diff --git a/.agents/skills/instrumentation/SKILL.md b/.agents/skills/instrumentation/SKILL.md index a6677d8a9..cb1193ff6 100644 --- a/.agents/skills/instrumentation/SKILL.md +++ b/.agents/skills/instrumentation/SKILL.md @@ -52,7 +52,7 @@ Map the change before editing: - Pass `Error` objects directly as `span.log({ error })` values. The SDK serializes errors correctly, so do not add local helpers that manually turn errors into message/stack strings unless an external API requires a non-`Error` representation. - Log only the useful surface. Prefer narrow, stable payloads over dumping full request/response objects; exclude redundant or overly large data when possible. - Automatic capture of inline multimodal bytes as Braintrust attachments must be opt-in. - Guard attachment creation and binary observation with `BRAINTRUST_CAPTURE_ATTACHMENTS`, and enable them only when its value is `1` or `true`. + Guard attachment creation and binary observation with `BRAINTRUST_AUTO_CAPTURE_ATTACHMENTS`, and enable them only when its value is `1` or `true`. Audio/video recordings may contain personal data and are separately controlled with their own explicit flags (`BRAINTRUST_CAPTURE_USER_AUDIO_ATTACHMENTS`, `BRAINTRUST_CAPTURE_AGENT_AUDIO_ATTACHMENTS`): enabling the shared flag for other media never records audio/video without these being enabled. Preserve useful text, metadata, metrics, and remote references when attachment capture is disabled, and omit inline bytes from logged payloads. - Segmentable streaming media must use the shared `configureInstrumentation({ streamingAttachmentBufferBytes })` setting instead of provider-specific chunk-size or time-based controls. diff --git a/.changeset/add-node-engine.md b/.changeset/add-node-engine.md new file mode 100644 index 000000000..10744b77a --- /dev/null +++ b/.changeset/add-node-engine.md @@ -0,0 +1,5 @@ +--- +"braintrust": major +--- + +feat(v4)!: Add Node Engine to package json diff --git a/.changeset/breezy-lines-draw.md b/.changeset/breezy-lines-draw.md new file mode 100644 index 000000000..c0bab996b --- /dev/null +++ b/.changeset/breezy-lines-draw.md @@ -0,0 +1,10 @@ +--- +"@braintrust/browser": major +"@braintrust/deepseek-harness": major +"@braintrust/otel": major +"@braintrust/templates-nunjucks-js": major +"@braintrust/temporal": major +"braintrust": major +--- + +build(sdk)!: Migrate to Vite+ and tree-shakeable ESM diff --git a/.changeset/chilly-ends-appear.md b/.changeset/chilly-ends-appear.md new file mode 100644 index 000000000..51e1b5719 --- /dev/null +++ b/.changeset/chilly-ends-appear.md @@ -0,0 +1,5 @@ +--- +"braintrust": patch +--- + +fix(instrumentation): Initialize tracing automatically in bundles diff --git a/.changeset/light-plants-return.md b/.changeset/light-plants-return.md new file mode 100644 index 000000000..3f1a80f76 --- /dev/null +++ b/.changeset/light-plants-return.md @@ -0,0 +1,6 @@ +--- +"@braintrust/deepseek-harness": major +"braintrust": major +--- + +feat!: Add `BRAINTRUST_CAPTURE_ATTACHMENTS` env var option and `captureAttachments` logger option for all integrations diff --git a/.changeset/reduce-api-to-a-minimum.md b/.changeset/reduce-api-to-a-minimum.md new file mode 100644 index 000000000..e91ea136d --- /dev/null +++ b/.changeset/reduce-api-to-a-minimum.md @@ -0,0 +1,8 @@ +--- +"braintrust": major +"@braintrust/otel": major +"@braintrust/browser": patch +"@braintrust/temporal": patch +--- + +ref!: Reduce API to a minimum that makes sense diff --git a/.changeset/remove-ai-sdk-v3.md b/.changeset/remove-ai-sdk-v3.md new file mode 100644 index 000000000..987c8fed2 --- /dev/null +++ b/.changeset/remove-ai-sdk-v3.md @@ -0,0 +1,5 @@ +--- +"braintrust": major +--- + +ref(ai-sdk)!: Remove AI SDK v3 support diff --git a/.changeset/remove-eve-pre-034.md b/.changeset/remove-eve-pre-034.md new file mode 100644 index 000000000..1e3034506 --- /dev/null +++ b/.changeset/remove-eve-pre-034.md @@ -0,0 +1,5 @@ +--- +"braintrust": major +--- + +ref(eve)!: Bump minimum eve version to `0.34.0` diff --git a/.changeset/remove-flue-pre-v1.md b/.changeset/remove-flue-pre-v1.md new file mode 100644 index 000000000..a029265f8 --- /dev/null +++ b/.changeset/remove-flue-pre-v1.md @@ -0,0 +1,5 @@ +--- +"braintrust": major +--- + +ref(v4)!: Remove flue pre-v1 support diff --git a/.changeset/remove-flush-async.md b/.changeset/remove-flush-async.md new file mode 100644 index 000000000..18903d6a9 --- /dev/null +++ b/.changeset/remove-flush-async.md @@ -0,0 +1,5 @@ +--- +"braintrust": major +--- + +ref!: Remove `flushAsync` option diff --git a/.changeset/remove-js-braintrust-cli.md b/.changeset/remove-js-braintrust-cli.md new file mode 100644 index 000000000..9dfca85f2 --- /dev/null +++ b/.changeset/remove-js-braintrust-cli.md @@ -0,0 +1,5 @@ +--- +"braintrust": major +--- + +ref(v4)!: Remove JS Braintrust CLI diff --git a/.changeset/remove-packaged-bt.md b/.changeset/remove-packaged-bt.md new file mode 100644 index 000000000..3f6d26903 --- /dev/null +++ b/.changeset/remove-packaged-bt.md @@ -0,0 +1,5 @@ +--- +"braintrust": major +--- + +ref!: Remove packaged `bt` binary from `braintrust` package diff --git a/.changeset/remove-plugins-public-api.md b/.changeset/remove-plugins-public-api.md new file mode 100644 index 000000000..21275cc2e --- /dev/null +++ b/.changeset/remove-plugins-public-api.md @@ -0,0 +1,5 @@ +--- +"braintrust": major +--- + +ref!: Remove plugins as part of public API diff --git a/.changeset/remove-zod-derived-types.md b/.changeset/remove-zod-derived-types.md new file mode 100644 index 000000000..c741bede7 --- /dev/null +++ b/.changeset/remove-zod-derived-types.md @@ -0,0 +1,5 @@ +--- +"braintrust": major +--- + +ref!: Remove Zod derived types from public SDK declarations diff --git a/.changeset/shy-parents-return.md b/.changeset/shy-parents-return.md new file mode 100644 index 000000000..e3047202f --- /dev/null +++ b/.changeset/shy-parents-return.md @@ -0,0 +1,6 @@ +--- +"braintrust": major +"@braintrust/deepseek-harness": major +--- + +refactor(sdk)!: Rename automatic attachment capture options diff --git a/.changeset/simplify-eval-types.md b/.changeset/simplify-eval-types.md new file mode 100644 index 000000000..2805f7950 --- /dev/null +++ b/.changeset/simplify-eval-types.md @@ -0,0 +1,5 @@ +--- +"braintrust": major +--- + +ref!: Simplify Eval type definitions diff --git a/.changeset/simplify-public-sdk-types.md b/.changeset/simplify-public-sdk-types.md new file mode 100644 index 000000000..0cff8f061 --- /dev/null +++ b/.changeset/simplify-public-sdk-types.md @@ -0,0 +1,5 @@ +--- +"braintrust": major +--- + +ref!: Don't make generated types part of public api diff --git a/.github/dependabot.yml b/.github/dependabot.yml index 262697300..7261c6abe 100644 --- a/.github/dependabot.yml +++ b/.github/dependabot.yml @@ -37,12 +37,12 @@ updates: - "eslint" - "@typescript-eslint/*" - "prettier" - - "turbo" - "typescript" - - "tsup" - "tsx" - "vitest" - "vite" + - "vite-plus" + - "@voidzero-dev/*" - "vite-tsconfig-paths" - "rollup" - "webpack" diff --git a/.github/scripts/npm-download-stats.ts b/.github/scripts/npm-download-stats.ts index 02fae240e..e95e8d75b 100644 --- a/.github/scripts/npm-download-stats.ts +++ b/.github/scripts/npm-download-stats.ts @@ -212,7 +212,7 @@ async function main() { method: "POST", headers: { "Content-Type": "application/json", - "DD-API-KEY": ddApiKey, + "DD-API-KEY": ddApiKey!, }, body: JSON.stringify(payload), }); diff --git a/.github/workflows/checks.yaml b/.github/workflows/checks.yaml index c3092fe02..91d746e97 100644 --- a/.github/workflows/checks.yaml +++ b/.github/workflows/checks.yaml @@ -40,8 +40,8 @@ jobs: uses: pnpm/action-setup@ea17c68df8912ef543352723c149a84f56e3d413 # v6.1.0 - name: Install dependencies run: pnpm install --frozen-lockfile - - name: Lint - run: pnpm run lint + - name: Static checks + run: pnpm run check check-typings: runs-on: ubuntu-latest @@ -88,9 +88,7 @@ jobs: fail-fast: false matrix: os: [ubuntu-latest, windows-2025-vs2026] - node-version: [20, 22, 24, 26] - env: - ACTIONS_ALLOW_USE_UNSECURE_NODE_VERSION: ${{ matrix.node-version == 20 && 'true' || '' }} + node-version: [22.18.0, 24, 26] steps: - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 - uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6.4.0 @@ -226,9 +224,7 @@ jobs: strategy: fail-fast: false matrix: - node-version: [20] - env: - ACTIONS_ALLOW_USE_UNSECURE_NODE_VERSION: "true" + node-version: [22.18.0] steps: - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 with: @@ -267,11 +263,13 @@ jobs: pnpm install --frozen-lockfile || { echo "Baseline install failed"; git checkout "$CURRENT_REF"; exit 0; } pnpm run build || { echo "Baseline build failed"; git checkout "$CURRENT_REF"; exit 0; } - mkdir -p "$BASELINE_DIR/js/dev" "$BASELINE_DIR/js/util" + mkdir -p "$BASELINE_DIR/js/util" cp -R js/dist "$BASELINE_DIR/js/" - cp -R js/dev/dist "$BASELINE_DIR/js/dev/" - cp -R js/util/dist "$BASELINE_DIR/js/util/" - echo "Copied js/dist, js/dev/dist, and js/util/dist to baseline" + cp js/package.json "$BASELINE_DIR/js/" + if [ -d js/util/dist ]; then + cp -R js/util/dist "$BASELINE_DIR/js/util/" + fi + echo "Copied package metadata and build outputs to baseline" git checkout "$CURRENT_REF" @@ -395,9 +393,7 @@ jobs: strategy: fail-fast: false matrix: - node-version: [20, 22, 24, 26] - env: - ACTIONS_ALLOW_USE_UNSECURE_NODE_VERSION: ${{ matrix.node-version == 20 && 'true' || '' }} + node-version: [22.18.0, 24, 26] steps: - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 - uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6.4.0 @@ -423,9 +419,7 @@ jobs: strategy: fail-fast: false matrix: - node-version: [20, 22, 24, 26] - env: - ACTIONS_ALLOW_USE_UNSECURE_NODE_VERSION: ${{ matrix.node-version == 20 && 'true' || '' }} + node-version: [22.18.0, 24, 26] steps: - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 - uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6.4.0 @@ -451,9 +445,7 @@ jobs: strategy: fail-fast: false matrix: - node-version: [20, 22, 24, 26] - env: - ACTIONS_ALLOW_USE_UNSECURE_NODE_VERSION: ${{ matrix.node-version == 20 && 'true' || '' }} + node-version: [22.18.0, 24, 26] steps: - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 - uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6.4.0 @@ -479,10 +471,8 @@ jobs: strategy: fail-fast: false matrix: - node-version: [20, 22, 24, 26] + node-version: [22.18.0, 24, 26] test-dir: [otel-v1, otel-v2] - env: - ACTIONS_ALLOW_USE_UNSECURE_NODE_VERSION: ${{ matrix.node-version == 20 && 'true' || '' }} steps: - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 - name: Set up Docker Buildx @@ -532,17 +522,6 @@ jobs: - name: Run hermetic tests working-directory: ./js run: pnpm run test:checks - - name: Run zod v3 tests - if: matrix.zod-version == '3.25.34' - working-directory: ./js - run: | - pnpm test:zod-v3 - - name: Run zod v4 tests - if: matrix.zod-version == '4.2.1' - working-directory: ./js - run: | - pnpm test:zod-v4 - checks-passed: name: checks-passed needs: diff --git a/.gitignore b/.gitignore index bf65941bd..bfe9f335e 100644 --- a/.gitignore +++ b/.gitignore @@ -24,3 +24,7 @@ tsconfig.tsbuildinfo # Auto-generated by @sentry/dotagents — do not commit these files. agents.lock .agents/.gitignore + +# Local bundle reports and before/after build artifacts. +.context/ +.vite/ diff --git a/.prettierignore b/.prettierignore deleted file mode 100644 index 8b8367a3c..000000000 --- a/.prettierignore +++ /dev/null @@ -1,17 +0,0 @@ -# Patch files use specific whitespace formatting required by unified diff format -patches/**/*.patch - -# E2E test snapshots -e2e/**/__snapshots__/**/* - -# Lock files -pnpm-lock.yaml -deno.lock - -# Template files with special formatting -js/scripts/openapi_zod_client_output_template.hbs - -# Generated dotagents skill content -.agents/skills/dotagents/ - -.context diff --git a/.prettierrc b/.prettierrc deleted file mode 100644 index f3bcd4c90..000000000 --- a/.prettierrc +++ /dev/null @@ -1,4 +0,0 @@ -{ - "singleQuote": false, - "trailingComma": "all" -} diff --git a/AGENTS.md b/AGENTS.md index e33462bd4..687f12491 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -25,6 +25,21 @@ mise install # Install toolchain and dependencies pnpm run build # Build all workspace packages (from repo root) ``` +## Public TypeScript APIs + +Do not derive SDK-owned public TypeScript types from Zod schemas (for example, +with `z.infer`, `z.input`, `z.output`, or equivalent schema-derived aliases). +Define public API types explicitly with interfaces, type aliases, or generated +plain types. Generic APIs may still infer types from caller-provided schemas. +When exporting a runtime validator, give it a compact public type such as +`z.ZodType` and test that the validator and public type stay in sync. + +Zod-derived public declarations can expand into large schema implementation +graphs. Those declarations are expensive for downstream TypeScript consumers to +parse, instantiate, and type-check, increasing compile time, declaration size, +and memory usage. They also expose validation-library implementation details as +part of the SDK's API surface. + ## Public API Keep public exports minimal. Generally, export only the requested runtime APIs and do not export types unless explicitly requested. @@ -55,6 +70,8 @@ Span names should generally remain stable across calls and versions. Do not incl Uses Vitest. Prefer running the **narrowest relevant test** rather than the full suite. +Generally, do not add tests for build or linting code or their configuration. Validate tooling changes by running the relevant build or lint commands instead. + **From `js/` directory:** ```bash diff --git a/README.md b/README.md index 0f7afe649..f72e5da85 100644 --- a/README.md +++ b/README.md @@ -34,27 +34,25 @@ Eval("Say Hi Bot", { }); ``` -Run it: +Install the `bt` CLI separately using the standalone installation instructions in the [CLI quickstart](https://www.braintrust.dev/docs/reference/cli/quickstart), then run it: ```bash -BRAINTRUST_API_KEY= npx braintrust eval tutorial.eval.ts +BRAINTRUST_API_KEY= bt eval tutorial.eval.ts ``` ## Packages -| Package | Purpose | npm | Docs | -| --------------------------- | ------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------ | ---------------------------------------------------------------------------------- | -| `braintrust` | Core JavaScript/TypeScript SDK for logging, tracing, evals, and CLI. | [![npm: braintrust](https://img.shields.io/npm/v/braintrust.svg)](https://www.npmjs.com/package/braintrust) | [js/README.md](js/README.md) | -| `@braintrust/browser` | Browser-focused SDK integration with AsyncLocalStorage polyfill support. | [![npm: @braintrust/browser](https://img.shields.io/npm/v/%40braintrust%2Fbrowser.svg)](https://www.npmjs.com/package/@braintrust/browser) | [integrations/browser-js/README.md](integrations/browser-js/README.md) | -| `@braintrust/langchain-js` | Deprecated; use the LangChain integration in `braintrust` instead. | [![npm: @braintrust/langchain-js](https://img.shields.io/npm/v/%40braintrust%2Flangchain-js.svg)](https://www.npmjs.com/package/@braintrust/langchain-js) | [integrations/langchain-js/README.md](integrations/langchain-js/README.md) | -| `@braintrust/openai-agents` | Deprecated; use the OpenAI Agents integration in `braintrust` instead. | [![npm: @braintrust/openai-agents](https://img.shields.io/npm/v/%40braintrust%2Fopenai-agents.svg)](https://www.npmjs.com/package/@braintrust/openai-agents) | [integrations/openai-agents-js/README.md](integrations/openai-agents-js/README.md) | -| `@braintrust/otel` | OpenTelemetry span processor and compatibility helpers for Braintrust tracing. | [![npm: @braintrust/otel](https://img.shields.io/npm/v/%40braintrust%2Fotel.svg)](https://www.npmjs.com/package/@braintrust/otel) | [integrations/otel-js/README.md](integrations/otel-js/README.md) | -| `@braintrust/temporal` | Temporal client/worker plugin and workflow interceptors for Braintrust tracing. | [![npm: @braintrust/temporal](https://img.shields.io/npm/v/%40braintrust%2Ftemporal.svg)](https://www.npmjs.com/package/@braintrust/temporal) | [integrations/temporal-js/README.md](integrations/temporal-js/README.md) | +| Package | Purpose | npm | Docs | +| ---------------------- | ------------------------------------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------ | +| `braintrust` | Core JavaScript/TypeScript SDK for logging, tracing, and evals. | [![npm: braintrust](https://img.shields.io/npm/v/braintrust.svg)](https://www.npmjs.com/package/braintrust) | [js/README.md](js/README.md) | +| `@braintrust/browser` | Browser-focused SDK integration with AsyncLocalStorage polyfill support. | [![npm: @braintrust/browser](https://img.shields.io/npm/v/%40braintrust%2Fbrowser.svg)](https://www.npmjs.com/package/@braintrust/browser) | [integrations/browser-js/README.md](integrations/browser-js/README.md) | +| `@braintrust/otel` | OpenTelemetry span processor and compatibility helpers for Braintrust tracing. | [![npm: @braintrust/otel](https://img.shields.io/npm/v/%40braintrust%2Fotel.svg)](https://www.npmjs.com/package/@braintrust/otel) | [integrations/otel-js/README.md](integrations/otel-js/README.md) | +| `@braintrust/temporal` | Temporal client/worker plugin and workflow interceptors for Braintrust tracing. | [![npm: @braintrust/temporal](https://img.shields.io/npm/v/%40braintrust%2Ftemporal.svg)](https://www.npmjs.com/package/@braintrust/temporal) | [integrations/temporal-js/README.md](integrations/temporal-js/README.md) | ## CLI `bt` -The `bt` cli is installed alongside `braintrust`, use it with `npx bt` or `pnpm exec bt`. -See the [documentation](https://www.braintrust.dev/docs/reference/cli/quickstart). +The `bt` CLI is installed separately from `braintrust`. +See the standalone installation instructions in the [CLI quickstart](https://www.braintrust.dev/docs/reference/cli/quickstart). ## Documentation diff --git a/dev-packages/seinfeld/package.json b/dev-packages/seinfeld/package.json index 4ec7b366f..0bcdb6b9d 100644 --- a/dev-packages/seinfeld/package.json +++ b/dev-packages/seinfeld/package.json @@ -5,25 +5,24 @@ "type": "module", "license": "MIT", "engines": { - "node": ">=18" + "node": ">=22.18.0" }, - "main": "./dist/index.cjs", + "main": "./dist/index.js", "module": "./dist/index.js", "types": "./dist/index.d.ts", "exports": { ".": { "types": "./dist/index.d.ts", - "import": "./dist/index.js", - "require": "./dist/index.cjs" + "default": "./dist/index.js" } }, "files": [ "dist" ], "scripts": { - "build": "tsup", + "build": "vp run compile", "check:typings": "tsc --noEmit", - "dev": "tsup --watch", + "dev": "vp pack --watch", "typecheck": "tsc --noEmit" }, "dependencies": { @@ -31,7 +30,8 @@ }, "devDependencies": { "@types/node": "^22.10.0", - "tsup": "^8.5.1", - "typescript": "^5.7.2" - } + "typescript": "^5.7.2", + "vite-plus": "catalog:" + }, + "sideEffects": false } diff --git a/dev-packages/seinfeld/src/format/v1.ts b/dev-packages/seinfeld/src/format/v1.ts index 558ddcfb5..a736337e3 100644 --- a/dev-packages/seinfeld/src/format/v1.ts +++ b/dev-packages/seinfeld/src/format/v1.ts @@ -1,4 +1,5 @@ import { z } from "zod"; +import type { CassetteFile } from "../cassette"; const bodyPayloadSchema = z.discriminatedUnion("kind", [ z.object({ kind: z.literal("empty") }), @@ -53,7 +54,7 @@ const cassetteMetaSchema = z }) .passthrough(); -export const cassetteSchema = z.object({ +export const cassetteSchema: z.ZodType = z.object({ version: z.any(), // TODO(luca): Remove after cassettes no longer have this field meta: cassetteMetaSchema.optional(), entries: z.array(cassetteEntrySchema), diff --git a/dev-packages/seinfeld/src/internal/well-known-headers.ts b/dev-packages/seinfeld/src/internal/well-known-headers.ts index 425e426a2..373185a40 100644 --- a/dev-packages/seinfeld/src/internal/well-known-headers.ts +++ b/dev-packages/seinfeld/src/internal/well-known-headers.ts @@ -3,7 +3,7 @@ * Keeping them in one place prevents the two subsystems from drifting. */ -export const TRANSPORT_HEADERS = [ +export const TRANSPORT_HEADERS: string[] = [ "content-encoding", "transfer-encoding", "connection", @@ -11,7 +11,7 @@ export const TRANSPORT_HEADERS = [ ]; /** Auth/session headers — stripped during matching and masked during redaction. */ -export const AUTH_HEADERS = [ +export const AUTH_HEADERS: string[] = [ "authorization", "api-key", "x-api-key", @@ -22,10 +22,10 @@ export const AUTH_HEADERS = [ "proxy-authorization", ]; -export const RATE_LIMIT_HEADERS = [ +export const RATE_LIMIT_HEADERS: Array = [ /^x-ratelimit-/i, /^x-rate-limit-/i, "retry-after", -] as Array; +]; -export const FINGERPRINT_HEADERS = ["user-agent"]; +export const FINGERPRINT_HEADERS: string[] = ["user-agent"]; diff --git a/dev-packages/seinfeld/tsconfig.json b/dev-packages/seinfeld/tsconfig.json index 6cb2a6d85..7c344b276 100644 --- a/dev-packages/seinfeld/tsconfig.json +++ b/dev-packages/seinfeld/tsconfig.json @@ -21,6 +21,6 @@ "rootDir": "./", "types": ["node"] }, - "include": ["src/**/*", "tsup.config.ts", "eslint.config.js"], + "include": ["src/**/*"], "exclude": ["node_modules", "dist", "examples"] } diff --git a/dev-packages/seinfeld/tsup.config.ts b/dev-packages/seinfeld/tsup.config.ts deleted file mode 100644 index 41c6c169c..000000000 --- a/dev-packages/seinfeld/tsup.config.ts +++ /dev/null @@ -1,14 +0,0 @@ -import { defineConfig } from "tsup"; - -export default defineConfig({ - entry: { - index: "src/index.ts", - }, - format: ["esm", "cjs"], - dts: true, - clean: true, - sourcemap: true, - target: "node18", - splitting: false, - treeshake: true, -}); diff --git a/dev-packages/seinfeld/vite.config.ts b/dev-packages/seinfeld/vite.config.ts new file mode 100644 index 000000000..0a535fb72 --- /dev/null +++ b/dev-packages/seinfeld/vite.config.ts @@ -0,0 +1,23 @@ +import { defineConfig } from "vite-plus"; + +export default defineConfig({ + run: { + tasks: { + compile: { + command: "vp pack", + dependsOn: [ + { task: "compile", from: ["dependencies", "devDependencies"] }, + ], + cache: { + input: [{ auto: true }, "!dist/**"], + untrackedEnv: ["SHELL", "BASH_ENV"], + output: ["dist/**"], + }, + }, + }, + }, + pack: { + dts: { generator: "oxc" }, + fixedExtension: false, + }, +}); diff --git a/e2e/.gitignore b/e2e/.gitignore new file mode 100644 index 000000000..7a6c09645 --- /dev/null +++ b/e2e/.gitignore @@ -0,0 +1 @@ +.vitest/ diff --git a/e2e/README.md b/e2e/README.md index de605a69b..a4e7aca73 100644 --- a/e2e/README.md +++ b/e2e/README.md @@ -23,7 +23,7 @@ e2e/ | |- scenario.test.ts # Colocated Vitest suite | |- package.json # Optional slim scenario-local deps | `- __snapshots__/ # Colocated snapshots -`- vitest.config.mts +`- vite.config.ts ``` Any extra files needed only by one scenario stay in that scenario folder. Anything reused by multiple scenarios belongs in `e2e/helpers/`. @@ -80,7 +80,7 @@ Wrapper scenarios often create a root span with `testRunId` metadata and then le - Find the scenario root span first. - Scope raw payload snapshots by `root_span_id` using `payloadRowsForRootSpan(...)`. - Prefer normalized span-tree snapshots from `matchSpanTreeSnapshot(...)`. The `.json` sibling is the structural contract, and the `.txt` sibling is the ASCII tree for review; both are asserted and should be updated together. -- If the wrapper has an explicit support matrix, reuse one shared test across version-specific scenario entries instead of duplicating the assertions. The AI SDK wrapper scenario uses this for supported v3-v6 package combinations. +- If the wrapper has an explicit support matrix, reuse one shared test across version-specific scenario entries instead of duplicating the assertions. The AI SDK wrapper scenario uses this for its supported package combinations. ### Runner-wrapper scenario pattern @@ -88,7 +88,7 @@ Some wrappers execute inside a nested test runner rather than a single SDK call. - Keep the outer e2e suite in `scenario.test.ts` and the spawned runner entrypoint in `scenario.ts`. - Put nested runner source in files like `runner.case.ts` or `runner.case.mjs`. -- Do not name nested runner files `*.test.ts`, because the outer `e2e/vitest.config.mts` includes `scenarios/**/*.test.ts` and will try to execute them directly. +- Do not name nested runner files `*.test.ts`, because the outer `e2e/vite.config.ts` includes `scenarios/**/*.test.ts` and will try to execute them directly. - Tag every traced test/eval with `metadata.testRunId` so the outer assertions can isolate rows across multiple trace roots with `payloadRowsForTestRunId(...)`. - If a nested runner needs its own test discovery rules, keep that config local to the scenario folder so the shared e2e config stays unchanged. @@ -201,7 +201,7 @@ Only whole scenarios can be selected; test filters, file paths, sharding, and sn These scenarios have cassette wiring in place and will use cassettes once they're recorded: -`anthropic-bedrock-instrumentation`, `anthropic-instrumentation`, `openai-instrumentation`, `openai-codex-instrumentation`, `ai-sdk-instrumentation`, `ai-sdk-otel-export`, `claude-agent-sdk-instrumentation`, `cohere-instrumentation`, `cursor-sdk-instrumentation`, `github-copilot-instrumentation`, `google-adk-instrumentation`, `google-genai-instrumentation`, `groq-instrumentation`, `huggingface-instrumentation`, `mistral-instrumentation`, `ollama-instrumentation`, `openrouter-agent-instrumentation`, `openrouter-instrumentation`, `typesafe-instrumentation`, `wrap-langchain-js-traces`. +`anthropic-bedrock-instrumentation`, `anthropic-instrumentation`, `openai-instrumentation`, `openai-codex-instrumentation`, `ai-sdk-instrumentation`, `ai-sdk-otel-export`, `claude-agent-sdk-instrumentation`, `cohere-instrumentation`, `cursor-sdk-instrumentation`, `github-copilot-instrumentation`, `google-adk-instrumentation`, `google-genai-instrumentation`, `groq-instrumentation`, `huggingface-instrumentation`, `mistral-instrumentation`, `ollama-instrumentation`, `openrouter-agent-instrumentation`, `openrouter-instrumentation`, `typesafe-instrumentation`. ### Cassette format diff --git a/e2e/config/pr-comment-scenarios.json b/e2e/config/pr-comment-scenarios.json index 5f2101f01..9a186ee5b 100644 --- a/e2e/config/pr-comment-scenarios.json +++ b/e2e/config/pr-comment-scenarios.json @@ -681,14 +681,6 @@ "label": "AI SDK Instrumentation", "metadataScenario": "ai-sdk-instrumentation", "variants": [ - { - "variantKey": "ai-sdk-v3", - "label": "v3 pinned" - }, - { - "variantKey": "ai-sdk-v3-latest", - "label": "v3 latest" - }, { "variantKey": "ai-sdk-v4", "label": "v4 pinned" @@ -773,14 +765,6 @@ "label": "Flue Instrumentation", "metadataScenario": "flue-instrumentation", "variants": [ - { - "variantKey": "flue-v0-8-0", - "label": "v0.8 pinned" - }, - { - "variantKey": "flue-v0-8-latest", - "label": "v0.8 latest" - }, { "variantKey": "flue-v1-0-0-beta-3", "label": "v1 pinned" @@ -804,10 +788,6 @@ "label": "Eve Instrumentation", "metadataScenario": "eve-instrumentation", "variants": [ - { - "variantKey": "eve-v0", - "label": "v0 pinned" - }, { "variantKey": "eve-v0-provider", "label": "v0 provider minimum" @@ -933,6 +913,14 @@ { "variantKey": "vitest-v4-latest", "label": "v4 latest" + }, + { + "variantKey": "vitest-v5", + "label": "v5 pinned" + }, + { + "variantKey": "vitest-v5-latest", + "label": "v5 latest" } ], "evals": [ @@ -944,21 +932,6 @@ } ] }, - { - "scenarioDirName": "wrap-langchain-js-traces", - "label": "LangChain JS Wrapper Traces", - "metadataScenario": "wrap-langchain-js-traces", - "variants": [ - { - "variantKey": "wrap-langchain-js-traces-v1", - "label": "v1 pinned" - }, - { - "variantKey": "wrap-langchain-js-traces-v1-latest", - "label": "v1 latest" - } - ] - }, { "scenarioDirName": "anthropic-vertex-instrumentation", "label": "Anthropic Vertex Instrumentation", diff --git a/e2e/helpers/attachment-capture-assertions.ts b/e2e/helpers/attachment-capture-assertions.ts new file mode 100644 index 000000000..69b2651db --- /dev/null +++ b/e2e/helpers/attachment-capture-assertions.ts @@ -0,0 +1,67 @@ +import { expect, test } from "vite-plus/test"; +import { withScenarioHarness } from "./scenario-harness"; +import { findLatestSpan } from "./trace-selectors"; +import { captureCases } from "./attachment-capture-scenario.mjs"; + +export function defineAttachmentCaptureTests(options: { + scenarioDir: string; + originalScenarioDir: string; + provider: "openai" | "anthropic"; + packageName: string; + variantKey: string; +}) { + for (const mode of ["wrapped", "auto"]) { + test(`attachment capture policies (${options.variantKey}, ${mode})`, async () => { + await withScenarioHarness(async (harness) => { + await harness.runNodeScenarioDir({ + scenarioDir: options.scenarioDir, + entry: "scenario.capture.mjs", + nodeArgs: mode === "auto" ? ["--import", "braintrust/hook.mjs"] : [], + env: { + CAPTURE_PACKAGE_NAME: options.packageName, + CAPTURE_WRAPPED: String(mode === "wrapped"), + BRAINTRUST_AUTO_CAPTURE_ATTACHMENTS: "", + }, + runContext: { + variantKey: options.variantKey, + originalScenarioDir: options.originalScenarioDir, + // The same Chat Completions request works on all supported SDK majors. + // Its real-provider recording lives in the existing v6 media cassette. + ...(options.provider === "openai" + ? { cassette: { variantKey: "openai-v6" } } + : {}), + }, + timeoutMs: 180_000, + }); + const events = harness.events(); + for (const testCase of captureCases) { + const root = findLatestSpan( + events, + `attachment-capture-${testCase.name}`, + ); + expect(root).toBeDefined(); + const children = events.filter( + (event) => + event.row.root_span_id === root?.row.root_span_id && + event.span.id !== root?.span.id, + ); + expect(children.length).toBeGreaterThan(0); + const payload = JSON.stringify(children); + if (testCase.enabled) + expect(payload).toContain("braintrust_attachment"); + else { + expect(payload).not.toContain("braintrust_attachment"); + expect(payload).not.toContain('"file_data":'); + expect(payload).not.toContain('"b64_json":'); + expect(payload).not.toContain('"data":'); + expect(payload).not.toContain("data:image/png;base64,"); + } + expect(payload).toContain("completion_tokens"); + expect(children.some((event) => event.row.output !== undefined)).toBe( + true, + ); + } + }); + }, 240_000); + } +} diff --git a/e2e/helpers/attachment-capture-scenario.mjs b/e2e/helpers/attachment-capture-scenario.mjs new file mode 100644 index 000000000..f2ae7e2a2 --- /dev/null +++ b/e2e/helpers/attachment-capture-scenario.mjs @@ -0,0 +1,125 @@ +import assert from "node:assert/strict"; +import { readFile } from "node:fs/promises"; +import { initLogger, wrapOpenAI, wrapAnthropic } from "braintrust"; +import { MINIMAL_PNG_BASE64 } from "./media-fixtures.mjs"; +import { scopedName } from "./provider-runtime.mjs"; + +export const captureCases = [ + { name: "default", env: "", enabled: false }, + { name: "local-default", env: "", global: true, local: true, enabled: false }, + { + name: "local-enabled", + env: "false", + global: false, + local: true, + option: true, + enabled: true, + }, + { + name: "local-disabled", + env: "true", + global: true, + local: true, + option: false, + enabled: false, + }, + { + name: "local-environment", + env: "true", + global: false, + local: true, + enabled: true, + }, + { name: "global-enabled", env: "false", option: true, enabled: true }, + { name: "global-disabled", env: "true", option: false, enabled: false }, +]; + +export async function runAttachmentCaptureScenario(provider, scenarioUrl, sdk) { + const wrapped = process.env.CAPTURE_WRAPPED === "true"; + let request; + if (provider === "anthropic") { + const raw = new sdk.default({ + apiKey: process.env.ANTHROPIC_API_KEY, + baseURL: process.env.ANTHROPIC_BASE_URL, + }); + const client = wrapped ? wrapAnthropic(raw) : raw; + const data = ( + await readFile(new URL("./test-image.png", scenarioUrl)) + ).toString("base64"); + request = async () => { + const result = await client.messages.create({ + model: "claude-haiku-4-5", + max_tokens: 32, + temperature: 0, + messages: [ + { + role: "user", + content: [ + { + type: "text", + text: "Describe the attached image in one short sentence.", + }, + { + type: "image", + source: { type: "base64", media_type: "image/png", data }, + }, + ], + }, + ], + }); + assert.ok(result.content.length > 0); + }; + } else if (provider === "openai") { + const raw = new sdk.default({ + apiKey: process.env.OPENAI_API_KEY, + baseURL: process.env.OPENAI_BASE_URL, + }); + const client = wrapped ? wrapOpenAI(raw) : raw; + request = async () => { + const result = await client.chat.completions.create({ + model: "gpt-4o-mini-2024-07-18", + messages: [ + { + role: "user", + content: [ + { + type: "text", + text: "Describe this image in three words or fewer.", + }, + { + type: "image_url", + image_url: { + url: `data:image/png;base64,${MINIMAL_PNG_BASE64}`, + }, + }, + ], + }, + ], + max_tokens: 24, + temperature: 0, + }); + assert.ok(result.choices[0].message.content); + }; + } + const projectName = scopedName("tmp-luca-attachment-capture"); + for (const testCase of captureCases) { + process.env.BRAINTRUST_AUTO_CAPTURE_ATTACHMENTS = testCase.env; + if (testCase.local) + initLogger({ projectName, autoCaptureAttachments: testCase.global }); + const logger = initLogger({ + projectName, + setCurrent: !testCase.local, + autoCaptureAttachments: testCase.option, + }); + await logger.traced(request, { + name: `attachment-capture-${testCase.name}`, + event: { + metadata: { + scenario: `${provider}-instrumentation`, + testRunId: process.env.BRAINTRUST_E2E_RUN_ID, + }, + }, + }); + await logger.flush(); + } +} diff --git a/e2e/helpers/cassette-audit.test.ts b/e2e/helpers/cassette-audit.test.ts index 32c84e260..c90e85fb1 100644 --- a/e2e/helpers/cassette-audit.test.ts +++ b/e2e/helpers/cassette-audit.test.ts @@ -11,7 +11,7 @@ import { tmpdir } from "node:os"; import path from "node:path"; import { fileURLToPath } from "node:url"; import { promisify } from "node:util"; -import { afterEach, beforeEach, expect, it, vi } from "vitest"; +import { afterEach, beforeEach, expect, it, vi } from "vite-plus/test"; import { auditCassettes } from "../scripts/cassette-audit.mjs"; import { withScenarioHarness } from "./scenario-harness"; diff --git a/e2e/helpers/describe-variant.ts b/e2e/helpers/describe-variant.ts index aeb115946..d807ebfa5 100644 --- a/e2e/helpers/describe-variant.ts +++ b/e2e/helpers/describe-variant.ts @@ -1,4 +1,4 @@ -import { describe } from "vitest"; +import { describe } from "vite-plus/test"; const cassetteMode = process.env.BRAINTRUST_E2E_CASSETTE_MODE; const isRecordingMode = @@ -8,11 +8,13 @@ const isRecordingMode = * Describe block for one version variant inside `describe.concurrent(...)`. * * During replay the block inherits the parent's concurrency, so variants and - * their wrapped/auto-hook suites run in parallel. `describe.sequential` here + * their wrapped/auto-hook suites run in parallel. Disabling concurrency here * would serialize every variant in the file. Recording stays sequential because * suites in the same variant can write to one shared cassette. * * Concurrent tests must use the test-context `expect` for file snapshots, e.g. * `matchSpanTreeSnapshot(events, path, { snapshotExpect: expect })`. */ -export const describeVariant = isRecordingMode ? describe.sequential : describe; +export function describeVariant(name: string, factory: () => void): void { + describe(name, isRecordingMode ? { concurrent: false } : {}, factory); +} diff --git a/e2e/helpers/file-snapshot.ts b/e2e/helpers/file-snapshot.ts index ce778ad80..a170fc8f7 100644 --- a/e2e/helpers/file-snapshot.ts +++ b/e2e/helpers/file-snapshot.ts @@ -1,6 +1,6 @@ import { dirname, join } from "node:path"; import { fileURLToPath } from "node:url"; -import { expect, type ExpectStatic } from "vitest"; +import { expect, type ExpectStatic } from "vite-plus/test"; import { normalizeForSnapshot, type Json } from "./normalize"; function sortJsonKeys(value: Json): Json { diff --git a/e2e/helpers/mock-braintrust-server.test.ts b/e2e/helpers/mock-braintrust-server.test.ts index cadf20a7d..516d08493 100644 --- a/e2e/helpers/mock-braintrust-server.test.ts +++ b/e2e/helpers/mock-braintrust-server.test.ts @@ -1,7 +1,7 @@ import { createServer } from "node:http"; import type { AddressInfo } from "node:net"; import { setTimeout } from "node:timers/promises"; -import { describe, expect, it } from "vitest"; +import { describe, expect, it } from "vite-plus/test"; import { startMockBraintrustServer } from "./mock-braintrust-server"; describe("production forwarding", () => { diff --git a/e2e/helpers/normalize.test.ts b/e2e/helpers/normalize.test.ts index fe645aa72..169733dba 100644 --- a/e2e/helpers/normalize.test.ts +++ b/e2e/helpers/normalize.test.ts @@ -1,7 +1,11 @@ -import { describe, expect, test } from "vitest"; +import { describe, expect, test } from "vite-plus/test"; import { normalizeForSnapshot } from "./normalize"; describe("normalizeForSnapshot", () => { + test("preserves large inline media without scanning it as a repository path", () => { + const media = `data:image/png;base64,${"a".repeat(3_000_000)}`; + expect(normalizeForSnapshot(media)).toBe(media); + }); test.each([0, 0.1, 1.2])( "normalizes tool wall time of %s seconds", (seconds) => { diff --git a/e2e/helpers/normalize.ts b/e2e/helpers/normalize.ts index ec994ded5..5829630dd 100644 --- a/e2e/helpers/normalize.ts +++ b/e2e/helpers/normalize.ts @@ -123,12 +123,12 @@ const CLAUDE_AGENT_OUTPUT_FILE_REGEX = const PROVIDER_HELPER_CALLER_REGEX = /^\/e2e\/helpers\/.+-scenario\.mjs$/; const ANTHROPIC_MESSAGE_STREAM_PATH_REGEX = /([/\\]node_modules[/\\]\.pnpm[/\\]@anthropic-ai\+sdk@[^/\\\s)]+[/\\]node_modules[/\\]@anthropic-ai[/\\]sdk[/\\])(?:src[/\\]lib[/\\]MessageStream\.ts|lib[/\\]MessageStream\.js)/g; -// tsup's `splitting: true` for our own `dist/` emits content-hashed chunk +// SDK builds emit content-hashed chunk // files (e.g. `/js/dist/chunk-7DWPOXBX.mjs`) whose names change any // time the bundle graph changes. Normalize them to a stable placeholder so // stack traces in error snapshots don't churn on unrelated bundle splits. const SDK_CHUNK_PATH_REGEX = - /(\/js\/dist\/)chunk-[A-Z0-9]+(\.(?:c?js|cjs|mjs))/g; + /(\/js\/dist\/(?:node\/|browser\/|workerd\/|edge-light\/)?)(?:chunk-[A-Z0-9]+|[\w-]+-[\w-]{8})(\.(?:c?js|cjs|mjs))/g; const ANTHROPIC_PNPM_VERSION_REGEX = /([/\\]\.pnpm[/\\]@anthropic-ai\+sdk@)[^/\\\s)]+/g; const LOCALHOST_HTTP_URL_SUBSTRING_REGEX = @@ -196,13 +196,16 @@ function normalizeStackLikeString(value: string): string { "/claude-agent/tasks/", ); - normalized = normalized.replace( - STACK_FRAME_REPO_PATH_REGEX, - (_, suffix: string) => `${suffix.replace(/\\/g, "/")}:0:0`, - ); - normalized = normalized.replace(REPO_PATH_REGEX, (_, suffix: string) => { - return `${suffix.replace(/\\/g, "/")}`; - }); + // Avoid quadratic path matching on large inline media strings. + if (normalized.includes("braintrust-sdk-javascript")) { + normalized = normalized.replace( + STACK_FRAME_REPO_PATH_REGEX, + (_, suffix: string) => `${suffix.replace(/\\/g, "/")}:0:0`, + ); + normalized = normalized.replace(REPO_PATH_REGEX, (_, suffix: string) => { + return `${suffix.replace(/\\/g, "/")}`; + }); + } normalized = normalized.replace(SDK_CHUNK_PATH_REGEX, "$1index$2"); normalized = normalized.replace( /((?:\/(?:e2e|js)\/[^:\s)\n]+)):\d+:\d+/g, diff --git a/e2e/helpers/pr-e2e-links.test.ts b/e2e/helpers/pr-e2e-links.test.ts index c99c63fcd..ba3988679 100644 --- a/e2e/helpers/pr-e2e-links.test.ts +++ b/e2e/helpers/pr-e2e-links.test.ts @@ -4,7 +4,7 @@ import { tmpdir } from "node:os"; import path from "node:path"; import { promisify } from "node:util"; import { fileURLToPath } from "node:url"; -import { expect, it } from "vitest"; +import { expect, it } from "vite-plus/test"; it("links published runs while preserving legacy records and excluding local-only runs", async () => { const dir = await mkdtemp(path.join(tmpdir(), "braintrust-e2e-links-")); diff --git a/e2e/helpers/prod-forwarding.ts b/e2e/helpers/prod-forwarding.ts index 522aedbe6..37f08d14f 100644 --- a/e2e/helpers/prod-forwarding.ts +++ b/e2e/helpers/prod-forwarding.ts @@ -31,7 +31,6 @@ export async function initializeProdForwarding(): Promise { const logger = initLogger({ apiKey, appUrl: process.env.BRAINTRUST_APP_URL, - asyncFlush: false, forceLogin: true, projectName, }); diff --git a/e2e/helpers/provider-runtime.mjs b/e2e/helpers/provider-runtime.mjs index e14119a20..391a022d3 100644 --- a/e2e/helpers/provider-runtime.mjs +++ b/e2e/helpers/provider-runtime.mjs @@ -1,4 +1,10 @@ -import { initLogger, startSpan, withCurrent } from "braintrust"; +import { + initLogger, + currentLogger, + injectTraceContext, + startSpan, + withCurrent, +} from "braintrust"; import { promises as fs } from "node:fs"; import { createRequire } from "node:module"; import path from "node:path"; @@ -27,8 +33,17 @@ export function scopedName(base, testRunId = getTestRunId()) { return `${base}-${suffix}`; } -export async function runOperation(name, operation, callback) { - const span = startSpan({ +export async function runOperation(name, operation, callback, options = {}) { + const logger = + options.autoCaptureAttachments === undefined + ? undefined + : initLogger({ + projectId: await currentLogger().id, + projectName: (await currentLogger().project).name, + setCurrent: false, + autoCaptureAttachments: options.autoCaptureAttachments, + }); + const args = { name, event: { metadata: { @@ -36,7 +51,10 @@ export async function runOperation(name, operation, callback) { testRunId: getTestRunId(), }, }, - }); + }; + const span = logger + ? logger.startSpan({ ...args, parent: injectTraceContext() }) + : startSpan(args); await withCurrent(span, callback); span.end(); diff --git a/e2e/helpers/span-tree.test.ts b/e2e/helpers/span-tree.test.ts index c4ec23ead..d683e2fcf 100644 --- a/e2e/helpers/span-tree.test.ts +++ b/e2e/helpers/span-tree.test.ts @@ -1,4 +1,4 @@ -import { describe, expect, test } from "vitest"; +import { describe, expect, test } from "vite-plus/test"; import type { CapturedLogEvent } from "./mock-braintrust-server"; import { formatSpanTreeJsonSnapshot } from "./span-tree"; diff --git a/e2e/helpers/span-tree.ts b/e2e/helpers/span-tree.ts index c99d24d18..bcb68b3c7 100644 --- a/e2e/helpers/span-tree.ts +++ b/e2e/helpers/span-tree.ts @@ -1,4 +1,4 @@ -import type { ExpectStatic } from "vitest"; +import type { ExpectStatic } from "vite-plus/test"; import { matchFileSnapshot } from "./file-snapshot"; import type { CapturedLogEvent } from "./mock-braintrust-server"; import { diff --git a/e2e/package.json b/e2e/package.json index 3fba053c1..164dc4065 100644 --- a/e2e/package.json +++ b/e2e/package.json @@ -9,9 +9,9 @@ "test:e2e:cassettes": "node ./scripts/run-e2e-tests.mjs --check-cassettes" }, "devDependencies": { + "@braintrust/browser": "workspace:^", "@braintrust/audio": "workspace:^", "@braintrust/deepseek-harness": "workspace:^", - "@braintrust/langchain-js": "workspace:^", "@braintrust/otel": "workspace:^", "@braintrust/seinfeld": "workspace:^", "@opentelemetry/api": ">=1.9.0", @@ -21,7 +21,8 @@ "braintrust": "workspace:^", "tsx": "^4.21.0", "typescript": "5.4.4", - "vitest": "4.1.11", - "zod": "4.3.6" + "vitest": "catalog:", + "zod": "4.3.6", + "vite-plus": "catalog:" } } diff --git a/e2e/scenarios/ai-sdk-batch-instrumentation/scenario.test.ts b/e2e/scenarios/ai-sdk-batch-instrumentation/scenario.test.ts index 803af2858..84ade4847 100644 --- a/e2e/scenarios/ai-sdk-batch-instrumentation/scenario.test.ts +++ b/e2e/scenarios/ai-sdk-batch-instrumentation/scenario.test.ts @@ -1,6 +1,6 @@ import { readFile, rm } from "node:fs/promises"; import { join } from "node:path"; -import { describe, expect, test } from "vitest"; +import { describe, expect, test } from "vite-plus/test"; import { resolveFileSnapshotPath } from "../../helpers/file-snapshot"; import { prepareScenarioDir, @@ -19,7 +19,7 @@ const recording = ["record", "record-missing"].includes( ); const timeoutMs = recording ? 22 * 60 * 1000 : 120_000; -describe.sequential("AI SDK batch instrumentation", () => { +describe("AI SDK batch instrumentation", { concurrent: false }, () => { for (const variantKey of ["ai-sdk-v7", "ai-sdk-v7-latest"]) { for (const mode of ["manual", "manual-with-auto"]) { test( diff --git a/e2e/scenarios/ai-sdk-harness-instrumentation/scenario.impl.mjs b/e2e/scenarios/ai-sdk-harness-instrumentation/scenario.impl.mjs index 06d37e48e..06eacc832 100644 --- a/e2e/scenarios/ai-sdk-harness-instrumentation/scenario.impl.mjs +++ b/e2e/scenarios/ai-sdk-harness-instrumentation/scenario.impl.mjs @@ -36,6 +36,13 @@ export async function runHarnessAgentScenario(HarnessAgent) { harness: createCodex({ model: process.env.AI_SDK_HARNESS_CODEX_E2E_MODEL ?? "gpt-5.4-mini", reasoningEffort: "low", + // Keep ambient Gateway credentials from bypassing the replay transport. + auth: { + openai: { + apiKey: process.env.OPENAI_API_KEY, + baseUrl: process.env.OPENAI_BASE_URL, + }, + }, webSearch: false, }), permissionMode: "allow-all", diff --git a/e2e/scenarios/ai-sdk-harness-instrumentation/scenario.test.ts b/e2e/scenarios/ai-sdk-harness-instrumentation/scenario.test.ts index 87054af1f..84c7ceaf1 100644 --- a/e2e/scenarios/ai-sdk-harness-instrumentation/scenario.test.ts +++ b/e2e/scenarios/ai-sdk-harness-instrumentation/scenario.test.ts @@ -1,4 +1,4 @@ -import { describe, expect, test } from "vitest"; +import { describe, expect, test } from "vite-plus/test"; import { readFile } from "node:fs/promises"; import { join } from "node:path"; import { resolveFileSnapshotPath } from "../../helpers/file-snapshot"; @@ -49,7 +49,7 @@ const scenarios = await Promise.all( })), ); -describe.sequential("HarnessAgent instrumentation variants", () => { +describe("HarnessAgent instrumentation variants", { concurrent: false }, () => { test( "preserves a turn across a real subprocess handoff", { timeout: TIMEOUT_MS }, @@ -154,205 +154,211 @@ describe.sequential("HarnessAgent instrumentation variants", () => { ); for (const scenario of scenarios) { - describe.sequential(`@ai-sdk/harness ${scenario.version}`, () => { - for (const mode of ["wrapped", "auto-hook"] as const) { - test(mode, { timeout: TIMEOUT_MS }, async () => { - await withScenarioHarness(async (harness) => { - const runContext = { - variantKey: scenario.variantKey, - originalScenarioDir, - }; - const env = { - AI_SDK_HARNESS_CODEX_PACKAGE_NAME: scenario.codexDependencyName, - AI_SDK_HARNESS_PACKAGE_NAME: scenario.dependencyName, - }; - if (mode === "wrapped") { - await harness.runScenarioDir({ - entry: "scenario.ts", - env, - runContext, - scenarioDir, - timeoutMs: TIMEOUT_MS, - }); - } else { - await harness.runNodeScenarioDir({ - entry: "scenario.mjs", - env, - nodeArgs: ["--import", "braintrust/hook.mjs"], - runContext, - scenarioDir, - timeoutMs: TIMEOUT_MS, - }); - } + describe( + `@ai-sdk/harness ${scenario.version}`, + { concurrent: false }, + () => { + for (const mode of ["wrapped", "auto-hook"] as const) { + test(mode, { timeout: TIMEOUT_MS }, async () => { + await withScenarioHarness(async (harness) => { + const runContext = { + variantKey: scenario.variantKey, + originalScenarioDir, + }; + const env = { + AI_SDK_HARNESS_CODEX_PACKAGE_NAME: scenario.codexDependencyName, + AI_SDK_HARNESS_PACKAGE_NAME: scenario.dependencyName, + }; + if (mode === "wrapped") { + await harness.runScenarioDir({ + entry: "scenario.ts", + env, + runContext, + scenarioDir, + timeoutMs: TIMEOUT_MS, + }); + } else { + await harness.runNodeScenarioDir({ + entry: "scenario.mjs", + env, + nodeArgs: ["--import", "braintrust/hook.mjs"], + runContext, + scenarioDir, + timeoutMs: TIMEOUT_MS, + }); + } - const events = harness.events(); - const turns = turnNames.flatMap((name) => - findAllSpans(events, name), - ); - const turnsByName = Object.fromEntries( - turns.map((turn) => [turn.span.name, turn]), - ); - expect(turns).toHaveLength(2); - expect(new Set(turns.map((turn) => turn.span.rootId)).size).toBe(2); + const events = harness.events(); + const turns = turnNames.flatMap((name) => + findAllSpans(events, name), + ); + const turnsByName = Object.fromEntries( + turns.map((turn) => [turn.span.name, turn]), + ); + expect(turns).toHaveLength(2); + expect(new Set(turns.map((turn) => turn.span.rootId)).size).toBe( + 2, + ); - for (const turn of turns) { - expect(turn.span.type).toBe("task"); - expect(turn.metadata).toMatchObject({ - harnessId: "codex", - permissionMode: "allow-all", - scenario: "ai-sdk-harness-instrumentation", - sessionId: "shared-harness-session", - testRunId: harness.testRunId, + for (const turn of turns) { + expect(turn.span.type).toBe("task"); + expect(turn.metadata).toMatchObject({ + harnessId: "codex", + permissionMode: "allow-all", + scenario: "ai-sdk-harness-instrumentation", + sessionId: "shared-harness-session", + testRunId: harness.testRunId, + }); + expect(turn.input).not.toHaveProperty("session"); + } + expect( + turnsByName["HarnessAgent.generate"]?.span.parentIds, + ).toEqual([]); + expect( + turnsByName["HarnessAgent.stream"]?.span.parentIds, + ).toEqual([]); + expect( + findAllSpans(events, "HarnessAgent.continueGenerate"), + ).toEqual([]); + expect( + findAllSpans(events, "HarnessAgent.continueStream"), + ).toEqual([]); + expect(turnsByName["HarnessAgent.generate"]?.input).toEqual({ + prompt: + 'Run the built-in bash command "touch /workspace/generate-started; sleep 5; printf GENERATE_OK" exactly once. After it finishes, reply exactly GENERATE_OK.', }); - expect(turn.input).not.toHaveProperty("session"); - } - expect( - turnsByName["HarnessAgent.generate"]?.span.parentIds, - ).toEqual([]); - expect(turnsByName["HarnessAgent.stream"]?.span.parentIds).toEqual( - [], - ); - expect( - findAllSpans(events, "HarnessAgent.continueGenerate"), - ).toEqual([]); - expect(findAllSpans(events, "HarnessAgent.continueStream")).toEqual( - [], - ); - expect(turnsByName["HarnessAgent.generate"]?.input).toEqual({ - prompt: - 'Run the built-in bash command "touch /workspace/generate-started; sleep 5; printf GENERATE_OK" exactly once. After it finishes, reply exactly GENERATE_OK.', - }); - expect(turnsByName["HarnessAgent.stream"]?.input).toEqual({ - messages: [ - { - role: "user", - content: - 'Run the built-in bash command "touch /workspace/stream-started; sleep 5; printf STREAM_OK" exactly once. After it finishes, reply exactly STREAM_OK.', - }, - ], - }); - for (const turn of turns) { - expect(turn.output).toBeDefined(); - expect(turn.metrics?.tokens).toEqual(expect.any(Number)); - expect(turn.metrics?.tokens).toBeGreaterThan(0); - } - - expect(findAllSpans(events, "HarnessAgent.createSession")).toEqual( - [], - ); - expect(findAllSpans(events, "doGenerate").length).toBeGreaterThan( - 0, - ); - const harnessSpans = findAllSpans(events, "harness"); - expect(harnessSpans).toHaveLength(4); - const bashSpans = findAllSpans(events, "bash"); - expect(bashSpans).toHaveLength(2); - for (const bashSpan of bashSpans) { - expect(bashSpan.span.type).toBe("tool"); - expect(bashSpan.span.parentIds).toHaveLength(1); - expect(bashSpan.metadata).toMatchObject({ - toolName: "bash", + expect(turnsByName["HarnessAgent.stream"]?.input).toEqual({ + messages: [ + { + role: "user", + content: + 'Run the built-in bash command "touch /workspace/stream-started; sleep 5; printf STREAM_OK" exactly once. After it finishes, reply exactly STREAM_OK.', + }, + ], }); - } + for (const turn of turns) { + expect(turn.output).toBeDefined(); + expect(turn.metrics?.tokens).toEqual(expect.any(Number)); + expect(turn.metrics?.tokens).toBeGreaterThan(0); + } - const generateBashSpan = bashSpans.find((span) => - String(span.input).includes("/workspace/generate-started"), - ); - const streamBashSpan = bashSpans.find((span) => - String(span.input).includes("/workspace/stream-started"), - ); - expect(generateBashSpan?.input).toBe( - '{"command":"/bin/bash -lc \'touch /workspace/generate-started; sleep 5; printf GENERATE_OK\'"}', - ); - expect(streamBashSpan?.input).toBe( - '{"command":"/bin/bash -lc \'touch /workspace/stream-started; sleep 5; printf STREAM_OK\'"}', - ); - expect(generateBashSpan?.span.rootId).toBe( - turnsByName["HarnessAgent.generate"]?.span.rootId, - ); - expect(generateBashSpan?.span.parentIds).toEqual([ - turnsByName["HarnessAgent.generate"]?.span.id, - ]); - expect(streamBashSpan?.span.rootId).toBe( - turnsByName["HarnessAgent.stream"]?.span.rootId, - ); - expect(streamBashSpan?.span.parentIds).toEqual([ - turnsByName["HarnessAgent.stream"]?.span.id, - ]); - for (const [toolSpan, initialName] of [ - [generateBashSpan, "HarnessAgent.generate"], - [streamBashSpan, "HarnessAgent.stream"], - ] as const) { expect( - numericMetric(turnsByName[initialName], "start"), - ).toBeLessThanOrEqual(numericMetric(toolSpan, "start")); - expect(numericMetric(toolSpan, "start")).toBeLessThanOrEqual( - numericMetric(turnsByName[initialName], "end"), + findAllSpans(events, "HarnessAgent.createSession"), + ).toEqual([]); + expect(findAllSpans(events, "doGenerate").length).toBeGreaterThan( + 0, ); - - const turn = turnsByName[initialName]; - const operations = harnessSpans - .filter((span) => span.span.rootId === turn?.span.rootId) - .sort( - (left, right) => - numericMetric(left, "start") - - numericMetric(right, "start"), - ); - expect(operations).toHaveLength(2); - for (const operation of operations) { - expect(operation.span.parentIds).toEqual([turn?.span.id]); + const harnessSpans = findAllSpans(events, "harness"); + expect(harnessSpans).toHaveLength(4); + const bashSpans = findAllSpans(events, "bash"); + expect(bashSpans).toHaveLength(2); + for (const bashSpan of bashSpans) { + expect(bashSpan.span.type).toBe("tool"); + expect(bashSpan.span.parentIds).toHaveLength(1); + expect(bashSpan.metadata).toMatchObject({ + toolName: "bash", + }); } - expect(numericMetric(operations[0], "start")).toBeLessThanOrEqual( - numericMetric(operations[1], "start"), + + const generateBashSpan = bashSpans.find((span) => + String(span.input).includes("/workspace/generate-started"), + ); + const streamBashSpan = bashSpans.find((span) => + String(span.input).includes("/workspace/stream-started"), ); - const modelSpans = findAllSpans(events, "doGenerate").filter( - (span) => - operations.some((operation) => - span.span.parentIds.includes(operation.span.id), - ), + expect(generateBashSpan?.input).toBe( + '{"command":"/bin/bash -lc \'touch /workspace/generate-started; sleep 5; printf GENERATE_OK\'"}', ); - expect(modelSpans).toHaveLength(2); - for (const operation of operations) { + expect(streamBashSpan?.input).toBe( + '{"command":"/bin/bash -lc \'touch /workspace/stream-started; sleep 5; printf STREAM_OK\'"}', + ); + expect(generateBashSpan?.span.rootId).toBe( + turnsByName["HarnessAgent.generate"]?.span.rootId, + ); + expect(generateBashSpan?.span.parentIds).toEqual([ + turnsByName["HarnessAgent.generate"]?.span.id, + ]); + expect(streamBashSpan?.span.rootId).toBe( + turnsByName["HarnessAgent.stream"]?.span.rootId, + ); + expect(streamBashSpan?.span.parentIds).toEqual([ + turnsByName["HarnessAgent.stream"]?.span.id, + ]); + for (const [toolSpan, initialName] of [ + [generateBashSpan, "HarnessAgent.generate"], + [streamBashSpan, "HarnessAgent.stream"], + ] as const) { expect( - modelSpans.filter((span) => - span.span.parentIds.includes(operation.span.id), - ), - ).toHaveLength(1); + numericMetric(turnsByName[initialName], "start"), + ).toBeLessThanOrEqual(numericMetric(toolSpan, "start")); + expect(numericMetric(toolSpan, "start")).toBeLessThanOrEqual( + numericMetric(turnsByName[initialName], "end"), + ); + + const turn = turnsByName[initialName]; + const operations = harnessSpans + .filter((span) => span.span.rootId === turn?.span.rootId) + .sort( + (left, right) => + numericMetric(left, "start") - + numericMetric(right, "start"), + ); + expect(operations).toHaveLength(2); + for (const operation of operations) { + expect(operation.span.parentIds).toEqual([turn?.span.id]); + } + expect( + numericMetric(operations[0], "start"), + ).toBeLessThanOrEqual(numericMetric(operations[1], "start")); + const modelSpans = findAllSpans(events, "doGenerate").filter( + (span) => + operations.some((operation) => + span.span.parentIds.includes(operation.span.id), + ), + ); + expect(modelSpans).toHaveLength(2); + for (const operation of operations) { + expect( + modelSpans.filter((span) => + span.span.parentIds.includes(operation.span.id), + ), + ).toHaveLength(1); + } } - } - // Harness releases report provider-executed tool timing at - // different lifecycle points. The focused assertions above cover - // its ownership and root lifetime without inventing a boundary. - const snapshotEvents = events.filter( - (event) => event.span.name !== "bash", - ); - await matchSpanTreeSnapshot( - snapshotEvents, - resolveFileSnapshotPath( - import.meta.url, - `${scenario.variantKey}-${mode}.span-tree.json`, - ), - { - normalize: { - // These fields can be split across either side of a - // suspended Codex turn. The trace hierarchy and model - // ordering remain stable and are snapshot-tested. - omittedKeys: [ - "callId", - "content", - "responseMessages", - "steps", - "text", - "toolCalls", - "toolResults", - ], + // Harness releases report provider-executed tool timing at + // different lifecycle points. The focused assertions above cover + // its ownership and root lifetime without inventing a boundary. + const snapshotEvents = events.filter( + (event) => event.span.name !== "bash", + ); + await matchSpanTreeSnapshot( + snapshotEvents, + resolveFileSnapshotPath( + import.meta.url, + `${scenario.variantKey}-${mode}.span-tree.json`, + ), + { + normalize: { + // These fields can be split across either side of a + // suspended Codex turn. The trace hierarchy and model + // ordering remain stable and are snapshot-tested. + omittedKeys: [ + "callId", + "content", + "responseMessages", + "steps", + "text", + "toolCalls", + "toolResults", + ], + }, }, - }, - ); + ); + }); }); - }); - } - }); + } + }, + ); } }); diff --git a/e2e/scenarios/ai-sdk-instrumentation/__cassettes__/ai-sdk-v3-latest.cassette.json b/e2e/scenarios/ai-sdk-instrumentation/__cassettes__/ai-sdk-v3-latest.cassette.json deleted file mode 100644 index 69cb213ea..000000000 --- a/e2e/scenarios/ai-sdk-instrumentation/__cassettes__/ai-sdk-v3-latest.cassette.json +++ /dev/null @@ -1,1148 +0,0 @@ -{ - "entries": [ - { - "callIndex": 0, - "id": "2583f51940dc092e", - "matchKey": "POST api.openai.com/v1/chat/completions", - "recordedAt": "2026-08-12T03:20:49.537Z", - "request": { - "body": { - "kind": "json", - "value": { - "max_tokens": 24, - "messages": [ - { - "content": "Reply with the single token PARIS and no punctuation.", - "role": "user" - } - ], - "model": "gpt-4o-mini-2024-07-18", - "temperature": 0 - } - }, - "headers": {}, - "method": "POST", - "url": "https://api.openai.com/v1/chat/completions" - }, - "response": { - "body": { - "kind": "json", - "value": { - "choices": [ - { - "finish_reason": "stop", - "index": 0, - "logprobs": null, - "message": { - "annotations": [], - "content": "PARIS", - "refusal": null, - "role": "assistant" - } - } - ], - "created": 1786504849, - "id": "chatcmpl-EBtoH326GRbZwguUwwJzfA1rP6Ts8", - "model": "gpt-4o-mini-2024-07-18", - "object": "chat.completion", - "service_tier": "default", - "system_fingerprint": "fp_fabbcdf5d9", - "usage": { - "completion_tokens": 2, - "completion_tokens_details": { - "accepted_prediction_tokens": 0, - "audio_tokens": 0, - "reasoning_tokens": 0, - "rejected_prediction_tokens": 0 - }, - "prompt_tokens": 18, - "prompt_tokens_details": { - "audio_tokens": 0, - "cached_tokens": 0 - }, - "total_tokens": 20 - } - } - }, - "headers": { - "access-control-expose-headers": "X-Request-ID, CF-Ray, CF-Ray", - "alt-svc": "h3=\":443\"; ma=86400", - "cf-cache-status": "DYNAMIC", - "cf-ray": "a29c58ab7ea48029-IAD", - "connection": "keep-alive", - "content-encoding": "gzip", - "content-type": "application/json", - "date": "Wed, 12 Aug 2026 03:20:49 GMT", - "openai-organization": "braintrust-data", - "openai-processing-ms": "274", - "openai-project": "proj_vsCSXafhhByzWOThMrJcZiw9", - "openai-version": "2020-10-01", - "server": "cloudflare", - "set-cookie": "[REDACTED]", - "strict-transport-security": "max-age=31536000; includeSubDomains; preload", - "transfer-encoding": "chunked", - "x-content-type-options": "nosniff", - "x-openai-proxy-wasm": "v0.1", - "x-ratelimit-limit-requests": "30000", - "x-ratelimit-limit-tokens": "150000000", - "x-ratelimit-remaining-requests": "29999", - "x-ratelimit-remaining-tokens": "149999985", - "x-ratelimit-reset-requests": "2ms", - "x-ratelimit-reset-tokens": "0s", - "x-request-id": "req_06b4b55bc7c94ccdb71d80a0aec0fee0" - }, - "status": 200, - "statusText": "OK" - } - }, - { - "callIndex": 1, - "id": "901ff936ff6b7255", - "matchKey": "POST api.openai.com/v1/chat/completions", - "recordedAt": "2026-08-12T03:20:49.997Z", - "request": { - "body": { - "kind": "json", - "value": { - "max_tokens": 32, - "messages": [ - { - "content": "Count from 1 to 3 and include the words one two three.", - "role": "user" - } - ], - "model": "gpt-4o-mini-2024-07-18", - "stream": true, - "temperature": 0 - } - }, - "headers": {}, - "method": "POST", - "url": "https://api.openai.com/v1/chat/completions" - }, - "response": { - "body": { - "chunks": [ - "data: {\"id\":\"chatcmpl-EBtoHHiQ2rImeNIBXy6wNKn48cg3c\",\"object\":\"chat.completion.chunk\",\"created\":1786504849,\"model\":\"gpt-4o-mini-2024-07-18\",\"service_tier\":\"default\",\"system_fingerprint\":\"fp_0950b7f384\",\"choices\":[{\"index\":0,\"delta\":{\"role\":\"assistant\",\"content\":\"\",\"refusal\":null},\"logprobs\":null,\"finish_reason\":null}],\"obfuscation\":\"QUqa0V\"}", - "data: {\"id\":\"chatcmpl-EBtoHHiQ2rImeNIBXy6wNKn48cg3c\",\"object\":\"chat.completion.chunk\",\"created\":1786504849,\"model\":\"gpt-4o-mini-2024-07-18\",\"service_tier\":\"default\",\"system_fingerprint\":\"fp_0950b7f384\",\"choices\":[{\"index\":0,\"delta\":{\"content\":\"One\"},\"logprobs\":null,\"finish_reason\":null}],\"obfuscation\":\"WUiC1\"}", - "data: {\"id\":\"chatcmpl-EBtoHHiQ2rImeNIBXy6wNKn48cg3c\",\"object\":\"chat.completion.chunk\",\"created\":1786504849,\"model\":\"gpt-4o-mini-2024-07-18\",\"service_tier\":\"default\",\"system_fingerprint\":\"fp_0950b7f384\",\"choices\":[{\"index\":0,\"delta\":{\"content\":\",\"},\"logprobs\":null,\"finish_reason\":null}],\"obfuscation\":\"hc4RV6V\"}", - "data: {\"id\":\"chatcmpl-EBtoHHiQ2rImeNIBXy6wNKn48cg3c\",\"object\":\"chat.completion.chunk\",\"created\":1786504849,\"model\":\"gpt-4o-mini-2024-07-18\",\"service_tier\":\"default\",\"system_fingerprint\":\"fp_0950b7f384\",\"choices\":[{\"index\":0,\"delta\":{\"content\":\" two\"},\"logprobs\":null,\"finish_reason\":null}],\"obfuscation\":\"5bTk\"}", - "data: {\"id\":\"chatcmpl-EBtoHHiQ2rImeNIBXy6wNKn48cg3c\",\"object\":\"chat.completion.chunk\",\"created\":1786504849,\"model\":\"gpt-4o-mini-2024-07-18\",\"service_tier\":\"default\",\"system_fingerprint\":\"fp_0950b7f384\",\"choices\":[{\"index\":0,\"delta\":{\"content\":\",\"},\"logprobs\":null,\"finish_reason\":null}],\"obfuscation\":\"lX2OKZ9\"}", - "data: {\"id\":\"chatcmpl-EBtoHHiQ2rImeNIBXy6wNKn48cg3c\",\"object\":\"chat.completion.chunk\",\"created\":1786504849,\"model\":\"gpt-4o-mini-2024-07-18\",\"service_tier\":\"default\",\"system_fingerprint\":\"fp_0950b7f384\",\"choices\":[{\"index\":0,\"delta\":{\"content\":\" three\"},\"logprobs\":null,\"finish_reason\":null}],\"obfuscation\":\"Aa\"}", - "data: {\"id\":\"chatcmpl-EBtoHHiQ2rImeNIBXy6wNKn48cg3c\",\"object\":\"chat.completion.chunk\",\"created\":1786504849,\"model\":\"gpt-4o-mini-2024-07-18\",\"service_tier\":\"default\",\"system_fingerprint\":\"fp_0950b7f384\",\"choices\":[{\"index\":0,\"delta\":{\"content\":\".\"},\"logprobs\":null,\"finish_reason\":null}],\"obfuscation\":\"ApL8mYr\"}", - "data: {\"id\":\"chatcmpl-EBtoHHiQ2rImeNIBXy6wNKn48cg3c\",\"object\":\"chat.completion.chunk\",\"created\":1786504849,\"model\":\"gpt-4o-mini-2024-07-18\",\"service_tier\":\"default\",\"system_fingerprint\":\"fp_0950b7f384\",\"choices\":[{\"index\":0,\"delta\":{},\"logprobs\":null,\"finish_reason\":\"stop\"}],\"obfuscation\":\"pn\"}", - "data: [DONE]" - ], - "kind": "sse" - }, - "headers": { - "access-control-expose-headers": "X-Request-ID, CF-Ray, CF-Ray", - "alt-svc": "h3=\":443\"; ma=86400", - "cf-cache-status": "DYNAMIC", - "cf-ray": "a29c58adb81c8029-IAD", - "connection": "keep-alive", - "content-type": "text/event-stream; charset=utf-8", - "date": "Wed, 12 Aug 2026 03:20:49 GMT", - "openai-organization": "braintrust-data", - "openai-processing-ms": "230", - "openai-project": "proj_vsCSXafhhByzWOThMrJcZiw9", - "openai-version": "2020-10-01", - "server": "cloudflare", - "set-cookie": "[REDACTED]", - "strict-transport-security": "max-age=31536000; includeSubDomains; preload", - "transfer-encoding": "chunked", - "x-content-type-options": "nosniff", - "x-openai-proxy-wasm": "v0.1", - "x-ratelimit-limit-requests": "30000", - "x-ratelimit-limit-tokens": "150000000", - "x-ratelimit-remaining-requests": "29999", - "x-ratelimit-remaining-tokens": "149999982", - "x-ratelimit-reset-requests": "2ms", - "x-ratelimit-reset-tokens": "0s", - "x-request-id": "req_4858eeb509ce41e692f1a591d072d2d9" - }, - "status": 200, - "statusText": "OK" - } - }, - { - "callIndex": 0, - "id": "49a95bf2b3466013", - "matchKey": "POST api.openai.com/v1/embeddings", - "recordedAt": "2026-08-12T03:20:50.133Z", - "request": { - "body": { - "kind": "json", - "value": { - "encoding_format": "float", - "input": ["Paris is the capital of France."], - "model": "text-embedding-3-small" - } - }, - "headers": {}, - "method": "POST", - "url": "https://api.openai.com/v1/embeddings" - }, - "response": { - "body": { - "kind": "json", - "value": { - "data": [ - { - "embedding": [ - 0.0196075439453125, 0.019073486328125, -0.01314544677734375, - -0.020660400390625, -0.0188140869140625, 0.029632568359375, - -0.0206298828125, 0.0236358642578125, 0.006320953369140625, - -0.01708984375, 0.004039764404296875, 0.0103607177734375, - -0.0289459228515625, 0.0068359375, -0.03192138671875, - 0.0251922607421875, -0.0282745361328125, 0.050262451171875, - 0.0914306640625, -0.02008056640625, 0.0222930908203125, - -0.01297760009765625, -0.0144805908203125, 0.034393310546875, - 0.052734375, -0.0011014938354492188, 0.00817108154296875, - 0.042510986328125, 0.0030460357666015625, 0.05987548828125, - 0.0164642333984375, -0.00787353515625, -0.00244903564453125, - 0.02423095703125, 0.01953125, -0.061065673828125, - -0.013336181640625, -0.03851318359375, 0.0079498291015625, - 0.0276641845703125, 0.0171051025390625, -0.02838134765625, - -0.0286865234375, 0.03277587890625, -0.035919189453125, - -0.047088623046875, -0.00940704345703125, -0.0224761962890625, - 0.02313232421875, -0.0037555694580078125, 0.045654296875, - -0.004848480224609375, 0.02020263671875, 0.00923919677734375, - 0.0044708251953125, 0.02252197265625, 0.03271484375, - 0.0433349609375, 0.0257110595703125, 0.0035400390625, - 0.051788330078125, 0.0206298828125, 0.04046630859375, - 0.01776123046875, -0.00559234619140625, 0.03271484375, - 0.021575927734375, 0.0322265625, 0.050048828125, - 0.02581787109375, 0.0187835693359375, 0.044342041015625, - -0.005947113037109375, 0.006748199462890625, 0.01885986328125, - 0.014678955078125, 0.004638671875, 0.038848876953125, - 0.0173492431640625, -0.03466796875, -0.0016269683837890625, - 0.01055908203125, 0.01372528076171875, -0.02166748046875, - -0.00677490234375, -0.01284027099609375, -0.00388336181640625, - 0.00274658203125, -0.002719879150390625, -0.0220794677734375, - 0.0014619827270507812, -0.01175689697265625, -0.0172119140625, - 0.015655517578125, -0.0308990478515625, 0.00458526611328125, - 0.0211334228515625, -0.04888916015625, -0.058837890625, - 0.02716064453125, 0.027862548828125, 0.001308441162109375, - -0.04681396484375, 0.0316162109375, 0.0155029296875, - -0.01544952392578125, 0.0004296302795410156, - -0.0005507469177246094, 0.01428985595703125, -0.0408935546875, - -0.021759033203125, 0.00412750244140625, -0.00025177001953125, - 0.032440185546875, -0.059783935546875, 0.01093292236328125, - 0.01397705078125, -0.00713348388671875, 0.031005859375, - -0.007572174072265625, -0.006343841552734375, - 0.0026073455810546875, -0.04180908203125, 0.040374755859375, - -0.03387451171875, -0.048248291015625, 0.006519317626953125, - 0.0264739990234375, 0.0227203369140625, -0.00856781005859375, - 0.004657745361328125, -0.044952392578125, 0.016876220703125, - 0.0258941650390625, 0.0506591796875, -0.00958251953125, - 0.033721923828125, -0.0057525634765625, 0.01751708984375, - 0.0177154541015625, -0.01558685302734375, 0.028533935546875, - -0.01058197021484375, 0.036529541015625, -0.0183258056640625, - -0.033843994140625, -0.031402587890625, 0.040069580078125, - 0.01015472412109375, -0.04278564453125, 0.016510009765625, - -0.0212554931640625, 0.00275421142578125, 0.0289459228515625, - -0.0037078857421875, -0.062103271484375, 0.01442718505859375, - -0.0018835067749023438, 0.0384521484375, 0.02569580078125, - -0.036865234375, 0.021331787109375, -0.046173095703125, - -0.016265869140625, -0.0034809112548828125, - 0.0081939697265625, -0.0090179443359375, -0.00542449951171875, - 0.011871337890625, 0.0159454345703125, -0.0235137939453125, - 0.00461578369140625, 0.051116943359375, 0.0389404296875, - 0.0225677490234375, -0.01392364501953125, -0.0185699462890625, - 0.08013916015625, 0.0262603759765625, -0.02142333984375, - -0.01103973388671875, -0.01428985595703125, 0.030731201171875, - -0.01473236083984375, 0.0189361572265625, 0.0250244140625, - -0.0206451416015625, -0.026885986328125, -0.0172882080078125, - 0.05322265625, 0.0615234375, -0.019073486328125, - 0.056396484375, -0.045440673828125, -0.06280517578125, - 0.0241546630859375, 0.0021800994873046875, - 0.006801605224609375, 0.0093231201171875, 0.00798797607421875, - -0.03924560546875, 0.04193115234375, -0.0056304931640625, - -0.00327301025390625, -0.01485443115234375, - 0.0191192626953125, -0.01004791259765625, -0.048309326171875, - 0.0295257568359375, -0.004802703857421875, - -0.0124359130859375, -0.00547027587890625, - -0.0262298583984375, 0.0384521484375, 0.03857421875, - -0.01038360595703125, 0.012908935546875, 0.034027099609375, - 0.0299072265625, -0.00550079345703125, 0.034637451171875, - 0.023406982421875, 0.0113067626953125, -0.01557159423828125, - -0.048919677734375, 0.055419921875, 0.052642822265625, - 0.00609588623046875, -0.044586181640625, 0.002712249755859375, - 0.00432586669921875, -0.028656005859375, -0.0147552490234375, - -0.015411376953125, -0.0269775390625, -0.01922607421875, - 0.003505706787109375, -0.0132598876953125, -0.04779052734375, - 0.019317626953125, -0.0338134765625, 0.01849365234375, - 0.01416015625, 0.0026702880859375, -0.00037384033203125, - 0.0026149749755859375, 0.01383209228515625, 0.037200927734375, - -0.0421142578125, 0.0126953125, 0.043365478515625, - -0.0171966552734375, -0.025360107421875, 0.017303466796875, - -0.03546142578125, 0.035125732421875, 0.006137847900390625, - -0.00630950927734375, -0.047698974609375, -0.0099029541015625, - 0.01043701171875, 0.0198974609375, -0.00995635986328125, - -0.0018243789672851562, -0.00839996337890625, - 0.0192108154296875, -0.0095977783203125, -0.023651123046875, - -0.0224609375, -0.030517578125, 0.03485107421875, - -0.0008287429809570312, -0.0177154541015625, 0.029052734375, - -0.020965576171875, 0.005268096923828125, -0.028167724609375, - 0.0009093284606933594, -0.001140594482421875, - 0.0157928466796875, -0.02178955078125, 0.0443115234375, - -0.01227569580078125, 0.01256561279296875, - -0.0095367431640625, -0.0198211669921875, 0.04046630859375, - 0.06732177734375, -0.032562255859375, 0.052032470703125, - 0.02166748046875, -0.021209716796875, -0.033935546875, - 0.0225982666015625, -0.008056640625, 0.006862640380859375, - 0.035552978515625, 0.0010929107666015625, - 0.006832122802734375, 0.012481689453125, -0.0239105224609375, - -0.0117950439453125, 0.004608154296875, 0.01525115966796875, - -0.049560546875, 0.048431396484375, -0.045501708984375, - 0.0196685791015625, 0.0521240234375, 0.00585174560546875, - -0.054107666015625, -0.036376953125, 0.04632568359375, - -0.06768798828125, -0.01151275634765625, 0.00713348388671875, - -0.0157928466796875, 0.0195770263671875, 0.03448486328125, - 0.01102447509765625, -0.00614166259765625, - 0.01256561279296875, 0.007549285888671875, - 0.0020580291748046875, -0.0272674560546875, 0.031707763671875, - 0.0144805908203125, 0.03839111328125, -0.0278778076171875, - -0.00634765625, 0.0008325576782226562, 0.00878143310546875, - -0.008514404296875, 0.06134033203125, -0.040191650390625, - -0.021484375, 0.003170013427734375, 0.029327392578125, - 0.003055572509765625, -0.00859832763671875, -0.02532958984375, - 0.0270538330078125, -0.035919189453125, 0.0270233154296875, - -0.0033893585205078125, -0.01428985595703125, 0.0377197265625, - 0.001129150390625, -0.005687713623046875, -0.03594970703125, - -0.0191802978515625, -0.0195770263671875, - 0.0004911422729492188, 0.0313720703125, -0.040771484375, - 0.0191802978515625, 0.06414794921875, 0.00946807861328125, - -0.05694580078125, 0.0130462646484375, -0.016510009765625, - -0.052764892578125, -0.0163116455078125, -0.018951416015625, - -0.03668212890625, -0.0084381103515625, - -0.0017042160034179688, -0.00557708740234375, - -0.03851318359375, 0.07061767578125, -0.049896240234375, - -0.0277252197265625, 0.0287322998046875, -0.02880859375, - 0.047088623046875, -0.030364990234375, -0.025054931640625, - -0.008270263671875, -0.04766845703125, -0.02618408203125, - -0.0643310546875, -0.002410888671875, 0.0355224609375, - -0.04705810546875, 0.03204345703125, 0.0369873046875, - 0.043701171875, -0.0550537109375, 0.01226043701171875, - -0.006168365478515625, 0.012237548828125, - 0.004772186279296875, 0.00537872314453125, 0.022552490234375, - -0.03302001953125, -0.0009675025939941406, -0.0103759765625, - -0.00447845458984375, -0.029510498046875, -0.0172576904296875, - 0.0028629302978515625, -0.0390625, 0.01015472412109375, - -0.006866455078125, 0.04296875, 0.028167724609375, - 0.0075225830078125, -0.01715087890625, 0.0215911865234375, - -0.0222625732421875, 0.006572723388671875, -0.037872314453125, - 0.015380859375, -0.003780364990234375, 0.0131683349609375, - 0.0540771484375, 0.0298919677734375, -0.07373046875, - 0.0330810546875, -0.02154541015625, 0.037506103515625, - 0.004802703857421875, -0.058837890625, 0.0113525390625, - 0.0028858184814453125, -0.007354736328125, - -0.01139068603515625, -0.06524658203125, -0.019317626953125, - 0.032623291015625, 0.00508880615234375, 0.021331787109375, - 0.0006260871887207031, -0.008514404296875, - 0.01102447509765625, 0.0150909423828125, 0.0099639892578125, - 0.01019287109375, -0.01428985595703125, 0.00167083740234375, - 0.047515869140625, -0.017364501953125, 0.01197052001953125, - -0.03564453125, -0.0462646484375, -0.017425537109375, - 0.055816650390625, 0.029937744140625, -0.045867919921875, - 0.06005859375, -0.0311737060546875, -0.00281524658203125, - -0.00878143310546875, -0.01318359375, -0.0246124267578125, - 0.0300140380859375, 0.0197296142578125, 0.0699462890625, - 0.00809478759765625, -0.03424072265625, 0.00490570068359375, - 0.046600341796875, -0.00392913818359375, 0.044769287109375, - 0.03271484375, 0.018768310546875, 0.0243072509765625, - 0.019378662109375, -0.038787841796875, -0.03131103515625, - 0.0158538818359375, 0.0335693359375, 0.0224456787109375, - 0.025848388671875, 0.007061004638671875, 0.0049285888671875, - -0.0595703125, 0.038726806640625, -0.0494384765625, - -0.06390380859375, -0.0011005401611328125, 0.0219268798828125, - -0.018798828125, 0.006317138671875, -0.0794677734375, - -0.00662994384765625, 0.00353240966796875, - 0.0020122528076171875, -0.0234527587890625, - 0.0159759521484375, 0.052032470703125, -0.038360595703125, - 0.01372528076171875, -0.00020372867584228516, - 0.021575927734375, -0.00679779052734375, -0.053985595703125, - -0.01056671142578125, -0.0272369384765625, - -0.0236053466796875, 0.023162841796875, 0.00165557861328125, - 0.0499267578125, 0.012908935546875, 0.033233642578125, - 0.08331298828125, 0.01042938232421875, 0.0039825439453125, - 0.00824737548828125, -0.0260772705078125, 0.0299530029296875, - -0.025299072265625, 0.032196044921875, 0.01166534423828125, - -0.010711669921875, -0.00376129150390625, -0.023162841796875, - 0.0157470703125, 0.01163482666015625, 0.002651214599609375, - 0.0198822021484375, 0.0246124267578125, -0.0152740478515625, - 0.004291534423828125, 0.032928466796875, 0.0141143798828125, - -0.024322509765625, -0.007518768310546875, 0.04766845703125, - -0.04901123046875, 0.0227508544921875, -0.0255584716796875, - -0.0141143798828125, -0.01641845703125, 0.0095367431640625, - 0.0183563232421875, 0.0184173583984375, 0.05340576171875, - 0.00008529424667358398, 0.03070068359375, - 0.0038967132568359375, -0.0161285400390625, - -0.006809234619140625, -0.0189361572265625, - -0.01512908935546875, -0.0167999267578125, 0.022003173828125, - -0.027374267578125, -0.03448486328125, -0.016998291015625, - -0.024627685546875, 0.045806884765625, -0.03558349609375, - -0.0439453125, -0.000728607177734375, -0.0036830902099609375, - -0.009368896484375, -0.0039520263671875, 0.026947021484375, - -0.01522064208984375, -0.00968170166015625, 0.06329345703125, - 0.037994384765625, 0.017242431640625, 0.0188751220703125, - 0.036865234375, 0.00426483154296875, 0.01039886474609375, - -0.01483154296875, -0.022216796875, -0.0118408203125, - 0.0484619140625, 0.01296234130859375, 0.0175018310546875, - 0.02081298828125, 0.0182037353515625, 0.010162353515625, - -0.003997802734375, -0.0299224853515625, 0.0654296875, - -0.0054168701171875, 0.01355743408203125, 0.032470703125, - -0.03521728515625, -0.04522705078125, 0.0218353271484375, - 0.0035457611083984375, -0.002376556396484375, -0.0419921875, - -0.0294342041015625, -0.0037860870361328125, - 0.01380157470703125, 0.0174713134765625, -0.0234527587890625, - 0.01428985595703125, 0.0014009475708007812, 0.01141357421875, - 0.0008606910705566406, -0.035858154296875, - -0.01201629638671875, 0.00235748291015625, 0.0251312255859375, - -0.019287109375, -0.0242156982421875, 0.01537322998046875, - -0.036956787109375, 0.00670623779296875, -0.0191650390625, - 0.0223846435546875, -0.007534027099609375, 0.0176849365234375, - -0.0187530517578125, 0.01541900634765625, 0.028289794921875, - -0.005496978759765625, 0.02801513671875, -0.02410888671875, - 0.023956298828125, -0.01284027099609375, -0.00730133056640625, - 0.01430511474609375, 0.03460693359375, -0.031982421875, - 0.0055694580078125, -0.005382537841796875, 0.047088623046875, - 0.0189361572265625, 0.0183868408203125, -0.0099945068359375, - -0.0172576904296875, 0.024444580078125, 0.0141754150390625, - 0.045562744140625, 0.0128021240234375, -0.0233917236328125, - -0.0251007080078125, -0.004085540771484375, - -0.0197906494140625, 0.008392333984375, -0.03021240234375, - 0.041046142578125, 0.0124664306640625, -0.0015897750854492188, - 0.0059967041015625, -0.006702423095703125, 0.0124664306640625, - 0.02105712890625, 0.04742431640625, 0.0295562744140625, - -0.04473876953125, 0.053680419921875, 0.0214385986328125, - -0.01190185546875, 0.003604888916015625, 0.004787445068359375, - 0.03082275390625, -0.00344085693359375, 0.0184173583984375, - -0.0284271240234375, 0.05914306640625, -0.0222625732421875, - -0.0287017822265625, 0.02880859375, -0.035003662109375, - -0.022186279296875, 0.0284881591796875, 0.0223388671875, - 0.0144500732421875, 0.043853759765625, 0.0281829833984375, - -0.047088623046875, -0.03582763671875, 0.04150390625, - 0.003925323486328125, 0.00934600830078125, -0.022003173828125, - -0.004741668701171875, -0.01403045654296875, - -0.04925537109375, -0.0005216598510742188, - -0.0065765380859375, 0.01715087890625, -0.0152130126953125, - 0.0200042724609375, -0.0130615234375, -0.0202484130859375, - -0.0025310516357421875, 0.0059661865234375, - 0.0185699462890625, -0.0101776123046875, -0.021728515625, - -0.01114654541015625, -0.0189361572265625, 0.0223541259765625, - 0.0265960693359375, -0.02960205078125, 0.019287109375, - -0.01416778564453125, 0.0121307373046875, 0.034515380859375, - -0.033782958984375, 0.000728607177734375, -0.03411865234375, - 0.00010061264038085938, 0.0010194778442382812, - 0.0228271484375, 0.009246826171875, -0.0114288330078125, - -0.00408935546875, 0.00986480712890625, -0.016845703125, - -0.039886474609375, -0.00739288330078125, - 0.007213592529296875, 0.0256805419921875, 0.01123809814453125, - 0.009185791015625, -0.03607177734375, 0.0251617431640625, - -0.017852783203125, -0.007503509521484375, -0.037750244140625, - -0.02001953125, -0.01474761962890625, -0.0113372802734375, - 0.0115814208984375, -0.00036406517028808594, - -0.014190673828125, -0.0032482147216796875, - 0.0017681121826171875, -0.0238494873046875, - -0.025299072265625, -0.0113525390625, 0.01398468017578125, - 0.01377105712890625, 0.01200103759765625, 0.00659942626953125, - -0.0007700920104980469, -0.05926513671875, 0.0075531005859375, - -0.033935546875, 0.00881195068359375, 0.002941131591796875, - -0.02313232421875, 0.02215576171875, 0.06939697265625, - -0.0294647216796875, -0.0032405853271484375, - -0.00983428955078125, -0.023223876953125, - -0.00310516357421875, -0.0014495849609375, - 0.01334381103515625, -0.0032253265380859375, 0.033447265625, - -0.0108642578125, 0.020355224609375, 0.0001189112663269043, - -0.0002608299255371094, -0.0404052734375, - 0.0038318634033203125, 0.0113983154296875, - -0.0255584716796875, -0.00572967529296875, 0.0184783935546875, - -0.0272064208984375, 0.01013946533203125, - -0.01021575927734375, -0.0146636962890625, - -0.0161285400390625, -0.022796630859375, 0.026214599609375, - -0.036590576171875, -0.0227813720703125, 0.0120086669921875, - 0.03369140625, -0.04632568359375, -0.012939453125, - -0.03302001953125, -0.035858154296875, 0.0019168853759765625, - -0.006450653076171875, 0.0162506103515625, 0.0193023681640625, - -0.00664520263671875, -0.01361083984375, 0.04168701171875, - -0.047821044921875, 0.011138916015625, -0.0188751220703125, - 0.00011795759201049805, -0.0157470703125, -0.01470947265625, - -0.01255035400390625, 0.0099334716796875, -0.0283355712890625, - 0.053131103515625, -0.02044677734375, -0.027008056640625, - -0.05059814453125, -0.01421356201171875, - 0.0009927749633789062, 0.0079803466796875, 0.0181884765625, - 0.0401611328125, 0.01983642578125, -0.028778076171875, - -0.004673004150390625, 0.0011386871337890625, - 0.0002682209014892578, -0.00746917724609375, - -0.038360595703125, -0.01195526123046875, -0.037689208984375, - 0.039764404296875, -0.0227813720703125, -0.00922393798828125, - -0.038116455078125, 0.0167388916015625, 0.01528167724609375, - -0.0020923614501953125, -0.0123291015625, - -0.007373809814453125, -0.04132080078125, 0.03277587890625, - -0.0265045166015625, 0.033172607421875, 0.0306549072265625, - -0.0022735595703125, 0.0080718994140625, -0.0269317626953125, - 0.0202178955078125, 0.0038299560546875, 0.022674560546875, - 0.00959014892578125, 0.039520263671875, 0.00336456298828125, - -0.0233917236328125, 0.0082855224609375, 0.01971435546875, - -0.035430908203125, 0.05047607421875, 0.006145477294921875, - -0.05767822265625, 0.00943756103515625, -0.005275726318359375, - 0.0142822265625, -0.012359619140625, -0.007305145263671875, - 0.006893157958984375, -0.023101806640625, 0.050323486328125, - -0.0291595458984375, 0.0227203369140625, -0.035614013671875, - 0.0396728515625, 0.00412750244140625, 0.034393310546875, - 0.00354766845703125, -0.032135009765625, -0.0251617431640625, - 0.003032684326171875, -0.0310211181640625, -0.033660888671875, - 0.0011720657348632812, -0.009918212890625, - -0.0187530517578125, -0.038116455078125, -0.0288543701171875, - 0.05987548828125, 0.005603790283203125, -0.032379150390625, - 0.0026187896728515625, 0.01201629638671875, - 0.001361846923828125, 0.0191497802734375, -0.00958251953125, - 0.01006317138671875, -0.01715087890625, -0.0546875, - -0.00728607177734375, 0.0243072509765625, 0.043853759765625, - -0.004398345947265625, -0.0305938720703125, - -0.0135650634765625, -0.0357666015625, -0.01088714599609375, - 0.01580810546875, 0.0311126708984375, 0.01107025146484375, - 0.0009198188781738281, 0.01346588134765625, - -0.00598907470703125, -0.031585693359375, 0.0192718505859375, - -0.0298919677734375, 0.024322509765625, -0.0038299560546875, - -0.03668212890625, -0.03363037109375, 0.01568603515625, - 0.027618408203125, -0.0020351409912109375, 0.029083251953125, - 0.0151824951171875, -0.025604248046875, -0.01169586181640625, - -0.003437042236328125, -0.030426025390625, -0.004547119140625, - -0.027496337890625, -0.017486572265625, 0.016510009765625, - 0.049224853515625, -0.044769287109375, 0.0099945068359375, - 0.03118896484375, -0.033172607421875, 0.018585205078125, - -0.032806396484375, 0.02734375, -0.03875732421875, - -0.05328369140625, 0.0248870849609375, 0.0003790855407714844, - -0.0165863037109375, -0.0008473396301269531, - 0.006267547607421875, -0.0216522216796875, - -0.004703521728515625, 0.0313720703125, -0.004276275634765625, - -0.0088653564453125, -0.01087188720703125, 0.032623291015625, - -0.046142578125, -0.0183563232421875, -0.00930023193359375, - 0.040618896484375, 0.006988525390625, 0.024169921875, - 0.007266998291015625, 0.013397216796875, 0.00238800048828125, - 0.00870513916015625, 0.02020263671875, -0.033050537109375, - 0.054840087890625, 0.023162841796875, -0.0002655982971191406, - -0.050750732421875, -0.023345947265625, -0.01496124267578125, - 0.02587890625, 0.00466156005859375, -0.01174163818359375, - 0.0265960693359375, -0.0006241798400878906, - -0.0022716522216796875, -0.0010480880737304688, - -0.01158905029296875, 0.0088043212890625, 0.01372528076171875, - -0.0122833251953125, 0.0022296905517578125, - -0.0183258056640625, 0.0221405029296875, 0.0283355712890625, - 0.042083740234375, -0.00858306884765625, -0.034881591796875, - 0.0240020751953125, 0.037689208984375, -0.01555633544921875, - -0.039154052734375, 0.05511474609375, 0.01129150390625, - 0.0042266845703125, 0.0019359588623046875, - -0.0081329345703125, 0.01029205322265625, - -0.002567291259765625, -0.0022258758544921875, - -0.0098876953125, -0.013153076171875, -0.035858154296875, - 0.01329803466796875, -0.01081085205078125, 0.0192718505859375, - 0.00901031494140625, 0.00010645389556884766, - 0.0134429931640625, -0.0038509368896484375, 0.01763916015625, - 0.0283355712890625, 0.01259613037109375, 0.00826263427734375, - 0.04632568359375, -0.054901123046875, -0.051239013671875, - 0.030975341796875, 0.0223846435546875, 0.0031890869140625, - -0.01381683349609375, -0.0230712890625, -0.00873565673828125, - 0.036590576171875, -0.010467529296875, -0.03173828125, - -0.004344940185546875, -0.03387451171875, - -0.00930023193359375, 0.0092620849609375, -0.0279541015625, - 0.0113372802734375, 0.0222930908203125, 0.0179901123046875, - -0.006481170654296875, -0.01105499267578125, - -0.005947113037109375, 0.005802154541015625, -0.0125732421875, - -0.00921630859375, -0.0013303756713867188, - -0.0197906494140625, -0.005924224853515625, - -0.0263214111328125, -0.01259613037109375, - -0.005962371826171875, 0.017303466796875, -0.0138397216796875, - -0.042022705078125, 0.0295562744140625, -0.015716552734375, - 0.0291748046875, 0.035552978515625, 0.00788116455078125, - -0.01380157470703125, -0.0011653900146484375, - -0.0090179443359375, -0.001216888427734375, - 0.0025177001953125, 0.0003676414489746094, 0.0197906494140625, - -0.02069091796875, 0.01342010498046875, 0.0030536651611328125, - 0.03173828125, -0.001850128173828125, -0.052215576171875, - -0.023193359375, -0.012176513671875, -0.0166778564453125, - -0.03997802734375, -0.041229248046875, -0.017974853515625, - -0.00385284423828125, 0.005035400390625, - -0.002956390380859375, 0.033843994140625, -0.01116943359375, - 0.0279083251953125, -0.0178070068359375, 0.024627685546875, - 0.032867431640625, 0.003444671630859375, 0.0240478515625, - -0.01445770263671875, 0.040771484375, -0.01230621337890625, - 0.00666046142578125, -0.008087158203125, 0.006805419921875, - -0.004276275634765625, 0.0221405029296875, -0.024749755859375, - 0.0400390625, 0.0138397216796875, 0.0361328125, - -0.0028705596923828125, -0.003787994384765625, - 0.06475830078125, 0.0302886962890625, -0.043304443359375, - -0.04559326171875, 0.020751953125, -0.0275115966796875, - -0.02569580078125, -0.01329803466796875, 0.003986358642578125, - 0.04998779296875, -0.0101776123046875, -0.0172119140625, - 0.048004150390625, 0.014007568359375, -0.0207061767578125, - -0.0168914794921875, 0.0146026611328125, 0.0286712646484375, - 0.03729248046875, -0.0244598388671875, 0.00667572021484375, - -0.0207366943359375, 0.03302001953125, 0.00540924072265625, - -0.0284576416015625, 0.01849365234375, -0.011444091796875, - 0.001476287841796875, -0.005615234375, 0.03778076171875, - 0.02642822265625, 0.01239776611328125, -0.01181793212890625, - 0.03521728515625, 0.013031005859375, 0.01139068603515625, - 0.00672149658203125, 0.024444580078125, 0.0197601318359375, - -0.0177001953125, -0.024200439453125, 0.0216827392578125, - -0.0004317760467529297, 0.00811767578125, -0.0131683349609375, - 0.0012493133544921875, -0.01227569580078125, - 0.0030918121337890625, 0.041107177734375, 0.012481689453125, - -0.029998779296875, -0.0092620849609375, -0.01904296875, - 0.0090484619140625, -0.0301055908203125, -0.00742340087890625, - -0.01364898681640625, 0.0297088623046875, 0.0328369140625, - -0.0116729736328125, 0.00152587890625, 0.045867919921875, - 0.0035400390625, -0.03497314453125, 0.023406982421875, - 0.03289794921875, -0.028076171875, 0.01800537109375, - 0.01024627685546875, -0.004169464111328125, -0.00396728515625, - -0.0245513916015625, -0.01021575927734375, -0.031585693359375, - -0.05474853515625, 0.0168304443359375, 0.026519775390625, - 0.0267333984375, -0.01230621337890625, -0.0149383544921875, - 0.0204620361328125, 0.015380859375, -0.0626220703125, - -0.0162353515625, 0.0220794677734375, -0.0171356201171875, - -0.01561737060546875, -0.035980224609375, 0.0192718505859375, - 0.043701171875, 0.0413818359375, 0.03314208984375, - 0.00016868114471435547, -0.000579833984375, - -0.0177154541015625, -0.037384033203125, 0.0377197265625, - 0.0264739990234375, 0.01340484619140625, 0.02313232421875, - 0.04119873046875, -0.0037288665771484375, -0.0226287841796875, - -0.0005016326904296875, 0.00885772705078125, - -0.0029125213623046875, 0.007633209228515625, 0.00732421875, - -0.0113983154296875, 0.00392913818359375, - -0.00774383544921875, -0.040008544921875, -0.0231781005859375, - 0.007091522216796875, -0.0286865234375, 0.032440185546875, - 0.0177001953125, -0.015167236328125, 0.0082550048828125, - -0.01465606689453125, 0.01073455810546875, - 0.007236480712890625, 0.00146484375, 0.03265380859375, - -0.0084686279296875, 0.0301361083984375, 0.0013427734375, - -0.01102447509765625, -0.0197906494140625, - 0.01364898681640625, -0.031219482421875, -0.0229034423828125, - 0.0275115966796875, 0.0003364086151123047, 0.050811767578125, - 0.0234222412109375, 0.01097869873046875, -0.017974853515625, - -0.0154876708984375, 0.016876220703125, 0.0070037841796875, - 0.00920867919921875, -0.0135040283203125, -0.019073486328125, - 0.028564453125, 0.00928497314453125, 0.030181884765625, - -0.01273345947265625, 0.00492095947265625, - 0.0024242401123046875, -0.06781005859375, -0.00714111328125, - 0.0021343231201171875, 0.003330230712890625, - -0.0007662773132324219, 0.019500732421875, 0.0095062255859375, - -0.0262298583984375, -0.00960540771484375, - 0.006473541259765625, 0.05169677734375, -0.017730712890625, - 0.0238037109375, -0.0214691162109375, 0.0304412841796875, - -0.0126190185546875, -0.03070068359375, -0.037078857421875, - 0.022369384765625, 0.034515380859375, -0.0205841064453125, - 0.004116058349609375, -0.038787841796875, -0.0221710205078125, - -0.024993896484375, -0.021453857421875, - -0.0003190040588378906, 0.0036602020263671875, - 0.0107269287109375, 0.0152435302734375, -0.016265869140625, - -0.0222930908203125, 0.0105133056640625, -0.0185546875, - -0.015045166015625, 0.0134735107421875, 0.002307891845703125, - 0.007579803466796875, -0.0022735595703125, 0.0276336669921875, - 0.0083160400390625, 0.01552581787109375, - -0.007038116455078125, 0.008331298828125, -0.0135955810546875, - 0.020721435546875, 0.050933837890625, 0.0147552490234375, - 0.0205841064453125, -0.00490570068359375, 0.038299560546875, - 0.0301055908203125, 0.05523681640625, 0.00766754150390625, - 0.00782012939453125, -0.00164794921875, 0.0191497802734375, - 0.002674102783203125, -0.017242431640625, 0.02203369140625, - -0.0187835693359375, 0.0350341796875, -0.01007080078125, - -0.04803466796875, 0.01506805419921875, -0.031646728515625, - 0.0182342529296875, 0.0239105224609375, 0.0435791015625, - 0.0156097412109375, -0.0275115966796875, 0.0169525146484375, - -0.0036716461181640625, 0.0154571533203125, - -0.00377655029296875, -0.003070831298828125, - 0.034332275390625, -0.0034275054931640625, - -0.0285797119140625, -0.03607177734375, - -0.0008091926574707031, -0.0127410888671875, -0.036376953125, - -0.007793426513671875, -0.043792724609375, 0.0270538330078125, - -0.058013916015625, -0.01438140869140625, - 0.007251739501953125, 0.045013427734375, 0.00720977783203125, - 0.0254058837890625, 0.054718017578125, 0.0061798095703125, - -0.0198822021484375, 0.010162353515625, -0.035919189453125, - 0.0014743804931640625, -0.0060577392578125, -0.02752685546875, - -0.01453399658203125, -0.0137481689453125, - -0.00218963623046875, -0.0038661956787109375, - 0.006755828857421875, 0.00434112548828125, -0.020294189453125, - 0.0181884765625, -0.004611968994140625, -0.0200042724609375, - 0.034454345703125, -0.0709228515625, -0.028533935546875, - 0.0260772705078125, -0.044677734375, 0.021240234375, - 0.032470703125, 0.0162811279296875, -0.01093292236328125, - -0.01284027099609375, -0.028900146484375, - 0.0009975433349609375, -0.0187835693359375, - 0.0098419189453125, 0.0171966552734375, 0.030670166015625, - 0.00463104248046875, -0.022796630859375, 0.0117950439453125, - -0.028472900390625, -0.021148681640625, -0.039337158203125, - 0.0176544189453125, -0.038238525390625, -0.039581298828125, - 0.002685546875, -0.01483154296875, 0.00533294677734375, - 0.0000928044319152832, 0.01045989990234375, -0.01025390625, - -0.0207061767578125, -0.025665283203125, 0.0222015380859375, - -0.0111083984375, 0.023406982421875, -0.0209197998046875, - -0.0036716461181640625, -0.037689208984375, - 0.0189056396484375, 0.0112457275390625, -0.016204833984375, - 0.0017604827880859375, 0.01241302490234375, - -0.0038776397705078125, -0.00168609619140625, 0.043701171875, - -0.0297698974609375, 0.005054473876953125, - -0.0214385986328125, -0.026611328125, -0.01385498046875, - 0.00682830810546875, -0.0007939338684082031, - 0.030181884765625, 0.051513671875, -0.04296875, - -0.037811279296875, -0.0005049705505371094, - 0.01035308837890625, 0.0136260986328125, 0.025604248046875, - 0.0211334228515625, -0.01605224609375, -0.0228118896484375, - -0.004764556884765625, 0.01377105712890625, - -0.01389312744140625, 0.0256805419921875, - 0.0035343170166015625, -0.026397705078125, - -0.0199737548828125, -0.018646240234375, -0.025054931640625, - 0.02752685546875, 0.01459503173828125, -0.002986907958984375, - 0.038421630859375, -0.0166168212890625, 0.00832366943359375, - 0.06048583984375, 0.0234527587890625, 0.01180267333984375, - 0.00638580322265625, -0.0140838623046875, -0.0074615478515625, - -0.00616455078125, -0.02105712890625, 0.000720977783203125, - -0.01064300537109375, 0.00774383544921875, 0.016632080078125, - 0.0108795166015625, -0.0135498046875, 0.00914764404296875, - 0.034332275390625, -0.01390838623046875, -0.037353515625, - -0.0296173095703125, 0.01849365234375, -0.0230255126953125, - 0.01108551025390625, -0.0214691162109375, 0.0247955322265625, - -0.00872802734375, 0.0184478759765625, 0.00928497314453125, - 0.0202484130859375, 0.0223541259765625, 0.006092071533203125, - 0.035430908203125, -0.035614013671875, -0.0004284381866455078, - -0.0036907196044921875, 0.0053863525390625, - 0.002460479736328125, 0.00722503662109375, - -0.0257110595703125, 0.00926971435546875, 0.00696563720703125, - -0.007740020751953125, 0.01490020751953125, -0.03143310546875, - 0.0160980224609375, -0.0022335052490234375, 0.0146484375, - 0.01129913330078125, 0.019683837890625, -0.0193634033203125, - 0.0092620849609375, 0.01381683349609375, 0.001338958740234375, - 0.01309967041015625, -0.0032100677490234375, - -0.00939178466796875, 0.02569580078125, 0.030731201171875, - 0.023895263671875, 0.0019083023071289062, -0.0124969482421875, - -0.01023101806640625, -0.03558349609375, 0.022857666015625, - -0.000028312206268310547, 0.0123291015625, 0.005157470703125, - -0.01056671142578125, 0.005367279052734375, 0.009490966796875, - 0.005687713623046875, -0.00585174560546875, - 0.01279449462890625, -0.01137542724609375, - 0.005809783935546875, 0.0124359130859375, - -0.006900787353515625, 0.04034423828125, -0.0111083984375, - -0.0012426376342773438, 0.0159454345703125, -0.0450439453125, - -0.01468658447265625, 0.00832366943359375, - -0.01213836669921875, -0.0028018951416015625, - 0.0014476776123046875, -0.00453948974609375, - -0.003498077392578125, -0.0117645263671875, - 0.00293731689453125, -0.0258941650390625, 0.008209228515625, - -0.00203704833984375, 0.0144195556640625, 0.036346435546875, - -0.0119476318359375, -0.03778076171875, 0.0245819091796875, - -0.035858154296875, 0.0084991455078125, 0.01824951171875, - 0.0088653564453125, -0.0467529296875, 0.01412200927734375, - 0.01203155517578125, -0.0232391357421875, - -0.00788116455078125, 0.0123443603515625, -0.031036376953125, - 0.003704071044921875, 0.0136260986328125, - 0.0031147003173828125, 0.0007843971252441406, - 0.00490570068359375 - ], - "index": 0, - "object": "embedding" - } - ], - "model": "text-embedding-3-small", - "object": "list", - "usage": { - "prompt_tokens": 7, - "total_tokens": 7 - } - } - }, - "headers": { - "access-control-expose-headers": "X-Request-ID, CF-Ray, CF-Ray", - "alt-svc": "h3=\":443\"; ma=86400", - "cf-cache-status": "DYNAMIC", - "cf-ray": "a29c58b09a1f8029-IAD", - "connection": "keep-alive", - "content-encoding": "gzip", - "content-type": "application/json", - "date": "Wed, 12 Aug 2026 03:20:50 GMT", - "openai-organization": "braintrust-data", - "openai-processing-ms": "53", - "openai-project": "proj_vsCSXafhhByzWOThMrJcZiw9", - "openai-version": "2020-10-01", - "server": "cloudflare", - "set-cookie": "[REDACTED]", - "strict-transport-security": "max-age=31536000; includeSubDomains; preload", - "transfer-encoding": "chunked", - "x-content-type-options": "nosniff", - "x-openai-proxy-wasm": "v0.1", - "x-ratelimit-limit-requests": "10000", - "x-ratelimit-limit-tokens": "10000000", - "x-ratelimit-remaining-requests": "9999", - "x-ratelimit-remaining-tokens": "9999993", - "x-ratelimit-reset-requests": "6ms", - "x-ratelimit-reset-tokens": "0s", - "x-request-id": "req_19802e0ed763486f94feced58ca31f72" - }, - "status": 200, - "statusText": "OK" - } - }, - { - "callIndex": 2, - "id": "f7a3ad27b5067191", - "matchKey": "POST api.openai.com/v1/chat/completions", - "recordedAt": "2026-08-12T03:20:50.707Z", - "request": { - "body": { - "kind": "json", - "value": { - "max_tokens": 128, - "messages": [ - { - "content": "You must inspect live weather via the provided get_weather tool before responding.", - "role": "system" - }, - { - "content": "Use the get_weather tool for Paris, France. If you do not call the tool, the answer is invalid.", - "role": "user" - } - ], - "model": "gpt-4o-mini-2024-07-18", - "temperature": 0, - "tool_choice": "auto", - "tools": [ - { - "function": { - "description": "Get the weather for a location", - "name": "get_weather", - "parameters": { - "$schema": "http://json-schema.org/draft-07/schema#", - "additionalProperties": false, - "properties": { - "location": { - "description": "The city and country", - "type": "string" - } - }, - "required": ["location"], - "type": "object" - } - }, - "type": "function" - } - ] - } - }, - "headers": {}, - "method": "POST", - "url": "https://api.openai.com/v1/chat/completions" - }, - "response": { - "body": { - "kind": "json", - "value": { - "choices": [ - { - "finish_reason": "tool_calls", - "index": 0, - "logprobs": null, - "message": { - "annotations": [], - "content": null, - "refusal": null, - "role": "assistant", - "tool_calls": [ - { - "function": { - "arguments": "{\"location\":\"Paris, France\"}", - "name": "get_weather" - }, - "id": "call_UMuGhN6ZkwsCp9em8q4OFyuV", - "type": "function" - } - ] - } - } - ], - "created": 1786504850, - "id": "chatcmpl-EBtoIGtRYm94JXcy7Q66tHaVUPGNP", - "model": "gpt-4o-mini-2024-07-18", - "object": "chat.completion", - "service_tier": "default", - "system_fingerprint": "fp_98fa25fd02", - "usage": { - "completion_tokens": 16, - "completion_tokens_details": { - "accepted_prediction_tokens": 0, - "audio_tokens": 0, - "reasoning_tokens": 0, - "rejected_prediction_tokens": 0 - }, - "prompt_tokens": 87, - "prompt_tokens_details": { - "audio_tokens": 0, - "cached_tokens": 0 - }, - "total_tokens": 103 - } - } - }, - "headers": { - "access-control-expose-headers": "X-Request-ID, CF-Ray, CF-Ray", - "alt-svc": "h3=\":443\"; ma=86400", - "cf-cache-status": "DYNAMIC", - "cf-ray": "a29c58b15ab98029-IAD", - "connection": "keep-alive", - "content-encoding": "gzip", - "content-type": "application/json", - "date": "Wed, 12 Aug 2026 03:20:50 GMT", - "openai-organization": "braintrust-data", - "openai-processing-ms": "501", - "openai-project": "proj_vsCSXafhhByzWOThMrJcZiw9", - "openai-version": "2020-10-01", - "server": "cloudflare", - "set-cookie": "[REDACTED]", - "strict-transport-security": "max-age=31536000; includeSubDomains; preload", - "transfer-encoding": "chunked", - "x-content-type-options": "nosniff", - "x-openai-proxy-wasm": "v0.1", - "x-ratelimit-limit-requests": "30000", - "x-ratelimit-limit-tokens": "150000000", - "x-ratelimit-remaining-requests": "29999", - "x-ratelimit-remaining-tokens": "149999952", - "x-ratelimit-reset-requests": "2ms", - "x-ratelimit-reset-tokens": "0s", - "x-request-id": "req_efa3e17526274413b40f8e0ae1515236" - }, - "status": 200, - "statusText": "OK" - } - }, - { - "callIndex": 3, - "id": "26ad5653def30060", - "matchKey": "POST api.openai.com/v1/chat/completions", - "recordedAt": "2026-08-12T03:20:51.320Z", - "request": { - "body": { - "kind": "json", - "value": { - "max_tokens": 32, - "messages": [ - { - "content": "Return JSON with {\"city\":\"Paris\"}.", - "role": "user" - } - ], - "model": "gpt-4o-mini-2024-07-18", - "temperature": 0, - "tool_choice": { - "function": { - "name": "json" - }, - "type": "function" - }, - "tools": [ - { - "function": { - "description": "Respond with a JSON object.", - "name": "json", - "parameters": { - "$schema": "http://json-schema.org/draft-07/schema#", - "additionalProperties": false, - "properties": { - "city": { - "type": "string" - } - }, - "required": ["city"], - "type": "object" - } - }, - "type": "function" - } - ] - } - }, - "headers": {}, - "method": "POST", - "url": "https://api.openai.com/v1/chat/completions" - }, - "response": { - "body": { - "kind": "json", - "value": { - "choices": [ - { - "finish_reason": "stop", - "index": 0, - "logprobs": null, - "message": { - "annotations": [], - "content": null, - "refusal": null, - "role": "assistant", - "tool_calls": [ - { - "function": { - "arguments": "{\"city\":\"Paris\"}", - "name": "json" - }, - "id": "call_SQcqBNyCGzH6a0M9YVRrzVRh", - "type": "function" - } - ] - } - } - ], - "created": 1786504850, - "id": "chatcmpl-EBtoI7EcjbAELqo9caSK830h7MXBt", - "model": "gpt-4o-mini-2024-07-18", - "object": "chat.completion", - "service_tier": "default", - "system_fingerprint": "fp_9afcdcbeed", - "usage": { - "completion_tokens": 5, - "completion_tokens_details": { - "accepted_prediction_tokens": 0, - "audio_tokens": 0, - "reasoning_tokens": 0, - "rejected_prediction_tokens": 0 - }, - "prompt_tokens": 59, - "prompt_tokens_details": { - "audio_tokens": 0, - "cached_tokens": 0 - }, - "total_tokens": 64 - } - } - }, - "headers": { - "access-control-expose-headers": "X-Request-ID, CF-Ray, CF-Ray", - "alt-svc": "h3=\":443\"; ma=86400", - "cf-cache-status": "DYNAMIC", - "cf-ray": "a29c58b4ed388029-IAD", - "connection": "keep-alive", - "content-encoding": "gzip", - "content-type": "application/json", - "date": "Wed, 12 Aug 2026 03:20:51 GMT", - "openai-organization": "braintrust-data", - "openai-processing-ms": "547", - "openai-project": "proj_vsCSXafhhByzWOThMrJcZiw9", - "openai-version": "2020-10-01", - "server": "cloudflare", - "set-cookie": "[REDACTED]", - "strict-transport-security": "max-age=31536000; includeSubDomains; preload", - "transfer-encoding": "chunked", - "x-content-type-options": "nosniff", - "x-openai-proxy-wasm": "v0.1", - "x-ratelimit-limit-requests": "30000", - "x-ratelimit-limit-tokens": "150000000", - "x-ratelimit-remaining-requests": "29999", - "x-ratelimit-remaining-tokens": "149999990", - "x-ratelimit-reset-requests": "2ms", - "x-ratelimit-reset-tokens": "0s", - "x-request-id": "req_0688ec8d78b54eb1adf36824bad5e9a6" - }, - "status": 200, - "statusText": "OK" - } - }, - { - "callIndex": 4, - "id": "1e0515e907a8bb36", - "matchKey": "POST api.openai.com/v1/chat/completions", - "recordedAt": "2026-08-12T03:20:51.850Z", - "request": { - "body": { - "kind": "json", - "value": { - "max_tokens": 32, - "messages": [ - { - "content": "Stream JSON with {\"city\":\"Paris\"}.", - "role": "user" - } - ], - "model": "gpt-4o-mini-2024-07-18", - "stream": true, - "temperature": 0, - "tool_choice": { - "function": { - "name": "json" - }, - "type": "function" - }, - "tools": [ - { - "function": { - "description": "Respond with a JSON object.", - "name": "json", - "parameters": { - "$schema": "http://json-schema.org/draft-07/schema#", - "additionalProperties": false, - "properties": { - "city": { - "type": "string" - } - }, - "required": ["city"], - "type": "object" - } - }, - "type": "function" - } - ] - } - }, - "headers": {}, - "method": "POST", - "url": "https://api.openai.com/v1/chat/completions" - }, - "response": { - "body": { - "chunks": [ - "data: {\"id\":\"chatcmpl-EBtoJLtlpqaMMJdyJkkyM7JLsRTq7\",\"object\":\"chat.completion.chunk\",\"created\":1786504851,\"model\":\"gpt-4o-mini-2024-07-18\",\"service_tier\":\"default\",\"system_fingerprint\":\"fp_9afcdcbeed\",\"choices\":[{\"index\":0,\"delta\":{\"role\":\"assistant\",\"content\":null,\"tool_calls\":[{\"index\":0,\"id\":\"call_WqHLU3gBYZ0G7o9SdYcxHhpm\",\"type\":\"function\",\"function\":{\"name\":\"json\",\"arguments\":\"\"}}],\"refusal\":null},\"logprobs\":null,\"finish_reason\":null}],\"obfuscation\":\"kiOb2CCw\"}", - "data: {\"id\":\"chatcmpl-EBtoJLtlpqaMMJdyJkkyM7JLsRTq7\",\"object\":\"chat.completion.chunk\",\"created\":1786504851,\"model\":\"gpt-4o-mini-2024-07-18\",\"service_tier\":\"default\",\"system_fingerprint\":\"fp_9afcdcbeed\",\"choices\":[{\"index\":0,\"delta\":{\"tool_calls\":[{\"index\":0,\"function\":{\"arguments\":\"{\\\"\"}}]},\"logprobs\":null,\"finish_reason\":null}],\"obfuscation\":\"5XFod0Hptnw\"}", - "data: {\"id\":\"chatcmpl-EBtoJLtlpqaMMJdyJkkyM7JLsRTq7\",\"object\":\"chat.completion.chunk\",\"created\":1786504851,\"model\":\"gpt-4o-mini-2024-07-18\",\"service_tier\":\"default\",\"system_fingerprint\":\"fp_9afcdcbeed\",\"choices\":[{\"index\":0,\"delta\":{\"tool_calls\":[{\"index\":0,\"function\":{\"arguments\":\"city\"}}]},\"logprobs\":null,\"finish_reason\":null}],\"obfuscation\":\"KszwHb7sDw\"}", - "data: {\"id\":\"chatcmpl-EBtoJLtlpqaMMJdyJkkyM7JLsRTq7\",\"object\":\"chat.completion.chunk\",\"created\":1786504851,\"model\":\"gpt-4o-mini-2024-07-18\",\"service_tier\":\"default\",\"system_fingerprint\":\"fp_9afcdcbeed\",\"choices\":[{\"index\":0,\"delta\":{\"tool_calls\":[{\"index\":0,\"function\":{\"arguments\":\"\\\":\\\"\"}}]},\"logprobs\":null,\"finish_reason\":null}],\"obfuscation\":\"430kM2ZZd\"}", - "data: {\"id\":\"chatcmpl-EBtoJLtlpqaMMJdyJkkyM7JLsRTq7\",\"object\":\"chat.completion.chunk\",\"created\":1786504851,\"model\":\"gpt-4o-mini-2024-07-18\",\"service_tier\":\"default\",\"system_fingerprint\":\"fp_9afcdcbeed\",\"choices\":[{\"index\":0,\"delta\":{\"tool_calls\":[{\"index\":0,\"function\":{\"arguments\":\"Paris\"}}]},\"logprobs\":null,\"finish_reason\":null}],\"obfuscation\":\"mbpoFD0Rh\"}", - "data: {\"id\":\"chatcmpl-EBtoJLtlpqaMMJdyJkkyM7JLsRTq7\",\"object\":\"chat.completion.chunk\",\"created\":1786504851,\"model\":\"gpt-4o-mini-2024-07-18\",\"service_tier\":\"default\",\"system_fingerprint\":\"fp_9afcdcbeed\",\"choices\":[{\"index\":0,\"delta\":{\"tool_calls\":[{\"index\":0,\"function\":{\"arguments\":\"\\\"}\"}}]},\"logprobs\":null,\"finish_reason\":null}],\"obfuscation\":\"N3lxDJJGf8o\"}", - "data: {\"id\":\"chatcmpl-EBtoJLtlpqaMMJdyJkkyM7JLsRTq7\",\"object\":\"chat.completion.chunk\",\"created\":1786504851,\"model\":\"gpt-4o-mini-2024-07-18\",\"service_tier\":\"default\",\"system_fingerprint\":\"fp_9afcdcbeed\",\"choices\":[{\"index\":0,\"delta\":{},\"logprobs\":null,\"finish_reason\":\"stop\"}],\"obfuscation\":\"oc\"}", - "data: [DONE]" - ], - "kind": "sse" - }, - "headers": { - "access-control-expose-headers": "X-Request-ID, CF-Ray, CF-Ray", - "alt-svc": "h3=\":443\"; ma=86400", - "cf-cache-status": "DYNAMIC", - "cf-ray": "a29c58b8cfd98029-IAD", - "connection": "keep-alive", - "content-type": "text/event-stream; charset=utf-8", - "date": "Wed, 12 Aug 2026 03:20:51 GMT", - "openai-organization": "braintrust-data", - "openai-processing-ms": "396", - "openai-project": "proj_vsCSXafhhByzWOThMrJcZiw9", - "openai-version": "2020-10-01", - "server": "cloudflare", - "set-cookie": "[REDACTED]", - "strict-transport-security": "max-age=31536000; includeSubDomains; preload", - "transfer-encoding": "chunked", - "x-content-type-options": "nosniff", - "x-openai-proxy-wasm": "v0.1", - "x-ratelimit-limit-requests": "30000", - "x-ratelimit-limit-tokens": "150000000", - "x-ratelimit-remaining-requests": "29999", - "x-ratelimit-remaining-tokens": "149999990", - "x-ratelimit-reset-requests": "2ms", - "x-ratelimit-reset-tokens": "0s", - "x-request-id": "req_261be71c049d4ae99c5544c36b60c3a5" - }, - "status": 200, - "statusText": "OK" - } - } - ], - "meta": { - "createdAt": "2026-07-06T14:02:52.301Z" - } -} diff --git a/e2e/scenarios/ai-sdk-instrumentation/__cassettes__/ai-sdk-v3.cassette.json b/e2e/scenarios/ai-sdk-instrumentation/__cassettes__/ai-sdk-v3.cassette.json deleted file mode 100644 index ac6394153..000000000 --- a/e2e/scenarios/ai-sdk-instrumentation/__cassettes__/ai-sdk-v3.cassette.json +++ /dev/null @@ -1,1151 +0,0 @@ -{ - "entries": [ - { - "callIndex": 0, - "id": "2583f51940dc092e", - "matchKey": "POST api.openai.com/v1/chat/completions", - "recordedAt": "2026-08-12T03:20:42.139Z", - "request": { - "body": { - "kind": "json", - "value": { - "max_tokens": 24, - "messages": [ - { - "content": "Reply with the single token PARIS and no punctuation.", - "role": "user" - } - ], - "model": "gpt-4o-mini-2024-07-18", - "temperature": 0 - } - }, - "headers": {}, - "method": "POST", - "url": "https://api.openai.com/v1/chat/completions" - }, - "response": { - "body": { - "kind": "json", - "value": { - "choices": [ - { - "finish_reason": "stop", - "index": 0, - "logprobs": null, - "message": { - "annotations": [], - "content": "PARIS", - "refusal": null, - "role": "assistant" - } - } - ], - "created": 1786504841, - "id": "chatcmpl-EBto9R78okjPTViSZCpqfiBBIKrs5", - "model": "gpt-4o-mini-2024-07-18", - "object": "chat.completion", - "service_tier": "default", - "system_fingerprint": "fp_fabbcdf5d9", - "usage": { - "completion_tokens": 2, - "completion_tokens_details": { - "accepted_prediction_tokens": 0, - "audio_tokens": 0, - "reasoning_tokens": 0, - "rejected_prediction_tokens": 0 - }, - "prompt_tokens": 18, - "prompt_tokens_details": { - "audio_tokens": 0, - "cached_tokens": 0 - }, - "total_tokens": 20 - } - } - }, - "headers": { - "access-control-expose-headers": "X-Request-ID, CF-Ray, CF-Ray", - "alt-svc": "h3=\":443\"; ma=86400", - "cf-cache-status": "DYNAMIC", - "cf-ray": "a29c587d3abe8029-IAD", - "connection": "keep-alive", - "content-encoding": "gzip", - "content-type": "application/json", - "date": "Wed, 12 Aug 2026 03:20:42 GMT", - "openai-organization": "braintrust-data", - "openai-processing-ms": "279", - "openai-project": "proj_vsCSXafhhByzWOThMrJcZiw9", - "openai-version": "2020-10-01", - "server": "cloudflare", - "set-cookie": "[REDACTED]", - "strict-transport-security": "max-age=31536000; includeSubDomains; preload", - "transfer-encoding": "chunked", - "x-content-type-options": "nosniff", - "x-openai-proxy-wasm": "v0.1", - "x-ratelimit-limit-requests": "30000", - "x-ratelimit-limit-tokens": "150000000", - "x-ratelimit-remaining-requests": "29999", - "x-ratelimit-remaining-tokens": "149999985", - "x-ratelimit-reset-requests": "2ms", - "x-ratelimit-reset-tokens": "0s", - "x-request-id": "req_84791dd2fdbb49ceb596955e38420fe0" - }, - "status": 200, - "statusText": "OK" - } - }, - { - "callIndex": 1, - "id": "901ff936ff6b7255", - "matchKey": "POST api.openai.com/v1/chat/completions", - "recordedAt": "2026-08-12T03:20:42.559Z", - "request": { - "body": { - "kind": "json", - "value": { - "max_tokens": 32, - "messages": [ - { - "content": "Count from 1 to 3 and include the words one two three.", - "role": "user" - } - ], - "model": "gpt-4o-mini-2024-07-18", - "stream": true, - "temperature": 0 - } - }, - "headers": {}, - "method": "POST", - "url": "https://api.openai.com/v1/chat/completions" - }, - "response": { - "body": { - "chunks": [ - "data: {\"id\":\"chatcmpl-EBtoAKna8F7GKBPsPskHdBhRm8Byl\",\"object\":\"chat.completion.chunk\",\"created\":1786504842,\"model\":\"gpt-4o-mini-2024-07-18\",\"service_tier\":\"default\",\"system_fingerprint\":\"fp_0950b7f384\",\"choices\":[{\"index\":0,\"delta\":{\"role\":\"assistant\",\"content\":\"\",\"refusal\":null},\"logprobs\":null,\"finish_reason\":null}],\"obfuscation\":\"IbkBYq\"}", - "data: {\"id\":\"chatcmpl-EBtoAKna8F7GKBPsPskHdBhRm8Byl\",\"object\":\"chat.completion.chunk\",\"created\":1786504842,\"model\":\"gpt-4o-mini-2024-07-18\",\"service_tier\":\"default\",\"system_fingerprint\":\"fp_0950b7f384\",\"choices\":[{\"index\":0,\"delta\":{\"content\":\"One\"},\"logprobs\":null,\"finish_reason\":null}],\"obfuscation\":\"B7cq1\"}", - "data: {\"id\":\"chatcmpl-EBtoAKna8F7GKBPsPskHdBhRm8Byl\",\"object\":\"chat.completion.chunk\",\"created\":1786504842,\"model\":\"gpt-4o-mini-2024-07-18\",\"service_tier\":\"default\",\"system_fingerprint\":\"fp_0950b7f384\",\"choices\":[{\"index\":0,\"delta\":{\"content\":\",\"},\"logprobs\":null,\"finish_reason\":null}],\"obfuscation\":\"UZIccye\"}", - "data: {\"id\":\"chatcmpl-EBtoAKna8F7GKBPsPskHdBhRm8Byl\",\"object\":\"chat.completion.chunk\",\"created\":1786504842,\"model\":\"gpt-4o-mini-2024-07-18\",\"service_tier\":\"default\",\"system_fingerprint\":\"fp_0950b7f384\",\"choices\":[{\"index\":0,\"delta\":{\"content\":\" two\"},\"logprobs\":null,\"finish_reason\":null}],\"obfuscation\":\"pMpo\"}", - "data: {\"id\":\"chatcmpl-EBtoAKna8F7GKBPsPskHdBhRm8Byl\",\"object\":\"chat.completion.chunk\",\"created\":1786504842,\"model\":\"gpt-4o-mini-2024-07-18\",\"service_tier\":\"default\",\"system_fingerprint\":\"fp_0950b7f384\",\"choices\":[{\"index\":0,\"delta\":{\"content\":\",\"},\"logprobs\":null,\"finish_reason\":null}],\"obfuscation\":\"J1nfxcN\"}", - "data: {\"id\":\"chatcmpl-EBtoAKna8F7GKBPsPskHdBhRm8Byl\",\"object\":\"chat.completion.chunk\",\"created\":1786504842,\"model\":\"gpt-4o-mini-2024-07-18\",\"service_tier\":\"default\",\"system_fingerprint\":\"fp_0950b7f384\",\"choices\":[{\"index\":0,\"delta\":{\"content\":\" three\"},\"logprobs\":null,\"finish_reason\":null}],\"obfuscation\":\"IA\"}", - "data: {\"id\":\"chatcmpl-EBtoAKna8F7GKBPsPskHdBhRm8Byl\",\"object\":\"chat.completion.chunk\",\"created\":1786504842,\"model\":\"gpt-4o-mini-2024-07-18\",\"service_tier\":\"default\",\"system_fingerprint\":\"fp_0950b7f384\",\"choices\":[{\"index\":0,\"delta\":{\"content\":\".\"},\"logprobs\":null,\"finish_reason\":null}],\"obfuscation\":\"17pj5wD\"}", - "data: {\"id\":\"chatcmpl-EBtoAKna8F7GKBPsPskHdBhRm8Byl\",\"object\":\"chat.completion.chunk\",\"created\":1786504842,\"model\":\"gpt-4o-mini-2024-07-18\",\"service_tier\":\"default\",\"system_fingerprint\":\"fp_0950b7f384\",\"choices\":[{\"index\":0,\"delta\":{},\"logprobs\":null,\"finish_reason\":\"stop\"}],\"obfuscation\":\"PG\"}", - "data: [DONE]" - ], - "kind": "sse" - }, - "headers": { - "access-control-expose-headers": "X-Request-ID, CF-Ray, CF-Ray", - "alt-svc": "h3=\":443\"; ma=86400", - "cf-cache-status": "DYNAMIC", - "cf-ray": "a29c587f7ce48029-IAD", - "connection": "keep-alive", - "content-type": "text/event-stream; charset=utf-8", - "date": "Wed, 12 Aug 2026 03:20:42 GMT", - "openai-organization": "braintrust-data", - "openai-processing-ms": "240", - "openai-project": "proj_vsCSXafhhByzWOThMrJcZiw9", - "openai-version": "2020-10-01", - "server": "cloudflare", - "set-cookie": "[REDACTED]", - "strict-transport-security": "max-age=31536000; includeSubDomains; preload", - "transfer-encoding": "chunked", - "x-content-type-options": "nosniff", - "x-openai-proxy-wasm": "v0.1", - "x-ratelimit-limit-requests": "30000", - "x-ratelimit-limit-tokens": "150000000", - "x-ratelimit-remaining-requests": "29999", - "x-ratelimit-remaining-tokens": "149999985", - "x-ratelimit-reset-requests": "2ms", - "x-ratelimit-reset-tokens": "0s", - "x-request-id": "req_a35d2f97e1e047d9b705abdd4b39a9ae" - }, - "status": 200, - "statusText": "OK" - } - }, - { - "callIndex": 0, - "id": "49a95bf2b3466013", - "matchKey": "POST api.openai.com/v1/embeddings", - "recordedAt": "2026-08-12T03:20:42.754Z", - "request": { - "body": { - "kind": "json", - "value": { - "encoding_format": "float", - "input": ["Paris is the capital of France."], - "model": "text-embedding-3-small" - } - }, - "headers": {}, - "method": "POST", - "url": "https://api.openai.com/v1/embeddings" - }, - "response": { - "body": { - "kind": "json", - "value": { - "data": [ - { - "embedding": [ - 0.0196380615234375, 0.019073486328125, -0.01320648193359375, - -0.0206451416015625, -0.0188751220703125, 0.02972412109375, - -0.020599365234375, 0.023651123046875, 0.006359100341796875, - -0.017059326171875, 0.004039764404296875, 0.01035308837890625, - -0.028961181640625, 0.00681304931640625, -0.03192138671875, - 0.02520751953125, -0.0282440185546875, 0.0501708984375, - 0.09149169921875, -0.020050048828125, 0.022308349609375, - -0.01296234130859375, -0.01445770263671875, 0.03436279296875, - 0.052764892578125, -0.0010938644409179688, 0.0081634521484375, - 0.042510986328125, 0.003047943115234375, 0.059844970703125, - 0.0164337158203125, -0.0078582763671875, - -0.0024394989013671875, 0.02423095703125, 0.0195159912109375, - -0.061065673828125, -0.0133514404296875, -0.038421630859375, - 0.00791168212890625, 0.027679443359375, 0.01708984375, - -0.0284271240234375, -0.0287322998046875, 0.03277587890625, - -0.035919189453125, -0.04705810546875, -0.0094451904296875, - -0.022491455078125, 0.02313232421875, -0.0037364959716796875, - 0.045654296875, -0.00487518310546875, 0.020172119140625, - 0.009246826171875, 0.00449371337890625, 0.022552490234375, - 0.03277587890625, 0.0433349609375, 0.02569580078125, - 0.0035457611083984375, 0.051788330078125, 0.020599365234375, - 0.04046630859375, 0.0177459716796875, -0.0055999755859375, - 0.03271484375, 0.0215606689453125, 0.032257080078125, - 0.050048828125, 0.0258026123046875, 0.018768310546875, - 0.044342041015625, -0.005954742431640625, 0.00675201416015625, - 0.0188751220703125, 0.01470947265625, 0.00463104248046875, - 0.038787841796875, 0.017364501953125, -0.03466796875, - -0.0016241073608398438, 0.01056671142578125, - 0.0137481689453125, -0.0216827392578125, -0.00678253173828125, - -0.01279449462890625, -0.00383758544921875, - 0.0027370452880859375, -0.0027408599853515625, - -0.0220947265625, 0.0014705657958984375, -0.0117034912109375, - -0.0172882080078125, 0.015655517578125, -0.03094482421875, - 0.00457000732421875, 0.0211029052734375, -0.04888916015625, - -0.058837890625, 0.02716064453125, 0.0279083251953125, - 0.0013456344604492188, -0.046783447265625, 0.0316162109375, - 0.0154876708984375, -0.01546478271484375, - 0.00037980079650878906, -0.0005192756652832031, - 0.0143280029296875, -0.040924072265625, -0.021728515625, - 0.004119873046875, -0.0002334117889404297, 0.032440185546875, - -0.059783935546875, 0.01092529296875, 0.014007568359375, - -0.00711822509765625, 0.031036376953125, - -0.007556915283203125, -0.00634002685546875, 0.0025634765625, - -0.04180908203125, 0.0404052734375, -0.03387451171875, - -0.048248291015625, 0.00656890869140625, 0.0265350341796875, - 0.0227813720703125, -0.00855255126953125, 0.00469970703125, - -0.044952392578125, 0.01690673828125, 0.025909423828125, - 0.05059814453125, -0.0096282958984375, 0.033782958984375, - -0.00574493408203125, 0.0175018310546875, 0.01776123046875, - -0.015625, 0.0285797119140625, -0.01055908203125, - 0.0364990234375, -0.018280029296875, -0.0338134765625, - -0.0313720703125, 0.040069580078125, 0.0101470947265625, - -0.04278564453125, 0.0164794921875, -0.0212554931640625, - 0.00272369384765625, 0.0289459228515625, - -0.003696441650390625, -0.0621337890625, 0.014434814453125, - -0.00196075439453125, 0.0384521484375, 0.025665283203125, - -0.036865234375, 0.0212860107421875, -0.046142578125, - -0.0162811279296875, -0.0034656524658203125, - 0.00821685791015625, -0.00901031494140625, - -0.00539398193359375, 0.011871337890625, 0.0160064697265625, - -0.0234527587890625, 0.004573822021484375, 0.0511474609375, - 0.038909912109375, 0.0225677490234375, -0.0139312744140625, - -0.018524169921875, 0.08013916015625, 0.026275634765625, - -0.0214385986328125, -0.01105499267578125, - -0.01430511474609375, 0.03076171875, -0.01468658447265625, - 0.0189056396484375, 0.0250244140625, -0.0206146240234375, - -0.0269317626953125, -0.0172882080078125, 0.05322265625, - 0.061492919921875, -0.0190887451171875, 0.056365966796875, - -0.04541015625, -0.0628662109375, 0.0241546630859375, - 0.002185821533203125, 0.0067596435546875, 0.00933074951171875, - 0.0079803466796875, -0.039215087890625, 0.041900634765625, - -0.0055999755859375, -0.0033016204833984375, - -0.01486968994140625, 0.019073486328125, -0.0100555419921875, - -0.04833984375, 0.029510498046875, -0.00475311279296875, - -0.01241302490234375, -0.005481719970703125, - -0.0262298583984375, 0.038421630859375, 0.038543701171875, - -0.01038360595703125, 0.012939453125, 0.033966064453125, - 0.0299072265625, -0.005496978759765625, 0.03466796875, - 0.023406982421875, 0.0113525390625, -0.0155792236328125, - -0.048919677734375, 0.055419921875, 0.05267333984375, - 0.00608062744140625, -0.04461669921875, 0.002704620361328125, - 0.00434112548828125, -0.0286407470703125, - -0.01476287841796875, -0.01540374755859375, - -0.0269317626953125, -0.019195556640625, 0.0035400390625, - -0.01326751708984375, -0.047821044921875, 0.0193634033203125, - -0.033843994140625, 0.0185394287109375, 0.0141448974609375, - 0.0026836395263671875, -0.00033974647521972656, - 0.002651214599609375, 0.01383209228515625, 0.03717041015625, - -0.042083740234375, 0.01270294189453125, 0.04339599609375, - -0.0172119140625, -0.025360107421875, 0.0172576904296875, - -0.035430908203125, 0.035125732421875, 0.00611114501953125, - -0.00634002685546875, -0.047698974609375, - -0.00983428955078125, 0.0104217529296875, 0.019866943359375, - -0.00997161865234375, -0.0018606185913085938, - -0.008392333984375, 0.019195556640625, -0.00960540771484375, - -0.023651123046875, -0.022491455078125, -0.030517578125, - 0.034912109375, -0.0008358955383300781, -0.0177154541015625, - 0.0290985107421875, -0.020965576171875, 0.005283355712890625, - -0.028228759765625, 0.0008840560913085938, -0.001129150390625, - 0.015777587890625, -0.0218505859375, 0.0443115234375, - -0.01230621337890625, 0.01255035400390625, - -0.00955963134765625, -0.01983642578125, 0.04046630859375, - 0.0672607421875, -0.0325927734375, 0.052032470703125, - 0.02166748046875, -0.021240234375, -0.033966064453125, - 0.022613525390625, -0.008087158203125, 0.006885528564453125, - 0.03558349609375, 0.0010814666748046875, 0.006816864013671875, - 0.0125274658203125, -0.0239105224609375, -0.01180267333984375, - 0.004608154296875, 0.0152740478515625, -0.04949951171875, - 0.0484619140625, -0.045501708984375, 0.019683837890625, - 0.0521240234375, 0.005802154541015625, -0.054107666015625, - -0.03643798828125, 0.04638671875, -0.0677490234375, - -0.01151275634765625, 0.007213592529296875, - -0.0158538818359375, 0.019561767578125, 0.03448486328125, - 0.011016845703125, -0.0061187744140625, 0.0125732421875, - 0.007537841796875, 0.002048492431640625, -0.0272369384765625, - 0.03173828125, 0.01447296142578125, 0.03839111328125, - -0.02783203125, -0.0063323974609375, 0.0008225440979003906, - 0.00875091552734375, -0.00853729248046875, 0.06134033203125, - -0.0401611328125, -0.021484375, 0.0031414031982421875, - 0.029296875, 0.003040313720703125, -0.0085601806640625, - -0.0253448486328125, 0.0270233154296875, -0.035919189453125, - 0.0270233154296875, -0.0033855438232421875, - -0.01428985595703125, 0.0377197265625, 0.0011196136474609375, - -0.00563812255859375, -0.03594970703125, -0.0191650390625, - -0.0196075439453125, 0.0004887580871582031, 0.0313720703125, - -0.040771484375, 0.0192108154296875, 0.064208984375, - 0.0094451904296875, -0.05694580078125, 0.01303863525390625, - -0.0164947509765625, -0.052764892578125, -0.0163116455078125, - -0.018951416015625, -0.036712646484375, -0.0084228515625, - -0.0016994476318359375, -0.005573272705078125, - -0.03851318359375, 0.07061767578125, -0.049896240234375, - -0.027740478515625, 0.0287628173828125, -0.028839111328125, - 0.04705810546875, -0.0303955078125, -0.0250396728515625, - -0.008270263671875, -0.047637939453125, -0.0262298583984375, - -0.0643310546875, -0.0024242401123046875, 0.0355224609375, - -0.047149658203125, 0.032012939453125, 0.03704833984375, - 0.043701171875, -0.05511474609375, 0.01226806640625, - -0.006153106689453125, 0.012237548828125, 0.00476837158203125, - 0.00540924072265625, 0.02252197265625, -0.03302001953125, - -0.0009093284606933594, -0.0104217529296875, - -0.00444793701171875, -0.0295257568359375, - -0.0172882080078125, 0.002864837646484375, -0.039031982421875, - 0.01010894775390625, -0.006862640380859375, 0.042938232421875, - 0.028167724609375, 0.007511138916015625, -0.0171661376953125, - 0.0216217041015625, -0.022247314453125, 0.00659942626953125, - -0.03790283203125, 0.01537322998046875, - -0.0037822723388671875, 0.01319122314453125, - 0.054046630859375, 0.0298919677734375, -0.07373046875, - 0.033111572265625, -0.021514892578125, 0.03753662109375, - 0.004749298095703125, -0.058837890625, 0.01134490966796875, - 0.0028514862060546875, -0.007328033447265625, - -0.01137542724609375, -0.06524658203125, -0.0193023681640625, - 0.03265380859375, 0.00508880615234375, 0.0213623046875, - 0.0005936622619628906, -0.0084991455078125, - 0.0110015869140625, 0.01507568359375, 0.0099639892578125, - 0.01019287109375, -0.01430511474609375, 0.0016298294067382812, - 0.047576904296875, -0.017364501953125, 0.01197052001953125, - -0.03570556640625, -0.0462646484375, -0.0174102783203125, - 0.055755615234375, 0.0299072265625, -0.045867919921875, - 0.060028076171875, -0.031219482421875, -0.002826690673828125, - -0.0088043212890625, -0.01319122314453125, - -0.0246429443359375, 0.0300750732421875, 0.0197296142578125, - 0.06988525390625, 0.00811767578125, -0.03424072265625, - 0.004909515380859375, 0.046600341796875, - -0.0038928985595703125, 0.044769287109375, 0.032745361328125, - 0.018707275390625, 0.024261474609375, 0.0193939208984375, - -0.038818359375, -0.031280517578125, 0.015869140625, - 0.0335693359375, 0.0224456787109375, 0.02581787109375, - 0.007049560546875, 0.00487518310546875, -0.059600830078125, - 0.03875732421875, -0.0494384765625, -0.06390380859375, - -0.0011310577392578125, 0.021942138671875, - -0.0187530517578125, 0.0063018798828125, -0.0794677734375, - -0.00656890869140625, 0.00356292724609375, - 0.0019741058349609375, -0.0234832763671875, - 0.0159759521484375, 0.052032470703125, -0.038421630859375, - 0.01377105712890625, -0.00019228458404541016, - 0.0215606689453125, -0.006824493408203125, -0.053955078125, - -0.0105743408203125, -0.0272216796875, -0.02362060546875, - 0.023193359375, 0.0016756057739257812, 0.0499267578125, - 0.0129547119140625, 0.033172607421875, 0.08331298828125, - 0.0103759765625, 0.003932952880859375, 0.00826263427734375, - -0.0260772705078125, 0.0300445556640625, -0.0252685546875, - 0.0322265625, 0.01165008544921875, -0.01071929931640625, - -0.0037631988525390625, -0.02313232421875, 0.0157470703125, - 0.0116424560546875, 0.0026683807373046875, 0.0199127197265625, - 0.0246124267578125, -0.01528167724609375, 0.004302978515625, - 0.032928466796875, 0.01409149169921875, -0.024322509765625, - -0.007480621337890625, 0.04766845703125, -0.04901123046875, - 0.0227203369140625, -0.0255889892578125, -0.014129638671875, - -0.0164337158203125, 0.0095062255859375, 0.01837158203125, - 0.01837158203125, 0.05340576171875, 0.00007402896881103516, - 0.03070068359375, 0.003925323486328125, -0.0161285400390625, - -0.00675201416015625, -0.0189666748046875, - -0.01511383056640625, -0.0167999267578125, 0.0219573974609375, - -0.0273590087890625, -0.034515380859375, -0.01702880859375, - -0.024627685546875, 0.045806884765625, -0.03558349609375, - -0.0439453125, -0.0007295608520507812, -0.0037212371826171875, - -0.00934600830078125, -0.00396728515625, 0.0269317626953125, - -0.01520538330078125, -0.00968170166015625, 0.063232421875, - 0.0379638671875, 0.0172882080078125, 0.01885986328125, - 0.036865234375, 0.00424957275390625, 0.010406494140625, - -0.01483917236328125, -0.022186279296875, - -0.01180267333984375, 0.0484619140625, 0.01297760009765625, - 0.0174713134765625, 0.020751953125, 0.01824951171875, - 0.01013946533203125, -0.00399017333984375, -0.029998779296875, - 0.0654296875, -0.005413055419921875, 0.0135650634765625, - 0.032470703125, -0.03521728515625, -0.04522705078125, - 0.0218505859375, 0.003498077392578125, -0.002376556396484375, - -0.0419921875, -0.0294647216796875, -0.003753662109375, - 0.01380157470703125, 0.0174560546875, -0.0234375, - 0.01435089111328125, 0.0014057159423828125, - 0.0113372802734375, 0.000843048095703125, -0.035858154296875, - -0.01200103759765625, 0.0024013519287109375, - 0.0251007080078125, -0.0193023681640625, -0.0242462158203125, - 0.01535797119140625, -0.0369873046875, 0.00672149658203125, - -0.0191650390625, 0.022369384765625, -0.0075225830078125, - 0.0176544189453125, -0.0187530517578125, 0.0154266357421875, - 0.028350830078125, -0.00547027587890625, 0.0279541015625, - -0.0240631103515625, 0.02398681640625, -0.0128021240234375, - -0.007293701171875, 0.0142974853515625, 0.03460693359375, - -0.032012939453125, 0.005584716796875, -0.00534820556640625, - 0.04705810546875, 0.018890380859375, 0.0184326171875, - -0.01000213623046875, -0.0172882080078125, 0.0244140625, - 0.01416778564453125, 0.045562744140625, 0.012786865234375, - -0.0233917236328125, -0.025146484375, -0.00409698486328125, - -0.019775390625, 0.00835418701171875, -0.0301971435546875, - 0.041046142578125, 0.012481689453125, -0.0015058517456054688, - 0.0059967041015625, -0.00667572021484375, 0.01248931884765625, - 0.02105712890625, 0.04742431640625, 0.0295562744140625, - -0.04473876953125, 0.05364990234375, 0.021392822265625, - -0.01189422607421875, 0.003597259521484375, - 0.004817962646484375, 0.03076171875, -0.0034389495849609375, - 0.0184326171875, -0.0283966064453125, 0.05914306640625, - -0.0222625732421875, -0.0287017822265625, 0.028778076171875, - -0.0350341796875, -0.0222015380859375, 0.02850341796875, - 0.022308349609375, 0.01444244384765625, 0.04388427734375, - 0.028167724609375, -0.04718017578125, -0.03582763671875, - 0.04150390625, 0.003902435302734375, 0.0093536376953125, - -0.02203369140625, -0.004726409912109375, -0.0140228271484375, - -0.049285888671875, -0.0005688667297363281, -0.006591796875, - 0.0171356201171875, -0.01517486572265625, 0.0199432373046875, - -0.013031005859375, -0.0202789306640625, - -0.002544403076171875, 0.005962371826171875, 0.0185546875, - -0.01016998291015625, -0.021697998046875, - -0.01117706298828125, -0.0189361572265625, 0.0223541259765625, - 0.0265960693359375, -0.0296173095703125, 0.019287109375, - -0.0141754150390625, 0.01216888427734375, 0.034698486328125, - -0.033782958984375, 0.0007343292236328125, -0.034149169921875, - 0.0001068115234375, 0.0009908676147460938, 0.0228271484375, - 0.00920867919921875, -0.01145172119140625, - -0.004093170166015625, 0.00984954833984375, - -0.0168609619140625, -0.039886474609375, - -0.007373809814453125, 0.007198333740234375, - 0.025665283203125, 0.01123046875, 0.0092315673828125, - -0.03607177734375, 0.0251922607421875, -0.01788330078125, - -0.007472991943359375, -0.03778076171875, -0.0200042724609375, - -0.01473236083984375, -0.01132965087890625, 0.0115966796875, - -0.0003600120544433594, -0.014190673828125, - -0.0032367706298828125, 0.0017652511596679688, - -0.023895263671875, -0.02532958984375, -0.0113525390625, - 0.0139923095703125, 0.01377105712890625, 0.01204681396484375, - 0.00659942626953125, -0.0007576942443847656, - -0.05926513671875, 0.007549285888671875, -0.033905029296875, - 0.00885009765625, 0.00292205810546875, -0.023193359375, - 0.022186279296875, 0.06939697265625, -0.02947998046875, - -0.0032253265380859375, -0.009796142578125, - -0.0232391357421875, -0.0031147003173828125, - -0.0014448165893554688, 0.0133056640625, - -0.003246307373046875, 0.033447265625, -0.0108184814453125, - 0.02032470703125, 0.00016582012176513672, - -0.0002765655517578125, -0.0404052734375, 0.00383758544921875, - 0.01140594482421875, -0.025543212890625, - -0.005756378173828125, 0.0184478759765625, -0.027191162109375, - 0.010162353515625, -0.01020050048828125, -0.0146636962890625, - -0.01611328125, -0.0227813720703125, 0.0262298583984375, - -0.036590576171875, -0.0228118896484375, 0.01200103759765625, - 0.03369140625, -0.04632568359375, -0.012908935546875, - -0.033050537109375, -0.035858154296875, 0.001895904541015625, - -0.00644683837890625, 0.0162811279296875, 0.0193023681640625, - -0.00662994384765625, -0.01361846923828125, 0.04168701171875, - -0.0478515625, 0.01113128662109375, -0.018890380859375, - 0.00014603137969970703, -0.015716552734375, - -0.01471710205078125, -0.01258087158203125, 0.00994873046875, - -0.028350830078125, 0.05316162109375, -0.02044677734375, - -0.0269927978515625, -0.0506591796875, -0.0142364501953125, - 0.0009512901306152344, 0.00801849365234375, - 0.0181732177734375, 0.04010009765625, 0.01983642578125, - -0.028778076171875, -0.004695892333984375, - 0.0011377334594726562, 0.0002841949462890625, - -0.007450103759765625, -0.038360595703125, - -0.01195526123046875, -0.037689208984375, 0.03973388671875, - -0.0227813720703125, -0.0092620849609375, -0.038116455078125, - 0.0167388916015625, 0.01529693603515625, - -0.002117156982421875, -0.01233673095703125, - -0.00740814208984375, -0.04132080078125, 0.03277587890625, - -0.026519775390625, 0.033172607421875, 0.0306549072265625, - -0.0022640228271484375, 0.0081024169921875, - -0.0269317626953125, 0.020233154296875, 0.0038509368896484375, - 0.0226898193359375, 0.00959014892578125, 0.039459228515625, - 0.003368377685546875, -0.0233306884765625, 0.0083160400390625, - 0.01971435546875, -0.035430908203125, 0.0504150390625, - 0.006153106689453125, -0.05767822265625, 0.00940704345703125, - -0.005260467529296875, 0.01422882080078125, - -0.0123748779296875, -0.00731658935546875, - 0.006916046142578125, -0.0230865478515625, 0.05035400390625, - -0.0291595458984375, 0.022705078125, -0.03564453125, - 0.039642333984375, 0.004154205322265625, 0.03436279296875, - 0.0035457611083984375, -0.0321044921875, -0.025146484375, - 0.003063201904296875, -0.030975341796875, -0.033599853515625, - 0.0011835098266601562, -0.00994873046875, -0.018798828125, - -0.03802490234375, -0.0288848876953125, 0.05987548828125, - 0.005603790283203125, -0.032440185546875, - 0.0026149749755859375, 0.011993408203125, - 0.001346588134765625, 0.0190887451171875, -0.009613037109375, - 0.01007080078125, -0.0171356201171875, -0.0546875, - -0.007312774658203125, 0.0242767333984375, 0.04388427734375, - -0.00441741943359375, -0.030609130859375, -0.0135498046875, - -0.0357666015625, -0.0108795166015625, 0.0157928466796875, - 0.0311126708984375, 0.01108551025390625, - 0.0009121894836425781, 0.01349639892578125, - -0.005985260009765625, -0.031585693359375, 0.019256591796875, - -0.029876708984375, 0.02435302734375, -0.0038127899169921875, - -0.036712646484375, -0.033660888671875, 0.0157012939453125, - 0.027618408203125, -0.0020122528076171875, 0.0290679931640625, - 0.01519775390625, -0.025604248046875, -0.01172637939453125, - -0.003452301025390625, -0.0303955078125, -0.00453948974609375, - -0.027496337890625, -0.01751708984375, 0.0164794921875, - 0.049224853515625, -0.044769287109375, 0.0099639892578125, - 0.03118896484375, -0.033203125, 0.0185546875, - -0.03277587890625, 0.02734375, -0.038726806640625, - -0.053253173828125, 0.024932861328125, 0.00039267539978027344, - -0.016571044921875, -0.0008702278137207031, - 0.006275177001953125, -0.021728515625, -0.004669189453125, - 0.031402587890625, -0.0042877197265625, -0.0088653564453125, - -0.010894775390625, 0.032623291015625, -0.046173095703125, - -0.0183563232421875, -0.0092620849609375, 0.040618896484375, - 0.006977081298828125, 0.0242156982421875, - 0.007274627685546875, 0.0134124755859375, 0.00235748291015625, - 0.00870513916015625, 0.0201873779296875, -0.033050537109375, - 0.05487060546875, 0.0231170654296875, -0.0002980232238769531, - -0.050750732421875, -0.0233917236328125, -0.01499176025390625, - 0.02587890625, 0.00461578369140625, -0.01174163818359375, - 0.026611328125, -0.0006389617919921875, -0.002239227294921875, - -0.0010595321655273438, -0.01160430908203125, - 0.008819580078125, 0.01372528076171875, -0.01226806640625, - 0.002239227294921875, -0.0183258056640625, 0.0221405029296875, - 0.0283203125, 0.0421142578125, -0.00855255126953125, - -0.034881591796875, 0.0240020751953125, 0.0377197265625, - -0.0155029296875, -0.03912353515625, 0.055145263671875, - 0.011260986328125, 0.004222869873046875, - 0.0019702911376953125, -0.0081024169921875, - 0.0102691650390625, -0.0025806427001953125, - -0.0022258758544921875, -0.00988006591796875, - -0.013153076171875, -0.035858154296875, 0.0132598876953125, - -0.0107879638671875, 0.019256591796875, 0.009033203125, - 0.00010395050048828125, 0.01342010498046875, -0.00390625, - 0.0177001953125, 0.028350830078125, 0.0125732421875, - 0.00830841064453125, 0.04632568359375, -0.054901123046875, - -0.051239013671875, 0.031005859375, 0.02239990234375, - 0.0031948089599609375, -0.01383209228515625, -0.0230712890625, - -0.00870513916015625, 0.03656005859375, -0.01050567626953125, - -0.031768798828125, -0.00438690185546875, -0.033843994140625, - -0.00930023193359375, 0.00926971435546875, -0.0279541015625, - 0.01134490966796875, 0.02227783203125, 0.017974853515625, - -0.006488800048828125, -0.0110931396484375, - -0.005939483642578125, 0.005809783935546875, -0.0125732421875, - -0.0092620849609375, -0.0013303756713867188, - -0.0197906494140625, -0.005931854248046875, -0.02630615234375, - -0.01263427734375, -0.006008148193359375, 0.017303466796875, - -0.0138702392578125, -0.0419921875, 0.0295562744140625, - -0.015716552734375, 0.0291748046875, 0.0355224609375, - 0.00782012939453125, -0.013824462890625, - -0.0011453628540039062, -0.009033203125, - -0.0012044906616210938, 0.002529144287109375, - 0.0003771781921386719, 0.01983642578125, -0.0207061767578125, - 0.01340484619140625, 0.003055572509765625, 0.031707763671875, - -0.0019130706787109375, -0.05224609375, -0.023223876953125, - -0.012176513671875, -0.01666259765625, -0.040008544921875, - -0.041229248046875, -0.01800537109375, -0.0038776397705078125, - 0.005023956298828125, -0.0029544830322265625, - 0.03387451171875, -0.011199951171875, 0.0279388427734375, - -0.01776123046875, 0.0246124267578125, 0.032867431640625, - 0.0034275054931640625, 0.0240325927734375, -0.014434814453125, - 0.040771484375, -0.0123138427734375, 0.006633758544921875, - -0.00806427001953125, 0.006805419921875, -0.00426483154296875, - 0.0221405029296875, -0.024749755859375, 0.040069580078125, - 0.013824462890625, 0.0361328125, -0.0028667449951171875, - -0.0037746429443359375, 0.06475830078125, 0.030303955078125, - -0.043304443359375, -0.04559326171875, 0.0207366943359375, - -0.0275115966796875, -0.0256805419921875, -0.01324462890625, - 0.004016876220703125, 0.050048828125, -0.01021575927734375, - -0.0172119140625, 0.048004150390625, 0.0139617919921875, - -0.0207061767578125, -0.0168914794921875, 0.0146026611328125, - 0.0286407470703125, 0.037322998046875, -0.0244598388671875, - 0.006633758544921875, -0.0207366943359375, 0.03302001953125, - 0.005397796630859375, -0.0284423828125, 0.0184783935546875, - -0.01146697998046875, 0.0014848709106445312, - -0.005641937255859375, 0.03778076171875, 0.0264434814453125, - 0.0123748779296875, -0.01178741455078125, 0.03521728515625, - 0.013092041015625, 0.01140594482421875, 0.00672149658203125, - 0.024505615234375, 0.019775390625, -0.0177154541015625, - -0.0242156982421875, 0.02166748046875, -0.0004470348358154297, - 0.00811004638671875, -0.013153076171875, - 0.0012683868408203125, -0.01226806640625, - 0.003078460693359375, 0.041107177734375, 0.0124969482421875, - -0.030029296875, -0.0092620849609375, -0.01904296875, - 0.00907135009765625, -0.0301055908203125, - -0.007450103759765625, -0.01367950439453125, - 0.0297698974609375, 0.032867431640625, -0.0117034912109375, - 0.0015239715576171875, 0.045867919921875, 0.00350189208984375, - -0.034942626953125, 0.023406982421875, 0.03289794921875, - -0.0280914306640625, 0.018035888671875, 0.010223388671875, - -0.00412750244140625, -0.003978729248046875, - -0.024566650390625, -0.01024627685546875, -0.031585693359375, - -0.05474853515625, 0.016815185546875, 0.0265045166015625, - 0.02679443359375, -0.01230621337890625, -0.014892578125, - 0.02044677734375, 0.0153656005859375, -0.06256103515625, - -0.0162506103515625, 0.0221099853515625, -0.017120361328125, - -0.015594482421875, -0.035980224609375, 0.0192718505859375, - 0.043701171875, 0.0413818359375, 0.03314208984375, - 0.0001302957534790039, -0.00060272216796875, - -0.0177154541015625, -0.037384033203125, 0.037750244140625, - 0.0265045166015625, 0.01338958740234375, 0.0231170654296875, - 0.04119873046875, -0.0037441253662109375, -0.0226898193359375, - -0.000499725341796875, 0.00890350341796875, - -0.002899169921875, 0.007648468017578125, - 0.007366180419921875, -0.0113983154296875, - 0.003940582275390625, -0.007781982421875, -0.0400390625, - -0.0231781005859375, 0.007110595703125, -0.0287017822265625, - 0.032440185546875, 0.0176849365234375, -0.01515960693359375, - 0.00826263427734375, -0.0146636962890625, 0.01073455810546875, - 0.007213592529296875, 0.0014743804931640625, 0.03265380859375, - -0.00846099853515625, 0.03009033203125, 0.0013189315795898438, - -0.01103973388671875, -0.01983642578125, 0.0136566162109375, - -0.0312347412109375, -0.0228729248046875, 0.0274658203125, - 0.00033664703369140625, 0.050811767578125, 0.0234222412109375, - 0.011016845703125, -0.01800537109375, -0.0154876708984375, - 0.016876220703125, 0.006988525390625, 0.00920867919921875, - -0.01349639892578125, -0.0190887451171875, 0.028564453125, - 0.0092926025390625, 0.03021240234375, -0.01274871826171875, - 0.0049285888671875, 0.0024471282958984375, -0.06781005859375, - -0.007171630859375, 0.002124786376953125, 0.00334930419921875, - -0.0007500648498535156, 0.01947021484375, 0.0095367431640625, - -0.0262298583984375, -0.00958251953125, 0.00647735595703125, - 0.051727294921875, -0.0177154541015625, 0.0238189697265625, - -0.021453857421875, 0.030426025390625, -0.01262664794921875, - -0.0307159423828125, -0.037078857421875, 0.0223541259765625, - 0.03448486328125, -0.020599365234375, 0.0041046142578125, - -0.038787841796875, -0.0221710205078125, -0.0249786376953125, - -0.0214385986328125, -0.0003192424774169922, - 0.0036640167236328125, 0.0107574462890625, 0.0152435302734375, - -0.0162811279296875, -0.0222930908203125, 0.01052093505859375, - -0.0185699462890625, -0.01507568359375, 0.013458251953125, - 0.0023326873779296875, 0.00759124755859375, - -0.0022640228271484375, 0.0276336669921875, - 0.0083465576171875, 0.0155181884765625, -0.00701904296875, - 0.00833892822265625, -0.01355743408203125, 0.02069091796875, - 0.050933837890625, 0.01476287841796875, 0.0205841064453125, - -0.004924774169921875, 0.038299560546875, 0.0301361083984375, - 0.055267333984375, 0.00765228271484375, 0.00782012939453125, - -0.0016546249389648438, 0.0191497802734375, - 0.0026912689208984375, -0.0172119140625, 0.0219879150390625, - -0.0187835693359375, 0.035064697265625, -0.010101318359375, - -0.048065185546875, 0.0150604248046875, -0.031707763671875, - 0.0182647705078125, 0.02392578125, 0.043548583984375, - 0.01558685302734375, -0.0275115966796875, 0.01690673828125, - -0.003688812255859375, 0.01544952392578125, - -0.0037899017333984375, -0.0030841827392578125, - 0.0343017578125, -0.003437042236328125, -0.0285797119140625, - -0.03607177734375, -0.0008149147033691406, -0.01275634765625, - -0.036376953125, -0.007778167724609375, -0.04376220703125, - 0.027099609375, -0.05804443359375, -0.0144195556640625, - 0.0072479248046875, 0.045013427734375, 0.007221221923828125, - 0.025390625, 0.054718017578125, 0.00618743896484375, - -0.0198974609375, 0.0101470947265625, -0.035919189453125, - 0.00145721435546875, -0.00609588623046875, -0.027557373046875, - -0.01454925537109375, -0.0137176513671875, -0.002197265625, - -0.0038280487060546875, 0.006763458251953125, - 0.00432586669921875, -0.0203094482421875, 0.0181732177734375, - -0.00463104248046875, -0.0200042724609375, 0.03448486328125, - -0.0709228515625, -0.028533935546875, 0.0260162353515625, - -0.044677734375, 0.0212249755859375, 0.032501220703125, - 0.0163116455078125, -0.01091766357421875, - -0.01284027099609375, -0.028900146484375, - 0.0009822845458984375, -0.0187835693359375, - 0.0098114013671875, 0.0172119140625, 0.0307159423828125, - 0.004657745361328125, -0.0228271484375, 0.01180267333984375, - -0.028472900390625, -0.0211639404296875, -0.03936767578125, - 0.0176239013671875, -0.038238525390625, -0.03961181640625, - 0.0026645660400390625, -0.01483154296875, - 0.005336761474609375, 0.00011247396469116211, - 0.01042938232421875, -0.01023101806640625, -0.020721435546875, - -0.0256500244140625, 0.022186279296875, -0.01110076904296875, - 0.0234222412109375, -0.0209197998046875, - -0.0036983489990234375, -0.037628173828125, - 0.0189056396484375, 0.0112762451171875, -0.0162200927734375, - 0.00176239013671875, 0.01241302490234375, - -0.0038700103759765625, -0.0017251968383789062, - 0.043731689453125, -0.0298004150390625, 0.0050811767578125, - -0.0214385986328125, -0.0266265869140625, - -0.01386260986328125, 0.006877899169921875, - -0.0007877349853515625, 0.03021240234375, 0.051544189453125, - -0.04302978515625, -0.037811279296875, -0.0004911422729492188, - 0.0103607177734375, 0.0136260986328125, 0.025604248046875, - 0.0211639404296875, -0.0160675048828125, -0.022796630859375, - -0.004734039306640625, 0.013763427734375, - -0.01389312744140625, 0.0256195068359375, - 0.0035343170166015625, -0.026397705078125, - -0.0199432373046875, -0.018646240234375, -0.0250701904296875, - 0.0275421142578125, 0.0146026611328125, -0.002964019775390625, - 0.03839111328125, -0.0166168212890625, 0.00832366943359375, - 0.060455322265625, 0.0234527587890625, 0.0117950439453125, - 0.006397247314453125, -0.014068603515625, - -0.007488250732421875, -0.00616455078125, -0.0210723876953125, - 0.0007615089416503906, -0.01064300537109375, 0.00775146484375, - 0.016632080078125, 0.01084136962890625, -0.01355743408203125, - 0.0091552734375, 0.03436279296875, -0.013916015625, - -0.037353515625, -0.029632568359375, 0.018524169921875, - -0.0230712890625, 0.01107025146484375, -0.021484375, - 0.0247955322265625, -0.00873565673828125, 0.0184783935546875, - 0.00930023193359375, 0.0202484130859375, 0.0223541259765625, - 0.00608062744140625, 0.035430908203125, -0.035614013671875, - -0.0004277229309082031, -0.003662109375, 0.00539398193359375, - 0.0024127960205078125, 0.007221221923828125, - -0.0256805419921875, 0.00926971435546875, 0.00698089599609375, - -0.0077667236328125, 0.014862060546875, -0.031402587890625, - 0.0160675048828125, -0.0022525787353515625, - 0.01468658447265625, 0.01129913330078125, 0.019622802734375, - -0.0193939208984375, 0.00928497314453125, 0.013824462890625, - 0.0013227462768554688, 0.0131072998046875, - -0.0032062530517578125, -0.0093841552734375, - 0.0257415771484375, 0.0307464599609375, 0.02386474609375, - 0.0018949508666992188, -0.01251220703125, -0.010223388671875, - -0.035614013671875, 0.0228118896484375, - -0.000028192996978759766, 0.0123138427734375, 0.005126953125, - -0.010589599609375, 0.00533294677734375, 0.009490966796875, - 0.00566864013671875, -0.005893707275390625, - 0.01282501220703125, -0.0113372802734375, 0.00579071044921875, - 0.01239776611328125, -0.0068359375, 0.04034423828125, - -0.011077880859375, -0.0012264251708984375, 0.015899658203125, - -0.0450439453125, -0.01468658447265625, 0.0083160400390625, - -0.0121612548828125, -0.0027618408203125, - 0.0014629364013671875, -0.004535675048828125, - -0.0034885406494140625, -0.01180267333984375, 0.0029296875, - -0.025909423828125, 0.00821685791015625, - -0.0020427703857421875, 0.014404296875, 0.036346435546875, - -0.01197052001953125, -0.03778076171875, 0.0245819091796875, - -0.035858154296875, 0.00846099853515625, 0.0182342529296875, - 0.0088653564453125, -0.0467529296875, 0.01409912109375, - 0.01203155517578125, -0.0232696533203125, -0.0079498291015625, - 0.0123291015625, -0.0310516357421875, 0.00372314453125, - 0.0136260986328125, 0.00312042236328125, - 0.0007691383361816406, 0.004894256591796875 - ], - "index": 0, - "object": "embedding" - } - ], - "model": "text-embedding-3-small", - "object": "list", - "usage": { - "prompt_tokens": 7, - "total_tokens": 7 - } - } - }, - "headers": { - "access-control-expose-headers": "X-Request-ID, CF-Ray, CF-Ray", - "alt-svc": "h3=\":443\"; ma=86400", - "cf-cache-status": "DYNAMIC", - "cf-ray": "a29c58821f188029-IAD", - "connection": "keep-alive", - "content-encoding": "gzip", - "content-type": "application/json", - "date": "Wed, 12 Aug 2026 03:20:42 GMT", - "openai-organization": "braintrust-data", - "openai-processing-ms": "95", - "openai-project": "proj_vsCSXafhhByzWOThMrJcZiw9", - "openai-version": "2020-10-01", - "server": "cloudflare", - "set-cookie": "[REDACTED]", - "strict-transport-security": "max-age=31536000; includeSubDomains; preload", - "transfer-encoding": "chunked", - "x-content-type-options": "nosniff", - "x-openai-proxy-wasm": "v0.1", - "x-ratelimit-limit-requests": "10000", - "x-ratelimit-limit-tokens": "10000000", - "x-ratelimit-remaining-requests": "9999", - "x-ratelimit-remaining-tokens": "9999993", - "x-ratelimit-reset-requests": "6ms", - "x-ratelimit-reset-tokens": "0s", - "x-request-id": "req_1b8ae0d06d8d4e85b5adf955e9278634" - }, - "status": 200, - "statusText": "OK" - } - }, - { - "callIndex": 2, - "id": "f7a3ad27b5067191", - "matchKey": "POST api.openai.com/v1/chat/completions", - "recordedAt": "2026-08-12T03:20:43.286Z", - "request": { - "body": { - "kind": "json", - "value": { - "max_tokens": 128, - "messages": [ - { - "content": "You must inspect live weather via the provided get_weather tool before responding.", - "role": "system" - }, - { - "content": "Use the get_weather tool for Paris, France. If you do not call the tool, the answer is invalid.", - "role": "user" - } - ], - "model": "gpt-4o-mini-2024-07-18", - "temperature": 0, - "tool_choice": "auto", - "tools": [ - { - "function": { - "description": "Get the weather for a location", - "name": "get_weather", - "parameters": { - "$schema": "http://json-schema.org/draft-07/schema#", - "additionalProperties": false, - "properties": { - "location": { - "description": "The city and country", - "type": "string" - } - }, - "required": ["location"], - "type": "object" - } - }, - "type": "function" - } - ] - } - }, - "headers": {}, - "method": "POST", - "url": "https://api.openai.com/v1/chat/completions" - }, - "response": { - "body": { - "kind": "json", - "value": { - "choices": [ - { - "finish_reason": "tool_calls", - "index": 0, - "logprobs": null, - "message": { - "annotations": [], - "content": null, - "refusal": null, - "role": "assistant", - "tool_calls": [ - { - "function": { - "arguments": "{\"location\":\"Paris, France\"}", - "name": "get_weather" - }, - "id": "call_7m4SqfMove1q1WJKoZ9L8vBF", - "type": "function" - } - ] - } - } - ], - "created": 1786504842, - "id": "chatcmpl-EBtoAmVcdUeW509E4AjMQHaUMKh64", - "model": "gpt-4o-mini-2024-07-18", - "object": "chat.completion", - "service_tier": "default", - "system_fingerprint": "fp_98fa25fd02", - "usage": { - "completion_tokens": 16, - "completion_tokens_details": { - "accepted_prediction_tokens": 0, - "audio_tokens": 0, - "reasoning_tokens": 0, - "rejected_prediction_tokens": 0 - }, - "prompt_tokens": 87, - "prompt_tokens_details": { - "audio_tokens": 0, - "cached_tokens": 0 - }, - "total_tokens": 103 - } - } - }, - "headers": { - "access-control-expose-headers": "X-Request-ID, CF-Ray, CF-Ray", - "alt-svc": "h3=\":443\"; ma=86400", - "cf-cache-status": "DYNAMIC", - "cf-ray": "a29c58833ff58029-IAD", - "connection": "keep-alive", - "content-encoding": "gzip", - "content-type": "application/json", - "date": "Wed, 12 Aug 2026 03:20:43 GMT", - "openai-organization": "braintrust-data", - "openai-processing-ms": "461", - "openai-project": "proj_vsCSXafhhByzWOThMrJcZiw9", - "openai-version": "2020-10-01", - "server": "cloudflare", - "set-cookie": "[REDACTED]", - "strict-transport-security": "max-age=31536000; includeSubDomains; preload", - "transfer-encoding": "chunked", - "x-content-type-options": "nosniff", - "x-openai-proxy-wasm": "v0.1", - "x-ratelimit-limit-requests": "30000", - "x-ratelimit-limit-tokens": "150000000", - "x-ratelimit-remaining-requests": "29999", - "x-ratelimit-remaining-tokens": "149999952", - "x-ratelimit-reset-requests": "2ms", - "x-ratelimit-reset-tokens": "0s", - "x-request-id": "req_441d4ebf09e94dbc93999d7b512958f2" - }, - "status": 200, - "statusText": "OK" - } - }, - { - "callIndex": 3, - "id": "26ad5653def30060", - "matchKey": "POST api.openai.com/v1/chat/completions", - "recordedAt": "2026-08-12T03:20:43.735Z", - "request": { - "body": { - "kind": "json", - "value": { - "max_tokens": 32, - "messages": [ - { - "content": "Return JSON with {\"city\":\"Paris\"}.", - "role": "user" - } - ], - "model": "gpt-4o-mini-2024-07-18", - "temperature": 0, - "tool_choice": { - "function": { - "name": "json" - }, - "type": "function" - }, - "tools": [ - { - "function": { - "description": "Respond with a JSON object.", - "name": "json", - "parameters": { - "$schema": "http://json-schema.org/draft-07/schema#", - "additionalProperties": false, - "properties": { - "city": { - "type": "string" - } - }, - "required": ["city"], - "type": "object" - } - }, - "type": "function" - } - ] - } - }, - "headers": {}, - "method": "POST", - "url": "https://api.openai.com/v1/chat/completions" - }, - "response": { - "body": { - "kind": "json", - "value": { - "choices": [ - { - "finish_reason": "stop", - "index": 0, - "logprobs": null, - "message": { - "annotations": [], - "content": null, - "refusal": null, - "role": "assistant", - "tool_calls": [ - { - "function": { - "arguments": "{\"city\":\"Paris\"}", - "name": "json" - }, - "id": "call_NaJO7RcNWWu9cXZ0MghmDigY", - "type": "function" - } - ] - } - } - ], - "created": 1786504843, - "id": "chatcmpl-EBtoBrlB2T86JYR73gtUJwn7DAbYf", - "model": "gpt-4o-mini-2024-07-18", - "object": "chat.completion", - "service_tier": "default", - "system_fingerprint": "fp_9afcdcbeed", - "usage": { - "completion_tokens": 5, - "completion_tokens_details": { - "accepted_prediction_tokens": 0, - "audio_tokens": 0, - "reasoning_tokens": 0, - "rejected_prediction_tokens": 0 - }, - "prompt_tokens": 59, - "prompt_tokens_details": { - "audio_tokens": 0, - "cached_tokens": 0 - }, - "total_tokens": 64 - } - } - }, - "headers": { - "access-control-expose-headers": "X-Request-ID, CF-Ray, CF-Ray", - "alt-svc": "h3=\":443\"; ma=86400", - "cf-cache-status": "DYNAMIC", - "cf-ray": "a29c58868ac18029-IAD", - "connection": "keep-alive", - "content-encoding": "gzip", - "content-type": "application/json", - "date": "Wed, 12 Aug 2026 03:20:43 GMT", - "openai-organization": "braintrust-data", - "openai-processing-ms": "380", - "openai-project": "proj_vsCSXafhhByzWOThMrJcZiw9", - "openai-version": "2020-10-01", - "server": "cloudflare", - "set-cookie": "[REDACTED]", - "strict-transport-security": "max-age=31536000; includeSubDomains; preload", - "transfer-encoding": "chunked", - "x-content-type-options": "nosniff", - "x-openai-proxy-wasm": "v0.1", - "x-ratelimit-limit-requests": "30000", - "x-ratelimit-limit-tokens": "150000000", - "x-ratelimit-remaining-requests": "29999", - "x-ratelimit-remaining-tokens": "149999990", - "x-ratelimit-reset-requests": "2ms", - "x-ratelimit-reset-tokens": "0s", - "x-request-id": "req_7ae5bcb488284eca9142bcc527b04043" - }, - "status": 200, - "statusText": "OK" - } - }, - { - "callIndex": 4, - "id": "1e0515e907a8bb36", - "matchKey": "POST api.openai.com/v1/chat/completions", - "recordedAt": "2026-08-12T03:20:44.319Z", - "request": { - "body": { - "kind": "json", - "value": { - "max_tokens": 32, - "messages": [ - { - "content": "Stream JSON with {\"city\":\"Paris\"}.", - "role": "user" - } - ], - "model": "gpt-4o-mini-2024-07-18", - "stream": true, - "temperature": 0, - "tool_choice": { - "function": { - "name": "json" - }, - "type": "function" - }, - "tools": [ - { - "function": { - "description": "Respond with a JSON object.", - "name": "json", - "parameters": { - "$schema": "http://json-schema.org/draft-07/schema#", - "additionalProperties": false, - "properties": { - "city": { - "type": "string" - } - }, - "required": ["city"], - "type": "object" - } - }, - "type": "function" - } - ] - } - }, - "headers": {}, - "method": "POST", - "url": "https://api.openai.com/v1/chat/completions" - }, - "response": { - "body": { - "chunks": [ - "data: {\"id\":\"chatcmpl-EBtoCBiLNVza52zxcVcNSx4yVdz3P\",\"object\":\"chat.completion.chunk\",\"created\":1786504844,\"model\":\"gpt-4o-mini-2024-07-18\",\"service_tier\":\"default\",\"system_fingerprint\":\"fp_9afcdcbeed\",\"choices\":[{\"index\":0,\"delta\":{\"role\":\"assistant\",\"content\":null,\"tool_calls\":[{\"index\":0,\"id\":\"call_jdXI9wWsrTj9Hoenr1l6mYu9\",\"type\":\"function\",\"function\":{\"name\":\"json\",\"arguments\":\"\"}}],\"refusal\":null},\"logprobs\":null,\"finish_reason\":null}],\"obfuscation\":\"OQrXmj1E\"}", - "data: {\"id\":\"chatcmpl-EBtoCBiLNVza52zxcVcNSx4yVdz3P\",\"object\":\"chat.completion.chunk\",\"created\":1786504844,\"model\":\"gpt-4o-mini-2024-07-18\",\"service_tier\":\"default\",\"system_fingerprint\":\"fp_9afcdcbeed\",\"choices\":[{\"index\":0,\"delta\":{\"tool_calls\":[{\"index\":0,\"function\":{\"arguments\":\"{\\\"\"}}]},\"logprobs\":null,\"finish_reason\":null}],\"obfuscation\":\"C9WUYk5oWce\"}", - "data: {\"id\":\"chatcmpl-EBtoCBiLNVza52zxcVcNSx4yVdz3P\",\"object\":\"chat.completion.chunk\",\"created\":1786504844,\"model\":\"gpt-4o-mini-2024-07-18\",\"service_tier\":\"default\",\"system_fingerprint\":\"fp_9afcdcbeed\",\"choices\":[{\"index\":0,\"delta\":{\"tool_calls\":[{\"index\":0,\"function\":{\"arguments\":\"city\"}}]},\"logprobs\":null,\"finish_reason\":null}],\"obfuscation\":\"8yFDeEuspJ\"}", - "data: {\"id\":\"chatcmpl-EBtoCBiLNVza52zxcVcNSx4yVdz3P\",\"object\":\"chat.completion.chunk\",\"created\":1786504844,\"model\":\"gpt-4o-mini-2024-07-18\",\"service_tier\":\"default\",\"system_fingerprint\":\"fp_9afcdcbeed\",\"choices\":[{\"index\":0,\"delta\":{\"tool_calls\":[{\"index\":0,\"function\":{\"arguments\":\"\\\":\\\"\"}}]},\"logprobs\":null,\"finish_reason\":null}],\"obfuscation\":\"hYvjgrAeg\"}", - "data: {\"id\":\"chatcmpl-EBtoCBiLNVza52zxcVcNSx4yVdz3P\",\"object\":\"chat.completion.chunk\",\"created\":1786504844,\"model\":\"gpt-4o-mini-2024-07-18\",\"service_tier\":\"default\",\"system_fingerprint\":\"fp_9afcdcbeed\",\"choices\":[{\"index\":0,\"delta\":{\"tool_calls\":[{\"index\":0,\"function\":{\"arguments\":\"Paris\"}}]},\"logprobs\":null,\"finish_reason\":null}],\"obfuscation\":\"IzInSLXSv\"}", - "data: {\"id\":\"chatcmpl-EBtoCBiLNVza52zxcVcNSx4yVdz3P\",\"object\":\"chat.completion.chunk\",\"created\":1786504844,\"model\":\"gpt-4o-mini-2024-07-18\",\"service_tier\":\"default\",\"system_fingerprint\":\"fp_9afcdcbeed\",\"choices\":[{\"index\":0,\"delta\":{\"tool_calls\":[{\"index\":0,\"function\":{\"arguments\":\"\\\"}\"}}]},\"logprobs\":null,\"finish_reason\":null}],\"obfuscation\":\"UElpLFcwh21\"}", - "data: {\"id\":\"chatcmpl-EBtoCBiLNVza52zxcVcNSx4yVdz3P\",\"object\":\"chat.completion.chunk\",\"created\":1786504844,\"model\":\"gpt-4o-mini-2024-07-18\",\"service_tier\":\"default\",\"system_fingerprint\":\"fp_9afcdcbeed\",\"choices\":[{\"index\":0,\"delta\":{},\"logprobs\":null,\"finish_reason\":\"stop\"}],\"obfuscation\":\"2N\"}", - "data: [DONE]" - ], - "kind": "sse" - }, - "headers": { - "access-control-expose-headers": "X-Request-ID, CF-Ray, CF-Ray", - "alt-svc": "h3=\":443\"; ma=86400", - "cf-cache-status": "DYNAMIC", - "cf-ray": "a29c58895cc08029-IAD", - "connection": "keep-alive", - "content-type": "text/event-stream; charset=utf-8", - "date": "Wed, 12 Aug 2026 03:20:44 GMT", - "openai-organization": "braintrust-data", - "openai-processing-ms": "465", - "openai-project": "proj_vsCSXafhhByzWOThMrJcZiw9", - "openai-version": "2020-10-01", - "server": "cloudflare", - "set-cookie": "[REDACTED]", - "strict-transport-security": "max-age=31536000; includeSubDomains; preload", - "transfer-encoding": "chunked", - "x-content-type-options": "nosniff", - "x-openai-proxy-wasm": "v0.1", - "x-ratelimit-limit-requests": "30000", - "x-ratelimit-limit-tokens": "150000000", - "x-ratelimit-remaining-requests": "29999", - "x-ratelimit-remaining-tokens": "149999990", - "x-ratelimit-reset-requests": "2ms", - "x-ratelimit-reset-tokens": "0s", - "x-request-id": "req_e4f8d627b090474d9b8d57d8e4a96786" - }, - "status": 200, - "statusText": "OK" - } - } - ], - "meta": { - "createdAt": "2026-07-06T14:02:46.168Z" - } -} diff --git a/e2e/scenarios/ai-sdk-instrumentation/__snapshots__/ai-sdk-v3-auto-hook.span-tree.json b/e2e/scenarios/ai-sdk-instrumentation/__snapshots__/ai-sdk-v3-auto-hook.span-tree.json deleted file mode 100644 index 3a9e3b97e..000000000 --- a/e2e/scenarios/ai-sdk-instrumentation/__snapshots__/ai-sdk-v3-auto-hook.span-tree.json +++ /dev/null @@ -1,918 +0,0 @@ -{ - "span_tree": [ - { - "name": "ai-sdk-instrumentation-root", - "type": "task", - "children": [ - { - "name": "ai-sdk-generate-operation", - "children": [ - { - "name": "generateText", - "type": "function", - "children": [ - { - "name": "doGenerate", - "type": "llm", - "children": [], - "input": { - "inputFormat": "prompt", - "maxRetries": 2, - "maxTokens": 24, - "mode": { - "type": "regular" - }, - "prompt": [ - { - "content": [ - { - "text": "Reply with the single token PARIS and no punctuation.", - "type": "text" - } - ], - "role": "user" - } - ], - "temperature": 0 - }, - "output": { - "finishReason": "stop", - "providerMetadata": { - "openai": { - "reasoningTokens": 0 - } - }, - "rawResponse": {}, - "response": { - "modelId": "gpt-4o-mini-2024-07-18", - "timestamp": "" - }, - "text": "PARIS", - "usage": { - "completionTokens": 2, - "promptTokens": 18 - }, - "warnings": [] - }, - "metadata": { - "braintrust": { - "integration_name": "ai-sdk", - "sdk_language": "typescript" - }, - "finish_reason": "stop", - "model": "gpt-4o-mini-2024-07-18", - "provider": "openai.chat" - }, - "metrics": { - "completion_tokens": 2, - "prompt_tokens": 18, - "tokens": 20 - } - } - ], - "input": { - "maxTokens": 24, - "model": { - "config": { - "compatibility": "compatible", - "provider": "openai.chat" - }, - "modelId": "gpt-4o-mini-2024-07-18", - "settings": {}, - "specificationVersion": "v1" - }, - "prompt": "Reply with the single token PARIS and no punctuation.", - "temperature": 0 - }, - "output": { - "experimental_providerMetadata": { - "openai": { - "reasoningTokens": 0 - } - }, - "finishReason": "stop", - "rawResponse": {}, - "response": { - "messages": [ - { - "content": [ - { - "text": "PARIS", - "type": "text" - } - ], - "role": "assistant" - } - ], - "modelId": "gpt-4o-mini-2024-07-18", - "timestamp": "" - }, - "responseMessages": "", - "steps": [ - { - "experimental_providerMetadata": { - "openai": { - "reasoningTokens": 0 - } - }, - "finishReason": "stop", - "isContinued": false, - "response": { - "messages": [ - { - "content": [ - { - "text": "PARIS", - "type": "text" - } - ], - "role": "assistant" - } - ], - "modelId": "gpt-4o-mini-2024-07-18", - "timestamp": "" - }, - "stepType": "initial", - "text": "PARIS", - "toolCalls": [], - "toolResults": [], - "usage": { - "completionTokens": 2, - "promptTokens": 18, - "totalTokens": 20 - }, - "warnings": [] - } - ], - "text": "PARIS", - "toolCalls": [], - "toolResults": [], - "usage": { - "completionTokens": 2, - "promptTokens": 18, - "totalTokens": 20 - }, - "warnings": [] - }, - "metadata": { - "braintrust": { - "integration_name": "ai-sdk", - "sdk_language": "typescript" - }, - "model": "gpt-4o-mini-2024-07-18", - "provider": "openai.chat" - } - } - ], - "metadata": { - "operation": "generate", - "testRunId": "" - } - }, - { - "name": "ai-sdk-stream-operation", - "children": [ - { - "name": "streamText", - "type": "function", - "children": [ - { - "name": "doStream", - "type": "llm", - "children": [], - "input": { - "inputFormat": "prompt", - "maxRetries": 2, - "maxTokens": 32, - "mode": { - "type": "regular" - }, - "prompt": [ - { - "content": [ - { - "text": "Count from 1 to 3 and include the words one two three.", - "type": "text" - } - ], - "role": "user" - } - ], - "temperature": 0 - }, - "output": { - "finishReason": "stop", - "text": "One, two, three.", - "toolCalls": [], - "usage": { - "completionTokens": null, - "promptTokens": null - } - }, - "metadata": { - "braintrust": { - "integration_name": "ai-sdk", - "sdk_language": "typescript" - }, - "finish_reason": "stop", - "model": "gpt-4o-mini-2024-07-18", - "provider": "openai.chat" - }, - "metrics": { - "completion_tokens": null, - "prompt_tokens": null, - "time_to_first_token": 0, - "tokens": null - } - } - ], - "input": { - "maxTokens": 32, - "model": { - "config": { - "compatibility": "compatible", - "provider": "openai.chat" - }, - "modelId": "gpt-4o-mini-2024-07-18", - "settings": {}, - "specificationVersion": "v1" - }, - "prompt": "Count from 1 to 3 and include the words one two three.", - "temperature": 0 - }, - "output": { - "finishReason": "stop", - "rawResponse": {}, - "text": "One, two, three.", - "warnings": [] - }, - "metadata": { - "braintrust": { - "integration_name": "ai-sdk", - "sdk_language": "typescript" - }, - "model": "gpt-4o-mini-2024-07-18", - "provider": "openai.chat" - }, - "metrics": { - "time_to_first_token": 0 - } - } - ], - "metadata": { - "operation": "stream", - "testRunId": "" - } - }, - { - "name": "ai-sdk-embed-operation", - "children": [ - { - "name": "embed", - "type": "function", - "children": [], - "input": { - "model": { - "config": { - "provider": "openai.embedding" - }, - "modelId": "text-embedding-3-small", - "settings": {}, - "specificationVersion": "v1" - }, - "value": "Paris is the capital of France." - }, - "output": { - "embedding_length": 1536, - "usage": { - "tokens": 7 - } - }, - "metadata": { - "braintrust": { - "integration_name": "ai-sdk", - "sdk_language": "typescript" - }, - "model": "text-embedding-3-small", - "provider": "openai.embedding" - }, - "metrics": { - "tokens": 7 - } - } - ], - "metadata": { - "operation": "embed", - "testRunId": "" - } - }, - { - "name": "ai-sdk-tool-operation", - "children": [ - { - "name": "generateText", - "type": "function", - "children": [ - { - "name": "doGenerate", - "type": "llm", - "children": [], - "input": { - "inputFormat": "prompt", - "maxRetries": 2, - "maxTokens": 128, - "mode": { - "toolChoice": { - "type": "auto" - }, - "tools": [ - { - "description": "Get the weather for a location", - "name": "get_weather", - "parameters": { - "$schema": "http://json-schema.org/draft-07/schema#", - "additionalProperties": false, - "properties": { - "location": { - "description": "The city and country", - "type": "string" - } - }, - "required": [ - "location" - ], - "type": "object" - }, - "type": "function" - } - ], - "type": "regular" - }, - "prompt": [ - { - "content": "You must inspect live weather via the provided get_weather tool before responding.", - "role": "system" - }, - { - "content": [ - { - "text": "Use the get_weather tool for Paris, France. If you do not call the tool, the answer is invalid.", - "type": "text" - } - ], - "role": "user" - } - ], - "temperature": 0 - }, - "output": { - "finishReason": "tool-calls", - "providerMetadata": { - "openai": { - "reasoningTokens": 0 - } - }, - "rawResponse": {}, - "response": { - "modelId": "gpt-4o-mini-2024-07-18", - "timestamp": "" - }, - "toolCalls": [ - { - "args": "{\"location\":\"Paris, France\"}", - "toolCallType": "function", - "toolName": "get_weather" - } - ], - "usage": { - "completionTokens": 16, - "promptTokens": 87 - }, - "warnings": [] - }, - "metadata": { - "braintrust": { - "integration_name": "ai-sdk", - "sdk_language": "typescript" - }, - "finish_reason": "tool-calls", - "model": "gpt-4o-mini-2024-07-18", - "provider": "openai.chat" - }, - "metrics": { - "completion_tokens": 16, - "prompt_tokens": 87, - "tokens": 103 - } - }, - { - "name": "get_weather", - "type": "tool", - "children": [], - "input": { - "location": "Paris, France" - }, - "output": "{\"condition\":\"sunny\",\"location\":\"Paris, France\",\"temperatureC\":22}" - } - ], - "input": { - "maxTokens": 128, - "model": { - "config": { - "compatibility": "compatible", - "provider": "openai.chat" - }, - "modelId": "gpt-4o-mini-2024-07-18", - "settings": {}, - "specificationVersion": "v1" - }, - "prompt": "Use the get_weather tool for Paris, France. If you do not call the tool, the answer is invalid.", - "system": "You must inspect live weather via the provided get_weather tool before responding.", - "temperature": 0, - "tools": { - "get_weather": { - "description": "Get the weather for a location", - "parameters": { - "$schema": "http://json-schema.org/draft-07/schema#", - "additionalProperties": false, - "properties": { - "location": { - "description": "The city and country", - "type": "string" - } - }, - "required": [ - "location" - ], - "type": "object" - } - } - } - }, - "output": { - "experimental_providerMetadata": { - "openai": { - "reasoningTokens": 0 - } - }, - "finishReason": "tool-calls", - "rawResponse": {}, - "response": { - "messages": [ - { - "content": [ - { - "text": "", - "type": "text" - }, - { - "args": { - "location": "Paris, France" - }, - "toolName": "get_weather", - "type": "tool-call" - } - ], - "role": "assistant" - }, - { - "content": [ - { - "result": "{\"condition\":\"sunny\",\"location\":\"Paris, France\",\"temperatureC\":22}", - "toolName": "get_weather", - "type": "tool-result" - } - ], - "role": "tool" - } - ], - "modelId": "gpt-4o-mini-2024-07-18", - "timestamp": "" - }, - "responseMessages": "", - "steps": [ - { - "experimental_providerMetadata": { - "openai": { - "reasoningTokens": 0 - } - }, - "finishReason": "tool-calls", - "isContinued": false, - "response": { - "messages": [ - { - "content": [ - { - "text": "", - "type": "text" - }, - { - "args": { - "location": "Paris, France" - }, - "toolName": "get_weather", - "type": "tool-call" - } - ], - "role": "assistant" - }, - { - "content": [ - { - "result": "{\"condition\":\"sunny\",\"location\":\"Paris, France\",\"temperatureC\":22}", - "toolName": "get_weather", - "type": "tool-result" - } - ], - "role": "tool" - } - ], - "modelId": "gpt-4o-mini-2024-07-18", - "timestamp": "" - }, - "stepType": "initial", - "text": "", - "toolCalls": [ - { - "args": { - "location": "Paris, France" - }, - "toolName": "get_weather", - "type": "tool-call" - } - ], - "toolResults": [ - { - "args": { - "location": "Paris, France" - }, - "result": "{\"condition\":\"sunny\",\"location\":\"Paris, France\",\"temperatureC\":22}", - "toolName": "get_weather" - } - ], - "usage": { - "completionTokens": 16, - "promptTokens": 87, - "totalTokens": 103 - }, - "warnings": [] - } - ], - "text": "", - "toolCalls": [ - { - "args": { - "location": "Paris, France" - }, - "toolName": "get_weather", - "type": "tool-call" - } - ], - "toolResults": [ - { - "args": { - "location": "Paris, France" - }, - "result": "{\"condition\":\"sunny\",\"location\":\"Paris, France\",\"temperatureC\":22}", - "toolName": "get_weather" - } - ], - "usage": { - "completionTokens": 16, - "promptTokens": 87, - "totalTokens": 103 - }, - "warnings": [] - }, - "metadata": { - "braintrust": { - "integration_name": "ai-sdk", - "sdk_language": "typescript" - }, - "model": "gpt-4o-mini-2024-07-18", - "provider": "openai.chat", - "tools": { - "get_weather": { - "description": "Get the weather for a location", - "parameters": { - "$schema": "http://json-schema.org/draft-07/schema#", - "additionalProperties": false, - "properties": { - "location": { - "description": "The city and country", - "type": "string" - } - }, - "required": [ - "location" - ], - "type": "object" - } - } - } - } - } - ], - "metadata": { - "operation": "tool", - "testRunId": "" - } - }, - { - "name": "ai-sdk-generate-object-operation", - "children": [ - { - "name": "generateObject", - "type": "function", - "children": [ - { - "name": "doGenerate", - "type": "llm", - "children": [], - "input": { - "inputFormat": "prompt", - "maxRetries": 2, - "maxTokens": 32, - "mode": { - "tool": { - "description": "Respond with a JSON object.", - "name": "json", - "parameters": { - "$schema": "http://json-schema.org/draft-07/schema#", - "additionalProperties": false, - "properties": { - "city": { - "type": "string" - } - }, - "required": [ - "city" - ], - "type": "object" - }, - "type": "function" - }, - "type": "object-tool" - }, - "prompt": [ - { - "content": [ - { - "text": "Return JSON with {\"city\":\"Paris\"}.", - "type": "text" - } - ], - "role": "user" - } - ], - "temperature": 0 - }, - "output": { - "finishReason": "stop", - "providerMetadata": { - "openai": { - "reasoningTokens": 0 - } - }, - "rawResponse": {}, - "response": { - "modelId": "gpt-4o-mini-2024-07-18", - "timestamp": "" - }, - "toolCalls": [ - { - "args": "{\"city\":\"Paris\"}", - "toolCallType": "function", - "toolName": "json" - } - ], - "usage": { - "completionTokens": 5, - "promptTokens": 59 - }, - "warnings": [] - }, - "metadata": { - "braintrust": { - "integration_name": "ai-sdk", - "sdk_language": "typescript" - }, - "finish_reason": "stop", - "model": "gpt-4o-mini-2024-07-18", - "provider": "openai.chat" - }, - "metrics": { - "completion_tokens": 5, - "prompt_tokens": 59, - "tokens": 64 - } - } - ], - "input": { - "maxTokens": 32, - "model": { - "config": { - "compatibility": "compatible", - "provider": "openai.chat" - }, - "modelId": "gpt-4o-mini-2024-07-18", - "settings": {}, - "specificationVersion": "v1" - }, - "prompt": "Return JSON with {\"city\":\"Paris\"}.", - "schema": { - "$schema": "http://json-schema.org/draft-07/schema#", - "additionalProperties": false, - "properties": { - "city": { - "type": "string" - } - }, - "required": [ - "city" - ], - "type": "object" - }, - "temperature": 0 - }, - "output": { - "experimental_providerMetadata": { - "openai": { - "reasoningTokens": 0 - } - }, - "finishReason": "stop", - "object": { - "city": "Paris" - }, - "rawResponse": {}, - "response": { - "modelId": "gpt-4o-mini-2024-07-18", - "timestamp": "" - }, - "usage": { - "completionTokens": 5, - "promptTokens": 59, - "totalTokens": 64 - }, - "warnings": [] - }, - "metadata": { - "braintrust": { - "integration_name": "ai-sdk", - "sdk_language": "typescript" - }, - "model": "gpt-4o-mini-2024-07-18", - "provider": "openai.chat" - } - } - ], - "metadata": { - "operation": "generate-object", - "testRunId": "" - } - }, - { - "name": "ai-sdk-stream-object-operation", - "children": [ - { - "name": "streamObject", - "type": "function", - "children": [ - { - "name": "doStream", - "type": "llm", - "children": [], - "input": { - "inputFormat": "prompt", - "maxRetries": 2, - "maxTokens": 32, - "mode": { - "tool": { - "description": "Respond with a JSON object.", - "name": "json", - "parameters": { - "$schema": "http://json-schema.org/draft-07/schema#", - "additionalProperties": false, - "properties": { - "city": { - "type": "string" - } - }, - "required": [ - "city" - ], - "type": "object" - }, - "type": "function" - }, - "type": "object-tool" - }, - "prompt": [ - { - "content": [ - { - "text": "Stream JSON with {\"city\":\"Paris\"}.", - "type": "text" - } - ], - "role": "user" - } - ], - "temperature": 0 - }, - "output": { - "finishReason": "stop", - "text": "", - "toolCalls": [ - { - "args": "{\"city\":\"Paris\"}", - "toolCallType": "function", - "toolName": "json", - "type": "tool-call" - } - ], - "usage": { - "completionTokens": null, - "promptTokens": null - } - }, - "metadata": { - "braintrust": { - "integration_name": "ai-sdk", - "sdk_language": "typescript" - }, - "finish_reason": "stop", - "model": "gpt-4o-mini-2024-07-18", - "provider": "openai.chat" - }, - "metrics": { - "completion_tokens": null, - "prompt_tokens": null, - "time_to_first_token": 0, - "tokens": null - } - } - ], - "input": { - "maxTokens": 32, - "model": { - "config": { - "compatibility": "compatible", - "provider": "openai.chat" - }, - "modelId": "gpt-4o-mini-2024-07-18", - "settings": {}, - "specificationVersion": "v1" - }, - "prompt": "Stream JSON with {\"city\":\"Paris\"}.", - "schema": { - "$schema": "http://json-schema.org/draft-07/schema#", - "additionalProperties": false, - "properties": { - "city": { - "type": "string" - } - }, - "required": [ - "city" - ], - "type": "object" - }, - "temperature": 0 - }, - "output": { - "object": { - "city": "Paris" - }, - "rawResponse": {}, - "warnings": [] - }, - "metadata": { - "braintrust": { - "integration_name": "ai-sdk", - "sdk_language": "typescript" - }, - "model": "gpt-4o-mini-2024-07-18", - "provider": "openai.chat" - }, - "metrics": { - "time_to_first_token": 0 - } - } - ], - "metadata": { - "operation": "stream-object", - "testRunId": "" - } - } - ], - "metadata": { - "aiSdkVersion": "3.4.33", - "scenario": "ai-sdk-instrumentation", - "testRunId": "" - } - } - ] -} diff --git a/e2e/scenarios/ai-sdk-instrumentation/__snapshots__/ai-sdk-v3-auto-hook.span-tree.txt b/e2e/scenarios/ai-sdk-instrumentation/__snapshots__/ai-sdk-v3-auto-hook.span-tree.txt deleted file mode 100644 index 2656453e0..000000000 --- a/e2e/scenarios/ai-sdk-instrumentation/__snapshots__/ai-sdk-v3-auto-hook.span-tree.txt +++ /dev/null @@ -1,833 +0,0 @@ -span_tree: -└── ai-sdk-instrumentation-root [task] - metadata: { - "aiSdkVersion": "3.4.33", - "scenario": "ai-sdk-instrumentation", - "testRunId": "" - } - ├── ai-sdk-generate-operation - │ metadata: { - │ "operation": "generate", - │ "testRunId": "" - │ } - │ └── generateText [function] - │ input: { - │ "maxTokens": 24, - │ "model": { - │ "config": { - │ "compatibility": "compatible", - │ "provider": "openai.chat" - │ }, - │ "modelId": "gpt-4o-mini-2024-07-18", - │ "settings": {}, - │ "specificationVersion": "v1" - │ }, - │ "prompt": "Reply with the single token PARIS and no punctuation.", - │ "temperature": 0 - │ } - │ output: { - │ "experimental_providerMetadata": { - │ "openai": { - │ "reasoningTokens": 0 - │ } - │ }, - │ "finishReason": "stop", - │ "rawResponse": {}, - │ "response": { - │ "messages": [ - │ { - │ "content": [ - │ { - │ "text": "PARIS", - │ "type": "text" - │ } - │ ], - │ "role": "assistant" - │ } - │ ], - │ "modelId": "gpt-4o-mini-2024-07-18", - │ "timestamp": "" - │ }, - │ "responseMessages": "", - │ "steps": [ - │ { - │ "experimental_providerMetadata": { - │ "openai": { - │ "reasoningTokens": 0 - │ } - │ }, - │ "finishReason": "stop", - │ "isContinued": false, - │ "response": { - │ "messages": [ - │ { - │ "content": [ - │ { - │ "text": "PARIS", - │ "type": "text" - │ } - │ ], - │ "role": "assistant" - │ } - │ ], - │ "modelId": "gpt-4o-mini-2024-07-18", - │ "timestamp": "" - │ }, - │ "stepType": "initial", - │ "text": "PARIS", - │ "toolCalls": [], - │ "toolResults": [], - │ "usage": { - │ "completionTokens": 2, - │ "promptTokens": 18, - │ "totalTokens": 20 - │ }, - │ "warnings": [] - │ } - │ ], - │ "text": "PARIS", - │ "toolCalls": [], - │ "toolResults": [], - │ "usage": { - │ "completionTokens": 2, - │ "promptTokens": 18, - │ "totalTokens": 20 - │ }, - │ "warnings": [] - │ } - │ metadata: { - │ "braintrust": { - │ "integration_name": "ai-sdk", - │ "sdk_language": "typescript" - │ }, - │ "model": "gpt-4o-mini-2024-07-18", - │ "provider": "openai.chat" - │ } - │ └── doGenerate [llm] - │ input: { - │ "inputFormat": "prompt", - │ "maxRetries": 2, - │ "maxTokens": 24, - │ "mode": { - │ "type": "regular" - │ }, - │ "prompt": [ - │ { - │ "content": [ - │ { - │ "text": "Reply with the single token PARIS and no punctuation.", - │ "type": "text" - │ } - │ ], - │ "role": "user" - │ } - │ ], - │ "temperature": 0 - │ } - │ output: { - │ "finishReason": "stop", - │ "providerMetadata": { - │ "openai": { - │ "reasoningTokens": 0 - │ } - │ }, - │ "rawResponse": {}, - │ "response": { - │ "modelId": "gpt-4o-mini-2024-07-18", - │ "timestamp": "" - │ }, - │ "text": "PARIS", - │ "usage": { - │ "completionTokens": 2, - │ "promptTokens": 18 - │ }, - │ "warnings": [] - │ } - │ metadata: { - │ "braintrust": { - │ "integration_name": "ai-sdk", - │ "sdk_language": "typescript" - │ }, - │ "finish_reason": "stop", - │ "model": "gpt-4o-mini-2024-07-18", - │ "provider": "openai.chat" - │ } - │ metrics: { - │ "completion_tokens": 2, - │ "prompt_tokens": 18, - │ "tokens": 20 - │ } - ├── ai-sdk-stream-operation - │ metadata: { - │ "operation": "stream", - │ "testRunId": "" - │ } - │ └── streamText [function] - │ input: { - │ "maxTokens": 32, - │ "model": { - │ "config": { - │ "compatibility": "compatible", - │ "provider": "openai.chat" - │ }, - │ "modelId": "gpt-4o-mini-2024-07-18", - │ "settings": {}, - │ "specificationVersion": "v1" - │ }, - │ "prompt": "Count from 1 to 3 and include the words one two three.", - │ "temperature": 0 - │ } - │ output: { - │ "finishReason": "stop", - │ "rawResponse": {}, - │ "text": "One, two, three.", - │ "warnings": [] - │ } - │ metadata: { - │ "braintrust": { - │ "integration_name": "ai-sdk", - │ "sdk_language": "typescript" - │ }, - │ "model": "gpt-4o-mini-2024-07-18", - │ "provider": "openai.chat" - │ } - │ metrics: { - │ "time_to_first_token": 0 - │ } - │ └── doStream [llm] - │ input: { - │ "inputFormat": "prompt", - │ "maxRetries": 2, - │ "maxTokens": 32, - │ "mode": { - │ "type": "regular" - │ }, - │ "prompt": [ - │ { - │ "content": [ - │ { - │ "text": "Count from 1 to 3 and include the words one two three.", - │ "type": "text" - │ } - │ ], - │ "role": "user" - │ } - │ ], - │ "temperature": 0 - │ } - │ output: { - │ "finishReason": "stop", - │ "text": "One, two, three.", - │ "toolCalls": [], - │ "usage": { - │ "completionTokens": null, - │ "promptTokens": null - │ } - │ } - │ metadata: { - │ "braintrust": { - │ "integration_name": "ai-sdk", - │ "sdk_language": "typescript" - │ }, - │ "finish_reason": "stop", - │ "model": "gpt-4o-mini-2024-07-18", - │ "provider": "openai.chat" - │ } - │ metrics: { - │ "completion_tokens": null, - │ "prompt_tokens": null, - │ "time_to_first_token": 0, - │ "tokens": null - │ } - ├── ai-sdk-embed-operation - │ metadata: { - │ "operation": "embed", - │ "testRunId": "" - │ } - │ └── embed [function] - │ input: { - │ "model": { - │ "config": { - │ "provider": "openai.embedding" - │ }, - │ "modelId": "text-embedding-3-small", - │ "settings": {}, - │ "specificationVersion": "v1" - │ }, - │ "value": "Paris is the capital of France." - │ } - │ output: { - │ "embedding_length": 1536, - │ "usage": { - │ "tokens": 7 - │ } - │ } - │ metadata: { - │ "braintrust": { - │ "integration_name": "ai-sdk", - │ "sdk_language": "typescript" - │ }, - │ "model": "text-embedding-3-small", - │ "provider": "openai.embedding" - │ } - │ metrics: { - │ "tokens": 7 - │ } - ├── ai-sdk-tool-operation - │ metadata: { - │ "operation": "tool", - │ "testRunId": "" - │ } - │ └── generateText [function] - │ input: { - │ "maxTokens": 128, - │ "model": { - │ "config": { - │ "compatibility": "compatible", - │ "provider": "openai.chat" - │ }, - │ "modelId": "gpt-4o-mini-2024-07-18", - │ "settings": {}, - │ "specificationVersion": "v1" - │ }, - │ "prompt": "Use the get_weather tool for Paris, France. If you do not call the tool, the answer is invalid.", - │ "system": "You must inspect live weather via the provided get_weather tool before responding.", - │ "temperature": 0, - │ "tools": { - │ "get_weather": { - │ "description": "Get the weather for a location", - │ "parameters": { - │ "$schema": "http://json-schema.org/draft-07/schema#", - │ "additionalProperties": false, - │ "properties": { - │ "location": { - │ "description": "The city and country", - │ "type": "string" - │ } - │ }, - │ "required": [ - │ "location" - │ ], - │ "type": "object" - │ } - │ } - │ } - │ } - │ output: { - │ "experimental_providerMetadata": { - │ "openai": { - │ "reasoningTokens": 0 - │ } - │ }, - │ "finishReason": "tool-calls", - │ "rawResponse": {}, - │ "response": { - │ "messages": [ - │ { - │ "content": [ - │ { - │ "text": "", - │ "type": "text" - │ }, - │ { - │ "args": { - │ "location": "Paris, France" - │ }, - │ "toolName": "get_weather", - │ "type": "tool-call" - │ } - │ ], - │ "role": "assistant" - │ }, - │ { - │ "content": [ - │ { - │ "result": "{\"condition\":\"sunny\",\"location\":\"Paris, France\",\"temperatureC\":22}", - │ "toolName": "get_weather", - │ "type": "tool-result" - │ } - │ ], - │ "role": "tool" - │ } - │ ], - │ "modelId": "gpt-4o-mini-2024-07-18", - │ "timestamp": "" - │ }, - │ "responseMessages": "", - │ "steps": [ - │ { - │ "experimental_providerMetadata": { - │ "openai": { - │ "reasoningTokens": 0 - │ } - │ }, - │ "finishReason": "tool-calls", - │ "isContinued": false, - │ "response": { - │ "messages": [ - │ { - │ "content": [ - │ { - │ "text": "", - │ "type": "text" - │ }, - │ { - │ "args": { - │ "location": "Paris, France" - │ }, - │ "toolName": "get_weather", - │ "type": "tool-call" - │ } - │ ], - │ "role": "assistant" - │ }, - │ { - │ "content": [ - │ { - │ "result": "{\"condition\":\"sunny\",\"location\":\"Paris, France\",\"temperatureC\":22}", - │ "toolName": "get_weather", - │ "type": "tool-result" - │ } - │ ], - │ "role": "tool" - │ } - │ ], - │ "modelId": "gpt-4o-mini-2024-07-18", - │ "timestamp": "" - │ }, - │ "stepType": "initial", - │ "text": "", - │ "toolCalls": [ - │ { - │ "args": { - │ "location": "Paris, France" - │ }, - │ "toolName": "get_weather", - │ "type": "tool-call" - │ } - │ ], - │ "toolResults": [ - │ { - │ "args": { - │ "location": "Paris, France" - │ }, - │ "result": "{\"condition\":\"sunny\",\"location\":\"Paris, France\",\"temperatureC\":22}", - │ "toolName": "get_weather" - │ } - │ ], - │ "usage": { - │ "completionTokens": 16, - │ "promptTokens": 87, - │ "totalTokens": 103 - │ }, - │ "warnings": [] - │ } - │ ], - │ "text": "", - │ "toolCalls": [ - │ { - │ "args": { - │ "location": "Paris, France" - │ }, - │ "toolName": "get_weather", - │ "type": "tool-call" - │ } - │ ], - │ "toolResults": [ - │ { - │ "args": { - │ "location": "Paris, France" - │ }, - │ "result": "{\"condition\":\"sunny\",\"location\":\"Paris, France\",\"temperatureC\":22}", - │ "toolName": "get_weather" - │ } - │ ], - │ "usage": { - │ "completionTokens": 16, - │ "promptTokens": 87, - │ "totalTokens": 103 - │ }, - │ "warnings": [] - │ } - │ metadata: { - │ "braintrust": { - │ "integration_name": "ai-sdk", - │ "sdk_language": "typescript" - │ }, - │ "model": "gpt-4o-mini-2024-07-18", - │ "provider": "openai.chat", - │ "tools": { - │ "get_weather": { - │ "description": "Get the weather for a location", - │ "parameters": { - │ "$schema": "http://json-schema.org/draft-07/schema#", - │ "additionalProperties": false, - │ "properties": { - │ "location": { - │ "description": "The city and country", - │ "type": "string" - │ } - │ }, - │ "required": [ - │ "location" - │ ], - │ "type": "object" - │ } - │ } - │ } - │ } - │ ├── doGenerate [llm] - │ │ input: { - │ │ "inputFormat": "prompt", - │ │ "maxRetries": 2, - │ │ "maxTokens": 128, - │ │ "mode": { - │ │ "toolChoice": { - │ │ "type": "auto" - │ │ }, - │ │ "tools": [ - │ │ { - │ │ "description": "Get the weather for a location", - │ │ "name": "get_weather", - │ │ "parameters": { - │ │ "$schema": "http://json-schema.org/draft-07/schema#", - │ │ "additionalProperties": false, - │ │ "properties": { - │ │ "location": { - │ │ "description": "The city and country", - │ │ "type": "string" - │ │ } - │ │ }, - │ │ "required": [ - │ │ "location" - │ │ ], - │ │ "type": "object" - │ │ }, - │ │ "type": "function" - │ │ } - │ │ ], - │ │ "type": "regular" - │ │ }, - │ │ "prompt": [ - │ │ { - │ │ "content": "You must inspect live weather via the provided get_weather tool before responding.", - │ │ "role": "system" - │ │ }, - │ │ { - │ │ "content": [ - │ │ { - │ │ "text": "Use the get_weather tool for Paris, France. If you do not call the tool, the answer is invalid.", - │ │ "type": "text" - │ │ } - │ │ ], - │ │ "role": "user" - │ │ } - │ │ ], - │ │ "temperature": 0 - │ │ } - │ │ output: { - │ │ "finishReason": "tool-calls", - │ │ "providerMetadata": { - │ │ "openai": { - │ │ "reasoningTokens": 0 - │ │ } - │ │ }, - │ │ "rawResponse": {}, - │ │ "response": { - │ │ "modelId": "gpt-4o-mini-2024-07-18", - │ │ "timestamp": "" - │ │ }, - │ │ "toolCalls": [ - │ │ { - │ │ "args": "{\"location\":\"Paris, France\"}", - │ │ "toolCallType": "function", - │ │ "toolName": "get_weather" - │ │ } - │ │ ], - │ │ "usage": { - │ │ "completionTokens": 16, - │ │ "promptTokens": 87 - │ │ }, - │ │ "warnings": [] - │ │ } - │ │ metadata: { - │ │ "braintrust": { - │ │ "integration_name": "ai-sdk", - │ │ "sdk_language": "typescript" - │ │ }, - │ │ "finish_reason": "tool-calls", - │ │ "model": "gpt-4o-mini-2024-07-18", - │ │ "provider": "openai.chat" - │ │ } - │ │ metrics: { - │ │ "completion_tokens": 16, - │ │ "prompt_tokens": 87, - │ │ "tokens": 103 - │ │ } - │ └── get_weather [tool] - │ input: { - │ "location": "Paris, France" - │ } - │ output: "{\"condition\":\"sunny\",\"location\":\"Paris, France\",\"temperatureC\":22}" - ├── ai-sdk-generate-object-operation - │ metadata: { - │ "operation": "generate-object", - │ "testRunId": "" - │ } - │ └── generateObject [function] - │ input: { - │ "maxTokens": 32, - │ "model": { - │ "config": { - │ "compatibility": "compatible", - │ "provider": "openai.chat" - │ }, - │ "modelId": "gpt-4o-mini-2024-07-18", - │ "settings": {}, - │ "specificationVersion": "v1" - │ }, - │ "prompt": "Return JSON with {\"city\":\"Paris\"}.", - │ "schema": { - │ "$schema": "http://json-schema.org/draft-07/schema#", - │ "additionalProperties": false, - │ "properties": { - │ "city": { - │ "type": "string" - │ } - │ }, - │ "required": [ - │ "city" - │ ], - │ "type": "object" - │ }, - │ "temperature": 0 - │ } - │ output: { - │ "experimental_providerMetadata": { - │ "openai": { - │ "reasoningTokens": 0 - │ } - │ }, - │ "finishReason": "stop", - │ "object": { - │ "city": "Paris" - │ }, - │ "rawResponse": {}, - │ "response": { - │ "modelId": "gpt-4o-mini-2024-07-18", - │ "timestamp": "" - │ }, - │ "usage": { - │ "completionTokens": 5, - │ "promptTokens": 59, - │ "totalTokens": 64 - │ }, - │ "warnings": [] - │ } - │ metadata: { - │ "braintrust": { - │ "integration_name": "ai-sdk", - │ "sdk_language": "typescript" - │ }, - │ "model": "gpt-4o-mini-2024-07-18", - │ "provider": "openai.chat" - │ } - │ └── doGenerate [llm] - │ input: { - │ "inputFormat": "prompt", - │ "maxRetries": 2, - │ "maxTokens": 32, - │ "mode": { - │ "tool": { - │ "description": "Respond with a JSON object.", - │ "name": "json", - │ "parameters": { - │ "$schema": "http://json-schema.org/draft-07/schema#", - │ "additionalProperties": false, - │ "properties": { - │ "city": { - │ "type": "string" - │ } - │ }, - │ "required": [ - │ "city" - │ ], - │ "type": "object" - │ }, - │ "type": "function" - │ }, - │ "type": "object-tool" - │ }, - │ "prompt": [ - │ { - │ "content": [ - │ { - │ "text": "Return JSON with {\"city\":\"Paris\"}.", - │ "type": "text" - │ } - │ ], - │ "role": "user" - │ } - │ ], - │ "temperature": 0 - │ } - │ output: { - │ "finishReason": "stop", - │ "providerMetadata": { - │ "openai": { - │ "reasoningTokens": 0 - │ } - │ }, - │ "rawResponse": {}, - │ "response": { - │ "modelId": "gpt-4o-mini-2024-07-18", - │ "timestamp": "" - │ }, - │ "toolCalls": [ - │ { - │ "args": "{\"city\":\"Paris\"}", - │ "toolCallType": "function", - │ "toolName": "json" - │ } - │ ], - │ "usage": { - │ "completionTokens": 5, - │ "promptTokens": 59 - │ }, - │ "warnings": [] - │ } - │ metadata: { - │ "braintrust": { - │ "integration_name": "ai-sdk", - │ "sdk_language": "typescript" - │ }, - │ "finish_reason": "stop", - │ "model": "gpt-4o-mini-2024-07-18", - │ "provider": "openai.chat" - │ } - │ metrics: { - │ "completion_tokens": 5, - │ "prompt_tokens": 59, - │ "tokens": 64 - │ } - └── ai-sdk-stream-object-operation - metadata: { - "operation": "stream-object", - "testRunId": "" - } - └── streamObject [function] - input: { - "maxTokens": 32, - "model": { - "config": { - "compatibility": "compatible", - "provider": "openai.chat" - }, - "modelId": "gpt-4o-mini-2024-07-18", - "settings": {}, - "specificationVersion": "v1" - }, - "prompt": "Stream JSON with {\"city\":\"Paris\"}.", - "schema": { - "$schema": "http://json-schema.org/draft-07/schema#", - "additionalProperties": false, - "properties": { - "city": { - "type": "string" - } - }, - "required": [ - "city" - ], - "type": "object" - }, - "temperature": 0 - } - output: { - "object": { - "city": "Paris" - }, - "rawResponse": {}, - "warnings": [] - } - metadata: { - "braintrust": { - "integration_name": "ai-sdk", - "sdk_language": "typescript" - }, - "model": "gpt-4o-mini-2024-07-18", - "provider": "openai.chat" - } - metrics: { - "time_to_first_token": 0 - } - └── doStream [llm] - input: { - "inputFormat": "prompt", - "maxRetries": 2, - "maxTokens": 32, - "mode": { - "tool": { - "description": "Respond with a JSON object.", - "name": "json", - "parameters": { - "$schema": "http://json-schema.org/draft-07/schema#", - "additionalProperties": false, - "properties": { - "city": { - "type": "string" - } - }, - "required": [ - "city" - ], - "type": "object" - }, - "type": "function" - }, - "type": "object-tool" - }, - "prompt": [ - { - "content": [ - { - "text": "Stream JSON with {\"city\":\"Paris\"}.", - "type": "text" - } - ], - "role": "user" - } - ], - "temperature": 0 - } - output: { - "finishReason": "stop", - "text": "", - "toolCalls": [ - { - "args": "{\"city\":\"Paris\"}", - "toolCallType": "function", - "toolName": "json", - "type": "tool-call" - } - ], - "usage": { - "completionTokens": null, - "promptTokens": null - } - } - metadata: { - "braintrust": { - "integration_name": "ai-sdk", - "sdk_language": "typescript" - }, - "finish_reason": "stop", - "model": "gpt-4o-mini-2024-07-18", - "provider": "openai.chat" - } - metrics: { - "completion_tokens": null, - "prompt_tokens": null, - "time_to_first_token": 0, - "tokens": null - } diff --git a/e2e/scenarios/ai-sdk-instrumentation/__snapshots__/ai-sdk-v3-latest-auto-hook.span-tree.json b/e2e/scenarios/ai-sdk-instrumentation/__snapshots__/ai-sdk-v3-latest-auto-hook.span-tree.json deleted file mode 100644 index 3a9e3b97e..000000000 --- a/e2e/scenarios/ai-sdk-instrumentation/__snapshots__/ai-sdk-v3-latest-auto-hook.span-tree.json +++ /dev/null @@ -1,918 +0,0 @@ -{ - "span_tree": [ - { - "name": "ai-sdk-instrumentation-root", - "type": "task", - "children": [ - { - "name": "ai-sdk-generate-operation", - "children": [ - { - "name": "generateText", - "type": "function", - "children": [ - { - "name": "doGenerate", - "type": "llm", - "children": [], - "input": { - "inputFormat": "prompt", - "maxRetries": 2, - "maxTokens": 24, - "mode": { - "type": "regular" - }, - "prompt": [ - { - "content": [ - { - "text": "Reply with the single token PARIS and no punctuation.", - "type": "text" - } - ], - "role": "user" - } - ], - "temperature": 0 - }, - "output": { - "finishReason": "stop", - "providerMetadata": { - "openai": { - "reasoningTokens": 0 - } - }, - "rawResponse": {}, - "response": { - "modelId": "gpt-4o-mini-2024-07-18", - "timestamp": "" - }, - "text": "PARIS", - "usage": { - "completionTokens": 2, - "promptTokens": 18 - }, - "warnings": [] - }, - "metadata": { - "braintrust": { - "integration_name": "ai-sdk", - "sdk_language": "typescript" - }, - "finish_reason": "stop", - "model": "gpt-4o-mini-2024-07-18", - "provider": "openai.chat" - }, - "metrics": { - "completion_tokens": 2, - "prompt_tokens": 18, - "tokens": 20 - } - } - ], - "input": { - "maxTokens": 24, - "model": { - "config": { - "compatibility": "compatible", - "provider": "openai.chat" - }, - "modelId": "gpt-4o-mini-2024-07-18", - "settings": {}, - "specificationVersion": "v1" - }, - "prompt": "Reply with the single token PARIS and no punctuation.", - "temperature": 0 - }, - "output": { - "experimental_providerMetadata": { - "openai": { - "reasoningTokens": 0 - } - }, - "finishReason": "stop", - "rawResponse": {}, - "response": { - "messages": [ - { - "content": [ - { - "text": "PARIS", - "type": "text" - } - ], - "role": "assistant" - } - ], - "modelId": "gpt-4o-mini-2024-07-18", - "timestamp": "" - }, - "responseMessages": "", - "steps": [ - { - "experimental_providerMetadata": { - "openai": { - "reasoningTokens": 0 - } - }, - "finishReason": "stop", - "isContinued": false, - "response": { - "messages": [ - { - "content": [ - { - "text": "PARIS", - "type": "text" - } - ], - "role": "assistant" - } - ], - "modelId": "gpt-4o-mini-2024-07-18", - "timestamp": "" - }, - "stepType": "initial", - "text": "PARIS", - "toolCalls": [], - "toolResults": [], - "usage": { - "completionTokens": 2, - "promptTokens": 18, - "totalTokens": 20 - }, - "warnings": [] - } - ], - "text": "PARIS", - "toolCalls": [], - "toolResults": [], - "usage": { - "completionTokens": 2, - "promptTokens": 18, - "totalTokens": 20 - }, - "warnings": [] - }, - "metadata": { - "braintrust": { - "integration_name": "ai-sdk", - "sdk_language": "typescript" - }, - "model": "gpt-4o-mini-2024-07-18", - "provider": "openai.chat" - } - } - ], - "metadata": { - "operation": "generate", - "testRunId": "" - } - }, - { - "name": "ai-sdk-stream-operation", - "children": [ - { - "name": "streamText", - "type": "function", - "children": [ - { - "name": "doStream", - "type": "llm", - "children": [], - "input": { - "inputFormat": "prompt", - "maxRetries": 2, - "maxTokens": 32, - "mode": { - "type": "regular" - }, - "prompt": [ - { - "content": [ - { - "text": "Count from 1 to 3 and include the words one two three.", - "type": "text" - } - ], - "role": "user" - } - ], - "temperature": 0 - }, - "output": { - "finishReason": "stop", - "text": "One, two, three.", - "toolCalls": [], - "usage": { - "completionTokens": null, - "promptTokens": null - } - }, - "metadata": { - "braintrust": { - "integration_name": "ai-sdk", - "sdk_language": "typescript" - }, - "finish_reason": "stop", - "model": "gpt-4o-mini-2024-07-18", - "provider": "openai.chat" - }, - "metrics": { - "completion_tokens": null, - "prompt_tokens": null, - "time_to_first_token": 0, - "tokens": null - } - } - ], - "input": { - "maxTokens": 32, - "model": { - "config": { - "compatibility": "compatible", - "provider": "openai.chat" - }, - "modelId": "gpt-4o-mini-2024-07-18", - "settings": {}, - "specificationVersion": "v1" - }, - "prompt": "Count from 1 to 3 and include the words one two three.", - "temperature": 0 - }, - "output": { - "finishReason": "stop", - "rawResponse": {}, - "text": "One, two, three.", - "warnings": [] - }, - "metadata": { - "braintrust": { - "integration_name": "ai-sdk", - "sdk_language": "typescript" - }, - "model": "gpt-4o-mini-2024-07-18", - "provider": "openai.chat" - }, - "metrics": { - "time_to_first_token": 0 - } - } - ], - "metadata": { - "operation": "stream", - "testRunId": "" - } - }, - { - "name": "ai-sdk-embed-operation", - "children": [ - { - "name": "embed", - "type": "function", - "children": [], - "input": { - "model": { - "config": { - "provider": "openai.embedding" - }, - "modelId": "text-embedding-3-small", - "settings": {}, - "specificationVersion": "v1" - }, - "value": "Paris is the capital of France." - }, - "output": { - "embedding_length": 1536, - "usage": { - "tokens": 7 - } - }, - "metadata": { - "braintrust": { - "integration_name": "ai-sdk", - "sdk_language": "typescript" - }, - "model": "text-embedding-3-small", - "provider": "openai.embedding" - }, - "metrics": { - "tokens": 7 - } - } - ], - "metadata": { - "operation": "embed", - "testRunId": "" - } - }, - { - "name": "ai-sdk-tool-operation", - "children": [ - { - "name": "generateText", - "type": "function", - "children": [ - { - "name": "doGenerate", - "type": "llm", - "children": [], - "input": { - "inputFormat": "prompt", - "maxRetries": 2, - "maxTokens": 128, - "mode": { - "toolChoice": { - "type": "auto" - }, - "tools": [ - { - "description": "Get the weather for a location", - "name": "get_weather", - "parameters": { - "$schema": "http://json-schema.org/draft-07/schema#", - "additionalProperties": false, - "properties": { - "location": { - "description": "The city and country", - "type": "string" - } - }, - "required": [ - "location" - ], - "type": "object" - }, - "type": "function" - } - ], - "type": "regular" - }, - "prompt": [ - { - "content": "You must inspect live weather via the provided get_weather tool before responding.", - "role": "system" - }, - { - "content": [ - { - "text": "Use the get_weather tool for Paris, France. If you do not call the tool, the answer is invalid.", - "type": "text" - } - ], - "role": "user" - } - ], - "temperature": 0 - }, - "output": { - "finishReason": "tool-calls", - "providerMetadata": { - "openai": { - "reasoningTokens": 0 - } - }, - "rawResponse": {}, - "response": { - "modelId": "gpt-4o-mini-2024-07-18", - "timestamp": "" - }, - "toolCalls": [ - { - "args": "{\"location\":\"Paris, France\"}", - "toolCallType": "function", - "toolName": "get_weather" - } - ], - "usage": { - "completionTokens": 16, - "promptTokens": 87 - }, - "warnings": [] - }, - "metadata": { - "braintrust": { - "integration_name": "ai-sdk", - "sdk_language": "typescript" - }, - "finish_reason": "tool-calls", - "model": "gpt-4o-mini-2024-07-18", - "provider": "openai.chat" - }, - "metrics": { - "completion_tokens": 16, - "prompt_tokens": 87, - "tokens": 103 - } - }, - { - "name": "get_weather", - "type": "tool", - "children": [], - "input": { - "location": "Paris, France" - }, - "output": "{\"condition\":\"sunny\",\"location\":\"Paris, France\",\"temperatureC\":22}" - } - ], - "input": { - "maxTokens": 128, - "model": { - "config": { - "compatibility": "compatible", - "provider": "openai.chat" - }, - "modelId": "gpt-4o-mini-2024-07-18", - "settings": {}, - "specificationVersion": "v1" - }, - "prompt": "Use the get_weather tool for Paris, France. If you do not call the tool, the answer is invalid.", - "system": "You must inspect live weather via the provided get_weather tool before responding.", - "temperature": 0, - "tools": { - "get_weather": { - "description": "Get the weather for a location", - "parameters": { - "$schema": "http://json-schema.org/draft-07/schema#", - "additionalProperties": false, - "properties": { - "location": { - "description": "The city and country", - "type": "string" - } - }, - "required": [ - "location" - ], - "type": "object" - } - } - } - }, - "output": { - "experimental_providerMetadata": { - "openai": { - "reasoningTokens": 0 - } - }, - "finishReason": "tool-calls", - "rawResponse": {}, - "response": { - "messages": [ - { - "content": [ - { - "text": "", - "type": "text" - }, - { - "args": { - "location": "Paris, France" - }, - "toolName": "get_weather", - "type": "tool-call" - } - ], - "role": "assistant" - }, - { - "content": [ - { - "result": "{\"condition\":\"sunny\",\"location\":\"Paris, France\",\"temperatureC\":22}", - "toolName": "get_weather", - "type": "tool-result" - } - ], - "role": "tool" - } - ], - "modelId": "gpt-4o-mini-2024-07-18", - "timestamp": "" - }, - "responseMessages": "", - "steps": [ - { - "experimental_providerMetadata": { - "openai": { - "reasoningTokens": 0 - } - }, - "finishReason": "tool-calls", - "isContinued": false, - "response": { - "messages": [ - { - "content": [ - { - "text": "", - "type": "text" - }, - { - "args": { - "location": "Paris, France" - }, - "toolName": "get_weather", - "type": "tool-call" - } - ], - "role": "assistant" - }, - { - "content": [ - { - "result": "{\"condition\":\"sunny\",\"location\":\"Paris, France\",\"temperatureC\":22}", - "toolName": "get_weather", - "type": "tool-result" - } - ], - "role": "tool" - } - ], - "modelId": "gpt-4o-mini-2024-07-18", - "timestamp": "" - }, - "stepType": "initial", - "text": "", - "toolCalls": [ - { - "args": { - "location": "Paris, France" - }, - "toolName": "get_weather", - "type": "tool-call" - } - ], - "toolResults": [ - { - "args": { - "location": "Paris, France" - }, - "result": "{\"condition\":\"sunny\",\"location\":\"Paris, France\",\"temperatureC\":22}", - "toolName": "get_weather" - } - ], - "usage": { - "completionTokens": 16, - "promptTokens": 87, - "totalTokens": 103 - }, - "warnings": [] - } - ], - "text": "", - "toolCalls": [ - { - "args": { - "location": "Paris, France" - }, - "toolName": "get_weather", - "type": "tool-call" - } - ], - "toolResults": [ - { - "args": { - "location": "Paris, France" - }, - "result": "{\"condition\":\"sunny\",\"location\":\"Paris, France\",\"temperatureC\":22}", - "toolName": "get_weather" - } - ], - "usage": { - "completionTokens": 16, - "promptTokens": 87, - "totalTokens": 103 - }, - "warnings": [] - }, - "metadata": { - "braintrust": { - "integration_name": "ai-sdk", - "sdk_language": "typescript" - }, - "model": "gpt-4o-mini-2024-07-18", - "provider": "openai.chat", - "tools": { - "get_weather": { - "description": "Get the weather for a location", - "parameters": { - "$schema": "http://json-schema.org/draft-07/schema#", - "additionalProperties": false, - "properties": { - "location": { - "description": "The city and country", - "type": "string" - } - }, - "required": [ - "location" - ], - "type": "object" - } - } - } - } - } - ], - "metadata": { - "operation": "tool", - "testRunId": "" - } - }, - { - "name": "ai-sdk-generate-object-operation", - "children": [ - { - "name": "generateObject", - "type": "function", - "children": [ - { - "name": "doGenerate", - "type": "llm", - "children": [], - "input": { - "inputFormat": "prompt", - "maxRetries": 2, - "maxTokens": 32, - "mode": { - "tool": { - "description": "Respond with a JSON object.", - "name": "json", - "parameters": { - "$schema": "http://json-schema.org/draft-07/schema#", - "additionalProperties": false, - "properties": { - "city": { - "type": "string" - } - }, - "required": [ - "city" - ], - "type": "object" - }, - "type": "function" - }, - "type": "object-tool" - }, - "prompt": [ - { - "content": [ - { - "text": "Return JSON with {\"city\":\"Paris\"}.", - "type": "text" - } - ], - "role": "user" - } - ], - "temperature": 0 - }, - "output": { - "finishReason": "stop", - "providerMetadata": { - "openai": { - "reasoningTokens": 0 - } - }, - "rawResponse": {}, - "response": { - "modelId": "gpt-4o-mini-2024-07-18", - "timestamp": "" - }, - "toolCalls": [ - { - "args": "{\"city\":\"Paris\"}", - "toolCallType": "function", - "toolName": "json" - } - ], - "usage": { - "completionTokens": 5, - "promptTokens": 59 - }, - "warnings": [] - }, - "metadata": { - "braintrust": { - "integration_name": "ai-sdk", - "sdk_language": "typescript" - }, - "finish_reason": "stop", - "model": "gpt-4o-mini-2024-07-18", - "provider": "openai.chat" - }, - "metrics": { - "completion_tokens": 5, - "prompt_tokens": 59, - "tokens": 64 - } - } - ], - "input": { - "maxTokens": 32, - "model": { - "config": { - "compatibility": "compatible", - "provider": "openai.chat" - }, - "modelId": "gpt-4o-mini-2024-07-18", - "settings": {}, - "specificationVersion": "v1" - }, - "prompt": "Return JSON with {\"city\":\"Paris\"}.", - "schema": { - "$schema": "http://json-schema.org/draft-07/schema#", - "additionalProperties": false, - "properties": { - "city": { - "type": "string" - } - }, - "required": [ - "city" - ], - "type": "object" - }, - "temperature": 0 - }, - "output": { - "experimental_providerMetadata": { - "openai": { - "reasoningTokens": 0 - } - }, - "finishReason": "stop", - "object": { - "city": "Paris" - }, - "rawResponse": {}, - "response": { - "modelId": "gpt-4o-mini-2024-07-18", - "timestamp": "" - }, - "usage": { - "completionTokens": 5, - "promptTokens": 59, - "totalTokens": 64 - }, - "warnings": [] - }, - "metadata": { - "braintrust": { - "integration_name": "ai-sdk", - "sdk_language": "typescript" - }, - "model": "gpt-4o-mini-2024-07-18", - "provider": "openai.chat" - } - } - ], - "metadata": { - "operation": "generate-object", - "testRunId": "" - } - }, - { - "name": "ai-sdk-stream-object-operation", - "children": [ - { - "name": "streamObject", - "type": "function", - "children": [ - { - "name": "doStream", - "type": "llm", - "children": [], - "input": { - "inputFormat": "prompt", - "maxRetries": 2, - "maxTokens": 32, - "mode": { - "tool": { - "description": "Respond with a JSON object.", - "name": "json", - "parameters": { - "$schema": "http://json-schema.org/draft-07/schema#", - "additionalProperties": false, - "properties": { - "city": { - "type": "string" - } - }, - "required": [ - "city" - ], - "type": "object" - }, - "type": "function" - }, - "type": "object-tool" - }, - "prompt": [ - { - "content": [ - { - "text": "Stream JSON with {\"city\":\"Paris\"}.", - "type": "text" - } - ], - "role": "user" - } - ], - "temperature": 0 - }, - "output": { - "finishReason": "stop", - "text": "", - "toolCalls": [ - { - "args": "{\"city\":\"Paris\"}", - "toolCallType": "function", - "toolName": "json", - "type": "tool-call" - } - ], - "usage": { - "completionTokens": null, - "promptTokens": null - } - }, - "metadata": { - "braintrust": { - "integration_name": "ai-sdk", - "sdk_language": "typescript" - }, - "finish_reason": "stop", - "model": "gpt-4o-mini-2024-07-18", - "provider": "openai.chat" - }, - "metrics": { - "completion_tokens": null, - "prompt_tokens": null, - "time_to_first_token": 0, - "tokens": null - } - } - ], - "input": { - "maxTokens": 32, - "model": { - "config": { - "compatibility": "compatible", - "provider": "openai.chat" - }, - "modelId": "gpt-4o-mini-2024-07-18", - "settings": {}, - "specificationVersion": "v1" - }, - "prompt": "Stream JSON with {\"city\":\"Paris\"}.", - "schema": { - "$schema": "http://json-schema.org/draft-07/schema#", - "additionalProperties": false, - "properties": { - "city": { - "type": "string" - } - }, - "required": [ - "city" - ], - "type": "object" - }, - "temperature": 0 - }, - "output": { - "object": { - "city": "Paris" - }, - "rawResponse": {}, - "warnings": [] - }, - "metadata": { - "braintrust": { - "integration_name": "ai-sdk", - "sdk_language": "typescript" - }, - "model": "gpt-4o-mini-2024-07-18", - "provider": "openai.chat" - }, - "metrics": { - "time_to_first_token": 0 - } - } - ], - "metadata": { - "operation": "stream-object", - "testRunId": "" - } - } - ], - "metadata": { - "aiSdkVersion": "3.4.33", - "scenario": "ai-sdk-instrumentation", - "testRunId": "" - } - } - ] -} diff --git a/e2e/scenarios/ai-sdk-instrumentation/__snapshots__/ai-sdk-v3-latest-auto-hook.span-tree.txt b/e2e/scenarios/ai-sdk-instrumentation/__snapshots__/ai-sdk-v3-latest-auto-hook.span-tree.txt deleted file mode 100644 index 2656453e0..000000000 --- a/e2e/scenarios/ai-sdk-instrumentation/__snapshots__/ai-sdk-v3-latest-auto-hook.span-tree.txt +++ /dev/null @@ -1,833 +0,0 @@ -span_tree: -└── ai-sdk-instrumentation-root [task] - metadata: { - "aiSdkVersion": "3.4.33", - "scenario": "ai-sdk-instrumentation", - "testRunId": "" - } - ├── ai-sdk-generate-operation - │ metadata: { - │ "operation": "generate", - │ "testRunId": "" - │ } - │ └── generateText [function] - │ input: { - │ "maxTokens": 24, - │ "model": { - │ "config": { - │ "compatibility": "compatible", - │ "provider": "openai.chat" - │ }, - │ "modelId": "gpt-4o-mini-2024-07-18", - │ "settings": {}, - │ "specificationVersion": "v1" - │ }, - │ "prompt": "Reply with the single token PARIS and no punctuation.", - │ "temperature": 0 - │ } - │ output: { - │ "experimental_providerMetadata": { - │ "openai": { - │ "reasoningTokens": 0 - │ } - │ }, - │ "finishReason": "stop", - │ "rawResponse": {}, - │ "response": { - │ "messages": [ - │ { - │ "content": [ - │ { - │ "text": "PARIS", - │ "type": "text" - │ } - │ ], - │ "role": "assistant" - │ } - │ ], - │ "modelId": "gpt-4o-mini-2024-07-18", - │ "timestamp": "" - │ }, - │ "responseMessages": "", - │ "steps": [ - │ { - │ "experimental_providerMetadata": { - │ "openai": { - │ "reasoningTokens": 0 - │ } - │ }, - │ "finishReason": "stop", - │ "isContinued": false, - │ "response": { - │ "messages": [ - │ { - │ "content": [ - │ { - │ "text": "PARIS", - │ "type": "text" - │ } - │ ], - │ "role": "assistant" - │ } - │ ], - │ "modelId": "gpt-4o-mini-2024-07-18", - │ "timestamp": "" - │ }, - │ "stepType": "initial", - │ "text": "PARIS", - │ "toolCalls": [], - │ "toolResults": [], - │ "usage": { - │ "completionTokens": 2, - │ "promptTokens": 18, - │ "totalTokens": 20 - │ }, - │ "warnings": [] - │ } - │ ], - │ "text": "PARIS", - │ "toolCalls": [], - │ "toolResults": [], - │ "usage": { - │ "completionTokens": 2, - │ "promptTokens": 18, - │ "totalTokens": 20 - │ }, - │ "warnings": [] - │ } - │ metadata: { - │ "braintrust": { - │ "integration_name": "ai-sdk", - │ "sdk_language": "typescript" - │ }, - │ "model": "gpt-4o-mini-2024-07-18", - │ "provider": "openai.chat" - │ } - │ └── doGenerate [llm] - │ input: { - │ "inputFormat": "prompt", - │ "maxRetries": 2, - │ "maxTokens": 24, - │ "mode": { - │ "type": "regular" - │ }, - │ "prompt": [ - │ { - │ "content": [ - │ { - │ "text": "Reply with the single token PARIS and no punctuation.", - │ "type": "text" - │ } - │ ], - │ "role": "user" - │ } - │ ], - │ "temperature": 0 - │ } - │ output: { - │ "finishReason": "stop", - │ "providerMetadata": { - │ "openai": { - │ "reasoningTokens": 0 - │ } - │ }, - │ "rawResponse": {}, - │ "response": { - │ "modelId": "gpt-4o-mini-2024-07-18", - │ "timestamp": "" - │ }, - │ "text": "PARIS", - │ "usage": { - │ "completionTokens": 2, - │ "promptTokens": 18 - │ }, - │ "warnings": [] - │ } - │ metadata: { - │ "braintrust": { - │ "integration_name": "ai-sdk", - │ "sdk_language": "typescript" - │ }, - │ "finish_reason": "stop", - │ "model": "gpt-4o-mini-2024-07-18", - │ "provider": "openai.chat" - │ } - │ metrics: { - │ "completion_tokens": 2, - │ "prompt_tokens": 18, - │ "tokens": 20 - │ } - ├── ai-sdk-stream-operation - │ metadata: { - │ "operation": "stream", - │ "testRunId": "" - │ } - │ └── streamText [function] - │ input: { - │ "maxTokens": 32, - │ "model": { - │ "config": { - │ "compatibility": "compatible", - │ "provider": "openai.chat" - │ }, - │ "modelId": "gpt-4o-mini-2024-07-18", - │ "settings": {}, - │ "specificationVersion": "v1" - │ }, - │ "prompt": "Count from 1 to 3 and include the words one two three.", - │ "temperature": 0 - │ } - │ output: { - │ "finishReason": "stop", - │ "rawResponse": {}, - │ "text": "One, two, three.", - │ "warnings": [] - │ } - │ metadata: { - │ "braintrust": { - │ "integration_name": "ai-sdk", - │ "sdk_language": "typescript" - │ }, - │ "model": "gpt-4o-mini-2024-07-18", - │ "provider": "openai.chat" - │ } - │ metrics: { - │ "time_to_first_token": 0 - │ } - │ └── doStream [llm] - │ input: { - │ "inputFormat": "prompt", - │ "maxRetries": 2, - │ "maxTokens": 32, - │ "mode": { - │ "type": "regular" - │ }, - │ "prompt": [ - │ { - │ "content": [ - │ { - │ "text": "Count from 1 to 3 and include the words one two three.", - │ "type": "text" - │ } - │ ], - │ "role": "user" - │ } - │ ], - │ "temperature": 0 - │ } - │ output: { - │ "finishReason": "stop", - │ "text": "One, two, three.", - │ "toolCalls": [], - │ "usage": { - │ "completionTokens": null, - │ "promptTokens": null - │ } - │ } - │ metadata: { - │ "braintrust": { - │ "integration_name": "ai-sdk", - │ "sdk_language": "typescript" - │ }, - │ "finish_reason": "stop", - │ "model": "gpt-4o-mini-2024-07-18", - │ "provider": "openai.chat" - │ } - │ metrics: { - │ "completion_tokens": null, - │ "prompt_tokens": null, - │ "time_to_first_token": 0, - │ "tokens": null - │ } - ├── ai-sdk-embed-operation - │ metadata: { - │ "operation": "embed", - │ "testRunId": "" - │ } - │ └── embed [function] - │ input: { - │ "model": { - │ "config": { - │ "provider": "openai.embedding" - │ }, - │ "modelId": "text-embedding-3-small", - │ "settings": {}, - │ "specificationVersion": "v1" - │ }, - │ "value": "Paris is the capital of France." - │ } - │ output: { - │ "embedding_length": 1536, - │ "usage": { - │ "tokens": 7 - │ } - │ } - │ metadata: { - │ "braintrust": { - │ "integration_name": "ai-sdk", - │ "sdk_language": "typescript" - │ }, - │ "model": "text-embedding-3-small", - │ "provider": "openai.embedding" - │ } - │ metrics: { - │ "tokens": 7 - │ } - ├── ai-sdk-tool-operation - │ metadata: { - │ "operation": "tool", - │ "testRunId": "" - │ } - │ └── generateText [function] - │ input: { - │ "maxTokens": 128, - │ "model": { - │ "config": { - │ "compatibility": "compatible", - │ "provider": "openai.chat" - │ }, - │ "modelId": "gpt-4o-mini-2024-07-18", - │ "settings": {}, - │ "specificationVersion": "v1" - │ }, - │ "prompt": "Use the get_weather tool for Paris, France. If you do not call the tool, the answer is invalid.", - │ "system": "You must inspect live weather via the provided get_weather tool before responding.", - │ "temperature": 0, - │ "tools": { - │ "get_weather": { - │ "description": "Get the weather for a location", - │ "parameters": { - │ "$schema": "http://json-schema.org/draft-07/schema#", - │ "additionalProperties": false, - │ "properties": { - │ "location": { - │ "description": "The city and country", - │ "type": "string" - │ } - │ }, - │ "required": [ - │ "location" - │ ], - │ "type": "object" - │ } - │ } - │ } - │ } - │ output: { - │ "experimental_providerMetadata": { - │ "openai": { - │ "reasoningTokens": 0 - │ } - │ }, - │ "finishReason": "tool-calls", - │ "rawResponse": {}, - │ "response": { - │ "messages": [ - │ { - │ "content": [ - │ { - │ "text": "", - │ "type": "text" - │ }, - │ { - │ "args": { - │ "location": "Paris, France" - │ }, - │ "toolName": "get_weather", - │ "type": "tool-call" - │ } - │ ], - │ "role": "assistant" - │ }, - │ { - │ "content": [ - │ { - │ "result": "{\"condition\":\"sunny\",\"location\":\"Paris, France\",\"temperatureC\":22}", - │ "toolName": "get_weather", - │ "type": "tool-result" - │ } - │ ], - │ "role": "tool" - │ } - │ ], - │ "modelId": "gpt-4o-mini-2024-07-18", - │ "timestamp": "" - │ }, - │ "responseMessages": "", - │ "steps": [ - │ { - │ "experimental_providerMetadata": { - │ "openai": { - │ "reasoningTokens": 0 - │ } - │ }, - │ "finishReason": "tool-calls", - │ "isContinued": false, - │ "response": { - │ "messages": [ - │ { - │ "content": [ - │ { - │ "text": "", - │ "type": "text" - │ }, - │ { - │ "args": { - │ "location": "Paris, France" - │ }, - │ "toolName": "get_weather", - │ "type": "tool-call" - │ } - │ ], - │ "role": "assistant" - │ }, - │ { - │ "content": [ - │ { - │ "result": "{\"condition\":\"sunny\",\"location\":\"Paris, France\",\"temperatureC\":22}", - │ "toolName": "get_weather", - │ "type": "tool-result" - │ } - │ ], - │ "role": "tool" - │ } - │ ], - │ "modelId": "gpt-4o-mini-2024-07-18", - │ "timestamp": "" - │ }, - │ "stepType": "initial", - │ "text": "", - │ "toolCalls": [ - │ { - │ "args": { - │ "location": "Paris, France" - │ }, - │ "toolName": "get_weather", - │ "type": "tool-call" - │ } - │ ], - │ "toolResults": [ - │ { - │ "args": { - │ "location": "Paris, France" - │ }, - │ "result": "{\"condition\":\"sunny\",\"location\":\"Paris, France\",\"temperatureC\":22}", - │ "toolName": "get_weather" - │ } - │ ], - │ "usage": { - │ "completionTokens": 16, - │ "promptTokens": 87, - │ "totalTokens": 103 - │ }, - │ "warnings": [] - │ } - │ ], - │ "text": "", - │ "toolCalls": [ - │ { - │ "args": { - │ "location": "Paris, France" - │ }, - │ "toolName": "get_weather", - │ "type": "tool-call" - │ } - │ ], - │ "toolResults": [ - │ { - │ "args": { - │ "location": "Paris, France" - │ }, - │ "result": "{\"condition\":\"sunny\",\"location\":\"Paris, France\",\"temperatureC\":22}", - │ "toolName": "get_weather" - │ } - │ ], - │ "usage": { - │ "completionTokens": 16, - │ "promptTokens": 87, - │ "totalTokens": 103 - │ }, - │ "warnings": [] - │ } - │ metadata: { - │ "braintrust": { - │ "integration_name": "ai-sdk", - │ "sdk_language": "typescript" - │ }, - │ "model": "gpt-4o-mini-2024-07-18", - │ "provider": "openai.chat", - │ "tools": { - │ "get_weather": { - │ "description": "Get the weather for a location", - │ "parameters": { - │ "$schema": "http://json-schema.org/draft-07/schema#", - │ "additionalProperties": false, - │ "properties": { - │ "location": { - │ "description": "The city and country", - │ "type": "string" - │ } - │ }, - │ "required": [ - │ "location" - │ ], - │ "type": "object" - │ } - │ } - │ } - │ } - │ ├── doGenerate [llm] - │ │ input: { - │ │ "inputFormat": "prompt", - │ │ "maxRetries": 2, - │ │ "maxTokens": 128, - │ │ "mode": { - │ │ "toolChoice": { - │ │ "type": "auto" - │ │ }, - │ │ "tools": [ - │ │ { - │ │ "description": "Get the weather for a location", - │ │ "name": "get_weather", - │ │ "parameters": { - │ │ "$schema": "http://json-schema.org/draft-07/schema#", - │ │ "additionalProperties": false, - │ │ "properties": { - │ │ "location": { - │ │ "description": "The city and country", - │ │ "type": "string" - │ │ } - │ │ }, - │ │ "required": [ - │ │ "location" - │ │ ], - │ │ "type": "object" - │ │ }, - │ │ "type": "function" - │ │ } - │ │ ], - │ │ "type": "regular" - │ │ }, - │ │ "prompt": [ - │ │ { - │ │ "content": "You must inspect live weather via the provided get_weather tool before responding.", - │ │ "role": "system" - │ │ }, - │ │ { - │ │ "content": [ - │ │ { - │ │ "text": "Use the get_weather tool for Paris, France. If you do not call the tool, the answer is invalid.", - │ │ "type": "text" - │ │ } - │ │ ], - │ │ "role": "user" - │ │ } - │ │ ], - │ │ "temperature": 0 - │ │ } - │ │ output: { - │ │ "finishReason": "tool-calls", - │ │ "providerMetadata": { - │ │ "openai": { - │ │ "reasoningTokens": 0 - │ │ } - │ │ }, - │ │ "rawResponse": {}, - │ │ "response": { - │ │ "modelId": "gpt-4o-mini-2024-07-18", - │ │ "timestamp": "" - │ │ }, - │ │ "toolCalls": [ - │ │ { - │ │ "args": "{\"location\":\"Paris, France\"}", - │ │ "toolCallType": "function", - │ │ "toolName": "get_weather" - │ │ } - │ │ ], - │ │ "usage": { - │ │ "completionTokens": 16, - │ │ "promptTokens": 87 - │ │ }, - │ │ "warnings": [] - │ │ } - │ │ metadata: { - │ │ "braintrust": { - │ │ "integration_name": "ai-sdk", - │ │ "sdk_language": "typescript" - │ │ }, - │ │ "finish_reason": "tool-calls", - │ │ "model": "gpt-4o-mini-2024-07-18", - │ │ "provider": "openai.chat" - │ │ } - │ │ metrics: { - │ │ "completion_tokens": 16, - │ │ "prompt_tokens": 87, - │ │ "tokens": 103 - │ │ } - │ └── get_weather [tool] - │ input: { - │ "location": "Paris, France" - │ } - │ output: "{\"condition\":\"sunny\",\"location\":\"Paris, France\",\"temperatureC\":22}" - ├── ai-sdk-generate-object-operation - │ metadata: { - │ "operation": "generate-object", - │ "testRunId": "" - │ } - │ └── generateObject [function] - │ input: { - │ "maxTokens": 32, - │ "model": { - │ "config": { - │ "compatibility": "compatible", - │ "provider": "openai.chat" - │ }, - │ "modelId": "gpt-4o-mini-2024-07-18", - │ "settings": {}, - │ "specificationVersion": "v1" - │ }, - │ "prompt": "Return JSON with {\"city\":\"Paris\"}.", - │ "schema": { - │ "$schema": "http://json-schema.org/draft-07/schema#", - │ "additionalProperties": false, - │ "properties": { - │ "city": { - │ "type": "string" - │ } - │ }, - │ "required": [ - │ "city" - │ ], - │ "type": "object" - │ }, - │ "temperature": 0 - │ } - │ output: { - │ "experimental_providerMetadata": { - │ "openai": { - │ "reasoningTokens": 0 - │ } - │ }, - │ "finishReason": "stop", - │ "object": { - │ "city": "Paris" - │ }, - │ "rawResponse": {}, - │ "response": { - │ "modelId": "gpt-4o-mini-2024-07-18", - │ "timestamp": "" - │ }, - │ "usage": { - │ "completionTokens": 5, - │ "promptTokens": 59, - │ "totalTokens": 64 - │ }, - │ "warnings": [] - │ } - │ metadata: { - │ "braintrust": { - │ "integration_name": "ai-sdk", - │ "sdk_language": "typescript" - │ }, - │ "model": "gpt-4o-mini-2024-07-18", - │ "provider": "openai.chat" - │ } - │ └── doGenerate [llm] - │ input: { - │ "inputFormat": "prompt", - │ "maxRetries": 2, - │ "maxTokens": 32, - │ "mode": { - │ "tool": { - │ "description": "Respond with a JSON object.", - │ "name": "json", - │ "parameters": { - │ "$schema": "http://json-schema.org/draft-07/schema#", - │ "additionalProperties": false, - │ "properties": { - │ "city": { - │ "type": "string" - │ } - │ }, - │ "required": [ - │ "city" - │ ], - │ "type": "object" - │ }, - │ "type": "function" - │ }, - │ "type": "object-tool" - │ }, - │ "prompt": [ - │ { - │ "content": [ - │ { - │ "text": "Return JSON with {\"city\":\"Paris\"}.", - │ "type": "text" - │ } - │ ], - │ "role": "user" - │ } - │ ], - │ "temperature": 0 - │ } - │ output: { - │ "finishReason": "stop", - │ "providerMetadata": { - │ "openai": { - │ "reasoningTokens": 0 - │ } - │ }, - │ "rawResponse": {}, - │ "response": { - │ "modelId": "gpt-4o-mini-2024-07-18", - │ "timestamp": "" - │ }, - │ "toolCalls": [ - │ { - │ "args": "{\"city\":\"Paris\"}", - │ "toolCallType": "function", - │ "toolName": "json" - │ } - │ ], - │ "usage": { - │ "completionTokens": 5, - │ "promptTokens": 59 - │ }, - │ "warnings": [] - │ } - │ metadata: { - │ "braintrust": { - │ "integration_name": "ai-sdk", - │ "sdk_language": "typescript" - │ }, - │ "finish_reason": "stop", - │ "model": "gpt-4o-mini-2024-07-18", - │ "provider": "openai.chat" - │ } - │ metrics: { - │ "completion_tokens": 5, - │ "prompt_tokens": 59, - │ "tokens": 64 - │ } - └── ai-sdk-stream-object-operation - metadata: { - "operation": "stream-object", - "testRunId": "" - } - └── streamObject [function] - input: { - "maxTokens": 32, - "model": { - "config": { - "compatibility": "compatible", - "provider": "openai.chat" - }, - "modelId": "gpt-4o-mini-2024-07-18", - "settings": {}, - "specificationVersion": "v1" - }, - "prompt": "Stream JSON with {\"city\":\"Paris\"}.", - "schema": { - "$schema": "http://json-schema.org/draft-07/schema#", - "additionalProperties": false, - "properties": { - "city": { - "type": "string" - } - }, - "required": [ - "city" - ], - "type": "object" - }, - "temperature": 0 - } - output: { - "object": { - "city": "Paris" - }, - "rawResponse": {}, - "warnings": [] - } - metadata: { - "braintrust": { - "integration_name": "ai-sdk", - "sdk_language": "typescript" - }, - "model": "gpt-4o-mini-2024-07-18", - "provider": "openai.chat" - } - metrics: { - "time_to_first_token": 0 - } - └── doStream [llm] - input: { - "inputFormat": "prompt", - "maxRetries": 2, - "maxTokens": 32, - "mode": { - "tool": { - "description": "Respond with a JSON object.", - "name": "json", - "parameters": { - "$schema": "http://json-schema.org/draft-07/schema#", - "additionalProperties": false, - "properties": { - "city": { - "type": "string" - } - }, - "required": [ - "city" - ], - "type": "object" - }, - "type": "function" - }, - "type": "object-tool" - }, - "prompt": [ - { - "content": [ - { - "text": "Stream JSON with {\"city\":\"Paris\"}.", - "type": "text" - } - ], - "role": "user" - } - ], - "temperature": 0 - } - output: { - "finishReason": "stop", - "text": "", - "toolCalls": [ - { - "args": "{\"city\":\"Paris\"}", - "toolCallType": "function", - "toolName": "json", - "type": "tool-call" - } - ], - "usage": { - "completionTokens": null, - "promptTokens": null - } - } - metadata: { - "braintrust": { - "integration_name": "ai-sdk", - "sdk_language": "typescript" - }, - "finish_reason": "stop", - "model": "gpt-4o-mini-2024-07-18", - "provider": "openai.chat" - } - metrics: { - "completion_tokens": null, - "prompt_tokens": null, - "time_to_first_token": 0, - "tokens": null - } diff --git a/e2e/scenarios/ai-sdk-instrumentation/__snapshots__/ai-sdk-v3-latest-wrapped.span-tree.json b/e2e/scenarios/ai-sdk-instrumentation/__snapshots__/ai-sdk-v3-latest-wrapped.span-tree.json deleted file mode 100644 index 3a9e3b97e..000000000 --- a/e2e/scenarios/ai-sdk-instrumentation/__snapshots__/ai-sdk-v3-latest-wrapped.span-tree.json +++ /dev/null @@ -1,918 +0,0 @@ -{ - "span_tree": [ - { - "name": "ai-sdk-instrumentation-root", - "type": "task", - "children": [ - { - "name": "ai-sdk-generate-operation", - "children": [ - { - "name": "generateText", - "type": "function", - "children": [ - { - "name": "doGenerate", - "type": "llm", - "children": [], - "input": { - "inputFormat": "prompt", - "maxRetries": 2, - "maxTokens": 24, - "mode": { - "type": "regular" - }, - "prompt": [ - { - "content": [ - { - "text": "Reply with the single token PARIS and no punctuation.", - "type": "text" - } - ], - "role": "user" - } - ], - "temperature": 0 - }, - "output": { - "finishReason": "stop", - "providerMetadata": { - "openai": { - "reasoningTokens": 0 - } - }, - "rawResponse": {}, - "response": { - "modelId": "gpt-4o-mini-2024-07-18", - "timestamp": "" - }, - "text": "PARIS", - "usage": { - "completionTokens": 2, - "promptTokens": 18 - }, - "warnings": [] - }, - "metadata": { - "braintrust": { - "integration_name": "ai-sdk", - "sdk_language": "typescript" - }, - "finish_reason": "stop", - "model": "gpt-4o-mini-2024-07-18", - "provider": "openai.chat" - }, - "metrics": { - "completion_tokens": 2, - "prompt_tokens": 18, - "tokens": 20 - } - } - ], - "input": { - "maxTokens": 24, - "model": { - "config": { - "compatibility": "compatible", - "provider": "openai.chat" - }, - "modelId": "gpt-4o-mini-2024-07-18", - "settings": {}, - "specificationVersion": "v1" - }, - "prompt": "Reply with the single token PARIS and no punctuation.", - "temperature": 0 - }, - "output": { - "experimental_providerMetadata": { - "openai": { - "reasoningTokens": 0 - } - }, - "finishReason": "stop", - "rawResponse": {}, - "response": { - "messages": [ - { - "content": [ - { - "text": "PARIS", - "type": "text" - } - ], - "role": "assistant" - } - ], - "modelId": "gpt-4o-mini-2024-07-18", - "timestamp": "" - }, - "responseMessages": "", - "steps": [ - { - "experimental_providerMetadata": { - "openai": { - "reasoningTokens": 0 - } - }, - "finishReason": "stop", - "isContinued": false, - "response": { - "messages": [ - { - "content": [ - { - "text": "PARIS", - "type": "text" - } - ], - "role": "assistant" - } - ], - "modelId": "gpt-4o-mini-2024-07-18", - "timestamp": "" - }, - "stepType": "initial", - "text": "PARIS", - "toolCalls": [], - "toolResults": [], - "usage": { - "completionTokens": 2, - "promptTokens": 18, - "totalTokens": 20 - }, - "warnings": [] - } - ], - "text": "PARIS", - "toolCalls": [], - "toolResults": [], - "usage": { - "completionTokens": 2, - "promptTokens": 18, - "totalTokens": 20 - }, - "warnings": [] - }, - "metadata": { - "braintrust": { - "integration_name": "ai-sdk", - "sdk_language": "typescript" - }, - "model": "gpt-4o-mini-2024-07-18", - "provider": "openai.chat" - } - } - ], - "metadata": { - "operation": "generate", - "testRunId": "" - } - }, - { - "name": "ai-sdk-stream-operation", - "children": [ - { - "name": "streamText", - "type": "function", - "children": [ - { - "name": "doStream", - "type": "llm", - "children": [], - "input": { - "inputFormat": "prompt", - "maxRetries": 2, - "maxTokens": 32, - "mode": { - "type": "regular" - }, - "prompt": [ - { - "content": [ - { - "text": "Count from 1 to 3 and include the words one two three.", - "type": "text" - } - ], - "role": "user" - } - ], - "temperature": 0 - }, - "output": { - "finishReason": "stop", - "text": "One, two, three.", - "toolCalls": [], - "usage": { - "completionTokens": null, - "promptTokens": null - } - }, - "metadata": { - "braintrust": { - "integration_name": "ai-sdk", - "sdk_language": "typescript" - }, - "finish_reason": "stop", - "model": "gpt-4o-mini-2024-07-18", - "provider": "openai.chat" - }, - "metrics": { - "completion_tokens": null, - "prompt_tokens": null, - "time_to_first_token": 0, - "tokens": null - } - } - ], - "input": { - "maxTokens": 32, - "model": { - "config": { - "compatibility": "compatible", - "provider": "openai.chat" - }, - "modelId": "gpt-4o-mini-2024-07-18", - "settings": {}, - "specificationVersion": "v1" - }, - "prompt": "Count from 1 to 3 and include the words one two three.", - "temperature": 0 - }, - "output": { - "finishReason": "stop", - "rawResponse": {}, - "text": "One, two, three.", - "warnings": [] - }, - "metadata": { - "braintrust": { - "integration_name": "ai-sdk", - "sdk_language": "typescript" - }, - "model": "gpt-4o-mini-2024-07-18", - "provider": "openai.chat" - }, - "metrics": { - "time_to_first_token": 0 - } - } - ], - "metadata": { - "operation": "stream", - "testRunId": "" - } - }, - { - "name": "ai-sdk-embed-operation", - "children": [ - { - "name": "embed", - "type": "function", - "children": [], - "input": { - "model": { - "config": { - "provider": "openai.embedding" - }, - "modelId": "text-embedding-3-small", - "settings": {}, - "specificationVersion": "v1" - }, - "value": "Paris is the capital of France." - }, - "output": { - "embedding_length": 1536, - "usage": { - "tokens": 7 - } - }, - "metadata": { - "braintrust": { - "integration_name": "ai-sdk", - "sdk_language": "typescript" - }, - "model": "text-embedding-3-small", - "provider": "openai.embedding" - }, - "metrics": { - "tokens": 7 - } - } - ], - "metadata": { - "operation": "embed", - "testRunId": "" - } - }, - { - "name": "ai-sdk-tool-operation", - "children": [ - { - "name": "generateText", - "type": "function", - "children": [ - { - "name": "doGenerate", - "type": "llm", - "children": [], - "input": { - "inputFormat": "prompt", - "maxRetries": 2, - "maxTokens": 128, - "mode": { - "toolChoice": { - "type": "auto" - }, - "tools": [ - { - "description": "Get the weather for a location", - "name": "get_weather", - "parameters": { - "$schema": "http://json-schema.org/draft-07/schema#", - "additionalProperties": false, - "properties": { - "location": { - "description": "The city and country", - "type": "string" - } - }, - "required": [ - "location" - ], - "type": "object" - }, - "type": "function" - } - ], - "type": "regular" - }, - "prompt": [ - { - "content": "You must inspect live weather via the provided get_weather tool before responding.", - "role": "system" - }, - { - "content": [ - { - "text": "Use the get_weather tool for Paris, France. If you do not call the tool, the answer is invalid.", - "type": "text" - } - ], - "role": "user" - } - ], - "temperature": 0 - }, - "output": { - "finishReason": "tool-calls", - "providerMetadata": { - "openai": { - "reasoningTokens": 0 - } - }, - "rawResponse": {}, - "response": { - "modelId": "gpt-4o-mini-2024-07-18", - "timestamp": "" - }, - "toolCalls": [ - { - "args": "{\"location\":\"Paris, France\"}", - "toolCallType": "function", - "toolName": "get_weather" - } - ], - "usage": { - "completionTokens": 16, - "promptTokens": 87 - }, - "warnings": [] - }, - "metadata": { - "braintrust": { - "integration_name": "ai-sdk", - "sdk_language": "typescript" - }, - "finish_reason": "tool-calls", - "model": "gpt-4o-mini-2024-07-18", - "provider": "openai.chat" - }, - "metrics": { - "completion_tokens": 16, - "prompt_tokens": 87, - "tokens": 103 - } - }, - { - "name": "get_weather", - "type": "tool", - "children": [], - "input": { - "location": "Paris, France" - }, - "output": "{\"condition\":\"sunny\",\"location\":\"Paris, France\",\"temperatureC\":22}" - } - ], - "input": { - "maxTokens": 128, - "model": { - "config": { - "compatibility": "compatible", - "provider": "openai.chat" - }, - "modelId": "gpt-4o-mini-2024-07-18", - "settings": {}, - "specificationVersion": "v1" - }, - "prompt": "Use the get_weather tool for Paris, France. If you do not call the tool, the answer is invalid.", - "system": "You must inspect live weather via the provided get_weather tool before responding.", - "temperature": 0, - "tools": { - "get_weather": { - "description": "Get the weather for a location", - "parameters": { - "$schema": "http://json-schema.org/draft-07/schema#", - "additionalProperties": false, - "properties": { - "location": { - "description": "The city and country", - "type": "string" - } - }, - "required": [ - "location" - ], - "type": "object" - } - } - } - }, - "output": { - "experimental_providerMetadata": { - "openai": { - "reasoningTokens": 0 - } - }, - "finishReason": "tool-calls", - "rawResponse": {}, - "response": { - "messages": [ - { - "content": [ - { - "text": "", - "type": "text" - }, - { - "args": { - "location": "Paris, France" - }, - "toolName": "get_weather", - "type": "tool-call" - } - ], - "role": "assistant" - }, - { - "content": [ - { - "result": "{\"condition\":\"sunny\",\"location\":\"Paris, France\",\"temperatureC\":22}", - "toolName": "get_weather", - "type": "tool-result" - } - ], - "role": "tool" - } - ], - "modelId": "gpt-4o-mini-2024-07-18", - "timestamp": "" - }, - "responseMessages": "", - "steps": [ - { - "experimental_providerMetadata": { - "openai": { - "reasoningTokens": 0 - } - }, - "finishReason": "tool-calls", - "isContinued": false, - "response": { - "messages": [ - { - "content": [ - { - "text": "", - "type": "text" - }, - { - "args": { - "location": "Paris, France" - }, - "toolName": "get_weather", - "type": "tool-call" - } - ], - "role": "assistant" - }, - { - "content": [ - { - "result": "{\"condition\":\"sunny\",\"location\":\"Paris, France\",\"temperatureC\":22}", - "toolName": "get_weather", - "type": "tool-result" - } - ], - "role": "tool" - } - ], - "modelId": "gpt-4o-mini-2024-07-18", - "timestamp": "" - }, - "stepType": "initial", - "text": "", - "toolCalls": [ - { - "args": { - "location": "Paris, France" - }, - "toolName": "get_weather", - "type": "tool-call" - } - ], - "toolResults": [ - { - "args": { - "location": "Paris, France" - }, - "result": "{\"condition\":\"sunny\",\"location\":\"Paris, France\",\"temperatureC\":22}", - "toolName": "get_weather" - } - ], - "usage": { - "completionTokens": 16, - "promptTokens": 87, - "totalTokens": 103 - }, - "warnings": [] - } - ], - "text": "", - "toolCalls": [ - { - "args": { - "location": "Paris, France" - }, - "toolName": "get_weather", - "type": "tool-call" - } - ], - "toolResults": [ - { - "args": { - "location": "Paris, France" - }, - "result": "{\"condition\":\"sunny\",\"location\":\"Paris, France\",\"temperatureC\":22}", - "toolName": "get_weather" - } - ], - "usage": { - "completionTokens": 16, - "promptTokens": 87, - "totalTokens": 103 - }, - "warnings": [] - }, - "metadata": { - "braintrust": { - "integration_name": "ai-sdk", - "sdk_language": "typescript" - }, - "model": "gpt-4o-mini-2024-07-18", - "provider": "openai.chat", - "tools": { - "get_weather": { - "description": "Get the weather for a location", - "parameters": { - "$schema": "http://json-schema.org/draft-07/schema#", - "additionalProperties": false, - "properties": { - "location": { - "description": "The city and country", - "type": "string" - } - }, - "required": [ - "location" - ], - "type": "object" - } - } - } - } - } - ], - "metadata": { - "operation": "tool", - "testRunId": "" - } - }, - { - "name": "ai-sdk-generate-object-operation", - "children": [ - { - "name": "generateObject", - "type": "function", - "children": [ - { - "name": "doGenerate", - "type": "llm", - "children": [], - "input": { - "inputFormat": "prompt", - "maxRetries": 2, - "maxTokens": 32, - "mode": { - "tool": { - "description": "Respond with a JSON object.", - "name": "json", - "parameters": { - "$schema": "http://json-schema.org/draft-07/schema#", - "additionalProperties": false, - "properties": { - "city": { - "type": "string" - } - }, - "required": [ - "city" - ], - "type": "object" - }, - "type": "function" - }, - "type": "object-tool" - }, - "prompt": [ - { - "content": [ - { - "text": "Return JSON with {\"city\":\"Paris\"}.", - "type": "text" - } - ], - "role": "user" - } - ], - "temperature": 0 - }, - "output": { - "finishReason": "stop", - "providerMetadata": { - "openai": { - "reasoningTokens": 0 - } - }, - "rawResponse": {}, - "response": { - "modelId": "gpt-4o-mini-2024-07-18", - "timestamp": "" - }, - "toolCalls": [ - { - "args": "{\"city\":\"Paris\"}", - "toolCallType": "function", - "toolName": "json" - } - ], - "usage": { - "completionTokens": 5, - "promptTokens": 59 - }, - "warnings": [] - }, - "metadata": { - "braintrust": { - "integration_name": "ai-sdk", - "sdk_language": "typescript" - }, - "finish_reason": "stop", - "model": "gpt-4o-mini-2024-07-18", - "provider": "openai.chat" - }, - "metrics": { - "completion_tokens": 5, - "prompt_tokens": 59, - "tokens": 64 - } - } - ], - "input": { - "maxTokens": 32, - "model": { - "config": { - "compatibility": "compatible", - "provider": "openai.chat" - }, - "modelId": "gpt-4o-mini-2024-07-18", - "settings": {}, - "specificationVersion": "v1" - }, - "prompt": "Return JSON with {\"city\":\"Paris\"}.", - "schema": { - "$schema": "http://json-schema.org/draft-07/schema#", - "additionalProperties": false, - "properties": { - "city": { - "type": "string" - } - }, - "required": [ - "city" - ], - "type": "object" - }, - "temperature": 0 - }, - "output": { - "experimental_providerMetadata": { - "openai": { - "reasoningTokens": 0 - } - }, - "finishReason": "stop", - "object": { - "city": "Paris" - }, - "rawResponse": {}, - "response": { - "modelId": "gpt-4o-mini-2024-07-18", - "timestamp": "" - }, - "usage": { - "completionTokens": 5, - "promptTokens": 59, - "totalTokens": 64 - }, - "warnings": [] - }, - "metadata": { - "braintrust": { - "integration_name": "ai-sdk", - "sdk_language": "typescript" - }, - "model": "gpt-4o-mini-2024-07-18", - "provider": "openai.chat" - } - } - ], - "metadata": { - "operation": "generate-object", - "testRunId": "" - } - }, - { - "name": "ai-sdk-stream-object-operation", - "children": [ - { - "name": "streamObject", - "type": "function", - "children": [ - { - "name": "doStream", - "type": "llm", - "children": [], - "input": { - "inputFormat": "prompt", - "maxRetries": 2, - "maxTokens": 32, - "mode": { - "tool": { - "description": "Respond with a JSON object.", - "name": "json", - "parameters": { - "$schema": "http://json-schema.org/draft-07/schema#", - "additionalProperties": false, - "properties": { - "city": { - "type": "string" - } - }, - "required": [ - "city" - ], - "type": "object" - }, - "type": "function" - }, - "type": "object-tool" - }, - "prompt": [ - { - "content": [ - { - "text": "Stream JSON with {\"city\":\"Paris\"}.", - "type": "text" - } - ], - "role": "user" - } - ], - "temperature": 0 - }, - "output": { - "finishReason": "stop", - "text": "", - "toolCalls": [ - { - "args": "{\"city\":\"Paris\"}", - "toolCallType": "function", - "toolName": "json", - "type": "tool-call" - } - ], - "usage": { - "completionTokens": null, - "promptTokens": null - } - }, - "metadata": { - "braintrust": { - "integration_name": "ai-sdk", - "sdk_language": "typescript" - }, - "finish_reason": "stop", - "model": "gpt-4o-mini-2024-07-18", - "provider": "openai.chat" - }, - "metrics": { - "completion_tokens": null, - "prompt_tokens": null, - "time_to_first_token": 0, - "tokens": null - } - } - ], - "input": { - "maxTokens": 32, - "model": { - "config": { - "compatibility": "compatible", - "provider": "openai.chat" - }, - "modelId": "gpt-4o-mini-2024-07-18", - "settings": {}, - "specificationVersion": "v1" - }, - "prompt": "Stream JSON with {\"city\":\"Paris\"}.", - "schema": { - "$schema": "http://json-schema.org/draft-07/schema#", - "additionalProperties": false, - "properties": { - "city": { - "type": "string" - } - }, - "required": [ - "city" - ], - "type": "object" - }, - "temperature": 0 - }, - "output": { - "object": { - "city": "Paris" - }, - "rawResponse": {}, - "warnings": [] - }, - "metadata": { - "braintrust": { - "integration_name": "ai-sdk", - "sdk_language": "typescript" - }, - "model": "gpt-4o-mini-2024-07-18", - "provider": "openai.chat" - }, - "metrics": { - "time_to_first_token": 0 - } - } - ], - "metadata": { - "operation": "stream-object", - "testRunId": "" - } - } - ], - "metadata": { - "aiSdkVersion": "3.4.33", - "scenario": "ai-sdk-instrumentation", - "testRunId": "" - } - } - ] -} diff --git a/e2e/scenarios/ai-sdk-instrumentation/__snapshots__/ai-sdk-v3-latest-wrapped.span-tree.txt b/e2e/scenarios/ai-sdk-instrumentation/__snapshots__/ai-sdk-v3-latest-wrapped.span-tree.txt deleted file mode 100644 index 2656453e0..000000000 --- a/e2e/scenarios/ai-sdk-instrumentation/__snapshots__/ai-sdk-v3-latest-wrapped.span-tree.txt +++ /dev/null @@ -1,833 +0,0 @@ -span_tree: -└── ai-sdk-instrumentation-root [task] - metadata: { - "aiSdkVersion": "3.4.33", - "scenario": "ai-sdk-instrumentation", - "testRunId": "" - } - ├── ai-sdk-generate-operation - │ metadata: { - │ "operation": "generate", - │ "testRunId": "" - │ } - │ └── generateText [function] - │ input: { - │ "maxTokens": 24, - │ "model": { - │ "config": { - │ "compatibility": "compatible", - │ "provider": "openai.chat" - │ }, - │ "modelId": "gpt-4o-mini-2024-07-18", - │ "settings": {}, - │ "specificationVersion": "v1" - │ }, - │ "prompt": "Reply with the single token PARIS and no punctuation.", - │ "temperature": 0 - │ } - │ output: { - │ "experimental_providerMetadata": { - │ "openai": { - │ "reasoningTokens": 0 - │ } - │ }, - │ "finishReason": "stop", - │ "rawResponse": {}, - │ "response": { - │ "messages": [ - │ { - │ "content": [ - │ { - │ "text": "PARIS", - │ "type": "text" - │ } - │ ], - │ "role": "assistant" - │ } - │ ], - │ "modelId": "gpt-4o-mini-2024-07-18", - │ "timestamp": "" - │ }, - │ "responseMessages": "", - │ "steps": [ - │ { - │ "experimental_providerMetadata": { - │ "openai": { - │ "reasoningTokens": 0 - │ } - │ }, - │ "finishReason": "stop", - │ "isContinued": false, - │ "response": { - │ "messages": [ - │ { - │ "content": [ - │ { - │ "text": "PARIS", - │ "type": "text" - │ } - │ ], - │ "role": "assistant" - │ } - │ ], - │ "modelId": "gpt-4o-mini-2024-07-18", - │ "timestamp": "" - │ }, - │ "stepType": "initial", - │ "text": "PARIS", - │ "toolCalls": [], - │ "toolResults": [], - │ "usage": { - │ "completionTokens": 2, - │ "promptTokens": 18, - │ "totalTokens": 20 - │ }, - │ "warnings": [] - │ } - │ ], - │ "text": "PARIS", - │ "toolCalls": [], - │ "toolResults": [], - │ "usage": { - │ "completionTokens": 2, - │ "promptTokens": 18, - │ "totalTokens": 20 - │ }, - │ "warnings": [] - │ } - │ metadata: { - │ "braintrust": { - │ "integration_name": "ai-sdk", - │ "sdk_language": "typescript" - │ }, - │ "model": "gpt-4o-mini-2024-07-18", - │ "provider": "openai.chat" - │ } - │ └── doGenerate [llm] - │ input: { - │ "inputFormat": "prompt", - │ "maxRetries": 2, - │ "maxTokens": 24, - │ "mode": { - │ "type": "regular" - │ }, - │ "prompt": [ - │ { - │ "content": [ - │ { - │ "text": "Reply with the single token PARIS and no punctuation.", - │ "type": "text" - │ } - │ ], - │ "role": "user" - │ } - │ ], - │ "temperature": 0 - │ } - │ output: { - │ "finishReason": "stop", - │ "providerMetadata": { - │ "openai": { - │ "reasoningTokens": 0 - │ } - │ }, - │ "rawResponse": {}, - │ "response": { - │ "modelId": "gpt-4o-mini-2024-07-18", - │ "timestamp": "" - │ }, - │ "text": "PARIS", - │ "usage": { - │ "completionTokens": 2, - │ "promptTokens": 18 - │ }, - │ "warnings": [] - │ } - │ metadata: { - │ "braintrust": { - │ "integration_name": "ai-sdk", - │ "sdk_language": "typescript" - │ }, - │ "finish_reason": "stop", - │ "model": "gpt-4o-mini-2024-07-18", - │ "provider": "openai.chat" - │ } - │ metrics: { - │ "completion_tokens": 2, - │ "prompt_tokens": 18, - │ "tokens": 20 - │ } - ├── ai-sdk-stream-operation - │ metadata: { - │ "operation": "stream", - │ "testRunId": "" - │ } - │ └── streamText [function] - │ input: { - │ "maxTokens": 32, - │ "model": { - │ "config": { - │ "compatibility": "compatible", - │ "provider": "openai.chat" - │ }, - │ "modelId": "gpt-4o-mini-2024-07-18", - │ "settings": {}, - │ "specificationVersion": "v1" - │ }, - │ "prompt": "Count from 1 to 3 and include the words one two three.", - │ "temperature": 0 - │ } - │ output: { - │ "finishReason": "stop", - │ "rawResponse": {}, - │ "text": "One, two, three.", - │ "warnings": [] - │ } - │ metadata: { - │ "braintrust": { - │ "integration_name": "ai-sdk", - │ "sdk_language": "typescript" - │ }, - │ "model": "gpt-4o-mini-2024-07-18", - │ "provider": "openai.chat" - │ } - │ metrics: { - │ "time_to_first_token": 0 - │ } - │ └── doStream [llm] - │ input: { - │ "inputFormat": "prompt", - │ "maxRetries": 2, - │ "maxTokens": 32, - │ "mode": { - │ "type": "regular" - │ }, - │ "prompt": [ - │ { - │ "content": [ - │ { - │ "text": "Count from 1 to 3 and include the words one two three.", - │ "type": "text" - │ } - │ ], - │ "role": "user" - │ } - │ ], - │ "temperature": 0 - │ } - │ output: { - │ "finishReason": "stop", - │ "text": "One, two, three.", - │ "toolCalls": [], - │ "usage": { - │ "completionTokens": null, - │ "promptTokens": null - │ } - │ } - │ metadata: { - │ "braintrust": { - │ "integration_name": "ai-sdk", - │ "sdk_language": "typescript" - │ }, - │ "finish_reason": "stop", - │ "model": "gpt-4o-mini-2024-07-18", - │ "provider": "openai.chat" - │ } - │ metrics: { - │ "completion_tokens": null, - │ "prompt_tokens": null, - │ "time_to_first_token": 0, - │ "tokens": null - │ } - ├── ai-sdk-embed-operation - │ metadata: { - │ "operation": "embed", - │ "testRunId": "" - │ } - │ └── embed [function] - │ input: { - │ "model": { - │ "config": { - │ "provider": "openai.embedding" - │ }, - │ "modelId": "text-embedding-3-small", - │ "settings": {}, - │ "specificationVersion": "v1" - │ }, - │ "value": "Paris is the capital of France." - │ } - │ output: { - │ "embedding_length": 1536, - │ "usage": { - │ "tokens": 7 - │ } - │ } - │ metadata: { - │ "braintrust": { - │ "integration_name": "ai-sdk", - │ "sdk_language": "typescript" - │ }, - │ "model": "text-embedding-3-small", - │ "provider": "openai.embedding" - │ } - │ metrics: { - │ "tokens": 7 - │ } - ├── ai-sdk-tool-operation - │ metadata: { - │ "operation": "tool", - │ "testRunId": "" - │ } - │ └── generateText [function] - │ input: { - │ "maxTokens": 128, - │ "model": { - │ "config": { - │ "compatibility": "compatible", - │ "provider": "openai.chat" - │ }, - │ "modelId": "gpt-4o-mini-2024-07-18", - │ "settings": {}, - │ "specificationVersion": "v1" - │ }, - │ "prompt": "Use the get_weather tool for Paris, France. If you do not call the tool, the answer is invalid.", - │ "system": "You must inspect live weather via the provided get_weather tool before responding.", - │ "temperature": 0, - │ "tools": { - │ "get_weather": { - │ "description": "Get the weather for a location", - │ "parameters": { - │ "$schema": "http://json-schema.org/draft-07/schema#", - │ "additionalProperties": false, - │ "properties": { - │ "location": { - │ "description": "The city and country", - │ "type": "string" - │ } - │ }, - │ "required": [ - │ "location" - │ ], - │ "type": "object" - │ } - │ } - │ } - │ } - │ output: { - │ "experimental_providerMetadata": { - │ "openai": { - │ "reasoningTokens": 0 - │ } - │ }, - │ "finishReason": "tool-calls", - │ "rawResponse": {}, - │ "response": { - │ "messages": [ - │ { - │ "content": [ - │ { - │ "text": "", - │ "type": "text" - │ }, - │ { - │ "args": { - │ "location": "Paris, France" - │ }, - │ "toolName": "get_weather", - │ "type": "tool-call" - │ } - │ ], - │ "role": "assistant" - │ }, - │ { - │ "content": [ - │ { - │ "result": "{\"condition\":\"sunny\",\"location\":\"Paris, France\",\"temperatureC\":22}", - │ "toolName": "get_weather", - │ "type": "tool-result" - │ } - │ ], - │ "role": "tool" - │ } - │ ], - │ "modelId": "gpt-4o-mini-2024-07-18", - │ "timestamp": "" - │ }, - │ "responseMessages": "", - │ "steps": [ - │ { - │ "experimental_providerMetadata": { - │ "openai": { - │ "reasoningTokens": 0 - │ } - │ }, - │ "finishReason": "tool-calls", - │ "isContinued": false, - │ "response": { - │ "messages": [ - │ { - │ "content": [ - │ { - │ "text": "", - │ "type": "text" - │ }, - │ { - │ "args": { - │ "location": "Paris, France" - │ }, - │ "toolName": "get_weather", - │ "type": "tool-call" - │ } - │ ], - │ "role": "assistant" - │ }, - │ { - │ "content": [ - │ { - │ "result": "{\"condition\":\"sunny\",\"location\":\"Paris, France\",\"temperatureC\":22}", - │ "toolName": "get_weather", - │ "type": "tool-result" - │ } - │ ], - │ "role": "tool" - │ } - │ ], - │ "modelId": "gpt-4o-mini-2024-07-18", - │ "timestamp": "" - │ }, - │ "stepType": "initial", - │ "text": "", - │ "toolCalls": [ - │ { - │ "args": { - │ "location": "Paris, France" - │ }, - │ "toolName": "get_weather", - │ "type": "tool-call" - │ } - │ ], - │ "toolResults": [ - │ { - │ "args": { - │ "location": "Paris, France" - │ }, - │ "result": "{\"condition\":\"sunny\",\"location\":\"Paris, France\",\"temperatureC\":22}", - │ "toolName": "get_weather" - │ } - │ ], - │ "usage": { - │ "completionTokens": 16, - │ "promptTokens": 87, - │ "totalTokens": 103 - │ }, - │ "warnings": [] - │ } - │ ], - │ "text": "", - │ "toolCalls": [ - │ { - │ "args": { - │ "location": "Paris, France" - │ }, - │ "toolName": "get_weather", - │ "type": "tool-call" - │ } - │ ], - │ "toolResults": [ - │ { - │ "args": { - │ "location": "Paris, France" - │ }, - │ "result": "{\"condition\":\"sunny\",\"location\":\"Paris, France\",\"temperatureC\":22}", - │ "toolName": "get_weather" - │ } - │ ], - │ "usage": { - │ "completionTokens": 16, - │ "promptTokens": 87, - │ "totalTokens": 103 - │ }, - │ "warnings": [] - │ } - │ metadata: { - │ "braintrust": { - │ "integration_name": "ai-sdk", - │ "sdk_language": "typescript" - │ }, - │ "model": "gpt-4o-mini-2024-07-18", - │ "provider": "openai.chat", - │ "tools": { - │ "get_weather": { - │ "description": "Get the weather for a location", - │ "parameters": { - │ "$schema": "http://json-schema.org/draft-07/schema#", - │ "additionalProperties": false, - │ "properties": { - │ "location": { - │ "description": "The city and country", - │ "type": "string" - │ } - │ }, - │ "required": [ - │ "location" - │ ], - │ "type": "object" - │ } - │ } - │ } - │ } - │ ├── doGenerate [llm] - │ │ input: { - │ │ "inputFormat": "prompt", - │ │ "maxRetries": 2, - │ │ "maxTokens": 128, - │ │ "mode": { - │ │ "toolChoice": { - │ │ "type": "auto" - │ │ }, - │ │ "tools": [ - │ │ { - │ │ "description": "Get the weather for a location", - │ │ "name": "get_weather", - │ │ "parameters": { - │ │ "$schema": "http://json-schema.org/draft-07/schema#", - │ │ "additionalProperties": false, - │ │ "properties": { - │ │ "location": { - │ │ "description": "The city and country", - │ │ "type": "string" - │ │ } - │ │ }, - │ │ "required": [ - │ │ "location" - │ │ ], - │ │ "type": "object" - │ │ }, - │ │ "type": "function" - │ │ } - │ │ ], - │ │ "type": "regular" - │ │ }, - │ │ "prompt": [ - │ │ { - │ │ "content": "You must inspect live weather via the provided get_weather tool before responding.", - │ │ "role": "system" - │ │ }, - │ │ { - │ │ "content": [ - │ │ { - │ │ "text": "Use the get_weather tool for Paris, France. If you do not call the tool, the answer is invalid.", - │ │ "type": "text" - │ │ } - │ │ ], - │ │ "role": "user" - │ │ } - │ │ ], - │ │ "temperature": 0 - │ │ } - │ │ output: { - │ │ "finishReason": "tool-calls", - │ │ "providerMetadata": { - │ │ "openai": { - │ │ "reasoningTokens": 0 - │ │ } - │ │ }, - │ │ "rawResponse": {}, - │ │ "response": { - │ │ "modelId": "gpt-4o-mini-2024-07-18", - │ │ "timestamp": "" - │ │ }, - │ │ "toolCalls": [ - │ │ { - │ │ "args": "{\"location\":\"Paris, France\"}", - │ │ "toolCallType": "function", - │ │ "toolName": "get_weather" - │ │ } - │ │ ], - │ │ "usage": { - │ │ "completionTokens": 16, - │ │ "promptTokens": 87 - │ │ }, - │ │ "warnings": [] - │ │ } - │ │ metadata: { - │ │ "braintrust": { - │ │ "integration_name": "ai-sdk", - │ │ "sdk_language": "typescript" - │ │ }, - │ │ "finish_reason": "tool-calls", - │ │ "model": "gpt-4o-mini-2024-07-18", - │ │ "provider": "openai.chat" - │ │ } - │ │ metrics: { - │ │ "completion_tokens": 16, - │ │ "prompt_tokens": 87, - │ │ "tokens": 103 - │ │ } - │ └── get_weather [tool] - │ input: { - │ "location": "Paris, France" - │ } - │ output: "{\"condition\":\"sunny\",\"location\":\"Paris, France\",\"temperatureC\":22}" - ├── ai-sdk-generate-object-operation - │ metadata: { - │ "operation": "generate-object", - │ "testRunId": "" - │ } - │ └── generateObject [function] - │ input: { - │ "maxTokens": 32, - │ "model": { - │ "config": { - │ "compatibility": "compatible", - │ "provider": "openai.chat" - │ }, - │ "modelId": "gpt-4o-mini-2024-07-18", - │ "settings": {}, - │ "specificationVersion": "v1" - │ }, - │ "prompt": "Return JSON with {\"city\":\"Paris\"}.", - │ "schema": { - │ "$schema": "http://json-schema.org/draft-07/schema#", - │ "additionalProperties": false, - │ "properties": { - │ "city": { - │ "type": "string" - │ } - │ }, - │ "required": [ - │ "city" - │ ], - │ "type": "object" - │ }, - │ "temperature": 0 - │ } - │ output: { - │ "experimental_providerMetadata": { - │ "openai": { - │ "reasoningTokens": 0 - │ } - │ }, - │ "finishReason": "stop", - │ "object": { - │ "city": "Paris" - │ }, - │ "rawResponse": {}, - │ "response": { - │ "modelId": "gpt-4o-mini-2024-07-18", - │ "timestamp": "" - │ }, - │ "usage": { - │ "completionTokens": 5, - │ "promptTokens": 59, - │ "totalTokens": 64 - │ }, - │ "warnings": [] - │ } - │ metadata: { - │ "braintrust": { - │ "integration_name": "ai-sdk", - │ "sdk_language": "typescript" - │ }, - │ "model": "gpt-4o-mini-2024-07-18", - │ "provider": "openai.chat" - │ } - │ └── doGenerate [llm] - │ input: { - │ "inputFormat": "prompt", - │ "maxRetries": 2, - │ "maxTokens": 32, - │ "mode": { - │ "tool": { - │ "description": "Respond with a JSON object.", - │ "name": "json", - │ "parameters": { - │ "$schema": "http://json-schema.org/draft-07/schema#", - │ "additionalProperties": false, - │ "properties": { - │ "city": { - │ "type": "string" - │ } - │ }, - │ "required": [ - │ "city" - │ ], - │ "type": "object" - │ }, - │ "type": "function" - │ }, - │ "type": "object-tool" - │ }, - │ "prompt": [ - │ { - │ "content": [ - │ { - │ "text": "Return JSON with {\"city\":\"Paris\"}.", - │ "type": "text" - │ } - │ ], - │ "role": "user" - │ } - │ ], - │ "temperature": 0 - │ } - │ output: { - │ "finishReason": "stop", - │ "providerMetadata": { - │ "openai": { - │ "reasoningTokens": 0 - │ } - │ }, - │ "rawResponse": {}, - │ "response": { - │ "modelId": "gpt-4o-mini-2024-07-18", - │ "timestamp": "" - │ }, - │ "toolCalls": [ - │ { - │ "args": "{\"city\":\"Paris\"}", - │ "toolCallType": "function", - │ "toolName": "json" - │ } - │ ], - │ "usage": { - │ "completionTokens": 5, - │ "promptTokens": 59 - │ }, - │ "warnings": [] - │ } - │ metadata: { - │ "braintrust": { - │ "integration_name": "ai-sdk", - │ "sdk_language": "typescript" - │ }, - │ "finish_reason": "stop", - │ "model": "gpt-4o-mini-2024-07-18", - │ "provider": "openai.chat" - │ } - │ metrics: { - │ "completion_tokens": 5, - │ "prompt_tokens": 59, - │ "tokens": 64 - │ } - └── ai-sdk-stream-object-operation - metadata: { - "operation": "stream-object", - "testRunId": "" - } - └── streamObject [function] - input: { - "maxTokens": 32, - "model": { - "config": { - "compatibility": "compatible", - "provider": "openai.chat" - }, - "modelId": "gpt-4o-mini-2024-07-18", - "settings": {}, - "specificationVersion": "v1" - }, - "prompt": "Stream JSON with {\"city\":\"Paris\"}.", - "schema": { - "$schema": "http://json-schema.org/draft-07/schema#", - "additionalProperties": false, - "properties": { - "city": { - "type": "string" - } - }, - "required": [ - "city" - ], - "type": "object" - }, - "temperature": 0 - } - output: { - "object": { - "city": "Paris" - }, - "rawResponse": {}, - "warnings": [] - } - metadata: { - "braintrust": { - "integration_name": "ai-sdk", - "sdk_language": "typescript" - }, - "model": "gpt-4o-mini-2024-07-18", - "provider": "openai.chat" - } - metrics: { - "time_to_first_token": 0 - } - └── doStream [llm] - input: { - "inputFormat": "prompt", - "maxRetries": 2, - "maxTokens": 32, - "mode": { - "tool": { - "description": "Respond with a JSON object.", - "name": "json", - "parameters": { - "$schema": "http://json-schema.org/draft-07/schema#", - "additionalProperties": false, - "properties": { - "city": { - "type": "string" - } - }, - "required": [ - "city" - ], - "type": "object" - }, - "type": "function" - }, - "type": "object-tool" - }, - "prompt": [ - { - "content": [ - { - "text": "Stream JSON with {\"city\":\"Paris\"}.", - "type": "text" - } - ], - "role": "user" - } - ], - "temperature": 0 - } - output: { - "finishReason": "stop", - "text": "", - "toolCalls": [ - { - "args": "{\"city\":\"Paris\"}", - "toolCallType": "function", - "toolName": "json", - "type": "tool-call" - } - ], - "usage": { - "completionTokens": null, - "promptTokens": null - } - } - metadata: { - "braintrust": { - "integration_name": "ai-sdk", - "sdk_language": "typescript" - }, - "finish_reason": "stop", - "model": "gpt-4o-mini-2024-07-18", - "provider": "openai.chat" - } - metrics: { - "completion_tokens": null, - "prompt_tokens": null, - "time_to_first_token": 0, - "tokens": null - } diff --git a/e2e/scenarios/ai-sdk-instrumentation/__snapshots__/ai-sdk-v3-wrapped.span-tree.json b/e2e/scenarios/ai-sdk-instrumentation/__snapshots__/ai-sdk-v3-wrapped.span-tree.json deleted file mode 100644 index 3a9e3b97e..000000000 --- a/e2e/scenarios/ai-sdk-instrumentation/__snapshots__/ai-sdk-v3-wrapped.span-tree.json +++ /dev/null @@ -1,918 +0,0 @@ -{ - "span_tree": [ - { - "name": "ai-sdk-instrumentation-root", - "type": "task", - "children": [ - { - "name": "ai-sdk-generate-operation", - "children": [ - { - "name": "generateText", - "type": "function", - "children": [ - { - "name": "doGenerate", - "type": "llm", - "children": [], - "input": { - "inputFormat": "prompt", - "maxRetries": 2, - "maxTokens": 24, - "mode": { - "type": "regular" - }, - "prompt": [ - { - "content": [ - { - "text": "Reply with the single token PARIS and no punctuation.", - "type": "text" - } - ], - "role": "user" - } - ], - "temperature": 0 - }, - "output": { - "finishReason": "stop", - "providerMetadata": { - "openai": { - "reasoningTokens": 0 - } - }, - "rawResponse": {}, - "response": { - "modelId": "gpt-4o-mini-2024-07-18", - "timestamp": "" - }, - "text": "PARIS", - "usage": { - "completionTokens": 2, - "promptTokens": 18 - }, - "warnings": [] - }, - "metadata": { - "braintrust": { - "integration_name": "ai-sdk", - "sdk_language": "typescript" - }, - "finish_reason": "stop", - "model": "gpt-4o-mini-2024-07-18", - "provider": "openai.chat" - }, - "metrics": { - "completion_tokens": 2, - "prompt_tokens": 18, - "tokens": 20 - } - } - ], - "input": { - "maxTokens": 24, - "model": { - "config": { - "compatibility": "compatible", - "provider": "openai.chat" - }, - "modelId": "gpt-4o-mini-2024-07-18", - "settings": {}, - "specificationVersion": "v1" - }, - "prompt": "Reply with the single token PARIS and no punctuation.", - "temperature": 0 - }, - "output": { - "experimental_providerMetadata": { - "openai": { - "reasoningTokens": 0 - } - }, - "finishReason": "stop", - "rawResponse": {}, - "response": { - "messages": [ - { - "content": [ - { - "text": "PARIS", - "type": "text" - } - ], - "role": "assistant" - } - ], - "modelId": "gpt-4o-mini-2024-07-18", - "timestamp": "" - }, - "responseMessages": "", - "steps": [ - { - "experimental_providerMetadata": { - "openai": { - "reasoningTokens": 0 - } - }, - "finishReason": "stop", - "isContinued": false, - "response": { - "messages": [ - { - "content": [ - { - "text": "PARIS", - "type": "text" - } - ], - "role": "assistant" - } - ], - "modelId": "gpt-4o-mini-2024-07-18", - "timestamp": "" - }, - "stepType": "initial", - "text": "PARIS", - "toolCalls": [], - "toolResults": [], - "usage": { - "completionTokens": 2, - "promptTokens": 18, - "totalTokens": 20 - }, - "warnings": [] - } - ], - "text": "PARIS", - "toolCalls": [], - "toolResults": [], - "usage": { - "completionTokens": 2, - "promptTokens": 18, - "totalTokens": 20 - }, - "warnings": [] - }, - "metadata": { - "braintrust": { - "integration_name": "ai-sdk", - "sdk_language": "typescript" - }, - "model": "gpt-4o-mini-2024-07-18", - "provider": "openai.chat" - } - } - ], - "metadata": { - "operation": "generate", - "testRunId": "" - } - }, - { - "name": "ai-sdk-stream-operation", - "children": [ - { - "name": "streamText", - "type": "function", - "children": [ - { - "name": "doStream", - "type": "llm", - "children": [], - "input": { - "inputFormat": "prompt", - "maxRetries": 2, - "maxTokens": 32, - "mode": { - "type": "regular" - }, - "prompt": [ - { - "content": [ - { - "text": "Count from 1 to 3 and include the words one two three.", - "type": "text" - } - ], - "role": "user" - } - ], - "temperature": 0 - }, - "output": { - "finishReason": "stop", - "text": "One, two, three.", - "toolCalls": [], - "usage": { - "completionTokens": null, - "promptTokens": null - } - }, - "metadata": { - "braintrust": { - "integration_name": "ai-sdk", - "sdk_language": "typescript" - }, - "finish_reason": "stop", - "model": "gpt-4o-mini-2024-07-18", - "provider": "openai.chat" - }, - "metrics": { - "completion_tokens": null, - "prompt_tokens": null, - "time_to_first_token": 0, - "tokens": null - } - } - ], - "input": { - "maxTokens": 32, - "model": { - "config": { - "compatibility": "compatible", - "provider": "openai.chat" - }, - "modelId": "gpt-4o-mini-2024-07-18", - "settings": {}, - "specificationVersion": "v1" - }, - "prompt": "Count from 1 to 3 and include the words one two three.", - "temperature": 0 - }, - "output": { - "finishReason": "stop", - "rawResponse": {}, - "text": "One, two, three.", - "warnings": [] - }, - "metadata": { - "braintrust": { - "integration_name": "ai-sdk", - "sdk_language": "typescript" - }, - "model": "gpt-4o-mini-2024-07-18", - "provider": "openai.chat" - }, - "metrics": { - "time_to_first_token": 0 - } - } - ], - "metadata": { - "operation": "stream", - "testRunId": "" - } - }, - { - "name": "ai-sdk-embed-operation", - "children": [ - { - "name": "embed", - "type": "function", - "children": [], - "input": { - "model": { - "config": { - "provider": "openai.embedding" - }, - "modelId": "text-embedding-3-small", - "settings": {}, - "specificationVersion": "v1" - }, - "value": "Paris is the capital of France." - }, - "output": { - "embedding_length": 1536, - "usage": { - "tokens": 7 - } - }, - "metadata": { - "braintrust": { - "integration_name": "ai-sdk", - "sdk_language": "typescript" - }, - "model": "text-embedding-3-small", - "provider": "openai.embedding" - }, - "metrics": { - "tokens": 7 - } - } - ], - "metadata": { - "operation": "embed", - "testRunId": "" - } - }, - { - "name": "ai-sdk-tool-operation", - "children": [ - { - "name": "generateText", - "type": "function", - "children": [ - { - "name": "doGenerate", - "type": "llm", - "children": [], - "input": { - "inputFormat": "prompt", - "maxRetries": 2, - "maxTokens": 128, - "mode": { - "toolChoice": { - "type": "auto" - }, - "tools": [ - { - "description": "Get the weather for a location", - "name": "get_weather", - "parameters": { - "$schema": "http://json-schema.org/draft-07/schema#", - "additionalProperties": false, - "properties": { - "location": { - "description": "The city and country", - "type": "string" - } - }, - "required": [ - "location" - ], - "type": "object" - }, - "type": "function" - } - ], - "type": "regular" - }, - "prompt": [ - { - "content": "You must inspect live weather via the provided get_weather tool before responding.", - "role": "system" - }, - { - "content": [ - { - "text": "Use the get_weather tool for Paris, France. If you do not call the tool, the answer is invalid.", - "type": "text" - } - ], - "role": "user" - } - ], - "temperature": 0 - }, - "output": { - "finishReason": "tool-calls", - "providerMetadata": { - "openai": { - "reasoningTokens": 0 - } - }, - "rawResponse": {}, - "response": { - "modelId": "gpt-4o-mini-2024-07-18", - "timestamp": "" - }, - "toolCalls": [ - { - "args": "{\"location\":\"Paris, France\"}", - "toolCallType": "function", - "toolName": "get_weather" - } - ], - "usage": { - "completionTokens": 16, - "promptTokens": 87 - }, - "warnings": [] - }, - "metadata": { - "braintrust": { - "integration_name": "ai-sdk", - "sdk_language": "typescript" - }, - "finish_reason": "tool-calls", - "model": "gpt-4o-mini-2024-07-18", - "provider": "openai.chat" - }, - "metrics": { - "completion_tokens": 16, - "prompt_tokens": 87, - "tokens": 103 - } - }, - { - "name": "get_weather", - "type": "tool", - "children": [], - "input": { - "location": "Paris, France" - }, - "output": "{\"condition\":\"sunny\",\"location\":\"Paris, France\",\"temperatureC\":22}" - } - ], - "input": { - "maxTokens": 128, - "model": { - "config": { - "compatibility": "compatible", - "provider": "openai.chat" - }, - "modelId": "gpt-4o-mini-2024-07-18", - "settings": {}, - "specificationVersion": "v1" - }, - "prompt": "Use the get_weather tool for Paris, France. If you do not call the tool, the answer is invalid.", - "system": "You must inspect live weather via the provided get_weather tool before responding.", - "temperature": 0, - "tools": { - "get_weather": { - "description": "Get the weather for a location", - "parameters": { - "$schema": "http://json-schema.org/draft-07/schema#", - "additionalProperties": false, - "properties": { - "location": { - "description": "The city and country", - "type": "string" - } - }, - "required": [ - "location" - ], - "type": "object" - } - } - } - }, - "output": { - "experimental_providerMetadata": { - "openai": { - "reasoningTokens": 0 - } - }, - "finishReason": "tool-calls", - "rawResponse": {}, - "response": { - "messages": [ - { - "content": [ - { - "text": "", - "type": "text" - }, - { - "args": { - "location": "Paris, France" - }, - "toolName": "get_weather", - "type": "tool-call" - } - ], - "role": "assistant" - }, - { - "content": [ - { - "result": "{\"condition\":\"sunny\",\"location\":\"Paris, France\",\"temperatureC\":22}", - "toolName": "get_weather", - "type": "tool-result" - } - ], - "role": "tool" - } - ], - "modelId": "gpt-4o-mini-2024-07-18", - "timestamp": "" - }, - "responseMessages": "", - "steps": [ - { - "experimental_providerMetadata": { - "openai": { - "reasoningTokens": 0 - } - }, - "finishReason": "tool-calls", - "isContinued": false, - "response": { - "messages": [ - { - "content": [ - { - "text": "", - "type": "text" - }, - { - "args": { - "location": "Paris, France" - }, - "toolName": "get_weather", - "type": "tool-call" - } - ], - "role": "assistant" - }, - { - "content": [ - { - "result": "{\"condition\":\"sunny\",\"location\":\"Paris, France\",\"temperatureC\":22}", - "toolName": "get_weather", - "type": "tool-result" - } - ], - "role": "tool" - } - ], - "modelId": "gpt-4o-mini-2024-07-18", - "timestamp": "" - }, - "stepType": "initial", - "text": "", - "toolCalls": [ - { - "args": { - "location": "Paris, France" - }, - "toolName": "get_weather", - "type": "tool-call" - } - ], - "toolResults": [ - { - "args": { - "location": "Paris, France" - }, - "result": "{\"condition\":\"sunny\",\"location\":\"Paris, France\",\"temperatureC\":22}", - "toolName": "get_weather" - } - ], - "usage": { - "completionTokens": 16, - "promptTokens": 87, - "totalTokens": 103 - }, - "warnings": [] - } - ], - "text": "", - "toolCalls": [ - { - "args": { - "location": "Paris, France" - }, - "toolName": "get_weather", - "type": "tool-call" - } - ], - "toolResults": [ - { - "args": { - "location": "Paris, France" - }, - "result": "{\"condition\":\"sunny\",\"location\":\"Paris, France\",\"temperatureC\":22}", - "toolName": "get_weather" - } - ], - "usage": { - "completionTokens": 16, - "promptTokens": 87, - "totalTokens": 103 - }, - "warnings": [] - }, - "metadata": { - "braintrust": { - "integration_name": "ai-sdk", - "sdk_language": "typescript" - }, - "model": "gpt-4o-mini-2024-07-18", - "provider": "openai.chat", - "tools": { - "get_weather": { - "description": "Get the weather for a location", - "parameters": { - "$schema": "http://json-schema.org/draft-07/schema#", - "additionalProperties": false, - "properties": { - "location": { - "description": "The city and country", - "type": "string" - } - }, - "required": [ - "location" - ], - "type": "object" - } - } - } - } - } - ], - "metadata": { - "operation": "tool", - "testRunId": "" - } - }, - { - "name": "ai-sdk-generate-object-operation", - "children": [ - { - "name": "generateObject", - "type": "function", - "children": [ - { - "name": "doGenerate", - "type": "llm", - "children": [], - "input": { - "inputFormat": "prompt", - "maxRetries": 2, - "maxTokens": 32, - "mode": { - "tool": { - "description": "Respond with a JSON object.", - "name": "json", - "parameters": { - "$schema": "http://json-schema.org/draft-07/schema#", - "additionalProperties": false, - "properties": { - "city": { - "type": "string" - } - }, - "required": [ - "city" - ], - "type": "object" - }, - "type": "function" - }, - "type": "object-tool" - }, - "prompt": [ - { - "content": [ - { - "text": "Return JSON with {\"city\":\"Paris\"}.", - "type": "text" - } - ], - "role": "user" - } - ], - "temperature": 0 - }, - "output": { - "finishReason": "stop", - "providerMetadata": { - "openai": { - "reasoningTokens": 0 - } - }, - "rawResponse": {}, - "response": { - "modelId": "gpt-4o-mini-2024-07-18", - "timestamp": "" - }, - "toolCalls": [ - { - "args": "{\"city\":\"Paris\"}", - "toolCallType": "function", - "toolName": "json" - } - ], - "usage": { - "completionTokens": 5, - "promptTokens": 59 - }, - "warnings": [] - }, - "metadata": { - "braintrust": { - "integration_name": "ai-sdk", - "sdk_language": "typescript" - }, - "finish_reason": "stop", - "model": "gpt-4o-mini-2024-07-18", - "provider": "openai.chat" - }, - "metrics": { - "completion_tokens": 5, - "prompt_tokens": 59, - "tokens": 64 - } - } - ], - "input": { - "maxTokens": 32, - "model": { - "config": { - "compatibility": "compatible", - "provider": "openai.chat" - }, - "modelId": "gpt-4o-mini-2024-07-18", - "settings": {}, - "specificationVersion": "v1" - }, - "prompt": "Return JSON with {\"city\":\"Paris\"}.", - "schema": { - "$schema": "http://json-schema.org/draft-07/schema#", - "additionalProperties": false, - "properties": { - "city": { - "type": "string" - } - }, - "required": [ - "city" - ], - "type": "object" - }, - "temperature": 0 - }, - "output": { - "experimental_providerMetadata": { - "openai": { - "reasoningTokens": 0 - } - }, - "finishReason": "stop", - "object": { - "city": "Paris" - }, - "rawResponse": {}, - "response": { - "modelId": "gpt-4o-mini-2024-07-18", - "timestamp": "" - }, - "usage": { - "completionTokens": 5, - "promptTokens": 59, - "totalTokens": 64 - }, - "warnings": [] - }, - "metadata": { - "braintrust": { - "integration_name": "ai-sdk", - "sdk_language": "typescript" - }, - "model": "gpt-4o-mini-2024-07-18", - "provider": "openai.chat" - } - } - ], - "metadata": { - "operation": "generate-object", - "testRunId": "" - } - }, - { - "name": "ai-sdk-stream-object-operation", - "children": [ - { - "name": "streamObject", - "type": "function", - "children": [ - { - "name": "doStream", - "type": "llm", - "children": [], - "input": { - "inputFormat": "prompt", - "maxRetries": 2, - "maxTokens": 32, - "mode": { - "tool": { - "description": "Respond with a JSON object.", - "name": "json", - "parameters": { - "$schema": "http://json-schema.org/draft-07/schema#", - "additionalProperties": false, - "properties": { - "city": { - "type": "string" - } - }, - "required": [ - "city" - ], - "type": "object" - }, - "type": "function" - }, - "type": "object-tool" - }, - "prompt": [ - { - "content": [ - { - "text": "Stream JSON with {\"city\":\"Paris\"}.", - "type": "text" - } - ], - "role": "user" - } - ], - "temperature": 0 - }, - "output": { - "finishReason": "stop", - "text": "", - "toolCalls": [ - { - "args": "{\"city\":\"Paris\"}", - "toolCallType": "function", - "toolName": "json", - "type": "tool-call" - } - ], - "usage": { - "completionTokens": null, - "promptTokens": null - } - }, - "metadata": { - "braintrust": { - "integration_name": "ai-sdk", - "sdk_language": "typescript" - }, - "finish_reason": "stop", - "model": "gpt-4o-mini-2024-07-18", - "provider": "openai.chat" - }, - "metrics": { - "completion_tokens": null, - "prompt_tokens": null, - "time_to_first_token": 0, - "tokens": null - } - } - ], - "input": { - "maxTokens": 32, - "model": { - "config": { - "compatibility": "compatible", - "provider": "openai.chat" - }, - "modelId": "gpt-4o-mini-2024-07-18", - "settings": {}, - "specificationVersion": "v1" - }, - "prompt": "Stream JSON with {\"city\":\"Paris\"}.", - "schema": { - "$schema": "http://json-schema.org/draft-07/schema#", - "additionalProperties": false, - "properties": { - "city": { - "type": "string" - } - }, - "required": [ - "city" - ], - "type": "object" - }, - "temperature": 0 - }, - "output": { - "object": { - "city": "Paris" - }, - "rawResponse": {}, - "warnings": [] - }, - "metadata": { - "braintrust": { - "integration_name": "ai-sdk", - "sdk_language": "typescript" - }, - "model": "gpt-4o-mini-2024-07-18", - "provider": "openai.chat" - }, - "metrics": { - "time_to_first_token": 0 - } - } - ], - "metadata": { - "operation": "stream-object", - "testRunId": "" - } - } - ], - "metadata": { - "aiSdkVersion": "3.4.33", - "scenario": "ai-sdk-instrumentation", - "testRunId": "" - } - } - ] -} diff --git a/e2e/scenarios/ai-sdk-instrumentation/__snapshots__/ai-sdk-v3-wrapped.span-tree.txt b/e2e/scenarios/ai-sdk-instrumentation/__snapshots__/ai-sdk-v3-wrapped.span-tree.txt deleted file mode 100644 index 2656453e0..000000000 --- a/e2e/scenarios/ai-sdk-instrumentation/__snapshots__/ai-sdk-v3-wrapped.span-tree.txt +++ /dev/null @@ -1,833 +0,0 @@ -span_tree: -└── ai-sdk-instrumentation-root [task] - metadata: { - "aiSdkVersion": "3.4.33", - "scenario": "ai-sdk-instrumentation", - "testRunId": "" - } - ├── ai-sdk-generate-operation - │ metadata: { - │ "operation": "generate", - │ "testRunId": "" - │ } - │ └── generateText [function] - │ input: { - │ "maxTokens": 24, - │ "model": { - │ "config": { - │ "compatibility": "compatible", - │ "provider": "openai.chat" - │ }, - │ "modelId": "gpt-4o-mini-2024-07-18", - │ "settings": {}, - │ "specificationVersion": "v1" - │ }, - │ "prompt": "Reply with the single token PARIS and no punctuation.", - │ "temperature": 0 - │ } - │ output: { - │ "experimental_providerMetadata": { - │ "openai": { - │ "reasoningTokens": 0 - │ } - │ }, - │ "finishReason": "stop", - │ "rawResponse": {}, - │ "response": { - │ "messages": [ - │ { - │ "content": [ - │ { - │ "text": "PARIS", - │ "type": "text" - │ } - │ ], - │ "role": "assistant" - │ } - │ ], - │ "modelId": "gpt-4o-mini-2024-07-18", - │ "timestamp": "" - │ }, - │ "responseMessages": "", - │ "steps": [ - │ { - │ "experimental_providerMetadata": { - │ "openai": { - │ "reasoningTokens": 0 - │ } - │ }, - │ "finishReason": "stop", - │ "isContinued": false, - │ "response": { - │ "messages": [ - │ { - │ "content": [ - │ { - │ "text": "PARIS", - │ "type": "text" - │ } - │ ], - │ "role": "assistant" - │ } - │ ], - │ "modelId": "gpt-4o-mini-2024-07-18", - │ "timestamp": "" - │ }, - │ "stepType": "initial", - │ "text": "PARIS", - │ "toolCalls": [], - │ "toolResults": [], - │ "usage": { - │ "completionTokens": 2, - │ "promptTokens": 18, - │ "totalTokens": 20 - │ }, - │ "warnings": [] - │ } - │ ], - │ "text": "PARIS", - │ "toolCalls": [], - │ "toolResults": [], - │ "usage": { - │ "completionTokens": 2, - │ "promptTokens": 18, - │ "totalTokens": 20 - │ }, - │ "warnings": [] - │ } - │ metadata: { - │ "braintrust": { - │ "integration_name": "ai-sdk", - │ "sdk_language": "typescript" - │ }, - │ "model": "gpt-4o-mini-2024-07-18", - │ "provider": "openai.chat" - │ } - │ └── doGenerate [llm] - │ input: { - │ "inputFormat": "prompt", - │ "maxRetries": 2, - │ "maxTokens": 24, - │ "mode": { - │ "type": "regular" - │ }, - │ "prompt": [ - │ { - │ "content": [ - │ { - │ "text": "Reply with the single token PARIS and no punctuation.", - │ "type": "text" - │ } - │ ], - │ "role": "user" - │ } - │ ], - │ "temperature": 0 - │ } - │ output: { - │ "finishReason": "stop", - │ "providerMetadata": { - │ "openai": { - │ "reasoningTokens": 0 - │ } - │ }, - │ "rawResponse": {}, - │ "response": { - │ "modelId": "gpt-4o-mini-2024-07-18", - │ "timestamp": "" - │ }, - │ "text": "PARIS", - │ "usage": { - │ "completionTokens": 2, - │ "promptTokens": 18 - │ }, - │ "warnings": [] - │ } - │ metadata: { - │ "braintrust": { - │ "integration_name": "ai-sdk", - │ "sdk_language": "typescript" - │ }, - │ "finish_reason": "stop", - │ "model": "gpt-4o-mini-2024-07-18", - │ "provider": "openai.chat" - │ } - │ metrics: { - │ "completion_tokens": 2, - │ "prompt_tokens": 18, - │ "tokens": 20 - │ } - ├── ai-sdk-stream-operation - │ metadata: { - │ "operation": "stream", - │ "testRunId": "" - │ } - │ └── streamText [function] - │ input: { - │ "maxTokens": 32, - │ "model": { - │ "config": { - │ "compatibility": "compatible", - │ "provider": "openai.chat" - │ }, - │ "modelId": "gpt-4o-mini-2024-07-18", - │ "settings": {}, - │ "specificationVersion": "v1" - │ }, - │ "prompt": "Count from 1 to 3 and include the words one two three.", - │ "temperature": 0 - │ } - │ output: { - │ "finishReason": "stop", - │ "rawResponse": {}, - │ "text": "One, two, three.", - │ "warnings": [] - │ } - │ metadata: { - │ "braintrust": { - │ "integration_name": "ai-sdk", - │ "sdk_language": "typescript" - │ }, - │ "model": "gpt-4o-mini-2024-07-18", - │ "provider": "openai.chat" - │ } - │ metrics: { - │ "time_to_first_token": 0 - │ } - │ └── doStream [llm] - │ input: { - │ "inputFormat": "prompt", - │ "maxRetries": 2, - │ "maxTokens": 32, - │ "mode": { - │ "type": "regular" - │ }, - │ "prompt": [ - │ { - │ "content": [ - │ { - │ "text": "Count from 1 to 3 and include the words one two three.", - │ "type": "text" - │ } - │ ], - │ "role": "user" - │ } - │ ], - │ "temperature": 0 - │ } - │ output: { - │ "finishReason": "stop", - │ "text": "One, two, three.", - │ "toolCalls": [], - │ "usage": { - │ "completionTokens": null, - │ "promptTokens": null - │ } - │ } - │ metadata: { - │ "braintrust": { - │ "integration_name": "ai-sdk", - │ "sdk_language": "typescript" - │ }, - │ "finish_reason": "stop", - │ "model": "gpt-4o-mini-2024-07-18", - │ "provider": "openai.chat" - │ } - │ metrics: { - │ "completion_tokens": null, - │ "prompt_tokens": null, - │ "time_to_first_token": 0, - │ "tokens": null - │ } - ├── ai-sdk-embed-operation - │ metadata: { - │ "operation": "embed", - │ "testRunId": "" - │ } - │ └── embed [function] - │ input: { - │ "model": { - │ "config": { - │ "provider": "openai.embedding" - │ }, - │ "modelId": "text-embedding-3-small", - │ "settings": {}, - │ "specificationVersion": "v1" - │ }, - │ "value": "Paris is the capital of France." - │ } - │ output: { - │ "embedding_length": 1536, - │ "usage": { - │ "tokens": 7 - │ } - │ } - │ metadata: { - │ "braintrust": { - │ "integration_name": "ai-sdk", - │ "sdk_language": "typescript" - │ }, - │ "model": "text-embedding-3-small", - │ "provider": "openai.embedding" - │ } - │ metrics: { - │ "tokens": 7 - │ } - ├── ai-sdk-tool-operation - │ metadata: { - │ "operation": "tool", - │ "testRunId": "" - │ } - │ └── generateText [function] - │ input: { - │ "maxTokens": 128, - │ "model": { - │ "config": { - │ "compatibility": "compatible", - │ "provider": "openai.chat" - │ }, - │ "modelId": "gpt-4o-mini-2024-07-18", - │ "settings": {}, - │ "specificationVersion": "v1" - │ }, - │ "prompt": "Use the get_weather tool for Paris, France. If you do not call the tool, the answer is invalid.", - │ "system": "You must inspect live weather via the provided get_weather tool before responding.", - │ "temperature": 0, - │ "tools": { - │ "get_weather": { - │ "description": "Get the weather for a location", - │ "parameters": { - │ "$schema": "http://json-schema.org/draft-07/schema#", - │ "additionalProperties": false, - │ "properties": { - │ "location": { - │ "description": "The city and country", - │ "type": "string" - │ } - │ }, - │ "required": [ - │ "location" - │ ], - │ "type": "object" - │ } - │ } - │ } - │ } - │ output: { - │ "experimental_providerMetadata": { - │ "openai": { - │ "reasoningTokens": 0 - │ } - │ }, - │ "finishReason": "tool-calls", - │ "rawResponse": {}, - │ "response": { - │ "messages": [ - │ { - │ "content": [ - │ { - │ "text": "", - │ "type": "text" - │ }, - │ { - │ "args": { - │ "location": "Paris, France" - │ }, - │ "toolName": "get_weather", - │ "type": "tool-call" - │ } - │ ], - │ "role": "assistant" - │ }, - │ { - │ "content": [ - │ { - │ "result": "{\"condition\":\"sunny\",\"location\":\"Paris, France\",\"temperatureC\":22}", - │ "toolName": "get_weather", - │ "type": "tool-result" - │ } - │ ], - │ "role": "tool" - │ } - │ ], - │ "modelId": "gpt-4o-mini-2024-07-18", - │ "timestamp": "" - │ }, - │ "responseMessages": "", - │ "steps": [ - │ { - │ "experimental_providerMetadata": { - │ "openai": { - │ "reasoningTokens": 0 - │ } - │ }, - │ "finishReason": "tool-calls", - │ "isContinued": false, - │ "response": { - │ "messages": [ - │ { - │ "content": [ - │ { - │ "text": "", - │ "type": "text" - │ }, - │ { - │ "args": { - │ "location": "Paris, France" - │ }, - │ "toolName": "get_weather", - │ "type": "tool-call" - │ } - │ ], - │ "role": "assistant" - │ }, - │ { - │ "content": [ - │ { - │ "result": "{\"condition\":\"sunny\",\"location\":\"Paris, France\",\"temperatureC\":22}", - │ "toolName": "get_weather", - │ "type": "tool-result" - │ } - │ ], - │ "role": "tool" - │ } - │ ], - │ "modelId": "gpt-4o-mini-2024-07-18", - │ "timestamp": "" - │ }, - │ "stepType": "initial", - │ "text": "", - │ "toolCalls": [ - │ { - │ "args": { - │ "location": "Paris, France" - │ }, - │ "toolName": "get_weather", - │ "type": "tool-call" - │ } - │ ], - │ "toolResults": [ - │ { - │ "args": { - │ "location": "Paris, France" - │ }, - │ "result": "{\"condition\":\"sunny\",\"location\":\"Paris, France\",\"temperatureC\":22}", - │ "toolName": "get_weather" - │ } - │ ], - │ "usage": { - │ "completionTokens": 16, - │ "promptTokens": 87, - │ "totalTokens": 103 - │ }, - │ "warnings": [] - │ } - │ ], - │ "text": "", - │ "toolCalls": [ - │ { - │ "args": { - │ "location": "Paris, France" - │ }, - │ "toolName": "get_weather", - │ "type": "tool-call" - │ } - │ ], - │ "toolResults": [ - │ { - │ "args": { - │ "location": "Paris, France" - │ }, - │ "result": "{\"condition\":\"sunny\",\"location\":\"Paris, France\",\"temperatureC\":22}", - │ "toolName": "get_weather" - │ } - │ ], - │ "usage": { - │ "completionTokens": 16, - │ "promptTokens": 87, - │ "totalTokens": 103 - │ }, - │ "warnings": [] - │ } - │ metadata: { - │ "braintrust": { - │ "integration_name": "ai-sdk", - │ "sdk_language": "typescript" - │ }, - │ "model": "gpt-4o-mini-2024-07-18", - │ "provider": "openai.chat", - │ "tools": { - │ "get_weather": { - │ "description": "Get the weather for a location", - │ "parameters": { - │ "$schema": "http://json-schema.org/draft-07/schema#", - │ "additionalProperties": false, - │ "properties": { - │ "location": { - │ "description": "The city and country", - │ "type": "string" - │ } - │ }, - │ "required": [ - │ "location" - │ ], - │ "type": "object" - │ } - │ } - │ } - │ } - │ ├── doGenerate [llm] - │ │ input: { - │ │ "inputFormat": "prompt", - │ │ "maxRetries": 2, - │ │ "maxTokens": 128, - │ │ "mode": { - │ │ "toolChoice": { - │ │ "type": "auto" - │ │ }, - │ │ "tools": [ - │ │ { - │ │ "description": "Get the weather for a location", - │ │ "name": "get_weather", - │ │ "parameters": { - │ │ "$schema": "http://json-schema.org/draft-07/schema#", - │ │ "additionalProperties": false, - │ │ "properties": { - │ │ "location": { - │ │ "description": "The city and country", - │ │ "type": "string" - │ │ } - │ │ }, - │ │ "required": [ - │ │ "location" - │ │ ], - │ │ "type": "object" - │ │ }, - │ │ "type": "function" - │ │ } - │ │ ], - │ │ "type": "regular" - │ │ }, - │ │ "prompt": [ - │ │ { - │ │ "content": "You must inspect live weather via the provided get_weather tool before responding.", - │ │ "role": "system" - │ │ }, - │ │ { - │ │ "content": [ - │ │ { - │ │ "text": "Use the get_weather tool for Paris, France. If you do not call the tool, the answer is invalid.", - │ │ "type": "text" - │ │ } - │ │ ], - │ │ "role": "user" - │ │ } - │ │ ], - │ │ "temperature": 0 - │ │ } - │ │ output: { - │ │ "finishReason": "tool-calls", - │ │ "providerMetadata": { - │ │ "openai": { - │ │ "reasoningTokens": 0 - │ │ } - │ │ }, - │ │ "rawResponse": {}, - │ │ "response": { - │ │ "modelId": "gpt-4o-mini-2024-07-18", - │ │ "timestamp": "" - │ │ }, - │ │ "toolCalls": [ - │ │ { - │ │ "args": "{\"location\":\"Paris, France\"}", - │ │ "toolCallType": "function", - │ │ "toolName": "get_weather" - │ │ } - │ │ ], - │ │ "usage": { - │ │ "completionTokens": 16, - │ │ "promptTokens": 87 - │ │ }, - │ │ "warnings": [] - │ │ } - │ │ metadata: { - │ │ "braintrust": { - │ │ "integration_name": "ai-sdk", - │ │ "sdk_language": "typescript" - │ │ }, - │ │ "finish_reason": "tool-calls", - │ │ "model": "gpt-4o-mini-2024-07-18", - │ │ "provider": "openai.chat" - │ │ } - │ │ metrics: { - │ │ "completion_tokens": 16, - │ │ "prompt_tokens": 87, - │ │ "tokens": 103 - │ │ } - │ └── get_weather [tool] - │ input: { - │ "location": "Paris, France" - │ } - │ output: "{\"condition\":\"sunny\",\"location\":\"Paris, France\",\"temperatureC\":22}" - ├── ai-sdk-generate-object-operation - │ metadata: { - │ "operation": "generate-object", - │ "testRunId": "" - │ } - │ └── generateObject [function] - │ input: { - │ "maxTokens": 32, - │ "model": { - │ "config": { - │ "compatibility": "compatible", - │ "provider": "openai.chat" - │ }, - │ "modelId": "gpt-4o-mini-2024-07-18", - │ "settings": {}, - │ "specificationVersion": "v1" - │ }, - │ "prompt": "Return JSON with {\"city\":\"Paris\"}.", - │ "schema": { - │ "$schema": "http://json-schema.org/draft-07/schema#", - │ "additionalProperties": false, - │ "properties": { - │ "city": { - │ "type": "string" - │ } - │ }, - │ "required": [ - │ "city" - │ ], - │ "type": "object" - │ }, - │ "temperature": 0 - │ } - │ output: { - │ "experimental_providerMetadata": { - │ "openai": { - │ "reasoningTokens": 0 - │ } - │ }, - │ "finishReason": "stop", - │ "object": { - │ "city": "Paris" - │ }, - │ "rawResponse": {}, - │ "response": { - │ "modelId": "gpt-4o-mini-2024-07-18", - │ "timestamp": "" - │ }, - │ "usage": { - │ "completionTokens": 5, - │ "promptTokens": 59, - │ "totalTokens": 64 - │ }, - │ "warnings": [] - │ } - │ metadata: { - │ "braintrust": { - │ "integration_name": "ai-sdk", - │ "sdk_language": "typescript" - │ }, - │ "model": "gpt-4o-mini-2024-07-18", - │ "provider": "openai.chat" - │ } - │ └── doGenerate [llm] - │ input: { - │ "inputFormat": "prompt", - │ "maxRetries": 2, - │ "maxTokens": 32, - │ "mode": { - │ "tool": { - │ "description": "Respond with a JSON object.", - │ "name": "json", - │ "parameters": { - │ "$schema": "http://json-schema.org/draft-07/schema#", - │ "additionalProperties": false, - │ "properties": { - │ "city": { - │ "type": "string" - │ } - │ }, - │ "required": [ - │ "city" - │ ], - │ "type": "object" - │ }, - │ "type": "function" - │ }, - │ "type": "object-tool" - │ }, - │ "prompt": [ - │ { - │ "content": [ - │ { - │ "text": "Return JSON with {\"city\":\"Paris\"}.", - │ "type": "text" - │ } - │ ], - │ "role": "user" - │ } - │ ], - │ "temperature": 0 - │ } - │ output: { - │ "finishReason": "stop", - │ "providerMetadata": { - │ "openai": { - │ "reasoningTokens": 0 - │ } - │ }, - │ "rawResponse": {}, - │ "response": { - │ "modelId": "gpt-4o-mini-2024-07-18", - │ "timestamp": "" - │ }, - │ "toolCalls": [ - │ { - │ "args": "{\"city\":\"Paris\"}", - │ "toolCallType": "function", - │ "toolName": "json" - │ } - │ ], - │ "usage": { - │ "completionTokens": 5, - │ "promptTokens": 59 - │ }, - │ "warnings": [] - │ } - │ metadata: { - │ "braintrust": { - │ "integration_name": "ai-sdk", - │ "sdk_language": "typescript" - │ }, - │ "finish_reason": "stop", - │ "model": "gpt-4o-mini-2024-07-18", - │ "provider": "openai.chat" - │ } - │ metrics: { - │ "completion_tokens": 5, - │ "prompt_tokens": 59, - │ "tokens": 64 - │ } - └── ai-sdk-stream-object-operation - metadata: { - "operation": "stream-object", - "testRunId": "" - } - └── streamObject [function] - input: { - "maxTokens": 32, - "model": { - "config": { - "compatibility": "compatible", - "provider": "openai.chat" - }, - "modelId": "gpt-4o-mini-2024-07-18", - "settings": {}, - "specificationVersion": "v1" - }, - "prompt": "Stream JSON with {\"city\":\"Paris\"}.", - "schema": { - "$schema": "http://json-schema.org/draft-07/schema#", - "additionalProperties": false, - "properties": { - "city": { - "type": "string" - } - }, - "required": [ - "city" - ], - "type": "object" - }, - "temperature": 0 - } - output: { - "object": { - "city": "Paris" - }, - "rawResponse": {}, - "warnings": [] - } - metadata: { - "braintrust": { - "integration_name": "ai-sdk", - "sdk_language": "typescript" - }, - "model": "gpt-4o-mini-2024-07-18", - "provider": "openai.chat" - } - metrics: { - "time_to_first_token": 0 - } - └── doStream [llm] - input: { - "inputFormat": "prompt", - "maxRetries": 2, - "maxTokens": 32, - "mode": { - "tool": { - "description": "Respond with a JSON object.", - "name": "json", - "parameters": { - "$schema": "http://json-schema.org/draft-07/schema#", - "additionalProperties": false, - "properties": { - "city": { - "type": "string" - } - }, - "required": [ - "city" - ], - "type": "object" - }, - "type": "function" - }, - "type": "object-tool" - }, - "prompt": [ - { - "content": [ - { - "text": "Stream JSON with {\"city\":\"Paris\"}.", - "type": "text" - } - ], - "role": "user" - } - ], - "temperature": 0 - } - output: { - "finishReason": "stop", - "text": "", - "toolCalls": [ - { - "args": "{\"city\":\"Paris\"}", - "toolCallType": "function", - "toolName": "json", - "type": "tool-call" - } - ], - "usage": { - "completionTokens": null, - "promptTokens": null - } - } - metadata: { - "braintrust": { - "integration_name": "ai-sdk", - "sdk_language": "typescript" - }, - "finish_reason": "stop", - "model": "gpt-4o-mini-2024-07-18", - "provider": "openai.chat" - } - metrics: { - "completion_tokens": null, - "prompt_tokens": null, - "time_to_first_token": 0, - "tokens": null - } diff --git a/e2e/scenarios/ai-sdk-instrumentation/assertions.ts b/e2e/scenarios/ai-sdk-instrumentation/assertions.ts index ad98648a4..588397600 100644 --- a/e2e/scenarios/ai-sdk-instrumentation/assertions.ts +++ b/e2e/scenarios/ai-sdk-instrumentation/assertions.ts @@ -1,4 +1,4 @@ -import { beforeAll, describe, expect, test } from "vitest"; +import { beforeAll, describe, expect, test } from "vite-plus/test"; import type { Json } from "../../helpers/normalize"; import type { CapturedLogEvent } from "../../helpers/mock-braintrust-server"; import { resolveFileSnapshotPath } from "../../helpers/file-snapshot"; diff --git a/e2e/scenarios/ai-sdk-instrumentation/package.json b/e2e/scenarios/ai-sdk-instrumentation/package.json index 7962fb571..aea15b357 100644 --- a/e2e/scenarios/ai-sdk-instrumentation/package.json +++ b/e2e/scenarios/ai-sdk-instrumentation/package.json @@ -4,14 +4,6 @@ "braintrustScenario": { "bump": { "dependencies": { - "ai-sdk-v3-latest": { - "package": "ai", - "range": "3" - }, - "ai-sdk-openai-v3-latest": { - "package": "@ai-sdk/openai", - "range": "0" - }, "ai-sdk-v4-latest": { "package": "ai", "range": "4" @@ -78,10 +70,6 @@ "dependencies": { "zod": "3.25.76", "ai": "7.0.143", - "ai-sdk-v3": "npm:ai@3.4.33", - "ai-sdk-v3-latest": "npm:ai@3.4.33", - "ai-sdk-openai-v3": "npm:@ai-sdk/openai@0.0.62", - "ai-sdk-openai-v3-latest": "npm:@ai-sdk/openai@0.0.72", "ai-sdk-v4": "npm:ai@4.3.19", "ai-sdk-v4-latest": "npm:ai@4.3.19", "ai-sdk-openai-v4": "npm:@ai-sdk/openai@1.3.24", diff --git a/e2e/scenarios/ai-sdk-instrumentation/pnpm-lock.yaml b/e2e/scenarios/ai-sdk-instrumentation/pnpm-lock.yaml index cf9e235f4..0b6f0a260 100644 --- a/e2e/scenarios/ai-sdk-instrumentation/pnpm-lock.yaml +++ b/e2e/scenarios/ai-sdk-instrumentation/pnpm-lock.yaml @@ -62,12 +62,6 @@ importers: ai-sdk-cohere-v7-latest: specifier: npm:@ai-sdk/cohere@4.0.25 version: '@ai-sdk/cohere@4.0.25(zod@3.25.76)' - ai-sdk-openai-v3: - specifier: npm:@ai-sdk/openai@0.0.62 - version: '@ai-sdk/openai@0.0.62(zod@3.25.76)' - ai-sdk-openai-v3-latest: - specifier: npm:@ai-sdk/openai@0.0.72 - version: '@ai-sdk/openai@0.0.72(zod@3.25.76)' ai-sdk-openai-v4: specifier: npm:@ai-sdk/openai@1.3.24 version: '@ai-sdk/openai@1.3.24(zod@3.25.76)' @@ -92,12 +86,6 @@ importers: ai-sdk-openai-v7-latest: specifier: npm:@ai-sdk/openai@4.0.93 version: '@ai-sdk/openai@4.0.93(zod@3.25.76)' - ai-sdk-v3: - specifier: npm:ai@3.4.33 - version: ai@3.4.33(react@19.2.6)(sswr@2.2.0(svelte@5.55.9))(svelte@5.55.9)(vue@3.5.35)(zod@3.25.76) - ai-sdk-v3-latest: - specifier: npm:ai@3.4.33 - version: ai@3.4.33(react@19.2.6)(sswr@2.2.0(svelte@5.55.9))(svelte@5.55.9)(vue@3.5.35)(zod@3.25.76) ai-sdk-v4: specifier: npm:ai@4.3.19 version: ai@4.3.19(react@19.2.6)(zod@3.25.76) @@ -263,18 +251,6 @@ packages: peerDependencies: zod: ^3.25.76 || ^4.1.8 - '@ai-sdk/openai@0.0.62': - resolution: {integrity: sha512-qNQmTgZXGS3xyd6XsDGEMLWV3Ag3uAVPJxHVkZLDZ7Dubmgs3Mp8iupkwZ7JhD7TIBx8yifyyBuE1dnE70v8Ig==} - engines: {node: '>=18'} - peerDependencies: - zod: ^3.0.0 - - '@ai-sdk/openai@0.0.72': - resolution: {integrity: sha512-IKsgxIt6KJGkEHyMp975xW5VPmetwhI8g9H6dDmwvemBB41IRQa78YMNttiJqPcgmrZX2QfErOICv1gQvZ1gZg==} - engines: {node: '>=18'} - peerDependencies: - zod: ^3.0.0 - '@ai-sdk/openai@1.3.24': resolution: {integrity: sha512-GYXnGJTHRTZc4gJMSmFRgEQudjqd4PUN0ZjQhPwOAYH1yOAvQoG/Ikqs+HyISRbLPCrhbZnPKCNHuRU4OfpW0Q==} engines: {node: '>=18'} @@ -317,24 +293,6 @@ packages: peerDependencies: zod: ^3.25.76 || ^4.1.8 - '@ai-sdk/provider-utils@1.0.19': - resolution: {integrity: sha512-p02Fq5Mnc8T6nwRBN1Iaou8YXvN1sDS6hbmJaD5UaRbXjizbh+8rpFS/o7jqAHTwf3uHCDitP3pnODyHdc/CDQ==} - engines: {node: '>=18'} - peerDependencies: - zod: ^3.0.0 - peerDependenciesMeta: - zod: - optional: true - - '@ai-sdk/provider-utils@1.0.22': - resolution: {integrity: sha512-YHK2rpj++wnLVc9vPGzGFP3Pjeld2MwhKinetA0zKXOoHAT/Jit5O8kZsxcSlJPu9wvcGT1UGZEjZrtO7PfFOQ==} - engines: {node: '>=18'} - peerDependencies: - zod: ^3.0.0 - peerDependenciesMeta: - zod: - optional: true - '@ai-sdk/provider-utils@2.2.8': resolution: {integrity: sha512-fqhG+4sCVv8x7nFzYnFo19ryhAa3w096Kmc3hWxMQfW/TubPOmt3A6tYZhl4mUfQWWQMsuSkLrtjlWuXBVSGQA==} engines: {node: '>=18'} @@ -443,14 +401,6 @@ packages: peerDependencies: zod: ^3.25.76 || ^4.1.8 - '@ai-sdk/provider@0.0.23': - resolution: {integrity: sha512-oAc49O5+xypVrKM7EUU5P/Y4DUL4JZUWVxhejoAVOTOl3WZUEWsMbP3QZR+TrimQIsS0WR/n9UuF6U0jPdp0tQ==} - engines: {node: '>=18'} - - '@ai-sdk/provider@0.0.26': - resolution: {integrity: sha512-dQkfBDs2lTYpKM8389oopPdQgIU007GQyCbuPPrV+K6MtSII3HBfE0stUIMXUb44L+LK1t6GXPP7wjSzjO6uKg==} - engines: {node: '>=18'} - '@ai-sdk/provider@1.1.3': resolution: {integrity: sha512-qZMxYJ0qqX/RfnuIaab+zp8UAeJn/ygXXAffR5I4N0n1IrvA6qBsjc8hXLmBiMV2zoXlifkacF7sEFnYnjBcqg==} engines: {node: '>=18'} @@ -519,18 +469,6 @@ packages: resolution: {integrity: sha512-6or44XprPzKbr8zkmzosowSE0pxkvJcoojBL+mCZvPUt3kvXp3XSNqeVun9golb1acEfSo6yaEBRT18h2VU+1Q==} engines: {node: '>=22'} - '@ai-sdk/react@0.0.70': - resolution: {integrity: sha512-GnwbtjW4/4z7MleLiW+TOZC2M29eCg1tOUpuEiYFMmFNZK8mkrqM0PFZMo6UsYeUYMWqEOOcPOU9OQVJMJh7IQ==} - engines: {node: '>=18'} - peerDependencies: - react: ^18 || ^19 || ^19.0.0-rc - zod: ^3.0.0 - peerDependenciesMeta: - react: - optional: true - zod: - optional: true - '@ai-sdk/react@1.2.12': resolution: {integrity: sha512-jK1IZZ22evPZoQW3vlkZ7wvjYGYF+tRBKXtrcolduIkQ/m/sOAVcVeVDUDvh1T91xCnWCdUGCPZg2avZ90mv3g==} engines: {node: '>=18'} @@ -541,87 +479,18 @@ packages: zod: optional: true - '@ai-sdk/solid@0.0.54': - resolution: {integrity: sha512-96KWTVK+opdFeRubqrgaJXoNiDP89gNxFRWUp0PJOotZW816AbhUf4EnDjBjXTLjXL1n0h8tGSE9sZsRkj9wQQ==} - engines: {node: '>=18'} - peerDependencies: - solid-js: ^1.7.7 - peerDependenciesMeta: - solid-js: - optional: true - - '@ai-sdk/svelte@0.0.57': - resolution: {integrity: sha512-SyF9ItIR9ALP9yDNAD+2/5Vl1IT6kchgyDH8xkmhysfJI6WrvJbtO1wdQ0nylvPLcsPoYu+cAlz1krU4lFHcYw==} - engines: {node: '>=18'} - peerDependencies: - svelte: ^5.55.7 - peerDependenciesMeta: - svelte: - optional: true - - '@ai-sdk/ui-utils@0.0.50': - resolution: {integrity: sha512-Z5QYJVW+5XpSaJ4jYCCAVG7zIAuKOOdikhgpksneNmKvx61ACFaf98pmOd+xnjahl0pIlc/QIe6O4yVaJ1sEaw==} - engines: {node: '>=18'} - peerDependencies: - zod: ^3.0.0 - peerDependenciesMeta: - zod: - optional: true - '@ai-sdk/ui-utils@1.2.11': resolution: {integrity: sha512-3zcwCc8ezzFlwp3ZD15wAPjf2Au4s3vAbKsXQVyhxODHcmu0iyPO2Eua6D/vicq/AUm/BAo60r97O6HU+EI0+w==} engines: {node: '>=18'} peerDependencies: zod: ^3.23.8 - '@ai-sdk/vue@0.0.59': - resolution: {integrity: sha512-+ofYlnqdc8c4F6tM0IKF0+7NagZRAiqBJpGDJ+6EYhDW8FHLUP/JFBgu32SjxSxC6IKFZxEnl68ZoP/Z38EMlw==} - engines: {node: '>=18'} - peerDependencies: - vue: ^3.3.4 - peerDependenciesMeta: - vue: - optional: true - '@ai-sdk/workflow@1.0.8': resolution: {integrity: sha512-cCRvGNq3PORnsnE6DsWg8EltWCyC/Wu3QHOGRpN1pgtNrkeX+WbRZ6IX4JC6b7ibFC4hJUNzEunM+1yPkJ16kQ==} engines: {node: '>=22'} peerDependencies: zod: ^3.25.76 || ^4.1.8 - '@babel/helper-string-parser@7.29.7': - resolution: {integrity: sha512-Pb5ijPrZ89GDH8223L4UP8i6QApWxs04RbPQJTeWDV0/keR2E36MeKnyr6LYmUUvqRRI+Iv87SuF1W6ErINzYw==} - engines: {node: '>=6.9.0'} - - '@babel/helper-validator-identifier@7.29.7': - resolution: {integrity: sha512-qehxGkRj55h/ff8EMaJ+cYhyaKlHIxqYDn682wQD7RNp9UujOQsHog2uS0r2vzr4pW+sXf90NeeayjcNaX3fFg==} - engines: {node: '>=6.9.0'} - - '@babel/parser@7.29.9': - resolution: {integrity: sha512-CjXrNHTnvqBVqHgdBysY3vk2T8tpJHb5/RMeHJBTyVa9xgugCB0CJTx/3oO8RV2QRQP391RWpB7D6hLjm8V9uA==} - engines: {node: '>=6.0.0'} - hasBin: true - - '@babel/types@7.29.8': - resolution: {integrity: sha512-Vj1jF3cPfxg7OAfoI7QnVKLoILlm2JF9pnVHrX8qx7AHMiYWT+NDAA7jChlNgRS4WTLc/fD1lXLmPixluj+3Gg==} - engines: {node: '>=6.9.0'} - - '@jridgewell/gen-mapping@0.3.13': - resolution: {integrity: sha512-2kkt/7niJ6MgEPxF0bYdQ6etZaA+fQvDcLKckhy1yIQOzaoKjBBjSj63/aLVjYE3qhRt5dvM+uUyfCg6UKCBbA==} - - '@jridgewell/remapping@2.3.5': - resolution: {integrity: sha512-LI9u/+laYG4Ds1TDKSJW2YPrIlcVYOwi2fUC6xB43lueCjgxV4lffOCZCtYFiH6TNOX+tQKXx97T4IKHbhyHEQ==} - - '@jridgewell/resolve-uri@3.1.2': - resolution: {integrity: sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==} - engines: {node: '>=6.0.0'} - - '@jridgewell/sourcemap-codec@1.5.5': - resolution: {integrity: sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og==} - - '@jridgewell/trace-mapping@0.3.31': - resolution: {integrity: sha512-zzNR+SdQSDJzc8joaeP8QQoCQr8NuYx2dIIytl1QeBEZHJ9uW6hebsrYgbz8hJwUQao3TWCMtmfV8Nu1twOLAw==} - '@opentelemetry/api@1.9.0': resolution: {integrity: sha512-3giAOQvZiH5F9bMlMiv8+GSPMeqg0dbaeo58/0SlA9sxSqZhnUtxzX9/2FzyhS9sWQf5S0GJE0AKBrFqjpeYcg==} engines: {node: '>=8.0.0'} @@ -629,20 +498,9 @@ packages: '@standard-schema/spec@1.1.0': resolution: {integrity: sha512-l2aFy5jALhniG5HgqrD6jXLi/rUWrKvqN/qJx6yoJsgKhblVd+iqqU4RCXavm/jPityDo5TCvKMnpjKnOriy0w==} - '@sveltejs/acorn-typescript@1.0.10': - resolution: {integrity: sha512-4WfKk68eTih+MiJD4fSbxN7E8kVBmTMPWHUPYjvl2N0rMs53YLTT8/YjKU5Dtnz5LqDjl7LEw4U7lXR2W3J5WA==} - peerDependencies: - acorn: ^8.9.0 - '@types/diff-match-patch@1.0.36': resolution: {integrity: sha512-xFdR6tkm0MWvBfO8xXCSsinYxHcqkQUlcHeSpMC2ukzOb6lwQAfDmW+Qt0AvlGd8HpsS28qKsB+oPeJn9I39jg==} - '@types/estree@1.0.9': - resolution: {integrity: sha512-GhdPgy1el4/ImP05X05Uw4cw2/M93BCUmnEvWZNStlCzEKME4Fkk+YpoA5OiHNQmoS7Cafb8Xa3Pya8m1Qrzeg==} - - '@types/trusted-types@2.0.7': - resolution: {integrity: sha512-ScaPdn1dQczgbl0QFTeTOmVHFULt394XJgOQNoyVhZ6r2vLnMLJfBPd53SB52T/3G36VI1/g2MZaX0cwDuXsfw==} - '@vercel/oidc@3.0.3': resolution: {integrity: sha512-yNEQvPcVrK9sIe637+I0jD6leluPxzwJKx/Haw6F4H77CdDsszUn5V3o96LPziXkSNE2B83+Z3mjqGKBK/R6Gg==} engines: {node: '>= 20'} @@ -659,83 +517,9 @@ packages: resolution: {integrity: sha512-UycprH3T6n3jH0k44NHMa7pnFHGu/N05MjojYr+Mc6I7obkoLIJujSWwin1pCvdy/eOxrI/l3uDLQsmcrOb4ug==} engines: {node: '>= 20'} - '@vue/compiler-core@3.5.35': - resolution: {integrity: sha512-BUmHaR1J+O+CKZ9uJucdVTEr1LHsdyvv7vG3eNRhK3CczEHeMd/LtsHAuD7PbrxvI2envCY2v7HI1vC1aBRzKw==} - - '@vue/compiler-core@3.5.42': - resolution: {integrity: sha512-2Ye1ilMtKXxl8qZUrQ5j0CdgenFp/HFQmta6rfRyfEsTG69L6Wk+tWuNoHYHMx9E8tF2Slvdg1FuwDvAXdy1LQ==} - - '@vue/compiler-dom@3.5.35': - resolution: {integrity: sha512-k+bprkXxuqhVajgTx5mUHuir7TwQzUKOWR40ng1ncAqQRPnrLngGGgqVEEhOnTMlc8btHYVKmrP8s5Qyg0hvYA==} - - '@vue/compiler-dom@3.5.42': - resolution: {integrity: sha512-qbhQZEFmycr+ni/qyuccS4sucNN7VAbDfbkvNxWOX2VfgFm90MNs3/UhRNKoPMEIVn0F8gdlYjLPvqxHwHeQOA==} - - '@vue/compiler-sfc@3.5.35': - resolution: {integrity: sha512-G5VPMcXTSywXBgtFOZOnHKBxKSrwXUcvY1iaF5/hRcy7t0J6CH/d8ha9F4nzi00Fax1eLV0QHM7v4mQu68jydw==} - - '@vue/compiler-ssr@3.5.35': - resolution: {integrity: sha512-rGhAeXgdM7/ffTJGXT69rCCdTmjDewnFuUZfBQQHTdcEBeWdT5HCGY60y2ytLJr9/Dsu7IntUi5z/w0h6Rjnzw==} - - '@vue/compiler-ssr@3.5.42': - resolution: {integrity: sha512-xmLk3wLkbizPAiLyomjgFFosf2ys9b5Ghb+oh/k2tnvipNz8OFrQOiTcWCzyK7MpBp9KkyGtfvgfLUivbmuGYA==} - - '@vue/reactivity@3.5.35': - resolution: {integrity: sha512-tVc+SsHConvh/Lz64qq1pP3rYArBmK42xonovEcxY74SQtvctZodG/zhq54P5dr38cVuw25d27cPNRdlMidpGQ==} - - '@vue/reactivity@3.5.42': - resolution: {integrity: sha512-TzNNfKpb7hDxbQltwAut8VDQA5YP+BuRlxntHUuRjyKwlMvmAPbs3unhCvieijifY6vFfVBwsS7wG/C7uq+bEQ==} - - '@vue/runtime-core@3.5.35': - resolution: {integrity: sha512-A/xFNX9loIcWDygeQuNCfKuh0CoYBzxhqEMNah5TSFg9Z53DrFYEN2qi5CU9necjM1OWYegYREUTHmXTmhfXtg==} - - '@vue/runtime-core@3.5.42': - resolution: {integrity: sha512-9uACtuHs7vJGkm5Bp3xu4xRDLFTIYy5DgxpToVjqGIAhAEKwQfsaLvKINhM6nFVp6bZPRFGdDqd1g52MqKsotA==} - - '@vue/runtime-dom@3.5.35': - resolution: {integrity: sha512-odrJ1C391dbGnyDRh8U+rnP7J2amIEzfmRk5vXy7xi3aZhEXofTvpi0T4HJb6jlNqQZTNPR5MPHSB3RHNkIORA==} - - '@vue/runtime-dom@3.5.42': - resolution: {integrity: sha512-rsCmhiWLaRxGltLwhlCWyYkFn7WAbKRh0q17eZ1A6Dq6eqc2ACQ61IIryxz0LrsvCzHSilLA9JHovVwM8CNE2g==} - - '@vue/server-renderer@3.5.42': - resolution: {integrity: sha512-2++5dUyYS4gvo7xQXSECUDhB7TS0aOl5SeVfC5qSq1Jgfhjvegw1zqhwTIR3imZ+QYPJQw9gfcFvXGAjGZ7ajQ==} - - '@vue/shared@3.5.35': - resolution: {integrity: sha512-zSbjL7gRXwks2ZQLRGCajBtBXEOXW9Ddhn/HvSdrGkE2dqGnumzW8XtusRrxrE9LvqtiqDXQ+A60Hp6mvdYxfA==} - - '@vue/shared@3.5.42': - resolution: {integrity: sha512-2rPxex1jQf4jvl9MOHl6YaXCPcrNqz/FstMOEh3QWY+/OME9nQTvl9WYeCwhW7AFjaR0SnngZGlp/wkR6rkI6g==} - '@workflow/serde@4.1.0': resolution: {integrity: sha512-pav4F2BoirECWR7Nf1TKt+2eETcBj7jj4cBefQ8VXQCA6NPkaKeLfj/zMgi+3zYV5ZIBT4GuUiphsj0/b9hPQQ==} - acorn@8.16.0: - resolution: {integrity: sha512-UVJyE9MttOsBQIDKw1skb9nAwQuR5wuGD3+82K6JgJlm/Y+KI92oNsMNGZCYdDsVtRHSak0pcV5Dno5+4jh9sw==} - engines: {node: '>=0.4.0'} - hasBin: true - - ai@3.4.33: - resolution: {integrity: sha512-plBlrVZKwPoRTmM8+D1sJac9Bq8eaa2jiZlHLZIWekKWI1yMWYZvCCEezY9ASPwRhULYDJB2VhKOBUUeg3S5JQ==} - engines: {node: '>=18'} - peerDependencies: - openai: ^4.42.0 - react: ^18 || ^19 || ^19.0.0-rc - sswr: ^2.1.0 - svelte: ^5.55.7 - zod: ^3.0.0 - peerDependenciesMeta: - openai: - optional: true - react: - optional: true - sswr: - optional: true - svelte: - optional: true - zod: - optional: true - ai@4.3.19: resolution: {integrity: sha512-dIE2bfNpqHN3r6IINp9znguYdhIOheKW2LDigAMrgt/upT3B8eBGPSCblENvaZGoq+hxaN9fSMzjWpbqloP+7Q==} engines: {node: '>=18'} @@ -797,57 +581,17 @@ packages: ajv@8.20.0: resolution: {integrity: sha512-Thbli+OlOj+iMPYFBVBfJ3OmCAnaSyNn4M1vz9T6Gka5Jt9ba/HIR56joy65tY6kx/FCF5VXNB819Y7/GUrBGA==} - aria-query@5.3.1: - resolution: {integrity: sha512-Z/ZeOgVl7bcSYZ/u/rh0fOpvEpq//LZmdbkXyc7syVzjPAhfOa9ebsdTSjEBDU4vs5nC98Kfduj1uFo0qyET3g==} - engines: {node: '>= 0.4'} - - axobject-query@4.1.0: - resolution: {integrity: sha512-qIj0G9wZbMGNLjLmg1PT6v2mE9AH2zlnADJD/2tC6E00hgmhUOfEB6greHPAfLRSufHqROIUTkw6E+M3lH0PTQ==} - engines: {node: '>= 0.4'} - chalk@5.6.2: resolution: {integrity: sha512-7NzBL0rN6fMUW+f7A6Io4h40qQlG+xGmtMxfbnH/K7TAtt8JQWVQK+6g0UXKMeVJoyV5EkkNsErQ8pVD3bLHbA==} engines: {node: ^12.17.0 || ^14.13 || >=16.0.0} - clsx@2.1.1: - resolution: {integrity: sha512-eYm0QWBtUrBWZWG0d386OGAw16Z995PiOVo2B7bjWSbHedGl5e0ZWaq65kOGgUSNesEIDkB9ISbTg/JK9dhCZA==} - engines: {node: '>=6'} - - csstype@3.2.3: - resolution: {integrity: sha512-z1HGKcYy2xA8AGQfwrn0PAy+PB7X/GSj3UVJW9qKyn43xWa+gl5nXmU4qqLMRzWVLFC8KusUX8T/0kCiOYpAIQ==} - dequal@2.0.3: resolution: {integrity: sha512-0je+qPKHEMohvfRTCEo3CrPG6cAzAYgmzKyxRiYSSDkS6eGJdyVJm7WaYA5ECaAD9wLB2T4EEeymA5aFVcYXCA==} engines: {node: '>=6'} - devalue@5.9.3: - resolution: {integrity: sha512-xRumYOCUZN/EesqHEU3WOXanOZNvfZFZ/o1AHVFDX1yI0UAkZkOgDXt341CzKoBVIkgQba55/+DjGBKrIoKcHw==} - diff-match-patch@1.0.5: resolution: {integrity: sha512-IayShXAgj/QMXgB0IWmKx+rOPuGMhqm5w6jvFxmVenXKIzRqTAAsbBPT3kWQeGANj3jGgvcvv4yK6SxqYmikgw==} - entities@7.0.1: - resolution: {integrity: sha512-TWrgLOFUQTH994YUyl1yT4uyavY5nNB5muff+RtWaqNVCAK408b5ZnnbNAUEWLTCpum9w6arT70i1XdQ4UeOPA==} - engines: {node: '>=0.12'} - - esm-env@1.2.2: - resolution: {integrity: sha512-Epxrv+Nr/CaL4ZcFGPJIYLWFom+YeV1DqMLHJoEd9SYRxNbaFruBwfEX/kkHUJf55j2+TUbmDcmuilbP1TmXHA==} - - esrap@2.2.9: - resolution: {integrity: sha512-4KijP+NxCWthMCUC3qHbE6n4vCjqgJS1uAYKhuT/GWfFTf1Qyive2TgOjep+gzbSzRfnNyaN/UU9YmdOt8Eg0A==} - peerDependencies: - '@typescript-eslint/types': ^8.2.0 - peerDependenciesMeta: - '@typescript-eslint/types': - optional: true - - estree-walker@2.0.2: - resolution: {integrity: sha512-Rfkk/Mp/DL7JVje3u18FxFujQlTNR2q6QfMSMB7AvCBx91NGj/ba3kCfza0f6dVDbw7YlRf/nDrn7pQrCCyQ/w==} - - eventsource-parser@1.1.2: - resolution: {integrity: sha512-v0eOBUbiaFojBu2s2NPBfYUoRR9GjcDNvCXVaqEf5vVfpIAh9f8RCo4vXTP8c63QRKCFwoLpMpTdPwwhEKVgzA==} - engines: {node: '>=14.18'} - eventsource-parser@3.0.8: resolution: {integrity: sha512-70QWGkr4snxr0OXLRWsFLeRBIRPuQOvt4s8QYjmUlmlkyTZkRqS7EDVRZtzU3TiyDbXSzaOeF0XUKy8PchzukQ==} engines: {node: '>=18.0.0'} @@ -858,9 +602,6 @@ packages: fast-uri@3.1.8: resolution: {integrity: sha512-GZMtZUTNRpOVIECoXwLNZS5xUGE+mVNbTB8h/7Rwh2TFWcBQiPzTgyZi05BF9UMZKkLJv8XBRJTlU7zg8+ZfMg==} - is-reference@3.0.3: - resolution: {integrity: sha512-ixkJoqQvAP88E6wLydLGGqCJsrFUnqoH6HnaczB8XmDH1oaWU+xxdptvikTgaEhtZ53Ky6YXiBuUI2WXLMCwjw==} - json-schema-traverse@1.0.0: resolution: {integrity: sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==} @@ -872,24 +613,11 @@ packages: engines: {node: ^18.0.0 || >=20.0.0} hasBin: true - locate-character@3.0.0: - resolution: {integrity: sha512-SW13ws7BjaeJ6p7Q6CO2nchbYEc3X3J6WrmTTDto7yMPqVSZTUyY5Tjbid+Ab8gLnATtygYtiDIJGQRRn2ZOiA==} - - magic-string@0.30.21: - resolution: {integrity: sha512-vd2F4YUyEXKGcLHoq+TEyCjxueSeHnFxyyjNp80yg0XV4vUhnDer/lvvlqM/arB5bXQN5K2/3oinyCRyx8T2CQ==} - nanoid@3.3.18: resolution: {integrity: sha512-DTg4MJbGMWkfi6VZFdNt2/caMbQy4Ou+Op/hJQvGEWcnVfoA1QA+xzRKAzw9jD6+GVOOeYr/mIcuDSdug6F6+w==} engines: {node: ^10 || ^12 || ^13.7 || ^14 || >=15.0.1} hasBin: true - picocolors@1.1.1: - resolution: {integrity: sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==} - - postcss@8.5.23: - resolution: {integrity: sha512-g50586zr4bZmwFiTlflMu8E0bDTb5I5gertgwAKmsdUlTQIhZtunzUlD1WSzwcVWPoAVpsrA6vlfCD7oXvRwgg==} - engines: {node: ^10 || ^12 || >=14} - react@19.2.6: resolution: {integrity: sha512-sfWGGfavi0xr8Pg0sVsyHMAOziVYKgPLNrS7ig+ivMNb3wbCBw3KxtflsGBAwD3gYQlE/AEZsTLgToRrSCjb0Q==} engines: {node: '>=0.10.0'} @@ -898,8 +626,8 @@ packages: resolution: {integrity: sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw==} engines: {node: '>=0.10.0'} - run@2.1.8: - resolution: {integrity: sha512-B+N1a8grWUEmQwS8QL+o0yoB0sjPitphQ/PaION9j0m/msDxA9j1fxgX6VC4m604/OzT3IAMAZIw7kn8zn1R0A==} + run@2.1.10: + resolution: {integrity: sha512-hNPVUA5r1Baqj4WaGcYlsXZoiD8Ir7O0HjITOdOBBDqf2rHnaPJheUrniPhWIuya/hEL3rmqItXoUnuS6hljPA==} engines: {node: '>=20.19.0'} peerDependencies: typescript: '>=5.1.6 <7' @@ -910,32 +638,11 @@ packages: secure-json-parse@2.7.0: resolution: {integrity: sha512-6aU+Rwsezw7VR8/nyvKTx8QpWH9FrcYiXXlqC4z5d5XQBDRqtbfsRjnwGyqbi3gddNtWHuEk9OANUotL26qKUw==} - source-map-js@1.2.2: - resolution: {integrity: sha512-KGj/8Y43x35aZVDtt+J4mK1hoLGHULMYfSkODJNQjNDC3oW1PqPoxMwo0pLUsWM/UEGzON/NxeHywEfNXNP3Vw==} - engines: {node: '>=0.10.0'} - - sswr@2.2.0: - resolution: {integrity: sha512-clTszLPZkmycALTHD1mXGU+mOtA/MIoLgS1KGTTzFNVm9rytQVykgRaP+z1zl572cz0bTqj4rFVoC2N+IGK4Sg==} - peerDependencies: - svelte: ^5.55.7 - - svelte@5.55.9: - resolution: {integrity: sha512-fTjjT8cHLDwigcu2j3pv7Jq04LklXevPB8uBgyHNiTXv+RMNvVnrjS4UEYrLMkhuq1vpCodHjiW+z/95SDs/fg==} - engines: {node: '>=18'} - swr@2.4.1: resolution: {integrity: sha512-2CC6CiKQtEwaEeNiqWTAw9PGykW8SR5zZX8MZk6TeAvEAnVS7Visz8WzphqgtQ8v2xz/4Q5K+j+SeMaKXeeQIA==} peerDependencies: react: ^16.11.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 - swrev@4.0.0: - resolution: {integrity: sha512-LqVcOHSB4cPGgitD1riJ1Hh4vdmITOp+BkmfmXRh4hSF/t7EnS4iD+SOTmq7w5pPm/SiPeto4ADbKS6dHUDWFA==} - - swrv@1.2.0: - resolution: {integrity: sha512-lH/g4UcNyj+7lzK4eRGT4C68Q4EhQ6JtM9otPRIASfhhzfLWtbZPHcMuhuba7S9YVYuxkMUGImwMyGpfbkH07A==} - peerDependencies: - vue: '>=3.2.26 < 4' - throttleit@2.1.0: resolution: {integrity: sha512-nt6AMGKW1p/70DF/hGBdJB57B8Tspmbp5gfJ8ilhLnt7kkr2ye7hzD6NVG8GGErk2HWF34igrL2CXmNIkzKqKw==} engines: {node: '>=18'} @@ -953,17 +660,6 @@ packages: peerDependencies: react: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 - vue@3.5.35: - resolution: {integrity: sha512-cx89fnr+0kVGHiNFG6y6s0bdjypJRFNZn6x3WPstNdQR1bi1mbB7h4v5IBGTsPJU3nK1+0Iqj3Zf+hZWMieR4Q==} - peerDependencies: - typescript: '*' - peerDependenciesMeta: - typescript: - optional: true - - zimmerframe@1.1.4: - resolution: {integrity: sha512-B58NGBEoc8Y9MWWCQGl/gq9xBCe4IiKM0a2x7GZdQKOW5Exr8S1W24J6OgM1njK8xCRGvAJIL/MxXHf6SkmQKQ==} - zod-to-json-schema@3.25.2: resolution: {integrity: sha512-O/PgfnpT1xKSDeQYSCfRI5Gy3hPf91mKVDuYLUHZJMiDFptvP41MSnWofm8dnCm0256ZNfZIM7DSzuSMAFnjHA==} peerDependencies: @@ -1013,7 +709,7 @@ snapshots: '@ai-sdk/code-mode@1.0.100(ai@7.0.143(zod@3.25.76))': dependencies: ai: 7.0.143(zod@3.25.76) - run: 2.1.8 + run: 2.1.10 transitivePeerDependencies: - typescript @@ -1109,18 +805,6 @@ snapshots: '@vercel/oidc': 3.2.0 zod: 3.25.76 - '@ai-sdk/openai@0.0.62(zod@3.25.76)': - dependencies: - '@ai-sdk/provider': 0.0.23 - '@ai-sdk/provider-utils': 1.0.19(zod@3.25.76) - zod: 3.25.76 - - '@ai-sdk/openai@0.0.72(zod@3.25.76)': - dependencies: - '@ai-sdk/provider': 0.0.26 - '@ai-sdk/provider-utils': 1.0.22(zod@3.25.76) - zod: 3.25.76 - '@ai-sdk/openai@1.3.24(zod@3.25.76)': dependencies: '@ai-sdk/provider': 1.1.3 @@ -1163,24 +847,6 @@ snapshots: '@ai-sdk/provider-utils': 5.0.59(zod@3.25.76) zod: 3.25.76 - '@ai-sdk/provider-utils@1.0.19(zod@3.25.76)': - dependencies: - '@ai-sdk/provider': 0.0.23 - eventsource-parser: 1.1.2 - nanoid: 3.3.18 - secure-json-parse: 2.7.0 - optionalDependencies: - zod: 3.25.76 - - '@ai-sdk/provider-utils@1.0.22(zod@3.25.76)': - dependencies: - '@ai-sdk/provider': 0.0.26 - eventsource-parser: 1.1.2 - nanoid: 3.3.18 - secure-json-parse: 2.7.0 - optionalDependencies: - zod: 3.25.76 - '@ai-sdk/provider-utils@2.2.8(zod@3.25.76)': dependencies: '@ai-sdk/provider': 1.1.3 @@ -1321,14 +987,6 @@ snapshots: undici: 7.29.1 zod: 3.25.76 - '@ai-sdk/provider@0.0.23': - dependencies: - json-schema: 0.4.0 - - '@ai-sdk/provider@0.0.26': - dependencies: - json-schema: 0.4.0 - '@ai-sdk/provider@1.1.3': dependencies: json-schema: 0.4.0 @@ -1397,16 +1055,6 @@ snapshots: dependencies: json-schema: 0.4.0 - '@ai-sdk/react@0.0.70(react@19.2.6)(zod@3.25.76)': - dependencies: - '@ai-sdk/provider-utils': 1.0.22(zod@3.25.76) - '@ai-sdk/ui-utils': 0.0.50(zod@3.25.76) - swr: 2.4.1(react@19.2.6) - throttleit: 2.1.0 - optionalDependencies: - react: 19.2.6 - zod: 3.25.76 - '@ai-sdk/react@1.2.12(react@19.2.6)(zod@3.25.76)': dependencies: '@ai-sdk/provider-utils': 2.2.8(zod@3.25.76) @@ -1417,33 +1065,6 @@ snapshots: optionalDependencies: zod: 3.25.76 - '@ai-sdk/solid@0.0.54(zod@3.25.76)': - dependencies: - '@ai-sdk/provider-utils': 1.0.22(zod@3.25.76) - '@ai-sdk/ui-utils': 0.0.50(zod@3.25.76) - transitivePeerDependencies: - - zod - - '@ai-sdk/svelte@0.0.57(svelte@5.55.9)(zod@3.25.76)': - dependencies: - '@ai-sdk/provider-utils': 1.0.22(zod@3.25.76) - '@ai-sdk/ui-utils': 0.0.50(zod@3.25.76) - sswr: 2.2.0(svelte@5.55.9) - optionalDependencies: - svelte: 5.55.9 - transitivePeerDependencies: - - zod - - '@ai-sdk/ui-utils@0.0.50(zod@3.25.76)': - dependencies: - '@ai-sdk/provider': 0.0.26 - '@ai-sdk/provider-utils': 1.0.22(zod@3.25.76) - json-schema: 0.4.0 - secure-json-parse: 2.7.0 - zod-to-json-schema: 3.25.2(zod@3.25.76) - optionalDependencies: - zod: 3.25.76 - '@ai-sdk/ui-utils@1.2.11(zod@3.25.76)': dependencies: '@ai-sdk/provider': 1.1.3 @@ -1451,16 +1072,6 @@ snapshots: zod: 3.25.76 zod-to-json-schema: 3.25.2(zod@3.25.76) - '@ai-sdk/vue@0.0.59(vue@3.5.35)(zod@3.25.76)': - dependencies: - '@ai-sdk/provider-utils': 1.0.22(zod@3.25.76) - '@ai-sdk/ui-utils': 0.0.50(zod@3.25.76) - swrv: 1.2.0(vue@3.5.35) - optionalDependencies: - vue: 3.5.35 - transitivePeerDependencies: - - zod - '@ai-sdk/workflow@1.0.8(zod@3.25.76)': dependencies: '@ai-sdk/provider': 4.0.1 @@ -1469,52 +1080,12 @@ snapshots: ajv: 8.20.0 zod: 3.25.76 - '@babel/helper-string-parser@7.29.7': {} - - '@babel/helper-validator-identifier@7.29.7': {} - - '@babel/parser@7.29.9': - dependencies: - '@babel/types': 7.29.8 - - '@babel/types@7.29.8': - dependencies: - '@babel/helper-string-parser': 7.29.7 - '@babel/helper-validator-identifier': 7.29.7 - - '@jridgewell/gen-mapping@0.3.13': - dependencies: - '@jridgewell/sourcemap-codec': 1.5.5 - '@jridgewell/trace-mapping': 0.3.31 - - '@jridgewell/remapping@2.3.5': - dependencies: - '@jridgewell/gen-mapping': 0.3.13 - '@jridgewell/trace-mapping': 0.3.31 - - '@jridgewell/resolve-uri@3.1.2': {} - - '@jridgewell/sourcemap-codec@1.5.5': {} - - '@jridgewell/trace-mapping@0.3.31': - dependencies: - '@jridgewell/resolve-uri': 3.1.2 - '@jridgewell/sourcemap-codec': 1.5.5 - '@opentelemetry/api@1.9.0': {} '@standard-schema/spec@1.1.0': {} - '@sveltejs/acorn-typescript@1.0.10(acorn@8.16.0)': - dependencies: - acorn: 8.16.0 - '@types/diff-match-patch@1.0.36': {} - '@types/estree@1.0.9': {} - - '@types/trusted-types@2.0.7': {} - '@vercel/oidc@3.0.3': {} '@vercel/oidc@3.0.5': {} @@ -1523,124 +1094,8 @@ snapshots: '@vercel/oidc@3.2.0': {} - '@vue/compiler-core@3.5.35': - dependencies: - '@babel/parser': 7.29.9 - '@vue/shared': 3.5.35 - entities: 7.0.1 - estree-walker: 2.0.2 - source-map-js: 1.2.2 - - '@vue/compiler-core@3.5.42': - dependencies: - '@babel/parser': 7.29.9 - '@vue/shared': 3.5.42 - entities: 7.0.1 - estree-walker: 2.0.2 - source-map-js: 1.2.2 - - '@vue/compiler-dom@3.5.35': - dependencies: - '@vue/compiler-core': 3.5.35 - '@vue/shared': 3.5.35 - - '@vue/compiler-dom@3.5.42': - dependencies: - '@vue/compiler-core': 3.5.42 - '@vue/shared': 3.5.42 - - '@vue/compiler-sfc@3.5.35': - dependencies: - '@babel/parser': 7.29.9 - '@vue/compiler-core': 3.5.35 - '@vue/compiler-dom': 3.5.35 - '@vue/compiler-ssr': 3.5.35 - '@vue/shared': 3.5.35 - estree-walker: 2.0.2 - magic-string: 0.30.21 - postcss: 8.5.23 - source-map-js: 1.2.2 - - '@vue/compiler-ssr@3.5.35': - dependencies: - '@vue/compiler-dom': 3.5.35 - '@vue/shared': 3.5.35 - - '@vue/compiler-ssr@3.5.42': - dependencies: - '@vue/compiler-dom': 3.5.42 - '@vue/shared': 3.5.42 - - '@vue/reactivity@3.5.35': - dependencies: - '@vue/shared': 3.5.35 - - '@vue/reactivity@3.5.42': - dependencies: - '@vue/shared': 3.5.42 - - '@vue/runtime-core@3.5.35': - dependencies: - '@vue/reactivity': 3.5.35 - '@vue/shared': 3.5.35 - - '@vue/runtime-core@3.5.42': - dependencies: - '@vue/reactivity': 3.5.42 - '@vue/shared': 3.5.42 - - '@vue/runtime-dom@3.5.35': - dependencies: - '@vue/reactivity': 3.5.35 - '@vue/runtime-core': 3.5.35 - '@vue/shared': 3.5.35 - csstype: 3.2.3 - - '@vue/runtime-dom@3.5.42': - dependencies: - '@vue/reactivity': 3.5.42 - '@vue/runtime-core': 3.5.42 - '@vue/shared': 3.5.42 - csstype: 3.2.3 - - '@vue/server-renderer@3.5.42': - dependencies: - '@vue/compiler-ssr': 3.5.42 - '@vue/runtime-dom': 3.5.42 - '@vue/shared': 3.5.42 - - '@vue/shared@3.5.35': {} - - '@vue/shared@3.5.42': {} - '@workflow/serde@4.1.0': {} - acorn@8.16.0: {} - - ai@3.4.33(react@19.2.6)(sswr@2.2.0(svelte@5.55.9))(svelte@5.55.9)(vue@3.5.35)(zod@3.25.76): - dependencies: - '@ai-sdk/provider': 0.0.26 - '@ai-sdk/provider-utils': 1.0.22(zod@3.25.76) - '@ai-sdk/react': 0.0.70(react@19.2.6)(zod@3.25.76) - '@ai-sdk/solid': 0.0.54(zod@3.25.76) - '@ai-sdk/svelte': 0.0.57(svelte@5.55.9)(zod@3.25.76) - '@ai-sdk/ui-utils': 0.0.50(zod@3.25.76) - '@ai-sdk/vue': 0.0.59(vue@3.5.35)(zod@3.25.76) - '@opentelemetry/api': 1.9.0 - eventsource-parser: 1.1.2 - json-schema: 0.4.0 - jsondiffpatch: 0.6.0 - secure-json-parse: 2.7.0 - zod-to-json-schema: 3.25.2(zod@3.25.76) - optionalDependencies: - react: 19.2.6 - sswr: 2.2.0(svelte@5.55.9) - svelte: 5.55.9 - zod: 3.25.76 - transitivePeerDependencies: - - solid-js - - vue - ai@4.3.19(react@19.2.6)(zod@3.25.76): dependencies: '@ai-sdk/provider': 1.1.3 @@ -1720,44 +1175,18 @@ snapshots: json-schema-traverse: 1.0.0 require-from-string: 2.0.2 - aria-query@5.3.1: {} - - axobject-query@4.1.0: {} - chalk@5.6.2: {} - clsx@2.1.1: {} - - csstype@3.2.3: {} - dequal@2.0.3: {} - devalue@5.9.3: {} - diff-match-patch@1.0.5: {} - entities@7.0.1: {} - - esm-env@1.2.2: {} - - esrap@2.2.9: - dependencies: - '@jridgewell/sourcemap-codec': 1.5.5 - - estree-walker@2.0.2: {} - - eventsource-parser@1.1.2: {} - eventsource-parser@3.0.8: {} fast-deep-equal@3.1.3: {} fast-uri@3.1.8: {} - is-reference@3.0.3: - dependencies: - '@types/estree': 1.0.9 - json-schema-traverse@1.0.0: {} json-schema@0.4.0: {} @@ -1768,70 +1197,22 @@ snapshots: chalk: 5.6.2 diff-match-patch: 1.0.5 - locate-character@3.0.0: {} - - magic-string@0.30.21: - dependencies: - '@jridgewell/sourcemap-codec': 1.5.5 - nanoid@3.3.18: {} - picocolors@1.1.1: {} - - postcss@8.5.23: - dependencies: - nanoid: 3.3.18 - picocolors: 1.1.1 - source-map-js: 1.2.2 - react@19.2.6: {} require-from-string@2.0.2: {} - run@2.1.8: {} + run@2.1.10: {} secure-json-parse@2.7.0: {} - source-map-js@1.2.2: {} - - sswr@2.2.0(svelte@5.55.9): - dependencies: - svelte: 5.55.9 - swrev: 4.0.0 - - svelte@5.55.9: - dependencies: - '@jridgewell/remapping': 2.3.5 - '@jridgewell/sourcemap-codec': 1.5.5 - '@sveltejs/acorn-typescript': 1.0.10(acorn@8.16.0) - '@types/estree': 1.0.9 - '@types/trusted-types': 2.0.7 - acorn: 8.16.0 - aria-query: 5.3.1 - axobject-query: 4.1.0 - clsx: 2.1.1 - devalue: 5.9.3 - esm-env: 1.2.2 - esrap: 2.2.9 - is-reference: 3.0.3 - locate-character: 3.0.0 - magic-string: 0.30.21 - zimmerframe: 1.1.4 - transitivePeerDependencies: - - '@typescript-eslint/types' - swr@2.4.1(react@19.2.6): dependencies: dequal: 2.0.3 react: 19.2.6 use-sync-external-store: 1.6.0(react@19.2.6) - swrev@4.0.0: {} - - swrv@1.2.0(vue@3.5.35): - dependencies: - vue: 3.5.35 - throttleit@2.1.0: {} undici@6.28.1: {} @@ -1842,16 +1223,6 @@ snapshots: dependencies: react: 19.2.6 - vue@3.5.35: - dependencies: - '@vue/compiler-dom': 3.5.35 - '@vue/compiler-sfc': 3.5.35 - '@vue/runtime-dom': 3.5.35 - '@vue/server-renderer': 3.5.42 - '@vue/shared': 3.5.35 - - zimmerframe@1.1.4: {} - zod-to-json-schema@3.25.2(zod@3.25.76): dependencies: zod: 3.25.76 diff --git a/e2e/scenarios/ai-sdk-instrumentation/scenario.ai-sdk-v3.mjs b/e2e/scenarios/ai-sdk-instrumentation/scenario.ai-sdk-v3.mjs deleted file mode 100644 index 451888c7a..000000000 --- a/e2e/scenarios/ai-sdk-instrumentation/scenario.ai-sdk-v3.mjs +++ /dev/null @@ -1,21 +0,0 @@ -const aiPackageName = process.env.AI_SDK_PACKAGE_NAME ?? "ai-sdk-v3-latest"; -const openaiPackageName = - process.env.AI_SDK_OPENAI_PACKAGE_NAME ?? "ai-sdk-openai-v3-latest"; -const ai = await import(aiPackageName); -const { createOpenAI, openai } = await import(openaiPackageName); -import { getInstalledPackageVersion } from "../../helpers/provider-runtime.mjs"; -import { runAutoAISDKInstrumentationOrExit } from "./scenario.impl.mjs"; - -runAutoAISDKInstrumentationOrExit({ - ai, - createOpenAI, - maxTokensKey: "maxTokens", - openai, - sdkVersion: await getInstalledPackageVersion(import.meta.url, aiPackageName), - supportsEmbedMany: false, - supportsGenerateObject: true, - supportsRerank: false, - supportsStreamObject: true, - supportsToolExecution: false, - toolSchemaKey: "parameters", -}); diff --git a/e2e/scenarios/ai-sdk-instrumentation/scenario.ai-sdk-v3.ts b/e2e/scenarios/ai-sdk-instrumentation/scenario.ai-sdk-v3.ts deleted file mode 100644 index cf589100c..000000000 --- a/e2e/scenarios/ai-sdk-instrumentation/scenario.ai-sdk-v3.ts +++ /dev/null @@ -1,30 +0,0 @@ -const aiPackageName = process.env.AI_SDK_PACKAGE_NAME ?? "ai-sdk-v3-latest"; -const openaiPackageName = - process.env.AI_SDK_OPENAI_PACKAGE_NAME ?? "ai-sdk-openai-v3-latest"; -import { - getInstalledPackageVersion, - runMain, -} from "../../helpers/scenario-runtime"; -import { runWrappedAISDKInstrumentation } from "./scenario.impl.mjs"; - -runMain(async () => { - const ai = await import(aiPackageName); - const { createOpenAI, openai } = await import(openaiPackageName); - - await runWrappedAISDKInstrumentation({ - ai, - createOpenAI, - maxTokensKey: "maxTokens", - openai, - sdkVersion: await getInstalledPackageVersion( - import.meta.url, - aiPackageName, - ), - supportsEmbedMany: false, - supportsGenerateObject: true, - supportsRerank: false, - supportsStreamObject: true, - supportsToolExecution: false, - toolSchemaKey: "parameters", - }); -}); diff --git a/e2e/scenarios/ai-sdk-instrumentation/scenario.concurrent-shared-model.test.ts b/e2e/scenarios/ai-sdk-instrumentation/scenario.concurrent-shared-model.test.ts index 25f494da9..012c08874 100644 --- a/e2e/scenarios/ai-sdk-instrumentation/scenario.concurrent-shared-model.test.ts +++ b/e2e/scenarios/ai-sdk-instrumentation/scenario.concurrent-shared-model.test.ts @@ -1,4 +1,4 @@ -import { expect, test } from "vitest"; +import { expect, test } from "vite-plus/test"; import { resolveFileSnapshotPath } from "../../helpers/file-snapshot"; import type { CapturedLogEvent } from "../../helpers/mock-braintrust-server"; import { diff --git a/e2e/scenarios/ai-sdk-instrumentation/scenario.impl.mjs b/e2e/scenarios/ai-sdk-instrumentation/scenario.impl.mjs index 42d6b6070..6f5e00f31 100644 --- a/e2e/scenarios/ai-sdk-instrumentation/scenario.impl.mjs +++ b/e2e/scenarios/ai-sdk-instrumentation/scenario.impl.mjs @@ -13,38 +13,6 @@ const CACHE_PROMPT_PREFIX = "braintrust cache prefix ".repeat(900).trim(); const OPENAI_CACHE_KEY = "braintrust-ai-sdk-e2e-cache"; const CACHE_FILL_DELAY_MS = 5_000; export const AI_SDK_SCENARIO_SPECS = [ - { - autoEntry: "scenario.ai-sdk-v3.mjs", - dependencyName: "ai-sdk-v3", - maxTokensKey: "maxTokens", - openaiModuleName: "ai-sdk-openai-v3", - packageName: "ai-sdk-v3", - snapshotName: "ai-sdk-v3", - supportsEmbedMany: false, - supportsProviderCacheAssertions: false, - supportsGenerateObject: true, - supportsRerank: false, - supportsStreamObject: true, - supportsToolExecution: false, - toolSchemaKey: "parameters", - wrapperEntry: "scenario.ai-sdk-v3.ts", - }, - { - autoEntry: "scenario.ai-sdk-v3.mjs", - dependencyName: "ai-sdk-v3-latest", - maxTokensKey: "maxTokens", - openaiModuleName: "ai-sdk-openai-v3-latest", - packageName: "ai-sdk-v3-latest", - snapshotName: "ai-sdk-v3-latest", - supportsEmbedMany: false, - supportsProviderCacheAssertions: false, - supportsGenerateObject: true, - supportsRerank: false, - supportsStreamObject: true, - supportsToolExecution: false, - toolSchemaKey: "parameters", - wrapperEntry: "scenario.ai-sdk-v3.ts", - }, { autoEntry: "scenario.ai-sdk-v4.mjs", dependencyName: "ai-sdk-v4", diff --git a/e2e/scenarios/ai-sdk-instrumentation/scenario.test.ts b/e2e/scenarios/ai-sdk-instrumentation/scenario.test.ts index 8934a921b..1d4f2dcf5 100644 --- a/e2e/scenarios/ai-sdk-instrumentation/scenario.test.ts +++ b/e2e/scenarios/ai-sdk-instrumentation/scenario.test.ts @@ -1,4 +1,4 @@ -import { describe } from "vitest"; +import { describe } from "vite-plus/test"; import { describeVariant } from "../../helpers/describe-variant"; import { prepareScenarioDir, @@ -33,7 +33,7 @@ function parseMajorVersion(version: string): number { const describeVariants = process.env.BRAINTRUST_E2E_CASSETTE_MODE === "record" || process.env.BRAINTRUST_E2E_CASSETTE_MODE === "record-missing" - ? describe.sequential + ? describe : describe.concurrent; describeVariants("variants", () => { @@ -57,13 +57,14 @@ describeVariants("variants", () => { await runScenarioDir({ entry: scenario.wrapperEntry, env: { + BRAINTRUST_AUTO_CAPTURE_ATTACHMENTS: "true", AI_SDK_PACKAGE_NAME: scenario.packageName, ...(scenario.codeModeModuleName ? { AI_SDK_CODE_MODE_PACKAGE_NAME: scenario.codeModeModuleName, } : {}), - BRAINTRUST_CAPTURE_ATTACHMENTS: "true", + BRAINTRUST_AUTO_CAPTURE_ATTACHMENTS: "true", AI_SDK_OPENAI_PACKAGE_NAME: scenario.openaiModuleName, AI_SDK_SUPPORTS_EVALUATE_STRING_MODEL: String( scenario.supportsEvaluateStringModel !== false, @@ -127,13 +128,14 @@ describeVariants("variants", () => { await runNodeScenarioDir({ entry: scenario.autoEntry, env: { + BRAINTRUST_AUTO_CAPTURE_ATTACHMENTS: "true", AI_SDK_PACKAGE_NAME: scenario.packageName, ...(scenario.codeModeModuleName ? { AI_SDK_CODE_MODE_PACKAGE_NAME: scenario.codeModeModuleName, } : {}), - BRAINTRUST_CAPTURE_ATTACHMENTS: "true", + BRAINTRUST_AUTO_CAPTURE_ATTACHMENTS: "true", AI_SDK_OPENAI_PACKAGE_NAME: scenario.openaiModuleName, AI_SDK_SUPPORTS_EVALUATE_STRING_MODEL: String( scenario.supportsEvaluateStringModel !== false, diff --git a/e2e/scenarios/ai-sdk-otel-export/scenario.test.ts b/e2e/scenarios/ai-sdk-otel-export/scenario.test.ts index 0454cb62e..005290caa 100644 --- a/e2e/scenarios/ai-sdk-otel-export/scenario.test.ts +++ b/e2e/scenarios/ai-sdk-otel-export/scenario.test.ts @@ -1,4 +1,4 @@ -import { expect, test } from "vitest"; +import { expect, test } from "vite-plus/test"; import { formatJsonFileSnapshot, matchFileSnapshot, diff --git a/e2e/scenarios/anthropic-bedrock-instrumentation/assertions.ts b/e2e/scenarios/anthropic-bedrock-instrumentation/assertions.ts index 7b32ee8f4..d343b006b 100644 --- a/e2e/scenarios/anthropic-bedrock-instrumentation/assertions.ts +++ b/e2e/scenarios/anthropic-bedrock-instrumentation/assertions.ts @@ -1,4 +1,4 @@ -import { beforeAll, describe, expect, test } from "vitest"; +import { beforeAll, describe, expect, test } from "vite-plus/test"; import type { CapturedLogEvent } from "../../helpers/mock-braintrust-server"; import { resolveFileSnapshotPath } from "../../helpers/file-snapshot"; import { diff --git a/e2e/scenarios/anthropic-bedrock-instrumentation/scenario.test.ts b/e2e/scenarios/anthropic-bedrock-instrumentation/scenario.test.ts index ff58b7580..7176b5c3a 100644 --- a/e2e/scenarios/anthropic-bedrock-instrumentation/scenario.test.ts +++ b/e2e/scenarios/anthropic-bedrock-instrumentation/scenario.test.ts @@ -1,4 +1,4 @@ -import { describe } from "vitest"; +import { describe } from "vite-plus/test"; import { describeVariant } from "../../helpers/describe-variant"; import { prepareScenarioDir, diff --git a/e2e/scenarios/anthropic-instrumentation/assertions.ts b/e2e/scenarios/anthropic-instrumentation/assertions.ts index d7f5ab4cd..fc172cc26 100644 --- a/e2e/scenarios/anthropic-instrumentation/assertions.ts +++ b/e2e/scenarios/anthropic-instrumentation/assertions.ts @@ -1,4 +1,4 @@ -import { beforeAll, describe, expect, test } from "vitest"; +import { beforeAll, describe, expect, test } from "vite-plus/test"; import type { Json } from "../../helpers/normalize"; import type { CapturedLogEvent } from "../../helpers/mock-braintrust-server"; import { resolveFileSnapshotPath } from "../../helpers/file-snapshot"; diff --git a/e2e/scenarios/anthropic-instrumentation/scenario.capture.mjs b/e2e/scenarios/anthropic-instrumentation/scenario.capture.mjs new file mode 100644 index 000000000..44a743716 --- /dev/null +++ b/e2e/scenarios/anthropic-instrumentation/scenario.capture.mjs @@ -0,0 +1,10 @@ +import { runMain } from "../../helpers/provider-runtime.mjs"; +import { runAttachmentCaptureScenario } from "../../helpers/attachment-capture-scenario.mjs"; + +runMain(async () => + runAttachmentCaptureScenario( + "anthropic", + import.meta.url, + await import(process.env.CAPTURE_PACKAGE_NAME), + ), +); diff --git a/e2e/scenarios/anthropic-instrumentation/scenario.test.ts b/e2e/scenarios/anthropic-instrumentation/scenario.test.ts index f7e05153b..f5bef14b1 100644 --- a/e2e/scenarios/anthropic-instrumentation/scenario.test.ts +++ b/e2e/scenarios/anthropic-instrumentation/scenario.test.ts @@ -1,4 +1,5 @@ -import { describe } from "vitest"; +import { defineAttachmentCaptureTests } from "../../helpers/attachment-capture-assertions"; +import { describe } from "vite-plus/test"; import { describeVariant } from "../../helpers/describe-variant"; import { prepareScenarioDir, @@ -53,7 +54,10 @@ describe.concurrent("variants", () => { runScenario: async ({ runScenarioDir }) => { await runScenarioDir({ entry: scenario.wrapperEntry, - env: { ANTHROPIC_PACKAGE_NAME: scenario.dependencyName }, + env: { + BRAINTRUST_AUTO_CAPTURE_ATTACHMENTS: "true", + ANTHROPIC_PACKAGE_NAME: scenario.dependencyName, + }, runContext: { variantKey: scenario.snapshotName, originalScenarioDir, @@ -77,7 +81,10 @@ describe.concurrent("variants", () => { runScenario: async ({ runNodeScenarioDir }) => { await runNodeScenarioDir({ entry: scenario.autoEntry, - env: { ANTHROPIC_PACKAGE_NAME: scenario.dependencyName }, + env: { + BRAINTRUST_AUTO_CAPTURE_ATTACHMENTS: "true", + ANTHROPIC_PACKAGE_NAME: scenario.dependencyName, + }, nodeArgs: ["--import", "braintrust/hook.mjs"], runContext: { variantKey: scenario.snapshotName, @@ -99,3 +106,13 @@ describe.concurrent("variants", () => { }); } }); + +for (const scenario of anthropicScenarios) { + defineAttachmentCaptureTests({ + scenarioDir, + originalScenarioDir, + provider: "anthropic", + packageName: scenario.dependencyName, + variantKey: scenario.snapshotName, + }); +} diff --git a/e2e/scenarios/anthropic-vertex-instrumentation/assertions.ts b/e2e/scenarios/anthropic-vertex-instrumentation/assertions.ts index f46db0e0c..702f602f5 100644 --- a/e2e/scenarios/anthropic-vertex-instrumentation/assertions.ts +++ b/e2e/scenarios/anthropic-vertex-instrumentation/assertions.ts @@ -1,4 +1,4 @@ -import { beforeAll, describe, expect, test } from "vitest"; +import { beforeAll, describe, expect, test } from "vite-plus/test"; import type { CapturedLogEvent } from "../../helpers/mock-braintrust-server"; import { resolveFileSnapshotPath } from "../../helpers/file-snapshot"; import { withScenarioHarness } from "../../helpers/scenario-harness"; diff --git a/e2e/scenarios/anthropic-vertex-instrumentation/scenario.test.ts b/e2e/scenarios/anthropic-vertex-instrumentation/scenario.test.ts index b19cd1a9d..640c1df87 100644 --- a/e2e/scenarios/anthropic-vertex-instrumentation/scenario.test.ts +++ b/e2e/scenarios/anthropic-vertex-instrumentation/scenario.test.ts @@ -1,4 +1,4 @@ -import { describe } from "vitest"; +import { describe } from "vite-plus/test"; import { describeVariant } from "../../helpers/describe-variant"; import { prepareScenarioDir, diff --git a/e2e/scenarios/bedrock-runtime-instrumentation/assertions.ts b/e2e/scenarios/bedrock-runtime-instrumentation/assertions.ts index 482418ce0..701b0f2a7 100644 --- a/e2e/scenarios/bedrock-runtime-instrumentation/assertions.ts +++ b/e2e/scenarios/bedrock-runtime-instrumentation/assertions.ts @@ -1,4 +1,4 @@ -import { beforeAll, describe, expect, test } from "vitest"; +import { beforeAll, describe, expect, test } from "vite-plus/test"; import type { CapturedLogEvent } from "../../helpers/mock-braintrust-server"; import { resolveFileSnapshotPath } from "../../helpers/file-snapshot"; import { diff --git a/e2e/scenarios/bedrock-runtime-instrumentation/scenario.test.ts b/e2e/scenarios/bedrock-runtime-instrumentation/scenario.test.ts index be0a30983..6a0749658 100644 --- a/e2e/scenarios/bedrock-runtime-instrumentation/scenario.test.ts +++ b/e2e/scenarios/bedrock-runtime-instrumentation/scenario.test.ts @@ -1,4 +1,4 @@ -import { describe } from "vitest"; +import { describe } from "vite-plus/test"; import { describeVariant } from "../../helpers/describe-variant"; import { prepareScenarioDir, diff --git a/e2e/scenarios/claude-agent-sdk-instrumentation/__snapshots__/claude-agent-sdk-v0-auto-hook.span-tree.json b/e2e/scenarios/claude-agent-sdk-instrumentation/__snapshots__/claude-agent-sdk-v0-auto-hook.span-tree.json index 2eb4f2250..ee005765f 100644 --- a/e2e/scenarios/claude-agent-sdk-instrumentation/__snapshots__/claude-agent-sdk-v0-auto-hook.span-tree.json +++ b/e2e/scenarios/claude-agent-sdk-instrumentation/__snapshots__/claude-agent-sdk-v0-auto-hook.span-tree.json @@ -969,7 +969,7 @@ { "name": "calculator-local-handler-divide", "children": [], - "error": "division by zero\n\nError: division by zero\n at traced.name (/e2e/scenarios/claude-agent-sdk-instrumentation/scenario.impl.mjs:0:0)\n at /js/dist/index.mjs:0:0\n at AsyncLocalStorage.run (node::0:0)\n at BraintrustContextManager.runInContext (/js/dist/index.mjs:0:0)\n at withCurrent (/js/dist/index.mjs:0:0)\n at /js/dist/index.mjs:0:0\n at runCatchFinally (/js/dist/index.mjs:0:0)\n at traced (/js/dist/index.mjs:0:0)\n at /e2e/scenarios/claude-agent-sdk-instrumentation/scenario.impl.mjs:0:0\n at runHandler (/js/dist/index.mjs:0:0)" + "error": "division by zero\n\nError: division by zero\n at traced.name (/e2e/scenarios/claude-agent-sdk-instrumentation/scenario.impl.mjs:0:0)\n at /js/dist/node/logger/core.js:0:0\n at AsyncLocalStorage.run (node::0:0)\n at BraintrustContextManager.runInContext (/js/dist/node/logger/core.js:0:0)\n at withCurrent (/js/dist/node/logger/core.js:0:0)\n at /js/dist/node/logger/core.js:0:0\n at runCatchFinally (/js/dist/node/util.js:0:0)\n at traced (/js/dist/node/logger/core.js:0:0)\n at /e2e/scenarios/claude-agent-sdk-instrumentation/scenario.impl.mjs:0:0\n at runHandler (/js/dist/node/instrumentation/providers/claude-agent-sdk-local-tool-spans.js:0:0)" } ], "input": { diff --git a/e2e/scenarios/claude-agent-sdk-instrumentation/__snapshots__/claude-agent-sdk-v0-auto-hook.span-tree.txt b/e2e/scenarios/claude-agent-sdk-instrumentation/__snapshots__/claude-agent-sdk-v0-auto-hook.span-tree.txt index da47a60b1..378829e92 100644 --- a/e2e/scenarios/claude-agent-sdk-instrumentation/__snapshots__/claude-agent-sdk-v0-auto-hook.span-tree.txt +++ b/e2e/scenarios/claude-agent-sdk-instrumentation/__snapshots__/claude-agent-sdk-v0-auto-hook.span-tree.txt @@ -880,7 +880,7 @@ span_tree: │ } │ error: "division by zero" │ └── calculator-local-handler-divide - │ error: "division by zero\n\nError: division by zero\n at traced.name (/e2e/scenarios/claude-agent-sdk-instrumentation/scenario.impl.mjs:0:0)\n at /js/dist/index.mjs:0:0\n at AsyncLocalStorage.run (node::0:0)\n at BraintrustContextManager.runInContext (/js/dist/index.mjs:0:0)\n at withCurrent (/js/dist/index.mjs:0:0)\n at /js/dist/index.mjs:0:0\n at runCatchFinally (/js/dist/index.mjs:0:0)\n at traced (/js/dist/index.mjs:0:0)\n at /e2e/scenarios/claude-agent-sdk-instrumentation/scenario.impl.mjs:0:0\n at runHandler (/js/dist/index.mjs:0:0)" + │ error: "division by zero\n\nError: division by zero\n at traced.name (/e2e/scenarios/claude-agent-sdk-instrumentation/scenario.impl.mjs:0:0)\n at /js/dist/node/logger/core.js:0:0\n at AsyncLocalStorage.run (node::0:0)\n at BraintrustContextManager.runInContext (/js/dist/node/logger/core.js:0:0)\n at withCurrent (/js/dist/node/logger/core.js:0:0)\n at /js/dist/node/logger/core.js:0:0\n at runCatchFinally (/js/dist/node/util.js:0:0)\n at traced (/js/dist/node/logger/core.js:0:0)\n at /e2e/scenarios/claude-agent-sdk-instrumentation/scenario.impl.mjs:0:0\n at runHandler (/js/dist/node/instrumentation/providers/claude-agent-sdk-local-tool-spans.js:0:0)" └── anthropic.messages.create [llm] input: [ { diff --git a/e2e/scenarios/claude-agent-sdk-instrumentation/__snapshots__/claude-agent-sdk-v0-latest-auto-hook.span-tree.json b/e2e/scenarios/claude-agent-sdk-instrumentation/__snapshots__/claude-agent-sdk-v0-latest-auto-hook.span-tree.json index d6336477c..8e21617ea 100644 --- a/e2e/scenarios/claude-agent-sdk-instrumentation/__snapshots__/claude-agent-sdk-v0-latest-auto-hook.span-tree.json +++ b/e2e/scenarios/claude-agent-sdk-instrumentation/__snapshots__/claude-agent-sdk-v0-latest-auto-hook.span-tree.json @@ -1022,7 +1022,7 @@ { "name": "calculator-local-handler-divide", "children": [], - "error": "division by zero\n\nError: division by zero\n at traced.name (/e2e/scenarios/claude-agent-sdk-instrumentation/scenario.impl.mjs:0:0)\n at /js/dist/index.mjs:0:0\n at AsyncLocalStorage.run (node::0:0)\n at BraintrustContextManager.runInContext (/js/dist/index.mjs:0:0)\n at withCurrent (/js/dist/index.mjs:0:0)\n at /js/dist/index.mjs:0:0\n at runCatchFinally (/js/dist/index.mjs:0:0)\n at traced (/js/dist/index.mjs:0:0)\n at /e2e/scenarios/claude-agent-sdk-instrumentation/scenario.impl.mjs:0:0\n at runHandler (/js/dist/index.mjs:0:0)" + "error": "division by zero\n\nError: division by zero\n at traced.name (/e2e/scenarios/claude-agent-sdk-instrumentation/scenario.impl.mjs:0:0)\n at /js/dist/node/logger/core.js:0:0\n at AsyncLocalStorage.run (node::0:0)\n at BraintrustContextManager.runInContext (/js/dist/node/logger/core.js:0:0)\n at withCurrent (/js/dist/node/logger/core.js:0:0)\n at /js/dist/node/logger/core.js:0:0\n at runCatchFinally (/js/dist/node/util.js:0:0)\n at traced (/js/dist/node/logger/core.js:0:0)\n at /e2e/scenarios/claude-agent-sdk-instrumentation/scenario.impl.mjs:0:0\n at runHandler (/js/dist/node/instrumentation/providers/claude-agent-sdk-local-tool-spans.js:0:0)" } ], "input": { diff --git a/e2e/scenarios/claude-agent-sdk-instrumentation/__snapshots__/claude-agent-sdk-v0-latest-auto-hook.span-tree.txt b/e2e/scenarios/claude-agent-sdk-instrumentation/__snapshots__/claude-agent-sdk-v0-latest-auto-hook.span-tree.txt index 7472de4ad..c02996c65 100644 --- a/e2e/scenarios/claude-agent-sdk-instrumentation/__snapshots__/claude-agent-sdk-v0-latest-auto-hook.span-tree.txt +++ b/e2e/scenarios/claude-agent-sdk-instrumentation/__snapshots__/claude-agent-sdk-v0-latest-auto-hook.span-tree.txt @@ -933,7 +933,7 @@ span_tree: │ } │ error: "division by zero" │ └── calculator-local-handler-divide - │ error: "division by zero\n\nError: division by zero\n at traced.name (/e2e/scenarios/claude-agent-sdk-instrumentation/scenario.impl.mjs:0:0)\n at /js/dist/index.mjs:0:0\n at AsyncLocalStorage.run (node::0:0)\n at BraintrustContextManager.runInContext (/js/dist/index.mjs:0:0)\n at withCurrent (/js/dist/index.mjs:0:0)\n at /js/dist/index.mjs:0:0\n at runCatchFinally (/js/dist/index.mjs:0:0)\n at traced (/js/dist/index.mjs:0:0)\n at /e2e/scenarios/claude-agent-sdk-instrumentation/scenario.impl.mjs:0:0\n at runHandler (/js/dist/index.mjs:0:0)" + │ error: "division by zero\n\nError: division by zero\n at traced.name (/e2e/scenarios/claude-agent-sdk-instrumentation/scenario.impl.mjs:0:0)\n at /js/dist/node/logger/core.js:0:0\n at AsyncLocalStorage.run (node::0:0)\n at BraintrustContextManager.runInContext (/js/dist/node/logger/core.js:0:0)\n at withCurrent (/js/dist/node/logger/core.js:0:0)\n at /js/dist/node/logger/core.js:0:0\n at runCatchFinally (/js/dist/node/util.js:0:0)\n at traced (/js/dist/node/logger/core.js:0:0)\n at /e2e/scenarios/claude-agent-sdk-instrumentation/scenario.impl.mjs:0:0\n at runHandler (/js/dist/node/instrumentation/providers/claude-agent-sdk-local-tool-spans.js:0:0)" └── anthropic.messages.create [llm] input: [ { diff --git a/e2e/scenarios/claude-agent-sdk-instrumentation/__snapshots__/claude-agent-sdk-v0-latest-wrapped.span-tree.json b/e2e/scenarios/claude-agent-sdk-instrumentation/__snapshots__/claude-agent-sdk-v0-latest-wrapped.span-tree.json index b2ee74b84..b6299642f 100644 --- a/e2e/scenarios/claude-agent-sdk-instrumentation/__snapshots__/claude-agent-sdk-v0-latest-wrapped.span-tree.json +++ b/e2e/scenarios/claude-agent-sdk-instrumentation/__snapshots__/claude-agent-sdk-v0-latest-wrapped.span-tree.json @@ -1022,7 +1022,7 @@ { "name": "calculator-local-handler-divide", "children": [], - "error": "division by zero\n\nError: division by zero\n at result.name (/e2e/scenarios/claude-agent-sdk-instrumentation/scenario.impl.mjs:0:0)\n at /js/dist/index.js:0:0\n at AsyncLocalStorage.run (node::0:0)\n at BraintrustContextManager.runInContext (/js/dist/index.js:0:0)\n at withCurrent (/js/dist/index.js:0:0)\n at /js/dist/index.js:0:0\n at runCatchFinally (/js/dist/index.js:0:0)\n at traced (/js/dist/index.js:0:0)\n at (/e2e/scenarios/claude-agent-sdk-instrumentation/scenario.impl.mjs:0:0)\n at runHandler (/js/dist/index.js:0:0)" + "error": "division by zero\n\nError: division by zero\n at result.name (/e2e/scenarios/claude-agent-sdk-instrumentation/scenario.impl.mjs:0:0)\n at (/js/dist/node/logger/core.js:0:0)\n at AsyncLocalStorage.run (node::0:0)\n at BraintrustContextManager.runInContext (/js/dist/node/logger/core.js:0:0)\n at withCurrent (/js/dist/node/logger/core.js:0:0)\n at (/js/dist/node/logger/core.js:0:0)\n at runCatchFinally (/js/dist/node/util.js:0:0)\n at traced (/js/dist/node/logger/core.js:0:0)\n at (/e2e/scenarios/claude-agent-sdk-instrumentation/scenario.impl.mjs:0:0)\n at runHandler (/js/dist/node/instrumentation/providers/claude-agent-sdk-local-tool-spans.js:0:0)" } ], "input": { diff --git a/e2e/scenarios/claude-agent-sdk-instrumentation/__snapshots__/claude-agent-sdk-v0-latest-wrapped.span-tree.txt b/e2e/scenarios/claude-agent-sdk-instrumentation/__snapshots__/claude-agent-sdk-v0-latest-wrapped.span-tree.txt index a0ba863a6..90a63cdd1 100644 --- a/e2e/scenarios/claude-agent-sdk-instrumentation/__snapshots__/claude-agent-sdk-v0-latest-wrapped.span-tree.txt +++ b/e2e/scenarios/claude-agent-sdk-instrumentation/__snapshots__/claude-agent-sdk-v0-latest-wrapped.span-tree.txt @@ -933,7 +933,7 @@ span_tree: │ } │ error: "division by zero" │ └── calculator-local-handler-divide - │ error: "division by zero\n\nError: division by zero\n at result.name (/e2e/scenarios/claude-agent-sdk-instrumentation/scenario.impl.mjs:0:0)\n at /js/dist/index.js:0:0\n at AsyncLocalStorage.run (node::0:0)\n at BraintrustContextManager.runInContext (/js/dist/index.js:0:0)\n at withCurrent (/js/dist/index.js:0:0)\n at /js/dist/index.js:0:0\n at runCatchFinally (/js/dist/index.js:0:0)\n at traced (/js/dist/index.js:0:0)\n at (/e2e/scenarios/claude-agent-sdk-instrumentation/scenario.impl.mjs:0:0)\n at runHandler (/js/dist/index.js:0:0)" + │ error: "division by zero\n\nError: division by zero\n at result.name (/e2e/scenarios/claude-agent-sdk-instrumentation/scenario.impl.mjs:0:0)\n at (/js/dist/node/logger/core.js:0:0)\n at AsyncLocalStorage.run (node::0:0)\n at BraintrustContextManager.runInContext (/js/dist/node/logger/core.js:0:0)\n at withCurrent (/js/dist/node/logger/core.js:0:0)\n at (/js/dist/node/logger/core.js:0:0)\n at runCatchFinally (/js/dist/node/util.js:0:0)\n at traced (/js/dist/node/logger/core.js:0:0)\n at (/e2e/scenarios/claude-agent-sdk-instrumentation/scenario.impl.mjs:0:0)\n at runHandler (/js/dist/node/instrumentation/providers/claude-agent-sdk-local-tool-spans.js:0:0)" └── anthropic.messages.create [llm] input: [ { diff --git a/e2e/scenarios/claude-agent-sdk-instrumentation/__snapshots__/claude-agent-sdk-v0-wrapped.span-tree.json b/e2e/scenarios/claude-agent-sdk-instrumentation/__snapshots__/claude-agent-sdk-v0-wrapped.span-tree.json index dfbed7125..10e14c123 100644 --- a/e2e/scenarios/claude-agent-sdk-instrumentation/__snapshots__/claude-agent-sdk-v0-wrapped.span-tree.json +++ b/e2e/scenarios/claude-agent-sdk-instrumentation/__snapshots__/claude-agent-sdk-v0-wrapped.span-tree.json @@ -969,7 +969,7 @@ { "name": "calculator-local-handler-divide", "children": [], - "error": "division by zero\n\nError: division by zero\n at result.name (/e2e/scenarios/claude-agent-sdk-instrumentation/scenario.impl.mjs:0:0)\n at /js/dist/index.js:0:0\n at AsyncLocalStorage.run (node::0:0)\n at BraintrustContextManager.runInContext (/js/dist/index.js:0:0)\n at withCurrent (/js/dist/index.js:0:0)\n at /js/dist/index.js:0:0\n at runCatchFinally (/js/dist/index.js:0:0)\n at traced (/js/dist/index.js:0:0)\n at (/e2e/scenarios/claude-agent-sdk-instrumentation/scenario.impl.mjs:0:0)\n at runHandler (/js/dist/index.js:0:0)" + "error": "division by zero\n\nError: division by zero\n at result.name (/e2e/scenarios/claude-agent-sdk-instrumentation/scenario.impl.mjs:0:0)\n at (/js/dist/node/logger/core.js:0:0)\n at AsyncLocalStorage.run (node::0:0)\n at BraintrustContextManager.runInContext (/js/dist/node/logger/core.js:0:0)\n at withCurrent (/js/dist/node/logger/core.js:0:0)\n at (/js/dist/node/logger/core.js:0:0)\n at runCatchFinally (/js/dist/node/util.js:0:0)\n at traced (/js/dist/node/logger/core.js:0:0)\n at (/e2e/scenarios/claude-agent-sdk-instrumentation/scenario.impl.mjs:0:0)\n at runHandler (/js/dist/node/instrumentation/providers/claude-agent-sdk-local-tool-spans.js:0:0)" } ], "input": { diff --git a/e2e/scenarios/claude-agent-sdk-instrumentation/__snapshots__/claude-agent-sdk-v0-wrapped.span-tree.txt b/e2e/scenarios/claude-agent-sdk-instrumentation/__snapshots__/claude-agent-sdk-v0-wrapped.span-tree.txt index c62370089..28f28e70d 100644 --- a/e2e/scenarios/claude-agent-sdk-instrumentation/__snapshots__/claude-agent-sdk-v0-wrapped.span-tree.txt +++ b/e2e/scenarios/claude-agent-sdk-instrumentation/__snapshots__/claude-agent-sdk-v0-wrapped.span-tree.txt @@ -880,7 +880,7 @@ span_tree: │ } │ error: "division by zero" │ └── calculator-local-handler-divide - │ error: "division by zero\n\nError: division by zero\n at result.name (/e2e/scenarios/claude-agent-sdk-instrumentation/scenario.impl.mjs:0:0)\n at /js/dist/index.js:0:0\n at AsyncLocalStorage.run (node::0:0)\n at BraintrustContextManager.runInContext (/js/dist/index.js:0:0)\n at withCurrent (/js/dist/index.js:0:0)\n at /js/dist/index.js:0:0\n at runCatchFinally (/js/dist/index.js:0:0)\n at traced (/js/dist/index.js:0:0)\n at (/e2e/scenarios/claude-agent-sdk-instrumentation/scenario.impl.mjs:0:0)\n at runHandler (/js/dist/index.js:0:0)" + │ error: "division by zero\n\nError: division by zero\n at result.name (/e2e/scenarios/claude-agent-sdk-instrumentation/scenario.impl.mjs:0:0)\n at (/js/dist/node/logger/core.js:0:0)\n at AsyncLocalStorage.run (node::0:0)\n at BraintrustContextManager.runInContext (/js/dist/node/logger/core.js:0:0)\n at withCurrent (/js/dist/node/logger/core.js:0:0)\n at (/js/dist/node/logger/core.js:0:0)\n at runCatchFinally (/js/dist/node/util.js:0:0)\n at traced (/js/dist/node/logger/core.js:0:0)\n at (/e2e/scenarios/claude-agent-sdk-instrumentation/scenario.impl.mjs:0:0)\n at runHandler (/js/dist/node/instrumentation/providers/claude-agent-sdk-local-tool-spans.js:0:0)" └── anthropic.messages.create [llm] input: [ { diff --git a/e2e/scenarios/claude-agent-sdk-instrumentation/assertions.ts b/e2e/scenarios/claude-agent-sdk-instrumentation/assertions.ts index b0d7cd806..49fd5ba0d 100644 --- a/e2e/scenarios/claude-agent-sdk-instrumentation/assertions.ts +++ b/e2e/scenarios/claude-agent-sdk-instrumentation/assertions.ts @@ -1,4 +1,4 @@ -import { beforeAll, describe, expect, test } from "vitest"; +import { beforeAll, describe, expect, test } from "vite-plus/test"; import type { Json } from "../../helpers/normalize"; import type { CapturedLogEvent } from "../../helpers/mock-braintrust-server"; import { resolveFileSnapshotPath } from "../../helpers/file-snapshot"; diff --git a/e2e/scenarios/claude-agent-sdk-instrumentation/scenario.test.ts b/e2e/scenarios/claude-agent-sdk-instrumentation/scenario.test.ts index a3bcbb96b..e3ff757bd 100644 --- a/e2e/scenarios/claude-agent-sdk-instrumentation/scenario.test.ts +++ b/e2e/scenarios/claude-agent-sdk-instrumentation/scenario.test.ts @@ -1,4 +1,4 @@ -import { describe } from "vitest"; +import { describe } from "vite-plus/test"; import { describeVariant } from "../../helpers/describe-variant"; import { prepareScenarioDir, diff --git a/e2e/scenarios/cloudflare-agents-instrumentation/assertions.ts b/e2e/scenarios/cloudflare-agents-instrumentation/assertions.ts index bba5b0b90..a4f6c5503 100644 --- a/e2e/scenarios/cloudflare-agents-instrumentation/assertions.ts +++ b/e2e/scenarios/cloudflare-agents-instrumentation/assertions.ts @@ -1,4 +1,4 @@ -import { beforeAll, describe, expect, test } from "vitest"; +import { beforeAll, describe, expect, test } from "vite-plus/test"; import type { CapturedLogEvent } from "../../helpers/mock-braintrust-server"; import { resolveFileSnapshotPath } from "../../helpers/file-snapshot"; import { diff --git a/e2e/scenarios/cloudflare-agents-instrumentation/scenario.test.ts b/e2e/scenarios/cloudflare-agents-instrumentation/scenario.test.ts index 09929d559..794068f05 100644 --- a/e2e/scenarios/cloudflare-agents-instrumentation/scenario.test.ts +++ b/e2e/scenarios/cloudflare-agents-instrumentation/scenario.test.ts @@ -1,4 +1,4 @@ -import { describe } from "vitest"; +import { describe } from "vite-plus/test"; import { prepareScenarioDir, readInstalledPackageVersion, @@ -34,30 +34,34 @@ const scenarios = await Promise.all( describe.concurrent("Cloudflare Agents versions", () => { for (const scenario of scenarios) { - describe.sequential(`${scenario.label} (${scenario.version})`, () => { - for (const mode of ["wrapped", "auto"] as const) { - defineCloudflareAgentsAssertions({ - name: `${mode} instrumentation`, - runScenario: async ({ runScenarioDir }) => { - await runScenarioDir({ - entry: "scenario.ts", - env: { - CLOUDFLARE_AGENTS_INSTRUMENTATION_MODE: mode, - CLOUDFLARE_AGENTS_PACKAGE_NAME: scenario.dependencyName, - }, - runContext: { - cassette: false, - variantKey: scenario.variantKey, - }, - scenarioDir, - timeoutMs: TIMEOUT_MS, - }); - }, - snapshotName: `${scenario.variantKey}-${mode}`, - testFileUrl: import.meta.url, - timeoutMs: TIMEOUT_MS, - }); - } - }); + describe( + `${scenario.label} (${scenario.version})`, + { concurrent: false }, + () => { + for (const mode of ["wrapped", "auto"] as const) { + defineCloudflareAgentsAssertions({ + name: `${mode} instrumentation`, + runScenario: async ({ runScenarioDir }) => { + await runScenarioDir({ + entry: "scenario.ts", + env: { + CLOUDFLARE_AGENTS_INSTRUMENTATION_MODE: mode, + CLOUDFLARE_AGENTS_PACKAGE_NAME: scenario.dependencyName, + }, + runContext: { + cassette: false, + variantKey: scenario.variantKey, + }, + scenarioDir, + timeoutMs: TIMEOUT_MS, + }); + }, + snapshotName: `${scenario.variantKey}-${mode}`, + testFileUrl: import.meta.url, + timeoutMs: TIMEOUT_MS, + }); + } + }, + ); } }); diff --git a/e2e/scenarios/cloudflare-agents-instrumentation/vite.config.ts b/e2e/scenarios/cloudflare-agents-instrumentation/vite.config.ts index ca68896e5..0e75a3319 100644 --- a/e2e/scenarios/cloudflare-agents-instrumentation/vite.config.ts +++ b/e2e/scenarios/cloudflare-agents-instrumentation/vite.config.ts @@ -7,9 +7,7 @@ const agentsPackageName = const instrumentationMode = process.env.CLOUDFLARE_AGENTS_INSTRUMENTATION_MODE ?? "auto"; const braintrustPlugins = - instrumentationMode === "auto" - ? braintrustVitePlugin({ useDiagnosticChannelCompatShim: true }) - : []; + instrumentationMode === "auto" ? braintrustVitePlugin({ browser: true }) : []; export default defineConfig({ define: { diff --git a/e2e/scenarios/cloudflare-agents-instrumentation/worker.ts b/e2e/scenarios/cloudflare-agents-instrumentation/worker.ts index f02b0c156..c12dc7923 100644 --- a/e2e/scenarios/cloudflare-agents-instrumentation/worker.ts +++ b/e2e/scenarios/cloudflare-agents-instrumentation/worker.ts @@ -1,3 +1,4 @@ +// Bundler transforms and runtime consumers are enabled independently. import { Agent, getAgentByName } from "agents"; import { initLogger, wrapCloudflareAgent } from "braintrust/workerd"; @@ -18,6 +19,9 @@ type StoredRun = { status: "aborted" | "completed" | "error"; }; +if (__CLOUDFLARE_AGENTS_INSTRUMENTATION_MODE__ === "auto") + await import("braintrust/auto"); + const BaseParentAgent = __CLOUDFLARE_AGENTS_INSTRUMENTATION_MODE__ === "wrapped" ? wrapCloudflareAgent(Agent) diff --git a/e2e/scenarios/cloudflare-ai-chat-instrumentation/assertions.ts b/e2e/scenarios/cloudflare-ai-chat-instrumentation/assertions.ts index ac5440af4..0db7d8b84 100644 --- a/e2e/scenarios/cloudflare-ai-chat-instrumentation/assertions.ts +++ b/e2e/scenarios/cloudflare-ai-chat-instrumentation/assertions.ts @@ -1,4 +1,4 @@ -import { beforeAll, describe, expect, test } from "vitest"; +import { beforeAll, describe, expect, test } from "vite-plus/test"; import type { CapturedLogEvent } from "../../helpers/mock-braintrust-server"; import { resolveFileSnapshotPath } from "../../helpers/file-snapshot"; import { diff --git a/e2e/scenarios/cloudflare-ai-chat-instrumentation/scenario.test.ts b/e2e/scenarios/cloudflare-ai-chat-instrumentation/scenario.test.ts index 2da8bc9b6..25e879d1d 100644 --- a/e2e/scenarios/cloudflare-ai-chat-instrumentation/scenario.test.ts +++ b/e2e/scenarios/cloudflare-ai-chat-instrumentation/scenario.test.ts @@ -1,4 +1,4 @@ -import { describe } from "vitest"; +import { describe } from "vite-plus/test"; import { prepareScenarioDir, readInstalledPackageVersion, @@ -30,35 +30,39 @@ const scenarios = await Promise.all( })), ); -describe.sequential("Cloudflare AI Chat variants", () => { +describe("Cloudflare AI Chat variants", { concurrent: false }, () => { for (const scenario of scenarios) { - describe.sequential(`@cloudflare/ai-chat ${scenario.version}`, () => { - for (const mode of ["manual", "auto"] as const) { - defineCloudflareAIChatAssertions({ - name: `${mode} instrumentation`, - runScenario: async ({ runNodeScenarioDir }) => { - await runNodeScenarioDir({ - entry: "scenario.mjs", - env: { - CLOUDFLARE_AI_CHAT_INSTRUMENTATION_MODE: mode, - CLOUDFLARE_AI_CHAT_PACKAGE_NAME: scenario.dependencyName, - }, - runContext: { - cassette: { + describe( + `@cloudflare/ai-chat ${scenario.version}`, + { concurrent: false }, + () => { + for (const mode of ["manual", "auto"] as const) { + defineCloudflareAIChatAssertions({ + name: `${mode} instrumentation`, + runScenario: async ({ runNodeScenarioDir }) => { + await runNodeScenarioDir({ + entry: "scenario.mjs", + env: { + CLOUDFLARE_AI_CHAT_INSTRUMENTATION_MODE: mode, + CLOUDFLARE_AI_CHAT_PACKAGE_NAME: scenario.dependencyName, + }, + runContext: { + cassette: { + variantKey: scenario.variantKey, + }, + originalScenarioDir, variantKey: scenario.variantKey, }, - originalScenarioDir, - variantKey: scenario.variantKey, - }, - scenarioDir, - timeoutMs: TIMEOUT_MS, - }); - }, - snapshotName: `${scenario.variantKey}-${mode}`, - testFileUrl: import.meta.url, - timeoutMs: TIMEOUT_MS, - }); - } - }); + scenarioDir, + timeoutMs: TIMEOUT_MS, + }); + }, + snapshotName: `${scenario.variantKey}-${mode}`, + testFileUrl: import.meta.url, + timeoutMs: TIMEOUT_MS, + }); + } + }, + ); } }); diff --git a/e2e/scenarios/cloudflare-ai-chat-instrumentation/vite.config.ts b/e2e/scenarios/cloudflare-ai-chat-instrumentation/vite.config.ts index 3952e5fd6..1c131a62d 100644 --- a/e2e/scenarios/cloudflare-ai-chat-instrumentation/vite.config.ts +++ b/e2e/scenarios/cloudflare-ai-chat-instrumentation/vite.config.ts @@ -25,9 +25,7 @@ export default defineConfig({ __OPENAI_BASE_URL__: JSON.stringify(process.env.OPENAI_BASE_URL), }, plugins: [ - ...(mode === "auto" - ? [braintrustVitePlugin({ useDiagnosticChannelCompatShim: true })] - : []), + ...(mode === "auto" ? [braintrustVitePlugin({ browser: true })] : []), cloudflare({ configPath: "./wrangler.toml", inspectorPort: 0 }), ], resolve: { diff --git a/e2e/scenarios/cloudflare-ai-chat-instrumentation/worker.ts b/e2e/scenarios/cloudflare-ai-chat-instrumentation/worker.ts index 09030ea12..5babc9c0b 100644 --- a/e2e/scenarios/cloudflare-ai-chat-instrumentation/worker.ts +++ b/e2e/scenarios/cloudflare-ai-chat-instrumentation/worker.ts @@ -1,3 +1,4 @@ +// Bundler transforms and runtime consumers are enabled independently. import * as aiNamespace from "ai"; import * as aiChatNamespace from "@cloudflare/ai-chat"; import { createOpenAI } from "@ai-sdk/openai"; @@ -13,6 +14,8 @@ declare const __CLOUDFLARE_AI_CHAT_MODE__: "auto" | "manual"; declare const __OPENAI_API_KEY__: string; declare const __OPENAI_BASE_URL__: string; +if (__CLOUDFLARE_AI_CHAT_MODE__ === "auto") await import("braintrust/auto"); + const SCENARIO_NAME = "cloudflare-ai-chat-instrumentation"; const SUCCESS_ROOT_NAME = "cloudflare-ai-chat-success-root"; const ERROR_ROOT_NAME = "cloudflare-ai-chat-error-root"; diff --git a/e2e/scenarios/cloudflare-think-instrumentation/assertions.ts b/e2e/scenarios/cloudflare-think-instrumentation/assertions.ts index 8452a8557..597fc0ab9 100644 --- a/e2e/scenarios/cloudflare-think-instrumentation/assertions.ts +++ b/e2e/scenarios/cloudflare-think-instrumentation/assertions.ts @@ -1,4 +1,4 @@ -import { beforeAll, describe, expect, test } from "vitest"; +import { beforeAll, describe, expect, test } from "vite-plus/test"; import type { CapturedLogEvent } from "../../helpers/mock-braintrust-server"; import { resolveFileSnapshotPath } from "../../helpers/file-snapshot"; import { diff --git a/e2e/scenarios/cloudflare-think-instrumentation/scenario.test.ts b/e2e/scenarios/cloudflare-think-instrumentation/scenario.test.ts index f088dcd3f..71bcb18c0 100644 --- a/e2e/scenarios/cloudflare-think-instrumentation/scenario.test.ts +++ b/e2e/scenarios/cloudflare-think-instrumentation/scenario.test.ts @@ -1,6 +1,6 @@ import { promises as fs } from "node:fs"; import path from "node:path"; -import { describe } from "vitest"; +import { describe } from "vite-plus/test"; import { prepareScenarioDir, readInstalledPackageVersion, @@ -56,36 +56,40 @@ for (const [aiMajor, minor] of [ const describeVariants = process.env.BRAINTRUST_E2E_CASSETTE_MODE === "record" || process.env.BRAINTRUST_E2E_CASSETTE_MODE === "record-missing" - ? describe.sequential + ? describe : describe.concurrent; describeVariants("Cloudflare Think variants", () => { for (const variant of variants) { - describe.sequential(`${variant.label} (${variant.version})`, () => { - for (const mode of ["manual", "auto"] as const) { - defineCloudflareThinkAssertions({ - usesV7Telemetry: variant.aiMajor === 7 && mode === "auto", - name: `${mode} Workers/Vite instrumentation`, - runScenario: async ({ runScenarioDir }) => { - await runScenarioDir({ - env: { - CLOUDFLARE_THINK_INSTRUMENTATION: mode, - CLOUDFLARE_THINK_PACKAGE_NAME: variant.dependencyName, - CLOUDFLARE_AGENTS_PACKAGE_NAME: variant.agentsPackageName, - }, - runContext: { - originalScenarioDir, - variantKey: `${variant.variantKey}-${mode}`, - }, - scenarioDir: variant.scenarioDir, - timeoutMs: TIMEOUT_MS, - }); - }, - snapshotName: `${variant.variantKey}-${mode}`, - testFileUrl: import.meta.url, - timeoutMs: TIMEOUT_MS, - }); - } - }); + describe( + `${variant.label} (${variant.version})`, + { concurrent: false }, + () => { + for (const mode of ["manual", "auto"] as const) { + defineCloudflareThinkAssertions({ + usesV7Telemetry: variant.aiMajor === 7 && mode === "auto", + name: `${mode} Workers/Vite instrumentation`, + runScenario: async ({ runScenarioDir }) => { + await runScenarioDir({ + env: { + CLOUDFLARE_THINK_INSTRUMENTATION: mode, + CLOUDFLARE_THINK_PACKAGE_NAME: variant.dependencyName, + CLOUDFLARE_AGENTS_PACKAGE_NAME: variant.agentsPackageName, + }, + runContext: { + originalScenarioDir, + variantKey: `${variant.variantKey}-${mode}`, + }, + scenarioDir: variant.scenarioDir, + timeoutMs: TIMEOUT_MS, + }); + }, + snapshotName: `${variant.variantKey}-${mode}`, + testFileUrl: import.meta.url, + timeoutMs: TIMEOUT_MS, + }); + } + }, + ); } }); diff --git a/e2e/scenarios/cloudflare-think-instrumentation/template/vite.config.ts b/e2e/scenarios/cloudflare-think-instrumentation/template/vite.config.ts index 6b53df1ac..6d572b90c 100644 --- a/e2e/scenarios/cloudflare-think-instrumentation/template/vite.config.ts +++ b/e2e/scenarios/cloudflare-think-instrumentation/template/vite.config.ts @@ -13,9 +13,7 @@ export default defineConfig({ ), }, plugins: [ - ...(manual - ? [] - : [braintrustVitePlugin({ useDiagnosticChannelCompatShim: true })]), + ...(manual ? [] : [braintrustVitePlugin({ browser: true })]), cloudflare({ inspectorPort: 0 }), ], resolve: { diff --git a/e2e/scenarios/cohere-instrumentation/assertions.ts b/e2e/scenarios/cohere-instrumentation/assertions.ts index 87668d3a6..d51579fbe 100644 --- a/e2e/scenarios/cohere-instrumentation/assertions.ts +++ b/e2e/scenarios/cohere-instrumentation/assertions.ts @@ -1,4 +1,4 @@ -import { beforeAll, describe, expect, test } from "vitest"; +import { beforeAll, describe, expect, test } from "vite-plus/test"; import type { CapturedLogEvent } from "../../helpers/mock-braintrust-server"; import { resolveFileSnapshotPath } from "../../helpers/file-snapshot"; import { diff --git a/e2e/scenarios/cohere-instrumentation/scenario.test.ts b/e2e/scenarios/cohere-instrumentation/scenario.test.ts index 76a01094e..63f8f7106 100644 --- a/e2e/scenarios/cohere-instrumentation/scenario.test.ts +++ b/e2e/scenarios/cohere-instrumentation/scenario.test.ts @@ -1,4 +1,4 @@ -import { describe } from "vitest"; +import { describe } from "vite-plus/test"; import { describeVariant } from "../../helpers/describe-variant"; import { prepareScenarioDir, diff --git a/e2e/scenarios/cursor-sdk-instrumentation/assertions.ts b/e2e/scenarios/cursor-sdk-instrumentation/assertions.ts index 94e5427ce..55d642ac6 100644 --- a/e2e/scenarios/cursor-sdk-instrumentation/assertions.ts +++ b/e2e/scenarios/cursor-sdk-instrumentation/assertions.ts @@ -1,4 +1,4 @@ -import { beforeAll, describe, expect, test } from "vitest"; +import { beforeAll, describe, expect, test } from "vite-plus/test"; import type { CapturedLogEvent } from "../../helpers/mock-braintrust-server"; import { resolveFileSnapshotPath } from "../../helpers/file-snapshot"; import { diff --git a/e2e/scenarios/cursor-sdk-instrumentation/scenario.test.ts b/e2e/scenarios/cursor-sdk-instrumentation/scenario.test.ts index 2e05d810e..cb72b2f55 100644 --- a/e2e/scenarios/cursor-sdk-instrumentation/scenario.test.ts +++ b/e2e/scenarios/cursor-sdk-instrumentation/scenario.test.ts @@ -1,4 +1,4 @@ -import { describe } from "vitest"; +import { describe } from "vite-plus/test"; import { prepareScenarioDir, readInstalledPackageVersion, @@ -40,45 +40,53 @@ const cursorSDKScenarios = await Promise.all( describe.concurrent("variants", () => { for (const cursorSDKScenario of cursorSDKScenarios) { - describe.sequential(`cursor sdk ${cursorSDKScenario.version}`, () => { - defineCursorSDKInstrumentationAssertions({ - name: "wrapped instrumentation", - runScenario: async ({ runScenarioDir }) => { - await runScenarioDir({ - entry: cursorSDKScenario.wrapperEntry, - env: { CURSOR_SDK_PACKAGE_NAME: cursorSDKScenario.dependencyName }, - runContext: { - variantKey: cursorSDKScenario.variantKey, - originalScenarioDir, - }, - scenarioDir, - timeoutMs: TIMEOUT_MS, - }); - }, - snapshotName: cursorSDKScenario.wrapperSnapshotName, - testFileUrl: import.meta.url, - timeoutMs: TIMEOUT_MS, - }); + describe( + `cursor sdk ${cursorSDKScenario.version}`, + { concurrent: false }, + () => { + defineCursorSDKInstrumentationAssertions({ + name: "wrapped instrumentation", + runScenario: async ({ runScenarioDir }) => { + await runScenarioDir({ + entry: cursorSDKScenario.wrapperEntry, + env: { + CURSOR_SDK_PACKAGE_NAME: cursorSDKScenario.dependencyName, + }, + runContext: { + variantKey: cursorSDKScenario.variantKey, + originalScenarioDir, + }, + scenarioDir, + timeoutMs: TIMEOUT_MS, + }); + }, + snapshotName: cursorSDKScenario.wrapperSnapshotName, + testFileUrl: import.meta.url, + timeoutMs: TIMEOUT_MS, + }); - defineCursorSDKInstrumentationAssertions({ - name: "auto-hook instrumentation", - runScenario: async ({ runNodeScenarioDir }) => { - await runNodeScenarioDir({ - entry: cursorSDKScenario.autoEntry, - env: { CURSOR_SDK_PACKAGE_NAME: cursorSDKScenario.dependencyName }, - nodeArgs: ["--import", "braintrust/hook.mjs"], - runContext: { - variantKey: cursorSDKScenario.variantKey, - originalScenarioDir, - }, - scenarioDir, - timeoutMs: TIMEOUT_MS, - }); - }, - snapshotName: cursorSDKScenario.autoSnapshotName, - testFileUrl: import.meta.url, - timeoutMs: TIMEOUT_MS, - }); - }); + defineCursorSDKInstrumentationAssertions({ + name: "auto-hook instrumentation", + runScenario: async ({ runNodeScenarioDir }) => { + await runNodeScenarioDir({ + entry: cursorSDKScenario.autoEntry, + env: { + CURSOR_SDK_PACKAGE_NAME: cursorSDKScenario.dependencyName, + }, + nodeArgs: ["--import", "braintrust/hook.mjs"], + runContext: { + variantKey: cursorSDKScenario.variantKey, + originalScenarioDir, + }, + scenarioDir, + timeoutMs: TIMEOUT_MS, + }); + }, + snapshotName: cursorSDKScenario.autoSnapshotName, + testFileUrl: import.meta.url, + timeoutMs: TIMEOUT_MS, + }); + }, + ); } }); diff --git a/e2e/scenarios/deepseek-harness-instrumentation/scenario.test.ts b/e2e/scenarios/deepseek-harness-instrumentation/scenario.test.ts index 741b1cb58..4ba8f173f 100644 --- a/e2e/scenarios/deepseek-harness-instrumentation/scenario.test.ts +++ b/e2e/scenarios/deepseek-harness-instrumentation/scenario.test.ts @@ -1,4 +1,4 @@ -import { beforeAll, describe, expect, test } from "vitest"; +import { beforeAll, describe, expect, test } from "vite-plus/test"; import { resolveFileSnapshotPath } from "../../helpers/file-snapshot"; import type { CapturedLogEvent } from "../../helpers/mock-braintrust-server"; import { @@ -41,124 +41,130 @@ const scenarios = await Promise.all( ); const TIMEOUT_MS = 120_000; -describe.sequential("DeepSeek Harness instrumentation variants", () => { - for (const scenario of scenarios) { - describe(`${scenario.label} (${scenario.version})`, () => { - let events: CapturedLogEvent[] = []; +describe( + "DeepSeek Harness instrumentation variants", + { concurrent: false }, + () => { + for (const scenario of scenarios) { + describe(`${scenario.label} (${scenario.version})`, () => { + let events: CapturedLogEvent[] = []; - beforeAll(async () => { - await withScenarioHarness( - async ({ events: harnessEvents, runScenarioDir }) => { - await runScenarioDir({ - entry: "scenario.ts", - env: { - DEEPSEEK_HARNESS_PACKAGE: scenario.dependencyName, - NODE_ENV: "development", - }, - runContext: { - originalScenarioDir, - variantKey: scenario.variantKey, - }, - scenarioDir, - timeoutMs: TIMEOUT_MS, - }); - events = harnessEvents(); - }, - ); - }, TIMEOUT_MS); + beforeAll(async () => { + await withScenarioHarness( + async ({ events: harnessEvents, runScenarioDir }) => { + await runScenarioDir({ + entry: "scenario.ts", + env: { + DEEPSEEK_HARNESS_PACKAGE: scenario.dependencyName, + NODE_ENV: "development", + }, + runContext: { + originalScenarioDir, + variantKey: scenario.variantKey, + }, + scenarioDir, + timeoutMs: TIMEOUT_MS, + }); + events = harnessEvents(); + }, + ); + }, TIMEOUT_MS); + + test("captures each user turn as an independent trace", async () => { + const turns = findAllSpans(events, "deepseek_harness.turn"); + const first = turns.find( + (turn) => + Array.isArray(turn.input) && + turn.input[0]?.content === + "Call lookup exactly once with query `first`. Then reply exactly: First Harness answer.", + ); + const second = turns.find( + (turn) => + Array.isArray(turn.input) && + turn.input[0]?.content === + "Call lookup exactly once with query `second`. Then reply exactly: Second Harness answer.", + ); + + expect(turns).toHaveLength(2); + expect(first?.span.type).toBe("task"); + expect(second?.span.type).toBe("task"); + expect(first?.span.parentIds).toEqual([]); + expect(second?.span.parentIds).toEqual([]); + expect(first?.span.rootId).not.toEqual(second?.span.rootId); + expect(first?.metadata?.["deepseek_harness.session_id"]).toEqual( + second?.metadata?.["deepseek_harness.session_id"], + ); + expect(first?.metadata?.scenario).toBe( + "deepseek-harness-instrumentation", + ); + expect(first?.metadata?.testRunId).toMatch(/^e2e-/); + expect(second?.metadata?.scenario).toBe( + "deepseek-harness-instrumentation", + ); + expect(second?.metadata?.testRunId).toBe(first?.metadata?.testRunId); + expect(first?.output).toBe("First Harness answer."); + expect(second?.output).toBe("Second Harness answer."); - test("captures each user turn as an independent trace", async () => { - const turns = findAllSpans(events, "deepseek_harness.turn"); - const first = turns.find( - (turn) => - Array.isArray(turn.input) && - turn.input[0]?.content === - "Call lookup exactly once with query `first`. Then reply exactly: First Harness answer.", - ); - const second = turns.find( - (turn) => - Array.isArray(turn.input) && - turn.input[0]?.content === - "Call lookup exactly once with query `second`. Then reply exactly: Second Harness answer.", - ); + for (const root of [first, second]) { + const steps = findChildSpans( + events, + "deepseek_harness.step", + root?.span.id, + ); + const lookup = findLatestChildSpan(events, "lookup", root?.span.id); + expect(steps).toHaveLength(2); + expect(steps.map((step) => step.span.type)).toEqual(["llm", "llm"]); + expect(steps.map((step) => step.metadata?.provider)).toEqual([ + "openai", + "openai", + ]); + expect(steps.map((step) => step.metadata?.model)).toEqual([ + "gpt-5.6-luna", + "gpt-5.6-luna", + ]); + expect(steps[0]?.output).toMatchObject([ + { + finish_reason: "tool_calls", + message: { + tool_calls: [ + { function: { name: "lookup" }, type: "function" }, + ], + }, + }, + ]); + expect(lookup?.span.type).toBe("tool"); + expect(lookup?.span.parentIds).toEqual([root?.span.id]); + expect(lookup?.output).toEqual( + expect.stringContaining("Lookup result"), + ); + expect(root?.metrics?.tokens).toEqual(expect.any(Number)); + expect(root?.metrics?.prompt_tokens).toEqual(expect.any(Number)); + expect(root?.metrics?.completion_tokens).toEqual( + expect.any(Number), + ); + } - expect(turns).toHaveLength(2); - expect(first?.span.type).toBe("task"); - expect(second?.span.type).toBe("task"); - expect(first?.span.parentIds).toEqual([]); - expect(second?.span.parentIds).toEqual([]); - expect(first?.span.rootId).not.toEqual(second?.span.rootId); - expect(first?.metadata?.["deepseek_harness.session_id"]).toEqual( - second?.metadata?.["deepseek_harness.session_id"], - ); - expect(first?.metadata?.scenario).toBe( - "deepseek-harness-instrumentation", - ); - expect(first?.metadata?.testRunId).toMatch(/^e2e-/); - expect(second?.metadata?.scenario).toBe( - "deepseek-harness-instrumentation", - ); - expect(second?.metadata?.testRunId).toBe(first?.metadata?.testRunId); - expect(first?.output).toBe("First Harness answer."); - expect(second?.output).toBe("Second Harness answer."); + for (const event of events) { + expect(spanInstrumentationName(event)).toBe("deepseek-harness"); + } - for (const root of [first, second]) { - const steps = findChildSpans( + await matchSpanTreeSnapshot( events, - "deepseek_harness.step", - root?.span.id, - ); - const lookup = findLatestChildSpan(events, "lookup", root?.span.id); - expect(steps).toHaveLength(2); - expect(steps.map((step) => step.span.type)).toEqual(["llm", "llm"]); - expect(steps.map((step) => step.metadata?.provider)).toEqual([ - "openai", - "openai", - ]); - expect(steps.map((step) => step.metadata?.model)).toEqual([ - "gpt-5.6-luna", - "gpt-5.6-luna", - ]); - expect(steps[0]?.output).toMatchObject([ + resolveFileSnapshotPath( + import.meta.url, + `${scenario.variantKey}.span-tree.json`, + ), { - finish_reason: "tool_calls", - message: { - tool_calls: [ - { function: { name: "lookup" }, type: "function" }, + normalize: { + additionalProviderIdKeys: [ + "deepseek_harness.call_id", + "deepseek_harness.session_id", ], }, }, - ]); - expect(lookup?.span.type).toBe("tool"); - expect(lookup?.span.parentIds).toEqual([root?.span.id]); - expect(lookup?.output).toEqual( - expect.stringContaining("Lookup result"), ); - expect(root?.metrics?.tokens).toEqual(expect.any(Number)); - expect(root?.metrics?.prompt_tokens).toEqual(expect.any(Number)); - expect(root?.metrics?.completion_tokens).toEqual(expect.any(Number)); - } - - for (const event of events) { - expect(spanInstrumentationName(event)).toBe("deepseek-harness"); - } - - await matchSpanTreeSnapshot( - events, - resolveFileSnapshotPath( - import.meta.url, - `${scenario.variantKey}.span-tree.json`, - ), - { - normalize: { - additionalProviderIdKeys: [ - "deepseek_harness.call_id", - "deepseek_harness.session_id", - ], - }, - }, - ); + }); }); - }); - } -}); + } + }, +); diff --git a/e2e/scenarios/deno-browser/__snapshots__/request-flow.json b/e2e/scenarios/deno-browser/__snapshots__/request-flow.json index f8eea5a3f..ecb486024 100644 --- a/e2e/scenarios/deno-browser/__snapshots__/request-flow.json +++ b/e2e/scenarios/deno-browser/__snapshots__/request-flow.json @@ -27,7 +27,11 @@ "api_version": 2, "rows": [ { - "context": {}, + "context": { + "caller_filename": "ext:cli/40_test.js", + "caller_functionname": "innerWrapped", + "caller_lineno": 0 + }, "created": "", "expected": "Paris", "id": "", @@ -45,16 +49,20 @@ }, "output": "Paris", "project_id": "", - "root_span_id": "", + "root_span_id": "", "span_attributes": { "exec_counter": 0, "name": "deno browser basic span", "type": "task" }, - "span_id": "" + "span_id": "" }, { - "context": {}, + "context": { + "caller_filename": "ext:cli/40_test.js", + "caller_functionname": "innerWrapped", + "caller_lineno": 0 + }, "created": "", "id": "", "input": { @@ -89,7 +97,11 @@ "span_id": "" }, { - "context": {}, + "context": { + "caller_filename": "ext:cli/40_test.js", + "caller_functionname": "innerWrapped", + "caller_lineno": 0 + }, "created": "", "id": "", "input": { @@ -120,7 +132,11 @@ "span_id": "" }, { - "context": {}, + "context": { + "caller_filename": "/e2e/scenarios/deno-browser/runner.case.ts", + "caller_functionname": "braintrust.traced.name", + "caller_lineno": 0 + }, "created": "", "id": "", "input": { @@ -142,18 +158,24 @@ "phase": "child" }, "project_id": "", - "root_span_id": "", + "root_span_id": "", "span_attributes": { "exec_counter": 3, - "name": "deno browser child span", - "type": "task" + "name": "deno browser child span" }, - "span_id": "" + "span_id": "", + "span_parents": [ + "" + ] }, { - "context": {}, + "context": { + "caller_filename": "ext:cli/40_test.js", + "caller_functionname": "async innerWrapped", + "caller_lineno": 0 + }, "created": "", - "id": "", + "id": "", "log_id": "g", "metadata": { "case": "nested-parent", @@ -165,18 +187,22 @@ "start": 0 }, "project_id": "", - "root_span_id": "", + "root_span_id": "", "span_attributes": { "exec_counter": 4, "name": "deno browser nested parent span", "type": "task" }, - "span_id": "" + "span_id": "" }, { - "context": {}, + "context": { + "caller_filename": "/e2e/scenarios/deno-browser/runner.case.ts", + "caller_functionname": "braintrust.traced.name", + "caller_lineno": 0 + }, "created": "", - "id": "", + "id": "", "log_id": "g", "metadata": { "case": "nested-child", @@ -188,18 +214,24 @@ "start": 0 }, "project_id": "", - "root_span_id": "", + "root_span_id": "", "span_attributes": { "exec_counter": 5, - "name": "deno browser nested child span", - "type": "task" + "name": "deno browser nested child span" }, - "span_id": "" + "span_id": "", + "span_parents": [ + "" + ] }, { - "context": {}, + "context": { + "caller_filename": "/e2e/scenarios/deno-browser/runner.case.ts", + "caller_functionname": "braintrust.traced.name", + "caller_lineno": 0 + }, "created": "", - "id": "", + "id": "", "log_id": "g", "metadata": { "case": "nested-grandchild", @@ -210,19 +242,28 @@ "end": 0, "start": 0 }, + "output": { + "depth": 3 + }, "project_id": "", - "root_span_id": "", + "root_span_id": "", "span_attributes": { "exec_counter": 6, - "name": "deno browser nested grandchild span", - "type": "task" + "name": "deno browser nested grandchild span" }, - "span_id": "" + "span_id": "", + "span_parents": [ + "" + ] }, { - "context": {}, + "context": { + "caller_filename": "ext:cli/40_test.js", + "caller_functionname": "async innerWrapped", + "caller_lineno": 0 + }, "created": "", - "id": "", + "id": "", "log_id": "g", "metadata": { "case": "current-span", @@ -233,14 +274,17 @@ "end": 0, "start": 0 }, + "output": { + "observedSpanId": "" + }, "project_id": "", - "root_span_id": "", + "root_span_id": "", "span_attributes": { "exec_counter": 7, "name": "deno browser current span", "type": "task" }, - "span_id": "" + "span_id": "" } ] }, @@ -251,7 +295,11 @@ "api_version": 2, "rows": [ { - "context": {}, + "context": { + "caller_filename": "ext:cli/40_test.js", + "caller_functionname": "innerWrapped", + "caller_lineno": 0 + }, "created": "", "expected": "Paris", "id": "", @@ -269,16 +317,20 @@ }, "output": "Paris", "project_id": "", - "root_span_id": "", + "root_span_id": "", "span_attributes": { "exec_counter": 0, "name": "deno browser basic span", "type": "task" }, - "span_id": "" + "span_id": "" }, { - "context": {}, + "context": { + "caller_filename": "ext:cli/40_test.js", + "caller_functionname": "innerWrapped", + "caller_lineno": 0 + }, "created": "", "id": "", "input": { @@ -313,7 +365,11 @@ "span_id": "" }, { - "context": {}, + "context": { + "caller_filename": "ext:cli/40_test.js", + "caller_functionname": "innerWrapped", + "caller_lineno": 0 + }, "created": "", "id": "", "input": { @@ -344,7 +400,11 @@ "span_id": "" }, { - "context": {}, + "context": { + "caller_filename": "/e2e/scenarios/deno-browser/runner.case.ts", + "caller_functionname": "braintrust.traced.name", + "caller_lineno": 0 + }, "created": "", "id": "", "input": { @@ -366,18 +426,24 @@ "phase": "child" }, "project_id": "", - "root_span_id": "", + "root_span_id": "", "span_attributes": { "exec_counter": 3, - "name": "deno browser child span", - "type": "task" + "name": "deno browser child span" }, - "span_id": "" + "span_id": "", + "span_parents": [ + "" + ] }, { - "context": {}, + "context": { + "caller_filename": "ext:cli/40_test.js", + "caller_functionname": "async innerWrapped", + "caller_lineno": 0 + }, "created": "", - "id": "", + "id": "", "log_id": "g", "metadata": { "case": "nested-parent", @@ -389,18 +455,22 @@ "start": 0 }, "project_id": "", - "root_span_id": "", + "root_span_id": "", "span_attributes": { "exec_counter": 4, "name": "deno browser nested parent span", "type": "task" }, - "span_id": "" + "span_id": "" }, { - "context": {}, + "context": { + "caller_filename": "/e2e/scenarios/deno-browser/runner.case.ts", + "caller_functionname": "braintrust.traced.name", + "caller_lineno": 0 + }, "created": "", - "id": "", + "id": "", "log_id": "g", "metadata": { "case": "nested-child", @@ -412,18 +482,24 @@ "start": 0 }, "project_id": "", - "root_span_id": "", + "root_span_id": "", "span_attributes": { "exec_counter": 5, - "name": "deno browser nested child span", - "type": "task" + "name": "deno browser nested child span" }, - "span_id": "" + "span_id": "", + "span_parents": [ + "" + ] }, { - "context": {}, + "context": { + "caller_filename": "/e2e/scenarios/deno-browser/runner.case.ts", + "caller_functionname": "braintrust.traced.name", + "caller_lineno": 0 + }, "created": "", - "id": "", + "id": "", "log_id": "g", "metadata": { "case": "nested-grandchild", @@ -434,19 +510,28 @@ "end": 0, "start": 0 }, + "output": { + "depth": 3 + }, "project_id": "", - "root_span_id": "", + "root_span_id": "", "span_attributes": { "exec_counter": 6, - "name": "deno browser nested grandchild span", - "type": "task" + "name": "deno browser nested grandchild span" }, - "span_id": "" + "span_id": "", + "span_parents": [ + "" + ] }, { - "context": {}, + "context": { + "caller_filename": "ext:cli/40_test.js", + "caller_functionname": "async innerWrapped", + "caller_lineno": 0 + }, "created": "", - "id": "", + "id": "", "log_id": "g", "metadata": { "case": "current-span", @@ -457,14 +542,17 @@ "end": 0, "start": 0 }, + "output": { + "observedSpanId": "" + }, "project_id": "", - "root_span_id": "", + "root_span_id": "", "span_attributes": { "exec_counter": 7, "name": "deno browser current span", "type": "task" }, - "span_id": "" + "span_id": "" } ] } diff --git a/e2e/scenarios/deno-browser/__snapshots__/span-tree.json b/e2e/scenarios/deno-browser/__snapshots__/span-tree.json index 9461d0edb..d5ffa8e80 100644 --- a/e2e/scenarios/deno-browser/__snapshots__/span-tree.json +++ b/e2e/scenarios/deno-browser/__snapshots__/span-tree.json @@ -39,7 +39,25 @@ { "name": "deno browser parent span", "type": "task", - "children": [], + "children": [ + { + "name": "deno browser child span", + "children": [], + "input": { + "step": "child", + "testRunId": "" + }, + "output": { + "ok": true, + "phase": "child" + }, + "metadata": { + "case": "child-span", + "scenario": "deno-browser", + "testRunId": "" + } + } + ], "input": { "phase": "parent", "testRunId": "" @@ -54,58 +72,46 @@ "testRunId": "" } }, - { - "name": "deno browser child span", - "type": "task", - "children": [], - "input": { - "step": "child", - "testRunId": "" - }, - "output": { - "ok": true, - "phase": "child" - }, - "metadata": { - "case": "child-span", - "scenario": "deno-browser", - "testRunId": "" - } - }, { "name": "deno browser nested parent span", "type": "task", - "children": [], + "children": [ + { + "name": "deno browser nested child span", + "children": [ + { + "name": "deno browser nested grandchild span", + "children": [], + "output": { + "depth": 3 + }, + "metadata": { + "case": "nested-grandchild", + "scenario": "deno-browser", + "testRunId": "" + } + } + ], + "metadata": { + "case": "nested-child", + "scenario": "deno-browser", + "testRunId": "" + } + } + ], "metadata": { "case": "nested-parent", "scenario": "deno-browser", "testRunId": "" } }, - { - "name": "deno browser nested child span", - "type": "task", - "children": [], - "metadata": { - "case": "nested-child", - "scenario": "deno-browser", - "testRunId": "" - } - }, - { - "name": "deno browser nested grandchild span", - "type": "task", - "children": [], - "metadata": { - "case": "nested-grandchild", - "scenario": "deno-browser", - "testRunId": "" - } - }, { "name": "deno browser current span", "type": "task", "children": [], + "output": { + "observedSpanId": "" + }, "metadata": { "case": "current-span", "scenario": "deno-browser", diff --git a/e2e/scenarios/deno-browser/__snapshots__/span-tree.txt b/e2e/scenarios/deno-browser/__snapshots__/span-tree.txt index 9af1c12e1..3f459283e 100644 --- a/e2e/scenarios/deno-browser/__snapshots__/span-tree.txt +++ b/e2e/scenarios/deno-browser/__snapshots__/span-tree.txt @@ -41,39 +41,45 @@ span_tree: │ "scenario": "deno-browser", │ "testRunId": "" │ } -├── deno browser child span [task] -│ input: { -│ "step": "child", -│ "testRunId": "" -│ } -│ output: { -│ "ok": true, -│ "phase": "child" -│ } -│ metadata: { -│ "case": "child-span", -│ "scenario": "deno-browser", -│ "testRunId": "" -│ } +│ └── deno browser child span +│ input: { +│ "step": "child", +│ "testRunId": "" +│ } +│ output: { +│ "ok": true, +│ "phase": "child" +│ } +│ metadata: { +│ "case": "child-span", +│ "scenario": "deno-browser", +│ "testRunId": "" +│ } ├── deno browser nested parent span [task] │ metadata: { │ "case": "nested-parent", │ "scenario": "deno-browser", │ "testRunId": "" │ } -├── deno browser nested child span [task] -│ metadata: { -│ "case": "nested-child", -│ "scenario": "deno-browser", -│ "testRunId": "" -│ } -├── deno browser nested grandchild span [task] -│ metadata: { -│ "case": "nested-grandchild", -│ "scenario": "deno-browser", -│ "testRunId": "" -│ } +│ └── deno browser nested child span +│ metadata: { +│ "case": "nested-child", +│ "scenario": "deno-browser", +│ "testRunId": "" +│ } +│ └── deno browser nested grandchild span +│ output: { +│ "depth": 3 +│ } +│ metadata: { +│ "case": "nested-grandchild", +│ "scenario": "deno-browser", +│ "testRunId": "" +│ } └── deno browser current span [task] + output: { + "observedSpanId": "" + } metadata: { "case": "current-span", "scenario": "deno-browser", diff --git a/e2e/scenarios/deno-browser/runner.case.ts b/e2e/scenarios/deno-browser/runner.case.ts index 88197b73b..6cf767321 100644 --- a/e2e/scenarios/deno-browser/runner.case.ts +++ b/e2e/scenarios/deno-browser/runner.case.ts @@ -1,6 +1,5 @@ -import * as braintrust from "braintrust/browser"; +import * as braintrust from "@braintrust/browser"; import { - expectBuildType, expectEvalWorks, expectMustacheTemplate, expectNamedExports, @@ -27,10 +26,8 @@ Deno.test("deno-browser exposes the expected browser runtime surface", () => { "initLogger", "currentSpan", "startSpan", - "log", "flush", "initDataset", - "initExperiment", "loadPrompt", "Prompt", "Eval", @@ -40,24 +37,17 @@ Deno.test("deno-browser exposes the expected browser runtime surface", () => { "Attachment", "invoke", "initFunction", - "Project", - "PromptBuilder", - "IDGenerator", + "projects", "BraintrustState", "login", - "_exportsForTestingOnly", ]); - expectBuildType(braintrust, "browser"); expectMustacheTemplate(braintrust); expectNunjucksTemplateUnavailable(braintrust); }); -Deno.test( - "deno-browser can run Eval locally without sending logs", - async () => { - await expectEvalWorks(braintrust); - }, -); +Deno.test("deno-browser can run Eval locally without sending logs", async () => { + await expectEvalWorks(braintrust); +}); Deno.test("deno-browser emits trace payloads over HTTP", async () => { const logger = createLogger(); diff --git a/e2e/scenarios/deno-browser/scenario.test.ts b/e2e/scenarios/deno-browser/scenario.test.ts index 46fe46f7d..87b993cc8 100644 --- a/e2e/scenarios/deno-browser/scenario.test.ts +++ b/e2e/scenarios/deno-browser/scenario.test.ts @@ -1,4 +1,4 @@ -import { expect, test } from "vitest"; +import { expect, test } from "vite-plus/test"; import { formatJsonFileSnapshot, matchFileSnapshot, @@ -128,11 +128,15 @@ test( phase: "child", ok: true, }); - expect(childSpan?.span.parentIds).toEqual([]); - expect(nestedChild?.span.parentIds).toEqual([]); - expect(nestedGrandchild?.span.parentIds).toEqual([]); - expect(nestedGrandchild?.output).toBeUndefined(); - expect(currentSpan?.output).toBeUndefined(); + expect(childSpan?.span.parentIds).toEqual([parentSpan?.span.id]); + expect(nestedChild?.span.parentIds).toEqual([nestedParent?.span.id]); + expect(nestedGrandchild?.span.parentIds).toEqual([ + nestedChild?.span.id, + ]); + expect(nestedGrandchild?.output).toEqual({ depth: 3 }); + expect(currentSpan?.output).toMatchObject({ + observedSpanId: currentSpan?.span.id, + }); const requests = requestsAfter( cursor, diff --git a/e2e/scenarios/deno-node/runner.case.ts b/e2e/scenarios/deno-node/runner.case.ts index b8f88b2db..2f21d1d91 100644 --- a/e2e/scenarios/deno-node/runner.case.ts +++ b/e2e/scenarios/deno-node/runner.case.ts @@ -1,6 +1,5 @@ import * as braintrust from "braintrust"; import { - expectBuildType, expectEvalWorks, expectMustacheTemplate, expectNamedExports, @@ -27,10 +26,8 @@ Deno.test("deno-node exposes the expected node runtime surface", () => { "initLogger", "currentSpan", "startSpan", - "log", "flush", "initDataset", - "initExperiment", "loadPrompt", "Prompt", "Eval", @@ -40,14 +37,10 @@ Deno.test("deno-node exposes the expected node runtime surface", () => { "Attachment", "invoke", "initFunction", - "Project", - "PromptBuilder", - "IDGenerator", + "projects", "BraintrustState", "login", - "_exportsForTestingOnly", ]); - expectBuildType(braintrust, "node"); expectMustacheTemplate(braintrust); expectNunjucksTemplateUnavailable(braintrust); }); diff --git a/e2e/scenarios/deno-node/scenario.test.ts b/e2e/scenarios/deno-node/scenario.test.ts index 9fa8b34f2..fd24abe51 100644 --- a/e2e/scenarios/deno-node/scenario.test.ts +++ b/e2e/scenarios/deno-node/scenario.test.ts @@ -1,4 +1,4 @@ -import { expect, test } from "vitest"; +import { expect, test } from "vite-plus/test"; import { formatJsonFileSnapshot, matchFileSnapshot, diff --git a/e2e/scenarios/elevenlabs-instrumentation/scenario.test.ts b/e2e/scenarios/elevenlabs-instrumentation/scenario.test.ts index e4d82876d..91546d766 100644 --- a/e2e/scenarios/elevenlabs-instrumentation/scenario.test.ts +++ b/e2e/scenarios/elevenlabs-instrumentation/scenario.test.ts @@ -1,4 +1,4 @@ -import { describe, expect, test } from "vitest"; +import { describe, expect, test } from "vite-plus/test"; import { prepareScenarioDir, resolveScenarioDir, @@ -16,7 +16,7 @@ const scenarioDir = await prepareScenarioDir({ scenarioDir: originalScenarioDir, }); for (const variant of ["elevenlabs-v2", "elevenlabs-v2-latest"]) { - describe.sequential(variant, () => { + describe(variant, { concurrent: false }, () => { for (const mode of ["wrapped", "auto", "wrapped-auto"]) { test( mode, @@ -30,6 +30,7 @@ for (const variant of ["elevenlabs-v2", "elevenlabs-v2-latest"]) { ? ["--import", "braintrust/hook.mjs"] : [], env: { + BRAINTRUST_AUTO_CAPTURE_ATTACHMENTS: "true", ELEVENLABS_PACKAGE_NAME: variant.replace( "elevenlabs-", "elevenlabs-sdk-", diff --git a/e2e/scenarios/eve-instrumentation/__cassettes__/eve-v0.cassette.blobs/216c5da47f83c7e5d8886e8fef437a1dd9abee6429bbc1dfede68b9216783bb5.bin b/e2e/scenarios/eve-instrumentation/__cassettes__/eve-v0.cassette.blobs/216c5da47f83c7e5d8886e8fef437a1dd9abee6429bbc1dfede68b9216783bb5.bin deleted file mode 100644 index eac520a38..000000000 --- a/e2e/scenarios/eve-instrumentation/__cassettes__/eve-v0.cassette.blobs/216c5da47f83c7e5d8886e8fef437a1dd9abee6429bbc1dfede68b9216783bb5.bin +++ /dev/null @@ -1,602 +0,0 @@ -data: {"id":"gen-1791215148-PVpnAFb30EqLOaA4TUuP","object":"chat.completion.chunk","created":1791215148,"model":"qwen/qwen3-30b-a3b","provider":"DeepInfra","choices":[{"index":0,"delta":{"content":"","role":"assistant","reasoning":"\n","reasoning_details":[{"type":"reasoning.text","text":"\n","format":"unknown","index":0}]},"finish_reason":null,"native_finish_reason":null}]} - -data: {"id":"gen-1791215148-PVpnAFb30EqLOaA4TUuP","object":"chat.completion.chunk","created":1791215148,"model":"qwen/qwen3-30b-a3b","provider":"DeepInfra","choices":[{"index":0,"delta":{"content":"","role":"assistant","reasoning":"Okay","reasoning_details":[{"type":"reasoning.text","text":"Okay","format":"unknown","index":0}]},"finish_reason":null,"native_finish_reason":null}]} - -data: {"id":"gen-1791215148-PVpnAFb30EqLOaA4TUuP","object":"chat.completion.chunk","created":1791215148,"model":"qwen/qwen3-30b-a3b","provider":"DeepInfra","choices":[{"index":0,"delta":{"content":"","role":"assistant","reasoning":",","reasoning_details":[{"type":"reasoning.text","text":",","format":"unknown","index":0}]},"finish_reason":null,"native_finish_reason":null}]} - -data: {"id":"gen-1791215148-PVpnAFb30EqLOaA4TUuP","object":"chat.completion.chunk","created":1791215148,"model":"qwen/qwen3-30b-a3b","provider":"DeepInfra","choices":[{"index":0,"delta":{"content":"","role":"assistant","reasoning":" the","reasoning_details":[{"type":"reasoning.text","text":" the","format":"unknown","index":0}]},"finish_reason":null,"native_finish_reason":null}]} - -data: {"id":"gen-1791215148-PVpnAFb30EqLOaA4TUuP","object":"chat.completion.chunk","created":1791215148,"model":"qwen/qwen3-30b-a3b","provider":"DeepInfra","choices":[{"index":0,"delta":{"content":"","role":"assistant","reasoning":" user","reasoning_details":[{"type":"reasoning.text","text":" user","format":"unknown","index":0}]},"finish_reason":null,"native_finish_reason":null}]} - -data: {"id":"gen-1791215148-PVpnAFb30EqLOaA4TUuP","object":"chat.completion.chunk","created":1791215148,"model":"qwen/qwen3-30b-a3b","provider":"DeepInfra","choices":[{"index":0,"delta":{"content":"","role":"assistant","reasoning":" wants","reasoning_details":[{"type":"reasoning.text","text":" wants","format":"unknown","index":0}]},"finish_reason":null,"native_finish_reason":null}]} - -data: {"id":"gen-1791215148-PVpnAFb30EqLOaA4TUuP","object":"chat.completion.chunk","created":1791215148,"model":"qwen/qwen3-30b-a3b","provider":"DeepInfra","choices":[{"index":0,"delta":{"content":"","role":"assistant","reasoning":" me","reasoning_details":[{"type":"reasoning.text","text":" me","format":"unknown","index":0}]},"finish_reason":null,"native_finish_reason":null}]} - -data: {"id":"gen-1791215148-PVpnAFb30EqLOaA4TUuP","object":"chat.completion.chunk","created":1791215148,"model":"qwen/qwen3-30b-a3b","provider":"DeepInfra","choices":[{"index":0,"delta":{"content":"","role":"assistant","reasoning":" to","reasoning_details":[{"type":"reasoning.text","text":" to","format":"unknown","index":0}]},"finish_reason":null,"native_finish_reason":null}]} - -data: {"id":"gen-1791215148-PVpnAFb30EqLOaA4TUuP","object":"chat.completion.chunk","created":1791215148,"model":"qwen/qwen3-30b-a3b","provider":"DeepInfra","choices":[{"index":0,"delta":{"content":"","role":"assistant","reasoning":" run","reasoning_details":[{"type":"reasoning.text","text":" run","format":"unknown","index":0}]},"finish_reason":null,"native_finish_reason":null}]} - -data: {"id":"gen-1791215148-PVpnAFb30EqLOaA4TUuP","object":"chat.completion.chunk","created":1791215148,"model":"qwen/qwen3-30b-a3b","provider":"DeepInfra","choices":[{"index":0,"delta":{"content":"","role":"assistant","reasoning":" the","reasoning_details":[{"type":"reasoning.text","text":" the","format":"unknown","index":0}]},"finish_reason":null,"native_finish_reason":null}]} - -data: {"id":"gen-1791215148-PVpnAFb30EqLOaA4TUuP","object":"chat.completion.chunk","created":1791215148,"model":"qwen/qwen3-30b-a3b","provider":"DeepInfra","choices":[{"index":0,"delta":{"content":"","role":"assistant","reasoning":" Brain","reasoning_details":[{"type":"reasoning.text","text":" Brain","format":"unknown","index":0}]},"finish_reason":null,"native_finish_reason":null}]} - -data: {"id":"gen-1791215148-PVpnAFb30EqLOaA4TUuP","object":"chat.completion.chunk","created":1791215148,"model":"qwen/qwen3-30b-a3b","provider":"DeepInfra","choices":[{"index":0,"delta":{"content":"","role":"assistant","reasoning":"trust","reasoning_details":[{"type":"reasoning.text","text":"trust","format":"unknown","index":0}]},"finish_reason":null,"native_finish_reason":null}]} - -data: {"id":"gen-1791215148-PVpnAFb30EqLOaA4TUuP","object":"chat.completion.chunk","created":1791215148,"model":"qwen/qwen3-30b-a3b","provider":"DeepInfra","choices":[{"index":0,"delta":{"content":"","role":"assistant","reasoning":" Eve","reasoning_details":[{"type":"reasoning.text","text":" Eve","format":"unknown","index":0}]},"finish_reason":null,"native_finish_reason":null}]} - -data: {"id":"gen-1791215148-PVpnAFb30EqLOaA4TUuP","object":"chat.completion.chunk","created":1791215148,"model":"qwen/qwen3-30b-a3b","provider":"DeepInfra","choices":[{"index":0,"delta":{"content":"","role":"assistant","reasoning":" instrumentation","reasoning_details":[{"type":"reasoning.text","text":" instrumentation","format":"unknown","index":0}]},"finish_reason":null,"native_finish_reason":null}]} - -data: {"id":"gen-1791215148-PVpnAFb30EqLOaA4TUuP","object":"chat.completion.chunk","created":1791215148,"model":"qwen/qwen3-30b-a3b","provider":"DeepInfra","choices":[{"index":0,"delta":{"content":"","role":"assistant","reasoning":" end","reasoning_details":[{"type":"reasoning.text","text":" end","format":"unknown","index":0}]},"finish_reason":null,"native_finish_reason":null}]} - -data: {"id":"gen-1791215148-PVpnAFb30EqLOaA4TUuP","object":"chat.completion.chunk","created":1791215148,"model":"qwen/qwen3-30b-a3b","provider":"DeepInfra","choices":[{"index":0,"delta":{"content":"","role":"assistant","reasoning":"-to","reasoning_details":[{"type":"reasoning.text","text":"-to","format":"unknown","index":0}]},"finish_reason":null,"native_finish_reason":null}]} - -data: {"id":"gen-1791215148-PVpnAFb30EqLOaA4TUuP","object":"chat.completion.chunk","created":1791215148,"model":"qwen/qwen3-30b-a3b","provider":"DeepInfra","choices":[{"index":0,"delta":{"content":"","role":"assistant","reasoning":"-end","reasoning_details":[{"type":"reasoning.text","text":"-end","format":"unknown","index":0}]},"finish_reason":null,"native_finish_reason":null}]} - -data: {"id":"gen-1791215148-PVpnAFb30EqLOaA4TUuP","object":"chat.completion.chunk","created":1791215148,"model":"qwen/qwen3-30b-a3b","provider":"DeepInfra","choices":[{"index":0,"delta":{"content":"","role":"assistant","reasoning":" scenario","reasoning_details":[{"type":"reasoning.text","text":" scenario","format":"unknown","index":0}]},"finish_reason":null,"native_finish_reason":null}]} - -data: {"id":"gen-1791215148-PVpnAFb30EqLOaA4TUuP","object":"chat.completion.chunk","created":1791215148,"model":"qwen/qwen3-30b-a3b","provider":"DeepInfra","choices":[{"index":0,"delta":{"content":"","role":"assistant","reasoning":".","reasoning_details":[{"type":"reasoning.text","text":".","format":"unknown","index":0}]},"finish_reason":null,"native_finish_reason":null}]} - -data: {"id":"gen-1791215148-PVpnAFb30EqLOaA4TUuP","object":"chat.completion.chunk","created":1791215148,"model":"qwen/qwen3-30b-a3b","provider":"DeepInfra","choices":[{"index":0,"delta":{"content":"","role":"assistant","reasoning":" Let","reasoning_details":[{"type":"reasoning.text","text":" Let","format":"unknown","index":0}]},"finish_reason":null,"native_finish_reason":null}]} - -data: {"id":"gen-1791215148-PVpnAFb30EqLOaA4TUuP","object":"chat.completion.chunk","created":1791215148,"model":"qwen/qwen3-30b-a3b","provider":"DeepInfra","choices":[{"index":0,"delta":{"content":"","role":"assistant","reasoning":" me","reasoning_details":[{"type":"reasoning.text","text":" me","format":"unknown","index":0}]},"finish_reason":null,"native_finish_reason":null}]} - -data: {"id":"gen-1791215148-PVpnAFb30EqLOaA4TUuP","object":"chat.completion.chunk","created":1791215148,"model":"qwen/qwen3-30b-a3b","provider":"DeepInfra","choices":[{"index":0,"delta":{"content":"","role":"assistant","reasoning":" start","reasoning_details":[{"type":"reasoning.text","text":" start","format":"unknown","index":0}]},"finish_reason":null,"native_finish_reason":null}]} - -data: {"id":"gen-1791215148-PVpnAFb30EqLOaA4TUuP","object":"chat.completion.chunk","created":1791215148,"model":"qwen/qwen3-30b-a3b","provider":"DeepInfra","choices":[{"index":0,"delta":{"content":"","role":"assistant","reasoning":" by","reasoning_details":[{"type":"reasoning.text","text":" by","format":"unknown","index":0}]},"finish_reason":null,"native_finish_reason":null}]} - -data: {"id":"gen-1791215148-PVpnAFb30EqLOaA4TUuP","object":"chat.completion.chunk","created":1791215148,"model":"qwen/qwen3-30b-a3b","provider":"DeepInfra","choices":[{"index":0,"delta":{"content":"","role":"assistant","reasoning":" understanding","reasoning_details":[{"type":"reasoning.text","text":" understanding","format":"unknown","index":0}]},"finish_reason":null,"native_finish_reason":null}]} - -data: {"id":"gen-1791215148-PVpnAFb30EqLOaA4TUuP","object":"chat.completion.chunk","created":1791215148,"model":"qwen/qwen3-30b-a3b","provider":"DeepInfra","choices":[{"index":0,"delta":{"content":"","role":"assistant","reasoning":" what","reasoning_details":[{"type":"reasoning.text","text":" what","format":"unknown","index":0}]},"finish_reason":null,"native_finish_reason":null}]} - -data: {"id":"gen-1791215148-PVpnAFb30EqLOaA4TUuP","object":"chat.completion.chunk","created":1791215148,"model":"qwen/qwen3-30b-a3b","provider":"DeepInfra","choices":[{"index":0,"delta":{"content":"","role":"assistant","reasoning":" that","reasoning_details":[{"type":"reasoning.text","text":" that","format":"unknown","index":0}]},"finish_reason":null,"native_finish_reason":null}]} - -data: {"id":"gen-1791215148-PVpnAFb30EqLOaA4TUuP","object":"chat.completion.chunk","created":1791215148,"model":"qwen/qwen3-30b-a3b","provider":"DeepInfra","choices":[{"index":0,"delta":{"content":"","role":"assistant","reasoning":" entails","reasoning_details":[{"type":"reasoning.text","text":" entails","format":"unknown","index":0}]},"finish_reason":null,"native_finish_reason":null}]} - -data: {"id":"gen-1791215148-PVpnAFb30EqLOaA4TUuP","object":"chat.completion.chunk","created":1791215148,"model":"qwen/qwen3-30b-a3b","provider":"DeepInfra","choices":[{"index":0,"delta":{"content":"","role":"assistant","reasoning":".","reasoning_details":[{"type":"reasoning.text","text":".","format":"unknown","index":0}]},"finish_reason":null,"native_finish_reason":null}]} - -data: {"id":"gen-1791215148-PVpnAFb30EqLOaA4TUuP","object":"chat.completion.chunk","created":1791215148,"model":"qwen/qwen3-30b-a3b","provider":"DeepInfra","choices":[{"index":0,"delta":{"content":"","role":"assistant","reasoning":" The","reasoning_details":[{"type":"reasoning.text","text":" The","format":"unknown","index":0}]},"finish_reason":null,"native_finish_reason":null}]} - -data: {"id":"gen-1791215148-PVpnAFb30EqLOaA4TUuP","object":"chat.completion.chunk","created":1791215148,"model":"qwen/qwen3-30b-a3b","provider":"DeepInfra","choices":[{"index":0,"delta":{"content":"","role":"assistant","reasoning":" user","reasoning_details":[{"type":"reasoning.text","text":" user","format":"unknown","index":0}]},"finish_reason":null,"native_finish_reason":null}]} - -data: {"id":"gen-1791215148-PVpnAFb30EqLOaA4TUuP","object":"chat.completion.chunk","created":1791215148,"model":"qwen/qwen3-30b-a3b","provider":"DeepInfra","choices":[{"index":0,"delta":{"content":"","role":"assistant","reasoning":" mentioned","reasoning_details":[{"type":"reasoning.text","text":" mentioned","format":"unknown","index":0}]},"finish_reason":null,"native_finish_reason":null}]} - -data: {"id":"gen-1791215148-PVpnAFb30EqLOaA4TUuP","object":"chat.completion.chunk","created":1791215148,"model":"qwen/qwen3-30b-a3b","provider":"DeepInfra","choices":[{"index":0,"delta":{"content":"","role":"assistant","reasoning":" \"","reasoning_details":[{"type":"reasoning.text","text":" \"","format":"unknown","index":0}]},"finish_reason":null,"native_finish_reason":null}]} - -data: {"id":"gen-1791215148-PVpnAFb30EqLOaA4TUuP","object":"chat.completion.chunk","created":1791215148,"model":"qwen/qwen3-30b-a3b","provider":"DeepInfra","choices":[{"index":0,"delta":{"content":"","role":"assistant","reasoning":"e","reasoning_details":[{"type":"reasoning.text","text":"e","format":"unknown","index":0}]},"finish_reason":null,"native_finish_reason":null}]} - -data: {"id":"gen-1791215148-PVpnAFb30EqLOaA4TUuP","object":"chat.completion.chunk","created":1791215148,"model":"qwen/qwen3-30b-a3b","provider":"DeepInfra","choices":[{"index":0,"delta":{"content":"","role":"assistant","reasoning":"2","reasoning_details":[{"type":"reasoning.text","text":"2","format":"unknown","index":0}]},"finish_reason":null,"native_finish_reason":null}]} - -data: {"id":"gen-1791215148-PVpnAFb30EqLOaA4TUuP","object":"chat.completion.chunk","created":1791215148,"model":"qwen/qwen3-30b-a3b","provider":"DeepInfra","choices":[{"index":0,"delta":{"content":"","role":"assistant","reasoning":"e","reasoning_details":[{"type":"reasoning.text","text":"e","format":"unknown","index":0}]},"finish_reason":null,"native_finish_reason":null}]} - -data: {"id":"gen-1791215148-PVpnAFb30EqLOaA4TUuP","object":"chat.completion.chunk","created":1791215148,"model":"qwen/qwen3-30b-a3b","provider":"DeepInfra","choices":[{"index":0,"delta":{"content":"","role":"assistant","reasoning":" scenario","reasoning_details":[{"type":"reasoning.text","text":" scenario","format":"unknown","index":0}]},"finish_reason":null,"native_finish_reason":null}]} - -data: {"id":"gen-1791215148-PVpnAFb30EqLOaA4TUuP","object":"chat.completion.chunk","created":1791215148,"model":"qwen/qwen3-30b-a3b","provider":"DeepInfra","choices":[{"index":0,"delta":{"content":"","role":"assistant","reasoning":",\"","reasoning_details":[{"type":"reasoning.text","text":",\"","format":"unknown","index":0}]},"finish_reason":null,"native_finish_reason":null}]} - -data: {"id":"gen-1791215148-PVpnAFb30EqLOaA4TUuP","object":"chat.completion.chunk","created":1791215148,"model":"qwen/qwen3-30b-a3b","provider":"DeepInfra","choices":[{"index":0,"delta":{"content":"","role":"assistant","reasoning":" which","reasoning_details":[{"type":"reasoning.text","text":" which","format":"unknown","index":0}]},"finish_reason":null,"native_finish_reason":null}]} - -data: {"id":"gen-1791215148-PVpnAFb30EqLOaA4TUuP","object":"chat.completion.chunk","created":1791215148,"model":"qwen/qwen3-30b-a3b","provider":"DeepInfra","choices":[{"index":0,"delta":{"content":"","role":"assistant","reasoning":" usually","reasoning_details":[{"type":"reasoning.text","text":" usually","format":"unknown","index":0}]},"finish_reason":null,"native_finish_reason":null}]} - -data: {"id":"gen-1791215148-PVpnAFb30EqLOaA4TUuP","object":"chat.completion.chunk","created":1791215148,"model":"qwen/qwen3-30b-a3b","provider":"DeepInfra","choices":[{"index":0,"delta":{"content":"","role":"assistant","reasoning":" stands","reasoning_details":[{"type":"reasoning.text","text":" stands","format":"unknown","index":0}]},"finish_reason":null,"native_finish_reason":null}]} - -data: {"id":"gen-1791215148-PVpnAFb30EqLOaA4TUuP","object":"chat.completion.chunk","created":1791215148,"model":"qwen/qwen3-30b-a3b","provider":"DeepInfra","choices":[{"index":0,"delta":{"content":"","role":"assistant","reasoning":" for","reasoning_details":[{"type":"reasoning.text","text":" for","format":"unknown","index":0}]},"finish_reason":null,"native_finish_reason":null}]} - -data: {"id":"gen-1791215148-PVpnAFb30EqLOaA4TUuP","object":"chat.completion.chunk","created":1791215148,"model":"qwen/qwen3-30b-a3b","provider":"DeepInfra","choices":[{"index":0,"delta":{"content":"","role":"assistant","reasoning":" end","reasoning_details":[{"type":"reasoning.text","text":" end","format":"unknown","index":0}]},"finish_reason":null,"native_finish_reason":null}]} - -data: {"id":"gen-1791215148-PVpnAFb30EqLOaA4TUuP","object":"chat.completion.chunk","created":1791215148,"model":"qwen/qwen3-30b-a3b","provider":"DeepInfra","choices":[{"index":0,"delta":{"content":"","role":"assistant","reasoning":"-to","reasoning_details":[{"type":"reasoning.text","text":"-to","format":"unknown","index":0}]},"finish_reason":null,"native_finish_reason":null}]} - -data: {"id":"gen-1791215148-PVpnAFb30EqLOaA4TUuP","object":"chat.completion.chunk","created":1791215148,"model":"qwen/qwen3-30b-a3b","provider":"DeepInfra","choices":[{"index":0,"delta":{"content":"","role":"assistant","reasoning":"-end","reasoning_details":[{"type":"reasoning.text","text":"-end","format":"unknown","index":0}]},"finish_reason":null,"native_finish_reason":null}]} - -data: {"id":"gen-1791215148-PVpnAFb30EqLOaA4TUuP","object":"chat.completion.chunk","created":1791215148,"model":"qwen/qwen3-30b-a3b","provider":"DeepInfra","choices":[{"index":0,"delta":{"content":"","role":"assistant","reasoning":" testing","reasoning_details":[{"type":"reasoning.text","text":" testing","format":"unknown","index":0}]},"finish_reason":null,"native_finish_reason":null}]} - -data: {"id":"gen-1791215148-PVpnAFb30EqLOaA4TUuP","object":"chat.completion.chunk","created":1791215148,"model":"qwen/qwen3-30b-a3b","provider":"DeepInfra","choices":[{"index":0,"delta":{"content":"","role":"assistant","reasoning":".","reasoning_details":[{"type":"reasoning.text","text":".","format":"unknown","index":0}]},"finish_reason":null,"native_finish_reason":null}]} - -data: {"id":"gen-1791215148-PVpnAFb30EqLOaA4TUuP","object":"chat.completion.chunk","created":1791215148,"model":"qwen/qwen3-30b-a3b","provider":"DeepInfra","choices":[{"index":0,"delta":{"content":"","role":"assistant","reasoning":" So","reasoning_details":[{"type":"reasoning.text","text":" So","format":"unknown","index":0}]},"finish_reason":null,"native_finish_reason":null}]} - -data: {"id":"gen-1791215148-PVpnAFb30EqLOaA4TUuP","object":"chat.completion.chunk","created":1791215148,"model":"qwen/qwen3-30b-a3b","provider":"DeepInfra","choices":[{"index":0,"delta":{"content":"","role":"assistant","reasoning":",","reasoning_details":[{"type":"reasoning.text","text":",","format":"unknown","index":0}]},"finish_reason":null,"native_finish_reason":null}]} - -: OPENROUTER PROCESSING - -data: {"id":"gen-1791215148-PVpnAFb30EqLOaA4TUuP","object":"chat.completion.chunk","created":1791215148,"model":"qwen/qwen3-30b-a3b","provider":"DeepInfra","choices":[{"index":0,"delta":{"content":"","role":"assistant","reasoning":" they","reasoning_details":[{"type":"reasoning.text","text":" they","format":"unknown","index":0}]},"finish_reason":null,"native_finish_reason":null}]} - -data: {"id":"gen-1791215148-PVpnAFb30EqLOaA4TUuP","object":"chat.completion.chunk","created":1791215148,"model":"qwen/qwen3-30b-a3b","provider":"DeepInfra","choices":[{"index":0,"delta":{"content":"","role":"assistant","reasoning":" probably","reasoning_details":[{"type":"reasoning.text","text":" probably","format":"unknown","index":0}]},"finish_reason":null,"native_finish_reason":null}]} - -data: {"id":"gen-1791215148-PVpnAFb30EqLOaA4TUuP","object":"chat.completion.chunk","created":1791215148,"model":"qwen/qwen3-30b-a3b","provider":"DeepInfra","choices":[{"index":0,"delta":{"content":"","role":"assistant","reasoning":" want","reasoning_details":[{"type":"reasoning.text","text":" want","format":"unknown","index":0}]},"finish_reason":null,"native_finish_reason":null}]} - -data: {"id":"gen-1791215148-PVpnAFb30EqLOaA4TUuP","object":"chat.completion.chunk","created":1791215148,"model":"qwen/qwen3-30b-a3b","provider":"DeepInfra","choices":[{"index":0,"delta":{"content":"","role":"assistant","reasoning":" to","reasoning_details":[{"type":"reasoning.text","text":" to","format":"unknown","index":0}]},"finish_reason":null,"native_finish_reason":null}]} - -data: {"id":"gen-1791215148-PVpnAFb30EqLOaA4TUuP","object":"chat.completion.chunk","created":1791215148,"model":"qwen/qwen3-30b-a3b","provider":"DeepInfra","choices":[{"index":0,"delta":{"content":"","role":"assistant","reasoning":" simulate","reasoning_details":[{"type":"reasoning.text","text":" simulate","format":"unknown","index":0}]},"finish_reason":null,"native_finish_reason":null}]} - -data: {"id":"gen-1791215148-PVpnAFb30EqLOaA4TUuP","object":"chat.completion.chunk","created":1791215148,"model":"qwen/qwen3-30b-a3b","provider":"DeepInfra","choices":[{"index":0,"delta":{"content":"","role":"assistant","reasoning":" or","reasoning_details":[{"type":"reasoning.text","text":" or","format":"unknown","index":0}]},"finish_reason":null,"native_finish_reason":null}]} - -data: {"id":"gen-1791215148-PVpnAFb30EqLOaA4TUuP","object":"chat.completion.chunk","created":1791215148,"model":"qwen/qwen3-30b-a3b","provider":"DeepInfra","choices":[{"index":0,"delta":{"content":"","role":"assistant","reasoning":" execute","reasoning_details":[{"type":"reasoning.text","text":" execute","format":"unknown","index":0}]},"finish_reason":null,"native_finish_reason":null}]} - -data: {"id":"gen-1791215148-PVpnAFb30EqLOaA4TUuP","object":"chat.completion.chunk","created":1791215148,"model":"qwen/qwen3-30b-a3b","provider":"DeepInfra","choices":[{"index":0,"delta":{"content":"","role":"assistant","reasoning":" a","reasoning_details":[{"type":"reasoning.text","text":" a","format":"unknown","index":0}]},"finish_reason":null,"native_finish_reason":null}]} - -data: {"id":"gen-1791215148-PVpnAFb30EqLOaA4TUuP","object":"chat.completion.chunk","created":1791215148,"model":"qwen/qwen3-30b-a3b","provider":"DeepInfra","choices":[{"index":0,"delta":{"content":"","role":"assistant","reasoning":" complete","reasoning_details":[{"type":"reasoning.text","text":" complete","format":"unknown","index":0}]},"finish_reason":null,"native_finish_reason":null}]} - -data: {"id":"gen-1791215148-PVpnAFb30EqLOaA4TUuP","object":"chat.completion.chunk","created":1791215148,"model":"qwen/qwen3-30b-a3b","provider":"DeepInfra","choices":[{"index":0,"delta":{"content":"","role":"assistant","reasoning":" workflow","reasoning_details":[{"type":"reasoning.text","text":" workflow","format":"unknown","index":0}]},"finish_reason":null,"native_finish_reason":null}]} - -data: {"id":"gen-1791215148-PVpnAFb30EqLOaA4TUuP","object":"chat.completion.chunk","created":1791215148,"model":"qwen/qwen3-30b-a3b","provider":"DeepInfra","choices":[{"index":0,"delta":{"content":"","role":"assistant","reasoning":" within","reasoning_details":[{"type":"reasoning.text","text":" within","format":"unknown","index":0}]},"finish_reason":null,"native_finish_reason":null}]} - -data: {"id":"gen-1791215148-PVpnAFb30EqLOaA4TUuP","object":"chat.completion.chunk","created":1791215148,"model":"qwen/qwen3-30b-a3b","provider":"DeepInfra","choices":[{"index":0,"delta":{"content":"","role":"assistant","reasoning":" the","reasoning_details":[{"type":"reasoning.text","text":" the","format":"unknown","index":0}]},"finish_reason":null,"native_finish_reason":null}]} - -data: {"id":"gen-1791215148-PVpnAFb30EqLOaA4TUuP","object":"chat.completion.chunk","created":1791215148,"model":"qwen/qwen3-30b-a3b","provider":"DeepInfra","choices":[{"index":0,"delta":{"content":"","role":"assistant","reasoning":" Eve","reasoning_details":[{"type":"reasoning.text","text":" Eve","format":"unknown","index":0}]},"finish_reason":null,"native_finish_reason":null}]} - -data: {"id":"gen-1791215148-PVpnAFb30EqLOaA4TUuP","object":"chat.completion.chunk","created":1791215148,"model":"qwen/qwen3-30b-a3b","provider":"DeepInfra","choices":[{"index":0,"delta":{"content":"","role":"assistant","reasoning":" instrumentation","reasoning_details":[{"type":"reasoning.text","text":" instrumentation","format":"unknown","index":0}]},"finish_reason":null,"native_finish_reason":null}]} - -data: {"id":"gen-1791215148-PVpnAFb30EqLOaA4TUuP","object":"chat.completion.chunk","created":1791215148,"model":"qwen/qwen3-30b-a3b","provider":"DeepInfra","choices":[{"index":0,"delta":{"content":"","role":"assistant","reasoning":" setup","reasoning_details":[{"type":"reasoning.text","text":" setup","format":"unknown","index":0}]},"finish_reason":null,"native_finish_reason":null}]} - -data: {"id":"gen-1791215148-PVpnAFb30EqLOaA4TUuP","object":"chat.completion.chunk","created":1791215148,"model":"qwen/qwen3-30b-a3b","provider":"DeepInfra","choices":[{"index":0,"delta":{"content":"","role":"assistant","reasoning":".\n\n","reasoning_details":[{"type":"reasoning.text","text":".\n\n","format":"unknown","index":0}]},"finish_reason":null,"native_finish_reason":null}]} - -data: {"id":"gen-1791215148-PVpnAFb30EqLOaA4TUuP","object":"chat.completion.chunk","created":1791215148,"model":"qwen/qwen3-30b-a3b","provider":"DeepInfra","choices":[{"index":0,"delta":{"content":"","role":"assistant","reasoning":"First","reasoning_details":[{"type":"reasoning.text","text":"First","format":"unknown","index":0}]},"finish_reason":null,"native_finish_reason":null}]} - -data: {"id":"gen-1791215148-PVpnAFb30EqLOaA4TUuP","object":"chat.completion.chunk","created":1791215148,"model":"qwen/qwen3-30b-a3b","provider":"DeepInfra","choices":[{"index":0,"delta":{"content":"","role":"assistant","reasoning":",","reasoning_details":[{"type":"reasoning.text","text":",","format":"unknown","index":0}]},"finish_reason":null,"native_finish_reason":null}]} - -data: {"id":"gen-1791215148-PVpnAFb30EqLOaA4TUuP","object":"chat.completion.chunk","created":1791215148,"model":"qwen/qwen3-30b-a3b","provider":"DeepInfra","choices":[{"index":0,"delta":{"content":"","role":"assistant","reasoning":" I","reasoning_details":[{"type":"reasoning.text","text":" I","format":"unknown","index":0}]},"finish_reason":null,"native_finish_reason":null}]} - -data: {"id":"gen-1791215148-PVpnAFb30EqLOaA4TUuP","object":"chat.completion.chunk","created":1791215148,"model":"qwen/qwen3-30b-a3b","provider":"DeepInfra","choices":[{"index":0,"delta":{"content":"","role":"assistant","reasoning":" need","reasoning_details":[{"type":"reasoning.text","text":" need","format":"unknown","index":0}]},"finish_reason":null,"native_finish_reason":null}]} - -data: {"id":"gen-1791215148-PVpnAFb30EqLOaA4TUuP","object":"chat.completion.chunk","created":1791215148,"model":"qwen/qwen3-30b-a3b","provider":"DeepInfra","choices":[{"index":0,"delta":{"content":"","role":"assistant","reasoning":" to","reasoning_details":[{"type":"reasoning.text","text":" to","format":"unknown","index":0}]},"finish_reason":null,"native_finish_reason":null}]} - -data: {"id":"gen-1791215148-PVpnAFb30EqLOaA4TUuP","object":"chat.completion.chunk","created":1791215148,"model":"qwen/qwen3-30b-a3b","provider":"DeepInfra","choices":[{"index":0,"delta":{"content":"","role":"assistant","reasoning":" check","reasoning_details":[{"type":"reasoning.text","text":" check","format":"unknown","index":0}]},"finish_reason":null,"native_finish_reason":null}]} - -data: {"id":"gen-1791215148-PVpnAFb30EqLOaA4TUuP","object":"chat.completion.chunk","created":1791215148,"model":"qwen/qwen3-30b-a3b","provider":"DeepInfra","choices":[{"index":0,"delta":{"content":"","role":"assistant","reasoning":" the","reasoning_details":[{"type":"reasoning.text","text":" the","format":"unknown","index":0}]},"finish_reason":null,"native_finish_reason":null}]} - -data: {"id":"gen-1791215148-PVpnAFb30EqLOaA4TUuP","object":"chat.completion.chunk","created":1791215148,"model":"qwen/qwen3-30b-a3b","provider":"DeepInfra","choices":[{"index":0,"delta":{"content":"","role":"assistant","reasoning":" available","reasoning_details":[{"type":"reasoning.text","text":" available","format":"unknown","index":0}]},"finish_reason":null,"native_finish_reason":null}]} - -data: {"id":"gen-1791215148-PVpnAFb30EqLOaA4TUuP","object":"chat.completion.chunk","created":1791215148,"model":"qwen/qwen3-30b-a3b","provider":"DeepInfra","choices":[{"index":0,"delta":{"content":"","role":"assistant","reasoning":" tools","reasoning_details":[{"type":"reasoning.text","text":" tools","format":"unknown","index":0}]},"finish_reason":null,"native_finish_reason":null}]} - -data: {"id":"gen-1791215148-PVpnAFb30EqLOaA4TUuP","object":"chat.completion.chunk","created":1791215148,"model":"qwen/qwen3-30b-a3b","provider":"DeepInfra","choices":[{"index":0,"delta":{"content":"","role":"assistant","reasoning":".","reasoning_details":[{"type":"reasoning.text","text":".","format":"unknown","index":0}]},"finish_reason":null,"native_finish_reason":null}]} - -data: {"id":"gen-1791215148-PVpnAFb30EqLOaA4TUuP","object":"chat.completion.chunk","created":1791215148,"model":"qwen/qwen3-30b-a3b","provider":"DeepInfra","choices":[{"index":0,"delta":{"content":"","role":"assistant","reasoning":" The","reasoning_details":[{"type":"reasoning.text","text":" The","format":"unknown","index":0}]},"finish_reason":null,"native_finish_reason":null}]} - -data: {"id":"gen-1791215148-PVpnAFb30EqLOaA4TUuP","object":"chat.completion.chunk","created":1791215148,"model":"qwen/qwen3-30b-a3b","provider":"DeepInfra","choices":[{"index":0,"delta":{"content":"","role":"assistant","reasoning":" user","reasoning_details":[{"type":"reasoning.text","text":" user","format":"unknown","index":0}]},"finish_reason":null,"native_finish_reason":null}]} - -data: {"id":"gen-1791215148-PVpnAFb30EqLOaA4TUuP","object":"chat.completion.chunk","created":1791215148,"model":"qwen/qwen3-30b-a3b","provider":"DeepInfra","choices":[{"index":0,"delta":{"content":"","role":"assistant","reasoning":" provided","reasoning_details":[{"type":"reasoning.text","text":" provided","format":"unknown","index":0}]},"finish_reason":null,"native_finish_reason":null}]} - -data: {"id":"gen-1791215148-PVpnAFb30EqLOaA4TUuP","object":"chat.completion.chunk","created":1791215148,"model":"qwen/qwen3-30b-a3b","provider":"DeepInfra","choices":[{"index":0,"delta":{"content":"","role":"assistant","reasoning":" a","reasoning_details":[{"type":"reasoning.text","text":" a","format":"unknown","index":0}]},"finish_reason":null,"native_finish_reason":null}]} - -data: {"id":"gen-1791215148-PVpnAFb30EqLOaA4TUuP","object":"chat.completion.chunk","created":1791215148,"model":"qwen/qwen3-30b-a3b","provider":"DeepInfra","choices":[{"index":0,"delta":{"content":"","role":"assistant","reasoning":" list","reasoning_details":[{"type":"reasoning.text","text":" list","format":"unknown","index":0}]},"finish_reason":null,"native_finish_reason":null}]} - -data: {"id":"gen-1791215148-PVpnAFb30EqLOaA4TUuP","object":"chat.completion.chunk","created":1791215148,"model":"qwen/qwen3-30b-a3b","provider":"DeepInfra","choices":[{"index":0,"delta":{"content":"","role":"assistant","reasoning":" of","reasoning_details":[{"type":"reasoning.text","text":" of","format":"unknown","index":0}]},"finish_reason":null,"native_finish_reason":null}]} - -data: {"id":"gen-1791215148-PVpnAFb30EqLOaA4TUuP","object":"chat.completion.chunk","created":1791215148,"model":"qwen/qwen3-30b-a3b","provider":"DeepInfra","choices":[{"index":0,"delta":{"content":"","role":"assistant","reasoning":" functions","reasoning_details":[{"type":"reasoning.text","text":" functions","format":"unknown","index":0}]},"finish_reason":null,"native_finish_reason":null}]} - -data: {"id":"gen-1791215148-PVpnAFb30EqLOaA4TUuP","object":"chat.completion.chunk","created":1791215148,"model":"qwen/qwen3-30b-a3b","provider":"DeepInfra","choices":[{"index":0,"delta":{"content":"","role":"assistant","reasoning":",","reasoning_details":[{"type":"reasoning.text","text":",","format":"unknown","index":0}]},"finish_reason":null,"native_finish_reason":null}]} - -data: {"id":"gen-1791215148-PVpnAFb30EqLOaA4TUuP","object":"chat.completion.chunk","created":1791215148,"model":"qwen/qwen3-30b-a3b","provider":"DeepInfra","choices":[{"index":0,"delta":{"content":"","role":"assistant","reasoning":" including","reasoning_details":[{"type":"reasoning.text","text":" including","format":"unknown","index":0}]},"finish_reason":null,"native_finish_reason":null}]} - -data: {"id":"gen-1791215148-PVpnAFb30EqLOaA4TUuP","object":"chat.completion.chunk","created":1791215148,"model":"qwen/qwen3-30b-a3b","provider":"DeepInfra","choices":[{"index":0,"delta":{"content":"","role":"assistant","reasoning":" researcher","reasoning_details":[{"type":"reasoning.text","text":" researcher","format":"unknown","index":0}]},"finish_reason":null,"native_finish_reason":null}]} - -data: {"id":"gen-1791215148-PVpnAFb30EqLOaA4TUuP","object":"chat.completion.chunk","created":1791215148,"model":"qwen/qwen3-30b-a3b","provider":"DeepInfra","choices":[{"index":0,"delta":{"content":"","role":"assistant","reasoning":",","reasoning_details":[{"type":"reasoning.text","text":",","format":"unknown","index":0}]},"finish_reason":null,"native_finish_reason":null}]} - -data: {"id":"gen-1791215148-PVpnAFb30EqLOaA4TUuP","object":"chat.completion.chunk","created":1791215148,"model":"qwen/qwen3-30b-a3b","provider":"DeepInfra","choices":[{"index":0,"delta":{"content":"","role":"assistant","reasoning":" read","reasoning_details":[{"type":"reasoning.text","text":" read","format":"unknown","index":0}]},"finish_reason":null,"native_finish_reason":null}]} - -data: {"id":"gen-1791215148-PVpnAFb30EqLOaA4TUuP","object":"chat.completion.chunk","created":1791215148,"model":"qwen/qwen3-30b-a3b","provider":"DeepInfra","choices":[{"index":0,"delta":{"content":"","role":"assistant","reasoning":",","reasoning_details":[{"type":"reasoning.text","text":",","format":"unknown","index":0}]},"finish_reason":null,"native_finish_reason":null}]} - -data: {"id":"gen-1791215148-PVpnAFb30EqLOaA4TUuP","object":"chat.completion.chunk","created":1791215148,"model":"qwen/qwen3-30b-a3b","provider":"DeepInfra","choices":[{"index":0,"delta":{"content":"","role":"assistant","reasoning":" and","reasoning_details":[{"type":"reasoning.text","text":" and","format":"unknown","index":0}]},"finish_reason":null,"native_finish_reason":null}]} - -data: {"id":"gen-1791215148-PVpnAFb30EqLOaA4TUuP","object":"chat.completion.chunk","created":1791215148,"model":"qwen/qwen3-30b-a3b","provider":"DeepInfra","choices":[{"index":0,"delta":{"content":"","role":"assistant","reasoning":" others","reasoning_details":[{"type":"reasoning.text","text":" others","format":"unknown","index":0}]},"finish_reason":null,"native_finish_reason":null}]} - -data: {"id":"gen-1791215148-PVpnAFb30EqLOaA4TUuP","object":"chat.completion.chunk","created":1791215148,"model":"qwen/qwen3-30b-a3b","provider":"DeepInfra","choices":[{"index":0,"delta":{"content":"","role":"assistant","reasoning":".","reasoning_details":[{"type":"reasoning.text","text":".","format":"unknown","index":0}]},"finish_reason":null,"native_finish_reason":null}]} - -data: {"id":"gen-1791215148-PVpnAFb30EqLOaA4TUuP","object":"chat.completion.chunk","created":1791215148,"model":"qwen/qwen3-30b-a3b","provider":"DeepInfra","choices":[{"index":0,"delta":{"content":"","role":"assistant","reasoning":" The","reasoning_details":[{"type":"reasoning.text","text":" The","format":"unknown","index":0}]},"finish_reason":null,"native_finish_reason":null}]} - -data: {"id":"gen-1791215148-PVpnAFb30EqLOaA4TUuP","object":"chat.completion.chunk","created":1791215148,"model":"qwen/qwen3-30b-a3b","provider":"DeepInfra","choices":[{"index":0,"delta":{"content":"","role":"assistant","reasoning":" instructions","reasoning_details":[{"type":"reasoning.text","text":" instructions","format":"unknown","index":0}]},"finish_reason":null,"native_finish_reason":null}]} - -data: {"id":"gen-1791215148-PVpnAFb30EqLOaA4TUuP","object":"chat.completion.chunk","created":1791215148,"model":"qwen/qwen3-30b-a3b","provider":"DeepInfra","choices":[{"index":0,"delta":{"content":"","role":"assistant","reasoning":" say","reasoning_details":[{"type":"reasoning.text","text":" say","format":"unknown","index":0}]},"finish_reason":null,"native_finish_reason":null}]} - -data: {"id":"gen-1791215148-PVpnAFb30EqLOaA4TUuP","object":"chat.completion.chunk","created":1791215148,"model":"qwen/qwen3-30b-a3b","provider":"DeepInfra","choices":[{"index":0,"delta":{"content":"","role":"assistant","reasoning":" that","reasoning_details":[{"type":"reasoning.text","text":" that","format":"unknown","index":0}]},"finish_reason":null,"native_finish_reason":null}]} - -data: {"id":"gen-1791215148-PVpnAFb30EqLOaA4TUuP","object":"chat.completion.chunk","created":1791215148,"model":"qwen/qwen3-30b-a3b","provider":"DeepInfra","choices":[{"index":0,"delta":{"content":"","role":"assistant","reasoning":" for","reasoning_details":[{"type":"reasoning.text","text":" for","format":"unknown","index":0}]},"finish_reason":null,"native_finish_reason":null}]} - -data: {"id":"gen-1791215148-PVpnAFb30EqLOaA4TUuP","object":"chat.completion.chunk","created":1791215148,"model":"qwen/qwen3-30b-a3b","provider":"DeepInfra","choices":[{"index":0,"delta":{"content":"","role":"assistant","reasoning":" every","reasoning_details":[{"type":"reasoning.text","text":" every","format":"unknown","index":0}]},"finish_reason":null,"native_finish_reason":null}]} - -data: {"id":"gen-1791215148-PVpnAFb30EqLOaA4TUuP","object":"chat.completion.chunk","created":1791215148,"model":"qwen/qwen3-30b-a3b","provider":"DeepInfra","choices":[{"index":0,"delta":{"content":"","role":"assistant","reasoning":" user","reasoning_details":[{"type":"reasoning.text","text":" user","format":"unknown","index":0}]},"finish_reason":null,"native_finish_reason":null}]} - -data: {"id":"gen-1791215148-PVpnAFb30EqLOaA4TUuP","object":"chat.completion.chunk","created":1791215148,"model":"qwen/qwen3-30b-a3b","provider":"DeepInfra","choices":[{"index":0,"delta":{"content":"","role":"assistant","reasoning":" task","reasoning_details":[{"type":"reasoning.text","text":" task","format":"unknown","index":0}]},"finish_reason":null,"native_finish_reason":null}]} - -data: {"id":"gen-1791215148-PVpnAFb30EqLOaA4TUuP","object":"chat.completion.chunk","created":1791215148,"model":"qwen/qwen3-30b-a3b","provider":"DeepInfra","choices":[{"index":0,"delta":{"content":"","role":"assistant","reasoning":",","reasoning_details":[{"type":"reasoning.text","text":",","format":"unknown","index":0}]},"finish_reason":null,"native_finish_reason":null}]} - -data: {"id":"gen-1791215148-PVpnAFb30EqLOaA4TUuP","object":"chat.completion.chunk","created":1791215148,"model":"qwen/qwen3-30b-a3b","provider":"DeepInfra","choices":[{"index":0,"delta":{"content":"","role":"assistant","reasoning":" I","reasoning_details":[{"type":"reasoning.text","text":" I","format":"unknown","index":0}]},"finish_reason":null,"native_finish_reason":null}]} - -data: {"id":"gen-1791215148-PVpnAFb30EqLOaA4TUuP","object":"chat.completion.chunk","created":1791215148,"model":"qwen/qwen3-30b-a3b","provider":"DeepInfra","choices":[{"index":0,"delta":{"content":"","role":"assistant","reasoning":" must","reasoning_details":[{"type":"reasoning.text","text":" must","format":"unknown","index":0}]},"finish_reason":null,"native_finish_reason":null}]} - -data: {"id":"gen-1791215148-PVpnAFb30EqLOaA4TUuP","object":"chat.completion.chunk","created":1791215148,"model":"qwen/qwen3-30b-a3b","provider":"DeepInfra","choices":[{"index":0,"delta":{"content":"","role":"assistant","reasoning":" first","reasoning_details":[{"type":"reasoning.text","text":" first","format":"unknown","index":0}]},"finish_reason":null,"native_finish_reason":null}]} - -data: {"id":"gen-1791215148-PVpnAFb30EqLOaA4TUuP","object":"chat.completion.chunk","created":1791215148,"model":"qwen/qwen3-30b-a3b","provider":"DeepInfra","choices":[{"index":0,"delta":{"content":"","role":"assistant","reasoning":" make","reasoning_details":[{"type":"reasoning.text","text":" make","format":"unknown","index":0}]},"finish_reason":null,"native_finish_reason":null}]} - -data: {"id":"gen-1791215148-PVpnAFb30EqLOaA4TUuP","object":"chat.completion.chunk","created":1791215148,"model":"qwen/qwen3-30b-a3b","provider":"DeepInfra","choices":[{"index":0,"delta":{"content":"","role":"assistant","reasoning":" two","reasoning_details":[{"type":"reasoning.text","text":" two","format":"unknown","index":0}]},"finish_reason":null,"native_finish_reason":null}]} - -data: {"id":"gen-1791215148-PVpnAFb30EqLOaA4TUuP","object":"chat.completion.chunk","created":1791215148,"model":"qwen/qwen3-30b-a3b","provider":"DeepInfra","choices":[{"index":0,"delta":{"content":"","role":"assistant","reasoning":" tool","reasoning_details":[{"type":"reasoning.text","text":" tool","format":"unknown","index":0}]},"finish_reason":null,"native_finish_reason":null}]} - -data: {"id":"gen-1791215148-PVpnAFb30EqLOaA4TUuP","object":"chat.completion.chunk","created":1791215148,"model":"qwen/qwen3-30b-a3b","provider":"DeepInfra","choices":[{"index":0,"delta":{"content":"","role":"assistant","reasoning":" calls","reasoning_details":[{"type":"reasoning.text","text":" calls","format":"unknown","index":0}]},"finish_reason":null,"native_finish_reason":null}]} - -data: {"id":"gen-1791215148-PVpnAFb30EqLOaA4TUuP","object":"chat.completion.chunk","created":1791215148,"model":"qwen/qwen3-30b-a3b","provider":"DeepInfra","choices":[{"index":0,"delta":{"content":"","role":"assistant","reasoning":":","reasoning_details":[{"type":"reasoning.text","text":":","format":"unknown","index":0}]},"finish_reason":null,"native_finish_reason":null}]} - -data: {"id":"gen-1791215148-PVpnAFb30EqLOaA4TUuP","object":"chat.completion.chunk","created":1791215148,"model":"qwen/qwen3-30b-a3b","provider":"DeepInfra","choices":[{"index":0,"delta":{"content":"","role":"assistant","reasoning":" researcher","reasoning_details":[{"type":"reasoning.text","text":" researcher","format":"unknown","index":0}]},"finish_reason":null,"native_finish_reason":null}]} - -data: {"id":"gen-1791215148-PVpnAFb30EqLOaA4TUuP","object":"chat.completion.chunk","created":1791215148,"model":"qwen/qwen3-30b-a3b","provider":"DeepInfra","choices":[{"index":0,"delta":{"content":"","role":"assistant","reasoning":" with","reasoning_details":[{"type":"reasoning.text","text":" with","format":"unknown","index":0}]},"finish_reason":null,"native_finish_reason":null}]} - -data: {"id":"gen-1791215148-PVpnAFb30EqLOaA4TUuP","object":"chat.completion.chunk","created":1791215148,"model":"qwen/qwen3-30b-a3b","provider":"DeepInfra","choices":[{"index":0,"delta":{"content":"","role":"assistant","reasoning":" the","reasoning_details":[{"type":"reasoning.text","text":" the","format":"unknown","index":0}]},"finish_reason":null,"native_finish_reason":null}]} - -data: {"id":"gen-1791215148-PVpnAFb30EqLOaA4TUuP","object":"chat.completion.chunk","created":1791215148,"model":"qwen/qwen3-30b-a3b","provider":"DeepInfra","choices":[{"index":0,"delta":{"content":"","role":"assistant","reasoning":" user","reasoning_details":[{"type":"reasoning.text","text":" user","format":"unknown","index":0}]},"finish_reason":null,"native_finish_reason":null}]} - -data: {"id":"gen-1791215148-PVpnAFb30EqLOaA4TUuP","object":"chat.completion.chunk","created":1791215148,"model":"qwen/qwen3-30b-a3b","provider":"DeepInfra","choices":[{"index":0,"delta":{"content":"","role":"assistant","reasoning":"'s","reasoning_details":[{"type":"reasoning.text","text":"'s","format":"unknown","index":0}]},"finish_reason":null,"native_finish_reason":null}]} - -data: {"id":"gen-1791215148-PVpnAFb30EqLOaA4TUuP","object":"chat.completion.chunk","created":1791215148,"model":"qwen/qwen3-30b-a3b","provider":"DeepInfra","choices":[{"index":0,"delta":{"content":"","role":"assistant","reasoning":" message","reasoning_details":[{"type":"reasoning.text","text":" message","format":"unknown","index":0}]},"finish_reason":null,"native_finish_reason":null}]} - -data: {"id":"gen-1791215148-PVpnAFb30EqLOaA4TUuP","object":"chat.completion.chunk","created":1791215148,"model":"qwen/qwen3-30b-a3b","provider":"DeepInfra","choices":[{"index":0,"delta":{"content":"","role":"assistant","reasoning":" and","reasoning_details":[{"type":"reasoning.text","text":" and","format":"unknown","index":0}]},"finish_reason":null,"native_finish_reason":null}]} - -data: {"id":"gen-1791215148-PVpnAFb30EqLOaA4TUuP","object":"chat.completion.chunk","created":1791215148,"model":"qwen/qwen3-30b-a3b","provider":"DeepInfra","choices":[{"index":0,"delta":{"content":"","role":"assistant","reasoning":" read","reasoning_details":[{"type":"reasoning.text","text":" read","format":"unknown","index":0}]},"finish_reason":null,"native_finish_reason":null}]} - -data: {"id":"gen-1791215148-PVpnAFb30EqLOaA4TUuP","object":"chat.completion.chunk","created":1791215148,"model":"qwen/qwen3-30b-a3b","provider":"DeepInfra","choices":[{"index":0,"delta":{"content":"","role":"assistant","reasoning":" with","reasoning_details":[{"type":"reasoning.text","text":" with","format":"unknown","index":0}]},"finish_reason":null,"native_finish_reason":null}]} - -data: {"id":"gen-1791215148-PVpnAFb30EqLOaA4TUuP","object":"chat.completion.chunk","created":1791215148,"model":"qwen/qwen3-30b-a3b","provider":"DeepInfra","choices":[{"index":0,"delta":{"content":"","role":"assistant","reasoning":" the","reasoning_details":[{"type":"reasoning.text","text":" the","format":"unknown","index":0}]},"finish_reason":null,"native_finish_reason":null}]} - -data: {"id":"gen-1791215148-PVpnAFb30EqLOaA4TUuP","object":"chat.completion.chunk","created":1791215148,"model":"qwen/qwen3-30b-a3b","provider":"DeepInfra","choices":[{"index":0,"delta":{"content":"","role":"assistant","reasoning":" specific","reasoning_details":[{"type":"reasoning.text","text":" specific","format":"unknown","index":0}]},"finish_reason":null,"native_finish_reason":null}]} - -data: {"id":"gen-1791215148-PVpnAFb30EqLOaA4TUuP","object":"chat.completion.chunk","created":1791215148,"model":"qwen/qwen3-30b-a3b","provider":"DeepInfra","choices":[{"index":0,"delta":{"content":"","role":"assistant","reasoning":" URL","reasoning_details":[{"type":"reasoning.text","text":" URL","format":"unknown","index":0}]},"finish_reason":null,"native_finish_reason":null}]} - -data: {"id":"gen-1791215148-PVpnAFb30EqLOaA4TUuP","object":"chat.completion.chunk","created":1791215148,"model":"qwen/qwen3-30b-a3b","provider":"DeepInfra","choices":[{"index":0,"delta":{"content":"","role":"assistant","reasoning":".","reasoning_details":[{"type":"reasoning.text","text":".","format":"unknown","index":0}]},"finish_reason":null,"native_finish_reason":null}]} - -data: {"id":"gen-1791215148-PVpnAFb30EqLOaA4TUuP","object":"chat.completion.chunk","created":1791215148,"model":"qwen/qwen3-30b-a3b","provider":"DeepInfra","choices":[{"index":0,"delta":{"content":"","role":"assistant","reasoning":" \n\n","reasoning_details":[{"type":"reasoning.text","text":" \n\n","format":"unknown","index":0}]},"finish_reason":null,"native_finish_reason":null}]} - -data: {"id":"gen-1791215148-PVpnAFb30EqLOaA4TUuP","object":"chat.completion.chunk","created":1791215148,"model":"qwen/qwen3-30b-a3b","provider":"DeepInfra","choices":[{"index":0,"delta":{"content":"","role":"assistant","reasoning":"The","reasoning_details":[{"type":"reasoning.text","text":"The","format":"unknown","index":0}]},"finish_reason":null,"native_finish_reason":null}]} - -data: {"id":"gen-1791215148-PVpnAFb30EqLOaA4TUuP","object":"chat.completion.chunk","created":1791215148,"model":"qwen/qwen3-30b-a3b","provider":"DeepInfra","choices":[{"index":0,"delta":{"content":"","role":"assistant","reasoning":" user","reasoning_details":[{"type":"reasoning.text","text":" user","format":"unknown","index":0}]},"finish_reason":null,"native_finish_reason":null}]} - -data: {"id":"gen-1791215148-PVpnAFb30EqLOaA4TUuP","object":"chat.completion.chunk","created":1791215148,"model":"qwen/qwen3-30b-a3b","provider":"DeepInfra","choices":[{"index":0,"delta":{"content":"","role":"assistant","reasoning":"'s","reasoning_details":[{"type":"reasoning.text","text":"'s","format":"unknown","index":0}]},"finish_reason":null,"native_finish_reason":null}]} - -data: {"id":"gen-1791215148-PVpnAFb30EqLOaA4TUuP","object":"chat.completion.chunk","created":1791215148,"model":"qwen/qwen3-30b-a3b","provider":"DeepInfra","choices":[{"index":0,"delta":{"content":"","role":"assistant","reasoning":" message","reasoning_details":[{"type":"reasoning.text","text":" message","format":"unknown","index":0}]},"finish_reason":null,"native_finish_reason":null}]} - -data: {"id":"gen-1791215148-PVpnAFb30EqLOaA4TUuP","object":"chat.completion.chunk","created":1791215148,"model":"qwen/qwen3-30b-a3b","provider":"DeepInfra","choices":[{"index":0,"delta":{"content":"","role":"assistant","reasoning":" is","reasoning_details":[{"type":"reasoning.text","text":" is","format":"unknown","index":0}]},"finish_reason":null,"native_finish_reason":null}]} - -data: {"id":"gen-1791215148-PVpnAFb30EqLOaA4TUuP","object":"chat.completion.chunk","created":1791215148,"model":"qwen/qwen3-30b-a3b","provider":"DeepInfra","choices":[{"index":0,"delta":{"content":"","role":"assistant","reasoning":" \"","reasoning_details":[{"type":"reasoning.text","text":" \"","format":"unknown","index":0}]},"finish_reason":null,"native_finish_reason":null}]} - -data: {"id":"gen-1791215148-PVpnAFb30EqLOaA4TUuP","object":"chat.completion.chunk","created":1791215148,"model":"qwen/qwen3-30b-a3b","provider":"DeepInfra","choices":[{"index":0,"delta":{"content":"","role":"assistant","reasoning":"Run","reasoning_details":[{"type":"reasoning.text","text":"Run","format":"unknown","index":0}]},"finish_reason":null,"native_finish_reason":null}]} - -data: {"id":"gen-1791215148-PVpnAFb30EqLOaA4TUuP","object":"chat.completion.chunk","created":1791215148,"model":"qwen/qwen3-30b-a3b","provider":"DeepInfra","choices":[{"index":0,"delta":{"content":"","role":"assistant","reasoning":" the","reasoning_details":[{"type":"reasoning.text","text":" the","format":"unknown","index":0}]},"finish_reason":null,"native_finish_reason":null}]} - -data: {"id":"gen-1791215148-PVpnAFb30EqLOaA4TUuP","object":"chat.completion.chunk","created":1791215148,"model":"qwen/qwen3-30b-a3b","provider":"DeepInfra","choices":[{"index":0,"delta":{"content":"","role":"assistant","reasoning":" Brain","reasoning_details":[{"type":"reasoning.text","text":" Brain","format":"unknown","index":0}]},"finish_reason":null,"native_finish_reason":null}]} - -data: {"id":"gen-1791215148-PVpnAFb30EqLOaA4TUuP","object":"chat.completion.chunk","created":1791215148,"model":"qwen/qwen3-30b-a3b","provider":"DeepInfra","choices":[{"index":0,"delta":{"content":"","role":"assistant","reasoning":"trust","reasoning_details":[{"type":"reasoning.text","text":"trust","format":"unknown","index":0}]},"finish_reason":null,"native_finish_reason":null}]} - -data: {"id":"gen-1791215148-PVpnAFb30EqLOaA4TUuP","object":"chat.completion.chunk","created":1791215148,"model":"qwen/qwen3-30b-a3b","provider":"DeepInfra","choices":[{"index":0,"delta":{"content":"","role":"assistant","reasoning":" Eve","reasoning_details":[{"type":"reasoning.text","text":" Eve","format":"unknown","index":0}]},"finish_reason":null,"native_finish_reason":null}]} - -data: {"id":"gen-1791215148-PVpnAFb30EqLOaA4TUuP","object":"chat.completion.chunk","created":1791215148,"model":"qwen/qwen3-30b-a3b","provider":"DeepInfra","choices":[{"index":0,"delta":{"content":"","role":"assistant","reasoning":" instrumentation","reasoning_details":[{"type":"reasoning.text","text":" instrumentation","format":"unknown","index":0}]},"finish_reason":null,"native_finish_reason":null}]} - -data: {"id":"gen-1791215148-PVpnAFb30EqLOaA4TUuP","object":"chat.completion.chunk","created":1791215148,"model":"qwen/qwen3-30b-a3b","provider":"DeepInfra","choices":[{"index":0,"delta":{"content":"","role":"assistant","reasoning":" e","reasoning_details":[{"type":"reasoning.text","text":" e","format":"unknown","index":0}]},"finish_reason":null,"native_finish_reason":null}]} - -data: {"id":"gen-1791215148-PVpnAFb30EqLOaA4TUuP","object":"chat.completion.chunk","created":1791215148,"model":"qwen/qwen3-30b-a3b","provider":"DeepInfra","choices":[{"index":0,"delta":{"content":"","role":"assistant","reasoning":"2","reasoning_details":[{"type":"reasoning.text","text":"2","format":"unknown","index":0}]},"finish_reason":null,"native_finish_reason":null}]} - -data: {"id":"gen-1791215148-PVpnAFb30EqLOaA4TUuP","object":"chat.completion.chunk","created":1791215148,"model":"qwen/qwen3-30b-a3b","provider":"DeepInfra","choices":[{"index":0,"delta":{"content":"","role":"assistant","reasoning":"e","reasoning_details":[{"type":"reasoning.text","text":"e","format":"unknown","index":0}]},"finish_reason":null,"native_finish_reason":null}]} - -data: {"id":"gen-1791215148-PVpnAFb30EqLOaA4TUuP","object":"chat.completion.chunk","created":1791215148,"model":"qwen/qwen3-30b-a3b","provider":"DeepInfra","choices":[{"index":0,"delta":{"content":"","role":"assistant","reasoning":" scenario","reasoning_details":[{"type":"reasoning.text","text":" scenario","format":"unknown","index":0}]},"finish_reason":null,"native_finish_reason":null}]} - -data: {"id":"gen-1791215148-PVpnAFb30EqLOaA4TUuP","object":"chat.completion.chunk","created":1791215148,"model":"qwen/qwen3-30b-a3b","provider":"DeepInfra","choices":[{"index":0,"delta":{"content":"","role":"assistant","reasoning":".\"","reasoning_details":[{"type":"reasoning.text","text":".\"","format":"unknown","index":0}]},"finish_reason":null,"native_finish_reason":null}]} - -data: {"id":"gen-1791215148-PVpnAFb30EqLOaA4TUuP","object":"chat.completion.chunk","created":1791215148,"model":"qwen/qwen3-30b-a3b","provider":"DeepInfra","choices":[{"index":0,"delta":{"content":"","role":"assistant","reasoning":" So","reasoning_details":[{"type":"reasoning.text","text":" So","format":"unknown","index":0}]},"finish_reason":null,"native_finish_reason":null}]} - -data: {"id":"gen-1791215148-PVpnAFb30EqLOaA4TUuP","object":"chat.completion.chunk","created":1791215148,"model":"qwen/qwen3-30b-a3b","provider":"DeepInfra","choices":[{"index":0,"delta":{"content":"","role":"assistant","reasoning":",","reasoning_details":[{"type":"reasoning.text","text":",","format":"unknown","index":0}]},"finish_reason":null,"native_finish_reason":null}]} - -data: {"id":"gen-1791215148-PVpnAFb30EqLOaA4TUuP","object":"chat.completion.chunk","created":1791215148,"model":"qwen/qwen3-30b-a3b","provider":"DeepInfra","choices":[{"index":0,"delta":{"content":"","role":"assistant","reasoning":" I","reasoning_details":[{"type":"reasoning.text","text":" I","format":"unknown","index":0}]},"finish_reason":null,"native_finish_reason":null}]} - -data: {"id":"gen-1791215148-PVpnAFb30EqLOaA4TUuP","object":"chat.completion.chunk","created":1791215148,"model":"qwen/qwen3-30b-a3b","provider":"DeepInfra","choices":[{"index":0,"delta":{"content":"","role":"assistant","reasoning":" should","reasoning_details":[{"type":"reasoning.text","text":" should","format":"unknown","index":0}]},"finish_reason":null,"native_finish_reason":null}]} - -data: {"id":"gen-1791215148-PVpnAFb30EqLOaA4TUuP","object":"chat.completion.chunk","created":1791215148,"model":"qwen/qwen3-30b-a3b","provider":"DeepInfra","choices":[{"index":0,"delta":{"content":"","role":"assistant","reasoning":" call","reasoning_details":[{"type":"reasoning.text","text":" call","format":"unknown","index":0}]},"finish_reason":null,"native_finish_reason":null}]} - -data: {"id":"gen-1791215148-PVpnAFb30EqLOaA4TUuP","object":"chat.completion.chunk","created":1791215148,"model":"qwen/qwen3-30b-a3b","provider":"DeepInfra","choices":[{"index":0,"delta":{"content":"","role":"assistant","reasoning":" the","reasoning_details":[{"type":"reasoning.text","text":" the","format":"unknown","index":0}]},"finish_reason":null,"native_finish_reason":null}]} - -data: {"id":"gen-1791215148-PVpnAFb30EqLOaA4TUuP","object":"chat.completion.chunk","created":1791215148,"model":"qwen/qwen3-30b-a3b","provider":"DeepInfra","choices":[{"index":0,"delta":{"content":"","role":"assistant","reasoning":" researcher","reasoning_details":[{"type":"reasoning.text","text":" researcher","format":"unknown","index":0}]},"finish_reason":null,"native_finish_reason":null}]} - -data: {"id":"gen-1791215148-PVpnAFb30EqLOaA4TUuP","object":"chat.completion.chunk","created":1791215148,"model":"qwen/qwen3-30b-a3b","provider":"DeepInfra","choices":[{"index":0,"delta":{"content":"","role":"assistant","reasoning":" function","reasoning_details":[{"type":"reasoning.text","text":" function","format":"unknown","index":0}]},"finish_reason":null,"native_finish_reason":null}]} - -data: {"id":"gen-1791215148-PVpnAFb30EqLOaA4TUuP","object":"chat.completion.chunk","created":1791215148,"model":"qwen/qwen3-30b-a3b","provider":"DeepInfra","choices":[{"index":0,"delta":{"content":"","role":"assistant","reasoning":" with","reasoning_details":[{"type":"reasoning.text","text":" with","format":"unknown","index":0}]},"finish_reason":null,"native_finish_reason":null}]} - -data: {"id":"gen-1791215148-PVpnAFb30EqLOaA4TUuP","object":"chat.completion.chunk","created":1791215148,"model":"qwen/qwen3-30b-a3b","provider":"DeepInfra","choices":[{"index":0,"delta":{"content":"","role":"assistant","reasoning":" this","reasoning_details":[{"type":"reasoning.text","text":" this","format":"unknown","index":0}]},"finish_reason":null,"native_finish_reason":null}]} - -data: {"id":"gen-1791215148-PVpnAFb30EqLOaA4TUuP","object":"chat.completion.chunk","created":1791215148,"model":"qwen/qwen3-30b-a3b","provider":"DeepInfra","choices":[{"index":0,"delta":{"content":"","role":"assistant","reasoning":" message","reasoning_details":[{"type":"reasoning.text","text":" message","format":"unknown","index":0}]},"finish_reason":null,"native_finish_reason":null}]} - -data: {"id":"gen-1791215148-PVpnAFb30EqLOaA4TUuP","object":"chat.completion.chunk","created":1791215148,"model":"qwen/qwen3-30b-a3b","provider":"DeepInfra","choices":[{"index":0,"delta":{"content":"","role":"assistant","reasoning":".","reasoning_details":[{"type":"reasoning.text","text":".","format":"unknown","index":0}]},"finish_reason":null,"native_finish_reason":null}]} - -data: {"id":"gen-1791215148-PVpnAFb30EqLOaA4TUuP","object":"chat.completion.chunk","created":1791215148,"model":"qwen/qwen3-30b-a3b","provider":"DeepInfra","choices":[{"index":0,"delta":{"content":"","role":"assistant","reasoning":" Then","reasoning_details":[{"type":"reasoning.text","text":" Then","format":"unknown","index":0}]},"finish_reason":null,"native_finish_reason":null}]} - -data: {"id":"gen-1791215148-PVpnAFb30EqLOaA4TUuP","object":"chat.completion.chunk","created":1791215148,"model":"qwen/qwen3-30b-a3b","provider":"DeepInfra","choices":[{"index":0,"delta":{"content":"","role":"assistant","reasoning":",","reasoning_details":[{"type":"reasoning.text","text":",","format":"unknown","index":0}]},"finish_reason":null,"native_finish_reason":null}]} - -data: {"id":"gen-1791215148-PVpnAFb30EqLOaA4TUuP","object":"chat.completion.chunk","created":1791215148,"model":"qwen/qwen3-30b-a3b","provider":"DeepInfra","choices":[{"index":0,"delta":{"content":"","role":"assistant","reasoning":" the","reasoning_details":[{"type":"reasoning.text","text":" the","format":"unknown","index":0}]},"finish_reason":null,"native_finish_reason":null}]} - -data: {"id":"gen-1791215148-PVpnAFb30EqLOaA4TUuP","object":"chat.completion.chunk","created":1791215148,"model":"qwen/qwen3-30b-a3b","provider":"DeepInfra","choices":[{"index":0,"delta":{"content":"","role":"assistant","reasoning":" read","reasoning_details":[{"type":"reasoning.text","text":" read","format":"unknown","index":0}]},"finish_reason":null,"native_finish_reason":null}]} - -data: {"id":"gen-1791215148-PVpnAFb30EqLOaA4TUuP","object":"chat.completion.chunk","created":1791215148,"model":"qwen/qwen3-30b-a3b","provider":"DeepInfra","choices":[{"index":0,"delta":{"content":"","role":"assistant","reasoning":" function","reasoning_details":[{"type":"reasoning.text","text":" function","format":"unknown","index":0}]},"finish_reason":null,"native_finish_reason":null}]} - -data: {"id":"gen-1791215148-PVpnAFb30EqLOaA4TUuP","object":"chat.completion.chunk","created":1791215148,"model":"qwen/qwen3-30b-a3b","provider":"DeepInfra","choices":[{"index":0,"delta":{"content":"","role":"assistant","reasoning":" should","reasoning_details":[{"type":"reasoning.text","text":" should","format":"unknown","index":0}]},"finish_reason":null,"native_finish_reason":null}]} - -data: {"id":"gen-1791215148-PVpnAFb30EqLOaA4TUuP","object":"chat.completion.chunk","created":1791215148,"model":"qwen/qwen3-30b-a3b","provider":"DeepInfra","choices":[{"index":0,"delta":{"content":"","role":"assistant","reasoning":" access","reasoning_details":[{"type":"reasoning.text","text":" access","format":"unknown","index":0}]},"finish_reason":null,"native_finish_reason":null}]} - -data: {"id":"gen-1791215148-PVpnAFb30EqLOaA4TUuP","object":"chat.completion.chunk","created":1791215148,"model":"qwen/qwen3-30b-a3b","provider":"DeepInfra","choices":[{"index":0,"delta":{"content":"","role":"assistant","reasoning":" the","reasoning_details":[{"type":"reasoning.text","text":" the","format":"unknown","index":0}]},"finish_reason":null,"native_finish_reason":null}]} - -data: {"id":"gen-1791215148-PVpnAFb30EqLOaA4TUuP","object":"chat.completion.chunk","created":1791215148,"model":"qwen/qwen3-30b-a3b","provider":"DeepInfra","choices":[{"index":0,"delta":{"content":"","role":"assistant","reasoning":" documentation","reasoning_details":[{"type":"reasoning.text","text":" documentation","format":"unknown","index":0}]},"finish_reason":null,"native_finish_reason":null}]} - -data: {"id":"gen-1791215148-PVpnAFb30EqLOaA4TUuP","object":"chat.completion.chunk","created":1791215148,"model":"qwen/qwen3-30b-a3b","provider":"DeepInfra","choices":[{"index":0,"delta":{"content":"","role":"assistant","reasoning":" URL","reasoning_details":[{"type":"reasoning.text","text":" URL","format":"unknown","index":0}]},"finish_reason":null,"native_finish_reason":null}]} - -data: {"id":"gen-1791215148-PVpnAFb30EqLOaA4TUuP","object":"chat.completion.chunk","created":1791215148,"model":"qwen/qwen3-30b-a3b","provider":"DeepInfra","choices":[{"index":0,"delta":{"content":"","role":"assistant","reasoning":" provided","reasoning_details":[{"type":"reasoning.text","text":" provided","format":"unknown","index":0}]},"finish_reason":null,"native_finish_reason":null}]} - -data: {"id":"gen-1791215148-PVpnAFb30EqLOaA4TUuP","object":"chat.completion.chunk","created":1791215148,"model":"qwen/qwen3-30b-a3b","provider":"DeepInfra","choices":[{"index":0,"delta":{"content":"","role":"assistant","reasoning":" earlier","reasoning_details":[{"type":"reasoning.text","text":" earlier","format":"unknown","index":0}]},"finish_reason":null,"native_finish_reason":null}]} - -data: {"id":"gen-1791215148-PVpnAFb30EqLOaA4TUuP","object":"chat.completion.chunk","created":1791215148,"model":"qwen/qwen3-30b-a3b","provider":"DeepInfra","choices":[{"index":0,"delta":{"content":"","role":"assistant","reasoning":":","reasoning_details":[{"type":"reasoning.text","text":":","format":"unknown","index":0}]},"finish_reason":null,"native_finish_reason":null}]} - -data: {"id":"gen-1791215148-PVpnAFb30EqLOaA4TUuP","object":"chat.completion.chunk","created":1791215148,"model":"qwen/qwen3-30b-a3b","provider":"DeepInfra","choices":[{"index":0,"delta":{"content":"","role":"assistant","reasoning":" https","reasoning_details":[{"type":"reasoning.text","text":" https","format":"unknown","index":0}]},"finish_reason":null,"native_finish_reason":null}]} - -data: {"id":"gen-1791215148-PVpnAFb30EqLOaA4TUuP","object":"chat.completion.chunk","created":1791215148,"model":"qwen/qwen3-30b-a3b","provider":"DeepInfra","choices":[{"index":0,"delta":{"content":"","role":"assistant","reasoning":"://","reasoning_details":[{"type":"reasoning.text","text":"://","format":"unknown","index":0}]},"finish_reason":null,"native_finish_reason":null}]} - -data: {"id":"gen-1791215148-PVpnAFb30EqLOaA4TUuP","object":"chat.completion.chunk","created":1791215148,"model":"qwen/qwen3-30b-a3b","provider":"DeepInfra","choices":[{"index":0,"delta":{"content":"","role":"assistant","reasoning":"eve","reasoning_details":[{"type":"reasoning.text","text":"eve","format":"unknown","index":0}]},"finish_reason":null,"native_finish_reason":null}]} - -data: {"id":"gen-1791215148-PVpnAFb30EqLOaA4TUuP","object":"chat.completion.chunk","created":1791215148,"model":"qwen/qwen3-30b-a3b","provider":"DeepInfra","choices":[{"index":0,"delta":{"content":"","role":"assistant","reasoning":".dev","reasoning_details":[{"type":"reasoning.text","text":".dev","format":"unknown","index":0}]},"finish_reason":null,"native_finish_reason":null}]} - -data: {"id":"gen-1791215148-PVpnAFb30EqLOaA4TUuP","object":"chat.completion.chunk","created":1791215148,"model":"qwen/qwen3-30b-a3b","provider":"DeepInfra","choices":[{"index":0,"delta":{"content":"","role":"assistant","reasoning":"/docs","reasoning_details":[{"type":"reasoning.text","text":"/docs","format":"unknown","index":0}]},"finish_reason":null,"native_finish_reason":null}]} - -data: {"id":"gen-1791215148-PVpnAFb30EqLOaA4TUuP","object":"chat.completion.chunk","created":1791215148,"model":"qwen/qwen3-30b-a3b","provider":"DeepInfra","choices":[{"index":0,"delta":{"content":"","role":"assistant","reasoning":"/g","reasoning_details":[{"type":"reasoning.text","text":"/g","format":"unknown","index":0}]},"finish_reason":null,"native_finish_reason":null}]} - -data: {"id":"gen-1791215148-PVpnAFb30EqLOaA4TUuP","object":"chat.completion.chunk","created":1791215148,"model":"qwen/qwen3-30b-a3b","provider":"DeepInfra","choices":[{"index":0,"delta":{"content":"","role":"assistant","reasoning":"uid","reasoning_details":[{"type":"reasoning.text","text":"uid","format":"unknown","index":0}]},"finish_reason":null,"native_finish_reason":null}]} - -data: {"id":"gen-1791215148-PVpnAFb30EqLOaA4TUuP","object":"chat.completion.chunk","created":1791215148,"model":"qwen/qwen3-30b-a3b","provider":"DeepInfra","choices":[{"index":0,"delta":{"content":"","role":"assistant","reasoning":"es","reasoning_details":[{"type":"reasoning.text","text":"es","format":"unknown","index":0}]},"finish_reason":null,"native_finish_reason":null}]} - -data: {"id":"gen-1791215148-PVpnAFb30EqLOaA4TUuP","object":"chat.completion.chunk","created":1791215148,"model":"qwen/qwen3-30b-a3b","provider":"DeepInfra","choices":[{"index":0,"delta":{"content":"","role":"assistant","reasoning":"/in","reasoning_details":[{"type":"reasoning.text","text":"/in","format":"unknown","index":0}]},"finish_reason":null,"native_finish_reason":null}]} - -data: {"id":"gen-1791215148-PVpnAFb30EqLOaA4TUuP","object":"chat.completion.chunk","created":1791215148,"model":"qwen/qwen3-30b-a3b","provider":"DeepInfra","choices":[{"index":0,"delta":{"content":"","role":"assistant","reasoning":"strument","reasoning_details":[{"type":"reasoning.text","text":"strument","format":"unknown","index":0}]},"finish_reason":null,"native_finish_reason":null}]} - -data: {"id":"gen-1791215148-PVpnAFb30EqLOaA4TUuP","object":"chat.completion.chunk","created":1791215148,"model":"qwen/qwen3-30b-a3b","provider":"DeepInfra","choices":[{"index":0,"delta":{"content":"","role":"assistant","reasoning":"ation","reasoning_details":[{"type":"reasoning.text","text":"ation","format":"unknown","index":0}]},"finish_reason":null,"native_finish_reason":null}]} - -data: {"id":"gen-1791215148-PVpnAFb30EqLOaA4TUuP","object":"chat.completion.chunk","created":1791215148,"model":"qwen/qwen3-30b-a3b","provider":"DeepInfra","choices":[{"index":0,"delta":{"content":"","role":"assistant","reasoning":".","reasoning_details":[{"type":"reasoning.text","text":".","format":"unknown","index":0}]},"finish_reason":null,"native_finish_reason":null}]} - -data: {"id":"gen-1791215148-PVpnAFb30EqLOaA4TUuP","object":"chat.completion.chunk","created":1791215148,"model":"qwen/qwen3-30b-a3b","provider":"DeepInfra","choices":[{"index":0,"delta":{"content":"","role":"assistant","reasoning":" \n\n","reasoning_details":[{"type":"reasoning.text","text":" \n\n","format":"unknown","index":0}]},"finish_reason":null,"native_finish_reason":null}]} - -data: {"id":"gen-1791215148-PVpnAFb30EqLOaA4TUuP","object":"chat.completion.chunk","created":1791215148,"model":"qwen/qwen3-30b-a3b","provider":"DeepInfra","choices":[{"index":0,"delta":{"content":"","role":"assistant","reasoning":"I","reasoning_details":[{"type":"reasoning.text","text":"I","format":"unknown","index":0}]},"finish_reason":null,"native_finish_reason":null}]} - -data: {"id":"gen-1791215148-PVpnAFb30EqLOaA4TUuP","object":"chat.completion.chunk","created":1791215148,"model":"qwen/qwen3-30b-a3b","provider":"DeepInfra","choices":[{"index":0,"delta":{"content":"","role":"assistant","reasoning":" need","reasoning_details":[{"type":"reasoning.text","text":" need","format":"unknown","index":0}]},"finish_reason":null,"native_finish_reason":null}]} - -data: {"id":"gen-1791215148-PVpnAFb30EqLOaA4TUuP","object":"chat.completion.chunk","created":1791215148,"model":"qwen/qwen3-30b-a3b","provider":"DeepInfra","choices":[{"index":0,"delta":{"content":"","role":"assistant","reasoning":" to","reasoning_details":[{"type":"reasoning.text","text":" to","format":"unknown","index":0}]},"finish_reason":null,"native_finish_reason":null}]} - -data: {"id":"gen-1791215148-PVpnAFb30EqLOaA4TUuP","object":"chat.completion.chunk","created":1791215148,"model":"qwen/qwen3-30b-a3b","provider":"DeepInfra","choices":[{"index":0,"delta":{"content":"","role":"assistant","reasoning":" make","reasoning_details":[{"type":"reasoning.text","text":" make","format":"unknown","index":0}]},"finish_reason":null,"native_finish_reason":null}]} - -data: {"id":"gen-1791215148-PVpnAFb30EqLOaA4TUuP","object":"chat.completion.chunk","created":1791215148,"model":"qwen/qwen3-30b-a3b","provider":"DeepInfra","choices":[{"index":0,"delta":{"content":"","role":"assistant","reasoning":" sure","reasoning_details":[{"type":"reasoning.text","text":" sure","format":"unknown","index":0}]},"finish_reason":null,"native_finish_reason":null}]} - -data: {"id":"gen-1791215148-PVpnAFb30EqLOaA4TUuP","object":"chat.completion.chunk","created":1791215148,"model":"qwen/qwen3-30b-a3b","provider":"DeepInfra","choices":[{"index":0,"delta":{"content":"","role":"assistant","reasoning":" both","reasoning_details":[{"type":"reasoning.text","text":" both","format":"unknown","index":0}]},"finish_reason":null,"native_finish_reason":null}]} - -data: {"id":"gen-1791215148-PVpnAFb30EqLOaA4TUuP","object":"chat.completion.chunk","created":1791215148,"model":"qwen/qwen3-30b-a3b","provider":"DeepInfra","choices":[{"index":0,"delta":{"content":"","role":"assistant","reasoning":" calls","reasoning_details":[{"type":"reasoning.text","text":" calls","format":"unknown","index":0}]},"finish_reason":null,"native_finish_reason":null}]} - -data: {"id":"gen-1791215148-PVpnAFb30EqLOaA4TUuP","object":"chat.completion.chunk","created":1791215148,"model":"qwen/qwen3-30b-a3b","provider":"DeepInfra","choices":[{"index":0,"delta":{"content":"","role":"assistant","reasoning":" are","reasoning_details":[{"type":"reasoning.text","text":" are","format":"unknown","index":0}]},"finish_reason":null,"native_finish_reason":null}]} - -data: {"id":"gen-1791215148-PVpnAFb30EqLOaA4TUuP","object":"chat.completion.chunk","created":1791215148,"model":"qwen/qwen3-30b-a3b","provider":"DeepInfra","choices":[{"index":0,"delta":{"content":"","role":"assistant","reasoning":" made","reasoning_details":[{"type":"reasoning.text","text":" made","format":"unknown","index":0}]},"finish_reason":null,"native_finish_reason":null}]} - -data: {"id":"gen-1791215148-PVpnAFb30EqLOaA4TUuP","object":"chat.completion.chunk","created":1791215148,"model":"qwen/qwen3-30b-a3b","provider":"DeepInfra","choices":[{"index":0,"delta":{"content":"","role":"assistant","reasoning":" in","reasoning_details":[{"type":"reasoning.text","text":" in","format":"unknown","index":0}]},"finish_reason":null,"native_finish_reason":null}]} - -data: {"id":"gen-1791215148-PVpnAFb30EqLOaA4TUuP","object":"chat.completion.chunk","created":1791215148,"model":"qwen/qwen3-30b-a3b","provider":"DeepInfra","choices":[{"index":0,"delta":{"content":"","role":"assistant","reasoning":" the","reasoning_details":[{"type":"reasoning.text","text":" the","format":"unknown","index":0}]},"finish_reason":null,"native_finish_reason":null}]} - -data: {"id":"gen-1791215148-PVpnAFb30EqLOaA4TUuP","object":"chat.completion.chunk","created":1791215148,"model":"qwen/qwen3-30b-a3b","provider":"DeepInfra","choices":[{"index":0,"delta":{"content":"","role":"assistant","reasoning":" same","reasoning_details":[{"type":"reasoning.text","text":" same","format":"unknown","index":0}]},"finish_reason":null,"native_finish_reason":null}]} - -data: {"id":"gen-1791215148-PVpnAFb30EqLOaA4TUuP","object":"chat.completion.chunk","created":1791215148,"model":"qwen/qwen3-30b-a3b","provider":"DeepInfra","choices":[{"index":0,"delta":{"content":"","role":"assistant","reasoning":" response","reasoning_details":[{"type":"reasoning.text","text":" response","format":"unknown","index":0}]},"finish_reason":null,"native_finish_reason":null}]} - -data: {"id":"gen-1791215148-PVpnAFb30EqLOaA4TUuP","object":"chat.completion.chunk","created":1791215148,"model":"qwen/qwen3-30b-a3b","provider":"DeepInfra","choices":[{"index":0,"delta":{"content":"","role":"assistant","reasoning":",","reasoning_details":[{"type":"reasoning.text","text":",","format":"unknown","index":0}]},"finish_reason":null,"native_finish_reason":null}]} - -data: {"id":"gen-1791215148-PVpnAFb30EqLOaA4TUuP","object":"chat.completion.chunk","created":1791215148,"model":"qwen/qwen3-30b-a3b","provider":"DeepInfra","choices":[{"index":0,"delta":{"content":"","role":"assistant","reasoning":" as","reasoning_details":[{"type":"reasoning.text","text":" as","format":"unknown","index":0}]},"finish_reason":null,"native_finish_reason":null}]} - -data: {"id":"gen-1791215148-PVpnAFb30EqLOaA4TUuP","object":"chat.completion.chunk","created":1791215148,"model":"qwen/qwen3-30b-a3b","provider":"DeepInfra","choices":[{"index":0,"delta":{"content":"","role":"assistant","reasoning":" per","reasoning_details":[{"type":"reasoning.text","text":" per","format":"unknown","index":0}]},"finish_reason":null,"native_finish_reason":null}]} - -data: {"id":"gen-1791215148-PVpnAFb30EqLOaA4TUuP","object":"chat.completion.chunk","created":1791215148,"model":"qwen/qwen3-30b-a3b","provider":"DeepInfra","choices":[{"index":0,"delta":{"content":"","role":"assistant","reasoning":" the","reasoning_details":[{"type":"reasoning.text","text":" the","format":"unknown","index":0}]},"finish_reason":null,"native_finish_reason":null}]} - -data: {"id":"gen-1791215148-PVpnAFb30EqLOaA4TUuP","object":"chat.completion.chunk","created":1791215148,"model":"qwen/qwen3-30b-a3b","provider":"DeepInfra","choices":[{"index":0,"delta":{"content":"","role":"assistant","reasoning":" instructions","reasoning_details":[{"type":"reasoning.text","text":" instructions","format":"unknown","index":0}]},"finish_reason":null,"native_finish_reason":null}]} - -data: {"id":"gen-1791215148-PVpnAFb30EqLOaA4TUuP","object":"chat.completion.chunk","created":1791215148,"model":"qwen/qwen3-30b-a3b","provider":"DeepInfra","choices":[{"index":0,"delta":{"content":"","role":"assistant","reasoning":".","reasoning_details":[{"type":"reasoning.text","text":".","format":"unknown","index":0}]},"finish_reason":null,"native_finish_reason":null}]} - -data: {"id":"gen-1791215148-PVpnAFb30EqLOaA4TUuP","object":"chat.completion.chunk","created":1791215148,"model":"qwen/qwen3-30b-a3b","provider":"DeepInfra","choices":[{"index":0,"delta":{"content":"","role":"assistant","reasoning":" No","reasoning_details":[{"type":"reasoning.text","text":" No","format":"unknown","index":0}]},"finish_reason":null,"native_finish_reason":null}]} - -data: {"id":"gen-1791215148-PVpnAFb30EqLOaA4TUuP","object":"chat.completion.chunk","created":1791215148,"model":"qwen/qwen3-30b-a3b","provider":"DeepInfra","choices":[{"index":0,"delta":{"content":"","role":"assistant","reasoning":" text","reasoning_details":[{"type":"reasoning.text","text":" text","format":"unknown","index":0}]},"finish_reason":null,"native_finish_reason":null}]} - -data: {"id":"gen-1791215148-PVpnAFb30EqLOaA4TUuP","object":"chat.completion.chunk","created":1791215148,"model":"qwen/qwen3-30b-a3b","provider":"DeepInfra","choices":[{"index":0,"delta":{"content":"","role":"assistant","reasoning":",","reasoning_details":[{"type":"reasoning.text","text":",","format":"unknown","index":0}]},"finish_reason":null,"native_finish_reason":null}]} - -data: {"id":"gen-1791215148-PVpnAFb30EqLOaA4TUuP","object":"chat.completion.chunk","created":1791215148,"model":"qwen/qwen3-30b-a3b","provider":"DeepInfra","choices":[{"index":0,"delta":{"content":"","role":"assistant","reasoning":" just","reasoning_details":[{"type":"reasoning.text","text":" just","format":"unknown","index":0}]},"finish_reason":null,"native_finish_reason":null}]} - -data: {"id":"gen-1791215148-PVpnAFb30EqLOaA4TUuP","object":"chat.completion.chunk","created":1791215148,"model":"qwen/qwen3-30b-a3b","provider":"DeepInfra","choices":[{"index":0,"delta":{"content":"","role":"assistant","reasoning":" the","reasoning_details":[{"type":"reasoning.text","text":" the","format":"unknown","index":0}]},"finish_reason":null,"native_finish_reason":null}]} - -data: {"id":"gen-1791215148-PVpnAFb30EqLOaA4TUuP","object":"chat.completion.chunk","created":1791215148,"model":"qwen/qwen3-30b-a3b","provider":"DeepInfra","choices":[{"index":0,"delta":{"content":"","role":"assistant","reasoning":" two","reasoning_details":[{"type":"reasoning.text","text":" two","format":"unknown","index":0}]},"finish_reason":null,"native_finish_reason":null}]} - -data: {"id":"gen-1791215148-PVpnAFb30EqLOaA4TUuP","object":"chat.completion.chunk","created":1791215148,"model":"qwen/qwen3-30b-a3b","provider":"DeepInfra","choices":[{"index":0,"delta":{"content":"","role":"assistant","reasoning":" tool","reasoning_details":[{"type":"reasoning.text","text":" tool","format":"unknown","index":0}]},"finish_reason":null,"native_finish_reason":null}]} - -data: {"id":"gen-1791215148-PVpnAFb30EqLOaA4TUuP","object":"chat.completion.chunk","created":1791215148,"model":"qwen/qwen3-30b-a3b","provider":"DeepInfra","choices":[{"index":0,"delta":{"content":"","role":"assistant","reasoning":" calls","reasoning_details":[{"type":"reasoning.text","text":" calls","format":"unknown","index":0}]},"finish_reason":null,"native_finish_reason":null}]} - -data: {"id":"gen-1791215148-PVpnAFb30EqLOaA4TUuP","object":"chat.completion.chunk","created":1791215148,"model":"qwen/qwen3-30b-a3b","provider":"DeepInfra","choices":[{"index":0,"delta":{"content":"","role":"assistant","reasoning":".","reasoning_details":[{"type":"reasoning.text","text":".","format":"unknown","index":0}]},"finish_reason":null,"native_finish_reason":null}]} - -data: {"id":"gen-1791215148-PVpnAFb30EqLOaA4TUuP","object":"chat.completion.chunk","created":1791215148,"model":"qwen/qwen3-30b-a3b","provider":"DeepInfra","choices":[{"index":0,"delta":{"content":"","role":"assistant","reasoning":" Then","reasoning_details":[{"type":"reasoning.text","text":" Then","format":"unknown","index":0}]},"finish_reason":null,"native_finish_reason":null}]} - -data: {"id":"gen-1791215148-PVpnAFb30EqLOaA4TUuP","object":"chat.completion.chunk","created":1791215148,"model":"qwen/qwen3-30b-a3b","provider":"DeepInfra","choices":[{"index":0,"delta":{"content":"","role":"assistant","reasoning":",","reasoning_details":[{"type":"reasoning.text","text":",","format":"unknown","index":0}]},"finish_reason":null,"native_finish_reason":null}]} - -data: {"id":"gen-1791215148-PVpnAFb30EqLOaA4TUuP","object":"chat.completion.chunk","created":1791215148,"model":"qwen/qwen3-30b-a3b","provider":"DeepInfra","choices":[{"index":0,"delta":{"content":"","role":"assistant","reasoning":" if","reasoning_details":[{"type":"reasoning.text","text":" if","format":"unknown","index":0}]},"finish_reason":null,"native_finish_reason":null}]} - -data: {"id":"gen-1791215148-PVpnAFb30EqLOaA4TUuP","object":"chat.completion.chunk","created":1791215148,"model":"qwen/qwen3-30b-a3b","provider":"DeepInfra","choices":[{"index":0,"delta":{"content":"","role":"assistant","reasoning":" the","reasoning_details":[{"type":"reasoning.text","text":" the","format":"unknown","index":0}]},"finish_reason":null,"native_finish_reason":null}]} - -data: {"id":"gen-1791215148-PVpnAFb30EqLOaA4TUuP","object":"chat.completion.chunk","created":1791215148,"model":"qwen/qwen3-30b-a3b","provider":"DeepInfra","choices":[{"index":0,"delta":{"content":"","role":"assistant","reasoning":" tool","reasoning_details":[{"type":"reasoning.text","text":" tool","format":"unknown","index":0}]},"finish_reason":null,"native_finish_reason":null}]} - -data: {"id":"gen-1791215148-PVpnAFb30EqLOaA4TUuP","object":"chat.completion.chunk","created":1791215148,"model":"qwen/qwen3-30b-a3b","provider":"DeepInfra","choices":[{"index":0,"delta":{"content":"","role":"assistant","reasoning":" results","reasoning_details":[{"type":"reasoning.text","text":" results","format":"unknown","index":0}]},"finish_reason":null,"native_finish_reason":null}]} - -data: {"id":"gen-1791215148-PVpnAFb30EqLOaA4TUuP","object":"chat.completion.chunk","created":1791215148,"model":"qwen/qwen3-30b-a3b","provider":"DeepInfra","choices":[{"index":0,"delta":{"content":"","role":"assistant","reasoning":" say","reasoning_details":[{"type":"reasoning.text","text":" say","format":"unknown","index":0}]},"finish_reason":null,"native_finish_reason":null}]} - -data: {"id":"gen-1791215148-PVpnAFb30EqLOaA4TUuP","object":"chat.completion.chunk","created":1791215148,"model":"qwen/qwen3-30b-a3b","provider":"DeepInfra","choices":[{"index":0,"delta":{"content":"","role":"assistant","reasoning":" \"","reasoning_details":[{"type":"reasoning.text","text":" \"","format":"unknown","index":0}]},"finish_reason":null,"native_finish_reason":null}]} - -data: {"id":"gen-1791215148-PVpnAFb30EqLOaA4TUuP","object":"chat.completion.chunk","created":1791215148,"model":"qwen/qwen3-30b-a3b","provider":"DeepInfra","choices":[{"index":0,"delta":{"content":"","role":"assistant","reasoning":"Started","reasoning_details":[{"type":"reasoning.text","text":"Started","format":"unknown","index":0}]},"finish_reason":null,"native_finish_reason":null}]} - -data: {"id":"gen-1791215148-PVpnAFb30EqLOaA4TUuP","object":"chat.completion.chunk","created":1791215148,"model":"qwen/qwen3-30b-a3b","provider":"DeepInfra","choices":[{"index":0,"delta":{"content":"","role":"assistant","reasoning":" task","reasoning_details":[{"type":"reasoning.text","text":" task","format":"unknown","index":0}]},"finish_reason":null,"native_finish_reason":null}]} - -data: {"id":"gen-1791215148-PVpnAFb30EqLOaA4TUuP","object":"chat.completion.chunk","created":1791215148,"model":"qwen/qwen3-30b-a3b","provider":"DeepInfra","choices":[{"index":0,"delta":{"content":"","role":"assistant","reasoning":",\"","reasoning_details":[{"type":"reasoning.text","text":",\"","format":"unknown","index":0}]},"finish_reason":null,"native_finish_reason":null}]} - -data: {"id":"gen-1791215148-PVpnAFb30EqLOaA4TUuP","object":"chat.completion.chunk","created":1791215148,"model":"qwen/qwen3-30b-a3b","provider":"DeepInfra","choices":[{"index":0,"delta":{"content":"","role":"assistant","reasoning":" I","reasoning_details":[{"type":"reasoning.text","text":" I","format":"unknown","index":0}]},"finish_reason":null,"native_finish_reason":null}]} - -data: {"id":"gen-1791215148-PVpnAFb30EqLOaA4TUuP","object":"chat.completion.chunk","created":1791215148,"model":"qwen/qwen3-30b-a3b","provider":"DeepInfra","choices":[{"index":0,"delta":{"content":"","role":"assistant","reasoning":" have","reasoning_details":[{"type":"reasoning.text","text":" have","format":"unknown","index":0}]},"finish_reason":null,"native_finish_reason":null}]} - -data: {"id":"gen-1791215148-PVpnAFb30EqLOaA4TUuP","object":"chat.completion.chunk","created":1791215148,"model":"qwen/qwen3-30b-a3b","provider":"DeepInfra","choices":[{"index":0,"delta":{"content":"","role":"assistant","reasoning":" to","reasoning_details":[{"type":"reasoning.text","text":" to","format":"unknown","index":0}]},"finish_reason":null,"native_finish_reason":null}]} - -data: {"id":"gen-1791215148-PVpnAFb30EqLOaA4TUuP","object":"chat.completion.chunk","created":1791215148,"model":"qwen/qwen3-30b-a3b","provider":"DeepInfra","choices":[{"index":0,"delta":{"content":"","role":"assistant","reasoning":" call","reasoning_details":[{"type":"reasoning.text","text":" call","format":"unknown","index":0}]},"finish_reason":null,"native_finish_reason":null}]} - -data: {"id":"gen-1791215148-PVpnAFb30EqLOaA4TUuP","object":"chat.completion.chunk","created":1791215148,"model":"qwen/qwen3-30b-a3b","provider":"DeepInfra","choices":[{"index":0,"delta":{"content":"","role":"assistant","reasoning":" task","reasoning_details":[{"type":"reasoning.text","text":" task","format":"unknown","index":0}]},"finish_reason":null,"native_finish_reason":null}]} - -data: {"id":"gen-1791215148-PVpnAFb30EqLOaA4TUuP","object":"chat.completion.chunk","created":1791215148,"model":"qwen/qwen3-30b-a3b","provider":"DeepInfra","choices":[{"index":0,"delta":{"content":"","role":"assistant","reasoning":"_wait","reasoning_details":[{"type":"reasoning.text","text":"_wait","format":"unknown","index":0}]},"finish_reason":null,"native_finish_reason":null}]} - -data: {"id":"gen-1791215148-PVpnAFb30EqLOaA4TUuP","object":"chat.completion.chunk","created":1791215148,"model":"qwen/qwen3-30b-a3b","provider":"DeepInfra","choices":[{"index":0,"delta":{"content":"","role":"assistant","reasoning":".","reasoning_details":[{"type":"reasoning.text","text":".","format":"unknown","index":0}]},"finish_reason":null,"native_finish_reason":null}]} - -data: {"id":"gen-1791215148-PVpnAFb30EqLOaA4TUuP","object":"chat.completion.chunk","created":1791215148,"model":"qwen/qwen3-30b-a3b","provider":"DeepInfra","choices":[{"index":0,"delta":{"content":"","role":"assistant","reasoning":" But","reasoning_details":[{"type":"reasoning.text","text":" But","format":"unknown","index":0}]},"finish_reason":null,"native_finish_reason":null}]} - -data: {"id":"gen-1791215148-PVpnAFb30EqLOaA4TUuP","object":"chat.completion.chunk","created":1791215148,"model":"qwen/qwen3-30b-a3b","provider":"DeepInfra","choices":[{"index":0,"delta":{"content":"","role":"assistant","reasoning":" since","reasoning_details":[{"type":"reasoning.text","text":" since","format":"unknown","index":0}]},"finish_reason":null,"native_finish_reason":null}]} - -data: {"id":"gen-1791215148-PVpnAFb30EqLOaA4TUuP","object":"chat.completion.chunk","created":1791215148,"model":"qwen/qwen3-30b-a3b","provider":"DeepInfra","choices":[{"index":0,"delta":{"content":"","role":"assistant","reasoning":" the","reasoning_details":[{"type":"reasoning.text","text":" the","format":"unknown","index":0}]},"finish_reason":null,"native_finish_reason":null}]} - -data: {"id":"gen-1791215148-PVpnAFb30EqLOaA4TUuP","object":"chat.completion.chunk","created":1791215148,"model":"qwen/qwen3-30b-a3b","provider":"DeepInfra","choices":[{"index":0,"delta":{"content":"","role":"assistant","reasoning":" user","reasoning_details":[{"type":"reasoning.text","text":" user","format":"unknown","index":0}]},"finish_reason":null,"native_finish_reason":null}]} - -data: {"id":"gen-1791215148-PVpnAFb30EqLOaA4TUuP","object":"chat.completion.chunk","created":1791215148,"model":"qwen/qwen3-30b-a3b","provider":"DeepInfra","choices":[{"index":0,"delta":{"content":"","role":"assistant","reasoning":" hasn","reasoning_details":[{"type":"reasoning.text","text":" hasn","format":"unknown","index":0}]},"finish_reason":null,"native_finish_reason":null}]} - -data: {"id":"gen-1791215148-PVpnAFb30EqLOaA4TUuP","object":"chat.completion.chunk","created":1791215148,"model":"qwen/qwen3-30b-a3b","provider":"DeepInfra","choices":[{"index":0,"delta":{"content":"","role":"assistant","reasoning":"'t","reasoning_details":[{"type":"reasoning.text","text":"'t","format":"unknown","index":0}]},"finish_reason":null,"native_finish_reason":null}]} - -data: {"id":"gen-1791215148-PVpnAFb30EqLOaA4TUuP","object":"chat.completion.chunk","created":1791215148,"model":"qwen/qwen3-30b-a3b","provider":"DeepInfra","choices":[{"index":0,"delta":{"content":"","role":"assistant","reasoning":" provided","reasoning_details":[{"type":"reasoning.text","text":" provided","format":"unknown","index":0}]},"finish_reason":null,"native_finish_reason":null}]} - -data: {"id":"gen-1791215148-PVpnAFb30EqLOaA4TUuP","object":"chat.completion.chunk","created":1791215148,"model":"qwen/qwen3-30b-a3b","provider":"DeepInfra","choices":[{"index":0,"delta":{"content":"","role":"assistant","reasoning":" any","reasoning_details":[{"type":"reasoning.text","text":" any","format":"unknown","index":0}]},"finish_reason":null,"native_finish_reason":null}]} - -data: {"id":"gen-1791215148-PVpnAFb30EqLOaA4TUuP","object":"chat.completion.chunk","created":1791215148,"model":"qwen/qwen3-30b-a3b","provider":"DeepInfra","choices":[{"index":0,"delta":{"content":"","role":"assistant","reasoning":" results","reasoning_details":[{"type":"reasoning.text","text":" results","format":"unknown","index":0}]},"finish_reason":null,"native_finish_reason":null}]} - -data: {"id":"gen-1791215148-PVpnAFb30EqLOaA4TUuP","object":"chat.completion.chunk","created":1791215148,"model":"qwen/qwen3-30b-a3b","provider":"DeepInfra","choices":[{"index":0,"delta":{"content":"","role":"assistant","reasoning":" yet","reasoning_details":[{"type":"reasoning.text","text":" yet","format":"unknown","index":0}]},"finish_reason":null,"native_finish_reason":null}]} - -data: {"id":"gen-1791215148-PVpnAFb30EqLOaA4TUuP","object":"chat.completion.chunk","created":1791215148,"model":"qwen/qwen3-30b-a3b","provider":"DeepInfra","choices":[{"index":0,"delta":{"content":"","role":"assistant","reasoning":",","reasoning_details":[{"type":"reasoning.text","text":",","format":"unknown","index":0}]},"finish_reason":null,"native_finish_reason":null}]} - -data: {"id":"gen-1791215148-PVpnAFb30EqLOaA4TUuP","object":"chat.completion.chunk","created":1791215148,"model":"qwen/qwen3-30b-a3b","provider":"DeepInfra","choices":[{"index":0,"delta":{"content":"","role":"assistant","reasoning":" I","reasoning_details":[{"type":"reasoning.text","text":" I","format":"unknown","index":0}]},"finish_reason":null,"native_finish_reason":null}]} - -data: {"id":"gen-1791215148-PVpnAFb30EqLOaA4TUuP","object":"chat.completion.chunk","created":1791215148,"model":"qwen/qwen3-30b-a3b","provider":"DeepInfra","choices":[{"index":0,"delta":{"content":"","role":"assistant","reasoning":" should","reasoning_details":[{"type":"reasoning.text","text":" should","format":"unknown","index":0}]},"finish_reason":null,"native_finish_reason":null}]} - -data: {"id":"gen-1791215148-PVpnAFb30EqLOaA4TUuP","object":"chat.completion.chunk","created":1791215148,"model":"qwen/qwen3-30b-a3b","provider":"DeepInfra","choices":[{"index":0,"delta":{"content":"","role":"assistant","reasoning":" just","reasoning_details":[{"type":"reasoning.text","text":" just","format":"unknown","index":0}]},"finish_reason":null,"native_finish_reason":null}]} - -data: {"id":"gen-1791215148-PVpnAFb30EqLOaA4TUuP","object":"chat.completion.chunk","created":1791215148,"model":"qwen/qwen3-30b-a3b","provider":"DeepInfra","choices":[{"index":0,"delta":{"content":"","role":"assistant","reasoning":" proceed","reasoning_details":[{"type":"reasoning.text","text":" proceed","format":"unknown","index":0}]},"finish_reason":null,"native_finish_reason":null}]} - -data: {"id":"gen-1791215148-PVpnAFb30EqLOaA4TUuP","object":"chat.completion.chunk","created":1791215148,"model":"qwen/qwen3-30b-a3b","provider":"DeepInfra","choices":[{"index":0,"delta":{"content":"","role":"assistant","reasoning":" with","reasoning_details":[{"type":"reasoning.text","text":" with","format":"unknown","index":0}]},"finish_reason":null,"native_finish_reason":null}]} - -data: {"id":"gen-1791215148-PVpnAFb30EqLOaA4TUuP","object":"chat.completion.chunk","created":1791215148,"model":"qwen/qwen3-30b-a3b","provider":"DeepInfra","choices":[{"index":0,"delta":{"content":"","role":"assistant","reasoning":" the","reasoning_details":[{"type":"reasoning.text","text":" the","format":"unknown","index":0}]},"finish_reason":null,"native_finish_reason":null}]} - -data: {"id":"gen-1791215148-PVpnAFb30EqLOaA4TUuP","object":"chat.completion.chunk","created":1791215148,"model":"qwen/qwen3-30b-a3b","provider":"DeepInfra","choices":[{"index":0,"delta":{"content":"","role":"assistant","reasoning":" initial","reasoning_details":[{"type":"reasoning.text","text":" initial","format":"unknown","index":0}]},"finish_reason":null,"native_finish_reason":null}]} - -data: {"id":"gen-1791215148-PVpnAFb30EqLOaA4TUuP","object":"chat.completion.chunk","created":1791215148,"model":"qwen/qwen3-30b-a3b","provider":"DeepInfra","choices":[{"index":0,"delta":{"content":"","role":"assistant","reasoning":" calls","reasoning_details":[{"type":"reasoning.text","text":" calls","format":"unknown","index":0}]},"finish_reason":null,"native_finish_reason":null}]} - -data: {"id":"gen-1791215148-PVpnAFb30EqLOaA4TUuP","object":"chat.completion.chunk","created":1791215148,"model":"qwen/qwen3-30b-a3b","provider":"DeepInfra","choices":[{"index":0,"delta":{"content":"","role":"assistant","reasoning":".\n\n","reasoning_details":[{"type":"reasoning.text","text":".\n\n","format":"unknown","index":0}]},"finish_reason":null,"native_finish_reason":null}]} - -data: {"id":"gen-1791215148-PVpnAFb30EqLOaA4TUuP","object":"chat.completion.chunk","created":1791215148,"model":"qwen/qwen3-30b-a3b","provider":"DeepInfra","choices":[{"index":0,"delta":{"content":"","role":"assistant","reasoning":"After","reasoning_details":[{"type":"reasoning.text","text":"After","format":"unknown","index":0}]},"finish_reason":null,"native_finish_reason":null}]} - -data: {"id":"gen-1791215148-PVpnAFb30EqLOaA4TUuP","object":"chat.completion.chunk","created":1791215148,"model":"qwen/qwen3-30b-a3b","provider":"DeepInfra","choices":[{"index":0,"delta":{"content":"","role":"assistant","reasoning":" both","reasoning_details":[{"type":"reasoning.text","text":" both","format":"unknown","index":0}]},"finish_reason":null,"native_finish_reason":null}]} - -data: {"id":"gen-1791215148-PVpnAFb30EqLOaA4TUuP","object":"chat.completion.chunk","created":1791215148,"model":"qwen/qwen3-30b-a3b","provider":"DeepInfra","choices":[{"index":0,"delta":{"content":"","role":"assistant","reasoning":" results","reasoning_details":[{"type":"reasoning.text","text":" results","format":"unknown","index":0}]},"finish_reason":null,"native_finish_reason":null}]} - -data: {"id":"gen-1791215148-PVpnAFb30EqLOaA4TUuP","object":"chat.completion.chunk","created":1791215148,"model":"qwen/qwen3-30b-a3b","provider":"DeepInfra","choices":[{"index":0,"delta":{"content":"","role":"assistant","reasoning":" are","reasoning_details":[{"type":"reasoning.text","text":" are","format":"unknown","index":0}]},"finish_reason":null,"native_finish_reason":null}]} - -data: {"id":"gen-1791215148-PVpnAFb30EqLOaA4TUuP","object":"chat.completion.chunk","created":1791215148,"model":"qwen/qwen3-30b-a3b","provider":"DeepInfra","choices":[{"index":0,"delta":{"content":"","role":"assistant","reasoning":" available","reasoning_details":[{"type":"reasoning.text","text":" available","format":"unknown","index":0}]},"finish_reason":null,"native_finish_reason":null}]} - -data: {"id":"gen-1791215148-PVpnAFb30EqLOaA4TUuP","object":"chat.completion.chunk","created":1791215148,"model":"qwen/qwen3-30b-a3b","provider":"DeepInfra","choices":[{"index":0,"delta":{"content":"","role":"assistant","reasoning":",","reasoning_details":[{"type":"reasoning.text","text":",","format":"unknown","index":0}]},"finish_reason":null,"native_finish_reason":null}]} - -data: {"id":"gen-1791215148-PVpnAFb30EqLOaA4TUuP","object":"chat.completion.chunk","created":1791215148,"model":"qwen/qwen3-30b-a3b","provider":"DeepInfra","choices":[{"index":0,"delta":{"content":"","role":"assistant","reasoning":" I","reasoning_details":[{"type":"reasoning.text","text":" I","format":"unknown","index":0}]},"finish_reason":null,"native_finish_reason":null}]} - -data: {"id":"gen-1791215148-PVpnAFb30EqLOaA4TUuP","object":"chat.completion.chunk","created":1791215148,"model":"qwen/qwen3-30b-a3b","provider":"DeepInfra","choices":[{"index":0,"delta":{"content":"","role":"assistant","reasoning":" need","reasoning_details":[{"type":"reasoning.text","text":" need","format":"unknown","index":0}]},"finish_reason":null,"native_finish_reason":null}]} - -data: {"id":"gen-1791215148-PVpnAFb30EqLOaA4TUuP","object":"chat.completion.chunk","created":1791215148,"model":"qwen/qwen3-30b-a3b","provider":"DeepInfra","choices":[{"index":0,"delta":{"content":"","role":"assistant","reasoning":" to","reasoning_details":[{"type":"reasoning.text","text":" to","format":"unknown","index":0}]},"finish_reason":null,"native_finish_reason":null}]} - -data: {"id":"gen-1791215148-PVpnAFb30EqLOaA4TUuP","object":"chat.completion.chunk","created":1791215148,"model":"qwen/qwen3-30b-a3b","provider":"DeepInfra","choices":[{"index":0,"delta":{"content":"","role":"assistant","reasoning":" answer","reasoning_details":[{"type":"reasoning.text","text":" answer","format":"unknown","index":0}]},"finish_reason":null,"native_finish_reason":null}]} - -data: {"id":"gen-1791215148-PVpnAFb30EqLOaA4TUuP","object":"chat.completion.chunk","created":1791215148,"model":"qwen/qwen3-30b-a3b","provider":"DeepInfra","choices":[{"index":0,"delta":{"content":"","role":"assistant","reasoning":" with","reasoning_details":[{"type":"reasoning.text","text":" with","format":"unknown","index":0}]},"finish_reason":null,"native_finish_reason":null}]} - -data: {"id":"gen-1791215148-PVpnAFb30EqLOaA4TUuP","object":"chat.completion.chunk","created":1791215148,"model":"qwen/qwen3-30b-a3b","provider":"DeepInfra","choices":[{"index":0,"delta":{"content":"","role":"assistant","reasoning":" a","reasoning_details":[{"type":"reasoning.text","text":" a","format":"unknown","index":0}]},"finish_reason":null,"native_finish_reason":null}]} - -data: {"id":"gen-1791215148-PVpnAFb30EqLOaA4TUuP","object":"chat.completion.chunk","created":1791215148,"model":"qwen/qwen3-30b-a3b","provider":"DeepInfra","choices":[{"index":0,"delta":{"content":"","role":"assistant","reasoning":" single","reasoning_details":[{"type":"reasoning.text","text":" single","format":"unknown","index":0}]},"finish_reason":null,"native_finish_reason":null}]} - -data: {"id":"gen-1791215148-PVpnAFb30EqLOaA4TUuP","object":"chat.completion.chunk","created":1791215148,"model":"qwen/qwen3-30b-a3b","provider":"DeepInfra","choices":[{"index":0,"delta":{"content":"","role":"assistant","reasoning":" sentence","reasoning_details":[{"type":"reasoning.text","text":" sentence","format":"unknown","index":0}]},"finish_reason":null,"native_finish_reason":null}]} - -data: {"id":"gen-1791215148-PVpnAFb30EqLOaA4TUuP","object":"chat.completion.chunk","created":1791215148,"model":"qwen/qwen3-30b-a3b","provider":"DeepInfra","choices":[{"index":0,"delta":{"content":"","role":"assistant","reasoning":" starting","reasoning_details":[{"type":"reasoning.text","text":" starting","format":"unknown","index":0}]},"finish_reason":null,"native_finish_reason":null}]} - -data: {"id":"gen-1791215148-PVpnAFb30EqLOaA4TUuP","object":"chat.completion.chunk","created":1791215148,"model":"qwen/qwen3-30b-a3b","provider":"DeepInfra","choices":[{"index":0,"delta":{"content":"","role":"assistant","reasoning":" with","reasoning_details":[{"type":"reasoning.text","text":" with","format":"unknown","index":0}]},"finish_reason":null,"native_finish_reason":null}]} - -data: {"id":"gen-1791215148-PVpnAFb30EqLOaA4TUuP","object":"chat.completion.chunk","created":1791215148,"model":"qwen/qwen3-30b-a3b","provider":"DeepInfra","choices":[{"index":0,"delta":{"content":"","role":"assistant","reasoning":" \"","reasoning_details":[{"type":"reasoning.text","text":" \"","format":"unknown","index":0}]},"finish_reason":null,"native_finish_reason":null}]} - -data: {"id":"gen-1791215148-PVpnAFb30EqLOaA4TUuP","object":"chat.completion.chunk","created":1791215148,"model":"qwen/qwen3-30b-a3b","provider":"DeepInfra","choices":[{"index":0,"delta":{"content":"","role":"assistant","reasoning":"Final","reasoning_details":[{"type":"reasoning.text","text":"Final","format":"unknown","index":0}]},"finish_reason":null,"native_finish_reason":null}]} - -data: {"id":"gen-1791215148-PVpnAFb30EqLOaA4TUuP","object":"chat.completion.chunk","created":1791215148,"model":"qwen/qwen3-30b-a3b","provider":"DeepInfra","choices":[{"index":0,"delta":{"content":"","role":"assistant","reasoning":" answer","reasoning_details":[{"type":"reasoning.text","text":" answer","format":"unknown","index":0}]},"finish_reason":null,"native_finish_reason":null}]} - -data: {"id":"gen-1791215148-PVpnAFb30EqLOaA4TUuP","object":"chat.completion.chunk","created":1791215148,"model":"qwen/qwen3-30b-a3b","provider":"DeepInfra","choices":[{"index":0,"delta":{"content":"","role":"assistant","reasoning":" from","reasoning_details":[{"type":"reasoning.text","text":" from","format":"unknown","index":0}]},"finish_reason":null,"native_finish_reason":null}]} - -data: {"id":"gen-1791215148-PVpnAFb30EqLOaA4TUuP","object":"chat.completion.chunk","created":1791215148,"model":"qwen/qwen3-30b-a3b","provider":"DeepInfra","choices":[{"index":0,"delta":{"content":"","role":"assistant","reasoning":" read","reasoning_details":[{"type":"reasoning.text","text":" read","format":"unknown","index":0}]},"finish_reason":null,"native_finish_reason":null}]} - -data: {"id":"gen-1791215148-PVpnAFb30EqLOaA4TUuP","object":"chat.completion.chunk","created":1791215148,"model":"qwen/qwen3-30b-a3b","provider":"DeepInfra","choices":[{"index":0,"delta":{"content":"","role":"assistant","reasoning":":\"","reasoning_details":[{"type":"reasoning.text","text":":\"","format":"unknown","index":0}]},"finish_reason":null,"native_finish_reason":null}]} - -data: {"id":"gen-1791215148-PVpnAFb30EqLOaA4TUuP","object":"chat.completion.chunk","created":1791215148,"model":"qwen/qwen3-30b-a3b","provider":"DeepInfra","choices":[{"index":0,"delta":{"content":"","role":"assistant","reasoning":" including","reasoning_details":[{"type":"reasoning.text","text":" including","format":"unknown","index":0}]},"finish_reason":null,"native_finish_reason":null}]} - -data: {"id":"gen-1791215148-PVpnAFb30EqLOaA4TUuP","object":"chat.completion.chunk","created":1791215148,"model":"qwen/qwen3-30b-a3b","provider":"DeepInfra","choices":[{"index":0,"delta":{"content":"","role":"assistant","reasoning":" the","reasoning_details":[{"type":"reasoning.text","text":" the","format":"unknown","index":0}]},"finish_reason":null,"native_finish_reason":null}]} - -data: {"id":"gen-1791215148-PVpnAFb30EqLOaA4TUuP","object":"chat.completion.chunk","created":1791215148,"model":"qwen/qwen3-30b-a3b","provider":"DeepInfra","choices":[{"index":0,"delta":{"content":"","role":"assistant","reasoning":" researcher","reasoning_details":[{"type":"reasoning.text","text":" researcher","format":"unknown","index":0}]},"finish_reason":null,"native_finish_reason":null}]} - -data: {"id":"gen-1791215148-PVpnAFb30EqLOaA4TUuP","object":"chat.completion.chunk","created":1791215148,"model":"qwen/qwen3-30b-a3b","provider":"DeepInfra","choices":[{"index":0,"delta":{"content":"","role":"assistant","reasoning":" result","reasoning_details":[{"type":"reasoning.text","text":" result","format":"unknown","index":0}]},"finish_reason":null,"native_finish_reason":null}]} - -data: {"id":"gen-1791215148-PVpnAFb30EqLOaA4TUuP","object":"chat.completion.chunk","created":1791215148,"model":"qwen/qwen3-30b-a3b","provider":"DeepInfra","choices":[{"index":0,"delta":{"content":"","role":"assistant","reasoning":",","reasoning_details":[{"type":"reasoning.text","text":",","format":"unknown","index":0}]},"finish_reason":null,"native_finish_reason":null}]} - -data: {"id":"gen-1791215148-PVpnAFb30EqLOaA4TUuP","object":"chat.completion.chunk","created":1791215148,"model":"qwen/qwen3-30b-a3b","provider":"DeepInfra","choices":[{"index":0,"delta":{"content":"","role":"assistant","reasoning":" read","reasoning_details":[{"type":"reasoning.text","text":" read","format":"unknown","index":0}]},"finish_reason":null,"native_finish_reason":null}]} - -data: {"id":"gen-1791215148-PVpnAFb30EqLOaA4TUuP","object":"chat.completion.chunk","created":1791215148,"model":"qwen/qwen3-30b-a3b","provider":"DeepInfra","choices":[{"index":0,"delta":{"content":"","role":"assistant","reasoning":" title","reasoning_details":[{"type":"reasoning.text","text":" title","format":"unknown","index":0}]},"finish_reason":null,"native_finish_reason":null}]} - -data: {"id":"gen-1791215148-PVpnAFb30EqLOaA4TUuP","object":"chat.completion.chunk","created":1791215148,"model":"qwen/qwen3-30b-a3b","provider":"DeepInfra","choices":[{"index":0,"delta":{"content":"","role":"assistant","reasoning":",","reasoning_details":[{"type":"reasoning.text","text":",","format":"unknown","index":0}]},"finish_reason":null,"native_finish_reason":null}]} - -data: {"id":"gen-1791215148-PVpnAFb30EqLOaA4TUuP","object":"chat.completion.chunk","created":1791215148,"model":"qwen/qwen3-30b-a3b","provider":"DeepInfra","choices":[{"index":0,"delta":{"content":"","role":"assistant","reasoning":" URL","reasoning_details":[{"type":"reasoning.text","text":" URL","format":"unknown","index":0}]},"finish_reason":null,"native_finish_reason":null}]} - -data: {"id":"gen-1791215148-PVpnAFb30EqLOaA4TUuP","object":"chat.completion.chunk","created":1791215148,"model":"qwen/qwen3-30b-a3b","provider":"DeepInfra","choices":[{"index":0,"delta":{"content":"","role":"assistant","reasoning":",","reasoning_details":[{"type":"reasoning.text","text":",","format":"unknown","index":0}]},"finish_reason":null,"native_finish_reason":null}]} - -data: {"id":"gen-1791215148-PVpnAFb30EqLOaA4TUuP","object":"chat.completion.chunk","created":1791215148,"model":"qwen/qwen3-30b-a3b","provider":"DeepInfra","choices":[{"index":0,"delta":{"content":"","role":"assistant","reasoning":" and","reasoning_details":[{"type":"reasoning.text","text":" and","format":"unknown","index":0}]},"finish_reason":null,"native_finish_reason":null}]} - -data: {"id":"gen-1791215148-PVpnAFb30EqLOaA4TUuP","object":"chat.completion.chunk","created":1791215148,"model":"qwen/qwen3-30b-a3b","provider":"DeepInfra","choices":[{"index":0,"delta":{"content":"","role":"assistant","reasoning":" excerpt","reasoning_details":[{"type":"reasoning.text","text":" excerpt","format":"unknown","index":0}]},"finish_reason":null,"native_finish_reason":null}]} - -data: {"id":"gen-1791215148-PVpnAFb30EqLOaA4TUuP","object":"chat.completion.chunk","created":1791215148,"model":"qwen/qwen3-30b-a3b","provider":"DeepInfra","choices":[{"index":0,"delta":{"content":"","role":"assistant","reasoning":".","reasoning_details":[{"type":"reasoning.text","text":".","format":"unknown","index":0}]},"finish_reason":null,"native_finish_reason":null}]} - -data: {"id":"gen-1791215148-PVpnAFb30EqLOaA4TUuP","object":"chat.completion.chunk","created":1791215148,"model":"qwen/qwen3-30b-a3b","provider":"DeepInfra","choices":[{"index":0,"delta":{"content":"","role":"assistant","reasoning":" But","reasoning_details":[{"type":"reasoning.text","text":" But","format":"unknown","index":0}]},"finish_reason":null,"native_finish_reason":null}]} - -data: {"id":"gen-1791215148-PVpnAFb30EqLOaA4TUuP","object":"chat.completion.chunk","created":1791215148,"model":"qwen/qwen3-30b-a3b","provider":"DeepInfra","choices":[{"index":0,"delta":{"content":"","role":"assistant","reasoning":" right","reasoning_details":[{"type":"reasoning.text","text":" right","format":"unknown","index":0}]},"finish_reason":null,"native_finish_reason":null}]} - -data: {"id":"gen-1791215148-PVpnAFb30EqLOaA4TUuP","object":"chat.completion.chunk","created":1791215148,"model":"qwen/qwen3-30b-a3b","provider":"DeepInfra","choices":[{"index":0,"delta":{"content":"","role":"assistant","reasoning":" now","reasoning_details":[{"type":"reasoning.text","text":" now","format":"unknown","index":0}]},"finish_reason":null,"native_finish_reason":null}]} - -data: {"id":"gen-1791215148-PVpnAFb30EqLOaA4TUuP","object":"chat.completion.chunk","created":1791215148,"model":"qwen/qwen3-30b-a3b","provider":"DeepInfra","choices":[{"index":0,"delta":{"content":"","role":"assistant","reasoning":",","reasoning_details":[{"type":"reasoning.text","text":",","format":"unknown","index":0}]},"finish_reason":null,"native_finish_reason":null}]} - -data: {"id":"gen-1791215148-PVpnAFb30EqLOaA4TUuP","object":"chat.completion.chunk","created":1791215148,"model":"qwen/qwen3-30b-a3b","provider":"DeepInfra","choices":[{"index":0,"delta":{"content":"","role":"assistant","reasoning":" the","reasoning_details":[{"type":"reasoning.text","text":" the","format":"unknown","index":0}]},"finish_reason":null,"native_finish_reason":null}]} - -data: {"id":"gen-1791215148-PVpnAFb30EqLOaA4TUuP","object":"chat.completion.chunk","created":1791215148,"model":"qwen/qwen3-30b-a3b","provider":"DeepInfra","choices":[{"index":0,"delta":{"content":"","role":"assistant","reasoning":" first","reasoning_details":[{"type":"reasoning.text","text":" first","format":"unknown","index":0}]},"finish_reason":null,"native_finish_reason":null}]} - -data: {"id":"gen-1791215148-PVpnAFb30EqLOaA4TUuP","object":"chat.completion.chunk","created":1791215148,"model":"qwen/qwen3-30b-a3b","provider":"DeepInfra","choices":[{"index":0,"delta":{"content":"","role":"assistant","reasoning":" step","reasoning_details":[{"type":"reasoning.text","text":" step","format":"unknown","index":0}]},"finish_reason":null,"native_finish_reason":null}]} - -data: {"id":"gen-1791215148-PVpnAFb30EqLOaA4TUuP","object":"chat.completion.chunk","created":1791215148,"model":"qwen/qwen3-30b-a3b","provider":"DeepInfra","choices":[{"index":0,"delta":{"content":"","role":"assistant","reasoning":" is","reasoning_details":[{"type":"reasoning.text","text":" is","format":"unknown","index":0}]},"finish_reason":null,"native_finish_reason":null}]} - -data: {"id":"gen-1791215148-PVpnAFb30EqLOaA4TUuP","object":"chat.completion.chunk","created":1791215148,"model":"qwen/qwen3-30b-a3b","provider":"DeepInfra","choices":[{"index":0,"delta":{"content":"","role":"assistant","reasoning":" to","reasoning_details":[{"type":"reasoning.text","text":" to","format":"unknown","index":0}]},"finish_reason":null,"native_finish_reason":null}]} - -data: {"id":"gen-1791215148-PVpnAFb30EqLOaA4TUuP","object":"chat.completion.chunk","created":1791215148,"model":"qwen/qwen3-30b-a3b","provider":"DeepInfra","choices":[{"index":0,"delta":{"content":"","role":"assistant","reasoning":" generate","reasoning_details":[{"type":"reasoning.text","text":" generate","format":"unknown","index":0}]},"finish_reason":null,"native_finish_reason":null}]} - -data: {"id":"gen-1791215148-PVpnAFb30EqLOaA4TUuP","object":"chat.completion.chunk","created":1791215148,"model":"qwen/qwen3-30b-a3b","provider":"DeepInfra","choices":[{"index":0,"delta":{"content":"","role":"assistant","reasoning":" the","reasoning_details":[{"type":"reasoning.text","text":" the","format":"unknown","index":0}]},"finish_reason":null,"native_finish_reason":null}]} - -data: {"id":"gen-1791215148-PVpnAFb30EqLOaA4TUuP","object":"chat.completion.chunk","created":1791215148,"model":"qwen/qwen3-30b-a3b","provider":"DeepInfra","choices":[{"index":0,"delta":{"content":"","role":"assistant","reasoning":" two","reasoning_details":[{"type":"reasoning.text","text":" two","format":"unknown","index":0}]},"finish_reason":null,"native_finish_reason":null}]} - -data: {"id":"gen-1791215148-PVpnAFb30EqLOaA4TUuP","object":"chat.completion.chunk","created":1791215148,"model":"qwen/qwen3-30b-a3b","provider":"DeepInfra","choices":[{"index":0,"delta":{"content":"","role":"assistant","reasoning":" tool","reasoning_details":[{"type":"reasoning.text","text":" tool","format":"unknown","index":0}]},"finish_reason":null,"native_finish_reason":null}]} - -data: {"id":"gen-1791215148-PVpnAFb30EqLOaA4TUuP","object":"chat.completion.chunk","created":1791215148,"model":"qwen/qwen3-30b-a3b","provider":"DeepInfra","choices":[{"index":0,"delta":{"content":"","role":"assistant","reasoning":" calls","reasoning_details":[{"type":"reasoning.text","text":" calls","format":"unknown","index":0}]},"finish_reason":null,"native_finish_reason":null}]} - -data: {"id":"gen-1791215148-PVpnAFb30EqLOaA4TUuP","object":"chat.completion.chunk","created":1791215148,"model":"qwen/qwen3-30b-a3b","provider":"DeepInfra","choices":[{"index":0,"delta":{"content":"","role":"assistant","reasoning":".\n","reasoning_details":[{"type":"reasoning.text","text":".\n","format":"unknown","index":0}]},"finish_reason":null,"native_finish_reason":null}]} - -: OPENROUTER PROCESSING - -data: {"id":"gen-1791215148-PVpnAFb30EqLOaA4TUuP","object":"chat.completion.chunk","created":1791215148,"model":"qwen/qwen3-30b-a3b","provider":"DeepInfra","choices":[{"index":0,"delta":{"content":null,"role":"assistant","tool_calls":[{"index":0,"id":"call_e82b","type":"function","function":{"name":"researcher","arguments":""}}]},"finish_reason":null,"native_finish_reason":null}]} - -data: {"id":"gen-1791215148-PVpnAFb30EqLOaA4TUuP","object":"chat.completion.chunk","created":1791215148,"model":"qwen/qwen3-30b-a3b","provider":"DeepInfra","choices":[{"index":0,"delta":{"content":null,"role":"assistant","tool_calls":[{"index":0,"function":{"arguments":"{\"message\": \"Run the Braintrust Eve instrumentation e2e scenario\"}"}}]},"finish_reason":null,"native_finish_reason":null}]} - -data: {"id":"gen-1791215148-PVpnAFb30EqLOaA4TUuP","object":"chat.completion.chunk","created":1791215148,"model":"qwen/qwen3-30b-a3b","provider":"DeepInfra","choices":[{"index":0,"delta":{"content":null,"role":"assistant","tool_calls":[{"index":1,"id":"call_614f","type":"function","function":{"name":"read","arguments":""}}]},"finish_reason":null,"native_finish_reason":null}]} - -data: {"id":"gen-1791215148-PVpnAFb30EqLOaA4TUuP","object":"chat.completion.chunk","created":1791215148,"model":"qwen/qwen3-30b-a3b","provider":"DeepInfra","choices":[{"index":0,"delta":{"content":null,"role":"assistant","tool_calls":[{"index":1,"function":{"arguments":"{\"url\": \"https://eve.dev/docs/guides/instrumentation\"}"}}]},"finish_reason":null,"native_finish_reason":null}]} - -data: {"id":"gen-1791215148-PVpnAFb30EqLOaA4TUuP","object":"chat.completion.chunk","created":1791215148,"model":"qwen/qwen3-30b-a3b","provider":"DeepInfra","choices":[{"index":0,"delta":{"content":"","role":"assistant","reasoning":null},"finish_reason":"tool_calls","native_finish_reason":"tool_calls"}]} - -data: {"id":"gen-1791215148-PVpnAFb30EqLOaA4TUuP","object":"chat.completion.chunk","created":1791215148,"model":"qwen/qwen3-30b-a3b","provider":"DeepInfra","service_tier":"default","choices":[{"index":0,"delta":{"content":"","role":"assistant"},"finish_reason":"tool_calls","native_finish_reason":"tool_calls"}],"usage":{"prompt_tokens":3396,"completion_tokens":353,"total_tokens":3749,"cost":0.00058402,"is_byok":false,"prompt_tokens_details":{"cached_tokens":0,"cache_write_tokens":0,"audio_tokens":0,"video_tokens":0},"cost_details":{"upstream_inference_cost":0.00058402,"upstream_inference_prompt_cost":0.00040752,"upstream_inference_completions_cost":0.0001765},"completion_tokens_details":{"reasoning_tokens":351,"image_tokens":0,"audio_tokens":0}}} - -data: [DONE] - diff --git a/e2e/scenarios/eve-instrumentation/__cassettes__/eve-v0.cassette.blobs/74cbb61e1f831a83ee356e49bf5d481b5679f3983fb1fb07c8b1505c9ac65d9f.bin b/e2e/scenarios/eve-instrumentation/__cassettes__/eve-v0.cassette.blobs/74cbb61e1f831a83ee356e49bf5d481b5679f3983fb1fb07c8b1505c9ac65d9f.bin deleted file mode 100644 index 6ae77a3ca..000000000 --- a/e2e/scenarios/eve-instrumentation/__cassettes__/eve-v0.cassette.blobs/74cbb61e1f831a83ee356e49bf5d481b5679f3983fb1fb07c8b1505c9ac65d9f.bin +++ /dev/null @@ -1,660 +0,0 @@ -data: {"id":"gen-1791215164-iEel1q5g7Q2702ZUqR69","object":"chat.completion.chunk","created":1791215164,"model":"qwen/qwen3-30b-a3b","provider":"DeepInfra","choices":[{"index":0,"delta":{"content":"","role":"assistant","reasoning":"\n","reasoning_details":[{"type":"reasoning.text","text":"\n","format":"unknown","index":0}]},"finish_reason":null,"native_finish_reason":null}]} - -data: {"id":"gen-1791215164-iEel1q5g7Q2702ZUqR69","object":"chat.completion.chunk","created":1791215164,"model":"qwen/qwen3-30b-a3b","provider":"DeepInfra","choices":[{"index":0,"delta":{"content":"","role":"assistant","reasoning":"Okay","reasoning_details":[{"type":"reasoning.text","text":"Okay","format":"unknown","index":0}]},"finish_reason":null,"native_finish_reason":null}]} - -data: {"id":"gen-1791215164-iEel1q5g7Q2702ZUqR69","object":"chat.completion.chunk","created":1791215164,"model":"qwen/qwen3-30b-a3b","provider":"DeepInfra","choices":[{"index":0,"delta":{"content":"","role":"assistant","reasoning":",","reasoning_details":[{"type":"reasoning.text","text":",","format":"unknown","index":0}]},"finish_reason":null,"native_finish_reason":null}]} - -data: {"id":"gen-1791215164-iEel1q5g7Q2702ZUqR69","object":"chat.completion.chunk","created":1791215164,"model":"qwen/qwen3-30b-a3b","provider":"DeepInfra","choices":[{"index":0,"delta":{"content":"","role":"assistant","reasoning":" let","reasoning_details":[{"type":"reasoning.text","text":" let","format":"unknown","index":0}]},"finish_reason":null,"native_finish_reason":null}]} - -data: {"id":"gen-1791215164-iEel1q5g7Q2702ZUqR69","object":"chat.completion.chunk","created":1791215164,"model":"qwen/qwen3-30b-a3b","provider":"DeepInfra","choices":[{"index":0,"delta":{"content":"","role":"assistant","reasoning":" me","reasoning_details":[{"type":"reasoning.text","text":" me","format":"unknown","index":0}]},"finish_reason":null,"native_finish_reason":null}]} - -data: {"id":"gen-1791215164-iEel1q5g7Q2702ZUqR69","object":"chat.completion.chunk","created":1791215164,"model":"qwen/qwen3-30b-a3b","provider":"DeepInfra","choices":[{"index":0,"delta":{"content":"","role":"assistant","reasoning":" process","reasoning_details":[{"type":"reasoning.text","text":" process","format":"unknown","index":0}]},"finish_reason":null,"native_finish_reason":null}]} - -data: {"id":"gen-1791215164-iEel1q5g7Q2702ZUqR69","object":"chat.completion.chunk","created":1791215164,"model":"qwen/qwen3-30b-a3b","provider":"DeepInfra","choices":[{"index":0,"delta":{"content":"","role":"assistant","reasoning":" what","reasoning_details":[{"type":"reasoning.text","text":" what","format":"unknown","index":0}]},"finish_reason":null,"native_finish_reason":null}]} - -data: {"id":"gen-1791215164-iEel1q5g7Q2702ZUqR69","object":"chat.completion.chunk","created":1791215164,"model":"qwen/qwen3-30b-a3b","provider":"DeepInfra","choices":[{"index":0,"delta":{"content":"","role":"assistant","reasoning":"'s","reasoning_details":[{"type":"reasoning.text","text":"'s","format":"unknown","index":0}]},"finish_reason":null,"native_finish_reason":null}]} - -data: {"id":"gen-1791215164-iEel1q5g7Q2702ZUqR69","object":"chat.completion.chunk","created":1791215164,"model":"qwen/qwen3-30b-a3b","provider":"DeepInfra","choices":[{"index":0,"delta":{"content":"","role":"assistant","reasoning":" happening","reasoning_details":[{"type":"reasoning.text","text":" happening","format":"unknown","index":0}]},"finish_reason":null,"native_finish_reason":null}]} - -data: {"id":"gen-1791215164-iEel1q5g7Q2702ZUqR69","object":"chat.completion.chunk","created":1791215164,"model":"qwen/qwen3-30b-a3b","provider":"DeepInfra","choices":[{"index":0,"delta":{"content":"","role":"assistant","reasoning":" here","reasoning_details":[{"type":"reasoning.text","text":" here","format":"unknown","index":0}]},"finish_reason":null,"native_finish_reason":null}]} - -data: {"id":"gen-1791215164-iEel1q5g7Q2702ZUqR69","object":"chat.completion.chunk","created":1791215164,"model":"qwen/qwen3-30b-a3b","provider":"DeepInfra","choices":[{"index":0,"delta":{"content":"","role":"assistant","reasoning":".","reasoning_details":[{"type":"reasoning.text","text":".","format":"unknown","index":0}]},"finish_reason":null,"native_finish_reason":null}]} - -data: {"id":"gen-1791215164-iEel1q5g7Q2702ZUqR69","object":"chat.completion.chunk","created":1791215164,"model":"qwen/qwen3-30b-a3b","provider":"DeepInfra","choices":[{"index":0,"delta":{"content":"","role":"assistant","reasoning":" The","reasoning_details":[{"type":"reasoning.text","text":" The","format":"unknown","index":0}]},"finish_reason":null,"native_finish_reason":null}]} - -data: {"id":"gen-1791215164-iEel1q5g7Q2702ZUqR69","object":"chat.completion.chunk","created":1791215164,"model":"qwen/qwen3-30b-a3b","provider":"DeepInfra","choices":[{"index":0,"delta":{"content":"","role":"assistant","reasoning":" user","reasoning_details":[{"type":"reasoning.text","text":" user","format":"unknown","index":0}]},"finish_reason":null,"native_finish_reason":null}]} - -data: {"id":"gen-1791215164-iEel1q5g7Q2702ZUqR69","object":"chat.completion.chunk","created":1791215164,"model":"qwen/qwen3-30b-a3b","provider":"DeepInfra","choices":[{"index":0,"delta":{"content":"","role":"assistant","reasoning":" asked","reasoning_details":[{"type":"reasoning.text","text":" asked","format":"unknown","index":0}]},"finish_reason":null,"native_finish_reason":null}]} - -data: {"id":"gen-1791215164-iEel1q5g7Q2702ZUqR69","object":"chat.completion.chunk","created":1791215164,"model":"qwen/qwen3-30b-a3b","provider":"DeepInfra","choices":[{"index":0,"delta":{"content":"","role":"assistant","reasoning":" to","reasoning_details":[{"type":"reasoning.text","text":" to","format":"unknown","index":0}]},"finish_reason":null,"native_finish_reason":null}]} - -data: {"id":"gen-1791215164-iEel1q5g7Q2702ZUqR69","object":"chat.completion.chunk","created":1791215164,"model":"qwen/qwen3-30b-a3b","provider":"DeepInfra","choices":[{"index":0,"delta":{"content":"","role":"assistant","reasoning":" run","reasoning_details":[{"type":"reasoning.text","text":" run","format":"unknown","index":0}]},"finish_reason":null,"native_finish_reason":null}]} - -data: {"id":"gen-1791215164-iEel1q5g7Q2702ZUqR69","object":"chat.completion.chunk","created":1791215164,"model":"qwen/qwen3-30b-a3b","provider":"DeepInfra","choices":[{"index":0,"delta":{"content":"","role":"assistant","reasoning":" the","reasoning_details":[{"type":"reasoning.text","text":" the","format":"unknown","index":0}]},"finish_reason":null,"native_finish_reason":null}]} - -data: {"id":"gen-1791215164-iEel1q5g7Q2702ZUqR69","object":"chat.completion.chunk","created":1791215164,"model":"qwen/qwen3-30b-a3b","provider":"DeepInfra","choices":[{"index":0,"delta":{"content":"","role":"assistant","reasoning":" Brain","reasoning_details":[{"type":"reasoning.text","text":" Brain","format":"unknown","index":0}]},"finish_reason":null,"native_finish_reason":null}]} - -data: {"id":"gen-1791215164-iEel1q5g7Q2702ZUqR69","object":"chat.completion.chunk","created":1791215164,"model":"qwen/qwen3-30b-a3b","provider":"DeepInfra","choices":[{"index":0,"delta":{"content":"","role":"assistant","reasoning":"trust","reasoning_details":[{"type":"reasoning.text","text":"trust","format":"unknown","index":0}]},"finish_reason":null,"native_finish_reason":null}]} - -data: {"id":"gen-1791215164-iEel1q5g7Q2702ZUqR69","object":"chat.completion.chunk","created":1791215164,"model":"qwen/qwen3-30b-a3b","provider":"DeepInfra","choices":[{"index":0,"delta":{"content":"","role":"assistant","reasoning":" Eve","reasoning_details":[{"type":"reasoning.text","text":" Eve","format":"unknown","index":0}]},"finish_reason":null,"native_finish_reason":null}]} - -data: {"id":"gen-1791215164-iEel1q5g7Q2702ZUqR69","object":"chat.completion.chunk","created":1791215164,"model":"qwen/qwen3-30b-a3b","provider":"DeepInfra","choices":[{"index":0,"delta":{"content":"","role":"assistant","reasoning":" instrumentation","reasoning_details":[{"type":"reasoning.text","text":" instrumentation","format":"unknown","index":0}]},"finish_reason":null,"native_finish_reason":null}]} - -data: {"id":"gen-1791215164-iEel1q5g7Q2702ZUqR69","object":"chat.completion.chunk","created":1791215164,"model":"qwen/qwen3-30b-a3b","provider":"DeepInfra","choices":[{"index":0,"delta":{"content":"","role":"assistant","reasoning":" e","reasoning_details":[{"type":"reasoning.text","text":" e","format":"unknown","index":0}]},"finish_reason":null,"native_finish_reason":null}]} - -data: {"id":"gen-1791215164-iEel1q5g7Q2702ZUqR69","object":"chat.completion.chunk","created":1791215164,"model":"qwen/qwen3-30b-a3b","provider":"DeepInfra","choices":[{"index":0,"delta":{"content":"","role":"assistant","reasoning":"2","reasoning_details":[{"type":"reasoning.text","text":"2","format":"unknown","index":0}]},"finish_reason":null,"native_finish_reason":null}]} - -data: {"id":"gen-1791215164-iEel1q5g7Q2702ZUqR69","object":"chat.completion.chunk","created":1791215164,"model":"qwen/qwen3-30b-a3b","provider":"DeepInfra","choices":[{"index":0,"delta":{"content":"","role":"assistant","reasoning":"e","reasoning_details":[{"type":"reasoning.text","text":"e","format":"unknown","index":0}]},"finish_reason":null,"native_finish_reason":null}]} - -data: {"id":"gen-1791215164-iEel1q5g7Q2702ZUqR69","object":"chat.completion.chunk","created":1791215164,"model":"qwen/qwen3-30b-a3b","provider":"DeepInfra","choices":[{"index":0,"delta":{"content":"","role":"assistant","reasoning":" scenario","reasoning_details":[{"type":"reasoning.text","text":" scenario","format":"unknown","index":0}]},"finish_reason":null,"native_finish_reason":null}]} - -data: {"id":"gen-1791215164-iEel1q5g7Q2702ZUqR69","object":"chat.completion.chunk","created":1791215164,"model":"qwen/qwen3-30b-a3b","provider":"DeepInfra","choices":[{"index":0,"delta":{"content":"","role":"assistant","reasoning":".","reasoning_details":[{"type":"reasoning.text","text":".","format":"unknown","index":0}]},"finish_reason":null,"native_finish_reason":null}]} - -data: {"id":"gen-1791215164-iEel1q5g7Q2702ZUqR69","object":"chat.completion.chunk","created":1791215164,"model":"qwen/qwen3-30b-a3b","provider":"DeepInfra","choices":[{"index":0,"delta":{"content":"","role":"assistant","reasoning":" According","reasoning_details":[{"type":"reasoning.text","text":" According","format":"unknown","index":0}]},"finish_reason":null,"native_finish_reason":null}]} - -data: {"id":"gen-1791215164-iEel1q5g7Q2702ZUqR69","object":"chat.completion.chunk","created":1791215164,"model":"qwen/qwen3-30b-a3b","provider":"DeepInfra","choices":[{"index":0,"delta":{"content":"","role":"assistant","reasoning":" to","reasoning_details":[{"type":"reasoning.text","text":" to","format":"unknown","index":0}]},"finish_reason":null,"native_finish_reason":null}]} - -data: {"id":"gen-1791215164-iEel1q5g7Q2702ZUqR69","object":"chat.completion.chunk","created":1791215164,"model":"qwen/qwen3-30b-a3b","provider":"DeepInfra","choices":[{"index":0,"delta":{"content":"","role":"assistant","reasoning":" the","reasoning_details":[{"type":"reasoning.text","text":" the","format":"unknown","index":0}]},"finish_reason":null,"native_finish_reason":null}]} - -data: {"id":"gen-1791215164-iEel1q5g7Q2702ZUqR69","object":"chat.completion.chunk","created":1791215164,"model":"qwen/qwen3-30b-a3b","provider":"DeepInfra","choices":[{"index":0,"delta":{"content":"","role":"assistant","reasoning":" instructions","reasoning_details":[{"type":"reasoning.text","text":" instructions","format":"unknown","index":0}]},"finish_reason":null,"native_finish_reason":null}]} - -data: {"id":"gen-1791215164-iEel1q5g7Q2702ZUqR69","object":"chat.completion.chunk","created":1791215164,"model":"qwen/qwen3-30b-a3b","provider":"DeepInfra","choices":[{"index":0,"delta":{"content":"","role":"assistant","reasoning":",","reasoning_details":[{"type":"reasoning.text","text":",","format":"unknown","index":0}]},"finish_reason":null,"native_finish_reason":null}]} - -data: {"id":"gen-1791215164-iEel1q5g7Q2702ZUqR69","object":"chat.completion.chunk","created":1791215164,"model":"qwen/qwen3-30b-a3b","provider":"DeepInfra","choices":[{"index":0,"delta":{"content":"","role":"assistant","reasoning":" I","reasoning_details":[{"type":"reasoning.text","text":" I","format":"unknown","index":0}]},"finish_reason":null,"native_finish_reason":null}]} - -data: {"id":"gen-1791215164-iEel1q5g7Q2702ZUqR69","object":"chat.completion.chunk","created":1791215164,"model":"qwen/qwen3-30b-a3b","provider":"DeepInfra","choices":[{"index":0,"delta":{"content":"","role":"assistant","reasoning":" needed","reasoning_details":[{"type":"reasoning.text","text":" needed","format":"unknown","index":0}]},"finish_reason":null,"native_finish_reason":null}]} - -data: {"id":"gen-1791215164-iEel1q5g7Q2702ZUqR69","object":"chat.completion.chunk","created":1791215164,"model":"qwen/qwen3-30b-a3b","provider":"DeepInfra","choices":[{"index":0,"delta":{"content":"","role":"assistant","reasoning":" to","reasoning_details":[{"type":"reasoning.text","text":" to","format":"unknown","index":0}]},"finish_reason":null,"native_finish_reason":null}]} - -data: {"id":"gen-1791215164-iEel1q5g7Q2702ZUqR69","object":"chat.completion.chunk","created":1791215164,"model":"qwen/qwen3-30b-a3b","provider":"DeepInfra","choices":[{"index":0,"delta":{"content":"","role":"assistant","reasoning":" make","reasoning_details":[{"type":"reasoning.text","text":" make","format":"unknown","index":0}]},"finish_reason":null,"native_finish_reason":null}]} - -data: {"id":"gen-1791215164-iEel1q5g7Q2702ZUqR69","object":"chat.completion.chunk","created":1791215164,"model":"qwen/qwen3-30b-a3b","provider":"DeepInfra","choices":[{"index":0,"delta":{"content":"","role":"assistant","reasoning":" two","reasoning_details":[{"type":"reasoning.text","text":" two","format":"unknown","index":0}]},"finish_reason":null,"native_finish_reason":null}]} - -data: {"id":"gen-1791215164-iEel1q5g7Q2702ZUqR69","object":"chat.completion.chunk","created":1791215164,"model":"qwen/qwen3-30b-a3b","provider":"DeepInfra","choices":[{"index":0,"delta":{"content":"","role":"assistant","reasoning":" initial","reasoning_details":[{"type":"reasoning.text","text":" initial","format":"unknown","index":0}]},"finish_reason":null,"native_finish_reason":null}]} - -data: {"id":"gen-1791215164-iEel1q5g7Q2702ZUqR69","object":"chat.completion.chunk","created":1791215164,"model":"qwen/qwen3-30b-a3b","provider":"DeepInfra","choices":[{"index":0,"delta":{"content":"","role":"assistant","reasoning":" tool","reasoning_details":[{"type":"reasoning.text","text":" tool","format":"unknown","index":0}]},"finish_reason":null,"native_finish_reason":null}]} - -data: {"id":"gen-1791215164-iEel1q5g7Q2702ZUqR69","object":"chat.completion.chunk","created":1791215164,"model":"qwen/qwen3-30b-a3b","provider":"DeepInfra","choices":[{"index":0,"delta":{"content":"","role":"assistant","reasoning":" calls","reasoning_details":[{"type":"reasoning.text","text":" calls","format":"unknown","index":0}]},"finish_reason":null,"native_finish_reason":null}]} - -data: {"id":"gen-1791215164-iEel1q5g7Q2702ZUqR69","object":"chat.completion.chunk","created":1791215164,"model":"qwen/qwen3-30b-a3b","provider":"DeepInfra","choices":[{"index":0,"delta":{"content":"","role":"assistant","reasoning":":","reasoning_details":[{"type":"reasoning.text","text":":","format":"unknown","index":0}]},"finish_reason":null,"native_finish_reason":null}]} - -data: {"id":"gen-1791215164-iEel1q5g7Q2702ZUqR69","object":"chat.completion.chunk","created":1791215164,"model":"qwen/qwen3-30b-a3b","provider":"DeepInfra","choices":[{"index":0,"delta":{"content":"","role":"assistant","reasoning":" researcher","reasoning_details":[{"type":"reasoning.text","text":" researcher","format":"unknown","index":0}]},"finish_reason":null,"native_finish_reason":null}]} - -data: {"id":"gen-1791215164-iEel1q5g7Q2702ZUqR69","object":"chat.completion.chunk","created":1791215164,"model":"qwen/qwen3-30b-a3b","provider":"DeepInfra","choices":[{"index":0,"delta":{"content":"","role":"assistant","reasoning":" with","reasoning_details":[{"type":"reasoning.text","text":" with","format":"unknown","index":0}]},"finish_reason":null,"native_finish_reason":null}]} - -data: {"id":"gen-1791215164-iEel1q5g7Q2702ZUqR69","object":"chat.completion.chunk","created":1791215164,"model":"qwen/qwen3-30b-a3b","provider":"DeepInfra","choices":[{"index":0,"delta":{"content":"","role":"assistant","reasoning":" the","reasoning_details":[{"type":"reasoning.text","text":" the","format":"unknown","index":0}]},"finish_reason":null,"native_finish_reason":null}]} - -data: {"id":"gen-1791215164-iEel1q5g7Q2702ZUqR69","object":"chat.completion.chunk","created":1791215164,"model":"qwen/qwen3-30b-a3b","provider":"DeepInfra","choices":[{"index":0,"delta":{"content":"","role":"assistant","reasoning":" user","reasoning_details":[{"type":"reasoning.text","text":" user","format":"unknown","index":0}]},"finish_reason":null,"native_finish_reason":null}]} - -data: {"id":"gen-1791215164-iEel1q5g7Q2702ZUqR69","object":"chat.completion.chunk","created":1791215164,"model":"qwen/qwen3-30b-a3b","provider":"DeepInfra","choices":[{"index":0,"delta":{"content":"","role":"assistant","reasoning":"'s","reasoning_details":[{"type":"reasoning.text","text":"'s","format":"unknown","index":0}]},"finish_reason":null,"native_finish_reason":null}]} - -data: {"id":"gen-1791215164-iEel1q5g7Q2702ZUqR69","object":"chat.completion.chunk","created":1791215164,"model":"qwen/qwen3-30b-a3b","provider":"DeepInfra","choices":[{"index":0,"delta":{"content":"","role":"assistant","reasoning":" message","reasoning_details":[{"type":"reasoning.text","text":" message","format":"unknown","index":0}]},"finish_reason":null,"native_finish_reason":null}]} - -data: {"id":"gen-1791215164-iEel1q5g7Q2702ZUqR69","object":"chat.completion.chunk","created":1791215164,"model":"qwen/qwen3-30b-a3b","provider":"DeepInfra","choices":[{"index":0,"delta":{"content":"","role":"assistant","reasoning":" and","reasoning_details":[{"type":"reasoning.text","text":" and","format":"unknown","index":0}]},"finish_reason":null,"native_finish_reason":null}]} - -data: {"id":"gen-1791215164-iEel1q5g7Q2702ZUqR69","object":"chat.completion.chunk","created":1791215164,"model":"qwen/qwen3-30b-a3b","provider":"DeepInfra","choices":[{"index":0,"delta":{"content":"","role":"assistant","reasoning":" read","reasoning_details":[{"type":"reasoning.text","text":" read","format":"unknown","index":0}]},"finish_reason":null,"native_finish_reason":null}]} - -data: {"id":"gen-1791215164-iEel1q5g7Q2702ZUqR69","object":"chat.completion.chunk","created":1791215164,"model":"qwen/qwen3-30b-a3b","provider":"DeepInfra","choices":[{"index":0,"delta":{"content":"","role":"assistant","reasoning":" with","reasoning_details":[{"type":"reasoning.text","text":" with","format":"unknown","index":0}]},"finish_reason":null,"native_finish_reason":null}]} - -data: {"id":"gen-1791215164-iEel1q5g7Q2702ZUqR69","object":"chat.completion.chunk","created":1791215164,"model":"qwen/qwen3-30b-a3b","provider":"DeepInfra","choices":[{"index":0,"delta":{"content":"","role":"assistant","reasoning":" the","reasoning_details":[{"type":"reasoning.text","text":" the","format":"unknown","index":0}]},"finish_reason":null,"native_finish_reason":null}]} - -data: {"id":"gen-1791215164-iEel1q5g7Q2702ZUqR69","object":"chat.completion.chunk","created":1791215164,"model":"qwen/qwen3-30b-a3b","provider":"DeepInfra","choices":[{"index":0,"delta":{"content":"","role":"assistant","reasoning":" specific","reasoning_details":[{"type":"reasoning.text","text":" specific","format":"unknown","index":0}]},"finish_reason":null,"native_finish_reason":null}]} - -data: {"id":"gen-1791215164-iEel1q5g7Q2702ZUqR69","object":"chat.completion.chunk","created":1791215164,"model":"qwen/qwen3-30b-a3b","provider":"DeepInfra","choices":[{"index":0,"delta":{"content":"","role":"assistant","reasoning":" URL","reasoning_details":[{"type":"reasoning.text","text":" URL","format":"unknown","index":0}]},"finish_reason":null,"native_finish_reason":null}]} - -data: {"id":"gen-1791215164-iEel1q5g7Q2702ZUqR69","object":"chat.completion.chunk","created":1791215164,"model":"qwen/qwen3-30b-a3b","provider":"DeepInfra","choices":[{"index":0,"delta":{"content":"","role":"assistant","reasoning":".","reasoning_details":[{"type":"reasoning.text","text":".","format":"unknown","index":0}]},"finish_reason":null,"native_finish_reason":null}]} - -data: {"id":"gen-1791215164-iEel1q5g7Q2702ZUqR69","object":"chat.completion.chunk","created":1791215164,"model":"qwen/qwen3-30b-a3b","provider":"DeepInfra","choices":[{"index":0,"delta":{"content":"","role":"assistant","reasoning":" Both","reasoning_details":[{"type":"reasoning.text","text":" Both","format":"unknown","index":0}]},"finish_reason":null,"native_finish_reason":null}]} - -data: {"id":"gen-1791215164-iEel1q5g7Q2702ZUqR69","object":"chat.completion.chunk","created":1791215164,"model":"qwen/qwen3-30b-a3b","provider":"DeepInfra","choices":[{"index":0,"delta":{"content":"","role":"assistant","reasoning":" were","reasoning_details":[{"type":"reasoning.text","text":" were","format":"unknown","index":0}]},"finish_reason":null,"native_finish_reason":null}]} - -data: {"id":"gen-1791215164-iEel1q5g7Q2702ZUqR69","object":"chat.completion.chunk","created":1791215164,"model":"qwen/qwen3-30b-a3b","provider":"DeepInfra","choices":[{"index":0,"delta":{"content":"","role":"assistant","reasoning":" made","reasoning_details":[{"type":"reasoning.text","text":" made","format":"unknown","index":0}]},"finish_reason":null,"native_finish_reason":null}]} - -data: {"id":"gen-1791215164-iEel1q5g7Q2702ZUqR69","object":"chat.completion.chunk","created":1791215164,"model":"qwen/qwen3-30b-a3b","provider":"DeepInfra","choices":[{"index":0,"delta":{"content":"","role":"assistant","reasoning":",","reasoning_details":[{"type":"reasoning.text","text":",","format":"unknown","index":0}]},"finish_reason":null,"native_finish_reason":null}]} - -data: {"id":"gen-1791215164-iEel1q5g7Q2702ZUqR69","object":"chat.completion.chunk","created":1791215164,"model":"qwen/qwen3-30b-a3b","provider":"DeepInfra","choices":[{"index":0,"delta":{"content":"","role":"assistant","reasoning":" and","reasoning_details":[{"type":"reasoning.text","text":" and","format":"unknown","index":0}]},"finish_reason":null,"native_finish_reason":null}]} - -data: {"id":"gen-1791215164-iEel1q5g7Q2702ZUqR69","object":"chat.completion.chunk","created":1791215164,"model":"qwen/qwen3-30b-a3b","provider":"DeepInfra","choices":[{"index":0,"delta":{"content":"","role":"assistant","reasoning":" now","reasoning_details":[{"type":"reasoning.text","text":" now","format":"unknown","index":0}]},"finish_reason":null,"native_finish_reason":null}]} - -data: {"id":"gen-1791215164-iEel1q5g7Q2702ZUqR69","object":"chat.completion.chunk","created":1791215164,"model":"qwen/qwen3-30b-a3b","provider":"DeepInfra","choices":[{"index":0,"delta":{"content":"","role":"assistant","reasoning":" the","reasoning_details":[{"type":"reasoning.text","text":" the","format":"unknown","index":0}]},"finish_reason":null,"native_finish_reason":null}]} - -data: {"id":"gen-1791215164-iEel1q5g7Q2702ZUqR69","object":"chat.completion.chunk","created":1791215164,"model":"qwen/qwen3-30b-a3b","provider":"DeepInfra","choices":[{"index":0,"delta":{"content":"","role":"assistant","reasoning":" results","reasoning_details":[{"type":"reasoning.text","text":" results","format":"unknown","index":0}]},"finish_reason":null,"native_finish_reason":null}]} - -data: {"id":"gen-1791215164-iEel1q5g7Q2702ZUqR69","object":"chat.completion.chunk","created":1791215164,"model":"qwen/qwen3-30b-a3b","provider":"DeepInfra","choices":[{"index":0,"delta":{"content":"","role":"assistant","reasoning":" are","reasoning_details":[{"type":"reasoning.text","text":" are","format":"unknown","index":0}]},"finish_reason":null,"native_finish_reason":null}]} - -data: {"id":"gen-1791215164-iEel1q5g7Q2702ZUqR69","object":"chat.completion.chunk","created":1791215164,"model":"qwen/qwen3-30b-a3b","provider":"DeepInfra","choices":[{"index":0,"delta":{"content":"","role":"assistant","reasoning":" in","reasoning_details":[{"type":"reasoning.text","text":" in","format":"unknown","index":0}]},"finish_reason":null,"native_finish_reason":null}]} - -data: {"id":"gen-1791215164-iEel1q5g7Q2702ZUqR69","object":"chat.completion.chunk","created":1791215164,"model":"qwen/qwen3-30b-a3b","provider":"DeepInfra","choices":[{"index":0,"delta":{"content":"","role":"assistant","reasoning":".\n\n","reasoning_details":[{"type":"reasoning.text","text":".\n\n","format":"unknown","index":0}]},"finish_reason":null,"native_finish_reason":null}]} - -data: {"id":"gen-1791215164-iEel1q5g7Q2702ZUqR69","object":"chat.completion.chunk","created":1791215164,"model":"qwen/qwen3-30b-a3b","provider":"DeepInfra","choices":[{"index":0,"delta":{"content":"","role":"assistant","reasoning":"The","reasoning_details":[{"type":"reasoning.text","text":"The","format":"unknown","index":0}]},"finish_reason":null,"native_finish_reason":null}]} - -data: {"id":"gen-1791215164-iEel1q5g7Q2702ZUqR69","object":"chat.completion.chunk","created":1791215164,"model":"qwen/qwen3-30b-a3b","provider":"DeepInfra","choices":[{"index":0,"delta":{"content":"","role":"assistant","reasoning":" researcher","reasoning_details":[{"type":"reasoning.text","text":" researcher","format":"unknown","index":0}]},"finish_reason":null,"native_finish_reason":null}]} - -data: {"id":"gen-1791215164-iEel1q5g7Q2702ZUqR69","object":"chat.completion.chunk","created":1791215164,"model":"qwen/qwen3-30b-a3b","provider":"DeepInfra","choices":[{"index":0,"delta":{"content":"","role":"assistant","reasoning":" result","reasoning_details":[{"type":"reasoning.text","text":" result","format":"unknown","index":0}]},"finish_reason":null,"native_finish_reason":null}]} - -data: {"id":"gen-1791215164-iEel1q5g7Q2702ZUqR69","object":"chat.completion.chunk","created":1791215164,"model":"qwen/qwen3-30b-a3b","provider":"DeepInfra","choices":[{"index":0,"delta":{"content":"","role":"assistant","reasoning":" mentions","reasoning_details":[{"type":"reasoning.text","text":" mentions","format":"unknown","index":0}]},"finish_reason":null,"native_finish_reason":null}]} - -data: {"id":"gen-1791215164-iEel1q5g7Q2702ZUqR69","object":"chat.completion.chunk","created":1791215164,"model":"qwen/qwen3-30b-a3b","provider":"DeepInfra","choices":[{"index":0,"delta":{"content":"","role":"assistant","reasoning":" \"","reasoning_details":[{"type":"reasoning.text","text":" \"","format":"unknown","index":0}]},"finish_reason":null,"native_finish_reason":null}]} - -data: {"id":"gen-1791215164-iEel1q5g7Q2702ZUqR69","object":"chat.completion.chunk","created":1791215164,"model":"qwen/qwen3-30b-a3b","provider":"DeepInfra","choices":[{"index":0,"delta":{"content":"","role":"assistant","reasoning":"E","reasoning_details":[{"type":"reasoning.text","text":"E","format":"unknown","index":0}]},"finish_reason":null,"native_finish_reason":null}]} - -data: {"id":"gen-1791215164-iEel1q5g7Q2702ZUqR69","object":"chat.completion.chunk","created":1791215164,"model":"qwen/qwen3-30b-a3b","provider":"DeepInfra","choices":[{"index":0,"delta":{"content":"","role":"assistant","reasoning":"ve","reasoning_details":[{"type":"reasoning.text","text":"ve","format":"unknown","index":0}]},"finish_reason":null,"native_finish_reason":null}]} - -data: {"id":"gen-1791215164-iEel1q5g7Q2702ZUqR69","object":"chat.completion.chunk","created":1791215164,"model":"qwen/qwen3-30b-a3b","provider":"DeepInfra","choices":[{"index":0,"delta":{"content":"","role":"assistant","reasoning":" instrumentation","reasoning_details":[{"type":"reasoning.text","text":" instrumentation","format":"unknown","index":0}]},"finish_reason":null,"native_finish_reason":null}]} - -data: {"id":"gen-1791215164-iEel1q5g7Q2702ZUqR69","object":"chat.completion.chunk","created":1791215164,"model":"qwen/qwen3-30b-a3b","provider":"DeepInfra","choices":[{"index":0,"delta":{"content":"","role":"assistant","reasoning":" https","reasoning_details":[{"type":"reasoning.text","text":" https","format":"unknown","index":0}]},"finish_reason":null,"native_finish_reason":null}]} - -data: {"id":"gen-1791215164-iEel1q5g7Q2702ZUqR69","object":"chat.completion.chunk","created":1791215164,"model":"qwen/qwen3-30b-a3b","provider":"DeepInfra","choices":[{"index":0,"delta":{"content":"","role":"assistant","reasoning":"://","reasoning_details":[{"type":"reasoning.text","text":"://","format":"unknown","index":0}]},"finish_reason":null,"native_finish_reason":null}]} - -data: {"id":"gen-1791215164-iEel1q5g7Q2702ZUqR69","object":"chat.completion.chunk","created":1791215164,"model":"qwen/qwen3-30b-a3b","provider":"DeepInfra","choices":[{"index":0,"delta":{"content":"","role":"assistant","reasoning":"eve","reasoning_details":[{"type":"reasoning.text","text":"eve","format":"unknown","index":0}]},"finish_reason":null,"native_finish_reason":null}]} - -data: {"id":"gen-1791215164-iEel1q5g7Q2702ZUqR69","object":"chat.completion.chunk","created":1791215164,"model":"qwen/qwen3-30b-a3b","provider":"DeepInfra","choices":[{"index":0,"delta":{"content":"","role":"assistant","reasoning":".dev","reasoning_details":[{"type":"reasoning.text","text":".dev","format":"unknown","index":0}]},"finish_reason":null,"native_finish_reason":null}]} - -data: {"id":"gen-1791215164-iEel1q5g7Q2702ZUqR69","object":"chat.completion.chunk","created":1791215164,"model":"qwen/qwen3-30b-a3b","provider":"DeepInfra","choices":[{"index":0,"delta":{"content":"","role":"assistant","reasoning":"/docs","reasoning_details":[{"type":"reasoning.text","text":"/docs","format":"unknown","index":0}]},"finish_reason":null,"native_finish_reason":null}]} - -data: {"id":"gen-1791215164-iEel1q5g7Q2702ZUqR69","object":"chat.completion.chunk","created":1791215164,"model":"qwen/qwen3-30b-a3b","provider":"DeepInfra","choices":[{"index":0,"delta":{"content":"","role":"assistant","reasoning":"/g","reasoning_details":[{"type":"reasoning.text","text":"/g","format":"unknown","index":0}]},"finish_reason":null,"native_finish_reason":null}]} - -data: {"id":"gen-1791215164-iEel1q5g7Q2702ZUqR69","object":"chat.completion.chunk","created":1791215164,"model":"qwen/qwen3-30b-a3b","provider":"DeepInfra","choices":[{"index":0,"delta":{"content":"","role":"assistant","reasoning":"uid","reasoning_details":[{"type":"reasoning.text","text":"uid","format":"unknown","index":0}]},"finish_reason":null,"native_finish_reason":null}]} - -data: {"id":"gen-1791215164-iEel1q5g7Q2702ZUqR69","object":"chat.completion.chunk","created":1791215164,"model":"qwen/qwen3-30b-a3b","provider":"DeepInfra","choices":[{"index":0,"delta":{"content":"","role":"assistant","reasoning":"es","reasoning_details":[{"type":"reasoning.text","text":"es","format":"unknown","index":0}]},"finish_reason":null,"native_finish_reason":null}]} - -data: {"id":"gen-1791215164-iEel1q5g7Q2702ZUqR69","object":"chat.completion.chunk","created":1791215164,"model":"qwen/qwen3-30b-a3b","provider":"DeepInfra","choices":[{"index":0,"delta":{"content":"","role":"assistant","reasoning":"/in","reasoning_details":[{"type":"reasoning.text","text":"/in","format":"unknown","index":0}]},"finish_reason":null,"native_finish_reason":null}]} - -data: {"id":"gen-1791215164-iEel1q5g7Q2702ZUqR69","object":"chat.completion.chunk","created":1791215164,"model":"qwen/qwen3-30b-a3b","provider":"DeepInfra","choices":[{"index":0,"delta":{"content":"","role":"assistant","reasoning":"strument","reasoning_details":[{"type":"reasoning.text","text":"strument","format":"unknown","index":0}]},"finish_reason":null,"native_finish_reason":null}]} - -data: {"id":"gen-1791215164-iEel1q5g7Q2702ZUqR69","object":"chat.completion.chunk","created":1791215164,"model":"qwen/qwen3-30b-a3b","provider":"DeepInfra","choices":[{"index":0,"delta":{"content":"","role":"assistant","reasoning":"ation","reasoning_details":[{"type":"reasoning.text","text":"ation","format":"unknown","index":0}]},"finish_reason":null,"native_finish_reason":null}]} - -data: {"id":"gen-1791215164-iEel1q5g7Q2702ZUqR69","object":"chat.completion.chunk","created":1791215164,"model":"qwen/qwen3-30b-a3b","provider":"DeepInfra","choices":[{"index":0,"delta":{"content":"","role":"assistant","reasoning":",\"","reasoning_details":[{"type":"reasoning.text","text":",\"","format":"unknown","index":0}]},"finish_reason":null,"native_finish_reason":null}]} - -data: {"id":"gen-1791215164-iEel1q5g7Q2702ZUqR69","object":"chat.completion.chunk","created":1791215164,"model":"qwen/qwen3-30b-a3b","provider":"DeepInfra","choices":[{"index":0,"delta":{"content":"","role":"assistant","reasoning":" which","reasoning_details":[{"type":"reasoning.text","text":" which","format":"unknown","index":0}]},"finish_reason":null,"native_finish_reason":null}]} - -data: {"id":"gen-1791215164-iEel1q5g7Q2702ZUqR69","object":"chat.completion.chunk","created":1791215164,"model":"qwen/qwen3-30b-a3b","provider":"DeepInfra","choices":[{"index":0,"delta":{"content":"","role":"assistant","reasoning":" seems","reasoning_details":[{"type":"reasoning.text","text":" seems","format":"unknown","index":0}]},"finish_reason":null,"native_finish_reason":null}]} - -data: {"id":"gen-1791215164-iEel1q5g7Q2702ZUqR69","object":"chat.completion.chunk","created":1791215164,"model":"qwen/qwen3-30b-a3b","provider":"DeepInfra","choices":[{"index":0,"delta":{"content":"","role":"assistant","reasoning":" like","reasoning_details":[{"type":"reasoning.text","text":" like","format":"unknown","index":0}]},"finish_reason":null,"native_finish_reason":null}]} - -data: {"id":"gen-1791215164-iEel1q5g7Q2702ZUqR69","object":"chat.completion.chunk","created":1791215164,"model":"qwen/qwen3-30b-a3b","provider":"DeepInfra","choices":[{"index":0,"delta":{"content":"","role":"assistant","reasoning":" a","reasoning_details":[{"type":"reasoning.text","text":" a","format":"unknown","index":0}]},"finish_reason":null,"native_finish_reason":null}]} - -data: {"id":"gen-1791215164-iEel1q5g7Q2702ZUqR69","object":"chat.completion.chunk","created":1791215164,"model":"qwen/qwen3-30b-a3b","provider":"DeepInfra","choices":[{"index":0,"delta":{"content":"","role":"assistant","reasoning":" confirmation","reasoning_details":[{"type":"reasoning.text","text":" confirmation","format":"unknown","index":0}]},"finish_reason":null,"native_finish_reason":null}]} - -data: {"id":"gen-1791215164-iEel1q5g7Q2702ZUqR69","object":"chat.completion.chunk","created":1791215164,"model":"qwen/qwen3-30b-a3b","provider":"DeepInfra","choices":[{"index":0,"delta":{"content":"","role":"assistant","reasoning":" that","reasoning_details":[{"type":"reasoning.text","text":" that","format":"unknown","index":0}]},"finish_reason":null,"native_finish_reason":null}]} - -data: {"id":"gen-1791215164-iEel1q5g7Q2702ZUqR69","object":"chat.completion.chunk","created":1791215164,"model":"qwen/qwen3-30b-a3b","provider":"DeepInfra","choices":[{"index":0,"delta":{"content":"","role":"assistant","reasoning":" the","reasoning_details":[{"type":"reasoning.text","text":" the","format":"unknown","index":0}]},"finish_reason":null,"native_finish_reason":null}]} - -data: {"id":"gen-1791215164-iEel1q5g7Q2702ZUqR69","object":"chat.completion.chunk","created":1791215164,"model":"qwen/qwen3-30b-a3b","provider":"DeepInfra","choices":[{"index":0,"delta":{"content":"","role":"assistant","reasoning":" research","reasoning_details":[{"type":"reasoning.text","text":" research","format":"unknown","index":0}]},"finish_reason":null,"native_finish_reason":null}]} - -data: {"id":"gen-1791215164-iEel1q5g7Q2702ZUqR69","object":"chat.completion.chunk","created":1791215164,"model":"qwen/qwen3-30b-a3b","provider":"DeepInfra","choices":[{"index":0,"delta":{"content":"","role":"assistant","reasoning":" task","reasoning_details":[{"type":"reasoning.text","text":" task","format":"unknown","index":0}]},"finish_reason":null,"native_finish_reason":null}]} - -data: {"id":"gen-1791215164-iEel1q5g7Q2702ZUqR69","object":"chat.completion.chunk","created":1791215164,"model":"qwen/qwen3-30b-a3b","provider":"DeepInfra","choices":[{"index":0,"delta":{"content":"","role":"assistant","reasoning":" was","reasoning_details":[{"type":"reasoning.text","text":" was","format":"unknown","index":0}]},"finish_reason":null,"native_finish_reason":null}]} - -data: {"id":"gen-1791215164-iEel1q5g7Q2702ZUqR69","object":"chat.completion.chunk","created":1791215164,"model":"qwen/qwen3-30b-a3b","provider":"DeepInfra","choices":[{"index":0,"delta":{"content":"","role":"assistant","reasoning":" started","reasoning_details":[{"type":"reasoning.text","text":" started","format":"unknown","index":0}]},"finish_reason":null,"native_finish_reason":null}]} - -data: {"id":"gen-1791215164-iEel1q5g7Q2702ZUqR69","object":"chat.completion.chunk","created":1791215164,"model":"qwen/qwen3-30b-a3b","provider":"DeepInfra","choices":[{"index":0,"delta":{"content":"","role":"assistant","reasoning":".","reasoning_details":[{"type":"reasoning.text","text":".","format":"unknown","index":0}]},"finish_reason":null,"native_finish_reason":null}]} - -data: {"id":"gen-1791215164-iEel1q5g7Q2702ZUqR69","object":"chat.completion.chunk","created":1791215164,"model":"qwen/qwen3-30b-a3b","provider":"DeepInfra","choices":[{"index":0,"delta":{"content":"","role":"assistant","reasoning":" The","reasoning_details":[{"type":"reasoning.text","text":" The","format":"unknown","index":0}]},"finish_reason":null,"native_finish_reason":null}]} - -data: {"id":"gen-1791215164-iEel1q5g7Q2702ZUqR69","object":"chat.completion.chunk","created":1791215164,"model":"qwen/qwen3-30b-a3b","provider":"DeepInfra","choices":[{"index":0,"delta":{"content":"","role":"assistant","reasoning":" read","reasoning_details":[{"type":"reasoning.text","text":" read","format":"unknown","index":0}]},"finish_reason":null,"native_finish_reason":null}]} - -data: {"id":"gen-1791215164-iEel1q5g7Q2702ZUqR69","object":"chat.completion.chunk","created":1791215164,"model":"qwen/qwen3-30b-a3b","provider":"DeepInfra","choices":[{"index":0,"delta":{"content":"","role":"assistant","reasoning":" tool","reasoning_details":[{"type":"reasoning.text","text":" tool","format":"unknown","index":0}]},"finish_reason":null,"native_finish_reason":null}]} - -data: {"id":"gen-1791215164-iEel1q5g7Q2702ZUqR69","object":"chat.completion.chunk","created":1791215164,"model":"qwen/qwen3-30b-a3b","provider":"DeepInfra","choices":[{"index":0,"delta":{"content":"","role":"assistant","reasoning":" returned","reasoning_details":[{"type":"reasoning.text","text":" returned","format":"unknown","index":0}]},"finish_reason":null,"native_finish_reason":null}]} - -data: {"id":"gen-1791215164-iEel1q5g7Q2702ZUqR69","object":"chat.completion.chunk","created":1791215164,"model":"qwen/qwen3-30b-a3b","provider":"DeepInfra","choices":[{"index":0,"delta":{"content":"","role":"assistant","reasoning":" a","reasoning_details":[{"type":"reasoning.text","text":" a","format":"unknown","index":0}]},"finish_reason":null,"native_finish_reason":null}]} - -data: {"id":"gen-1791215164-iEel1q5g7Q2702ZUqR69","object":"chat.completion.chunk","created":1791215164,"model":"qwen/qwen3-30b-a3b","provider":"DeepInfra","choices":[{"index":0,"delta":{"content":"","role":"assistant","reasoning":" snippet","reasoning_details":[{"type":"reasoning.text","text":" snippet","format":"unknown","index":0}]},"finish_reason":null,"native_finish_reason":null}]} - -data: {"id":"gen-1791215164-iEel1q5g7Q2702ZUqR69","object":"chat.completion.chunk","created":1791215164,"model":"qwen/qwen3-30b-a3b","provider":"DeepInfra","choices":[{"index":0,"delta":{"content":"","role":"assistant","reasoning":" from","reasoning_details":[{"type":"reasoning.text","text":" from","format":"unknown","index":0}]},"finish_reason":null,"native_finish_reason":null}]} - -data: {"id":"gen-1791215164-iEel1q5g7Q2702ZUqR69","object":"chat.completion.chunk","created":1791215164,"model":"qwen/qwen3-30b-a3b","provider":"DeepInfra","choices":[{"index":0,"delta":{"content":"","role":"assistant","reasoning":" the","reasoning_details":[{"type":"reasoning.text","text":" the","format":"unknown","index":0}]},"finish_reason":null,"native_finish_reason":null}]} - -data: {"id":"gen-1791215164-iEel1q5g7Q2702ZUqR69","object":"chat.completion.chunk","created":1791215164,"model":"qwen/qwen3-30b-a3b","provider":"DeepInfra","choices":[{"index":0,"delta":{"content":"","role":"assistant","reasoning":" documentation","reasoning_details":[{"type":"reasoning.text","text":" documentation","format":"unknown","index":0}]},"finish_reason":null,"native_finish_reason":null}]} - -data: {"id":"gen-1791215164-iEel1q5g7Q2702ZUqR69","object":"chat.completion.chunk","created":1791215164,"model":"qwen/qwen3-30b-a3b","provider":"DeepInfra","choices":[{"index":0,"delta":{"content":"","role":"assistant","reasoning":",","reasoning_details":[{"type":"reasoning.text","text":",","format":"unknown","index":0}]},"finish_reason":null,"native_finish_reason":null}]} - -data: {"id":"gen-1791215164-iEel1q5g7Q2702ZUqR69","object":"chat.completion.chunk","created":1791215164,"model":"qwen/qwen3-30b-a3b","provider":"DeepInfra","choices":[{"index":0,"delta":{"content":"","role":"assistant","reasoning":" including","reasoning_details":[{"type":"reasoning.text","text":" including","format":"unknown","index":0}]},"finish_reason":null,"native_finish_reason":null}]} - -data: {"id":"gen-1791215164-iEel1q5g7Q2702ZUqR69","object":"chat.completion.chunk","created":1791215164,"model":"qwen/qwen3-30b-a3b","provider":"DeepInfra","choices":[{"index":0,"delta":{"content":"","role":"assistant","reasoning":" the","reasoning_details":[{"type":"reasoning.text","text":" the","format":"unknown","index":0}]},"finish_reason":null,"native_finish_reason":null}]} - -data: {"id":"gen-1791215164-iEel1q5g7Q2702ZUqR69","object":"chat.completion.chunk","created":1791215164,"model":"qwen/qwen3-30b-a3b","provider":"DeepInfra","choices":[{"index":0,"delta":{"content":"","role":"assistant","reasoning":" title","reasoning_details":[{"type":"reasoning.text","text":" title","format":"unknown","index":0}]},"finish_reason":null,"native_finish_reason":null}]} - -data: {"id":"gen-1791215164-iEel1q5g7Q2702ZUqR69","object":"chat.completion.chunk","created":1791215164,"model":"qwen/qwen3-30b-a3b","provider":"DeepInfra","choices":[{"index":0,"delta":{"content":"","role":"assistant","reasoning":",","reasoning_details":[{"type":"reasoning.text","text":",","format":"unknown","index":0}]},"finish_reason":null,"native_finish_reason":null}]} - -data: {"id":"gen-1791215164-iEel1q5g7Q2702ZUqR69","object":"chat.completion.chunk","created":1791215164,"model":"qwen/qwen3-30b-a3b","provider":"DeepInfra","choices":[{"index":0,"delta":{"content":"","role":"assistant","reasoning":" section","reasoning_details":[{"type":"reasoning.text","text":" section","format":"unknown","index":0}]},"finish_reason":null,"native_finish_reason":null}]} - -data: {"id":"gen-1791215164-iEel1q5g7Q2702ZUqR69","object":"chat.completion.chunk","created":1791215164,"model":"qwen/qwen3-30b-a3b","provider":"DeepInfra","choices":[{"index":0,"delta":{"content":"","role":"assistant","reasoning":",","reasoning_details":[{"type":"reasoning.text","text":",","format":"unknown","index":0}]},"finish_reason":null,"native_finish_reason":null}]} - -data: {"id":"gen-1791215164-iEel1q5g7Q2702ZUqR69","object":"chat.completion.chunk","created":1791215164,"model":"qwen/qwen3-30b-a3b","provider":"DeepInfra","choices":[{"index":0,"delta":{"content":"","role":"assistant","reasoning":" and","reasoning_details":[{"type":"reasoning.text","text":" and","format":"unknown","index":0}]},"finish_reason":null,"native_finish_reason":null}]} - -data: {"id":"gen-1791215164-iEel1q5g7Q2702ZUqR69","object":"chat.completion.chunk","created":1791215164,"model":"qwen/qwen3-30b-a3b","provider":"DeepInfra","choices":[{"index":0,"delta":{"content":"","role":"assistant","reasoning":" an","reasoning_details":[{"type":"reasoning.text","text":" an","format":"unknown","index":0}]},"finish_reason":null,"native_finish_reason":null}]} - -data: {"id":"gen-1791215164-iEel1q5g7Q2702ZUqR69","object":"chat.completion.chunk","created":1791215164,"model":"qwen/qwen3-30b-a3b","provider":"DeepInfra","choices":[{"index":0,"delta":{"content":"","role":"assistant","reasoning":" excerpt","reasoning_details":[{"type":"reasoning.text","text":" excerpt","format":"unknown","index":0}]},"finish_reason":null,"native_finish_reason":null}]} - -data: {"id":"gen-1791215164-iEel1q5g7Q2702ZUqR69","object":"chat.completion.chunk","created":1791215164,"model":"qwen/qwen3-30b-a3b","provider":"DeepInfra","choices":[{"index":0,"delta":{"content":"","role":"assistant","reasoning":" about","reasoning_details":[{"type":"reasoning.text","text":" about","format":"unknown","index":0}]},"finish_reason":null,"native_finish_reason":null}]} - -data: {"id":"gen-1791215164-iEel1q5g7Q2702ZUqR69","object":"chat.completion.chunk","created":1791215164,"model":"qwen/qwen3-30b-a3b","provider":"DeepInfra","choices":[{"index":0,"delta":{"content":"","role":"assistant","reasoning":" Eve","reasoning_details":[{"type":"reasoning.text","text":" Eve","format":"unknown","index":0}]},"finish_reason":null,"native_finish_reason":null}]} - -data: {"id":"gen-1791215164-iEel1q5g7Q2702ZUqR69","object":"chat.completion.chunk","created":1791215164,"model":"qwen/qwen3-30b-a3b","provider":"DeepInfra","choices":[{"index":0,"delta":{"content":"","role":"assistant","reasoning":" hooks","reasoning_details":[{"type":"reasoning.text","text":" hooks","format":"unknown","index":0}]},"finish_reason":null,"native_finish_reason":null}]} - -data: {"id":"gen-1791215164-iEel1q5g7Q2702ZUqR69","object":"chat.completion.chunk","created":1791215164,"model":"qwen/qwen3-30b-a3b","provider":"DeepInfra","choices":[{"index":0,"delta":{"content":"","role":"assistant","reasoning":" mapping","reasoning_details":[{"type":"reasoning.text","text":" mapping","format":"unknown","index":0}]},"finish_reason":null,"native_finish_reason":null}]} - -data: {"id":"gen-1791215164-iEel1q5g7Q2702ZUqR69","object":"chat.completion.chunk","created":1791215164,"model":"qwen/qwen3-30b-a3b","provider":"DeepInfra","choices":[{"index":0,"delta":{"content":"","role":"assistant","reasoning":" to","reasoning_details":[{"type":"reasoning.text","text":" to","format":"unknown","index":0}]},"finish_reason":null,"native_finish_reason":null}]} - -data: {"id":"gen-1791215164-iEel1q5g7Q2702ZUqR69","object":"chat.completion.chunk","created":1791215164,"model":"qwen/qwen3-30b-a3b","provider":"DeepInfra","choices":[{"index":0,"delta":{"content":"","role":"assistant","reasoning":" a","reasoning_details":[{"type":"reasoning.text","text":" a","format":"unknown","index":0}]},"finish_reason":null,"native_finish_reason":null}]} - -data: {"id":"gen-1791215164-iEel1q5g7Q2702ZUqR69","object":"chat.completion.chunk","created":1791215164,"model":"qwen/qwen3-30b-a3b","provider":"DeepInfra","choices":[{"index":0,"delta":{"content":"","role":"assistant","reasoning":" nested","reasoning_details":[{"type":"reasoning.text","text":" nested","format":"unknown","index":0}]},"finish_reason":null,"native_finish_reason":null}]} - -data: {"id":"gen-1791215164-iEel1q5g7Q2702ZUqR69","object":"chat.completion.chunk","created":1791215164,"model":"qwen/qwen3-30b-a3b","provider":"DeepInfra","choices":[{"index":0,"delta":{"content":"","role":"assistant","reasoning":" turn","reasoning_details":[{"type":"reasoning.text","text":" turn","format":"unknown","index":0}]},"finish_reason":null,"native_finish_reason":null}]} - -data: {"id":"gen-1791215164-iEel1q5g7Q2702ZUqR69","object":"chat.completion.chunk","created":1791215164,"model":"qwen/qwen3-30b-a3b","provider":"DeepInfra","choices":[{"index":0,"delta":{"content":"","role":"assistant","reasoning":" trace","reasoning_details":[{"type":"reasoning.text","text":" trace","format":"unknown","index":0}]},"finish_reason":null,"native_finish_reason":null}]} - -data: {"id":"gen-1791215164-iEel1q5g7Q2702ZUqR69","object":"chat.completion.chunk","created":1791215164,"model":"qwen/qwen3-30b-a3b","provider":"DeepInfra","choices":[{"index":0,"delta":{"content":"","role":"assistant","reasoning":".\n\n","reasoning_details":[{"type":"reasoning.text","text":".\n\n","format":"unknown","index":0}]},"finish_reason":null,"native_finish_reason":null}]} - -data: {"id":"gen-1791215164-iEel1q5g7Q2702ZUqR69","object":"chat.completion.chunk","created":1791215164,"model":"qwen/qwen3-30b-a3b","provider":"DeepInfra","choices":[{"index":0,"delta":{"content":"","role":"assistant","reasoning":"Now","reasoning_details":[{"type":"reasoning.text","text":"Now","format":"unknown","index":0}]},"finish_reason":null,"native_finish_reason":null}]} - -data: {"id":"gen-1791215164-iEel1q5g7Q2702ZUqR69","object":"chat.completion.chunk","created":1791215164,"model":"qwen/qwen3-30b-a3b","provider":"DeepInfra","choices":[{"index":0,"delta":{"content":"","role":"assistant","reasoning":",","reasoning_details":[{"type":"reasoning.text","text":",","format":"unknown","index":0}]},"finish_reason":null,"native_finish_reason":null}]} - -data: {"id":"gen-1791215164-iEel1q5g7Q2702ZUqR69","object":"chat.completion.chunk","created":1791215164,"model":"qwen/qwen3-30b-a3b","provider":"DeepInfra","choices":[{"index":0,"delta":{"content":"","role":"assistant","reasoning":" the","reasoning_details":[{"type":"reasoning.text","text":" the","format":"unknown","index":0}]},"finish_reason":null,"native_finish_reason":null}]} - -data: {"id":"gen-1791215164-iEel1q5g7Q2702ZUqR69","object":"chat.completion.chunk","created":1791215164,"model":"qwen/qwen3-30b-a3b","provider":"DeepInfra","choices":[{"index":0,"delta":{"content":"","role":"assistant","reasoning":" next","reasoning_details":[{"type":"reasoning.text","text":" next","format":"unknown","index":0}]},"finish_reason":null,"native_finish_reason":null}]} - -data: {"id":"gen-1791215164-iEel1q5g7Q2702ZUqR69","object":"chat.completion.chunk","created":1791215164,"model":"qwen/qwen3-30b-a3b","provider":"DeepInfra","choices":[{"index":0,"delta":{"content":"","role":"assistant","reasoning":" step","reasoning_details":[{"type":"reasoning.text","text":" step","format":"unknown","index":0}]},"finish_reason":null,"native_finish_reason":null}]} - -data: {"id":"gen-1791215164-iEel1q5g7Q2702ZUqR69","object":"chat.completion.chunk","created":1791215164,"model":"qwen/qwen3-30b-a3b","provider":"DeepInfra","choices":[{"index":0,"delta":{"content":"","role":"assistant","reasoning":" is","reasoning_details":[{"type":"reasoning.text","text":" is","format":"unknown","index":0}]},"finish_reason":null,"native_finish_reason":null}]} - -data: {"id":"gen-1791215164-iEel1q5g7Q2702ZUqR69","object":"chat.completion.chunk","created":1791215164,"model":"qwen/qwen3-30b-a3b","provider":"DeepInfra","choices":[{"index":0,"delta":{"content":"","role":"assistant","reasoning":" to","reasoning_details":[{"type":"reasoning.text","text":" to","format":"unknown","index":0}]},"finish_reason":null,"native_finish_reason":null}]} - -data: {"id":"gen-1791215164-iEel1q5g7Q2702ZUqR69","object":"chat.completion.chunk","created":1791215164,"model":"qwen/qwen3-30b-a3b","provider":"DeepInfra","choices":[{"index":0,"delta":{"content":"","role":"assistant","reasoning":" compile","reasoning_details":[{"type":"reasoning.text","text":" compile","format":"unknown","index":0}]},"finish_reason":null,"native_finish_reason":null}]} - -data: {"id":"gen-1791215164-iEel1q5g7Q2702ZUqR69","object":"chat.completion.chunk","created":1791215164,"model":"qwen/qwen3-30b-a3b","provider":"DeepInfra","choices":[{"index":0,"delta":{"content":"","role":"assistant","reasoning":" this","reasoning_details":[{"type":"reasoning.text","text":" this","format":"unknown","index":0}]},"finish_reason":null,"native_finish_reason":null}]} - -data: {"id":"gen-1791215164-iEel1q5g7Q2702ZUqR69","object":"chat.completion.chunk","created":1791215164,"model":"qwen/qwen3-30b-a3b","provider":"DeepInfra","choices":[{"index":0,"delta":{"content":"","role":"assistant","reasoning":" information","reasoning_details":[{"type":"reasoning.text","text":" information","format":"unknown","index":0}]},"finish_reason":null,"native_finish_reason":null}]} - -data: {"id":"gen-1791215164-iEel1q5g7Q2702ZUqR69","object":"chat.completion.chunk","created":1791215164,"model":"qwen/qwen3-30b-a3b","provider":"DeepInfra","choices":[{"index":0,"delta":{"content":"","role":"assistant","reasoning":" into","reasoning_details":[{"type":"reasoning.text","text":" into","format":"unknown","index":0}]},"finish_reason":null,"native_finish_reason":null}]} - -data: {"id":"gen-1791215164-iEel1q5g7Q2702ZUqR69","object":"chat.completion.chunk","created":1791215164,"model":"qwen/qwen3-30b-a3b","provider":"DeepInfra","choices":[{"index":0,"delta":{"content":"","role":"assistant","reasoning":" a","reasoning_details":[{"type":"reasoning.text","text":" a","format":"unknown","index":0}]},"finish_reason":null,"native_finish_reason":null}]} - -data: {"id":"gen-1791215164-iEel1q5g7Q2702ZUqR69","object":"chat.completion.chunk","created":1791215164,"model":"qwen/qwen3-30b-a3b","provider":"DeepInfra","choices":[{"index":0,"delta":{"content":"","role":"assistant","reasoning":" final","reasoning_details":[{"type":"reasoning.text","text":" final","format":"unknown","index":0}]},"finish_reason":null,"native_finish_reason":null}]} - -data: {"id":"gen-1791215164-iEel1q5g7Q2702ZUqR69","object":"chat.completion.chunk","created":1791215164,"model":"qwen/qwen3-30b-a3b","provider":"DeepInfra","choices":[{"index":0,"delta":{"content":"","role":"assistant","reasoning":" answer","reasoning_details":[{"type":"reasoning.text","text":" answer","format":"unknown","index":0}]},"finish_reason":null,"native_finish_reason":null}]} - -data: {"id":"gen-1791215164-iEel1q5g7Q2702ZUqR69","object":"chat.completion.chunk","created":1791215164,"model":"qwen/qwen3-30b-a3b","provider":"DeepInfra","choices":[{"index":0,"delta":{"content":"","role":"assistant","reasoning":".","reasoning_details":[{"type":"reasoning.text","text":".","format":"unknown","index":0}]},"finish_reason":null,"native_finish_reason":null}]} - -data: {"id":"gen-1791215164-iEel1q5g7Q2702ZUqR69","object":"chat.completion.chunk","created":1791215164,"model":"qwen/qwen3-30b-a3b","provider":"DeepInfra","choices":[{"index":0,"delta":{"content":"","role":"assistant","reasoning":" The","reasoning_details":[{"type":"reasoning.text","text":" The","format":"unknown","index":0}]},"finish_reason":null,"native_finish_reason":null}]} - -data: {"id":"gen-1791215164-iEel1q5g7Q2702ZUqR69","object":"chat.completion.chunk","created":1791215164,"model":"qwen/qwen3-30b-a3b","provider":"DeepInfra","choices":[{"index":0,"delta":{"content":"","role":"assistant","reasoning":" instructions","reasoning_details":[{"type":"reasoning.text","text":" instructions","format":"unknown","index":0}]},"finish_reason":null,"native_finish_reason":null}]} - -data: {"id":"gen-1791215164-iEel1q5g7Q2702ZUqR69","object":"chat.completion.chunk","created":1791215164,"model":"qwen/qwen3-30b-a3b","provider":"DeepInfra","choices":[{"index":0,"delta":{"content":"","role":"assistant","reasoning":" specify","reasoning_details":[{"type":"reasoning.text","text":" specify","format":"unknown","index":0}]},"finish_reason":null,"native_finish_reason":null}]} - -data: {"id":"gen-1791215164-iEel1q5g7Q2702ZUqR69","object":"chat.completion.chunk","created":1791215164,"model":"qwen/qwen3-30b-a3b","provider":"DeepInfra","choices":[{"index":0,"delta":{"content":"","role":"assistant","reasoning":" that","reasoning_details":[{"type":"reasoning.text","text":" that","format":"unknown","index":0}]},"finish_reason":null,"native_finish_reason":null}]} - -data: {"id":"gen-1791215164-iEel1q5g7Q2702ZUqR69","object":"chat.completion.chunk","created":1791215164,"model":"qwen/qwen3-30b-a3b","provider":"DeepInfra","choices":[{"index":0,"delta":{"content":"","role":"assistant","reasoning":" the","reasoning_details":[{"type":"reasoning.text","text":" the","format":"unknown","index":0}]},"finish_reason":null,"native_finish_reason":null}]} - -data: {"id":"gen-1791215164-iEel1q5g7Q2702ZUqR69","object":"chat.completion.chunk","created":1791215164,"model":"qwen/qwen3-30b-a3b","provider":"DeepInfra","choices":[{"index":0,"delta":{"content":"","role":"assistant","reasoning":" answer","reasoning_details":[{"type":"reasoning.text","text":" answer","format":"unknown","index":0}]},"finish_reason":null,"native_finish_reason":null}]} - -data: {"id":"gen-1791215164-iEel1q5g7Q2702ZUqR69","object":"chat.completion.chunk","created":1791215164,"model":"qwen/qwen3-30b-a3b","provider":"DeepInfra","choices":[{"index":0,"delta":{"content":"","role":"assistant","reasoning":" must","reasoning_details":[{"type":"reasoning.text","text":" must","format":"unknown","index":0}]},"finish_reason":null,"native_finish_reason":null}]} - -data: {"id":"gen-1791215164-iEel1q5g7Q2702ZUqR69","object":"chat.completion.chunk","created":1791215164,"model":"qwen/qwen3-30b-a3b","provider":"DeepInfra","choices":[{"index":0,"delta":{"content":"","role":"assistant","reasoning":" start","reasoning_details":[{"type":"reasoning.text","text":" start","format":"unknown","index":0}]},"finish_reason":null,"native_finish_reason":null}]} - -data: {"id":"gen-1791215164-iEel1q5g7Q2702ZUqR69","object":"chat.completion.chunk","created":1791215164,"model":"qwen/qwen3-30b-a3b","provider":"DeepInfra","choices":[{"index":0,"delta":{"content":"","role":"assistant","reasoning":" with","reasoning_details":[{"type":"reasoning.text","text":" with","format":"unknown","index":0}]},"finish_reason":null,"native_finish_reason":null}]} - -data: {"id":"gen-1791215164-iEel1q5g7Q2702ZUqR69","object":"chat.completion.chunk","created":1791215164,"model":"qwen/qwen3-30b-a3b","provider":"DeepInfra","choices":[{"index":0,"delta":{"content":"","role":"assistant","reasoning":" \"","reasoning_details":[{"type":"reasoning.text","text":" \"","format":"unknown","index":0}]},"finish_reason":null,"native_finish_reason":null}]} - -data: {"id":"gen-1791215164-iEel1q5g7Q2702ZUqR69","object":"chat.completion.chunk","created":1791215164,"model":"qwen/qwen3-30b-a3b","provider":"DeepInfra","choices":[{"index":0,"delta":{"content":"","role":"assistant","reasoning":"Final","reasoning_details":[{"type":"reasoning.text","text":"Final","format":"unknown","index":0}]},"finish_reason":null,"native_finish_reason":null}]} - -data: {"id":"gen-1791215164-iEel1q5g7Q2702ZUqR69","object":"chat.completion.chunk","created":1791215164,"model":"qwen/qwen3-30b-a3b","provider":"DeepInfra","choices":[{"index":0,"delta":{"content":"","role":"assistant","reasoning":" answer","reasoning_details":[{"type":"reasoning.text","text":" answer","format":"unknown","index":0}]},"finish_reason":null,"native_finish_reason":null}]} - -data: {"id":"gen-1791215164-iEel1q5g7Q2702ZUqR69","object":"chat.completion.chunk","created":1791215164,"model":"qwen/qwen3-30b-a3b","provider":"DeepInfra","choices":[{"index":0,"delta":{"content":"","role":"assistant","reasoning":" from","reasoning_details":[{"type":"reasoning.text","text":" from","format":"unknown","index":0}]},"finish_reason":null,"native_finish_reason":null}]} - -data: {"id":"gen-1791215164-iEel1q5g7Q2702ZUqR69","object":"chat.completion.chunk","created":1791215164,"model":"qwen/qwen3-30b-a3b","provider":"DeepInfra","choices":[{"index":0,"delta":{"content":"","role":"assistant","reasoning":" read","reasoning_details":[{"type":"reasoning.text","text":" read","format":"unknown","index":0}]},"finish_reason":null,"native_finish_reason":null}]} - -data: {"id":"gen-1791215164-iEel1q5g7Q2702ZUqR69","object":"chat.completion.chunk","created":1791215164,"model":"qwen/qwen3-30b-a3b","provider":"DeepInfra","choices":[{"index":0,"delta":{"content":"","role":"assistant","reasoning":":\"","reasoning_details":[{"type":"reasoning.text","text":":\"","format":"unknown","index":0}]},"finish_reason":null,"native_finish_reason":null}]} - -data: {"id":"gen-1791215164-iEel1q5g7Q2702ZUqR69","object":"chat.completion.chunk","created":1791215164,"model":"qwen/qwen3-30b-a3b","provider":"DeepInfra","choices":[{"index":0,"delta":{"content":"","role":"assistant","reasoning":" and","reasoning_details":[{"type":"reasoning.text","text":" and","format":"unknown","index":0}]},"finish_reason":null,"native_finish_reason":null}]} - -data: {"id":"gen-1791215164-iEel1q5g7Q2702ZUqR69","object":"chat.completion.chunk","created":1791215164,"model":"qwen/qwen3-30b-a3b","provider":"DeepInfra","choices":[{"index":0,"delta":{"content":"","role":"assistant","reasoning":" include","reasoning_details":[{"type":"reasoning.text","text":" include","format":"unknown","index":0}]},"finish_reason":null,"native_finish_reason":null}]} - -data: {"id":"gen-1791215164-iEel1q5g7Q2702ZUqR69","object":"chat.completion.chunk","created":1791215164,"model":"qwen/qwen3-30b-a3b","provider":"DeepInfra","choices":[{"index":0,"delta":{"content":"","role":"assistant","reasoning":" the","reasoning_details":[{"type":"reasoning.text","text":" the","format":"unknown","index":0}]},"finish_reason":null,"native_finish_reason":null}]} - -data: {"id":"gen-1791215164-iEel1q5g7Q2702ZUqR69","object":"chat.completion.chunk","created":1791215164,"model":"qwen/qwen3-30b-a3b","provider":"DeepInfra","choices":[{"index":0,"delta":{"content":"","role":"assistant","reasoning":" researcher","reasoning_details":[{"type":"reasoning.text","text":" researcher","format":"unknown","index":0}]},"finish_reason":null,"native_finish_reason":null}]} - -data: {"id":"gen-1791215164-iEel1q5g7Q2702ZUqR69","object":"chat.completion.chunk","created":1791215164,"model":"qwen/qwen3-30b-a3b","provider":"DeepInfra","choices":[{"index":0,"delta":{"content":"","role":"assistant","reasoning":" result","reasoning_details":[{"type":"reasoning.text","text":" result","format":"unknown","index":0}]},"finish_reason":null,"native_finish_reason":null}]} - -data: {"id":"gen-1791215164-iEel1q5g7Q2702ZUqR69","object":"chat.completion.chunk","created":1791215164,"model":"qwen/qwen3-30b-a3b","provider":"DeepInfra","choices":[{"index":0,"delta":{"content":"","role":"assistant","reasoning":",","reasoning_details":[{"type":"reasoning.text","text":",","format":"unknown","index":0}]},"finish_reason":null,"native_finish_reason":null}]} - -data: {"id":"gen-1791215164-iEel1q5g7Q2702ZUqR69","object":"chat.completion.chunk","created":1791215164,"model":"qwen/qwen3-30b-a3b","provider":"DeepInfra","choices":[{"index":0,"delta":{"content":"","role":"assistant","reasoning":" the","reasoning_details":[{"type":"reasoning.text","text":" the","format":"unknown","index":0}]},"finish_reason":null,"native_finish_reason":null}]} - -data: {"id":"gen-1791215164-iEel1q5g7Q2702ZUqR69","object":"chat.completion.chunk","created":1791215164,"model":"qwen/qwen3-30b-a3b","provider":"DeepInfra","choices":[{"index":0,"delta":{"content":"","role":"assistant","reasoning":" read","reasoning_details":[{"type":"reasoning.text","text":" read","format":"unknown","index":0}]},"finish_reason":null,"native_finish_reason":null}]} - -data: {"id":"gen-1791215164-iEel1q5g7Q2702ZUqR69","object":"chat.completion.chunk","created":1791215164,"model":"qwen/qwen3-30b-a3b","provider":"DeepInfra","choices":[{"index":0,"delta":{"content":"","role":"assistant","reasoning":" title","reasoning_details":[{"type":"reasoning.text","text":" title","format":"unknown","index":0}]},"finish_reason":null,"native_finish_reason":null}]} - -data: {"id":"gen-1791215164-iEel1q5g7Q2702ZUqR69","object":"chat.completion.chunk","created":1791215164,"model":"qwen/qwen3-30b-a3b","provider":"DeepInfra","choices":[{"index":0,"delta":{"content":"","role":"assistant","reasoning":",","reasoning_details":[{"type":"reasoning.text","text":",","format":"unknown","index":0}]},"finish_reason":null,"native_finish_reason":null}]} - -data: {"id":"gen-1791215164-iEel1q5g7Q2702ZUqR69","object":"chat.completion.chunk","created":1791215164,"model":"qwen/qwen3-30b-a3b","provider":"DeepInfra","choices":[{"index":0,"delta":{"content":"","role":"assistant","reasoning":" URL","reasoning_details":[{"type":"reasoning.text","text":" URL","format":"unknown","index":0}]},"finish_reason":null,"native_finish_reason":null}]} - -data: {"id":"gen-1791215164-iEel1q5g7Q2702ZUqR69","object":"chat.completion.chunk","created":1791215164,"model":"qwen/qwen3-30b-a3b","provider":"DeepInfra","choices":[{"index":0,"delta":{"content":"","role":"assistant","reasoning":",","reasoning_details":[{"type":"reasoning.text","text":",","format":"unknown","index":0}]},"finish_reason":null,"native_finish_reason":null}]} - -data: {"id":"gen-1791215164-iEel1q5g7Q2702ZUqR69","object":"chat.completion.chunk","created":1791215164,"model":"qwen/qwen3-30b-a3b","provider":"DeepInfra","choices":[{"index":0,"delta":{"content":"","role":"assistant","reasoning":" and","reasoning_details":[{"type":"reasoning.text","text":" and","format":"unknown","index":0}]},"finish_reason":null,"native_finish_reason":null}]} - -data: {"id":"gen-1791215164-iEel1q5g7Q2702ZUqR69","object":"chat.completion.chunk","created":1791215164,"model":"qwen/qwen3-30b-a3b","provider":"DeepInfra","choices":[{"index":0,"delta":{"content":"","role":"assistant","reasoning":" excerpt","reasoning_details":[{"type":"reasoning.text","text":" excerpt","format":"unknown","index":0}]},"finish_reason":null,"native_finish_reason":null}]} - -data: {"id":"gen-1791215164-iEel1q5g7Q2702ZUqR69","object":"chat.completion.chunk","created":1791215164,"model":"qwen/qwen3-30b-a3b","provider":"DeepInfra","choices":[{"index":0,"delta":{"content":"","role":"assistant","reasoning":".","reasoning_details":[{"type":"reasoning.text","text":".","format":"unknown","index":0}]},"finish_reason":null,"native_finish_reason":null}]} - -data: {"id":"gen-1791215164-iEel1q5g7Q2702ZUqR69","object":"chat.completion.chunk","created":1791215164,"model":"qwen/qwen3-30b-a3b","provider":"DeepInfra","choices":[{"index":0,"delta":{"content":"","role":"assistant","reasoning":" \n\n","reasoning_details":[{"type":"reasoning.text","text":" \n\n","format":"unknown","index":0}]},"finish_reason":null,"native_finish_reason":null}]} - -data: {"id":"gen-1791215164-iEel1q5g7Q2702ZUqR69","object":"chat.completion.chunk","created":1791215164,"model":"qwen/qwen3-30b-a3b","provider":"DeepInfra","choices":[{"index":0,"delta":{"content":"","role":"assistant","reasoning":"I","reasoning_details":[{"type":"reasoning.text","text":"I","format":"unknown","index":0}]},"finish_reason":null,"native_finish_reason":null}]} - -data: {"id":"gen-1791215164-iEel1q5g7Q2702ZUqR69","object":"chat.completion.chunk","created":1791215164,"model":"qwen/qwen3-30b-a3b","provider":"DeepInfra","choices":[{"index":0,"delta":{"content":"","role":"assistant","reasoning":" need","reasoning_details":[{"type":"reasoning.text","text":" need","format":"unknown","index":0}]},"finish_reason":null,"native_finish_reason":null}]} - -data: {"id":"gen-1791215164-iEel1q5g7Q2702ZUqR69","object":"chat.completion.chunk","created":1791215164,"model":"qwen/qwen3-30b-a3b","provider":"DeepInfra","choices":[{"index":0,"delta":{"content":"","role":"assistant","reasoning":" to","reasoning_details":[{"type":"reasoning.text","text":" to","format":"unknown","index":0}]},"finish_reason":null,"native_finish_reason":null}]} - -data: {"id":"gen-1791215164-iEel1q5g7Q2702ZUqR69","object":"chat.completion.chunk","created":1791215164,"model":"qwen/qwen3-30b-a3b","provider":"DeepInfra","choices":[{"index":0,"delta":{"content":"","role":"assistant","reasoning":" ensure","reasoning_details":[{"type":"reasoning.text","text":" ensure","format":"unknown","index":0}]},"finish_reason":null,"native_finish_reason":null}]} - -data: {"id":"gen-1791215164-iEel1q5g7Q2702ZUqR69","object":"chat.completion.chunk","created":1791215164,"model":"qwen/qwen3-30b-a3b","provider":"DeepInfra","choices":[{"index":0,"delta":{"content":"","role":"assistant","reasoning":" that","reasoning_details":[{"type":"reasoning.text","text":" that","format":"unknown","index":0}]},"finish_reason":null,"native_finish_reason":null}]} - -data: {"id":"gen-1791215164-iEel1q5g7Q2702ZUqR69","object":"chat.completion.chunk","created":1791215164,"model":"qwen/qwen3-30b-a3b","provider":"DeepInfra","choices":[{"index":0,"delta":{"content":"","role":"assistant","reasoning":" all","reasoning_details":[{"type":"reasoning.text","text":" all","format":"unknown","index":0}]},"finish_reason":null,"native_finish_reason":null}]} - -data: {"id":"gen-1791215164-iEel1q5g7Q2702ZUqR69","object":"chat.completion.chunk","created":1791215164,"model":"qwen/qwen3-30b-a3b","provider":"DeepInfra","choices":[{"index":0,"delta":{"content":"","role":"assistant","reasoning":" required","reasoning_details":[{"type":"reasoning.text","text":" required","format":"unknown","index":0}]},"finish_reason":null,"native_finish_reason":null}]} - -data: {"id":"gen-1791215164-iEel1q5g7Q2702ZUqR69","object":"chat.completion.chunk","created":1791215164,"model":"qwen/qwen3-30b-a3b","provider":"DeepInfra","choices":[{"index":0,"delta":{"content":"","role":"assistant","reasoning":" elements","reasoning_details":[{"type":"reasoning.text","text":" elements","format":"unknown","index":0}]},"finish_reason":null,"native_finish_reason":null}]} - -data: {"id":"gen-1791215164-iEel1q5g7Q2702ZUqR69","object":"chat.completion.chunk","created":1791215164,"model":"qwen/qwen3-30b-a3b","provider":"DeepInfra","choices":[{"index":0,"delta":{"content":"","role":"assistant","reasoning":" are","reasoning_details":[{"type":"reasoning.text","text":" are","format":"unknown","index":0}]},"finish_reason":null,"native_finish_reason":null}]} - -data: {"id":"gen-1791215164-iEel1q5g7Q2702ZUqR69","object":"chat.completion.chunk","created":1791215164,"model":"qwen/qwen3-30b-a3b","provider":"DeepInfra","choices":[{"index":0,"delta":{"content":"","role":"assistant","reasoning":" included","reasoning_details":[{"type":"reasoning.text","text":" included","format":"unknown","index":0}]},"finish_reason":null,"native_finish_reason":null}]} - -data: {"id":"gen-1791215164-iEel1q5g7Q2702ZUqR69","object":"chat.completion.chunk","created":1791215164,"model":"qwen/qwen3-30b-a3b","provider":"DeepInfra","choices":[{"index":0,"delta":{"content":"","role":"assistant","reasoning":".","reasoning_details":[{"type":"reasoning.text","text":".","format":"unknown","index":0}]},"finish_reason":null,"native_finish_reason":null}]} - -data: {"id":"gen-1791215164-iEel1q5g7Q2702ZUqR69","object":"chat.completion.chunk","created":1791215164,"model":"qwen/qwen3-30b-a3b","provider":"DeepInfra","choices":[{"index":0,"delta":{"content":"","role":"assistant","reasoning":" The","reasoning_details":[{"type":"reasoning.text","text":" The","format":"unknown","index":0}]},"finish_reason":null,"native_finish_reason":null}]} - -data: {"id":"gen-1791215164-iEel1q5g7Q2702ZUqR69","object":"chat.completion.chunk","created":1791215164,"model":"qwen/qwen3-30b-a3b","provider":"DeepInfra","choices":[{"index":0,"delta":{"content":"","role":"assistant","reasoning":" researcher","reasoning_details":[{"type":"reasoning.text","text":" researcher","format":"unknown","index":0}]},"finish_reason":null,"native_finish_reason":null}]} - -data: {"id":"gen-1791215164-iEel1q5g7Q2702ZUqR69","object":"chat.completion.chunk","created":1791215164,"model":"qwen/qwen3-30b-a3b","provider":"DeepInfra","choices":[{"index":0,"delta":{"content":"","role":"assistant","reasoning":" result","reasoning_details":[{"type":"reasoning.text","text":" result","format":"unknown","index":0}]},"finish_reason":null,"native_finish_reason":null}]} - -data: {"id":"gen-1791215164-iEel1q5g7Q2702ZUqR69","object":"chat.completion.chunk","created":1791215164,"model":"qwen/qwen3-30b-a3b","provider":"DeepInfra","choices":[{"index":0,"delta":{"content":"","role":"assistant","reasoning":" is","reasoning_details":[{"type":"reasoning.text","text":" is","format":"unknown","index":0}]},"finish_reason":null,"native_finish_reason":null}]} - -data: {"id":"gen-1791215164-iEel1q5g7Q2702ZUqR69","object":"chat.completion.chunk","created":1791215164,"model":"qwen/qwen3-30b-a3b","provider":"DeepInfra","choices":[{"index":0,"delta":{"content":"","role":"assistant","reasoning":" \"","reasoning_details":[{"type":"reasoning.text","text":" \"","format":"unknown","index":0}]},"finish_reason":null,"native_finish_reason":null}]} - -data: {"id":"gen-1791215164-iEel1q5g7Q2702ZUqR69","object":"chat.completion.chunk","created":1791215164,"model":"qwen/qwen3-30b-a3b","provider":"DeepInfra","choices":[{"index":0,"delta":{"content":"","role":"assistant","reasoning":"E","reasoning_details":[{"type":"reasoning.text","text":"E","format":"unknown","index":0}]},"finish_reason":null,"native_finish_reason":null}]} - -data: {"id":"gen-1791215164-iEel1q5g7Q2702ZUqR69","object":"chat.completion.chunk","created":1791215164,"model":"qwen/qwen3-30b-a3b","provider":"DeepInfra","choices":[{"index":0,"delta":{"content":"","role":"assistant","reasoning":"ve","reasoning_details":[{"type":"reasoning.text","text":"ve","format":"unknown","index":0}]},"finish_reason":null,"native_finish_reason":null}]} - -data: {"id":"gen-1791215164-iEel1q5g7Q2702ZUqR69","object":"chat.completion.chunk","created":1791215164,"model":"qwen/qwen3-30b-a3b","provider":"DeepInfra","choices":[{"index":0,"delta":{"content":"","role":"assistant","reasoning":" instrumentation","reasoning_details":[{"type":"reasoning.text","text":" instrumentation","format":"unknown","index":0}]},"finish_reason":null,"native_finish_reason":null}]} - -data: {"id":"gen-1791215164-iEel1q5g7Q2702ZUqR69","object":"chat.completion.chunk","created":1791215164,"model":"qwen/qwen3-30b-a3b","provider":"DeepInfra","choices":[{"index":0,"delta":{"content":"","role":"assistant","reasoning":" https","reasoning_details":[{"type":"reasoning.text","text":" https","format":"unknown","index":0}]},"finish_reason":null,"native_finish_reason":null}]} - -data: {"id":"gen-1791215164-iEel1q5g7Q2702ZUqR69","object":"chat.completion.chunk","created":1791215164,"model":"qwen/qwen3-30b-a3b","provider":"DeepInfra","choices":[{"index":0,"delta":{"content":"","role":"assistant","reasoning":"://","reasoning_details":[{"type":"reasoning.text","text":"://","format":"unknown","index":0}]},"finish_reason":null,"native_finish_reason":null}]} - -data: {"id":"gen-1791215164-iEel1q5g7Q2702ZUqR69","object":"chat.completion.chunk","created":1791215164,"model":"qwen/qwen3-30b-a3b","provider":"DeepInfra","choices":[{"index":0,"delta":{"content":"","role":"assistant","reasoning":"eve","reasoning_details":[{"type":"reasoning.text","text":"eve","format":"unknown","index":0}]},"finish_reason":null,"native_finish_reason":null}]} - -data: {"id":"gen-1791215164-iEel1q5g7Q2702ZUqR69","object":"chat.completion.chunk","created":1791215164,"model":"qwen/qwen3-30b-a3b","provider":"DeepInfra","choices":[{"index":0,"delta":{"content":"","role":"assistant","reasoning":".dev","reasoning_details":[{"type":"reasoning.text","text":".dev","format":"unknown","index":0}]},"finish_reason":null,"native_finish_reason":null}]} - -data: {"id":"gen-1791215164-iEel1q5g7Q2702ZUqR69","object":"chat.completion.chunk","created":1791215164,"model":"qwen/qwen3-30b-a3b","provider":"DeepInfra","choices":[{"index":0,"delta":{"content":"","role":"assistant","reasoning":"/docs","reasoning_details":[{"type":"reasoning.text","text":"/docs","format":"unknown","index":0}]},"finish_reason":null,"native_finish_reason":null}]} - -data: {"id":"gen-1791215164-iEel1q5g7Q2702ZUqR69","object":"chat.completion.chunk","created":1791215164,"model":"qwen/qwen3-30b-a3b","provider":"DeepInfra","choices":[{"index":0,"delta":{"content":"","role":"assistant","reasoning":"/g","reasoning_details":[{"type":"reasoning.text","text":"/g","format":"unknown","index":0}]},"finish_reason":null,"native_finish_reason":null}]} - -data: {"id":"gen-1791215164-iEel1q5g7Q2702ZUqR69","object":"chat.completion.chunk","created":1791215164,"model":"qwen/qwen3-30b-a3b","provider":"DeepInfra","choices":[{"index":0,"delta":{"content":"","role":"assistant","reasoning":"uid","reasoning_details":[{"type":"reasoning.text","text":"uid","format":"unknown","index":0}]},"finish_reason":null,"native_finish_reason":null}]} - -data: {"id":"gen-1791215164-iEel1q5g7Q2702ZUqR69","object":"chat.completion.chunk","created":1791215164,"model":"qwen/qwen3-30b-a3b","provider":"DeepInfra","choices":[{"index":0,"delta":{"content":"","role":"assistant","reasoning":"es","reasoning_details":[{"type":"reasoning.text","text":"es","format":"unknown","index":0}]},"finish_reason":null,"native_finish_reason":null}]} - -data: {"id":"gen-1791215164-iEel1q5g7Q2702ZUqR69","object":"chat.completion.chunk","created":1791215164,"model":"qwen/qwen3-30b-a3b","provider":"DeepInfra","choices":[{"index":0,"delta":{"content":"","role":"assistant","reasoning":"/in","reasoning_details":[{"type":"reasoning.text","text":"/in","format":"unknown","index":0}]},"finish_reason":null,"native_finish_reason":null}]} - -data: {"id":"gen-1791215164-iEel1q5g7Q2702ZUqR69","object":"chat.completion.chunk","created":1791215164,"model":"qwen/qwen3-30b-a3b","provider":"DeepInfra","choices":[{"index":0,"delta":{"content":"","role":"assistant","reasoning":"strument","reasoning_details":[{"type":"reasoning.text","text":"strument","format":"unknown","index":0}]},"finish_reason":null,"native_finish_reason":null}]} - -data: {"id":"gen-1791215164-iEel1q5g7Q2702ZUqR69","object":"chat.completion.chunk","created":1791215164,"model":"qwen/qwen3-30b-a3b","provider":"DeepInfra","choices":[{"index":0,"delta":{"content":"","role":"assistant","reasoning":"ation","reasoning_details":[{"type":"reasoning.text","text":"ation","format":"unknown","index":0}]},"finish_reason":null,"native_finish_reason":null}]} - -data: {"id":"gen-1791215164-iEel1q5g7Q2702ZUqR69","object":"chat.completion.chunk","created":1791215164,"model":"qwen/qwen3-30b-a3b","provider":"DeepInfra","choices":[{"index":0,"delta":{"content":"","role":"assistant","reasoning":",\"","reasoning_details":[{"type":"reasoning.text","text":",\"","format":"unknown","index":0}]},"finish_reason":null,"native_finish_reason":null}]} - -data: {"id":"gen-1791215164-iEel1q5g7Q2702ZUqR69","object":"chat.completion.chunk","created":1791215164,"model":"qwen/qwen3-30b-a3b","provider":"DeepInfra","choices":[{"index":0,"delta":{"content":"","role":"assistant","reasoning":" the","reasoning_details":[{"type":"reasoning.text","text":" the","format":"unknown","index":0}]},"finish_reason":null,"native_finish_reason":null}]} - -data: {"id":"gen-1791215164-iEel1q5g7Q2702ZUqR69","object":"chat.completion.chunk","created":1791215164,"model":"qwen/qwen3-30b-a3b","provider":"DeepInfra","choices":[{"index":0,"delta":{"content":"","role":"assistant","reasoning":" title","reasoning_details":[{"type":"reasoning.text","text":" title","format":"unknown","index":0}]},"finish_reason":null,"native_finish_reason":null}]} - -data: {"id":"gen-1791215164-iEel1q5g7Q2702ZUqR69","object":"chat.completion.chunk","created":1791215164,"model":"qwen/qwen3-30b-a3b","provider":"DeepInfra","choices":[{"index":0,"delta":{"content":"","role":"assistant","reasoning":" is","reasoning_details":[{"type":"reasoning.text","text":" is","format":"unknown","index":0}]},"finish_reason":null,"native_finish_reason":null}]} - -data: {"id":"gen-1791215164-iEel1q5g7Q2702ZUqR69","object":"chat.completion.chunk","created":1791215164,"model":"qwen/qwen3-30b-a3b","provider":"DeepInfra","choices":[{"index":0,"delta":{"content":"","role":"assistant","reasoning":" \"","reasoning_details":[{"type":"reasoning.text","text":" \"","format":"unknown","index":0}]},"finish_reason":null,"native_finish_reason":null}]} - -data: {"id":"gen-1791215164-iEel1q5g7Q2702ZUqR69","object":"chat.completion.chunk","created":1791215164,"model":"qwen/qwen3-30b-a3b","provider":"DeepInfra","choices":[{"index":0,"delta":{"content":"","role":"assistant","reasoning":"E","reasoning_details":[{"type":"reasoning.text","text":"E","format":"unknown","index":0}]},"finish_reason":null,"native_finish_reason":null}]} - -data: {"id":"gen-1791215164-iEel1q5g7Q2702ZUqR69","object":"chat.completion.chunk","created":1791215164,"model":"qwen/qwen3-30b-a3b","provider":"DeepInfra","choices":[{"index":0,"delta":{"content":"","role":"assistant","reasoning":"ve","reasoning_details":[{"type":"reasoning.text","text":"ve","format":"unknown","index":0}]},"finish_reason":null,"native_finish_reason":null}]} - -data: {"id":"gen-1791215164-iEel1q5g7Q2702ZUqR69","object":"chat.completion.chunk","created":1791215164,"model":"qwen/qwen3-30b-a3b","provider":"DeepInfra","choices":[{"index":0,"delta":{"content":"","role":"assistant","reasoning":" instrumentation","reasoning_details":[{"type":"reasoning.text","text":" instrumentation","format":"unknown","index":0}]},"finish_reason":null,"native_finish_reason":null}]} - -data: {"id":"gen-1791215164-iEel1q5g7Q2702ZUqR69","object":"chat.completion.chunk","created":1791215164,"model":"qwen/qwen3-30b-a3b","provider":"DeepInfra","choices":[{"index":0,"delta":{"content":"","role":"assistant","reasoning":",\"","reasoning_details":[{"type":"reasoning.text","text":",\"","format":"unknown","index":0}]},"finish_reason":null,"native_finish_reason":null}]} - -data: {"id":"gen-1791215164-iEel1q5g7Q2702ZUqR69","object":"chat.completion.chunk","created":1791215164,"model":"qwen/qwen3-30b-a3b","provider":"DeepInfra","choices":[{"index":0,"delta":{"content":"","role":"assistant","reasoning":" the","reasoning_details":[{"type":"reasoning.text","text":" the","format":"unknown","index":0}]},"finish_reason":null,"native_finish_reason":null}]} - -data: {"id":"gen-1791215164-iEel1q5g7Q2702ZUqR69","object":"chat.completion.chunk","created":1791215164,"model":"qwen/qwen3-30b-a3b","provider":"DeepInfra","choices":[{"index":0,"delta":{"content":"","role":"assistant","reasoning":" URL","reasoning_details":[{"type":"reasoning.text","text":" URL","format":"unknown","index":0}]},"finish_reason":null,"native_finish_reason":null}]} - -data: {"id":"gen-1791215164-iEel1q5g7Q2702ZUqR69","object":"chat.completion.chunk","created":1791215164,"model":"qwen/qwen3-30b-a3b","provider":"DeepInfra","choices":[{"index":0,"delta":{"content":"","role":"assistant","reasoning":" is","reasoning_details":[{"type":"reasoning.text","text":" is","format":"unknown","index":0}]},"finish_reason":null,"native_finish_reason":null}]} - -data: {"id":"gen-1791215164-iEel1q5g7Q2702ZUqR69","object":"chat.completion.chunk","created":1791215164,"model":"qwen/qwen3-30b-a3b","provider":"DeepInfra","choices":[{"index":0,"delta":{"content":"","role":"assistant","reasoning":" the","reasoning_details":[{"type":"reasoning.text","text":" the","format":"unknown","index":0}]},"finish_reason":null,"native_finish_reason":null}]} - -data: {"id":"gen-1791215164-iEel1q5g7Q2702ZUqR69","object":"chat.completion.chunk","created":1791215164,"model":"qwen/qwen3-30b-a3b","provider":"DeepInfra","choices":[{"index":0,"delta":{"content":"","role":"assistant","reasoning":" same","reasoning_details":[{"type":"reasoning.text","text":" same","format":"unknown","index":0}]},"finish_reason":null,"native_finish_reason":null}]} - -data: {"id":"gen-1791215164-iEel1q5g7Q2702ZUqR69","object":"chat.completion.chunk","created":1791215164,"model":"qwen/qwen3-30b-a3b","provider":"DeepInfra","choices":[{"index":0,"delta":{"content":"","role":"assistant","reasoning":",","reasoning_details":[{"type":"reasoning.text","text":",","format":"unknown","index":0}]},"finish_reason":null,"native_finish_reason":null}]} - -data: {"id":"gen-1791215164-iEel1q5g7Q2702ZUqR69","object":"chat.completion.chunk","created":1791215164,"model":"qwen/qwen3-30b-a3b","provider":"DeepInfra","choices":[{"index":0,"delta":{"content":"","role":"assistant","reasoning":" and","reasoning_details":[{"type":"reasoning.text","text":" and","format":"unknown","index":0}]},"finish_reason":null,"native_finish_reason":null}]} - -data: {"id":"gen-1791215164-iEel1q5g7Q2702ZUqR69","object":"chat.completion.chunk","created":1791215164,"model":"qwen/qwen3-30b-a3b","provider":"DeepInfra","choices":[{"index":0,"delta":{"content":"","role":"assistant","reasoning":" the","reasoning_details":[{"type":"reasoning.text","text":" the","format":"unknown","index":0}]},"finish_reason":null,"native_finish_reason":null}]} - -data: {"id":"gen-1791215164-iEel1q5g7Q2702ZUqR69","object":"chat.completion.chunk","created":1791215164,"model":"qwen/qwen3-30b-a3b","provider":"DeepInfra","choices":[{"index":0,"delta":{"content":"","role":"assistant","reasoning":" excerpt","reasoning_details":[{"type":"reasoning.text","text":" excerpt","format":"unknown","index":0}]},"finish_reason":null,"native_finish_reason":null}]} - -data: {"id":"gen-1791215164-iEel1q5g7Q2702ZUqR69","object":"chat.completion.chunk","created":1791215164,"model":"qwen/qwen3-30b-a3b","provider":"DeepInfra","choices":[{"index":0,"delta":{"content":"","role":"assistant","reasoning":" is","reasoning_details":[{"type":"reasoning.text","text":" is","format":"unknown","index":0}]},"finish_reason":null,"native_finish_reason":null}]} - -data: {"id":"gen-1791215164-iEel1q5g7Q2702ZUqR69","object":"chat.completion.chunk","created":1791215164,"model":"qwen/qwen3-30b-a3b","provider":"DeepInfra","choices":[{"index":0,"delta":{"content":"","role":"assistant","reasoning":" the","reasoning_details":[{"type":"reasoning.text","text":" the","format":"unknown","index":0}]},"finish_reason":null,"native_finish_reason":null}]} - -data: {"id":"gen-1791215164-iEel1q5g7Q2702ZUqR69","object":"chat.completion.chunk","created":1791215164,"model":"qwen/qwen3-30b-a3b","provider":"DeepInfra","choices":[{"index":0,"delta":{"content":"","role":"assistant","reasoning":" provided","reasoning_details":[{"type":"reasoning.text","text":" provided","format":"unknown","index":0}]},"finish_reason":null,"native_finish_reason":null}]} - -data: {"id":"gen-1791215164-iEel1q5g7Q2702ZUqR69","object":"chat.completion.chunk","created":1791215164,"model":"qwen/qwen3-30b-a3b","provider":"DeepInfra","choices":[{"index":0,"delta":{"content":"","role":"assistant","reasoning":" text","reasoning_details":[{"type":"reasoning.text","text":" text","format":"unknown","index":0}]},"finish_reason":null,"native_finish_reason":null}]} - -data: {"id":"gen-1791215164-iEel1q5g7Q2702ZUqR69","object":"chat.completion.chunk","created":1791215164,"model":"qwen/qwen3-30b-a3b","provider":"DeepInfra","choices":[{"index":0,"delta":{"content":"","role":"assistant","reasoning":".","reasoning_details":[{"type":"reasoning.text","text":".","format":"unknown","index":0}]},"finish_reason":null,"native_finish_reason":null}]} - -data: {"id":"gen-1791215164-iEel1q5g7Q2702ZUqR69","object":"chat.completion.chunk","created":1791215164,"model":"qwen/qwen3-30b-a3b","provider":"DeepInfra","choices":[{"index":0,"delta":{"content":"","role":"assistant","reasoning":" \n\n","reasoning_details":[{"type":"reasoning.text","text":" \n\n","format":"unknown","index":0}]},"finish_reason":null,"native_finish_reason":null}]} - -data: {"id":"gen-1791215164-iEel1q5g7Q2702ZUqR69","object":"chat.completion.chunk","created":1791215164,"model":"qwen/qwen3-30b-a3b","provider":"DeepInfra","choices":[{"index":0,"delta":{"content":"","role":"assistant","reasoning":"I","reasoning_details":[{"type":"reasoning.text","text":"I","format":"unknown","index":0}]},"finish_reason":null,"native_finish_reason":null}]} - -data: {"id":"gen-1791215164-iEel1q5g7Q2702ZUqR69","object":"chat.completion.chunk","created":1791215164,"model":"qwen/qwen3-30b-a3b","provider":"DeepInfra","choices":[{"index":0,"delta":{"content":"","role":"assistant","reasoning":" should","reasoning_details":[{"type":"reasoning.text","text":" should","format":"unknown","index":0}]},"finish_reason":null,"native_finish_reason":null}]} - -data: {"id":"gen-1791215164-iEel1q5g7Q2702ZUqR69","object":"chat.completion.chunk","created":1791215164,"model":"qwen/qwen3-30b-a3b","provider":"DeepInfra","choices":[{"index":0,"delta":{"content":"","role":"assistant","reasoning":" structure","reasoning_details":[{"type":"reasoning.text","text":" structure","format":"unknown","index":0}]},"finish_reason":null,"native_finish_reason":null}]} - -data: {"id":"gen-1791215164-iEel1q5g7Q2702ZUqR69","object":"chat.completion.chunk","created":1791215164,"model":"qwen/qwen3-30b-a3b","provider":"DeepInfra","choices":[{"index":0,"delta":{"content":"","role":"assistant","reasoning":" the","reasoning_details":[{"type":"reasoning.text","text":" the","format":"unknown","index":0}]},"finish_reason":null,"native_finish_reason":null}]} - -data: {"id":"gen-1791215164-iEel1q5g7Q2702ZUqR69","object":"chat.completion.chunk","created":1791215164,"model":"qwen/qwen3-30b-a3b","provider":"DeepInfra","choices":[{"index":0,"delta":{"content":"","role":"assistant","reasoning":" sentence","reasoning_details":[{"type":"reasoning.text","text":" sentence","format":"unknown","index":0}]},"finish_reason":null,"native_finish_reason":null}]} - -data: {"id":"gen-1791215164-iEel1q5g7Q2702ZUqR69","object":"chat.completion.chunk","created":1791215164,"model":"qwen/qwen3-30b-a3b","provider":"DeepInfra","choices":[{"index":0,"delta":{"content":"","role":"assistant","reasoning":" correctly","reasoning_details":[{"type":"reasoning.text","text":" correctly","format":"unknown","index":0}]},"finish_reason":null,"native_finish_reason":null}]} - -data: {"id":"gen-1791215164-iEel1q5g7Q2702ZUqR69","object":"chat.completion.chunk","created":1791215164,"model":"qwen/qwen3-30b-a3b","provider":"DeepInfra","choices":[{"index":0,"delta":{"content":"","role":"assistant","reasoning":",","reasoning_details":[{"type":"reasoning.text","text":",","format":"unknown","index":0}]},"finish_reason":null,"native_finish_reason":null}]} - -data: {"id":"gen-1791215164-iEel1q5g7Q2702ZUqR69","object":"chat.completion.chunk","created":1791215164,"model":"qwen/qwen3-30b-a3b","provider":"DeepInfra","choices":[{"index":0,"delta":{"content":"","role":"assistant","reasoning":" making","reasoning_details":[{"type":"reasoning.text","text":" making","format":"unknown","index":0}]},"finish_reason":null,"native_finish_reason":null}]} - -data: {"id":"gen-1791215164-iEel1q5g7Q2702ZUqR69","object":"chat.completion.chunk","created":1791215164,"model":"qwen/qwen3-30b-a3b","provider":"DeepInfra","choices":[{"index":0,"delta":{"content":"","role":"assistant","reasoning":" sure","reasoning_details":[{"type":"reasoning.text","text":" sure","format":"unknown","index":0}]},"finish_reason":null,"native_finish_reason":null}]} - -data: {"id":"gen-1791215164-iEel1q5g7Q2702ZUqR69","object":"chat.completion.chunk","created":1791215164,"model":"qwen/qwen3-30b-a3b","provider":"DeepInfra","choices":[{"index":0,"delta":{"content":"","role":"assistant","reasoning":" there","reasoning_details":[{"type":"reasoning.text","text":" there","format":"unknown","index":0}]},"finish_reason":null,"native_finish_reason":null}]} - -data: {"id":"gen-1791215164-iEel1q5g7Q2702ZUqR69","object":"chat.completion.chunk","created":1791215164,"model":"qwen/qwen3-30b-a3b","provider":"DeepInfra","choices":[{"index":0,"delta":{"content":"","role":"assistant","reasoning":"'s","reasoning_details":[{"type":"reasoning.text","text":"'s","format":"unknown","index":0}]},"finish_reason":null,"native_finish_reason":null}]} - -data: {"id":"gen-1791215164-iEel1q5g7Q2702ZUqR69","object":"chat.completion.chunk","created":1791215164,"model":"qwen/qwen3-30b-a3b","provider":"DeepInfra","choices":[{"index":0,"delta":{"content":"","role":"assistant","reasoning":" no","reasoning_details":[{"type":"reasoning.text","text":" no","format":"unknown","index":0}]},"finish_reason":null,"native_finish_reason":null}]} - -data: {"id":"gen-1791215164-iEel1q5g7Q2702ZUqR69","object":"chat.completion.chunk","created":1791215164,"model":"qwen/qwen3-30b-a3b","provider":"DeepInfra","choices":[{"index":0,"delta":{"content":"","role":"assistant","reasoning":" markdown","reasoning_details":[{"type":"reasoning.text","text":" markdown","format":"unknown","index":0}]},"finish_reason":null,"native_finish_reason":null}]} - -data: {"id":"gen-1791215164-iEel1q5g7Q2702ZUqR69","object":"chat.completion.chunk","created":1791215164,"model":"qwen/qwen3-30b-a3b","provider":"DeepInfra","choices":[{"index":0,"delta":{"content":"","role":"assistant","reasoning":" and","reasoning_details":[{"type":"reasoning.text","text":" and","format":"unknown","index":0}]},"finish_reason":null,"native_finish_reason":null}]} - -data: {"id":"gen-1791215164-iEel1q5g7Q2702ZUqR69","object":"chat.completion.chunk","created":1791215164,"model":"qwen/qwen3-30b-a3b","provider":"DeepInfra","choices":[{"index":0,"delta":{"content":"","role":"assistant","reasoning":" that","reasoning_details":[{"type":"reasoning.text","text":" that","format":"unknown","index":0}]},"finish_reason":null,"native_finish_reason":null}]} - -data: {"id":"gen-1791215164-iEel1q5g7Q2702ZUqR69","object":"chat.completion.chunk","created":1791215164,"model":"qwen/qwen3-30b-a3b","provider":"DeepInfra","choices":[{"index":0,"delta":{"content":"","role":"assistant","reasoning":" it","reasoning_details":[{"type":"reasoning.text","text":" it","format":"unknown","index":0}]},"finish_reason":null,"native_finish_reason":null}]} - -data: {"id":"gen-1791215164-iEel1q5g7Q2702ZUqR69","object":"chat.completion.chunk","created":1791215164,"model":"qwen/qwen3-30b-a3b","provider":"DeepInfra","choices":[{"index":0,"delta":{"content":"","role":"assistant","reasoning":"'s","reasoning_details":[{"type":"reasoning.text","text":"'s","format":"unknown","index":0}]},"finish_reason":null,"native_finish_reason":null}]} - -data: {"id":"gen-1791215164-iEel1q5g7Q2702ZUqR69","object":"chat.completion.chunk","created":1791215164,"model":"qwen/qwen3-30b-a3b","provider":"DeepInfra","choices":[{"index":0,"delta":{"content":"","role":"assistant","reasoning":" a","reasoning_details":[{"type":"reasoning.text","text":" a","format":"unknown","index":0}]},"finish_reason":null,"native_finish_reason":null}]} - -data: {"id":"gen-1791215164-iEel1q5g7Q2702ZUqR69","object":"chat.completion.chunk","created":1791215164,"model":"qwen/qwen3-30b-a3b","provider":"DeepInfra","choices":[{"index":0,"delta":{"content":"","role":"assistant","reasoning":" single","reasoning_details":[{"type":"reasoning.text","text":" single","format":"unknown","index":0}]},"finish_reason":null,"native_finish_reason":null}]} - -data: {"id":"gen-1791215164-iEel1q5g7Q2702ZUqR69","object":"chat.completion.chunk","created":1791215164,"model":"qwen/qwen3-30b-a3b","provider":"DeepInfra","choices":[{"index":0,"delta":{"content":"","role":"assistant","reasoning":",","reasoning_details":[{"type":"reasoning.text","text":",","format":"unknown","index":0}]},"finish_reason":null,"native_finish_reason":null}]} - -data: {"id":"gen-1791215164-iEel1q5g7Q2702ZUqR69","object":"chat.completion.chunk","created":1791215164,"model":"qwen/qwen3-30b-a3b","provider":"DeepInfra","choices":[{"index":0,"delta":{"content":"","role":"assistant","reasoning":" coherent","reasoning_details":[{"type":"reasoning.text","text":" coherent","format":"unknown","index":0}]},"finish_reason":null,"native_finish_reason":null}]} - -data: {"id":"gen-1791215164-iEel1q5g7Q2702ZUqR69","object":"chat.completion.chunk","created":1791215164,"model":"qwen/qwen3-30b-a3b","provider":"DeepInfra","choices":[{"index":0,"delta":{"content":"","role":"assistant","reasoning":" sentence","reasoning_details":[{"type":"reasoning.text","text":" sentence","format":"unknown","index":0}]},"finish_reason":null,"native_finish_reason":null}]} - -data: {"id":"gen-1791215164-iEel1q5g7Q2702ZUqR69","object":"chat.completion.chunk","created":1791215164,"model":"qwen/qwen3-30b-a3b","provider":"DeepInfra","choices":[{"index":0,"delta":{"content":"","role":"assistant","reasoning":".","reasoning_details":[{"type":"reasoning.text","text":".","format":"unknown","index":0}]},"finish_reason":null,"native_finish_reason":null}]} - -data: {"id":"gen-1791215164-iEel1q5g7Q2702ZUqR69","object":"chat.completion.chunk","created":1791215164,"model":"qwen/qwen3-30b-a3b","provider":"DeepInfra","choices":[{"index":0,"delta":{"content":"","role":"assistant","reasoning":" Let","reasoning_details":[{"type":"reasoning.text","text":" Let","format":"unknown","index":0}]},"finish_reason":null,"native_finish_reason":null}]} - -data: {"id":"gen-1791215164-iEel1q5g7Q2702ZUqR69","object":"chat.completion.chunk","created":1791215164,"model":"qwen/qwen3-30b-a3b","provider":"DeepInfra","choices":[{"index":0,"delta":{"content":"","role":"assistant","reasoning":" me","reasoning_details":[{"type":"reasoning.text","text":" me","format":"unknown","index":0}]},"finish_reason":null,"native_finish_reason":null}]} - -data: {"id":"gen-1791215164-iEel1q5g7Q2702ZUqR69","object":"chat.completion.chunk","created":1791215164,"model":"qwen/qwen3-30b-a3b","provider":"DeepInfra","choices":[{"index":0,"delta":{"content":"","role":"assistant","reasoning":" check","reasoning_details":[{"type":"reasoning.text","text":" check","format":"unknown","index":0}]},"finish_reason":null,"native_finish_reason":null}]} - -data: {"id":"gen-1791215164-iEel1q5g7Q2702ZUqR69","object":"chat.completion.chunk","created":1791215164,"model":"qwen/qwen3-30b-a3b","provider":"DeepInfra","choices":[{"index":0,"delta":{"content":"","role":"assistant","reasoning":" for","reasoning_details":[{"type":"reasoning.text","text":" for","format":"unknown","index":0}]},"finish_reason":null,"native_finish_reason":null}]} - -data: {"id":"gen-1791215164-iEel1q5g7Q2702ZUqR69","object":"chat.completion.chunk","created":1791215164,"model":"qwen/qwen3-30b-a3b","provider":"DeepInfra","choices":[{"index":0,"delta":{"content":"","role":"assistant","reasoning":" any","reasoning_details":[{"type":"reasoning.text","text":" any","format":"unknown","index":0}]},"finish_reason":null,"native_finish_reason":null}]} - -data: {"id":"gen-1791215164-iEel1q5g7Q2702ZUqR69","object":"chat.completion.chunk","created":1791215164,"model":"qwen/qwen3-30b-a3b","provider":"DeepInfra","choices":[{"index":0,"delta":{"content":"","role":"assistant","reasoning":" ty","reasoning_details":[{"type":"reasoning.text","text":" ty","format":"unknown","index":0}]},"finish_reason":null,"native_finish_reason":null}]} - -data: {"id":"gen-1791215164-iEel1q5g7Q2702ZUqR69","object":"chat.completion.chunk","created":1791215164,"model":"qwen/qwen3-30b-a3b","provider":"DeepInfra","choices":[{"index":0,"delta":{"content":"","role":"assistant","reasoning":"pos","reasoning_details":[{"type":"reasoning.text","text":"pos","format":"unknown","index":0}]},"finish_reason":null,"native_finish_reason":null}]} - -data: {"id":"gen-1791215164-iEel1q5g7Q2702ZUqR69","object":"chat.completion.chunk","created":1791215164,"model":"qwen/qwen3-30b-a3b","provider":"DeepInfra","choices":[{"index":0,"delta":{"content":"","role":"assistant","reasoning":" or","reasoning_details":[{"type":"reasoning.text","text":" or","format":"unknown","index":0}]},"finish_reason":null,"native_finish_reason":null}]} - -data: {"id":"gen-1791215164-iEel1q5g7Q2702ZUqR69","object":"chat.completion.chunk","created":1791215164,"model":"qwen/qwen3-30b-a3b","provider":"DeepInfra","choices":[{"index":0,"delta":{"content":"","role":"assistant","reasoning":" missing","reasoning_details":[{"type":"reasoning.text","text":" missing","format":"unknown","index":0}]},"finish_reason":null,"native_finish_reason":null}]} - -data: {"id":"gen-1791215164-iEel1q5g7Q2702ZUqR69","object":"chat.completion.chunk","created":1791215164,"model":"qwen/qwen3-30b-a3b","provider":"DeepInfra","choices":[{"index":0,"delta":{"content":"","role":"assistant","reasoning":" parts","reasoning_details":[{"type":"reasoning.text","text":" parts","format":"unknown","index":0}]},"finish_reason":null,"native_finish_reason":null}]} - -data: {"id":"gen-1791215164-iEel1q5g7Q2702ZUqR69","object":"chat.completion.chunk","created":1791215164,"model":"qwen/qwen3-30b-a3b","provider":"DeepInfra","choices":[{"index":0,"delta":{"content":"","role":"assistant","reasoning":".","reasoning_details":[{"type":"reasoning.text","text":".","format":"unknown","index":0}]},"finish_reason":null,"native_finish_reason":null}]} - -data: {"id":"gen-1791215164-iEel1q5g7Q2702ZUqR69","object":"chat.completion.chunk","created":1791215164,"model":"qwen/qwen3-30b-a3b","provider":"DeepInfra","choices":[{"index":0,"delta":{"content":"","role":"assistant","reasoning":" Everything","reasoning_details":[{"type":"reasoning.text","text":" Everything","format":"unknown","index":0}]},"finish_reason":null,"native_finish_reason":null}]} - -data: {"id":"gen-1791215164-iEel1q5g7Q2702ZUqR69","object":"chat.completion.chunk","created":1791215164,"model":"qwen/qwen3-30b-a3b","provider":"DeepInfra","choices":[{"index":0,"delta":{"content":"","role":"assistant","reasoning":" seems","reasoning_details":[{"type":"reasoning.text","text":" seems","format":"unknown","index":0}]},"finish_reason":null,"native_finish_reason":null}]} - -data: {"id":"gen-1791215164-iEel1q5g7Q2702ZUqR69","object":"chat.completion.chunk","created":1791215164,"model":"qwen/qwen3-30b-a3b","provider":"DeepInfra","choices":[{"index":0,"delta":{"content":"","role":"assistant","reasoning":" to","reasoning_details":[{"type":"reasoning.text","text":" to","format":"unknown","index":0}]},"finish_reason":null,"native_finish_reason":null}]} - -data: {"id":"gen-1791215164-iEel1q5g7Q2702ZUqR69","object":"chat.completion.chunk","created":1791215164,"model":"qwen/qwen3-30b-a3b","provider":"DeepInfra","choices":[{"index":0,"delta":{"content":"","role":"assistant","reasoning":" be","reasoning_details":[{"type":"reasoning.text","text":" be","format":"unknown","index":0}]},"finish_reason":null,"native_finish_reason":null}]} - -data: {"id":"gen-1791215164-iEel1q5g7Q2702ZUqR69","object":"chat.completion.chunk","created":1791215164,"model":"qwen/qwen3-30b-a3b","provider":"DeepInfra","choices":[{"index":0,"delta":{"content":"","role":"assistant","reasoning":" in","reasoning_details":[{"type":"reasoning.text","text":" in","format":"unknown","index":0}]},"finish_reason":null,"native_finish_reason":null}]} - -data: {"id":"gen-1791215164-iEel1q5g7Q2702ZUqR69","object":"chat.completion.chunk","created":1791215164,"model":"qwen/qwen3-30b-a3b","provider":"DeepInfra","choices":[{"index":0,"delta":{"content":"","role":"assistant","reasoning":" order","reasoning_details":[{"type":"reasoning.text","text":" order","format":"unknown","index":0}]},"finish_reason":null,"native_finish_reason":null}]} - -data: {"id":"gen-1791215164-iEel1q5g7Q2702ZUqR69","object":"chat.completion.chunk","created":1791215164,"model":"qwen/qwen3-30b-a3b","provider":"DeepInfra","choices":[{"index":0,"delta":{"content":"","role":"assistant","reasoning":".","reasoning_details":[{"type":"reasoning.text","text":".","format":"unknown","index":0}]},"finish_reason":null,"native_finish_reason":null}]} - -data: {"id":"gen-1791215164-iEel1q5g7Q2702ZUqR69","object":"chat.completion.chunk","created":1791215164,"model":"qwen/qwen3-30b-a3b","provider":"DeepInfra","choices":[{"index":0,"delta":{"content":"","role":"assistant","reasoning":" Time","reasoning_details":[{"type":"reasoning.text","text":" Time","format":"unknown","index":0}]},"finish_reason":null,"native_finish_reason":null}]} - -data: {"id":"gen-1791215164-iEel1q5g7Q2702ZUqR69","object":"chat.completion.chunk","created":1791215164,"model":"qwen/qwen3-30b-a3b","provider":"DeepInfra","choices":[{"index":0,"delta":{"content":"","role":"assistant","reasoning":" to","reasoning_details":[{"type":"reasoning.text","text":" to","format":"unknown","index":0}]},"finish_reason":null,"native_finish_reason":null}]} - -data: {"id":"gen-1791215164-iEel1q5g7Q2702ZUqR69","object":"chat.completion.chunk","created":1791215164,"model":"qwen/qwen3-30b-a3b","provider":"DeepInfra","choices":[{"index":0,"delta":{"content":"","role":"assistant","reasoning":" put","reasoning_details":[{"type":"reasoning.text","text":" put","format":"unknown","index":0}]},"finish_reason":null,"native_finish_reason":null}]} - -data: {"id":"gen-1791215164-iEel1q5g7Q2702ZUqR69","object":"chat.completion.chunk","created":1791215164,"model":"qwen/qwen3-30b-a3b","provider":"DeepInfra","choices":[{"index":0,"delta":{"content":"","role":"assistant","reasoning":" it","reasoning_details":[{"type":"reasoning.text","text":" it","format":"unknown","index":0}]},"finish_reason":null,"native_finish_reason":null}]} - -data: {"id":"gen-1791215164-iEel1q5g7Q2702ZUqR69","object":"chat.completion.chunk","created":1791215164,"model":"qwen/qwen3-30b-a3b","provider":"DeepInfra","choices":[{"index":0,"delta":{"content":"","role":"assistant","reasoning":" all","reasoning_details":[{"type":"reasoning.text","text":" all","format":"unknown","index":0}]},"finish_reason":null,"native_finish_reason":null}]} - -data: {"id":"gen-1791215164-iEel1q5g7Q2702ZUqR69","object":"chat.completion.chunk","created":1791215164,"model":"qwen/qwen3-30b-a3b","provider":"DeepInfra","choices":[{"index":0,"delta":{"content":"","role":"assistant","reasoning":" together","reasoning_details":[{"type":"reasoning.text","text":" together","format":"unknown","index":0}]},"finish_reason":null,"native_finish_reason":null}]} - -data: {"id":"gen-1791215164-iEel1q5g7Q2702ZUqR69","object":"chat.completion.chunk","created":1791215164,"model":"qwen/qwen3-30b-a3b","provider":"DeepInfra","choices":[{"index":0,"delta":{"content":"","role":"assistant","reasoning":".\n","reasoning_details":[{"type":"reasoning.text","text":".\n","format":"unknown","index":0}]},"finish_reason":null,"native_finish_reason":null}]} - -data: {"id":"gen-1791215164-iEel1q5g7Q2702ZUqR69","object":"chat.completion.chunk","created":1791215164,"model":"qwen/qwen3-30b-a3b","provider":"DeepInfra","choices":[{"index":0,"delta":{"content":"Final","role":"assistant"},"finish_reason":null,"native_finish_reason":null}]} - -data: {"id":"gen-1791215164-iEel1q5g7Q2702ZUqR69","object":"chat.completion.chunk","created":1791215164,"model":"qwen/qwen3-30b-a3b","provider":"DeepInfra","choices":[{"index":0,"delta":{"content":" answer","role":"assistant"},"finish_reason":null,"native_finish_reason":null}]} - -data: {"id":"gen-1791215164-iEel1q5g7Q2702ZUqR69","object":"chat.completion.chunk","created":1791215164,"model":"qwen/qwen3-30b-a3b","provider":"DeepInfra","choices":[{"index":0,"delta":{"content":" from","role":"assistant"},"finish_reason":null,"native_finish_reason":null}]} - -data: {"id":"gen-1791215164-iEel1q5g7Q2702ZUqR69","object":"chat.completion.chunk","created":1791215164,"model":"qwen/qwen3-30b-a3b","provider":"DeepInfra","choices":[{"index":0,"delta":{"content":" read","role":"assistant"},"finish_reason":null,"native_finish_reason":null}]} - -data: {"id":"gen-1791215164-iEel1q5g7Q2702ZUqR69","object":"chat.completion.chunk","created":1791215164,"model":"qwen/qwen3-30b-a3b","provider":"DeepInfra","choices":[{"index":0,"delta":{"content":":","role":"assistant"},"finish_reason":null,"native_finish_reason":null}]} - -data: {"id":"gen-1791215164-iEel1q5g7Q2702ZUqR69","object":"chat.completion.chunk","created":1791215164,"model":"qwen/qwen3-30b-a3b","provider":"DeepInfra","choices":[{"index":0,"delta":{"content":" Eve","role":"assistant"},"finish_reason":null,"native_finish_reason":null}]} - -data: {"id":"gen-1791215164-iEel1q5g7Q2702ZUqR69","object":"chat.completion.chunk","created":1791215164,"model":"qwen/qwen3-30b-a3b","provider":"DeepInfra","choices":[{"index":0,"delta":{"content":" instrumentation","role":"assistant"},"finish_reason":null,"native_finish_reason":null}]} - -data: {"id":"gen-1791215164-iEel1q5g7Q2702ZUqR69","object":"chat.completion.chunk","created":1791215164,"model":"qwen/qwen3-30b-a3b","provider":"DeepInfra","choices":[{"index":0,"delta":{"content":" https","role":"assistant"},"finish_reason":null,"native_finish_reason":null}]} - -data: {"id":"gen-1791215164-iEel1q5g7Q2702ZUqR69","object":"chat.completion.chunk","created":1791215164,"model":"qwen/qwen3-30b-a3b","provider":"DeepInfra","choices":[{"index":0,"delta":{"content":"://","role":"assistant"},"finish_reason":null,"native_finish_reason":null}]} - -data: {"id":"gen-1791215164-iEel1q5g7Q2702ZUqR69","object":"chat.completion.chunk","created":1791215164,"model":"qwen/qwen3-30b-a3b","provider":"DeepInfra","choices":[{"index":0,"delta":{"content":"eve","role":"assistant"},"finish_reason":null,"native_finish_reason":null}]} - -data: {"id":"gen-1791215164-iEel1q5g7Q2702ZUqR69","object":"chat.completion.chunk","created":1791215164,"model":"qwen/qwen3-30b-a3b","provider":"DeepInfra","choices":[{"index":0,"delta":{"content":".dev","role":"assistant"},"finish_reason":null,"native_finish_reason":null}]} - -data: {"id":"gen-1791215164-iEel1q5g7Q2702ZUqR69","object":"chat.completion.chunk","created":1791215164,"model":"qwen/qwen3-30b-a3b","provider":"DeepInfra","choices":[{"index":0,"delta":{"content":"/docs","role":"assistant"},"finish_reason":null,"native_finish_reason":null}]} - -data: {"id":"gen-1791215164-iEel1q5g7Q2702ZUqR69","object":"chat.completion.chunk","created":1791215164,"model":"qwen/qwen3-30b-a3b","provider":"DeepInfra","choices":[{"index":0,"delta":{"content":"/g","role":"assistant"},"finish_reason":null,"native_finish_reason":null}]} - -data: {"id":"gen-1791215164-iEel1q5g7Q2702ZUqR69","object":"chat.completion.chunk","created":1791215164,"model":"qwen/qwen3-30b-a3b","provider":"DeepInfra","choices":[{"index":0,"delta":{"content":"uid","role":"assistant"},"finish_reason":null,"native_finish_reason":null}]} - -data: {"id":"gen-1791215164-iEel1q5g7Q2702ZUqR69","object":"chat.completion.chunk","created":1791215164,"model":"qwen/qwen3-30b-a3b","provider":"DeepInfra","choices":[{"index":0,"delta":{"content":"es","role":"assistant"},"finish_reason":null,"native_finish_reason":null}]} - -data: {"id":"gen-1791215164-iEel1q5g7Q2702ZUqR69","object":"chat.completion.chunk","created":1791215164,"model":"qwen/qwen3-30b-a3b","provider":"DeepInfra","choices":[{"index":0,"delta":{"content":"/in","role":"assistant"},"finish_reason":null,"native_finish_reason":null}]} - -data: {"id":"gen-1791215164-iEel1q5g7Q2702ZUqR69","object":"chat.completion.chunk","created":1791215164,"model":"qwen/qwen3-30b-a3b","provider":"DeepInfra","choices":[{"index":0,"delta":{"content":"strument","role":"assistant"},"finish_reason":null,"native_finish_reason":null}]} - -data: {"id":"gen-1791215164-iEel1q5g7Q2702ZUqR69","object":"chat.completion.chunk","created":1791215164,"model":"qwen/qwen3-30b-a3b","provider":"DeepInfra","choices":[{"index":0,"delta":{"content":"ation","role":"assistant"},"finish_reason":null,"native_finish_reason":null}]} - -data: {"id":"gen-1791215164-iEel1q5g7Q2702ZUqR69","object":"chat.completion.chunk","created":1791215164,"model":"qwen/qwen3-30b-a3b","provider":"DeepInfra","choices":[{"index":0,"delta":{"content":" -","role":"assistant"},"finish_reason":null,"native_finish_reason":null}]} - -data: {"id":"gen-1791215164-iEel1q5g7Q2702ZUqR69","object":"chat.completion.chunk","created":1791215164,"model":"qwen/qwen3-30b-a3b","provider":"DeepInfra","choices":[{"index":0,"delta":{"content":" \"","role":"assistant"},"finish_reason":null,"native_finish_reason":null}]} - -data: {"id":"gen-1791215164-iEel1q5g7Q2702ZUqR69","object":"chat.completion.chunk","created":1791215164,"model":"qwen/qwen3-30b-a3b","provider":"DeepInfra","choices":[{"index":0,"delta":{"content":"E","role":"assistant"},"finish_reason":null,"native_finish_reason":null}]} - -data: {"id":"gen-1791215164-iEel1q5g7Q2702ZUqR69","object":"chat.completion.chunk","created":1791215164,"model":"qwen/qwen3-30b-a3b","provider":"DeepInfra","choices":[{"index":0,"delta":{"content":"ve","role":"assistant"},"finish_reason":null,"native_finish_reason":null}]} - -data: {"id":"gen-1791215164-iEel1q5g7Q2702ZUqR69","object":"chat.completion.chunk","created":1791215164,"model":"qwen/qwen3-30b-a3b","provider":"DeepInfra","choices":[{"index":0,"delta":{"content":" hooks","role":"assistant"},"finish_reason":null,"native_finish_reason":null}]} - -data: {"id":"gen-1791215164-iEel1q5g7Q2702ZUqR69","object":"chat.completion.chunk","created":1791215164,"model":"qwen/qwen3-30b-a3b","provider":"DeepInfra","choices":[{"index":0,"delta":{"content":" expose","role":"assistant"},"finish_reason":null,"native_finish_reason":null}]} - -data: {"id":"gen-1791215164-iEel1q5g7Q2702ZUqR69","object":"chat.completion.chunk","created":1791215164,"model":"qwen/qwen3-30b-a3b","provider":"DeepInfra","choices":[{"index":0,"delta":{"content":" runtime","role":"assistant"},"finish_reason":null,"native_finish_reason":null}]} - -data: {"id":"gen-1791215164-iEel1q5g7Q2702ZUqR69","object":"chat.completion.chunk","created":1791215164,"model":"qwen/qwen3-30b-a3b","provider":"DeepInfra","choices":[{"index":0,"delta":{"content":" stream","role":"assistant"},"finish_reason":null,"native_finish_reason":null}]} - -data: {"id":"gen-1791215164-iEel1q5g7Q2702ZUqR69","object":"chat.completion.chunk","created":1791215164,"model":"qwen/qwen3-30b-a3b","provider":"DeepInfra","choices":[{"index":0,"delta":{"content":" events","role":"assistant"},"finish_reason":null,"native_finish_reason":null}]} - -data: {"id":"gen-1791215164-iEel1q5g7Q2702ZUqR69","object":"chat.completion.chunk","created":1791215164,"model":"qwen/qwen3-30b-a3b","provider":"DeepInfra","choices":[{"index":0,"delta":{"content":" that","role":"assistant"},"finish_reason":null,"native_finish_reason":null}]} - -data: {"id":"gen-1791215164-iEel1q5g7Q2702ZUqR69","object":"chat.completion.chunk","created":1791215164,"model":"qwen/qwen3-30b-a3b","provider":"DeepInfra","choices":[{"index":0,"delta":{"content":" Brain","role":"assistant"},"finish_reason":null,"native_finish_reason":null}]} - -data: {"id":"gen-1791215164-iEel1q5g7Q2702ZUqR69","object":"chat.completion.chunk","created":1791215164,"model":"qwen/qwen3-30b-a3b","provider":"DeepInfra","choices":[{"index":0,"delta":{"content":"trust","role":"assistant"},"finish_reason":null,"native_finish_reason":null}]} - -data: {"id":"gen-1791215164-iEel1q5g7Q2702ZUqR69","object":"chat.completion.chunk","created":1791215164,"model":"qwen/qwen3-30b-a3b","provider":"DeepInfra","choices":[{"index":0,"delta":{"content":" maps","role":"assistant"},"finish_reason":null,"native_finish_reason":null}]} - -data: {"id":"gen-1791215164-iEel1q5g7Q2702ZUqR69","object":"chat.completion.chunk","created":1791215164,"model":"qwen/qwen3-30b-a3b","provider":"DeepInfra","choices":[{"index":0,"delta":{"content":" into","role":"assistant"},"finish_reason":null,"native_finish_reason":null}]} - -data: {"id":"gen-1791215164-iEel1q5g7Q2702ZUqR69","object":"chat.completion.chunk","created":1791215164,"model":"qwen/qwen3-30b-a3b","provider":"DeepInfra","choices":[{"index":0,"delta":{"content":" a","role":"assistant"},"finish_reason":null,"native_finish_reason":null}]} - -data: {"id":"gen-1791215164-iEel1q5g7Q2702ZUqR69","object":"chat.completion.chunk","created":1791215164,"model":"qwen/qwen3-30b-a3b","provider":"DeepInfra","choices":[{"index":0,"delta":{"content":" nested","role":"assistant"},"finish_reason":null,"native_finish_reason":null}]} - -data: {"id":"gen-1791215164-iEel1q5g7Q2702ZUqR69","object":"chat.completion.chunk","created":1791215164,"model":"qwen/qwen3-30b-a3b","provider":"DeepInfra","choices":[{"index":0,"delta":{"content":" turn","role":"assistant"},"finish_reason":null,"native_finish_reason":null}]} - -data: {"id":"gen-1791215164-iEel1q5g7Q2702ZUqR69","object":"chat.completion.chunk","created":1791215164,"model":"qwen/qwen3-30b-a3b","provider":"DeepInfra","choices":[{"index":0,"delta":{"content":" trace","role":"assistant"},"finish_reason":null,"native_finish_reason":null}]} - -data: {"id":"gen-1791215164-iEel1q5g7Q2702ZUqR69","object":"chat.completion.chunk","created":1791215164,"model":"qwen/qwen3-30b-a3b","provider":"DeepInfra","choices":[{"index":0,"delta":{"content":".\"","role":"assistant"},"finish_reason":null,"native_finish_reason":null}]} - -data: {"id":"gen-1791215164-iEel1q5g7Q2702ZUqR69","object":"chat.completion.chunk","created":1791215164,"model":"qwen/qwen3-30b-a3b","provider":"DeepInfra","choices":[{"index":0,"delta":{"content":" (","role":"assistant"},"finish_reason":null,"native_finish_reason":null}]} - -data: {"id":"gen-1791215164-iEel1q5g7Q2702ZUqR69","object":"chat.completion.chunk","created":1791215164,"model":"qwen/qwen3-30b-a3b","provider":"DeepInfra","choices":[{"index":0,"delta":{"content":"E","role":"assistant"},"finish_reason":null,"native_finish_reason":null}]} - -data: {"id":"gen-1791215164-iEel1q5g7Q2702ZUqR69","object":"chat.completion.chunk","created":1791215164,"model":"qwen/qwen3-30b-a3b","provider":"DeepInfra","choices":[{"index":0,"delta":{"content":"ve","role":"assistant"},"finish_reason":null,"native_finish_reason":null}]} - -data: {"id":"gen-1791215164-iEel1q5g7Q2702ZUqR69","object":"chat.completion.chunk","created":1791215164,"model":"qwen/qwen3-30b-a3b","provider":"DeepInfra","choices":[{"index":0,"delta":{"content":" instrumentation","role":"assistant"},"finish_reason":null,"native_finish_reason":null}]} - -data: {"id":"gen-1791215164-iEel1q5g7Q2702ZUqR69","object":"chat.completion.chunk","created":1791215164,"model":"qwen/qwen3-30b-a3b","provider":"DeepInfra","choices":[{"index":0,"delta":{"content":",","role":"assistant"},"finish_reason":null,"native_finish_reason":null}]} - -data: {"id":"gen-1791215164-iEel1q5g7Q2702ZUqR69","object":"chat.completion.chunk","created":1791215164,"model":"qwen/qwen3-30b-a3b","provider":"DeepInfra","choices":[{"index":0,"delta":{"content":" https","role":"assistant"},"finish_reason":null,"native_finish_reason":null}]} - -data: {"id":"gen-1791215164-iEel1q5g7Q2702ZUqR69","object":"chat.completion.chunk","created":1791215164,"model":"qwen/qwen3-30b-a3b","provider":"DeepInfra","choices":[{"index":0,"delta":{"content":"://","role":"assistant"},"finish_reason":null,"native_finish_reason":null}]} - -data: {"id":"gen-1791215164-iEel1q5g7Q2702ZUqR69","object":"chat.completion.chunk","created":1791215164,"model":"qwen/qwen3-30b-a3b","provider":"DeepInfra","choices":[{"index":0,"delta":{"content":"eve","role":"assistant"},"finish_reason":null,"native_finish_reason":null}]} - -data: {"id":"gen-1791215164-iEel1q5g7Q2702ZUqR69","object":"chat.completion.chunk","created":1791215164,"model":"qwen/qwen3-30b-a3b","provider":"DeepInfra","choices":[{"index":0,"delta":{"content":".dev","role":"assistant"},"finish_reason":null,"native_finish_reason":null}]} - -data: {"id":"gen-1791215164-iEel1q5g7Q2702ZUqR69","object":"chat.completion.chunk","created":1791215164,"model":"qwen/qwen3-30b-a3b","provider":"DeepInfra","choices":[{"index":0,"delta":{"content":"/docs","role":"assistant"},"finish_reason":null,"native_finish_reason":null}]} - -data: {"id":"gen-1791215164-iEel1q5g7Q2702ZUqR69","object":"chat.completion.chunk","created":1791215164,"model":"qwen/qwen3-30b-a3b","provider":"DeepInfra","choices":[{"index":0,"delta":{"content":"/g","role":"assistant"},"finish_reason":null,"native_finish_reason":null}]} - -data: {"id":"gen-1791215164-iEel1q5g7Q2702ZUqR69","object":"chat.completion.chunk","created":1791215164,"model":"qwen/qwen3-30b-a3b","provider":"DeepInfra","choices":[{"index":0,"delta":{"content":"uid","role":"assistant"},"finish_reason":null,"native_finish_reason":null}]} - -data: {"id":"gen-1791215164-iEel1q5g7Q2702ZUqR69","object":"chat.completion.chunk","created":1791215164,"model":"qwen/qwen3-30b-a3b","provider":"DeepInfra","choices":[{"index":0,"delta":{"content":"es","role":"assistant"},"finish_reason":null,"native_finish_reason":null}]} - -data: {"id":"gen-1791215164-iEel1q5g7Q2702ZUqR69","object":"chat.completion.chunk","created":1791215164,"model":"qwen/qwen3-30b-a3b","provider":"DeepInfra","choices":[{"index":0,"delta":{"content":"/in","role":"assistant"},"finish_reason":null,"native_finish_reason":null}]} - -data: {"id":"gen-1791215164-iEel1q5g7Q2702ZUqR69","object":"chat.completion.chunk","created":1791215164,"model":"qwen/qwen3-30b-a3b","provider":"DeepInfra","choices":[{"index":0,"delta":{"content":"strument","role":"assistant"},"finish_reason":null,"native_finish_reason":null}]} - -data: {"id":"gen-1791215164-iEel1q5g7Q2702ZUqR69","object":"chat.completion.chunk","created":1791215164,"model":"qwen/qwen3-30b-a3b","provider":"DeepInfra","choices":[{"index":0,"delta":{"content":"ation","role":"assistant"},"finish_reason":null,"native_finish_reason":null}]} - -data: {"id":"gen-1791215164-iEel1q5g7Q2702ZUqR69","object":"chat.completion.chunk","created":1791215164,"model":"qwen/qwen3-30b-a3b","provider":"DeepInfra","choices":[{"index":0,"delta":{"content":")","role":"assistant"},"finish_reason":null,"native_finish_reason":null}]} - -data: {"id":"gen-1791215164-iEel1q5g7Q2702ZUqR69","object":"chat.completion.chunk","created":1791215164,"model":"qwen/qwen3-30b-a3b","provider":"DeepInfra","choices":[{"index":0,"delta":{"content":"","role":"assistant","reasoning":null},"finish_reason":"stop","native_finish_reason":"stop"}]} - -data: {"id":"gen-1791215164-iEel1q5g7Q2702ZUqR69","object":"chat.completion.chunk","created":1791215164,"model":"qwen/qwen3-30b-a3b","provider":"DeepInfra","service_tier":"default","choices":[{"index":0,"delta":{"content":"","role":"assistant"},"finish_reason":"stop","native_finish_reason":"stop"}],"usage":{"prompt_tokens":3826,"completion_tokens":331,"total_tokens":4157,"cost":0.00062462,"is_byok":false,"prompt_tokens_details":{"cached_tokens":0,"cache_write_tokens":0,"audio_tokens":0,"video_tokens":0},"cost_details":{"upstream_inference_cost":0.00062462,"upstream_inference_prompt_cost":0.00045912,"upstream_inference_completions_cost":0.0001655},"completion_tokens_details":{"reasoning_tokens":331,"image_tokens":0,"audio_tokens":0}}} - -data: [DONE] - diff --git a/e2e/scenarios/eve-instrumentation/__cassettes__/eve-v0.cassette.blobs/93d38f5860874f5d41365a1089fc6d39d15c1e647323424c090499d64c892b41.bin b/e2e/scenarios/eve-instrumentation/__cassettes__/eve-v0.cassette.blobs/93d38f5860874f5d41365a1089fc6d39d15c1e647323424c090499d64c892b41.bin deleted file mode 100644 index 7e183d30c..000000000 --- a/e2e/scenarios/eve-instrumentation/__cassettes__/eve-v0.cassette.blobs/93d38f5860874f5d41365a1089fc6d39d15c1e647323424c090499d64c892b41.bin +++ /dev/null @@ -1,384 +0,0 @@ -data: {"id":"gen-1791215160-CvDN4CyWOR2sGTk9s3co","object":"chat.completion.chunk","created":1791215160,"model":"qwen/qwen3-30b-a3b","provider":"DeepInfra","choices":[{"index":0,"delta":{"content":"","role":"assistant","reasoning":"\n","reasoning_details":[{"type":"reasoning.text","text":"\n","format":"unknown","index":0}]},"finish_reason":null,"native_finish_reason":null}]} - -data: {"id":"gen-1791215160-CvDN4CyWOR2sGTk9s3co","object":"chat.completion.chunk","created":1791215160,"model":"qwen/qwen3-30b-a3b","provider":"DeepInfra","choices":[{"index":0,"delta":{"content":"","role":"assistant","reasoning":"Okay","reasoning_details":[{"type":"reasoning.text","text":"Okay","format":"unknown","index":0}]},"finish_reason":null,"native_finish_reason":null}]} - -data: {"id":"gen-1791215160-CvDN4CyWOR2sGTk9s3co","object":"chat.completion.chunk","created":1791215160,"model":"qwen/qwen3-30b-a3b","provider":"DeepInfra","choices":[{"index":0,"delta":{"content":"","role":"assistant","reasoning":",","reasoning_details":[{"type":"reasoning.text","text":",","format":"unknown","index":0}]},"finish_reason":null,"native_finish_reason":null}]} - -data: {"id":"gen-1791215160-CvDN4CyWOR2sGTk9s3co","object":"chat.completion.chunk","created":1791215160,"model":"qwen/qwen3-30b-a3b","provider":"DeepInfra","choices":[{"index":0,"delta":{"content":"","role":"assistant","reasoning":" the","reasoning_details":[{"type":"reasoning.text","text":" the","format":"unknown","index":0}]},"finish_reason":null,"native_finish_reason":null}]} - -data: {"id":"gen-1791215160-CvDN4CyWOR2sGTk9s3co","object":"chat.completion.chunk","created":1791215160,"model":"qwen/qwen3-30b-a3b","provider":"DeepInfra","choices":[{"index":0,"delta":{"content":"","role":"assistant","reasoning":" user","reasoning_details":[{"type":"reasoning.text","text":" user","format":"unknown","index":0}]},"finish_reason":null,"native_finish_reason":null}]} - -data: {"id":"gen-1791215160-CvDN4CyWOR2sGTk9s3co","object":"chat.completion.chunk","created":1791215160,"model":"qwen/qwen3-30b-a3b","provider":"DeepInfra","choices":[{"index":0,"delta":{"content":"","role":"assistant","reasoning":" wants","reasoning_details":[{"type":"reasoning.text","text":" wants","format":"unknown","index":0}]},"finish_reason":null,"native_finish_reason":null}]} - -data: {"id":"gen-1791215160-CvDN4CyWOR2sGTk9s3co","object":"chat.completion.chunk","created":1791215160,"model":"qwen/qwen3-30b-a3b","provider":"DeepInfra","choices":[{"index":0,"delta":{"content":"","role":"assistant","reasoning":" me","reasoning_details":[{"type":"reasoning.text","text":" me","format":"unknown","index":0}]},"finish_reason":null,"native_finish_reason":null}]} - -data: {"id":"gen-1791215160-CvDN4CyWOR2sGTk9s3co","object":"chat.completion.chunk","created":1791215160,"model":"qwen/qwen3-30b-a3b","provider":"DeepInfra","choices":[{"index":0,"delta":{"content":"","role":"assistant","reasoning":" to","reasoning_details":[{"type":"reasoning.text","text":" to","format":"unknown","index":0}]},"finish_reason":null,"native_finish_reason":null}]} - -data: {"id":"gen-1791215160-CvDN4CyWOR2sGTk9s3co","object":"chat.completion.chunk","created":1791215160,"model":"qwen/qwen3-30b-a3b","provider":"DeepInfra","choices":[{"index":0,"delta":{"content":"","role":"assistant","reasoning":" act","reasoning_details":[{"type":"reasoning.text","text":" act","format":"unknown","index":0}]},"finish_reason":null,"native_finish_reason":null}]} - -data: {"id":"gen-1791215160-CvDN4CyWOR2sGTk9s3co","object":"chat.completion.chunk","created":1791215160,"model":"qwen/qwen3-30b-a3b","provider":"DeepInfra","choices":[{"index":0,"delta":{"content":"","role":"assistant","reasoning":" as","reasoning_details":[{"type":"reasoning.text","text":" as","format":"unknown","index":0}]},"finish_reason":null,"native_finish_reason":null}]} - -data: {"id":"gen-1791215160-CvDN4CyWOR2sGTk9s3co","object":"chat.completion.chunk","created":1791215160,"model":"qwen/qwen3-30b-a3b","provider":"DeepInfra","choices":[{"index":0,"delta":{"content":"","role":"assistant","reasoning":" the","reasoning_details":[{"type":"reasoning.text","text":" the","format":"unknown","index":0}]},"finish_reason":null,"native_finish_reason":null}]} - -data: {"id":"gen-1791215160-CvDN4CyWOR2sGTk9s3co","object":"chat.completion.chunk","created":1791215160,"model":"qwen/qwen3-30b-a3b","provider":"DeepInfra","choices":[{"index":0,"delta":{"content":"","role":"assistant","reasoning":" researcher","reasoning_details":[{"type":"reasoning.text","text":" researcher","format":"unknown","index":0}]},"finish_reason":null,"native_finish_reason":null}]} - -data: {"id":"gen-1791215160-CvDN4CyWOR2sGTk9s3co","object":"chat.completion.chunk","created":1791215160,"model":"qwen/qwen3-30b-a3b","provider":"DeepInfra","choices":[{"index":0,"delta":{"content":"","role":"assistant","reasoning":" sub","reasoning_details":[{"type":"reasoning.text","text":" sub","format":"unknown","index":0}]},"finish_reason":null,"native_finish_reason":null}]} - -data: {"id":"gen-1791215160-CvDN4CyWOR2sGTk9s3co","object":"chat.completion.chunk","created":1791215160,"model":"qwen/qwen3-30b-a3b","provider":"DeepInfra","choices":[{"index":0,"delta":{"content":"","role":"assistant","reasoning":"agent","reasoning_details":[{"type":"reasoning.text","text":"agent","format":"unknown","index":0}]},"finish_reason":null,"native_finish_reason":null}]} - -data: {"id":"gen-1791215160-CvDN4CyWOR2sGTk9s3co","object":"chat.completion.chunk","created":1791215160,"model":"qwen/qwen3-30b-a3b","provider":"DeepInfra","choices":[{"index":0,"delta":{"content":"","role":"assistant","reasoning":" for","reasoning_details":[{"type":"reasoning.text","text":" for","format":"unknown","index":0}]},"finish_reason":null,"native_finish_reason":null}]} - -data: {"id":"gen-1791215160-CvDN4CyWOR2sGTk9s3co","object":"chat.completion.chunk","created":1791215160,"model":"qwen/qwen3-30b-a3b","provider":"DeepInfra","choices":[{"index":0,"delta":{"content":"","role":"assistant","reasoning":" Brain","reasoning_details":[{"type":"reasoning.text","text":" Brain","format":"unknown","index":0}]},"finish_reason":null,"native_finish_reason":null}]} - -data: {"id":"gen-1791215160-CvDN4CyWOR2sGTk9s3co","object":"chat.completion.chunk","created":1791215160,"model":"qwen/qwen3-30b-a3b","provider":"DeepInfra","choices":[{"index":0,"delta":{"content":"","role":"assistant","reasoning":"trust","reasoning_details":[{"type":"reasoning.text","text":"trust","format":"unknown","index":0}]},"finish_reason":null,"native_finish_reason":null}]} - -data: {"id":"gen-1791215160-CvDN4CyWOR2sGTk9s3co","object":"chat.completion.chunk","created":1791215160,"model":"qwen/qwen3-30b-a3b","provider":"DeepInfra","choices":[{"index":0,"delta":{"content":"","role":"assistant","reasoning":" Eve","reasoning_details":[{"type":"reasoning.text","text":" Eve","format":"unknown","index":0}]},"finish_reason":null,"native_finish_reason":null}]} - -data: {"id":"gen-1791215160-CvDN4CyWOR2sGTk9s3co","object":"chat.completion.chunk","created":1791215160,"model":"qwen/qwen3-30b-a3b","provider":"DeepInfra","choices":[{"index":0,"delta":{"content":"","role":"assistant","reasoning":" instrumentation","reasoning_details":[{"type":"reasoning.text","text":" instrumentation","format":"unknown","index":0}]},"finish_reason":null,"native_finish_reason":null}]} - -data: {"id":"gen-1791215160-CvDN4CyWOR2sGTk9s3co","object":"chat.completion.chunk","created":1791215160,"model":"qwen/qwen3-30b-a3b","provider":"DeepInfra","choices":[{"index":0,"delta":{"content":"","role":"assistant","reasoning":" tests","reasoning_details":[{"type":"reasoning.text","text":" tests","format":"unknown","index":0}]},"finish_reason":null,"native_finish_reason":null}]} - -data: {"id":"gen-1791215160-CvDN4CyWOR2sGTk9s3co","object":"chat.completion.chunk","created":1791215160,"model":"qwen/qwen3-30b-a3b","provider":"DeepInfra","choices":[{"index":0,"delta":{"content":"","role":"assistant","reasoning":".","reasoning_details":[{"type":"reasoning.text","text":".","format":"unknown","index":0}]},"finish_reason":null,"native_finish_reason":null}]} - -data: {"id":"gen-1791215160-CvDN4CyWOR2sGTk9s3co","object":"chat.completion.chunk","created":1791215160,"model":"qwen/qwen3-30b-a3b","provider":"DeepInfra","choices":[{"index":0,"delta":{"content":"","role":"assistant","reasoning":" The","reasoning_details":[{"type":"reasoning.text","text":" The","format":"unknown","index":0}]},"finish_reason":null,"native_finish_reason":null}]} - -data: {"id":"gen-1791215160-CvDN4CyWOR2sGTk9s3co","object":"chat.completion.chunk","created":1791215160,"model":"qwen/qwen3-30b-a3b","provider":"DeepInfra","choices":[{"index":0,"delta":{"content":"","role":"assistant","reasoning":" task","reasoning_details":[{"type":"reasoning.text","text":" task","format":"unknown","index":0}]},"finish_reason":null,"native_finish_reason":null}]} - -data: {"id":"gen-1791215160-CvDN4CyWOR2sGTk9s3co","object":"chat.completion.chunk","created":1791215160,"model":"qwen/qwen3-30b-a3b","provider":"DeepInfra","choices":[{"index":0,"delta":{"content":"","role":"assistant","reasoning":" is","reasoning_details":[{"type":"reasoning.text","text":" is","format":"unknown","index":0}]},"finish_reason":null,"native_finish_reason":null}]} - -data: {"id":"gen-1791215160-CvDN4CyWOR2sGTk9s3co","object":"chat.completion.chunk","created":1791215160,"model":"qwen/qwen3-30b-a3b","provider":"DeepInfra","choices":[{"index":0,"delta":{"content":"","role":"assistant","reasoning":" to","reasoning_details":[{"type":"reasoning.text","text":" to","format":"unknown","index":0}]},"finish_reason":null,"native_finish_reason":null}]} - -data: {"id":"gen-1791215160-CvDN4CyWOR2sGTk9s3co","object":"chat.completion.chunk","created":1791215160,"model":"qwen/qwen3-30b-a3b","provider":"DeepInfra","choices":[{"index":0,"delta":{"content":"","role":"assistant","reasoning":" research","reasoning_details":[{"type":"reasoning.text","text":" research","format":"unknown","index":0}]},"finish_reason":null,"native_finish_reason":null}]} - -data: {"id":"gen-1791215160-CvDN4CyWOR2sGTk9s3co","object":"chat.completion.chunk","created":1791215160,"model":"qwen/qwen3-30b-a3b","provider":"DeepInfra","choices":[{"index":0,"delta":{"content":"","role":"assistant","reasoning":" the","reasoning_details":[{"type":"reasoning.text","text":" the","format":"unknown","index":0}]},"finish_reason":null,"native_finish_reason":null}]} - -data: {"id":"gen-1791215160-CvDN4CyWOR2sGTk9s3co","object":"chat.completion.chunk","created":1791215160,"model":"qwen/qwen3-30b-a3b","provider":"DeepInfra","choices":[{"index":0,"delta":{"content":"","role":"assistant","reasoning":" Eve","reasoning_details":[{"type":"reasoning.text","text":" Eve","format":"unknown","index":0}]},"finish_reason":null,"native_finish_reason":null}]} - -data: {"id":"gen-1791215160-CvDN4CyWOR2sGTk9s3co","object":"chat.completion.chunk","created":1791215160,"model":"qwen/qwen3-30b-a3b","provider":"DeepInfra","choices":[{"index":0,"delta":{"content":"","role":"assistant","reasoning":" instrumentation","reasoning_details":[{"type":"reasoning.text","text":" instrumentation","format":"unknown","index":0}]},"finish_reason":null,"native_finish_reason":null}]} - -data: {"id":"gen-1791215160-CvDN4CyWOR2sGTk9s3co","object":"chat.completion.chunk","created":1791215160,"model":"qwen/qwen3-30b-a3b","provider":"DeepInfra","choices":[{"index":0,"delta":{"content":"","role":"assistant","reasoning":" documentation","reasoning_details":[{"type":"reasoning.text","text":" documentation","format":"unknown","index":0}]},"finish_reason":null,"native_finish_reason":null}]} - -data: {"id":"gen-1791215160-CvDN4CyWOR2sGTk9s3co","object":"chat.completion.chunk","created":1791215160,"model":"qwen/qwen3-30b-a3b","provider":"DeepInfra","choices":[{"index":0,"delta":{"content":"","role":"assistant","reasoning":" before","reasoning_details":[{"type":"reasoning.text","text":" before","format":"unknown","index":0}]},"finish_reason":null,"native_finish_reason":null}]} - -data: {"id":"gen-1791215160-CvDN4CyWOR2sGTk9s3co","object":"chat.completion.chunk","created":1791215160,"model":"qwen/qwen3-30b-a3b","provider":"DeepInfra","choices":[{"index":0,"delta":{"content":"","role":"assistant","reasoning":" the","reasoning_details":[{"type":"reasoning.text","text":" the","format":"unknown","index":0}]},"finish_reason":null,"native_finish_reason":null}]} - -data: {"id":"gen-1791215160-CvDN4CyWOR2sGTk9s3co","object":"chat.completion.chunk","created":1791215160,"model":"qwen/qwen3-30b-a3b","provider":"DeepInfra","choices":[{"index":0,"delta":{"content":"","role":"assistant","reasoning":" parent","reasoning_details":[{"type":"reasoning.text","text":" parent","format":"unknown","index":0}]},"finish_reason":null,"native_finish_reason":null}]} - -data: {"id":"gen-1791215160-CvDN4CyWOR2sGTk9s3co","object":"chat.completion.chunk","created":1791215160,"model":"qwen/qwen3-30b-a3b","provider":"DeepInfra","choices":[{"index":0,"delta":{"content":"","role":"assistant","reasoning":" reads","reasoning_details":[{"type":"reasoning.text","text":" reads","format":"unknown","index":0}]},"finish_reason":null,"native_finish_reason":null}]} - -data: {"id":"gen-1791215160-CvDN4CyWOR2sGTk9s3co","object":"chat.completion.chunk","created":1791215160,"model":"qwen/qwen3-30b-a3b","provider":"DeepInfra","choices":[{"index":0,"delta":{"content":"","role":"assistant","reasoning":" it","reasoning_details":[{"type":"reasoning.text","text":" it","format":"unknown","index":0}]},"finish_reason":null,"native_finish_reason":null}]} - -data: {"id":"gen-1791215160-CvDN4CyWOR2sGTk9s3co","object":"chat.completion.chunk","created":1791215160,"model":"qwen/qwen3-30b-a3b","provider":"DeepInfra","choices":[{"index":0,"delta":{"content":"","role":"assistant","reasoning":".","reasoning_details":[{"type":"reasoning.text","text":".","format":"unknown","index":0}]},"finish_reason":null,"native_finish_reason":null}]} - -data: {"id":"gen-1791215160-CvDN4CyWOR2sGTk9s3co","object":"chat.completion.chunk","created":1791215160,"model":"qwen/qwen3-30b-a3b","provider":"DeepInfra","choices":[{"index":0,"delta":{"content":"","role":"assistant","reasoning":" The","reasoning_details":[{"type":"reasoning.text","text":" The","format":"unknown","index":0}]},"finish_reason":null,"native_finish_reason":null}]} - -data: {"id":"gen-1791215160-CvDN4CyWOR2sGTk9s3co","object":"chat.completion.chunk","created":1791215160,"model":"qwen/qwen3-30b-a3b","provider":"DeepInfra","choices":[{"index":0,"delta":{"content":"","role":"assistant","reasoning":" caller","reasoning_details":[{"type":"reasoning.text","text":" caller","format":"unknown","index":0}]},"finish_reason":null,"native_finish_reason":null}]} - -data: {"id":"gen-1791215160-CvDN4CyWOR2sGTk9s3co","object":"chat.completion.chunk","created":1791215160,"model":"qwen/qwen3-30b-a3b","provider":"DeepInfra","choices":[{"index":0,"delta":{"content":"","role":"assistant","reasoning":"'s","reasoning_details":[{"type":"reasoning.text","text":"'s","format":"unknown","index":0}]},"finish_reason":null,"native_finish_reason":null}]} - -data: {"id":"gen-1791215160-CvDN4CyWOR2sGTk9s3co","object":"chat.completion.chunk","created":1791215160,"model":"qwen/qwen3-30b-a3b","provider":"DeepInfra","choices":[{"index":0,"delta":{"content":"","role":"assistant","reasoning":" message","reasoning_details":[{"type":"reasoning.text","text":" message","format":"unknown","index":0}]},"finish_reason":null,"native_finish_reason":null}]} - -data: {"id":"gen-1791215160-CvDN4CyWOR2sGTk9s3co","object":"chat.completion.chunk","created":1791215160,"model":"qwen/qwen3-30b-a3b","provider":"DeepInfra","choices":[{"index":0,"delta":{"content":"","role":"assistant","reasoning":" is","reasoning_details":[{"type":"reasoning.text","text":" is","format":"unknown","index":0}]},"finish_reason":null,"native_finish_reason":null}]} - -data: {"id":"gen-1791215160-CvDN4CyWOR2sGTk9s3co","object":"chat.completion.chunk","created":1791215160,"model":"qwen/qwen3-30b-a3b","provider":"DeepInfra","choices":[{"index":0,"delta":{"content":"","role":"assistant","reasoning":" to","reasoning_details":[{"type":"reasoning.text","text":" to","format":"unknown","index":0}]},"finish_reason":null,"native_finish_reason":null}]} - -data: {"id":"gen-1791215160-CvDN4CyWOR2sGTk9s3co","object":"chat.completion.chunk","created":1791215160,"model":"qwen/qwen3-30b-a3b","provider":"DeepInfra","choices":[{"index":0,"delta":{"content":"","role":"assistant","reasoning":" \"","reasoning_details":[{"type":"reasoning.text","text":" \"","format":"unknown","index":0}]},"finish_reason":null,"native_finish_reason":null}]} - -data: {"id":"gen-1791215160-CvDN4CyWOR2sGTk9s3co","object":"chat.completion.chunk","created":1791215160,"model":"qwen/qwen3-30b-a3b","provider":"DeepInfra","choices":[{"index":0,"delta":{"content":"","role":"assistant","reasoning":"Run","reasoning_details":[{"type":"reasoning.text","text":"Run","format":"unknown","index":0}]},"finish_reason":null,"native_finish_reason":null}]} - -data: {"id":"gen-1791215160-CvDN4CyWOR2sGTk9s3co","object":"chat.completion.chunk","created":1791215160,"model":"qwen/qwen3-30b-a3b","provider":"DeepInfra","choices":[{"index":0,"delta":{"content":"","role":"assistant","reasoning":" the","reasoning_details":[{"type":"reasoning.text","text":" the","format":"unknown","index":0}]},"finish_reason":null,"native_finish_reason":null}]} - -data: {"id":"gen-1791215160-CvDN4CyWOR2sGTk9s3co","object":"chat.completion.chunk","created":1791215160,"model":"qwen/qwen3-30b-a3b","provider":"DeepInfra","choices":[{"index":0,"delta":{"content":"","role":"assistant","reasoning":" Brain","reasoning_details":[{"type":"reasoning.text","text":" Brain","format":"unknown","index":0}]},"finish_reason":null,"native_finish_reason":null}]} - -data: {"id":"gen-1791215160-CvDN4CyWOR2sGTk9s3co","object":"chat.completion.chunk","created":1791215160,"model":"qwen/qwen3-30b-a3b","provider":"DeepInfra","choices":[{"index":0,"delta":{"content":"","role":"assistant","reasoning":"trust","reasoning_details":[{"type":"reasoning.text","text":"trust","format":"unknown","index":0}]},"finish_reason":null,"native_finish_reason":null}]} - -data: {"id":"gen-1791215160-CvDN4CyWOR2sGTk9s3co","object":"chat.completion.chunk","created":1791215160,"model":"qwen/qwen3-30b-a3b","provider":"DeepInfra","choices":[{"index":0,"delta":{"content":"","role":"assistant","reasoning":" Eve","reasoning_details":[{"type":"reasoning.text","text":" Eve","format":"unknown","index":0}]},"finish_reason":null,"native_finish_reason":null}]} - -data: {"id":"gen-1791215160-CvDN4CyWOR2sGTk9s3co","object":"chat.completion.chunk","created":1791215160,"model":"qwen/qwen3-30b-a3b","provider":"DeepInfra","choices":[{"index":0,"delta":{"content":"","role":"assistant","reasoning":" instrumentation","reasoning_details":[{"type":"reasoning.text","text":" instrumentation","format":"unknown","index":0}]},"finish_reason":null,"native_finish_reason":null}]} - -data: {"id":"gen-1791215160-CvDN4CyWOR2sGTk9s3co","object":"chat.completion.chunk","created":1791215160,"model":"qwen/qwen3-30b-a3b","provider":"DeepInfra","choices":[{"index":0,"delta":{"content":"","role":"assistant","reasoning":" e","reasoning_details":[{"type":"reasoning.text","text":" e","format":"unknown","index":0}]},"finish_reason":null,"native_finish_reason":null}]} - -data: {"id":"gen-1791215160-CvDN4CyWOR2sGTk9s3co","object":"chat.completion.chunk","created":1791215160,"model":"qwen/qwen3-30b-a3b","provider":"DeepInfra","choices":[{"index":0,"delta":{"content":"","role":"assistant","reasoning":"2","reasoning_details":[{"type":"reasoning.text","text":"2","format":"unknown","index":0}]},"finish_reason":null,"native_finish_reason":null}]} - -data: {"id":"gen-1791215160-CvDN4CyWOR2sGTk9s3co","object":"chat.completion.chunk","created":1791215160,"model":"qwen/qwen3-30b-a3b","provider":"DeepInfra","choices":[{"index":0,"delta":{"content":"","role":"assistant","reasoning":"e","reasoning_details":[{"type":"reasoning.text","text":"e","format":"unknown","index":0}]},"finish_reason":null,"native_finish_reason":null}]} - -data: {"id":"gen-1791215160-CvDN4CyWOR2sGTk9s3co","object":"chat.completion.chunk","created":1791215160,"model":"qwen/qwen3-30b-a3b","provider":"DeepInfra","choices":[{"index":0,"delta":{"content":"","role":"assistant","reasoning":" scenario","reasoning_details":[{"type":"reasoning.text","text":" scenario","format":"unknown","index":0}]},"finish_reason":null,"native_finish_reason":null}]} - -data: {"id":"gen-1791215160-CvDN4CyWOR2sGTk9s3co","object":"chat.completion.chunk","created":1791215160,"model":"qwen/qwen3-30b-a3b","provider":"DeepInfra","choices":[{"index":0,"delta":{"content":"","role":"assistant","reasoning":".\"\n\n","reasoning_details":[{"type":"reasoning.text","text":".\"\n\n","format":"unknown","index":0}]},"finish_reason":null,"native_finish_reason":null}]} - -data: {"id":"gen-1791215160-CvDN4CyWOR2sGTk9s3co","object":"chat.completion.chunk","created":1791215160,"model":"qwen/qwen3-30b-a3b","provider":"DeepInfra","choices":[{"index":0,"delta":{"content":"","role":"assistant","reasoning":"First","reasoning_details":[{"type":"reasoning.text","text":"First","format":"unknown","index":0}]},"finish_reason":null,"native_finish_reason":null}]} - -data: {"id":"gen-1791215160-CvDN4CyWOR2sGTk9s3co","object":"chat.completion.chunk","created":1791215160,"model":"qwen/qwen3-30b-a3b","provider":"DeepInfra","choices":[{"index":0,"delta":{"content":"","role":"assistant","reasoning":",","reasoning_details":[{"type":"reasoning.text","text":",","format":"unknown","index":0}]},"finish_reason":null,"native_finish_reason":null}]} - -data: {"id":"gen-1791215160-CvDN4CyWOR2sGTk9s3co","object":"chat.completion.chunk","created":1791215160,"model":"qwen/qwen3-30b-a3b","provider":"DeepInfra","choices":[{"index":0,"delta":{"content":"","role":"assistant","reasoning":" I","reasoning_details":[{"type":"reasoning.text","text":" I","format":"unknown","index":0}]},"finish_reason":null,"native_finish_reason":null}]} - -data: {"id":"gen-1791215160-CvDN4CyWOR2sGTk9s3co","object":"chat.completion.chunk","created":1791215160,"model":"qwen/qwen3-30b-a3b","provider":"DeepInfra","choices":[{"index":0,"delta":{"content":"","role":"assistant","reasoning":" need","reasoning_details":[{"type":"reasoning.text","text":" need","format":"unknown","index":0}]},"finish_reason":null,"native_finish_reason":null}]} - -data: {"id":"gen-1791215160-CvDN4CyWOR2sGTk9s3co","object":"chat.completion.chunk","created":1791215160,"model":"qwen/qwen3-30b-a3b","provider":"DeepInfra","choices":[{"index":0,"delta":{"content":"","role":"assistant","reasoning":" to","reasoning_details":[{"type":"reasoning.text","text":" to","format":"unknown","index":0}]},"finish_reason":null,"native_finish_reason":null}]} - -data: {"id":"gen-1791215160-CvDN4CyWOR2sGTk9s3co","object":"chat.completion.chunk","created":1791215160,"model":"qwen/qwen3-30b-a3b","provider":"DeepInfra","choices":[{"index":0,"delta":{"content":"","role":"assistant","reasoning":" call","reasoning_details":[{"type":"reasoning.text","text":" call","format":"unknown","index":0}]},"finish_reason":null,"native_finish_reason":null}]} - -data: {"id":"gen-1791215160-CvDN4CyWOR2sGTk9s3co","object":"chat.completion.chunk","created":1791215160,"model":"qwen/qwen3-30b-a3b","provider":"DeepInfra","choices":[{"index":0,"delta":{"content":"","role":"assistant","reasoning":" the","reasoning_details":[{"type":"reasoning.text","text":" the","format":"unknown","index":0}]},"finish_reason":null,"native_finish_reason":null}]} - -data: {"id":"gen-1791215160-CvDN4CyWOR2sGTk9s3co","object":"chat.completion.chunk","created":1791215160,"model":"qwen/qwen3-30b-a3b","provider":"DeepInfra","choices":[{"index":0,"delta":{"content":"","role":"assistant","reasoning":" search","reasoning_details":[{"type":"reasoning.text","text":" search","format":"unknown","index":0}]},"finish_reason":null,"native_finish_reason":null}]} - -data: {"id":"gen-1791215160-CvDN4CyWOR2sGTk9s3co","object":"chat.completion.chunk","created":1791215160,"model":"qwen/qwen3-30b-a3b","provider":"DeepInfra","choices":[{"index":0,"delta":{"content":"","role":"assistant","reasoning":" tool","reasoning_details":[{"type":"reasoning.text","text":" tool","format":"unknown","index":0}]},"finish_reason":null,"native_finish_reason":null}]} - -data: {"id":"gen-1791215160-CvDN4CyWOR2sGTk9s3co","object":"chat.completion.chunk","created":1791215160,"model":"qwen/qwen3-30b-a3b","provider":"DeepInfra","choices":[{"index":0,"delta":{"content":"","role":"assistant","reasoning":" with","reasoning_details":[{"type":"reasoning.text","text":" with","format":"unknown","index":0}]},"finish_reason":null,"native_finish_reason":null}]} - -data: {"id":"gen-1791215160-CvDN4CyWOR2sGTk9s3co","object":"chat.completion.chunk","created":1791215160,"model":"qwen/qwen3-30b-a3b","provider":"DeepInfra","choices":[{"index":0,"delta":{"content":"","role":"assistant","reasoning":" the","reasoning_details":[{"type":"reasoning.text","text":" the","format":"unknown","index":0}]},"finish_reason":null,"native_finish_reason":null}]} - -data: {"id":"gen-1791215160-CvDN4CyWOR2sGTk9s3co","object":"chat.completion.chunk","created":1791215160,"model":"qwen/qwen3-30b-a3b","provider":"DeepInfra","choices":[{"index":0,"delta":{"content":"","role":"assistant","reasoning":" query","reasoning_details":[{"type":"reasoning.text","text":" query","format":"unknown","index":0}]},"finish_reason":null,"native_finish_reason":null}]} - -data: {"id":"gen-1791215160-CvDN4CyWOR2sGTk9s3co","object":"chat.completion.chunk","created":1791215160,"model":"qwen/qwen3-30b-a3b","provider":"DeepInfra","choices":[{"index":0,"delta":{"content":"","role":"assistant","reasoning":" \"","reasoning_details":[{"type":"reasoning.text","text":" \"","format":"unknown","index":0}]},"finish_reason":null,"native_finish_reason":null}]} - -data: {"id":"gen-1791215160-CvDN4CyWOR2sGTk9s3co","object":"chat.completion.chunk","created":1791215160,"model":"qwen/qwen3-30b-a3b","provider":"DeepInfra","choices":[{"index":0,"delta":{"content":"","role":"assistant","reasoning":"Run","reasoning_details":[{"type":"reasoning.text","text":"Run","format":"unknown","index":0}]},"finish_reason":null,"native_finish_reason":null}]} - -data: {"id":"gen-1791215160-CvDN4CyWOR2sGTk9s3co","object":"chat.completion.chunk","created":1791215160,"model":"qwen/qwen3-30b-a3b","provider":"DeepInfra","choices":[{"index":0,"delta":{"content":"","role":"assistant","reasoning":" the","reasoning_details":[{"type":"reasoning.text","text":" the","format":"unknown","index":0}]},"finish_reason":null,"native_finish_reason":null}]} - -data: {"id":"gen-1791215160-CvDN4CyWOR2sGTk9s3co","object":"chat.completion.chunk","created":1791215160,"model":"qwen/qwen3-30b-a3b","provider":"DeepInfra","choices":[{"index":0,"delta":{"content":"","role":"assistant","reasoning":" Brain","reasoning_details":[{"type":"reasoning.text","text":" Brain","format":"unknown","index":0}]},"finish_reason":null,"native_finish_reason":null}]} - -data: {"id":"gen-1791215160-CvDN4CyWOR2sGTk9s3co","object":"chat.completion.chunk","created":1791215160,"model":"qwen/qwen3-30b-a3b","provider":"DeepInfra","choices":[{"index":0,"delta":{"content":"","role":"assistant","reasoning":"trust","reasoning_details":[{"type":"reasoning.text","text":"trust","format":"unknown","index":0}]},"finish_reason":null,"native_finish_reason":null}]} - -data: {"id":"gen-1791215160-CvDN4CyWOR2sGTk9s3co","object":"chat.completion.chunk","created":1791215160,"model":"qwen/qwen3-30b-a3b","provider":"DeepInfra","choices":[{"index":0,"delta":{"content":"","role":"assistant","reasoning":" Eve","reasoning_details":[{"type":"reasoning.text","text":" Eve","format":"unknown","index":0}]},"finish_reason":null,"native_finish_reason":null}]} - -data: {"id":"gen-1791215160-CvDN4CyWOR2sGTk9s3co","object":"chat.completion.chunk","created":1791215160,"model":"qwen/qwen3-30b-a3b","provider":"DeepInfra","choices":[{"index":0,"delta":{"content":"","role":"assistant","reasoning":" instrumentation","reasoning_details":[{"type":"reasoning.text","text":" instrumentation","format":"unknown","index":0}]},"finish_reason":null,"native_finish_reason":null}]} - -data: {"id":"gen-1791215160-CvDN4CyWOR2sGTk9s3co","object":"chat.completion.chunk","created":1791215160,"model":"qwen/qwen3-30b-a3b","provider":"DeepInfra","choices":[{"index":0,"delta":{"content":"","role":"assistant","reasoning":" e","reasoning_details":[{"type":"reasoning.text","text":" e","format":"unknown","index":0}]},"finish_reason":null,"native_finish_reason":null}]} - -data: {"id":"gen-1791215160-CvDN4CyWOR2sGTk9s3co","object":"chat.completion.chunk","created":1791215160,"model":"qwen/qwen3-30b-a3b","provider":"DeepInfra","choices":[{"index":0,"delta":{"content":"","role":"assistant","reasoning":"2","reasoning_details":[{"type":"reasoning.text","text":"2","format":"unknown","index":0}]},"finish_reason":null,"native_finish_reason":null}]} - -data: {"id":"gen-1791215160-CvDN4CyWOR2sGTk9s3co","object":"chat.completion.chunk","created":1791215160,"model":"qwen/qwen3-30b-a3b","provider":"DeepInfra","choices":[{"index":0,"delta":{"content":"","role":"assistant","reasoning":"e","reasoning_details":[{"type":"reasoning.text","text":"e","format":"unknown","index":0}]},"finish_reason":null,"native_finish_reason":null}]} - -data: {"id":"gen-1791215160-CvDN4CyWOR2sGTk9s3co","object":"chat.completion.chunk","created":1791215160,"model":"qwen/qwen3-30b-a3b","provider":"DeepInfra","choices":[{"index":0,"delta":{"content":"","role":"assistant","reasoning":" scenario","reasoning_details":[{"type":"reasoning.text","text":" scenario","format":"unknown","index":0}]},"finish_reason":null,"native_finish_reason":null}]} - -data: {"id":"gen-1791215160-CvDN4CyWOR2sGTk9s3co","object":"chat.completion.chunk","created":1791215160,"model":"qwen/qwen3-30b-a3b","provider":"DeepInfra","choices":[{"index":0,"delta":{"content":"","role":"assistant","reasoning":".\"","reasoning_details":[{"type":"reasoning.text","text":".\"","format":"unknown","index":0}]},"finish_reason":null,"native_finish_reason":null}]} - -data: {"id":"gen-1791215160-CvDN4CyWOR2sGTk9s3co","object":"chat.completion.chunk","created":1791215160,"model":"qwen/qwen3-30b-a3b","provider":"DeepInfra","choices":[{"index":0,"delta":{"content":"","role":"assistant","reasoning":" The","reasoning_details":[{"type":"reasoning.text","text":" The","format":"unknown","index":0}]},"finish_reason":null,"native_finish_reason":null}]} - -data: {"id":"gen-1791215160-CvDN4CyWOR2sGTk9s3co","object":"chat.completion.chunk","created":1791215160,"model":"qwen/qwen3-30b-a3b","provider":"DeepInfra","choices":[{"index":0,"delta":{"content":"","role":"assistant","reasoning":" search","reasoning_details":[{"type":"reasoning.text","text":" search","format":"unknown","index":0}]},"finish_reason":null,"native_finish_reason":null}]} - -data: {"id":"gen-1791215160-CvDN4CyWOR2sGTk9s3co","object":"chat.completion.chunk","created":1791215160,"model":"qwen/qwen3-30b-a3b","provider":"DeepInfra","choices":[{"index":0,"delta":{"content":"","role":"assistant","reasoning":" result","reasoning_details":[{"type":"reasoning.text","text":" result","format":"unknown","index":0}]},"finish_reason":null,"native_finish_reason":null}]} - -data: {"id":"gen-1791215160-CvDN4CyWOR2sGTk9s3co","object":"chat.completion.chunk","created":1791215160,"model":"qwen/qwen3-30b-a3b","provider":"DeepInfra","choices":[{"index":0,"delta":{"content":"","role":"assistant","reasoning":" returned","reasoning_details":[{"type":"reasoning.text","text":" returned","format":"unknown","index":0}]},"finish_reason":null,"native_finish_reason":null}]} - -data: {"id":"gen-1791215160-CvDN4CyWOR2sGTk9s3co","object":"chat.completion.chunk","created":1791215160,"model":"qwen/qwen3-30b-a3b","provider":"DeepInfra","choices":[{"index":0,"delta":{"content":"","role":"assistant","reasoning":" a","reasoning_details":[{"type":"reasoning.text","text":" a","format":"unknown","index":0}]},"finish_reason":null,"native_finish_reason":null}]} - -data: {"id":"gen-1791215160-CvDN4CyWOR2sGTk9s3co","object":"chat.completion.chunk","created":1791215160,"model":"qwen/qwen3-30b-a3b","provider":"DeepInfra","choices":[{"index":0,"delta":{"content":"","role":"assistant","reasoning":" title","reasoning_details":[{"type":"reasoning.text","text":" title","format":"unknown","index":0}]},"finish_reason":null,"native_finish_reason":null}]} - -data: {"id":"gen-1791215160-CvDN4CyWOR2sGTk9s3co","object":"chat.completion.chunk","created":1791215160,"model":"qwen/qwen3-30b-a3b","provider":"DeepInfra","choices":[{"index":0,"delta":{"content":"","role":"assistant","reasoning":" \"","reasoning_details":[{"type":"reasoning.text","text":" \"","format":"unknown","index":0}]},"finish_reason":null,"native_finish_reason":null}]} - -data: {"id":"gen-1791215160-CvDN4CyWOR2sGTk9s3co","object":"chat.completion.chunk","created":1791215160,"model":"qwen/qwen3-30b-a3b","provider":"DeepInfra","choices":[{"index":0,"delta":{"content":"","role":"assistant","reasoning":"E","reasoning_details":[{"type":"reasoning.text","text":"E","format":"unknown","index":0}]},"finish_reason":null,"native_finish_reason":null}]} - -data: {"id":"gen-1791215160-CvDN4CyWOR2sGTk9s3co","object":"chat.completion.chunk","created":1791215160,"model":"qwen/qwen3-30b-a3b","provider":"DeepInfra","choices":[{"index":0,"delta":{"content":"","role":"assistant","reasoning":"ve","reasoning_details":[{"type":"reasoning.text","text":"ve","format":"unknown","index":0}]},"finish_reason":null,"native_finish_reason":null}]} - -data: {"id":"gen-1791215160-CvDN4CyWOR2sGTk9s3co","object":"chat.completion.chunk","created":1791215160,"model":"qwen/qwen3-30b-a3b","provider":"DeepInfra","choices":[{"index":0,"delta":{"content":"","role":"assistant","reasoning":" instrumentation","reasoning_details":[{"type":"reasoning.text","text":" instrumentation","format":"unknown","index":0}]},"finish_reason":null,"native_finish_reason":null}]} - -data: {"id":"gen-1791215160-CvDN4CyWOR2sGTk9s3co","object":"chat.completion.chunk","created":1791215160,"model":"qwen/qwen3-30b-a3b","provider":"DeepInfra","choices":[{"index":0,"delta":{"content":"","role":"assistant","reasoning":"\"","reasoning_details":[{"type":"reasoning.text","text":"\"","format":"unknown","index":0}]},"finish_reason":null,"native_finish_reason":null}]} - -data: {"id":"gen-1791215160-CvDN4CyWOR2sGTk9s3co","object":"chat.completion.chunk","created":1791215160,"model":"qwen/qwen3-30b-a3b","provider":"DeepInfra","choices":[{"index":0,"delta":{"content":"","role":"assistant","reasoning":" and","reasoning_details":[{"type":"reasoning.text","text":" and","format":"unknown","index":0}]},"finish_reason":null,"native_finish_reason":null}]} - -data: {"id":"gen-1791215160-CvDN4CyWOR2sGTk9s3co","object":"chat.completion.chunk","created":1791215160,"model":"qwen/qwen3-30b-a3b","provider":"DeepInfra","choices":[{"index":0,"delta":{"content":"","role":"assistant","reasoning":" a","reasoning_details":[{"type":"reasoning.text","text":" a","format":"unknown","index":0}]},"finish_reason":null,"native_finish_reason":null}]} - -data: {"id":"gen-1791215160-CvDN4CyWOR2sGTk9s3co","object":"chat.completion.chunk","created":1791215160,"model":"qwen/qwen3-30b-a3b","provider":"DeepInfra","choices":[{"index":0,"delta":{"content":"","role":"assistant","reasoning":" URL","reasoning_details":[{"type":"reasoning.text","text":" URL","format":"unknown","index":0}]},"finish_reason":null,"native_finish_reason":null}]} - -data: {"id":"gen-1791215160-CvDN4CyWOR2sGTk9s3co","object":"chat.completion.chunk","created":1791215160,"model":"qwen/qwen3-30b-a3b","provider":"DeepInfra","choices":[{"index":0,"delta":{"content":"","role":"assistant","reasoning":".","reasoning_details":[{"type":"reasoning.text","text":".","format":"unknown","index":0}]},"finish_reason":null,"native_finish_reason":null}]} - -data: {"id":"gen-1791215160-CvDN4CyWOR2sGTk9s3co","object":"chat.completion.chunk","created":1791215160,"model":"qwen/qwen3-30b-a3b","provider":"DeepInfra","choices":[{"index":0,"delta":{"content":"","role":"assistant","reasoning":" Now","reasoning_details":[{"type":"reasoning.text","text":" Now","format":"unknown","index":0}]},"finish_reason":null,"native_finish_reason":null}]} - -data: {"id":"gen-1791215160-CvDN4CyWOR2sGTk9s3co","object":"chat.completion.chunk","created":1791215160,"model":"qwen/qwen3-30b-a3b","provider":"DeepInfra","choices":[{"index":0,"delta":{"content":"","role":"assistant","reasoning":",","reasoning_details":[{"type":"reasoning.text","text":",","format":"unknown","index":0}]},"finish_reason":null,"native_finish_reason":null}]} - -data: {"id":"gen-1791215160-CvDN4CyWOR2sGTk9s3co","object":"chat.completion.chunk","created":1791215160,"model":"qwen/qwen3-30b-a3b","provider":"DeepInfra","choices":[{"index":0,"delta":{"content":"","role":"assistant","reasoning":" I","reasoning_details":[{"type":"reasoning.text","text":" I","format":"unknown","index":0}]},"finish_reason":null,"native_finish_reason":null}]} - -data: {"id":"gen-1791215160-CvDN4CyWOR2sGTk9s3co","object":"chat.completion.chunk","created":1791215160,"model":"qwen/qwen3-30b-a3b","provider":"DeepInfra","choices":[{"index":0,"delta":{"content":"","role":"assistant","reasoning":" need","reasoning_details":[{"type":"reasoning.text","text":" need","format":"unknown","index":0}]},"finish_reason":null,"native_finish_reason":null}]} - -data: {"id":"gen-1791215160-CvDN4CyWOR2sGTk9s3co","object":"chat.completion.chunk","created":1791215160,"model":"qwen/qwen3-30b-a3b","provider":"DeepInfra","choices":[{"index":0,"delta":{"content":"","role":"assistant","reasoning":" to","reasoning_details":[{"type":"reasoning.text","text":" to","format":"unknown","index":0}]},"finish_reason":null,"native_finish_reason":null}]} - -data: {"id":"gen-1791215160-CvDN4CyWOR2sGTk9s3co","object":"chat.completion.chunk","created":1791215160,"model":"qwen/qwen3-30b-a3b","provider":"DeepInfra","choices":[{"index":0,"delta":{"content":"","role":"assistant","reasoning":" format","reasoning_details":[{"type":"reasoning.text","text":" format","format":"unknown","index":0}]},"finish_reason":null,"native_finish_reason":null}]} - -data: {"id":"gen-1791215160-CvDN4CyWOR2sGTk9s3co","object":"chat.completion.chunk","created":1791215160,"model":"qwen/qwen3-30b-a3b","provider":"DeepInfra","choices":[{"index":0,"delta":{"content":"","role":"assistant","reasoning":" the","reasoning_details":[{"type":"reasoning.text","text":" the","format":"unknown","index":0}]},"finish_reason":null,"native_finish_reason":null}]} - -data: {"id":"gen-1791215160-CvDN4CyWOR2sGTk9s3co","object":"chat.completion.chunk","created":1791215160,"model":"qwen/qwen3-30b-a3b","provider":"DeepInfra","choices":[{"index":0,"delta":{"content":"","role":"assistant","reasoning":" answer","reasoning_details":[{"type":"reasoning.text","text":" answer","format":"unknown","index":0}]},"finish_reason":null,"native_finish_reason":null}]} - -data: {"id":"gen-1791215160-CvDN4CyWOR2sGTk9s3co","object":"chat.completion.chunk","created":1791215160,"model":"qwen/qwen3-30b-a3b","provider":"DeepInfra","choices":[{"index":0,"delta":{"content":"","role":"assistant","reasoning":" as","reasoning_details":[{"type":"reasoning.text","text":" as","format":"unknown","index":0}]},"finish_reason":null,"native_finish_reason":null}]} - -data: {"id":"gen-1791215160-CvDN4CyWOR2sGTk9s3co","object":"chat.completion.chunk","created":1791215160,"model":"qwen/qwen3-30b-a3b","provider":"DeepInfra","choices":[{"index":0,"delta":{"content":"","role":"assistant","reasoning":" specified","reasoning_details":[{"type":"reasoning.text","text":" specified","format":"unknown","index":0}]},"finish_reason":null,"native_finish_reason":null}]} - -data: {"id":"gen-1791215160-CvDN4CyWOR2sGTk9s3co","object":"chat.completion.chunk","created":1791215160,"model":"qwen/qwen3-30b-a3b","provider":"DeepInfra","choices":[{"index":0,"delta":{"content":"","role":"assistant","reasoning":".\n\n","reasoning_details":[{"type":"reasoning.text","text":".\n\n","format":"unknown","index":0}]},"finish_reason":null,"native_finish_reason":null}]} - -data: {"id":"gen-1791215160-CvDN4CyWOR2sGTk9s3co","object":"chat.completion.chunk","created":1791215160,"model":"qwen/qwen3-30b-a3b","provider":"DeepInfra","choices":[{"index":0,"delta":{"content":"","role":"assistant","reasoning":"The","reasoning_details":[{"type":"reasoning.text","text":"The","format":"unknown","index":0}]},"finish_reason":null,"native_finish_reason":null}]} - -data: {"id":"gen-1791215160-CvDN4CyWOR2sGTk9s3co","object":"chat.completion.chunk","created":1791215160,"model":"qwen/qwen3-30b-a3b","provider":"DeepInfra","choices":[{"index":0,"delta":{"content":"","role":"assistant","reasoning":" answer","reasoning_details":[{"type":"reasoning.text","text":" answer","format":"unknown","index":0}]},"finish_reason":null,"native_finish_reason":null}]} - -data: {"id":"gen-1791215160-CvDN4CyWOR2sGTk9s3co","object":"chat.completion.chunk","created":1791215160,"model":"qwen/qwen3-30b-a3b","provider":"DeepInfra","choices":[{"index":0,"delta":{"content":"","role":"assistant","reasoning":" should","reasoning_details":[{"type":"reasoning.text","text":" should","format":"unknown","index":0}]},"finish_reason":null,"native_finish_reason":null}]} - -data: {"id":"gen-1791215160-CvDN4CyWOR2sGTk9s3co","object":"chat.completion.chunk","created":1791215160,"model":"qwen/qwen3-30b-a3b","provider":"DeepInfra","choices":[{"index":0,"delta":{"content":"","role":"assistant","reasoning":" start","reasoning_details":[{"type":"reasoning.text","text":" start","format":"unknown","index":0}]},"finish_reason":null,"native_finish_reason":null}]} - -data: {"id":"gen-1791215160-CvDN4CyWOR2sGTk9s3co","object":"chat.completion.chunk","created":1791215160,"model":"qwen/qwen3-30b-a3b","provider":"DeepInfra","choices":[{"index":0,"delta":{"content":"","role":"assistant","reasoning":" with","reasoning_details":[{"type":"reasoning.text","text":" with","format":"unknown","index":0}]},"finish_reason":null,"native_finish_reason":null}]} - -data: {"id":"gen-1791215160-CvDN4CyWOR2sGTk9s3co","object":"chat.completion.chunk","created":1791215160,"model":"qwen/qwen3-30b-a3b","provider":"DeepInfra","choices":[{"index":0,"delta":{"content":"","role":"assistant","reasoning":" \"","reasoning_details":[{"type":"reasoning.text","text":" \"","format":"unknown","index":0}]},"finish_reason":null,"native_finish_reason":null}]} - -data: {"id":"gen-1791215160-CvDN4CyWOR2sGTk9s3co","object":"chat.completion.chunk","created":1791215160,"model":"qwen/qwen3-30b-a3b","provider":"DeepInfra","choices":[{"index":0,"delta":{"content":"","role":"assistant","reasoning":"Research","reasoning_details":[{"type":"reasoning.text","text":"Research","format":"unknown","index":0}]},"finish_reason":null,"native_finish_reason":null}]} - -data: {"id":"gen-1791215160-CvDN4CyWOR2sGTk9s3co","object":"chat.completion.chunk","created":1791215160,"model":"qwen/qwen3-30b-a3b","provider":"DeepInfra","choices":[{"index":0,"delta":{"content":"","role":"assistant","reasoning":"er","reasoning_details":[{"type":"reasoning.text","text":"er","format":"unknown","index":0}]},"finish_reason":null,"native_finish_reason":null}]} - -data: {"id":"gen-1791215160-CvDN4CyWOR2sGTk9s3co","object":"chat.completion.chunk","created":1791215160,"model":"qwen/qwen3-30b-a3b","provider":"DeepInfra","choices":[{"index":0,"delta":{"content":"","role":"assistant","reasoning":" result","reasoning_details":[{"type":"reasoning.text","text":" result","format":"unknown","index":0}]},"finish_reason":null,"native_finish_reason":null}]} - -data: {"id":"gen-1791215160-CvDN4CyWOR2sGTk9s3co","object":"chat.completion.chunk","created":1791215160,"model":"qwen/qwen3-30b-a3b","provider":"DeepInfra","choices":[{"index":0,"delta":{"content":"","role":"assistant","reasoning":":\"","reasoning_details":[{"type":"reasoning.text","text":":\"","format":"unknown","index":0}]},"finish_reason":null,"native_finish_reason":null}]} - -data: {"id":"gen-1791215160-CvDN4CyWOR2sGTk9s3co","object":"chat.completion.chunk","created":1791215160,"model":"qwen/qwen3-30b-a3b","provider":"DeepInfra","choices":[{"index":0,"delta":{"content":"","role":"assistant","reasoning":" followed","reasoning_details":[{"type":"reasoning.text","text":" followed","format":"unknown","index":0}]},"finish_reason":null,"native_finish_reason":null}]} - -data: {"id":"gen-1791215160-CvDN4CyWOR2sGTk9s3co","object":"chat.completion.chunk","created":1791215160,"model":"qwen/qwen3-30b-a3b","provider":"DeepInfra","choices":[{"index":0,"delta":{"content":"","role":"assistant","reasoning":" by","reasoning_details":[{"type":"reasoning.text","text":" by","format":"unknown","index":0}]},"finish_reason":null,"native_finish_reason":null}]} - -data: {"id":"gen-1791215160-CvDN4CyWOR2sGTk9s3co","object":"chat.completion.chunk","created":1791215160,"model":"qwen/qwen3-30b-a3b","provider":"DeepInfra","choices":[{"index":0,"delta":{"content":"","role":"assistant","reasoning":" the","reasoning_details":[{"type":"reasoning.text","text":" the","format":"unknown","index":0}]},"finish_reason":null,"native_finish_reason":null}]} - -data: {"id":"gen-1791215160-CvDN4CyWOR2sGTk9s3co","object":"chat.completion.chunk","created":1791215160,"model":"qwen/qwen3-30b-a3b","provider":"DeepInfra","choices":[{"index":0,"delta":{"content":"","role":"assistant","reasoning":" title","reasoning_details":[{"type":"reasoning.text","text":" title","format":"unknown","index":0}]},"finish_reason":null,"native_finish_reason":null}]} - -data: {"id":"gen-1791215160-CvDN4CyWOR2sGTk9s3co","object":"chat.completion.chunk","created":1791215160,"model":"qwen/qwen3-30b-a3b","provider":"DeepInfra","choices":[{"index":0,"delta":{"content":"","role":"assistant","reasoning":" and","reasoning_details":[{"type":"reasoning.text","text":" and","format":"unknown","index":0}]},"finish_reason":null,"native_finish_reason":null}]} - -data: {"id":"gen-1791215160-CvDN4CyWOR2sGTk9s3co","object":"chat.completion.chunk","created":1791215160,"model":"qwen/qwen3-30b-a3b","provider":"DeepInfra","choices":[{"index":0,"delta":{"content":"","role":"assistant","reasoning":" URL","reasoning_details":[{"type":"reasoning.text","text":" URL","format":"unknown","index":0}]},"finish_reason":null,"native_finish_reason":null}]} - -data: {"id":"gen-1791215160-CvDN4CyWOR2sGTk9s3co","object":"chat.completion.chunk","created":1791215160,"model":"qwen/qwen3-30b-a3b","provider":"DeepInfra","choices":[{"index":0,"delta":{"content":"","role":"assistant","reasoning":".","reasoning_details":[{"type":"reasoning.text","text":".","format":"unknown","index":0}]},"finish_reason":null,"native_finish_reason":null}]} - -data: {"id":"gen-1791215160-CvDN4CyWOR2sGTk9s3co","object":"chat.completion.chunk","created":1791215160,"model":"qwen/qwen3-30b-a3b","provider":"DeepInfra","choices":[{"index":0,"delta":{"content":"","role":"assistant","reasoning":" I","reasoning_details":[{"type":"reasoning.text","text":" I","format":"unknown","index":0}]},"finish_reason":null,"native_finish_reason":null}]} - -data: {"id":"gen-1791215160-CvDN4CyWOR2sGTk9s3co","object":"chat.completion.chunk","created":1791215160,"model":"qwen/qwen3-30b-a3b","provider":"DeepInfra","choices":[{"index":0,"delta":{"content":"","role":"assistant","reasoning":" need","reasoning_details":[{"type":"reasoning.text","text":" need","format":"unknown","index":0}]},"finish_reason":null,"native_finish_reason":null}]} - -data: {"id":"gen-1791215160-CvDN4CyWOR2sGTk9s3co","object":"chat.completion.chunk","created":1791215160,"model":"qwen/qwen3-30b-a3b","provider":"DeepInfra","choices":[{"index":0,"delta":{"content":"","role":"assistant","reasoning":" to","reasoning_details":[{"type":"reasoning.text","text":" to","format":"unknown","index":0}]},"finish_reason":null,"native_finish_reason":null}]} - -data: {"id":"gen-1791215160-CvDN4CyWOR2sGTk9s3co","object":"chat.completion.chunk","created":1791215160,"model":"qwen/qwen3-30b-a3b","provider":"DeepInfra","choices":[{"index":0,"delta":{"content":"","role":"assistant","reasoning":" make","reasoning_details":[{"type":"reasoning.text","text":" make","format":"unknown","index":0}]},"finish_reason":null,"native_finish_reason":null}]} - -data: {"id":"gen-1791215160-CvDN4CyWOR2sGTk9s3co","object":"chat.completion.chunk","created":1791215160,"model":"qwen/qwen3-30b-a3b","provider":"DeepInfra","choices":[{"index":0,"delta":{"content":"","role":"assistant","reasoning":" sure","reasoning_details":[{"type":"reasoning.text","text":" sure","format":"unknown","index":0}]},"finish_reason":null,"native_finish_reason":null}]} - -data: {"id":"gen-1791215160-CvDN4CyWOR2sGTk9s3co","object":"chat.completion.chunk","created":1791215160,"model":"qwen/qwen3-30b-a3b","provider":"DeepInfra","choices":[{"index":0,"delta":{"content":"","role":"assistant","reasoning":" it","reasoning_details":[{"type":"reasoning.text","text":" it","format":"unknown","index":0}]},"finish_reason":null,"native_finish_reason":null}]} - -data: {"id":"gen-1791215160-CvDN4CyWOR2sGTk9s3co","object":"chat.completion.chunk","created":1791215160,"model":"qwen/qwen3-30b-a3b","provider":"DeepInfra","choices":[{"index":0,"delta":{"content":"","role":"assistant","reasoning":"'s","reasoning_details":[{"type":"reasoning.text","text":"'s","format":"unknown","index":0}]},"finish_reason":null,"native_finish_reason":null}]} - -data: {"id":"gen-1791215160-CvDN4CyWOR2sGTk9s3co","object":"chat.completion.chunk","created":1791215160,"model":"qwen/qwen3-30b-a3b","provider":"DeepInfra","choices":[{"index":0,"delta":{"content":"","role":"assistant","reasoning":" a","reasoning_details":[{"type":"reasoning.text","text":" a","format":"unknown","index":0}]},"finish_reason":null,"native_finish_reason":null}]} - -data: {"id":"gen-1791215160-CvDN4CyWOR2sGTk9s3co","object":"chat.completion.chunk","created":1791215160,"model":"qwen/qwen3-30b-a3b","provider":"DeepInfra","choices":[{"index":0,"delta":{"content":"","role":"assistant","reasoning":" single","reasoning_details":[{"type":"reasoning.text","text":" single","format":"unknown","index":0}]},"finish_reason":null,"native_finish_reason":null}]} - -data: {"id":"gen-1791215160-CvDN4CyWOR2sGTk9s3co","object":"chat.completion.chunk","created":1791215160,"model":"qwen/qwen3-30b-a3b","provider":"DeepInfra","choices":[{"index":0,"delta":{"content":"","role":"assistant","reasoning":" sentence","reasoning_details":[{"type":"reasoning.text","text":" sentence","format":"unknown","index":0}]},"finish_reason":null,"native_finish_reason":null}]} - -data: {"id":"gen-1791215160-CvDN4CyWOR2sGTk9s3co","object":"chat.completion.chunk","created":1791215160,"model":"qwen/qwen3-30b-a3b","provider":"DeepInfra","choices":[{"index":0,"delta":{"content":"","role":"assistant","reasoning":".","reasoning_details":[{"type":"reasoning.text","text":".","format":"unknown","index":0}]},"finish_reason":null,"native_finish_reason":null}]} - -data: {"id":"gen-1791215160-CvDN4CyWOR2sGTk9s3co","object":"chat.completion.chunk","created":1791215160,"model":"qwen/qwen3-30b-a3b","provider":"DeepInfra","choices":[{"index":0,"delta":{"content":"","role":"assistant","reasoning":" The","reasoning_details":[{"type":"reasoning.text","text":" The","format":"unknown","index":0}]},"finish_reason":null,"native_finish_reason":null}]} - -data: {"id":"gen-1791215160-CvDN4CyWOR2sGTk9s3co","object":"chat.completion.chunk","created":1791215160,"model":"qwen/qwen3-30b-a3b","provider":"DeepInfra","choices":[{"index":0,"delta":{"content":"","role":"assistant","reasoning":" example","reasoning_details":[{"type":"reasoning.text","text":" example","format":"unknown","index":0}]},"finish_reason":null,"native_finish_reason":null}]} - -data: {"id":"gen-1791215160-CvDN4CyWOR2sGTk9s3co","object":"chat.completion.chunk","created":1791215160,"model":"qwen/qwen3-30b-a3b","provider":"DeepInfra","choices":[{"index":0,"delta":{"content":"","role":"assistant","reasoning":" given","reasoning_details":[{"type":"reasoning.text","text":" given","format":"unknown","index":0}]},"finish_reason":null,"native_finish_reason":null}]} - -data: {"id":"gen-1791215160-CvDN4CyWOR2sGTk9s3co","object":"chat.completion.chunk","created":1791215160,"model":"qwen/qwen3-30b-a3b","provider":"DeepInfra","choices":[{"index":0,"delta":{"content":"","role":"assistant","reasoning":" in","reasoning_details":[{"type":"reasoning.text","text":" in","format":"unknown","index":0}]},"finish_reason":null,"native_finish_reason":null}]} - -data: {"id":"gen-1791215160-CvDN4CyWOR2sGTk9s3co","object":"chat.completion.chunk","created":1791215160,"model":"qwen/qwen3-30b-a3b","provider":"DeepInfra","choices":[{"index":0,"delta":{"content":"","role":"assistant","reasoning":" the","reasoning_details":[{"type":"reasoning.text","text":" the","format":"unknown","index":0}]},"finish_reason":null,"native_finish_reason":null}]} - -data: {"id":"gen-1791215160-CvDN4CyWOR2sGTk9s3co","object":"chat.completion.chunk","created":1791215160,"model":"qwen/qwen3-30b-a3b","provider":"DeepInfra","choices":[{"index":0,"delta":{"content":"","role":"assistant","reasoning":" instructions","reasoning_details":[{"type":"reasoning.text","text":" instructions","format":"unknown","index":0}]},"finish_reason":null,"native_finish_reason":null}]} - -data: {"id":"gen-1791215160-CvDN4CyWOR2sGTk9s3co","object":"chat.completion.chunk","created":1791215160,"model":"qwen/qwen3-30b-a3b","provider":"DeepInfra","choices":[{"index":0,"delta":{"content":"","role":"assistant","reasoning":" uses","reasoning_details":[{"type":"reasoning.text","text":" uses","format":"unknown","index":0}]},"finish_reason":null,"native_finish_reason":null}]} - -data: {"id":"gen-1791215160-CvDN4CyWOR2sGTk9s3co","object":"chat.completion.chunk","created":1791215160,"model":"qwen/qwen3-30b-a3b","provider":"DeepInfra","choices":[{"index":0,"delta":{"content":"","role":"assistant","reasoning":" the","reasoning_details":[{"type":"reasoning.text","text":" the","format":"unknown","index":0}]},"finish_reason":null,"native_finish_reason":null}]} - -data: {"id":"gen-1791215160-CvDN4CyWOR2sGTk9s3co","object":"chat.completion.chunk","created":1791215160,"model":"qwen/qwen3-30b-a3b","provider":"DeepInfra","choices":[{"index":0,"delta":{"content":"","role":"assistant","reasoning":" search","reasoning_details":[{"type":"reasoning.text","text":" search","format":"unknown","index":0}]},"finish_reason":null,"native_finish_reason":null}]} - -data: {"id":"gen-1791215160-CvDN4CyWOR2sGTk9s3co","object":"chat.completion.chunk","created":1791215160,"model":"qwen/qwen3-30b-a3b","provider":"DeepInfra","choices":[{"index":0,"delta":{"content":"","role":"assistant","reasoning":" title","reasoning_details":[{"type":"reasoning.text","text":" title","format":"unknown","index":0}]},"finish_reason":null,"native_finish_reason":null}]} - -data: {"id":"gen-1791215160-CvDN4CyWOR2sGTk9s3co","object":"chat.completion.chunk","created":1791215160,"model":"qwen/qwen3-30b-a3b","provider":"DeepInfra","choices":[{"index":0,"delta":{"content":"","role":"assistant","reasoning":" and","reasoning_details":[{"type":"reasoning.text","text":" and","format":"unknown","index":0}]},"finish_reason":null,"native_finish_reason":null}]} - -data: {"id":"gen-1791215160-CvDN4CyWOR2sGTk9s3co","object":"chat.completion.chunk","created":1791215160,"model":"qwen/qwen3-30b-a3b","provider":"DeepInfra","choices":[{"index":0,"delta":{"content":"","role":"assistant","reasoning":" URL","reasoning_details":[{"type":"reasoning.text","text":" URL","format":"unknown","index":0}]},"finish_reason":null,"native_finish_reason":null}]} - -data: {"id":"gen-1791215160-CvDN4CyWOR2sGTk9s3co","object":"chat.completion.chunk","created":1791215160,"model":"qwen/qwen3-30b-a3b","provider":"DeepInfra","choices":[{"index":0,"delta":{"content":"","role":"assistant","reasoning":",","reasoning_details":[{"type":"reasoning.text","text":",","format":"unknown","index":0}]},"finish_reason":null,"native_finish_reason":null}]} - -data: {"id":"gen-1791215160-CvDN4CyWOR2sGTk9s3co","object":"chat.completion.chunk","created":1791215160,"model":"qwen/qwen3-30b-a3b","provider":"DeepInfra","choices":[{"index":0,"delta":{"content":"","role":"assistant","reasoning":" so","reasoning_details":[{"type":"reasoning.text","text":" so","format":"unknown","index":0}]},"finish_reason":null,"native_finish_reason":null}]} - -data: {"id":"gen-1791215160-CvDN4CyWOR2sGTk9s3co","object":"chat.completion.chunk","created":1791215160,"model":"qwen/qwen3-30b-a3b","provider":"DeepInfra","choices":[{"index":0,"delta":{"content":"","role":"assistant","reasoning":" I","reasoning_details":[{"type":"reasoning.text","text":" I","format":"unknown","index":0}]},"finish_reason":null,"native_finish_reason":null}]} - -data: {"id":"gen-1791215160-CvDN4CyWOR2sGTk9s3co","object":"chat.completion.chunk","created":1791215160,"model":"qwen/qwen3-30b-a3b","provider":"DeepInfra","choices":[{"index":0,"delta":{"content":"","role":"assistant","reasoning":"'ll","reasoning_details":[{"type":"reasoning.text","text":"'ll","format":"unknown","index":0}]},"finish_reason":null,"native_finish_reason":null}]} - -data: {"id":"gen-1791215160-CvDN4CyWOR2sGTk9s3co","object":"chat.completion.chunk","created":1791215160,"model":"qwen/qwen3-30b-a3b","provider":"DeepInfra","choices":[{"index":0,"delta":{"content":"","role":"assistant","reasoning":" follow","reasoning_details":[{"type":"reasoning.text","text":" follow","format":"unknown","index":0}]},"finish_reason":null,"native_finish_reason":null}]} - -data: {"id":"gen-1791215160-CvDN4CyWOR2sGTk9s3co","object":"chat.completion.chunk","created":1791215160,"model":"qwen/qwen3-30b-a3b","provider":"DeepInfra","choices":[{"index":0,"delta":{"content":"","role":"assistant","reasoning":" that","reasoning_details":[{"type":"reasoning.text","text":" that","format":"unknown","index":0}]},"finish_reason":null,"native_finish_reason":null}]} - -data: {"id":"gen-1791215160-CvDN4CyWOR2sGTk9s3co","object":"chat.completion.chunk","created":1791215160,"model":"qwen/qwen3-30b-a3b","provider":"DeepInfra","choices":[{"index":0,"delta":{"content":"","role":"assistant","reasoning":" structure","reasoning_details":[{"type":"reasoning.text","text":" structure","format":"unknown","index":0}]},"finish_reason":null,"native_finish_reason":null}]} - -data: {"id":"gen-1791215160-CvDN4CyWOR2sGTk9s3co","object":"chat.completion.chunk","created":1791215160,"model":"qwen/qwen3-30b-a3b","provider":"DeepInfra","choices":[{"index":0,"delta":{"content":"","role":"assistant","reasoning":".","reasoning_details":[{"type":"reasoning.text","text":".","format":"unknown","index":0}]},"finish_reason":null,"native_finish_reason":null}]} - -data: {"id":"gen-1791215160-CvDN4CyWOR2sGTk9s3co","object":"chat.completion.chunk","created":1791215160,"model":"qwen/qwen3-30b-a3b","provider":"DeepInfra","choices":[{"index":0,"delta":{"content":"","role":"assistant","reasoning":" No","reasoning_details":[{"type":"reasoning.text","text":" No","format":"unknown","index":0}]},"finish_reason":null,"native_finish_reason":null}]} - -data: {"id":"gen-1791215160-CvDN4CyWOR2sGTk9s3co","object":"chat.completion.chunk","created":1791215160,"model":"qwen/qwen3-30b-a3b","provider":"DeepInfra","choices":[{"index":0,"delta":{"content":"","role":"assistant","reasoning":" other","reasoning_details":[{"type":"reasoning.text","text":" other","format":"unknown","index":0}]},"finish_reason":null,"native_finish_reason":null}]} - -data: {"id":"gen-1791215160-CvDN4CyWOR2sGTk9s3co","object":"chat.completion.chunk","created":1791215160,"model":"qwen/qwen3-30b-a3b","provider":"DeepInfra","choices":[{"index":0,"delta":{"content":"","role":"assistant","reasoning":" tools","reasoning_details":[{"type":"reasoning.text","text":" tools","format":"unknown","index":0}]},"finish_reason":null,"native_finish_reason":null}]} - -data: {"id":"gen-1791215160-CvDN4CyWOR2sGTk9s3co","object":"chat.completion.chunk","created":1791215160,"model":"qwen/qwen3-30b-a3b","provider":"DeepInfra","choices":[{"index":0,"delta":{"content":"","role":"assistant","reasoning":" are","reasoning_details":[{"type":"reasoning.text","text":" are","format":"unknown","index":0}]},"finish_reason":null,"native_finish_reason":null}]} - -data: {"id":"gen-1791215160-CvDN4CyWOR2sGTk9s3co","object":"chat.completion.chunk","created":1791215160,"model":"qwen/qwen3-30b-a3b","provider":"DeepInfra","choices":[{"index":0,"delta":{"content":"","role":"assistant","reasoning":" needed","reasoning_details":[{"type":"reasoning.text","text":" needed","format":"unknown","index":0}]},"finish_reason":null,"native_finish_reason":null}]} - -data: {"id":"gen-1791215160-CvDN4CyWOR2sGTk9s3co","object":"chat.completion.chunk","created":1791215160,"model":"qwen/qwen3-30b-a3b","provider":"DeepInfra","choices":[{"index":0,"delta":{"content":"","role":"assistant","reasoning":" here","reasoning_details":[{"type":"reasoning.text","text":" here","format":"unknown","index":0}]},"finish_reason":null,"native_finish_reason":null}]} - -data: {"id":"gen-1791215160-CvDN4CyWOR2sGTk9s3co","object":"chat.completion.chunk","created":1791215160,"model":"qwen/qwen3-30b-a3b","provider":"DeepInfra","choices":[{"index":0,"delta":{"content":"","role":"assistant","reasoning":" since","reasoning_details":[{"type":"reasoning.text","text":" since","format":"unknown","index":0}]},"finish_reason":null,"native_finish_reason":null}]} - -data: {"id":"gen-1791215160-CvDN4CyWOR2sGTk9s3co","object":"chat.completion.chunk","created":1791215160,"model":"qwen/qwen3-30b-a3b","provider":"DeepInfra","choices":[{"index":0,"delta":{"content":"","role":"assistant","reasoning":" the","reasoning_details":[{"type":"reasoning.text","text":" the","format":"unknown","index":0}]},"finish_reason":null,"native_finish_reason":null}]} - -data: {"id":"gen-1791215160-CvDN4CyWOR2sGTk9s3co","object":"chat.completion.chunk","created":1791215160,"model":"qwen/qwen3-30b-a3b","provider":"DeepInfra","choices":[{"index":0,"delta":{"content":"","role":"assistant","reasoning":" search","reasoning_details":[{"type":"reasoning.text","text":" search","format":"unknown","index":0}]},"finish_reason":null,"native_finish_reason":null}]} - -data: {"id":"gen-1791215160-CvDN4CyWOR2sGTk9s3co","object":"chat.completion.chunk","created":1791215160,"model":"qwen/qwen3-30b-a3b","provider":"DeepInfra","choices":[{"index":0,"delta":{"content":"","role":"assistant","reasoning":" provided","reasoning_details":[{"type":"reasoning.text","text":" provided","format":"unknown","index":0}]},"finish_reason":null,"native_finish_reason":null}]} - -data: {"id":"gen-1791215160-CvDN4CyWOR2sGTk9s3co","object":"chat.completion.chunk","created":1791215160,"model":"qwen/qwen3-30b-a3b","provider":"DeepInfra","choices":[{"index":0,"delta":{"content":"","role":"assistant","reasoning":" the","reasoning_details":[{"type":"reasoning.text","text":" the","format":"unknown","index":0}]},"finish_reason":null,"native_finish_reason":null}]} - -data: {"id":"gen-1791215160-CvDN4CyWOR2sGTk9s3co","object":"chat.completion.chunk","created":1791215160,"model":"qwen/qwen3-30b-a3b","provider":"DeepInfra","choices":[{"index":0,"delta":{"content":"","role":"assistant","reasoning":" necessary","reasoning_details":[{"type":"reasoning.text","text":" necessary","format":"unknown","index":0}]},"finish_reason":null,"native_finish_reason":null}]} - -data: {"id":"gen-1791215160-CvDN4CyWOR2sGTk9s3co","object":"chat.completion.chunk","created":1791215160,"model":"qwen/qwen3-30b-a3b","provider":"DeepInfra","choices":[{"index":0,"delta":{"content":"","role":"assistant","reasoning":" information","reasoning_details":[{"type":"reasoning.text","text":" information","format":"unknown","index":0}]},"finish_reason":null,"native_finish_reason":null}]} - -data: {"id":"gen-1791215160-CvDN4CyWOR2sGTk9s3co","object":"chat.completion.chunk","created":1791215160,"model":"qwen/qwen3-30b-a3b","provider":"DeepInfra","choices":[{"index":0,"delta":{"content":"","role":"assistant","reasoning":".","reasoning_details":[{"type":"reasoning.text","text":".","format":"unknown","index":0}]},"finish_reason":null,"native_finish_reason":null}]} - -data: {"id":"gen-1791215160-CvDN4CyWOR2sGTk9s3co","object":"chat.completion.chunk","created":1791215160,"model":"qwen/qwen3-30b-a3b","provider":"DeepInfra","choices":[{"index":0,"delta":{"content":"","role":"assistant","reasoning":" Just","reasoning_details":[{"type":"reasoning.text","text":" Just","format":"unknown","index":0}]},"finish_reason":null,"native_finish_reason":null}]} - -data: {"id":"gen-1791215160-CvDN4CyWOR2sGTk9s3co","object":"chat.completion.chunk","created":1791215160,"model":"qwen/qwen3-30b-a3b","provider":"DeepInfra","choices":[{"index":0,"delta":{"content":"","role":"assistant","reasoning":" present","reasoning_details":[{"type":"reasoning.text","text":" present","format":"unknown","index":0}]},"finish_reason":null,"native_finish_reason":null}]} - -data: {"id":"gen-1791215160-CvDN4CyWOR2sGTk9s3co","object":"chat.completion.chunk","created":1791215160,"model":"qwen/qwen3-30b-a3b","provider":"DeepInfra","choices":[{"index":0,"delta":{"content":"","role":"assistant","reasoning":" the","reasoning_details":[{"type":"reasoning.text","text":" the","format":"unknown","index":0}]},"finish_reason":null,"native_finish_reason":null}]} - -data: {"id":"gen-1791215160-CvDN4CyWOR2sGTk9s3co","object":"chat.completion.chunk","created":1791215160,"model":"qwen/qwen3-30b-a3b","provider":"DeepInfra","choices":[{"index":0,"delta":{"content":"","role":"assistant","reasoning":" result","reasoning_details":[{"type":"reasoning.text","text":" result","format":"unknown","index":0}]},"finish_reason":null,"native_finish_reason":null}]} - -data: {"id":"gen-1791215160-CvDN4CyWOR2sGTk9s3co","object":"chat.completion.chunk","created":1791215160,"model":"qwen/qwen3-30b-a3b","provider":"DeepInfra","choices":[{"index":0,"delta":{"content":"","role":"assistant","reasoning":" clearly","reasoning_details":[{"type":"reasoning.text","text":" clearly","format":"unknown","index":0}]},"finish_reason":null,"native_finish_reason":null}]} - -data: {"id":"gen-1791215160-CvDN4CyWOR2sGTk9s3co","object":"chat.completion.chunk","created":1791215160,"model":"qwen/qwen3-30b-a3b","provider":"DeepInfra","choices":[{"index":0,"delta":{"content":"","role":"assistant","reasoning":".\n","reasoning_details":[{"type":"reasoning.text","text":".\n","format":"unknown","index":0}]},"finish_reason":null,"native_finish_reason":null}]} - -data: {"id":"gen-1791215160-CvDN4CyWOR2sGTk9s3co","object":"chat.completion.chunk","created":1791215160,"model":"qwen/qwen3-30b-a3b","provider":"DeepInfra","choices":[{"index":0,"delta":{"content":"Research","role":"assistant"},"finish_reason":null,"native_finish_reason":null}]} - -data: {"id":"gen-1791215160-CvDN4CyWOR2sGTk9s3co","object":"chat.completion.chunk","created":1791215160,"model":"qwen/qwen3-30b-a3b","provider":"DeepInfra","choices":[{"index":0,"delta":{"content":"er","role":"assistant"},"finish_reason":null,"native_finish_reason":null}]} - -data: {"id":"gen-1791215160-CvDN4CyWOR2sGTk9s3co","object":"chat.completion.chunk","created":1791215160,"model":"qwen/qwen3-30b-a3b","provider":"DeepInfra","choices":[{"index":0,"delta":{"content":" result","role":"assistant"},"finish_reason":null,"native_finish_reason":null}]} - -data: {"id":"gen-1791215160-CvDN4CyWOR2sGTk9s3co","object":"chat.completion.chunk","created":1791215160,"model":"qwen/qwen3-30b-a3b","provider":"DeepInfra","choices":[{"index":0,"delta":{"content":":","role":"assistant"},"finish_reason":null,"native_finish_reason":null}]} - -data: {"id":"gen-1791215160-CvDN4CyWOR2sGTk9s3co","object":"chat.completion.chunk","created":1791215160,"model":"qwen/qwen3-30b-a3b","provider":"DeepInfra","choices":[{"index":0,"delta":{"content":" Eve","role":"assistant"},"finish_reason":null,"native_finish_reason":null}]} - -data: {"id":"gen-1791215160-CvDN4CyWOR2sGTk9s3co","object":"chat.completion.chunk","created":1791215160,"model":"qwen/qwen3-30b-a3b","provider":"DeepInfra","choices":[{"index":0,"delta":{"content":" instrumentation","role":"assistant"},"finish_reason":null,"native_finish_reason":null}]} - -data: {"id":"gen-1791215160-CvDN4CyWOR2sGTk9s3co","object":"chat.completion.chunk","created":1791215160,"model":"qwen/qwen3-30b-a3b","provider":"DeepInfra","choices":[{"index":0,"delta":{"content":" https","role":"assistant"},"finish_reason":null,"native_finish_reason":null}]} - -data: {"id":"gen-1791215160-CvDN4CyWOR2sGTk9s3co","object":"chat.completion.chunk","created":1791215160,"model":"qwen/qwen3-30b-a3b","provider":"DeepInfra","choices":[{"index":0,"delta":{"content":"://","role":"assistant"},"finish_reason":null,"native_finish_reason":null}]} - -data: {"id":"gen-1791215160-CvDN4CyWOR2sGTk9s3co","object":"chat.completion.chunk","created":1791215160,"model":"qwen/qwen3-30b-a3b","provider":"DeepInfra","choices":[{"index":0,"delta":{"content":"eve","role":"assistant"},"finish_reason":null,"native_finish_reason":null}]} - -data: {"id":"gen-1791215160-CvDN4CyWOR2sGTk9s3co","object":"chat.completion.chunk","created":1791215160,"model":"qwen/qwen3-30b-a3b","provider":"DeepInfra","choices":[{"index":0,"delta":{"content":".dev","role":"assistant"},"finish_reason":null,"native_finish_reason":null}]} - -data: {"id":"gen-1791215160-CvDN4CyWOR2sGTk9s3co","object":"chat.completion.chunk","created":1791215160,"model":"qwen/qwen3-30b-a3b","provider":"DeepInfra","choices":[{"index":0,"delta":{"content":"/docs","role":"assistant"},"finish_reason":null,"native_finish_reason":null}]} - -data: {"id":"gen-1791215160-CvDN4CyWOR2sGTk9s3co","object":"chat.completion.chunk","created":1791215160,"model":"qwen/qwen3-30b-a3b","provider":"DeepInfra","choices":[{"index":0,"delta":{"content":"/g","role":"assistant"},"finish_reason":null,"native_finish_reason":null}]} - -data: {"id":"gen-1791215160-CvDN4CyWOR2sGTk9s3co","object":"chat.completion.chunk","created":1791215160,"model":"qwen/qwen3-30b-a3b","provider":"DeepInfra","choices":[{"index":0,"delta":{"content":"uid","role":"assistant"},"finish_reason":null,"native_finish_reason":null}]} - -data: {"id":"gen-1791215160-CvDN4CyWOR2sGTk9s3co","object":"chat.completion.chunk","created":1791215160,"model":"qwen/qwen3-30b-a3b","provider":"DeepInfra","choices":[{"index":0,"delta":{"content":"es","role":"assistant"},"finish_reason":null,"native_finish_reason":null}]} - -data: {"id":"gen-1791215160-CvDN4CyWOR2sGTk9s3co","object":"chat.completion.chunk","created":1791215160,"model":"qwen/qwen3-30b-a3b","provider":"DeepInfra","choices":[{"index":0,"delta":{"content":"/in","role":"assistant"},"finish_reason":null,"native_finish_reason":null}]} - -data: {"id":"gen-1791215160-CvDN4CyWOR2sGTk9s3co","object":"chat.completion.chunk","created":1791215160,"model":"qwen/qwen3-30b-a3b","provider":"DeepInfra","choices":[{"index":0,"delta":{"content":"strument","role":"assistant"},"finish_reason":null,"native_finish_reason":null}]} - -data: {"id":"gen-1791215160-CvDN4CyWOR2sGTk9s3co","object":"chat.completion.chunk","created":1791215160,"model":"qwen/qwen3-30b-a3b","provider":"DeepInfra","choices":[{"index":0,"delta":{"content":"ation","role":"assistant"},"finish_reason":null,"native_finish_reason":null}]} - -data: {"id":"gen-1791215160-CvDN4CyWOR2sGTk9s3co","object":"chat.completion.chunk","created":1791215160,"model":"qwen/qwen3-30b-a3b","provider":"DeepInfra","choices":[{"index":0,"delta":{"content":"","role":"assistant","reasoning":null},"finish_reason":"stop","native_finish_reason":"stop"}]} - -data: {"id":"gen-1791215160-CvDN4CyWOR2sGTk9s3co","object":"chat.completion.chunk","created":1791215160,"model":"qwen/qwen3-30b-a3b","provider":"DeepInfra","service_tier":"default","choices":[{"index":0,"delta":{"content":"","role":"assistant"},"finish_reason":"stop","native_finish_reason":"stop"}],"usage":{"prompt_tokens":3369,"completion_tokens":193,"total_tokens":3562,"cost":0.00050078,"is_byok":false,"prompt_tokens_details":{"cached_tokens":0,"cache_write_tokens":0,"audio_tokens":0,"video_tokens":0},"cost_details":{"upstream_inference_cost":0.00050078,"upstream_inference_prompt_cost":0.00040428,"upstream_inference_completions_cost":0.0000965},"completion_tokens_details":{"reasoning_tokens":212,"image_tokens":0,"audio_tokens":0}}} - -data: [DONE] - diff --git a/e2e/scenarios/eve-instrumentation/__cassettes__/eve-v0.cassette.blobs/bd499a7b095943c5518ebb65b2d81ab0674e63dc823e3fbac6e5106bcc1e3861.bin b/e2e/scenarios/eve-instrumentation/__cassettes__/eve-v0.cassette.blobs/bd499a7b095943c5518ebb65b2d81ab0674e63dc823e3fbac6e5106bcc1e3861.bin deleted file mode 100644 index 5902aaeb4..000000000 --- a/e2e/scenarios/eve-instrumentation/__cassettes__/eve-v0.cassette.blobs/bd499a7b095943c5518ebb65b2d81ab0674e63dc823e3fbac6e5106bcc1e3861.bin +++ /dev/null @@ -1,462 +0,0 @@ -data: {"id":"gen-1791215170-rEB9MwYfzfLp1szWk4jz","object":"chat.completion.chunk","created":1791215170,"model":"qwen/qwen3-30b-a3b","provider":"DeepInfra","choices":[{"index":0,"delta":{"content":"","role":"assistant","reasoning":"\n","reasoning_details":[{"type":"reasoning.text","text":"\n","format":"unknown","index":0}]},"finish_reason":null,"native_finish_reason":null}]} - -data: {"id":"gen-1791215170-rEB9MwYfzfLp1szWk4jz","object":"chat.completion.chunk","created":1791215170,"model":"qwen/qwen3-30b-a3b","provider":"DeepInfra","choices":[{"index":0,"delta":{"content":"","role":"assistant","reasoning":"Okay","reasoning_details":[{"type":"reasoning.text","text":"Okay","format":"unknown","index":0}]},"finish_reason":null,"native_finish_reason":null}]} - -data: {"id":"gen-1791215170-rEB9MwYfzfLp1szWk4jz","object":"chat.completion.chunk","created":1791215170,"model":"qwen/qwen3-30b-a3b","provider":"DeepInfra","choices":[{"index":0,"delta":{"content":"","role":"assistant","reasoning":",","reasoning_details":[{"type":"reasoning.text","text":",","format":"unknown","index":0}]},"finish_reason":null,"native_finish_reason":null}]} - -data: {"id":"gen-1791215170-rEB9MwYfzfLp1szWk4jz","object":"chat.completion.chunk","created":1791215170,"model":"qwen/qwen3-30b-a3b","provider":"DeepInfra","choices":[{"index":0,"delta":{"content":"","role":"assistant","reasoning":" the","reasoning_details":[{"type":"reasoning.text","text":" the","format":"unknown","index":0}]},"finish_reason":null,"native_finish_reason":null}]} - -data: {"id":"gen-1791215170-rEB9MwYfzfLp1szWk4jz","object":"chat.completion.chunk","created":1791215170,"model":"qwen/qwen3-30b-a3b","provider":"DeepInfra","choices":[{"index":0,"delta":{"content":"","role":"assistant","reasoning":" user","reasoning_details":[{"type":"reasoning.text","text":" user","format":"unknown","index":0}]},"finish_reason":null,"native_finish_reason":null}]} - -data: {"id":"gen-1791215170-rEB9MwYfzfLp1szWk4jz","object":"chat.completion.chunk","created":1791215170,"model":"qwen/qwen3-30b-a3b","provider":"DeepInfra","choices":[{"index":0,"delta":{"content":"","role":"assistant","reasoning":" wants","reasoning_details":[{"type":"reasoning.text","text":" wants","format":"unknown","index":0}]},"finish_reason":null,"native_finish_reason":null}]} - -data: {"id":"gen-1791215170-rEB9MwYfzfLp1szWk4jz","object":"chat.completion.chunk","created":1791215170,"model":"qwen/qwen3-30b-a3b","provider":"DeepInfra","choices":[{"index":0,"delta":{"content":"","role":"assistant","reasoning":" me","reasoning_details":[{"type":"reasoning.text","text":" me","format":"unknown","index":0}]},"finish_reason":null,"native_finish_reason":null}]} - -data: {"id":"gen-1791215170-rEB9MwYfzfLp1szWk4jz","object":"chat.completion.chunk","created":1791215170,"model":"qwen/qwen3-30b-a3b","provider":"DeepInfra","choices":[{"index":0,"delta":{"content":"","role":"assistant","reasoning":" to","reasoning_details":[{"type":"reasoning.text","text":" to","format":"unknown","index":0}]},"finish_reason":null,"native_finish_reason":null}]} - -data: {"id":"gen-1791215170-rEB9MwYfzfLp1szWk4jz","object":"chat.completion.chunk","created":1791215170,"model":"qwen/qwen3-30b-a3b","provider":"DeepInfra","choices":[{"index":0,"delta":{"content":"","role":"assistant","reasoning":" run","reasoning_details":[{"type":"reasoning.text","text":" run","format":"unknown","index":0}]},"finish_reason":null,"native_finish_reason":null}]} - -data: {"id":"gen-1791215170-rEB9MwYfzfLp1szWk4jz","object":"chat.completion.chunk","created":1791215170,"model":"qwen/qwen3-30b-a3b","provider":"DeepInfra","choices":[{"index":0,"delta":{"content":"","role":"assistant","reasoning":" the","reasoning_details":[{"type":"reasoning.text","text":" the","format":"unknown","index":0}]},"finish_reason":null,"native_finish_reason":null}]} - -data: {"id":"gen-1791215170-rEB9MwYfzfLp1szWk4jz","object":"chat.completion.chunk","created":1791215170,"model":"qwen/qwen3-30b-a3b","provider":"DeepInfra","choices":[{"index":0,"delta":{"content":"","role":"assistant","reasoning":" Brain","reasoning_details":[{"type":"reasoning.text","text":" Brain","format":"unknown","index":0}]},"finish_reason":null,"native_finish_reason":null}]} - -data: {"id":"gen-1791215170-rEB9MwYfzfLp1szWk4jz","object":"chat.completion.chunk","created":1791215170,"model":"qwen/qwen3-30b-a3b","provider":"DeepInfra","choices":[{"index":0,"delta":{"content":"","role":"assistant","reasoning":"trust","reasoning_details":[{"type":"reasoning.text","text":"trust","format":"unknown","index":0}]},"finish_reason":null,"native_finish_reason":null}]} - -data: {"id":"gen-1791215170-rEB9MwYfzfLp1szWk4jz","object":"chat.completion.chunk","created":1791215170,"model":"qwen/qwen3-30b-a3b","provider":"DeepInfra","choices":[{"index":0,"delta":{"content":"","role":"assistant","reasoning":" Eve","reasoning_details":[{"type":"reasoning.text","text":" Eve","format":"unknown","index":0}]},"finish_reason":null,"native_finish_reason":null}]} - -data: {"id":"gen-1791215170-rEB9MwYfzfLp1szWk4jz","object":"chat.completion.chunk","created":1791215170,"model":"qwen/qwen3-30b-a3b","provider":"DeepInfra","choices":[{"index":0,"delta":{"content":"","role":"assistant","reasoning":" instrumentation","reasoning_details":[{"type":"reasoning.text","text":" instrumentation","format":"unknown","index":0}]},"finish_reason":null,"native_finish_reason":null}]} - -data: {"id":"gen-1791215170-rEB9MwYfzfLp1szWk4jz","object":"chat.completion.chunk","created":1791215170,"model":"qwen/qwen3-30b-a3b","provider":"DeepInfra","choices":[{"index":0,"delta":{"content":"","role":"assistant","reasoning":" e","reasoning_details":[{"type":"reasoning.text","text":" e","format":"unknown","index":0}]},"finish_reason":null,"native_finish_reason":null}]} - -data: {"id":"gen-1791215170-rEB9MwYfzfLp1szWk4jz","object":"chat.completion.chunk","created":1791215170,"model":"qwen/qwen3-30b-a3b","provider":"DeepInfra","choices":[{"index":0,"delta":{"content":"","role":"assistant","reasoning":"2","reasoning_details":[{"type":"reasoning.text","text":"2","format":"unknown","index":0}]},"finish_reason":null,"native_finish_reason":null}]} - -data: {"id":"gen-1791215170-rEB9MwYfzfLp1szWk4jz","object":"chat.completion.chunk","created":1791215170,"model":"qwen/qwen3-30b-a3b","provider":"DeepInfra","choices":[{"index":0,"delta":{"content":"","role":"assistant","reasoning":"e","reasoning_details":[{"type":"reasoning.text","text":"e","format":"unknown","index":0}]},"finish_reason":null,"native_finish_reason":null}]} - -data: {"id":"gen-1791215170-rEB9MwYfzfLp1szWk4jz","object":"chat.completion.chunk","created":1791215170,"model":"qwen/qwen3-30b-a3b","provider":"DeepInfra","choices":[{"index":0,"delta":{"content":"","role":"assistant","reasoning":" scenario","reasoning_details":[{"type":"reasoning.text","text":" scenario","format":"unknown","index":0}]},"finish_reason":null,"native_finish_reason":null}]} - -data: {"id":"gen-1791215170-rEB9MwYfzfLp1szWk4jz","object":"chat.completion.chunk","created":1791215170,"model":"qwen/qwen3-30b-a3b","provider":"DeepInfra","choices":[{"index":0,"delta":{"content":"","role":"assistant","reasoning":" again","reasoning_details":[{"type":"reasoning.text","text":" again","format":"unknown","index":0}]},"finish_reason":null,"native_finish_reason":null}]} - -data: {"id":"gen-1791215170-rEB9MwYfzfLp1szWk4jz","object":"chat.completion.chunk","created":1791215170,"model":"qwen/qwen3-30b-a3b","provider":"DeepInfra","choices":[{"index":0,"delta":{"content":"","role":"assistant","reasoning":".","reasoning_details":[{"type":"reasoning.text","text":".","format":"unknown","index":0}]},"finish_reason":null,"native_finish_reason":null}]} - -data: {"id":"gen-1791215170-rEB9MwYfzfLp1szWk4jz","object":"chat.completion.chunk","created":1791215170,"model":"qwen/qwen3-30b-a3b","provider":"DeepInfra","choices":[{"index":0,"delta":{"content":"","role":"assistant","reasoning":" Let","reasoning_details":[{"type":"reasoning.text","text":" Let","format":"unknown","index":0}]},"finish_reason":null,"native_finish_reason":null}]} - -data: {"id":"gen-1791215170-rEB9MwYfzfLp1szWk4jz","object":"chat.completion.chunk","created":1791215170,"model":"qwen/qwen3-30b-a3b","provider":"DeepInfra","choices":[{"index":0,"delta":{"content":"","role":"assistant","reasoning":" me","reasoning_details":[{"type":"reasoning.text","text":" me","format":"unknown","index":0}]},"finish_reason":null,"native_finish_reason":null}]} - -data: {"id":"gen-1791215170-rEB9MwYfzfLp1szWk4jz","object":"chat.completion.chunk","created":1791215170,"model":"qwen/qwen3-30b-a3b","provider":"DeepInfra","choices":[{"index":0,"delta":{"content":"","role":"assistant","reasoning":" check","reasoning_details":[{"type":"reasoning.text","text":" check","format":"unknown","index":0}]},"finish_reason":null,"native_finish_reason":null}]} - -data: {"id":"gen-1791215170-rEB9MwYfzfLp1szWk4jz","object":"chat.completion.chunk","created":1791215170,"model":"qwen/qwen3-30b-a3b","provider":"DeepInfra","choices":[{"index":0,"delta":{"content":"","role":"assistant","reasoning":" the","reasoning_details":[{"type":"reasoning.text","text":" the","format":"unknown","index":0}]},"finish_reason":null,"native_finish_reason":null}]} - -data: {"id":"gen-1791215170-rEB9MwYfzfLp1szWk4jz","object":"chat.completion.chunk","created":1791215170,"model":"qwen/qwen3-30b-a3b","provider":"DeepInfra","choices":[{"index":0,"delta":{"content":"","role":"assistant","reasoning":" previous","reasoning_details":[{"type":"reasoning.text","text":" previous","format":"unknown","index":0}]},"finish_reason":null,"native_finish_reason":null}]} - -data: {"id":"gen-1791215170-rEB9MwYfzfLp1szWk4jz","object":"chat.completion.chunk","created":1791215170,"model":"qwen/qwen3-30b-a3b","provider":"DeepInfra","choices":[{"index":0,"delta":{"content":"","role":"assistant","reasoning":" interactions","reasoning_details":[{"type":"reasoning.text","text":" interactions","format":"unknown","index":0}]},"finish_reason":null,"native_finish_reason":null}]} - -data: {"id":"gen-1791215170-rEB9MwYfzfLp1szWk4jz","object":"chat.completion.chunk","created":1791215170,"model":"qwen/qwen3-30b-a3b","provider":"DeepInfra","choices":[{"index":0,"delta":{"content":"","role":"assistant","reasoning":" to","reasoning_details":[{"type":"reasoning.text","text":" to","format":"unknown","index":0}]},"finish_reason":null,"native_finish_reason":null}]} - -data: {"id":"gen-1791215170-rEB9MwYfzfLp1szWk4jz","object":"chat.completion.chunk","created":1791215170,"model":"qwen/qwen3-30b-a3b","provider":"DeepInfra","choices":[{"index":0,"delta":{"content":"","role":"assistant","reasoning":" make","reasoning_details":[{"type":"reasoning.text","text":" make","format":"unknown","index":0}]},"finish_reason":null,"native_finish_reason":null}]} - -data: {"id":"gen-1791215170-rEB9MwYfzfLp1szWk4jz","object":"chat.completion.chunk","created":1791215170,"model":"qwen/qwen3-30b-a3b","provider":"DeepInfra","choices":[{"index":0,"delta":{"content":"","role":"assistant","reasoning":" sure","reasoning_details":[{"type":"reasoning.text","text":" sure","format":"unknown","index":0}]},"finish_reason":null,"native_finish_reason":null}]} - -data: {"id":"gen-1791215170-rEB9MwYfzfLp1szWk4jz","object":"chat.completion.chunk","created":1791215170,"model":"qwen/qwen3-30b-a3b","provider":"DeepInfra","choices":[{"index":0,"delta":{"content":"","role":"assistant","reasoning":" I","reasoning_details":[{"type":"reasoning.text","text":" I","format":"unknown","index":0}]},"finish_reason":null,"native_finish_reason":null}]} - -data: {"id":"gen-1791215170-rEB9MwYfzfLp1szWk4jz","object":"chat.completion.chunk","created":1791215170,"model":"qwen/qwen3-30b-a3b","provider":"DeepInfra","choices":[{"index":0,"delta":{"content":"","role":"assistant","reasoning":" follow","reasoning_details":[{"type":"reasoning.text","text":" follow","format":"unknown","index":0}]},"finish_reason":null,"native_finish_reason":null}]} - -data: {"id":"gen-1791215170-rEB9MwYfzfLp1szWk4jz","object":"chat.completion.chunk","created":1791215170,"model":"qwen/qwen3-30b-a3b","provider":"DeepInfra","choices":[{"index":0,"delta":{"content":"","role":"assistant","reasoning":" the","reasoning_details":[{"type":"reasoning.text","text":" the","format":"unknown","index":0}]},"finish_reason":null,"native_finish_reason":null}]} - -data: {"id":"gen-1791215170-rEB9MwYfzfLp1szWk4jz","object":"chat.completion.chunk","created":1791215170,"model":"qwen/qwen3-30b-a3b","provider":"DeepInfra","choices":[{"index":0,"delta":{"content":"","role":"assistant","reasoning":" correct","reasoning_details":[{"type":"reasoning.text","text":" correct","format":"unknown","index":0}]},"finish_reason":null,"native_finish_reason":null}]} - -data: {"id":"gen-1791215170-rEB9MwYfzfLp1szWk4jz","object":"chat.completion.chunk","created":1791215170,"model":"qwen/qwen3-30b-a3b","provider":"DeepInfra","choices":[{"index":0,"delta":{"content":"","role":"assistant","reasoning":" steps","reasoning_details":[{"type":"reasoning.text","text":" steps","format":"unknown","index":0}]},"finish_reason":null,"native_finish_reason":null}]} - -data: {"id":"gen-1791215170-rEB9MwYfzfLp1szWk4jz","object":"chat.completion.chunk","created":1791215170,"model":"qwen/qwen3-30b-a3b","provider":"DeepInfra","choices":[{"index":0,"delta":{"content":"","role":"assistant","reasoning":".\n\n","reasoning_details":[{"type":"reasoning.text","text":".\n\n","format":"unknown","index":0}]},"finish_reason":null,"native_finish_reason":null}]} - -data: {"id":"gen-1791215170-rEB9MwYfzfLp1szWk4jz","object":"chat.completion.chunk","created":1791215170,"model":"qwen/qwen3-30b-a3b","provider":"DeepInfra","choices":[{"index":0,"delta":{"content":"","role":"assistant","reasoning":"In","reasoning_details":[{"type":"reasoning.text","text":"In","format":"unknown","index":0}]},"finish_reason":null,"native_finish_reason":null}]} - -data: {"id":"gen-1791215170-rEB9MwYfzfLp1szWk4jz","object":"chat.completion.chunk","created":1791215170,"model":"qwen/qwen3-30b-a3b","provider":"DeepInfra","choices":[{"index":0,"delta":{"content":"","role":"assistant","reasoning":" the","reasoning_details":[{"type":"reasoning.text","text":" the","format":"unknown","index":0}]},"finish_reason":null,"native_finish_reason":null}]} - -data: {"id":"gen-1791215170-rEB9MwYfzfLp1szWk4jz","object":"chat.completion.chunk","created":1791215170,"model":"qwen/qwen3-30b-a3b","provider":"DeepInfra","choices":[{"index":0,"delta":{"content":"","role":"assistant","reasoning":" history","reasoning_details":[{"type":"reasoning.text","text":" history","format":"unknown","index":0}]},"finish_reason":null,"native_finish_reason":null}]} - -data: {"id":"gen-1791215170-rEB9MwYfzfLp1szWk4jz","object":"chat.completion.chunk","created":1791215170,"model":"qwen/qwen3-30b-a3b","provider":"DeepInfra","choices":[{"index":0,"delta":{"content":"","role":"assistant","reasoning":",","reasoning_details":[{"type":"reasoning.text","text":",","format":"unknown","index":0}]},"finish_reason":null,"native_finish_reason":null}]} - -data: {"id":"gen-1791215170-rEB9MwYfzfLp1szWk4jz","object":"chat.completion.chunk","created":1791215170,"model":"qwen/qwen3-30b-a3b","provider":"DeepInfra","choices":[{"index":0,"delta":{"content":"","role":"assistant","reasoning":" the","reasoning_details":[{"type":"reasoning.text","text":" the","format":"unknown","index":0}]},"finish_reason":null,"native_finish_reason":null}]} - -data: {"id":"gen-1791215170-rEB9MwYfzfLp1szWk4jz","object":"chat.completion.chunk","created":1791215170,"model":"qwen/qwen3-30b-a3b","provider":"DeepInfra","choices":[{"index":0,"delta":{"content":"","role":"assistant","reasoning":" user","reasoning_details":[{"type":"reasoning.text","text":" user","format":"unknown","index":0}]},"finish_reason":null,"native_finish_reason":null}]} - -data: {"id":"gen-1791215170-rEB9MwYfzfLp1szWk4jz","object":"chat.completion.chunk","created":1791215170,"model":"qwen/qwen3-30b-a3b","provider":"DeepInfra","choices":[{"index":0,"delta":{"content":"","role":"assistant","reasoning":" first","reasoning_details":[{"type":"reasoning.text","text":" first","format":"unknown","index":0}]},"finish_reason":null,"native_finish_reason":null}]} - -data: {"id":"gen-1791215170-rEB9MwYfzfLp1szWk4jz","object":"chat.completion.chunk","created":1791215170,"model":"qwen/qwen3-30b-a3b","provider":"DeepInfra","choices":[{"index":0,"delta":{"content":"","role":"assistant","reasoning":" asked","reasoning_details":[{"type":"reasoning.text","text":" asked","format":"unknown","index":0}]},"finish_reason":null,"native_finish_reason":null}]} - -data: {"id":"gen-1791215170-rEB9MwYfzfLp1szWk4jz","object":"chat.completion.chunk","created":1791215170,"model":"qwen/qwen3-30b-a3b","provider":"DeepInfra","choices":[{"index":0,"delta":{"content":"","role":"assistant","reasoning":" to","reasoning_details":[{"type":"reasoning.text","text":" to","format":"unknown","index":0}]},"finish_reason":null,"native_finish_reason":null}]} - -data: {"id":"gen-1791215170-rEB9MwYfzfLp1szWk4jz","object":"chat.completion.chunk","created":1791215170,"model":"qwen/qwen3-30b-a3b","provider":"DeepInfra","choices":[{"index":0,"delta":{"content":"","role":"assistant","reasoning":" run","reasoning_details":[{"type":"reasoning.text","text":" run","format":"unknown","index":0}]},"finish_reason":null,"native_finish_reason":null}]} - -data: {"id":"gen-1791215170-rEB9MwYfzfLp1szWk4jz","object":"chat.completion.chunk","created":1791215170,"model":"qwen/qwen3-30b-a3b","provider":"DeepInfra","choices":[{"index":0,"delta":{"content":"","role":"assistant","reasoning":" the","reasoning_details":[{"type":"reasoning.text","text":" the","format":"unknown","index":0}]},"finish_reason":null,"native_finish_reason":null}]} - -data: {"id":"gen-1791215170-rEB9MwYfzfLp1szWk4jz","object":"chat.completion.chunk","created":1791215170,"model":"qwen/qwen3-30b-a3b","provider":"DeepInfra","choices":[{"index":0,"delta":{"content":"","role":"assistant","reasoning":" scenario","reasoning_details":[{"type":"reasoning.text","text":" scenario","format":"unknown","index":0}]},"finish_reason":null,"native_finish_reason":null}]} - -data: {"id":"gen-1791215170-rEB9MwYfzfLp1szWk4jz","object":"chat.completion.chunk","created":1791215170,"model":"qwen/qwen3-30b-a3b","provider":"DeepInfra","choices":[{"index":0,"delta":{"content":"","role":"assistant","reasoning":",","reasoning_details":[{"type":"reasoning.text","text":",","format":"unknown","index":0}]},"finish_reason":null,"native_finish_reason":null}]} - -data: {"id":"gen-1791215170-rEB9MwYfzfLp1szWk4jz","object":"chat.completion.chunk","created":1791215170,"model":"qwen/qwen3-30b-a3b","provider":"DeepInfra","choices":[{"index":0,"delta":{"content":"","role":"assistant","reasoning":" and","reasoning_details":[{"type":"reasoning.text","text":" and","format":"unknown","index":0}]},"finish_reason":null,"native_finish_reason":null}]} - -data: {"id":"gen-1791215170-rEB9MwYfzfLp1szWk4jz","object":"chat.completion.chunk","created":1791215170,"model":"qwen/qwen3-30b-a3b","provider":"DeepInfra","choices":[{"index":0,"delta":{"content":"","role":"assistant","reasoning":" I","reasoning_details":[{"type":"reasoning.text","text":" I","format":"unknown","index":0}]},"finish_reason":null,"native_finish_reason":null}]} - -data: {"id":"gen-1791215170-rEB9MwYfzfLp1szWk4jz","object":"chat.completion.chunk","created":1791215170,"model":"qwen/qwen3-30b-a3b","provider":"DeepInfra","choices":[{"index":0,"delta":{"content":"","role":"assistant","reasoning":" called","reasoning_details":[{"type":"reasoning.text","text":" called","format":"unknown","index":0}]},"finish_reason":null,"native_finish_reason":null}]} - -data: {"id":"gen-1791215170-rEB9MwYfzfLp1szWk4jz","object":"chat.completion.chunk","created":1791215170,"model":"qwen/qwen3-30b-a3b","provider":"DeepInfra","choices":[{"index":0,"delta":{"content":"","role":"assistant","reasoning":" the","reasoning_details":[{"type":"reasoning.text","text":" the","format":"unknown","index":0}]},"finish_reason":null,"native_finish_reason":null}]} - -data: {"id":"gen-1791215170-rEB9MwYfzfLp1szWk4jz","object":"chat.completion.chunk","created":1791215170,"model":"qwen/qwen3-30b-a3b","provider":"DeepInfra","choices":[{"index":0,"delta":{"content":"","role":"assistant","reasoning":" researcher","reasoning_details":[{"type":"reasoning.text","text":" researcher","format":"unknown","index":0}]},"finish_reason":null,"native_finish_reason":null}]} - -data: {"id":"gen-1791215170-rEB9MwYfzfLp1szWk4jz","object":"chat.completion.chunk","created":1791215170,"model":"qwen/qwen3-30b-a3b","provider":"DeepInfra","choices":[{"index":0,"delta":{"content":"","role":"assistant","reasoning":" and","reasoning_details":[{"type":"reasoning.text","text":" and","format":"unknown","index":0}]},"finish_reason":null,"native_finish_reason":null}]} - -data: {"id":"gen-1791215170-rEB9MwYfzfLp1szWk4jz","object":"chat.completion.chunk","created":1791215170,"model":"qwen/qwen3-30b-a3b","provider":"DeepInfra","choices":[{"index":0,"delta":{"content":"","role":"assistant","reasoning":" read","reasoning_details":[{"type":"reasoning.text","text":" read","format":"unknown","index":0}]},"finish_reason":null,"native_finish_reason":null}]} - -data: {"id":"gen-1791215170-rEB9MwYfzfLp1szWk4jz","object":"chat.completion.chunk","created":1791215170,"model":"qwen/qwen3-30b-a3b","provider":"DeepInfra","choices":[{"index":0,"delta":{"content":"","role":"assistant","reasoning":" functions","reasoning_details":[{"type":"reasoning.text","text":" functions","format":"unknown","index":0}]},"finish_reason":null,"native_finish_reason":null}]} - -data: {"id":"gen-1791215170-rEB9MwYfzfLp1szWk4jz","object":"chat.completion.chunk","created":1791215170,"model":"qwen/qwen3-30b-a3b","provider":"DeepInfra","choices":[{"index":0,"delta":{"content":"","role":"assistant","reasoning":".","reasoning_details":[{"type":"reasoning.text","text":".","format":"unknown","index":0}]},"finish_reason":null,"native_finish_reason":null}]} - -data: {"id":"gen-1791215170-rEB9MwYfzfLp1szWk4jz","object":"chat.completion.chunk","created":1791215170,"model":"qwen/qwen3-30b-a3b","provider":"DeepInfra","choices":[{"index":0,"delta":{"content":"","role":"assistant","reasoning":" The","reasoning_details":[{"type":"reasoning.text","text":" The","format":"unknown","index":0}]},"finish_reason":null,"native_finish_reason":null}]} - -data: {"id":"gen-1791215170-rEB9MwYfzfLp1szWk4jz","object":"chat.completion.chunk","created":1791215170,"model":"qwen/qwen3-30b-a3b","provider":"DeepInfra","choices":[{"index":0,"delta":{"content":"","role":"assistant","reasoning":" read","reasoning_details":[{"type":"reasoning.text","text":" read","format":"unknown","index":0}]},"finish_reason":null,"native_finish_reason":null}]} - -data: {"id":"gen-1791215170-rEB9MwYfzfLp1szWk4jz","object":"chat.completion.chunk","created":1791215170,"model":"qwen/qwen3-30b-a3b","provider":"DeepInfra","choices":[{"index":0,"delta":{"content":"","role":"assistant","reasoning":" function","reasoning_details":[{"type":"reasoning.text","text":" function","format":"unknown","index":0}]},"finish_reason":null,"native_finish_reason":null}]} - -data: {"id":"gen-1791215170-rEB9MwYfzfLp1szWk4jz","object":"chat.completion.chunk","created":1791215170,"model":"qwen/qwen3-30b-a3b","provider":"DeepInfra","choices":[{"index":0,"delta":{"content":"","role":"assistant","reasoning":" fetched","reasoning_details":[{"type":"reasoning.text","text":" fetched","format":"unknown","index":0}]},"finish_reason":null,"native_finish_reason":null}]} - -data: {"id":"gen-1791215170-rEB9MwYfzfLp1szWk4jz","object":"chat.completion.chunk","created":1791215170,"model":"qwen/qwen3-30b-a3b","provider":"DeepInfra","choices":[{"index":0,"delta":{"content":"","role":"assistant","reasoning":" the","reasoning_details":[{"type":"reasoning.text","text":" the","format":"unknown","index":0}]},"finish_reason":null,"native_finish_reason":null}]} - -data: {"id":"gen-1791215170-rEB9MwYfzfLp1szWk4jz","object":"chat.completion.chunk","created":1791215170,"model":"qwen/qwen3-30b-a3b","provider":"DeepInfra","choices":[{"index":0,"delta":{"content":"","role":"assistant","reasoning":" documentation","reasoning_details":[{"type":"reasoning.text","text":" documentation","format":"unknown","index":0}]},"finish_reason":null,"native_finish_reason":null}]} - -data: {"id":"gen-1791215170-rEB9MwYfzfLp1szWk4jz","object":"chat.completion.chunk","created":1791215170,"model":"qwen/qwen3-30b-a3b","provider":"DeepInfra","choices":[{"index":0,"delta":{"content":"","role":"assistant","reasoning":" page","reasoning_details":[{"type":"reasoning.text","text":" page","format":"unknown","index":0}]},"finish_reason":null,"native_finish_reason":null}]} - -data: {"id":"gen-1791215170-rEB9MwYfzfLp1szWk4jz","object":"chat.completion.chunk","created":1791215170,"model":"qwen/qwen3-30b-a3b","provider":"DeepInfra","choices":[{"index":0,"delta":{"content":"","role":"assistant","reasoning":",","reasoning_details":[{"type":"reasoning.text","text":",","format":"unknown","index":0}]},"finish_reason":null,"native_finish_reason":null}]} - -data: {"id":"gen-1791215170-rEB9MwYfzfLp1szWk4jz","object":"chat.completion.chunk","created":1791215170,"model":"qwen/qwen3-30b-a3b","provider":"DeepInfra","choices":[{"index":0,"delta":{"content":"","role":"assistant","reasoning":" and","reasoning_details":[{"type":"reasoning.text","text":" and","format":"unknown","index":0}]},"finish_reason":null,"native_finish_reason":null}]} - -data: {"id":"gen-1791215170-rEB9MwYfzfLp1szWk4jz","object":"chat.completion.chunk","created":1791215170,"model":"qwen/qwen3-30b-a3b","provider":"DeepInfra","choices":[{"index":0,"delta":{"content":"","role":"assistant","reasoning":" the","reasoning_details":[{"type":"reasoning.text","text":" the","format":"unknown","index":0}]},"finish_reason":null,"native_finish_reason":null}]} - -data: {"id":"gen-1791215170-rEB9MwYfzfLp1szWk4jz","object":"chat.completion.chunk","created":1791215170,"model":"qwen/qwen3-30b-a3b","provider":"DeepInfra","choices":[{"index":0,"delta":{"content":"","role":"assistant","reasoning":" researcher","reasoning_details":[{"type":"reasoning.text","text":" researcher","format":"unknown","index":0}]},"finish_reason":null,"native_finish_reason":null}]} - -data: {"id":"gen-1791215170-rEB9MwYfzfLp1szWk4jz","object":"chat.completion.chunk","created":1791215170,"model":"qwen/qwen3-30b-a3b","provider":"DeepInfra","choices":[{"index":0,"delta":{"content":"","role":"assistant","reasoning":" result","reasoning_details":[{"type":"reasoning.text","text":" result","format":"unknown","index":0}]},"finish_reason":null,"native_finish_reason":null}]} - -data: {"id":"gen-1791215170-rEB9MwYfzfLp1szWk4jz","object":"chat.completion.chunk","created":1791215170,"model":"qwen/qwen3-30b-a3b","provider":"DeepInfra","choices":[{"index":0,"delta":{"content":"","role":"assistant","reasoning":" was","reasoning_details":[{"type":"reasoning.text","text":" was","format":"unknown","index":0}]},"finish_reason":null,"native_finish_reason":null}]} - -data: {"id":"gen-1791215170-rEB9MwYfzfLp1szWk4jz","object":"chat.completion.chunk","created":1791215170,"model":"qwen/qwen3-30b-a3b","provider":"DeepInfra","choices":[{"index":0,"delta":{"content":"","role":"assistant","reasoning":" provided","reasoning_details":[{"type":"reasoning.text","text":" provided","format":"unknown","index":0}]},"finish_reason":null,"native_finish_reason":null}]} - -data: {"id":"gen-1791215170-rEB9MwYfzfLp1szWk4jz","object":"chat.completion.chunk","created":1791215170,"model":"qwen/qwen3-30b-a3b","provider":"DeepInfra","choices":[{"index":0,"delta":{"content":"","role":"assistant","reasoning":".","reasoning_details":[{"type":"reasoning.text","text":".","format":"unknown","index":0}]},"finish_reason":null,"native_finish_reason":null}]} - -data: {"id":"gen-1791215170-rEB9MwYfzfLp1szWk4jz","object":"chat.completion.chunk","created":1791215170,"model":"qwen/qwen3-30b-a3b","provider":"DeepInfra","choices":[{"index":0,"delta":{"content":"","role":"assistant","reasoning":" Then","reasoning_details":[{"type":"reasoning.text","text":" Then","format":"unknown","index":0}]},"finish_reason":null,"native_finish_reason":null}]} - -data: {"id":"gen-1791215170-rEB9MwYfzfLp1szWk4jz","object":"chat.completion.chunk","created":1791215170,"model":"qwen/qwen3-30b-a3b","provider":"DeepInfra","choices":[{"index":0,"delta":{"content":"","role":"assistant","reasoning":" the","reasoning_details":[{"type":"reasoning.text","text":" the","format":"unknown","index":0}]},"finish_reason":null,"native_finish_reason":null}]} - -data: {"id":"gen-1791215170-rEB9MwYfzfLp1szWk4jz","object":"chat.completion.chunk","created":1791215170,"model":"qwen/qwen3-30b-a3b","provider":"DeepInfra","choices":[{"index":0,"delta":{"content":"","role":"assistant","reasoning":" user","reasoning_details":[{"type":"reasoning.text","text":" user","format":"unknown","index":0}]},"finish_reason":null,"native_finish_reason":null}]} - -data: {"id":"gen-1791215170-rEB9MwYfzfLp1szWk4jz","object":"chat.completion.chunk","created":1791215170,"model":"qwen/qwen3-30b-a3b","provider":"DeepInfra","choices":[{"index":0,"delta":{"content":"","role":"assistant","reasoning":" asked","reasoning_details":[{"type":"reasoning.text","text":" asked","format":"unknown","index":0}]},"finish_reason":null,"native_finish_reason":null}]} - -data: {"id":"gen-1791215170-rEB9MwYfzfLp1szWk4jz","object":"chat.completion.chunk","created":1791215170,"model":"qwen/qwen3-30b-a3b","provider":"DeepInfra","choices":[{"index":0,"delta":{"content":"","role":"assistant","reasoning":" again","reasoning_details":[{"type":"reasoning.text","text":" again","format":"unknown","index":0}]},"finish_reason":null,"native_finish_reason":null}]} - -data: {"id":"gen-1791215170-rEB9MwYfzfLp1szWk4jz","object":"chat.completion.chunk","created":1791215170,"model":"qwen/qwen3-30b-a3b","provider":"DeepInfra","choices":[{"index":0,"delta":{"content":"","role":"assistant","reasoning":",","reasoning_details":[{"type":"reasoning.text","text":",","format":"unknown","index":0}]},"finish_reason":null,"native_finish_reason":null}]} - -data: {"id":"gen-1791215170-rEB9MwYfzfLp1szWk4jz","object":"chat.completion.chunk","created":1791215170,"model":"qwen/qwen3-30b-a3b","provider":"DeepInfra","choices":[{"index":0,"delta":{"content":"","role":"assistant","reasoning":" and","reasoning_details":[{"type":"reasoning.text","text":" and","format":"unknown","index":0}]},"finish_reason":null,"native_finish_reason":null}]} - -data: {"id":"gen-1791215170-rEB9MwYfzfLp1szWk4jz","object":"chat.completion.chunk","created":1791215170,"model":"qwen/qwen3-30b-a3b","provider":"DeepInfra","choices":[{"index":0,"delta":{"content":"","role":"assistant","reasoning":" I","reasoning_details":[{"type":"reasoning.text","text":" I","format":"unknown","index":0}]},"finish_reason":null,"native_finish_reason":null}]} - -data: {"id":"gen-1791215170-rEB9MwYfzfLp1szWk4jz","object":"chat.completion.chunk","created":1791215170,"model":"qwen/qwen3-30b-a3b","provider":"DeepInfra","choices":[{"index":0,"delta":{"content":"","role":"assistant","reasoning":" followed","reasoning_details":[{"type":"reasoning.text","text":" followed","format":"unknown","index":0}]},"finish_reason":null,"native_finish_reason":null}]} - -data: {"id":"gen-1791215170-rEB9MwYfzfLp1szWk4jz","object":"chat.completion.chunk","created":1791215170,"model":"qwen/qwen3-30b-a3b","provider":"DeepInfra","choices":[{"index":0,"delta":{"content":"","role":"assistant","reasoning":" the","reasoning_details":[{"type":"reasoning.text","text":" the","format":"unknown","index":0}]},"finish_reason":null,"native_finish_reason":null}]} - -data: {"id":"gen-1791215170-rEB9MwYfzfLp1szWk4jz","object":"chat.completion.chunk","created":1791215170,"model":"qwen/qwen3-30b-a3b","provider":"DeepInfra","choices":[{"index":0,"delta":{"content":"","role":"assistant","reasoning":" same","reasoning_details":[{"type":"reasoning.text","text":" same","format":"unknown","index":0}]},"finish_reason":null,"native_finish_reason":null}]} - -data: {"id":"gen-1791215170-rEB9MwYfzfLp1szWk4jz","object":"chat.completion.chunk","created":1791215170,"model":"qwen/qwen3-30b-a3b","provider":"DeepInfra","choices":[{"index":0,"delta":{"content":"","role":"assistant","reasoning":" steps","reasoning_details":[{"type":"reasoning.text","text":" steps","format":"unknown","index":0}]},"finish_reason":null,"native_finish_reason":null}]} - -data: {"id":"gen-1791215170-rEB9MwYfzfLp1szWk4jz","object":"chat.completion.chunk","created":1791215170,"model":"qwen/qwen3-30b-a3b","provider":"DeepInfra","choices":[{"index":0,"delta":{"content":"","role":"assistant","reasoning":".","reasoning_details":[{"type":"reasoning.text","text":".","format":"unknown","index":0}]},"finish_reason":null,"native_finish_reason":null}]} - -data: {"id":"gen-1791215170-rEB9MwYfzfLp1szWk4jz","object":"chat.completion.chunk","created":1791215170,"model":"qwen/qwen3-30b-a3b","provider":"DeepInfra","choices":[{"index":0,"delta":{"content":"","role":"assistant","reasoning":" Now","reasoning_details":[{"type":"reasoning.text","text":" Now","format":"unknown","index":0}]},"finish_reason":null,"native_finish_reason":null}]} - -data: {"id":"gen-1791215170-rEB9MwYfzfLp1szWk4jz","object":"chat.completion.chunk","created":1791215170,"model":"qwen/qwen3-30b-a3b","provider":"DeepInfra","choices":[{"index":0,"delta":{"content":"","role":"assistant","reasoning":",","reasoning_details":[{"type":"reasoning.text","text":",","format":"unknown","index":0}]},"finish_reason":null,"native_finish_reason":null}]} - -data: {"id":"gen-1791215170-rEB9MwYfzfLp1szWk4jz","object":"chat.completion.chunk","created":1791215170,"model":"qwen/qwen3-30b-a3b","provider":"DeepInfra","choices":[{"index":0,"delta":{"content":"","role":"assistant","reasoning":" the","reasoning_details":[{"type":"reasoning.text","text":" the","format":"unknown","index":0}]},"finish_reason":null,"native_finish_reason":null}]} - -data: {"id":"gen-1791215170-rEB9MwYfzfLp1szWk4jz","object":"chat.completion.chunk","created":1791215170,"model":"qwen/qwen3-30b-a3b","provider":"DeepInfra","choices":[{"index":0,"delta":{"content":"","role":"assistant","reasoning":" user","reasoning_details":[{"type":"reasoning.text","text":" user","format":"unknown","index":0}]},"finish_reason":null,"native_finish_reason":null}]} - -data: {"id":"gen-1791215170-rEB9MwYfzfLp1szWk4jz","object":"chat.completion.chunk","created":1791215170,"model":"qwen/qwen3-30b-a3b","provider":"DeepInfra","choices":[{"index":0,"delta":{"content":"","role":"assistant","reasoning":" is","reasoning_details":[{"type":"reasoning.text","text":" is","format":"unknown","index":0}]},"finish_reason":null,"native_finish_reason":null}]} - -data: {"id":"gen-1791215170-rEB9MwYfzfLp1szWk4jz","object":"chat.completion.chunk","created":1791215170,"model":"qwen/qwen3-30b-a3b","provider":"DeepInfra","choices":[{"index":0,"delta":{"content":"","role":"assistant","reasoning":" asking","reasoning_details":[{"type":"reasoning.text","text":" asking","format":"unknown","index":0}]},"finish_reason":null,"native_finish_reason":null}]} - -data: {"id":"gen-1791215170-rEB9MwYfzfLp1szWk4jz","object":"chat.completion.chunk","created":1791215170,"model":"qwen/qwen3-30b-a3b","provider":"DeepInfra","choices":[{"index":0,"delta":{"content":"","role":"assistant","reasoning":" \"","reasoning_details":[{"type":"reasoning.text","text":" \"","format":"unknown","index":0}]},"finish_reason":null,"native_finish_reason":null}]} - -data: {"id":"gen-1791215170-rEB9MwYfzfLp1szWk4jz","object":"chat.completion.chunk","created":1791215170,"model":"qwen/qwen3-30b-a3b","provider":"DeepInfra","choices":[{"index":0,"delta":{"content":"","role":"assistant","reasoning":"again","reasoning_details":[{"type":"reasoning.text","text":"again","format":"unknown","index":0}]},"finish_reason":null,"native_finish_reason":null}]} - -data: {"id":"gen-1791215170-rEB9MwYfzfLp1szWk4jz","object":"chat.completion.chunk","created":1791215170,"model":"qwen/qwen3-30b-a3b","provider":"DeepInfra","choices":[{"index":0,"delta":{"content":"","role":"assistant","reasoning":",\"","reasoning_details":[{"type":"reasoning.text","text":",\"","format":"unknown","index":0}]},"finish_reason":null,"native_finish_reason":null}]} - -data: {"id":"gen-1791215170-rEB9MwYfzfLp1szWk4jz","object":"chat.completion.chunk","created":1791215170,"model":"qwen/qwen3-30b-a3b","provider":"DeepInfra","choices":[{"index":0,"delta":{"content":"","role":"assistant","reasoning":" so","reasoning_details":[{"type":"reasoning.text","text":" so","format":"unknown","index":0}]},"finish_reason":null,"native_finish_reason":null}]} - -data: {"id":"gen-1791215170-rEB9MwYfzfLp1szWk4jz","object":"chat.completion.chunk","created":1791215170,"model":"qwen/qwen3-30b-a3b","provider":"DeepInfra","choices":[{"index":0,"delta":{"content":"","role":"assistant","reasoning":" I","reasoning_details":[{"type":"reasoning.text","text":" I","format":"unknown","index":0}]},"finish_reason":null,"native_finish_reason":null}]} - -data: {"id":"gen-1791215170-rEB9MwYfzfLp1szWk4jz","object":"chat.completion.chunk","created":1791215170,"model":"qwen/qwen3-30b-a3b","provider":"DeepInfra","choices":[{"index":0,"delta":{"content":"","role":"assistant","reasoning":" need","reasoning_details":[{"type":"reasoning.text","text":" need","format":"unknown","index":0}]},"finish_reason":null,"native_finish_reason":null}]} - -data: {"id":"gen-1791215170-rEB9MwYfzfLp1szWk4jz","object":"chat.completion.chunk","created":1791215170,"model":"qwen/qwen3-30b-a3b","provider":"DeepInfra","choices":[{"index":0,"delta":{"content":"","role":"assistant","reasoning":" to","reasoning_details":[{"type":"reasoning.text","text":" to","format":"unknown","index":0}]},"finish_reason":null,"native_finish_reason":null}]} - -data: {"id":"gen-1791215170-rEB9MwYfzfLp1szWk4jz","object":"chat.completion.chunk","created":1791215170,"model":"qwen/qwen3-30b-a3b","provider":"DeepInfra","choices":[{"index":0,"delta":{"content":"","role":"assistant","reasoning":" repeat","reasoning_details":[{"type":"reasoning.text","text":" repeat","format":"unknown","index":0}]},"finish_reason":null,"native_finish_reason":null}]} - -data: {"id":"gen-1791215170-rEB9MwYfzfLp1szWk4jz","object":"chat.completion.chunk","created":1791215170,"model":"qwen/qwen3-30b-a3b","provider":"DeepInfra","choices":[{"index":0,"delta":{"content":"","role":"assistant","reasoning":" the","reasoning_details":[{"type":"reasoning.text","text":" the","format":"unknown","index":0}]},"finish_reason":null,"native_finish_reason":null}]} - -data: {"id":"gen-1791215170-rEB9MwYfzfLp1szWk4jz","object":"chat.completion.chunk","created":1791215170,"model":"qwen/qwen3-30b-a3b","provider":"DeepInfra","choices":[{"index":0,"delta":{"content":"","role":"assistant","reasoning":" process","reasoning_details":[{"type":"reasoning.text","text":" process","format":"unknown","index":0}]},"finish_reason":null,"native_finish_reason":null}]} - -data: {"id":"gen-1791215170-rEB9MwYfzfLp1szWk4jz","object":"chat.completion.chunk","created":1791215170,"model":"qwen/qwen3-30b-a3b","provider":"DeepInfra","choices":[{"index":0,"delta":{"content":"","role":"assistant","reasoning":".\n\n","reasoning_details":[{"type":"reasoning.text","text":".\n\n","format":"unknown","index":0}]},"finish_reason":null,"native_finish_reason":null}]} - -data: {"id":"gen-1791215170-rEB9MwYfzfLp1szWk4jz","object":"chat.completion.chunk","created":1791215170,"model":"qwen/qwen3-30b-a3b","provider":"DeepInfra","choices":[{"index":0,"delta":{"content":"","role":"assistant","reasoning":"The","reasoning_details":[{"type":"reasoning.text","text":"The","format":"unknown","index":0}]},"finish_reason":null,"native_finish_reason":null}]} - -data: {"id":"gen-1791215170-rEB9MwYfzfLp1szWk4jz","object":"chat.completion.chunk","created":1791215170,"model":"qwen/qwen3-30b-a3b","provider":"DeepInfra","choices":[{"index":0,"delta":{"content":"","role":"assistant","reasoning":" instructions","reasoning_details":[{"type":"reasoning.text","text":" instructions","format":"unknown","index":0}]},"finish_reason":null,"native_finish_reason":null}]} - -data: {"id":"gen-1791215170-rEB9MwYfzfLp1szWk4jz","object":"chat.completion.chunk","created":1791215170,"model":"qwen/qwen3-30b-a3b","provider":"DeepInfra","choices":[{"index":0,"delta":{"content":"","role":"assistant","reasoning":" say","reasoning_details":[{"type":"reasoning.text","text":" say","format":"unknown","index":0}]},"finish_reason":null,"native_finish_reason":null}]} - -data: {"id":"gen-1791215170-rEB9MwYfzfLp1szWk4jz","object":"chat.completion.chunk","created":1791215170,"model":"qwen/qwen3-30b-a3b","provider":"DeepInfra","choices":[{"index":0,"delta":{"content":"","role":"assistant","reasoning":" that","reasoning_details":[{"type":"reasoning.text","text":" that","format":"unknown","index":0}]},"finish_reason":null,"native_finish_reason":null}]} - -data: {"id":"gen-1791215170-rEB9MwYfzfLp1szWk4jz","object":"chat.completion.chunk","created":1791215170,"model":"qwen/qwen3-30b-a3b","provider":"DeepInfra","choices":[{"index":0,"delta":{"content":"","role":"assistant","reasoning":" for","reasoning_details":[{"type":"reasoning.text","text":" for","format":"unknown","index":0}]},"finish_reason":null,"native_finish_reason":null}]} - -data: {"id":"gen-1791215170-rEB9MwYfzfLp1szWk4jz","object":"chat.completion.chunk","created":1791215170,"model":"qwen/qwen3-30b-a3b","provider":"DeepInfra","choices":[{"index":0,"delta":{"content":"","role":"assistant","reasoning":" every","reasoning_details":[{"type":"reasoning.text","text":" every","format":"unknown","index":0}]},"finish_reason":null,"native_finish_reason":null}]} - -data: {"id":"gen-1791215170-rEB9MwYfzfLp1szWk4jz","object":"chat.completion.chunk","created":1791215170,"model":"qwen/qwen3-30b-a3b","provider":"DeepInfra","choices":[{"index":0,"delta":{"content":"","role":"assistant","reasoning":" user","reasoning_details":[{"type":"reasoning.text","text":" user","format":"unknown","index":0}]},"finish_reason":null,"native_finish_reason":null}]} - -data: {"id":"gen-1791215170-rEB9MwYfzfLp1szWk4jz","object":"chat.completion.chunk","created":1791215170,"model":"qwen/qwen3-30b-a3b","provider":"DeepInfra","choices":[{"index":0,"delta":{"content":"","role":"assistant","reasoning":" task","reasoning_details":[{"type":"reasoning.text","text":" task","format":"unknown","index":0}]},"finish_reason":null,"native_finish_reason":null}]} - -data: {"id":"gen-1791215170-rEB9MwYfzfLp1szWk4jz","object":"chat.completion.chunk","created":1791215170,"model":"qwen/qwen3-30b-a3b","provider":"DeepInfra","choices":[{"index":0,"delta":{"content":"","role":"assistant","reasoning":",","reasoning_details":[{"type":"reasoning.text","text":",","format":"unknown","index":0}]},"finish_reason":null,"native_finish_reason":null}]} - -data: {"id":"gen-1791215170-rEB9MwYfzfLp1szWk4jz","object":"chat.completion.chunk","created":1791215170,"model":"qwen/qwen3-30b-a3b","provider":"DeepInfra","choices":[{"index":0,"delta":{"content":"","role":"assistant","reasoning":" I","reasoning_details":[{"type":"reasoning.text","text":" I","format":"unknown","index":0}]},"finish_reason":null,"native_finish_reason":null}]} - -data: {"id":"gen-1791215170-rEB9MwYfzfLp1szWk4jz","object":"chat.completion.chunk","created":1791215170,"model":"qwen/qwen3-30b-a3b","provider":"DeepInfra","choices":[{"index":0,"delta":{"content":"","role":"assistant","reasoning":" must","reasoning_details":[{"type":"reasoning.text","text":" must","format":"unknown","index":0}]},"finish_reason":null,"native_finish_reason":null}]} - -data: {"id":"gen-1791215170-rEB9MwYfzfLp1szWk4jz","object":"chat.completion.chunk","created":1791215170,"model":"qwen/qwen3-30b-a3b","provider":"DeepInfra","choices":[{"index":0,"delta":{"content":"","role":"assistant","reasoning":" first","reasoning_details":[{"type":"reasoning.text","text":" first","format":"unknown","index":0}]},"finish_reason":null,"native_finish_reason":null}]} - -data: {"id":"gen-1791215170-rEB9MwYfzfLp1szWk4jz","object":"chat.completion.chunk","created":1791215170,"model":"qwen/qwen3-30b-a3b","provider":"DeepInfra","choices":[{"index":0,"delta":{"content":"","role":"assistant","reasoning":" call","reasoning_details":[{"type":"reasoning.text","text":" call","format":"unknown","index":0}]},"finish_reason":null,"native_finish_reason":null}]} - -data: {"id":"gen-1791215170-rEB9MwYfzfLp1szWk4jz","object":"chat.completion.chunk","created":1791215170,"model":"qwen/qwen3-30b-a3b","provider":"DeepInfra","choices":[{"index":0,"delta":{"content":"","role":"assistant","reasoning":" researcher","reasoning_details":[{"type":"reasoning.text","text":" researcher","format":"unknown","index":0}]},"finish_reason":null,"native_finish_reason":null}]} - -data: {"id":"gen-1791215170-rEB9MwYfzfLp1szWk4jz","object":"chat.completion.chunk","created":1791215170,"model":"qwen/qwen3-30b-a3b","provider":"DeepInfra","choices":[{"index":0,"delta":{"content":"","role":"assistant","reasoning":" with","reasoning_details":[{"type":"reasoning.text","text":" with","format":"unknown","index":0}]},"finish_reason":null,"native_finish_reason":null}]} - -data: {"id":"gen-1791215170-rEB9MwYfzfLp1szWk4jz","object":"chat.completion.chunk","created":1791215170,"model":"qwen/qwen3-30b-a3b","provider":"DeepInfra","choices":[{"index":0,"delta":{"content":"","role":"assistant","reasoning":" the","reasoning_details":[{"type":"reasoning.text","text":" the","format":"unknown","index":0}]},"finish_reason":null,"native_finish_reason":null}]} - -data: {"id":"gen-1791215170-rEB9MwYfzfLp1szWk4jz","object":"chat.completion.chunk","created":1791215170,"model":"qwen/qwen3-30b-a3b","provider":"DeepInfra","choices":[{"index":0,"delta":{"content":"","role":"assistant","reasoning":" full","reasoning_details":[{"type":"reasoning.text","text":" full","format":"unknown","index":0}]},"finish_reason":null,"native_finish_reason":null}]} - -data: {"id":"gen-1791215170-rEB9MwYfzfLp1szWk4jz","object":"chat.completion.chunk","created":1791215170,"model":"qwen/qwen3-30b-a3b","provider":"DeepInfra","choices":[{"index":0,"delta":{"content":"","role":"assistant","reasoning":" user","reasoning_details":[{"type":"reasoning.text","text":" user","format":"unknown","index":0}]},"finish_reason":null,"native_finish_reason":null}]} - -data: {"id":"gen-1791215170-rEB9MwYfzfLp1szWk4jz","object":"chat.completion.chunk","created":1791215170,"model":"qwen/qwen3-30b-a3b","provider":"DeepInfra","choices":[{"index":0,"delta":{"content":"","role":"assistant","reasoning":" message","reasoning_details":[{"type":"reasoning.text","text":" message","format":"unknown","index":0}]},"finish_reason":null,"native_finish_reason":null}]} - -data: {"id":"gen-1791215170-rEB9MwYfzfLp1szWk4jz","object":"chat.completion.chunk","created":1791215170,"model":"qwen/qwen3-30b-a3b","provider":"DeepInfra","choices":[{"index":0,"delta":{"content":"","role":"assistant","reasoning":" and","reasoning_details":[{"type":"reasoning.text","text":" and","format":"unknown","index":0}]},"finish_reason":null,"native_finish_reason":null}]} - -data: {"id":"gen-1791215170-rEB9MwYfzfLp1szWk4jz","object":"chat.completion.chunk","created":1791215170,"model":"qwen/qwen3-30b-a3b","provider":"DeepInfra","choices":[{"index":0,"delta":{"content":"","role":"assistant","reasoning":" read","reasoning_details":[{"type":"reasoning.text","text":" read","format":"unknown","index":0}]},"finish_reason":null,"native_finish_reason":null}]} - -data: {"id":"gen-1791215170-rEB9MwYfzfLp1szWk4jz","object":"chat.completion.chunk","created":1791215170,"model":"qwen/qwen3-30b-a3b","provider":"DeepInfra","choices":[{"index":0,"delta":{"content":"","role":"assistant","reasoning":" with","reasoning_details":[{"type":"reasoning.text","text":" with","format":"unknown","index":0}]},"finish_reason":null,"native_finish_reason":null}]} - -data: {"id":"gen-1791215170-rEB9MwYfzfLp1szWk4jz","object":"chat.completion.chunk","created":1791215170,"model":"qwen/qwen3-30b-a3b","provider":"DeepInfra","choices":[{"index":0,"delta":{"content":"","role":"assistant","reasoning":" the","reasoning_details":[{"type":"reasoning.text","text":" the","format":"unknown","index":0}]},"finish_reason":null,"native_finish_reason":null}]} - -data: {"id":"gen-1791215170-rEB9MwYfzfLp1szWk4jz","object":"chat.completion.chunk","created":1791215170,"model":"qwen/qwen3-30b-a3b","provider":"DeepInfra","choices":[{"index":0,"delta":{"content":"","role":"assistant","reasoning":" specific","reasoning_details":[{"type":"reasoning.text","text":" specific","format":"unknown","index":0}]},"finish_reason":null,"native_finish_reason":null}]} - -data: {"id":"gen-1791215170-rEB9MwYfzfLp1szWk4jz","object":"chat.completion.chunk","created":1791215170,"model":"qwen/qwen3-30b-a3b","provider":"DeepInfra","choices":[{"index":0,"delta":{"content":"","role":"assistant","reasoning":" URL","reasoning_details":[{"type":"reasoning.text","text":" URL","format":"unknown","index":0}]},"finish_reason":null,"native_finish_reason":null}]} - -data: {"id":"gen-1791215170-rEB9MwYfzfLp1szWk4jz","object":"chat.completion.chunk","created":1791215170,"model":"qwen/qwen3-30b-a3b","provider":"DeepInfra","choices":[{"index":0,"delta":{"content":"","role":"assistant","reasoning":".","reasoning_details":[{"type":"reasoning.text","text":".","format":"unknown","index":0}]},"finish_reason":null,"native_finish_reason":null}]} - -data: {"id":"gen-1791215170-rEB9MwYfzfLp1szWk4jz","object":"chat.completion.chunk","created":1791215170,"model":"qwen/qwen3-30b-a3b","provider":"DeepInfra","choices":[{"index":0,"delta":{"content":"","role":"assistant","reasoning":" Since","reasoning_details":[{"type":"reasoning.text","text":" Since","format":"unknown","index":0}]},"finish_reason":null,"native_finish_reason":null}]} - -data: {"id":"gen-1791215170-rEB9MwYfzfLp1szWk4jz","object":"chat.completion.chunk","created":1791215170,"model":"qwen/qwen3-30b-a3b","provider":"DeepInfra","choices":[{"index":0,"delta":{"content":"","role":"assistant","reasoning":" the","reasoning_details":[{"type":"reasoning.text","text":" the","format":"unknown","index":0}]},"finish_reason":null,"native_finish_reason":null}]} - -data: {"id":"gen-1791215170-rEB9MwYfzfLp1szWk4jz","object":"chat.completion.chunk","created":1791215170,"model":"qwen/qwen3-30b-a3b","provider":"DeepInfra","choices":[{"index":0,"delta":{"content":"","role":"assistant","reasoning":" user","reasoning_details":[{"type":"reasoning.text","text":" user","format":"unknown","index":0}]},"finish_reason":null,"native_finish_reason":null}]} - -data: {"id":"gen-1791215170-rEB9MwYfzfLp1szWk4jz","object":"chat.completion.chunk","created":1791215170,"model":"qwen/qwen3-30b-a3b","provider":"DeepInfra","choices":[{"index":0,"delta":{"content":"","role":"assistant","reasoning":" mentioned","reasoning_details":[{"type":"reasoning.text","text":" mentioned","format":"unknown","index":0}]},"finish_reason":null,"native_finish_reason":null}]} - -data: {"id":"gen-1791215170-rEB9MwYfzfLp1szWk4jz","object":"chat.completion.chunk","created":1791215170,"model":"qwen/qwen3-30b-a3b","provider":"DeepInfra","choices":[{"index":0,"delta":{"content":"","role":"assistant","reasoning":" \"","reasoning_details":[{"type":"reasoning.text","text":" \"","format":"unknown","index":0}]},"finish_reason":null,"native_finish_reason":null}]} - -data: {"id":"gen-1791215170-rEB9MwYfzfLp1szWk4jz","object":"chat.completion.chunk","created":1791215170,"model":"qwen/qwen3-30b-a3b","provider":"DeepInfra","choices":[{"index":0,"delta":{"content":"","role":"assistant","reasoning":"again","reasoning_details":[{"type":"reasoning.text","text":"again","format":"unknown","index":0}]},"finish_reason":null,"native_finish_reason":null}]} - -data: {"id":"gen-1791215170-rEB9MwYfzfLp1szWk4jz","object":"chat.completion.chunk","created":1791215170,"model":"qwen/qwen3-30b-a3b","provider":"DeepInfra","choices":[{"index":0,"delta":{"content":"","role":"assistant","reasoning":",\"","reasoning_details":[{"type":"reasoning.text","text":",\"","format":"unknown","index":0}]},"finish_reason":null,"native_finish_reason":null}]} - -data: {"id":"gen-1791215170-rEB9MwYfzfLp1szWk4jz","object":"chat.completion.chunk","created":1791215170,"model":"qwen/qwen3-30b-a3b","provider":"DeepInfra","choices":[{"index":0,"delta":{"content":"","role":"assistant","reasoning":" the","reasoning_details":[{"type":"reasoning.text","text":" the","format":"unknown","index":0}]},"finish_reason":null,"native_finish_reason":null}]} - -data: {"id":"gen-1791215170-rEB9MwYfzfLp1szWk4jz","object":"chat.completion.chunk","created":1791215170,"model":"qwen/qwen3-30b-a3b","provider":"DeepInfra","choices":[{"index":0,"delta":{"content":"","role":"assistant","reasoning":" message","reasoning_details":[{"type":"reasoning.text","text":" message","format":"unknown","index":0}]},"finish_reason":null,"native_finish_reason":null}]} - -data: {"id":"gen-1791215170-rEB9MwYfzfLp1szWk4jz","object":"chat.completion.chunk","created":1791215170,"model":"qwen/qwen3-30b-a3b","provider":"DeepInfra","choices":[{"index":0,"delta":{"content":"","role":"assistant","reasoning":" should","reasoning_details":[{"type":"reasoning.text","text":" should","format":"unknown","index":0}]},"finish_reason":null,"native_finish_reason":null}]} - -data: {"id":"gen-1791215170-rEB9MwYfzfLp1szWk4jz","object":"chat.completion.chunk","created":1791215170,"model":"qwen/qwen3-30b-a3b","provider":"DeepInfra","choices":[{"index":0,"delta":{"content":"","role":"assistant","reasoning":" be","reasoning_details":[{"type":"reasoning.text","text":" be","format":"unknown","index":0}]},"finish_reason":null,"native_finish_reason":null}]} - -data: {"id":"gen-1791215170-rEB9MwYfzfLp1szWk4jz","object":"chat.completion.chunk","created":1791215170,"model":"qwen/qwen3-30b-a3b","provider":"DeepInfra","choices":[{"index":0,"delta":{"content":"","role":"assistant","reasoning":" exactly","reasoning_details":[{"type":"reasoning.text","text":" exactly","format":"unknown","index":0}]},"finish_reason":null,"native_finish_reason":null}]} - -data: {"id":"gen-1791215170-rEB9MwYfzfLp1szWk4jz","object":"chat.completion.chunk","created":1791215170,"model":"qwen/qwen3-30b-a3b","provider":"DeepInfra","choices":[{"index":0,"delta":{"content":"","role":"assistant","reasoning":" \"","reasoning_details":[{"type":"reasoning.text","text":" \"","format":"unknown","index":0}]},"finish_reason":null,"native_finish_reason":null}]} - -data: {"id":"gen-1791215170-rEB9MwYfzfLp1szWk4jz","object":"chat.completion.chunk","created":1791215170,"model":"qwen/qwen3-30b-a3b","provider":"DeepInfra","choices":[{"index":0,"delta":{"content":"","role":"assistant","reasoning":"Run","reasoning_details":[{"type":"reasoning.text","text":"Run","format":"unknown","index":0}]},"finish_reason":null,"native_finish_reason":null}]} - -data: {"id":"gen-1791215170-rEB9MwYfzfLp1szWk4jz","object":"chat.completion.chunk","created":1791215170,"model":"qwen/qwen3-30b-a3b","provider":"DeepInfra","choices":[{"index":0,"delta":{"content":"","role":"assistant","reasoning":" the","reasoning_details":[{"type":"reasoning.text","text":" the","format":"unknown","index":0}]},"finish_reason":null,"native_finish_reason":null}]} - -data: {"id":"gen-1791215170-rEB9MwYfzfLp1szWk4jz","object":"chat.completion.chunk","created":1791215170,"model":"qwen/qwen3-30b-a3b","provider":"DeepInfra","choices":[{"index":0,"delta":{"content":"","role":"assistant","reasoning":" Brain","reasoning_details":[{"type":"reasoning.text","text":" Brain","format":"unknown","index":0}]},"finish_reason":null,"native_finish_reason":null}]} - -data: {"id":"gen-1791215170-rEB9MwYfzfLp1szWk4jz","object":"chat.completion.chunk","created":1791215170,"model":"qwen/qwen3-30b-a3b","provider":"DeepInfra","choices":[{"index":0,"delta":{"content":"","role":"assistant","reasoning":"trust","reasoning_details":[{"type":"reasoning.text","text":"trust","format":"unknown","index":0}]},"finish_reason":null,"native_finish_reason":null}]} - -data: {"id":"gen-1791215170-rEB9MwYfzfLp1szWk4jz","object":"chat.completion.chunk","created":1791215170,"model":"qwen/qwen3-30b-a3b","provider":"DeepInfra","choices":[{"index":0,"delta":{"content":"","role":"assistant","reasoning":" Eve","reasoning_details":[{"type":"reasoning.text","text":" Eve","format":"unknown","index":0}]},"finish_reason":null,"native_finish_reason":null}]} - -data: {"id":"gen-1791215170-rEB9MwYfzfLp1szWk4jz","object":"chat.completion.chunk","created":1791215170,"model":"qwen/qwen3-30b-a3b","provider":"DeepInfra","choices":[{"index":0,"delta":{"content":"","role":"assistant","reasoning":" instrumentation","reasoning_details":[{"type":"reasoning.text","text":" instrumentation","format":"unknown","index":0}]},"finish_reason":null,"native_finish_reason":null}]} - -data: {"id":"gen-1791215170-rEB9MwYfzfLp1szWk4jz","object":"chat.completion.chunk","created":1791215170,"model":"qwen/qwen3-30b-a3b","provider":"DeepInfra","choices":[{"index":0,"delta":{"content":"","role":"assistant","reasoning":" e","reasoning_details":[{"type":"reasoning.text","text":" e","format":"unknown","index":0}]},"finish_reason":null,"native_finish_reason":null}]} - -data: {"id":"gen-1791215170-rEB9MwYfzfLp1szWk4jz","object":"chat.completion.chunk","created":1791215170,"model":"qwen/qwen3-30b-a3b","provider":"DeepInfra","choices":[{"index":0,"delta":{"content":"","role":"assistant","reasoning":"2","reasoning_details":[{"type":"reasoning.text","text":"2","format":"unknown","index":0}]},"finish_reason":null,"native_finish_reason":null}]} - -data: {"id":"gen-1791215170-rEB9MwYfzfLp1szWk4jz","object":"chat.completion.chunk","created":1791215170,"model":"qwen/qwen3-30b-a3b","provider":"DeepInfra","choices":[{"index":0,"delta":{"content":"","role":"assistant","reasoning":"e","reasoning_details":[{"type":"reasoning.text","text":"e","format":"unknown","index":0}]},"finish_reason":null,"native_finish_reason":null}]} - -data: {"id":"gen-1791215170-rEB9MwYfzfLp1szWk4jz","object":"chat.completion.chunk","created":1791215170,"model":"qwen/qwen3-30b-a3b","provider":"DeepInfra","choices":[{"index":0,"delta":{"content":"","role":"assistant","reasoning":" scenario","reasoning_details":[{"type":"reasoning.text","text":" scenario","format":"unknown","index":0}]},"finish_reason":null,"native_finish_reason":null}]} - -data: {"id":"gen-1791215170-rEB9MwYfzfLp1szWk4jz","object":"chat.completion.chunk","created":1791215170,"model":"qwen/qwen3-30b-a3b","provider":"DeepInfra","choices":[{"index":0,"delta":{"content":"","role":"assistant","reasoning":" again","reasoning_details":[{"type":"reasoning.text","text":" again","format":"unknown","index":0}]},"finish_reason":null,"native_finish_reason":null}]} - -data: {"id":"gen-1791215170-rEB9MwYfzfLp1szWk4jz","object":"chat.completion.chunk","created":1791215170,"model":"qwen/qwen3-30b-a3b","provider":"DeepInfra","choices":[{"index":0,"delta":{"content":"","role":"assistant","reasoning":".\"","reasoning_details":[{"type":"reasoning.text","text":".\"","format":"unknown","index":0}]},"finish_reason":null,"native_finish_reason":null}]} - -data: {"id":"gen-1791215170-rEB9MwYfzfLp1szWk4jz","object":"chat.completion.chunk","created":1791215170,"model":"qwen/qwen3-30b-a3b","provider":"DeepInfra","choices":[{"index":0,"delta":{"content":"","role":"assistant","reasoning":" I","reasoning_details":[{"type":"reasoning.text","text":" I","format":"unknown","index":0}]},"finish_reason":null,"native_finish_reason":null}]} - -data: {"id":"gen-1791215170-rEB9MwYfzfLp1szWk4jz","object":"chat.completion.chunk","created":1791215170,"model":"qwen/qwen3-30b-a3b","provider":"DeepInfra","choices":[{"index":0,"delta":{"content":"","role":"assistant","reasoning":" need","reasoning_details":[{"type":"reasoning.text","text":" need","format":"unknown","index":0}]},"finish_reason":null,"native_finish_reason":null}]} - -data: {"id":"gen-1791215170-rEB9MwYfzfLp1szWk4jz","object":"chat.completion.chunk","created":1791215170,"model":"qwen/qwen3-30b-a3b","provider":"DeepInfra","choices":[{"index":0,"delta":{"content":"","role":"assistant","reasoning":" to","reasoning_details":[{"type":"reasoning.text","text":" to","format":"unknown","index":0}]},"finish_reason":null,"native_finish_reason":null}]} - -data: {"id":"gen-1791215170-rEB9MwYfzfLp1szWk4jz","object":"chat.completion.chunk","created":1791215170,"model":"qwen/qwen3-30b-a3b","provider":"DeepInfra","choices":[{"index":0,"delta":{"content":"","role":"assistant","reasoning":" make","reasoning_details":[{"type":"reasoning.text","text":" make","format":"unknown","index":0}]},"finish_reason":null,"native_finish_reason":null}]} - -data: {"id":"gen-1791215170-rEB9MwYfzfLp1szWk4jz","object":"chat.completion.chunk","created":1791215170,"model":"qwen/qwen3-30b-a3b","provider":"DeepInfra","choices":[{"index":0,"delta":{"content":"","role":"assistant","reasoning":" sure","reasoning_details":[{"type":"reasoning.text","text":" sure","format":"unknown","index":0}]},"finish_reason":null,"native_finish_reason":null}]} - -data: {"id":"gen-1791215170-rEB9MwYfzfLp1szWk4jz","object":"chat.completion.chunk","created":1791215170,"model":"qwen/qwen3-30b-a3b","provider":"DeepInfra","choices":[{"index":0,"delta":{"content":"","role":"assistant","reasoning":" the","reasoning_details":[{"type":"reasoning.text","text":" the","format":"unknown","index":0}]},"finish_reason":null,"native_finish_reason":null}]} - -data: {"id":"gen-1791215170-rEB9MwYfzfLp1szWk4jz","object":"chat.completion.chunk","created":1791215170,"model":"qwen/qwen3-30b-a3b","provider":"DeepInfra","choices":[{"index":0,"delta":{"content":"","role":"assistant","reasoning":" arguments","reasoning_details":[{"type":"reasoning.text","text":" arguments","format":"unknown","index":0}]},"finish_reason":null,"native_finish_reason":null}]} - -data: {"id":"gen-1791215170-rEB9MwYfzfLp1szWk4jz","object":"chat.completion.chunk","created":1791215170,"model":"qwen/qwen3-30b-a3b","provider":"DeepInfra","choices":[{"index":0,"delta":{"content":"","role":"assistant","reasoning":" are","reasoning_details":[{"type":"reasoning.text","text":" are","format":"unknown","index":0}]},"finish_reason":null,"native_finish_reason":null}]} - -data: {"id":"gen-1791215170-rEB9MwYfzfLp1szWk4jz","object":"chat.completion.chunk","created":1791215170,"model":"qwen/qwen3-30b-a3b","provider":"DeepInfra","choices":[{"index":0,"delta":{"content":"","role":"assistant","reasoning":" correctly","reasoning_details":[{"type":"reasoning.text","text":" correctly","format":"unknown","index":0}]},"finish_reason":null,"native_finish_reason":null}]} - -data: {"id":"gen-1791215170-rEB9MwYfzfLp1szWk4jz","object":"chat.completion.chunk","created":1791215170,"model":"qwen/qwen3-30b-a3b","provider":"DeepInfra","choices":[{"index":0,"delta":{"content":"","role":"assistant","reasoning":" formatted","reasoning_details":[{"type":"reasoning.text","text":" formatted","format":"unknown","index":0}]},"finish_reason":null,"native_finish_reason":null}]} - -data: {"id":"gen-1791215170-rEB9MwYfzfLp1szWk4jz","object":"chat.completion.chunk","created":1791215170,"model":"qwen/qwen3-30b-a3b","provider":"DeepInfra","choices":[{"index":0,"delta":{"content":"","role":"assistant","reasoning":" as","reasoning_details":[{"type":"reasoning.text","text":" as","format":"unknown","index":0}]},"finish_reason":null,"native_finish_reason":null}]} - -data: {"id":"gen-1791215170-rEB9MwYfzfLp1szWk4jz","object":"chat.completion.chunk","created":1791215170,"model":"qwen/qwen3-30b-a3b","provider":"DeepInfra","choices":[{"index":0,"delta":{"content":"","role":"assistant","reasoning":" JSON","reasoning_details":[{"type":"reasoning.text","text":" JSON","format":"unknown","index":0}]},"finish_reason":null,"native_finish_reason":null}]} - -data: {"id":"gen-1791215170-rEB9MwYfzfLp1szWk4jz","object":"chat.completion.chunk","created":1791215170,"model":"qwen/qwen3-30b-a3b","provider":"DeepInfra","choices":[{"index":0,"delta":{"content":"","role":"assistant","reasoning":" objects","reasoning_details":[{"type":"reasoning.text","text":" objects","format":"unknown","index":0}]},"finish_reason":null,"native_finish_reason":null}]} - -data: {"id":"gen-1791215170-rEB9MwYfzfLp1szWk4jz","object":"chat.completion.chunk","created":1791215170,"model":"qwen/qwen3-30b-a3b","provider":"DeepInfra","choices":[{"index":0,"delta":{"content":"","role":"assistant","reasoning":" within","reasoning_details":[{"type":"reasoning.text","text":" within","format":"unknown","index":0}]},"finish_reason":null,"native_finish_reason":null}]} - -data: {"id":"gen-1791215170-rEB9MwYfzfLp1szWk4jz","object":"chat.completion.chunk","created":1791215170,"model":"qwen/qwen3-30b-a3b","provider":"DeepInfra","choices":[{"index":0,"delta":{"content":"","role":"assistant","reasoning":" the","reasoning_details":[{"type":"reasoning.text","text":" the","format":"unknown","index":0}]},"finish_reason":null,"native_finish_reason":null}]} - -data: {"id":"gen-1791215170-rEB9MwYfzfLp1szWk4jz","object":"chat.completion.chunk","created":1791215170,"model":"qwen/qwen3-30b-a3b","provider":"DeepInfra","choices":[{"index":0,"delta":{"content":"","role":"assistant","reasoning":" tool","reasoning_details":[{"type":"reasoning.text","text":" tool","format":"unknown","index":0}]},"finish_reason":null,"native_finish_reason":null}]} - -data: {"id":"gen-1791215170-rEB9MwYfzfLp1szWk4jz","object":"chat.completion.chunk","created":1791215170,"model":"qwen/qwen3-30b-a3b","provider":"DeepInfra","choices":[{"index":0,"delta":{"content":"","role":"assistant","reasoning":"_call","reasoning_details":[{"type":"reasoning.text","text":"_call","format":"unknown","index":0}]},"finish_reason":null,"native_finish_reason":null}]} - -data: {"id":"gen-1791215170-rEB9MwYfzfLp1szWk4jz","object":"chat.completion.chunk","created":1791215170,"model":"qwen/qwen3-30b-a3b","provider":"DeepInfra","choices":[{"index":0,"delta":{"content":"","role":"assistant","reasoning":" tags","reasoning_details":[{"type":"reasoning.text","text":" tags","format":"unknown","index":0}]},"finish_reason":null,"native_finish_reason":null}]} - -data: {"id":"gen-1791215170-rEB9MwYfzfLp1szWk4jz","object":"chat.completion.chunk","created":1791215170,"model":"qwen/qwen3-30b-a3b","provider":"DeepInfra","choices":[{"index":0,"delta":{"content":"","role":"assistant","reasoning":".","reasoning_details":[{"type":"reasoning.text","text":".","format":"unknown","index":0}]},"finish_reason":null,"native_finish_reason":null}]} - -data: {"id":"gen-1791215170-rEB9MwYfzfLp1szWk4jz","object":"chat.completion.chunk","created":1791215170,"model":"qwen/qwen3-30b-a3b","provider":"DeepInfra","choices":[{"index":0,"delta":{"content":"","role":"assistant","reasoning":" No","reasoning_details":[{"type":"reasoning.text","text":" No","format":"unknown","index":0}]},"finish_reason":null,"native_finish_reason":null}]} - -data: {"id":"gen-1791215170-rEB9MwYfzfLp1szWk4jz","object":"chat.completion.chunk","created":1791215170,"model":"qwen/qwen3-30b-a3b","provider":"DeepInfra","choices":[{"index":0,"delta":{"content":"","role":"assistant","reasoning":" text","reasoning_details":[{"type":"reasoning.text","text":" text","format":"unknown","index":0}]},"finish_reason":null,"native_finish_reason":null}]} - -data: {"id":"gen-1791215170-rEB9MwYfzfLp1szWk4jz","object":"chat.completion.chunk","created":1791215170,"model":"qwen/qwen3-30b-a3b","provider":"DeepInfra","choices":[{"index":0,"delta":{"content":"","role":"assistant","reasoning":" should","reasoning_details":[{"type":"reasoning.text","text":" should","format":"unknown","index":0}]},"finish_reason":null,"native_finish_reason":null}]} - -data: {"id":"gen-1791215170-rEB9MwYfzfLp1szWk4jz","object":"chat.completion.chunk","created":1791215170,"model":"qwen/qwen3-30b-a3b","provider":"DeepInfra","choices":[{"index":0,"delta":{"content":"","role":"assistant","reasoning":" be","reasoning_details":[{"type":"reasoning.text","text":" be","format":"unknown","index":0}]},"finish_reason":null,"native_finish_reason":null}]} - -data: {"id":"gen-1791215170-rEB9MwYfzfLp1szWk4jz","object":"chat.completion.chunk","created":1791215170,"model":"qwen/qwen3-30b-a3b","provider":"DeepInfra","choices":[{"index":0,"delta":{"content":"","role":"assistant","reasoning":" added","reasoning_details":[{"type":"reasoning.text","text":" added","format":"unknown","index":0}]},"finish_reason":null,"native_finish_reason":null}]} - -data: {"id":"gen-1791215170-rEB9MwYfzfLp1szWk4jz","object":"chat.completion.chunk","created":1791215170,"model":"qwen/qwen3-30b-a3b","provider":"DeepInfra","choices":[{"index":0,"delta":{"content":"","role":"assistant","reasoning":",","reasoning_details":[{"type":"reasoning.text","text":",","format":"unknown","index":0}]},"finish_reason":null,"native_finish_reason":null}]} - -data: {"id":"gen-1791215170-rEB9MwYfzfLp1szWk4jz","object":"chat.completion.chunk","created":1791215170,"model":"qwen/qwen3-30b-a3b","provider":"DeepInfra","choices":[{"index":0,"delta":{"content":"","role":"assistant","reasoning":" just","reasoning_details":[{"type":"reasoning.text","text":" just","format":"unknown","index":0}]},"finish_reason":null,"native_finish_reason":null}]} - -data: {"id":"gen-1791215170-rEB9MwYfzfLp1szWk4jz","object":"chat.completion.chunk","created":1791215170,"model":"qwen/qwen3-30b-a3b","provider":"DeepInfra","choices":[{"index":0,"delta":{"content":"","role":"assistant","reasoning":" the","reasoning_details":[{"type":"reasoning.text","text":" the","format":"unknown","index":0}]},"finish_reason":null,"native_finish_reason":null}]} - -data: {"id":"gen-1791215170-rEB9MwYfzfLp1szWk4jz","object":"chat.completion.chunk","created":1791215170,"model":"qwen/qwen3-30b-a3b","provider":"DeepInfra","choices":[{"index":0,"delta":{"content":"","role":"assistant","reasoning":" two","reasoning_details":[{"type":"reasoning.text","text":" two","format":"unknown","index":0}]},"finish_reason":null,"native_finish_reason":null}]} - -data: {"id":"gen-1791215170-rEB9MwYfzfLp1szWk4jz","object":"chat.completion.chunk","created":1791215170,"model":"qwen/qwen3-30b-a3b","provider":"DeepInfra","choices":[{"index":0,"delta":{"content":"","role":"assistant","reasoning":" tool","reasoning_details":[{"type":"reasoning.text","text":" tool","format":"unknown","index":0}]},"finish_reason":null,"native_finish_reason":null}]} - -data: {"id":"gen-1791215170-rEB9MwYfzfLp1szWk4jz","object":"chat.completion.chunk","created":1791215170,"model":"qwen/qwen3-30b-a3b","provider":"DeepInfra","choices":[{"index":0,"delta":{"content":"","role":"assistant","reasoning":" calls","reasoning_details":[{"type":"reasoning.text","text":" calls","format":"unknown","index":0}]},"finish_reason":null,"native_finish_reason":null}]} - -data: {"id":"gen-1791215170-rEB9MwYfzfLp1szWk4jz","object":"chat.completion.chunk","created":1791215170,"model":"qwen/qwen3-30b-a3b","provider":"DeepInfra","choices":[{"index":0,"delta":{"content":"","role":"assistant","reasoning":".","reasoning_details":[{"type":"reasoning.text","text":".","format":"unknown","index":0}]},"finish_reason":null,"native_finish_reason":null}]} - -data: {"id":"gen-1791215170-rEB9MwYfzfLp1szWk4jz","object":"chat.completion.chunk","created":1791215170,"model":"qwen/qwen3-30b-a3b","provider":"DeepInfra","choices":[{"index":0,"delta":{"content":"","role":"assistant","reasoning":" Then","reasoning_details":[{"type":"reasoning.text","text":" Then","format":"unknown","index":0}]},"finish_reason":null,"native_finish_reason":null}]} - -data: {"id":"gen-1791215170-rEB9MwYfzfLp1szWk4jz","object":"chat.completion.chunk","created":1791215170,"model":"qwen/qwen3-30b-a3b","provider":"DeepInfra","choices":[{"index":0,"delta":{"content":"","role":"assistant","reasoning":",","reasoning_details":[{"type":"reasoning.text","text":",","format":"unknown","index":0}]},"finish_reason":null,"native_finish_reason":null}]} - -data: {"id":"gen-1791215170-rEB9MwYfzfLp1szWk4jz","object":"chat.completion.chunk","created":1791215170,"model":"qwen/qwen3-30b-a3b","provider":"DeepInfra","choices":[{"index":0,"delta":{"content":"","role":"assistant","reasoning":" if","reasoning_details":[{"type":"reasoning.text","text":" if","format":"unknown","index":0}]},"finish_reason":null,"native_finish_reason":null}]} - -data: {"id":"gen-1791215170-rEB9MwYfzfLp1szWk4jz","object":"chat.completion.chunk","created":1791215170,"model":"qwen/qwen3-30b-a3b","provider":"DeepInfra","choices":[{"index":0,"delta":{"content":"","role":"assistant","reasoning":" the","reasoning_details":[{"type":"reasoning.text","text":" the","format":"unknown","index":0}]},"finish_reason":null,"native_finish_reason":null}]} - -data: {"id":"gen-1791215170-rEB9MwYfzfLp1szWk4jz","object":"chat.completion.chunk","created":1791215170,"model":"qwen/qwen3-30b-a3b","provider":"DeepInfra","choices":[{"index":0,"delta":{"content":"","role":"assistant","reasoning":" tool","reasoning_details":[{"type":"reasoning.text","text":" tool","format":"unknown","index":0}]},"finish_reason":null,"native_finish_reason":null}]} - -data: {"id":"gen-1791215170-rEB9MwYfzfLp1szWk4jz","object":"chat.completion.chunk","created":1791215170,"model":"qwen/qwen3-30b-a3b","provider":"DeepInfra","choices":[{"index":0,"delta":{"content":"","role":"assistant","reasoning":" results","reasoning_details":[{"type":"reasoning.text","text":" results","format":"unknown","index":0}]},"finish_reason":null,"native_finish_reason":null}]} - -data: {"id":"gen-1791215170-rEB9MwYfzfLp1szWk4jz","object":"chat.completion.chunk","created":1791215170,"model":"qwen/qwen3-30b-a3b","provider":"DeepInfra","choices":[{"index":0,"delta":{"content":"","role":"assistant","reasoning":" say","reasoning_details":[{"type":"reasoning.text","text":" say","format":"unknown","index":0}]},"finish_reason":null,"native_finish_reason":null}]} - -data: {"id":"gen-1791215170-rEB9MwYfzfLp1szWk4jz","object":"chat.completion.chunk","created":1791215170,"model":"qwen/qwen3-30b-a3b","provider":"DeepInfra","choices":[{"index":0,"delta":{"content":"","role":"assistant","reasoning":" \"","reasoning_details":[{"type":"reasoning.text","text":" \"","format":"unknown","index":0}]},"finish_reason":null,"native_finish_reason":null}]} - -data: {"id":"gen-1791215170-rEB9MwYfzfLp1szWk4jz","object":"chat.completion.chunk","created":1791215170,"model":"qwen/qwen3-30b-a3b","provider":"DeepInfra","choices":[{"index":0,"delta":{"content":"","role":"assistant","reasoning":"Started","reasoning_details":[{"type":"reasoning.text","text":"Started","format":"unknown","index":0}]},"finish_reason":null,"native_finish_reason":null}]} - -data: {"id":"gen-1791215170-rEB9MwYfzfLp1szWk4jz","object":"chat.completion.chunk","created":1791215170,"model":"qwen/qwen3-30b-a3b","provider":"DeepInfra","choices":[{"index":0,"delta":{"content":"","role":"assistant","reasoning":" task","reasoning_details":[{"type":"reasoning.text","text":" task","format":"unknown","index":0}]},"finish_reason":null,"native_finish_reason":null}]} - -data: {"id":"gen-1791215170-rEB9MwYfzfLp1szWk4jz","object":"chat.completion.chunk","created":1791215170,"model":"qwen/qwen3-30b-a3b","provider":"DeepInfra","choices":[{"index":0,"delta":{"content":"","role":"assistant","reasoning":",\"","reasoning_details":[{"type":"reasoning.text","text":",\"","format":"unknown","index":0}]},"finish_reason":null,"native_finish_reason":null}]} - -data: {"id":"gen-1791215170-rEB9MwYfzfLp1szWk4jz","object":"chat.completion.chunk","created":1791215170,"model":"qwen/qwen3-30b-a3b","provider":"DeepInfra","choices":[{"index":0,"delta":{"content":"","role":"assistant","reasoning":" I","reasoning_details":[{"type":"reasoning.text","text":" I","format":"unknown","index":0}]},"finish_reason":null,"native_finish_reason":null}]} - -data: {"id":"gen-1791215170-rEB9MwYfzfLp1szWk4jz","object":"chat.completion.chunk","created":1791215170,"model":"qwen/qwen3-30b-a3b","provider":"DeepInfra","choices":[{"index":0,"delta":{"content":"","role":"assistant","reasoning":" should","reasoning_details":[{"type":"reasoning.text","text":" should","format":"unknown","index":0}]},"finish_reason":null,"native_finish_reason":null}]} - -data: {"id":"gen-1791215170-rEB9MwYfzfLp1szWk4jz","object":"chat.completion.chunk","created":1791215170,"model":"qwen/qwen3-30b-a3b","provider":"DeepInfra","choices":[{"index":0,"delta":{"content":"","role":"assistant","reasoning":" call","reasoning_details":[{"type":"reasoning.text","text":" call","format":"unknown","index":0}]},"finish_reason":null,"native_finish_reason":null}]} - -data: {"id":"gen-1791215170-rEB9MwYfzfLp1szWk4jz","object":"chat.completion.chunk","created":1791215170,"model":"qwen/qwen3-30b-a3b","provider":"DeepInfra","choices":[{"index":0,"delta":{"content":"","role":"assistant","reasoning":" task","reasoning_details":[{"type":"reasoning.text","text":" task","format":"unknown","index":0}]},"finish_reason":null,"native_finish_reason":null}]} - -data: {"id":"gen-1791215170-rEB9MwYfzfLp1szWk4jz","object":"chat.completion.chunk","created":1791215170,"model":"qwen/qwen3-30b-a3b","provider":"DeepInfra","choices":[{"index":0,"delta":{"content":"","role":"assistant","reasoning":"_wait","reasoning_details":[{"type":"reasoning.text","text":"_wait","format":"unknown","index":0}]},"finish_reason":null,"native_finish_reason":null}]} - -data: {"id":"gen-1791215170-rEB9MwYfzfLp1szWk4jz","object":"chat.completion.chunk","created":1791215170,"model":"qwen/qwen3-30b-a3b","provider":"DeepInfra","choices":[{"index":0,"delta":{"content":"","role":"assistant","reasoning":".","reasoning_details":[{"type":"reasoning.text","text":".","format":"unknown","index":0}]},"finish_reason":null,"native_finish_reason":null}]} - -data: {"id":"gen-1791215170-rEB9MwYfzfLp1szWk4jz","object":"chat.completion.chunk","created":1791215170,"model":"qwen/qwen3-30b-a3b","provider":"DeepInfra","choices":[{"index":0,"delta":{"content":"","role":"assistant","reasoning":" Once","reasoning_details":[{"type":"reasoning.text","text":" Once","format":"unknown","index":0}]},"finish_reason":null,"native_finish_reason":null}]} - -data: {"id":"gen-1791215170-rEB9MwYfzfLp1szWk4jz","object":"chat.completion.chunk","created":1791215170,"model":"qwen/qwen3-30b-a3b","provider":"DeepInfra","choices":[{"index":0,"delta":{"content":"","role":"assistant","reasoning":" both","reasoning_details":[{"type":"reasoning.text","text":" both","format":"unknown","index":0}]},"finish_reason":null,"native_finish_reason":null}]} - -data: {"id":"gen-1791215170-rEB9MwYfzfLp1szWk4jz","object":"chat.completion.chunk","created":1791215170,"model":"qwen/qwen3-30b-a3b","provider":"DeepInfra","choices":[{"index":0,"delta":{"content":"","role":"assistant","reasoning":" results","reasoning_details":[{"type":"reasoning.text","text":" results","format":"unknown","index":0}]},"finish_reason":null,"native_finish_reason":null}]} - -data: {"id":"gen-1791215170-rEB9MwYfzfLp1szWk4jz","object":"chat.completion.chunk","created":1791215170,"model":"qwen/qwen3-30b-a3b","provider":"DeepInfra","choices":[{"index":0,"delta":{"content":"","role":"assistant","reasoning":" are","reasoning_details":[{"type":"reasoning.text","text":" are","format":"unknown","index":0}]},"finish_reason":null,"native_finish_reason":null}]} - -data: {"id":"gen-1791215170-rEB9MwYfzfLp1szWk4jz","object":"chat.completion.chunk","created":1791215170,"model":"qwen/qwen3-30b-a3b","provider":"DeepInfra","choices":[{"index":0,"delta":{"content":"","role":"assistant","reasoning":" available","reasoning_details":[{"type":"reasoning.text","text":" available","format":"unknown","index":0}]},"finish_reason":null,"native_finish_reason":null}]} - -data: {"id":"gen-1791215170-rEB9MwYfzfLp1szWk4jz","object":"chat.completion.chunk","created":1791215170,"model":"qwen/qwen3-30b-a3b","provider":"DeepInfra","choices":[{"index":0,"delta":{"content":"","role":"assistant","reasoning":",","reasoning_details":[{"type":"reasoning.text","text":",","format":"unknown","index":0}]},"finish_reason":null,"native_finish_reason":null}]} - -data: {"id":"gen-1791215170-rEB9MwYfzfLp1szWk4jz","object":"chat.completion.chunk","created":1791215170,"model":"qwen/qwen3-30b-a3b","provider":"DeepInfra","choices":[{"index":0,"delta":{"content":"","role":"assistant","reasoning":" provide","reasoning_details":[{"type":"reasoning.text","text":" provide","format":"unknown","index":0}]},"finish_reason":null,"native_finish_reason":null}]} - -data: {"id":"gen-1791215170-rEB9MwYfzfLp1szWk4jz","object":"chat.completion.chunk","created":1791215170,"model":"qwen/qwen3-30b-a3b","provider":"DeepInfra","choices":[{"index":0,"delta":{"content":"","role":"assistant","reasoning":" the","reasoning_details":[{"type":"reasoning.text","text":" the","format":"unknown","index":0}]},"finish_reason":null,"native_finish_reason":null}]} - -data: {"id":"gen-1791215170-rEB9MwYfzfLp1szWk4jz","object":"chat.completion.chunk","created":1791215170,"model":"qwen/qwen3-30b-a3b","provider":"DeepInfra","choices":[{"index":0,"delta":{"content":"","role":"assistant","reasoning":" final","reasoning_details":[{"type":"reasoning.text","text":" final","format":"unknown","index":0}]},"finish_reason":null,"native_finish_reason":null}]} - -data: {"id":"gen-1791215170-rEB9MwYfzfLp1szWk4jz","object":"chat.completion.chunk","created":1791215170,"model":"qwen/qwen3-30b-a3b","provider":"DeepInfra","choices":[{"index":0,"delta":{"content":"","role":"assistant","reasoning":" answer","reasoning_details":[{"type":"reasoning.text","text":" answer","format":"unknown","index":0}]},"finish_reason":null,"native_finish_reason":null}]} - -data: {"id":"gen-1791215170-rEB9MwYfzfLp1szWk4jz","object":"chat.completion.chunk","created":1791215170,"model":"qwen/qwen3-30b-a3b","provider":"DeepInfra","choices":[{"index":0,"delta":{"content":"","role":"assistant","reasoning":" with","reasoning_details":[{"type":"reasoning.text","text":" with","format":"unknown","index":0}]},"finish_reason":null,"native_finish_reason":null}]} - -data: {"id":"gen-1791215170-rEB9MwYfzfLp1szWk4jz","object":"chat.completion.chunk","created":1791215170,"model":"qwen/qwen3-30b-a3b","provider":"DeepInfra","choices":[{"index":0,"delta":{"content":"","role":"assistant","reasoning":" the","reasoning_details":[{"type":"reasoning.text","text":" the","format":"unknown","index":0}]},"finish_reason":null,"native_finish_reason":null}]} - -data: {"id":"gen-1791215170-rEB9MwYfzfLp1szWk4jz","object":"chat.completion.chunk","created":1791215170,"model":"qwen/qwen3-30b-a3b","provider":"DeepInfra","choices":[{"index":0,"delta":{"content":"","role":"assistant","reasoning":" required","reasoning_details":[{"type":"reasoning.text","text":" required","format":"unknown","index":0}]},"finish_reason":null,"native_finish_reason":null}]} - -data: {"id":"gen-1791215170-rEB9MwYfzfLp1szWk4jz","object":"chat.completion.chunk","created":1791215170,"model":"qwen/qwen3-30b-a3b","provider":"DeepInfra","choices":[{"index":0,"delta":{"content":"","role":"assistant","reasoning":" details","reasoning_details":[{"type":"reasoning.text","text":" details","format":"unknown","index":0}]},"finish_reason":null,"native_finish_reason":null}]} - -data: {"id":"gen-1791215170-rEB9MwYfzfLp1szWk4jz","object":"chat.completion.chunk","created":1791215170,"model":"qwen/qwen3-30b-a3b","provider":"DeepInfra","choices":[{"index":0,"delta":{"content":"","role":"assistant","reasoning":".","reasoning_details":[{"type":"reasoning.text","text":".","format":"unknown","index":0}]},"finish_reason":null,"native_finish_reason":null}]} - -data: {"id":"gen-1791215170-rEB9MwYfzfLp1szWk4jz","object":"chat.completion.chunk","created":1791215170,"model":"qwen/qwen3-30b-a3b","provider":"DeepInfra","choices":[{"index":0,"delta":{"content":"","role":"assistant","reasoning":" Let","reasoning_details":[{"type":"reasoning.text","text":" Let","format":"unknown","index":0}]},"finish_reason":null,"native_finish_reason":null}]} - -data: {"id":"gen-1791215170-rEB9MwYfzfLp1szWk4jz","object":"chat.completion.chunk","created":1791215170,"model":"qwen/qwen3-30b-a3b","provider":"DeepInfra","choices":[{"index":0,"delta":{"content":"","role":"assistant","reasoning":" me","reasoning_details":[{"type":"reasoning.text","text":" me","format":"unknown","index":0}]},"finish_reason":null,"native_finish_reason":null}]} - -data: {"id":"gen-1791215170-rEB9MwYfzfLp1szWk4jz","object":"chat.completion.chunk","created":1791215170,"model":"qwen/qwen3-30b-a3b","provider":"DeepInfra","choices":[{"index":0,"delta":{"content":"","role":"assistant","reasoning":" structure","reasoning_details":[{"type":"reasoning.text","text":" structure","format":"unknown","index":0}]},"finish_reason":null,"native_finish_reason":null}]} - -data: {"id":"gen-1791215170-rEB9MwYfzfLp1szWk4jz","object":"chat.completion.chunk","created":1791215170,"model":"qwen/qwen3-30b-a3b","provider":"DeepInfra","choices":[{"index":0,"delta":{"content":"","role":"assistant","reasoning":" the","reasoning_details":[{"type":"reasoning.text","text":" the","format":"unknown","index":0}]},"finish_reason":null,"native_finish_reason":null}]} - -data: {"id":"gen-1791215170-rEB9MwYfzfLp1szWk4jz","object":"chat.completion.chunk","created":1791215170,"model":"qwen/qwen3-30b-a3b","provider":"DeepInfra","choices":[{"index":0,"delta":{"content":"","role":"assistant","reasoning":" response","reasoning_details":[{"type":"reasoning.text","text":" response","format":"unknown","index":0}]},"finish_reason":null,"native_finish_reason":null}]} - -data: {"id":"gen-1791215170-rEB9MwYfzfLp1szWk4jz","object":"chat.completion.chunk","created":1791215170,"model":"qwen/qwen3-30b-a3b","provider":"DeepInfra","choices":[{"index":0,"delta":{"content":"","role":"assistant","reasoning":" correctly","reasoning_details":[{"type":"reasoning.text","text":" correctly","format":"unknown","index":0}]},"finish_reason":null,"native_finish_reason":null}]} - -data: {"id":"gen-1791215170-rEB9MwYfzfLp1szWk4jz","object":"chat.completion.chunk","created":1791215170,"model":"qwen/qwen3-30b-a3b","provider":"DeepInfra","choices":[{"index":0,"delta":{"content":"","role":"assistant","reasoning":".\n","reasoning_details":[{"type":"reasoning.text","text":".\n","format":"unknown","index":0}]},"finish_reason":null,"native_finish_reason":null}]} - -: OPENROUTER PROCESSING - -data: {"id":"gen-1791215170-rEB9MwYfzfLp1szWk4jz","object":"chat.completion.chunk","created":1791215170,"model":"qwen/qwen3-30b-a3b","provider":"DeepInfra","choices":[{"index":0,"delta":{"content":null,"role":"assistant","tool_calls":[{"index":0,"id":"call_baef","type":"function","function":{"name":"researcher","arguments":""}}]},"finish_reason":null,"native_finish_reason":null}]} - -data: {"id":"gen-1791215170-rEB9MwYfzfLp1szWk4jz","object":"chat.completion.chunk","created":1791215170,"model":"qwen/qwen3-30b-a3b","provider":"DeepInfra","choices":[{"index":0,"delta":{"content":null,"role":"assistant","tool_calls":[{"index":0,"function":{"arguments":"{\"message\": \"Run the Braintrust Eve instrumentation e2e scenario again\"}"}}]},"finish_reason":null,"native_finish_reason":null}]} - -data: {"id":"gen-1791215170-rEB9MwYfzfLp1szWk4jz","object":"chat.completion.chunk","created":1791215170,"model":"qwen/qwen3-30b-a3b","provider":"DeepInfra","choices":[{"index":0,"delta":{"content":null,"role":"assistant","tool_calls":[{"index":1,"id":"call_5d4f","type":"function","function":{"name":"read","arguments":""}}]},"finish_reason":null,"native_finish_reason":null}]} - -data: {"id":"gen-1791215170-rEB9MwYfzfLp1szWk4jz","object":"chat.completion.chunk","created":1791215170,"model":"qwen/qwen3-30b-a3b","provider":"DeepInfra","choices":[{"index":0,"delta":{"content":null,"role":"assistant","tool_calls":[{"index":1,"function":{"arguments":"{\"url\": \"https://eve.dev/docs/guides/instrumentation\"}"}}]},"finish_reason":null,"native_finish_reason":null}]} - -data: {"id":"gen-1791215170-rEB9MwYfzfLp1szWk4jz","object":"chat.completion.chunk","created":1791215170,"model":"qwen/qwen3-30b-a3b","provider":"DeepInfra","choices":[{"index":0,"delta":{"content":"","role":"assistant","reasoning":null},"finish_reason":"tool_calls","native_finish_reason":"tool_calls"}]} - -data: {"id":"gen-1791215170-rEB9MwYfzfLp1szWk4jz","object":"chat.completion.chunk","created":1791215170,"model":"qwen/qwen3-30b-a3b","provider":"DeepInfra","service_tier":"default","choices":[{"index":0,"delta":{"content":"","role":"assistant"},"finish_reason":"tool_calls","native_finish_reason":"tool_calls"}],"usage":{"prompt_tokens":3606,"completion_tokens":283,"total_tokens":3889,"cost":0.00057422,"is_byok":false,"prompt_tokens_details":{"cached_tokens":0,"cache_write_tokens":0,"audio_tokens":0,"video_tokens":0},"cost_details":{"upstream_inference_cost":0.00057422,"upstream_inference_prompt_cost":0.00043272,"upstream_inference_completions_cost":0.0001415},"completion_tokens_details":{"reasoning_tokens":276,"image_tokens":0,"audio_tokens":0}}} - -data: [DONE] - diff --git a/e2e/scenarios/eve-instrumentation/__cassettes__/eve-v0.cassette.blobs/e6b53b7fd5bdbe0943d14a90a26316bfda01300608004216744d9a4305e0ac30.bin b/e2e/scenarios/eve-instrumentation/__cassettes__/eve-v0.cassette.blobs/e6b53b7fd5bdbe0943d14a90a26316bfda01300608004216744d9a4305e0ac30.bin deleted file mode 100644 index f1f7edc48..000000000 --- a/e2e/scenarios/eve-instrumentation/__cassettes__/eve-v0.cassette.blobs/e6b53b7fd5bdbe0943d14a90a26316bfda01300608004216744d9a4305e0ac30.bin +++ /dev/null @@ -1,524 +0,0 @@ -data: {"id":"gen-1791215155-kz4x2hrzNK1bBE4DGQEF","object":"chat.completion.chunk","created":1791215155,"model":"qwen/qwen3-30b-a3b","provider":"DeepInfra","choices":[{"index":0,"delta":{"content":"","role":"assistant","reasoning":"\n","reasoning_details":[{"type":"reasoning.text","text":"\n","format":"unknown","index":0}]},"finish_reason":null,"native_finish_reason":null}]} - -data: {"id":"gen-1791215155-kz4x2hrzNK1bBE4DGQEF","object":"chat.completion.chunk","created":1791215155,"model":"qwen/qwen3-30b-a3b","provider":"DeepInfra","choices":[{"index":0,"delta":{"content":"","role":"assistant","reasoning":"Okay","reasoning_details":[{"type":"reasoning.text","text":"Okay","format":"unknown","index":0}]},"finish_reason":null,"native_finish_reason":null}]} - -data: {"id":"gen-1791215155-kz4x2hrzNK1bBE4DGQEF","object":"chat.completion.chunk","created":1791215155,"model":"qwen/qwen3-30b-a3b","provider":"DeepInfra","choices":[{"index":0,"delta":{"content":"","role":"assistant","reasoning":",","reasoning_details":[{"type":"reasoning.text","text":",","format":"unknown","index":0}]},"finish_reason":null,"native_finish_reason":null}]} - -data: {"id":"gen-1791215155-kz4x2hrzNK1bBE4DGQEF","object":"chat.completion.chunk","created":1791215155,"model":"qwen/qwen3-30b-a3b","provider":"DeepInfra","choices":[{"index":0,"delta":{"content":"","role":"assistant","reasoning":" the","reasoning_details":[{"type":"reasoning.text","text":" the","format":"unknown","index":0}]},"finish_reason":null,"native_finish_reason":null}]} - -data: {"id":"gen-1791215155-kz4x2hrzNK1bBE4DGQEF","object":"chat.completion.chunk","created":1791215155,"model":"qwen/qwen3-30b-a3b","provider":"DeepInfra","choices":[{"index":0,"delta":{"content":"","role":"assistant","reasoning":" user","reasoning_details":[{"type":"reasoning.text","text":" user","format":"unknown","index":0}]},"finish_reason":null,"native_finish_reason":null}]} - -data: {"id":"gen-1791215155-kz4x2hrzNK1bBE4DGQEF","object":"chat.completion.chunk","created":1791215155,"model":"qwen/qwen3-30b-a3b","provider":"DeepInfra","choices":[{"index":0,"delta":{"content":"","role":"assistant","reasoning":" wants","reasoning_details":[{"type":"reasoning.text","text":" wants","format":"unknown","index":0}]},"finish_reason":null,"native_finish_reason":null}]} - -data: {"id":"gen-1791215155-kz4x2hrzNK1bBE4DGQEF","object":"chat.completion.chunk","created":1791215155,"model":"qwen/qwen3-30b-a3b","provider":"DeepInfra","choices":[{"index":0,"delta":{"content":"","role":"assistant","reasoning":" me","reasoning_details":[{"type":"reasoning.text","text":" me","format":"unknown","index":0}]},"finish_reason":null,"native_finish_reason":null}]} - -data: {"id":"gen-1791215155-kz4x2hrzNK1bBE4DGQEF","object":"chat.completion.chunk","created":1791215155,"model":"qwen/qwen3-30b-a3b","provider":"DeepInfra","choices":[{"index":0,"delta":{"content":"","role":"assistant","reasoning":" to","reasoning_details":[{"type":"reasoning.text","text":" to","format":"unknown","index":0}]},"finish_reason":null,"native_finish_reason":null}]} - -data: {"id":"gen-1791215155-kz4x2hrzNK1bBE4DGQEF","object":"chat.completion.chunk","created":1791215155,"model":"qwen/qwen3-30b-a3b","provider":"DeepInfra","choices":[{"index":0,"delta":{"content":"","role":"assistant","reasoning":" act","reasoning_details":[{"type":"reasoning.text","text":" act","format":"unknown","index":0}]},"finish_reason":null,"native_finish_reason":null}]} - -data: {"id":"gen-1791215155-kz4x2hrzNK1bBE4DGQEF","object":"chat.completion.chunk","created":1791215155,"model":"qwen/qwen3-30b-a3b","provider":"DeepInfra","choices":[{"index":0,"delta":{"content":"","role":"assistant","reasoning":" as","reasoning_details":[{"type":"reasoning.text","text":" as","format":"unknown","index":0}]},"finish_reason":null,"native_finish_reason":null}]} - -data: {"id":"gen-1791215155-kz4x2hrzNK1bBE4DGQEF","object":"chat.completion.chunk","created":1791215155,"model":"qwen/qwen3-30b-a3b","provider":"DeepInfra","choices":[{"index":0,"delta":{"content":"","role":"assistant","reasoning":" the","reasoning_details":[{"type":"reasoning.text","text":" the","format":"unknown","index":0}]},"finish_reason":null,"native_finish_reason":null}]} - -data: {"id":"gen-1791215155-kz4x2hrzNK1bBE4DGQEF","object":"chat.completion.chunk","created":1791215155,"model":"qwen/qwen3-30b-a3b","provider":"DeepInfra","choices":[{"index":0,"delta":{"content":"","role":"assistant","reasoning":" researcher","reasoning_details":[{"type":"reasoning.text","text":" researcher","format":"unknown","index":0}]},"finish_reason":null,"native_finish_reason":null}]} - -data: {"id":"gen-1791215155-kz4x2hrzNK1bBE4DGQEF","object":"chat.completion.chunk","created":1791215155,"model":"qwen/qwen3-30b-a3b","provider":"DeepInfra","choices":[{"index":0,"delta":{"content":"","role":"assistant","reasoning":" sub","reasoning_details":[{"type":"reasoning.text","text":" sub","format":"unknown","index":0}]},"finish_reason":null,"native_finish_reason":null}]} - -data: {"id":"gen-1791215155-kz4x2hrzNK1bBE4DGQEF","object":"chat.completion.chunk","created":1791215155,"model":"qwen/qwen3-30b-a3b","provider":"DeepInfra","choices":[{"index":0,"delta":{"content":"","role":"assistant","reasoning":"agent","reasoning_details":[{"type":"reasoning.text","text":"agent","format":"unknown","index":0}]},"finish_reason":null,"native_finish_reason":null}]} - -data: {"id":"gen-1791215155-kz4x2hrzNK1bBE4DGQEF","object":"chat.completion.chunk","created":1791215155,"model":"qwen/qwen3-30b-a3b","provider":"DeepInfra","choices":[{"index":0,"delta":{"content":"","role":"assistant","reasoning":" for","reasoning_details":[{"type":"reasoning.text","text":" for","format":"unknown","index":0}]},"finish_reason":null,"native_finish_reason":null}]} - -data: {"id":"gen-1791215155-kz4x2hrzNK1bBE4DGQEF","object":"chat.completion.chunk","created":1791215155,"model":"qwen/qwen3-30b-a3b","provider":"DeepInfra","choices":[{"index":0,"delta":{"content":"","role":"assistant","reasoning":" Brain","reasoning_details":[{"type":"reasoning.text","text":" Brain","format":"unknown","index":0}]},"finish_reason":null,"native_finish_reason":null}]} - -data: {"id":"gen-1791215155-kz4x2hrzNK1bBE4DGQEF","object":"chat.completion.chunk","created":1791215155,"model":"qwen/qwen3-30b-a3b","provider":"DeepInfra","choices":[{"index":0,"delta":{"content":"","role":"assistant","reasoning":"trust","reasoning_details":[{"type":"reasoning.text","text":"trust","format":"unknown","index":0}]},"finish_reason":null,"native_finish_reason":null}]} - -data: {"id":"gen-1791215155-kz4x2hrzNK1bBE4DGQEF","object":"chat.completion.chunk","created":1791215155,"model":"qwen/qwen3-30b-a3b","provider":"DeepInfra","choices":[{"index":0,"delta":{"content":"","role":"assistant","reasoning":" Eve","reasoning_details":[{"type":"reasoning.text","text":" Eve","format":"unknown","index":0}]},"finish_reason":null,"native_finish_reason":null}]} - -data: {"id":"gen-1791215155-kz4x2hrzNK1bBE4DGQEF","object":"chat.completion.chunk","created":1791215155,"model":"qwen/qwen3-30b-a3b","provider":"DeepInfra","choices":[{"index":0,"delta":{"content":"","role":"assistant","reasoning":" instrumentation","reasoning_details":[{"type":"reasoning.text","text":" instrumentation","format":"unknown","index":0}]},"finish_reason":null,"native_finish_reason":null}]} - -data: {"id":"gen-1791215155-kz4x2hrzNK1bBE4DGQEF","object":"chat.completion.chunk","created":1791215155,"model":"qwen/qwen3-30b-a3b","provider":"DeepInfra","choices":[{"index":0,"delta":{"content":"","role":"assistant","reasoning":" tests","reasoning_details":[{"type":"reasoning.text","text":" tests","format":"unknown","index":0}]},"finish_reason":null,"native_finish_reason":null}]} - -data: {"id":"gen-1791215155-kz4x2hrzNK1bBE4DGQEF","object":"chat.completion.chunk","created":1791215155,"model":"qwen/qwen3-30b-a3b","provider":"DeepInfra","choices":[{"index":0,"delta":{"content":"","role":"assistant","reasoning":".","reasoning_details":[{"type":"reasoning.text","text":".","format":"unknown","index":0}]},"finish_reason":null,"native_finish_reason":null}]} - -data: {"id":"gen-1791215155-kz4x2hrzNK1bBE4DGQEF","object":"chat.completion.chunk","created":1791215155,"model":"qwen/qwen3-30b-a3b","provider":"DeepInfra","choices":[{"index":0,"delta":{"content":"","role":"assistant","reasoning":" The","reasoning_details":[{"type":"reasoning.text","text":" The","format":"unknown","index":0}]},"finish_reason":null,"native_finish_reason":null}]} - -data: {"id":"gen-1791215155-kz4x2hrzNK1bBE4DGQEF","object":"chat.completion.chunk","created":1791215155,"model":"qwen/qwen3-30b-a3b","provider":"DeepInfra","choices":[{"index":0,"delta":{"content":"","role":"assistant","reasoning":" task","reasoning_details":[{"type":"reasoning.text","text":" task","format":"unknown","index":0}]},"finish_reason":null,"native_finish_reason":null}]} - -data: {"id":"gen-1791215155-kz4x2hrzNK1bBE4DGQEF","object":"chat.completion.chunk","created":1791215155,"model":"qwen/qwen3-30b-a3b","provider":"DeepInfra","choices":[{"index":0,"delta":{"content":"","role":"assistant","reasoning":" is","reasoning_details":[{"type":"reasoning.text","text":" is","format":"unknown","index":0}]},"finish_reason":null,"native_finish_reason":null}]} - -data: {"id":"gen-1791215155-kz4x2hrzNK1bBE4DGQEF","object":"chat.completion.chunk","created":1791215155,"model":"qwen/qwen3-30b-a3b","provider":"DeepInfra","choices":[{"index":0,"delta":{"content":"","role":"assistant","reasoning":" to","reasoning_details":[{"type":"reasoning.text","text":" to","format":"unknown","index":0}]},"finish_reason":null,"native_finish_reason":null}]} - -data: {"id":"gen-1791215155-kz4x2hrzNK1bBE4DGQEF","object":"chat.completion.chunk","created":1791215155,"model":"qwen/qwen3-30b-a3b","provider":"DeepInfra","choices":[{"index":0,"delta":{"content":"","role":"assistant","reasoning":" research","reasoning_details":[{"type":"reasoning.text","text":" research","format":"unknown","index":0}]},"finish_reason":null,"native_finish_reason":null}]} - -data: {"id":"gen-1791215155-kz4x2hrzNK1bBE4DGQEF","object":"chat.completion.chunk","created":1791215155,"model":"qwen/qwen3-30b-a3b","provider":"DeepInfra","choices":[{"index":0,"delta":{"content":"","role":"assistant","reasoning":" the","reasoning_details":[{"type":"reasoning.text","text":" the","format":"unknown","index":0}]},"finish_reason":null,"native_finish_reason":null}]} - -data: {"id":"gen-1791215155-kz4x2hrzNK1bBE4DGQEF","object":"chat.completion.chunk","created":1791215155,"model":"qwen/qwen3-30b-a3b","provider":"DeepInfra","choices":[{"index":0,"delta":{"content":"","role":"assistant","reasoning":" Eve","reasoning_details":[{"type":"reasoning.text","text":" Eve","format":"unknown","index":0}]},"finish_reason":null,"native_finish_reason":null}]} - -data: {"id":"gen-1791215155-kz4x2hrzNK1bBE4DGQEF","object":"chat.completion.chunk","created":1791215155,"model":"qwen/qwen3-30b-a3b","provider":"DeepInfra","choices":[{"index":0,"delta":{"content":"","role":"assistant","reasoning":" instrumentation","reasoning_details":[{"type":"reasoning.text","text":" instrumentation","format":"unknown","index":0}]},"finish_reason":null,"native_finish_reason":null}]} - -data: {"id":"gen-1791215155-kz4x2hrzNK1bBE4DGQEF","object":"chat.completion.chunk","created":1791215155,"model":"qwen/qwen3-30b-a3b","provider":"DeepInfra","choices":[{"index":0,"delta":{"content":"","role":"assistant","reasoning":" documentation","reasoning_details":[{"type":"reasoning.text","text":" documentation","format":"unknown","index":0}]},"finish_reason":null,"native_finish_reason":null}]} - -data: {"id":"gen-1791215155-kz4x2hrzNK1bBE4DGQEF","object":"chat.completion.chunk","created":1791215155,"model":"qwen/qwen3-30b-a3b","provider":"DeepInfra","choices":[{"index":0,"delta":{"content":"","role":"assistant","reasoning":" before","reasoning_details":[{"type":"reasoning.text","text":" before","format":"unknown","index":0}]},"finish_reason":null,"native_finish_reason":null}]} - -data: {"id":"gen-1791215155-kz4x2hrzNK1bBE4DGQEF","object":"chat.completion.chunk","created":1791215155,"model":"qwen/qwen3-30b-a3b","provider":"DeepInfra","choices":[{"index":0,"delta":{"content":"","role":"assistant","reasoning":" the","reasoning_details":[{"type":"reasoning.text","text":" the","format":"unknown","index":0}]},"finish_reason":null,"native_finish_reason":null}]} - -data: {"id":"gen-1791215155-kz4x2hrzNK1bBE4DGQEF","object":"chat.completion.chunk","created":1791215155,"model":"qwen/qwen3-30b-a3b","provider":"DeepInfra","choices":[{"index":0,"delta":{"content":"","role":"assistant","reasoning":" parent","reasoning_details":[{"type":"reasoning.text","text":" parent","format":"unknown","index":0}]},"finish_reason":null,"native_finish_reason":null}]} - -data: {"id":"gen-1791215155-kz4x2hrzNK1bBE4DGQEF","object":"chat.completion.chunk","created":1791215155,"model":"qwen/qwen3-30b-a3b","provider":"DeepInfra","choices":[{"index":0,"delta":{"content":"","role":"assistant","reasoning":" reads","reasoning_details":[{"type":"reasoning.text","text":" reads","format":"unknown","index":0}]},"finish_reason":null,"native_finish_reason":null}]} - -data: {"id":"gen-1791215155-kz4x2hrzNK1bBE4DGQEF","object":"chat.completion.chunk","created":1791215155,"model":"qwen/qwen3-30b-a3b","provider":"DeepInfra","choices":[{"index":0,"delta":{"content":"","role":"assistant","reasoning":" it","reasoning_details":[{"type":"reasoning.text","text":" it","format":"unknown","index":0}]},"finish_reason":null,"native_finish_reason":null}]} - -data: {"id":"gen-1791215155-kz4x2hrzNK1bBE4DGQEF","object":"chat.completion.chunk","created":1791215155,"model":"qwen/qwen3-30b-a3b","provider":"DeepInfra","choices":[{"index":0,"delta":{"content":"","role":"assistant","reasoning":".","reasoning_details":[{"type":"reasoning.text","text":".","format":"unknown","index":0}]},"finish_reason":null,"native_finish_reason":null}]} - -data: {"id":"gen-1791215155-kz4x2hrzNK1bBE4DGQEF","object":"chat.completion.chunk","created":1791215155,"model":"qwen/qwen3-30b-a3b","provider":"DeepInfra","choices":[{"index":0,"delta":{"content":"","role":"assistant","reasoning":" The","reasoning_details":[{"type":"reasoning.text","text":" The","format":"unknown","index":0}]},"finish_reason":null,"native_finish_reason":null}]} - -data: {"id":"gen-1791215155-kz4x2hrzNK1bBE4DGQEF","object":"chat.completion.chunk","created":1791215155,"model":"qwen/qwen3-30b-a3b","provider":"DeepInfra","choices":[{"index":0,"delta":{"content":"","role":"assistant","reasoning":" caller","reasoning_details":[{"type":"reasoning.text","text":" caller","format":"unknown","index":0}]},"finish_reason":null,"native_finish_reason":null}]} - -data: {"id":"gen-1791215155-kz4x2hrzNK1bBE4DGQEF","object":"chat.completion.chunk","created":1791215155,"model":"qwen/qwen3-30b-a3b","provider":"DeepInfra","choices":[{"index":0,"delta":{"content":"","role":"assistant","reasoning":"'s","reasoning_details":[{"type":"reasoning.text","text":"'s","format":"unknown","index":0}]},"finish_reason":null,"native_finish_reason":null}]} - -data: {"id":"gen-1791215155-kz4x2hrzNK1bBE4DGQEF","object":"chat.completion.chunk","created":1791215155,"model":"qwen/qwen3-30b-a3b","provider":"DeepInfra","choices":[{"index":0,"delta":{"content":"","role":"assistant","reasoning":" message","reasoning_details":[{"type":"reasoning.text","text":" message","format":"unknown","index":0}]},"finish_reason":null,"native_finish_reason":null}]} - -data: {"id":"gen-1791215155-kz4x2hrzNK1bBE4DGQEF","object":"chat.completion.chunk","created":1791215155,"model":"qwen/qwen3-30b-a3b","provider":"DeepInfra","choices":[{"index":0,"delta":{"content":"","role":"assistant","reasoning":" is","reasoning_details":[{"type":"reasoning.text","text":" is","format":"unknown","index":0}]},"finish_reason":null,"native_finish_reason":null}]} - -data: {"id":"gen-1791215155-kz4x2hrzNK1bBE4DGQEF","object":"chat.completion.chunk","created":1791215155,"model":"qwen/qwen3-30b-a3b","provider":"DeepInfra","choices":[{"index":0,"delta":{"content":"","role":"assistant","reasoning":" to","reasoning_details":[{"type":"reasoning.text","text":" to","format":"unknown","index":0}]},"finish_reason":null,"native_finish_reason":null}]} - -data: {"id":"gen-1791215155-kz4x2hrzNK1bBE4DGQEF","object":"chat.completion.chunk","created":1791215155,"model":"qwen/qwen3-30b-a3b","provider":"DeepInfra","choices":[{"index":0,"delta":{"content":"","role":"assistant","reasoning":" \"","reasoning_details":[{"type":"reasoning.text","text":" \"","format":"unknown","index":0}]},"finish_reason":null,"native_finish_reason":null}]} - -data: {"id":"gen-1791215155-kz4x2hrzNK1bBE4DGQEF","object":"chat.completion.chunk","created":1791215155,"model":"qwen/qwen3-30b-a3b","provider":"DeepInfra","choices":[{"index":0,"delta":{"content":"","role":"assistant","reasoning":"Run","reasoning_details":[{"type":"reasoning.text","text":"Run","format":"unknown","index":0}]},"finish_reason":null,"native_finish_reason":null}]} - -data: {"id":"gen-1791215155-kz4x2hrzNK1bBE4DGQEF","object":"chat.completion.chunk","created":1791215155,"model":"qwen/qwen3-30b-a3b","provider":"DeepInfra","choices":[{"index":0,"delta":{"content":"","role":"assistant","reasoning":" the","reasoning_details":[{"type":"reasoning.text","text":" the","format":"unknown","index":0}]},"finish_reason":null,"native_finish_reason":null}]} - -data: {"id":"gen-1791215155-kz4x2hrzNK1bBE4DGQEF","object":"chat.completion.chunk","created":1791215155,"model":"qwen/qwen3-30b-a3b","provider":"DeepInfra","choices":[{"index":0,"delta":{"content":"","role":"assistant","reasoning":" Brain","reasoning_details":[{"type":"reasoning.text","text":" Brain","format":"unknown","index":0}]},"finish_reason":null,"native_finish_reason":null}]} - -data: {"id":"gen-1791215155-kz4x2hrzNK1bBE4DGQEF","object":"chat.completion.chunk","created":1791215155,"model":"qwen/qwen3-30b-a3b","provider":"DeepInfra","choices":[{"index":0,"delta":{"content":"","role":"assistant","reasoning":"trust","reasoning_details":[{"type":"reasoning.text","text":"trust","format":"unknown","index":0}]},"finish_reason":null,"native_finish_reason":null}]} - -data: {"id":"gen-1791215155-kz4x2hrzNK1bBE4DGQEF","object":"chat.completion.chunk","created":1791215155,"model":"qwen/qwen3-30b-a3b","provider":"DeepInfra","choices":[{"index":0,"delta":{"content":"","role":"assistant","reasoning":" Eve","reasoning_details":[{"type":"reasoning.text","text":" Eve","format":"unknown","index":0}]},"finish_reason":null,"native_finish_reason":null}]} - -data: {"id":"gen-1791215155-kz4x2hrzNK1bBE4DGQEF","object":"chat.completion.chunk","created":1791215155,"model":"qwen/qwen3-30b-a3b","provider":"DeepInfra","choices":[{"index":0,"delta":{"content":"","role":"assistant","reasoning":" instrumentation","reasoning_details":[{"type":"reasoning.text","text":" instrumentation","format":"unknown","index":0}]},"finish_reason":null,"native_finish_reason":null}]} - -data: {"id":"gen-1791215155-kz4x2hrzNK1bBE4DGQEF","object":"chat.completion.chunk","created":1791215155,"model":"qwen/qwen3-30b-a3b","provider":"DeepInfra","choices":[{"index":0,"delta":{"content":"","role":"assistant","reasoning":" e","reasoning_details":[{"type":"reasoning.text","text":" e","format":"unknown","index":0}]},"finish_reason":null,"native_finish_reason":null}]} - -data: {"id":"gen-1791215155-kz4x2hrzNK1bBE4DGQEF","object":"chat.completion.chunk","created":1791215155,"model":"qwen/qwen3-30b-a3b","provider":"DeepInfra","choices":[{"index":0,"delta":{"content":"","role":"assistant","reasoning":"2","reasoning_details":[{"type":"reasoning.text","text":"2","format":"unknown","index":0}]},"finish_reason":null,"native_finish_reason":null}]} - -data: {"id":"gen-1791215155-kz4x2hrzNK1bBE4DGQEF","object":"chat.completion.chunk","created":1791215155,"model":"qwen/qwen3-30b-a3b","provider":"DeepInfra","choices":[{"index":0,"delta":{"content":"","role":"assistant","reasoning":"e","reasoning_details":[{"type":"reasoning.text","text":"e","format":"unknown","index":0}]},"finish_reason":null,"native_finish_reason":null}]} - -data: {"id":"gen-1791215155-kz4x2hrzNK1bBE4DGQEF","object":"chat.completion.chunk","created":1791215155,"model":"qwen/qwen3-30b-a3b","provider":"DeepInfra","choices":[{"index":0,"delta":{"content":"","role":"assistant","reasoning":" scenario","reasoning_details":[{"type":"reasoning.text","text":" scenario","format":"unknown","index":0}]},"finish_reason":null,"native_finish_reason":null}]} - -data: {"id":"gen-1791215155-kz4x2hrzNK1bBE4DGQEF","object":"chat.completion.chunk","created":1791215155,"model":"qwen/qwen3-30b-a3b","provider":"DeepInfra","choices":[{"index":0,"delta":{"content":"","role":"assistant","reasoning":".\"","reasoning_details":[{"type":"reasoning.text","text":".\"","format":"unknown","index":0}]},"finish_reason":null,"native_finish_reason":null}]} - -data: {"id":"gen-1791215155-kz4x2hrzNK1bBE4DGQEF","object":"chat.completion.chunk","created":1791215155,"model":"qwen/qwen3-30b-a3b","provider":"DeepInfra","choices":[{"index":0,"delta":{"content":"","role":"assistant","reasoning":" \n\n","reasoning_details":[{"type":"reasoning.text","text":" \n\n","format":"unknown","index":0}]},"finish_reason":null,"native_finish_reason":null}]} - -data: {"id":"gen-1791215155-kz4x2hrzNK1bBE4DGQEF","object":"chat.completion.chunk","created":1791215155,"model":"qwen/qwen3-30b-a3b","provider":"DeepInfra","choices":[{"index":0,"delta":{"content":"","role":"assistant","reasoning":"First","reasoning_details":[{"type":"reasoning.text","text":"First","format":"unknown","index":0}]},"finish_reason":null,"native_finish_reason":null}]} - -data: {"id":"gen-1791215155-kz4x2hrzNK1bBE4DGQEF","object":"chat.completion.chunk","created":1791215155,"model":"qwen/qwen3-30b-a3b","provider":"DeepInfra","choices":[{"index":0,"delta":{"content":"","role":"assistant","reasoning":",","reasoning_details":[{"type":"reasoning.text","text":",","format":"unknown","index":0}]},"finish_reason":null,"native_finish_reason":null}]} - -data: {"id":"gen-1791215155-kz4x2hrzNK1bBE4DGQEF","object":"chat.completion.chunk","created":1791215155,"model":"qwen/qwen3-30b-a3b","provider":"DeepInfra","choices":[{"index":0,"delta":{"content":"","role":"assistant","reasoning":" I","reasoning_details":[{"type":"reasoning.text","text":" I","format":"unknown","index":0}]},"finish_reason":null,"native_finish_reason":null}]} - -data: {"id":"gen-1791215155-kz4x2hrzNK1bBE4DGQEF","object":"chat.completion.chunk","created":1791215155,"model":"qwen/qwen3-30b-a3b","provider":"DeepInfra","choices":[{"index":0,"delta":{"content":"","role":"assistant","reasoning":" need","reasoning_details":[{"type":"reasoning.text","text":" need","format":"unknown","index":0}]},"finish_reason":null,"native_finish_reason":null}]} - -data: {"id":"gen-1791215155-kz4x2hrzNK1bBE4DGQEF","object":"chat.completion.chunk","created":1791215155,"model":"qwen/qwen3-30b-a3b","provider":"DeepInfra","choices":[{"index":0,"delta":{"content":"","role":"assistant","reasoning":" to","reasoning_details":[{"type":"reasoning.text","text":" to","format":"unknown","index":0}]},"finish_reason":null,"native_finish_reason":null}]} - -data: {"id":"gen-1791215155-kz4x2hrzNK1bBE4DGQEF","object":"chat.completion.chunk","created":1791215155,"model":"qwen/qwen3-30b-a3b","provider":"DeepInfra","choices":[{"index":0,"delta":{"content":"","role":"assistant","reasoning":" figure","reasoning_details":[{"type":"reasoning.text","text":" figure","format":"unknown","index":0}]},"finish_reason":null,"native_finish_reason":null}]} - -data: {"id":"gen-1791215155-kz4x2hrzNK1bBE4DGQEF","object":"chat.completion.chunk","created":1791215155,"model":"qwen/qwen3-30b-a3b","provider":"DeepInfra","choices":[{"index":0,"delta":{"content":"","role":"assistant","reasoning":" out","reasoning_details":[{"type":"reasoning.text","text":" out","format":"unknown","index":0}]},"finish_reason":null,"native_finish_reason":null}]} - -data: {"id":"gen-1791215155-kz4x2hrzNK1bBE4DGQEF","object":"chat.completion.chunk","created":1791215155,"model":"qwen/qwen3-30b-a3b","provider":"DeepInfra","choices":[{"index":0,"delta":{"content":"","role":"assistant","reasoning":" what","reasoning_details":[{"type":"reasoning.text","text":" what","format":"unknown","index":0}]},"finish_reason":null,"native_finish_reason":null}]} - -data: {"id":"gen-1791215155-kz4x2hrzNK1bBE4DGQEF","object":"chat.completion.chunk","created":1791215155,"model":"qwen/qwen3-30b-a3b","provider":"DeepInfra","choices":[{"index":0,"delta":{"content":"","role":"assistant","reasoning":" steps","reasoning_details":[{"type":"reasoning.text","text":" steps","format":"unknown","index":0}]},"finish_reason":null,"native_finish_reason":null}]} - -data: {"id":"gen-1791215155-kz4x2hrzNK1bBE4DGQEF","object":"chat.completion.chunk","created":1791215155,"model":"qwen/qwen3-30b-a3b","provider":"DeepInfra","choices":[{"index":0,"delta":{"content":"","role":"assistant","reasoning":" are","reasoning_details":[{"type":"reasoning.text","text":" are","format":"unknown","index":0}]},"finish_reason":null,"native_finish_reason":null}]} - -data: {"id":"gen-1791215155-kz4x2hrzNK1bBE4DGQEF","object":"chat.completion.chunk","created":1791215155,"model":"qwen/qwen3-30b-a3b","provider":"DeepInfra","choices":[{"index":0,"delta":{"content":"","role":"assistant","reasoning":" required","reasoning_details":[{"type":"reasoning.text","text":" required","format":"unknown","index":0}]},"finish_reason":null,"native_finish_reason":null}]} - -data: {"id":"gen-1791215155-kz4x2hrzNK1bBE4DGQEF","object":"chat.completion.chunk","created":1791215155,"model":"qwen/qwen3-30b-a3b","provider":"DeepInfra","choices":[{"index":0,"delta":{"content":"","role":"assistant","reasoning":" here","reasoning_details":[{"type":"reasoning.text","text":" here","format":"unknown","index":0}]},"finish_reason":null,"native_finish_reason":null}]} - -data: {"id":"gen-1791215155-kz4x2hrzNK1bBE4DGQEF","object":"chat.completion.chunk","created":1791215155,"model":"qwen/qwen3-30b-a3b","provider":"DeepInfra","choices":[{"index":0,"delta":{"content":"","role":"assistant","reasoning":".","reasoning_details":[{"type":"reasoning.text","text":".","format":"unknown","index":0}]},"finish_reason":null,"native_finish_reason":null}]} - -data: {"id":"gen-1791215155-kz4x2hrzNK1bBE4DGQEF","object":"chat.completion.chunk","created":1791215155,"model":"qwen/qwen3-30b-a3b","provider":"DeepInfra","choices":[{"index":0,"delta":{"content":"","role":"assistant","reasoning":" Since","reasoning_details":[{"type":"reasoning.text","text":" Since","format":"unknown","index":0}]},"finish_reason":null,"native_finish_reason":null}]} - -data: {"id":"gen-1791215155-kz4x2hrzNK1bBE4DGQEF","object":"chat.completion.chunk","created":1791215155,"model":"qwen/qwen3-30b-a3b","provider":"DeepInfra","choices":[{"index":0,"delta":{"content":"","role":"assistant","reasoning":" the","reasoning_details":[{"type":"reasoning.text","text":" the","format":"unknown","index":0}]},"finish_reason":null,"native_finish_reason":null}]} - -data: {"id":"gen-1791215155-kz4x2hrzNK1bBE4DGQEF","object":"chat.completion.chunk","created":1791215155,"model":"qwen/qwen3-30b-a3b","provider":"DeepInfra","choices":[{"index":0,"delta":{"content":"","role":"assistant","reasoning":" user","reasoning_details":[{"type":"reasoning.text","text":" user","format":"unknown","index":0}]},"finish_reason":null,"native_finish_reason":null}]} - -data: {"id":"gen-1791215155-kz4x2hrzNK1bBE4DGQEF","object":"chat.completion.chunk","created":1791215155,"model":"qwen/qwen3-30b-a3b","provider":"DeepInfra","choices":[{"index":0,"delta":{"content":"","role":"assistant","reasoning":" mentioned","reasoning_details":[{"type":"reasoning.text","text":" mentioned","format":"unknown","index":0}]},"finish_reason":null,"native_finish_reason":null}]} - -data: {"id":"gen-1791215155-kz4x2hrzNK1bBE4DGQEF","object":"chat.completion.chunk","created":1791215155,"model":"qwen/qwen3-30b-a3b","provider":"DeepInfra","choices":[{"index":0,"delta":{"content":"","role":"assistant","reasoning":" running","reasoning_details":[{"type":"reasoning.text","text":" running","format":"unknown","index":0}]},"finish_reason":null,"native_finish_reason":null}]} - -data: {"id":"gen-1791215155-kz4x2hrzNK1bBE4DGQEF","object":"chat.completion.chunk","created":1791215155,"model":"qwen/qwen3-30b-a3b","provider":"DeepInfra","choices":[{"index":0,"delta":{"content":"","role":"assistant","reasoning":" an","reasoning_details":[{"type":"reasoning.text","text":" an","format":"unknown","index":0}]},"finish_reason":null,"native_finish_reason":null}]} - -data: {"id":"gen-1791215155-kz4x2hrzNK1bBE4DGQEF","object":"chat.completion.chunk","created":1791215155,"model":"qwen/qwen3-30b-a3b","provider":"DeepInfra","choices":[{"index":0,"delta":{"content":"","role":"assistant","reasoning":" end","reasoning_details":[{"type":"reasoning.text","text":" end","format":"unknown","index":0}]},"finish_reason":null,"native_finish_reason":null}]} - -data: {"id":"gen-1791215155-kz4x2hrzNK1bBE4DGQEF","object":"chat.completion.chunk","created":1791215155,"model":"qwen/qwen3-30b-a3b","provider":"DeepInfra","choices":[{"index":0,"delta":{"content":"","role":"assistant","reasoning":"-to","reasoning_details":[{"type":"reasoning.text","text":"-to","format":"unknown","index":0}]},"finish_reason":null,"native_finish_reason":null}]} - -data: {"id":"gen-1791215155-kz4x2hrzNK1bBE4DGQEF","object":"chat.completion.chunk","created":1791215155,"model":"qwen/qwen3-30b-a3b","provider":"DeepInfra","choices":[{"index":0,"delta":{"content":"","role":"assistant","reasoning":"-end","reasoning_details":[{"type":"reasoning.text","text":"-end","format":"unknown","index":0}]},"finish_reason":null,"native_finish_reason":null}]} - -data: {"id":"gen-1791215155-kz4x2hrzNK1bBE4DGQEF","object":"chat.completion.chunk","created":1791215155,"model":"qwen/qwen3-30b-a3b","provider":"DeepInfra","choices":[{"index":0,"delta":{"content":"","role":"assistant","reasoning":" scenario","reasoning_details":[{"type":"reasoning.text","text":" scenario","format":"unknown","index":0}]},"finish_reason":null,"native_finish_reason":null}]} - -data: {"id":"gen-1791215155-kz4x2hrzNK1bBE4DGQEF","object":"chat.completion.chunk","created":1791215155,"model":"qwen/qwen3-30b-a3b","provider":"DeepInfra","choices":[{"index":0,"delta":{"content":"","role":"assistant","reasoning":",","reasoning_details":[{"type":"reasoning.text","text":",","format":"unknown","index":0}]},"finish_reason":null,"native_finish_reason":null}]} - -data: {"id":"gen-1791215155-kz4x2hrzNK1bBE4DGQEF","object":"chat.completion.chunk","created":1791215155,"model":"qwen/qwen3-30b-a3b","provider":"DeepInfra","choices":[{"index":0,"delta":{"content":"","role":"assistant","reasoning":" I","reasoning_details":[{"type":"reasoning.text","text":" I","format":"unknown","index":0}]},"finish_reason":null,"native_finish_reason":null}]} - -data: {"id":"gen-1791215155-kz4x2hrzNK1bBE4DGQEF","object":"chat.completion.chunk","created":1791215155,"model":"qwen/qwen3-30b-a3b","provider":"DeepInfra","choices":[{"index":0,"delta":{"content":"","role":"assistant","reasoning":" should","reasoning_details":[{"type":"reasoning.text","text":" should","format":"unknown","index":0}]},"finish_reason":null,"native_finish_reason":null}]} - -data: {"id":"gen-1791215155-kz4x2hrzNK1bBE4DGQEF","object":"chat.completion.chunk","created":1791215155,"model":"qwen/qwen3-30b-a3b","provider":"DeepInfra","choices":[{"index":0,"delta":{"content":"","role":"assistant","reasoning":" look","reasoning_details":[{"type":"reasoning.text","text":" look","format":"unknown","index":0}]},"finish_reason":null,"native_finish_reason":null}]} - -data: {"id":"gen-1791215155-kz4x2hrzNK1bBE4DGQEF","object":"chat.completion.chunk","created":1791215155,"model":"qwen/qwen3-30b-a3b","provider":"DeepInfra","choices":[{"index":0,"delta":{"content":"","role":"assistant","reasoning":" into","reasoning_details":[{"type":"reasoning.text","text":" into","format":"unknown","index":0}]},"finish_reason":null,"native_finish_reason":null}]} - -data: {"id":"gen-1791215155-kz4x2hrzNK1bBE4DGQEF","object":"chat.completion.chunk","created":1791215155,"model":"qwen/qwen3-30b-a3b","provider":"DeepInfra","choices":[{"index":0,"delta":{"content":"","role":"assistant","reasoning":" the","reasoning_details":[{"type":"reasoning.text","text":" the","format":"unknown","index":0}]},"finish_reason":null,"native_finish_reason":null}]} - -data: {"id":"gen-1791215155-kz4x2hrzNK1bBE4DGQEF","object":"chat.completion.chunk","created":1791215155,"model":"qwen/qwen3-30b-a3b","provider":"DeepInfra","choices":[{"index":0,"delta":{"content":"","role":"assistant","reasoning":" documentation","reasoning_details":[{"type":"reasoning.text","text":" documentation","format":"unknown","index":0}]},"finish_reason":null,"native_finish_reason":null}]} - -data: {"id":"gen-1791215155-kz4x2hrzNK1bBE4DGQEF","object":"chat.completion.chunk","created":1791215155,"model":"qwen/qwen3-30b-a3b","provider":"DeepInfra","choices":[{"index":0,"delta":{"content":"","role":"assistant","reasoning":" to","reasoning_details":[{"type":"reasoning.text","text":" to","format":"unknown","index":0}]},"finish_reason":null,"native_finish_reason":null}]} - -data: {"id":"gen-1791215155-kz4x2hrzNK1bBE4DGQEF","object":"chat.completion.chunk","created":1791215155,"model":"qwen/qwen3-30b-a3b","provider":"DeepInfra","choices":[{"index":0,"delta":{"content":"","role":"assistant","reasoning":" find","reasoning_details":[{"type":"reasoning.text","text":" find","format":"unknown","index":0}]},"finish_reason":null,"native_finish_reason":null}]} - -data: {"id":"gen-1791215155-kz4x2hrzNK1bBE4DGQEF","object":"chat.completion.chunk","created":1791215155,"model":"qwen/qwen3-30b-a3b","provider":"DeepInfra","choices":[{"index":0,"delta":{"content":"","role":"assistant","reasoning":" relevant","reasoning_details":[{"type":"reasoning.text","text":" relevant","format":"unknown","index":0}]},"finish_reason":null,"native_finish_reason":null}]} - -data: {"id":"gen-1791215155-kz4x2hrzNK1bBE4DGQEF","object":"chat.completion.chunk","created":1791215155,"model":"qwen/qwen3-30b-a3b","provider":"DeepInfra","choices":[{"index":0,"delta":{"content":"","role":"assistant","reasoning":" information","reasoning_details":[{"type":"reasoning.text","text":" information","format":"unknown","index":0}]},"finish_reason":null,"native_finish_reason":null}]} - -data: {"id":"gen-1791215155-kz4x2hrzNK1bBE4DGQEF","object":"chat.completion.chunk","created":1791215155,"model":"qwen/qwen3-30b-a3b","provider":"DeepInfra","choices":[{"index":0,"delta":{"content":"","role":"assistant","reasoning":".","reasoning_details":[{"type":"reasoning.text","text":".","format":"unknown","index":0}]},"finish_reason":null,"native_finish_reason":null}]} - -data: {"id":"gen-1791215155-kz4x2hrzNK1bBE4DGQEF","object":"chat.completion.chunk","created":1791215155,"model":"qwen/qwen3-30b-a3b","provider":"DeepInfra","choices":[{"index":0,"delta":{"content":"","role":"assistant","reasoning":" The","reasoning_details":[{"type":"reasoning.text","text":" The","format":"unknown","index":0}]},"finish_reason":null,"native_finish_reason":null}]} - -data: {"id":"gen-1791215155-kz4x2hrzNK1bBE4DGQEF","object":"chat.completion.chunk","created":1791215155,"model":"qwen/qwen3-30b-a3b","provider":"DeepInfra","choices":[{"index":0,"delta":{"content":"","role":"assistant","reasoning":" available","reasoning_details":[{"type":"reasoning.text","text":" available","format":"unknown","index":0}]},"finish_reason":null,"native_finish_reason":null}]} - -data: {"id":"gen-1791215155-kz4x2hrzNK1bBE4DGQEF","object":"chat.completion.chunk","created":1791215155,"model":"qwen/qwen3-30b-a3b","provider":"DeepInfra","choices":[{"index":0,"delta":{"content":"","role":"assistant","reasoning":" tools","reasoning_details":[{"type":"reasoning.text","text":" tools","format":"unknown","index":0}]},"finish_reason":null,"native_finish_reason":null}]} - -data: {"id":"gen-1791215155-kz4x2hrzNK1bBE4DGQEF","object":"chat.completion.chunk","created":1791215155,"model":"qwen/qwen3-30b-a3b","provider":"DeepInfra","choices":[{"index":0,"delta":{"content":"","role":"assistant","reasoning":" include","reasoning_details":[{"type":"reasoning.text","text":" include","format":"unknown","index":0}]},"finish_reason":null,"native_finish_reason":null}]} - -data: {"id":"gen-1791215155-kz4x2hrzNK1bBE4DGQEF","object":"chat.completion.chunk","created":1791215155,"model":"qwen/qwen3-30b-a3b","provider":"DeepInfra","choices":[{"index":0,"delta":{"content":"","role":"assistant","reasoning":" a","reasoning_details":[{"type":"reasoning.text","text":" a","format":"unknown","index":0}]},"finish_reason":null,"native_finish_reason":null}]} - -data: {"id":"gen-1791215155-kz4x2hrzNK1bBE4DGQEF","object":"chat.completion.chunk","created":1791215155,"model":"qwen/qwen3-30b-a3b","provider":"DeepInfra","choices":[{"index":0,"delta":{"content":"","role":"assistant","reasoning":" search","reasoning_details":[{"type":"reasoning.text","text":" search","format":"unknown","index":0}]},"finish_reason":null,"native_finish_reason":null}]} - -data: {"id":"gen-1791215155-kz4x2hrzNK1bBE4DGQEF","object":"chat.completion.chunk","created":1791215155,"model":"qwen/qwen3-30b-a3b","provider":"DeepInfra","choices":[{"index":0,"delta":{"content":"","role":"assistant","reasoning":" function","reasoning_details":[{"type":"reasoning.text","text":" function","format":"unknown","index":0}]},"finish_reason":null,"native_finish_reason":null}]} - -data: {"id":"gen-1791215155-kz4x2hrzNK1bBE4DGQEF","object":"chat.completion.chunk","created":1791215155,"model":"qwen/qwen3-30b-a3b","provider":"DeepInfra","choices":[{"index":0,"delta":{"content":"","role":"assistant","reasoning":" that","reasoning_details":[{"type":"reasoning.text","text":" that","format":"unknown","index":0}]},"finish_reason":null,"native_finish_reason":null}]} - -data: {"id":"gen-1791215155-kz4x2hrzNK1bBE4DGQEF","object":"chat.completion.chunk","created":1791215155,"model":"qwen/qwen3-30b-a3b","provider":"DeepInfra","choices":[{"index":0,"delta":{"content":"","role":"assistant","reasoning":" queries","reasoning_details":[{"type":"reasoning.text","text":" queries","format":"unknown","index":0}]},"finish_reason":null,"native_finish_reason":null}]} - -data: {"id":"gen-1791215155-kz4x2hrzNK1bBE4DGQEF","object":"chat.completion.chunk","created":1791215155,"model":"qwen/qwen3-30b-a3b","provider":"DeepInfra","choices":[{"index":0,"delta":{"content":"","role":"assistant","reasoning":" the","reasoning_details":[{"type":"reasoning.text","text":" the","format":"unknown","index":0}]},"finish_reason":null,"native_finish_reason":null}]} - -data: {"id":"gen-1791215155-kz4x2hrzNK1bBE4DGQEF","object":"chat.completion.chunk","created":1791215155,"model":"qwen/qwen3-30b-a3b","provider":"DeepInfra","choices":[{"index":0,"delta":{"content":"","role":"assistant","reasoning":" Eve","reasoning_details":[{"type":"reasoning.text","text":" Eve","format":"unknown","index":0}]},"finish_reason":null,"native_finish_reason":null}]} - -data: {"id":"gen-1791215155-kz4x2hrzNK1bBE4DGQEF","object":"chat.completion.chunk","created":1791215155,"model":"qwen/qwen3-30b-a3b","provider":"DeepInfra","choices":[{"index":0,"delta":{"content":"","role":"assistant","reasoning":" instrumentation","reasoning_details":[{"type":"reasoning.text","text":" instrumentation","format":"unknown","index":0}]},"finish_reason":null,"native_finish_reason":null}]} - -data: {"id":"gen-1791215155-kz4x2hrzNK1bBE4DGQEF","object":"chat.completion.chunk","created":1791215155,"model":"qwen/qwen3-30b-a3b","provider":"DeepInfra","choices":[{"index":0,"delta":{"content":"","role":"assistant","reasoning":" documentation","reasoning_details":[{"type":"reasoning.text","text":" documentation","format":"unknown","index":0}]},"finish_reason":null,"native_finish_reason":null}]} - -data: {"id":"gen-1791215155-kz4x2hrzNK1bBE4DGQEF","object":"chat.completion.chunk","created":1791215155,"model":"qwen/qwen3-30b-a3b","provider":"DeepInfra","choices":[{"index":0,"delta":{"content":"","role":"assistant","reasoning":".","reasoning_details":[{"type":"reasoning.text","text":".","format":"unknown","index":0}]},"finish_reason":null,"native_finish_reason":null}]} - -data: {"id":"gen-1791215155-kz4x2hrzNK1bBE4DGQEF","object":"chat.completion.chunk","created":1791215155,"model":"qwen/qwen3-30b-a3b","provider":"DeepInfra","choices":[{"index":0,"delta":{"content":"","role":"assistant","reasoning":" \n\n","reasoning_details":[{"type":"reasoning.text","text":" \n\n","format":"unknown","index":0}]},"finish_reason":null,"native_finish_reason":null}]} - -data: {"id":"gen-1791215155-kz4x2hrzNK1bBE4DGQEF","object":"chat.completion.chunk","created":1791215155,"model":"qwen/qwen3-30b-a3b","provider":"DeepInfra","choices":[{"index":0,"delta":{"content":"","role":"assistant","reasoning":"The","reasoning_details":[{"type":"reasoning.text","text":"The","format":"unknown","index":0}]},"finish_reason":null,"native_finish_reason":null}]} - -data: {"id":"gen-1791215155-kz4x2hrzNK1bBE4DGQEF","object":"chat.completion.chunk","created":1791215155,"model":"qwen/qwen3-30b-a3b","provider":"DeepInfra","choices":[{"index":0,"delta":{"content":"","role":"assistant","reasoning":" instructions","reasoning_details":[{"type":"reasoning.text","text":" instructions","format":"unknown","index":0}]},"finish_reason":null,"native_finish_reason":null}]} - -data: {"id":"gen-1791215155-kz4x2hrzNK1bBE4DGQEF","object":"chat.completion.chunk","created":1791215155,"model":"qwen/qwen3-30b-a3b","provider":"DeepInfra","choices":[{"index":0,"delta":{"content":"","role":"assistant","reasoning":" say","reasoning_details":[{"type":"reasoning.text","text":" say","format":"unknown","index":0}]},"finish_reason":null,"native_finish_reason":null}]} - -data: {"id":"gen-1791215155-kz4x2hrzNK1bBE4DGQEF","object":"chat.completion.chunk","created":1791215155,"model":"qwen/qwen3-30b-a3b","provider":"DeepInfra","choices":[{"index":0,"delta":{"content":"","role":"assistant","reasoning":" to","reasoning_details":[{"type":"reasoning.text","text":" to","format":"unknown","index":0}]},"finish_reason":null,"native_finish_reason":null}]} - -data: {"id":"gen-1791215155-kz4x2hrzNK1bBE4DGQEF","object":"chat.completion.chunk","created":1791215155,"model":"qwen/qwen3-30b-a3b","provider":"DeepInfra","choices":[{"index":0,"delta":{"content":"","role":"assistant","reasoning":" call","reasoning_details":[{"type":"reasoning.text","text":" call","format":"unknown","index":0}]},"finish_reason":null,"native_finish_reason":null}]} - -data: {"id":"gen-1791215155-kz4x2hrzNK1bBE4DGQEF","object":"chat.completion.chunk","created":1791215155,"model":"qwen/qwen3-30b-a3b","provider":"DeepInfra","choices":[{"index":0,"delta":{"content":"","role":"assistant","reasoning":" the","reasoning_details":[{"type":"reasoning.text","text":" the","format":"unknown","index":0}]},"finish_reason":null,"native_finish_reason":null}]} - -data: {"id":"gen-1791215155-kz4x2hrzNK1bBE4DGQEF","object":"chat.completion.chunk","created":1791215155,"model":"qwen/qwen3-30b-a3b","provider":"DeepInfra","choices":[{"index":0,"delta":{"content":"","role":"assistant","reasoning":" search","reasoning_details":[{"type":"reasoning.text","text":" search","format":"unknown","index":0}]},"finish_reason":null,"native_finish_reason":null}]} - -data: {"id":"gen-1791215155-kz4x2hrzNK1bBE4DGQEF","object":"chat.completion.chunk","created":1791215155,"model":"qwen/qwen3-30b-a3b","provider":"DeepInfra","choices":[{"index":0,"delta":{"content":"","role":"assistant","reasoning":" tool","reasoning_details":[{"type":"reasoning.text","text":" tool","format":"unknown","index":0}]},"finish_reason":null,"native_finish_reason":null}]} - -data: {"id":"gen-1791215155-kz4x2hrzNK1bBE4DGQEF","object":"chat.completion.chunk","created":1791215155,"model":"qwen/qwen3-30b-a3b","provider":"DeepInfra","choices":[{"index":0,"delta":{"content":"","role":"assistant","reasoning":" exactly","reasoning_details":[{"type":"reasoning.text","text":" exactly","format":"unknown","index":0}]},"finish_reason":null,"native_finish_reason":null}]} - -data: {"id":"gen-1791215155-kz4x2hrzNK1bBE4DGQEF","object":"chat.completion.chunk","created":1791215155,"model":"qwen/qwen3-30b-a3b","provider":"DeepInfra","choices":[{"index":0,"delta":{"content":"","role":"assistant","reasoning":" once","reasoning_details":[{"type":"reasoning.text","text":" once","format":"unknown","index":0}]},"finish_reason":null,"native_finish_reason":null}]} - -data: {"id":"gen-1791215155-kz4x2hrzNK1bBE4DGQEF","object":"chat.completion.chunk","created":1791215155,"model":"qwen/qwen3-30b-a3b","provider":"DeepInfra","choices":[{"index":0,"delta":{"content":"","role":"assistant","reasoning":" with","reasoning_details":[{"type":"reasoning.text","text":" with","format":"unknown","index":0}]},"finish_reason":null,"native_finish_reason":null}]} - -data: {"id":"gen-1791215155-kz4x2hrzNK1bBE4DGQEF","object":"chat.completion.chunk","created":1791215155,"model":"qwen/qwen3-30b-a3b","provider":"DeepInfra","choices":[{"index":0,"delta":{"content":"","role":"assistant","reasoning":" the","reasoning_details":[{"type":"reasoning.text","text":" the","format":"unknown","index":0}]},"finish_reason":null,"native_finish_reason":null}]} - -data: {"id":"gen-1791215155-kz4x2hrzNK1bBE4DGQEF","object":"chat.completion.chunk","created":1791215155,"model":"qwen/qwen3-30b-a3b","provider":"DeepInfra","choices":[{"index":0,"delta":{"content":"","role":"assistant","reasoning":" full","reasoning_details":[{"type":"reasoning.text","text":" full","format":"unknown","index":0}]},"finish_reason":null,"native_finish_reason":null}]} - -data: {"id":"gen-1791215155-kz4x2hrzNK1bBE4DGQEF","object":"chat.completion.chunk","created":1791215155,"model":"qwen/qwen3-30b-a3b","provider":"DeepInfra","choices":[{"index":0,"delta":{"content":"","role":"assistant","reasoning":" task","reasoning_details":[{"type":"reasoning.text","text":" task","format":"unknown","index":0}]},"finish_reason":null,"native_finish_reason":null}]} - -data: {"id":"gen-1791215155-kz4x2hrzNK1bBE4DGQEF","object":"chat.completion.chunk","created":1791215155,"model":"qwen/qwen3-30b-a3b","provider":"DeepInfra","choices":[{"index":0,"delta":{"content":"","role":"assistant","reasoning":" as","reasoning_details":[{"type":"reasoning.text","text":" as","format":"unknown","index":0}]},"finish_reason":null,"native_finish_reason":null}]} - -data: {"id":"gen-1791215155-kz4x2hrzNK1bBE4DGQEF","object":"chat.completion.chunk","created":1791215155,"model":"qwen/qwen3-30b-a3b","provider":"DeepInfra","choices":[{"index":0,"delta":{"content":"","role":"assistant","reasoning":" the","reasoning_details":[{"type":"reasoning.text","text":" the","format":"unknown","index":0}]},"finish_reason":null,"native_finish_reason":null}]} - -data: {"id":"gen-1791215155-kz4x2hrzNK1bBE4DGQEF","object":"chat.completion.chunk","created":1791215155,"model":"qwen/qwen3-30b-a3b","provider":"DeepInfra","choices":[{"index":0,"delta":{"content":"","role":"assistant","reasoning":" query","reasoning_details":[{"type":"reasoning.text","text":" query","format":"unknown","index":0}]},"finish_reason":null,"native_finish_reason":null}]} - -data: {"id":"gen-1791215155-kz4x2hrzNK1bBE4DGQEF","object":"chat.completion.chunk","created":1791215155,"model":"qwen/qwen3-30b-a3b","provider":"DeepInfra","choices":[{"index":0,"delta":{"content":"","role":"assistant","reasoning":".","reasoning_details":[{"type":"reasoning.text","text":".","format":"unknown","index":0}]},"finish_reason":null,"native_finish_reason":null}]} - -data: {"id":"gen-1791215155-kz4x2hrzNK1bBE4DGQEF","object":"chat.completion.chunk","created":1791215155,"model":"qwen/qwen3-30b-a3b","provider":"DeepInfra","choices":[{"index":0,"delta":{"content":"","role":"assistant","reasoning":" So","reasoning_details":[{"type":"reasoning.text","text":" So","format":"unknown","index":0}]},"finish_reason":null,"native_finish_reason":null}]} - -data: {"id":"gen-1791215155-kz4x2hrzNK1bBE4DGQEF","object":"chat.completion.chunk","created":1791215155,"model":"qwen/qwen3-30b-a3b","provider":"DeepInfra","choices":[{"index":0,"delta":{"content":"","role":"assistant","reasoning":",","reasoning_details":[{"type":"reasoning.text","text":",","format":"unknown","index":0}]},"finish_reason":null,"native_finish_reason":null}]} - -data: {"id":"gen-1791215155-kz4x2hrzNK1bBE4DGQEF","object":"chat.completion.chunk","created":1791215155,"model":"qwen/qwen3-30b-a3b","provider":"DeepInfra","choices":[{"index":0,"delta":{"content":"","role":"assistant","reasoning":" the","reasoning_details":[{"type":"reasoning.text","text":" the","format":"unknown","index":0}]},"finish_reason":null,"native_finish_reason":null}]} - -data: {"id":"gen-1791215155-kz4x2hrzNK1bBE4DGQEF","object":"chat.completion.chunk","created":1791215155,"model":"qwen/qwen3-30b-a3b","provider":"DeepInfra","choices":[{"index":0,"delta":{"content":"","role":"assistant","reasoning":" first","reasoning_details":[{"type":"reasoning.text","text":" first","format":"unknown","index":0}]},"finish_reason":null,"native_finish_reason":null}]} - -data: {"id":"gen-1791215155-kz4x2hrzNK1bBE4DGQEF","object":"chat.completion.chunk","created":1791215155,"model":"qwen/qwen3-30b-a3b","provider":"DeepInfra","choices":[{"index":0,"delta":{"content":"","role":"assistant","reasoning":" step","reasoning_details":[{"type":"reasoning.text","text":" step","format":"unknown","index":0}]},"finish_reason":null,"native_finish_reason":null}]} - -data: {"id":"gen-1791215155-kz4x2hrzNK1bBE4DGQEF","object":"chat.completion.chunk","created":1791215155,"model":"qwen/qwen3-30b-a3b","provider":"DeepInfra","choices":[{"index":0,"delta":{"content":"","role":"assistant","reasoning":" is","reasoning_details":[{"type":"reasoning.text","text":" is","format":"unknown","index":0}]},"finish_reason":null,"native_finish_reason":null}]} - -data: {"id":"gen-1791215155-kz4x2hrzNK1bBE4DGQEF","object":"chat.completion.chunk","created":1791215155,"model":"qwen/qwen3-30b-a3b","provider":"DeepInfra","choices":[{"index":0,"delta":{"content":"","role":"assistant","reasoning":" to","reasoning_details":[{"type":"reasoning.text","text":" to","format":"unknown","index":0}]},"finish_reason":null,"native_finish_reason":null}]} - -data: {"id":"gen-1791215155-kz4x2hrzNK1bBE4DGQEF","object":"chat.completion.chunk","created":1791215155,"model":"qwen/qwen3-30b-a3b","provider":"DeepInfra","choices":[{"index":0,"delta":{"content":"","role":"assistant","reasoning":" use","reasoning_details":[{"type":"reasoning.text","text":" use","format":"unknown","index":0}]},"finish_reason":null,"native_finish_reason":null}]} - -data: {"id":"gen-1791215155-kz4x2hrzNK1bBE4DGQEF","object":"chat.completion.chunk","created":1791215155,"model":"qwen/qwen3-30b-a3b","provider":"DeepInfra","choices":[{"index":0,"delta":{"content":"","role":"assistant","reasoning":" the","reasoning_details":[{"type":"reasoning.text","text":" the","format":"unknown","index":0}]},"finish_reason":null,"native_finish_reason":null}]} - -data: {"id":"gen-1791215155-kz4x2hrzNK1bBE4DGQEF","object":"chat.completion.chunk","created":1791215155,"model":"qwen/qwen3-30b-a3b","provider":"DeepInfra","choices":[{"index":0,"delta":{"content":"","role":"assistant","reasoning":" search","reasoning_details":[{"type":"reasoning.text","text":" search","format":"unknown","index":0}]},"finish_reason":null,"native_finish_reason":null}]} - -data: {"id":"gen-1791215155-kz4x2hrzNK1bBE4DGQEF","object":"chat.completion.chunk","created":1791215155,"model":"qwen/qwen3-30b-a3b","provider":"DeepInfra","choices":[{"index":0,"delta":{"content":"","role":"assistant","reasoning":" function","reasoning_details":[{"type":"reasoning.text","text":" function","format":"unknown","index":0}]},"finish_reason":null,"native_finish_reason":null}]} - -data: {"id":"gen-1791215155-kz4x2hrzNK1bBE4DGQEF","object":"chat.completion.chunk","created":1791215155,"model":"qwen/qwen3-30b-a3b","provider":"DeepInfra","choices":[{"index":0,"delta":{"content":"","role":"assistant","reasoning":" with","reasoning_details":[{"type":"reasoning.text","text":" with","format":"unknown","index":0}]},"finish_reason":null,"native_finish_reason":null}]} - -data: {"id":"gen-1791215155-kz4x2hrzNK1bBE4DGQEF","object":"chat.completion.chunk","created":1791215155,"model":"qwen/qwen3-30b-a3b","provider":"DeepInfra","choices":[{"index":0,"delta":{"content":"","role":"assistant","reasoning":" the","reasoning_details":[{"type":"reasoning.text","text":" the","format":"unknown","index":0}]},"finish_reason":null,"native_finish_reason":null}]} - -data: {"id":"gen-1791215155-kz4x2hrzNK1bBE4DGQEF","object":"chat.completion.chunk","created":1791215155,"model":"qwen/qwen3-30b-a3b","provider":"DeepInfra","choices":[{"index":0,"delta":{"content":"","role":"assistant","reasoning":" query","reasoning_details":[{"type":"reasoning.text","text":" query","format":"unknown","index":0}]},"finish_reason":null,"native_finish_reason":null}]} - -data: {"id":"gen-1791215155-kz4x2hrzNK1bBE4DGQEF","object":"chat.completion.chunk","created":1791215155,"model":"qwen/qwen3-30b-a3b","provider":"DeepInfra","choices":[{"index":0,"delta":{"content":"","role":"assistant","reasoning":" \"","reasoning_details":[{"type":"reasoning.text","text":" \"","format":"unknown","index":0}]},"finish_reason":null,"native_finish_reason":null}]} - -data: {"id":"gen-1791215155-kz4x2hrzNK1bBE4DGQEF","object":"chat.completion.chunk","created":1791215155,"model":"qwen/qwen3-30b-a3b","provider":"DeepInfra","choices":[{"index":0,"delta":{"content":"","role":"assistant","reasoning":"Run","reasoning_details":[{"type":"reasoning.text","text":"Run","format":"unknown","index":0}]},"finish_reason":null,"native_finish_reason":null}]} - -data: {"id":"gen-1791215155-kz4x2hrzNK1bBE4DGQEF","object":"chat.completion.chunk","created":1791215155,"model":"qwen/qwen3-30b-a3b","provider":"DeepInfra","choices":[{"index":0,"delta":{"content":"","role":"assistant","reasoning":" the","reasoning_details":[{"type":"reasoning.text","text":" the","format":"unknown","index":0}]},"finish_reason":null,"native_finish_reason":null}]} - -data: {"id":"gen-1791215155-kz4x2hrzNK1bBE4DGQEF","object":"chat.completion.chunk","created":1791215155,"model":"qwen/qwen3-30b-a3b","provider":"DeepInfra","choices":[{"index":0,"delta":{"content":"","role":"assistant","reasoning":" Brain","reasoning_details":[{"type":"reasoning.text","text":" Brain","format":"unknown","index":0}]},"finish_reason":null,"native_finish_reason":null}]} - -data: {"id":"gen-1791215155-kz4x2hrzNK1bBE4DGQEF","object":"chat.completion.chunk","created":1791215155,"model":"qwen/qwen3-30b-a3b","provider":"DeepInfra","choices":[{"index":0,"delta":{"content":"","role":"assistant","reasoning":"trust","reasoning_details":[{"type":"reasoning.text","text":"trust","format":"unknown","index":0}]},"finish_reason":null,"native_finish_reason":null}]} - -data: {"id":"gen-1791215155-kz4x2hrzNK1bBE4DGQEF","object":"chat.completion.chunk","created":1791215155,"model":"qwen/qwen3-30b-a3b","provider":"DeepInfra","choices":[{"index":0,"delta":{"content":"","role":"assistant","reasoning":" Eve","reasoning_details":[{"type":"reasoning.text","text":" Eve","format":"unknown","index":0}]},"finish_reason":null,"native_finish_reason":null}]} - -data: {"id":"gen-1791215155-kz4x2hrzNK1bBE4DGQEF","object":"chat.completion.chunk","created":1791215155,"model":"qwen/qwen3-30b-a3b","provider":"DeepInfra","choices":[{"index":0,"delta":{"content":"","role":"assistant","reasoning":" instrumentation","reasoning_details":[{"type":"reasoning.text","text":" instrumentation","format":"unknown","index":0}]},"finish_reason":null,"native_finish_reason":null}]} - -data: {"id":"gen-1791215155-kz4x2hrzNK1bBE4DGQEF","object":"chat.completion.chunk","created":1791215155,"model":"qwen/qwen3-30b-a3b","provider":"DeepInfra","choices":[{"index":0,"delta":{"content":"","role":"assistant","reasoning":" e","reasoning_details":[{"type":"reasoning.text","text":" e","format":"unknown","index":0}]},"finish_reason":null,"native_finish_reason":null}]} - -data: {"id":"gen-1791215155-kz4x2hrzNK1bBE4DGQEF","object":"chat.completion.chunk","created":1791215155,"model":"qwen/qwen3-30b-a3b","provider":"DeepInfra","choices":[{"index":0,"delta":{"content":"","role":"assistant","reasoning":"2","reasoning_details":[{"type":"reasoning.text","text":"2","format":"unknown","index":0}]},"finish_reason":null,"native_finish_reason":null}]} - -data: {"id":"gen-1791215155-kz4x2hrzNK1bBE4DGQEF","object":"chat.completion.chunk","created":1791215155,"model":"qwen/qwen3-30b-a3b","provider":"DeepInfra","choices":[{"index":0,"delta":{"content":"","role":"assistant","reasoning":"e","reasoning_details":[{"type":"reasoning.text","text":"e","format":"unknown","index":0}]},"finish_reason":null,"native_finish_reason":null}]} - -data: {"id":"gen-1791215155-kz4x2hrzNK1bBE4DGQEF","object":"chat.completion.chunk","created":1791215155,"model":"qwen/qwen3-30b-a3b","provider":"DeepInfra","choices":[{"index":0,"delta":{"content":"","role":"assistant","reasoning":" scenario","reasoning_details":[{"type":"reasoning.text","text":" scenario","format":"unknown","index":0}]},"finish_reason":null,"native_finish_reason":null}]} - -data: {"id":"gen-1791215155-kz4x2hrzNK1bBE4DGQEF","object":"chat.completion.chunk","created":1791215155,"model":"qwen/qwen3-30b-a3b","provider":"DeepInfra","choices":[{"index":0,"delta":{"content":"","role":"assistant","reasoning":".\"","reasoning_details":[{"type":"reasoning.text","text":".\"","format":"unknown","index":0}]},"finish_reason":null,"native_finish_reason":null}]} - -data: {"id":"gen-1791215155-kz4x2hrzNK1bBE4DGQEF","object":"chat.completion.chunk","created":1791215155,"model":"qwen/qwen3-30b-a3b","provider":"DeepInfra","choices":[{"index":0,"delta":{"content":"","role":"assistant","reasoning":" That","reasoning_details":[{"type":"reasoning.text","text":" That","format":"unknown","index":0}]},"finish_reason":null,"native_finish_reason":null}]} - -data: {"id":"gen-1791215155-kz4x2hrzNK1bBE4DGQEF","object":"chat.completion.chunk","created":1791215155,"model":"qwen/qwen3-30b-a3b","provider":"DeepInfra","choices":[{"index":0,"delta":{"content":"","role":"assistant","reasoning":" should","reasoning_details":[{"type":"reasoning.text","text":" should","format":"unknown","index":0}]},"finish_reason":null,"native_finish_reason":null}]} - -data: {"id":"gen-1791215155-kz4x2hrzNK1bBE4DGQEF","object":"chat.completion.chunk","created":1791215155,"model":"qwen/qwen3-30b-a3b","provider":"DeepInfra","choices":[{"index":0,"delta":{"content":"","role":"assistant","reasoning":" retrieve","reasoning_details":[{"type":"reasoning.text","text":" retrieve","format":"unknown","index":0}]},"finish_reason":null,"native_finish_reason":null}]} - -data: {"id":"gen-1791215155-kz4x2hrzNK1bBE4DGQEF","object":"chat.completion.chunk","created":1791215155,"model":"qwen/qwen3-30b-a3b","provider":"DeepInfra","choices":[{"index":0,"delta":{"content":"","role":"assistant","reasoning":" the","reasoning_details":[{"type":"reasoning.text","text":" the","format":"unknown","index":0}]},"finish_reason":null,"native_finish_reason":null}]} - -data: {"id":"gen-1791215155-kz4x2hrzNK1bBE4DGQEF","object":"chat.completion.chunk","created":1791215155,"model":"qwen/qwen3-30b-a3b","provider":"DeepInfra","choices":[{"index":0,"delta":{"content":"","role":"assistant","reasoning":" necessary","reasoning_details":[{"type":"reasoning.text","text":" necessary","format":"unknown","index":0}]},"finish_reason":null,"native_finish_reason":null}]} - -data: {"id":"gen-1791215155-kz4x2hrzNK1bBE4DGQEF","object":"chat.completion.chunk","created":1791215155,"model":"qwen/qwen3-30b-a3b","provider":"DeepInfra","choices":[{"index":0,"delta":{"content":"","role":"assistant","reasoning":" documentation","reasoning_details":[{"type":"reasoning.text","text":" documentation","format":"unknown","index":0}]},"finish_reason":null,"native_finish_reason":null}]} - -data: {"id":"gen-1791215155-kz4x2hrzNK1bBE4DGQEF","object":"chat.completion.chunk","created":1791215155,"model":"qwen/qwen3-30b-a3b","provider":"DeepInfra","choices":[{"index":0,"delta":{"content":"","role":"assistant","reasoning":".\n\n","reasoning_details":[{"type":"reasoning.text","text":".\n\n","format":"unknown","index":0}]},"finish_reason":null,"native_finish_reason":null}]} - -data: {"id":"gen-1791215155-kz4x2hrzNK1bBE4DGQEF","object":"chat.completion.chunk","created":1791215155,"model":"qwen/qwen3-30b-a3b","provider":"DeepInfra","choices":[{"index":0,"delta":{"content":"","role":"assistant","reasoning":"After","reasoning_details":[{"type":"reasoning.text","text":"After","format":"unknown","index":0}]},"finish_reason":null,"native_finish_reason":null}]} - -data: {"id":"gen-1791215155-kz4x2hrzNK1bBE4DGQEF","object":"chat.completion.chunk","created":1791215155,"model":"qwen/qwen3-30b-a3b","provider":"DeepInfra","choices":[{"index":0,"delta":{"content":"","role":"assistant","reasoning":" getting","reasoning_details":[{"type":"reasoning.text","text":" getting","format":"unknown","index":0}]},"finish_reason":null,"native_finish_reason":null}]} - -data: {"id":"gen-1791215155-kz4x2hrzNK1bBE4DGQEF","object":"chat.completion.chunk","created":1791215155,"model":"qwen/qwen3-30b-a3b","provider":"DeepInfra","choices":[{"index":0,"delta":{"content":"","role":"assistant","reasoning":" the","reasoning_details":[{"type":"reasoning.text","text":" the","format":"unknown","index":0}]},"finish_reason":null,"native_finish_reason":null}]} - -data: {"id":"gen-1791215155-kz4x2hrzNK1bBE4DGQEF","object":"chat.completion.chunk","created":1791215155,"model":"qwen/qwen3-30b-a3b","provider":"DeepInfra","choices":[{"index":0,"delta":{"content":"","role":"assistant","reasoning":" search","reasoning_details":[{"type":"reasoning.text","text":" search","format":"unknown","index":0}]},"finish_reason":null,"native_finish_reason":null}]} - -data: {"id":"gen-1791215155-kz4x2hrzNK1bBE4DGQEF","object":"chat.completion.chunk","created":1791215155,"model":"qwen/qwen3-30b-a3b","provider":"DeepInfra","choices":[{"index":0,"delta":{"content":"","role":"assistant","reasoning":" results","reasoning_details":[{"type":"reasoning.text","text":" results","format":"unknown","index":0}]},"finish_reason":null,"native_finish_reason":null}]} - -data: {"id":"gen-1791215155-kz4x2hrzNK1bBE4DGQEF","object":"chat.completion.chunk","created":1791215155,"model":"qwen/qwen3-30b-a3b","provider":"DeepInfra","choices":[{"index":0,"delta":{"content":"","role":"assistant","reasoning":",","reasoning_details":[{"type":"reasoning.text","text":",","format":"unknown","index":0}]},"finish_reason":null,"native_finish_reason":null}]} - -data: {"id":"gen-1791215155-kz4x2hrzNK1bBE4DGQEF","object":"chat.completion.chunk","created":1791215155,"model":"qwen/qwen3-30b-a3b","provider":"DeepInfra","choices":[{"index":0,"delta":{"content":"","role":"assistant","reasoning":" I","reasoning_details":[{"type":"reasoning.text","text":" I","format":"unknown","index":0}]},"finish_reason":null,"native_finish_reason":null}]} - -data: {"id":"gen-1791215155-kz4x2hrzNK1bBE4DGQEF","object":"chat.completion.chunk","created":1791215155,"model":"qwen/qwen3-30b-a3b","provider":"DeepInfra","choices":[{"index":0,"delta":{"content":"","role":"assistant","reasoning":" need","reasoning_details":[{"type":"reasoning.text","text":" need","format":"unknown","index":0}]},"finish_reason":null,"native_finish_reason":null}]} - -data: {"id":"gen-1791215155-kz4x2hrzNK1bBE4DGQEF","object":"chat.completion.chunk","created":1791215155,"model":"qwen/qwen3-30b-a3b","provider":"DeepInfra","choices":[{"index":0,"delta":{"content":"","role":"assistant","reasoning":" to","reasoning_details":[{"type":"reasoning.text","text":" to","format":"unknown","index":0}]},"finish_reason":null,"native_finish_reason":null}]} - -data: {"id":"gen-1791215155-kz4x2hrzNK1bBE4DGQEF","object":"chat.completion.chunk","created":1791215155,"model":"qwen/qwen3-30b-a3b","provider":"DeepInfra","choices":[{"index":0,"delta":{"content":"","role":"assistant","reasoning":" provide","reasoning_details":[{"type":"reasoning.text","text":" provide","format":"unknown","index":0}]},"finish_reason":null,"native_finish_reason":null}]} - -data: {"id":"gen-1791215155-kz4x2hrzNK1bBE4DGQEF","object":"chat.completion.chunk","created":1791215155,"model":"qwen/qwen3-30b-a3b","provider":"DeepInfra","choices":[{"index":0,"delta":{"content":"","role":"assistant","reasoning":" a","reasoning_details":[{"type":"reasoning.text","text":" a","format":"unknown","index":0}]},"finish_reason":null,"native_finish_reason":null}]} - -data: {"id":"gen-1791215155-kz4x2hrzNK1bBE4DGQEF","object":"chat.completion.chunk","created":1791215155,"model":"qwen/qwen3-30b-a3b","provider":"DeepInfra","choices":[{"index":0,"delta":{"content":"","role":"assistant","reasoning":" single","reasoning_details":[{"type":"reasoning.text","text":" single","format":"unknown","index":0}]},"finish_reason":null,"native_finish_reason":null}]} - -data: {"id":"gen-1791215155-kz4x2hrzNK1bBE4DGQEF","object":"chat.completion.chunk","created":1791215155,"model":"qwen/qwen3-30b-a3b","provider":"DeepInfra","choices":[{"index":0,"delta":{"content":"","role":"assistant","reasoning":"-s","reasoning_details":[{"type":"reasoning.text","text":"-s","format":"unknown","index":0}]},"finish_reason":null,"native_finish_reason":null}]} - -data: {"id":"gen-1791215155-kz4x2hrzNK1bBE4DGQEF","object":"chat.completion.chunk","created":1791215155,"model":"qwen/qwen3-30b-a3b","provider":"DeepInfra","choices":[{"index":0,"delta":{"content":"","role":"assistant","reasoning":"entence","reasoning_details":[{"type":"reasoning.text","text":"entence","format":"unknown","index":0}]},"finish_reason":null,"native_finish_reason":null}]} - -data: {"id":"gen-1791215155-kz4x2hrzNK1bBE4DGQEF","object":"chat.completion.chunk","created":1791215155,"model":"qwen/qwen3-30b-a3b","provider":"DeepInfra","choices":[{"index":0,"delta":{"content":"","role":"assistant","reasoning":" answer","reasoning_details":[{"type":"reasoning.text","text":" answer","format":"unknown","index":0}]},"finish_reason":null,"native_finish_reason":null}]} - -data: {"id":"gen-1791215155-kz4x2hrzNK1bBE4DGQEF","object":"chat.completion.chunk","created":1791215155,"model":"qwen/qwen3-30b-a3b","provider":"DeepInfra","choices":[{"index":0,"delta":{"content":"","role":"assistant","reasoning":" starting","reasoning_details":[{"type":"reasoning.text","text":" starting","format":"unknown","index":0}]},"finish_reason":null,"native_finish_reason":null}]} - -data: {"id":"gen-1791215155-kz4x2hrzNK1bBE4DGQEF","object":"chat.completion.chunk","created":1791215155,"model":"qwen/qwen3-30b-a3b","provider":"DeepInfra","choices":[{"index":0,"delta":{"content":"","role":"assistant","reasoning":" with","reasoning_details":[{"type":"reasoning.text","text":" with","format":"unknown","index":0}]},"finish_reason":null,"native_finish_reason":null}]} - -data: {"id":"gen-1791215155-kz4x2hrzNK1bBE4DGQEF","object":"chat.completion.chunk","created":1791215155,"model":"qwen/qwen3-30b-a3b","provider":"DeepInfra","choices":[{"index":0,"delta":{"content":"","role":"assistant","reasoning":" \"","reasoning_details":[{"type":"reasoning.text","text":" \"","format":"unknown","index":0}]},"finish_reason":null,"native_finish_reason":null}]} - -data: {"id":"gen-1791215155-kz4x2hrzNK1bBE4DGQEF","object":"chat.completion.chunk","created":1791215155,"model":"qwen/qwen3-30b-a3b","provider":"DeepInfra","choices":[{"index":0,"delta":{"content":"","role":"assistant","reasoning":"Research","reasoning_details":[{"type":"reasoning.text","text":"Research","format":"unknown","index":0}]},"finish_reason":null,"native_finish_reason":null}]} - -data: {"id":"gen-1791215155-kz4x2hrzNK1bBE4DGQEF","object":"chat.completion.chunk","created":1791215155,"model":"qwen/qwen3-30b-a3b","provider":"DeepInfra","choices":[{"index":0,"delta":{"content":"","role":"assistant","reasoning":"er","reasoning_details":[{"type":"reasoning.text","text":"er","format":"unknown","index":0}]},"finish_reason":null,"native_finish_reason":null}]} - -data: {"id":"gen-1791215155-kz4x2hrzNK1bBE4DGQEF","object":"chat.completion.chunk","created":1791215155,"model":"qwen/qwen3-30b-a3b","provider":"DeepInfra","choices":[{"index":0,"delta":{"content":"","role":"assistant","reasoning":" result","reasoning_details":[{"type":"reasoning.text","text":" result","format":"unknown","index":0}]},"finish_reason":null,"native_finish_reason":null}]} - -data: {"id":"gen-1791215155-kz4x2hrzNK1bBE4DGQEF","object":"chat.completion.chunk","created":1791215155,"model":"qwen/qwen3-30b-a3b","provider":"DeepInfra","choices":[{"index":0,"delta":{"content":"","role":"assistant","reasoning":":\"","reasoning_details":[{"type":"reasoning.text","text":":\"","format":"unknown","index":0}]},"finish_reason":null,"native_finish_reason":null}]} - -data: {"id":"gen-1791215155-kz4x2hrzNK1bBE4DGQEF","object":"chat.completion.chunk","created":1791215155,"model":"qwen/qwen3-30b-a3b","provider":"DeepInfra","choices":[{"index":0,"delta":{"content":"","role":"assistant","reasoning":" including","reasoning_details":[{"type":"reasoning.text","text":" including","format":"unknown","index":0}]},"finish_reason":null,"native_finish_reason":null}]} - -data: {"id":"gen-1791215155-kz4x2hrzNK1bBE4DGQEF","object":"chat.completion.chunk","created":1791215155,"model":"qwen/qwen3-30b-a3b","provider":"DeepInfra","choices":[{"index":0,"delta":{"content":"","role":"assistant","reasoning":" the","reasoning_details":[{"type":"reasoning.text","text":" the","format":"unknown","index":0}]},"finish_reason":null,"native_finish_reason":null}]} - -data: {"id":"gen-1791215155-kz4x2hrzNK1bBE4DGQEF","object":"chat.completion.chunk","created":1791215155,"model":"qwen/qwen3-30b-a3b","provider":"DeepInfra","choices":[{"index":0,"delta":{"content":"","role":"assistant","reasoning":" search","reasoning_details":[{"type":"reasoning.text","text":" search","format":"unknown","index":0}]},"finish_reason":null,"native_finish_reason":null}]} - -data: {"id":"gen-1791215155-kz4x2hrzNK1bBE4DGQEF","object":"chat.completion.chunk","created":1791215155,"model":"qwen/qwen3-30b-a3b","provider":"DeepInfra","choices":[{"index":0,"delta":{"content":"","role":"assistant","reasoning":" title","reasoning_details":[{"type":"reasoning.text","text":" title","format":"unknown","index":0}]},"finish_reason":null,"native_finish_reason":null}]} - -data: {"id":"gen-1791215155-kz4x2hrzNK1bBE4DGQEF","object":"chat.completion.chunk","created":1791215155,"model":"qwen/qwen3-30b-a3b","provider":"DeepInfra","choices":[{"index":0,"delta":{"content":"","role":"assistant","reasoning":" and","reasoning_details":[{"type":"reasoning.text","text":" and","format":"unknown","index":0}]},"finish_reason":null,"native_finish_reason":null}]} - -data: {"id":"gen-1791215155-kz4x2hrzNK1bBE4DGQEF","object":"chat.completion.chunk","created":1791215155,"model":"qwen/qwen3-30b-a3b","provider":"DeepInfra","choices":[{"index":0,"delta":{"content":"","role":"assistant","reasoning":" URL","reasoning_details":[{"type":"reasoning.text","text":" URL","format":"unknown","index":0}]},"finish_reason":null,"native_finish_reason":null}]} - -data: {"id":"gen-1791215155-kz4x2hrzNK1bBE4DGQEF","object":"chat.completion.chunk","created":1791215155,"model":"qwen/qwen3-30b-a3b","provider":"DeepInfra","choices":[{"index":0,"delta":{"content":"","role":"assistant","reasoning":".","reasoning_details":[{"type":"reasoning.text","text":".","format":"unknown","index":0}]},"finish_reason":null,"native_finish_reason":null}]} - -data: {"id":"gen-1791215155-kz4x2hrzNK1bBE4DGQEF","object":"chat.completion.chunk","created":1791215155,"model":"qwen/qwen3-30b-a3b","provider":"DeepInfra","choices":[{"index":0,"delta":{"content":"","role":"assistant","reasoning":" But","reasoning_details":[{"type":"reasoning.text","text":" But","format":"unknown","index":0}]},"finish_reason":null,"native_finish_reason":null}]} - -data: {"id":"gen-1791215155-kz4x2hrzNK1bBE4DGQEF","object":"chat.completion.chunk","created":1791215155,"model":"qwen/qwen3-30b-a3b","provider":"DeepInfra","choices":[{"index":0,"delta":{"content":"","role":"assistant","reasoning":" since","reasoning_details":[{"type":"reasoning.text","text":" since","format":"unknown","index":0}]},"finish_reason":null,"native_finish_reason":null}]} - -data: {"id":"gen-1791215155-kz4x2hrzNK1bBE4DGQEF","object":"chat.completion.chunk","created":1791215155,"model":"qwen/qwen3-30b-a3b","provider":"DeepInfra","choices":[{"index":0,"delta":{"content":"","role":"assistant","reasoning":" I","reasoning_details":[{"type":"reasoning.text","text":" I","format":"unknown","index":0}]},"finish_reason":null,"native_finish_reason":null}]} - -data: {"id":"gen-1791215155-kz4x2hrzNK1bBE4DGQEF","object":"chat.completion.chunk","created":1791215155,"model":"qwen/qwen3-30b-a3b","provider":"DeepInfra","choices":[{"index":0,"delta":{"content":"","role":"assistant","reasoning":" don","reasoning_details":[{"type":"reasoning.text","text":" don","format":"unknown","index":0}]},"finish_reason":null,"native_finish_reason":null}]} - -data: {"id":"gen-1791215155-kz4x2hrzNK1bBE4DGQEF","object":"chat.completion.chunk","created":1791215155,"model":"qwen/qwen3-30b-a3b","provider":"DeepInfra","choices":[{"index":0,"delta":{"content":"","role":"assistant","reasoning":"'t","reasoning_details":[{"type":"reasoning.text","text":"'t","format":"unknown","index":0}]},"finish_reason":null,"native_finish_reason":null}]} - -data: {"id":"gen-1791215155-kz4x2hrzNK1bBE4DGQEF","object":"chat.completion.chunk","created":1791215155,"model":"qwen/qwen3-30b-a3b","provider":"DeepInfra","choices":[{"index":0,"delta":{"content":"","role":"assistant","reasoning":" have","reasoning_details":[{"type":"reasoning.text","text":" have","format":"unknown","index":0}]},"finish_reason":null,"native_finish_reason":null}]} - -data: {"id":"gen-1791215155-kz4x2hrzNK1bBE4DGQEF","object":"chat.completion.chunk","created":1791215155,"model":"qwen/qwen3-30b-a3b","provider":"DeepInfra","choices":[{"index":0,"delta":{"content":"","role":"assistant","reasoning":" real","reasoning_details":[{"type":"reasoning.text","text":" real","format":"unknown","index":0}]},"finish_reason":null,"native_finish_reason":null}]} - -data: {"id":"gen-1791215155-kz4x2hrzNK1bBE4DGQEF","object":"chat.completion.chunk","created":1791215155,"model":"qwen/qwen3-30b-a3b","provider":"DeepInfra","choices":[{"index":0,"delta":{"content":"","role":"assistant","reasoning":"-time","reasoning_details":[{"type":"reasoning.text","text":"-time","format":"unknown","index":0}]},"finish_reason":null,"native_finish_reason":null}]} - -data: {"id":"gen-1791215155-kz4x2hrzNK1bBE4DGQEF","object":"chat.completion.chunk","created":1791215155,"model":"qwen/qwen3-30b-a3b","provider":"DeepInfra","choices":[{"index":0,"delta":{"content":"","role":"assistant","reasoning":" data","reasoning_details":[{"type":"reasoning.text","text":" data","format":"unknown","index":0}]},"finish_reason":null,"native_finish_reason":null}]} - -data: {"id":"gen-1791215155-kz4x2hrzNK1bBE4DGQEF","object":"chat.completion.chunk","created":1791215155,"model":"qwen/qwen3-30b-a3b","provider":"DeepInfra","choices":[{"index":0,"delta":{"content":"","role":"assistant","reasoning":",","reasoning_details":[{"type":"reasoning.text","text":",","format":"unknown","index":0}]},"finish_reason":null,"native_finish_reason":null}]} - -data: {"id":"gen-1791215155-kz4x2hrzNK1bBE4DGQEF","object":"chat.completion.chunk","created":1791215155,"model":"qwen/qwen3-30b-a3b","provider":"DeepInfra","choices":[{"index":0,"delta":{"content":"","role":"assistant","reasoning":" I","reasoning_details":[{"type":"reasoning.text","text":" I","format":"unknown","index":0}]},"finish_reason":null,"native_finish_reason":null}]} - -data: {"id":"gen-1791215155-kz4x2hrzNK1bBE4DGQEF","object":"chat.completion.chunk","created":1791215155,"model":"qwen/qwen3-30b-a3b","provider":"DeepInfra","choices":[{"index":0,"delta":{"content":"","role":"assistant","reasoning":"'ll","reasoning_details":[{"type":"reasoning.text","text":"'ll","format":"unknown","index":0}]},"finish_reason":null,"native_finish_reason":null}]} - -data: {"id":"gen-1791215155-kz4x2hrzNK1bBE4DGQEF","object":"chat.completion.chunk","created":1791215155,"model":"qwen/qwen3-30b-a3b","provider":"DeepInfra","choices":[{"index":0,"delta":{"content":"","role":"assistant","reasoning":" simulate","reasoning_details":[{"type":"reasoning.text","text":" simulate","format":"unknown","index":0}]},"finish_reason":null,"native_finish_reason":null}]} - -data: {"id":"gen-1791215155-kz4x2hrzNK1bBE4DGQEF","object":"chat.completion.chunk","created":1791215155,"model":"qwen/qwen3-30b-a3b","provider":"DeepInfra","choices":[{"index":0,"delta":{"content":"","role":"assistant","reasoning":" the","reasoning_details":[{"type":"reasoning.text","text":" the","format":"unknown","index":0}]},"finish_reason":null,"native_finish_reason":null}]} - -data: {"id":"gen-1791215155-kz4x2hrzNK1bBE4DGQEF","object":"chat.completion.chunk","created":1791215155,"model":"qwen/qwen3-30b-a3b","provider":"DeepInfra","choices":[{"index":0,"delta":{"content":"","role":"assistant","reasoning":" response","reasoning_details":[{"type":"reasoning.text","text":" response","format":"unknown","index":0}]},"finish_reason":null,"native_finish_reason":null}]} - -data: {"id":"gen-1791215155-kz4x2hrzNK1bBE4DGQEF","object":"chat.completion.chunk","created":1791215155,"model":"qwen/qwen3-30b-a3b","provider":"DeepInfra","choices":[{"index":0,"delta":{"content":"","role":"assistant","reasoning":" based","reasoning_details":[{"type":"reasoning.text","text":" based","format":"unknown","index":0}]},"finish_reason":null,"native_finish_reason":null}]} - -data: {"id":"gen-1791215155-kz4x2hrzNK1bBE4DGQEF","object":"chat.completion.chunk","created":1791215155,"model":"qwen/qwen3-30b-a3b","provider":"DeepInfra","choices":[{"index":0,"delta":{"content":"","role":"assistant","reasoning":" on","reasoning_details":[{"type":"reasoning.text","text":" on","format":"unknown","index":0}]},"finish_reason":null,"native_finish_reason":null}]} - -data: {"id":"gen-1791215155-kz4x2hrzNK1bBE4DGQEF","object":"chat.completion.chunk","created":1791215155,"model":"qwen/qwen3-30b-a3b","provider":"DeepInfra","choices":[{"index":0,"delta":{"content":"","role":"assistant","reasoning":" typical","reasoning_details":[{"type":"reasoning.text","text":" typical","format":"unknown","index":0}]},"finish_reason":null,"native_finish_reason":null}]} - -data: {"id":"gen-1791215155-kz4x2hrzNK1bBE4DGQEF","object":"chat.completion.chunk","created":1791215155,"model":"qwen/qwen3-30b-a3b","provider":"DeepInfra","choices":[{"index":0,"delta":{"content":"","role":"assistant","reasoning":" documentation","reasoning_details":[{"type":"reasoning.text","text":" documentation","format":"unknown","index":0}]},"finish_reason":null,"native_finish_reason":null}]} - -data: {"id":"gen-1791215155-kz4x2hrzNK1bBE4DGQEF","object":"chat.completion.chunk","created":1791215155,"model":"qwen/qwen3-30b-a3b","provider":"DeepInfra","choices":[{"index":0,"delta":{"content":"","role":"assistant","reasoning":" structures","reasoning_details":[{"type":"reasoning.text","text":" structures","format":"unknown","index":0}]},"finish_reason":null,"native_finish_reason":null}]} - -data: {"id":"gen-1791215155-kz4x2hrzNK1bBE4DGQEF","object":"chat.completion.chunk","created":1791215155,"model":"qwen/qwen3-30b-a3b","provider":"DeepInfra","choices":[{"index":0,"delta":{"content":"","role":"assistant","reasoning":".","reasoning_details":[{"type":"reasoning.text","text":".","format":"unknown","index":0}]},"finish_reason":null,"native_finish_reason":null}]} - -data: {"id":"gen-1791215155-kz4x2hrzNK1bBE4DGQEF","object":"chat.completion.chunk","created":1791215155,"model":"qwen/qwen3-30b-a3b","provider":"DeepInfra","choices":[{"index":0,"delta":{"content":"","role":"assistant","reasoning":" \n\n","reasoning_details":[{"type":"reasoning.text","text":" \n\n","format":"unknown","index":0}]},"finish_reason":null,"native_finish_reason":null}]} - -data: {"id":"gen-1791215155-kz4x2hrzNK1bBE4DGQEF","object":"chat.completion.chunk","created":1791215155,"model":"qwen/qwen3-30b-a3b","provider":"DeepInfra","choices":[{"index":0,"delta":{"content":"","role":"assistant","reasoning":"I","reasoning_details":[{"type":"reasoning.text","text":"I","format":"unknown","index":0}]},"finish_reason":null,"native_finish_reason":null}]} - -data: {"id":"gen-1791215155-kz4x2hrzNK1bBE4DGQEF","object":"chat.completion.chunk","created":1791215155,"model":"qwen/qwen3-30b-a3b","provider":"DeepInfra","choices":[{"index":0,"delta":{"content":"","role":"assistant","reasoning":" should","reasoning_details":[{"type":"reasoning.text","text":" should","format":"unknown","index":0}]},"finish_reason":null,"native_finish_reason":null}]} - -data: {"id":"gen-1791215155-kz4x2hrzNK1bBE4DGQEF","object":"chat.completion.chunk","created":1791215155,"model":"qwen/qwen3-30b-a3b","provider":"DeepInfra","choices":[{"index":0,"delta":{"content":"","role":"assistant","reasoning":" check","reasoning_details":[{"type":"reasoning.text","text":" check","format":"unknown","index":0}]},"finish_reason":null,"native_finish_reason":null}]} - -data: {"id":"gen-1791215155-kz4x2hrzNK1bBE4DGQEF","object":"chat.completion.chunk","created":1791215155,"model":"qwen/qwen3-30b-a3b","provider":"DeepInfra","choices":[{"index":0,"delta":{"content":"","role":"assistant","reasoning":" if","reasoning_details":[{"type":"reasoning.text","text":" if","format":"unknown","index":0}]},"finish_reason":null,"native_finish_reason":null}]} - -data: {"id":"gen-1791215155-kz4x2hrzNK1bBE4DGQEF","object":"chat.completion.chunk","created":1791215155,"model":"qwen/qwen3-30b-a3b","provider":"DeepInfra","choices":[{"index":0,"delta":{"content":"","role":"assistant","reasoning":" there","reasoning_details":[{"type":"reasoning.text","text":" there","format":"unknown","index":0}]},"finish_reason":null,"native_finish_reason":null}]} - -data: {"id":"gen-1791215155-kz4x2hrzNK1bBE4DGQEF","object":"chat.completion.chunk","created":1791215155,"model":"qwen/qwen3-30b-a3b","provider":"DeepInfra","choices":[{"index":0,"delta":{"content":"","role":"assistant","reasoning":" are","reasoning_details":[{"type":"reasoning.text","text":" are","format":"unknown","index":0}]},"finish_reason":null,"native_finish_reason":null}]} - -data: {"id":"gen-1791215155-kz4x2hrzNK1bBE4DGQEF","object":"chat.completion.chunk","created":1791215155,"model":"qwen/qwen3-30b-a3b","provider":"DeepInfra","choices":[{"index":0,"delta":{"content":"","role":"assistant","reasoning":" any","reasoning_details":[{"type":"reasoning.text","text":" any","format":"unknown","index":0}]},"finish_reason":null,"native_finish_reason":null}]} - -data: {"id":"gen-1791215155-kz4x2hrzNK1bBE4DGQEF","object":"chat.completion.chunk","created":1791215155,"model":"qwen/qwen3-30b-a3b","provider":"DeepInfra","choices":[{"index":0,"delta":{"content":"","role":"assistant","reasoning":" other","reasoning_details":[{"type":"reasoning.text","text":" other","format":"unknown","index":0}]},"finish_reason":null,"native_finish_reason":null}]} - -data: {"id":"gen-1791215155-kz4x2hrzNK1bBE4DGQEF","object":"chat.completion.chunk","created":1791215155,"model":"qwen/qwen3-30b-a3b","provider":"DeepInfra","choices":[{"index":0,"delta":{"content":"","role":"assistant","reasoning":" tools","reasoning_details":[{"type":"reasoning.text","text":" tools","format":"unknown","index":0}]},"finish_reason":null,"native_finish_reason":null}]} - -data: {"id":"gen-1791215155-kz4x2hrzNK1bBE4DGQEF","object":"chat.completion.chunk","created":1791215155,"model":"qwen/qwen3-30b-a3b","provider":"DeepInfra","choices":[{"index":0,"delta":{"content":"","role":"assistant","reasoning":" needed","reasoning_details":[{"type":"reasoning.text","text":" needed","format":"unknown","index":0}]},"finish_reason":null,"native_finish_reason":null}]} - -data: {"id":"gen-1791215155-kz4x2hrzNK1bBE4DGQEF","object":"chat.completion.chunk","created":1791215155,"model":"qwen/qwen3-30b-a3b","provider":"DeepInfra","choices":[{"index":0,"delta":{"content":"","role":"assistant","reasoning":",","reasoning_details":[{"type":"reasoning.text","text":",","format":"unknown","index":0}]},"finish_reason":null,"native_finish_reason":null}]} - -data: {"id":"gen-1791215155-kz4x2hrzNK1bBE4DGQEF","object":"chat.completion.chunk","created":1791215155,"model":"qwen/qwen3-30b-a3b","provider":"DeepInfra","choices":[{"index":0,"delta":{"content":"","role":"assistant","reasoning":" but","reasoning_details":[{"type":"reasoning.text","text":" but","format":"unknown","index":0}]},"finish_reason":null,"native_finish_reason":null}]} - -data: {"id":"gen-1791215155-kz4x2hrzNK1bBE4DGQEF","object":"chat.completion.chunk","created":1791215155,"model":"qwen/qwen3-30b-a3b","provider":"DeepInfra","choices":[{"index":0,"delta":{"content":"","role":"assistant","reasoning":" according","reasoning_details":[{"type":"reasoning.text","text":" according","format":"unknown","index":0}]},"finish_reason":null,"native_finish_reason":null}]} - -data: {"id":"gen-1791215155-kz4x2hrzNK1bBE4DGQEF","object":"chat.completion.chunk","created":1791215155,"model":"qwen/qwen3-30b-a3b","provider":"DeepInfra","choices":[{"index":0,"delta":{"content":"","role":"assistant","reasoning":" to","reasoning_details":[{"type":"reasoning.text","text":" to","format":"unknown","index":0}]},"finish_reason":null,"native_finish_reason":null}]} - -data: {"id":"gen-1791215155-kz4x2hrzNK1bBE4DGQEF","object":"chat.completion.chunk","created":1791215155,"model":"qwen/qwen3-30b-a3b","provider":"DeepInfra","choices":[{"index":0,"delta":{"content":"","role":"assistant","reasoning":" the","reasoning_details":[{"type":"reasoning.text","text":" the","format":"unknown","index":0}]},"finish_reason":null,"native_finish_reason":null}]} - -data: {"id":"gen-1791215155-kz4x2hrzNK1bBE4DGQEF","object":"chat.completion.chunk","created":1791215155,"model":"qwen/qwen3-30b-a3b","provider":"DeepInfra","choices":[{"index":0,"delta":{"content":"","role":"assistant","reasoning":" task","reasoning_details":[{"type":"reasoning.text","text":" task","format":"unknown","index":0}]},"finish_reason":null,"native_finish_reason":null}]} - -data: {"id":"gen-1791215155-kz4x2hrzNK1bBE4DGQEF","object":"chat.completion.chunk","created":1791215155,"model":"qwen/qwen3-30b-a3b","provider":"DeepInfra","choices":[{"index":0,"delta":{"content":"","role":"assistant","reasoning":",","reasoning_details":[{"type":"reasoning.text","text":",","format":"unknown","index":0}]},"finish_reason":null,"native_finish_reason":null}]} - -data: {"id":"gen-1791215155-kz4x2hrzNK1bBE4DGQEF","object":"chat.completion.chunk","created":1791215155,"model":"qwen/qwen3-30b-a3b","provider":"DeepInfra","choices":[{"index":0,"delta":{"content":"","role":"assistant","reasoning":" after","reasoning_details":[{"type":"reasoning.text","text":" after","format":"unknown","index":0}]},"finish_reason":null,"native_finish_reason":null}]} - -data: {"id":"gen-1791215155-kz4x2hrzNK1bBE4DGQEF","object":"chat.completion.chunk","created":1791215155,"model":"qwen/qwen3-30b-a3b","provider":"DeepInfra","choices":[{"index":0,"delta":{"content":"","role":"assistant","reasoning":" the","reasoning_details":[{"type":"reasoning.text","text":" the","format":"unknown","index":0}]},"finish_reason":null,"native_finish_reason":null}]} - -data: {"id":"gen-1791215155-kz4x2hrzNK1bBE4DGQEF","object":"chat.completion.chunk","created":1791215155,"model":"qwen/qwen3-30b-a3b","provider":"DeepInfra","choices":[{"index":0,"delta":{"content":"","role":"assistant","reasoning":" search","reasoning_details":[{"type":"reasoning.text","text":" search","format":"unknown","index":0}]},"finish_reason":null,"native_finish_reason":null}]} - -data: {"id":"gen-1791215155-kz4x2hrzNK1bBE4DGQEF","object":"chat.completion.chunk","created":1791215155,"model":"qwen/qwen3-30b-a3b","provider":"DeepInfra","choices":[{"index":0,"delta":{"content":"","role":"assistant","reasoning":",","reasoning_details":[{"type":"reasoning.text","text":",","format":"unknown","index":0}]},"finish_reason":null,"native_finish_reason":null}]} - -data: {"id":"gen-1791215155-kz4x2hrzNK1bBE4DGQEF","object":"chat.completion.chunk","created":1791215155,"model":"qwen/qwen3-30b-a3b","provider":"DeepInfra","choices":[{"index":0,"delta":{"content":"","role":"assistant","reasoning":" the","reasoning_details":[{"type":"reasoning.text","text":" the","format":"unknown","index":0}]},"finish_reason":null,"native_finish_reason":null}]} - -data: {"id":"gen-1791215155-kz4x2hrzNK1bBE4DGQEF","object":"chat.completion.chunk","created":1791215155,"model":"qwen/qwen3-30b-a3b","provider":"DeepInfra","choices":[{"index":0,"delta":{"content":"","role":"assistant","reasoning":" answer","reasoning_details":[{"type":"reasoning.text","text":" answer","format":"unknown","index":0}]},"finish_reason":null,"native_finish_reason":null}]} - -data: {"id":"gen-1791215155-kz4x2hrzNK1bBE4DGQEF","object":"chat.completion.chunk","created":1791215155,"model":"qwen/qwen3-30b-a3b","provider":"DeepInfra","choices":[{"index":0,"delta":{"content":"","role":"assistant","reasoning":" is","reasoning_details":[{"type":"reasoning.text","text":" is","format":"unknown","index":0}]},"finish_reason":null,"native_finish_reason":null}]} - -data: {"id":"gen-1791215155-kz4x2hrzNK1bBE4DGQEF","object":"chat.completion.chunk","created":1791215155,"model":"qwen/qwen3-30b-a3b","provider":"DeepInfra","choices":[{"index":0,"delta":{"content":"","role":"assistant","reasoning":" straightforward","reasoning_details":[{"type":"reasoning.text","text":" straightforward","format":"unknown","index":0}]},"finish_reason":null,"native_finish_reason":null}]} - -data: {"id":"gen-1791215155-kz4x2hrzNK1bBE4DGQEF","object":"chat.completion.chunk","created":1791215155,"model":"qwen/qwen3-30b-a3b","provider":"DeepInfra","choices":[{"index":0,"delta":{"content":"","role":"assistant","reasoning":".","reasoning_details":[{"type":"reasoning.text","text":".","format":"unknown","index":0}]},"finish_reason":null,"native_finish_reason":null}]} - -data: {"id":"gen-1791215155-kz4x2hrzNK1bBE4DGQEF","object":"chat.completion.chunk","created":1791215155,"model":"qwen/qwen3-30b-a3b","provider":"DeepInfra","choices":[{"index":0,"delta":{"content":"","role":"assistant","reasoning":" No","reasoning_details":[{"type":"reasoning.text","text":" No","format":"unknown","index":0}]},"finish_reason":null,"native_finish_reason":null}]} - -data: {"id":"gen-1791215155-kz4x2hrzNK1bBE4DGQEF","object":"chat.completion.chunk","created":1791215155,"model":"qwen/qwen3-30b-a3b","provider":"DeepInfra","choices":[{"index":0,"delta":{"content":"","role":"assistant","reasoning":" other","reasoning_details":[{"type":"reasoning.text","text":" other","format":"unknown","index":0}]},"finish_reason":null,"native_finish_reason":null}]} - -data: {"id":"gen-1791215155-kz4x2hrzNK1bBE4DGQEF","object":"chat.completion.chunk","created":1791215155,"model":"qwen/qwen3-30b-a3b","provider":"DeepInfra","choices":[{"index":0,"delta":{"content":"","role":"assistant","reasoning":" functions","reasoning_details":[{"type":"reasoning.text","text":" functions","format":"unknown","index":0}]},"finish_reason":null,"native_finish_reason":null}]} - -data: {"id":"gen-1791215155-kz4x2hrzNK1bBE4DGQEF","object":"chat.completion.chunk","created":1791215155,"model":"qwen/qwen3-30b-a3b","provider":"DeepInfra","choices":[{"index":0,"delta":{"content":"","role":"assistant","reasoning":" like","reasoning_details":[{"type":"reasoning.text","text":" like","format":"unknown","index":0}]},"finish_reason":null,"native_finish_reason":null}]} - -data: {"id":"gen-1791215155-kz4x2hrzNK1bBE4DGQEF","object":"chat.completion.chunk","created":1791215155,"model":"qwen/qwen3-30b-a3b","provider":"DeepInfra","choices":[{"index":0,"delta":{"content":"","role":"assistant","reasoning":" grep","reasoning_details":[{"type":"reasoning.text","text":" grep","format":"unknown","index":0}]},"finish_reason":null,"native_finish_reason":null}]} - -data: {"id":"gen-1791215155-kz4x2hrzNK1bBE4DGQEF","object":"chat.completion.chunk","created":1791215155,"model":"qwen/qwen3-30b-a3b","provider":"DeepInfra","choices":[{"index":0,"delta":{"content":"","role":"assistant","reasoning":" or","reasoning_details":[{"type":"reasoning.text","text":" or","format":"unknown","index":0}]},"finish_reason":null,"native_finish_reason":null}]} - -data: {"id":"gen-1791215155-kz4x2hrzNK1bBE4DGQEF","object":"chat.completion.chunk","created":1791215155,"model":"qwen/qwen3-30b-a3b","provider":"DeepInfra","choices":[{"index":0,"delta":{"content":"","role":"assistant","reasoning":" read","reasoning_details":[{"type":"reasoning.text","text":" read","format":"unknown","index":0}]},"finish_reason":null,"native_finish_reason":null}]} - -data: {"id":"gen-1791215155-kz4x2hrzNK1bBE4DGQEF","object":"chat.completion.chunk","created":1791215155,"model":"qwen/qwen3-30b-a3b","provider":"DeepInfra","choices":[{"index":0,"delta":{"content":"","role":"assistant","reasoning":"_file","reasoning_details":[{"type":"reasoning.text","text":"_file","format":"unknown","index":0}]},"finish_reason":null,"native_finish_reason":null}]} - -data: {"id":"gen-1791215155-kz4x2hrzNK1bBE4DGQEF","object":"chat.completion.chunk","created":1791215155,"model":"qwen/qwen3-30b-a3b","provider":"DeepInfra","choices":[{"index":0,"delta":{"content":"","role":"assistant","reasoning":" seem","reasoning_details":[{"type":"reasoning.text","text":" seem","format":"unknown","index":0}]},"finish_reason":null,"native_finish_reason":null}]} - -data: {"id":"gen-1791215155-kz4x2hrzNK1bBE4DGQEF","object":"chat.completion.chunk","created":1791215155,"model":"qwen/qwen3-30b-a3b","provider":"DeepInfra","choices":[{"index":0,"delta":{"content":"","role":"assistant","reasoning":" necessary","reasoning_details":[{"type":"reasoning.text","text":" necessary","format":"unknown","index":0}]},"finish_reason":null,"native_finish_reason":null}]} - -data: {"id":"gen-1791215155-kz4x2hrzNK1bBE4DGQEF","object":"chat.completion.chunk","created":1791215155,"model":"qwen/qwen3-30b-a3b","provider":"DeepInfra","choices":[{"index":0,"delta":{"content":"","role":"assistant","reasoning":" here","reasoning_details":[{"type":"reasoning.text","text":" here","format":"unknown","index":0}]},"finish_reason":null,"native_finish_reason":null}]} - -data: {"id":"gen-1791215155-kz4x2hrzNK1bBE4DGQEF","object":"chat.completion.chunk","created":1791215155,"model":"qwen/qwen3-30b-a3b","provider":"DeepInfra","choices":[{"index":0,"delta":{"content":"","role":"assistant","reasoning":".","reasoning_details":[{"type":"reasoning.text","text":".","format":"unknown","index":0}]},"finish_reason":null,"native_finish_reason":null}]} - -data: {"id":"gen-1791215155-kz4x2hrzNK1bBE4DGQEF","object":"chat.completion.chunk","created":1791215155,"model":"qwen/qwen3-30b-a3b","provider":"DeepInfra","choices":[{"index":0,"delta":{"content":"","role":"assistant","reasoning":" The","reasoning_details":[{"type":"reasoning.text","text":" The","format":"unknown","index":0}]},"finish_reason":null,"native_finish_reason":null}]} - -data: {"id":"gen-1791215155-kz4x2hrzNK1bBE4DGQEF","object":"chat.completion.chunk","created":1791215155,"model":"qwen/qwen3-30b-a3b","provider":"DeepInfra","choices":[{"index":0,"delta":{"content":"","role":"assistant","reasoning":" main","reasoning_details":[{"type":"reasoning.text","text":" main","format":"unknown","index":0}]},"finish_reason":null,"native_finish_reason":null}]} - -data: {"id":"gen-1791215155-kz4x2hrzNK1bBE4DGQEF","object":"chat.completion.chunk","created":1791215155,"model":"qwen/qwen3-30b-a3b","provider":"DeepInfra","choices":[{"index":0,"delta":{"content":"","role":"assistant","reasoning":" action","reasoning_details":[{"type":"reasoning.text","text":" action","format":"unknown","index":0}]},"finish_reason":null,"native_finish_reason":null}]} - -data: {"id":"gen-1791215155-kz4x2hrzNK1bBE4DGQEF","object":"chat.completion.chunk","created":1791215155,"model":"qwen/qwen3-30b-a3b","provider":"DeepInfra","choices":[{"index":0,"delta":{"content":"","role":"assistant","reasoning":" is","reasoning_details":[{"type":"reasoning.text","text":" is","format":"unknown","index":0}]},"finish_reason":null,"native_finish_reason":null}]} - -data: {"id":"gen-1791215155-kz4x2hrzNK1bBE4DGQEF","object":"chat.completion.chunk","created":1791215155,"model":"qwen/qwen3-30b-a3b","provider":"DeepInfra","choices":[{"index":0,"delta":{"content":"","role":"assistant","reasoning":" the","reasoning_details":[{"type":"reasoning.text","text":" the","format":"unknown","index":0}]},"finish_reason":null,"native_finish_reason":null}]} - -data: {"id":"gen-1791215155-kz4x2hrzNK1bBE4DGQEF","object":"chat.completion.chunk","created":1791215155,"model":"qwen/qwen3-30b-a3b","provider":"DeepInfra","choices":[{"index":0,"delta":{"content":"","role":"assistant","reasoning":" search","reasoning_details":[{"type":"reasoning.text","text":" search","format":"unknown","index":0}]},"finish_reason":null,"native_finish_reason":null}]} - -data: {"id":"gen-1791215155-kz4x2hrzNK1bBE4DGQEF","object":"chat.completion.chunk","created":1791215155,"model":"qwen/qwen3-30b-a3b","provider":"DeepInfra","choices":[{"index":0,"delta":{"content":"","role":"assistant","reasoning":",","reasoning_details":[{"type":"reasoning.text","text":",","format":"unknown","index":0}]},"finish_reason":null,"native_finish_reason":null}]} - -data: {"id":"gen-1791215155-kz4x2hrzNK1bBE4DGQEF","object":"chat.completion.chunk","created":1791215155,"model":"qwen/qwen3-30b-a3b","provider":"DeepInfra","choices":[{"index":0,"delta":{"content":"","role":"assistant","reasoning":" then","reasoning_details":[{"type":"reasoning.text","text":" then","format":"unknown","index":0}]},"finish_reason":null,"native_finish_reason":null}]} - -data: {"id":"gen-1791215155-kz4x2hrzNK1bBE4DGQEF","object":"chat.completion.chunk","created":1791215155,"model":"qwen/qwen3-30b-a3b","provider":"DeepInfra","choices":[{"index":0,"delta":{"content":"","role":"assistant","reasoning":" presenting","reasoning_details":[{"type":"reasoning.text","text":" presenting","format":"unknown","index":0}]},"finish_reason":null,"native_finish_reason":null}]} - -data: {"id":"gen-1791215155-kz4x2hrzNK1bBE4DGQEF","object":"chat.completion.chunk","created":1791215155,"model":"qwen/qwen3-30b-a3b","provider":"DeepInfra","choices":[{"index":0,"delta":{"content":"","role":"assistant","reasoning":" the","reasoning_details":[{"type":"reasoning.text","text":" the","format":"unknown","index":0}]},"finish_reason":null,"native_finish_reason":null}]} - -data: {"id":"gen-1791215155-kz4x2hrzNK1bBE4DGQEF","object":"chat.completion.chunk","created":1791215155,"model":"qwen/qwen3-30b-a3b","provider":"DeepInfra","choices":[{"index":0,"delta":{"content":"","role":"assistant","reasoning":" result","reasoning_details":[{"type":"reasoning.text","text":" result","format":"unknown","index":0}]},"finish_reason":null,"native_finish_reason":null}]} - -data: {"id":"gen-1791215155-kz4x2hrzNK1bBE4DGQEF","object":"chat.completion.chunk","created":1791215155,"model":"qwen/qwen3-30b-a3b","provider":"DeepInfra","choices":[{"index":0,"delta":{"content":"","role":"assistant","reasoning":".\n","reasoning_details":[{"type":"reasoning.text","text":".\n","format":"unknown","index":0}]},"finish_reason":null,"native_finish_reason":null}]} - -data: {"id":"gen-1791215155-kz4x2hrzNK1bBE4DGQEF","object":"chat.completion.chunk","created":1791215155,"model":"qwen/qwen3-30b-a3b","provider":"DeepInfra","choices":[{"index":0,"delta":{"content":null,"role":"assistant","tool_calls":[{"index":0,"id":"call_cb73","type":"function","function":{"name":"search","arguments":""}}]},"finish_reason":null,"native_finish_reason":null}]} - -data: {"id":"gen-1791215155-kz4x2hrzNK1bBE4DGQEF","object":"chat.completion.chunk","created":1791215155,"model":"qwen/qwen3-30b-a3b","provider":"DeepInfra","choices":[{"index":0,"delta":{"content":null,"role":"assistant","tool_calls":[{"index":0,"function":{"arguments":"{\"query\": \"Run the Braintrust Eve instrumentation e2e scenario\"}"}}]},"finish_reason":null,"native_finish_reason":null}]} - -data: {"id":"gen-1791215155-kz4x2hrzNK1bBE4DGQEF","object":"chat.completion.chunk","created":1791215155,"model":"qwen/qwen3-30b-a3b","provider":"DeepInfra","choices":[{"index":0,"delta":{"content":"","role":"assistant","reasoning":null},"finish_reason":"tool_calls","native_finish_reason":"tool_calls"}]} - -data: {"id":"gen-1791215155-kz4x2hrzNK1bBE4DGQEF","object":"chat.completion.chunk","created":1791215155,"model":"qwen/qwen3-30b-a3b","provider":"DeepInfra","service_tier":"default","choices":[{"index":0,"delta":{"content":"","role":"assistant"},"finish_reason":"tool_calls","native_finish_reason":"tool_calls"}],"usage":{"prompt_tokens":3036,"completion_tokens":288,"total_tokens":3324,"cost":0.00050832,"is_byok":false,"prompt_tokens_details":{"cached_tokens":0,"cache_write_tokens":0,"audio_tokens":0,"video_tokens":0},"cost_details":{"upstream_inference_cost":0.00050832,"upstream_inference_prompt_cost":0.00036432,"upstream_inference_completions_cost":0.000144},"completion_tokens_details":{"reasoning_tokens":335,"image_tokens":0,"audio_tokens":0}}} - -data: [DONE] - diff --git a/e2e/scenarios/eve-instrumentation/__cassettes__/eve-v0.cassette.json b/e2e/scenarios/eve-instrumentation/__cassettes__/eve-v0.cassette.json deleted file mode 100644 index 3c9d9dcfc..000000000 --- a/e2e/scenarios/eve-instrumentation/__cassettes__/eve-v0.cassette.json +++ /dev/null @@ -1,3973 +0,0 @@ -{ - "entries": [ - { - "callIndex": 0, - "id": "3c79a0d899ec7366", - "matchKey": "POST openrouter.ai/api/v1/chat/completions", - "recordedAt": "2026-10-05T15:45:51.378Z", - "request": { - "body": { - "kind": "json", - "value": { - "messages": [ - { - "content": [ - { - "text": "Instructions (instructions)\nYou are a deterministic fixture agent for Braintrust Eve instrumentation tests.\n\nFor every user task, follow these steps exactly:\n\n1. Your first response MUST contain exactly these two tool calls and no text:\n - researcher with only this argument: {\"message\":\"\"}.\n Copy the current user message verbatim, including words such as \"again\";\n never reuse a message from an earlier turn. Do not pass outputSchema.\n - read with only this argument:\n {\"url\":\"https://eve.dev/docs/guides/instrumentation\"}.\n Emit both calls simultaneously in the same response, with researcher first\n and read second, so Eve executes them as one parallel batch. The calls are\n independent. NEVER call one by itself, wait for its result, or emit either\n call in a separate response.\n2. If a tool result says \"Started task\", your next response MUST be exactly one\n task_wait call with no arguments and no text.\n3. Only after both results are available, answer with a single sentence that\n starts with \"Final answer from read:\" and includes the researcher result, read\n title, URL, and read excerpt.\n\nTool execution\nA single tool or subagent call runs as one serial action. If you call multiple independent tools or subagents in one response, eve treats that batch as parallel work. Only batch work that is independent and does not rely on another call in the same response.", - "type": "text" - } - ], - "role": "system" - }, - { - "content": "Run the Braintrust Eve instrumentation e2e scenario", - "role": "user" - } - ], - "model": "qwen/qwen3-30b-a3b", - "provider": { - "only": ["deepinfra"], - "require_parameters": true - }, - "reasoning": { - "effort": "low" - }, - "stream": true, - "tool_choice": "auto", - "tools": [ - { - "function": { - "description": "Ask the user a question and wait for their response before continuing. Use this when you need clarification or a choice from the user.", - "name": "ask_question", - "parameters": { - "additionalProperties": false, - "properties": { - "allowFreeform": { - "description": "Whether the user may answer with freeform text instead of selecting one of the provided options.", - "type": "boolean" - }, - "options": { - "description": "Selectable answer options to present to the user.", - "items": { - "additionalProperties": false, - "properties": { - "description": { - "description": "Optional additional context for this option.", - "type": "string" - }, - "id": { - "description": "Stable identifier for the option.", - "type": "string" - }, - "label": { - "description": "User-facing label for the option.", - "type": "string" - }, - "style": { - "description": "Visual treatment hint for the option.", - "enum": ["primary", "danger", "default"], - "type": "string" - } - }, - "required": ["id", "label"], - "type": "object" - }, - "type": "array" - }, - "prompt": { - "description": "The prompt to present to the user.", - "type": "string" - } - }, - "required": ["prompt"], - "type": "object" - } - }, - "type": "function" - }, - { - "function": { - "description": "Execute a shell command in the shared workspace environment.", - "name": "bash", - "parameters": { - "additionalProperties": false, - "properties": { - "command": { - "description": "The shell command to execute.", - "type": "string" - } - }, - "required": ["command"], - "type": "object" - } - }, - "type": "function" - }, - { - "function": { - "description": "Fast file pattern matching tool that works with any codebase size.\n\nUsage:\n- Supports glob patterns like \"**/*.js\" or \"src/**/*.ts\".\n- Returns matching file paths.\n- Use this tool when you need to find files by name patterns.\n- If you are unsure of the correct file path, use the glob tool to look up filenames by glob pattern.\n- Use the grep tool instead if you need to search file contents.\n- Call this tool in parallel when you know there are multiple patterns to search for.", - "name": "glob", - "parameters": { - "additionalProperties": false, - "properties": { - "limit": { - "description": "Maximum number of results to return. Defaults to 100.", - "maximum": 1000, - "minimum": 1, - "type": "integer" - }, - "path": { - "description": "The directory to search in. Defaults to /workspace. Must be an absolute path. Omit to use the default.", - "type": "string" - }, - "pattern": { - "description": "The glob pattern to match files against (e.g. \"**/*.ts\", \"src/**/*.js\").", - "type": "string" - } - }, - "required": ["pattern"], - "type": "object" - } - }, - "type": "function" - }, - { - "function": { - "description": "Fast content search tool that works with any codebase size.\n\nUsage:\n- Searches file contents using regular expressions.\n- Supports full regex syntax (e.g. \"log.*Error\", \"function\\s+\\w+\").\n- Filter files by pattern with the glob parameter (e.g. \"*.js\", \"*.{ts,tsx}\").\n- Returns matching lines with file paths and line numbers.\n- Use this tool when you need to find files containing specific patterns.\n- Use the glob tool instead if you only need to find files by name.\n- Call this tool in parallel when you have multiple independent searches.\n- Any line longer than 2000 characters is truncated.", - "name": "grep", - "parameters": { - "additionalProperties": false, - "properties": { - "context": { - "description": "Number of surrounding context lines to include before and after each match. Defaults to 0.", - "minimum": 0, - "type": "integer" - }, - "glob": { - "description": "Filter files by glob pattern (e.g. \"*.ts\", \"*.{ts,tsx}\").", - "type": "string" - }, - "ignoreCase": { - "description": "Perform case-insensitive search. Defaults to false.", - "type": "boolean" - }, - "limit": { - "description": "Maximum number of matches to return per file. Defaults to 100.", - "maximum": 1000, - "minimum": 1, - "type": "integer" - }, - "literal": { - "description": "Treat the pattern as a literal string instead of a regular expression. Defaults to false.", - "type": "boolean" - }, - "path": { - "description": "The directory or file to search in. Defaults to /workspace. Must be an absolute path. Omit to use the default.", - "type": "string" - }, - "pattern": { - "description": "The regex pattern to search for in file contents (e.g. \"log.*Error\", \"function\\s+\\w+\").", - "type": "string" - } - }, - "required": ["pattern"], - "type": "object" - } - }, - "type": "function" - }, - { - "function": { - "description": "Read a file from the local filesystem. If the path does not exist, an error is returned.\n\nUsage:\n- The filePath parameter should be an absolute path.\n- By default, this tool returns up to 2000 lines from the start of the file.\n- The offset parameter is the line number to start from (1-indexed).\n- To read later sections, call this tool again with a larger offset.\n- Use the grep tool to find specific content in large files or files with long lines.\n- If you are unsure of the correct file path, use the glob tool to look up filenames by glob pattern.\n- Contents are returned with each line prefixed by its line number as `: `. For example, if a file has contents \"foo\\n\", you will receive \"1: foo\\n\".\n- Any line longer than 2000 characters is truncated.\n- Call this tool in parallel when you know there are multiple files you want to read.\n- Avoid tiny repeated slices (30 line chunks). If you need more context, read a larger window.", - "name": "read_file", - "parameters": { - "additionalProperties": false, - "properties": { - "filePath": { - "description": "The absolute path to the file to read.", - "type": "string" - }, - "limit": { - "description": "Maximum number of lines to return. Defaults to 2000.", - "minimum": 1, - "type": "integer" - }, - "offset": { - "description": "1-based line number to start from. Defaults to 1.", - "minimum": 1, - "type": "integer" - } - }, - "required": ["filePath"], - "type": "object" - } - }, - "type": "function" - }, - { - "function": { - "description": "Writes a file to the local filesystem.\n\nUsage:\n- This tool will overwrite the existing file if there is one at the provided path.\n- If this is an existing file, you MUST use the read_file tool first to read the file's contents. This tool will fail if you did not read the file first.\n- ALWAYS prefer editing existing files in the codebase. NEVER write new files unless explicitly required.\n- NEVER proactively create documentation files (*.md) or README files. Only create documentation files if explicitly requested by the User.\n- Only use emojis if the user explicitly requests it. Avoid writing emojis to files unless asked.", - "name": "write_file", - "parameters": { - "additionalProperties": false, - "properties": { - "content": { - "description": "Complete replacement file contents.", - "type": "string" - }, - "filePath": { - "description": "The absolute path to the file to write (must be absolute, not relative).", - "type": "string" - } - }, - "required": ["filePath", "content"], - "type": "object" - } - }, - "type": "function" - }, - { - "function": { - "description": "Use this tool to create and manage a structured task list for the current session.\nThis helps you track progress, organize complex tasks, and demonstrate thoroughness.\n\nWhen to use:\n- Complex multistep tasks requiring 3 or more distinct steps\n- When the user provides multiple tasks or a numbered list\n- After receiving new instructions, to capture requirements\n- After completing a task, to mark it complete and add follow-ups\n\nWhen NOT to use:\n- Single, straightforward tasks that need no tracking\n- Purely conversational or informational requests\n\nUsage:\n- Call with `todos` to replace the entire list (full replacement write)\n- Call without `todos` to read the current list\n- Both return the full current list with status counts\n- Mark tasks in_progress when you start, completed when done\n- Only have ONE task in_progress at a time", - "name": "todo", - "parameters": { - "additionalProperties": false, - "properties": { - "todos": { - "description": "The updated todo list. Omit to read the current list without modifying it.", - "items": { - "additionalProperties": false, - "properties": { - "content": { - "description": "Brief description of the task.", - "type": "string" - }, - "priority": { - "description": "Priority level of the task.", - "enum": ["high", "medium", "low"], - "type": "string" - }, - "status": { - "description": "Current status of the task.", - "enum": [ - "pending", - "in_progress", - "completed", - "cancelled" - ], - "type": "string" - } - }, - "required": ["content", "status", "priority"], - "type": "object" - }, - "type": "array" - } - }, - "type": "object" - } - }, - "type": "function" - }, - { - "function": { - "description": "Fetch a webpage and return its content in the requested format. Use this to retrieve and analyze content from URLs.\n\nUsage notes:\n- The URL must be a fully-formed valid URL starting with http:// or https://\n- HTML responses are automatically converted to markdown or plain text based on the requested format\n- Format options: \"markdown\" (default), \"text\", or \"html\"\n- Default timeout is 30 seconds (max 120 seconds)\n- Maximum response size is 5 MB; content is further capped at the shared tool-output budget (50 KB / 2000 lines)\n- This tool is read-only and does not modify any files", - "name": "web_fetch", - "parameters": { - "additionalProperties": false, - "properties": { - "format": { - "description": "The format to return the content in (text, markdown, or html). HTML responses are automatically converted to the requested format. Defaults to \"markdown\".", - "enum": ["markdown", "text", "html"], - "type": "string" - }, - "timeout": { - "description": "Optional timeout in seconds. Defaults to 30, max 120.", - "type": "number" - }, - "url": { - "description": "The fully-formed URL to fetch content from. Must start with http:// or https://.", - "type": "string" - } - }, - "required": ["url"], - "type": "object" - } - }, - "type": "function" - }, - { - "function": { - "description": "Load the full instructions for one available skill by name or id. Use this tool when the request clearly matches a listed skill description or when the user explicitly asks for that skill. This is not for MCP connections; use connection_search to access an installed connection. Loading adds the skill instructions to the current turn. Choose the \"skill\" value from the Available skills block.", - "name": "load_skill", - "parameters": { - "additionalProperties": false, - "properties": { - "skill": { - "description": "Available skill name or id.", - "type": "string" - } - }, - "required": ["skill"], - "type": "object" - } - }, - "type": "function" - }, - { - "function": { - "description": "Reads a deterministic Eve documentation page fixture.", - "name": "read", - "parameters": { - "$schema": "http://json-schema.org/draft-07/schema#", - "additionalProperties": false, - "properties": { - "url": { - "type": "string" - } - }, - "required": ["url"], - "type": "object" - } - }, - "type": "function" - }, - { - "function": { - "description": "Searches the Eve instrumentation documentation fixture.", - "name": "search", - "parameters": { - "$schema": "http://json-schema.org/draft-07/schema#", - "additionalProperties": false, - "properties": { - "query": { - "type": "string" - } - }, - "required": ["query"], - "type": "object" - } - }, - "type": "function" - }, - { - "function": { - "description": "Research the Eve instrumentation documentation before the parent reads it.", - "name": "researcher", - "parameters": { - "additionalProperties": false, - "properties": { - "message": { - "description": "The message to send to the subagent. Provide all context the subagent needs to complete the task; the subagent does not see the parent's history.", - "type": "string" - }, - "outputSchema": { - "description": "When provided, the subagent runs in task mode and must produce structured output matching this JSON Schema. The structured output becomes the tool result.", - "type": "object" - } - }, - "required": ["message"], - "type": "object" - } - }, - "type": "function" - }, - { - "function": { - "description": "Delegate a focused subtask to a fresh copy of yourself. Use it to isolate complex work or split a large task into independent pieces. Issue multiple `agent` calls in one response to run a small fixed set in parallel. Each child has fresh history and state but shares your tools and sandbox, so include essential context in `message` and give parallel writers non-overlapping scopes.", - "name": "agent", - "parameters": { - "additionalProperties": false, - "properties": { - "message": { - "description": "The message to send to the subagent. Provide all context the subagent needs to complete the task; the subagent does not see the parent's history.", - "type": "string" - }, - "outputSchema": { - "description": "When provided, the subagent runs in task mode and must produce structured output matching this JSON Schema. The structured output becomes the tool result.", - "type": "object" - } - }, - "required": ["message"], - "type": "object" - } - }, - "type": "function" - } - ] - } - }, - "headers": {}, - "method": "POST", - "url": "https://openrouter.ai/api/v1/chat/completions" - }, - "response": { - "body": { - "contentType": "text/event-stream", - "kind": "binary", - "path": "eve-v0.cassette.blobs/216c5da47f83c7e5d8886e8fef437a1dd9abee6429bbc1dfede68b9216783bb5.bin", - "sha256": "216c5da47f83c7e5d8886e8fef437a1dd9abee6429bbc1dfede68b9216783bb5" - }, - "headers": { - "access-control-allow-origin": "*", - "access-control-expose-headers": "X-Generation-Id,X-Provider-Name,request-id,cf-ray", - "cache-control": "no-cache", - "cf-ray": "a45d8e38cd893845-YYZ", - "content-type": "text/event-stream", - "date": "Mon, 05 Oct 2026 15:45:49 GMT", - "permissions-policy": "payment=(self \"https://checkout.stripe.com\" \"https://connect-js.stripe.com\" \"https://js.stripe.com\" \"https://*.js.stripe.com\" \"https://hooks.stripe.com\")", - "referrer-policy": "no-referrer, strict-origin-when-cross-origin", - "server": "cloudflare", - "set-cookie": "[REDACTED]", - "x-content-type-options": "nosniff", - "x-generation-id": "gen-1791215148-PVpnAFb30EqLOaA4TUuP" - }, - "status": 200 - } - }, - { - "callIndex": 1, - "id": "1e16aa22e1861e03", - "matchKey": "POST openrouter.ai/api/v1/chat/completions", - "recordedAt": "2026-10-05T15:45:57.259Z", - "request": { - "body": { - "kind": "json", - "value": { - "messages": [ - { - "content": [ - { - "text": "Instructions (instructions)\nYou are the deterministic researcher subagent for Braintrust Eve instrumentation tests.\n\nFor every task:\n\n1. Call the search tool exactly once with the full task as the query.\n2. After the search result is available, answer with a single sentence that starts\n with \"Researcher result:\" and includes the search title and URL.\n\nTool execution\nA single tool or subagent call runs as one serial action. If you call multiple independent tools or subagents in one response, eve treats that batch as parallel work. Only batch work that is independent and does not rely on another call in the same response.", - "type": "text" - } - ], - "role": "system" - }, - { - "content": "You are the subagent \"researcher\".\nDescription: Research the Eve instrumentation documentation before the parent reads it.\n\nThe caller delegated the following task to you. Complete it and return the final result directly.\n\nCaller message:\nRun the Braintrust Eve instrumentation e2e scenario", - "role": "user" - } - ], - "model": "qwen/qwen3-30b-a3b", - "provider": { - "only": ["deepinfra"], - "require_parameters": true - }, - "reasoning": { - "effort": "low" - }, - "stream": true, - "tool_choice": "auto", - "tools": [ - { - "function": { - "description": "Ask the user a question and wait for their response before continuing. Use this when you need clarification or a choice from the user.", - "name": "ask_question", - "parameters": { - "additionalProperties": false, - "properties": { - "allowFreeform": { - "description": "Whether the user may answer with freeform text instead of selecting one of the provided options.", - "type": "boolean" - }, - "options": { - "description": "Selectable answer options to present to the user.", - "items": { - "additionalProperties": false, - "properties": { - "description": { - "description": "Optional additional context for this option.", - "type": "string" - }, - "id": { - "description": "Stable identifier for the option.", - "type": "string" - }, - "label": { - "description": "User-facing label for the option.", - "type": "string" - }, - "style": { - "description": "Visual treatment hint for the option.", - "enum": ["primary", "danger", "default"], - "type": "string" - } - }, - "required": ["id", "label"], - "type": "object" - }, - "type": "array" - }, - "prompt": { - "description": "The prompt to present to the user.", - "type": "string" - } - }, - "required": ["prompt"], - "type": "object" - } - }, - "type": "function" - }, - { - "function": { - "description": "Execute a shell command in the shared workspace environment.", - "name": "bash", - "parameters": { - "additionalProperties": false, - "properties": { - "command": { - "description": "The shell command to execute.", - "type": "string" - } - }, - "required": ["command"], - "type": "object" - } - }, - "type": "function" - }, - { - "function": { - "description": "Fast file pattern matching tool that works with any codebase size.\n\nUsage:\n- Supports glob patterns like \"**/*.js\" or \"src/**/*.ts\".\n- Returns matching file paths.\n- Use this tool when you need to find files by name patterns.\n- If you are unsure of the correct file path, use the glob tool to look up filenames by glob pattern.\n- Use the grep tool instead if you need to search file contents.\n- Call this tool in parallel when you know there are multiple patterns to search for.", - "name": "glob", - "parameters": { - "additionalProperties": false, - "properties": { - "limit": { - "description": "Maximum number of results to return. Defaults to 100.", - "maximum": 1000, - "minimum": 1, - "type": "integer" - }, - "path": { - "description": "The directory to search in. Defaults to /workspace. Must be an absolute path. Omit to use the default.", - "type": "string" - }, - "pattern": { - "description": "The glob pattern to match files against (e.g. \"**/*.ts\", \"src/**/*.js\").", - "type": "string" - } - }, - "required": ["pattern"], - "type": "object" - } - }, - "type": "function" - }, - { - "function": { - "description": "Fast content search tool that works with any codebase size.\n\nUsage:\n- Searches file contents using regular expressions.\n- Supports full regex syntax (e.g. \"log.*Error\", \"function\\s+\\w+\").\n- Filter files by pattern with the glob parameter (e.g. \"*.js\", \"*.{ts,tsx}\").\n- Returns matching lines with file paths and line numbers.\n- Use this tool when you need to find files containing specific patterns.\n- Use the glob tool instead if you only need to find files by name.\n- Call this tool in parallel when you have multiple independent searches.\n- Any line longer than 2000 characters is truncated.", - "name": "grep", - "parameters": { - "additionalProperties": false, - "properties": { - "context": { - "description": "Number of surrounding context lines to include before and after each match. Defaults to 0.", - "minimum": 0, - "type": "integer" - }, - "glob": { - "description": "Filter files by glob pattern (e.g. \"*.ts\", \"*.{ts,tsx}\").", - "type": "string" - }, - "ignoreCase": { - "description": "Perform case-insensitive search. Defaults to false.", - "type": "boolean" - }, - "limit": { - "description": "Maximum number of matches to return per file. Defaults to 100.", - "maximum": 1000, - "minimum": 1, - "type": "integer" - }, - "literal": { - "description": "Treat the pattern as a literal string instead of a regular expression. Defaults to false.", - "type": "boolean" - }, - "path": { - "description": "The directory or file to search in. Defaults to /workspace. Must be an absolute path. Omit to use the default.", - "type": "string" - }, - "pattern": { - "description": "The regex pattern to search for in file contents (e.g. \"log.*Error\", \"function\\s+\\w+\").", - "type": "string" - } - }, - "required": ["pattern"], - "type": "object" - } - }, - "type": "function" - }, - { - "function": { - "description": "Read a file from the local filesystem. If the path does not exist, an error is returned.\n\nUsage:\n- The filePath parameter should be an absolute path.\n- By default, this tool returns up to 2000 lines from the start of the file.\n- The offset parameter is the line number to start from (1-indexed).\n- To read later sections, call this tool again with a larger offset.\n- Use the grep tool to find specific content in large files or files with long lines.\n- If you are unsure of the correct file path, use the glob tool to look up filenames by glob pattern.\n- Contents are returned with each line prefixed by its line number as `: `. For example, if a file has contents \"foo\\n\", you will receive \"1: foo\\n\".\n- Any line longer than 2000 characters is truncated.\n- Call this tool in parallel when you know there are multiple files you want to read.\n- Avoid tiny repeated slices (30 line chunks). If you need more context, read a larger window.", - "name": "read_file", - "parameters": { - "additionalProperties": false, - "properties": { - "filePath": { - "description": "The absolute path to the file to read.", - "type": "string" - }, - "limit": { - "description": "Maximum number of lines to return. Defaults to 2000.", - "minimum": 1, - "type": "integer" - }, - "offset": { - "description": "1-based line number to start from. Defaults to 1.", - "minimum": 1, - "type": "integer" - } - }, - "required": ["filePath"], - "type": "object" - } - }, - "type": "function" - }, - { - "function": { - "description": "Writes a file to the local filesystem.\n\nUsage:\n- This tool will overwrite the existing file if there is one at the provided path.\n- If this is an existing file, you MUST use the read_file tool first to read the file's contents. This tool will fail if you did not read the file first.\n- ALWAYS prefer editing existing files in the codebase. NEVER write new files unless explicitly required.\n- NEVER proactively create documentation files (*.md) or README files. Only create documentation files if explicitly requested by the User.\n- Only use emojis if the user explicitly requests it. Avoid writing emojis to files unless asked.", - "name": "write_file", - "parameters": { - "additionalProperties": false, - "properties": { - "content": { - "description": "Complete replacement file contents.", - "type": "string" - }, - "filePath": { - "description": "The absolute path to the file to write (must be absolute, not relative).", - "type": "string" - } - }, - "required": ["filePath", "content"], - "type": "object" - } - }, - "type": "function" - }, - { - "function": { - "description": "Use this tool to create and manage a structured task list for the current session.\nThis helps you track progress, organize complex tasks, and demonstrate thoroughness.\n\nWhen to use:\n- Complex multistep tasks requiring 3 or more distinct steps\n- When the user provides multiple tasks or a numbered list\n- After receiving new instructions, to capture requirements\n- After completing a task, to mark it complete and add follow-ups\n\nWhen NOT to use:\n- Single, straightforward tasks that need no tracking\n- Purely conversational or informational requests\n\nUsage:\n- Call with `todos` to replace the entire list (full replacement write)\n- Call without `todos` to read the current list\n- Both return the full current list with status counts\n- Mark tasks in_progress when you start, completed when done\n- Only have ONE task in_progress at a time", - "name": "todo", - "parameters": { - "additionalProperties": false, - "properties": { - "todos": { - "description": "The updated todo list. Omit to read the current list without modifying it.", - "items": { - "additionalProperties": false, - "properties": { - "content": { - "description": "Brief description of the task.", - "type": "string" - }, - "priority": { - "description": "Priority level of the task.", - "enum": ["high", "medium", "low"], - "type": "string" - }, - "status": { - "description": "Current status of the task.", - "enum": [ - "pending", - "in_progress", - "completed", - "cancelled" - ], - "type": "string" - } - }, - "required": ["content", "status", "priority"], - "type": "object" - }, - "type": "array" - } - }, - "type": "object" - } - }, - "type": "function" - }, - { - "function": { - "description": "Fetch a webpage and return its content in the requested format. Use this to retrieve and analyze content from URLs.\n\nUsage notes:\n- The URL must be a fully-formed valid URL starting with http:// or https://\n- HTML responses are automatically converted to markdown or plain text based on the requested format\n- Format options: \"markdown\" (default), \"text\", or \"html\"\n- Default timeout is 30 seconds (max 120 seconds)\n- Maximum response size is 5 MB; content is further capped at the shared tool-output budget (50 KB / 2000 lines)\n- This tool is read-only and does not modify any files", - "name": "web_fetch", - "parameters": { - "additionalProperties": false, - "properties": { - "format": { - "description": "The format to return the content in (text, markdown, or html). HTML responses are automatically converted to the requested format. Defaults to \"markdown\".", - "enum": ["markdown", "text", "html"], - "type": "string" - }, - "timeout": { - "description": "Optional timeout in seconds. Defaults to 30, max 120.", - "type": "number" - }, - "url": { - "description": "The fully-formed URL to fetch content from. Must start with http:// or https://.", - "type": "string" - } - }, - "required": ["url"], - "type": "object" - } - }, - "type": "function" - }, - { - "function": { - "description": "Load the full instructions for one available skill by name or id. Use this tool when the request clearly matches a listed skill description or when the user explicitly asks for that skill. This is not for MCP connections; use connection_search to access an installed connection. Loading adds the skill instructions to the current turn. Choose the \"skill\" value from the Available skills block.", - "name": "load_skill", - "parameters": { - "additionalProperties": false, - "properties": { - "skill": { - "description": "Available skill name or id.", - "type": "string" - } - }, - "required": ["skill"], - "type": "object" - } - }, - "type": "function" - }, - { - "function": { - "description": "Searches the Eve instrumentation documentation fixture.", - "name": "search", - "parameters": { - "$schema": "http://json-schema.org/draft-07/schema#", - "additionalProperties": false, - "properties": { - "query": { - "type": "string" - } - }, - "required": ["query"], - "type": "object" - } - }, - "type": "function" - }, - { - "function": { - "description": "Delegate a focused subtask to a fresh copy of yourself. Use it to isolate complex work or split a large task into independent pieces. Issue multiple `agent` calls in one response to run a small fixed set in parallel. Each child has fresh history and state but shares your tools and sandbox, so include essential context in `message` and give parallel writers non-overlapping scopes.", - "name": "agent", - "parameters": { - "additionalProperties": false, - "properties": { - "message": { - "description": "The message to send to the subagent. Provide all context the subagent needs to complete the task; the subagent does not see the parent's history.", - "type": "string" - }, - "outputSchema": { - "description": "When provided, the subagent runs in task mode and must produce structured output matching this JSON Schema. The structured output becomes the tool result.", - "type": "object" - } - }, - "required": ["message"], - "type": "object" - } - }, - "type": "function" - } - ] - } - }, - "headers": {}, - "method": "POST", - "url": "https://openrouter.ai/api/v1/chat/completions" - }, - "response": { - "body": { - "contentType": "text/event-stream", - "kind": "binary", - "path": "eve-v0.cassette.blobs/e6b53b7fd5bdbe0943d14a90a26316bfda01300608004216744d9a4305e0ac30.bin", - "sha256": "e6b53b7fd5bdbe0943d14a90a26316bfda01300608004216744d9a4305e0ac30" - }, - "headers": { - "access-control-allow-origin": "*", - "access-control-expose-headers": "X-Generation-Id,X-Provider-Name,request-id,cf-ray", - "cache-control": "no-cache", - "cf-ray": "a45d8e614b193845-YYZ", - "content-type": "text/event-stream", - "date": "Mon, 05 Oct 2026 15:45:55 GMT", - "permissions-policy": "payment=(self \"https://checkout.stripe.com\" \"https://connect-js.stripe.com\" \"https://js.stripe.com\" \"https://*.js.stripe.com\" \"https://hooks.stripe.com\")", - "referrer-policy": "no-referrer, strict-origin-when-cross-origin", - "server": "cloudflare", - "set-cookie": "[REDACTED]", - "x-content-type-options": "nosniff", - "x-generation-id": "gen-1791215155-kz4x2hrzNK1bBE4DGQEF" - }, - "status": 200 - } - }, - { - "callIndex": 2, - "id": "077f61cdd6b7fca8", - "matchKey": "POST openrouter.ai/api/v1/chat/completions", - "recordedAt": "2026-10-05T15:46:02.031Z", - "request": { - "body": { - "kind": "json", - "value": { - "messages": [ - { - "content": [ - { - "text": "Instructions (instructions)\nYou are the deterministic researcher subagent for Braintrust Eve instrumentation tests.\n\nFor every task:\n\n1. Call the search tool exactly once with the full task as the query.\n2. After the search result is available, answer with a single sentence that starts\n with \"Researcher result:\" and includes the search title and URL.\n\nTool execution\nA single tool or subagent call runs as one serial action. If you call multiple independent tools or subagents in one response, eve treats that batch as parallel work. Only batch work that is independent and does not rely on another call in the same response.", - "type": "text" - } - ], - "role": "system" - }, - { - "content": "You are the subagent \"researcher\".\nDescription: Research the Eve instrumentation documentation before the parent reads it.\n\nThe caller delegated the following task to you. Complete it and return the final result directly.\n\nCaller message:\nRun the Braintrust Eve instrumentation e2e scenario", - "role": "user" - }, - { - "content": null, - "reasoning": "\nOkay, the user wants me to act as the researcher subagent for Braintrust Eve instrumentation tests. The task is to research the Eve instrumentation documentation before the parent reads it. The caller's message is to \"Run the Braintrust Eve instrumentation e2e scenario.\" \n\nFirst, I need to figure out what steps are required here. Since the user mentioned running an end-to-end scenario, I should look into the documentation to find relevant information. The available tools include a search function that queries the Eve instrumentation documentation. \n\nThe instructions say to call the search tool exactly once with the full task as the query. So, the first step is to use the search function with the query \"Run the Braintrust Eve instrumentation e2e scenario.\" That should retrieve the necessary documentation.\n\nAfter getting the search results, I need to provide a single-sentence answer starting with \"Researcher result:\" including the search title and URL. But since I don't have real-time data, I'll simulate the response based on typical documentation structures. \n\nI should check if there are any other tools needed, but according to the task, after the search, the answer is straightforward. No other functions like grep or read_file seem necessary here. The main action is the search, then presenting the result.\n", - "reasoning_details": [ - { - "format": "unknown", - "index": 0, - "text": "\nOkay, the user wants me to act as the researcher subagent for Braintrust Eve instrumentation tests. The task is to research the Eve instrumentation documentation before the parent reads it. The caller's message is to \"Run the Braintrust Eve instrumentation e2e scenario.\" \n\nFirst, I need to figure out what steps are required here. Since the user mentioned running an end-to-end scenario, I should look into the documentation to find relevant information. The available tools include a search function that queries the Eve instrumentation documentation. \n\nThe instructions say to call the search tool exactly once with the full task as the query. So, the first step is to use the search function with the query \"Run the Braintrust Eve instrumentation e2e scenario.\" That should retrieve the necessary documentation.\n\nAfter getting the search results, I need to provide a single-sentence answer starting with \"Researcher result:\" including the search title and URL. But since I don't have real-time data, I'll simulate the response based on typical documentation structures. \n\nI should check if there are any other tools needed, but according to the task, after the search, the answer is straightforward. No other functions like grep or read_file seem necessary here. The main action is the search, then presenting the result.\n", - "type": "reasoning.text" - } - ], - "role": "assistant", - "tool_calls": [ - { - "function": { - "arguments": "{\"query\":\"Run the Braintrust Eve instrumentation e2e scenario\"}", - "name": "search" - }, - "id": "call_cb73", - "type": "function" - } - ] - }, - { - "content": "{\"query\":\"Run the Braintrust Eve instrumentation e2e scenario\",\"title\":\"Eve instrumentation\",\"url\":\"https://eve.dev/docs/guides/instrumentation\"}", - "name": "search", - "role": "tool", - "tool_call_id": "call_cb73" - } - ], - "model": "qwen/qwen3-30b-a3b", - "provider": { - "only": ["deepinfra"], - "require_parameters": true - }, - "reasoning": { - "effort": "low" - }, - "stream": true, - "tool_choice": "auto", - "tools": [ - { - "function": { - "description": "Ask the user a question and wait for their response before continuing. Use this when you need clarification or a choice from the user.", - "name": "ask_question", - "parameters": { - "additionalProperties": false, - "properties": { - "allowFreeform": { - "description": "Whether the user may answer with freeform text instead of selecting one of the provided options.", - "type": "boolean" - }, - "options": { - "description": "Selectable answer options to present to the user.", - "items": { - "additionalProperties": false, - "properties": { - "description": { - "description": "Optional additional context for this option.", - "type": "string" - }, - "id": { - "description": "Stable identifier for the option.", - "type": "string" - }, - "label": { - "description": "User-facing label for the option.", - "type": "string" - }, - "style": { - "description": "Visual treatment hint for the option.", - "enum": ["primary", "danger", "default"], - "type": "string" - } - }, - "required": ["id", "label"], - "type": "object" - }, - "type": "array" - }, - "prompt": { - "description": "The prompt to present to the user.", - "type": "string" - } - }, - "required": ["prompt"], - "type": "object" - } - }, - "type": "function" - }, - { - "function": { - "description": "Execute a shell command in the shared workspace environment.", - "name": "bash", - "parameters": { - "additionalProperties": false, - "properties": { - "command": { - "description": "The shell command to execute.", - "type": "string" - } - }, - "required": ["command"], - "type": "object" - } - }, - "type": "function" - }, - { - "function": { - "description": "Fast file pattern matching tool that works with any codebase size.\n\nUsage:\n- Supports glob patterns like \"**/*.js\" or \"src/**/*.ts\".\n- Returns matching file paths.\n- Use this tool when you need to find files by name patterns.\n- If you are unsure of the correct file path, use the glob tool to look up filenames by glob pattern.\n- Use the grep tool instead if you need to search file contents.\n- Call this tool in parallel when you know there are multiple patterns to search for.", - "name": "glob", - "parameters": { - "additionalProperties": false, - "properties": { - "limit": { - "description": "Maximum number of results to return. Defaults to 100.", - "maximum": 1000, - "minimum": 1, - "type": "integer" - }, - "path": { - "description": "The directory to search in. Defaults to /workspace. Must be an absolute path. Omit to use the default.", - "type": "string" - }, - "pattern": { - "description": "The glob pattern to match files against (e.g. \"**/*.ts\", \"src/**/*.js\").", - "type": "string" - } - }, - "required": ["pattern"], - "type": "object" - } - }, - "type": "function" - }, - { - "function": { - "description": "Fast content search tool that works with any codebase size.\n\nUsage:\n- Searches file contents using regular expressions.\n- Supports full regex syntax (e.g. \"log.*Error\", \"function\\s+\\w+\").\n- Filter files by pattern with the glob parameter (e.g. \"*.js\", \"*.{ts,tsx}\").\n- Returns matching lines with file paths and line numbers.\n- Use this tool when you need to find files containing specific patterns.\n- Use the glob tool instead if you only need to find files by name.\n- Call this tool in parallel when you have multiple independent searches.\n- Any line longer than 2000 characters is truncated.", - "name": "grep", - "parameters": { - "additionalProperties": false, - "properties": { - "context": { - "description": "Number of surrounding context lines to include before and after each match. Defaults to 0.", - "minimum": 0, - "type": "integer" - }, - "glob": { - "description": "Filter files by glob pattern (e.g. \"*.ts\", \"*.{ts,tsx}\").", - "type": "string" - }, - "ignoreCase": { - "description": "Perform case-insensitive search. Defaults to false.", - "type": "boolean" - }, - "limit": { - "description": "Maximum number of matches to return per file. Defaults to 100.", - "maximum": 1000, - "minimum": 1, - "type": "integer" - }, - "literal": { - "description": "Treat the pattern as a literal string instead of a regular expression. Defaults to false.", - "type": "boolean" - }, - "path": { - "description": "The directory or file to search in. Defaults to /workspace. Must be an absolute path. Omit to use the default.", - "type": "string" - }, - "pattern": { - "description": "The regex pattern to search for in file contents (e.g. \"log.*Error\", \"function\\s+\\w+\").", - "type": "string" - } - }, - "required": ["pattern"], - "type": "object" - } - }, - "type": "function" - }, - { - "function": { - "description": "Read a file from the local filesystem. If the path does not exist, an error is returned.\n\nUsage:\n- The filePath parameter should be an absolute path.\n- By default, this tool returns up to 2000 lines from the start of the file.\n- The offset parameter is the line number to start from (1-indexed).\n- To read later sections, call this tool again with a larger offset.\n- Use the grep tool to find specific content in large files or files with long lines.\n- If you are unsure of the correct file path, use the glob tool to look up filenames by glob pattern.\n- Contents are returned with each line prefixed by its line number as `: `. For example, if a file has contents \"foo\\n\", you will receive \"1: foo\\n\".\n- Any line longer than 2000 characters is truncated.\n- Call this tool in parallel when you know there are multiple files you want to read.\n- Avoid tiny repeated slices (30 line chunks). If you need more context, read a larger window.", - "name": "read_file", - "parameters": { - "additionalProperties": false, - "properties": { - "filePath": { - "description": "The absolute path to the file to read.", - "type": "string" - }, - "limit": { - "description": "Maximum number of lines to return. Defaults to 2000.", - "minimum": 1, - "type": "integer" - }, - "offset": { - "description": "1-based line number to start from. Defaults to 1.", - "minimum": 1, - "type": "integer" - } - }, - "required": ["filePath"], - "type": "object" - } - }, - "type": "function" - }, - { - "function": { - "description": "Writes a file to the local filesystem.\n\nUsage:\n- This tool will overwrite the existing file if there is one at the provided path.\n- If this is an existing file, you MUST use the read_file tool first to read the file's contents. This tool will fail if you did not read the file first.\n- ALWAYS prefer editing existing files in the codebase. NEVER write new files unless explicitly required.\n- NEVER proactively create documentation files (*.md) or README files. Only create documentation files if explicitly requested by the User.\n- Only use emojis if the user explicitly requests it. Avoid writing emojis to files unless asked.", - "name": "write_file", - "parameters": { - "additionalProperties": false, - "properties": { - "content": { - "description": "Complete replacement file contents.", - "type": "string" - }, - "filePath": { - "description": "The absolute path to the file to write (must be absolute, not relative).", - "type": "string" - } - }, - "required": ["filePath", "content"], - "type": "object" - } - }, - "type": "function" - }, - { - "function": { - "description": "Use this tool to create and manage a structured task list for the current session.\nThis helps you track progress, organize complex tasks, and demonstrate thoroughness.\n\nWhen to use:\n- Complex multistep tasks requiring 3 or more distinct steps\n- When the user provides multiple tasks or a numbered list\n- After receiving new instructions, to capture requirements\n- After completing a task, to mark it complete and add follow-ups\n\nWhen NOT to use:\n- Single, straightforward tasks that need no tracking\n- Purely conversational or informational requests\n\nUsage:\n- Call with `todos` to replace the entire list (full replacement write)\n- Call without `todos` to read the current list\n- Both return the full current list with status counts\n- Mark tasks in_progress when you start, completed when done\n- Only have ONE task in_progress at a time", - "name": "todo", - "parameters": { - "additionalProperties": false, - "properties": { - "todos": { - "description": "The updated todo list. Omit to read the current list without modifying it.", - "items": { - "additionalProperties": false, - "properties": { - "content": { - "description": "Brief description of the task.", - "type": "string" - }, - "priority": { - "description": "Priority level of the task.", - "enum": ["high", "medium", "low"], - "type": "string" - }, - "status": { - "description": "Current status of the task.", - "enum": [ - "pending", - "in_progress", - "completed", - "cancelled" - ], - "type": "string" - } - }, - "required": ["content", "status", "priority"], - "type": "object" - }, - "type": "array" - } - }, - "type": "object" - } - }, - "type": "function" - }, - { - "function": { - "description": "Fetch a webpage and return its content in the requested format. Use this to retrieve and analyze content from URLs.\n\nUsage notes:\n- The URL must be a fully-formed valid URL starting with http:// or https://\n- HTML responses are automatically converted to markdown or plain text based on the requested format\n- Format options: \"markdown\" (default), \"text\", or \"html\"\n- Default timeout is 30 seconds (max 120 seconds)\n- Maximum response size is 5 MB; content is further capped at the shared tool-output budget (50 KB / 2000 lines)\n- This tool is read-only and does not modify any files", - "name": "web_fetch", - "parameters": { - "additionalProperties": false, - "properties": { - "format": { - "description": "The format to return the content in (text, markdown, or html). HTML responses are automatically converted to the requested format. Defaults to \"markdown\".", - "enum": ["markdown", "text", "html"], - "type": "string" - }, - "timeout": { - "description": "Optional timeout in seconds. Defaults to 30, max 120.", - "type": "number" - }, - "url": { - "description": "The fully-formed URL to fetch content from. Must start with http:// or https://.", - "type": "string" - } - }, - "required": ["url"], - "type": "object" - } - }, - "type": "function" - }, - { - "function": { - "description": "Load the full instructions for one available skill by name or id. Use this tool when the request clearly matches a listed skill description or when the user explicitly asks for that skill. This is not for MCP connections; use connection_search to access an installed connection. Loading adds the skill instructions to the current turn. Choose the \"skill\" value from the Available skills block.", - "name": "load_skill", - "parameters": { - "additionalProperties": false, - "properties": { - "skill": { - "description": "Available skill name or id.", - "type": "string" - } - }, - "required": ["skill"], - "type": "object" - } - }, - "type": "function" - }, - { - "function": { - "description": "Searches the Eve instrumentation documentation fixture.", - "name": "search", - "parameters": { - "$schema": "http://json-schema.org/draft-07/schema#", - "additionalProperties": false, - "properties": { - "query": { - "type": "string" - } - }, - "required": ["query"], - "type": "object" - } - }, - "type": "function" - }, - { - "function": { - "description": "Delegate a focused subtask to a fresh copy of yourself. Use it to isolate complex work or split a large task into independent pieces. Issue multiple `agent` calls in one response to run a small fixed set in parallel. Each child has fresh history and state but shares your tools and sandbox, so include essential context in `message` and give parallel writers non-overlapping scopes.", - "name": "agent", - "parameters": { - "additionalProperties": false, - "properties": { - "message": { - "description": "The message to send to the subagent. Provide all context the subagent needs to complete the task; the subagent does not see the parent's history.", - "type": "string" - }, - "outputSchema": { - "description": "When provided, the subagent runs in task mode and must produce structured output matching this JSON Schema. The structured output becomes the tool result.", - "type": "object" - } - }, - "required": ["message"], - "type": "object" - } - }, - "type": "function" - } - ] - } - }, - "headers": {}, - "method": "POST", - "url": "https://openrouter.ai/api/v1/chat/completions" - }, - "response": { - "body": { - "contentType": "text/event-stream", - "kind": "binary", - "path": "eve-v0.cassette.blobs/93d38f5860874f5d41365a1089fc6d39d15c1e647323424c090499d64c892b41.bin", - "sha256": "93d38f5860874f5d41365a1089fc6d39d15c1e647323424c090499d64c892b41" - }, - "headers": { - "access-control-allow-origin": "*", - "access-control-expose-headers": "X-Generation-Id,X-Provider-Name,request-id,cf-ray", - "cache-control": "no-cache", - "cf-ray": "a45d8e8258083845-YYZ", - "content-type": "text/event-stream", - "date": "Mon, 05 Oct 2026 15:46:00 GMT", - "permissions-policy": "payment=(self \"https://checkout.stripe.com\" \"https://connect-js.stripe.com\" \"https://js.stripe.com\" \"https://*.js.stripe.com\" \"https://hooks.stripe.com\")", - "referrer-policy": "no-referrer, strict-origin-when-cross-origin", - "server": "cloudflare", - "set-cookie": "[REDACTED]", - "x-content-type-options": "nosniff", - "x-generation-id": "gen-1791215160-CvDN4CyWOR2sGTk9s3co" - }, - "status": 200 - } - }, - { - "callIndex": 3, - "id": "2b4cbbc91c07030b", - "matchKey": "POST openrouter.ai/api/v1/chat/completions", - "recordedAt": "2026-10-05T15:46:06.099Z", - "request": { - "body": { - "kind": "json", - "value": { - "messages": [ - { - "content": [ - { - "text": "Instructions (instructions)\nYou are a deterministic fixture agent for Braintrust Eve instrumentation tests.\n\nFor every user task, follow these steps exactly:\n\n1. Your first response MUST contain exactly these two tool calls and no text:\n - researcher with only this argument: {\"message\":\"\"}.\n Copy the current user message verbatim, including words such as \"again\";\n never reuse a message from an earlier turn. Do not pass outputSchema.\n - read with only this argument:\n {\"url\":\"https://eve.dev/docs/guides/instrumentation\"}.\n Emit both calls simultaneously in the same response, with researcher first\n and read second, so Eve executes them as one parallel batch. The calls are\n independent. NEVER call one by itself, wait for its result, or emit either\n call in a separate response.\n2. If a tool result says \"Started task\", your next response MUST be exactly one\n task_wait call with no arguments and no text.\n3. Only after both results are available, answer with a single sentence that\n starts with \"Final answer from read:\" and includes the researcher result, read\n title, URL, and read excerpt.\n\nTool execution\nA single tool or subagent call runs as one serial action. If you call multiple independent tools or subagents in one response, eve treats that batch as parallel work. Only batch work that is independent and does not rely on another call in the same response.", - "type": "text" - } - ], - "role": "system" - }, - { - "content": "Run the Braintrust Eve instrumentation e2e scenario", - "role": "user" - }, - { - "content": null, - "reasoning": "\nOkay, the user wants me to run the Braintrust Eve instrumentation end-to-end scenario. Let me start by understanding what that entails. The user mentioned \"e2e scenario,\" which usually stands for end-to-end testing. So, they probably want to simulate or execute a complete workflow within the Eve instrumentation setup.\n\nFirst, I need to check the available tools. The user provided a list of functions, including researcher, read, and others. The instructions say that for every user task, I must first make two tool calls: researcher with the user's message and read with the specific URL. \n\nThe user's message is \"Run the Braintrust Eve instrumentation e2e scenario.\" So, I should call the researcher function with this message. Then, the read function should access the documentation URL provided earlier: https://eve.dev/docs/guides/instrumentation. \n\nI need to make sure both calls are made in the same response, as per the instructions. No text, just the two tool calls. Then, if the tool results say \"Started task,\" I have to call task_wait. But since the user hasn't provided any results yet, I should just proceed with the initial calls.\n\nAfter both results are available, I need to answer with a single sentence starting with \"Final answer from read:\" including the researcher result, read title, URL, and excerpt. But right now, the first step is to generate the two tool calls.\n", - "reasoning_details": [ - { - "format": "unknown", - "index": 0, - "text": "\nOkay, the user wants me to run the Braintrust Eve instrumentation end-to-end scenario. Let me start by understanding what that entails. The user mentioned \"e2e scenario,\" which usually stands for end-to-end testing. So, they probably want to simulate or execute a complete workflow within the Eve instrumentation setup.\n\nFirst, I need to check the available tools. The user provided a list of functions, including researcher, read, and others. The instructions say that for every user task, I must first make two tool calls: researcher with the user's message and read with the specific URL. \n\nThe user's message is \"Run the Braintrust Eve instrumentation e2e scenario.\" So, I should call the researcher function with this message. Then, the read function should access the documentation URL provided earlier: https://eve.dev/docs/guides/instrumentation. \n\nI need to make sure both calls are made in the same response, as per the instructions. No text, just the two tool calls. Then, if the tool results say \"Started task,\" I have to call task_wait. But since the user hasn't provided any results yet, I should just proceed with the initial calls.\n\nAfter both results are available, I need to answer with a single sentence starting with \"Final answer from read:\" including the researcher result, read title, URL, and excerpt. But right now, the first step is to generate the two tool calls.\n", - "type": "reasoning.text" - } - ], - "role": "assistant", - "tool_calls": [ - { - "function": { - "arguments": "{\"message\":\"Run the Braintrust Eve instrumentation e2e scenario\"}", - "name": "researcher" - }, - "id": "call_e82b", - "type": "function" - }, - { - "function": { - "arguments": "{\"url\":\"https://eve.dev/docs/guides/instrumentation\"}", - "name": "read" - }, - "id": "call_614f", - "type": "function" - } - ] - }, - { - "content": "{\"excerpt\":\"Eve hooks expose runtime stream events that Braintrust maps into a nested turn trace.\",\"section\":\"Runtime context\",\"title\":\"Eve instrumentation\",\"url\":\"https://eve.dev/docs/guides/instrumentation\"}", - "name": "read", - "role": "tool", - "tool_call_id": "call_614f" - }, - { - "content": "Researcher result: Eve instrumentation https://eve.dev/docs/guides/instrumentation", - "name": "researcher", - "role": "tool", - "tool_call_id": "call_e82b" - } - ], - "model": "qwen/qwen3-30b-a3b", - "provider": { - "only": ["deepinfra"], - "require_parameters": true - }, - "reasoning": { - "effort": "low" - }, - "stream": true, - "tool_choice": "auto", - "tools": [ - { - "function": { - "description": "Ask the user a question and wait for their response before continuing. Use this when you need clarification or a choice from the user.", - "name": "ask_question", - "parameters": { - "additionalProperties": false, - "properties": { - "allowFreeform": { - "description": "Whether the user may answer with freeform text instead of selecting one of the provided options.", - "type": "boolean" - }, - "options": { - "description": "Selectable answer options to present to the user.", - "items": { - "additionalProperties": false, - "properties": { - "description": { - "description": "Optional additional context for this option.", - "type": "string" - }, - "id": { - "description": "Stable identifier for the option.", - "type": "string" - }, - "label": { - "description": "User-facing label for the option.", - "type": "string" - }, - "style": { - "description": "Visual treatment hint for the option.", - "enum": ["primary", "danger", "default"], - "type": "string" - } - }, - "required": ["id", "label"], - "type": "object" - }, - "type": "array" - }, - "prompt": { - "description": "The prompt to present to the user.", - "type": "string" - } - }, - "required": ["prompt"], - "type": "object" - } - }, - "type": "function" - }, - { - "function": { - "description": "Execute a shell command in the shared workspace environment.", - "name": "bash", - "parameters": { - "additionalProperties": false, - "properties": { - "command": { - "description": "The shell command to execute.", - "type": "string" - } - }, - "required": ["command"], - "type": "object" - } - }, - "type": "function" - }, - { - "function": { - "description": "Fast file pattern matching tool that works with any codebase size.\n\nUsage:\n- Supports glob patterns like \"**/*.js\" or \"src/**/*.ts\".\n- Returns matching file paths.\n- Use this tool when you need to find files by name patterns.\n- If you are unsure of the correct file path, use the glob tool to look up filenames by glob pattern.\n- Use the grep tool instead if you need to search file contents.\n- Call this tool in parallel when you know there are multiple patterns to search for.", - "name": "glob", - "parameters": { - "additionalProperties": false, - "properties": { - "limit": { - "description": "Maximum number of results to return. Defaults to 100.", - "maximum": 1000, - "minimum": 1, - "type": "integer" - }, - "path": { - "description": "The directory to search in. Defaults to /workspace. Must be an absolute path. Omit to use the default.", - "type": "string" - }, - "pattern": { - "description": "The glob pattern to match files against (e.g. \"**/*.ts\", \"src/**/*.js\").", - "type": "string" - } - }, - "required": ["pattern"], - "type": "object" - } - }, - "type": "function" - }, - { - "function": { - "description": "Fast content search tool that works with any codebase size.\n\nUsage:\n- Searches file contents using regular expressions.\n- Supports full regex syntax (e.g. \"log.*Error\", \"function\\s+\\w+\").\n- Filter files by pattern with the glob parameter (e.g. \"*.js\", \"*.{ts,tsx}\").\n- Returns matching lines with file paths and line numbers.\n- Use this tool when you need to find files containing specific patterns.\n- Use the glob tool instead if you only need to find files by name.\n- Call this tool in parallel when you have multiple independent searches.\n- Any line longer than 2000 characters is truncated.", - "name": "grep", - "parameters": { - "additionalProperties": false, - "properties": { - "context": { - "description": "Number of surrounding context lines to include before and after each match. Defaults to 0.", - "minimum": 0, - "type": "integer" - }, - "glob": { - "description": "Filter files by glob pattern (e.g. \"*.ts\", \"*.{ts,tsx}\").", - "type": "string" - }, - "ignoreCase": { - "description": "Perform case-insensitive search. Defaults to false.", - "type": "boolean" - }, - "limit": { - "description": "Maximum number of matches to return per file. Defaults to 100.", - "maximum": 1000, - "minimum": 1, - "type": "integer" - }, - "literal": { - "description": "Treat the pattern as a literal string instead of a regular expression. Defaults to false.", - "type": "boolean" - }, - "path": { - "description": "The directory or file to search in. Defaults to /workspace. Must be an absolute path. Omit to use the default.", - "type": "string" - }, - "pattern": { - "description": "The regex pattern to search for in file contents (e.g. \"log.*Error\", \"function\\s+\\w+\").", - "type": "string" - } - }, - "required": ["pattern"], - "type": "object" - } - }, - "type": "function" - }, - { - "function": { - "description": "Read a file from the local filesystem. If the path does not exist, an error is returned.\n\nUsage:\n- The filePath parameter should be an absolute path.\n- By default, this tool returns up to 2000 lines from the start of the file.\n- The offset parameter is the line number to start from (1-indexed).\n- To read later sections, call this tool again with a larger offset.\n- Use the grep tool to find specific content in large files or files with long lines.\n- If you are unsure of the correct file path, use the glob tool to look up filenames by glob pattern.\n- Contents are returned with each line prefixed by its line number as `: `. For example, if a file has contents \"foo\\n\", you will receive \"1: foo\\n\".\n- Any line longer than 2000 characters is truncated.\n- Call this tool in parallel when you know there are multiple files you want to read.\n- Avoid tiny repeated slices (30 line chunks). If you need more context, read a larger window.", - "name": "read_file", - "parameters": { - "additionalProperties": false, - "properties": { - "filePath": { - "description": "The absolute path to the file to read.", - "type": "string" - }, - "limit": { - "description": "Maximum number of lines to return. Defaults to 2000.", - "minimum": 1, - "type": "integer" - }, - "offset": { - "description": "1-based line number to start from. Defaults to 1.", - "minimum": 1, - "type": "integer" - } - }, - "required": ["filePath"], - "type": "object" - } - }, - "type": "function" - }, - { - "function": { - "description": "Writes a file to the local filesystem.\n\nUsage:\n- This tool will overwrite the existing file if there is one at the provided path.\n- If this is an existing file, you MUST use the read_file tool first to read the file's contents. This tool will fail if you did not read the file first.\n- ALWAYS prefer editing existing files in the codebase. NEVER write new files unless explicitly required.\n- NEVER proactively create documentation files (*.md) or README files. Only create documentation files if explicitly requested by the User.\n- Only use emojis if the user explicitly requests it. Avoid writing emojis to files unless asked.", - "name": "write_file", - "parameters": { - "additionalProperties": false, - "properties": { - "content": { - "description": "Complete replacement file contents.", - "type": "string" - }, - "filePath": { - "description": "The absolute path to the file to write (must be absolute, not relative).", - "type": "string" - } - }, - "required": ["filePath", "content"], - "type": "object" - } - }, - "type": "function" - }, - { - "function": { - "description": "Use this tool to create and manage a structured task list for the current session.\nThis helps you track progress, organize complex tasks, and demonstrate thoroughness.\n\nWhen to use:\n- Complex multistep tasks requiring 3 or more distinct steps\n- When the user provides multiple tasks or a numbered list\n- After receiving new instructions, to capture requirements\n- After completing a task, to mark it complete and add follow-ups\n\nWhen NOT to use:\n- Single, straightforward tasks that need no tracking\n- Purely conversational or informational requests\n\nUsage:\n- Call with `todos` to replace the entire list (full replacement write)\n- Call without `todos` to read the current list\n- Both return the full current list with status counts\n- Mark tasks in_progress when you start, completed when done\n- Only have ONE task in_progress at a time", - "name": "todo", - "parameters": { - "additionalProperties": false, - "properties": { - "todos": { - "description": "The updated todo list. Omit to read the current list without modifying it.", - "items": { - "additionalProperties": false, - "properties": { - "content": { - "description": "Brief description of the task.", - "type": "string" - }, - "priority": { - "description": "Priority level of the task.", - "enum": ["high", "medium", "low"], - "type": "string" - }, - "status": { - "description": "Current status of the task.", - "enum": [ - "pending", - "in_progress", - "completed", - "cancelled" - ], - "type": "string" - } - }, - "required": ["content", "status", "priority"], - "type": "object" - }, - "type": "array" - } - }, - "type": "object" - } - }, - "type": "function" - }, - { - "function": { - "description": "Fetch a webpage and return its content in the requested format. Use this to retrieve and analyze content from URLs.\n\nUsage notes:\n- The URL must be a fully-formed valid URL starting with http:// or https://\n- HTML responses are automatically converted to markdown or plain text based on the requested format\n- Format options: \"markdown\" (default), \"text\", or \"html\"\n- Default timeout is 30 seconds (max 120 seconds)\n- Maximum response size is 5 MB; content is further capped at the shared tool-output budget (50 KB / 2000 lines)\n- This tool is read-only and does not modify any files", - "name": "web_fetch", - "parameters": { - "additionalProperties": false, - "properties": { - "format": { - "description": "The format to return the content in (text, markdown, or html). HTML responses are automatically converted to the requested format. Defaults to \"markdown\".", - "enum": ["markdown", "text", "html"], - "type": "string" - }, - "timeout": { - "description": "Optional timeout in seconds. Defaults to 30, max 120.", - "type": "number" - }, - "url": { - "description": "The fully-formed URL to fetch content from. Must start with http:// or https://.", - "type": "string" - } - }, - "required": ["url"], - "type": "object" - } - }, - "type": "function" - }, - { - "function": { - "description": "Load the full instructions for one available skill by name or id. Use this tool when the request clearly matches a listed skill description or when the user explicitly asks for that skill. This is not for MCP connections; use connection_search to access an installed connection. Loading adds the skill instructions to the current turn. Choose the \"skill\" value from the Available skills block.", - "name": "load_skill", - "parameters": { - "additionalProperties": false, - "properties": { - "skill": { - "description": "Available skill name or id.", - "type": "string" - } - }, - "required": ["skill"], - "type": "object" - } - }, - "type": "function" - }, - { - "function": { - "description": "Reads a deterministic Eve documentation page fixture.", - "name": "read", - "parameters": { - "$schema": "http://json-schema.org/draft-07/schema#", - "additionalProperties": false, - "properties": { - "url": { - "type": "string" - } - }, - "required": ["url"], - "type": "object" - } - }, - "type": "function" - }, - { - "function": { - "description": "Searches the Eve instrumentation documentation fixture.", - "name": "search", - "parameters": { - "$schema": "http://json-schema.org/draft-07/schema#", - "additionalProperties": false, - "properties": { - "query": { - "type": "string" - } - }, - "required": ["query"], - "type": "object" - } - }, - "type": "function" - }, - { - "function": { - "description": "Research the Eve instrumentation documentation before the parent reads it.", - "name": "researcher", - "parameters": { - "additionalProperties": false, - "properties": { - "message": { - "description": "The message to send to the subagent. Provide all context the subagent needs to complete the task; the subagent does not see the parent's history.", - "type": "string" - }, - "outputSchema": { - "description": "When provided, the subagent runs in task mode and must produce structured output matching this JSON Schema. The structured output becomes the tool result.", - "type": "object" - } - }, - "required": ["message"], - "type": "object" - } - }, - "type": "function" - }, - { - "function": { - "description": "Delegate a focused subtask to a fresh copy of yourself. Use it to isolate complex work or split a large task into independent pieces. Issue multiple `agent` calls in one response to run a small fixed set in parallel. Each child has fresh history and state but shares your tools and sandbox, so include essential context in `message` and give parallel writers non-overlapping scopes.", - "name": "agent", - "parameters": { - "additionalProperties": false, - "properties": { - "message": { - "description": "The message to send to the subagent. Provide all context the subagent needs to complete the task; the subagent does not see the parent's history.", - "type": "string" - }, - "outputSchema": { - "description": "When provided, the subagent runs in task mode and must produce structured output matching this JSON Schema. The structured output becomes the tool result.", - "type": "object" - } - }, - "required": ["message"], - "type": "object" - } - }, - "type": "function" - } - ] - } - }, - "headers": {}, - "method": "POST", - "url": "https://openrouter.ai/api/v1/chat/completions" - }, - "response": { - "body": { - "contentType": "text/event-stream", - "kind": "binary", - "path": "eve-v0.cassette.blobs/74cbb61e1f831a83ee356e49bf5d481b5679f3983fb1fb07c8b1505c9ac65d9f.bin", - "sha256": "74cbb61e1f831a83ee356e49bf5d481b5679f3983fb1fb07c8b1505c9ac65d9f" - }, - "headers": { - "access-control-allow-origin": "*", - "access-control-expose-headers": "X-Generation-Id,X-Provider-Name,request-id,cf-ray", - "cache-control": "no-cache", - "cf-ray": "a45d8e99df7e3845-YYZ", - "content-type": "text/event-stream", - "date": "Mon, 05 Oct 2026 15:46:04 GMT", - "permissions-policy": "payment=(self \"https://checkout.stripe.com\" \"https://connect-js.stripe.com\" \"https://js.stripe.com\" \"https://*.js.stripe.com\" \"https://hooks.stripe.com\")", - "referrer-policy": "no-referrer, strict-origin-when-cross-origin", - "server": "cloudflare", - "set-cookie": "[REDACTED]", - "x-content-type-options": "nosniff", - "x-generation-id": "gen-1791215164-iEel1q5g7Q2702ZUqR69" - }, - "status": 200 - } - }, - { - "callIndex": 4, - "id": "897d643c7593df85", - "matchKey": "POST openrouter.ai/api/v1/chat/completions", - "recordedAt": "2026-10-05T15:46:13.851Z", - "request": { - "body": { - "kind": "json", - "value": { - "messages": [ - { - "content": [ - { - "text": "Instructions (instructions)\nYou are a deterministic fixture agent for Braintrust Eve instrumentation tests.\n\nFor every user task, follow these steps exactly:\n\n1. Your first response MUST contain exactly these two tool calls and no text:\n - researcher with only this argument: {\"message\":\"\"}.\n Copy the current user message verbatim, including words such as \"again\";\n never reuse a message from an earlier turn. Do not pass outputSchema.\n - read with only this argument:\n {\"url\":\"https://eve.dev/docs/guides/instrumentation\"}.\n Emit both calls simultaneously in the same response, with researcher first\n and read second, so Eve executes them as one parallel batch. The calls are\n independent. NEVER call one by itself, wait for its result, or emit either\n call in a separate response.\n2. If a tool result says \"Started task\", your next response MUST be exactly one\n task_wait call with no arguments and no text.\n3. Only after both results are available, answer with a single sentence that\n starts with \"Final answer from read:\" and includes the researcher result, read\n title, URL, and read excerpt.\n\nTool execution\nA single tool or subagent call runs as one serial action. If you call multiple independent tools or subagents in one response, eve treats that batch as parallel work. Only batch work that is independent and does not rely on another call in the same response.", - "type": "text" - } - ], - "role": "system" - }, - { - "content": "Run the Braintrust Eve instrumentation e2e scenario", - "role": "user" - }, - { - "content": null, - "reasoning": "\nOkay, the user wants me to run the Braintrust Eve instrumentation end-to-end scenario. Let me start by understanding what that entails. The user mentioned \"e2e scenario,\" which usually stands for end-to-end testing. So, they probably want to simulate or execute a complete workflow within the Eve instrumentation setup.\n\nFirst, I need to check the available tools. The user provided a list of functions, including researcher, read, and others. The instructions say that for every user task, I must first make two tool calls: researcher with the user's message and read with the specific URL. \n\nThe user's message is \"Run the Braintrust Eve instrumentation e2e scenario.\" So, I should call the researcher function with this message. Then, the read function should access the documentation URL provided earlier: https://eve.dev/docs/guides/instrumentation. \n\nI need to make sure both calls are made in the same response, as per the instructions. No text, just the two tool calls. Then, if the tool results say \"Started task,\" I have to call task_wait. But since the user hasn't provided any results yet, I should just proceed with the initial calls.\n\nAfter both results are available, I need to answer with a single sentence starting with \"Final answer from read:\" including the researcher result, read title, URL, and excerpt. But right now, the first step is to generate the two tool calls.\n", - "reasoning_details": [ - { - "format": "unknown", - "index": 0, - "text": "\nOkay, the user wants me to run the Braintrust Eve instrumentation end-to-end scenario. Let me start by understanding what that entails. The user mentioned \"e2e scenario,\" which usually stands for end-to-end testing. So, they probably want to simulate or execute a complete workflow within the Eve instrumentation setup.\n\nFirst, I need to check the available tools. The user provided a list of functions, including researcher, read, and others. The instructions say that for every user task, I must first make two tool calls: researcher with the user's message and read with the specific URL. \n\nThe user's message is \"Run the Braintrust Eve instrumentation e2e scenario.\" So, I should call the researcher function with this message. Then, the read function should access the documentation URL provided earlier: https://eve.dev/docs/guides/instrumentation. \n\nI need to make sure both calls are made in the same response, as per the instructions. No text, just the two tool calls. Then, if the tool results say \"Started task,\" I have to call task_wait. But since the user hasn't provided any results yet, I should just proceed with the initial calls.\n\nAfter both results are available, I need to answer with a single sentence starting with \"Final answer from read:\" including the researcher result, read title, URL, and excerpt. But right now, the first step is to generate the two tool calls.\n", - "type": "reasoning.text" - } - ], - "role": "assistant", - "tool_calls": [ - { - "function": { - "arguments": "{\"message\":\"Run the Braintrust Eve instrumentation e2e scenario\"}", - "name": "researcher" - }, - "id": "call_e82b", - "type": "function" - }, - { - "function": { - "arguments": "{\"url\":\"https://eve.dev/docs/guides/instrumentation\"}", - "name": "read" - }, - "id": "call_614f", - "type": "function" - } - ] - }, - { - "content": "{\"excerpt\":\"Eve hooks expose runtime stream events that Braintrust maps into a nested turn trace.\",\"section\":\"Runtime context\",\"title\":\"Eve instrumentation\",\"url\":\"https://eve.dev/docs/guides/instrumentation\"}", - "name": "read", - "role": "tool", - "tool_call_id": "call_614f" - }, - { - "content": "Researcher result: Eve instrumentation https://eve.dev/docs/guides/instrumentation", - "name": "researcher", - "role": "tool", - "tool_call_id": "call_e82b" - }, - { - "content": "Final answer from read: Eve instrumentation https://eve.dev/docs/guides/instrumentation - \"Eve hooks expose runtime stream events that Braintrust maps into a nested turn trace.\" (Eve instrumentation, https://eve.dev/docs/guides/instrumentation)", - "reasoning": "\nOkay, let me process what's happening here. The user asked to run the Braintrust Eve instrumentation e2e scenario. According to the instructions, I needed to make two initial tool calls: researcher with the user's message and read with the specific URL. Both were made, and now the results are in.\n\nThe researcher result mentions \"Eve instrumentation https://eve.dev/docs/guides/instrumentation,\" which seems like a confirmation that the research task was started. The read tool returned a snippet from the documentation, including the title, section, and an excerpt about Eve hooks mapping to a nested turn trace.\n\nNow, the next step is to compile this information into a final answer. The instructions specify that the answer must start with \"Final answer from read:\" and include the researcher result, the read title, URL, and excerpt. \n\nI need to ensure that all required elements are included. The researcher result is \"Eve instrumentation https://eve.dev/docs/guides/instrumentation,\" the title is \"Eve instrumentation,\" the URL is the same, and the excerpt is the provided text. \n\nI should structure the sentence correctly, making sure there's no markdown and that it's a single, coherent sentence. Let me check for any typos or missing parts. Everything seems to be in order. Time to put it all together.\n", - "reasoning_details": [ - { - "format": "unknown", - "index": 0, - "text": "\nOkay, let me process what's happening here. The user asked to run the Braintrust Eve instrumentation e2e scenario. According to the instructions, I needed to make two initial tool calls: researcher with the user's message and read with the specific URL. Both were made, and now the results are in.\n\nThe researcher result mentions \"Eve instrumentation https://eve.dev/docs/guides/instrumentation,\" which seems like a confirmation that the research task was started. The read tool returned a snippet from the documentation, including the title, section, and an excerpt about Eve hooks mapping to a nested turn trace.\n\nNow, the next step is to compile this information into a final answer. The instructions specify that the answer must start with \"Final answer from read:\" and include the researcher result, the read title, URL, and excerpt. \n\nI need to ensure that all required elements are included. The researcher result is \"Eve instrumentation https://eve.dev/docs/guides/instrumentation,\" the title is \"Eve instrumentation,\" the URL is the same, and the excerpt is the provided text. \n\nI should structure the sentence correctly, making sure there's no markdown and that it's a single, coherent sentence. Let me check for any typos or missing parts. Everything seems to be in order. Time to put it all together.\n", - "type": "reasoning.text" - } - ], - "role": "assistant" - }, - { - "content": "Run the Braintrust Eve instrumentation e2e scenario again", - "role": "user" - } - ], - "model": "qwen/qwen3-30b-a3b", - "provider": { - "only": ["deepinfra"], - "require_parameters": true - }, - "reasoning": { - "effort": "low" - }, - "stream": true, - "tool_choice": "auto", - "tools": [ - { - "function": { - "description": "Ask the user a question and wait for their response before continuing. Use this when you need clarification or a choice from the user.", - "name": "ask_question", - "parameters": { - "additionalProperties": false, - "properties": { - "allowFreeform": { - "description": "Whether the user may answer with freeform text instead of selecting one of the provided options.", - "type": "boolean" - }, - "options": { - "description": "Selectable answer options to present to the user.", - "items": { - "additionalProperties": false, - "properties": { - "description": { - "description": "Optional additional context for this option.", - "type": "string" - }, - "id": { - "description": "Stable identifier for the option.", - "type": "string" - }, - "label": { - "description": "User-facing label for the option.", - "type": "string" - }, - "style": { - "description": "Visual treatment hint for the option.", - "enum": ["primary", "danger", "default"], - "type": "string" - } - }, - "required": ["id", "label"], - "type": "object" - }, - "type": "array" - }, - "prompt": { - "description": "The prompt to present to the user.", - "type": "string" - } - }, - "required": ["prompt"], - "type": "object" - } - }, - "type": "function" - }, - { - "function": { - "description": "Execute a shell command in the shared workspace environment.", - "name": "bash", - "parameters": { - "additionalProperties": false, - "properties": { - "command": { - "description": "The shell command to execute.", - "type": "string" - } - }, - "required": ["command"], - "type": "object" - } - }, - "type": "function" - }, - { - "function": { - "description": "Fast file pattern matching tool that works with any codebase size.\n\nUsage:\n- Supports glob patterns like \"**/*.js\" or \"src/**/*.ts\".\n- Returns matching file paths.\n- Use this tool when you need to find files by name patterns.\n- If you are unsure of the correct file path, use the glob tool to look up filenames by glob pattern.\n- Use the grep tool instead if you need to search file contents.\n- Call this tool in parallel when you know there are multiple patterns to search for.", - "name": "glob", - "parameters": { - "additionalProperties": false, - "properties": { - "limit": { - "description": "Maximum number of results to return. Defaults to 100.", - "maximum": 1000, - "minimum": 1, - "type": "integer" - }, - "path": { - "description": "The directory to search in. Defaults to /workspace. Must be an absolute path. Omit to use the default.", - "type": "string" - }, - "pattern": { - "description": "The glob pattern to match files against (e.g. \"**/*.ts\", \"src/**/*.js\").", - "type": "string" - } - }, - "required": ["pattern"], - "type": "object" - } - }, - "type": "function" - }, - { - "function": { - "description": "Fast content search tool that works with any codebase size.\n\nUsage:\n- Searches file contents using regular expressions.\n- Supports full regex syntax (e.g. \"log.*Error\", \"function\\s+\\w+\").\n- Filter files by pattern with the glob parameter (e.g. \"*.js\", \"*.{ts,tsx}\").\n- Returns matching lines with file paths and line numbers.\n- Use this tool when you need to find files containing specific patterns.\n- Use the glob tool instead if you only need to find files by name.\n- Call this tool in parallel when you have multiple independent searches.\n- Any line longer than 2000 characters is truncated.", - "name": "grep", - "parameters": { - "additionalProperties": false, - "properties": { - "context": { - "description": "Number of surrounding context lines to include before and after each match. Defaults to 0.", - "minimum": 0, - "type": "integer" - }, - "glob": { - "description": "Filter files by glob pattern (e.g. \"*.ts\", \"*.{ts,tsx}\").", - "type": "string" - }, - "ignoreCase": { - "description": "Perform case-insensitive search. Defaults to false.", - "type": "boolean" - }, - "limit": { - "description": "Maximum number of matches to return per file. Defaults to 100.", - "maximum": 1000, - "minimum": 1, - "type": "integer" - }, - "literal": { - "description": "Treat the pattern as a literal string instead of a regular expression. Defaults to false.", - "type": "boolean" - }, - "path": { - "description": "The directory or file to search in. Defaults to /workspace. Must be an absolute path. Omit to use the default.", - "type": "string" - }, - "pattern": { - "description": "The regex pattern to search for in file contents (e.g. \"log.*Error\", \"function\\s+\\w+\").", - "type": "string" - } - }, - "required": ["pattern"], - "type": "object" - } - }, - "type": "function" - }, - { - "function": { - "description": "Read a file from the local filesystem. If the path does not exist, an error is returned.\n\nUsage:\n- The filePath parameter should be an absolute path.\n- By default, this tool returns up to 2000 lines from the start of the file.\n- The offset parameter is the line number to start from (1-indexed).\n- To read later sections, call this tool again with a larger offset.\n- Use the grep tool to find specific content in large files or files with long lines.\n- If you are unsure of the correct file path, use the glob tool to look up filenames by glob pattern.\n- Contents are returned with each line prefixed by its line number as `: `. For example, if a file has contents \"foo\\n\", you will receive \"1: foo\\n\".\n- Any line longer than 2000 characters is truncated.\n- Call this tool in parallel when you know there are multiple files you want to read.\n- Avoid tiny repeated slices (30 line chunks). If you need more context, read a larger window.", - "name": "read_file", - "parameters": { - "additionalProperties": false, - "properties": { - "filePath": { - "description": "The absolute path to the file to read.", - "type": "string" - }, - "limit": { - "description": "Maximum number of lines to return. Defaults to 2000.", - "minimum": 1, - "type": "integer" - }, - "offset": { - "description": "1-based line number to start from. Defaults to 1.", - "minimum": 1, - "type": "integer" - } - }, - "required": ["filePath"], - "type": "object" - } - }, - "type": "function" - }, - { - "function": { - "description": "Writes a file to the local filesystem.\n\nUsage:\n- This tool will overwrite the existing file if there is one at the provided path.\n- If this is an existing file, you MUST use the read_file tool first to read the file's contents. This tool will fail if you did not read the file first.\n- ALWAYS prefer editing existing files in the codebase. NEVER write new files unless explicitly required.\n- NEVER proactively create documentation files (*.md) or README files. Only create documentation files if explicitly requested by the User.\n- Only use emojis if the user explicitly requests it. Avoid writing emojis to files unless asked.", - "name": "write_file", - "parameters": { - "additionalProperties": false, - "properties": { - "content": { - "description": "Complete replacement file contents.", - "type": "string" - }, - "filePath": { - "description": "The absolute path to the file to write (must be absolute, not relative).", - "type": "string" - } - }, - "required": ["filePath", "content"], - "type": "object" - } - }, - "type": "function" - }, - { - "function": { - "description": "Use this tool to create and manage a structured task list for the current session.\nThis helps you track progress, organize complex tasks, and demonstrate thoroughness.\n\nWhen to use:\n- Complex multistep tasks requiring 3 or more distinct steps\n- When the user provides multiple tasks or a numbered list\n- After receiving new instructions, to capture requirements\n- After completing a task, to mark it complete and add follow-ups\n\nWhen NOT to use:\n- Single, straightforward tasks that need no tracking\n- Purely conversational or informational requests\n\nUsage:\n- Call with `todos` to replace the entire list (full replacement write)\n- Call without `todos` to read the current list\n- Both return the full current list with status counts\n- Mark tasks in_progress when you start, completed when done\n- Only have ONE task in_progress at a time", - "name": "todo", - "parameters": { - "additionalProperties": false, - "properties": { - "todos": { - "description": "The updated todo list. Omit to read the current list without modifying it.", - "items": { - "additionalProperties": false, - "properties": { - "content": { - "description": "Brief description of the task.", - "type": "string" - }, - "priority": { - "description": "Priority level of the task.", - "enum": ["high", "medium", "low"], - "type": "string" - }, - "status": { - "description": "Current status of the task.", - "enum": [ - "pending", - "in_progress", - "completed", - "cancelled" - ], - "type": "string" - } - }, - "required": ["content", "status", "priority"], - "type": "object" - }, - "type": "array" - } - }, - "type": "object" - } - }, - "type": "function" - }, - { - "function": { - "description": "Fetch a webpage and return its content in the requested format. Use this to retrieve and analyze content from URLs.\n\nUsage notes:\n- The URL must be a fully-formed valid URL starting with http:// or https://\n- HTML responses are automatically converted to markdown or plain text based on the requested format\n- Format options: \"markdown\" (default), \"text\", or \"html\"\n- Default timeout is 30 seconds (max 120 seconds)\n- Maximum response size is 5 MB; content is further capped at the shared tool-output budget (50 KB / 2000 lines)\n- This tool is read-only and does not modify any files", - "name": "web_fetch", - "parameters": { - "additionalProperties": false, - "properties": { - "format": { - "description": "The format to return the content in (text, markdown, or html). HTML responses are automatically converted to the requested format. Defaults to \"markdown\".", - "enum": ["markdown", "text", "html"], - "type": "string" - }, - "timeout": { - "description": "Optional timeout in seconds. Defaults to 30, max 120.", - "type": "number" - }, - "url": { - "description": "The fully-formed URL to fetch content from. Must start with http:// or https://.", - "type": "string" - } - }, - "required": ["url"], - "type": "object" - } - }, - "type": "function" - }, - { - "function": { - "description": "Load the full instructions for one available skill by name or id. Use this tool when the request clearly matches a listed skill description or when the user explicitly asks for that skill. This is not for MCP connections; use connection_search to access an installed connection. Loading adds the skill instructions to the current turn. Choose the \"skill\" value from the Available skills block.", - "name": "load_skill", - "parameters": { - "additionalProperties": false, - "properties": { - "skill": { - "description": "Available skill name or id.", - "type": "string" - } - }, - "required": ["skill"], - "type": "object" - } - }, - "type": "function" - }, - { - "function": { - "description": "Reads a deterministic Eve documentation page fixture.", - "name": "read", - "parameters": { - "$schema": "http://json-schema.org/draft-07/schema#", - "additionalProperties": false, - "properties": { - "url": { - "type": "string" - } - }, - "required": ["url"], - "type": "object" - } - }, - "type": "function" - }, - { - "function": { - "description": "Searches the Eve instrumentation documentation fixture.", - "name": "search", - "parameters": { - "$schema": "http://json-schema.org/draft-07/schema#", - "additionalProperties": false, - "properties": { - "query": { - "type": "string" - } - }, - "required": ["query"], - "type": "object" - } - }, - "type": "function" - }, - { - "function": { - "description": "Research the Eve instrumentation documentation before the parent reads it.", - "name": "researcher", - "parameters": { - "additionalProperties": false, - "properties": { - "message": { - "description": "The message to send to the subagent. Provide all context the subagent needs to complete the task; the subagent does not see the parent's history.", - "type": "string" - }, - "outputSchema": { - "description": "When provided, the subagent runs in task mode and must produce structured output matching this JSON Schema. The structured output becomes the tool result.", - "type": "object" - } - }, - "required": ["message"], - "type": "object" - } - }, - "type": "function" - }, - { - "function": { - "description": "Delegate a focused subtask to a fresh copy of yourself. Use it to isolate complex work or split a large task into independent pieces. Issue multiple `agent` calls in one response to run a small fixed set in parallel. Each child has fresh history and state but shares your tools and sandbox, so include essential context in `message` and give parallel writers non-overlapping scopes.", - "name": "agent", - "parameters": { - "additionalProperties": false, - "properties": { - "message": { - "description": "The message to send to the subagent. Provide all context the subagent needs to complete the task; the subagent does not see the parent's history.", - "type": "string" - }, - "outputSchema": { - "description": "When provided, the subagent runs in task mode and must produce structured output matching this JSON Schema. The structured output becomes the tool result.", - "type": "object" - } - }, - "required": ["message"], - "type": "object" - } - }, - "type": "function" - } - ] - } - }, - "headers": {}, - "method": "POST", - "url": "https://openrouter.ai/api/v1/chat/completions" - }, - "response": { - "body": { - "contentType": "text/event-stream", - "kind": "binary", - "path": "eve-v0.cassette.blobs/bd499a7b095943c5518ebb65b2d81ab0674e63dc823e3fbac6e5106bcc1e3861.bin", - "sha256": "bd499a7b095943c5518ebb65b2d81ab0674e63dc823e3fbac6e5106bcc1e3861" - }, - "headers": { - "access-control-allow-origin": "*", - "access-control-expose-headers": "X-Generation-Id,X-Provider-Name,request-id,cf-ray", - "cache-control": "no-cache", - "cf-ray": "a45d8ec0aeb819cf-YYZ", - "content-type": "text/event-stream", - "date": "Mon, 05 Oct 2026 15:46:10 GMT", - "permissions-policy": "payment=(self \"https://checkout.stripe.com\" \"https://connect-js.stripe.com\" \"https://js.stripe.com\" \"https://*.js.stripe.com\" \"https://hooks.stripe.com\")", - "referrer-policy": "no-referrer, strict-origin-when-cross-origin", - "server": "cloudflare", - "set-cookie": "[REDACTED]", - "x-content-type-options": "nosniff", - "x-generation-id": "gen-1791215170-rEB9MwYfzfLp1szWk4jz" - }, - "status": 200 - } - }, - { - "callIndex": 5, - "id": "f3f182a4775a24fe", - "matchKey": "POST openrouter.ai/api/v1/chat/completions", - "recordedAt": "2026-10-05T15:46:19.433Z", - "request": { - "body": { - "kind": "json", - "value": { - "messages": [ - { - "content": [ - { - "text": "Instructions (instructions)\nYou are the deterministic researcher subagent for Braintrust Eve instrumentation tests.\n\nFor every task:\n\n1. Call the search tool exactly once with the full task as the query.\n2. After the search result is available, answer with a single sentence that starts\n with \"Researcher result:\" and includes the search title and URL.\n\nTool execution\nA single tool or subagent call runs as one serial action. If you call multiple independent tools or subagents in one response, eve treats that batch as parallel work. Only batch work that is independent and does not rely on another call in the same response.", - "type": "text" - } - ], - "role": "system" - }, - { - "content": "You are the subagent \"researcher\".\nDescription: Research the Eve instrumentation documentation before the parent reads it.\n\nThe caller delegated the following task to you. Complete it and return the final result directly.\n\nCaller message:\nRun the Braintrust Eve instrumentation e2e scenario again", - "role": "user" - } - ], - "model": "qwen/qwen3-30b-a3b", - "provider": { - "only": ["deepinfra"], - "require_parameters": true - }, - "reasoning": { - "effort": "low" - }, - "stream": true, - "tool_choice": "auto", - "tools": [ - { - "function": { - "description": "Ask the user a question and wait for their response before continuing. Use this when you need clarification or a choice from the user.", - "name": "ask_question", - "parameters": { - "additionalProperties": false, - "properties": { - "allowFreeform": { - "description": "Whether the user may answer with freeform text instead of selecting one of the provided options.", - "type": "boolean" - }, - "options": { - "description": "Selectable answer options to present to the user.", - "items": { - "additionalProperties": false, - "properties": { - "description": { - "description": "Optional additional context for this option.", - "type": "string" - }, - "id": { - "description": "Stable identifier for the option.", - "type": "string" - }, - "label": { - "description": "User-facing label for the option.", - "type": "string" - }, - "style": { - "description": "Visual treatment hint for the option.", - "enum": ["primary", "danger", "default"], - "type": "string" - } - }, - "required": ["id", "label"], - "type": "object" - }, - "type": "array" - }, - "prompt": { - "description": "The prompt to present to the user.", - "type": "string" - } - }, - "required": ["prompt"], - "type": "object" - } - }, - "type": "function" - }, - { - "function": { - "description": "Execute a shell command in the shared workspace environment.", - "name": "bash", - "parameters": { - "additionalProperties": false, - "properties": { - "command": { - "description": "The shell command to execute.", - "type": "string" - } - }, - "required": ["command"], - "type": "object" - } - }, - "type": "function" - }, - { - "function": { - "description": "Fast file pattern matching tool that works with any codebase size.\n\nUsage:\n- Supports glob patterns like \"**/*.js\" or \"src/**/*.ts\".\n- Returns matching file paths.\n- Use this tool when you need to find files by name patterns.\n- If you are unsure of the correct file path, use the glob tool to look up filenames by glob pattern.\n- Use the grep tool instead if you need to search file contents.\n- Call this tool in parallel when you know there are multiple patterns to search for.", - "name": "glob", - "parameters": { - "additionalProperties": false, - "properties": { - "limit": { - "description": "Maximum number of results to return. Defaults to 100.", - "maximum": 1000, - "minimum": 1, - "type": "integer" - }, - "path": { - "description": "The directory to search in. Defaults to /workspace. Must be an absolute path. Omit to use the default.", - "type": "string" - }, - "pattern": { - "description": "The glob pattern to match files against (e.g. \"**/*.ts\", \"src/**/*.js\").", - "type": "string" - } - }, - "required": ["pattern"], - "type": "object" - } - }, - "type": "function" - }, - { - "function": { - "description": "Fast content search tool that works with any codebase size.\n\nUsage:\n- Searches file contents using regular expressions.\n- Supports full regex syntax (e.g. \"log.*Error\", \"function\\s+\\w+\").\n- Filter files by pattern with the glob parameter (e.g. \"*.js\", \"*.{ts,tsx}\").\n- Returns matching lines with file paths and line numbers.\n- Use this tool when you need to find files containing specific patterns.\n- Use the glob tool instead if you only need to find files by name.\n- Call this tool in parallel when you have multiple independent searches.\n- Any line longer than 2000 characters is truncated.", - "name": "grep", - "parameters": { - "additionalProperties": false, - "properties": { - "context": { - "description": "Number of surrounding context lines to include before and after each match. Defaults to 0.", - "minimum": 0, - "type": "integer" - }, - "glob": { - "description": "Filter files by glob pattern (e.g. \"*.ts\", \"*.{ts,tsx}\").", - "type": "string" - }, - "ignoreCase": { - "description": "Perform case-insensitive search. Defaults to false.", - "type": "boolean" - }, - "limit": { - "description": "Maximum number of matches to return per file. Defaults to 100.", - "maximum": 1000, - "minimum": 1, - "type": "integer" - }, - "literal": { - "description": "Treat the pattern as a literal string instead of a regular expression. Defaults to false.", - "type": "boolean" - }, - "path": { - "description": "The directory or file to search in. Defaults to /workspace. Must be an absolute path. Omit to use the default.", - "type": "string" - }, - "pattern": { - "description": "The regex pattern to search for in file contents (e.g. \"log.*Error\", \"function\\s+\\w+\").", - "type": "string" - } - }, - "required": ["pattern"], - "type": "object" - } - }, - "type": "function" - }, - { - "function": { - "description": "Read a file from the local filesystem. If the path does not exist, an error is returned.\n\nUsage:\n- The filePath parameter should be an absolute path.\n- By default, this tool returns up to 2000 lines from the start of the file.\n- The offset parameter is the line number to start from (1-indexed).\n- To read later sections, call this tool again with a larger offset.\n- Use the grep tool to find specific content in large files or files with long lines.\n- If you are unsure of the correct file path, use the glob tool to look up filenames by glob pattern.\n- Contents are returned with each line prefixed by its line number as `: `. For example, if a file has contents \"foo\\n\", you will receive \"1: foo\\n\".\n- Any line longer than 2000 characters is truncated.\n- Call this tool in parallel when you know there are multiple files you want to read.\n- Avoid tiny repeated slices (30 line chunks). If you need more context, read a larger window.", - "name": "read_file", - "parameters": { - "additionalProperties": false, - "properties": { - "filePath": { - "description": "The absolute path to the file to read.", - "type": "string" - }, - "limit": { - "description": "Maximum number of lines to return. Defaults to 2000.", - "minimum": 1, - "type": "integer" - }, - "offset": { - "description": "1-based line number to start from. Defaults to 1.", - "minimum": 1, - "type": "integer" - } - }, - "required": ["filePath"], - "type": "object" - } - }, - "type": "function" - }, - { - "function": { - "description": "Writes a file to the local filesystem.\n\nUsage:\n- This tool will overwrite the existing file if there is one at the provided path.\n- If this is an existing file, you MUST use the read_file tool first to read the file's contents. This tool will fail if you did not read the file first.\n- ALWAYS prefer editing existing files in the codebase. NEVER write new files unless explicitly required.\n- NEVER proactively create documentation files (*.md) or README files. Only create documentation files if explicitly requested by the User.\n- Only use emojis if the user explicitly requests it. Avoid writing emojis to files unless asked.", - "name": "write_file", - "parameters": { - "additionalProperties": false, - "properties": { - "content": { - "description": "Complete replacement file contents.", - "type": "string" - }, - "filePath": { - "description": "The absolute path to the file to write (must be absolute, not relative).", - "type": "string" - } - }, - "required": ["filePath", "content"], - "type": "object" - } - }, - "type": "function" - }, - { - "function": { - "description": "Use this tool to create and manage a structured task list for the current session.\nThis helps you track progress, organize complex tasks, and demonstrate thoroughness.\n\nWhen to use:\n- Complex multistep tasks requiring 3 or more distinct steps\n- When the user provides multiple tasks or a numbered list\n- After receiving new instructions, to capture requirements\n- After completing a task, to mark it complete and add follow-ups\n\nWhen NOT to use:\n- Single, straightforward tasks that need no tracking\n- Purely conversational or informational requests\n\nUsage:\n- Call with `todos` to replace the entire list (full replacement write)\n- Call without `todos` to read the current list\n- Both return the full current list with status counts\n- Mark tasks in_progress when you start, completed when done\n- Only have ONE task in_progress at a time", - "name": "todo", - "parameters": { - "additionalProperties": false, - "properties": { - "todos": { - "description": "The updated todo list. Omit to read the current list without modifying it.", - "items": { - "additionalProperties": false, - "properties": { - "content": { - "description": "Brief description of the task.", - "type": "string" - }, - "priority": { - "description": "Priority level of the task.", - "enum": ["high", "medium", "low"], - "type": "string" - }, - "status": { - "description": "Current status of the task.", - "enum": [ - "pending", - "in_progress", - "completed", - "cancelled" - ], - "type": "string" - } - }, - "required": ["content", "status", "priority"], - "type": "object" - }, - "type": "array" - } - }, - "type": "object" - } - }, - "type": "function" - }, - { - "function": { - "description": "Fetch a webpage and return its content in the requested format. Use this to retrieve and analyze content from URLs.\n\nUsage notes:\n- The URL must be a fully-formed valid URL starting with http:// or https://\n- HTML responses are automatically converted to markdown or plain text based on the requested format\n- Format options: \"markdown\" (default), \"text\", or \"html\"\n- Default timeout is 30 seconds (max 120 seconds)\n- Maximum response size is 5 MB; content is further capped at the shared tool-output budget (50 KB / 2000 lines)\n- This tool is read-only and does not modify any files", - "name": "web_fetch", - "parameters": { - "additionalProperties": false, - "properties": { - "format": { - "description": "The format to return the content in (text, markdown, or html). HTML responses are automatically converted to the requested format. Defaults to \"markdown\".", - "enum": ["markdown", "text", "html"], - "type": "string" - }, - "timeout": { - "description": "Optional timeout in seconds. Defaults to 30, max 120.", - "type": "number" - }, - "url": { - "description": "The fully-formed URL to fetch content from. Must start with http:// or https://.", - "type": "string" - } - }, - "required": ["url"], - "type": "object" - } - }, - "type": "function" - }, - { - "function": { - "description": "Load the full instructions for one available skill by name or id. Use this tool when the request clearly matches a listed skill description or when the user explicitly asks for that skill. This is not for MCP connections; use connection_search to access an installed connection. Loading adds the skill instructions to the current turn. Choose the \"skill\" value from the Available skills block.", - "name": "load_skill", - "parameters": { - "additionalProperties": false, - "properties": { - "skill": { - "description": "Available skill name or id.", - "type": "string" - } - }, - "required": ["skill"], - "type": "object" - } - }, - "type": "function" - }, - { - "function": { - "description": "Searches the Eve instrumentation documentation fixture.", - "name": "search", - "parameters": { - "$schema": "http://json-schema.org/draft-07/schema#", - "additionalProperties": false, - "properties": { - "query": { - "type": "string" - } - }, - "required": ["query"], - "type": "object" - } - }, - "type": "function" - }, - { - "function": { - "description": "Delegate a focused subtask to a fresh copy of yourself. Use it to isolate complex work or split a large task into independent pieces. Issue multiple `agent` calls in one response to run a small fixed set in parallel. Each child has fresh history and state but shares your tools and sandbox, so include essential context in `message` and give parallel writers non-overlapping scopes.", - "name": "agent", - "parameters": { - "additionalProperties": false, - "properties": { - "message": { - "description": "The message to send to the subagent. Provide all context the subagent needs to complete the task; the subagent does not see the parent's history.", - "type": "string" - }, - "outputSchema": { - "description": "When provided, the subagent runs in task mode and must produce structured output matching this JSON Schema. The structured output becomes the tool result.", - "type": "object" - } - }, - "required": ["message"], - "type": "object" - } - }, - "type": "function" - } - ] - } - }, - "headers": {}, - "method": "POST", - "url": "https://openrouter.ai/api/v1/chat/completions" - }, - "response": { - "body": { - "chunks": [ - "data: {\"id\":\"gen-1791215176-nzI6X3OkiLZMwgf1ZcZp\",\"object\":\"chat.completion.chunk\",\"created\":1791215176,\"model\":\"qwen/qwen3-30b-a3b\",\"provider\":\"DeepInfra\",\"choices\":[{\"index\":0,\"delta\":{\"content\":\"\",\"role\":\"assistant\",\"reasoning\":\"\\n\",\"reasoning_details\":[{\"type\":\"reasoning.text\",\"text\":\"\\n\",\"format\":\"unknown\",\"index\":0}]},\"finish_reason\":null,\"native_finish_reason\":null}]}", - "data: {\"id\":\"gen-1791215176-nzI6X3OkiLZMwgf1ZcZp\",\"object\":\"chat.completion.chunk\",\"created\":1791215176,\"model\":\"qwen/qwen3-30b-a3b\",\"provider\":\"DeepInfra\",\"choices\":[{\"index\":0,\"delta\":{\"content\":\"\",\"role\":\"assistant\",\"reasoning\":\"Okay\",\"reasoning_details\":[{\"type\":\"reasoning.text\",\"text\":\"Okay\",\"format\":\"unknown\",\"index\":0}]},\"finish_reason\":null,\"native_finish_reason\":null}]}", - "data: {\"id\":\"gen-1791215176-nzI6X3OkiLZMwgf1ZcZp\",\"object\":\"chat.completion.chunk\",\"created\":1791215176,\"model\":\"qwen/qwen3-30b-a3b\",\"provider\":\"DeepInfra\",\"choices\":[{\"index\":0,\"delta\":{\"content\":\"\",\"role\":\"assistant\",\"reasoning\":\",\",\"reasoning_details\":[{\"type\":\"reasoning.text\",\"text\":\",\",\"format\":\"unknown\",\"index\":0}]},\"finish_reason\":null,\"native_finish_reason\":null}]}", - "data: {\"id\":\"gen-1791215176-nzI6X3OkiLZMwgf1ZcZp\",\"object\":\"chat.completion.chunk\",\"created\":1791215176,\"model\":\"qwen/qwen3-30b-a3b\",\"provider\":\"DeepInfra\",\"choices\":[{\"index\":0,\"delta\":{\"content\":\"\",\"role\":\"assistant\",\"reasoning\":\" the\",\"reasoning_details\":[{\"type\":\"reasoning.text\",\"text\":\" the\",\"format\":\"unknown\",\"index\":0}]},\"finish_reason\":null,\"native_finish_reason\":null}]}", - "data: {\"id\":\"gen-1791215176-nzI6X3OkiLZMwgf1ZcZp\",\"object\":\"chat.completion.chunk\",\"created\":1791215176,\"model\":\"qwen/qwen3-30b-a3b\",\"provider\":\"DeepInfra\",\"choices\":[{\"index\":0,\"delta\":{\"content\":\"\",\"role\":\"assistant\",\"reasoning\":\" user\",\"reasoning_details\":[{\"type\":\"reasoning.text\",\"text\":\" user\",\"format\":\"unknown\",\"index\":0}]},\"finish_reason\":null,\"native_finish_reason\":null}]}", - "data: {\"id\":\"gen-1791215176-nzI6X3OkiLZMwgf1ZcZp\",\"object\":\"chat.completion.chunk\",\"created\":1791215176,\"model\":\"qwen/qwen3-30b-a3b\",\"provider\":\"DeepInfra\",\"choices\":[{\"index\":0,\"delta\":{\"content\":\"\",\"role\":\"assistant\",\"reasoning\":\" wants\",\"reasoning_details\":[{\"type\":\"reasoning.text\",\"text\":\" wants\",\"format\":\"unknown\",\"index\":0}]},\"finish_reason\":null,\"native_finish_reason\":null}]}", - "data: {\"id\":\"gen-1791215176-nzI6X3OkiLZMwgf1ZcZp\",\"object\":\"chat.completion.chunk\",\"created\":1791215176,\"model\":\"qwen/qwen3-30b-a3b\",\"provider\":\"DeepInfra\",\"choices\":[{\"index\":0,\"delta\":{\"content\":\"\",\"role\":\"assistant\",\"reasoning\":\" me\",\"reasoning_details\":[{\"type\":\"reasoning.text\",\"text\":\" me\",\"format\":\"unknown\",\"index\":0}]},\"finish_reason\":null,\"native_finish_reason\":null}]}", - "data: {\"id\":\"gen-1791215176-nzI6X3OkiLZMwgf1ZcZp\",\"object\":\"chat.completion.chunk\",\"created\":1791215176,\"model\":\"qwen/qwen3-30b-a3b\",\"provider\":\"DeepInfra\",\"choices\":[{\"index\":0,\"delta\":{\"content\":\"\",\"role\":\"assistant\",\"reasoning\":\" to\",\"reasoning_details\":[{\"type\":\"reasoning.text\",\"text\":\" to\",\"format\":\"unknown\",\"index\":0}]},\"finish_reason\":null,\"native_finish_reason\":null}]}", - "data: {\"id\":\"gen-1791215176-nzI6X3OkiLZMwgf1ZcZp\",\"object\":\"chat.completion.chunk\",\"created\":1791215176,\"model\":\"qwen/qwen3-30b-a3b\",\"provider\":\"DeepInfra\",\"choices\":[{\"index\":0,\"delta\":{\"content\":\"\",\"role\":\"assistant\",\"reasoning\":\" run\",\"reasoning_details\":[{\"type\":\"reasoning.text\",\"text\":\" run\",\"format\":\"unknown\",\"index\":0}]},\"finish_reason\":null,\"native_finish_reason\":null}]}", - "data: {\"id\":\"gen-1791215176-nzI6X3OkiLZMwgf1ZcZp\",\"object\":\"chat.completion.chunk\",\"created\":1791215176,\"model\":\"qwen/qwen3-30b-a3b\",\"provider\":\"DeepInfra\",\"choices\":[{\"index\":0,\"delta\":{\"content\":\"\",\"role\":\"assistant\",\"reasoning\":\" the\",\"reasoning_details\":[{\"type\":\"reasoning.text\",\"text\":\" the\",\"format\":\"unknown\",\"index\":0}]},\"finish_reason\":null,\"native_finish_reason\":null}]}", - "data: {\"id\":\"gen-1791215176-nzI6X3OkiLZMwgf1ZcZp\",\"object\":\"chat.completion.chunk\",\"created\":1791215176,\"model\":\"qwen/qwen3-30b-a3b\",\"provider\":\"DeepInfra\",\"choices\":[{\"index\":0,\"delta\":{\"content\":\"\",\"role\":\"assistant\",\"reasoning\":\" Brain\",\"reasoning_details\":[{\"type\":\"reasoning.text\",\"text\":\" Brain\",\"format\":\"unknown\",\"index\":0}]},\"finish_reason\":null,\"native_finish_reason\":null}]}", - "data: {\"id\":\"gen-1791215176-nzI6X3OkiLZMwgf1ZcZp\",\"object\":\"chat.completion.chunk\",\"created\":1791215176,\"model\":\"qwen/qwen3-30b-a3b\",\"provider\":\"DeepInfra\",\"choices\":[{\"index\":0,\"delta\":{\"content\":\"\",\"role\":\"assistant\",\"reasoning\":\"trust\",\"reasoning_details\":[{\"type\":\"reasoning.text\",\"text\":\"trust\",\"format\":\"unknown\",\"index\":0}]},\"finish_reason\":null,\"native_finish_reason\":null}]}", - "data: {\"id\":\"gen-1791215176-nzI6X3OkiLZMwgf1ZcZp\",\"object\":\"chat.completion.chunk\",\"created\":1791215176,\"model\":\"qwen/qwen3-30b-a3b\",\"provider\":\"DeepInfra\",\"choices\":[{\"index\":0,\"delta\":{\"content\":\"\",\"role\":\"assistant\",\"reasoning\":\" Eve\",\"reasoning_details\":[{\"type\":\"reasoning.text\",\"text\":\" Eve\",\"format\":\"unknown\",\"index\":0}]},\"finish_reason\":null,\"native_finish_reason\":null}]}", - "data: {\"id\":\"gen-1791215176-nzI6X3OkiLZMwgf1ZcZp\",\"object\":\"chat.completion.chunk\",\"created\":1791215176,\"model\":\"qwen/qwen3-30b-a3b\",\"provider\":\"DeepInfra\",\"choices\":[{\"index\":0,\"delta\":{\"content\":\"\",\"role\":\"assistant\",\"reasoning\":\" instrumentation\",\"reasoning_details\":[{\"type\":\"reasoning.text\",\"text\":\" instrumentation\",\"format\":\"unknown\",\"index\":0}]},\"finish_reason\":null,\"native_finish_reason\":null}]}", - "data: {\"id\":\"gen-1791215176-nzI6X3OkiLZMwgf1ZcZp\",\"object\":\"chat.completion.chunk\",\"created\":1791215176,\"model\":\"qwen/qwen3-30b-a3b\",\"provider\":\"DeepInfra\",\"choices\":[{\"index\":0,\"delta\":{\"content\":\"\",\"role\":\"assistant\",\"reasoning\":\" end\",\"reasoning_details\":[{\"type\":\"reasoning.text\",\"text\":\" end\",\"format\":\"unknown\",\"index\":0}]},\"finish_reason\":null,\"native_finish_reason\":null}]}", - "data: {\"id\":\"gen-1791215176-nzI6X3OkiLZMwgf1ZcZp\",\"object\":\"chat.completion.chunk\",\"created\":1791215176,\"model\":\"qwen/qwen3-30b-a3b\",\"provider\":\"DeepInfra\",\"choices\":[{\"index\":0,\"delta\":{\"content\":\"\",\"role\":\"assistant\",\"reasoning\":\"-to\",\"reasoning_details\":[{\"type\":\"reasoning.text\",\"text\":\"-to\",\"format\":\"unknown\",\"index\":0}]},\"finish_reason\":null,\"native_finish_reason\":null}]}", - "data: {\"id\":\"gen-1791215176-nzI6X3OkiLZMwgf1ZcZp\",\"object\":\"chat.completion.chunk\",\"created\":1791215176,\"model\":\"qwen/qwen3-30b-a3b\",\"provider\":\"DeepInfra\",\"choices\":[{\"index\":0,\"delta\":{\"content\":\"\",\"role\":\"assistant\",\"reasoning\":\"-end\",\"reasoning_details\":[{\"type\":\"reasoning.text\",\"text\":\"-end\",\"format\":\"unknown\",\"index\":0}]},\"finish_reason\":null,\"native_finish_reason\":null}]}", - "data: {\"id\":\"gen-1791215176-nzI6X3OkiLZMwgf1ZcZp\",\"object\":\"chat.completion.chunk\",\"created\":1791215176,\"model\":\"qwen/qwen3-30b-a3b\",\"provider\":\"DeepInfra\",\"choices\":[{\"index\":0,\"delta\":{\"content\":\"\",\"role\":\"assistant\",\"reasoning\":\" scenario\",\"reasoning_details\":[{\"type\":\"reasoning.text\",\"text\":\" scenario\",\"format\":\"unknown\",\"index\":0}]},\"finish_reason\":null,\"native_finish_reason\":null}]}", - "data: {\"id\":\"gen-1791215176-nzI6X3OkiLZMwgf1ZcZp\",\"object\":\"chat.completion.chunk\",\"created\":1791215176,\"model\":\"qwen/qwen3-30b-a3b\",\"provider\":\"DeepInfra\",\"choices\":[{\"index\":0,\"delta\":{\"content\":\"\",\"role\":\"assistant\",\"reasoning\":\" again\",\"reasoning_details\":[{\"type\":\"reasoning.text\",\"text\":\" again\",\"format\":\"unknown\",\"index\":0}]},\"finish_reason\":null,\"native_finish_reason\":null}]}", - "data: {\"id\":\"gen-1791215176-nzI6X3OkiLZMwgf1ZcZp\",\"object\":\"chat.completion.chunk\",\"created\":1791215176,\"model\":\"qwen/qwen3-30b-a3b\",\"provider\":\"DeepInfra\",\"choices\":[{\"index\":0,\"delta\":{\"content\":\"\",\"role\":\"assistant\",\"reasoning\":\".\",\"reasoning_details\":[{\"type\":\"reasoning.text\",\"text\":\".\",\"format\":\"unknown\",\"index\":0}]},\"finish_reason\":null,\"native_finish_reason\":null}]}", - "data: {\"id\":\"gen-1791215176-nzI6X3OkiLZMwgf1ZcZp\",\"object\":\"chat.completion.chunk\",\"created\":1791215176,\"model\":\"qwen/qwen3-30b-a3b\",\"provider\":\"DeepInfra\",\"choices\":[{\"index\":0,\"delta\":{\"content\":\"\",\"role\":\"assistant\",\"reasoning\":\" Since\",\"reasoning_details\":[{\"type\":\"reasoning.text\",\"text\":\" Since\",\"format\":\"unknown\",\"index\":0}]},\"finish_reason\":null,\"native_finish_reason\":null}]}", - "data: {\"id\":\"gen-1791215176-nzI6X3OkiLZMwgf1ZcZp\",\"object\":\"chat.completion.chunk\",\"created\":1791215176,\"model\":\"qwen/qwen3-30b-a3b\",\"provider\":\"DeepInfra\",\"choices\":[{\"index\":0,\"delta\":{\"content\":\"\",\"role\":\"assistant\",\"reasoning\":\" I\",\"reasoning_details\":[{\"type\":\"reasoning.text\",\"text\":\" I\",\"format\":\"unknown\",\"index\":0}]},\"finish_reason\":null,\"native_finish_reason\":null}]}", - "data: {\"id\":\"gen-1791215176-nzI6X3OkiLZMwgf1ZcZp\",\"object\":\"chat.completion.chunk\",\"created\":1791215176,\"model\":\"qwen/qwen3-30b-a3b\",\"provider\":\"DeepInfra\",\"choices\":[{\"index\":0,\"delta\":{\"content\":\"\",\"role\":\"assistant\",\"reasoning\":\"'m\",\"reasoning_details\":[{\"type\":\"reasoning.text\",\"text\":\"'m\",\"format\":\"unknown\",\"index\":0}]},\"finish_reason\":null,\"native_finish_reason\":null}]}", - "data: {\"id\":\"gen-1791215176-nzI6X3OkiLZMwgf1ZcZp\",\"object\":\"chat.completion.chunk\",\"created\":1791215176,\"model\":\"qwen/qwen3-30b-a3b\",\"provider\":\"DeepInfra\",\"choices\":[{\"index\":0,\"delta\":{\"content\":\"\",\"role\":\"assistant\",\"reasoning\":\" the\",\"reasoning_details\":[{\"type\":\"reasoning.text\",\"text\":\" the\",\"format\":\"unknown\",\"index\":0}]},\"finish_reason\":null,\"native_finish_reason\":null}]}", - "data: {\"id\":\"gen-1791215176-nzI6X3OkiLZMwgf1ZcZp\",\"object\":\"chat.completion.chunk\",\"created\":1791215176,\"model\":\"qwen/qwen3-30b-a3b\",\"provider\":\"DeepInfra\",\"choices\":[{\"index\":0,\"delta\":{\"content\":\"\",\"role\":\"assistant\",\"reasoning\":\" researcher\",\"reasoning_details\":[{\"type\":\"reasoning.text\",\"text\":\" researcher\",\"format\":\"unknown\",\"index\":0}]},\"finish_reason\":null,\"native_finish_reason\":null}]}", - "data: {\"id\":\"gen-1791215176-nzI6X3OkiLZMwgf1ZcZp\",\"object\":\"chat.completion.chunk\",\"created\":1791215176,\"model\":\"qwen/qwen3-30b-a3b\",\"provider\":\"DeepInfra\",\"choices\":[{\"index\":0,\"delta\":{\"content\":\"\",\"role\":\"assistant\",\"reasoning\":\" sub\",\"reasoning_details\":[{\"type\":\"reasoning.text\",\"text\":\" sub\",\"format\":\"unknown\",\"index\":0}]},\"finish_reason\":null,\"native_finish_reason\":null}]}", - "data: {\"id\":\"gen-1791215176-nzI6X3OkiLZMwgf1ZcZp\",\"object\":\"chat.completion.chunk\",\"created\":1791215176,\"model\":\"qwen/qwen3-30b-a3b\",\"provider\":\"DeepInfra\",\"choices\":[{\"index\":0,\"delta\":{\"content\":\"\",\"role\":\"assistant\",\"reasoning\":\"agent\",\"reasoning_details\":[{\"type\":\"reasoning.text\",\"text\":\"agent\",\"format\":\"unknown\",\"index\":0}]},\"finish_reason\":null,\"native_finish_reason\":null}]}", - "data: {\"id\":\"gen-1791215176-nzI6X3OkiLZMwgf1ZcZp\",\"object\":\"chat.completion.chunk\",\"created\":1791215176,\"model\":\"qwen/qwen3-30b-a3b\",\"provider\":\"DeepInfra\",\"choices\":[{\"index\":0,\"delta\":{\"content\":\"\",\"role\":\"assistant\",\"reasoning\":\",\",\"reasoning_details\":[{\"type\":\"reasoning.text\",\"text\":\",\",\"format\":\"unknown\",\"index\":0}]},\"finish_reason\":null,\"native_finish_reason\":null}]}", - "data: {\"id\":\"gen-1791215176-nzI6X3OkiLZMwgf1ZcZp\",\"object\":\"chat.completion.chunk\",\"created\":1791215176,\"model\":\"qwen/qwen3-30b-a3b\",\"provider\":\"DeepInfra\",\"choices\":[{\"index\":0,\"delta\":{\"content\":\"\",\"role\":\"assistant\",\"reasoning\":\" my\",\"reasoning_details\":[{\"type\":\"reasoning.text\",\"text\":\" my\",\"format\":\"unknown\",\"index\":0}]},\"finish_reason\":null,\"native_finish_reason\":null}]}", - "data: {\"id\":\"gen-1791215176-nzI6X3OkiLZMwgf1ZcZp\",\"object\":\"chat.completion.chunk\",\"created\":1791215176,\"model\":\"qwen/qwen3-30b-a3b\",\"provider\":\"DeepInfra\",\"choices\":[{\"index\":0,\"delta\":{\"content\":\"\",\"role\":\"assistant\",\"reasoning\":\" job\",\"reasoning_details\":[{\"type\":\"reasoning.text\",\"text\":\" job\",\"format\":\"unknown\",\"index\":0}]},\"finish_reason\":null,\"native_finish_reason\":null}]}", - "data: {\"id\":\"gen-1791215176-nzI6X3OkiLZMwgf1ZcZp\",\"object\":\"chat.completion.chunk\",\"created\":1791215176,\"model\":\"qwen/qwen3-30b-a3b\",\"provider\":\"DeepInfra\",\"choices\":[{\"index\":0,\"delta\":{\"content\":\"\",\"role\":\"assistant\",\"reasoning\":\" is\",\"reasoning_details\":[{\"type\":\"reasoning.text\",\"text\":\" is\",\"format\":\"unknown\",\"index\":0}]},\"finish_reason\":null,\"native_finish_reason\":null}]}", - "data: {\"id\":\"gen-1791215176-nzI6X3OkiLZMwgf1ZcZp\",\"object\":\"chat.completion.chunk\",\"created\":1791215176,\"model\":\"qwen/qwen3-30b-a3b\",\"provider\":\"DeepInfra\",\"choices\":[{\"index\":0,\"delta\":{\"content\":\"\",\"role\":\"assistant\",\"reasoning\":\" to\",\"reasoning_details\":[{\"type\":\"reasoning.text\",\"text\":\" to\",\"format\":\"unknown\",\"index\":0}]},\"finish_reason\":null,\"native_finish_reason\":null}]}", - "data: {\"id\":\"gen-1791215176-nzI6X3OkiLZMwgf1ZcZp\",\"object\":\"chat.completion.chunk\",\"created\":1791215176,\"model\":\"qwen/qwen3-30b-a3b\",\"provider\":\"DeepInfra\",\"choices\":[{\"index\":0,\"delta\":{\"content\":\"\",\"role\":\"assistant\",\"reasoning\":\" look\",\"reasoning_details\":[{\"type\":\"reasoning.text\",\"text\":\" look\",\"format\":\"unknown\",\"index\":0}]},\"finish_reason\":null,\"native_finish_reason\":null}]}", - "data: {\"id\":\"gen-1791215176-nzI6X3OkiLZMwgf1ZcZp\",\"object\":\"chat.completion.chunk\",\"created\":1791215176,\"model\":\"qwen/qwen3-30b-a3b\",\"provider\":\"DeepInfra\",\"choices\":[{\"index\":0,\"delta\":{\"content\":\"\",\"role\":\"assistant\",\"reasoning\":\" up\",\"reasoning_details\":[{\"type\":\"reasoning.text\",\"text\":\" up\",\"format\":\"unknown\",\"index\":0}]},\"finish_reason\":null,\"native_finish_reason\":null}]}", - "data: {\"id\":\"gen-1791215176-nzI6X3OkiLZMwgf1ZcZp\",\"object\":\"chat.completion.chunk\",\"created\":1791215176,\"model\":\"qwen/qwen3-30b-a3b\",\"provider\":\"DeepInfra\",\"choices\":[{\"index\":0,\"delta\":{\"content\":\"\",\"role\":\"assistant\",\"reasoning\":\" the\",\"reasoning_details\":[{\"type\":\"reasoning.text\",\"text\":\" the\",\"format\":\"unknown\",\"index\":0}]},\"finish_reason\":null,\"native_finish_reason\":null}]}", - "data: {\"id\":\"gen-1791215176-nzI6X3OkiLZMwgf1ZcZp\",\"object\":\"chat.completion.chunk\",\"created\":1791215176,\"model\":\"qwen/qwen3-30b-a3b\",\"provider\":\"DeepInfra\",\"choices\":[{\"index\":0,\"delta\":{\"content\":\"\",\"role\":\"assistant\",\"reasoning\":\" documentation\",\"reasoning_details\":[{\"type\":\"reasoning.text\",\"text\":\" documentation\",\"format\":\"unknown\",\"index\":0}]},\"finish_reason\":null,\"native_finish_reason\":null}]}", - "data: {\"id\":\"gen-1791215176-nzI6X3OkiLZMwgf1ZcZp\",\"object\":\"chat.completion.chunk\",\"created\":1791215176,\"model\":\"qwen/qwen3-30b-a3b\",\"provider\":\"DeepInfra\",\"choices\":[{\"index\":0,\"delta\":{\"content\":\"\",\"role\":\"assistant\",\"reasoning\":\" first\",\"reasoning_details\":[{\"type\":\"reasoning.text\",\"text\":\" first\",\"format\":\"unknown\",\"index\":0}]},\"finish_reason\":null,\"native_finish_reason\":null}]}", - "data: {\"id\":\"gen-1791215176-nzI6X3OkiLZMwgf1ZcZp\",\"object\":\"chat.completion.chunk\",\"created\":1791215176,\"model\":\"qwen/qwen3-30b-a3b\",\"provider\":\"DeepInfra\",\"choices\":[{\"index\":0,\"delta\":{\"content\":\"\",\"role\":\"assistant\",\"reasoning\":\".\",\"reasoning_details\":[{\"type\":\"reasoning.text\",\"text\":\".\",\"format\":\"unknown\",\"index\":0}]},\"finish_reason\":null,\"native_finish_reason\":null}]}", - "data: {\"id\":\"gen-1791215176-nzI6X3OkiLZMwgf1ZcZp\",\"object\":\"chat.completion.chunk\",\"created\":1791215176,\"model\":\"qwen/qwen3-30b-a3b\",\"provider\":\"DeepInfra\",\"choices\":[{\"index\":0,\"delta\":{\"content\":\"\",\"role\":\"assistant\",\"reasoning\":\" Let\",\"reasoning_details\":[{\"type\":\"reasoning.text\",\"text\":\" Let\",\"format\":\"unknown\",\"index\":0}]},\"finish_reason\":null,\"native_finish_reason\":null}]}", - "data: {\"id\":\"gen-1791215176-nzI6X3OkiLZMwgf1ZcZp\",\"object\":\"chat.completion.chunk\",\"created\":1791215176,\"model\":\"qwen/qwen3-30b-a3b\",\"provider\":\"DeepInfra\",\"choices\":[{\"index\":0,\"delta\":{\"content\":\"\",\"role\":\"assistant\",\"reasoning\":\" me\",\"reasoning_details\":[{\"type\":\"reasoning.text\",\"text\":\" me\",\"format\":\"unknown\",\"index\":0}]},\"finish_reason\":null,\"native_finish_reason\":null}]}", - "data: {\"id\":\"gen-1791215176-nzI6X3OkiLZMwgf1ZcZp\",\"object\":\"chat.completion.chunk\",\"created\":1791215176,\"model\":\"qwen/qwen3-30b-a3b\",\"provider\":\"DeepInfra\",\"choices\":[{\"index\":0,\"delta\":{\"content\":\"\",\"role\":\"assistant\",\"reasoning\":\" check\",\"reasoning_details\":[{\"type\":\"reasoning.text\",\"text\":\" check\",\"format\":\"unknown\",\"index\":0}]},\"finish_reason\":null,\"native_finish_reason\":null}]}", - "data: {\"id\":\"gen-1791215176-nzI6X3OkiLZMwgf1ZcZp\",\"object\":\"chat.completion.chunk\",\"created\":1791215176,\"model\":\"qwen/qwen3-30b-a3b\",\"provider\":\"DeepInfra\",\"choices\":[{\"index\":0,\"delta\":{\"content\":\"\",\"role\":\"assistant\",\"reasoning\":\" the\",\"reasoning_details\":[{\"type\":\"reasoning.text\",\"text\":\" the\",\"format\":\"unknown\",\"index\":0}]},\"finish_reason\":null,\"native_finish_reason\":null}]}", - "data: {\"id\":\"gen-1791215176-nzI6X3OkiLZMwgf1ZcZp\",\"object\":\"chat.completion.chunk\",\"created\":1791215176,\"model\":\"qwen/qwen3-30b-a3b\",\"provider\":\"DeepInfra\",\"choices\":[{\"index\":0,\"delta\":{\"content\":\"\",\"role\":\"assistant\",\"reasoning\":\" tools\",\"reasoning_details\":[{\"type\":\"reasoning.text\",\"text\":\" tools\",\"format\":\"unknown\",\"index\":0}]},\"finish_reason\":null,\"native_finish_reason\":null}]}", - "data: {\"id\":\"gen-1791215176-nzI6X3OkiLZMwgf1ZcZp\",\"object\":\"chat.completion.chunk\",\"created\":1791215176,\"model\":\"qwen/qwen3-30b-a3b\",\"provider\":\"DeepInfra\",\"choices\":[{\"index\":0,\"delta\":{\"content\":\"\",\"role\":\"assistant\",\"reasoning\":\" available\",\"reasoning_details\":[{\"type\":\"reasoning.text\",\"text\":\" available\",\"format\":\"unknown\",\"index\":0}]},\"finish_reason\":null,\"native_finish_reason\":null}]}", - "data: {\"id\":\"gen-1791215176-nzI6X3OkiLZMwgf1ZcZp\",\"object\":\"chat.completion.chunk\",\"created\":1791215176,\"model\":\"qwen/qwen3-30b-a3b\",\"provider\":\"DeepInfra\",\"choices\":[{\"index\":0,\"delta\":{\"content\":\"\",\"role\":\"assistant\",\"reasoning\":\".\",\"reasoning_details\":[{\"type\":\"reasoning.text\",\"text\":\".\",\"format\":\"unknown\",\"index\":0}]},\"finish_reason\":null,\"native_finish_reason\":null}]}", - "data: {\"id\":\"gen-1791215176-nzI6X3OkiLZMwgf1ZcZp\",\"object\":\"chat.completion.chunk\",\"created\":1791215176,\"model\":\"qwen/qwen3-30b-a3b\",\"provider\":\"DeepInfra\",\"choices\":[{\"index\":0,\"delta\":{\"content\":\"\",\"role\":\"assistant\",\"reasoning\":\" There\",\"reasoning_details\":[{\"type\":\"reasoning.text\",\"text\":\" There\",\"format\":\"unknown\",\"index\":0}]},\"finish_reason\":null,\"native_finish_reason\":null}]}", - "data: {\"id\":\"gen-1791215176-nzI6X3OkiLZMwgf1ZcZp\",\"object\":\"chat.completion.chunk\",\"created\":1791215176,\"model\":\"qwen/qwen3-30b-a3b\",\"provider\":\"DeepInfra\",\"choices\":[{\"index\":0,\"delta\":{\"content\":\"\",\"role\":\"assistant\",\"reasoning\":\"'s\",\"reasoning_details\":[{\"type\":\"reasoning.text\",\"text\":\"'s\",\"format\":\"unknown\",\"index\":0}]},\"finish_reason\":null,\"native_finish_reason\":null}]}", - "data: {\"id\":\"gen-1791215176-nzI6X3OkiLZMwgf1ZcZp\",\"object\":\"chat.completion.chunk\",\"created\":1791215176,\"model\":\"qwen/qwen3-30b-a3b\",\"provider\":\"DeepInfra\",\"choices\":[{\"index\":0,\"delta\":{\"content\":\"\",\"role\":\"assistant\",\"reasoning\":\" a\",\"reasoning_details\":[{\"type\":\"reasoning.text\",\"text\":\" a\",\"format\":\"unknown\",\"index\":0}]},\"finish_reason\":null,\"native_finish_reason\":null}]}", - "data: {\"id\":\"gen-1791215176-nzI6X3OkiLZMwgf1ZcZp\",\"object\":\"chat.completion.chunk\",\"created\":1791215176,\"model\":\"qwen/qwen3-30b-a3b\",\"provider\":\"DeepInfra\",\"choices\":[{\"index\":0,\"delta\":{\"content\":\"\",\"role\":\"assistant\",\"reasoning\":\" '\",\"reasoning_details\":[{\"type\":\"reasoning.text\",\"text\":\" '\",\"format\":\"unknown\",\"index\":0}]},\"finish_reason\":null,\"native_finish_reason\":null}]}", - "data: {\"id\":\"gen-1791215176-nzI6X3OkiLZMwgf1ZcZp\",\"object\":\"chat.completion.chunk\",\"created\":1791215176,\"model\":\"qwen/qwen3-30b-a3b\",\"provider\":\"DeepInfra\",\"choices\":[{\"index\":0,\"delta\":{\"content\":\"\",\"role\":\"assistant\",\"reasoning\":\"search\",\"reasoning_details\":[{\"type\":\"reasoning.text\",\"text\":\"search\",\"format\":\"unknown\",\"index\":0}]},\"finish_reason\":null,\"native_finish_reason\":null}]}", - "data: {\"id\":\"gen-1791215176-nzI6X3OkiLZMwgf1ZcZp\",\"object\":\"chat.completion.chunk\",\"created\":1791215176,\"model\":\"qwen/qwen3-30b-a3b\",\"provider\":\"DeepInfra\",\"choices\":[{\"index\":0,\"delta\":{\"content\":\"\",\"role\":\"assistant\",\"reasoning\":\"'\",\"reasoning_details\":[{\"type\":\"reasoning.text\",\"text\":\"'\",\"format\":\"unknown\",\"index\":0}]},\"finish_reason\":null,\"native_finish_reason\":null}]}", - "data: {\"id\":\"gen-1791215176-nzI6X3OkiLZMwgf1ZcZp\",\"object\":\"chat.completion.chunk\",\"created\":1791215176,\"model\":\"qwen/qwen3-30b-a3b\",\"provider\":\"DeepInfra\",\"choices\":[{\"index\":0,\"delta\":{\"content\":\"\",\"role\":\"assistant\",\"reasoning\":\" function\",\"reasoning_details\":[{\"type\":\"reasoning.text\",\"text\":\" function\",\"format\":\"unknown\",\"index\":0}]},\"finish_reason\":null,\"native_finish_reason\":null}]}", - "data: {\"id\":\"gen-1791215176-nzI6X3OkiLZMwgf1ZcZp\",\"object\":\"chat.completion.chunk\",\"created\":1791215176,\"model\":\"qwen/qwen3-30b-a3b\",\"provider\":\"DeepInfra\",\"choices\":[{\"index\":0,\"delta\":{\"content\":\"\",\"role\":\"assistant\",\"reasoning\":\" that\",\"reasoning_details\":[{\"type\":\"reasoning.text\",\"text\":\" that\",\"format\":\"unknown\",\"index\":0}]},\"finish_reason\":null,\"native_finish_reason\":null}]}", - "data: {\"id\":\"gen-1791215176-nzI6X3OkiLZMwgf1ZcZp\",\"object\":\"chat.completion.chunk\",\"created\":1791215176,\"model\":\"qwen/qwen3-30b-a3b\",\"provider\":\"DeepInfra\",\"choices\":[{\"index\":0,\"delta\":{\"content\":\"\",\"role\":\"assistant\",\"reasoning\":\" queries\",\"reasoning_details\":[{\"type\":\"reasoning.text\",\"text\":\" queries\",\"format\":\"unknown\",\"index\":0}]},\"finish_reason\":null,\"native_finish_reason\":null}]}", - "data: {\"id\":\"gen-1791215176-nzI6X3OkiLZMwgf1ZcZp\",\"object\":\"chat.completion.chunk\",\"created\":1791215176,\"model\":\"qwen/qwen3-30b-a3b\",\"provider\":\"DeepInfra\",\"choices\":[{\"index\":0,\"delta\":{\"content\":\"\",\"role\":\"assistant\",\"reasoning\":\" the\",\"reasoning_details\":[{\"type\":\"reasoning.text\",\"text\":\" the\",\"format\":\"unknown\",\"index\":0}]},\"finish_reason\":null,\"native_finish_reason\":null}]}", - "data: {\"id\":\"gen-1791215176-nzI6X3OkiLZMwgf1ZcZp\",\"object\":\"chat.completion.chunk\",\"created\":1791215176,\"model\":\"qwen/qwen3-30b-a3b\",\"provider\":\"DeepInfra\",\"choices\":[{\"index\":0,\"delta\":{\"content\":\"\",\"role\":\"assistant\",\"reasoning\":\" Eve\",\"reasoning_details\":[{\"type\":\"reasoning.text\",\"text\":\" Eve\",\"format\":\"unknown\",\"index\":0}]},\"finish_reason\":null,\"native_finish_reason\":null}]}", - "data: {\"id\":\"gen-1791215176-nzI6X3OkiLZMwgf1ZcZp\",\"object\":\"chat.completion.chunk\",\"created\":1791215176,\"model\":\"qwen/qwen3-30b-a3b\",\"provider\":\"DeepInfra\",\"choices\":[{\"index\":0,\"delta\":{\"content\":\"\",\"role\":\"assistant\",\"reasoning\":\" documentation\",\"reasoning_details\":[{\"type\":\"reasoning.text\",\"text\":\" documentation\",\"format\":\"unknown\",\"index\":0}]},\"finish_reason\":null,\"native_finish_reason\":null}]}", - "data: {\"id\":\"gen-1791215176-nzI6X3OkiLZMwgf1ZcZp\",\"object\":\"chat.completion.chunk\",\"created\":1791215176,\"model\":\"qwen/qwen3-30b-a3b\",\"provider\":\"DeepInfra\",\"choices\":[{\"index\":0,\"delta\":{\"content\":\"\",\"role\":\"assistant\",\"reasoning\":\".\",\"reasoning_details\":[{\"type\":\"reasoning.text\",\"text\":\".\",\"format\":\"unknown\",\"index\":0}]},\"finish_reason\":null,\"native_finish_reason\":null}]}", - "data: {\"id\":\"gen-1791215176-nzI6X3OkiLZMwgf1ZcZp\",\"object\":\"chat.completion.chunk\",\"created\":1791215176,\"model\":\"qwen/qwen3-30b-a3b\",\"provider\":\"DeepInfra\",\"choices\":[{\"index\":0,\"delta\":{\"content\":\"\",\"role\":\"assistant\",\"reasoning\":\" I\",\"reasoning_details\":[{\"type\":\"reasoning.text\",\"text\":\" I\",\"format\":\"unknown\",\"index\":0}]},\"finish_reason\":null,\"native_finish_reason\":null}]}", - "data: {\"id\":\"gen-1791215176-nzI6X3OkiLZMwgf1ZcZp\",\"object\":\"chat.completion.chunk\",\"created\":1791215176,\"model\":\"qwen/qwen3-30b-a3b\",\"provider\":\"DeepInfra\",\"choices\":[{\"index\":0,\"delta\":{\"content\":\"\",\"role\":\"assistant\",\"reasoning\":\" need\",\"reasoning_details\":[{\"type\":\"reasoning.text\",\"text\":\" need\",\"format\":\"unknown\",\"index\":0}]},\"finish_reason\":null,\"native_finish_reason\":null}]}", - "data: {\"id\":\"gen-1791215176-nzI6X3OkiLZMwgf1ZcZp\",\"object\":\"chat.completion.chunk\",\"created\":1791215176,\"model\":\"qwen/qwen3-30b-a3b\",\"provider\":\"DeepInfra\",\"choices\":[{\"index\":0,\"delta\":{\"content\":\"\",\"role\":\"assistant\",\"reasoning\":\" to\",\"reasoning_details\":[{\"type\":\"reasoning.text\",\"text\":\" to\",\"format\":\"unknown\",\"index\":0}]},\"finish_reason\":null,\"native_finish_reason\":null}]}", - "data: {\"id\":\"gen-1791215176-nzI6X3OkiLZMwgf1ZcZp\",\"object\":\"chat.completion.chunk\",\"created\":1791215176,\"model\":\"qwen/qwen3-30b-a3b\",\"provider\":\"DeepInfra\",\"choices\":[{\"index\":0,\"delta\":{\"content\":\"\",\"role\":\"assistant\",\"reasoning\":\" use\",\"reasoning_details\":[{\"type\":\"reasoning.text\",\"text\":\" use\",\"format\":\"unknown\",\"index\":0}]},\"finish_reason\":null,\"native_finish_reason\":null}]}", - "data: {\"id\":\"gen-1791215176-nzI6X3OkiLZMwgf1ZcZp\",\"object\":\"chat.completion.chunk\",\"created\":1791215176,\"model\":\"qwen/qwen3-30b-a3b\",\"provider\":\"DeepInfra\",\"choices\":[{\"index\":0,\"delta\":{\"content\":\"\",\"role\":\"assistant\",\"reasoning\":\" that\",\"reasoning_details\":[{\"type\":\"reasoning.text\",\"text\":\" that\",\"format\":\"unknown\",\"index\":0}]},\"finish_reason\":null,\"native_finish_reason\":null}]}", - "data: {\"id\":\"gen-1791215176-nzI6X3OkiLZMwgf1ZcZp\",\"object\":\"chat.completion.chunk\",\"created\":1791215176,\"model\":\"qwen/qwen3-30b-a3b\",\"provider\":\"DeepInfra\",\"choices\":[{\"index\":0,\"delta\":{\"content\":\"\",\"role\":\"assistant\",\"reasoning\":\".\",\"reasoning_details\":[{\"type\":\"reasoning.text\",\"text\":\".\",\"format\":\"unknown\",\"index\":0}]},\"finish_reason\":null,\"native_finish_reason\":null}]}", - "data: {\"id\":\"gen-1791215176-nzI6X3OkiLZMwgf1ZcZp\",\"object\":\"chat.completion.chunk\",\"created\":1791215176,\"model\":\"qwen/qwen3-30b-a3b\",\"provider\":\"DeepInfra\",\"choices\":[{\"index\":0,\"delta\":{\"content\":\"\",\"role\":\"assistant\",\"reasoning\":\" The\",\"reasoning_details\":[{\"type\":\"reasoning.text\",\"text\":\" The\",\"format\":\"unknown\",\"index\":0}]},\"finish_reason\":null,\"native_finish_reason\":null}]}", - "data: {\"id\":\"gen-1791215176-nzI6X3OkiLZMwgf1ZcZp\",\"object\":\"chat.completion.chunk\",\"created\":1791215176,\"model\":\"qwen/qwen3-30b-a3b\",\"provider\":\"DeepInfra\",\"choices\":[{\"index\":0,\"delta\":{\"content\":\"\",\"role\":\"assistant\",\"reasoning\":\" task\",\"reasoning_details\":[{\"type\":\"reasoning.text\",\"text\":\" task\",\"format\":\"unknown\",\"index\":0}]},\"finish_reason\":null,\"native_finish_reason\":null}]}", - "data: {\"id\":\"gen-1791215176-nzI6X3OkiLZMwgf1ZcZp\",\"object\":\"chat.completion.chunk\",\"created\":1791215176,\"model\":\"qwen/qwen3-30b-a3b\",\"provider\":\"DeepInfra\",\"choices\":[{\"index\":0,\"delta\":{\"content\":\"\",\"role\":\"assistant\",\"reasoning\":\" is\",\"reasoning_details\":[{\"type\":\"reasoning.text\",\"text\":\" is\",\"format\":\"unknown\",\"index\":0}]},\"finish_reason\":null,\"native_finish_reason\":null}]}", - "data: {\"id\":\"gen-1791215176-nzI6X3OkiLZMwgf1ZcZp\",\"object\":\"chat.completion.chunk\",\"created\":1791215176,\"model\":\"qwen/qwen3-30b-a3b\",\"provider\":\"DeepInfra\",\"choices\":[{\"index\":0,\"delta\":{\"content\":\"\",\"role\":\"assistant\",\"reasoning\":\" to\",\"reasoning_details\":[{\"type\":\"reasoning.text\",\"text\":\" to\",\"format\":\"unknown\",\"index\":0}]},\"finish_reason\":null,\"native_finish_reason\":null}]}", - "data: {\"id\":\"gen-1791215176-nzI6X3OkiLZMwgf1ZcZp\",\"object\":\"chat.completion.chunk\",\"created\":1791215176,\"model\":\"qwen/qwen3-30b-a3b\",\"provider\":\"DeepInfra\",\"choices\":[{\"index\":0,\"delta\":{\"content\":\"\",\"role\":\"assistant\",\"reasoning\":\" \\\"\",\"reasoning_details\":[{\"type\":\"reasoning.text\",\"text\":\" \\\"\",\"format\":\"unknown\",\"index\":0}]},\"finish_reason\":null,\"native_finish_reason\":null}]}", - "data: {\"id\":\"gen-1791215176-nzI6X3OkiLZMwgf1ZcZp\",\"object\":\"chat.completion.chunk\",\"created\":1791215176,\"model\":\"qwen/qwen3-30b-a3b\",\"provider\":\"DeepInfra\",\"choices\":[{\"index\":0,\"delta\":{\"content\":\"\",\"role\":\"assistant\",\"reasoning\":\"Run\",\"reasoning_details\":[{\"type\":\"reasoning.text\",\"text\":\"Run\",\"format\":\"unknown\",\"index\":0}]},\"finish_reason\":null,\"native_finish_reason\":null}]}", - "data: {\"id\":\"gen-1791215176-nzI6X3OkiLZMwgf1ZcZp\",\"object\":\"chat.completion.chunk\",\"created\":1791215176,\"model\":\"qwen/qwen3-30b-a3b\",\"provider\":\"DeepInfra\",\"choices\":[{\"index\":0,\"delta\":{\"content\":\"\",\"role\":\"assistant\",\"reasoning\":\" the\",\"reasoning_details\":[{\"type\":\"reasoning.text\",\"text\":\" the\",\"format\":\"unknown\",\"index\":0}]},\"finish_reason\":null,\"native_finish_reason\":null}]}", - "data: {\"id\":\"gen-1791215176-nzI6X3OkiLZMwgf1ZcZp\",\"object\":\"chat.completion.chunk\",\"created\":1791215176,\"model\":\"qwen/qwen3-30b-a3b\",\"provider\":\"DeepInfra\",\"choices\":[{\"index\":0,\"delta\":{\"content\":\"\",\"role\":\"assistant\",\"reasoning\":\" Brain\",\"reasoning_details\":[{\"type\":\"reasoning.text\",\"text\":\" Brain\",\"format\":\"unknown\",\"index\":0}]},\"finish_reason\":null,\"native_finish_reason\":null}]}", - "data: {\"id\":\"gen-1791215176-nzI6X3OkiLZMwgf1ZcZp\",\"object\":\"chat.completion.chunk\",\"created\":1791215176,\"model\":\"qwen/qwen3-30b-a3b\",\"provider\":\"DeepInfra\",\"choices\":[{\"index\":0,\"delta\":{\"content\":\"\",\"role\":\"assistant\",\"reasoning\":\"trust\",\"reasoning_details\":[{\"type\":\"reasoning.text\",\"text\":\"trust\",\"format\":\"unknown\",\"index\":0}]},\"finish_reason\":null,\"native_finish_reason\":null}]}", - "data: {\"id\":\"gen-1791215176-nzI6X3OkiLZMwgf1ZcZp\",\"object\":\"chat.completion.chunk\",\"created\":1791215176,\"model\":\"qwen/qwen3-30b-a3b\",\"provider\":\"DeepInfra\",\"choices\":[{\"index\":0,\"delta\":{\"content\":\"\",\"role\":\"assistant\",\"reasoning\":\" Eve\",\"reasoning_details\":[{\"type\":\"reasoning.text\",\"text\":\" Eve\",\"format\":\"unknown\",\"index\":0}]},\"finish_reason\":null,\"native_finish_reason\":null}]}", - "data: {\"id\":\"gen-1791215176-nzI6X3OkiLZMwgf1ZcZp\",\"object\":\"chat.completion.chunk\",\"created\":1791215176,\"model\":\"qwen/qwen3-30b-a3b\",\"provider\":\"DeepInfra\",\"choices\":[{\"index\":0,\"delta\":{\"content\":\"\",\"role\":\"assistant\",\"reasoning\":\" instrumentation\",\"reasoning_details\":[{\"type\":\"reasoning.text\",\"text\":\" instrumentation\",\"format\":\"unknown\",\"index\":0}]},\"finish_reason\":null,\"native_finish_reason\":null}]}", - "data: {\"id\":\"gen-1791215176-nzI6X3OkiLZMwgf1ZcZp\",\"object\":\"chat.completion.chunk\",\"created\":1791215176,\"model\":\"qwen/qwen3-30b-a3b\",\"provider\":\"DeepInfra\",\"choices\":[{\"index\":0,\"delta\":{\"content\":\"\",\"role\":\"assistant\",\"reasoning\":\" e\",\"reasoning_details\":[{\"type\":\"reasoning.text\",\"text\":\" e\",\"format\":\"unknown\",\"index\":0}]},\"finish_reason\":null,\"native_finish_reason\":null}]}", - "data: {\"id\":\"gen-1791215176-nzI6X3OkiLZMwgf1ZcZp\",\"object\":\"chat.completion.chunk\",\"created\":1791215176,\"model\":\"qwen/qwen3-30b-a3b\",\"provider\":\"DeepInfra\",\"choices\":[{\"index\":0,\"delta\":{\"content\":\"\",\"role\":\"assistant\",\"reasoning\":\"2\",\"reasoning_details\":[{\"type\":\"reasoning.text\",\"text\":\"2\",\"format\":\"unknown\",\"index\":0}]},\"finish_reason\":null,\"native_finish_reason\":null}]}", - "data: {\"id\":\"gen-1791215176-nzI6X3OkiLZMwgf1ZcZp\",\"object\":\"chat.completion.chunk\",\"created\":1791215176,\"model\":\"qwen/qwen3-30b-a3b\",\"provider\":\"DeepInfra\",\"choices\":[{\"index\":0,\"delta\":{\"content\":\"\",\"role\":\"assistant\",\"reasoning\":\"e\",\"reasoning_details\":[{\"type\":\"reasoning.text\",\"text\":\"e\",\"format\":\"unknown\",\"index\":0}]},\"finish_reason\":null,\"native_finish_reason\":null}]}", - "data: {\"id\":\"gen-1791215176-nzI6X3OkiLZMwgf1ZcZp\",\"object\":\"chat.completion.chunk\",\"created\":1791215176,\"model\":\"qwen/qwen3-30b-a3b\",\"provider\":\"DeepInfra\",\"choices\":[{\"index\":0,\"delta\":{\"content\":\"\",\"role\":\"assistant\",\"reasoning\":\" scenario\",\"reasoning_details\":[{\"type\":\"reasoning.text\",\"text\":\" scenario\",\"format\":\"unknown\",\"index\":0}]},\"finish_reason\":null,\"native_finish_reason\":null}]}", - "data: {\"id\":\"gen-1791215176-nzI6X3OkiLZMwgf1ZcZp\",\"object\":\"chat.completion.chunk\",\"created\":1791215176,\"model\":\"qwen/qwen3-30b-a3b\",\"provider\":\"DeepInfra\",\"choices\":[{\"index\":0,\"delta\":{\"content\":\"\",\"role\":\"assistant\",\"reasoning\":\" again\",\"reasoning_details\":[{\"type\":\"reasoning.text\",\"text\":\" again\",\"format\":\"unknown\",\"index\":0}]},\"finish_reason\":null,\"native_finish_reason\":null}]}", - "data: {\"id\":\"gen-1791215176-nzI6X3OkiLZMwgf1ZcZp\",\"object\":\"chat.completion.chunk\",\"created\":1791215176,\"model\":\"qwen/qwen3-30b-a3b\",\"provider\":\"DeepInfra\",\"choices\":[{\"index\":0,\"delta\":{\"content\":\"\",\"role\":\"assistant\",\"reasoning\":\",\\\"\",\"reasoning_details\":[{\"type\":\"reasoning.text\",\"text\":\",\\\"\",\"format\":\"unknown\",\"index\":0}]},\"finish_reason\":null,\"native_finish_reason\":null}]}", - "data: {\"id\":\"gen-1791215176-nzI6X3OkiLZMwgf1ZcZp\",\"object\":\"chat.completion.chunk\",\"created\":1791215176,\"model\":\"qwen/qwen3-30b-a3b\",\"provider\":\"DeepInfra\",\"choices\":[{\"index\":0,\"delta\":{\"content\":\"\",\"role\":\"assistant\",\"reasoning\":\" so\",\"reasoning_details\":[{\"type\":\"reasoning.text\",\"text\":\" so\",\"format\":\"unknown\",\"index\":0}]},\"finish_reason\":null,\"native_finish_reason\":null}]}", - "data: {\"id\":\"gen-1791215176-nzI6X3OkiLZMwgf1ZcZp\",\"object\":\"chat.completion.chunk\",\"created\":1791215176,\"model\":\"qwen/qwen3-30b-a3b\",\"provider\":\"DeepInfra\",\"choices\":[{\"index\":0,\"delta\":{\"content\":\"\",\"role\":\"assistant\",\"reasoning\":\" I\",\"reasoning_details\":[{\"type\":\"reasoning.text\",\"text\":\" I\",\"format\":\"unknown\",\"index\":0}]},\"finish_reason\":null,\"native_finish_reason\":null}]}", - "data: {\"id\":\"gen-1791215176-nzI6X3OkiLZMwgf1ZcZp\",\"object\":\"chat.completion.chunk\",\"created\":1791215176,\"model\":\"qwen/qwen3-30b-a3b\",\"provider\":\"DeepInfra\",\"choices\":[{\"index\":0,\"delta\":{\"content\":\"\",\"role\":\"assistant\",\"reasoning\":\"'ll\",\"reasoning_details\":[{\"type\":\"reasoning.text\",\"text\":\"'ll\",\"format\":\"unknown\",\"index\":0}]},\"finish_reason\":null,\"native_finish_reason\":null}]}", - "data: {\"id\":\"gen-1791215176-nzI6X3OkiLZMwgf1ZcZp\",\"object\":\"chat.completion.chunk\",\"created\":1791215176,\"model\":\"qwen/qwen3-30b-a3b\",\"provider\":\"DeepInfra\",\"choices\":[{\"index\":0,\"delta\":{\"content\":\"\",\"role\":\"assistant\",\"reasoning\":\" use\",\"reasoning_details\":[{\"type\":\"reasoning.text\",\"text\":\" use\",\"format\":\"unknown\",\"index\":0}]},\"finish_reason\":null,\"native_finish_reason\":null}]}", - "data: {\"id\":\"gen-1791215176-nzI6X3OkiLZMwgf1ZcZp\",\"object\":\"chat.completion.chunk\",\"created\":1791215176,\"model\":\"qwen/qwen3-30b-a3b\",\"provider\":\"DeepInfra\",\"choices\":[{\"index\":0,\"delta\":{\"content\":\"\",\"role\":\"assistant\",\"reasoning\":\" that\",\"reasoning_details\":[{\"type\":\"reasoning.text\",\"text\":\" that\",\"format\":\"unknown\",\"index\":0}]},\"finish_reason\":null,\"native_finish_reason\":null}]}", - "data: {\"id\":\"gen-1791215176-nzI6X3OkiLZMwgf1ZcZp\",\"object\":\"chat.completion.chunk\",\"created\":1791215176,\"model\":\"qwen/qwen3-30b-a3b\",\"provider\":\"DeepInfra\",\"choices\":[{\"index\":0,\"delta\":{\"content\":\"\",\"role\":\"assistant\",\"reasoning\":\" exact\",\"reasoning_details\":[{\"type\":\"reasoning.text\",\"text\":\" exact\",\"format\":\"unknown\",\"index\":0}]},\"finish_reason\":null,\"native_finish_reason\":null}]}", - "data: {\"id\":\"gen-1791215176-nzI6X3OkiLZMwgf1ZcZp\",\"object\":\"chat.completion.chunk\",\"created\":1791215176,\"model\":\"qwen/qwen3-30b-a3b\",\"provider\":\"DeepInfra\",\"choices\":[{\"index\":0,\"delta\":{\"content\":\"\",\"role\":\"assistant\",\"reasoning\":\" query\",\"reasoning_details\":[{\"type\":\"reasoning.text\",\"text\":\" query\",\"format\":\"unknown\",\"index\":0}]},\"finish_reason\":null,\"native_finish_reason\":null}]}", - "data: {\"id\":\"gen-1791215176-nzI6X3OkiLZMwgf1ZcZp\",\"object\":\"chat.completion.chunk\",\"created\":1791215176,\"model\":\"qwen/qwen3-30b-a3b\",\"provider\":\"DeepInfra\",\"choices\":[{\"index\":0,\"delta\":{\"content\":\"\",\"role\":\"assistant\",\"reasoning\":\" for\",\"reasoning_details\":[{\"type\":\"reasoning.text\",\"text\":\" for\",\"format\":\"unknown\",\"index\":0}]},\"finish_reason\":null,\"native_finish_reason\":null}]}", - "data: {\"id\":\"gen-1791215176-nzI6X3OkiLZMwgf1ZcZp\",\"object\":\"chat.completion.chunk\",\"created\":1791215176,\"model\":\"qwen/qwen3-30b-a3b\",\"provider\":\"DeepInfra\",\"choices\":[{\"index\":0,\"delta\":{\"content\":\"\",\"role\":\"assistant\",\"reasoning\":\" the\",\"reasoning_details\":[{\"type\":\"reasoning.text\",\"text\":\" the\",\"format\":\"unknown\",\"index\":0}]},\"finish_reason\":null,\"native_finish_reason\":null}]}", - "data: {\"id\":\"gen-1791215176-nzI6X3OkiLZMwgf1ZcZp\",\"object\":\"chat.completion.chunk\",\"created\":1791215176,\"model\":\"qwen/qwen3-30b-a3b\",\"provider\":\"DeepInfra\",\"choices\":[{\"index\":0,\"delta\":{\"content\":\"\",\"role\":\"assistant\",\"reasoning\":\" search\",\"reasoning_details\":[{\"type\":\"reasoning.text\",\"text\":\" search\",\"format\":\"unknown\",\"index\":0}]},\"finish_reason\":null,\"native_finish_reason\":null}]}", - "data: {\"id\":\"gen-1791215176-nzI6X3OkiLZMwgf1ZcZp\",\"object\":\"chat.completion.chunk\",\"created\":1791215176,\"model\":\"qwen/qwen3-30b-a3b\",\"provider\":\"DeepInfra\",\"choices\":[{\"index\":0,\"delta\":{\"content\":\"\",\"role\":\"assistant\",\"reasoning\":\".\",\"reasoning_details\":[{\"type\":\"reasoning.text\",\"text\":\".\",\"format\":\"unknown\",\"index\":0}]},\"finish_reason\":null,\"native_finish_reason\":null}]}", - "data: {\"id\":\"gen-1791215176-nzI6X3OkiLZMwgf1ZcZp\",\"object\":\"chat.completion.chunk\",\"created\":1791215176,\"model\":\"qwen/qwen3-30b-a3b\",\"provider\":\"DeepInfra\",\"choices\":[{\"index\":0,\"delta\":{\"content\":\"\",\"role\":\"assistant\",\"reasoning\":\" I\",\"reasoning_details\":[{\"type\":\"reasoning.text\",\"text\":\" I\",\"format\":\"unknown\",\"index\":0}]},\"finish_reason\":null,\"native_finish_reason\":null}]}", - "data: {\"id\":\"gen-1791215176-nzI6X3OkiLZMwgf1ZcZp\",\"object\":\"chat.completion.chunk\",\"created\":1791215176,\"model\":\"qwen/qwen3-30b-a3b\",\"provider\":\"DeepInfra\",\"choices\":[{\"index\":0,\"delta\":{\"content\":\"\",\"role\":\"assistant\",\"reasoning\":\"'ll\",\"reasoning_details\":[{\"type\":\"reasoning.text\",\"text\":\"'ll\",\"format\":\"unknown\",\"index\":0}]},\"finish_reason\":null,\"native_finish_reason\":null}]}", - "data: {\"id\":\"gen-1791215176-nzI6X3OkiLZMwgf1ZcZp\",\"object\":\"chat.completion.chunk\",\"created\":1791215176,\"model\":\"qwen/qwen3-30b-a3b\",\"provider\":\"DeepInfra\",\"choices\":[{\"index\":0,\"delta\":{\"content\":\"\",\"role\":\"assistant\",\"reasoning\":\" make\",\"reasoning_details\":[{\"type\":\"reasoning.text\",\"text\":\" make\",\"format\":\"unknown\",\"index\":0}]},\"finish_reason\":null,\"native_finish_reason\":null}]}", - "data: {\"id\":\"gen-1791215176-nzI6X3OkiLZMwgf1ZcZp\",\"object\":\"chat.completion.chunk\",\"created\":1791215176,\"model\":\"qwen/qwen3-30b-a3b\",\"provider\":\"DeepInfra\",\"choices\":[{\"index\":0,\"delta\":{\"content\":\"\",\"role\":\"assistant\",\"reasoning\":\" sure\",\"reasoning_details\":[{\"type\":\"reasoning.text\",\"text\":\" sure\",\"format\":\"unknown\",\"index\":0}]},\"finish_reason\":null,\"native_finish_reason\":null}]}", - "data: {\"id\":\"gen-1791215176-nzI6X3OkiLZMwgf1ZcZp\",\"object\":\"chat.completion.chunk\",\"created\":1791215176,\"model\":\"qwen/qwen3-30b-a3b\",\"provider\":\"DeepInfra\",\"choices\":[{\"index\":0,\"delta\":{\"content\":\"\",\"role\":\"assistant\",\"reasoning\":\" to\",\"reasoning_details\":[{\"type\":\"reasoning.text\",\"text\":\" to\",\"format\":\"unknown\",\"index\":0}]},\"finish_reason\":null,\"native_finish_reason\":null}]}", - "data: {\"id\":\"gen-1791215176-nzI6X3OkiLZMwgf1ZcZp\",\"object\":\"chat.completion.chunk\",\"created\":1791215176,\"model\":\"qwen/qwen3-30b-a3b\",\"provider\":\"DeepInfra\",\"choices\":[{\"index\":0,\"delta\":{\"content\":\"\",\"role\":\"assistant\",\"reasoning\":\" call\",\"reasoning_details\":[{\"type\":\"reasoning.text\",\"text\":\" call\",\"format\":\"unknown\",\"index\":0}]},\"finish_reason\":null,\"native_finish_reason\":null}]}", - "data: {\"id\":\"gen-1791215176-nzI6X3OkiLZMwgf1ZcZp\",\"object\":\"chat.completion.chunk\",\"created\":1791215176,\"model\":\"qwen/qwen3-30b-a3b\",\"provider\":\"DeepInfra\",\"choices\":[{\"index\":0,\"delta\":{\"content\":\"\",\"role\":\"assistant\",\"reasoning\":\" the\",\"reasoning_details\":[{\"type\":\"reasoning.text\",\"text\":\" the\",\"format\":\"unknown\",\"index\":0}]},\"finish_reason\":null,\"native_finish_reason\":null}]}", - "data: {\"id\":\"gen-1791215176-nzI6X3OkiLZMwgf1ZcZp\",\"object\":\"chat.completion.chunk\",\"created\":1791215176,\"model\":\"qwen/qwen3-30b-a3b\",\"provider\":\"DeepInfra\",\"choices\":[{\"index\":0,\"delta\":{\"content\":\"\",\"role\":\"assistant\",\"reasoning\":\" search\",\"reasoning_details\":[{\"type\":\"reasoning.text\",\"text\":\" search\",\"format\":\"unknown\",\"index\":0}]},\"finish_reason\":null,\"native_finish_reason\":null}]}", - "data: {\"id\":\"gen-1791215176-nzI6X3OkiLZMwgf1ZcZp\",\"object\":\"chat.completion.chunk\",\"created\":1791215176,\"model\":\"qwen/qwen3-30b-a3b\",\"provider\":\"DeepInfra\",\"choices\":[{\"index\":0,\"delta\":{\"content\":\"\",\"role\":\"assistant\",\"reasoning\":\" tool\",\"reasoning_details\":[{\"type\":\"reasoning.text\",\"text\":\" tool\",\"format\":\"unknown\",\"index\":0}]},\"finish_reason\":null,\"native_finish_reason\":null}]}", - "data: {\"id\":\"gen-1791215176-nzI6X3OkiLZMwgf1ZcZp\",\"object\":\"chat.completion.chunk\",\"created\":1791215176,\"model\":\"qwen/qwen3-30b-a3b\",\"provider\":\"DeepInfra\",\"choices\":[{\"index\":0,\"delta\":{\"content\":\"\",\"role\":\"assistant\",\"reasoning\":\" once\",\"reasoning_details\":[{\"type\":\"reasoning.text\",\"text\":\" once\",\"format\":\"unknown\",\"index\":0}]},\"finish_reason\":null,\"native_finish_reason\":null}]}", - "data: {\"id\":\"gen-1791215176-nzI6X3OkiLZMwgf1ZcZp\",\"object\":\"chat.completion.chunk\",\"created\":1791215176,\"model\":\"qwen/qwen3-30b-a3b\",\"provider\":\"DeepInfra\",\"choices\":[{\"index\":0,\"delta\":{\"content\":\"\",\"role\":\"assistant\",\"reasoning\":\" with\",\"reasoning_details\":[{\"type\":\"reasoning.text\",\"text\":\" with\",\"format\":\"unknown\",\"index\":0}]},\"finish_reason\":null,\"native_finish_reason\":null}]}", - "data: {\"id\":\"gen-1791215176-nzI6X3OkiLZMwgf1ZcZp\",\"object\":\"chat.completion.chunk\",\"created\":1791215176,\"model\":\"qwen/qwen3-30b-a3b\",\"provider\":\"DeepInfra\",\"choices\":[{\"index\":0,\"delta\":{\"content\":\"\",\"role\":\"assistant\",\"reasoning\":\" the\",\"reasoning_details\":[{\"type\":\"reasoning.text\",\"text\":\" the\",\"format\":\"unknown\",\"index\":0}]},\"finish_reason\":null,\"native_finish_reason\":null}]}", - "data: {\"id\":\"gen-1791215176-nzI6X3OkiLZMwgf1ZcZp\",\"object\":\"chat.completion.chunk\",\"created\":1791215176,\"model\":\"qwen/qwen3-30b-a3b\",\"provider\":\"DeepInfra\",\"choices\":[{\"index\":0,\"delta\":{\"content\":\"\",\"role\":\"assistant\",\"reasoning\":\" full\",\"reasoning_details\":[{\"type\":\"reasoning.text\",\"text\":\" full\",\"format\":\"unknown\",\"index\":0}]},\"finish_reason\":null,\"native_finish_reason\":null}]}", - "data: {\"id\":\"gen-1791215176-nzI6X3OkiLZMwgf1ZcZp\",\"object\":\"chat.completion.chunk\",\"created\":1791215176,\"model\":\"qwen/qwen3-30b-a3b\",\"provider\":\"DeepInfra\",\"choices\":[{\"index\":0,\"delta\":{\"content\":\"\",\"role\":\"assistant\",\"reasoning\":\" task\",\"reasoning_details\":[{\"type\":\"reasoning.text\",\"text\":\" task\",\"format\":\"unknown\",\"index\":0}]},\"finish_reason\":null,\"native_finish_reason\":null}]}", - "data: {\"id\":\"gen-1791215176-nzI6X3OkiLZMwgf1ZcZp\",\"object\":\"chat.completion.chunk\",\"created\":1791215176,\"model\":\"qwen/qwen3-30b-a3b\",\"provider\":\"DeepInfra\",\"choices\":[{\"index\":0,\"delta\":{\"content\":\"\",\"role\":\"assistant\",\"reasoning\":\" as\",\"reasoning_details\":[{\"type\":\"reasoning.text\",\"text\":\" as\",\"format\":\"unknown\",\"index\":0}]},\"finish_reason\":null,\"native_finish_reason\":null}]}", - "data: {\"id\":\"gen-1791215176-nzI6X3OkiLZMwgf1ZcZp\",\"object\":\"chat.completion.chunk\",\"created\":1791215176,\"model\":\"qwen/qwen3-30b-a3b\",\"provider\":\"DeepInfra\",\"choices\":[{\"index\":0,\"delta\":{\"content\":\"\",\"role\":\"assistant\",\"reasoning\":\" the\",\"reasoning_details\":[{\"type\":\"reasoning.text\",\"text\":\" the\",\"format\":\"unknown\",\"index\":0}]},\"finish_reason\":null,\"native_finish_reason\":null}]}", - "data: {\"id\":\"gen-1791215176-nzI6X3OkiLZMwgf1ZcZp\",\"object\":\"chat.completion.chunk\",\"created\":1791215176,\"model\":\"qwen/qwen3-30b-a3b\",\"provider\":\"DeepInfra\",\"choices\":[{\"index\":0,\"delta\":{\"content\":\"\",\"role\":\"assistant\",\"reasoning\":\" query\",\"reasoning_details\":[{\"type\":\"reasoning.text\",\"text\":\" query\",\"format\":\"unknown\",\"index\":0}]},\"finish_reason\":null,\"native_finish_reason\":null}]}", - "data: {\"id\":\"gen-1791215176-nzI6X3OkiLZMwgf1ZcZp\",\"object\":\"chat.completion.chunk\",\"created\":1791215176,\"model\":\"qwen/qwen3-30b-a3b\",\"provider\":\"DeepInfra\",\"choices\":[{\"index\":0,\"delta\":{\"content\":\"\",\"role\":\"assistant\",\"reasoning\":\".\",\"reasoning_details\":[{\"type\":\"reasoning.text\",\"text\":\".\",\"format\":\"unknown\",\"index\":0}]},\"finish_reason\":null,\"native_finish_reason\":null}]}", - "data: {\"id\":\"gen-1791215176-nzI6X3OkiLZMwgf1ZcZp\",\"object\":\"chat.completion.chunk\",\"created\":1791215176,\"model\":\"qwen/qwen3-30b-a3b\",\"provider\":\"DeepInfra\",\"choices\":[{\"index\":0,\"delta\":{\"content\":\"\",\"role\":\"assistant\",\"reasoning\":\" Then\",\"reasoning_details\":[{\"type\":\"reasoning.text\",\"text\":\" Then\",\"format\":\"unknown\",\"index\":0}]},\"finish_reason\":null,\"native_finish_reason\":null}]}", - "data: {\"id\":\"gen-1791215176-nzI6X3OkiLZMwgf1ZcZp\",\"object\":\"chat.completion.chunk\",\"created\":1791215176,\"model\":\"qwen/qwen3-30b-a3b\",\"provider\":\"DeepInfra\",\"choices\":[{\"index\":0,\"delta\":{\"content\":\"\",\"role\":\"assistant\",\"reasoning\":\",\",\"reasoning_details\":[{\"type\":\"reasoning.text\",\"text\":\",\",\"format\":\"unknown\",\"index\":0}]},\"finish_reason\":null,\"native_finish_reason\":null}]}", - "data: {\"id\":\"gen-1791215176-nzI6X3OkiLZMwgf1ZcZp\",\"object\":\"chat.completion.chunk\",\"created\":1791215176,\"model\":\"qwen/qwen3-30b-a3b\",\"provider\":\"DeepInfra\",\"choices\":[{\"index\":0,\"delta\":{\"content\":\"\",\"role\":\"assistant\",\"reasoning\":\" once\",\"reasoning_details\":[{\"type\":\"reasoning.text\",\"text\":\" once\",\"format\":\"unknown\",\"index\":0}]},\"finish_reason\":null,\"native_finish_reason\":null}]}", - "data: {\"id\":\"gen-1791215176-nzI6X3OkiLZMwgf1ZcZp\",\"object\":\"chat.completion.chunk\",\"created\":1791215176,\"model\":\"qwen/qwen3-30b-a3b\",\"provider\":\"DeepInfra\",\"choices\":[{\"index\":0,\"delta\":{\"content\":\"\",\"role\":\"assistant\",\"reasoning\":\" I\",\"reasoning_details\":[{\"type\":\"reasoning.text\",\"text\":\" I\",\"format\":\"unknown\",\"index\":0}]},\"finish_reason\":null,\"native_finish_reason\":null}]}", - "data: {\"id\":\"gen-1791215176-nzI6X3OkiLZMwgf1ZcZp\",\"object\":\"chat.completion.chunk\",\"created\":1791215176,\"model\":\"qwen/qwen3-30b-a3b\",\"provider\":\"DeepInfra\",\"choices\":[{\"index\":0,\"delta\":{\"content\":\"\",\"role\":\"assistant\",\"reasoning\":\" get\",\"reasoning_details\":[{\"type\":\"reasoning.text\",\"text\":\" get\",\"format\":\"unknown\",\"index\":0}]},\"finish_reason\":null,\"native_finish_reason\":null}]}", - "data: {\"id\":\"gen-1791215176-nzI6X3OkiLZMwgf1ZcZp\",\"object\":\"chat.completion.chunk\",\"created\":1791215176,\"model\":\"qwen/qwen3-30b-a3b\",\"provider\":\"DeepInfra\",\"choices\":[{\"index\":0,\"delta\":{\"content\":\"\",\"role\":\"assistant\",\"reasoning\":\" the\",\"reasoning_details\":[{\"type\":\"reasoning.text\",\"text\":\" the\",\"format\":\"unknown\",\"index\":0}]},\"finish_reason\":null,\"native_finish_reason\":null}]}", - "data: {\"id\":\"gen-1791215176-nzI6X3OkiLZMwgf1ZcZp\",\"object\":\"chat.completion.chunk\",\"created\":1791215176,\"model\":\"qwen/qwen3-30b-a3b\",\"provider\":\"DeepInfra\",\"choices\":[{\"index\":0,\"delta\":{\"content\":\"\",\"role\":\"assistant\",\"reasoning\":\" results\",\"reasoning_details\":[{\"type\":\"reasoning.text\",\"text\":\" results\",\"format\":\"unknown\",\"index\":0}]},\"finish_reason\":null,\"native_finish_reason\":null}]}", - "data: {\"id\":\"gen-1791215176-nzI6X3OkiLZMwgf1ZcZp\",\"object\":\"chat.completion.chunk\",\"created\":1791215176,\"model\":\"qwen/qwen3-30b-a3b\",\"provider\":\"DeepInfra\",\"choices\":[{\"index\":0,\"delta\":{\"content\":\"\",\"role\":\"assistant\",\"reasoning\":\",\",\"reasoning_details\":[{\"type\":\"reasoning.text\",\"text\":\",\",\"format\":\"unknown\",\"index\":0}]},\"finish_reason\":null,\"native_finish_reason\":null}]}", - "data: {\"id\":\"gen-1791215176-nzI6X3OkiLZMwgf1ZcZp\",\"object\":\"chat.completion.chunk\",\"created\":1791215176,\"model\":\"qwen/qwen3-30b-a3b\",\"provider\":\"DeepInfra\",\"choices\":[{\"index\":0,\"delta\":{\"content\":\"\",\"role\":\"assistant\",\"reasoning\":\" I\",\"reasoning_details\":[{\"type\":\"reasoning.text\",\"text\":\" I\",\"format\":\"unknown\",\"index\":0}]},\"finish_reason\":null,\"native_finish_reason\":null}]}", - "data: {\"id\":\"gen-1791215176-nzI6X3OkiLZMwgf1ZcZp\",\"object\":\"chat.completion.chunk\",\"created\":1791215176,\"model\":\"qwen/qwen3-30b-a3b\",\"provider\":\"DeepInfra\",\"choices\":[{\"index\":0,\"delta\":{\"content\":\"\",\"role\":\"assistant\",\"reasoning\":\"'ll\",\"reasoning_details\":[{\"type\":\"reasoning.text\",\"text\":\"'ll\",\"format\":\"unknown\",\"index\":0}]},\"finish_reason\":null,\"native_finish_reason\":null}]}", - "data: {\"id\":\"gen-1791215176-nzI6X3OkiLZMwgf1ZcZp\",\"object\":\"chat.completion.chunk\",\"created\":1791215176,\"model\":\"qwen/qwen3-30b-a3b\",\"provider\":\"DeepInfra\",\"choices\":[{\"index\":0,\"delta\":{\"content\":\"\",\"role\":\"assistant\",\"reasoning\":\" format\",\"reasoning_details\":[{\"type\":\"reasoning.text\",\"text\":\" format\",\"format\":\"unknown\",\"index\":0}]},\"finish_reason\":null,\"native_finish_reason\":null}]}", - "data: {\"id\":\"gen-1791215176-nzI6X3OkiLZMwgf1ZcZp\",\"object\":\"chat.completion.chunk\",\"created\":1791215176,\"model\":\"qwen/qwen3-30b-a3b\",\"provider\":\"DeepInfra\",\"choices\":[{\"index\":0,\"delta\":{\"content\":\"\",\"role\":\"assistant\",\"reasoning\":\" the\",\"reasoning_details\":[{\"type\":\"reasoning.text\",\"text\":\" the\",\"format\":\"unknown\",\"index\":0}]},\"finish_reason\":null,\"native_finish_reason\":null}]}", - "data: {\"id\":\"gen-1791215176-nzI6X3OkiLZMwgf1ZcZp\",\"object\":\"chat.completion.chunk\",\"created\":1791215176,\"model\":\"qwen/qwen3-30b-a3b\",\"provider\":\"DeepInfra\",\"choices\":[{\"index\":0,\"delta\":{\"content\":\"\",\"role\":\"assistant\",\"reasoning\":\" answer\",\"reasoning_details\":[{\"type\":\"reasoning.text\",\"text\":\" answer\",\"format\":\"unknown\",\"index\":0}]},\"finish_reason\":null,\"native_finish_reason\":null}]}", - "data: {\"id\":\"gen-1791215176-nzI6X3OkiLZMwgf1ZcZp\",\"object\":\"chat.completion.chunk\",\"created\":1791215176,\"model\":\"qwen/qwen3-30b-a3b\",\"provider\":\"DeepInfra\",\"choices\":[{\"index\":0,\"delta\":{\"content\":\"\",\"role\":\"assistant\",\"reasoning\":\" with\",\"reasoning_details\":[{\"type\":\"reasoning.text\",\"text\":\" with\",\"format\":\"unknown\",\"index\":0}]},\"finish_reason\":null,\"native_finish_reason\":null}]}", - "data: {\"id\":\"gen-1791215176-nzI6X3OkiLZMwgf1ZcZp\",\"object\":\"chat.completion.chunk\",\"created\":1791215176,\"model\":\"qwen/qwen3-30b-a3b\",\"provider\":\"DeepInfra\",\"choices\":[{\"index\":0,\"delta\":{\"content\":\"\",\"role\":\"assistant\",\"reasoning\":\" the\",\"reasoning_details\":[{\"type\":\"reasoning.text\",\"text\":\" the\",\"format\":\"unknown\",\"index\":0}]},\"finish_reason\":null,\"native_finish_reason\":null}]}", - "data: {\"id\":\"gen-1791215176-nzI6X3OkiLZMwgf1ZcZp\",\"object\":\"chat.completion.chunk\",\"created\":1791215176,\"model\":\"qwen/qwen3-30b-a3b\",\"provider\":\"DeepInfra\",\"choices\":[{\"index\":0,\"delta\":{\"content\":\"\",\"role\":\"assistant\",\"reasoning\":\" title\",\"reasoning_details\":[{\"type\":\"reasoning.text\",\"text\":\" title\",\"format\":\"unknown\",\"index\":0}]},\"finish_reason\":null,\"native_finish_reason\":null}]}", - "data: {\"id\":\"gen-1791215176-nzI6X3OkiLZMwgf1ZcZp\",\"object\":\"chat.completion.chunk\",\"created\":1791215176,\"model\":\"qwen/qwen3-30b-a3b\",\"provider\":\"DeepInfra\",\"choices\":[{\"index\":0,\"delta\":{\"content\":\"\",\"role\":\"assistant\",\"reasoning\":\" and\",\"reasoning_details\":[{\"type\":\"reasoning.text\",\"text\":\" and\",\"format\":\"unknown\",\"index\":0}]},\"finish_reason\":null,\"native_finish_reason\":null}]}", - "data: {\"id\":\"gen-1791215176-nzI6X3OkiLZMwgf1ZcZp\",\"object\":\"chat.completion.chunk\",\"created\":1791215176,\"model\":\"qwen/qwen3-30b-a3b\",\"provider\":\"DeepInfra\",\"choices\":[{\"index\":0,\"delta\":{\"content\":\"\",\"role\":\"assistant\",\"reasoning\":\" URL\",\"reasoning_details\":[{\"type\":\"reasoning.text\",\"text\":\" URL\",\"format\":\"unknown\",\"index\":0}]},\"finish_reason\":null,\"native_finish_reason\":null}]}", - "data: {\"id\":\"gen-1791215176-nzI6X3OkiLZMwgf1ZcZp\",\"object\":\"chat.completion.chunk\",\"created\":1791215176,\"model\":\"qwen/qwen3-30b-a3b\",\"provider\":\"DeepInfra\",\"choices\":[{\"index\":0,\"delta\":{\"content\":\"\",\"role\":\"assistant\",\"reasoning\":\" from\",\"reasoning_details\":[{\"type\":\"reasoning.text\",\"text\":\" from\",\"format\":\"unknown\",\"index\":0}]},\"finish_reason\":null,\"native_finish_reason\":null}]}", - "data: {\"id\":\"gen-1791215176-nzI6X3OkiLZMwgf1ZcZp\",\"object\":\"chat.completion.chunk\",\"created\":1791215176,\"model\":\"qwen/qwen3-30b-a3b\",\"provider\":\"DeepInfra\",\"choices\":[{\"index\":0,\"delta\":{\"content\":\"\",\"role\":\"assistant\",\"reasoning\":\" the\",\"reasoning_details\":[{\"type\":\"reasoning.text\",\"text\":\" the\",\"format\":\"unknown\",\"index\":0}]},\"finish_reason\":null,\"native_finish_reason\":null}]}", - "data: {\"id\":\"gen-1791215176-nzI6X3OkiLZMwgf1ZcZp\",\"object\":\"chat.completion.chunk\",\"created\":1791215176,\"model\":\"qwen/qwen3-30b-a3b\",\"provider\":\"DeepInfra\",\"choices\":[{\"index\":0,\"delta\":{\"content\":\"\",\"role\":\"assistant\",\"reasoning\":\" search\",\"reasoning_details\":[{\"type\":\"reasoning.text\",\"text\":\" search\",\"format\":\"unknown\",\"index\":0}]},\"finish_reason\":null,\"native_finish_reason\":null}]}", - "data: {\"id\":\"gen-1791215176-nzI6X3OkiLZMwgf1ZcZp\",\"object\":\"chat.completion.chunk\",\"created\":1791215176,\"model\":\"qwen/qwen3-30b-a3b\",\"provider\":\"DeepInfra\",\"choices\":[{\"index\":0,\"delta\":{\"content\":\"\",\"role\":\"assistant\",\"reasoning\":\" result\",\"reasoning_details\":[{\"type\":\"reasoning.text\",\"text\":\" result\",\"format\":\"unknown\",\"index\":0}]},\"finish_reason\":null,\"native_finish_reason\":null}]}", - "data: {\"id\":\"gen-1791215176-nzI6X3OkiLZMwgf1ZcZp\",\"object\":\"chat.completion.chunk\",\"created\":1791215176,\"model\":\"qwen/qwen3-30b-a3b\",\"provider\":\"DeepInfra\",\"choices\":[{\"index\":0,\"delta\":{\"content\":\"\",\"role\":\"assistant\",\"reasoning\":\".\",\"reasoning_details\":[{\"type\":\"reasoning.text\",\"text\":\".\",\"format\":\"unknown\",\"index\":0}]},\"finish_reason\":null,\"native_finish_reason\":null}]}", - "data: {\"id\":\"gen-1791215176-nzI6X3OkiLZMwgf1ZcZp\",\"object\":\"chat.completion.chunk\",\"created\":1791215176,\"model\":\"qwen/qwen3-30b-a3b\",\"provider\":\"DeepInfra\",\"choices\":[{\"index\":0,\"delta\":{\"content\":\"\",\"role\":\"assistant\",\"reasoning\":\" Let\",\"reasoning_details\":[{\"type\":\"reasoning.text\",\"text\":\" Let\",\"format\":\"unknown\",\"index\":0}]},\"finish_reason\":null,\"native_finish_reason\":null}]}", - "data: {\"id\":\"gen-1791215176-nzI6X3OkiLZMwgf1ZcZp\",\"object\":\"chat.completion.chunk\",\"created\":1791215176,\"model\":\"qwen/qwen3-30b-a3b\",\"provider\":\"DeepInfra\",\"choices\":[{\"index\":0,\"delta\":{\"content\":\"\",\"role\":\"assistant\",\"reasoning\":\" me\",\"reasoning_details\":[{\"type\":\"reasoning.text\",\"text\":\" me\",\"format\":\"unknown\",\"index\":0}]},\"finish_reason\":null,\"native_finish_reason\":null}]}", - "data: {\"id\":\"gen-1791215176-nzI6X3OkiLZMwgf1ZcZp\",\"object\":\"chat.completion.chunk\",\"created\":1791215176,\"model\":\"qwen/qwen3-30b-a3b\",\"provider\":\"DeepInfra\",\"choices\":[{\"index\":0,\"delta\":{\"content\":\"\",\"role\":\"assistant\",\"reasoning\":\" structure\",\"reasoning_details\":[{\"type\":\"reasoning.text\",\"text\":\" structure\",\"format\":\"unknown\",\"index\":0}]},\"finish_reason\":null,\"native_finish_reason\":null}]}", - "data: {\"id\":\"gen-1791215176-nzI6X3OkiLZMwgf1ZcZp\",\"object\":\"chat.completion.chunk\",\"created\":1791215176,\"model\":\"qwen/qwen3-30b-a3b\",\"provider\":\"DeepInfra\",\"choices\":[{\"index\":0,\"delta\":{\"content\":\"\",\"role\":\"assistant\",\"reasoning\":\" the\",\"reasoning_details\":[{\"type\":\"reasoning.text\",\"text\":\" the\",\"format\":\"unknown\",\"index\":0}]},\"finish_reason\":null,\"native_finish_reason\":null}]}", - "data: {\"id\":\"gen-1791215176-nzI6X3OkiLZMwgf1ZcZp\",\"object\":\"chat.completion.chunk\",\"created\":1791215176,\"model\":\"qwen/qwen3-30b-a3b\",\"provider\":\"DeepInfra\",\"choices\":[{\"index\":0,\"delta\":{\"content\":\"\",\"role\":\"assistant\",\"reasoning\":\" tool\",\"reasoning_details\":[{\"type\":\"reasoning.text\",\"text\":\" tool\",\"format\":\"unknown\",\"index\":0}]},\"finish_reason\":null,\"native_finish_reason\":null}]}", - "data: {\"id\":\"gen-1791215176-nzI6X3OkiLZMwgf1ZcZp\",\"object\":\"chat.completion.chunk\",\"created\":1791215176,\"model\":\"qwen/qwen3-30b-a3b\",\"provider\":\"DeepInfra\",\"choices\":[{\"index\":0,\"delta\":{\"content\":\"\",\"role\":\"assistant\",\"reasoning\":\" call\",\"reasoning_details\":[{\"type\":\"reasoning.text\",\"text\":\" call\",\"format\":\"unknown\",\"index\":0}]},\"finish_reason\":null,\"native_finish_reason\":null}]}", - "data: {\"id\":\"gen-1791215176-nzI6X3OkiLZMwgf1ZcZp\",\"object\":\"chat.completion.chunk\",\"created\":1791215176,\"model\":\"qwen/qwen3-30b-a3b\",\"provider\":\"DeepInfra\",\"choices\":[{\"index\":0,\"delta\":{\"content\":\"\",\"role\":\"assistant\",\"reasoning\":\" properly\",\"reasoning_details\":[{\"type\":\"reasoning.text\",\"text\":\" properly\",\"format\":\"unknown\",\"index\":0}]},\"finish_reason\":null,\"native_finish_reason\":null}]}", - "data: {\"id\":\"gen-1791215176-nzI6X3OkiLZMwgf1ZcZp\",\"object\":\"chat.completion.chunk\",\"created\":1791215176,\"model\":\"qwen/qwen3-30b-a3b\",\"provider\":\"DeepInfra\",\"choices\":[{\"index\":0,\"delta\":{\"content\":\"\",\"role\":\"assistant\",\"reasoning\":\".\\n\",\"reasoning_details\":[{\"type\":\"reasoning.text\",\"text\":\".\\n\",\"format\":\"unknown\",\"index\":0}]},\"finish_reason\":null,\"native_finish_reason\":null}]}", - "data: {\"id\":\"gen-1791215176-nzI6X3OkiLZMwgf1ZcZp\",\"object\":\"chat.completion.chunk\",\"created\":1791215176,\"model\":\"qwen/qwen3-30b-a3b\",\"provider\":\"DeepInfra\",\"choices\":[{\"index\":0,\"delta\":{\"content\":null,\"role\":\"assistant\",\"tool_calls\":[{\"index\":0,\"id\":\"call_333f\",\"type\":\"function\",\"function\":{\"name\":\"search\",\"arguments\":\"\"}}]},\"finish_reason\":null,\"native_finish_reason\":null}]}", - "data: {\"id\":\"gen-1791215176-nzI6X3OkiLZMwgf1ZcZp\",\"object\":\"chat.completion.chunk\",\"created\":1791215176,\"model\":\"qwen/qwen3-30b-a3b\",\"provider\":\"DeepInfra\",\"choices\":[{\"index\":0,\"delta\":{\"content\":null,\"role\":\"assistant\",\"tool_calls\":[{\"index\":0,\"function\":{\"arguments\":\"{\\\"query\\\": \\\"Run the Braintrust Eve instrumentation e2e scenario again\\\"}\"}}]},\"finish_reason\":null,\"native_finish_reason\":null}]}", - "data: {\"id\":\"gen-1791215176-nzI6X3OkiLZMwgf1ZcZp\",\"object\":\"chat.completion.chunk\",\"created\":1791215176,\"model\":\"qwen/qwen3-30b-a3b\",\"provider\":\"DeepInfra\",\"choices\":[{\"index\":0,\"delta\":{\"content\":\"\",\"role\":\"assistant\",\"reasoning\":null},\"finish_reason\":\"tool_calls\",\"native_finish_reason\":\"tool_calls\"}]}", - "data: {\"id\":\"gen-1791215176-nzI6X3OkiLZMwgf1ZcZp\",\"object\":\"chat.completion.chunk\",\"created\":1791215176,\"model\":\"qwen/qwen3-30b-a3b\",\"provider\":\"DeepInfra\",\"service_tier\":\"default\",\"choices\":[{\"index\":0,\"delta\":{\"content\":\"\",\"role\":\"assistant\"},\"finish_reason\":\"tool_calls\",\"native_finish_reason\":\"tool_calls\"}],\"usage\":{\"prompt_tokens\":3037,\"completion_tokens\":173,\"total_tokens\":3210,\"cost\":0.00045094,\"is_byok\":false,\"prompt_tokens_details\":{\"cached_tokens\":0,\"cache_write_tokens\":0,\"audio_tokens\":0,\"video_tokens\":0},\"cost_details\":{\"upstream_inference_cost\":0.00045094,\"upstream_inference_prompt_cost\":0.00036444,\"upstream_inference_completions_cost\":0.0000865},\"completion_tokens_details\":{\"reasoning_tokens\":159,\"image_tokens\":0,\"audio_tokens\":0}}}", - "data: [DONE]" - ], - "kind": "sse" - }, - "headers": { - "access-control-allow-origin": "*", - "access-control-expose-headers": "X-Generation-Id,X-Provider-Name,request-id,cf-ray", - "cache-control": "no-cache", - "cf-ray": "a45d8ee80f7d19cf-YYZ", - "content-type": "text/event-stream", - "date": "Mon, 05 Oct 2026 15:46:17 GMT", - "permissions-policy": "payment=(self \"https://checkout.stripe.com\" \"https://connect-js.stripe.com\" \"https://js.stripe.com\" \"https://*.js.stripe.com\" \"https://hooks.stripe.com\")", - "referrer-policy": "no-referrer, strict-origin-when-cross-origin", - "server": "cloudflare", - "set-cookie": "[REDACTED]", - "x-content-type-options": "nosniff", - "x-generation-id": "gen-1791215176-nzI6X3OkiLZMwgf1ZcZp" - }, - "status": 200 - } - }, - { - "callIndex": 6, - "id": "b99ab9c34a9e0a2b", - "matchKey": "POST openrouter.ai/api/v1/chat/completions", - "recordedAt": "2026-10-05T15:46:23.226Z", - "request": { - "body": { - "kind": "json", - "value": { - "messages": [ - { - "content": [ - { - "text": "Instructions (instructions)\nYou are the deterministic researcher subagent for Braintrust Eve instrumentation tests.\n\nFor every task:\n\n1. Call the search tool exactly once with the full task as the query.\n2. After the search result is available, answer with a single sentence that starts\n with \"Researcher result:\" and includes the search title and URL.\n\nTool execution\nA single tool or subagent call runs as one serial action. If you call multiple independent tools or subagents in one response, eve treats that batch as parallel work. Only batch work that is independent and does not rely on another call in the same response.", - "type": "text" - } - ], - "role": "system" - }, - { - "content": "You are the subagent \"researcher\".\nDescription: Research the Eve instrumentation documentation before the parent reads it.\n\nThe caller delegated the following task to you. Complete it and return the final result directly.\n\nCaller message:\nRun the Braintrust Eve instrumentation e2e scenario again", - "role": "user" - }, - { - "content": null, - "reasoning": "\nOkay, the user wants me to run the Braintrust Eve instrumentation end-to-end scenario again. Since I'm the researcher subagent, my job is to look up the documentation first. Let me check the tools available. There's a 'search' function that queries the Eve documentation. I need to use that. The task is to \"Run the Braintrust Eve instrumentation e2e scenario again,\" so I'll use that exact query for the search. I'll make sure to call the search tool once with the full task as the query. Then, once I get the results, I'll format the answer with the title and URL from the search result. Let me structure the tool call properly.\n", - "reasoning_details": [ - { - "format": "unknown", - "index": 0, - "text": "\nOkay, the user wants me to run the Braintrust Eve instrumentation end-to-end scenario again. Since I'm the researcher subagent, my job is to look up the documentation first. Let me check the tools available. There's a 'search' function that queries the Eve documentation. I need to use that. The task is to \"Run the Braintrust Eve instrumentation e2e scenario again,\" so I'll use that exact query for the search. I'll make sure to call the search tool once with the full task as the query. Then, once I get the results, I'll format the answer with the title and URL from the search result. Let me structure the tool call properly.\n", - "type": "reasoning.text" - } - ], - "role": "assistant", - "tool_calls": [ - { - "function": { - "arguments": "{\"query\":\"Run the Braintrust Eve instrumentation e2e scenario again\"}", - "name": "search" - }, - "id": "call_333f", - "type": "function" - } - ] - }, - { - "content": "{\"query\":\"Run the Braintrust Eve instrumentation e2e scenario again\",\"title\":\"Eve instrumentation\",\"url\":\"https://eve.dev/docs/guides/instrumentation\"}", - "name": "search", - "role": "tool", - "tool_call_id": "call_333f" - } - ], - "model": "qwen/qwen3-30b-a3b", - "provider": { - "only": ["deepinfra"], - "require_parameters": true - }, - "reasoning": { - "effort": "low" - }, - "stream": true, - "tool_choice": "auto", - "tools": [ - { - "function": { - "description": "Ask the user a question and wait for their response before continuing. Use this when you need clarification or a choice from the user.", - "name": "ask_question", - "parameters": { - "additionalProperties": false, - "properties": { - "allowFreeform": { - "description": "Whether the user may answer with freeform text instead of selecting one of the provided options.", - "type": "boolean" - }, - "options": { - "description": "Selectable answer options to present to the user.", - "items": { - "additionalProperties": false, - "properties": { - "description": { - "description": "Optional additional context for this option.", - "type": "string" - }, - "id": { - "description": "Stable identifier for the option.", - "type": "string" - }, - "label": { - "description": "User-facing label for the option.", - "type": "string" - }, - "style": { - "description": "Visual treatment hint for the option.", - "enum": ["primary", "danger", "default"], - "type": "string" - } - }, - "required": ["id", "label"], - "type": "object" - }, - "type": "array" - }, - "prompt": { - "description": "The prompt to present to the user.", - "type": "string" - } - }, - "required": ["prompt"], - "type": "object" - } - }, - "type": "function" - }, - { - "function": { - "description": "Execute a shell command in the shared workspace environment.", - "name": "bash", - "parameters": { - "additionalProperties": false, - "properties": { - "command": { - "description": "The shell command to execute.", - "type": "string" - } - }, - "required": ["command"], - "type": "object" - } - }, - "type": "function" - }, - { - "function": { - "description": "Fast file pattern matching tool that works with any codebase size.\n\nUsage:\n- Supports glob patterns like \"**/*.js\" or \"src/**/*.ts\".\n- Returns matching file paths.\n- Use this tool when you need to find files by name patterns.\n- If you are unsure of the correct file path, use the glob tool to look up filenames by glob pattern.\n- Use the grep tool instead if you need to search file contents.\n- Call this tool in parallel when you know there are multiple patterns to search for.", - "name": "glob", - "parameters": { - "additionalProperties": false, - "properties": { - "limit": { - "description": "Maximum number of results to return. Defaults to 100.", - "maximum": 1000, - "minimum": 1, - "type": "integer" - }, - "path": { - "description": "The directory to search in. Defaults to /workspace. Must be an absolute path. Omit to use the default.", - "type": "string" - }, - "pattern": { - "description": "The glob pattern to match files against (e.g. \"**/*.ts\", \"src/**/*.js\").", - "type": "string" - } - }, - "required": ["pattern"], - "type": "object" - } - }, - "type": "function" - }, - { - "function": { - "description": "Fast content search tool that works with any codebase size.\n\nUsage:\n- Searches file contents using regular expressions.\n- Supports full regex syntax (e.g. \"log.*Error\", \"function\\s+\\w+\").\n- Filter files by pattern with the glob parameter (e.g. \"*.js\", \"*.{ts,tsx}\").\n- Returns matching lines with file paths and line numbers.\n- Use this tool when you need to find files containing specific patterns.\n- Use the glob tool instead if you only need to find files by name.\n- Call this tool in parallel when you have multiple independent searches.\n- Any line longer than 2000 characters is truncated.", - "name": "grep", - "parameters": { - "additionalProperties": false, - "properties": { - "context": { - "description": "Number of surrounding context lines to include before and after each match. Defaults to 0.", - "minimum": 0, - "type": "integer" - }, - "glob": { - "description": "Filter files by glob pattern (e.g. \"*.ts\", \"*.{ts,tsx}\").", - "type": "string" - }, - "ignoreCase": { - "description": "Perform case-insensitive search. Defaults to false.", - "type": "boolean" - }, - "limit": { - "description": "Maximum number of matches to return per file. Defaults to 100.", - "maximum": 1000, - "minimum": 1, - "type": "integer" - }, - "literal": { - "description": "Treat the pattern as a literal string instead of a regular expression. Defaults to false.", - "type": "boolean" - }, - "path": { - "description": "The directory or file to search in. Defaults to /workspace. Must be an absolute path. Omit to use the default.", - "type": "string" - }, - "pattern": { - "description": "The regex pattern to search for in file contents (e.g. \"log.*Error\", \"function\\s+\\w+\").", - "type": "string" - } - }, - "required": ["pattern"], - "type": "object" - } - }, - "type": "function" - }, - { - "function": { - "description": "Read a file from the local filesystem. If the path does not exist, an error is returned.\n\nUsage:\n- The filePath parameter should be an absolute path.\n- By default, this tool returns up to 2000 lines from the start of the file.\n- The offset parameter is the line number to start from (1-indexed).\n- To read later sections, call this tool again with a larger offset.\n- Use the grep tool to find specific content in large files or files with long lines.\n- If you are unsure of the correct file path, use the glob tool to look up filenames by glob pattern.\n- Contents are returned with each line prefixed by its line number as `: `. For example, if a file has contents \"foo\\n\", you will receive \"1: foo\\n\".\n- Any line longer than 2000 characters is truncated.\n- Call this tool in parallel when you know there are multiple files you want to read.\n- Avoid tiny repeated slices (30 line chunks). If you need more context, read a larger window.", - "name": "read_file", - "parameters": { - "additionalProperties": false, - "properties": { - "filePath": { - "description": "The absolute path to the file to read.", - "type": "string" - }, - "limit": { - "description": "Maximum number of lines to return. Defaults to 2000.", - "minimum": 1, - "type": "integer" - }, - "offset": { - "description": "1-based line number to start from. Defaults to 1.", - "minimum": 1, - "type": "integer" - } - }, - "required": ["filePath"], - "type": "object" - } - }, - "type": "function" - }, - { - "function": { - "description": "Writes a file to the local filesystem.\n\nUsage:\n- This tool will overwrite the existing file if there is one at the provided path.\n- If this is an existing file, you MUST use the read_file tool first to read the file's contents. This tool will fail if you did not read the file first.\n- ALWAYS prefer editing existing files in the codebase. NEVER write new files unless explicitly required.\n- NEVER proactively create documentation files (*.md) or README files. Only create documentation files if explicitly requested by the User.\n- Only use emojis if the user explicitly requests it. Avoid writing emojis to files unless asked.", - "name": "write_file", - "parameters": { - "additionalProperties": false, - "properties": { - "content": { - "description": "Complete replacement file contents.", - "type": "string" - }, - "filePath": { - "description": "The absolute path to the file to write (must be absolute, not relative).", - "type": "string" - } - }, - "required": ["filePath", "content"], - "type": "object" - } - }, - "type": "function" - }, - { - "function": { - "description": "Use this tool to create and manage a structured task list for the current session.\nThis helps you track progress, organize complex tasks, and demonstrate thoroughness.\n\nWhen to use:\n- Complex multistep tasks requiring 3 or more distinct steps\n- When the user provides multiple tasks or a numbered list\n- After receiving new instructions, to capture requirements\n- After completing a task, to mark it complete and add follow-ups\n\nWhen NOT to use:\n- Single, straightforward tasks that need no tracking\n- Purely conversational or informational requests\n\nUsage:\n- Call with `todos` to replace the entire list (full replacement write)\n- Call without `todos` to read the current list\n- Both return the full current list with status counts\n- Mark tasks in_progress when you start, completed when done\n- Only have ONE task in_progress at a time", - "name": "todo", - "parameters": { - "additionalProperties": false, - "properties": { - "todos": { - "description": "The updated todo list. Omit to read the current list without modifying it.", - "items": { - "additionalProperties": false, - "properties": { - "content": { - "description": "Brief description of the task.", - "type": "string" - }, - "priority": { - "description": "Priority level of the task.", - "enum": ["high", "medium", "low"], - "type": "string" - }, - "status": { - "description": "Current status of the task.", - "enum": [ - "pending", - "in_progress", - "completed", - "cancelled" - ], - "type": "string" - } - }, - "required": ["content", "status", "priority"], - "type": "object" - }, - "type": "array" - } - }, - "type": "object" - } - }, - "type": "function" - }, - { - "function": { - "description": "Fetch a webpage and return its content in the requested format. Use this to retrieve and analyze content from URLs.\n\nUsage notes:\n- The URL must be a fully-formed valid URL starting with http:// or https://\n- HTML responses are automatically converted to markdown or plain text based on the requested format\n- Format options: \"markdown\" (default), \"text\", or \"html\"\n- Default timeout is 30 seconds (max 120 seconds)\n- Maximum response size is 5 MB; content is further capped at the shared tool-output budget (50 KB / 2000 lines)\n- This tool is read-only and does not modify any files", - "name": "web_fetch", - "parameters": { - "additionalProperties": false, - "properties": { - "format": { - "description": "The format to return the content in (text, markdown, or html). HTML responses are automatically converted to the requested format. Defaults to \"markdown\".", - "enum": ["markdown", "text", "html"], - "type": "string" - }, - "timeout": { - "description": "Optional timeout in seconds. Defaults to 30, max 120.", - "type": "number" - }, - "url": { - "description": "The fully-formed URL to fetch content from. Must start with http:// or https://.", - "type": "string" - } - }, - "required": ["url"], - "type": "object" - } - }, - "type": "function" - }, - { - "function": { - "description": "Load the full instructions for one available skill by name or id. Use this tool when the request clearly matches a listed skill description or when the user explicitly asks for that skill. This is not for MCP connections; use connection_search to access an installed connection. Loading adds the skill instructions to the current turn. Choose the \"skill\" value from the Available skills block.", - "name": "load_skill", - "parameters": { - "additionalProperties": false, - "properties": { - "skill": { - "description": "Available skill name or id.", - "type": "string" - } - }, - "required": ["skill"], - "type": "object" - } - }, - "type": "function" - }, - { - "function": { - "description": "Searches the Eve instrumentation documentation fixture.", - "name": "search", - "parameters": { - "$schema": "http://json-schema.org/draft-07/schema#", - "additionalProperties": false, - "properties": { - "query": { - "type": "string" - } - }, - "required": ["query"], - "type": "object" - } - }, - "type": "function" - }, - { - "function": { - "description": "Delegate a focused subtask to a fresh copy of yourself. Use it to isolate complex work or split a large task into independent pieces. Issue multiple `agent` calls in one response to run a small fixed set in parallel. Each child has fresh history and state but shares your tools and sandbox, so include essential context in `message` and give parallel writers non-overlapping scopes.", - "name": "agent", - "parameters": { - "additionalProperties": false, - "properties": { - "message": { - "description": "The message to send to the subagent. Provide all context the subagent needs to complete the task; the subagent does not see the parent's history.", - "type": "string" - }, - "outputSchema": { - "description": "When provided, the subagent runs in task mode and must produce structured output matching this JSON Schema. The structured output becomes the tool result.", - "type": "object" - } - }, - "required": ["message"], - "type": "object" - } - }, - "type": "function" - } - ] - } - }, - "headers": {}, - "method": "POST", - "url": "https://openrouter.ai/api/v1/chat/completions" - }, - "response": { - "body": { - "chunks": [ - "data: {\"id\":\"gen-1791215181-RSctj8jzHB2AmoUqfU9L\",\"object\":\"chat.completion.chunk\",\"created\":1791215181,\"model\":\"qwen/qwen3-30b-a3b\",\"provider\":\"DeepInfra\",\"choices\":[{\"index\":0,\"delta\":{\"content\":\"\",\"role\":\"assistant\",\"reasoning\":\"\\n\",\"reasoning_details\":[{\"type\":\"reasoning.text\",\"text\":\"\\n\",\"format\":\"unknown\",\"index\":0}]},\"finish_reason\":null,\"native_finish_reason\":null}]}", - "data: {\"id\":\"gen-1791215181-RSctj8jzHB2AmoUqfU9L\",\"object\":\"chat.completion.chunk\",\"created\":1791215181,\"model\":\"qwen/qwen3-30b-a3b\",\"provider\":\"DeepInfra\",\"choices\":[{\"index\":0,\"delta\":{\"content\":\"\",\"role\":\"assistant\",\"reasoning\":\"Okay\",\"reasoning_details\":[{\"type\":\"reasoning.text\",\"text\":\"Okay\",\"format\":\"unknown\",\"index\":0}]},\"finish_reason\":null,\"native_finish_reason\":null}]}", - "data: {\"id\":\"gen-1791215181-RSctj8jzHB2AmoUqfU9L\",\"object\":\"chat.completion.chunk\",\"created\":1791215181,\"model\":\"qwen/qwen3-30b-a3b\",\"provider\":\"DeepInfra\",\"choices\":[{\"index\":0,\"delta\":{\"content\":\"\",\"role\":\"assistant\",\"reasoning\":\",\",\"reasoning_details\":[{\"type\":\"reasoning.text\",\"text\":\",\",\"format\":\"unknown\",\"index\":0}]},\"finish_reason\":null,\"native_finish_reason\":null}]}", - "data: {\"id\":\"gen-1791215181-RSctj8jzHB2AmoUqfU9L\",\"object\":\"chat.completion.chunk\",\"created\":1791215181,\"model\":\"qwen/qwen3-30b-a3b\",\"provider\":\"DeepInfra\",\"choices\":[{\"index\":0,\"delta\":{\"content\":\"\",\"role\":\"assistant\",\"reasoning\":\" the\",\"reasoning_details\":[{\"type\":\"reasoning.text\",\"text\":\" the\",\"format\":\"unknown\",\"index\":0}]},\"finish_reason\":null,\"native_finish_reason\":null}]}", - "data: {\"id\":\"gen-1791215181-RSctj8jzHB2AmoUqfU9L\",\"object\":\"chat.completion.chunk\",\"created\":1791215181,\"model\":\"qwen/qwen3-30b-a3b\",\"provider\":\"DeepInfra\",\"choices\":[{\"index\":0,\"delta\":{\"content\":\"\",\"role\":\"assistant\",\"reasoning\":\" user\",\"reasoning_details\":[{\"type\":\"reasoning.text\",\"text\":\" user\",\"format\":\"unknown\",\"index\":0}]},\"finish_reason\":null,\"native_finish_reason\":null}]}", - "data: {\"id\":\"gen-1791215181-RSctj8jzHB2AmoUqfU9L\",\"object\":\"chat.completion.chunk\",\"created\":1791215181,\"model\":\"qwen/qwen3-30b-a3b\",\"provider\":\"DeepInfra\",\"choices\":[{\"index\":0,\"delta\":{\"content\":\"\",\"role\":\"assistant\",\"reasoning\":\" asked\",\"reasoning_details\":[{\"type\":\"reasoning.text\",\"text\":\" asked\",\"format\":\"unknown\",\"index\":0}]},\"finish_reason\":null,\"native_finish_reason\":null}]}", - "data: {\"id\":\"gen-1791215181-RSctj8jzHB2AmoUqfU9L\",\"object\":\"chat.completion.chunk\",\"created\":1791215181,\"model\":\"qwen/qwen3-30b-a3b\",\"provider\":\"DeepInfra\",\"choices\":[{\"index\":0,\"delta\":{\"content\":\"\",\"role\":\"assistant\",\"reasoning\":\" me\",\"reasoning_details\":[{\"type\":\"reasoning.text\",\"text\":\" me\",\"format\":\"unknown\",\"index\":0}]},\"finish_reason\":null,\"native_finish_reason\":null}]}", - "data: {\"id\":\"gen-1791215181-RSctj8jzHB2AmoUqfU9L\",\"object\":\"chat.completion.chunk\",\"created\":1791215181,\"model\":\"qwen/qwen3-30b-a3b\",\"provider\":\"DeepInfra\",\"choices\":[{\"index\":0,\"delta\":{\"content\":\"\",\"role\":\"assistant\",\"reasoning\":\" to\",\"reasoning_details\":[{\"type\":\"reasoning.text\",\"text\":\" to\",\"format\":\"unknown\",\"index\":0}]},\"finish_reason\":null,\"native_finish_reason\":null}]}", - "data: {\"id\":\"gen-1791215181-RSctj8jzHB2AmoUqfU9L\",\"object\":\"chat.completion.chunk\",\"created\":1791215181,\"model\":\"qwen/qwen3-30b-a3b\",\"provider\":\"DeepInfra\",\"choices\":[{\"index\":0,\"delta\":{\"content\":\"\",\"role\":\"assistant\",\"reasoning\":\" run\",\"reasoning_details\":[{\"type\":\"reasoning.text\",\"text\":\" run\",\"format\":\"unknown\",\"index\":0}]},\"finish_reason\":null,\"native_finish_reason\":null}]}", - "data: {\"id\":\"gen-1791215181-RSctj8jzHB2AmoUqfU9L\",\"object\":\"chat.completion.chunk\",\"created\":1791215181,\"model\":\"qwen/qwen3-30b-a3b\",\"provider\":\"DeepInfra\",\"choices\":[{\"index\":0,\"delta\":{\"content\":\"\",\"role\":\"assistant\",\"reasoning\":\" the\",\"reasoning_details\":[{\"type\":\"reasoning.text\",\"text\":\" the\",\"format\":\"unknown\",\"index\":0}]},\"finish_reason\":null,\"native_finish_reason\":null}]}", - "data: {\"id\":\"gen-1791215181-RSctj8jzHB2AmoUqfU9L\",\"object\":\"chat.completion.chunk\",\"created\":1791215181,\"model\":\"qwen/qwen3-30b-a3b\",\"provider\":\"DeepInfra\",\"choices\":[{\"index\":0,\"delta\":{\"content\":\"\",\"role\":\"assistant\",\"reasoning\":\" Brain\",\"reasoning_details\":[{\"type\":\"reasoning.text\",\"text\":\" Brain\",\"format\":\"unknown\",\"index\":0}]},\"finish_reason\":null,\"native_finish_reason\":null}]}", - "data: {\"id\":\"gen-1791215181-RSctj8jzHB2AmoUqfU9L\",\"object\":\"chat.completion.chunk\",\"created\":1791215181,\"model\":\"qwen/qwen3-30b-a3b\",\"provider\":\"DeepInfra\",\"choices\":[{\"index\":0,\"delta\":{\"content\":\"\",\"role\":\"assistant\",\"reasoning\":\"trust\",\"reasoning_details\":[{\"type\":\"reasoning.text\",\"text\":\"trust\",\"format\":\"unknown\",\"index\":0}]},\"finish_reason\":null,\"native_finish_reason\":null}]}", - "data: {\"id\":\"gen-1791215181-RSctj8jzHB2AmoUqfU9L\",\"object\":\"chat.completion.chunk\",\"created\":1791215181,\"model\":\"qwen/qwen3-30b-a3b\",\"provider\":\"DeepInfra\",\"choices\":[{\"index\":0,\"delta\":{\"content\":\"\",\"role\":\"assistant\",\"reasoning\":\" Eve\",\"reasoning_details\":[{\"type\":\"reasoning.text\",\"text\":\" Eve\",\"format\":\"unknown\",\"index\":0}]},\"finish_reason\":null,\"native_finish_reason\":null}]}", - "data: {\"id\":\"gen-1791215181-RSctj8jzHB2AmoUqfU9L\",\"object\":\"chat.completion.chunk\",\"created\":1791215181,\"model\":\"qwen/qwen3-30b-a3b\",\"provider\":\"DeepInfra\",\"choices\":[{\"index\":0,\"delta\":{\"content\":\"\",\"role\":\"assistant\",\"reasoning\":\" instrumentation\",\"reasoning_details\":[{\"type\":\"reasoning.text\",\"text\":\" instrumentation\",\"format\":\"unknown\",\"index\":0}]},\"finish_reason\":null,\"native_finish_reason\":null}]}", - "data: {\"id\":\"gen-1791215181-RSctj8jzHB2AmoUqfU9L\",\"object\":\"chat.completion.chunk\",\"created\":1791215181,\"model\":\"qwen/qwen3-30b-a3b\",\"provider\":\"DeepInfra\",\"choices\":[{\"index\":0,\"delta\":{\"content\":\"\",\"role\":\"assistant\",\"reasoning\":\" end\",\"reasoning_details\":[{\"type\":\"reasoning.text\",\"text\":\" end\",\"format\":\"unknown\",\"index\":0}]},\"finish_reason\":null,\"native_finish_reason\":null}]}", - "data: {\"id\":\"gen-1791215181-RSctj8jzHB2AmoUqfU9L\",\"object\":\"chat.completion.chunk\",\"created\":1791215181,\"model\":\"qwen/qwen3-30b-a3b\",\"provider\":\"DeepInfra\",\"choices\":[{\"index\":0,\"delta\":{\"content\":\"\",\"role\":\"assistant\",\"reasoning\":\"-to\",\"reasoning_details\":[{\"type\":\"reasoning.text\",\"text\":\"-to\",\"format\":\"unknown\",\"index\":0}]},\"finish_reason\":null,\"native_finish_reason\":null}]}", - "data: {\"id\":\"gen-1791215181-RSctj8jzHB2AmoUqfU9L\",\"object\":\"chat.completion.chunk\",\"created\":1791215181,\"model\":\"qwen/qwen3-30b-a3b\",\"provider\":\"DeepInfra\",\"choices\":[{\"index\":0,\"delta\":{\"content\":\"\",\"role\":\"assistant\",\"reasoning\":\"-end\",\"reasoning_details\":[{\"type\":\"reasoning.text\",\"text\":\"-end\",\"format\":\"unknown\",\"index\":0}]},\"finish_reason\":null,\"native_finish_reason\":null}]}", - "data: {\"id\":\"gen-1791215181-RSctj8jzHB2AmoUqfU9L\",\"object\":\"chat.completion.chunk\",\"created\":1791215181,\"model\":\"qwen/qwen3-30b-a3b\",\"provider\":\"DeepInfra\",\"choices\":[{\"index\":0,\"delta\":{\"content\":\"\",\"role\":\"assistant\",\"reasoning\":\" scenario\",\"reasoning_details\":[{\"type\":\"reasoning.text\",\"text\":\" scenario\",\"format\":\"unknown\",\"index\":0}]},\"finish_reason\":null,\"native_finish_reason\":null}]}", - "data: {\"id\":\"gen-1791215181-RSctj8jzHB2AmoUqfU9L\",\"object\":\"chat.completion.chunk\",\"created\":1791215181,\"model\":\"qwen/qwen3-30b-a3b\",\"provider\":\"DeepInfra\",\"choices\":[{\"index\":0,\"delta\":{\"content\":\"\",\"role\":\"assistant\",\"reasoning\":\" again\",\"reasoning_details\":[{\"type\":\"reasoning.text\",\"text\":\" again\",\"format\":\"unknown\",\"index\":0}]},\"finish_reason\":null,\"native_finish_reason\":null}]}", - "data: {\"id\":\"gen-1791215181-RSctj8jzHB2AmoUqfU9L\",\"object\":\"chat.completion.chunk\",\"created\":1791215181,\"model\":\"qwen/qwen3-30b-a3b\",\"provider\":\"DeepInfra\",\"choices\":[{\"index\":0,\"delta\":{\"content\":\"\",\"role\":\"assistant\",\"reasoning\":\".\",\"reasoning_details\":[{\"type\":\"reasoning.text\",\"text\":\".\",\"format\":\"unknown\",\"index\":0}]},\"finish_reason\":null,\"native_finish_reason\":null}]}", - "data: {\"id\":\"gen-1791215181-RSctj8jzHB2AmoUqfU9L\",\"object\":\"chat.completion.chunk\",\"created\":1791215181,\"model\":\"qwen/qwen3-30b-a3b\",\"provider\":\"DeepInfra\",\"choices\":[{\"index\":0,\"delta\":{\"content\":\"\",\"role\":\"assistant\",\"reasoning\":\" As\",\"reasoning_details\":[{\"type\":\"reasoning.text\",\"text\":\" As\",\"format\":\"unknown\",\"index\":0}]},\"finish_reason\":null,\"native_finish_reason\":null}]}", - "data: {\"id\":\"gen-1791215181-RSctj8jzHB2AmoUqfU9L\",\"object\":\"chat.completion.chunk\",\"created\":1791215181,\"model\":\"qwen/qwen3-30b-a3b\",\"provider\":\"DeepInfra\",\"choices\":[{\"index\":0,\"delta\":{\"content\":\"\",\"role\":\"assistant\",\"reasoning\":\" the\",\"reasoning_details\":[{\"type\":\"reasoning.text\",\"text\":\" the\",\"format\":\"unknown\",\"index\":0}]},\"finish_reason\":null,\"native_finish_reason\":null}]}", - "data: {\"id\":\"gen-1791215181-RSctj8jzHB2AmoUqfU9L\",\"object\":\"chat.completion.chunk\",\"created\":1791215181,\"model\":\"qwen/qwen3-30b-a3b\",\"provider\":\"DeepInfra\",\"choices\":[{\"index\":0,\"delta\":{\"content\":\"\",\"role\":\"assistant\",\"reasoning\":\" researcher\",\"reasoning_details\":[{\"type\":\"reasoning.text\",\"text\":\" researcher\",\"format\":\"unknown\",\"index\":0}]},\"finish_reason\":null,\"native_finish_reason\":null}]}", - "data: {\"id\":\"gen-1791215181-RSctj8jzHB2AmoUqfU9L\",\"object\":\"chat.completion.chunk\",\"created\":1791215181,\"model\":\"qwen/qwen3-30b-a3b\",\"provider\":\"DeepInfra\",\"choices\":[{\"index\":0,\"delta\":{\"content\":\"\",\"role\":\"assistant\",\"reasoning\":\" sub\",\"reasoning_details\":[{\"type\":\"reasoning.text\",\"text\":\" sub\",\"format\":\"unknown\",\"index\":0}]},\"finish_reason\":null,\"native_finish_reason\":null}]}", - "data: {\"id\":\"gen-1791215181-RSctj8jzHB2AmoUqfU9L\",\"object\":\"chat.completion.chunk\",\"created\":1791215181,\"model\":\"qwen/qwen3-30b-a3b\",\"provider\":\"DeepInfra\",\"choices\":[{\"index\":0,\"delta\":{\"content\":\"\",\"role\":\"assistant\",\"reasoning\":\"agent\",\"reasoning_details\":[{\"type\":\"reasoning.text\",\"text\":\"agent\",\"format\":\"unknown\",\"index\":0}]},\"finish_reason\":null,\"native_finish_reason\":null}]}", - "data: {\"id\":\"gen-1791215181-RSctj8jzHB2AmoUqfU9L\",\"object\":\"chat.completion.chunk\",\"created\":1791215181,\"model\":\"qwen/qwen3-30b-a3b\",\"provider\":\"DeepInfra\",\"choices\":[{\"index\":0,\"delta\":{\"content\":\"\",\"role\":\"assistant\",\"reasoning\":\",\",\"reasoning_details\":[{\"type\":\"reasoning.text\",\"text\":\",\",\"format\":\"unknown\",\"index\":0}]},\"finish_reason\":null,\"native_finish_reason\":null}]}", - "data: {\"id\":\"gen-1791215181-RSctj8jzHB2AmoUqfU9L\",\"object\":\"chat.completion.chunk\",\"created\":1791215181,\"model\":\"qwen/qwen3-30b-a3b\",\"provider\":\"DeepInfra\",\"choices\":[{\"index\":0,\"delta\":{\"content\":\"\",\"role\":\"assistant\",\"reasoning\":\" I\",\"reasoning_details\":[{\"type\":\"reasoning.text\",\"text\":\" I\",\"format\":\"unknown\",\"index\":0}]},\"finish_reason\":null,\"native_finish_reason\":null}]}", - "data: {\"id\":\"gen-1791215181-RSctj8jzHB2AmoUqfU9L\",\"object\":\"chat.completion.chunk\",\"created\":1791215181,\"model\":\"qwen/qwen3-30b-a3b\",\"provider\":\"DeepInfra\",\"choices\":[{\"index\":0,\"delta\":{\"content\":\"\",\"role\":\"assistant\",\"reasoning\":\" need\",\"reasoning_details\":[{\"type\":\"reasoning.text\",\"text\":\" need\",\"format\":\"unknown\",\"index\":0}]},\"finish_reason\":null,\"native_finish_reason\":null}]}", - "data: {\"id\":\"gen-1791215181-RSctj8jzHB2AmoUqfU9L\",\"object\":\"chat.completion.chunk\",\"created\":1791215181,\"model\":\"qwen/qwen3-30b-a3b\",\"provider\":\"DeepInfra\",\"choices\":[{\"index\":0,\"delta\":{\"content\":\"\",\"role\":\"assistant\",\"reasoning\":\" to\",\"reasoning_details\":[{\"type\":\"reasoning.text\",\"text\":\" to\",\"format\":\"unknown\",\"index\":0}]},\"finish_reason\":null,\"native_finish_reason\":null}]}", - "data: {\"id\":\"gen-1791215181-RSctj8jzHB2AmoUqfU9L\",\"object\":\"chat.completion.chunk\",\"created\":1791215181,\"model\":\"qwen/qwen3-30b-a3b\",\"provider\":\"DeepInfra\",\"choices\":[{\"index\":0,\"delta\":{\"content\":\"\",\"role\":\"assistant\",\"reasoning\":\" check\",\"reasoning_details\":[{\"type\":\"reasoning.text\",\"text\":\" check\",\"format\":\"unknown\",\"index\":0}]},\"finish_reason\":null,\"native_finish_reason\":null}]}", - "data: {\"id\":\"gen-1791215181-RSctj8jzHB2AmoUqfU9L\",\"object\":\"chat.completion.chunk\",\"created\":1791215181,\"model\":\"qwen/qwen3-30b-a3b\",\"provider\":\"DeepInfra\",\"choices\":[{\"index\":0,\"delta\":{\"content\":\"\",\"role\":\"assistant\",\"reasoning\":\" the\",\"reasoning_details\":[{\"type\":\"reasoning.text\",\"text\":\" the\",\"format\":\"unknown\",\"index\":0}]},\"finish_reason\":null,\"native_finish_reason\":null}]}", - "data: {\"id\":\"gen-1791215181-RSctj8jzHB2AmoUqfU9L\",\"object\":\"chat.completion.chunk\",\"created\":1791215181,\"model\":\"qwen/qwen3-30b-a3b\",\"provider\":\"DeepInfra\",\"choices\":[{\"index\":0,\"delta\":{\"content\":\"\",\"role\":\"assistant\",\"reasoning\":\" documentation\",\"reasoning_details\":[{\"type\":\"reasoning.text\",\"text\":\" documentation\",\"format\":\"unknown\",\"index\":0}]},\"finish_reason\":null,\"native_finish_reason\":null}]}", - "data: {\"id\":\"gen-1791215181-RSctj8jzHB2AmoUqfU9L\",\"object\":\"chat.completion.chunk\",\"created\":1791215181,\"model\":\"qwen/qwen3-30b-a3b\",\"provider\":\"DeepInfra\",\"choices\":[{\"index\":0,\"delta\":{\"content\":\"\",\"role\":\"assistant\",\"reasoning\":\" first\",\"reasoning_details\":[{\"type\":\"reasoning.text\",\"text\":\" first\",\"format\":\"unknown\",\"index\":0}]},\"finish_reason\":null,\"native_finish_reason\":null}]}", - "data: {\"id\":\"gen-1791215181-RSctj8jzHB2AmoUqfU9L\",\"object\":\"chat.completion.chunk\",\"created\":1791215181,\"model\":\"qwen/qwen3-30b-a3b\",\"provider\":\"DeepInfra\",\"choices\":[{\"index\":0,\"delta\":{\"content\":\"\",\"role\":\"assistant\",\"reasoning\":\".\",\"reasoning_details\":[{\"type\":\"reasoning.text\",\"text\":\".\",\"format\":\"unknown\",\"index\":0}]},\"finish_reason\":null,\"native_finish_reason\":null}]}", - "data: {\"id\":\"gen-1791215181-RSctj8jzHB2AmoUqfU9L\",\"object\":\"chat.completion.chunk\",\"created\":1791215181,\"model\":\"qwen/qwen3-30b-a3b\",\"provider\":\"DeepInfra\",\"choices\":[{\"index\":0,\"delta\":{\"content\":\"\",\"role\":\"assistant\",\"reasoning\":\" I\",\"reasoning_details\":[{\"type\":\"reasoning.text\",\"text\":\" I\",\"format\":\"unknown\",\"index\":0}]},\"finish_reason\":null,\"native_finish_reason\":null}]}", - "data: {\"id\":\"gen-1791215181-RSctj8jzHB2AmoUqfU9L\",\"object\":\"chat.completion.chunk\",\"created\":1791215181,\"model\":\"qwen/qwen3-30b-a3b\",\"provider\":\"DeepInfra\",\"choices\":[{\"index\":0,\"delta\":{\"content\":\"\",\"role\":\"assistant\",\"reasoning\":\" used\",\"reasoning_details\":[{\"type\":\"reasoning.text\",\"text\":\" used\",\"format\":\"unknown\",\"index\":0}]},\"finish_reason\":null,\"native_finish_reason\":null}]}", - "data: {\"id\":\"gen-1791215181-RSctj8jzHB2AmoUqfU9L\",\"object\":\"chat.completion.chunk\",\"created\":1791215181,\"model\":\"qwen/qwen3-30b-a3b\",\"provider\":\"DeepInfra\",\"choices\":[{\"index\":0,\"delta\":{\"content\":\"\",\"role\":\"assistant\",\"reasoning\":\" the\",\"reasoning_details\":[{\"type\":\"reasoning.text\",\"text\":\" the\",\"format\":\"unknown\",\"index\":0}]},\"finish_reason\":null,\"native_finish_reason\":null}]}", - "data: {\"id\":\"gen-1791215181-RSctj8jzHB2AmoUqfU9L\",\"object\":\"chat.completion.chunk\",\"created\":1791215181,\"model\":\"qwen/qwen3-30b-a3b\",\"provider\":\"DeepInfra\",\"choices\":[{\"index\":0,\"delta\":{\"content\":\"\",\"role\":\"assistant\",\"reasoning\":\" search\",\"reasoning_details\":[{\"type\":\"reasoning.text\",\"text\":\" search\",\"format\":\"unknown\",\"index\":0}]},\"finish_reason\":null,\"native_finish_reason\":null}]}", - "data: {\"id\":\"gen-1791215181-RSctj8jzHB2AmoUqfU9L\",\"object\":\"chat.completion.chunk\",\"created\":1791215181,\"model\":\"qwen/qwen3-30b-a3b\",\"provider\":\"DeepInfra\",\"choices\":[{\"index\":0,\"delta\":{\"content\":\"\",\"role\":\"assistant\",\"reasoning\":\" tool\",\"reasoning_details\":[{\"type\":\"reasoning.text\",\"text\":\" tool\",\"format\":\"unknown\",\"index\":0}]},\"finish_reason\":null,\"native_finish_reason\":null}]}", - "data: {\"id\":\"gen-1791215181-RSctj8jzHB2AmoUqfU9L\",\"object\":\"chat.completion.chunk\",\"created\":1791215181,\"model\":\"qwen/qwen3-30b-a3b\",\"provider\":\"DeepInfra\",\"choices\":[{\"index\":0,\"delta\":{\"content\":\"\",\"role\":\"assistant\",\"reasoning\":\" with\",\"reasoning_details\":[{\"type\":\"reasoning.text\",\"text\":\" with\",\"format\":\"unknown\",\"index\":0}]},\"finish_reason\":null,\"native_finish_reason\":null}]}", - "data: {\"id\":\"gen-1791215181-RSctj8jzHB2AmoUqfU9L\",\"object\":\"chat.completion.chunk\",\"created\":1791215181,\"model\":\"qwen/qwen3-30b-a3b\",\"provider\":\"DeepInfra\",\"choices\":[{\"index\":0,\"delta\":{\"content\":\"\",\"role\":\"assistant\",\"reasoning\":\" the\",\"reasoning_details\":[{\"type\":\"reasoning.text\",\"text\":\" the\",\"format\":\"unknown\",\"index\":0}]},\"finish_reason\":null,\"native_finish_reason\":null}]}", - "data: {\"id\":\"gen-1791215181-RSctj8jzHB2AmoUqfU9L\",\"object\":\"chat.completion.chunk\",\"created\":1791215181,\"model\":\"qwen/qwen3-30b-a3b\",\"provider\":\"DeepInfra\",\"choices\":[{\"index\":0,\"delta\":{\"content\":\"\",\"role\":\"assistant\",\"reasoning\":\" exact\",\"reasoning_details\":[{\"type\":\"reasoning.text\",\"text\":\" exact\",\"format\":\"unknown\",\"index\":0}]},\"finish_reason\":null,\"native_finish_reason\":null}]}", - "data: {\"id\":\"gen-1791215181-RSctj8jzHB2AmoUqfU9L\",\"object\":\"chat.completion.chunk\",\"created\":1791215181,\"model\":\"qwen/qwen3-30b-a3b\",\"provider\":\"DeepInfra\",\"choices\":[{\"index\":0,\"delta\":{\"content\":\"\",\"role\":\"assistant\",\"reasoning\":\" query\",\"reasoning_details\":[{\"type\":\"reasoning.text\",\"text\":\" query\",\"format\":\"unknown\",\"index\":0}]},\"finish_reason\":null,\"native_finish_reason\":null}]}", - "data: {\"id\":\"gen-1791215181-RSctj8jzHB2AmoUqfU9L\",\"object\":\"chat.completion.chunk\",\"created\":1791215181,\"model\":\"qwen/qwen3-30b-a3b\",\"provider\":\"DeepInfra\",\"choices\":[{\"index\":0,\"delta\":{\"content\":\"\",\"role\":\"assistant\",\"reasoning\":\" they\",\"reasoning_details\":[{\"type\":\"reasoning.text\",\"text\":\" they\",\"format\":\"unknown\",\"index\":0}]},\"finish_reason\":null,\"native_finish_reason\":null}]}", - "data: {\"id\":\"gen-1791215181-RSctj8jzHB2AmoUqfU9L\",\"object\":\"chat.completion.chunk\",\"created\":1791215181,\"model\":\"qwen/qwen3-30b-a3b\",\"provider\":\"DeepInfra\",\"choices\":[{\"index\":0,\"delta\":{\"content\":\"\",\"role\":\"assistant\",\"reasoning\":\" provided\",\"reasoning_details\":[{\"type\":\"reasoning.text\",\"text\":\" provided\",\"format\":\"unknown\",\"index\":0}]},\"finish_reason\":null,\"native_finish_reason\":null}]}", - "data: {\"id\":\"gen-1791215181-RSctj8jzHB2AmoUqfU9L\",\"object\":\"chat.completion.chunk\",\"created\":1791215181,\"model\":\"qwen/qwen3-30b-a3b\",\"provider\":\"DeepInfra\",\"choices\":[{\"index\":0,\"delta\":{\"content\":\"\",\"role\":\"assistant\",\"reasoning\":\".\",\"reasoning_details\":[{\"type\":\"reasoning.text\",\"text\":\".\",\"format\":\"unknown\",\"index\":0}]},\"finish_reason\":null,\"native_finish_reason\":null}]}", - "data: {\"id\":\"gen-1791215181-RSctj8jzHB2AmoUqfU9L\",\"object\":\"chat.completion.chunk\",\"created\":1791215181,\"model\":\"qwen/qwen3-30b-a3b\",\"provider\":\"DeepInfra\",\"choices\":[{\"index\":0,\"delta\":{\"content\":\"\",\"role\":\"assistant\",\"reasoning\":\" The\",\"reasoning_details\":[{\"type\":\"reasoning.text\",\"text\":\" The\",\"format\":\"unknown\",\"index\":0}]},\"finish_reason\":null,\"native_finish_reason\":null}]}", - "data: {\"id\":\"gen-1791215181-RSctj8jzHB2AmoUqfU9L\",\"object\":\"chat.completion.chunk\",\"created\":1791215181,\"model\":\"qwen/qwen3-30b-a3b\",\"provider\":\"DeepInfra\",\"choices\":[{\"index\":0,\"delta\":{\"content\":\"\",\"role\":\"assistant\",\"reasoning\":\" result\",\"reasoning_details\":[{\"type\":\"reasoning.text\",\"text\":\" result\",\"format\":\"unknown\",\"index\":0}]},\"finish_reason\":null,\"native_finish_reason\":null}]}", - "data: {\"id\":\"gen-1791215181-RSctj8jzHB2AmoUqfU9L\",\"object\":\"chat.completion.chunk\",\"created\":1791215181,\"model\":\"qwen/qwen3-30b-a3b\",\"provider\":\"DeepInfra\",\"choices\":[{\"index\":0,\"delta\":{\"content\":\"\",\"role\":\"assistant\",\"reasoning\":\" came\",\"reasoning_details\":[{\"type\":\"reasoning.text\",\"text\":\" came\",\"format\":\"unknown\",\"index\":0}]},\"finish_reason\":null,\"native_finish_reason\":null}]}", - "data: {\"id\":\"gen-1791215181-RSctj8jzHB2AmoUqfU9L\",\"object\":\"chat.completion.chunk\",\"created\":1791215181,\"model\":\"qwen/qwen3-30b-a3b\",\"provider\":\"DeepInfra\",\"choices\":[{\"index\":0,\"delta\":{\"content\":\"\",\"role\":\"assistant\",\"reasoning\":\" back\",\"reasoning_details\":[{\"type\":\"reasoning.text\",\"text\":\" back\",\"format\":\"unknown\",\"index\":0}]},\"finish_reason\":null,\"native_finish_reason\":null}]}", - "data: {\"id\":\"gen-1791215181-RSctj8jzHB2AmoUqfU9L\",\"object\":\"chat.completion.chunk\",\"created\":1791215181,\"model\":\"qwen/qwen3-30b-a3b\",\"provider\":\"DeepInfra\",\"choices\":[{\"index\":0,\"delta\":{\"content\":\"\",\"role\":\"assistant\",\"reasoning\":\" with\",\"reasoning_details\":[{\"type\":\"reasoning.text\",\"text\":\" with\",\"format\":\"unknown\",\"index\":0}]},\"finish_reason\":null,\"native_finish_reason\":null}]}", - "data: {\"id\":\"gen-1791215181-RSctj8jzHB2AmoUqfU9L\",\"object\":\"chat.completion.chunk\",\"created\":1791215181,\"model\":\"qwen/qwen3-30b-a3b\",\"provider\":\"DeepInfra\",\"choices\":[{\"index\":0,\"delta\":{\"content\":\"\",\"role\":\"assistant\",\"reasoning\":\" the\",\"reasoning_details\":[{\"type\":\"reasoning.text\",\"text\":\" the\",\"format\":\"unknown\",\"index\":0}]},\"finish_reason\":null,\"native_finish_reason\":null}]}", - "data: {\"id\":\"gen-1791215181-RSctj8jzHB2AmoUqfU9L\",\"object\":\"chat.completion.chunk\",\"created\":1791215181,\"model\":\"qwen/qwen3-30b-a3b\",\"provider\":\"DeepInfra\",\"choices\":[{\"index\":0,\"delta\":{\"content\":\"\",\"role\":\"assistant\",\"reasoning\":\" title\",\"reasoning_details\":[{\"type\":\"reasoning.text\",\"text\":\" title\",\"format\":\"unknown\",\"index\":0}]},\"finish_reason\":null,\"native_finish_reason\":null}]}", - "data: {\"id\":\"gen-1791215181-RSctj8jzHB2AmoUqfU9L\",\"object\":\"chat.completion.chunk\",\"created\":1791215181,\"model\":\"qwen/qwen3-30b-a3b\",\"provider\":\"DeepInfra\",\"choices\":[{\"index\":0,\"delta\":{\"content\":\"\",\"role\":\"assistant\",\"reasoning\":\" \\\"\",\"reasoning_details\":[{\"type\":\"reasoning.text\",\"text\":\" \\\"\",\"format\":\"unknown\",\"index\":0}]},\"finish_reason\":null,\"native_finish_reason\":null}]}", - "data: {\"id\":\"gen-1791215181-RSctj8jzHB2AmoUqfU9L\",\"object\":\"chat.completion.chunk\",\"created\":1791215181,\"model\":\"qwen/qwen3-30b-a3b\",\"provider\":\"DeepInfra\",\"choices\":[{\"index\":0,\"delta\":{\"content\":\"\",\"role\":\"assistant\",\"reasoning\":\"E\",\"reasoning_details\":[{\"type\":\"reasoning.text\",\"text\":\"E\",\"format\":\"unknown\",\"index\":0}]},\"finish_reason\":null,\"native_finish_reason\":null}]}", - "data: {\"id\":\"gen-1791215181-RSctj8jzHB2AmoUqfU9L\",\"object\":\"chat.completion.chunk\",\"created\":1791215181,\"model\":\"qwen/qwen3-30b-a3b\",\"provider\":\"DeepInfra\",\"choices\":[{\"index\":0,\"delta\":{\"content\":\"\",\"role\":\"assistant\",\"reasoning\":\"ve\",\"reasoning_details\":[{\"type\":\"reasoning.text\",\"text\":\"ve\",\"format\":\"unknown\",\"index\":0}]},\"finish_reason\":null,\"native_finish_reason\":null}]}", - "data: {\"id\":\"gen-1791215181-RSctj8jzHB2AmoUqfU9L\",\"object\":\"chat.completion.chunk\",\"created\":1791215181,\"model\":\"qwen/qwen3-30b-a3b\",\"provider\":\"DeepInfra\",\"choices\":[{\"index\":0,\"delta\":{\"content\":\"\",\"role\":\"assistant\",\"reasoning\":\" instrumentation\",\"reasoning_details\":[{\"type\":\"reasoning.text\",\"text\":\" instrumentation\",\"format\":\"unknown\",\"index\":0}]},\"finish_reason\":null,\"native_finish_reason\":null}]}", - "data: {\"id\":\"gen-1791215181-RSctj8jzHB2AmoUqfU9L\",\"object\":\"chat.completion.chunk\",\"created\":1791215181,\"model\":\"qwen/qwen3-30b-a3b\",\"provider\":\"DeepInfra\",\"choices\":[{\"index\":0,\"delta\":{\"content\":\"\",\"role\":\"assistant\",\"reasoning\":\"\\\"\",\"reasoning_details\":[{\"type\":\"reasoning.text\",\"text\":\"\\\"\",\"format\":\"unknown\",\"index\":0}]},\"finish_reason\":null,\"native_finish_reason\":null}]}", - "data: {\"id\":\"gen-1791215181-RSctj8jzHB2AmoUqfU9L\",\"object\":\"chat.completion.chunk\",\"created\":1791215181,\"model\":\"qwen/qwen3-30b-a3b\",\"provider\":\"DeepInfra\",\"choices\":[{\"index\":0,\"delta\":{\"content\":\"\",\"role\":\"assistant\",\"reasoning\":\" and\",\"reasoning_details\":[{\"type\":\"reasoning.text\",\"text\":\" and\",\"format\":\"unknown\",\"index\":0}]},\"finish_reason\":null,\"native_finish_reason\":null}]}", - "data: {\"id\":\"gen-1791215181-RSctj8jzHB2AmoUqfU9L\",\"object\":\"chat.completion.chunk\",\"created\":1791215181,\"model\":\"qwen/qwen3-30b-a3b\",\"provider\":\"DeepInfra\",\"choices\":[{\"index\":0,\"delta\":{\"content\":\"\",\"role\":\"assistant\",\"reasoning\":\" a\",\"reasoning_details\":[{\"type\":\"reasoning.text\",\"text\":\" a\",\"format\":\"unknown\",\"index\":0}]},\"finish_reason\":null,\"native_finish_reason\":null}]}", - "data: {\"id\":\"gen-1791215181-RSctj8jzHB2AmoUqfU9L\",\"object\":\"chat.completion.chunk\",\"created\":1791215181,\"model\":\"qwen/qwen3-30b-a3b\",\"provider\":\"DeepInfra\",\"choices\":[{\"index\":0,\"delta\":{\"content\":\"\",\"role\":\"assistant\",\"reasoning\":\" URL\",\"reasoning_details\":[{\"type\":\"reasoning.text\",\"text\":\" URL\",\"format\":\"unknown\",\"index\":0}]},\"finish_reason\":null,\"native_finish_reason\":null}]}", - "data: {\"id\":\"gen-1791215181-RSctj8jzHB2AmoUqfU9L\",\"object\":\"chat.completion.chunk\",\"created\":1791215181,\"model\":\"qwen/qwen3-30b-a3b\",\"provider\":\"DeepInfra\",\"choices\":[{\"index\":0,\"delta\":{\"content\":\"\",\"role\":\"assistant\",\"reasoning\":\".\",\"reasoning_details\":[{\"type\":\"reasoning.text\",\"text\":\".\",\"format\":\"unknown\",\"index\":0}]},\"finish_reason\":null,\"native_finish_reason\":null}]}", - "data: {\"id\":\"gen-1791215181-RSctj8jzHB2AmoUqfU9L\",\"object\":\"chat.completion.chunk\",\"created\":1791215181,\"model\":\"qwen/qwen3-30b-a3b\",\"provider\":\"DeepInfra\",\"choices\":[{\"index\":0,\"delta\":{\"content\":\"\",\"role\":\"assistant\",\"reasoning\":\" Now\",\"reasoning_details\":[{\"type\":\"reasoning.text\",\"text\":\" Now\",\"format\":\"unknown\",\"index\":0}]},\"finish_reason\":null,\"native_finish_reason\":null}]}", - "data: {\"id\":\"gen-1791215181-RSctj8jzHB2AmoUqfU9L\",\"object\":\"chat.completion.chunk\",\"created\":1791215181,\"model\":\"qwen/qwen3-30b-a3b\",\"provider\":\"DeepInfra\",\"choices\":[{\"index\":0,\"delta\":{\"content\":\"\",\"role\":\"assistant\",\"reasoning\":\",\",\"reasoning_details\":[{\"type\":\"reasoning.text\",\"text\":\",\",\"format\":\"unknown\",\"index\":0}]},\"finish_reason\":null,\"native_finish_reason\":null}]}", - "data: {\"id\":\"gen-1791215181-RSctj8jzHB2AmoUqfU9L\",\"object\":\"chat.completion.chunk\",\"created\":1791215181,\"model\":\"qwen/qwen3-30b-a3b\",\"provider\":\"DeepInfra\",\"choices\":[{\"index\":0,\"delta\":{\"content\":\"\",\"role\":\"assistant\",\"reasoning\":\" I\",\"reasoning_details\":[{\"type\":\"reasoning.text\",\"text\":\" I\",\"format\":\"unknown\",\"index\":0}]},\"finish_reason\":null,\"native_finish_reason\":null}]}", - "data: {\"id\":\"gen-1791215181-RSctj8jzHB2AmoUqfU9L\",\"object\":\"chat.completion.chunk\",\"created\":1791215181,\"model\":\"qwen/qwen3-30b-a3b\",\"provider\":\"DeepInfra\",\"choices\":[{\"index\":0,\"delta\":{\"content\":\"\",\"role\":\"assistant\",\"reasoning\":\" should\",\"reasoning_details\":[{\"type\":\"reasoning.text\",\"text\":\" should\",\"format\":\"unknown\",\"index\":0}]},\"finish_reason\":null,\"native_finish_reason\":null}]}", - "data: {\"id\":\"gen-1791215181-RSctj8jzHB2AmoUqfU9L\",\"object\":\"chat.completion.chunk\",\"created\":1791215181,\"model\":\"qwen/qwen3-30b-a3b\",\"provider\":\"DeepInfra\",\"choices\":[{\"index\":0,\"delta\":{\"content\":\"\",\"role\":\"assistant\",\"reasoning\":\" present\",\"reasoning_details\":[{\"type\":\"reasoning.text\",\"text\":\" present\",\"format\":\"unknown\",\"index\":0}]},\"finish_reason\":null,\"native_finish_reason\":null}]}", - "data: {\"id\":\"gen-1791215181-RSctj8jzHB2AmoUqfU9L\",\"object\":\"chat.completion.chunk\",\"created\":1791215181,\"model\":\"qwen/qwen3-30b-a3b\",\"provider\":\"DeepInfra\",\"choices\":[{\"index\":0,\"delta\":{\"content\":\"\",\"role\":\"assistant\",\"reasoning\":\" that\",\"reasoning_details\":[{\"type\":\"reasoning.text\",\"text\":\" that\",\"format\":\"unknown\",\"index\":0}]},\"finish_reason\":null,\"native_finish_reason\":null}]}", - "data: {\"id\":\"gen-1791215181-RSctj8jzHB2AmoUqfU9L\",\"object\":\"chat.completion.chunk\",\"created\":1791215181,\"model\":\"qwen/qwen3-30b-a3b\",\"provider\":\"DeepInfra\",\"choices\":[{\"index\":0,\"delta\":{\"content\":\"\",\"role\":\"assistant\",\"reasoning\":\" information\",\"reasoning_details\":[{\"type\":\"reasoning.text\",\"text\":\" information\",\"format\":\"unknown\",\"index\":0}]},\"finish_reason\":null,\"native_finish_reason\":null}]}", - "data: {\"id\":\"gen-1791215181-RSctj8jzHB2AmoUqfU9L\",\"object\":\"chat.completion.chunk\",\"created\":1791215181,\"model\":\"qwen/qwen3-30b-a3b\",\"provider\":\"DeepInfra\",\"choices\":[{\"index\":0,\"delta\":{\"content\":\"\",\"role\":\"assistant\",\"reasoning\":\" clearly\",\"reasoning_details\":[{\"type\":\"reasoning.text\",\"text\":\" clearly\",\"format\":\"unknown\",\"index\":0}]},\"finish_reason\":null,\"native_finish_reason\":null}]}", - "data: {\"id\":\"gen-1791215181-RSctj8jzHB2AmoUqfU9L\",\"object\":\"chat.completion.chunk\",\"created\":1791215181,\"model\":\"qwen/qwen3-30b-a3b\",\"provider\":\"DeepInfra\",\"choices\":[{\"index\":0,\"delta\":{\"content\":\"\",\"role\":\"assistant\",\"reasoning\":\".\",\"reasoning_details\":[{\"type\":\"reasoning.text\",\"text\":\".\",\"format\":\"unknown\",\"index\":0}]},\"finish_reason\":null,\"native_finish_reason\":null}]}", - "data: {\"id\":\"gen-1791215181-RSctj8jzHB2AmoUqfU9L\",\"object\":\"chat.completion.chunk\",\"created\":1791215181,\"model\":\"qwen/qwen3-30b-a3b\",\"provider\":\"DeepInfra\",\"choices\":[{\"index\":0,\"delta\":{\"content\":\"\",\"role\":\"assistant\",\"reasoning\":\" The\",\"reasoning_details\":[{\"type\":\"reasoning.text\",\"text\":\" The\",\"format\":\"unknown\",\"index\":0}]},\"finish_reason\":null,\"native_finish_reason\":null}]}", - "data: {\"id\":\"gen-1791215181-RSctj8jzHB2AmoUqfU9L\",\"object\":\"chat.completion.chunk\",\"created\":1791215181,\"model\":\"qwen/qwen3-30b-a3b\",\"provider\":\"DeepInfra\",\"choices\":[{\"index\":0,\"delta\":{\"content\":\"\",\"role\":\"assistant\",\"reasoning\":\" instructions\",\"reasoning_details\":[{\"type\":\"reasoning.text\",\"text\":\" instructions\",\"format\":\"unknown\",\"index\":0}]},\"finish_reason\":null,\"native_finish_reason\":null}]}", - "data: {\"id\":\"gen-1791215181-RSctj8jzHB2AmoUqfU9L\",\"object\":\"chat.completion.chunk\",\"created\":1791215181,\"model\":\"qwen/qwen3-30b-a3b\",\"provider\":\"DeepInfra\",\"choices\":[{\"index\":0,\"delta\":{\"content\":\"\",\"role\":\"assistant\",\"reasoning\":\" say\",\"reasoning_details\":[{\"type\":\"reasoning.text\",\"text\":\" say\",\"format\":\"unknown\",\"index\":0}]},\"finish_reason\":null,\"native_finish_reason\":null}]}", - "data: {\"id\":\"gen-1791215181-RSctj8jzHB2AmoUqfU9L\",\"object\":\"chat.completion.chunk\",\"created\":1791215181,\"model\":\"qwen/qwen3-30b-a3b\",\"provider\":\"DeepInfra\",\"choices\":[{\"index\":0,\"delta\":{\"content\":\"\",\"role\":\"assistant\",\"reasoning\":\" to\",\"reasoning_details\":[{\"type\":\"reasoning.text\",\"text\":\" to\",\"format\":\"unknown\",\"index\":0}]},\"finish_reason\":null,\"native_finish_reason\":null}]}", - "data: {\"id\":\"gen-1791215181-RSctj8jzHB2AmoUqfU9L\",\"object\":\"chat.completion.chunk\",\"created\":1791215181,\"model\":\"qwen/qwen3-30b-a3b\",\"provider\":\"DeepInfra\",\"choices\":[{\"index\":0,\"delta\":{\"content\":\"\",\"role\":\"assistant\",\"reasoning\":\" start\",\"reasoning_details\":[{\"type\":\"reasoning.text\",\"text\":\" start\",\"format\":\"unknown\",\"index\":0}]},\"finish_reason\":null,\"native_finish_reason\":null}]}", - "data: {\"id\":\"gen-1791215181-RSctj8jzHB2AmoUqfU9L\",\"object\":\"chat.completion.chunk\",\"created\":1791215181,\"model\":\"qwen/qwen3-30b-a3b\",\"provider\":\"DeepInfra\",\"choices\":[{\"index\":0,\"delta\":{\"content\":\"\",\"role\":\"assistant\",\"reasoning\":\" the\",\"reasoning_details\":[{\"type\":\"reasoning.text\",\"text\":\" the\",\"format\":\"unknown\",\"index\":0}]},\"finish_reason\":null,\"native_finish_reason\":null}]}", - "data: {\"id\":\"gen-1791215181-RSctj8jzHB2AmoUqfU9L\",\"object\":\"chat.completion.chunk\",\"created\":1791215181,\"model\":\"qwen/qwen3-30b-a3b\",\"provider\":\"DeepInfra\",\"choices\":[{\"index\":0,\"delta\":{\"content\":\"\",\"role\":\"assistant\",\"reasoning\":\" answer\",\"reasoning_details\":[{\"type\":\"reasoning.text\",\"text\":\" answer\",\"format\":\"unknown\",\"index\":0}]},\"finish_reason\":null,\"native_finish_reason\":null}]}", - "data: {\"id\":\"gen-1791215181-RSctj8jzHB2AmoUqfU9L\",\"object\":\"chat.completion.chunk\",\"created\":1791215181,\"model\":\"qwen/qwen3-30b-a3b\",\"provider\":\"DeepInfra\",\"choices\":[{\"index\":0,\"delta\":{\"content\":\"\",\"role\":\"assistant\",\"reasoning\":\" with\",\"reasoning_details\":[{\"type\":\"reasoning.text\",\"text\":\" with\",\"format\":\"unknown\",\"index\":0}]},\"finish_reason\":null,\"native_finish_reason\":null}]}", - "data: {\"id\":\"gen-1791215181-RSctj8jzHB2AmoUqfU9L\",\"object\":\"chat.completion.chunk\",\"created\":1791215181,\"model\":\"qwen/qwen3-30b-a3b\",\"provider\":\"DeepInfra\",\"choices\":[{\"index\":0,\"delta\":{\"content\":\"\",\"role\":\"assistant\",\"reasoning\":\" \\\"\",\"reasoning_details\":[{\"type\":\"reasoning.text\",\"text\":\" \\\"\",\"format\":\"unknown\",\"index\":0}]},\"finish_reason\":null,\"native_finish_reason\":null}]}", - "data: {\"id\":\"gen-1791215181-RSctj8jzHB2AmoUqfU9L\",\"object\":\"chat.completion.chunk\",\"created\":1791215181,\"model\":\"qwen/qwen3-30b-a3b\",\"provider\":\"DeepInfra\",\"choices\":[{\"index\":0,\"delta\":{\"content\":\"\",\"role\":\"assistant\",\"reasoning\":\"Research\",\"reasoning_details\":[{\"type\":\"reasoning.text\",\"text\":\"Research\",\"format\":\"unknown\",\"index\":0}]},\"finish_reason\":null,\"native_finish_reason\":null}]}", - "data: {\"id\":\"gen-1791215181-RSctj8jzHB2AmoUqfU9L\",\"object\":\"chat.completion.chunk\",\"created\":1791215181,\"model\":\"qwen/qwen3-30b-a3b\",\"provider\":\"DeepInfra\",\"choices\":[{\"index\":0,\"delta\":{\"content\":\"\",\"role\":\"assistant\",\"reasoning\":\"er\",\"reasoning_details\":[{\"type\":\"reasoning.text\",\"text\":\"er\",\"format\":\"unknown\",\"index\":0}]},\"finish_reason\":null,\"native_finish_reason\":null}]}", - "data: {\"id\":\"gen-1791215181-RSctj8jzHB2AmoUqfU9L\",\"object\":\"chat.completion.chunk\",\"created\":1791215181,\"model\":\"qwen/qwen3-30b-a3b\",\"provider\":\"DeepInfra\",\"choices\":[{\"index\":0,\"delta\":{\"content\":\"\",\"role\":\"assistant\",\"reasoning\":\" result\",\"reasoning_details\":[{\"type\":\"reasoning.text\",\"text\":\" result\",\"format\":\"unknown\",\"index\":0}]},\"finish_reason\":null,\"native_finish_reason\":null}]}", - "data: {\"id\":\"gen-1791215181-RSctj8jzHB2AmoUqfU9L\",\"object\":\"chat.completion.chunk\",\"created\":1791215181,\"model\":\"qwen/qwen3-30b-a3b\",\"provider\":\"DeepInfra\",\"choices\":[{\"index\":0,\"delta\":{\"content\":\"\",\"role\":\"assistant\",\"reasoning\":\":\\\"\",\"reasoning_details\":[{\"type\":\"reasoning.text\",\"text\":\":\\\"\",\"format\":\"unknown\",\"index\":0}]},\"finish_reason\":null,\"native_finish_reason\":null}]}", - "data: {\"id\":\"gen-1791215181-RSctj8jzHB2AmoUqfU9L\",\"object\":\"chat.completion.chunk\",\"created\":1791215181,\"model\":\"qwen/qwen3-30b-a3b\",\"provider\":\"DeepInfra\",\"choices\":[{\"index\":0,\"delta\":{\"content\":\"\",\"role\":\"assistant\",\"reasoning\":\" and\",\"reasoning_details\":[{\"type\":\"reasoning.text\",\"text\":\" and\",\"format\":\"unknown\",\"index\":0}]},\"finish_reason\":null,\"native_finish_reason\":null}]}", - "data: {\"id\":\"gen-1791215181-RSctj8jzHB2AmoUqfU9L\",\"object\":\"chat.completion.chunk\",\"created\":1791215181,\"model\":\"qwen/qwen3-30b-a3b\",\"provider\":\"DeepInfra\",\"choices\":[{\"index\":0,\"delta\":{\"content\":\"\",\"role\":\"assistant\",\"reasoning\":\" include\",\"reasoning_details\":[{\"type\":\"reasoning.text\",\"text\":\" include\",\"format\":\"unknown\",\"index\":0}]},\"finish_reason\":null,\"native_finish_reason\":null}]}", - "data: {\"id\":\"gen-1791215181-RSctj8jzHB2AmoUqfU9L\",\"object\":\"chat.completion.chunk\",\"created\":1791215181,\"model\":\"qwen/qwen3-30b-a3b\",\"provider\":\"DeepInfra\",\"choices\":[{\"index\":0,\"delta\":{\"content\":\"\",\"role\":\"assistant\",\"reasoning\":\" the\",\"reasoning_details\":[{\"type\":\"reasoning.text\",\"text\":\" the\",\"format\":\"unknown\",\"index\":0}]},\"finish_reason\":null,\"native_finish_reason\":null}]}", - "data: {\"id\":\"gen-1791215181-RSctj8jzHB2AmoUqfU9L\",\"object\":\"chat.completion.chunk\",\"created\":1791215181,\"model\":\"qwen/qwen3-30b-a3b\",\"provider\":\"DeepInfra\",\"choices\":[{\"index\":0,\"delta\":{\"content\":\"\",\"role\":\"assistant\",\"reasoning\":\" title\",\"reasoning_details\":[{\"type\":\"reasoning.text\",\"text\":\" title\",\"format\":\"unknown\",\"index\":0}]},\"finish_reason\":null,\"native_finish_reason\":null}]}", - "data: {\"id\":\"gen-1791215181-RSctj8jzHB2AmoUqfU9L\",\"object\":\"chat.completion.chunk\",\"created\":1791215181,\"model\":\"qwen/qwen3-30b-a3b\",\"provider\":\"DeepInfra\",\"choices\":[{\"index\":0,\"delta\":{\"content\":\"\",\"role\":\"assistant\",\"reasoning\":\" and\",\"reasoning_details\":[{\"type\":\"reasoning.text\",\"text\":\" and\",\"format\":\"unknown\",\"index\":0}]},\"finish_reason\":null,\"native_finish_reason\":null}]}", - "data: {\"id\":\"gen-1791215181-RSctj8jzHB2AmoUqfU9L\",\"object\":\"chat.completion.chunk\",\"created\":1791215181,\"model\":\"qwen/qwen3-30b-a3b\",\"provider\":\"DeepInfra\",\"choices\":[{\"index\":0,\"delta\":{\"content\":\"\",\"role\":\"assistant\",\"reasoning\":\" URL\",\"reasoning_details\":[{\"type\":\"reasoning.text\",\"text\":\" URL\",\"format\":\"unknown\",\"index\":0}]},\"finish_reason\":null,\"native_finish_reason\":null}]}", - "data: {\"id\":\"gen-1791215181-RSctj8jzHB2AmoUqfU9L\",\"object\":\"chat.completion.chunk\",\"created\":1791215181,\"model\":\"qwen/qwen3-30b-a3b\",\"provider\":\"DeepInfra\",\"choices\":[{\"index\":0,\"delta\":{\"content\":\"\",\"role\":\"assistant\",\"reasoning\":\".\",\"reasoning_details\":[{\"type\":\"reasoning.text\",\"text\":\".\",\"format\":\"unknown\",\"index\":0}]},\"finish_reason\":null,\"native_finish_reason\":null}]}", - "data: {\"id\":\"gen-1791215181-RSctj8jzHB2AmoUqfU9L\",\"object\":\"chat.completion.chunk\",\"created\":1791215181,\"model\":\"qwen/qwen3-30b-a3b\",\"provider\":\"DeepInfra\",\"choices\":[{\"index\":0,\"delta\":{\"content\":\"\",\"role\":\"assistant\",\"reasoning\":\" I\",\"reasoning_details\":[{\"type\":\"reasoning.text\",\"text\":\" I\",\"format\":\"unknown\",\"index\":0}]},\"finish_reason\":null,\"native_finish_reason\":null}]}", - "data: {\"id\":\"gen-1791215181-RSctj8jzHB2AmoUqfU9L\",\"object\":\"chat.completion.chunk\",\"created\":1791215181,\"model\":\"qwen/qwen3-30b-a3b\",\"provider\":\"DeepInfra\",\"choices\":[{\"index\":0,\"delta\":{\"content\":\"\",\"role\":\"assistant\",\"reasoning\":\" need\",\"reasoning_details\":[{\"type\":\"reasoning.text\",\"text\":\" need\",\"format\":\"unknown\",\"index\":0}]},\"finish_reason\":null,\"native_finish_reason\":null}]}", - "data: {\"id\":\"gen-1791215181-RSctj8jzHB2AmoUqfU9L\",\"object\":\"chat.completion.chunk\",\"created\":1791215181,\"model\":\"qwen/qwen3-30b-a3b\",\"provider\":\"DeepInfra\",\"choices\":[{\"index\":0,\"delta\":{\"content\":\"\",\"role\":\"assistant\",\"reasoning\":\" to\",\"reasoning_details\":[{\"type\":\"reasoning.text\",\"text\":\" to\",\"format\":\"unknown\",\"index\":0}]},\"finish_reason\":null,\"native_finish_reason\":null}]}", - "data: {\"id\":\"gen-1791215181-RSctj8jzHB2AmoUqfU9L\",\"object\":\"chat.completion.chunk\",\"created\":1791215181,\"model\":\"qwen/qwen3-30b-a3b\",\"provider\":\"DeepInfra\",\"choices\":[{\"index\":0,\"delta\":{\"content\":\"\",\"role\":\"assistant\",\"reasoning\":\" make\",\"reasoning_details\":[{\"type\":\"reasoning.text\",\"text\":\" make\",\"format\":\"unknown\",\"index\":0}]},\"finish_reason\":null,\"native_finish_reason\":null}]}", - "data: {\"id\":\"gen-1791215181-RSctj8jzHB2AmoUqfU9L\",\"object\":\"chat.completion.chunk\",\"created\":1791215181,\"model\":\"qwen/qwen3-30b-a3b\",\"provider\":\"DeepInfra\",\"choices\":[{\"index\":0,\"delta\":{\"content\":\"\",\"role\":\"assistant\",\"reasoning\":\" sure\",\"reasoning_details\":[{\"type\":\"reasoning.text\",\"text\":\" sure\",\"format\":\"unknown\",\"index\":0}]},\"finish_reason\":null,\"native_finish_reason\":null}]}", - "data: {\"id\":\"gen-1791215181-RSctj8jzHB2AmoUqfU9L\",\"object\":\"chat.completion.chunk\",\"created\":1791215181,\"model\":\"qwen/qwen3-30b-a3b\",\"provider\":\"DeepInfra\",\"choices\":[{\"index\":0,\"delta\":{\"content\":\"\",\"role\":\"assistant\",\"reasoning\":\" it\",\"reasoning_details\":[{\"type\":\"reasoning.text\",\"text\":\" it\",\"format\":\"unknown\",\"index\":0}]},\"finish_reason\":null,\"native_finish_reason\":null}]}", - "data: {\"id\":\"gen-1791215181-RSctj8jzHB2AmoUqfU9L\",\"object\":\"chat.completion.chunk\",\"created\":1791215181,\"model\":\"qwen/qwen3-30b-a3b\",\"provider\":\"DeepInfra\",\"choices\":[{\"index\":0,\"delta\":{\"content\":\"\",\"role\":\"assistant\",\"reasoning\":\"'s\",\"reasoning_details\":[{\"type\":\"reasoning.text\",\"text\":\"'s\",\"format\":\"unknown\",\"index\":0}]},\"finish_reason\":null,\"native_finish_reason\":null}]}", - "data: {\"id\":\"gen-1791215181-RSctj8jzHB2AmoUqfU9L\",\"object\":\"chat.completion.chunk\",\"created\":1791215181,\"model\":\"qwen/qwen3-30b-a3b\",\"provider\":\"DeepInfra\",\"choices\":[{\"index\":0,\"delta\":{\"content\":\"\",\"role\":\"assistant\",\"reasoning\":\" a\",\"reasoning_details\":[{\"type\":\"reasoning.text\",\"text\":\" a\",\"format\":\"unknown\",\"index\":0}]},\"finish_reason\":null,\"native_finish_reason\":null}]}", - "data: {\"id\":\"gen-1791215181-RSctj8jzHB2AmoUqfU9L\",\"object\":\"chat.completion.chunk\",\"created\":1791215181,\"model\":\"qwen/qwen3-30b-a3b\",\"provider\":\"DeepInfra\",\"choices\":[{\"index\":0,\"delta\":{\"content\":\"\",\"role\":\"assistant\",\"reasoning\":\" single\",\"reasoning_details\":[{\"type\":\"reasoning.text\",\"text\":\" single\",\"format\":\"unknown\",\"index\":0}]},\"finish_reason\":null,\"native_finish_reason\":null}]}", - "data: {\"id\":\"gen-1791215181-RSctj8jzHB2AmoUqfU9L\",\"object\":\"chat.completion.chunk\",\"created\":1791215181,\"model\":\"qwen/qwen3-30b-a3b\",\"provider\":\"DeepInfra\",\"choices\":[{\"index\":0,\"delta\":{\"content\":\"\",\"role\":\"assistant\",\"reasoning\":\" sentence\",\"reasoning_details\":[{\"type\":\"reasoning.text\",\"text\":\" sentence\",\"format\":\"unknown\",\"index\":0}]},\"finish_reason\":null,\"native_finish_reason\":null}]}", - "data: {\"id\":\"gen-1791215181-RSctj8jzHB2AmoUqfU9L\",\"object\":\"chat.completion.chunk\",\"created\":1791215181,\"model\":\"qwen/qwen3-30b-a3b\",\"provider\":\"DeepInfra\",\"choices\":[{\"index\":0,\"delta\":{\"content\":\"\",\"role\":\"assistant\",\"reasoning\":\".\",\"reasoning_details\":[{\"type\":\"reasoning.text\",\"text\":\".\",\"format\":\"unknown\",\"index\":0}]},\"finish_reason\":null,\"native_finish_reason\":null}]}", - "data: {\"id\":\"gen-1791215181-RSctj8jzHB2AmoUqfU9L\",\"object\":\"chat.completion.chunk\",\"created\":1791215181,\"model\":\"qwen/qwen3-30b-a3b\",\"provider\":\"DeepInfra\",\"choices\":[{\"index\":0,\"delta\":{\"content\":\"\",\"role\":\"assistant\",\"reasoning\":\" Let\",\"reasoning_details\":[{\"type\":\"reasoning.text\",\"text\":\" Let\",\"format\":\"unknown\",\"index\":0}]},\"finish_reason\":null,\"native_finish_reason\":null}]}", - "data: {\"id\":\"gen-1791215181-RSctj8jzHB2AmoUqfU9L\",\"object\":\"chat.completion.chunk\",\"created\":1791215181,\"model\":\"qwen/qwen3-30b-a3b\",\"provider\":\"DeepInfra\",\"choices\":[{\"index\":0,\"delta\":{\"content\":\"\",\"role\":\"assistant\",\"reasoning\":\" me\",\"reasoning_details\":[{\"type\":\"reasoning.text\",\"text\":\" me\",\"format\":\"unknown\",\"index\":0}]},\"finish_reason\":null,\"native_finish_reason\":null}]}", - "data: {\"id\":\"gen-1791215181-RSctj8jzHB2AmoUqfU9L\",\"object\":\"chat.completion.chunk\",\"created\":1791215181,\"model\":\"qwen/qwen3-30b-a3b\",\"provider\":\"DeepInfra\",\"choices\":[{\"index\":0,\"delta\":{\"content\":\"\",\"role\":\"assistant\",\"reasoning\":\" format\",\"reasoning_details\":[{\"type\":\"reasoning.text\",\"text\":\" format\",\"format\":\"unknown\",\"index\":0}]},\"finish_reason\":null,\"native_finish_reason\":null}]}", - "data: {\"id\":\"gen-1791215181-RSctj8jzHB2AmoUqfU9L\",\"object\":\"chat.completion.chunk\",\"created\":1791215181,\"model\":\"qwen/qwen3-30b-a3b\",\"provider\":\"DeepInfra\",\"choices\":[{\"index\":0,\"delta\":{\"content\":\"\",\"role\":\"assistant\",\"reasoning\":\" it\",\"reasoning_details\":[{\"type\":\"reasoning.text\",\"text\":\" it\",\"format\":\"unknown\",\"index\":0}]},\"finish_reason\":null,\"native_finish_reason\":null}]}", - "data: {\"id\":\"gen-1791215181-RSctj8jzHB2AmoUqfU9L\",\"object\":\"chat.completion.chunk\",\"created\":1791215181,\"model\":\"qwen/qwen3-30b-a3b\",\"provider\":\"DeepInfra\",\"choices\":[{\"index\":0,\"delta\":{\"content\":\"\",\"role\":\"assistant\",\"reasoning\":\" correctly\",\"reasoning_details\":[{\"type\":\"reasoning.text\",\"text\":\" correctly\",\"format\":\"unknown\",\"index\":0}]},\"finish_reason\":null,\"native_finish_reason\":null}]}", - "data: {\"id\":\"gen-1791215181-RSctj8jzHB2AmoUqfU9L\",\"object\":\"chat.completion.chunk\",\"created\":1791215181,\"model\":\"qwen/qwen3-30b-a3b\",\"provider\":\"DeepInfra\",\"choices\":[{\"index\":0,\"delta\":{\"content\":\"\",\"role\":\"assistant\",\"reasoning\":\".\\n\",\"reasoning_details\":[{\"type\":\"reasoning.text\",\"text\":\".\\n\",\"format\":\"unknown\",\"index\":0}]},\"finish_reason\":null,\"native_finish_reason\":null}]}", - "data: {\"id\":\"gen-1791215181-RSctj8jzHB2AmoUqfU9L\",\"object\":\"chat.completion.chunk\",\"created\":1791215181,\"model\":\"qwen/qwen3-30b-a3b\",\"provider\":\"DeepInfra\",\"choices\":[{\"index\":0,\"delta\":{\"content\":\"Research\",\"role\":\"assistant\"},\"finish_reason\":null,\"native_finish_reason\":null}]}", - "data: {\"id\":\"gen-1791215181-RSctj8jzHB2AmoUqfU9L\",\"object\":\"chat.completion.chunk\",\"created\":1791215181,\"model\":\"qwen/qwen3-30b-a3b\",\"provider\":\"DeepInfra\",\"choices\":[{\"index\":0,\"delta\":{\"content\":\"er\",\"role\":\"assistant\"},\"finish_reason\":null,\"native_finish_reason\":null}]}", - "data: {\"id\":\"gen-1791215181-RSctj8jzHB2AmoUqfU9L\",\"object\":\"chat.completion.chunk\",\"created\":1791215181,\"model\":\"qwen/qwen3-30b-a3b\",\"provider\":\"DeepInfra\",\"choices\":[{\"index\":0,\"delta\":{\"content\":\" result\",\"role\":\"assistant\"},\"finish_reason\":null,\"native_finish_reason\":null}]}", - "data: {\"id\":\"gen-1791215181-RSctj8jzHB2AmoUqfU9L\",\"object\":\"chat.completion.chunk\",\"created\":1791215181,\"model\":\"qwen/qwen3-30b-a3b\",\"provider\":\"DeepInfra\",\"choices\":[{\"index\":0,\"delta\":{\"content\":\":\",\"role\":\"assistant\"},\"finish_reason\":null,\"native_finish_reason\":null}]}", - "data: {\"id\":\"gen-1791215181-RSctj8jzHB2AmoUqfU9L\",\"object\":\"chat.completion.chunk\",\"created\":1791215181,\"model\":\"qwen/qwen3-30b-a3b\",\"provider\":\"DeepInfra\",\"choices\":[{\"index\":0,\"delta\":{\"content\":\" The\",\"role\":\"assistant\"},\"finish_reason\":null,\"native_finish_reason\":null}]}", - "data: {\"id\":\"gen-1791215181-RSctj8jzHB2AmoUqfU9L\",\"object\":\"chat.completion.chunk\",\"created\":1791215181,\"model\":\"qwen/qwen3-30b-a3b\",\"provider\":\"DeepInfra\",\"choices\":[{\"index\":0,\"delta\":{\"content\":\" Brain\",\"role\":\"assistant\"},\"finish_reason\":null,\"native_finish_reason\":null}]}", - "data: {\"id\":\"gen-1791215181-RSctj8jzHB2AmoUqfU9L\",\"object\":\"chat.completion.chunk\",\"created\":1791215181,\"model\":\"qwen/qwen3-30b-a3b\",\"provider\":\"DeepInfra\",\"choices\":[{\"index\":0,\"delta\":{\"content\":\"trust\",\"role\":\"assistant\"},\"finish_reason\":null,\"native_finish_reason\":null}]}", - "data: {\"id\":\"gen-1791215181-RSctj8jzHB2AmoUqfU9L\",\"object\":\"chat.completion.chunk\",\"created\":1791215181,\"model\":\"qwen/qwen3-30b-a3b\",\"provider\":\"DeepInfra\",\"choices\":[{\"index\":0,\"delta\":{\"content\":\" Eve\",\"role\":\"assistant\"},\"finish_reason\":null,\"native_finish_reason\":null}]}", - "data: {\"id\":\"gen-1791215181-RSctj8jzHB2AmoUqfU9L\",\"object\":\"chat.completion.chunk\",\"created\":1791215181,\"model\":\"qwen/qwen3-30b-a3b\",\"provider\":\"DeepInfra\",\"choices\":[{\"index\":0,\"delta\":{\"content\":\" instrumentation\",\"role\":\"assistant\"},\"finish_reason\":null,\"native_finish_reason\":null}]}", - "data: {\"id\":\"gen-1791215181-RSctj8jzHB2AmoUqfU9L\",\"object\":\"chat.completion.chunk\",\"created\":1791215181,\"model\":\"qwen/qwen3-30b-a3b\",\"provider\":\"DeepInfra\",\"choices\":[{\"index\":0,\"delta\":{\"content\":\" documentation\",\"role\":\"assistant\"},\"finish_reason\":null,\"native_finish_reason\":null}]}", - "data: {\"id\":\"gen-1791215181-RSctj8jzHB2AmoUqfU9L\",\"object\":\"chat.completion.chunk\",\"created\":1791215181,\"model\":\"qwen/qwen3-30b-a3b\",\"provider\":\"DeepInfra\",\"choices\":[{\"index\":0,\"delta\":{\"content\":\" can\",\"role\":\"assistant\"},\"finish_reason\":null,\"native_finish_reason\":null}]}", - "data: {\"id\":\"gen-1791215181-RSctj8jzHB2AmoUqfU9L\",\"object\":\"chat.completion.chunk\",\"created\":1791215181,\"model\":\"qwen/qwen3-30b-a3b\",\"provider\":\"DeepInfra\",\"choices\":[{\"index\":0,\"delta\":{\"content\":\" be\",\"role\":\"assistant\"},\"finish_reason\":null,\"native_finish_reason\":null}]}", - "data: {\"id\":\"gen-1791215181-RSctj8jzHB2AmoUqfU9L\",\"object\":\"chat.completion.chunk\",\"created\":1791215181,\"model\":\"qwen/qwen3-30b-a3b\",\"provider\":\"DeepInfra\",\"choices\":[{\"index\":0,\"delta\":{\"content\":\" found\",\"role\":\"assistant\"},\"finish_reason\":null,\"native_finish_reason\":null}]}", - "data: {\"id\":\"gen-1791215181-RSctj8jzHB2AmoUqfU9L\",\"object\":\"chat.completion.chunk\",\"created\":1791215181,\"model\":\"qwen/qwen3-30b-a3b\",\"provider\":\"DeepInfra\",\"choices\":[{\"index\":0,\"delta\":{\"content\":\" here\",\"role\":\"assistant\"},\"finish_reason\":null,\"native_finish_reason\":null}]}", - "data: {\"id\":\"gen-1791215181-RSctj8jzHB2AmoUqfU9L\",\"object\":\"chat.completion.chunk\",\"created\":1791215181,\"model\":\"qwen/qwen3-30b-a3b\",\"provider\":\"DeepInfra\",\"choices\":[{\"index\":0,\"delta\":{\"content\":\":\",\"role\":\"assistant\"},\"finish_reason\":null,\"native_finish_reason\":null}]}", - "data: {\"id\":\"gen-1791215181-RSctj8jzHB2AmoUqfU9L\",\"object\":\"chat.completion.chunk\",\"created\":1791215181,\"model\":\"qwen/qwen3-30b-a3b\",\"provider\":\"DeepInfra\",\"choices\":[{\"index\":0,\"delta\":{\"content\":\" [\",\"role\":\"assistant\"},\"finish_reason\":null,\"native_finish_reason\":null}]}", - "data: {\"id\":\"gen-1791215181-RSctj8jzHB2AmoUqfU9L\",\"object\":\"chat.completion.chunk\",\"created\":1791215181,\"model\":\"qwen/qwen3-30b-a3b\",\"provider\":\"DeepInfra\",\"choices\":[{\"index\":0,\"delta\":{\"content\":\"E\",\"role\":\"assistant\"},\"finish_reason\":null,\"native_finish_reason\":null}]}", - "data: {\"id\":\"gen-1791215181-RSctj8jzHB2AmoUqfU9L\",\"object\":\"chat.completion.chunk\",\"created\":1791215181,\"model\":\"qwen/qwen3-30b-a3b\",\"provider\":\"DeepInfra\",\"choices\":[{\"index\":0,\"delta\":{\"content\":\"ve\",\"role\":\"assistant\"},\"finish_reason\":null,\"native_finish_reason\":null}]}", - "data: {\"id\":\"gen-1791215181-RSctj8jzHB2AmoUqfU9L\",\"object\":\"chat.completion.chunk\",\"created\":1791215181,\"model\":\"qwen/qwen3-30b-a3b\",\"provider\":\"DeepInfra\",\"choices\":[{\"index\":0,\"delta\":{\"content\":\" instrumentation\",\"role\":\"assistant\"},\"finish_reason\":null,\"native_finish_reason\":null}]}", - "data: {\"id\":\"gen-1791215181-RSctj8jzHB2AmoUqfU9L\",\"object\":\"chat.completion.chunk\",\"created\":1791215181,\"model\":\"qwen/qwen3-30b-a3b\",\"provider\":\"DeepInfra\",\"choices\":[{\"index\":0,\"delta\":{\"content\":\"](\",\"role\":\"assistant\"},\"finish_reason\":null,\"native_finish_reason\":null}]}", - "data: {\"id\":\"gen-1791215181-RSctj8jzHB2AmoUqfU9L\",\"object\":\"chat.completion.chunk\",\"created\":1791215181,\"model\":\"qwen/qwen3-30b-a3b\",\"provider\":\"DeepInfra\",\"choices\":[{\"index\":0,\"delta\":{\"content\":\"https\",\"role\":\"assistant\"},\"finish_reason\":null,\"native_finish_reason\":null}]}", - "data: {\"id\":\"gen-1791215181-RSctj8jzHB2AmoUqfU9L\",\"object\":\"chat.completion.chunk\",\"created\":1791215181,\"model\":\"qwen/qwen3-30b-a3b\",\"provider\":\"DeepInfra\",\"choices\":[{\"index\":0,\"delta\":{\"content\":\"://\",\"role\":\"assistant\"},\"finish_reason\":null,\"native_finish_reason\":null}]}", - "data: {\"id\":\"gen-1791215181-RSctj8jzHB2AmoUqfU9L\",\"object\":\"chat.completion.chunk\",\"created\":1791215181,\"model\":\"qwen/qwen3-30b-a3b\",\"provider\":\"DeepInfra\",\"choices\":[{\"index\":0,\"delta\":{\"content\":\"eve\",\"role\":\"assistant\"},\"finish_reason\":null,\"native_finish_reason\":null}]}", - "data: {\"id\":\"gen-1791215181-RSctj8jzHB2AmoUqfU9L\",\"object\":\"chat.completion.chunk\",\"created\":1791215181,\"model\":\"qwen/qwen3-30b-a3b\",\"provider\":\"DeepInfra\",\"choices\":[{\"index\":0,\"delta\":{\"content\":\".dev\",\"role\":\"assistant\"},\"finish_reason\":null,\"native_finish_reason\":null}]}", - "data: {\"id\":\"gen-1791215181-RSctj8jzHB2AmoUqfU9L\",\"object\":\"chat.completion.chunk\",\"created\":1791215181,\"model\":\"qwen/qwen3-30b-a3b\",\"provider\":\"DeepInfra\",\"choices\":[{\"index\":0,\"delta\":{\"content\":\"/docs\",\"role\":\"assistant\"},\"finish_reason\":null,\"native_finish_reason\":null}]}", - "data: {\"id\":\"gen-1791215181-RSctj8jzHB2AmoUqfU9L\",\"object\":\"chat.completion.chunk\",\"created\":1791215181,\"model\":\"qwen/qwen3-30b-a3b\",\"provider\":\"DeepInfra\",\"choices\":[{\"index\":0,\"delta\":{\"content\":\"/g\",\"role\":\"assistant\"},\"finish_reason\":null,\"native_finish_reason\":null}]}", - "data: {\"id\":\"gen-1791215181-RSctj8jzHB2AmoUqfU9L\",\"object\":\"chat.completion.chunk\",\"created\":1791215181,\"model\":\"qwen/qwen3-30b-a3b\",\"provider\":\"DeepInfra\",\"choices\":[{\"index\":0,\"delta\":{\"content\":\"uid\",\"role\":\"assistant\"},\"finish_reason\":null,\"native_finish_reason\":null}]}", - "data: {\"id\":\"gen-1791215181-RSctj8jzHB2AmoUqfU9L\",\"object\":\"chat.completion.chunk\",\"created\":1791215181,\"model\":\"qwen/qwen3-30b-a3b\",\"provider\":\"DeepInfra\",\"choices\":[{\"index\":0,\"delta\":{\"content\":\"es\",\"role\":\"assistant\"},\"finish_reason\":null,\"native_finish_reason\":null}]}", - "data: {\"id\":\"gen-1791215181-RSctj8jzHB2AmoUqfU9L\",\"object\":\"chat.completion.chunk\",\"created\":1791215181,\"model\":\"qwen/qwen3-30b-a3b\",\"provider\":\"DeepInfra\",\"choices\":[{\"index\":0,\"delta\":{\"content\":\"/in\",\"role\":\"assistant\"},\"finish_reason\":null,\"native_finish_reason\":null}]}", - "data: {\"id\":\"gen-1791215181-RSctj8jzHB2AmoUqfU9L\",\"object\":\"chat.completion.chunk\",\"created\":1791215181,\"model\":\"qwen/qwen3-30b-a3b\",\"provider\":\"DeepInfra\",\"choices\":[{\"index\":0,\"delta\":{\"content\":\"strument\",\"role\":\"assistant\"},\"finish_reason\":null,\"native_finish_reason\":null}]}", - "data: {\"id\":\"gen-1791215181-RSctj8jzHB2AmoUqfU9L\",\"object\":\"chat.completion.chunk\",\"created\":1791215181,\"model\":\"qwen/qwen3-30b-a3b\",\"provider\":\"DeepInfra\",\"choices\":[{\"index\":0,\"delta\":{\"content\":\"ation\",\"role\":\"assistant\"},\"finish_reason\":null,\"native_finish_reason\":null}]}", - "data: {\"id\":\"gen-1791215181-RSctj8jzHB2AmoUqfU9L\",\"object\":\"chat.completion.chunk\",\"created\":1791215181,\"model\":\"qwen/qwen3-30b-a3b\",\"provider\":\"DeepInfra\",\"choices\":[{\"index\":0,\"delta\":{\"content\":\").\",\"role\":\"assistant\"},\"finish_reason\":null,\"native_finish_reason\":null}]}", - "data: {\"id\":\"gen-1791215181-RSctj8jzHB2AmoUqfU9L\",\"object\":\"chat.completion.chunk\",\"created\":1791215181,\"model\":\"qwen/qwen3-30b-a3b\",\"provider\":\"DeepInfra\",\"choices\":[{\"index\":0,\"delta\":{\"content\":\"\",\"role\":\"assistant\",\"reasoning\":null},\"finish_reason\":\"stop\",\"native_finish_reason\":\"stop\"}]}", - "data: {\"id\":\"gen-1791215181-RSctj8jzHB2AmoUqfU9L\",\"object\":\"chat.completion.chunk\",\"created\":1791215181,\"model\":\"qwen/qwen3-30b-a3b\",\"provider\":\"DeepInfra\",\"service_tier\":\"default\",\"choices\":[{\"index\":0,\"delta\":{\"content\":\"\",\"role\":\"assistant\"},\"finish_reason\":\"stop\",\"native_finish_reason\":\"stop\"}],\"usage\":{\"prompt_tokens\":3256,\"completion_tokens\":144,\"total_tokens\":3400,\"cost\":0.00046272,\"is_byok\":false,\"prompt_tokens_details\":{\"cached_tokens\":0,\"cache_write_tokens\":0,\"audio_tokens\":0,\"video_tokens\":0},\"cost_details\":{\"upstream_inference_cost\":0.00046272,\"upstream_inference_prompt_cost\":0.00039072,\"upstream_inference_completions_cost\":0.000072},\"completion_tokens_details\":{\"reasoning_tokens\":128,\"image_tokens\":0,\"audio_tokens\":0}}}", - "data: [DONE]" - ], - "kind": "sse" - }, - "headers": { - "access-control-allow-origin": "*", - "access-control-expose-headers": "X-Generation-Id,X-Provider-Name,request-id,cf-ray", - "cache-control": "no-cache", - "cf-ray": "a45d8f03ed9f19cf-YYZ", - "content-type": "text/event-stream", - "date": "Mon, 05 Oct 2026 15:46:21 GMT", - "permissions-policy": "payment=(self \"https://checkout.stripe.com\" \"https://connect-js.stripe.com\" \"https://js.stripe.com\" \"https://*.js.stripe.com\" \"https://hooks.stripe.com\")", - "referrer-policy": "no-referrer, strict-origin-when-cross-origin", - "server": "cloudflare", - "set-cookie": "[REDACTED]", - "x-content-type-options": "nosniff", - "x-generation-id": "gen-1791215181-RSctj8jzHB2AmoUqfU9L" - }, - "status": 200 - } - }, - { - "callIndex": 7, - "id": "7873eb548c06c9b7", - "matchKey": "POST openrouter.ai/api/v1/chat/completions", - "recordedAt": "2026-10-05T15:46:27.309Z", - "request": { - "body": { - "kind": "json", - "value": { - "messages": [ - { - "content": [ - { - "text": "Instructions (instructions)\nYou are a deterministic fixture agent for Braintrust Eve instrumentation tests.\n\nFor every user task, follow these steps exactly:\n\n1. Your first response MUST contain exactly these two tool calls and no text:\n - researcher with only this argument: {\"message\":\"\"}.\n Copy the current user message verbatim, including words such as \"again\";\n never reuse a message from an earlier turn. Do not pass outputSchema.\n - read with only this argument:\n {\"url\":\"https://eve.dev/docs/guides/instrumentation\"}.\n Emit both calls simultaneously in the same response, with researcher first\n and read second, so Eve executes them as one parallel batch. The calls are\n independent. NEVER call one by itself, wait for its result, or emit either\n call in a separate response.\n2. If a tool result says \"Started task\", your next response MUST be exactly one\n task_wait call with no arguments and no text.\n3. Only after both results are available, answer with a single sentence that\n starts with \"Final answer from read:\" and includes the researcher result, read\n title, URL, and read excerpt.\n\nTool execution\nA single tool or subagent call runs as one serial action. If you call multiple independent tools or subagents in one response, eve treats that batch as parallel work. Only batch work that is independent and does not rely on another call in the same response.", - "type": "text" - } - ], - "role": "system" - }, - { - "content": "Run the Braintrust Eve instrumentation e2e scenario", - "role": "user" - }, - { - "content": null, - "reasoning": "\nOkay, the user wants me to run the Braintrust Eve instrumentation end-to-end scenario. Let me start by understanding what that entails. The user mentioned \"e2e scenario,\" which usually stands for end-to-end testing. So, they probably want to simulate or execute a complete workflow within the Eve instrumentation setup.\n\nFirst, I need to check the available tools. The user provided a list of functions, including researcher, read, and others. The instructions say that for every user task, I must first make two tool calls: researcher with the user's message and read with the specific URL. \n\nThe user's message is \"Run the Braintrust Eve instrumentation e2e scenario.\" So, I should call the researcher function with this message. Then, the read function should access the documentation URL provided earlier: https://eve.dev/docs/guides/instrumentation. \n\nI need to make sure both calls are made in the same response, as per the instructions. No text, just the two tool calls. Then, if the tool results say \"Started task,\" I have to call task_wait. But since the user hasn't provided any results yet, I should just proceed with the initial calls.\n\nAfter both results are available, I need to answer with a single sentence starting with \"Final answer from read:\" including the researcher result, read title, URL, and excerpt. But right now, the first step is to generate the two tool calls.\n", - "reasoning_details": [ - { - "format": "unknown", - "index": 0, - "text": "\nOkay, the user wants me to run the Braintrust Eve instrumentation end-to-end scenario. Let me start by understanding what that entails. The user mentioned \"e2e scenario,\" which usually stands for end-to-end testing. So, they probably want to simulate or execute a complete workflow within the Eve instrumentation setup.\n\nFirst, I need to check the available tools. The user provided a list of functions, including researcher, read, and others. The instructions say that for every user task, I must first make two tool calls: researcher with the user's message and read with the specific URL. \n\nThe user's message is \"Run the Braintrust Eve instrumentation e2e scenario.\" So, I should call the researcher function with this message. Then, the read function should access the documentation URL provided earlier: https://eve.dev/docs/guides/instrumentation. \n\nI need to make sure both calls are made in the same response, as per the instructions. No text, just the two tool calls. Then, if the tool results say \"Started task,\" I have to call task_wait. But since the user hasn't provided any results yet, I should just proceed with the initial calls.\n\nAfter both results are available, I need to answer with a single sentence starting with \"Final answer from read:\" including the researcher result, read title, URL, and excerpt. But right now, the first step is to generate the two tool calls.\n", - "type": "reasoning.text" - } - ], - "role": "assistant", - "tool_calls": [ - { - "function": { - "arguments": "{\"message\":\"Run the Braintrust Eve instrumentation e2e scenario\"}", - "name": "researcher" - }, - "id": "call_e82b", - "type": "function" - }, - { - "function": { - "arguments": "{\"url\":\"https://eve.dev/docs/guides/instrumentation\"}", - "name": "read" - }, - "id": "call_614f", - "type": "function" - } - ] - }, - { - "content": "{\"excerpt\":\"Eve hooks expose runtime stream events that Braintrust maps into a nested turn trace.\",\"section\":\"Runtime context\",\"title\":\"Eve instrumentation\",\"url\":\"https://eve.dev/docs/guides/instrumentation\"}", - "name": "read", - "role": "tool", - "tool_call_id": "call_614f" - }, - { - "content": "Researcher result: Eve instrumentation https://eve.dev/docs/guides/instrumentation", - "name": "researcher", - "role": "tool", - "tool_call_id": "call_e82b" - }, - { - "content": "Final answer from read: Eve instrumentation https://eve.dev/docs/guides/instrumentation - \"Eve hooks expose runtime stream events that Braintrust maps into a nested turn trace.\" (Eve instrumentation, https://eve.dev/docs/guides/instrumentation)", - "reasoning": "\nOkay, let me process what's happening here. The user asked to run the Braintrust Eve instrumentation e2e scenario. According to the instructions, I needed to make two initial tool calls: researcher with the user's message and read with the specific URL. Both were made, and now the results are in.\n\nThe researcher result mentions \"Eve instrumentation https://eve.dev/docs/guides/instrumentation,\" which seems like a confirmation that the research task was started. The read tool returned a snippet from the documentation, including the title, section, and an excerpt about Eve hooks mapping to a nested turn trace.\n\nNow, the next step is to compile this information into a final answer. The instructions specify that the answer must start with \"Final answer from read:\" and include the researcher result, the read title, URL, and excerpt. \n\nI need to ensure that all required elements are included. The researcher result is \"Eve instrumentation https://eve.dev/docs/guides/instrumentation,\" the title is \"Eve instrumentation,\" the URL is the same, and the excerpt is the provided text. \n\nI should structure the sentence correctly, making sure there's no markdown and that it's a single, coherent sentence. Let me check for any typos or missing parts. Everything seems to be in order. Time to put it all together.\n", - "reasoning_details": [ - { - "format": "unknown", - "index": 0, - "text": "\nOkay, let me process what's happening here. The user asked to run the Braintrust Eve instrumentation e2e scenario. According to the instructions, I needed to make two initial tool calls: researcher with the user's message and read with the specific URL. Both were made, and now the results are in.\n\nThe researcher result mentions \"Eve instrumentation https://eve.dev/docs/guides/instrumentation,\" which seems like a confirmation that the research task was started. The read tool returned a snippet from the documentation, including the title, section, and an excerpt about Eve hooks mapping to a nested turn trace.\n\nNow, the next step is to compile this information into a final answer. The instructions specify that the answer must start with \"Final answer from read:\" and include the researcher result, the read title, URL, and excerpt. \n\nI need to ensure that all required elements are included. The researcher result is \"Eve instrumentation https://eve.dev/docs/guides/instrumentation,\" the title is \"Eve instrumentation,\" the URL is the same, and the excerpt is the provided text. \n\nI should structure the sentence correctly, making sure there's no markdown and that it's a single, coherent sentence. Let me check for any typos or missing parts. Everything seems to be in order. Time to put it all together.\n", - "type": "reasoning.text" - } - ], - "role": "assistant" - }, - { - "content": "Run the Braintrust Eve instrumentation e2e scenario again", - "role": "user" - }, - { - "content": null, - "reasoning": "\nOkay, the user wants me to run the Braintrust Eve instrumentation e2e scenario again. Let me check the previous interactions to make sure I follow the correct steps.\n\nIn the history, the user first asked to run the scenario, and I called the researcher and read functions. The read function fetched the documentation page, and the researcher result was provided. Then the user asked again, and I followed the same steps. Now, the user is asking \"again,\" so I need to repeat the process.\n\nThe instructions say that for every user task, I must first call researcher with the full user message and read with the specific URL. Since the user mentioned \"again,\" the message should be exactly \"Run the Braintrust Eve instrumentation e2e scenario again.\" I need to make sure the arguments are correctly formatted as JSON objects within the tool_call tags. No text should be added, just the two tool calls. Then, if the tool results say \"Started task,\" I should call task_wait. Once both results are available, provide the final answer with the required details. Let me structure the response correctly.\n", - "reasoning_details": [ - { - "format": "unknown", - "index": 0, - "text": "\nOkay, the user wants me to run the Braintrust Eve instrumentation e2e scenario again. Let me check the previous interactions to make sure I follow the correct steps.\n\nIn the history, the user first asked to run the scenario, and I called the researcher and read functions. The read function fetched the documentation page, and the researcher result was provided. Then the user asked again, and I followed the same steps. Now, the user is asking \"again,\" so I need to repeat the process.\n\nThe instructions say that for every user task, I must first call researcher with the full user message and read with the specific URL. Since the user mentioned \"again,\" the message should be exactly \"Run the Braintrust Eve instrumentation e2e scenario again.\" I need to make sure the arguments are correctly formatted as JSON objects within the tool_call tags. No text should be added, just the two tool calls. Then, if the tool results say \"Started task,\" I should call task_wait. Once both results are available, provide the final answer with the required details. Let me structure the response correctly.\n", - "type": "reasoning.text" - } - ], - "role": "assistant", - "tool_calls": [ - { - "function": { - "arguments": "{\"message\":\"Run the Braintrust Eve instrumentation e2e scenario again\"}", - "name": "researcher" - }, - "id": "call_baef", - "type": "function" - }, - { - "function": { - "arguments": "{\"url\":\"https://eve.dev/docs/guides/instrumentation\"}", - "name": "read" - }, - "id": "call_5d4f", - "type": "function" - } - ] - }, - { - "content": "{\"excerpt\":\"Eve hooks expose runtime stream events that Braintrust maps into a nested turn trace.\",\"section\":\"Runtime context\",\"title\":\"Eve instrumentation\",\"url\":\"https://eve.dev/docs/guides/instrumentation\"}", - "name": "read", - "role": "tool", - "tool_call_id": "call_5d4f" - }, - { - "content": "Researcher result: The Braintrust Eve instrumentation documentation can be found here: [Eve instrumentation](https://eve.dev/docs/guides/instrumentation).", - "name": "researcher", - "role": "tool", - "tool_call_id": "call_baef" - } - ], - "model": "qwen/qwen3-30b-a3b", - "provider": { - "only": ["deepinfra"], - "require_parameters": true - }, - "reasoning": { - "effort": "low" - }, - "stream": true, - "tool_choice": "auto", - "tools": [ - { - "function": { - "description": "Ask the user a question and wait for their response before continuing. Use this when you need clarification or a choice from the user.", - "name": "ask_question", - "parameters": { - "additionalProperties": false, - "properties": { - "allowFreeform": { - "description": "Whether the user may answer with freeform text instead of selecting one of the provided options.", - "type": "boolean" - }, - "options": { - "description": "Selectable answer options to present to the user.", - "items": { - "additionalProperties": false, - "properties": { - "description": { - "description": "Optional additional context for this option.", - "type": "string" - }, - "id": { - "description": "Stable identifier for the option.", - "type": "string" - }, - "label": { - "description": "User-facing label for the option.", - "type": "string" - }, - "style": { - "description": "Visual treatment hint for the option.", - "enum": ["primary", "danger", "default"], - "type": "string" - } - }, - "required": ["id", "label"], - "type": "object" - }, - "type": "array" - }, - "prompt": { - "description": "The prompt to present to the user.", - "type": "string" - } - }, - "required": ["prompt"], - "type": "object" - } - }, - "type": "function" - }, - { - "function": { - "description": "Execute a shell command in the shared workspace environment.", - "name": "bash", - "parameters": { - "additionalProperties": false, - "properties": { - "command": { - "description": "The shell command to execute.", - "type": "string" - } - }, - "required": ["command"], - "type": "object" - } - }, - "type": "function" - }, - { - "function": { - "description": "Fast file pattern matching tool that works with any codebase size.\n\nUsage:\n- Supports glob patterns like \"**/*.js\" or \"src/**/*.ts\".\n- Returns matching file paths.\n- Use this tool when you need to find files by name patterns.\n- If you are unsure of the correct file path, use the glob tool to look up filenames by glob pattern.\n- Use the grep tool instead if you need to search file contents.\n- Call this tool in parallel when you know there are multiple patterns to search for.", - "name": "glob", - "parameters": { - "additionalProperties": false, - "properties": { - "limit": { - "description": "Maximum number of results to return. Defaults to 100.", - "maximum": 1000, - "minimum": 1, - "type": "integer" - }, - "path": { - "description": "The directory to search in. Defaults to /workspace. Must be an absolute path. Omit to use the default.", - "type": "string" - }, - "pattern": { - "description": "The glob pattern to match files against (e.g. \"**/*.ts\", \"src/**/*.js\").", - "type": "string" - } - }, - "required": ["pattern"], - "type": "object" - } - }, - "type": "function" - }, - { - "function": { - "description": "Fast content search tool that works with any codebase size.\n\nUsage:\n- Searches file contents using regular expressions.\n- Supports full regex syntax (e.g. \"log.*Error\", \"function\\s+\\w+\").\n- Filter files by pattern with the glob parameter (e.g. \"*.js\", \"*.{ts,tsx}\").\n- Returns matching lines with file paths and line numbers.\n- Use this tool when you need to find files containing specific patterns.\n- Use the glob tool instead if you only need to find files by name.\n- Call this tool in parallel when you have multiple independent searches.\n- Any line longer than 2000 characters is truncated.", - "name": "grep", - "parameters": { - "additionalProperties": false, - "properties": { - "context": { - "description": "Number of surrounding context lines to include before and after each match. Defaults to 0.", - "minimum": 0, - "type": "integer" - }, - "glob": { - "description": "Filter files by glob pattern (e.g. \"*.ts\", \"*.{ts,tsx}\").", - "type": "string" - }, - "ignoreCase": { - "description": "Perform case-insensitive search. Defaults to false.", - "type": "boolean" - }, - "limit": { - "description": "Maximum number of matches to return per file. Defaults to 100.", - "maximum": 1000, - "minimum": 1, - "type": "integer" - }, - "literal": { - "description": "Treat the pattern as a literal string instead of a regular expression. Defaults to false.", - "type": "boolean" - }, - "path": { - "description": "The directory or file to search in. Defaults to /workspace. Must be an absolute path. Omit to use the default.", - "type": "string" - }, - "pattern": { - "description": "The regex pattern to search for in file contents (e.g. \"log.*Error\", \"function\\s+\\w+\").", - "type": "string" - } - }, - "required": ["pattern"], - "type": "object" - } - }, - "type": "function" - }, - { - "function": { - "description": "Read a file from the local filesystem. If the path does not exist, an error is returned.\n\nUsage:\n- The filePath parameter should be an absolute path.\n- By default, this tool returns up to 2000 lines from the start of the file.\n- The offset parameter is the line number to start from (1-indexed).\n- To read later sections, call this tool again with a larger offset.\n- Use the grep tool to find specific content in large files or files with long lines.\n- If you are unsure of the correct file path, use the glob tool to look up filenames by glob pattern.\n- Contents are returned with each line prefixed by its line number as `: `. For example, if a file has contents \"foo\\n\", you will receive \"1: foo\\n\".\n- Any line longer than 2000 characters is truncated.\n- Call this tool in parallel when you know there are multiple files you want to read.\n- Avoid tiny repeated slices (30 line chunks). If you need more context, read a larger window.", - "name": "read_file", - "parameters": { - "additionalProperties": false, - "properties": { - "filePath": { - "description": "The absolute path to the file to read.", - "type": "string" - }, - "limit": { - "description": "Maximum number of lines to return. Defaults to 2000.", - "minimum": 1, - "type": "integer" - }, - "offset": { - "description": "1-based line number to start from. Defaults to 1.", - "minimum": 1, - "type": "integer" - } - }, - "required": ["filePath"], - "type": "object" - } - }, - "type": "function" - }, - { - "function": { - "description": "Writes a file to the local filesystem.\n\nUsage:\n- This tool will overwrite the existing file if there is one at the provided path.\n- If this is an existing file, you MUST use the read_file tool first to read the file's contents. This tool will fail if you did not read the file first.\n- ALWAYS prefer editing existing files in the codebase. NEVER write new files unless explicitly required.\n- NEVER proactively create documentation files (*.md) or README files. Only create documentation files if explicitly requested by the User.\n- Only use emojis if the user explicitly requests it. Avoid writing emojis to files unless asked.", - "name": "write_file", - "parameters": { - "additionalProperties": false, - "properties": { - "content": { - "description": "Complete replacement file contents.", - "type": "string" - }, - "filePath": { - "description": "The absolute path to the file to write (must be absolute, not relative).", - "type": "string" - } - }, - "required": ["filePath", "content"], - "type": "object" - } - }, - "type": "function" - }, - { - "function": { - "description": "Use this tool to create and manage a structured task list for the current session.\nThis helps you track progress, organize complex tasks, and demonstrate thoroughness.\n\nWhen to use:\n- Complex multistep tasks requiring 3 or more distinct steps\n- When the user provides multiple tasks or a numbered list\n- After receiving new instructions, to capture requirements\n- After completing a task, to mark it complete and add follow-ups\n\nWhen NOT to use:\n- Single, straightforward tasks that need no tracking\n- Purely conversational or informational requests\n\nUsage:\n- Call with `todos` to replace the entire list (full replacement write)\n- Call without `todos` to read the current list\n- Both return the full current list with status counts\n- Mark tasks in_progress when you start, completed when done\n- Only have ONE task in_progress at a time", - "name": "todo", - "parameters": { - "additionalProperties": false, - "properties": { - "todos": { - "description": "The updated todo list. Omit to read the current list without modifying it.", - "items": { - "additionalProperties": false, - "properties": { - "content": { - "description": "Brief description of the task.", - "type": "string" - }, - "priority": { - "description": "Priority level of the task.", - "enum": ["high", "medium", "low"], - "type": "string" - }, - "status": { - "description": "Current status of the task.", - "enum": [ - "pending", - "in_progress", - "completed", - "cancelled" - ], - "type": "string" - } - }, - "required": ["content", "status", "priority"], - "type": "object" - }, - "type": "array" - } - }, - "type": "object" - } - }, - "type": "function" - }, - { - "function": { - "description": "Fetch a webpage and return its content in the requested format. Use this to retrieve and analyze content from URLs.\n\nUsage notes:\n- The URL must be a fully-formed valid URL starting with http:// or https://\n- HTML responses are automatically converted to markdown or plain text based on the requested format\n- Format options: \"markdown\" (default), \"text\", or \"html\"\n- Default timeout is 30 seconds (max 120 seconds)\n- Maximum response size is 5 MB; content is further capped at the shared tool-output budget (50 KB / 2000 lines)\n- This tool is read-only and does not modify any files", - "name": "web_fetch", - "parameters": { - "additionalProperties": false, - "properties": { - "format": { - "description": "The format to return the content in (text, markdown, or html). HTML responses are automatically converted to the requested format. Defaults to \"markdown\".", - "enum": ["markdown", "text", "html"], - "type": "string" - }, - "timeout": { - "description": "Optional timeout in seconds. Defaults to 30, max 120.", - "type": "number" - }, - "url": { - "description": "The fully-formed URL to fetch content from. Must start with http:// or https://.", - "type": "string" - } - }, - "required": ["url"], - "type": "object" - } - }, - "type": "function" - }, - { - "function": { - "description": "Load the full instructions for one available skill by name or id. Use this tool when the request clearly matches a listed skill description or when the user explicitly asks for that skill. This is not for MCP connections; use connection_search to access an installed connection. Loading adds the skill instructions to the current turn. Choose the \"skill\" value from the Available skills block.", - "name": "load_skill", - "parameters": { - "additionalProperties": false, - "properties": { - "skill": { - "description": "Available skill name or id.", - "type": "string" - } - }, - "required": ["skill"], - "type": "object" - } - }, - "type": "function" - }, - { - "function": { - "description": "Reads a deterministic Eve documentation page fixture.", - "name": "read", - "parameters": { - "$schema": "http://json-schema.org/draft-07/schema#", - "additionalProperties": false, - "properties": { - "url": { - "type": "string" - } - }, - "required": ["url"], - "type": "object" - } - }, - "type": "function" - }, - { - "function": { - "description": "Searches the Eve instrumentation documentation fixture.", - "name": "search", - "parameters": { - "$schema": "http://json-schema.org/draft-07/schema#", - "additionalProperties": false, - "properties": { - "query": { - "type": "string" - } - }, - "required": ["query"], - "type": "object" - } - }, - "type": "function" - }, - { - "function": { - "description": "Research the Eve instrumentation documentation before the parent reads it.", - "name": "researcher", - "parameters": { - "additionalProperties": false, - "properties": { - "message": { - "description": "The message to send to the subagent. Provide all context the subagent needs to complete the task; the subagent does not see the parent's history.", - "type": "string" - }, - "outputSchema": { - "description": "When provided, the subagent runs in task mode and must produce structured output matching this JSON Schema. The structured output becomes the tool result.", - "type": "object" - } - }, - "required": ["message"], - "type": "object" - } - }, - "type": "function" - }, - { - "function": { - "description": "Delegate a focused subtask to a fresh copy of yourself. Use it to isolate complex work or split a large task into independent pieces. Issue multiple `agent` calls in one response to run a small fixed set in parallel. Each child has fresh history and state but shares your tools and sandbox, so include essential context in `message` and give parallel writers non-overlapping scopes.", - "name": "agent", - "parameters": { - "additionalProperties": false, - "properties": { - "message": { - "description": "The message to send to the subagent. Provide all context the subagent needs to complete the task; the subagent does not see the parent's history.", - "type": "string" - }, - "outputSchema": { - "description": "When provided, the subagent runs in task mode and must produce structured output matching this JSON Schema. The structured output becomes the tool result.", - "type": "object" - } - }, - "required": ["message"], - "type": "object" - } - }, - "type": "function" - } - ] - } - }, - "headers": {}, - "method": "POST", - "url": "https://openrouter.ai/api/v1/chat/completions" - }, - "response": { - "body": { - "chunks": [ - "data: {\"id\":\"gen-1791215185-kD0GaG3sgjyoR5VOl5oJ\",\"object\":\"chat.completion.chunk\",\"created\":1791215185,\"model\":\"qwen/qwen3-30b-a3b\",\"provider\":\"DeepInfra\",\"choices\":[{\"index\":0,\"delta\":{\"content\":\"\",\"role\":\"assistant\",\"reasoning\":\"\\n\",\"reasoning_details\":[{\"type\":\"reasoning.text\",\"text\":\"\\n\",\"format\":\"unknown\",\"index\":0}]},\"finish_reason\":null,\"native_finish_reason\":null}]}", - "data: {\"id\":\"gen-1791215185-kD0GaG3sgjyoR5VOl5oJ\",\"object\":\"chat.completion.chunk\",\"created\":1791215185,\"model\":\"qwen/qwen3-30b-a3b\",\"provider\":\"DeepInfra\",\"choices\":[{\"index\":0,\"delta\":{\"content\":\"\",\"role\":\"assistant\",\"reasoning\":\"Okay\",\"reasoning_details\":[{\"type\":\"reasoning.text\",\"text\":\"Okay\",\"format\":\"unknown\",\"index\":0}]},\"finish_reason\":null,\"native_finish_reason\":null}]}", - "data: {\"id\":\"gen-1791215185-kD0GaG3sgjyoR5VOl5oJ\",\"object\":\"chat.completion.chunk\",\"created\":1791215185,\"model\":\"qwen/qwen3-30b-a3b\",\"provider\":\"DeepInfra\",\"choices\":[{\"index\":0,\"delta\":{\"content\":\"\",\"role\":\"assistant\",\"reasoning\":\",\",\"reasoning_details\":[{\"type\":\"reasoning.text\",\"text\":\",\",\"format\":\"unknown\",\"index\":0}]},\"finish_reason\":null,\"native_finish_reason\":null}]}", - "data: {\"id\":\"gen-1791215185-kD0GaG3sgjyoR5VOl5oJ\",\"object\":\"chat.completion.chunk\",\"created\":1791215185,\"model\":\"qwen/qwen3-30b-a3b\",\"provider\":\"DeepInfra\",\"choices\":[{\"index\":0,\"delta\":{\"content\":\"\",\"role\":\"assistant\",\"reasoning\":\" the\",\"reasoning_details\":[{\"type\":\"reasoning.text\",\"text\":\" the\",\"format\":\"unknown\",\"index\":0}]},\"finish_reason\":null,\"native_finish_reason\":null}]}", - "data: {\"id\":\"gen-1791215185-kD0GaG3sgjyoR5VOl5oJ\",\"object\":\"chat.completion.chunk\",\"created\":1791215185,\"model\":\"qwen/qwen3-30b-a3b\",\"provider\":\"DeepInfra\",\"choices\":[{\"index\":0,\"delta\":{\"content\":\"\",\"role\":\"assistant\",\"reasoning\":\" user\",\"reasoning_details\":[{\"type\":\"reasoning.text\",\"text\":\" user\",\"format\":\"unknown\",\"index\":0}]},\"finish_reason\":null,\"native_finish_reason\":null}]}", - "data: {\"id\":\"gen-1791215185-kD0GaG3sgjyoR5VOl5oJ\",\"object\":\"chat.completion.chunk\",\"created\":1791215185,\"model\":\"qwen/qwen3-30b-a3b\",\"provider\":\"DeepInfra\",\"choices\":[{\"index\":0,\"delta\":{\"content\":\"\",\"role\":\"assistant\",\"reasoning\":\" asked\",\"reasoning_details\":[{\"type\":\"reasoning.text\",\"text\":\" asked\",\"format\":\"unknown\",\"index\":0}]},\"finish_reason\":null,\"native_finish_reason\":null}]}", - "data: {\"id\":\"gen-1791215185-kD0GaG3sgjyoR5VOl5oJ\",\"object\":\"chat.completion.chunk\",\"created\":1791215185,\"model\":\"qwen/qwen3-30b-a3b\",\"provider\":\"DeepInfra\",\"choices\":[{\"index\":0,\"delta\":{\"content\":\"\",\"role\":\"assistant\",\"reasoning\":\" to\",\"reasoning_details\":[{\"type\":\"reasoning.text\",\"text\":\" to\",\"format\":\"unknown\",\"index\":0}]},\"finish_reason\":null,\"native_finish_reason\":null}]}", - "data: {\"id\":\"gen-1791215185-kD0GaG3sgjyoR5VOl5oJ\",\"object\":\"chat.completion.chunk\",\"created\":1791215185,\"model\":\"qwen/qwen3-30b-a3b\",\"provider\":\"DeepInfra\",\"choices\":[{\"index\":0,\"delta\":{\"content\":\"\",\"role\":\"assistant\",\"reasoning\":\" run\",\"reasoning_details\":[{\"type\":\"reasoning.text\",\"text\":\" run\",\"format\":\"unknown\",\"index\":0}]},\"finish_reason\":null,\"native_finish_reason\":null}]}", - "data: {\"id\":\"gen-1791215185-kD0GaG3sgjyoR5VOl5oJ\",\"object\":\"chat.completion.chunk\",\"created\":1791215185,\"model\":\"qwen/qwen3-30b-a3b\",\"provider\":\"DeepInfra\",\"choices\":[{\"index\":0,\"delta\":{\"content\":\"\",\"role\":\"assistant\",\"reasoning\":\" the\",\"reasoning_details\":[{\"type\":\"reasoning.text\",\"text\":\" the\",\"format\":\"unknown\",\"index\":0}]},\"finish_reason\":null,\"native_finish_reason\":null}]}", - "data: {\"id\":\"gen-1791215185-kD0GaG3sgjyoR5VOl5oJ\",\"object\":\"chat.completion.chunk\",\"created\":1791215185,\"model\":\"qwen/qwen3-30b-a3b\",\"provider\":\"DeepInfra\",\"choices\":[{\"index\":0,\"delta\":{\"content\":\"\",\"role\":\"assistant\",\"reasoning\":\" scenario\",\"reasoning_details\":[{\"type\":\"reasoning.text\",\"text\":\" scenario\",\"format\":\"unknown\",\"index\":0}]},\"finish_reason\":null,\"native_finish_reason\":null}]}", - "data: {\"id\":\"gen-1791215185-kD0GaG3sgjyoR5VOl5oJ\",\"object\":\"chat.completion.chunk\",\"created\":1791215185,\"model\":\"qwen/qwen3-30b-a3b\",\"provider\":\"DeepInfra\",\"choices\":[{\"index\":0,\"delta\":{\"content\":\"\",\"role\":\"assistant\",\"reasoning\":\" again\",\"reasoning_details\":[{\"type\":\"reasoning.text\",\"text\":\" again\",\"format\":\"unknown\",\"index\":0}]},\"finish_reason\":null,\"native_finish_reason\":null}]}", - "data: {\"id\":\"gen-1791215185-kD0GaG3sgjyoR5VOl5oJ\",\"object\":\"chat.completion.chunk\",\"created\":1791215185,\"model\":\"qwen/qwen3-30b-a3b\",\"provider\":\"DeepInfra\",\"choices\":[{\"index\":0,\"delta\":{\"content\":\"\",\"role\":\"assistant\",\"reasoning\":\",\",\"reasoning_details\":[{\"type\":\"reasoning.text\",\"text\":\",\",\"format\":\"unknown\",\"index\":0}]},\"finish_reason\":null,\"native_finish_reason\":null}]}", - "data: {\"id\":\"gen-1791215185-kD0GaG3sgjyoR5VOl5oJ\",\"object\":\"chat.completion.chunk\",\"created\":1791215185,\"model\":\"qwen/qwen3-30b-a3b\",\"provider\":\"DeepInfra\",\"choices\":[{\"index\":0,\"delta\":{\"content\":\"\",\"role\":\"assistant\",\"reasoning\":\" so\",\"reasoning_details\":[{\"type\":\"reasoning.text\",\"text\":\" so\",\"format\":\"unknown\",\"index\":0}]},\"finish_reason\":null,\"native_finish_reason\":null}]}", - "data: {\"id\":\"gen-1791215185-kD0GaG3sgjyoR5VOl5oJ\",\"object\":\"chat.completion.chunk\",\"created\":1791215185,\"model\":\"qwen/qwen3-30b-a3b\",\"provider\":\"DeepInfra\",\"choices\":[{\"index\":0,\"delta\":{\"content\":\"\",\"role\":\"assistant\",\"reasoning\":\" I\",\"reasoning_details\":[{\"type\":\"reasoning.text\",\"text\":\" I\",\"format\":\"unknown\",\"index\":0}]},\"finish_reason\":null,\"native_finish_reason\":null}]}", - "data: {\"id\":\"gen-1791215185-kD0GaG3sgjyoR5VOl5oJ\",\"object\":\"chat.completion.chunk\",\"created\":1791215185,\"model\":\"qwen/qwen3-30b-a3b\",\"provider\":\"DeepInfra\",\"choices\":[{\"index\":0,\"delta\":{\"content\":\"\",\"role\":\"assistant\",\"reasoning\":\" need\",\"reasoning_details\":[{\"type\":\"reasoning.text\",\"text\":\" need\",\"format\":\"unknown\",\"index\":0}]},\"finish_reason\":null,\"native_finish_reason\":null}]}", - "data: {\"id\":\"gen-1791215185-kD0GaG3sgjyoR5VOl5oJ\",\"object\":\"chat.completion.chunk\",\"created\":1791215185,\"model\":\"qwen/qwen3-30b-a3b\",\"provider\":\"DeepInfra\",\"choices\":[{\"index\":0,\"delta\":{\"content\":\"\",\"role\":\"assistant\",\"reasoning\":\" to\",\"reasoning_details\":[{\"type\":\"reasoning.text\",\"text\":\" to\",\"format\":\"unknown\",\"index\":0}]},\"finish_reason\":null,\"native_finish_reason\":null}]}", - "data: {\"id\":\"gen-1791215185-kD0GaG3sgjyoR5VOl5oJ\",\"object\":\"chat.completion.chunk\",\"created\":1791215185,\"model\":\"qwen/qwen3-30b-a3b\",\"provider\":\"DeepInfra\",\"choices\":[{\"index\":0,\"delta\":{\"content\":\"\",\"role\":\"assistant\",\"reasoning\":\" follow\",\"reasoning_details\":[{\"type\":\"reasoning.text\",\"text\":\" follow\",\"format\":\"unknown\",\"index\":0}]},\"finish_reason\":null,\"native_finish_reason\":null}]}", - "data: {\"id\":\"gen-1791215185-kD0GaG3sgjyoR5VOl5oJ\",\"object\":\"chat.completion.chunk\",\"created\":1791215185,\"model\":\"qwen/qwen3-30b-a3b\",\"provider\":\"DeepInfra\",\"choices\":[{\"index\":0,\"delta\":{\"content\":\"\",\"role\":\"assistant\",\"reasoning\":\" the\",\"reasoning_details\":[{\"type\":\"reasoning.text\",\"text\":\" the\",\"format\":\"unknown\",\"index\":0}]},\"finish_reason\":null,\"native_finish_reason\":null}]}", - "data: {\"id\":\"gen-1791215185-kD0GaG3sgjyoR5VOl5oJ\",\"object\":\"chat.completion.chunk\",\"created\":1791215185,\"model\":\"qwen/qwen3-30b-a3b\",\"provider\":\"DeepInfra\",\"choices\":[{\"index\":0,\"delta\":{\"content\":\"\",\"role\":\"assistant\",\"reasoning\":\" same\",\"reasoning_details\":[{\"type\":\"reasoning.text\",\"text\":\" same\",\"format\":\"unknown\",\"index\":0}]},\"finish_reason\":null,\"native_finish_reason\":null}]}", - "data: {\"id\":\"gen-1791215185-kD0GaG3sgjyoR5VOl5oJ\",\"object\":\"chat.completion.chunk\",\"created\":1791215185,\"model\":\"qwen/qwen3-30b-a3b\",\"provider\":\"DeepInfra\",\"choices\":[{\"index\":0,\"delta\":{\"content\":\"\",\"role\":\"assistant\",\"reasoning\":\" steps\",\"reasoning_details\":[{\"type\":\"reasoning.text\",\"text\":\" steps\",\"format\":\"unknown\",\"index\":0}]},\"finish_reason\":null,\"native_finish_reason\":null}]}", - "data: {\"id\":\"gen-1791215185-kD0GaG3sgjyoR5VOl5oJ\",\"object\":\"chat.completion.chunk\",\"created\":1791215185,\"model\":\"qwen/qwen3-30b-a3b\",\"provider\":\"DeepInfra\",\"choices\":[{\"index\":0,\"delta\":{\"content\":\"\",\"role\":\"assistant\",\"reasoning\":\" as\",\"reasoning_details\":[{\"type\":\"reasoning.text\",\"text\":\" as\",\"format\":\"unknown\",\"index\":0}]},\"finish_reason\":null,\"native_finish_reason\":null}]}", - "data: {\"id\":\"gen-1791215185-kD0GaG3sgjyoR5VOl5oJ\",\"object\":\"chat.completion.chunk\",\"created\":1791215185,\"model\":\"qwen/qwen3-30b-a3b\",\"provider\":\"DeepInfra\",\"choices\":[{\"index\":0,\"delta\":{\"content\":\"\",\"role\":\"assistant\",\"reasoning\":\" before\",\"reasoning_details\":[{\"type\":\"reasoning.text\",\"text\":\" before\",\"format\":\"unknown\",\"index\":0}]},\"finish_reason\":null,\"native_finish_reason\":null}]}", - "data: {\"id\":\"gen-1791215185-kD0GaG3sgjyoR5VOl5oJ\",\"object\":\"chat.completion.chunk\",\"created\":1791215185,\"model\":\"qwen/qwen3-30b-a3b\",\"provider\":\"DeepInfra\",\"choices\":[{\"index\":0,\"delta\":{\"content\":\"\",\"role\":\"assistant\",\"reasoning\":\".\",\"reasoning_details\":[{\"type\":\"reasoning.text\",\"text\":\".\",\"format\":\"unknown\",\"index\":0}]},\"finish_reason\":null,\"native_finish_reason\":null}]}", - "data: {\"id\":\"gen-1791215185-kD0GaG3sgjyoR5VOl5oJ\",\"object\":\"chat.completion.chunk\",\"created\":1791215185,\"model\":\"qwen/qwen3-30b-a3b\",\"provider\":\"DeepInfra\",\"choices\":[{\"index\":0,\"delta\":{\"content\":\"\",\"role\":\"assistant\",\"reasoning\":\" First\",\"reasoning_details\":[{\"type\":\"reasoning.text\",\"text\":\" First\",\"format\":\"unknown\",\"index\":0}]},\"finish_reason\":null,\"native_finish_reason\":null}]}", - "data: {\"id\":\"gen-1791215185-kD0GaG3sgjyoR5VOl5oJ\",\"object\":\"chat.completion.chunk\",\"created\":1791215185,\"model\":\"qwen/qwen3-30b-a3b\",\"provider\":\"DeepInfra\",\"choices\":[{\"index\":0,\"delta\":{\"content\":\"\",\"role\":\"assistant\",\"reasoning\":\",\",\"reasoning_details\":[{\"type\":\"reasoning.text\",\"text\":\",\",\"format\":\"unknown\",\"index\":0}]},\"finish_reason\":null,\"native_finish_reason\":null}]}", - "data: {\"id\":\"gen-1791215185-kD0GaG3sgjyoR5VOl5oJ\",\"object\":\"chat.completion.chunk\",\"created\":1791215185,\"model\":\"qwen/qwen3-30b-a3b\",\"provider\":\"DeepInfra\",\"choices\":[{\"index\":0,\"delta\":{\"content\":\"\",\"role\":\"assistant\",\"reasoning\":\" I\",\"reasoning_details\":[{\"type\":\"reasoning.text\",\"text\":\" I\",\"format\":\"unknown\",\"index\":0}]},\"finish_reason\":null,\"native_finish_reason\":null}]}", - "data: {\"id\":\"gen-1791215185-kD0GaG3sgjyoR5VOl5oJ\",\"object\":\"chat.completion.chunk\",\"created\":1791215185,\"model\":\"qwen/qwen3-30b-a3b\",\"provider\":\"DeepInfra\",\"choices\":[{\"index\":0,\"delta\":{\"content\":\"\",\"role\":\"assistant\",\"reasoning\":\" called\",\"reasoning_details\":[{\"type\":\"reasoning.text\",\"text\":\" called\",\"format\":\"unknown\",\"index\":0}]},\"finish_reason\":null,\"native_finish_reason\":null}]}", - "data: {\"id\":\"gen-1791215185-kD0GaG3sgjyoR5VOl5oJ\",\"object\":\"chat.completion.chunk\",\"created\":1791215185,\"model\":\"qwen/qwen3-30b-a3b\",\"provider\":\"DeepInfra\",\"choices\":[{\"index\":0,\"delta\":{\"content\":\"\",\"role\":\"assistant\",\"reasoning\":\" the\",\"reasoning_details\":[{\"type\":\"reasoning.text\",\"text\":\" the\",\"format\":\"unknown\",\"index\":0}]},\"finish_reason\":null,\"native_finish_reason\":null}]}", - "data: {\"id\":\"gen-1791215185-kD0GaG3sgjyoR5VOl5oJ\",\"object\":\"chat.completion.chunk\",\"created\":1791215185,\"model\":\"qwen/qwen3-30b-a3b\",\"provider\":\"DeepInfra\",\"choices\":[{\"index\":0,\"delta\":{\"content\":\"\",\"role\":\"assistant\",\"reasoning\":\" researcher\",\"reasoning_details\":[{\"type\":\"reasoning.text\",\"text\":\" researcher\",\"format\":\"unknown\",\"index\":0}]},\"finish_reason\":null,\"native_finish_reason\":null}]}", - "data: {\"id\":\"gen-1791215185-kD0GaG3sgjyoR5VOl5oJ\",\"object\":\"chat.completion.chunk\",\"created\":1791215185,\"model\":\"qwen/qwen3-30b-a3b\",\"provider\":\"DeepInfra\",\"choices\":[{\"index\":0,\"delta\":{\"content\":\"\",\"role\":\"assistant\",\"reasoning\":\" with\",\"reasoning_details\":[{\"type\":\"reasoning.text\",\"text\":\" with\",\"format\":\"unknown\",\"index\":0}]},\"finish_reason\":null,\"native_finish_reason\":null}]}", - "data: {\"id\":\"gen-1791215185-kD0GaG3sgjyoR5VOl5oJ\",\"object\":\"chat.completion.chunk\",\"created\":1791215185,\"model\":\"qwen/qwen3-30b-a3b\",\"provider\":\"DeepInfra\",\"choices\":[{\"index\":0,\"delta\":{\"content\":\"\",\"role\":\"assistant\",\"reasoning\":\" the\",\"reasoning_details\":[{\"type\":\"reasoning.text\",\"text\":\" the\",\"format\":\"unknown\",\"index\":0}]},\"finish_reason\":null,\"native_finish_reason\":null}]}", - "data: {\"id\":\"gen-1791215185-kD0GaG3sgjyoR5VOl5oJ\",\"object\":\"chat.completion.chunk\",\"created\":1791215185,\"model\":\"qwen/qwen3-30b-a3b\",\"provider\":\"DeepInfra\",\"choices\":[{\"index\":0,\"delta\":{\"content\":\"\",\"role\":\"assistant\",\"reasoning\":\" message\",\"reasoning_details\":[{\"type\":\"reasoning.text\",\"text\":\" message\",\"format\":\"unknown\",\"index\":0}]},\"finish_reason\":null,\"native_finish_reason\":null}]}", - "data: {\"id\":\"gen-1791215185-kD0GaG3sgjyoR5VOl5oJ\",\"object\":\"chat.completion.chunk\",\"created\":1791215185,\"model\":\"qwen/qwen3-30b-a3b\",\"provider\":\"DeepInfra\",\"choices\":[{\"index\":0,\"delta\":{\"content\":\"\",\"role\":\"assistant\",\"reasoning\":\" \\\"\",\"reasoning_details\":[{\"type\":\"reasoning.text\",\"text\":\" \\\"\",\"format\":\"unknown\",\"index\":0}]},\"finish_reason\":null,\"native_finish_reason\":null}]}", - "data: {\"id\":\"gen-1791215185-kD0GaG3sgjyoR5VOl5oJ\",\"object\":\"chat.completion.chunk\",\"created\":1791215185,\"model\":\"qwen/qwen3-30b-a3b\",\"provider\":\"DeepInfra\",\"choices\":[{\"index\":0,\"delta\":{\"content\":\"\",\"role\":\"assistant\",\"reasoning\":\"Run\",\"reasoning_details\":[{\"type\":\"reasoning.text\",\"text\":\"Run\",\"format\":\"unknown\",\"index\":0}]},\"finish_reason\":null,\"native_finish_reason\":null}]}", - "data: {\"id\":\"gen-1791215185-kD0GaG3sgjyoR5VOl5oJ\",\"object\":\"chat.completion.chunk\",\"created\":1791215185,\"model\":\"qwen/qwen3-30b-a3b\",\"provider\":\"DeepInfra\",\"choices\":[{\"index\":0,\"delta\":{\"content\":\"\",\"role\":\"assistant\",\"reasoning\":\" the\",\"reasoning_details\":[{\"type\":\"reasoning.text\",\"text\":\" the\",\"format\":\"unknown\",\"index\":0}]},\"finish_reason\":null,\"native_finish_reason\":null}]}", - "data: {\"id\":\"gen-1791215185-kD0GaG3sgjyoR5VOl5oJ\",\"object\":\"chat.completion.chunk\",\"created\":1791215185,\"model\":\"qwen/qwen3-30b-a3b\",\"provider\":\"DeepInfra\",\"choices\":[{\"index\":0,\"delta\":{\"content\":\"\",\"role\":\"assistant\",\"reasoning\":\" Brain\",\"reasoning_details\":[{\"type\":\"reasoning.text\",\"text\":\" Brain\",\"format\":\"unknown\",\"index\":0}]},\"finish_reason\":null,\"native_finish_reason\":null}]}", - "data: {\"id\":\"gen-1791215185-kD0GaG3sgjyoR5VOl5oJ\",\"object\":\"chat.completion.chunk\",\"created\":1791215185,\"model\":\"qwen/qwen3-30b-a3b\",\"provider\":\"DeepInfra\",\"choices\":[{\"index\":0,\"delta\":{\"content\":\"\",\"role\":\"assistant\",\"reasoning\":\"trust\",\"reasoning_details\":[{\"type\":\"reasoning.text\",\"text\":\"trust\",\"format\":\"unknown\",\"index\":0}]},\"finish_reason\":null,\"native_finish_reason\":null}]}", - "data: {\"id\":\"gen-1791215185-kD0GaG3sgjyoR5VOl5oJ\",\"object\":\"chat.completion.chunk\",\"created\":1791215185,\"model\":\"qwen/qwen3-30b-a3b\",\"provider\":\"DeepInfra\",\"choices\":[{\"index\":0,\"delta\":{\"content\":\"\",\"role\":\"assistant\",\"reasoning\":\" Eve\",\"reasoning_details\":[{\"type\":\"reasoning.text\",\"text\":\" Eve\",\"format\":\"unknown\",\"index\":0}]},\"finish_reason\":null,\"native_finish_reason\":null}]}", - "data: {\"id\":\"gen-1791215185-kD0GaG3sgjyoR5VOl5oJ\",\"object\":\"chat.completion.chunk\",\"created\":1791215185,\"model\":\"qwen/qwen3-30b-a3b\",\"provider\":\"DeepInfra\",\"choices\":[{\"index\":0,\"delta\":{\"content\":\"\",\"role\":\"assistant\",\"reasoning\":\" instrumentation\",\"reasoning_details\":[{\"type\":\"reasoning.text\",\"text\":\" instrumentation\",\"format\":\"unknown\",\"index\":0}]},\"finish_reason\":null,\"native_finish_reason\":null}]}", - "data: {\"id\":\"gen-1791215185-kD0GaG3sgjyoR5VOl5oJ\",\"object\":\"chat.completion.chunk\",\"created\":1791215185,\"model\":\"qwen/qwen3-30b-a3b\",\"provider\":\"DeepInfra\",\"choices\":[{\"index\":0,\"delta\":{\"content\":\"\",\"role\":\"assistant\",\"reasoning\":\" e\",\"reasoning_details\":[{\"type\":\"reasoning.text\",\"text\":\" e\",\"format\":\"unknown\",\"index\":0}]},\"finish_reason\":null,\"native_finish_reason\":null}]}", - "data: {\"id\":\"gen-1791215185-kD0GaG3sgjyoR5VOl5oJ\",\"object\":\"chat.completion.chunk\",\"created\":1791215185,\"model\":\"qwen/qwen3-30b-a3b\",\"provider\":\"DeepInfra\",\"choices\":[{\"index\":0,\"delta\":{\"content\":\"\",\"role\":\"assistant\",\"reasoning\":\"2\",\"reasoning_details\":[{\"type\":\"reasoning.text\",\"text\":\"2\",\"format\":\"unknown\",\"index\":0}]},\"finish_reason\":null,\"native_finish_reason\":null}]}", - "data: {\"id\":\"gen-1791215185-kD0GaG3sgjyoR5VOl5oJ\",\"object\":\"chat.completion.chunk\",\"created\":1791215185,\"model\":\"qwen/qwen3-30b-a3b\",\"provider\":\"DeepInfra\",\"choices\":[{\"index\":0,\"delta\":{\"content\":\"\",\"role\":\"assistant\",\"reasoning\":\"e\",\"reasoning_details\":[{\"type\":\"reasoning.text\",\"text\":\"e\",\"format\":\"unknown\",\"index\":0}]},\"finish_reason\":null,\"native_finish_reason\":null}]}", - "data: {\"id\":\"gen-1791215185-kD0GaG3sgjyoR5VOl5oJ\",\"object\":\"chat.completion.chunk\",\"created\":1791215185,\"model\":\"qwen/qwen3-30b-a3b\",\"provider\":\"DeepInfra\",\"choices\":[{\"index\":0,\"delta\":{\"content\":\"\",\"role\":\"assistant\",\"reasoning\":\" scenario\",\"reasoning_details\":[{\"type\":\"reasoning.text\",\"text\":\" scenario\",\"format\":\"unknown\",\"index\":0}]},\"finish_reason\":null,\"native_finish_reason\":null}]}", - "data: {\"id\":\"gen-1791215185-kD0GaG3sgjyoR5VOl5oJ\",\"object\":\"chat.completion.chunk\",\"created\":1791215185,\"model\":\"qwen/qwen3-30b-a3b\",\"provider\":\"DeepInfra\",\"choices\":[{\"index\":0,\"delta\":{\"content\":\"\",\"role\":\"assistant\",\"reasoning\":\" again\",\"reasoning_details\":[{\"type\":\"reasoning.text\",\"text\":\" again\",\"format\":\"unknown\",\"index\":0}]},\"finish_reason\":null,\"native_finish_reason\":null}]}", - "data: {\"id\":\"gen-1791215185-kD0GaG3sgjyoR5VOl5oJ\",\"object\":\"chat.completion.chunk\",\"created\":1791215185,\"model\":\"qwen/qwen3-30b-a3b\",\"provider\":\"DeepInfra\",\"choices\":[{\"index\":0,\"delta\":{\"content\":\"\",\"role\":\"assistant\",\"reasoning\":\"\\\"\",\"reasoning_details\":[{\"type\":\"reasoning.text\",\"text\":\"\\\"\",\"format\":\"unknown\",\"index\":0}]},\"finish_reason\":null,\"native_finish_reason\":null}]}", - "data: {\"id\":\"gen-1791215185-kD0GaG3sgjyoR5VOl5oJ\",\"object\":\"chat.completion.chunk\",\"created\":1791215185,\"model\":\"qwen/qwen3-30b-a3b\",\"provider\":\"DeepInfra\",\"choices\":[{\"index\":0,\"delta\":{\"content\":\"\",\"role\":\"assistant\",\"reasoning\":\" and\",\"reasoning_details\":[{\"type\":\"reasoning.text\",\"text\":\" and\",\"format\":\"unknown\",\"index\":0}]},\"finish_reason\":null,\"native_finish_reason\":null}]}", - "data: {\"id\":\"gen-1791215185-kD0GaG3sgjyoR5VOl5oJ\",\"object\":\"chat.completion.chunk\",\"created\":1791215185,\"model\":\"qwen/qwen3-30b-a3b\",\"provider\":\"DeepInfra\",\"choices\":[{\"index\":0,\"delta\":{\"content\":\"\",\"role\":\"assistant\",\"reasoning\":\" the\",\"reasoning_details\":[{\"type\":\"reasoning.text\",\"text\":\" the\",\"format\":\"unknown\",\"index\":0}]},\"finish_reason\":null,\"native_finish_reason\":null}]}", - "data: {\"id\":\"gen-1791215185-kD0GaG3sgjyoR5VOl5oJ\",\"object\":\"chat.completion.chunk\",\"created\":1791215185,\"model\":\"qwen/qwen3-30b-a3b\",\"provider\":\"DeepInfra\",\"choices\":[{\"index\":0,\"delta\":{\"content\":\"\",\"role\":\"assistant\",\"reasoning\":\" read\",\"reasoning_details\":[{\"type\":\"reasoning.text\",\"text\":\" read\",\"format\":\"unknown\",\"index\":0}]},\"finish_reason\":null,\"native_finish_reason\":null}]}", - "data: {\"id\":\"gen-1791215185-kD0GaG3sgjyoR5VOl5oJ\",\"object\":\"chat.completion.chunk\",\"created\":1791215185,\"model\":\"qwen/qwen3-30b-a3b\",\"provider\":\"DeepInfra\",\"choices\":[{\"index\":0,\"delta\":{\"content\":\"\",\"role\":\"assistant\",\"reasoning\":\" function\",\"reasoning_details\":[{\"type\":\"reasoning.text\",\"text\":\" function\",\"format\":\"unknown\",\"index\":0}]},\"finish_reason\":null,\"native_finish_reason\":null}]}", - "data: {\"id\":\"gen-1791215185-kD0GaG3sgjyoR5VOl5oJ\",\"object\":\"chat.completion.chunk\",\"created\":1791215185,\"model\":\"qwen/qwen3-30b-a3b\",\"provider\":\"DeepInfra\",\"choices\":[{\"index\":0,\"delta\":{\"content\":\"\",\"role\":\"assistant\",\"reasoning\":\" with\",\"reasoning_details\":[{\"type\":\"reasoning.text\",\"text\":\" with\",\"format\":\"unknown\",\"index\":0}]},\"finish_reason\":null,\"native_finish_reason\":null}]}", - "data: {\"id\":\"gen-1791215185-kD0GaG3sgjyoR5VOl5oJ\",\"object\":\"chat.completion.chunk\",\"created\":1791215185,\"model\":\"qwen/qwen3-30b-a3b\",\"provider\":\"DeepInfra\",\"choices\":[{\"index\":0,\"delta\":{\"content\":\"\",\"role\":\"assistant\",\"reasoning\":\" the\",\"reasoning_details\":[{\"type\":\"reasoning.text\",\"text\":\" the\",\"format\":\"unknown\",\"index\":0}]},\"finish_reason\":null,\"native_finish_reason\":null}]}", - "data: {\"id\":\"gen-1791215185-kD0GaG3sgjyoR5VOl5oJ\",\"object\":\"chat.completion.chunk\",\"created\":1791215185,\"model\":\"qwen/qwen3-30b-a3b\",\"provider\":\"DeepInfra\",\"choices\":[{\"index\":0,\"delta\":{\"content\":\"\",\"role\":\"assistant\",\"reasoning\":\" URL\",\"reasoning_details\":[{\"type\":\"reasoning.text\",\"text\":\" URL\",\"format\":\"unknown\",\"index\":0}]},\"finish_reason\":null,\"native_finish_reason\":null}]}", - "data: {\"id\":\"gen-1791215185-kD0GaG3sgjyoR5VOl5oJ\",\"object\":\"chat.completion.chunk\",\"created\":1791215185,\"model\":\"qwen/qwen3-30b-a3b\",\"provider\":\"DeepInfra\",\"choices\":[{\"index\":0,\"delta\":{\"content\":\"\",\"role\":\"assistant\",\"reasoning\":\".\",\"reasoning_details\":[{\"type\":\"reasoning.text\",\"text\":\".\",\"format\":\"unknown\",\"index\":0}]},\"finish_reason\":null,\"native_finish_reason\":null}]}", - "data: {\"id\":\"gen-1791215185-kD0GaG3sgjyoR5VOl5oJ\",\"object\":\"chat.completion.chunk\",\"created\":1791215185,\"model\":\"qwen/qwen3-30b-a3b\",\"provider\":\"DeepInfra\",\"choices\":[{\"index\":0,\"delta\":{\"content\":\"\",\"role\":\"assistant\",\"reasoning\":\" The\",\"reasoning_details\":[{\"type\":\"reasoning.text\",\"text\":\" The\",\"format\":\"unknown\",\"index\":0}]},\"finish_reason\":null,\"native_finish_reason\":null}]}", - "data: {\"id\":\"gen-1791215185-kD0GaG3sgjyoR5VOl5oJ\",\"object\":\"chat.completion.chunk\",\"created\":1791215185,\"model\":\"qwen/qwen3-30b-a3b\",\"provider\":\"DeepInfra\",\"choices\":[{\"index\":0,\"delta\":{\"content\":\"\",\"role\":\"assistant\",\"reasoning\":\" read\",\"reasoning_details\":[{\"type\":\"reasoning.text\",\"text\":\" read\",\"format\":\"unknown\",\"index\":0}]},\"finish_reason\":null,\"native_finish_reason\":null}]}", - "data: {\"id\":\"gen-1791215185-kD0GaG3sgjyoR5VOl5oJ\",\"object\":\"chat.completion.chunk\",\"created\":1791215185,\"model\":\"qwen/qwen3-30b-a3b\",\"provider\":\"DeepInfra\",\"choices\":[{\"index\":0,\"delta\":{\"content\":\"\",\"role\":\"assistant\",\"reasoning\":\" result\",\"reasoning_details\":[{\"type\":\"reasoning.text\",\"text\":\" result\",\"format\":\"unknown\",\"index\":0}]},\"finish_reason\":null,\"native_finish_reason\":null}]}", - "data: {\"id\":\"gen-1791215185-kD0GaG3sgjyoR5VOl5oJ\",\"object\":\"chat.completion.chunk\",\"created\":1791215185,\"model\":\"qwen/qwen3-30b-a3b\",\"provider\":\"DeepInfra\",\"choices\":[{\"index\":0,\"delta\":{\"content\":\"\",\"role\":\"assistant\",\"reasoning\":\" came\",\"reasoning_details\":[{\"type\":\"reasoning.text\",\"text\":\" came\",\"format\":\"unknown\",\"index\":0}]},\"finish_reason\":null,\"native_finish_reason\":null}]}", - "data: {\"id\":\"gen-1791215185-kD0GaG3sgjyoR5VOl5oJ\",\"object\":\"chat.completion.chunk\",\"created\":1791215185,\"model\":\"qwen/qwen3-30b-a3b\",\"provider\":\"DeepInfra\",\"choices\":[{\"index\":0,\"delta\":{\"content\":\"\",\"role\":\"assistant\",\"reasoning\":\" back\",\"reasoning_details\":[{\"type\":\"reasoning.text\",\"text\":\" back\",\"format\":\"unknown\",\"index\":0}]},\"finish_reason\":null,\"native_finish_reason\":null}]}", - "data: {\"id\":\"gen-1791215185-kD0GaG3sgjyoR5VOl5oJ\",\"object\":\"chat.completion.chunk\",\"created\":1791215185,\"model\":\"qwen/qwen3-30b-a3b\",\"provider\":\"DeepInfra\",\"choices\":[{\"index\":0,\"delta\":{\"content\":\"\",\"role\":\"assistant\",\"reasoning\":\" with\",\"reasoning_details\":[{\"type\":\"reasoning.text\",\"text\":\" with\",\"format\":\"unknown\",\"index\":0}]},\"finish_reason\":null,\"native_finish_reason\":null}]}", - "data: {\"id\":\"gen-1791215185-kD0GaG3sgjyoR5VOl5oJ\",\"object\":\"chat.completion.chunk\",\"created\":1791215185,\"model\":\"qwen/qwen3-30b-a3b\",\"provider\":\"DeepInfra\",\"choices\":[{\"index\":0,\"delta\":{\"content\":\"\",\"role\":\"assistant\",\"reasoning\":\" the\",\"reasoning_details\":[{\"type\":\"reasoning.text\",\"text\":\" the\",\"format\":\"unknown\",\"index\":0}]},\"finish_reason\":null,\"native_finish_reason\":null}]}", - "data: {\"id\":\"gen-1791215185-kD0GaG3sgjyoR5VOl5oJ\",\"object\":\"chat.completion.chunk\",\"created\":1791215185,\"model\":\"qwen/qwen3-30b-a3b\",\"provider\":\"DeepInfra\",\"choices\":[{\"index\":0,\"delta\":{\"content\":\"\",\"role\":\"assistant\",\"reasoning\":\" same\",\"reasoning_details\":[{\"type\":\"reasoning.text\",\"text\":\" same\",\"format\":\"unknown\",\"index\":0}]},\"finish_reason\":null,\"native_finish_reason\":null}]}", - "data: {\"id\":\"gen-1791215185-kD0GaG3sgjyoR5VOl5oJ\",\"object\":\"chat.completion.chunk\",\"created\":1791215185,\"model\":\"qwen/qwen3-30b-a3b\",\"provider\":\"DeepInfra\",\"choices\":[{\"index\":0,\"delta\":{\"content\":\"\",\"role\":\"assistant\",\"reasoning\":\" excerpt\",\"reasoning_details\":[{\"type\":\"reasoning.text\",\"text\":\" excerpt\",\"format\":\"unknown\",\"index\":0}]},\"finish_reason\":null,\"native_finish_reason\":null}]}", - "data: {\"id\":\"gen-1791215185-kD0GaG3sgjyoR5VOl5oJ\",\"object\":\"chat.completion.chunk\",\"created\":1791215185,\"model\":\"qwen/qwen3-30b-a3b\",\"provider\":\"DeepInfra\",\"choices\":[{\"index\":0,\"delta\":{\"content\":\"\",\"role\":\"assistant\",\"reasoning\":\" as\",\"reasoning_details\":[{\"type\":\"reasoning.text\",\"text\":\" as\",\"format\":\"unknown\",\"index\":0}]},\"finish_reason\":null,\"native_finish_reason\":null}]}", - "data: {\"id\":\"gen-1791215185-kD0GaG3sgjyoR5VOl5oJ\",\"object\":\"chat.completion.chunk\",\"created\":1791215185,\"model\":\"qwen/qwen3-30b-a3b\",\"provider\":\"DeepInfra\",\"choices\":[{\"index\":0,\"delta\":{\"content\":\"\",\"role\":\"assistant\",\"reasoning\":\" before\",\"reasoning_details\":[{\"type\":\"reasoning.text\",\"text\":\" before\",\"format\":\"unknown\",\"index\":0}]},\"finish_reason\":null,\"native_finish_reason\":null}]}", - "data: {\"id\":\"gen-1791215185-kD0GaG3sgjyoR5VOl5oJ\",\"object\":\"chat.completion.chunk\",\"created\":1791215185,\"model\":\"qwen/qwen3-30b-a3b\",\"provider\":\"DeepInfra\",\"choices\":[{\"index\":0,\"delta\":{\"content\":\"\",\"role\":\"assistant\",\"reasoning\":\".\",\"reasoning_details\":[{\"type\":\"reasoning.text\",\"text\":\".\",\"format\":\"unknown\",\"index\":0}]},\"finish_reason\":null,\"native_finish_reason\":null}]}", - "data: {\"id\":\"gen-1791215185-kD0GaG3sgjyoR5VOl5oJ\",\"object\":\"chat.completion.chunk\",\"created\":1791215185,\"model\":\"qwen/qwen3-30b-a3b\",\"provider\":\"DeepInfra\",\"choices\":[{\"index\":0,\"delta\":{\"content\":\"\",\"role\":\"assistant\",\"reasoning\":\" Now\",\"reasoning_details\":[{\"type\":\"reasoning.text\",\"text\":\" Now\",\"format\":\"unknown\",\"index\":0}]},\"finish_reason\":null,\"native_finish_reason\":null}]}", - "data: {\"id\":\"gen-1791215185-kD0GaG3sgjyoR5VOl5oJ\",\"object\":\"chat.completion.chunk\",\"created\":1791215185,\"model\":\"qwen/qwen3-30b-a3b\",\"provider\":\"DeepInfra\",\"choices\":[{\"index\":0,\"delta\":{\"content\":\"\",\"role\":\"assistant\",\"reasoning\":\",\",\"reasoning_details\":[{\"type\":\"reasoning.text\",\"text\":\",\",\"format\":\"unknown\",\"index\":0}]},\"finish_reason\":null,\"native_finish_reason\":null}]}", - "data: {\"id\":\"gen-1791215185-kD0GaG3sgjyoR5VOl5oJ\",\"object\":\"chat.completion.chunk\",\"created\":1791215185,\"model\":\"qwen/qwen3-30b-a3b\",\"provider\":\"DeepInfra\",\"choices\":[{\"index\":0,\"delta\":{\"content\":\"\",\"role\":\"assistant\",\"reasoning\":\" I\",\"reasoning_details\":[{\"type\":\"reasoning.text\",\"text\":\" I\",\"format\":\"unknown\",\"index\":0}]},\"finish_reason\":null,\"native_finish_reason\":null}]}", - "data: {\"id\":\"gen-1791215185-kD0GaG3sgjyoR5VOl5oJ\",\"object\":\"chat.completion.chunk\",\"created\":1791215185,\"model\":\"qwen/qwen3-30b-a3b\",\"provider\":\"DeepInfra\",\"choices\":[{\"index\":0,\"delta\":{\"content\":\"\",\"role\":\"assistant\",\"reasoning\":\" need\",\"reasoning_details\":[{\"type\":\"reasoning.text\",\"text\":\" need\",\"format\":\"unknown\",\"index\":0}]},\"finish_reason\":null,\"native_finish_reason\":null}]}", - "data: {\"id\":\"gen-1791215185-kD0GaG3sgjyoR5VOl5oJ\",\"object\":\"chat.completion.chunk\",\"created\":1791215185,\"model\":\"qwen/qwen3-30b-a3b\",\"provider\":\"DeepInfra\",\"choices\":[{\"index\":0,\"delta\":{\"content\":\"\",\"role\":\"assistant\",\"reasoning\":\" to\",\"reasoning_details\":[{\"type\":\"reasoning.text\",\"text\":\" to\",\"format\":\"unknown\",\"index\":0}]},\"finish_reason\":null,\"native_finish_reason\":null}]}", - "data: {\"id\":\"gen-1791215185-kD0GaG3sgjyoR5VOl5oJ\",\"object\":\"chat.completion.chunk\",\"created\":1791215185,\"model\":\"qwen/qwen3-30b-a3b\",\"provider\":\"DeepInfra\",\"choices\":[{\"index\":0,\"delta\":{\"content\":\"\",\"role\":\"assistant\",\"reasoning\":\" check\",\"reasoning_details\":[{\"type\":\"reasoning.text\",\"text\":\" check\",\"format\":\"unknown\",\"index\":0}]},\"finish_reason\":null,\"native_finish_reason\":null}]}", - "data: {\"id\":\"gen-1791215185-kD0GaG3sgjyoR5VOl5oJ\",\"object\":\"chat.completion.chunk\",\"created\":1791215185,\"model\":\"qwen/qwen3-30b-a3b\",\"provider\":\"DeepInfra\",\"choices\":[{\"index\":0,\"delta\":{\"content\":\"\",\"role\":\"assistant\",\"reasoning\":\" if\",\"reasoning_details\":[{\"type\":\"reasoning.text\",\"text\":\" if\",\"format\":\"unknown\",\"index\":0}]},\"finish_reason\":null,\"native_finish_reason\":null}]}", - "data: {\"id\":\"gen-1791215185-kD0GaG3sgjyoR5VOl5oJ\",\"object\":\"chat.completion.chunk\",\"created\":1791215185,\"model\":\"qwen/qwen3-30b-a3b\",\"provider\":\"DeepInfra\",\"choices\":[{\"index\":0,\"delta\":{\"content\":\"\",\"role\":\"assistant\",\"reasoning\":\" there\",\"reasoning_details\":[{\"type\":\"reasoning.text\",\"text\":\" there\",\"format\":\"unknown\",\"index\":0}]},\"finish_reason\":null,\"native_finish_reason\":null}]}", - "data: {\"id\":\"gen-1791215185-kD0GaG3sgjyoR5VOl5oJ\",\"object\":\"chat.completion.chunk\",\"created\":1791215185,\"model\":\"qwen/qwen3-30b-a3b\",\"provider\":\"DeepInfra\",\"choices\":[{\"index\":0,\"delta\":{\"content\":\"\",\"role\":\"assistant\",\"reasoning\":\"'s\",\"reasoning_details\":[{\"type\":\"reasoning.text\",\"text\":\"'s\",\"format\":\"unknown\",\"index\":0}]},\"finish_reason\":null,\"native_finish_reason\":null}]}", - "data: {\"id\":\"gen-1791215185-kD0GaG3sgjyoR5VOl5oJ\",\"object\":\"chat.completion.chunk\",\"created\":1791215185,\"model\":\"qwen/qwen3-30b-a3b\",\"provider\":\"DeepInfra\",\"choices\":[{\"index\":0,\"delta\":{\"content\":\"\",\"role\":\"assistant\",\"reasoning\":\" a\",\"reasoning_details\":[{\"type\":\"reasoning.text\",\"text\":\" a\",\"format\":\"unknown\",\"index\":0}]},\"finish_reason\":null,\"native_finish_reason\":null}]}", - "data: {\"id\":\"gen-1791215185-kD0GaG3sgjyoR5VOl5oJ\",\"object\":\"chat.completion.chunk\",\"created\":1791215185,\"model\":\"qwen/qwen3-30b-a3b\",\"provider\":\"DeepInfra\",\"choices\":[{\"index\":0,\"delta\":{\"content\":\"\",\"role\":\"assistant\",\"reasoning\":\" need\",\"reasoning_details\":[{\"type\":\"reasoning.text\",\"text\":\" need\",\"format\":\"unknown\",\"index\":0}]},\"finish_reason\":null,\"native_finish_reason\":null}]}", - "data: {\"id\":\"gen-1791215185-kD0GaG3sgjyoR5VOl5oJ\",\"object\":\"chat.completion.chunk\",\"created\":1791215185,\"model\":\"qwen/qwen3-30b-a3b\",\"provider\":\"DeepInfra\",\"choices\":[{\"index\":0,\"delta\":{\"content\":\"\",\"role\":\"assistant\",\"reasoning\":\" to\",\"reasoning_details\":[{\"type\":\"reasoning.text\",\"text\":\" to\",\"format\":\"unknown\",\"index\":0}]},\"finish_reason\":null,\"native_finish_reason\":null}]}", - "data: {\"id\":\"gen-1791215185-kD0GaG3sgjyoR5VOl5oJ\",\"object\":\"chat.completion.chunk\",\"created\":1791215185,\"model\":\"qwen/qwen3-30b-a3b\",\"provider\":\"DeepInfra\",\"choices\":[{\"index\":0,\"delta\":{\"content\":\"\",\"role\":\"assistant\",\"reasoning\":\" call\",\"reasoning_details\":[{\"type\":\"reasoning.text\",\"text\":\" call\",\"format\":\"unknown\",\"index\":0}]},\"finish_reason\":null,\"native_finish_reason\":null}]}", - "data: {\"id\":\"gen-1791215185-kD0GaG3sgjyoR5VOl5oJ\",\"object\":\"chat.completion.chunk\",\"created\":1791215185,\"model\":\"qwen/qwen3-30b-a3b\",\"provider\":\"DeepInfra\",\"choices\":[{\"index\":0,\"delta\":{\"content\":\"\",\"role\":\"assistant\",\"reasoning\":\" task\",\"reasoning_details\":[{\"type\":\"reasoning.text\",\"text\":\" task\",\"format\":\"unknown\",\"index\":0}]},\"finish_reason\":null,\"native_finish_reason\":null}]}", - "data: {\"id\":\"gen-1791215185-kD0GaG3sgjyoR5VOl5oJ\",\"object\":\"chat.completion.chunk\",\"created\":1791215185,\"model\":\"qwen/qwen3-30b-a3b\",\"provider\":\"DeepInfra\",\"choices\":[{\"index\":0,\"delta\":{\"content\":\"\",\"role\":\"assistant\",\"reasoning\":\"_wait\",\"reasoning_details\":[{\"type\":\"reasoning.text\",\"text\":\"_wait\",\"format\":\"unknown\",\"index\":0}]},\"finish_reason\":null,\"native_finish_reason\":null}]}", - "data: {\"id\":\"gen-1791215185-kD0GaG3sgjyoR5VOl5oJ\",\"object\":\"chat.completion.chunk\",\"created\":1791215185,\"model\":\"qwen/qwen3-30b-a3b\",\"provider\":\"DeepInfra\",\"choices\":[{\"index\":0,\"delta\":{\"content\":\"\",\"role\":\"assistant\",\"reasoning\":\",\",\"reasoning_details\":[{\"type\":\"reasoning.text\",\"text\":\",\",\"format\":\"unknown\",\"index\":0}]},\"finish_reason\":null,\"native_finish_reason\":null}]}", - "data: {\"id\":\"gen-1791215185-kD0GaG3sgjyoR5VOl5oJ\",\"object\":\"chat.completion.chunk\",\"created\":1791215185,\"model\":\"qwen/qwen3-30b-a3b\",\"provider\":\"DeepInfra\",\"choices\":[{\"index\":0,\"delta\":{\"content\":\"\",\"role\":\"assistant\",\"reasoning\":\" but\",\"reasoning_details\":[{\"type\":\"reasoning.text\",\"text\":\" but\",\"format\":\"unknown\",\"index\":0}]},\"finish_reason\":null,\"native_finish_reason\":null}]}", - "data: {\"id\":\"gen-1791215185-kD0GaG3sgjyoR5VOl5oJ\",\"object\":\"chat.completion.chunk\",\"created\":1791215185,\"model\":\"qwen/qwen3-30b-a3b\",\"provider\":\"DeepInfra\",\"choices\":[{\"index\":0,\"delta\":{\"content\":\"\",\"role\":\"assistant\",\"reasoning\":\" since\",\"reasoning_details\":[{\"type\":\"reasoning.text\",\"text\":\" since\",\"format\":\"unknown\",\"index\":0}]},\"finish_reason\":null,\"native_finish_reason\":null}]}", - "data: {\"id\":\"gen-1791215185-kD0GaG3sgjyoR5VOl5oJ\",\"object\":\"chat.completion.chunk\",\"created\":1791215185,\"model\":\"qwen/qwen3-30b-a3b\",\"provider\":\"DeepInfra\",\"choices\":[{\"index\":0,\"delta\":{\"content\":\"\",\"role\":\"assistant\",\"reasoning\":\" the\",\"reasoning_details\":[{\"type\":\"reasoning.text\",\"text\":\" the\",\"format\":\"unknown\",\"index\":0}]},\"finish_reason\":null,\"native_finish_reason\":null}]}", - "data: {\"id\":\"gen-1791215185-kD0GaG3sgjyoR5VOl5oJ\",\"object\":\"chat.completion.chunk\",\"created\":1791215185,\"model\":\"qwen/qwen3-30b-a3b\",\"provider\":\"DeepInfra\",\"choices\":[{\"index\":0,\"delta\":{\"content\":\"\",\"role\":\"assistant\",\"reasoning\":\" researcher\",\"reasoning_details\":[{\"type\":\"reasoning.text\",\"text\":\" researcher\",\"format\":\"unknown\",\"index\":0}]},\"finish_reason\":null,\"native_finish_reason\":null}]}", - "data: {\"id\":\"gen-1791215185-kD0GaG3sgjyoR5VOl5oJ\",\"object\":\"chat.completion.chunk\",\"created\":1791215185,\"model\":\"qwen/qwen3-30b-a3b\",\"provider\":\"DeepInfra\",\"choices\":[{\"index\":0,\"delta\":{\"content\":\"\",\"role\":\"assistant\",\"reasoning\":\" result\",\"reasoning_details\":[{\"type\":\"reasoning.text\",\"text\":\" result\",\"format\":\"unknown\",\"index\":0}]},\"finish_reason\":null,\"native_finish_reason\":null}]}", - "data: {\"id\":\"gen-1791215185-kD0GaG3sgjyoR5VOl5oJ\",\"object\":\"chat.completion.chunk\",\"created\":1791215185,\"model\":\"qwen/qwen3-30b-a3b\",\"provider\":\"DeepInfra\",\"choices\":[{\"index\":0,\"delta\":{\"content\":\"\",\"role\":\"assistant\",\"reasoning\":\" doesn\",\"reasoning_details\":[{\"type\":\"reasoning.text\",\"text\":\" doesn\",\"format\":\"unknown\",\"index\":0}]},\"finish_reason\":null,\"native_finish_reason\":null}]}", - "data: {\"id\":\"gen-1791215185-kD0GaG3sgjyoR5VOl5oJ\",\"object\":\"chat.completion.chunk\",\"created\":1791215185,\"model\":\"qwen/qwen3-30b-a3b\",\"provider\":\"DeepInfra\",\"choices\":[{\"index\":0,\"delta\":{\"content\":\"\",\"role\":\"assistant\",\"reasoning\":\"'t\",\"reasoning_details\":[{\"type\":\"reasoning.text\",\"text\":\"'t\",\"format\":\"unknown\",\"index\":0}]},\"finish_reason\":null,\"native_finish_reason\":null}]}", - "data: {\"id\":\"gen-1791215185-kD0GaG3sgjyoR5VOl5oJ\",\"object\":\"chat.completion.chunk\",\"created\":1791215185,\"model\":\"qwen/qwen3-30b-a3b\",\"provider\":\"DeepInfra\",\"choices\":[{\"index\":0,\"delta\":{\"content\":\"\",\"role\":\"assistant\",\"reasoning\":\" mention\",\"reasoning_details\":[{\"type\":\"reasoning.text\",\"text\":\" mention\",\"format\":\"unknown\",\"index\":0}]},\"finish_reason\":null,\"native_finish_reason\":null}]}", - "data: {\"id\":\"gen-1791215185-kD0GaG3sgjyoR5VOl5oJ\",\"object\":\"chat.completion.chunk\",\"created\":1791215185,\"model\":\"qwen/qwen3-30b-a3b\",\"provider\":\"DeepInfra\",\"choices\":[{\"index\":0,\"delta\":{\"content\":\"\",\"role\":\"assistant\",\"reasoning\":\" \\\"\",\"reasoning_details\":[{\"type\":\"reasoning.text\",\"text\":\" \\\"\",\"format\":\"unknown\",\"index\":0}]},\"finish_reason\":null,\"native_finish_reason\":null}]}", - "data: {\"id\":\"gen-1791215185-kD0GaG3sgjyoR5VOl5oJ\",\"object\":\"chat.completion.chunk\",\"created\":1791215185,\"model\":\"qwen/qwen3-30b-a3b\",\"provider\":\"DeepInfra\",\"choices\":[{\"index\":0,\"delta\":{\"content\":\"\",\"role\":\"assistant\",\"reasoning\":\"Started\",\"reasoning_details\":[{\"type\":\"reasoning.text\",\"text\":\"Started\",\"format\":\"unknown\",\"index\":0}]},\"finish_reason\":null,\"native_finish_reason\":null}]}", - "data: {\"id\":\"gen-1791215185-kD0GaG3sgjyoR5VOl5oJ\",\"object\":\"chat.completion.chunk\",\"created\":1791215185,\"model\":\"qwen/qwen3-30b-a3b\",\"provider\":\"DeepInfra\",\"choices\":[{\"index\":0,\"delta\":{\"content\":\"\",\"role\":\"assistant\",\"reasoning\":\" task\",\"reasoning_details\":[{\"type\":\"reasoning.text\",\"text\":\" task\",\"format\":\"unknown\",\"index\":0}]},\"finish_reason\":null,\"native_finish_reason\":null}]}", - "data: {\"id\":\"gen-1791215185-kD0GaG3sgjyoR5VOl5oJ\",\"object\":\"chat.completion.chunk\",\"created\":1791215185,\"model\":\"qwen/qwen3-30b-a3b\",\"provider\":\"DeepInfra\",\"choices\":[{\"index\":0,\"delta\":{\"content\":\"\",\"role\":\"assistant\",\"reasoning\":\",\\\"\",\"reasoning_details\":[{\"type\":\"reasoning.text\",\"text\":\",\\\"\",\"format\":\"unknown\",\"index\":0}]},\"finish_reason\":null,\"native_finish_reason\":null}]}", - "data: {\"id\":\"gen-1791215185-kD0GaG3sgjyoR5VOl5oJ\",\"object\":\"chat.completion.chunk\",\"created\":1791215185,\"model\":\"qwen/qwen3-30b-a3b\",\"provider\":\"DeepInfra\",\"choices\":[{\"index\":0,\"delta\":{\"content\":\"\",\"role\":\"assistant\",\"reasoning\":\" maybe\",\"reasoning_details\":[{\"type\":\"reasoning.text\",\"text\":\" maybe\",\"format\":\"unknown\",\"index\":0}]},\"finish_reason\":null,\"native_finish_reason\":null}]}", - "data: {\"id\":\"gen-1791215185-kD0GaG3sgjyoR5VOl5oJ\",\"object\":\"chat.completion.chunk\",\"created\":1791215185,\"model\":\"qwen/qwen3-30b-a3b\",\"provider\":\"DeepInfra\",\"choices\":[{\"index\":0,\"delta\":{\"content\":\"\",\"role\":\"assistant\",\"reasoning\":\" not\",\"reasoning_details\":[{\"type\":\"reasoning.text\",\"text\":\" not\",\"format\":\"unknown\",\"index\":0}]},\"finish_reason\":null,\"native_finish_reason\":null}]}", - "data: {\"id\":\"gen-1791215185-kD0GaG3sgjyoR5VOl5oJ\",\"object\":\"chat.completion.chunk\",\"created\":1791215185,\"model\":\"qwen/qwen3-30b-a3b\",\"provider\":\"DeepInfra\",\"choices\":[{\"index\":0,\"delta\":{\"content\":\"\",\"role\":\"assistant\",\"reasoning\":\".\",\"reasoning_details\":[{\"type\":\"reasoning.text\",\"text\":\".\",\"format\":\"unknown\",\"index\":0}]},\"finish_reason\":null,\"native_finish_reason\":null}]}", - "data: {\"id\":\"gen-1791215185-kD0GaG3sgjyoR5VOl5oJ\",\"object\":\"chat.completion.chunk\",\"created\":1791215185,\"model\":\"qwen/qwen3-30b-a3b\",\"provider\":\"DeepInfra\",\"choices\":[{\"index\":0,\"delta\":{\"content\":\"\",\"role\":\"assistant\",\"reasoning\":\" The\",\"reasoning_details\":[{\"type\":\"reasoning.text\",\"text\":\" The\",\"format\":\"unknown\",\"index\":0}]},\"finish_reason\":null,\"native_finish_reason\":null}]}", - "data: {\"id\":\"gen-1791215185-kD0GaG3sgjyoR5VOl5oJ\",\"object\":\"chat.completion.chunk\",\"created\":1791215185,\"model\":\"qwen/qwen3-30b-a3b\",\"provider\":\"DeepInfra\",\"choices\":[{\"index\":0,\"delta\":{\"content\":\"\",\"role\":\"assistant\",\"reasoning\":\" final\",\"reasoning_details\":[{\"type\":\"reasoning.text\",\"text\":\" final\",\"format\":\"unknown\",\"index\":0}]},\"finish_reason\":null,\"native_finish_reason\":null}]}", - "data: {\"id\":\"gen-1791215185-kD0GaG3sgjyoR5VOl5oJ\",\"object\":\"chat.completion.chunk\",\"created\":1791215185,\"model\":\"qwen/qwen3-30b-a3b\",\"provider\":\"DeepInfra\",\"choices\":[{\"index\":0,\"delta\":{\"content\":\"\",\"role\":\"assistant\",\"reasoning\":\" answer\",\"reasoning_details\":[{\"type\":\"reasoning.text\",\"text\":\" answer\",\"format\":\"unknown\",\"index\":0}]},\"finish_reason\":null,\"native_finish_reason\":null}]}", - "data: {\"id\":\"gen-1791215185-kD0GaG3sgjyoR5VOl5oJ\",\"object\":\"chat.completion.chunk\",\"created\":1791215185,\"model\":\"qwen/qwen3-30b-a3b\",\"provider\":\"DeepInfra\",\"choices\":[{\"index\":0,\"delta\":{\"content\":\"\",\"role\":\"assistant\",\"reasoning\":\" should\",\"reasoning_details\":[{\"type\":\"reasoning.text\",\"text\":\" should\",\"format\":\"unknown\",\"index\":0}]},\"finish_reason\":null,\"native_finish_reason\":null}]}", - "data: {\"id\":\"gen-1791215185-kD0GaG3sgjyoR5VOl5oJ\",\"object\":\"chat.completion.chunk\",\"created\":1791215185,\"model\":\"qwen/qwen3-30b-a3b\",\"provider\":\"DeepInfra\",\"choices\":[{\"index\":0,\"delta\":{\"content\":\"\",\"role\":\"assistant\",\"reasoning\":\" include\",\"reasoning_details\":[{\"type\":\"reasoning.text\",\"text\":\" include\",\"format\":\"unknown\",\"index\":0}]},\"finish_reason\":null,\"native_finish_reason\":null}]}", - "data: {\"id\":\"gen-1791215185-kD0GaG3sgjyoR5VOl5oJ\",\"object\":\"chat.completion.chunk\",\"created\":1791215185,\"model\":\"qwen/qwen3-30b-a3b\",\"provider\":\"DeepInfra\",\"choices\":[{\"index\":0,\"delta\":{\"content\":\"\",\"role\":\"assistant\",\"reasoning\":\" the\",\"reasoning_details\":[{\"type\":\"reasoning.text\",\"text\":\" the\",\"format\":\"unknown\",\"index\":0}]},\"finish_reason\":null,\"native_finish_reason\":null}]}", - "data: {\"id\":\"gen-1791215185-kD0GaG3sgjyoR5VOl5oJ\",\"object\":\"chat.completion.chunk\",\"created\":1791215185,\"model\":\"qwen/qwen3-30b-a3b\",\"provider\":\"DeepInfra\",\"choices\":[{\"index\":0,\"delta\":{\"content\":\"\",\"role\":\"assistant\",\"reasoning\":\" researcher\",\"reasoning_details\":[{\"type\":\"reasoning.text\",\"text\":\" researcher\",\"format\":\"unknown\",\"index\":0}]},\"finish_reason\":null,\"native_finish_reason\":null}]}", - "data: {\"id\":\"gen-1791215185-kD0GaG3sgjyoR5VOl5oJ\",\"object\":\"chat.completion.chunk\",\"created\":1791215185,\"model\":\"qwen/qwen3-30b-a3b\",\"provider\":\"DeepInfra\",\"choices\":[{\"index\":0,\"delta\":{\"content\":\"\",\"role\":\"assistant\",\"reasoning\":\" result\",\"reasoning_details\":[{\"type\":\"reasoning.text\",\"text\":\" result\",\"format\":\"unknown\",\"index\":0}]},\"finish_reason\":null,\"native_finish_reason\":null}]}", - "data: {\"id\":\"gen-1791215185-kD0GaG3sgjyoR5VOl5oJ\",\"object\":\"chat.completion.chunk\",\"created\":1791215185,\"model\":\"qwen/qwen3-30b-a3b\",\"provider\":\"DeepInfra\",\"choices\":[{\"index\":0,\"delta\":{\"content\":\"\",\"role\":\"assistant\",\"reasoning\":\",\",\"reasoning_details\":[{\"type\":\"reasoning.text\",\"text\":\",\",\"format\":\"unknown\",\"index\":0}]},\"finish_reason\":null,\"native_finish_reason\":null}]}", - "data: {\"id\":\"gen-1791215185-kD0GaG3sgjyoR5VOl5oJ\",\"object\":\"chat.completion.chunk\",\"created\":1791215185,\"model\":\"qwen/qwen3-30b-a3b\",\"provider\":\"DeepInfra\",\"choices\":[{\"index\":0,\"delta\":{\"content\":\"\",\"role\":\"assistant\",\"reasoning\":\" the\",\"reasoning_details\":[{\"type\":\"reasoning.text\",\"text\":\" the\",\"format\":\"unknown\",\"index\":0}]},\"finish_reason\":null,\"native_finish_reason\":null}]}", - "data: {\"id\":\"gen-1791215185-kD0GaG3sgjyoR5VOl5oJ\",\"object\":\"chat.completion.chunk\",\"created\":1791215185,\"model\":\"qwen/qwen3-30b-a3b\",\"provider\":\"DeepInfra\",\"choices\":[{\"index\":0,\"delta\":{\"content\":\"\",\"role\":\"assistant\",\"reasoning\":\" title\",\"reasoning_details\":[{\"type\":\"reasoning.text\",\"text\":\" title\",\"format\":\"unknown\",\"index\":0}]},\"finish_reason\":null,\"native_finish_reason\":null}]}", - "data: {\"id\":\"gen-1791215185-kD0GaG3sgjyoR5VOl5oJ\",\"object\":\"chat.completion.chunk\",\"created\":1791215185,\"model\":\"qwen/qwen3-30b-a3b\",\"provider\":\"DeepInfra\",\"choices\":[{\"index\":0,\"delta\":{\"content\":\"\",\"role\":\"assistant\",\"reasoning\":\",\",\"reasoning_details\":[{\"type\":\"reasoning.text\",\"text\":\",\",\"format\":\"unknown\",\"index\":0}]},\"finish_reason\":null,\"native_finish_reason\":null}]}", - "data: {\"id\":\"gen-1791215185-kD0GaG3sgjyoR5VOl5oJ\",\"object\":\"chat.completion.chunk\",\"created\":1791215185,\"model\":\"qwen/qwen3-30b-a3b\",\"provider\":\"DeepInfra\",\"choices\":[{\"index\":0,\"delta\":{\"content\":\"\",\"role\":\"assistant\",\"reasoning\":\" URL\",\"reasoning_details\":[{\"type\":\"reasoning.text\",\"text\":\" URL\",\"format\":\"unknown\",\"index\":0}]},\"finish_reason\":null,\"native_finish_reason\":null}]}", - "data: {\"id\":\"gen-1791215185-kD0GaG3sgjyoR5VOl5oJ\",\"object\":\"chat.completion.chunk\",\"created\":1791215185,\"model\":\"qwen/qwen3-30b-a3b\",\"provider\":\"DeepInfra\",\"choices\":[{\"index\":0,\"delta\":{\"content\":\"\",\"role\":\"assistant\",\"reasoning\":\",\",\"reasoning_details\":[{\"type\":\"reasoning.text\",\"text\":\",\",\"format\":\"unknown\",\"index\":0}]},\"finish_reason\":null,\"native_finish_reason\":null}]}", - "data: {\"id\":\"gen-1791215185-kD0GaG3sgjyoR5VOl5oJ\",\"object\":\"chat.completion.chunk\",\"created\":1791215185,\"model\":\"qwen/qwen3-30b-a3b\",\"provider\":\"DeepInfra\",\"choices\":[{\"index\":0,\"delta\":{\"content\":\"\",\"role\":\"assistant\",\"reasoning\":\" and\",\"reasoning_details\":[{\"type\":\"reasoning.text\",\"text\":\" and\",\"format\":\"unknown\",\"index\":0}]},\"finish_reason\":null,\"native_finish_reason\":null}]}", - "data: {\"id\":\"gen-1791215185-kD0GaG3sgjyoR5VOl5oJ\",\"object\":\"chat.completion.chunk\",\"created\":1791215185,\"model\":\"qwen/qwen3-30b-a3b\",\"provider\":\"DeepInfra\",\"choices\":[{\"index\":0,\"delta\":{\"content\":\"\",\"role\":\"assistant\",\"reasoning\":\" excerpt\",\"reasoning_details\":[{\"type\":\"reasoning.text\",\"text\":\" excerpt\",\"format\":\"unknown\",\"index\":0}]},\"finish_reason\":null,\"native_finish_reason\":null}]}", - "data: {\"id\":\"gen-1791215185-kD0GaG3sgjyoR5VOl5oJ\",\"object\":\"chat.completion.chunk\",\"created\":1791215185,\"model\":\"qwen/qwen3-30b-a3b\",\"provider\":\"DeepInfra\",\"choices\":[{\"index\":0,\"delta\":{\"content\":\"\",\"role\":\"assistant\",\"reasoning\":\".\",\"reasoning_details\":[{\"type\":\"reasoning.text\",\"text\":\".\",\"format\":\"unknown\",\"index\":0}]},\"finish_reason\":null,\"native_finish_reason\":null}]}", - "data: {\"id\":\"gen-1791215185-kD0GaG3sgjyoR5VOl5oJ\",\"object\":\"chat.completion.chunk\",\"created\":1791215185,\"model\":\"qwen/qwen3-30b-a3b\",\"provider\":\"DeepInfra\",\"choices\":[{\"index\":0,\"delta\":{\"content\":\"\",\"role\":\"assistant\",\"reasoning\":\" Let\",\"reasoning_details\":[{\"type\":\"reasoning.text\",\"text\":\" Let\",\"format\":\"unknown\",\"index\":0}]},\"finish_reason\":null,\"native_finish_reason\":null}]}", - "data: {\"id\":\"gen-1791215185-kD0GaG3sgjyoR5VOl5oJ\",\"object\":\"chat.completion.chunk\",\"created\":1791215185,\"model\":\"qwen/qwen3-30b-a3b\",\"provider\":\"DeepInfra\",\"choices\":[{\"index\":0,\"delta\":{\"content\":\"\",\"role\":\"assistant\",\"reasoning\":\" me\",\"reasoning_details\":[{\"type\":\"reasoning.text\",\"text\":\" me\",\"format\":\"unknown\",\"index\":0}]},\"finish_reason\":null,\"native_finish_reason\":null}]}", - "data: {\"id\":\"gen-1791215185-kD0GaG3sgjyoR5VOl5oJ\",\"object\":\"chat.completion.chunk\",\"created\":1791215185,\"model\":\"qwen/qwen3-30b-a3b\",\"provider\":\"DeepInfra\",\"choices\":[{\"index\":0,\"delta\":{\"content\":\"\",\"role\":\"assistant\",\"reasoning\":\" structure\",\"reasoning_details\":[{\"type\":\"reasoning.text\",\"text\":\" structure\",\"format\":\"unknown\",\"index\":0}]},\"finish_reason\":null,\"native_finish_reason\":null}]}", - "data: {\"id\":\"gen-1791215185-kD0GaG3sgjyoR5VOl5oJ\",\"object\":\"chat.completion.chunk\",\"created\":1791215185,\"model\":\"qwen/qwen3-30b-a3b\",\"provider\":\"DeepInfra\",\"choices\":[{\"index\":0,\"delta\":{\"content\":\"\",\"role\":\"assistant\",\"reasoning\":\" that\",\"reasoning_details\":[{\"type\":\"reasoning.text\",\"text\":\" that\",\"format\":\"unknown\",\"index\":0}]},\"finish_reason\":null,\"native_finish_reason\":null}]}", - "data: {\"id\":\"gen-1791215185-kD0GaG3sgjyoR5VOl5oJ\",\"object\":\"chat.completion.chunk\",\"created\":1791215185,\"model\":\"qwen/qwen3-30b-a3b\",\"provider\":\"DeepInfra\",\"choices\":[{\"index\":0,\"delta\":{\"content\":\"\",\"role\":\"assistant\",\"reasoning\":\" properly\",\"reasoning_details\":[{\"type\":\"reasoning.text\",\"text\":\" properly\",\"format\":\"unknown\",\"index\":0}]},\"finish_reason\":null,\"native_finish_reason\":null}]}", - "data: {\"id\":\"gen-1791215185-kD0GaG3sgjyoR5VOl5oJ\",\"object\":\"chat.completion.chunk\",\"created\":1791215185,\"model\":\"qwen/qwen3-30b-a3b\",\"provider\":\"DeepInfra\",\"choices\":[{\"index\":0,\"delta\":{\"content\":\"\",\"role\":\"assistant\",\"reasoning\":\".\\n\",\"reasoning_details\":[{\"type\":\"reasoning.text\",\"text\":\".\\n\",\"format\":\"unknown\",\"index\":0}]},\"finish_reason\":null,\"native_finish_reason\":null}]}", - "data: {\"id\":\"gen-1791215185-kD0GaG3sgjyoR5VOl5oJ\",\"object\":\"chat.completion.chunk\",\"created\":1791215185,\"model\":\"qwen/qwen3-30b-a3b\",\"provider\":\"DeepInfra\",\"choices\":[{\"index\":0,\"delta\":{\"content\":\"Final\",\"role\":\"assistant\"},\"finish_reason\":null,\"native_finish_reason\":null}]}", - "data: {\"id\":\"gen-1791215185-kD0GaG3sgjyoR5VOl5oJ\",\"object\":\"chat.completion.chunk\",\"created\":1791215185,\"model\":\"qwen/qwen3-30b-a3b\",\"provider\":\"DeepInfra\",\"choices\":[{\"index\":0,\"delta\":{\"content\":\" answer\",\"role\":\"assistant\"},\"finish_reason\":null,\"native_finish_reason\":null}]}", - "data: {\"id\":\"gen-1791215185-kD0GaG3sgjyoR5VOl5oJ\",\"object\":\"chat.completion.chunk\",\"created\":1791215185,\"model\":\"qwen/qwen3-30b-a3b\",\"provider\":\"DeepInfra\",\"choices\":[{\"index\":0,\"delta\":{\"content\":\" from\",\"role\":\"assistant\"},\"finish_reason\":null,\"native_finish_reason\":null}]}", - "data: {\"id\":\"gen-1791215185-kD0GaG3sgjyoR5VOl5oJ\",\"object\":\"chat.completion.chunk\",\"created\":1791215185,\"model\":\"qwen/qwen3-30b-a3b\",\"provider\":\"DeepInfra\",\"choices\":[{\"index\":0,\"delta\":{\"content\":\" read\",\"role\":\"assistant\"},\"finish_reason\":null,\"native_finish_reason\":null}]}", - "data: {\"id\":\"gen-1791215185-kD0GaG3sgjyoR5VOl5oJ\",\"object\":\"chat.completion.chunk\",\"created\":1791215185,\"model\":\"qwen/qwen3-30b-a3b\",\"provider\":\"DeepInfra\",\"choices\":[{\"index\":0,\"delta\":{\"content\":\":\",\"role\":\"assistant\"},\"finish_reason\":null,\"native_finish_reason\":null}]}", - "data: {\"id\":\"gen-1791215185-kD0GaG3sgjyoR5VOl5oJ\",\"object\":\"chat.completion.chunk\",\"created\":1791215185,\"model\":\"qwen/qwen3-30b-a3b\",\"provider\":\"DeepInfra\",\"choices\":[{\"index\":0,\"delta\":{\"content\":\" Eve\",\"role\":\"assistant\"},\"finish_reason\":null,\"native_finish_reason\":null}]}", - "data: {\"id\":\"gen-1791215185-kD0GaG3sgjyoR5VOl5oJ\",\"object\":\"chat.completion.chunk\",\"created\":1791215185,\"model\":\"qwen/qwen3-30b-a3b\",\"provider\":\"DeepInfra\",\"choices\":[{\"index\":0,\"delta\":{\"content\":\" instrumentation\",\"role\":\"assistant\"},\"finish_reason\":null,\"native_finish_reason\":null}]}", - "data: {\"id\":\"gen-1791215185-kD0GaG3sgjyoR5VOl5oJ\",\"object\":\"chat.completion.chunk\",\"created\":1791215185,\"model\":\"qwen/qwen3-30b-a3b\",\"provider\":\"DeepInfra\",\"choices\":[{\"index\":0,\"delta\":{\"content\":\" https\",\"role\":\"assistant\"},\"finish_reason\":null,\"native_finish_reason\":null}]}", - "data: {\"id\":\"gen-1791215185-kD0GaG3sgjyoR5VOl5oJ\",\"object\":\"chat.completion.chunk\",\"created\":1791215185,\"model\":\"qwen/qwen3-30b-a3b\",\"provider\":\"DeepInfra\",\"choices\":[{\"index\":0,\"delta\":{\"content\":\"://\",\"role\":\"assistant\"},\"finish_reason\":null,\"native_finish_reason\":null}]}", - "data: {\"id\":\"gen-1791215185-kD0GaG3sgjyoR5VOl5oJ\",\"object\":\"chat.completion.chunk\",\"created\":1791215185,\"model\":\"qwen/qwen3-30b-a3b\",\"provider\":\"DeepInfra\",\"choices\":[{\"index\":0,\"delta\":{\"content\":\"eve\",\"role\":\"assistant\"},\"finish_reason\":null,\"native_finish_reason\":null}]}", - "data: {\"id\":\"gen-1791215185-kD0GaG3sgjyoR5VOl5oJ\",\"object\":\"chat.completion.chunk\",\"created\":1791215185,\"model\":\"qwen/qwen3-30b-a3b\",\"provider\":\"DeepInfra\",\"choices\":[{\"index\":0,\"delta\":{\"content\":\".dev\",\"role\":\"assistant\"},\"finish_reason\":null,\"native_finish_reason\":null}]}", - "data: {\"id\":\"gen-1791215185-kD0GaG3sgjyoR5VOl5oJ\",\"object\":\"chat.completion.chunk\",\"created\":1791215185,\"model\":\"qwen/qwen3-30b-a3b\",\"provider\":\"DeepInfra\",\"choices\":[{\"index\":0,\"delta\":{\"content\":\"/docs\",\"role\":\"assistant\"},\"finish_reason\":null,\"native_finish_reason\":null}]}", - "data: {\"id\":\"gen-1791215185-kD0GaG3sgjyoR5VOl5oJ\",\"object\":\"chat.completion.chunk\",\"created\":1791215185,\"model\":\"qwen/qwen3-30b-a3b\",\"provider\":\"DeepInfra\",\"choices\":[{\"index\":0,\"delta\":{\"content\":\"/g\",\"role\":\"assistant\"},\"finish_reason\":null,\"native_finish_reason\":null}]}", - "data: {\"id\":\"gen-1791215185-kD0GaG3sgjyoR5VOl5oJ\",\"object\":\"chat.completion.chunk\",\"created\":1791215185,\"model\":\"qwen/qwen3-30b-a3b\",\"provider\":\"DeepInfra\",\"choices\":[{\"index\":0,\"delta\":{\"content\":\"uid\",\"role\":\"assistant\"},\"finish_reason\":null,\"native_finish_reason\":null}]}", - "data: {\"id\":\"gen-1791215185-kD0GaG3sgjyoR5VOl5oJ\",\"object\":\"chat.completion.chunk\",\"created\":1791215185,\"model\":\"qwen/qwen3-30b-a3b\",\"provider\":\"DeepInfra\",\"choices\":[{\"index\":0,\"delta\":{\"content\":\"es\",\"role\":\"assistant\"},\"finish_reason\":null,\"native_finish_reason\":null}]}", - "data: {\"id\":\"gen-1791215185-kD0GaG3sgjyoR5VOl5oJ\",\"object\":\"chat.completion.chunk\",\"created\":1791215185,\"model\":\"qwen/qwen3-30b-a3b\",\"provider\":\"DeepInfra\",\"choices\":[{\"index\":0,\"delta\":{\"content\":\"/in\",\"role\":\"assistant\"},\"finish_reason\":null,\"native_finish_reason\":null}]}", - "data: {\"id\":\"gen-1791215185-kD0GaG3sgjyoR5VOl5oJ\",\"object\":\"chat.completion.chunk\",\"created\":1791215185,\"model\":\"qwen/qwen3-30b-a3b\",\"provider\":\"DeepInfra\",\"choices\":[{\"index\":0,\"delta\":{\"content\":\"strument\",\"role\":\"assistant\"},\"finish_reason\":null,\"native_finish_reason\":null}]}", - "data: {\"id\":\"gen-1791215185-kD0GaG3sgjyoR5VOl5oJ\",\"object\":\"chat.completion.chunk\",\"created\":1791215185,\"model\":\"qwen/qwen3-30b-a3b\",\"provider\":\"DeepInfra\",\"choices\":[{\"index\":0,\"delta\":{\"content\":\"ation\",\"role\":\"assistant\"},\"finish_reason\":null,\"native_finish_reason\":null}]}", - "data: {\"id\":\"gen-1791215185-kD0GaG3sgjyoR5VOl5oJ\",\"object\":\"chat.completion.chunk\",\"created\":1791215185,\"model\":\"qwen/qwen3-30b-a3b\",\"provider\":\"DeepInfra\",\"choices\":[{\"index\":0,\"delta\":{\"content\":\" -\",\"role\":\"assistant\"},\"finish_reason\":null,\"native_finish_reason\":null}]}", - "data: {\"id\":\"gen-1791215185-kD0GaG3sgjyoR5VOl5oJ\",\"object\":\"chat.completion.chunk\",\"created\":1791215185,\"model\":\"qwen/qwen3-30b-a3b\",\"provider\":\"DeepInfra\",\"choices\":[{\"index\":0,\"delta\":{\"content\":\" \\\"\",\"role\":\"assistant\"},\"finish_reason\":null,\"native_finish_reason\":null}]}", - "data: {\"id\":\"gen-1791215185-kD0GaG3sgjyoR5VOl5oJ\",\"object\":\"chat.completion.chunk\",\"created\":1791215185,\"model\":\"qwen/qwen3-30b-a3b\",\"provider\":\"DeepInfra\",\"choices\":[{\"index\":0,\"delta\":{\"content\":\"E\",\"role\":\"assistant\"},\"finish_reason\":null,\"native_finish_reason\":null}]}", - "data: {\"id\":\"gen-1791215185-kD0GaG3sgjyoR5VOl5oJ\",\"object\":\"chat.completion.chunk\",\"created\":1791215185,\"model\":\"qwen/qwen3-30b-a3b\",\"provider\":\"DeepInfra\",\"choices\":[{\"index\":0,\"delta\":{\"content\":\"ve\",\"role\":\"assistant\"},\"finish_reason\":null,\"native_finish_reason\":null}]}", - "data: {\"id\":\"gen-1791215185-kD0GaG3sgjyoR5VOl5oJ\",\"object\":\"chat.completion.chunk\",\"created\":1791215185,\"model\":\"qwen/qwen3-30b-a3b\",\"provider\":\"DeepInfra\",\"choices\":[{\"index\":0,\"delta\":{\"content\":\" hooks\",\"role\":\"assistant\"},\"finish_reason\":null,\"native_finish_reason\":null}]}", - "data: {\"id\":\"gen-1791215185-kD0GaG3sgjyoR5VOl5oJ\",\"object\":\"chat.completion.chunk\",\"created\":1791215185,\"model\":\"qwen/qwen3-30b-a3b\",\"provider\":\"DeepInfra\",\"choices\":[{\"index\":0,\"delta\":{\"content\":\" expose\",\"role\":\"assistant\"},\"finish_reason\":null,\"native_finish_reason\":null}]}", - "data: {\"id\":\"gen-1791215185-kD0GaG3sgjyoR5VOl5oJ\",\"object\":\"chat.completion.chunk\",\"created\":1791215185,\"model\":\"qwen/qwen3-30b-a3b\",\"provider\":\"DeepInfra\",\"choices\":[{\"index\":0,\"delta\":{\"content\":\" runtime\",\"role\":\"assistant\"},\"finish_reason\":null,\"native_finish_reason\":null}]}", - "data: {\"id\":\"gen-1791215185-kD0GaG3sgjyoR5VOl5oJ\",\"object\":\"chat.completion.chunk\",\"created\":1791215185,\"model\":\"qwen/qwen3-30b-a3b\",\"provider\":\"DeepInfra\",\"choices\":[{\"index\":0,\"delta\":{\"content\":\" stream\",\"role\":\"assistant\"},\"finish_reason\":null,\"native_finish_reason\":null}]}", - "data: {\"id\":\"gen-1791215185-kD0GaG3sgjyoR5VOl5oJ\",\"object\":\"chat.completion.chunk\",\"created\":1791215185,\"model\":\"qwen/qwen3-30b-a3b\",\"provider\":\"DeepInfra\",\"choices\":[{\"index\":0,\"delta\":{\"content\":\" events\",\"role\":\"assistant\"},\"finish_reason\":null,\"native_finish_reason\":null}]}", - "data: {\"id\":\"gen-1791215185-kD0GaG3sgjyoR5VOl5oJ\",\"object\":\"chat.completion.chunk\",\"created\":1791215185,\"model\":\"qwen/qwen3-30b-a3b\",\"provider\":\"DeepInfra\",\"choices\":[{\"index\":0,\"delta\":{\"content\":\" that\",\"role\":\"assistant\"},\"finish_reason\":null,\"native_finish_reason\":null}]}", - "data: {\"id\":\"gen-1791215185-kD0GaG3sgjyoR5VOl5oJ\",\"object\":\"chat.completion.chunk\",\"created\":1791215185,\"model\":\"qwen/qwen3-30b-a3b\",\"provider\":\"DeepInfra\",\"choices\":[{\"index\":0,\"delta\":{\"content\":\" Brain\",\"role\":\"assistant\"},\"finish_reason\":null,\"native_finish_reason\":null}]}", - "data: {\"id\":\"gen-1791215185-kD0GaG3sgjyoR5VOl5oJ\",\"object\":\"chat.completion.chunk\",\"created\":1791215185,\"model\":\"qwen/qwen3-30b-a3b\",\"provider\":\"DeepInfra\",\"choices\":[{\"index\":0,\"delta\":{\"content\":\"trust\",\"role\":\"assistant\"},\"finish_reason\":null,\"native_finish_reason\":null}]}", - "data: {\"id\":\"gen-1791215185-kD0GaG3sgjyoR5VOl5oJ\",\"object\":\"chat.completion.chunk\",\"created\":1791215185,\"model\":\"qwen/qwen3-30b-a3b\",\"provider\":\"DeepInfra\",\"choices\":[{\"index\":0,\"delta\":{\"content\":\" maps\",\"role\":\"assistant\"},\"finish_reason\":null,\"native_finish_reason\":null}]}", - "data: {\"id\":\"gen-1791215185-kD0GaG3sgjyoR5VOl5oJ\",\"object\":\"chat.completion.chunk\",\"created\":1791215185,\"model\":\"qwen/qwen3-30b-a3b\",\"provider\":\"DeepInfra\",\"choices\":[{\"index\":0,\"delta\":{\"content\":\" into\",\"role\":\"assistant\"},\"finish_reason\":null,\"native_finish_reason\":null}]}", - "data: {\"id\":\"gen-1791215185-kD0GaG3sgjyoR5VOl5oJ\",\"object\":\"chat.completion.chunk\",\"created\":1791215185,\"model\":\"qwen/qwen3-30b-a3b\",\"provider\":\"DeepInfra\",\"choices\":[{\"index\":0,\"delta\":{\"content\":\" a\",\"role\":\"assistant\"},\"finish_reason\":null,\"native_finish_reason\":null}]}", - "data: {\"id\":\"gen-1791215185-kD0GaG3sgjyoR5VOl5oJ\",\"object\":\"chat.completion.chunk\",\"created\":1791215185,\"model\":\"qwen/qwen3-30b-a3b\",\"provider\":\"DeepInfra\",\"choices\":[{\"index\":0,\"delta\":{\"content\":\" nested\",\"role\":\"assistant\"},\"finish_reason\":null,\"native_finish_reason\":null}]}", - "data: {\"id\":\"gen-1791215185-kD0GaG3sgjyoR5VOl5oJ\",\"object\":\"chat.completion.chunk\",\"created\":1791215185,\"model\":\"qwen/qwen3-30b-a3b\",\"provider\":\"DeepInfra\",\"choices\":[{\"index\":0,\"delta\":{\"content\":\" turn\",\"role\":\"assistant\"},\"finish_reason\":null,\"native_finish_reason\":null}]}", - "data: {\"id\":\"gen-1791215185-kD0GaG3sgjyoR5VOl5oJ\",\"object\":\"chat.completion.chunk\",\"created\":1791215185,\"model\":\"qwen/qwen3-30b-a3b\",\"provider\":\"DeepInfra\",\"choices\":[{\"index\":0,\"delta\":{\"content\":\" trace\",\"role\":\"assistant\"},\"finish_reason\":null,\"native_finish_reason\":null}]}", - "data: {\"id\":\"gen-1791215185-kD0GaG3sgjyoR5VOl5oJ\",\"object\":\"chat.completion.chunk\",\"created\":1791215185,\"model\":\"qwen/qwen3-30b-a3b\",\"provider\":\"DeepInfra\",\"choices\":[{\"index\":0,\"delta\":{\"content\":\".\\\"\",\"role\":\"assistant\"},\"finish_reason\":null,\"native_finish_reason\":null}]}", - "data: {\"id\":\"gen-1791215185-kD0GaG3sgjyoR5VOl5oJ\",\"object\":\"chat.completion.chunk\",\"created\":1791215185,\"model\":\"qwen/qwen3-30b-a3b\",\"provider\":\"DeepInfra\",\"choices\":[{\"index\":0,\"delta\":{\"content\":\" (\",\"role\":\"assistant\"},\"finish_reason\":null,\"native_finish_reason\":null}]}", - "data: {\"id\":\"gen-1791215185-kD0GaG3sgjyoR5VOl5oJ\",\"object\":\"chat.completion.chunk\",\"created\":1791215185,\"model\":\"qwen/qwen3-30b-a3b\",\"provider\":\"DeepInfra\",\"choices\":[{\"index\":0,\"delta\":{\"content\":\"E\",\"role\":\"assistant\"},\"finish_reason\":null,\"native_finish_reason\":null}]}", - "data: {\"id\":\"gen-1791215185-kD0GaG3sgjyoR5VOl5oJ\",\"object\":\"chat.completion.chunk\",\"created\":1791215185,\"model\":\"qwen/qwen3-30b-a3b\",\"provider\":\"DeepInfra\",\"choices\":[{\"index\":0,\"delta\":{\"content\":\"ve\",\"role\":\"assistant\"},\"finish_reason\":null,\"native_finish_reason\":null}]}", - "data: {\"id\":\"gen-1791215185-kD0GaG3sgjyoR5VOl5oJ\",\"object\":\"chat.completion.chunk\",\"created\":1791215185,\"model\":\"qwen/qwen3-30b-a3b\",\"provider\":\"DeepInfra\",\"choices\":[{\"index\":0,\"delta\":{\"content\":\" instrumentation\",\"role\":\"assistant\"},\"finish_reason\":null,\"native_finish_reason\":null}]}", - "data: {\"id\":\"gen-1791215185-kD0GaG3sgjyoR5VOl5oJ\",\"object\":\"chat.completion.chunk\",\"created\":1791215185,\"model\":\"qwen/qwen3-30b-a3b\",\"provider\":\"DeepInfra\",\"choices\":[{\"index\":0,\"delta\":{\"content\":\",\",\"role\":\"assistant\"},\"finish_reason\":null,\"native_finish_reason\":null}]}", - "data: {\"id\":\"gen-1791215185-kD0GaG3sgjyoR5VOl5oJ\",\"object\":\"chat.completion.chunk\",\"created\":1791215185,\"model\":\"qwen/qwen3-30b-a3b\",\"provider\":\"DeepInfra\",\"choices\":[{\"index\":0,\"delta\":{\"content\":\" https\",\"role\":\"assistant\"},\"finish_reason\":null,\"native_finish_reason\":null}]}", - "data: {\"id\":\"gen-1791215185-kD0GaG3sgjyoR5VOl5oJ\",\"object\":\"chat.completion.chunk\",\"created\":1791215185,\"model\":\"qwen/qwen3-30b-a3b\",\"provider\":\"DeepInfra\",\"choices\":[{\"index\":0,\"delta\":{\"content\":\"://\",\"role\":\"assistant\"},\"finish_reason\":null,\"native_finish_reason\":null}]}", - "data: {\"id\":\"gen-1791215185-kD0GaG3sgjyoR5VOl5oJ\",\"object\":\"chat.completion.chunk\",\"created\":1791215185,\"model\":\"qwen/qwen3-30b-a3b\",\"provider\":\"DeepInfra\",\"choices\":[{\"index\":0,\"delta\":{\"content\":\"eve\",\"role\":\"assistant\"},\"finish_reason\":null,\"native_finish_reason\":null}]}", - "data: {\"id\":\"gen-1791215185-kD0GaG3sgjyoR5VOl5oJ\",\"object\":\"chat.completion.chunk\",\"created\":1791215185,\"model\":\"qwen/qwen3-30b-a3b\",\"provider\":\"DeepInfra\",\"choices\":[{\"index\":0,\"delta\":{\"content\":\".dev\",\"role\":\"assistant\"},\"finish_reason\":null,\"native_finish_reason\":null}]}", - "data: {\"id\":\"gen-1791215185-kD0GaG3sgjyoR5VOl5oJ\",\"object\":\"chat.completion.chunk\",\"created\":1791215185,\"model\":\"qwen/qwen3-30b-a3b\",\"provider\":\"DeepInfra\",\"choices\":[{\"index\":0,\"delta\":{\"content\":\"/docs\",\"role\":\"assistant\"},\"finish_reason\":null,\"native_finish_reason\":null}]}", - "data: {\"id\":\"gen-1791215185-kD0GaG3sgjyoR5VOl5oJ\",\"object\":\"chat.completion.chunk\",\"created\":1791215185,\"model\":\"qwen/qwen3-30b-a3b\",\"provider\":\"DeepInfra\",\"choices\":[{\"index\":0,\"delta\":{\"content\":\"/g\",\"role\":\"assistant\"},\"finish_reason\":null,\"native_finish_reason\":null}]}", - "data: {\"id\":\"gen-1791215185-kD0GaG3sgjyoR5VOl5oJ\",\"object\":\"chat.completion.chunk\",\"created\":1791215185,\"model\":\"qwen/qwen3-30b-a3b\",\"provider\":\"DeepInfra\",\"choices\":[{\"index\":0,\"delta\":{\"content\":\"uid\",\"role\":\"assistant\"},\"finish_reason\":null,\"native_finish_reason\":null}]}", - "data: {\"id\":\"gen-1791215185-kD0GaG3sgjyoR5VOl5oJ\",\"object\":\"chat.completion.chunk\",\"created\":1791215185,\"model\":\"qwen/qwen3-30b-a3b\",\"provider\":\"DeepInfra\",\"choices\":[{\"index\":0,\"delta\":{\"content\":\"es\",\"role\":\"assistant\"},\"finish_reason\":null,\"native_finish_reason\":null}]}", - "data: {\"id\":\"gen-1791215185-kD0GaG3sgjyoR5VOl5oJ\",\"object\":\"chat.completion.chunk\",\"created\":1791215185,\"model\":\"qwen/qwen3-30b-a3b\",\"provider\":\"DeepInfra\",\"choices\":[{\"index\":0,\"delta\":{\"content\":\"/in\",\"role\":\"assistant\"},\"finish_reason\":null,\"native_finish_reason\":null}]}", - "data: {\"id\":\"gen-1791215185-kD0GaG3sgjyoR5VOl5oJ\",\"object\":\"chat.completion.chunk\",\"created\":1791215185,\"model\":\"qwen/qwen3-30b-a3b\",\"provider\":\"DeepInfra\",\"choices\":[{\"index\":0,\"delta\":{\"content\":\"strument\",\"role\":\"assistant\"},\"finish_reason\":null,\"native_finish_reason\":null}]}", - "data: {\"id\":\"gen-1791215185-kD0GaG3sgjyoR5VOl5oJ\",\"object\":\"chat.completion.chunk\",\"created\":1791215185,\"model\":\"qwen/qwen3-30b-a3b\",\"provider\":\"DeepInfra\",\"choices\":[{\"index\":0,\"delta\":{\"content\":\"ation\",\"role\":\"assistant\"},\"finish_reason\":null,\"native_finish_reason\":null}]}", - "data: {\"id\":\"gen-1791215185-kD0GaG3sgjyoR5VOl5oJ\",\"object\":\"chat.completion.chunk\",\"created\":1791215185,\"model\":\"qwen/qwen3-30b-a3b\",\"provider\":\"DeepInfra\",\"choices\":[{\"index\":0,\"delta\":{\"content\":\")\",\"role\":\"assistant\"},\"finish_reason\":null,\"native_finish_reason\":null}]}", - "data: {\"id\":\"gen-1791215185-kD0GaG3sgjyoR5VOl5oJ\",\"object\":\"chat.completion.chunk\",\"created\":1791215185,\"model\":\"qwen/qwen3-30b-a3b\",\"provider\":\"DeepInfra\",\"choices\":[{\"index\":0,\"delta\":{\"content\":\"\",\"role\":\"assistant\",\"reasoning\":null},\"finish_reason\":\"stop\",\"native_finish_reason\":\"stop\"}]}", - "data: {\"id\":\"gen-1791215185-kD0GaG3sgjyoR5VOl5oJ\",\"object\":\"chat.completion.chunk\",\"created\":1791215185,\"model\":\"qwen/qwen3-30b-a3b\",\"provider\":\"DeepInfra\",\"service_tier\":\"default\",\"choices\":[{\"index\":0,\"delta\":{\"content\":\"\",\"role\":\"assistant\"},\"finish_reason\":\"stop\",\"native_finish_reason\":\"stop\"}],\"usage\":{\"prompt_tokens\":3982,\"completion_tokens\":177,\"total_tokens\":4159,\"cost\":0.00056634,\"is_byok\":false,\"prompt_tokens_details\":{\"cached_tokens\":0,\"cache_write_tokens\":0,\"audio_tokens\":0,\"video_tokens\":0},\"cost_details\":{\"upstream_inference_cost\":0.00056634,\"upstream_inference_prompt_cost\":0.00047784,\"upstream_inference_completions_cost\":0.0000885},\"completion_tokens_details\":{\"reasoning_tokens\":138,\"image_tokens\":0,\"audio_tokens\":0}}}", - "data: [DONE]" - ], - "kind": "sse" - }, - "headers": { - "access-control-allow-origin": "*", - "access-control-expose-headers": "X-Generation-Id,X-Provider-Name,request-id,cf-ray", - "cache-control": "no-cache", - "cf-ray": "a45d8f1c0eb319cf-YYZ", - "content-type": "text/event-stream", - "date": "Mon, 05 Oct 2026 15:46:25 GMT", - "permissions-policy": "payment=(self \"https://checkout.stripe.com\" \"https://connect-js.stripe.com\" \"https://js.stripe.com\" \"https://*.js.stripe.com\" \"https://hooks.stripe.com\")", - "referrer-policy": "no-referrer, strict-origin-when-cross-origin", - "server": "cloudflare", - "set-cookie": "[REDACTED]", - "x-content-type-options": "nosniff", - "x-generation-id": "gen-1791215185-kD0GaG3sgjyoR5VOl5oJ" - }, - "status": 200 - } - } - ], - "meta": { - "createdAt": "2026-07-29T12:33:36.152Z" - } -} diff --git a/e2e/scenarios/eve-instrumentation/__snapshots__/eve-v0-70.span-tree.json b/e2e/scenarios/eve-instrumentation/__snapshots__/eve-v0-70.span-tree.json index 0c81ca20d..a4fb1f511 100644 --- a/e2e/scenarios/eve-instrumentation/__snapshots__/eve-v0-70.span-tree.json +++ b/e2e/scenarios/eve-instrumentation/__snapshots__/eve-v0-70.span-tree.json @@ -151,7 +151,6 @@ "url": "https://eve.dev/docs/guides/instrumentation" }, "metadata": { - "eve.action_kind": "tool-call", "eve.session_id": "", "scenario": "eve-instrumentation", "testRunId": "" @@ -259,6 +258,13 @@ "scenario": "eve-instrumentation", "testRunId": "" }, + "metrics": { + "completion_tokens": 337, + "prompt_cache_creation_tokens": 0, + "prompt_cached_tokens": 0, + "prompt_tokens": 2980, + "tokens": 3317 + }, "context": { "caller_filename": "", "caller_functionname": "", @@ -271,7 +277,6 @@ }, "output": "Started task researcher-. Its result will arrive in a message. To send it another message, call researcher again with taskId researcher-.", "metadata": { - "eve.action_kind": "tool-call", "eve.session_id": "", "scenario": "eve-instrumentation", "testRunId": "" @@ -296,7 +301,6 @@ "url": "https://eve.dev/docs/guides/instrumentation" }, "metadata": { - "eve.action_kind": "tool-call", "eve.session_id": "", "scenario": "eve-instrumentation", "testRunId": "" @@ -429,7 +433,6 @@ "children": [], "output": "researcher- completed; its result follows.", "metadata": { - "eve.action_kind": "tool-call", "eve.session_id": "", "scenario": "eve-instrumentation", "testRunId": "" @@ -596,6 +599,13 @@ "scenario": "eve-instrumentation", "testRunId": "" }, + "metrics": { + "completion_tokens": 1227, + "prompt_cache_creation_tokens": 0, + "prompt_cached_tokens": 0, + "prompt_tokens": 8323, + "tokens": 9550 + }, "context": { "caller_filename": "", "caller_functionname": "", @@ -864,7 +874,6 @@ "url": "https://eve.dev/docs/guides/instrumentation" }, "metadata": { - "eve.action_kind": "tool-call", "eve.session_id": "", "scenario": "eve-instrumentation", "testRunId": "" @@ -972,6 +981,13 @@ "scenario": "eve-instrumentation", "testRunId": "" }, + "metrics": { + "completion_tokens": 347, + "prompt_cache_creation_tokens": 0, + "prompt_cached_tokens": 0, + "prompt_tokens": 2996, + "tokens": 3343 + }, "context": { "caller_filename": "", "caller_functionname": "", @@ -984,7 +1000,6 @@ }, "output": "Started task researcher-. Its result will arrive in a message. To send it another message, call researcher again with taskId researcher-.", "metadata": { - "eve.action_kind": "tool-call", "eve.session_id": "", "scenario": "eve-instrumentation", "testRunId": "" @@ -1009,7 +1024,6 @@ "url": "https://eve.dev/docs/guides/instrumentation" }, "metadata": { - "eve.action_kind": "tool-call", "eve.session_id": "", "scenario": "eve-instrumentation", "testRunId": "" @@ -1253,7 +1267,6 @@ "children": [], "output": "researcher- completed; its result follows.", "metadata": { - "eve.action_kind": "tool-call", "eve.session_id": "", "scenario": "eve-instrumentation", "testRunId": "" @@ -1531,6 +1544,13 @@ "scenario": "eve-instrumentation", "testRunId": "" }, + "metrics": { + "completion_tokens": 1020, + "prompt_cache_creation_tokens": 0, + "prompt_cached_tokens": 0, + "prompt_tokens": 9594, + "tokens": 10614 + }, "context": { "caller_filename": "", "caller_functionname": "", @@ -1692,7 +1712,6 @@ "url": "https://eve.dev/docs/guides/instrumentation" }, "metadata": { - "eve.action_kind": "tool-call", "eve.session_id": "", "scenario": "eve-instrumentation", "testRunId": "" @@ -1800,6 +1819,13 @@ "scenario": "eve-instrumentation", "testRunId": "" }, + "metrics": { + "completion_tokens": 298, + "prompt_cache_creation_tokens": 0, + "prompt_cached_tokens": 0, + "prompt_tokens": 2980, + "tokens": 3278 + }, "context": { "caller_filename": "", "caller_functionname": "", @@ -1812,7 +1838,6 @@ }, "output": "Started task researcher-. Its result will arrive in a message. To send it another message, call researcher again with taskId researcher-.", "metadata": { - "eve.action_kind": "tool-call", "eve.session_id": "", "scenario": "eve-instrumentation", "testRunId": "" @@ -1837,7 +1862,6 @@ "url": "https://eve.dev/docs/guides/instrumentation" }, "metadata": { - "eve.action_kind": "tool-call", "eve.session_id": "", "scenario": "eve-instrumentation", "testRunId": "" @@ -1970,7 +1994,6 @@ "children": [], "output": "researcher- completed; its result follows.", "metadata": { - "eve.action_kind": "tool-call", "eve.session_id": "", "scenario": "eve-instrumentation", "testRunId": "" @@ -2137,6 +2160,13 @@ "scenario": "eve-instrumentation", "testRunId": "" }, + "metrics": { + "completion_tokens": 1579, + "prompt_cache_creation_tokens": 0, + "prompt_cached_tokens": 0, + "prompt_tokens": 8247, + "tokens": 9826 + }, "context": { "caller_filename": "", "caller_functionname": "", diff --git a/e2e/scenarios/eve-instrumentation/__snapshots__/eve-v0-70.span-tree.txt b/e2e/scenarios/eve-instrumentation/__snapshots__/eve-v0-70.span-tree.txt index e58992d22..a1c29915c 100644 --- a/e2e/scenarios/eve-instrumentation/__snapshots__/eve-v0-70.span-tree.txt +++ b/e2e/scenarios/eve-instrumentation/__snapshots__/eve-v0-70.span-tree.txt @@ -12,6 +12,13 @@ span_tree: │ "scenario": "eve-instrumentation", │ "testRunId": "" │ } +│ metrics: { +│ "completion_tokens": 1227, +│ "prompt_cache_creation_tokens": 0, +│ "prompt_cached_tokens": 0, +│ "prompt_tokens": 8323, +│ "tokens": 9550 +│ } │ context: { │ "caller_filename": "", │ "caller_functionname": "", @@ -86,7 +93,6 @@ span_tree: │ │ } │ │ output: "Started task researcher-. Its result will arrive in a message. To send it another message, call researcher again with taskId researcher-." │ │ metadata: { -│ │ "eve.action_kind": "tool-call", │ │ "eve.session_id": "", │ │ "scenario": "eve-instrumentation", │ │ "testRunId": "" @@ -109,6 +115,13 @@ span_tree: │ │ "scenario": "eve-instrumentation", │ │ "testRunId": "" │ │ } +│ │ metrics: { +│ │ "completion_tokens": 337, +│ │ "prompt_cache_creation_tokens": 0, +│ │ "prompt_cached_tokens": 0, +│ │ "prompt_tokens": 2980, +│ │ "tokens": 3317 +│ │ } │ │ context: { │ │ "caller_filename": "", │ │ "caller_functionname": "", @@ -179,7 +192,6 @@ span_tree: │ │ │ "url": "https://eve.dev/docs/guides/instrumentation" │ │ │ } │ │ │ metadata: { -│ │ │ "eve.action_kind": "tool-call", │ │ │ "eve.session_id": "", │ │ │ "scenario": "eve-instrumentation", │ │ │ "testRunId": "" @@ -280,7 +292,6 @@ span_tree: │ │ "url": "https://eve.dev/docs/guides/instrumentation" │ │ } │ │ metadata: { -│ │ "eve.action_kind": "tool-call", │ │ "eve.session_id": "", │ │ "scenario": "eve-instrumentation", │ │ "testRunId": "" @@ -405,7 +416,6 @@ span_tree: │ ├── task_wait [tool] │ │ output: "researcher- completed; its result follows." │ │ metadata: { -│ │ "eve.action_kind": "tool-call", │ │ "eve.session_id": "", │ │ "scenario": "eve-instrumentation", │ │ "testRunId": "" @@ -567,6 +577,13 @@ span_tree: │ "scenario": "eve-instrumentation", │ "testRunId": "" │ } +│ metrics: { +│ "completion_tokens": 1020, +│ "prompt_cache_creation_tokens": 0, +│ "prompt_cached_tokens": 0, +│ "prompt_tokens": 9594, +│ "tokens": 10614 +│ } │ context: { │ "caller_filename": "", │ "caller_functionname": "", @@ -752,7 +769,6 @@ span_tree: │ │ } │ │ output: "Started task researcher-. Its result will arrive in a message. To send it another message, call researcher again with taskId researcher-." │ │ metadata: { -│ │ "eve.action_kind": "tool-call", │ │ "eve.session_id": "", │ │ "scenario": "eve-instrumentation", │ │ "testRunId": "" @@ -775,6 +791,13 @@ span_tree: │ │ "scenario": "eve-instrumentation", │ │ "testRunId": "" │ │ } +│ │ metrics: { +│ │ "completion_tokens": 347, +│ │ "prompt_cache_creation_tokens": 0, +│ │ "prompt_cached_tokens": 0, +│ │ "prompt_tokens": 2996, +│ │ "tokens": 3343 +│ │ } │ │ context: { │ │ "caller_filename": "", │ │ "caller_functionname": "", @@ -845,7 +868,6 @@ span_tree: │ │ │ "url": "https://eve.dev/docs/guides/instrumentation" │ │ │ } │ │ │ metadata: { -│ │ │ "eve.action_kind": "tool-call", │ │ │ "eve.session_id": "", │ │ │ "scenario": "eve-instrumentation", │ │ │ "testRunId": "" @@ -946,7 +968,6 @@ span_tree: │ │ "url": "https://eve.dev/docs/guides/instrumentation" │ │ } │ │ metadata: { -│ │ "eve.action_kind": "tool-call", │ │ "eve.session_id": "", │ │ "scenario": "eve-instrumentation", │ │ "testRunId": "" @@ -1182,7 +1203,6 @@ span_tree: │ ├── task_wait [tool] │ │ output: "researcher- completed; its result follows." │ │ metadata: { -│ │ "eve.action_kind": "tool-call", │ │ "eve.session_id": "", │ │ "scenario": "eve-instrumentation", │ │ "testRunId": "" @@ -1465,6 +1485,13 @@ span_tree: "scenario": "eve-instrumentation", "testRunId": "" } + metrics: { + "completion_tokens": 1579, + "prompt_cache_creation_tokens": 0, + "prompt_cached_tokens": 0, + "prompt_tokens": 8247, + "tokens": 9826 + } context: { "caller_filename": "", "caller_functionname": "", @@ -1539,7 +1566,6 @@ span_tree: │ } │ output: "Started task researcher-. Its result will arrive in a message. To send it another message, call researcher again with taskId researcher-." │ metadata: { - │ "eve.action_kind": "tool-call", │ "eve.session_id": "", │ "scenario": "eve-instrumentation", │ "testRunId": "" @@ -1562,6 +1588,13 @@ span_tree: │ "scenario": "eve-instrumentation", │ "testRunId": "" │ } + │ metrics: { + │ "completion_tokens": 298, + │ "prompt_cache_creation_tokens": 0, + │ "prompt_cached_tokens": 0, + │ "prompt_tokens": 2980, + │ "tokens": 3278 + │ } │ context: { │ "caller_filename": "", │ "caller_functionname": "", @@ -1632,7 +1665,6 @@ span_tree: │ │ "url": "https://eve.dev/docs/guides/instrumentation" │ │ } │ │ metadata: { - │ │ "eve.action_kind": "tool-call", │ │ "eve.session_id": "", │ │ "scenario": "eve-instrumentation", │ │ "testRunId": "" @@ -1733,7 +1765,6 @@ span_tree: │ "url": "https://eve.dev/docs/guides/instrumentation" │ } │ metadata: { - │ "eve.action_kind": "tool-call", │ "eve.session_id": "", │ "scenario": "eve-instrumentation", │ "testRunId": "" @@ -1858,7 +1889,6 @@ span_tree: ├── task_wait [tool] │ output: "researcher- completed; its result follows." │ metadata: { - │ "eve.action_kind": "tool-call", │ "eve.session_id": "", │ "scenario": "eve-instrumentation", │ "testRunId": "" diff --git a/e2e/scenarios/eve-instrumentation/__snapshots__/eve-v0-72.span-tree.json b/e2e/scenarios/eve-instrumentation/__snapshots__/eve-v0-72.span-tree.json index 6baa9bb48..53fedb496 100644 --- a/e2e/scenarios/eve-instrumentation/__snapshots__/eve-v0-72.span-tree.json +++ b/e2e/scenarios/eve-instrumentation/__snapshots__/eve-v0-72.span-tree.json @@ -151,7 +151,6 @@ "url": "https://eve.dev/docs/guides/instrumentation" }, "metadata": { - "eve.action_kind": "tool-call", "eve.session_id": "", "scenario": "eve-instrumentation", "testRunId": "" @@ -259,6 +258,13 @@ "scenario": "eve-instrumentation", "testRunId": "" }, + "metrics": { + "completion_tokens": 307, + "prompt_cache_creation_tokens": 0, + "prompt_cached_tokens": 0, + "prompt_tokens": 3106, + "tokens": 3413 + }, "context": { "caller_filename": "", "caller_functionname": "", @@ -271,7 +277,6 @@ }, "output": "Started task researcher-. Its result will arrive in a message. To send it another message, call researcher again with taskId researcher-.", "metadata": { - "eve.action_kind": "subagent-call", "eve.session_id": "", "scenario": "eve-instrumentation", "testRunId": "" @@ -296,7 +301,6 @@ "url": "https://eve.dev/docs/guides/instrumentation" }, "metadata": { - "eve.action_kind": "tool-call", "eve.session_id": "", "scenario": "eve-instrumentation", "testRunId": "" @@ -429,7 +433,6 @@ "children": [], "output": "researcher- completed; its result follows.", "metadata": { - "eve.action_kind": "tool-call", "eve.session_id": "", "scenario": "eve-instrumentation", "testRunId": "" @@ -596,6 +599,13 @@ "scenario": "eve-instrumentation", "testRunId": "" }, + "metrics": { + "completion_tokens": 1363, + "prompt_cache_creation_tokens": 0, + "prompt_cached_tokens": 0, + "prompt_tokens": 8536, + "tokens": 9899 + }, "context": { "caller_filename": "", "caller_functionname": "", @@ -864,7 +874,6 @@ "url": "https://eve.dev/docs/guides/instrumentation" }, "metadata": { - "eve.action_kind": "tool-call", "eve.session_id": "", "scenario": "eve-instrumentation", "testRunId": "" @@ -972,6 +981,13 @@ "scenario": "eve-instrumentation", "testRunId": "" }, + "metrics": { + "completion_tokens": 350, + "prompt_cache_creation_tokens": 0, + "prompt_cached_tokens": 0, + "prompt_tokens": 3135, + "tokens": 3485 + }, "context": { "caller_filename": "", "caller_functionname": "", @@ -984,7 +1000,6 @@ }, "output": "Started task researcher-. Its result will arrive in a message. To send it another message, call researcher again with taskId researcher-.", "metadata": { - "eve.action_kind": "subagent-call", "eve.session_id": "", "scenario": "eve-instrumentation", "testRunId": "" @@ -1009,7 +1024,6 @@ "url": "https://eve.dev/docs/guides/instrumentation" }, "metadata": { - "eve.action_kind": "tool-call", "eve.session_id": "", "scenario": "eve-instrumentation", "testRunId": "" @@ -1253,7 +1267,6 @@ "children": [], "output": "researcher- completed; its result follows.", "metadata": { - "eve.action_kind": "tool-call", "eve.session_id": "", "scenario": "eve-instrumentation", "testRunId": "" @@ -1531,6 +1544,13 @@ "scenario": "eve-instrumentation", "testRunId": "" }, + "metrics": { + "completion_tokens": 927, + "prompt_cache_creation_tokens": 0, + "prompt_cached_tokens": 0, + "prompt_tokens": 9798, + "tokens": 10725 + }, "context": { "caller_filename": "", "caller_functionname": "", @@ -1692,7 +1712,6 @@ "url": "https://eve.dev/docs/guides/instrumentation" }, "metadata": { - "eve.action_kind": "tool-call", "eve.session_id": "", "scenario": "eve-instrumentation", "testRunId": "" @@ -1800,6 +1819,13 @@ "scenario": "eve-instrumentation", "testRunId": "" }, + "metrics": { + "completion_tokens": 350, + "prompt_cache_creation_tokens": 0, + "prompt_cached_tokens": 0, + "prompt_tokens": 3153, + "tokens": 3503 + }, "context": { "caller_filename": "", "caller_functionname": "", @@ -1812,7 +1838,6 @@ }, "output": "Started task researcher-. Its result will arrive in a message. To send it another message, call researcher again with taskId researcher-.", "metadata": { - "eve.action_kind": "subagent-call", "eve.session_id": "", "scenario": "eve-instrumentation", "testRunId": "" @@ -1837,7 +1862,6 @@ "url": "https://eve.dev/docs/guides/instrumentation" }, "metadata": { - "eve.action_kind": "tool-call", "eve.session_id": "", "scenario": "eve-instrumentation", "testRunId": "" @@ -1970,7 +1994,6 @@ "children": [], "output": "researcher- completed; its result follows.", "metadata": { - "eve.action_kind": "tool-call", "eve.session_id": "", "scenario": "eve-instrumentation", "testRunId": "" @@ -2137,6 +2160,13 @@ "scenario": "eve-instrumentation", "testRunId": "" }, + "metrics": { + "completion_tokens": 1179, + "prompt_cache_creation_tokens": 0, + "prompt_cached_tokens": 0, + "prompt_tokens": 8451, + "tokens": 9630 + }, "context": { "caller_filename": "", "caller_functionname": "", diff --git a/e2e/scenarios/eve-instrumentation/__snapshots__/eve-v0-72.span-tree.txt b/e2e/scenarios/eve-instrumentation/__snapshots__/eve-v0-72.span-tree.txt index 13b01017f..5b50349e1 100644 --- a/e2e/scenarios/eve-instrumentation/__snapshots__/eve-v0-72.span-tree.txt +++ b/e2e/scenarios/eve-instrumentation/__snapshots__/eve-v0-72.span-tree.txt @@ -12,6 +12,13 @@ span_tree: │ "scenario": "eve-instrumentation", │ "testRunId": "" │ } +│ metrics: { +│ "completion_tokens": 1363, +│ "prompt_cache_creation_tokens": 0, +│ "prompt_cached_tokens": 0, +│ "prompt_tokens": 8536, +│ "tokens": 9899 +│ } │ context: { │ "caller_filename": "", │ "caller_functionname": "", @@ -86,7 +93,6 @@ span_tree: │ │ } │ │ output: "Started task researcher-. Its result will arrive in a message. To send it another message, call researcher again with taskId researcher-." │ │ metadata: { -│ │ "eve.action_kind": "subagent-call", │ │ "eve.session_id": "", │ │ "scenario": "eve-instrumentation", │ │ "testRunId": "" @@ -109,6 +115,13 @@ span_tree: │ │ "scenario": "eve-instrumentation", │ │ "testRunId": "" │ │ } +│ │ metrics: { +│ │ "completion_tokens": 307, +│ │ "prompt_cache_creation_tokens": 0, +│ │ "prompt_cached_tokens": 0, +│ │ "prompt_tokens": 3106, +│ │ "tokens": 3413 +│ │ } │ │ context: { │ │ "caller_filename": "", │ │ "caller_functionname": "", @@ -179,7 +192,6 @@ span_tree: │ │ │ "url": "https://eve.dev/docs/guides/instrumentation" │ │ │ } │ │ │ metadata: { -│ │ │ "eve.action_kind": "tool-call", │ │ │ "eve.session_id": "", │ │ │ "scenario": "eve-instrumentation", │ │ │ "testRunId": "" @@ -280,7 +292,6 @@ span_tree: │ │ "url": "https://eve.dev/docs/guides/instrumentation" │ │ } │ │ metadata: { -│ │ "eve.action_kind": "tool-call", │ │ "eve.session_id": "", │ │ "scenario": "eve-instrumentation", │ │ "testRunId": "" @@ -405,7 +416,6 @@ span_tree: │ ├── task_wait [tool] │ │ output: "researcher- completed; its result follows." │ │ metadata: { -│ │ "eve.action_kind": "tool-call", │ │ "eve.session_id": "", │ │ "scenario": "eve-instrumentation", │ │ "testRunId": "" @@ -567,6 +577,13 @@ span_tree: │ "scenario": "eve-instrumentation", │ "testRunId": "" │ } +│ metrics: { +│ "completion_tokens": 927, +│ "prompt_cache_creation_tokens": 0, +│ "prompt_cached_tokens": 0, +│ "prompt_tokens": 9798, +│ "tokens": 10725 +│ } │ context: { │ "caller_filename": "", │ "caller_functionname": "", @@ -752,7 +769,6 @@ span_tree: │ │ } │ │ output: "Started task researcher-. Its result will arrive in a message. To send it another message, call researcher again with taskId researcher-." │ │ metadata: { -│ │ "eve.action_kind": "subagent-call", │ │ "eve.session_id": "", │ │ "scenario": "eve-instrumentation", │ │ "testRunId": "" @@ -775,6 +791,13 @@ span_tree: │ │ "scenario": "eve-instrumentation", │ │ "testRunId": "" │ │ } +│ │ metrics: { +│ │ "completion_tokens": 350, +│ │ "prompt_cache_creation_tokens": 0, +│ │ "prompt_cached_tokens": 0, +│ │ "prompt_tokens": 3135, +│ │ "tokens": 3485 +│ │ } │ │ context: { │ │ "caller_filename": "", │ │ "caller_functionname": "", @@ -845,7 +868,6 @@ span_tree: │ │ │ "url": "https://eve.dev/docs/guides/instrumentation" │ │ │ } │ │ │ metadata: { -│ │ │ "eve.action_kind": "tool-call", │ │ │ "eve.session_id": "", │ │ │ "scenario": "eve-instrumentation", │ │ │ "testRunId": "" @@ -946,7 +968,6 @@ span_tree: │ │ "url": "https://eve.dev/docs/guides/instrumentation" │ │ } │ │ metadata: { -│ │ "eve.action_kind": "tool-call", │ │ "eve.session_id": "", │ │ "scenario": "eve-instrumentation", │ │ "testRunId": "" @@ -1182,7 +1203,6 @@ span_tree: │ ├── task_wait [tool] │ │ output: "researcher- completed; its result follows." │ │ metadata: { -│ │ "eve.action_kind": "tool-call", │ │ "eve.session_id": "", │ │ "scenario": "eve-instrumentation", │ │ "testRunId": "" @@ -1465,6 +1485,13 @@ span_tree: "scenario": "eve-instrumentation", "testRunId": "" } + metrics: { + "completion_tokens": 1179, + "prompt_cache_creation_tokens": 0, + "prompt_cached_tokens": 0, + "prompt_tokens": 8451, + "tokens": 9630 + } context: { "caller_filename": "", "caller_functionname": "", @@ -1539,7 +1566,6 @@ span_tree: │ } │ output: "Started task researcher-. Its result will arrive in a message. To send it another message, call researcher again with taskId researcher-." │ metadata: { - │ "eve.action_kind": "subagent-call", │ "eve.session_id": "", │ "scenario": "eve-instrumentation", │ "testRunId": "" @@ -1562,6 +1588,13 @@ span_tree: │ "scenario": "eve-instrumentation", │ "testRunId": "" │ } + │ metrics: { + │ "completion_tokens": 350, + │ "prompt_cache_creation_tokens": 0, + │ "prompt_cached_tokens": 0, + │ "prompt_tokens": 3153, + │ "tokens": 3503 + │ } │ context: { │ "caller_filename": "", │ "caller_functionname": "", @@ -1632,7 +1665,6 @@ span_tree: │ │ "url": "https://eve.dev/docs/guides/instrumentation" │ │ } │ │ metadata: { - │ │ "eve.action_kind": "tool-call", │ │ "eve.session_id": "", │ │ "scenario": "eve-instrumentation", │ │ "testRunId": "" @@ -1733,7 +1765,6 @@ span_tree: │ "url": "https://eve.dev/docs/guides/instrumentation" │ } │ metadata: { - │ "eve.action_kind": "tool-call", │ "eve.session_id": "", │ "scenario": "eve-instrumentation", │ "testRunId": "" @@ -1858,7 +1889,6 @@ span_tree: ├── task_wait [tool] │ output: "researcher- completed; its result follows." │ metadata: { - │ "eve.action_kind": "tool-call", │ "eve.session_id": "", │ "scenario": "eve-instrumentation", │ "testRunId": "" diff --git a/e2e/scenarios/eve-instrumentation/__snapshots__/eve-v0-latest-pinned.span-tree.json b/e2e/scenarios/eve-instrumentation/__snapshots__/eve-v0-latest-pinned.span-tree.json index 2c4401092..01f1cedce 100644 --- a/e2e/scenarios/eve-instrumentation/__snapshots__/eve-v0-latest-pinned.span-tree.json +++ b/e2e/scenarios/eve-instrumentation/__snapshots__/eve-v0-latest-pinned.span-tree.json @@ -151,7 +151,6 @@ "url": "https://eve.dev/docs/guides/instrumentation" }, "metadata": { - "eve.action_kind": "tool-call", "eve.session_id": "", "scenario": "eve-instrumentation", "testRunId": "" @@ -259,6 +258,13 @@ "scenario": "eve-instrumentation", "testRunId": "" }, + "metrics": { + "completion_tokens": 379, + "prompt_cache_creation_tokens": 0, + "prompt_cached_tokens": 0, + "prompt_tokens": 6418, + "tokens": 6797 + }, "context": { "caller_filename": "", "caller_functionname": "", @@ -271,7 +277,6 @@ }, "output": "Researcher result: Eve instrumentation https://eve.dev/docs/guides/instrumentation", "metadata": { - "eve.action_kind": "subagent-call", "eve.session_id": "", "scenario": "eve-instrumentation", "testRunId": "" @@ -296,7 +301,6 @@ "url": "https://eve.dev/docs/guides/instrumentation" }, "metadata": { - "eve.action_kind": "tool-call", "eve.session_id": "", "scenario": "eve-instrumentation", "testRunId": "" @@ -427,6 +431,13 @@ "scenario": "eve-instrumentation", "testRunId": "" }, + "metrics": { + "completion_tokens": 697, + "prompt_cache_creation_tokens": 0, + "prompt_cached_tokens": 0, + "prompt_tokens": 7946, + "tokens": 8643 + }, "context": { "caller_filename": "", "caller_functionname": "", @@ -659,7 +670,6 @@ "url": "https://eve.dev/docs/guides/instrumentation" }, "metadata": { - "eve.action_kind": "tool-call", "eve.session_id": "", "scenario": "eve-instrumentation", "testRunId": "" @@ -767,6 +777,13 @@ "scenario": "eve-instrumentation", "testRunId": "" }, + "metrics": { + "completion_tokens": 352, + "prompt_cache_creation_tokens": 0, + "prompt_cached_tokens": 0, + "prompt_tokens": 6386, + "tokens": 6738 + }, "context": { "caller_filename": "", "caller_functionname": "", @@ -779,7 +796,6 @@ }, "output": "Researcher result: Eve instrumentation https://eve.dev/docs/guides/instrumentation", "metadata": { - "eve.action_kind": "subagent-call", "eve.session_id": "", "scenario": "eve-instrumentation", "testRunId": "" @@ -804,7 +820,6 @@ "url": "https://eve.dev/docs/guides/instrumentation" }, "metadata": { - "eve.action_kind": "tool-call", "eve.session_id": "", "scenario": "eve-instrumentation", "testRunId": "" @@ -1010,6 +1025,13 @@ "scenario": "eve-instrumentation", "testRunId": "" }, + "metrics": { + "completion_tokens": 768, + "prompt_cache_creation_tokens": 0, + "prompt_cached_tokens": 0, + "prompt_tokens": 8515, + "tokens": 9283 + }, "context": { "caller_filename": "", "caller_functionname": "", @@ -1171,7 +1193,6 @@ "url": "https://eve.dev/docs/guides/instrumentation" }, "metadata": { - "eve.action_kind": "tool-call", "eve.session_id": "", "scenario": "eve-instrumentation", "testRunId": "" @@ -1279,6 +1300,13 @@ "scenario": "eve-instrumentation", "testRunId": "" }, + "metrics": { + "completion_tokens": 502, + "prompt_cache_creation_tokens": 0, + "prompt_cached_tokens": 0, + "prompt_tokens": 6461, + "tokens": 6963 + }, "context": { "caller_filename": "", "caller_functionname": "", @@ -1291,7 +1319,6 @@ }, "output": "Researcher result: Eve instrumentation https://eve.dev/docs/guides/instrumentation", "metadata": { - "eve.action_kind": "subagent-call", "eve.session_id": "", "scenario": "eve-instrumentation", "testRunId": "" @@ -1316,7 +1343,6 @@ "url": "https://eve.dev/docs/guides/instrumentation" }, "metadata": { - "eve.action_kind": "tool-call", "eve.session_id": "", "scenario": "eve-instrumentation", "testRunId": "" @@ -1447,6 +1473,13 @@ "scenario": "eve-instrumentation", "testRunId": "" }, + "metrics": { + "completion_tokens": 811, + "prompt_cache_creation_tokens": 0, + "prompt_cached_tokens": 0, + "prompt_tokens": 8020, + "tokens": 8831 + }, "context": { "caller_filename": "", "caller_functionname": "", diff --git a/e2e/scenarios/eve-instrumentation/__snapshots__/eve-v0-latest-pinned.span-tree.txt b/e2e/scenarios/eve-instrumentation/__snapshots__/eve-v0-latest-pinned.span-tree.txt index 1bf5dcc21..0c9001377 100644 --- a/e2e/scenarios/eve-instrumentation/__snapshots__/eve-v0-latest-pinned.span-tree.txt +++ b/e2e/scenarios/eve-instrumentation/__snapshots__/eve-v0-latest-pinned.span-tree.txt @@ -12,6 +12,13 @@ span_tree: │ "scenario": "eve-instrumentation", │ "testRunId": "" │ } +│ metrics: { +│ "completion_tokens": 697, +│ "prompt_cache_creation_tokens": 0, +│ "prompt_cached_tokens": 0, +│ "prompt_tokens": 7946, +│ "tokens": 8643 +│ } │ context: { │ "caller_filename": "", │ "caller_functionname": "", @@ -86,7 +93,6 @@ span_tree: │ │ } │ │ output: "Researcher result: Eve instrumentation https://eve.dev/docs/guides/instrumentation" │ │ metadata: { -│ │ "eve.action_kind": "subagent-call", │ │ "eve.session_id": "", │ │ "scenario": "eve-instrumentation", │ │ "testRunId": "" @@ -109,6 +115,13 @@ span_tree: │ │ "scenario": "eve-instrumentation", │ │ "testRunId": "" │ │ } +│ │ metrics: { +│ │ "completion_tokens": 379, +│ │ "prompt_cache_creation_tokens": 0, +│ │ "prompt_cached_tokens": 0, +│ │ "prompt_tokens": 6418, +│ │ "tokens": 6797 +│ │ } │ │ context: { │ │ "caller_filename": "", │ │ "caller_functionname": "", @@ -179,7 +192,6 @@ span_tree: │ │ │ "url": "https://eve.dev/docs/guides/instrumentation" │ │ │ } │ │ │ metadata: { -│ │ │ "eve.action_kind": "tool-call", │ │ │ "eve.session_id": "", │ │ │ "scenario": "eve-instrumentation", │ │ │ "testRunId": "" @@ -280,7 +292,6 @@ span_tree: │ │ "url": "https://eve.dev/docs/guides/instrumentation" │ │ } │ │ metadata: { -│ │ "eve.action_kind": "tool-call", │ │ "eve.session_id": "", │ │ "scenario": "eve-instrumentation", │ │ "testRunId": "" @@ -406,6 +417,13 @@ span_tree: │ "scenario": "eve-instrumentation", │ "testRunId": "" │ } +│ metrics: { +│ "completion_tokens": 768, +│ "prompt_cache_creation_tokens": 0, +│ "prompt_cached_tokens": 0, +│ "prompt_tokens": 8515, +│ "tokens": 9283 +│ } │ context: { │ "caller_filename": "", │ "caller_functionname": "", @@ -555,7 +573,6 @@ span_tree: │ │ } │ │ output: "Researcher result: Eve instrumentation https://eve.dev/docs/guides/instrumentation" │ │ metadata: { -│ │ "eve.action_kind": "subagent-call", │ │ "eve.session_id": "", │ │ "scenario": "eve-instrumentation", │ │ "testRunId": "" @@ -578,6 +595,13 @@ span_tree: │ │ "scenario": "eve-instrumentation", │ │ "testRunId": "" │ │ } +│ │ metrics: { +│ │ "completion_tokens": 352, +│ │ "prompt_cache_creation_tokens": 0, +│ │ "prompt_cached_tokens": 0, +│ │ "prompt_tokens": 6386, +│ │ "tokens": 6738 +│ │ } │ │ context: { │ │ "caller_filename": "", │ │ "caller_functionname": "", @@ -648,7 +672,6 @@ span_tree: │ │ │ "url": "https://eve.dev/docs/guides/instrumentation" │ │ │ } │ │ │ metadata: { -│ │ │ "eve.action_kind": "tool-call", │ │ │ "eve.session_id": "", │ │ │ "scenario": "eve-instrumentation", │ │ │ "testRunId": "" @@ -749,7 +772,6 @@ span_tree: │ │ "url": "https://eve.dev/docs/guides/instrumentation" │ │ } │ │ metadata: { -│ │ "eve.action_kind": "tool-call", │ │ "eve.session_id": "", │ │ "scenario": "eve-instrumentation", │ │ "testRunId": "" @@ -960,6 +982,13 @@ span_tree: "scenario": "eve-instrumentation", "testRunId": "" } + metrics: { + "completion_tokens": 811, + "prompt_cache_creation_tokens": 0, + "prompt_cached_tokens": 0, + "prompt_tokens": 8020, + "tokens": 8831 + } context: { "caller_filename": "", "caller_functionname": "", @@ -1034,7 +1063,6 @@ span_tree: │ } │ output: "Researcher result: Eve instrumentation https://eve.dev/docs/guides/instrumentation" │ metadata: { - │ "eve.action_kind": "subagent-call", │ "eve.session_id": "", │ "scenario": "eve-instrumentation", │ "testRunId": "" @@ -1057,6 +1085,13 @@ span_tree: │ "scenario": "eve-instrumentation", │ "testRunId": "" │ } + │ metrics: { + │ "completion_tokens": 502, + │ "prompt_cache_creation_tokens": 0, + │ "prompt_cached_tokens": 0, + │ "prompt_tokens": 6461, + │ "tokens": 6963 + │ } │ context: { │ "caller_filename": "", │ "caller_functionname": "", @@ -1127,7 +1162,6 @@ span_tree: │ │ "url": "https://eve.dev/docs/guides/instrumentation" │ │ } │ │ metadata: { - │ │ "eve.action_kind": "tool-call", │ │ "eve.session_id": "", │ │ "scenario": "eve-instrumentation", │ │ "testRunId": "" @@ -1228,7 +1262,6 @@ span_tree: │ "url": "https://eve.dev/docs/guides/instrumentation" │ } │ metadata: { - │ "eve.action_kind": "tool-call", │ "eve.session_id": "", │ "scenario": "eve-instrumentation", │ "testRunId": "" diff --git a/e2e/scenarios/eve-instrumentation/__snapshots__/eve-v0-latest.span-tree.json b/e2e/scenarios/eve-instrumentation/__snapshots__/eve-v0-latest.span-tree.json index 4f911d559..b446fd537 100644 --- a/e2e/scenarios/eve-instrumentation/__snapshots__/eve-v0-latest.span-tree.json +++ b/e2e/scenarios/eve-instrumentation/__snapshots__/eve-v0-latest.span-tree.json @@ -151,7 +151,6 @@ "url": "https://eve.dev/docs/guides/instrumentation" }, "metadata": { - "eve.action_kind": "tool-call", "eve.session_id": "", "scenario": "eve-instrumentation", "testRunId": "" @@ -259,6 +258,13 @@ "scenario": "eve-instrumentation", "testRunId": "" }, + "metrics": { + "completion_tokens": 309, + "prompt_cache_creation_tokens": 0, + "prompt_cached_tokens": 0, + "prompt_tokens": 2941, + "tokens": 3250 + }, "context": { "caller_filename": "", "caller_functionname": "", @@ -271,7 +277,6 @@ }, "output": "Started task researcher-. Its result will arrive in a message. To send it another message, call researcher again with taskId researcher-.", "metadata": { - "eve.action_kind": "subagent-call", "eve.session_id": "", "scenario": "eve-instrumentation", "testRunId": "" @@ -296,7 +301,6 @@ "url": "https://eve.dev/docs/guides/instrumentation" }, "metadata": { - "eve.action_kind": "tool-call", "eve.session_id": "", "scenario": "eve-instrumentation", "testRunId": "" @@ -429,7 +433,6 @@ "children": [], "output": "researcher- completed; its result follows.", "metadata": { - "eve.action_kind": "tool-call", "eve.session_id": "", "scenario": "eve-instrumentation", "testRunId": "" @@ -596,6 +599,13 @@ "scenario": "eve-instrumentation", "testRunId": "" }, + "metrics": { + "completion_tokens": 1168, + "prompt_cache_creation_tokens": 0, + "prompt_cached_tokens": 0, + "prompt_tokens": 8331, + "tokens": 9499 + }, "context": { "caller_filename": "", "caller_functionname": "", @@ -864,7 +874,6 @@ "url": "https://eve.dev/docs/guides/instrumentation" }, "metadata": { - "eve.action_kind": "tool-call", "eve.session_id": "", "scenario": "eve-instrumentation", "testRunId": "" @@ -972,6 +981,13 @@ "scenario": "eve-instrumentation", "testRunId": "" }, + "metrics": { + "completion_tokens": 290, + "prompt_cache_creation_tokens": 0, + "prompt_cached_tokens": 0, + "prompt_tokens": 2953, + "tokens": 3243 + }, "context": { "caller_filename": "", "caller_functionname": "", @@ -984,7 +1000,6 @@ }, "output": "Started task researcher-. Its result will arrive in a message. To send it another message, call researcher again with taskId researcher-.", "metadata": { - "eve.action_kind": "subagent-call", "eve.session_id": "", "scenario": "eve-instrumentation", "testRunId": "" @@ -1009,7 +1024,6 @@ "url": "https://eve.dev/docs/guides/instrumentation" }, "metadata": { - "eve.action_kind": "tool-call", "eve.session_id": "", "scenario": "eve-instrumentation", "testRunId": "" @@ -1253,7 +1267,6 @@ "children": [], "output": "researcher- completed; its result follows.", "metadata": { - "eve.action_kind": "tool-call", "eve.session_id": "", "scenario": "eve-instrumentation", "testRunId": "" @@ -1531,6 +1544,13 @@ "scenario": "eve-instrumentation", "testRunId": "" }, + "metrics": { + "completion_tokens": 872, + "prompt_cache_creation_tokens": 0, + "prompt_cached_tokens": 0, + "prompt_tokens": 9614, + "tokens": 10486 + }, "context": { "caller_filename": "", "caller_functionname": "", @@ -1692,7 +1712,6 @@ "url": "https://eve.dev/docs/guides/instrumentation" }, "metadata": { - "eve.action_kind": "tool-call", "eve.session_id": "", "scenario": "eve-instrumentation", "testRunId": "" @@ -1800,6 +1819,13 @@ "scenario": "eve-instrumentation", "testRunId": "" }, + "metrics": { + "completion_tokens": 293, + "prompt_cache_creation_tokens": 0, + "prompt_cached_tokens": 0, + "prompt_tokens": 2933, + "tokens": 3226 + }, "context": { "caller_filename": "", "caller_functionname": "", @@ -1812,7 +1838,6 @@ }, "output": "Started task researcher-. Its result will arrive in a message. To send it another message, call researcher again with taskId researcher-.", "metadata": { - "eve.action_kind": "subagent-call", "eve.session_id": "", "scenario": "eve-instrumentation", "testRunId": "" @@ -1837,7 +1862,6 @@ "url": "https://eve.dev/docs/guides/instrumentation" }, "metadata": { - "eve.action_kind": "tool-call", "eve.session_id": "", "scenario": "eve-instrumentation", "testRunId": "" @@ -1970,7 +1994,6 @@ "children": [], "output": "researcher- completed; its result follows.", "metadata": { - "eve.action_kind": "tool-call", "eve.session_id": "", "scenario": "eve-instrumentation", "testRunId": "" @@ -2137,6 +2160,13 @@ "scenario": "eve-instrumentation", "testRunId": "" }, + "metrics": { + "completion_tokens": 1035, + "prompt_cache_creation_tokens": 0, + "prompt_cached_tokens": 0, + "prompt_tokens": 8237, + "tokens": 9272 + }, "context": { "caller_filename": "", "caller_functionname": "", diff --git a/e2e/scenarios/eve-instrumentation/__snapshots__/eve-v0-latest.span-tree.txt b/e2e/scenarios/eve-instrumentation/__snapshots__/eve-v0-latest.span-tree.txt index 74595d15c..ccf818c5f 100644 --- a/e2e/scenarios/eve-instrumentation/__snapshots__/eve-v0-latest.span-tree.txt +++ b/e2e/scenarios/eve-instrumentation/__snapshots__/eve-v0-latest.span-tree.txt @@ -12,6 +12,13 @@ span_tree: │ "scenario": "eve-instrumentation", │ "testRunId": "" │ } +│ metrics: { +│ "completion_tokens": 1168, +│ "prompt_cache_creation_tokens": 0, +│ "prompt_cached_tokens": 0, +│ "prompt_tokens": 8331, +│ "tokens": 9499 +│ } │ context: { │ "caller_filename": "", │ "caller_functionname": "", @@ -86,7 +93,6 @@ span_tree: │ │ } │ │ output: "Started task researcher-. Its result will arrive in a message. To send it another message, call researcher again with taskId researcher-." │ │ metadata: { -│ │ "eve.action_kind": "subagent-call", │ │ "eve.session_id": "", │ │ "scenario": "eve-instrumentation", │ │ "testRunId": "" @@ -109,6 +115,13 @@ span_tree: │ │ "scenario": "eve-instrumentation", │ │ "testRunId": "" │ │ } +│ │ metrics: { +│ │ "completion_tokens": 309, +│ │ "prompt_cache_creation_tokens": 0, +│ │ "prompt_cached_tokens": 0, +│ │ "prompt_tokens": 2941, +│ │ "tokens": 3250 +│ │ } │ │ context: { │ │ "caller_filename": "", │ │ "caller_functionname": "", @@ -179,7 +192,6 @@ span_tree: │ │ │ "url": "https://eve.dev/docs/guides/instrumentation" │ │ │ } │ │ │ metadata: { -│ │ │ "eve.action_kind": "tool-call", │ │ │ "eve.session_id": "", │ │ │ "scenario": "eve-instrumentation", │ │ │ "testRunId": "" @@ -280,7 +292,6 @@ span_tree: │ │ "url": "https://eve.dev/docs/guides/instrumentation" │ │ } │ │ metadata: { -│ │ "eve.action_kind": "tool-call", │ │ "eve.session_id": "", │ │ "scenario": "eve-instrumentation", │ │ "testRunId": "" @@ -405,7 +416,6 @@ span_tree: │ ├── eve__task_wait [tool] │ │ output: "researcher- completed; its result follows." │ │ metadata: { -│ │ "eve.action_kind": "tool-call", │ │ "eve.session_id": "", │ │ "scenario": "eve-instrumentation", │ │ "testRunId": "" @@ -567,6 +577,13 @@ span_tree: │ "scenario": "eve-instrumentation", │ "testRunId": "" │ } +│ metrics: { +│ "completion_tokens": 872, +│ "prompt_cache_creation_tokens": 0, +│ "prompt_cached_tokens": 0, +│ "prompt_tokens": 9614, +│ "tokens": 10486 +│ } │ context: { │ "caller_filename": "", │ "caller_functionname": "", @@ -752,7 +769,6 @@ span_tree: │ │ } │ │ output: "Started task researcher-. Its result will arrive in a message. To send it another message, call researcher again with taskId researcher-." │ │ metadata: { -│ │ "eve.action_kind": "subagent-call", │ │ "eve.session_id": "", │ │ "scenario": "eve-instrumentation", │ │ "testRunId": "" @@ -775,6 +791,13 @@ span_tree: │ │ "scenario": "eve-instrumentation", │ │ "testRunId": "" │ │ } +│ │ metrics: { +│ │ "completion_tokens": 290, +│ │ "prompt_cache_creation_tokens": 0, +│ │ "prompt_cached_tokens": 0, +│ │ "prompt_tokens": 2953, +│ │ "tokens": 3243 +│ │ } │ │ context: { │ │ "caller_filename": "", │ │ "caller_functionname": "", @@ -845,7 +868,6 @@ span_tree: │ │ │ "url": "https://eve.dev/docs/guides/instrumentation" │ │ │ } │ │ │ metadata: { -│ │ │ "eve.action_kind": "tool-call", │ │ │ "eve.session_id": "", │ │ │ "scenario": "eve-instrumentation", │ │ │ "testRunId": "" @@ -946,7 +968,6 @@ span_tree: │ │ "url": "https://eve.dev/docs/guides/instrumentation" │ │ } │ │ metadata: { -│ │ "eve.action_kind": "tool-call", │ │ "eve.session_id": "", │ │ "scenario": "eve-instrumentation", │ │ "testRunId": "" @@ -1182,7 +1203,6 @@ span_tree: │ ├── eve__task_wait [tool] │ │ output: "researcher- completed; its result follows." │ │ metadata: { -│ │ "eve.action_kind": "tool-call", │ │ "eve.session_id": "", │ │ "scenario": "eve-instrumentation", │ │ "testRunId": "" @@ -1465,6 +1485,13 @@ span_tree: "scenario": "eve-instrumentation", "testRunId": "" } + metrics: { + "completion_tokens": 1035, + "prompt_cache_creation_tokens": 0, + "prompt_cached_tokens": 0, + "prompt_tokens": 8237, + "tokens": 9272 + } context: { "caller_filename": "", "caller_functionname": "", @@ -1539,7 +1566,6 @@ span_tree: │ } │ output: "Started task researcher-. Its result will arrive in a message. To send it another message, call researcher again with taskId researcher-." │ metadata: { - │ "eve.action_kind": "subagent-call", │ "eve.session_id": "", │ "scenario": "eve-instrumentation", │ "testRunId": "" @@ -1562,6 +1588,13 @@ span_tree: │ "scenario": "eve-instrumentation", │ "testRunId": "" │ } + │ metrics: { + │ "completion_tokens": 293, + │ "prompt_cache_creation_tokens": 0, + │ "prompt_cached_tokens": 0, + │ "prompt_tokens": 2933, + │ "tokens": 3226 + │ } │ context: { │ "caller_filename": "", │ "caller_functionname": "", @@ -1632,7 +1665,6 @@ span_tree: │ │ "url": "https://eve.dev/docs/guides/instrumentation" │ │ } │ │ metadata: { - │ │ "eve.action_kind": "tool-call", │ │ "eve.session_id": "", │ │ "scenario": "eve-instrumentation", │ │ "testRunId": "" @@ -1733,7 +1765,6 @@ span_tree: │ "url": "https://eve.dev/docs/guides/instrumentation" │ } │ metadata: { - │ "eve.action_kind": "tool-call", │ "eve.session_id": "", │ "scenario": "eve-instrumentation", │ "testRunId": "" @@ -1858,7 +1889,6 @@ span_tree: ├── eve__task_wait [tool] │ output: "researcher- completed; its result follows." │ metadata: { - │ "eve.action_kind": "tool-call", │ "eve.session_id": "", │ "scenario": "eve-instrumentation", │ "testRunId": "" diff --git a/e2e/scenarios/eve-instrumentation/__snapshots__/eve-v0-provider.span-tree.json b/e2e/scenarios/eve-instrumentation/__snapshots__/eve-v0-provider.span-tree.json index 02f5688b2..9d3e3e26c 100644 --- a/e2e/scenarios/eve-instrumentation/__snapshots__/eve-v0-provider.span-tree.json +++ b/e2e/scenarios/eve-instrumentation/__snapshots__/eve-v0-provider.span-tree.json @@ -151,7 +151,6 @@ "url": "https://eve.dev/docs/guides/instrumentation" }, "metadata": { - "eve.action_kind": "tool-call", "eve.session_id": "", "scenario": "eve-instrumentation", "testRunId": "" @@ -259,6 +258,13 @@ "scenario": "eve-instrumentation", "testRunId": "" }, + "metrics": { + "completion_tokens": 319, + "prompt_cache_creation_tokens": 0, + "prompt_cached_tokens": 0, + "prompt_tokens": 6358, + "tokens": 6677 + }, "context": { "caller_filename": "", "caller_functionname": "", @@ -271,7 +277,6 @@ }, "output": "Researcher result: The Braintrust Eve instrumentation end-to-end scenario is documented in the [Eve instrumentation guide](https://eve.dev/docs/guides/instrumentation).", "metadata": { - "eve.action_kind": "subagent-call", "eve.session_id": "", "scenario": "eve-instrumentation", "testRunId": "" @@ -296,7 +301,6 @@ "url": "https://eve.dev/docs/guides/instrumentation" }, "metadata": { - "eve.action_kind": "tool-call", "eve.session_id": "", "scenario": "eve-instrumentation", "testRunId": "" @@ -427,6 +431,13 @@ "scenario": "eve-instrumentation", "testRunId": "" }, + "metrics": { + "completion_tokens": 669, + "prompt_cache_creation_tokens": 0, + "prompt_cached_tokens": 0, + "prompt_tokens": 7919, + "tokens": 8588 + }, "context": { "caller_filename": "", "caller_functionname": "", @@ -659,7 +670,6 @@ "url": "https://eve.dev/docs/guides/instrumentation" }, "metadata": { - "eve.action_kind": "tool-call", "eve.session_id": "", "scenario": "eve-instrumentation", "testRunId": "" @@ -767,6 +777,13 @@ "scenario": "eve-instrumentation", "testRunId": "" }, + "metrics": { + "completion_tokens": 307, + "prompt_cache_creation_tokens": 0, + "prompt_cached_tokens": 0, + "prompt_tokens": 6342, + "tokens": 6649 + }, "context": { "caller_filename": "", "caller_functionname": "", @@ -779,7 +796,6 @@ }, "output": "Researcher result: Eve instrumentation https://eve.dev/docs/guides/instrumentation", "metadata": { - "eve.action_kind": "subagent-call", "eve.session_id": "", "scenario": "eve-instrumentation", "testRunId": "" @@ -804,7 +820,6 @@ "url": "https://eve.dev/docs/guides/instrumentation" }, "metadata": { - "eve.action_kind": "tool-call", "eve.session_id": "", "scenario": "eve-instrumentation", "testRunId": "" @@ -1010,6 +1025,13 @@ "scenario": "eve-instrumentation", "testRunId": "" }, + "metrics": { + "completion_tokens": 620, + "prompt_cache_creation_tokens": 0, + "prompt_cached_tokens": 0, + "prompt_tokens": 8453, + "tokens": 9073 + }, "context": { "caller_filename": "", "caller_functionname": "", @@ -1161,6 +1183,13 @@ "scenario": "eve-instrumentation", "testRunId": "" }, + "metrics": { + "completion_tokens": 358, + "prompt_cache_creation_tokens": 0, + "prompt_cached_tokens": 0, + "prompt_tokens": 3077, + "tokens": 3435 + }, "context": { "caller_filename": "", "caller_functionname": "", @@ -1173,7 +1202,6 @@ }, "output": "Researcher result: Braintrust Eve Instrumentation E2E Scenario Guide (https://docs.eve.dev/instrumentation/e2e-testing)", "metadata": { - "eve.action_kind": "subagent-call", "eve.session_id": "", "scenario": "eve-instrumentation", "testRunId": "" @@ -1198,7 +1226,6 @@ "url": "https://eve.dev/docs/guides/instrumentation" }, "metadata": { - "eve.action_kind": "tool-call", "eve.session_id": "", "scenario": "eve-instrumentation", "testRunId": "" @@ -1329,6 +1356,13 @@ "scenario": "eve-instrumentation", "testRunId": "" }, + "metrics": { + "completion_tokens": 652, + "prompt_cache_creation_tokens": 0, + "prompt_cached_tokens": 0, + "prompt_tokens": 7908, + "tokens": 8560 + }, "context": { "caller_filename": "", "caller_functionname": "", diff --git a/e2e/scenarios/eve-instrumentation/__snapshots__/eve-v0-provider.span-tree.txt b/e2e/scenarios/eve-instrumentation/__snapshots__/eve-v0-provider.span-tree.txt index 8eace1b13..e89d57ee5 100644 --- a/e2e/scenarios/eve-instrumentation/__snapshots__/eve-v0-provider.span-tree.txt +++ b/e2e/scenarios/eve-instrumentation/__snapshots__/eve-v0-provider.span-tree.txt @@ -12,6 +12,13 @@ span_tree: │ "scenario": "eve-instrumentation", │ "testRunId": "" │ } +│ metrics: { +│ "completion_tokens": 669, +│ "prompt_cache_creation_tokens": 0, +│ "prompt_cached_tokens": 0, +│ "prompt_tokens": 7919, +│ "tokens": 8588 +│ } │ context: { │ "caller_filename": "", │ "caller_functionname": "", @@ -86,7 +93,6 @@ span_tree: │ │ } │ │ output: "Researcher result: The Braintrust Eve instrumentation end-to-end scenario is documented in the [Eve instrumentation guide](https://eve.dev/docs/guides/instrumentation)." │ │ metadata: { -│ │ "eve.action_kind": "subagent-call", │ │ "eve.session_id": "", │ │ "scenario": "eve-instrumentation", │ │ "testRunId": "" @@ -109,6 +115,13 @@ span_tree: │ │ "scenario": "eve-instrumentation", │ │ "testRunId": "" │ │ } +│ │ metrics: { +│ │ "completion_tokens": 319, +│ │ "prompt_cache_creation_tokens": 0, +│ │ "prompt_cached_tokens": 0, +│ │ "prompt_tokens": 6358, +│ │ "tokens": 6677 +│ │ } │ │ context: { │ │ "caller_filename": "", │ │ "caller_functionname": "", @@ -179,7 +192,6 @@ span_tree: │ │ │ "url": "https://eve.dev/docs/guides/instrumentation" │ │ │ } │ │ │ metadata: { -│ │ │ "eve.action_kind": "tool-call", │ │ │ "eve.session_id": "", │ │ │ "scenario": "eve-instrumentation", │ │ │ "testRunId": "" @@ -280,7 +292,6 @@ span_tree: │ │ "url": "https://eve.dev/docs/guides/instrumentation" │ │ } │ │ metadata: { -│ │ "eve.action_kind": "tool-call", │ │ "eve.session_id": "", │ │ "scenario": "eve-instrumentation", │ │ "testRunId": "" @@ -406,6 +417,13 @@ span_tree: │ "scenario": "eve-instrumentation", │ "testRunId": "" │ } +│ metrics: { +│ "completion_tokens": 620, +│ "prompt_cache_creation_tokens": 0, +│ "prompt_cached_tokens": 0, +│ "prompt_tokens": 8453, +│ "tokens": 9073 +│ } │ context: { │ "caller_filename": "", │ "caller_functionname": "", @@ -555,7 +573,6 @@ span_tree: │ │ } │ │ output: "Researcher result: Eve instrumentation https://eve.dev/docs/guides/instrumentation" │ │ metadata: { -│ │ "eve.action_kind": "subagent-call", │ │ "eve.session_id": "", │ │ "scenario": "eve-instrumentation", │ │ "testRunId": "" @@ -578,6 +595,13 @@ span_tree: │ │ "scenario": "eve-instrumentation", │ │ "testRunId": "" │ │ } +│ │ metrics: { +│ │ "completion_tokens": 307, +│ │ "prompt_cache_creation_tokens": 0, +│ │ "prompt_cached_tokens": 0, +│ │ "prompt_tokens": 6342, +│ │ "tokens": 6649 +│ │ } │ │ context: { │ │ "caller_filename": "", │ │ "caller_functionname": "", @@ -648,7 +672,6 @@ span_tree: │ │ │ "url": "https://eve.dev/docs/guides/instrumentation" │ │ │ } │ │ │ metadata: { -│ │ │ "eve.action_kind": "tool-call", │ │ │ "eve.session_id": "", │ │ │ "scenario": "eve-instrumentation", │ │ │ "testRunId": "" @@ -749,7 +772,6 @@ span_tree: │ │ "url": "https://eve.dev/docs/guides/instrumentation" │ │ } │ │ metadata: { -│ │ "eve.action_kind": "tool-call", │ │ "eve.session_id": "", │ │ "scenario": "eve-instrumentation", │ │ "testRunId": "" @@ -960,6 +982,13 @@ span_tree: "scenario": "eve-instrumentation", "testRunId": "" } + metrics: { + "completion_tokens": 652, + "prompt_cache_creation_tokens": 0, + "prompt_cached_tokens": 0, + "prompt_tokens": 7908, + "tokens": 8560 + } context: { "caller_filename": "", "caller_functionname": "", @@ -1034,7 +1063,6 @@ span_tree: │ } │ output: "Researcher result: Braintrust Eve Instrumentation E2E Scenario Guide (https://docs.eve.dev/instrumentation/e2e-testing)" │ metadata: { - │ "eve.action_kind": "subagent-call", │ "eve.session_id": "", │ "scenario": "eve-instrumentation", │ "testRunId": "" @@ -1057,6 +1085,13 @@ span_tree: │ "scenario": "eve-instrumentation", │ "testRunId": "" │ } + │ metrics: { + │ "completion_tokens": 358, + │ "prompt_cache_creation_tokens": 0, + │ "prompt_cached_tokens": 0, + │ "prompt_tokens": 3077, + │ "tokens": 3435 + │ } │ context: { │ "caller_filename": "", │ "caller_functionname": "", @@ -1118,7 +1153,6 @@ span_tree: │ "url": "https://eve.dev/docs/guides/instrumentation" │ } │ metadata: { - │ "eve.action_kind": "tool-call", │ "eve.session_id": "", │ "scenario": "eve-instrumentation", │ "testRunId": "" diff --git a/e2e/scenarios/eve-instrumentation/__snapshots__/eve-v0.span-tree.json b/e2e/scenarios/eve-instrumentation/__snapshots__/eve-v0.span-tree.json deleted file mode 100644 index ca7da60a3..000000000 --- a/e2e/scenarios/eve-instrumentation/__snapshots__/eve-v0.span-tree.json +++ /dev/null @@ -1,1038 +0,0 @@ -{ - "span_tree": [ - { - "name": "eve.turn", - "type": "task", - "children": [ - { - "name": "eve.step", - "type": "llm", - "children": [], - "input": [ - { - "content": "Instructions (instructions)\nYou are a deterministic fixture agent for Braintrust Eve instrumentation tests.\n\nFor every user task, follow these steps exactly:\n\n1. Your first response MUST contain exactly these two tool calls and no text:\n - researcher with only this argument: {\"message\":\"\"}.\n Copy the current user message verbatim, including words such as \"again\";\n never reuse a message from an earlier turn. Do not pass outputSchema.\n - read with only this argument:\n {\"url\":\"https://eve.dev/docs/guides/instrumentation\"}.\n Emit both calls simultaneously in the same response, with researcher first\n and read second, so Eve executes them as one parallel batch. The calls are\n independent. NEVER call one by itself, wait for its result, or emit either\n call in a separate response.\n2. If a tool result says \"Started task\", your next response MUST be exactly one\n task_wait call with no arguments and no text.\n3. Only after both results are available, answer with a single sentence that\n starts with \"Final answer from read:\" and includes the researcher result, read\n title, URL, and read excerpt.\n\nTool execution\nA single tool or subagent call runs as one serial action. If you call multiple independent tools or subagents in one response, eve treats that batch as parallel work. Only batch work that is independent and does not rely on another call in the same response.", - "role": "system" - }, - { - "content": "Run the Braintrust Eve instrumentation e2e scenario", - "role": "user" - } - ], - "output": [ - { - "finish_reason": "tool_calls", - "index": 0, - "message": { - "content": null, - "reasoning": [ - { - "content": "\nOkay, the user wants me to run the Braintrust Eve instrumentation end-to-end scenario. Let me start by understanding what that entails. The user mentioned \"e2e scenario,\" which usually stands for end-to-end testing. So, they probably want to simulate or execute a complete workflow within the Eve instrumentation setup.\n\nFirst, I need to check the available tools. The user provided a list of functions, including researcher, read, and others. The instructions say that for every user task, I must first make two tool calls: researcher with the user's message and read with the specific URL. \n\nThe user's message is \"Run the Braintrust Eve instrumentation e2e scenario.\" So, I should call the researcher function with this message. Then, the read function should access the documentation URL provided earlier: https://eve.dev/docs/guides/instrumentation. \n\nI need to make sure both calls are made in the same response, as per the instructions. No text, just the two tool calls. Then, if the tool results say \"Started task,\" I have to call task_wait. But since the user hasn't provided any results yet, I should just proceed with the initial calls.\n\nAfter both results are available, I need to answer with a single sentence starting with \"Final answer from read:\" including the researcher result, read title, URL, and excerpt. But right now, the first step is to generate the two tool calls.\n" - } - ], - "role": "assistant", - "tool_calls": [ - { - "function": { - "arguments": "{\"message\":\"Run the Braintrust Eve instrumentation e2e scenario\"}", - "name": "researcher" - }, - "id": "", - "type": "function" - }, - { - "function": { - "arguments": "{\"url\":\"https://eve.dev/docs/guides/instrumentation\"}", - "name": "read" - }, - "id": "", - "type": "function" - } - ] - } - } - ], - "metadata": { - "eve.session_id": "", - "scenario": "eve-instrumentation", - "testRunId": "" - }, - "metrics": { - "completion_tokens": 353, - "prompt_cache_creation_tokens": 0, - "prompt_cached_tokens": 0, - "prompt_tokens": 3396, - "tokens": 3749 - }, - "context": { - "caller_filename": "", - "caller_functionname": "", - "caller_lineno": 0 - } - }, - { - "name": "researcher", - "type": "tool", - "children": [ - { - "name": "eve.turn", - "type": "task", - "children": [ - { - "name": "eve.step", - "type": "llm", - "children": [], - "input": [ - { - "content": "Instructions (instructions)\nYou are the deterministic researcher subagent for Braintrust Eve instrumentation tests.\n\nFor every task:\n\n1. Call the search tool exactly once with the full task as the query.\n2. After the search result is available, answer with a single sentence that starts\n with \"Researcher result:\" and includes the search title and URL.\n\nTool execution\nA single tool or subagent call runs as one serial action. If you call multiple independent tools or subagents in one response, eve treats that batch as parallel work. Only batch work that is independent and does not rely on another call in the same response.", - "role": "system" - }, - { - "content": "You are the subagent \"researcher\".\nDescription: Research the Eve instrumentation documentation before the parent reads it.\n\nThe caller delegated the following task to you. Complete it and return the final result directly.\n\nCaller message:\nRun the Braintrust Eve instrumentation e2e scenario", - "role": "user" - } - ], - "output": [ - { - "finish_reason": "tool_calls", - "index": 0, - "message": { - "content": null, - "reasoning": [ - { - "content": "\nOkay, the user wants me to act as the researcher subagent for Braintrust Eve instrumentation tests. The task is to research the Eve instrumentation documentation before the parent reads it. The caller's message is to \"Run the Braintrust Eve instrumentation e2e scenario.\" \n\nFirst, I need to figure out what steps are required here. Since the user mentioned running an end-to-end scenario, I should look into the documentation to find relevant information. The available tools include a search function that queries the Eve instrumentation documentation. \n\nThe instructions say to call the search tool exactly once with the full task as the query. So, the first step is to use the search function with the query \"Run the Braintrust Eve instrumentation e2e scenario.\" That should retrieve the necessary documentation.\n\nAfter getting the search results, I need to provide a single-sentence answer starting with \"Researcher result:\" including the search title and URL. But since I don't have real-time data, I'll simulate the response based on typical documentation structures. \n\nI should check if there are any other tools needed, but according to the task, after the search, the answer is straightforward. No other functions like grep or read_file seem necessary here. The main action is the search, then presenting the result.\n" - } - ], - "role": "assistant", - "tool_calls": [ - { - "function": { - "arguments": "{\"query\":\"Run the Braintrust Eve instrumentation e2e scenario\"}", - "name": "search" - }, - "id": "", - "type": "function" - } - ] - } - } - ], - "metadata": { - "eve.session_id": "", - "model": "qwen/qwen3-30b-a3b", - "provider": "openrouter", - "scenario": "eve-instrumentation", - "testRunId": "" - }, - "metrics": { - "completion_tokens": 288, - "prompt_cache_creation_tokens": 0, - "prompt_cached_tokens": 0, - "prompt_tokens": 3036, - "tokens": 3324 - }, - "context": { - "caller_filename": "", - "caller_functionname": "", - "caller_lineno": 0 - } - }, - { - "name": "search", - "type": "tool", - "children": [], - "input": { - "query": "Run the Braintrust Eve instrumentation e2e scenario" - }, - "output": { - "query": "Run the Braintrust Eve instrumentation e2e scenario", - "title": "Eve instrumentation", - "url": "https://eve.dev/docs/guides/instrumentation" - }, - "metadata": { - "eve.session_id": "", - "scenario": "eve-instrumentation", - "testRunId": "" - }, - "context": { - "caller_filename": "", - "caller_functionname": "", - "caller_lineno": 0 - } - }, - { - "name": "eve.step", - "type": "llm", - "children": [], - "input": [ - { - "content": "Instructions (instructions)\nYou are the deterministic researcher subagent for Braintrust Eve instrumentation tests.\n\nFor every task:\n\n1. Call the search tool exactly once with the full task as the query.\n2. After the search result is available, answer with a single sentence that starts\n with \"Researcher result:\" and includes the search title and URL.\n\nTool execution\nA single tool or subagent call runs as one serial action. If you call multiple independent tools or subagents in one response, eve treats that batch as parallel work. Only batch work that is independent and does not rely on another call in the same response.", - "role": "system" - }, - { - "content": "You are the subagent \"researcher\".\nDescription: Research the Eve instrumentation documentation before the parent reads it.\n\nThe caller delegated the following task to you. Complete it and return the final result directly.\n\nCaller message:\nRun the Braintrust Eve instrumentation e2e scenario", - "role": "user" - }, - { - "content": [ - { - "text": "\nOkay, the user wants me to act as the researcher subagent for Braintrust Eve instrumentation tests. The task is to research the Eve instrumentation documentation before the parent reads it. The caller's message is to \"Run the Braintrust Eve instrumentation e2e scenario.\" \n\nFirst, I need to figure out what steps are required here. Since the user mentioned running an end-to-end scenario, I should look into the documentation to find relevant information. The available tools include a search function that queries the Eve instrumentation documentation. \n\nThe instructions say to call the search tool exactly once with the full task as the query. So, the first step is to use the search function with the query \"Run the Braintrust Eve instrumentation e2e scenario.\" That should retrieve the necessary documentation.\n\nAfter getting the search results, I need to provide a single-sentence answer starting with \"Researcher result:\" including the search title and URL. But since I don't have real-time data, I'll simulate the response based on typical documentation structures. \n\nI should check if there are any other tools needed, but according to the task, after the search, the answer is straightforward. No other functions like grep or read_file seem necessary here. The main action is the search, then presenting the result.\n", - "type": "reasoning" - }, - { - "input": { - "query": "Run the Braintrust Eve instrumentation e2e scenario" - }, - "toolCallId": "", - "toolName": "search", - "type": "tool-call" - } - ], - "role": "assistant" - }, - { - "content": [ - { - "output": { - "type": "json", - "value": { - "query": "Run the Braintrust Eve instrumentation e2e scenario", - "title": "Eve instrumentation", - "url": "https://eve.dev/docs/guides/instrumentation" - } - }, - "toolCallId": "", - "toolName": "search", - "type": "tool-result" - } - ], - "role": "tool" - } - ], - "output": [ - { - "finish_reason": "stop", - "index": 0, - "message": { - "content": "Researcher result: Eve instrumentation https://eve.dev/docs/guides/instrumentation", - "reasoning": [ - { - "content": "\nOkay, the user wants me to act as the researcher subagent for Braintrust Eve instrumentation tests. The task is to research the Eve instrumentation documentation before the parent reads it. The caller's message is to \"Run the Braintrust Eve instrumentation e2e scenario.\"\n\nFirst, I need to call the search tool with the query \"Run the Braintrust Eve instrumentation e2e scenario.\" The search result returned a title \"Eve instrumentation\" and a URL. Now, I need to format the answer as specified.\n\nThe answer should start with \"Researcher result:\" followed by the title and URL. I need to make sure it's a single sentence. The example given in the instructions uses the search title and URL, so I'll follow that structure. No other tools are needed here since the search provided the necessary information. Just present the result clearly.\n" - } - ], - "role": "assistant" - } - } - ], - "metadata": { - "eve.session_id": "", - "model": "qwen/qwen3-30b-a3b", - "provider": "openrouter", - "scenario": "eve-instrumentation", - "testRunId": "" - }, - "metrics": { - "completion_tokens": 193, - "prompt_cache_creation_tokens": 0, - "prompt_cached_tokens": 0, - "prompt_tokens": 3369, - "tokens": 3562 - }, - "context": { - "caller_filename": "", - "caller_functionname": "", - "caller_lineno": 0 - } - } - ], - "input": [ - { - "content": "You are the subagent \"researcher\".\nDescription: Research the Eve instrumentation documentation before the parent reads it.\n\nThe caller delegated the following task to you. Complete it and return the final result directly.\n\nCaller message:\nRun the Braintrust Eve instrumentation e2e scenario", - "role": "user" - } - ], - "output": "Researcher result: Eve instrumentation https://eve.dev/docs/guides/instrumentation", - "metadata": { - "eve.session_id": "", - "model": "qwen/qwen3-30b-a3b", - "provider": "openrouter", - "scenario": "eve-instrumentation", - "testRunId": "" - }, - "metrics": { - "completion_tokens": 481, - "prompt_cache_creation_tokens": 0, - "prompt_cached_tokens": 0, - "prompt_tokens": 6405, - "tokens": 6886 - }, - "context": { - "caller_filename": "", - "caller_functionname": "", - "caller_lineno": 0 - } - } - ], - "input": { - "message": "Run the Braintrust Eve instrumentation e2e scenario" - }, - "output": "Researcher result: Eve instrumentation https://eve.dev/docs/guides/instrumentation", - "metadata": { - "eve.session_id": "", - "scenario": "eve-instrumentation", - "testRunId": "" - }, - "context": { - "caller_filename": "", - "caller_functionname": "", - "caller_lineno": 0 - } - }, - { - "name": "read", - "type": "tool", - "children": [], - "input": { - "url": "https://eve.dev/docs/guides/instrumentation" - }, - "output": { - "excerpt": "Eve hooks expose runtime stream events that Braintrust maps into a nested turn trace.", - "section": "Runtime context", - "title": "Eve instrumentation", - "url": "https://eve.dev/docs/guides/instrumentation" - }, - "metadata": { - "eve.session_id": "", - "scenario": "eve-instrumentation", - "testRunId": "" - }, - "context": { - "caller_filename": "", - "caller_functionname": "", - "caller_lineno": 0 - } - }, - { - "name": "eve.step", - "type": "llm", - "children": [], - "input": [ - { - "content": "Instructions (instructions)\nYou are a deterministic fixture agent for Braintrust Eve instrumentation tests.\n\nFor every user task, follow these steps exactly:\n\n1. Your first response MUST contain exactly these two tool calls and no text:\n - researcher with only this argument: {\"message\":\"\"}.\n Copy the current user message verbatim, including words such as \"again\";\n never reuse a message from an earlier turn. Do not pass outputSchema.\n - read with only this argument:\n {\"url\":\"https://eve.dev/docs/guides/instrumentation\"}.\n Emit both calls simultaneously in the same response, with researcher first\n and read second, so Eve executes them as one parallel batch. The calls are\n independent. NEVER call one by itself, wait for its result, or emit either\n call in a separate response.\n2. If a tool result says \"Started task\", your next response MUST be exactly one\n task_wait call with no arguments and no text.\n3. Only after both results are available, answer with a single sentence that\n starts with \"Final answer from read:\" and includes the researcher result, read\n title, URL, and read excerpt.\n\nTool execution\nA single tool or subagent call runs as one serial action. If you call multiple independent tools or subagents in one response, eve treats that batch as parallel work. Only batch work that is independent and does not rely on another call in the same response.", - "role": "system" - }, - { - "content": "Run the Braintrust Eve instrumentation e2e scenario", - "role": "user" - }, - { - "content": [ - { - "text": "\nOkay, the user wants me to run the Braintrust Eve instrumentation end-to-end scenario. Let me start by understanding what that entails. The user mentioned \"e2e scenario,\" which usually stands for end-to-end testing. So, they probably want to simulate or execute a complete workflow within the Eve instrumentation setup.\n\nFirst, I need to check the available tools. The user provided a list of functions, including researcher, read, and others. The instructions say that for every user task, I must first make two tool calls: researcher with the user's message and read with the specific URL. \n\nThe user's message is \"Run the Braintrust Eve instrumentation e2e scenario.\" So, I should call the researcher function with this message. Then, the read function should access the documentation URL provided earlier: https://eve.dev/docs/guides/instrumentation. \n\nI need to make sure both calls are made in the same response, as per the instructions. No text, just the two tool calls. Then, if the tool results say \"Started task,\" I have to call task_wait. But since the user hasn't provided any results yet, I should just proceed with the initial calls.\n\nAfter both results are available, I need to answer with a single sentence starting with \"Final answer from read:\" including the researcher result, read title, URL, and excerpt. But right now, the first step is to generate the two tool calls.\n", - "type": "reasoning" - }, - { - "input": { - "message": "Run the Braintrust Eve instrumentation e2e scenario" - }, - "toolCallId": "", - "toolName": "researcher", - "type": "tool-call" - }, - { - "input": { - "url": "https://eve.dev/docs/guides/instrumentation" - }, - "toolCallId": "", - "toolName": "read", - "type": "tool-call" - } - ], - "role": "assistant" - }, - { - "content": [ - { - "output": { - "type": "json", - "value": { - "excerpt": "Eve hooks expose runtime stream events that Braintrust maps into a nested turn trace.", - "section": "Runtime context", - "title": "Eve instrumentation", - "url": "https://eve.dev/docs/guides/instrumentation" - } - }, - "toolCallId": "", - "toolName": "read", - "type": "tool-result" - } - ], - "role": "tool" - }, - { - "content": [ - { - "output": { - "type": "text", - "value": "Researcher result: Eve instrumentation https://eve.dev/docs/guides/instrumentation" - }, - "toolCallId": "", - "toolName": "researcher", - "type": "tool-result" - } - ], - "role": "tool" - } - ], - "output": [ - { - "finish_reason": "stop", - "index": 0, - "message": { - "content": "Final answer from read: Eve instrumentation https://eve.dev/docs/guides/instrumentation - \"Eve hooks expose runtime stream events that Braintrust maps into a nested turn trace.\" (Eve instrumentation, https://eve.dev/docs/guides/instrumentation)", - "reasoning": [ - { - "content": "\nOkay, let me process what's happening here. The user asked to run the Braintrust Eve instrumentation e2e scenario. According to the instructions, I needed to make two initial tool calls: researcher with the user's message and read with the specific URL. Both were made, and now the results are in.\n\nThe researcher result mentions \"Eve instrumentation https://eve.dev/docs/guides/instrumentation,\" which seems like a confirmation that the research task was started. The read tool returned a snippet from the documentation, including the title, section, and an excerpt about Eve hooks mapping to a nested turn trace.\n\nNow, the next step is to compile this information into a final answer. The instructions specify that the answer must start with \"Final answer from read:\" and include the researcher result, the read title, URL, and excerpt. \n\nI need to ensure that all required elements are included. The researcher result is \"Eve instrumentation https://eve.dev/docs/guides/instrumentation,\" the title is \"Eve instrumentation,\" the URL is the same, and the excerpt is the provided text. \n\nI should structure the sentence correctly, making sure there's no markdown and that it's a single, coherent sentence. Let me check for any typos or missing parts. Everything seems to be in order. Time to put it all together.\n" - } - ], - "role": "assistant" - } - } - ], - "metadata": { - "eve.session_id": "", - "scenario": "eve-instrumentation", - "testRunId": "" - }, - "metrics": { - "completion_tokens": 331, - "prompt_cache_creation_tokens": 0, - "prompt_cached_tokens": 0, - "prompt_tokens": 3826, - "tokens": 4157 - }, - "context": { - "caller_filename": "", - "caller_functionname": "", - "caller_lineno": 0 - } - } - ], - "input": [ - { - "content": "Run the Braintrust Eve instrumentation e2e scenario", - "role": "user" - } - ], - "output": "Final answer from read: Eve instrumentation https://eve.dev/docs/guides/instrumentation - \"Eve hooks expose runtime stream events that Braintrust maps into a nested turn trace.\" (Eve instrumentation, https://eve.dev/docs/guides/instrumentation)", - "metadata": { - "eve.session_id": "", - "scenario": "eve-instrumentation", - "testRunId": "" - }, - "metrics": { - "completion_tokens": 684, - "prompt_cache_creation_tokens": 0, - "prompt_cached_tokens": 0, - "prompt_tokens": 7222, - "tokens": 7906 - }, - "context": { - "caller_filename": "", - "caller_functionname": "", - "caller_lineno": 0 - } - }, - { - "name": "eve.turn", - "type": "task", - "children": [ - { - "name": "eve.step", - "type": "llm", - "children": [], - "input": [ - { - "content": "Instructions (instructions)\nYou are a deterministic fixture agent for Braintrust Eve instrumentation tests.\n\nFor every user task, follow these steps exactly:\n\n1. Your first response MUST contain exactly these two tool calls and no text:\n - researcher with only this argument: {\"message\":\"\"}.\n Copy the current user message verbatim, including words such as \"again\";\n never reuse a message from an earlier turn. Do not pass outputSchema.\n - read with only this argument:\n {\"url\":\"https://eve.dev/docs/guides/instrumentation\"}.\n Emit both calls simultaneously in the same response, with researcher first\n and read second, so Eve executes them as one parallel batch. The calls are\n independent. NEVER call one by itself, wait for its result, or emit either\n call in a separate response.\n2. If a tool result says \"Started task\", your next response MUST be exactly one\n task_wait call with no arguments and no text.\n3. Only after both results are available, answer with a single sentence that\n starts with \"Final answer from read:\" and includes the researcher result, read\n title, URL, and read excerpt.\n\nTool execution\nA single tool or subagent call runs as one serial action. If you call multiple independent tools or subagents in one response, eve treats that batch as parallel work. Only batch work that is independent and does not rely on another call in the same response.", - "role": "system" - }, - { - "content": "Run the Braintrust Eve instrumentation e2e scenario", - "role": "user" - }, - { - "content": [ - { - "text": "\nOkay, the user wants me to run the Braintrust Eve instrumentation end-to-end scenario. Let me start by understanding what that entails. The user mentioned \"e2e scenario,\" which usually stands for end-to-end testing. So, they probably want to simulate or execute a complete workflow within the Eve instrumentation setup.\n\nFirst, I need to check the available tools. The user provided a list of functions, including researcher, read, and others. The instructions say that for every user task, I must first make two tool calls: researcher with the user's message and read with the specific URL. \n\nThe user's message is \"Run the Braintrust Eve instrumentation e2e scenario.\" So, I should call the researcher function with this message. Then, the read function should access the documentation URL provided earlier: https://eve.dev/docs/guides/instrumentation. \n\nI need to make sure both calls are made in the same response, as per the instructions. No text, just the two tool calls. Then, if the tool results say \"Started task,\" I have to call task_wait. But since the user hasn't provided any results yet, I should just proceed with the initial calls.\n\nAfter both results are available, I need to answer with a single sentence starting with \"Final answer from read:\" including the researcher result, read title, URL, and excerpt. But right now, the first step is to generate the two tool calls.\n", - "type": "reasoning" - }, - { - "input": { - "message": "Run the Braintrust Eve instrumentation e2e scenario" - }, - "toolCallId": "", - "toolName": "researcher", - "type": "tool-call" - }, - { - "input": { - "url": "https://eve.dev/docs/guides/instrumentation" - }, - "toolCallId": "", - "toolName": "read", - "type": "tool-call" - } - ], - "role": "assistant" - }, - { - "content": [ - { - "output": { - "type": "json", - "value": { - "excerpt": "Eve hooks expose runtime stream events that Braintrust maps into a nested turn trace.", - "section": "Runtime context", - "title": "Eve instrumentation", - "url": "https://eve.dev/docs/guides/instrumentation" - } - }, - "toolCallId": "", - "toolName": "read", - "type": "tool-result" - } - ], - "role": "tool" - }, - { - "content": [ - { - "output": { - "type": "text", - "value": "Researcher result: Eve instrumentation https://eve.dev/docs/guides/instrumentation" - }, - "toolCallId": "", - "toolName": "researcher", - "type": "tool-result" - } - ], - "role": "tool" - }, - { - "content": [ - { - "text": "\nOkay, let me process what's happening here. The user asked to run the Braintrust Eve instrumentation e2e scenario. According to the instructions, I needed to make two initial tool calls: researcher with the user's message and read with the specific URL. Both were made, and now the results are in.\n\nThe researcher result mentions \"Eve instrumentation https://eve.dev/docs/guides/instrumentation,\" which seems like a confirmation that the research task was started. The read tool returned a snippet from the documentation, including the title, section, and an excerpt about Eve hooks mapping to a nested turn trace.\n\nNow, the next step is to compile this information into a final answer. The instructions specify that the answer must start with \"Final answer from read:\" and include the researcher result, the read title, URL, and excerpt. \n\nI need to ensure that all required elements are included. The researcher result is \"Eve instrumentation https://eve.dev/docs/guides/instrumentation,\" the title is \"Eve instrumentation,\" the URL is the same, and the excerpt is the provided text. \n\nI should structure the sentence correctly, making sure there's no markdown and that it's a single, coherent sentence. Let me check for any typos or missing parts. Everything seems to be in order. Time to put it all together.\n", - "type": "reasoning" - }, - { - "text": "Final answer from read: Eve instrumentation https://eve.dev/docs/guides/instrumentation - \"Eve hooks expose runtime stream events that Braintrust maps into a nested turn trace.\" (Eve instrumentation, https://eve.dev/docs/guides/instrumentation)", - "type": "text" - } - ], - "role": "assistant" - }, - { - "content": "Run the Braintrust Eve instrumentation e2e scenario again", - "role": "user" - } - ], - "output": [ - { - "finish_reason": "tool_calls", - "index": 0, - "message": { - "content": null, - "reasoning": [ - { - "content": "\nOkay, the user wants me to run the Braintrust Eve instrumentation e2e scenario again. Let me check the previous interactions to make sure I follow the correct steps.\n\nIn the history, the user first asked to run the scenario, and I called the researcher and read functions. The read function fetched the documentation page, and the researcher result was provided. Then the user asked again, and I followed the same steps. Now, the user is asking \"again,\" so I need to repeat the process.\n\nThe instructions say that for every user task, I must first call researcher with the full user message and read with the specific URL. Since the user mentioned \"again,\" the message should be exactly \"Run the Braintrust Eve instrumentation e2e scenario again.\" I need to make sure the arguments are correctly formatted as JSON objects within the tool_call tags. No text should be added, just the two tool calls. Then, if the tool results say \"Started task,\" I should call task_wait. Once both results are available, provide the final answer with the required details. Let me structure the response correctly.\n" - } - ], - "role": "assistant", - "tool_calls": [ - { - "function": { - "arguments": "{\"message\":\"Run the Braintrust Eve instrumentation e2e scenario again\"}", - "name": "researcher" - }, - "id": "", - "type": "function" - }, - { - "function": { - "arguments": "{\"url\":\"https://eve.dev/docs/guides/instrumentation\"}", - "name": "read" - }, - "id": "", - "type": "function" - } - ] - } - } - ], - "metadata": { - "eve.session_id": "", - "scenario": "eve-instrumentation", - "testRunId": "" - }, - "metrics": { - "completion_tokens": 283, - "prompt_cache_creation_tokens": 0, - "prompt_cached_tokens": 0, - "prompt_tokens": 3606, - "tokens": 3889 - }, - "context": { - "caller_filename": "", - "caller_functionname": "", - "caller_lineno": 0 - } - }, - { - "name": "researcher", - "type": "tool", - "children": [ - { - "name": "eve.turn", - "type": "task", - "children": [ - { - "name": "eve.step", - "type": "llm", - "children": [], - "input": [ - { - "content": "Instructions (instructions)\nYou are the deterministic researcher subagent for Braintrust Eve instrumentation tests.\n\nFor every task:\n\n1. Call the search tool exactly once with the full task as the query.\n2. After the search result is available, answer with a single sentence that starts\n with \"Researcher result:\" and includes the search title and URL.\n\nTool execution\nA single tool or subagent call runs as one serial action. If you call multiple independent tools or subagents in one response, eve treats that batch as parallel work. Only batch work that is independent and does not rely on another call in the same response.", - "role": "system" - }, - { - "content": "You are the subagent \"researcher\".\nDescription: Research the Eve instrumentation documentation before the parent reads it.\n\nThe caller delegated the following task to you. Complete it and return the final result directly.\n\nCaller message:\nRun the Braintrust Eve instrumentation e2e scenario again", - "role": "user" - } - ], - "output": [ - { - "finish_reason": "tool_calls", - "index": 0, - "message": { - "content": null, - "reasoning": [ - { - "content": "\nOkay, the user wants me to run the Braintrust Eve instrumentation end-to-end scenario again. Since I'm the researcher subagent, my job is to look up the documentation first. Let me check the tools available. There's a 'search' function that queries the Eve documentation. I need to use that. The task is to \"Run the Braintrust Eve instrumentation e2e scenario again,\" so I'll use that exact query for the search. I'll make sure to call the search tool once with the full task as the query. Then, once I get the results, I'll format the answer with the title and URL from the search result. Let me structure the tool call properly.\n" - } - ], - "role": "assistant", - "tool_calls": [ - { - "function": { - "arguments": "{\"query\":\"Run the Braintrust Eve instrumentation e2e scenario again\"}", - "name": "search" - }, - "id": "", - "type": "function" - } - ] - } - } - ], - "metadata": { - "eve.session_id": "", - "model": "qwen/qwen3-30b-a3b", - "provider": "openrouter", - "scenario": "eve-instrumentation", - "testRunId": "" - }, - "metrics": { - "completion_tokens": 173, - "prompt_cache_creation_tokens": 0, - "prompt_cached_tokens": 0, - "prompt_tokens": 3037, - "tokens": 3210 - }, - "context": { - "caller_filename": "", - "caller_functionname": "", - "caller_lineno": 0 - } - }, - { - "name": "search", - "type": "tool", - "children": [], - "input": { - "query": "Run the Braintrust Eve instrumentation e2e scenario again" - }, - "output": { - "query": "Run the Braintrust Eve instrumentation e2e scenario again", - "title": "Eve instrumentation", - "url": "https://eve.dev/docs/guides/instrumentation" - }, - "metadata": { - "eve.session_id": "", - "scenario": "eve-instrumentation", - "testRunId": "" - }, - "context": { - "caller_filename": "", - "caller_functionname": "", - "caller_lineno": 0 - } - }, - { - "name": "eve.step", - "type": "llm", - "children": [], - "input": [ - { - "content": "Instructions (instructions)\nYou are the deterministic researcher subagent for Braintrust Eve instrumentation tests.\n\nFor every task:\n\n1. Call the search tool exactly once with the full task as the query.\n2. After the search result is available, answer with a single sentence that starts\n with \"Researcher result:\" and includes the search title and URL.\n\nTool execution\nA single tool or subagent call runs as one serial action. If you call multiple independent tools or subagents in one response, eve treats that batch as parallel work. Only batch work that is independent and does not rely on another call in the same response.", - "role": "system" - }, - { - "content": "You are the subagent \"researcher\".\nDescription: Research the Eve instrumentation documentation before the parent reads it.\n\nThe caller delegated the following task to you. Complete it and return the final result directly.\n\nCaller message:\nRun the Braintrust Eve instrumentation e2e scenario again", - "role": "user" - }, - { - "content": [ - { - "text": "\nOkay, the user wants me to run the Braintrust Eve instrumentation end-to-end scenario again. Since I'm the researcher subagent, my job is to look up the documentation first. Let me check the tools available. There's a 'search' function that queries the Eve documentation. I need to use that. The task is to \"Run the Braintrust Eve instrumentation e2e scenario again,\" so I'll use that exact query for the search. I'll make sure to call the search tool once with the full task as the query. Then, once I get the results, I'll format the answer with the title and URL from the search result. Let me structure the tool call properly.\n", - "type": "reasoning" - }, - { - "input": { - "query": "Run the Braintrust Eve instrumentation e2e scenario again" - }, - "toolCallId": "", - "toolName": "search", - "type": "tool-call" - } - ], - "role": "assistant" - }, - { - "content": [ - { - "output": { - "type": "json", - "value": { - "query": "Run the Braintrust Eve instrumentation e2e scenario again", - "title": "Eve instrumentation", - "url": "https://eve.dev/docs/guides/instrumentation" - } - }, - "toolCallId": "", - "toolName": "search", - "type": "tool-result" - } - ], - "role": "tool" - } - ], - "output": [ - { - "finish_reason": "stop", - "index": 0, - "message": { - "content": "Researcher result: The Braintrust Eve instrumentation documentation can be found here: [Eve instrumentation](https://eve.dev/docs/guides/instrumentation).", - "reasoning": [ - { - "content": "\nOkay, the user asked me to run the Braintrust Eve instrumentation end-to-end scenario again. As the researcher subagent, I need to check the documentation first. I used the search tool with the exact query they provided. The result came back with the title \"Eve instrumentation\" and a URL. Now, I should present that information clearly. The instructions say to start the answer with \"Researcher result:\" and include the title and URL. I need to make sure it's a single sentence. Let me format it correctly.\n" - } - ], - "role": "assistant" - } - } - ], - "metadata": { - "eve.session_id": "", - "model": "qwen/qwen3-30b-a3b", - "provider": "openrouter", - "scenario": "eve-instrumentation", - "testRunId": "" - }, - "metrics": { - "completion_tokens": 144, - "prompt_cache_creation_tokens": 0, - "prompt_cached_tokens": 0, - "prompt_tokens": 3256, - "tokens": 3400 - }, - "context": { - "caller_filename": "", - "caller_functionname": "", - "caller_lineno": 0 - } - } - ], - "input": [ - { - "content": "You are the subagent \"researcher\".\nDescription: Research the Eve instrumentation documentation before the parent reads it.\n\nThe caller delegated the following task to you. Complete it and return the final result directly.\n\nCaller message:\nRun the Braintrust Eve instrumentation e2e scenario again", - "role": "user" - } - ], - "output": "Researcher result: The Braintrust Eve instrumentation documentation can be found here: [Eve instrumentation](https://eve.dev/docs/guides/instrumentation).", - "metadata": { - "eve.session_id": "", - "model": "qwen/qwen3-30b-a3b", - "provider": "openrouter", - "scenario": "eve-instrumentation", - "testRunId": "" - }, - "metrics": { - "completion_tokens": 317, - "prompt_cache_creation_tokens": 0, - "prompt_cached_tokens": 0, - "prompt_tokens": 6293, - "tokens": 6610 - }, - "context": { - "caller_filename": "", - "caller_functionname": "", - "caller_lineno": 0 - } - } - ], - "input": { - "message": "Run the Braintrust Eve instrumentation e2e scenario again" - }, - "output": "Researcher result: The Braintrust Eve instrumentation documentation can be found here: [Eve instrumentation](https://eve.dev/docs/guides/instrumentation).", - "metadata": { - "eve.session_id": "", - "scenario": "eve-instrumentation", - "testRunId": "" - }, - "context": { - "caller_filename": "", - "caller_functionname": "", - "caller_lineno": 0 - } - }, - { - "name": "read", - "type": "tool", - "children": [], - "input": { - "url": "https://eve.dev/docs/guides/instrumentation" - }, - "output": { - "excerpt": "Eve hooks expose runtime stream events that Braintrust maps into a nested turn trace.", - "section": "Runtime context", - "title": "Eve instrumentation", - "url": "https://eve.dev/docs/guides/instrumentation" - }, - "metadata": { - "eve.session_id": "", - "scenario": "eve-instrumentation", - "testRunId": "" - }, - "context": { - "caller_filename": "", - "caller_functionname": "", - "caller_lineno": 0 - } - }, - { - "name": "eve.step", - "type": "llm", - "children": [], - "input": [ - { - "content": "Instructions (instructions)\nYou are a deterministic fixture agent for Braintrust Eve instrumentation tests.\n\nFor every user task, follow these steps exactly:\n\n1. Your first response MUST contain exactly these two tool calls and no text:\n - researcher with only this argument: {\"message\":\"\"}.\n Copy the current user message verbatim, including words such as \"again\";\n never reuse a message from an earlier turn. Do not pass outputSchema.\n - read with only this argument:\n {\"url\":\"https://eve.dev/docs/guides/instrumentation\"}.\n Emit both calls simultaneously in the same response, with researcher first\n and read second, so Eve executes them as one parallel batch. The calls are\n independent. NEVER call one by itself, wait for its result, or emit either\n call in a separate response.\n2. If a tool result says \"Started task\", your next response MUST be exactly one\n task_wait call with no arguments and no text.\n3. Only after both results are available, answer with a single sentence that\n starts with \"Final answer from read:\" and includes the researcher result, read\n title, URL, and read excerpt.\n\nTool execution\nA single tool or subagent call runs as one serial action. If you call multiple independent tools or subagents in one response, eve treats that batch as parallel work. Only batch work that is independent and does not rely on another call in the same response.", - "role": "system" - }, - { - "content": "Run the Braintrust Eve instrumentation e2e scenario", - "role": "user" - }, - { - "content": [ - { - "text": "\nOkay, the user wants me to run the Braintrust Eve instrumentation end-to-end scenario. Let me start by understanding what that entails. The user mentioned \"e2e scenario,\" which usually stands for end-to-end testing. So, they probably want to simulate or execute a complete workflow within the Eve instrumentation setup.\n\nFirst, I need to check the available tools. The user provided a list of functions, including researcher, read, and others. The instructions say that for every user task, I must first make two tool calls: researcher with the user's message and read with the specific URL. \n\nThe user's message is \"Run the Braintrust Eve instrumentation e2e scenario.\" So, I should call the researcher function with this message. Then, the read function should access the documentation URL provided earlier: https://eve.dev/docs/guides/instrumentation. \n\nI need to make sure both calls are made in the same response, as per the instructions. No text, just the two tool calls. Then, if the tool results say \"Started task,\" I have to call task_wait. But since the user hasn't provided any results yet, I should just proceed with the initial calls.\n\nAfter both results are available, I need to answer with a single sentence starting with \"Final answer from read:\" including the researcher result, read title, URL, and excerpt. But right now, the first step is to generate the two tool calls.\n", - "type": "reasoning" - }, - { - "input": { - "message": "Run the Braintrust Eve instrumentation e2e scenario" - }, - "toolCallId": "", - "toolName": "researcher", - "type": "tool-call" - }, - { - "input": { - "url": "https://eve.dev/docs/guides/instrumentation" - }, - "toolCallId": "", - "toolName": "read", - "type": "tool-call" - } - ], - "role": "assistant" - }, - { - "content": [ - { - "output": { - "type": "json", - "value": { - "excerpt": "Eve hooks expose runtime stream events that Braintrust maps into a nested turn trace.", - "section": "Runtime context", - "title": "Eve instrumentation", - "url": "https://eve.dev/docs/guides/instrumentation" - } - }, - "toolCallId": "", - "toolName": "read", - "type": "tool-result" - } - ], - "role": "tool" - }, - { - "content": [ - { - "output": { - "type": "text", - "value": "Researcher result: Eve instrumentation https://eve.dev/docs/guides/instrumentation" - }, - "toolCallId": "", - "toolName": "researcher", - "type": "tool-result" - } - ], - "role": "tool" - }, - { - "content": [ - { - "text": "\nOkay, let me process what's happening here. The user asked to run the Braintrust Eve instrumentation e2e scenario. According to the instructions, I needed to make two initial tool calls: researcher with the user's message and read with the specific URL. Both were made, and now the results are in.\n\nThe researcher result mentions \"Eve instrumentation https://eve.dev/docs/guides/instrumentation,\" which seems like a confirmation that the research task was started. The read tool returned a snippet from the documentation, including the title, section, and an excerpt about Eve hooks mapping to a nested turn trace.\n\nNow, the next step is to compile this information into a final answer. The instructions specify that the answer must start with \"Final answer from read:\" and include the researcher result, the read title, URL, and excerpt. \n\nI need to ensure that all required elements are included. The researcher result is \"Eve instrumentation https://eve.dev/docs/guides/instrumentation,\" the title is \"Eve instrumentation,\" the URL is the same, and the excerpt is the provided text. \n\nI should structure the sentence correctly, making sure there's no markdown and that it's a single, coherent sentence. Let me check for any typos or missing parts. Everything seems to be in order. Time to put it all together.\n", - "type": "reasoning" - }, - { - "text": "Final answer from read: Eve instrumentation https://eve.dev/docs/guides/instrumentation - \"Eve hooks expose runtime stream events that Braintrust maps into a nested turn trace.\" (Eve instrumentation, https://eve.dev/docs/guides/instrumentation)", - "type": "text" - } - ], - "role": "assistant" - }, - { - "content": "Run the Braintrust Eve instrumentation e2e scenario again", - "role": "user" - }, - { - "content": [ - { - "text": "\nOkay, the user wants me to run the Braintrust Eve instrumentation e2e scenario again. Let me check the previous interactions to make sure I follow the correct steps.\n\nIn the history, the user first asked to run the scenario, and I called the researcher and read functions. The read function fetched the documentation page, and the researcher result was provided. Then the user asked again, and I followed the same steps. Now, the user is asking \"again,\" so I need to repeat the process.\n\nThe instructions say that for every user task, I must first call researcher with the full user message and read with the specific URL. Since the user mentioned \"again,\" the message should be exactly \"Run the Braintrust Eve instrumentation e2e scenario again.\" I need to make sure the arguments are correctly formatted as JSON objects within the tool_call tags. No text should be added, just the two tool calls. Then, if the tool results say \"Started task,\" I should call task_wait. Once both results are available, provide the final answer with the required details. Let me structure the response correctly.\n", - "type": "reasoning" - }, - { - "input": { - "message": "Run the Braintrust Eve instrumentation e2e scenario again" - }, - "toolCallId": "", - "toolName": "researcher", - "type": "tool-call" - }, - { - "input": { - "url": "https://eve.dev/docs/guides/instrumentation" - }, - "toolCallId": "", - "toolName": "read", - "type": "tool-call" - } - ], - "role": "assistant" - }, - { - "content": [ - { - "output": { - "type": "json", - "value": { - "excerpt": "Eve hooks expose runtime stream events that Braintrust maps into a nested turn trace.", - "section": "Runtime context", - "title": "Eve instrumentation", - "url": "https://eve.dev/docs/guides/instrumentation" - } - }, - "toolCallId": "", - "toolName": "read", - "type": "tool-result" - } - ], - "role": "tool" - }, - { - "content": [ - { - "output": { - "type": "text", - "value": "Researcher result: The Braintrust Eve instrumentation documentation can be found here: [Eve instrumentation](https://eve.dev/docs/guides/instrumentation)." - }, - "toolCallId": "", - "toolName": "researcher", - "type": "tool-result" - } - ], - "role": "tool" - } - ], - "output": [ - { - "finish_reason": "stop", - "index": 0, - "message": { - "content": "Final answer from read: Eve instrumentation https://eve.dev/docs/guides/instrumentation - \"Eve hooks expose runtime stream events that Braintrust maps into a nested turn trace.\" (Eve instrumentation, https://eve.dev/docs/guides/instrumentation)", - "reasoning": [ - { - "content": "\nOkay, the user asked to run the scenario again, so I need to follow the same steps as before. First, I called the researcher with the message \"Run the Braintrust Eve instrumentation e2e scenario again\" and the read function with the URL. The read result came back with the same excerpt as before. Now, I need to check if there's a need to call task_wait, but since the researcher result doesn't mention \"Started task,\" maybe not. The final answer should include the researcher result, the title, URL, and excerpt. Let me structure that properly.\n" - } - ], - "role": "assistant" - } - } - ], - "metadata": { - "eve.session_id": "", - "scenario": "eve-instrumentation", - "testRunId": "" - }, - "metrics": { - "completion_tokens": 177, - "prompt_cache_creation_tokens": 0, - "prompt_cached_tokens": 0, - "prompt_tokens": 3982, - "tokens": 4159 - }, - "context": { - "caller_filename": "", - "caller_functionname": "", - "caller_lineno": 0 - } - } - ], - "input": [ - { - "content": "Run the Braintrust Eve instrumentation e2e scenario again", - "role": "user" - } - ], - "output": "Final answer from read: Eve instrumentation https://eve.dev/docs/guides/instrumentation - \"Eve hooks expose runtime stream events that Braintrust maps into a nested turn trace.\" (Eve instrumentation, https://eve.dev/docs/guides/instrumentation)", - "metadata": { - "eve.session_id": "", - "scenario": "eve-instrumentation", - "testRunId": "" - }, - "metrics": { - "completion_tokens": 460, - "prompt_cache_creation_tokens": 0, - "prompt_cached_tokens": 0, - "prompt_tokens": 7588, - "tokens": 8048 - }, - "context": { - "caller_filename": "", - "caller_functionname": "", - "caller_lineno": 0 - } - } - ] -} diff --git a/e2e/scenarios/eve-instrumentation/__snapshots__/eve-v0.span-tree.txt b/e2e/scenarios/eve-instrumentation/__snapshots__/eve-v0.span-tree.txt deleted file mode 100644 index 71dfcb160..000000000 --- a/e2e/scenarios/eve-instrumentation/__snapshots__/eve-v0.span-tree.txt +++ /dev/null @@ -1,957 +0,0 @@ -span_tree: -├── eve.turn [task] -│ input: [ -│ { -│ "content": "Run the Braintrust Eve instrumentation e2e scenario", -│ "role": "user" -│ } -│ ] -│ output: "Final answer from read: Eve instrumentation https://eve.dev/docs/guides/instrumentation - \"Eve hooks expose runtime stream events that Braintrust maps into a nested turn trace.\" (Eve instrumentation, https://eve.dev/docs/guides/instrumentation)" -│ metadata: { -│ "eve.session_id": "", -│ "scenario": "eve-instrumentation", -│ "testRunId": "" -│ } -│ metrics: { -│ "completion_tokens": 684, -│ "prompt_cache_creation_tokens": 0, -│ "prompt_cached_tokens": 0, -│ "prompt_tokens": 7222, -│ "tokens": 7906 -│ } -│ context: { -│ "caller_filename": "", -│ "caller_functionname": "", -│ "caller_lineno": 0 -│ } -│ ├── eve.step [llm] -│ │ input: [ -│ │ { -│ │ "content": "Instructions (instructions)\nYou are a deterministic fixture agent for Braintrust Eve instrumentation tests.\n\nFor every user task, follow these steps exactly:\n\n1. Your first response MUST contain exactly these two tool calls and no text:\n - researcher with only this argument: {\"message\":\"\"}.\n Copy the current user message verbatim, including words such as \"again\";\n never reuse a message from an earlier turn. Do not pass outputSchema.\n - read with only this argument:\n {\"url\":\"https://eve.dev/docs/guides/instrumentation\"}.\n Emit both calls simultaneously in the same response, with researcher first\n and read second, so Eve executes them as one parallel batch. The calls are\n independent. NEVER call one by itself, wait for its result, or emit either\n call in a separate response.\n2. If a tool result says \"Started task\", your next response MUST be exactly one\n task_wait call with no arguments and no text.\n3. Only after both results are available, answer with a single sentence that\n starts with \"Final answer from read:\" and includes the researcher result, read\n title, URL, and read excerpt.\n\nTool execution\nA single tool or subagent call runs as one serial action. If you call multiple independent tools or subagents in one response, eve treats that batch as parallel work. Only batch work that is independent and does not rely on another call in the same response.", -│ │ "role": "system" -│ │ }, -│ │ { -│ │ "content": "Run the Braintrust Eve instrumentation e2e scenario", -│ │ "role": "user" -│ │ } -│ │ ] -│ │ output: [ -│ │ { -│ │ "finish_reason": "tool_calls", -│ │ "index": 0, -│ │ "message": { -│ │ "content": null, -│ │ "reasoning": [ -│ │ { -│ │ "content": "\nOkay, the user wants me to run the Braintrust Eve instrumentation end-to-end scenario. Let me start by understanding what that entails. The user mentioned \"e2e scenario,\" which usually stands for end-to-end testing. So, they probably want to simulate or execute a complete workflow within the Eve instrumentation setup.\n\nFirst, I need to check the available tools. The user provided a list of functions, including researcher, read, and others. The instructions say that for every user task, I must first make two tool calls: researcher with the user's message and read with the specific URL. \n\nThe user's message is \"Run the Braintrust Eve instrumentation e2e scenario.\" So, I should call the researcher function with this message. Then, the read function should access the documentation URL provided earlier: https://eve.dev/docs/guides/instrumentation. \n\nI need to make sure both calls are made in the same response, as per the instructions. No text, just the two tool calls. Then, if the tool results say \"Started task,\" I have to call task_wait. But since the user hasn't provided any results yet, I should just proceed with the initial calls.\n\nAfter both results are available, I need to answer with a single sentence starting with \"Final answer from read:\" including the researcher result, read title, URL, and excerpt. But right now, the first step is to generate the two tool calls.\n" -│ │ } -│ │ ], -│ │ "role": "assistant", -│ │ "tool_calls": [ -│ │ { -│ │ "function": { -│ │ "arguments": "{\"message\":\"Run the Braintrust Eve instrumentation e2e scenario\"}", -│ │ "name": "researcher" -│ │ }, -│ │ "id": "", -│ │ "type": "function" -│ │ }, -│ │ { -│ │ "function": { -│ │ "arguments": "{\"url\":\"https://eve.dev/docs/guides/instrumentation\"}", -│ │ "name": "read" -│ │ }, -│ │ "id": "", -│ │ "type": "function" -│ │ } -│ │ ] -│ │ } -│ │ } -│ │ ] -│ │ metadata: { -│ │ "eve.session_id": "", -│ │ "scenario": "eve-instrumentation", -│ │ "testRunId": "" -│ │ } -│ │ metrics: { -│ │ "completion_tokens": 353, -│ │ "prompt_cache_creation_tokens": 0, -│ │ "prompt_cached_tokens": 0, -│ │ "prompt_tokens": 3396, -│ │ "tokens": 3749 -│ │ } -│ │ context: { -│ │ "caller_filename": "", -│ │ "caller_functionname": "", -│ │ "caller_lineno": 0 -│ │ } -│ ├── researcher [tool] -│ │ input: { -│ │ "message": "Run the Braintrust Eve instrumentation e2e scenario" -│ │ } -│ │ output: "Researcher result: Eve instrumentation https://eve.dev/docs/guides/instrumentation" -│ │ metadata: { -│ │ "eve.session_id": "", -│ │ "scenario": "eve-instrumentation", -│ │ "testRunId": "" -│ │ } -│ │ context: { -│ │ "caller_filename": "", -│ │ "caller_functionname": "", -│ │ "caller_lineno": 0 -│ │ } -│ │ └── eve.turn [task] -│ │ input: [ -│ │ { -│ │ "content": "You are the subagent \"researcher\".\nDescription: Research the Eve instrumentation documentation before the parent reads it.\n\nThe caller delegated the following task to you. Complete it and return the final result directly.\n\nCaller message:\nRun the Braintrust Eve instrumentation e2e scenario", -│ │ "role": "user" -│ │ } -│ │ ] -│ │ output: "Researcher result: Eve instrumentation https://eve.dev/docs/guides/instrumentation" -│ │ metadata: { -│ │ "eve.session_id": "", -│ │ "model": "qwen/qwen3-30b-a3b", -│ │ "provider": "openrouter", -│ │ "scenario": "eve-instrumentation", -│ │ "testRunId": "" -│ │ } -│ │ metrics: { -│ │ "completion_tokens": 481, -│ │ "prompt_cache_creation_tokens": 0, -│ │ "prompt_cached_tokens": 0, -│ │ "prompt_tokens": 6405, -│ │ "tokens": 6886 -│ │ } -│ │ context: { -│ │ "caller_filename": "", -│ │ "caller_functionname": "", -│ │ "caller_lineno": 0 -│ │ } -│ │ ├── eve.step [llm] -│ │ │ input: [ -│ │ │ { -│ │ │ "content": "Instructions (instructions)\nYou are the deterministic researcher subagent for Braintrust Eve instrumentation tests.\n\nFor every task:\n\n1. Call the search tool exactly once with the full task as the query.\n2. After the search result is available, answer with a single sentence that starts\n with \"Researcher result:\" and includes the search title and URL.\n\nTool execution\nA single tool or subagent call runs as one serial action. If you call multiple independent tools or subagents in one response, eve treats that batch as parallel work. Only batch work that is independent and does not rely on another call in the same response.", -│ │ │ "role": "system" -│ │ │ }, -│ │ │ { -│ │ │ "content": "You are the subagent \"researcher\".\nDescription: Research the Eve instrumentation documentation before the parent reads it.\n\nThe caller delegated the following task to you. Complete it and return the final result directly.\n\nCaller message:\nRun the Braintrust Eve instrumentation e2e scenario", -│ │ │ "role": "user" -│ │ │ } -│ │ │ ] -│ │ │ output: [ -│ │ │ { -│ │ │ "finish_reason": "tool_calls", -│ │ │ "index": 0, -│ │ │ "message": { -│ │ │ "content": null, -│ │ │ "reasoning": [ -│ │ │ { -│ │ │ "content": "\nOkay, the user wants me to act as the researcher subagent for Braintrust Eve instrumentation tests. The task is to research the Eve instrumentation documentation before the parent reads it. The caller's message is to \"Run the Braintrust Eve instrumentation e2e scenario.\" \n\nFirst, I need to figure out what steps are required here. Since the user mentioned running an end-to-end scenario, I should look into the documentation to find relevant information. The available tools include a search function that queries the Eve instrumentation documentation. \n\nThe instructions say to call the search tool exactly once with the full task as the query. So, the first step is to use the search function with the query \"Run the Braintrust Eve instrumentation e2e scenario.\" That should retrieve the necessary documentation.\n\nAfter getting the search results, I need to provide a single-sentence answer starting with \"Researcher result:\" including the search title and URL. But since I don't have real-time data, I'll simulate the response based on typical documentation structures. \n\nI should check if there are any other tools needed, but according to the task, after the search, the answer is straightforward. No other functions like grep or read_file seem necessary here. The main action is the search, then presenting the result.\n" -│ │ │ } -│ │ │ ], -│ │ │ "role": "assistant", -│ │ │ "tool_calls": [ -│ │ │ { -│ │ │ "function": { -│ │ │ "arguments": "{\"query\":\"Run the Braintrust Eve instrumentation e2e scenario\"}", -│ │ │ "name": "search" -│ │ │ }, -│ │ │ "id": "", -│ │ │ "type": "function" -│ │ │ } -│ │ │ ] -│ │ │ } -│ │ │ } -│ │ │ ] -│ │ │ metadata: { -│ │ │ "eve.session_id": "", -│ │ │ "model": "qwen/qwen3-30b-a3b", -│ │ │ "provider": "openrouter", -│ │ │ "scenario": "eve-instrumentation", -│ │ │ "testRunId": "" -│ │ │ } -│ │ │ metrics: { -│ │ │ "completion_tokens": 288, -│ │ │ "prompt_cache_creation_tokens": 0, -│ │ │ "prompt_cached_tokens": 0, -│ │ │ "prompt_tokens": 3036, -│ │ │ "tokens": 3324 -│ │ │ } -│ │ │ context: { -│ │ │ "caller_filename": "", -│ │ │ "caller_functionname": "", -│ │ │ "caller_lineno": 0 -│ │ │ } -│ │ ├── search [tool] -│ │ │ input: { -│ │ │ "query": "Run the Braintrust Eve instrumentation e2e scenario" -│ │ │ } -│ │ │ output: { -│ │ │ "query": "Run the Braintrust Eve instrumentation e2e scenario", -│ │ │ "title": "Eve instrumentation", -│ │ │ "url": "https://eve.dev/docs/guides/instrumentation" -│ │ │ } -│ │ │ metadata: { -│ │ │ "eve.session_id": "", -│ │ │ "scenario": "eve-instrumentation", -│ │ │ "testRunId": "" -│ │ │ } -│ │ │ context: { -│ │ │ "caller_filename": "", -│ │ │ "caller_functionname": "", -│ │ │ "caller_lineno": 0 -│ │ │ } -│ │ └── eve.step [llm] -│ │ input: [ -│ │ { -│ │ "content": "Instructions (instructions)\nYou are the deterministic researcher subagent for Braintrust Eve instrumentation tests.\n\nFor every task:\n\n1. Call the search tool exactly once with the full task as the query.\n2. After the search result is available, answer with a single sentence that starts\n with \"Researcher result:\" and includes the search title and URL.\n\nTool execution\nA single tool or subagent call runs as one serial action. If you call multiple independent tools or subagents in one response, eve treats that batch as parallel work. Only batch work that is independent and does not rely on another call in the same response.", -│ │ "role": "system" -│ │ }, -│ │ { -│ │ "content": "You are the subagent \"researcher\".\nDescription: Research the Eve instrumentation documentation before the parent reads it.\n\nThe caller delegated the following task to you. Complete it and return the final result directly.\n\nCaller message:\nRun the Braintrust Eve instrumentation e2e scenario", -│ │ "role": "user" -│ │ }, -│ │ { -│ │ "content": [ -│ │ { -│ │ "text": "\nOkay, the user wants me to act as the researcher subagent for Braintrust Eve instrumentation tests. The task is to research the Eve instrumentation documentation before the parent reads it. The caller's message is to \"Run the Braintrust Eve instrumentation e2e scenario.\" \n\nFirst, I need to figure out what steps are required here. Since the user mentioned running an end-to-end scenario, I should look into the documentation to find relevant information. The available tools include a search function that queries the Eve instrumentation documentation. \n\nThe instructions say to call the search tool exactly once with the full task as the query. So, the first step is to use the search function with the query \"Run the Braintrust Eve instrumentation e2e scenario.\" That should retrieve the necessary documentation.\n\nAfter getting the search results, I need to provide a single-sentence answer starting with \"Researcher result:\" including the search title and URL. But since I don't have real-time data, I'll simulate the response based on typical documentation structures. \n\nI should check if there are any other tools needed, but according to the task, after the search, the answer is straightforward. No other functions like grep or read_file seem necessary here. The main action is the search, then presenting the result.\n", -│ │ "type": "reasoning" -│ │ }, -│ │ { -│ │ "input": { -│ │ "query": "Run the Braintrust Eve instrumentation e2e scenario" -│ │ }, -│ │ "toolCallId": "", -│ │ "toolName": "search", -│ │ "type": "tool-call" -│ │ } -│ │ ], -│ │ "role": "assistant" -│ │ }, -│ │ { -│ │ "content": [ -│ │ { -│ │ "output": { -│ │ "type": "json", -│ │ "value": { -│ │ "query": "Run the Braintrust Eve instrumentation e2e scenario", -│ │ "title": "Eve instrumentation", -│ │ "url": "https://eve.dev/docs/guides/instrumentation" -│ │ } -│ │ }, -│ │ "toolCallId": "", -│ │ "toolName": "search", -│ │ "type": "tool-result" -│ │ } -│ │ ], -│ │ "role": "tool" -│ │ } -│ │ ] -│ │ output: [ -│ │ { -│ │ "finish_reason": "stop", -│ │ "index": 0, -│ │ "message": { -│ │ "content": "Researcher result: Eve instrumentation https://eve.dev/docs/guides/instrumentation", -│ │ "reasoning": [ -│ │ { -│ │ "content": "\nOkay, the user wants me to act as the researcher subagent for Braintrust Eve instrumentation tests. The task is to research the Eve instrumentation documentation before the parent reads it. The caller's message is to \"Run the Braintrust Eve instrumentation e2e scenario.\"\n\nFirst, I need to call the search tool with the query \"Run the Braintrust Eve instrumentation e2e scenario.\" The search result returned a title \"Eve instrumentation\" and a URL. Now, I need to format the answer as specified.\n\nThe answer should start with \"Researcher result:\" followed by the title and URL. I need to make sure it's a single sentence. The example given in the instructions uses the search title and URL, so I'll follow that structure. No other tools are needed here since the search provided the necessary information. Just present the result clearly.\n" -│ │ } -│ │ ], -│ │ "role": "assistant" -│ │ } -│ │ } -│ │ ] -│ │ metadata: { -│ │ "eve.session_id": "", -│ │ "model": "qwen/qwen3-30b-a3b", -│ │ "provider": "openrouter", -│ │ "scenario": "eve-instrumentation", -│ │ "testRunId": "" -│ │ } -│ │ metrics: { -│ │ "completion_tokens": 193, -│ │ "prompt_cache_creation_tokens": 0, -│ │ "prompt_cached_tokens": 0, -│ │ "prompt_tokens": 3369, -│ │ "tokens": 3562 -│ │ } -│ │ context: { -│ │ "caller_filename": "", -│ │ "caller_functionname": "", -│ │ "caller_lineno": 0 -│ │ } -│ ├── read [tool] -│ │ input: { -│ │ "url": "https://eve.dev/docs/guides/instrumentation" -│ │ } -│ │ output: { -│ │ "excerpt": "Eve hooks expose runtime stream events that Braintrust maps into a nested turn trace.", -│ │ "section": "Runtime context", -│ │ "title": "Eve instrumentation", -│ │ "url": "https://eve.dev/docs/guides/instrumentation" -│ │ } -│ │ metadata: { -│ │ "eve.session_id": "", -│ │ "scenario": "eve-instrumentation", -│ │ "testRunId": "" -│ │ } -│ │ context: { -│ │ "caller_filename": "", -│ │ "caller_functionname": "", -│ │ "caller_lineno": 0 -│ │ } -│ └── eve.step [llm] -│ input: [ -│ { -│ "content": "Instructions (instructions)\nYou are a deterministic fixture agent for Braintrust Eve instrumentation tests.\n\nFor every user task, follow these steps exactly:\n\n1. Your first response MUST contain exactly these two tool calls and no text:\n - researcher with only this argument: {\"message\":\"\"}.\n Copy the current user message verbatim, including words such as \"again\";\n never reuse a message from an earlier turn. Do not pass outputSchema.\n - read with only this argument:\n {\"url\":\"https://eve.dev/docs/guides/instrumentation\"}.\n Emit both calls simultaneously in the same response, with researcher first\n and read second, so Eve executes them as one parallel batch. The calls are\n independent. NEVER call one by itself, wait for its result, or emit either\n call in a separate response.\n2. If a tool result says \"Started task\", your next response MUST be exactly one\n task_wait call with no arguments and no text.\n3. Only after both results are available, answer with a single sentence that\n starts with \"Final answer from read:\" and includes the researcher result, read\n title, URL, and read excerpt.\n\nTool execution\nA single tool or subagent call runs as one serial action. If you call multiple independent tools or subagents in one response, eve treats that batch as parallel work. Only batch work that is independent and does not rely on another call in the same response.", -│ "role": "system" -│ }, -│ { -│ "content": "Run the Braintrust Eve instrumentation e2e scenario", -│ "role": "user" -│ }, -│ { -│ "content": [ -│ { -│ "text": "\nOkay, the user wants me to run the Braintrust Eve instrumentation end-to-end scenario. Let me start by understanding what that entails. The user mentioned \"e2e scenario,\" which usually stands for end-to-end testing. So, they probably want to simulate or execute a complete workflow within the Eve instrumentation setup.\n\nFirst, I need to check the available tools. The user provided a list of functions, including researcher, read, and others. The instructions say that for every user task, I must first make two tool calls: researcher with the user's message and read with the specific URL. \n\nThe user's message is \"Run the Braintrust Eve instrumentation e2e scenario.\" So, I should call the researcher function with this message. Then, the read function should access the documentation URL provided earlier: https://eve.dev/docs/guides/instrumentation. \n\nI need to make sure both calls are made in the same response, as per the instructions. No text, just the two tool calls. Then, if the tool results say \"Started task,\" I have to call task_wait. But since the user hasn't provided any results yet, I should just proceed with the initial calls.\n\nAfter both results are available, I need to answer with a single sentence starting with \"Final answer from read:\" including the researcher result, read title, URL, and excerpt. But right now, the first step is to generate the two tool calls.\n", -│ "type": "reasoning" -│ }, -│ { -│ "input": { -│ "message": "Run the Braintrust Eve instrumentation e2e scenario" -│ }, -│ "toolCallId": "", -│ "toolName": "researcher", -│ "type": "tool-call" -│ }, -│ { -│ "input": { -│ "url": "https://eve.dev/docs/guides/instrumentation" -│ }, -│ "toolCallId": "", -│ "toolName": "read", -│ "type": "tool-call" -│ } -│ ], -│ "role": "assistant" -│ }, -│ { -│ "content": [ -│ { -│ "output": { -│ "type": "json", -│ "value": { -│ "excerpt": "Eve hooks expose runtime stream events that Braintrust maps into a nested turn trace.", -│ "section": "Runtime context", -│ "title": "Eve instrumentation", -│ "url": "https://eve.dev/docs/guides/instrumentation" -│ } -│ }, -│ "toolCallId": "", -│ "toolName": "read", -│ "type": "tool-result" -│ } -│ ], -│ "role": "tool" -│ }, -│ { -│ "content": [ -│ { -│ "output": { -│ "type": "text", -│ "value": "Researcher result: Eve instrumentation https://eve.dev/docs/guides/instrumentation" -│ }, -│ "toolCallId": "", -│ "toolName": "researcher", -│ "type": "tool-result" -│ } -│ ], -│ "role": "tool" -│ } -│ ] -│ output: [ -│ { -│ "finish_reason": "stop", -│ "index": 0, -│ "message": { -│ "content": "Final answer from read: Eve instrumentation https://eve.dev/docs/guides/instrumentation - \"Eve hooks expose runtime stream events that Braintrust maps into a nested turn trace.\" (Eve instrumentation, https://eve.dev/docs/guides/instrumentation)", -│ "reasoning": [ -│ { -│ "content": "\nOkay, let me process what's happening here. The user asked to run the Braintrust Eve instrumentation e2e scenario. According to the instructions, I needed to make two initial tool calls: researcher with the user's message and read with the specific URL. Both were made, and now the results are in.\n\nThe researcher result mentions \"Eve instrumentation https://eve.dev/docs/guides/instrumentation,\" which seems like a confirmation that the research task was started. The read tool returned a snippet from the documentation, including the title, section, and an excerpt about Eve hooks mapping to a nested turn trace.\n\nNow, the next step is to compile this information into a final answer. The instructions specify that the answer must start with \"Final answer from read:\" and include the researcher result, the read title, URL, and excerpt. \n\nI need to ensure that all required elements are included. The researcher result is \"Eve instrumentation https://eve.dev/docs/guides/instrumentation,\" the title is \"Eve instrumentation,\" the URL is the same, and the excerpt is the provided text. \n\nI should structure the sentence correctly, making sure there's no markdown and that it's a single, coherent sentence. Let me check for any typos or missing parts. Everything seems to be in order. Time to put it all together.\n" -│ } -│ ], -│ "role": "assistant" -│ } -│ } -│ ] -│ metadata: { -│ "eve.session_id": "", -│ "scenario": "eve-instrumentation", -│ "testRunId": "" -│ } -│ metrics: { -│ "completion_tokens": 331, -│ "prompt_cache_creation_tokens": 0, -│ "prompt_cached_tokens": 0, -│ "prompt_tokens": 3826, -│ "tokens": 4157 -│ } -│ context: { -│ "caller_filename": "", -│ "caller_functionname": "", -│ "caller_lineno": 0 -│ } -└── eve.turn [task] - input: [ - { - "content": "Run the Braintrust Eve instrumentation e2e scenario again", - "role": "user" - } - ] - output: "Final answer from read: Eve instrumentation https://eve.dev/docs/guides/instrumentation - \"Eve hooks expose runtime stream events that Braintrust maps into a nested turn trace.\" (Eve instrumentation, https://eve.dev/docs/guides/instrumentation)" - metadata: { - "eve.session_id": "", - "scenario": "eve-instrumentation", - "testRunId": "" - } - metrics: { - "completion_tokens": 460, - "prompt_cache_creation_tokens": 0, - "prompt_cached_tokens": 0, - "prompt_tokens": 7588, - "tokens": 8048 - } - context: { - "caller_filename": "", - "caller_functionname": "", - "caller_lineno": 0 - } - ├── eve.step [llm] - │ input: [ - │ { - │ "content": "Instructions (instructions)\nYou are a deterministic fixture agent for Braintrust Eve instrumentation tests.\n\nFor every user task, follow these steps exactly:\n\n1. Your first response MUST contain exactly these two tool calls and no text:\n - researcher with only this argument: {\"message\":\"\"}.\n Copy the current user message verbatim, including words such as \"again\";\n never reuse a message from an earlier turn. Do not pass outputSchema.\n - read with only this argument:\n {\"url\":\"https://eve.dev/docs/guides/instrumentation\"}.\n Emit both calls simultaneously in the same response, with researcher first\n and read second, so Eve executes them as one parallel batch. The calls are\n independent. NEVER call one by itself, wait for its result, or emit either\n call in a separate response.\n2. If a tool result says \"Started task\", your next response MUST be exactly one\n task_wait call with no arguments and no text.\n3. Only after both results are available, answer with a single sentence that\n starts with \"Final answer from read:\" and includes the researcher result, read\n title, URL, and read excerpt.\n\nTool execution\nA single tool or subagent call runs as one serial action. If you call multiple independent tools or subagents in one response, eve treats that batch as parallel work. Only batch work that is independent and does not rely on another call in the same response.", - │ "role": "system" - │ }, - │ { - │ "content": "Run the Braintrust Eve instrumentation e2e scenario", - │ "role": "user" - │ }, - │ { - │ "content": [ - │ { - │ "text": "\nOkay, the user wants me to run the Braintrust Eve instrumentation end-to-end scenario. Let me start by understanding what that entails. The user mentioned \"e2e scenario,\" which usually stands for end-to-end testing. So, they probably want to simulate or execute a complete workflow within the Eve instrumentation setup.\n\nFirst, I need to check the available tools. The user provided a list of functions, including researcher, read, and others. The instructions say that for every user task, I must first make two tool calls: researcher with the user's message and read with the specific URL. \n\nThe user's message is \"Run the Braintrust Eve instrumentation e2e scenario.\" So, I should call the researcher function with this message. Then, the read function should access the documentation URL provided earlier: https://eve.dev/docs/guides/instrumentation. \n\nI need to make sure both calls are made in the same response, as per the instructions. No text, just the two tool calls. Then, if the tool results say \"Started task,\" I have to call task_wait. But since the user hasn't provided any results yet, I should just proceed with the initial calls.\n\nAfter both results are available, I need to answer with a single sentence starting with \"Final answer from read:\" including the researcher result, read title, URL, and excerpt. But right now, the first step is to generate the two tool calls.\n", - │ "type": "reasoning" - │ }, - │ { - │ "input": { - │ "message": "Run the Braintrust Eve instrumentation e2e scenario" - │ }, - │ "toolCallId": "", - │ "toolName": "researcher", - │ "type": "tool-call" - │ }, - │ { - │ "input": { - │ "url": "https://eve.dev/docs/guides/instrumentation" - │ }, - │ "toolCallId": "", - │ "toolName": "read", - │ "type": "tool-call" - │ } - │ ], - │ "role": "assistant" - │ }, - │ { - │ "content": [ - │ { - │ "output": { - │ "type": "json", - │ "value": { - │ "excerpt": "Eve hooks expose runtime stream events that Braintrust maps into a nested turn trace.", - │ "section": "Runtime context", - │ "title": "Eve instrumentation", - │ "url": "https://eve.dev/docs/guides/instrumentation" - │ } - │ }, - │ "toolCallId": "", - │ "toolName": "read", - │ "type": "tool-result" - │ } - │ ], - │ "role": "tool" - │ }, - │ { - │ "content": [ - │ { - │ "output": { - │ "type": "text", - │ "value": "Researcher result: Eve instrumentation https://eve.dev/docs/guides/instrumentation" - │ }, - │ "toolCallId": "", - │ "toolName": "researcher", - │ "type": "tool-result" - │ } - │ ], - │ "role": "tool" - │ }, - │ { - │ "content": [ - │ { - │ "text": "\nOkay, let me process what's happening here. The user asked to run the Braintrust Eve instrumentation e2e scenario. According to the instructions, I needed to make two initial tool calls: researcher with the user's message and read with the specific URL. Both were made, and now the results are in.\n\nThe researcher result mentions \"Eve instrumentation https://eve.dev/docs/guides/instrumentation,\" which seems like a confirmation that the research task was started. The read tool returned a snippet from the documentation, including the title, section, and an excerpt about Eve hooks mapping to a nested turn trace.\n\nNow, the next step is to compile this information into a final answer. The instructions specify that the answer must start with \"Final answer from read:\" and include the researcher result, the read title, URL, and excerpt. \n\nI need to ensure that all required elements are included. The researcher result is \"Eve instrumentation https://eve.dev/docs/guides/instrumentation,\" the title is \"Eve instrumentation,\" the URL is the same, and the excerpt is the provided text. \n\nI should structure the sentence correctly, making sure there's no markdown and that it's a single, coherent sentence. Let me check for any typos or missing parts. Everything seems to be in order. Time to put it all together.\n", - │ "type": "reasoning" - │ }, - │ { - │ "text": "Final answer from read: Eve instrumentation https://eve.dev/docs/guides/instrumentation - \"Eve hooks expose runtime stream events that Braintrust maps into a nested turn trace.\" (Eve instrumentation, https://eve.dev/docs/guides/instrumentation)", - │ "type": "text" - │ } - │ ], - │ "role": "assistant" - │ }, - │ { - │ "content": "Run the Braintrust Eve instrumentation e2e scenario again", - │ "role": "user" - │ } - │ ] - │ output: [ - │ { - │ "finish_reason": "tool_calls", - │ "index": 0, - │ "message": { - │ "content": null, - │ "reasoning": [ - │ { - │ "content": "\nOkay, the user wants me to run the Braintrust Eve instrumentation e2e scenario again. Let me check the previous interactions to make sure I follow the correct steps.\n\nIn the history, the user first asked to run the scenario, and I called the researcher and read functions. The read function fetched the documentation page, and the researcher result was provided. Then the user asked again, and I followed the same steps. Now, the user is asking \"again,\" so I need to repeat the process.\n\nThe instructions say that for every user task, I must first call researcher with the full user message and read with the specific URL. Since the user mentioned \"again,\" the message should be exactly \"Run the Braintrust Eve instrumentation e2e scenario again.\" I need to make sure the arguments are correctly formatted as JSON objects within the tool_call tags. No text should be added, just the two tool calls. Then, if the tool results say \"Started task,\" I should call task_wait. Once both results are available, provide the final answer with the required details. Let me structure the response correctly.\n" - │ } - │ ], - │ "role": "assistant", - │ "tool_calls": [ - │ { - │ "function": { - │ "arguments": "{\"message\":\"Run the Braintrust Eve instrumentation e2e scenario again\"}", - │ "name": "researcher" - │ }, - │ "id": "", - │ "type": "function" - │ }, - │ { - │ "function": { - │ "arguments": "{\"url\":\"https://eve.dev/docs/guides/instrumentation\"}", - │ "name": "read" - │ }, - │ "id": "", - │ "type": "function" - │ } - │ ] - │ } - │ } - │ ] - │ metadata: { - │ "eve.session_id": "", - │ "scenario": "eve-instrumentation", - │ "testRunId": "" - │ } - │ metrics: { - │ "completion_tokens": 283, - │ "prompt_cache_creation_tokens": 0, - │ "prompt_cached_tokens": 0, - │ "prompt_tokens": 3606, - │ "tokens": 3889 - │ } - │ context: { - │ "caller_filename": "", - │ "caller_functionname": "", - │ "caller_lineno": 0 - │ } - ├── researcher [tool] - │ input: { - │ "message": "Run the Braintrust Eve instrumentation e2e scenario again" - │ } - │ output: "Researcher result: The Braintrust Eve instrumentation documentation can be found here: [Eve instrumentation](https://eve.dev/docs/guides/instrumentation)." - │ metadata: { - │ "eve.session_id": "", - │ "scenario": "eve-instrumentation", - │ "testRunId": "" - │ } - │ context: { - │ "caller_filename": "", - │ "caller_functionname": "", - │ "caller_lineno": 0 - │ } - │ └── eve.turn [task] - │ input: [ - │ { - │ "content": "You are the subagent \"researcher\".\nDescription: Research the Eve instrumentation documentation before the parent reads it.\n\nThe caller delegated the following task to you. Complete it and return the final result directly.\n\nCaller message:\nRun the Braintrust Eve instrumentation e2e scenario again", - │ "role": "user" - │ } - │ ] - │ output: "Researcher result: The Braintrust Eve instrumentation documentation can be found here: [Eve instrumentation](https://eve.dev/docs/guides/instrumentation)." - │ metadata: { - │ "eve.session_id": "", - │ "model": "qwen/qwen3-30b-a3b", - │ "provider": "openrouter", - │ "scenario": "eve-instrumentation", - │ "testRunId": "" - │ } - │ metrics: { - │ "completion_tokens": 317, - │ "prompt_cache_creation_tokens": 0, - │ "prompt_cached_tokens": 0, - │ "prompt_tokens": 6293, - │ "tokens": 6610 - │ } - │ context: { - │ "caller_filename": "", - │ "caller_functionname": "", - │ "caller_lineno": 0 - │ } - │ ├── eve.step [llm] - │ │ input: [ - │ │ { - │ │ "content": "Instructions (instructions)\nYou are the deterministic researcher subagent for Braintrust Eve instrumentation tests.\n\nFor every task:\n\n1. Call the search tool exactly once with the full task as the query.\n2. After the search result is available, answer with a single sentence that starts\n with \"Researcher result:\" and includes the search title and URL.\n\nTool execution\nA single tool or subagent call runs as one serial action. If you call multiple independent tools or subagents in one response, eve treats that batch as parallel work. Only batch work that is independent and does not rely on another call in the same response.", - │ │ "role": "system" - │ │ }, - │ │ { - │ │ "content": "You are the subagent \"researcher\".\nDescription: Research the Eve instrumentation documentation before the parent reads it.\n\nThe caller delegated the following task to you. Complete it and return the final result directly.\n\nCaller message:\nRun the Braintrust Eve instrumentation e2e scenario again", - │ │ "role": "user" - │ │ } - │ │ ] - │ │ output: [ - │ │ { - │ │ "finish_reason": "tool_calls", - │ │ "index": 0, - │ │ "message": { - │ │ "content": null, - │ │ "reasoning": [ - │ │ { - │ │ "content": "\nOkay, the user wants me to run the Braintrust Eve instrumentation end-to-end scenario again. Since I'm the researcher subagent, my job is to look up the documentation first. Let me check the tools available. There's a 'search' function that queries the Eve documentation. I need to use that. The task is to \"Run the Braintrust Eve instrumentation e2e scenario again,\" so I'll use that exact query for the search. I'll make sure to call the search tool once with the full task as the query. Then, once I get the results, I'll format the answer with the title and URL from the search result. Let me structure the tool call properly.\n" - │ │ } - │ │ ], - │ │ "role": "assistant", - │ │ "tool_calls": [ - │ │ { - │ │ "function": { - │ │ "arguments": "{\"query\":\"Run the Braintrust Eve instrumentation e2e scenario again\"}", - │ │ "name": "search" - │ │ }, - │ │ "id": "", - │ │ "type": "function" - │ │ } - │ │ ] - │ │ } - │ │ } - │ │ ] - │ │ metadata: { - │ │ "eve.session_id": "", - │ │ "model": "qwen/qwen3-30b-a3b", - │ │ "provider": "openrouter", - │ │ "scenario": "eve-instrumentation", - │ │ "testRunId": "" - │ │ } - │ │ metrics: { - │ │ "completion_tokens": 173, - │ │ "prompt_cache_creation_tokens": 0, - │ │ "prompt_cached_tokens": 0, - │ │ "prompt_tokens": 3037, - │ │ "tokens": 3210 - │ │ } - │ │ context: { - │ │ "caller_filename": "", - │ │ "caller_functionname": "", - │ │ "caller_lineno": 0 - │ │ } - │ ├── search [tool] - │ │ input: { - │ │ "query": "Run the Braintrust Eve instrumentation e2e scenario again" - │ │ } - │ │ output: { - │ │ "query": "Run the Braintrust Eve instrumentation e2e scenario again", - │ │ "title": "Eve instrumentation", - │ │ "url": "https://eve.dev/docs/guides/instrumentation" - │ │ } - │ │ metadata: { - │ │ "eve.session_id": "", - │ │ "scenario": "eve-instrumentation", - │ │ "testRunId": "" - │ │ } - │ │ context: { - │ │ "caller_filename": "", - │ │ "caller_functionname": "", - │ │ "caller_lineno": 0 - │ │ } - │ └── eve.step [llm] - │ input: [ - │ { - │ "content": "Instructions (instructions)\nYou are the deterministic researcher subagent for Braintrust Eve instrumentation tests.\n\nFor every task:\n\n1. Call the search tool exactly once with the full task as the query.\n2. After the search result is available, answer with a single sentence that starts\n with \"Researcher result:\" and includes the search title and URL.\n\nTool execution\nA single tool or subagent call runs as one serial action. If you call multiple independent tools or subagents in one response, eve treats that batch as parallel work. Only batch work that is independent and does not rely on another call in the same response.", - │ "role": "system" - │ }, - │ { - │ "content": "You are the subagent \"researcher\".\nDescription: Research the Eve instrumentation documentation before the parent reads it.\n\nThe caller delegated the following task to you. Complete it and return the final result directly.\n\nCaller message:\nRun the Braintrust Eve instrumentation e2e scenario again", - │ "role": "user" - │ }, - │ { - │ "content": [ - │ { - │ "text": "\nOkay, the user wants me to run the Braintrust Eve instrumentation end-to-end scenario again. Since I'm the researcher subagent, my job is to look up the documentation first. Let me check the tools available. There's a 'search' function that queries the Eve documentation. I need to use that. The task is to \"Run the Braintrust Eve instrumentation e2e scenario again,\" so I'll use that exact query for the search. I'll make sure to call the search tool once with the full task as the query. Then, once I get the results, I'll format the answer with the title and URL from the search result. Let me structure the tool call properly.\n", - │ "type": "reasoning" - │ }, - │ { - │ "input": { - │ "query": "Run the Braintrust Eve instrumentation e2e scenario again" - │ }, - │ "toolCallId": "", - │ "toolName": "search", - │ "type": "tool-call" - │ } - │ ], - │ "role": "assistant" - │ }, - │ { - │ "content": [ - │ { - │ "output": { - │ "type": "json", - │ "value": { - │ "query": "Run the Braintrust Eve instrumentation e2e scenario again", - │ "title": "Eve instrumentation", - │ "url": "https://eve.dev/docs/guides/instrumentation" - │ } - │ }, - │ "toolCallId": "", - │ "toolName": "search", - │ "type": "tool-result" - │ } - │ ], - │ "role": "tool" - │ } - │ ] - │ output: [ - │ { - │ "finish_reason": "stop", - │ "index": 0, - │ "message": { - │ "content": "Researcher result: The Braintrust Eve instrumentation documentation can be found here: [Eve instrumentation](https://eve.dev/docs/guides/instrumentation).", - │ "reasoning": [ - │ { - │ "content": "\nOkay, the user asked me to run the Braintrust Eve instrumentation end-to-end scenario again. As the researcher subagent, I need to check the documentation first. I used the search tool with the exact query they provided. The result came back with the title \"Eve instrumentation\" and a URL. Now, I should present that information clearly. The instructions say to start the answer with \"Researcher result:\" and include the title and URL. I need to make sure it's a single sentence. Let me format it correctly.\n" - │ } - │ ], - │ "role": "assistant" - │ } - │ } - │ ] - │ metadata: { - │ "eve.session_id": "", - │ "model": "qwen/qwen3-30b-a3b", - │ "provider": "openrouter", - │ "scenario": "eve-instrumentation", - │ "testRunId": "" - │ } - │ metrics: { - │ "completion_tokens": 144, - │ "prompt_cache_creation_tokens": 0, - │ "prompt_cached_tokens": 0, - │ "prompt_tokens": 3256, - │ "tokens": 3400 - │ } - │ context: { - │ "caller_filename": "", - │ "caller_functionname": "", - │ "caller_lineno": 0 - │ } - ├── read [tool] - │ input: { - │ "url": "https://eve.dev/docs/guides/instrumentation" - │ } - │ output: { - │ "excerpt": "Eve hooks expose runtime stream events that Braintrust maps into a nested turn trace.", - │ "section": "Runtime context", - │ "title": "Eve instrumentation", - │ "url": "https://eve.dev/docs/guides/instrumentation" - │ } - │ metadata: { - │ "eve.session_id": "", - │ "scenario": "eve-instrumentation", - │ "testRunId": "" - │ } - │ context: { - │ "caller_filename": "", - │ "caller_functionname": "", - │ "caller_lineno": 0 - │ } - └── eve.step [llm] - input: [ - { - "content": "Instructions (instructions)\nYou are a deterministic fixture agent for Braintrust Eve instrumentation tests.\n\nFor every user task, follow these steps exactly:\n\n1. Your first response MUST contain exactly these two tool calls and no text:\n - researcher with only this argument: {\"message\":\"\"}.\n Copy the current user message verbatim, including words such as \"again\";\n never reuse a message from an earlier turn. Do not pass outputSchema.\n - read with only this argument:\n {\"url\":\"https://eve.dev/docs/guides/instrumentation\"}.\n Emit both calls simultaneously in the same response, with researcher first\n and read second, so Eve executes them as one parallel batch. The calls are\n independent. NEVER call one by itself, wait for its result, or emit either\n call in a separate response.\n2. If a tool result says \"Started task\", your next response MUST be exactly one\n task_wait call with no arguments and no text.\n3. Only after both results are available, answer with a single sentence that\n starts with \"Final answer from read:\" and includes the researcher result, read\n title, URL, and read excerpt.\n\nTool execution\nA single tool or subagent call runs as one serial action. If you call multiple independent tools or subagents in one response, eve treats that batch as parallel work. Only batch work that is independent and does not rely on another call in the same response.", - "role": "system" - }, - { - "content": "Run the Braintrust Eve instrumentation e2e scenario", - "role": "user" - }, - { - "content": [ - { - "text": "\nOkay, the user wants me to run the Braintrust Eve instrumentation end-to-end scenario. Let me start by understanding what that entails. The user mentioned \"e2e scenario,\" which usually stands for end-to-end testing. So, they probably want to simulate or execute a complete workflow within the Eve instrumentation setup.\n\nFirst, I need to check the available tools. The user provided a list of functions, including researcher, read, and others. The instructions say that for every user task, I must first make two tool calls: researcher with the user's message and read with the specific URL. \n\nThe user's message is \"Run the Braintrust Eve instrumentation e2e scenario.\" So, I should call the researcher function with this message. Then, the read function should access the documentation URL provided earlier: https://eve.dev/docs/guides/instrumentation. \n\nI need to make sure both calls are made in the same response, as per the instructions. No text, just the two tool calls. Then, if the tool results say \"Started task,\" I have to call task_wait. But since the user hasn't provided any results yet, I should just proceed with the initial calls.\n\nAfter both results are available, I need to answer with a single sentence starting with \"Final answer from read:\" including the researcher result, read title, URL, and excerpt. But right now, the first step is to generate the two tool calls.\n", - "type": "reasoning" - }, - { - "input": { - "message": "Run the Braintrust Eve instrumentation e2e scenario" - }, - "toolCallId": "", - "toolName": "researcher", - "type": "tool-call" - }, - { - "input": { - "url": "https://eve.dev/docs/guides/instrumentation" - }, - "toolCallId": "", - "toolName": "read", - "type": "tool-call" - } - ], - "role": "assistant" - }, - { - "content": [ - { - "output": { - "type": "json", - "value": { - "excerpt": "Eve hooks expose runtime stream events that Braintrust maps into a nested turn trace.", - "section": "Runtime context", - "title": "Eve instrumentation", - "url": "https://eve.dev/docs/guides/instrumentation" - } - }, - "toolCallId": "", - "toolName": "read", - "type": "tool-result" - } - ], - "role": "tool" - }, - { - "content": [ - { - "output": { - "type": "text", - "value": "Researcher result: Eve instrumentation https://eve.dev/docs/guides/instrumentation" - }, - "toolCallId": "", - "toolName": "researcher", - "type": "tool-result" - } - ], - "role": "tool" - }, - { - "content": [ - { - "text": "\nOkay, let me process what's happening here. The user asked to run the Braintrust Eve instrumentation e2e scenario. According to the instructions, I needed to make two initial tool calls: researcher with the user's message and read with the specific URL. Both were made, and now the results are in.\n\nThe researcher result mentions \"Eve instrumentation https://eve.dev/docs/guides/instrumentation,\" which seems like a confirmation that the research task was started. The read tool returned a snippet from the documentation, including the title, section, and an excerpt about Eve hooks mapping to a nested turn trace.\n\nNow, the next step is to compile this information into a final answer. The instructions specify that the answer must start with \"Final answer from read:\" and include the researcher result, the read title, URL, and excerpt. \n\nI need to ensure that all required elements are included. The researcher result is \"Eve instrumentation https://eve.dev/docs/guides/instrumentation,\" the title is \"Eve instrumentation,\" the URL is the same, and the excerpt is the provided text. \n\nI should structure the sentence correctly, making sure there's no markdown and that it's a single, coherent sentence. Let me check for any typos or missing parts. Everything seems to be in order. Time to put it all together.\n", - "type": "reasoning" - }, - { - "text": "Final answer from read: Eve instrumentation https://eve.dev/docs/guides/instrumentation - \"Eve hooks expose runtime stream events that Braintrust maps into a nested turn trace.\" (Eve instrumentation, https://eve.dev/docs/guides/instrumentation)", - "type": "text" - } - ], - "role": "assistant" - }, - { - "content": "Run the Braintrust Eve instrumentation e2e scenario again", - "role": "user" - }, - { - "content": [ - { - "text": "\nOkay, the user wants me to run the Braintrust Eve instrumentation e2e scenario again. Let me check the previous interactions to make sure I follow the correct steps.\n\nIn the history, the user first asked to run the scenario, and I called the researcher and read functions. The read function fetched the documentation page, and the researcher result was provided. Then the user asked again, and I followed the same steps. Now, the user is asking \"again,\" so I need to repeat the process.\n\nThe instructions say that for every user task, I must first call researcher with the full user message and read with the specific URL. Since the user mentioned \"again,\" the message should be exactly \"Run the Braintrust Eve instrumentation e2e scenario again.\" I need to make sure the arguments are correctly formatted as JSON objects within the tool_call tags. No text should be added, just the two tool calls. Then, if the tool results say \"Started task,\" I should call task_wait. Once both results are available, provide the final answer with the required details. Let me structure the response correctly.\n", - "type": "reasoning" - }, - { - "input": { - "message": "Run the Braintrust Eve instrumentation e2e scenario again" - }, - "toolCallId": "", - "toolName": "researcher", - "type": "tool-call" - }, - { - "input": { - "url": "https://eve.dev/docs/guides/instrumentation" - }, - "toolCallId": "", - "toolName": "read", - "type": "tool-call" - } - ], - "role": "assistant" - }, - { - "content": [ - { - "output": { - "type": "json", - "value": { - "excerpt": "Eve hooks expose runtime stream events that Braintrust maps into a nested turn trace.", - "section": "Runtime context", - "title": "Eve instrumentation", - "url": "https://eve.dev/docs/guides/instrumentation" - } - }, - "toolCallId": "", - "toolName": "read", - "type": "tool-result" - } - ], - "role": "tool" - }, - { - "content": [ - { - "output": { - "type": "text", - "value": "Researcher result: The Braintrust Eve instrumentation documentation can be found here: [Eve instrumentation](https://eve.dev/docs/guides/instrumentation)." - }, - "toolCallId": "", - "toolName": "researcher", - "type": "tool-result" - } - ], - "role": "tool" - } - ] - output: [ - { - "finish_reason": "stop", - "index": 0, - "message": { - "content": "Final answer from read: Eve instrumentation https://eve.dev/docs/guides/instrumentation - \"Eve hooks expose runtime stream events that Braintrust maps into a nested turn trace.\" (Eve instrumentation, https://eve.dev/docs/guides/instrumentation)", - "reasoning": [ - { - "content": "\nOkay, the user asked to run the scenario again, so I need to follow the same steps as before. First, I called the researcher with the message \"Run the Braintrust Eve instrumentation e2e scenario again\" and the read function with the URL. The read result came back with the same excerpt as before. Now, I need to check if there's a need to call task_wait, but since the researcher result doesn't mention \"Started task,\" maybe not. The final answer should include the researcher result, the title, URL, and excerpt. Let me structure that properly.\n" - } - ], - "role": "assistant" - } - } - ] - metadata: { - "eve.session_id": "", - "scenario": "eve-instrumentation", - "testRunId": "" - } - metrics: { - "completion_tokens": 177, - "prompt_cache_creation_tokens": 0, - "prompt_cached_tokens": 0, - "prompt_tokens": 3982, - "tokens": 4159 - } - context: { - "caller_filename": "", - "caller_functionname": "", - "caller_lineno": 0 - } diff --git a/e2e/scenarios/eve-instrumentation/agent/hooks/braintrust.ts b/e2e/scenarios/eve-instrumentation/agent/hooks/braintrust.ts deleted file mode 100644 index 40278f1dc..000000000 --- a/e2e/scenarios/eve-instrumentation/agent/hooks/braintrust.ts +++ /dev/null @@ -1,17 +0,0 @@ -import { braintrustEveHook } from "braintrust"; -import { defineState } from "eve/context"; -import { defineHook } from "eve/hooks"; - -export default defineHook( - process.env.EVE_INSTRUMENTATION_PROVIDER === "1" - ? { events: {} } - : braintrustEveHook({ - defineState, - metadata: { - scenario: "eve-instrumentation", - ...(process.env.BRAINTRUST_E2E_RUN_ID - ? { testRunId: process.env.BRAINTRUST_E2E_RUN_ID } - : {}), - }, - }), -); diff --git a/e2e/scenarios/eve-instrumentation/agent/subagents/researcher/hooks/braintrust.ts b/e2e/scenarios/eve-instrumentation/agent/subagents/researcher/hooks/braintrust.ts deleted file mode 100644 index 40278f1dc..000000000 --- a/e2e/scenarios/eve-instrumentation/agent/subagents/researcher/hooks/braintrust.ts +++ /dev/null @@ -1,17 +0,0 @@ -import { braintrustEveHook } from "braintrust"; -import { defineState } from "eve/context"; -import { defineHook } from "eve/hooks"; - -export default defineHook( - process.env.EVE_INSTRUMENTATION_PROVIDER === "1" - ? { events: {} } - : braintrustEveHook({ - defineState, - metadata: { - scenario: "eve-instrumentation", - ...(process.env.BRAINTRUST_E2E_RUN_ID - ? { testRunId: process.env.BRAINTRUST_E2E_RUN_ID } - : {}), - }, - }), -); diff --git a/e2e/scenarios/eve-instrumentation/config/legacy-instrumentation.ts b/e2e/scenarios/eve-instrumentation/config/legacy-instrumentation.ts deleted file mode 100644 index a3f8b3841..000000000 --- a/e2e/scenarios/eve-instrumentation/config/legacy-instrumentation.ts +++ /dev/null @@ -1,11 +0,0 @@ -import { braintrustEveInstrumentation, initLogger } from "braintrust"; -import { defineState } from "eve/context"; - -export default braintrustEveInstrumentation({ - defineState, - setup: ({ agentName }) => { - initLogger({ - projectName: process.env.BRAINTRUST_E2E_PROJECT_NAME || agentName, - }); - }, -}); diff --git a/e2e/scenarios/eve-instrumentation/config/provider-instrumentation.ts b/e2e/scenarios/eve-instrumentation/config/provider-instrumentation.ts index a16edc43b..d68116ebe 100644 --- a/e2e/scenarios/eve-instrumentation/config/provider-instrumentation.ts +++ b/e2e/scenarios/eve-instrumentation/config/provider-instrumentation.ts @@ -1,4 +1,5 @@ -import { braintrustEveInstrumentation, initLogger } from "braintrust"; +import { initLogger } from "braintrust"; +import { braintrustEveInstrumentation } from "braintrust/instrumentation"; export default braintrustEveInstrumentation({ metadata: { diff --git a/e2e/scenarios/eve-instrumentation/config/typecheck.ts b/e2e/scenarios/eve-instrumentation/config/typecheck.ts index 006c00edf..70b64c75a 100644 --- a/e2e/scenarios/eve-instrumentation/config/typecheck.ts +++ b/e2e/scenarios/eve-instrumentation/config/typecheck.ts @@ -1,8 +1,4 @@ -import { braintrustEveHook, braintrustEveInstrumentation } from "braintrust"; -import { defineState } from "eve/context"; -import { defineHook } from "eve/hooks"; +import { braintrustEveInstrumentation } from "braintrust/instrumentation"; import { defineInstrumentation } from "eve/instrumentation"; -defineHook(braintrustEveHook({ defineState })); -defineInstrumentation(braintrustEveInstrumentation({ defineState })); defineInstrumentation(braintrustEveInstrumentation({})); diff --git a/e2e/scenarios/eve-instrumentation/package.json b/e2e/scenarios/eve-instrumentation/package.json index 463cef24a..e91b2ec11 100644 --- a/e2e/scenarios/eve-instrumentation/package.json +++ b/e2e/scenarios/eve-instrumentation/package.json @@ -16,7 +16,6 @@ "@openrouter/ai-sdk-provider": "3.0.0", "ai": "7.0.137", "eve": "0.52.2", - "eve-v0": "npm:eve@0.22.1", "eve-v0-latest-pinned": "npm:eve@0.37.1", "eve-v0-provider": "npm:eve@0.34.0", "eve-v0-70": "npm:eve@0.70.0", diff --git a/e2e/scenarios/eve-instrumentation/pnpm-lock.yaml b/e2e/scenarios/eve-instrumentation/pnpm-lock.yaml index 39cebcd56..051956c27 100644 --- a/e2e/scenarios/eve-instrumentation/pnpm-lock.yaml +++ b/e2e/scenarios/eve-instrumentation/pnpm-lock.yaml @@ -21,9 +21,6 @@ importers: eve: specifier: 0.52.2 version: 0.52.2(ai@7.0.137(zod@4.3.6)) - eve-v0: - specifier: npm:eve@0.22.1 - version: eve@0.22.1(ai@7.0.137(zod@4.3.6)) eve-v0-70: specifier: npm:eve@0.70.0 version: eve@0.70.0(ai@7.0.137(zod@4.3.6)) @@ -370,26 +367,6 @@ packages: resolution: {integrity: sha512-2iDP2DfheAMMAKeXBggEuFmpSq+1Xs6wQoHba1g65pZFXlC3VHD1O6/tgVzS6GQLv+P2koPKZPKgKhXkigNBdg==} hasBin: true - eve@0.22.1: - resolution: {integrity: sha512-4nqy8SEJpS94fM8sxd0Or+irvf/CmS0qENxouKyyegst3Qtn06xZbuSEoI1p7i4wB6sprr95udXh7Ulct6ABrA==} - engines: {node: '>=24'} - hasBin: true - peerDependencies: - '@opentelemetry/api': ^1.0.0 - ai: ^7.0.0 - braintrust: ^3.0.0 - just-bash: ^3.0.0 - microsandbox: ^0.5.0 - peerDependenciesMeta: - '@opentelemetry/api': - optional: true - braintrust: - optional: true - just-bash: - optional: true - microsandbox: - optional: true - eve@0.34.0: resolution: {integrity: sha512-zgrYIbm8u78Furxfdyzaknf5AxFCzwmTGfLEvgax00BUNXu3eebaos8igjYQyM4q5JAxlWAoZCJuItE6r2S0cw==} engines: {node: '>=24'} @@ -950,50 +927,6 @@ snapshots: httpxy: 0.5.5 srvx: 1.0.4 - eve@0.22.1(ai@7.0.137(zod@4.3.6)): - dependencies: - ai: 7.0.137(zod@4.3.6) - nitro: 3.0.260610-beta - transitivePeerDependencies: - - '@azure/app-configuration' - - '@azure/cosmos' - - '@azure/data-tables' - - '@azure/identity' - - '@azure/keyvault-secrets' - - '@azure/storage-blob' - - '@capacitor/preferences' - - '@deno/kv' - - '@electric-sql/pglite' - - '@libsql/client' - - '@netlify/blobs' - - '@netlify/runtime' - - '@planetscale/database' - - '@upstash/redis' - - '@vercel/blob' - - '@vercel/functions' - - '@vercel/kv' - - '@vercel/queue' - - aws4fetch - - better-sqlite3 - - chokidar - - dotenv - - drizzle-orm - - giget - - idb-keyval - - ioredis - - jiti - - lru-cache - - miniflare - - mongodb - - mysql2 - - rollup - - sqlite3 - - uploadthing - - vite - - wrangler - - xml2js - - zephyr-agent - eve@0.34.0(ai@7.0.137(zod@4.3.6)): dependencies: ai: 7.0.137(zod@4.3.6) diff --git a/e2e/scenarios/eve-instrumentation/scenario.test.ts b/e2e/scenarios/eve-instrumentation/scenario.test.ts index 85cde7423..db47f4ab9 100644 --- a/e2e/scenarios/eve-instrumentation/scenario.test.ts +++ b/e2e/scenarios/eve-instrumentation/scenario.test.ts @@ -1,4 +1,4 @@ -import { beforeAll, describe, expect, test } from "vitest"; +import { beforeAll, describe, expect, test } from "vite-plus/test"; import { resolveFileSnapshotPath } from "../../helpers/file-snapshot"; import type { CapturedLogEvent, @@ -25,13 +25,6 @@ const scenarioDir = await prepareScenarioDir({ }); const eveScenarios = await Promise.all( [ - { - dependencyName: "eve-v0", - label: "v0 pinned", - provider: false, - tasks: false, - variantKey: "eve-v0", - }, { dependencyName: "eve-v0-provider", label: "v0 provider minimum", @@ -78,7 +71,7 @@ const eveScenarios = await Promise.all( const TIMEOUT_MS = process.env.BRAINTRUST_E2E_CASSETTE_MODE === "record" ? 300_000 : 120_000; -describe.sequential("eve instrumentation variants", () => { +describe("eve instrumentation variants", { concurrent: false }, () => { for (const scenario of eveScenarios) { describe(`${scenario.label} (${scenario.version})`, () => { // Eve 0.76 moved built-in tools into the eve namespace. @@ -207,15 +200,9 @@ describe.sequential("eve instrumentation variants", () => { }, ]); expect(root?.output).toContain("Final answer from read"); - if (scenario.provider) { - expect(root?.metrics).not.toHaveProperty("completion_tokens"); - expect(root?.metrics).not.toHaveProperty("prompt_tokens"); - expect(root?.metrics).not.toHaveProperty("tokens"); - } else { - expect(root?.metrics?.completion_tokens).toEqual(expect.any(Number)); - expect(root?.metrics?.prompt_tokens).toEqual(expect.any(Number)); - expect(root?.metrics?.tokens).toEqual(expect.any(Number)); - } + expect(root?.metrics?.completion_tokens).toEqual(expect.any(Number)); + expect(root?.metrics?.prompt_tokens).toEqual(expect.any(Number)); + expect(root?.metrics?.tokens).toEqual(expect.any(Number)); // Eve 0.70+ runs the researcher as a task, so the parent waits for its // result in an extra step before answering. diff --git a/e2e/scenarios/eve-instrumentation/scenario.ts b/e2e/scenarios/eve-instrumentation/scenario.ts index 4229b729b..35464ae53 100644 --- a/e2e/scenarios/eve-instrumentation/scenario.ts +++ b/e2e/scenarios/eve-instrumentation/scenario.ts @@ -67,28 +67,21 @@ async function main() { recursive: true, }); await rm(path.join(agentDir, "channels", "eve.ts"), { force: true }); - if (process.env.EVE_INSTRUMENTATION_PROVIDER === "1") { - // Eve 0.62 made instrumentation providers stable and rejects the flag. - const [major = 0, minor = 0] = evePackage.version.split(".").map(Number); - if (major === 0 && minor < 62) { - process.env.EVE_EXPERIMENTAL_INSTRUMENTATION_PROVIDERS = "1"; - } - await mkdir(path.join(agentDir, "instrumentation"), { recursive: true }); - await mkdir(path.join(agentDir, "channels"), { recursive: true }); - await copyFile( - path.join(process.cwd(), "config", "provider-instrumentation.ts"), - path.join(agentDir, "instrumentation", "braintrust.ts"), - ); - await copyFile( - path.join(process.cwd(), "config", "provider-eve-channel.ts"), - path.join(agentDir, "channels", "eve.ts"), - ); - } else { - await copyFile( - path.join(process.cwd(), "config", "legacy-instrumentation.ts"), - path.join(agentDir, "instrumentation.ts"), - ); + // Eve 0.62 made instrumentation providers stable and rejects the flag. + const [major = 0, minor = 0] = evePackage.version.split(".").map(Number); + if (major === 0 && minor < 62) { + process.env.EVE_EXPERIMENTAL_INSTRUMENTATION_PROVIDERS = "1"; } + await mkdir(path.join(agentDir, "instrumentation"), { recursive: true }); + await mkdir(path.join(agentDir, "channels"), { recursive: true }); + await copyFile( + path.join(process.cwd(), "config", "provider-instrumentation.ts"), + path.join(agentDir, "instrumentation", "braintrust.ts"), + ); + await copyFile( + path.join(process.cwd(), "config", "provider-eve-channel.ts"), + path.join(agentDir, "channels", "eve.ts"), + ); const eveBin = path.join(evePackageDir, eveBinPath); diff --git a/e2e/scenarios/flue-instrumentation/.flue/app.mjs b/e2e/scenarios/flue-instrumentation/.flue/app.mjs index 900402277..7e22bc269 100644 --- a/e2e/scenarios/flue-instrumentation/.flue/app.mjs +++ b/e2e/scenarios/flue-instrumentation/.flue/app.mjs @@ -1,12 +1,13 @@ -import { mkdir, writeFile } from "node:fs/promises"; -import { dirname } from "node:path"; import { flush, initLogger } from "braintrust"; +import { braintrustFlueInstrumentation } from "braintrust/instrumentation"; +import { Hono } from "hono"; const runtimePackageName = process.env.FLUE_RUNTIME_PACKAGE_NAME ?? "@flue/runtime"; -const { configureProvider, flue, observe } = await import( - `${runtimePackageName}/app` -); +const [{ instrument, registerProvider }, { flue }] = await Promise.all([ + import(runtimePackageName), + import(`${runtimePackageName}/routing`), +]); function projectName() { const configured = process.env.BRAINTRUST_E2E_PROJECT_NAME; @@ -19,84 +20,24 @@ function projectName() { initLogger({ projectName: projectName() }); -const exitProcess = process.exit.bind(process); -if (process.env.FLUE_E2E_FLUSH_FILE) { - let isExiting = false; - process.exit = (code) => { - if (isExiting) { - return exitProcess(code); - } - isExiting = true; - const keepAlive = setTimeout(() => {}, 30_000); - void flushBeforeExit() - .catch((error) => { - console.error(error); - }) - .finally(() => { - clearTimeout(keepAlive); - exitProcess(code); - }); - }; -} - -if (process.env.FLUE_E2E_EXPLICIT_OBSERVE === "1") { - const { braintrustFlueObserver } = await import("braintrust"); - observe(braintrustFlueObserver); -} +instrument(braintrustFlueInstrumentation()); const openAIBaseUrl = process.env.OPENAI_BASE_URL ?? process.env.BRAINTRUST_E2E_MODEL_BASE_URL; if (openAIBaseUrl) { - configureProvider("openai", { baseUrl: openAIBaseUrl }); + registerProvider("openai", { baseUrl: openAIBaseUrl }); } const anthropicBaseUrl = process.env.ANTHROPIC_BASE_URL; if (anthropicBaseUrl) { - configureProvider("anthropic", { + registerProvider("anthropic", { apiKey: process.env.ANTHROPIC_API_KEY ?? "test-key", baseUrl: anthropicBaseUrl, }); } -let didScheduleFlush = false; -function scheduleFinalFlush(exitAfterFlush = false) { - if (didScheduleFlush) { - return; - } - didScheduleFlush = true; - const keepAlive = setTimeout(() => {}, 30_000); - void flushBeforeExit() - .catch((error) => { - console.error(error); - }) - .finally(() => { - clearTimeout(keepAlive); - if (exitAfterFlush) { - exitProcess(0); - } - }); -} - -process.on("SIGTERM", () => { - scheduleFinalFlush(true); -}); - -process.on("beforeExit", () => { - scheduleFinalFlush(); -}); - -const app = flue(); - -async function flushBeforeExit() { - await new Promise((resolve) => setTimeout(resolve, 250)); - await flush(); - if (process.env.FLUE_E2E_FLUSH_FILE) { - await mkdir(dirname(process.env.FLUE_E2E_FLUSH_FILE), { - recursive: true, - }); - await writeFile(process.env.FLUE_E2E_FLUSH_FILE, "ok"); - } -} +const app = new Hono(); +app.route("/", flue()); export default { async fetch(request, env, ctx) { diff --git a/e2e/scenarios/flue-instrumentation/.flue/workflows/instrumentation.mjs b/e2e/scenarios/flue-instrumentation/.flue/workflows/instrumentation.mjs index dd15a5516..509be2b53 100644 --- a/e2e/scenarios/flue-instrumentation/.flue/workflows/instrumentation.mjs +++ b/e2e/scenarios/flue-instrumentation/.flue/workflows/instrumentation.mjs @@ -1,4 +1,5 @@ import { traced } from "braintrust"; +import * as v from "valibot"; import { FLUE_MODEL, FLUE_REASONING_MODEL, @@ -7,10 +8,11 @@ import { const runtimePackageName = process.env.FLUE_RUNTIME_PACKAGE_NAME ?? "@flue/runtime"; -const [{ createAgent, Type }, { local }] = await Promise.all([ - import(runtimePackageName), - import(`${runtimePackageName}/node`), -]); +const [{ defineAgent, defineTool, defineWorkflow }, { local }] = + await Promise.all([ + import(runtimePackageName), + import(`${runtimePackageName}/node`), + ]); function flueModel() { return process.env.FLUE_E2E_MODEL ?? FLUE_MODEL; @@ -34,7 +36,7 @@ function flueReasoningThinkingLevel() { return process.env.FLUE_E2E_REASONING_THINKING_LEVEL ?? "medium"; } -const flueE2EAgent = createAgent(() => ({ +const flueE2EAgent = defineAgent(() => ({ compaction: { keepRecentTokens: 1, reserveTokens: 64, @@ -51,10 +53,14 @@ const flueE2EAgent = createAgent(() => ({ thinkingLevel: "off", })); -const lookupTool = { +const lookupTool = defineTool({ description: "Return a deterministic lookup result with an id needed by web_search.", - execute: async (args) => { + input: v.object({ + query: v.string(), + }), + name: "lookup", + run: async ({ input }) => { await traced( async (span) => { span.log({ output: "lookup-active" }); @@ -69,109 +75,109 @@ const lookupTool = { }, ); - return JSON.stringify({ + return { id: "flue-session-2026", - query: args.query, + query: input.query, topic: "session instrumentation", - }); + }; }, - name: "lookup", - parameters: Type.Object({ - query: Type.String(), - }), -}; +}); -const webSearchTool = { +const webSearchTool = defineTool({ description: "Search a deterministic local web index. Requires the id returned by lookup.", - execute: async (args) => - JSON.stringify({ - lookupId: args.lookupId, - query: args.query, - results: [ - { - title: "Flue reasoning stream instrumentation", - url: "https://example.test/flue/reasoning-streams", - }, - ], - }), + input: v.object({ + lookupId: v.string(), + query: v.string(), + }), name: "web_search", - parameters: Type.Object({ - lookupId: Type.String(), - query: Type.String(), + run: async ({ input }) => ({ + lookupId: input.lookupId, + query: input.query, + results: [ + { + title: "Flue reasoning stream instrumentation", + url: "https://example.test/flue/reasoning-streams", + }, + ], }), -}; +}); -const summarizeSourceTool = { +const summarizeSourceTool = defineTool({ description: "Summarize the selected deterministic source after web_search returns a URL.", - execute: async (args) => - JSON.stringify({ - summary: - "Flue emits reasoning, tool execution, and LLM turn events separately.", - url: args.url, - }), + input: v.object({ + url: v.string(), + }), name: "summarize_source", - parameters: Type.Object({ - url: Type.String(), + run: async ({ input }) => ({ + summary: + "Flue emits reasoning, tool execution, and LLM turn events separately.", + url: input.url, }), -}; +}); export async function route(_ctx, next) { await next(); } -export async function run({ init, payload }) { - await traced( - async (span) => { - span.log({ output: "active" }); - }, - { - name: "flue.workflowCurrentProbe", - event: { - metadata: { - scenario: SCENARIO_NAME, +export default defineWorkflow({ + agent: flueE2EAgent, + input: v.object({ + scenario: v.optional(v.string()), + metadata: v.optional(v.record(v.string(), v.unknown())), + }), + async run({ harness, input }) { + await traced( + async (span) => { + span.log({ output: "active" }); + }, + { + name: "flue.workflowCurrentProbe", + event: { + metadata: { + scenario: SCENARIO_NAME, + }, }, }, - }, - ); + ); - const harness = await init(flueE2EAgent, { name: "default" }); - const session = await harness.session("main"); - const skillSession = await harness.session("skill"); - const taskSession = await harness.session("task"); - - await session.prompt( - [ - "Complete this instrumented research flow.", - "Call exactly one tool per turn and wait for each tool result before choosing the next tool.", - 'Step 1: call lookup with query "flue instrumentation".', - 'Step 2: use the lookup result id as lookupId and call web_search with query "Braintrust Flue reasoning stream instrumentation".', - "Step 3: use the first web_search result url and call summarize_source.", - "After summarize_source returns, reply with exactly PROMPT_DONE and no other text.", - ].join(" "), - { - model: fluePromptModel(), - thinkingLevel: fluePromptThinkingLevel(), - tools: [lookupTool, webSearchTool, summarizeSourceTool], - }, - ); + const session = await harness.session("main"); + const skillSession = await harness.session("skill"); + const taskSession = await harness.session("task"); + + await session.prompt( + [ + "Complete this instrumented research flow.", + "Call exactly one tool per turn and wait for each tool result before choosing the next tool.", + 'Step 1: call lookup with query "flue instrumentation".', + 'Step 2: use the lookup result id as lookupId and call web_search with query "Braintrust Flue reasoning stream instrumentation".', + "Step 3: use the first web_search result url and call summarize_source.", + "After summarize_source returns, reply with exactly PROMPT_DONE and no other text.", + ].join(" "), + { + model: fluePromptModel(), + thinkingLevel: fluePromptThinkingLevel(), + tools: [lookupTool, webSearchTool, summarizeSourceTool], + }, + ); - await skillSession.skill("e2e-flue-skill", { - args: { marker: "SKILL_DONE" }, - model: flueReasoningModel(), - thinkingLevel: "off", - }); + await skillSession.skill("e2e-flue-skill", { + args: { marker: "SKILL_DONE" }, + model: flueReasoningModel(), + thinkingLevel: "off", + }); - await taskSession.task("Reply with exactly TASK_DONE and no other text.", { - model: FLUE_MODEL, - thinkingLevel: "off", - }); + await taskSession.task("Reply with exactly TASK_DONE and no other text.", { + model: FLUE_MODEL, + thinkingLevel: "off", + }); - await session.compact(); + await session.compact(); - return { - scenario: payload?.scenario ?? SCENARIO_NAME, - status: "done", - }; -} + return { + scenario: input.scenario ?? SCENARIO_NAME, + status: "done", + }; + }, +}); diff --git a/e2e/scenarios/flue-instrumentation/__cassettes__/flue-v0-8-0.cassette.json b/e2e/scenarios/flue-instrumentation/__cassettes__/flue-v0-8-0.cassette.json deleted file mode 100644 index 996df5098..000000000 --- a/e2e/scenarios/flue-instrumentation/__cassettes__/flue-v0-8-0.cassette.json +++ /dev/null @@ -1,3727 +0,0 @@ -{ - "entries": [ - { - "callIndex": 0, - "id": "fcd9d51c145fb90b", - "matchKey": "POST api.anthropic.com/v1/messages", - "recordedAt": "2026-05-28T13:40:49.578Z", - "request": { - "body": { - "kind": "json", - "value": { - "max_tokens": 64000, - "messages": [ - { - "content": [ - { - "cache_control": { - "type": "ephemeral" - }, - "text": "Complete this instrumented research flow. Call exactly one tool per turn and wait for each tool result before choosing the next tool. Step 1: call lookup with query \"flue instrumentation\". Step 2: use the lookup result id as lookupId and call web_search with query \"Braintrust Flue reasoning stream instrumentation\". Step 3: use the first web_search result url and call summarize_source. After summarize_source returns, reply with exactly PROMPT_DONE and no other text.", - "type": "text" - } - ], - "role": "user" - } - ], - "model": "claude-sonnet-4-5-20250929", - "stream": true, - "system": [ - { - "cache_control": { - "type": "ephemeral" - }, - "text": "You are running in headless mode with no human operator. Work autonomously — never ask questions, never wait for user input. Make your best judgment and proceed independently.\n\nYou are a deterministic Flue instrumentation test agent. Follow user instructions exactly. When asked for a marker, output only that marker and no extra text. When running a local skill file, read it yourself and do not delegate it to a task.\n\n## Available Skills\n\nThe following skills provide specialized instructions for specific tasks. When a task matches a skill description, activate that skill before proceeding so its full instructions are loaded. Skill instructions and supporting resources stay lazy until activation or explicit file reads.\n\n- **e2e-flue-skill** — Deterministic skill used by the Braintrust Flue instrumentation e2e test.\n\nDate: Thu, May 28, 2026\nWorking directory: /Users/lucaforstner/conductor/workspaces/braintrust-sdk-javascript/shanghai-v1/e2e/.bt-tmp/run-u9uXnv/scenarios/flue-instrumentation\n\nDirectory structure:\n.agents\n__cassettes__\n__snapshots__\nassertions.ts\ncassette-filter.mjs\nconstants.mjs\nnode_modules\npackage.json\npnpm-lock.yaml\nscenario.impl.mjs\nscenario.mjs\nscenario.test.ts\nscenario.ts", - "type": "text" - } - ], - "thinking": { - "budget_tokens": 8192, - "display": "summarized", - "type": "enabled" - }, - "tools": [ - { - "description": "Read a file or list a directory. For files, output is truncated to 2000 lines or 50KB — use offset/limit for large files. For directories, returns the list of entries.", - "eager_input_streaming": true, - "input_schema": { - "properties": { - "limit": { - "description": "Maximum number of lines to read", - "type": "number" - }, - "offset": { - "description": "Line number to start from (1-indexed)", - "type": "number" - }, - "path": { - "description": "Path to the file to read", - "type": "string" - } - }, - "required": ["path"], - "type": "object" - }, - "name": "read" - }, - { - "description": "Write content to a file. Creates the file and parent directories if they do not exist.", - "eager_input_streaming": true, - "input_schema": { - "properties": { - "content": { - "description": "Content to write to the file", - "type": "string" - }, - "path": { - "description": "Path to the file to write", - "type": "string" - } - }, - "required": ["path", "content"], - "type": "object" - }, - "name": "write" - }, - { - "description": "Edit a file using exact text replacement. The oldText must match a unique region of the file. Use replaceAll to replace all occurrences.", - "eager_input_streaming": true, - "input_schema": { - "properties": { - "newText": { - "description": "Replacement text", - "type": "string" - }, - "oldText": { - "description": "Exact text to find (must be unique)", - "type": "string" - }, - "path": { - "description": "Path to the file to edit", - "type": "string" - }, - "replaceAll": { - "description": "Replace all occurrences", - "type": "boolean" - } - }, - "required": ["path", "oldText", "newText"], - "type": "object" - }, - "name": "edit" - }, - { - "description": "Execute a bash command. Returns stdout and stderr. Output is truncated to the last 2000 lines or 50KB.", - "eager_input_streaming": true, - "input_schema": { - "properties": { - "command": { - "description": "Bash command to execute", - "type": "string" - }, - "timeout": { - "description": "Timeout in seconds", - "type": "number" - } - }, - "required": ["command"], - "type": "object" - }, - "name": "bash" - }, - { - "description": "Search file contents for a regex pattern. Returns matching lines with file paths and line numbers.", - "eager_input_streaming": true, - "input_schema": { - "properties": { - "include": { - "description": "Glob filter, e.g. \"*.ts\"", - "type": "string" - }, - "path": { - "description": "Directory or file to search (default: .)", - "type": "string" - }, - "pattern": { - "description": "Search pattern (regex)", - "type": "string" - } - }, - "required": ["pattern"], - "type": "object" - }, - "name": "grep" - }, - { - "description": "Find files by filename pattern using shell find -name semantics. Returns matching file paths.", - "eager_input_streaming": true, - "input_schema": { - "properties": { - "path": { - "description": "Directory to search in (default: .)", - "type": "string" - }, - "pattern": { - "description": "Filename pattern, e.g. \"*.ts\"", - "type": "string" - } - }, - "required": ["pattern"], - "type": "object" - }, - "name": "glob" - }, - { - "description": "Delegate a focused task to a detached child agent with its own context. Use this for independent research, file exploration, or parallel work. The task returns only its final answer to this conversation. No subagents are currently defined.", - "eager_input_streaming": true, - "input_schema": { - "properties": { - "agent": { - "description": "Declared subagent to use for the child agent", - "type": "string" - }, - "cwd": { - "description": "Working directory for the child agent. AGENTS.md and skills are discovered from here.", - "type": "string" - }, - "description": { - "description": "Short human-readable label for the delegated work", - "type": "string" - }, - "prompt": { - "description": "Focused instructions for the child agent", - "type": "string" - } - }, - "required": ["prompt"], - "type": "object" - }, - "name": "task" - }, - { - "description": "Return a deterministic lookup result with an id needed by web_search.", - "eager_input_streaming": true, - "input_schema": { - "properties": { - "query": { - "type": "string" - } - }, - "required": ["query"], - "type": "object" - }, - "name": "lookup" - }, - { - "description": "Search a deterministic local web index. Requires the id returned by lookup.", - "eager_input_streaming": true, - "input_schema": { - "properties": { - "lookupId": { - "type": "string" - }, - "query": { - "type": "string" - } - }, - "required": ["lookupId", "query"], - "type": "object" - }, - "name": "web_search" - }, - { - "cache_control": { - "type": "ephemeral" - }, - "description": "Summarize the selected deterministic source after web_search returns a URL.", - "eager_input_streaming": true, - "input_schema": { - "properties": { - "url": { - "type": "string" - } - }, - "required": ["url"], - "type": "object" - }, - "name": "summarize_source" - } - ] - } - }, - "headers": {}, - "method": "POST", - "url": "https://api.anthropic.com/v1/messages" - }, - "response": { - "body": { - "chunks": [ - "event: message_start\ndata: {\"type\":\"message_start\",\"message\":{\"model\":\"claude-sonnet-4-5-20250929\",\"id\":\"msg_01A7K7rFFvpGEkYsKHme6rCx\",\"type\":\"message\",\"role\":\"assistant\",\"content\":[],\"stop_reason\":null,\"stop_sequence\":null,\"stop_details\":null,\"usage\":{\"input_tokens\":10,\"cache_creation_input_tokens\":2222,\"cache_read_input_tokens\":0,\"cache_creation\":{\"ephemeral_5m_input_tokens\":2222,\"ephemeral_1h_input_tokens\":0},\"output_tokens\":8,\"service_tier\":\"standard\",\"inference_geo\":\"not_available\"}}}", - "event: content_block_start\ndata: {\"type\":\"content_block_start\",\"index\":0,\"content_block\":{\"type\":\"thinking\",\"thinking\":\"\",\"signature\":\"\"}}", - "event: ping\ndata: {\"type\":\"ping\"}", - "event: content_block_delta\ndata: {\"type\":\"content_block_delta\",\"index\":0,\"delta\":{\"type\":\"thinking_delta\",\"thinking\":\"The user wants me to complete a research\"}}", - "event: content_block_delta\ndata: {\"type\":\"content_block_delta\",\"index\":0,\"delta\":{\"type\":\"thinking_delta\",\"thinking\":\" flow with exactly three steps:\\n1. Call lookup with query \\\"flue instrumentation\\\"\\n2. Call web_search with the\"}}", - "event: content_block_delta\ndata: {\"type\":\"content_block_delta\",\"index\":0,\"delta\":{\"type\":\"thinking_delta\",\"thinking\":\" lookupId from step 1 and query \\\"Braintrust Flue reasoning stream instrumentation\\\"\\n3. Call summarize_source with the first URL from step 2\"}}", - "event: content_block_delta\ndata: {\"type\":\"content_block_delta\",\"index\":0,\"delta\":{\"type\":\"thinking_delta\",\"thinking\":\"\\n4. Reply with exactly \\\"PROMPT_DONE\\\"\\n\\nThe user emphasizes I should call exactly one tool per turn and wait for each result\"}}", - "event: content_block_delta\ndata: {\"type\":\"content_block_delta\",\"index\":0,\"delta\":{\"type\":\"thinking_delta\",\"thinking\":\". Let me start with step 1.\"}}", - "event: content_block_delta\ndata: {\"type\":\"content_block_delta\",\"index\":0,\"delta\":{\"type\":\"signature_delta\",\"signature\":\"EoQFCm4IDhgCKkD+AoKMhpXNPUJ5eANwM28uUCJPCE+AXh0ACw7aXniX+YN04zdJl3hlHMbtAuKhhczLiEeOJtu4KtVpea5u5NFdMhpjbGF1ZGUtc29ubmV0LTQtNS0yMDI1MDkyOTgAQgh0aGlua2luZxIMIOTWCNEtsiI9vF4SGgwPXC861uZx8lpOziEiMLsCukGIluD0U44degzYnHFc+BwXNIEB+yatSHlZE+a5smr9crZcft3OLmsIdXuaFSrDAyQxWd9oqfJdnvza9eWIaOCeNkVCjrTc/wotBwh5Xp8m9k/WKt7P4zzUJBuLQ1f6ZnsdWkL8OiL/2E7Up55z2+UuVWsg1Y90/x27YRFdQjv3RnztYp4bZ5oIHAlTJoCzWiBYEh/Z8c2O+wy6MHo1cp0T57OroB/4HAqRZxrjTOXpKYLmYKXAY5tU8DTd3A0/FfEhg3oPSbOhX+112szFCpm3BRNJYooUUhwSu1hO4H/6r7d/Yp9XIM4tBfzuDx83CC/YetdOFgnYkdliw0xaUGaj4o37rWIRctWHAMdlUc4NDA0Ljc1uirHDXkN8EJwWSoIjPSBTA/p/wgLrvRV5Yy9Z0dMoWOpgYF6t1H/tNAyY8ml1CA7IWyd3jnfTZ1/G3Y0xFDwJDFlb4/QKc67w36SSCa+X9mMoTxD/DYN249lsII+XX3lQ7F1zJ07THOtCyRirCaRhmE/kGwl5tiB1QHrWa9bNHxQxf9i92435JGKN053Fe14AR70MtBSzIU2/zbgvhB1vF5lZt2i46hNxfo4lXR495gEwdwC6+8HJnlEJAJkdeHfZKj+RiwXoiYKPKsSbHi83X8Hh32IifuglNxdlxVQYAQ==\"}}", - "event: content_block_stop\ndata: {\"type\":\"content_block_stop\",\"index\":0}", - "event: content_block_start\ndata: {\"type\":\"content_block_start\",\"index\":1,\"content_block\":{\"type\":\"text\",\"text\":\"\"}}", - "event: content_block_delta\ndata: {\"type\":\"content_block_delta\",\"index\":1,\"delta\":{\"type\":\"text_delta\",\"text\":\"I'll complete this instrumented research flow step\"}}", - "event: content_block_delta\ndata: {\"type\":\"content_block_delta\",\"index\":1,\"delta\":{\"type\":\"text_delta\",\"text\":\" by step. Starting with Step 1:\"}}", - "event: content_block_stop\ndata: {\"type\":\"content_block_stop\",\"index\":1}", - "event: content_block_start\ndata: {\"type\":\"content_block_start\",\"index\":2,\"content_block\":{\"type\":\"tool_use\",\"id\":\"toolu_01MbotvqKgQV4ecxQ566vyuC\",\"name\":\"lookup\",\"input\":{},\"caller\":{\"type\":\"direct\"}}}", - "event: content_block_delta\ndata: {\"type\":\"content_block_delta\",\"index\":2,\"delta\":{\"type\":\"input_json_delta\",\"partial_json\":\"\"}}", - "event: content_block_delta\ndata: {\"type\":\"content_block_delta\",\"index\":2,\"delta\":{\"type\":\"input_json_delta\",\"partial_json\":\"{\\\"query\\\": \\\"flue instrumentation\"}}", - "event: content_block_delta\ndata: {\"type\":\"content_block_delta\",\"index\":2,\"delta\":{\"type\":\"input_json_delta\",\"partial_json\":\"\\\"}\"}}", - "event: content_block_stop\ndata: {\"type\":\"content_block_stop\",\"index\":2}", - "event: message_delta\ndata: {\"type\":\"message_delta\",\"delta\":{\"stop_reason\":\"tool_use\",\"stop_sequence\":null,\"stop_details\":null},\"usage\":{\"input_tokens\":10,\"cache_creation_input_tokens\":2222,\"cache_read_input_tokens\":0,\"output_tokens\":196,\"output_tokens_details\":{\"thinking_tokens\":120}}}", - "event: message_stop\ndata: {\"type\":\"message_stop\"}" - ], - "kind": "sse" - }, - "headers": { - "anthropic-organization-id": "27796668-7351-40ac-acc4-024aee8995a5", - "anthropic-ratelimit-input-tokens-limit": "3000000", - "anthropic-ratelimit-input-tokens-remaining": "2999000", - "anthropic-ratelimit-input-tokens-reset": "2026-05-28T13:40:45Z", - "anthropic-ratelimit-output-tokens-limit": "600000", - "anthropic-ratelimit-output-tokens-remaining": "600000", - "anthropic-ratelimit-output-tokens-reset": "2026-05-28T13:40:45Z", - "anthropic-ratelimit-requests-limit": "20000", - "anthropic-ratelimit-requests-remaining": "19999", - "anthropic-ratelimit-requests-reset": "2026-05-28T13:40:45Z", - "anthropic-ratelimit-tokens-limit": "3600000", - "anthropic-ratelimit-tokens-remaining": "3599000", - "anthropic-ratelimit-tokens-reset": "2026-05-28T13:40:45Z", - "cache-control": "no-cache", - "cf-cache-status": "DYNAMIC", - "cf-ray": "a02dac490ea0c301-VIE", - "connection": "keep-alive", - "content-encoding": "gzip", - "content-security-policy": "default-src 'none'; frame-ancestors 'none'", - "content-type": "text/event-stream; charset=utf-8", - "date": "Thu, 28 May 2026 13:40:47 GMT", - "request-id": "req_011CbV4Yrzdciu7aM7XyPJvu", - "server": "cloudflare", - "set-cookie": "[REDACTED]", - "strict-transport-security": "max-age=31536000; includeSubDomains; preload", - "traceresponse": "00-def621f99a586b9f40e35c15380d1f90-62267612b2e498ea-01", - "transfer-encoding": "chunked", - "vary": "Accept-Encoding", - "x-envoy-upstream-service-time": "1355", - "x-robots-tag": "none" - }, - "status": 200, - "statusText": "OK" - } - }, - { - "callIndex": 1, - "id": "ff22a182ea5a9c06", - "matchKey": "POST api.anthropic.com/v1/messages", - "recordedAt": "2026-05-28T13:40:53.810Z", - "request": { - "body": { - "kind": "json", - "value": { - "max_tokens": 64000, - "messages": [ - { - "content": [ - { - "text": "Complete this instrumented research flow. Call exactly one tool per turn and wait for each tool result before choosing the next tool. Step 1: call lookup with query \"flue instrumentation\". Step 2: use the lookup result id as lookupId and call web_search with query \"Braintrust Flue reasoning stream instrumentation\". Step 3: use the first web_search result url and call summarize_source. After summarize_source returns, reply with exactly PROMPT_DONE and no other text.", - "type": "text" - } - ], - "role": "user" - }, - { - "content": [ - { - "signature": "EoQFCm4IDhgCKkD+AoKMhpXNPUJ5eANwM28uUCJPCE+AXh0ACw7aXniX+YN04zdJl3hlHMbtAuKhhczLiEeOJtu4KtVpea5u5NFdMhpjbGF1ZGUtc29ubmV0LTQtNS0yMDI1MDkyOTgAQgh0aGlua2luZxIMIOTWCNEtsiI9vF4SGgwPXC861uZx8lpOziEiMLsCukGIluD0U44degzYnHFc+BwXNIEB+yatSHlZE+a5smr9crZcft3OLmsIdXuaFSrDAyQxWd9oqfJdnvza9eWIaOCeNkVCjrTc/wotBwh5Xp8m9k/WKt7P4zzUJBuLQ1f6ZnsdWkL8OiL/2E7Up55z2+UuVWsg1Y90/x27YRFdQjv3RnztYp4bZ5oIHAlTJoCzWiBYEh/Z8c2O+wy6MHo1cp0T57OroB/4HAqRZxrjTOXpKYLmYKXAY5tU8DTd3A0/FfEhg3oPSbOhX+112szFCpm3BRNJYooUUhwSu1hO4H/6r7d/Yp9XIM4tBfzuDx83CC/YetdOFgnYkdliw0xaUGaj4o37rWIRctWHAMdlUc4NDA0Ljc1uirHDXkN8EJwWSoIjPSBTA/p/wgLrvRV5Yy9Z0dMoWOpgYF6t1H/tNAyY8ml1CA7IWyd3jnfTZ1/G3Y0xFDwJDFlb4/QKc67w36SSCa+X9mMoTxD/DYN249lsII+XX3lQ7F1zJ07THOtCyRirCaRhmE/kGwl5tiB1QHrWa9bNHxQxf9i92435JGKN053Fe14AR70MtBSzIU2/zbgvhB1vF5lZt2i46hNxfo4lXR495gEwdwC6+8HJnlEJAJkdeHfZKj+RiwXoiYKPKsSbHi83X8Hh32IifuglNxdlxVQYAQ==", - "thinking": "The user wants me to complete a research flow with exactly three steps:\n1. Call lookup with query \"flue instrumentation\"\n2. Call web_search with the lookupId from step 1 and query \"Braintrust Flue reasoning stream instrumentation\"\n3. Call summarize_source with the first URL from step 2\n4. Reply with exactly \"PROMPT_DONE\"\n\nThe user emphasizes I should call exactly one tool per turn and wait for each result. Let me start with step 1.", - "type": "thinking" - }, - { - "text": "I'll complete this instrumented research flow step by step. Starting with Step 1:", - "type": "text" - }, - { - "id": "toolu_01MbotvqKgQV4ecxQ566vyuC", - "input": { - "query": "flue instrumentation" - }, - "name": "lookup", - "type": "tool_use" - } - ], - "role": "assistant" - }, - { - "content": [ - { - "cache_control": { - "type": "ephemeral" - }, - "content": "{\"id\":\"flue-session-2026\",\"query\":\"flue instrumentation\",\"topic\":\"session instrumentation\"}", - "is_error": false, - "tool_use_id": "toolu_01MbotvqKgQV4ecxQ566vyuC", - "type": "tool_result" - } - ], - "role": "user" - } - ], - "model": "claude-sonnet-4-5-20250929", - "stream": true, - "system": [ - { - "cache_control": { - "type": "ephemeral" - }, - "text": "You are running in headless mode with no human operator. Work autonomously — never ask questions, never wait for user input. Make your best judgment and proceed independently.\n\nYou are a deterministic Flue instrumentation test agent. Follow user instructions exactly. When asked for a marker, output only that marker and no extra text. When running a local skill file, read it yourself and do not delegate it to a task.\n\n## Available Skills\n\nThe following skills provide specialized instructions for specific tasks. When a task matches a skill description, activate that skill before proceeding so its full instructions are loaded. Skill instructions and supporting resources stay lazy until activation or explicit file reads.\n\n- **e2e-flue-skill** — Deterministic skill used by the Braintrust Flue instrumentation e2e test.\n\nDate: Thu, May 28, 2026\nWorking directory: /Users/lucaforstner/conductor/workspaces/braintrust-sdk-javascript/shanghai-v1/e2e/.bt-tmp/run-u9uXnv/scenarios/flue-instrumentation\n\nDirectory structure:\n.agents\n__cassettes__\n__snapshots__\nassertions.ts\ncassette-filter.mjs\nconstants.mjs\nnode_modules\npackage.json\npnpm-lock.yaml\nscenario.impl.mjs\nscenario.mjs\nscenario.test.ts\nscenario.ts", - "type": "text" - } - ], - "thinking": { - "budget_tokens": 8192, - "display": "summarized", - "type": "enabled" - }, - "tools": [ - { - "description": "Read a file or list a directory. For files, output is truncated to 2000 lines or 50KB — use offset/limit for large files. For directories, returns the list of entries.", - "eager_input_streaming": true, - "input_schema": { - "properties": { - "limit": { - "description": "Maximum number of lines to read", - "type": "number" - }, - "offset": { - "description": "Line number to start from (1-indexed)", - "type": "number" - }, - "path": { - "description": "Path to the file to read", - "type": "string" - } - }, - "required": ["path"], - "type": "object" - }, - "name": "read" - }, - { - "description": "Write content to a file. Creates the file and parent directories if they do not exist.", - "eager_input_streaming": true, - "input_schema": { - "properties": { - "content": { - "description": "Content to write to the file", - "type": "string" - }, - "path": { - "description": "Path to the file to write", - "type": "string" - } - }, - "required": ["path", "content"], - "type": "object" - }, - "name": "write" - }, - { - "description": "Edit a file using exact text replacement. The oldText must match a unique region of the file. Use replaceAll to replace all occurrences.", - "eager_input_streaming": true, - "input_schema": { - "properties": { - "newText": { - "description": "Replacement text", - "type": "string" - }, - "oldText": { - "description": "Exact text to find (must be unique)", - "type": "string" - }, - "path": { - "description": "Path to the file to edit", - "type": "string" - }, - "replaceAll": { - "description": "Replace all occurrences", - "type": "boolean" - } - }, - "required": ["path", "oldText", "newText"], - "type": "object" - }, - "name": "edit" - }, - { - "description": "Execute a bash command. Returns stdout and stderr. Output is truncated to the last 2000 lines or 50KB.", - "eager_input_streaming": true, - "input_schema": { - "properties": { - "command": { - "description": "Bash command to execute", - "type": "string" - }, - "timeout": { - "description": "Timeout in seconds", - "type": "number" - } - }, - "required": ["command"], - "type": "object" - }, - "name": "bash" - }, - { - "description": "Search file contents for a regex pattern. Returns matching lines with file paths and line numbers.", - "eager_input_streaming": true, - "input_schema": { - "properties": { - "include": { - "description": "Glob filter, e.g. \"*.ts\"", - "type": "string" - }, - "path": { - "description": "Directory or file to search (default: .)", - "type": "string" - }, - "pattern": { - "description": "Search pattern (regex)", - "type": "string" - } - }, - "required": ["pattern"], - "type": "object" - }, - "name": "grep" - }, - { - "description": "Find files by filename pattern using shell find -name semantics. Returns matching file paths.", - "eager_input_streaming": true, - "input_schema": { - "properties": { - "path": { - "description": "Directory to search in (default: .)", - "type": "string" - }, - "pattern": { - "description": "Filename pattern, e.g. \"*.ts\"", - "type": "string" - } - }, - "required": ["pattern"], - "type": "object" - }, - "name": "glob" - }, - { - "description": "Delegate a focused task to a detached child agent with its own context. Use this for independent research, file exploration, or parallel work. The task returns only its final answer to this conversation. No subagents are currently defined.", - "eager_input_streaming": true, - "input_schema": { - "properties": { - "agent": { - "description": "Declared subagent to use for the child agent", - "type": "string" - }, - "cwd": { - "description": "Working directory for the child agent. AGENTS.md and skills are discovered from here.", - "type": "string" - }, - "description": { - "description": "Short human-readable label for the delegated work", - "type": "string" - }, - "prompt": { - "description": "Focused instructions for the child agent", - "type": "string" - } - }, - "required": ["prompt"], - "type": "object" - }, - "name": "task" - }, - { - "description": "Return a deterministic lookup result with an id needed by web_search.", - "eager_input_streaming": true, - "input_schema": { - "properties": { - "query": { - "type": "string" - } - }, - "required": ["query"], - "type": "object" - }, - "name": "lookup" - }, - { - "description": "Search a deterministic local web index. Requires the id returned by lookup.", - "eager_input_streaming": true, - "input_schema": { - "properties": { - "lookupId": { - "type": "string" - }, - "query": { - "type": "string" - } - }, - "required": ["lookupId", "query"], - "type": "object" - }, - "name": "web_search" - }, - { - "cache_control": { - "type": "ephemeral" - }, - "description": "Summarize the selected deterministic source after web_search returns a URL.", - "eager_input_streaming": true, - "input_schema": { - "properties": { - "url": { - "type": "string" - } - }, - "required": ["url"], - "type": "object" - }, - "name": "summarize_source" - } - ] - } - }, - "headers": {}, - "method": "POST", - "url": "https://api.anthropic.com/v1/messages" - }, - "response": { - "body": { - "chunks": [ - "event: message_start\ndata: {\"type\":\"message_start\",\"message\":{\"model\":\"claude-sonnet-4-5-20250929\",\"id\":\"msg_01HZbhxuwLjyFGr1czSAWHTh\",\"type\":\"message\",\"role\":\"assistant\",\"content\":[],\"stop_reason\":null,\"stop_sequence\":null,\"stop_details\":null,\"usage\":{\"input_tokens\":13,\"cache_creation_input_tokens\":236,\"cache_read_input_tokens\":2222,\"cache_creation\":{\"ephemeral_5m_input_tokens\":236,\"ephemeral_1h_input_tokens\":0},\"output_tokens\":1,\"service_tier\":\"standard\",\"inference_geo\":\"not_available\"}}}", - "event: content_block_start\ndata: {\"type\":\"content_block_start\",\"index\":0,\"content_block\":{\"type\":\"thinking\",\"thinking\":\"\",\"signature\":\"\"}}", - "event: ping\ndata: {\"type\":\"ping\"}", - "event: content_block_delta\ndata: {\"type\":\"content_block_delta\",\"index\":0,\"delta\":{\"type\":\"thinking_delta\",\"thinking\":\"Good\"}}", - "event: content_block_delta\ndata: {\"type\":\"content_block_delta\",\"index\":0,\"delta\":{\"type\":\"thinking_delta\",\"thinking\":\", I got the lookup result with id \\\"flue-session-2026\\\". Now I need to proceed to step 2:\"}}", - "event: content_block_delta\ndata: {\"type\":\"content_block_delta\",\"index\":0,\"delta\":{\"type\":\"thinking_delta\",\"thinking\":\" call web_search with this lookupId and the query \\\"Braintrust Flue reasoning stream instrumentation\\\".\"}}", - "event: content_block_delta\ndata: {\"type\":\"content_block_delta\",\"index\":0,\"delta\":{\"type\":\"signature_delta\",\"signature\":\"EpEDCm4IDhgCKkAMahve1kB1j4njp1ZSzO8eCK4bb5Ex7MB6tjdoOXfuUDRfkBqvIGdwVly8GiA8GGYC7Y/RVfREXF6A6OXHTwFyMhpjbGF1ZGUtc29ubmV0LTQtNS0yMDI1MDkyOTgAQgh0aGlua2luZxIMLw+fsa4yo7a7ZSoIGgyaTdvH020n2dQj9bUiMB4OokY29w+uZb4rqDnKDbLXUqUj6U0f1QCGQF2ZBYDDfDJyM5Jv1xY3rQiUUjkhBirQAS4XivjvMnWI5zHBmpI/u/pq4aGdrdb3vUsJ/ne6R1d80JA/INGF8vLkQcZ5xxEK0jU4yRE+ldc2uzavmtoxIKAFD6bqKks1Qv6u+TIZtuW7tUB/7jFxDw93KmJNruMIa8c1J6pPUyBy3xE7QwgmU0kDfIfdnljxLoEBiMwtnxjCPj9lT7HHz4FkrhbnQTV4MvAQohCLjaSbOTd6LdLxwtJPm9mc26ID4eYN1gCCzyAxGXhqLHlzc23PafvQjsZliWnLhaiNMnG0YEo78XkuJQsYAQ==\"}}", - "event: content_block_stop\ndata: {\"type\":\"content_block_stop\",\"index\":0}", - "event: content_block_start\ndata: {\"type\":\"content_block_start\",\"index\":1,\"content_block\":{\"type\":\"text\",\"text\":\"\"}}", - "event: content_block_delta\ndata: {\"type\":\"content_block_delta\",\"index\":1,\"delta\":{\"type\":\"text_delta\",\"text\":\"Now proceeding to Step 2:\"}}", - "event: content_block_stop\ndata: {\"type\":\"content_block_stop\",\"index\":1}", - "event: content_block_start\ndata: {\"type\":\"content_block_start\",\"index\":2,\"content_block\":{\"type\":\"tool_use\",\"id\":\"toolu_018rc1ig7vXeron3rLfpUtRT\",\"name\":\"web_search\",\"input\":{},\"caller\":{\"type\":\"direct\"}}}", - "event: content_block_delta\ndata: {\"type\":\"content_block_delta\",\"index\":2,\"delta\":{\"type\":\"input_json_delta\",\"partial_json\":\"\"}}", - "event: content_block_delta\ndata: {\"type\":\"content_block_delta\",\"index\":2,\"delta\":{\"type\":\"input_json_delta\",\"partial_json\":\"{\\\"lookupId\\\": \\\"flue-session-2026\"}}", - "event: content_block_delta\ndata: {\"type\":\"content_block_delta\",\"index\":2,\"delta\":{\"type\":\"input_json_delta\",\"partial_json\":\"\\\", \\\"query\\\": \\\"Braintrust Flue reasoning stream instru\"}}", - "event: content_block_delta\ndata: {\"type\":\"content_block_delta\",\"index\":2,\"delta\":{\"type\":\"input_json_delta\",\"partial_json\":\"mentation\"}}", - "event: content_block_delta\ndata: {\"type\":\"content_block_delta\",\"index\":2,\"delta\":{\"type\":\"input_json_delta\",\"partial_json\":\"\\\"}\"}}", - "event: content_block_stop\ndata: {\"type\":\"content_block_stop\",\"index\":2}", - "event: message_delta\ndata: {\"type\":\"message_delta\",\"delta\":{\"stop_reason\":\"tool_use\",\"stop_sequence\":null,\"stop_details\":null},\"usage\":{\"input_tokens\":13,\"cache_creation_input_tokens\":236,\"cache_read_input_tokens\":2222,\"output_tokens\":158,\"output_tokens_details\":{\"thinking_tokens\":59}}}", - "event: message_stop\ndata: {\"type\":\"message_stop\"}" - ], - "kind": "sse" - }, - "headers": { - "anthropic-organization-id": "27796668-7351-40ac-acc4-024aee8995a5", - "anthropic-ratelimit-input-tokens-limit": "3000000", - "anthropic-ratelimit-input-tokens-remaining": "2999000", - "anthropic-ratelimit-input-tokens-reset": "2026-05-28T13:40:49Z", - "anthropic-ratelimit-output-tokens-limit": "600000", - "anthropic-ratelimit-output-tokens-remaining": "600000", - "anthropic-ratelimit-output-tokens-reset": "2026-05-28T13:40:49Z", - "anthropic-ratelimit-requests-limit": "20000", - "anthropic-ratelimit-requests-remaining": "19999", - "anthropic-ratelimit-requests-reset": "2026-05-28T13:40:49Z", - "anthropic-ratelimit-tokens-limit": "3600000", - "anthropic-ratelimit-tokens-remaining": "3599000", - "anthropic-ratelimit-tokens-reset": "2026-05-28T13:40:49Z", - "cache-control": "no-cache", - "cf-cache-status": "DYNAMIC", - "cf-ray": "a02dac631dbac301-VIE", - "connection": "keep-alive", - "content-encoding": "gzip", - "content-security-policy": "default-src 'none'; frame-ancestors 'none'", - "content-type": "text/event-stream; charset=utf-8", - "date": "Thu, 28 May 2026 13:40:52 GMT", - "request-id": "req_011CbV4ZAqRr9QtjYnVyY69n", - "server": "cloudflare", - "set-cookie": "[REDACTED]", - "strict-transport-security": "max-age=31536000; includeSubDomains; preload", - "traceresponse": "00-68d6456f3077bdeb0822f73c39a51d2d-5be926a484ba813d-01", - "transfer-encoding": "chunked", - "vary": "Accept-Encoding", - "x-envoy-upstream-service-time": "2225", - "x-robots-tag": "none" - }, - "status": 200, - "statusText": "OK" - } - }, - { - "callIndex": 2, - "id": "f1d50a1e6c760d6c", - "matchKey": "POST api.anthropic.com/v1/messages", - "recordedAt": "2026-05-28T13:40:58.541Z", - "request": { - "body": { - "kind": "json", - "value": { - "max_tokens": 64000, - "messages": [ - { - "content": [ - { - "text": "Complete this instrumented research flow. Call exactly one tool per turn and wait for each tool result before choosing the next tool. Step 1: call lookup with query \"flue instrumentation\". Step 2: use the lookup result id as lookupId and call web_search with query \"Braintrust Flue reasoning stream instrumentation\". Step 3: use the first web_search result url and call summarize_source. After summarize_source returns, reply with exactly PROMPT_DONE and no other text.", - "type": "text" - } - ], - "role": "user" - }, - { - "content": [ - { - "signature": "EoQFCm4IDhgCKkD+AoKMhpXNPUJ5eANwM28uUCJPCE+AXh0ACw7aXniX+YN04zdJl3hlHMbtAuKhhczLiEeOJtu4KtVpea5u5NFdMhpjbGF1ZGUtc29ubmV0LTQtNS0yMDI1MDkyOTgAQgh0aGlua2luZxIMIOTWCNEtsiI9vF4SGgwPXC861uZx8lpOziEiMLsCukGIluD0U44degzYnHFc+BwXNIEB+yatSHlZE+a5smr9crZcft3OLmsIdXuaFSrDAyQxWd9oqfJdnvza9eWIaOCeNkVCjrTc/wotBwh5Xp8m9k/WKt7P4zzUJBuLQ1f6ZnsdWkL8OiL/2E7Up55z2+UuVWsg1Y90/x27YRFdQjv3RnztYp4bZ5oIHAlTJoCzWiBYEh/Z8c2O+wy6MHo1cp0T57OroB/4HAqRZxrjTOXpKYLmYKXAY5tU8DTd3A0/FfEhg3oPSbOhX+112szFCpm3BRNJYooUUhwSu1hO4H/6r7d/Yp9XIM4tBfzuDx83CC/YetdOFgnYkdliw0xaUGaj4o37rWIRctWHAMdlUc4NDA0Ljc1uirHDXkN8EJwWSoIjPSBTA/p/wgLrvRV5Yy9Z0dMoWOpgYF6t1H/tNAyY8ml1CA7IWyd3jnfTZ1/G3Y0xFDwJDFlb4/QKc67w36SSCa+X9mMoTxD/DYN249lsII+XX3lQ7F1zJ07THOtCyRirCaRhmE/kGwl5tiB1QHrWa9bNHxQxf9i92435JGKN053Fe14AR70MtBSzIU2/zbgvhB1vF5lZt2i46hNxfo4lXR495gEwdwC6+8HJnlEJAJkdeHfZKj+RiwXoiYKPKsSbHi83X8Hh32IifuglNxdlxVQYAQ==", - "thinking": "The user wants me to complete a research flow with exactly three steps:\n1. Call lookup with query \"flue instrumentation\"\n2. Call web_search with the lookupId from step 1 and query \"Braintrust Flue reasoning stream instrumentation\"\n3. Call summarize_source with the first URL from step 2\n4. Reply with exactly \"PROMPT_DONE\"\n\nThe user emphasizes I should call exactly one tool per turn and wait for each result. Let me start with step 1.", - "type": "thinking" - }, - { - "text": "I'll complete this instrumented research flow step by step. Starting with Step 1:", - "type": "text" - }, - { - "id": "toolu_01MbotvqKgQV4ecxQ566vyuC", - "input": { - "query": "flue instrumentation" - }, - "name": "lookup", - "type": "tool_use" - } - ], - "role": "assistant" - }, - { - "content": [ - { - "content": "{\"id\":\"flue-session-2026\",\"query\":\"flue instrumentation\",\"topic\":\"session instrumentation\"}", - "is_error": false, - "tool_use_id": "toolu_01MbotvqKgQV4ecxQ566vyuC", - "type": "tool_result" - } - ], - "role": "user" - }, - { - "content": [ - { - "signature": "EpEDCm4IDhgCKkAMahve1kB1j4njp1ZSzO8eCK4bb5Ex7MB6tjdoOXfuUDRfkBqvIGdwVly8GiA8GGYC7Y/RVfREXF6A6OXHTwFyMhpjbGF1ZGUtc29ubmV0LTQtNS0yMDI1MDkyOTgAQgh0aGlua2luZxIMLw+fsa4yo7a7ZSoIGgyaTdvH020n2dQj9bUiMB4OokY29w+uZb4rqDnKDbLXUqUj6U0f1QCGQF2ZBYDDfDJyM5Jv1xY3rQiUUjkhBirQAS4XivjvMnWI5zHBmpI/u/pq4aGdrdb3vUsJ/ne6R1d80JA/INGF8vLkQcZ5xxEK0jU4yRE+ldc2uzavmtoxIKAFD6bqKks1Qv6u+TIZtuW7tUB/7jFxDw93KmJNruMIa8c1J6pPUyBy3xE7QwgmU0kDfIfdnljxLoEBiMwtnxjCPj9lT7HHz4FkrhbnQTV4MvAQohCLjaSbOTd6LdLxwtJPm9mc26ID4eYN1gCCzyAxGXhqLHlzc23PafvQjsZliWnLhaiNMnG0YEo78XkuJQsYAQ==", - "thinking": "Good, I got the lookup result with id \"flue-session-2026\". Now I need to proceed to step 2: call web_search with this lookupId and the query \"Braintrust Flue reasoning stream instrumentation\".", - "type": "thinking" - }, - { - "text": "Now proceeding to Step 2:", - "type": "text" - }, - { - "id": "toolu_018rc1ig7vXeron3rLfpUtRT", - "input": { - "lookupId": "flue-session-2026", - "query": "Braintrust Flue reasoning stream instrumentation" - }, - "name": "web_search", - "type": "tool_use" - } - ], - "role": "assistant" - }, - { - "content": [ - { - "cache_control": { - "type": "ephemeral" - }, - "content": "{\"lookupId\":\"flue-session-2026\",\"query\":\"Braintrust Flue reasoning stream instrumentation\",\"results\":[{\"title\":\"Flue reasoning stream instrumentation\",\"url\":\"https://example.test/flue/reasoning-streams\"}]}", - "is_error": false, - "tool_use_id": "toolu_018rc1ig7vXeron3rLfpUtRT", - "type": "tool_result" - } - ], - "role": "user" - } - ], - "model": "claude-sonnet-4-5-20250929", - "stream": true, - "system": [ - { - "cache_control": { - "type": "ephemeral" - }, - "text": "You are running in headless mode with no human operator. Work autonomously — never ask questions, never wait for user input. Make your best judgment and proceed independently.\n\nYou are a deterministic Flue instrumentation test agent. Follow user instructions exactly. When asked for a marker, output only that marker and no extra text. When running a local skill file, read it yourself and do not delegate it to a task.\n\n## Available Skills\n\nThe following skills provide specialized instructions for specific tasks. When a task matches a skill description, activate that skill before proceeding so its full instructions are loaded. Skill instructions and supporting resources stay lazy until activation or explicit file reads.\n\n- **e2e-flue-skill** — Deterministic skill used by the Braintrust Flue instrumentation e2e test.\n\nDate: Thu, May 28, 2026\nWorking directory: /Users/lucaforstner/conductor/workspaces/braintrust-sdk-javascript/shanghai-v1/e2e/.bt-tmp/run-u9uXnv/scenarios/flue-instrumentation\n\nDirectory structure:\n.agents\n__cassettes__\n__snapshots__\nassertions.ts\ncassette-filter.mjs\nconstants.mjs\nnode_modules\npackage.json\npnpm-lock.yaml\nscenario.impl.mjs\nscenario.mjs\nscenario.test.ts\nscenario.ts", - "type": "text" - } - ], - "thinking": { - "budget_tokens": 8192, - "display": "summarized", - "type": "enabled" - }, - "tools": [ - { - "description": "Read a file or list a directory. For files, output is truncated to 2000 lines or 50KB — use offset/limit for large files. For directories, returns the list of entries.", - "eager_input_streaming": true, - "input_schema": { - "properties": { - "limit": { - "description": "Maximum number of lines to read", - "type": "number" - }, - "offset": { - "description": "Line number to start from (1-indexed)", - "type": "number" - }, - "path": { - "description": "Path to the file to read", - "type": "string" - } - }, - "required": ["path"], - "type": "object" - }, - "name": "read" - }, - { - "description": "Write content to a file. Creates the file and parent directories if they do not exist.", - "eager_input_streaming": true, - "input_schema": { - "properties": { - "content": { - "description": "Content to write to the file", - "type": "string" - }, - "path": { - "description": "Path to the file to write", - "type": "string" - } - }, - "required": ["path", "content"], - "type": "object" - }, - "name": "write" - }, - { - "description": "Edit a file using exact text replacement. The oldText must match a unique region of the file. Use replaceAll to replace all occurrences.", - "eager_input_streaming": true, - "input_schema": { - "properties": { - "newText": { - "description": "Replacement text", - "type": "string" - }, - "oldText": { - "description": "Exact text to find (must be unique)", - "type": "string" - }, - "path": { - "description": "Path to the file to edit", - "type": "string" - }, - "replaceAll": { - "description": "Replace all occurrences", - "type": "boolean" - } - }, - "required": ["path", "oldText", "newText"], - "type": "object" - }, - "name": "edit" - }, - { - "description": "Execute a bash command. Returns stdout and stderr. Output is truncated to the last 2000 lines or 50KB.", - "eager_input_streaming": true, - "input_schema": { - "properties": { - "command": { - "description": "Bash command to execute", - "type": "string" - }, - "timeout": { - "description": "Timeout in seconds", - "type": "number" - } - }, - "required": ["command"], - "type": "object" - }, - "name": "bash" - }, - { - "description": "Search file contents for a regex pattern. Returns matching lines with file paths and line numbers.", - "eager_input_streaming": true, - "input_schema": { - "properties": { - "include": { - "description": "Glob filter, e.g. \"*.ts\"", - "type": "string" - }, - "path": { - "description": "Directory or file to search (default: .)", - "type": "string" - }, - "pattern": { - "description": "Search pattern (regex)", - "type": "string" - } - }, - "required": ["pattern"], - "type": "object" - }, - "name": "grep" - }, - { - "description": "Find files by filename pattern using shell find -name semantics. Returns matching file paths.", - "eager_input_streaming": true, - "input_schema": { - "properties": { - "path": { - "description": "Directory to search in (default: .)", - "type": "string" - }, - "pattern": { - "description": "Filename pattern, e.g. \"*.ts\"", - "type": "string" - } - }, - "required": ["pattern"], - "type": "object" - }, - "name": "glob" - }, - { - "description": "Delegate a focused task to a detached child agent with its own context. Use this for independent research, file exploration, or parallel work. The task returns only its final answer to this conversation. No subagents are currently defined.", - "eager_input_streaming": true, - "input_schema": { - "properties": { - "agent": { - "description": "Declared subagent to use for the child agent", - "type": "string" - }, - "cwd": { - "description": "Working directory for the child agent. AGENTS.md and skills are discovered from here.", - "type": "string" - }, - "description": { - "description": "Short human-readable label for the delegated work", - "type": "string" - }, - "prompt": { - "description": "Focused instructions for the child agent", - "type": "string" - } - }, - "required": ["prompt"], - "type": "object" - }, - "name": "task" - }, - { - "description": "Return a deterministic lookup result with an id needed by web_search.", - "eager_input_streaming": true, - "input_schema": { - "properties": { - "query": { - "type": "string" - } - }, - "required": ["query"], - "type": "object" - }, - "name": "lookup" - }, - { - "description": "Search a deterministic local web index. Requires the id returned by lookup.", - "eager_input_streaming": true, - "input_schema": { - "properties": { - "lookupId": { - "type": "string" - }, - "query": { - "type": "string" - } - }, - "required": ["lookupId", "query"], - "type": "object" - }, - "name": "web_search" - }, - { - "cache_control": { - "type": "ephemeral" - }, - "description": "Summarize the selected deterministic source after web_search returns a URL.", - "eager_input_streaming": true, - "input_schema": { - "properties": { - "url": { - "type": "string" - } - }, - "required": ["url"], - "type": "object" - }, - "name": "summarize_source" - } - ] - } - }, - "headers": {}, - "method": "POST", - "url": "https://api.anthropic.com/v1/messages" - }, - "response": { - "body": { - "chunks": [ - "event: message_start\ndata: {\"type\":\"message_start\",\"message\":{\"model\":\"claude-sonnet-4-5-20250929\",\"id\":\"msg_01HJoo9ncrvNPVcaq2UWN45L\",\"type\":\"message\",\"role\":\"assistant\",\"content\":[],\"stop_reason\":null,\"stop_sequence\":null,\"stop_details\":null,\"usage\":{\"input_tokens\":13,\"cache_creation_input_tokens\":234,\"cache_read_input_tokens\":2458,\"cache_creation\":{\"ephemeral_5m_input_tokens\":234,\"ephemeral_1h_input_tokens\":0},\"output_tokens\":4,\"service_tier\":\"standard\",\"inference_geo\":\"not_available\"}}}", - "event: content_block_start\ndata: {\"type\":\"content_block_start\",\"index\":0,\"content_block\":{\"type\":\"thinking\",\"thinking\":\"\",\"signature\":\"\"}}", - "event: ping\ndata: {\"type\":\"ping\"}", - "event: content_block_delta\ndata: {\"type\":\"content_block_delta\",\"index\":0,\"delta\":{\"type\":\"thinking_delta\",\"thinking\":\"Perfect! I got\"}}", - "event: content_block_delta\ndata: {\"type\":\"content_block_delta\",\"index\":0,\"delta\":{\"type\":\"thinking_delta\",\"thinking\":\" the web_search results with one result. The first (and only) result has the URL\"}}", - "event: content_block_delta\ndata: {\"type\":\"content_block_delta\",\"index\":0,\"delta\":{\"type\":\"thinking_delta\",\"thinking\":\" \\\"https://example.test/flue/reasoning-streams\\\". Now I need to proceed to step 3: call summarize_source with this URL.\"}}", - "event: content_block_delta\ndata: {\"type\":\"content_block_delta\",\"index\":0,\"delta\":{\"type\":\"signature_delta\",\"signature\":\"EqQDCm4IDhgCKkAXv7EDAhWMcrBz11bUd5qxlzyy03CAGQfRgLHDE0uKzLATh1o2sedd8GxLwwQqV0CmLRfNxsdK/ZDp3Lks9iysMhpjbGF1ZGUtc29ubmV0LTQtNS0yMDI1MDkyOTgAQgh0aGlua2luZxIMXDMniOy5Dy0KI8x7GgzgHs7BYJMs6/N0ur8iMPe0SwnHhP39OjEu7P8ASwK9ffwa3VP67QOlPzOstRRdEZNd0mq+BVjpmBXB+QcAzCrjAeAh/UO/d7SfFmXjqwA3//thoL/TTlGa6W+fdOCFRikn65JWT4ZdVFetfqD+9/QCP/nXEwMsLnTBAxC4EGryrgs2YT+OWwmEJ7rHKFeDs5djhwSb2J2ZJO1kRvD9s2vCCDkImygYBSIVgx1Y0CZJHhzn2Vf39v0Fbth2wDZa+OqxivV9NmDZEjmgcM+tTRx1CCnaww8LZEJQ68v/dUJy7hhD4MrNcQ62evJYPfT3+4EKKWJ/7HI7KXz91//+YAkph63XstbJMFJ3AldAFiq3ecauXt5r42N5gmCmwZX+sI48ENLGGAE=\"}}", - "event: content_block_stop\ndata: {\"type\":\"content_block_stop\",\"index\":0}", - "event: content_block_start\ndata: {\"type\":\"content_block_start\",\"index\":1,\"content_block\":{\"type\":\"text\",\"text\":\"\"}}", - "event: content_block_delta\ndata: {\"type\":\"content_block_delta\",\"index\":1,\"delta\":{\"type\":\"text_delta\",\"text\":\"Now proceeding to Step 3:\"}}", - "event: content_block_stop\ndata: {\"type\":\"content_block_stop\",\"index\":1}", - "event: content_block_start\ndata: {\"type\":\"content_block_start\",\"index\":2,\"content_block\":{\"type\":\"tool_use\",\"id\":\"toolu_01MRjt3UmRqrUYHt1ZC8gND9\",\"name\":\"summarize_source\",\"input\":{},\"caller\":{\"type\":\"direct\"}}}", - "event: content_block_delta\ndata: {\"type\":\"content_block_delta\",\"index\":2,\"delta\":{\"type\":\"input_json_delta\",\"partial_json\":\"\"}}", - "event: content_block_delta\ndata: {\"type\":\"content_block_delta\",\"index\":2,\"delta\":{\"type\":\"input_json_delta\",\"partial_json\":\"{\\\"url\\\": \\\"https://example.test/flue/reasoning-streams\"}}", - "event: content_block_delta\ndata: {\"type\":\"content_block_delta\",\"index\":2,\"delta\":{\"type\":\"input_json_delta\",\"partial_json\":\"\\\"}\"}}", - "event: content_block_stop\ndata: {\"type\":\"content_block_stop\",\"index\":2}", - "event: message_delta\ndata: {\"type\":\"message_delta\",\"delta\":{\"stop_reason\":\"tool_use\",\"stop_sequence\":null,\"stop_details\":null},\"usage\":{\"input_tokens\":13,\"cache_creation_input_tokens\":234,\"cache_read_input_tokens\":2458,\"output_tokens\":138,\"output_tokens_details\":{\"thinking_tokens\":61}}}", - "event: message_stop\ndata: {\"type\":\"message_stop\"}" - ], - "kind": "sse" - }, - "headers": { - "anthropic-organization-id": "27796668-7351-40ac-acc4-024aee8995a5", - "anthropic-ratelimit-input-tokens-limit": "3000000", - "anthropic-ratelimit-input-tokens-remaining": "2998000", - "anthropic-ratelimit-input-tokens-reset": "2026-05-28T13:40:54Z", - "anthropic-ratelimit-output-tokens-limit": "600000", - "anthropic-ratelimit-output-tokens-remaining": "600000", - "anthropic-ratelimit-output-tokens-reset": "2026-05-28T13:40:54Z", - "anthropic-ratelimit-requests-limit": "20000", - "anthropic-ratelimit-requests-remaining": "19999", - "anthropic-ratelimit-requests-reset": "2026-05-28T13:40:54Z", - "anthropic-ratelimit-tokens-limit": "3600000", - "anthropic-ratelimit-tokens-remaining": "3598000", - "anthropic-ratelimit-tokens-reset": "2026-05-28T13:40:54Z", - "cache-control": "no-cache", - "cf-cache-status": "DYNAMIC", - "cf-ray": "a02dac7d4a1fc301-VIE", - "connection": "keep-alive", - "content-encoding": "gzip", - "content-security-policy": "default-src 'none'; frame-ancestors 'none'", - "content-type": "text/event-stream; charset=utf-8", - "date": "Thu, 28 May 2026 13:40:56 GMT", - "request-id": "req_011CbV4ZUiiFpwboN3YtdzkH", - "server": "cloudflare", - "set-cookie": "[REDACTED]", - "strict-transport-security": "max-age=31536000; includeSubDomains; preload", - "traceresponse": "00-6392939f911bfe9568c361c78254c370-cb7c6141cf28bb77-01", - "transfer-encoding": "chunked", - "vary": "Accept-Encoding", - "x-envoy-upstream-service-time": "2757", - "x-robots-tag": "none" - }, - "status": 200, - "statusText": "OK" - } - }, - { - "callIndex": 3, - "id": "305957222aa1f6e9", - "matchKey": "POST api.anthropic.com/v1/messages", - "recordedAt": "2026-05-28T13:41:01.029Z", - "request": { - "body": { - "kind": "json", - "value": { - "max_tokens": 64000, - "messages": [ - { - "content": [ - { - "text": "Complete this instrumented research flow. Call exactly one tool per turn and wait for each tool result before choosing the next tool. Step 1: call lookup with query \"flue instrumentation\". Step 2: use the lookup result id as lookupId and call web_search with query \"Braintrust Flue reasoning stream instrumentation\". Step 3: use the first web_search result url and call summarize_source. After summarize_source returns, reply with exactly PROMPT_DONE and no other text.", - "type": "text" - } - ], - "role": "user" - }, - { - "content": [ - { - "signature": "EoQFCm4IDhgCKkD+AoKMhpXNPUJ5eANwM28uUCJPCE+AXh0ACw7aXniX+YN04zdJl3hlHMbtAuKhhczLiEeOJtu4KtVpea5u5NFdMhpjbGF1ZGUtc29ubmV0LTQtNS0yMDI1MDkyOTgAQgh0aGlua2luZxIMIOTWCNEtsiI9vF4SGgwPXC861uZx8lpOziEiMLsCukGIluD0U44degzYnHFc+BwXNIEB+yatSHlZE+a5smr9crZcft3OLmsIdXuaFSrDAyQxWd9oqfJdnvza9eWIaOCeNkVCjrTc/wotBwh5Xp8m9k/WKt7P4zzUJBuLQ1f6ZnsdWkL8OiL/2E7Up55z2+UuVWsg1Y90/x27YRFdQjv3RnztYp4bZ5oIHAlTJoCzWiBYEh/Z8c2O+wy6MHo1cp0T57OroB/4HAqRZxrjTOXpKYLmYKXAY5tU8DTd3A0/FfEhg3oPSbOhX+112szFCpm3BRNJYooUUhwSu1hO4H/6r7d/Yp9XIM4tBfzuDx83CC/YetdOFgnYkdliw0xaUGaj4o37rWIRctWHAMdlUc4NDA0Ljc1uirHDXkN8EJwWSoIjPSBTA/p/wgLrvRV5Yy9Z0dMoWOpgYF6t1H/tNAyY8ml1CA7IWyd3jnfTZ1/G3Y0xFDwJDFlb4/QKc67w36SSCa+X9mMoTxD/DYN249lsII+XX3lQ7F1zJ07THOtCyRirCaRhmE/kGwl5tiB1QHrWa9bNHxQxf9i92435JGKN053Fe14AR70MtBSzIU2/zbgvhB1vF5lZt2i46hNxfo4lXR495gEwdwC6+8HJnlEJAJkdeHfZKj+RiwXoiYKPKsSbHi83X8Hh32IifuglNxdlxVQYAQ==", - "thinking": "The user wants me to complete a research flow with exactly three steps:\n1. Call lookup with query \"flue instrumentation\"\n2. Call web_search with the lookupId from step 1 and query \"Braintrust Flue reasoning stream instrumentation\"\n3. Call summarize_source with the first URL from step 2\n4. Reply with exactly \"PROMPT_DONE\"\n\nThe user emphasizes I should call exactly one tool per turn and wait for each result. Let me start with step 1.", - "type": "thinking" - }, - { - "text": "I'll complete this instrumented research flow step by step. Starting with Step 1:", - "type": "text" - }, - { - "id": "toolu_01MbotvqKgQV4ecxQ566vyuC", - "input": { - "query": "flue instrumentation" - }, - "name": "lookup", - "type": "tool_use" - } - ], - "role": "assistant" - }, - { - "content": [ - { - "content": "{\"id\":\"flue-session-2026\",\"query\":\"flue instrumentation\",\"topic\":\"session instrumentation\"}", - "is_error": false, - "tool_use_id": "toolu_01MbotvqKgQV4ecxQ566vyuC", - "type": "tool_result" - } - ], - "role": "user" - }, - { - "content": [ - { - "signature": "EpEDCm4IDhgCKkAMahve1kB1j4njp1ZSzO8eCK4bb5Ex7MB6tjdoOXfuUDRfkBqvIGdwVly8GiA8GGYC7Y/RVfREXF6A6OXHTwFyMhpjbGF1ZGUtc29ubmV0LTQtNS0yMDI1MDkyOTgAQgh0aGlua2luZxIMLw+fsa4yo7a7ZSoIGgyaTdvH020n2dQj9bUiMB4OokY29w+uZb4rqDnKDbLXUqUj6U0f1QCGQF2ZBYDDfDJyM5Jv1xY3rQiUUjkhBirQAS4XivjvMnWI5zHBmpI/u/pq4aGdrdb3vUsJ/ne6R1d80JA/INGF8vLkQcZ5xxEK0jU4yRE+ldc2uzavmtoxIKAFD6bqKks1Qv6u+TIZtuW7tUB/7jFxDw93KmJNruMIa8c1J6pPUyBy3xE7QwgmU0kDfIfdnljxLoEBiMwtnxjCPj9lT7HHz4FkrhbnQTV4MvAQohCLjaSbOTd6LdLxwtJPm9mc26ID4eYN1gCCzyAxGXhqLHlzc23PafvQjsZliWnLhaiNMnG0YEo78XkuJQsYAQ==", - "thinking": "Good, I got the lookup result with id \"flue-session-2026\". Now I need to proceed to step 2: call web_search with this lookupId and the query \"Braintrust Flue reasoning stream instrumentation\".", - "type": "thinking" - }, - { - "text": "Now proceeding to Step 2:", - "type": "text" - }, - { - "id": "toolu_018rc1ig7vXeron3rLfpUtRT", - "input": { - "lookupId": "flue-session-2026", - "query": "Braintrust Flue reasoning stream instrumentation" - }, - "name": "web_search", - "type": "tool_use" - } - ], - "role": "assistant" - }, - { - "content": [ - { - "content": "{\"lookupId\":\"flue-session-2026\",\"query\":\"Braintrust Flue reasoning stream instrumentation\",\"results\":[{\"title\":\"Flue reasoning stream instrumentation\",\"url\":\"https://example.test/flue/reasoning-streams\"}]}", - "is_error": false, - "tool_use_id": "toolu_018rc1ig7vXeron3rLfpUtRT", - "type": "tool_result" - } - ], - "role": "user" - }, - { - "content": [ - { - "signature": "EqQDCm4IDhgCKkAXv7EDAhWMcrBz11bUd5qxlzyy03CAGQfRgLHDE0uKzLATh1o2sedd8GxLwwQqV0CmLRfNxsdK/ZDp3Lks9iysMhpjbGF1ZGUtc29ubmV0LTQtNS0yMDI1MDkyOTgAQgh0aGlua2luZxIMXDMniOy5Dy0KI8x7GgzgHs7BYJMs6/N0ur8iMPe0SwnHhP39OjEu7P8ASwK9ffwa3VP67QOlPzOstRRdEZNd0mq+BVjpmBXB+QcAzCrjAeAh/UO/d7SfFmXjqwA3//thoL/TTlGa6W+fdOCFRikn65JWT4ZdVFetfqD+9/QCP/nXEwMsLnTBAxC4EGryrgs2YT+OWwmEJ7rHKFeDs5djhwSb2J2ZJO1kRvD9s2vCCDkImygYBSIVgx1Y0CZJHhzn2Vf39v0Fbth2wDZa+OqxivV9NmDZEjmgcM+tTRx1CCnaww8LZEJQ68v/dUJy7hhD4MrNcQ62evJYPfT3+4EKKWJ/7HI7KXz91//+YAkph63XstbJMFJ3AldAFiq3ecauXt5r42N5gmCmwZX+sI48ENLGGAE=", - "thinking": "Perfect! I got the web_search results with one result. The first (and only) result has the URL \"https://example.test/flue/reasoning-streams\". Now I need to proceed to step 3: call summarize_source with this URL.", - "type": "thinking" - }, - { - "text": "Now proceeding to Step 3:", - "type": "text" - }, - { - "id": "toolu_01MRjt3UmRqrUYHt1ZC8gND9", - "input": { - "url": "https://example.test/flue/reasoning-streams" - }, - "name": "summarize_source", - "type": "tool_use" - } - ], - "role": "assistant" - }, - { - "content": [ - { - "cache_control": { - "type": "ephemeral" - }, - "content": "{\"summary\":\"Flue emits reasoning, tool execution, and LLM turn events separately.\",\"url\":\"https://example.test/flue/reasoning-streams\"}", - "is_error": false, - "tool_use_id": "toolu_01MRjt3UmRqrUYHt1ZC8gND9", - "type": "tool_result" - } - ], - "role": "user" - } - ], - "model": "claude-sonnet-4-5-20250929", - "stream": true, - "system": [ - { - "cache_control": { - "type": "ephemeral" - }, - "text": "You are running in headless mode with no human operator. Work autonomously — never ask questions, never wait for user input. Make your best judgment and proceed independently.\n\nYou are a deterministic Flue instrumentation test agent. Follow user instructions exactly. When asked for a marker, output only that marker and no extra text. When running a local skill file, read it yourself and do not delegate it to a task.\n\n## Available Skills\n\nThe following skills provide specialized instructions for specific tasks. When a task matches a skill description, activate that skill before proceeding so its full instructions are loaded. Skill instructions and supporting resources stay lazy until activation or explicit file reads.\n\n- **e2e-flue-skill** — Deterministic skill used by the Braintrust Flue instrumentation e2e test.\n\nDate: Thu, May 28, 2026\nWorking directory: /Users/lucaforstner/conductor/workspaces/braintrust-sdk-javascript/shanghai-v1/e2e/.bt-tmp/run-u9uXnv/scenarios/flue-instrumentation\n\nDirectory structure:\n.agents\n__cassettes__\n__snapshots__\nassertions.ts\ncassette-filter.mjs\nconstants.mjs\nnode_modules\npackage.json\npnpm-lock.yaml\nscenario.impl.mjs\nscenario.mjs\nscenario.test.ts\nscenario.ts", - "type": "text" - } - ], - "thinking": { - "budget_tokens": 8192, - "display": "summarized", - "type": "enabled" - }, - "tools": [ - { - "description": "Read a file or list a directory. For files, output is truncated to 2000 lines or 50KB — use offset/limit for large files. For directories, returns the list of entries.", - "eager_input_streaming": true, - "input_schema": { - "properties": { - "limit": { - "description": "Maximum number of lines to read", - "type": "number" - }, - "offset": { - "description": "Line number to start from (1-indexed)", - "type": "number" - }, - "path": { - "description": "Path to the file to read", - "type": "string" - } - }, - "required": ["path"], - "type": "object" - }, - "name": "read" - }, - { - "description": "Write content to a file. Creates the file and parent directories if they do not exist.", - "eager_input_streaming": true, - "input_schema": { - "properties": { - "content": { - "description": "Content to write to the file", - "type": "string" - }, - "path": { - "description": "Path to the file to write", - "type": "string" - } - }, - "required": ["path", "content"], - "type": "object" - }, - "name": "write" - }, - { - "description": "Edit a file using exact text replacement. The oldText must match a unique region of the file. Use replaceAll to replace all occurrences.", - "eager_input_streaming": true, - "input_schema": { - "properties": { - "newText": { - "description": "Replacement text", - "type": "string" - }, - "oldText": { - "description": "Exact text to find (must be unique)", - "type": "string" - }, - "path": { - "description": "Path to the file to edit", - "type": "string" - }, - "replaceAll": { - "description": "Replace all occurrences", - "type": "boolean" - } - }, - "required": ["path", "oldText", "newText"], - "type": "object" - }, - "name": "edit" - }, - { - "description": "Execute a bash command. Returns stdout and stderr. Output is truncated to the last 2000 lines or 50KB.", - "eager_input_streaming": true, - "input_schema": { - "properties": { - "command": { - "description": "Bash command to execute", - "type": "string" - }, - "timeout": { - "description": "Timeout in seconds", - "type": "number" - } - }, - "required": ["command"], - "type": "object" - }, - "name": "bash" - }, - { - "description": "Search file contents for a regex pattern. Returns matching lines with file paths and line numbers.", - "eager_input_streaming": true, - "input_schema": { - "properties": { - "include": { - "description": "Glob filter, e.g. \"*.ts\"", - "type": "string" - }, - "path": { - "description": "Directory or file to search (default: .)", - "type": "string" - }, - "pattern": { - "description": "Search pattern (regex)", - "type": "string" - } - }, - "required": ["pattern"], - "type": "object" - }, - "name": "grep" - }, - { - "description": "Find files by filename pattern using shell find -name semantics. Returns matching file paths.", - "eager_input_streaming": true, - "input_schema": { - "properties": { - "path": { - "description": "Directory to search in (default: .)", - "type": "string" - }, - "pattern": { - "description": "Filename pattern, e.g. \"*.ts\"", - "type": "string" - } - }, - "required": ["pattern"], - "type": "object" - }, - "name": "glob" - }, - { - "description": "Delegate a focused task to a detached child agent with its own context. Use this for independent research, file exploration, or parallel work. The task returns only its final answer to this conversation. No subagents are currently defined.", - "eager_input_streaming": true, - "input_schema": { - "properties": { - "agent": { - "description": "Declared subagent to use for the child agent", - "type": "string" - }, - "cwd": { - "description": "Working directory for the child agent. AGENTS.md and skills are discovered from here.", - "type": "string" - }, - "description": { - "description": "Short human-readable label for the delegated work", - "type": "string" - }, - "prompt": { - "description": "Focused instructions for the child agent", - "type": "string" - } - }, - "required": ["prompt"], - "type": "object" - }, - "name": "task" - }, - { - "description": "Return a deterministic lookup result with an id needed by web_search.", - "eager_input_streaming": true, - "input_schema": { - "properties": { - "query": { - "type": "string" - } - }, - "required": ["query"], - "type": "object" - }, - "name": "lookup" - }, - { - "description": "Search a deterministic local web index. Requires the id returned by lookup.", - "eager_input_streaming": true, - "input_schema": { - "properties": { - "lookupId": { - "type": "string" - }, - "query": { - "type": "string" - } - }, - "required": ["lookupId", "query"], - "type": "object" - }, - "name": "web_search" - }, - { - "cache_control": { - "type": "ephemeral" - }, - "description": "Summarize the selected deterministic source after web_search returns a URL.", - "eager_input_streaming": true, - "input_schema": { - "properties": { - "url": { - "type": "string" - } - }, - "required": ["url"], - "type": "object" - }, - "name": "summarize_source" - } - ] - } - }, - "headers": {}, - "method": "POST", - "url": "https://api.anthropic.com/v1/messages" - }, - "response": { - "body": { - "chunks": [ - "event: message_start\ndata: {\"type\":\"message_start\",\"message\":{\"model\":\"claude-sonnet-4-5-20250929\",\"id\":\"msg_019YC1YEtmEwfw9dyuY3MPdx\",\"type\":\"message\",\"role\":\"assistant\",\"content\":[],\"stop_reason\":null,\"stop_sequence\":null,\"stop_details\":null,\"usage\":{\"input_tokens\":13,\"cache_creation_input_tokens\":194,\"cache_read_input_tokens\":2692,\"cache_creation\":{\"ephemeral_5m_input_tokens\":194,\"ephemeral_1h_input_tokens\":0},\"output_tokens\":1,\"service_tier\":\"standard\",\"inference_geo\":\"not_available\"}}}", - "event: content_block_start\ndata: {\"type\":\"content_block_start\",\"index\":0,\"content_block\":{\"type\":\"thinking\",\"thinking\":\"\",\"signature\":\"\"}}", - "event: ping\ndata: {\"type\":\"ping\"}", - "event: content_block_delta\ndata: {\"type\":\"content_block_delta\",\"index\":0,\"delta\":{\"type\":\"thinking_delta\",\"thinking\":\"Perfect\"}}", - "event: content_block_delta\ndata: {\"type\":\"content_block_delta\",\"index\":0,\"delta\":{\"type\":\"thinking_delta\",\"thinking\":\"! The summarize_source call has completed successfully. According to the instructions, I should now reply with exactly \\\"PROMPT_DONE\\\" and no other text.\"}}", - "event: content_block_delta\ndata: {\"type\":\"content_block_delta\",\"index\":0,\"delta\":{\"type\":\"thinking_delta\",\"thinking\":\"\"}}", - "event: content_block_delta\ndata: {\"type\":\"content_block_delta\",\"index\":0,\"delta\":{\"type\":\"signature_delta\",\"signature\":\"Eu8CCm4IDhgCKkAn2QOIIlYfvcs6ynsmhCslHDtBsrTA4CfmimK7zyz6+k6dZldV2JBtEBET41It0gFcGEKC0n0mAhaYVuLutpDDMhpjbGF1ZGUtc29ubmV0LTQtNS0yMDI1MDkyOTgAQgh0aGlua2luZxIM1xfLptxSF8rKzou8GgwRLuVmfiBYOT4CZIsiMAYz8TpvchMAWodP+T9uZ+dJ47MVdNv+p86nZXmT5d5EWlCroiAOFHHzJH2o0UjJQSquAX6wu+jIC8/DXwurGIIf2ToldPuJWF5HGj8kHMnwh777XS1NUFl1irM24rvP7kTAv3Dp8YEwb8t3g1EsicY7j+C5DzgwxVsPYCVOCgdMqQjtsqpL7AX5KQ9+hR1gK1DQjshv4Gze3hw/+C+9iptd3RlDIuwO0cm8NY+sVqeWVh3TtQujQlVt1rW/HfWjclrtT0LZSGwc6RypmfMMQi6lncIk/EPgy0XbS1plKW0tLBgB\"}}", - "event: content_block_stop\ndata: {\"type\":\"content_block_stop\",\"index\":0}", - "event: content_block_start\ndata: {\"type\":\"content_block_start\",\"index\":1,\"content_block\":{\"type\":\"text\",\"text\":\"\"}}", - "event: content_block_delta\ndata: {\"type\":\"content_block_delta\",\"index\":1,\"delta\":{\"type\":\"text_delta\",\"text\":\"PROMPT_DONE\"}}", - "event: content_block_stop\ndata: {\"type\":\"content_block_stop\",\"index\":1}", - "event: message_delta\ndata: {\"type\":\"message_delta\",\"delta\":{\"stop_reason\":\"end_turn\",\"stop_sequence\":null,\"stop_details\":null},\"usage\":{\"input_tokens\":13,\"cache_creation_input_tokens\":194,\"cache_read_input_tokens\":2692,\"output_tokens\":51,\"output_tokens_details\":{\"thinking_tokens\":40}}}", - "event: message_stop\ndata: {\"type\":\"message_stop\"}" - ], - "kind": "sse" - }, - "headers": { - "anthropic-organization-id": "27796668-7351-40ac-acc4-024aee8995a5", - "anthropic-ratelimit-input-tokens-limit": "3000000", - "anthropic-ratelimit-input-tokens-remaining": "2998000", - "anthropic-ratelimit-input-tokens-reset": "2026-05-28T13:40:58Z", - "anthropic-ratelimit-output-tokens-limit": "600000", - "anthropic-ratelimit-output-tokens-remaining": "600000", - "anthropic-ratelimit-output-tokens-reset": "2026-05-28T13:40:58Z", - "anthropic-ratelimit-requests-limit": "20000", - "anthropic-ratelimit-requests-remaining": "19999", - "anthropic-ratelimit-requests-reset": "2026-05-28T13:40:58Z", - "anthropic-ratelimit-tokens-limit": "3600000", - "anthropic-ratelimit-tokens-remaining": "3598000", - "anthropic-ratelimit-tokens-reset": "2026-05-28T13:40:58Z", - "cache-control": "no-cache", - "cf-cache-status": "DYNAMIC", - "cf-ray": "a02dac9afd8fc301-VIE", - "connection": "keep-alive", - "content-encoding": "gzip", - "content-security-policy": "default-src 'none'; frame-ancestors 'none'", - "content-type": "text/event-stream; charset=utf-8", - "date": "Thu, 28 May 2026 13:41:00 GMT", - "request-id": "req_011CbV4Zq3L7gJxP1Y46qapx", - "server": "cloudflare", - "set-cookie": "[REDACTED]", - "strict-transport-security": "max-age=31536000; includeSubDomains; preload", - "traceresponse": "00-e0b4a5e0f6b8f2625262dec81a52e61e-c551de5bb2f6f203-01", - "transfer-encoding": "chunked", - "vary": "Accept-Encoding", - "x-envoy-upstream-service-time": "1421", - "x-robots-tag": "none" - }, - "status": 200, - "statusText": "OK" - } - }, - { - "callIndex": 4, - "id": "3d93fe37f72a947f", - "matchKey": "POST api.anthropic.com/v1/messages", - "recordedAt": "2026-05-28T13:41:05.303Z", - "request": { - "body": { - "kind": "json", - "value": { - "max_tokens": 64000, - "messages": [ - { - "content": [ - { - "cache_control": { - "type": "ephemeral" - }, - "text": "Run the skill named \"e2e-flue-skill\".\n\nArguments:\n{\n \"marker\": \"SKILL_DONE\"\n}", - "type": "text" - } - ], - "role": "user" - } - ], - "model": "claude-sonnet-4-5-20250929", - "stream": true, - "system": [ - { - "cache_control": { - "type": "ephemeral" - }, - "text": "You are running in headless mode with no human operator. Work autonomously — never ask questions, never wait for user input. Make your best judgment and proceed independently.\n\nYou are a deterministic Flue instrumentation test agent. Follow user instructions exactly. When asked for a marker, output only that marker and no extra text. When running a local skill file, read it yourself and do not delegate it to a task.\n\n## Available Skills\n\nThe following skills provide specialized instructions for specific tasks. When a task matches a skill description, activate that skill before proceeding so its full instructions are loaded. Skill instructions and supporting resources stay lazy until activation or explicit file reads.\n\n- **e2e-flue-skill** — Deterministic skill used by the Braintrust Flue instrumentation e2e test.\n\nDate: Thu, May 28, 2026\nWorking directory: /Users/lucaforstner/conductor/workspaces/braintrust-sdk-javascript/shanghai-v1/e2e/.bt-tmp/run-u9uXnv/scenarios/flue-instrumentation\n\nDirectory structure:\n.agents\n__cassettes__\n__snapshots__\nassertions.ts\ncassette-filter.mjs\nconstants.mjs\nnode_modules\npackage.json\npnpm-lock.yaml\nscenario.impl.mjs\nscenario.mjs\nscenario.test.ts\nscenario.ts", - "type": "text" - } - ], - "thinking": { - "type": "disabled" - }, - "tools": [ - { - "description": "Read a file or list a directory. For files, output is truncated to 2000 lines or 50KB — use offset/limit for large files. For directories, returns the list of entries.", - "eager_input_streaming": true, - "input_schema": { - "properties": { - "limit": { - "description": "Maximum number of lines to read", - "type": "number" - }, - "offset": { - "description": "Line number to start from (1-indexed)", - "type": "number" - }, - "path": { - "description": "Path to the file to read", - "type": "string" - } - }, - "required": ["path"], - "type": "object" - }, - "name": "read" - }, - { - "description": "Write content to a file. Creates the file and parent directories if they do not exist.", - "eager_input_streaming": true, - "input_schema": { - "properties": { - "content": { - "description": "Content to write to the file", - "type": "string" - }, - "path": { - "description": "Path to the file to write", - "type": "string" - } - }, - "required": ["path", "content"], - "type": "object" - }, - "name": "write" - }, - { - "description": "Edit a file using exact text replacement. The oldText must match a unique region of the file. Use replaceAll to replace all occurrences.", - "eager_input_streaming": true, - "input_schema": { - "properties": { - "newText": { - "description": "Replacement text", - "type": "string" - }, - "oldText": { - "description": "Exact text to find (must be unique)", - "type": "string" - }, - "path": { - "description": "Path to the file to edit", - "type": "string" - }, - "replaceAll": { - "description": "Replace all occurrences", - "type": "boolean" - } - }, - "required": ["path", "oldText", "newText"], - "type": "object" - }, - "name": "edit" - }, - { - "description": "Execute a bash command. Returns stdout and stderr. Output is truncated to the last 2000 lines or 50KB.", - "eager_input_streaming": true, - "input_schema": { - "properties": { - "command": { - "description": "Bash command to execute", - "type": "string" - }, - "timeout": { - "description": "Timeout in seconds", - "type": "number" - } - }, - "required": ["command"], - "type": "object" - }, - "name": "bash" - }, - { - "description": "Search file contents for a regex pattern. Returns matching lines with file paths and line numbers.", - "eager_input_streaming": true, - "input_schema": { - "properties": { - "include": { - "description": "Glob filter, e.g. \"*.ts\"", - "type": "string" - }, - "path": { - "description": "Directory or file to search (default: .)", - "type": "string" - }, - "pattern": { - "description": "Search pattern (regex)", - "type": "string" - } - }, - "required": ["pattern"], - "type": "object" - }, - "name": "grep" - }, - { - "description": "Find files by filename pattern using shell find -name semantics. Returns matching file paths.", - "eager_input_streaming": true, - "input_schema": { - "properties": { - "path": { - "description": "Directory to search in (default: .)", - "type": "string" - }, - "pattern": { - "description": "Filename pattern, e.g. \"*.ts\"", - "type": "string" - } - }, - "required": ["pattern"], - "type": "object" - }, - "name": "glob" - }, - { - "cache_control": { - "type": "ephemeral" - }, - "description": "Delegate a focused task to a detached child agent with its own context. Use this for independent research, file exploration, or parallel work. The task returns only its final answer to this conversation. No subagents are currently defined.", - "eager_input_streaming": true, - "input_schema": { - "properties": { - "agent": { - "description": "Declared subagent to use for the child agent", - "type": "string" - }, - "cwd": { - "description": "Working directory for the child agent. AGENTS.md and skills are discovered from here.", - "type": "string" - }, - "description": { - "description": "Short human-readable label for the delegated work", - "type": "string" - }, - "prompt": { - "description": "Focused instructions for the child agent", - "type": "string" - } - }, - "required": ["prompt"], - "type": "object" - }, - "name": "task" - } - ] - } - }, - "headers": {}, - "method": "POST", - "url": "https://api.anthropic.com/v1/messages" - }, - "response": { - "body": { - "chunks": [ - "event: message_start\ndata: {\"type\":\"message_start\",\"message\":{\"model\":\"claude-sonnet-4-5-20250929\",\"id\":\"msg_01SoNm9ntxkXQkRrBSidrXSB\",\"type\":\"message\",\"role\":\"assistant\",\"content\":[],\"stop_reason\":null,\"stop_sequence\":null,\"stop_details\":null,\"usage\":{\"input_tokens\":3,\"cache_creation_input_tokens\":670,\"cache_read_input_tokens\":1099,\"cache_creation\":{\"ephemeral_5m_input_tokens\":670,\"ephemeral_1h_input_tokens\":0},\"output_tokens\":5,\"service_tier\":\"standard\",\"inference_geo\":\"not_available\"}}}", - "event: content_block_start\ndata: {\"type\":\"content_block_start\",\"index\":0,\"content_block\":{\"type\":\"text\",\"text\":\"\"}}", - "event: ping\ndata: {\"type\":\"ping\"}", - "event: content_block_delta\ndata: {\"type\":\"content_block_delta\",\"index\":0,\"delta\":{\"type\":\"text_delta\",\"text\":\"I'll run the e\"}}", - "event: content_block_delta\ndata: {\"type\":\"content_block_delta\",\"index\":0,\"delta\":{\"type\":\"text_delta\",\"text\":\"2e-flue-skill. Let me first read the skill file to understand its instructions.\"}}", - "event: content_block_stop\ndata: {\"type\":\"content_block_stop\",\"index\":0}", - "event: content_block_start\ndata: {\"type\":\"content_block_start\",\"index\":1,\"content_block\":{\"type\":\"tool_use\",\"id\":\"toolu_0116y5v4MmMkEaDkjkEPPjTH\",\"name\":\"read\",\"input\":{},\"caller\":{\"type\":\"direct\"}}}", - "event: content_block_delta\ndata: {\"type\":\"content_block_delta\",\"index\":1,\"delta\":{\"type\":\"input_json_delta\",\"partial_json\":\"\"}}", - "event: content_block_delta\ndata: {\"type\":\"content_block_delta\",\"index\":1,\"delta\":{\"type\":\"input_json_delta\",\"partial_json\":\"{\\\"path\\\": \\\".agents/e2e-flue\"}}", - "event: content_block_delta\ndata: {\"type\":\"content_block_delta\",\"index\":1,\"delta\":{\"type\":\"input_json_delta\",\"partial_json\":\"-skill.md\"}}", - "event: content_block_delta\ndata: {\"type\":\"content_block_delta\",\"index\":1,\"delta\":{\"type\":\"input_json_delta\",\"partial_json\":\"\\\"}\"}}", - "event: content_block_stop\ndata: {\"type\":\"content_block_stop\",\"index\":1}", - "event: message_delta\ndata: {\"type\":\"message_delta\",\"delta\":{\"stop_reason\":\"tool_use\",\"stop_sequence\":null,\"stop_details\":null},\"usage\":{\"input_tokens\":3,\"cache_creation_input_tokens\":670,\"cache_read_input_tokens\":1099,\"output_tokens\":89}}", - "event: message_stop\ndata: {\"type\":\"message_stop\"}" - ], - "kind": "sse" - }, - "headers": { - "anthropic-organization-id": "27796668-7351-40ac-acc4-024aee8995a5", - "anthropic-ratelimit-input-tokens-limit": "3000000", - "anthropic-ratelimit-input-tokens-remaining": "2999000", - "anthropic-ratelimit-input-tokens-reset": "2026-05-28T13:41:01Z", - "anthropic-ratelimit-output-tokens-limit": "600000", - "anthropic-ratelimit-output-tokens-remaining": "600000", - "anthropic-ratelimit-output-tokens-reset": "2026-05-28T13:41:01Z", - "anthropic-ratelimit-requests-limit": "20000", - "anthropic-ratelimit-requests-remaining": "19999", - "anthropic-ratelimit-requests-reset": "2026-05-28T13:41:01Z", - "anthropic-ratelimit-tokens-limit": "3600000", - "anthropic-ratelimit-tokens-remaining": "3599000", - "anthropic-ratelimit-tokens-reset": "2026-05-28T13:41:01Z", - "cache-control": "no-cache", - "cf-cache-status": "DYNAMIC", - "cf-ray": "a02dacab0e1fc301-VIE", - "connection": "keep-alive", - "content-encoding": "gzip", - "content-security-policy": "default-src 'none'; frame-ancestors 'none'", - "content-type": "text/event-stream; charset=utf-8", - "date": "Thu, 28 May 2026 13:41:04 GMT", - "request-id": "req_011CbV4a2cMY5XByBTCzUoHh", - "server": "cloudflare", - "set-cookie": "[REDACTED]", - "strict-transport-security": "max-age=31536000; includeSubDomains; preload", - "traceresponse": "00-e05c9702b7456a4b1c655ed13d92ba42-b48b861130669b0c-01", - "transfer-encoding": "chunked", - "vary": "Accept-Encoding", - "x-envoy-upstream-service-time": "2864", - "x-robots-tag": "none" - }, - "status": 200, - "statusText": "OK" - } - }, - { - "callIndex": 5, - "id": "fb4ed2bb1569d980", - "matchKey": "POST api.anthropic.com/v1/messages", - "recordedAt": "2026-05-28T13:41:08.068Z", - "request": { - "body": { - "kind": "json", - "value": { - "max_tokens": 64000, - "messages": [ - { - "content": [ - { - "text": "Run the skill named \"e2e-flue-skill\".\n\nArguments:\n{\n \"marker\": \"SKILL_DONE\"\n}", - "type": "text" - } - ], - "role": "user" - }, - { - "content": [ - { - "text": "I'll run the e2e-flue-skill. Let me first read the skill file to understand its instructions.", - "type": "text" - }, - { - "id": "toolu_0116y5v4MmMkEaDkjkEPPjTH", - "input": { - "path": ".agents/e2e-flue-skill.md" - }, - "name": "read", - "type": "tool_use" - } - ], - "role": "assistant" - }, - { - "content": [ - { - "cache_control": { - "type": "ephemeral" - }, - "content": "ENOENT: no such file or directory, open '/Users/lucaforstner/conductor/workspaces/braintrust-sdk-javascript/shanghai-v1/e2e/.bt-tmp/run-u9uXnv/scenarios/flue-instrumentation/.agents/e2e-flue-skill.md'", - "is_error": true, - "tool_use_id": "toolu_0116y5v4MmMkEaDkjkEPPjTH", - "type": "tool_result" - } - ], - "role": "user" - } - ], - "model": "claude-sonnet-4-5-20250929", - "stream": true, - "system": [ - { - "cache_control": { - "type": "ephemeral" - }, - "text": "You are running in headless mode with no human operator. Work autonomously — never ask questions, never wait for user input. Make your best judgment and proceed independently.\n\nYou are a deterministic Flue instrumentation test agent. Follow user instructions exactly. When asked for a marker, output only that marker and no extra text. When running a local skill file, read it yourself and do not delegate it to a task.\n\n## Available Skills\n\nThe following skills provide specialized instructions for specific tasks. When a task matches a skill description, activate that skill before proceeding so its full instructions are loaded. Skill instructions and supporting resources stay lazy until activation or explicit file reads.\n\n- **e2e-flue-skill** — Deterministic skill used by the Braintrust Flue instrumentation e2e test.\n\nDate: Thu, May 28, 2026\nWorking directory: /Users/lucaforstner/conductor/workspaces/braintrust-sdk-javascript/shanghai-v1/e2e/.bt-tmp/run-u9uXnv/scenarios/flue-instrumentation\n\nDirectory structure:\n.agents\n__cassettes__\n__snapshots__\nassertions.ts\ncassette-filter.mjs\nconstants.mjs\nnode_modules\npackage.json\npnpm-lock.yaml\nscenario.impl.mjs\nscenario.mjs\nscenario.test.ts\nscenario.ts", - "type": "text" - } - ], - "thinking": { - "type": "disabled" - }, - "tools": [ - { - "description": "Read a file or list a directory. For files, output is truncated to 2000 lines or 50KB — use offset/limit for large files. For directories, returns the list of entries.", - "eager_input_streaming": true, - "input_schema": { - "properties": { - "limit": { - "description": "Maximum number of lines to read", - "type": "number" - }, - "offset": { - "description": "Line number to start from (1-indexed)", - "type": "number" - }, - "path": { - "description": "Path to the file to read", - "type": "string" - } - }, - "required": ["path"], - "type": "object" - }, - "name": "read" - }, - { - "description": "Write content to a file. Creates the file and parent directories if they do not exist.", - "eager_input_streaming": true, - "input_schema": { - "properties": { - "content": { - "description": "Content to write to the file", - "type": "string" - }, - "path": { - "description": "Path to the file to write", - "type": "string" - } - }, - "required": ["path", "content"], - "type": "object" - }, - "name": "write" - }, - { - "description": "Edit a file using exact text replacement. The oldText must match a unique region of the file. Use replaceAll to replace all occurrences.", - "eager_input_streaming": true, - "input_schema": { - "properties": { - "newText": { - "description": "Replacement text", - "type": "string" - }, - "oldText": { - "description": "Exact text to find (must be unique)", - "type": "string" - }, - "path": { - "description": "Path to the file to edit", - "type": "string" - }, - "replaceAll": { - "description": "Replace all occurrences", - "type": "boolean" - } - }, - "required": ["path", "oldText", "newText"], - "type": "object" - }, - "name": "edit" - }, - { - "description": "Execute a bash command. Returns stdout and stderr. Output is truncated to the last 2000 lines or 50KB.", - "eager_input_streaming": true, - "input_schema": { - "properties": { - "command": { - "description": "Bash command to execute", - "type": "string" - }, - "timeout": { - "description": "Timeout in seconds", - "type": "number" - } - }, - "required": ["command"], - "type": "object" - }, - "name": "bash" - }, - { - "description": "Search file contents for a regex pattern. Returns matching lines with file paths and line numbers.", - "eager_input_streaming": true, - "input_schema": { - "properties": { - "include": { - "description": "Glob filter, e.g. \"*.ts\"", - "type": "string" - }, - "path": { - "description": "Directory or file to search (default: .)", - "type": "string" - }, - "pattern": { - "description": "Search pattern (regex)", - "type": "string" - } - }, - "required": ["pattern"], - "type": "object" - }, - "name": "grep" - }, - { - "description": "Find files by filename pattern using shell find -name semantics. Returns matching file paths.", - "eager_input_streaming": true, - "input_schema": { - "properties": { - "path": { - "description": "Directory to search in (default: .)", - "type": "string" - }, - "pattern": { - "description": "Filename pattern, e.g. \"*.ts\"", - "type": "string" - } - }, - "required": ["pattern"], - "type": "object" - }, - "name": "glob" - }, - { - "cache_control": { - "type": "ephemeral" - }, - "description": "Delegate a focused task to a detached child agent with its own context. Use this for independent research, file exploration, or parallel work. The task returns only its final answer to this conversation. No subagents are currently defined.", - "eager_input_streaming": true, - "input_schema": { - "properties": { - "agent": { - "description": "Declared subagent to use for the child agent", - "type": "string" - }, - "cwd": { - "description": "Working directory for the child agent. AGENTS.md and skills are discovered from here.", - "type": "string" - }, - "description": { - "description": "Short human-readable label for the delegated work", - "type": "string" - }, - "prompt": { - "description": "Focused instructions for the child agent", - "type": "string" - } - }, - "required": ["prompt"], - "type": "object" - }, - "name": "task" - } - ] - } - }, - "headers": {}, - "method": "POST", - "url": "https://api.anthropic.com/v1/messages" - }, - "response": { - "body": { - "chunks": [ - "event: message_start\ndata: {\"type\":\"message_start\",\"message\":{\"model\":\"claude-sonnet-4-5-20250929\",\"id\":\"msg_01HWMEiz1LLcSzQG6KkyGVxZ\",\"type\":\"message\",\"role\":\"assistant\",\"content\":[],\"stop_reason\":null,\"stop_sequence\":null,\"stop_details\":null,\"usage\":{\"input_tokens\":7,\"cache_creation_input_tokens\":183,\"cache_read_input_tokens\":1769,\"cache_creation\":{\"ephemeral_5m_input_tokens\":183,\"ephemeral_1h_input_tokens\":0},\"output_tokens\":52,\"service_tier\":\"standard\",\"inference_geo\":\"not_available\"}}}", - "event: content_block_start\ndata: {\"type\":\"content_block_start\",\"index\":0,\"content_block\":{\"type\":\"tool_use\",\"id\":\"toolu_01VLK49ybvQa2MZoZ8FxbJCx\",\"name\":\"read\",\"input\":{},\"caller\":{\"type\":\"direct\"}}}", - "event: ping\ndata: {\"type\":\"ping\"}", - "event: content_block_delta\ndata: {\"type\":\"content_block_delta\",\"index\":0,\"delta\":{\"type\":\"input_json_delta\",\"partial_json\":\"\"}}", - "event: content_block_delta\ndata: {\"type\":\"content_block_delta\",\"index\":0,\"delta\":{\"type\":\"input_json_delta\",\"partial_json\":\"{\\\"path\\\": \\\".agents\"}}", - "event: content_block_delta\ndata: {\"type\":\"content_block_delta\",\"index\":0,\"delta\":{\"type\":\"input_json_delta\",\"partial_json\":\"\\\"}\"}}", - "event: content_block_stop\ndata: {\"type\":\"content_block_stop\",\"index\":0}", - "event: message_delta\ndata: {\"type\":\"message_delta\",\"delta\":{\"stop_reason\":\"tool_use\",\"stop_sequence\":null,\"stop_details\":null},\"usage\":{\"input_tokens\":7,\"cache_creation_input_tokens\":183,\"cache_read_input_tokens\":1769,\"output_tokens\":52}}", - "event: message_stop\ndata: {\"type\":\"message_stop\"}" - ], - "kind": "sse" - }, - "headers": { - "anthropic-organization-id": "27796668-7351-40ac-acc4-024aee8995a5", - "anthropic-ratelimit-input-tokens-limit": "3000000", - "anthropic-ratelimit-input-tokens-remaining": "2999000", - "anthropic-ratelimit-input-tokens-reset": "2026-05-28T13:41:05Z", - "anthropic-ratelimit-output-tokens-limit": "600000", - "anthropic-ratelimit-output-tokens-remaining": "600000", - "anthropic-ratelimit-output-tokens-reset": "2026-05-28T13:41:05Z", - "anthropic-ratelimit-requests-limit": "20000", - "anthropic-ratelimit-requests-remaining": "19999", - "anthropic-ratelimit-requests-reset": "2026-05-28T13:41:05Z", - "anthropic-ratelimit-tokens-limit": "3600000", - "anthropic-ratelimit-tokens-remaining": "3599000", - "anthropic-ratelimit-tokens-reset": "2026-05-28T13:41:05Z", - "cache-control": "no-cache", - "cf-cache-status": "DYNAMIC", - "cf-ray": "a02dacc65c8dc301-VIE", - "connection": "keep-alive", - "content-encoding": "gzip", - "content-security-policy": "default-src 'none'; frame-ancestors 'none'", - "content-type": "text/event-stream; charset=utf-8", - "date": "Thu, 28 May 2026 13:41:07 GMT", - "request-id": "req_011CbV4aLhY55XXCvF766Z84", - "server": "cloudflare", - "set-cookie": "[REDACTED]", - "strict-transport-security": "max-age=31536000; includeSubDomains; preload", - "traceresponse": "00-1467e6f052128317c79e4dcdcc34c0e2-95e633d304e0c3ad-01", - "transfer-encoding": "chunked", - "vary": "Accept-Encoding", - "x-envoy-upstream-service-time": "2046", - "x-robots-tag": "none" - }, - "status": 200, - "statusText": "OK" - } - }, - { - "callIndex": 6, - "id": "2d5b5e2519b5661b", - "matchKey": "POST api.anthropic.com/v1/messages", - "recordedAt": "2026-05-28T13:41:10.832Z", - "request": { - "body": { - "kind": "json", - "value": { - "max_tokens": 64000, - "messages": [ - { - "content": [ - { - "text": "Run the skill named \"e2e-flue-skill\".\n\nArguments:\n{\n \"marker\": \"SKILL_DONE\"\n}", - "type": "text" - } - ], - "role": "user" - }, - { - "content": [ - { - "text": "I'll run the e2e-flue-skill. Let me first read the skill file to understand its instructions.", - "type": "text" - }, - { - "id": "toolu_0116y5v4MmMkEaDkjkEPPjTH", - "input": { - "path": ".agents/e2e-flue-skill.md" - }, - "name": "read", - "type": "tool_use" - } - ], - "role": "assistant" - }, - { - "content": [ - { - "content": "ENOENT: no such file or directory, open '/Users/lucaforstner/conductor/workspaces/braintrust-sdk-javascript/shanghai-v1/e2e/.bt-tmp/run-u9uXnv/scenarios/flue-instrumentation/.agents/e2e-flue-skill.md'", - "is_error": true, - "tool_use_id": "toolu_0116y5v4MmMkEaDkjkEPPjTH", - "type": "tool_result" - } - ], - "role": "user" - }, - { - "content": [ - { - "id": "toolu_01VLK49ybvQa2MZoZ8FxbJCx", - "input": { - "path": ".agents" - }, - "name": "read", - "type": "tool_use" - } - ], - "role": "assistant" - }, - { - "content": [ - { - "cache_control": { - "type": "ephemeral" - }, - "content": "skills", - "is_error": false, - "tool_use_id": "toolu_01VLK49ybvQa2MZoZ8FxbJCx", - "type": "tool_result" - } - ], - "role": "user" - } - ], - "model": "claude-sonnet-4-5-20250929", - "stream": true, - "system": [ - { - "cache_control": { - "type": "ephemeral" - }, - "text": "You are running in headless mode with no human operator. Work autonomously — never ask questions, never wait for user input. Make your best judgment and proceed independently.\n\nYou are a deterministic Flue instrumentation test agent. Follow user instructions exactly. When asked for a marker, output only that marker and no extra text. When running a local skill file, read it yourself and do not delegate it to a task.\n\n## Available Skills\n\nThe following skills provide specialized instructions for specific tasks. When a task matches a skill description, activate that skill before proceeding so its full instructions are loaded. Skill instructions and supporting resources stay lazy until activation or explicit file reads.\n\n- **e2e-flue-skill** — Deterministic skill used by the Braintrust Flue instrumentation e2e test.\n\nDate: Thu, May 28, 2026\nWorking directory: /Users/lucaforstner/conductor/workspaces/braintrust-sdk-javascript/shanghai-v1/e2e/.bt-tmp/run-u9uXnv/scenarios/flue-instrumentation\n\nDirectory structure:\n.agents\n__cassettes__\n__snapshots__\nassertions.ts\ncassette-filter.mjs\nconstants.mjs\nnode_modules\npackage.json\npnpm-lock.yaml\nscenario.impl.mjs\nscenario.mjs\nscenario.test.ts\nscenario.ts", - "type": "text" - } - ], - "thinking": { - "type": "disabled" - }, - "tools": [ - { - "description": "Read a file or list a directory. For files, output is truncated to 2000 lines or 50KB — use offset/limit for large files. For directories, returns the list of entries.", - "eager_input_streaming": true, - "input_schema": { - "properties": { - "limit": { - "description": "Maximum number of lines to read", - "type": "number" - }, - "offset": { - "description": "Line number to start from (1-indexed)", - "type": "number" - }, - "path": { - "description": "Path to the file to read", - "type": "string" - } - }, - "required": ["path"], - "type": "object" - }, - "name": "read" - }, - { - "description": "Write content to a file. Creates the file and parent directories if they do not exist.", - "eager_input_streaming": true, - "input_schema": { - "properties": { - "content": { - "description": "Content to write to the file", - "type": "string" - }, - "path": { - "description": "Path to the file to write", - "type": "string" - } - }, - "required": ["path", "content"], - "type": "object" - }, - "name": "write" - }, - { - "description": "Edit a file using exact text replacement. The oldText must match a unique region of the file. Use replaceAll to replace all occurrences.", - "eager_input_streaming": true, - "input_schema": { - "properties": { - "newText": { - "description": "Replacement text", - "type": "string" - }, - "oldText": { - "description": "Exact text to find (must be unique)", - "type": "string" - }, - "path": { - "description": "Path to the file to edit", - "type": "string" - }, - "replaceAll": { - "description": "Replace all occurrences", - "type": "boolean" - } - }, - "required": ["path", "oldText", "newText"], - "type": "object" - }, - "name": "edit" - }, - { - "description": "Execute a bash command. Returns stdout and stderr. Output is truncated to the last 2000 lines or 50KB.", - "eager_input_streaming": true, - "input_schema": { - "properties": { - "command": { - "description": "Bash command to execute", - "type": "string" - }, - "timeout": { - "description": "Timeout in seconds", - "type": "number" - } - }, - "required": ["command"], - "type": "object" - }, - "name": "bash" - }, - { - "description": "Search file contents for a regex pattern. Returns matching lines with file paths and line numbers.", - "eager_input_streaming": true, - "input_schema": { - "properties": { - "include": { - "description": "Glob filter, e.g. \"*.ts\"", - "type": "string" - }, - "path": { - "description": "Directory or file to search (default: .)", - "type": "string" - }, - "pattern": { - "description": "Search pattern (regex)", - "type": "string" - } - }, - "required": ["pattern"], - "type": "object" - }, - "name": "grep" - }, - { - "description": "Find files by filename pattern using shell find -name semantics. Returns matching file paths.", - "eager_input_streaming": true, - "input_schema": { - "properties": { - "path": { - "description": "Directory to search in (default: .)", - "type": "string" - }, - "pattern": { - "description": "Filename pattern, e.g. \"*.ts\"", - "type": "string" - } - }, - "required": ["pattern"], - "type": "object" - }, - "name": "glob" - }, - { - "cache_control": { - "type": "ephemeral" - }, - "description": "Delegate a focused task to a detached child agent with its own context. Use this for independent research, file exploration, or parallel work. The task returns only its final answer to this conversation. No subagents are currently defined.", - "eager_input_streaming": true, - "input_schema": { - "properties": { - "agent": { - "description": "Declared subagent to use for the child agent", - "type": "string" - }, - "cwd": { - "description": "Working directory for the child agent. AGENTS.md and skills are discovered from here.", - "type": "string" - }, - "description": { - "description": "Short human-readable label for the delegated work", - "type": "string" - }, - "prompt": { - "description": "Focused instructions for the child agent", - "type": "string" - } - }, - "required": ["prompt"], - "type": "object" - }, - "name": "task" - } - ] - } - }, - "headers": {}, - "method": "POST", - "url": "https://api.anthropic.com/v1/messages" - }, - "response": { - "body": { - "chunks": [ - "event: message_start\ndata: {\"type\":\"message_start\",\"message\":{\"model\":\"claude-sonnet-4-5-20250929\",\"id\":\"msg_01RRobMzKrtpPcHZAGQrRZCo\",\"type\":\"message\",\"role\":\"assistant\",\"content\":[],\"stop_reason\":null,\"stop_sequence\":null,\"stop_details\":null,\"usage\":{\"input_tokens\":6,\"cache_creation_input_tokens\":66,\"cache_read_input_tokens\":1952,\"cache_creation\":{\"ephemeral_5m_input_tokens\":66,\"ephemeral_1h_input_tokens\":0},\"output_tokens\":54,\"service_tier\":\"standard\",\"inference_geo\":\"not_available\"}}}", - "event: content_block_start\ndata: {\"type\":\"content_block_start\",\"index\":0,\"content_block\":{\"type\":\"tool_use\",\"id\":\"toolu_018pSd7nKSm3kfi2DVmzQPsA\",\"name\":\"read\",\"input\":{},\"caller\":{\"type\":\"direct\"}}}", - "event: ping\ndata: {\"type\":\"ping\"}", - "event: content_block_delta\ndata: {\"type\":\"content_block_delta\",\"index\":0,\"delta\":{\"type\":\"input_json_delta\",\"partial_json\":\"\"}}", - "event: content_block_delta\ndata: {\"type\":\"content_block_delta\",\"index\":0,\"delta\":{\"type\":\"input_json_delta\",\"partial_json\":\"{\\\"path\\\": \\\".agents/skills\"}}", - "event: content_block_delta\ndata: {\"type\":\"content_block_delta\",\"index\":0,\"delta\":{\"type\":\"input_json_delta\",\"partial_json\":\"\\\"}\"}}", - "event: content_block_stop\ndata: {\"type\":\"content_block_stop\",\"index\":0}", - "event: message_delta\ndata: {\"type\":\"message_delta\",\"delta\":{\"stop_reason\":\"tool_use\",\"stop_sequence\":null,\"stop_details\":null},\"usage\":{\"input_tokens\":6,\"cache_creation_input_tokens\":66,\"cache_read_input_tokens\":1952,\"output_tokens\":54}}", - "event: message_stop\ndata: {\"type\":\"message_stop\"}" - ], - "kind": "sse" - }, - "headers": { - "anthropic-organization-id": "27796668-7351-40ac-acc4-024aee8995a5", - "anthropic-ratelimit-input-tokens-limit": "3000000", - "anthropic-ratelimit-input-tokens-remaining": "2999000", - "anthropic-ratelimit-input-tokens-reset": "2026-05-28T13:41:08Z", - "anthropic-ratelimit-output-tokens-limit": "600000", - "anthropic-ratelimit-output-tokens-remaining": "600000", - "anthropic-ratelimit-output-tokens-reset": "2026-05-28T13:41:08Z", - "anthropic-ratelimit-requests-limit": "20000", - "anthropic-ratelimit-requests-remaining": "19999", - "anthropic-ratelimit-requests-reset": "2026-05-28T13:41:08Z", - "anthropic-ratelimit-tokens-limit": "3600000", - "anthropic-ratelimit-tokens-remaining": "3599000", - "anthropic-ratelimit-tokens-reset": "2026-05-28T13:41:08Z", - "cache-control": "no-cache", - "cf-cache-status": "DYNAMIC", - "cf-ray": "a02dacd76bc8c301-VIE", - "connection": "keep-alive", - "content-encoding": "gzip", - "content-security-policy": "default-src 'none'; frame-ancestors 'none'", - "content-type": "text/event-stream; charset=utf-8", - "date": "Thu, 28 May 2026 13:41:10 GMT", - "request-id": "req_011CbV4aYPzoGb5kYRUcefKG", - "server": "cloudflare", - "set-cookie": "[REDACTED]", - "strict-transport-security": "max-age=31536000; includeSubDomains; preload", - "traceresponse": "00-eb3b7c53373bb1de39b3c714cb8004f5-e91254d86c0e7b64-01", - "transfer-encoding": "chunked", - "vary": "Accept-Encoding", - "x-envoy-upstream-service-time": "2024", - "x-robots-tag": "none" - }, - "status": 200, - "statusText": "OK" - } - }, - { - "callIndex": 7, - "id": "2cbc017f3c17c05f", - "matchKey": "POST api.anthropic.com/v1/messages", - "recordedAt": "2026-05-28T13:41:13.081Z", - "request": { - "body": { - "kind": "json", - "value": { - "max_tokens": 64000, - "messages": [ - { - "content": [ - { - "text": "Run the skill named \"e2e-flue-skill\".\n\nArguments:\n{\n \"marker\": \"SKILL_DONE\"\n}", - "type": "text" - } - ], - "role": "user" - }, - { - "content": [ - { - "text": "I'll run the e2e-flue-skill. Let me first read the skill file to understand its instructions.", - "type": "text" - }, - { - "id": "toolu_0116y5v4MmMkEaDkjkEPPjTH", - "input": { - "path": ".agents/e2e-flue-skill.md" - }, - "name": "read", - "type": "tool_use" - } - ], - "role": "assistant" - }, - { - "content": [ - { - "content": "ENOENT: no such file or directory, open '/Users/lucaforstner/conductor/workspaces/braintrust-sdk-javascript/shanghai-v1/e2e/.bt-tmp/run-u9uXnv/scenarios/flue-instrumentation/.agents/e2e-flue-skill.md'", - "is_error": true, - "tool_use_id": "toolu_0116y5v4MmMkEaDkjkEPPjTH", - "type": "tool_result" - } - ], - "role": "user" - }, - { - "content": [ - { - "id": "toolu_01VLK49ybvQa2MZoZ8FxbJCx", - "input": { - "path": ".agents" - }, - "name": "read", - "type": "tool_use" - } - ], - "role": "assistant" - }, - { - "content": [ - { - "content": "skills", - "is_error": false, - "tool_use_id": "toolu_01VLK49ybvQa2MZoZ8FxbJCx", - "type": "tool_result" - } - ], - "role": "user" - }, - { - "content": [ - { - "id": "toolu_018pSd7nKSm3kfi2DVmzQPsA", - "input": { - "path": ".agents/skills" - }, - "name": "read", - "type": "tool_use" - } - ], - "role": "assistant" - }, - { - "content": [ - { - "cache_control": { - "type": "ephemeral" - }, - "content": "e2e-flue-skill", - "is_error": false, - "tool_use_id": "toolu_018pSd7nKSm3kfi2DVmzQPsA", - "type": "tool_result" - } - ], - "role": "user" - } - ], - "model": "claude-sonnet-4-5-20250929", - "stream": true, - "system": [ - { - "cache_control": { - "type": "ephemeral" - }, - "text": "You are running in headless mode with no human operator. Work autonomously — never ask questions, never wait for user input. Make your best judgment and proceed independently.\n\nYou are a deterministic Flue instrumentation test agent. Follow user instructions exactly. When asked for a marker, output only that marker and no extra text. When running a local skill file, read it yourself and do not delegate it to a task.\n\n## Available Skills\n\nThe following skills provide specialized instructions for specific tasks. When a task matches a skill description, activate that skill before proceeding so its full instructions are loaded. Skill instructions and supporting resources stay lazy until activation or explicit file reads.\n\n- **e2e-flue-skill** — Deterministic skill used by the Braintrust Flue instrumentation e2e test.\n\nDate: Thu, May 28, 2026\nWorking directory: /Users/lucaforstner/conductor/workspaces/braintrust-sdk-javascript/shanghai-v1/e2e/.bt-tmp/run-u9uXnv/scenarios/flue-instrumentation\n\nDirectory structure:\n.agents\n__cassettes__\n__snapshots__\nassertions.ts\ncassette-filter.mjs\nconstants.mjs\nnode_modules\npackage.json\npnpm-lock.yaml\nscenario.impl.mjs\nscenario.mjs\nscenario.test.ts\nscenario.ts", - "type": "text" - } - ], - "thinking": { - "type": "disabled" - }, - "tools": [ - { - "description": "Read a file or list a directory. For files, output is truncated to 2000 lines or 50KB — use offset/limit for large files. For directories, returns the list of entries.", - "eager_input_streaming": true, - "input_schema": { - "properties": { - "limit": { - "description": "Maximum number of lines to read", - "type": "number" - }, - "offset": { - "description": "Line number to start from (1-indexed)", - "type": "number" - }, - "path": { - "description": "Path to the file to read", - "type": "string" - } - }, - "required": ["path"], - "type": "object" - }, - "name": "read" - }, - { - "description": "Write content to a file. Creates the file and parent directories if they do not exist.", - "eager_input_streaming": true, - "input_schema": { - "properties": { - "content": { - "description": "Content to write to the file", - "type": "string" - }, - "path": { - "description": "Path to the file to write", - "type": "string" - } - }, - "required": ["path", "content"], - "type": "object" - }, - "name": "write" - }, - { - "description": "Edit a file using exact text replacement. The oldText must match a unique region of the file. Use replaceAll to replace all occurrences.", - "eager_input_streaming": true, - "input_schema": { - "properties": { - "newText": { - "description": "Replacement text", - "type": "string" - }, - "oldText": { - "description": "Exact text to find (must be unique)", - "type": "string" - }, - "path": { - "description": "Path to the file to edit", - "type": "string" - }, - "replaceAll": { - "description": "Replace all occurrences", - "type": "boolean" - } - }, - "required": ["path", "oldText", "newText"], - "type": "object" - }, - "name": "edit" - }, - { - "description": "Execute a bash command. Returns stdout and stderr. Output is truncated to the last 2000 lines or 50KB.", - "eager_input_streaming": true, - "input_schema": { - "properties": { - "command": { - "description": "Bash command to execute", - "type": "string" - }, - "timeout": { - "description": "Timeout in seconds", - "type": "number" - } - }, - "required": ["command"], - "type": "object" - }, - "name": "bash" - }, - { - "description": "Search file contents for a regex pattern. Returns matching lines with file paths and line numbers.", - "eager_input_streaming": true, - "input_schema": { - "properties": { - "include": { - "description": "Glob filter, e.g. \"*.ts\"", - "type": "string" - }, - "path": { - "description": "Directory or file to search (default: .)", - "type": "string" - }, - "pattern": { - "description": "Search pattern (regex)", - "type": "string" - } - }, - "required": ["pattern"], - "type": "object" - }, - "name": "grep" - }, - { - "description": "Find files by filename pattern using shell find -name semantics. Returns matching file paths.", - "eager_input_streaming": true, - "input_schema": { - "properties": { - "path": { - "description": "Directory to search in (default: .)", - "type": "string" - }, - "pattern": { - "description": "Filename pattern, e.g. \"*.ts\"", - "type": "string" - } - }, - "required": ["pattern"], - "type": "object" - }, - "name": "glob" - }, - { - "cache_control": { - "type": "ephemeral" - }, - "description": "Delegate a focused task to a detached child agent with its own context. Use this for independent research, file exploration, or parallel work. The task returns only its final answer to this conversation. No subagents are currently defined.", - "eager_input_streaming": true, - "input_schema": { - "properties": { - "agent": { - "description": "Declared subagent to use for the child agent", - "type": "string" - }, - "cwd": { - "description": "Working directory for the child agent. AGENTS.md and skills are discovered from here.", - "type": "string" - }, - "description": { - "description": "Short human-readable label for the delegated work", - "type": "string" - }, - "prompt": { - "description": "Focused instructions for the child agent", - "type": "string" - } - }, - "required": ["prompt"], - "type": "object" - }, - "name": "task" - } - ] - } - }, - "headers": {}, - "method": "POST", - "url": "https://api.anthropic.com/v1/messages" - }, - "response": { - "body": { - "chunks": [ - "event: message_start\ndata: {\"type\":\"message_start\",\"message\":{\"model\":\"claude-sonnet-4-5-20250929\",\"id\":\"msg_01HaDyq5MaZpVHgVGhHJLmvs\",\"type\":\"message\",\"role\":\"assistant\",\"content\":[],\"stop_reason\":null,\"stop_sequence\":null,\"stop_details\":null,\"usage\":{\"input_tokens\":6,\"cache_creation_input_tokens\":74,\"cache_read_input_tokens\":2018,\"cache_creation\":{\"ephemeral_5m_input_tokens\":74,\"ephemeral_1h_input_tokens\":0},\"output_tokens\":63,\"service_tier\":\"standard\",\"inference_geo\":\"not_available\"}}}", - "event: content_block_start\ndata: {\"type\":\"content_block_start\",\"index\":0,\"content_block\":{\"type\":\"tool_use\",\"id\":\"toolu_012TAbdNbuuFaDw1naUg1vbT\",\"name\":\"read\",\"input\":{},\"caller\":{\"type\":\"direct\"}}}", - "event: ping\ndata: {\"type\":\"ping\"}", - "event: content_block_delta\ndata: {\"type\":\"content_block_delta\",\"index\":0,\"delta\":{\"type\":\"input_json_delta\",\"partial_json\":\"\"}}", - "event: content_block_delta\ndata: {\"type\":\"content_block_delta\",\"index\":0,\"delta\":{\"type\":\"input_json_delta\",\"partial_json\":\"{\\\"path\\\": \\\".agents/skills/e2e-flue-skill\"}}", - "event: content_block_delta\ndata: {\"type\":\"content_block_delta\",\"index\":0,\"delta\":{\"type\":\"input_json_delta\",\"partial_json\":\"\\\"}\"}}", - "event: content_block_stop\ndata: {\"type\":\"content_block_stop\",\"index\":0}", - "event: message_delta\ndata: {\"type\":\"message_delta\",\"delta\":{\"stop_reason\":\"tool_use\",\"stop_sequence\":null,\"stop_details\":null},\"usage\":{\"input_tokens\":6,\"cache_creation_input_tokens\":74,\"cache_read_input_tokens\":2018,\"output_tokens\":63}}", - "event: message_stop\ndata: {\"type\":\"message_stop\"}" - ], - "kind": "sse" - }, - "headers": { - "anthropic-organization-id": "27796668-7351-40ac-acc4-024aee8995a5", - "anthropic-ratelimit-input-tokens-limit": "3000000", - "anthropic-ratelimit-input-tokens-remaining": "2999000", - "anthropic-ratelimit-input-tokens-reset": "2026-05-28T13:41:11Z", - "anthropic-ratelimit-output-tokens-limit": "600000", - "anthropic-ratelimit-output-tokens-remaining": "600000", - "anthropic-ratelimit-output-tokens-reset": "2026-05-28T13:41:11Z", - "anthropic-ratelimit-requests-limit": "20000", - "anthropic-ratelimit-requests-remaining": "19999", - "anthropic-ratelimit-requests-reset": "2026-05-28T13:41:11Z", - "anthropic-ratelimit-tokens-limit": "3600000", - "anthropic-ratelimit-tokens-remaining": "3599000", - "anthropic-ratelimit-tokens-reset": "2026-05-28T13:41:11Z", - "cache-control": "no-cache", - "cf-cache-status": "DYNAMIC", - "cf-ray": "a02dace85ad2c301-VIE", - "connection": "keep-alive", - "content-encoding": "gzip", - "content-security-policy": "default-src 'none'; frame-ancestors 'none'", - "content-type": "text/event-stream; charset=utf-8", - "date": "Thu, 28 May 2026 13:41:13 GMT", - "request-id": "req_011CbV4ajy1g4mH27mS5Ju8N", - "server": "cloudflare", - "set-cookie": "[REDACTED]", - "strict-transport-security": "max-age=31536000; includeSubDomains; preload", - "traceresponse": "00-93ed38ba66b829c67909054d6945c9cb-af18094065e38879-01", - "transfer-encoding": "chunked", - "vary": "Accept-Encoding", - "x-envoy-upstream-service-time": "1778", - "x-robots-tag": "none" - }, - "status": 200, - "statusText": "OK" - } - }, - { - "callIndex": 8, - "id": "4b885efa84390a1f", - "matchKey": "POST api.anthropic.com/v1/messages", - "recordedAt": "2026-05-28T13:41:15.608Z", - "request": { - "body": { - "kind": "json", - "value": { - "max_tokens": 64000, - "messages": [ - { - "content": [ - { - "text": "Run the skill named \"e2e-flue-skill\".\n\nArguments:\n{\n \"marker\": \"SKILL_DONE\"\n}", - "type": "text" - } - ], - "role": "user" - }, - { - "content": [ - { - "text": "I'll run the e2e-flue-skill. Let me first read the skill file to understand its instructions.", - "type": "text" - }, - { - "id": "toolu_0116y5v4MmMkEaDkjkEPPjTH", - "input": { - "path": ".agents/e2e-flue-skill.md" - }, - "name": "read", - "type": "tool_use" - } - ], - "role": "assistant" - }, - { - "content": [ - { - "content": "ENOENT: no such file or directory, open '/Users/lucaforstner/conductor/workspaces/braintrust-sdk-javascript/shanghai-v1/e2e/.bt-tmp/run-u9uXnv/scenarios/flue-instrumentation/.agents/e2e-flue-skill.md'", - "is_error": true, - "tool_use_id": "toolu_0116y5v4MmMkEaDkjkEPPjTH", - "type": "tool_result" - } - ], - "role": "user" - }, - { - "content": [ - { - "id": "toolu_01VLK49ybvQa2MZoZ8FxbJCx", - "input": { - "path": ".agents" - }, - "name": "read", - "type": "tool_use" - } - ], - "role": "assistant" - }, - { - "content": [ - { - "content": "skills", - "is_error": false, - "tool_use_id": "toolu_01VLK49ybvQa2MZoZ8FxbJCx", - "type": "tool_result" - } - ], - "role": "user" - }, - { - "content": [ - { - "id": "toolu_018pSd7nKSm3kfi2DVmzQPsA", - "input": { - "path": ".agents/skills" - }, - "name": "read", - "type": "tool_use" - } - ], - "role": "assistant" - }, - { - "content": [ - { - "content": "e2e-flue-skill", - "is_error": false, - "tool_use_id": "toolu_018pSd7nKSm3kfi2DVmzQPsA", - "type": "tool_result" - } - ], - "role": "user" - }, - { - "content": [ - { - "id": "toolu_012TAbdNbuuFaDw1naUg1vbT", - "input": { - "path": ".agents/skills/e2e-flue-skill" - }, - "name": "read", - "type": "tool_use" - } - ], - "role": "assistant" - }, - { - "content": [ - { - "cache_control": { - "type": "ephemeral" - }, - "content": "SKILL.md", - "is_error": false, - "tool_use_id": "toolu_012TAbdNbuuFaDw1naUg1vbT", - "type": "tool_result" - } - ], - "role": "user" - } - ], - "model": "claude-sonnet-4-5-20250929", - "stream": true, - "system": [ - { - "cache_control": { - "type": "ephemeral" - }, - "text": "You are running in headless mode with no human operator. Work autonomously — never ask questions, never wait for user input. Make your best judgment and proceed independently.\n\nYou are a deterministic Flue instrumentation test agent. Follow user instructions exactly. When asked for a marker, output only that marker and no extra text. When running a local skill file, read it yourself and do not delegate it to a task.\n\n## Available Skills\n\nThe following skills provide specialized instructions for specific tasks. When a task matches a skill description, activate that skill before proceeding so its full instructions are loaded. Skill instructions and supporting resources stay lazy until activation or explicit file reads.\n\n- **e2e-flue-skill** — Deterministic skill used by the Braintrust Flue instrumentation e2e test.\n\nDate: Thu, May 28, 2026\nWorking directory: /Users/lucaforstner/conductor/workspaces/braintrust-sdk-javascript/shanghai-v1/e2e/.bt-tmp/run-u9uXnv/scenarios/flue-instrumentation\n\nDirectory structure:\n.agents\n__cassettes__\n__snapshots__\nassertions.ts\ncassette-filter.mjs\nconstants.mjs\nnode_modules\npackage.json\npnpm-lock.yaml\nscenario.impl.mjs\nscenario.mjs\nscenario.test.ts\nscenario.ts", - "type": "text" - } - ], - "thinking": { - "type": "disabled" - }, - "tools": [ - { - "description": "Read a file or list a directory. For files, output is truncated to 2000 lines or 50KB — use offset/limit for large files. For directories, returns the list of entries.", - "eager_input_streaming": true, - "input_schema": { - "properties": { - "limit": { - "description": "Maximum number of lines to read", - "type": "number" - }, - "offset": { - "description": "Line number to start from (1-indexed)", - "type": "number" - }, - "path": { - "description": "Path to the file to read", - "type": "string" - } - }, - "required": ["path"], - "type": "object" - }, - "name": "read" - }, - { - "description": "Write content to a file. Creates the file and parent directories if they do not exist.", - "eager_input_streaming": true, - "input_schema": { - "properties": { - "content": { - "description": "Content to write to the file", - "type": "string" - }, - "path": { - "description": "Path to the file to write", - "type": "string" - } - }, - "required": ["path", "content"], - "type": "object" - }, - "name": "write" - }, - { - "description": "Edit a file using exact text replacement. The oldText must match a unique region of the file. Use replaceAll to replace all occurrences.", - "eager_input_streaming": true, - "input_schema": { - "properties": { - "newText": { - "description": "Replacement text", - "type": "string" - }, - "oldText": { - "description": "Exact text to find (must be unique)", - "type": "string" - }, - "path": { - "description": "Path to the file to edit", - "type": "string" - }, - "replaceAll": { - "description": "Replace all occurrences", - "type": "boolean" - } - }, - "required": ["path", "oldText", "newText"], - "type": "object" - }, - "name": "edit" - }, - { - "description": "Execute a bash command. Returns stdout and stderr. Output is truncated to the last 2000 lines or 50KB.", - "eager_input_streaming": true, - "input_schema": { - "properties": { - "command": { - "description": "Bash command to execute", - "type": "string" - }, - "timeout": { - "description": "Timeout in seconds", - "type": "number" - } - }, - "required": ["command"], - "type": "object" - }, - "name": "bash" - }, - { - "description": "Search file contents for a regex pattern. Returns matching lines with file paths and line numbers.", - "eager_input_streaming": true, - "input_schema": { - "properties": { - "include": { - "description": "Glob filter, e.g. \"*.ts\"", - "type": "string" - }, - "path": { - "description": "Directory or file to search (default: .)", - "type": "string" - }, - "pattern": { - "description": "Search pattern (regex)", - "type": "string" - } - }, - "required": ["pattern"], - "type": "object" - }, - "name": "grep" - }, - { - "description": "Find files by filename pattern using shell find -name semantics. Returns matching file paths.", - "eager_input_streaming": true, - "input_schema": { - "properties": { - "path": { - "description": "Directory to search in (default: .)", - "type": "string" - }, - "pattern": { - "description": "Filename pattern, e.g. \"*.ts\"", - "type": "string" - } - }, - "required": ["pattern"], - "type": "object" - }, - "name": "glob" - }, - { - "cache_control": { - "type": "ephemeral" - }, - "description": "Delegate a focused task to a detached child agent with its own context. Use this for independent research, file exploration, or parallel work. The task returns only its final answer to this conversation. No subagents are currently defined.", - "eager_input_streaming": true, - "input_schema": { - "properties": { - "agent": { - "description": "Declared subagent to use for the child agent", - "type": "string" - }, - "cwd": { - "description": "Working directory for the child agent. AGENTS.md and skills are discovered from here.", - "type": "string" - }, - "description": { - "description": "Short human-readable label for the delegated work", - "type": "string" - }, - "prompt": { - "description": "Focused instructions for the child agent", - "type": "string" - } - }, - "required": ["prompt"], - "type": "object" - }, - "name": "task" - } - ] - } - }, - "headers": {}, - "method": "POST", - "url": "https://api.anthropic.com/v1/messages" - }, - "response": { - "body": { - "chunks": [ - "event: message_start\ndata: {\"type\":\"message_start\",\"message\":{\"model\":\"claude-sonnet-4-5-20250929\",\"id\":\"msg_01FCY15E5PvgZ9MKVwMLQfmY\",\"type\":\"message\",\"role\":\"assistant\",\"content\":[],\"stop_reason\":null,\"stop_sequence\":null,\"stop_details\":null,\"usage\":{\"input_tokens\":6,\"cache_creation_input_tokens\":79,\"cache_read_input_tokens\":2092,\"cache_creation\":{\"ephemeral_5m_input_tokens\":79,\"ephemeral_1h_input_tokens\":0},\"output_tokens\":66,\"service_tier\":\"standard\",\"inference_geo\":\"not_available\"}}}", - "event: content_block_start\ndata: {\"type\":\"content_block_start\",\"index\":0,\"content_block\":{\"type\":\"tool_use\",\"id\":\"toolu_01WQVh4vmLxZQhqpoXRvUi68\",\"name\":\"read\",\"input\":{},\"caller\":{\"type\":\"direct\"}}}", - "event: ping\ndata: {\"type\":\"ping\"}", - "event: content_block_delta\ndata: {\"type\":\"content_block_delta\",\"index\":0,\"delta\":{\"type\":\"input_json_delta\",\"partial_json\":\"\"}}", - "event: content_block_delta\ndata: {\"type\":\"content_block_delta\",\"index\":0,\"delta\":{\"type\":\"input_json_delta\",\"partial_json\":\"{\\\"path\\\": \\\".agents/skills/e2e-flue-skill/SKILL.md\"}}", - "event: content_block_delta\ndata: {\"type\":\"content_block_delta\",\"index\":0,\"delta\":{\"type\":\"input_json_delta\",\"partial_json\":\"\\\"}\"}}", - "event: content_block_stop\ndata: {\"type\":\"content_block_stop\",\"index\":0}", - "event: message_delta\ndata: {\"type\":\"message_delta\",\"delta\":{\"stop_reason\":\"tool_use\",\"stop_sequence\":null,\"stop_details\":null},\"usage\":{\"input_tokens\":6,\"cache_creation_input_tokens\":79,\"cache_read_input_tokens\":2092,\"output_tokens\":68}}", - "event: message_stop\ndata: {\"type\":\"message_stop\"}" - ], - "kind": "sse" - }, - "headers": { - "anthropic-organization-id": "27796668-7351-40ac-acc4-024aee8995a5", - "anthropic-ratelimit-input-tokens-limit": "3000000", - "anthropic-ratelimit-input-tokens-remaining": "2999000", - "anthropic-ratelimit-input-tokens-reset": "2026-05-28T13:41:13Z", - "anthropic-ratelimit-output-tokens-limit": "600000", - "anthropic-ratelimit-output-tokens-remaining": "600000", - "anthropic-ratelimit-output-tokens-reset": "2026-05-28T13:41:13Z", - "anthropic-ratelimit-requests-limit": "20000", - "anthropic-ratelimit-requests-remaining": "19999", - "anthropic-ratelimit-requests-reset": "2026-05-28T13:41:13Z", - "anthropic-ratelimit-tokens-limit": "3600000", - "anthropic-ratelimit-tokens-remaining": "3599000", - "anthropic-ratelimit-tokens-reset": "2026-05-28T13:41:13Z", - "cache-control": "no-cache", - "cf-cache-status": "DYNAMIC", - "cf-ray": "a02dacf599b5c301-VIE", - "connection": "keep-alive", - "content-encoding": "gzip", - "content-security-policy": "default-src 'none'; frame-ancestors 'none'", - "content-type": "text/event-stream; charset=utf-8", - "date": "Thu, 28 May 2026 13:41:15 GMT", - "request-id": "req_011CbV4atzVFuefiAGaQnZig", - "server": "cloudflare", - "set-cookie": "[REDACTED]", - "strict-transport-security": "max-age=31536000; includeSubDomains; preload", - "traceresponse": "00-75d6c58deb0a99da3a3ea03db1ae3fd9-01d8b31ff753a269-01", - "transfer-encoding": "chunked", - "vary": "Accept-Encoding", - "x-envoy-upstream-service-time": "2118", - "x-robots-tag": "none" - }, - "status": 200, - "statusText": "OK" - } - }, - { - "callIndex": 9, - "id": "05dcf3cc09c5aa27", - "matchKey": "POST api.anthropic.com/v1/messages", - "recordedAt": "2026-05-28T13:41:17.354Z", - "request": { - "body": { - "kind": "json", - "value": { - "max_tokens": 64000, - "messages": [ - { - "content": [ - { - "text": "Run the skill named \"e2e-flue-skill\".\n\nArguments:\n{\n \"marker\": \"SKILL_DONE\"\n}", - "type": "text" - } - ], - "role": "user" - }, - { - "content": [ - { - "text": "I'll run the e2e-flue-skill. Let me first read the skill file to understand its instructions.", - "type": "text" - }, - { - "id": "toolu_0116y5v4MmMkEaDkjkEPPjTH", - "input": { - "path": ".agents/e2e-flue-skill.md" - }, - "name": "read", - "type": "tool_use" - } - ], - "role": "assistant" - }, - { - "content": [ - { - "content": "ENOENT: no such file or directory, open '/Users/lucaforstner/conductor/workspaces/braintrust-sdk-javascript/shanghai-v1/e2e/.bt-tmp/run-u9uXnv/scenarios/flue-instrumentation/.agents/e2e-flue-skill.md'", - "is_error": true, - "tool_use_id": "toolu_0116y5v4MmMkEaDkjkEPPjTH", - "type": "tool_result" - } - ], - "role": "user" - }, - { - "content": [ - { - "id": "toolu_01VLK49ybvQa2MZoZ8FxbJCx", - "input": { - "path": ".agents" - }, - "name": "read", - "type": "tool_use" - } - ], - "role": "assistant" - }, - { - "content": [ - { - "content": "skills", - "is_error": false, - "tool_use_id": "toolu_01VLK49ybvQa2MZoZ8FxbJCx", - "type": "tool_result" - } - ], - "role": "user" - }, - { - "content": [ - { - "id": "toolu_018pSd7nKSm3kfi2DVmzQPsA", - "input": { - "path": ".agents/skills" - }, - "name": "read", - "type": "tool_use" - } - ], - "role": "assistant" - }, - { - "content": [ - { - "content": "e2e-flue-skill", - "is_error": false, - "tool_use_id": "toolu_018pSd7nKSm3kfi2DVmzQPsA", - "type": "tool_result" - } - ], - "role": "user" - }, - { - "content": [ - { - "id": "toolu_012TAbdNbuuFaDw1naUg1vbT", - "input": { - "path": ".agents/skills/e2e-flue-skill" - }, - "name": "read", - "type": "tool_use" - } - ], - "role": "assistant" - }, - { - "content": [ - { - "content": "SKILL.md", - "is_error": false, - "tool_use_id": "toolu_012TAbdNbuuFaDw1naUg1vbT", - "type": "tool_result" - } - ], - "role": "user" - }, - { - "content": [ - { - "id": "toolu_01WQVh4vmLxZQhqpoXRvUi68", - "input": { - "path": ".agents/skills/e2e-flue-skill/SKILL.md" - }, - "name": "read", - "type": "tool_use" - } - ], - "role": "assistant" - }, - { - "content": [ - { - "cache_control": { - "type": "ephemeral" - }, - "content": "---\nname: e2e-flue-skill\ndescription: Deterministic skill used by the Braintrust Flue instrumentation e2e test.\n---\n\nReturn the marker from the provided args exactly once. Output no other text.\n", - "is_error": false, - "tool_use_id": "toolu_01WQVh4vmLxZQhqpoXRvUi68", - "type": "tool_result" - } - ], - "role": "user" - } - ], - "model": "claude-sonnet-4-5-20250929", - "stream": true, - "system": [ - { - "cache_control": { - "type": "ephemeral" - }, - "text": "You are running in headless mode with no human operator. Work autonomously — never ask questions, never wait for user input. Make your best judgment and proceed independently.\n\nYou are a deterministic Flue instrumentation test agent. Follow user instructions exactly. When asked for a marker, output only that marker and no extra text. When running a local skill file, read it yourself and do not delegate it to a task.\n\n## Available Skills\n\nThe following skills provide specialized instructions for specific tasks. When a task matches a skill description, activate that skill before proceeding so its full instructions are loaded. Skill instructions and supporting resources stay lazy until activation or explicit file reads.\n\n- **e2e-flue-skill** — Deterministic skill used by the Braintrust Flue instrumentation e2e test.\n\nDate: Thu, May 28, 2026\nWorking directory: /Users/lucaforstner/conductor/workspaces/braintrust-sdk-javascript/shanghai-v1/e2e/.bt-tmp/run-u9uXnv/scenarios/flue-instrumentation\n\nDirectory structure:\n.agents\n__cassettes__\n__snapshots__\nassertions.ts\ncassette-filter.mjs\nconstants.mjs\nnode_modules\npackage.json\npnpm-lock.yaml\nscenario.impl.mjs\nscenario.mjs\nscenario.test.ts\nscenario.ts", - "type": "text" - } - ], - "thinking": { - "type": "disabled" - }, - "tools": [ - { - "description": "Read a file or list a directory. For files, output is truncated to 2000 lines or 50KB — use offset/limit for large files. For directories, returns the list of entries.", - "eager_input_streaming": true, - "input_schema": { - "properties": { - "limit": { - "description": "Maximum number of lines to read", - "type": "number" - }, - "offset": { - "description": "Line number to start from (1-indexed)", - "type": "number" - }, - "path": { - "description": "Path to the file to read", - "type": "string" - } - }, - "required": ["path"], - "type": "object" - }, - "name": "read" - }, - { - "description": "Write content to a file. Creates the file and parent directories if they do not exist.", - "eager_input_streaming": true, - "input_schema": { - "properties": { - "content": { - "description": "Content to write to the file", - "type": "string" - }, - "path": { - "description": "Path to the file to write", - "type": "string" - } - }, - "required": ["path", "content"], - "type": "object" - }, - "name": "write" - }, - { - "description": "Edit a file using exact text replacement. The oldText must match a unique region of the file. Use replaceAll to replace all occurrences.", - "eager_input_streaming": true, - "input_schema": { - "properties": { - "newText": { - "description": "Replacement text", - "type": "string" - }, - "oldText": { - "description": "Exact text to find (must be unique)", - "type": "string" - }, - "path": { - "description": "Path to the file to edit", - "type": "string" - }, - "replaceAll": { - "description": "Replace all occurrences", - "type": "boolean" - } - }, - "required": ["path", "oldText", "newText"], - "type": "object" - }, - "name": "edit" - }, - { - "description": "Execute a bash command. Returns stdout and stderr. Output is truncated to the last 2000 lines or 50KB.", - "eager_input_streaming": true, - "input_schema": { - "properties": { - "command": { - "description": "Bash command to execute", - "type": "string" - }, - "timeout": { - "description": "Timeout in seconds", - "type": "number" - } - }, - "required": ["command"], - "type": "object" - }, - "name": "bash" - }, - { - "description": "Search file contents for a regex pattern. Returns matching lines with file paths and line numbers.", - "eager_input_streaming": true, - "input_schema": { - "properties": { - "include": { - "description": "Glob filter, e.g. \"*.ts\"", - "type": "string" - }, - "path": { - "description": "Directory or file to search (default: .)", - "type": "string" - }, - "pattern": { - "description": "Search pattern (regex)", - "type": "string" - } - }, - "required": ["pattern"], - "type": "object" - }, - "name": "grep" - }, - { - "description": "Find files by filename pattern using shell find -name semantics. Returns matching file paths.", - "eager_input_streaming": true, - "input_schema": { - "properties": { - "path": { - "description": "Directory to search in (default: .)", - "type": "string" - }, - "pattern": { - "description": "Filename pattern, e.g. \"*.ts\"", - "type": "string" - } - }, - "required": ["pattern"], - "type": "object" - }, - "name": "glob" - }, - { - "cache_control": { - "type": "ephemeral" - }, - "description": "Delegate a focused task to a detached child agent with its own context. Use this for independent research, file exploration, or parallel work. The task returns only its final answer to this conversation. No subagents are currently defined.", - "eager_input_streaming": true, - "input_schema": { - "properties": { - "agent": { - "description": "Declared subagent to use for the child agent", - "type": "string" - }, - "cwd": { - "description": "Working directory for the child agent. AGENTS.md and skills are discovered from here.", - "type": "string" - }, - "description": { - "description": "Short human-readable label for the delegated work", - "type": "string" - }, - "prompt": { - "description": "Focused instructions for the child agent", - "type": "string" - } - }, - "required": ["prompt"], - "type": "object" - }, - "name": "task" - } - ] - } - }, - "headers": {}, - "method": "POST", - "url": "https://api.anthropic.com/v1/messages" - }, - "response": { - "body": { - "chunks": [ - "event: message_start\ndata: {\"type\":\"message_start\",\"message\":{\"model\":\"claude-sonnet-4-5-20250929\",\"id\":\"msg_01F7J8rewVTLnDMtxDcVdkiQ\",\"type\":\"message\",\"role\":\"assistant\",\"content\":[],\"stop_reason\":null,\"stop_sequence\":null,\"stop_details\":null,\"usage\":{\"input_tokens\":6,\"cache_creation_input_tokens\":135,\"cache_read_input_tokens\":2171,\"cache_creation\":{\"ephemeral_5m_input_tokens\":135,\"ephemeral_1h_input_tokens\":0},\"output_tokens\":3,\"service_tier\":\"standard\",\"inference_geo\":\"not_available\"}}}", - "event: content_block_start\ndata: {\"type\":\"content_block_start\",\"index\":0,\"content_block\":{\"type\":\"text\",\"text\":\"\"}}", - "event: ping\ndata: {\"type\":\"ping\"}", - "event: content_block_delta\ndata: {\"type\":\"content_block_delta\",\"index\":0,\"delta\":{\"type\":\"text_delta\",\"text\":\"SKILL_\"}}", - "event: content_block_delta\ndata: {\"type\":\"content_block_delta\",\"index\":0,\"delta\":{\"type\":\"text_delta\",\"text\":\"DONE\"}}", - "event: content_block_stop\ndata: {\"type\":\"content_block_stop\",\"index\":0}", - "event: message_delta\ndata: {\"type\":\"message_delta\",\"delta\":{\"stop_reason\":\"end_turn\",\"stop_sequence\":null,\"stop_details\":null},\"usage\":{\"input_tokens\":6,\"cache_creation_input_tokens\":135,\"cache_read_input_tokens\":2171,\"output_tokens\":8}}", - "event: message_stop\ndata: {\"type\":\"message_stop\"}" - ], - "kind": "sse" - }, - "headers": { - "anthropic-organization-id": "27796668-7351-40ac-acc4-024aee8995a5", - "anthropic-ratelimit-input-tokens-limit": "3000000", - "anthropic-ratelimit-input-tokens-remaining": "2999000", - "anthropic-ratelimit-input-tokens-reset": "2026-05-28T13:41:15Z", - "anthropic-ratelimit-output-tokens-limit": "600000", - "anthropic-ratelimit-output-tokens-remaining": "600000", - "anthropic-ratelimit-output-tokens-reset": "2026-05-28T13:41:15Z", - "anthropic-ratelimit-requests-limit": "20000", - "anthropic-ratelimit-requests-remaining": "19999", - "anthropic-ratelimit-requests-reset": "2026-05-28T13:41:15Z", - "anthropic-ratelimit-tokens-limit": "3600000", - "anthropic-ratelimit-tokens-remaining": "3599000", - "anthropic-ratelimit-tokens-reset": "2026-05-28T13:41:15Z", - "cache-control": "no-cache", - "cf-cache-status": "DYNAMIC", - "cf-ray": "a02dad05abc5c301-VIE", - "connection": "keep-alive", - "content-encoding": "gzip", - "content-security-policy": "default-src 'none'; frame-ancestors 'none'", - "content-type": "text/event-stream; charset=utf-8", - "date": "Thu, 28 May 2026 13:41:17 GMT", - "request-id": "req_011CbV4b62WtkEAN1uuw6ZTa", - "server": "cloudflare", - "set-cookie": "[REDACTED]", - "strict-transport-security": "max-age=31536000; includeSubDomains; preload", - "traceresponse": "00-a95abb0aa0dc3ddebd7bd3e84f6431b6-69727f41ec2a9c74-01", - "transfer-encoding": "chunked", - "vary": "Accept-Encoding", - "x-envoy-upstream-service-time": "1258", - "x-robots-tag": "none" - }, - "status": 200, - "statusText": "OK" - } - }, - { - "callIndex": 0, - "id": "7c0e162d6794cf5d", - "matchKey": "POST api.openai.com/v1/responses", - "recordedAt": "2026-05-28T13:41:20.282Z", - "request": { - "body": { - "kind": "json", - "value": { - "input": [ - { - "content": "You are running in headless mode with no human operator. Work autonomously — never ask questions, never wait for user input. Make your best judgment and proceed independently.\n\nYou are a deterministic Flue instrumentation test agent. Follow user instructions exactly. When asked for a marker, output only that marker and no extra text. When running a local skill file, read it yourself and do not delegate it to a task.\n\n## Available Skills\n\nThe following skills provide specialized instructions for specific tasks. When a task matches a skill description, activate that skill before proceeding so its full instructions are loaded. Skill instructions and supporting resources stay lazy until activation or explicit file reads.\n\n- **e2e-flue-skill** — Deterministic skill used by the Braintrust Flue instrumentation e2e test.\n\nDate: Thu, May 28, 2026\nWorking directory: /Users/lucaforstner/conductor/workspaces/braintrust-sdk-javascript/shanghai-v1/e2e/.bt-tmp/run-u9uXnv/scenarios/flue-instrumentation\n\nDirectory structure:\n.agents\n__cassettes__\n__snapshots__\nassertions.ts\ncassette-filter.mjs\nconstants.mjs\nnode_modules\npackage.json\npnpm-lock.yaml\nscenario.impl.mjs\nscenario.mjs\nscenario.test.ts\nscenario.ts", - "role": "system" - }, - { - "content": [ - { - "text": "Reply with exactly TASK_DONE and no other text.", - "type": "input_text" - } - ], - "role": "user" - } - ], - "model": "gpt-4o-mini", - "prompt_cache_key": "[REDACTED]", - "store": false, - "stream": true, - "tools": [ - { - "description": "Read a file or list a directory. For files, output is truncated to 2000 lines or 50KB — use offset/limit for large files. For directories, returns the list of entries.", - "name": "read", - "parameters": { - "properties": { - "limit": { - "description": "Maximum number of lines to read", - "type": "number" - }, - "offset": { - "description": "Line number to start from (1-indexed)", - "type": "number" - }, - "path": { - "description": "Path to the file to read", - "type": "string" - } - }, - "required": ["path"], - "type": "object" - }, - "strict": false, - "type": "function" - }, - { - "description": "Write content to a file. Creates the file and parent directories if they do not exist.", - "name": "write", - "parameters": { - "properties": { - "content": { - "description": "Content to write to the file", - "type": "string" - }, - "path": { - "description": "Path to the file to write", - "type": "string" - } - }, - "required": ["path", "content"], - "type": "object" - }, - "strict": false, - "type": "function" - }, - { - "description": "Edit a file using exact text replacement. The oldText must match a unique region of the file. Use replaceAll to replace all occurrences.", - "name": "edit", - "parameters": { - "properties": { - "newText": { - "description": "Replacement text", - "type": "string" - }, - "oldText": { - "description": "Exact text to find (must be unique)", - "type": "string" - }, - "path": { - "description": "Path to the file to edit", - "type": "string" - }, - "replaceAll": { - "description": "Replace all occurrences", - "type": "boolean" - } - }, - "required": ["path", "oldText", "newText"], - "type": "object" - }, - "strict": false, - "type": "function" - }, - { - "description": "Execute a bash command. Returns stdout and stderr. Output is truncated to the last 2000 lines or 50KB.", - "name": "bash", - "parameters": { - "properties": { - "command": { - "description": "Bash command to execute", - "type": "string" - }, - "timeout": { - "description": "Timeout in seconds", - "type": "number" - } - }, - "required": ["command"], - "type": "object" - }, - "strict": false, - "type": "function" - }, - { - "description": "Search file contents for a regex pattern. Returns matching lines with file paths and line numbers.", - "name": "grep", - "parameters": { - "properties": { - "include": { - "description": "Glob filter, e.g. \"*.ts\"", - "type": "string" - }, - "path": { - "description": "Directory or file to search (default: .)", - "type": "string" - }, - "pattern": { - "description": "Search pattern (regex)", - "type": "string" - } - }, - "required": ["pattern"], - "type": "object" - }, - "strict": false, - "type": "function" - }, - { - "description": "Find files by filename pattern using shell find -name semantics. Returns matching file paths.", - "name": "glob", - "parameters": { - "properties": { - "path": { - "description": "Directory to search in (default: .)", - "type": "string" - }, - "pattern": { - "description": "Filename pattern, e.g. \"*.ts\"", - "type": "string" - } - }, - "required": ["pattern"], - "type": "object" - }, - "strict": false, - "type": "function" - }, - { - "description": "Delegate a focused task to a detached child agent with its own context. Use this for independent research, file exploration, or parallel work. The task returns only its final answer to this conversation. No subagents are currently defined.", - "name": "task", - "parameters": { - "properties": { - "agent": { - "description": "Declared subagent to use for the child agent", - "type": "string" - }, - "cwd": { - "description": "Working directory for the child agent. AGENTS.md and skills are discovered from here.", - "type": "string" - }, - "description": { - "description": "Short human-readable label for the delegated work", - "type": "string" - }, - "prompt": { - "description": "Focused instructions for the child agent", - "type": "string" - } - }, - "required": ["prompt"], - "type": "object" - }, - "strict": false, - "type": "function" - } - ] - } - }, - "headers": {}, - "method": "POST", - "url": "https://api.openai.com/v1/responses" - }, - "response": { - "body": { - "chunks": [ - "event: response.created\ndata: {\"type\":\"response.created\",\"response\":{\"id\":\"resp_09b32b2821e9a4d8016a1845ff2ea88192a734cf6ba6abcb9d\",\"object\":\"response\",\"created_at\":1779975679,\"status\":\"in_progress\",\"background\":false,\"completed_at\":null,\"error\":null,\"frequency_penalty\":0,\"incomplete_details\":null,\"instructions\":null,\"max_output_tokens\":null,\"max_tool_calls\":null,\"model\":\"gpt-4o-mini-2024-07-18\",\"moderation\":null,\"output\":[],\"parallel_tool_calls\":true,\"presence_penalty\":0,\"previous_response_id\":null,\"prompt_cache_key\":\"flue-e2e-instance::default::task:task:4cfd6e11-bac1-4736-9b87-b0\",\"prompt_cache_retention\":\"in_memory\",\"reasoning\":{\"context\":null,\"effort\":null,\"summary\":null},\"safety_identifier\":null,\"service_tier\":\"auto\",\"store\":false,\"temperature\":1,\"text\":{\"format\":{\"type\":\"text\"},\"verbosity\":\"medium\"},\"tool_choice\":\"auto\",\"tools\":[{\"type\":\"function\",\"description\":\"Read a file or list a directory. For files, output is truncated to 2000 lines or 50KB — use offset/limit for large files. For directories, returns the list of entries.\",\"name\":\"read\",\"parameters\":{\"type\":\"object\",\"required\":[\"path\"],\"properties\":{\"path\":{\"type\":\"string\",\"description\":\"Path to the file to read\"},\"offset\":{\"type\":\"number\",\"description\":\"Line number to start from (1-indexed)\"},\"limit\":{\"type\":\"number\",\"description\":\"Maximum number of lines to read\"}}},\"strict\":false},{\"type\":\"function\",\"description\":\"Write content to a file. Creates the file and parent directories if they do not exist.\",\"name\":\"write\",\"parameters\":{\"type\":\"object\",\"required\":[\"path\",\"content\"],\"properties\":{\"path\":{\"type\":\"string\",\"description\":\"Path to the file to write\"},\"content\":{\"type\":\"string\",\"description\":\"Content to write to the file\"}}},\"strict\":false},{\"type\":\"function\",\"description\":\"Edit a file using exact text replacement. The oldText must match a unique region of the file. Use replaceAll to replace all occurrences.\",\"name\":\"edit\",\"parameters\":{\"type\":\"object\",\"required\":[\"path\",\"oldText\",\"newText\"],\"properties\":{\"path\":{\"type\":\"string\",\"description\":\"Path to the file to edit\"},\"oldText\":{\"type\":\"string\",\"description\":\"Exact text to find (must be unique)\"},\"newText\":{\"type\":\"string\",\"description\":\"Replacement text\"},\"replaceAll\":{\"type\":\"boolean\",\"description\":\"Replace all occurrences\"}}},\"strict\":false},{\"type\":\"function\",\"description\":\"Execute a bash command. Returns stdout and stderr. Output is truncated to the last 2000 lines or 50KB.\",\"name\":\"bash\",\"parameters\":{\"type\":\"object\",\"required\":[\"command\"],\"properties\":{\"command\":{\"type\":\"string\",\"description\":\"Bash command to execute\"},\"timeout\":{\"type\":\"number\",\"description\":\"Timeout in seconds\"}}},\"strict\":false},{\"type\":\"function\",\"description\":\"Search file contents for a regex pattern. Returns matching lines with file paths and line numbers.\",\"name\":\"grep\",\"parameters\":{\"type\":\"object\",\"required\":[\"pattern\"],\"properties\":{\"pattern\":{\"type\":\"string\",\"description\":\"Search pattern (regex)\"},\"path\":{\"type\":\"string\",\"description\":\"Directory or file to search (default: .)\"},\"include\":{\"type\":\"string\",\"description\":\"Glob filter, e.g. \\\"*.ts\\\"\"}}},\"strict\":false},{\"type\":\"function\",\"description\":\"Find files by filename pattern using shell find -name semantics. Returns matching file paths.\",\"name\":\"glob\",\"parameters\":{\"type\":\"object\",\"required\":[\"pattern\"],\"properties\":{\"pattern\":{\"type\":\"string\",\"description\":\"Filename pattern, e.g. \\\"*.ts\\\"\"},\"path\":{\"type\":\"string\",\"description\":\"Directory to search in (default: .)\"}}},\"strict\":false},{\"type\":\"function\",\"description\":\"Delegate a focused task to a detached child agent with its own context. Use this for independent research, file exploration, or parallel work. The task returns only its final answer to this conversation. No subagents are currently defined.\",\"name\":\"task\",\"parameters\":{\"type\":\"object\",\"required\":[\"prompt\"],\"properties\":{\"description\":{\"type\":\"string\",\"description\":\"Short human-readable label for the delegated work\"},\"prompt\":{\"type\":\"string\",\"description\":\"Focused instructions for the child agent\"},\"agent\":{\"type\":\"string\",\"description\":\"Declared subagent to use for the child agent\"},\"cwd\":{\"type\":\"string\",\"description\":\"Working directory for the child agent. AGENTS.md and skills are discovered from here.\"}}},\"strict\":false}],\"top_logprobs\":0,\"top_p\":1,\"truncation\":\"disabled\",\"usage\":null,\"user\":null,\"metadata\":{}},\"sequence_number\":0}", - "event: response.in_progress\ndata: {\"type\":\"response.in_progress\",\"response\":{\"id\":\"resp_09b32b2821e9a4d8016a1845ff2ea88192a734cf6ba6abcb9d\",\"object\":\"response\",\"created_at\":1779975679,\"status\":\"in_progress\",\"background\":false,\"completed_at\":null,\"error\":null,\"frequency_penalty\":0,\"incomplete_details\":null,\"instructions\":null,\"max_output_tokens\":null,\"max_tool_calls\":null,\"model\":\"gpt-4o-mini-2024-07-18\",\"moderation\":null,\"output\":[],\"parallel_tool_calls\":true,\"presence_penalty\":0,\"previous_response_id\":null,\"prompt_cache_key\":\"flue-e2e-instance::default::task:task:4cfd6e11-bac1-4736-9b87-b0\",\"prompt_cache_retention\":\"in_memory\",\"reasoning\":{\"context\":null,\"effort\":null,\"summary\":null},\"safety_identifier\":null,\"service_tier\":\"auto\",\"store\":false,\"temperature\":1,\"text\":{\"format\":{\"type\":\"text\"},\"verbosity\":\"medium\"},\"tool_choice\":\"auto\",\"tools\":[{\"type\":\"function\",\"description\":\"Read a file or list a directory. For files, output is truncated to 2000 lines or 50KB — use offset/limit for large files. For directories, returns the list of entries.\",\"name\":\"read\",\"parameters\":{\"type\":\"object\",\"required\":[\"path\"],\"properties\":{\"path\":{\"type\":\"string\",\"description\":\"Path to the file to read\"},\"offset\":{\"type\":\"number\",\"description\":\"Line number to start from (1-indexed)\"},\"limit\":{\"type\":\"number\",\"description\":\"Maximum number of lines to read\"}}},\"strict\":false},{\"type\":\"function\",\"description\":\"Write content to a file. Creates the file and parent directories if they do not exist.\",\"name\":\"write\",\"parameters\":{\"type\":\"object\",\"required\":[\"path\",\"content\"],\"properties\":{\"path\":{\"type\":\"string\",\"description\":\"Path to the file to write\"},\"content\":{\"type\":\"string\",\"description\":\"Content to write to the file\"}}},\"strict\":false},{\"type\":\"function\",\"description\":\"Edit a file using exact text replacement. The oldText must match a unique region of the file. Use replaceAll to replace all occurrences.\",\"name\":\"edit\",\"parameters\":{\"type\":\"object\",\"required\":[\"path\",\"oldText\",\"newText\"],\"properties\":{\"path\":{\"type\":\"string\",\"description\":\"Path to the file to edit\"},\"oldText\":{\"type\":\"string\",\"description\":\"Exact text to find (must be unique)\"},\"newText\":{\"type\":\"string\",\"description\":\"Replacement text\"},\"replaceAll\":{\"type\":\"boolean\",\"description\":\"Replace all occurrences\"}}},\"strict\":false},{\"type\":\"function\",\"description\":\"Execute a bash command. Returns stdout and stderr. Output is truncated to the last 2000 lines or 50KB.\",\"name\":\"bash\",\"parameters\":{\"type\":\"object\",\"required\":[\"command\"],\"properties\":{\"command\":{\"type\":\"string\",\"description\":\"Bash command to execute\"},\"timeout\":{\"type\":\"number\",\"description\":\"Timeout in seconds\"}}},\"strict\":false},{\"type\":\"function\",\"description\":\"Search file contents for a regex pattern. Returns matching lines with file paths and line numbers.\",\"name\":\"grep\",\"parameters\":{\"type\":\"object\",\"required\":[\"pattern\"],\"properties\":{\"pattern\":{\"type\":\"string\",\"description\":\"Search pattern (regex)\"},\"path\":{\"type\":\"string\",\"description\":\"Directory or file to search (default: .)\"},\"include\":{\"type\":\"string\",\"description\":\"Glob filter, e.g. \\\"*.ts\\\"\"}}},\"strict\":false},{\"type\":\"function\",\"description\":\"Find files by filename pattern using shell find -name semantics. Returns matching file paths.\",\"name\":\"glob\",\"parameters\":{\"type\":\"object\",\"required\":[\"pattern\"],\"properties\":{\"pattern\":{\"type\":\"string\",\"description\":\"Filename pattern, e.g. \\\"*.ts\\\"\"},\"path\":{\"type\":\"string\",\"description\":\"Directory to search in (default: .)\"}}},\"strict\":false},{\"type\":\"function\",\"description\":\"Delegate a focused task to a detached child agent with its own context. Use this for independent research, file exploration, or parallel work. The task returns only its final answer to this conversation. No subagents are currently defined.\",\"name\":\"task\",\"parameters\":{\"type\":\"object\",\"required\":[\"prompt\"],\"properties\":{\"description\":{\"type\":\"string\",\"description\":\"Short human-readable label for the delegated work\"},\"prompt\":{\"type\":\"string\",\"description\":\"Focused instructions for the child agent\"},\"agent\":{\"type\":\"string\",\"description\":\"Declared subagent to use for the child agent\"},\"cwd\":{\"type\":\"string\",\"description\":\"Working directory for the child agent. AGENTS.md and skills are discovered from here.\"}}},\"strict\":false}],\"top_logprobs\":0,\"top_p\":1,\"truncation\":\"disabled\",\"usage\":null,\"user\":null,\"metadata\":{}},\"sequence_number\":1}", - "event: response.output_item.added\ndata: {\"type\":\"response.output_item.added\",\"item\":{\"id\":\"msg_09b32b2821e9a4d8016a184600019081929d34bb7d4c6a246f\",\"type\":\"message\",\"status\":\"in_progress\",\"content\":[],\"role\":\"assistant\"},\"output_index\":0,\"sequence_number\":2}", - "event: response.content_part.added\ndata: {\"type\":\"response.content_part.added\",\"content_index\":0,\"item_id\":\"msg_09b32b2821e9a4d8016a184600019081929d34bb7d4c6a246f\",\"output_index\":0,\"part\":{\"type\":\"output_text\",\"annotations\":[],\"logprobs\":[],\"text\":\"\"},\"sequence_number\":3}", - "event: response.output_text.delta\ndata: {\"type\":\"response.output_text.delta\",\"content_index\":0,\"delta\":\"TASK\",\"item_id\":\"msg_09b32b2821e9a4d8016a184600019081929d34bb7d4c6a246f\",\"logprobs\":[],\"obfuscation\":\"pyb2YZ3HkAzp\",\"output_index\":0,\"sequence_number\":4}", - "event: response.output_text.delta\ndata: {\"type\":\"response.output_text.delta\",\"content_index\":0,\"delta\":\"_DONE\",\"item_id\":\"msg_09b32b2821e9a4d8016a184600019081929d34bb7d4c6a246f\",\"logprobs\":[],\"obfuscation\":\"zDbKM0sYFZQ\",\"output_index\":0,\"sequence_number\":5}", - "event: response.output_text.done\ndata: {\"type\":\"response.output_text.done\",\"content_index\":0,\"item_id\":\"msg_09b32b2821e9a4d8016a184600019081929d34bb7d4c6a246f\",\"logprobs\":[],\"output_index\":0,\"sequence_number\":6,\"text\":\"TASK_DONE\"}", - "event: response.content_part.done\ndata: {\"type\":\"response.content_part.done\",\"content_index\":0,\"item_id\":\"msg_09b32b2821e9a4d8016a184600019081929d34bb7d4c6a246f\",\"output_index\":0,\"part\":{\"type\":\"output_text\",\"annotations\":[],\"logprobs\":[],\"text\":\"TASK_DONE\"},\"sequence_number\":7}", - "event: response.output_item.done\ndata: {\"type\":\"response.output_item.done\",\"item\":{\"id\":\"msg_09b32b2821e9a4d8016a184600019081929d34bb7d4c6a246f\",\"type\":\"message\",\"status\":\"completed\",\"content\":[{\"type\":\"output_text\",\"annotations\":[],\"logprobs\":[],\"text\":\"TASK_DONE\"}],\"role\":\"assistant\"},\"output_index\":0,\"sequence_number\":8}", - "event: response.completed\ndata: {\"type\":\"response.completed\",\"response\":{\"id\":\"resp_09b32b2821e9a4d8016a1845ff2ea88192a734cf6ba6abcb9d\",\"object\":\"response\",\"created_at\":1779975679,\"status\":\"completed\",\"background\":false,\"completed_at\":1779975680,\"error\":null,\"frequency_penalty\":0,\"incomplete_details\":null,\"instructions\":null,\"max_output_tokens\":null,\"max_tool_calls\":null,\"model\":\"gpt-4o-mini-2024-07-18\",\"moderation\":null,\"output\":[{\"id\":\"msg_09b32b2821e9a4d8016a184600019081929d34bb7d4c6a246f\",\"type\":\"message\",\"status\":\"completed\",\"content\":[{\"type\":\"output_text\",\"annotations\":[],\"logprobs\":[],\"text\":\"TASK_DONE\"}],\"role\":\"assistant\"}],\"parallel_tool_calls\":true,\"presence_penalty\":0,\"previous_response_id\":null,\"prompt_cache_key\":\"flue-e2e-instance::default::task:task:4cfd6e11-bac1-4736-9b87-b0\",\"prompt_cache_retention\":\"in_memory\",\"reasoning\":{\"context\":null,\"effort\":null,\"summary\":null},\"safety_identifier\":null,\"service_tier\":\"default\",\"store\":false,\"temperature\":1,\"text\":{\"format\":{\"type\":\"text\"},\"verbosity\":\"medium\"},\"tool_choice\":\"auto\",\"tools\":[{\"type\":\"function\",\"description\":\"Read a file or list a directory. For files, output is truncated to 2000 lines or 50KB — use offset/limit for large files. For directories, returns the list of entries.\",\"name\":\"read\",\"parameters\":{\"type\":\"object\",\"required\":[\"path\"],\"properties\":{\"path\":{\"type\":\"string\",\"description\":\"Path to the file to read\"},\"offset\":{\"type\":\"number\",\"description\":\"Line number to start from (1-indexed)\"},\"limit\":{\"type\":\"number\",\"description\":\"Maximum number of lines to read\"}}},\"strict\":false},{\"type\":\"function\",\"description\":\"Write content to a file. Creates the file and parent directories if they do not exist.\",\"name\":\"write\",\"parameters\":{\"type\":\"object\",\"required\":[\"path\",\"content\"],\"properties\":{\"path\":{\"type\":\"string\",\"description\":\"Path to the file to write\"},\"content\":{\"type\":\"string\",\"description\":\"Content to write to the file\"}}},\"strict\":false},{\"type\":\"function\",\"description\":\"Edit a file using exact text replacement. The oldText must match a unique region of the file. Use replaceAll to replace all occurrences.\",\"name\":\"edit\",\"parameters\":{\"type\":\"object\",\"required\":[\"path\",\"oldText\",\"newText\"],\"properties\":{\"path\":{\"type\":\"string\",\"description\":\"Path to the file to edit\"},\"oldText\":{\"type\":\"string\",\"description\":\"Exact text to find (must be unique)\"},\"newText\":{\"type\":\"string\",\"description\":\"Replacement text\"},\"replaceAll\":{\"type\":\"boolean\",\"description\":\"Replace all occurrences\"}}},\"strict\":false},{\"type\":\"function\",\"description\":\"Execute a bash command. Returns stdout and stderr. Output is truncated to the last 2000 lines or 50KB.\",\"name\":\"bash\",\"parameters\":{\"type\":\"object\",\"required\":[\"command\"],\"properties\":{\"command\":{\"type\":\"string\",\"description\":\"Bash command to execute\"},\"timeout\":{\"type\":\"number\",\"description\":\"Timeout in seconds\"}}},\"strict\":false},{\"type\":\"function\",\"description\":\"Search file contents for a regex pattern. Returns matching lines with file paths and line numbers.\",\"name\":\"grep\",\"parameters\":{\"type\":\"object\",\"required\":[\"pattern\"],\"properties\":{\"pattern\":{\"type\":\"string\",\"description\":\"Search pattern (regex)\"},\"path\":{\"type\":\"string\",\"description\":\"Directory or file to search (default: .)\"},\"include\":{\"type\":\"string\",\"description\":\"Glob filter, e.g. \\\"*.ts\\\"\"}}},\"strict\":false},{\"type\":\"function\",\"description\":\"Find files by filename pattern using shell find -name semantics. Returns matching file paths.\",\"name\":\"glob\",\"parameters\":{\"type\":\"object\",\"required\":[\"pattern\"],\"properties\":{\"pattern\":{\"type\":\"string\",\"description\":\"Filename pattern, e.g. \\\"*.ts\\\"\"},\"path\":{\"type\":\"string\",\"description\":\"Directory to search in (default: .)\"}}},\"strict\":false},{\"type\":\"function\",\"description\":\"Delegate a focused task to a detached child agent with its own context. Use this for independent research, file exploration, or parallel work. The task returns only its final answer to this conversation. No subagents are currently defined.\",\"name\":\"task\",\"parameters\":{\"type\":\"object\",\"required\":[\"prompt\"],\"properties\":{\"description\":{\"type\":\"string\",\"description\":\"Short human-readable label for the delegated work\"},\"prompt\":{\"type\":\"string\",\"description\":\"Focused instructions for the child agent\"},\"agent\":{\"type\":\"string\",\"description\":\"Declared subagent to use for the child agent\"},\"cwd\":{\"type\":\"string\",\"description\":\"Working directory for the child agent. AGENTS.md and skills are discovered from here.\"}}},\"strict\":false}],\"top_logprobs\":0,\"top_p\":1,\"truncation\":\"disabled\",\"usage\":{\"input_tokens\":804,\"input_tokens_details\":{\"cached_tokens\":0},\"output_tokens\":4,\"output_tokens_details\":{\"reasoning_tokens\":0},\"total_tokens\":808},\"user\":null,\"metadata\":{}},\"sequence_number\":9}" - ], - "kind": "sse" - }, - "headers": { - "access-control-expose-headers": "X-Request-ID, CF-Ray, CF-Ray", - "alt-svc": "h3=\":443\"; ma=86400", - "cf-cache-status": "DYNAMIC", - "cf-ray": "a02dad1668335adb-VIE", - "connection": "keep-alive", - "content-type": "text/event-stream; charset=utf-8", - "date": "Thu, 28 May 2026 13:41:19 GMT", - "openai-organization": "braintrust-data", - "openai-processing-ms": "321", - "openai-project": "proj_vsCSXafhhByzWOThMrJcZiw9", - "openai-version": "2020-10-01", - "server": "cloudflare", - "set-cookie": "[REDACTED]", - "strict-transport-security": "max-age=31536000; includeSubDomains; preload", - "transfer-encoding": "chunked", - "x-content-type-options": "nosniff", - "x-request-id": "req_fdef3765899a4c91bc975a65974dd9bb" - }, - "status": 200, - "statusText": "OK" - } - }, - { - "callIndex": 1, - "id": "f12aff3913439248", - "matchKey": "POST api.openai.com/v1/responses", - "recordedAt": "2026-05-28T13:41:21.738Z", - "request": { - "body": { - "kind": "json", - "value": { - "input": [ - { - "content": "You are a context summarization assistant. Your task is to read a conversation between a user and an AI coding assistant, then produce a structured summary following the exact format specified.\n\nDo NOT continue the conversation. Do NOT respond to any questions in the conversation. ONLY output the structured summary.", - "role": "system" - }, - { - "content": [ - { - "text": "\n[User]: Complete this instrumented research flow. Call exactly one tool per turn and wait for each tool result before choosing the next tool. Step 1: call lookup with query \"flue instrumentation\". Step 2: use the lookup result id as lookupId and call web_search with query \"Braintrust Flue reasoning stream instrumentation\". Step 3: use the first web_search result url and call summarize_source. After summarize_source returns, reply with exactly PROMPT_DONE and no other text.\n\n[Assistant thinking]: The user wants me to complete a research flow with exactly three steps:\n1. Call lookup with query \"flue instrumentation\"\n2. Call web_search with the lookupId from step 1 and query \"Braintrust Flue reasoning stream instrumentation\"\n3. Call summarize_source with the first URL from step 2\n4. Reply with exactly \"PROMPT_DONE\"\n\nThe user emphasizes I should call exactly one tool per turn and wait for each result. Let me start with step 1.\n\n[Assistant]: I'll complete this instrumented research flow step by step. Starting with Step 1:\n\n[Assistant tool calls]: lookup(query=\"flue instrumentation\")\n\n[Tool result]: {\"id\":\"flue-session-2026\",\"query\":\"flue instrumentation\",\"topic\":\"session instrumentation\"}\n\n[Assistant thinking]: Good, I got the lookup result with id \"flue-session-2026\". Now I need to proceed to step 2: call web_search with this lookupId and the query \"Braintrust Flue reasoning stream instrumentation\".\n\n[Assistant]: Now proceeding to Step 2:\n\n[Assistant tool calls]: web_search(lookupId=\"flue-session-2026\", query=\"Braintrust Flue reasoning stream instrumentation\")\n\n[Tool result]: {\"lookupId\":\"flue-session-2026\",\"query\":\"Braintrust Flue reasoning stream instrumentation\",\"results\":[{\"title\":\"Flue reasoning stream instrumentation\",\"url\":\"https://example.test/flue/reasoning-streams\"}]}\n\n[Assistant thinking]: Perfect! I got the web_search results with one result. The first (and only) result has the URL \"https://example.test/flue/reasoning-streams\". Now I need to proceed to step 3: call summarize_source with this URL.\n\n[Assistant]: Now proceeding to Step 3:\n\n[Assistant tool calls]: summarize_source(url=\"https://example.test/flue/reasoning-streams\")\n\n[Tool result]: {\"summary\":\"Flue emits reasoning, tool execution, and LLM turn events separately.\",\"url\":\"https://example.test/flue/reasoning-streams\"}\n\n\nThis is the PREFIX of a turn that was too large to keep. The SUFFIX (recent work) is retained.\n\nSummarize the prefix to provide context for the retained suffix:\n\n## Original Request\n[What did the user ask for in this turn?]\n\n## Early Progress\n- [Key decisions and work done in the prefix]\n\n## Context for Suffix\n- [Information needed to understand the retained recent work]\n\nBe concise. Focus on what's needed to understand the kept suffix.", - "type": "input_text" - } - ], - "role": "user" - } - ], - "max_output_tokens": 32, - "model": "gpt-4o-mini", - "store": false, - "stream": true - } - }, - "headers": {}, - "method": "POST", - "url": "https://api.openai.com/v1/responses" - }, - "response": { - "body": { - "chunks": [ - "event: response.created\ndata: {\"type\":\"response.created\",\"response\":{\"id\":\"resp_01ecf68325f9e202016a184600a16881a08ded2a1961eda9f9\",\"object\":\"response\",\"created_at\":1779975680,\"status\":\"in_progress\",\"background\":false,\"completed_at\":null,\"error\":null,\"frequency_penalty\":0,\"incomplete_details\":null,\"instructions\":null,\"max_output_tokens\":32,\"max_tool_calls\":null,\"model\":\"gpt-4o-mini-2024-07-18\",\"moderation\":null,\"output\":[],\"parallel_tool_calls\":true,\"presence_penalty\":0,\"previous_response_id\":null,\"prompt_cache_key\":null,\"prompt_cache_retention\":\"in_memory\",\"reasoning\":{\"context\":null,\"effort\":null,\"summary\":null},\"safety_identifier\":null,\"service_tier\":\"auto\",\"store\":false,\"temperature\":1,\"text\":{\"format\":{\"type\":\"text\"},\"verbosity\":\"medium\"},\"tool_choice\":\"auto\",\"tools\":[],\"top_logprobs\":0,\"top_p\":1,\"truncation\":\"disabled\",\"usage\":null,\"user\":null,\"metadata\":{}},\"sequence_number\":0}", - "event: response.in_progress\ndata: {\"type\":\"response.in_progress\",\"response\":{\"id\":\"resp_01ecf68325f9e202016a184600a16881a08ded2a1961eda9f9\",\"object\":\"response\",\"created_at\":1779975680,\"status\":\"in_progress\",\"background\":false,\"completed_at\":null,\"error\":null,\"frequency_penalty\":0,\"incomplete_details\":null,\"instructions\":null,\"max_output_tokens\":32,\"max_tool_calls\":null,\"model\":\"gpt-4o-mini-2024-07-18\",\"moderation\":null,\"output\":[],\"parallel_tool_calls\":true,\"presence_penalty\":0,\"previous_response_id\":null,\"prompt_cache_key\":null,\"prompt_cache_retention\":\"in_memory\",\"reasoning\":{\"context\":null,\"effort\":null,\"summary\":null},\"safety_identifier\":null,\"service_tier\":\"auto\",\"store\":false,\"temperature\":1,\"text\":{\"format\":{\"type\":\"text\"},\"verbosity\":\"medium\"},\"tool_choice\":\"auto\",\"tools\":[],\"top_logprobs\":0,\"top_p\":1,\"truncation\":\"disabled\",\"usage\":null,\"user\":null,\"metadata\":{}},\"sequence_number\":1}", - "event: response.output_item.added\ndata: {\"type\":\"response.output_item.added\",\"item\":{\"id\":\"msg_01ecf68325f9e202016a184601309081a0878ef4054e950d32\",\"type\":\"message\",\"status\":\"in_progress\",\"content\":[],\"role\":\"assistant\"},\"output_index\":0,\"sequence_number\":2}", - "event: response.content_part.added\ndata: {\"type\":\"response.content_part.added\",\"content_index\":0,\"item_id\":\"msg_01ecf68325f9e202016a184601309081a0878ef4054e950d32\",\"output_index\":0,\"part\":{\"type\":\"output_text\",\"annotations\":[],\"logprobs\":[],\"text\":\"\"},\"sequence_number\":3}", - "event: response.output_text.delta\ndata: {\"type\":\"response.output_text.delta\",\"content_index\":0,\"delta\":\"##\",\"item_id\":\"msg_01ecf68325f9e202016a184601309081a0878ef4054e950d32\",\"logprobs\":[],\"obfuscation\":\"d6v2dmzPZ7YChr\",\"output_index\":0,\"sequence_number\":4}", - "event: response.output_text.delta\ndata: {\"type\":\"response.output_text.delta\",\"content_index\":0,\"delta\":\" Original\",\"item_id\":\"msg_01ecf68325f9e202016a184601309081a0878ef4054e950d32\",\"logprobs\":[],\"obfuscation\":\"AQ3m9tJ\",\"output_index\":0,\"sequence_number\":5}", - "event: response.output_text.delta\ndata: {\"type\":\"response.output_text.delta\",\"content_index\":0,\"delta\":\" Request\",\"item_id\":\"msg_01ecf68325f9e202016a184601309081a0878ef4054e950d32\",\"logprobs\":[],\"obfuscation\":\"UKhv1LKl\",\"output_index\":0,\"sequence_number\":6}", - "event: response.output_text.delta\ndata: {\"type\":\"response.output_text.delta\",\"content_index\":0,\"delta\":\"\\n\",\"item_id\":\"msg_01ecf68325f9e202016a184601309081a0878ef4054e950d32\",\"logprobs\":[],\"obfuscation\":\"5muwgiakPkjQduY\",\"output_index\":0,\"sequence_number\":7}", - "event: response.output_text.delta\ndata: {\"type\":\"response.output_text.delta\",\"content_index\":0,\"delta\":\"The\",\"item_id\":\"msg_01ecf68325f9e202016a184601309081a0878ef4054e950d32\",\"logprobs\":[],\"obfuscation\":\"gYHJ7xqWx893Q\",\"output_index\":0,\"sequence_number\":8}", - "event: response.output_text.delta\ndata: {\"type\":\"response.output_text.delta\",\"content_index\":0,\"delta\":\" user\",\"item_id\":\"msg_01ecf68325f9e202016a184601309081a0878ef4054e950d32\",\"logprobs\":[],\"obfuscation\":\"ta2Vk95LgSS\",\"output_index\":0,\"sequence_number\":9}", - "event: response.output_text.delta\ndata: {\"type\":\"response.output_text.delta\",\"content_index\":0,\"delta\":\" requested\",\"item_id\":\"msg_01ecf68325f9e202016a184601309081a0878ef4054e950d32\",\"logprobs\":[],\"obfuscation\":\"T18OOl\",\"output_index\":0,\"sequence_number\":10}", - "event: response.output_text.delta\ndata: {\"type\":\"response.output_text.delta\",\"content_index\":0,\"delta\":\" to\",\"item_id\":\"msg_01ecf68325f9e202016a184601309081a0878ef4054e950d32\",\"logprobs\":[],\"obfuscation\":\"CgWTkMgs6kW4s\",\"output_index\":0,\"sequence_number\":11}", - "event: response.output_text.delta\ndata: {\"type\":\"response.output_text.delta\",\"content_index\":0,\"delta\":\" complete\",\"item_id\":\"msg_01ecf68325f9e202016a184601309081a0878ef4054e950d32\",\"logprobs\":[],\"obfuscation\":\"PQ7u3KE\",\"output_index\":0,\"sequence_number\":12}", - "event: response.output_text.delta\ndata: {\"type\":\"response.output_text.delta\",\"content_index\":0,\"delta\":\" an\",\"item_id\":\"msg_01ecf68325f9e202016a184601309081a0878ef4054e950d32\",\"logprobs\":[],\"obfuscation\":\"fyFt8NlmxkL8T\",\"output_index\":0,\"sequence_number\":13}", - "event: response.output_text.delta\ndata: {\"type\":\"response.output_text.delta\",\"content_index\":0,\"delta\":\" instrument\",\"item_id\":\"msg_01ecf68325f9e202016a184601309081a0878ef4054e950d32\",\"logprobs\":[],\"obfuscation\":\"idBTA\",\"output_index\":0,\"sequence_number\":14}", - "event: response.output_text.delta\ndata: {\"type\":\"response.output_text.delta\",\"content_index\":0,\"delta\":\"ed\",\"item_id\":\"msg_01ecf68325f9e202016a184601309081a0878ef4054e950d32\",\"logprobs\":[],\"obfuscation\":\"Jqp8Abx3NlEpYO\",\"output_index\":0,\"sequence_number\":15}", - "event: response.output_text.delta\ndata: {\"type\":\"response.output_text.delta\",\"content_index\":0,\"delta\":\" research\",\"item_id\":\"msg_01ecf68325f9e202016a184601309081a0878ef4054e950d32\",\"logprobs\":[],\"obfuscation\":\"2Z7W3UL\",\"output_index\":0,\"sequence_number\":16}", - "event: response.output_text.delta\ndata: {\"type\":\"response.output_text.delta\",\"content_index\":0,\"delta\":\" flow\",\"item_id\":\"msg_01ecf68325f9e202016a184601309081a0878ef4054e950d32\",\"logprobs\":[],\"obfuscation\":\"CWcmUwdOK0I\",\"output_index\":0,\"sequence_number\":17}", - "event: response.output_text.delta\ndata: {\"type\":\"response.output_text.delta\",\"content_index\":0,\"delta\":\" consisting\",\"item_id\":\"msg_01ecf68325f9e202016a184601309081a0878ef4054e950d32\",\"logprobs\":[],\"obfuscation\":\"5A8K0\",\"output_index\":0,\"sequence_number\":18}", - "event: response.output_text.delta\ndata: {\"type\":\"response.output_text.delta\",\"content_index\":0,\"delta\":\" of\",\"item_id\":\"msg_01ecf68325f9e202016a184601309081a0878ef4054e950d32\",\"logprobs\":[],\"obfuscation\":\"8mUUf5KvarpcJ\",\"output_index\":0,\"sequence_number\":19}", - "event: response.output_text.delta\ndata: {\"type\":\"response.output_text.delta\",\"content_index\":0,\"delta\":\" three\",\"item_id\":\"msg_01ecf68325f9e202016a184601309081a0878ef4054e950d32\",\"logprobs\":[],\"obfuscation\":\"RpVzByEpb2\",\"output_index\":0,\"sequence_number\":20}", - "event: response.output_text.delta\ndata: {\"type\":\"response.output_text.delta\",\"content_index\":0,\"delta\":\" specific\",\"item_id\":\"msg_01ecf68325f9e202016a184601309081a0878ef4054e950d32\",\"logprobs\":[],\"obfuscation\":\"9qbTunN\",\"output_index\":0,\"sequence_number\":21}", - "event: response.output_text.delta\ndata: {\"type\":\"response.output_text.delta\",\"content_index\":0,\"delta\":\" steps\",\"item_id\":\"msg_01ecf68325f9e202016a184601309081a0878ef4054e950d32\",\"logprobs\":[],\"obfuscation\":\"sRljhC5wZy\",\"output_index\":0,\"sequence_number\":22}", - "event: response.output_text.delta\ndata: {\"type\":\"response.output_text.delta\",\"content_index\":0,\"delta\":\",\",\"item_id\":\"msg_01ecf68325f9e202016a184601309081a0878ef4054e950d32\",\"logprobs\":[],\"obfuscation\":\"LQiEbRM4fizUNQm\",\"output_index\":0,\"sequence_number\":23}", - "event: response.output_text.delta\ndata: {\"type\":\"response.output_text.delta\",\"content_index\":0,\"delta\":\" involving\",\"item_id\":\"msg_01ecf68325f9e202016a184601309081a0878ef4054e950d32\",\"logprobs\":[],\"obfuscation\":\"CFyfLz\",\"output_index\":0,\"sequence_number\":24}", - "event: response.output_text.delta\ndata: {\"type\":\"response.output_text.delta\",\"content_index\":0,\"delta\":\" calling\",\"item_id\":\"msg_01ecf68325f9e202016a184601309081a0878ef4054e950d32\",\"logprobs\":[],\"obfuscation\":\"E6cI70HR\",\"output_index\":0,\"sequence_number\":25}", - "event: response.output_text.delta\ndata: {\"type\":\"response.output_text.delta\",\"content_index\":0,\"delta\":\" various\",\"item_id\":\"msg_01ecf68325f9e202016a184601309081a0878ef4054e950d32\",\"logprobs\":[],\"obfuscation\":\"GFRuEgJV\",\"output_index\":0,\"sequence_number\":26}", - "event: response.output_text.delta\ndata: {\"type\":\"response.output_text.delta\",\"content_index\":0,\"delta\":\" tools\",\"item_id\":\"msg_01ecf68325f9e202016a184601309081a0878ef4054e950d32\",\"logprobs\":[],\"obfuscation\":\"X4Ttj2uHGX\",\"output_index\":0,\"sequence_number\":27}", - "event: response.output_text.delta\ndata: {\"type\":\"response.output_text.delta\",\"content_index\":0,\"delta\":\" in\",\"item_id\":\"msg_01ecf68325f9e202016a184601309081a0878ef4054e950d32\",\"logprobs\":[],\"obfuscation\":\"9vlDZO40VZwFr\",\"output_index\":0,\"sequence_number\":28}", - "event: response.output_text.delta\ndata: {\"type\":\"response.output_text.delta\",\"content_index\":0,\"delta\":\" a\",\"item_id\":\"msg_01ecf68325f9e202016a184601309081a0878ef4054e950d32\",\"logprobs\":[],\"obfuscation\":\"tWdPAInHFKDip7\",\"output_index\":0,\"sequence_number\":29}", - "event: response.output_text.delta\ndata: {\"type\":\"response.output_text.delta\",\"content_index\":0,\"delta\":\" sequential\",\"item_id\":\"msg_01ecf68325f9e202016a184601309081a0878ef4054e950d32\",\"logprobs\":[],\"obfuscation\":\"Rct1b\",\"output_index\":0,\"sequence_number\":30}", - "event: response.output_text.delta\ndata: {\"type\":\"response.output_text.delta\",\"content_index\":0,\"delta\":\" manner\",\"item_id\":\"msg_01ecf68325f9e202016a184601309081a0878ef4054e950d32\",\"logprobs\":[],\"obfuscation\":\"0x4W3URIt\",\"output_index\":0,\"sequence_number\":31}", - "event: response.output_text.delta\ndata: {\"type\":\"response.output_text.delta\",\"content_index\":0,\"delta\":\".\\n\\n\",\"item_id\":\"msg_01ecf68325f9e202016a184601309081a0878ef4054e950d32\",\"logprobs\":[],\"obfuscation\":\"EscdvHRY9LVkB\",\"output_index\":0,\"sequence_number\":32}", - "event: response.output_text.delta\ndata: {\"type\":\"response.output_text.delta\",\"content_index\":0,\"delta\":\"##\",\"item_id\":\"msg_01ecf68325f9e202016a184601309081a0878ef4054e950d32\",\"logprobs\":[],\"obfuscation\":\"7fE3Nivp8RY0eK\",\"output_index\":0,\"sequence_number\":33}", - "event: response.output_text.delta\ndata: {\"type\":\"response.output_text.delta\",\"content_index\":0,\"delta\":\" Early\",\"item_id\":\"msg_01ecf68325f9e202016a184601309081a0878ef4054e950d32\",\"logprobs\":[],\"obfuscation\":\"qsGDoylwsO\",\"output_index\":0,\"sequence_number\":34}", - "event: response.output_text.delta\ndata: {\"type\":\"response.output_text.delta\",\"content_index\":0,\"delta\":\" Progress\",\"item_id\":\"msg_01ecf68325f9e202016a184601309081a0878ef4054e950d32\",\"logprobs\":[],\"obfuscation\":\"WxaVUkk\",\"output_index\":0,\"sequence_number\":35}", - "event: response.output_text.done\ndata: {\"type\":\"response.output_text.done\",\"content_index\":0,\"item_id\":\"msg_01ecf68325f9e202016a184601309081a0878ef4054e950d32\",\"logprobs\":[],\"output_index\":0,\"sequence_number\":36,\"text\":\"## Original Request\\nThe user requested to complete an instrumented research flow consisting of three specific steps, involving calling various tools in a sequential manner.\\n\\n## Early Progress\"}", - "event: response.content_part.done\ndata: {\"type\":\"response.content_part.done\",\"content_index\":0,\"item_id\":\"msg_01ecf68325f9e202016a184601309081a0878ef4054e950d32\",\"output_index\":0,\"part\":{\"type\":\"output_text\",\"annotations\":[],\"logprobs\":[],\"text\":\"## Original Request\\nThe user requested to complete an instrumented research flow consisting of three specific steps, involving calling various tools in a sequential manner.\\n\\n## Early Progress\"},\"sequence_number\":37}", - "event: response.output_item.done\ndata: {\"type\":\"response.output_item.done\",\"item\":{\"id\":\"msg_01ecf68325f9e202016a184601309081a0878ef4054e950d32\",\"type\":\"message\",\"status\":\"incomplete\",\"content\":[{\"type\":\"output_text\",\"annotations\":[],\"logprobs\":[],\"text\":\"## Original Request\\nThe user requested to complete an instrumented research flow consisting of three specific steps, involving calling various tools in a sequential manner.\\n\\n## Early Progress\"}],\"role\":\"assistant\"},\"output_index\":0,\"sequence_number\":38}", - "event: response.incomplete\ndata: {\"type\":\"response.incomplete\",\"response\":{\"id\":\"resp_01ecf68325f9e202016a184600a16881a08ded2a1961eda9f9\",\"object\":\"response\",\"created_at\":1779975680,\"status\":\"incomplete\",\"background\":false,\"completed_at\":null,\"error\":null,\"frequency_penalty\":0,\"incomplete_details\":{\"reason\":\"max_output_tokens\"},\"instructions\":null,\"max_output_tokens\":32,\"max_tool_calls\":null,\"model\":\"gpt-4o-mini-2024-07-18\",\"moderation\":null,\"output\":[{\"id\":\"msg_01ecf68325f9e202016a184601309081a0878ef4054e950d32\",\"type\":\"message\",\"status\":\"incomplete\",\"content\":[{\"type\":\"output_text\",\"annotations\":[],\"logprobs\":[],\"text\":\"## Original Request\\nThe user requested to complete an instrumented research flow consisting of three specific steps, involving calling various tools in a sequential manner.\\n\\n## Early Progress\"}],\"role\":\"assistant\"}],\"parallel_tool_calls\":true,\"presence_penalty\":0,\"previous_response_id\":null,\"prompt_cache_key\":null,\"prompt_cache_retention\":\"in_memory\",\"reasoning\":{\"context\":null,\"effort\":null,\"summary\":null},\"safety_identifier\":null,\"service_tier\":\"default\",\"store\":false,\"temperature\":1,\"text\":{\"format\":{\"type\":\"text\"},\"verbosity\":\"medium\"},\"tool_choice\":\"auto\",\"tools\":[],\"top_logprobs\":0,\"top_p\":1,\"truncation\":\"disabled\",\"usage\":{\"input_tokens\":694,\"input_tokens_details\":{\"cached_tokens\":0},\"output_tokens\":32,\"output_tokens_details\":{\"reasoning_tokens\":0},\"total_tokens\":726},\"user\":null,\"metadata\":{}},\"sequence_number\":39}" - ], - "kind": "sse" - }, - "headers": { - "access-control-expose-headers": "X-Request-ID, CF-Ray, CF-Ray", - "alt-svc": "h3=\":443\"; ma=86400", - "cf-cache-status": "DYNAMIC", - "cf-ray": "a02dad234c015adb-VIE", - "connection": "keep-alive", - "content-type": "text/event-stream; charset=utf-8", - "date": "Thu, 28 May 2026 13:41:20 GMT", - "openai-organization": "braintrust-data", - "openai-processing-ms": "90", - "openai-project": "proj_vsCSXafhhByzWOThMrJcZiw9", - "openai-version": "2020-10-01", - "server": "cloudflare", - "set-cookie": "[REDACTED]", - "strict-transport-security": "max-age=31536000; includeSubDomains; preload", - "transfer-encoding": "chunked", - "x-content-type-options": "nosniff", - "x-request-id": "req_2bb8f213d8be45a8a8c6368e34e80335" - }, - "status": 200, - "statusText": "OK" - } - } - ], - "meta": { - "createdAt": "2026-05-28T13:01:46.277Z" - } -} diff --git a/e2e/scenarios/flue-instrumentation/__cassettes__/flue-v0-8-latest.cassette.json b/e2e/scenarios/flue-instrumentation/__cassettes__/flue-v0-8-latest.cassette.json deleted file mode 100644 index 69d4e8bd6..000000000 --- a/e2e/scenarios/flue-instrumentation/__cassettes__/flue-v0-8-latest.cassette.json +++ /dev/null @@ -1,5859 +0,0 @@ -{ - "entries": [ - { - "callIndex": 0, - "id": "7c0e162d6794cf5d", - "matchKey": "POST api.openai.com/v1/responses", - "recordedAt": "2026-08-03T09:12:00.374Z", - "request": { - "body": { - "kind": "json", - "value": { - "include": ["reasoning.encrypted_content"], - "input": [ - { - "content": "You are running in headless mode with no human operator. Work autonomously — never ask questions, never wait for user input. Make your best judgment and proceed independently.\n\nYou are a deterministic Flue instrumentation test agent. Follow user instructions exactly. When asked for a marker, output only that marker and no extra text. When running a local skill file, read it yourself and do not delegate it to a task.\n\n## Available Skills\n\nThe following skills provide specialized instructions for specific tasks. When a task matches a skill description, activate that skill before proceeding so its full instructions are loaded. Skill instructions and supporting resources stay lazy until activation or explicit file reads.\n\n- **e2e-flue-skill** — Deterministic skill used by the Braintrust Flue instrumentation e2e test.\n\nDate: Mon, Aug 3, 2026\nWorking directory: .\n\nDirectory structure:\n.agents\n.flue\n.flue-build\n__cassettes__\n__snapshots__\nassertions.ts\ncassette-filter.mjs\nconstants.mjs\ndist\nnode_modules\npackage.json\npnpm-lock.yaml\nscenario.cli.mjs\nscenario.impl.mjs\nscenario.mjs\nscenario.test.ts\nscenario.ts\nscenario.v2.ts\nv2-assertions.ts\nversions", - "role": "developer" - }, - { - "content": [ - { - "text": "Complete this instrumented research flow. Call exactly one tool per turn and wait for each tool result before choosing the next tool. Step 1: call lookup with query \"flue instrumentation\". Step 2: use the lookup result id as lookupId and call web_search with query \"Braintrust Flue reasoning stream instrumentation\". Step 3: use the first web_search result url and call summarize_source. After summarize_source returns, reply with exactly PROMPT_DONE and no other text.", - "type": "input_text" - } - ], - "role": "user" - } - ], - "model": "gpt-5.4-nano", - "prompt_cache_key": "[REDACTED]", - "reasoning": { - "effort": "low", - "summary": "auto" - }, - "store": false, - "stream": true, - "tools": [ - { - "description": "Read a file or list a directory. For files, output is truncated to 2000 lines or 50KB — use offset/limit for large files. For directories, returns the list of entries.", - "name": "read", - "parameters": { - "properties": { - "limit": { - "description": "Maximum number of lines to read", - "type": "number" - }, - "offset": { - "description": "Line number to start from (1-indexed)", - "type": "number" - }, - "path": { - "description": "Path to the file to read", - "type": "string" - } - }, - "required": ["path"], - "type": "object" - }, - "strict": false, - "type": "function" - }, - { - "description": "Write content to a file. Creates the file and parent directories if they do not exist.", - "name": "write", - "parameters": { - "properties": { - "content": { - "description": "Content to write to the file", - "type": "string" - }, - "path": { - "description": "Path to the file to write", - "type": "string" - } - }, - "required": ["path", "content"], - "type": "object" - }, - "strict": false, - "type": "function" - }, - { - "description": "Edit a file using exact text replacement. The oldText must match a unique region of the file. Use replaceAll to replace all occurrences.", - "name": "edit", - "parameters": { - "properties": { - "newText": { - "description": "Replacement text", - "type": "string" - }, - "oldText": { - "description": "Exact text to find (must be unique)", - "type": "string" - }, - "path": { - "description": "Path to the file to edit", - "type": "string" - }, - "replaceAll": { - "description": "Replace all occurrences", - "type": "boolean" - } - }, - "required": ["path", "oldText", "newText"], - "type": "object" - }, - "strict": false, - "type": "function" - }, - { - "description": "Execute a bash command. Returns stdout and stderr. Output is truncated to the last 2000 lines or 50KB.", - "name": "bash", - "parameters": { - "properties": { - "command": { - "description": "Bash command to execute", - "type": "string" - }, - "timeout": { - "description": "Timeout in seconds", - "type": "number" - } - }, - "required": ["command"], - "type": "object" - }, - "strict": false, - "type": "function" - }, - { - "description": "Search file contents for a regex pattern. Returns matching lines with file paths and line numbers.", - "name": "grep", - "parameters": { - "properties": { - "include": { - "description": "Glob filter, e.g. \"*.ts\"", - "type": "string" - }, - "path": { - "description": "Directory or file to search (default: .)", - "type": "string" - }, - "pattern": { - "description": "Search pattern (regex)", - "type": "string" - } - }, - "required": ["pattern"], - "type": "object" - }, - "strict": false, - "type": "function" - }, - { - "description": "Find files by filename pattern using shell find -name semantics. Returns matching file paths.", - "name": "glob", - "parameters": { - "properties": { - "path": { - "description": "Directory to search in (default: .)", - "type": "string" - }, - "pattern": { - "description": "Filename pattern, e.g. \"*.ts\"", - "type": "string" - } - }, - "required": ["pattern"], - "type": "object" - }, - "strict": false, - "type": "function" - }, - { - "description": "Delegate a focused task to a detached child agent with its own context. Use this for independent research, file exploration, or parallel work. The task returns only its final answer to this conversation. No subagents are currently defined.", - "name": "task", - "parameters": { - "properties": { - "agent": { - "description": "Declared subagent to use for the child agent", - "type": "string" - }, - "cwd": { - "description": "Working directory for the child agent. AGENTS.md and skills are discovered from here.", - "type": "string" - }, - "description": { - "description": "Short human-readable label for the delegated work", - "type": "string" - }, - "prompt": { - "description": "Focused instructions for the child agent", - "type": "string" - } - }, - "required": ["prompt"], - "type": "object" - }, - "strict": false, - "type": "function" - }, - { - "description": "Return a deterministic lookup result with an id needed by web_search.", - "name": "lookup", - "parameters": { - "properties": { - "query": { - "type": "string" - } - }, - "required": ["query"], - "type": "object" - }, - "strict": false, - "type": "function" - }, - { - "description": "Search a deterministic local web index. Requires the id returned by lookup.", - "name": "web_search", - "parameters": { - "properties": { - "lookupId": { - "type": "string" - }, - "query": { - "type": "string" - } - }, - "required": ["lookupId", "query"], - "type": "object" - }, - "strict": false, - "type": "function" - }, - { - "description": "Summarize the selected deterministic source after web_search returns a URL.", - "name": "summarize_source", - "parameters": { - "properties": { - "url": { - "type": "string" - } - }, - "required": ["url"], - "type": "object" - }, - "strict": false, - "type": "function" - } - ] - } - }, - "headers": {}, - "method": "POST", - "url": "https://api.openai.com/v1/responses" - }, - "response": { - "body": { - "chunks": [ - "event: response.created\ndata: {\"type\":\"response.created\",\"response\":{\"id\":\"resp_045458052dc10379016a705b5f8ed48191ac5f4bcf9d7df429\",\"object\":\"response\",\"created_at\":1785748319,\"status\":\"in_progress\",\"background\":false,\"completed_at\":null,\"error\":null,\"frequency_penalty\":0,\"incomplete_details\":null,\"instructions\":null,\"max_output_tokens\":null,\"max_tool_calls\":null,\"model\":\"gpt-5.4-nano-2026-03-17\",\"moderation\":null,\"output\":[],\"parallel_tool_calls\":true,\"presence_penalty\":0,\"previous_response_id\":null,\"prompt_cache_key\":\"workflow:instrumentation:01KZ3E9TWTAEK4P4P3F63HKAKC::default::ma\",\"prompt_cache_retention\":\"24h\",\"reasoning\":{\"context\":\"current_turn\",\"effort\":\"low\",\"mode\":\"standard\",\"summary\":\"detailed\"},\"safety_identifier\":null,\"service_tier\":\"auto\",\"store\":false,\"temperature\":1,\"text\":{\"format\":{\"type\":\"text\"},\"verbosity\":\"medium\"},\"tool_choice\":\"auto\",\"tool_usage\":{\"image_gen\":{\"input_tokens\":0,\"input_tokens_details\":{\"image_tokens\":0,\"text_tokens\":0},\"output_tokens\":0,\"output_tokens_details\":{\"image_tokens\":0,\"text_tokens\":0},\"total_tokens\":0},\"web_search\":{\"num_requests\":0}},\"tools\":[{\"type\":\"function\",\"description\":\"Read a file or list a directory. For files, output is truncated to 2000 lines or 50KB — use offset/limit for large files. For directories, returns the list of entries.\",\"name\":\"read\",\"output_schema\":null,\"parameters\":{\"type\":\"object\",\"required\":[\"path\"],\"properties\":{\"path\":{\"type\":\"string\",\"description\":\"Path to the file to read\"},\"offset\":{\"type\":\"number\",\"description\":\"Line number to start from (1-indexed)\"},\"limit\":{\"type\":\"number\",\"description\":\"Maximum number of lines to read\"}}},\"strict\":false},{\"type\":\"function\",\"description\":\"Write content to a file. Creates the file and parent directories if they do not exist.\",\"name\":\"write\",\"output_schema\":null,\"parameters\":{\"type\":\"object\",\"required\":[\"path\",\"content\"],\"properties\":{\"path\":{\"type\":\"string\",\"description\":\"Path to the file to write\"},\"content\":{\"type\":\"string\",\"description\":\"Content to write to the file\"}}},\"strict\":false},{\"type\":\"function\",\"description\":\"Edit a file using exact text replacement. The oldText must match a unique region of the file. Use replaceAll to replace all occurrences.\",\"name\":\"edit\",\"output_schema\":null,\"parameters\":{\"type\":\"object\",\"required\":[\"path\",\"oldText\",\"newText\"],\"properties\":{\"path\":{\"type\":\"string\",\"description\":\"Path to the file to edit\"},\"oldText\":{\"type\":\"string\",\"description\":\"Exact text to find (must be unique)\"},\"newText\":{\"type\":\"string\",\"description\":\"Replacement text\"},\"replaceAll\":{\"type\":\"boolean\",\"description\":\"Replace all occurrences\"}}},\"strict\":false},{\"type\":\"function\",\"description\":\"Execute a bash command. Returns stdout and stderr. Output is truncated to the last 2000 lines or 50KB.\",\"name\":\"bash\",\"output_schema\":null,\"parameters\":{\"type\":\"object\",\"required\":[\"command\"],\"properties\":{\"command\":{\"type\":\"string\",\"description\":\"Bash command to execute\"},\"timeout\":{\"type\":\"number\",\"description\":\"Timeout in seconds\"}}},\"strict\":false},{\"type\":\"function\",\"description\":\"Search file contents for a regex pattern. Returns matching lines with file paths and line numbers.\",\"name\":\"grep\",\"output_schema\":null,\"parameters\":{\"type\":\"object\",\"required\":[\"pattern\"],\"properties\":{\"pattern\":{\"type\":\"string\",\"description\":\"Search pattern (regex)\"},\"path\":{\"type\":\"string\",\"description\":\"Directory or file to search (default: .)\"},\"include\":{\"type\":\"string\",\"description\":\"Glob filter, e.g. \\\"*.ts\\\"\"}}},\"strict\":false},{\"type\":\"function\",\"description\":\"Find files by filename pattern using shell find -name semantics. Returns matching file paths.\",\"name\":\"glob\",\"output_schema\":null,\"parameters\":{\"type\":\"object\",\"required\":[\"pattern\"],\"properties\":{\"pattern\":{\"type\":\"string\",\"description\":\"Filename pattern, e.g. \\\"*.ts\\\"\"},\"path\":{\"type\":\"string\",\"description\":\"Directory to search in (default: .)\"}}},\"strict\":false},{\"type\":\"function\",\"description\":\"Delegate a focused task to a detached child agent with its own context. Use this for independent research, file exploration, or parallel work. The task returns only its final answer to this conversation. No subagents are currently defined.\",\"name\":\"task\",\"output_schema\":null,\"parameters\":{\"type\":\"object\",\"required\":[\"prompt\"],\"properties\":{\"description\":{\"type\":\"string\",\"description\":\"Short human-readable label for the delegated work\"},\"prompt\":{\"type\":\"string\",\"description\":\"Focused instructions for the child agent\"},\"agent\":{\"type\":\"string\",\"description\":\"Declared subagent to use for the child agent\"},\"cwd\":{\"type\":\"string\",\"description\":\"Working directory for the child agent. AGENTS.md and skills are discovered from here.\"}}},\"strict\":false},{\"type\":\"function\",\"description\":\"Return a deterministic lookup result with an id needed by web_search.\",\"name\":\"lookup\",\"output_schema\":null,\"parameters\":{\"type\":\"object\",\"required\":[\"query\"],\"properties\":{\"query\":{\"type\":\"string\"}}},\"strict\":false},{\"type\":\"function\",\"description\":\"Search a deterministic local web index. Requires the id returned by lookup.\",\"name\":\"web_search\",\"output_schema\":null,\"parameters\":{\"type\":\"object\",\"required\":[\"lookupId\",\"query\"],\"properties\":{\"lookupId\":{\"type\":\"string\"},\"query\":{\"type\":\"string\"}}},\"strict\":false},{\"type\":\"function\",\"description\":\"Summarize the selected deterministic source after web_search returns a URL.\",\"name\":\"summarize_source\",\"output_schema\":null,\"parameters\":{\"type\":\"object\",\"required\":[\"url\"],\"properties\":{\"url\":{\"type\":\"string\"}}},\"strict\":false}],\"top_logprobs\":0,\"top_p\":0.98,\"truncation\":\"disabled\",\"usage\":null,\"user\":null,\"metadata\":{}},\"sequence_number\":0}", - "event: response.in_progress\ndata: {\"type\":\"response.in_progress\",\"response\":{\"id\":\"resp_045458052dc10379016a705b5f8ed48191ac5f4bcf9d7df429\",\"object\":\"response\",\"created_at\":1785748319,\"status\":\"in_progress\",\"background\":false,\"completed_at\":null,\"error\":null,\"frequency_penalty\":0,\"incomplete_details\":null,\"instructions\":null,\"max_output_tokens\":null,\"max_tool_calls\":null,\"model\":\"gpt-5.4-nano-2026-03-17\",\"moderation\":null,\"output\":[],\"parallel_tool_calls\":true,\"presence_penalty\":0,\"previous_response_id\":null,\"prompt_cache_key\":\"workflow:instrumentation:01KZ3E9TWTAEK4P4P3F63HKAKC::default::ma\",\"prompt_cache_retention\":\"24h\",\"reasoning\":{\"context\":\"current_turn\",\"effort\":\"low\",\"mode\":\"standard\",\"summary\":\"detailed\"},\"safety_identifier\":null,\"service_tier\":\"auto\",\"store\":false,\"temperature\":1,\"text\":{\"format\":{\"type\":\"text\"},\"verbosity\":\"medium\"},\"tool_choice\":\"auto\",\"tool_usage\":{\"image_gen\":{\"input_tokens\":0,\"input_tokens_details\":{\"image_tokens\":0,\"text_tokens\":0},\"output_tokens\":0,\"output_tokens_details\":{\"image_tokens\":0,\"text_tokens\":0},\"total_tokens\":0},\"web_search\":{\"num_requests\":0}},\"tools\":[{\"type\":\"function\",\"description\":\"Read a file or list a directory. For files, output is truncated to 2000 lines or 50KB — use offset/limit for large files. For directories, returns the list of entries.\",\"name\":\"read\",\"output_schema\":null,\"parameters\":{\"type\":\"object\",\"required\":[\"path\"],\"properties\":{\"path\":{\"type\":\"string\",\"description\":\"Path to the file to read\"},\"offset\":{\"type\":\"number\",\"description\":\"Line number to start from (1-indexed)\"},\"limit\":{\"type\":\"number\",\"description\":\"Maximum number of lines to read\"}}},\"strict\":false},{\"type\":\"function\",\"description\":\"Write content to a file. Creates the file and parent directories if they do not exist.\",\"name\":\"write\",\"output_schema\":null,\"parameters\":{\"type\":\"object\",\"required\":[\"path\",\"content\"],\"properties\":{\"path\":{\"type\":\"string\",\"description\":\"Path to the file to write\"},\"content\":{\"type\":\"string\",\"description\":\"Content to write to the file\"}}},\"strict\":false},{\"type\":\"function\",\"description\":\"Edit a file using exact text replacement. The oldText must match a unique region of the file. Use replaceAll to replace all occurrences.\",\"name\":\"edit\",\"output_schema\":null,\"parameters\":{\"type\":\"object\",\"required\":[\"path\",\"oldText\",\"newText\"],\"properties\":{\"path\":{\"type\":\"string\",\"description\":\"Path to the file to edit\"},\"oldText\":{\"type\":\"string\",\"description\":\"Exact text to find (must be unique)\"},\"newText\":{\"type\":\"string\",\"description\":\"Replacement text\"},\"replaceAll\":{\"type\":\"boolean\",\"description\":\"Replace all occurrences\"}}},\"strict\":false},{\"type\":\"function\",\"description\":\"Execute a bash command. Returns stdout and stderr. Output is truncated to the last 2000 lines or 50KB.\",\"name\":\"bash\",\"output_schema\":null,\"parameters\":{\"type\":\"object\",\"required\":[\"command\"],\"properties\":{\"command\":{\"type\":\"string\",\"description\":\"Bash command to execute\"},\"timeout\":{\"type\":\"number\",\"description\":\"Timeout in seconds\"}}},\"strict\":false},{\"type\":\"function\",\"description\":\"Search file contents for a regex pattern. Returns matching lines with file paths and line numbers.\",\"name\":\"grep\",\"output_schema\":null,\"parameters\":{\"type\":\"object\",\"required\":[\"pattern\"],\"properties\":{\"pattern\":{\"type\":\"string\",\"description\":\"Search pattern (regex)\"},\"path\":{\"type\":\"string\",\"description\":\"Directory or file to search (default: .)\"},\"include\":{\"type\":\"string\",\"description\":\"Glob filter, e.g. \\\"*.ts\\\"\"}}},\"strict\":false},{\"type\":\"function\",\"description\":\"Find files by filename pattern using shell find -name semantics. Returns matching file paths.\",\"name\":\"glob\",\"output_schema\":null,\"parameters\":{\"type\":\"object\",\"required\":[\"pattern\"],\"properties\":{\"pattern\":{\"type\":\"string\",\"description\":\"Filename pattern, e.g. \\\"*.ts\\\"\"},\"path\":{\"type\":\"string\",\"description\":\"Directory to search in (default: .)\"}}},\"strict\":false},{\"type\":\"function\",\"description\":\"Delegate a focused task to a detached child agent with its own context. Use this for independent research, file exploration, or parallel work. The task returns only its final answer to this conversation. No subagents are currently defined.\",\"name\":\"task\",\"output_schema\":null,\"parameters\":{\"type\":\"object\",\"required\":[\"prompt\"],\"properties\":{\"description\":{\"type\":\"string\",\"description\":\"Short human-readable label for the delegated work\"},\"prompt\":{\"type\":\"string\",\"description\":\"Focused instructions for the child agent\"},\"agent\":{\"type\":\"string\",\"description\":\"Declared subagent to use for the child agent\"},\"cwd\":{\"type\":\"string\",\"description\":\"Working directory for the child agent. AGENTS.md and skills are discovered from here.\"}}},\"strict\":false},{\"type\":\"function\",\"description\":\"Return a deterministic lookup result with an id needed by web_search.\",\"name\":\"lookup\",\"output_schema\":null,\"parameters\":{\"type\":\"object\",\"required\":[\"query\"],\"properties\":{\"query\":{\"type\":\"string\"}}},\"strict\":false},{\"type\":\"function\",\"description\":\"Search a deterministic local web index. Requires the id returned by lookup.\",\"name\":\"web_search\",\"output_schema\":null,\"parameters\":{\"type\":\"object\",\"required\":[\"lookupId\",\"query\"],\"properties\":{\"lookupId\":{\"type\":\"string\"},\"query\":{\"type\":\"string\"}}},\"strict\":false},{\"type\":\"function\",\"description\":\"Summarize the selected deterministic source after web_search returns a URL.\",\"name\":\"summarize_source\",\"output_schema\":null,\"parameters\":{\"type\":\"object\",\"required\":[\"url\"],\"properties\":{\"url\":{\"type\":\"string\"}}},\"strict\":false}],\"top_logprobs\":0,\"top_p\":0.98,\"truncation\":\"disabled\",\"usage\":null,\"user\":null,\"metadata\":{}},\"sequence_number\":1}", - "event: response.output_item.added\ndata: {\"type\":\"response.output_item.added\",\"item\":{\"id\":\"fc_045458052dc10379016a705b6008408191b27653755cf5bd03\",\"type\":\"function_call\",\"status\":\"in_progress\",\"arguments\":\"\",\"call_id\":\"call_rBgonKxWHwhN9E8aQ4VobIOe\",\"name\":\"lookup\"},\"output_index\":0,\"sequence_number\":2}", - "event: response.function_call_arguments.delta\ndata: {\"type\":\"response.function_call_arguments.delta\",\"delta\":\"{\\\"\",\"item_id\":\"fc_045458052dc10379016a705b6008408191b27653755cf5bd03\",\"obfuscation\":\"FJ9TowjZDi2V7k\",\"output_index\":0,\"sequence_number\":3}", - "event: response.function_call_arguments.delta\ndata: {\"type\":\"response.function_call_arguments.delta\",\"delta\":\"query\",\"item_id\":\"fc_045458052dc10379016a705b6008408191b27653755cf5bd03\",\"obfuscation\":\"s6VEwiE9UsM\",\"output_index\":0,\"sequence_number\":4}", - "event: response.function_call_arguments.delta\ndata: {\"type\":\"response.function_call_arguments.delta\",\"delta\":\"\\\":\\\"\",\"item_id\":\"fc_045458052dc10379016a705b6008408191b27653755cf5bd03\",\"obfuscation\":\"6aWQMwZgQPiRf\",\"output_index\":0,\"sequence_number\":5}", - "event: response.function_call_arguments.delta\ndata: {\"type\":\"response.function_call_arguments.delta\",\"delta\":\"fl\",\"item_id\":\"fc_045458052dc10379016a705b6008408191b27653755cf5bd03\",\"obfuscation\":\"KUoAPUAlQ7cmX9\",\"output_index\":0,\"sequence_number\":6}", - "event: response.function_call_arguments.delta\ndata: {\"type\":\"response.function_call_arguments.delta\",\"delta\":\"ue\",\"item_id\":\"fc_045458052dc10379016a705b6008408191b27653755cf5bd03\",\"obfuscation\":\"lQUgpuDVqWyLbo\",\"output_index\":0,\"sequence_number\":7}", - "event: response.function_call_arguments.delta\ndata: {\"type\":\"response.function_call_arguments.delta\",\"delta\":\" instrumentation\",\"item_id\":\"fc_045458052dc10379016a705b6008408191b27653755cf5bd03\",\"obfuscation\":\"\",\"output_index\":0,\"sequence_number\":8}", - "event: response.function_call_arguments.delta\ndata: {\"type\":\"response.function_call_arguments.delta\",\"delta\":\"\\\"}\",\"item_id\":\"fc_045458052dc10379016a705b6008408191b27653755cf5bd03\",\"obfuscation\":\"jkQ7s9o73nxqwB\",\"output_index\":0,\"sequence_number\":9}", - "event: response.function_call_arguments.done\ndata: {\"type\":\"response.function_call_arguments.done\",\"arguments\":\"{\\\"query\\\":\\\"flue instrumentation\\\"}\",\"item_id\":\"fc_045458052dc10379016a705b6008408191b27653755cf5bd03\",\"output_index\":0,\"sequence_number\":10}", - "event: response.output_item.done\ndata: {\"type\":\"response.output_item.done\",\"item\":{\"id\":\"fc_045458052dc10379016a705b6008408191b27653755cf5bd03\",\"type\":\"function_call\",\"status\":\"completed\",\"arguments\":\"{\\\"query\\\":\\\"flue instrumentation\\\"}\",\"call_id\":\"call_rBgonKxWHwhN9E8aQ4VobIOe\",\"name\":\"lookup\"},\"output_index\":0,\"sequence_number\":11}", - "event: response.completed\ndata: {\"type\":\"response.completed\",\"response\":{\"id\":\"resp_045458052dc10379016a705b5f8ed48191ac5f4bcf9d7df429\",\"object\":\"response\",\"created_at\":1785748319,\"status\":\"completed\",\"background\":false,\"completed_at\":1785748320,\"error\":null,\"frequency_penalty\":0,\"incomplete_details\":null,\"instructions\":null,\"max_output_tokens\":null,\"max_tool_calls\":null,\"model\":\"gpt-5.4-nano-2026-03-17\",\"moderation\":null,\"output\":[{\"id\":\"fc_045458052dc10379016a705b6008408191b27653755cf5bd03\",\"type\":\"function_call\",\"status\":\"completed\",\"arguments\":\"{\\\"query\\\":\\\"flue instrumentation\\\"}\",\"call_id\":\"call_rBgonKxWHwhN9E8aQ4VobIOe\",\"name\":\"lookup\"}],\"parallel_tool_calls\":true,\"presence_penalty\":0,\"previous_response_id\":null,\"prompt_cache_key\":\"workflow:instrumentation:01KZ3E9TWTAEK4P4P3F63HKAKC::default::ma\",\"prompt_cache_retention\":\"24h\",\"reasoning\":{\"context\":\"current_turn\",\"effort\":\"low\",\"mode\":\"standard\",\"summary\":\"detailed\"},\"safety_identifier\":null,\"service_tier\":\"default\",\"store\":false,\"temperature\":1,\"text\":{\"format\":{\"type\":\"text\"},\"verbosity\":\"medium\"},\"tool_choice\":\"auto\",\"tool_usage\":{\"image_gen\":{\"input_tokens\":0,\"input_tokens_details\":{\"image_tokens\":0,\"text_tokens\":0},\"output_tokens\":0,\"output_tokens_details\":{\"image_tokens\":0,\"text_tokens\":0},\"total_tokens\":0},\"web_search\":{\"num_requests\":0}},\"tools\":[{\"type\":\"function\",\"description\":\"Read a file or list a directory. For files, output is truncated to 2000 lines or 50KB — use offset/limit for large files. For directories, returns the list of entries.\",\"name\":\"read\",\"output_schema\":null,\"parameters\":{\"type\":\"object\",\"required\":[\"path\"],\"properties\":{\"path\":{\"type\":\"string\",\"description\":\"Path to the file to read\"},\"offset\":{\"type\":\"number\",\"description\":\"Line number to start from (1-indexed)\"},\"limit\":{\"type\":\"number\",\"description\":\"Maximum number of lines to read\"}}},\"strict\":false},{\"type\":\"function\",\"description\":\"Write content to a file. Creates the file and parent directories if they do not exist.\",\"name\":\"write\",\"output_schema\":null,\"parameters\":{\"type\":\"object\",\"required\":[\"path\",\"content\"],\"properties\":{\"path\":{\"type\":\"string\",\"description\":\"Path to the file to write\"},\"content\":{\"type\":\"string\",\"description\":\"Content to write to the file\"}}},\"strict\":false},{\"type\":\"function\",\"description\":\"Edit a file using exact text replacement. The oldText must match a unique region of the file. Use replaceAll to replace all occurrences.\",\"name\":\"edit\",\"output_schema\":null,\"parameters\":{\"type\":\"object\",\"required\":[\"path\",\"oldText\",\"newText\"],\"properties\":{\"path\":{\"type\":\"string\",\"description\":\"Path to the file to edit\"},\"oldText\":{\"type\":\"string\",\"description\":\"Exact text to find (must be unique)\"},\"newText\":{\"type\":\"string\",\"description\":\"Replacement text\"},\"replaceAll\":{\"type\":\"boolean\",\"description\":\"Replace all occurrences\"}}},\"strict\":false},{\"type\":\"function\",\"description\":\"Execute a bash command. Returns stdout and stderr. Output is truncated to the last 2000 lines or 50KB.\",\"name\":\"bash\",\"output_schema\":null,\"parameters\":{\"type\":\"object\",\"required\":[\"command\"],\"properties\":{\"command\":{\"type\":\"string\",\"description\":\"Bash command to execute\"},\"timeout\":{\"type\":\"number\",\"description\":\"Timeout in seconds\"}}},\"strict\":false},{\"type\":\"function\",\"description\":\"Search file contents for a regex pattern. Returns matching lines with file paths and line numbers.\",\"name\":\"grep\",\"output_schema\":null,\"parameters\":{\"type\":\"object\",\"required\":[\"pattern\"],\"properties\":{\"pattern\":{\"type\":\"string\",\"description\":\"Search pattern (regex)\"},\"path\":{\"type\":\"string\",\"description\":\"Directory or file to search (default: .)\"},\"include\":{\"type\":\"string\",\"description\":\"Glob filter, e.g. \\\"*.ts\\\"\"}}},\"strict\":false},{\"type\":\"function\",\"description\":\"Find files by filename pattern using shell find -name semantics. Returns matching file paths.\",\"name\":\"glob\",\"output_schema\":null,\"parameters\":{\"type\":\"object\",\"required\":[\"pattern\"],\"properties\":{\"pattern\":{\"type\":\"string\",\"description\":\"Filename pattern, e.g. \\\"*.ts\\\"\"},\"path\":{\"type\":\"string\",\"description\":\"Directory to search in (default: .)\"}}},\"strict\":false},{\"type\":\"function\",\"description\":\"Delegate a focused task to a detached child agent with its own context. Use this for independent research, file exploration, or parallel work. The task returns only its final answer to this conversation. No subagents are currently defined.\",\"name\":\"task\",\"output_schema\":null,\"parameters\":{\"type\":\"object\",\"required\":[\"prompt\"],\"properties\":{\"description\":{\"type\":\"string\",\"description\":\"Short human-readable label for the delegated work\"},\"prompt\":{\"type\":\"string\",\"description\":\"Focused instructions for the child agent\"},\"agent\":{\"type\":\"string\",\"description\":\"Declared subagent to use for the child agent\"},\"cwd\":{\"type\":\"string\",\"description\":\"Working directory for the child agent. AGENTS.md and skills are discovered from here.\"}}},\"strict\":false},{\"type\":\"function\",\"description\":\"Return a deterministic lookup result with an id needed by web_search.\",\"name\":\"lookup\",\"output_schema\":null,\"parameters\":{\"type\":\"object\",\"required\":[\"query\"],\"properties\":{\"query\":{\"type\":\"string\"}}},\"strict\":false},{\"type\":\"function\",\"description\":\"Search a deterministic local web index. Requires the id returned by lookup.\",\"name\":\"web_search\",\"output_schema\":null,\"parameters\":{\"type\":\"object\",\"required\":[\"lookupId\",\"query\"],\"properties\":{\"lookupId\":{\"type\":\"string\"},\"query\":{\"type\":\"string\"}}},\"strict\":false},{\"type\":\"function\",\"description\":\"Summarize the selected deterministic source after web_search returns a URL.\",\"name\":\"summarize_source\",\"output_schema\":null,\"parameters\":{\"type\":\"object\",\"required\":[\"url\"],\"properties\":{\"url\":{\"type\":\"string\"}}},\"strict\":false}],\"top_logprobs\":0,\"top_p\":0.98,\"truncation\":\"disabled\",\"usage\":{\"input_tokens\":1005,\"input_tokens_details\":{\"cache_write_tokens\":0,\"cached_tokens\":0},\"output_tokens\":19,\"output_tokens_details\":{\"reasoning_tokens\":0},\"total_tokens\":1024},\"user\":null,\"metadata\":{}},\"sequence_number\":12}" - ], - "kind": "sse" - }, - "headers": { - "access-control-expose-headers": "X-Request-ID, CF-Ray, CF-Ray", - "alt-svc": "h3=\":443\"; ma=86400", - "cf-cache-status": "DYNAMIC", - "cf-ray": "a25432b38d617bb6-IAD", - "connection": "keep-alive", - "content-type": "text/event-stream; charset=utf-8", - "date": "Mon, 03 Aug 2026 09:11:59 GMT", - "openai-organization": "braintrust-data", - "openai-processing-ms": "572", - "openai-project": "proj_vsCSXafhhByzWOThMrJcZiw9", - "openai-version": "2020-10-01", - "server": "cloudflare", - "set-cookie": "[REDACTED]", - "strict-transport-security": "max-age=31536000; includeSubDomains; preload", - "transfer-encoding": "chunked", - "x-content-type-options": "nosniff", - "x-ratelimit-limit-requests": "30000", - "x-ratelimit-limit-tokens": "180000000", - "x-ratelimit-remaining-requests": "29999", - "x-ratelimit-remaining-tokens": "179998539", - "x-ratelimit-reset-requests": "2ms", - "x-ratelimit-reset-tokens": "0s", - "x-request-id": "req_3ad47b9556ec41a8bf046bb73633b660" - }, - "status": 200, - "statusText": "OK" - } - }, - { - "callIndex": 1, - "id": "f12aff3913439248", - "matchKey": "POST api.openai.com/v1/responses", - "recordedAt": "2026-08-03T09:12:01.038Z", - "request": { - "body": { - "kind": "json", - "value": { - "include": ["reasoning.encrypted_content"], - "input": [ - { - "content": "You are running in headless mode with no human operator. Work autonomously — never ask questions, never wait for user input. Make your best judgment and proceed independently.\n\nYou are a deterministic Flue instrumentation test agent. Follow user instructions exactly. When asked for a marker, output only that marker and no extra text. When running a local skill file, read it yourself and do not delegate it to a task.\n\n## Available Skills\n\nThe following skills provide specialized instructions for specific tasks. When a task matches a skill description, activate that skill before proceeding so its full instructions are loaded. Skill instructions and supporting resources stay lazy until activation or explicit file reads.\n\n- **e2e-flue-skill** — Deterministic skill used by the Braintrust Flue instrumentation e2e test.\n\nDate: Mon, Aug 3, 2026\nWorking directory: .\n\nDirectory structure:\n.agents\n.flue\n.flue-build\n__cassettes__\n__snapshots__\nassertions.ts\ncassette-filter.mjs\nconstants.mjs\ndist\nnode_modules\npackage.json\npnpm-lock.yaml\nscenario.cli.mjs\nscenario.impl.mjs\nscenario.mjs\nscenario.test.ts\nscenario.ts\nscenario.v2.ts\nv2-assertions.ts\nversions", - "role": "developer" - }, - { - "content": [ - { - "text": "Complete this instrumented research flow. Call exactly one tool per turn and wait for each tool result before choosing the next tool. Step 1: call lookup with query \"flue instrumentation\". Step 2: use the lookup result id as lookupId and call web_search with query \"Braintrust Flue reasoning stream instrumentation\". Step 3: use the first web_search result url and call summarize_source. After summarize_source returns, reply with exactly PROMPT_DONE and no other text.", - "type": "input_text" - } - ], - "role": "user" - }, - { - "arguments": "{\"query\":\"flue instrumentation\"}", - "call_id": "call_rBgonKxWHwhN9E8aQ4VobIOe", - "id": "fc_045458052dc10379016a705b6008408191b27653755cf5bd03", - "name": "lookup", - "type": "function_call" - }, - { - "call_id": "call_rBgonKxWHwhN9E8aQ4VobIOe", - "output": "{\"id\":\"flue-session-2026\",\"query\":\"flue instrumentation\",\"topic\":\"session instrumentation\"}", - "type": "function_call_output" - } - ], - "model": "gpt-5.4-nano", - "prompt_cache_key": "[REDACTED]", - "reasoning": { - "effort": "low", - "summary": "auto" - }, - "store": false, - "stream": true, - "tools": [ - { - "description": "Read a file or list a directory. For files, output is truncated to 2000 lines or 50KB — use offset/limit for large files. For directories, returns the list of entries.", - "name": "read", - "parameters": { - "properties": { - "limit": { - "description": "Maximum number of lines to read", - "type": "number" - }, - "offset": { - "description": "Line number to start from (1-indexed)", - "type": "number" - }, - "path": { - "description": "Path to the file to read", - "type": "string" - } - }, - "required": ["path"], - "type": "object" - }, - "strict": false, - "type": "function" - }, - { - "description": "Write content to a file. Creates the file and parent directories if they do not exist.", - "name": "write", - "parameters": { - "properties": { - "content": { - "description": "Content to write to the file", - "type": "string" - }, - "path": { - "description": "Path to the file to write", - "type": "string" - } - }, - "required": ["path", "content"], - "type": "object" - }, - "strict": false, - "type": "function" - }, - { - "description": "Edit a file using exact text replacement. The oldText must match a unique region of the file. Use replaceAll to replace all occurrences.", - "name": "edit", - "parameters": { - "properties": { - "newText": { - "description": "Replacement text", - "type": "string" - }, - "oldText": { - "description": "Exact text to find (must be unique)", - "type": "string" - }, - "path": { - "description": "Path to the file to edit", - "type": "string" - }, - "replaceAll": { - "description": "Replace all occurrences", - "type": "boolean" - } - }, - "required": ["path", "oldText", "newText"], - "type": "object" - }, - "strict": false, - "type": "function" - }, - { - "description": "Execute a bash command. Returns stdout and stderr. Output is truncated to the last 2000 lines or 50KB.", - "name": "bash", - "parameters": { - "properties": { - "command": { - "description": "Bash command to execute", - "type": "string" - }, - "timeout": { - "description": "Timeout in seconds", - "type": "number" - } - }, - "required": ["command"], - "type": "object" - }, - "strict": false, - "type": "function" - }, - { - "description": "Search file contents for a regex pattern. Returns matching lines with file paths and line numbers.", - "name": "grep", - "parameters": { - "properties": { - "include": { - "description": "Glob filter, e.g. \"*.ts\"", - "type": "string" - }, - "path": { - "description": "Directory or file to search (default: .)", - "type": "string" - }, - "pattern": { - "description": "Search pattern (regex)", - "type": "string" - } - }, - "required": ["pattern"], - "type": "object" - }, - "strict": false, - "type": "function" - }, - { - "description": "Find files by filename pattern using shell find -name semantics. Returns matching file paths.", - "name": "glob", - "parameters": { - "properties": { - "path": { - "description": "Directory to search in (default: .)", - "type": "string" - }, - "pattern": { - "description": "Filename pattern, e.g. \"*.ts\"", - "type": "string" - } - }, - "required": ["pattern"], - "type": "object" - }, - "strict": false, - "type": "function" - }, - { - "description": "Delegate a focused task to a detached child agent with its own context. Use this for independent research, file exploration, or parallel work. The task returns only its final answer to this conversation. No subagents are currently defined.", - "name": "task", - "parameters": { - "properties": { - "agent": { - "description": "Declared subagent to use for the child agent", - "type": "string" - }, - "cwd": { - "description": "Working directory for the child agent. AGENTS.md and skills are discovered from here.", - "type": "string" - }, - "description": { - "description": "Short human-readable label for the delegated work", - "type": "string" - }, - "prompt": { - "description": "Focused instructions for the child agent", - "type": "string" - } - }, - "required": ["prompt"], - "type": "object" - }, - "strict": false, - "type": "function" - }, - { - "description": "Return a deterministic lookup result with an id needed by web_search.", - "name": "lookup", - "parameters": { - "properties": { - "query": { - "type": "string" - } - }, - "required": ["query"], - "type": "object" - }, - "strict": false, - "type": "function" - }, - { - "description": "Search a deterministic local web index. Requires the id returned by lookup.", - "name": "web_search", - "parameters": { - "properties": { - "lookupId": { - "type": "string" - }, - "query": { - "type": "string" - } - }, - "required": ["lookupId", "query"], - "type": "object" - }, - "strict": false, - "type": "function" - }, - { - "description": "Summarize the selected deterministic source after web_search returns a URL.", - "name": "summarize_source", - "parameters": { - "properties": { - "url": { - "type": "string" - } - }, - "required": ["url"], - "type": "object" - }, - "strict": false, - "type": "function" - } - ] - } - }, - "headers": {}, - "method": "POST", - "url": "https://api.openai.com/v1/responses" - }, - "response": { - "body": { - "chunks": [ - "event: response.created\ndata: {\"type\":\"response.created\",\"response\":{\"id\":\"resp_045458052dc10379016a705b6070c48191b73ad8a8f9b550f7\",\"object\":\"response\",\"created_at\":1785748320,\"status\":\"in_progress\",\"background\":false,\"completed_at\":null,\"error\":null,\"frequency_penalty\":0,\"incomplete_details\":null,\"instructions\":null,\"max_output_tokens\":null,\"max_tool_calls\":null,\"model\":\"gpt-5.4-nano-2026-03-17\",\"moderation\":null,\"output\":[],\"parallel_tool_calls\":true,\"presence_penalty\":0,\"previous_response_id\":null,\"prompt_cache_key\":\"workflow:instrumentation:01KZ3E9TWTAEK4P4P3F63HKAKC::default::ma\",\"prompt_cache_retention\":\"24h\",\"reasoning\":{\"context\":\"current_turn\",\"effort\":\"low\",\"mode\":\"standard\",\"summary\":\"detailed\"},\"safety_identifier\":null,\"service_tier\":\"auto\",\"store\":false,\"temperature\":1,\"text\":{\"format\":{\"type\":\"text\"},\"verbosity\":\"medium\"},\"tool_choice\":\"auto\",\"tool_usage\":{\"image_gen\":{\"input_tokens\":0,\"input_tokens_details\":{\"image_tokens\":0,\"text_tokens\":0},\"output_tokens\":0,\"output_tokens_details\":{\"image_tokens\":0,\"text_tokens\":0},\"total_tokens\":0},\"web_search\":{\"num_requests\":0}},\"tools\":[{\"type\":\"function\",\"description\":\"Read a file or list a directory. For files, output is truncated to 2000 lines or 50KB — use offset/limit for large files. For directories, returns the list of entries.\",\"name\":\"read\",\"output_schema\":null,\"parameters\":{\"type\":\"object\",\"required\":[\"path\"],\"properties\":{\"path\":{\"type\":\"string\",\"description\":\"Path to the file to read\"},\"offset\":{\"type\":\"number\",\"description\":\"Line number to start from (1-indexed)\"},\"limit\":{\"type\":\"number\",\"description\":\"Maximum number of lines to read\"}}},\"strict\":false},{\"type\":\"function\",\"description\":\"Write content to a file. Creates the file and parent directories if they do not exist.\",\"name\":\"write\",\"output_schema\":null,\"parameters\":{\"type\":\"object\",\"required\":[\"path\",\"content\"],\"properties\":{\"path\":{\"type\":\"string\",\"description\":\"Path to the file to write\"},\"content\":{\"type\":\"string\",\"description\":\"Content to write to the file\"}}},\"strict\":false},{\"type\":\"function\",\"description\":\"Edit a file using exact text replacement. The oldText must match a unique region of the file. Use replaceAll to replace all occurrences.\",\"name\":\"edit\",\"output_schema\":null,\"parameters\":{\"type\":\"object\",\"required\":[\"path\",\"oldText\",\"newText\"],\"properties\":{\"path\":{\"type\":\"string\",\"description\":\"Path to the file to edit\"},\"oldText\":{\"type\":\"string\",\"description\":\"Exact text to find (must be unique)\"},\"newText\":{\"type\":\"string\",\"description\":\"Replacement text\"},\"replaceAll\":{\"type\":\"boolean\",\"description\":\"Replace all occurrences\"}}},\"strict\":false},{\"type\":\"function\",\"description\":\"Execute a bash command. Returns stdout and stderr. Output is truncated to the last 2000 lines or 50KB.\",\"name\":\"bash\",\"output_schema\":null,\"parameters\":{\"type\":\"object\",\"required\":[\"command\"],\"properties\":{\"command\":{\"type\":\"string\",\"description\":\"Bash command to execute\"},\"timeout\":{\"type\":\"number\",\"description\":\"Timeout in seconds\"}}},\"strict\":false},{\"type\":\"function\",\"description\":\"Search file contents for a regex pattern. Returns matching lines with file paths and line numbers.\",\"name\":\"grep\",\"output_schema\":null,\"parameters\":{\"type\":\"object\",\"required\":[\"pattern\"],\"properties\":{\"pattern\":{\"type\":\"string\",\"description\":\"Search pattern (regex)\"},\"path\":{\"type\":\"string\",\"description\":\"Directory or file to search (default: .)\"},\"include\":{\"type\":\"string\",\"description\":\"Glob filter, e.g. \\\"*.ts\\\"\"}}},\"strict\":false},{\"type\":\"function\",\"description\":\"Find files by filename pattern using shell find -name semantics. Returns matching file paths.\",\"name\":\"glob\",\"output_schema\":null,\"parameters\":{\"type\":\"object\",\"required\":[\"pattern\"],\"properties\":{\"pattern\":{\"type\":\"string\",\"description\":\"Filename pattern, e.g. \\\"*.ts\\\"\"},\"path\":{\"type\":\"string\",\"description\":\"Directory to search in (default: .)\"}}},\"strict\":false},{\"type\":\"function\",\"description\":\"Delegate a focused task to a detached child agent with its own context. Use this for independent research, file exploration, or parallel work. The task returns only its final answer to this conversation. No subagents are currently defined.\",\"name\":\"task\",\"output_schema\":null,\"parameters\":{\"type\":\"object\",\"required\":[\"prompt\"],\"properties\":{\"description\":{\"type\":\"string\",\"description\":\"Short human-readable label for the delegated work\"},\"prompt\":{\"type\":\"string\",\"description\":\"Focused instructions for the child agent\"},\"agent\":{\"type\":\"string\",\"description\":\"Declared subagent to use for the child agent\"},\"cwd\":{\"type\":\"string\",\"description\":\"Working directory for the child agent. AGENTS.md and skills are discovered from here.\"}}},\"strict\":false},{\"type\":\"function\",\"description\":\"Return a deterministic lookup result with an id needed by web_search.\",\"name\":\"lookup\",\"output_schema\":null,\"parameters\":{\"type\":\"object\",\"required\":[\"query\"],\"properties\":{\"query\":{\"type\":\"string\"}}},\"strict\":false},{\"type\":\"function\",\"description\":\"Search a deterministic local web index. Requires the id returned by lookup.\",\"name\":\"web_search\",\"output_schema\":null,\"parameters\":{\"type\":\"object\",\"required\":[\"lookupId\",\"query\"],\"properties\":{\"lookupId\":{\"type\":\"string\"},\"query\":{\"type\":\"string\"}}},\"strict\":false},{\"type\":\"function\",\"description\":\"Summarize the selected deterministic source after web_search returns a URL.\",\"name\":\"summarize_source\",\"output_schema\":null,\"parameters\":{\"type\":\"object\",\"required\":[\"url\"],\"properties\":{\"url\":{\"type\":\"string\"}}},\"strict\":false}],\"top_logprobs\":0,\"top_p\":0.98,\"truncation\":\"disabled\",\"usage\":null,\"user\":null,\"metadata\":{}},\"sequence_number\":0}", - "event: response.in_progress\ndata: {\"type\":\"response.in_progress\",\"response\":{\"id\":\"resp_045458052dc10379016a705b6070c48191b73ad8a8f9b550f7\",\"object\":\"response\",\"created_at\":1785748320,\"status\":\"in_progress\",\"background\":false,\"completed_at\":null,\"error\":null,\"frequency_penalty\":0,\"incomplete_details\":null,\"instructions\":null,\"max_output_tokens\":null,\"max_tool_calls\":null,\"model\":\"gpt-5.4-nano-2026-03-17\",\"moderation\":null,\"output\":[],\"parallel_tool_calls\":true,\"presence_penalty\":0,\"previous_response_id\":null,\"prompt_cache_key\":\"workflow:instrumentation:01KZ3E9TWTAEK4P4P3F63HKAKC::default::ma\",\"prompt_cache_retention\":\"24h\",\"reasoning\":{\"context\":\"current_turn\",\"effort\":\"low\",\"mode\":\"standard\",\"summary\":\"detailed\"},\"safety_identifier\":null,\"service_tier\":\"auto\",\"store\":false,\"temperature\":1,\"text\":{\"format\":{\"type\":\"text\"},\"verbosity\":\"medium\"},\"tool_choice\":\"auto\",\"tool_usage\":{\"image_gen\":{\"input_tokens\":0,\"input_tokens_details\":{\"image_tokens\":0,\"text_tokens\":0},\"output_tokens\":0,\"output_tokens_details\":{\"image_tokens\":0,\"text_tokens\":0},\"total_tokens\":0},\"web_search\":{\"num_requests\":0}},\"tools\":[{\"type\":\"function\",\"description\":\"Read a file or list a directory. For files, output is truncated to 2000 lines or 50KB — use offset/limit for large files. For directories, returns the list of entries.\",\"name\":\"read\",\"output_schema\":null,\"parameters\":{\"type\":\"object\",\"required\":[\"path\"],\"properties\":{\"path\":{\"type\":\"string\",\"description\":\"Path to the file to read\"},\"offset\":{\"type\":\"number\",\"description\":\"Line number to start from (1-indexed)\"},\"limit\":{\"type\":\"number\",\"description\":\"Maximum number of lines to read\"}}},\"strict\":false},{\"type\":\"function\",\"description\":\"Write content to a file. Creates the file and parent directories if they do not exist.\",\"name\":\"write\",\"output_schema\":null,\"parameters\":{\"type\":\"object\",\"required\":[\"path\",\"content\"],\"properties\":{\"path\":{\"type\":\"string\",\"description\":\"Path to the file to write\"},\"content\":{\"type\":\"string\",\"description\":\"Content to write to the file\"}}},\"strict\":false},{\"type\":\"function\",\"description\":\"Edit a file using exact text replacement. The oldText must match a unique region of the file. Use replaceAll to replace all occurrences.\",\"name\":\"edit\",\"output_schema\":null,\"parameters\":{\"type\":\"object\",\"required\":[\"path\",\"oldText\",\"newText\"],\"properties\":{\"path\":{\"type\":\"string\",\"description\":\"Path to the file to edit\"},\"oldText\":{\"type\":\"string\",\"description\":\"Exact text to find (must be unique)\"},\"newText\":{\"type\":\"string\",\"description\":\"Replacement text\"},\"replaceAll\":{\"type\":\"boolean\",\"description\":\"Replace all occurrences\"}}},\"strict\":false},{\"type\":\"function\",\"description\":\"Execute a bash command. Returns stdout and stderr. Output is truncated to the last 2000 lines or 50KB.\",\"name\":\"bash\",\"output_schema\":null,\"parameters\":{\"type\":\"object\",\"required\":[\"command\"],\"properties\":{\"command\":{\"type\":\"string\",\"description\":\"Bash command to execute\"},\"timeout\":{\"type\":\"number\",\"description\":\"Timeout in seconds\"}}},\"strict\":false},{\"type\":\"function\",\"description\":\"Search file contents for a regex pattern. Returns matching lines with file paths and line numbers.\",\"name\":\"grep\",\"output_schema\":null,\"parameters\":{\"type\":\"object\",\"required\":[\"pattern\"],\"properties\":{\"pattern\":{\"type\":\"string\",\"description\":\"Search pattern (regex)\"},\"path\":{\"type\":\"string\",\"description\":\"Directory or file to search (default: .)\"},\"include\":{\"type\":\"string\",\"description\":\"Glob filter, e.g. \\\"*.ts\\\"\"}}},\"strict\":false},{\"type\":\"function\",\"description\":\"Find files by filename pattern using shell find -name semantics. Returns matching file paths.\",\"name\":\"glob\",\"output_schema\":null,\"parameters\":{\"type\":\"object\",\"required\":[\"pattern\"],\"properties\":{\"pattern\":{\"type\":\"string\",\"description\":\"Filename pattern, e.g. \\\"*.ts\\\"\"},\"path\":{\"type\":\"string\",\"description\":\"Directory to search in (default: .)\"}}},\"strict\":false},{\"type\":\"function\",\"description\":\"Delegate a focused task to a detached child agent with its own context. Use this for independent research, file exploration, or parallel work. The task returns only its final answer to this conversation. No subagents are currently defined.\",\"name\":\"task\",\"output_schema\":null,\"parameters\":{\"type\":\"object\",\"required\":[\"prompt\"],\"properties\":{\"description\":{\"type\":\"string\",\"description\":\"Short human-readable label for the delegated work\"},\"prompt\":{\"type\":\"string\",\"description\":\"Focused instructions for the child agent\"},\"agent\":{\"type\":\"string\",\"description\":\"Declared subagent to use for the child agent\"},\"cwd\":{\"type\":\"string\",\"description\":\"Working directory for the child agent. AGENTS.md and skills are discovered from here.\"}}},\"strict\":false},{\"type\":\"function\",\"description\":\"Return a deterministic lookup result with an id needed by web_search.\",\"name\":\"lookup\",\"output_schema\":null,\"parameters\":{\"type\":\"object\",\"required\":[\"query\"],\"properties\":{\"query\":{\"type\":\"string\"}}},\"strict\":false},{\"type\":\"function\",\"description\":\"Search a deterministic local web index. Requires the id returned by lookup.\",\"name\":\"web_search\",\"output_schema\":null,\"parameters\":{\"type\":\"object\",\"required\":[\"lookupId\",\"query\"],\"properties\":{\"lookupId\":{\"type\":\"string\"},\"query\":{\"type\":\"string\"}}},\"strict\":false},{\"type\":\"function\",\"description\":\"Summarize the selected deterministic source after web_search returns a URL.\",\"name\":\"summarize_source\",\"output_schema\":null,\"parameters\":{\"type\":\"object\",\"required\":[\"url\"],\"properties\":{\"url\":{\"type\":\"string\"}}},\"strict\":false}],\"top_logprobs\":0,\"top_p\":0.98,\"truncation\":\"disabled\",\"usage\":null,\"user\":null,\"metadata\":{}},\"sequence_number\":1}", - "event: response.output_item.added\ndata: {\"type\":\"response.output_item.added\",\"item\":{\"id\":\"fc_045458052dc10379016a705b60c92c81918da2fab9710ee199\",\"type\":\"function_call\",\"status\":\"in_progress\",\"arguments\":\"\",\"call_id\":\"call_fGesVCba2aABMoxSNfVnDVnR\",\"name\":\"web_search\"},\"output_index\":0,\"sequence_number\":2}", - "event: response.function_call_arguments.delta\ndata: {\"type\":\"response.function_call_arguments.delta\",\"delta\":\"{\\\"\",\"item_id\":\"fc_045458052dc10379016a705b60c92c81918da2fab9710ee199\",\"obfuscation\":\"CXnKLapUymev31\",\"output_index\":0,\"sequence_number\":3}", - "event: response.function_call_arguments.delta\ndata: {\"type\":\"response.function_call_arguments.delta\",\"delta\":\"lookup\",\"item_id\":\"fc_045458052dc10379016a705b60c92c81918da2fab9710ee199\",\"obfuscation\":\"hPsgf6WVxe\",\"output_index\":0,\"sequence_number\":4}", - "event: response.function_call_arguments.delta\ndata: {\"type\":\"response.function_call_arguments.delta\",\"delta\":\"Id\",\"item_id\":\"fc_045458052dc10379016a705b60c92c81918da2fab9710ee199\",\"obfuscation\":\"jPh8SwIrBbqksW\",\"output_index\":0,\"sequence_number\":5}", - "event: response.function_call_arguments.delta\ndata: {\"type\":\"response.function_call_arguments.delta\",\"delta\":\"\\\":\\\"\",\"item_id\":\"fc_045458052dc10379016a705b60c92c81918da2fab9710ee199\",\"obfuscation\":\"IlU35DSXGkesV\",\"output_index\":0,\"sequence_number\":6}", - "event: response.function_call_arguments.delta\ndata: {\"type\":\"response.function_call_arguments.delta\",\"delta\":\"fl\",\"item_id\":\"fc_045458052dc10379016a705b60c92c81918da2fab9710ee199\",\"obfuscation\":\"1XJ1BKxdLr8xZ1\",\"output_index\":0,\"sequence_number\":7}", - "event: response.function_call_arguments.delta\ndata: {\"type\":\"response.function_call_arguments.delta\",\"delta\":\"ue\",\"item_id\":\"fc_045458052dc10379016a705b60c92c81918da2fab9710ee199\",\"obfuscation\":\"1OTUMDyCdYkVv8\",\"output_index\":0,\"sequence_number\":8}", - "event: response.function_call_arguments.delta\ndata: {\"type\":\"response.function_call_arguments.delta\",\"delta\":\"-session\",\"item_id\":\"fc_045458052dc10379016a705b60c92c81918da2fab9710ee199\",\"obfuscation\":\"Nuz4FnUt\",\"output_index\":0,\"sequence_number\":9}", - "event: response.function_call_arguments.delta\ndata: {\"type\":\"response.function_call_arguments.delta\",\"delta\":\"-\",\"item_id\":\"fc_045458052dc10379016a705b60c92c81918da2fab9710ee199\",\"obfuscation\":\"HwzHAjBygsfiSUX\",\"output_index\":0,\"sequence_number\":10}", - "event: response.function_call_arguments.delta\ndata: {\"type\":\"response.function_call_arguments.delta\",\"delta\":\"202\",\"item_id\":\"fc_045458052dc10379016a705b60c92c81918da2fab9710ee199\",\"obfuscation\":\"Yp4H7ZJmIH7Dv\",\"output_index\":0,\"sequence_number\":11}", - "event: response.function_call_arguments.delta\ndata: {\"type\":\"response.function_call_arguments.delta\",\"delta\":\"6\",\"item_id\":\"fc_045458052dc10379016a705b60c92c81918da2fab9710ee199\",\"obfuscation\":\"EYhWYsmGpl4z0l3\",\"output_index\":0,\"sequence_number\":12}", - "event: response.function_call_arguments.delta\ndata: {\"type\":\"response.function_call_arguments.delta\",\"delta\":\"\\\",\\\"\",\"item_id\":\"fc_045458052dc10379016a705b60c92c81918da2fab9710ee199\",\"obfuscation\":\"8RFEMjEMC5stu\",\"output_index\":0,\"sequence_number\":13}", - "event: response.function_call_arguments.delta\ndata: {\"type\":\"response.function_call_arguments.delta\",\"delta\":\"query\",\"item_id\":\"fc_045458052dc10379016a705b60c92c81918da2fab9710ee199\",\"obfuscation\":\"1ZkQEmh5ANn\",\"output_index\":0,\"sequence_number\":14}", - "event: response.function_call_arguments.delta\ndata: {\"type\":\"response.function_call_arguments.delta\",\"delta\":\"\\\":\\\"\",\"item_id\":\"fc_045458052dc10379016a705b60c92c81918da2fab9710ee199\",\"obfuscation\":\"0KsiqU7uz6ecQ\",\"output_index\":0,\"sequence_number\":15}", - "event: response.function_call_arguments.delta\ndata: {\"type\":\"response.function_call_arguments.delta\",\"delta\":\"Brain\",\"item_id\":\"fc_045458052dc10379016a705b60c92c81918da2fab9710ee199\",\"obfuscation\":\"nrOMFbiD09E\",\"output_index\":0,\"sequence_number\":16}", - "event: response.function_call_arguments.delta\ndata: {\"type\":\"response.function_call_arguments.delta\",\"delta\":\"trust\",\"item_id\":\"fc_045458052dc10379016a705b60c92c81918da2fab9710ee199\",\"obfuscation\":\"QnKfskbAhz0\",\"output_index\":0,\"sequence_number\":17}", - "event: response.function_call_arguments.delta\ndata: {\"type\":\"response.function_call_arguments.delta\",\"delta\":\" Fl\",\"item_id\":\"fc_045458052dc10379016a705b60c92c81918da2fab9710ee199\",\"obfuscation\":\"q6Uz9SRPWbTCf\",\"output_index\":0,\"sequence_number\":18}", - "event: response.function_call_arguments.delta\ndata: {\"type\":\"response.function_call_arguments.delta\",\"delta\":\"ue\",\"item_id\":\"fc_045458052dc10379016a705b60c92c81918da2fab9710ee199\",\"obfuscation\":\"ZFMpp0BC39uzCN\",\"output_index\":0,\"sequence_number\":19}", - "event: response.function_call_arguments.delta\ndata: {\"type\":\"response.function_call_arguments.delta\",\"delta\":\" reasoning\",\"item_id\":\"fc_045458052dc10379016a705b60c92c81918da2fab9710ee199\",\"obfuscation\":\"2MwmX9\",\"output_index\":0,\"sequence_number\":20}", - "event: response.function_call_arguments.delta\ndata: {\"type\":\"response.function_call_arguments.delta\",\"delta\":\" stream\",\"item_id\":\"fc_045458052dc10379016a705b60c92c81918da2fab9710ee199\",\"obfuscation\":\"2aJP5sGuM\",\"output_index\":0,\"sequence_number\":21}", - "event: response.function_call_arguments.delta\ndata: {\"type\":\"response.function_call_arguments.delta\",\"delta\":\" instrumentation\",\"item_id\":\"fc_045458052dc10379016a705b60c92c81918da2fab9710ee199\",\"obfuscation\":\"\",\"output_index\":0,\"sequence_number\":22}", - "event: response.function_call_arguments.delta\ndata: {\"type\":\"response.function_call_arguments.delta\",\"delta\":\"\\\"}\",\"item_id\":\"fc_045458052dc10379016a705b60c92c81918da2fab9710ee199\",\"obfuscation\":\"UvySj7uLIjgfiB\",\"output_index\":0,\"sequence_number\":23}", - "event: response.function_call_arguments.done\ndata: {\"type\":\"response.function_call_arguments.done\",\"arguments\":\"{\\\"lookupId\\\":\\\"flue-session-2026\\\",\\\"query\\\":\\\"Braintrust Flue reasoning stream instrumentation\\\"}\",\"item_id\":\"fc_045458052dc10379016a705b60c92c81918da2fab9710ee199\",\"output_index\":0,\"sequence_number\":24}", - "event: response.output_item.done\ndata: {\"type\":\"response.output_item.done\",\"item\":{\"id\":\"fc_045458052dc10379016a705b60c92c81918da2fab9710ee199\",\"type\":\"function_call\",\"status\":\"completed\",\"arguments\":\"{\\\"lookupId\\\":\\\"flue-session-2026\\\",\\\"query\\\":\\\"Braintrust Flue reasoning stream instrumentation\\\"}\",\"call_id\":\"call_fGesVCba2aABMoxSNfVnDVnR\",\"name\":\"web_search\"},\"output_index\":0,\"sequence_number\":25}", - "event: response.completed\ndata: {\"type\":\"response.completed\",\"response\":{\"id\":\"resp_045458052dc10379016a705b6070c48191b73ad8a8f9b550f7\",\"object\":\"response\",\"created_at\":1785748320,\"status\":\"completed\",\"background\":false,\"completed_at\":1785748320,\"error\":null,\"frequency_penalty\":0,\"incomplete_details\":null,\"instructions\":null,\"max_output_tokens\":null,\"max_tool_calls\":null,\"model\":\"gpt-5.4-nano-2026-03-17\",\"moderation\":null,\"output\":[{\"id\":\"fc_045458052dc10379016a705b60c92c81918da2fab9710ee199\",\"type\":\"function_call\",\"status\":\"completed\",\"arguments\":\"{\\\"lookupId\\\":\\\"flue-session-2026\\\",\\\"query\\\":\\\"Braintrust Flue reasoning stream instrumentation\\\"}\",\"call_id\":\"call_fGesVCba2aABMoxSNfVnDVnR\",\"name\":\"web_search\"}],\"parallel_tool_calls\":true,\"presence_penalty\":0,\"previous_response_id\":null,\"prompt_cache_key\":\"workflow:instrumentation:01KZ3E9TWTAEK4P4P3F63HKAKC::default::ma\",\"prompt_cache_retention\":\"24h\",\"reasoning\":{\"context\":\"current_turn\",\"effort\":\"low\",\"mode\":\"standard\",\"summary\":\"detailed\"},\"safety_identifier\":null,\"service_tier\":\"default\",\"store\":false,\"temperature\":1,\"text\":{\"format\":{\"type\":\"text\"},\"verbosity\":\"medium\"},\"tool_choice\":\"auto\",\"tool_usage\":{\"image_gen\":{\"input_tokens\":0,\"input_tokens_details\":{\"image_tokens\":0,\"text_tokens\":0},\"output_tokens\":0,\"output_tokens_details\":{\"image_tokens\":0,\"text_tokens\":0},\"total_tokens\":0},\"web_search\":{\"num_requests\":0}},\"tools\":[{\"type\":\"function\",\"description\":\"Read a file or list a directory. For files, output is truncated to 2000 lines or 50KB — use offset/limit for large files. For directories, returns the list of entries.\",\"name\":\"read\",\"output_schema\":null,\"parameters\":{\"type\":\"object\",\"required\":[\"path\"],\"properties\":{\"path\":{\"type\":\"string\",\"description\":\"Path to the file to read\"},\"offset\":{\"type\":\"number\",\"description\":\"Line number to start from (1-indexed)\"},\"limit\":{\"type\":\"number\",\"description\":\"Maximum number of lines to read\"}}},\"strict\":false},{\"type\":\"function\",\"description\":\"Write content to a file. Creates the file and parent directories if they do not exist.\",\"name\":\"write\",\"output_schema\":null,\"parameters\":{\"type\":\"object\",\"required\":[\"path\",\"content\"],\"properties\":{\"path\":{\"type\":\"string\",\"description\":\"Path to the file to write\"},\"content\":{\"type\":\"string\",\"description\":\"Content to write to the file\"}}},\"strict\":false},{\"type\":\"function\",\"description\":\"Edit a file using exact text replacement. The oldText must match a unique region of the file. Use replaceAll to replace all occurrences.\",\"name\":\"edit\",\"output_schema\":null,\"parameters\":{\"type\":\"object\",\"required\":[\"path\",\"oldText\",\"newText\"],\"properties\":{\"path\":{\"type\":\"string\",\"description\":\"Path to the file to edit\"},\"oldText\":{\"type\":\"string\",\"description\":\"Exact text to find (must be unique)\"},\"newText\":{\"type\":\"string\",\"description\":\"Replacement text\"},\"replaceAll\":{\"type\":\"boolean\",\"description\":\"Replace all occurrences\"}}},\"strict\":false},{\"type\":\"function\",\"description\":\"Execute a bash command. Returns stdout and stderr. Output is truncated to the last 2000 lines or 50KB.\",\"name\":\"bash\",\"output_schema\":null,\"parameters\":{\"type\":\"object\",\"required\":[\"command\"],\"properties\":{\"command\":{\"type\":\"string\",\"description\":\"Bash command to execute\"},\"timeout\":{\"type\":\"number\",\"description\":\"Timeout in seconds\"}}},\"strict\":false},{\"type\":\"function\",\"description\":\"Search file contents for a regex pattern. Returns matching lines with file paths and line numbers.\",\"name\":\"grep\",\"output_schema\":null,\"parameters\":{\"type\":\"object\",\"required\":[\"pattern\"],\"properties\":{\"pattern\":{\"type\":\"string\",\"description\":\"Search pattern (regex)\"},\"path\":{\"type\":\"string\",\"description\":\"Directory or file to search (default: .)\"},\"include\":{\"type\":\"string\",\"description\":\"Glob filter, e.g. \\\"*.ts\\\"\"}}},\"strict\":false},{\"type\":\"function\",\"description\":\"Find files by filename pattern using shell find -name semantics. Returns matching file paths.\",\"name\":\"glob\",\"output_schema\":null,\"parameters\":{\"type\":\"object\",\"required\":[\"pattern\"],\"properties\":{\"pattern\":{\"type\":\"string\",\"description\":\"Filename pattern, e.g. \\\"*.ts\\\"\"},\"path\":{\"type\":\"string\",\"description\":\"Directory to search in (default: .)\"}}},\"strict\":false},{\"type\":\"function\",\"description\":\"Delegate a focused task to a detached child agent with its own context. Use this for independent research, file exploration, or parallel work. The task returns only its final answer to this conversation. No subagents are currently defined.\",\"name\":\"task\",\"output_schema\":null,\"parameters\":{\"type\":\"object\",\"required\":[\"prompt\"],\"properties\":{\"description\":{\"type\":\"string\",\"description\":\"Short human-readable label for the delegated work\"},\"prompt\":{\"type\":\"string\",\"description\":\"Focused instructions for the child agent\"},\"agent\":{\"type\":\"string\",\"description\":\"Declared subagent to use for the child agent\"},\"cwd\":{\"type\":\"string\",\"description\":\"Working directory for the child agent. AGENTS.md and skills are discovered from here.\"}}},\"strict\":false},{\"type\":\"function\",\"description\":\"Return a deterministic lookup result with an id needed by web_search.\",\"name\":\"lookup\",\"output_schema\":null,\"parameters\":{\"type\":\"object\",\"required\":[\"query\"],\"properties\":{\"query\":{\"type\":\"string\"}}},\"strict\":false},{\"type\":\"function\",\"description\":\"Search a deterministic local web index. Requires the id returned by lookup.\",\"name\":\"web_search\",\"output_schema\":null,\"parameters\":{\"type\":\"object\",\"required\":[\"lookupId\",\"query\"],\"properties\":{\"lookupId\":{\"type\":\"string\"},\"query\":{\"type\":\"string\"}}},\"strict\":false},{\"type\":\"function\",\"description\":\"Summarize the selected deterministic source after web_search returns a URL.\",\"name\":\"summarize_source\",\"output_schema\":null,\"parameters\":{\"type\":\"object\",\"required\":[\"url\"],\"properties\":{\"url\":{\"type\":\"string\"}}},\"strict\":false}],\"top_logprobs\":0,\"top_p\":0.98,\"truncation\":\"disabled\",\"usage\":{\"input_tokens\":1055,\"input_tokens_details\":{\"cache_write_tokens\":0,\"cached_tokens\":0},\"output_tokens\":34,\"output_tokens_details\":{\"reasoning_tokens\":0},\"total_tokens\":1089},\"user\":null,\"metadata\":{}},\"sequence_number\":26}" - ], - "kind": "sse" - }, - "headers": { - "access-control-expose-headers": "X-Request-ID, CF-Ray, CF-Ray", - "alt-svc": "h3=\":443\"; ma=86400", - "cf-cache-status": "DYNAMIC", - "cf-ray": "a25432ba7c1a7bb6-IAD", - "connection": "keep-alive", - "content-type": "text/event-stream; charset=utf-8", - "date": "Mon, 03 Aug 2026 09:12:00 GMT", - "openai-organization": "braintrust-data", - "openai-processing-ms": "236", - "openai-project": "proj_vsCSXafhhByzWOThMrJcZiw9", - "openai-version": "2020-10-01", - "server": "cloudflare", - "set-cookie": "[REDACTED]", - "strict-transport-security": "max-age=31536000; includeSubDomains; preload", - "transfer-encoding": "chunked", - "x-content-type-options": "nosniff", - "x-ratelimit-limit-requests": "30000", - "x-ratelimit-limit-tokens": "180000000", - "x-ratelimit-remaining-requests": "29999", - "x-ratelimit-remaining-tokens": "179998488", - "x-ratelimit-reset-requests": "2ms", - "x-ratelimit-reset-tokens": "0s", - "x-request-id": "req_b7babd52eb11438ca65da8e3e06cd733" - }, - "status": 200, - "statusText": "OK" - } - }, - { - "callIndex": 2, - "id": "5d2312f425df9f2f", - "matchKey": "POST api.openai.com/v1/responses", - "recordedAt": "2026-08-03T09:12:02.466Z", - "request": { - "body": { - "kind": "json", - "value": { - "include": ["reasoning.encrypted_content"], - "input": [ - { - "content": "You are running in headless mode with no human operator. Work autonomously — never ask questions, never wait for user input. Make your best judgment and proceed independently.\n\nYou are a deterministic Flue instrumentation test agent. Follow user instructions exactly. When asked for a marker, output only that marker and no extra text. When running a local skill file, read it yourself and do not delegate it to a task.\n\n## Available Skills\n\nThe following skills provide specialized instructions for specific tasks. When a task matches a skill description, activate that skill before proceeding so its full instructions are loaded. Skill instructions and supporting resources stay lazy until activation or explicit file reads.\n\n- **e2e-flue-skill** — Deterministic skill used by the Braintrust Flue instrumentation e2e test.\n\nDate: Mon, Aug 3, 2026\nWorking directory: .\n\nDirectory structure:\n.agents\n.flue\n.flue-build\n__cassettes__\n__snapshots__\nassertions.ts\ncassette-filter.mjs\nconstants.mjs\ndist\nnode_modules\npackage.json\npnpm-lock.yaml\nscenario.cli.mjs\nscenario.impl.mjs\nscenario.mjs\nscenario.test.ts\nscenario.ts\nscenario.v2.ts\nv2-assertions.ts\nversions", - "role": "developer" - }, - { - "content": [ - { - "text": "Complete this instrumented research flow. Call exactly one tool per turn and wait for each tool result before choosing the next tool. Step 1: call lookup with query \"flue instrumentation\". Step 2: use the lookup result id as lookupId and call web_search with query \"Braintrust Flue reasoning stream instrumentation\". Step 3: use the first web_search result url and call summarize_source. After summarize_source returns, reply with exactly PROMPT_DONE and no other text.", - "type": "input_text" - } - ], - "role": "user" - }, - { - "arguments": "{\"query\":\"flue instrumentation\"}", - "call_id": "call_rBgonKxWHwhN9E8aQ4VobIOe", - "id": "fc_045458052dc10379016a705b6008408191b27653755cf5bd03", - "name": "lookup", - "type": "function_call" - }, - { - "call_id": "call_rBgonKxWHwhN9E8aQ4VobIOe", - "output": "{\"id\":\"flue-session-2026\",\"query\":\"flue instrumentation\",\"topic\":\"session instrumentation\"}", - "type": "function_call_output" - }, - { - "arguments": "{\"lookupId\":\"flue-session-2026\",\"query\":\"Braintrust Flue reasoning stream instrumentation\"}", - "call_id": "call_fGesVCba2aABMoxSNfVnDVnR", - "id": "fc_045458052dc10379016a705b60c92c81918da2fab9710ee199", - "name": "web_search", - "type": "function_call" - }, - { - "call_id": "call_fGesVCba2aABMoxSNfVnDVnR", - "output": "{\"lookupId\":\"flue-session-2026\",\"query\":\"Braintrust Flue reasoning stream instrumentation\",\"results\":[{\"title\":\"Flue reasoning stream instrumentation\",\"url\":\"https://example.test/flue/reasoning-streams\"}]}", - "type": "function_call_output" - } - ], - "model": "gpt-5.4-nano", - "prompt_cache_key": "[REDACTED]", - "reasoning": { - "effort": "low", - "summary": "auto" - }, - "store": false, - "stream": true, - "tools": [ - { - "description": "Read a file or list a directory. For files, output is truncated to 2000 lines or 50KB — use offset/limit for large files. For directories, returns the list of entries.", - "name": "read", - "parameters": { - "properties": { - "limit": { - "description": "Maximum number of lines to read", - "type": "number" - }, - "offset": { - "description": "Line number to start from (1-indexed)", - "type": "number" - }, - "path": { - "description": "Path to the file to read", - "type": "string" - } - }, - "required": ["path"], - "type": "object" - }, - "strict": false, - "type": "function" - }, - { - "description": "Write content to a file. Creates the file and parent directories if they do not exist.", - "name": "write", - "parameters": { - "properties": { - "content": { - "description": "Content to write to the file", - "type": "string" - }, - "path": { - "description": "Path to the file to write", - "type": "string" - } - }, - "required": ["path", "content"], - "type": "object" - }, - "strict": false, - "type": "function" - }, - { - "description": "Edit a file using exact text replacement. The oldText must match a unique region of the file. Use replaceAll to replace all occurrences.", - "name": "edit", - "parameters": { - "properties": { - "newText": { - "description": "Replacement text", - "type": "string" - }, - "oldText": { - "description": "Exact text to find (must be unique)", - "type": "string" - }, - "path": { - "description": "Path to the file to edit", - "type": "string" - }, - "replaceAll": { - "description": "Replace all occurrences", - "type": "boolean" - } - }, - "required": ["path", "oldText", "newText"], - "type": "object" - }, - "strict": false, - "type": "function" - }, - { - "description": "Execute a bash command. Returns stdout and stderr. Output is truncated to the last 2000 lines or 50KB.", - "name": "bash", - "parameters": { - "properties": { - "command": { - "description": "Bash command to execute", - "type": "string" - }, - "timeout": { - "description": "Timeout in seconds", - "type": "number" - } - }, - "required": ["command"], - "type": "object" - }, - "strict": false, - "type": "function" - }, - { - "description": "Search file contents for a regex pattern. Returns matching lines with file paths and line numbers.", - "name": "grep", - "parameters": { - "properties": { - "include": { - "description": "Glob filter, e.g. \"*.ts\"", - "type": "string" - }, - "path": { - "description": "Directory or file to search (default: .)", - "type": "string" - }, - "pattern": { - "description": "Search pattern (regex)", - "type": "string" - } - }, - "required": ["pattern"], - "type": "object" - }, - "strict": false, - "type": "function" - }, - { - "description": "Find files by filename pattern using shell find -name semantics. Returns matching file paths.", - "name": "glob", - "parameters": { - "properties": { - "path": { - "description": "Directory to search in (default: .)", - "type": "string" - }, - "pattern": { - "description": "Filename pattern, e.g. \"*.ts\"", - "type": "string" - } - }, - "required": ["pattern"], - "type": "object" - }, - "strict": false, - "type": "function" - }, - { - "description": "Delegate a focused task to a detached child agent with its own context. Use this for independent research, file exploration, or parallel work. The task returns only its final answer to this conversation. No subagents are currently defined.", - "name": "task", - "parameters": { - "properties": { - "agent": { - "description": "Declared subagent to use for the child agent", - "type": "string" - }, - "cwd": { - "description": "Working directory for the child agent. AGENTS.md and skills are discovered from here.", - "type": "string" - }, - "description": { - "description": "Short human-readable label for the delegated work", - "type": "string" - }, - "prompt": { - "description": "Focused instructions for the child agent", - "type": "string" - } - }, - "required": ["prompt"], - "type": "object" - }, - "strict": false, - "type": "function" - }, - { - "description": "Return a deterministic lookup result with an id needed by web_search.", - "name": "lookup", - "parameters": { - "properties": { - "query": { - "type": "string" - } - }, - "required": ["query"], - "type": "object" - }, - "strict": false, - "type": "function" - }, - { - "description": "Search a deterministic local web index. Requires the id returned by lookup.", - "name": "web_search", - "parameters": { - "properties": { - "lookupId": { - "type": "string" - }, - "query": { - "type": "string" - } - }, - "required": ["lookupId", "query"], - "type": "object" - }, - "strict": false, - "type": "function" - }, - { - "description": "Summarize the selected deterministic source after web_search returns a URL.", - "name": "summarize_source", - "parameters": { - "properties": { - "url": { - "type": "string" - } - }, - "required": ["url"], - "type": "object" - }, - "strict": false, - "type": "function" - } - ] - } - }, - "headers": {}, - "method": "POST", - "url": "https://api.openai.com/v1/responses" - }, - "response": { - "body": { - "chunks": [ - "event: response.created\ndata: {\"type\":\"response.created\",\"response\":{\"id\":\"resp_045458052dc10379016a705b611e8881919ad2adde8d6874b9\",\"object\":\"response\",\"created_at\":1785748321,\"status\":\"in_progress\",\"background\":false,\"completed_at\":null,\"error\":null,\"frequency_penalty\":0,\"incomplete_details\":null,\"instructions\":null,\"max_output_tokens\":null,\"max_tool_calls\":null,\"model\":\"gpt-5.4-nano-2026-03-17\",\"moderation\":null,\"output\":[],\"parallel_tool_calls\":true,\"presence_penalty\":0,\"previous_response_id\":null,\"prompt_cache_key\":\"workflow:instrumentation:01KZ3E9TWTAEK4P4P3F63HKAKC::default::ma\",\"prompt_cache_retention\":\"24h\",\"reasoning\":{\"context\":\"current_turn\",\"effort\":\"low\",\"mode\":\"standard\",\"summary\":\"detailed\"},\"safety_identifier\":null,\"service_tier\":\"auto\",\"store\":false,\"temperature\":1,\"text\":{\"format\":{\"type\":\"text\"},\"verbosity\":\"medium\"},\"tool_choice\":\"auto\",\"tool_usage\":{\"image_gen\":{\"input_tokens\":0,\"input_tokens_details\":{\"image_tokens\":0,\"text_tokens\":0},\"output_tokens\":0,\"output_tokens_details\":{\"image_tokens\":0,\"text_tokens\":0},\"total_tokens\":0},\"web_search\":{\"num_requests\":0}},\"tools\":[{\"type\":\"function\",\"description\":\"Read a file or list a directory. For files, output is truncated to 2000 lines or 50KB — use offset/limit for large files. For directories, returns the list of entries.\",\"name\":\"read\",\"output_schema\":null,\"parameters\":{\"type\":\"object\",\"required\":[\"path\"],\"properties\":{\"path\":{\"type\":\"string\",\"description\":\"Path to the file to read\"},\"offset\":{\"type\":\"number\",\"description\":\"Line number to start from (1-indexed)\"},\"limit\":{\"type\":\"number\",\"description\":\"Maximum number of lines to read\"}}},\"strict\":false},{\"type\":\"function\",\"description\":\"Write content to a file. Creates the file and parent directories if they do not exist.\",\"name\":\"write\",\"output_schema\":null,\"parameters\":{\"type\":\"object\",\"required\":[\"path\",\"content\"],\"properties\":{\"path\":{\"type\":\"string\",\"description\":\"Path to the file to write\"},\"content\":{\"type\":\"string\",\"description\":\"Content to write to the file\"}}},\"strict\":false},{\"type\":\"function\",\"description\":\"Edit a file using exact text replacement. The oldText must match a unique region of the file. Use replaceAll to replace all occurrences.\",\"name\":\"edit\",\"output_schema\":null,\"parameters\":{\"type\":\"object\",\"required\":[\"path\",\"oldText\",\"newText\"],\"properties\":{\"path\":{\"type\":\"string\",\"description\":\"Path to the file to edit\"},\"oldText\":{\"type\":\"string\",\"description\":\"Exact text to find (must be unique)\"},\"newText\":{\"type\":\"string\",\"description\":\"Replacement text\"},\"replaceAll\":{\"type\":\"boolean\",\"description\":\"Replace all occurrences\"}}},\"strict\":false},{\"type\":\"function\",\"description\":\"Execute a bash command. Returns stdout and stderr. Output is truncated to the last 2000 lines or 50KB.\",\"name\":\"bash\",\"output_schema\":null,\"parameters\":{\"type\":\"object\",\"required\":[\"command\"],\"properties\":{\"command\":{\"type\":\"string\",\"description\":\"Bash command to execute\"},\"timeout\":{\"type\":\"number\",\"description\":\"Timeout in seconds\"}}},\"strict\":false},{\"type\":\"function\",\"description\":\"Search file contents for a regex pattern. Returns matching lines with file paths and line numbers.\",\"name\":\"grep\",\"output_schema\":null,\"parameters\":{\"type\":\"object\",\"required\":[\"pattern\"],\"properties\":{\"pattern\":{\"type\":\"string\",\"description\":\"Search pattern (regex)\"},\"path\":{\"type\":\"string\",\"description\":\"Directory or file to search (default: .)\"},\"include\":{\"type\":\"string\",\"description\":\"Glob filter, e.g. \\\"*.ts\\\"\"}}},\"strict\":false},{\"type\":\"function\",\"description\":\"Find files by filename pattern using shell find -name semantics. Returns matching file paths.\",\"name\":\"glob\",\"output_schema\":null,\"parameters\":{\"type\":\"object\",\"required\":[\"pattern\"],\"properties\":{\"pattern\":{\"type\":\"string\",\"description\":\"Filename pattern, e.g. \\\"*.ts\\\"\"},\"path\":{\"type\":\"string\",\"description\":\"Directory to search in (default: .)\"}}},\"strict\":false},{\"type\":\"function\",\"description\":\"Delegate a focused task to a detached child agent with its own context. Use this for independent research, file exploration, or parallel work. The task returns only its final answer to this conversation. No subagents are currently defined.\",\"name\":\"task\",\"output_schema\":null,\"parameters\":{\"type\":\"object\",\"required\":[\"prompt\"],\"properties\":{\"description\":{\"type\":\"string\",\"description\":\"Short human-readable label for the delegated work\"},\"prompt\":{\"type\":\"string\",\"description\":\"Focused instructions for the child agent\"},\"agent\":{\"type\":\"string\",\"description\":\"Declared subagent to use for the child agent\"},\"cwd\":{\"type\":\"string\",\"description\":\"Working directory for the child agent. AGENTS.md and skills are discovered from here.\"}}},\"strict\":false},{\"type\":\"function\",\"description\":\"Return a deterministic lookup result with an id needed by web_search.\",\"name\":\"lookup\",\"output_schema\":null,\"parameters\":{\"type\":\"object\",\"required\":[\"query\"],\"properties\":{\"query\":{\"type\":\"string\"}}},\"strict\":false},{\"type\":\"function\",\"description\":\"Search a deterministic local web index. Requires the id returned by lookup.\",\"name\":\"web_search\",\"output_schema\":null,\"parameters\":{\"type\":\"object\",\"required\":[\"lookupId\",\"query\"],\"properties\":{\"lookupId\":{\"type\":\"string\"},\"query\":{\"type\":\"string\"}}},\"strict\":false},{\"type\":\"function\",\"description\":\"Summarize the selected deterministic source after web_search returns a URL.\",\"name\":\"summarize_source\",\"output_schema\":null,\"parameters\":{\"type\":\"object\",\"required\":[\"url\"],\"properties\":{\"url\":{\"type\":\"string\"}}},\"strict\":false}],\"top_logprobs\":0,\"top_p\":0.98,\"truncation\":\"disabled\",\"usage\":null,\"user\":null,\"metadata\":{}},\"sequence_number\":0}", - "event: response.in_progress\ndata: {\"type\":\"response.in_progress\",\"response\":{\"id\":\"resp_045458052dc10379016a705b611e8881919ad2adde8d6874b9\",\"object\":\"response\",\"created_at\":1785748321,\"status\":\"in_progress\",\"background\":false,\"completed_at\":null,\"error\":null,\"frequency_penalty\":0,\"incomplete_details\":null,\"instructions\":null,\"max_output_tokens\":null,\"max_tool_calls\":null,\"model\":\"gpt-5.4-nano-2026-03-17\",\"moderation\":null,\"output\":[],\"parallel_tool_calls\":true,\"presence_penalty\":0,\"previous_response_id\":null,\"prompt_cache_key\":\"workflow:instrumentation:01KZ3E9TWTAEK4P4P3F63HKAKC::default::ma\",\"prompt_cache_retention\":\"24h\",\"reasoning\":{\"context\":\"current_turn\",\"effort\":\"low\",\"mode\":\"standard\",\"summary\":\"detailed\"},\"safety_identifier\":null,\"service_tier\":\"auto\",\"store\":false,\"temperature\":1,\"text\":{\"format\":{\"type\":\"text\"},\"verbosity\":\"medium\"},\"tool_choice\":\"auto\",\"tool_usage\":{\"image_gen\":{\"input_tokens\":0,\"input_tokens_details\":{\"image_tokens\":0,\"text_tokens\":0},\"output_tokens\":0,\"output_tokens_details\":{\"image_tokens\":0,\"text_tokens\":0},\"total_tokens\":0},\"web_search\":{\"num_requests\":0}},\"tools\":[{\"type\":\"function\",\"description\":\"Read a file or list a directory. For files, output is truncated to 2000 lines or 50KB — use offset/limit for large files. For directories, returns the list of entries.\",\"name\":\"read\",\"output_schema\":null,\"parameters\":{\"type\":\"object\",\"required\":[\"path\"],\"properties\":{\"path\":{\"type\":\"string\",\"description\":\"Path to the file to read\"},\"offset\":{\"type\":\"number\",\"description\":\"Line number to start from (1-indexed)\"},\"limit\":{\"type\":\"number\",\"description\":\"Maximum number of lines to read\"}}},\"strict\":false},{\"type\":\"function\",\"description\":\"Write content to a file. Creates the file and parent directories if they do not exist.\",\"name\":\"write\",\"output_schema\":null,\"parameters\":{\"type\":\"object\",\"required\":[\"path\",\"content\"],\"properties\":{\"path\":{\"type\":\"string\",\"description\":\"Path to the file to write\"},\"content\":{\"type\":\"string\",\"description\":\"Content to write to the file\"}}},\"strict\":false},{\"type\":\"function\",\"description\":\"Edit a file using exact text replacement. The oldText must match a unique region of the file. Use replaceAll to replace all occurrences.\",\"name\":\"edit\",\"output_schema\":null,\"parameters\":{\"type\":\"object\",\"required\":[\"path\",\"oldText\",\"newText\"],\"properties\":{\"path\":{\"type\":\"string\",\"description\":\"Path to the file to edit\"},\"oldText\":{\"type\":\"string\",\"description\":\"Exact text to find (must be unique)\"},\"newText\":{\"type\":\"string\",\"description\":\"Replacement text\"},\"replaceAll\":{\"type\":\"boolean\",\"description\":\"Replace all occurrences\"}}},\"strict\":false},{\"type\":\"function\",\"description\":\"Execute a bash command. Returns stdout and stderr. Output is truncated to the last 2000 lines or 50KB.\",\"name\":\"bash\",\"output_schema\":null,\"parameters\":{\"type\":\"object\",\"required\":[\"command\"],\"properties\":{\"command\":{\"type\":\"string\",\"description\":\"Bash command to execute\"},\"timeout\":{\"type\":\"number\",\"description\":\"Timeout in seconds\"}}},\"strict\":false},{\"type\":\"function\",\"description\":\"Search file contents for a regex pattern. Returns matching lines with file paths and line numbers.\",\"name\":\"grep\",\"output_schema\":null,\"parameters\":{\"type\":\"object\",\"required\":[\"pattern\"],\"properties\":{\"pattern\":{\"type\":\"string\",\"description\":\"Search pattern (regex)\"},\"path\":{\"type\":\"string\",\"description\":\"Directory or file to search (default: .)\"},\"include\":{\"type\":\"string\",\"description\":\"Glob filter, e.g. \\\"*.ts\\\"\"}}},\"strict\":false},{\"type\":\"function\",\"description\":\"Find files by filename pattern using shell find -name semantics. Returns matching file paths.\",\"name\":\"glob\",\"output_schema\":null,\"parameters\":{\"type\":\"object\",\"required\":[\"pattern\"],\"properties\":{\"pattern\":{\"type\":\"string\",\"description\":\"Filename pattern, e.g. \\\"*.ts\\\"\"},\"path\":{\"type\":\"string\",\"description\":\"Directory to search in (default: .)\"}}},\"strict\":false},{\"type\":\"function\",\"description\":\"Delegate a focused task to a detached child agent with its own context. Use this for independent research, file exploration, or parallel work. The task returns only its final answer to this conversation. No subagents are currently defined.\",\"name\":\"task\",\"output_schema\":null,\"parameters\":{\"type\":\"object\",\"required\":[\"prompt\"],\"properties\":{\"description\":{\"type\":\"string\",\"description\":\"Short human-readable label for the delegated work\"},\"prompt\":{\"type\":\"string\",\"description\":\"Focused instructions for the child agent\"},\"agent\":{\"type\":\"string\",\"description\":\"Declared subagent to use for the child agent\"},\"cwd\":{\"type\":\"string\",\"description\":\"Working directory for the child agent. AGENTS.md and skills are discovered from here.\"}}},\"strict\":false},{\"type\":\"function\",\"description\":\"Return a deterministic lookup result with an id needed by web_search.\",\"name\":\"lookup\",\"output_schema\":null,\"parameters\":{\"type\":\"object\",\"required\":[\"query\"],\"properties\":{\"query\":{\"type\":\"string\"}}},\"strict\":false},{\"type\":\"function\",\"description\":\"Search a deterministic local web index. Requires the id returned by lookup.\",\"name\":\"web_search\",\"output_schema\":null,\"parameters\":{\"type\":\"object\",\"required\":[\"lookupId\",\"query\"],\"properties\":{\"lookupId\":{\"type\":\"string\"},\"query\":{\"type\":\"string\"}}},\"strict\":false},{\"type\":\"function\",\"description\":\"Summarize the selected deterministic source after web_search returns a URL.\",\"name\":\"summarize_source\",\"output_schema\":null,\"parameters\":{\"type\":\"object\",\"required\":[\"url\"],\"properties\":{\"url\":{\"type\":\"string\"}}},\"strict\":false}],\"top_logprobs\":0,\"top_p\":0.98,\"truncation\":\"disabled\",\"usage\":null,\"user\":null,\"metadata\":{}},\"sequence_number\":1}", - "event: response.output_item.added\ndata: {\"type\":\"response.output_item.added\",\"item\":{\"id\":\"fc_045458052dc10379016a705b6194148191a2820dc720d327c5\",\"type\":\"function_call\",\"status\":\"in_progress\",\"arguments\":\"\",\"call_id\":\"call_4oiajYhDnktEd6wZwQjSMwpb\",\"name\":\"summarize_source\"},\"output_index\":0,\"sequence_number\":2}", - "event: response.function_call_arguments.delta\ndata: {\"type\":\"response.function_call_arguments.delta\",\"delta\":\"{\\\"\",\"item_id\":\"fc_045458052dc10379016a705b6194148191a2820dc720d327c5\",\"obfuscation\":\"hK03W2coG6GFdN\",\"output_index\":0,\"sequence_number\":3}", - "event: response.function_call_arguments.delta\ndata: {\"type\":\"response.function_call_arguments.delta\",\"delta\":\"url\",\"item_id\":\"fc_045458052dc10379016a705b6194148191a2820dc720d327c5\",\"obfuscation\":\"Uu8U6Kh8Nj5Gs\",\"output_index\":0,\"sequence_number\":4}", - "event: response.function_call_arguments.delta\ndata: {\"type\":\"response.function_call_arguments.delta\",\"delta\":\"\\\":\\\"\",\"item_id\":\"fc_045458052dc10379016a705b6194148191a2820dc720d327c5\",\"obfuscation\":\"sI2ly4ADW4764\",\"output_index\":0,\"sequence_number\":5}", - "event: response.function_call_arguments.delta\ndata: {\"type\":\"response.function_call_arguments.delta\",\"delta\":\"https\",\"item_id\":\"fc_045458052dc10379016a705b6194148191a2820dc720d327c5\",\"obfuscation\":\"ub8xQfxuyrk\",\"output_index\":0,\"sequence_number\":6}", - "event: response.function_call_arguments.delta\ndata: {\"type\":\"response.function_call_arguments.delta\",\"delta\":\"://\",\"item_id\":\"fc_045458052dc10379016a705b6194148191a2820dc720d327c5\",\"obfuscation\":\"JvDy4VPF3RT8t\",\"output_index\":0,\"sequence_number\":7}", - "event: response.function_call_arguments.delta\ndata: {\"type\":\"response.function_call_arguments.delta\",\"delta\":\"example\",\"item_id\":\"fc_045458052dc10379016a705b6194148191a2820dc720d327c5\",\"obfuscation\":\"nUx1jB9C1\",\"output_index\":0,\"sequence_number\":8}", - "event: response.function_call_arguments.delta\ndata: {\"type\":\"response.function_call_arguments.delta\",\"delta\":\".test\",\"item_id\":\"fc_045458052dc10379016a705b6194148191a2820dc720d327c5\",\"obfuscation\":\"nESgdh4F5D9\",\"output_index\":0,\"sequence_number\":9}", - "event: response.function_call_arguments.delta\ndata: {\"type\":\"response.function_call_arguments.delta\",\"delta\":\"/fl\",\"item_id\":\"fc_045458052dc10379016a705b6194148191a2820dc720d327c5\",\"obfuscation\":\"3SOSypdEqxfoT\",\"output_index\":0,\"sequence_number\":10}", - "event: response.function_call_arguments.delta\ndata: {\"type\":\"response.function_call_arguments.delta\",\"delta\":\"ue\",\"item_id\":\"fc_045458052dc10379016a705b6194148191a2820dc720d327c5\",\"obfuscation\":\"WfNnPvGWaIcJYU\",\"output_index\":0,\"sequence_number\":11}", - "event: response.function_call_arguments.delta\ndata: {\"type\":\"response.function_call_arguments.delta\",\"delta\":\"/re\",\"item_id\":\"fc_045458052dc10379016a705b6194148191a2820dc720d327c5\",\"obfuscation\":\"GSIAZIvM9SbTS\",\"output_index\":0,\"sequence_number\":12}", - "event: response.function_call_arguments.delta\ndata: {\"type\":\"response.function_call_arguments.delta\",\"delta\":\"ason\",\"item_id\":\"fc_045458052dc10379016a705b6194148191a2820dc720d327c5\",\"obfuscation\":\"ELHGgjctvQHZ\",\"output_index\":0,\"sequence_number\":13}", - "event: response.function_call_arguments.delta\ndata: {\"type\":\"response.function_call_arguments.delta\",\"delta\":\"ing\",\"item_id\":\"fc_045458052dc10379016a705b6194148191a2820dc720d327c5\",\"obfuscation\":\"AGLq1WDbPPiDU\",\"output_index\":0,\"sequence_number\":14}", - "event: response.function_call_arguments.delta\ndata: {\"type\":\"response.function_call_arguments.delta\",\"delta\":\"-stream\",\"item_id\":\"fc_045458052dc10379016a705b6194148191a2820dc720d327c5\",\"obfuscation\":\"BuKw2YnJj\",\"output_index\":0,\"sequence_number\":15}", - "event: response.function_call_arguments.delta\ndata: {\"type\":\"response.function_call_arguments.delta\",\"delta\":\"s\",\"item_id\":\"fc_045458052dc10379016a705b6194148191a2820dc720d327c5\",\"obfuscation\":\"szEOAd66hgyQxiR\",\"output_index\":0,\"sequence_number\":16}", - "event: response.function_call_arguments.delta\ndata: {\"type\":\"response.function_call_arguments.delta\",\"delta\":\"\\\"}\",\"item_id\":\"fc_045458052dc10379016a705b6194148191a2820dc720d327c5\",\"obfuscation\":\"nWuCGD2j8eK0lG\",\"output_index\":0,\"sequence_number\":17}", - "event: response.function_call_arguments.done\ndata: {\"type\":\"response.function_call_arguments.done\",\"arguments\":\"{\\\"url\\\":\\\"https://example.test/flue/reasoning-streams\\\"}\",\"item_id\":\"fc_045458052dc10379016a705b6194148191a2820dc720d327c5\",\"output_index\":0,\"sequence_number\":18}", - "event: response.output_item.done\ndata: {\"type\":\"response.output_item.done\",\"item\":{\"id\":\"fc_045458052dc10379016a705b6194148191a2820dc720d327c5\",\"type\":\"function_call\",\"status\":\"completed\",\"arguments\":\"{\\\"url\\\":\\\"https://example.test/flue/reasoning-streams\\\"}\",\"call_id\":\"call_4oiajYhDnktEd6wZwQjSMwpb\",\"name\":\"summarize_source\"},\"output_index\":0,\"sequence_number\":19}", - "event: response.completed\ndata: {\"type\":\"response.completed\",\"response\":{\"id\":\"resp_045458052dc10379016a705b611e8881919ad2adde8d6874b9\",\"object\":\"response\",\"created_at\":1785748321,\"status\":\"completed\",\"background\":false,\"completed_at\":1785748321,\"error\":null,\"frequency_penalty\":0,\"incomplete_details\":null,\"instructions\":null,\"max_output_tokens\":null,\"max_tool_calls\":null,\"model\":\"gpt-5.4-nano-2026-03-17\",\"moderation\":null,\"output\":[{\"id\":\"fc_045458052dc10379016a705b6194148191a2820dc720d327c5\",\"type\":\"function_call\",\"status\":\"completed\",\"arguments\":\"{\\\"url\\\":\\\"https://example.test/flue/reasoning-streams\\\"}\",\"call_id\":\"call_4oiajYhDnktEd6wZwQjSMwpb\",\"name\":\"summarize_source\"}],\"parallel_tool_calls\":true,\"presence_penalty\":0,\"previous_response_id\":null,\"prompt_cache_key\":\"workflow:instrumentation:01KZ3E9TWTAEK4P4P3F63HKAKC::default::ma\",\"prompt_cache_retention\":\"24h\",\"reasoning\":{\"context\":\"current_turn\",\"effort\":\"low\",\"mode\":\"standard\",\"summary\":\"detailed\"},\"safety_identifier\":null,\"service_tier\":\"default\",\"store\":false,\"temperature\":1,\"text\":{\"format\":{\"type\":\"text\"},\"verbosity\":\"medium\"},\"tool_choice\":\"auto\",\"tool_usage\":{\"image_gen\":{\"input_tokens\":0,\"input_tokens_details\":{\"image_tokens\":0,\"text_tokens\":0},\"output_tokens\":0,\"output_tokens_details\":{\"image_tokens\":0,\"text_tokens\":0},\"total_tokens\":0},\"web_search\":{\"num_requests\":0}},\"tools\":[{\"type\":\"function\",\"description\":\"Read a file or list a directory. For files, output is truncated to 2000 lines or 50KB — use offset/limit for large files. For directories, returns the list of entries.\",\"name\":\"read\",\"output_schema\":null,\"parameters\":{\"type\":\"object\",\"required\":[\"path\"],\"properties\":{\"path\":{\"type\":\"string\",\"description\":\"Path to the file to read\"},\"offset\":{\"type\":\"number\",\"description\":\"Line number to start from (1-indexed)\"},\"limit\":{\"type\":\"number\",\"description\":\"Maximum number of lines to read\"}}},\"strict\":false},{\"type\":\"function\",\"description\":\"Write content to a file. Creates the file and parent directories if they do not exist.\",\"name\":\"write\",\"output_schema\":null,\"parameters\":{\"type\":\"object\",\"required\":[\"path\",\"content\"],\"properties\":{\"path\":{\"type\":\"string\",\"description\":\"Path to the file to write\"},\"content\":{\"type\":\"string\",\"description\":\"Content to write to the file\"}}},\"strict\":false},{\"type\":\"function\",\"description\":\"Edit a file using exact text replacement. The oldText must match a unique region of the file. Use replaceAll to replace all occurrences.\",\"name\":\"edit\",\"output_schema\":null,\"parameters\":{\"type\":\"object\",\"required\":[\"path\",\"oldText\",\"newText\"],\"properties\":{\"path\":{\"type\":\"string\",\"description\":\"Path to the file to edit\"},\"oldText\":{\"type\":\"string\",\"description\":\"Exact text to find (must be unique)\"},\"newText\":{\"type\":\"string\",\"description\":\"Replacement text\"},\"replaceAll\":{\"type\":\"boolean\",\"description\":\"Replace all occurrences\"}}},\"strict\":false},{\"type\":\"function\",\"description\":\"Execute a bash command. Returns stdout and stderr. Output is truncated to the last 2000 lines or 50KB.\",\"name\":\"bash\",\"output_schema\":null,\"parameters\":{\"type\":\"object\",\"required\":[\"command\"],\"properties\":{\"command\":{\"type\":\"string\",\"description\":\"Bash command to execute\"},\"timeout\":{\"type\":\"number\",\"description\":\"Timeout in seconds\"}}},\"strict\":false},{\"type\":\"function\",\"description\":\"Search file contents for a regex pattern. Returns matching lines with file paths and line numbers.\",\"name\":\"grep\",\"output_schema\":null,\"parameters\":{\"type\":\"object\",\"required\":[\"pattern\"],\"properties\":{\"pattern\":{\"type\":\"string\",\"description\":\"Search pattern (regex)\"},\"path\":{\"type\":\"string\",\"description\":\"Directory or file to search (default: .)\"},\"include\":{\"type\":\"string\",\"description\":\"Glob filter, e.g. \\\"*.ts\\\"\"}}},\"strict\":false},{\"type\":\"function\",\"description\":\"Find files by filename pattern using shell find -name semantics. Returns matching file paths.\",\"name\":\"glob\",\"output_schema\":null,\"parameters\":{\"type\":\"object\",\"required\":[\"pattern\"],\"properties\":{\"pattern\":{\"type\":\"string\",\"description\":\"Filename pattern, e.g. \\\"*.ts\\\"\"},\"path\":{\"type\":\"string\",\"description\":\"Directory to search in (default: .)\"}}},\"strict\":false},{\"type\":\"function\",\"description\":\"Delegate a focused task to a detached child agent with its own context. Use this for independent research, file exploration, or parallel work. The task returns only its final answer to this conversation. No subagents are currently defined.\",\"name\":\"task\",\"output_schema\":null,\"parameters\":{\"type\":\"object\",\"required\":[\"prompt\"],\"properties\":{\"description\":{\"type\":\"string\",\"description\":\"Short human-readable label for the delegated work\"},\"prompt\":{\"type\":\"string\",\"description\":\"Focused instructions for the child agent\"},\"agent\":{\"type\":\"string\",\"description\":\"Declared subagent to use for the child agent\"},\"cwd\":{\"type\":\"string\",\"description\":\"Working directory for the child agent. AGENTS.md and skills are discovered from here.\"}}},\"strict\":false},{\"type\":\"function\",\"description\":\"Return a deterministic lookup result with an id needed by web_search.\",\"name\":\"lookup\",\"output_schema\":null,\"parameters\":{\"type\":\"object\",\"required\":[\"query\"],\"properties\":{\"query\":{\"type\":\"string\"}}},\"strict\":false},{\"type\":\"function\",\"description\":\"Search a deterministic local web index. Requires the id returned by lookup.\",\"name\":\"web_search\",\"output_schema\":null,\"parameters\":{\"type\":\"object\",\"required\":[\"lookupId\",\"query\"],\"properties\":{\"lookupId\":{\"type\":\"string\"},\"query\":{\"type\":\"string\"}}},\"strict\":false},{\"type\":\"function\",\"description\":\"Summarize the selected deterministic source after web_search returns a URL.\",\"name\":\"summarize_source\",\"output_schema\":null,\"parameters\":{\"type\":\"object\",\"required\":[\"url\"],\"properties\":{\"url\":{\"type\":\"string\"}}},\"strict\":false}],\"top_logprobs\":0,\"top_p\":0.98,\"truncation\":\"disabled\",\"usage\":{\"input_tokens\":1147,\"input_tokens_details\":{\"cache_write_tokens\":0,\"cached_tokens\":0},\"output_tokens\":30,\"output_tokens_details\":{\"reasoning_tokens\":0},\"total_tokens\":1177},\"user\":null,\"metadata\":{}},\"sequence_number\":20}" - ], - "kind": "sse" - }, - "headers": { - "access-control-expose-headers": "X-Request-ID, CF-Ray, CF-Ray", - "alt-svc": "h3=\":443\"; ma=86400", - "cf-cache-status": "DYNAMIC", - "cf-ray": "a25432be8c937bb6-IAD", - "connection": "keep-alive", - "content-type": "text/event-stream; charset=utf-8", - "date": "Mon, 03 Aug 2026 09:12:01 GMT", - "openai-organization": "braintrust-data", - "openai-processing-ms": "326", - "openai-project": "proj_vsCSXafhhByzWOThMrJcZiw9", - "openai-version": "2020-10-01", - "server": "cloudflare", - "set-cookie": "[REDACTED]", - "strict-transport-security": "max-age=31536000; includeSubDomains; preload", - "transfer-encoding": "chunked", - "x-content-type-options": "nosniff", - "x-ratelimit-limit-requests": "30000", - "x-ratelimit-limit-tokens": "180000000", - "x-ratelimit-remaining-requests": "29999", - "x-ratelimit-remaining-tokens": "179998398", - "x-ratelimit-reset-requests": "2ms", - "x-ratelimit-reset-tokens": "0s", - "x-request-id": "req_65060d10d7dc405b99f44cac4ba179c7" - }, - "status": 200, - "statusText": "OK" - } - }, - { - "callIndex": 3, - "id": "83cbbf6da482c905", - "matchKey": "POST api.openai.com/v1/responses", - "recordedAt": "2026-08-03T09:12:03.092Z", - "request": { - "body": { - "kind": "json", - "value": { - "include": ["reasoning.encrypted_content"], - "input": [ - { - "content": "You are running in headless mode with no human operator. Work autonomously — never ask questions, never wait for user input. Make your best judgment and proceed independently.\n\nYou are a deterministic Flue instrumentation test agent. Follow user instructions exactly. When asked for a marker, output only that marker and no extra text. When running a local skill file, read it yourself and do not delegate it to a task.\n\n## Available Skills\n\nThe following skills provide specialized instructions for specific tasks. When a task matches a skill description, activate that skill before proceeding so its full instructions are loaded. Skill instructions and supporting resources stay lazy until activation or explicit file reads.\n\n- **e2e-flue-skill** — Deterministic skill used by the Braintrust Flue instrumentation e2e test.\n\nDate: Mon, Aug 3, 2026\nWorking directory: .\n\nDirectory structure:\n.agents\n.flue\n.flue-build\n__cassettes__\n__snapshots__\nassertions.ts\ncassette-filter.mjs\nconstants.mjs\ndist\nnode_modules\npackage.json\npnpm-lock.yaml\nscenario.cli.mjs\nscenario.impl.mjs\nscenario.mjs\nscenario.test.ts\nscenario.ts\nscenario.v2.ts\nv2-assertions.ts\nversions", - "role": "developer" - }, - { - "content": [ - { - "text": "Complete this instrumented research flow. Call exactly one tool per turn and wait for each tool result before choosing the next tool. Step 1: call lookup with query \"flue instrumentation\". Step 2: use the lookup result id as lookupId and call web_search with query \"Braintrust Flue reasoning stream instrumentation\". Step 3: use the first web_search result url and call summarize_source. After summarize_source returns, reply with exactly PROMPT_DONE and no other text.", - "type": "input_text" - } - ], - "role": "user" - }, - { - "arguments": "{\"query\":\"flue instrumentation\"}", - "call_id": "call_rBgonKxWHwhN9E8aQ4VobIOe", - "id": "fc_045458052dc10379016a705b6008408191b27653755cf5bd03", - "name": "lookup", - "type": "function_call" - }, - { - "call_id": "call_rBgonKxWHwhN9E8aQ4VobIOe", - "output": "{\"id\":\"flue-session-2026\",\"query\":\"flue instrumentation\",\"topic\":\"session instrumentation\"}", - "type": "function_call_output" - }, - { - "arguments": "{\"lookupId\":\"flue-session-2026\",\"query\":\"Braintrust Flue reasoning stream instrumentation\"}", - "call_id": "call_fGesVCba2aABMoxSNfVnDVnR", - "id": "fc_045458052dc10379016a705b60c92c81918da2fab9710ee199", - "name": "web_search", - "type": "function_call" - }, - { - "call_id": "call_fGesVCba2aABMoxSNfVnDVnR", - "output": "{\"lookupId\":\"flue-session-2026\",\"query\":\"Braintrust Flue reasoning stream instrumentation\",\"results\":[{\"title\":\"Flue reasoning stream instrumentation\",\"url\":\"https://example.test/flue/reasoning-streams\"}]}", - "type": "function_call_output" - }, - { - "arguments": "{\"url\":\"https://example.test/flue/reasoning-streams\"}", - "call_id": "call_4oiajYhDnktEd6wZwQjSMwpb", - "id": "fc_045458052dc10379016a705b6194148191a2820dc720d327c5", - "name": "summarize_source", - "type": "function_call" - }, - { - "call_id": "call_4oiajYhDnktEd6wZwQjSMwpb", - "output": "{\"summary\":\"Flue emits reasoning, tool execution, and LLM turn events separately.\",\"url\":\"https://example.test/flue/reasoning-streams\"}", - "type": "function_call_output" - } - ], - "model": "gpt-5.4-nano", - "prompt_cache_key": "[REDACTED]", - "reasoning": { - "effort": "low", - "summary": "auto" - }, - "store": false, - "stream": true, - "tools": [ - { - "description": "Read a file or list a directory. For files, output is truncated to 2000 lines or 50KB — use offset/limit for large files. For directories, returns the list of entries.", - "name": "read", - "parameters": { - "properties": { - "limit": { - "description": "Maximum number of lines to read", - "type": "number" - }, - "offset": { - "description": "Line number to start from (1-indexed)", - "type": "number" - }, - "path": { - "description": "Path to the file to read", - "type": "string" - } - }, - "required": ["path"], - "type": "object" - }, - "strict": false, - "type": "function" - }, - { - "description": "Write content to a file. Creates the file and parent directories if they do not exist.", - "name": "write", - "parameters": { - "properties": { - "content": { - "description": "Content to write to the file", - "type": "string" - }, - "path": { - "description": "Path to the file to write", - "type": "string" - } - }, - "required": ["path", "content"], - "type": "object" - }, - "strict": false, - "type": "function" - }, - { - "description": "Edit a file using exact text replacement. The oldText must match a unique region of the file. Use replaceAll to replace all occurrences.", - "name": "edit", - "parameters": { - "properties": { - "newText": { - "description": "Replacement text", - "type": "string" - }, - "oldText": { - "description": "Exact text to find (must be unique)", - "type": "string" - }, - "path": { - "description": "Path to the file to edit", - "type": "string" - }, - "replaceAll": { - "description": "Replace all occurrences", - "type": "boolean" - } - }, - "required": ["path", "oldText", "newText"], - "type": "object" - }, - "strict": false, - "type": "function" - }, - { - "description": "Execute a bash command. Returns stdout and stderr. Output is truncated to the last 2000 lines or 50KB.", - "name": "bash", - "parameters": { - "properties": { - "command": { - "description": "Bash command to execute", - "type": "string" - }, - "timeout": { - "description": "Timeout in seconds", - "type": "number" - } - }, - "required": ["command"], - "type": "object" - }, - "strict": false, - "type": "function" - }, - { - "description": "Search file contents for a regex pattern. Returns matching lines with file paths and line numbers.", - "name": "grep", - "parameters": { - "properties": { - "include": { - "description": "Glob filter, e.g. \"*.ts\"", - "type": "string" - }, - "path": { - "description": "Directory or file to search (default: .)", - "type": "string" - }, - "pattern": { - "description": "Search pattern (regex)", - "type": "string" - } - }, - "required": ["pattern"], - "type": "object" - }, - "strict": false, - "type": "function" - }, - { - "description": "Find files by filename pattern using shell find -name semantics. Returns matching file paths.", - "name": "glob", - "parameters": { - "properties": { - "path": { - "description": "Directory to search in (default: .)", - "type": "string" - }, - "pattern": { - "description": "Filename pattern, e.g. \"*.ts\"", - "type": "string" - } - }, - "required": ["pattern"], - "type": "object" - }, - "strict": false, - "type": "function" - }, - { - "description": "Delegate a focused task to a detached child agent with its own context. Use this for independent research, file exploration, or parallel work. The task returns only its final answer to this conversation. No subagents are currently defined.", - "name": "task", - "parameters": { - "properties": { - "agent": { - "description": "Declared subagent to use for the child agent", - "type": "string" - }, - "cwd": { - "description": "Working directory for the child agent. AGENTS.md and skills are discovered from here.", - "type": "string" - }, - "description": { - "description": "Short human-readable label for the delegated work", - "type": "string" - }, - "prompt": { - "description": "Focused instructions for the child agent", - "type": "string" - } - }, - "required": ["prompt"], - "type": "object" - }, - "strict": false, - "type": "function" - }, - { - "description": "Return a deterministic lookup result with an id needed by web_search.", - "name": "lookup", - "parameters": { - "properties": { - "query": { - "type": "string" - } - }, - "required": ["query"], - "type": "object" - }, - "strict": false, - "type": "function" - }, - { - "description": "Search a deterministic local web index. Requires the id returned by lookup.", - "name": "web_search", - "parameters": { - "properties": { - "lookupId": { - "type": "string" - }, - "query": { - "type": "string" - } - }, - "required": ["lookupId", "query"], - "type": "object" - }, - "strict": false, - "type": "function" - }, - { - "description": "Summarize the selected deterministic source after web_search returns a URL.", - "name": "summarize_source", - "parameters": { - "properties": { - "url": { - "type": "string" - } - }, - "required": ["url"], - "type": "object" - }, - "strict": false, - "type": "function" - } - ] - } - }, - "headers": {}, - "method": "POST", - "url": "https://api.openai.com/v1/responses" - }, - "response": { - "body": { - "chunks": [ - "event: response.created\ndata: {\"type\":\"response.created\",\"response\":{\"id\":\"resp_045458052dc10379016a705b6286dc8191be79bd3493b23c38\",\"object\":\"response\",\"created_at\":1785748322,\"status\":\"in_progress\",\"background\":false,\"completed_at\":null,\"error\":null,\"frequency_penalty\":0,\"incomplete_details\":null,\"instructions\":null,\"max_output_tokens\":null,\"max_tool_calls\":null,\"model\":\"gpt-5.4-nano-2026-03-17\",\"moderation\":null,\"output\":[],\"parallel_tool_calls\":true,\"presence_penalty\":0,\"previous_response_id\":null,\"prompt_cache_key\":\"workflow:instrumentation:01KZ3E9TWTAEK4P4P3F63HKAKC::default::ma\",\"prompt_cache_retention\":\"24h\",\"reasoning\":{\"context\":\"current_turn\",\"effort\":\"low\",\"mode\":\"standard\",\"summary\":\"detailed\"},\"safety_identifier\":null,\"service_tier\":\"auto\",\"store\":false,\"temperature\":1,\"text\":{\"format\":{\"type\":\"text\"},\"verbosity\":\"medium\"},\"tool_choice\":\"auto\",\"tool_usage\":{\"image_gen\":{\"input_tokens\":0,\"input_tokens_details\":{\"image_tokens\":0,\"text_tokens\":0},\"output_tokens\":0,\"output_tokens_details\":{\"image_tokens\":0,\"text_tokens\":0},\"total_tokens\":0},\"web_search\":{\"num_requests\":0}},\"tools\":[{\"type\":\"function\",\"description\":\"Read a file or list a directory. For files, output is truncated to 2000 lines or 50KB — use offset/limit for large files. For directories, returns the list of entries.\",\"name\":\"read\",\"output_schema\":null,\"parameters\":{\"type\":\"object\",\"required\":[\"path\"],\"properties\":{\"path\":{\"type\":\"string\",\"description\":\"Path to the file to read\"},\"offset\":{\"type\":\"number\",\"description\":\"Line number to start from (1-indexed)\"},\"limit\":{\"type\":\"number\",\"description\":\"Maximum number of lines to read\"}}},\"strict\":false},{\"type\":\"function\",\"description\":\"Write content to a file. Creates the file and parent directories if they do not exist.\",\"name\":\"write\",\"output_schema\":null,\"parameters\":{\"type\":\"object\",\"required\":[\"path\",\"content\"],\"properties\":{\"path\":{\"type\":\"string\",\"description\":\"Path to the file to write\"},\"content\":{\"type\":\"string\",\"description\":\"Content to write to the file\"}}},\"strict\":false},{\"type\":\"function\",\"description\":\"Edit a file using exact text replacement. The oldText must match a unique region of the file. Use replaceAll to replace all occurrences.\",\"name\":\"edit\",\"output_schema\":null,\"parameters\":{\"type\":\"object\",\"required\":[\"path\",\"oldText\",\"newText\"],\"properties\":{\"path\":{\"type\":\"string\",\"description\":\"Path to the file to edit\"},\"oldText\":{\"type\":\"string\",\"description\":\"Exact text to find (must be unique)\"},\"newText\":{\"type\":\"string\",\"description\":\"Replacement text\"},\"replaceAll\":{\"type\":\"boolean\",\"description\":\"Replace all occurrences\"}}},\"strict\":false},{\"type\":\"function\",\"description\":\"Execute a bash command. Returns stdout and stderr. Output is truncated to the last 2000 lines or 50KB.\",\"name\":\"bash\",\"output_schema\":null,\"parameters\":{\"type\":\"object\",\"required\":[\"command\"],\"properties\":{\"command\":{\"type\":\"string\",\"description\":\"Bash command to execute\"},\"timeout\":{\"type\":\"number\",\"description\":\"Timeout in seconds\"}}},\"strict\":false},{\"type\":\"function\",\"description\":\"Search file contents for a regex pattern. Returns matching lines with file paths and line numbers.\",\"name\":\"grep\",\"output_schema\":null,\"parameters\":{\"type\":\"object\",\"required\":[\"pattern\"],\"properties\":{\"pattern\":{\"type\":\"string\",\"description\":\"Search pattern (regex)\"},\"path\":{\"type\":\"string\",\"description\":\"Directory or file to search (default: .)\"},\"include\":{\"type\":\"string\",\"description\":\"Glob filter, e.g. \\\"*.ts\\\"\"}}},\"strict\":false},{\"type\":\"function\",\"description\":\"Find files by filename pattern using shell find -name semantics. Returns matching file paths.\",\"name\":\"glob\",\"output_schema\":null,\"parameters\":{\"type\":\"object\",\"required\":[\"pattern\"],\"properties\":{\"pattern\":{\"type\":\"string\",\"description\":\"Filename pattern, e.g. \\\"*.ts\\\"\"},\"path\":{\"type\":\"string\",\"description\":\"Directory to search in (default: .)\"}}},\"strict\":false},{\"type\":\"function\",\"description\":\"Delegate a focused task to a detached child agent with its own context. Use this for independent research, file exploration, or parallel work. The task returns only its final answer to this conversation. No subagents are currently defined.\",\"name\":\"task\",\"output_schema\":null,\"parameters\":{\"type\":\"object\",\"required\":[\"prompt\"],\"properties\":{\"description\":{\"type\":\"string\",\"description\":\"Short human-readable label for the delegated work\"},\"prompt\":{\"type\":\"string\",\"description\":\"Focused instructions for the child agent\"},\"agent\":{\"type\":\"string\",\"description\":\"Declared subagent to use for the child agent\"},\"cwd\":{\"type\":\"string\",\"description\":\"Working directory for the child agent. AGENTS.md and skills are discovered from here.\"}}},\"strict\":false},{\"type\":\"function\",\"description\":\"Return a deterministic lookup result with an id needed by web_search.\",\"name\":\"lookup\",\"output_schema\":null,\"parameters\":{\"type\":\"object\",\"required\":[\"query\"],\"properties\":{\"query\":{\"type\":\"string\"}}},\"strict\":false},{\"type\":\"function\",\"description\":\"Search a deterministic local web index. Requires the id returned by lookup.\",\"name\":\"web_search\",\"output_schema\":null,\"parameters\":{\"type\":\"object\",\"required\":[\"lookupId\",\"query\"],\"properties\":{\"lookupId\":{\"type\":\"string\"},\"query\":{\"type\":\"string\"}}},\"strict\":false},{\"type\":\"function\",\"description\":\"Summarize the selected deterministic source after web_search returns a URL.\",\"name\":\"summarize_source\",\"output_schema\":null,\"parameters\":{\"type\":\"object\",\"required\":[\"url\"],\"properties\":{\"url\":{\"type\":\"string\"}}},\"strict\":false}],\"top_logprobs\":0,\"top_p\":0.98,\"truncation\":\"disabled\",\"usage\":null,\"user\":null,\"metadata\":{}},\"sequence_number\":0}", - "event: response.in_progress\ndata: {\"type\":\"response.in_progress\",\"response\":{\"id\":\"resp_045458052dc10379016a705b6286dc8191be79bd3493b23c38\",\"object\":\"response\",\"created_at\":1785748322,\"status\":\"in_progress\",\"background\":false,\"completed_at\":null,\"error\":null,\"frequency_penalty\":0,\"incomplete_details\":null,\"instructions\":null,\"max_output_tokens\":null,\"max_tool_calls\":null,\"model\":\"gpt-5.4-nano-2026-03-17\",\"moderation\":null,\"output\":[],\"parallel_tool_calls\":true,\"presence_penalty\":0,\"previous_response_id\":null,\"prompt_cache_key\":\"workflow:instrumentation:01KZ3E9TWTAEK4P4P3F63HKAKC::default::ma\",\"prompt_cache_retention\":\"24h\",\"reasoning\":{\"context\":\"current_turn\",\"effort\":\"low\",\"mode\":\"standard\",\"summary\":\"detailed\"},\"safety_identifier\":null,\"service_tier\":\"auto\",\"store\":false,\"temperature\":1,\"text\":{\"format\":{\"type\":\"text\"},\"verbosity\":\"medium\"},\"tool_choice\":\"auto\",\"tool_usage\":{\"image_gen\":{\"input_tokens\":0,\"input_tokens_details\":{\"image_tokens\":0,\"text_tokens\":0},\"output_tokens\":0,\"output_tokens_details\":{\"image_tokens\":0,\"text_tokens\":0},\"total_tokens\":0},\"web_search\":{\"num_requests\":0}},\"tools\":[{\"type\":\"function\",\"description\":\"Read a file or list a directory. For files, output is truncated to 2000 lines or 50KB — use offset/limit for large files. For directories, returns the list of entries.\",\"name\":\"read\",\"output_schema\":null,\"parameters\":{\"type\":\"object\",\"required\":[\"path\"],\"properties\":{\"path\":{\"type\":\"string\",\"description\":\"Path to the file to read\"},\"offset\":{\"type\":\"number\",\"description\":\"Line number to start from (1-indexed)\"},\"limit\":{\"type\":\"number\",\"description\":\"Maximum number of lines to read\"}}},\"strict\":false},{\"type\":\"function\",\"description\":\"Write content to a file. Creates the file and parent directories if they do not exist.\",\"name\":\"write\",\"output_schema\":null,\"parameters\":{\"type\":\"object\",\"required\":[\"path\",\"content\"],\"properties\":{\"path\":{\"type\":\"string\",\"description\":\"Path to the file to write\"},\"content\":{\"type\":\"string\",\"description\":\"Content to write to the file\"}}},\"strict\":false},{\"type\":\"function\",\"description\":\"Edit a file using exact text replacement. The oldText must match a unique region of the file. Use replaceAll to replace all occurrences.\",\"name\":\"edit\",\"output_schema\":null,\"parameters\":{\"type\":\"object\",\"required\":[\"path\",\"oldText\",\"newText\"],\"properties\":{\"path\":{\"type\":\"string\",\"description\":\"Path to the file to edit\"},\"oldText\":{\"type\":\"string\",\"description\":\"Exact text to find (must be unique)\"},\"newText\":{\"type\":\"string\",\"description\":\"Replacement text\"},\"replaceAll\":{\"type\":\"boolean\",\"description\":\"Replace all occurrences\"}}},\"strict\":false},{\"type\":\"function\",\"description\":\"Execute a bash command. Returns stdout and stderr. Output is truncated to the last 2000 lines or 50KB.\",\"name\":\"bash\",\"output_schema\":null,\"parameters\":{\"type\":\"object\",\"required\":[\"command\"],\"properties\":{\"command\":{\"type\":\"string\",\"description\":\"Bash command to execute\"},\"timeout\":{\"type\":\"number\",\"description\":\"Timeout in seconds\"}}},\"strict\":false},{\"type\":\"function\",\"description\":\"Search file contents for a regex pattern. Returns matching lines with file paths and line numbers.\",\"name\":\"grep\",\"output_schema\":null,\"parameters\":{\"type\":\"object\",\"required\":[\"pattern\"],\"properties\":{\"pattern\":{\"type\":\"string\",\"description\":\"Search pattern (regex)\"},\"path\":{\"type\":\"string\",\"description\":\"Directory or file to search (default: .)\"},\"include\":{\"type\":\"string\",\"description\":\"Glob filter, e.g. \\\"*.ts\\\"\"}}},\"strict\":false},{\"type\":\"function\",\"description\":\"Find files by filename pattern using shell find -name semantics. Returns matching file paths.\",\"name\":\"glob\",\"output_schema\":null,\"parameters\":{\"type\":\"object\",\"required\":[\"pattern\"],\"properties\":{\"pattern\":{\"type\":\"string\",\"description\":\"Filename pattern, e.g. \\\"*.ts\\\"\"},\"path\":{\"type\":\"string\",\"description\":\"Directory to search in (default: .)\"}}},\"strict\":false},{\"type\":\"function\",\"description\":\"Delegate a focused task to a detached child agent with its own context. Use this for independent research, file exploration, or parallel work. The task returns only its final answer to this conversation. No subagents are currently defined.\",\"name\":\"task\",\"output_schema\":null,\"parameters\":{\"type\":\"object\",\"required\":[\"prompt\"],\"properties\":{\"description\":{\"type\":\"string\",\"description\":\"Short human-readable label for the delegated work\"},\"prompt\":{\"type\":\"string\",\"description\":\"Focused instructions for the child agent\"},\"agent\":{\"type\":\"string\",\"description\":\"Declared subagent to use for the child agent\"},\"cwd\":{\"type\":\"string\",\"description\":\"Working directory for the child agent. AGENTS.md and skills are discovered from here.\"}}},\"strict\":false},{\"type\":\"function\",\"description\":\"Return a deterministic lookup result with an id needed by web_search.\",\"name\":\"lookup\",\"output_schema\":null,\"parameters\":{\"type\":\"object\",\"required\":[\"query\"],\"properties\":{\"query\":{\"type\":\"string\"}}},\"strict\":false},{\"type\":\"function\",\"description\":\"Search a deterministic local web index. Requires the id returned by lookup.\",\"name\":\"web_search\",\"output_schema\":null,\"parameters\":{\"type\":\"object\",\"required\":[\"lookupId\",\"query\"],\"properties\":{\"lookupId\":{\"type\":\"string\"},\"query\":{\"type\":\"string\"}}},\"strict\":false},{\"type\":\"function\",\"description\":\"Summarize the selected deterministic source after web_search returns a URL.\",\"name\":\"summarize_source\",\"output_schema\":null,\"parameters\":{\"type\":\"object\",\"required\":[\"url\"],\"properties\":{\"url\":{\"type\":\"string\"}}},\"strict\":false}],\"top_logprobs\":0,\"top_p\":0.98,\"truncation\":\"disabled\",\"usage\":null,\"user\":null,\"metadata\":{}},\"sequence_number\":1}", - "event: response.output_item.added\ndata: {\"type\":\"response.output_item.added\",\"item\":{\"id\":\"msg_045458052dc10379016a705b62dbd8819189b8287061debf12\",\"type\":\"message\",\"status\":\"in_progress\",\"content\":[],\"phase\":\"final_answer\",\"role\":\"assistant\"},\"output_index\":0,\"sequence_number\":2}", - "event: response.content_part.added\ndata: {\"type\":\"response.content_part.added\",\"content_index\":0,\"item_id\":\"msg_045458052dc10379016a705b62dbd8819189b8287061debf12\",\"output_index\":0,\"part\":{\"type\":\"output_text\",\"annotations\":[],\"logprobs\":[],\"text\":\"\"},\"sequence_number\":3}", - "event: response.output_text.delta\ndata: {\"type\":\"response.output_text.delta\",\"content_index\":0,\"delta\":\"PROM\",\"item_id\":\"msg_045458052dc10379016a705b62dbd8819189b8287061debf12\",\"logprobs\":[],\"obfuscation\":\"ttkbAlDp50d6\",\"output_index\":0,\"sequence_number\":4}", - "event: response.output_text.delta\ndata: {\"type\":\"response.output_text.delta\",\"content_index\":0,\"delta\":\"PT\",\"item_id\":\"msg_045458052dc10379016a705b62dbd8819189b8287061debf12\",\"logprobs\":[],\"obfuscation\":\"Z6PXaim62hCyKh\",\"output_index\":0,\"sequence_number\":5}", - "event: response.output_text.delta\ndata: {\"type\":\"response.output_text.delta\",\"content_index\":0,\"delta\":\"_DONE\",\"item_id\":\"msg_045458052dc10379016a705b62dbd8819189b8287061debf12\",\"logprobs\":[],\"obfuscation\":\"MiAXFIn3IuM\",\"output_index\":0,\"sequence_number\":6}", - "event: response.output_text.done\ndata: {\"type\":\"response.output_text.done\",\"content_index\":0,\"item_id\":\"msg_045458052dc10379016a705b62dbd8819189b8287061debf12\",\"logprobs\":[],\"output_index\":0,\"sequence_number\":7,\"text\":\"PROMPT_DONE\"}", - "event: response.content_part.done\ndata: {\"type\":\"response.content_part.done\",\"content_index\":0,\"item_id\":\"msg_045458052dc10379016a705b62dbd8819189b8287061debf12\",\"output_index\":0,\"part\":{\"type\":\"output_text\",\"annotations\":[],\"logprobs\":[],\"text\":\"PROMPT_DONE\"},\"sequence_number\":8}", - "event: response.output_item.done\ndata: {\"type\":\"response.output_item.done\",\"item\":{\"id\":\"msg_045458052dc10379016a705b62dbd8819189b8287061debf12\",\"type\":\"message\",\"status\":\"completed\",\"content\":[{\"type\":\"output_text\",\"annotations\":[],\"logprobs\":[],\"text\":\"PROMPT_DONE\"}],\"phase\":\"final_answer\",\"role\":\"assistant\"},\"output_index\":0,\"sequence_number\":9}", - "event: response.completed\ndata: {\"type\":\"response.completed\",\"response\":{\"id\":\"resp_045458052dc10379016a705b6286dc8191be79bd3493b23c38\",\"object\":\"response\",\"created_at\":1785748322,\"status\":\"completed\",\"background\":false,\"completed_at\":1785748323,\"error\":null,\"frequency_penalty\":0,\"incomplete_details\":null,\"instructions\":null,\"max_output_tokens\":null,\"max_tool_calls\":null,\"model\":\"gpt-5.4-nano-2026-03-17\",\"moderation\":null,\"output\":[{\"id\":\"msg_045458052dc10379016a705b62dbd8819189b8287061debf12\",\"type\":\"message\",\"status\":\"completed\",\"content\":[{\"type\":\"output_text\",\"annotations\":[],\"logprobs\":[],\"text\":\"PROMPT_DONE\"}],\"phase\":\"final_answer\",\"role\":\"assistant\"}],\"parallel_tool_calls\":true,\"presence_penalty\":0,\"previous_response_id\":null,\"prompt_cache_key\":\"workflow:instrumentation:01KZ3E9TWTAEK4P4P3F63HKAKC::default::ma\",\"prompt_cache_retention\":\"24h\",\"reasoning\":{\"context\":\"current_turn\",\"effort\":\"low\",\"mode\":\"standard\",\"summary\":\"detailed\"},\"safety_identifier\":null,\"service_tier\":\"default\",\"store\":false,\"temperature\":1,\"text\":{\"format\":{\"type\":\"text\"},\"verbosity\":\"medium\"},\"tool_choice\":\"auto\",\"tool_usage\":{\"image_gen\":{\"input_tokens\":0,\"input_tokens_details\":{\"image_tokens\":0,\"text_tokens\":0},\"output_tokens\":0,\"output_tokens_details\":{\"image_tokens\":0,\"text_tokens\":0},\"total_tokens\":0},\"web_search\":{\"num_requests\":0}},\"tools\":[{\"type\":\"function\",\"description\":\"Read a file or list a directory. For files, output is truncated to 2000 lines or 50KB — use offset/limit for large files. For directories, returns the list of entries.\",\"name\":\"read\",\"output_schema\":null,\"parameters\":{\"type\":\"object\",\"required\":[\"path\"],\"properties\":{\"path\":{\"type\":\"string\",\"description\":\"Path to the file to read\"},\"offset\":{\"type\":\"number\",\"description\":\"Line number to start from (1-indexed)\"},\"limit\":{\"type\":\"number\",\"description\":\"Maximum number of lines to read\"}}},\"strict\":false},{\"type\":\"function\",\"description\":\"Write content to a file. Creates the file and parent directories if they do not exist.\",\"name\":\"write\",\"output_schema\":null,\"parameters\":{\"type\":\"object\",\"required\":[\"path\",\"content\"],\"properties\":{\"path\":{\"type\":\"string\",\"description\":\"Path to the file to write\"},\"content\":{\"type\":\"string\",\"description\":\"Content to write to the file\"}}},\"strict\":false},{\"type\":\"function\",\"description\":\"Edit a file using exact text replacement. The oldText must match a unique region of the file. Use replaceAll to replace all occurrences.\",\"name\":\"edit\",\"output_schema\":null,\"parameters\":{\"type\":\"object\",\"required\":[\"path\",\"oldText\",\"newText\"],\"properties\":{\"path\":{\"type\":\"string\",\"description\":\"Path to the file to edit\"},\"oldText\":{\"type\":\"string\",\"description\":\"Exact text to find (must be unique)\"},\"newText\":{\"type\":\"string\",\"description\":\"Replacement text\"},\"replaceAll\":{\"type\":\"boolean\",\"description\":\"Replace all occurrences\"}}},\"strict\":false},{\"type\":\"function\",\"description\":\"Execute a bash command. Returns stdout and stderr. Output is truncated to the last 2000 lines or 50KB.\",\"name\":\"bash\",\"output_schema\":null,\"parameters\":{\"type\":\"object\",\"required\":[\"command\"],\"properties\":{\"command\":{\"type\":\"string\",\"description\":\"Bash command to execute\"},\"timeout\":{\"type\":\"number\",\"description\":\"Timeout in seconds\"}}},\"strict\":false},{\"type\":\"function\",\"description\":\"Search file contents for a regex pattern. Returns matching lines with file paths and line numbers.\",\"name\":\"grep\",\"output_schema\":null,\"parameters\":{\"type\":\"object\",\"required\":[\"pattern\"],\"properties\":{\"pattern\":{\"type\":\"string\",\"description\":\"Search pattern (regex)\"},\"path\":{\"type\":\"string\",\"description\":\"Directory or file to search (default: .)\"},\"include\":{\"type\":\"string\",\"description\":\"Glob filter, e.g. \\\"*.ts\\\"\"}}},\"strict\":false},{\"type\":\"function\",\"description\":\"Find files by filename pattern using shell find -name semantics. Returns matching file paths.\",\"name\":\"glob\",\"output_schema\":null,\"parameters\":{\"type\":\"object\",\"required\":[\"pattern\"],\"properties\":{\"pattern\":{\"type\":\"string\",\"description\":\"Filename pattern, e.g. \\\"*.ts\\\"\"},\"path\":{\"type\":\"string\",\"description\":\"Directory to search in (default: .)\"}}},\"strict\":false},{\"type\":\"function\",\"description\":\"Delegate a focused task to a detached child agent with its own context. Use this for independent research, file exploration, or parallel work. The task returns only its final answer to this conversation. No subagents are currently defined.\",\"name\":\"task\",\"output_schema\":null,\"parameters\":{\"type\":\"object\",\"required\":[\"prompt\"],\"properties\":{\"description\":{\"type\":\"string\",\"description\":\"Short human-readable label for the delegated work\"},\"prompt\":{\"type\":\"string\",\"description\":\"Focused instructions for the child agent\"},\"agent\":{\"type\":\"string\",\"description\":\"Declared subagent to use for the child agent\"},\"cwd\":{\"type\":\"string\",\"description\":\"Working directory for the child agent. AGENTS.md and skills are discovered from here.\"}}},\"strict\":false},{\"type\":\"function\",\"description\":\"Return a deterministic lookup result with an id needed by web_search.\",\"name\":\"lookup\",\"output_schema\":null,\"parameters\":{\"type\":\"object\",\"required\":[\"query\"],\"properties\":{\"query\":{\"type\":\"string\"}}},\"strict\":false},{\"type\":\"function\",\"description\":\"Search a deterministic local web index. Requires the id returned by lookup.\",\"name\":\"web_search\",\"output_schema\":null,\"parameters\":{\"type\":\"object\",\"required\":[\"lookupId\",\"query\"],\"properties\":{\"lookupId\":{\"type\":\"string\"},\"query\":{\"type\":\"string\"}}},\"strict\":false},{\"type\":\"function\",\"description\":\"Summarize the selected deterministic source after web_search returns a URL.\",\"name\":\"summarize_source\",\"output_schema\":null,\"parameters\":{\"type\":\"object\",\"required\":[\"url\"],\"properties\":{\"url\":{\"type\":\"string\"}}},\"strict\":false}],\"top_logprobs\":0,\"top_p\":0.98,\"truncation\":\"disabled\",\"usage\":{\"input_tokens\":1222,\"input_tokens_details\":{\"cache_write_tokens\":0,\"cached_tokens\":0},\"output_tokens\":7,\"output_tokens_details\":{\"reasoning_tokens\":0},\"total_tokens\":1229},\"user\":null,\"metadata\":{}},\"sequence_number\":10}" - ], - "kind": "sse" - }, - "headers": { - "access-control-expose-headers": "X-Request-ID, CF-Ray, CF-Ray", - "alt-svc": "h3=\":443\"; ma=86400", - "cf-cache-status": "DYNAMIC", - "cf-ray": "a25432c7682f7bb6-IAD", - "connection": "keep-alive", - "content-type": "text/event-stream; charset=utf-8", - "date": "Mon, 03 Aug 2026 09:12:02 GMT", - "openai-organization": "braintrust-data", - "openai-processing-ms": "281", - "openai-project": "proj_vsCSXafhhByzWOThMrJcZiw9", - "openai-version": "2020-10-01", - "server": "cloudflare", - "set-cookie": "[REDACTED]", - "strict-transport-security": "max-age=31536000; includeSubDomains; preload", - "transfer-encoding": "chunked", - "x-content-type-options": "nosniff", - "x-ratelimit-limit-requests": "30000", - "x-ratelimit-limit-tokens": "180000000", - "x-ratelimit-remaining-requests": "29999", - "x-ratelimit-remaining-tokens": "179998323", - "x-ratelimit-reset-requests": "2ms", - "x-ratelimit-reset-tokens": "0s", - "x-request-id": "req_89fa79409a904f43a453a36d2ca890d0" - }, - "status": 200, - "statusText": "OK" - } - }, - { - "callIndex": 4, - "id": "417e64addcf59da7", - "matchKey": "POST api.openai.com/v1/responses", - "recordedAt": "2026-08-03T09:12:04.374Z", - "request": { - "body": { - "kind": "json", - "value": { - "input": [ - { - "content": "You are running in headless mode with no human operator. Work autonomously — never ask questions, never wait for user input. Make your best judgment and proceed independently.\n\nYou are a deterministic Flue instrumentation test agent. Follow user instructions exactly. When asked for a marker, output only that marker and no extra text. When running a local skill file, read it yourself and do not delegate it to a task.\n\n## Available Skills\n\nThe following skills provide specialized instructions for specific tasks. When a task matches a skill description, activate that skill before proceeding so its full instructions are loaded. Skill instructions and supporting resources stay lazy until activation or explicit file reads.\n\n- **e2e-flue-skill** — Deterministic skill used by the Braintrust Flue instrumentation e2e test.\n\nDate: Mon, Aug 3, 2026\nWorking directory: .\n\nDirectory structure:\n.agents\n.flue\n.flue-build\n__cassettes__\n__snapshots__\nassertions.ts\ncassette-filter.mjs\nconstants.mjs\ndist\nnode_modules\npackage.json\npnpm-lock.yaml\nscenario.cli.mjs\nscenario.impl.mjs\nscenario.mjs\nscenario.test.ts\nscenario.ts\nscenario.v2.ts\nv2-assertions.ts\nversions", - "role": "developer" - }, - { - "content": [ - { - "text": "Run the skill named \"e2e-flue-skill\".\n\nArguments:\n{\n \"marker\": \"SKILL_DONE\"\n}", - "type": "input_text" - } - ], - "role": "user" - } - ], - "model": "gpt-5.4-nano", - "prompt_cache_key": "[REDACTED]", - "reasoning": { - "effort": "none" - }, - "store": false, - "stream": true, - "tools": [ - { - "description": "Read a file or list a directory. For files, output is truncated to 2000 lines or 50KB — use offset/limit for large files. For directories, returns the list of entries.", - "name": "read", - "parameters": { - "properties": { - "limit": { - "description": "Maximum number of lines to read", - "type": "number" - }, - "offset": { - "description": "Line number to start from (1-indexed)", - "type": "number" - }, - "path": { - "description": "Path to the file to read", - "type": "string" - } - }, - "required": ["path"], - "type": "object" - }, - "strict": false, - "type": "function" - }, - { - "description": "Write content to a file. Creates the file and parent directories if they do not exist.", - "name": "write", - "parameters": { - "properties": { - "content": { - "description": "Content to write to the file", - "type": "string" - }, - "path": { - "description": "Path to the file to write", - "type": "string" - } - }, - "required": ["path", "content"], - "type": "object" - }, - "strict": false, - "type": "function" - }, - { - "description": "Edit a file using exact text replacement. The oldText must match a unique region of the file. Use replaceAll to replace all occurrences.", - "name": "edit", - "parameters": { - "properties": { - "newText": { - "description": "Replacement text", - "type": "string" - }, - "oldText": { - "description": "Exact text to find (must be unique)", - "type": "string" - }, - "path": { - "description": "Path to the file to edit", - "type": "string" - }, - "replaceAll": { - "description": "Replace all occurrences", - "type": "boolean" - } - }, - "required": ["path", "oldText", "newText"], - "type": "object" - }, - "strict": false, - "type": "function" - }, - { - "description": "Execute a bash command. Returns stdout and stderr. Output is truncated to the last 2000 lines or 50KB.", - "name": "bash", - "parameters": { - "properties": { - "command": { - "description": "Bash command to execute", - "type": "string" - }, - "timeout": { - "description": "Timeout in seconds", - "type": "number" - } - }, - "required": ["command"], - "type": "object" - }, - "strict": false, - "type": "function" - }, - { - "description": "Search file contents for a regex pattern. Returns matching lines with file paths and line numbers.", - "name": "grep", - "parameters": { - "properties": { - "include": { - "description": "Glob filter, e.g. \"*.ts\"", - "type": "string" - }, - "path": { - "description": "Directory or file to search (default: .)", - "type": "string" - }, - "pattern": { - "description": "Search pattern (regex)", - "type": "string" - } - }, - "required": ["pattern"], - "type": "object" - }, - "strict": false, - "type": "function" - }, - { - "description": "Find files by filename pattern using shell find -name semantics. Returns matching file paths.", - "name": "glob", - "parameters": { - "properties": { - "path": { - "description": "Directory to search in (default: .)", - "type": "string" - }, - "pattern": { - "description": "Filename pattern, e.g. \"*.ts\"", - "type": "string" - } - }, - "required": ["pattern"], - "type": "object" - }, - "strict": false, - "type": "function" - }, - { - "description": "Delegate a focused task to a detached child agent with its own context. Use this for independent research, file exploration, or parallel work. The task returns only its final answer to this conversation. No subagents are currently defined.", - "name": "task", - "parameters": { - "properties": { - "agent": { - "description": "Declared subagent to use for the child agent", - "type": "string" - }, - "cwd": { - "description": "Working directory for the child agent. AGENTS.md and skills are discovered from here.", - "type": "string" - }, - "description": { - "description": "Short human-readable label for the delegated work", - "type": "string" - }, - "prompt": { - "description": "Focused instructions for the child agent", - "type": "string" - } - }, - "required": ["prompt"], - "type": "object" - }, - "strict": false, - "type": "function" - } - ] - } - }, - "headers": {}, - "method": "POST", - "url": "https://api.openai.com/v1/responses" - }, - "response": { - "body": { - "chunks": [ - "event: response.created\ndata: {\"type\":\"response.created\",\"response\":{\"id\":\"resp_0f099b3fce0721fd016a705b63235881a393625d6974243e44\",\"object\":\"response\",\"created_at\":1785748323,\"status\":\"in_progress\",\"background\":false,\"completed_at\":null,\"error\":null,\"frequency_penalty\":0,\"incomplete_details\":null,\"instructions\":null,\"max_output_tokens\":null,\"max_tool_calls\":null,\"model\":\"gpt-5.4-nano-2026-03-17\",\"moderation\":null,\"output\":[],\"parallel_tool_calls\":true,\"presence_penalty\":0,\"previous_response_id\":null,\"prompt_cache_key\":\"workflow:instrumentation:01KZ3E9TWTAEK4P4P3F63HKAKC::default::sk\",\"prompt_cache_retention\":\"24h\",\"reasoning\":{\"context\":\"current_turn\",\"effort\":\"none\",\"mode\":\"standard\",\"summary\":null},\"safety_identifier\":null,\"service_tier\":\"auto\",\"store\":false,\"temperature\":1,\"text\":{\"format\":{\"type\":\"text\"},\"verbosity\":\"medium\"},\"tool_choice\":\"auto\",\"tool_usage\":{\"image_gen\":{\"input_tokens\":0,\"input_tokens_details\":{\"image_tokens\":0,\"text_tokens\":0},\"output_tokens\":0,\"output_tokens_details\":{\"image_tokens\":0,\"text_tokens\":0},\"total_tokens\":0},\"web_search\":{\"num_requests\":0}},\"tools\":[{\"type\":\"function\",\"description\":\"Read a file or list a directory. For files, output is truncated to 2000 lines or 50KB — use offset/limit for large files. For directories, returns the list of entries.\",\"name\":\"read\",\"output_schema\":null,\"parameters\":{\"type\":\"object\",\"required\":[\"path\"],\"properties\":{\"path\":{\"type\":\"string\",\"description\":\"Path to the file to read\"},\"offset\":{\"type\":\"number\",\"description\":\"Line number to start from (1-indexed)\"},\"limit\":{\"type\":\"number\",\"description\":\"Maximum number of lines to read\"}}},\"strict\":false},{\"type\":\"function\",\"description\":\"Write content to a file. Creates the file and parent directories if they do not exist.\",\"name\":\"write\",\"output_schema\":null,\"parameters\":{\"type\":\"object\",\"required\":[\"path\",\"content\"],\"properties\":{\"path\":{\"type\":\"string\",\"description\":\"Path to the file to write\"},\"content\":{\"type\":\"string\",\"description\":\"Content to write to the file\"}}},\"strict\":false},{\"type\":\"function\",\"description\":\"Edit a file using exact text replacement. The oldText must match a unique region of the file. Use replaceAll to replace all occurrences.\",\"name\":\"edit\",\"output_schema\":null,\"parameters\":{\"type\":\"object\",\"required\":[\"path\",\"oldText\",\"newText\"],\"properties\":{\"path\":{\"type\":\"string\",\"description\":\"Path to the file to edit\"},\"oldText\":{\"type\":\"string\",\"description\":\"Exact text to find (must be unique)\"},\"newText\":{\"type\":\"string\",\"description\":\"Replacement text\"},\"replaceAll\":{\"type\":\"boolean\",\"description\":\"Replace all occurrences\"}}},\"strict\":false},{\"type\":\"function\",\"description\":\"Execute a bash command. Returns stdout and stderr. Output is truncated to the last 2000 lines or 50KB.\",\"name\":\"bash\",\"output_schema\":null,\"parameters\":{\"type\":\"object\",\"required\":[\"command\"],\"properties\":{\"command\":{\"type\":\"string\",\"description\":\"Bash command to execute\"},\"timeout\":{\"type\":\"number\",\"description\":\"Timeout in seconds\"}}},\"strict\":false},{\"type\":\"function\",\"description\":\"Search file contents for a regex pattern. Returns matching lines with file paths and line numbers.\",\"name\":\"grep\",\"output_schema\":null,\"parameters\":{\"type\":\"object\",\"required\":[\"pattern\"],\"properties\":{\"pattern\":{\"type\":\"string\",\"description\":\"Search pattern (regex)\"},\"path\":{\"type\":\"string\",\"description\":\"Directory or file to search (default: .)\"},\"include\":{\"type\":\"string\",\"description\":\"Glob filter, e.g. \\\"*.ts\\\"\"}}},\"strict\":false},{\"type\":\"function\",\"description\":\"Find files by filename pattern using shell find -name semantics. Returns matching file paths.\",\"name\":\"glob\",\"output_schema\":null,\"parameters\":{\"type\":\"object\",\"required\":[\"pattern\"],\"properties\":{\"pattern\":{\"type\":\"string\",\"description\":\"Filename pattern, e.g. \\\"*.ts\\\"\"},\"path\":{\"type\":\"string\",\"description\":\"Directory to search in (default: .)\"}}},\"strict\":false},{\"type\":\"function\",\"description\":\"Delegate a focused task to a detached child agent with its own context. Use this for independent research, file exploration, or parallel work. The task returns only its final answer to this conversation. No subagents are currently defined.\",\"name\":\"task\",\"output_schema\":null,\"parameters\":{\"type\":\"object\",\"required\":[\"prompt\"],\"properties\":{\"description\":{\"type\":\"string\",\"description\":\"Short human-readable label for the delegated work\"},\"prompt\":{\"type\":\"string\",\"description\":\"Focused instructions for the child agent\"},\"agent\":{\"type\":\"string\",\"description\":\"Declared subagent to use for the child agent\"},\"cwd\":{\"type\":\"string\",\"description\":\"Working directory for the child agent. AGENTS.md and skills are discovered from here.\"}}},\"strict\":false}],\"top_logprobs\":0,\"top_p\":0.98,\"truncation\":\"disabled\",\"usage\":null,\"user\":null,\"metadata\":{}},\"sequence_number\":0}", - "event: response.in_progress\ndata: {\"type\":\"response.in_progress\",\"response\":{\"id\":\"resp_0f099b3fce0721fd016a705b63235881a393625d6974243e44\",\"object\":\"response\",\"created_at\":1785748323,\"status\":\"in_progress\",\"background\":false,\"completed_at\":null,\"error\":null,\"frequency_penalty\":0,\"incomplete_details\":null,\"instructions\":null,\"max_output_tokens\":null,\"max_tool_calls\":null,\"model\":\"gpt-5.4-nano-2026-03-17\",\"moderation\":null,\"output\":[],\"parallel_tool_calls\":true,\"presence_penalty\":0,\"previous_response_id\":null,\"prompt_cache_key\":\"workflow:instrumentation:01KZ3E9TWTAEK4P4P3F63HKAKC::default::sk\",\"prompt_cache_retention\":\"24h\",\"reasoning\":{\"context\":\"current_turn\",\"effort\":\"none\",\"mode\":\"standard\",\"summary\":null},\"safety_identifier\":null,\"service_tier\":\"auto\",\"store\":false,\"temperature\":1,\"text\":{\"format\":{\"type\":\"text\"},\"verbosity\":\"medium\"},\"tool_choice\":\"auto\",\"tool_usage\":{\"image_gen\":{\"input_tokens\":0,\"input_tokens_details\":{\"image_tokens\":0,\"text_tokens\":0},\"output_tokens\":0,\"output_tokens_details\":{\"image_tokens\":0,\"text_tokens\":0},\"total_tokens\":0},\"web_search\":{\"num_requests\":0}},\"tools\":[{\"type\":\"function\",\"description\":\"Read a file or list a directory. For files, output is truncated to 2000 lines or 50KB — use offset/limit for large files. For directories, returns the list of entries.\",\"name\":\"read\",\"output_schema\":null,\"parameters\":{\"type\":\"object\",\"required\":[\"path\"],\"properties\":{\"path\":{\"type\":\"string\",\"description\":\"Path to the file to read\"},\"offset\":{\"type\":\"number\",\"description\":\"Line number to start from (1-indexed)\"},\"limit\":{\"type\":\"number\",\"description\":\"Maximum number of lines to read\"}}},\"strict\":false},{\"type\":\"function\",\"description\":\"Write content to a file. Creates the file and parent directories if they do not exist.\",\"name\":\"write\",\"output_schema\":null,\"parameters\":{\"type\":\"object\",\"required\":[\"path\",\"content\"],\"properties\":{\"path\":{\"type\":\"string\",\"description\":\"Path to the file to write\"},\"content\":{\"type\":\"string\",\"description\":\"Content to write to the file\"}}},\"strict\":false},{\"type\":\"function\",\"description\":\"Edit a file using exact text replacement. The oldText must match a unique region of the file. Use replaceAll to replace all occurrences.\",\"name\":\"edit\",\"output_schema\":null,\"parameters\":{\"type\":\"object\",\"required\":[\"path\",\"oldText\",\"newText\"],\"properties\":{\"path\":{\"type\":\"string\",\"description\":\"Path to the file to edit\"},\"oldText\":{\"type\":\"string\",\"description\":\"Exact text to find (must be unique)\"},\"newText\":{\"type\":\"string\",\"description\":\"Replacement text\"},\"replaceAll\":{\"type\":\"boolean\",\"description\":\"Replace all occurrences\"}}},\"strict\":false},{\"type\":\"function\",\"description\":\"Execute a bash command. Returns stdout and stderr. Output is truncated to the last 2000 lines or 50KB.\",\"name\":\"bash\",\"output_schema\":null,\"parameters\":{\"type\":\"object\",\"required\":[\"command\"],\"properties\":{\"command\":{\"type\":\"string\",\"description\":\"Bash command to execute\"},\"timeout\":{\"type\":\"number\",\"description\":\"Timeout in seconds\"}}},\"strict\":false},{\"type\":\"function\",\"description\":\"Search file contents for a regex pattern. Returns matching lines with file paths and line numbers.\",\"name\":\"grep\",\"output_schema\":null,\"parameters\":{\"type\":\"object\",\"required\":[\"pattern\"],\"properties\":{\"pattern\":{\"type\":\"string\",\"description\":\"Search pattern (regex)\"},\"path\":{\"type\":\"string\",\"description\":\"Directory or file to search (default: .)\"},\"include\":{\"type\":\"string\",\"description\":\"Glob filter, e.g. \\\"*.ts\\\"\"}}},\"strict\":false},{\"type\":\"function\",\"description\":\"Find files by filename pattern using shell find -name semantics. Returns matching file paths.\",\"name\":\"glob\",\"output_schema\":null,\"parameters\":{\"type\":\"object\",\"required\":[\"pattern\"],\"properties\":{\"pattern\":{\"type\":\"string\",\"description\":\"Filename pattern, e.g. \\\"*.ts\\\"\"},\"path\":{\"type\":\"string\",\"description\":\"Directory to search in (default: .)\"}}},\"strict\":false},{\"type\":\"function\",\"description\":\"Delegate a focused task to a detached child agent with its own context. Use this for independent research, file exploration, or parallel work. The task returns only its final answer to this conversation. No subagents are currently defined.\",\"name\":\"task\",\"output_schema\":null,\"parameters\":{\"type\":\"object\",\"required\":[\"prompt\"],\"properties\":{\"description\":{\"type\":\"string\",\"description\":\"Short human-readable label for the delegated work\"},\"prompt\":{\"type\":\"string\",\"description\":\"Focused instructions for the child agent\"},\"agent\":{\"type\":\"string\",\"description\":\"Declared subagent to use for the child agent\"},\"cwd\":{\"type\":\"string\",\"description\":\"Working directory for the child agent. AGENTS.md and skills are discovered from here.\"}}},\"strict\":false}],\"top_logprobs\":0,\"top_p\":0.98,\"truncation\":\"disabled\",\"usage\":null,\"user\":null,\"metadata\":{}},\"sequence_number\":1}", - "event: response.output_item.added\ndata: {\"type\":\"response.output_item.added\",\"item\":{\"id\":\"fc_0f099b3fce0721fd016a705b63c99081a3af4dfbf2ce0314eb\",\"type\":\"function_call\",\"status\":\"in_progress\",\"arguments\":\"\",\"call_id\":\"call_R2a5LOml0NZqq5XTcTCKfbMW\",\"name\":\"task\"},\"output_index\":0,\"sequence_number\":2}", - "event: response.function_call_arguments.delta\ndata: {\"type\":\"response.function_call_arguments.delta\",\"delta\":\"{\\\"\",\"item_id\":\"fc_0f099b3fce0721fd016a705b63c99081a3af4dfbf2ce0314eb\",\"obfuscation\":\"CbHdtJmEy68R82\",\"output_index\":0,\"sequence_number\":3}", - "event: response.function_call_arguments.delta\ndata: {\"type\":\"response.function_call_arguments.delta\",\"delta\":\"description\",\"item_id\":\"fc_0f099b3fce0721fd016a705b63c99081a3af4dfbf2ce0314eb\",\"obfuscation\":\"N0n5J\",\"output_index\":0,\"sequence_number\":4}", - "event: response.function_call_arguments.delta\ndata: {\"type\":\"response.function_call_arguments.delta\",\"delta\":\"\\\":\\\"\",\"item_id\":\"fc_0f099b3fce0721fd016a705b63c99081a3af4dfbf2ce0314eb\",\"obfuscation\":\"oVS9emhElTbAe\",\"output_index\":0,\"sequence_number\":5}", - "event: response.function_call_arguments.delta\ndata: {\"type\":\"response.function_call_arguments.delta\",\"delta\":\"Run\",\"item_id\":\"fc_0f099b3fce0721fd016a705b63c99081a3af4dfbf2ce0314eb\",\"obfuscation\":\"44SOZV25Sx36v\",\"output_index\":0,\"sequence_number\":6}", - "event: response.function_call_arguments.delta\ndata: {\"type\":\"response.function_call_arguments.delta\",\"delta\":\" the\",\"item_id\":\"fc_0f099b3fce0721fd016a705b63c99081a3af4dfbf2ce0314eb\",\"obfuscation\":\"5rEAHjD7s9AY\",\"output_index\":0,\"sequence_number\":7}", - "event: response.function_call_arguments.delta\ndata: {\"type\":\"response.function_call_arguments.delta\",\"delta\":\" deterministic\",\"item_id\":\"fc_0f099b3fce0721fd016a705b63c99081a3af4dfbf2ce0314eb\",\"obfuscation\":\"XS\",\"output_index\":0,\"sequence_number\":8}", - "event: response.function_call_arguments.delta\ndata: {\"type\":\"response.function_call_arguments.delta\",\"delta\":\" Fl\",\"item_id\":\"fc_0f099b3fce0721fd016a705b63c99081a3af4dfbf2ce0314eb\",\"obfuscation\":\"M9xvWA7f348PV\",\"output_index\":0,\"sequence_number\":9}", - "event: response.function_call_arguments.delta\ndata: {\"type\":\"response.function_call_arguments.delta\",\"delta\":\"ue\",\"item_id\":\"fc_0f099b3fce0721fd016a705b63c99081a3af4dfbf2ce0314eb\",\"obfuscation\":\"Q6wOJG5PkJoZVd\",\"output_index\":0,\"sequence_number\":10}", - "event: response.function_call_arguments.delta\ndata: {\"type\":\"response.function_call_arguments.delta\",\"delta\":\" instrumentation\",\"item_id\":\"fc_0f099b3fce0721fd016a705b63c99081a3af4dfbf2ce0314eb\",\"obfuscation\":\"\",\"output_index\":0,\"sequence_number\":11}", - "event: response.function_call_arguments.delta\ndata: {\"type\":\"response.function_call_arguments.delta\",\"delta\":\" e\",\"item_id\":\"fc_0f099b3fce0721fd016a705b63c99081a3af4dfbf2ce0314eb\",\"obfuscation\":\"YPWsSbID38RZ8M\",\"output_index\":0,\"sequence_number\":12}", - "event: response.function_call_arguments.delta\ndata: {\"type\":\"response.function_call_arguments.delta\",\"delta\":\"2\",\"item_id\":\"fc_0f099b3fce0721fd016a705b63c99081a3af4dfbf2ce0314eb\",\"obfuscation\":\"DoKfuME0Em0z2CG\",\"output_index\":0,\"sequence_number\":13}", - "event: response.function_call_arguments.delta\ndata: {\"type\":\"response.function_call_arguments.delta\",\"delta\":\"e\",\"item_id\":\"fc_0f099b3fce0721fd016a705b63c99081a3af4dfbf2ce0314eb\",\"obfuscation\":\"XBFbIY4Fow2p3wz\",\"output_index\":0,\"sequence_number\":14}", - "event: response.function_call_arguments.delta\ndata: {\"type\":\"response.function_call_arguments.delta\",\"delta\":\" test\",\"item_id\":\"fc_0f099b3fce0721fd016a705b63c99081a3af4dfbf2ce0314eb\",\"obfuscation\":\"JX98m2RkrWD\",\"output_index\":0,\"sequence_number\":15}", - "event: response.function_call_arguments.delta\ndata: {\"type\":\"response.function_call_arguments.delta\",\"delta\":\" skill\",\"item_id\":\"fc_0f099b3fce0721fd016a705b63c99081a3af4dfbf2ce0314eb\",\"obfuscation\":\"yWTHn4IL3k\",\"output_index\":0,\"sequence_number\":16}", - "event: response.function_call_arguments.delta\ndata: {\"type\":\"response.function_call_arguments.delta\",\"delta\":\" e\",\"item_id\":\"fc_0f099b3fce0721fd016a705b63c99081a3af4dfbf2ce0314eb\",\"obfuscation\":\"tJChmRwpGkHi7f\",\"output_index\":0,\"sequence_number\":17}", - "event: response.function_call_arguments.delta\ndata: {\"type\":\"response.function_call_arguments.delta\",\"delta\":\"2\",\"item_id\":\"fc_0f099b3fce0721fd016a705b63c99081a3af4dfbf2ce0314eb\",\"obfuscation\":\"59X6X9YSOr0tsbm\",\"output_index\":0,\"sequence_number\":18}", - "event: response.function_call_arguments.delta\ndata: {\"type\":\"response.function_call_arguments.delta\",\"delta\":\"e\",\"item_id\":\"fc_0f099b3fce0721fd016a705b63c99081a3af4dfbf2ce0314eb\",\"obfuscation\":\"YX8fGT8PwVkyRUz\",\"output_index\":0,\"sequence_number\":19}", - "event: response.function_call_arguments.delta\ndata: {\"type\":\"response.function_call_arguments.delta\",\"delta\":\"-fl\",\"item_id\":\"fc_0f099b3fce0721fd016a705b63c99081a3af4dfbf2ce0314eb\",\"obfuscation\":\"2vD42kigSA1dk\",\"output_index\":0,\"sequence_number\":20}", - "event: response.function_call_arguments.delta\ndata: {\"type\":\"response.function_call_arguments.delta\",\"delta\":\"ue\",\"item_id\":\"fc_0f099b3fce0721fd016a705b63c99081a3af4dfbf2ce0314eb\",\"obfuscation\":\"SR8lnZvAOFNlAd\",\"output_index\":0,\"sequence_number\":21}", - "event: response.function_call_arguments.delta\ndata: {\"type\":\"response.function_call_arguments.delta\",\"delta\":\"-s\",\"item_id\":\"fc_0f099b3fce0721fd016a705b63c99081a3af4dfbf2ce0314eb\",\"obfuscation\":\"e5LCrEk4SxTUxd\",\"output_index\":0,\"sequence_number\":22}", - "event: response.function_call_arguments.delta\ndata: {\"type\":\"response.function_call_arguments.delta\",\"delta\":\"kill\",\"item_id\":\"fc_0f099b3fce0721fd016a705b63c99081a3af4dfbf2ce0314eb\",\"obfuscation\":\"yQv5KXABVNyh\",\"output_index\":0,\"sequence_number\":23}", - "event: response.function_call_arguments.delta\ndata: {\"type\":\"response.function_call_arguments.delta\",\"delta\":\".\\\",\\\"\",\"item_id\":\"fc_0f099b3fce0721fd016a705b63c99081a3af4dfbf2ce0314eb\",\"obfuscation\":\"3SJXRzWCr6dy\",\"output_index\":0,\"sequence_number\":24}", - "event: response.function_call_arguments.delta\ndata: {\"type\":\"response.function_call_arguments.delta\",\"delta\":\"prompt\",\"item_id\":\"fc_0f099b3fce0721fd016a705b63c99081a3af4dfbf2ce0314eb\",\"obfuscation\":\"jnnoMIl9VV\",\"output_index\":0,\"sequence_number\":25}", - "event: response.function_call_arguments.delta\ndata: {\"type\":\"response.function_call_arguments.delta\",\"delta\":\"\\\":\\\"\",\"item_id\":\"fc_0f099b3fce0721fd016a705b63c99081a3af4dfbf2ce0314eb\",\"obfuscation\":\"cbOKoZJQXGHZv\",\"output_index\":0,\"sequence_number\":26}", - "event: response.function_call_arguments.delta\ndata: {\"type\":\"response.function_call_arguments.delta\",\"delta\":\"Run\",\"item_id\":\"fc_0f099b3fce0721fd016a705b63c99081a3af4dfbf2ce0314eb\",\"obfuscation\":\"lOwODhO09Mw2T\",\"output_index\":0,\"sequence_number\":27}", - "event: response.function_call_arguments.delta\ndata: {\"type\":\"response.function_call_arguments.delta\",\"delta\":\" the\",\"item_id\":\"fc_0f099b3fce0721fd016a705b63c99081a3af4dfbf2ce0314eb\",\"obfuscation\":\"HJKMWHVzo3e8\",\"output_index\":0,\"sequence_number\":28}", - "event: response.function_call_arguments.delta\ndata: {\"type\":\"response.function_call_arguments.delta\",\"delta\":\" skill\",\"item_id\":\"fc_0f099b3fce0721fd016a705b63c99081a3af4dfbf2ce0314eb\",\"obfuscation\":\"pfSw7BP6rF\",\"output_index\":0,\"sequence_number\":29}", - "event: response.function_call_arguments.delta\ndata: {\"type\":\"response.function_call_arguments.delta\",\"delta\":\" named\",\"item_id\":\"fc_0f099b3fce0721fd016a705b63c99081a3af4dfbf2ce0314eb\",\"obfuscation\":\"gdjMLdp6nl\",\"output_index\":0,\"sequence_number\":30}", - "event: response.function_call_arguments.delta\ndata: {\"type\":\"response.function_call_arguments.delta\",\"delta\":\" \\\\\\\"\",\"item_id\":\"fc_0f099b3fce0721fd016a705b63c99081a3af4dfbf2ce0314eb\",\"obfuscation\":\"hb129i9TWIbbM\",\"output_index\":0,\"sequence_number\":31}", - "event: response.function_call_arguments.delta\ndata: {\"type\":\"response.function_call_arguments.delta\",\"delta\":\"e\",\"item_id\":\"fc_0f099b3fce0721fd016a705b63c99081a3af4dfbf2ce0314eb\",\"obfuscation\":\"EUDmWBk8oSvNHEU\",\"output_index\":0,\"sequence_number\":32}", - "event: response.function_call_arguments.delta\ndata: {\"type\":\"response.function_call_arguments.delta\",\"delta\":\"2\",\"item_id\":\"fc_0f099b3fce0721fd016a705b63c99081a3af4dfbf2ce0314eb\",\"obfuscation\":\"PkeI32czJqwmlmr\",\"output_index\":0,\"sequence_number\":33}", - "event: response.function_call_arguments.delta\ndata: {\"type\":\"response.function_call_arguments.delta\",\"delta\":\"e\",\"item_id\":\"fc_0f099b3fce0721fd016a705b63c99081a3af4dfbf2ce0314eb\",\"obfuscation\":\"H2UQSRf8sMZNRBG\",\"output_index\":0,\"sequence_number\":34}", - "event: response.function_call_arguments.delta\ndata: {\"type\":\"response.function_call_arguments.delta\",\"delta\":\"-fl\",\"item_id\":\"fc_0f099b3fce0721fd016a705b63c99081a3af4dfbf2ce0314eb\",\"obfuscation\":\"coZw5rkSM9llJ\",\"output_index\":0,\"sequence_number\":35}", - "event: response.function_call_arguments.delta\ndata: {\"type\":\"response.function_call_arguments.delta\",\"delta\":\"ue\",\"item_id\":\"fc_0f099b3fce0721fd016a705b63c99081a3af4dfbf2ce0314eb\",\"obfuscation\":\"RTDrspHm2QXfY9\",\"output_index\":0,\"sequence_number\":36}", - "event: response.function_call_arguments.delta\ndata: {\"type\":\"response.function_call_arguments.delta\",\"delta\":\"-s\",\"item_id\":\"fc_0f099b3fce0721fd016a705b63c99081a3af4dfbf2ce0314eb\",\"obfuscation\":\"dhLU5GXYLswOhi\",\"output_index\":0,\"sequence_number\":37}", - "event: response.function_call_arguments.delta\ndata: {\"type\":\"response.function_call_arguments.delta\",\"delta\":\"kill\",\"item_id\":\"fc_0f099b3fce0721fd016a705b63c99081a3af4dfbf2ce0314eb\",\"obfuscation\":\"PNAPCNmx4QJp\",\"output_index\":0,\"sequence_number\":38}", - "event: response.function_call_arguments.delta\ndata: {\"type\":\"response.function_call_arguments.delta\",\"delta\":\"\\\\\\\"\",\"item_id\":\"fc_0f099b3fce0721fd016a705b63c99081a3af4dfbf2ce0314eb\",\"obfuscation\":\"CpbMQHjxstrcsu\",\"output_index\":0,\"sequence_number\":39}", - "event: response.function_call_arguments.delta\ndata: {\"type\":\"response.function_call_arguments.delta\",\"delta\":\" with\",\"item_id\":\"fc_0f099b3fce0721fd016a705b63c99081a3af4dfbf2ce0314eb\",\"obfuscation\":\"KOoqv80ko3G\",\"output_index\":0,\"sequence_number\":40}", - "event: response.function_call_arguments.delta\ndata: {\"type\":\"response.function_call_arguments.delta\",\"delta\":\" arguments\",\"item_id\":\"fc_0f099b3fce0721fd016a705b63c99081a3af4dfbf2ce0314eb\",\"obfuscation\":\"jcYH3O\",\"output_index\":0,\"sequence_number\":41}", - "event: response.function_call_arguments.delta\ndata: {\"type\":\"response.function_call_arguments.delta\",\"delta\":\" {\",\"item_id\":\"fc_0f099b3fce0721fd016a705b63c99081a3af4dfbf2ce0314eb\",\"obfuscation\":\"szXf5lZHyCpilh\",\"output_index\":0,\"sequence_number\":42}", - "event: response.function_call_arguments.delta\ndata: {\"type\":\"response.function_call_arguments.delta\",\"delta\":\"\\\\\\\"\",\"item_id\":\"fc_0f099b3fce0721fd016a705b63c99081a3af4dfbf2ce0314eb\",\"obfuscation\":\"52sGZR8YVknFj3\",\"output_index\":0,\"sequence_number\":43}", - "event: response.function_call_arguments.delta\ndata: {\"type\":\"response.function_call_arguments.delta\",\"delta\":\"marker\",\"item_id\":\"fc_0f099b3fce0721fd016a705b63c99081a3af4dfbf2ce0314eb\",\"obfuscation\":\"J8ZugJCeBJ\",\"output_index\":0,\"sequence_number\":44}", - "event: response.function_call_arguments.delta\ndata: {\"type\":\"response.function_call_arguments.delta\",\"delta\":\"\\\\\\\":\\\\\\\"\",\"item_id\":\"fc_0f099b3fce0721fd016a705b63c99081a3af4dfbf2ce0314eb\",\"obfuscation\":\"2Ffk7q5BGoV\",\"output_index\":0,\"sequence_number\":45}", - "event: response.function_call_arguments.delta\ndata: {\"type\":\"response.function_call_arguments.delta\",\"delta\":\"SK\",\"item_id\":\"fc_0f099b3fce0721fd016a705b63c99081a3af4dfbf2ce0314eb\",\"obfuscation\":\"xeKflxURNDoVyb\",\"output_index\":0,\"sequence_number\":46}", - "event: response.function_call_arguments.delta\ndata: {\"type\":\"response.function_call_arguments.delta\",\"delta\":\"ILL\",\"item_id\":\"fc_0f099b3fce0721fd016a705b63c99081a3af4dfbf2ce0314eb\",\"obfuscation\":\"XjKJ0bhJcewlH\",\"output_index\":0,\"sequence_number\":47}", - "event: response.function_call_arguments.delta\ndata: {\"type\":\"response.function_call_arguments.delta\",\"delta\":\"_DONE\",\"item_id\":\"fc_0f099b3fce0721fd016a705b63c99081a3af4dfbf2ce0314eb\",\"obfuscation\":\"R4FhmYdoiCO\",\"output_index\":0,\"sequence_number\":48}", - "event: response.function_call_arguments.delta\ndata: {\"type\":\"response.function_call_arguments.delta\",\"delta\":\"\\\\\\\"\",\"item_id\":\"fc_0f099b3fce0721fd016a705b63c99081a3af4dfbf2ce0314eb\",\"obfuscation\":\"X6XdGVk1CC6NjY\",\"output_index\":0,\"sequence_number\":49}", - "event: response.function_call_arguments.delta\ndata: {\"type\":\"response.function_call_arguments.delta\",\"delta\":\"}.\",\"item_id\":\"fc_0f099b3fce0721fd016a705b63c99081a3af4dfbf2ce0314eb\",\"obfuscation\":\"YYRBPkT64KLrlE\",\"output_index\":0,\"sequence_number\":50}", - "event: response.function_call_arguments.delta\ndata: {\"type\":\"response.function_call_arguments.delta\",\"delta\":\" Output\",\"item_id\":\"fc_0f099b3fce0721fd016a705b63c99081a3af4dfbf2ce0314eb\",\"obfuscation\":\"tzFy24CSr\",\"output_index\":0,\"sequence_number\":51}", - "event: response.function_call_arguments.delta\ndata: {\"type\":\"response.function_call_arguments.delta\",\"delta\":\" only\",\"item_id\":\"fc_0f099b3fce0721fd016a705b63c99081a3af4dfbf2ce0314eb\",\"obfuscation\":\"rf2kD3jJIfw\",\"output_index\":0,\"sequence_number\":52}", - "event: response.function_call_arguments.delta\ndata: {\"type\":\"response.function_call_arguments.delta\",\"delta\":\" the\",\"item_id\":\"fc_0f099b3fce0721fd016a705b63c99081a3af4dfbf2ce0314eb\",\"obfuscation\":\"MZEAVTJ1ktEY\",\"output_index\":0,\"sequence_number\":53}", - "event: response.function_call_arguments.delta\ndata: {\"type\":\"response.function_call_arguments.delta\",\"delta\":\" marker\",\"item_id\":\"fc_0f099b3fce0721fd016a705b63c99081a3af4dfbf2ce0314eb\",\"obfuscation\":\"hsFGNt1CH\",\"output_index\":0,\"sequence_number\":54}", - "event: response.function_call_arguments.delta\ndata: {\"type\":\"response.function_call_arguments.delta\",\"delta\":\" and\",\"item_id\":\"fc_0f099b3fce0721fd016a705b63c99081a3af4dfbf2ce0314eb\",\"obfuscation\":\"7jo0OqDghkTU\",\"output_index\":0,\"sequence_number\":55}", - "event: response.function_call_arguments.delta\ndata: {\"type\":\"response.function_call_arguments.delta\",\"delta\":\" no\",\"item_id\":\"fc_0f099b3fce0721fd016a705b63c99081a3af4dfbf2ce0314eb\",\"obfuscation\":\"x7Q4KF2sTB6xe\",\"output_index\":0,\"sequence_number\":56}", - "event: response.function_call_arguments.delta\ndata: {\"type\":\"response.function_call_arguments.delta\",\"delta\":\" extra\",\"item_id\":\"fc_0f099b3fce0721fd016a705b63c99081a3af4dfbf2ce0314eb\",\"obfuscation\":\"5lWyL7VcaY\",\"output_index\":0,\"sequence_number\":57}", - "event: response.function_call_arguments.delta\ndata: {\"type\":\"response.function_call_arguments.delta\",\"delta\":\" text\",\"item_id\":\"fc_0f099b3fce0721fd016a705b63c99081a3af4dfbf2ce0314eb\",\"obfuscation\":\"P6M5oge56Cp\",\"output_index\":0,\"sequence_number\":58}", - "event: response.function_call_arguments.delta\ndata: {\"type\":\"response.function_call_arguments.delta\",\"delta\":\" when\",\"item_id\":\"fc_0f099b3fce0721fd016a705b63c99081a3af4dfbf2ce0314eb\",\"obfuscation\":\"aSZOArmUWCv\",\"output_index\":0,\"sequence_number\":59}", - "event: response.function_call_arguments.delta\ndata: {\"type\":\"response.function_call_arguments.delta\",\"delta\":\" finished\",\"item_id\":\"fc_0f099b3fce0721fd016a705b63c99081a3af4dfbf2ce0314eb\",\"obfuscation\":\"Bcwcv1j\",\"output_index\":0,\"sequence_number\":60}", - "event: response.function_call_arguments.delta\ndata: {\"type\":\"response.function_call_arguments.delta\",\"delta\":\".\\\",\\\"\",\"item_id\":\"fc_0f099b3fce0721fd016a705b63c99081a3af4dfbf2ce0314eb\",\"obfuscation\":\"QPfZWJIaOZZK\",\"output_index\":0,\"sequence_number\":61}", - "event: response.function_call_arguments.delta\ndata: {\"type\":\"response.function_call_arguments.delta\",\"delta\":\"cwd\",\"item_id\":\"fc_0f099b3fce0721fd016a705b63c99081a3af4dfbf2ce0314eb\",\"obfuscation\":\"8CNMuVP6JkXXp\",\"output_index\":0,\"sequence_number\":62}", - "event: response.function_call_arguments.delta\ndata: {\"type\":\"response.function_call_arguments.delta\",\"delta\":\"\\\":\",\"item_id\":\"fc_0f099b3fce0721fd016a705b63c99081a3af4dfbf2ce0314eb\",\"obfuscation\":\"Zyun5ONtg0VES4\",\"output_index\":0,\"sequence_number\":63}", - "event: response.function_call_arguments.delta\ndata: {\"type\":\"response.function_call_arguments.delta\",\"delta\":\"\\\"/\",\"item_id\":\"fc_0f099b3fce0721fd016a705b63c99081a3af4dfbf2ce0314eb\",\"obfuscation\":\"0hQ1Tpvy40v6ov\",\"output_index\":0,\"sequence_number\":64}", - "event: response.function_call_arguments.delta\ndata: {\"type\":\"response.function_call_arguments.delta\",\"delta\":\"ver\",\"item_id\":\"fc_0f099b3fce0721fd016a705b63c99081a3af4dfbf2ce0314eb\",\"obfuscation\":\"AX7KPkq2wczcX\",\"output_index\":0,\"sequence_number\":65}", - "event: response.function_call_arguments.delta\ndata: {\"type\":\"response.function_call_arguments.delta\",\"delta\":\"cel\",\"item_id\":\"fc_0f099b3fce0721fd016a705b63c99081a3af4dfbf2ce0314eb\",\"obfuscation\":\"gqTRHNnYCqD98\",\"output_index\":0,\"sequence_number\":66}", - "event: response.function_call_arguments.delta\ndata: {\"type\":\"response.function_call_arguments.delta\",\"delta\":\"/s\",\"item_id\":\"fc_0f099b3fce0721fd016a705b63c99081a3af4dfbf2ce0314eb\",\"obfuscation\":\"gkSS5rmctsBm7v\",\"output_index\":0,\"sequence_number\":67}", - "event: response.function_call_arguments.delta\ndata: {\"type\":\"response.function_call_arguments.delta\",\"delta\":\"andbox\",\"item_id\":\"fc_0f099b3fce0721fd016a705b63c99081a3af4dfbf2ce0314eb\",\"obfuscation\":\"Mfu7ocBSyP\",\"output_index\":0,\"sequence_number\":68}", - "event: response.function_call_arguments.delta\ndata: {\"type\":\"response.function_call_arguments.delta\",\"delta\":\"/\",\"item_id\":\"fc_0f099b3fce0721fd016a705b63c99081a3af4dfbf2ce0314eb\",\"obfuscation\":\"RYfnUiUEM7nSWVO\",\"output_index\":0,\"sequence_number\":69}", - "event: response.function_call_arguments.delta\ndata: {\"type\":\"response.function_call_arguments.delta\",\"delta\":\"brain\",\"item_id\":\"fc_0f099b3fce0721fd016a705b63c99081a3af4dfbf2ce0314eb\",\"obfuscation\":\"9TJqEA9kGKQ\",\"output_index\":0,\"sequence_number\":70}", - "event: response.function_call_arguments.delta\ndata: {\"type\":\"response.function_call_arguments.delta\",\"delta\":\"trust\",\"item_id\":\"fc_0f099b3fce0721fd016a705b63c99081a3af4dfbf2ce0314eb\",\"obfuscation\":\"lYlMUrHuRU3\",\"output_index\":0,\"sequence_number\":71}", - "event: response.function_call_arguments.delta\ndata: {\"type\":\"response.function_call_arguments.delta\",\"delta\":\"data\",\"item_id\":\"fc_0f099b3fce0721fd016a705b63c99081a3af4dfbf2ce0314eb\",\"obfuscation\":\"7rcyz6emzCww\",\"output_index\":0,\"sequence_number\":72}", - "event: response.function_call_arguments.delta\ndata: {\"type\":\"response.function_call_arguments.delta\",\"delta\":\"/\",\"item_id\":\"fc_0f099b3fce0721fd016a705b63c99081a3af4dfbf2ce0314eb\",\"obfuscation\":\"iuArNS0owWYS7ks\",\"output_index\":0,\"sequence_number\":73}", - "event: response.function_call_arguments.delta\ndata: {\"type\":\"response.function_call_arguments.delta\",\"delta\":\"brain\",\"item_id\":\"fc_0f099b3fce0721fd016a705b63c99081a3af4dfbf2ce0314eb\",\"obfuscation\":\"aO4xFHIWApy\",\"output_index\":0,\"sequence_number\":74}", - "event: response.function_call_arguments.delta\ndata: {\"type\":\"response.function_call_arguments.delta\",\"delta\":\"trust\",\"item_id\":\"fc_0f099b3fce0721fd016a705b63c99081a3af4dfbf2ce0314eb\",\"obfuscation\":\"MJOzy2JsM8I\",\"output_index\":0,\"sequence_number\":75}", - "event: response.function_call_arguments.delta\ndata: {\"type\":\"response.function_call_arguments.delta\",\"delta\":\"-sdk\",\"item_id\":\"fc_0f099b3fce0721fd016a705b63c99081a3af4dfbf2ce0314eb\",\"obfuscation\":\"sO3CIxyDjZko\",\"output_index\":0,\"sequence_number\":76}", - "event: response.function_call_arguments.delta\ndata: {\"type\":\"response.function_call_arguments.delta\",\"delta\":\"-j\",\"item_id\":\"fc_0f099b3fce0721fd016a705b63c99081a3af4dfbf2ce0314eb\",\"obfuscation\":\"NXKXqePfpHdojz\",\"output_index\":0,\"sequence_number\":77}", - "event: response.function_call_arguments.delta\ndata: {\"type\":\"response.function_call_arguments.delta\",\"delta\":\"avascript\",\"item_id\":\"fc_0f099b3fce0721fd016a705b63c99081a3af4dfbf2ce0314eb\",\"obfuscation\":\"r2Ux15z\",\"output_index\":0,\"sequence_number\":78}", - "event: response.function_call_arguments.delta\ndata: {\"type\":\"response.function_call_arguments.delta\",\"delta\":\"/e\",\"item_id\":\"fc_0f099b3fce0721fd016a705b63c99081a3af4dfbf2ce0314eb\",\"obfuscation\":\"Kab8AO35yKTKmS\",\"output_index\":0,\"sequence_number\":79}", - "event: response.function_call_arguments.delta\ndata: {\"type\":\"response.function_call_arguments.delta\",\"delta\":\"2\",\"item_id\":\"fc_0f099b3fce0721fd016a705b63c99081a3af4dfbf2ce0314eb\",\"obfuscation\":\"tIH5zoiqj9CTjyo\",\"output_index\":0,\"sequence_number\":80}", - "event: response.function_call_arguments.delta\ndata: {\"type\":\"response.function_call_arguments.delta\",\"delta\":\"e\",\"item_id\":\"fc_0f099b3fce0721fd016a705b63c99081a3af4dfbf2ce0314eb\",\"obfuscation\":\"iLppUWbjIW0Alm6\",\"output_index\":0,\"sequence_number\":81}", - "event: response.function_call_arguments.delta\ndata: {\"type\":\"response.function_call_arguments.delta\",\"delta\":\"/.\",\"item_id\":\"fc_0f099b3fce0721fd016a705b63c99081a3af4dfbf2ce0314eb\",\"obfuscation\":\"eBh1cQtXPmFWjX\",\"output_index\":0,\"sequence_number\":82}", - "event: response.function_call_arguments.delta\ndata: {\"type\":\"response.function_call_arguments.delta\",\"delta\":\"bt\",\"item_id\":\"fc_0f099b3fce0721fd016a705b63c99081a3af4dfbf2ce0314eb\",\"obfuscation\":\"ejJv2wp49FgCgw\",\"output_index\":0,\"sequence_number\":83}", - "event: response.function_call_arguments.delta\ndata: {\"type\":\"response.function_call_arguments.delta\",\"delta\":\"-t\",\"item_id\":\"fc_0f099b3fce0721fd016a705b63c99081a3af4dfbf2ce0314eb\",\"obfuscation\":\"Pe5APONBPL5Cz5\",\"output_index\":0,\"sequence_number\":84}", - "event: response.function_call_arguments.delta\ndata: {\"type\":\"response.function_call_arguments.delta\",\"delta\":\"mp\",\"item_id\":\"fc_0f099b3fce0721fd016a705b63c99081a3af4dfbf2ce0314eb\",\"obfuscation\":\"8L98Ly29NFwgR8\",\"output_index\":0,\"sequence_number\":85}", - "event: response.function_call_arguments.delta\ndata: {\"type\":\"response.function_call_arguments.delta\",\"delta\":\"/run\",\"item_id\":\"fc_0f099b3fce0721fd016a705b63c99081a3af4dfbf2ce0314eb\",\"obfuscation\":\"zhXouMXCcpBH\",\"output_index\":0,\"sequence_number\":86}", - "event: response.function_call_arguments.delta\ndata: {\"type\":\"response.function_call_arguments.delta\",\"delta\":\"-\",\"item_id\":\"fc_0f099b3fce0721fd016a705b63c99081a3af4dfbf2ce0314eb\",\"obfuscation\":\"tfHJ3cnyfuchLJx\",\"output_index\":0,\"sequence_number\":87}", - "event: response.function_call_arguments.delta\ndata: {\"type\":\"response.function_call_arguments.delta\",\"delta\":\"9\",\"item_id\":\"fc_0f099b3fce0721fd016a705b63c99081a3af4dfbf2ce0314eb\",\"obfuscation\":\"Dj1UH61VeDE9CVJ\",\"output_index\":0,\"sequence_number\":88}", - "event: response.function_call_arguments.delta\ndata: {\"type\":\"response.function_call_arguments.delta\",\"delta\":\"sp\",\"item_id\":\"fc_0f099b3fce0721fd016a705b63c99081a3af4dfbf2ce0314eb\",\"obfuscation\":\"mEcW9oStLYq57Z\",\"output_index\":0,\"sequence_number\":89}", - "event: response.function_call_arguments.delta\ndata: {\"type\":\"response.function_call_arguments.delta\",\"delta\":\"u\",\"item_id\":\"fc_0f099b3fce0721fd016a705b63c99081a3af4dfbf2ce0314eb\",\"obfuscation\":\"da4hpEx6UsGmIdX\",\"output_index\":0,\"sequence_number\":90}", - "event: response.function_call_arguments.delta\ndata: {\"type\":\"response.function_call_arguments.delta\",\"delta\":\"Hg\",\"item_id\":\"fc_0f099b3fce0721fd016a705b63c99081a3af4dfbf2ce0314eb\",\"obfuscation\":\"wZYe5LO1qqwywa\",\"output_index\":0,\"sequence_number\":91}", - "event: response.function_call_arguments.delta\ndata: {\"type\":\"response.function_call_arguments.delta\",\"delta\":\"/sc\",\"item_id\":\"fc_0f099b3fce0721fd016a705b63c99081a3af4dfbf2ce0314eb\",\"obfuscation\":\"FPrjbypLHyxRn\",\"output_index\":0,\"sequence_number\":92}", - "event: response.function_call_arguments.delta\ndata: {\"type\":\"response.function_call_arguments.delta\",\"delta\":\"enarios\",\"item_id\":\"fc_0f099b3fce0721fd016a705b63c99081a3af4dfbf2ce0314eb\",\"obfuscation\":\"pj0NPDTch\",\"output_index\":0,\"sequence_number\":93}", - "event: response.function_call_arguments.delta\ndata: {\"type\":\"response.function_call_arguments.delta\",\"delta\":\"/fl\",\"item_id\":\"fc_0f099b3fce0721fd016a705b63c99081a3af4dfbf2ce0314eb\",\"obfuscation\":\"xd0ux5YOhgezS\",\"output_index\":0,\"sequence_number\":94}", - "event: response.function_call_arguments.delta\ndata: {\"type\":\"response.function_call_arguments.delta\",\"delta\":\"ue\",\"item_id\":\"fc_0f099b3fce0721fd016a705b63c99081a3af4dfbf2ce0314eb\",\"obfuscation\":\"cETdOP2IPG6g1d\",\"output_index\":0,\"sequence_number\":95}", - "event: response.function_call_arguments.delta\ndata: {\"type\":\"response.function_call_arguments.delta\",\"delta\":\"-in\",\"item_id\":\"fc_0f099b3fce0721fd016a705b63c99081a3af4dfbf2ce0314eb\",\"obfuscation\":\"MzOzX2St8G3JA\",\"output_index\":0,\"sequence_number\":96}", - "event: response.function_call_arguments.delta\ndata: {\"type\":\"response.function_call_arguments.delta\",\"delta\":\"strument\",\"item_id\":\"fc_0f099b3fce0721fd016a705b63c99081a3af4dfbf2ce0314eb\",\"obfuscation\":\"TbtAamDa\",\"output_index\":0,\"sequence_number\":97}", - "event: response.function_call_arguments.delta\ndata: {\"type\":\"response.function_call_arguments.delta\",\"delta\":\"ation\",\"item_id\":\"fc_0f099b3fce0721fd016a705b63c99081a3af4dfbf2ce0314eb\",\"obfuscation\":\"CJ8PGzQrtmy\",\"output_index\":0,\"sequence_number\":98}", - "event: response.function_call_arguments.delta\ndata: {\"type\":\"response.function_call_arguments.delta\",\"delta\":\"\\\"}\",\"item_id\":\"fc_0f099b3fce0721fd016a705b63c99081a3af4dfbf2ce0314eb\",\"obfuscation\":\"wrvfq006IzH0mA\",\"output_index\":0,\"sequence_number\":99}", - "event: response.function_call_arguments.done\ndata: {\"type\":\"response.function_call_arguments.done\",\"arguments\":\"{\\\"description\\\":\\\"Run the deterministic Flue instrumentation e2e test skill e2e-flue-skill.\\\",\\\"prompt\\\":\\\"Run the skill named \\\\\\\"e2e-flue-skill\\\\\\\" with arguments {\\\\\\\"marker\\\\\\\":\\\\\\\"SKILL_DONE\\\\\\\"}. Output only the marker and no extra text when finished.\\\",\\\"cwd\\\":\\\".\\\"}\",\"item_id\":\"fc_0f099b3fce0721fd016a705b63c99081a3af4dfbf2ce0314eb\",\"output_index\":0,\"sequence_number\":100}", - "event: response.output_item.done\ndata: {\"type\":\"response.output_item.done\",\"item\":{\"id\":\"fc_0f099b3fce0721fd016a705b63c99081a3af4dfbf2ce0314eb\",\"type\":\"function_call\",\"status\":\"completed\",\"arguments\":\"{\\\"description\\\":\\\"Run the deterministic Flue instrumentation e2e test skill e2e-flue-skill.\\\",\\\"prompt\\\":\\\"Run the skill named \\\\\\\"e2e-flue-skill\\\\\\\" with arguments {\\\\\\\"marker\\\\\\\":\\\\\\\"SKILL_DONE\\\\\\\"}. Output only the marker and no extra text when finished.\\\",\\\"cwd\\\":\\\".\\\"}\",\"call_id\":\"call_R2a5LOml0NZqq5XTcTCKfbMW\",\"name\":\"task\"},\"output_index\":0,\"sequence_number\":101}", - "event: response.completed\ndata: {\"type\":\"response.completed\",\"response\":{\"id\":\"resp_0f099b3fce0721fd016a705b63235881a393625d6974243e44\",\"object\":\"response\",\"created_at\":1785748323,\"status\":\"completed\",\"background\":false,\"completed_at\":1785748324,\"error\":null,\"frequency_penalty\":0,\"incomplete_details\":null,\"instructions\":null,\"max_output_tokens\":null,\"max_tool_calls\":null,\"model\":\"gpt-5.4-nano-2026-03-17\",\"moderation\":null,\"output\":[{\"id\":\"fc_0f099b3fce0721fd016a705b63c99081a3af4dfbf2ce0314eb\",\"type\":\"function_call\",\"status\":\"completed\",\"arguments\":\"{\\\"description\\\":\\\"Run the deterministic Flue instrumentation e2e test skill e2e-flue-skill.\\\",\\\"prompt\\\":\\\"Run the skill named \\\\\\\"e2e-flue-skill\\\\\\\" with arguments {\\\\\\\"marker\\\\\\\":\\\\\\\"SKILL_DONE\\\\\\\"}. Output only the marker and no extra text when finished.\\\",\\\"cwd\\\":\\\".\\\"}\",\"call_id\":\"call_R2a5LOml0NZqq5XTcTCKfbMW\",\"name\":\"task\"}],\"parallel_tool_calls\":true,\"presence_penalty\":0,\"previous_response_id\":null,\"prompt_cache_key\":\"workflow:instrumentation:01KZ3E9TWTAEK4P4P3F63HKAKC::default::sk\",\"prompt_cache_retention\":\"24h\",\"reasoning\":{\"context\":\"current_turn\",\"effort\":\"none\",\"mode\":\"standard\",\"summary\":null},\"safety_identifier\":null,\"service_tier\":\"default\",\"store\":false,\"temperature\":1,\"text\":{\"format\":{\"type\":\"text\"},\"verbosity\":\"medium\"},\"tool_choice\":\"auto\",\"tool_usage\":{\"image_gen\":{\"input_tokens\":0,\"input_tokens_details\":{\"image_tokens\":0,\"text_tokens\":0},\"output_tokens\":0,\"output_tokens_details\":{\"image_tokens\":0,\"text_tokens\":0},\"total_tokens\":0},\"web_search\":{\"num_requests\":0}},\"tools\":[{\"type\":\"function\",\"description\":\"Read a file or list a directory. For files, output is truncated to 2000 lines or 50KB — use offset/limit for large files. For directories, returns the list of entries.\",\"name\":\"read\",\"output_schema\":null,\"parameters\":{\"type\":\"object\",\"required\":[\"path\"],\"properties\":{\"path\":{\"type\":\"string\",\"description\":\"Path to the file to read\"},\"offset\":{\"type\":\"number\",\"description\":\"Line number to start from (1-indexed)\"},\"limit\":{\"type\":\"number\",\"description\":\"Maximum number of lines to read\"}}},\"strict\":false},{\"type\":\"function\",\"description\":\"Write content to a file. Creates the file and parent directories if they do not exist.\",\"name\":\"write\",\"output_schema\":null,\"parameters\":{\"type\":\"object\",\"required\":[\"path\",\"content\"],\"properties\":{\"path\":{\"type\":\"string\",\"description\":\"Path to the file to write\"},\"content\":{\"type\":\"string\",\"description\":\"Content to write to the file\"}}},\"strict\":false},{\"type\":\"function\",\"description\":\"Edit a file using exact text replacement. The oldText must match a unique region of the file. Use replaceAll to replace all occurrences.\",\"name\":\"edit\",\"output_schema\":null,\"parameters\":{\"type\":\"object\",\"required\":[\"path\",\"oldText\",\"newText\"],\"properties\":{\"path\":{\"type\":\"string\",\"description\":\"Path to the file to edit\"},\"oldText\":{\"type\":\"string\",\"description\":\"Exact text to find (must be unique)\"},\"newText\":{\"type\":\"string\",\"description\":\"Replacement text\"},\"replaceAll\":{\"type\":\"boolean\",\"description\":\"Replace all occurrences\"}}},\"strict\":false},{\"type\":\"function\",\"description\":\"Execute a bash command. Returns stdout and stderr. Output is truncated to the last 2000 lines or 50KB.\",\"name\":\"bash\",\"output_schema\":null,\"parameters\":{\"type\":\"object\",\"required\":[\"command\"],\"properties\":{\"command\":{\"type\":\"string\",\"description\":\"Bash command to execute\"},\"timeout\":{\"type\":\"number\",\"description\":\"Timeout in seconds\"}}},\"strict\":false},{\"type\":\"function\",\"description\":\"Search file contents for a regex pattern. Returns matching lines with file paths and line numbers.\",\"name\":\"grep\",\"output_schema\":null,\"parameters\":{\"type\":\"object\",\"required\":[\"pattern\"],\"properties\":{\"pattern\":{\"type\":\"string\",\"description\":\"Search pattern (regex)\"},\"path\":{\"type\":\"string\",\"description\":\"Directory or file to search (default: .)\"},\"include\":{\"type\":\"string\",\"description\":\"Glob filter, e.g. \\\"*.ts\\\"\"}}},\"strict\":false},{\"type\":\"function\",\"description\":\"Find files by filename pattern using shell find -name semantics. Returns matching file paths.\",\"name\":\"glob\",\"output_schema\":null,\"parameters\":{\"type\":\"object\",\"required\":[\"pattern\"],\"properties\":{\"pattern\":{\"type\":\"string\",\"description\":\"Filename pattern, e.g. \\\"*.ts\\\"\"},\"path\":{\"type\":\"string\",\"description\":\"Directory to search in (default: .)\"}}},\"strict\":false},{\"type\":\"function\",\"description\":\"Delegate a focused task to a detached child agent with its own context. Use this for independent research, file exploration, or parallel work. The task returns only its final answer to this conversation. No subagents are currently defined.\",\"name\":\"task\",\"output_schema\":null,\"parameters\":{\"type\":\"object\",\"required\":[\"prompt\"],\"properties\":{\"description\":{\"type\":\"string\",\"description\":\"Short human-readable label for the delegated work\"},\"prompt\":{\"type\":\"string\",\"description\":\"Focused instructions for the child agent\"},\"agent\":{\"type\":\"string\",\"description\":\"Declared subagent to use for the child agent\"},\"cwd\":{\"type\":\"string\",\"description\":\"Working directory for the child agent. AGENTS.md and skills are discovered from here.\"}}},\"strict\":false}],\"top_logprobs\":0,\"top_p\":0.98,\"truncation\":\"disabled\",\"usage\":{\"input_tokens\":847,\"input_tokens_details\":{\"cache_write_tokens\":0,\"cached_tokens\":0},\"output_tokens\":109,\"output_tokens_details\":{\"reasoning_tokens\":0},\"total_tokens\":956},\"user\":null,\"metadata\":{}},\"sequence_number\":102}" - ], - "kind": "sse" - }, - "headers": { - "access-control-expose-headers": "X-Request-ID, CF-Ray, CF-Ray", - "alt-svc": "h3=\":443\"; ma=86400", - "cf-cache-status": "DYNAMIC", - "cf-ray": "a25432cb5fc97bb6-IAD", - "connection": "keep-alive", - "content-type": "text/event-stream; charset=utf-8", - "date": "Mon, 03 Aug 2026 09:12:03 GMT", - "openai-organization": "braintrust-data", - "openai-processing-ms": "206", - "openai-project": "proj_vsCSXafhhByzWOThMrJcZiw9", - "openai-version": "2020-10-01", - "server": "cloudflare", - "set-cookie": "[REDACTED]", - "strict-transport-security": "max-age=31536000; includeSubDomains; preload", - "transfer-encoding": "chunked", - "x-content-type-options": "nosniff", - "x-ratelimit-limit-requests": "30000", - "x-ratelimit-limit-tokens": "180000000", - "x-ratelimit-remaining-requests": "29999", - "x-ratelimit-remaining-tokens": "179998698", - "x-ratelimit-reset-requests": "2ms", - "x-ratelimit-reset-tokens": "0s", - "x-request-id": "req_4f8a543160de4adea11337a977a14713" - }, - "status": 200, - "statusText": "OK" - } - }, - { - "callIndex": 5, - "id": "341675a0a1a88c63", - "matchKey": "POST api.openai.com/v1/responses", - "recordedAt": "2026-08-03T09:12:05.801Z", - "request": { - "body": { - "kind": "json", - "value": { - "input": [ - { - "content": "You are running in headless mode with no human operator. Work autonomously — never ask questions, never wait for user input. Make your best judgment and proceed independently.\n\nYou are a deterministic Flue instrumentation test agent. Follow user instructions exactly. When asked for a marker, output only that marker and no extra text. When running a local skill file, read it yourself and do not delegate it to a task.\n\n## Available Skills\n\nThe following skills provide specialized instructions for specific tasks. When a task matches a skill description, activate that skill before proceeding so its full instructions are loaded. Skill instructions and supporting resources stay lazy until activation or explicit file reads.\n\n- **e2e-flue-skill** — Deterministic skill used by the Braintrust Flue instrumentation e2e test.\n\nDate: Mon, Aug 3, 2026\nWorking directory: .\n\nDirectory structure:\n.agents\n.flue\n.flue-build\n__cassettes__\n__snapshots__\nassertions.ts\ncassette-filter.mjs\nconstants.mjs\ndist\nnode_modules\npackage.json\npnpm-lock.yaml\nscenario.cli.mjs\nscenario.impl.mjs\nscenario.mjs\nscenario.test.ts\nscenario.ts\nscenario.v2.ts\nv2-assertions.ts\nversions", - "role": "developer" - }, - { - "content": [ - { - "text": "Run the skill named \"e2e-flue-skill\" with arguments {\"marker\":\"SKILL_DONE\"}. Output only the marker and no extra text when finished.", - "type": "input_text" - } - ], - "role": "user" - } - ], - "model": "gpt-5.4-nano", - "prompt_cache_key": "[REDACTED]", - "reasoning": { - "effort": "none" - }, - "store": false, - "stream": true, - "tools": [ - { - "description": "Read a file or list a directory. For files, output is truncated to 2000 lines or 50KB — use offset/limit for large files. For directories, returns the list of entries.", - "name": "read", - "parameters": { - "properties": { - "limit": { - "description": "Maximum number of lines to read", - "type": "number" - }, - "offset": { - "description": "Line number to start from (1-indexed)", - "type": "number" - }, - "path": { - "description": "Path to the file to read", - "type": "string" - } - }, - "required": ["path"], - "type": "object" - }, - "strict": false, - "type": "function" - }, - { - "description": "Write content to a file. Creates the file and parent directories if they do not exist.", - "name": "write", - "parameters": { - "properties": { - "content": { - "description": "Content to write to the file", - "type": "string" - }, - "path": { - "description": "Path to the file to write", - "type": "string" - } - }, - "required": ["path", "content"], - "type": "object" - }, - "strict": false, - "type": "function" - }, - { - "description": "Edit a file using exact text replacement. The oldText must match a unique region of the file. Use replaceAll to replace all occurrences.", - "name": "edit", - "parameters": { - "properties": { - "newText": { - "description": "Replacement text", - "type": "string" - }, - "oldText": { - "description": "Exact text to find (must be unique)", - "type": "string" - }, - "path": { - "description": "Path to the file to edit", - "type": "string" - }, - "replaceAll": { - "description": "Replace all occurrences", - "type": "boolean" - } - }, - "required": ["path", "oldText", "newText"], - "type": "object" - }, - "strict": false, - "type": "function" - }, - { - "description": "Execute a bash command. Returns stdout and stderr. Output is truncated to the last 2000 lines or 50KB.", - "name": "bash", - "parameters": { - "properties": { - "command": { - "description": "Bash command to execute", - "type": "string" - }, - "timeout": { - "description": "Timeout in seconds", - "type": "number" - } - }, - "required": ["command"], - "type": "object" - }, - "strict": false, - "type": "function" - }, - { - "description": "Search file contents for a regex pattern. Returns matching lines with file paths and line numbers.", - "name": "grep", - "parameters": { - "properties": { - "include": { - "description": "Glob filter, e.g. \"*.ts\"", - "type": "string" - }, - "path": { - "description": "Directory or file to search (default: .)", - "type": "string" - }, - "pattern": { - "description": "Search pattern (regex)", - "type": "string" - } - }, - "required": ["pattern"], - "type": "object" - }, - "strict": false, - "type": "function" - }, - { - "description": "Find files by filename pattern using shell find -name semantics. Returns matching file paths.", - "name": "glob", - "parameters": { - "properties": { - "path": { - "description": "Directory to search in (default: .)", - "type": "string" - }, - "pattern": { - "description": "Filename pattern, e.g. \"*.ts\"", - "type": "string" - } - }, - "required": ["pattern"], - "type": "object" - }, - "strict": false, - "type": "function" - }, - { - "description": "Delegate a focused task to a detached child agent with its own context. Use this for independent research, file exploration, or parallel work. The task returns only its final answer to this conversation. No subagents are currently defined.", - "name": "task", - "parameters": { - "properties": { - "agent": { - "description": "Declared subagent to use for the child agent", - "type": "string" - }, - "cwd": { - "description": "Working directory for the child agent. AGENTS.md and skills are discovered from here.", - "type": "string" - }, - "description": { - "description": "Short human-readable label for the delegated work", - "type": "string" - }, - "prompt": { - "description": "Focused instructions for the child agent", - "type": "string" - } - }, - "required": ["prompt"], - "type": "object" - }, - "strict": false, - "type": "function" - } - ] - } - }, - "headers": {}, - "method": "POST", - "url": "https://api.openai.com/v1/responses" - }, - "response": { - "body": { - "chunks": [ - "event: response.created\ndata: {\"type\":\"response.created\",\"response\":{\"id\":\"resp_054a16a2f2ea3217016a705b648f7081a3b96733816812acb8\",\"object\":\"response\",\"created_at\":1785748324,\"status\":\"in_progress\",\"background\":false,\"completed_at\":null,\"error\":null,\"frequency_penalty\":0,\"incomplete_details\":null,\"instructions\":null,\"max_output_tokens\":null,\"max_tool_calls\":null,\"model\":\"gpt-5.4-nano-2026-03-17\",\"moderation\":null,\"output\":[],\"parallel_tool_calls\":true,\"presence_penalty\":0,\"previous_response_id\":null,\"prompt_cache_key\":\"workflow:instrumentation:01KZ3E9TWTAEK4P4P3F63HKAKC::default::ta\",\"prompt_cache_retention\":\"24h\",\"reasoning\":{\"context\":\"current_turn\",\"effort\":\"none\",\"mode\":\"standard\",\"summary\":null},\"safety_identifier\":null,\"service_tier\":\"auto\",\"store\":false,\"temperature\":1,\"text\":{\"format\":{\"type\":\"text\"},\"verbosity\":\"medium\"},\"tool_choice\":\"auto\",\"tool_usage\":{\"image_gen\":{\"input_tokens\":0,\"input_tokens_details\":{\"image_tokens\":0,\"text_tokens\":0},\"output_tokens\":0,\"output_tokens_details\":{\"image_tokens\":0,\"text_tokens\":0},\"total_tokens\":0},\"web_search\":{\"num_requests\":0}},\"tools\":[{\"type\":\"function\",\"description\":\"Read a file or list a directory. For files, output is truncated to 2000 lines or 50KB — use offset/limit for large files. For directories, returns the list of entries.\",\"name\":\"read\",\"output_schema\":null,\"parameters\":{\"type\":\"object\",\"required\":[\"path\"],\"properties\":{\"path\":{\"type\":\"string\",\"description\":\"Path to the file to read\"},\"offset\":{\"type\":\"number\",\"description\":\"Line number to start from (1-indexed)\"},\"limit\":{\"type\":\"number\",\"description\":\"Maximum number of lines to read\"}}},\"strict\":false},{\"type\":\"function\",\"description\":\"Write content to a file. Creates the file and parent directories if they do not exist.\",\"name\":\"write\",\"output_schema\":null,\"parameters\":{\"type\":\"object\",\"required\":[\"path\",\"content\"],\"properties\":{\"path\":{\"type\":\"string\",\"description\":\"Path to the file to write\"},\"content\":{\"type\":\"string\",\"description\":\"Content to write to the file\"}}},\"strict\":false},{\"type\":\"function\",\"description\":\"Edit a file using exact text replacement. The oldText must match a unique region of the file. Use replaceAll to replace all occurrences.\",\"name\":\"edit\",\"output_schema\":null,\"parameters\":{\"type\":\"object\",\"required\":[\"path\",\"oldText\",\"newText\"],\"properties\":{\"path\":{\"type\":\"string\",\"description\":\"Path to the file to edit\"},\"oldText\":{\"type\":\"string\",\"description\":\"Exact text to find (must be unique)\"},\"newText\":{\"type\":\"string\",\"description\":\"Replacement text\"},\"replaceAll\":{\"type\":\"boolean\",\"description\":\"Replace all occurrences\"}}},\"strict\":false},{\"type\":\"function\",\"description\":\"Execute a bash command. Returns stdout and stderr. Output is truncated to the last 2000 lines or 50KB.\",\"name\":\"bash\",\"output_schema\":null,\"parameters\":{\"type\":\"object\",\"required\":[\"command\"],\"properties\":{\"command\":{\"type\":\"string\",\"description\":\"Bash command to execute\"},\"timeout\":{\"type\":\"number\",\"description\":\"Timeout in seconds\"}}},\"strict\":false},{\"type\":\"function\",\"description\":\"Search file contents for a regex pattern. Returns matching lines with file paths and line numbers.\",\"name\":\"grep\",\"output_schema\":null,\"parameters\":{\"type\":\"object\",\"required\":[\"pattern\"],\"properties\":{\"pattern\":{\"type\":\"string\",\"description\":\"Search pattern (regex)\"},\"path\":{\"type\":\"string\",\"description\":\"Directory or file to search (default: .)\"},\"include\":{\"type\":\"string\",\"description\":\"Glob filter, e.g. \\\"*.ts\\\"\"}}},\"strict\":false},{\"type\":\"function\",\"description\":\"Find files by filename pattern using shell find -name semantics. Returns matching file paths.\",\"name\":\"glob\",\"output_schema\":null,\"parameters\":{\"type\":\"object\",\"required\":[\"pattern\"],\"properties\":{\"pattern\":{\"type\":\"string\",\"description\":\"Filename pattern, e.g. \\\"*.ts\\\"\"},\"path\":{\"type\":\"string\",\"description\":\"Directory to search in (default: .)\"}}},\"strict\":false},{\"type\":\"function\",\"description\":\"Delegate a focused task to a detached child agent with its own context. Use this for independent research, file exploration, or parallel work. The task returns only its final answer to this conversation. No subagents are currently defined.\",\"name\":\"task\",\"output_schema\":null,\"parameters\":{\"type\":\"object\",\"required\":[\"prompt\"],\"properties\":{\"description\":{\"type\":\"string\",\"description\":\"Short human-readable label for the delegated work\"},\"prompt\":{\"type\":\"string\",\"description\":\"Focused instructions for the child agent\"},\"agent\":{\"type\":\"string\",\"description\":\"Declared subagent to use for the child agent\"},\"cwd\":{\"type\":\"string\",\"description\":\"Working directory for the child agent. AGENTS.md and skills are discovered from here.\"}}},\"strict\":false}],\"top_logprobs\":0,\"top_p\":0.98,\"truncation\":\"disabled\",\"usage\":null,\"user\":null,\"metadata\":{}},\"sequence_number\":0}", - "event: response.in_progress\ndata: {\"type\":\"response.in_progress\",\"response\":{\"id\":\"resp_054a16a2f2ea3217016a705b648f7081a3b96733816812acb8\",\"object\":\"response\",\"created_at\":1785748324,\"status\":\"in_progress\",\"background\":false,\"completed_at\":null,\"error\":null,\"frequency_penalty\":0,\"incomplete_details\":null,\"instructions\":null,\"max_output_tokens\":null,\"max_tool_calls\":null,\"model\":\"gpt-5.4-nano-2026-03-17\",\"moderation\":null,\"output\":[],\"parallel_tool_calls\":true,\"presence_penalty\":0,\"previous_response_id\":null,\"prompt_cache_key\":\"workflow:instrumentation:01KZ3E9TWTAEK4P4P3F63HKAKC::default::ta\",\"prompt_cache_retention\":\"24h\",\"reasoning\":{\"context\":\"current_turn\",\"effort\":\"none\",\"mode\":\"standard\",\"summary\":null},\"safety_identifier\":null,\"service_tier\":\"auto\",\"store\":false,\"temperature\":1,\"text\":{\"format\":{\"type\":\"text\"},\"verbosity\":\"medium\"},\"tool_choice\":\"auto\",\"tool_usage\":{\"image_gen\":{\"input_tokens\":0,\"input_tokens_details\":{\"image_tokens\":0,\"text_tokens\":0},\"output_tokens\":0,\"output_tokens_details\":{\"image_tokens\":0,\"text_tokens\":0},\"total_tokens\":0},\"web_search\":{\"num_requests\":0}},\"tools\":[{\"type\":\"function\",\"description\":\"Read a file or list a directory. For files, output is truncated to 2000 lines or 50KB — use offset/limit for large files. For directories, returns the list of entries.\",\"name\":\"read\",\"output_schema\":null,\"parameters\":{\"type\":\"object\",\"required\":[\"path\"],\"properties\":{\"path\":{\"type\":\"string\",\"description\":\"Path to the file to read\"},\"offset\":{\"type\":\"number\",\"description\":\"Line number to start from (1-indexed)\"},\"limit\":{\"type\":\"number\",\"description\":\"Maximum number of lines to read\"}}},\"strict\":false},{\"type\":\"function\",\"description\":\"Write content to a file. Creates the file and parent directories if they do not exist.\",\"name\":\"write\",\"output_schema\":null,\"parameters\":{\"type\":\"object\",\"required\":[\"path\",\"content\"],\"properties\":{\"path\":{\"type\":\"string\",\"description\":\"Path to the file to write\"},\"content\":{\"type\":\"string\",\"description\":\"Content to write to the file\"}}},\"strict\":false},{\"type\":\"function\",\"description\":\"Edit a file using exact text replacement. The oldText must match a unique region of the file. Use replaceAll to replace all occurrences.\",\"name\":\"edit\",\"output_schema\":null,\"parameters\":{\"type\":\"object\",\"required\":[\"path\",\"oldText\",\"newText\"],\"properties\":{\"path\":{\"type\":\"string\",\"description\":\"Path to the file to edit\"},\"oldText\":{\"type\":\"string\",\"description\":\"Exact text to find (must be unique)\"},\"newText\":{\"type\":\"string\",\"description\":\"Replacement text\"},\"replaceAll\":{\"type\":\"boolean\",\"description\":\"Replace all occurrences\"}}},\"strict\":false},{\"type\":\"function\",\"description\":\"Execute a bash command. Returns stdout and stderr. Output is truncated to the last 2000 lines or 50KB.\",\"name\":\"bash\",\"output_schema\":null,\"parameters\":{\"type\":\"object\",\"required\":[\"command\"],\"properties\":{\"command\":{\"type\":\"string\",\"description\":\"Bash command to execute\"},\"timeout\":{\"type\":\"number\",\"description\":\"Timeout in seconds\"}}},\"strict\":false},{\"type\":\"function\",\"description\":\"Search file contents for a regex pattern. Returns matching lines with file paths and line numbers.\",\"name\":\"grep\",\"output_schema\":null,\"parameters\":{\"type\":\"object\",\"required\":[\"pattern\"],\"properties\":{\"pattern\":{\"type\":\"string\",\"description\":\"Search pattern (regex)\"},\"path\":{\"type\":\"string\",\"description\":\"Directory or file to search (default: .)\"},\"include\":{\"type\":\"string\",\"description\":\"Glob filter, e.g. \\\"*.ts\\\"\"}}},\"strict\":false},{\"type\":\"function\",\"description\":\"Find files by filename pattern using shell find -name semantics. Returns matching file paths.\",\"name\":\"glob\",\"output_schema\":null,\"parameters\":{\"type\":\"object\",\"required\":[\"pattern\"],\"properties\":{\"pattern\":{\"type\":\"string\",\"description\":\"Filename pattern, e.g. \\\"*.ts\\\"\"},\"path\":{\"type\":\"string\",\"description\":\"Directory to search in (default: .)\"}}},\"strict\":false},{\"type\":\"function\",\"description\":\"Delegate a focused task to a detached child agent with its own context. Use this for independent research, file exploration, or parallel work. The task returns only its final answer to this conversation. No subagents are currently defined.\",\"name\":\"task\",\"output_schema\":null,\"parameters\":{\"type\":\"object\",\"required\":[\"prompt\"],\"properties\":{\"description\":{\"type\":\"string\",\"description\":\"Short human-readable label for the delegated work\"},\"prompt\":{\"type\":\"string\",\"description\":\"Focused instructions for the child agent\"},\"agent\":{\"type\":\"string\",\"description\":\"Declared subagent to use for the child agent\"},\"cwd\":{\"type\":\"string\",\"description\":\"Working directory for the child agent. AGENTS.md and skills are discovered from here.\"}}},\"strict\":false}],\"top_logprobs\":0,\"top_p\":0.98,\"truncation\":\"disabled\",\"usage\":null,\"user\":null,\"metadata\":{}},\"sequence_number\":1}", - "event: response.output_item.added\ndata: {\"type\":\"response.output_item.added\",\"item\":{\"id\":\"fc_054a16a2f2ea3217016a705b64fe5481a390185c9576df12b6\",\"type\":\"function_call\",\"status\":\"in_progress\",\"arguments\":\"\",\"call_id\":\"call_w6pyYZgnhlSOAXul75DAG1Cj\",\"name\":\"task\"},\"output_index\":0,\"sequence_number\":2}", - "event: response.function_call_arguments.delta\ndata: {\"type\":\"response.function_call_arguments.delta\",\"delta\":\"{\\\"\",\"item_id\":\"fc_054a16a2f2ea3217016a705b64fe5481a390185c9576df12b6\",\"obfuscation\":\"ljpLbllVFarTqw\",\"output_index\":0,\"sequence_number\":3}", - "event: response.function_call_arguments.delta\ndata: {\"type\":\"response.function_call_arguments.delta\",\"delta\":\"description\",\"item_id\":\"fc_054a16a2f2ea3217016a705b64fe5481a390185c9576df12b6\",\"obfuscation\":\"dI5yK\",\"output_index\":0,\"sequence_number\":4}", - "event: response.function_call_arguments.delta\ndata: {\"type\":\"response.function_call_arguments.delta\",\"delta\":\"\\\":\\\"\",\"item_id\":\"fc_054a16a2f2ea3217016a705b64fe5481a390185c9576df12b6\",\"obfuscation\":\"gaE80vRkyue6q\",\"output_index\":0,\"sequence_number\":5}", - "event: response.function_call_arguments.delta\ndata: {\"type\":\"response.function_call_arguments.delta\",\"delta\":\"Run\",\"item_id\":\"fc_054a16a2f2ea3217016a705b64fe5481a390185c9576df12b6\",\"obfuscation\":\"1yh2B3GwgUIkU\",\"output_index\":0,\"sequence_number\":6}", - "event: response.function_call_arguments.delta\ndata: {\"type\":\"response.function_call_arguments.delta\",\"delta\":\" e\",\"item_id\":\"fc_054a16a2f2ea3217016a705b64fe5481a390185c9576df12b6\",\"obfuscation\":\"Aoc2XDyvRflwpK\",\"output_index\":0,\"sequence_number\":7}", - "event: response.function_call_arguments.delta\ndata: {\"type\":\"response.function_call_arguments.delta\",\"delta\":\"2\",\"item_id\":\"fc_054a16a2f2ea3217016a705b64fe5481a390185c9576df12b6\",\"obfuscation\":\"gKDO3Feq1OjvKMT\",\"output_index\":0,\"sequence_number\":8}", - "event: response.function_call_arguments.delta\ndata: {\"type\":\"response.function_call_arguments.delta\",\"delta\":\"e\",\"item_id\":\"fc_054a16a2f2ea3217016a705b64fe5481a390185c9576df12b6\",\"obfuscation\":\"d2O2IlHGALtgxiz\",\"output_index\":0,\"sequence_number\":9}", - "event: response.function_call_arguments.delta\ndata: {\"type\":\"response.function_call_arguments.delta\",\"delta\":\"-fl\",\"item_id\":\"fc_054a16a2f2ea3217016a705b64fe5481a390185c9576df12b6\",\"obfuscation\":\"c9m5GXjLlVm1X\",\"output_index\":0,\"sequence_number\":10}", - "event: response.function_call_arguments.delta\ndata: {\"type\":\"response.function_call_arguments.delta\",\"delta\":\"ue\",\"item_id\":\"fc_054a16a2f2ea3217016a705b64fe5481a390185c9576df12b6\",\"obfuscation\":\"NUY5MBuNzz8sZQ\",\"output_index\":0,\"sequence_number\":11}", - "event: response.function_call_arguments.delta\ndata: {\"type\":\"response.function_call_arguments.delta\",\"delta\":\"-s\",\"item_id\":\"fc_054a16a2f2ea3217016a705b64fe5481a390185c9576df12b6\",\"obfuscation\":\"9Pwv64hhPdkCsg\",\"output_index\":0,\"sequence_number\":12}", - "event: response.function_call_arguments.delta\ndata: {\"type\":\"response.function_call_arguments.delta\",\"delta\":\"kill\",\"item_id\":\"fc_054a16a2f2ea3217016a705b64fe5481a390185c9576df12b6\",\"obfuscation\":\"iOoKz15nYfXp\",\"output_index\":0,\"sequence_number\":13}", - "event: response.function_call_arguments.delta\ndata: {\"type\":\"response.function_call_arguments.delta\",\"delta\":\" with\",\"item_id\":\"fc_054a16a2f2ea3217016a705b64fe5481a390185c9576df12b6\",\"obfuscation\":\"UCahcEprOGt\",\"output_index\":0,\"sequence_number\":14}", - "event: response.function_call_arguments.delta\ndata: {\"type\":\"response.function_call_arguments.delta\",\"delta\":\" marker\",\"item_id\":\"fc_054a16a2f2ea3217016a705b64fe5481a390185c9576df12b6\",\"obfuscation\":\"TaOkeLqZF\",\"output_index\":0,\"sequence_number\":15}", - "event: response.function_call_arguments.delta\ndata: {\"type\":\"response.function_call_arguments.delta\",\"delta\":\" SK\",\"item_id\":\"fc_054a16a2f2ea3217016a705b64fe5481a390185c9576df12b6\",\"obfuscation\":\"VxlrD2puv3ABI\",\"output_index\":0,\"sequence_number\":16}", - "event: response.function_call_arguments.delta\ndata: {\"type\":\"response.function_call_arguments.delta\",\"delta\":\"ILL\",\"item_id\":\"fc_054a16a2f2ea3217016a705b64fe5481a390185c9576df12b6\",\"obfuscation\":\"DebTGrlabFRQY\",\"output_index\":0,\"sequence_number\":17}", - "event: response.function_call_arguments.delta\ndata: {\"type\":\"response.function_call_arguments.delta\",\"delta\":\"_DONE\",\"item_id\":\"fc_054a16a2f2ea3217016a705b64fe5481a390185c9576df12b6\",\"obfuscation\":\"Y6K3aF28EHz\",\"output_index\":0,\"sequence_number\":18}", - "event: response.function_call_arguments.delta\ndata: {\"type\":\"response.function_call_arguments.delta\",\"delta\":\"\\\",\\\"\",\"item_id\":\"fc_054a16a2f2ea3217016a705b64fe5481a390185c9576df12b6\",\"obfuscation\":\"py2Pd0lnhbcOC\",\"output_index\":0,\"sequence_number\":19}", - "event: response.function_call_arguments.delta\ndata: {\"type\":\"response.function_call_arguments.delta\",\"delta\":\"prompt\",\"item_id\":\"fc_054a16a2f2ea3217016a705b64fe5481a390185c9576df12b6\",\"obfuscation\":\"PM2ySL1u5c\",\"output_index\":0,\"sequence_number\":20}", - "event: response.function_call_arguments.delta\ndata: {\"type\":\"response.function_call_arguments.delta\",\"delta\":\"\\\":\\\"\",\"item_id\":\"fc_054a16a2f2ea3217016a705b64fe5481a390185c9576df12b6\",\"obfuscation\":\"GEFeUIpfX9F6O\",\"output_index\":0,\"sequence_number\":21}", - "event: response.function_call_arguments.delta\ndata: {\"type\":\"response.function_call_arguments.delta\",\"delta\":\"Run\",\"item_id\":\"fc_054a16a2f2ea3217016a705b64fe5481a390185c9576df12b6\",\"obfuscation\":\"g8vFm9KNQk3gN\",\"output_index\":0,\"sequence_number\":22}", - "event: response.function_call_arguments.delta\ndata: {\"type\":\"response.function_call_arguments.delta\",\"delta\":\" the\",\"item_id\":\"fc_054a16a2f2ea3217016a705b64fe5481a390185c9576df12b6\",\"obfuscation\":\"cXxSzWyVKRHs\",\"output_index\":0,\"sequence_number\":23}", - "event: response.function_call_arguments.delta\ndata: {\"type\":\"response.function_call_arguments.delta\",\"delta\":\" skill\",\"item_id\":\"fc_054a16a2f2ea3217016a705b64fe5481a390185c9576df12b6\",\"obfuscation\":\"bwbVemN5vW\",\"output_index\":0,\"sequence_number\":24}", - "event: response.function_call_arguments.delta\ndata: {\"type\":\"response.function_call_arguments.delta\",\"delta\":\" named\",\"item_id\":\"fc_054a16a2f2ea3217016a705b64fe5481a390185c9576df12b6\",\"obfuscation\":\"4DdymViVIi\",\"output_index\":0,\"sequence_number\":25}", - "event: response.function_call_arguments.delta\ndata: {\"type\":\"response.function_call_arguments.delta\",\"delta\":\" \\\\\\\"\",\"item_id\":\"fc_054a16a2f2ea3217016a705b64fe5481a390185c9576df12b6\",\"obfuscation\":\"9QwzJKU8yuXI1\",\"output_index\":0,\"sequence_number\":26}", - "event: response.function_call_arguments.delta\ndata: {\"type\":\"response.function_call_arguments.delta\",\"delta\":\"e\",\"item_id\":\"fc_054a16a2f2ea3217016a705b64fe5481a390185c9576df12b6\",\"obfuscation\":\"qc8c9FjKJwS85NJ\",\"output_index\":0,\"sequence_number\":27}", - "event: response.function_call_arguments.delta\ndata: {\"type\":\"response.function_call_arguments.delta\",\"delta\":\"2\",\"item_id\":\"fc_054a16a2f2ea3217016a705b64fe5481a390185c9576df12b6\",\"obfuscation\":\"ewNZxFO4TdAgDUM\",\"output_index\":0,\"sequence_number\":28}", - "event: response.function_call_arguments.delta\ndata: {\"type\":\"response.function_call_arguments.delta\",\"delta\":\"e\",\"item_id\":\"fc_054a16a2f2ea3217016a705b64fe5481a390185c9576df12b6\",\"obfuscation\":\"Y8xyYUu8EtAbcT1\",\"output_index\":0,\"sequence_number\":29}", - "event: response.function_call_arguments.delta\ndata: {\"type\":\"response.function_call_arguments.delta\",\"delta\":\"-fl\",\"item_id\":\"fc_054a16a2f2ea3217016a705b64fe5481a390185c9576df12b6\",\"obfuscation\":\"88SoGMEyRHz8M\",\"output_index\":0,\"sequence_number\":30}", - "event: response.function_call_arguments.delta\ndata: {\"type\":\"response.function_call_arguments.delta\",\"delta\":\"ue\",\"item_id\":\"fc_054a16a2f2ea3217016a705b64fe5481a390185c9576df12b6\",\"obfuscation\":\"DpP2H8WkpDUzag\",\"output_index\":0,\"sequence_number\":31}", - "event: response.function_call_arguments.delta\ndata: {\"type\":\"response.function_call_arguments.delta\",\"delta\":\"-s\",\"item_id\":\"fc_054a16a2f2ea3217016a705b64fe5481a390185c9576df12b6\",\"obfuscation\":\"5jctq9RddqcIt8\",\"output_index\":0,\"sequence_number\":32}", - "event: response.function_call_arguments.delta\ndata: {\"type\":\"response.function_call_arguments.delta\",\"delta\":\"kill\",\"item_id\":\"fc_054a16a2f2ea3217016a705b64fe5481a390185c9576df12b6\",\"obfuscation\":\"xpmcaPHyYGYa\",\"output_index\":0,\"sequence_number\":33}", - "event: response.function_call_arguments.delta\ndata: {\"type\":\"response.function_call_arguments.delta\",\"delta\":\"\\\\\\\"\",\"item_id\":\"fc_054a16a2f2ea3217016a705b64fe5481a390185c9576df12b6\",\"obfuscation\":\"kumygUHU0Bd682\",\"output_index\":0,\"sequence_number\":34}", - "event: response.function_call_arguments.delta\ndata: {\"type\":\"response.function_call_arguments.delta\",\"delta\":\" with\",\"item_id\":\"fc_054a16a2f2ea3217016a705b64fe5481a390185c9576df12b6\",\"obfuscation\":\"1aD8huNzRAg\",\"output_index\":0,\"sequence_number\":35}", - "event: response.function_call_arguments.delta\ndata: {\"type\":\"response.function_call_arguments.delta\",\"delta\":\" arguments\",\"item_id\":\"fc_054a16a2f2ea3217016a705b64fe5481a390185c9576df12b6\",\"obfuscation\":\"u62pY1\",\"output_index\":0,\"sequence_number\":36}", - "event: response.function_call_arguments.delta\ndata: {\"type\":\"response.function_call_arguments.delta\",\"delta\":\" {\",\"item_id\":\"fc_054a16a2f2ea3217016a705b64fe5481a390185c9576df12b6\",\"obfuscation\":\"DlufXJUKJUw7nw\",\"output_index\":0,\"sequence_number\":37}", - "event: response.function_call_arguments.delta\ndata: {\"type\":\"response.function_call_arguments.delta\",\"delta\":\"\\\\\\\"\",\"item_id\":\"fc_054a16a2f2ea3217016a705b64fe5481a390185c9576df12b6\",\"obfuscation\":\"dqIkwjYJ3Ruj51\",\"output_index\":0,\"sequence_number\":38}", - "event: response.function_call_arguments.delta\ndata: {\"type\":\"response.function_call_arguments.delta\",\"delta\":\"marker\",\"item_id\":\"fc_054a16a2f2ea3217016a705b64fe5481a390185c9576df12b6\",\"obfuscation\":\"stUcL5JVqc\",\"output_index\":0,\"sequence_number\":39}", - "event: response.function_call_arguments.delta\ndata: {\"type\":\"response.function_call_arguments.delta\",\"delta\":\"\\\\\\\":\\\\\\\"\",\"item_id\":\"fc_054a16a2f2ea3217016a705b64fe5481a390185c9576df12b6\",\"obfuscation\":\"cUfPJq2cus1\",\"output_index\":0,\"sequence_number\":40}", - "event: response.function_call_arguments.delta\ndata: {\"type\":\"response.function_call_arguments.delta\",\"delta\":\"SK\",\"item_id\":\"fc_054a16a2f2ea3217016a705b64fe5481a390185c9576df12b6\",\"obfuscation\":\"ZzPWTx2aFPwBYM\",\"output_index\":0,\"sequence_number\":41}", - "event: response.function_call_arguments.delta\ndata: {\"type\":\"response.function_call_arguments.delta\",\"delta\":\"ILL\",\"item_id\":\"fc_054a16a2f2ea3217016a705b64fe5481a390185c9576df12b6\",\"obfuscation\":\"Hbw389fTXAJOT\",\"output_index\":0,\"sequence_number\":42}", - "event: response.function_call_arguments.delta\ndata: {\"type\":\"response.function_call_arguments.delta\",\"delta\":\"_DONE\",\"item_id\":\"fc_054a16a2f2ea3217016a705b64fe5481a390185c9576df12b6\",\"obfuscation\":\"ALgrsM7YeM5\",\"output_index\":0,\"sequence_number\":43}", - "event: response.function_call_arguments.delta\ndata: {\"type\":\"response.function_call_arguments.delta\",\"delta\":\"\\\\\\\"\",\"item_id\":\"fc_054a16a2f2ea3217016a705b64fe5481a390185c9576df12b6\",\"obfuscation\":\"D4fcykCofi3ap6\",\"output_index\":0,\"sequence_number\":44}", - "event: response.function_call_arguments.delta\ndata: {\"type\":\"response.function_call_arguments.delta\",\"delta\":\"}.\",\"item_id\":\"fc_054a16a2f2ea3217016a705b64fe5481a390185c9576df12b6\",\"obfuscation\":\"79o07UPm9B29cw\",\"output_index\":0,\"sequence_number\":45}", - "event: response.function_call_arguments.delta\ndata: {\"type\":\"response.function_call_arguments.delta\",\"delta\":\" Ensure\",\"item_id\":\"fc_054a16a2f2ea3217016a705b64fe5481a390185c9576df12b6\",\"obfuscation\":\"1JXF9XGzd\",\"output_index\":0,\"sequence_number\":46}", - "event: response.function_call_arguments.delta\ndata: {\"type\":\"response.function_call_arguments.delta\",\"delta\":\" completion\",\"item_id\":\"fc_054a16a2f2ea3217016a705b64fe5481a390185c9576df12b6\",\"obfuscation\":\"vfueK\",\"output_index\":0,\"sequence_number\":47}", - "event: response.function_call_arguments.delta\ndata: {\"type\":\"response.function_call_arguments.delta\",\"delta\":\" and\",\"item_id\":\"fc_054a16a2f2ea3217016a705b64fe5481a390185c9576df12b6\",\"obfuscation\":\"VlKAzFGFtrcw\",\"output_index\":0,\"sequence_number\":48}", - "event: response.function_call_arguments.delta\ndata: {\"type\":\"response.function_call_arguments.delta\",\"delta\":\" report\",\"item_id\":\"fc_054a16a2f2ea3217016a705b64fe5481a390185c9576df12b6\",\"obfuscation\":\"wBeH1rhXr\",\"output_index\":0,\"sequence_number\":49}", - "event: response.function_call_arguments.delta\ndata: {\"type\":\"response.function_call_arguments.delta\",\"delta\":\" the\",\"item_id\":\"fc_054a16a2f2ea3217016a705b64fe5481a390185c9576df12b6\",\"obfuscation\":\"6EO4vypCMr7q\",\"output_index\":0,\"sequence_number\":50}", - "event: response.function_call_arguments.delta\ndata: {\"type\":\"response.function_call_arguments.delta\",\"delta\":\" final\",\"item_id\":\"fc_054a16a2f2ea3217016a705b64fe5481a390185c9576df12b6\",\"obfuscation\":\"0pF35NFSxj\",\"output_index\":0,\"sequence_number\":51}", - "event: response.function_call_arguments.delta\ndata: {\"type\":\"response.function_call_arguments.delta\",\"delta\":\" output\",\"item_id\":\"fc_054a16a2f2ea3217016a705b64fe5481a390185c9576df12b6\",\"obfuscation\":\"Fyh51oBFK\",\"output_index\":0,\"sequence_number\":52}", - "event: response.function_call_arguments.delta\ndata: {\"type\":\"response.function_call_arguments.delta\",\"delta\":\" marker\",\"item_id\":\"fc_054a16a2f2ea3217016a705b64fe5481a390185c9576df12b6\",\"obfuscation\":\"2fdE8CEMT\",\"output_index\":0,\"sequence_number\":53}", - "event: response.function_call_arguments.delta\ndata: {\"type\":\"response.function_call_arguments.delta\",\"delta\":\" only\",\"item_id\":\"fc_054a16a2f2ea3217016a705b64fe5481a390185c9576df12b6\",\"obfuscation\":\"Oi1kYTafLLr\",\"output_index\":0,\"sequence_number\":54}", - "event: response.function_call_arguments.delta\ndata: {\"type\":\"response.function_call_arguments.delta\",\"delta\":\".\\\",\\\"\",\"item_id\":\"fc_054a16a2f2ea3217016a705b64fe5481a390185c9576df12b6\",\"obfuscation\":\"JHNARwrOOg28\",\"output_index\":0,\"sequence_number\":55}", - "event: response.function_call_arguments.delta\ndata: {\"type\":\"response.function_call_arguments.delta\",\"delta\":\"agent\",\"item_id\":\"fc_054a16a2f2ea3217016a705b64fe5481a390185c9576df12b6\",\"obfuscation\":\"JujpXurFNFJ\",\"output_index\":0,\"sequence_number\":56}", - "event: response.function_call_arguments.delta\ndata: {\"type\":\"response.function_call_arguments.delta\",\"delta\":\"\\\":\\\"\",\"item_id\":\"fc_054a16a2f2ea3217016a705b64fe5481a390185c9576df12b6\",\"obfuscation\":\"7UtnbpjQ9Vd5r\",\"output_index\":0,\"sequence_number\":57}", - "event: response.function_call_arguments.delta\ndata: {\"type\":\"response.function_call_arguments.delta\",\"delta\":\"e\",\"item_id\":\"fc_054a16a2f2ea3217016a705b64fe5481a390185c9576df12b6\",\"obfuscation\":\"lUMgv4pWgFguMgN\",\"output_index\":0,\"sequence_number\":58}", - "event: response.function_call_arguments.delta\ndata: {\"type\":\"response.function_call_arguments.delta\",\"delta\":\"2\",\"item_id\":\"fc_054a16a2f2ea3217016a705b64fe5481a390185c9576df12b6\",\"obfuscation\":\"lquqyIOhUG8FFqb\",\"output_index\":0,\"sequence_number\":59}", - "event: response.function_call_arguments.delta\ndata: {\"type\":\"response.function_call_arguments.delta\",\"delta\":\"e\",\"item_id\":\"fc_054a16a2f2ea3217016a705b64fe5481a390185c9576df12b6\",\"obfuscation\":\"z1aAXV10WTtEwfO\",\"output_index\":0,\"sequence_number\":60}", - "event: response.function_call_arguments.delta\ndata: {\"type\":\"response.function_call_arguments.delta\",\"delta\":\"-fl\",\"item_id\":\"fc_054a16a2f2ea3217016a705b64fe5481a390185c9576df12b6\",\"obfuscation\":\"z29NnUuO8awvU\",\"output_index\":0,\"sequence_number\":61}", - "event: response.function_call_arguments.delta\ndata: {\"type\":\"response.function_call_arguments.delta\",\"delta\":\"ue\",\"item_id\":\"fc_054a16a2f2ea3217016a705b64fe5481a390185c9576df12b6\",\"obfuscation\":\"vjUb5xZd4tz6Nc\",\"output_index\":0,\"sequence_number\":62}", - "event: response.function_call_arguments.delta\ndata: {\"type\":\"response.function_call_arguments.delta\",\"delta\":\"-s\",\"item_id\":\"fc_054a16a2f2ea3217016a705b64fe5481a390185c9576df12b6\",\"obfuscation\":\"S79uypIplW7mcH\",\"output_index\":0,\"sequence_number\":63}", - "event: response.function_call_arguments.delta\ndata: {\"type\":\"response.function_call_arguments.delta\",\"delta\":\"kill\",\"item_id\":\"fc_054a16a2f2ea3217016a705b64fe5481a390185c9576df12b6\",\"obfuscation\":\"GgbDCJps9iY5\",\"output_index\":0,\"sequence_number\":64}", - "event: response.function_call_arguments.delta\ndata: {\"type\":\"response.function_call_arguments.delta\",\"delta\":\"\\\",\\\"\",\"item_id\":\"fc_054a16a2f2ea3217016a705b64fe5481a390185c9576df12b6\",\"obfuscation\":\"vCCHCimmRyAgx\",\"output_index\":0,\"sequence_number\":65}", - "event: response.function_call_arguments.delta\ndata: {\"type\":\"response.function_call_arguments.delta\",\"delta\":\"cwd\",\"item_id\":\"fc_054a16a2f2ea3217016a705b64fe5481a390185c9576df12b6\",\"obfuscation\":\"AnLDQiNay8ENp\",\"output_index\":0,\"sequence_number\":66}", - "event: response.function_call_arguments.delta\ndata: {\"type\":\"response.function_call_arguments.delta\",\"delta\":\"\\\":\",\"item_id\":\"fc_054a16a2f2ea3217016a705b64fe5481a390185c9576df12b6\",\"obfuscation\":\"6YVXzJiSMPKoqe\",\"output_index\":0,\"sequence_number\":67}", - "event: response.function_call_arguments.delta\ndata: {\"type\":\"response.function_call_arguments.delta\",\"delta\":\"\\\"/\",\"item_id\":\"fc_054a16a2f2ea3217016a705b64fe5481a390185c9576df12b6\",\"obfuscation\":\"2iVYgZDItlXYju\",\"output_index\":0,\"sequence_number\":68}", - "event: response.function_call_arguments.delta\ndata: {\"type\":\"response.function_call_arguments.delta\",\"delta\":\"ver\",\"item_id\":\"fc_054a16a2f2ea3217016a705b64fe5481a390185c9576df12b6\",\"obfuscation\":\"p8iffYf14P4rP\",\"output_index\":0,\"sequence_number\":69}", - "event: response.function_call_arguments.delta\ndata: {\"type\":\"response.function_call_arguments.delta\",\"delta\":\"cel\",\"item_id\":\"fc_054a16a2f2ea3217016a705b64fe5481a390185c9576df12b6\",\"obfuscation\":\"jHIrIgM5myBm6\",\"output_index\":0,\"sequence_number\":70}", - "event: response.function_call_arguments.delta\ndata: {\"type\":\"response.function_call_arguments.delta\",\"delta\":\"/s\",\"item_id\":\"fc_054a16a2f2ea3217016a705b64fe5481a390185c9576df12b6\",\"obfuscation\":\"Yud9IuaLw1CQTi\",\"output_index\":0,\"sequence_number\":71}", - "event: response.function_call_arguments.delta\ndata: {\"type\":\"response.function_call_arguments.delta\",\"delta\":\"andbox\",\"item_id\":\"fc_054a16a2f2ea3217016a705b64fe5481a390185c9576df12b6\",\"obfuscation\":\"sxVNzIOgmI\",\"output_index\":0,\"sequence_number\":72}", - "event: response.function_call_arguments.delta\ndata: {\"type\":\"response.function_call_arguments.delta\",\"delta\":\"/\",\"item_id\":\"fc_054a16a2f2ea3217016a705b64fe5481a390185c9576df12b6\",\"obfuscation\":\"BFftBcl4q4mRiic\",\"output_index\":0,\"sequence_number\":73}", - "event: response.function_call_arguments.delta\ndata: {\"type\":\"response.function_call_arguments.delta\",\"delta\":\"brain\",\"item_id\":\"fc_054a16a2f2ea3217016a705b64fe5481a390185c9576df12b6\",\"obfuscation\":\"bvKWRZsGF3z\",\"output_index\":0,\"sequence_number\":74}", - "event: response.function_call_arguments.delta\ndata: {\"type\":\"response.function_call_arguments.delta\",\"delta\":\"trust\",\"item_id\":\"fc_054a16a2f2ea3217016a705b64fe5481a390185c9576df12b6\",\"obfuscation\":\"8fCBYfWjld6\",\"output_index\":0,\"sequence_number\":75}", - "event: response.function_call_arguments.delta\ndata: {\"type\":\"response.function_call_arguments.delta\",\"delta\":\"data\",\"item_id\":\"fc_054a16a2f2ea3217016a705b64fe5481a390185c9576df12b6\",\"obfuscation\":\"bWCV0wB1yOIj\",\"output_index\":0,\"sequence_number\":76}", - "event: response.function_call_arguments.delta\ndata: {\"type\":\"response.function_call_arguments.delta\",\"delta\":\"/\",\"item_id\":\"fc_054a16a2f2ea3217016a705b64fe5481a390185c9576df12b6\",\"obfuscation\":\"gMLQ5nGNZU5RQcA\",\"output_index\":0,\"sequence_number\":77}", - "event: response.function_call_arguments.delta\ndata: {\"type\":\"response.function_call_arguments.delta\",\"delta\":\"brain\",\"item_id\":\"fc_054a16a2f2ea3217016a705b64fe5481a390185c9576df12b6\",\"obfuscation\":\"HbDV4gJR67J\",\"output_index\":0,\"sequence_number\":78}", - "event: response.function_call_arguments.delta\ndata: {\"type\":\"response.function_call_arguments.delta\",\"delta\":\"trust\",\"item_id\":\"fc_054a16a2f2ea3217016a705b64fe5481a390185c9576df12b6\",\"obfuscation\":\"fY6Fye7lRfU\",\"output_index\":0,\"sequence_number\":79}", - "event: response.function_call_arguments.delta\ndata: {\"type\":\"response.function_call_arguments.delta\",\"delta\":\"-sdk\",\"item_id\":\"fc_054a16a2f2ea3217016a705b64fe5481a390185c9576df12b6\",\"obfuscation\":\"nDcCSPqarvNr\",\"output_index\":0,\"sequence_number\":80}", - "event: response.function_call_arguments.delta\ndata: {\"type\":\"response.function_call_arguments.delta\",\"delta\":\"-j\",\"item_id\":\"fc_054a16a2f2ea3217016a705b64fe5481a390185c9576df12b6\",\"obfuscation\":\"CDVkHgmi29Aam7\",\"output_index\":0,\"sequence_number\":81}", - "event: response.function_call_arguments.delta\ndata: {\"type\":\"response.function_call_arguments.delta\",\"delta\":\"avascript\",\"item_id\":\"fc_054a16a2f2ea3217016a705b64fe5481a390185c9576df12b6\",\"obfuscation\":\"yMH6cPx\",\"output_index\":0,\"sequence_number\":82}", - "event: response.function_call_arguments.delta\ndata: {\"type\":\"response.function_call_arguments.delta\",\"delta\":\"/e\",\"item_id\":\"fc_054a16a2f2ea3217016a705b64fe5481a390185c9576df12b6\",\"obfuscation\":\"zoEfWKIxJ3vi4r\",\"output_index\":0,\"sequence_number\":83}", - "event: response.function_call_arguments.delta\ndata: {\"type\":\"response.function_call_arguments.delta\",\"delta\":\"2\",\"item_id\":\"fc_054a16a2f2ea3217016a705b64fe5481a390185c9576df12b6\",\"obfuscation\":\"ti33GAqnm3z55of\",\"output_index\":0,\"sequence_number\":84}", - "event: response.function_call_arguments.delta\ndata: {\"type\":\"response.function_call_arguments.delta\",\"delta\":\"e\",\"item_id\":\"fc_054a16a2f2ea3217016a705b64fe5481a390185c9576df12b6\",\"obfuscation\":\"iKabCz0geb00km7\",\"output_index\":0,\"sequence_number\":85}", - "event: response.function_call_arguments.delta\ndata: {\"type\":\"response.function_call_arguments.delta\",\"delta\":\"/.\",\"item_id\":\"fc_054a16a2f2ea3217016a705b64fe5481a390185c9576df12b6\",\"obfuscation\":\"kQaYnx9LpaZ4YA\",\"output_index\":0,\"sequence_number\":86}", - "event: response.function_call_arguments.delta\ndata: {\"type\":\"response.function_call_arguments.delta\",\"delta\":\"bt\",\"item_id\":\"fc_054a16a2f2ea3217016a705b64fe5481a390185c9576df12b6\",\"obfuscation\":\"rHzMr4AWuPUfim\",\"output_index\":0,\"sequence_number\":87}", - "event: response.function_call_arguments.delta\ndata: {\"type\":\"response.function_call_arguments.delta\",\"delta\":\"-t\",\"item_id\":\"fc_054a16a2f2ea3217016a705b64fe5481a390185c9576df12b6\",\"obfuscation\":\"nJma8KAPeFozgD\",\"output_index\":0,\"sequence_number\":88}", - "event: response.function_call_arguments.delta\ndata: {\"type\":\"response.function_call_arguments.delta\",\"delta\":\"mp\",\"item_id\":\"fc_054a16a2f2ea3217016a705b64fe5481a390185c9576df12b6\",\"obfuscation\":\"bJV57iCDmqv2VA\",\"output_index\":0,\"sequence_number\":89}", - "event: response.function_call_arguments.delta\ndata: {\"type\":\"response.function_call_arguments.delta\",\"delta\":\"/run\",\"item_id\":\"fc_054a16a2f2ea3217016a705b64fe5481a390185c9576df12b6\",\"obfuscation\":\"IpVQRAjXMPE4\",\"output_index\":0,\"sequence_number\":90}", - "event: response.function_call_arguments.delta\ndata: {\"type\":\"response.function_call_arguments.delta\",\"delta\":\"-\",\"item_id\":\"fc_054a16a2f2ea3217016a705b64fe5481a390185c9576df12b6\",\"obfuscation\":\"pzJyXElP7ou0ovn\",\"output_index\":0,\"sequence_number\":91}", - "event: response.function_call_arguments.delta\ndata: {\"type\":\"response.function_call_arguments.delta\",\"delta\":\"9\",\"item_id\":\"fc_054a16a2f2ea3217016a705b64fe5481a390185c9576df12b6\",\"obfuscation\":\"1ZlIYGuEL0CGzns\",\"output_index\":0,\"sequence_number\":92}", - "event: response.function_call_arguments.delta\ndata: {\"type\":\"response.function_call_arguments.delta\",\"delta\":\"sp\",\"item_id\":\"fc_054a16a2f2ea3217016a705b64fe5481a390185c9576df12b6\",\"obfuscation\":\"QVS1D8jJbqlZ7X\",\"output_index\":0,\"sequence_number\":93}", - "event: response.function_call_arguments.delta\ndata: {\"type\":\"response.function_call_arguments.delta\",\"delta\":\"u\",\"item_id\":\"fc_054a16a2f2ea3217016a705b64fe5481a390185c9576df12b6\",\"obfuscation\":\"2mhaJD9do70hkwJ\",\"output_index\":0,\"sequence_number\":94}", - "event: response.function_call_arguments.delta\ndata: {\"type\":\"response.function_call_arguments.delta\",\"delta\":\"Hg\",\"item_id\":\"fc_054a16a2f2ea3217016a705b64fe5481a390185c9576df12b6\",\"obfuscation\":\"dXooQVlSHUeRZP\",\"output_index\":0,\"sequence_number\":95}", - "event: response.function_call_arguments.delta\ndata: {\"type\":\"response.function_call_arguments.delta\",\"delta\":\"/sc\",\"item_id\":\"fc_054a16a2f2ea3217016a705b64fe5481a390185c9576df12b6\",\"obfuscation\":\"JYgibM5tWMAjH\",\"output_index\":0,\"sequence_number\":96}", - "event: response.function_call_arguments.delta\ndata: {\"type\":\"response.function_call_arguments.delta\",\"delta\":\"enarios\",\"item_id\":\"fc_054a16a2f2ea3217016a705b64fe5481a390185c9576df12b6\",\"obfuscation\":\"JH3lHc5hu\",\"output_index\":0,\"sequence_number\":97}", - "event: response.function_call_arguments.delta\ndata: {\"type\":\"response.function_call_arguments.delta\",\"delta\":\"/fl\",\"item_id\":\"fc_054a16a2f2ea3217016a705b64fe5481a390185c9576df12b6\",\"obfuscation\":\"S7XC3UsMmTfuf\",\"output_index\":0,\"sequence_number\":98}", - "event: response.function_call_arguments.delta\ndata: {\"type\":\"response.function_call_arguments.delta\",\"delta\":\"ue\",\"item_id\":\"fc_054a16a2f2ea3217016a705b64fe5481a390185c9576df12b6\",\"obfuscation\":\"xWgqKM1XAI7Gka\",\"output_index\":0,\"sequence_number\":99}", - "event: response.function_call_arguments.delta\ndata: {\"type\":\"response.function_call_arguments.delta\",\"delta\":\"-in\",\"item_id\":\"fc_054a16a2f2ea3217016a705b64fe5481a390185c9576df12b6\",\"obfuscation\":\"7l79NEJnKe3pM\",\"output_index\":0,\"sequence_number\":100}", - "event: response.function_call_arguments.delta\ndata: {\"type\":\"response.function_call_arguments.delta\",\"delta\":\"strument\",\"item_id\":\"fc_054a16a2f2ea3217016a705b64fe5481a390185c9576df12b6\",\"obfuscation\":\"F9vfZGnW\",\"output_index\":0,\"sequence_number\":101}", - "event: response.function_call_arguments.delta\ndata: {\"type\":\"response.function_call_arguments.delta\",\"delta\":\"ation\",\"item_id\":\"fc_054a16a2f2ea3217016a705b64fe5481a390185c9576df12b6\",\"obfuscation\":\"iNKeVMoTNBN\",\"output_index\":0,\"sequence_number\":102}", - "event: response.function_call_arguments.delta\ndata: {\"type\":\"response.function_call_arguments.delta\",\"delta\":\"\\\"}\",\"item_id\":\"fc_054a16a2f2ea3217016a705b64fe5481a390185c9576df12b6\",\"obfuscation\":\"dJaOqvNAHb8EH5\",\"output_index\":0,\"sequence_number\":103}", - "event: response.function_call_arguments.done\ndata: {\"type\":\"response.function_call_arguments.done\",\"arguments\":\"{\\\"description\\\":\\\"Run e2e-flue-skill with marker SKILL_DONE\\\",\\\"prompt\\\":\\\"Run the skill named \\\\\\\"e2e-flue-skill\\\\\\\" with arguments {\\\\\\\"marker\\\\\\\":\\\\\\\"SKILL_DONE\\\\\\\"}. Ensure completion and report the final output marker only.\\\",\\\"agent\\\":\\\"e2e-flue-skill\\\",\\\"cwd\\\":\\\".\\\"}\",\"item_id\":\"fc_054a16a2f2ea3217016a705b64fe5481a390185c9576df12b6\",\"output_index\":0,\"sequence_number\":104}", - "event: response.output_item.done\ndata: {\"type\":\"response.output_item.done\",\"item\":{\"id\":\"fc_054a16a2f2ea3217016a705b64fe5481a390185c9576df12b6\",\"type\":\"function_call\",\"status\":\"completed\",\"arguments\":\"{\\\"description\\\":\\\"Run e2e-flue-skill with marker SKILL_DONE\\\",\\\"prompt\\\":\\\"Run the skill named \\\\\\\"e2e-flue-skill\\\\\\\" with arguments {\\\\\\\"marker\\\\\\\":\\\\\\\"SKILL_DONE\\\\\\\"}. Ensure completion and report the final output marker only.\\\",\\\"agent\\\":\\\"e2e-flue-skill\\\",\\\"cwd\\\":\\\".\\\"}\",\"call_id\":\"call_w6pyYZgnhlSOAXul75DAG1Cj\",\"name\":\"task\"},\"output_index\":0,\"sequence_number\":105}", - "event: response.completed\ndata: {\"type\":\"response.completed\",\"response\":{\"id\":\"resp_054a16a2f2ea3217016a705b648f7081a3b96733816812acb8\",\"object\":\"response\",\"created_at\":1785748324,\"status\":\"completed\",\"background\":false,\"completed_at\":1785748325,\"error\":null,\"frequency_penalty\":0,\"incomplete_details\":null,\"instructions\":null,\"max_output_tokens\":null,\"max_tool_calls\":null,\"model\":\"gpt-5.4-nano-2026-03-17\",\"moderation\":null,\"output\":[{\"id\":\"fc_054a16a2f2ea3217016a705b64fe5481a390185c9576df12b6\",\"type\":\"function_call\",\"status\":\"completed\",\"arguments\":\"{\\\"description\\\":\\\"Run e2e-flue-skill with marker SKILL_DONE\\\",\\\"prompt\\\":\\\"Run the skill named \\\\\\\"e2e-flue-skill\\\\\\\" with arguments {\\\\\\\"marker\\\\\\\":\\\\\\\"SKILL_DONE\\\\\\\"}. Ensure completion and report the final output marker only.\\\",\\\"agent\\\":\\\"e2e-flue-skill\\\",\\\"cwd\\\":\\\".\\\"}\",\"call_id\":\"call_w6pyYZgnhlSOAXul75DAG1Cj\",\"name\":\"task\"}],\"parallel_tool_calls\":true,\"presence_penalty\":0,\"previous_response_id\":null,\"prompt_cache_key\":\"workflow:instrumentation:01KZ3E9TWTAEK4P4P3F63HKAKC::default::ta\",\"prompt_cache_retention\":\"24h\",\"reasoning\":{\"context\":\"current_turn\",\"effort\":\"none\",\"mode\":\"standard\",\"summary\":null},\"safety_identifier\":null,\"service_tier\":\"default\",\"store\":false,\"temperature\":1,\"text\":{\"format\":{\"type\":\"text\"},\"verbosity\":\"medium\"},\"tool_choice\":\"auto\",\"tool_usage\":{\"image_gen\":{\"input_tokens\":0,\"input_tokens_details\":{\"image_tokens\":0,\"text_tokens\":0},\"output_tokens\":0,\"output_tokens_details\":{\"image_tokens\":0,\"text_tokens\":0},\"total_tokens\":0},\"web_search\":{\"num_requests\":0}},\"tools\":[{\"type\":\"function\",\"description\":\"Read a file or list a directory. For files, output is truncated to 2000 lines or 50KB — use offset/limit for large files. For directories, returns the list of entries.\",\"name\":\"read\",\"output_schema\":null,\"parameters\":{\"type\":\"object\",\"required\":[\"path\"],\"properties\":{\"path\":{\"type\":\"string\",\"description\":\"Path to the file to read\"},\"offset\":{\"type\":\"number\",\"description\":\"Line number to start from (1-indexed)\"},\"limit\":{\"type\":\"number\",\"description\":\"Maximum number of lines to read\"}}},\"strict\":false},{\"type\":\"function\",\"description\":\"Write content to a file. Creates the file and parent directories if they do not exist.\",\"name\":\"write\",\"output_schema\":null,\"parameters\":{\"type\":\"object\",\"required\":[\"path\",\"content\"],\"properties\":{\"path\":{\"type\":\"string\",\"description\":\"Path to the file to write\"},\"content\":{\"type\":\"string\",\"description\":\"Content to write to the file\"}}},\"strict\":false},{\"type\":\"function\",\"description\":\"Edit a file using exact text replacement. The oldText must match a unique region of the file. Use replaceAll to replace all occurrences.\",\"name\":\"edit\",\"output_schema\":null,\"parameters\":{\"type\":\"object\",\"required\":[\"path\",\"oldText\",\"newText\"],\"properties\":{\"path\":{\"type\":\"string\",\"description\":\"Path to the file to edit\"},\"oldText\":{\"type\":\"string\",\"description\":\"Exact text to find (must be unique)\"},\"newText\":{\"type\":\"string\",\"description\":\"Replacement text\"},\"replaceAll\":{\"type\":\"boolean\",\"description\":\"Replace all occurrences\"}}},\"strict\":false},{\"type\":\"function\",\"description\":\"Execute a bash command. Returns stdout and stderr. Output is truncated to the last 2000 lines or 50KB.\",\"name\":\"bash\",\"output_schema\":null,\"parameters\":{\"type\":\"object\",\"required\":[\"command\"],\"properties\":{\"command\":{\"type\":\"string\",\"description\":\"Bash command to execute\"},\"timeout\":{\"type\":\"number\",\"description\":\"Timeout in seconds\"}}},\"strict\":false},{\"type\":\"function\",\"description\":\"Search file contents for a regex pattern. Returns matching lines with file paths and line numbers.\",\"name\":\"grep\",\"output_schema\":null,\"parameters\":{\"type\":\"object\",\"required\":[\"pattern\"],\"properties\":{\"pattern\":{\"type\":\"string\",\"description\":\"Search pattern (regex)\"},\"path\":{\"type\":\"string\",\"description\":\"Directory or file to search (default: .)\"},\"include\":{\"type\":\"string\",\"description\":\"Glob filter, e.g. \\\"*.ts\\\"\"}}},\"strict\":false},{\"type\":\"function\",\"description\":\"Find files by filename pattern using shell find -name semantics. Returns matching file paths.\",\"name\":\"glob\",\"output_schema\":null,\"parameters\":{\"type\":\"object\",\"required\":[\"pattern\"],\"properties\":{\"pattern\":{\"type\":\"string\",\"description\":\"Filename pattern, e.g. \\\"*.ts\\\"\"},\"path\":{\"type\":\"string\",\"description\":\"Directory to search in (default: .)\"}}},\"strict\":false},{\"type\":\"function\",\"description\":\"Delegate a focused task to a detached child agent with its own context. Use this for independent research, file exploration, or parallel work. The task returns only its final answer to this conversation. No subagents are currently defined.\",\"name\":\"task\",\"output_schema\":null,\"parameters\":{\"type\":\"object\",\"required\":[\"prompt\"],\"properties\":{\"description\":{\"type\":\"string\",\"description\":\"Short human-readable label for the delegated work\"},\"prompt\":{\"type\":\"string\",\"description\":\"Focused instructions for the child agent\"},\"agent\":{\"type\":\"string\",\"description\":\"Declared subagent to use for the child agent\"},\"cwd\":{\"type\":\"string\",\"description\":\"Working directory for the child agent. AGENTS.md and skills are discovered from here.\"}}},\"strict\":false}],\"top_logprobs\":0,\"top_p\":0.98,\"truncation\":\"disabled\",\"usage\":{\"input_tokens\":855,\"input_tokens_details\":{\"cache_write_tokens\":0,\"cached_tokens\":0},\"output_tokens\":113,\"output_tokens_details\":{\"reasoning_tokens\":0},\"total_tokens\":968},\"user\":null,\"metadata\":{}},\"sequence_number\":106}" - ], - "kind": "sse" - }, - "headers": { - "access-control-expose-headers": "X-Request-ID, CF-Ray, CF-Ray", - "alt-svc": "h3=\":443\"; ma=86400", - "cf-cache-status": "DYNAMIC", - "cf-ray": "a25432d37fd37bb6-IAD", - "connection": "keep-alive", - "content-type": "text/event-stream; charset=utf-8", - "date": "Mon, 03 Aug 2026 09:12:04 GMT", - "openai-organization": "braintrust-data", - "openai-processing-ms": "268", - "openai-project": "proj_vsCSXafhhByzWOThMrJcZiw9", - "openai-version": "2020-10-01", - "server": "cloudflare", - "set-cookie": "[REDACTED]", - "strict-transport-security": "max-age=31536000; includeSubDomains; preload", - "transfer-encoding": "chunked", - "x-content-type-options": "nosniff", - "x-ratelimit-limit-requests": "30000", - "x-ratelimit-limit-tokens": "180000000", - "x-ratelimit-remaining-requests": "29999", - "x-ratelimit-remaining-tokens": "179998692", - "x-ratelimit-reset-requests": "2ms", - "x-ratelimit-reset-tokens": "0s", - "x-request-id": "req_bf6f5e02b3da40c19ef5905452f8e5f6" - }, - "status": 200, - "statusText": "OK" - } - }, - { - "callIndex": 6, - "id": "cbb9070d68f84d1b", - "matchKey": "POST api.openai.com/v1/responses", - "recordedAt": "2026-08-03T09:12:06.856Z", - "request": { - "body": { - "kind": "json", - "value": { - "input": [ - { - "content": "You are running in headless mode with no human operator. Work autonomously — never ask questions, never wait for user input. Make your best judgment and proceed independently.\n\nYou are a deterministic Flue instrumentation test agent. Follow user instructions exactly. When asked for a marker, output only that marker and no extra text. When running a local skill file, read it yourself and do not delegate it to a task.\n\n## Available Skills\n\nThe following skills provide specialized instructions for specific tasks. When a task matches a skill description, activate that skill before proceeding so its full instructions are loaded. Skill instructions and supporting resources stay lazy until activation or explicit file reads.\n\n- **e2e-flue-skill** — Deterministic skill used by the Braintrust Flue instrumentation e2e test.\n\nDate: Mon, Aug 3, 2026\nWorking directory: .\n\nDirectory structure:\n.agents\n.flue\n.flue-build\n__cassettes__\n__snapshots__\nassertions.ts\ncassette-filter.mjs\nconstants.mjs\ndist\nnode_modules\npackage.json\npnpm-lock.yaml\nscenario.cli.mjs\nscenario.impl.mjs\nscenario.mjs\nscenario.test.ts\nscenario.ts\nscenario.v2.ts\nv2-assertions.ts\nversions", - "role": "developer" - }, - { - "content": [ - { - "text": "Run the skill named \"e2e-flue-skill\" with arguments {\"marker\":\"SKILL_DONE\"}. Output only the marker and no extra text when finished.", - "type": "input_text" - } - ], - "role": "user" - }, - { - "arguments": "{\"description\":\"Run e2e-flue-skill with marker SKILL_DONE\",\"prompt\":\"Run the skill named \\\"e2e-flue-skill\\\" with arguments {\\\"marker\\\":\\\"SKILL_DONE\\\"}. Ensure completion and report the final output marker only.\",\"agent\":\"e2e-flue-skill\",\"cwd\":\".\"}", - "call_id": "call_w6pyYZgnhlSOAXul75DAG1Cj", - "id": "fc_054a16a2f2ea3217016a705b64fe5481a390185c9576df12b6", - "name": "task", - "type": "function_call" - }, - { - "call_id": "call_w6pyYZgnhlSOAXul75DAG1Cj", - "output": "[flue] Subagent \"e2e-flue-skill\" is not declared. Available: (none).", - "type": "function_call_output" - } - ], - "model": "gpt-5.4-nano", - "prompt_cache_key": "[REDACTED]", - "reasoning": { - "effort": "none" - }, - "store": false, - "stream": true, - "tools": [ - { - "description": "Read a file or list a directory. For files, output is truncated to 2000 lines or 50KB — use offset/limit for large files. For directories, returns the list of entries.", - "name": "read", - "parameters": { - "properties": { - "limit": { - "description": "Maximum number of lines to read", - "type": "number" - }, - "offset": { - "description": "Line number to start from (1-indexed)", - "type": "number" - }, - "path": { - "description": "Path to the file to read", - "type": "string" - } - }, - "required": ["path"], - "type": "object" - }, - "strict": false, - "type": "function" - }, - { - "description": "Write content to a file. Creates the file and parent directories if they do not exist.", - "name": "write", - "parameters": { - "properties": { - "content": { - "description": "Content to write to the file", - "type": "string" - }, - "path": { - "description": "Path to the file to write", - "type": "string" - } - }, - "required": ["path", "content"], - "type": "object" - }, - "strict": false, - "type": "function" - }, - { - "description": "Edit a file using exact text replacement. The oldText must match a unique region of the file. Use replaceAll to replace all occurrences.", - "name": "edit", - "parameters": { - "properties": { - "newText": { - "description": "Replacement text", - "type": "string" - }, - "oldText": { - "description": "Exact text to find (must be unique)", - "type": "string" - }, - "path": { - "description": "Path to the file to edit", - "type": "string" - }, - "replaceAll": { - "description": "Replace all occurrences", - "type": "boolean" - } - }, - "required": ["path", "oldText", "newText"], - "type": "object" - }, - "strict": false, - "type": "function" - }, - { - "description": "Execute a bash command. Returns stdout and stderr. Output is truncated to the last 2000 lines or 50KB.", - "name": "bash", - "parameters": { - "properties": { - "command": { - "description": "Bash command to execute", - "type": "string" - }, - "timeout": { - "description": "Timeout in seconds", - "type": "number" - } - }, - "required": ["command"], - "type": "object" - }, - "strict": false, - "type": "function" - }, - { - "description": "Search file contents for a regex pattern. Returns matching lines with file paths and line numbers.", - "name": "grep", - "parameters": { - "properties": { - "include": { - "description": "Glob filter, e.g. \"*.ts\"", - "type": "string" - }, - "path": { - "description": "Directory or file to search (default: .)", - "type": "string" - }, - "pattern": { - "description": "Search pattern (regex)", - "type": "string" - } - }, - "required": ["pattern"], - "type": "object" - }, - "strict": false, - "type": "function" - }, - { - "description": "Find files by filename pattern using shell find -name semantics. Returns matching file paths.", - "name": "glob", - "parameters": { - "properties": { - "path": { - "description": "Directory to search in (default: .)", - "type": "string" - }, - "pattern": { - "description": "Filename pattern, e.g. \"*.ts\"", - "type": "string" - } - }, - "required": ["pattern"], - "type": "object" - }, - "strict": false, - "type": "function" - }, - { - "description": "Delegate a focused task to a detached child agent with its own context. Use this for independent research, file exploration, or parallel work. The task returns only its final answer to this conversation. No subagents are currently defined.", - "name": "task", - "parameters": { - "properties": { - "agent": { - "description": "Declared subagent to use for the child agent", - "type": "string" - }, - "cwd": { - "description": "Working directory for the child agent. AGENTS.md and skills are discovered from here.", - "type": "string" - }, - "description": { - "description": "Short human-readable label for the delegated work", - "type": "string" - }, - "prompt": { - "description": "Focused instructions for the child agent", - "type": "string" - } - }, - "required": ["prompt"], - "type": "object" - }, - "strict": false, - "type": "function" - } - ] - } - }, - "headers": {}, - "method": "POST", - "url": "https://api.openai.com/v1/responses" - }, - "response": { - "body": { - "chunks": [ - "event: response.created\ndata: {\"type\":\"response.created\",\"response\":{\"id\":\"resp_054a16a2f2ea3217016a705b65dc0881a3a79d930ad7069d95\",\"object\":\"response\",\"created_at\":1785748325,\"status\":\"in_progress\",\"background\":false,\"completed_at\":null,\"error\":null,\"frequency_penalty\":0,\"incomplete_details\":null,\"instructions\":null,\"max_output_tokens\":null,\"max_tool_calls\":null,\"model\":\"gpt-5.4-nano-2026-03-17\",\"moderation\":null,\"output\":[],\"parallel_tool_calls\":true,\"presence_penalty\":0,\"previous_response_id\":null,\"prompt_cache_key\":\"workflow:instrumentation:01KZ3E9TWTAEK4P4P3F63HKAKC::default::ta\",\"prompt_cache_retention\":\"24h\",\"reasoning\":{\"context\":\"current_turn\",\"effort\":\"none\",\"mode\":\"standard\",\"summary\":null},\"safety_identifier\":null,\"service_tier\":\"auto\",\"store\":false,\"temperature\":1,\"text\":{\"format\":{\"type\":\"text\"},\"verbosity\":\"medium\"},\"tool_choice\":\"auto\",\"tool_usage\":{\"image_gen\":{\"input_tokens\":0,\"input_tokens_details\":{\"image_tokens\":0,\"text_tokens\":0},\"output_tokens\":0,\"output_tokens_details\":{\"image_tokens\":0,\"text_tokens\":0},\"total_tokens\":0},\"web_search\":{\"num_requests\":0}},\"tools\":[{\"type\":\"function\",\"description\":\"Read a file or list a directory. For files, output is truncated to 2000 lines or 50KB — use offset/limit for large files. For directories, returns the list of entries.\",\"name\":\"read\",\"output_schema\":null,\"parameters\":{\"type\":\"object\",\"required\":[\"path\"],\"properties\":{\"path\":{\"type\":\"string\",\"description\":\"Path to the file to read\"},\"offset\":{\"type\":\"number\",\"description\":\"Line number to start from (1-indexed)\"},\"limit\":{\"type\":\"number\",\"description\":\"Maximum number of lines to read\"}}},\"strict\":false},{\"type\":\"function\",\"description\":\"Write content to a file. Creates the file and parent directories if they do not exist.\",\"name\":\"write\",\"output_schema\":null,\"parameters\":{\"type\":\"object\",\"required\":[\"path\",\"content\"],\"properties\":{\"path\":{\"type\":\"string\",\"description\":\"Path to the file to write\"},\"content\":{\"type\":\"string\",\"description\":\"Content to write to the file\"}}},\"strict\":false},{\"type\":\"function\",\"description\":\"Edit a file using exact text replacement. The oldText must match a unique region of the file. Use replaceAll to replace all occurrences.\",\"name\":\"edit\",\"output_schema\":null,\"parameters\":{\"type\":\"object\",\"required\":[\"path\",\"oldText\",\"newText\"],\"properties\":{\"path\":{\"type\":\"string\",\"description\":\"Path to the file to edit\"},\"oldText\":{\"type\":\"string\",\"description\":\"Exact text to find (must be unique)\"},\"newText\":{\"type\":\"string\",\"description\":\"Replacement text\"},\"replaceAll\":{\"type\":\"boolean\",\"description\":\"Replace all occurrences\"}}},\"strict\":false},{\"type\":\"function\",\"description\":\"Execute a bash command. Returns stdout and stderr. Output is truncated to the last 2000 lines or 50KB.\",\"name\":\"bash\",\"output_schema\":null,\"parameters\":{\"type\":\"object\",\"required\":[\"command\"],\"properties\":{\"command\":{\"type\":\"string\",\"description\":\"Bash command to execute\"},\"timeout\":{\"type\":\"number\",\"description\":\"Timeout in seconds\"}}},\"strict\":false},{\"type\":\"function\",\"description\":\"Search file contents for a regex pattern. Returns matching lines with file paths and line numbers.\",\"name\":\"grep\",\"output_schema\":null,\"parameters\":{\"type\":\"object\",\"required\":[\"pattern\"],\"properties\":{\"pattern\":{\"type\":\"string\",\"description\":\"Search pattern (regex)\"},\"path\":{\"type\":\"string\",\"description\":\"Directory or file to search (default: .)\"},\"include\":{\"type\":\"string\",\"description\":\"Glob filter, e.g. \\\"*.ts\\\"\"}}},\"strict\":false},{\"type\":\"function\",\"description\":\"Find files by filename pattern using shell find -name semantics. Returns matching file paths.\",\"name\":\"glob\",\"output_schema\":null,\"parameters\":{\"type\":\"object\",\"required\":[\"pattern\"],\"properties\":{\"pattern\":{\"type\":\"string\",\"description\":\"Filename pattern, e.g. \\\"*.ts\\\"\"},\"path\":{\"type\":\"string\",\"description\":\"Directory to search in (default: .)\"}}},\"strict\":false},{\"type\":\"function\",\"description\":\"Delegate a focused task to a detached child agent with its own context. Use this for independent research, file exploration, or parallel work. The task returns only its final answer to this conversation. No subagents are currently defined.\",\"name\":\"task\",\"output_schema\":null,\"parameters\":{\"type\":\"object\",\"required\":[\"prompt\"],\"properties\":{\"description\":{\"type\":\"string\",\"description\":\"Short human-readable label for the delegated work\"},\"prompt\":{\"type\":\"string\",\"description\":\"Focused instructions for the child agent\"},\"agent\":{\"type\":\"string\",\"description\":\"Declared subagent to use for the child agent\"},\"cwd\":{\"type\":\"string\",\"description\":\"Working directory for the child agent. AGENTS.md and skills are discovered from here.\"}}},\"strict\":false}],\"top_logprobs\":0,\"top_p\":0.98,\"truncation\":\"disabled\",\"usage\":null,\"user\":null,\"metadata\":{}},\"sequence_number\":0}", - "event: response.in_progress\ndata: {\"type\":\"response.in_progress\",\"response\":{\"id\":\"resp_054a16a2f2ea3217016a705b65dc0881a3a79d930ad7069d95\",\"object\":\"response\",\"created_at\":1785748325,\"status\":\"in_progress\",\"background\":false,\"completed_at\":null,\"error\":null,\"frequency_penalty\":0,\"incomplete_details\":null,\"instructions\":null,\"max_output_tokens\":null,\"max_tool_calls\":null,\"model\":\"gpt-5.4-nano-2026-03-17\",\"moderation\":null,\"output\":[],\"parallel_tool_calls\":true,\"presence_penalty\":0,\"previous_response_id\":null,\"prompt_cache_key\":\"workflow:instrumentation:01KZ3E9TWTAEK4P4P3F63HKAKC::default::ta\",\"prompt_cache_retention\":\"24h\",\"reasoning\":{\"context\":\"current_turn\",\"effort\":\"none\",\"mode\":\"standard\",\"summary\":null},\"safety_identifier\":null,\"service_tier\":\"auto\",\"store\":false,\"temperature\":1,\"text\":{\"format\":{\"type\":\"text\"},\"verbosity\":\"medium\"},\"tool_choice\":\"auto\",\"tool_usage\":{\"image_gen\":{\"input_tokens\":0,\"input_tokens_details\":{\"image_tokens\":0,\"text_tokens\":0},\"output_tokens\":0,\"output_tokens_details\":{\"image_tokens\":0,\"text_tokens\":0},\"total_tokens\":0},\"web_search\":{\"num_requests\":0}},\"tools\":[{\"type\":\"function\",\"description\":\"Read a file or list a directory. For files, output is truncated to 2000 lines or 50KB — use offset/limit for large files. For directories, returns the list of entries.\",\"name\":\"read\",\"output_schema\":null,\"parameters\":{\"type\":\"object\",\"required\":[\"path\"],\"properties\":{\"path\":{\"type\":\"string\",\"description\":\"Path to the file to read\"},\"offset\":{\"type\":\"number\",\"description\":\"Line number to start from (1-indexed)\"},\"limit\":{\"type\":\"number\",\"description\":\"Maximum number of lines to read\"}}},\"strict\":false},{\"type\":\"function\",\"description\":\"Write content to a file. Creates the file and parent directories if they do not exist.\",\"name\":\"write\",\"output_schema\":null,\"parameters\":{\"type\":\"object\",\"required\":[\"path\",\"content\"],\"properties\":{\"path\":{\"type\":\"string\",\"description\":\"Path to the file to write\"},\"content\":{\"type\":\"string\",\"description\":\"Content to write to the file\"}}},\"strict\":false},{\"type\":\"function\",\"description\":\"Edit a file using exact text replacement. The oldText must match a unique region of the file. Use replaceAll to replace all occurrences.\",\"name\":\"edit\",\"output_schema\":null,\"parameters\":{\"type\":\"object\",\"required\":[\"path\",\"oldText\",\"newText\"],\"properties\":{\"path\":{\"type\":\"string\",\"description\":\"Path to the file to edit\"},\"oldText\":{\"type\":\"string\",\"description\":\"Exact text to find (must be unique)\"},\"newText\":{\"type\":\"string\",\"description\":\"Replacement text\"},\"replaceAll\":{\"type\":\"boolean\",\"description\":\"Replace all occurrences\"}}},\"strict\":false},{\"type\":\"function\",\"description\":\"Execute a bash command. Returns stdout and stderr. Output is truncated to the last 2000 lines or 50KB.\",\"name\":\"bash\",\"output_schema\":null,\"parameters\":{\"type\":\"object\",\"required\":[\"command\"],\"properties\":{\"command\":{\"type\":\"string\",\"description\":\"Bash command to execute\"},\"timeout\":{\"type\":\"number\",\"description\":\"Timeout in seconds\"}}},\"strict\":false},{\"type\":\"function\",\"description\":\"Search file contents for a regex pattern. Returns matching lines with file paths and line numbers.\",\"name\":\"grep\",\"output_schema\":null,\"parameters\":{\"type\":\"object\",\"required\":[\"pattern\"],\"properties\":{\"pattern\":{\"type\":\"string\",\"description\":\"Search pattern (regex)\"},\"path\":{\"type\":\"string\",\"description\":\"Directory or file to search (default: .)\"},\"include\":{\"type\":\"string\",\"description\":\"Glob filter, e.g. \\\"*.ts\\\"\"}}},\"strict\":false},{\"type\":\"function\",\"description\":\"Find files by filename pattern using shell find -name semantics. Returns matching file paths.\",\"name\":\"glob\",\"output_schema\":null,\"parameters\":{\"type\":\"object\",\"required\":[\"pattern\"],\"properties\":{\"pattern\":{\"type\":\"string\",\"description\":\"Filename pattern, e.g. \\\"*.ts\\\"\"},\"path\":{\"type\":\"string\",\"description\":\"Directory to search in (default: .)\"}}},\"strict\":false},{\"type\":\"function\",\"description\":\"Delegate a focused task to a detached child agent with its own context. Use this for independent research, file exploration, or parallel work. The task returns only its final answer to this conversation. No subagents are currently defined.\",\"name\":\"task\",\"output_schema\":null,\"parameters\":{\"type\":\"object\",\"required\":[\"prompt\"],\"properties\":{\"description\":{\"type\":\"string\",\"description\":\"Short human-readable label for the delegated work\"},\"prompt\":{\"type\":\"string\",\"description\":\"Focused instructions for the child agent\"},\"agent\":{\"type\":\"string\",\"description\":\"Declared subagent to use for the child agent\"},\"cwd\":{\"type\":\"string\",\"description\":\"Working directory for the child agent. AGENTS.md and skills are discovered from here.\"}}},\"strict\":false}],\"top_logprobs\":0,\"top_p\":0.98,\"truncation\":\"disabled\",\"usage\":null,\"user\":null,\"metadata\":{}},\"sequence_number\":1}", - "event: response.output_item.added\ndata: {\"type\":\"response.output_item.added\",\"item\":{\"id\":\"fc_054a16a2f2ea3217016a705b66460481a3a5de06df603e35ee\",\"type\":\"function_call\",\"status\":\"in_progress\",\"arguments\":\"\",\"call_id\":\"call_6zEBnJRmXFIkGFwNjWLZP4uC\",\"name\":\"bash\"},\"output_index\":0,\"sequence_number\":2}", - "event: response.function_call_arguments.delta\ndata: {\"type\":\"response.function_call_arguments.delta\",\"delta\":\"{\\\"\",\"item_id\":\"fc_054a16a2f2ea3217016a705b66460481a3a5de06df603e35ee\",\"obfuscation\":\"qYr2RE37LRYj8T\",\"output_index\":0,\"sequence_number\":3}", - "event: response.function_call_arguments.delta\ndata: {\"type\":\"response.function_call_arguments.delta\",\"delta\":\"command\",\"item_id\":\"fc_054a16a2f2ea3217016a705b66460481a3a5de06df603e35ee\",\"obfuscation\":\"5olycNCp4\",\"output_index\":0,\"sequence_number\":4}", - "event: response.function_call_arguments.delta\ndata: {\"type\":\"response.function_call_arguments.delta\",\"delta\":\"\\\":\\\"\",\"item_id\":\"fc_054a16a2f2ea3217016a705b66460481a3a5de06df603e35ee\",\"obfuscation\":\"TtIA15U6WTwyB\",\"output_index\":0,\"sequence_number\":5}", - "event: response.function_call_arguments.delta\ndata: {\"type\":\"response.function_call_arguments.delta\",\"delta\":\"ls\",\"item_id\":\"fc_054a16a2f2ea3217016a705b66460481a3a5de06df603e35ee\",\"obfuscation\":\"8gpmFqa6OBiMdp\",\"output_index\":0,\"sequence_number\":6}", - "event: response.function_call_arguments.delta\ndata: {\"type\":\"response.function_call_arguments.delta\",\"delta\":\" -\",\"item_id\":\"fc_054a16a2f2ea3217016a705b66460481a3a5de06df603e35ee\",\"obfuscation\":\"HqbfEOyA6n1XM5\",\"output_index\":0,\"sequence_number\":7}", - "event: response.function_call_arguments.delta\ndata: {\"type\":\"response.function_call_arguments.delta\",\"delta\":\"la\",\"item_id\":\"fc_054a16a2f2ea3217016a705b66460481a3a5de06df603e35ee\",\"obfuscation\":\"zkZ09ufXuvTuYH\",\"output_index\":0,\"sequence_number\":8}", - "event: response.function_call_arguments.delta\ndata: {\"type\":\"response.function_call_arguments.delta\",\"delta\":\" /\",\"item_id\":\"fc_054a16a2f2ea3217016a705b66460481a3a5de06df603e35ee\",\"obfuscation\":\"IsgyMPXjN3RyQT\",\"output_index\":0,\"sequence_number\":9}", - "event: response.function_call_arguments.delta\ndata: {\"type\":\"response.function_call_arguments.delta\",\"delta\":\"ver\",\"item_id\":\"fc_054a16a2f2ea3217016a705b66460481a3a5de06df603e35ee\",\"obfuscation\":\"QwfPCO1ymGdY7\",\"output_index\":0,\"sequence_number\":10}", - "event: response.function_call_arguments.delta\ndata: {\"type\":\"response.function_call_arguments.delta\",\"delta\":\"cel\",\"item_id\":\"fc_054a16a2f2ea3217016a705b66460481a3a5de06df603e35ee\",\"obfuscation\":\"ktEAC1neLBJD0\",\"output_index\":0,\"sequence_number\":11}", - "event: response.function_call_arguments.delta\ndata: {\"type\":\"response.function_call_arguments.delta\",\"delta\":\"/s\",\"item_id\":\"fc_054a16a2f2ea3217016a705b66460481a3a5de06df603e35ee\",\"obfuscation\":\"qSxXdSs4TsmFt5\",\"output_index\":0,\"sequence_number\":12}", - "event: response.function_call_arguments.delta\ndata: {\"type\":\"response.function_call_arguments.delta\",\"delta\":\"andbox\",\"item_id\":\"fc_054a16a2f2ea3217016a705b66460481a3a5de06df603e35ee\",\"obfuscation\":\"NIrFRZbher\",\"output_index\":0,\"sequence_number\":13}", - "event: response.function_call_arguments.delta\ndata: {\"type\":\"response.function_call_arguments.delta\",\"delta\":\"/\",\"item_id\":\"fc_054a16a2f2ea3217016a705b66460481a3a5de06df603e35ee\",\"obfuscation\":\"h1HKJqOxjX7neu9\",\"output_index\":0,\"sequence_number\":14}", - "event: response.function_call_arguments.delta\ndata: {\"type\":\"response.function_call_arguments.delta\",\"delta\":\"brain\",\"item_id\":\"fc_054a16a2f2ea3217016a705b66460481a3a5de06df603e35ee\",\"obfuscation\":\"g5de6pXeQeu\",\"output_index\":0,\"sequence_number\":15}", - "event: response.function_call_arguments.delta\ndata: {\"type\":\"response.function_call_arguments.delta\",\"delta\":\"trust\",\"item_id\":\"fc_054a16a2f2ea3217016a705b66460481a3a5de06df603e35ee\",\"obfuscation\":\"O8W7X6T6QJ9\",\"output_index\":0,\"sequence_number\":16}", - "event: response.function_call_arguments.delta\ndata: {\"type\":\"response.function_call_arguments.delta\",\"delta\":\"data\",\"item_id\":\"fc_054a16a2f2ea3217016a705b66460481a3a5de06df603e35ee\",\"obfuscation\":\"ZwdOUP0nNaaU\",\"output_index\":0,\"sequence_number\":17}", - "event: response.function_call_arguments.delta\ndata: {\"type\":\"response.function_call_arguments.delta\",\"delta\":\"/\",\"item_id\":\"fc_054a16a2f2ea3217016a705b66460481a3a5de06df603e35ee\",\"obfuscation\":\"RdDtnAGnoUWZHMl\",\"output_index\":0,\"sequence_number\":18}", - "event: response.function_call_arguments.delta\ndata: {\"type\":\"response.function_call_arguments.delta\",\"delta\":\"brain\",\"item_id\":\"fc_054a16a2f2ea3217016a705b66460481a3a5de06df603e35ee\",\"obfuscation\":\"bJadnE3z1Pn\",\"output_index\":0,\"sequence_number\":19}", - "event: response.function_call_arguments.delta\ndata: {\"type\":\"response.function_call_arguments.delta\",\"delta\":\"trust\",\"item_id\":\"fc_054a16a2f2ea3217016a705b66460481a3a5de06df603e35ee\",\"obfuscation\":\"QdtHJab2Nju\",\"output_index\":0,\"sequence_number\":20}", - "event: response.function_call_arguments.delta\ndata: {\"type\":\"response.function_call_arguments.delta\",\"delta\":\"-sdk\",\"item_id\":\"fc_054a16a2f2ea3217016a705b66460481a3a5de06df603e35ee\",\"obfuscation\":\"d0BalsPUN30E\",\"output_index\":0,\"sequence_number\":21}", - "event: response.function_call_arguments.delta\ndata: {\"type\":\"response.function_call_arguments.delta\",\"delta\":\"-j\",\"item_id\":\"fc_054a16a2f2ea3217016a705b66460481a3a5de06df603e35ee\",\"obfuscation\":\"zkZSKvyjjnJqmd\",\"output_index\":0,\"sequence_number\":22}", - "event: response.function_call_arguments.delta\ndata: {\"type\":\"response.function_call_arguments.delta\",\"delta\":\"avascript\",\"item_id\":\"fc_054a16a2f2ea3217016a705b66460481a3a5de06df603e35ee\",\"obfuscation\":\"gidR8ss\",\"output_index\":0,\"sequence_number\":23}", - "event: response.function_call_arguments.delta\ndata: {\"type\":\"response.function_call_arguments.delta\",\"delta\":\"/e\",\"item_id\":\"fc_054a16a2f2ea3217016a705b66460481a3a5de06df603e35ee\",\"obfuscation\":\"sTij2BeE8hhf4F\",\"output_index\":0,\"sequence_number\":24}", - "event: response.function_call_arguments.delta\ndata: {\"type\":\"response.function_call_arguments.delta\",\"delta\":\"2\",\"item_id\":\"fc_054a16a2f2ea3217016a705b66460481a3a5de06df603e35ee\",\"obfuscation\":\"U6tRlf8eVA0ZfFt\",\"output_index\":0,\"sequence_number\":25}", - "event: response.function_call_arguments.delta\ndata: {\"type\":\"response.function_call_arguments.delta\",\"delta\":\"e\",\"item_id\":\"fc_054a16a2f2ea3217016a705b66460481a3a5de06df603e35ee\",\"obfuscation\":\"3iym56BtyHXWWzj\",\"output_index\":0,\"sequence_number\":26}", - "event: response.function_call_arguments.delta\ndata: {\"type\":\"response.function_call_arguments.delta\",\"delta\":\"/.\",\"item_id\":\"fc_054a16a2f2ea3217016a705b66460481a3a5de06df603e35ee\",\"obfuscation\":\"yPazRePg1CYCjy\",\"output_index\":0,\"sequence_number\":27}", - "event: response.function_call_arguments.delta\ndata: {\"type\":\"response.function_call_arguments.delta\",\"delta\":\"bt\",\"item_id\":\"fc_054a16a2f2ea3217016a705b66460481a3a5de06df603e35ee\",\"obfuscation\":\"XlYRyV5eP5RuAi\",\"output_index\":0,\"sequence_number\":28}", - "event: response.function_call_arguments.delta\ndata: {\"type\":\"response.function_call_arguments.delta\",\"delta\":\"-t\",\"item_id\":\"fc_054a16a2f2ea3217016a705b66460481a3a5de06df603e35ee\",\"obfuscation\":\"cEUjV7aq8IJmrd\",\"output_index\":0,\"sequence_number\":29}", - "event: response.function_call_arguments.delta\ndata: {\"type\":\"response.function_call_arguments.delta\",\"delta\":\"mp\",\"item_id\":\"fc_054a16a2f2ea3217016a705b66460481a3a5de06df603e35ee\",\"obfuscation\":\"llpLkANV2zyfnb\",\"output_index\":0,\"sequence_number\":30}", - "event: response.function_call_arguments.delta\ndata: {\"type\":\"response.function_call_arguments.delta\",\"delta\":\"/run\",\"item_id\":\"fc_054a16a2f2ea3217016a705b66460481a3a5de06df603e35ee\",\"obfuscation\":\"flFQ8Wd13kY2\",\"output_index\":0,\"sequence_number\":31}", - "event: response.function_call_arguments.delta\ndata: {\"type\":\"response.function_call_arguments.delta\",\"delta\":\"-\",\"item_id\":\"fc_054a16a2f2ea3217016a705b66460481a3a5de06df603e35ee\",\"obfuscation\":\"6NV0XhXU74sXecB\",\"output_index\":0,\"sequence_number\":32}", - "event: response.function_call_arguments.delta\ndata: {\"type\":\"response.function_call_arguments.delta\",\"delta\":\"9\",\"item_id\":\"fc_054a16a2f2ea3217016a705b66460481a3a5de06df603e35ee\",\"obfuscation\":\"3e8DIPv3Q4zimTJ\",\"output_index\":0,\"sequence_number\":33}", - "event: response.function_call_arguments.delta\ndata: {\"type\":\"response.function_call_arguments.delta\",\"delta\":\"sp\",\"item_id\":\"fc_054a16a2f2ea3217016a705b66460481a3a5de06df603e35ee\",\"obfuscation\":\"CcYFgrShio2OGa\",\"output_index\":0,\"sequence_number\":34}", - "event: response.function_call_arguments.delta\ndata: {\"type\":\"response.function_call_arguments.delta\",\"delta\":\"u\",\"item_id\":\"fc_054a16a2f2ea3217016a705b66460481a3a5de06df603e35ee\",\"obfuscation\":\"SB8CftjcqKYqDAq\",\"output_index\":0,\"sequence_number\":35}", - "event: response.function_call_arguments.delta\ndata: {\"type\":\"response.function_call_arguments.delta\",\"delta\":\"Hg\",\"item_id\":\"fc_054a16a2f2ea3217016a705b66460481a3a5de06df603e35ee\",\"obfuscation\":\"o0TbOyqaOwnbjc\",\"output_index\":0,\"sequence_number\":36}", - "event: response.function_call_arguments.delta\ndata: {\"type\":\"response.function_call_arguments.delta\",\"delta\":\"/sc\",\"item_id\":\"fc_054a16a2f2ea3217016a705b66460481a3a5de06df603e35ee\",\"obfuscation\":\"AxgExC2R2UrOj\",\"output_index\":0,\"sequence_number\":37}", - "event: response.function_call_arguments.delta\ndata: {\"type\":\"response.function_call_arguments.delta\",\"delta\":\"enarios\",\"item_id\":\"fc_054a16a2f2ea3217016a705b66460481a3a5de06df603e35ee\",\"obfuscation\":\"GktP5dj47\",\"output_index\":0,\"sequence_number\":38}", - "event: response.function_call_arguments.delta\ndata: {\"type\":\"response.function_call_arguments.delta\",\"delta\":\"/fl\",\"item_id\":\"fc_054a16a2f2ea3217016a705b66460481a3a5de06df603e35ee\",\"obfuscation\":\"D8Qyuf0OZ5udR\",\"output_index\":0,\"sequence_number\":39}", - "event: response.function_call_arguments.delta\ndata: {\"type\":\"response.function_call_arguments.delta\",\"delta\":\"ue\",\"item_id\":\"fc_054a16a2f2ea3217016a705b66460481a3a5de06df603e35ee\",\"obfuscation\":\"rTVImQr26P3Vsq\",\"output_index\":0,\"sequence_number\":40}", - "event: response.function_call_arguments.delta\ndata: {\"type\":\"response.function_call_arguments.delta\",\"delta\":\"-in\",\"item_id\":\"fc_054a16a2f2ea3217016a705b66460481a3a5de06df603e35ee\",\"obfuscation\":\"SPXiRAIfqk20q\",\"output_index\":0,\"sequence_number\":41}", - "event: response.function_call_arguments.delta\ndata: {\"type\":\"response.function_call_arguments.delta\",\"delta\":\"strument\",\"item_id\":\"fc_054a16a2f2ea3217016a705b66460481a3a5de06df603e35ee\",\"obfuscation\":\"ukKgXlCi\",\"output_index\":0,\"sequence_number\":42}", - "event: response.function_call_arguments.delta\ndata: {\"type\":\"response.function_call_arguments.delta\",\"delta\":\"ation\",\"item_id\":\"fc_054a16a2f2ea3217016a705b66460481a3a5de06df603e35ee\",\"obfuscation\":\"q5sV1H9oEPR\",\"output_index\":0,\"sequence_number\":43}", - "event: response.function_call_arguments.delta\ndata: {\"type\":\"response.function_call_arguments.delta\",\"delta\":\"/.\",\"item_id\":\"fc_054a16a2f2ea3217016a705b66460481a3a5de06df603e35ee\",\"obfuscation\":\"Vbdq3gYacR4ydn\",\"output_index\":0,\"sequence_number\":44}", - "event: response.function_call_arguments.delta\ndata: {\"type\":\"response.function_call_arguments.delta\",\"delta\":\"agents\",\"item_id\":\"fc_054a16a2f2ea3217016a705b66460481a3a5de06df603e35ee\",\"obfuscation\":\"iYjX4AJgNl\",\"output_index\":0,\"sequence_number\":45}", - "event: response.function_call_arguments.delta\ndata: {\"type\":\"response.function_call_arguments.delta\",\"delta\":\" &&\",\"item_id\":\"fc_054a16a2f2ea3217016a705b66460481a3a5de06df603e35ee\",\"obfuscation\":\"QpXEqJPwcIzx8\",\"output_index\":0,\"sequence_number\":46}", - "event: response.function_call_arguments.delta\ndata: {\"type\":\"response.function_call_arguments.delta\",\"delta\":\" find\",\"item_id\":\"fc_054a16a2f2ea3217016a705b66460481a3a5de06df603e35ee\",\"obfuscation\":\"idAm4VrdUd9\",\"output_index\":0,\"sequence_number\":47}", - "event: response.function_call_arguments.delta\ndata: {\"type\":\"response.function_call_arguments.delta\",\"delta\":\" /\",\"item_id\":\"fc_054a16a2f2ea3217016a705b66460481a3a5de06df603e35ee\",\"obfuscation\":\"xlH3WH5e7K7TVT\",\"output_index\":0,\"sequence_number\":48}", - "event: response.function_call_arguments.delta\ndata: {\"type\":\"response.function_call_arguments.delta\",\"delta\":\"ver\",\"item_id\":\"fc_054a16a2f2ea3217016a705b66460481a3a5de06df603e35ee\",\"obfuscation\":\"ci7N2JhvoR4v3\",\"output_index\":0,\"sequence_number\":49}", - "event: response.function_call_arguments.delta\ndata: {\"type\":\"response.function_call_arguments.delta\",\"delta\":\"cel\",\"item_id\":\"fc_054a16a2f2ea3217016a705b66460481a3a5de06df603e35ee\",\"obfuscation\":\"lNESXh61G4kcn\",\"output_index\":0,\"sequence_number\":50}", - "event: response.function_call_arguments.delta\ndata: {\"type\":\"response.function_call_arguments.delta\",\"delta\":\"/s\",\"item_id\":\"fc_054a16a2f2ea3217016a705b66460481a3a5de06df603e35ee\",\"obfuscation\":\"JHLNRMdy9opWYh\",\"output_index\":0,\"sequence_number\":51}", - "event: response.function_call_arguments.delta\ndata: {\"type\":\"response.function_call_arguments.delta\",\"delta\":\"andbox\",\"item_id\":\"fc_054a16a2f2ea3217016a705b66460481a3a5de06df603e35ee\",\"obfuscation\":\"dgKik8Wq8j\",\"output_index\":0,\"sequence_number\":52}", - "event: response.function_call_arguments.delta\ndata: {\"type\":\"response.function_call_arguments.delta\",\"delta\":\"/\",\"item_id\":\"fc_054a16a2f2ea3217016a705b66460481a3a5de06df603e35ee\",\"obfuscation\":\"ivSoP1Az4lunbMK\",\"output_index\":0,\"sequence_number\":53}", - "event: response.function_call_arguments.delta\ndata: {\"type\":\"response.function_call_arguments.delta\",\"delta\":\"brain\",\"item_id\":\"fc_054a16a2f2ea3217016a705b66460481a3a5de06df603e35ee\",\"obfuscation\":\"ei4Les3EoOx\",\"output_index\":0,\"sequence_number\":54}", - "event: response.function_call_arguments.delta\ndata: {\"type\":\"response.function_call_arguments.delta\",\"delta\":\"trust\",\"item_id\":\"fc_054a16a2f2ea3217016a705b66460481a3a5de06df603e35ee\",\"obfuscation\":\"j583CvShsMn\",\"output_index\":0,\"sequence_number\":55}", - "event: response.function_call_arguments.delta\ndata: {\"type\":\"response.function_call_arguments.delta\",\"delta\":\"data\",\"item_id\":\"fc_054a16a2f2ea3217016a705b66460481a3a5de06df603e35ee\",\"obfuscation\":\"8pCfiAeFxnGs\",\"output_index\":0,\"sequence_number\":56}", - "event: response.function_call_arguments.delta\ndata: {\"type\":\"response.function_call_arguments.delta\",\"delta\":\"/\",\"item_id\":\"fc_054a16a2f2ea3217016a705b66460481a3a5de06df603e35ee\",\"obfuscation\":\"BrQSS7SwzKOQJMI\",\"output_index\":0,\"sequence_number\":57}", - "event: response.function_call_arguments.delta\ndata: {\"type\":\"response.function_call_arguments.delta\",\"delta\":\"brain\",\"item_id\":\"fc_054a16a2f2ea3217016a705b66460481a3a5de06df603e35ee\",\"obfuscation\":\"mq3EFmYB311\",\"output_index\":0,\"sequence_number\":58}", - "event: response.function_call_arguments.delta\ndata: {\"type\":\"response.function_call_arguments.delta\",\"delta\":\"trust\",\"item_id\":\"fc_054a16a2f2ea3217016a705b66460481a3a5de06df603e35ee\",\"obfuscation\":\"hO6jOpGPatJ\",\"output_index\":0,\"sequence_number\":59}", - "event: response.function_call_arguments.delta\ndata: {\"type\":\"response.function_call_arguments.delta\",\"delta\":\"-sdk\",\"item_id\":\"fc_054a16a2f2ea3217016a705b66460481a3a5de06df603e35ee\",\"obfuscation\":\"lbKGYWB3E1BM\",\"output_index\":0,\"sequence_number\":60}", - "event: response.function_call_arguments.delta\ndata: {\"type\":\"response.function_call_arguments.delta\",\"delta\":\"-j\",\"item_id\":\"fc_054a16a2f2ea3217016a705b66460481a3a5de06df603e35ee\",\"obfuscation\":\"u16CBhAKfJTH5L\",\"output_index\":0,\"sequence_number\":61}", - "event: response.function_call_arguments.delta\ndata: {\"type\":\"response.function_call_arguments.delta\",\"delta\":\"avascript\",\"item_id\":\"fc_054a16a2f2ea3217016a705b66460481a3a5de06df603e35ee\",\"obfuscation\":\"5hJroXc\",\"output_index\":0,\"sequence_number\":62}", - "event: response.function_call_arguments.delta\ndata: {\"type\":\"response.function_call_arguments.delta\",\"delta\":\"/e\",\"item_id\":\"fc_054a16a2f2ea3217016a705b66460481a3a5de06df603e35ee\",\"obfuscation\":\"33pMuKaTNDmMqZ\",\"output_index\":0,\"sequence_number\":63}", - "event: response.function_call_arguments.delta\ndata: {\"type\":\"response.function_call_arguments.delta\",\"delta\":\"2\",\"item_id\":\"fc_054a16a2f2ea3217016a705b66460481a3a5de06df603e35ee\",\"obfuscation\":\"7PfD7iSXELGdvpH\",\"output_index\":0,\"sequence_number\":64}", - "event: response.function_call_arguments.delta\ndata: {\"type\":\"response.function_call_arguments.delta\",\"delta\":\"e\",\"item_id\":\"fc_054a16a2f2ea3217016a705b66460481a3a5de06df603e35ee\",\"obfuscation\":\"l89ZUwAGJ4COOHT\",\"output_index\":0,\"sequence_number\":65}", - "event: response.function_call_arguments.delta\ndata: {\"type\":\"response.function_call_arguments.delta\",\"delta\":\" -\",\"item_id\":\"fc_054a16a2f2ea3217016a705b66460481a3a5de06df603e35ee\",\"obfuscation\":\"heNzYKqh1OCQTD\",\"output_index\":0,\"sequence_number\":66}", - "event: response.function_call_arguments.delta\ndata: {\"type\":\"response.function_call_arguments.delta\",\"delta\":\"max\",\"item_id\":\"fc_054a16a2f2ea3217016a705b66460481a3a5de06df603e35ee\",\"obfuscation\":\"sU1Q9AhPAADW3\",\"output_index\":0,\"sequence_number\":67}", - "event: response.function_call_arguments.delta\ndata: {\"type\":\"response.function_call_arguments.delta\",\"delta\":\"depth\",\"item_id\":\"fc_054a16a2f2ea3217016a705b66460481a3a5de06df603e35ee\",\"obfuscation\":\"G2KErWebcGP\",\"output_index\":0,\"sequence_number\":68}", - "event: response.function_call_arguments.delta\ndata: {\"type\":\"response.function_call_arguments.delta\",\"delta\":\" \",\"item_id\":\"fc_054a16a2f2ea3217016a705b66460481a3a5de06df603e35ee\",\"obfuscation\":\"Hk3AzR5adCxbnFT\",\"output_index\":0,\"sequence_number\":69}", - "event: response.function_call_arguments.delta\ndata: {\"type\":\"response.function_call_arguments.delta\",\"delta\":\"4\",\"item_id\":\"fc_054a16a2f2ea3217016a705b66460481a3a5de06df603e35ee\",\"obfuscation\":\"2AwssIctaSpQjDt\",\"output_index\":0,\"sequence_number\":70}", - "event: response.function_call_arguments.delta\ndata: {\"type\":\"response.function_call_arguments.delta\",\"delta\":\" -\",\"item_id\":\"fc_054a16a2f2ea3217016a705b66460481a3a5de06df603e35ee\",\"obfuscation\":\"qTL2wxyksScrpT\",\"output_index\":0,\"sequence_number\":71}", - "event: response.function_call_arguments.delta\ndata: {\"type\":\"response.function_call_arguments.delta\",\"delta\":\"name\",\"item_id\":\"fc_054a16a2f2ea3217016a705b66460481a3a5de06df603e35ee\",\"obfuscation\":\"9dAiG7pNttSR\",\"output_index\":0,\"sequence_number\":72}", - "event: response.function_call_arguments.delta\ndata: {\"type\":\"response.function_call_arguments.delta\",\"delta\":\" AG\",\"item_id\":\"fc_054a16a2f2ea3217016a705b66460481a3a5de06df603e35ee\",\"obfuscation\":\"vyWvpkSniB10E\",\"output_index\":0,\"sequence_number\":73}", - "event: response.function_call_arguments.delta\ndata: {\"type\":\"response.function_call_arguments.delta\",\"delta\":\"ENTS\",\"item_id\":\"fc_054a16a2f2ea3217016a705b66460481a3a5de06df603e35ee\",\"obfuscation\":\"8X34qqxb7k4T\",\"output_index\":0,\"sequence_number\":74}", - "event: response.function_call_arguments.delta\ndata: {\"type\":\"response.function_call_arguments.delta\",\"delta\":\".md\",\"item_id\":\"fc_054a16a2f2ea3217016a705b66460481a3a5de06df603e35ee\",\"obfuscation\":\"srlvyGZKNg8fy\",\"output_index\":0,\"sequence_number\":75}", - "event: response.function_call_arguments.delta\ndata: {\"type\":\"response.function_call_arguments.delta\",\"delta\":\" -\",\"item_id\":\"fc_054a16a2f2ea3217016a705b66460481a3a5de06df603e35ee\",\"obfuscation\":\"kX3C3ee4M4LAQZ\",\"output_index\":0,\"sequence_number\":76}", - "event: response.function_call_arguments.delta\ndata: {\"type\":\"response.function_call_arguments.delta\",\"delta\":\"print\",\"item_id\":\"fc_054a16a2f2ea3217016a705b66460481a3a5de06df603e35ee\",\"obfuscation\":\"kuuWd4dbfED\",\"output_index\":0,\"sequence_number\":77}", - "event: response.function_call_arguments.delta\ndata: {\"type\":\"response.function_call_arguments.delta\",\"delta\":\"\\\",\\\"\",\"item_id\":\"fc_054a16a2f2ea3217016a705b66460481a3a5de06df603e35ee\",\"obfuscation\":\"t1AZzeTRFs1dW\",\"output_index\":0,\"sequence_number\":78}", - "event: response.function_call_arguments.delta\ndata: {\"type\":\"response.function_call_arguments.delta\",\"delta\":\"timeout\",\"item_id\":\"fc_054a16a2f2ea3217016a705b66460481a3a5de06df603e35ee\",\"obfuscation\":\"J9nNvn2yP\",\"output_index\":0,\"sequence_number\":79}", - "event: response.function_call_arguments.delta\ndata: {\"type\":\"response.function_call_arguments.delta\",\"delta\":\"\\\":\",\"item_id\":\"fc_054a16a2f2ea3217016a705b66460481a3a5de06df603e35ee\",\"obfuscation\":\"HoGIKlERH8SubV\",\"output_index\":0,\"sequence_number\":80}", - "event: response.function_call_arguments.delta\ndata: {\"type\":\"response.function_call_arguments.delta\",\"delta\":\"100\",\"item_id\":\"fc_054a16a2f2ea3217016a705b66460481a3a5de06df603e35ee\",\"obfuscation\":\"K0vUuFSWy0H29\",\"output_index\":0,\"sequence_number\":81}", - "event: response.function_call_arguments.delta\ndata: {\"type\":\"response.function_call_arguments.delta\",\"delta\":\"000\",\"item_id\":\"fc_054a16a2f2ea3217016a705b66460481a3a5de06df603e35ee\",\"obfuscation\":\"iki9PdBtWpJFT\",\"output_index\":0,\"sequence_number\":82}", - "event: response.function_call_arguments.delta\ndata: {\"type\":\"response.function_call_arguments.delta\",\"delta\":\"}\",\"item_id\":\"fc_054a16a2f2ea3217016a705b66460481a3a5de06df603e35ee\",\"obfuscation\":\"XUhEnBL2ma1rfYy\",\"output_index\":0,\"sequence_number\":83}", - "event: response.function_call_arguments.done\ndata: {\"type\":\"response.function_call_arguments.done\",\"arguments\":\"{\\\"command\\\":\\\"find . -maxdepth 4 -name AGENTS.md -print\\\",\\\"timeout\\\":100000}\",\"item_id\":\"fc_054a16a2f2ea3217016a705b66460481a3a5de06df603e35ee\",\"output_index\":0,\"sequence_number\":84}", - "event: response.output_item.done\ndata: {\"type\":\"response.output_item.done\",\"item\":{\"id\":\"fc_054a16a2f2ea3217016a705b66460481a3a5de06df603e35ee\",\"type\":\"function_call\",\"status\":\"completed\",\"arguments\":\"{\\\"command\\\":\\\"find . -maxdepth 4 -name AGENTS.md -print\\\",\\\"timeout\\\":100000}\",\"call_id\":\"call_6zEBnJRmXFIkGFwNjWLZP4uC\",\"name\":\"bash\"},\"output_index\":0,\"sequence_number\":85}", - "event: response.completed\ndata: {\"type\":\"response.completed\",\"response\":{\"id\":\"resp_054a16a2f2ea3217016a705b65dc0881a3a79d930ad7069d95\",\"object\":\"response\",\"created_at\":1785748325,\"status\":\"completed\",\"background\":false,\"completed_at\":1785748326,\"error\":null,\"frequency_penalty\":0,\"incomplete_details\":null,\"instructions\":null,\"max_output_tokens\":null,\"max_tool_calls\":null,\"model\":\"gpt-5.4-nano-2026-03-17\",\"moderation\":null,\"output\":[{\"id\":\"fc_054a16a2f2ea3217016a705b66460481a3a5de06df603e35ee\",\"type\":\"function_call\",\"status\":\"completed\",\"arguments\":\"{\\\"command\\\":\\\"find . -maxdepth 4 -name AGENTS.md -print\\\",\\\"timeout\\\":100000}\",\"call_id\":\"call_6zEBnJRmXFIkGFwNjWLZP4uC\",\"name\":\"bash\"}],\"parallel_tool_calls\":true,\"presence_penalty\":0,\"previous_response_id\":null,\"prompt_cache_key\":\"workflow:instrumentation:01KZ3E9TWTAEK4P4P3F63HKAKC::default::ta\",\"prompt_cache_retention\":\"24h\",\"reasoning\":{\"context\":\"current_turn\",\"effort\":\"none\",\"mode\":\"standard\",\"summary\":null},\"safety_identifier\":null,\"service_tier\":\"default\",\"store\":false,\"temperature\":1,\"text\":{\"format\":{\"type\":\"text\"},\"verbosity\":\"medium\"},\"tool_choice\":\"auto\",\"tool_usage\":{\"image_gen\":{\"input_tokens\":0,\"input_tokens_details\":{\"image_tokens\":0,\"text_tokens\":0},\"output_tokens\":0,\"output_tokens_details\":{\"image_tokens\":0,\"text_tokens\":0},\"total_tokens\":0},\"web_search\":{\"num_requests\":0}},\"tools\":[{\"type\":\"function\",\"description\":\"Read a file or list a directory. For files, output is truncated to 2000 lines or 50KB — use offset/limit for large files. For directories, returns the list of entries.\",\"name\":\"read\",\"output_schema\":null,\"parameters\":{\"type\":\"object\",\"required\":[\"path\"],\"properties\":{\"path\":{\"type\":\"string\",\"description\":\"Path to the file to read\"},\"offset\":{\"type\":\"number\",\"description\":\"Line number to start from (1-indexed)\"},\"limit\":{\"type\":\"number\",\"description\":\"Maximum number of lines to read\"}}},\"strict\":false},{\"type\":\"function\",\"description\":\"Write content to a file. Creates the file and parent directories if they do not exist.\",\"name\":\"write\",\"output_schema\":null,\"parameters\":{\"type\":\"object\",\"required\":[\"path\",\"content\"],\"properties\":{\"path\":{\"type\":\"string\",\"description\":\"Path to the file to write\"},\"content\":{\"type\":\"string\",\"description\":\"Content to write to the file\"}}},\"strict\":false},{\"type\":\"function\",\"description\":\"Edit a file using exact text replacement. The oldText must match a unique region of the file. Use replaceAll to replace all occurrences.\",\"name\":\"edit\",\"output_schema\":null,\"parameters\":{\"type\":\"object\",\"required\":[\"path\",\"oldText\",\"newText\"],\"properties\":{\"path\":{\"type\":\"string\",\"description\":\"Path to the file to edit\"},\"oldText\":{\"type\":\"string\",\"description\":\"Exact text to find (must be unique)\"},\"newText\":{\"type\":\"string\",\"description\":\"Replacement text\"},\"replaceAll\":{\"type\":\"boolean\",\"description\":\"Replace all occurrences\"}}},\"strict\":false},{\"type\":\"function\",\"description\":\"Execute a bash command. Returns stdout and stderr. Output is truncated to the last 2000 lines or 50KB.\",\"name\":\"bash\",\"output_schema\":null,\"parameters\":{\"type\":\"object\",\"required\":[\"command\"],\"properties\":{\"command\":{\"type\":\"string\",\"description\":\"Bash command to execute\"},\"timeout\":{\"type\":\"number\",\"description\":\"Timeout in seconds\"}}},\"strict\":false},{\"type\":\"function\",\"description\":\"Search file contents for a regex pattern. Returns matching lines with file paths and line numbers.\",\"name\":\"grep\",\"output_schema\":null,\"parameters\":{\"type\":\"object\",\"required\":[\"pattern\"],\"properties\":{\"pattern\":{\"type\":\"string\",\"description\":\"Search pattern (regex)\"},\"path\":{\"type\":\"string\",\"description\":\"Directory or file to search (default: .)\"},\"include\":{\"type\":\"string\",\"description\":\"Glob filter, e.g. \\\"*.ts\\\"\"}}},\"strict\":false},{\"type\":\"function\",\"description\":\"Find files by filename pattern using shell find -name semantics. Returns matching file paths.\",\"name\":\"glob\",\"output_schema\":null,\"parameters\":{\"type\":\"object\",\"required\":[\"pattern\"],\"properties\":{\"pattern\":{\"type\":\"string\",\"description\":\"Filename pattern, e.g. \\\"*.ts\\\"\"},\"path\":{\"type\":\"string\",\"description\":\"Directory to search in (default: .)\"}}},\"strict\":false},{\"type\":\"function\",\"description\":\"Delegate a focused task to a detached child agent with its own context. Use this for independent research, file exploration, or parallel work. The task returns only its final answer to this conversation. No subagents are currently defined.\",\"name\":\"task\",\"output_schema\":null,\"parameters\":{\"type\":\"object\",\"required\":[\"prompt\"],\"properties\":{\"description\":{\"type\":\"string\",\"description\":\"Short human-readable label for the delegated work\"},\"prompt\":{\"type\":\"string\",\"description\":\"Focused instructions for the child agent\"},\"agent\":{\"type\":\"string\",\"description\":\"Declared subagent to use for the child agent\"},\"cwd\":{\"type\":\"string\",\"description\":\"Working directory for the child agent. AGENTS.md and skills are discovered from here.\"}}},\"strict\":false}],\"top_logprobs\":0,\"top_p\":0.98,\"truncation\":\"disabled\",\"usage\":{\"input_tokens\":1002,\"input_tokens_details\":{\"cache_write_tokens\":0,\"cached_tokens\":0},\"output_tokens\":94,\"output_tokens_details\":{\"reasoning_tokens\":0},\"total_tokens\":1096},\"user\":null,\"metadata\":{}},\"sequence_number\":86}" - ], - "kind": "sse" - }, - "headers": { - "access-control-expose-headers": "X-Request-ID, CF-Ray, CF-Ray", - "alt-svc": "h3=\":443\"; ma=86400", - "cf-cache-status": "DYNAMIC", - "cf-ray": "a25432dc5a4c7bb6-IAD", - "connection": "keep-alive", - "content-type": "text/event-stream; charset=utf-8", - "date": "Mon, 03 Aug 2026 09:12:06 GMT", - "openai-organization": "braintrust-data", - "openai-processing-ms": "201", - "openai-project": "proj_vsCSXafhhByzWOThMrJcZiw9", - "openai-version": "2020-10-01", - "server": "cloudflare", - "set-cookie": "[REDACTED]", - "strict-transport-security": "max-age=31536000; includeSubDomains; preload", - "transfer-encoding": "chunked", - "x-content-type-options": "nosniff", - "x-ratelimit-limit-requests": "30000", - "x-ratelimit-limit-tokens": "180000000", - "x-ratelimit-remaining-requests": "29999", - "x-ratelimit-remaining-tokens": "179998545", - "x-ratelimit-reset-requests": "2ms", - "x-ratelimit-reset-tokens": "0s", - "x-request-id": "req_3ec73f40ef984f3ea2f7b4df76b2890e" - }, - "status": 200, - "statusText": "OK" - } - }, - { - "callIndex": 7, - "id": "bccc54d212e2503d", - "matchKey": "POST api.openai.com/v1/responses", - "recordedAt": "2026-08-03T09:12:08.056Z", - "request": { - "body": { - "kind": "json", - "value": { - "input": [ - { - "content": "You are running in headless mode with no human operator. Work autonomously — never ask questions, never wait for user input. Make your best judgment and proceed independently.\n\nYou are a deterministic Flue instrumentation test agent. Follow user instructions exactly. When asked for a marker, output only that marker and no extra text. When running a local skill file, read it yourself and do not delegate it to a task.\n\n## Available Skills\n\nThe following skills provide specialized instructions for specific tasks. When a task matches a skill description, activate that skill before proceeding so its full instructions are loaded. Skill instructions and supporting resources stay lazy until activation or explicit file reads.\n\n- **e2e-flue-skill** — Deterministic skill used by the Braintrust Flue instrumentation e2e test.\n\nDate: Mon, Aug 3, 2026\nWorking directory: .\n\nDirectory structure:\n.agents\n.flue\n.flue-build\n__cassettes__\n__snapshots__\nassertions.ts\ncassette-filter.mjs\nconstants.mjs\ndist\nnode_modules\npackage.json\npnpm-lock.yaml\nscenario.cli.mjs\nscenario.impl.mjs\nscenario.mjs\nscenario.test.ts\nscenario.ts\nscenario.v2.ts\nv2-assertions.ts\nversions", - "role": "developer" - }, - { - "content": [ - { - "text": "Run the skill named \"e2e-flue-skill\" with arguments {\"marker\":\"SKILL_DONE\"}. Output only the marker and no extra text when finished.", - "type": "input_text" - } - ], - "role": "user" - }, - { - "arguments": "{\"description\":\"Run e2e-flue-skill with marker SKILL_DONE\",\"prompt\":\"Run the skill named \\\"e2e-flue-skill\\\" with arguments {\\\"marker\\\":\\\"SKILL_DONE\\\"}. Ensure completion and report the final output marker only.\",\"agent\":\"e2e-flue-skill\",\"cwd\":\".\"}", - "call_id": "call_w6pyYZgnhlSOAXul75DAG1Cj", - "id": "fc_054a16a2f2ea3217016a705b64fe5481a390185c9576df12b6", - "name": "task", - "type": "function_call" - }, - { - "call_id": "call_w6pyYZgnhlSOAXul75DAG1Cj", - "output": "[flue] Subagent \"e2e-flue-skill\" is not declared. Available: (none).", - "type": "function_call_output" - }, - { - "arguments": "{\"command\":\"find . -maxdepth 4 -name AGENTS.md -print\",\"timeout\":100000}", - "call_id": "call_6zEBnJRmXFIkGFwNjWLZP4uC", - "id": "fc_054a16a2f2ea3217016a705b66460481a3a5de06df603e35ee", - "name": "bash", - "type": "function_call" - }, - { - "call_id": "call_6zEBnJRmXFIkGFwNjWLZP4uC", - "output": "total 4\ndrwxr-xr-x 3 vercel-sandbox vercel-sandbox 20 Aug 3 09:10 .\ndrwxr-xr-x 10 vercel-sandbox vercel-sandbox 4096 Aug 3 09:11 ..\ndrwxr-xr-x 3 vercel-sandbox vercel-sandbox 28 Aug 3 09:10 skills", - "type": "function_call_output" - } - ], - "model": "gpt-5.4-nano", - "prompt_cache_key": "[REDACTED]", - "reasoning": { - "effort": "none" - }, - "store": false, - "stream": true, - "tools": [ - { - "description": "Read a file or list a directory. For files, output is truncated to 2000 lines or 50KB — use offset/limit for large files. For directories, returns the list of entries.", - "name": "read", - "parameters": { - "properties": { - "limit": { - "description": "Maximum number of lines to read", - "type": "number" - }, - "offset": { - "description": "Line number to start from (1-indexed)", - "type": "number" - }, - "path": { - "description": "Path to the file to read", - "type": "string" - } - }, - "required": ["path"], - "type": "object" - }, - "strict": false, - "type": "function" - }, - { - "description": "Write content to a file. Creates the file and parent directories if they do not exist.", - "name": "write", - "parameters": { - "properties": { - "content": { - "description": "Content to write to the file", - "type": "string" - }, - "path": { - "description": "Path to the file to write", - "type": "string" - } - }, - "required": ["path", "content"], - "type": "object" - }, - "strict": false, - "type": "function" - }, - { - "description": "Edit a file using exact text replacement. The oldText must match a unique region of the file. Use replaceAll to replace all occurrences.", - "name": "edit", - "parameters": { - "properties": { - "newText": { - "description": "Replacement text", - "type": "string" - }, - "oldText": { - "description": "Exact text to find (must be unique)", - "type": "string" - }, - "path": { - "description": "Path to the file to edit", - "type": "string" - }, - "replaceAll": { - "description": "Replace all occurrences", - "type": "boolean" - } - }, - "required": ["path", "oldText", "newText"], - "type": "object" - }, - "strict": false, - "type": "function" - }, - { - "description": "Execute a bash command. Returns stdout and stderr. Output is truncated to the last 2000 lines or 50KB.", - "name": "bash", - "parameters": { - "properties": { - "command": { - "description": "Bash command to execute", - "type": "string" - }, - "timeout": { - "description": "Timeout in seconds", - "type": "number" - } - }, - "required": ["command"], - "type": "object" - }, - "strict": false, - "type": "function" - }, - { - "description": "Search file contents for a regex pattern. Returns matching lines with file paths and line numbers.", - "name": "grep", - "parameters": { - "properties": { - "include": { - "description": "Glob filter, e.g. \"*.ts\"", - "type": "string" - }, - "path": { - "description": "Directory or file to search (default: .)", - "type": "string" - }, - "pattern": { - "description": "Search pattern (regex)", - "type": "string" - } - }, - "required": ["pattern"], - "type": "object" - }, - "strict": false, - "type": "function" - }, - { - "description": "Find files by filename pattern using shell find -name semantics. Returns matching file paths.", - "name": "glob", - "parameters": { - "properties": { - "path": { - "description": "Directory to search in (default: .)", - "type": "string" - }, - "pattern": { - "description": "Filename pattern, e.g. \"*.ts\"", - "type": "string" - } - }, - "required": ["pattern"], - "type": "object" - }, - "strict": false, - "type": "function" - }, - { - "description": "Delegate a focused task to a detached child agent with its own context. Use this for independent research, file exploration, or parallel work. The task returns only its final answer to this conversation. No subagents are currently defined.", - "name": "task", - "parameters": { - "properties": { - "agent": { - "description": "Declared subagent to use for the child agent", - "type": "string" - }, - "cwd": { - "description": "Working directory for the child agent. AGENTS.md and skills are discovered from here.", - "type": "string" - }, - "description": { - "description": "Short human-readable label for the delegated work", - "type": "string" - }, - "prompt": { - "description": "Focused instructions for the child agent", - "type": "string" - } - }, - "required": ["prompt"], - "type": "object" - }, - "strict": false, - "type": "function" - } - ] - } - }, - "headers": {}, - "method": "POST", - "url": "https://api.openai.com/v1/responses" - }, - "response": { - "body": { - "chunks": [ - "event: response.created\ndata: {\"type\":\"response.created\",\"response\":{\"id\":\"resp_054a16a2f2ea3217016a705b66ec3081a3b53487cd28a912dd\",\"object\":\"response\",\"created_at\":1785748326,\"status\":\"in_progress\",\"background\":false,\"completed_at\":null,\"error\":null,\"frequency_penalty\":0,\"incomplete_details\":null,\"instructions\":null,\"max_output_tokens\":null,\"max_tool_calls\":null,\"model\":\"gpt-5.4-nano-2026-03-17\",\"moderation\":null,\"output\":[],\"parallel_tool_calls\":true,\"presence_penalty\":0,\"previous_response_id\":null,\"prompt_cache_key\":\"workflow:instrumentation:01KZ3E9TWTAEK4P4P3F63HKAKC::default::ta\",\"prompt_cache_retention\":\"24h\",\"reasoning\":{\"context\":\"current_turn\",\"effort\":\"none\",\"mode\":\"standard\",\"summary\":null},\"safety_identifier\":null,\"service_tier\":\"auto\",\"store\":false,\"temperature\":1,\"text\":{\"format\":{\"type\":\"text\"},\"verbosity\":\"medium\"},\"tool_choice\":\"auto\",\"tool_usage\":{\"image_gen\":{\"input_tokens\":0,\"input_tokens_details\":{\"image_tokens\":0,\"text_tokens\":0},\"output_tokens\":0,\"output_tokens_details\":{\"image_tokens\":0,\"text_tokens\":0},\"total_tokens\":0},\"web_search\":{\"num_requests\":0}},\"tools\":[{\"type\":\"function\",\"description\":\"Read a file or list a directory. For files, output is truncated to 2000 lines or 50KB — use offset/limit for large files. For directories, returns the list of entries.\",\"name\":\"read\",\"output_schema\":null,\"parameters\":{\"type\":\"object\",\"required\":[\"path\"],\"properties\":{\"path\":{\"type\":\"string\",\"description\":\"Path to the file to read\"},\"offset\":{\"type\":\"number\",\"description\":\"Line number to start from (1-indexed)\"},\"limit\":{\"type\":\"number\",\"description\":\"Maximum number of lines to read\"}}},\"strict\":false},{\"type\":\"function\",\"description\":\"Write content to a file. Creates the file and parent directories if they do not exist.\",\"name\":\"write\",\"output_schema\":null,\"parameters\":{\"type\":\"object\",\"required\":[\"path\",\"content\"],\"properties\":{\"path\":{\"type\":\"string\",\"description\":\"Path to the file to write\"},\"content\":{\"type\":\"string\",\"description\":\"Content to write to the file\"}}},\"strict\":false},{\"type\":\"function\",\"description\":\"Edit a file using exact text replacement. The oldText must match a unique region of the file. Use replaceAll to replace all occurrences.\",\"name\":\"edit\",\"output_schema\":null,\"parameters\":{\"type\":\"object\",\"required\":[\"path\",\"oldText\",\"newText\"],\"properties\":{\"path\":{\"type\":\"string\",\"description\":\"Path to the file to edit\"},\"oldText\":{\"type\":\"string\",\"description\":\"Exact text to find (must be unique)\"},\"newText\":{\"type\":\"string\",\"description\":\"Replacement text\"},\"replaceAll\":{\"type\":\"boolean\",\"description\":\"Replace all occurrences\"}}},\"strict\":false},{\"type\":\"function\",\"description\":\"Execute a bash command. Returns stdout and stderr. Output is truncated to the last 2000 lines or 50KB.\",\"name\":\"bash\",\"output_schema\":null,\"parameters\":{\"type\":\"object\",\"required\":[\"command\"],\"properties\":{\"command\":{\"type\":\"string\",\"description\":\"Bash command to execute\"},\"timeout\":{\"type\":\"number\",\"description\":\"Timeout in seconds\"}}},\"strict\":false},{\"type\":\"function\",\"description\":\"Search file contents for a regex pattern. Returns matching lines with file paths and line numbers.\",\"name\":\"grep\",\"output_schema\":null,\"parameters\":{\"type\":\"object\",\"required\":[\"pattern\"],\"properties\":{\"pattern\":{\"type\":\"string\",\"description\":\"Search pattern (regex)\"},\"path\":{\"type\":\"string\",\"description\":\"Directory or file to search (default: .)\"},\"include\":{\"type\":\"string\",\"description\":\"Glob filter, e.g. \\\"*.ts\\\"\"}}},\"strict\":false},{\"type\":\"function\",\"description\":\"Find files by filename pattern using shell find -name semantics. Returns matching file paths.\",\"name\":\"glob\",\"output_schema\":null,\"parameters\":{\"type\":\"object\",\"required\":[\"pattern\"],\"properties\":{\"pattern\":{\"type\":\"string\",\"description\":\"Filename pattern, e.g. \\\"*.ts\\\"\"},\"path\":{\"type\":\"string\",\"description\":\"Directory to search in (default: .)\"}}},\"strict\":false},{\"type\":\"function\",\"description\":\"Delegate a focused task to a detached child agent with its own context. Use this for independent research, file exploration, or parallel work. The task returns only its final answer to this conversation. No subagents are currently defined.\",\"name\":\"task\",\"output_schema\":null,\"parameters\":{\"type\":\"object\",\"required\":[\"prompt\"],\"properties\":{\"description\":{\"type\":\"string\",\"description\":\"Short human-readable label for the delegated work\"},\"prompt\":{\"type\":\"string\",\"description\":\"Focused instructions for the child agent\"},\"agent\":{\"type\":\"string\",\"description\":\"Declared subagent to use for the child agent\"},\"cwd\":{\"type\":\"string\",\"description\":\"Working directory for the child agent. AGENTS.md and skills are discovered from here.\"}}},\"strict\":false}],\"top_logprobs\":0,\"top_p\":0.98,\"truncation\":\"disabled\",\"usage\":null,\"user\":null,\"metadata\":{}},\"sequence_number\":0}", - "event: response.in_progress\ndata: {\"type\":\"response.in_progress\",\"response\":{\"id\":\"resp_054a16a2f2ea3217016a705b66ec3081a3b53487cd28a912dd\",\"object\":\"response\",\"created_at\":1785748326,\"status\":\"in_progress\",\"background\":false,\"completed_at\":null,\"error\":null,\"frequency_penalty\":0,\"incomplete_details\":null,\"instructions\":null,\"max_output_tokens\":null,\"max_tool_calls\":null,\"model\":\"gpt-5.4-nano-2026-03-17\",\"moderation\":null,\"output\":[],\"parallel_tool_calls\":true,\"presence_penalty\":0,\"previous_response_id\":null,\"prompt_cache_key\":\"workflow:instrumentation:01KZ3E9TWTAEK4P4P3F63HKAKC::default::ta\",\"prompt_cache_retention\":\"24h\",\"reasoning\":{\"context\":\"current_turn\",\"effort\":\"none\",\"mode\":\"standard\",\"summary\":null},\"safety_identifier\":null,\"service_tier\":\"auto\",\"store\":false,\"temperature\":1,\"text\":{\"format\":{\"type\":\"text\"},\"verbosity\":\"medium\"},\"tool_choice\":\"auto\",\"tool_usage\":{\"image_gen\":{\"input_tokens\":0,\"input_tokens_details\":{\"image_tokens\":0,\"text_tokens\":0},\"output_tokens\":0,\"output_tokens_details\":{\"image_tokens\":0,\"text_tokens\":0},\"total_tokens\":0},\"web_search\":{\"num_requests\":0}},\"tools\":[{\"type\":\"function\",\"description\":\"Read a file or list a directory. For files, output is truncated to 2000 lines or 50KB — use offset/limit for large files. For directories, returns the list of entries.\",\"name\":\"read\",\"output_schema\":null,\"parameters\":{\"type\":\"object\",\"required\":[\"path\"],\"properties\":{\"path\":{\"type\":\"string\",\"description\":\"Path to the file to read\"},\"offset\":{\"type\":\"number\",\"description\":\"Line number to start from (1-indexed)\"},\"limit\":{\"type\":\"number\",\"description\":\"Maximum number of lines to read\"}}},\"strict\":false},{\"type\":\"function\",\"description\":\"Write content to a file. Creates the file and parent directories if they do not exist.\",\"name\":\"write\",\"output_schema\":null,\"parameters\":{\"type\":\"object\",\"required\":[\"path\",\"content\"],\"properties\":{\"path\":{\"type\":\"string\",\"description\":\"Path to the file to write\"},\"content\":{\"type\":\"string\",\"description\":\"Content to write to the file\"}}},\"strict\":false},{\"type\":\"function\",\"description\":\"Edit a file using exact text replacement. The oldText must match a unique region of the file. Use replaceAll to replace all occurrences.\",\"name\":\"edit\",\"output_schema\":null,\"parameters\":{\"type\":\"object\",\"required\":[\"path\",\"oldText\",\"newText\"],\"properties\":{\"path\":{\"type\":\"string\",\"description\":\"Path to the file to edit\"},\"oldText\":{\"type\":\"string\",\"description\":\"Exact text to find (must be unique)\"},\"newText\":{\"type\":\"string\",\"description\":\"Replacement text\"},\"replaceAll\":{\"type\":\"boolean\",\"description\":\"Replace all occurrences\"}}},\"strict\":false},{\"type\":\"function\",\"description\":\"Execute a bash command. Returns stdout and stderr. Output is truncated to the last 2000 lines or 50KB.\",\"name\":\"bash\",\"output_schema\":null,\"parameters\":{\"type\":\"object\",\"required\":[\"command\"],\"properties\":{\"command\":{\"type\":\"string\",\"description\":\"Bash command to execute\"},\"timeout\":{\"type\":\"number\",\"description\":\"Timeout in seconds\"}}},\"strict\":false},{\"type\":\"function\",\"description\":\"Search file contents for a regex pattern. Returns matching lines with file paths and line numbers.\",\"name\":\"grep\",\"output_schema\":null,\"parameters\":{\"type\":\"object\",\"required\":[\"pattern\"],\"properties\":{\"pattern\":{\"type\":\"string\",\"description\":\"Search pattern (regex)\"},\"path\":{\"type\":\"string\",\"description\":\"Directory or file to search (default: .)\"},\"include\":{\"type\":\"string\",\"description\":\"Glob filter, e.g. \\\"*.ts\\\"\"}}},\"strict\":false},{\"type\":\"function\",\"description\":\"Find files by filename pattern using shell find -name semantics. Returns matching file paths.\",\"name\":\"glob\",\"output_schema\":null,\"parameters\":{\"type\":\"object\",\"required\":[\"pattern\"],\"properties\":{\"pattern\":{\"type\":\"string\",\"description\":\"Filename pattern, e.g. \\\"*.ts\\\"\"},\"path\":{\"type\":\"string\",\"description\":\"Directory to search in (default: .)\"}}},\"strict\":false},{\"type\":\"function\",\"description\":\"Delegate a focused task to a detached child agent with its own context. Use this for independent research, file exploration, or parallel work. The task returns only its final answer to this conversation. No subagents are currently defined.\",\"name\":\"task\",\"output_schema\":null,\"parameters\":{\"type\":\"object\",\"required\":[\"prompt\"],\"properties\":{\"description\":{\"type\":\"string\",\"description\":\"Short human-readable label for the delegated work\"},\"prompt\":{\"type\":\"string\",\"description\":\"Focused instructions for the child agent\"},\"agent\":{\"type\":\"string\",\"description\":\"Declared subagent to use for the child agent\"},\"cwd\":{\"type\":\"string\",\"description\":\"Working directory for the child agent. AGENTS.md and skills are discovered from here.\"}}},\"strict\":false}],\"top_logprobs\":0,\"top_p\":0.98,\"truncation\":\"disabled\",\"usage\":null,\"user\":null,\"metadata\":{}},\"sequence_number\":1}", - "event: response.output_item.added\ndata: {\"type\":\"response.output_item.added\",\"item\":{\"id\":\"fc_054a16a2f2ea3217016a705b67575881a3aacde02b9053a321\",\"type\":\"function_call\",\"status\":\"in_progress\",\"arguments\":\"\",\"call_id\":\"call_jQUCie0ZgaXUrZCDcezp0msl\",\"name\":\"bash\"},\"output_index\":0,\"sequence_number\":2}", - "event: response.function_call_arguments.delta\ndata: {\"type\":\"response.function_call_arguments.delta\",\"delta\":\"{\\\"\",\"item_id\":\"fc_054a16a2f2ea3217016a705b67575881a3aacde02b9053a321\",\"obfuscation\":\"5s5fPUr1lnx6LN\",\"output_index\":0,\"sequence_number\":3}", - "event: response.function_call_arguments.delta\ndata: {\"type\":\"response.function_call_arguments.delta\",\"delta\":\"command\",\"item_id\":\"fc_054a16a2f2ea3217016a705b67575881a3aacde02b9053a321\",\"obfuscation\":\"EzIXsl5P7\",\"output_index\":0,\"sequence_number\":4}", - "event: response.function_call_arguments.delta\ndata: {\"type\":\"response.function_call_arguments.delta\",\"delta\":\"\\\":\\\"\",\"item_id\":\"fc_054a16a2f2ea3217016a705b67575881a3aacde02b9053a321\",\"obfuscation\":\"rfpcd360vn0Zc\",\"output_index\":0,\"sequence_number\":5}", - "event: response.function_call_arguments.delta\ndata: {\"type\":\"response.function_call_arguments.delta\",\"delta\":\"find\",\"item_id\":\"fc_054a16a2f2ea3217016a705b67575881a3aacde02b9053a321\",\"obfuscation\":\"gbSugqPh322J\",\"output_index\":0,\"sequence_number\":6}", - "event: response.function_call_arguments.delta\ndata: {\"type\":\"response.function_call_arguments.delta\",\"delta\":\" /\",\"item_id\":\"fc_054a16a2f2ea3217016a705b67575881a3aacde02b9053a321\",\"obfuscation\":\"un8JuSPgzNW7Tq\",\"output_index\":0,\"sequence_number\":7}", - "event: response.function_call_arguments.delta\ndata: {\"type\":\"response.function_call_arguments.delta\",\"delta\":\"ver\",\"item_id\":\"fc_054a16a2f2ea3217016a705b67575881a3aacde02b9053a321\",\"obfuscation\":\"AT24QvJgyIKCb\",\"output_index\":0,\"sequence_number\":8}", - "event: response.function_call_arguments.delta\ndata: {\"type\":\"response.function_call_arguments.delta\",\"delta\":\"cel\",\"item_id\":\"fc_054a16a2f2ea3217016a705b67575881a3aacde02b9053a321\",\"obfuscation\":\"M2WqPhiDm0W0B\",\"output_index\":0,\"sequence_number\":9}", - "event: response.function_call_arguments.delta\ndata: {\"type\":\"response.function_call_arguments.delta\",\"delta\":\"/s\",\"item_id\":\"fc_054a16a2f2ea3217016a705b67575881a3aacde02b9053a321\",\"obfuscation\":\"HnCl5JHASkkvRV\",\"output_index\":0,\"sequence_number\":10}", - "event: response.function_call_arguments.delta\ndata: {\"type\":\"response.function_call_arguments.delta\",\"delta\":\"andbox\",\"item_id\":\"fc_054a16a2f2ea3217016a705b67575881a3aacde02b9053a321\",\"obfuscation\":\"9hqImMoIYc\",\"output_index\":0,\"sequence_number\":11}", - "event: response.function_call_arguments.delta\ndata: {\"type\":\"response.function_call_arguments.delta\",\"delta\":\"/\",\"item_id\":\"fc_054a16a2f2ea3217016a705b67575881a3aacde02b9053a321\",\"obfuscation\":\"G2MFkp35u1CeVM3\",\"output_index\":0,\"sequence_number\":12}", - "event: response.function_call_arguments.delta\ndata: {\"type\":\"response.function_call_arguments.delta\",\"delta\":\"brain\",\"item_id\":\"fc_054a16a2f2ea3217016a705b67575881a3aacde02b9053a321\",\"obfuscation\":\"S9DNif6AAaj\",\"output_index\":0,\"sequence_number\":13}", - "event: response.function_call_arguments.delta\ndata: {\"type\":\"response.function_call_arguments.delta\",\"delta\":\"trust\",\"item_id\":\"fc_054a16a2f2ea3217016a705b67575881a3aacde02b9053a321\",\"obfuscation\":\"KFRgE1qwn8O\",\"output_index\":0,\"sequence_number\":14}", - "event: response.function_call_arguments.delta\ndata: {\"type\":\"response.function_call_arguments.delta\",\"delta\":\"data\",\"item_id\":\"fc_054a16a2f2ea3217016a705b67575881a3aacde02b9053a321\",\"obfuscation\":\"iRZhcqXd9XRs\",\"output_index\":0,\"sequence_number\":15}", - "event: response.function_call_arguments.delta\ndata: {\"type\":\"response.function_call_arguments.delta\",\"delta\":\"/\",\"item_id\":\"fc_054a16a2f2ea3217016a705b67575881a3aacde02b9053a321\",\"obfuscation\":\"9XF5P57pZo3uNAy\",\"output_index\":0,\"sequence_number\":16}", - "event: response.function_call_arguments.delta\ndata: {\"type\":\"response.function_call_arguments.delta\",\"delta\":\"brain\",\"item_id\":\"fc_054a16a2f2ea3217016a705b67575881a3aacde02b9053a321\",\"obfuscation\":\"4P9IWtruXv8\",\"output_index\":0,\"sequence_number\":17}", - "event: response.function_call_arguments.delta\ndata: {\"type\":\"response.function_call_arguments.delta\",\"delta\":\"trust\",\"item_id\":\"fc_054a16a2f2ea3217016a705b67575881a3aacde02b9053a321\",\"obfuscation\":\"6tLj1KQL0OS\",\"output_index\":0,\"sequence_number\":18}", - "event: response.function_call_arguments.delta\ndata: {\"type\":\"response.function_call_arguments.delta\",\"delta\":\"-sdk\",\"item_id\":\"fc_054a16a2f2ea3217016a705b67575881a3aacde02b9053a321\",\"obfuscation\":\"ZJX7xdMeL6wZ\",\"output_index\":0,\"sequence_number\":19}", - "event: response.function_call_arguments.delta\ndata: {\"type\":\"response.function_call_arguments.delta\",\"delta\":\"-j\",\"item_id\":\"fc_054a16a2f2ea3217016a705b67575881a3aacde02b9053a321\",\"obfuscation\":\"LJ7CGVyndyKtYf\",\"output_index\":0,\"sequence_number\":20}", - "event: response.function_call_arguments.delta\ndata: {\"type\":\"response.function_call_arguments.delta\",\"delta\":\"avascript\",\"item_id\":\"fc_054a16a2f2ea3217016a705b67575881a3aacde02b9053a321\",\"obfuscation\":\"xJcnaku\",\"output_index\":0,\"sequence_number\":21}", - "event: response.function_call_arguments.delta\ndata: {\"type\":\"response.function_call_arguments.delta\",\"delta\":\"/e\",\"item_id\":\"fc_054a16a2f2ea3217016a705b67575881a3aacde02b9053a321\",\"obfuscation\":\"PfTOXv6id59uZk\",\"output_index\":0,\"sequence_number\":22}", - "event: response.function_call_arguments.delta\ndata: {\"type\":\"response.function_call_arguments.delta\",\"delta\":\"2\",\"item_id\":\"fc_054a16a2f2ea3217016a705b67575881a3aacde02b9053a321\",\"obfuscation\":\"ZygymPXfo8RsWR7\",\"output_index\":0,\"sequence_number\":23}", - "event: response.function_call_arguments.delta\ndata: {\"type\":\"response.function_call_arguments.delta\",\"delta\":\"e\",\"item_id\":\"fc_054a16a2f2ea3217016a705b67575881a3aacde02b9053a321\",\"obfuscation\":\"tcZjEPZR3q54QCO\",\"output_index\":0,\"sequence_number\":24}", - "event: response.function_call_arguments.delta\ndata: {\"type\":\"response.function_call_arguments.delta\",\"delta\":\"/.\",\"item_id\":\"fc_054a16a2f2ea3217016a705b67575881a3aacde02b9053a321\",\"obfuscation\":\"Is3Ft8kklGHlSC\",\"output_index\":0,\"sequence_number\":25}", - "event: response.function_call_arguments.delta\ndata: {\"type\":\"response.function_call_arguments.delta\",\"delta\":\"bt\",\"item_id\":\"fc_054a16a2f2ea3217016a705b67575881a3aacde02b9053a321\",\"obfuscation\":\"J9UUPkfaryOCWA\",\"output_index\":0,\"sequence_number\":26}", - "event: response.function_call_arguments.delta\ndata: {\"type\":\"response.function_call_arguments.delta\",\"delta\":\"-t\",\"item_id\":\"fc_054a16a2f2ea3217016a705b67575881a3aacde02b9053a321\",\"obfuscation\":\"tUpNGsjI3owh2O\",\"output_index\":0,\"sequence_number\":27}", - "event: response.function_call_arguments.delta\ndata: {\"type\":\"response.function_call_arguments.delta\",\"delta\":\"mp\",\"item_id\":\"fc_054a16a2f2ea3217016a705b67575881a3aacde02b9053a321\",\"obfuscation\":\"uhvu4t3Z33cBrQ\",\"output_index\":0,\"sequence_number\":28}", - "event: response.function_call_arguments.delta\ndata: {\"type\":\"response.function_call_arguments.delta\",\"delta\":\"/run\",\"item_id\":\"fc_054a16a2f2ea3217016a705b67575881a3aacde02b9053a321\",\"obfuscation\":\"EY7RAwkNVqHj\",\"output_index\":0,\"sequence_number\":29}", - "event: response.function_call_arguments.delta\ndata: {\"type\":\"response.function_call_arguments.delta\",\"delta\":\"-\",\"item_id\":\"fc_054a16a2f2ea3217016a705b67575881a3aacde02b9053a321\",\"obfuscation\":\"WzBJvCmbkvTjzRe\",\"output_index\":0,\"sequence_number\":30}", - "event: response.function_call_arguments.delta\ndata: {\"type\":\"response.function_call_arguments.delta\",\"delta\":\"9\",\"item_id\":\"fc_054a16a2f2ea3217016a705b67575881a3aacde02b9053a321\",\"obfuscation\":\"T1c8ikRVDyXs3K4\",\"output_index\":0,\"sequence_number\":31}", - "event: response.function_call_arguments.delta\ndata: {\"type\":\"response.function_call_arguments.delta\",\"delta\":\"sp\",\"item_id\":\"fc_054a16a2f2ea3217016a705b67575881a3aacde02b9053a321\",\"obfuscation\":\"TLa4CtuK49G4fL\",\"output_index\":0,\"sequence_number\":32}", - "event: response.function_call_arguments.delta\ndata: {\"type\":\"response.function_call_arguments.delta\",\"delta\":\"u\",\"item_id\":\"fc_054a16a2f2ea3217016a705b67575881a3aacde02b9053a321\",\"obfuscation\":\"vUKKjMsXw5sIkHv\",\"output_index\":0,\"sequence_number\":33}", - "event: response.function_call_arguments.delta\ndata: {\"type\":\"response.function_call_arguments.delta\",\"delta\":\"Hg\",\"item_id\":\"fc_054a16a2f2ea3217016a705b67575881a3aacde02b9053a321\",\"obfuscation\":\"SD4uFMMoVX66s1\",\"output_index\":0,\"sequence_number\":34}", - "event: response.function_call_arguments.delta\ndata: {\"type\":\"response.function_call_arguments.delta\",\"delta\":\"/sc\",\"item_id\":\"fc_054a16a2f2ea3217016a705b67575881a3aacde02b9053a321\",\"obfuscation\":\"dsOEaf4EkLy14\",\"output_index\":0,\"sequence_number\":35}", - "event: response.function_call_arguments.delta\ndata: {\"type\":\"response.function_call_arguments.delta\",\"delta\":\"enarios\",\"item_id\":\"fc_054a16a2f2ea3217016a705b67575881a3aacde02b9053a321\",\"obfuscation\":\"Sk1iL5lxc\",\"output_index\":0,\"sequence_number\":36}", - "event: response.function_call_arguments.delta\ndata: {\"type\":\"response.function_call_arguments.delta\",\"delta\":\"/fl\",\"item_id\":\"fc_054a16a2f2ea3217016a705b67575881a3aacde02b9053a321\",\"obfuscation\":\"j3F97pquP3wT0\",\"output_index\":0,\"sequence_number\":37}", - "event: response.function_call_arguments.delta\ndata: {\"type\":\"response.function_call_arguments.delta\",\"delta\":\"ue\",\"item_id\":\"fc_054a16a2f2ea3217016a705b67575881a3aacde02b9053a321\",\"obfuscation\":\"5SHqC6FW1engeD\",\"output_index\":0,\"sequence_number\":38}", - "event: response.function_call_arguments.delta\ndata: {\"type\":\"response.function_call_arguments.delta\",\"delta\":\"-in\",\"item_id\":\"fc_054a16a2f2ea3217016a705b67575881a3aacde02b9053a321\",\"obfuscation\":\"MKvWbeQhFJuyU\",\"output_index\":0,\"sequence_number\":39}", - "event: response.function_call_arguments.delta\ndata: {\"type\":\"response.function_call_arguments.delta\",\"delta\":\"strument\",\"item_id\":\"fc_054a16a2f2ea3217016a705b67575881a3aacde02b9053a321\",\"obfuscation\":\"0JPR573Y\",\"output_index\":0,\"sequence_number\":40}", - "event: response.function_call_arguments.delta\ndata: {\"type\":\"response.function_call_arguments.delta\",\"delta\":\"ation\",\"item_id\":\"fc_054a16a2f2ea3217016a705b67575881a3aacde02b9053a321\",\"obfuscation\":\"tJ0iHGcFnz2\",\"output_index\":0,\"sequence_number\":41}", - "event: response.function_call_arguments.delta\ndata: {\"type\":\"response.function_call_arguments.delta\",\"delta\":\" -\",\"item_id\":\"fc_054a16a2f2ea3217016a705b67575881a3aacde02b9053a321\",\"obfuscation\":\"2Xe2zCjtFpD1Aa\",\"output_index\":0,\"sequence_number\":42}", - "event: response.function_call_arguments.delta\ndata: {\"type\":\"response.function_call_arguments.delta\",\"delta\":\"max\",\"item_id\":\"fc_054a16a2f2ea3217016a705b67575881a3aacde02b9053a321\",\"obfuscation\":\"9fKJvO4eySsc7\",\"output_index\":0,\"sequence_number\":43}", - "event: response.function_call_arguments.delta\ndata: {\"type\":\"response.function_call_arguments.delta\",\"delta\":\"depth\",\"item_id\":\"fc_054a16a2f2ea3217016a705b67575881a3aacde02b9053a321\",\"obfuscation\":\"adTuj16Pog1\",\"output_index\":0,\"sequence_number\":44}", - "event: response.function_call_arguments.delta\ndata: {\"type\":\"response.function_call_arguments.delta\",\"delta\":\" \",\"item_id\":\"fc_054a16a2f2ea3217016a705b67575881a3aacde02b9053a321\",\"obfuscation\":\"RG1GXpp0dtqROaL\",\"output_index\":0,\"sequence_number\":45}", - "event: response.function_call_arguments.delta\ndata: {\"type\":\"response.function_call_arguments.delta\",\"delta\":\"4\",\"item_id\":\"fc_054a16a2f2ea3217016a705b67575881a3aacde02b9053a321\",\"obfuscation\":\"62nieO7lhOujIMV\",\"output_index\":0,\"sequence_number\":46}", - "event: response.function_call_arguments.delta\ndata: {\"type\":\"response.function_call_arguments.delta\",\"delta\":\" -\",\"item_id\":\"fc_054a16a2f2ea3217016a705b67575881a3aacde02b9053a321\",\"obfuscation\":\"f85mHnnTvDNVsm\",\"output_index\":0,\"sequence_number\":47}", - "event: response.function_call_arguments.delta\ndata: {\"type\":\"response.function_call_arguments.delta\",\"delta\":\"type\",\"item_id\":\"fc_054a16a2f2ea3217016a705b67575881a3aacde02b9053a321\",\"obfuscation\":\"oOkDwdNirXZn\",\"output_index\":0,\"sequence_number\":48}", - "event: response.function_call_arguments.delta\ndata: {\"type\":\"response.function_call_arguments.delta\",\"delta\":\" f\",\"item_id\":\"fc_054a16a2f2ea3217016a705b67575881a3aacde02b9053a321\",\"obfuscation\":\"7hsgm1tQLQmfWU\",\"output_index\":0,\"sequence_number\":49}", - "event: response.function_call_arguments.delta\ndata: {\"type\":\"response.function_call_arguments.delta\",\"delta\":\" -\",\"item_id\":\"fc_054a16a2f2ea3217016a705b67575881a3aacde02b9053a321\",\"obfuscation\":\"TEtJIF7zjvh5BC\",\"output_index\":0,\"sequence_number\":50}", - "event: response.function_call_arguments.delta\ndata: {\"type\":\"response.function_call_arguments.delta\",\"delta\":\"name\",\"item_id\":\"fc_054a16a2f2ea3217016a705b67575881a3aacde02b9053a321\",\"obfuscation\":\"TRWuvVnlxjuw\",\"output_index\":0,\"sequence_number\":51}", - "event: response.function_call_arguments.delta\ndata: {\"type\":\"response.function_call_arguments.delta\",\"delta\":\" AG\",\"item_id\":\"fc_054a16a2f2ea3217016a705b67575881a3aacde02b9053a321\",\"obfuscation\":\"OPMH46rRaY9wj\",\"output_index\":0,\"sequence_number\":52}", - "event: response.function_call_arguments.delta\ndata: {\"type\":\"response.function_call_arguments.delta\",\"delta\":\"ENTS\",\"item_id\":\"fc_054a16a2f2ea3217016a705b67575881a3aacde02b9053a321\",\"obfuscation\":\"Wx1ktH8noul5\",\"output_index\":0,\"sequence_number\":53}", - "event: response.function_call_arguments.delta\ndata: {\"type\":\"response.function_call_arguments.delta\",\"delta\":\".md\",\"item_id\":\"fc_054a16a2f2ea3217016a705b67575881a3aacde02b9053a321\",\"obfuscation\":\"Oo8KevjbHLtSH\",\"output_index\":0,\"sequence_number\":54}", - "event: response.function_call_arguments.delta\ndata: {\"type\":\"response.function_call_arguments.delta\",\"delta\":\" -\",\"item_id\":\"fc_054a16a2f2ea3217016a705b67575881a3aacde02b9053a321\",\"obfuscation\":\"W1tKVt07sDFz6L\",\"output_index\":0,\"sequence_number\":55}", - "event: response.function_call_arguments.delta\ndata: {\"type\":\"response.function_call_arguments.delta\",\"delta\":\"print\",\"item_id\":\"fc_054a16a2f2ea3217016a705b67575881a3aacde02b9053a321\",\"obfuscation\":\"Ca0wTzfowmG\",\"output_index\":0,\"sequence_number\":56}", - "event: response.function_call_arguments.delta\ndata: {\"type\":\"response.function_call_arguments.delta\",\"delta\":\" -\",\"item_id\":\"fc_054a16a2f2ea3217016a705b67575881a3aacde02b9053a321\",\"obfuscation\":\"InAeZlZgCo2EWg\",\"output_index\":0,\"sequence_number\":57}", - "event: response.function_call_arguments.delta\ndata: {\"type\":\"response.function_call_arguments.delta\",\"delta\":\"exec\",\"item_id\":\"fc_054a16a2f2ea3217016a705b67575881a3aacde02b9053a321\",\"obfuscation\":\"xuVbI7qC8ZR7\",\"output_index\":0,\"sequence_number\":58}", - "event: response.function_call_arguments.delta\ndata: {\"type\":\"response.function_call_arguments.delta\",\"delta\":\" sed\",\"item_id\":\"fc_054a16a2f2ea3217016a705b67575881a3aacde02b9053a321\",\"obfuscation\":\"rWSvxjZ31UQg\",\"output_index\":0,\"sequence_number\":59}", - "event: response.function_call_arguments.delta\ndata: {\"type\":\"response.function_call_arguments.delta\",\"delta\":\" -\",\"item_id\":\"fc_054a16a2f2ea3217016a705b67575881a3aacde02b9053a321\",\"obfuscation\":\"LsdCqDwIUZ1aXY\",\"output_index\":0,\"sequence_number\":60}", - "event: response.function_call_arguments.delta\ndata: {\"type\":\"response.function_call_arguments.delta\",\"delta\":\"n\",\"item_id\":\"fc_054a16a2f2ea3217016a705b67575881a3aacde02b9053a321\",\"obfuscation\":\"9t5AteQjutiyqe3\",\"output_index\":0,\"sequence_number\":61}", - "event: response.function_call_arguments.delta\ndata: {\"type\":\"response.function_call_arguments.delta\",\"delta\":\" '\",\"item_id\":\"fc_054a16a2f2ea3217016a705b67575881a3aacde02b9053a321\",\"obfuscation\":\"b8zSZflAk9EXLf\",\"output_index\":0,\"sequence_number\":62}", - "event: response.function_call_arguments.delta\ndata: {\"type\":\"response.function_call_arguments.delta\",\"delta\":\"1\",\"item_id\":\"fc_054a16a2f2ea3217016a705b67575881a3aacde02b9053a321\",\"obfuscation\":\"i7Uvk2jgzfKhd4j\",\"output_index\":0,\"sequence_number\":63}", - "event: response.function_call_arguments.delta\ndata: {\"type\":\"response.function_call_arguments.delta\",\"delta\":\",\",\"item_id\":\"fc_054a16a2f2ea3217016a705b67575881a3aacde02b9053a321\",\"obfuscation\":\"dY2J1TUAF8vRFo8\",\"output_index\":0,\"sequence_number\":64}", - "event: response.function_call_arguments.delta\ndata: {\"type\":\"response.function_call_arguments.delta\",\"delta\":\"200\",\"item_id\":\"fc_054a16a2f2ea3217016a705b67575881a3aacde02b9053a321\",\"obfuscation\":\"3t3yMCd2EF2u3\",\"output_index\":0,\"sequence_number\":65}", - "event: response.function_call_arguments.delta\ndata: {\"type\":\"response.function_call_arguments.delta\",\"delta\":\"p\",\"item_id\":\"fc_054a16a2f2ea3217016a705b67575881a3aacde02b9053a321\",\"obfuscation\":\"LI717GeLz7d3rCb\",\"output_index\":0,\"sequence_number\":66}", - "event: response.function_call_arguments.delta\ndata: {\"type\":\"response.function_call_arguments.delta\",\"delta\":\"'\",\"item_id\":\"fc_054a16a2f2ea3217016a705b67575881a3aacde02b9053a321\",\"obfuscation\":\"aUymdN14yxm7RVU\",\"output_index\":0,\"sequence_number\":67}", - "event: response.function_call_arguments.delta\ndata: {\"type\":\"response.function_call_arguments.delta\",\"delta\":\" {}\",\"item_id\":\"fc_054a16a2f2ea3217016a705b67575881a3aacde02b9053a321\",\"obfuscation\":\"SsZA4kHNYtJqo\",\"output_index\":0,\"sequence_number\":68}", - "event: response.function_call_arguments.delta\ndata: {\"type\":\"response.function_call_arguments.delta\",\"delta\":\" \\\\\\\\\",\"item_id\":\"fc_054a16a2f2ea3217016a705b67575881a3aacde02b9053a321\",\"obfuscation\":\"VZIldvECynY3p\",\"output_index\":0,\"sequence_number\":69}", - "event: response.function_call_arguments.delta\ndata: {\"type\":\"response.function_call_arguments.delta\",\"delta\":\";\",\"item_id\":\"fc_054a16a2f2ea3217016a705b67575881a3aacde02b9053a321\",\"obfuscation\":\"LXiCXRoQhZXP5HK\",\"output_index\":0,\"sequence_number\":70}", - "event: response.function_call_arguments.delta\ndata: {\"type\":\"response.function_call_arguments.delta\",\"delta\":\"\\\",\\\"\",\"item_id\":\"fc_054a16a2f2ea3217016a705b67575881a3aacde02b9053a321\",\"obfuscation\":\"lN4m4dNvIh4ID\",\"output_index\":0,\"sequence_number\":71}", - "event: response.function_call_arguments.delta\ndata: {\"type\":\"response.function_call_arguments.delta\",\"delta\":\"timeout\",\"item_id\":\"fc_054a16a2f2ea3217016a705b67575881a3aacde02b9053a321\",\"obfuscation\":\"bGxGZD252\",\"output_index\":0,\"sequence_number\":72}", - "event: response.function_call_arguments.delta\ndata: {\"type\":\"response.function_call_arguments.delta\",\"delta\":\"\\\":\",\"item_id\":\"fc_054a16a2f2ea3217016a705b67575881a3aacde02b9053a321\",\"obfuscation\":\"Zz2e8ECX6n6SVc\",\"output_index\":0,\"sequence_number\":73}", - "event: response.function_call_arguments.delta\ndata: {\"type\":\"response.function_call_arguments.delta\",\"delta\":\"100\",\"item_id\":\"fc_054a16a2f2ea3217016a705b67575881a3aacde02b9053a321\",\"obfuscation\":\"Zi6V6iOPLR6s7\",\"output_index\":0,\"sequence_number\":74}", - "event: response.function_call_arguments.delta\ndata: {\"type\":\"response.function_call_arguments.delta\",\"delta\":\"000\",\"item_id\":\"fc_054a16a2f2ea3217016a705b67575881a3aacde02b9053a321\",\"obfuscation\":\"rp63i4YuRxltd\",\"output_index\":0,\"sequence_number\":75}", - "event: response.function_call_arguments.delta\ndata: {\"type\":\"response.function_call_arguments.delta\",\"delta\":\"}\",\"item_id\":\"fc_054a16a2f2ea3217016a705b67575881a3aacde02b9053a321\",\"obfuscation\":\"74rLY4yKHw7IjTn\",\"output_index\":0,\"sequence_number\":76}", - "event: response.function_call_arguments.done\ndata: {\"type\":\"response.function_call_arguments.done\",\"arguments\":\"{\\\"command\\\":\\\"find . -maxdepth 4 -type f -name AGENTS.md -print -exec sed -n '1,200p' {} \\\\\\\\;\\\",\\\"timeout\\\":100000}\",\"item_id\":\"fc_054a16a2f2ea3217016a705b67575881a3aacde02b9053a321\",\"output_index\":0,\"sequence_number\":77}", - "event: response.output_item.done\ndata: {\"type\":\"response.output_item.done\",\"item\":{\"id\":\"fc_054a16a2f2ea3217016a705b67575881a3aacde02b9053a321\",\"type\":\"function_call\",\"status\":\"completed\",\"arguments\":\"{\\\"command\\\":\\\"find . -maxdepth 4 -type f -name AGENTS.md -print -exec sed -n '1,200p' {} \\\\\\\\;\\\",\\\"timeout\\\":100000}\",\"call_id\":\"call_jQUCie0ZgaXUrZCDcezp0msl\",\"name\":\"bash\"},\"output_index\":0,\"sequence_number\":78}", - "event: response.completed\ndata: {\"type\":\"response.completed\",\"response\":{\"id\":\"resp_054a16a2f2ea3217016a705b66ec3081a3b53487cd28a912dd\",\"object\":\"response\",\"created_at\":1785748326,\"status\":\"completed\",\"background\":false,\"completed_at\":1785748328,\"error\":null,\"frequency_penalty\":0,\"incomplete_details\":null,\"instructions\":null,\"max_output_tokens\":null,\"max_tool_calls\":null,\"model\":\"gpt-5.4-nano-2026-03-17\",\"moderation\":null,\"output\":[{\"id\":\"fc_054a16a2f2ea3217016a705b67575881a3aacde02b9053a321\",\"type\":\"function_call\",\"status\":\"completed\",\"arguments\":\"{\\\"command\\\":\\\"find . -maxdepth 4 -type f -name AGENTS.md -print -exec sed -n '1,200p' {} \\\\\\\\;\\\",\\\"timeout\\\":100000}\",\"call_id\":\"call_jQUCie0ZgaXUrZCDcezp0msl\",\"name\":\"bash\"}],\"parallel_tool_calls\":true,\"presence_penalty\":0,\"previous_response_id\":null,\"prompt_cache_key\":\"workflow:instrumentation:01KZ3E9TWTAEK4P4P3F63HKAKC::default::ta\",\"prompt_cache_retention\":\"24h\",\"reasoning\":{\"context\":\"current_turn\",\"effort\":\"none\",\"mode\":\"standard\",\"summary\":null},\"safety_identifier\":null,\"service_tier\":\"default\",\"store\":false,\"temperature\":1,\"text\":{\"format\":{\"type\":\"text\"},\"verbosity\":\"medium\"},\"tool_choice\":\"auto\",\"tool_usage\":{\"image_gen\":{\"input_tokens\":0,\"input_tokens_details\":{\"image_tokens\":0,\"text_tokens\":0},\"output_tokens\":0,\"output_tokens_details\":{\"image_tokens\":0,\"text_tokens\":0},\"total_tokens\":0},\"web_search\":{\"num_requests\":0}},\"tools\":[{\"type\":\"function\",\"description\":\"Read a file or list a directory. For files, output is truncated to 2000 lines or 50KB — use offset/limit for large files. For directories, returns the list of entries.\",\"name\":\"read\",\"output_schema\":null,\"parameters\":{\"type\":\"object\",\"required\":[\"path\"],\"properties\":{\"path\":{\"type\":\"string\",\"description\":\"Path to the file to read\"},\"offset\":{\"type\":\"number\",\"description\":\"Line number to start from (1-indexed)\"},\"limit\":{\"type\":\"number\",\"description\":\"Maximum number of lines to read\"}}},\"strict\":false},{\"type\":\"function\",\"description\":\"Write content to a file. Creates the file and parent directories if they do not exist.\",\"name\":\"write\",\"output_schema\":null,\"parameters\":{\"type\":\"object\",\"required\":[\"path\",\"content\"],\"properties\":{\"path\":{\"type\":\"string\",\"description\":\"Path to the file to write\"},\"content\":{\"type\":\"string\",\"description\":\"Content to write to the file\"}}},\"strict\":false},{\"type\":\"function\",\"description\":\"Edit a file using exact text replacement. The oldText must match a unique region of the file. Use replaceAll to replace all occurrences.\",\"name\":\"edit\",\"output_schema\":null,\"parameters\":{\"type\":\"object\",\"required\":[\"path\",\"oldText\",\"newText\"],\"properties\":{\"path\":{\"type\":\"string\",\"description\":\"Path to the file to edit\"},\"oldText\":{\"type\":\"string\",\"description\":\"Exact text to find (must be unique)\"},\"newText\":{\"type\":\"string\",\"description\":\"Replacement text\"},\"replaceAll\":{\"type\":\"boolean\",\"description\":\"Replace all occurrences\"}}},\"strict\":false},{\"type\":\"function\",\"description\":\"Execute a bash command. Returns stdout and stderr. Output is truncated to the last 2000 lines or 50KB.\",\"name\":\"bash\",\"output_schema\":null,\"parameters\":{\"type\":\"object\",\"required\":[\"command\"],\"properties\":{\"command\":{\"type\":\"string\",\"description\":\"Bash command to execute\"},\"timeout\":{\"type\":\"number\",\"description\":\"Timeout in seconds\"}}},\"strict\":false},{\"type\":\"function\",\"description\":\"Search file contents for a regex pattern. Returns matching lines with file paths and line numbers.\",\"name\":\"grep\",\"output_schema\":null,\"parameters\":{\"type\":\"object\",\"required\":[\"pattern\"],\"properties\":{\"pattern\":{\"type\":\"string\",\"description\":\"Search pattern (regex)\"},\"path\":{\"type\":\"string\",\"description\":\"Directory or file to search (default: .)\"},\"include\":{\"type\":\"string\",\"description\":\"Glob filter, e.g. \\\"*.ts\\\"\"}}},\"strict\":false},{\"type\":\"function\",\"description\":\"Find files by filename pattern using shell find -name semantics. Returns matching file paths.\",\"name\":\"glob\",\"output_schema\":null,\"parameters\":{\"type\":\"object\",\"required\":[\"pattern\"],\"properties\":{\"pattern\":{\"type\":\"string\",\"description\":\"Filename pattern, e.g. \\\"*.ts\\\"\"},\"path\":{\"type\":\"string\",\"description\":\"Directory to search in (default: .)\"}}},\"strict\":false},{\"type\":\"function\",\"description\":\"Delegate a focused task to a detached child agent with its own context. Use this for independent research, file exploration, or parallel work. The task returns only its final answer to this conversation. No subagents are currently defined.\",\"name\":\"task\",\"output_schema\":null,\"parameters\":{\"type\":\"object\",\"required\":[\"prompt\"],\"properties\":{\"description\":{\"type\":\"string\",\"description\":\"Short human-readable label for the delegated work\"},\"prompt\":{\"type\":\"string\",\"description\":\"Focused instructions for the child agent\"},\"agent\":{\"type\":\"string\",\"description\":\"Declared subagent to use for the child agent\"},\"cwd\":{\"type\":\"string\",\"description\":\"Working directory for the child agent. AGENTS.md and skills are discovered from here.\"}}},\"strict\":false}],\"top_logprobs\":0,\"top_p\":0.98,\"truncation\":\"disabled\",\"usage\":{\"input_tokens\":1197,\"input_tokens_details\":{\"cache_write_tokens\":0,\"cached_tokens\":0},\"output_tokens\":87,\"output_tokens_details\":{\"reasoning_tokens\":0},\"total_tokens\":1284},\"user\":null,\"metadata\":{}},\"sequence_number\":79}" - ], - "kind": "sse" - }, - "headers": { - "access-control-expose-headers": "X-Request-ID, CF-Ray, CF-Ray", - "alt-svc": "h3=\":443\"; ma=86400", - "cf-cache-status": "DYNAMIC", - "cf-ray": "a25432e308c57bb6-IAD", - "connection": "keep-alive", - "content-type": "text/event-stream; charset=utf-8", - "date": "Mon, 03 Aug 2026 09:12:07 GMT", - "openai-organization": "braintrust-data", - "openai-processing-ms": "181", - "openai-project": "proj_vsCSXafhhByzWOThMrJcZiw9", - "openai-version": "2020-10-01", - "server": "cloudflare", - "set-cookie": "[REDACTED]", - "strict-transport-security": "max-age=31536000; includeSubDomains; preload", - "transfer-encoding": "chunked", - "x-content-type-options": "nosniff", - "x-ratelimit-limit-requests": "30000", - "x-ratelimit-limit-tokens": "180000000", - "x-ratelimit-remaining-requests": "29999", - "x-ratelimit-remaining-tokens": "179998347", - "x-ratelimit-reset-requests": "2ms", - "x-ratelimit-reset-tokens": "0s", - "x-request-id": "req_5c4fd875329344cfa3d20c5925805429" - }, - "status": 200, - "statusText": "OK" - } - }, - { - "callIndex": 8, - "id": "8eec740c1be01c5c", - "matchKey": "POST api.openai.com/v1/responses", - "recordedAt": "2026-08-03T09:12:09.317Z", - "request": { - "body": { - "kind": "json", - "value": { - "input": [ - { - "content": "You are running in headless mode with no human operator. Work autonomously — never ask questions, never wait for user input. Make your best judgment and proceed independently.\n\nYou are a deterministic Flue instrumentation test agent. Follow user instructions exactly. When asked for a marker, output only that marker and no extra text. When running a local skill file, read it yourself and do not delegate it to a task.\n\n## Available Skills\n\nThe following skills provide specialized instructions for specific tasks. When a task matches a skill description, activate that skill before proceeding so its full instructions are loaded. Skill instructions and supporting resources stay lazy until activation or explicit file reads.\n\n- **e2e-flue-skill** — Deterministic skill used by the Braintrust Flue instrumentation e2e test.\n\nDate: Mon, Aug 3, 2026\nWorking directory: .\n\nDirectory structure:\n.agents\n.flue\n.flue-build\n__cassettes__\n__snapshots__\nassertions.ts\ncassette-filter.mjs\nconstants.mjs\ndist\nnode_modules\npackage.json\npnpm-lock.yaml\nscenario.cli.mjs\nscenario.impl.mjs\nscenario.mjs\nscenario.test.ts\nscenario.ts\nscenario.v2.ts\nv2-assertions.ts\nversions", - "role": "developer" - }, - { - "content": [ - { - "text": "Run the skill named \"e2e-flue-skill\" with arguments {\"marker\":\"SKILL_DONE\"}. Output only the marker and no extra text when finished.", - "type": "input_text" - } - ], - "role": "user" - }, - { - "arguments": "{\"description\":\"Run e2e-flue-skill with marker SKILL_DONE\",\"prompt\":\"Run the skill named \\\"e2e-flue-skill\\\" with arguments {\\\"marker\\\":\\\"SKILL_DONE\\\"}. Ensure completion and report the final output marker only.\",\"agent\":\"e2e-flue-skill\",\"cwd\":\".\"}", - "call_id": "call_w6pyYZgnhlSOAXul75DAG1Cj", - "id": "fc_054a16a2f2ea3217016a705b64fe5481a390185c9576df12b6", - "name": "task", - "type": "function_call" - }, - { - "call_id": "call_w6pyYZgnhlSOAXul75DAG1Cj", - "output": "[flue] Subagent \"e2e-flue-skill\" is not declared. Available: (none).", - "type": "function_call_output" - }, - { - "arguments": "{\"command\":\"find . -maxdepth 4 -name AGENTS.md -print\",\"timeout\":100000}", - "call_id": "call_6zEBnJRmXFIkGFwNjWLZP4uC", - "id": "fc_054a16a2f2ea3217016a705b66460481a3a5de06df603e35ee", - "name": "bash", - "type": "function_call" - }, - { - "call_id": "call_6zEBnJRmXFIkGFwNjWLZP4uC", - "output": "total 4\ndrwxr-xr-x 3 vercel-sandbox vercel-sandbox 20 Aug 3 09:10 .\ndrwxr-xr-x 10 vercel-sandbox vercel-sandbox 4096 Aug 3 09:11 ..\ndrwxr-xr-x 3 vercel-sandbox vercel-sandbox 28 Aug 3 09:10 skills", - "type": "function_call_output" - }, - { - "arguments": "{\"command\":\"find . -maxdepth 4 -type f -name AGENTS.md -print -exec sed -n '1,200p' {} \\\\;\",\"timeout\":100000}", - "call_id": "call_jQUCie0ZgaXUrZCDcezp0msl", - "id": "fc_054a16a2f2ea3217016a705b67575881a3aacde02b9053a321", - "name": "bash", - "type": "function_call" - }, - { - "call_id": "call_jQUCie0ZgaXUrZCDcezp0msl", - "output": "(no output)", - "type": "function_call_output" - } - ], - "model": "gpt-5.4-nano", - "prompt_cache_key": "[REDACTED]", - "reasoning": { - "effort": "none" - }, - "store": false, - "stream": true, - "tools": [ - { - "description": "Read a file or list a directory. For files, output is truncated to 2000 lines or 50KB — use offset/limit for large files. For directories, returns the list of entries.", - "name": "read", - "parameters": { - "properties": { - "limit": { - "description": "Maximum number of lines to read", - "type": "number" - }, - "offset": { - "description": "Line number to start from (1-indexed)", - "type": "number" - }, - "path": { - "description": "Path to the file to read", - "type": "string" - } - }, - "required": ["path"], - "type": "object" - }, - "strict": false, - "type": "function" - }, - { - "description": "Write content to a file. Creates the file and parent directories if they do not exist.", - "name": "write", - "parameters": { - "properties": { - "content": { - "description": "Content to write to the file", - "type": "string" - }, - "path": { - "description": "Path to the file to write", - "type": "string" - } - }, - "required": ["path", "content"], - "type": "object" - }, - "strict": false, - "type": "function" - }, - { - "description": "Edit a file using exact text replacement. The oldText must match a unique region of the file. Use replaceAll to replace all occurrences.", - "name": "edit", - "parameters": { - "properties": { - "newText": { - "description": "Replacement text", - "type": "string" - }, - "oldText": { - "description": "Exact text to find (must be unique)", - "type": "string" - }, - "path": { - "description": "Path to the file to edit", - "type": "string" - }, - "replaceAll": { - "description": "Replace all occurrences", - "type": "boolean" - } - }, - "required": ["path", "oldText", "newText"], - "type": "object" - }, - "strict": false, - "type": "function" - }, - { - "description": "Execute a bash command. Returns stdout and stderr. Output is truncated to the last 2000 lines or 50KB.", - "name": "bash", - "parameters": { - "properties": { - "command": { - "description": "Bash command to execute", - "type": "string" - }, - "timeout": { - "description": "Timeout in seconds", - "type": "number" - } - }, - "required": ["command"], - "type": "object" - }, - "strict": false, - "type": "function" - }, - { - "description": "Search file contents for a regex pattern. Returns matching lines with file paths and line numbers.", - "name": "grep", - "parameters": { - "properties": { - "include": { - "description": "Glob filter, e.g. \"*.ts\"", - "type": "string" - }, - "path": { - "description": "Directory or file to search (default: .)", - "type": "string" - }, - "pattern": { - "description": "Search pattern (regex)", - "type": "string" - } - }, - "required": ["pattern"], - "type": "object" - }, - "strict": false, - "type": "function" - }, - { - "description": "Find files by filename pattern using shell find -name semantics. Returns matching file paths.", - "name": "glob", - "parameters": { - "properties": { - "path": { - "description": "Directory to search in (default: .)", - "type": "string" - }, - "pattern": { - "description": "Filename pattern, e.g. \"*.ts\"", - "type": "string" - } - }, - "required": ["pattern"], - "type": "object" - }, - "strict": false, - "type": "function" - }, - { - "description": "Delegate a focused task to a detached child agent with its own context. Use this for independent research, file exploration, or parallel work. The task returns only its final answer to this conversation. No subagents are currently defined.", - "name": "task", - "parameters": { - "properties": { - "agent": { - "description": "Declared subagent to use for the child agent", - "type": "string" - }, - "cwd": { - "description": "Working directory for the child agent. AGENTS.md and skills are discovered from here.", - "type": "string" - }, - "description": { - "description": "Short human-readable label for the delegated work", - "type": "string" - }, - "prompt": { - "description": "Focused instructions for the child agent", - "type": "string" - } - }, - "required": ["prompt"], - "type": "object" - }, - "strict": false, - "type": "function" - } - ] - } - }, - "headers": {}, - "method": "POST", - "url": "https://api.openai.com/v1/responses" - }, - "response": { - "body": { - "chunks": [ - "event: response.created\ndata: {\"type\":\"response.created\",\"response\":{\"id\":\"resp_054a16a2f2ea3217016a705b6820c481a3b64f6527345dc3ca\",\"object\":\"response\",\"created_at\":1785748328,\"status\":\"in_progress\",\"background\":false,\"completed_at\":null,\"error\":null,\"frequency_penalty\":0,\"incomplete_details\":null,\"instructions\":null,\"max_output_tokens\":null,\"max_tool_calls\":null,\"model\":\"gpt-5.4-nano-2026-03-17\",\"moderation\":null,\"output\":[],\"parallel_tool_calls\":true,\"presence_penalty\":0,\"previous_response_id\":null,\"prompt_cache_key\":\"workflow:instrumentation:01KZ3E9TWTAEK4P4P3F63HKAKC::default::ta\",\"prompt_cache_retention\":\"24h\",\"reasoning\":{\"context\":\"current_turn\",\"effort\":\"none\",\"mode\":\"standard\",\"summary\":null},\"safety_identifier\":null,\"service_tier\":\"auto\",\"store\":false,\"temperature\":1,\"text\":{\"format\":{\"type\":\"text\"},\"verbosity\":\"medium\"},\"tool_choice\":\"auto\",\"tool_usage\":{\"image_gen\":{\"input_tokens\":0,\"input_tokens_details\":{\"image_tokens\":0,\"text_tokens\":0},\"output_tokens\":0,\"output_tokens_details\":{\"image_tokens\":0,\"text_tokens\":0},\"total_tokens\":0},\"web_search\":{\"num_requests\":0}},\"tools\":[{\"type\":\"function\",\"description\":\"Read a file or list a directory. For files, output is truncated to 2000 lines or 50KB — use offset/limit for large files. For directories, returns the list of entries.\",\"name\":\"read\",\"output_schema\":null,\"parameters\":{\"type\":\"object\",\"required\":[\"path\"],\"properties\":{\"path\":{\"type\":\"string\",\"description\":\"Path to the file to read\"},\"offset\":{\"type\":\"number\",\"description\":\"Line number to start from (1-indexed)\"},\"limit\":{\"type\":\"number\",\"description\":\"Maximum number of lines to read\"}}},\"strict\":false},{\"type\":\"function\",\"description\":\"Write content to a file. Creates the file and parent directories if they do not exist.\",\"name\":\"write\",\"output_schema\":null,\"parameters\":{\"type\":\"object\",\"required\":[\"path\",\"content\"],\"properties\":{\"path\":{\"type\":\"string\",\"description\":\"Path to the file to write\"},\"content\":{\"type\":\"string\",\"description\":\"Content to write to the file\"}}},\"strict\":false},{\"type\":\"function\",\"description\":\"Edit a file using exact text replacement. The oldText must match a unique region of the file. Use replaceAll to replace all occurrences.\",\"name\":\"edit\",\"output_schema\":null,\"parameters\":{\"type\":\"object\",\"required\":[\"path\",\"oldText\",\"newText\"],\"properties\":{\"path\":{\"type\":\"string\",\"description\":\"Path to the file to edit\"},\"oldText\":{\"type\":\"string\",\"description\":\"Exact text to find (must be unique)\"},\"newText\":{\"type\":\"string\",\"description\":\"Replacement text\"},\"replaceAll\":{\"type\":\"boolean\",\"description\":\"Replace all occurrences\"}}},\"strict\":false},{\"type\":\"function\",\"description\":\"Execute a bash command. Returns stdout and stderr. Output is truncated to the last 2000 lines or 50KB.\",\"name\":\"bash\",\"output_schema\":null,\"parameters\":{\"type\":\"object\",\"required\":[\"command\"],\"properties\":{\"command\":{\"type\":\"string\",\"description\":\"Bash command to execute\"},\"timeout\":{\"type\":\"number\",\"description\":\"Timeout in seconds\"}}},\"strict\":false},{\"type\":\"function\",\"description\":\"Search file contents for a regex pattern. Returns matching lines with file paths and line numbers.\",\"name\":\"grep\",\"output_schema\":null,\"parameters\":{\"type\":\"object\",\"required\":[\"pattern\"],\"properties\":{\"pattern\":{\"type\":\"string\",\"description\":\"Search pattern (regex)\"},\"path\":{\"type\":\"string\",\"description\":\"Directory or file to search (default: .)\"},\"include\":{\"type\":\"string\",\"description\":\"Glob filter, e.g. \\\"*.ts\\\"\"}}},\"strict\":false},{\"type\":\"function\",\"description\":\"Find files by filename pattern using shell find -name semantics. Returns matching file paths.\",\"name\":\"glob\",\"output_schema\":null,\"parameters\":{\"type\":\"object\",\"required\":[\"pattern\"],\"properties\":{\"pattern\":{\"type\":\"string\",\"description\":\"Filename pattern, e.g. \\\"*.ts\\\"\"},\"path\":{\"type\":\"string\",\"description\":\"Directory to search in (default: .)\"}}},\"strict\":false},{\"type\":\"function\",\"description\":\"Delegate a focused task to a detached child agent with its own context. Use this for independent research, file exploration, or parallel work. The task returns only its final answer to this conversation. No subagents are currently defined.\",\"name\":\"task\",\"output_schema\":null,\"parameters\":{\"type\":\"object\",\"required\":[\"prompt\"],\"properties\":{\"description\":{\"type\":\"string\",\"description\":\"Short human-readable label for the delegated work\"},\"prompt\":{\"type\":\"string\",\"description\":\"Focused instructions for the child agent\"},\"agent\":{\"type\":\"string\",\"description\":\"Declared subagent to use for the child agent\"},\"cwd\":{\"type\":\"string\",\"description\":\"Working directory for the child agent. AGENTS.md and skills are discovered from here.\"}}},\"strict\":false}],\"top_logprobs\":0,\"top_p\":0.98,\"truncation\":\"disabled\",\"usage\":null,\"user\":null,\"metadata\":{}},\"sequence_number\":0}", - "event: response.in_progress\ndata: {\"type\":\"response.in_progress\",\"response\":{\"id\":\"resp_054a16a2f2ea3217016a705b6820c481a3b64f6527345dc3ca\",\"object\":\"response\",\"created_at\":1785748328,\"status\":\"in_progress\",\"background\":false,\"completed_at\":null,\"error\":null,\"frequency_penalty\":0,\"incomplete_details\":null,\"instructions\":null,\"max_output_tokens\":null,\"max_tool_calls\":null,\"model\":\"gpt-5.4-nano-2026-03-17\",\"moderation\":null,\"output\":[],\"parallel_tool_calls\":true,\"presence_penalty\":0,\"previous_response_id\":null,\"prompt_cache_key\":\"workflow:instrumentation:01KZ3E9TWTAEK4P4P3F63HKAKC::default::ta\",\"prompt_cache_retention\":\"24h\",\"reasoning\":{\"context\":\"current_turn\",\"effort\":\"none\",\"mode\":\"standard\",\"summary\":null},\"safety_identifier\":null,\"service_tier\":\"auto\",\"store\":false,\"temperature\":1,\"text\":{\"format\":{\"type\":\"text\"},\"verbosity\":\"medium\"},\"tool_choice\":\"auto\",\"tool_usage\":{\"image_gen\":{\"input_tokens\":0,\"input_tokens_details\":{\"image_tokens\":0,\"text_tokens\":0},\"output_tokens\":0,\"output_tokens_details\":{\"image_tokens\":0,\"text_tokens\":0},\"total_tokens\":0},\"web_search\":{\"num_requests\":0}},\"tools\":[{\"type\":\"function\",\"description\":\"Read a file or list a directory. For files, output is truncated to 2000 lines or 50KB — use offset/limit for large files. For directories, returns the list of entries.\",\"name\":\"read\",\"output_schema\":null,\"parameters\":{\"type\":\"object\",\"required\":[\"path\"],\"properties\":{\"path\":{\"type\":\"string\",\"description\":\"Path to the file to read\"},\"offset\":{\"type\":\"number\",\"description\":\"Line number to start from (1-indexed)\"},\"limit\":{\"type\":\"number\",\"description\":\"Maximum number of lines to read\"}}},\"strict\":false},{\"type\":\"function\",\"description\":\"Write content to a file. Creates the file and parent directories if they do not exist.\",\"name\":\"write\",\"output_schema\":null,\"parameters\":{\"type\":\"object\",\"required\":[\"path\",\"content\"],\"properties\":{\"path\":{\"type\":\"string\",\"description\":\"Path to the file to write\"},\"content\":{\"type\":\"string\",\"description\":\"Content to write to the file\"}}},\"strict\":false},{\"type\":\"function\",\"description\":\"Edit a file using exact text replacement. The oldText must match a unique region of the file. Use replaceAll to replace all occurrences.\",\"name\":\"edit\",\"output_schema\":null,\"parameters\":{\"type\":\"object\",\"required\":[\"path\",\"oldText\",\"newText\"],\"properties\":{\"path\":{\"type\":\"string\",\"description\":\"Path to the file to edit\"},\"oldText\":{\"type\":\"string\",\"description\":\"Exact text to find (must be unique)\"},\"newText\":{\"type\":\"string\",\"description\":\"Replacement text\"},\"replaceAll\":{\"type\":\"boolean\",\"description\":\"Replace all occurrences\"}}},\"strict\":false},{\"type\":\"function\",\"description\":\"Execute a bash command. Returns stdout and stderr. Output is truncated to the last 2000 lines or 50KB.\",\"name\":\"bash\",\"output_schema\":null,\"parameters\":{\"type\":\"object\",\"required\":[\"command\"],\"properties\":{\"command\":{\"type\":\"string\",\"description\":\"Bash command to execute\"},\"timeout\":{\"type\":\"number\",\"description\":\"Timeout in seconds\"}}},\"strict\":false},{\"type\":\"function\",\"description\":\"Search file contents for a regex pattern. Returns matching lines with file paths and line numbers.\",\"name\":\"grep\",\"output_schema\":null,\"parameters\":{\"type\":\"object\",\"required\":[\"pattern\"],\"properties\":{\"pattern\":{\"type\":\"string\",\"description\":\"Search pattern (regex)\"},\"path\":{\"type\":\"string\",\"description\":\"Directory or file to search (default: .)\"},\"include\":{\"type\":\"string\",\"description\":\"Glob filter, e.g. \\\"*.ts\\\"\"}}},\"strict\":false},{\"type\":\"function\",\"description\":\"Find files by filename pattern using shell find -name semantics. Returns matching file paths.\",\"name\":\"glob\",\"output_schema\":null,\"parameters\":{\"type\":\"object\",\"required\":[\"pattern\"],\"properties\":{\"pattern\":{\"type\":\"string\",\"description\":\"Filename pattern, e.g. \\\"*.ts\\\"\"},\"path\":{\"type\":\"string\",\"description\":\"Directory to search in (default: .)\"}}},\"strict\":false},{\"type\":\"function\",\"description\":\"Delegate a focused task to a detached child agent with its own context. Use this for independent research, file exploration, or parallel work. The task returns only its final answer to this conversation. No subagents are currently defined.\",\"name\":\"task\",\"output_schema\":null,\"parameters\":{\"type\":\"object\",\"required\":[\"prompt\"],\"properties\":{\"description\":{\"type\":\"string\",\"description\":\"Short human-readable label for the delegated work\"},\"prompt\":{\"type\":\"string\",\"description\":\"Focused instructions for the child agent\"},\"agent\":{\"type\":\"string\",\"description\":\"Declared subagent to use for the child agent\"},\"cwd\":{\"type\":\"string\",\"description\":\"Working directory for the child agent. AGENTS.md and skills are discovered from here.\"}}},\"strict\":false}],\"top_logprobs\":0,\"top_p\":0.98,\"truncation\":\"disabled\",\"usage\":null,\"user\":null,\"metadata\":{}},\"sequence_number\":1}", - "event: response.output_item.added\ndata: {\"type\":\"response.output_item.added\",\"item\":{\"id\":\"fc_054a16a2f2ea3217016a705b68c39881a3b1e518599236e41a\",\"type\":\"function_call\",\"status\":\"in_progress\",\"arguments\":\"\",\"call_id\":\"call_NgMSUb6wIR0GAf9U8LXERtBQ\",\"name\":\"bash\"},\"output_index\":0,\"sequence_number\":2}", - "event: response.function_call_arguments.delta\ndata: {\"type\":\"response.function_call_arguments.delta\",\"delta\":\"{\\\"\",\"item_id\":\"fc_054a16a2f2ea3217016a705b68c39881a3b1e518599236e41a\",\"obfuscation\":\"Ecm4AJUnR33nzy\",\"output_index\":0,\"sequence_number\":3}", - "event: response.function_call_arguments.delta\ndata: {\"type\":\"response.function_call_arguments.delta\",\"delta\":\"command\",\"item_id\":\"fc_054a16a2f2ea3217016a705b68c39881a3b1e518599236e41a\",\"obfuscation\":\"qAhzrPXMU\",\"output_index\":0,\"sequence_number\":4}", - "event: response.function_call_arguments.delta\ndata: {\"type\":\"response.function_call_arguments.delta\",\"delta\":\"\\\":\\\"\",\"item_id\":\"fc_054a16a2f2ea3217016a705b68c39881a3b1e518599236e41a\",\"obfuscation\":\"HjoLYpLsOOApn\",\"output_index\":0,\"sequence_number\":5}", - "event: response.function_call_arguments.delta\ndata: {\"type\":\"response.function_call_arguments.delta\",\"delta\":\"find\",\"item_id\":\"fc_054a16a2f2ea3217016a705b68c39881a3b1e518599236e41a\",\"obfuscation\":\"COx5RXyau1iu\",\"output_index\":0,\"sequence_number\":6}", - "event: response.function_call_arguments.delta\ndata: {\"type\":\"response.function_call_arguments.delta\",\"delta\":\" /\",\"item_id\":\"fc_054a16a2f2ea3217016a705b68c39881a3b1e518599236e41a\",\"obfuscation\":\"7fcdZxijyLBFbj\",\"output_index\":0,\"sequence_number\":7}", - "event: response.function_call_arguments.delta\ndata: {\"type\":\"response.function_call_arguments.delta\",\"delta\":\"ver\",\"item_id\":\"fc_054a16a2f2ea3217016a705b68c39881a3b1e518599236e41a\",\"obfuscation\":\"EdhPBWD97UXXj\",\"output_index\":0,\"sequence_number\":8}", - "event: response.function_call_arguments.delta\ndata: {\"type\":\"response.function_call_arguments.delta\",\"delta\":\"cel\",\"item_id\":\"fc_054a16a2f2ea3217016a705b68c39881a3b1e518599236e41a\",\"obfuscation\":\"g4JtDEv0GyNkp\",\"output_index\":0,\"sequence_number\":9}", - "event: response.function_call_arguments.delta\ndata: {\"type\":\"response.function_call_arguments.delta\",\"delta\":\"/s\",\"item_id\":\"fc_054a16a2f2ea3217016a705b68c39881a3b1e518599236e41a\",\"obfuscation\":\"Yu3ytbv7EfB9L7\",\"output_index\":0,\"sequence_number\":10}", - "event: response.function_call_arguments.delta\ndata: {\"type\":\"response.function_call_arguments.delta\",\"delta\":\"andbox\",\"item_id\":\"fc_054a16a2f2ea3217016a705b68c39881a3b1e518599236e41a\",\"obfuscation\":\"loDk0mviWC\",\"output_index\":0,\"sequence_number\":11}", - "event: response.function_call_arguments.delta\ndata: {\"type\":\"response.function_call_arguments.delta\",\"delta\":\"/\",\"item_id\":\"fc_054a16a2f2ea3217016a705b68c39881a3b1e518599236e41a\",\"obfuscation\":\"4KyvKX7bONkWif0\",\"output_index\":0,\"sequence_number\":12}", - "event: response.function_call_arguments.delta\ndata: {\"type\":\"response.function_call_arguments.delta\",\"delta\":\"brain\",\"item_id\":\"fc_054a16a2f2ea3217016a705b68c39881a3b1e518599236e41a\",\"obfuscation\":\"yowGulOqnpo\",\"output_index\":0,\"sequence_number\":13}", - "event: response.function_call_arguments.delta\ndata: {\"type\":\"response.function_call_arguments.delta\",\"delta\":\"trust\",\"item_id\":\"fc_054a16a2f2ea3217016a705b68c39881a3b1e518599236e41a\",\"obfuscation\":\"83qIG2PGqLt\",\"output_index\":0,\"sequence_number\":14}", - "event: response.function_call_arguments.delta\ndata: {\"type\":\"response.function_call_arguments.delta\",\"delta\":\"data\",\"item_id\":\"fc_054a16a2f2ea3217016a705b68c39881a3b1e518599236e41a\",\"obfuscation\":\"ZFbal7RnszIt\",\"output_index\":0,\"sequence_number\":15}", - "event: response.function_call_arguments.delta\ndata: {\"type\":\"response.function_call_arguments.delta\",\"delta\":\"/\",\"item_id\":\"fc_054a16a2f2ea3217016a705b68c39881a3b1e518599236e41a\",\"obfuscation\":\"GVlOdY86Xo86jAw\",\"output_index\":0,\"sequence_number\":16}", - "event: response.function_call_arguments.delta\ndata: {\"type\":\"response.function_call_arguments.delta\",\"delta\":\"brain\",\"item_id\":\"fc_054a16a2f2ea3217016a705b68c39881a3b1e518599236e41a\",\"obfuscation\":\"mjcerP27QOt\",\"output_index\":0,\"sequence_number\":17}", - "event: response.function_call_arguments.delta\ndata: {\"type\":\"response.function_call_arguments.delta\",\"delta\":\"trust\",\"item_id\":\"fc_054a16a2f2ea3217016a705b68c39881a3b1e518599236e41a\",\"obfuscation\":\"oQnyazxVeT6\",\"output_index\":0,\"sequence_number\":18}", - "event: response.function_call_arguments.delta\ndata: {\"type\":\"response.function_call_arguments.delta\",\"delta\":\"-sdk\",\"item_id\":\"fc_054a16a2f2ea3217016a705b68c39881a3b1e518599236e41a\",\"obfuscation\":\"2CRHfaUuS1Dd\",\"output_index\":0,\"sequence_number\":19}", - "event: response.function_call_arguments.delta\ndata: {\"type\":\"response.function_call_arguments.delta\",\"delta\":\"-j\",\"item_id\":\"fc_054a16a2f2ea3217016a705b68c39881a3b1e518599236e41a\",\"obfuscation\":\"3IfHRkUmRfpbWX\",\"output_index\":0,\"sequence_number\":20}", - "event: response.function_call_arguments.delta\ndata: {\"type\":\"response.function_call_arguments.delta\",\"delta\":\"avascript\",\"item_id\":\"fc_054a16a2f2ea3217016a705b68c39881a3b1e518599236e41a\",\"obfuscation\":\"uJPFidS\",\"output_index\":0,\"sequence_number\":21}", - "event: response.function_call_arguments.delta\ndata: {\"type\":\"response.function_call_arguments.delta\",\"delta\":\"/e\",\"item_id\":\"fc_054a16a2f2ea3217016a705b68c39881a3b1e518599236e41a\",\"obfuscation\":\"PAbcespQ5gkoQu\",\"output_index\":0,\"sequence_number\":22}", - "event: response.function_call_arguments.delta\ndata: {\"type\":\"response.function_call_arguments.delta\",\"delta\":\"2\",\"item_id\":\"fc_054a16a2f2ea3217016a705b68c39881a3b1e518599236e41a\",\"obfuscation\":\"By2Tx9QLwPZRKd1\",\"output_index\":0,\"sequence_number\":23}", - "event: response.function_call_arguments.delta\ndata: {\"type\":\"response.function_call_arguments.delta\",\"delta\":\"e\",\"item_id\":\"fc_054a16a2f2ea3217016a705b68c39881a3b1e518599236e41a\",\"obfuscation\":\"jCZWJ7sTG0eCEsh\",\"output_index\":0,\"sequence_number\":24}", - "event: response.function_call_arguments.delta\ndata: {\"type\":\"response.function_call_arguments.delta\",\"delta\":\" -\",\"item_id\":\"fc_054a16a2f2ea3217016a705b68c39881a3b1e518599236e41a\",\"obfuscation\":\"gu7kgzv2IJ1Nyl\",\"output_index\":0,\"sequence_number\":25}", - "event: response.function_call_arguments.delta\ndata: {\"type\":\"response.function_call_arguments.delta\",\"delta\":\"max\",\"item_id\":\"fc_054a16a2f2ea3217016a705b68c39881a3b1e518599236e41a\",\"obfuscation\":\"ftZoJcWqYeSjP\",\"output_index\":0,\"sequence_number\":26}", - "event: response.function_call_arguments.delta\ndata: {\"type\":\"response.function_call_arguments.delta\",\"delta\":\"depth\",\"item_id\":\"fc_054a16a2f2ea3217016a705b68c39881a3b1e518599236e41a\",\"obfuscation\":\"pVAaXJ9DEpZ\",\"output_index\":0,\"sequence_number\":27}", - "event: response.function_call_arguments.delta\ndata: {\"type\":\"response.function_call_arguments.delta\",\"delta\":\" \",\"item_id\":\"fc_054a16a2f2ea3217016a705b68c39881a3b1e518599236e41a\",\"obfuscation\":\"sR3ARyMb8XihmRv\",\"output_index\":0,\"sequence_number\":28}", - "event: response.function_call_arguments.delta\ndata: {\"type\":\"response.function_call_arguments.delta\",\"delta\":\"6\",\"item_id\":\"fc_054a16a2f2ea3217016a705b68c39881a3b1e518599236e41a\",\"obfuscation\":\"O5oruVniSAltaiT\",\"output_index\":0,\"sequence_number\":29}", - "event: response.function_call_arguments.delta\ndata: {\"type\":\"response.function_call_arguments.delta\",\"delta\":\" -\",\"item_id\":\"fc_054a16a2f2ea3217016a705b68c39881a3b1e518599236e41a\",\"obfuscation\":\"vv0l4nT2EtqHMG\",\"output_index\":0,\"sequence_number\":30}", - "event: response.function_call_arguments.delta\ndata: {\"type\":\"response.function_call_arguments.delta\",\"delta\":\"type\",\"item_id\":\"fc_054a16a2f2ea3217016a705b68c39881a3b1e518599236e41a\",\"obfuscation\":\"eCQV3vrXZ37f\",\"output_index\":0,\"sequence_number\":31}", - "event: response.function_call_arguments.delta\ndata: {\"type\":\"response.function_call_arguments.delta\",\"delta\":\" f\",\"item_id\":\"fc_054a16a2f2ea3217016a705b68c39881a3b1e518599236e41a\",\"obfuscation\":\"LD2tChTMhpkm7O\",\"output_index\":0,\"sequence_number\":32}", - "event: response.function_call_arguments.delta\ndata: {\"type\":\"response.function_call_arguments.delta\",\"delta\":\" -\",\"item_id\":\"fc_054a16a2f2ea3217016a705b68c39881a3b1e518599236e41a\",\"obfuscation\":\"dbjx8JTI5wqqpk\",\"output_index\":0,\"sequence_number\":33}", - "event: response.function_call_arguments.delta\ndata: {\"type\":\"response.function_call_arguments.delta\",\"delta\":\"name\",\"item_id\":\"fc_054a16a2f2ea3217016a705b68c39881a3b1e518599236e41a\",\"obfuscation\":\"CMGMxsC84MqB\",\"output_index\":0,\"sequence_number\":34}", - "event: response.function_call_arguments.delta\ndata: {\"type\":\"response.function_call_arguments.delta\",\"delta\":\" '\",\"item_id\":\"fc_054a16a2f2ea3217016a705b68c39881a3b1e518599236e41a\",\"obfuscation\":\"IP4ilgz1kRzQlU\",\"output_index\":0,\"sequence_number\":35}", - "event: response.function_call_arguments.delta\ndata: {\"type\":\"response.function_call_arguments.delta\",\"delta\":\"AG\",\"item_id\":\"fc_054a16a2f2ea3217016a705b68c39881a3b1e518599236e41a\",\"obfuscation\":\"30S1wp0FSdH8j0\",\"output_index\":0,\"sequence_number\":36}", - "event: response.function_call_arguments.delta\ndata: {\"type\":\"response.function_call_arguments.delta\",\"delta\":\"ENTS\",\"item_id\":\"fc_054a16a2f2ea3217016a705b68c39881a3b1e518599236e41a\",\"obfuscation\":\"LbBUDVVtGwst\",\"output_index\":0,\"sequence_number\":37}", - "event: response.function_call_arguments.delta\ndata: {\"type\":\"response.function_call_arguments.delta\",\"delta\":\".md\",\"item_id\":\"fc_054a16a2f2ea3217016a705b68c39881a3b1e518599236e41a\",\"obfuscation\":\"8piB3U9Zy7jyA\",\"output_index\":0,\"sequence_number\":38}", - "event: response.function_call_arguments.delta\ndata: {\"type\":\"response.function_call_arguments.delta\",\"delta\":\"'\",\"item_id\":\"fc_054a16a2f2ea3217016a705b68c39881a3b1e518599236e41a\",\"obfuscation\":\"9DRBJbRy6maC4gS\",\"output_index\":0,\"sequence_number\":39}", - "event: response.function_call_arguments.delta\ndata: {\"type\":\"response.function_call_arguments.delta\",\"delta\":\" -\",\"item_id\":\"fc_054a16a2f2ea3217016a705b68c39881a3b1e518599236e41a\",\"obfuscation\":\"XAt6MDPojUNwz3\",\"output_index\":0,\"sequence_number\":40}", - "event: response.function_call_arguments.delta\ndata: {\"type\":\"response.function_call_arguments.delta\",\"delta\":\"print\",\"item_id\":\"fc_054a16a2f2ea3217016a705b68c39881a3b1e518599236e41a\",\"obfuscation\":\"2sb7d6gE12q\",\"output_index\":0,\"sequence_number\":41}", - "event: response.function_call_arguments.delta\ndata: {\"type\":\"response.function_call_arguments.delta\",\"delta\":\"\\\",\\\"\",\"item_id\":\"fc_054a16a2f2ea3217016a705b68c39881a3b1e518599236e41a\",\"obfuscation\":\"78xkqC87iDA2l\",\"output_index\":0,\"sequence_number\":42}", - "event: response.function_call_arguments.delta\ndata: {\"type\":\"response.function_call_arguments.delta\",\"delta\":\"timeout\",\"item_id\":\"fc_054a16a2f2ea3217016a705b68c39881a3b1e518599236e41a\",\"obfuscation\":\"LZICbxiFz\",\"output_index\":0,\"sequence_number\":43}", - "event: response.function_call_arguments.delta\ndata: {\"type\":\"response.function_call_arguments.delta\",\"delta\":\"\\\":\",\"item_id\":\"fc_054a16a2f2ea3217016a705b68c39881a3b1e518599236e41a\",\"obfuscation\":\"L39ZgCy9F9L7ou\",\"output_index\":0,\"sequence_number\":44}", - "event: response.function_call_arguments.delta\ndata: {\"type\":\"response.function_call_arguments.delta\",\"delta\":\"100\",\"item_id\":\"fc_054a16a2f2ea3217016a705b68c39881a3b1e518599236e41a\",\"obfuscation\":\"cmBrFOna5ifiL\",\"output_index\":0,\"sequence_number\":45}", - "event: response.function_call_arguments.delta\ndata: {\"type\":\"response.function_call_arguments.delta\",\"delta\":\"000\",\"item_id\":\"fc_054a16a2f2ea3217016a705b68c39881a3b1e518599236e41a\",\"obfuscation\":\"z5qEzhvJIbUVN\",\"output_index\":0,\"sequence_number\":46}", - "event: response.function_call_arguments.delta\ndata: {\"type\":\"response.function_call_arguments.delta\",\"delta\":\"}\",\"item_id\":\"fc_054a16a2f2ea3217016a705b68c39881a3b1e518599236e41a\",\"obfuscation\":\"S29PMSSd6WVTIYN\",\"output_index\":0,\"sequence_number\":47}", - "event: response.function_call_arguments.done\ndata: {\"type\":\"response.function_call_arguments.done\",\"arguments\":\"{\\\"command\\\":\\\"find . -maxdepth 6 -type f -name 'AGENTS.md' -print\\\",\\\"timeout\\\":100000}\",\"item_id\":\"fc_054a16a2f2ea3217016a705b68c39881a3b1e518599236e41a\",\"output_index\":0,\"sequence_number\":48}", - "event: response.output_item.done\ndata: {\"type\":\"response.output_item.done\",\"item\":{\"id\":\"fc_054a16a2f2ea3217016a705b68c39881a3b1e518599236e41a\",\"type\":\"function_call\",\"status\":\"completed\",\"arguments\":\"{\\\"command\\\":\\\"find . -maxdepth 6 -type f -name 'AGENTS.md' -print\\\",\\\"timeout\\\":100000}\",\"call_id\":\"call_NgMSUb6wIR0GAf9U8LXERtBQ\",\"name\":\"bash\"},\"output_index\":0,\"sequence_number\":49}", - "event: response.completed\ndata: {\"type\":\"response.completed\",\"response\":{\"id\":\"resp_054a16a2f2ea3217016a705b6820c481a3b64f6527345dc3ca\",\"object\":\"response\",\"created_at\":1785748328,\"status\":\"completed\",\"background\":false,\"completed_at\":1785748329,\"error\":null,\"frequency_penalty\":0,\"incomplete_details\":null,\"instructions\":null,\"max_output_tokens\":null,\"max_tool_calls\":null,\"model\":\"gpt-5.4-nano-2026-03-17\",\"moderation\":null,\"output\":[{\"id\":\"fc_054a16a2f2ea3217016a705b68c39881a3b1e518599236e41a\",\"type\":\"function_call\",\"status\":\"completed\",\"arguments\":\"{\\\"command\\\":\\\"find . -maxdepth 6 -type f -name 'AGENTS.md' -print\\\",\\\"timeout\\\":100000}\",\"call_id\":\"call_NgMSUb6wIR0GAf9U8LXERtBQ\",\"name\":\"bash\"}],\"parallel_tool_calls\":true,\"presence_penalty\":0,\"previous_response_id\":null,\"prompt_cache_key\":\"workflow:instrumentation:01KZ3E9TWTAEK4P4P3F63HKAKC::default::ta\",\"prompt_cache_retention\":\"24h\",\"reasoning\":{\"context\":\"current_turn\",\"effort\":\"none\",\"mode\":\"standard\",\"summary\":null},\"safety_identifier\":null,\"service_tier\":\"default\",\"store\":false,\"temperature\":1,\"text\":{\"format\":{\"type\":\"text\"},\"verbosity\":\"medium\"},\"tool_choice\":\"auto\",\"tool_usage\":{\"image_gen\":{\"input_tokens\":0,\"input_tokens_details\":{\"image_tokens\":0,\"text_tokens\":0},\"output_tokens\":0,\"output_tokens_details\":{\"image_tokens\":0,\"text_tokens\":0},\"total_tokens\":0},\"web_search\":{\"num_requests\":0}},\"tools\":[{\"type\":\"function\",\"description\":\"Read a file or list a directory. For files, output is truncated to 2000 lines or 50KB — use offset/limit for large files. For directories, returns the list of entries.\",\"name\":\"read\",\"output_schema\":null,\"parameters\":{\"type\":\"object\",\"required\":[\"path\"],\"properties\":{\"path\":{\"type\":\"string\",\"description\":\"Path to the file to read\"},\"offset\":{\"type\":\"number\",\"description\":\"Line number to start from (1-indexed)\"},\"limit\":{\"type\":\"number\",\"description\":\"Maximum number of lines to read\"}}},\"strict\":false},{\"type\":\"function\",\"description\":\"Write content to a file. Creates the file and parent directories if they do not exist.\",\"name\":\"write\",\"output_schema\":null,\"parameters\":{\"type\":\"object\",\"required\":[\"path\",\"content\"],\"properties\":{\"path\":{\"type\":\"string\",\"description\":\"Path to the file to write\"},\"content\":{\"type\":\"string\",\"description\":\"Content to write to the file\"}}},\"strict\":false},{\"type\":\"function\",\"description\":\"Edit a file using exact text replacement. The oldText must match a unique region of the file. Use replaceAll to replace all occurrences.\",\"name\":\"edit\",\"output_schema\":null,\"parameters\":{\"type\":\"object\",\"required\":[\"path\",\"oldText\",\"newText\"],\"properties\":{\"path\":{\"type\":\"string\",\"description\":\"Path to the file to edit\"},\"oldText\":{\"type\":\"string\",\"description\":\"Exact text to find (must be unique)\"},\"newText\":{\"type\":\"string\",\"description\":\"Replacement text\"},\"replaceAll\":{\"type\":\"boolean\",\"description\":\"Replace all occurrences\"}}},\"strict\":false},{\"type\":\"function\",\"description\":\"Execute a bash command. Returns stdout and stderr. Output is truncated to the last 2000 lines or 50KB.\",\"name\":\"bash\",\"output_schema\":null,\"parameters\":{\"type\":\"object\",\"required\":[\"command\"],\"properties\":{\"command\":{\"type\":\"string\",\"description\":\"Bash command to execute\"},\"timeout\":{\"type\":\"number\",\"description\":\"Timeout in seconds\"}}},\"strict\":false},{\"type\":\"function\",\"description\":\"Search file contents for a regex pattern. Returns matching lines with file paths and line numbers.\",\"name\":\"grep\",\"output_schema\":null,\"parameters\":{\"type\":\"object\",\"required\":[\"pattern\"],\"properties\":{\"pattern\":{\"type\":\"string\",\"description\":\"Search pattern (regex)\"},\"path\":{\"type\":\"string\",\"description\":\"Directory or file to search (default: .)\"},\"include\":{\"type\":\"string\",\"description\":\"Glob filter, e.g. \\\"*.ts\\\"\"}}},\"strict\":false},{\"type\":\"function\",\"description\":\"Find files by filename pattern using shell find -name semantics. Returns matching file paths.\",\"name\":\"glob\",\"output_schema\":null,\"parameters\":{\"type\":\"object\",\"required\":[\"pattern\"],\"properties\":{\"pattern\":{\"type\":\"string\",\"description\":\"Filename pattern, e.g. \\\"*.ts\\\"\"},\"path\":{\"type\":\"string\",\"description\":\"Directory to search in (default: .)\"}}},\"strict\":false},{\"type\":\"function\",\"description\":\"Delegate a focused task to a detached child agent with its own context. Use this for independent research, file exploration, or parallel work. The task returns only its final answer to this conversation. No subagents are currently defined.\",\"name\":\"task\",\"output_schema\":null,\"parameters\":{\"type\":\"object\",\"required\":[\"prompt\"],\"properties\":{\"description\":{\"type\":\"string\",\"description\":\"Short human-readable label for the delegated work\"},\"prompt\":{\"type\":\"string\",\"description\":\"Focused instructions for the child agent\"},\"agent\":{\"type\":\"string\",\"description\":\"Declared subagent to use for the child agent\"},\"cwd\":{\"type\":\"string\",\"description\":\"Working directory for the child agent. AGENTS.md and skills are discovered from here.\"}}},\"strict\":false}],\"top_logprobs\":0,\"top_p\":0.98,\"truncation\":\"disabled\",\"usage\":{\"input_tokens\":1298,\"input_tokens_details\":{\"cache_write_tokens\":0,\"cached_tokens\":0},\"output_tokens\":58,\"output_tokens_details\":{\"reasoning_tokens\":0},\"total_tokens\":1356},\"user\":null,\"metadata\":{}},\"sequence_number\":50}" - ], - "kind": "sse" - }, - "headers": { - "access-control-expose-headers": "X-Request-ID, CF-Ray, CF-Ray", - "alt-svc": "h3=\":443\"; ma=86400", - "cf-cache-status": "DYNAMIC", - "cf-ray": "a25432ea7f5d7bb6-IAD", - "connection": "keep-alive", - "content-type": "text/event-stream; charset=utf-8", - "date": "Mon, 03 Aug 2026 09:12:08 GMT", - "openai-organization": "braintrust-data", - "openai-processing-ms": "428", - "openai-project": "proj_vsCSXafhhByzWOThMrJcZiw9", - "openai-version": "2020-10-01", - "server": "cloudflare", - "set-cookie": "[REDACTED]", - "strict-transport-security": "max-age=31536000; includeSubDomains; preload", - "transfer-encoding": "chunked", - "x-content-type-options": "nosniff", - "x-ratelimit-limit-requests": "30000", - "x-ratelimit-limit-tokens": "180000000", - "x-ratelimit-remaining-requests": "29999", - "x-ratelimit-remaining-tokens": "179998245", - "x-ratelimit-reset-requests": "2ms", - "x-ratelimit-reset-tokens": "0s", - "x-request-id": "req_e00fc12bdba24599905386f92e325433" - }, - "status": 200, - "statusText": "OK" - } - }, - { - "callIndex": 9, - "id": "6945bf18d345ad4a", - "matchKey": "POST api.openai.com/v1/responses", - "recordedAt": "2026-08-03T09:12:10.503Z", - "request": { - "body": { - "kind": "json", - "value": { - "input": [ - { - "content": "You are running in headless mode with no human operator. Work autonomously — never ask questions, never wait for user input. Make your best judgment and proceed independently.\n\nYou are a deterministic Flue instrumentation test agent. Follow user instructions exactly. When asked for a marker, output only that marker and no extra text. When running a local skill file, read it yourself and do not delegate it to a task.\n\n## Available Skills\n\nThe following skills provide specialized instructions for specific tasks. When a task matches a skill description, activate that skill before proceeding so its full instructions are loaded. Skill instructions and supporting resources stay lazy until activation or explicit file reads.\n\n- **e2e-flue-skill** — Deterministic skill used by the Braintrust Flue instrumentation e2e test.\n\nDate: Mon, Aug 3, 2026\nWorking directory: .\n\nDirectory structure:\n.agents\n.flue\n.flue-build\n__cassettes__\n__snapshots__\nassertions.ts\ncassette-filter.mjs\nconstants.mjs\ndist\nnode_modules\npackage.json\npnpm-lock.yaml\nscenario.cli.mjs\nscenario.impl.mjs\nscenario.mjs\nscenario.test.ts\nscenario.ts\nscenario.v2.ts\nv2-assertions.ts\nversions", - "role": "developer" - }, - { - "content": [ - { - "text": "Run the skill named \"e2e-flue-skill\" with arguments {\"marker\":\"SKILL_DONE\"}. Output only the marker and no extra text when finished.", - "type": "input_text" - } - ], - "role": "user" - }, - { - "arguments": "{\"description\":\"Run e2e-flue-skill with marker SKILL_DONE\",\"prompt\":\"Run the skill named \\\"e2e-flue-skill\\\" with arguments {\\\"marker\\\":\\\"SKILL_DONE\\\"}. Ensure completion and report the final output marker only.\",\"agent\":\"e2e-flue-skill\",\"cwd\":\".\"}", - "call_id": "call_w6pyYZgnhlSOAXul75DAG1Cj", - "id": "fc_054a16a2f2ea3217016a705b64fe5481a390185c9576df12b6", - "name": "task", - "type": "function_call" - }, - { - "call_id": "call_w6pyYZgnhlSOAXul75DAG1Cj", - "output": "[flue] Subagent \"e2e-flue-skill\" is not declared. Available: (none).", - "type": "function_call_output" - }, - { - "arguments": "{\"command\":\"find . -maxdepth 4 -name AGENTS.md -print\",\"timeout\":100000}", - "call_id": "call_6zEBnJRmXFIkGFwNjWLZP4uC", - "id": "fc_054a16a2f2ea3217016a705b66460481a3a5de06df603e35ee", - "name": "bash", - "type": "function_call" - }, - { - "call_id": "call_6zEBnJRmXFIkGFwNjWLZP4uC", - "output": "total 4\ndrwxr-xr-x 3 vercel-sandbox vercel-sandbox 20 Aug 3 09:10 .\ndrwxr-xr-x 10 vercel-sandbox vercel-sandbox 4096 Aug 3 09:11 ..\ndrwxr-xr-x 3 vercel-sandbox vercel-sandbox 28 Aug 3 09:10 skills", - "type": "function_call_output" - }, - { - "arguments": "{\"command\":\"find . -maxdepth 4 -type f -name AGENTS.md -print -exec sed -n '1,200p' {} \\\\;\",\"timeout\":100000}", - "call_id": "call_jQUCie0ZgaXUrZCDcezp0msl", - "id": "fc_054a16a2f2ea3217016a705b67575881a3aacde02b9053a321", - "name": "bash", - "type": "function_call" - }, - { - "call_id": "call_jQUCie0ZgaXUrZCDcezp0msl", - "output": "(no output)", - "type": "function_call_output" - }, - { - "arguments": "{\"command\":\"find . -maxdepth 6 -type f -name 'AGENTS.md' -print\",\"timeout\":100000}", - "call_id": "call_NgMSUb6wIR0GAf9U8LXERtBQ", - "id": "fc_054a16a2f2ea3217016a705b68c39881a3b1e518599236e41a", - "name": "bash", - "type": "function_call" - }, - { - "call_id": "call_NgMSUb6wIR0GAf9U8LXERtBQ", - "output": "(no output)", - "type": "function_call_output" - } - ], - "model": "gpt-5.4-nano", - "prompt_cache_key": "[REDACTED]", - "reasoning": { - "effort": "none" - }, - "store": false, - "stream": true, - "tools": [ - { - "description": "Read a file or list a directory. For files, output is truncated to 2000 lines or 50KB — use offset/limit for large files. For directories, returns the list of entries.", - "name": "read", - "parameters": { - "properties": { - "limit": { - "description": "Maximum number of lines to read", - "type": "number" - }, - "offset": { - "description": "Line number to start from (1-indexed)", - "type": "number" - }, - "path": { - "description": "Path to the file to read", - "type": "string" - } - }, - "required": ["path"], - "type": "object" - }, - "strict": false, - "type": "function" - }, - { - "description": "Write content to a file. Creates the file and parent directories if they do not exist.", - "name": "write", - "parameters": { - "properties": { - "content": { - "description": "Content to write to the file", - "type": "string" - }, - "path": { - "description": "Path to the file to write", - "type": "string" - } - }, - "required": ["path", "content"], - "type": "object" - }, - "strict": false, - "type": "function" - }, - { - "description": "Edit a file using exact text replacement. The oldText must match a unique region of the file. Use replaceAll to replace all occurrences.", - "name": "edit", - "parameters": { - "properties": { - "newText": { - "description": "Replacement text", - "type": "string" - }, - "oldText": { - "description": "Exact text to find (must be unique)", - "type": "string" - }, - "path": { - "description": "Path to the file to edit", - "type": "string" - }, - "replaceAll": { - "description": "Replace all occurrences", - "type": "boolean" - } - }, - "required": ["path", "oldText", "newText"], - "type": "object" - }, - "strict": false, - "type": "function" - }, - { - "description": "Execute a bash command. Returns stdout and stderr. Output is truncated to the last 2000 lines or 50KB.", - "name": "bash", - "parameters": { - "properties": { - "command": { - "description": "Bash command to execute", - "type": "string" - }, - "timeout": { - "description": "Timeout in seconds", - "type": "number" - } - }, - "required": ["command"], - "type": "object" - }, - "strict": false, - "type": "function" - }, - { - "description": "Search file contents for a regex pattern. Returns matching lines with file paths and line numbers.", - "name": "grep", - "parameters": { - "properties": { - "include": { - "description": "Glob filter, e.g. \"*.ts\"", - "type": "string" - }, - "path": { - "description": "Directory or file to search (default: .)", - "type": "string" - }, - "pattern": { - "description": "Search pattern (regex)", - "type": "string" - } - }, - "required": ["pattern"], - "type": "object" - }, - "strict": false, - "type": "function" - }, - { - "description": "Find files by filename pattern using shell find -name semantics. Returns matching file paths.", - "name": "glob", - "parameters": { - "properties": { - "path": { - "description": "Directory to search in (default: .)", - "type": "string" - }, - "pattern": { - "description": "Filename pattern, e.g. \"*.ts\"", - "type": "string" - } - }, - "required": ["pattern"], - "type": "object" - }, - "strict": false, - "type": "function" - }, - { - "description": "Delegate a focused task to a detached child agent with its own context. Use this for independent research, file exploration, or parallel work. The task returns only its final answer to this conversation. No subagents are currently defined.", - "name": "task", - "parameters": { - "properties": { - "agent": { - "description": "Declared subagent to use for the child agent", - "type": "string" - }, - "cwd": { - "description": "Working directory for the child agent. AGENTS.md and skills are discovered from here.", - "type": "string" - }, - "description": { - "description": "Short human-readable label for the delegated work", - "type": "string" - }, - "prompt": { - "description": "Focused instructions for the child agent", - "type": "string" - } - }, - "required": ["prompt"], - "type": "object" - }, - "strict": false, - "type": "function" - } - ] - } - }, - "headers": {}, - "method": "POST", - "url": "https://api.openai.com/v1/responses" - }, - "response": { - "body": { - "chunks": [ - "event: response.created\ndata: {\"type\":\"response.created\",\"response\":{\"id\":\"resp_054a16a2f2ea3217016a705b69681081a381f7cdcf2e5f8c38\",\"object\":\"response\",\"created_at\":1785748329,\"status\":\"in_progress\",\"background\":false,\"completed_at\":null,\"error\":null,\"frequency_penalty\":0,\"incomplete_details\":null,\"instructions\":null,\"max_output_tokens\":null,\"max_tool_calls\":null,\"model\":\"gpt-5.4-nano-2026-03-17\",\"moderation\":null,\"output\":[],\"parallel_tool_calls\":true,\"presence_penalty\":0,\"previous_response_id\":null,\"prompt_cache_key\":\"workflow:instrumentation:01KZ3E9TWTAEK4P4P3F63HKAKC::default::ta\",\"prompt_cache_retention\":\"24h\",\"reasoning\":{\"context\":\"current_turn\",\"effort\":\"none\",\"mode\":\"standard\",\"summary\":null},\"safety_identifier\":null,\"service_tier\":\"auto\",\"store\":false,\"temperature\":1,\"text\":{\"format\":{\"type\":\"text\"},\"verbosity\":\"medium\"},\"tool_choice\":\"auto\",\"tool_usage\":{\"image_gen\":{\"input_tokens\":0,\"input_tokens_details\":{\"image_tokens\":0,\"text_tokens\":0},\"output_tokens\":0,\"output_tokens_details\":{\"image_tokens\":0,\"text_tokens\":0},\"total_tokens\":0},\"web_search\":{\"num_requests\":0}},\"tools\":[{\"type\":\"function\",\"description\":\"Read a file or list a directory. For files, output is truncated to 2000 lines or 50KB — use offset/limit for large files. For directories, returns the list of entries.\",\"name\":\"read\",\"output_schema\":null,\"parameters\":{\"type\":\"object\",\"required\":[\"path\"],\"properties\":{\"path\":{\"type\":\"string\",\"description\":\"Path to the file to read\"},\"offset\":{\"type\":\"number\",\"description\":\"Line number to start from (1-indexed)\"},\"limit\":{\"type\":\"number\",\"description\":\"Maximum number of lines to read\"}}},\"strict\":false},{\"type\":\"function\",\"description\":\"Write content to a file. Creates the file and parent directories if they do not exist.\",\"name\":\"write\",\"output_schema\":null,\"parameters\":{\"type\":\"object\",\"required\":[\"path\",\"content\"],\"properties\":{\"path\":{\"type\":\"string\",\"description\":\"Path to the file to write\"},\"content\":{\"type\":\"string\",\"description\":\"Content to write to the file\"}}},\"strict\":false},{\"type\":\"function\",\"description\":\"Edit a file using exact text replacement. The oldText must match a unique region of the file. Use replaceAll to replace all occurrences.\",\"name\":\"edit\",\"output_schema\":null,\"parameters\":{\"type\":\"object\",\"required\":[\"path\",\"oldText\",\"newText\"],\"properties\":{\"path\":{\"type\":\"string\",\"description\":\"Path to the file to edit\"},\"oldText\":{\"type\":\"string\",\"description\":\"Exact text to find (must be unique)\"},\"newText\":{\"type\":\"string\",\"description\":\"Replacement text\"},\"replaceAll\":{\"type\":\"boolean\",\"description\":\"Replace all occurrences\"}}},\"strict\":false},{\"type\":\"function\",\"description\":\"Execute a bash command. Returns stdout and stderr. Output is truncated to the last 2000 lines or 50KB.\",\"name\":\"bash\",\"output_schema\":null,\"parameters\":{\"type\":\"object\",\"required\":[\"command\"],\"properties\":{\"command\":{\"type\":\"string\",\"description\":\"Bash command to execute\"},\"timeout\":{\"type\":\"number\",\"description\":\"Timeout in seconds\"}}},\"strict\":false},{\"type\":\"function\",\"description\":\"Search file contents for a regex pattern. Returns matching lines with file paths and line numbers.\",\"name\":\"grep\",\"output_schema\":null,\"parameters\":{\"type\":\"object\",\"required\":[\"pattern\"],\"properties\":{\"pattern\":{\"type\":\"string\",\"description\":\"Search pattern (regex)\"},\"path\":{\"type\":\"string\",\"description\":\"Directory or file to search (default: .)\"},\"include\":{\"type\":\"string\",\"description\":\"Glob filter, e.g. \\\"*.ts\\\"\"}}},\"strict\":false},{\"type\":\"function\",\"description\":\"Find files by filename pattern using shell find -name semantics. Returns matching file paths.\",\"name\":\"glob\",\"output_schema\":null,\"parameters\":{\"type\":\"object\",\"required\":[\"pattern\"],\"properties\":{\"pattern\":{\"type\":\"string\",\"description\":\"Filename pattern, e.g. \\\"*.ts\\\"\"},\"path\":{\"type\":\"string\",\"description\":\"Directory to search in (default: .)\"}}},\"strict\":false},{\"type\":\"function\",\"description\":\"Delegate a focused task to a detached child agent with its own context. Use this for independent research, file exploration, or parallel work. The task returns only its final answer to this conversation. No subagents are currently defined.\",\"name\":\"task\",\"output_schema\":null,\"parameters\":{\"type\":\"object\",\"required\":[\"prompt\"],\"properties\":{\"description\":{\"type\":\"string\",\"description\":\"Short human-readable label for the delegated work\"},\"prompt\":{\"type\":\"string\",\"description\":\"Focused instructions for the child agent\"},\"agent\":{\"type\":\"string\",\"description\":\"Declared subagent to use for the child agent\"},\"cwd\":{\"type\":\"string\",\"description\":\"Working directory for the child agent. AGENTS.md and skills are discovered from here.\"}}},\"strict\":false}],\"top_logprobs\":0,\"top_p\":0.98,\"truncation\":\"disabled\",\"usage\":null,\"user\":null,\"metadata\":{}},\"sequence_number\":0}", - "event: response.in_progress\ndata: {\"type\":\"response.in_progress\",\"response\":{\"id\":\"resp_054a16a2f2ea3217016a705b69681081a381f7cdcf2e5f8c38\",\"object\":\"response\",\"created_at\":1785748329,\"status\":\"in_progress\",\"background\":false,\"completed_at\":null,\"error\":null,\"frequency_penalty\":0,\"incomplete_details\":null,\"instructions\":null,\"max_output_tokens\":null,\"max_tool_calls\":null,\"model\":\"gpt-5.4-nano-2026-03-17\",\"moderation\":null,\"output\":[],\"parallel_tool_calls\":true,\"presence_penalty\":0,\"previous_response_id\":null,\"prompt_cache_key\":\"workflow:instrumentation:01KZ3E9TWTAEK4P4P3F63HKAKC::default::ta\",\"prompt_cache_retention\":\"24h\",\"reasoning\":{\"context\":\"current_turn\",\"effort\":\"none\",\"mode\":\"standard\",\"summary\":null},\"safety_identifier\":null,\"service_tier\":\"auto\",\"store\":false,\"temperature\":1,\"text\":{\"format\":{\"type\":\"text\"},\"verbosity\":\"medium\"},\"tool_choice\":\"auto\",\"tool_usage\":{\"image_gen\":{\"input_tokens\":0,\"input_tokens_details\":{\"image_tokens\":0,\"text_tokens\":0},\"output_tokens\":0,\"output_tokens_details\":{\"image_tokens\":0,\"text_tokens\":0},\"total_tokens\":0},\"web_search\":{\"num_requests\":0}},\"tools\":[{\"type\":\"function\",\"description\":\"Read a file or list a directory. For files, output is truncated to 2000 lines or 50KB — use offset/limit for large files. For directories, returns the list of entries.\",\"name\":\"read\",\"output_schema\":null,\"parameters\":{\"type\":\"object\",\"required\":[\"path\"],\"properties\":{\"path\":{\"type\":\"string\",\"description\":\"Path to the file to read\"},\"offset\":{\"type\":\"number\",\"description\":\"Line number to start from (1-indexed)\"},\"limit\":{\"type\":\"number\",\"description\":\"Maximum number of lines to read\"}}},\"strict\":false},{\"type\":\"function\",\"description\":\"Write content to a file. Creates the file and parent directories if they do not exist.\",\"name\":\"write\",\"output_schema\":null,\"parameters\":{\"type\":\"object\",\"required\":[\"path\",\"content\"],\"properties\":{\"path\":{\"type\":\"string\",\"description\":\"Path to the file to write\"},\"content\":{\"type\":\"string\",\"description\":\"Content to write to the file\"}}},\"strict\":false},{\"type\":\"function\",\"description\":\"Edit a file using exact text replacement. The oldText must match a unique region of the file. Use replaceAll to replace all occurrences.\",\"name\":\"edit\",\"output_schema\":null,\"parameters\":{\"type\":\"object\",\"required\":[\"path\",\"oldText\",\"newText\"],\"properties\":{\"path\":{\"type\":\"string\",\"description\":\"Path to the file to edit\"},\"oldText\":{\"type\":\"string\",\"description\":\"Exact text to find (must be unique)\"},\"newText\":{\"type\":\"string\",\"description\":\"Replacement text\"},\"replaceAll\":{\"type\":\"boolean\",\"description\":\"Replace all occurrences\"}}},\"strict\":false},{\"type\":\"function\",\"description\":\"Execute a bash command. Returns stdout and stderr. Output is truncated to the last 2000 lines or 50KB.\",\"name\":\"bash\",\"output_schema\":null,\"parameters\":{\"type\":\"object\",\"required\":[\"command\"],\"properties\":{\"command\":{\"type\":\"string\",\"description\":\"Bash command to execute\"},\"timeout\":{\"type\":\"number\",\"description\":\"Timeout in seconds\"}}},\"strict\":false},{\"type\":\"function\",\"description\":\"Search file contents for a regex pattern. Returns matching lines with file paths and line numbers.\",\"name\":\"grep\",\"output_schema\":null,\"parameters\":{\"type\":\"object\",\"required\":[\"pattern\"],\"properties\":{\"pattern\":{\"type\":\"string\",\"description\":\"Search pattern (regex)\"},\"path\":{\"type\":\"string\",\"description\":\"Directory or file to search (default: .)\"},\"include\":{\"type\":\"string\",\"description\":\"Glob filter, e.g. \\\"*.ts\\\"\"}}},\"strict\":false},{\"type\":\"function\",\"description\":\"Find files by filename pattern using shell find -name semantics. Returns matching file paths.\",\"name\":\"glob\",\"output_schema\":null,\"parameters\":{\"type\":\"object\",\"required\":[\"pattern\"],\"properties\":{\"pattern\":{\"type\":\"string\",\"description\":\"Filename pattern, e.g. \\\"*.ts\\\"\"},\"path\":{\"type\":\"string\",\"description\":\"Directory to search in (default: .)\"}}},\"strict\":false},{\"type\":\"function\",\"description\":\"Delegate a focused task to a detached child agent with its own context. Use this for independent research, file exploration, or parallel work. The task returns only its final answer to this conversation. No subagents are currently defined.\",\"name\":\"task\",\"output_schema\":null,\"parameters\":{\"type\":\"object\",\"required\":[\"prompt\"],\"properties\":{\"description\":{\"type\":\"string\",\"description\":\"Short human-readable label for the delegated work\"},\"prompt\":{\"type\":\"string\",\"description\":\"Focused instructions for the child agent\"},\"agent\":{\"type\":\"string\",\"description\":\"Declared subagent to use for the child agent\"},\"cwd\":{\"type\":\"string\",\"description\":\"Working directory for the child agent. AGENTS.md and skills are discovered from here.\"}}},\"strict\":false}],\"top_logprobs\":0,\"top_p\":0.98,\"truncation\":\"disabled\",\"usage\":null,\"user\":null,\"metadata\":{}},\"sequence_number\":1}", - "event: response.output_item.added\ndata: {\"type\":\"response.output_item.added\",\"item\":{\"id\":\"fc_054a16a2f2ea3217016a705b69edd081a38c31583ec56eca5f\",\"type\":\"function_call\",\"status\":\"in_progress\",\"arguments\":\"\",\"call_id\":\"call_BXKiQ5KG8c42XPPp94zgcY3H\",\"name\":\"bash\"},\"output_index\":0,\"sequence_number\":2}", - "event: response.function_call_arguments.delta\ndata: {\"type\":\"response.function_call_arguments.delta\",\"delta\":\"{\\\"\",\"item_id\":\"fc_054a16a2f2ea3217016a705b69edd081a38c31583ec56eca5f\",\"obfuscation\":\"hG7XwE8kTftCFD\",\"output_index\":0,\"sequence_number\":3}", - "event: response.function_call_arguments.delta\ndata: {\"type\":\"response.function_call_arguments.delta\",\"delta\":\"command\",\"item_id\":\"fc_054a16a2f2ea3217016a705b69edd081a38c31583ec56eca5f\",\"obfuscation\":\"SAESyTCDU\",\"output_index\":0,\"sequence_number\":4}", - "event: response.function_call_arguments.delta\ndata: {\"type\":\"response.function_call_arguments.delta\",\"delta\":\"\\\":\\\"\",\"item_id\":\"fc_054a16a2f2ea3217016a705b69edd081a38c31583ec56eca5f\",\"obfuscation\":\"FdSHAU6VPfEiP\",\"output_index\":0,\"sequence_number\":5}", - "event: response.function_call_arguments.delta\ndata: {\"type\":\"response.function_call_arguments.delta\",\"delta\":\"find\",\"item_id\":\"fc_054a16a2f2ea3217016a705b69edd081a38c31583ec56eca5f\",\"obfuscation\":\"JTitwOdHc00P\",\"output_index\":0,\"sequence_number\":6}", - "event: response.function_call_arguments.delta\ndata: {\"type\":\"response.function_call_arguments.delta\",\"delta\":\" /\",\"item_id\":\"fc_054a16a2f2ea3217016a705b69edd081a38c31583ec56eca5f\",\"obfuscation\":\"Z5ba86g8nOxzKr\",\"output_index\":0,\"sequence_number\":7}", - "event: response.function_call_arguments.delta\ndata: {\"type\":\"response.function_call_arguments.delta\",\"delta\":\"ver\",\"item_id\":\"fc_054a16a2f2ea3217016a705b69edd081a38c31583ec56eca5f\",\"obfuscation\":\"BUYeIHkrw1ca3\",\"output_index\":0,\"sequence_number\":8}", - "event: response.function_call_arguments.delta\ndata: {\"type\":\"response.function_call_arguments.delta\",\"delta\":\"cel\",\"item_id\":\"fc_054a16a2f2ea3217016a705b69edd081a38c31583ec56eca5f\",\"obfuscation\":\"Jnlz6e6YIi8An\",\"output_index\":0,\"sequence_number\":9}", - "event: response.function_call_arguments.delta\ndata: {\"type\":\"response.function_call_arguments.delta\",\"delta\":\"/s\",\"item_id\":\"fc_054a16a2f2ea3217016a705b69edd081a38c31583ec56eca5f\",\"obfuscation\":\"EiAxOlmPQ6R9Lk\",\"output_index\":0,\"sequence_number\":10}", - "event: response.function_call_arguments.delta\ndata: {\"type\":\"response.function_call_arguments.delta\",\"delta\":\"andbox\",\"item_id\":\"fc_054a16a2f2ea3217016a705b69edd081a38c31583ec56eca5f\",\"obfuscation\":\"HLwMNKLqXd\",\"output_index\":0,\"sequence_number\":11}", - "event: response.function_call_arguments.delta\ndata: {\"type\":\"response.function_call_arguments.delta\",\"delta\":\"/\",\"item_id\":\"fc_054a16a2f2ea3217016a705b69edd081a38c31583ec56eca5f\",\"obfuscation\":\"CyMa169D7RtZmdr\",\"output_index\":0,\"sequence_number\":12}", - "event: response.function_call_arguments.delta\ndata: {\"type\":\"response.function_call_arguments.delta\",\"delta\":\"brain\",\"item_id\":\"fc_054a16a2f2ea3217016a705b69edd081a38c31583ec56eca5f\",\"obfuscation\":\"eBBwyrijOzo\",\"output_index\":0,\"sequence_number\":13}", - "event: response.function_call_arguments.delta\ndata: {\"type\":\"response.function_call_arguments.delta\",\"delta\":\"trust\",\"item_id\":\"fc_054a16a2f2ea3217016a705b69edd081a38c31583ec56eca5f\",\"obfuscation\":\"9oZJd40vJbv\",\"output_index\":0,\"sequence_number\":14}", - "event: response.function_call_arguments.delta\ndata: {\"type\":\"response.function_call_arguments.delta\",\"delta\":\"data\",\"item_id\":\"fc_054a16a2f2ea3217016a705b69edd081a38c31583ec56eca5f\",\"obfuscation\":\"au0nu8FGh24U\",\"output_index\":0,\"sequence_number\":15}", - "event: response.function_call_arguments.delta\ndata: {\"type\":\"response.function_call_arguments.delta\",\"delta\":\"/\",\"item_id\":\"fc_054a16a2f2ea3217016a705b69edd081a38c31583ec56eca5f\",\"obfuscation\":\"mH2gvFHGB1gaA18\",\"output_index\":0,\"sequence_number\":16}", - "event: response.function_call_arguments.delta\ndata: {\"type\":\"response.function_call_arguments.delta\",\"delta\":\"brain\",\"item_id\":\"fc_054a16a2f2ea3217016a705b69edd081a38c31583ec56eca5f\",\"obfuscation\":\"W1iZL4cRSMq\",\"output_index\":0,\"sequence_number\":17}", - "event: response.function_call_arguments.delta\ndata: {\"type\":\"response.function_call_arguments.delta\",\"delta\":\"trust\",\"item_id\":\"fc_054a16a2f2ea3217016a705b69edd081a38c31583ec56eca5f\",\"obfuscation\":\"QUUnH5ISGOi\",\"output_index\":0,\"sequence_number\":18}", - "event: response.function_call_arguments.delta\ndata: {\"type\":\"response.function_call_arguments.delta\",\"delta\":\"-sdk\",\"item_id\":\"fc_054a16a2f2ea3217016a705b69edd081a38c31583ec56eca5f\",\"obfuscation\":\"eeLPcF9XcrHV\",\"output_index\":0,\"sequence_number\":19}", - "event: response.function_call_arguments.delta\ndata: {\"type\":\"response.function_call_arguments.delta\",\"delta\":\"-j\",\"item_id\":\"fc_054a16a2f2ea3217016a705b69edd081a38c31583ec56eca5f\",\"obfuscation\":\"lJ7n3mjPmhDNxo\",\"output_index\":0,\"sequence_number\":20}", - "event: response.function_call_arguments.delta\ndata: {\"type\":\"response.function_call_arguments.delta\",\"delta\":\"avascript\",\"item_id\":\"fc_054a16a2f2ea3217016a705b69edd081a38c31583ec56eca5f\",\"obfuscation\":\"Cq8la1K\",\"output_index\":0,\"sequence_number\":21}", - "event: response.function_call_arguments.delta\ndata: {\"type\":\"response.function_call_arguments.delta\",\"delta\":\"/e\",\"item_id\":\"fc_054a16a2f2ea3217016a705b69edd081a38c31583ec56eca5f\",\"obfuscation\":\"QCMTFUK5pMEVRm\",\"output_index\":0,\"sequence_number\":22}", - "event: response.function_call_arguments.delta\ndata: {\"type\":\"response.function_call_arguments.delta\",\"delta\":\"2\",\"item_id\":\"fc_054a16a2f2ea3217016a705b69edd081a38c31583ec56eca5f\",\"obfuscation\":\"BgRGVixu3UbxQDA\",\"output_index\":0,\"sequence_number\":23}", - "event: response.function_call_arguments.delta\ndata: {\"type\":\"response.function_call_arguments.delta\",\"delta\":\"e\",\"item_id\":\"fc_054a16a2f2ea3217016a705b69edd081a38c31583ec56eca5f\",\"obfuscation\":\"aqbUU7TCMgXGNus\",\"output_index\":0,\"sequence_number\":24}", - "event: response.function_call_arguments.delta\ndata: {\"type\":\"response.function_call_arguments.delta\",\"delta\":\" -\",\"item_id\":\"fc_054a16a2f2ea3217016a705b69edd081a38c31583ec56eca5f\",\"obfuscation\":\"0aahbmWnQc34bI\",\"output_index\":0,\"sequence_number\":25}", - "event: response.function_call_arguments.delta\ndata: {\"type\":\"response.function_call_arguments.delta\",\"delta\":\"max\",\"item_id\":\"fc_054a16a2f2ea3217016a705b69edd081a38c31583ec56eca5f\",\"obfuscation\":\"hLd8UqgGfFZ9D\",\"output_index\":0,\"sequence_number\":26}", - "event: response.function_call_arguments.delta\ndata: {\"type\":\"response.function_call_arguments.delta\",\"delta\":\"depth\",\"item_id\":\"fc_054a16a2f2ea3217016a705b69edd081a38c31583ec56eca5f\",\"obfuscation\":\"GmhwY6EWAHh\",\"output_index\":0,\"sequence_number\":27}", - "event: response.function_call_arguments.delta\ndata: {\"type\":\"response.function_call_arguments.delta\",\"delta\":\" \",\"item_id\":\"fc_054a16a2f2ea3217016a705b69edd081a38c31583ec56eca5f\",\"obfuscation\":\"NRuEQgh0p3eRHWo\",\"output_index\":0,\"sequence_number\":28}", - "event: response.function_call_arguments.delta\ndata: {\"type\":\"response.function_call_arguments.delta\",\"delta\":\"5\",\"item_id\":\"fc_054a16a2f2ea3217016a705b69edd081a38c31583ec56eca5f\",\"obfuscation\":\"VSUdF5XfszF7J7C\",\"output_index\":0,\"sequence_number\":29}", - "event: response.function_call_arguments.delta\ndata: {\"type\":\"response.function_call_arguments.delta\",\"delta\":\" -\",\"item_id\":\"fc_054a16a2f2ea3217016a705b69edd081a38c31583ec56eca5f\",\"obfuscation\":\"7bgbWInYXsOzEZ\",\"output_index\":0,\"sequence_number\":30}", - "event: response.function_call_arguments.delta\ndata: {\"type\":\"response.function_call_arguments.delta\",\"delta\":\"type\",\"item_id\":\"fc_054a16a2f2ea3217016a705b69edd081a38c31583ec56eca5f\",\"obfuscation\":\"ZdXd8MYxzBVa\",\"output_index\":0,\"sequence_number\":31}", - "event: response.function_call_arguments.delta\ndata: {\"type\":\"response.function_call_arguments.delta\",\"delta\":\" f\",\"item_id\":\"fc_054a16a2f2ea3217016a705b69edd081a38c31583ec56eca5f\",\"obfuscation\":\"lKHlgmcHv4yQ7n\",\"output_index\":0,\"sequence_number\":32}", - "event: response.function_call_arguments.delta\ndata: {\"type\":\"response.function_call_arguments.delta\",\"delta\":\" -\",\"item_id\":\"fc_054a16a2f2ea3217016a705b69edd081a38c31583ec56eca5f\",\"obfuscation\":\"O3v0EDtKmgDYYi\",\"output_index\":0,\"sequence_number\":33}", - "event: response.function_call_arguments.delta\ndata: {\"type\":\"response.function_call_arguments.delta\",\"delta\":\"name\",\"item_id\":\"fc_054a16a2f2ea3217016a705b69edd081a38c31583ec56eca5f\",\"obfuscation\":\"yOsJv2sjpuJT\",\"output_index\":0,\"sequence_number\":34}", - "event: response.function_call_arguments.delta\ndata: {\"type\":\"response.function_call_arguments.delta\",\"delta\":\" '*\",\"item_id\":\"fc_054a16a2f2ea3217016a705b69edd081a38c31583ec56eca5f\",\"obfuscation\":\"bT4zdeKIYUfHc\",\"output_index\":0,\"sequence_number\":35}", - "event: response.function_call_arguments.delta\ndata: {\"type\":\"response.function_call_arguments.delta\",\"delta\":\"fl\",\"item_id\":\"fc_054a16a2f2ea3217016a705b69edd081a38c31583ec56eca5f\",\"obfuscation\":\"jujglZGbsBwPa7\",\"output_index\":0,\"sequence_number\":36}", - "event: response.function_call_arguments.delta\ndata: {\"type\":\"response.function_call_arguments.delta\",\"delta\":\"ue\",\"item_id\":\"fc_054a16a2f2ea3217016a705b69edd081a38c31583ec56eca5f\",\"obfuscation\":\"LKKBJiCjnXh4xf\",\"output_index\":0,\"sequence_number\":37}", - "event: response.function_call_arguments.delta\ndata: {\"type\":\"response.function_call_arguments.delta\",\"delta\":\"*\",\"item_id\":\"fc_054a16a2f2ea3217016a705b69edd081a38c31583ec56eca5f\",\"obfuscation\":\"shy3qPjXsnqPzfn\",\"output_index\":0,\"sequence_number\":38}", - "event: response.function_call_arguments.delta\ndata: {\"type\":\"response.function_call_arguments.delta\",\"delta\":\"skill\",\"item_id\":\"fc_054a16a2f2ea3217016a705b69edd081a38c31583ec56eca5f\",\"obfuscation\":\"QCXNC1UuV7e\",\"output_index\":0,\"sequence_number\":39}", - "event: response.function_call_arguments.delta\ndata: {\"type\":\"response.function_call_arguments.delta\",\"delta\":\"*\",\"item_id\":\"fc_054a16a2f2ea3217016a705b69edd081a38c31583ec56eca5f\",\"obfuscation\":\"XG8mVOll3Ohqe18\",\"output_index\":0,\"sequence_number\":40}", - "event: response.function_call_arguments.delta\ndata: {\"type\":\"response.function_call_arguments.delta\",\"delta\":\"'\",\"item_id\":\"fc_054a16a2f2ea3217016a705b69edd081a38c31583ec56eca5f\",\"obfuscation\":\"twOToqPAOsKMMVy\",\"output_index\":0,\"sequence_number\":41}", - "event: response.function_call_arguments.delta\ndata: {\"type\":\"response.function_call_arguments.delta\",\"delta\":\" -\",\"item_id\":\"fc_054a16a2f2ea3217016a705b69edd081a38c31583ec56eca5f\",\"obfuscation\":\"DZtAspDqcPPg83\",\"output_index\":0,\"sequence_number\":42}", - "event: response.function_call_arguments.delta\ndata: {\"type\":\"response.function_call_arguments.delta\",\"delta\":\"o\",\"item_id\":\"fc_054a16a2f2ea3217016a705b69edd081a38c31583ec56eca5f\",\"obfuscation\":\"1vqyG924TjjeMm1\",\"output_index\":0,\"sequence_number\":43}", - "event: response.function_call_arguments.delta\ndata: {\"type\":\"response.function_call_arguments.delta\",\"delta\":\" -\",\"item_id\":\"fc_054a16a2f2ea3217016a705b69edd081a38c31583ec56eca5f\",\"obfuscation\":\"vbMfw1I6yQT071\",\"output_index\":0,\"sequence_number\":44}", - "event: response.function_call_arguments.delta\ndata: {\"type\":\"response.function_call_arguments.delta\",\"delta\":\"name\",\"item_id\":\"fc_054a16a2f2ea3217016a705b69edd081a38c31583ec56eca5f\",\"obfuscation\":\"yEkTfUjLa7T3\",\"output_index\":0,\"sequence_number\":45}", - "event: response.function_call_arguments.delta\ndata: {\"type\":\"response.function_call_arguments.delta\",\"delta\":\" '*\",\"item_id\":\"fc_054a16a2f2ea3217016a705b69edd081a38c31583ec56eca5f\",\"obfuscation\":\"QbcNOxt5ouCjy\",\"output_index\":0,\"sequence_number\":46}", - "event: response.function_call_arguments.delta\ndata: {\"type\":\"response.function_call_arguments.delta\",\"delta\":\"skill\",\"item_id\":\"fc_054a16a2f2ea3217016a705b69edd081a38c31583ec56eca5f\",\"obfuscation\":\"fkrT1lk0jv9\",\"output_index\":0,\"sequence_number\":47}", - "event: response.function_call_arguments.delta\ndata: {\"type\":\"response.function_call_arguments.delta\",\"delta\":\"*\",\"item_id\":\"fc_054a16a2f2ea3217016a705b69edd081a38c31583ec56eca5f\",\"obfuscation\":\"9RVtGG8sxWEOjJr\",\"output_index\":0,\"sequence_number\":48}", - "event: response.function_call_arguments.delta\ndata: {\"type\":\"response.function_call_arguments.delta\",\"delta\":\"'\",\"item_id\":\"fc_054a16a2f2ea3217016a705b69edd081a38c31583ec56eca5f\",\"obfuscation\":\"Za4XJQ2Z2IsOIeb\",\"output_index\":0,\"sequence_number\":49}", - "event: response.function_call_arguments.delta\ndata: {\"type\":\"response.function_call_arguments.delta\",\"delta\":\" |\",\"item_id\":\"fc_054a16a2f2ea3217016a705b69edd081a38c31583ec56eca5f\",\"obfuscation\":\"px85R0MdttFruZ\",\"output_index\":0,\"sequence_number\":50}", - "event: response.function_call_arguments.delta\ndata: {\"type\":\"response.function_call_arguments.delta\",\"delta\":\" head\",\"item_id\":\"fc_054a16a2f2ea3217016a705b69edd081a38c31583ec56eca5f\",\"obfuscation\":\"Y88f7Ld1Ora\",\"output_index\":0,\"sequence_number\":51}", - "event: response.function_call_arguments.delta\ndata: {\"type\":\"response.function_call_arguments.delta\",\"delta\":\"\\\",\\\"\",\"item_id\":\"fc_054a16a2f2ea3217016a705b69edd081a38c31583ec56eca5f\",\"obfuscation\":\"8moCEbjvgTG1n\",\"output_index\":0,\"sequence_number\":52}", - "event: response.function_call_arguments.delta\ndata: {\"type\":\"response.function_call_arguments.delta\",\"delta\":\"timeout\",\"item_id\":\"fc_054a16a2f2ea3217016a705b69edd081a38c31583ec56eca5f\",\"obfuscation\":\"Kmyh4pQvd\",\"output_index\":0,\"sequence_number\":53}", - "event: response.function_call_arguments.delta\ndata: {\"type\":\"response.function_call_arguments.delta\",\"delta\":\"\\\":\",\"item_id\":\"fc_054a16a2f2ea3217016a705b69edd081a38c31583ec56eca5f\",\"obfuscation\":\"GrJ0KmP1JEa4oC\",\"output_index\":0,\"sequence_number\":54}", - "event: response.function_call_arguments.delta\ndata: {\"type\":\"response.function_call_arguments.delta\",\"delta\":\"100\",\"item_id\":\"fc_054a16a2f2ea3217016a705b69edd081a38c31583ec56eca5f\",\"obfuscation\":\"tQ7b2AdVP1fwn\",\"output_index\":0,\"sequence_number\":55}", - "event: response.function_call_arguments.delta\ndata: {\"type\":\"response.function_call_arguments.delta\",\"delta\":\"000\",\"item_id\":\"fc_054a16a2f2ea3217016a705b69edd081a38c31583ec56eca5f\",\"obfuscation\":\"OGHfUzdqw3zns\",\"output_index\":0,\"sequence_number\":56}", - "event: response.function_call_arguments.delta\ndata: {\"type\":\"response.function_call_arguments.delta\",\"delta\":\"}\",\"item_id\":\"fc_054a16a2f2ea3217016a705b69edd081a38c31583ec56eca5f\",\"obfuscation\":\"4USiuoYvOOTVjiY\",\"output_index\":0,\"sequence_number\":57}", - "event: response.function_call_arguments.done\ndata: {\"type\":\"response.function_call_arguments.done\",\"arguments\":\"{\\\"command\\\":\\\"find . -maxdepth 5 -type f -name '*flue*skill*' -o -name '*skill*' | head\\\",\\\"timeout\\\":100000}\",\"item_id\":\"fc_054a16a2f2ea3217016a705b69edd081a38c31583ec56eca5f\",\"output_index\":0,\"sequence_number\":58}", - "event: response.output_item.done\ndata: {\"type\":\"response.output_item.done\",\"item\":{\"id\":\"fc_054a16a2f2ea3217016a705b69edd081a38c31583ec56eca5f\",\"type\":\"function_call\",\"status\":\"completed\",\"arguments\":\"{\\\"command\\\":\\\"find . -maxdepth 5 -type f -name '*flue*skill*' -o -name '*skill*' | head\\\",\\\"timeout\\\":100000}\",\"call_id\":\"call_BXKiQ5KG8c42XPPp94zgcY3H\",\"name\":\"bash\"},\"output_index\":0,\"sequence_number\":59}", - "event: response.completed\ndata: {\"type\":\"response.completed\",\"response\":{\"id\":\"resp_054a16a2f2ea3217016a705b69681081a381f7cdcf2e5f8c38\",\"object\":\"response\",\"created_at\":1785748329,\"status\":\"completed\",\"background\":false,\"completed_at\":1785748330,\"error\":null,\"frequency_penalty\":0,\"incomplete_details\":null,\"instructions\":null,\"max_output_tokens\":null,\"max_tool_calls\":null,\"model\":\"gpt-5.4-nano-2026-03-17\",\"moderation\":null,\"output\":[{\"id\":\"fc_054a16a2f2ea3217016a705b69edd081a38c31583ec56eca5f\",\"type\":\"function_call\",\"status\":\"completed\",\"arguments\":\"{\\\"command\\\":\\\"find . -maxdepth 5 -type f -name '*flue*skill*' -o -name '*skill*' | head\\\",\\\"timeout\\\":100000}\",\"call_id\":\"call_BXKiQ5KG8c42XPPp94zgcY3H\",\"name\":\"bash\"}],\"parallel_tool_calls\":true,\"presence_penalty\":0,\"previous_response_id\":null,\"prompt_cache_key\":\"workflow:instrumentation:01KZ3E9TWTAEK4P4P3F63HKAKC::default::ta\",\"prompt_cache_retention\":\"24h\",\"reasoning\":{\"context\":\"current_turn\",\"effort\":\"none\",\"mode\":\"standard\",\"summary\":null},\"safety_identifier\":null,\"service_tier\":\"default\",\"store\":false,\"temperature\":1,\"text\":{\"format\":{\"type\":\"text\"},\"verbosity\":\"medium\"},\"tool_choice\":\"auto\",\"tool_usage\":{\"image_gen\":{\"input_tokens\":0,\"input_tokens_details\":{\"image_tokens\":0,\"text_tokens\":0},\"output_tokens\":0,\"output_tokens_details\":{\"image_tokens\":0,\"text_tokens\":0},\"total_tokens\":0},\"web_search\":{\"num_requests\":0}},\"tools\":[{\"type\":\"function\",\"description\":\"Read a file or list a directory. For files, output is truncated to 2000 lines or 50KB — use offset/limit for large files. For directories, returns the list of entries.\",\"name\":\"read\",\"output_schema\":null,\"parameters\":{\"type\":\"object\",\"required\":[\"path\"],\"properties\":{\"path\":{\"type\":\"string\",\"description\":\"Path to the file to read\"},\"offset\":{\"type\":\"number\",\"description\":\"Line number to start from (1-indexed)\"},\"limit\":{\"type\":\"number\",\"description\":\"Maximum number of lines to read\"}}},\"strict\":false},{\"type\":\"function\",\"description\":\"Write content to a file. Creates the file and parent directories if they do not exist.\",\"name\":\"write\",\"output_schema\":null,\"parameters\":{\"type\":\"object\",\"required\":[\"path\",\"content\"],\"properties\":{\"path\":{\"type\":\"string\",\"description\":\"Path to the file to write\"},\"content\":{\"type\":\"string\",\"description\":\"Content to write to the file\"}}},\"strict\":false},{\"type\":\"function\",\"description\":\"Edit a file using exact text replacement. The oldText must match a unique region of the file. Use replaceAll to replace all occurrences.\",\"name\":\"edit\",\"output_schema\":null,\"parameters\":{\"type\":\"object\",\"required\":[\"path\",\"oldText\",\"newText\"],\"properties\":{\"path\":{\"type\":\"string\",\"description\":\"Path to the file to edit\"},\"oldText\":{\"type\":\"string\",\"description\":\"Exact text to find (must be unique)\"},\"newText\":{\"type\":\"string\",\"description\":\"Replacement text\"},\"replaceAll\":{\"type\":\"boolean\",\"description\":\"Replace all occurrences\"}}},\"strict\":false},{\"type\":\"function\",\"description\":\"Execute a bash command. Returns stdout and stderr. Output is truncated to the last 2000 lines or 50KB.\",\"name\":\"bash\",\"output_schema\":null,\"parameters\":{\"type\":\"object\",\"required\":[\"command\"],\"properties\":{\"command\":{\"type\":\"string\",\"description\":\"Bash command to execute\"},\"timeout\":{\"type\":\"number\",\"description\":\"Timeout in seconds\"}}},\"strict\":false},{\"type\":\"function\",\"description\":\"Search file contents for a regex pattern. Returns matching lines with file paths and line numbers.\",\"name\":\"grep\",\"output_schema\":null,\"parameters\":{\"type\":\"object\",\"required\":[\"pattern\"],\"properties\":{\"pattern\":{\"type\":\"string\",\"description\":\"Search pattern (regex)\"},\"path\":{\"type\":\"string\",\"description\":\"Directory or file to search (default: .)\"},\"include\":{\"type\":\"string\",\"description\":\"Glob filter, e.g. \\\"*.ts\\\"\"}}},\"strict\":false},{\"type\":\"function\",\"description\":\"Find files by filename pattern using shell find -name semantics. Returns matching file paths.\",\"name\":\"glob\",\"output_schema\":null,\"parameters\":{\"type\":\"object\",\"required\":[\"pattern\"],\"properties\":{\"pattern\":{\"type\":\"string\",\"description\":\"Filename pattern, e.g. \\\"*.ts\\\"\"},\"path\":{\"type\":\"string\",\"description\":\"Directory to search in (default: .)\"}}},\"strict\":false},{\"type\":\"function\",\"description\":\"Delegate a focused task to a detached child agent with its own context. Use this for independent research, file exploration, or parallel work. The task returns only its final answer to this conversation. No subagents are currently defined.\",\"name\":\"task\",\"output_schema\":null,\"parameters\":{\"type\":\"object\",\"required\":[\"prompt\"],\"properties\":{\"description\":{\"type\":\"string\",\"description\":\"Short human-readable label for the delegated work\"},\"prompt\":{\"type\":\"string\",\"description\":\"Focused instructions for the child agent\"},\"agent\":{\"type\":\"string\",\"description\":\"Declared subagent to use for the child agent\"},\"cwd\":{\"type\":\"string\",\"description\":\"Working directory for the child agent. AGENTS.md and skills are discovered from here.\"}}},\"strict\":false}],\"top_logprobs\":0,\"top_p\":0.98,\"truncation\":\"disabled\",\"usage\":{\"input_tokens\":1370,\"input_tokens_details\":{\"cache_write_tokens\":0,\"cached_tokens\":0},\"output_tokens\":68,\"output_tokens_details\":{\"reasoning_tokens\":0},\"total_tokens\":1438},\"user\":null,\"metadata\":{}},\"sequence_number\":60}" - ], - "kind": "sse" - }, - "headers": { - "access-control-expose-headers": "X-Request-ID, CF-Ray, CF-Ray", - "alt-svc": "h3=\":443\"; ma=86400", - "cf-cache-status": "DYNAMIC", - "cf-ray": "a25432f26f707bb6-IAD", - "connection": "keep-alive", - "content-type": "text/event-stream; charset=utf-8", - "date": "Mon, 03 Aug 2026 09:12:09 GMT", - "openai-organization": "braintrust-data", - "openai-processing-ms": "283", - "openai-project": "proj_vsCSXafhhByzWOThMrJcZiw9", - "openai-version": "2020-10-01", - "server": "cloudflare", - "set-cookie": "[REDACTED]", - "strict-transport-security": "max-age=31536000; includeSubDomains; preload", - "transfer-encoding": "chunked", - "x-content-type-options": "nosniff", - "x-ratelimit-limit-requests": "30000", - "x-ratelimit-limit-tokens": "180000000", - "x-ratelimit-remaining-requests": "29999", - "x-ratelimit-remaining-tokens": "179998176", - "x-ratelimit-reset-requests": "2ms", - "x-ratelimit-reset-tokens": "0s", - "x-request-id": "req_f5479925a7844533a4ae27ed832d027b" - }, - "status": 200, - "statusText": "OK" - } - }, - { - "callIndex": 10, - "id": "ab34eb26595033a1", - "matchKey": "POST api.openai.com/v1/responses", - "recordedAt": "2026-08-03T09:12:12.905Z", - "request": { - "body": { - "kind": "json", - "value": { - "input": [ - { - "content": "You are running in headless mode with no human operator. Work autonomously — never ask questions, never wait for user input. Make your best judgment and proceed independently.\n\nYou are a deterministic Flue instrumentation test agent. Follow user instructions exactly. When asked for a marker, output only that marker and no extra text. When running a local skill file, read it yourself and do not delegate it to a task.\n\n## Available Skills\n\nThe following skills provide specialized instructions for specific tasks. When a task matches a skill description, activate that skill before proceeding so its full instructions are loaded. Skill instructions and supporting resources stay lazy until activation or explicit file reads.\n\n- **e2e-flue-skill** — Deterministic skill used by the Braintrust Flue instrumentation e2e test.\n\nDate: Mon, Aug 3, 2026\nWorking directory: .\n\nDirectory structure:\n.agents\n.flue\n.flue-build\n__cassettes__\n__snapshots__\nassertions.ts\ncassette-filter.mjs\nconstants.mjs\ndist\nnode_modules\npackage.json\npnpm-lock.yaml\nscenario.cli.mjs\nscenario.impl.mjs\nscenario.mjs\nscenario.test.ts\nscenario.ts\nscenario.v2.ts\nv2-assertions.ts\nversions", - "role": "developer" - }, - { - "content": [ - { - "text": "Run the skill named \"e2e-flue-skill\" with arguments {\"marker\":\"SKILL_DONE\"}. Output only the marker and no extra text when finished.", - "type": "input_text" - } - ], - "role": "user" - }, - { - "arguments": "{\"description\":\"Run e2e-flue-skill with marker SKILL_DONE\",\"prompt\":\"Run the skill named \\\"e2e-flue-skill\\\" with arguments {\\\"marker\\\":\\\"SKILL_DONE\\\"}. Ensure completion and report the final output marker only.\",\"agent\":\"e2e-flue-skill\",\"cwd\":\".\"}", - "call_id": "call_w6pyYZgnhlSOAXul75DAG1Cj", - "id": "fc_054a16a2f2ea3217016a705b64fe5481a390185c9576df12b6", - "name": "task", - "type": "function_call" - }, - { - "call_id": "call_w6pyYZgnhlSOAXul75DAG1Cj", - "output": "[flue] Subagent \"e2e-flue-skill\" is not declared. Available: (none).", - "type": "function_call_output" - }, - { - "arguments": "{\"command\":\"find . -maxdepth 4 -name AGENTS.md -print\",\"timeout\":100000}", - "call_id": "call_6zEBnJRmXFIkGFwNjWLZP4uC", - "id": "fc_054a16a2f2ea3217016a705b66460481a3a5de06df603e35ee", - "name": "bash", - "type": "function_call" - }, - { - "call_id": "call_6zEBnJRmXFIkGFwNjWLZP4uC", - "output": "total 4\ndrwxr-xr-x 3 vercel-sandbox vercel-sandbox 20 Aug 3 09:10 .\ndrwxr-xr-x 10 vercel-sandbox vercel-sandbox 4096 Aug 3 09:11 ..\ndrwxr-xr-x 3 vercel-sandbox vercel-sandbox 28 Aug 3 09:10 skills", - "type": "function_call_output" - }, - { - "arguments": "{\"command\":\"find . -maxdepth 4 -type f -name AGENTS.md -print -exec sed -n '1,200p' {} \\\\;\",\"timeout\":100000}", - "call_id": "call_jQUCie0ZgaXUrZCDcezp0msl", - "id": "fc_054a16a2f2ea3217016a705b67575881a3aacde02b9053a321", - "name": "bash", - "type": "function_call" - }, - { - "call_id": "call_jQUCie0ZgaXUrZCDcezp0msl", - "output": "(no output)", - "type": "function_call_output" - }, - { - "arguments": "{\"command\":\"find . -maxdepth 6 -type f -name 'AGENTS.md' -print\",\"timeout\":100000}", - "call_id": "call_NgMSUb6wIR0GAf9U8LXERtBQ", - "id": "fc_054a16a2f2ea3217016a705b68c39881a3b1e518599236e41a", - "name": "bash", - "type": "function_call" - }, - { - "call_id": "call_NgMSUb6wIR0GAf9U8LXERtBQ", - "output": "(no output)", - "type": "function_call_output" - }, - { - "arguments": "{\"command\":\"find . -maxdepth 5 -type f -name '*flue*skill*' -o -name '*skill*' | head\",\"timeout\":100000}", - "call_id": "call_BXKiQ5KG8c42XPPp94zgcY3H", - "id": "fc_054a16a2f2ea3217016a705b69edd081a38c31583ec56eca5f", - "name": "bash", - "type": "function_call" - }, - { - "call_id": "call_BXKiQ5KG8c42XPPp94zgcY3H", - "output": "./.agents/skills\n./.agents/skills/e2e-flue-skill", - "type": "function_call_output" - } - ], - "model": "gpt-5.4-nano", - "prompt_cache_key": "[REDACTED]", - "reasoning": { - "effort": "none" - }, - "store": false, - "stream": true, - "tools": [ - { - "description": "Read a file or list a directory. For files, output is truncated to 2000 lines or 50KB — use offset/limit for large files. For directories, returns the list of entries.", - "name": "read", - "parameters": { - "properties": { - "limit": { - "description": "Maximum number of lines to read", - "type": "number" - }, - "offset": { - "description": "Line number to start from (1-indexed)", - "type": "number" - }, - "path": { - "description": "Path to the file to read", - "type": "string" - } - }, - "required": ["path"], - "type": "object" - }, - "strict": false, - "type": "function" - }, - { - "description": "Write content to a file. Creates the file and parent directories if they do not exist.", - "name": "write", - "parameters": { - "properties": { - "content": { - "description": "Content to write to the file", - "type": "string" - }, - "path": { - "description": "Path to the file to write", - "type": "string" - } - }, - "required": ["path", "content"], - "type": "object" - }, - "strict": false, - "type": "function" - }, - { - "description": "Edit a file using exact text replacement. The oldText must match a unique region of the file. Use replaceAll to replace all occurrences.", - "name": "edit", - "parameters": { - "properties": { - "newText": { - "description": "Replacement text", - "type": "string" - }, - "oldText": { - "description": "Exact text to find (must be unique)", - "type": "string" - }, - "path": { - "description": "Path to the file to edit", - "type": "string" - }, - "replaceAll": { - "description": "Replace all occurrences", - "type": "boolean" - } - }, - "required": ["path", "oldText", "newText"], - "type": "object" - }, - "strict": false, - "type": "function" - }, - { - "description": "Execute a bash command. Returns stdout and stderr. Output is truncated to the last 2000 lines or 50KB.", - "name": "bash", - "parameters": { - "properties": { - "command": { - "description": "Bash command to execute", - "type": "string" - }, - "timeout": { - "description": "Timeout in seconds", - "type": "number" - } - }, - "required": ["command"], - "type": "object" - }, - "strict": false, - "type": "function" - }, - { - "description": "Search file contents for a regex pattern. Returns matching lines with file paths and line numbers.", - "name": "grep", - "parameters": { - "properties": { - "include": { - "description": "Glob filter, e.g. \"*.ts\"", - "type": "string" - }, - "path": { - "description": "Directory or file to search (default: .)", - "type": "string" - }, - "pattern": { - "description": "Search pattern (regex)", - "type": "string" - } - }, - "required": ["pattern"], - "type": "object" - }, - "strict": false, - "type": "function" - }, - { - "description": "Find files by filename pattern using shell find -name semantics. Returns matching file paths.", - "name": "glob", - "parameters": { - "properties": { - "path": { - "description": "Directory to search in (default: .)", - "type": "string" - }, - "pattern": { - "description": "Filename pattern, e.g. \"*.ts\"", - "type": "string" - } - }, - "required": ["pattern"], - "type": "object" - }, - "strict": false, - "type": "function" - }, - { - "description": "Delegate a focused task to a detached child agent with its own context. Use this for independent research, file exploration, or parallel work. The task returns only its final answer to this conversation. No subagents are currently defined.", - "name": "task", - "parameters": { - "properties": { - "agent": { - "description": "Declared subagent to use for the child agent", - "type": "string" - }, - "cwd": { - "description": "Working directory for the child agent. AGENTS.md and skills are discovered from here.", - "type": "string" - }, - "description": { - "description": "Short human-readable label for the delegated work", - "type": "string" - }, - "prompt": { - "description": "Focused instructions for the child agent", - "type": "string" - } - }, - "required": ["prompt"], - "type": "object" - }, - "strict": false, - "type": "function" - } - ] - } - }, - "headers": {}, - "method": "POST", - "url": "https://api.openai.com/v1/responses" - }, - "response": { - "body": { - "chunks": [ - "event: response.created\ndata: {\"type\":\"response.created\",\"response\":{\"id\":\"resp_054a16a2f2ea3217016a705b6af7d081a396a76851300752a1\",\"object\":\"response\",\"created_at\":1785748331,\"status\":\"in_progress\",\"background\":false,\"completed_at\":null,\"error\":null,\"frequency_penalty\":0,\"incomplete_details\":null,\"instructions\":null,\"max_output_tokens\":null,\"max_tool_calls\":null,\"model\":\"gpt-5.4-nano-2026-03-17\",\"moderation\":null,\"output\":[],\"parallel_tool_calls\":true,\"presence_penalty\":0,\"previous_response_id\":null,\"prompt_cache_key\":\"workflow:instrumentation:01KZ3E9TWTAEK4P4P3F63HKAKC::default::ta\",\"prompt_cache_retention\":\"24h\",\"reasoning\":{\"context\":\"current_turn\",\"effort\":\"none\",\"mode\":\"standard\",\"summary\":null},\"safety_identifier\":null,\"service_tier\":\"auto\",\"store\":false,\"temperature\":1,\"text\":{\"format\":{\"type\":\"text\"},\"verbosity\":\"medium\"},\"tool_choice\":\"auto\",\"tool_usage\":{\"image_gen\":{\"input_tokens\":0,\"input_tokens_details\":{\"image_tokens\":0,\"text_tokens\":0},\"output_tokens\":0,\"output_tokens_details\":{\"image_tokens\":0,\"text_tokens\":0},\"total_tokens\":0},\"web_search\":{\"num_requests\":0}},\"tools\":[{\"type\":\"function\",\"description\":\"Read a file or list a directory. For files, output is truncated to 2000 lines or 50KB — use offset/limit for large files. For directories, returns the list of entries.\",\"name\":\"read\",\"output_schema\":null,\"parameters\":{\"type\":\"object\",\"required\":[\"path\"],\"properties\":{\"path\":{\"type\":\"string\",\"description\":\"Path to the file to read\"},\"offset\":{\"type\":\"number\",\"description\":\"Line number to start from (1-indexed)\"},\"limit\":{\"type\":\"number\",\"description\":\"Maximum number of lines to read\"}}},\"strict\":false},{\"type\":\"function\",\"description\":\"Write content to a file. Creates the file and parent directories if they do not exist.\",\"name\":\"write\",\"output_schema\":null,\"parameters\":{\"type\":\"object\",\"required\":[\"path\",\"content\"],\"properties\":{\"path\":{\"type\":\"string\",\"description\":\"Path to the file to write\"},\"content\":{\"type\":\"string\",\"description\":\"Content to write to the file\"}}},\"strict\":false},{\"type\":\"function\",\"description\":\"Edit a file using exact text replacement. The oldText must match a unique region of the file. Use replaceAll to replace all occurrences.\",\"name\":\"edit\",\"output_schema\":null,\"parameters\":{\"type\":\"object\",\"required\":[\"path\",\"oldText\",\"newText\"],\"properties\":{\"path\":{\"type\":\"string\",\"description\":\"Path to the file to edit\"},\"oldText\":{\"type\":\"string\",\"description\":\"Exact text to find (must be unique)\"},\"newText\":{\"type\":\"string\",\"description\":\"Replacement text\"},\"replaceAll\":{\"type\":\"boolean\",\"description\":\"Replace all occurrences\"}}},\"strict\":false},{\"type\":\"function\",\"description\":\"Execute a bash command. Returns stdout and stderr. Output is truncated to the last 2000 lines or 50KB.\",\"name\":\"bash\",\"output_schema\":null,\"parameters\":{\"type\":\"object\",\"required\":[\"command\"],\"properties\":{\"command\":{\"type\":\"string\",\"description\":\"Bash command to execute\"},\"timeout\":{\"type\":\"number\",\"description\":\"Timeout in seconds\"}}},\"strict\":false},{\"type\":\"function\",\"description\":\"Search file contents for a regex pattern. Returns matching lines with file paths and line numbers.\",\"name\":\"grep\",\"output_schema\":null,\"parameters\":{\"type\":\"object\",\"required\":[\"pattern\"],\"properties\":{\"pattern\":{\"type\":\"string\",\"description\":\"Search pattern (regex)\"},\"path\":{\"type\":\"string\",\"description\":\"Directory or file to search (default: .)\"},\"include\":{\"type\":\"string\",\"description\":\"Glob filter, e.g. \\\"*.ts\\\"\"}}},\"strict\":false},{\"type\":\"function\",\"description\":\"Find files by filename pattern using shell find -name semantics. Returns matching file paths.\",\"name\":\"glob\",\"output_schema\":null,\"parameters\":{\"type\":\"object\",\"required\":[\"pattern\"],\"properties\":{\"pattern\":{\"type\":\"string\",\"description\":\"Filename pattern, e.g. \\\"*.ts\\\"\"},\"path\":{\"type\":\"string\",\"description\":\"Directory to search in (default: .)\"}}},\"strict\":false},{\"type\":\"function\",\"description\":\"Delegate a focused task to a detached child agent with its own context. Use this for independent research, file exploration, or parallel work. The task returns only its final answer to this conversation. No subagents are currently defined.\",\"name\":\"task\",\"output_schema\":null,\"parameters\":{\"type\":\"object\",\"required\":[\"prompt\"],\"properties\":{\"description\":{\"type\":\"string\",\"description\":\"Short human-readable label for the delegated work\"},\"prompt\":{\"type\":\"string\",\"description\":\"Focused instructions for the child agent\"},\"agent\":{\"type\":\"string\",\"description\":\"Declared subagent to use for the child agent\"},\"cwd\":{\"type\":\"string\",\"description\":\"Working directory for the child agent. AGENTS.md and skills are discovered from here.\"}}},\"strict\":false}],\"top_logprobs\":0,\"top_p\":0.98,\"truncation\":\"disabled\",\"usage\":null,\"user\":null,\"metadata\":{}},\"sequence_number\":0}", - "event: response.in_progress\ndata: {\"type\":\"response.in_progress\",\"response\":{\"id\":\"resp_054a16a2f2ea3217016a705b6af7d081a396a76851300752a1\",\"object\":\"response\",\"created_at\":1785748331,\"status\":\"in_progress\",\"background\":false,\"completed_at\":null,\"error\":null,\"frequency_penalty\":0,\"incomplete_details\":null,\"instructions\":null,\"max_output_tokens\":null,\"max_tool_calls\":null,\"model\":\"gpt-5.4-nano-2026-03-17\",\"moderation\":null,\"output\":[],\"parallel_tool_calls\":true,\"presence_penalty\":0,\"previous_response_id\":null,\"prompt_cache_key\":\"workflow:instrumentation:01KZ3E9TWTAEK4P4P3F63HKAKC::default::ta\",\"prompt_cache_retention\":\"24h\",\"reasoning\":{\"context\":\"current_turn\",\"effort\":\"none\",\"mode\":\"standard\",\"summary\":null},\"safety_identifier\":null,\"service_tier\":\"auto\",\"store\":false,\"temperature\":1,\"text\":{\"format\":{\"type\":\"text\"},\"verbosity\":\"medium\"},\"tool_choice\":\"auto\",\"tool_usage\":{\"image_gen\":{\"input_tokens\":0,\"input_tokens_details\":{\"image_tokens\":0,\"text_tokens\":0},\"output_tokens\":0,\"output_tokens_details\":{\"image_tokens\":0,\"text_tokens\":0},\"total_tokens\":0},\"web_search\":{\"num_requests\":0}},\"tools\":[{\"type\":\"function\",\"description\":\"Read a file or list a directory. For files, output is truncated to 2000 lines or 50KB — use offset/limit for large files. For directories, returns the list of entries.\",\"name\":\"read\",\"output_schema\":null,\"parameters\":{\"type\":\"object\",\"required\":[\"path\"],\"properties\":{\"path\":{\"type\":\"string\",\"description\":\"Path to the file to read\"},\"offset\":{\"type\":\"number\",\"description\":\"Line number to start from (1-indexed)\"},\"limit\":{\"type\":\"number\",\"description\":\"Maximum number of lines to read\"}}},\"strict\":false},{\"type\":\"function\",\"description\":\"Write content to a file. Creates the file and parent directories if they do not exist.\",\"name\":\"write\",\"output_schema\":null,\"parameters\":{\"type\":\"object\",\"required\":[\"path\",\"content\"],\"properties\":{\"path\":{\"type\":\"string\",\"description\":\"Path to the file to write\"},\"content\":{\"type\":\"string\",\"description\":\"Content to write to the file\"}}},\"strict\":false},{\"type\":\"function\",\"description\":\"Edit a file using exact text replacement. The oldText must match a unique region of the file. Use replaceAll to replace all occurrences.\",\"name\":\"edit\",\"output_schema\":null,\"parameters\":{\"type\":\"object\",\"required\":[\"path\",\"oldText\",\"newText\"],\"properties\":{\"path\":{\"type\":\"string\",\"description\":\"Path to the file to edit\"},\"oldText\":{\"type\":\"string\",\"description\":\"Exact text to find (must be unique)\"},\"newText\":{\"type\":\"string\",\"description\":\"Replacement text\"},\"replaceAll\":{\"type\":\"boolean\",\"description\":\"Replace all occurrences\"}}},\"strict\":false},{\"type\":\"function\",\"description\":\"Execute a bash command. Returns stdout and stderr. Output is truncated to the last 2000 lines or 50KB.\",\"name\":\"bash\",\"output_schema\":null,\"parameters\":{\"type\":\"object\",\"required\":[\"command\"],\"properties\":{\"command\":{\"type\":\"string\",\"description\":\"Bash command to execute\"},\"timeout\":{\"type\":\"number\",\"description\":\"Timeout in seconds\"}}},\"strict\":false},{\"type\":\"function\",\"description\":\"Search file contents for a regex pattern. Returns matching lines with file paths and line numbers.\",\"name\":\"grep\",\"output_schema\":null,\"parameters\":{\"type\":\"object\",\"required\":[\"pattern\"],\"properties\":{\"pattern\":{\"type\":\"string\",\"description\":\"Search pattern (regex)\"},\"path\":{\"type\":\"string\",\"description\":\"Directory or file to search (default: .)\"},\"include\":{\"type\":\"string\",\"description\":\"Glob filter, e.g. \\\"*.ts\\\"\"}}},\"strict\":false},{\"type\":\"function\",\"description\":\"Find files by filename pattern using shell find -name semantics. Returns matching file paths.\",\"name\":\"glob\",\"output_schema\":null,\"parameters\":{\"type\":\"object\",\"required\":[\"pattern\"],\"properties\":{\"pattern\":{\"type\":\"string\",\"description\":\"Filename pattern, e.g. \\\"*.ts\\\"\"},\"path\":{\"type\":\"string\",\"description\":\"Directory to search in (default: .)\"}}},\"strict\":false},{\"type\":\"function\",\"description\":\"Delegate a focused task to a detached child agent with its own context. Use this for independent research, file exploration, or parallel work. The task returns only its final answer to this conversation. No subagents are currently defined.\",\"name\":\"task\",\"output_schema\":null,\"parameters\":{\"type\":\"object\",\"required\":[\"prompt\"],\"properties\":{\"description\":{\"type\":\"string\",\"description\":\"Short human-readable label for the delegated work\"},\"prompt\":{\"type\":\"string\",\"description\":\"Focused instructions for the child agent\"},\"agent\":{\"type\":\"string\",\"description\":\"Declared subagent to use for the child agent\"},\"cwd\":{\"type\":\"string\",\"description\":\"Working directory for the child agent. AGENTS.md and skills are discovered from here.\"}}},\"strict\":false}],\"top_logprobs\":0,\"top_p\":0.98,\"truncation\":\"disabled\",\"usage\":null,\"user\":null,\"metadata\":{}},\"sequence_number\":1}", - "event: response.output_item.added\ndata: {\"type\":\"response.output_item.added\",\"item\":{\"id\":\"fc_054a16a2f2ea3217016a705b6b96cc81a3ac4761db24198a63\",\"type\":\"function_call\",\"status\":\"in_progress\",\"arguments\":\"\",\"call_id\":\"call_Zmmrs64LFscpAHsIUaIfEcAN\",\"name\":\"bash\"},\"output_index\":0,\"sequence_number\":2}", - "event: response.function_call_arguments.delta\ndata: {\"type\":\"response.function_call_arguments.delta\",\"delta\":\"{\\\"\",\"item_id\":\"fc_054a16a2f2ea3217016a705b6b96cc81a3ac4761db24198a63\",\"obfuscation\":\"K1hVgurfEtUc37\",\"output_index\":0,\"sequence_number\":3}", - "event: response.function_call_arguments.delta\ndata: {\"type\":\"response.function_call_arguments.delta\",\"delta\":\"command\",\"item_id\":\"fc_054a16a2f2ea3217016a705b6b96cc81a3ac4761db24198a63\",\"obfuscation\":\"luCO3JGp3\",\"output_index\":0,\"sequence_number\":4}", - "event: response.function_call_arguments.delta\ndata: {\"type\":\"response.function_call_arguments.delta\",\"delta\":\"\\\":\\\"\",\"item_id\":\"fc_054a16a2f2ea3217016a705b6b96cc81a3ac4761db24198a63\",\"obfuscation\":\"FmitdgZUwsXVs\",\"output_index\":0,\"sequence_number\":5}", - "event: response.function_call_arguments.delta\ndata: {\"type\":\"response.function_call_arguments.delta\",\"delta\":\"ls\",\"item_id\":\"fc_054a16a2f2ea3217016a705b6b96cc81a3ac4761db24198a63\",\"obfuscation\":\"VZpJ9At0woeytk\",\"output_index\":0,\"sequence_number\":6}", - "event: response.function_call_arguments.delta\ndata: {\"type\":\"response.function_call_arguments.delta\",\"delta\":\" -\",\"item_id\":\"fc_054a16a2f2ea3217016a705b6b96cc81a3ac4761db24198a63\",\"obfuscation\":\"0xukceLNBwYJiK\",\"output_index\":0,\"sequence_number\":7}", - "event: response.function_call_arguments.delta\ndata: {\"type\":\"response.function_call_arguments.delta\",\"delta\":\"la\",\"item_id\":\"fc_054a16a2f2ea3217016a705b6b96cc81a3ac4761db24198a63\",\"obfuscation\":\"1s2AnPNungEZlQ\",\"output_index\":0,\"sequence_number\":8}", - "event: response.function_call_arguments.delta\ndata: {\"type\":\"response.function_call_arguments.delta\",\"delta\":\" /\",\"item_id\":\"fc_054a16a2f2ea3217016a705b6b96cc81a3ac4761db24198a63\",\"obfuscation\":\"Bfcx9jEBxq6eit\",\"output_index\":0,\"sequence_number\":9}", - "event: response.function_call_arguments.delta\ndata: {\"type\":\"response.function_call_arguments.delta\",\"delta\":\"ver\",\"item_id\":\"fc_054a16a2f2ea3217016a705b6b96cc81a3ac4761db24198a63\",\"obfuscation\":\"1rFsvnpXQsSA2\",\"output_index\":0,\"sequence_number\":10}", - "event: response.function_call_arguments.delta\ndata: {\"type\":\"response.function_call_arguments.delta\",\"delta\":\"cel\",\"item_id\":\"fc_054a16a2f2ea3217016a705b6b96cc81a3ac4761db24198a63\",\"obfuscation\":\"WHxut9fzLrXkW\",\"output_index\":0,\"sequence_number\":11}", - "event: response.function_call_arguments.delta\ndata: {\"type\":\"response.function_call_arguments.delta\",\"delta\":\"/s\",\"item_id\":\"fc_054a16a2f2ea3217016a705b6b96cc81a3ac4761db24198a63\",\"obfuscation\":\"BzqeyKc2WZeyRg\",\"output_index\":0,\"sequence_number\":12}", - "event: response.function_call_arguments.delta\ndata: {\"type\":\"response.function_call_arguments.delta\",\"delta\":\"andbox\",\"item_id\":\"fc_054a16a2f2ea3217016a705b6b96cc81a3ac4761db24198a63\",\"obfuscation\":\"ii7jPgkIJ8\",\"output_index\":0,\"sequence_number\":13}", - "event: response.function_call_arguments.delta\ndata: {\"type\":\"response.function_call_arguments.delta\",\"delta\":\"/\",\"item_id\":\"fc_054a16a2f2ea3217016a705b6b96cc81a3ac4761db24198a63\",\"obfuscation\":\"PkykxLdvlfrkHPL\",\"output_index\":0,\"sequence_number\":14}", - "event: response.function_call_arguments.delta\ndata: {\"type\":\"response.function_call_arguments.delta\",\"delta\":\"brain\",\"item_id\":\"fc_054a16a2f2ea3217016a705b6b96cc81a3ac4761db24198a63\",\"obfuscation\":\"12UfuwAycuU\",\"output_index\":0,\"sequence_number\":15}", - "event: response.function_call_arguments.delta\ndata: {\"type\":\"response.function_call_arguments.delta\",\"delta\":\"trust\",\"item_id\":\"fc_054a16a2f2ea3217016a705b6b96cc81a3ac4761db24198a63\",\"obfuscation\":\"3K3OjXe08nc\",\"output_index\":0,\"sequence_number\":16}", - "event: response.function_call_arguments.delta\ndata: {\"type\":\"response.function_call_arguments.delta\",\"delta\":\"data\",\"item_id\":\"fc_054a16a2f2ea3217016a705b6b96cc81a3ac4761db24198a63\",\"obfuscation\":\"PkaaeYRGj55a\",\"output_index\":0,\"sequence_number\":17}", - "event: response.function_call_arguments.delta\ndata: {\"type\":\"response.function_call_arguments.delta\",\"delta\":\"/\",\"item_id\":\"fc_054a16a2f2ea3217016a705b6b96cc81a3ac4761db24198a63\",\"obfuscation\":\"UHfCbDbKjO5lYqr\",\"output_index\":0,\"sequence_number\":18}", - "event: response.function_call_arguments.delta\ndata: {\"type\":\"response.function_call_arguments.delta\",\"delta\":\"brain\",\"item_id\":\"fc_054a16a2f2ea3217016a705b6b96cc81a3ac4761db24198a63\",\"obfuscation\":\"MOTWOSsU43Z\",\"output_index\":0,\"sequence_number\":19}", - "event: response.function_call_arguments.delta\ndata: {\"type\":\"response.function_call_arguments.delta\",\"delta\":\"trust\",\"item_id\":\"fc_054a16a2f2ea3217016a705b6b96cc81a3ac4761db24198a63\",\"obfuscation\":\"dmMQk0sQrGh\",\"output_index\":0,\"sequence_number\":20}", - "event: response.function_call_arguments.delta\ndata: {\"type\":\"response.function_call_arguments.delta\",\"delta\":\"-sdk\",\"item_id\":\"fc_054a16a2f2ea3217016a705b6b96cc81a3ac4761db24198a63\",\"obfuscation\":\"XtEiI6ADgEIV\",\"output_index\":0,\"sequence_number\":21}", - "event: response.function_call_arguments.delta\ndata: {\"type\":\"response.function_call_arguments.delta\",\"delta\":\"-j\",\"item_id\":\"fc_054a16a2f2ea3217016a705b6b96cc81a3ac4761db24198a63\",\"obfuscation\":\"5ovKtom8zqryPR\",\"output_index\":0,\"sequence_number\":22}", - "event: response.function_call_arguments.delta\ndata: {\"type\":\"response.function_call_arguments.delta\",\"delta\":\"avascript\",\"item_id\":\"fc_054a16a2f2ea3217016a705b6b96cc81a3ac4761db24198a63\",\"obfuscation\":\"7eUUx0L\",\"output_index\":0,\"sequence_number\":23}", - "event: response.function_call_arguments.delta\ndata: {\"type\":\"response.function_call_arguments.delta\",\"delta\":\"/e\",\"item_id\":\"fc_054a16a2f2ea3217016a705b6b96cc81a3ac4761db24198a63\",\"obfuscation\":\"9QbEfSAXVpmE07\",\"output_index\":0,\"sequence_number\":24}", - "event: response.function_call_arguments.delta\ndata: {\"type\":\"response.function_call_arguments.delta\",\"delta\":\"2\",\"item_id\":\"fc_054a16a2f2ea3217016a705b6b96cc81a3ac4761db24198a63\",\"obfuscation\":\"BeGJDWOdDgtyZwk\",\"output_index\":0,\"sequence_number\":25}", - "event: response.function_call_arguments.delta\ndata: {\"type\":\"response.function_call_arguments.delta\",\"delta\":\"e\",\"item_id\":\"fc_054a16a2f2ea3217016a705b6b96cc81a3ac4761db24198a63\",\"obfuscation\":\"Vio65vRQsUs4hgC\",\"output_index\":0,\"sequence_number\":26}", - "event: response.function_call_arguments.delta\ndata: {\"type\":\"response.function_call_arguments.delta\",\"delta\":\"/sc\",\"item_id\":\"fc_054a16a2f2ea3217016a705b6b96cc81a3ac4761db24198a63\",\"obfuscation\":\"6P7cD2KzvBQE3\",\"output_index\":0,\"sequence_number\":27}", - "event: response.function_call_arguments.delta\ndata: {\"type\":\"response.function_call_arguments.delta\",\"delta\":\"enarios\",\"item_id\":\"fc_054a16a2f2ea3217016a705b6b96cc81a3ac4761db24198a63\",\"obfuscation\":\"RJSR2IM5S\",\"output_index\":0,\"sequence_number\":28}", - "event: response.function_call_arguments.delta\ndata: {\"type\":\"response.function_call_arguments.delta\",\"delta\":\"/fl\",\"item_id\":\"fc_054a16a2f2ea3217016a705b6b96cc81a3ac4761db24198a63\",\"obfuscation\":\"uSAAuJhHqx2h6\",\"output_index\":0,\"sequence_number\":29}", - "event: response.function_call_arguments.delta\ndata: {\"type\":\"response.function_call_arguments.delta\",\"delta\":\"ue\",\"item_id\":\"fc_054a16a2f2ea3217016a705b6b96cc81a3ac4761db24198a63\",\"obfuscation\":\"LURBTZTvJSTf7s\",\"output_index\":0,\"sequence_number\":30}", - "event: response.function_call_arguments.delta\ndata: {\"type\":\"response.function_call_arguments.delta\",\"delta\":\"-in\",\"item_id\":\"fc_054a16a2f2ea3217016a705b6b96cc81a3ac4761db24198a63\",\"obfuscation\":\"b3ZtE4ekxYWXs\",\"output_index\":0,\"sequence_number\":31}", - "event: response.function_call_arguments.delta\ndata: {\"type\":\"response.function_call_arguments.delta\",\"delta\":\"strument\",\"item_id\":\"fc_054a16a2f2ea3217016a705b6b96cc81a3ac4761db24198a63\",\"obfuscation\":\"WUoTVOo4\",\"output_index\":0,\"sequence_number\":32}", - "event: response.function_call_arguments.delta\ndata: {\"type\":\"response.function_call_arguments.delta\",\"delta\":\"ation\",\"item_id\":\"fc_054a16a2f2ea3217016a705b6b96cc81a3ac4761db24198a63\",\"obfuscation\":\"n6TvbFOtdSH\",\"output_index\":0,\"sequence_number\":33}", - "event: response.function_call_arguments.delta\ndata: {\"type\":\"response.function_call_arguments.delta\",\"delta\":\"/.\",\"item_id\":\"fc_054a16a2f2ea3217016a705b6b96cc81a3ac4761db24198a63\",\"obfuscation\":\"1Lt96k7JA9dUeI\",\"output_index\":0,\"sequence_number\":34}", - "event: response.function_call_arguments.delta\ndata: {\"type\":\"response.function_call_arguments.delta\",\"delta\":\"agents\",\"item_id\":\"fc_054a16a2f2ea3217016a705b6b96cc81a3ac4761db24198a63\",\"obfuscation\":\"O7qjKmlEms\",\"output_index\":0,\"sequence_number\":35}", - "event: response.function_call_arguments.delta\ndata: {\"type\":\"response.function_call_arguments.delta\",\"delta\":\"/\",\"item_id\":\"fc_054a16a2f2ea3217016a705b6b96cc81a3ac4761db24198a63\",\"obfuscation\":\"I1E6t9owIg9P1WR\",\"output_index\":0,\"sequence_number\":36}", - "event: response.function_call_arguments.delta\ndata: {\"type\":\"response.function_call_arguments.delta\",\"delta\":\"skills\",\"item_id\":\"fc_054a16a2f2ea3217016a705b6b96cc81a3ac4761db24198a63\",\"obfuscation\":\"21ZpKkvrdb\",\"output_index\":0,\"sequence_number\":37}", - "event: response.function_call_arguments.delta\ndata: {\"type\":\"response.function_call_arguments.delta\",\"delta\":\"/e\",\"item_id\":\"fc_054a16a2f2ea3217016a705b6b96cc81a3ac4761db24198a63\",\"obfuscation\":\"zV3b4fO0R5USoJ\",\"output_index\":0,\"sequence_number\":38}", - "event: response.function_call_arguments.delta\ndata: {\"type\":\"response.function_call_arguments.delta\",\"delta\":\"2\",\"item_id\":\"fc_054a16a2f2ea3217016a705b6b96cc81a3ac4761db24198a63\",\"obfuscation\":\"wtfZeZvShndVNQM\",\"output_index\":0,\"sequence_number\":39}", - "event: response.function_call_arguments.delta\ndata: {\"type\":\"response.function_call_arguments.delta\",\"delta\":\"e\",\"item_id\":\"fc_054a16a2f2ea3217016a705b6b96cc81a3ac4761db24198a63\",\"obfuscation\":\"8DgrYaTc5tF9qQN\",\"output_index\":0,\"sequence_number\":40}", - "event: response.function_call_arguments.delta\ndata: {\"type\":\"response.function_call_arguments.delta\",\"delta\":\"-fl\",\"item_id\":\"fc_054a16a2f2ea3217016a705b6b96cc81a3ac4761db24198a63\",\"obfuscation\":\"WMhoTEOnE6Xro\",\"output_index\":0,\"sequence_number\":41}", - "event: response.function_call_arguments.delta\ndata: {\"type\":\"response.function_call_arguments.delta\",\"delta\":\"ue\",\"item_id\":\"fc_054a16a2f2ea3217016a705b6b96cc81a3ac4761db24198a63\",\"obfuscation\":\"XaFg3AblnqowIQ\",\"output_index\":0,\"sequence_number\":42}", - "event: response.function_call_arguments.delta\ndata: {\"type\":\"response.function_call_arguments.delta\",\"delta\":\"-s\",\"item_id\":\"fc_054a16a2f2ea3217016a705b6b96cc81a3ac4761db24198a63\",\"obfuscation\":\"FJ1AtHWIUTZkuK\",\"output_index\":0,\"sequence_number\":43}", - "event: response.function_call_arguments.delta\ndata: {\"type\":\"response.function_call_arguments.delta\",\"delta\":\"kill\",\"item_id\":\"fc_054a16a2f2ea3217016a705b6b96cc81a3ac4761db24198a63\",\"obfuscation\":\"K2KXxbd3uIZt\",\"output_index\":0,\"sequence_number\":44}", - "event: response.function_call_arguments.delta\ndata: {\"type\":\"response.function_call_arguments.delta\",\"delta\":\" &&\",\"item_id\":\"fc_054a16a2f2ea3217016a705b6b96cc81a3ac4761db24198a63\",\"obfuscation\":\"ek6YUPE8Cp9AE\",\"output_index\":0,\"sequence_number\":45}", - "event: response.function_call_arguments.delta\ndata: {\"type\":\"response.function_call_arguments.delta\",\"delta\":\" find\",\"item_id\":\"fc_054a16a2f2ea3217016a705b6b96cc81a3ac4761db24198a63\",\"obfuscation\":\"XM049MlPwBE\",\"output_index\":0,\"sequence_number\":46}", - "event: response.function_call_arguments.delta\ndata: {\"type\":\"response.function_call_arguments.delta\",\"delta\":\" /\",\"item_id\":\"fc_054a16a2f2ea3217016a705b6b96cc81a3ac4761db24198a63\",\"obfuscation\":\"SCFb56qc5ogbCE\",\"output_index\":0,\"sequence_number\":47}", - "event: response.function_call_arguments.delta\ndata: {\"type\":\"response.function_call_arguments.delta\",\"delta\":\"ver\",\"item_id\":\"fc_054a16a2f2ea3217016a705b6b96cc81a3ac4761db24198a63\",\"obfuscation\":\"nB5JRT4lhiSpZ\",\"output_index\":0,\"sequence_number\":48}", - "event: response.function_call_arguments.delta\ndata: {\"type\":\"response.function_call_arguments.delta\",\"delta\":\"cel\",\"item_id\":\"fc_054a16a2f2ea3217016a705b6b96cc81a3ac4761db24198a63\",\"obfuscation\":\"DozjlarmrsGSB\",\"output_index\":0,\"sequence_number\":49}", - "event: response.function_call_arguments.delta\ndata: {\"type\":\"response.function_call_arguments.delta\",\"delta\":\"/s\",\"item_id\":\"fc_054a16a2f2ea3217016a705b6b96cc81a3ac4761db24198a63\",\"obfuscation\":\"CvaFfLoJcDammS\",\"output_index\":0,\"sequence_number\":50}", - "event: response.function_call_arguments.delta\ndata: {\"type\":\"response.function_call_arguments.delta\",\"delta\":\"andbox\",\"item_id\":\"fc_054a16a2f2ea3217016a705b6b96cc81a3ac4761db24198a63\",\"obfuscation\":\"6yYxC29jBg\",\"output_index\":0,\"sequence_number\":51}", - "event: response.function_call_arguments.delta\ndata: {\"type\":\"response.function_call_arguments.delta\",\"delta\":\"/\",\"item_id\":\"fc_054a16a2f2ea3217016a705b6b96cc81a3ac4761db24198a63\",\"obfuscation\":\"NpUUfvLkXCyoKMW\",\"output_index\":0,\"sequence_number\":52}", - "event: response.function_call_arguments.delta\ndata: {\"type\":\"response.function_call_arguments.delta\",\"delta\":\"brain\",\"item_id\":\"fc_054a16a2f2ea3217016a705b6b96cc81a3ac4761db24198a63\",\"obfuscation\":\"ym9bVn0oRVM\",\"output_index\":0,\"sequence_number\":53}", - "event: response.function_call_arguments.delta\ndata: {\"type\":\"response.function_call_arguments.delta\",\"delta\":\"trust\",\"item_id\":\"fc_054a16a2f2ea3217016a705b6b96cc81a3ac4761db24198a63\",\"obfuscation\":\"BPomQliEf6i\",\"output_index\":0,\"sequence_number\":54}", - "event: response.function_call_arguments.delta\ndata: {\"type\":\"response.function_call_arguments.delta\",\"delta\":\"data\",\"item_id\":\"fc_054a16a2f2ea3217016a705b6b96cc81a3ac4761db24198a63\",\"obfuscation\":\"0hcPCne66Cg8\",\"output_index\":0,\"sequence_number\":55}", - "event: response.function_call_arguments.delta\ndata: {\"type\":\"response.function_call_arguments.delta\",\"delta\":\"/\",\"item_id\":\"fc_054a16a2f2ea3217016a705b6b96cc81a3ac4761db24198a63\",\"obfuscation\":\"TfdWF7zbYNainuF\",\"output_index\":0,\"sequence_number\":56}", - "event: response.function_call_arguments.delta\ndata: {\"type\":\"response.function_call_arguments.delta\",\"delta\":\"brain\",\"item_id\":\"fc_054a16a2f2ea3217016a705b6b96cc81a3ac4761db24198a63\",\"obfuscation\":\"eYEUL1yBJZh\",\"output_index\":0,\"sequence_number\":57}", - "event: response.function_call_arguments.delta\ndata: {\"type\":\"response.function_call_arguments.delta\",\"delta\":\"trust\",\"item_id\":\"fc_054a16a2f2ea3217016a705b6b96cc81a3ac4761db24198a63\",\"obfuscation\":\"EfjIxAAX6o0\",\"output_index\":0,\"sequence_number\":58}", - "event: response.function_call_arguments.delta\ndata: {\"type\":\"response.function_call_arguments.delta\",\"delta\":\"-sdk\",\"item_id\":\"fc_054a16a2f2ea3217016a705b6b96cc81a3ac4761db24198a63\",\"obfuscation\":\"8hFJ6Wya2ZL8\",\"output_index\":0,\"sequence_number\":59}", - "event: response.function_call_arguments.delta\ndata: {\"type\":\"response.function_call_arguments.delta\",\"delta\":\"-j\",\"item_id\":\"fc_054a16a2f2ea3217016a705b6b96cc81a3ac4761db24198a63\",\"obfuscation\":\"BUmLaB9uO7p4JA\",\"output_index\":0,\"sequence_number\":60}", - "event: response.function_call_arguments.delta\ndata: {\"type\":\"response.function_call_arguments.delta\",\"delta\":\"avascript\",\"item_id\":\"fc_054a16a2f2ea3217016a705b6b96cc81a3ac4761db24198a63\",\"obfuscation\":\"TZCxxmh\",\"output_index\":0,\"sequence_number\":61}", - "event: response.function_call_arguments.delta\ndata: {\"type\":\"response.function_call_arguments.delta\",\"delta\":\"/e\",\"item_id\":\"fc_054a16a2f2ea3217016a705b6b96cc81a3ac4761db24198a63\",\"obfuscation\":\"Wkjm95wXRvIPMz\",\"output_index\":0,\"sequence_number\":62}", - "event: response.function_call_arguments.delta\ndata: {\"type\":\"response.function_call_arguments.delta\",\"delta\":\"2\",\"item_id\":\"fc_054a16a2f2ea3217016a705b6b96cc81a3ac4761db24198a63\",\"obfuscation\":\"IWO2TynZ5pN8CwA\",\"output_index\":0,\"sequence_number\":63}", - "event: response.function_call_arguments.delta\ndata: {\"type\":\"response.function_call_arguments.delta\",\"delta\":\"e\",\"item_id\":\"fc_054a16a2f2ea3217016a705b6b96cc81a3ac4761db24198a63\",\"obfuscation\":\"AYKtiZZuh0gJgYj\",\"output_index\":0,\"sequence_number\":64}", - "event: response.function_call_arguments.delta\ndata: {\"type\":\"response.function_call_arguments.delta\",\"delta\":\"/sc\",\"item_id\":\"fc_054a16a2f2ea3217016a705b6b96cc81a3ac4761db24198a63\",\"obfuscation\":\"rcDuq1t53ub50\",\"output_index\":0,\"sequence_number\":65}", - "event: response.function_call_arguments.delta\ndata: {\"type\":\"response.function_call_arguments.delta\",\"delta\":\"enarios\",\"item_id\":\"fc_054a16a2f2ea3217016a705b6b96cc81a3ac4761db24198a63\",\"obfuscation\":\"cPtht6oAy\",\"output_index\":0,\"sequence_number\":66}", - "event: response.function_call_arguments.delta\ndata: {\"type\":\"response.function_call_arguments.delta\",\"delta\":\"/fl\",\"item_id\":\"fc_054a16a2f2ea3217016a705b6b96cc81a3ac4761db24198a63\",\"obfuscation\":\"tQudFftzB650V\",\"output_index\":0,\"sequence_number\":67}", - "event: response.function_call_arguments.delta\ndata: {\"type\":\"response.function_call_arguments.delta\",\"delta\":\"ue\",\"item_id\":\"fc_054a16a2f2ea3217016a705b6b96cc81a3ac4761db24198a63\",\"obfuscation\":\"3iUp76L3SIYhqb\",\"output_index\":0,\"sequence_number\":68}", - "event: response.function_call_arguments.delta\ndata: {\"type\":\"response.function_call_arguments.delta\",\"delta\":\"-in\",\"item_id\":\"fc_054a16a2f2ea3217016a705b6b96cc81a3ac4761db24198a63\",\"obfuscation\":\"ZXAa0ULdLud1i\",\"output_index\":0,\"sequence_number\":69}", - "event: response.function_call_arguments.delta\ndata: {\"type\":\"response.function_call_arguments.delta\",\"delta\":\"strument\",\"item_id\":\"fc_054a16a2f2ea3217016a705b6b96cc81a3ac4761db24198a63\",\"obfuscation\":\"kzf0iJxr\",\"output_index\":0,\"sequence_number\":70}", - "event: response.function_call_arguments.delta\ndata: {\"type\":\"response.function_call_arguments.delta\",\"delta\":\"ation\",\"item_id\":\"fc_054a16a2f2ea3217016a705b6b96cc81a3ac4761db24198a63\",\"obfuscation\":\"rw7Suy9RUTq\",\"output_index\":0,\"sequence_number\":71}", - "event: response.function_call_arguments.delta\ndata: {\"type\":\"response.function_call_arguments.delta\",\"delta\":\"/.\",\"item_id\":\"fc_054a16a2f2ea3217016a705b6b96cc81a3ac4761db24198a63\",\"obfuscation\":\"uPuc49BoOe4rwv\",\"output_index\":0,\"sequence_number\":72}", - "event: response.function_call_arguments.delta\ndata: {\"type\":\"response.function_call_arguments.delta\",\"delta\":\"agents\",\"item_id\":\"fc_054a16a2f2ea3217016a705b6b96cc81a3ac4761db24198a63\",\"obfuscation\":\"xCcbSL9wCG\",\"output_index\":0,\"sequence_number\":73}", - "event: response.function_call_arguments.delta\ndata: {\"type\":\"response.function_call_arguments.delta\",\"delta\":\"/\",\"item_id\":\"fc_054a16a2f2ea3217016a705b6b96cc81a3ac4761db24198a63\",\"obfuscation\":\"0kPSap2qJXeYe5c\",\"output_index\":0,\"sequence_number\":74}", - "event: response.function_call_arguments.delta\ndata: {\"type\":\"response.function_call_arguments.delta\",\"delta\":\"skills\",\"item_id\":\"fc_054a16a2f2ea3217016a705b6b96cc81a3ac4761db24198a63\",\"obfuscation\":\"07SeHSKAgE\",\"output_index\":0,\"sequence_number\":75}", - "event: response.function_call_arguments.delta\ndata: {\"type\":\"response.function_call_arguments.delta\",\"delta\":\"/e\",\"item_id\":\"fc_054a16a2f2ea3217016a705b6b96cc81a3ac4761db24198a63\",\"obfuscation\":\"g2UQCFv27V3sqv\",\"output_index\":0,\"sequence_number\":76}", - "event: response.function_call_arguments.delta\ndata: {\"type\":\"response.function_call_arguments.delta\",\"delta\":\"2\",\"item_id\":\"fc_054a16a2f2ea3217016a705b6b96cc81a3ac4761db24198a63\",\"obfuscation\":\"YniEsIzqh6veIWP\",\"output_index\":0,\"sequence_number\":77}", - "event: response.function_call_arguments.delta\ndata: {\"type\":\"response.function_call_arguments.delta\",\"delta\":\"e\",\"item_id\":\"fc_054a16a2f2ea3217016a705b6b96cc81a3ac4761db24198a63\",\"obfuscation\":\"64iQG5gdZWPuPkQ\",\"output_index\":0,\"sequence_number\":78}", - "event: response.function_call_arguments.delta\ndata: {\"type\":\"response.function_call_arguments.delta\",\"delta\":\"-fl\",\"item_id\":\"fc_054a16a2f2ea3217016a705b6b96cc81a3ac4761db24198a63\",\"obfuscation\":\"Kz4PY7cfevVr9\",\"output_index\":0,\"sequence_number\":79}", - "event: response.function_call_arguments.delta\ndata: {\"type\":\"response.function_call_arguments.delta\",\"delta\":\"ue\",\"item_id\":\"fc_054a16a2f2ea3217016a705b6b96cc81a3ac4761db24198a63\",\"obfuscation\":\"dAurB55pjAoLFj\",\"output_index\":0,\"sequence_number\":80}", - "event: response.function_call_arguments.delta\ndata: {\"type\":\"response.function_call_arguments.delta\",\"delta\":\"-s\",\"item_id\":\"fc_054a16a2f2ea3217016a705b6b96cc81a3ac4761db24198a63\",\"obfuscation\":\"o5PWW9PAQB5Kvq\",\"output_index\":0,\"sequence_number\":81}", - "event: response.function_call_arguments.delta\ndata: {\"type\":\"response.function_call_arguments.delta\",\"delta\":\"kill\",\"item_id\":\"fc_054a16a2f2ea3217016a705b6b96cc81a3ac4761db24198a63\",\"obfuscation\":\"kEfcPgdMqlKI\",\"output_index\":0,\"sequence_number\":82}", - "event: response.function_call_arguments.delta\ndata: {\"type\":\"response.function_call_arguments.delta\",\"delta\":\" -\",\"item_id\":\"fc_054a16a2f2ea3217016a705b6b96cc81a3ac4761db24198a63\",\"obfuscation\":\"SJWGXR6ryAJ2DE\",\"output_index\":0,\"sequence_number\":83}", - "event: response.function_call_arguments.delta\ndata: {\"type\":\"response.function_call_arguments.delta\",\"delta\":\"max\",\"item_id\":\"fc_054a16a2f2ea3217016a705b6b96cc81a3ac4761db24198a63\",\"obfuscation\":\"4QU605ENItN8B\",\"output_index\":0,\"sequence_number\":84}", - "event: response.function_call_arguments.delta\ndata: {\"type\":\"response.function_call_arguments.delta\",\"delta\":\"depth\",\"item_id\":\"fc_054a16a2f2ea3217016a705b6b96cc81a3ac4761db24198a63\",\"obfuscation\":\"4XthyjdZM9w\",\"output_index\":0,\"sequence_number\":85}", - "event: response.function_call_arguments.delta\ndata: {\"type\":\"response.function_call_arguments.delta\",\"delta\":\" \",\"item_id\":\"fc_054a16a2f2ea3217016a705b6b96cc81a3ac4761db24198a63\",\"obfuscation\":\"WgGDumDYzUufFUI\",\"output_index\":0,\"sequence_number\":86}", - "event: response.function_call_arguments.delta\ndata: {\"type\":\"response.function_call_arguments.delta\",\"delta\":\"2\",\"item_id\":\"fc_054a16a2f2ea3217016a705b6b96cc81a3ac4761db24198a63\",\"obfuscation\":\"oQ8wQwbVorceatB\",\"output_index\":0,\"sequence_number\":87}", - "event: response.function_call_arguments.delta\ndata: {\"type\":\"response.function_call_arguments.delta\",\"delta\":\" -\",\"item_id\":\"fc_054a16a2f2ea3217016a705b6b96cc81a3ac4761db24198a63\",\"obfuscation\":\"sxrOiShGGRNzfx\",\"output_index\":0,\"sequence_number\":88}", - "event: response.function_call_arguments.delta\ndata: {\"type\":\"response.function_call_arguments.delta\",\"delta\":\"type\",\"item_id\":\"fc_054a16a2f2ea3217016a705b6b96cc81a3ac4761db24198a63\",\"obfuscation\":\"BwSNPMU6iGnd\",\"output_index\":0,\"sequence_number\":89}", - "event: response.function_call_arguments.delta\ndata: {\"type\":\"response.function_call_arguments.delta\",\"delta\":\" f\",\"item_id\":\"fc_054a16a2f2ea3217016a705b6b96cc81a3ac4761db24198a63\",\"obfuscation\":\"Rm35RryQU13YdR\",\"output_index\":0,\"sequence_number\":90}", - "event: response.function_call_arguments.delta\ndata: {\"type\":\"response.function_call_arguments.delta\",\"delta\":\" -\",\"item_id\":\"fc_054a16a2f2ea3217016a705b6b96cc81a3ac4761db24198a63\",\"obfuscation\":\"zJ5ddKoSwCzVU4\",\"output_index\":0,\"sequence_number\":91}", - "event: response.function_call_arguments.delta\ndata: {\"type\":\"response.function_call_arguments.delta\",\"delta\":\"print\",\"item_id\":\"fc_054a16a2f2ea3217016a705b6b96cc81a3ac4761db24198a63\",\"obfuscation\":\"h80D5JBABPH\",\"output_index\":0,\"sequence_number\":92}", - "event: response.function_call_arguments.delta\ndata: {\"type\":\"response.function_call_arguments.delta\",\"delta\":\"\\\",\\\"\",\"item_id\":\"fc_054a16a2f2ea3217016a705b6b96cc81a3ac4761db24198a63\",\"obfuscation\":\"rpdDODEBJorbc\",\"output_index\":0,\"sequence_number\":93}", - "event: response.function_call_arguments.delta\ndata: {\"type\":\"response.function_call_arguments.delta\",\"delta\":\"timeout\",\"item_id\":\"fc_054a16a2f2ea3217016a705b6b96cc81a3ac4761db24198a63\",\"obfuscation\":\"p4vKlv5m6\",\"output_index\":0,\"sequence_number\":94}", - "event: response.function_call_arguments.delta\ndata: {\"type\":\"response.function_call_arguments.delta\",\"delta\":\"\\\":\",\"item_id\":\"fc_054a16a2f2ea3217016a705b6b96cc81a3ac4761db24198a63\",\"obfuscation\":\"Zbhx8YnzpAicDP\",\"output_index\":0,\"sequence_number\":95}", - "event: response.function_call_arguments.delta\ndata: {\"type\":\"response.function_call_arguments.delta\",\"delta\":\"100\",\"item_id\":\"fc_054a16a2f2ea3217016a705b6b96cc81a3ac4761db24198a63\",\"obfuscation\":\"r4y6kurWe2zky\",\"output_index\":0,\"sequence_number\":96}", - "event: response.function_call_arguments.delta\ndata: {\"type\":\"response.function_call_arguments.delta\",\"delta\":\"000\",\"item_id\":\"fc_054a16a2f2ea3217016a705b6b96cc81a3ac4761db24198a63\",\"obfuscation\":\"x0tuUliZpjxkA\",\"output_index\":0,\"sequence_number\":97}", - "event: response.function_call_arguments.delta\ndata: {\"type\":\"response.function_call_arguments.delta\",\"delta\":\"}\",\"item_id\":\"fc_054a16a2f2ea3217016a705b6b96cc81a3ac4761db24198a63\",\"obfuscation\":\"VTLJl2mMhQOUOyQ\",\"output_index\":0,\"sequence_number\":98}", - "event: response.function_call_arguments.done\ndata: {\"type\":\"response.function_call_arguments.done\",\"arguments\":\"{\\\"command\\\":\\\"find ./.agents/skills/e2e-flue-skill -maxdepth 2 -type f -print\\\",\\\"timeout\\\":100000}\",\"item_id\":\"fc_054a16a2f2ea3217016a705b6b96cc81a3ac4761db24198a63\",\"output_index\":0,\"sequence_number\":99}", - "event: response.output_item.done\ndata: {\"type\":\"response.output_item.done\",\"item\":{\"id\":\"fc_054a16a2f2ea3217016a705b6b96cc81a3ac4761db24198a63\",\"type\":\"function_call\",\"status\":\"completed\",\"arguments\":\"{\\\"command\\\":\\\"find ./.agents/skills/e2e-flue-skill -maxdepth 2 -type f -print\\\",\\\"timeout\\\":100000}\",\"call_id\":\"call_Zmmrs64LFscpAHsIUaIfEcAN\",\"name\":\"bash\"},\"output_index\":0,\"sequence_number\":100}", - "event: response.completed\ndata: {\"type\":\"response.completed\",\"response\":{\"id\":\"resp_054a16a2f2ea3217016a705b6af7d081a396a76851300752a1\",\"object\":\"response\",\"created_at\":1785748331,\"status\":\"completed\",\"background\":false,\"completed_at\":1785748332,\"error\":null,\"frequency_penalty\":0,\"incomplete_details\":null,\"instructions\":null,\"max_output_tokens\":null,\"max_tool_calls\":null,\"model\":\"gpt-5.4-nano-2026-03-17\",\"moderation\":null,\"output\":[{\"id\":\"fc_054a16a2f2ea3217016a705b6b96cc81a3ac4761db24198a63\",\"type\":\"function_call\",\"status\":\"completed\",\"arguments\":\"{\\\"command\\\":\\\"find ./.agents/skills/e2e-flue-skill -maxdepth 2 -type f -print\\\",\\\"timeout\\\":100000}\",\"call_id\":\"call_Zmmrs64LFscpAHsIUaIfEcAN\",\"name\":\"bash\"}],\"parallel_tool_calls\":true,\"presence_penalty\":0,\"previous_response_id\":null,\"prompt_cache_key\":\"workflow:instrumentation:01KZ3E9TWTAEK4P4P3F63HKAKC::default::ta\",\"prompt_cache_retention\":\"24h\",\"reasoning\":{\"context\":\"current_turn\",\"effort\":\"none\",\"mode\":\"standard\",\"summary\":null},\"safety_identifier\":null,\"service_tier\":\"default\",\"store\":false,\"temperature\":1,\"text\":{\"format\":{\"type\":\"text\"},\"verbosity\":\"medium\"},\"tool_choice\":\"auto\",\"tool_usage\":{\"image_gen\":{\"input_tokens\":0,\"input_tokens_details\":{\"image_tokens\":0,\"text_tokens\":0},\"output_tokens\":0,\"output_tokens_details\":{\"image_tokens\":0,\"text_tokens\":0},\"total_tokens\":0},\"web_search\":{\"num_requests\":0}},\"tools\":[{\"type\":\"function\",\"description\":\"Read a file or list a directory. For files, output is truncated to 2000 lines or 50KB — use offset/limit for large files. For directories, returns the list of entries.\",\"name\":\"read\",\"output_schema\":null,\"parameters\":{\"type\":\"object\",\"required\":[\"path\"],\"properties\":{\"path\":{\"type\":\"string\",\"description\":\"Path to the file to read\"},\"offset\":{\"type\":\"number\",\"description\":\"Line number to start from (1-indexed)\"},\"limit\":{\"type\":\"number\",\"description\":\"Maximum number of lines to read\"}}},\"strict\":false},{\"type\":\"function\",\"description\":\"Write content to a file. Creates the file and parent directories if they do not exist.\",\"name\":\"write\",\"output_schema\":null,\"parameters\":{\"type\":\"object\",\"required\":[\"path\",\"content\"],\"properties\":{\"path\":{\"type\":\"string\",\"description\":\"Path to the file to write\"},\"content\":{\"type\":\"string\",\"description\":\"Content to write to the file\"}}},\"strict\":false},{\"type\":\"function\",\"description\":\"Edit a file using exact text replacement. The oldText must match a unique region of the file. Use replaceAll to replace all occurrences.\",\"name\":\"edit\",\"output_schema\":null,\"parameters\":{\"type\":\"object\",\"required\":[\"path\",\"oldText\",\"newText\"],\"properties\":{\"path\":{\"type\":\"string\",\"description\":\"Path to the file to edit\"},\"oldText\":{\"type\":\"string\",\"description\":\"Exact text to find (must be unique)\"},\"newText\":{\"type\":\"string\",\"description\":\"Replacement text\"},\"replaceAll\":{\"type\":\"boolean\",\"description\":\"Replace all occurrences\"}}},\"strict\":false},{\"type\":\"function\",\"description\":\"Execute a bash command. Returns stdout and stderr. Output is truncated to the last 2000 lines or 50KB.\",\"name\":\"bash\",\"output_schema\":null,\"parameters\":{\"type\":\"object\",\"required\":[\"command\"],\"properties\":{\"command\":{\"type\":\"string\",\"description\":\"Bash command to execute\"},\"timeout\":{\"type\":\"number\",\"description\":\"Timeout in seconds\"}}},\"strict\":false},{\"type\":\"function\",\"description\":\"Search file contents for a regex pattern. Returns matching lines with file paths and line numbers.\",\"name\":\"grep\",\"output_schema\":null,\"parameters\":{\"type\":\"object\",\"required\":[\"pattern\"],\"properties\":{\"pattern\":{\"type\":\"string\",\"description\":\"Search pattern (regex)\"},\"path\":{\"type\":\"string\",\"description\":\"Directory or file to search (default: .)\"},\"include\":{\"type\":\"string\",\"description\":\"Glob filter, e.g. \\\"*.ts\\\"\"}}},\"strict\":false},{\"type\":\"function\",\"description\":\"Find files by filename pattern using shell find -name semantics. Returns matching file paths.\",\"name\":\"glob\",\"output_schema\":null,\"parameters\":{\"type\":\"object\",\"required\":[\"pattern\"],\"properties\":{\"pattern\":{\"type\":\"string\",\"description\":\"Filename pattern, e.g. \\\"*.ts\\\"\"},\"path\":{\"type\":\"string\",\"description\":\"Directory to search in (default: .)\"}}},\"strict\":false},{\"type\":\"function\",\"description\":\"Delegate a focused task to a detached child agent with its own context. Use this for independent research, file exploration, or parallel work. The task returns only its final answer to this conversation. No subagents are currently defined.\",\"name\":\"task\",\"output_schema\":null,\"parameters\":{\"type\":\"object\",\"required\":[\"prompt\"],\"properties\":{\"description\":{\"type\":\"string\",\"description\":\"Short human-readable label for the delegated work\"},\"prompt\":{\"type\":\"string\",\"description\":\"Focused instructions for the child agent\"},\"agent\":{\"type\":\"string\",\"description\":\"Declared subagent to use for the child agent\"},\"cwd\":{\"type\":\"string\",\"description\":\"Working directory for the child agent. AGENTS.md and skills are discovered from here.\"}}},\"strict\":false}],\"top_logprobs\":0,\"top_p\":0.98,\"truncation\":\"disabled\",\"usage\":{\"input_tokens\":1513,\"input_tokens_details\":{\"cache_write_tokens\":0,\"cached_tokens\":0},\"output_tokens\":109,\"output_tokens_details\":{\"reasoning_tokens\":0},\"total_tokens\":1622},\"user\":null,\"metadata\":{}},\"sequence_number\":101}" - ], - "kind": "sse" - }, - "headers": { - "access-control-expose-headers": "X-Request-ID, CF-Ray, CF-Ray", - "alt-svc": "h3=\":443\"; ma=86400", - "cf-cache-status": "DYNAMIC", - "cf-ray": "a25432f9bf6f7bb6-IAD", - "connection": "keep-alive", - "content-type": "text/event-stream; charset=utf-8", - "date": "Mon, 03 Aug 2026 09:12:11 GMT", - "openai-organization": "braintrust-data", - "openai-processing-ms": "688", - "openai-project": "proj_vsCSXafhhByzWOThMrJcZiw9", - "openai-version": "2020-10-01", - "server": "cloudflare", - "set-cookie": "[REDACTED]", - "strict-transport-security": "max-age=31536000; includeSubDomains; preload", - "transfer-encoding": "chunked", - "x-content-type-options": "nosniff", - "x-ratelimit-limit-requests": "30000", - "x-ratelimit-limit-tokens": "180000000", - "x-ratelimit-remaining-requests": "29999", - "x-ratelimit-remaining-tokens": "179998032", - "x-ratelimit-reset-requests": "2ms", - "x-ratelimit-reset-tokens": "0s", - "x-request-id": "req_2ceebd83455e4b27b79fc2d72fb5dd3c" - }, - "status": 200, - "statusText": "OK" - } - }, - { - "callIndex": 11, - "id": "250e64f864c72337", - "matchKey": "POST api.openai.com/v1/responses", - "recordedAt": "2026-08-03T09:12:14.154Z", - "request": { - "body": { - "kind": "json", - "value": { - "input": [ - { - "content": "You are running in headless mode with no human operator. Work autonomously — never ask questions, never wait for user input. Make your best judgment and proceed independently.\n\nYou are a deterministic Flue instrumentation test agent. Follow user instructions exactly. When asked for a marker, output only that marker and no extra text. When running a local skill file, read it yourself and do not delegate it to a task.\n\n## Available Skills\n\nThe following skills provide specialized instructions for specific tasks. When a task matches a skill description, activate that skill before proceeding so its full instructions are loaded. Skill instructions and supporting resources stay lazy until activation or explicit file reads.\n\n- **e2e-flue-skill** — Deterministic skill used by the Braintrust Flue instrumentation e2e test.\n\nDate: Mon, Aug 3, 2026\nWorking directory: .\n\nDirectory structure:\n.agents\n.flue\n.flue-build\n__cassettes__\n__snapshots__\nassertions.ts\ncassette-filter.mjs\nconstants.mjs\ndist\nnode_modules\npackage.json\npnpm-lock.yaml\nscenario.cli.mjs\nscenario.impl.mjs\nscenario.mjs\nscenario.test.ts\nscenario.ts\nscenario.v2.ts\nv2-assertions.ts\nversions", - "role": "developer" - }, - { - "content": [ - { - "text": "Run the skill named \"e2e-flue-skill\" with arguments {\"marker\":\"SKILL_DONE\"}. Output only the marker and no extra text when finished.", - "type": "input_text" - } - ], - "role": "user" - }, - { - "arguments": "{\"description\":\"Run e2e-flue-skill with marker SKILL_DONE\",\"prompt\":\"Run the skill named \\\"e2e-flue-skill\\\" with arguments {\\\"marker\\\":\\\"SKILL_DONE\\\"}. Ensure completion and report the final output marker only.\",\"agent\":\"e2e-flue-skill\",\"cwd\":\".\"}", - "call_id": "call_w6pyYZgnhlSOAXul75DAG1Cj", - "id": "fc_054a16a2f2ea3217016a705b64fe5481a390185c9576df12b6", - "name": "task", - "type": "function_call" - }, - { - "call_id": "call_w6pyYZgnhlSOAXul75DAG1Cj", - "output": "[flue] Subagent \"e2e-flue-skill\" is not declared. Available: (none).", - "type": "function_call_output" - }, - { - "arguments": "{\"command\":\"find . -maxdepth 4 -name AGENTS.md -print\",\"timeout\":100000}", - "call_id": "call_6zEBnJRmXFIkGFwNjWLZP4uC", - "id": "fc_054a16a2f2ea3217016a705b66460481a3a5de06df603e35ee", - "name": "bash", - "type": "function_call" - }, - { - "call_id": "call_6zEBnJRmXFIkGFwNjWLZP4uC", - "output": "total 4\ndrwxr-xr-x 3 vercel-sandbox vercel-sandbox 20 Aug 3 09:10 .\ndrwxr-xr-x 10 vercel-sandbox vercel-sandbox 4096 Aug 3 09:11 ..\ndrwxr-xr-x 3 vercel-sandbox vercel-sandbox 28 Aug 3 09:10 skills", - "type": "function_call_output" - }, - { - "arguments": "{\"command\":\"find . -maxdepth 4 -type f -name AGENTS.md -print -exec sed -n '1,200p' {} \\\\;\",\"timeout\":100000}", - "call_id": "call_jQUCie0ZgaXUrZCDcezp0msl", - "id": "fc_054a16a2f2ea3217016a705b67575881a3aacde02b9053a321", - "name": "bash", - "type": "function_call" - }, - { - "call_id": "call_jQUCie0ZgaXUrZCDcezp0msl", - "output": "(no output)", - "type": "function_call_output" - }, - { - "arguments": "{\"command\":\"find . -maxdepth 6 -type f -name 'AGENTS.md' -print\",\"timeout\":100000}", - "call_id": "call_NgMSUb6wIR0GAf9U8LXERtBQ", - "id": "fc_054a16a2f2ea3217016a705b68c39881a3b1e518599236e41a", - "name": "bash", - "type": "function_call" - }, - { - "call_id": "call_NgMSUb6wIR0GAf9U8LXERtBQ", - "output": "(no output)", - "type": "function_call_output" - }, - { - "arguments": "{\"command\":\"find . -maxdepth 5 -type f -name '*flue*skill*' -o -name '*skill*' | head\",\"timeout\":100000}", - "call_id": "call_BXKiQ5KG8c42XPPp94zgcY3H", - "id": "fc_054a16a2f2ea3217016a705b69edd081a38c31583ec56eca5f", - "name": "bash", - "type": "function_call" - }, - { - "call_id": "call_BXKiQ5KG8c42XPPp94zgcY3H", - "output": "./.agents/skills\n./.agents/skills/e2e-flue-skill", - "type": "function_call_output" - }, - { - "arguments": "{\"command\":\"find ./.agents/skills/e2e-flue-skill -maxdepth 2 -type f -print\",\"timeout\":100000}", - "call_id": "call_Zmmrs64LFscpAHsIUaIfEcAN", - "id": "fc_054a16a2f2ea3217016a705b6b96cc81a3ac4761db24198a63", - "name": "bash", - "type": "function_call" - }, - { - "call_id": "call_Zmmrs64LFscpAHsIUaIfEcAN", - "output": "total 4\ndrwxr-xr-x 2 vercel-sandbox vercel-sandbox 22 Aug 3 07:55 .\ndrwxr-xr-x 3 vercel-sandbox vercel-sandbox 28 Aug 3 07:55 ..\n-rw-r--r-- 1 vercel-sandbox vercel-sandbox 194 Aug 3 07:55 SKILL.md\n./.agents/skills/e2e-flue-skill/SKILL.md", - "type": "function_call_output" - } - ], - "model": "gpt-5.4-nano", - "prompt_cache_key": "[REDACTED]", - "reasoning": { - "effort": "none" - }, - "store": false, - "stream": true, - "tools": [ - { - "description": "Read a file or list a directory. For files, output is truncated to 2000 lines or 50KB — use offset/limit for large files. For directories, returns the list of entries.", - "name": "read", - "parameters": { - "properties": { - "limit": { - "description": "Maximum number of lines to read", - "type": "number" - }, - "offset": { - "description": "Line number to start from (1-indexed)", - "type": "number" - }, - "path": { - "description": "Path to the file to read", - "type": "string" - } - }, - "required": ["path"], - "type": "object" - }, - "strict": false, - "type": "function" - }, - { - "description": "Write content to a file. Creates the file and parent directories if they do not exist.", - "name": "write", - "parameters": { - "properties": { - "content": { - "description": "Content to write to the file", - "type": "string" - }, - "path": { - "description": "Path to the file to write", - "type": "string" - } - }, - "required": ["path", "content"], - "type": "object" - }, - "strict": false, - "type": "function" - }, - { - "description": "Edit a file using exact text replacement. The oldText must match a unique region of the file. Use replaceAll to replace all occurrences.", - "name": "edit", - "parameters": { - "properties": { - "newText": { - "description": "Replacement text", - "type": "string" - }, - "oldText": { - "description": "Exact text to find (must be unique)", - "type": "string" - }, - "path": { - "description": "Path to the file to edit", - "type": "string" - }, - "replaceAll": { - "description": "Replace all occurrences", - "type": "boolean" - } - }, - "required": ["path", "oldText", "newText"], - "type": "object" - }, - "strict": false, - "type": "function" - }, - { - "description": "Execute a bash command. Returns stdout and stderr. Output is truncated to the last 2000 lines or 50KB.", - "name": "bash", - "parameters": { - "properties": { - "command": { - "description": "Bash command to execute", - "type": "string" - }, - "timeout": { - "description": "Timeout in seconds", - "type": "number" - } - }, - "required": ["command"], - "type": "object" - }, - "strict": false, - "type": "function" - }, - { - "description": "Search file contents for a regex pattern. Returns matching lines with file paths and line numbers.", - "name": "grep", - "parameters": { - "properties": { - "include": { - "description": "Glob filter, e.g. \"*.ts\"", - "type": "string" - }, - "path": { - "description": "Directory or file to search (default: .)", - "type": "string" - }, - "pattern": { - "description": "Search pattern (regex)", - "type": "string" - } - }, - "required": ["pattern"], - "type": "object" - }, - "strict": false, - "type": "function" - }, - { - "description": "Find files by filename pattern using shell find -name semantics. Returns matching file paths.", - "name": "glob", - "parameters": { - "properties": { - "path": { - "description": "Directory to search in (default: .)", - "type": "string" - }, - "pattern": { - "description": "Filename pattern, e.g. \"*.ts\"", - "type": "string" - } - }, - "required": ["pattern"], - "type": "object" - }, - "strict": false, - "type": "function" - }, - { - "description": "Delegate a focused task to a detached child agent with its own context. Use this for independent research, file exploration, or parallel work. The task returns only its final answer to this conversation. No subagents are currently defined.", - "name": "task", - "parameters": { - "properties": { - "agent": { - "description": "Declared subagent to use for the child agent", - "type": "string" - }, - "cwd": { - "description": "Working directory for the child agent. AGENTS.md and skills are discovered from here.", - "type": "string" - }, - "description": { - "description": "Short human-readable label for the delegated work", - "type": "string" - }, - "prompt": { - "description": "Focused instructions for the child agent", - "type": "string" - } - }, - "required": ["prompt"], - "type": "object" - }, - "strict": false, - "type": "function" - } - ] - } - }, - "headers": {}, - "method": "POST", - "url": "https://api.openai.com/v1/responses" - }, - "response": { - "body": { - "chunks": [ - "event: response.created\ndata: {\"type\":\"response.created\",\"response\":{\"id\":\"resp_054a16a2f2ea3217016a705b6d008c81a3a332b5376350158b\",\"object\":\"response\",\"created_at\":1785748333,\"status\":\"in_progress\",\"background\":false,\"completed_at\":null,\"error\":null,\"frequency_penalty\":0,\"incomplete_details\":null,\"instructions\":null,\"max_output_tokens\":null,\"max_tool_calls\":null,\"model\":\"gpt-5.4-nano-2026-03-17\",\"moderation\":null,\"output\":[],\"parallel_tool_calls\":true,\"presence_penalty\":0,\"previous_response_id\":null,\"prompt_cache_key\":\"workflow:instrumentation:01KZ3E9TWTAEK4P4P3F63HKAKC::default::ta\",\"prompt_cache_retention\":\"24h\",\"reasoning\":{\"context\":\"current_turn\",\"effort\":\"none\",\"mode\":\"standard\",\"summary\":null},\"safety_identifier\":null,\"service_tier\":\"auto\",\"store\":false,\"temperature\":1,\"text\":{\"format\":{\"type\":\"text\"},\"verbosity\":\"medium\"},\"tool_choice\":\"auto\",\"tool_usage\":{\"image_gen\":{\"input_tokens\":0,\"input_tokens_details\":{\"image_tokens\":0,\"text_tokens\":0},\"output_tokens\":0,\"output_tokens_details\":{\"image_tokens\":0,\"text_tokens\":0},\"total_tokens\":0},\"web_search\":{\"num_requests\":0}},\"tools\":[{\"type\":\"function\",\"description\":\"Read a file or list a directory. For files, output is truncated to 2000 lines or 50KB — use offset/limit for large files. For directories, returns the list of entries.\",\"name\":\"read\",\"output_schema\":null,\"parameters\":{\"type\":\"object\",\"required\":[\"path\"],\"properties\":{\"path\":{\"type\":\"string\",\"description\":\"Path to the file to read\"},\"offset\":{\"type\":\"number\",\"description\":\"Line number to start from (1-indexed)\"},\"limit\":{\"type\":\"number\",\"description\":\"Maximum number of lines to read\"}}},\"strict\":false},{\"type\":\"function\",\"description\":\"Write content to a file. Creates the file and parent directories if they do not exist.\",\"name\":\"write\",\"output_schema\":null,\"parameters\":{\"type\":\"object\",\"required\":[\"path\",\"content\"],\"properties\":{\"path\":{\"type\":\"string\",\"description\":\"Path to the file to write\"},\"content\":{\"type\":\"string\",\"description\":\"Content to write to the file\"}}},\"strict\":false},{\"type\":\"function\",\"description\":\"Edit a file using exact text replacement. The oldText must match a unique region of the file. Use replaceAll to replace all occurrences.\",\"name\":\"edit\",\"output_schema\":null,\"parameters\":{\"type\":\"object\",\"required\":[\"path\",\"oldText\",\"newText\"],\"properties\":{\"path\":{\"type\":\"string\",\"description\":\"Path to the file to edit\"},\"oldText\":{\"type\":\"string\",\"description\":\"Exact text to find (must be unique)\"},\"newText\":{\"type\":\"string\",\"description\":\"Replacement text\"},\"replaceAll\":{\"type\":\"boolean\",\"description\":\"Replace all occurrences\"}}},\"strict\":false},{\"type\":\"function\",\"description\":\"Execute a bash command. Returns stdout and stderr. Output is truncated to the last 2000 lines or 50KB.\",\"name\":\"bash\",\"output_schema\":null,\"parameters\":{\"type\":\"object\",\"required\":[\"command\"],\"properties\":{\"command\":{\"type\":\"string\",\"description\":\"Bash command to execute\"},\"timeout\":{\"type\":\"number\",\"description\":\"Timeout in seconds\"}}},\"strict\":false},{\"type\":\"function\",\"description\":\"Search file contents for a regex pattern. Returns matching lines with file paths and line numbers.\",\"name\":\"grep\",\"output_schema\":null,\"parameters\":{\"type\":\"object\",\"required\":[\"pattern\"],\"properties\":{\"pattern\":{\"type\":\"string\",\"description\":\"Search pattern (regex)\"},\"path\":{\"type\":\"string\",\"description\":\"Directory or file to search (default: .)\"},\"include\":{\"type\":\"string\",\"description\":\"Glob filter, e.g. \\\"*.ts\\\"\"}}},\"strict\":false},{\"type\":\"function\",\"description\":\"Find files by filename pattern using shell find -name semantics. Returns matching file paths.\",\"name\":\"glob\",\"output_schema\":null,\"parameters\":{\"type\":\"object\",\"required\":[\"pattern\"],\"properties\":{\"pattern\":{\"type\":\"string\",\"description\":\"Filename pattern, e.g. \\\"*.ts\\\"\"},\"path\":{\"type\":\"string\",\"description\":\"Directory to search in (default: .)\"}}},\"strict\":false},{\"type\":\"function\",\"description\":\"Delegate a focused task to a detached child agent with its own context. Use this for independent research, file exploration, or parallel work. The task returns only its final answer to this conversation. No subagents are currently defined.\",\"name\":\"task\",\"output_schema\":null,\"parameters\":{\"type\":\"object\",\"required\":[\"prompt\"],\"properties\":{\"description\":{\"type\":\"string\",\"description\":\"Short human-readable label for the delegated work\"},\"prompt\":{\"type\":\"string\",\"description\":\"Focused instructions for the child agent\"},\"agent\":{\"type\":\"string\",\"description\":\"Declared subagent to use for the child agent\"},\"cwd\":{\"type\":\"string\",\"description\":\"Working directory for the child agent. AGENTS.md and skills are discovered from here.\"}}},\"strict\":false}],\"top_logprobs\":0,\"top_p\":0.98,\"truncation\":\"disabled\",\"usage\":null,\"user\":null,\"metadata\":{}},\"sequence_number\":0}", - "event: response.in_progress\ndata: {\"type\":\"response.in_progress\",\"response\":{\"id\":\"resp_054a16a2f2ea3217016a705b6d008c81a3a332b5376350158b\",\"object\":\"response\",\"created_at\":1785748333,\"status\":\"in_progress\",\"background\":false,\"completed_at\":null,\"error\":null,\"frequency_penalty\":0,\"incomplete_details\":null,\"instructions\":null,\"max_output_tokens\":null,\"max_tool_calls\":null,\"model\":\"gpt-5.4-nano-2026-03-17\",\"moderation\":null,\"output\":[],\"parallel_tool_calls\":true,\"presence_penalty\":0,\"previous_response_id\":null,\"prompt_cache_key\":\"workflow:instrumentation:01KZ3E9TWTAEK4P4P3F63HKAKC::default::ta\",\"prompt_cache_retention\":\"24h\",\"reasoning\":{\"context\":\"current_turn\",\"effort\":\"none\",\"mode\":\"standard\",\"summary\":null},\"safety_identifier\":null,\"service_tier\":\"auto\",\"store\":false,\"temperature\":1,\"text\":{\"format\":{\"type\":\"text\"},\"verbosity\":\"medium\"},\"tool_choice\":\"auto\",\"tool_usage\":{\"image_gen\":{\"input_tokens\":0,\"input_tokens_details\":{\"image_tokens\":0,\"text_tokens\":0},\"output_tokens\":0,\"output_tokens_details\":{\"image_tokens\":0,\"text_tokens\":0},\"total_tokens\":0},\"web_search\":{\"num_requests\":0}},\"tools\":[{\"type\":\"function\",\"description\":\"Read a file or list a directory. For files, output is truncated to 2000 lines or 50KB — use offset/limit for large files. For directories, returns the list of entries.\",\"name\":\"read\",\"output_schema\":null,\"parameters\":{\"type\":\"object\",\"required\":[\"path\"],\"properties\":{\"path\":{\"type\":\"string\",\"description\":\"Path to the file to read\"},\"offset\":{\"type\":\"number\",\"description\":\"Line number to start from (1-indexed)\"},\"limit\":{\"type\":\"number\",\"description\":\"Maximum number of lines to read\"}}},\"strict\":false},{\"type\":\"function\",\"description\":\"Write content to a file. Creates the file and parent directories if they do not exist.\",\"name\":\"write\",\"output_schema\":null,\"parameters\":{\"type\":\"object\",\"required\":[\"path\",\"content\"],\"properties\":{\"path\":{\"type\":\"string\",\"description\":\"Path to the file to write\"},\"content\":{\"type\":\"string\",\"description\":\"Content to write to the file\"}}},\"strict\":false},{\"type\":\"function\",\"description\":\"Edit a file using exact text replacement. The oldText must match a unique region of the file. Use replaceAll to replace all occurrences.\",\"name\":\"edit\",\"output_schema\":null,\"parameters\":{\"type\":\"object\",\"required\":[\"path\",\"oldText\",\"newText\"],\"properties\":{\"path\":{\"type\":\"string\",\"description\":\"Path to the file to edit\"},\"oldText\":{\"type\":\"string\",\"description\":\"Exact text to find (must be unique)\"},\"newText\":{\"type\":\"string\",\"description\":\"Replacement text\"},\"replaceAll\":{\"type\":\"boolean\",\"description\":\"Replace all occurrences\"}}},\"strict\":false},{\"type\":\"function\",\"description\":\"Execute a bash command. Returns stdout and stderr. Output is truncated to the last 2000 lines or 50KB.\",\"name\":\"bash\",\"output_schema\":null,\"parameters\":{\"type\":\"object\",\"required\":[\"command\"],\"properties\":{\"command\":{\"type\":\"string\",\"description\":\"Bash command to execute\"},\"timeout\":{\"type\":\"number\",\"description\":\"Timeout in seconds\"}}},\"strict\":false},{\"type\":\"function\",\"description\":\"Search file contents for a regex pattern. Returns matching lines with file paths and line numbers.\",\"name\":\"grep\",\"output_schema\":null,\"parameters\":{\"type\":\"object\",\"required\":[\"pattern\"],\"properties\":{\"pattern\":{\"type\":\"string\",\"description\":\"Search pattern (regex)\"},\"path\":{\"type\":\"string\",\"description\":\"Directory or file to search (default: .)\"},\"include\":{\"type\":\"string\",\"description\":\"Glob filter, e.g. \\\"*.ts\\\"\"}}},\"strict\":false},{\"type\":\"function\",\"description\":\"Find files by filename pattern using shell find -name semantics. Returns matching file paths.\",\"name\":\"glob\",\"output_schema\":null,\"parameters\":{\"type\":\"object\",\"required\":[\"pattern\"],\"properties\":{\"pattern\":{\"type\":\"string\",\"description\":\"Filename pattern, e.g. \\\"*.ts\\\"\"},\"path\":{\"type\":\"string\",\"description\":\"Directory to search in (default: .)\"}}},\"strict\":false},{\"type\":\"function\",\"description\":\"Delegate a focused task to a detached child agent with its own context. Use this for independent research, file exploration, or parallel work. The task returns only its final answer to this conversation. No subagents are currently defined.\",\"name\":\"task\",\"output_schema\":null,\"parameters\":{\"type\":\"object\",\"required\":[\"prompt\"],\"properties\":{\"description\":{\"type\":\"string\",\"description\":\"Short human-readable label for the delegated work\"},\"prompt\":{\"type\":\"string\",\"description\":\"Focused instructions for the child agent\"},\"agent\":{\"type\":\"string\",\"description\":\"Declared subagent to use for the child agent\"},\"cwd\":{\"type\":\"string\",\"description\":\"Working directory for the child agent. AGENTS.md and skills are discovered from here.\"}}},\"strict\":false}],\"top_logprobs\":0,\"top_p\":0.98,\"truncation\":\"disabled\",\"usage\":null,\"user\":null,\"metadata\":{}},\"sequence_number\":1}", - "event: response.output_item.added\ndata: {\"type\":\"response.output_item.added\",\"item\":{\"id\":\"fc_054a16a2f2ea3217016a705b6d71cc81a38ac06634a309a997\",\"type\":\"function_call\",\"status\":\"in_progress\",\"arguments\":\"\",\"call_id\":\"call_KXcpmIy4Cbfugk4SNT2Ge7sG\",\"name\":\"read\"},\"output_index\":0,\"sequence_number\":2}", - "event: response.function_call_arguments.delta\ndata: {\"type\":\"response.function_call_arguments.delta\",\"delta\":\"{\\\"\",\"item_id\":\"fc_054a16a2f2ea3217016a705b6d71cc81a38ac06634a309a997\",\"obfuscation\":\"AplT1bUEIbLxZO\",\"output_index\":0,\"sequence_number\":3}", - "event: response.function_call_arguments.delta\ndata: {\"type\":\"response.function_call_arguments.delta\",\"delta\":\"path\",\"item_id\":\"fc_054a16a2f2ea3217016a705b6d71cc81a38ac06634a309a997\",\"obfuscation\":\"m2vm6uJH1fpp\",\"output_index\":0,\"sequence_number\":4}", - "event: response.function_call_arguments.delta\ndata: {\"type\":\"response.function_call_arguments.delta\",\"delta\":\"\\\":\",\"item_id\":\"fc_054a16a2f2ea3217016a705b6d71cc81a38ac06634a309a997\",\"obfuscation\":\"UE4B3xJGoZjFiD\",\"output_index\":0,\"sequence_number\":5}", - "event: response.function_call_arguments.delta\ndata: {\"type\":\"response.function_call_arguments.delta\",\"delta\":\"\\\"/\",\"item_id\":\"fc_054a16a2f2ea3217016a705b6d71cc81a38ac06634a309a997\",\"obfuscation\":\"vXAYv4q4OGZeBm\",\"output_index\":0,\"sequence_number\":6}", - "event: response.function_call_arguments.delta\ndata: {\"type\":\"response.function_call_arguments.delta\",\"delta\":\"ver\",\"item_id\":\"fc_054a16a2f2ea3217016a705b6d71cc81a38ac06634a309a997\",\"obfuscation\":\"lZr7JtLSOpK81\",\"output_index\":0,\"sequence_number\":7}", - "event: response.function_call_arguments.delta\ndata: {\"type\":\"response.function_call_arguments.delta\",\"delta\":\"cel\",\"item_id\":\"fc_054a16a2f2ea3217016a705b6d71cc81a38ac06634a309a997\",\"obfuscation\":\"AlOhsp6mt6WsL\",\"output_index\":0,\"sequence_number\":8}", - "event: response.function_call_arguments.delta\ndata: {\"type\":\"response.function_call_arguments.delta\",\"delta\":\"/s\",\"item_id\":\"fc_054a16a2f2ea3217016a705b6d71cc81a38ac06634a309a997\",\"obfuscation\":\"5nqW1Lb0h9tswT\",\"output_index\":0,\"sequence_number\":9}", - "event: response.function_call_arguments.delta\ndata: {\"type\":\"response.function_call_arguments.delta\",\"delta\":\"andbox\",\"item_id\":\"fc_054a16a2f2ea3217016a705b6d71cc81a38ac06634a309a997\",\"obfuscation\":\"A7di1P36tJ\",\"output_index\":0,\"sequence_number\":10}", - "event: response.function_call_arguments.delta\ndata: {\"type\":\"response.function_call_arguments.delta\",\"delta\":\"/\",\"item_id\":\"fc_054a16a2f2ea3217016a705b6d71cc81a38ac06634a309a997\",\"obfuscation\":\"sXrATqCJ4vWOD1I\",\"output_index\":0,\"sequence_number\":11}", - "event: response.function_call_arguments.delta\ndata: {\"type\":\"response.function_call_arguments.delta\",\"delta\":\"brain\",\"item_id\":\"fc_054a16a2f2ea3217016a705b6d71cc81a38ac06634a309a997\",\"obfuscation\":\"dOlnaweYHcH\",\"output_index\":0,\"sequence_number\":12}", - "event: response.function_call_arguments.delta\ndata: {\"type\":\"response.function_call_arguments.delta\",\"delta\":\"trust\",\"item_id\":\"fc_054a16a2f2ea3217016a705b6d71cc81a38ac06634a309a997\",\"obfuscation\":\"1e9JdY1CZrh\",\"output_index\":0,\"sequence_number\":13}", - "event: response.function_call_arguments.delta\ndata: {\"type\":\"response.function_call_arguments.delta\",\"delta\":\"data\",\"item_id\":\"fc_054a16a2f2ea3217016a705b6d71cc81a38ac06634a309a997\",\"obfuscation\":\"ItoYfCrGd190\",\"output_index\":0,\"sequence_number\":14}", - "event: response.function_call_arguments.delta\ndata: {\"type\":\"response.function_call_arguments.delta\",\"delta\":\"/\",\"item_id\":\"fc_054a16a2f2ea3217016a705b6d71cc81a38ac06634a309a997\",\"obfuscation\":\"U2GDfehNMcQdTxK\",\"output_index\":0,\"sequence_number\":15}", - "event: response.function_call_arguments.delta\ndata: {\"type\":\"response.function_call_arguments.delta\",\"delta\":\"brain\",\"item_id\":\"fc_054a16a2f2ea3217016a705b6d71cc81a38ac06634a309a997\",\"obfuscation\":\"3sMRftpfAWB\",\"output_index\":0,\"sequence_number\":16}", - "event: response.function_call_arguments.delta\ndata: {\"type\":\"response.function_call_arguments.delta\",\"delta\":\"trust\",\"item_id\":\"fc_054a16a2f2ea3217016a705b6d71cc81a38ac06634a309a997\",\"obfuscation\":\"RtP3uejwoaW\",\"output_index\":0,\"sequence_number\":17}", - "event: response.function_call_arguments.delta\ndata: {\"type\":\"response.function_call_arguments.delta\",\"delta\":\"-sdk\",\"item_id\":\"fc_054a16a2f2ea3217016a705b6d71cc81a38ac06634a309a997\",\"obfuscation\":\"WTDkaRqKEdlO\",\"output_index\":0,\"sequence_number\":18}", - "event: response.function_call_arguments.delta\ndata: {\"type\":\"response.function_call_arguments.delta\",\"delta\":\"-j\",\"item_id\":\"fc_054a16a2f2ea3217016a705b6d71cc81a38ac06634a309a997\",\"obfuscation\":\"Sicm3wGutAJOUR\",\"output_index\":0,\"sequence_number\":19}", - "event: response.function_call_arguments.delta\ndata: {\"type\":\"response.function_call_arguments.delta\",\"delta\":\"avascript\",\"item_id\":\"fc_054a16a2f2ea3217016a705b6d71cc81a38ac06634a309a997\",\"obfuscation\":\"i3Iuw9L\",\"output_index\":0,\"sequence_number\":20}", - "event: response.function_call_arguments.delta\ndata: {\"type\":\"response.function_call_arguments.delta\",\"delta\":\"/e\",\"item_id\":\"fc_054a16a2f2ea3217016a705b6d71cc81a38ac06634a309a997\",\"obfuscation\":\"cNyMn1ymq3OidT\",\"output_index\":0,\"sequence_number\":21}", - "event: response.function_call_arguments.delta\ndata: {\"type\":\"response.function_call_arguments.delta\",\"delta\":\"2\",\"item_id\":\"fc_054a16a2f2ea3217016a705b6d71cc81a38ac06634a309a997\",\"obfuscation\":\"TF4UthkOjNccn7Q\",\"output_index\":0,\"sequence_number\":22}", - "event: response.function_call_arguments.delta\ndata: {\"type\":\"response.function_call_arguments.delta\",\"delta\":\"e\",\"item_id\":\"fc_054a16a2f2ea3217016a705b6d71cc81a38ac06634a309a997\",\"obfuscation\":\"WCH0Nr5syDCGWhy\",\"output_index\":0,\"sequence_number\":23}", - "event: response.function_call_arguments.delta\ndata: {\"type\":\"response.function_call_arguments.delta\",\"delta\":\"/sc\",\"item_id\":\"fc_054a16a2f2ea3217016a705b6d71cc81a38ac06634a309a997\",\"obfuscation\":\"9QxeDK80jR88i\",\"output_index\":0,\"sequence_number\":24}", - "event: response.function_call_arguments.delta\ndata: {\"type\":\"response.function_call_arguments.delta\",\"delta\":\"enarios\",\"item_id\":\"fc_054a16a2f2ea3217016a705b6d71cc81a38ac06634a309a997\",\"obfuscation\":\"ZWEwRV5Aa\",\"output_index\":0,\"sequence_number\":25}", - "event: response.function_call_arguments.delta\ndata: {\"type\":\"response.function_call_arguments.delta\",\"delta\":\"/fl\",\"item_id\":\"fc_054a16a2f2ea3217016a705b6d71cc81a38ac06634a309a997\",\"obfuscation\":\"GCLbXJEBfam0u\",\"output_index\":0,\"sequence_number\":26}", - "event: response.function_call_arguments.delta\ndata: {\"type\":\"response.function_call_arguments.delta\",\"delta\":\"ue\",\"item_id\":\"fc_054a16a2f2ea3217016a705b6d71cc81a38ac06634a309a997\",\"obfuscation\":\"fYviPD7BD1YoJ7\",\"output_index\":0,\"sequence_number\":27}", - "event: response.function_call_arguments.delta\ndata: {\"type\":\"response.function_call_arguments.delta\",\"delta\":\"-in\",\"item_id\":\"fc_054a16a2f2ea3217016a705b6d71cc81a38ac06634a309a997\",\"obfuscation\":\"12G2xIB4r4pGx\",\"output_index\":0,\"sequence_number\":28}", - "event: response.function_call_arguments.delta\ndata: {\"type\":\"response.function_call_arguments.delta\",\"delta\":\"strument\",\"item_id\":\"fc_054a16a2f2ea3217016a705b6d71cc81a38ac06634a309a997\",\"obfuscation\":\"RBo7r0Kw\",\"output_index\":0,\"sequence_number\":29}", - "event: response.function_call_arguments.delta\ndata: {\"type\":\"response.function_call_arguments.delta\",\"delta\":\"ation\",\"item_id\":\"fc_054a16a2f2ea3217016a705b6d71cc81a38ac06634a309a997\",\"obfuscation\":\"LdN0iq6VGNg\",\"output_index\":0,\"sequence_number\":30}", - "event: response.function_call_arguments.delta\ndata: {\"type\":\"response.function_call_arguments.delta\",\"delta\":\"/.\",\"item_id\":\"fc_054a16a2f2ea3217016a705b6d71cc81a38ac06634a309a997\",\"obfuscation\":\"SC3tJji0aA9vxI\",\"output_index\":0,\"sequence_number\":31}", - "event: response.function_call_arguments.delta\ndata: {\"type\":\"response.function_call_arguments.delta\",\"delta\":\"agents\",\"item_id\":\"fc_054a16a2f2ea3217016a705b6d71cc81a38ac06634a309a997\",\"obfuscation\":\"46FVinGMH1\",\"output_index\":0,\"sequence_number\":32}", - "event: response.function_call_arguments.delta\ndata: {\"type\":\"response.function_call_arguments.delta\",\"delta\":\"/\",\"item_id\":\"fc_054a16a2f2ea3217016a705b6d71cc81a38ac06634a309a997\",\"obfuscation\":\"Tu4YeGAIlzJv7RO\",\"output_index\":0,\"sequence_number\":33}", - "event: response.function_call_arguments.delta\ndata: {\"type\":\"response.function_call_arguments.delta\",\"delta\":\"skills\",\"item_id\":\"fc_054a16a2f2ea3217016a705b6d71cc81a38ac06634a309a997\",\"obfuscation\":\"chyXB4Vg5n\",\"output_index\":0,\"sequence_number\":34}", - "event: response.function_call_arguments.delta\ndata: {\"type\":\"response.function_call_arguments.delta\",\"delta\":\"/e\",\"item_id\":\"fc_054a16a2f2ea3217016a705b6d71cc81a38ac06634a309a997\",\"obfuscation\":\"koR43IjON9fzgj\",\"output_index\":0,\"sequence_number\":35}", - "event: response.function_call_arguments.delta\ndata: {\"type\":\"response.function_call_arguments.delta\",\"delta\":\"2\",\"item_id\":\"fc_054a16a2f2ea3217016a705b6d71cc81a38ac06634a309a997\",\"obfuscation\":\"2PamefwKxxhd235\",\"output_index\":0,\"sequence_number\":36}", - "event: response.function_call_arguments.delta\ndata: {\"type\":\"response.function_call_arguments.delta\",\"delta\":\"e\",\"item_id\":\"fc_054a16a2f2ea3217016a705b6d71cc81a38ac06634a309a997\",\"obfuscation\":\"7OGNeIaOwukKH4k\",\"output_index\":0,\"sequence_number\":37}", - "event: response.function_call_arguments.delta\ndata: {\"type\":\"response.function_call_arguments.delta\",\"delta\":\"-fl\",\"item_id\":\"fc_054a16a2f2ea3217016a705b6d71cc81a38ac06634a309a997\",\"obfuscation\":\"LyzjvJ7oVbrNO\",\"output_index\":0,\"sequence_number\":38}", - "event: response.function_call_arguments.delta\ndata: {\"type\":\"response.function_call_arguments.delta\",\"delta\":\"ue\",\"item_id\":\"fc_054a16a2f2ea3217016a705b6d71cc81a38ac06634a309a997\",\"obfuscation\":\"F6FvzwFZ7LJK05\",\"output_index\":0,\"sequence_number\":39}", - "event: response.function_call_arguments.delta\ndata: {\"type\":\"response.function_call_arguments.delta\",\"delta\":\"-s\",\"item_id\":\"fc_054a16a2f2ea3217016a705b6d71cc81a38ac06634a309a997\",\"obfuscation\":\"PvR9f24G2kgLxp\",\"output_index\":0,\"sequence_number\":40}", - "event: response.function_call_arguments.delta\ndata: {\"type\":\"response.function_call_arguments.delta\",\"delta\":\"kill\",\"item_id\":\"fc_054a16a2f2ea3217016a705b6d71cc81a38ac06634a309a997\",\"obfuscation\":\"Cc4f9PrTB9gy\",\"output_index\":0,\"sequence_number\":41}", - "event: response.function_call_arguments.delta\ndata: {\"type\":\"response.function_call_arguments.delta\",\"delta\":\"/S\",\"item_id\":\"fc_054a16a2f2ea3217016a705b6d71cc81a38ac06634a309a997\",\"obfuscation\":\"NFmNKyCIgJRpgX\",\"output_index\":0,\"sequence_number\":42}", - "event: response.function_call_arguments.delta\ndata: {\"type\":\"response.function_call_arguments.delta\",\"delta\":\"K\",\"item_id\":\"fc_054a16a2f2ea3217016a705b6d71cc81a38ac06634a309a997\",\"obfuscation\":\"i6Hkkg289FwqvEa\",\"output_index\":0,\"sequence_number\":43}", - "event: response.function_call_arguments.delta\ndata: {\"type\":\"response.function_call_arguments.delta\",\"delta\":\"ILL\",\"item_id\":\"fc_054a16a2f2ea3217016a705b6d71cc81a38ac06634a309a997\",\"obfuscation\":\"Z0w7G2ePBPqRw\",\"output_index\":0,\"sequence_number\":44}", - "event: response.function_call_arguments.delta\ndata: {\"type\":\"response.function_call_arguments.delta\",\"delta\":\".md\",\"item_id\":\"fc_054a16a2f2ea3217016a705b6d71cc81a38ac06634a309a997\",\"obfuscation\":\"U8sCPOgqSHwmL\",\"output_index\":0,\"sequence_number\":45}", - "event: response.function_call_arguments.delta\ndata: {\"type\":\"response.function_call_arguments.delta\",\"delta\":\"\\\",\\\"\",\"item_id\":\"fc_054a16a2f2ea3217016a705b6d71cc81a38ac06634a309a997\",\"obfuscation\":\"Rj1PgiaJnVY9e\",\"output_index\":0,\"sequence_number\":46}", - "event: response.function_call_arguments.delta\ndata: {\"type\":\"response.function_call_arguments.delta\",\"delta\":\"offset\",\"item_id\":\"fc_054a16a2f2ea3217016a705b6d71cc81a38ac06634a309a997\",\"obfuscation\":\"Nv7VtoPO8D\",\"output_index\":0,\"sequence_number\":47}", - "event: response.function_call_arguments.delta\ndata: {\"type\":\"response.function_call_arguments.delta\",\"delta\":\"\\\":\",\"item_id\":\"fc_054a16a2f2ea3217016a705b6d71cc81a38ac06634a309a997\",\"obfuscation\":\"xMA7iDRhGXyoRS\",\"output_index\":0,\"sequence_number\":48}", - "event: response.function_call_arguments.delta\ndata: {\"type\":\"response.function_call_arguments.delta\",\"delta\":\"1\",\"item_id\":\"fc_054a16a2f2ea3217016a705b6d71cc81a38ac06634a309a997\",\"obfuscation\":\"4HmQwBvniK7325Y\",\"output_index\":0,\"sequence_number\":49}", - "event: response.function_call_arguments.delta\ndata: {\"type\":\"response.function_call_arguments.delta\",\"delta\":\",\\\"\",\"item_id\":\"fc_054a16a2f2ea3217016a705b6d71cc81a38ac06634a309a997\",\"obfuscation\":\"fp7VCNlkmETdHR\",\"output_index\":0,\"sequence_number\":50}", - "event: response.function_call_arguments.delta\ndata: {\"type\":\"response.function_call_arguments.delta\",\"delta\":\"limit\",\"item_id\":\"fc_054a16a2f2ea3217016a705b6d71cc81a38ac06634a309a997\",\"obfuscation\":\"JnAHVomsUGV\",\"output_index\":0,\"sequence_number\":51}", - "event: response.function_call_arguments.delta\ndata: {\"type\":\"response.function_call_arguments.delta\",\"delta\":\"\\\":\",\"item_id\":\"fc_054a16a2f2ea3217016a705b6d71cc81a38ac06634a309a997\",\"obfuscation\":\"oMhJTWBoaVQje5\",\"output_index\":0,\"sequence_number\":52}", - "event: response.function_call_arguments.delta\ndata: {\"type\":\"response.function_call_arguments.delta\",\"delta\":\"200\",\"item_id\":\"fc_054a16a2f2ea3217016a705b6d71cc81a38ac06634a309a997\",\"obfuscation\":\"pck09M2RCSfdF\",\"output_index\":0,\"sequence_number\":53}", - "event: response.function_call_arguments.delta\ndata: {\"type\":\"response.function_call_arguments.delta\",\"delta\":\"}\",\"item_id\":\"fc_054a16a2f2ea3217016a705b6d71cc81a38ac06634a309a997\",\"obfuscation\":\"v0WZruprCkbMb5J\",\"output_index\":0,\"sequence_number\":54}", - "event: response.function_call_arguments.done\ndata: {\"type\":\"response.function_call_arguments.done\",\"arguments\":\"{\\\"path\\\":\\\"./.agents/skills/e2e-flue-skill/SKILL.md\\\",\\\"offset\\\":1,\\\"limit\\\":200}\",\"item_id\":\"fc_054a16a2f2ea3217016a705b6d71cc81a38ac06634a309a997\",\"output_index\":0,\"sequence_number\":55}", - "event: response.output_item.done\ndata: {\"type\":\"response.output_item.done\",\"item\":{\"id\":\"fc_054a16a2f2ea3217016a705b6d71cc81a38ac06634a309a997\",\"type\":\"function_call\",\"status\":\"completed\",\"arguments\":\"{\\\"path\\\":\\\"./.agents/skills/e2e-flue-skill/SKILL.md\\\",\\\"offset\\\":1,\\\"limit\\\":200}\",\"call_id\":\"call_KXcpmIy4Cbfugk4SNT2Ge7sG\",\"name\":\"read\"},\"output_index\":0,\"sequence_number\":56}", - "event: response.completed\ndata: {\"type\":\"response.completed\",\"response\":{\"id\":\"resp_054a16a2f2ea3217016a705b6d008c81a3a332b5376350158b\",\"object\":\"response\",\"created_at\":1785748333,\"status\":\"completed\",\"background\":false,\"completed_at\":1785748334,\"error\":null,\"frequency_penalty\":0,\"incomplete_details\":null,\"instructions\":null,\"max_output_tokens\":null,\"max_tool_calls\":null,\"model\":\"gpt-5.4-nano-2026-03-17\",\"moderation\":null,\"output\":[{\"id\":\"fc_054a16a2f2ea3217016a705b6d71cc81a38ac06634a309a997\",\"type\":\"function_call\",\"status\":\"completed\",\"arguments\":\"{\\\"path\\\":\\\"./.agents/skills/e2e-flue-skill/SKILL.md\\\",\\\"offset\\\":1,\\\"limit\\\":200}\",\"call_id\":\"call_KXcpmIy4Cbfugk4SNT2Ge7sG\",\"name\":\"read\"}],\"parallel_tool_calls\":true,\"presence_penalty\":0,\"previous_response_id\":null,\"prompt_cache_key\":\"workflow:instrumentation:01KZ3E9TWTAEK4P4P3F63HKAKC::default::ta\",\"prompt_cache_retention\":\"24h\",\"reasoning\":{\"context\":\"current_turn\",\"effort\":\"none\",\"mode\":\"standard\",\"summary\":null},\"safety_identifier\":null,\"service_tier\":\"default\",\"store\":false,\"temperature\":1,\"text\":{\"format\":{\"type\":\"text\"},\"verbosity\":\"medium\"},\"tool_choice\":\"auto\",\"tool_usage\":{\"image_gen\":{\"input_tokens\":0,\"input_tokens_details\":{\"image_tokens\":0,\"text_tokens\":0},\"output_tokens\":0,\"output_tokens_details\":{\"image_tokens\":0,\"text_tokens\":0},\"total_tokens\":0},\"web_search\":{\"num_requests\":0}},\"tools\":[{\"type\":\"function\",\"description\":\"Read a file or list a directory. For files, output is truncated to 2000 lines or 50KB — use offset/limit for large files. For directories, returns the list of entries.\",\"name\":\"read\",\"output_schema\":null,\"parameters\":{\"type\":\"object\",\"required\":[\"path\"],\"properties\":{\"path\":{\"type\":\"string\",\"description\":\"Path to the file to read\"},\"offset\":{\"type\":\"number\",\"description\":\"Line number to start from (1-indexed)\"},\"limit\":{\"type\":\"number\",\"description\":\"Maximum number of lines to read\"}}},\"strict\":false},{\"type\":\"function\",\"description\":\"Write content to a file. Creates the file and parent directories if they do not exist.\",\"name\":\"write\",\"output_schema\":null,\"parameters\":{\"type\":\"object\",\"required\":[\"path\",\"content\"],\"properties\":{\"path\":{\"type\":\"string\",\"description\":\"Path to the file to write\"},\"content\":{\"type\":\"string\",\"description\":\"Content to write to the file\"}}},\"strict\":false},{\"type\":\"function\",\"description\":\"Edit a file using exact text replacement. The oldText must match a unique region of the file. Use replaceAll to replace all occurrences.\",\"name\":\"edit\",\"output_schema\":null,\"parameters\":{\"type\":\"object\",\"required\":[\"path\",\"oldText\",\"newText\"],\"properties\":{\"path\":{\"type\":\"string\",\"description\":\"Path to the file to edit\"},\"oldText\":{\"type\":\"string\",\"description\":\"Exact text to find (must be unique)\"},\"newText\":{\"type\":\"string\",\"description\":\"Replacement text\"},\"replaceAll\":{\"type\":\"boolean\",\"description\":\"Replace all occurrences\"}}},\"strict\":false},{\"type\":\"function\",\"description\":\"Execute a bash command. Returns stdout and stderr. Output is truncated to the last 2000 lines or 50KB.\",\"name\":\"bash\",\"output_schema\":null,\"parameters\":{\"type\":\"object\",\"required\":[\"command\"],\"properties\":{\"command\":{\"type\":\"string\",\"description\":\"Bash command to execute\"},\"timeout\":{\"type\":\"number\",\"description\":\"Timeout in seconds\"}}},\"strict\":false},{\"type\":\"function\",\"description\":\"Search file contents for a regex pattern. Returns matching lines with file paths and line numbers.\",\"name\":\"grep\",\"output_schema\":null,\"parameters\":{\"type\":\"object\",\"required\":[\"pattern\"],\"properties\":{\"pattern\":{\"type\":\"string\",\"description\":\"Search pattern (regex)\"},\"path\":{\"type\":\"string\",\"description\":\"Directory or file to search (default: .)\"},\"include\":{\"type\":\"string\",\"description\":\"Glob filter, e.g. \\\"*.ts\\\"\"}}},\"strict\":false},{\"type\":\"function\",\"description\":\"Find files by filename pattern using shell find -name semantics. Returns matching file paths.\",\"name\":\"glob\",\"output_schema\":null,\"parameters\":{\"type\":\"object\",\"required\":[\"pattern\"],\"properties\":{\"pattern\":{\"type\":\"string\",\"description\":\"Filename pattern, e.g. \\\"*.ts\\\"\"},\"path\":{\"type\":\"string\",\"description\":\"Directory to search in (default: .)\"}}},\"strict\":false},{\"type\":\"function\",\"description\":\"Delegate a focused task to a detached child agent with its own context. Use this for independent research, file exploration, or parallel work. The task returns only its final answer to this conversation. No subagents are currently defined.\",\"name\":\"task\",\"output_schema\":null,\"parameters\":{\"type\":\"object\",\"required\":[\"prompt\"],\"properties\":{\"description\":{\"type\":\"string\",\"description\":\"Short human-readable label for the delegated work\"},\"prompt\":{\"type\":\"string\",\"description\":\"Focused instructions for the child agent\"},\"agent\":{\"type\":\"string\",\"description\":\"Declared subagent to use for the child agent\"},\"cwd\":{\"type\":\"string\",\"description\":\"Working directory for the child agent. AGENTS.md and skills are discovered from here.\"}}},\"strict\":false}],\"top_logprobs\":0,\"top_p\":0.98,\"truncation\":\"disabled\",\"usage\":{\"input_tokens\":1762,\"input_tokens_details\":{\"cache_write_tokens\":0,\"cached_tokens\":1536},\"output_tokens\":64,\"output_tokens_details\":{\"reasoning_tokens\":0},\"total_tokens\":1826},\"user\":null,\"metadata\":{}},\"sequence_number\":57}" - ], - "kind": "sse" - }, - "headers": { - "access-control-expose-headers": "X-Request-ID, CF-Ray, CF-Ray", - "alt-svc": "h3=\":443\"; ma=86400", - "cf-cache-status": "DYNAMIC", - "cf-ray": "a2543308de867bb6-IAD", - "connection": "keep-alive", - "content-type": "text/event-stream; charset=utf-8", - "date": "Mon, 03 Aug 2026 09:12:13 GMT", - "openai-organization": "braintrust-data", - "openai-processing-ms": "248", - "openai-project": "proj_vsCSXafhhByzWOThMrJcZiw9", - "openai-version": "2020-10-01", - "server": "cloudflare", - "set-cookie": "[REDACTED]", - "strict-transport-security": "max-age=31536000; includeSubDomains; preload", - "transfer-encoding": "chunked", - "x-content-type-options": "nosniff", - "x-ratelimit-limit-requests": "30000", - "x-ratelimit-limit-tokens": "180000000", - "x-ratelimit-remaining-requests": "29999", - "x-ratelimit-remaining-tokens": "179997783", - "x-ratelimit-reset-requests": "2ms", - "x-ratelimit-reset-tokens": "0s", - "x-request-id": "req_33374d00629445b4aa73562f25cf18f0" - }, - "status": 200, - "statusText": "OK" - } - }, - { - "callIndex": 12, - "id": "fecb7c9f8d04e1bd", - "matchKey": "POST api.openai.com/v1/responses", - "recordedAt": "2026-08-03T09:12:15.213Z", - "request": { - "body": { - "kind": "json", - "value": { - "input": [ - { - "content": "You are running in headless mode with no human operator. Work autonomously — never ask questions, never wait for user input. Make your best judgment and proceed independently.\n\nYou are a deterministic Flue instrumentation test agent. Follow user instructions exactly. When asked for a marker, output only that marker and no extra text. When running a local skill file, read it yourself and do not delegate it to a task.\n\n## Available Skills\n\nThe following skills provide specialized instructions for specific tasks. When a task matches a skill description, activate that skill before proceeding so its full instructions are loaded. Skill instructions and supporting resources stay lazy until activation or explicit file reads.\n\n- **e2e-flue-skill** — Deterministic skill used by the Braintrust Flue instrumentation e2e test.\n\nDate: Mon, Aug 3, 2026\nWorking directory: .\n\nDirectory structure:\n.agents\n.flue\n.flue-build\n__cassettes__\n__snapshots__\nassertions.ts\ncassette-filter.mjs\nconstants.mjs\ndist\nnode_modules\npackage.json\npnpm-lock.yaml\nscenario.cli.mjs\nscenario.impl.mjs\nscenario.mjs\nscenario.test.ts\nscenario.ts\nscenario.v2.ts\nv2-assertions.ts\nversions", - "role": "developer" - }, - { - "content": [ - { - "text": "Run the skill named \"e2e-flue-skill\" with arguments {\"marker\":\"SKILL_DONE\"}. Output only the marker and no extra text when finished.", - "type": "input_text" - } - ], - "role": "user" - }, - { - "arguments": "{\"description\":\"Run e2e-flue-skill with marker SKILL_DONE\",\"prompt\":\"Run the skill named \\\"e2e-flue-skill\\\" with arguments {\\\"marker\\\":\\\"SKILL_DONE\\\"}. Ensure completion and report the final output marker only.\",\"agent\":\"e2e-flue-skill\",\"cwd\":\".\"}", - "call_id": "call_w6pyYZgnhlSOAXul75DAG1Cj", - "id": "fc_054a16a2f2ea3217016a705b64fe5481a390185c9576df12b6", - "name": "task", - "type": "function_call" - }, - { - "call_id": "call_w6pyYZgnhlSOAXul75DAG1Cj", - "output": "[flue] Subagent \"e2e-flue-skill\" is not declared. Available: (none).", - "type": "function_call_output" - }, - { - "arguments": "{\"command\":\"find . -maxdepth 4 -name AGENTS.md -print\",\"timeout\":100000}", - "call_id": "call_6zEBnJRmXFIkGFwNjWLZP4uC", - "id": "fc_054a16a2f2ea3217016a705b66460481a3a5de06df603e35ee", - "name": "bash", - "type": "function_call" - }, - { - "call_id": "call_6zEBnJRmXFIkGFwNjWLZP4uC", - "output": "total 4\ndrwxr-xr-x 3 vercel-sandbox vercel-sandbox 20 Aug 3 09:10 .\ndrwxr-xr-x 10 vercel-sandbox vercel-sandbox 4096 Aug 3 09:11 ..\ndrwxr-xr-x 3 vercel-sandbox vercel-sandbox 28 Aug 3 09:10 skills", - "type": "function_call_output" - }, - { - "arguments": "{\"command\":\"find . -maxdepth 4 -type f -name AGENTS.md -print -exec sed -n '1,200p' {} \\\\;\",\"timeout\":100000}", - "call_id": "call_jQUCie0ZgaXUrZCDcezp0msl", - "id": "fc_054a16a2f2ea3217016a705b67575881a3aacde02b9053a321", - "name": "bash", - "type": "function_call" - }, - { - "call_id": "call_jQUCie0ZgaXUrZCDcezp0msl", - "output": "(no output)", - "type": "function_call_output" - }, - { - "arguments": "{\"command\":\"find . -maxdepth 6 -type f -name 'AGENTS.md' -print\",\"timeout\":100000}", - "call_id": "call_NgMSUb6wIR0GAf9U8LXERtBQ", - "id": "fc_054a16a2f2ea3217016a705b68c39881a3b1e518599236e41a", - "name": "bash", - "type": "function_call" - }, - { - "call_id": "call_NgMSUb6wIR0GAf9U8LXERtBQ", - "output": "(no output)", - "type": "function_call_output" - }, - { - "arguments": "{\"command\":\"find . -maxdepth 5 -type f -name '*flue*skill*' -o -name '*skill*' | head\",\"timeout\":100000}", - "call_id": "call_BXKiQ5KG8c42XPPp94zgcY3H", - "id": "fc_054a16a2f2ea3217016a705b69edd081a38c31583ec56eca5f", - "name": "bash", - "type": "function_call" - }, - { - "call_id": "call_BXKiQ5KG8c42XPPp94zgcY3H", - "output": "./.agents/skills\n./.agents/skills/e2e-flue-skill", - "type": "function_call_output" - }, - { - "arguments": "{\"command\":\"find ./.agents/skills/e2e-flue-skill -maxdepth 2 -type f -print\",\"timeout\":100000}", - "call_id": "call_Zmmrs64LFscpAHsIUaIfEcAN", - "id": "fc_054a16a2f2ea3217016a705b6b96cc81a3ac4761db24198a63", - "name": "bash", - "type": "function_call" - }, - { - "call_id": "call_Zmmrs64LFscpAHsIUaIfEcAN", - "output": "total 4\ndrwxr-xr-x 2 vercel-sandbox vercel-sandbox 22 Aug 3 07:55 .\ndrwxr-xr-x 3 vercel-sandbox vercel-sandbox 28 Aug 3 07:55 ..\n-rw-r--r-- 1 vercel-sandbox vercel-sandbox 194 Aug 3 07:55 SKILL.md\n./.agents/skills/e2e-flue-skill/SKILL.md", - "type": "function_call_output" - }, - { - "arguments": "{\"path\":\"./.agents/skills/e2e-flue-skill/SKILL.md\",\"offset\":1,\"limit\":200}", - "call_id": "call_KXcpmIy4Cbfugk4SNT2Ge7sG", - "id": "fc_054a16a2f2ea3217016a705b6d71cc81a38ac06634a309a997", - "name": "read", - "type": "function_call" - }, - { - "call_id": "call_KXcpmIy4Cbfugk4SNT2Ge7sG", - "output": "---\nname: e2e-flue-skill\ndescription: Deterministic skill used by the Braintrust Flue instrumentation e2e test.\n---\n\nReturn the marker from the provided args exactly once. Output no other text.\n", - "type": "function_call_output" - } - ], - "model": "gpt-5.4-nano", - "prompt_cache_key": "[REDACTED]", - "reasoning": { - "effort": "none" - }, - "store": false, - "stream": true, - "tools": [ - { - "description": "Read a file or list a directory. For files, output is truncated to 2000 lines or 50KB — use offset/limit for large files. For directories, returns the list of entries.", - "name": "read", - "parameters": { - "properties": { - "limit": { - "description": "Maximum number of lines to read", - "type": "number" - }, - "offset": { - "description": "Line number to start from (1-indexed)", - "type": "number" - }, - "path": { - "description": "Path to the file to read", - "type": "string" - } - }, - "required": ["path"], - "type": "object" - }, - "strict": false, - "type": "function" - }, - { - "description": "Write content to a file. Creates the file and parent directories if they do not exist.", - "name": "write", - "parameters": { - "properties": { - "content": { - "description": "Content to write to the file", - "type": "string" - }, - "path": { - "description": "Path to the file to write", - "type": "string" - } - }, - "required": ["path", "content"], - "type": "object" - }, - "strict": false, - "type": "function" - }, - { - "description": "Edit a file using exact text replacement. The oldText must match a unique region of the file. Use replaceAll to replace all occurrences.", - "name": "edit", - "parameters": { - "properties": { - "newText": { - "description": "Replacement text", - "type": "string" - }, - "oldText": { - "description": "Exact text to find (must be unique)", - "type": "string" - }, - "path": { - "description": "Path to the file to edit", - "type": "string" - }, - "replaceAll": { - "description": "Replace all occurrences", - "type": "boolean" - } - }, - "required": ["path", "oldText", "newText"], - "type": "object" - }, - "strict": false, - "type": "function" - }, - { - "description": "Execute a bash command. Returns stdout and stderr. Output is truncated to the last 2000 lines or 50KB.", - "name": "bash", - "parameters": { - "properties": { - "command": { - "description": "Bash command to execute", - "type": "string" - }, - "timeout": { - "description": "Timeout in seconds", - "type": "number" - } - }, - "required": ["command"], - "type": "object" - }, - "strict": false, - "type": "function" - }, - { - "description": "Search file contents for a regex pattern. Returns matching lines with file paths and line numbers.", - "name": "grep", - "parameters": { - "properties": { - "include": { - "description": "Glob filter, e.g. \"*.ts\"", - "type": "string" - }, - "path": { - "description": "Directory or file to search (default: .)", - "type": "string" - }, - "pattern": { - "description": "Search pattern (regex)", - "type": "string" - } - }, - "required": ["pattern"], - "type": "object" - }, - "strict": false, - "type": "function" - }, - { - "description": "Find files by filename pattern using shell find -name semantics. Returns matching file paths.", - "name": "glob", - "parameters": { - "properties": { - "path": { - "description": "Directory to search in (default: .)", - "type": "string" - }, - "pattern": { - "description": "Filename pattern, e.g. \"*.ts\"", - "type": "string" - } - }, - "required": ["pattern"], - "type": "object" - }, - "strict": false, - "type": "function" - }, - { - "description": "Delegate a focused task to a detached child agent with its own context. Use this for independent research, file exploration, or parallel work. The task returns only its final answer to this conversation. No subagents are currently defined.", - "name": "task", - "parameters": { - "properties": { - "agent": { - "description": "Declared subagent to use for the child agent", - "type": "string" - }, - "cwd": { - "description": "Working directory for the child agent. AGENTS.md and skills are discovered from here.", - "type": "string" - }, - "description": { - "description": "Short human-readable label for the delegated work", - "type": "string" - }, - "prompt": { - "description": "Focused instructions for the child agent", - "type": "string" - } - }, - "required": ["prompt"], - "type": "object" - }, - "strict": false, - "type": "function" - } - ] - } - }, - "headers": {}, - "method": "POST", - "url": "https://api.openai.com/v1/responses" - }, - "response": { - "body": { - "chunks": [ - "event: response.created\ndata: {\"type\":\"response.created\",\"response\":{\"id\":\"resp_054a16a2f2ea3217016a705b6e34cc81a383184de2af428d09\",\"object\":\"response\",\"created_at\":1785748334,\"status\":\"in_progress\",\"background\":false,\"completed_at\":null,\"error\":null,\"frequency_penalty\":0,\"incomplete_details\":null,\"instructions\":null,\"max_output_tokens\":null,\"max_tool_calls\":null,\"model\":\"gpt-5.4-nano-2026-03-17\",\"moderation\":null,\"output\":[],\"parallel_tool_calls\":true,\"presence_penalty\":0,\"previous_response_id\":null,\"prompt_cache_key\":\"workflow:instrumentation:01KZ3E9TWTAEK4P4P3F63HKAKC::default::ta\",\"prompt_cache_retention\":\"24h\",\"reasoning\":{\"context\":\"current_turn\",\"effort\":\"none\",\"mode\":\"standard\",\"summary\":null},\"safety_identifier\":null,\"service_tier\":\"auto\",\"store\":false,\"temperature\":1,\"text\":{\"format\":{\"type\":\"text\"},\"verbosity\":\"medium\"},\"tool_choice\":\"auto\",\"tool_usage\":{\"image_gen\":{\"input_tokens\":0,\"input_tokens_details\":{\"image_tokens\":0,\"text_tokens\":0},\"output_tokens\":0,\"output_tokens_details\":{\"image_tokens\":0,\"text_tokens\":0},\"total_tokens\":0},\"web_search\":{\"num_requests\":0}},\"tools\":[{\"type\":\"function\",\"description\":\"Read a file or list a directory. For files, output is truncated to 2000 lines or 50KB — use offset/limit for large files. For directories, returns the list of entries.\",\"name\":\"read\",\"output_schema\":null,\"parameters\":{\"type\":\"object\",\"required\":[\"path\"],\"properties\":{\"path\":{\"type\":\"string\",\"description\":\"Path to the file to read\"},\"offset\":{\"type\":\"number\",\"description\":\"Line number to start from (1-indexed)\"},\"limit\":{\"type\":\"number\",\"description\":\"Maximum number of lines to read\"}}},\"strict\":false},{\"type\":\"function\",\"description\":\"Write content to a file. Creates the file and parent directories if they do not exist.\",\"name\":\"write\",\"output_schema\":null,\"parameters\":{\"type\":\"object\",\"required\":[\"path\",\"content\"],\"properties\":{\"path\":{\"type\":\"string\",\"description\":\"Path to the file to write\"},\"content\":{\"type\":\"string\",\"description\":\"Content to write to the file\"}}},\"strict\":false},{\"type\":\"function\",\"description\":\"Edit a file using exact text replacement. The oldText must match a unique region of the file. Use replaceAll to replace all occurrences.\",\"name\":\"edit\",\"output_schema\":null,\"parameters\":{\"type\":\"object\",\"required\":[\"path\",\"oldText\",\"newText\"],\"properties\":{\"path\":{\"type\":\"string\",\"description\":\"Path to the file to edit\"},\"oldText\":{\"type\":\"string\",\"description\":\"Exact text to find (must be unique)\"},\"newText\":{\"type\":\"string\",\"description\":\"Replacement text\"},\"replaceAll\":{\"type\":\"boolean\",\"description\":\"Replace all occurrences\"}}},\"strict\":false},{\"type\":\"function\",\"description\":\"Execute a bash command. Returns stdout and stderr. Output is truncated to the last 2000 lines or 50KB.\",\"name\":\"bash\",\"output_schema\":null,\"parameters\":{\"type\":\"object\",\"required\":[\"command\"],\"properties\":{\"command\":{\"type\":\"string\",\"description\":\"Bash command to execute\"},\"timeout\":{\"type\":\"number\",\"description\":\"Timeout in seconds\"}}},\"strict\":false},{\"type\":\"function\",\"description\":\"Search file contents for a regex pattern. Returns matching lines with file paths and line numbers.\",\"name\":\"grep\",\"output_schema\":null,\"parameters\":{\"type\":\"object\",\"required\":[\"pattern\"],\"properties\":{\"pattern\":{\"type\":\"string\",\"description\":\"Search pattern (regex)\"},\"path\":{\"type\":\"string\",\"description\":\"Directory or file to search (default: .)\"},\"include\":{\"type\":\"string\",\"description\":\"Glob filter, e.g. \\\"*.ts\\\"\"}}},\"strict\":false},{\"type\":\"function\",\"description\":\"Find files by filename pattern using shell find -name semantics. Returns matching file paths.\",\"name\":\"glob\",\"output_schema\":null,\"parameters\":{\"type\":\"object\",\"required\":[\"pattern\"],\"properties\":{\"pattern\":{\"type\":\"string\",\"description\":\"Filename pattern, e.g. \\\"*.ts\\\"\"},\"path\":{\"type\":\"string\",\"description\":\"Directory to search in (default: .)\"}}},\"strict\":false},{\"type\":\"function\",\"description\":\"Delegate a focused task to a detached child agent with its own context. Use this for independent research, file exploration, or parallel work. The task returns only its final answer to this conversation. No subagents are currently defined.\",\"name\":\"task\",\"output_schema\":null,\"parameters\":{\"type\":\"object\",\"required\":[\"prompt\"],\"properties\":{\"description\":{\"type\":\"string\",\"description\":\"Short human-readable label for the delegated work\"},\"prompt\":{\"type\":\"string\",\"description\":\"Focused instructions for the child agent\"},\"agent\":{\"type\":\"string\",\"description\":\"Declared subagent to use for the child agent\"},\"cwd\":{\"type\":\"string\",\"description\":\"Working directory for the child agent. AGENTS.md and skills are discovered from here.\"}}},\"strict\":false}],\"top_logprobs\":0,\"top_p\":0.98,\"truncation\":\"disabled\",\"usage\":null,\"user\":null,\"metadata\":{}},\"sequence_number\":0}", - "event: response.in_progress\ndata: {\"type\":\"response.in_progress\",\"response\":{\"id\":\"resp_054a16a2f2ea3217016a705b6e34cc81a383184de2af428d09\",\"object\":\"response\",\"created_at\":1785748334,\"status\":\"in_progress\",\"background\":false,\"completed_at\":null,\"error\":null,\"frequency_penalty\":0,\"incomplete_details\":null,\"instructions\":null,\"max_output_tokens\":null,\"max_tool_calls\":null,\"model\":\"gpt-5.4-nano-2026-03-17\",\"moderation\":null,\"output\":[],\"parallel_tool_calls\":true,\"presence_penalty\":0,\"previous_response_id\":null,\"prompt_cache_key\":\"workflow:instrumentation:01KZ3E9TWTAEK4P4P3F63HKAKC::default::ta\",\"prompt_cache_retention\":\"24h\",\"reasoning\":{\"context\":\"current_turn\",\"effort\":\"none\",\"mode\":\"standard\",\"summary\":null},\"safety_identifier\":null,\"service_tier\":\"auto\",\"store\":false,\"temperature\":1,\"text\":{\"format\":{\"type\":\"text\"},\"verbosity\":\"medium\"},\"tool_choice\":\"auto\",\"tool_usage\":{\"image_gen\":{\"input_tokens\":0,\"input_tokens_details\":{\"image_tokens\":0,\"text_tokens\":0},\"output_tokens\":0,\"output_tokens_details\":{\"image_tokens\":0,\"text_tokens\":0},\"total_tokens\":0},\"web_search\":{\"num_requests\":0}},\"tools\":[{\"type\":\"function\",\"description\":\"Read a file or list a directory. For files, output is truncated to 2000 lines or 50KB — use offset/limit for large files. For directories, returns the list of entries.\",\"name\":\"read\",\"output_schema\":null,\"parameters\":{\"type\":\"object\",\"required\":[\"path\"],\"properties\":{\"path\":{\"type\":\"string\",\"description\":\"Path to the file to read\"},\"offset\":{\"type\":\"number\",\"description\":\"Line number to start from (1-indexed)\"},\"limit\":{\"type\":\"number\",\"description\":\"Maximum number of lines to read\"}}},\"strict\":false},{\"type\":\"function\",\"description\":\"Write content to a file. Creates the file and parent directories if they do not exist.\",\"name\":\"write\",\"output_schema\":null,\"parameters\":{\"type\":\"object\",\"required\":[\"path\",\"content\"],\"properties\":{\"path\":{\"type\":\"string\",\"description\":\"Path to the file to write\"},\"content\":{\"type\":\"string\",\"description\":\"Content to write to the file\"}}},\"strict\":false},{\"type\":\"function\",\"description\":\"Edit a file using exact text replacement. The oldText must match a unique region of the file. Use replaceAll to replace all occurrences.\",\"name\":\"edit\",\"output_schema\":null,\"parameters\":{\"type\":\"object\",\"required\":[\"path\",\"oldText\",\"newText\"],\"properties\":{\"path\":{\"type\":\"string\",\"description\":\"Path to the file to edit\"},\"oldText\":{\"type\":\"string\",\"description\":\"Exact text to find (must be unique)\"},\"newText\":{\"type\":\"string\",\"description\":\"Replacement text\"},\"replaceAll\":{\"type\":\"boolean\",\"description\":\"Replace all occurrences\"}}},\"strict\":false},{\"type\":\"function\",\"description\":\"Execute a bash command. Returns stdout and stderr. Output is truncated to the last 2000 lines or 50KB.\",\"name\":\"bash\",\"output_schema\":null,\"parameters\":{\"type\":\"object\",\"required\":[\"command\"],\"properties\":{\"command\":{\"type\":\"string\",\"description\":\"Bash command to execute\"},\"timeout\":{\"type\":\"number\",\"description\":\"Timeout in seconds\"}}},\"strict\":false},{\"type\":\"function\",\"description\":\"Search file contents for a regex pattern. Returns matching lines with file paths and line numbers.\",\"name\":\"grep\",\"output_schema\":null,\"parameters\":{\"type\":\"object\",\"required\":[\"pattern\"],\"properties\":{\"pattern\":{\"type\":\"string\",\"description\":\"Search pattern (regex)\"},\"path\":{\"type\":\"string\",\"description\":\"Directory or file to search (default: .)\"},\"include\":{\"type\":\"string\",\"description\":\"Glob filter, e.g. \\\"*.ts\\\"\"}}},\"strict\":false},{\"type\":\"function\",\"description\":\"Find files by filename pattern using shell find -name semantics. Returns matching file paths.\",\"name\":\"glob\",\"output_schema\":null,\"parameters\":{\"type\":\"object\",\"required\":[\"pattern\"],\"properties\":{\"pattern\":{\"type\":\"string\",\"description\":\"Filename pattern, e.g. \\\"*.ts\\\"\"},\"path\":{\"type\":\"string\",\"description\":\"Directory to search in (default: .)\"}}},\"strict\":false},{\"type\":\"function\",\"description\":\"Delegate a focused task to a detached child agent with its own context. Use this for independent research, file exploration, or parallel work. The task returns only its final answer to this conversation. No subagents are currently defined.\",\"name\":\"task\",\"output_schema\":null,\"parameters\":{\"type\":\"object\",\"required\":[\"prompt\"],\"properties\":{\"description\":{\"type\":\"string\",\"description\":\"Short human-readable label for the delegated work\"},\"prompt\":{\"type\":\"string\",\"description\":\"Focused instructions for the child agent\"},\"agent\":{\"type\":\"string\",\"description\":\"Declared subagent to use for the child agent\"},\"cwd\":{\"type\":\"string\",\"description\":\"Working directory for the child agent. AGENTS.md and skills are discovered from here.\"}}},\"strict\":false}],\"top_logprobs\":0,\"top_p\":0.98,\"truncation\":\"disabled\",\"usage\":null,\"user\":null,\"metadata\":{}},\"sequence_number\":1}", - "event: response.output_item.added\ndata: {\"type\":\"response.output_item.added\",\"item\":{\"id\":\"fc_054a16a2f2ea3217016a705b6e90f481a39bc44496fbadf441\",\"type\":\"function_call\",\"status\":\"in_progress\",\"arguments\":\"\",\"call_id\":\"call_xQybhnQYuflmfp7gyAScVKjY\",\"name\":\"bash\"},\"output_index\":0,\"sequence_number\":2}", - "event: response.function_call_arguments.delta\ndata: {\"type\":\"response.function_call_arguments.delta\",\"delta\":\"{\\\"\",\"item_id\":\"fc_054a16a2f2ea3217016a705b6e90f481a39bc44496fbadf441\",\"obfuscation\":\"IsfKDqHlaKpPlc\",\"output_index\":0,\"sequence_number\":3}", - "event: response.function_call_arguments.delta\ndata: {\"type\":\"response.function_call_arguments.delta\",\"delta\":\"command\",\"item_id\":\"fc_054a16a2f2ea3217016a705b6e90f481a39bc44496fbadf441\",\"obfuscation\":\"05wTcU2vE\",\"output_index\":0,\"sequence_number\":4}", - "event: response.function_call_arguments.delta\ndata: {\"type\":\"response.function_call_arguments.delta\",\"delta\":\"\\\":\\\"\",\"item_id\":\"fc_054a16a2f2ea3217016a705b6e90f481a39bc44496fbadf441\",\"obfuscation\":\"I7n2P9cIUaGZ5\",\"output_index\":0,\"sequence_number\":5}", - "event: response.function_call_arguments.delta\ndata: {\"type\":\"response.function_call_arguments.delta\",\"delta\":\"node\",\"item_id\":\"fc_054a16a2f2ea3217016a705b6e90f481a39bc44496fbadf441\",\"obfuscation\":\"66No8n2S3lQV\",\"output_index\":0,\"sequence_number\":6}", - "event: response.function_call_arguments.delta\ndata: {\"type\":\"response.function_call_arguments.delta\",\"delta\":\" -\",\"item_id\":\"fc_054a16a2f2ea3217016a705b6e90f481a39bc44496fbadf441\",\"obfuscation\":\"pfQfFqmrd7Cttd\",\"output_index\":0,\"sequence_number\":7}", - "event: response.function_call_arguments.delta\ndata: {\"type\":\"response.function_call_arguments.delta\",\"delta\":\"v\",\"item_id\":\"fc_054a16a2f2ea3217016a705b6e90f481a39bc44496fbadf441\",\"obfuscation\":\"MsjXV30TNBXaVIN\",\"output_index\":0,\"sequence_number\":8}", - "event: response.function_call_arguments.delta\ndata: {\"type\":\"response.function_call_arguments.delta\",\"delta\":\" &&\",\"item_id\":\"fc_054a16a2f2ea3217016a705b6e90f481a39bc44496fbadf441\",\"obfuscation\":\"sbVmMrYzWCmWN\",\"output_index\":0,\"sequence_number\":9}", - "event: response.function_call_arguments.delta\ndata: {\"type\":\"response.function_call_arguments.delta\",\"delta\":\" ls\",\"item_id\":\"fc_054a16a2f2ea3217016a705b6e90f481a39bc44496fbadf441\",\"obfuscation\":\"f3AOiA6bYx0WA\",\"output_index\":0,\"sequence_number\":10}", - "event: response.function_call_arguments.delta\ndata: {\"type\":\"response.function_call_arguments.delta\",\"delta\":\" -\",\"item_id\":\"fc_054a16a2f2ea3217016a705b6e90f481a39bc44496fbadf441\",\"obfuscation\":\"YwMIVfxBwLPyPz\",\"output_index\":0,\"sequence_number\":11}", - "event: response.function_call_arguments.delta\ndata: {\"type\":\"response.function_call_arguments.delta\",\"delta\":\"la\",\"item_id\":\"fc_054a16a2f2ea3217016a705b6e90f481a39bc44496fbadf441\",\"obfuscation\":\"yosVAZ9d354t0E\",\"output_index\":0,\"sequence_number\":12}", - "event: response.function_call_arguments.delta\ndata: {\"type\":\"response.function_call_arguments.delta\",\"delta\":\" /\",\"item_id\":\"fc_054a16a2f2ea3217016a705b6e90f481a39bc44496fbadf441\",\"obfuscation\":\"RtzYTt0vUd1KL5\",\"output_index\":0,\"sequence_number\":13}", - "event: response.function_call_arguments.delta\ndata: {\"type\":\"response.function_call_arguments.delta\",\"delta\":\"ver\",\"item_id\":\"fc_054a16a2f2ea3217016a705b6e90f481a39bc44496fbadf441\",\"obfuscation\":\"UZCqFJvIdZJfi\",\"output_index\":0,\"sequence_number\":14}", - "event: response.function_call_arguments.delta\ndata: {\"type\":\"response.function_call_arguments.delta\",\"delta\":\"cel\",\"item_id\":\"fc_054a16a2f2ea3217016a705b6e90f481a39bc44496fbadf441\",\"obfuscation\":\"ob8WdL1Zrd6z4\",\"output_index\":0,\"sequence_number\":15}", - "event: response.function_call_arguments.delta\ndata: {\"type\":\"response.function_call_arguments.delta\",\"delta\":\"/s\",\"item_id\":\"fc_054a16a2f2ea3217016a705b6e90f481a39bc44496fbadf441\",\"obfuscation\":\"SHFL6SDvDru2gO\",\"output_index\":0,\"sequence_number\":16}", - "event: response.function_call_arguments.delta\ndata: {\"type\":\"response.function_call_arguments.delta\",\"delta\":\"andbox\",\"item_id\":\"fc_054a16a2f2ea3217016a705b6e90f481a39bc44496fbadf441\",\"obfuscation\":\"SlANW27QBQ\",\"output_index\":0,\"sequence_number\":17}", - "event: response.function_call_arguments.delta\ndata: {\"type\":\"response.function_call_arguments.delta\",\"delta\":\"/\",\"item_id\":\"fc_054a16a2f2ea3217016a705b6e90f481a39bc44496fbadf441\",\"obfuscation\":\"Llwwd75yCVN6cFY\",\"output_index\":0,\"sequence_number\":18}", - "event: response.function_call_arguments.delta\ndata: {\"type\":\"response.function_call_arguments.delta\",\"delta\":\"brain\",\"item_id\":\"fc_054a16a2f2ea3217016a705b6e90f481a39bc44496fbadf441\",\"obfuscation\":\"Sp2PfwThtXK\",\"output_index\":0,\"sequence_number\":19}", - "event: response.function_call_arguments.delta\ndata: {\"type\":\"response.function_call_arguments.delta\",\"delta\":\"trust\",\"item_id\":\"fc_054a16a2f2ea3217016a705b6e90f481a39bc44496fbadf441\",\"obfuscation\":\"WTr9EL0CMfU\",\"output_index\":0,\"sequence_number\":20}", - "event: response.function_call_arguments.delta\ndata: {\"type\":\"response.function_call_arguments.delta\",\"delta\":\"data\",\"item_id\":\"fc_054a16a2f2ea3217016a705b6e90f481a39bc44496fbadf441\",\"obfuscation\":\"iXd0y36BKeMz\",\"output_index\":0,\"sequence_number\":21}", - "event: response.function_call_arguments.delta\ndata: {\"type\":\"response.function_call_arguments.delta\",\"delta\":\"/\",\"item_id\":\"fc_054a16a2f2ea3217016a705b6e90f481a39bc44496fbadf441\",\"obfuscation\":\"TMVJ4avBWNU1PMg\",\"output_index\":0,\"sequence_number\":22}", - "event: response.function_call_arguments.delta\ndata: {\"type\":\"response.function_call_arguments.delta\",\"delta\":\"brain\",\"item_id\":\"fc_054a16a2f2ea3217016a705b6e90f481a39bc44496fbadf441\",\"obfuscation\":\"gGYEjs7floV\",\"output_index\":0,\"sequence_number\":23}", - "event: response.function_call_arguments.delta\ndata: {\"type\":\"response.function_call_arguments.delta\",\"delta\":\"trust\",\"item_id\":\"fc_054a16a2f2ea3217016a705b6e90f481a39bc44496fbadf441\",\"obfuscation\":\"nJfWpNMEeYh\",\"output_index\":0,\"sequence_number\":24}", - "event: response.function_call_arguments.delta\ndata: {\"type\":\"response.function_call_arguments.delta\",\"delta\":\"-sdk\",\"item_id\":\"fc_054a16a2f2ea3217016a705b6e90f481a39bc44496fbadf441\",\"obfuscation\":\"VnnFujipwf0B\",\"output_index\":0,\"sequence_number\":25}", - "event: response.function_call_arguments.delta\ndata: {\"type\":\"response.function_call_arguments.delta\",\"delta\":\"-j\",\"item_id\":\"fc_054a16a2f2ea3217016a705b6e90f481a39bc44496fbadf441\",\"obfuscation\":\"AAXM8t27HpiY8a\",\"output_index\":0,\"sequence_number\":26}", - "event: response.function_call_arguments.delta\ndata: {\"type\":\"response.function_call_arguments.delta\",\"delta\":\"avascript\",\"item_id\":\"fc_054a16a2f2ea3217016a705b6e90f481a39bc44496fbadf441\",\"obfuscation\":\"51XrioX\",\"output_index\":0,\"sequence_number\":27}", - "event: response.function_call_arguments.delta\ndata: {\"type\":\"response.function_call_arguments.delta\",\"delta\":\"/e\",\"item_id\":\"fc_054a16a2f2ea3217016a705b6e90f481a39bc44496fbadf441\",\"obfuscation\":\"zAaUUwSEMO3Bxs\",\"output_index\":0,\"sequence_number\":28}", - "event: response.function_call_arguments.delta\ndata: {\"type\":\"response.function_call_arguments.delta\",\"delta\":\"2\",\"item_id\":\"fc_054a16a2f2ea3217016a705b6e90f481a39bc44496fbadf441\",\"obfuscation\":\"vyBpS3fu1undfoT\",\"output_index\":0,\"sequence_number\":29}", - "event: response.function_call_arguments.delta\ndata: {\"type\":\"response.function_call_arguments.delta\",\"delta\":\"e\",\"item_id\":\"fc_054a16a2f2ea3217016a705b6e90f481a39bc44496fbadf441\",\"obfuscation\":\"myDkZQqFtQPgErv\",\"output_index\":0,\"sequence_number\":30}", - "event: response.function_call_arguments.delta\ndata: {\"type\":\"response.function_call_arguments.delta\",\"delta\":\"/sc\",\"item_id\":\"fc_054a16a2f2ea3217016a705b6e90f481a39bc44496fbadf441\",\"obfuscation\":\"7jYdsKtAqGJsx\",\"output_index\":0,\"sequence_number\":31}", - "event: response.function_call_arguments.delta\ndata: {\"type\":\"response.function_call_arguments.delta\",\"delta\":\"enarios\",\"item_id\":\"fc_054a16a2f2ea3217016a705b6e90f481a39bc44496fbadf441\",\"obfuscation\":\"FgfnvD0oV\",\"output_index\":0,\"sequence_number\":32}", - "event: response.function_call_arguments.delta\ndata: {\"type\":\"response.function_call_arguments.delta\",\"delta\":\"/fl\",\"item_id\":\"fc_054a16a2f2ea3217016a705b6e90f481a39bc44496fbadf441\",\"obfuscation\":\"ECM3KkIZxcc2j\",\"output_index\":0,\"sequence_number\":33}", - "event: response.function_call_arguments.delta\ndata: {\"type\":\"response.function_call_arguments.delta\",\"delta\":\"ue\",\"item_id\":\"fc_054a16a2f2ea3217016a705b6e90f481a39bc44496fbadf441\",\"obfuscation\":\"XIzDZYr4JpAjhV\",\"output_index\":0,\"sequence_number\":34}", - "event: response.function_call_arguments.delta\ndata: {\"type\":\"response.function_call_arguments.delta\",\"delta\":\"-in\",\"item_id\":\"fc_054a16a2f2ea3217016a705b6e90f481a39bc44496fbadf441\",\"obfuscation\":\"VudZPLnjNH5PJ\",\"output_index\":0,\"sequence_number\":35}", - "event: response.function_call_arguments.delta\ndata: {\"type\":\"response.function_call_arguments.delta\",\"delta\":\"strument\",\"item_id\":\"fc_054a16a2f2ea3217016a705b6e90f481a39bc44496fbadf441\",\"obfuscation\":\"i2iTyJpP\",\"output_index\":0,\"sequence_number\":36}", - "event: response.function_call_arguments.delta\ndata: {\"type\":\"response.function_call_arguments.delta\",\"delta\":\"ation\",\"item_id\":\"fc_054a16a2f2ea3217016a705b6e90f481a39bc44496fbadf441\",\"obfuscation\":\"ZtblFC8j0Wk\",\"output_index\":0,\"sequence_number\":37}", - "event: response.function_call_arguments.delta\ndata: {\"type\":\"response.function_call_arguments.delta\",\"delta\":\" &&\",\"item_id\":\"fc_054a16a2f2ea3217016a705b6e90f481a39bc44496fbadf441\",\"obfuscation\":\"1l1tQ40NrGj8E\",\"output_index\":0,\"sequence_number\":38}", - "event: response.function_call_arguments.delta\ndata: {\"type\":\"response.function_call_arguments.delta\",\"delta\":\" cat\",\"item_id\":\"fc_054a16a2f2ea3217016a705b6e90f481a39bc44496fbadf441\",\"obfuscation\":\"41U8EU6Ib9cg\",\"output_index\":0,\"sequence_number\":39}", - "event: response.function_call_arguments.delta\ndata: {\"type\":\"response.function_call_arguments.delta\",\"delta\":\" /\",\"item_id\":\"fc_054a16a2f2ea3217016a705b6e90f481a39bc44496fbadf441\",\"obfuscation\":\"cglsLI76yH9ClQ\",\"output_index\":0,\"sequence_number\":40}", - "event: response.function_call_arguments.delta\ndata: {\"type\":\"response.function_call_arguments.delta\",\"delta\":\"ver\",\"item_id\":\"fc_054a16a2f2ea3217016a705b6e90f481a39bc44496fbadf441\",\"obfuscation\":\"qvk54uq2LZnZa\",\"output_index\":0,\"sequence_number\":41}", - "event: response.function_call_arguments.delta\ndata: {\"type\":\"response.function_call_arguments.delta\",\"delta\":\"cel\",\"item_id\":\"fc_054a16a2f2ea3217016a705b6e90f481a39bc44496fbadf441\",\"obfuscation\":\"aovMJj1uFOFEM\",\"output_index\":0,\"sequence_number\":42}", - "event: response.function_call_arguments.delta\ndata: {\"type\":\"response.function_call_arguments.delta\",\"delta\":\"/s\",\"item_id\":\"fc_054a16a2f2ea3217016a705b6e90f481a39bc44496fbadf441\",\"obfuscation\":\"NQpTAggULtv8zO\",\"output_index\":0,\"sequence_number\":43}", - "event: response.function_call_arguments.delta\ndata: {\"type\":\"response.function_call_arguments.delta\",\"delta\":\"andbox\",\"item_id\":\"fc_054a16a2f2ea3217016a705b6e90f481a39bc44496fbadf441\",\"obfuscation\":\"5837PUGg7V\",\"output_index\":0,\"sequence_number\":44}", - "event: response.function_call_arguments.delta\ndata: {\"type\":\"response.function_call_arguments.delta\",\"delta\":\"/\",\"item_id\":\"fc_054a16a2f2ea3217016a705b6e90f481a39bc44496fbadf441\",\"obfuscation\":\"JhMbsWIyEoPXZRB\",\"output_index\":0,\"sequence_number\":45}", - "event: response.function_call_arguments.delta\ndata: {\"type\":\"response.function_call_arguments.delta\",\"delta\":\"brain\",\"item_id\":\"fc_054a16a2f2ea3217016a705b6e90f481a39bc44496fbadf441\",\"obfuscation\":\"wUKGqlUZBXL\",\"output_index\":0,\"sequence_number\":46}", - "event: response.function_call_arguments.delta\ndata: {\"type\":\"response.function_call_arguments.delta\",\"delta\":\"trust\",\"item_id\":\"fc_054a16a2f2ea3217016a705b6e90f481a39bc44496fbadf441\",\"obfuscation\":\"5fqXYCtg0Gt\",\"output_index\":0,\"sequence_number\":47}", - "event: response.function_call_arguments.delta\ndata: {\"type\":\"response.function_call_arguments.delta\",\"delta\":\"data\",\"item_id\":\"fc_054a16a2f2ea3217016a705b6e90f481a39bc44496fbadf441\",\"obfuscation\":\"4Q1cI53ucyWL\",\"output_index\":0,\"sequence_number\":48}", - "event: response.function_call_arguments.delta\ndata: {\"type\":\"response.function_call_arguments.delta\",\"delta\":\"/\",\"item_id\":\"fc_054a16a2f2ea3217016a705b6e90f481a39bc44496fbadf441\",\"obfuscation\":\"abd5F6mWTBD3FqR\",\"output_index\":0,\"sequence_number\":49}", - "event: response.function_call_arguments.delta\ndata: {\"type\":\"response.function_call_arguments.delta\",\"delta\":\"brain\",\"item_id\":\"fc_054a16a2f2ea3217016a705b6e90f481a39bc44496fbadf441\",\"obfuscation\":\"YwaHJecYa6Z\",\"output_index\":0,\"sequence_number\":50}", - "event: response.function_call_arguments.delta\ndata: {\"type\":\"response.function_call_arguments.delta\",\"delta\":\"trust\",\"item_id\":\"fc_054a16a2f2ea3217016a705b6e90f481a39bc44496fbadf441\",\"obfuscation\":\"IZiyFBgwGNk\",\"output_index\":0,\"sequence_number\":51}", - "event: response.function_call_arguments.delta\ndata: {\"type\":\"response.function_call_arguments.delta\",\"delta\":\"-sdk\",\"item_id\":\"fc_054a16a2f2ea3217016a705b6e90f481a39bc44496fbadf441\",\"obfuscation\":\"DefferqgDs1G\",\"output_index\":0,\"sequence_number\":52}", - "event: response.function_call_arguments.delta\ndata: {\"type\":\"response.function_call_arguments.delta\",\"delta\":\"-j\",\"item_id\":\"fc_054a16a2f2ea3217016a705b6e90f481a39bc44496fbadf441\",\"obfuscation\":\"jf9cr3akDjVA60\",\"output_index\":0,\"sequence_number\":53}", - "event: response.function_call_arguments.delta\ndata: {\"type\":\"response.function_call_arguments.delta\",\"delta\":\"avascript\",\"item_id\":\"fc_054a16a2f2ea3217016a705b6e90f481a39bc44496fbadf441\",\"obfuscation\":\"XaGvBNu\",\"output_index\":0,\"sequence_number\":54}", - "event: response.function_call_arguments.delta\ndata: {\"type\":\"response.function_call_arguments.delta\",\"delta\":\"/e\",\"item_id\":\"fc_054a16a2f2ea3217016a705b6e90f481a39bc44496fbadf441\",\"obfuscation\":\"5UiGHyP1PGgpsP\",\"output_index\":0,\"sequence_number\":55}", - "event: response.function_call_arguments.delta\ndata: {\"type\":\"response.function_call_arguments.delta\",\"delta\":\"2\",\"item_id\":\"fc_054a16a2f2ea3217016a705b6e90f481a39bc44496fbadf441\",\"obfuscation\":\"WKaJqwMQWdtBVYa\",\"output_index\":0,\"sequence_number\":56}", - "event: response.function_call_arguments.delta\ndata: {\"type\":\"response.function_call_arguments.delta\",\"delta\":\"e\",\"item_id\":\"fc_054a16a2f2ea3217016a705b6e90f481a39bc44496fbadf441\",\"obfuscation\":\"DiOuTdnwYCCUpLq\",\"output_index\":0,\"sequence_number\":57}", - "event: response.function_call_arguments.delta\ndata: {\"type\":\"response.function_call_arguments.delta\",\"delta\":\"/sc\",\"item_id\":\"fc_054a16a2f2ea3217016a705b6e90f481a39bc44496fbadf441\",\"obfuscation\":\"JNIox4EYrQqid\",\"output_index\":0,\"sequence_number\":58}", - "event: response.function_call_arguments.delta\ndata: {\"type\":\"response.function_call_arguments.delta\",\"delta\":\"enarios\",\"item_id\":\"fc_054a16a2f2ea3217016a705b6e90f481a39bc44496fbadf441\",\"obfuscation\":\"rQexF5fiq\",\"output_index\":0,\"sequence_number\":59}", - "event: response.function_call_arguments.delta\ndata: {\"type\":\"response.function_call_arguments.delta\",\"delta\":\"/fl\",\"item_id\":\"fc_054a16a2f2ea3217016a705b6e90f481a39bc44496fbadf441\",\"obfuscation\":\"CxjB3QQkwwxPq\",\"output_index\":0,\"sequence_number\":60}", - "event: response.function_call_arguments.delta\ndata: {\"type\":\"response.function_call_arguments.delta\",\"delta\":\"ue\",\"item_id\":\"fc_054a16a2f2ea3217016a705b6e90f481a39bc44496fbadf441\",\"obfuscation\":\"KvqTdaudMieVrZ\",\"output_index\":0,\"sequence_number\":61}", - "event: response.function_call_arguments.delta\ndata: {\"type\":\"response.function_call_arguments.delta\",\"delta\":\"-in\",\"item_id\":\"fc_054a16a2f2ea3217016a705b6e90f481a39bc44496fbadf441\",\"obfuscation\":\"hqrEuju9Hubvj\",\"output_index\":0,\"sequence_number\":62}", - "event: response.function_call_arguments.delta\ndata: {\"type\":\"response.function_call_arguments.delta\",\"delta\":\"strument\",\"item_id\":\"fc_054a16a2f2ea3217016a705b6e90f481a39bc44496fbadf441\",\"obfuscation\":\"xqdV3tk5\",\"output_index\":0,\"sequence_number\":63}", - "event: response.function_call_arguments.delta\ndata: {\"type\":\"response.function_call_arguments.delta\",\"delta\":\"ation\",\"item_id\":\"fc_054a16a2f2ea3217016a705b6e90f481a39bc44496fbadf441\",\"obfuscation\":\"finNWbOcOSK\",\"output_index\":0,\"sequence_number\":64}", - "event: response.function_call_arguments.delta\ndata: {\"type\":\"response.function_call_arguments.delta\",\"delta\":\"/package\",\"item_id\":\"fc_054a16a2f2ea3217016a705b6e90f481a39bc44496fbadf441\",\"obfuscation\":\"w5O538dR\",\"output_index\":0,\"sequence_number\":65}", - "event: response.function_call_arguments.delta\ndata: {\"type\":\"response.function_call_arguments.delta\",\"delta\":\".json\",\"item_id\":\"fc_054a16a2f2ea3217016a705b6e90f481a39bc44496fbadf441\",\"obfuscation\":\"5lixmLK9osS\",\"output_index\":0,\"sequence_number\":66}", - "event: response.function_call_arguments.delta\ndata: {\"type\":\"response.function_call_arguments.delta\",\"delta\":\" \",\"item_id\":\"fc_054a16a2f2ea3217016a705b6e90f481a39bc44496fbadf441\",\"obfuscation\":\"UfcwAy15pK3UyDX\",\"output_index\":0,\"sequence_number\":67}", - "event: response.function_call_arguments.delta\ndata: {\"type\":\"response.function_call_arguments.delta\",\"delta\":\"2\",\"item_id\":\"fc_054a16a2f2ea3217016a705b6e90f481a39bc44496fbadf441\",\"obfuscation\":\"8kuEminD3O9nEe6\",\"output_index\":0,\"sequence_number\":68}", - "event: response.function_call_arguments.delta\ndata: {\"type\":\"response.function_call_arguments.delta\",\"delta\":\">/\",\"item_id\":\"fc_054a16a2f2ea3217016a705b6e90f481a39bc44496fbadf441\",\"obfuscation\":\"0ujGWvOIOaHg9t\",\"output_index\":0,\"sequence_number\":69}", - "event: response.function_call_arguments.delta\ndata: {\"type\":\"response.function_call_arguments.delta\",\"delta\":\"dev\",\"item_id\":\"fc_054a16a2f2ea3217016a705b6e90f481a39bc44496fbadf441\",\"obfuscation\":\"l87mZp3YVC56A\",\"output_index\":0,\"sequence_number\":70}", - "event: response.function_call_arguments.delta\ndata: {\"type\":\"response.function_call_arguments.delta\",\"delta\":\"/null\",\"item_id\":\"fc_054a16a2f2ea3217016a705b6e90f481a39bc44496fbadf441\",\"obfuscation\":\"z1OHwKMfyTN\",\"output_index\":0,\"sequence_number\":71}", - "event: response.function_call_arguments.delta\ndata: {\"type\":\"response.function_call_arguments.delta\",\"delta\":\" ||\",\"item_id\":\"fc_054a16a2f2ea3217016a705b6e90f481a39bc44496fbadf441\",\"obfuscation\":\"Nd0PVbdoXgVdP\",\"output_index\":0,\"sequence_number\":72}", - "event: response.function_call_arguments.delta\ndata: {\"type\":\"response.function_call_arguments.delta\",\"delta\":\" true\",\"item_id\":\"fc_054a16a2f2ea3217016a705b6e90f481a39bc44496fbadf441\",\"obfuscation\":\"gYO8swE9D00\",\"output_index\":0,\"sequence_number\":73}", - "event: response.function_call_arguments.delta\ndata: {\"type\":\"response.function_call_arguments.delta\",\"delta\":\"\\\",\\\"\",\"item_id\":\"fc_054a16a2f2ea3217016a705b6e90f481a39bc44496fbadf441\",\"obfuscation\":\"dAlT06VEGUSd4\",\"output_index\":0,\"sequence_number\":74}", - "event: response.function_call_arguments.delta\ndata: {\"type\":\"response.function_call_arguments.delta\",\"delta\":\"timeout\",\"item_id\":\"fc_054a16a2f2ea3217016a705b6e90f481a39bc44496fbadf441\",\"obfuscation\":\"9zDDLkKve\",\"output_index\":0,\"sequence_number\":75}", - "event: response.function_call_arguments.delta\ndata: {\"type\":\"response.function_call_arguments.delta\",\"delta\":\"\\\":\",\"item_id\":\"fc_054a16a2f2ea3217016a705b6e90f481a39bc44496fbadf441\",\"obfuscation\":\"bOgN2cbK9QPYUV\",\"output_index\":0,\"sequence_number\":76}", - "event: response.function_call_arguments.delta\ndata: {\"type\":\"response.function_call_arguments.delta\",\"delta\":\"100\",\"item_id\":\"fc_054a16a2f2ea3217016a705b6e90f481a39bc44496fbadf441\",\"obfuscation\":\"2ycAaEUguB849\",\"output_index\":0,\"sequence_number\":77}", - "event: response.function_call_arguments.delta\ndata: {\"type\":\"response.function_call_arguments.delta\",\"delta\":\"000\",\"item_id\":\"fc_054a16a2f2ea3217016a705b6e90f481a39bc44496fbadf441\",\"obfuscation\":\"jCkJ68Rcb58dt\",\"output_index\":0,\"sequence_number\":78}", - "event: response.function_call_arguments.delta\ndata: {\"type\":\"response.function_call_arguments.delta\",\"delta\":\"}\",\"item_id\":\"fc_054a16a2f2ea3217016a705b6e90f481a39bc44496fbadf441\",\"obfuscation\":\"bbF8wOnG3Zvtgvs\",\"output_index\":0,\"sequence_number\":79}", - "event: response.function_call_arguments.done\ndata: {\"type\":\"response.function_call_arguments.done\",\"arguments\":\"{\\\"command\\\":\\\"cat ./package.json\\\",\\\"timeout\\\":100000}\",\"item_id\":\"fc_054a16a2f2ea3217016a705b6e90f481a39bc44496fbadf441\",\"output_index\":0,\"sequence_number\":80}", - "event: response.output_item.done\ndata: {\"type\":\"response.output_item.done\",\"item\":{\"id\":\"fc_054a16a2f2ea3217016a705b6e90f481a39bc44496fbadf441\",\"type\":\"function_call\",\"status\":\"completed\",\"arguments\":\"{\\\"command\\\":\\\"cat ./package.json\\\",\\\"timeout\\\":100000}\",\"call_id\":\"call_xQybhnQYuflmfp7gyAScVKjY\",\"name\":\"bash\"},\"output_index\":0,\"sequence_number\":81}", - "event: response.completed\ndata: {\"type\":\"response.completed\",\"response\":{\"id\":\"resp_054a16a2f2ea3217016a705b6e34cc81a383184de2af428d09\",\"object\":\"response\",\"created_at\":1785748334,\"status\":\"completed\",\"background\":false,\"completed_at\":1785748335,\"error\":null,\"frequency_penalty\":0,\"incomplete_details\":null,\"instructions\":null,\"max_output_tokens\":null,\"max_tool_calls\":null,\"model\":\"gpt-5.4-nano-2026-03-17\",\"moderation\":null,\"output\":[{\"id\":\"fc_054a16a2f2ea3217016a705b6e90f481a39bc44496fbadf441\",\"type\":\"function_call\",\"status\":\"completed\",\"arguments\":\"{\\\"command\\\":\\\"cat ./package.json\\\",\\\"timeout\\\":100000}\",\"call_id\":\"call_xQybhnQYuflmfp7gyAScVKjY\",\"name\":\"bash\"}],\"parallel_tool_calls\":true,\"presence_penalty\":0,\"previous_response_id\":null,\"prompt_cache_key\":\"workflow:instrumentation:01KZ3E9TWTAEK4P4P3F63HKAKC::default::ta\",\"prompt_cache_retention\":\"24h\",\"reasoning\":{\"context\":\"current_turn\",\"effort\":\"none\",\"mode\":\"standard\",\"summary\":null},\"safety_identifier\":null,\"service_tier\":\"default\",\"store\":false,\"temperature\":1,\"text\":{\"format\":{\"type\":\"text\"},\"verbosity\":\"medium\"},\"tool_choice\":\"auto\",\"tool_usage\":{\"image_gen\":{\"input_tokens\":0,\"input_tokens_details\":{\"image_tokens\":0,\"text_tokens\":0},\"output_tokens\":0,\"output_tokens_details\":{\"image_tokens\":0,\"text_tokens\":0},\"total_tokens\":0},\"web_search\":{\"num_requests\":0}},\"tools\":[{\"type\":\"function\",\"description\":\"Read a file or list a directory. For files, output is truncated to 2000 lines or 50KB — use offset/limit for large files. For directories, returns the list of entries.\",\"name\":\"read\",\"output_schema\":null,\"parameters\":{\"type\":\"object\",\"required\":[\"path\"],\"properties\":{\"path\":{\"type\":\"string\",\"description\":\"Path to the file to read\"},\"offset\":{\"type\":\"number\",\"description\":\"Line number to start from (1-indexed)\"},\"limit\":{\"type\":\"number\",\"description\":\"Maximum number of lines to read\"}}},\"strict\":false},{\"type\":\"function\",\"description\":\"Write content to a file. Creates the file and parent directories if they do not exist.\",\"name\":\"write\",\"output_schema\":null,\"parameters\":{\"type\":\"object\",\"required\":[\"path\",\"content\"],\"properties\":{\"path\":{\"type\":\"string\",\"description\":\"Path to the file to write\"},\"content\":{\"type\":\"string\",\"description\":\"Content to write to the file\"}}},\"strict\":false},{\"type\":\"function\",\"description\":\"Edit a file using exact text replacement. The oldText must match a unique region of the file. Use replaceAll to replace all occurrences.\",\"name\":\"edit\",\"output_schema\":null,\"parameters\":{\"type\":\"object\",\"required\":[\"path\",\"oldText\",\"newText\"],\"properties\":{\"path\":{\"type\":\"string\",\"description\":\"Path to the file to edit\"},\"oldText\":{\"type\":\"string\",\"description\":\"Exact text to find (must be unique)\"},\"newText\":{\"type\":\"string\",\"description\":\"Replacement text\"},\"replaceAll\":{\"type\":\"boolean\",\"description\":\"Replace all occurrences\"}}},\"strict\":false},{\"type\":\"function\",\"description\":\"Execute a bash command. Returns stdout and stderr. Output is truncated to the last 2000 lines or 50KB.\",\"name\":\"bash\",\"output_schema\":null,\"parameters\":{\"type\":\"object\",\"required\":[\"command\"],\"properties\":{\"command\":{\"type\":\"string\",\"description\":\"Bash command to execute\"},\"timeout\":{\"type\":\"number\",\"description\":\"Timeout in seconds\"}}},\"strict\":false},{\"type\":\"function\",\"description\":\"Search file contents for a regex pattern. Returns matching lines with file paths and line numbers.\",\"name\":\"grep\",\"output_schema\":null,\"parameters\":{\"type\":\"object\",\"required\":[\"pattern\"],\"properties\":{\"pattern\":{\"type\":\"string\",\"description\":\"Search pattern (regex)\"},\"path\":{\"type\":\"string\",\"description\":\"Directory or file to search (default: .)\"},\"include\":{\"type\":\"string\",\"description\":\"Glob filter, e.g. \\\"*.ts\\\"\"}}},\"strict\":false},{\"type\":\"function\",\"description\":\"Find files by filename pattern using shell find -name semantics. Returns matching file paths.\",\"name\":\"glob\",\"output_schema\":null,\"parameters\":{\"type\":\"object\",\"required\":[\"pattern\"],\"properties\":{\"pattern\":{\"type\":\"string\",\"description\":\"Filename pattern, e.g. \\\"*.ts\\\"\"},\"path\":{\"type\":\"string\",\"description\":\"Directory to search in (default: .)\"}}},\"strict\":false},{\"type\":\"function\",\"description\":\"Delegate a focused task to a detached child agent with its own context. Use this for independent research, file exploration, or parallel work. The task returns only its final answer to this conversation. No subagents are currently defined.\",\"name\":\"task\",\"output_schema\":null,\"parameters\":{\"type\":\"object\",\"required\":[\"prompt\"],\"properties\":{\"description\":{\"type\":\"string\",\"description\":\"Short human-readable label for the delegated work\"},\"prompt\":{\"type\":\"string\",\"description\":\"Focused instructions for the child agent\"},\"agent\":{\"type\":\"string\",\"description\":\"Declared subagent to use for the child agent\"},\"cwd\":{\"type\":\"string\",\"description\":\"Working directory for the child agent. AGENTS.md and skills are discovered from here.\"}}},\"strict\":false}],\"top_logprobs\":0,\"top_p\":0.98,\"truncation\":\"disabled\",\"usage\":{\"input_tokens\":1882,\"input_tokens_details\":{\"cache_write_tokens\":0,\"cached_tokens\":1536},\"output_tokens\":90,\"output_tokens_details\":{\"reasoning_tokens\":0},\"total_tokens\":1972},\"user\":null,\"metadata\":{}},\"sequence_number\":82}" - ], - "kind": "sse" - }, - "headers": { - "access-control-expose-headers": "X-Request-ID, CF-Ray, CF-Ray", - "alt-svc": "h3=\":443\"; ma=86400", - "cf-cache-status": "DYNAMIC", - "cf-ray": "a25433108e2d7bb6-IAD", - "connection": "keep-alive", - "content-type": "text/event-stream; charset=utf-8", - "date": "Mon, 03 Aug 2026 09:12:14 GMT", - "openai-organization": "braintrust-data", - "openai-processing-ms": "170", - "openai-project": "proj_vsCSXafhhByzWOThMrJcZiw9", - "openai-version": "2020-10-01", - "server": "cloudflare", - "set-cookie": "[REDACTED]", - "strict-transport-security": "max-age=31536000; includeSubDomains; preload", - "transfer-encoding": "chunked", - "x-content-type-options": "nosniff", - "x-ratelimit-limit-requests": "30000", - "x-ratelimit-limit-tokens": "180000000", - "x-ratelimit-remaining-requests": "29999", - "x-ratelimit-remaining-tokens": "179997663", - "x-ratelimit-reset-requests": "2ms", - "x-ratelimit-reset-tokens": "0s", - "x-request-id": "req_be4542a23d5f4e17b4285d598ac2400b" - }, - "status": 200, - "statusText": "OK" - } - }, - { - "callIndex": 13, - "id": "eebc152ba05b8412", - "matchKey": "POST api.openai.com/v1/responses", - "recordedAt": "2026-08-03T09:12:16.175Z", - "request": { - "body": { - "kind": "json", - "value": { - "input": [ - { - "content": "You are running in headless mode with no human operator. Work autonomously — never ask questions, never wait for user input. Make your best judgment and proceed independently.\n\nYou are a deterministic Flue instrumentation test agent. Follow user instructions exactly. When asked for a marker, output only that marker and no extra text. When running a local skill file, read it yourself and do not delegate it to a task.\n\n## Available Skills\n\nThe following skills provide specialized instructions for specific tasks. When a task matches a skill description, activate that skill before proceeding so its full instructions are loaded. Skill instructions and supporting resources stay lazy until activation or explicit file reads.\n\n- **e2e-flue-skill** — Deterministic skill used by the Braintrust Flue instrumentation e2e test.\n\nDate: Mon, Aug 3, 2026\nWorking directory: .\n\nDirectory structure:\n.agents\n.flue\n.flue-build\n__cassettes__\n__snapshots__\nassertions.ts\ncassette-filter.mjs\nconstants.mjs\ndist\nnode_modules\npackage.json\npnpm-lock.yaml\nscenario.cli.mjs\nscenario.impl.mjs\nscenario.mjs\nscenario.test.ts\nscenario.ts\nscenario.v2.ts\nv2-assertions.ts\nversions", - "role": "developer" - }, - { - "content": [ - { - "text": "Run the skill named \"e2e-flue-skill\" with arguments {\"marker\":\"SKILL_DONE\"}. Output only the marker and no extra text when finished.", - "type": "input_text" - } - ], - "role": "user" - }, - { - "arguments": "{\"description\":\"Run e2e-flue-skill with marker SKILL_DONE\",\"prompt\":\"Run the skill named \\\"e2e-flue-skill\\\" with arguments {\\\"marker\\\":\\\"SKILL_DONE\\\"}. Ensure completion and report the final output marker only.\",\"agent\":\"e2e-flue-skill\",\"cwd\":\".\"}", - "call_id": "call_w6pyYZgnhlSOAXul75DAG1Cj", - "id": "fc_054a16a2f2ea3217016a705b64fe5481a390185c9576df12b6", - "name": "task", - "type": "function_call" - }, - { - "call_id": "call_w6pyYZgnhlSOAXul75DAG1Cj", - "output": "[flue] Subagent \"e2e-flue-skill\" is not declared. Available: (none).", - "type": "function_call_output" - }, - { - "arguments": "{\"command\":\"find . -maxdepth 4 -name AGENTS.md -print\",\"timeout\":100000}", - "call_id": "call_6zEBnJRmXFIkGFwNjWLZP4uC", - "id": "fc_054a16a2f2ea3217016a705b66460481a3a5de06df603e35ee", - "name": "bash", - "type": "function_call" - }, - { - "call_id": "call_6zEBnJRmXFIkGFwNjWLZP4uC", - "output": "total 4\ndrwxr-xr-x 3 vercel-sandbox vercel-sandbox 20 Aug 3 09:10 .\ndrwxr-xr-x 10 vercel-sandbox vercel-sandbox 4096 Aug 3 09:11 ..\ndrwxr-xr-x 3 vercel-sandbox vercel-sandbox 28 Aug 3 09:10 skills", - "type": "function_call_output" - }, - { - "arguments": "{\"command\":\"find . -maxdepth 4 -type f -name AGENTS.md -print -exec sed -n '1,200p' {} \\\\;\",\"timeout\":100000}", - "call_id": "call_jQUCie0ZgaXUrZCDcezp0msl", - "id": "fc_054a16a2f2ea3217016a705b67575881a3aacde02b9053a321", - "name": "bash", - "type": "function_call" - }, - { - "call_id": "call_jQUCie0ZgaXUrZCDcezp0msl", - "output": "(no output)", - "type": "function_call_output" - }, - { - "arguments": "{\"command\":\"find . -maxdepth 6 -type f -name 'AGENTS.md' -print\",\"timeout\":100000}", - "call_id": "call_NgMSUb6wIR0GAf9U8LXERtBQ", - "id": "fc_054a16a2f2ea3217016a705b68c39881a3b1e518599236e41a", - "name": "bash", - "type": "function_call" - }, - { - "call_id": "call_NgMSUb6wIR0GAf9U8LXERtBQ", - "output": "(no output)", - "type": "function_call_output" - }, - { - "arguments": "{\"command\":\"find . -maxdepth 5 -type f -name '*flue*skill*' -o -name '*skill*' | head\",\"timeout\":100000}", - "call_id": "call_BXKiQ5KG8c42XPPp94zgcY3H", - "id": "fc_054a16a2f2ea3217016a705b69edd081a38c31583ec56eca5f", - "name": "bash", - "type": "function_call" - }, - { - "call_id": "call_BXKiQ5KG8c42XPPp94zgcY3H", - "output": "./.agents/skills\n./.agents/skills/e2e-flue-skill", - "type": "function_call_output" - }, - { - "arguments": "{\"command\":\"find ./.agents/skills/e2e-flue-skill -maxdepth 2 -type f -print\",\"timeout\":100000}", - "call_id": "call_Zmmrs64LFscpAHsIUaIfEcAN", - "id": "fc_054a16a2f2ea3217016a705b6b96cc81a3ac4761db24198a63", - "name": "bash", - "type": "function_call" - }, - { - "call_id": "call_Zmmrs64LFscpAHsIUaIfEcAN", - "output": "total 4\ndrwxr-xr-x 2 vercel-sandbox vercel-sandbox 22 Aug 3 07:55 .\ndrwxr-xr-x 3 vercel-sandbox vercel-sandbox 28 Aug 3 07:55 ..\n-rw-r--r-- 1 vercel-sandbox vercel-sandbox 194 Aug 3 07:55 SKILL.md\n./.agents/skills/e2e-flue-skill/SKILL.md", - "type": "function_call_output" - }, - { - "arguments": "{\"path\":\"./.agents/skills/e2e-flue-skill/SKILL.md\",\"offset\":1,\"limit\":200}", - "call_id": "call_KXcpmIy4Cbfugk4SNT2Ge7sG", - "id": "fc_054a16a2f2ea3217016a705b6d71cc81a38ac06634a309a997", - "name": "read", - "type": "function_call" - }, - { - "call_id": "call_KXcpmIy4Cbfugk4SNT2Ge7sG", - "output": "---\nname: e2e-flue-skill\ndescription: Deterministic skill used by the Braintrust Flue instrumentation e2e test.\n---\n\nReturn the marker from the provided args exactly once. Output no other text.\n", - "type": "function_call_output" - }, - { - "arguments": "{\"command\":\"cat ./package.json\",\"timeout\":100000}", - "call_id": "call_xQybhnQYuflmfp7gyAScVKjY", - "id": "fc_054a16a2f2ea3217016a705b6e90f481a39bc44496fbadf441", - "name": "bash", - "type": "function_call" - }, - { - "call_id": "call_xQybhnQYuflmfp7gyAScVKjY", - "output": "v24.14.1\ntotal 244\ndrwxr-xr-x 7 vercel-sandbox vercel-sandbox 4096 Aug 3 09:06 .\ndrwxr-xr-x 46 vercel-sandbox vercel-sandbox 4096 Aug 3 07:55 ..\ndrwxr-xr-x 3 vercel-sandbox vercel-sandbox 20 Aug 3 07:55 .agents\ndrwxr-xr-x 3 vercel-sandbox vercel-sandbox 38 Aug 3 07:55 .flue\ndrwxr-xr-x 2 vercel-sandbox vercel-sandbox 218 Aug 3 09:11 __cassettes__\ndrwxr-xr-x 2 vercel-sandbox vercel-sandbox 4096 Aug 3 08:49 __snapshots__\n-rw-r--r-- 1 vercel-sandbox vercel-sandbox 12484 Aug 3 08:45 assertions.ts\n-rw-r--r-- 1 vercel-sandbox vercel-sandbox 380 Aug 3 07:55 cassette-filter.mjs\n-rw-r--r-- 1 vercel-sandbox vercel-sandbox 215 Aug 3 07:55 constants.mjs\n-rw-r--r-- 1 vercel-sandbox vercel-sandbox 2000 Aug 3 09:06 package.json\n-rw-r--r-- 1 vercel-sandbox vercel-sandbox 167098 Aug 3 09:06 pnpm-lock.yaml\n-rw-r--r-- 1 vercel-sandbox vercel-sandbox 123 Aug 3 07:55 scenario.cli.mjs\n-rw-r--r-- 1 vercel-sandbox vercel-sandbox 6877 Aug 3 09:06 scenario.impl.mjs\n-rw-r--r-- 1 vercel-sandbox vercel-sandbox 203 Aug 3 07:55 scenario.mjs\n-rw-r--r-- 1 vercel-sandbox vercel-sandbox 8066 Aug 3 09:09 scenario.test.ts\n-rw-r--r-- 1 vercel-sandbox vercel-sandbox 121 Aug 3 07:55 scenario.ts\n-rw-r--r-- 1 vercel-sandbox vercel-sandbox 2376 Aug 3 08:49 scenario.v2.ts\n-rw-r--r-- 1 vercel-sandbox vercel-sandbox 4616 Aug 3 08:50 v2-assertions.ts\ndrwxr-xr-x 4 vercel-sandbox vercel-sandbox 26 Aug 3 08:06 versions\n{\n \"name\": \"flue-instrumentation-scenario\",\n \"private\": true,\n \"type\": \"module\",\n \"braintrustScenario\": {\n \"bump\": {\n \"dependencies\": {\n \"flue-cli-v0-8-latest\": {\n \"package\": \"@flue/cli\",\n \"range\": \">=0.8.0 <0.9.0\"\n },\n \"flue-cli-v1-latest\": {\n \"package\": \"@flue/cli\",\n \"range\": \">=1.0.0-beta.3 <2.0.0-0\",\n \"allowPrerelease\": true\n },\n \"flue-runtime-v0-8-latest\": {\n \"package\": \"@flue/runtime\",\n \"range\": \">=0.8.0 <0.9.0\"\n },\n \"flue-runtime-v1-latest\": {\n \"package\": \"@flue/runtime\",\n \"range\": \">=1.0.0-beta.3 <2.0.0-0\",\n \"allowPrerelease\": true\n },\n \"flue-runtime-v2-latest\": {\n \"package\": \"@flue/runtime\",\n \"range\": \">=2.0.0 <3.0.0\"\n }\n }\n }\n },\n \"dependencies\": {\n \"@flue/cli\": \"0.8.0\",\n \"@flue/runtime\": \"0.8.0\",\n \"flue-cli-v0-8-latest\": \"npm:@flue/cli@0.8.1\",\n \"flue-cli-v1\": \"npm:@flue/cli@1.0.0-beta.3\",\n \"flue-cli-v1-latest\": \"npm:@flue/cli@1.0.0-beta.9\",\n \"flue-runtime-v0-8-latest\": \"npm:@flue/runtime@0.8.1\",\n \"flue-runtime-v1\": \"npm:@flue/runtime@1.0.0-beta.3\",\n \"flue-runtime-v1-latest\": \"npm:@flue/runtime@1.0.0-beta.9\",\n \"flue-runtime-v2\": \"npm:@flue/runtime@2.0.0\",\n \"flue-runtime-v2-latest\": \"npm:@flue/runtime@2.0.1\",\n \"pi-ai-v2\": \"npm:@earendil-works/pi-ai@0.83.0\",\n \"valibot\": \"1.4.2\"\n },\n \"pnpm\": {\n \"overrides\": {\n \"@flue/runtime@0.8.0>@earendil-works/pi-agent-core\": \"0.75.4\",\n \"@flue/runtime@0.8.0>@earendil-works/pi-ai\": \"0.75.4\",\n \"@flue/runtime@0.8.1>@earendil-works/pi-agent-core\": \"0.75.4\",\n \"@flue/runtime@0.8.1>@earendil-works/pi-ai\": \"0.75.4\",\n \"@hono/node-server@>=2.0.0 <2.0.10\": \"^2.0.10\",\n \"fast-uri@<3.1.4\": \"^3.1.4\",\n \"hono@>=4.0.0 <4.12.27\": \"^4.12.27\",\n \"brace-expansion@>=3.0.0 <5.0.8\": \"^5.0.8\",\n \"postcss@<8.5.18\": \"^8.5.18\",\n \"valibot@<1.4.2\": \"^1.4.2\"\n }\n }\n}", - "type": "function_call_output" - } - ], - "model": "gpt-5.4-nano", - "prompt_cache_key": "[REDACTED]", - "reasoning": { - "effort": "none" - }, - "store": false, - "stream": true, - "tools": [ - { - "description": "Read a file or list a directory. For files, output is truncated to 2000 lines or 50KB — use offset/limit for large files. For directories, returns the list of entries.", - "name": "read", - "parameters": { - "properties": { - "limit": { - "description": "Maximum number of lines to read", - "type": "number" - }, - "offset": { - "description": "Line number to start from (1-indexed)", - "type": "number" - }, - "path": { - "description": "Path to the file to read", - "type": "string" - } - }, - "required": ["path"], - "type": "object" - }, - "strict": false, - "type": "function" - }, - { - "description": "Write content to a file. Creates the file and parent directories if they do not exist.", - "name": "write", - "parameters": { - "properties": { - "content": { - "description": "Content to write to the file", - "type": "string" - }, - "path": { - "description": "Path to the file to write", - "type": "string" - } - }, - "required": ["path", "content"], - "type": "object" - }, - "strict": false, - "type": "function" - }, - { - "description": "Edit a file using exact text replacement. The oldText must match a unique region of the file. Use replaceAll to replace all occurrences.", - "name": "edit", - "parameters": { - "properties": { - "newText": { - "description": "Replacement text", - "type": "string" - }, - "oldText": { - "description": "Exact text to find (must be unique)", - "type": "string" - }, - "path": { - "description": "Path to the file to edit", - "type": "string" - }, - "replaceAll": { - "description": "Replace all occurrences", - "type": "boolean" - } - }, - "required": ["path", "oldText", "newText"], - "type": "object" - }, - "strict": false, - "type": "function" - }, - { - "description": "Execute a bash command. Returns stdout and stderr. Output is truncated to the last 2000 lines or 50KB.", - "name": "bash", - "parameters": { - "properties": { - "command": { - "description": "Bash command to execute", - "type": "string" - }, - "timeout": { - "description": "Timeout in seconds", - "type": "number" - } - }, - "required": ["command"], - "type": "object" - }, - "strict": false, - "type": "function" - }, - { - "description": "Search file contents for a regex pattern. Returns matching lines with file paths and line numbers.", - "name": "grep", - "parameters": { - "properties": { - "include": { - "description": "Glob filter, e.g. \"*.ts\"", - "type": "string" - }, - "path": { - "description": "Directory or file to search (default: .)", - "type": "string" - }, - "pattern": { - "description": "Search pattern (regex)", - "type": "string" - } - }, - "required": ["pattern"], - "type": "object" - }, - "strict": false, - "type": "function" - }, - { - "description": "Find files by filename pattern using shell find -name semantics. Returns matching file paths.", - "name": "glob", - "parameters": { - "properties": { - "path": { - "description": "Directory to search in (default: .)", - "type": "string" - }, - "pattern": { - "description": "Filename pattern, e.g. \"*.ts\"", - "type": "string" - } - }, - "required": ["pattern"], - "type": "object" - }, - "strict": false, - "type": "function" - }, - { - "description": "Delegate a focused task to a detached child agent with its own context. Use this for independent research, file exploration, or parallel work. The task returns only its final answer to this conversation. No subagents are currently defined.", - "name": "task", - "parameters": { - "properties": { - "agent": { - "description": "Declared subagent to use for the child agent", - "type": "string" - }, - "cwd": { - "description": "Working directory for the child agent. AGENTS.md and skills are discovered from here.", - "type": "string" - }, - "description": { - "description": "Short human-readable label for the delegated work", - "type": "string" - }, - "prompt": { - "description": "Focused instructions for the child agent", - "type": "string" - } - }, - "required": ["prompt"], - "type": "object" - }, - "strict": false, - "type": "function" - } - ] - } - }, - "headers": {}, - "method": "POST", - "url": "https://api.openai.com/v1/responses" - }, - "response": { - "body": { - "chunks": [ - "event: response.created\ndata: {\"type\":\"response.created\",\"response\":{\"id\":\"resp_054a16a2f2ea3217016a705b6f493c81a3a225e3a6b86bbee9\",\"object\":\"response\",\"created_at\":1785748335,\"status\":\"in_progress\",\"background\":false,\"completed_at\":null,\"error\":null,\"frequency_penalty\":0,\"incomplete_details\":null,\"instructions\":null,\"max_output_tokens\":null,\"max_tool_calls\":null,\"model\":\"gpt-5.4-nano-2026-03-17\",\"moderation\":null,\"output\":[],\"parallel_tool_calls\":true,\"presence_penalty\":0,\"previous_response_id\":null,\"prompt_cache_key\":\"workflow:instrumentation:01KZ3E9TWTAEK4P4P3F63HKAKC::default::ta\",\"prompt_cache_retention\":\"24h\",\"reasoning\":{\"context\":\"current_turn\",\"effort\":\"none\",\"mode\":\"standard\",\"summary\":null},\"safety_identifier\":null,\"service_tier\":\"auto\",\"store\":false,\"temperature\":1,\"text\":{\"format\":{\"type\":\"text\"},\"verbosity\":\"medium\"},\"tool_choice\":\"auto\",\"tool_usage\":{\"image_gen\":{\"input_tokens\":0,\"input_tokens_details\":{\"image_tokens\":0,\"text_tokens\":0},\"output_tokens\":0,\"output_tokens_details\":{\"image_tokens\":0,\"text_tokens\":0},\"total_tokens\":0},\"web_search\":{\"num_requests\":0}},\"tools\":[{\"type\":\"function\",\"description\":\"Read a file or list a directory. For files, output is truncated to 2000 lines or 50KB — use offset/limit for large files. For directories, returns the list of entries.\",\"name\":\"read\",\"output_schema\":null,\"parameters\":{\"type\":\"object\",\"required\":[\"path\"],\"properties\":{\"path\":{\"type\":\"string\",\"description\":\"Path to the file to read\"},\"offset\":{\"type\":\"number\",\"description\":\"Line number to start from (1-indexed)\"},\"limit\":{\"type\":\"number\",\"description\":\"Maximum number of lines to read\"}}},\"strict\":false},{\"type\":\"function\",\"description\":\"Write content to a file. Creates the file and parent directories if they do not exist.\",\"name\":\"write\",\"output_schema\":null,\"parameters\":{\"type\":\"object\",\"required\":[\"path\",\"content\"],\"properties\":{\"path\":{\"type\":\"string\",\"description\":\"Path to the file to write\"},\"content\":{\"type\":\"string\",\"description\":\"Content to write to the file\"}}},\"strict\":false},{\"type\":\"function\",\"description\":\"Edit a file using exact text replacement. The oldText must match a unique region of the file. Use replaceAll to replace all occurrences.\",\"name\":\"edit\",\"output_schema\":null,\"parameters\":{\"type\":\"object\",\"required\":[\"path\",\"oldText\",\"newText\"],\"properties\":{\"path\":{\"type\":\"string\",\"description\":\"Path to the file to edit\"},\"oldText\":{\"type\":\"string\",\"description\":\"Exact text to find (must be unique)\"},\"newText\":{\"type\":\"string\",\"description\":\"Replacement text\"},\"replaceAll\":{\"type\":\"boolean\",\"description\":\"Replace all occurrences\"}}},\"strict\":false},{\"type\":\"function\",\"description\":\"Execute a bash command. Returns stdout and stderr. Output is truncated to the last 2000 lines or 50KB.\",\"name\":\"bash\",\"output_schema\":null,\"parameters\":{\"type\":\"object\",\"required\":[\"command\"],\"properties\":{\"command\":{\"type\":\"string\",\"description\":\"Bash command to execute\"},\"timeout\":{\"type\":\"number\",\"description\":\"Timeout in seconds\"}}},\"strict\":false},{\"type\":\"function\",\"description\":\"Search file contents for a regex pattern. Returns matching lines with file paths and line numbers.\",\"name\":\"grep\",\"output_schema\":null,\"parameters\":{\"type\":\"object\",\"required\":[\"pattern\"],\"properties\":{\"pattern\":{\"type\":\"string\",\"description\":\"Search pattern (regex)\"},\"path\":{\"type\":\"string\",\"description\":\"Directory or file to search (default: .)\"},\"include\":{\"type\":\"string\",\"description\":\"Glob filter, e.g. \\\"*.ts\\\"\"}}},\"strict\":false},{\"type\":\"function\",\"description\":\"Find files by filename pattern using shell find -name semantics. Returns matching file paths.\",\"name\":\"glob\",\"output_schema\":null,\"parameters\":{\"type\":\"object\",\"required\":[\"pattern\"],\"properties\":{\"pattern\":{\"type\":\"string\",\"description\":\"Filename pattern, e.g. \\\"*.ts\\\"\"},\"path\":{\"type\":\"string\",\"description\":\"Directory to search in (default: .)\"}}},\"strict\":false},{\"type\":\"function\",\"description\":\"Delegate a focused task to a detached child agent with its own context. Use this for independent research, file exploration, or parallel work. The task returns only its final answer to this conversation. No subagents are currently defined.\",\"name\":\"task\",\"output_schema\":null,\"parameters\":{\"type\":\"object\",\"required\":[\"prompt\"],\"properties\":{\"description\":{\"type\":\"string\",\"description\":\"Short human-readable label for the delegated work\"},\"prompt\":{\"type\":\"string\",\"description\":\"Focused instructions for the child agent\"},\"agent\":{\"type\":\"string\",\"description\":\"Declared subagent to use for the child agent\"},\"cwd\":{\"type\":\"string\",\"description\":\"Working directory for the child agent. AGENTS.md and skills are discovered from here.\"}}},\"strict\":false}],\"top_logprobs\":0,\"top_p\":0.98,\"truncation\":\"disabled\",\"usage\":null,\"user\":null,\"metadata\":{}},\"sequence_number\":0}", - "event: response.in_progress\ndata: {\"type\":\"response.in_progress\",\"response\":{\"id\":\"resp_054a16a2f2ea3217016a705b6f493c81a3a225e3a6b86bbee9\",\"object\":\"response\",\"created_at\":1785748335,\"status\":\"in_progress\",\"background\":false,\"completed_at\":null,\"error\":null,\"frequency_penalty\":0,\"incomplete_details\":null,\"instructions\":null,\"max_output_tokens\":null,\"max_tool_calls\":null,\"model\":\"gpt-5.4-nano-2026-03-17\",\"moderation\":null,\"output\":[],\"parallel_tool_calls\":true,\"presence_penalty\":0,\"previous_response_id\":null,\"prompt_cache_key\":\"workflow:instrumentation:01KZ3E9TWTAEK4P4P3F63HKAKC::default::ta\",\"prompt_cache_retention\":\"24h\",\"reasoning\":{\"context\":\"current_turn\",\"effort\":\"none\",\"mode\":\"standard\",\"summary\":null},\"safety_identifier\":null,\"service_tier\":\"auto\",\"store\":false,\"temperature\":1,\"text\":{\"format\":{\"type\":\"text\"},\"verbosity\":\"medium\"},\"tool_choice\":\"auto\",\"tool_usage\":{\"image_gen\":{\"input_tokens\":0,\"input_tokens_details\":{\"image_tokens\":0,\"text_tokens\":0},\"output_tokens\":0,\"output_tokens_details\":{\"image_tokens\":0,\"text_tokens\":0},\"total_tokens\":0},\"web_search\":{\"num_requests\":0}},\"tools\":[{\"type\":\"function\",\"description\":\"Read a file or list a directory. For files, output is truncated to 2000 lines or 50KB — use offset/limit for large files. For directories, returns the list of entries.\",\"name\":\"read\",\"output_schema\":null,\"parameters\":{\"type\":\"object\",\"required\":[\"path\"],\"properties\":{\"path\":{\"type\":\"string\",\"description\":\"Path to the file to read\"},\"offset\":{\"type\":\"number\",\"description\":\"Line number to start from (1-indexed)\"},\"limit\":{\"type\":\"number\",\"description\":\"Maximum number of lines to read\"}}},\"strict\":false},{\"type\":\"function\",\"description\":\"Write content to a file. Creates the file and parent directories if they do not exist.\",\"name\":\"write\",\"output_schema\":null,\"parameters\":{\"type\":\"object\",\"required\":[\"path\",\"content\"],\"properties\":{\"path\":{\"type\":\"string\",\"description\":\"Path to the file to write\"},\"content\":{\"type\":\"string\",\"description\":\"Content to write to the file\"}}},\"strict\":false},{\"type\":\"function\",\"description\":\"Edit a file using exact text replacement. The oldText must match a unique region of the file. Use replaceAll to replace all occurrences.\",\"name\":\"edit\",\"output_schema\":null,\"parameters\":{\"type\":\"object\",\"required\":[\"path\",\"oldText\",\"newText\"],\"properties\":{\"path\":{\"type\":\"string\",\"description\":\"Path to the file to edit\"},\"oldText\":{\"type\":\"string\",\"description\":\"Exact text to find (must be unique)\"},\"newText\":{\"type\":\"string\",\"description\":\"Replacement text\"},\"replaceAll\":{\"type\":\"boolean\",\"description\":\"Replace all occurrences\"}}},\"strict\":false},{\"type\":\"function\",\"description\":\"Execute a bash command. Returns stdout and stderr. Output is truncated to the last 2000 lines or 50KB.\",\"name\":\"bash\",\"output_schema\":null,\"parameters\":{\"type\":\"object\",\"required\":[\"command\"],\"properties\":{\"command\":{\"type\":\"string\",\"description\":\"Bash command to execute\"},\"timeout\":{\"type\":\"number\",\"description\":\"Timeout in seconds\"}}},\"strict\":false},{\"type\":\"function\",\"description\":\"Search file contents for a regex pattern. Returns matching lines with file paths and line numbers.\",\"name\":\"grep\",\"output_schema\":null,\"parameters\":{\"type\":\"object\",\"required\":[\"pattern\"],\"properties\":{\"pattern\":{\"type\":\"string\",\"description\":\"Search pattern (regex)\"},\"path\":{\"type\":\"string\",\"description\":\"Directory or file to search (default: .)\"},\"include\":{\"type\":\"string\",\"description\":\"Glob filter, e.g. \\\"*.ts\\\"\"}}},\"strict\":false},{\"type\":\"function\",\"description\":\"Find files by filename pattern using shell find -name semantics. Returns matching file paths.\",\"name\":\"glob\",\"output_schema\":null,\"parameters\":{\"type\":\"object\",\"required\":[\"pattern\"],\"properties\":{\"pattern\":{\"type\":\"string\",\"description\":\"Filename pattern, e.g. \\\"*.ts\\\"\"},\"path\":{\"type\":\"string\",\"description\":\"Directory to search in (default: .)\"}}},\"strict\":false},{\"type\":\"function\",\"description\":\"Delegate a focused task to a detached child agent with its own context. Use this for independent research, file exploration, or parallel work. The task returns only its final answer to this conversation. No subagents are currently defined.\",\"name\":\"task\",\"output_schema\":null,\"parameters\":{\"type\":\"object\",\"required\":[\"prompt\"],\"properties\":{\"description\":{\"type\":\"string\",\"description\":\"Short human-readable label for the delegated work\"},\"prompt\":{\"type\":\"string\",\"description\":\"Focused instructions for the child agent\"},\"agent\":{\"type\":\"string\",\"description\":\"Declared subagent to use for the child agent\"},\"cwd\":{\"type\":\"string\",\"description\":\"Working directory for the child agent. AGENTS.md and skills are discovered from here.\"}}},\"strict\":false}],\"top_logprobs\":0,\"top_p\":0.98,\"truncation\":\"disabled\",\"usage\":null,\"user\":null,\"metadata\":{}},\"sequence_number\":1}", - "event: response.output_item.added\ndata: {\"type\":\"response.output_item.added\",\"item\":{\"id\":\"fc_054a16a2f2ea3217016a705b6fb64c81a38ff895c9553c8763\",\"type\":\"function_call\",\"status\":\"in_progress\",\"arguments\":\"\",\"call_id\":\"call_7fRJCCDIZe4x17WaroXZwmjV\",\"name\":\"bash\"},\"output_index\":0,\"sequence_number\":2}", - "event: response.function_call_arguments.delta\ndata: {\"type\":\"response.function_call_arguments.delta\",\"delta\":\"{\\\"\",\"item_id\":\"fc_054a16a2f2ea3217016a705b6fb64c81a38ff895c9553c8763\",\"obfuscation\":\"fHq8b6uDNpdKyA\",\"output_index\":0,\"sequence_number\":3}", - "event: response.function_call_arguments.delta\ndata: {\"type\":\"response.function_call_arguments.delta\",\"delta\":\"command\",\"item_id\":\"fc_054a16a2f2ea3217016a705b6fb64c81a38ff895c9553c8763\",\"obfuscation\":\"thvsscitV\",\"output_index\":0,\"sequence_number\":4}", - "event: response.function_call_arguments.delta\ndata: {\"type\":\"response.function_call_arguments.delta\",\"delta\":\"\\\":\\\"\",\"item_id\":\"fc_054a16a2f2ea3217016a705b6fb64c81a38ff895c9553c8763\",\"obfuscation\":\"4KhNnIoFmUxL4\",\"output_index\":0,\"sequence_number\":5}", - "event: response.function_call_arguments.delta\ndata: {\"type\":\"response.function_call_arguments.delta\",\"delta\":\"cd\",\"item_id\":\"fc_054a16a2f2ea3217016a705b6fb64c81a38ff895c9553c8763\",\"obfuscation\":\"2MAKSqM5RZc02D\",\"output_index\":0,\"sequence_number\":6}", - "event: response.function_call_arguments.delta\ndata: {\"type\":\"response.function_call_arguments.delta\",\"delta\":\" /\",\"item_id\":\"fc_054a16a2f2ea3217016a705b6fb64c81a38ff895c9553c8763\",\"obfuscation\":\"rtJwB0qEFcuGP0\",\"output_index\":0,\"sequence_number\":7}", - "event: response.function_call_arguments.delta\ndata: {\"type\":\"response.function_call_arguments.delta\",\"delta\":\"ver\",\"item_id\":\"fc_054a16a2f2ea3217016a705b6fb64c81a38ff895c9553c8763\",\"obfuscation\":\"F1lTsvrOqKTGW\",\"output_index\":0,\"sequence_number\":8}", - "event: response.function_call_arguments.delta\ndata: {\"type\":\"response.function_call_arguments.delta\",\"delta\":\"cel\",\"item_id\":\"fc_054a16a2f2ea3217016a705b6fb64c81a38ff895c9553c8763\",\"obfuscation\":\"OW6Ho3Im2OXeD\",\"output_index\":0,\"sequence_number\":9}", - "event: response.function_call_arguments.delta\ndata: {\"type\":\"response.function_call_arguments.delta\",\"delta\":\"/s\",\"item_id\":\"fc_054a16a2f2ea3217016a705b6fb64c81a38ff895c9553c8763\",\"obfuscation\":\"OeETspVfDui6qp\",\"output_index\":0,\"sequence_number\":10}", - "event: response.function_call_arguments.delta\ndata: {\"type\":\"response.function_call_arguments.delta\",\"delta\":\"andbox\",\"item_id\":\"fc_054a16a2f2ea3217016a705b6fb64c81a38ff895c9553c8763\",\"obfuscation\":\"TYKoEchhWA\",\"output_index\":0,\"sequence_number\":11}", - "event: response.function_call_arguments.delta\ndata: {\"type\":\"response.function_call_arguments.delta\",\"delta\":\"/\",\"item_id\":\"fc_054a16a2f2ea3217016a705b6fb64c81a38ff895c9553c8763\",\"obfuscation\":\"mlWpUOMOoFsxvPZ\",\"output_index\":0,\"sequence_number\":12}", - "event: response.function_call_arguments.delta\ndata: {\"type\":\"response.function_call_arguments.delta\",\"delta\":\"brain\",\"item_id\":\"fc_054a16a2f2ea3217016a705b6fb64c81a38ff895c9553c8763\",\"obfuscation\":\"dhWAHpJ9eAO\",\"output_index\":0,\"sequence_number\":13}", - "event: response.function_call_arguments.delta\ndata: {\"type\":\"response.function_call_arguments.delta\",\"delta\":\"trust\",\"item_id\":\"fc_054a16a2f2ea3217016a705b6fb64c81a38ff895c9553c8763\",\"obfuscation\":\"obL0WnjlMLS\",\"output_index\":0,\"sequence_number\":14}", - "event: response.function_call_arguments.delta\ndata: {\"type\":\"response.function_call_arguments.delta\",\"delta\":\"data\",\"item_id\":\"fc_054a16a2f2ea3217016a705b6fb64c81a38ff895c9553c8763\",\"obfuscation\":\"VQtHA1VRMGyD\",\"output_index\":0,\"sequence_number\":15}", - "event: response.function_call_arguments.delta\ndata: {\"type\":\"response.function_call_arguments.delta\",\"delta\":\"/\",\"item_id\":\"fc_054a16a2f2ea3217016a705b6fb64c81a38ff895c9553c8763\",\"obfuscation\":\"v3crkHcTsSeS6g9\",\"output_index\":0,\"sequence_number\":16}", - "event: response.function_call_arguments.delta\ndata: {\"type\":\"response.function_call_arguments.delta\",\"delta\":\"brain\",\"item_id\":\"fc_054a16a2f2ea3217016a705b6fb64c81a38ff895c9553c8763\",\"obfuscation\":\"fLyFqifDKnd\",\"output_index\":0,\"sequence_number\":17}", - "event: response.function_call_arguments.delta\ndata: {\"type\":\"response.function_call_arguments.delta\",\"delta\":\"trust\",\"item_id\":\"fc_054a16a2f2ea3217016a705b6fb64c81a38ff895c9553c8763\",\"obfuscation\":\"zOZwoEc9EwU\",\"output_index\":0,\"sequence_number\":18}", - "event: response.function_call_arguments.delta\ndata: {\"type\":\"response.function_call_arguments.delta\",\"delta\":\"-sdk\",\"item_id\":\"fc_054a16a2f2ea3217016a705b6fb64c81a38ff895c9553c8763\",\"obfuscation\":\"jKM1tjLK0Nox\",\"output_index\":0,\"sequence_number\":19}", - "event: response.function_call_arguments.delta\ndata: {\"type\":\"response.function_call_arguments.delta\",\"delta\":\"-j\",\"item_id\":\"fc_054a16a2f2ea3217016a705b6fb64c81a38ff895c9553c8763\",\"obfuscation\":\"N8hk8Xm3OX7w2R\",\"output_index\":0,\"sequence_number\":20}", - "event: response.function_call_arguments.delta\ndata: {\"type\":\"response.function_call_arguments.delta\",\"delta\":\"avascript\",\"item_id\":\"fc_054a16a2f2ea3217016a705b6fb64c81a38ff895c9553c8763\",\"obfuscation\":\"P8EgPwQ\",\"output_index\":0,\"sequence_number\":21}", - "event: response.function_call_arguments.delta\ndata: {\"type\":\"response.function_call_arguments.delta\",\"delta\":\"/e\",\"item_id\":\"fc_054a16a2f2ea3217016a705b6fb64c81a38ff895c9553c8763\",\"obfuscation\":\"aIWWYDGHFlbnf0\",\"output_index\":0,\"sequence_number\":22}", - "event: response.function_call_arguments.delta\ndata: {\"type\":\"response.function_call_arguments.delta\",\"delta\":\"2\",\"item_id\":\"fc_054a16a2f2ea3217016a705b6fb64c81a38ff895c9553c8763\",\"obfuscation\":\"k9oYAIfrrIU8RoN\",\"output_index\":0,\"sequence_number\":23}", - "event: response.function_call_arguments.delta\ndata: {\"type\":\"response.function_call_arguments.delta\",\"delta\":\"e\",\"item_id\":\"fc_054a16a2f2ea3217016a705b6fb64c81a38ff895c9553c8763\",\"obfuscation\":\"qYFO9k1cYtpeWCN\",\"output_index\":0,\"sequence_number\":24}", - "event: response.function_call_arguments.delta\ndata: {\"type\":\"response.function_call_arguments.delta\",\"delta\":\"/sc\",\"item_id\":\"fc_054a16a2f2ea3217016a705b6fb64c81a38ff895c9553c8763\",\"obfuscation\":\"h3KfOXIs1dENs\",\"output_index\":0,\"sequence_number\":25}", - "event: response.function_call_arguments.delta\ndata: {\"type\":\"response.function_call_arguments.delta\",\"delta\":\"enarios\",\"item_id\":\"fc_054a16a2f2ea3217016a705b6fb64c81a38ff895c9553c8763\",\"obfuscation\":\"JH679LrpQ\",\"output_index\":0,\"sequence_number\":26}", - "event: response.function_call_arguments.delta\ndata: {\"type\":\"response.function_call_arguments.delta\",\"delta\":\"/fl\",\"item_id\":\"fc_054a16a2f2ea3217016a705b6fb64c81a38ff895c9553c8763\",\"obfuscation\":\"gMCaFOZiRoUg3\",\"output_index\":0,\"sequence_number\":27}", - "event: response.function_call_arguments.delta\ndata: {\"type\":\"response.function_call_arguments.delta\",\"delta\":\"ue\",\"item_id\":\"fc_054a16a2f2ea3217016a705b6fb64c81a38ff895c9553c8763\",\"obfuscation\":\"yXl3sFNxeXVgqy\",\"output_index\":0,\"sequence_number\":28}", - "event: response.function_call_arguments.delta\ndata: {\"type\":\"response.function_call_arguments.delta\",\"delta\":\"-in\",\"item_id\":\"fc_054a16a2f2ea3217016a705b6fb64c81a38ff895c9553c8763\",\"obfuscation\":\"glJkNUpBDKD1z\",\"output_index\":0,\"sequence_number\":29}", - "event: response.function_call_arguments.delta\ndata: {\"type\":\"response.function_call_arguments.delta\",\"delta\":\"strument\",\"item_id\":\"fc_054a16a2f2ea3217016a705b6fb64c81a38ff895c9553c8763\",\"obfuscation\":\"VBlCVyqb\",\"output_index\":0,\"sequence_number\":30}", - "event: response.function_call_arguments.delta\ndata: {\"type\":\"response.function_call_arguments.delta\",\"delta\":\"ation\",\"item_id\":\"fc_054a16a2f2ea3217016a705b6fb64c81a38ff895c9553c8763\",\"obfuscation\":\"SNMP91Q3rDW\",\"output_index\":0,\"sequence_number\":31}", - "event: response.function_call_arguments.delta\ndata: {\"type\":\"response.function_call_arguments.delta\",\"delta\":\" &&\",\"item_id\":\"fc_054a16a2f2ea3217016a705b6fb64c81a38ff895c9553c8763\",\"obfuscation\":\"X3ihmCKcYuVXU\",\"output_index\":0,\"sequence_number\":32}", - "event: response.function_call_arguments.delta\ndata: {\"type\":\"response.function_call_arguments.delta\",\"delta\":\" node\",\"item_id\":\"fc_054a16a2f2ea3217016a705b6fb64c81a38ff895c9553c8763\",\"obfuscation\":\"JKpFNiOkxT3\",\"output_index\":0,\"sequence_number\":33}", - "event: response.function_call_arguments.delta\ndata: {\"type\":\"response.function_call_arguments.delta\",\"delta\":\" scenario\",\"item_id\":\"fc_054a16a2f2ea3217016a705b6fb64c81a38ff895c9553c8763\",\"obfuscation\":\"bFvkMJJ\",\"output_index\":0,\"sequence_number\":34}", - "event: response.function_call_arguments.delta\ndata: {\"type\":\"response.function_call_arguments.delta\",\"delta\":\".cli\",\"item_id\":\"fc_054a16a2f2ea3217016a705b6fb64c81a38ff895c9553c8763\",\"obfuscation\":\"kUiDpbheCAHH\",\"output_index\":0,\"sequence_number\":35}", - "event: response.function_call_arguments.delta\ndata: {\"type\":\"response.function_call_arguments.delta\",\"delta\":\".m\",\"item_id\":\"fc_054a16a2f2ea3217016a705b6fb64c81a38ff895c9553c8763\",\"obfuscation\":\"FxsGZq8BCGo3gl\",\"output_index\":0,\"sequence_number\":36}", - "event: response.function_call_arguments.delta\ndata: {\"type\":\"response.function_call_arguments.delta\",\"delta\":\"js\",\"item_id\":\"fc_054a16a2f2ea3217016a705b6fb64c81a38ff895c9553c8763\",\"obfuscation\":\"Dp4QtMwr7c3BK1\",\"output_index\":0,\"sequence_number\":37}", - "event: response.function_call_arguments.delta\ndata: {\"type\":\"response.function_call_arguments.delta\",\"delta\":\" --\",\"item_id\":\"fc_054a16a2f2ea3217016a705b6fb64c81a38ff895c9553c8763\",\"obfuscation\":\"PeegSlydvrC1L\",\"output_index\":0,\"sequence_number\":38}", - "event: response.function_call_arguments.delta\ndata: {\"type\":\"response.function_call_arguments.delta\",\"delta\":\"marker\",\"item_id\":\"fc_054a16a2f2ea3217016a705b6fb64c81a38ff895c9553c8763\",\"obfuscation\":\"wvkAVwRdNi\",\"output_index\":0,\"sequence_number\":39}", - "event: response.function_call_arguments.delta\ndata: {\"type\":\"response.function_call_arguments.delta\",\"delta\":\" SK\",\"item_id\":\"fc_054a16a2f2ea3217016a705b6fb64c81a38ff895c9553c8763\",\"obfuscation\":\"PTHJb9FqWdVqt\",\"output_index\":0,\"sequence_number\":40}", - "event: response.function_call_arguments.delta\ndata: {\"type\":\"response.function_call_arguments.delta\",\"delta\":\"ILL\",\"item_id\":\"fc_054a16a2f2ea3217016a705b6fb64c81a38ff895c9553c8763\",\"obfuscation\":\"2fQZxFddpb4N0\",\"output_index\":0,\"sequence_number\":41}", - "event: response.function_call_arguments.delta\ndata: {\"type\":\"response.function_call_arguments.delta\",\"delta\":\"_DONE\",\"item_id\":\"fc_054a16a2f2ea3217016a705b6fb64c81a38ff895c9553c8763\",\"obfuscation\":\"k70XHwhwure\",\"output_index\":0,\"sequence_number\":42}", - "event: response.function_call_arguments.delta\ndata: {\"type\":\"response.function_call_arguments.delta\",\"delta\":\"\\\",\\\"\",\"item_id\":\"fc_054a16a2f2ea3217016a705b6fb64c81a38ff895c9553c8763\",\"obfuscation\":\"rGVgz0vPyy7dV\",\"output_index\":0,\"sequence_number\":43}", - "event: response.function_call_arguments.delta\ndata: {\"type\":\"response.function_call_arguments.delta\",\"delta\":\"timeout\",\"item_id\":\"fc_054a16a2f2ea3217016a705b6fb64c81a38ff895c9553c8763\",\"obfuscation\":\"MAkwSc4cj\",\"output_index\":0,\"sequence_number\":44}", - "event: response.function_call_arguments.delta\ndata: {\"type\":\"response.function_call_arguments.delta\",\"delta\":\"\\\":\",\"item_id\":\"fc_054a16a2f2ea3217016a705b6fb64c81a38ff895c9553c8763\",\"obfuscation\":\"RnIym4ATTjxwrH\",\"output_index\":0,\"sequence_number\":45}", - "event: response.function_call_arguments.delta\ndata: {\"type\":\"response.function_call_arguments.delta\",\"delta\":\"100\",\"item_id\":\"fc_054a16a2f2ea3217016a705b6fb64c81a38ff895c9553c8763\",\"obfuscation\":\"soJ0BJsFSpsnc\",\"output_index\":0,\"sequence_number\":46}", - "event: response.function_call_arguments.delta\ndata: {\"type\":\"response.function_call_arguments.delta\",\"delta\":\"000\",\"item_id\":\"fc_054a16a2f2ea3217016a705b6fb64c81a38ff895c9553c8763\",\"obfuscation\":\"DQKN9g0JhSsmY\",\"output_index\":0,\"sequence_number\":47}", - "event: response.function_call_arguments.delta\ndata: {\"type\":\"response.function_call_arguments.delta\",\"delta\":\"}\",\"item_id\":\"fc_054a16a2f2ea3217016a705b6fb64c81a38ff895c9553c8763\",\"obfuscation\":\"BTOAjj33gQRbswj\",\"output_index\":0,\"sequence_number\":48}", - "event: response.function_call_arguments.done\ndata: {\"type\":\"response.function_call_arguments.done\",\"arguments\":\"{\\\"command\\\":\\\"printf SKILL_DONE\\\",\\\"timeout\\\":100000}\",\"item_id\":\"fc_054a16a2f2ea3217016a705b6fb64c81a38ff895c9553c8763\",\"output_index\":0,\"sequence_number\":49}", - "event: response.output_item.done\ndata: {\"type\":\"response.output_item.done\",\"item\":{\"id\":\"fc_054a16a2f2ea3217016a705b6fb64c81a38ff895c9553c8763\",\"type\":\"function_call\",\"status\":\"completed\",\"arguments\":\"{\\\"command\\\":\\\"printf SKILL_DONE\\\",\\\"timeout\\\":100000}\",\"call_id\":\"call_7fRJCCDIZe4x17WaroXZwmjV\",\"name\":\"bash\"},\"output_index\":0,\"sequence_number\":50}", - "event: response.completed\ndata: {\"type\":\"response.completed\",\"response\":{\"id\":\"resp_054a16a2f2ea3217016a705b6f493c81a3a225e3a6b86bbee9\",\"object\":\"response\",\"created_at\":1785748335,\"status\":\"completed\",\"background\":false,\"completed_at\":1785748336,\"error\":null,\"frequency_penalty\":0,\"incomplete_details\":null,\"instructions\":null,\"max_output_tokens\":null,\"max_tool_calls\":null,\"model\":\"gpt-5.4-nano-2026-03-17\",\"moderation\":null,\"output\":[{\"id\":\"fc_054a16a2f2ea3217016a705b6fb64c81a38ff895c9553c8763\",\"type\":\"function_call\",\"status\":\"completed\",\"arguments\":\"{\\\"command\\\":\\\"printf SKILL_DONE\\\",\\\"timeout\\\":100000}\",\"call_id\":\"call_7fRJCCDIZe4x17WaroXZwmjV\",\"name\":\"bash\"}],\"parallel_tool_calls\":true,\"presence_penalty\":0,\"previous_response_id\":null,\"prompt_cache_key\":\"workflow:instrumentation:01KZ3E9TWTAEK4P4P3F63HKAKC::default::ta\",\"prompt_cache_retention\":\"24h\",\"reasoning\":{\"context\":\"current_turn\",\"effort\":\"none\",\"mode\":\"standard\",\"summary\":null},\"safety_identifier\":null,\"service_tier\":\"default\",\"store\":false,\"temperature\":1,\"text\":{\"format\":{\"type\":\"text\"},\"verbosity\":\"medium\"},\"tool_choice\":\"auto\",\"tool_usage\":{\"image_gen\":{\"input_tokens\":0,\"input_tokens_details\":{\"image_tokens\":0,\"text_tokens\":0},\"output_tokens\":0,\"output_tokens_details\":{\"image_tokens\":0,\"text_tokens\":0},\"total_tokens\":0},\"web_search\":{\"num_requests\":0}},\"tools\":[{\"type\":\"function\",\"description\":\"Read a file or list a directory. For files, output is truncated to 2000 lines or 50KB — use offset/limit for large files. For directories, returns the list of entries.\",\"name\":\"read\",\"output_schema\":null,\"parameters\":{\"type\":\"object\",\"required\":[\"path\"],\"properties\":{\"path\":{\"type\":\"string\",\"description\":\"Path to the file to read\"},\"offset\":{\"type\":\"number\",\"description\":\"Line number to start from (1-indexed)\"},\"limit\":{\"type\":\"number\",\"description\":\"Maximum number of lines to read\"}}},\"strict\":false},{\"type\":\"function\",\"description\":\"Write content to a file. Creates the file and parent directories if they do not exist.\",\"name\":\"write\",\"output_schema\":null,\"parameters\":{\"type\":\"object\",\"required\":[\"path\",\"content\"],\"properties\":{\"path\":{\"type\":\"string\",\"description\":\"Path to the file to write\"},\"content\":{\"type\":\"string\",\"description\":\"Content to write to the file\"}}},\"strict\":false},{\"type\":\"function\",\"description\":\"Edit a file using exact text replacement. The oldText must match a unique region of the file. Use replaceAll to replace all occurrences.\",\"name\":\"edit\",\"output_schema\":null,\"parameters\":{\"type\":\"object\",\"required\":[\"path\",\"oldText\",\"newText\"],\"properties\":{\"path\":{\"type\":\"string\",\"description\":\"Path to the file to edit\"},\"oldText\":{\"type\":\"string\",\"description\":\"Exact text to find (must be unique)\"},\"newText\":{\"type\":\"string\",\"description\":\"Replacement text\"},\"replaceAll\":{\"type\":\"boolean\",\"description\":\"Replace all occurrences\"}}},\"strict\":false},{\"type\":\"function\",\"description\":\"Execute a bash command. Returns stdout and stderr. Output is truncated to the last 2000 lines or 50KB.\",\"name\":\"bash\",\"output_schema\":null,\"parameters\":{\"type\":\"object\",\"required\":[\"command\"],\"properties\":{\"command\":{\"type\":\"string\",\"description\":\"Bash command to execute\"},\"timeout\":{\"type\":\"number\",\"description\":\"Timeout in seconds\"}}},\"strict\":false},{\"type\":\"function\",\"description\":\"Search file contents for a regex pattern. Returns matching lines with file paths and line numbers.\",\"name\":\"grep\",\"output_schema\":null,\"parameters\":{\"type\":\"object\",\"required\":[\"pattern\"],\"properties\":{\"pattern\":{\"type\":\"string\",\"description\":\"Search pattern (regex)\"},\"path\":{\"type\":\"string\",\"description\":\"Directory or file to search (default: .)\"},\"include\":{\"type\":\"string\",\"description\":\"Glob filter, e.g. \\\"*.ts\\\"\"}}},\"strict\":false},{\"type\":\"function\",\"description\":\"Find files by filename pattern using shell find -name semantics. Returns matching file paths.\",\"name\":\"glob\",\"output_schema\":null,\"parameters\":{\"type\":\"object\",\"required\":[\"pattern\"],\"properties\":{\"pattern\":{\"type\":\"string\",\"description\":\"Filename pattern, e.g. \\\"*.ts\\\"\"},\"path\":{\"type\":\"string\",\"description\":\"Directory to search in (default: .)\"}}},\"strict\":false},{\"type\":\"function\",\"description\":\"Delegate a focused task to a detached child agent with its own context. Use this for independent research, file exploration, or parallel work. The task returns only its final answer to this conversation. No subagents are currently defined.\",\"name\":\"task\",\"output_schema\":null,\"parameters\":{\"type\":\"object\",\"required\":[\"prompt\"],\"properties\":{\"description\":{\"type\":\"string\",\"description\":\"Short human-readable label for the delegated work\"},\"prompt\":{\"type\":\"string\",\"description\":\"Focused instructions for the child agent\"},\"agent\":{\"type\":\"string\",\"description\":\"Declared subagent to use for the child agent\"},\"cwd\":{\"type\":\"string\",\"description\":\"Working directory for the child agent. AGENTS.md and skills are discovered from here.\"}}},\"strict\":false}],\"top_logprobs\":0,\"top_p\":0.98,\"truncation\":\"disabled\",\"usage\":{\"input_tokens\":3421,\"input_tokens_details\":{\"cache_write_tokens\":0,\"cached_tokens\":1536},\"output_tokens\":59,\"output_tokens_details\":{\"reasoning_tokens\":0},\"total_tokens\":3480},\"user\":null,\"metadata\":{}},\"sequence_number\":51}" - ], - "kind": "sse" - }, - "headers": { - "access-control-expose-headers": "X-Request-ID, CF-Ray, CF-Ray", - "alt-svc": "h3=\":443\"; ma=86400", - "cf-cache-status": "DYNAMIC", - "cf-ray": "a25433173b9c7bb6-IAD", - "connection": "keep-alive", - "content-type": "text/event-stream; charset=utf-8", - "date": "Mon, 03 Aug 2026 09:12:15 GMT", - "openai-organization": "braintrust-data", - "openai-processing-ms": "200", - "openai-project": "proj_vsCSXafhhByzWOThMrJcZiw9", - "openai-version": "2020-10-01", - "server": "cloudflare", - "set-cookie": "[REDACTED]", - "strict-transport-security": "max-age=31536000; includeSubDomains; preload", - "transfer-encoding": "chunked", - "x-content-type-options": "nosniff", - "x-ratelimit-limit-requests": "30000", - "x-ratelimit-limit-tokens": "180000000", - "x-ratelimit-remaining-requests": "29999", - "x-ratelimit-remaining-tokens": "179996124", - "x-ratelimit-reset-requests": "2ms", - "x-ratelimit-reset-tokens": "1ms", - "x-request-id": "req_3d947e9c4a9c4ea6a016b86fe1407743" - }, - "status": 200, - "statusText": "OK" - } - }, - { - "callIndex": 14, - "id": "bc08e707f2920073", - "matchKey": "POST api.openai.com/v1/responses", - "recordedAt": "2026-08-03T09:12:16.776Z", - "request": { - "body": { - "kind": "json", - "value": { - "input": [ - { - "content": "You are running in headless mode with no human operator. Work autonomously — never ask questions, never wait for user input. Make your best judgment and proceed independently.\n\nYou are a deterministic Flue instrumentation test agent. Follow user instructions exactly. When asked for a marker, output only that marker and no extra text. When running a local skill file, read it yourself and do not delegate it to a task.\n\n## Available Skills\n\nThe following skills provide specialized instructions for specific tasks. When a task matches a skill description, activate that skill before proceeding so its full instructions are loaded. Skill instructions and supporting resources stay lazy until activation or explicit file reads.\n\n- **e2e-flue-skill** — Deterministic skill used by the Braintrust Flue instrumentation e2e test.\n\nDate: Mon, Aug 3, 2026\nWorking directory: .\n\nDirectory structure:\n.agents\n.flue\n.flue-build\n__cassettes__\n__snapshots__\nassertions.ts\ncassette-filter.mjs\nconstants.mjs\ndist\nnode_modules\npackage.json\npnpm-lock.yaml\nscenario.cli.mjs\nscenario.impl.mjs\nscenario.mjs\nscenario.test.ts\nscenario.ts\nscenario.v2.ts\nv2-assertions.ts\nversions", - "role": "developer" - }, - { - "content": [ - { - "text": "Run the skill named \"e2e-flue-skill\" with arguments {\"marker\":\"SKILL_DONE\"}. Output only the marker and no extra text when finished.", - "type": "input_text" - } - ], - "role": "user" - }, - { - "arguments": "{\"description\":\"Run e2e-flue-skill with marker SKILL_DONE\",\"prompt\":\"Run the skill named \\\"e2e-flue-skill\\\" with arguments {\\\"marker\\\":\\\"SKILL_DONE\\\"}. Ensure completion and report the final output marker only.\",\"agent\":\"e2e-flue-skill\",\"cwd\":\".\"}", - "call_id": "call_w6pyYZgnhlSOAXul75DAG1Cj", - "id": "fc_054a16a2f2ea3217016a705b64fe5481a390185c9576df12b6", - "name": "task", - "type": "function_call" - }, - { - "call_id": "call_w6pyYZgnhlSOAXul75DAG1Cj", - "output": "[flue] Subagent \"e2e-flue-skill\" is not declared. Available: (none).", - "type": "function_call_output" - }, - { - "arguments": "{\"command\":\"find . -maxdepth 4 -name AGENTS.md -print\",\"timeout\":100000}", - "call_id": "call_6zEBnJRmXFIkGFwNjWLZP4uC", - "id": "fc_054a16a2f2ea3217016a705b66460481a3a5de06df603e35ee", - "name": "bash", - "type": "function_call" - }, - { - "call_id": "call_6zEBnJRmXFIkGFwNjWLZP4uC", - "output": "total 4\ndrwxr-xr-x 3 vercel-sandbox vercel-sandbox 20 Aug 3 09:10 .\ndrwxr-xr-x 10 vercel-sandbox vercel-sandbox 4096 Aug 3 09:11 ..\ndrwxr-xr-x 3 vercel-sandbox vercel-sandbox 28 Aug 3 09:10 skills", - "type": "function_call_output" - }, - { - "arguments": "{\"command\":\"find . -maxdepth 4 -type f -name AGENTS.md -print -exec sed -n '1,200p' {} \\\\;\",\"timeout\":100000}", - "call_id": "call_jQUCie0ZgaXUrZCDcezp0msl", - "id": "fc_054a16a2f2ea3217016a705b67575881a3aacde02b9053a321", - "name": "bash", - "type": "function_call" - }, - { - "call_id": "call_jQUCie0ZgaXUrZCDcezp0msl", - "output": "(no output)", - "type": "function_call_output" - }, - { - "arguments": "{\"command\":\"find . -maxdepth 6 -type f -name 'AGENTS.md' -print\",\"timeout\":100000}", - "call_id": "call_NgMSUb6wIR0GAf9U8LXERtBQ", - "id": "fc_054a16a2f2ea3217016a705b68c39881a3b1e518599236e41a", - "name": "bash", - "type": "function_call" - }, - { - "call_id": "call_NgMSUb6wIR0GAf9U8LXERtBQ", - "output": "(no output)", - "type": "function_call_output" - }, - { - "arguments": "{\"command\":\"find . -maxdepth 5 -type f -name '*flue*skill*' -o -name '*skill*' | head\",\"timeout\":100000}", - "call_id": "call_BXKiQ5KG8c42XPPp94zgcY3H", - "id": "fc_054a16a2f2ea3217016a705b69edd081a38c31583ec56eca5f", - "name": "bash", - "type": "function_call" - }, - { - "call_id": "call_BXKiQ5KG8c42XPPp94zgcY3H", - "output": "./.agents/skills\n./.agents/skills/e2e-flue-skill", - "type": "function_call_output" - }, - { - "arguments": "{\"command\":\"find ./.agents/skills/e2e-flue-skill -maxdepth 2 -type f -print\",\"timeout\":100000}", - "call_id": "call_Zmmrs64LFscpAHsIUaIfEcAN", - "id": "fc_054a16a2f2ea3217016a705b6b96cc81a3ac4761db24198a63", - "name": "bash", - "type": "function_call" - }, - { - "call_id": "call_Zmmrs64LFscpAHsIUaIfEcAN", - "output": "total 4\ndrwxr-xr-x 2 vercel-sandbox vercel-sandbox 22 Aug 3 07:55 .\ndrwxr-xr-x 3 vercel-sandbox vercel-sandbox 28 Aug 3 07:55 ..\n-rw-r--r-- 1 vercel-sandbox vercel-sandbox 194 Aug 3 07:55 SKILL.md\n./.agents/skills/e2e-flue-skill/SKILL.md", - "type": "function_call_output" - }, - { - "arguments": "{\"path\":\"./.agents/skills/e2e-flue-skill/SKILL.md\",\"offset\":1,\"limit\":200}", - "call_id": "call_KXcpmIy4Cbfugk4SNT2Ge7sG", - "id": "fc_054a16a2f2ea3217016a705b6d71cc81a38ac06634a309a997", - "name": "read", - "type": "function_call" - }, - { - "call_id": "call_KXcpmIy4Cbfugk4SNT2Ge7sG", - "output": "---\nname: e2e-flue-skill\ndescription: Deterministic skill used by the Braintrust Flue instrumentation e2e test.\n---\n\nReturn the marker from the provided args exactly once. Output no other text.\n", - "type": "function_call_output" - }, - { - "arguments": "{\"command\":\"cat ./package.json\",\"timeout\":100000}", - "call_id": "call_xQybhnQYuflmfp7gyAScVKjY", - "id": "fc_054a16a2f2ea3217016a705b6e90f481a39bc44496fbadf441", - "name": "bash", - "type": "function_call" - }, - { - "call_id": "call_xQybhnQYuflmfp7gyAScVKjY", - "output": "v24.14.1\ntotal 244\ndrwxr-xr-x 7 vercel-sandbox vercel-sandbox 4096 Aug 3 09:06 .\ndrwxr-xr-x 46 vercel-sandbox vercel-sandbox 4096 Aug 3 07:55 ..\ndrwxr-xr-x 3 vercel-sandbox vercel-sandbox 20 Aug 3 07:55 .agents\ndrwxr-xr-x 3 vercel-sandbox vercel-sandbox 38 Aug 3 07:55 .flue\ndrwxr-xr-x 2 vercel-sandbox vercel-sandbox 218 Aug 3 09:11 __cassettes__\ndrwxr-xr-x 2 vercel-sandbox vercel-sandbox 4096 Aug 3 08:49 __snapshots__\n-rw-r--r-- 1 vercel-sandbox vercel-sandbox 12484 Aug 3 08:45 assertions.ts\n-rw-r--r-- 1 vercel-sandbox vercel-sandbox 380 Aug 3 07:55 cassette-filter.mjs\n-rw-r--r-- 1 vercel-sandbox vercel-sandbox 215 Aug 3 07:55 constants.mjs\n-rw-r--r-- 1 vercel-sandbox vercel-sandbox 2000 Aug 3 09:06 package.json\n-rw-r--r-- 1 vercel-sandbox vercel-sandbox 167098 Aug 3 09:06 pnpm-lock.yaml\n-rw-r--r-- 1 vercel-sandbox vercel-sandbox 123 Aug 3 07:55 scenario.cli.mjs\n-rw-r--r-- 1 vercel-sandbox vercel-sandbox 6877 Aug 3 09:06 scenario.impl.mjs\n-rw-r--r-- 1 vercel-sandbox vercel-sandbox 203 Aug 3 07:55 scenario.mjs\n-rw-r--r-- 1 vercel-sandbox vercel-sandbox 8066 Aug 3 09:09 scenario.test.ts\n-rw-r--r-- 1 vercel-sandbox vercel-sandbox 121 Aug 3 07:55 scenario.ts\n-rw-r--r-- 1 vercel-sandbox vercel-sandbox 2376 Aug 3 08:49 scenario.v2.ts\n-rw-r--r-- 1 vercel-sandbox vercel-sandbox 4616 Aug 3 08:50 v2-assertions.ts\ndrwxr-xr-x 4 vercel-sandbox vercel-sandbox 26 Aug 3 08:06 versions\n{\n \"name\": \"flue-instrumentation-scenario\",\n \"private\": true,\n \"type\": \"module\",\n \"braintrustScenario\": {\n \"bump\": {\n \"dependencies\": {\n \"flue-cli-v0-8-latest\": {\n \"package\": \"@flue/cli\",\n \"range\": \">=0.8.0 <0.9.0\"\n },\n \"flue-cli-v1-latest\": {\n \"package\": \"@flue/cli\",\n \"range\": \">=1.0.0-beta.3 <2.0.0-0\",\n \"allowPrerelease\": true\n },\n \"flue-runtime-v0-8-latest\": {\n \"package\": \"@flue/runtime\",\n \"range\": \">=0.8.0 <0.9.0\"\n },\n \"flue-runtime-v1-latest\": {\n \"package\": \"@flue/runtime\",\n \"range\": \">=1.0.0-beta.3 <2.0.0-0\",\n \"allowPrerelease\": true\n },\n \"flue-runtime-v2-latest\": {\n \"package\": \"@flue/runtime\",\n \"range\": \">=2.0.0 <3.0.0\"\n }\n }\n }\n },\n \"dependencies\": {\n \"@flue/cli\": \"0.8.0\",\n \"@flue/runtime\": \"0.8.0\",\n \"flue-cli-v0-8-latest\": \"npm:@flue/cli@0.8.1\",\n \"flue-cli-v1\": \"npm:@flue/cli@1.0.0-beta.3\",\n \"flue-cli-v1-latest\": \"npm:@flue/cli@1.0.0-beta.9\",\n \"flue-runtime-v0-8-latest\": \"npm:@flue/runtime@0.8.1\",\n \"flue-runtime-v1\": \"npm:@flue/runtime@1.0.0-beta.3\",\n \"flue-runtime-v1-latest\": \"npm:@flue/runtime@1.0.0-beta.9\",\n \"flue-runtime-v2\": \"npm:@flue/runtime@2.0.0\",\n \"flue-runtime-v2-latest\": \"npm:@flue/runtime@2.0.1\",\n \"pi-ai-v2\": \"npm:@earendil-works/pi-ai@0.83.0\",\n \"valibot\": \"1.4.2\"\n },\n \"pnpm\": {\n \"overrides\": {\n \"@flue/runtime@0.8.0>@earendil-works/pi-agent-core\": \"0.75.4\",\n \"@flue/runtime@0.8.0>@earendil-works/pi-ai\": \"0.75.4\",\n \"@flue/runtime@0.8.1>@earendil-works/pi-agent-core\": \"0.75.4\",\n \"@flue/runtime@0.8.1>@earendil-works/pi-ai\": \"0.75.4\",\n \"@hono/node-server@>=2.0.0 <2.0.10\": \"^2.0.10\",\n \"fast-uri@<3.1.4\": \"^3.1.4\",\n \"hono@>=4.0.0 <4.12.27\": \"^4.12.27\",\n \"brace-expansion@>=3.0.0 <5.0.8\": \"^5.0.8\",\n \"postcss@<8.5.18\": \"^8.5.18\",\n \"valibot@<1.4.2\": \"^1.4.2\"\n }\n }\n}", - "type": "function_call_output" - }, - { - "arguments": "{\"command\":\"printf SKILL_DONE\",\"timeout\":100000}", - "call_id": "call_7fRJCCDIZe4x17WaroXZwmjV", - "id": "fc_054a16a2f2ea3217016a705b6fb64c81a38ff895c9553c8763", - "name": "bash", - "type": "function_call" - }, - { - "call_id": "call_7fRJCCDIZe4x17WaroXZwmjV", - "output": "node:internal/modules/package_json_reader:301\n throw new ERR_MODULE_NOT_FOUND(packageName, fileURLToPath(base), null);\n ^\n\nError [ERR_MODULE_NOT_FOUND]: Cannot find package '@flue/cli' imported from ./scenario.impl.mjs\n at Object.getPackageJSONURL (node:internal/modules/package_json_reader:301:9)\n at packageResolve (node:internal/modules/esm/resolve:768:81)\n at moduleResolve (node:internal/modules/esm/resolve:859:18)\n at defaultResolve (node:internal/modules/esm/resolve:991:11)\n at #cachedDefaultResolve (node:internal/modules/esm/loader:719:20)\n at #resolveAndMaybeBlockOnLoaderThread (node:internal/modules/esm/loader:736:38)\n at ModuleLoader.resolveSync (node:internal/modules/esm/loader:765:52)\n at Object.resolve (node:internal/modules/esm/initialize_import_meta:37:25)\n at file://./scenario.impl.mjs:12:33\n at ModuleJob.run (node:internal/modules/esm/module_job:430:25) {\n code: 'ERR_MODULE_NOT_FOUND'\n}\n\nNode.js v24.14.1\n\nCommand exited with code 1", - "type": "function_call_output" - } - ], - "model": "gpt-5.4-nano", - "prompt_cache_key": "[REDACTED]", - "reasoning": { - "effort": "none" - }, - "store": false, - "stream": true, - "tools": [ - { - "description": "Read a file or list a directory. For files, output is truncated to 2000 lines or 50KB — use offset/limit for large files. For directories, returns the list of entries.", - "name": "read", - "parameters": { - "properties": { - "limit": { - "description": "Maximum number of lines to read", - "type": "number" - }, - "offset": { - "description": "Line number to start from (1-indexed)", - "type": "number" - }, - "path": { - "description": "Path to the file to read", - "type": "string" - } - }, - "required": ["path"], - "type": "object" - }, - "strict": false, - "type": "function" - }, - { - "description": "Write content to a file. Creates the file and parent directories if they do not exist.", - "name": "write", - "parameters": { - "properties": { - "content": { - "description": "Content to write to the file", - "type": "string" - }, - "path": { - "description": "Path to the file to write", - "type": "string" - } - }, - "required": ["path", "content"], - "type": "object" - }, - "strict": false, - "type": "function" - }, - { - "description": "Edit a file using exact text replacement. The oldText must match a unique region of the file. Use replaceAll to replace all occurrences.", - "name": "edit", - "parameters": { - "properties": { - "newText": { - "description": "Replacement text", - "type": "string" - }, - "oldText": { - "description": "Exact text to find (must be unique)", - "type": "string" - }, - "path": { - "description": "Path to the file to edit", - "type": "string" - }, - "replaceAll": { - "description": "Replace all occurrences", - "type": "boolean" - } - }, - "required": ["path", "oldText", "newText"], - "type": "object" - }, - "strict": false, - "type": "function" - }, - { - "description": "Execute a bash command. Returns stdout and stderr. Output is truncated to the last 2000 lines or 50KB.", - "name": "bash", - "parameters": { - "properties": { - "command": { - "description": "Bash command to execute", - "type": "string" - }, - "timeout": { - "description": "Timeout in seconds", - "type": "number" - } - }, - "required": ["command"], - "type": "object" - }, - "strict": false, - "type": "function" - }, - { - "description": "Search file contents for a regex pattern. Returns matching lines with file paths and line numbers.", - "name": "grep", - "parameters": { - "properties": { - "include": { - "description": "Glob filter, e.g. \"*.ts\"", - "type": "string" - }, - "path": { - "description": "Directory or file to search (default: .)", - "type": "string" - }, - "pattern": { - "description": "Search pattern (regex)", - "type": "string" - } - }, - "required": ["pattern"], - "type": "object" - }, - "strict": false, - "type": "function" - }, - { - "description": "Find files by filename pattern using shell find -name semantics. Returns matching file paths.", - "name": "glob", - "parameters": { - "properties": { - "path": { - "description": "Directory to search in (default: .)", - "type": "string" - }, - "pattern": { - "description": "Filename pattern, e.g. \"*.ts\"", - "type": "string" - } - }, - "required": ["pattern"], - "type": "object" - }, - "strict": false, - "type": "function" - }, - { - "description": "Delegate a focused task to a detached child agent with its own context. Use this for independent research, file exploration, or parallel work. The task returns only its final answer to this conversation. No subagents are currently defined.", - "name": "task", - "parameters": { - "properties": { - "agent": { - "description": "Declared subagent to use for the child agent", - "type": "string" - }, - "cwd": { - "description": "Working directory for the child agent. AGENTS.md and skills are discovered from here.", - "type": "string" - }, - "description": { - "description": "Short human-readable label for the delegated work", - "type": "string" - }, - "prompt": { - "description": "Focused instructions for the child agent", - "type": "string" - } - }, - "required": ["prompt"], - "type": "object" - }, - "strict": false, - "type": "function" - } - ] - } - }, - "headers": {}, - "method": "POST", - "url": "https://api.openai.com/v1/responses" - }, - "response": { - "body": { - "chunks": [ - "event: response.created\ndata: {\"type\":\"response.created\",\"response\":{\"id\":\"resp_054a16a2f2ea3217016a705b70474881a39a050b8889ca15cd\",\"object\":\"response\",\"created_at\":1785748336,\"status\":\"in_progress\",\"background\":false,\"completed_at\":null,\"error\":null,\"frequency_penalty\":0,\"incomplete_details\":null,\"instructions\":null,\"max_output_tokens\":null,\"max_tool_calls\":null,\"model\":\"gpt-5.4-nano-2026-03-17\",\"moderation\":null,\"output\":[],\"parallel_tool_calls\":true,\"presence_penalty\":0,\"previous_response_id\":null,\"prompt_cache_key\":\"workflow:instrumentation:01KZ3E9TWTAEK4P4P3F63HKAKC::default::ta\",\"prompt_cache_retention\":\"24h\",\"reasoning\":{\"context\":\"current_turn\",\"effort\":\"none\",\"mode\":\"standard\",\"summary\":null},\"safety_identifier\":null,\"service_tier\":\"auto\",\"store\":false,\"temperature\":1,\"text\":{\"format\":{\"type\":\"text\"},\"verbosity\":\"medium\"},\"tool_choice\":\"auto\",\"tool_usage\":{\"image_gen\":{\"input_tokens\":0,\"input_tokens_details\":{\"image_tokens\":0,\"text_tokens\":0},\"output_tokens\":0,\"output_tokens_details\":{\"image_tokens\":0,\"text_tokens\":0},\"total_tokens\":0},\"web_search\":{\"num_requests\":0}},\"tools\":[{\"type\":\"function\",\"description\":\"Read a file or list a directory. For files, output is truncated to 2000 lines or 50KB — use offset/limit for large files. For directories, returns the list of entries.\",\"name\":\"read\",\"output_schema\":null,\"parameters\":{\"type\":\"object\",\"required\":[\"path\"],\"properties\":{\"path\":{\"type\":\"string\",\"description\":\"Path to the file to read\"},\"offset\":{\"type\":\"number\",\"description\":\"Line number to start from (1-indexed)\"},\"limit\":{\"type\":\"number\",\"description\":\"Maximum number of lines to read\"}}},\"strict\":false},{\"type\":\"function\",\"description\":\"Write content to a file. Creates the file and parent directories if they do not exist.\",\"name\":\"write\",\"output_schema\":null,\"parameters\":{\"type\":\"object\",\"required\":[\"path\",\"content\"],\"properties\":{\"path\":{\"type\":\"string\",\"description\":\"Path to the file to write\"},\"content\":{\"type\":\"string\",\"description\":\"Content to write to the file\"}}},\"strict\":false},{\"type\":\"function\",\"description\":\"Edit a file using exact text replacement. The oldText must match a unique region of the file. Use replaceAll to replace all occurrences.\",\"name\":\"edit\",\"output_schema\":null,\"parameters\":{\"type\":\"object\",\"required\":[\"path\",\"oldText\",\"newText\"],\"properties\":{\"path\":{\"type\":\"string\",\"description\":\"Path to the file to edit\"},\"oldText\":{\"type\":\"string\",\"description\":\"Exact text to find (must be unique)\"},\"newText\":{\"type\":\"string\",\"description\":\"Replacement text\"},\"replaceAll\":{\"type\":\"boolean\",\"description\":\"Replace all occurrences\"}}},\"strict\":false},{\"type\":\"function\",\"description\":\"Execute a bash command. Returns stdout and stderr. Output is truncated to the last 2000 lines or 50KB.\",\"name\":\"bash\",\"output_schema\":null,\"parameters\":{\"type\":\"object\",\"required\":[\"command\"],\"properties\":{\"command\":{\"type\":\"string\",\"description\":\"Bash command to execute\"},\"timeout\":{\"type\":\"number\",\"description\":\"Timeout in seconds\"}}},\"strict\":false},{\"type\":\"function\",\"description\":\"Search file contents for a regex pattern. Returns matching lines with file paths and line numbers.\",\"name\":\"grep\",\"output_schema\":null,\"parameters\":{\"type\":\"object\",\"required\":[\"pattern\"],\"properties\":{\"pattern\":{\"type\":\"string\",\"description\":\"Search pattern (regex)\"},\"path\":{\"type\":\"string\",\"description\":\"Directory or file to search (default: .)\"},\"include\":{\"type\":\"string\",\"description\":\"Glob filter, e.g. \\\"*.ts\\\"\"}}},\"strict\":false},{\"type\":\"function\",\"description\":\"Find files by filename pattern using shell find -name semantics. Returns matching file paths.\",\"name\":\"glob\",\"output_schema\":null,\"parameters\":{\"type\":\"object\",\"required\":[\"pattern\"],\"properties\":{\"pattern\":{\"type\":\"string\",\"description\":\"Filename pattern, e.g. \\\"*.ts\\\"\"},\"path\":{\"type\":\"string\",\"description\":\"Directory to search in (default: .)\"}}},\"strict\":false},{\"type\":\"function\",\"description\":\"Delegate a focused task to a detached child agent with its own context. Use this for independent research, file exploration, or parallel work. The task returns only its final answer to this conversation. No subagents are currently defined.\",\"name\":\"task\",\"output_schema\":null,\"parameters\":{\"type\":\"object\",\"required\":[\"prompt\"],\"properties\":{\"description\":{\"type\":\"string\",\"description\":\"Short human-readable label for the delegated work\"},\"prompt\":{\"type\":\"string\",\"description\":\"Focused instructions for the child agent\"},\"agent\":{\"type\":\"string\",\"description\":\"Declared subagent to use for the child agent\"},\"cwd\":{\"type\":\"string\",\"description\":\"Working directory for the child agent. AGENTS.md and skills are discovered from here.\"}}},\"strict\":false}],\"top_logprobs\":0,\"top_p\":0.98,\"truncation\":\"disabled\",\"usage\":null,\"user\":null,\"metadata\":{}},\"sequence_number\":0}", - "event: response.in_progress\ndata: {\"type\":\"response.in_progress\",\"response\":{\"id\":\"resp_054a16a2f2ea3217016a705b70474881a39a050b8889ca15cd\",\"object\":\"response\",\"created_at\":1785748336,\"status\":\"in_progress\",\"background\":false,\"completed_at\":null,\"error\":null,\"frequency_penalty\":0,\"incomplete_details\":null,\"instructions\":null,\"max_output_tokens\":null,\"max_tool_calls\":null,\"model\":\"gpt-5.4-nano-2026-03-17\",\"moderation\":null,\"output\":[],\"parallel_tool_calls\":true,\"presence_penalty\":0,\"previous_response_id\":null,\"prompt_cache_key\":\"workflow:instrumentation:01KZ3E9TWTAEK4P4P3F63HKAKC::default::ta\",\"prompt_cache_retention\":\"24h\",\"reasoning\":{\"context\":\"current_turn\",\"effort\":\"none\",\"mode\":\"standard\",\"summary\":null},\"safety_identifier\":null,\"service_tier\":\"auto\",\"store\":false,\"temperature\":1,\"text\":{\"format\":{\"type\":\"text\"},\"verbosity\":\"medium\"},\"tool_choice\":\"auto\",\"tool_usage\":{\"image_gen\":{\"input_tokens\":0,\"input_tokens_details\":{\"image_tokens\":0,\"text_tokens\":0},\"output_tokens\":0,\"output_tokens_details\":{\"image_tokens\":0,\"text_tokens\":0},\"total_tokens\":0},\"web_search\":{\"num_requests\":0}},\"tools\":[{\"type\":\"function\",\"description\":\"Read a file or list a directory. For files, output is truncated to 2000 lines or 50KB — use offset/limit for large files. For directories, returns the list of entries.\",\"name\":\"read\",\"output_schema\":null,\"parameters\":{\"type\":\"object\",\"required\":[\"path\"],\"properties\":{\"path\":{\"type\":\"string\",\"description\":\"Path to the file to read\"},\"offset\":{\"type\":\"number\",\"description\":\"Line number to start from (1-indexed)\"},\"limit\":{\"type\":\"number\",\"description\":\"Maximum number of lines to read\"}}},\"strict\":false},{\"type\":\"function\",\"description\":\"Write content to a file. Creates the file and parent directories if they do not exist.\",\"name\":\"write\",\"output_schema\":null,\"parameters\":{\"type\":\"object\",\"required\":[\"path\",\"content\"],\"properties\":{\"path\":{\"type\":\"string\",\"description\":\"Path to the file to write\"},\"content\":{\"type\":\"string\",\"description\":\"Content to write to the file\"}}},\"strict\":false},{\"type\":\"function\",\"description\":\"Edit a file using exact text replacement. The oldText must match a unique region of the file. Use replaceAll to replace all occurrences.\",\"name\":\"edit\",\"output_schema\":null,\"parameters\":{\"type\":\"object\",\"required\":[\"path\",\"oldText\",\"newText\"],\"properties\":{\"path\":{\"type\":\"string\",\"description\":\"Path to the file to edit\"},\"oldText\":{\"type\":\"string\",\"description\":\"Exact text to find (must be unique)\"},\"newText\":{\"type\":\"string\",\"description\":\"Replacement text\"},\"replaceAll\":{\"type\":\"boolean\",\"description\":\"Replace all occurrences\"}}},\"strict\":false},{\"type\":\"function\",\"description\":\"Execute a bash command. Returns stdout and stderr. Output is truncated to the last 2000 lines or 50KB.\",\"name\":\"bash\",\"output_schema\":null,\"parameters\":{\"type\":\"object\",\"required\":[\"command\"],\"properties\":{\"command\":{\"type\":\"string\",\"description\":\"Bash command to execute\"},\"timeout\":{\"type\":\"number\",\"description\":\"Timeout in seconds\"}}},\"strict\":false},{\"type\":\"function\",\"description\":\"Search file contents for a regex pattern. Returns matching lines with file paths and line numbers.\",\"name\":\"grep\",\"output_schema\":null,\"parameters\":{\"type\":\"object\",\"required\":[\"pattern\"],\"properties\":{\"pattern\":{\"type\":\"string\",\"description\":\"Search pattern (regex)\"},\"path\":{\"type\":\"string\",\"description\":\"Directory or file to search (default: .)\"},\"include\":{\"type\":\"string\",\"description\":\"Glob filter, e.g. \\\"*.ts\\\"\"}}},\"strict\":false},{\"type\":\"function\",\"description\":\"Find files by filename pattern using shell find -name semantics. Returns matching file paths.\",\"name\":\"glob\",\"output_schema\":null,\"parameters\":{\"type\":\"object\",\"required\":[\"pattern\"],\"properties\":{\"pattern\":{\"type\":\"string\",\"description\":\"Filename pattern, e.g. \\\"*.ts\\\"\"},\"path\":{\"type\":\"string\",\"description\":\"Directory to search in (default: .)\"}}},\"strict\":false},{\"type\":\"function\",\"description\":\"Delegate a focused task to a detached child agent with its own context. Use this for independent research, file exploration, or parallel work. The task returns only its final answer to this conversation. No subagents are currently defined.\",\"name\":\"task\",\"output_schema\":null,\"parameters\":{\"type\":\"object\",\"required\":[\"prompt\"],\"properties\":{\"description\":{\"type\":\"string\",\"description\":\"Short human-readable label for the delegated work\"},\"prompt\":{\"type\":\"string\",\"description\":\"Focused instructions for the child agent\"},\"agent\":{\"type\":\"string\",\"description\":\"Declared subagent to use for the child agent\"},\"cwd\":{\"type\":\"string\",\"description\":\"Working directory for the child agent. AGENTS.md and skills are discovered from here.\"}}},\"strict\":false}],\"top_logprobs\":0,\"top_p\":0.98,\"truncation\":\"disabled\",\"usage\":null,\"user\":null,\"metadata\":{}},\"sequence_number\":1}", - "event: response.output_item.added\ndata: {\"type\":\"response.output_item.added\",\"item\":{\"id\":\"msg_054a16a2f2ea3217016a705b70a41481a38ece40ecddf69512\",\"type\":\"message\",\"status\":\"in_progress\",\"content\":[],\"phase\":\"final_answer\",\"role\":\"assistant\"},\"output_index\":0,\"sequence_number\":2}", - "event: response.content_part.added\ndata: {\"type\":\"response.content_part.added\",\"content_index\":0,\"item_id\":\"msg_054a16a2f2ea3217016a705b70a41481a38ece40ecddf69512\",\"output_index\":0,\"part\":{\"type\":\"output_text\",\"annotations\":[],\"logprobs\":[],\"text\":\"\"},\"sequence_number\":3}", - "event: response.output_text.delta\ndata: {\"type\":\"response.output_text.delta\",\"content_index\":0,\"delta\":\"SK\",\"item_id\":\"msg_054a16a2f2ea3217016a705b70a41481a38ece40ecddf69512\",\"logprobs\":[],\"obfuscation\":\"M9SWfj095W22jm\",\"output_index\":0,\"sequence_number\":4}", - "event: response.output_text.delta\ndata: {\"type\":\"response.output_text.delta\",\"content_index\":0,\"delta\":\"ILL\",\"item_id\":\"msg_054a16a2f2ea3217016a705b70a41481a38ece40ecddf69512\",\"logprobs\":[],\"obfuscation\":\"NnKtLGfgvufIN\",\"output_index\":0,\"sequence_number\":5}", - "event: response.output_text.delta\ndata: {\"type\":\"response.output_text.delta\",\"content_index\":0,\"delta\":\"_DONE\",\"item_id\":\"msg_054a16a2f2ea3217016a705b70a41481a38ece40ecddf69512\",\"logprobs\":[],\"obfuscation\":\"wHpr2r6Jc26\",\"output_index\":0,\"sequence_number\":6}", - "event: response.output_text.done\ndata: {\"type\":\"response.output_text.done\",\"content_index\":0,\"item_id\":\"msg_054a16a2f2ea3217016a705b70a41481a38ece40ecddf69512\",\"logprobs\":[],\"output_index\":0,\"sequence_number\":7,\"text\":\"SKILL_DONE\"}", - "event: response.content_part.done\ndata: {\"type\":\"response.content_part.done\",\"content_index\":0,\"item_id\":\"msg_054a16a2f2ea3217016a705b70a41481a38ece40ecddf69512\",\"output_index\":0,\"part\":{\"type\":\"output_text\",\"annotations\":[],\"logprobs\":[],\"text\":\"SKILL_DONE\"},\"sequence_number\":8}", - "event: response.output_item.done\ndata: {\"type\":\"response.output_item.done\",\"item\":{\"id\":\"msg_054a16a2f2ea3217016a705b70a41481a38ece40ecddf69512\",\"type\":\"message\",\"status\":\"completed\",\"content\":[{\"type\":\"output_text\",\"annotations\":[],\"logprobs\":[],\"text\":\"SKILL_DONE\"}],\"phase\":\"final_answer\",\"role\":\"assistant\"},\"output_index\":0,\"sequence_number\":9}", - "event: response.completed\ndata: {\"type\":\"response.completed\",\"response\":{\"id\":\"resp_054a16a2f2ea3217016a705b70474881a39a050b8889ca15cd\",\"object\":\"response\",\"created_at\":1785748336,\"status\":\"completed\",\"background\":false,\"completed_at\":1785748336,\"error\":null,\"frequency_penalty\":0,\"incomplete_details\":null,\"instructions\":null,\"max_output_tokens\":null,\"max_tool_calls\":null,\"model\":\"gpt-5.4-nano-2026-03-17\",\"moderation\":null,\"output\":[{\"id\":\"msg_054a16a2f2ea3217016a705b70a41481a38ece40ecddf69512\",\"type\":\"message\",\"status\":\"completed\",\"content\":[{\"type\":\"output_text\",\"annotations\":[],\"logprobs\":[],\"text\":\"SKILL_DONE\"}],\"phase\":\"final_answer\",\"role\":\"assistant\"}],\"parallel_tool_calls\":true,\"presence_penalty\":0,\"previous_response_id\":null,\"prompt_cache_key\":\"workflow:instrumentation:01KZ3E9TWTAEK4P4P3F63HKAKC::default::ta\",\"prompt_cache_retention\":\"24h\",\"reasoning\":{\"context\":\"current_turn\",\"effort\":\"none\",\"mode\":\"standard\",\"summary\":null},\"safety_identifier\":null,\"service_tier\":\"default\",\"store\":false,\"temperature\":1,\"text\":{\"format\":{\"type\":\"text\"},\"verbosity\":\"medium\"},\"tool_choice\":\"auto\",\"tool_usage\":{\"image_gen\":{\"input_tokens\":0,\"input_tokens_details\":{\"image_tokens\":0,\"text_tokens\":0},\"output_tokens\":0,\"output_tokens_details\":{\"image_tokens\":0,\"text_tokens\":0},\"total_tokens\":0},\"web_search\":{\"num_requests\":0}},\"tools\":[{\"type\":\"function\",\"description\":\"Read a file or list a directory. For files, output is truncated to 2000 lines or 50KB — use offset/limit for large files. For directories, returns the list of entries.\",\"name\":\"read\",\"output_schema\":null,\"parameters\":{\"type\":\"object\",\"required\":[\"path\"],\"properties\":{\"path\":{\"type\":\"string\",\"description\":\"Path to the file to read\"},\"offset\":{\"type\":\"number\",\"description\":\"Line number to start from (1-indexed)\"},\"limit\":{\"type\":\"number\",\"description\":\"Maximum number of lines to read\"}}},\"strict\":false},{\"type\":\"function\",\"description\":\"Write content to a file. Creates the file and parent directories if they do not exist.\",\"name\":\"write\",\"output_schema\":null,\"parameters\":{\"type\":\"object\",\"required\":[\"path\",\"content\"],\"properties\":{\"path\":{\"type\":\"string\",\"description\":\"Path to the file to write\"},\"content\":{\"type\":\"string\",\"description\":\"Content to write to the file\"}}},\"strict\":false},{\"type\":\"function\",\"description\":\"Edit a file using exact text replacement. The oldText must match a unique region of the file. Use replaceAll to replace all occurrences.\",\"name\":\"edit\",\"output_schema\":null,\"parameters\":{\"type\":\"object\",\"required\":[\"path\",\"oldText\",\"newText\"],\"properties\":{\"path\":{\"type\":\"string\",\"description\":\"Path to the file to edit\"},\"oldText\":{\"type\":\"string\",\"description\":\"Exact text to find (must be unique)\"},\"newText\":{\"type\":\"string\",\"description\":\"Replacement text\"},\"replaceAll\":{\"type\":\"boolean\",\"description\":\"Replace all occurrences\"}}},\"strict\":false},{\"type\":\"function\",\"description\":\"Execute a bash command. Returns stdout and stderr. Output is truncated to the last 2000 lines or 50KB.\",\"name\":\"bash\",\"output_schema\":null,\"parameters\":{\"type\":\"object\",\"required\":[\"command\"],\"properties\":{\"command\":{\"type\":\"string\",\"description\":\"Bash command to execute\"},\"timeout\":{\"type\":\"number\",\"description\":\"Timeout in seconds\"}}},\"strict\":false},{\"type\":\"function\",\"description\":\"Search file contents for a regex pattern. Returns matching lines with file paths and line numbers.\",\"name\":\"grep\",\"output_schema\":null,\"parameters\":{\"type\":\"object\",\"required\":[\"pattern\"],\"properties\":{\"pattern\":{\"type\":\"string\",\"description\":\"Search pattern (regex)\"},\"path\":{\"type\":\"string\",\"description\":\"Directory or file to search (default: .)\"},\"include\":{\"type\":\"string\",\"description\":\"Glob filter, e.g. \\\"*.ts\\\"\"}}},\"strict\":false},{\"type\":\"function\",\"description\":\"Find files by filename pattern using shell find -name semantics. Returns matching file paths.\",\"name\":\"glob\",\"output_schema\":null,\"parameters\":{\"type\":\"object\",\"required\":[\"pattern\"],\"properties\":{\"pattern\":{\"type\":\"string\",\"description\":\"Filename pattern, e.g. \\\"*.ts\\\"\"},\"path\":{\"type\":\"string\",\"description\":\"Directory to search in (default: .)\"}}},\"strict\":false},{\"type\":\"function\",\"description\":\"Delegate a focused task to a detached child agent with its own context. Use this for independent research, file exploration, or parallel work. The task returns only its final answer to this conversation. No subagents are currently defined.\",\"name\":\"task\",\"output_schema\":null,\"parameters\":{\"type\":\"object\",\"required\":[\"prompt\"],\"properties\":{\"description\":{\"type\":\"string\",\"description\":\"Short human-readable label for the delegated work\"},\"prompt\":{\"type\":\"string\",\"description\":\"Focused instructions for the child agent\"},\"agent\":{\"type\":\"string\",\"description\":\"Declared subagent to use for the child agent\"},\"cwd\":{\"type\":\"string\",\"description\":\"Working directory for the child agent. AGENTS.md and skills are discovered from here.\"}}},\"strict\":false}],\"top_logprobs\":0,\"top_p\":0.98,\"truncation\":\"disabled\",\"usage\":{\"input_tokens\":3812,\"input_tokens_details\":{\"cache_write_tokens\":0,\"cached_tokens\":2560},\"output_tokens\":7,\"output_tokens_details\":{\"reasoning_tokens\":0},\"total_tokens\":3819},\"user\":null,\"metadata\":{}},\"sequence_number\":10}" - ], - "kind": "sse" - }, - "headers": { - "access-control-expose-headers": "X-Request-ID, CF-Ray, CF-Ray", - "alt-svc": "h3=\":443\"; ma=86400", - "cf-cache-status": "DYNAMIC", - "cf-ray": "a254331d7fe07bb6-IAD", - "connection": "keep-alive", - "content-type": "text/event-stream; charset=utf-8", - "date": "Mon, 03 Aug 2026 09:12:16 GMT", - "openai-organization": "braintrust-data", - "openai-processing-ms": "201", - "openai-project": "proj_vsCSXafhhByzWOThMrJcZiw9", - "openai-version": "2020-10-01", - "server": "cloudflare", - "set-cookie": "[REDACTED]", - "strict-transport-security": "max-age=31536000; includeSubDomains; preload", - "transfer-encoding": "chunked", - "x-content-type-options": "nosniff", - "x-ratelimit-limit-requests": "30000", - "x-ratelimit-limit-tokens": "180000000", - "x-ratelimit-remaining-requests": "29999", - "x-ratelimit-remaining-tokens": "179995734", - "x-ratelimit-reset-requests": "2ms", - "x-ratelimit-reset-tokens": "1ms", - "x-request-id": "req_6dc67fd2f6124704a9d86e5e918bac37" - }, - "status": 200, - "statusText": "OK" - } - }, - { - "callIndex": 15, - "id": "b3f219cac4c52d6c", - "matchKey": "POST api.openai.com/v1/responses", - "recordedAt": "2026-08-03T09:12:17.515Z", - "request": { - "body": { - "kind": "json", - "value": { - "input": [ - { - "content": "You are running in headless mode with no human operator. Work autonomously — never ask questions, never wait for user input. Make your best judgment and proceed independently.\n\nYou are a deterministic Flue instrumentation test agent. Follow user instructions exactly. When asked for a marker, output only that marker and no extra text. When running a local skill file, read it yourself and do not delegate it to a task.\n\n## Available Skills\n\nThe following skills provide specialized instructions for specific tasks. When a task matches a skill description, activate that skill before proceeding so its full instructions are loaded. Skill instructions and supporting resources stay lazy until activation or explicit file reads.\n\n- **e2e-flue-skill** — Deterministic skill used by the Braintrust Flue instrumentation e2e test.\n\nDate: Mon, Aug 3, 2026\nWorking directory: .\n\nDirectory structure:\n.agents\n.flue\n.flue-build\n__cassettes__\n__snapshots__\nassertions.ts\ncassette-filter.mjs\nconstants.mjs\ndist\nnode_modules\npackage.json\npnpm-lock.yaml\nscenario.cli.mjs\nscenario.impl.mjs\nscenario.mjs\nscenario.test.ts\nscenario.ts\nscenario.v2.ts\nv2-assertions.ts\nversions", - "role": "developer" - }, - { - "content": [ - { - "text": "Run the skill named \"e2e-flue-skill\".\n\nArguments:\n{\n \"marker\": \"SKILL_DONE\"\n}", - "type": "input_text" - } - ], - "role": "user" - }, - { - "arguments": "{\"description\":\"Run the deterministic Flue instrumentation e2e test skill e2e-flue-skill.\",\"prompt\":\"Run the skill named \\\"e2e-flue-skill\\\" with arguments {\\\"marker\\\":\\\"SKILL_DONE\\\"}. Output only the marker and no extra text when finished.\",\"cwd\":\".\"}", - "call_id": "call_R2a5LOml0NZqq5XTcTCKfbMW", - "id": "fc_0f099b3fce0721fd016a705b63c99081a3af4dfbf2ce0314eb", - "name": "task", - "type": "function_call" - }, - { - "call_id": "call_R2a5LOml0NZqq5XTcTCKfbMW", - "output": "SKILL_DONE", - "type": "function_call_output" - } - ], - "model": "gpt-5.4-nano", - "prompt_cache_key": "[REDACTED]", - "reasoning": { - "effort": "none" - }, - "store": false, - "stream": true, - "tools": [ - { - "description": "Read a file or list a directory. For files, output is truncated to 2000 lines or 50KB — use offset/limit for large files. For directories, returns the list of entries.", - "name": "read", - "parameters": { - "properties": { - "limit": { - "description": "Maximum number of lines to read", - "type": "number" - }, - "offset": { - "description": "Line number to start from (1-indexed)", - "type": "number" - }, - "path": { - "description": "Path to the file to read", - "type": "string" - } - }, - "required": ["path"], - "type": "object" - }, - "strict": false, - "type": "function" - }, - { - "description": "Write content to a file. Creates the file and parent directories if they do not exist.", - "name": "write", - "parameters": { - "properties": { - "content": { - "description": "Content to write to the file", - "type": "string" - }, - "path": { - "description": "Path to the file to write", - "type": "string" - } - }, - "required": ["path", "content"], - "type": "object" - }, - "strict": false, - "type": "function" - }, - { - "description": "Edit a file using exact text replacement. The oldText must match a unique region of the file. Use replaceAll to replace all occurrences.", - "name": "edit", - "parameters": { - "properties": { - "newText": { - "description": "Replacement text", - "type": "string" - }, - "oldText": { - "description": "Exact text to find (must be unique)", - "type": "string" - }, - "path": { - "description": "Path to the file to edit", - "type": "string" - }, - "replaceAll": { - "description": "Replace all occurrences", - "type": "boolean" - } - }, - "required": ["path", "oldText", "newText"], - "type": "object" - }, - "strict": false, - "type": "function" - }, - { - "description": "Execute a bash command. Returns stdout and stderr. Output is truncated to the last 2000 lines or 50KB.", - "name": "bash", - "parameters": { - "properties": { - "command": { - "description": "Bash command to execute", - "type": "string" - }, - "timeout": { - "description": "Timeout in seconds", - "type": "number" - } - }, - "required": ["command"], - "type": "object" - }, - "strict": false, - "type": "function" - }, - { - "description": "Search file contents for a regex pattern. Returns matching lines with file paths and line numbers.", - "name": "grep", - "parameters": { - "properties": { - "include": { - "description": "Glob filter, e.g. \"*.ts\"", - "type": "string" - }, - "path": { - "description": "Directory or file to search (default: .)", - "type": "string" - }, - "pattern": { - "description": "Search pattern (regex)", - "type": "string" - } - }, - "required": ["pattern"], - "type": "object" - }, - "strict": false, - "type": "function" - }, - { - "description": "Find files by filename pattern using shell find -name semantics. Returns matching file paths.", - "name": "glob", - "parameters": { - "properties": { - "path": { - "description": "Directory to search in (default: .)", - "type": "string" - }, - "pattern": { - "description": "Filename pattern, e.g. \"*.ts\"", - "type": "string" - } - }, - "required": ["pattern"], - "type": "object" - }, - "strict": false, - "type": "function" - }, - { - "description": "Delegate a focused task to a detached child agent with its own context. Use this for independent research, file exploration, or parallel work. The task returns only its final answer to this conversation. No subagents are currently defined.", - "name": "task", - "parameters": { - "properties": { - "agent": { - "description": "Declared subagent to use for the child agent", - "type": "string" - }, - "cwd": { - "description": "Working directory for the child agent. AGENTS.md and skills are discovered from here.", - "type": "string" - }, - "description": { - "description": "Short human-readable label for the delegated work", - "type": "string" - }, - "prompt": { - "description": "Focused instructions for the child agent", - "type": "string" - } - }, - "required": ["prompt"], - "type": "object" - }, - "strict": false, - "type": "function" - } - ] - } - }, - "headers": {}, - "method": "POST", - "url": "https://api.openai.com/v1/responses" - }, - "response": { - "body": { - "chunks": [ - "event: response.created\ndata: {\"type\":\"response.created\",\"response\":{\"id\":\"resp_0f099b3fce0721fd016a705b70d65881a3b5bc873ea54e7c00\",\"object\":\"response\",\"created_at\":1785748336,\"status\":\"in_progress\",\"background\":false,\"completed_at\":null,\"error\":null,\"frequency_penalty\":0,\"incomplete_details\":null,\"instructions\":null,\"max_output_tokens\":null,\"max_tool_calls\":null,\"model\":\"gpt-5.4-nano-2026-03-17\",\"moderation\":null,\"output\":[],\"parallel_tool_calls\":true,\"presence_penalty\":0,\"previous_response_id\":null,\"prompt_cache_key\":\"workflow:instrumentation:01KZ3E9TWTAEK4P4P3F63HKAKC::default::sk\",\"prompt_cache_retention\":\"24h\",\"reasoning\":{\"context\":\"current_turn\",\"effort\":\"none\",\"mode\":\"standard\",\"summary\":null},\"safety_identifier\":null,\"service_tier\":\"auto\",\"store\":false,\"temperature\":1,\"text\":{\"format\":{\"type\":\"text\"},\"verbosity\":\"medium\"},\"tool_choice\":\"auto\",\"tool_usage\":{\"image_gen\":{\"input_tokens\":0,\"input_tokens_details\":{\"image_tokens\":0,\"text_tokens\":0},\"output_tokens\":0,\"output_tokens_details\":{\"image_tokens\":0,\"text_tokens\":0},\"total_tokens\":0},\"web_search\":{\"num_requests\":0}},\"tools\":[{\"type\":\"function\",\"description\":\"Read a file or list a directory. For files, output is truncated to 2000 lines or 50KB — use offset/limit for large files. For directories, returns the list of entries.\",\"name\":\"read\",\"output_schema\":null,\"parameters\":{\"type\":\"object\",\"required\":[\"path\"],\"properties\":{\"path\":{\"type\":\"string\",\"description\":\"Path to the file to read\"},\"offset\":{\"type\":\"number\",\"description\":\"Line number to start from (1-indexed)\"},\"limit\":{\"type\":\"number\",\"description\":\"Maximum number of lines to read\"}}},\"strict\":false},{\"type\":\"function\",\"description\":\"Write content to a file. Creates the file and parent directories if they do not exist.\",\"name\":\"write\",\"output_schema\":null,\"parameters\":{\"type\":\"object\",\"required\":[\"path\",\"content\"],\"properties\":{\"path\":{\"type\":\"string\",\"description\":\"Path to the file to write\"},\"content\":{\"type\":\"string\",\"description\":\"Content to write to the file\"}}},\"strict\":false},{\"type\":\"function\",\"description\":\"Edit a file using exact text replacement. The oldText must match a unique region of the file. Use replaceAll to replace all occurrences.\",\"name\":\"edit\",\"output_schema\":null,\"parameters\":{\"type\":\"object\",\"required\":[\"path\",\"oldText\",\"newText\"],\"properties\":{\"path\":{\"type\":\"string\",\"description\":\"Path to the file to edit\"},\"oldText\":{\"type\":\"string\",\"description\":\"Exact text to find (must be unique)\"},\"newText\":{\"type\":\"string\",\"description\":\"Replacement text\"},\"replaceAll\":{\"type\":\"boolean\",\"description\":\"Replace all occurrences\"}}},\"strict\":false},{\"type\":\"function\",\"description\":\"Execute a bash command. Returns stdout and stderr. Output is truncated to the last 2000 lines or 50KB.\",\"name\":\"bash\",\"output_schema\":null,\"parameters\":{\"type\":\"object\",\"required\":[\"command\"],\"properties\":{\"command\":{\"type\":\"string\",\"description\":\"Bash command to execute\"},\"timeout\":{\"type\":\"number\",\"description\":\"Timeout in seconds\"}}},\"strict\":false},{\"type\":\"function\",\"description\":\"Search file contents for a regex pattern. Returns matching lines with file paths and line numbers.\",\"name\":\"grep\",\"output_schema\":null,\"parameters\":{\"type\":\"object\",\"required\":[\"pattern\"],\"properties\":{\"pattern\":{\"type\":\"string\",\"description\":\"Search pattern (regex)\"},\"path\":{\"type\":\"string\",\"description\":\"Directory or file to search (default: .)\"},\"include\":{\"type\":\"string\",\"description\":\"Glob filter, e.g. \\\"*.ts\\\"\"}}},\"strict\":false},{\"type\":\"function\",\"description\":\"Find files by filename pattern using shell find -name semantics. Returns matching file paths.\",\"name\":\"glob\",\"output_schema\":null,\"parameters\":{\"type\":\"object\",\"required\":[\"pattern\"],\"properties\":{\"pattern\":{\"type\":\"string\",\"description\":\"Filename pattern, e.g. \\\"*.ts\\\"\"},\"path\":{\"type\":\"string\",\"description\":\"Directory to search in (default: .)\"}}},\"strict\":false},{\"type\":\"function\",\"description\":\"Delegate a focused task to a detached child agent with its own context. Use this for independent research, file exploration, or parallel work. The task returns only its final answer to this conversation. No subagents are currently defined.\",\"name\":\"task\",\"output_schema\":null,\"parameters\":{\"type\":\"object\",\"required\":[\"prompt\"],\"properties\":{\"description\":{\"type\":\"string\",\"description\":\"Short human-readable label for the delegated work\"},\"prompt\":{\"type\":\"string\",\"description\":\"Focused instructions for the child agent\"},\"agent\":{\"type\":\"string\",\"description\":\"Declared subagent to use for the child agent\"},\"cwd\":{\"type\":\"string\",\"description\":\"Working directory for the child agent. AGENTS.md and skills are discovered from here.\"}}},\"strict\":false}],\"top_logprobs\":0,\"top_p\":0.98,\"truncation\":\"disabled\",\"usage\":null,\"user\":null,\"metadata\":{}},\"sequence_number\":0}", - "event: response.in_progress\ndata: {\"type\":\"response.in_progress\",\"response\":{\"id\":\"resp_0f099b3fce0721fd016a705b70d65881a3b5bc873ea54e7c00\",\"object\":\"response\",\"created_at\":1785748336,\"status\":\"in_progress\",\"background\":false,\"completed_at\":null,\"error\":null,\"frequency_penalty\":0,\"incomplete_details\":null,\"instructions\":null,\"max_output_tokens\":null,\"max_tool_calls\":null,\"model\":\"gpt-5.4-nano-2026-03-17\",\"moderation\":null,\"output\":[],\"parallel_tool_calls\":true,\"presence_penalty\":0,\"previous_response_id\":null,\"prompt_cache_key\":\"workflow:instrumentation:01KZ3E9TWTAEK4P4P3F63HKAKC::default::sk\",\"prompt_cache_retention\":\"24h\",\"reasoning\":{\"context\":\"current_turn\",\"effort\":\"none\",\"mode\":\"standard\",\"summary\":null},\"safety_identifier\":null,\"service_tier\":\"auto\",\"store\":false,\"temperature\":1,\"text\":{\"format\":{\"type\":\"text\"},\"verbosity\":\"medium\"},\"tool_choice\":\"auto\",\"tool_usage\":{\"image_gen\":{\"input_tokens\":0,\"input_tokens_details\":{\"image_tokens\":0,\"text_tokens\":0},\"output_tokens\":0,\"output_tokens_details\":{\"image_tokens\":0,\"text_tokens\":0},\"total_tokens\":0},\"web_search\":{\"num_requests\":0}},\"tools\":[{\"type\":\"function\",\"description\":\"Read a file or list a directory. For files, output is truncated to 2000 lines or 50KB — use offset/limit for large files. For directories, returns the list of entries.\",\"name\":\"read\",\"output_schema\":null,\"parameters\":{\"type\":\"object\",\"required\":[\"path\"],\"properties\":{\"path\":{\"type\":\"string\",\"description\":\"Path to the file to read\"},\"offset\":{\"type\":\"number\",\"description\":\"Line number to start from (1-indexed)\"},\"limit\":{\"type\":\"number\",\"description\":\"Maximum number of lines to read\"}}},\"strict\":false},{\"type\":\"function\",\"description\":\"Write content to a file. Creates the file and parent directories if they do not exist.\",\"name\":\"write\",\"output_schema\":null,\"parameters\":{\"type\":\"object\",\"required\":[\"path\",\"content\"],\"properties\":{\"path\":{\"type\":\"string\",\"description\":\"Path to the file to write\"},\"content\":{\"type\":\"string\",\"description\":\"Content to write to the file\"}}},\"strict\":false},{\"type\":\"function\",\"description\":\"Edit a file using exact text replacement. The oldText must match a unique region of the file. Use replaceAll to replace all occurrences.\",\"name\":\"edit\",\"output_schema\":null,\"parameters\":{\"type\":\"object\",\"required\":[\"path\",\"oldText\",\"newText\"],\"properties\":{\"path\":{\"type\":\"string\",\"description\":\"Path to the file to edit\"},\"oldText\":{\"type\":\"string\",\"description\":\"Exact text to find (must be unique)\"},\"newText\":{\"type\":\"string\",\"description\":\"Replacement text\"},\"replaceAll\":{\"type\":\"boolean\",\"description\":\"Replace all occurrences\"}}},\"strict\":false},{\"type\":\"function\",\"description\":\"Execute a bash command. Returns stdout and stderr. Output is truncated to the last 2000 lines or 50KB.\",\"name\":\"bash\",\"output_schema\":null,\"parameters\":{\"type\":\"object\",\"required\":[\"command\"],\"properties\":{\"command\":{\"type\":\"string\",\"description\":\"Bash command to execute\"},\"timeout\":{\"type\":\"number\",\"description\":\"Timeout in seconds\"}}},\"strict\":false},{\"type\":\"function\",\"description\":\"Search file contents for a regex pattern. Returns matching lines with file paths and line numbers.\",\"name\":\"grep\",\"output_schema\":null,\"parameters\":{\"type\":\"object\",\"required\":[\"pattern\"],\"properties\":{\"pattern\":{\"type\":\"string\",\"description\":\"Search pattern (regex)\"},\"path\":{\"type\":\"string\",\"description\":\"Directory or file to search (default: .)\"},\"include\":{\"type\":\"string\",\"description\":\"Glob filter, e.g. \\\"*.ts\\\"\"}}},\"strict\":false},{\"type\":\"function\",\"description\":\"Find files by filename pattern using shell find -name semantics. Returns matching file paths.\",\"name\":\"glob\",\"output_schema\":null,\"parameters\":{\"type\":\"object\",\"required\":[\"pattern\"],\"properties\":{\"pattern\":{\"type\":\"string\",\"description\":\"Filename pattern, e.g. \\\"*.ts\\\"\"},\"path\":{\"type\":\"string\",\"description\":\"Directory to search in (default: .)\"}}},\"strict\":false},{\"type\":\"function\",\"description\":\"Delegate a focused task to a detached child agent with its own context. Use this for independent research, file exploration, or parallel work. The task returns only its final answer to this conversation. No subagents are currently defined.\",\"name\":\"task\",\"output_schema\":null,\"parameters\":{\"type\":\"object\",\"required\":[\"prompt\"],\"properties\":{\"description\":{\"type\":\"string\",\"description\":\"Short human-readable label for the delegated work\"},\"prompt\":{\"type\":\"string\",\"description\":\"Focused instructions for the child agent\"},\"agent\":{\"type\":\"string\",\"description\":\"Declared subagent to use for the child agent\"},\"cwd\":{\"type\":\"string\",\"description\":\"Working directory for the child agent. AGENTS.md and skills are discovered from here.\"}}},\"strict\":false}],\"top_logprobs\":0,\"top_p\":0.98,\"truncation\":\"disabled\",\"usage\":null,\"user\":null,\"metadata\":{}},\"sequence_number\":1}", - "event: response.output_item.added\ndata: {\"type\":\"response.output_item.added\",\"item\":{\"id\":\"msg_0f099b3fce0721fd016a705b71558881a38c53a767e09d9e31\",\"type\":\"message\",\"status\":\"in_progress\",\"content\":[],\"phase\":\"final_answer\",\"role\":\"assistant\"},\"output_index\":0,\"sequence_number\":2}", - "event: response.content_part.added\ndata: {\"type\":\"response.content_part.added\",\"content_index\":0,\"item_id\":\"msg_0f099b3fce0721fd016a705b71558881a38c53a767e09d9e31\",\"output_index\":0,\"part\":{\"type\":\"output_text\",\"annotations\":[],\"logprobs\":[],\"text\":\"\"},\"sequence_number\":3}", - "event: response.output_text.delta\ndata: {\"type\":\"response.output_text.delta\",\"content_index\":0,\"delta\":\"SK\",\"item_id\":\"msg_0f099b3fce0721fd016a705b71558881a38c53a767e09d9e31\",\"logprobs\":[],\"obfuscation\":\"iSZ7rWanncJQ68\",\"output_index\":0,\"sequence_number\":4}", - "event: response.output_text.delta\ndata: {\"type\":\"response.output_text.delta\",\"content_index\":0,\"delta\":\"ILL\",\"item_id\":\"msg_0f099b3fce0721fd016a705b71558881a38c53a767e09d9e31\",\"logprobs\":[],\"obfuscation\":\"PQly4lHaqRXSu\",\"output_index\":0,\"sequence_number\":5}", - "event: response.output_text.delta\ndata: {\"type\":\"response.output_text.delta\",\"content_index\":0,\"delta\":\"_DONE\",\"item_id\":\"msg_0f099b3fce0721fd016a705b71558881a38c53a767e09d9e31\",\"logprobs\":[],\"obfuscation\":\"WSIPPwqqidY\",\"output_index\":0,\"sequence_number\":6}", - "event: response.output_text.done\ndata: {\"type\":\"response.output_text.done\",\"content_index\":0,\"item_id\":\"msg_0f099b3fce0721fd016a705b71558881a38c53a767e09d9e31\",\"logprobs\":[],\"output_index\":0,\"sequence_number\":7,\"text\":\"SKILL_DONE\"}", - "event: response.content_part.done\ndata: {\"type\":\"response.content_part.done\",\"content_index\":0,\"item_id\":\"msg_0f099b3fce0721fd016a705b71558881a38c53a767e09d9e31\",\"output_index\":0,\"part\":{\"type\":\"output_text\",\"annotations\":[],\"logprobs\":[],\"text\":\"SKILL_DONE\"},\"sequence_number\":8}", - "event: response.output_item.done\ndata: {\"type\":\"response.output_item.done\",\"item\":{\"id\":\"msg_0f099b3fce0721fd016a705b71558881a38c53a767e09d9e31\",\"type\":\"message\",\"status\":\"completed\",\"content\":[{\"type\":\"output_text\",\"annotations\":[],\"logprobs\":[],\"text\":\"SKILL_DONE\"}],\"phase\":\"final_answer\",\"role\":\"assistant\"},\"output_index\":0,\"sequence_number\":9}", - "event: response.completed\ndata: {\"type\":\"response.completed\",\"response\":{\"id\":\"resp_0f099b3fce0721fd016a705b70d65881a3b5bc873ea54e7c00\",\"object\":\"response\",\"created_at\":1785748336,\"status\":\"completed\",\"background\":false,\"completed_at\":1785748337,\"error\":null,\"frequency_penalty\":0,\"incomplete_details\":null,\"instructions\":null,\"max_output_tokens\":null,\"max_tool_calls\":null,\"model\":\"gpt-5.4-nano-2026-03-17\",\"moderation\":null,\"output\":[{\"id\":\"msg_0f099b3fce0721fd016a705b71558881a38c53a767e09d9e31\",\"type\":\"message\",\"status\":\"completed\",\"content\":[{\"type\":\"output_text\",\"annotations\":[],\"logprobs\":[],\"text\":\"SKILL_DONE\"}],\"phase\":\"final_answer\",\"role\":\"assistant\"}],\"parallel_tool_calls\":true,\"presence_penalty\":0,\"previous_response_id\":null,\"prompt_cache_key\":\"workflow:instrumentation:01KZ3E9TWTAEK4P4P3F63HKAKC::default::sk\",\"prompt_cache_retention\":\"24h\",\"reasoning\":{\"context\":\"current_turn\",\"effort\":\"none\",\"mode\":\"standard\",\"summary\":null},\"safety_identifier\":null,\"service_tier\":\"default\",\"store\":false,\"temperature\":1,\"text\":{\"format\":{\"type\":\"text\"},\"verbosity\":\"medium\"},\"tool_choice\":\"auto\",\"tool_usage\":{\"image_gen\":{\"input_tokens\":0,\"input_tokens_details\":{\"image_tokens\":0,\"text_tokens\":0},\"output_tokens\":0,\"output_tokens_details\":{\"image_tokens\":0,\"text_tokens\":0},\"total_tokens\":0},\"web_search\":{\"num_requests\":0}},\"tools\":[{\"type\":\"function\",\"description\":\"Read a file or list a directory. For files, output is truncated to 2000 lines or 50KB — use offset/limit for large files. For directories, returns the list of entries.\",\"name\":\"read\",\"output_schema\":null,\"parameters\":{\"type\":\"object\",\"required\":[\"path\"],\"properties\":{\"path\":{\"type\":\"string\",\"description\":\"Path to the file to read\"},\"offset\":{\"type\":\"number\",\"description\":\"Line number to start from (1-indexed)\"},\"limit\":{\"type\":\"number\",\"description\":\"Maximum number of lines to read\"}}},\"strict\":false},{\"type\":\"function\",\"description\":\"Write content to a file. Creates the file and parent directories if they do not exist.\",\"name\":\"write\",\"output_schema\":null,\"parameters\":{\"type\":\"object\",\"required\":[\"path\",\"content\"],\"properties\":{\"path\":{\"type\":\"string\",\"description\":\"Path to the file to write\"},\"content\":{\"type\":\"string\",\"description\":\"Content to write to the file\"}}},\"strict\":false},{\"type\":\"function\",\"description\":\"Edit a file using exact text replacement. The oldText must match a unique region of the file. Use replaceAll to replace all occurrences.\",\"name\":\"edit\",\"output_schema\":null,\"parameters\":{\"type\":\"object\",\"required\":[\"path\",\"oldText\",\"newText\"],\"properties\":{\"path\":{\"type\":\"string\",\"description\":\"Path to the file to edit\"},\"oldText\":{\"type\":\"string\",\"description\":\"Exact text to find (must be unique)\"},\"newText\":{\"type\":\"string\",\"description\":\"Replacement text\"},\"replaceAll\":{\"type\":\"boolean\",\"description\":\"Replace all occurrences\"}}},\"strict\":false},{\"type\":\"function\",\"description\":\"Execute a bash command. Returns stdout and stderr. Output is truncated to the last 2000 lines or 50KB.\",\"name\":\"bash\",\"output_schema\":null,\"parameters\":{\"type\":\"object\",\"required\":[\"command\"],\"properties\":{\"command\":{\"type\":\"string\",\"description\":\"Bash command to execute\"},\"timeout\":{\"type\":\"number\",\"description\":\"Timeout in seconds\"}}},\"strict\":false},{\"type\":\"function\",\"description\":\"Search file contents for a regex pattern. Returns matching lines with file paths and line numbers.\",\"name\":\"grep\",\"output_schema\":null,\"parameters\":{\"type\":\"object\",\"required\":[\"pattern\"],\"properties\":{\"pattern\":{\"type\":\"string\",\"description\":\"Search pattern (regex)\"},\"path\":{\"type\":\"string\",\"description\":\"Directory or file to search (default: .)\"},\"include\":{\"type\":\"string\",\"description\":\"Glob filter, e.g. \\\"*.ts\\\"\"}}},\"strict\":false},{\"type\":\"function\",\"description\":\"Find files by filename pattern using shell find -name semantics. Returns matching file paths.\",\"name\":\"glob\",\"output_schema\":null,\"parameters\":{\"type\":\"object\",\"required\":[\"pattern\"],\"properties\":{\"pattern\":{\"type\":\"string\",\"description\":\"Filename pattern, e.g. \\\"*.ts\\\"\"},\"path\":{\"type\":\"string\",\"description\":\"Directory to search in (default: .)\"}}},\"strict\":false},{\"type\":\"function\",\"description\":\"Delegate a focused task to a detached child agent with its own context. Use this for independent research, file exploration, or parallel work. The task returns only its final answer to this conversation. No subagents are currently defined.\",\"name\":\"task\",\"output_schema\":null,\"parameters\":{\"type\":\"object\",\"required\":[\"prompt\"],\"properties\":{\"description\":{\"type\":\"string\",\"description\":\"Short human-readable label for the delegated work\"},\"prompt\":{\"type\":\"string\",\"description\":\"Focused instructions for the child agent\"},\"agent\":{\"type\":\"string\",\"description\":\"Declared subagent to use for the child agent\"},\"cwd\":{\"type\":\"string\",\"description\":\"Working directory for the child agent. AGENTS.md and skills are discovered from here.\"}}},\"strict\":false}],\"top_logprobs\":0,\"top_p\":0.98,\"truncation\":\"disabled\",\"usage\":{\"input_tokens\":969,\"input_tokens_details\":{\"cache_write_tokens\":0,\"cached_tokens\":0},\"output_tokens\":7,\"output_tokens_details\":{\"reasoning_tokens\":0},\"total_tokens\":976},\"user\":null,\"metadata\":{}},\"sequence_number\":10}" - ], - "kind": "sse" - }, - "headers": { - "access-control-expose-headers": "X-Request-ID, CF-Ray, CF-Ray", - "alt-svc": "h3=\":443\"; ma=86400", - "cf-cache-status": "DYNAMIC", - "cf-ray": "a2543320eec07bb6-IAD", - "connection": "keep-alive", - "content-type": "text/event-stream; charset=utf-8", - "date": "Mon, 03 Aug 2026 09:12:17 GMT", - "openai-organization": "braintrust-data", - "openai-processing-ms": "226", - "openai-project": "proj_vsCSXafhhByzWOThMrJcZiw9", - "openai-version": "2020-10-01", - "server": "cloudflare", - "set-cookie": "[REDACTED]", - "strict-transport-security": "max-age=31536000; includeSubDomains; preload", - "transfer-encoding": "chunked", - "x-content-type-options": "nosniff", - "x-ratelimit-limit-requests": "30000", - "x-ratelimit-limit-tokens": "180000000", - "x-ratelimit-remaining-requests": "29999", - "x-ratelimit-remaining-tokens": "179998575", - "x-ratelimit-reset-requests": "2ms", - "x-ratelimit-reset-tokens": "0s", - "x-request-id": "req_0f63631954824ce89d221047a6871115" - }, - "status": 200, - "statusText": "OK" - } - }, - { - "callIndex": 16, - "id": "eea2c2f028018d92", - "matchKey": "POST api.openai.com/v1/responses", - "recordedAt": "2026-08-03T09:12:18.781Z", - "request": { - "body": { - "kind": "json", - "value": { - "input": [ - { - "content": "You are running in headless mode with no human operator. Work autonomously — never ask questions, never wait for user input. Make your best judgment and proceed independently.\n\nYou are a deterministic Flue instrumentation test agent. Follow user instructions exactly. When asked for a marker, output only that marker and no extra text. When running a local skill file, read it yourself and do not delegate it to a task.\n\n## Available Skills\n\nThe following skills provide specialized instructions for specific tasks. When a task matches a skill description, activate that skill before proceeding so its full instructions are loaded. Skill instructions and supporting resources stay lazy until activation or explicit file reads.\n\n- **e2e-flue-skill** — Deterministic skill used by the Braintrust Flue instrumentation e2e test.\n\nDate: Mon, Aug 3, 2026\nWorking directory: .\n\nDirectory structure:\n.agents\n.flue\n.flue-build\n__cassettes__\n__snapshots__\nassertions.ts\ncassette-filter.mjs\nconstants.mjs\ndist\nnode_modules\npackage.json\npnpm-lock.yaml\nscenario.cli.mjs\nscenario.impl.mjs\nscenario.mjs\nscenario.test.ts\nscenario.ts\nscenario.v2.ts\nv2-assertions.ts\nversions", - "role": "system" - }, - { - "content": [ - { - "text": "Reply with exactly TASK_DONE and no other text.", - "type": "input_text" - } - ], - "role": "user" - } - ], - "model": "gpt-4o-mini", - "prompt_cache_key": "[REDACTED]", - "store": false, - "stream": true, - "tools": [ - { - "description": "Read a file or list a directory. For files, output is truncated to 2000 lines or 50KB — use offset/limit for large files. For directories, returns the list of entries.", - "name": "read", - "parameters": { - "properties": { - "limit": { - "description": "Maximum number of lines to read", - "type": "number" - }, - "offset": { - "description": "Line number to start from (1-indexed)", - "type": "number" - }, - "path": { - "description": "Path to the file to read", - "type": "string" - } - }, - "required": ["path"], - "type": "object" - }, - "strict": false, - "type": "function" - }, - { - "description": "Write content to a file. Creates the file and parent directories if they do not exist.", - "name": "write", - "parameters": { - "properties": { - "content": { - "description": "Content to write to the file", - "type": "string" - }, - "path": { - "description": "Path to the file to write", - "type": "string" - } - }, - "required": ["path", "content"], - "type": "object" - }, - "strict": false, - "type": "function" - }, - { - "description": "Edit a file using exact text replacement. The oldText must match a unique region of the file. Use replaceAll to replace all occurrences.", - "name": "edit", - "parameters": { - "properties": { - "newText": { - "description": "Replacement text", - "type": "string" - }, - "oldText": { - "description": "Exact text to find (must be unique)", - "type": "string" - }, - "path": { - "description": "Path to the file to edit", - "type": "string" - }, - "replaceAll": { - "description": "Replace all occurrences", - "type": "boolean" - } - }, - "required": ["path", "oldText", "newText"], - "type": "object" - }, - "strict": false, - "type": "function" - }, - { - "description": "Execute a bash command. Returns stdout and stderr. Output is truncated to the last 2000 lines or 50KB.", - "name": "bash", - "parameters": { - "properties": { - "command": { - "description": "Bash command to execute", - "type": "string" - }, - "timeout": { - "description": "Timeout in seconds", - "type": "number" - } - }, - "required": ["command"], - "type": "object" - }, - "strict": false, - "type": "function" - }, - { - "description": "Search file contents for a regex pattern. Returns matching lines with file paths and line numbers.", - "name": "grep", - "parameters": { - "properties": { - "include": { - "description": "Glob filter, e.g. \"*.ts\"", - "type": "string" - }, - "path": { - "description": "Directory or file to search (default: .)", - "type": "string" - }, - "pattern": { - "description": "Search pattern (regex)", - "type": "string" - } - }, - "required": ["pattern"], - "type": "object" - }, - "strict": false, - "type": "function" - }, - { - "description": "Find files by filename pattern using shell find -name semantics. Returns matching file paths.", - "name": "glob", - "parameters": { - "properties": { - "path": { - "description": "Directory to search in (default: .)", - "type": "string" - }, - "pattern": { - "description": "Filename pattern, e.g. \"*.ts\"", - "type": "string" - } - }, - "required": ["pattern"], - "type": "object" - }, - "strict": false, - "type": "function" - }, - { - "description": "Delegate a focused task to a detached child agent with its own context. Use this for independent research, file exploration, or parallel work. The task returns only its final answer to this conversation. No subagents are currently defined.", - "name": "task", - "parameters": { - "properties": { - "agent": { - "description": "Declared subagent to use for the child agent", - "type": "string" - }, - "cwd": { - "description": "Working directory for the child agent. AGENTS.md and skills are discovered from here.", - "type": "string" - }, - "description": { - "description": "Short human-readable label for the delegated work", - "type": "string" - }, - "prompt": { - "description": "Focused instructions for the child agent", - "type": "string" - } - }, - "required": ["prompt"], - "type": "object" - }, - "strict": false, - "type": "function" - } - ] - } - }, - "headers": {}, - "method": "POST", - "url": "https://api.openai.com/v1/responses" - }, - "response": { - "body": { - "chunks": [ - "event: response.created\ndata: {\"type\":\"response.created\",\"response\":{\"id\":\"resp_0f3adfdb1510c165016a705b7192bc81a094e1dbada8d8b42b\",\"object\":\"response\",\"created_at\":1785748337,\"status\":\"in_progress\",\"background\":false,\"completed_at\":null,\"error\":null,\"frequency_penalty\":0,\"incomplete_details\":null,\"instructions\":null,\"max_output_tokens\":null,\"max_tool_calls\":null,\"model\":\"gpt-4o-mini-2024-07-18\",\"moderation\":null,\"output\":[],\"parallel_tool_calls\":true,\"presence_penalty\":0,\"previous_response_id\":null,\"prompt_cache_key\":\"workflow:instrumentation:01KZ3E9TWTAEK4P4P3F63HKAKC::default::ta\",\"prompt_cache_retention\":\"in_memory\",\"reasoning\":{\"context\":null,\"effort\":null,\"summary\":null},\"safety_identifier\":null,\"service_tier\":\"auto\",\"store\":false,\"temperature\":1,\"text\":{\"format\":{\"type\":\"text\"},\"verbosity\":\"medium\"},\"tool_choice\":\"auto\",\"tool_usage\":{\"image_gen\":{\"input_tokens\":0,\"input_tokens_details\":{\"image_tokens\":0,\"text_tokens\":0},\"output_tokens\":0,\"output_tokens_details\":{\"image_tokens\":0,\"text_tokens\":0},\"total_tokens\":0},\"web_search\":{\"num_requests\":0}},\"tools\":[{\"type\":\"function\",\"description\":\"Read a file or list a directory. For files, output is truncated to 2000 lines or 50KB — use offset/limit for large files. For directories, returns the list of entries.\",\"name\":\"read\",\"output_schema\":null,\"parameters\":{\"type\":\"object\",\"required\":[\"path\"],\"properties\":{\"path\":{\"type\":\"string\",\"description\":\"Path to the file to read\"},\"offset\":{\"type\":\"number\",\"description\":\"Line number to start from (1-indexed)\"},\"limit\":{\"type\":\"number\",\"description\":\"Maximum number of lines to read\"}}},\"strict\":false},{\"type\":\"function\",\"description\":\"Write content to a file. Creates the file and parent directories if they do not exist.\",\"name\":\"write\",\"output_schema\":null,\"parameters\":{\"type\":\"object\",\"required\":[\"path\",\"content\"],\"properties\":{\"path\":{\"type\":\"string\",\"description\":\"Path to the file to write\"},\"content\":{\"type\":\"string\",\"description\":\"Content to write to the file\"}}},\"strict\":false},{\"type\":\"function\",\"description\":\"Edit a file using exact text replacement. The oldText must match a unique region of the file. Use replaceAll to replace all occurrences.\",\"name\":\"edit\",\"output_schema\":null,\"parameters\":{\"type\":\"object\",\"required\":[\"path\",\"oldText\",\"newText\"],\"properties\":{\"path\":{\"type\":\"string\",\"description\":\"Path to the file to edit\"},\"oldText\":{\"type\":\"string\",\"description\":\"Exact text to find (must be unique)\"},\"newText\":{\"type\":\"string\",\"description\":\"Replacement text\"},\"replaceAll\":{\"type\":\"boolean\",\"description\":\"Replace all occurrences\"}}},\"strict\":false},{\"type\":\"function\",\"description\":\"Execute a bash command. Returns stdout and stderr. Output is truncated to the last 2000 lines or 50KB.\",\"name\":\"bash\",\"output_schema\":null,\"parameters\":{\"type\":\"object\",\"required\":[\"command\"],\"properties\":{\"command\":{\"type\":\"string\",\"description\":\"Bash command to execute\"},\"timeout\":{\"type\":\"number\",\"description\":\"Timeout in seconds\"}}},\"strict\":false},{\"type\":\"function\",\"description\":\"Search file contents for a regex pattern. Returns matching lines with file paths and line numbers.\",\"name\":\"grep\",\"output_schema\":null,\"parameters\":{\"type\":\"object\",\"required\":[\"pattern\"],\"properties\":{\"pattern\":{\"type\":\"string\",\"description\":\"Search pattern (regex)\"},\"path\":{\"type\":\"string\",\"description\":\"Directory or file to search (default: .)\"},\"include\":{\"type\":\"string\",\"description\":\"Glob filter, e.g. \\\"*.ts\\\"\"}}},\"strict\":false},{\"type\":\"function\",\"description\":\"Find files by filename pattern using shell find -name semantics. Returns matching file paths.\",\"name\":\"glob\",\"output_schema\":null,\"parameters\":{\"type\":\"object\",\"required\":[\"pattern\"],\"properties\":{\"pattern\":{\"type\":\"string\",\"description\":\"Filename pattern, e.g. \\\"*.ts\\\"\"},\"path\":{\"type\":\"string\",\"description\":\"Directory to search in (default: .)\"}}},\"strict\":false},{\"type\":\"function\",\"description\":\"Delegate a focused task to a detached child agent with its own context. Use this for independent research, file exploration, or parallel work. The task returns only its final answer to this conversation. No subagents are currently defined.\",\"name\":\"task\",\"output_schema\":null,\"parameters\":{\"type\":\"object\",\"required\":[\"prompt\"],\"properties\":{\"description\":{\"type\":\"string\",\"description\":\"Short human-readable label for the delegated work\"},\"prompt\":{\"type\":\"string\",\"description\":\"Focused instructions for the child agent\"},\"agent\":{\"type\":\"string\",\"description\":\"Declared subagent to use for the child agent\"},\"cwd\":{\"type\":\"string\",\"description\":\"Working directory for the child agent. AGENTS.md and skills are discovered from here.\"}}},\"strict\":false}],\"top_logprobs\":0,\"top_p\":1,\"truncation\":\"disabled\",\"usage\":null,\"user\":null,\"metadata\":{}},\"sequence_number\":0}", - "event: response.in_progress\ndata: {\"type\":\"response.in_progress\",\"response\":{\"id\":\"resp_0f3adfdb1510c165016a705b7192bc81a094e1dbada8d8b42b\",\"object\":\"response\",\"created_at\":1785748337,\"status\":\"in_progress\",\"background\":false,\"completed_at\":null,\"error\":null,\"frequency_penalty\":0,\"incomplete_details\":null,\"instructions\":null,\"max_output_tokens\":null,\"max_tool_calls\":null,\"model\":\"gpt-4o-mini-2024-07-18\",\"moderation\":null,\"output\":[],\"parallel_tool_calls\":true,\"presence_penalty\":0,\"previous_response_id\":null,\"prompt_cache_key\":\"workflow:instrumentation:01KZ3E9TWTAEK4P4P3F63HKAKC::default::ta\",\"prompt_cache_retention\":\"in_memory\",\"reasoning\":{\"context\":null,\"effort\":null,\"summary\":null},\"safety_identifier\":null,\"service_tier\":\"auto\",\"store\":false,\"temperature\":1,\"text\":{\"format\":{\"type\":\"text\"},\"verbosity\":\"medium\"},\"tool_choice\":\"auto\",\"tool_usage\":{\"image_gen\":{\"input_tokens\":0,\"input_tokens_details\":{\"image_tokens\":0,\"text_tokens\":0},\"output_tokens\":0,\"output_tokens_details\":{\"image_tokens\":0,\"text_tokens\":0},\"total_tokens\":0},\"web_search\":{\"num_requests\":0}},\"tools\":[{\"type\":\"function\",\"description\":\"Read a file or list a directory. For files, output is truncated to 2000 lines or 50KB — use offset/limit for large files. For directories, returns the list of entries.\",\"name\":\"read\",\"output_schema\":null,\"parameters\":{\"type\":\"object\",\"required\":[\"path\"],\"properties\":{\"path\":{\"type\":\"string\",\"description\":\"Path to the file to read\"},\"offset\":{\"type\":\"number\",\"description\":\"Line number to start from (1-indexed)\"},\"limit\":{\"type\":\"number\",\"description\":\"Maximum number of lines to read\"}}},\"strict\":false},{\"type\":\"function\",\"description\":\"Write content to a file. Creates the file and parent directories if they do not exist.\",\"name\":\"write\",\"output_schema\":null,\"parameters\":{\"type\":\"object\",\"required\":[\"path\",\"content\"],\"properties\":{\"path\":{\"type\":\"string\",\"description\":\"Path to the file to write\"},\"content\":{\"type\":\"string\",\"description\":\"Content to write to the file\"}}},\"strict\":false},{\"type\":\"function\",\"description\":\"Edit a file using exact text replacement. The oldText must match a unique region of the file. Use replaceAll to replace all occurrences.\",\"name\":\"edit\",\"output_schema\":null,\"parameters\":{\"type\":\"object\",\"required\":[\"path\",\"oldText\",\"newText\"],\"properties\":{\"path\":{\"type\":\"string\",\"description\":\"Path to the file to edit\"},\"oldText\":{\"type\":\"string\",\"description\":\"Exact text to find (must be unique)\"},\"newText\":{\"type\":\"string\",\"description\":\"Replacement text\"},\"replaceAll\":{\"type\":\"boolean\",\"description\":\"Replace all occurrences\"}}},\"strict\":false},{\"type\":\"function\",\"description\":\"Execute a bash command. Returns stdout and stderr. Output is truncated to the last 2000 lines or 50KB.\",\"name\":\"bash\",\"output_schema\":null,\"parameters\":{\"type\":\"object\",\"required\":[\"command\"],\"properties\":{\"command\":{\"type\":\"string\",\"description\":\"Bash command to execute\"},\"timeout\":{\"type\":\"number\",\"description\":\"Timeout in seconds\"}}},\"strict\":false},{\"type\":\"function\",\"description\":\"Search file contents for a regex pattern. Returns matching lines with file paths and line numbers.\",\"name\":\"grep\",\"output_schema\":null,\"parameters\":{\"type\":\"object\",\"required\":[\"pattern\"],\"properties\":{\"pattern\":{\"type\":\"string\",\"description\":\"Search pattern (regex)\"},\"path\":{\"type\":\"string\",\"description\":\"Directory or file to search (default: .)\"},\"include\":{\"type\":\"string\",\"description\":\"Glob filter, e.g. \\\"*.ts\\\"\"}}},\"strict\":false},{\"type\":\"function\",\"description\":\"Find files by filename pattern using shell find -name semantics. Returns matching file paths.\",\"name\":\"glob\",\"output_schema\":null,\"parameters\":{\"type\":\"object\",\"required\":[\"pattern\"],\"properties\":{\"pattern\":{\"type\":\"string\",\"description\":\"Filename pattern, e.g. \\\"*.ts\\\"\"},\"path\":{\"type\":\"string\",\"description\":\"Directory to search in (default: .)\"}}},\"strict\":false},{\"type\":\"function\",\"description\":\"Delegate a focused task to a detached child agent with its own context. Use this for independent research, file exploration, or parallel work. The task returns only its final answer to this conversation. No subagents are currently defined.\",\"name\":\"task\",\"output_schema\":null,\"parameters\":{\"type\":\"object\",\"required\":[\"prompt\"],\"properties\":{\"description\":{\"type\":\"string\",\"description\":\"Short human-readable label for the delegated work\"},\"prompt\":{\"type\":\"string\",\"description\":\"Focused instructions for the child agent\"},\"agent\":{\"type\":\"string\",\"description\":\"Declared subagent to use for the child agent\"},\"cwd\":{\"type\":\"string\",\"description\":\"Working directory for the child agent. AGENTS.md and skills are discovered from here.\"}}},\"strict\":false}],\"top_logprobs\":0,\"top_p\":1,\"truncation\":\"disabled\",\"usage\":null,\"user\":null,\"metadata\":{}},\"sequence_number\":1}", - "event: response.output_item.added\ndata: {\"type\":\"response.output_item.added\",\"item\":{\"id\":\"msg_0f3adfdb1510c165016a705b72843c81a0b30d242da683a882\",\"type\":\"message\",\"status\":\"in_progress\",\"content\":[],\"role\":\"assistant\"},\"output_index\":0,\"sequence_number\":2}", - "event: response.content_part.added\ndata: {\"type\":\"response.content_part.added\",\"content_index\":0,\"item_id\":\"msg_0f3adfdb1510c165016a705b72843c81a0b30d242da683a882\",\"output_index\":0,\"part\":{\"type\":\"output_text\",\"annotations\":[],\"logprobs\":[],\"text\":\"\"},\"sequence_number\":3}", - "event: response.output_text.delta\ndata: {\"type\":\"response.output_text.delta\",\"content_index\":0,\"delta\":\"TASK\",\"item_id\":\"msg_0f3adfdb1510c165016a705b72843c81a0b30d242da683a882\",\"logprobs\":[],\"obfuscation\":\"mlb5p2sBa8Nm\",\"output_index\":0,\"sequence_number\":4}", - "event: response.output_text.delta\ndata: {\"type\":\"response.output_text.delta\",\"content_index\":0,\"delta\":\"_DONE\",\"item_id\":\"msg_0f3adfdb1510c165016a705b72843c81a0b30d242da683a882\",\"logprobs\":[],\"obfuscation\":\"oWnwIAUMpIA\",\"output_index\":0,\"sequence_number\":5}", - "event: response.output_text.done\ndata: {\"type\":\"response.output_text.done\",\"content_index\":0,\"item_id\":\"msg_0f3adfdb1510c165016a705b72843c81a0b30d242da683a882\",\"logprobs\":[],\"output_index\":0,\"sequence_number\":6,\"text\":\"TASK_DONE\"}", - "event: response.content_part.done\ndata: {\"type\":\"response.content_part.done\",\"content_index\":0,\"item_id\":\"msg_0f3adfdb1510c165016a705b72843c81a0b30d242da683a882\",\"output_index\":0,\"part\":{\"type\":\"output_text\",\"annotations\":[],\"logprobs\":[],\"text\":\"TASK_DONE\"},\"sequence_number\":7}", - "event: response.output_item.done\ndata: {\"type\":\"response.output_item.done\",\"item\":{\"id\":\"msg_0f3adfdb1510c165016a705b72843c81a0b30d242da683a882\",\"type\":\"message\",\"status\":\"completed\",\"content\":[{\"type\":\"output_text\",\"annotations\":[],\"logprobs\":[],\"text\":\"TASK_DONE\"}],\"role\":\"assistant\"},\"output_index\":0,\"sequence_number\":8}", - "event: response.completed\ndata: {\"type\":\"response.completed\",\"response\":{\"id\":\"resp_0f3adfdb1510c165016a705b7192bc81a094e1dbada8d8b42b\",\"object\":\"response\",\"created_at\":1785748337,\"status\":\"completed\",\"background\":false,\"completed_at\":1785748338,\"error\":null,\"frequency_penalty\":0,\"incomplete_details\":null,\"instructions\":null,\"max_output_tokens\":null,\"max_tool_calls\":null,\"model\":\"gpt-4o-mini-2024-07-18\",\"moderation\":null,\"output\":[{\"id\":\"msg_0f3adfdb1510c165016a705b72843c81a0b30d242da683a882\",\"type\":\"message\",\"status\":\"completed\",\"content\":[{\"type\":\"output_text\",\"annotations\":[],\"logprobs\":[],\"text\":\"TASK_DONE\"}],\"role\":\"assistant\"}],\"parallel_tool_calls\":true,\"presence_penalty\":0,\"previous_response_id\":null,\"prompt_cache_key\":\"workflow:instrumentation:01KZ3E9TWTAEK4P4P3F63HKAKC::default::ta\",\"prompt_cache_retention\":\"in_memory\",\"reasoning\":{\"context\":null,\"effort\":null,\"summary\":null},\"safety_identifier\":null,\"service_tier\":\"default\",\"store\":false,\"temperature\":1,\"text\":{\"format\":{\"type\":\"text\"},\"verbosity\":\"medium\"},\"tool_choice\":\"auto\",\"tool_usage\":{\"image_gen\":{\"input_tokens\":0,\"input_tokens_details\":{\"image_tokens\":0,\"text_tokens\":0},\"output_tokens\":0,\"output_tokens_details\":{\"image_tokens\":0,\"text_tokens\":0},\"total_tokens\":0},\"web_search\":{\"num_requests\":0}},\"tools\":[{\"type\":\"function\",\"description\":\"Read a file or list a directory. For files, output is truncated to 2000 lines or 50KB — use offset/limit for large files. For directories, returns the list of entries.\",\"name\":\"read\",\"output_schema\":null,\"parameters\":{\"type\":\"object\",\"required\":[\"path\"],\"properties\":{\"path\":{\"type\":\"string\",\"description\":\"Path to the file to read\"},\"offset\":{\"type\":\"number\",\"description\":\"Line number to start from (1-indexed)\"},\"limit\":{\"type\":\"number\",\"description\":\"Maximum number of lines to read\"}}},\"strict\":false},{\"type\":\"function\",\"description\":\"Write content to a file. Creates the file and parent directories if they do not exist.\",\"name\":\"write\",\"output_schema\":null,\"parameters\":{\"type\":\"object\",\"required\":[\"path\",\"content\"],\"properties\":{\"path\":{\"type\":\"string\",\"description\":\"Path to the file to write\"},\"content\":{\"type\":\"string\",\"description\":\"Content to write to the file\"}}},\"strict\":false},{\"type\":\"function\",\"description\":\"Edit a file using exact text replacement. The oldText must match a unique region of the file. Use replaceAll to replace all occurrences.\",\"name\":\"edit\",\"output_schema\":null,\"parameters\":{\"type\":\"object\",\"required\":[\"path\",\"oldText\",\"newText\"],\"properties\":{\"path\":{\"type\":\"string\",\"description\":\"Path to the file to edit\"},\"oldText\":{\"type\":\"string\",\"description\":\"Exact text to find (must be unique)\"},\"newText\":{\"type\":\"string\",\"description\":\"Replacement text\"},\"replaceAll\":{\"type\":\"boolean\",\"description\":\"Replace all occurrences\"}}},\"strict\":false},{\"type\":\"function\",\"description\":\"Execute a bash command. Returns stdout and stderr. Output is truncated to the last 2000 lines or 50KB.\",\"name\":\"bash\",\"output_schema\":null,\"parameters\":{\"type\":\"object\",\"required\":[\"command\"],\"properties\":{\"command\":{\"type\":\"string\",\"description\":\"Bash command to execute\"},\"timeout\":{\"type\":\"number\",\"description\":\"Timeout in seconds\"}}},\"strict\":false},{\"type\":\"function\",\"description\":\"Search file contents for a regex pattern. Returns matching lines with file paths and line numbers.\",\"name\":\"grep\",\"output_schema\":null,\"parameters\":{\"type\":\"object\",\"required\":[\"pattern\"],\"properties\":{\"pattern\":{\"type\":\"string\",\"description\":\"Search pattern (regex)\"},\"path\":{\"type\":\"string\",\"description\":\"Directory or file to search (default: .)\"},\"include\":{\"type\":\"string\",\"description\":\"Glob filter, e.g. \\\"*.ts\\\"\"}}},\"strict\":false},{\"type\":\"function\",\"description\":\"Find files by filename pattern using shell find -name semantics. Returns matching file paths.\",\"name\":\"glob\",\"output_schema\":null,\"parameters\":{\"type\":\"object\",\"required\":[\"pattern\"],\"properties\":{\"pattern\":{\"type\":\"string\",\"description\":\"Filename pattern, e.g. \\\"*.ts\\\"\"},\"path\":{\"type\":\"string\",\"description\":\"Directory to search in (default: .)\"}}},\"strict\":false},{\"type\":\"function\",\"description\":\"Delegate a focused task to a detached child agent with its own context. Use this for independent research, file exploration, or parallel work. The task returns only its final answer to this conversation. No subagents are currently defined.\",\"name\":\"task\",\"output_schema\":null,\"parameters\":{\"type\":\"object\",\"required\":[\"prompt\"],\"properties\":{\"description\":{\"type\":\"string\",\"description\":\"Short human-readable label for the delegated work\"},\"prompt\":{\"type\":\"string\",\"description\":\"Focused instructions for the child agent\"},\"agent\":{\"type\":\"string\",\"description\":\"Declared subagent to use for the child agent\"},\"cwd\":{\"type\":\"string\",\"description\":\"Working directory for the child agent. AGENTS.md and skills are discovered from here.\"}}},\"strict\":false}],\"top_logprobs\":0,\"top_p\":1,\"truncation\":\"disabled\",\"usage\":{\"input_tokens\":826,\"input_tokens_details\":{\"cache_write_tokens\":0,\"cached_tokens\":0},\"output_tokens\":4,\"output_tokens_details\":{\"reasoning_tokens\":0},\"total_tokens\":830},\"user\":null,\"metadata\":{}},\"sequence_number\":9}" - ], - "kind": "sse" - }, - "headers": { - "access-control-expose-headers": "X-Request-ID, CF-Ray, CF-Ray", - "alt-svc": "h3=\":443\"; ma=86400", - "cf-cache-status": "DYNAMIC", - "cf-ray": "a254332588917bb6-IAD", - "connection": "keep-alive", - "content-type": "text/event-stream; charset=utf-8", - "date": "Mon, 03 Aug 2026 09:12:17 GMT", - "openai-organization": "braintrust-data", - "openai-processing-ms": "169", - "openai-project": "proj_vsCSXafhhByzWOThMrJcZiw9", - "openai-version": "2020-10-01", - "server": "cloudflare", - "set-cookie": "[REDACTED]", - "strict-transport-security": "max-age=31536000; includeSubDomains; preload", - "transfer-encoding": "chunked", - "x-content-type-options": "nosniff", - "x-request-id": "req_44ff6ef3357a4d78a0e4b836a0777717" - }, - "status": 200, - "statusText": "OK" - } - }, - { - "callIndex": 17, - "id": "dd8319160ba3e4cf", - "matchKey": "POST api.openai.com/v1/responses", - "recordedAt": "2026-08-03T09:12:19.721Z", - "request": { - "body": { - "kind": "json", - "value": { - "input": [ - { - "content": "You are a context summarization assistant. Your task is to read a conversation between a user and an AI coding assistant, then produce a structured summary following the exact format specified.\n\nDo NOT continue the conversation. Do NOT respond to any questions in the conversation. ONLY output the structured summary.", - "role": "system" - }, - { - "content": [ - { - "text": "\n[User]: Complete this instrumented research flow. Call exactly one tool per turn and wait for each tool result before choosing the next tool. Step 1: call lookup with query \"flue instrumentation\". Step 2: use the lookup result id as lookupId and call web_search with query \"Braintrust Flue reasoning stream instrumentation\". Step 3: use the first web_search result url and call summarize_source. After summarize_source returns, reply with exactly PROMPT_DONE and no other text.\n\n[Assistant tool calls]: lookup(query=\"flue instrumentation\")\n\n[Tool result]: {\"id\":\"flue-session-2026\",\"query\":\"flue instrumentation\",\"topic\":\"session instrumentation\"}\n\n[Assistant tool calls]: web_search(lookupId=\"flue-session-2026\", query=\"Braintrust Flue reasoning stream instrumentation\")\n\n[Tool result]: {\"lookupId\":\"flue-session-2026\",\"query\":\"Braintrust Flue reasoning stream instrumentation\",\"results\":[{\"title\":\"Flue reasoning stream instrumentation\",\"url\":\"https://example.test/flue/reasoning-streams\"}]}\n\n[Assistant tool calls]: summarize_source(url=\"https://example.test/flue/reasoning-streams\")\n\n[Tool result]: {\"summary\":\"Flue emits reasoning, tool execution, and LLM turn events separately.\",\"url\":\"https://example.test/flue/reasoning-streams\"}\n\n\nThis is the PREFIX of a turn that was too large to keep. The SUFFIX (recent work) is retained.\n\nSummarize the prefix to provide context for the retained suffix:\n\n## Original Request\n[What did the user ask for in this turn?]\n\n## Early Progress\n- [Key decisions and work done in the prefix]\n\n## Context for Suffix\n- [Information needed to understand the retained recent work]\n\nBe concise. Focus on what's needed to understand the kept suffix.", - "type": "input_text" - } - ], - "role": "user" - } - ], - "max_output_tokens": 32, - "model": "gpt-4o-mini", - "store": false, - "stream": true - } - }, - "headers": {}, - "method": "POST", - "url": "https://api.openai.com/v1/responses" - }, - "response": { - "body": { - "chunks": [ - "event: response.created\ndata: {\"type\":\"response.created\",\"response\":{\"id\":\"resp_04d28ba03840bb65016a705b72d36081a3a387e30f2739466c\",\"object\":\"response\",\"created_at\":1785748338,\"status\":\"in_progress\",\"background\":false,\"completed_at\":null,\"error\":null,\"frequency_penalty\":0,\"incomplete_details\":null,\"instructions\":null,\"max_output_tokens\":32,\"max_tool_calls\":null,\"model\":\"gpt-4o-mini-2024-07-18\",\"moderation\":null,\"output\":[],\"parallel_tool_calls\":true,\"presence_penalty\":0,\"previous_response_id\":null,\"prompt_cache_key\":null,\"prompt_cache_retention\":\"in_memory\",\"reasoning\":{\"context\":null,\"effort\":null,\"summary\":null},\"safety_identifier\":null,\"service_tier\":\"auto\",\"store\":false,\"temperature\":1,\"text\":{\"format\":{\"type\":\"text\"},\"verbosity\":\"medium\"},\"tool_choice\":\"auto\",\"tool_usage\":{\"image_gen\":{\"input_tokens\":0,\"input_tokens_details\":{\"image_tokens\":0,\"text_tokens\":0},\"output_tokens\":0,\"output_tokens_details\":{\"image_tokens\":0,\"text_tokens\":0},\"total_tokens\":0},\"web_search\":{\"num_requests\":0}},\"tools\":[],\"top_logprobs\":0,\"top_p\":1,\"truncation\":\"disabled\",\"usage\":null,\"user\":null,\"metadata\":{}},\"sequence_number\":0}", - "event: response.in_progress\ndata: {\"type\":\"response.in_progress\",\"response\":{\"id\":\"resp_04d28ba03840bb65016a705b72d36081a3a387e30f2739466c\",\"object\":\"response\",\"created_at\":1785748338,\"status\":\"in_progress\",\"background\":false,\"completed_at\":null,\"error\":null,\"frequency_penalty\":0,\"incomplete_details\":null,\"instructions\":null,\"max_output_tokens\":32,\"max_tool_calls\":null,\"model\":\"gpt-4o-mini-2024-07-18\",\"moderation\":null,\"output\":[],\"parallel_tool_calls\":true,\"presence_penalty\":0,\"previous_response_id\":null,\"prompt_cache_key\":null,\"prompt_cache_retention\":\"in_memory\",\"reasoning\":{\"context\":null,\"effort\":null,\"summary\":null},\"safety_identifier\":null,\"service_tier\":\"auto\",\"store\":false,\"temperature\":1,\"text\":{\"format\":{\"type\":\"text\"},\"verbosity\":\"medium\"},\"tool_choice\":\"auto\",\"tool_usage\":{\"image_gen\":{\"input_tokens\":0,\"input_tokens_details\":{\"image_tokens\":0,\"text_tokens\":0},\"output_tokens\":0,\"output_tokens_details\":{\"image_tokens\":0,\"text_tokens\":0},\"total_tokens\":0},\"web_search\":{\"num_requests\":0}},\"tools\":[],\"top_logprobs\":0,\"top_p\":1,\"truncation\":\"disabled\",\"usage\":null,\"user\":null,\"metadata\":{}},\"sequence_number\":1}", - "event: response.output_item.added\ndata: {\"type\":\"response.output_item.added\",\"item\":{\"id\":\"msg_04d28ba03840bb65016a705b7358a081a38e69464057840036\",\"type\":\"message\",\"status\":\"in_progress\",\"content\":[],\"role\":\"assistant\"},\"output_index\":0,\"sequence_number\":2}", - "event: response.content_part.added\ndata: {\"type\":\"response.content_part.added\",\"content_index\":0,\"item_id\":\"msg_04d28ba03840bb65016a705b7358a081a38e69464057840036\",\"output_index\":0,\"part\":{\"type\":\"output_text\",\"annotations\":[],\"logprobs\":[],\"text\":\"\"},\"sequence_number\":3}", - "event: response.output_text.delta\ndata: {\"type\":\"response.output_text.delta\",\"content_index\":0,\"delta\":\"##\",\"item_id\":\"msg_04d28ba03840bb65016a705b7358a081a38e69464057840036\",\"logprobs\":[],\"obfuscation\":\"pnRDruuzK4zmSi\",\"output_index\":0,\"sequence_number\":4}", - "event: response.output_text.delta\ndata: {\"type\":\"response.output_text.delta\",\"content_index\":0,\"delta\":\" Original\",\"item_id\":\"msg_04d28ba03840bb65016a705b7358a081a38e69464057840036\",\"logprobs\":[],\"obfuscation\":\"xeQeZBG\",\"output_index\":0,\"sequence_number\":5}", - "event: response.output_text.delta\ndata: {\"type\":\"response.output_text.delta\",\"content_index\":0,\"delta\":\" Request\",\"item_id\":\"msg_04d28ba03840bb65016a705b7358a081a38e69464057840036\",\"logprobs\":[],\"obfuscation\":\"wDl6Xfa7\",\"output_index\":0,\"sequence_number\":6}", - "event: response.output_text.delta\ndata: {\"type\":\"response.output_text.delta\",\"content_index\":0,\"delta\":\"\\n\",\"item_id\":\"msg_04d28ba03840bb65016a705b7358a081a38e69464057840036\",\"logprobs\":[],\"obfuscation\":\"SPfPR8TAgZd37wg\",\"output_index\":0,\"sequence_number\":7}", - "event: response.output_text.delta\ndata: {\"type\":\"response.output_text.delta\",\"content_index\":0,\"delta\":\"The\",\"item_id\":\"msg_04d28ba03840bb65016a705b7358a081a38e69464057840036\",\"logprobs\":[],\"obfuscation\":\"cgHwyTaOZmFtZ\",\"output_index\":0,\"sequence_number\":8}", - "event: response.output_text.delta\ndata: {\"type\":\"response.output_text.delta\",\"content_index\":0,\"delta\":\" user\",\"item_id\":\"msg_04d28ba03840bb65016a705b7358a081a38e69464057840036\",\"logprobs\":[],\"obfuscation\":\"1z0yUfwbdDc\",\"output_index\":0,\"sequence_number\":9}", - "event: response.output_text.delta\ndata: {\"type\":\"response.output_text.delta\",\"content_index\":0,\"delta\":\" requested\",\"item_id\":\"msg_04d28ba03840bb65016a705b7358a081a38e69464057840036\",\"logprobs\":[],\"obfuscation\":\"eCqgHs\",\"output_index\":0,\"sequence_number\":10}", - "event: response.output_text.delta\ndata: {\"type\":\"response.output_text.delta\",\"content_index\":0,\"delta\":\" a\",\"item_id\":\"msg_04d28ba03840bb65016a705b7358a081a38e69464057840036\",\"logprobs\":[],\"obfuscation\":\"7RAJkk4N6FYOim\",\"output_index\":0,\"sequence_number\":11}", - "event: response.output_text.delta\ndata: {\"type\":\"response.output_text.delta\",\"content_index\":0,\"delta\":\" step\",\"item_id\":\"msg_04d28ba03840bb65016a705b7358a081a38e69464057840036\",\"logprobs\":[],\"obfuscation\":\"y7scL3Od23b\",\"output_index\":0,\"sequence_number\":12}", - "event: response.output_text.delta\ndata: {\"type\":\"response.output_text.delta\",\"content_index\":0,\"delta\":\"-by\",\"item_id\":\"msg_04d28ba03840bb65016a705b7358a081a38e69464057840036\",\"logprobs\":[],\"obfuscation\":\"W7lrv8VHEOIfi\",\"output_index\":0,\"sequence_number\":13}", - "event: response.output_text.delta\ndata: {\"type\":\"response.output_text.delta\",\"content_index\":0,\"delta\":\"-step\",\"item_id\":\"msg_04d28ba03840bb65016a705b7358a081a38e69464057840036\",\"logprobs\":[],\"obfuscation\":\"4LIgLNAJAB0\",\"output_index\":0,\"sequence_number\":14}", - "event: response.output_text.delta\ndata: {\"type\":\"response.output_text.delta\",\"content_index\":0,\"delta\":\" execution\",\"item_id\":\"msg_04d28ba03840bb65016a705b7358a081a38e69464057840036\",\"logprobs\":[],\"obfuscation\":\"F077yL\",\"output_index\":0,\"sequence_number\":15}", - "event: response.output_text.delta\ndata: {\"type\":\"response.output_text.delta\",\"content_index\":0,\"delta\":\" of\",\"item_id\":\"msg_04d28ba03840bb65016a705b7358a081a38e69464057840036\",\"logprobs\":[],\"obfuscation\":\"fMOsa5wMUpP8t\",\"output_index\":0,\"sequence_number\":16}", - "event: response.output_text.delta\ndata: {\"type\":\"response.output_text.delta\",\"content_index\":0,\"delta\":\" an\",\"item_id\":\"msg_04d28ba03840bb65016a705b7358a081a38e69464057840036\",\"logprobs\":[],\"obfuscation\":\"SXWFdQci5VvIi\",\"output_index\":0,\"sequence_number\":17}", - "event: response.output_text.delta\ndata: {\"type\":\"response.output_text.delta\",\"content_index\":0,\"delta\":\" instrument\",\"item_id\":\"msg_04d28ba03840bb65016a705b7358a081a38e69464057840036\",\"logprobs\":[],\"obfuscation\":\"IM33q\",\"output_index\":0,\"sequence_number\":18}", - "event: response.output_text.delta\ndata: {\"type\":\"response.output_text.delta\",\"content_index\":0,\"delta\":\"ed\",\"item_id\":\"msg_04d28ba03840bb65016a705b7358a081a38e69464057840036\",\"logprobs\":[],\"obfuscation\":\"nO8YVpkRDW8oOQ\",\"output_index\":0,\"sequence_number\":19}", - "event: response.output_text.delta\ndata: {\"type\":\"response.output_text.delta\",\"content_index\":0,\"delta\":\" research\",\"item_id\":\"msg_04d28ba03840bb65016a705b7358a081a38e69464057840036\",\"logprobs\":[],\"obfuscation\":\"xjbeOFx\",\"output_index\":0,\"sequence_number\":20}", - "event: response.output_text.delta\ndata: {\"type\":\"response.output_text.delta\",\"content_index\":0,\"delta\":\" flow\",\"item_id\":\"msg_04d28ba03840bb65016a705b7358a081a38e69464057840036\",\"logprobs\":[],\"obfuscation\":\"PjjjDiy3SBJ\",\"output_index\":0,\"sequence_number\":21}", - "event: response.output_text.delta\ndata: {\"type\":\"response.output_text.delta\",\"content_index\":0,\"delta\":\" involving\",\"item_id\":\"msg_04d28ba03840bb65016a705b7358a081a38e69464057840036\",\"logprobs\":[],\"obfuscation\":\"I58VnM\",\"output_index\":0,\"sequence_number\":22}", - "event: response.output_text.delta\ndata: {\"type\":\"response.output_text.delta\",\"content_index\":0,\"delta\":\" tool\",\"item_id\":\"msg_04d28ba03840bb65016a705b7358a081a38e69464057840036\",\"logprobs\":[],\"obfuscation\":\"i0bH5Hefbkz\",\"output_index\":0,\"sequence_number\":23}", - "event: response.output_text.delta\ndata: {\"type\":\"response.output_text.delta\",\"content_index\":0,\"delta\":\" calls\",\"item_id\":\"msg_04d28ba03840bb65016a705b7358a081a38e69464057840036\",\"logprobs\":[],\"obfuscation\":\"BEMMaOeJJU\",\"output_index\":0,\"sequence_number\":24}", - "event: response.output_text.delta\ndata: {\"type\":\"response.output_text.delta\",\"content_index\":0,\"delta\":\" to\",\"item_id\":\"msg_04d28ba03840bb65016a705b7358a081a38e69464057840036\",\"logprobs\":[],\"obfuscation\":\"gPeC3aszPMi0S\",\"output_index\":0,\"sequence_number\":25}", - "event: response.output_text.delta\ndata: {\"type\":\"response.output_text.delta\",\"content_index\":0,\"delta\":\" gather\",\"item_id\":\"msg_04d28ba03840bb65016a705b7358a081a38e69464057840036\",\"logprobs\":[],\"obfuscation\":\"zlkkxcR5g\",\"output_index\":0,\"sequence_number\":26}", - "event: response.output_text.delta\ndata: {\"type\":\"response.output_text.delta\",\"content_index\":0,\"delta\":\" information\",\"item_id\":\"msg_04d28ba03840bb65016a705b7358a081a38e69464057840036\",\"logprobs\":[],\"obfuscation\":\"7hhs\",\"output_index\":0,\"sequence_number\":27}", - "event: response.output_text.delta\ndata: {\"type\":\"response.output_text.delta\",\"content_index\":0,\"delta\":\" on\",\"item_id\":\"msg_04d28ba03840bb65016a705b7358a081a38e69464057840036\",\"logprobs\":[],\"obfuscation\":\"lLr5hZIXvG8Gb\",\"output_index\":0,\"sequence_number\":28}", - "event: response.output_text.delta\ndata: {\"type\":\"response.output_text.delta\",\"content_index\":0,\"delta\":\" \\\"\",\"item_id\":\"msg_04d28ba03840bb65016a705b7358a081a38e69464057840036\",\"logprobs\":[],\"obfuscation\":\"5qdl5MlY2bxs7F\",\"output_index\":0,\"sequence_number\":29}", - "event: response.output_text.delta\ndata: {\"type\":\"response.output_text.delta\",\"content_index\":0,\"delta\":\"fl\",\"item_id\":\"msg_04d28ba03840bb65016a705b7358a081a38e69464057840036\",\"logprobs\":[],\"obfuscation\":\"yt6z8GDsVV0Qew\",\"output_index\":0,\"sequence_number\":30}", - "event: response.output_text.delta\ndata: {\"type\":\"response.output_text.delta\",\"content_index\":0,\"delta\":\"ue\",\"item_id\":\"msg_04d28ba03840bb65016a705b7358a081a38e69464057840036\",\"logprobs\":[],\"obfuscation\":\"3QmzDfnfod8ZRQ\",\"output_index\":0,\"sequence_number\":31}", - "event: response.output_text.delta\ndata: {\"type\":\"response.output_text.delta\",\"content_index\":0,\"delta\":\" instrumentation\",\"item_id\":\"msg_04d28ba03840bb65016a705b7358a081a38e69464057840036\",\"logprobs\":[],\"obfuscation\":\"\",\"output_index\":0,\"sequence_number\":32}", - "event: response.output_text.delta\ndata: {\"type\":\"response.output_text.delta\",\"content_index\":0,\"delta\":\"\\\"\",\"item_id\":\"msg_04d28ba03840bb65016a705b7358a081a38e69464057840036\",\"logprobs\":[],\"obfuscation\":\"yiSjj02o3FbcC4U\",\"output_index\":0,\"sequence_number\":33}", - "event: response.output_text.delta\ndata: {\"type\":\"response.output_text.delta\",\"content_index\":0,\"delta\":\" and\",\"item_id\":\"msg_04d28ba03840bb65016a705b7358a081a38e69464057840036\",\"logprobs\":[],\"obfuscation\":\"796Vjv2p8Npd\",\"output_index\":0,\"sequence_number\":34}", - "event: response.output_text.delta\ndata: {\"type\":\"response.output_text.delta\",\"content_index\":0,\"delta\":\" \\\"\",\"item_id\":\"msg_04d28ba03840bb65016a705b7358a081a38e69464057840036\",\"logprobs\":[],\"obfuscation\":\"WkdXPw3c1tG4kc\",\"output_index\":0,\"sequence_number\":35}", - "event: response.output_text.done\ndata: {\"type\":\"response.output_text.done\",\"content_index\":0,\"item_id\":\"msg_04d28ba03840bb65016a705b7358a081a38e69464057840036\",\"logprobs\":[],\"output_index\":0,\"sequence_number\":36,\"text\":\"## Original Request\\nThe user requested a step-by-step execution of an instrumented research flow involving tool calls to gather information on \\\"flue instrumentation\\\" and \\\"\"}", - "event: response.content_part.done\ndata: {\"type\":\"response.content_part.done\",\"content_index\":0,\"item_id\":\"msg_04d28ba03840bb65016a705b7358a081a38e69464057840036\",\"output_index\":0,\"part\":{\"type\":\"output_text\",\"annotations\":[],\"logprobs\":[],\"text\":\"## Original Request\\nThe user requested a step-by-step execution of an instrumented research flow involving tool calls to gather information on \\\"flue instrumentation\\\" and \\\"\"},\"sequence_number\":37}", - "event: response.output_item.done\ndata: {\"type\":\"response.output_item.done\",\"item\":{\"id\":\"msg_04d28ba03840bb65016a705b7358a081a38e69464057840036\",\"type\":\"message\",\"status\":\"incomplete\",\"content\":[{\"type\":\"output_text\",\"annotations\":[],\"logprobs\":[],\"text\":\"## Original Request\\nThe user requested a step-by-step execution of an instrumented research flow involving tool calls to gather information on \\\"flue instrumentation\\\" and \\\"\"}],\"role\":\"assistant\"},\"output_index\":0,\"sequence_number\":38}", - "event: response.incomplete\ndata: {\"type\":\"response.incomplete\",\"response\":{\"id\":\"resp_04d28ba03840bb65016a705b72d36081a3a387e30f2739466c\",\"object\":\"response\",\"created_at\":1785748338,\"status\":\"incomplete\",\"background\":false,\"completed_at\":null,\"error\":null,\"frequency_penalty\":0,\"incomplete_details\":{\"reason\":\"max_output_tokens\"},\"instructions\":null,\"max_output_tokens\":32,\"max_tool_calls\":null,\"model\":\"gpt-4o-mini-2024-07-18\",\"moderation\":null,\"output\":[{\"id\":\"msg_04d28ba03840bb65016a705b7358a081a38e69464057840036\",\"type\":\"message\",\"status\":\"incomplete\",\"content\":[{\"type\":\"output_text\",\"annotations\":[],\"logprobs\":[],\"text\":\"## Original Request\\nThe user requested a step-by-step execution of an instrumented research flow involving tool calls to gather information on \\\"flue instrumentation\\\" and \\\"\"}],\"role\":\"assistant\"}],\"parallel_tool_calls\":true,\"presence_penalty\":0,\"previous_response_id\":null,\"prompt_cache_key\":null,\"prompt_cache_retention\":\"in_memory\",\"reasoning\":{\"context\":null,\"effort\":null,\"summary\":null},\"safety_identifier\":null,\"service_tier\":\"default\",\"store\":false,\"temperature\":1,\"text\":{\"format\":{\"type\":\"text\"},\"verbosity\":\"medium\"},\"tool_choice\":\"auto\",\"tool_usage\":{\"image_gen\":{\"input_tokens\":0,\"input_tokens_details\":{\"image_tokens\":0,\"text_tokens\":0},\"output_tokens\":0,\"output_tokens_details\":{\"image_tokens\":0,\"text_tokens\":0},\"total_tokens\":0},\"web_search\":{\"num_requests\":0}},\"tools\":[],\"top_logprobs\":0,\"top_p\":1,\"truncation\":\"disabled\",\"usage\":{\"input_tokens\":446,\"input_tokens_details\":{\"cache_write_tokens\":0,\"cached_tokens\":0},\"output_tokens\":32,\"output_tokens_details\":{\"reasoning_tokens\":0},\"total_tokens\":478},\"user\":null,\"metadata\":{}},\"sequence_number\":39}" - ], - "kind": "sse" - }, - "headers": { - "access-control-expose-headers": "X-Request-ID, CF-Ray, CF-Ray", - "alt-svc": "h3=\":443\"; ma=86400", - "cf-cache-status": "DYNAMIC", - "cf-ray": "a254332d68c17bb6-IAD", - "connection": "keep-alive", - "content-type": "text/event-stream; charset=utf-8", - "date": "Mon, 03 Aug 2026 09:12:18 GMT", - "openai-organization": "braintrust-data", - "openai-processing-ms": "159", - "openai-project": "proj_vsCSXafhhByzWOThMrJcZiw9", - "openai-version": "2020-10-01", - "server": "cloudflare", - "set-cookie": "[REDACTED]", - "strict-transport-security": "max-age=31536000; includeSubDomains; preload", - "transfer-encoding": "chunked", - "x-content-type-options": "nosniff", - "x-request-id": "req_7ea02cb4cc7c43e08845985aea9af023" - }, - "status": 200, - "statusText": "OK" - } - } - ], - "meta": { - "createdAt": "2026-08-03T09:07:11.649Z" - } -} diff --git a/e2e/scenarios/flue-instrumentation/__snapshots__/flue-v0-8-0-auto-hook.span-tree.json b/e2e/scenarios/flue-instrumentation/__snapshots__/flue-v0-8-0-auto-hook.span-tree.json deleted file mode 100644 index f06e08da7..000000000 --- a/e2e/scenarios/flue-instrumentation/__snapshots__/flue-v0-8-0-auto-hook.span-tree.json +++ /dev/null @@ -1,1448 +0,0 @@ -{ - "span_tree": [ - { - "name": "workflow:instrumentation", - "type": "task", - "children": [ - { - "name": "flue.skill", - "type": "task", - "children": [ - { - "name": "flue.turn", - "type": "llm", - "children": [], - "input": [ - { - "content": [ - { - "text": "Run the skill named \"e2e-flue-skill\".\n\nArguments:\n{\n \"marker\": \"SKILL_DONE\"\n}", - "type": "text" - } - ], - "role": "user" - } - ], - "output": { - "content": [ - { - "text": "I'll run the e2e-flue-skill. Let me first read the skill file to understand its instructions.", - "type": "text" - }, - { - "arguments": { - "path": ".agents/e2e-flue-skill.md" - }, - "id": "", - "name": "read", - "type": "toolCall" - } - ], - "role": "assistant" - }, - "metadata": { - "flue.api": "anthropic-messages", - "flue.model": "claude-sonnet-4-5-20250929", - "flue.provider": "anthropic", - "flue.session": "skill", - "flue.stop_reason": "toolUse", - "flue.turn_purpose": "agent", - "model": "claude-sonnet-4-5-20250929", - "provider": "anthropic" - }, - "metrics": { - "completion_tokens": 89, - "duration_ms": 0, - "estimated_cost": 0.0041862, - "prompt_cache_creation_tokens": 670, - "prompt_cached_tokens": 1099, - "prompt_tokens": 1772, - "tokens": 1861 - } - }, - { - "name": "tool:read", - "type": "tool", - "children": [], - "input": { - "path": ".agents/e2e-flue-skill.md" - }, - "output": { - "content": [ - { - "text": "ENOENT: no such file or directory, open '/e2e/scenarios/flue-instrumentation/.agents/e2e-flue-skill.md'", - "type": "text" - } - ], - "details": {} - }, - "metadata": { - "flue.session": "skill", - "flue.tool_name": "read", - "provider": "flue" - }, - "metrics": { - "duration_ms": 0 - }, - "error": "{\"content\":[{\"type\":\"text\",\"text\":\"ENOENT: no such file or directory, open '/e2e/scenarios/flue-instrumentation/.agents/e2e-flue-skill.md'\"}],\"details\":{}}" - }, - { - "name": "flue.turn", - "type": "llm", - "children": [], - "input": [ - { - "content": [ - { - "text": "Run the skill named \"e2e-flue-skill\".\n\nArguments:\n{\n \"marker\": \"SKILL_DONE\"\n}", - "type": "text" - } - ], - "role": "user" - }, - { - "content": [ - { - "text": "I'll run the e2e-flue-skill. Let me first read the skill file to understand its instructions.", - "type": "text" - }, - { - "arguments": { - "path": ".agents/e2e-flue-skill.md" - }, - "id": "", - "name": "read", - "type": "toolCall" - } - ], - "role": "assistant" - }, - { - "content": [ - { - "text": "ENOENT: no such file or directory, open '/e2e/scenarios/flue-instrumentation/.agents/e2e-flue-skill.md'", - "type": "text" - } - ], - "isError": true, - "role": "toolResult", - "toolCallId": "", - "toolName": "read" - } - ], - "output": { - "content": [ - { - "arguments": { - "path": ".agents" - }, - "id": "", - "name": "read", - "type": "toolCall" - } - ], - "role": "assistant" - }, - "metadata": { - "flue.api": "anthropic-messages", - "flue.model": "claude-sonnet-4-5-20250929", - "flue.provider": "anthropic", - "flue.session": "skill", - "flue.stop_reason": "toolUse", - "flue.turn_purpose": "agent", - "model": "claude-sonnet-4-5-20250929", - "provider": "anthropic" - }, - "metrics": { - "completion_tokens": 52, - "duration_ms": 0, - "estimated_cost": 0.00201795, - "prompt_cache_creation_tokens": 183, - "prompt_cached_tokens": 1769, - "prompt_tokens": 1959, - "tokens": 2011 - } - }, - { - "name": "tool:read", - "type": "tool", - "children": [], - "input": { - "path": ".agents" - }, - "output": { - "content": [ - { - "text": "skills", - "type": "text" - } - ], - "details": { - "entries": 1, - "isDirectory": true, - "path": ".agents" - } - }, - "metadata": { - "flue.session": "skill", - "flue.tool_name": "read", - "provider": "flue" - }, - "metrics": { - "duration_ms": 0 - } - }, - { - "name": "flue.turn", - "type": "llm", - "children": [], - "input": [ - { - "content": [ - { - "text": "Run the skill named \"e2e-flue-skill\".\n\nArguments:\n{\n \"marker\": \"SKILL_DONE\"\n}", - "type": "text" - } - ], - "role": "user" - }, - { - "content": [ - { - "text": "I'll run the e2e-flue-skill. Let me first read the skill file to understand its instructions.", - "type": "text" - }, - { - "arguments": { - "path": ".agents/e2e-flue-skill.md" - }, - "id": "", - "name": "read", - "type": "toolCall" - } - ], - "role": "assistant" - }, - { - "content": [ - { - "text": "ENOENT: no such file or directory, open '/e2e/scenarios/flue-instrumentation/.agents/e2e-flue-skill.md'", - "type": "text" - } - ], - "isError": true, - "role": "toolResult", - "toolCallId": "", - "toolName": "read" - }, - { - "content": [ - { - "arguments": { - "path": ".agents" - }, - "id": "", - "name": "read", - "type": "toolCall" - } - ], - "role": "assistant" - }, - { - "content": [ - { - "text": "skills", - "type": "text" - } - ], - "isError": false, - "role": "toolResult", - "toolCallId": "", - "toolName": "read" - } - ], - "output": { - "content": [ - { - "arguments": { - "path": ".agents/skills" - }, - "id": "", - "name": "read", - "type": "toolCall" - } - ], - "role": "assistant" - }, - "metadata": { - "flue.api": "anthropic-messages", - "flue.model": "claude-sonnet-4-5-20250929", - "flue.provider": "anthropic", - "flue.session": "skill", - "flue.stop_reason": "toolUse", - "flue.turn_purpose": "agent", - "model": "claude-sonnet-4-5-20250929", - "provider": "anthropic" - }, - "metrics": { - "completion_tokens": 54, - "duration_ms": 0, - "estimated_cost": 0.0016611, - "prompt_cache_creation_tokens": 66, - "prompt_cached_tokens": 1952, - "prompt_tokens": 2024, - "tokens": 2078 - } - }, - { - "name": "tool:read", - "type": "tool", - "children": [], - "input": { - "path": ".agents/skills" - }, - "output": { - "content": [ - { - "text": "e2e-flue-skill", - "type": "text" - } - ], - "details": { - "entries": 1, - "isDirectory": true, - "path": ".agents/skills" - } - }, - "metadata": { - "flue.session": "skill", - "flue.tool_name": "read", - "provider": "flue" - }, - "metrics": { - "duration_ms": 0 - } - }, - { - "name": "flue.turn", - "type": "llm", - "children": [], - "input": [ - { - "content": [ - { - "text": "Run the skill named \"e2e-flue-skill\".\n\nArguments:\n{\n \"marker\": \"SKILL_DONE\"\n}", - "type": "text" - } - ], - "role": "user" - }, - { - "content": [ - { - "text": "I'll run the e2e-flue-skill. Let me first read the skill file to understand its instructions.", - "type": "text" - }, - { - "arguments": { - "path": ".agents/e2e-flue-skill.md" - }, - "id": "", - "name": "read", - "type": "toolCall" - } - ], - "role": "assistant" - }, - { - "content": [ - { - "text": "ENOENT: no such file or directory, open '/e2e/scenarios/flue-instrumentation/.agents/e2e-flue-skill.md'", - "type": "text" - } - ], - "isError": true, - "role": "toolResult", - "toolCallId": "", - "toolName": "read" - }, - { - "content": [ - { - "arguments": { - "path": ".agents" - }, - "id": "", - "name": "read", - "type": "toolCall" - } - ], - "role": "assistant" - }, - { - "content": [ - { - "text": "skills", - "type": "text" - } - ], - "isError": false, - "role": "toolResult", - "toolCallId": "", - "toolName": "read" - }, - { - "content": [ - { - "arguments": { - "path": ".agents/skills" - }, - "id": "", - "name": "read", - "type": "toolCall" - } - ], - "role": "assistant" - }, - { - "content": [ - { - "text": "e2e-flue-skill", - "type": "text" - } - ], - "isError": false, - "role": "toolResult", - "toolCallId": "", - "toolName": "read" - } - ], - "output": { - "content": [ - { - "arguments": { - "path": ".agents/skills/e2e-flue-skill" - }, - "id": "", - "name": "read", - "type": "toolCall" - } - ], - "role": "assistant" - }, - "metadata": { - "flue.api": "anthropic-messages", - "flue.model": "claude-sonnet-4-5-20250929", - "flue.provider": "anthropic", - "flue.session": "skill", - "flue.stop_reason": "toolUse", - "flue.turn_purpose": "agent", - "model": "claude-sonnet-4-5-20250929", - "provider": "anthropic" - }, - "metrics": { - "completion_tokens": 63, - "duration_ms": 0, - "estimated_cost": 0.0018459, - "prompt_cache_creation_tokens": 74, - "prompt_cached_tokens": 2018, - "prompt_tokens": 2098, - "tokens": 2161 - } - }, - { - "name": "tool:read", - "type": "tool", - "children": [], - "input": { - "path": ".agents/skills/e2e-flue-skill" - }, - "output": { - "content": [ - { - "text": "SKILL.md", - "type": "text" - } - ], - "details": { - "entries": 1, - "isDirectory": true, - "path": ".agents/skills/e2e-flue-skill" - } - }, - "metadata": { - "flue.session": "skill", - "flue.tool_name": "read", - "provider": "flue" - }, - "metrics": { - "duration_ms": 0 - } - }, - { - "name": "flue.turn", - "type": "llm", - "children": [], - "input": [ - { - "content": [ - { - "text": "Run the skill named \"e2e-flue-skill\".\n\nArguments:\n{\n \"marker\": \"SKILL_DONE\"\n}", - "type": "text" - } - ], - "role": "user" - }, - { - "content": [ - { - "text": "I'll run the e2e-flue-skill. Let me first read the skill file to understand its instructions.", - "type": "text" - }, - { - "arguments": { - "path": ".agents/e2e-flue-skill.md" - }, - "id": "", - "name": "read", - "type": "toolCall" - } - ], - "role": "assistant" - }, - { - "content": [ - { - "text": "ENOENT: no such file or directory, open '/e2e/scenarios/flue-instrumentation/.agents/e2e-flue-skill.md'", - "type": "text" - } - ], - "isError": true, - "role": "toolResult", - "toolCallId": "", - "toolName": "read" - }, - { - "content": [ - { - "arguments": { - "path": ".agents" - }, - "id": "", - "name": "read", - "type": "toolCall" - } - ], - "role": "assistant" - }, - { - "content": [ - { - "text": "skills", - "type": "text" - } - ], - "isError": false, - "role": "toolResult", - "toolCallId": "", - "toolName": "read" - }, - { - "content": [ - { - "arguments": { - "path": ".agents/skills" - }, - "id": "", - "name": "read", - "type": "toolCall" - } - ], - "role": "assistant" - }, - { - "content": [ - { - "text": "e2e-flue-skill", - "type": "text" - } - ], - "isError": false, - "role": "toolResult", - "toolCallId": "", - "toolName": "read" - }, - { - "content": [ - { - "arguments": { - "path": ".agents/skills/e2e-flue-skill" - }, - "id": "", - "name": "read", - "type": "toolCall" - } - ], - "role": "assistant" - }, - { - "content": [ - { - "text": "SKILL.md", - "type": "text" - } - ], - "isError": false, - "role": "toolResult", - "toolCallId": "", - "toolName": "read" - } - ], - "output": { - "content": [ - { - "arguments": { - "path": ".agents/skills/e2e-flue-skill/SKILL.md" - }, - "id": "", - "name": "read", - "type": "toolCall" - } - ], - "role": "assistant" - }, - "metadata": { - "flue.api": "anthropic-messages", - "flue.model": "claude-sonnet-4-5-20250929", - "flue.provider": "anthropic", - "flue.session": "skill", - "flue.stop_reason": "toolUse", - "flue.turn_purpose": "agent", - "model": "claude-sonnet-4-5-20250929", - "provider": "anthropic" - }, - "metrics": { - "completion_tokens": 68, - "duration_ms": 0, - "estimated_cost": 0.0019618500000000002, - "prompt_cache_creation_tokens": 79, - "prompt_cached_tokens": 2092, - "prompt_tokens": 2177, - "tokens": 2245 - } - }, - { - "name": "tool:read", - "type": "tool", - "children": [], - "input": { - "path": ".agents/skills/e2e-flue-skill/SKILL.md" - }, - "output": { - "content": [ - { - "text": "---\nname: e2e-flue-skill\ndescription: Deterministic skill used by the Braintrust Flue instrumentation e2e test.\n---\n\nReturn the marker from the provided args exactly once. Output no other text.\n", - "type": "text" - } - ], - "details": { - "lines": 7, - "path": ".agents/skills/e2e-flue-skill/SKILL.md" - } - }, - "metadata": { - "flue.session": "skill", - "flue.tool_name": "read", - "provider": "flue" - }, - "metrics": { - "duration_ms": 0 - } - }, - { - "name": "flue.turn", - "type": "llm", - "children": [], - "input": [ - { - "content": [ - { - "text": "Run the skill named \"e2e-flue-skill\".\n\nArguments:\n{\n \"marker\": \"SKILL_DONE\"\n}", - "type": "text" - } - ], - "role": "user" - }, - { - "content": [ - { - "text": "I'll run the e2e-flue-skill. Let me first read the skill file to understand its instructions.", - "type": "text" - }, - { - "arguments": { - "path": ".agents/e2e-flue-skill.md" - }, - "id": "", - "name": "read", - "type": "toolCall" - } - ], - "role": "assistant" - }, - { - "content": [ - { - "text": "ENOENT: no such file or directory, open '/e2e/scenarios/flue-instrumentation/.agents/e2e-flue-skill.md'", - "type": "text" - } - ], - "isError": true, - "role": "toolResult", - "toolCallId": "", - "toolName": "read" - }, - { - "content": [ - { - "arguments": { - "path": ".agents" - }, - "id": "", - "name": "read", - "type": "toolCall" - } - ], - "role": "assistant" - }, - { - "content": [ - { - "text": "skills", - "type": "text" - } - ], - "isError": false, - "role": "toolResult", - "toolCallId": "", - "toolName": "read" - }, - { - "content": [ - { - "arguments": { - "path": ".agents/skills" - }, - "id": "", - "name": "read", - "type": "toolCall" - } - ], - "role": "assistant" - }, - { - "content": [ - { - "text": "e2e-flue-skill", - "type": "text" - } - ], - "isError": false, - "role": "toolResult", - "toolCallId": "", - "toolName": "read" - }, - { - "content": [ - { - "arguments": { - "path": ".agents/skills/e2e-flue-skill" - }, - "id": "", - "name": "read", - "type": "toolCall" - } - ], - "role": "assistant" - }, - { - "content": [ - { - "text": "SKILL.md", - "type": "text" - } - ], - "isError": false, - "role": "toolResult", - "toolCallId": "", - "toolName": "read" - }, - { - "content": [ - { - "arguments": { - "path": ".agents/skills/e2e-flue-skill/SKILL.md" - }, - "id": "", - "name": "read", - "type": "toolCall" - } - ], - "role": "assistant" - }, - { - "content": [ - { - "text": "---\nname: e2e-flue-skill\ndescription: Deterministic skill used by the Braintrust Flue instrumentation e2e test.\n---\n\nReturn the marker from the provided args exactly once. Output no other text.\n", - "type": "text" - } - ], - "isError": false, - "role": "toolResult", - "toolCallId": "", - "toolName": "read" - } - ], - "output": { - "content": [ - { - "text": "SKILL_DONE", - "type": "text" - } - ], - "role": "assistant" - }, - "metadata": { - "flue.api": "anthropic-messages", - "flue.model": "claude-sonnet-4-5-20250929", - "flue.provider": "anthropic", - "flue.session": "skill", - "flue.stop_reason": "stop", - "flue.turn_purpose": "agent", - "model": "claude-sonnet-4-5-20250929", - "provider": "anthropic" - }, - "metrics": { - "completion_tokens": 8, - "duration_ms": 0, - "estimated_cost": 0.00129555, - "prompt_cache_creation_tokens": 135, - "prompt_cached_tokens": 2171, - "prompt_tokens": 2312, - "tokens": 2320 - } - } - ], - "input": [ - { - "content": [ - { - "text": "Run the skill named \"e2e-flue-skill\".\n\nArguments:\n{\n \"marker\": \"SKILL_DONE\"\n}", - "type": "text" - } - ], - "role": "user" - } - ], - "output": "SKILL_DONE", - "metadata": { - "flue.operation": "skill", - "flue.session": "skill", - "flue.workflow_name": "instrumentation", - "provider": "flue", - "scenario": "flue-instrumentation" - }, - "metrics": { - "duration_ms": 0 - } - }, - { - "name": "flue.task", - "type": "task", - "children": [ - { - "name": "flue.turn", - "type": "llm", - "children": [], - "input": [ - { - "content": [ - { - "text": "Reply with exactly TASK_DONE and no other text.", - "type": "text" - } - ], - "role": "user" - } - ], - "output": { - "content": [ - { - "text": "TASK_DONE", - "textSignature": "{\"v\":1,\"id\":\"msg_09b32b2821e9a4d8016a184600019081929d34bb7d4c6a246f\"}", - "type": "text" - } - ], - "role": "assistant" - }, - "metadata": { - "flue.api": "openai-responses", - "flue.input_mode": "full", - "flue.model": "gpt-4o-mini", - "flue.provider": "openai", - "flue.session": "task:task:", - "flue.stop_reason": "stop", - "flue.turn_purpose": "agent", - "model": "gpt-4o-mini", - "provider": "openai" - }, - "metrics": { - "completion_tokens": 4, - "duration_ms": 0, - "estimated_cost": 0.00012299999999999998, - "prompt_cache_creation_tokens": 0, - "prompt_cached_tokens": 0, - "prompt_tokens": 804, - "tokens": 808 - } - } - ], - "input": "Reply with exactly TASK_DONE and no other text.", - "output": "TASK_DONE", - "metadata": { - "flue.session": "task", - "provider": "flue" - }, - "metrics": { - "duration_ms": 0 - } - }, - { - "name": "flue.compact", - "type": "task", - "children": [ - { - "name": "compaction:manual", - "type": "task", - "children": [ - { - "name": "flue.turn", - "type": "llm", - "children": [], - "input": [ - { - "content": [ - { - "text": "\n[User]: Complete this instrumented research flow. Call exactly one tool per turn and wait for each tool result before choosing the next tool. Step 1: call lookup with query \"flue instrumentation\". Step 2: use the lookup result id as lookupId and call web_search with query \"Braintrust Flue reasoning stream instrumentation\". Step 3: use the first web_search result url and call summarize_source. After summarize_source returns, reply with exactly PROMPT_DONE and no other text.\n\n[Assistant thinking]: The user wants me to complete a research flow with exactly three steps:\n1. Call lookup with query \"flue instrumentation\"\n2. Call web_search with the lookupId from step 1 and query \"Braintrust Flue reasoning stream instrumentation\"\n3. Call summarize_source with the first URL from step 2\n4. Reply with exactly \"PROMPT_DONE\"\n\nThe user emphasizes I should call exactly one tool per turn and wait for each result. Let me start with step 1.\n\n[Assistant]: I'll complete this instrumented research flow step by step. Starting with Step 1:\n\n[Assistant tool calls]: lookup(query=\"flue instrumentation\")\n\n[Tool result]: {\"id\":\"flue-session-2026\",\"query\":\"flue instrumentation\",\"topic\":\"session instrumentation\"}\n\n[Assistant thinking]: Good, I got the lookup result with id \"flue-session-2026\". Now I need to proceed to step 2: call web_search with this lookupId and the query \"Braintrust Flue reasoning stream instrumentation\".\n\n[Assistant]: Now proceeding to Step 2:\n\n[Assistant tool calls]: web_search(lookupId=\"flue-session-2026\", query=\"Braintrust Flue reasoning stream instrumentation\")\n\n[Tool result]: {\"lookupId\":\"flue-session-2026\",\"query\":\"Braintrust Flue reasoning stream instrumentation\",\"results\":[{\"title\":\"Flue reasoning stream instrumentation\",\"url\":\"https://example.test/flue/reasoning-streams\"}]}\n\n[Assistant thinking]: Perfect! I got the web_search results with one result. The first (and only) result has the URL \"https://example.test/flue/reasoning-streams\". Now I need to proceed to step 3: call summarize_source with this URL.\n\n[Assistant]: Now proceeding to Step 3:\n\n[Assistant tool calls]: summarize_source(url=\"https://example.test/flue/reasoning-streams\")\n\n[Tool result]: {\"summary\":\"Flue emits reasoning, tool execution, and LLM turn events separately.\",\"url\":\"https://example.test/flue/reasoning-streams\"}\n\n\nThis is the PREFIX of a turn that was too large to keep. The SUFFIX (recent work) is retained.\n\nSummarize the prefix to provide context for the retained suffix:\n\n## Original Request\n[What did the user ask for in this turn?]\n\n## Early Progress\n- [Key decisions and work done in the prefix]\n\n## Context for Suffix\n- [Information needed to understand the retained recent work]\n\nBe concise. Focus on what's needed to understand the kept suffix.", - "type": "text" - } - ], - "role": "user" - } - ], - "output": { - "content": [ - { - "text": "## Original Request\nThe user requested to complete an instrumented research flow consisting of three specific steps, involving calling various tools in a sequential manner.\n\n## Early Progress", - "textSignature": "{\"v\":1,\"id\":\"msg_01ecf68325f9e202016a184601309081a0878ef4054e950d32\"}", - "type": "text" - } - ], - "role": "assistant" - }, - "metadata": { - "flue.api": "openai-responses", - "flue.model": "gpt-4o-mini", - "flue.provider": "openai", - "flue.session": "main", - "flue.stop_reason": "stop", - "flue.turn_purpose": "compaction_prefix", - "model": "gpt-4o-mini", - "provider": "openai" - }, - "metrics": { - "completion_tokens": 0, - "duration_ms": 0, - "estimated_cost": 0, - "prompt_cache_creation_tokens": 0, - "prompt_cached_tokens": 0, - "prompt_tokens": 0, - "tokens": 0 - } - } - ], - "input": { - "estimatedTokens": 2950, - "reason": "manual" - }, - "output": { - "messagesAfter": 2, - "messagesBefore": 8 - }, - "metadata": { - "flue.compaction_reason": "manual", - "flue.session": "main", - "provider": "flue" - }, - "metrics": { - "duration_ms": 0, - "messages_after": 2, - "messages_before": 8 - } - } - ], - "input": { - "estimatedTokens": 2950, - "reason": "manual" - }, - "output": { - "completed": true - }, - "metadata": { - "flue.operation": "compact", - "flue.session": "main", - "flue.workflow_name": "instrumentation", - "provider": "flue", - "scenario": "flue-instrumentation" - }, - "metrics": { - "duration_ms": 0 - } - } - ], - "input": { - "metadata": { - "scenario": "flue-instrumentation", - "testRunId": "" - }, - "scenario": "flue-instrumentation" - }, - "output": { - "scenario": "flue-instrumentation", - "status": "done" - }, - "metadata": { - "flue.workflow_name": "instrumentation", - "provider": "flue", - "scenario": "flue-instrumentation" - }, - "metrics": { - "duration_ms": 0 - } - }, - { - "name": "flue.prompt", - "type": "task", - "children": [ - { - "name": "flue.turn", - "type": "llm", - "children": [], - "input": [ - { - "content": [ - { - "text": "Complete this instrumented research flow. Call exactly one tool per turn and wait for each tool result before choosing the next tool. Step 1: call lookup with query \"flue instrumentation\". Step 2: use the lookup result id as lookupId and call web_search with query \"Braintrust Flue reasoning stream instrumentation\". Step 3: use the first web_search result url and call summarize_source. After summarize_source returns, reply with exactly PROMPT_DONE and no other text.", - "type": "text" - } - ], - "role": "user" - } - ], - "output": { - "content": [ - { - "thinking": "The user wants me to complete a research flow with exactly three steps:\n1. Call lookup with query \"flue instrumentation\"\n2. Call web_search with the lookupId from step 1 and query \"Braintrust Flue reasoning stream instrumentation\"\n3. Call summarize_source with the first URL from step 2\n4. Reply with exactly \"PROMPT_DONE\"\n\nThe user emphasizes I should call exactly one tool per turn and wait for each result. Let me start with step 1.", - "thinkingSignature": "EoQFCm4IDhgCKkD+AoKMhpXNPUJ5eANwM28uUCJPCE+AXh0ACw7aXniX+YN04zdJl3hlHMbtAuKhhczLiEeOJtu4KtVpea5u5NFdMhpjbGF1ZGUtc29ubmV0LTQtNS0yMDI1MDkyOTgAQgh0aGlua2luZxIMIOTWCNEtsiI9vF4SGgwPXC861uZx8lpOziEiMLsCukGIluD0U44degzYnHFc+BwXNIEB+yatSHlZE+a5smr9crZcft3OLmsIdXuaFSrDAyQxWd9oqfJdnvza9eWIaOCeNkVCjrTc/wotBwh5Xp8m9k/WKt7P4zzUJBuLQ1f6ZnsdWkL8OiL/2E7Up55z2+UuVWsg1Y90/x27YRFdQjv3RnztYp4bZ5oIHAlTJoCzWiBYEh/Z8c2O+wy6MHo1cp0T57OroB/4HAqRZxrjTOXpKYLmYKXAY5tU8DTd3A0/FfEhg3oPSbOhX+112szFCpm3BRNJYooUUhwSu1hO4H/6r7d/Yp9XIM4tBfzuDx83CC/YetdOFgnYkdliw0xaUGaj4o37rWIRctWHAMdlUc4NDA0Ljc1uirHDXkN8EJwWSoIjPSBTA/p/wgLrvRV5Yy9Z0dMoWOpgYF6t1H/tNAyY8ml1CA7IWyd3jnfTZ1/G3Y0xFDwJDFlb4/QKc67w36SSCa+X9mMoTxD/DYN249lsII+XX3lQ7F1zJ07THOtCyRirCaRhmE/kGwl5tiB1QHrWa9bNHxQxf9i92435JGKN053Fe14AR70MtBSzIU2/zbgvhB1vF5lZt2i46hNxfo4lXR495gEwdwC6+8HJnlEJAJkdeHfZKj+RiwXoiYKPKsSbHi83X8Hh32IifuglNxdlxVQYAQ==", - "type": "thinking" - }, - { - "text": "I'll complete this instrumented research flow step by step. Starting with Step 1:", - "type": "text" - }, - { - "arguments": { - "query": "flue instrumentation" - }, - "id": "", - "name": "lookup", - "type": "toolCall" - } - ], - "role": "assistant" - }, - "metadata": { - "flue.api": "anthropic-messages", - "flue.input_mode": "full", - "flue.model": "claude-sonnet-4-5-20250929", - "flue.provider": "anthropic", - "flue.session": "main", - "flue.stop_reason": "toolUse", - "flue.turn_purpose": "agent", - "model": "claude-sonnet-4-5-20250929", - "provider": "anthropic" - }, - "metrics": { - "completion_tokens": 196, - "duration_ms": 0, - "estimated_cost": 0.0113025, - "prompt_cache_creation_tokens": 2222, - "prompt_cached_tokens": 0, - "prompt_tokens": 2232, - "tokens": 2428 - } - }, - { - "name": "tool:lookup", - "type": "tool", - "children": [], - "input": { - "query": "flue instrumentation" - }, - "output": { - "content": [ - { - "text": "{\"id\":\"flue-session-2026\",\"query\":\"flue instrumentation\",\"topic\":\"session instrumentation\"}", - "type": "text" - } - ], - "details": { - "customTool": "lookup" - } - }, - "metadata": { - "flue.session": "main", - "flue.tool_name": "lookup", - "provider": "flue" - }, - "metrics": { - "duration_ms": 0 - } - }, - { - "name": "flue.turn", - "type": "llm", - "children": [], - "input": [ - { - "content": [ - { - "thinking": "The user wants me to complete a research flow with exactly three steps:\n1. Call lookup with query \"flue instrumentation\"\n2. Call web_search with the lookupId from step 1 and query \"Braintrust Flue reasoning stream instrumentation\"\n3. Call summarize_source with the first URL from step 2\n4. Reply with exactly \"PROMPT_DONE\"\n\nThe user emphasizes I should call exactly one tool per turn and wait for each result. Let me start with step 1.", - "thinkingSignature": "EoQFCm4IDhgCKkD+AoKMhpXNPUJ5eANwM28uUCJPCE+AXh0ACw7aXniX+YN04zdJl3hlHMbtAuKhhczLiEeOJtu4KtVpea5u5NFdMhpjbGF1ZGUtc29ubmV0LTQtNS0yMDI1MDkyOTgAQgh0aGlua2luZxIMIOTWCNEtsiI9vF4SGgwPXC861uZx8lpOziEiMLsCukGIluD0U44degzYnHFc+BwXNIEB+yatSHlZE+a5smr9crZcft3OLmsIdXuaFSrDAyQxWd9oqfJdnvza9eWIaOCeNkVCjrTc/wotBwh5Xp8m9k/WKt7P4zzUJBuLQ1f6ZnsdWkL8OiL/2E7Up55z2+UuVWsg1Y90/x27YRFdQjv3RnztYp4bZ5oIHAlTJoCzWiBYEh/Z8c2O+wy6MHo1cp0T57OroB/4HAqRZxrjTOXpKYLmYKXAY5tU8DTd3A0/FfEhg3oPSbOhX+112szFCpm3BRNJYooUUhwSu1hO4H/6r7d/Yp9XIM4tBfzuDx83CC/YetdOFgnYkdliw0xaUGaj4o37rWIRctWHAMdlUc4NDA0Ljc1uirHDXkN8EJwWSoIjPSBTA/p/wgLrvRV5Yy9Z0dMoWOpgYF6t1H/tNAyY8ml1CA7IWyd3jnfTZ1/G3Y0xFDwJDFlb4/QKc67w36SSCa+X9mMoTxD/DYN249lsII+XX3lQ7F1zJ07THOtCyRirCaRhmE/kGwl5tiB1QHrWa9bNHxQxf9i92435JGKN053Fe14AR70MtBSzIU2/zbgvhB1vF5lZt2i46hNxfo4lXR495gEwdwC6+8HJnlEJAJkdeHfZKj+RiwXoiYKPKsSbHi83X8Hh32IifuglNxdlxVQYAQ==", - "type": "thinking" - }, - { - "text": "I'll complete this instrumented research flow step by step. Starting with Step 1:", - "type": "text" - }, - { - "arguments": { - "query": "flue instrumentation" - }, - "id": "", - "name": "lookup", - "type": "toolCall" - } - ], - "role": "assistant" - }, - { - "content": [ - { - "text": "{\"id\":\"flue-session-2026\",\"query\":\"flue instrumentation\",\"topic\":\"session instrumentation\"}", - "type": "text" - } - ], - "isError": false, - "role": "toolResult", - "toolCallId": "", - "toolName": "lookup" - } - ], - "output": { - "content": [ - { - "thinking": "Good, I got the lookup result with id \"flue-session-2026\". Now I need to proceed to step 2: call web_search with this lookupId and the query \"Braintrust Flue reasoning stream instrumentation\".", - "thinkingSignature": "EpEDCm4IDhgCKkAMahve1kB1j4njp1ZSzO8eCK4bb5Ex7MB6tjdoOXfuUDRfkBqvIGdwVly8GiA8GGYC7Y/RVfREXF6A6OXHTwFyMhpjbGF1ZGUtc29ubmV0LTQtNS0yMDI1MDkyOTgAQgh0aGlua2luZxIMLw+fsa4yo7a7ZSoIGgyaTdvH020n2dQj9bUiMB4OokY29w+uZb4rqDnKDbLXUqUj6U0f1QCGQF2ZBYDDfDJyM5Jv1xY3rQiUUjkhBirQAS4XivjvMnWI5zHBmpI/u/pq4aGdrdb3vUsJ/ne6R1d80JA/INGF8vLkQcZ5xxEK0jU4yRE+ldc2uzavmtoxIKAFD6bqKks1Qv6u+TIZtuW7tUB/7jFxDw93KmJNruMIa8c1J6pPUyBy3xE7QwgmU0kDfIfdnljxLoEBiMwtnxjCPj9lT7HHz4FkrhbnQTV4MvAQohCLjaSbOTd6LdLxwtJPm9mc26ID4eYN1gCCzyAxGXhqLHlzc23PafvQjsZliWnLhaiNMnG0YEo78XkuJQsYAQ==", - "type": "thinking" - }, - { - "text": "Now proceeding to Step 2:", - "type": "text" - }, - { - "arguments": { - "lookupId": "flue-session-2026", - "query": "Braintrust Flue reasoning stream instrumentation" - }, - "id": "", - "name": "web_search", - "type": "toolCall" - } - ], - "role": "assistant" - }, - "metadata": { - "flue.api": "anthropic-messages", - "flue.input_message_offset": 1, - "flue.input_mode": "delta", - "flue.model": "claude-sonnet-4-5-20250929", - "flue.provider": "anthropic", - "flue.session": "main", - "flue.stop_reason": "toolUse", - "flue.turn_purpose": "agent", - "model": "claude-sonnet-4-5-20250929", - "provider": "anthropic" - }, - "metrics": { - "completion_tokens": 158, - "duration_ms": 0, - "estimated_cost": 0.0039606, - "prompt_cache_creation_tokens": 236, - "prompt_cached_tokens": 2222, - "prompt_tokens": 2471, - "tokens": 2629 - } - }, - { - "name": "tool:web_search", - "type": "tool", - "children": [], - "input": { - "lookupId": "flue-session-2026", - "query": "Braintrust Flue reasoning stream instrumentation" - }, - "output": { - "content": [ - { - "text": "{\"lookupId\":\"flue-session-2026\",\"query\":\"Braintrust Flue reasoning stream instrumentation\",\"results\":[{\"title\":\"Flue reasoning stream instrumentation\",\"url\":\"https://example.test/flue/reasoning-streams\"}]}", - "type": "text" - } - ], - "details": { - "customTool": "web_search" - } - }, - "metadata": { - "flue.session": "main", - "flue.tool_name": "web_search", - "provider": "flue" - }, - "metrics": { - "duration_ms": 0 - } - }, - { - "name": "flue.turn", - "type": "llm", - "children": [], - "input": [ - { - "content": [ - { - "thinking": "Good, I got the lookup result with id \"flue-session-2026\". Now I need to proceed to step 2: call web_search with this lookupId and the query \"Braintrust Flue reasoning stream instrumentation\".", - "thinkingSignature": "EpEDCm4IDhgCKkAMahve1kB1j4njp1ZSzO8eCK4bb5Ex7MB6tjdoOXfuUDRfkBqvIGdwVly8GiA8GGYC7Y/RVfREXF6A6OXHTwFyMhpjbGF1ZGUtc29ubmV0LTQtNS0yMDI1MDkyOTgAQgh0aGlua2luZxIMLw+fsa4yo7a7ZSoIGgyaTdvH020n2dQj9bUiMB4OokY29w+uZb4rqDnKDbLXUqUj6U0f1QCGQF2ZBYDDfDJyM5Jv1xY3rQiUUjkhBirQAS4XivjvMnWI5zHBmpI/u/pq4aGdrdb3vUsJ/ne6R1d80JA/INGF8vLkQcZ5xxEK0jU4yRE+ldc2uzavmtoxIKAFD6bqKks1Qv6u+TIZtuW7tUB/7jFxDw93KmJNruMIa8c1J6pPUyBy3xE7QwgmU0kDfIfdnljxLoEBiMwtnxjCPj9lT7HHz4FkrhbnQTV4MvAQohCLjaSbOTd6LdLxwtJPm9mc26ID4eYN1gCCzyAxGXhqLHlzc23PafvQjsZliWnLhaiNMnG0YEo78XkuJQsYAQ==", - "type": "thinking" - }, - { - "text": "Now proceeding to Step 2:", - "type": "text" - }, - { - "arguments": { - "lookupId": "flue-session-2026", - "query": "Braintrust Flue reasoning stream instrumentation" - }, - "id": "", - "name": "web_search", - "type": "toolCall" - } - ], - "role": "assistant" - }, - { - "content": [ - { - "text": "{\"lookupId\":\"flue-session-2026\",\"query\":\"Braintrust Flue reasoning stream instrumentation\",\"results\":[{\"title\":\"Flue reasoning stream instrumentation\",\"url\":\"https://example.test/flue/reasoning-streams\"}]}", - "type": "text" - } - ], - "isError": false, - "role": "toolResult", - "toolCallId": "", - "toolName": "web_search" - } - ], - "output": { - "content": [ - { - "thinking": "Perfect! I got the web_search results with one result. The first (and only) result has the URL \"https://example.test/flue/reasoning-streams\". Now I need to proceed to step 3: call summarize_source with this URL.", - "thinkingSignature": "EqQDCm4IDhgCKkAXv7EDAhWMcrBz11bUd5qxlzyy03CAGQfRgLHDE0uKzLATh1o2sedd8GxLwwQqV0CmLRfNxsdK/ZDp3Lks9iysMhpjbGF1ZGUtc29ubmV0LTQtNS0yMDI1MDkyOTgAQgh0aGlua2luZxIMXDMniOy5Dy0KI8x7GgzgHs7BYJMs6/N0ur8iMPe0SwnHhP39OjEu7P8ASwK9ffwa3VP67QOlPzOstRRdEZNd0mq+BVjpmBXB+QcAzCrjAeAh/UO/d7SfFmXjqwA3//thoL/TTlGa6W+fdOCFRikn65JWT4ZdVFetfqD+9/QCP/nXEwMsLnTBAxC4EGryrgs2YT+OWwmEJ7rHKFeDs5djhwSb2J2ZJO1kRvD9s2vCCDkImygYBSIVgx1Y0CZJHhzn2Vf39v0Fbth2wDZa+OqxivV9NmDZEjmgcM+tTRx1CCnaww8LZEJQ68v/dUJy7hhD4MrNcQ62evJYPfT3+4EKKWJ/7HI7KXz91//+YAkph63XstbJMFJ3AldAFiq3ecauXt5r42N5gmCmwZX+sI48ENLGGAE=", - "type": "thinking" - }, - { - "text": "Now proceeding to Step 3:", - "type": "text" - }, - { - "arguments": { - "url": "https://example.test/flue/reasoning-streams" - }, - "id": "", - "name": "summarize_source", - "type": "toolCall" - } - ], - "role": "assistant" - }, - "metadata": { - "flue.api": "anthropic-messages", - "flue.input_message_offset": 3, - "flue.input_mode": "delta", - "flue.model": "claude-sonnet-4-5-20250929", - "flue.provider": "anthropic", - "flue.session": "main", - "flue.stop_reason": "toolUse", - "flue.turn_purpose": "agent", - "model": "claude-sonnet-4-5-20250929", - "provider": "anthropic" - }, - "metrics": { - "completion_tokens": 138, - "duration_ms": 0, - "estimated_cost": 0.0037239000000000005, - "prompt_cache_creation_tokens": 234, - "prompt_cached_tokens": 2458, - "prompt_tokens": 2705, - "tokens": 2843 - } - }, - { - "name": "tool:summarize_source", - "type": "tool", - "children": [], - "input": { - "url": "https://example.test/flue/reasoning-streams" - }, - "output": { - "content": [ - { - "text": "{\"summary\":\"Flue emits reasoning, tool execution, and LLM turn events separately.\",\"url\":\"https://example.test/flue/reasoning-streams\"}", - "type": "text" - } - ], - "details": { - "customTool": "summarize_source" - } - }, - "metadata": { - "flue.session": "main", - "flue.tool_name": "summarize_source", - "provider": "flue" - }, - "metrics": { - "duration_ms": 0 - } - }, - { - "name": "flue.turn", - "type": "llm", - "children": [], - "input": [ - { - "content": [ - { - "thinking": "Perfect! I got the web_search results with one result. The first (and only) result has the URL \"https://example.test/flue/reasoning-streams\". Now I need to proceed to step 3: call summarize_source with this URL.", - "thinkingSignature": "EqQDCm4IDhgCKkAXv7EDAhWMcrBz11bUd5qxlzyy03CAGQfRgLHDE0uKzLATh1o2sedd8GxLwwQqV0CmLRfNxsdK/ZDp3Lks9iysMhpjbGF1ZGUtc29ubmV0LTQtNS0yMDI1MDkyOTgAQgh0aGlua2luZxIMXDMniOy5Dy0KI8x7GgzgHs7BYJMs6/N0ur8iMPe0SwnHhP39OjEu7P8ASwK9ffwa3VP67QOlPzOstRRdEZNd0mq+BVjpmBXB+QcAzCrjAeAh/UO/d7SfFmXjqwA3//thoL/TTlGa6W+fdOCFRikn65JWT4ZdVFetfqD+9/QCP/nXEwMsLnTBAxC4EGryrgs2YT+OWwmEJ7rHKFeDs5djhwSb2J2ZJO1kRvD9s2vCCDkImygYBSIVgx1Y0CZJHhzn2Vf39v0Fbth2wDZa+OqxivV9NmDZEjmgcM+tTRx1CCnaww8LZEJQ68v/dUJy7hhD4MrNcQ62evJYPfT3+4EKKWJ/7HI7KXz91//+YAkph63XstbJMFJ3AldAFiq3ecauXt5r42N5gmCmwZX+sI48ENLGGAE=", - "type": "thinking" - }, - { - "text": "Now proceeding to Step 3:", - "type": "text" - }, - { - "arguments": { - "url": "https://example.test/flue/reasoning-streams" - }, - "id": "", - "name": "summarize_source", - "type": "toolCall" - } - ], - "role": "assistant" - }, - { - "content": [ - { - "text": "{\"summary\":\"Flue emits reasoning, tool execution, and LLM turn events separately.\",\"url\":\"https://example.test/flue/reasoning-streams\"}", - "type": "text" - } - ], - "isError": false, - "role": "toolResult", - "toolCallId": "", - "toolName": "summarize_source" - } - ], - "output": { - "content": [ - { - "thinking": "Perfect! The summarize_source call has completed successfully. According to the instructions, I should now reply with exactly \"PROMPT_DONE\" and no other text.", - "thinkingSignature": "Eu8CCm4IDhgCKkAn2QOIIlYfvcs6ynsmhCslHDtBsrTA4CfmimK7zyz6+k6dZldV2JBtEBET41It0gFcGEKC0n0mAhaYVuLutpDDMhpjbGF1ZGUtc29ubmV0LTQtNS0yMDI1MDkyOTgAQgh0aGlua2luZxIM1xfLptxSF8rKzou8GgwRLuVmfiBYOT4CZIsiMAYz8TpvchMAWodP+T9uZ+dJ47MVdNv+p86nZXmT5d5EWlCroiAOFHHzJH2o0UjJQSquAX6wu+jIC8/DXwurGIIf2ToldPuJWF5HGj8kHMnwh777XS1NUFl1irM24rvP7kTAv3Dp8YEwb8t3g1EsicY7j+C5DzgwxVsPYCVOCgdMqQjtsqpL7AX5KQ9+hR1gK1DQjshv4Gze3hw/+C+9iptd3RlDIuwO0cm8NY+sVqeWVh3TtQujQlVt1rW/HfWjclrtT0LZSGwc6RypmfMMQi6lncIk/EPgy0XbS1plKW0tLBgB", - "type": "thinking" - }, - { - "text": "PROMPT_DONE", - "type": "text" - } - ], - "role": "assistant" - }, - "metadata": { - "flue.api": "anthropic-messages", - "flue.input_message_offset": 5, - "flue.input_mode": "delta", - "flue.model": "claude-sonnet-4-5-20250929", - "flue.provider": "anthropic", - "flue.session": "main", - "flue.stop_reason": "stop", - "flue.turn_purpose": "agent", - "model": "claude-sonnet-4-5-20250929", - "provider": "anthropic" - }, - "metrics": { - "completion_tokens": 51, - "duration_ms": 0, - "estimated_cost": 0.0023391, - "prompt_cache_creation_tokens": 194, - "prompt_cached_tokens": 2692, - "prompt_tokens": 2899, - "tokens": 2950 - } - } - ], - "input": [ - { - "content": [ - { - "text": "Complete this instrumented research flow. Call exactly one tool per turn and wait for each tool result before choosing the next tool. Step 1: call lookup with query \"flue instrumentation\". Step 2: use the lookup result id as lookupId and call web_search with query \"Braintrust Flue reasoning stream instrumentation\". Step 3: use the first web_search result url and call summarize_source. After summarize_source returns, reply with exactly PROMPT_DONE and no other text.", - "type": "text" - } - ], - "role": "user" - } - ], - "output": "PROMPT_DONE", - "metadata": { - "flue.operation": "prompt", - "flue.session": "main", - "flue.workflow_name": "instrumentation", - "provider": "flue", - "scenario": "flue-instrumentation" - }, - "metrics": { - "duration_ms": 0 - } - } - ] -} diff --git a/e2e/scenarios/flue-instrumentation/__snapshots__/flue-v0-8-0-auto-hook.span-tree.txt b/e2e/scenarios/flue-instrumentation/__snapshots__/flue-v0-8-0-auto-hook.span-tree.txt deleted file mode 100644 index aaf72f391..000000000 --- a/e2e/scenarios/flue-instrumentation/__snapshots__/flue-v0-8-0-auto-hook.span-tree.txt +++ /dev/null @@ -1,1335 +0,0 @@ -span_tree: -├── workflow:instrumentation [task] -│ input: { -│ "metadata": { -│ "scenario": "flue-instrumentation", -│ "testRunId": "" -│ }, -│ "scenario": "flue-instrumentation" -│ } -│ output: { -│ "scenario": "flue-instrumentation", -│ "status": "done" -│ } -│ metadata: { -│ "flue.workflow_name": "instrumentation", -│ "provider": "flue", -│ "scenario": "flue-instrumentation" -│ } -│ metrics: { -│ "duration_ms": 0 -│ } -│ ├── flue.skill [task] -│ │ input: [ -│ │ { -│ │ "content": [ -│ │ { -│ │ "text": "Run the skill named \"e2e-flue-skill\".\n\nArguments:\n{\n \"marker\": \"SKILL_DONE\"\n}", -│ │ "type": "text" -│ │ } -│ │ ], -│ │ "role": "user" -│ │ } -│ │ ] -│ │ output: "SKILL_DONE" -│ │ metadata: { -│ │ "flue.operation": "skill", -│ │ "flue.session": "skill", -│ │ "flue.workflow_name": "instrumentation", -│ │ "provider": "flue", -│ │ "scenario": "flue-instrumentation" -│ │ } -│ │ metrics: { -│ │ "duration_ms": 0 -│ │ } -│ │ ├── flue.turn [llm] -│ │ │ input: [ -│ │ │ { -│ │ │ "content": [ -│ │ │ { -│ │ │ "text": "Run the skill named \"e2e-flue-skill\".\n\nArguments:\n{\n \"marker\": \"SKILL_DONE\"\n}", -│ │ │ "type": "text" -│ │ │ } -│ │ │ ], -│ │ │ "role": "user" -│ │ │ } -│ │ │ ] -│ │ │ output: { -│ │ │ "content": [ -│ │ │ { -│ │ │ "text": "I'll run the e2e-flue-skill. Let me first read the skill file to understand its instructions.", -│ │ │ "type": "text" -│ │ │ }, -│ │ │ { -│ │ │ "arguments": { -│ │ │ "path": ".agents/e2e-flue-skill.md" -│ │ │ }, -│ │ │ "id": "", -│ │ │ "name": "read", -│ │ │ "type": "toolCall" -│ │ │ } -│ │ │ ], -│ │ │ "role": "assistant" -│ │ │ } -│ │ │ metadata: { -│ │ │ "flue.api": "anthropic-messages", -│ │ │ "flue.model": "claude-sonnet-4-5-20250929", -│ │ │ "flue.provider": "anthropic", -│ │ │ "flue.session": "skill", -│ │ │ "flue.stop_reason": "toolUse", -│ │ │ "flue.turn_purpose": "agent", -│ │ │ "model": "claude-sonnet-4-5-20250929", -│ │ │ "provider": "anthropic" -│ │ │ } -│ │ │ metrics: { -│ │ │ "completion_tokens": 89, -│ │ │ "duration_ms": 0, -│ │ │ "estimated_cost": 0.0041862, -│ │ │ "prompt_cache_creation_tokens": 670, -│ │ │ "prompt_cached_tokens": 1099, -│ │ │ "prompt_tokens": 1772, -│ │ │ "tokens": 1861 -│ │ │ } -│ │ ├── tool:read [tool] -│ │ │ input: { -│ │ │ "path": ".agents/e2e-flue-skill.md" -│ │ │ } -│ │ │ output: { -│ │ │ "content": [ -│ │ │ { -│ │ │ "text": "ENOENT: no such file or directory, open '/e2e/scenarios/flue-instrumentation/.agents/e2e-flue-skill.md'", -│ │ │ "type": "text" -│ │ │ } -│ │ │ ], -│ │ │ "details": {} -│ │ │ } -│ │ │ metadata: { -│ │ │ "flue.session": "skill", -│ │ │ "flue.tool_name": "read", -│ │ │ "provider": "flue" -│ │ │ } -│ │ │ metrics: { -│ │ │ "duration_ms": 0 -│ │ │ } -│ │ │ error: "{\"content\":[{\"type\":\"text\",\"text\":\"ENOENT: no such file or directory, open '/e2e/scenarios/flue-instrumentation/.agents/e2e-flue-skill.md'\"}],\"details\":{}}" -│ │ ├── flue.turn [llm] -│ │ │ input: [ -│ │ │ { -│ │ │ "content": [ -│ │ │ { -│ │ │ "text": "Run the skill named \"e2e-flue-skill\".\n\nArguments:\n{\n \"marker\": \"SKILL_DONE\"\n}", -│ │ │ "type": "text" -│ │ │ } -│ │ │ ], -│ │ │ "role": "user" -│ │ │ }, -│ │ │ { -│ │ │ "content": [ -│ │ │ { -│ │ │ "text": "I'll run the e2e-flue-skill. Let me first read the skill file to understand its instructions.", -│ │ │ "type": "text" -│ │ │ }, -│ │ │ { -│ │ │ "arguments": { -│ │ │ "path": ".agents/e2e-flue-skill.md" -│ │ │ }, -│ │ │ "id": "", -│ │ │ "name": "read", -│ │ │ "type": "toolCall" -│ │ │ } -│ │ │ ], -│ │ │ "role": "assistant" -│ │ │ }, -│ │ │ { -│ │ │ "content": [ -│ │ │ { -│ │ │ "text": "ENOENT: no such file or directory, open '/e2e/scenarios/flue-instrumentation/.agents/e2e-flue-skill.md'", -│ │ │ "type": "text" -│ │ │ } -│ │ │ ], -│ │ │ "isError": true, -│ │ │ "role": "toolResult", -│ │ │ "toolCallId": "", -│ │ │ "toolName": "read" -│ │ │ } -│ │ │ ] -│ │ │ output: { -│ │ │ "content": [ -│ │ │ { -│ │ │ "arguments": { -│ │ │ "path": ".agents" -│ │ │ }, -│ │ │ "id": "", -│ │ │ "name": "read", -│ │ │ "type": "toolCall" -│ │ │ } -│ │ │ ], -│ │ │ "role": "assistant" -│ │ │ } -│ │ │ metadata: { -│ │ │ "flue.api": "anthropic-messages", -│ │ │ "flue.model": "claude-sonnet-4-5-20250929", -│ │ │ "flue.provider": "anthropic", -│ │ │ "flue.session": "skill", -│ │ │ "flue.stop_reason": "toolUse", -│ │ │ "flue.turn_purpose": "agent", -│ │ │ "model": "claude-sonnet-4-5-20250929", -│ │ │ "provider": "anthropic" -│ │ │ } -│ │ │ metrics: { -│ │ │ "completion_tokens": 52, -│ │ │ "duration_ms": 0, -│ │ │ "estimated_cost": 0.00201795, -│ │ │ "prompt_cache_creation_tokens": 183, -│ │ │ "prompt_cached_tokens": 1769, -│ │ │ "prompt_tokens": 1959, -│ │ │ "tokens": 2011 -│ │ │ } -│ │ ├── tool:read [tool] -│ │ │ input: { -│ │ │ "path": ".agents" -│ │ │ } -│ │ │ output: { -│ │ │ "content": [ -│ │ │ { -│ │ │ "text": "skills", -│ │ │ "type": "text" -│ │ │ } -│ │ │ ], -│ │ │ "details": { -│ │ │ "entries": 1, -│ │ │ "isDirectory": true, -│ │ │ "path": ".agents" -│ │ │ } -│ │ │ } -│ │ │ metadata: { -│ │ │ "flue.session": "skill", -│ │ │ "flue.tool_name": "read", -│ │ │ "provider": "flue" -│ │ │ } -│ │ │ metrics: { -│ │ │ "duration_ms": 0 -│ │ │ } -│ │ ├── flue.turn [llm] -│ │ │ input: [ -│ │ │ { -│ │ │ "content": [ -│ │ │ { -│ │ │ "text": "Run the skill named \"e2e-flue-skill\".\n\nArguments:\n{\n \"marker\": \"SKILL_DONE\"\n}", -│ │ │ "type": "text" -│ │ │ } -│ │ │ ], -│ │ │ "role": "user" -│ │ │ }, -│ │ │ { -│ │ │ "content": [ -│ │ │ { -│ │ │ "text": "I'll run the e2e-flue-skill. Let me first read the skill file to understand its instructions.", -│ │ │ "type": "text" -│ │ │ }, -│ │ │ { -│ │ │ "arguments": { -│ │ │ "path": ".agents/e2e-flue-skill.md" -│ │ │ }, -│ │ │ "id": "", -│ │ │ "name": "read", -│ │ │ "type": "toolCall" -│ │ │ } -│ │ │ ], -│ │ │ "role": "assistant" -│ │ │ }, -│ │ │ { -│ │ │ "content": [ -│ │ │ { -│ │ │ "text": "ENOENT: no such file or directory, open '/e2e/scenarios/flue-instrumentation/.agents/e2e-flue-skill.md'", -│ │ │ "type": "text" -│ │ │ } -│ │ │ ], -│ │ │ "isError": true, -│ │ │ "role": "toolResult", -│ │ │ "toolCallId": "", -│ │ │ "toolName": "read" -│ │ │ }, -│ │ │ { -│ │ │ "content": [ -│ │ │ { -│ │ │ "arguments": { -│ │ │ "path": ".agents" -│ │ │ }, -│ │ │ "id": "", -│ │ │ "name": "read", -│ │ │ "type": "toolCall" -│ │ │ } -│ │ │ ], -│ │ │ "role": "assistant" -│ │ │ }, -│ │ │ { -│ │ │ "content": [ -│ │ │ { -│ │ │ "text": "skills", -│ │ │ "type": "text" -│ │ │ } -│ │ │ ], -│ │ │ "isError": false, -│ │ │ "role": "toolResult", -│ │ │ "toolCallId": "", -│ │ │ "toolName": "read" -│ │ │ } -│ │ │ ] -│ │ │ output: { -│ │ │ "content": [ -│ │ │ { -│ │ │ "arguments": { -│ │ │ "path": ".agents/skills" -│ │ │ }, -│ │ │ "id": "", -│ │ │ "name": "read", -│ │ │ "type": "toolCall" -│ │ │ } -│ │ │ ], -│ │ │ "role": "assistant" -│ │ │ } -│ │ │ metadata: { -│ │ │ "flue.api": "anthropic-messages", -│ │ │ "flue.model": "claude-sonnet-4-5-20250929", -│ │ │ "flue.provider": "anthropic", -│ │ │ "flue.session": "skill", -│ │ │ "flue.stop_reason": "toolUse", -│ │ │ "flue.turn_purpose": "agent", -│ │ │ "model": "claude-sonnet-4-5-20250929", -│ │ │ "provider": "anthropic" -│ │ │ } -│ │ │ metrics: { -│ │ │ "completion_tokens": 54, -│ │ │ "duration_ms": 0, -│ │ │ "estimated_cost": 0.0016611, -│ │ │ "prompt_cache_creation_tokens": 66, -│ │ │ "prompt_cached_tokens": 1952, -│ │ │ "prompt_tokens": 2024, -│ │ │ "tokens": 2078 -│ │ │ } -│ │ ├── tool:read [tool] -│ │ │ input: { -│ │ │ "path": ".agents/skills" -│ │ │ } -│ │ │ output: { -│ │ │ "content": [ -│ │ │ { -│ │ │ "text": "e2e-flue-skill", -│ │ │ "type": "text" -│ │ │ } -│ │ │ ], -│ │ │ "details": { -│ │ │ "entries": 1, -│ │ │ "isDirectory": true, -│ │ │ "path": ".agents/skills" -│ │ │ } -│ │ │ } -│ │ │ metadata: { -│ │ │ "flue.session": "skill", -│ │ │ "flue.tool_name": "read", -│ │ │ "provider": "flue" -│ │ │ } -│ │ │ metrics: { -│ │ │ "duration_ms": 0 -│ │ │ } -│ │ ├── flue.turn [llm] -│ │ │ input: [ -│ │ │ { -│ │ │ "content": [ -│ │ │ { -│ │ │ "text": "Run the skill named \"e2e-flue-skill\".\n\nArguments:\n{\n \"marker\": \"SKILL_DONE\"\n}", -│ │ │ "type": "text" -│ │ │ } -│ │ │ ], -│ │ │ "role": "user" -│ │ │ }, -│ │ │ { -│ │ │ "content": [ -│ │ │ { -│ │ │ "text": "I'll run the e2e-flue-skill. Let me first read the skill file to understand its instructions.", -│ │ │ "type": "text" -│ │ │ }, -│ │ │ { -│ │ │ "arguments": { -│ │ │ "path": ".agents/e2e-flue-skill.md" -│ │ │ }, -│ │ │ "id": "", -│ │ │ "name": "read", -│ │ │ "type": "toolCall" -│ │ │ } -│ │ │ ], -│ │ │ "role": "assistant" -│ │ │ }, -│ │ │ { -│ │ │ "content": [ -│ │ │ { -│ │ │ "text": "ENOENT: no such file or directory, open '/e2e/scenarios/flue-instrumentation/.agents/e2e-flue-skill.md'", -│ │ │ "type": "text" -│ │ │ } -│ │ │ ], -│ │ │ "isError": true, -│ │ │ "role": "toolResult", -│ │ │ "toolCallId": "", -│ │ │ "toolName": "read" -│ │ │ }, -│ │ │ { -│ │ │ "content": [ -│ │ │ { -│ │ │ "arguments": { -│ │ │ "path": ".agents" -│ │ │ }, -│ │ │ "id": "", -│ │ │ "name": "read", -│ │ │ "type": "toolCall" -│ │ │ } -│ │ │ ], -│ │ │ "role": "assistant" -│ │ │ }, -│ │ │ { -│ │ │ "content": [ -│ │ │ { -│ │ │ "text": "skills", -│ │ │ "type": "text" -│ │ │ } -│ │ │ ], -│ │ │ "isError": false, -│ │ │ "role": "toolResult", -│ │ │ "toolCallId": "", -│ │ │ "toolName": "read" -│ │ │ }, -│ │ │ { -│ │ │ "content": [ -│ │ │ { -│ │ │ "arguments": { -│ │ │ "path": ".agents/skills" -│ │ │ }, -│ │ │ "id": "", -│ │ │ "name": "read", -│ │ │ "type": "toolCall" -│ │ │ } -│ │ │ ], -│ │ │ "role": "assistant" -│ │ │ }, -│ │ │ { -│ │ │ "content": [ -│ │ │ { -│ │ │ "text": "e2e-flue-skill", -│ │ │ "type": "text" -│ │ │ } -│ │ │ ], -│ │ │ "isError": false, -│ │ │ "role": "toolResult", -│ │ │ "toolCallId": "", -│ │ │ "toolName": "read" -│ │ │ } -│ │ │ ] -│ │ │ output: { -│ │ │ "content": [ -│ │ │ { -│ │ │ "arguments": { -│ │ │ "path": ".agents/skills/e2e-flue-skill" -│ │ │ }, -│ │ │ "id": "", -│ │ │ "name": "read", -│ │ │ "type": "toolCall" -│ │ │ } -│ │ │ ], -│ │ │ "role": "assistant" -│ │ │ } -│ │ │ metadata: { -│ │ │ "flue.api": "anthropic-messages", -│ │ │ "flue.model": "claude-sonnet-4-5-20250929", -│ │ │ "flue.provider": "anthropic", -│ │ │ "flue.session": "skill", -│ │ │ "flue.stop_reason": "toolUse", -│ │ │ "flue.turn_purpose": "agent", -│ │ │ "model": "claude-sonnet-4-5-20250929", -│ │ │ "provider": "anthropic" -│ │ │ } -│ │ │ metrics: { -│ │ │ "completion_tokens": 63, -│ │ │ "duration_ms": 0, -│ │ │ "estimated_cost": 0.0018459, -│ │ │ "prompt_cache_creation_tokens": 74, -│ │ │ "prompt_cached_tokens": 2018, -│ │ │ "prompt_tokens": 2098, -│ │ │ "tokens": 2161 -│ │ │ } -│ │ ├── tool:read [tool] -│ │ │ input: { -│ │ │ "path": ".agents/skills/e2e-flue-skill" -│ │ │ } -│ │ │ output: { -│ │ │ "content": [ -│ │ │ { -│ │ │ "text": "SKILL.md", -│ │ │ "type": "text" -│ │ │ } -│ │ │ ], -│ │ │ "details": { -│ │ │ "entries": 1, -│ │ │ "isDirectory": true, -│ │ │ "path": ".agents/skills/e2e-flue-skill" -│ │ │ } -│ │ │ } -│ │ │ metadata: { -│ │ │ "flue.session": "skill", -│ │ │ "flue.tool_name": "read", -│ │ │ "provider": "flue" -│ │ │ } -│ │ │ metrics: { -│ │ │ "duration_ms": 0 -│ │ │ } -│ │ ├── flue.turn [llm] -│ │ │ input: [ -│ │ │ { -│ │ │ "content": [ -│ │ │ { -│ │ │ "text": "Run the skill named \"e2e-flue-skill\".\n\nArguments:\n{\n \"marker\": \"SKILL_DONE\"\n}", -│ │ │ "type": "text" -│ │ │ } -│ │ │ ], -│ │ │ "role": "user" -│ │ │ }, -│ │ │ { -│ │ │ "content": [ -│ │ │ { -│ │ │ "text": "I'll run the e2e-flue-skill. Let me first read the skill file to understand its instructions.", -│ │ │ "type": "text" -│ │ │ }, -│ │ │ { -│ │ │ "arguments": { -│ │ │ "path": ".agents/e2e-flue-skill.md" -│ │ │ }, -│ │ │ "id": "", -│ │ │ "name": "read", -│ │ │ "type": "toolCall" -│ │ │ } -│ │ │ ], -│ │ │ "role": "assistant" -│ │ │ }, -│ │ │ { -│ │ │ "content": [ -│ │ │ { -│ │ │ "text": "ENOENT: no such file or directory, open '/e2e/scenarios/flue-instrumentation/.agents/e2e-flue-skill.md'", -│ │ │ "type": "text" -│ │ │ } -│ │ │ ], -│ │ │ "isError": true, -│ │ │ "role": "toolResult", -│ │ │ "toolCallId": "", -│ │ │ "toolName": "read" -│ │ │ }, -│ │ │ { -│ │ │ "content": [ -│ │ │ { -│ │ │ "arguments": { -│ │ │ "path": ".agents" -│ │ │ }, -│ │ │ "id": "", -│ │ │ "name": "read", -│ │ │ "type": "toolCall" -│ │ │ } -│ │ │ ], -│ │ │ "role": "assistant" -│ │ │ }, -│ │ │ { -│ │ │ "content": [ -│ │ │ { -│ │ │ "text": "skills", -│ │ │ "type": "text" -│ │ │ } -│ │ │ ], -│ │ │ "isError": false, -│ │ │ "role": "toolResult", -│ │ │ "toolCallId": "", -│ │ │ "toolName": "read" -│ │ │ }, -│ │ │ { -│ │ │ "content": [ -│ │ │ { -│ │ │ "arguments": { -│ │ │ "path": ".agents/skills" -│ │ │ }, -│ │ │ "id": "", -│ │ │ "name": "read", -│ │ │ "type": "toolCall" -│ │ │ } -│ │ │ ], -│ │ │ "role": "assistant" -│ │ │ }, -│ │ │ { -│ │ │ "content": [ -│ │ │ { -│ │ │ "text": "e2e-flue-skill", -│ │ │ "type": "text" -│ │ │ } -│ │ │ ], -│ │ │ "isError": false, -│ │ │ "role": "toolResult", -│ │ │ "toolCallId": "", -│ │ │ "toolName": "read" -│ │ │ }, -│ │ │ { -│ │ │ "content": [ -│ │ │ { -│ │ │ "arguments": { -│ │ │ "path": ".agents/skills/e2e-flue-skill" -│ │ │ }, -│ │ │ "id": "", -│ │ │ "name": "read", -│ │ │ "type": "toolCall" -│ │ │ } -│ │ │ ], -│ │ │ "role": "assistant" -│ │ │ }, -│ │ │ { -│ │ │ "content": [ -│ │ │ { -│ │ │ "text": "SKILL.md", -│ │ │ "type": "text" -│ │ │ } -│ │ │ ], -│ │ │ "isError": false, -│ │ │ "role": "toolResult", -│ │ │ "toolCallId": "", -│ │ │ "toolName": "read" -│ │ │ } -│ │ │ ] -│ │ │ output: { -│ │ │ "content": [ -│ │ │ { -│ │ │ "arguments": { -│ │ │ "path": ".agents/skills/e2e-flue-skill/SKILL.md" -│ │ │ }, -│ │ │ "id": "", -│ │ │ "name": "read", -│ │ │ "type": "toolCall" -│ │ │ } -│ │ │ ], -│ │ │ "role": "assistant" -│ │ │ } -│ │ │ metadata: { -│ │ │ "flue.api": "anthropic-messages", -│ │ │ "flue.model": "claude-sonnet-4-5-20250929", -│ │ │ "flue.provider": "anthropic", -│ │ │ "flue.session": "skill", -│ │ │ "flue.stop_reason": "toolUse", -│ │ │ "flue.turn_purpose": "agent", -│ │ │ "model": "claude-sonnet-4-5-20250929", -│ │ │ "provider": "anthropic" -│ │ │ } -│ │ │ metrics: { -│ │ │ "completion_tokens": 68, -│ │ │ "duration_ms": 0, -│ │ │ "estimated_cost": 0.0019618500000000002, -│ │ │ "prompt_cache_creation_tokens": 79, -│ │ │ "prompt_cached_tokens": 2092, -│ │ │ "prompt_tokens": 2177, -│ │ │ "tokens": 2245 -│ │ │ } -│ │ ├── tool:read [tool] -│ │ │ input: { -│ │ │ "path": ".agents/skills/e2e-flue-skill/SKILL.md" -│ │ │ } -│ │ │ output: { -│ │ │ "content": [ -│ │ │ { -│ │ │ "text": "---\nname: e2e-flue-skill\ndescription: Deterministic skill used by the Braintrust Flue instrumentation e2e test.\n---\n\nReturn the marker from the provided args exactly once. Output no other text.\n", -│ │ │ "type": "text" -│ │ │ } -│ │ │ ], -│ │ │ "details": { -│ │ │ "lines": 7, -│ │ │ "path": ".agents/skills/e2e-flue-skill/SKILL.md" -│ │ │ } -│ │ │ } -│ │ │ metadata: { -│ │ │ "flue.session": "skill", -│ │ │ "flue.tool_name": "read", -│ │ │ "provider": "flue" -│ │ │ } -│ │ │ metrics: { -│ │ │ "duration_ms": 0 -│ │ │ } -│ │ └── flue.turn [llm] -│ │ input: [ -│ │ { -│ │ "content": [ -│ │ { -│ │ "text": "Run the skill named \"e2e-flue-skill\".\n\nArguments:\n{\n \"marker\": \"SKILL_DONE\"\n}", -│ │ "type": "text" -│ │ } -│ │ ], -│ │ "role": "user" -│ │ }, -│ │ { -│ │ "content": [ -│ │ { -│ │ "text": "I'll run the e2e-flue-skill. Let me first read the skill file to understand its instructions.", -│ │ "type": "text" -│ │ }, -│ │ { -│ │ "arguments": { -│ │ "path": ".agents/e2e-flue-skill.md" -│ │ }, -│ │ "id": "", -│ │ "name": "read", -│ │ "type": "toolCall" -│ │ } -│ │ ], -│ │ "role": "assistant" -│ │ }, -│ │ { -│ │ "content": [ -│ │ { -│ │ "text": "ENOENT: no such file or directory, open '/e2e/scenarios/flue-instrumentation/.agents/e2e-flue-skill.md'", -│ │ "type": "text" -│ │ } -│ │ ], -│ │ "isError": true, -│ │ "role": "toolResult", -│ │ "toolCallId": "", -│ │ "toolName": "read" -│ │ }, -│ │ { -│ │ "content": [ -│ │ { -│ │ "arguments": { -│ │ "path": ".agents" -│ │ }, -│ │ "id": "", -│ │ "name": "read", -│ │ "type": "toolCall" -│ │ } -│ │ ], -│ │ "role": "assistant" -│ │ }, -│ │ { -│ │ "content": [ -│ │ { -│ │ "text": "skills", -│ │ "type": "text" -│ │ } -│ │ ], -│ │ "isError": false, -│ │ "role": "toolResult", -│ │ "toolCallId": "", -│ │ "toolName": "read" -│ │ }, -│ │ { -│ │ "content": [ -│ │ { -│ │ "arguments": { -│ │ "path": ".agents/skills" -│ │ }, -│ │ "id": "", -│ │ "name": "read", -│ │ "type": "toolCall" -│ │ } -│ │ ], -│ │ "role": "assistant" -│ │ }, -│ │ { -│ │ "content": [ -│ │ { -│ │ "text": "e2e-flue-skill", -│ │ "type": "text" -│ │ } -│ │ ], -│ │ "isError": false, -│ │ "role": "toolResult", -│ │ "toolCallId": "", -│ │ "toolName": "read" -│ │ }, -│ │ { -│ │ "content": [ -│ │ { -│ │ "arguments": { -│ │ "path": ".agents/skills/e2e-flue-skill" -│ │ }, -│ │ "id": "", -│ │ "name": "read", -│ │ "type": "toolCall" -│ │ } -│ │ ], -│ │ "role": "assistant" -│ │ }, -│ │ { -│ │ "content": [ -│ │ { -│ │ "text": "SKILL.md", -│ │ "type": "text" -│ │ } -│ │ ], -│ │ "isError": false, -│ │ "role": "toolResult", -│ │ "toolCallId": "", -│ │ "toolName": "read" -│ │ }, -│ │ { -│ │ "content": [ -│ │ { -│ │ "arguments": { -│ │ "path": ".agents/skills/e2e-flue-skill/SKILL.md" -│ │ }, -│ │ "id": "", -│ │ "name": "read", -│ │ "type": "toolCall" -│ │ } -│ │ ], -│ │ "role": "assistant" -│ │ }, -│ │ { -│ │ "content": [ -│ │ { -│ │ "text": "---\nname: e2e-flue-skill\ndescription: Deterministic skill used by the Braintrust Flue instrumentation e2e test.\n---\n\nReturn the marker from the provided args exactly once. Output no other text.\n", -│ │ "type": "text" -│ │ } -│ │ ], -│ │ "isError": false, -│ │ "role": "toolResult", -│ │ "toolCallId": "", -│ │ "toolName": "read" -│ │ } -│ │ ] -│ │ output: { -│ │ "content": [ -│ │ { -│ │ "text": "SKILL_DONE", -│ │ "type": "text" -│ │ } -│ │ ], -│ │ "role": "assistant" -│ │ } -│ │ metadata: { -│ │ "flue.api": "anthropic-messages", -│ │ "flue.model": "claude-sonnet-4-5-20250929", -│ │ "flue.provider": "anthropic", -│ │ "flue.session": "skill", -│ │ "flue.stop_reason": "stop", -│ │ "flue.turn_purpose": "agent", -│ │ "model": "claude-sonnet-4-5-20250929", -│ │ "provider": "anthropic" -│ │ } -│ │ metrics: { -│ │ "completion_tokens": 8, -│ │ "duration_ms": 0, -│ │ "estimated_cost": 0.00129555, -│ │ "prompt_cache_creation_tokens": 135, -│ │ "prompt_cached_tokens": 2171, -│ │ "prompt_tokens": 2312, -│ │ "tokens": 2320 -│ │ } -│ ├── flue.task [task] -│ │ input: "Reply with exactly TASK_DONE and no other text." -│ │ output: "TASK_DONE" -│ │ metadata: { -│ │ "flue.session": "task", -│ │ "provider": "flue" -│ │ } -│ │ metrics: { -│ │ "duration_ms": 0 -│ │ } -│ │ └── flue.turn [llm] -│ │ input: [ -│ │ { -│ │ "content": [ -│ │ { -│ │ "text": "Reply with exactly TASK_DONE and no other text.", -│ │ "type": "text" -│ │ } -│ │ ], -│ │ "role": "user" -│ │ } -│ │ ] -│ │ output: { -│ │ "content": [ -│ │ { -│ │ "text": "TASK_DONE", -│ │ "textSignature": "{\"v\":1,\"id\":\"msg_09b32b2821e9a4d8016a184600019081929d34bb7d4c6a246f\"}", -│ │ "type": "text" -│ │ } -│ │ ], -│ │ "role": "assistant" -│ │ } -│ │ metadata: { -│ │ "flue.api": "openai-responses", -│ │ "flue.input_mode": "full", -│ │ "flue.model": "gpt-4o-mini", -│ │ "flue.provider": "openai", -│ │ "flue.session": "task:task:", -│ │ "flue.stop_reason": "stop", -│ │ "flue.turn_purpose": "agent", -│ │ "model": "gpt-4o-mini", -│ │ "provider": "openai" -│ │ } -│ │ metrics: { -│ │ "completion_tokens": 4, -│ │ "duration_ms": 0, -│ │ "estimated_cost": 0.00012299999999999998, -│ │ "prompt_cache_creation_tokens": 0, -│ │ "prompt_cached_tokens": 0, -│ │ "prompt_tokens": 804, -│ │ "tokens": 808 -│ │ } -│ └── flue.compact [task] -│ input: { -│ "estimatedTokens": 2950, -│ "reason": "manual" -│ } -│ output: { -│ "completed": true -│ } -│ metadata: { -│ "flue.operation": "compact", -│ "flue.session": "main", -│ "flue.workflow_name": "instrumentation", -│ "provider": "flue", -│ "scenario": "flue-instrumentation" -│ } -│ metrics: { -│ "duration_ms": 0 -│ } -│ └── compaction:manual [task] -│ input: { -│ "estimatedTokens": 2950, -│ "reason": "manual" -│ } -│ output: { -│ "messagesAfter": 2, -│ "messagesBefore": 8 -│ } -│ metadata: { -│ "flue.compaction_reason": "manual", -│ "flue.session": "main", -│ "provider": "flue" -│ } -│ metrics: { -│ "duration_ms": 0, -│ "messages_after": 2, -│ "messages_before": 8 -│ } -│ └── flue.turn [llm] -│ input: [ -│ { -│ "content": [ -│ { -│ "text": "\n[User]: Complete this instrumented research flow. Call exactly one tool per turn and wait for each tool result before choosing the next tool. Step 1: call lookup with query \"flue instrumentation\". Step 2: use the lookup result id as lookupId and call web_search with query \"Braintrust Flue reasoning stream instrumentation\". Step 3: use the first web_search result url and call summarize_source. After summarize_source returns, reply with exactly PROMPT_DONE and no other text.\n\n[Assistant thinking]: The user wants me to complete a research flow with exactly three steps:\n1. Call lookup with query \"flue instrumentation\"\n2. Call web_search with the lookupId from step 1 and query \"Braintrust Flue reasoning stream instrumentation\"\n3. Call summarize_source with the first URL from step 2\n4. Reply with exactly \"PROMPT_DONE\"\n\nThe user emphasizes I should call exactly one tool per turn and wait for each result. Let me start with step 1.\n\n[Assistant]: I'll complete this instrumented research flow step by step. Starting with Step 1:\n\n[Assistant tool calls]: lookup(query=\"flue instrumentation\")\n\n[Tool result]: {\"id\":\"flue-session-2026\",\"query\":\"flue instrumentation\",\"topic\":\"session instrumentation\"}\n\n[Assistant thinking]: Good, I got the lookup result with id \"flue-session-2026\". Now I need to proceed to step 2: call web_search with this lookupId and the query \"Braintrust Flue reasoning stream instrumentation\".\n\n[Assistant]: Now proceeding to Step 2:\n\n[Assistant tool calls]: web_search(lookupId=\"flue-session-2026\", query=\"Braintrust Flue reasoning stream instrumentation\")\n\n[Tool result]: {\"lookupId\":\"flue-session-2026\",\"query\":\"Braintrust Flue reasoning stream instrumentation\",\"results\":[{\"title\":\"Flue reasoning stream instrumentation\",\"url\":\"https://example.test/flue/reasoning-streams\"}]}\n\n[Assistant thinking]: Perfect! I got the web_search results with one result. The first (and only) result has the URL \"https://example.test/flue/reasoning-streams\". Now I need to proceed to step 3: call summarize_source with this URL.\n\n[Assistant]: Now proceeding to Step 3:\n\n[Assistant tool calls]: summarize_source(url=\"https://example.test/flue/reasoning-streams\")\n\n[Tool result]: {\"summary\":\"Flue emits reasoning, tool execution, and LLM turn events separately.\",\"url\":\"https://example.test/flue/reasoning-streams\"}\n\n\nThis is the PREFIX of a turn that was too large to keep. The SUFFIX (recent work) is retained.\n\nSummarize the prefix to provide context for the retained suffix:\n\n## Original Request\n[What did the user ask for in this turn?]\n\n## Early Progress\n- [Key decisions and work done in the prefix]\n\n## Context for Suffix\n- [Information needed to understand the retained recent work]\n\nBe concise. Focus on what's needed to understand the kept suffix.", -│ "type": "text" -│ } -│ ], -│ "role": "user" -│ } -│ ] -│ output: { -│ "content": [ -│ { -│ "text": "## Original Request\nThe user requested to complete an instrumented research flow consisting of three specific steps, involving calling various tools in a sequential manner.\n\n## Early Progress", -│ "textSignature": "{\"v\":1,\"id\":\"msg_01ecf68325f9e202016a184601309081a0878ef4054e950d32\"}", -│ "type": "text" -│ } -│ ], -│ "role": "assistant" -│ } -│ metadata: { -│ "flue.api": "openai-responses", -│ "flue.model": "gpt-4o-mini", -│ "flue.provider": "openai", -│ "flue.session": "main", -│ "flue.stop_reason": "stop", -│ "flue.turn_purpose": "compaction_prefix", -│ "model": "gpt-4o-mini", -│ "provider": "openai" -│ } -│ metrics: { -│ "completion_tokens": 0, -│ "duration_ms": 0, -│ "estimated_cost": 0, -│ "prompt_cache_creation_tokens": 0, -│ "prompt_cached_tokens": 0, -│ "prompt_tokens": 0, -│ "tokens": 0 -│ } -└── flue.prompt [task] - input: [ - { - "content": [ - { - "text": "Complete this instrumented research flow. Call exactly one tool per turn and wait for each tool result before choosing the next tool. Step 1: call lookup with query \"flue instrumentation\". Step 2: use the lookup result id as lookupId and call web_search with query \"Braintrust Flue reasoning stream instrumentation\". Step 3: use the first web_search result url and call summarize_source. After summarize_source returns, reply with exactly PROMPT_DONE and no other text.", - "type": "text" - } - ], - "role": "user" - } - ] - output: "PROMPT_DONE" - metadata: { - "flue.operation": "prompt", - "flue.session": "main", - "flue.workflow_name": "instrumentation", - "provider": "flue", - "scenario": "flue-instrumentation" - } - metrics: { - "duration_ms": 0 - } - ├── flue.turn [llm] - │ input: [ - │ { - │ "content": [ - │ { - │ "text": "Complete this instrumented research flow. Call exactly one tool per turn and wait for each tool result before choosing the next tool. Step 1: call lookup with query \"flue instrumentation\". Step 2: use the lookup result id as lookupId and call web_search with query \"Braintrust Flue reasoning stream instrumentation\". Step 3: use the first web_search result url and call summarize_source. After summarize_source returns, reply with exactly PROMPT_DONE and no other text.", - │ "type": "text" - │ } - │ ], - │ "role": "user" - │ } - │ ] - │ output: { - │ "content": [ - │ { - │ "thinking": "The user wants me to complete a research flow with exactly three steps:\n1. Call lookup with query \"flue instrumentation\"\n2. Call web_search with the lookupId from step 1 and query \"Braintrust Flue reasoning stream instrumentation\"\n3. Call summarize_source with the first URL from step 2\n4. Reply with exactly \"PROMPT_DONE\"\n\nThe user emphasizes I should call exactly one tool per turn and wait for each result. Let me start with step 1.", - │ "thinkingSignature": "EoQFCm4IDhgCKkD+AoKMhpXNPUJ5eANwM28uUCJPCE+AXh0ACw7aXniX+YN04zdJl3hlHMbtAuKhhczLiEeOJtu4KtVpea5u5NFdMhpjbGF1ZGUtc29ubmV0LTQtNS0yMDI1MDkyOTgAQgh0aGlua2luZxIMIOTWCNEtsiI9vF4SGgwPXC861uZx8lpOziEiMLsCukGIluD0U44degzYnHFc+BwXNIEB+yatSHlZE+a5smr9crZcft3OLmsIdXuaFSrDAyQxWd9oqfJdnvza9eWIaOCeNkVCjrTc/wotBwh5Xp8m9k/WKt7P4zzUJBuLQ1f6ZnsdWkL8OiL/2E7Up55z2+UuVWsg1Y90/x27YRFdQjv3RnztYp4bZ5oIHAlTJoCzWiBYEh/Z8c2O+wy6MHo1cp0T57OroB/4HAqRZxrjTOXpKYLmYKXAY5tU8DTd3A0/FfEhg3oPSbOhX+112szFCpm3BRNJYooUUhwSu1hO4H/6r7d/Yp9XIM4tBfzuDx83CC/YetdOFgnYkdliw0xaUGaj4o37rWIRctWHAMdlUc4NDA0Ljc1uirHDXkN8EJwWSoIjPSBTA/p/wgLrvRV5Yy9Z0dMoWOpgYF6t1H/tNAyY8ml1CA7IWyd3jnfTZ1/G3Y0xFDwJDFlb4/QKc67w36SSCa+X9mMoTxD/DYN249lsII+XX3lQ7F1zJ07THOtCyRirCaRhmE/kGwl5tiB1QHrWa9bNHxQxf9i92435JGKN053Fe14AR70MtBSzIU2/zbgvhB1vF5lZt2i46hNxfo4lXR495gEwdwC6+8HJnlEJAJkdeHfZKj+RiwXoiYKPKsSbHi83X8Hh32IifuglNxdlxVQYAQ==", - │ "type": "thinking" - │ }, - │ { - │ "text": "I'll complete this instrumented research flow step by step. Starting with Step 1:", - │ "type": "text" - │ }, - │ { - │ "arguments": { - │ "query": "flue instrumentation" - │ }, - │ "id": "", - │ "name": "lookup", - │ "type": "toolCall" - │ } - │ ], - │ "role": "assistant" - │ } - │ metadata: { - │ "flue.api": "anthropic-messages", - │ "flue.input_mode": "full", - │ "flue.model": "claude-sonnet-4-5-20250929", - │ "flue.provider": "anthropic", - │ "flue.session": "main", - │ "flue.stop_reason": "toolUse", - │ "flue.turn_purpose": "agent", - │ "model": "claude-sonnet-4-5-20250929", - │ "provider": "anthropic" - │ } - │ metrics: { - │ "completion_tokens": 196, - │ "duration_ms": 0, - │ "estimated_cost": 0.0113025, - │ "prompt_cache_creation_tokens": 2222, - │ "prompt_cached_tokens": 0, - │ "prompt_tokens": 2232, - │ "tokens": 2428 - │ } - ├── tool:lookup [tool] - │ input: { - │ "query": "flue instrumentation" - │ } - │ output: { - │ "content": [ - │ { - │ "text": "{\"id\":\"flue-session-2026\",\"query\":\"flue instrumentation\",\"topic\":\"session instrumentation\"}", - │ "type": "text" - │ } - │ ], - │ "details": { - │ "customTool": "lookup" - │ } - │ } - │ metadata: { - │ "flue.session": "main", - │ "flue.tool_name": "lookup", - │ "provider": "flue" - │ } - │ metrics: { - │ "duration_ms": 0 - │ } - ├── flue.turn [llm] - │ input: [ - │ { - │ "content": [ - │ { - │ "thinking": "The user wants me to complete a research flow with exactly three steps:\n1. Call lookup with query \"flue instrumentation\"\n2. Call web_search with the lookupId from step 1 and query \"Braintrust Flue reasoning stream instrumentation\"\n3. Call summarize_source with the first URL from step 2\n4. Reply with exactly \"PROMPT_DONE\"\n\nThe user emphasizes I should call exactly one tool per turn and wait for each result. Let me start with step 1.", - │ "thinkingSignature": "EoQFCm4IDhgCKkD+AoKMhpXNPUJ5eANwM28uUCJPCE+AXh0ACw7aXniX+YN04zdJl3hlHMbtAuKhhczLiEeOJtu4KtVpea5u5NFdMhpjbGF1ZGUtc29ubmV0LTQtNS0yMDI1MDkyOTgAQgh0aGlua2luZxIMIOTWCNEtsiI9vF4SGgwPXC861uZx8lpOziEiMLsCukGIluD0U44degzYnHFc+BwXNIEB+yatSHlZE+a5smr9crZcft3OLmsIdXuaFSrDAyQxWd9oqfJdnvza9eWIaOCeNkVCjrTc/wotBwh5Xp8m9k/WKt7P4zzUJBuLQ1f6ZnsdWkL8OiL/2E7Up55z2+UuVWsg1Y90/x27YRFdQjv3RnztYp4bZ5oIHAlTJoCzWiBYEh/Z8c2O+wy6MHo1cp0T57OroB/4HAqRZxrjTOXpKYLmYKXAY5tU8DTd3A0/FfEhg3oPSbOhX+112szFCpm3BRNJYooUUhwSu1hO4H/6r7d/Yp9XIM4tBfzuDx83CC/YetdOFgnYkdliw0xaUGaj4o37rWIRctWHAMdlUc4NDA0Ljc1uirHDXkN8EJwWSoIjPSBTA/p/wgLrvRV5Yy9Z0dMoWOpgYF6t1H/tNAyY8ml1CA7IWyd3jnfTZ1/G3Y0xFDwJDFlb4/QKc67w36SSCa+X9mMoTxD/DYN249lsII+XX3lQ7F1zJ07THOtCyRirCaRhmE/kGwl5tiB1QHrWa9bNHxQxf9i92435JGKN053Fe14AR70MtBSzIU2/zbgvhB1vF5lZt2i46hNxfo4lXR495gEwdwC6+8HJnlEJAJkdeHfZKj+RiwXoiYKPKsSbHi83X8Hh32IifuglNxdlxVQYAQ==", - │ "type": "thinking" - │ }, - │ { - │ "text": "I'll complete this instrumented research flow step by step. Starting with Step 1:", - │ "type": "text" - │ }, - │ { - │ "arguments": { - │ "query": "flue instrumentation" - │ }, - │ "id": "", - │ "name": "lookup", - │ "type": "toolCall" - │ } - │ ], - │ "role": "assistant" - │ }, - │ { - │ "content": [ - │ { - │ "text": "{\"id\":\"flue-session-2026\",\"query\":\"flue instrumentation\",\"topic\":\"session instrumentation\"}", - │ "type": "text" - │ } - │ ], - │ "isError": false, - │ "role": "toolResult", - │ "toolCallId": "", - │ "toolName": "lookup" - │ } - │ ] - │ output: { - │ "content": [ - │ { - │ "thinking": "Good, I got the lookup result with id \"flue-session-2026\". Now I need to proceed to step 2: call web_search with this lookupId and the query \"Braintrust Flue reasoning stream instrumentation\".", - │ "thinkingSignature": "EpEDCm4IDhgCKkAMahve1kB1j4njp1ZSzO8eCK4bb5Ex7MB6tjdoOXfuUDRfkBqvIGdwVly8GiA8GGYC7Y/RVfREXF6A6OXHTwFyMhpjbGF1ZGUtc29ubmV0LTQtNS0yMDI1MDkyOTgAQgh0aGlua2luZxIMLw+fsa4yo7a7ZSoIGgyaTdvH020n2dQj9bUiMB4OokY29w+uZb4rqDnKDbLXUqUj6U0f1QCGQF2ZBYDDfDJyM5Jv1xY3rQiUUjkhBirQAS4XivjvMnWI5zHBmpI/u/pq4aGdrdb3vUsJ/ne6R1d80JA/INGF8vLkQcZ5xxEK0jU4yRE+ldc2uzavmtoxIKAFD6bqKks1Qv6u+TIZtuW7tUB/7jFxDw93KmJNruMIa8c1J6pPUyBy3xE7QwgmU0kDfIfdnljxLoEBiMwtnxjCPj9lT7HHz4FkrhbnQTV4MvAQohCLjaSbOTd6LdLxwtJPm9mc26ID4eYN1gCCzyAxGXhqLHlzc23PafvQjsZliWnLhaiNMnG0YEo78XkuJQsYAQ==", - │ "type": "thinking" - │ }, - │ { - │ "text": "Now proceeding to Step 2:", - │ "type": "text" - │ }, - │ { - │ "arguments": { - │ "lookupId": "flue-session-2026", - │ "query": "Braintrust Flue reasoning stream instrumentation" - │ }, - │ "id": "", - │ "name": "web_search", - │ "type": "toolCall" - │ } - │ ], - │ "role": "assistant" - │ } - │ metadata: { - │ "flue.api": "anthropic-messages", - │ "flue.input_message_offset": 1, - │ "flue.input_mode": "delta", - │ "flue.model": "claude-sonnet-4-5-20250929", - │ "flue.provider": "anthropic", - │ "flue.session": "main", - │ "flue.stop_reason": "toolUse", - │ "flue.turn_purpose": "agent", - │ "model": "claude-sonnet-4-5-20250929", - │ "provider": "anthropic" - │ } - │ metrics: { - │ "completion_tokens": 158, - │ "duration_ms": 0, - │ "estimated_cost": 0.0039606, - │ "prompt_cache_creation_tokens": 236, - │ "prompt_cached_tokens": 2222, - │ "prompt_tokens": 2471, - │ "tokens": 2629 - │ } - ├── tool:web_search [tool] - │ input: { - │ "lookupId": "flue-session-2026", - │ "query": "Braintrust Flue reasoning stream instrumentation" - │ } - │ output: { - │ "content": [ - │ { - │ "text": "{\"lookupId\":\"flue-session-2026\",\"query\":\"Braintrust Flue reasoning stream instrumentation\",\"results\":[{\"title\":\"Flue reasoning stream instrumentation\",\"url\":\"https://example.test/flue/reasoning-streams\"}]}", - │ "type": "text" - │ } - │ ], - │ "details": { - │ "customTool": "web_search" - │ } - │ } - │ metadata: { - │ "flue.session": "main", - │ "flue.tool_name": "web_search", - │ "provider": "flue" - │ } - │ metrics: { - │ "duration_ms": 0 - │ } - ├── flue.turn [llm] - │ input: [ - │ { - │ "content": [ - │ { - │ "thinking": "Good, I got the lookup result with id \"flue-session-2026\". Now I need to proceed to step 2: call web_search with this lookupId and the query \"Braintrust Flue reasoning stream instrumentation\".", - │ "thinkingSignature": "EpEDCm4IDhgCKkAMahve1kB1j4njp1ZSzO8eCK4bb5Ex7MB6tjdoOXfuUDRfkBqvIGdwVly8GiA8GGYC7Y/RVfREXF6A6OXHTwFyMhpjbGF1ZGUtc29ubmV0LTQtNS0yMDI1MDkyOTgAQgh0aGlua2luZxIMLw+fsa4yo7a7ZSoIGgyaTdvH020n2dQj9bUiMB4OokY29w+uZb4rqDnKDbLXUqUj6U0f1QCGQF2ZBYDDfDJyM5Jv1xY3rQiUUjkhBirQAS4XivjvMnWI5zHBmpI/u/pq4aGdrdb3vUsJ/ne6R1d80JA/INGF8vLkQcZ5xxEK0jU4yRE+ldc2uzavmtoxIKAFD6bqKks1Qv6u+TIZtuW7tUB/7jFxDw93KmJNruMIa8c1J6pPUyBy3xE7QwgmU0kDfIfdnljxLoEBiMwtnxjCPj9lT7HHz4FkrhbnQTV4MvAQohCLjaSbOTd6LdLxwtJPm9mc26ID4eYN1gCCzyAxGXhqLHlzc23PafvQjsZliWnLhaiNMnG0YEo78XkuJQsYAQ==", - │ "type": "thinking" - │ }, - │ { - │ "text": "Now proceeding to Step 2:", - │ "type": "text" - │ }, - │ { - │ "arguments": { - │ "lookupId": "flue-session-2026", - │ "query": "Braintrust Flue reasoning stream instrumentation" - │ }, - │ "id": "", - │ "name": "web_search", - │ "type": "toolCall" - │ } - │ ], - │ "role": "assistant" - │ }, - │ { - │ "content": [ - │ { - │ "text": "{\"lookupId\":\"flue-session-2026\",\"query\":\"Braintrust Flue reasoning stream instrumentation\",\"results\":[{\"title\":\"Flue reasoning stream instrumentation\",\"url\":\"https://example.test/flue/reasoning-streams\"}]}", - │ "type": "text" - │ } - │ ], - │ "isError": false, - │ "role": "toolResult", - │ "toolCallId": "", - │ "toolName": "web_search" - │ } - │ ] - │ output: { - │ "content": [ - │ { - │ "thinking": "Perfect! I got the web_search results with one result. The first (and only) result has the URL \"https://example.test/flue/reasoning-streams\". Now I need to proceed to step 3: call summarize_source with this URL.", - │ "thinkingSignature": "EqQDCm4IDhgCKkAXv7EDAhWMcrBz11bUd5qxlzyy03CAGQfRgLHDE0uKzLATh1o2sedd8GxLwwQqV0CmLRfNxsdK/ZDp3Lks9iysMhpjbGF1ZGUtc29ubmV0LTQtNS0yMDI1MDkyOTgAQgh0aGlua2luZxIMXDMniOy5Dy0KI8x7GgzgHs7BYJMs6/N0ur8iMPe0SwnHhP39OjEu7P8ASwK9ffwa3VP67QOlPzOstRRdEZNd0mq+BVjpmBXB+QcAzCrjAeAh/UO/d7SfFmXjqwA3//thoL/TTlGa6W+fdOCFRikn65JWT4ZdVFetfqD+9/QCP/nXEwMsLnTBAxC4EGryrgs2YT+OWwmEJ7rHKFeDs5djhwSb2J2ZJO1kRvD9s2vCCDkImygYBSIVgx1Y0CZJHhzn2Vf39v0Fbth2wDZa+OqxivV9NmDZEjmgcM+tTRx1CCnaww8LZEJQ68v/dUJy7hhD4MrNcQ62evJYPfT3+4EKKWJ/7HI7KXz91//+YAkph63XstbJMFJ3AldAFiq3ecauXt5r42N5gmCmwZX+sI48ENLGGAE=", - │ "type": "thinking" - │ }, - │ { - │ "text": "Now proceeding to Step 3:", - │ "type": "text" - │ }, - │ { - │ "arguments": { - │ "url": "https://example.test/flue/reasoning-streams" - │ }, - │ "id": "", - │ "name": "summarize_source", - │ "type": "toolCall" - │ } - │ ], - │ "role": "assistant" - │ } - │ metadata: { - │ "flue.api": "anthropic-messages", - │ "flue.input_message_offset": 3, - │ "flue.input_mode": "delta", - │ "flue.model": "claude-sonnet-4-5-20250929", - │ "flue.provider": "anthropic", - │ "flue.session": "main", - │ "flue.stop_reason": "toolUse", - │ "flue.turn_purpose": "agent", - │ "model": "claude-sonnet-4-5-20250929", - │ "provider": "anthropic" - │ } - │ metrics: { - │ "completion_tokens": 138, - │ "duration_ms": 0, - │ "estimated_cost": 0.0037239000000000005, - │ "prompt_cache_creation_tokens": 234, - │ "prompt_cached_tokens": 2458, - │ "prompt_tokens": 2705, - │ "tokens": 2843 - │ } - ├── tool:summarize_source [tool] - │ input: { - │ "url": "https://example.test/flue/reasoning-streams" - │ } - │ output: { - │ "content": [ - │ { - │ "text": "{\"summary\":\"Flue emits reasoning, tool execution, and LLM turn events separately.\",\"url\":\"https://example.test/flue/reasoning-streams\"}", - │ "type": "text" - │ } - │ ], - │ "details": { - │ "customTool": "summarize_source" - │ } - │ } - │ metadata: { - │ "flue.session": "main", - │ "flue.tool_name": "summarize_source", - │ "provider": "flue" - │ } - │ metrics: { - │ "duration_ms": 0 - │ } - └── flue.turn [llm] - input: [ - { - "content": [ - { - "thinking": "Perfect! I got the web_search results with one result. The first (and only) result has the URL \"https://example.test/flue/reasoning-streams\". Now I need to proceed to step 3: call summarize_source with this URL.", - "thinkingSignature": "EqQDCm4IDhgCKkAXv7EDAhWMcrBz11bUd5qxlzyy03CAGQfRgLHDE0uKzLATh1o2sedd8GxLwwQqV0CmLRfNxsdK/ZDp3Lks9iysMhpjbGF1ZGUtc29ubmV0LTQtNS0yMDI1MDkyOTgAQgh0aGlua2luZxIMXDMniOy5Dy0KI8x7GgzgHs7BYJMs6/N0ur8iMPe0SwnHhP39OjEu7P8ASwK9ffwa3VP67QOlPzOstRRdEZNd0mq+BVjpmBXB+QcAzCrjAeAh/UO/d7SfFmXjqwA3//thoL/TTlGa6W+fdOCFRikn65JWT4ZdVFetfqD+9/QCP/nXEwMsLnTBAxC4EGryrgs2YT+OWwmEJ7rHKFeDs5djhwSb2J2ZJO1kRvD9s2vCCDkImygYBSIVgx1Y0CZJHhzn2Vf39v0Fbth2wDZa+OqxivV9NmDZEjmgcM+tTRx1CCnaww8LZEJQ68v/dUJy7hhD4MrNcQ62evJYPfT3+4EKKWJ/7HI7KXz91//+YAkph63XstbJMFJ3AldAFiq3ecauXt5r42N5gmCmwZX+sI48ENLGGAE=", - "type": "thinking" - }, - { - "text": "Now proceeding to Step 3:", - "type": "text" - }, - { - "arguments": { - "url": "https://example.test/flue/reasoning-streams" - }, - "id": "", - "name": "summarize_source", - "type": "toolCall" - } - ], - "role": "assistant" - }, - { - "content": [ - { - "text": "{\"summary\":\"Flue emits reasoning, tool execution, and LLM turn events separately.\",\"url\":\"https://example.test/flue/reasoning-streams\"}", - "type": "text" - } - ], - "isError": false, - "role": "toolResult", - "toolCallId": "", - "toolName": "summarize_source" - } - ] - output: { - "content": [ - { - "thinking": "Perfect! The summarize_source call has completed successfully. According to the instructions, I should now reply with exactly \"PROMPT_DONE\" and no other text.", - "thinkingSignature": "Eu8CCm4IDhgCKkAn2QOIIlYfvcs6ynsmhCslHDtBsrTA4CfmimK7zyz6+k6dZldV2JBtEBET41It0gFcGEKC0n0mAhaYVuLutpDDMhpjbGF1ZGUtc29ubmV0LTQtNS0yMDI1MDkyOTgAQgh0aGlua2luZxIM1xfLptxSF8rKzou8GgwRLuVmfiBYOT4CZIsiMAYz8TpvchMAWodP+T9uZ+dJ47MVdNv+p86nZXmT5d5EWlCroiAOFHHzJH2o0UjJQSquAX6wu+jIC8/DXwurGIIf2ToldPuJWF5HGj8kHMnwh777XS1NUFl1irM24rvP7kTAv3Dp8YEwb8t3g1EsicY7j+C5DzgwxVsPYCVOCgdMqQjtsqpL7AX5KQ9+hR1gK1DQjshv4Gze3hw/+C+9iptd3RlDIuwO0cm8NY+sVqeWVh3TtQujQlVt1rW/HfWjclrtT0LZSGwc6RypmfMMQi6lncIk/EPgy0XbS1plKW0tLBgB", - "type": "thinking" - }, - { - "text": "PROMPT_DONE", - "type": "text" - } - ], - "role": "assistant" - } - metadata: { - "flue.api": "anthropic-messages", - "flue.input_message_offset": 5, - "flue.input_mode": "delta", - "flue.model": "claude-sonnet-4-5-20250929", - "flue.provider": "anthropic", - "flue.session": "main", - "flue.stop_reason": "stop", - "flue.turn_purpose": "agent", - "model": "claude-sonnet-4-5-20250929", - "provider": "anthropic" - } - metrics: { - "completion_tokens": 51, - "duration_ms": 0, - "estimated_cost": 0.0023391, - "prompt_cache_creation_tokens": 194, - "prompt_cached_tokens": 2692, - "prompt_tokens": 2899, - "tokens": 2950 - } diff --git a/e2e/scenarios/flue-instrumentation/__snapshots__/flue-v0-8-0-cli.span-tree.json b/e2e/scenarios/flue-instrumentation/__snapshots__/flue-v0-8-0-cli.span-tree.json deleted file mode 100644 index f06e08da7..000000000 --- a/e2e/scenarios/flue-instrumentation/__snapshots__/flue-v0-8-0-cli.span-tree.json +++ /dev/null @@ -1,1448 +0,0 @@ -{ - "span_tree": [ - { - "name": "workflow:instrumentation", - "type": "task", - "children": [ - { - "name": "flue.skill", - "type": "task", - "children": [ - { - "name": "flue.turn", - "type": "llm", - "children": [], - "input": [ - { - "content": [ - { - "text": "Run the skill named \"e2e-flue-skill\".\n\nArguments:\n{\n \"marker\": \"SKILL_DONE\"\n}", - "type": "text" - } - ], - "role": "user" - } - ], - "output": { - "content": [ - { - "text": "I'll run the e2e-flue-skill. Let me first read the skill file to understand its instructions.", - "type": "text" - }, - { - "arguments": { - "path": ".agents/e2e-flue-skill.md" - }, - "id": "", - "name": "read", - "type": "toolCall" - } - ], - "role": "assistant" - }, - "metadata": { - "flue.api": "anthropic-messages", - "flue.model": "claude-sonnet-4-5-20250929", - "flue.provider": "anthropic", - "flue.session": "skill", - "flue.stop_reason": "toolUse", - "flue.turn_purpose": "agent", - "model": "claude-sonnet-4-5-20250929", - "provider": "anthropic" - }, - "metrics": { - "completion_tokens": 89, - "duration_ms": 0, - "estimated_cost": 0.0041862, - "prompt_cache_creation_tokens": 670, - "prompt_cached_tokens": 1099, - "prompt_tokens": 1772, - "tokens": 1861 - } - }, - { - "name": "tool:read", - "type": "tool", - "children": [], - "input": { - "path": ".agents/e2e-flue-skill.md" - }, - "output": { - "content": [ - { - "text": "ENOENT: no such file or directory, open '/e2e/scenarios/flue-instrumentation/.agents/e2e-flue-skill.md'", - "type": "text" - } - ], - "details": {} - }, - "metadata": { - "flue.session": "skill", - "flue.tool_name": "read", - "provider": "flue" - }, - "metrics": { - "duration_ms": 0 - }, - "error": "{\"content\":[{\"type\":\"text\",\"text\":\"ENOENT: no such file or directory, open '/e2e/scenarios/flue-instrumentation/.agents/e2e-flue-skill.md'\"}],\"details\":{}}" - }, - { - "name": "flue.turn", - "type": "llm", - "children": [], - "input": [ - { - "content": [ - { - "text": "Run the skill named \"e2e-flue-skill\".\n\nArguments:\n{\n \"marker\": \"SKILL_DONE\"\n}", - "type": "text" - } - ], - "role": "user" - }, - { - "content": [ - { - "text": "I'll run the e2e-flue-skill. Let me first read the skill file to understand its instructions.", - "type": "text" - }, - { - "arguments": { - "path": ".agents/e2e-flue-skill.md" - }, - "id": "", - "name": "read", - "type": "toolCall" - } - ], - "role": "assistant" - }, - { - "content": [ - { - "text": "ENOENT: no such file or directory, open '/e2e/scenarios/flue-instrumentation/.agents/e2e-flue-skill.md'", - "type": "text" - } - ], - "isError": true, - "role": "toolResult", - "toolCallId": "", - "toolName": "read" - } - ], - "output": { - "content": [ - { - "arguments": { - "path": ".agents" - }, - "id": "", - "name": "read", - "type": "toolCall" - } - ], - "role": "assistant" - }, - "metadata": { - "flue.api": "anthropic-messages", - "flue.model": "claude-sonnet-4-5-20250929", - "flue.provider": "anthropic", - "flue.session": "skill", - "flue.stop_reason": "toolUse", - "flue.turn_purpose": "agent", - "model": "claude-sonnet-4-5-20250929", - "provider": "anthropic" - }, - "metrics": { - "completion_tokens": 52, - "duration_ms": 0, - "estimated_cost": 0.00201795, - "prompt_cache_creation_tokens": 183, - "prompt_cached_tokens": 1769, - "prompt_tokens": 1959, - "tokens": 2011 - } - }, - { - "name": "tool:read", - "type": "tool", - "children": [], - "input": { - "path": ".agents" - }, - "output": { - "content": [ - { - "text": "skills", - "type": "text" - } - ], - "details": { - "entries": 1, - "isDirectory": true, - "path": ".agents" - } - }, - "metadata": { - "flue.session": "skill", - "flue.tool_name": "read", - "provider": "flue" - }, - "metrics": { - "duration_ms": 0 - } - }, - { - "name": "flue.turn", - "type": "llm", - "children": [], - "input": [ - { - "content": [ - { - "text": "Run the skill named \"e2e-flue-skill\".\n\nArguments:\n{\n \"marker\": \"SKILL_DONE\"\n}", - "type": "text" - } - ], - "role": "user" - }, - { - "content": [ - { - "text": "I'll run the e2e-flue-skill. Let me first read the skill file to understand its instructions.", - "type": "text" - }, - { - "arguments": { - "path": ".agents/e2e-flue-skill.md" - }, - "id": "", - "name": "read", - "type": "toolCall" - } - ], - "role": "assistant" - }, - { - "content": [ - { - "text": "ENOENT: no such file or directory, open '/e2e/scenarios/flue-instrumentation/.agents/e2e-flue-skill.md'", - "type": "text" - } - ], - "isError": true, - "role": "toolResult", - "toolCallId": "", - "toolName": "read" - }, - { - "content": [ - { - "arguments": { - "path": ".agents" - }, - "id": "", - "name": "read", - "type": "toolCall" - } - ], - "role": "assistant" - }, - { - "content": [ - { - "text": "skills", - "type": "text" - } - ], - "isError": false, - "role": "toolResult", - "toolCallId": "", - "toolName": "read" - } - ], - "output": { - "content": [ - { - "arguments": { - "path": ".agents/skills" - }, - "id": "", - "name": "read", - "type": "toolCall" - } - ], - "role": "assistant" - }, - "metadata": { - "flue.api": "anthropic-messages", - "flue.model": "claude-sonnet-4-5-20250929", - "flue.provider": "anthropic", - "flue.session": "skill", - "flue.stop_reason": "toolUse", - "flue.turn_purpose": "agent", - "model": "claude-sonnet-4-5-20250929", - "provider": "anthropic" - }, - "metrics": { - "completion_tokens": 54, - "duration_ms": 0, - "estimated_cost": 0.0016611, - "prompt_cache_creation_tokens": 66, - "prompt_cached_tokens": 1952, - "prompt_tokens": 2024, - "tokens": 2078 - } - }, - { - "name": "tool:read", - "type": "tool", - "children": [], - "input": { - "path": ".agents/skills" - }, - "output": { - "content": [ - { - "text": "e2e-flue-skill", - "type": "text" - } - ], - "details": { - "entries": 1, - "isDirectory": true, - "path": ".agents/skills" - } - }, - "metadata": { - "flue.session": "skill", - "flue.tool_name": "read", - "provider": "flue" - }, - "metrics": { - "duration_ms": 0 - } - }, - { - "name": "flue.turn", - "type": "llm", - "children": [], - "input": [ - { - "content": [ - { - "text": "Run the skill named \"e2e-flue-skill\".\n\nArguments:\n{\n \"marker\": \"SKILL_DONE\"\n}", - "type": "text" - } - ], - "role": "user" - }, - { - "content": [ - { - "text": "I'll run the e2e-flue-skill. Let me first read the skill file to understand its instructions.", - "type": "text" - }, - { - "arguments": { - "path": ".agents/e2e-flue-skill.md" - }, - "id": "", - "name": "read", - "type": "toolCall" - } - ], - "role": "assistant" - }, - { - "content": [ - { - "text": "ENOENT: no such file or directory, open '/e2e/scenarios/flue-instrumentation/.agents/e2e-flue-skill.md'", - "type": "text" - } - ], - "isError": true, - "role": "toolResult", - "toolCallId": "", - "toolName": "read" - }, - { - "content": [ - { - "arguments": { - "path": ".agents" - }, - "id": "", - "name": "read", - "type": "toolCall" - } - ], - "role": "assistant" - }, - { - "content": [ - { - "text": "skills", - "type": "text" - } - ], - "isError": false, - "role": "toolResult", - "toolCallId": "", - "toolName": "read" - }, - { - "content": [ - { - "arguments": { - "path": ".agents/skills" - }, - "id": "", - "name": "read", - "type": "toolCall" - } - ], - "role": "assistant" - }, - { - "content": [ - { - "text": "e2e-flue-skill", - "type": "text" - } - ], - "isError": false, - "role": "toolResult", - "toolCallId": "", - "toolName": "read" - } - ], - "output": { - "content": [ - { - "arguments": { - "path": ".agents/skills/e2e-flue-skill" - }, - "id": "", - "name": "read", - "type": "toolCall" - } - ], - "role": "assistant" - }, - "metadata": { - "flue.api": "anthropic-messages", - "flue.model": "claude-sonnet-4-5-20250929", - "flue.provider": "anthropic", - "flue.session": "skill", - "flue.stop_reason": "toolUse", - "flue.turn_purpose": "agent", - "model": "claude-sonnet-4-5-20250929", - "provider": "anthropic" - }, - "metrics": { - "completion_tokens": 63, - "duration_ms": 0, - "estimated_cost": 0.0018459, - "prompt_cache_creation_tokens": 74, - "prompt_cached_tokens": 2018, - "prompt_tokens": 2098, - "tokens": 2161 - } - }, - { - "name": "tool:read", - "type": "tool", - "children": [], - "input": { - "path": ".agents/skills/e2e-flue-skill" - }, - "output": { - "content": [ - { - "text": "SKILL.md", - "type": "text" - } - ], - "details": { - "entries": 1, - "isDirectory": true, - "path": ".agents/skills/e2e-flue-skill" - } - }, - "metadata": { - "flue.session": "skill", - "flue.tool_name": "read", - "provider": "flue" - }, - "metrics": { - "duration_ms": 0 - } - }, - { - "name": "flue.turn", - "type": "llm", - "children": [], - "input": [ - { - "content": [ - { - "text": "Run the skill named \"e2e-flue-skill\".\n\nArguments:\n{\n \"marker\": \"SKILL_DONE\"\n}", - "type": "text" - } - ], - "role": "user" - }, - { - "content": [ - { - "text": "I'll run the e2e-flue-skill. Let me first read the skill file to understand its instructions.", - "type": "text" - }, - { - "arguments": { - "path": ".agents/e2e-flue-skill.md" - }, - "id": "", - "name": "read", - "type": "toolCall" - } - ], - "role": "assistant" - }, - { - "content": [ - { - "text": "ENOENT: no such file or directory, open '/e2e/scenarios/flue-instrumentation/.agents/e2e-flue-skill.md'", - "type": "text" - } - ], - "isError": true, - "role": "toolResult", - "toolCallId": "", - "toolName": "read" - }, - { - "content": [ - { - "arguments": { - "path": ".agents" - }, - "id": "", - "name": "read", - "type": "toolCall" - } - ], - "role": "assistant" - }, - { - "content": [ - { - "text": "skills", - "type": "text" - } - ], - "isError": false, - "role": "toolResult", - "toolCallId": "", - "toolName": "read" - }, - { - "content": [ - { - "arguments": { - "path": ".agents/skills" - }, - "id": "", - "name": "read", - "type": "toolCall" - } - ], - "role": "assistant" - }, - { - "content": [ - { - "text": "e2e-flue-skill", - "type": "text" - } - ], - "isError": false, - "role": "toolResult", - "toolCallId": "", - "toolName": "read" - }, - { - "content": [ - { - "arguments": { - "path": ".agents/skills/e2e-flue-skill" - }, - "id": "", - "name": "read", - "type": "toolCall" - } - ], - "role": "assistant" - }, - { - "content": [ - { - "text": "SKILL.md", - "type": "text" - } - ], - "isError": false, - "role": "toolResult", - "toolCallId": "", - "toolName": "read" - } - ], - "output": { - "content": [ - { - "arguments": { - "path": ".agents/skills/e2e-flue-skill/SKILL.md" - }, - "id": "", - "name": "read", - "type": "toolCall" - } - ], - "role": "assistant" - }, - "metadata": { - "flue.api": "anthropic-messages", - "flue.model": "claude-sonnet-4-5-20250929", - "flue.provider": "anthropic", - "flue.session": "skill", - "flue.stop_reason": "toolUse", - "flue.turn_purpose": "agent", - "model": "claude-sonnet-4-5-20250929", - "provider": "anthropic" - }, - "metrics": { - "completion_tokens": 68, - "duration_ms": 0, - "estimated_cost": 0.0019618500000000002, - "prompt_cache_creation_tokens": 79, - "prompt_cached_tokens": 2092, - "prompt_tokens": 2177, - "tokens": 2245 - } - }, - { - "name": "tool:read", - "type": "tool", - "children": [], - "input": { - "path": ".agents/skills/e2e-flue-skill/SKILL.md" - }, - "output": { - "content": [ - { - "text": "---\nname: e2e-flue-skill\ndescription: Deterministic skill used by the Braintrust Flue instrumentation e2e test.\n---\n\nReturn the marker from the provided args exactly once. Output no other text.\n", - "type": "text" - } - ], - "details": { - "lines": 7, - "path": ".agents/skills/e2e-flue-skill/SKILL.md" - } - }, - "metadata": { - "flue.session": "skill", - "flue.tool_name": "read", - "provider": "flue" - }, - "metrics": { - "duration_ms": 0 - } - }, - { - "name": "flue.turn", - "type": "llm", - "children": [], - "input": [ - { - "content": [ - { - "text": "Run the skill named \"e2e-flue-skill\".\n\nArguments:\n{\n \"marker\": \"SKILL_DONE\"\n}", - "type": "text" - } - ], - "role": "user" - }, - { - "content": [ - { - "text": "I'll run the e2e-flue-skill. Let me first read the skill file to understand its instructions.", - "type": "text" - }, - { - "arguments": { - "path": ".agents/e2e-flue-skill.md" - }, - "id": "", - "name": "read", - "type": "toolCall" - } - ], - "role": "assistant" - }, - { - "content": [ - { - "text": "ENOENT: no such file or directory, open '/e2e/scenarios/flue-instrumentation/.agents/e2e-flue-skill.md'", - "type": "text" - } - ], - "isError": true, - "role": "toolResult", - "toolCallId": "", - "toolName": "read" - }, - { - "content": [ - { - "arguments": { - "path": ".agents" - }, - "id": "", - "name": "read", - "type": "toolCall" - } - ], - "role": "assistant" - }, - { - "content": [ - { - "text": "skills", - "type": "text" - } - ], - "isError": false, - "role": "toolResult", - "toolCallId": "", - "toolName": "read" - }, - { - "content": [ - { - "arguments": { - "path": ".agents/skills" - }, - "id": "", - "name": "read", - "type": "toolCall" - } - ], - "role": "assistant" - }, - { - "content": [ - { - "text": "e2e-flue-skill", - "type": "text" - } - ], - "isError": false, - "role": "toolResult", - "toolCallId": "", - "toolName": "read" - }, - { - "content": [ - { - "arguments": { - "path": ".agents/skills/e2e-flue-skill" - }, - "id": "", - "name": "read", - "type": "toolCall" - } - ], - "role": "assistant" - }, - { - "content": [ - { - "text": "SKILL.md", - "type": "text" - } - ], - "isError": false, - "role": "toolResult", - "toolCallId": "", - "toolName": "read" - }, - { - "content": [ - { - "arguments": { - "path": ".agents/skills/e2e-flue-skill/SKILL.md" - }, - "id": "", - "name": "read", - "type": "toolCall" - } - ], - "role": "assistant" - }, - { - "content": [ - { - "text": "---\nname: e2e-flue-skill\ndescription: Deterministic skill used by the Braintrust Flue instrumentation e2e test.\n---\n\nReturn the marker from the provided args exactly once. Output no other text.\n", - "type": "text" - } - ], - "isError": false, - "role": "toolResult", - "toolCallId": "", - "toolName": "read" - } - ], - "output": { - "content": [ - { - "text": "SKILL_DONE", - "type": "text" - } - ], - "role": "assistant" - }, - "metadata": { - "flue.api": "anthropic-messages", - "flue.model": "claude-sonnet-4-5-20250929", - "flue.provider": "anthropic", - "flue.session": "skill", - "flue.stop_reason": "stop", - "flue.turn_purpose": "agent", - "model": "claude-sonnet-4-5-20250929", - "provider": "anthropic" - }, - "metrics": { - "completion_tokens": 8, - "duration_ms": 0, - "estimated_cost": 0.00129555, - "prompt_cache_creation_tokens": 135, - "prompt_cached_tokens": 2171, - "prompt_tokens": 2312, - "tokens": 2320 - } - } - ], - "input": [ - { - "content": [ - { - "text": "Run the skill named \"e2e-flue-skill\".\n\nArguments:\n{\n \"marker\": \"SKILL_DONE\"\n}", - "type": "text" - } - ], - "role": "user" - } - ], - "output": "SKILL_DONE", - "metadata": { - "flue.operation": "skill", - "flue.session": "skill", - "flue.workflow_name": "instrumentation", - "provider": "flue", - "scenario": "flue-instrumentation" - }, - "metrics": { - "duration_ms": 0 - } - }, - { - "name": "flue.task", - "type": "task", - "children": [ - { - "name": "flue.turn", - "type": "llm", - "children": [], - "input": [ - { - "content": [ - { - "text": "Reply with exactly TASK_DONE and no other text.", - "type": "text" - } - ], - "role": "user" - } - ], - "output": { - "content": [ - { - "text": "TASK_DONE", - "textSignature": "{\"v\":1,\"id\":\"msg_09b32b2821e9a4d8016a184600019081929d34bb7d4c6a246f\"}", - "type": "text" - } - ], - "role": "assistant" - }, - "metadata": { - "flue.api": "openai-responses", - "flue.input_mode": "full", - "flue.model": "gpt-4o-mini", - "flue.provider": "openai", - "flue.session": "task:task:", - "flue.stop_reason": "stop", - "flue.turn_purpose": "agent", - "model": "gpt-4o-mini", - "provider": "openai" - }, - "metrics": { - "completion_tokens": 4, - "duration_ms": 0, - "estimated_cost": 0.00012299999999999998, - "prompt_cache_creation_tokens": 0, - "prompt_cached_tokens": 0, - "prompt_tokens": 804, - "tokens": 808 - } - } - ], - "input": "Reply with exactly TASK_DONE and no other text.", - "output": "TASK_DONE", - "metadata": { - "flue.session": "task", - "provider": "flue" - }, - "metrics": { - "duration_ms": 0 - } - }, - { - "name": "flue.compact", - "type": "task", - "children": [ - { - "name": "compaction:manual", - "type": "task", - "children": [ - { - "name": "flue.turn", - "type": "llm", - "children": [], - "input": [ - { - "content": [ - { - "text": "\n[User]: Complete this instrumented research flow. Call exactly one tool per turn and wait for each tool result before choosing the next tool. Step 1: call lookup with query \"flue instrumentation\". Step 2: use the lookup result id as lookupId and call web_search with query \"Braintrust Flue reasoning stream instrumentation\". Step 3: use the first web_search result url and call summarize_source. After summarize_source returns, reply with exactly PROMPT_DONE and no other text.\n\n[Assistant thinking]: The user wants me to complete a research flow with exactly three steps:\n1. Call lookup with query \"flue instrumentation\"\n2. Call web_search with the lookupId from step 1 and query \"Braintrust Flue reasoning stream instrumentation\"\n3. Call summarize_source with the first URL from step 2\n4. Reply with exactly \"PROMPT_DONE\"\n\nThe user emphasizes I should call exactly one tool per turn and wait for each result. Let me start with step 1.\n\n[Assistant]: I'll complete this instrumented research flow step by step. Starting with Step 1:\n\n[Assistant tool calls]: lookup(query=\"flue instrumentation\")\n\n[Tool result]: {\"id\":\"flue-session-2026\",\"query\":\"flue instrumentation\",\"topic\":\"session instrumentation\"}\n\n[Assistant thinking]: Good, I got the lookup result with id \"flue-session-2026\". Now I need to proceed to step 2: call web_search with this lookupId and the query \"Braintrust Flue reasoning stream instrumentation\".\n\n[Assistant]: Now proceeding to Step 2:\n\n[Assistant tool calls]: web_search(lookupId=\"flue-session-2026\", query=\"Braintrust Flue reasoning stream instrumentation\")\n\n[Tool result]: {\"lookupId\":\"flue-session-2026\",\"query\":\"Braintrust Flue reasoning stream instrumentation\",\"results\":[{\"title\":\"Flue reasoning stream instrumentation\",\"url\":\"https://example.test/flue/reasoning-streams\"}]}\n\n[Assistant thinking]: Perfect! I got the web_search results with one result. The first (and only) result has the URL \"https://example.test/flue/reasoning-streams\". Now I need to proceed to step 3: call summarize_source with this URL.\n\n[Assistant]: Now proceeding to Step 3:\n\n[Assistant tool calls]: summarize_source(url=\"https://example.test/flue/reasoning-streams\")\n\n[Tool result]: {\"summary\":\"Flue emits reasoning, tool execution, and LLM turn events separately.\",\"url\":\"https://example.test/flue/reasoning-streams\"}\n\n\nThis is the PREFIX of a turn that was too large to keep. The SUFFIX (recent work) is retained.\n\nSummarize the prefix to provide context for the retained suffix:\n\n## Original Request\n[What did the user ask for in this turn?]\n\n## Early Progress\n- [Key decisions and work done in the prefix]\n\n## Context for Suffix\n- [Information needed to understand the retained recent work]\n\nBe concise. Focus on what's needed to understand the kept suffix.", - "type": "text" - } - ], - "role": "user" - } - ], - "output": { - "content": [ - { - "text": "## Original Request\nThe user requested to complete an instrumented research flow consisting of three specific steps, involving calling various tools in a sequential manner.\n\n## Early Progress", - "textSignature": "{\"v\":1,\"id\":\"msg_01ecf68325f9e202016a184601309081a0878ef4054e950d32\"}", - "type": "text" - } - ], - "role": "assistant" - }, - "metadata": { - "flue.api": "openai-responses", - "flue.model": "gpt-4o-mini", - "flue.provider": "openai", - "flue.session": "main", - "flue.stop_reason": "stop", - "flue.turn_purpose": "compaction_prefix", - "model": "gpt-4o-mini", - "provider": "openai" - }, - "metrics": { - "completion_tokens": 0, - "duration_ms": 0, - "estimated_cost": 0, - "prompt_cache_creation_tokens": 0, - "prompt_cached_tokens": 0, - "prompt_tokens": 0, - "tokens": 0 - } - } - ], - "input": { - "estimatedTokens": 2950, - "reason": "manual" - }, - "output": { - "messagesAfter": 2, - "messagesBefore": 8 - }, - "metadata": { - "flue.compaction_reason": "manual", - "flue.session": "main", - "provider": "flue" - }, - "metrics": { - "duration_ms": 0, - "messages_after": 2, - "messages_before": 8 - } - } - ], - "input": { - "estimatedTokens": 2950, - "reason": "manual" - }, - "output": { - "completed": true - }, - "metadata": { - "flue.operation": "compact", - "flue.session": "main", - "flue.workflow_name": "instrumentation", - "provider": "flue", - "scenario": "flue-instrumentation" - }, - "metrics": { - "duration_ms": 0 - } - } - ], - "input": { - "metadata": { - "scenario": "flue-instrumentation", - "testRunId": "" - }, - "scenario": "flue-instrumentation" - }, - "output": { - "scenario": "flue-instrumentation", - "status": "done" - }, - "metadata": { - "flue.workflow_name": "instrumentation", - "provider": "flue", - "scenario": "flue-instrumentation" - }, - "metrics": { - "duration_ms": 0 - } - }, - { - "name": "flue.prompt", - "type": "task", - "children": [ - { - "name": "flue.turn", - "type": "llm", - "children": [], - "input": [ - { - "content": [ - { - "text": "Complete this instrumented research flow. Call exactly one tool per turn and wait for each tool result before choosing the next tool. Step 1: call lookup with query \"flue instrumentation\". Step 2: use the lookup result id as lookupId and call web_search with query \"Braintrust Flue reasoning stream instrumentation\". Step 3: use the first web_search result url and call summarize_source. After summarize_source returns, reply with exactly PROMPT_DONE and no other text.", - "type": "text" - } - ], - "role": "user" - } - ], - "output": { - "content": [ - { - "thinking": "The user wants me to complete a research flow with exactly three steps:\n1. Call lookup with query \"flue instrumentation\"\n2. Call web_search with the lookupId from step 1 and query \"Braintrust Flue reasoning stream instrumentation\"\n3. Call summarize_source with the first URL from step 2\n4. Reply with exactly \"PROMPT_DONE\"\n\nThe user emphasizes I should call exactly one tool per turn and wait for each result. Let me start with step 1.", - "thinkingSignature": "EoQFCm4IDhgCKkD+AoKMhpXNPUJ5eANwM28uUCJPCE+AXh0ACw7aXniX+YN04zdJl3hlHMbtAuKhhczLiEeOJtu4KtVpea5u5NFdMhpjbGF1ZGUtc29ubmV0LTQtNS0yMDI1MDkyOTgAQgh0aGlua2luZxIMIOTWCNEtsiI9vF4SGgwPXC861uZx8lpOziEiMLsCukGIluD0U44degzYnHFc+BwXNIEB+yatSHlZE+a5smr9crZcft3OLmsIdXuaFSrDAyQxWd9oqfJdnvza9eWIaOCeNkVCjrTc/wotBwh5Xp8m9k/WKt7P4zzUJBuLQ1f6ZnsdWkL8OiL/2E7Up55z2+UuVWsg1Y90/x27YRFdQjv3RnztYp4bZ5oIHAlTJoCzWiBYEh/Z8c2O+wy6MHo1cp0T57OroB/4HAqRZxrjTOXpKYLmYKXAY5tU8DTd3A0/FfEhg3oPSbOhX+112szFCpm3BRNJYooUUhwSu1hO4H/6r7d/Yp9XIM4tBfzuDx83CC/YetdOFgnYkdliw0xaUGaj4o37rWIRctWHAMdlUc4NDA0Ljc1uirHDXkN8EJwWSoIjPSBTA/p/wgLrvRV5Yy9Z0dMoWOpgYF6t1H/tNAyY8ml1CA7IWyd3jnfTZ1/G3Y0xFDwJDFlb4/QKc67w36SSCa+X9mMoTxD/DYN249lsII+XX3lQ7F1zJ07THOtCyRirCaRhmE/kGwl5tiB1QHrWa9bNHxQxf9i92435JGKN053Fe14AR70MtBSzIU2/zbgvhB1vF5lZt2i46hNxfo4lXR495gEwdwC6+8HJnlEJAJkdeHfZKj+RiwXoiYKPKsSbHi83X8Hh32IifuglNxdlxVQYAQ==", - "type": "thinking" - }, - { - "text": "I'll complete this instrumented research flow step by step. Starting with Step 1:", - "type": "text" - }, - { - "arguments": { - "query": "flue instrumentation" - }, - "id": "", - "name": "lookup", - "type": "toolCall" - } - ], - "role": "assistant" - }, - "metadata": { - "flue.api": "anthropic-messages", - "flue.input_mode": "full", - "flue.model": "claude-sonnet-4-5-20250929", - "flue.provider": "anthropic", - "flue.session": "main", - "flue.stop_reason": "toolUse", - "flue.turn_purpose": "agent", - "model": "claude-sonnet-4-5-20250929", - "provider": "anthropic" - }, - "metrics": { - "completion_tokens": 196, - "duration_ms": 0, - "estimated_cost": 0.0113025, - "prompt_cache_creation_tokens": 2222, - "prompt_cached_tokens": 0, - "prompt_tokens": 2232, - "tokens": 2428 - } - }, - { - "name": "tool:lookup", - "type": "tool", - "children": [], - "input": { - "query": "flue instrumentation" - }, - "output": { - "content": [ - { - "text": "{\"id\":\"flue-session-2026\",\"query\":\"flue instrumentation\",\"topic\":\"session instrumentation\"}", - "type": "text" - } - ], - "details": { - "customTool": "lookup" - } - }, - "metadata": { - "flue.session": "main", - "flue.tool_name": "lookup", - "provider": "flue" - }, - "metrics": { - "duration_ms": 0 - } - }, - { - "name": "flue.turn", - "type": "llm", - "children": [], - "input": [ - { - "content": [ - { - "thinking": "The user wants me to complete a research flow with exactly three steps:\n1. Call lookup with query \"flue instrumentation\"\n2. Call web_search with the lookupId from step 1 and query \"Braintrust Flue reasoning stream instrumentation\"\n3. Call summarize_source with the first URL from step 2\n4. Reply with exactly \"PROMPT_DONE\"\n\nThe user emphasizes I should call exactly one tool per turn and wait for each result. Let me start with step 1.", - "thinkingSignature": "EoQFCm4IDhgCKkD+AoKMhpXNPUJ5eANwM28uUCJPCE+AXh0ACw7aXniX+YN04zdJl3hlHMbtAuKhhczLiEeOJtu4KtVpea5u5NFdMhpjbGF1ZGUtc29ubmV0LTQtNS0yMDI1MDkyOTgAQgh0aGlua2luZxIMIOTWCNEtsiI9vF4SGgwPXC861uZx8lpOziEiMLsCukGIluD0U44degzYnHFc+BwXNIEB+yatSHlZE+a5smr9crZcft3OLmsIdXuaFSrDAyQxWd9oqfJdnvza9eWIaOCeNkVCjrTc/wotBwh5Xp8m9k/WKt7P4zzUJBuLQ1f6ZnsdWkL8OiL/2E7Up55z2+UuVWsg1Y90/x27YRFdQjv3RnztYp4bZ5oIHAlTJoCzWiBYEh/Z8c2O+wy6MHo1cp0T57OroB/4HAqRZxrjTOXpKYLmYKXAY5tU8DTd3A0/FfEhg3oPSbOhX+112szFCpm3BRNJYooUUhwSu1hO4H/6r7d/Yp9XIM4tBfzuDx83CC/YetdOFgnYkdliw0xaUGaj4o37rWIRctWHAMdlUc4NDA0Ljc1uirHDXkN8EJwWSoIjPSBTA/p/wgLrvRV5Yy9Z0dMoWOpgYF6t1H/tNAyY8ml1CA7IWyd3jnfTZ1/G3Y0xFDwJDFlb4/QKc67w36SSCa+X9mMoTxD/DYN249lsII+XX3lQ7F1zJ07THOtCyRirCaRhmE/kGwl5tiB1QHrWa9bNHxQxf9i92435JGKN053Fe14AR70MtBSzIU2/zbgvhB1vF5lZt2i46hNxfo4lXR495gEwdwC6+8HJnlEJAJkdeHfZKj+RiwXoiYKPKsSbHi83X8Hh32IifuglNxdlxVQYAQ==", - "type": "thinking" - }, - { - "text": "I'll complete this instrumented research flow step by step. Starting with Step 1:", - "type": "text" - }, - { - "arguments": { - "query": "flue instrumentation" - }, - "id": "", - "name": "lookup", - "type": "toolCall" - } - ], - "role": "assistant" - }, - { - "content": [ - { - "text": "{\"id\":\"flue-session-2026\",\"query\":\"flue instrumentation\",\"topic\":\"session instrumentation\"}", - "type": "text" - } - ], - "isError": false, - "role": "toolResult", - "toolCallId": "", - "toolName": "lookup" - } - ], - "output": { - "content": [ - { - "thinking": "Good, I got the lookup result with id \"flue-session-2026\". Now I need to proceed to step 2: call web_search with this lookupId and the query \"Braintrust Flue reasoning stream instrumentation\".", - "thinkingSignature": "EpEDCm4IDhgCKkAMahve1kB1j4njp1ZSzO8eCK4bb5Ex7MB6tjdoOXfuUDRfkBqvIGdwVly8GiA8GGYC7Y/RVfREXF6A6OXHTwFyMhpjbGF1ZGUtc29ubmV0LTQtNS0yMDI1MDkyOTgAQgh0aGlua2luZxIMLw+fsa4yo7a7ZSoIGgyaTdvH020n2dQj9bUiMB4OokY29w+uZb4rqDnKDbLXUqUj6U0f1QCGQF2ZBYDDfDJyM5Jv1xY3rQiUUjkhBirQAS4XivjvMnWI5zHBmpI/u/pq4aGdrdb3vUsJ/ne6R1d80JA/INGF8vLkQcZ5xxEK0jU4yRE+ldc2uzavmtoxIKAFD6bqKks1Qv6u+TIZtuW7tUB/7jFxDw93KmJNruMIa8c1J6pPUyBy3xE7QwgmU0kDfIfdnljxLoEBiMwtnxjCPj9lT7HHz4FkrhbnQTV4MvAQohCLjaSbOTd6LdLxwtJPm9mc26ID4eYN1gCCzyAxGXhqLHlzc23PafvQjsZliWnLhaiNMnG0YEo78XkuJQsYAQ==", - "type": "thinking" - }, - { - "text": "Now proceeding to Step 2:", - "type": "text" - }, - { - "arguments": { - "lookupId": "flue-session-2026", - "query": "Braintrust Flue reasoning stream instrumentation" - }, - "id": "", - "name": "web_search", - "type": "toolCall" - } - ], - "role": "assistant" - }, - "metadata": { - "flue.api": "anthropic-messages", - "flue.input_message_offset": 1, - "flue.input_mode": "delta", - "flue.model": "claude-sonnet-4-5-20250929", - "flue.provider": "anthropic", - "flue.session": "main", - "flue.stop_reason": "toolUse", - "flue.turn_purpose": "agent", - "model": "claude-sonnet-4-5-20250929", - "provider": "anthropic" - }, - "metrics": { - "completion_tokens": 158, - "duration_ms": 0, - "estimated_cost": 0.0039606, - "prompt_cache_creation_tokens": 236, - "prompt_cached_tokens": 2222, - "prompt_tokens": 2471, - "tokens": 2629 - } - }, - { - "name": "tool:web_search", - "type": "tool", - "children": [], - "input": { - "lookupId": "flue-session-2026", - "query": "Braintrust Flue reasoning stream instrumentation" - }, - "output": { - "content": [ - { - "text": "{\"lookupId\":\"flue-session-2026\",\"query\":\"Braintrust Flue reasoning stream instrumentation\",\"results\":[{\"title\":\"Flue reasoning stream instrumentation\",\"url\":\"https://example.test/flue/reasoning-streams\"}]}", - "type": "text" - } - ], - "details": { - "customTool": "web_search" - } - }, - "metadata": { - "flue.session": "main", - "flue.tool_name": "web_search", - "provider": "flue" - }, - "metrics": { - "duration_ms": 0 - } - }, - { - "name": "flue.turn", - "type": "llm", - "children": [], - "input": [ - { - "content": [ - { - "thinking": "Good, I got the lookup result with id \"flue-session-2026\". Now I need to proceed to step 2: call web_search with this lookupId and the query \"Braintrust Flue reasoning stream instrumentation\".", - "thinkingSignature": "EpEDCm4IDhgCKkAMahve1kB1j4njp1ZSzO8eCK4bb5Ex7MB6tjdoOXfuUDRfkBqvIGdwVly8GiA8GGYC7Y/RVfREXF6A6OXHTwFyMhpjbGF1ZGUtc29ubmV0LTQtNS0yMDI1MDkyOTgAQgh0aGlua2luZxIMLw+fsa4yo7a7ZSoIGgyaTdvH020n2dQj9bUiMB4OokY29w+uZb4rqDnKDbLXUqUj6U0f1QCGQF2ZBYDDfDJyM5Jv1xY3rQiUUjkhBirQAS4XivjvMnWI5zHBmpI/u/pq4aGdrdb3vUsJ/ne6R1d80JA/INGF8vLkQcZ5xxEK0jU4yRE+ldc2uzavmtoxIKAFD6bqKks1Qv6u+TIZtuW7tUB/7jFxDw93KmJNruMIa8c1J6pPUyBy3xE7QwgmU0kDfIfdnljxLoEBiMwtnxjCPj9lT7HHz4FkrhbnQTV4MvAQohCLjaSbOTd6LdLxwtJPm9mc26ID4eYN1gCCzyAxGXhqLHlzc23PafvQjsZliWnLhaiNMnG0YEo78XkuJQsYAQ==", - "type": "thinking" - }, - { - "text": "Now proceeding to Step 2:", - "type": "text" - }, - { - "arguments": { - "lookupId": "flue-session-2026", - "query": "Braintrust Flue reasoning stream instrumentation" - }, - "id": "", - "name": "web_search", - "type": "toolCall" - } - ], - "role": "assistant" - }, - { - "content": [ - { - "text": "{\"lookupId\":\"flue-session-2026\",\"query\":\"Braintrust Flue reasoning stream instrumentation\",\"results\":[{\"title\":\"Flue reasoning stream instrumentation\",\"url\":\"https://example.test/flue/reasoning-streams\"}]}", - "type": "text" - } - ], - "isError": false, - "role": "toolResult", - "toolCallId": "", - "toolName": "web_search" - } - ], - "output": { - "content": [ - { - "thinking": "Perfect! I got the web_search results with one result. The first (and only) result has the URL \"https://example.test/flue/reasoning-streams\". Now I need to proceed to step 3: call summarize_source with this URL.", - "thinkingSignature": "EqQDCm4IDhgCKkAXv7EDAhWMcrBz11bUd5qxlzyy03CAGQfRgLHDE0uKzLATh1o2sedd8GxLwwQqV0CmLRfNxsdK/ZDp3Lks9iysMhpjbGF1ZGUtc29ubmV0LTQtNS0yMDI1MDkyOTgAQgh0aGlua2luZxIMXDMniOy5Dy0KI8x7GgzgHs7BYJMs6/N0ur8iMPe0SwnHhP39OjEu7P8ASwK9ffwa3VP67QOlPzOstRRdEZNd0mq+BVjpmBXB+QcAzCrjAeAh/UO/d7SfFmXjqwA3//thoL/TTlGa6W+fdOCFRikn65JWT4ZdVFetfqD+9/QCP/nXEwMsLnTBAxC4EGryrgs2YT+OWwmEJ7rHKFeDs5djhwSb2J2ZJO1kRvD9s2vCCDkImygYBSIVgx1Y0CZJHhzn2Vf39v0Fbth2wDZa+OqxivV9NmDZEjmgcM+tTRx1CCnaww8LZEJQ68v/dUJy7hhD4MrNcQ62evJYPfT3+4EKKWJ/7HI7KXz91//+YAkph63XstbJMFJ3AldAFiq3ecauXt5r42N5gmCmwZX+sI48ENLGGAE=", - "type": "thinking" - }, - { - "text": "Now proceeding to Step 3:", - "type": "text" - }, - { - "arguments": { - "url": "https://example.test/flue/reasoning-streams" - }, - "id": "", - "name": "summarize_source", - "type": "toolCall" - } - ], - "role": "assistant" - }, - "metadata": { - "flue.api": "anthropic-messages", - "flue.input_message_offset": 3, - "flue.input_mode": "delta", - "flue.model": "claude-sonnet-4-5-20250929", - "flue.provider": "anthropic", - "flue.session": "main", - "flue.stop_reason": "toolUse", - "flue.turn_purpose": "agent", - "model": "claude-sonnet-4-5-20250929", - "provider": "anthropic" - }, - "metrics": { - "completion_tokens": 138, - "duration_ms": 0, - "estimated_cost": 0.0037239000000000005, - "prompt_cache_creation_tokens": 234, - "prompt_cached_tokens": 2458, - "prompt_tokens": 2705, - "tokens": 2843 - } - }, - { - "name": "tool:summarize_source", - "type": "tool", - "children": [], - "input": { - "url": "https://example.test/flue/reasoning-streams" - }, - "output": { - "content": [ - { - "text": "{\"summary\":\"Flue emits reasoning, tool execution, and LLM turn events separately.\",\"url\":\"https://example.test/flue/reasoning-streams\"}", - "type": "text" - } - ], - "details": { - "customTool": "summarize_source" - } - }, - "metadata": { - "flue.session": "main", - "flue.tool_name": "summarize_source", - "provider": "flue" - }, - "metrics": { - "duration_ms": 0 - } - }, - { - "name": "flue.turn", - "type": "llm", - "children": [], - "input": [ - { - "content": [ - { - "thinking": "Perfect! I got the web_search results with one result. The first (and only) result has the URL \"https://example.test/flue/reasoning-streams\". Now I need to proceed to step 3: call summarize_source with this URL.", - "thinkingSignature": "EqQDCm4IDhgCKkAXv7EDAhWMcrBz11bUd5qxlzyy03CAGQfRgLHDE0uKzLATh1o2sedd8GxLwwQqV0CmLRfNxsdK/ZDp3Lks9iysMhpjbGF1ZGUtc29ubmV0LTQtNS0yMDI1MDkyOTgAQgh0aGlua2luZxIMXDMniOy5Dy0KI8x7GgzgHs7BYJMs6/N0ur8iMPe0SwnHhP39OjEu7P8ASwK9ffwa3VP67QOlPzOstRRdEZNd0mq+BVjpmBXB+QcAzCrjAeAh/UO/d7SfFmXjqwA3//thoL/TTlGa6W+fdOCFRikn65JWT4ZdVFetfqD+9/QCP/nXEwMsLnTBAxC4EGryrgs2YT+OWwmEJ7rHKFeDs5djhwSb2J2ZJO1kRvD9s2vCCDkImygYBSIVgx1Y0CZJHhzn2Vf39v0Fbth2wDZa+OqxivV9NmDZEjmgcM+tTRx1CCnaww8LZEJQ68v/dUJy7hhD4MrNcQ62evJYPfT3+4EKKWJ/7HI7KXz91//+YAkph63XstbJMFJ3AldAFiq3ecauXt5r42N5gmCmwZX+sI48ENLGGAE=", - "type": "thinking" - }, - { - "text": "Now proceeding to Step 3:", - "type": "text" - }, - { - "arguments": { - "url": "https://example.test/flue/reasoning-streams" - }, - "id": "", - "name": "summarize_source", - "type": "toolCall" - } - ], - "role": "assistant" - }, - { - "content": [ - { - "text": "{\"summary\":\"Flue emits reasoning, tool execution, and LLM turn events separately.\",\"url\":\"https://example.test/flue/reasoning-streams\"}", - "type": "text" - } - ], - "isError": false, - "role": "toolResult", - "toolCallId": "", - "toolName": "summarize_source" - } - ], - "output": { - "content": [ - { - "thinking": "Perfect! The summarize_source call has completed successfully. According to the instructions, I should now reply with exactly \"PROMPT_DONE\" and no other text.", - "thinkingSignature": "Eu8CCm4IDhgCKkAn2QOIIlYfvcs6ynsmhCslHDtBsrTA4CfmimK7zyz6+k6dZldV2JBtEBET41It0gFcGEKC0n0mAhaYVuLutpDDMhpjbGF1ZGUtc29ubmV0LTQtNS0yMDI1MDkyOTgAQgh0aGlua2luZxIM1xfLptxSF8rKzou8GgwRLuVmfiBYOT4CZIsiMAYz8TpvchMAWodP+T9uZ+dJ47MVdNv+p86nZXmT5d5EWlCroiAOFHHzJH2o0UjJQSquAX6wu+jIC8/DXwurGIIf2ToldPuJWF5HGj8kHMnwh777XS1NUFl1irM24rvP7kTAv3Dp8YEwb8t3g1EsicY7j+C5DzgwxVsPYCVOCgdMqQjtsqpL7AX5KQ9+hR1gK1DQjshv4Gze3hw/+C+9iptd3RlDIuwO0cm8NY+sVqeWVh3TtQujQlVt1rW/HfWjclrtT0LZSGwc6RypmfMMQi6lncIk/EPgy0XbS1plKW0tLBgB", - "type": "thinking" - }, - { - "text": "PROMPT_DONE", - "type": "text" - } - ], - "role": "assistant" - }, - "metadata": { - "flue.api": "anthropic-messages", - "flue.input_message_offset": 5, - "flue.input_mode": "delta", - "flue.model": "claude-sonnet-4-5-20250929", - "flue.provider": "anthropic", - "flue.session": "main", - "flue.stop_reason": "stop", - "flue.turn_purpose": "agent", - "model": "claude-sonnet-4-5-20250929", - "provider": "anthropic" - }, - "metrics": { - "completion_tokens": 51, - "duration_ms": 0, - "estimated_cost": 0.0023391, - "prompt_cache_creation_tokens": 194, - "prompt_cached_tokens": 2692, - "prompt_tokens": 2899, - "tokens": 2950 - } - } - ], - "input": [ - { - "content": [ - { - "text": "Complete this instrumented research flow. Call exactly one tool per turn and wait for each tool result before choosing the next tool. Step 1: call lookup with query \"flue instrumentation\". Step 2: use the lookup result id as lookupId and call web_search with query \"Braintrust Flue reasoning stream instrumentation\". Step 3: use the first web_search result url and call summarize_source. After summarize_source returns, reply with exactly PROMPT_DONE and no other text.", - "type": "text" - } - ], - "role": "user" - } - ], - "output": "PROMPT_DONE", - "metadata": { - "flue.operation": "prompt", - "flue.session": "main", - "flue.workflow_name": "instrumentation", - "provider": "flue", - "scenario": "flue-instrumentation" - }, - "metrics": { - "duration_ms": 0 - } - } - ] -} diff --git a/e2e/scenarios/flue-instrumentation/__snapshots__/flue-v0-8-0-cli.span-tree.txt b/e2e/scenarios/flue-instrumentation/__snapshots__/flue-v0-8-0-cli.span-tree.txt deleted file mode 100644 index aaf72f391..000000000 --- a/e2e/scenarios/flue-instrumentation/__snapshots__/flue-v0-8-0-cli.span-tree.txt +++ /dev/null @@ -1,1335 +0,0 @@ -span_tree: -├── workflow:instrumentation [task] -│ input: { -│ "metadata": { -│ "scenario": "flue-instrumentation", -│ "testRunId": "" -│ }, -│ "scenario": "flue-instrumentation" -│ } -│ output: { -│ "scenario": "flue-instrumentation", -│ "status": "done" -│ } -│ metadata: { -│ "flue.workflow_name": "instrumentation", -│ "provider": "flue", -│ "scenario": "flue-instrumentation" -│ } -│ metrics: { -│ "duration_ms": 0 -│ } -│ ├── flue.skill [task] -│ │ input: [ -│ │ { -│ │ "content": [ -│ │ { -│ │ "text": "Run the skill named \"e2e-flue-skill\".\n\nArguments:\n{\n \"marker\": \"SKILL_DONE\"\n}", -│ │ "type": "text" -│ │ } -│ │ ], -│ │ "role": "user" -│ │ } -│ │ ] -│ │ output: "SKILL_DONE" -│ │ metadata: { -│ │ "flue.operation": "skill", -│ │ "flue.session": "skill", -│ │ "flue.workflow_name": "instrumentation", -│ │ "provider": "flue", -│ │ "scenario": "flue-instrumentation" -│ │ } -│ │ metrics: { -│ │ "duration_ms": 0 -│ │ } -│ │ ├── flue.turn [llm] -│ │ │ input: [ -│ │ │ { -│ │ │ "content": [ -│ │ │ { -│ │ │ "text": "Run the skill named \"e2e-flue-skill\".\n\nArguments:\n{\n \"marker\": \"SKILL_DONE\"\n}", -│ │ │ "type": "text" -│ │ │ } -│ │ │ ], -│ │ │ "role": "user" -│ │ │ } -│ │ │ ] -│ │ │ output: { -│ │ │ "content": [ -│ │ │ { -│ │ │ "text": "I'll run the e2e-flue-skill. Let me first read the skill file to understand its instructions.", -│ │ │ "type": "text" -│ │ │ }, -│ │ │ { -│ │ │ "arguments": { -│ │ │ "path": ".agents/e2e-flue-skill.md" -│ │ │ }, -│ │ │ "id": "", -│ │ │ "name": "read", -│ │ │ "type": "toolCall" -│ │ │ } -│ │ │ ], -│ │ │ "role": "assistant" -│ │ │ } -│ │ │ metadata: { -│ │ │ "flue.api": "anthropic-messages", -│ │ │ "flue.model": "claude-sonnet-4-5-20250929", -│ │ │ "flue.provider": "anthropic", -│ │ │ "flue.session": "skill", -│ │ │ "flue.stop_reason": "toolUse", -│ │ │ "flue.turn_purpose": "agent", -│ │ │ "model": "claude-sonnet-4-5-20250929", -│ │ │ "provider": "anthropic" -│ │ │ } -│ │ │ metrics: { -│ │ │ "completion_tokens": 89, -│ │ │ "duration_ms": 0, -│ │ │ "estimated_cost": 0.0041862, -│ │ │ "prompt_cache_creation_tokens": 670, -│ │ │ "prompt_cached_tokens": 1099, -│ │ │ "prompt_tokens": 1772, -│ │ │ "tokens": 1861 -│ │ │ } -│ │ ├── tool:read [tool] -│ │ │ input: { -│ │ │ "path": ".agents/e2e-flue-skill.md" -│ │ │ } -│ │ │ output: { -│ │ │ "content": [ -│ │ │ { -│ │ │ "text": "ENOENT: no such file or directory, open '/e2e/scenarios/flue-instrumentation/.agents/e2e-flue-skill.md'", -│ │ │ "type": "text" -│ │ │ } -│ │ │ ], -│ │ │ "details": {} -│ │ │ } -│ │ │ metadata: { -│ │ │ "flue.session": "skill", -│ │ │ "flue.tool_name": "read", -│ │ │ "provider": "flue" -│ │ │ } -│ │ │ metrics: { -│ │ │ "duration_ms": 0 -│ │ │ } -│ │ │ error: "{\"content\":[{\"type\":\"text\",\"text\":\"ENOENT: no such file or directory, open '/e2e/scenarios/flue-instrumentation/.agents/e2e-flue-skill.md'\"}],\"details\":{}}" -│ │ ├── flue.turn [llm] -│ │ │ input: [ -│ │ │ { -│ │ │ "content": [ -│ │ │ { -│ │ │ "text": "Run the skill named \"e2e-flue-skill\".\n\nArguments:\n{\n \"marker\": \"SKILL_DONE\"\n}", -│ │ │ "type": "text" -│ │ │ } -│ │ │ ], -│ │ │ "role": "user" -│ │ │ }, -│ │ │ { -│ │ │ "content": [ -│ │ │ { -│ │ │ "text": "I'll run the e2e-flue-skill. Let me first read the skill file to understand its instructions.", -│ │ │ "type": "text" -│ │ │ }, -│ │ │ { -│ │ │ "arguments": { -│ │ │ "path": ".agents/e2e-flue-skill.md" -│ │ │ }, -│ │ │ "id": "", -│ │ │ "name": "read", -│ │ │ "type": "toolCall" -│ │ │ } -│ │ │ ], -│ │ │ "role": "assistant" -│ │ │ }, -│ │ │ { -│ │ │ "content": [ -│ │ │ { -│ │ │ "text": "ENOENT: no such file or directory, open '/e2e/scenarios/flue-instrumentation/.agents/e2e-flue-skill.md'", -│ │ │ "type": "text" -│ │ │ } -│ │ │ ], -│ │ │ "isError": true, -│ │ │ "role": "toolResult", -│ │ │ "toolCallId": "", -│ │ │ "toolName": "read" -│ │ │ } -│ │ │ ] -│ │ │ output: { -│ │ │ "content": [ -│ │ │ { -│ │ │ "arguments": { -│ │ │ "path": ".agents" -│ │ │ }, -│ │ │ "id": "", -│ │ │ "name": "read", -│ │ │ "type": "toolCall" -│ │ │ } -│ │ │ ], -│ │ │ "role": "assistant" -│ │ │ } -│ │ │ metadata: { -│ │ │ "flue.api": "anthropic-messages", -│ │ │ "flue.model": "claude-sonnet-4-5-20250929", -│ │ │ "flue.provider": "anthropic", -│ │ │ "flue.session": "skill", -│ │ │ "flue.stop_reason": "toolUse", -│ │ │ "flue.turn_purpose": "agent", -│ │ │ "model": "claude-sonnet-4-5-20250929", -│ │ │ "provider": "anthropic" -│ │ │ } -│ │ │ metrics: { -│ │ │ "completion_tokens": 52, -│ │ │ "duration_ms": 0, -│ │ │ "estimated_cost": 0.00201795, -│ │ │ "prompt_cache_creation_tokens": 183, -│ │ │ "prompt_cached_tokens": 1769, -│ │ │ "prompt_tokens": 1959, -│ │ │ "tokens": 2011 -│ │ │ } -│ │ ├── tool:read [tool] -│ │ │ input: { -│ │ │ "path": ".agents" -│ │ │ } -│ │ │ output: { -│ │ │ "content": [ -│ │ │ { -│ │ │ "text": "skills", -│ │ │ "type": "text" -│ │ │ } -│ │ │ ], -│ │ │ "details": { -│ │ │ "entries": 1, -│ │ │ "isDirectory": true, -│ │ │ "path": ".agents" -│ │ │ } -│ │ │ } -│ │ │ metadata: { -│ │ │ "flue.session": "skill", -│ │ │ "flue.tool_name": "read", -│ │ │ "provider": "flue" -│ │ │ } -│ │ │ metrics: { -│ │ │ "duration_ms": 0 -│ │ │ } -│ │ ├── flue.turn [llm] -│ │ │ input: [ -│ │ │ { -│ │ │ "content": [ -│ │ │ { -│ │ │ "text": "Run the skill named \"e2e-flue-skill\".\n\nArguments:\n{\n \"marker\": \"SKILL_DONE\"\n}", -│ │ │ "type": "text" -│ │ │ } -│ │ │ ], -│ │ │ "role": "user" -│ │ │ }, -│ │ │ { -│ │ │ "content": [ -│ │ │ { -│ │ │ "text": "I'll run the e2e-flue-skill. Let me first read the skill file to understand its instructions.", -│ │ │ "type": "text" -│ │ │ }, -│ │ │ { -│ │ │ "arguments": { -│ │ │ "path": ".agents/e2e-flue-skill.md" -│ │ │ }, -│ │ │ "id": "", -│ │ │ "name": "read", -│ │ │ "type": "toolCall" -│ │ │ } -│ │ │ ], -│ │ │ "role": "assistant" -│ │ │ }, -│ │ │ { -│ │ │ "content": [ -│ │ │ { -│ │ │ "text": "ENOENT: no such file or directory, open '/e2e/scenarios/flue-instrumentation/.agents/e2e-flue-skill.md'", -│ │ │ "type": "text" -│ │ │ } -│ │ │ ], -│ │ │ "isError": true, -│ │ │ "role": "toolResult", -│ │ │ "toolCallId": "", -│ │ │ "toolName": "read" -│ │ │ }, -│ │ │ { -│ │ │ "content": [ -│ │ │ { -│ │ │ "arguments": { -│ │ │ "path": ".agents" -│ │ │ }, -│ │ │ "id": "", -│ │ │ "name": "read", -│ │ │ "type": "toolCall" -│ │ │ } -│ │ │ ], -│ │ │ "role": "assistant" -│ │ │ }, -│ │ │ { -│ │ │ "content": [ -│ │ │ { -│ │ │ "text": "skills", -│ │ │ "type": "text" -│ │ │ } -│ │ │ ], -│ │ │ "isError": false, -│ │ │ "role": "toolResult", -│ │ │ "toolCallId": "", -│ │ │ "toolName": "read" -│ │ │ } -│ │ │ ] -│ │ │ output: { -│ │ │ "content": [ -│ │ │ { -│ │ │ "arguments": { -│ │ │ "path": ".agents/skills" -│ │ │ }, -│ │ │ "id": "", -│ │ │ "name": "read", -│ │ │ "type": "toolCall" -│ │ │ } -│ │ │ ], -│ │ │ "role": "assistant" -│ │ │ } -│ │ │ metadata: { -│ │ │ "flue.api": "anthropic-messages", -│ │ │ "flue.model": "claude-sonnet-4-5-20250929", -│ │ │ "flue.provider": "anthropic", -│ │ │ "flue.session": "skill", -│ │ │ "flue.stop_reason": "toolUse", -│ │ │ "flue.turn_purpose": "agent", -│ │ │ "model": "claude-sonnet-4-5-20250929", -│ │ │ "provider": "anthropic" -│ │ │ } -│ │ │ metrics: { -│ │ │ "completion_tokens": 54, -│ │ │ "duration_ms": 0, -│ │ │ "estimated_cost": 0.0016611, -│ │ │ "prompt_cache_creation_tokens": 66, -│ │ │ "prompt_cached_tokens": 1952, -│ │ │ "prompt_tokens": 2024, -│ │ │ "tokens": 2078 -│ │ │ } -│ │ ├── tool:read [tool] -│ │ │ input: { -│ │ │ "path": ".agents/skills" -│ │ │ } -│ │ │ output: { -│ │ │ "content": [ -│ │ │ { -│ │ │ "text": "e2e-flue-skill", -│ │ │ "type": "text" -│ │ │ } -│ │ │ ], -│ │ │ "details": { -│ │ │ "entries": 1, -│ │ │ "isDirectory": true, -│ │ │ "path": ".agents/skills" -│ │ │ } -│ │ │ } -│ │ │ metadata: { -│ │ │ "flue.session": "skill", -│ │ │ "flue.tool_name": "read", -│ │ │ "provider": "flue" -│ │ │ } -│ │ │ metrics: { -│ │ │ "duration_ms": 0 -│ │ │ } -│ │ ├── flue.turn [llm] -│ │ │ input: [ -│ │ │ { -│ │ │ "content": [ -│ │ │ { -│ │ │ "text": "Run the skill named \"e2e-flue-skill\".\n\nArguments:\n{\n \"marker\": \"SKILL_DONE\"\n}", -│ │ │ "type": "text" -│ │ │ } -│ │ │ ], -│ │ │ "role": "user" -│ │ │ }, -│ │ │ { -│ │ │ "content": [ -│ │ │ { -│ │ │ "text": "I'll run the e2e-flue-skill. Let me first read the skill file to understand its instructions.", -│ │ │ "type": "text" -│ │ │ }, -│ │ │ { -│ │ │ "arguments": { -│ │ │ "path": ".agents/e2e-flue-skill.md" -│ │ │ }, -│ │ │ "id": "", -│ │ │ "name": "read", -│ │ │ "type": "toolCall" -│ │ │ } -│ │ │ ], -│ │ │ "role": "assistant" -│ │ │ }, -│ │ │ { -│ │ │ "content": [ -│ │ │ { -│ │ │ "text": "ENOENT: no such file or directory, open '/e2e/scenarios/flue-instrumentation/.agents/e2e-flue-skill.md'", -│ │ │ "type": "text" -│ │ │ } -│ │ │ ], -│ │ │ "isError": true, -│ │ │ "role": "toolResult", -│ │ │ "toolCallId": "", -│ │ │ "toolName": "read" -│ │ │ }, -│ │ │ { -│ │ │ "content": [ -│ │ │ { -│ │ │ "arguments": { -│ │ │ "path": ".agents" -│ │ │ }, -│ │ │ "id": "", -│ │ │ "name": "read", -│ │ │ "type": "toolCall" -│ │ │ } -│ │ │ ], -│ │ │ "role": "assistant" -│ │ │ }, -│ │ │ { -│ │ │ "content": [ -│ │ │ { -│ │ │ "text": "skills", -│ │ │ "type": "text" -│ │ │ } -│ │ │ ], -│ │ │ "isError": false, -│ │ │ "role": "toolResult", -│ │ │ "toolCallId": "", -│ │ │ "toolName": "read" -│ │ │ }, -│ │ │ { -│ │ │ "content": [ -│ │ │ { -│ │ │ "arguments": { -│ │ │ "path": ".agents/skills" -│ │ │ }, -│ │ │ "id": "", -│ │ │ "name": "read", -│ │ │ "type": "toolCall" -│ │ │ } -│ │ │ ], -│ │ │ "role": "assistant" -│ │ │ }, -│ │ │ { -│ │ │ "content": [ -│ │ │ { -│ │ │ "text": "e2e-flue-skill", -│ │ │ "type": "text" -│ │ │ } -│ │ │ ], -│ │ │ "isError": false, -│ │ │ "role": "toolResult", -│ │ │ "toolCallId": "", -│ │ │ "toolName": "read" -│ │ │ } -│ │ │ ] -│ │ │ output: { -│ │ │ "content": [ -│ │ │ { -│ │ │ "arguments": { -│ │ │ "path": ".agents/skills/e2e-flue-skill" -│ │ │ }, -│ │ │ "id": "", -│ │ │ "name": "read", -│ │ │ "type": "toolCall" -│ │ │ } -│ │ │ ], -│ │ │ "role": "assistant" -│ │ │ } -│ │ │ metadata: { -│ │ │ "flue.api": "anthropic-messages", -│ │ │ "flue.model": "claude-sonnet-4-5-20250929", -│ │ │ "flue.provider": "anthropic", -│ │ │ "flue.session": "skill", -│ │ │ "flue.stop_reason": "toolUse", -│ │ │ "flue.turn_purpose": "agent", -│ │ │ "model": "claude-sonnet-4-5-20250929", -│ │ │ "provider": "anthropic" -│ │ │ } -│ │ │ metrics: { -│ │ │ "completion_tokens": 63, -│ │ │ "duration_ms": 0, -│ │ │ "estimated_cost": 0.0018459, -│ │ │ "prompt_cache_creation_tokens": 74, -│ │ │ "prompt_cached_tokens": 2018, -│ │ │ "prompt_tokens": 2098, -│ │ │ "tokens": 2161 -│ │ │ } -│ │ ├── tool:read [tool] -│ │ │ input: { -│ │ │ "path": ".agents/skills/e2e-flue-skill" -│ │ │ } -│ │ │ output: { -│ │ │ "content": [ -│ │ │ { -│ │ │ "text": "SKILL.md", -│ │ │ "type": "text" -│ │ │ } -│ │ │ ], -│ │ │ "details": { -│ │ │ "entries": 1, -│ │ │ "isDirectory": true, -│ │ │ "path": ".agents/skills/e2e-flue-skill" -│ │ │ } -│ │ │ } -│ │ │ metadata: { -│ │ │ "flue.session": "skill", -│ │ │ "flue.tool_name": "read", -│ │ │ "provider": "flue" -│ │ │ } -│ │ │ metrics: { -│ │ │ "duration_ms": 0 -│ │ │ } -│ │ ├── flue.turn [llm] -│ │ │ input: [ -│ │ │ { -│ │ │ "content": [ -│ │ │ { -│ │ │ "text": "Run the skill named \"e2e-flue-skill\".\n\nArguments:\n{\n \"marker\": \"SKILL_DONE\"\n}", -│ │ │ "type": "text" -│ │ │ } -│ │ │ ], -│ │ │ "role": "user" -│ │ │ }, -│ │ │ { -│ │ │ "content": [ -│ │ │ { -│ │ │ "text": "I'll run the e2e-flue-skill. Let me first read the skill file to understand its instructions.", -│ │ │ "type": "text" -│ │ │ }, -│ │ │ { -│ │ │ "arguments": { -│ │ │ "path": ".agents/e2e-flue-skill.md" -│ │ │ }, -│ │ │ "id": "", -│ │ │ "name": "read", -│ │ │ "type": "toolCall" -│ │ │ } -│ │ │ ], -│ │ │ "role": "assistant" -│ │ │ }, -│ │ │ { -│ │ │ "content": [ -│ │ │ { -│ │ │ "text": "ENOENT: no such file or directory, open '/e2e/scenarios/flue-instrumentation/.agents/e2e-flue-skill.md'", -│ │ │ "type": "text" -│ │ │ } -│ │ │ ], -│ │ │ "isError": true, -│ │ │ "role": "toolResult", -│ │ │ "toolCallId": "", -│ │ │ "toolName": "read" -│ │ │ }, -│ │ │ { -│ │ │ "content": [ -│ │ │ { -│ │ │ "arguments": { -│ │ │ "path": ".agents" -│ │ │ }, -│ │ │ "id": "", -│ │ │ "name": "read", -│ │ │ "type": "toolCall" -│ │ │ } -│ │ │ ], -│ │ │ "role": "assistant" -│ │ │ }, -│ │ │ { -│ │ │ "content": [ -│ │ │ { -│ │ │ "text": "skills", -│ │ │ "type": "text" -│ │ │ } -│ │ │ ], -│ │ │ "isError": false, -│ │ │ "role": "toolResult", -│ │ │ "toolCallId": "", -│ │ │ "toolName": "read" -│ │ │ }, -│ │ │ { -│ │ │ "content": [ -│ │ │ { -│ │ │ "arguments": { -│ │ │ "path": ".agents/skills" -│ │ │ }, -│ │ │ "id": "", -│ │ │ "name": "read", -│ │ │ "type": "toolCall" -│ │ │ } -│ │ │ ], -│ │ │ "role": "assistant" -│ │ │ }, -│ │ │ { -│ │ │ "content": [ -│ │ │ { -│ │ │ "text": "e2e-flue-skill", -│ │ │ "type": "text" -│ │ │ } -│ │ │ ], -│ │ │ "isError": false, -│ │ │ "role": "toolResult", -│ │ │ "toolCallId": "", -│ │ │ "toolName": "read" -│ │ │ }, -│ │ │ { -│ │ │ "content": [ -│ │ │ { -│ │ │ "arguments": { -│ │ │ "path": ".agents/skills/e2e-flue-skill" -│ │ │ }, -│ │ │ "id": "", -│ │ │ "name": "read", -│ │ │ "type": "toolCall" -│ │ │ } -│ │ │ ], -│ │ │ "role": "assistant" -│ │ │ }, -│ │ │ { -│ │ │ "content": [ -│ │ │ { -│ │ │ "text": "SKILL.md", -│ │ │ "type": "text" -│ │ │ } -│ │ │ ], -│ │ │ "isError": false, -│ │ │ "role": "toolResult", -│ │ │ "toolCallId": "", -│ │ │ "toolName": "read" -│ │ │ } -│ │ │ ] -│ │ │ output: { -│ │ │ "content": [ -│ │ │ { -│ │ │ "arguments": { -│ │ │ "path": ".agents/skills/e2e-flue-skill/SKILL.md" -│ │ │ }, -│ │ │ "id": "", -│ │ │ "name": "read", -│ │ │ "type": "toolCall" -│ │ │ } -│ │ │ ], -│ │ │ "role": "assistant" -│ │ │ } -│ │ │ metadata: { -│ │ │ "flue.api": "anthropic-messages", -│ │ │ "flue.model": "claude-sonnet-4-5-20250929", -│ │ │ "flue.provider": "anthropic", -│ │ │ "flue.session": "skill", -│ │ │ "flue.stop_reason": "toolUse", -│ │ │ "flue.turn_purpose": "agent", -│ │ │ "model": "claude-sonnet-4-5-20250929", -│ │ │ "provider": "anthropic" -│ │ │ } -│ │ │ metrics: { -│ │ │ "completion_tokens": 68, -│ │ │ "duration_ms": 0, -│ │ │ "estimated_cost": 0.0019618500000000002, -│ │ │ "prompt_cache_creation_tokens": 79, -│ │ │ "prompt_cached_tokens": 2092, -│ │ │ "prompt_tokens": 2177, -│ │ │ "tokens": 2245 -│ │ │ } -│ │ ├── tool:read [tool] -│ │ │ input: { -│ │ │ "path": ".agents/skills/e2e-flue-skill/SKILL.md" -│ │ │ } -│ │ │ output: { -│ │ │ "content": [ -│ │ │ { -│ │ │ "text": "---\nname: e2e-flue-skill\ndescription: Deterministic skill used by the Braintrust Flue instrumentation e2e test.\n---\n\nReturn the marker from the provided args exactly once. Output no other text.\n", -│ │ │ "type": "text" -│ │ │ } -│ │ │ ], -│ │ │ "details": { -│ │ │ "lines": 7, -│ │ │ "path": ".agents/skills/e2e-flue-skill/SKILL.md" -│ │ │ } -│ │ │ } -│ │ │ metadata: { -│ │ │ "flue.session": "skill", -│ │ │ "flue.tool_name": "read", -│ │ │ "provider": "flue" -│ │ │ } -│ │ │ metrics: { -│ │ │ "duration_ms": 0 -│ │ │ } -│ │ └── flue.turn [llm] -│ │ input: [ -│ │ { -│ │ "content": [ -│ │ { -│ │ "text": "Run the skill named \"e2e-flue-skill\".\n\nArguments:\n{\n \"marker\": \"SKILL_DONE\"\n}", -│ │ "type": "text" -│ │ } -│ │ ], -│ │ "role": "user" -│ │ }, -│ │ { -│ │ "content": [ -│ │ { -│ │ "text": "I'll run the e2e-flue-skill. Let me first read the skill file to understand its instructions.", -│ │ "type": "text" -│ │ }, -│ │ { -│ │ "arguments": { -│ │ "path": ".agents/e2e-flue-skill.md" -│ │ }, -│ │ "id": "", -│ │ "name": "read", -│ │ "type": "toolCall" -│ │ } -│ │ ], -│ │ "role": "assistant" -│ │ }, -│ │ { -│ │ "content": [ -│ │ { -│ │ "text": "ENOENT: no such file or directory, open '/e2e/scenarios/flue-instrumentation/.agents/e2e-flue-skill.md'", -│ │ "type": "text" -│ │ } -│ │ ], -│ │ "isError": true, -│ │ "role": "toolResult", -│ │ "toolCallId": "", -│ │ "toolName": "read" -│ │ }, -│ │ { -│ │ "content": [ -│ │ { -│ │ "arguments": { -│ │ "path": ".agents" -│ │ }, -│ │ "id": "", -│ │ "name": "read", -│ │ "type": "toolCall" -│ │ } -│ │ ], -│ │ "role": "assistant" -│ │ }, -│ │ { -│ │ "content": [ -│ │ { -│ │ "text": "skills", -│ │ "type": "text" -│ │ } -│ │ ], -│ │ "isError": false, -│ │ "role": "toolResult", -│ │ "toolCallId": "", -│ │ "toolName": "read" -│ │ }, -│ │ { -│ │ "content": [ -│ │ { -│ │ "arguments": { -│ │ "path": ".agents/skills" -│ │ }, -│ │ "id": "", -│ │ "name": "read", -│ │ "type": "toolCall" -│ │ } -│ │ ], -│ │ "role": "assistant" -│ │ }, -│ │ { -│ │ "content": [ -│ │ { -│ │ "text": "e2e-flue-skill", -│ │ "type": "text" -│ │ } -│ │ ], -│ │ "isError": false, -│ │ "role": "toolResult", -│ │ "toolCallId": "", -│ │ "toolName": "read" -│ │ }, -│ │ { -│ │ "content": [ -│ │ { -│ │ "arguments": { -│ │ "path": ".agents/skills/e2e-flue-skill" -│ │ }, -│ │ "id": "", -│ │ "name": "read", -│ │ "type": "toolCall" -│ │ } -│ │ ], -│ │ "role": "assistant" -│ │ }, -│ │ { -│ │ "content": [ -│ │ { -│ │ "text": "SKILL.md", -│ │ "type": "text" -│ │ } -│ │ ], -│ │ "isError": false, -│ │ "role": "toolResult", -│ │ "toolCallId": "", -│ │ "toolName": "read" -│ │ }, -│ │ { -│ │ "content": [ -│ │ { -│ │ "arguments": { -│ │ "path": ".agents/skills/e2e-flue-skill/SKILL.md" -│ │ }, -│ │ "id": "", -│ │ "name": "read", -│ │ "type": "toolCall" -│ │ } -│ │ ], -│ │ "role": "assistant" -│ │ }, -│ │ { -│ │ "content": [ -│ │ { -│ │ "text": "---\nname: e2e-flue-skill\ndescription: Deterministic skill used by the Braintrust Flue instrumentation e2e test.\n---\n\nReturn the marker from the provided args exactly once. Output no other text.\n", -│ │ "type": "text" -│ │ } -│ │ ], -│ │ "isError": false, -│ │ "role": "toolResult", -│ │ "toolCallId": "", -│ │ "toolName": "read" -│ │ } -│ │ ] -│ │ output: { -│ │ "content": [ -│ │ { -│ │ "text": "SKILL_DONE", -│ │ "type": "text" -│ │ } -│ │ ], -│ │ "role": "assistant" -│ │ } -│ │ metadata: { -│ │ "flue.api": "anthropic-messages", -│ │ "flue.model": "claude-sonnet-4-5-20250929", -│ │ "flue.provider": "anthropic", -│ │ "flue.session": "skill", -│ │ "flue.stop_reason": "stop", -│ │ "flue.turn_purpose": "agent", -│ │ "model": "claude-sonnet-4-5-20250929", -│ │ "provider": "anthropic" -│ │ } -│ │ metrics: { -│ │ "completion_tokens": 8, -│ │ "duration_ms": 0, -│ │ "estimated_cost": 0.00129555, -│ │ "prompt_cache_creation_tokens": 135, -│ │ "prompt_cached_tokens": 2171, -│ │ "prompt_tokens": 2312, -│ │ "tokens": 2320 -│ │ } -│ ├── flue.task [task] -│ │ input: "Reply with exactly TASK_DONE and no other text." -│ │ output: "TASK_DONE" -│ │ metadata: { -│ │ "flue.session": "task", -│ │ "provider": "flue" -│ │ } -│ │ metrics: { -│ │ "duration_ms": 0 -│ │ } -│ │ └── flue.turn [llm] -│ │ input: [ -│ │ { -│ │ "content": [ -│ │ { -│ │ "text": "Reply with exactly TASK_DONE and no other text.", -│ │ "type": "text" -│ │ } -│ │ ], -│ │ "role": "user" -│ │ } -│ │ ] -│ │ output: { -│ │ "content": [ -│ │ { -│ │ "text": "TASK_DONE", -│ │ "textSignature": "{\"v\":1,\"id\":\"msg_09b32b2821e9a4d8016a184600019081929d34bb7d4c6a246f\"}", -│ │ "type": "text" -│ │ } -│ │ ], -│ │ "role": "assistant" -│ │ } -│ │ metadata: { -│ │ "flue.api": "openai-responses", -│ │ "flue.input_mode": "full", -│ │ "flue.model": "gpt-4o-mini", -│ │ "flue.provider": "openai", -│ │ "flue.session": "task:task:", -│ │ "flue.stop_reason": "stop", -│ │ "flue.turn_purpose": "agent", -│ │ "model": "gpt-4o-mini", -│ │ "provider": "openai" -│ │ } -│ │ metrics: { -│ │ "completion_tokens": 4, -│ │ "duration_ms": 0, -│ │ "estimated_cost": 0.00012299999999999998, -│ │ "prompt_cache_creation_tokens": 0, -│ │ "prompt_cached_tokens": 0, -│ │ "prompt_tokens": 804, -│ │ "tokens": 808 -│ │ } -│ └── flue.compact [task] -│ input: { -│ "estimatedTokens": 2950, -│ "reason": "manual" -│ } -│ output: { -│ "completed": true -│ } -│ metadata: { -│ "flue.operation": "compact", -│ "flue.session": "main", -│ "flue.workflow_name": "instrumentation", -│ "provider": "flue", -│ "scenario": "flue-instrumentation" -│ } -│ metrics: { -│ "duration_ms": 0 -│ } -│ └── compaction:manual [task] -│ input: { -│ "estimatedTokens": 2950, -│ "reason": "manual" -│ } -│ output: { -│ "messagesAfter": 2, -│ "messagesBefore": 8 -│ } -│ metadata: { -│ "flue.compaction_reason": "manual", -│ "flue.session": "main", -│ "provider": "flue" -│ } -│ metrics: { -│ "duration_ms": 0, -│ "messages_after": 2, -│ "messages_before": 8 -│ } -│ └── flue.turn [llm] -│ input: [ -│ { -│ "content": [ -│ { -│ "text": "\n[User]: Complete this instrumented research flow. Call exactly one tool per turn and wait for each tool result before choosing the next tool. Step 1: call lookup with query \"flue instrumentation\". Step 2: use the lookup result id as lookupId and call web_search with query \"Braintrust Flue reasoning stream instrumentation\". Step 3: use the first web_search result url and call summarize_source. After summarize_source returns, reply with exactly PROMPT_DONE and no other text.\n\n[Assistant thinking]: The user wants me to complete a research flow with exactly three steps:\n1. Call lookup with query \"flue instrumentation\"\n2. Call web_search with the lookupId from step 1 and query \"Braintrust Flue reasoning stream instrumentation\"\n3. Call summarize_source with the first URL from step 2\n4. Reply with exactly \"PROMPT_DONE\"\n\nThe user emphasizes I should call exactly one tool per turn and wait for each result. Let me start with step 1.\n\n[Assistant]: I'll complete this instrumented research flow step by step. Starting with Step 1:\n\n[Assistant tool calls]: lookup(query=\"flue instrumentation\")\n\n[Tool result]: {\"id\":\"flue-session-2026\",\"query\":\"flue instrumentation\",\"topic\":\"session instrumentation\"}\n\n[Assistant thinking]: Good, I got the lookup result with id \"flue-session-2026\". Now I need to proceed to step 2: call web_search with this lookupId and the query \"Braintrust Flue reasoning stream instrumentation\".\n\n[Assistant]: Now proceeding to Step 2:\n\n[Assistant tool calls]: web_search(lookupId=\"flue-session-2026\", query=\"Braintrust Flue reasoning stream instrumentation\")\n\n[Tool result]: {\"lookupId\":\"flue-session-2026\",\"query\":\"Braintrust Flue reasoning stream instrumentation\",\"results\":[{\"title\":\"Flue reasoning stream instrumentation\",\"url\":\"https://example.test/flue/reasoning-streams\"}]}\n\n[Assistant thinking]: Perfect! I got the web_search results with one result. The first (and only) result has the URL \"https://example.test/flue/reasoning-streams\". Now I need to proceed to step 3: call summarize_source with this URL.\n\n[Assistant]: Now proceeding to Step 3:\n\n[Assistant tool calls]: summarize_source(url=\"https://example.test/flue/reasoning-streams\")\n\n[Tool result]: {\"summary\":\"Flue emits reasoning, tool execution, and LLM turn events separately.\",\"url\":\"https://example.test/flue/reasoning-streams\"}\n\n\nThis is the PREFIX of a turn that was too large to keep. The SUFFIX (recent work) is retained.\n\nSummarize the prefix to provide context for the retained suffix:\n\n## Original Request\n[What did the user ask for in this turn?]\n\n## Early Progress\n- [Key decisions and work done in the prefix]\n\n## Context for Suffix\n- [Information needed to understand the retained recent work]\n\nBe concise. Focus on what's needed to understand the kept suffix.", -│ "type": "text" -│ } -│ ], -│ "role": "user" -│ } -│ ] -│ output: { -│ "content": [ -│ { -│ "text": "## Original Request\nThe user requested to complete an instrumented research flow consisting of three specific steps, involving calling various tools in a sequential manner.\n\n## Early Progress", -│ "textSignature": "{\"v\":1,\"id\":\"msg_01ecf68325f9e202016a184601309081a0878ef4054e950d32\"}", -│ "type": "text" -│ } -│ ], -│ "role": "assistant" -│ } -│ metadata: { -│ "flue.api": "openai-responses", -│ "flue.model": "gpt-4o-mini", -│ "flue.provider": "openai", -│ "flue.session": "main", -│ "flue.stop_reason": "stop", -│ "flue.turn_purpose": "compaction_prefix", -│ "model": "gpt-4o-mini", -│ "provider": "openai" -│ } -│ metrics: { -│ "completion_tokens": 0, -│ "duration_ms": 0, -│ "estimated_cost": 0, -│ "prompt_cache_creation_tokens": 0, -│ "prompt_cached_tokens": 0, -│ "prompt_tokens": 0, -│ "tokens": 0 -│ } -└── flue.prompt [task] - input: [ - { - "content": [ - { - "text": "Complete this instrumented research flow. Call exactly one tool per turn and wait for each tool result before choosing the next tool. Step 1: call lookup with query \"flue instrumentation\". Step 2: use the lookup result id as lookupId and call web_search with query \"Braintrust Flue reasoning stream instrumentation\". Step 3: use the first web_search result url and call summarize_source. After summarize_source returns, reply with exactly PROMPT_DONE and no other text.", - "type": "text" - } - ], - "role": "user" - } - ] - output: "PROMPT_DONE" - metadata: { - "flue.operation": "prompt", - "flue.session": "main", - "flue.workflow_name": "instrumentation", - "provider": "flue", - "scenario": "flue-instrumentation" - } - metrics: { - "duration_ms": 0 - } - ├── flue.turn [llm] - │ input: [ - │ { - │ "content": [ - │ { - │ "text": "Complete this instrumented research flow. Call exactly one tool per turn and wait for each tool result before choosing the next tool. Step 1: call lookup with query \"flue instrumentation\". Step 2: use the lookup result id as lookupId and call web_search with query \"Braintrust Flue reasoning stream instrumentation\". Step 3: use the first web_search result url and call summarize_source. After summarize_source returns, reply with exactly PROMPT_DONE and no other text.", - │ "type": "text" - │ } - │ ], - │ "role": "user" - │ } - │ ] - │ output: { - │ "content": [ - │ { - │ "thinking": "The user wants me to complete a research flow with exactly three steps:\n1. Call lookup with query \"flue instrumentation\"\n2. Call web_search with the lookupId from step 1 and query \"Braintrust Flue reasoning stream instrumentation\"\n3. Call summarize_source with the first URL from step 2\n4. Reply with exactly \"PROMPT_DONE\"\n\nThe user emphasizes I should call exactly one tool per turn and wait for each result. Let me start with step 1.", - │ "thinkingSignature": "EoQFCm4IDhgCKkD+AoKMhpXNPUJ5eANwM28uUCJPCE+AXh0ACw7aXniX+YN04zdJl3hlHMbtAuKhhczLiEeOJtu4KtVpea5u5NFdMhpjbGF1ZGUtc29ubmV0LTQtNS0yMDI1MDkyOTgAQgh0aGlua2luZxIMIOTWCNEtsiI9vF4SGgwPXC861uZx8lpOziEiMLsCukGIluD0U44degzYnHFc+BwXNIEB+yatSHlZE+a5smr9crZcft3OLmsIdXuaFSrDAyQxWd9oqfJdnvza9eWIaOCeNkVCjrTc/wotBwh5Xp8m9k/WKt7P4zzUJBuLQ1f6ZnsdWkL8OiL/2E7Up55z2+UuVWsg1Y90/x27YRFdQjv3RnztYp4bZ5oIHAlTJoCzWiBYEh/Z8c2O+wy6MHo1cp0T57OroB/4HAqRZxrjTOXpKYLmYKXAY5tU8DTd3A0/FfEhg3oPSbOhX+112szFCpm3BRNJYooUUhwSu1hO4H/6r7d/Yp9XIM4tBfzuDx83CC/YetdOFgnYkdliw0xaUGaj4o37rWIRctWHAMdlUc4NDA0Ljc1uirHDXkN8EJwWSoIjPSBTA/p/wgLrvRV5Yy9Z0dMoWOpgYF6t1H/tNAyY8ml1CA7IWyd3jnfTZ1/G3Y0xFDwJDFlb4/QKc67w36SSCa+X9mMoTxD/DYN249lsII+XX3lQ7F1zJ07THOtCyRirCaRhmE/kGwl5tiB1QHrWa9bNHxQxf9i92435JGKN053Fe14AR70MtBSzIU2/zbgvhB1vF5lZt2i46hNxfo4lXR495gEwdwC6+8HJnlEJAJkdeHfZKj+RiwXoiYKPKsSbHi83X8Hh32IifuglNxdlxVQYAQ==", - │ "type": "thinking" - │ }, - │ { - │ "text": "I'll complete this instrumented research flow step by step. Starting with Step 1:", - │ "type": "text" - │ }, - │ { - │ "arguments": { - │ "query": "flue instrumentation" - │ }, - │ "id": "", - │ "name": "lookup", - │ "type": "toolCall" - │ } - │ ], - │ "role": "assistant" - │ } - │ metadata: { - │ "flue.api": "anthropic-messages", - │ "flue.input_mode": "full", - │ "flue.model": "claude-sonnet-4-5-20250929", - │ "flue.provider": "anthropic", - │ "flue.session": "main", - │ "flue.stop_reason": "toolUse", - │ "flue.turn_purpose": "agent", - │ "model": "claude-sonnet-4-5-20250929", - │ "provider": "anthropic" - │ } - │ metrics: { - │ "completion_tokens": 196, - │ "duration_ms": 0, - │ "estimated_cost": 0.0113025, - │ "prompt_cache_creation_tokens": 2222, - │ "prompt_cached_tokens": 0, - │ "prompt_tokens": 2232, - │ "tokens": 2428 - │ } - ├── tool:lookup [tool] - │ input: { - │ "query": "flue instrumentation" - │ } - │ output: { - │ "content": [ - │ { - │ "text": "{\"id\":\"flue-session-2026\",\"query\":\"flue instrumentation\",\"topic\":\"session instrumentation\"}", - │ "type": "text" - │ } - │ ], - │ "details": { - │ "customTool": "lookup" - │ } - │ } - │ metadata: { - │ "flue.session": "main", - │ "flue.tool_name": "lookup", - │ "provider": "flue" - │ } - │ metrics: { - │ "duration_ms": 0 - │ } - ├── flue.turn [llm] - │ input: [ - │ { - │ "content": [ - │ { - │ "thinking": "The user wants me to complete a research flow with exactly three steps:\n1. Call lookup with query \"flue instrumentation\"\n2. Call web_search with the lookupId from step 1 and query \"Braintrust Flue reasoning stream instrumentation\"\n3. Call summarize_source with the first URL from step 2\n4. Reply with exactly \"PROMPT_DONE\"\n\nThe user emphasizes I should call exactly one tool per turn and wait for each result. Let me start with step 1.", - │ "thinkingSignature": "EoQFCm4IDhgCKkD+AoKMhpXNPUJ5eANwM28uUCJPCE+AXh0ACw7aXniX+YN04zdJl3hlHMbtAuKhhczLiEeOJtu4KtVpea5u5NFdMhpjbGF1ZGUtc29ubmV0LTQtNS0yMDI1MDkyOTgAQgh0aGlua2luZxIMIOTWCNEtsiI9vF4SGgwPXC861uZx8lpOziEiMLsCukGIluD0U44degzYnHFc+BwXNIEB+yatSHlZE+a5smr9crZcft3OLmsIdXuaFSrDAyQxWd9oqfJdnvza9eWIaOCeNkVCjrTc/wotBwh5Xp8m9k/WKt7P4zzUJBuLQ1f6ZnsdWkL8OiL/2E7Up55z2+UuVWsg1Y90/x27YRFdQjv3RnztYp4bZ5oIHAlTJoCzWiBYEh/Z8c2O+wy6MHo1cp0T57OroB/4HAqRZxrjTOXpKYLmYKXAY5tU8DTd3A0/FfEhg3oPSbOhX+112szFCpm3BRNJYooUUhwSu1hO4H/6r7d/Yp9XIM4tBfzuDx83CC/YetdOFgnYkdliw0xaUGaj4o37rWIRctWHAMdlUc4NDA0Ljc1uirHDXkN8EJwWSoIjPSBTA/p/wgLrvRV5Yy9Z0dMoWOpgYF6t1H/tNAyY8ml1CA7IWyd3jnfTZ1/G3Y0xFDwJDFlb4/QKc67w36SSCa+X9mMoTxD/DYN249lsII+XX3lQ7F1zJ07THOtCyRirCaRhmE/kGwl5tiB1QHrWa9bNHxQxf9i92435JGKN053Fe14AR70MtBSzIU2/zbgvhB1vF5lZt2i46hNxfo4lXR495gEwdwC6+8HJnlEJAJkdeHfZKj+RiwXoiYKPKsSbHi83X8Hh32IifuglNxdlxVQYAQ==", - │ "type": "thinking" - │ }, - │ { - │ "text": "I'll complete this instrumented research flow step by step. Starting with Step 1:", - │ "type": "text" - │ }, - │ { - │ "arguments": { - │ "query": "flue instrumentation" - │ }, - │ "id": "", - │ "name": "lookup", - │ "type": "toolCall" - │ } - │ ], - │ "role": "assistant" - │ }, - │ { - │ "content": [ - │ { - │ "text": "{\"id\":\"flue-session-2026\",\"query\":\"flue instrumentation\",\"topic\":\"session instrumentation\"}", - │ "type": "text" - │ } - │ ], - │ "isError": false, - │ "role": "toolResult", - │ "toolCallId": "", - │ "toolName": "lookup" - │ } - │ ] - │ output: { - │ "content": [ - │ { - │ "thinking": "Good, I got the lookup result with id \"flue-session-2026\". Now I need to proceed to step 2: call web_search with this lookupId and the query \"Braintrust Flue reasoning stream instrumentation\".", - │ "thinkingSignature": "EpEDCm4IDhgCKkAMahve1kB1j4njp1ZSzO8eCK4bb5Ex7MB6tjdoOXfuUDRfkBqvIGdwVly8GiA8GGYC7Y/RVfREXF6A6OXHTwFyMhpjbGF1ZGUtc29ubmV0LTQtNS0yMDI1MDkyOTgAQgh0aGlua2luZxIMLw+fsa4yo7a7ZSoIGgyaTdvH020n2dQj9bUiMB4OokY29w+uZb4rqDnKDbLXUqUj6U0f1QCGQF2ZBYDDfDJyM5Jv1xY3rQiUUjkhBirQAS4XivjvMnWI5zHBmpI/u/pq4aGdrdb3vUsJ/ne6R1d80JA/INGF8vLkQcZ5xxEK0jU4yRE+ldc2uzavmtoxIKAFD6bqKks1Qv6u+TIZtuW7tUB/7jFxDw93KmJNruMIa8c1J6pPUyBy3xE7QwgmU0kDfIfdnljxLoEBiMwtnxjCPj9lT7HHz4FkrhbnQTV4MvAQohCLjaSbOTd6LdLxwtJPm9mc26ID4eYN1gCCzyAxGXhqLHlzc23PafvQjsZliWnLhaiNMnG0YEo78XkuJQsYAQ==", - │ "type": "thinking" - │ }, - │ { - │ "text": "Now proceeding to Step 2:", - │ "type": "text" - │ }, - │ { - │ "arguments": { - │ "lookupId": "flue-session-2026", - │ "query": "Braintrust Flue reasoning stream instrumentation" - │ }, - │ "id": "", - │ "name": "web_search", - │ "type": "toolCall" - │ } - │ ], - │ "role": "assistant" - │ } - │ metadata: { - │ "flue.api": "anthropic-messages", - │ "flue.input_message_offset": 1, - │ "flue.input_mode": "delta", - │ "flue.model": "claude-sonnet-4-5-20250929", - │ "flue.provider": "anthropic", - │ "flue.session": "main", - │ "flue.stop_reason": "toolUse", - │ "flue.turn_purpose": "agent", - │ "model": "claude-sonnet-4-5-20250929", - │ "provider": "anthropic" - │ } - │ metrics: { - │ "completion_tokens": 158, - │ "duration_ms": 0, - │ "estimated_cost": 0.0039606, - │ "prompt_cache_creation_tokens": 236, - │ "prompt_cached_tokens": 2222, - │ "prompt_tokens": 2471, - │ "tokens": 2629 - │ } - ├── tool:web_search [tool] - │ input: { - │ "lookupId": "flue-session-2026", - │ "query": "Braintrust Flue reasoning stream instrumentation" - │ } - │ output: { - │ "content": [ - │ { - │ "text": "{\"lookupId\":\"flue-session-2026\",\"query\":\"Braintrust Flue reasoning stream instrumentation\",\"results\":[{\"title\":\"Flue reasoning stream instrumentation\",\"url\":\"https://example.test/flue/reasoning-streams\"}]}", - │ "type": "text" - │ } - │ ], - │ "details": { - │ "customTool": "web_search" - │ } - │ } - │ metadata: { - │ "flue.session": "main", - │ "flue.tool_name": "web_search", - │ "provider": "flue" - │ } - │ metrics: { - │ "duration_ms": 0 - │ } - ├── flue.turn [llm] - │ input: [ - │ { - │ "content": [ - │ { - │ "thinking": "Good, I got the lookup result with id \"flue-session-2026\". Now I need to proceed to step 2: call web_search with this lookupId and the query \"Braintrust Flue reasoning stream instrumentation\".", - │ "thinkingSignature": "EpEDCm4IDhgCKkAMahve1kB1j4njp1ZSzO8eCK4bb5Ex7MB6tjdoOXfuUDRfkBqvIGdwVly8GiA8GGYC7Y/RVfREXF6A6OXHTwFyMhpjbGF1ZGUtc29ubmV0LTQtNS0yMDI1MDkyOTgAQgh0aGlua2luZxIMLw+fsa4yo7a7ZSoIGgyaTdvH020n2dQj9bUiMB4OokY29w+uZb4rqDnKDbLXUqUj6U0f1QCGQF2ZBYDDfDJyM5Jv1xY3rQiUUjkhBirQAS4XivjvMnWI5zHBmpI/u/pq4aGdrdb3vUsJ/ne6R1d80JA/INGF8vLkQcZ5xxEK0jU4yRE+ldc2uzavmtoxIKAFD6bqKks1Qv6u+TIZtuW7tUB/7jFxDw93KmJNruMIa8c1J6pPUyBy3xE7QwgmU0kDfIfdnljxLoEBiMwtnxjCPj9lT7HHz4FkrhbnQTV4MvAQohCLjaSbOTd6LdLxwtJPm9mc26ID4eYN1gCCzyAxGXhqLHlzc23PafvQjsZliWnLhaiNMnG0YEo78XkuJQsYAQ==", - │ "type": "thinking" - │ }, - │ { - │ "text": "Now proceeding to Step 2:", - │ "type": "text" - │ }, - │ { - │ "arguments": { - │ "lookupId": "flue-session-2026", - │ "query": "Braintrust Flue reasoning stream instrumentation" - │ }, - │ "id": "", - │ "name": "web_search", - │ "type": "toolCall" - │ } - │ ], - │ "role": "assistant" - │ }, - │ { - │ "content": [ - │ { - │ "text": "{\"lookupId\":\"flue-session-2026\",\"query\":\"Braintrust Flue reasoning stream instrumentation\",\"results\":[{\"title\":\"Flue reasoning stream instrumentation\",\"url\":\"https://example.test/flue/reasoning-streams\"}]}", - │ "type": "text" - │ } - │ ], - │ "isError": false, - │ "role": "toolResult", - │ "toolCallId": "", - │ "toolName": "web_search" - │ } - │ ] - │ output: { - │ "content": [ - │ { - │ "thinking": "Perfect! I got the web_search results with one result. The first (and only) result has the URL \"https://example.test/flue/reasoning-streams\". Now I need to proceed to step 3: call summarize_source with this URL.", - │ "thinkingSignature": "EqQDCm4IDhgCKkAXv7EDAhWMcrBz11bUd5qxlzyy03CAGQfRgLHDE0uKzLATh1o2sedd8GxLwwQqV0CmLRfNxsdK/ZDp3Lks9iysMhpjbGF1ZGUtc29ubmV0LTQtNS0yMDI1MDkyOTgAQgh0aGlua2luZxIMXDMniOy5Dy0KI8x7GgzgHs7BYJMs6/N0ur8iMPe0SwnHhP39OjEu7P8ASwK9ffwa3VP67QOlPzOstRRdEZNd0mq+BVjpmBXB+QcAzCrjAeAh/UO/d7SfFmXjqwA3//thoL/TTlGa6W+fdOCFRikn65JWT4ZdVFetfqD+9/QCP/nXEwMsLnTBAxC4EGryrgs2YT+OWwmEJ7rHKFeDs5djhwSb2J2ZJO1kRvD9s2vCCDkImygYBSIVgx1Y0CZJHhzn2Vf39v0Fbth2wDZa+OqxivV9NmDZEjmgcM+tTRx1CCnaww8LZEJQ68v/dUJy7hhD4MrNcQ62evJYPfT3+4EKKWJ/7HI7KXz91//+YAkph63XstbJMFJ3AldAFiq3ecauXt5r42N5gmCmwZX+sI48ENLGGAE=", - │ "type": "thinking" - │ }, - │ { - │ "text": "Now proceeding to Step 3:", - │ "type": "text" - │ }, - │ { - │ "arguments": { - │ "url": "https://example.test/flue/reasoning-streams" - │ }, - │ "id": "", - │ "name": "summarize_source", - │ "type": "toolCall" - │ } - │ ], - │ "role": "assistant" - │ } - │ metadata: { - │ "flue.api": "anthropic-messages", - │ "flue.input_message_offset": 3, - │ "flue.input_mode": "delta", - │ "flue.model": "claude-sonnet-4-5-20250929", - │ "flue.provider": "anthropic", - │ "flue.session": "main", - │ "flue.stop_reason": "toolUse", - │ "flue.turn_purpose": "agent", - │ "model": "claude-sonnet-4-5-20250929", - │ "provider": "anthropic" - │ } - │ metrics: { - │ "completion_tokens": 138, - │ "duration_ms": 0, - │ "estimated_cost": 0.0037239000000000005, - │ "prompt_cache_creation_tokens": 234, - │ "prompt_cached_tokens": 2458, - │ "prompt_tokens": 2705, - │ "tokens": 2843 - │ } - ├── tool:summarize_source [tool] - │ input: { - │ "url": "https://example.test/flue/reasoning-streams" - │ } - │ output: { - │ "content": [ - │ { - │ "text": "{\"summary\":\"Flue emits reasoning, tool execution, and LLM turn events separately.\",\"url\":\"https://example.test/flue/reasoning-streams\"}", - │ "type": "text" - │ } - │ ], - │ "details": { - │ "customTool": "summarize_source" - │ } - │ } - │ metadata: { - │ "flue.session": "main", - │ "flue.tool_name": "summarize_source", - │ "provider": "flue" - │ } - │ metrics: { - │ "duration_ms": 0 - │ } - └── flue.turn [llm] - input: [ - { - "content": [ - { - "thinking": "Perfect! I got the web_search results with one result. The first (and only) result has the URL \"https://example.test/flue/reasoning-streams\". Now I need to proceed to step 3: call summarize_source with this URL.", - "thinkingSignature": "EqQDCm4IDhgCKkAXv7EDAhWMcrBz11bUd5qxlzyy03CAGQfRgLHDE0uKzLATh1o2sedd8GxLwwQqV0CmLRfNxsdK/ZDp3Lks9iysMhpjbGF1ZGUtc29ubmV0LTQtNS0yMDI1MDkyOTgAQgh0aGlua2luZxIMXDMniOy5Dy0KI8x7GgzgHs7BYJMs6/N0ur8iMPe0SwnHhP39OjEu7P8ASwK9ffwa3VP67QOlPzOstRRdEZNd0mq+BVjpmBXB+QcAzCrjAeAh/UO/d7SfFmXjqwA3//thoL/TTlGa6W+fdOCFRikn65JWT4ZdVFetfqD+9/QCP/nXEwMsLnTBAxC4EGryrgs2YT+OWwmEJ7rHKFeDs5djhwSb2J2ZJO1kRvD9s2vCCDkImygYBSIVgx1Y0CZJHhzn2Vf39v0Fbth2wDZa+OqxivV9NmDZEjmgcM+tTRx1CCnaww8LZEJQ68v/dUJy7hhD4MrNcQ62evJYPfT3+4EKKWJ/7HI7KXz91//+YAkph63XstbJMFJ3AldAFiq3ecauXt5r42N5gmCmwZX+sI48ENLGGAE=", - "type": "thinking" - }, - { - "text": "Now proceeding to Step 3:", - "type": "text" - }, - { - "arguments": { - "url": "https://example.test/flue/reasoning-streams" - }, - "id": "", - "name": "summarize_source", - "type": "toolCall" - } - ], - "role": "assistant" - }, - { - "content": [ - { - "text": "{\"summary\":\"Flue emits reasoning, tool execution, and LLM turn events separately.\",\"url\":\"https://example.test/flue/reasoning-streams\"}", - "type": "text" - } - ], - "isError": false, - "role": "toolResult", - "toolCallId": "", - "toolName": "summarize_source" - } - ] - output: { - "content": [ - { - "thinking": "Perfect! The summarize_source call has completed successfully. According to the instructions, I should now reply with exactly \"PROMPT_DONE\" and no other text.", - "thinkingSignature": "Eu8CCm4IDhgCKkAn2QOIIlYfvcs6ynsmhCslHDtBsrTA4CfmimK7zyz6+k6dZldV2JBtEBET41It0gFcGEKC0n0mAhaYVuLutpDDMhpjbGF1ZGUtc29ubmV0LTQtNS0yMDI1MDkyOTgAQgh0aGlua2luZxIM1xfLptxSF8rKzou8GgwRLuVmfiBYOT4CZIsiMAYz8TpvchMAWodP+T9uZ+dJ47MVdNv+p86nZXmT5d5EWlCroiAOFHHzJH2o0UjJQSquAX6wu+jIC8/DXwurGIIf2ToldPuJWF5HGj8kHMnwh777XS1NUFl1irM24rvP7kTAv3Dp8YEwb8t3g1EsicY7j+C5DzgwxVsPYCVOCgdMqQjtsqpL7AX5KQ9+hR1gK1DQjshv4Gze3hw/+C+9iptd3RlDIuwO0cm8NY+sVqeWVh3TtQujQlVt1rW/HfWjclrtT0LZSGwc6RypmfMMQi6lncIk/EPgy0XbS1plKW0tLBgB", - "type": "thinking" - }, - { - "text": "PROMPT_DONE", - "type": "text" - } - ], - "role": "assistant" - } - metadata: { - "flue.api": "anthropic-messages", - "flue.input_message_offset": 5, - "flue.input_mode": "delta", - "flue.model": "claude-sonnet-4-5-20250929", - "flue.provider": "anthropic", - "flue.session": "main", - "flue.stop_reason": "stop", - "flue.turn_purpose": "agent", - "model": "claude-sonnet-4-5-20250929", - "provider": "anthropic" - } - metrics: { - "completion_tokens": 51, - "duration_ms": 0, - "estimated_cost": 0.0023391, - "prompt_cache_creation_tokens": 194, - "prompt_cached_tokens": 2692, - "prompt_tokens": 2899, - "tokens": 2950 - } diff --git a/e2e/scenarios/flue-instrumentation/__snapshots__/flue-v0-8-0-explicit.span-tree.json b/e2e/scenarios/flue-instrumentation/__snapshots__/flue-v0-8-0-explicit.span-tree.json deleted file mode 100644 index f06e08da7..000000000 --- a/e2e/scenarios/flue-instrumentation/__snapshots__/flue-v0-8-0-explicit.span-tree.json +++ /dev/null @@ -1,1448 +0,0 @@ -{ - "span_tree": [ - { - "name": "workflow:instrumentation", - "type": "task", - "children": [ - { - "name": "flue.skill", - "type": "task", - "children": [ - { - "name": "flue.turn", - "type": "llm", - "children": [], - "input": [ - { - "content": [ - { - "text": "Run the skill named \"e2e-flue-skill\".\n\nArguments:\n{\n \"marker\": \"SKILL_DONE\"\n}", - "type": "text" - } - ], - "role": "user" - } - ], - "output": { - "content": [ - { - "text": "I'll run the e2e-flue-skill. Let me first read the skill file to understand its instructions.", - "type": "text" - }, - { - "arguments": { - "path": ".agents/e2e-flue-skill.md" - }, - "id": "", - "name": "read", - "type": "toolCall" - } - ], - "role": "assistant" - }, - "metadata": { - "flue.api": "anthropic-messages", - "flue.model": "claude-sonnet-4-5-20250929", - "flue.provider": "anthropic", - "flue.session": "skill", - "flue.stop_reason": "toolUse", - "flue.turn_purpose": "agent", - "model": "claude-sonnet-4-5-20250929", - "provider": "anthropic" - }, - "metrics": { - "completion_tokens": 89, - "duration_ms": 0, - "estimated_cost": 0.0041862, - "prompt_cache_creation_tokens": 670, - "prompt_cached_tokens": 1099, - "prompt_tokens": 1772, - "tokens": 1861 - } - }, - { - "name": "tool:read", - "type": "tool", - "children": [], - "input": { - "path": ".agents/e2e-flue-skill.md" - }, - "output": { - "content": [ - { - "text": "ENOENT: no such file or directory, open '/e2e/scenarios/flue-instrumentation/.agents/e2e-flue-skill.md'", - "type": "text" - } - ], - "details": {} - }, - "metadata": { - "flue.session": "skill", - "flue.tool_name": "read", - "provider": "flue" - }, - "metrics": { - "duration_ms": 0 - }, - "error": "{\"content\":[{\"type\":\"text\",\"text\":\"ENOENT: no such file or directory, open '/e2e/scenarios/flue-instrumentation/.agents/e2e-flue-skill.md'\"}],\"details\":{}}" - }, - { - "name": "flue.turn", - "type": "llm", - "children": [], - "input": [ - { - "content": [ - { - "text": "Run the skill named \"e2e-flue-skill\".\n\nArguments:\n{\n \"marker\": \"SKILL_DONE\"\n}", - "type": "text" - } - ], - "role": "user" - }, - { - "content": [ - { - "text": "I'll run the e2e-flue-skill. Let me first read the skill file to understand its instructions.", - "type": "text" - }, - { - "arguments": { - "path": ".agents/e2e-flue-skill.md" - }, - "id": "", - "name": "read", - "type": "toolCall" - } - ], - "role": "assistant" - }, - { - "content": [ - { - "text": "ENOENT: no such file or directory, open '/e2e/scenarios/flue-instrumentation/.agents/e2e-flue-skill.md'", - "type": "text" - } - ], - "isError": true, - "role": "toolResult", - "toolCallId": "", - "toolName": "read" - } - ], - "output": { - "content": [ - { - "arguments": { - "path": ".agents" - }, - "id": "", - "name": "read", - "type": "toolCall" - } - ], - "role": "assistant" - }, - "metadata": { - "flue.api": "anthropic-messages", - "flue.model": "claude-sonnet-4-5-20250929", - "flue.provider": "anthropic", - "flue.session": "skill", - "flue.stop_reason": "toolUse", - "flue.turn_purpose": "agent", - "model": "claude-sonnet-4-5-20250929", - "provider": "anthropic" - }, - "metrics": { - "completion_tokens": 52, - "duration_ms": 0, - "estimated_cost": 0.00201795, - "prompt_cache_creation_tokens": 183, - "prompt_cached_tokens": 1769, - "prompt_tokens": 1959, - "tokens": 2011 - } - }, - { - "name": "tool:read", - "type": "tool", - "children": [], - "input": { - "path": ".agents" - }, - "output": { - "content": [ - { - "text": "skills", - "type": "text" - } - ], - "details": { - "entries": 1, - "isDirectory": true, - "path": ".agents" - } - }, - "metadata": { - "flue.session": "skill", - "flue.tool_name": "read", - "provider": "flue" - }, - "metrics": { - "duration_ms": 0 - } - }, - { - "name": "flue.turn", - "type": "llm", - "children": [], - "input": [ - { - "content": [ - { - "text": "Run the skill named \"e2e-flue-skill\".\n\nArguments:\n{\n \"marker\": \"SKILL_DONE\"\n}", - "type": "text" - } - ], - "role": "user" - }, - { - "content": [ - { - "text": "I'll run the e2e-flue-skill. Let me first read the skill file to understand its instructions.", - "type": "text" - }, - { - "arguments": { - "path": ".agents/e2e-flue-skill.md" - }, - "id": "", - "name": "read", - "type": "toolCall" - } - ], - "role": "assistant" - }, - { - "content": [ - { - "text": "ENOENT: no such file or directory, open '/e2e/scenarios/flue-instrumentation/.agents/e2e-flue-skill.md'", - "type": "text" - } - ], - "isError": true, - "role": "toolResult", - "toolCallId": "", - "toolName": "read" - }, - { - "content": [ - { - "arguments": { - "path": ".agents" - }, - "id": "", - "name": "read", - "type": "toolCall" - } - ], - "role": "assistant" - }, - { - "content": [ - { - "text": "skills", - "type": "text" - } - ], - "isError": false, - "role": "toolResult", - "toolCallId": "", - "toolName": "read" - } - ], - "output": { - "content": [ - { - "arguments": { - "path": ".agents/skills" - }, - "id": "", - "name": "read", - "type": "toolCall" - } - ], - "role": "assistant" - }, - "metadata": { - "flue.api": "anthropic-messages", - "flue.model": "claude-sonnet-4-5-20250929", - "flue.provider": "anthropic", - "flue.session": "skill", - "flue.stop_reason": "toolUse", - "flue.turn_purpose": "agent", - "model": "claude-sonnet-4-5-20250929", - "provider": "anthropic" - }, - "metrics": { - "completion_tokens": 54, - "duration_ms": 0, - "estimated_cost": 0.0016611, - "prompt_cache_creation_tokens": 66, - "prompt_cached_tokens": 1952, - "prompt_tokens": 2024, - "tokens": 2078 - } - }, - { - "name": "tool:read", - "type": "tool", - "children": [], - "input": { - "path": ".agents/skills" - }, - "output": { - "content": [ - { - "text": "e2e-flue-skill", - "type": "text" - } - ], - "details": { - "entries": 1, - "isDirectory": true, - "path": ".agents/skills" - } - }, - "metadata": { - "flue.session": "skill", - "flue.tool_name": "read", - "provider": "flue" - }, - "metrics": { - "duration_ms": 0 - } - }, - { - "name": "flue.turn", - "type": "llm", - "children": [], - "input": [ - { - "content": [ - { - "text": "Run the skill named \"e2e-flue-skill\".\n\nArguments:\n{\n \"marker\": \"SKILL_DONE\"\n}", - "type": "text" - } - ], - "role": "user" - }, - { - "content": [ - { - "text": "I'll run the e2e-flue-skill. Let me first read the skill file to understand its instructions.", - "type": "text" - }, - { - "arguments": { - "path": ".agents/e2e-flue-skill.md" - }, - "id": "", - "name": "read", - "type": "toolCall" - } - ], - "role": "assistant" - }, - { - "content": [ - { - "text": "ENOENT: no such file or directory, open '/e2e/scenarios/flue-instrumentation/.agents/e2e-flue-skill.md'", - "type": "text" - } - ], - "isError": true, - "role": "toolResult", - "toolCallId": "", - "toolName": "read" - }, - { - "content": [ - { - "arguments": { - "path": ".agents" - }, - "id": "", - "name": "read", - "type": "toolCall" - } - ], - "role": "assistant" - }, - { - "content": [ - { - "text": "skills", - "type": "text" - } - ], - "isError": false, - "role": "toolResult", - "toolCallId": "", - "toolName": "read" - }, - { - "content": [ - { - "arguments": { - "path": ".agents/skills" - }, - "id": "", - "name": "read", - "type": "toolCall" - } - ], - "role": "assistant" - }, - { - "content": [ - { - "text": "e2e-flue-skill", - "type": "text" - } - ], - "isError": false, - "role": "toolResult", - "toolCallId": "", - "toolName": "read" - } - ], - "output": { - "content": [ - { - "arguments": { - "path": ".agents/skills/e2e-flue-skill" - }, - "id": "", - "name": "read", - "type": "toolCall" - } - ], - "role": "assistant" - }, - "metadata": { - "flue.api": "anthropic-messages", - "flue.model": "claude-sonnet-4-5-20250929", - "flue.provider": "anthropic", - "flue.session": "skill", - "flue.stop_reason": "toolUse", - "flue.turn_purpose": "agent", - "model": "claude-sonnet-4-5-20250929", - "provider": "anthropic" - }, - "metrics": { - "completion_tokens": 63, - "duration_ms": 0, - "estimated_cost": 0.0018459, - "prompt_cache_creation_tokens": 74, - "prompt_cached_tokens": 2018, - "prompt_tokens": 2098, - "tokens": 2161 - } - }, - { - "name": "tool:read", - "type": "tool", - "children": [], - "input": { - "path": ".agents/skills/e2e-flue-skill" - }, - "output": { - "content": [ - { - "text": "SKILL.md", - "type": "text" - } - ], - "details": { - "entries": 1, - "isDirectory": true, - "path": ".agents/skills/e2e-flue-skill" - } - }, - "metadata": { - "flue.session": "skill", - "flue.tool_name": "read", - "provider": "flue" - }, - "metrics": { - "duration_ms": 0 - } - }, - { - "name": "flue.turn", - "type": "llm", - "children": [], - "input": [ - { - "content": [ - { - "text": "Run the skill named \"e2e-flue-skill\".\n\nArguments:\n{\n \"marker\": \"SKILL_DONE\"\n}", - "type": "text" - } - ], - "role": "user" - }, - { - "content": [ - { - "text": "I'll run the e2e-flue-skill. Let me first read the skill file to understand its instructions.", - "type": "text" - }, - { - "arguments": { - "path": ".agents/e2e-flue-skill.md" - }, - "id": "", - "name": "read", - "type": "toolCall" - } - ], - "role": "assistant" - }, - { - "content": [ - { - "text": "ENOENT: no such file or directory, open '/e2e/scenarios/flue-instrumentation/.agents/e2e-flue-skill.md'", - "type": "text" - } - ], - "isError": true, - "role": "toolResult", - "toolCallId": "", - "toolName": "read" - }, - { - "content": [ - { - "arguments": { - "path": ".agents" - }, - "id": "", - "name": "read", - "type": "toolCall" - } - ], - "role": "assistant" - }, - { - "content": [ - { - "text": "skills", - "type": "text" - } - ], - "isError": false, - "role": "toolResult", - "toolCallId": "", - "toolName": "read" - }, - { - "content": [ - { - "arguments": { - "path": ".agents/skills" - }, - "id": "", - "name": "read", - "type": "toolCall" - } - ], - "role": "assistant" - }, - { - "content": [ - { - "text": "e2e-flue-skill", - "type": "text" - } - ], - "isError": false, - "role": "toolResult", - "toolCallId": "", - "toolName": "read" - }, - { - "content": [ - { - "arguments": { - "path": ".agents/skills/e2e-flue-skill" - }, - "id": "", - "name": "read", - "type": "toolCall" - } - ], - "role": "assistant" - }, - { - "content": [ - { - "text": "SKILL.md", - "type": "text" - } - ], - "isError": false, - "role": "toolResult", - "toolCallId": "", - "toolName": "read" - } - ], - "output": { - "content": [ - { - "arguments": { - "path": ".agents/skills/e2e-flue-skill/SKILL.md" - }, - "id": "", - "name": "read", - "type": "toolCall" - } - ], - "role": "assistant" - }, - "metadata": { - "flue.api": "anthropic-messages", - "flue.model": "claude-sonnet-4-5-20250929", - "flue.provider": "anthropic", - "flue.session": "skill", - "flue.stop_reason": "toolUse", - "flue.turn_purpose": "agent", - "model": "claude-sonnet-4-5-20250929", - "provider": "anthropic" - }, - "metrics": { - "completion_tokens": 68, - "duration_ms": 0, - "estimated_cost": 0.0019618500000000002, - "prompt_cache_creation_tokens": 79, - "prompt_cached_tokens": 2092, - "prompt_tokens": 2177, - "tokens": 2245 - } - }, - { - "name": "tool:read", - "type": "tool", - "children": [], - "input": { - "path": ".agents/skills/e2e-flue-skill/SKILL.md" - }, - "output": { - "content": [ - { - "text": "---\nname: e2e-flue-skill\ndescription: Deterministic skill used by the Braintrust Flue instrumentation e2e test.\n---\n\nReturn the marker from the provided args exactly once. Output no other text.\n", - "type": "text" - } - ], - "details": { - "lines": 7, - "path": ".agents/skills/e2e-flue-skill/SKILL.md" - } - }, - "metadata": { - "flue.session": "skill", - "flue.tool_name": "read", - "provider": "flue" - }, - "metrics": { - "duration_ms": 0 - } - }, - { - "name": "flue.turn", - "type": "llm", - "children": [], - "input": [ - { - "content": [ - { - "text": "Run the skill named \"e2e-flue-skill\".\n\nArguments:\n{\n \"marker\": \"SKILL_DONE\"\n}", - "type": "text" - } - ], - "role": "user" - }, - { - "content": [ - { - "text": "I'll run the e2e-flue-skill. Let me first read the skill file to understand its instructions.", - "type": "text" - }, - { - "arguments": { - "path": ".agents/e2e-flue-skill.md" - }, - "id": "", - "name": "read", - "type": "toolCall" - } - ], - "role": "assistant" - }, - { - "content": [ - { - "text": "ENOENT: no such file or directory, open '/e2e/scenarios/flue-instrumentation/.agents/e2e-flue-skill.md'", - "type": "text" - } - ], - "isError": true, - "role": "toolResult", - "toolCallId": "", - "toolName": "read" - }, - { - "content": [ - { - "arguments": { - "path": ".agents" - }, - "id": "", - "name": "read", - "type": "toolCall" - } - ], - "role": "assistant" - }, - { - "content": [ - { - "text": "skills", - "type": "text" - } - ], - "isError": false, - "role": "toolResult", - "toolCallId": "", - "toolName": "read" - }, - { - "content": [ - { - "arguments": { - "path": ".agents/skills" - }, - "id": "", - "name": "read", - "type": "toolCall" - } - ], - "role": "assistant" - }, - { - "content": [ - { - "text": "e2e-flue-skill", - "type": "text" - } - ], - "isError": false, - "role": "toolResult", - "toolCallId": "", - "toolName": "read" - }, - { - "content": [ - { - "arguments": { - "path": ".agents/skills/e2e-flue-skill" - }, - "id": "", - "name": "read", - "type": "toolCall" - } - ], - "role": "assistant" - }, - { - "content": [ - { - "text": "SKILL.md", - "type": "text" - } - ], - "isError": false, - "role": "toolResult", - "toolCallId": "", - "toolName": "read" - }, - { - "content": [ - { - "arguments": { - "path": ".agents/skills/e2e-flue-skill/SKILL.md" - }, - "id": "", - "name": "read", - "type": "toolCall" - } - ], - "role": "assistant" - }, - { - "content": [ - { - "text": "---\nname: e2e-flue-skill\ndescription: Deterministic skill used by the Braintrust Flue instrumentation e2e test.\n---\n\nReturn the marker from the provided args exactly once. Output no other text.\n", - "type": "text" - } - ], - "isError": false, - "role": "toolResult", - "toolCallId": "", - "toolName": "read" - } - ], - "output": { - "content": [ - { - "text": "SKILL_DONE", - "type": "text" - } - ], - "role": "assistant" - }, - "metadata": { - "flue.api": "anthropic-messages", - "flue.model": "claude-sonnet-4-5-20250929", - "flue.provider": "anthropic", - "flue.session": "skill", - "flue.stop_reason": "stop", - "flue.turn_purpose": "agent", - "model": "claude-sonnet-4-5-20250929", - "provider": "anthropic" - }, - "metrics": { - "completion_tokens": 8, - "duration_ms": 0, - "estimated_cost": 0.00129555, - "prompt_cache_creation_tokens": 135, - "prompt_cached_tokens": 2171, - "prompt_tokens": 2312, - "tokens": 2320 - } - } - ], - "input": [ - { - "content": [ - { - "text": "Run the skill named \"e2e-flue-skill\".\n\nArguments:\n{\n \"marker\": \"SKILL_DONE\"\n}", - "type": "text" - } - ], - "role": "user" - } - ], - "output": "SKILL_DONE", - "metadata": { - "flue.operation": "skill", - "flue.session": "skill", - "flue.workflow_name": "instrumentation", - "provider": "flue", - "scenario": "flue-instrumentation" - }, - "metrics": { - "duration_ms": 0 - } - }, - { - "name": "flue.task", - "type": "task", - "children": [ - { - "name": "flue.turn", - "type": "llm", - "children": [], - "input": [ - { - "content": [ - { - "text": "Reply with exactly TASK_DONE and no other text.", - "type": "text" - } - ], - "role": "user" - } - ], - "output": { - "content": [ - { - "text": "TASK_DONE", - "textSignature": "{\"v\":1,\"id\":\"msg_09b32b2821e9a4d8016a184600019081929d34bb7d4c6a246f\"}", - "type": "text" - } - ], - "role": "assistant" - }, - "metadata": { - "flue.api": "openai-responses", - "flue.input_mode": "full", - "flue.model": "gpt-4o-mini", - "flue.provider": "openai", - "flue.session": "task:task:", - "flue.stop_reason": "stop", - "flue.turn_purpose": "agent", - "model": "gpt-4o-mini", - "provider": "openai" - }, - "metrics": { - "completion_tokens": 4, - "duration_ms": 0, - "estimated_cost": 0.00012299999999999998, - "prompt_cache_creation_tokens": 0, - "prompt_cached_tokens": 0, - "prompt_tokens": 804, - "tokens": 808 - } - } - ], - "input": "Reply with exactly TASK_DONE and no other text.", - "output": "TASK_DONE", - "metadata": { - "flue.session": "task", - "provider": "flue" - }, - "metrics": { - "duration_ms": 0 - } - }, - { - "name": "flue.compact", - "type": "task", - "children": [ - { - "name": "compaction:manual", - "type": "task", - "children": [ - { - "name": "flue.turn", - "type": "llm", - "children": [], - "input": [ - { - "content": [ - { - "text": "\n[User]: Complete this instrumented research flow. Call exactly one tool per turn and wait for each tool result before choosing the next tool. Step 1: call lookup with query \"flue instrumentation\". Step 2: use the lookup result id as lookupId and call web_search with query \"Braintrust Flue reasoning stream instrumentation\". Step 3: use the first web_search result url and call summarize_source. After summarize_source returns, reply with exactly PROMPT_DONE and no other text.\n\n[Assistant thinking]: The user wants me to complete a research flow with exactly three steps:\n1. Call lookup with query \"flue instrumentation\"\n2. Call web_search with the lookupId from step 1 and query \"Braintrust Flue reasoning stream instrumentation\"\n3. Call summarize_source with the first URL from step 2\n4. Reply with exactly \"PROMPT_DONE\"\n\nThe user emphasizes I should call exactly one tool per turn and wait for each result. Let me start with step 1.\n\n[Assistant]: I'll complete this instrumented research flow step by step. Starting with Step 1:\n\n[Assistant tool calls]: lookup(query=\"flue instrumentation\")\n\n[Tool result]: {\"id\":\"flue-session-2026\",\"query\":\"flue instrumentation\",\"topic\":\"session instrumentation\"}\n\n[Assistant thinking]: Good, I got the lookup result with id \"flue-session-2026\". Now I need to proceed to step 2: call web_search with this lookupId and the query \"Braintrust Flue reasoning stream instrumentation\".\n\n[Assistant]: Now proceeding to Step 2:\n\n[Assistant tool calls]: web_search(lookupId=\"flue-session-2026\", query=\"Braintrust Flue reasoning stream instrumentation\")\n\n[Tool result]: {\"lookupId\":\"flue-session-2026\",\"query\":\"Braintrust Flue reasoning stream instrumentation\",\"results\":[{\"title\":\"Flue reasoning stream instrumentation\",\"url\":\"https://example.test/flue/reasoning-streams\"}]}\n\n[Assistant thinking]: Perfect! I got the web_search results with one result. The first (and only) result has the URL \"https://example.test/flue/reasoning-streams\". Now I need to proceed to step 3: call summarize_source with this URL.\n\n[Assistant]: Now proceeding to Step 3:\n\n[Assistant tool calls]: summarize_source(url=\"https://example.test/flue/reasoning-streams\")\n\n[Tool result]: {\"summary\":\"Flue emits reasoning, tool execution, and LLM turn events separately.\",\"url\":\"https://example.test/flue/reasoning-streams\"}\n\n\nThis is the PREFIX of a turn that was too large to keep. The SUFFIX (recent work) is retained.\n\nSummarize the prefix to provide context for the retained suffix:\n\n## Original Request\n[What did the user ask for in this turn?]\n\n## Early Progress\n- [Key decisions and work done in the prefix]\n\n## Context for Suffix\n- [Information needed to understand the retained recent work]\n\nBe concise. Focus on what's needed to understand the kept suffix.", - "type": "text" - } - ], - "role": "user" - } - ], - "output": { - "content": [ - { - "text": "## Original Request\nThe user requested to complete an instrumented research flow consisting of three specific steps, involving calling various tools in a sequential manner.\n\n## Early Progress", - "textSignature": "{\"v\":1,\"id\":\"msg_01ecf68325f9e202016a184601309081a0878ef4054e950d32\"}", - "type": "text" - } - ], - "role": "assistant" - }, - "metadata": { - "flue.api": "openai-responses", - "flue.model": "gpt-4o-mini", - "flue.provider": "openai", - "flue.session": "main", - "flue.stop_reason": "stop", - "flue.turn_purpose": "compaction_prefix", - "model": "gpt-4o-mini", - "provider": "openai" - }, - "metrics": { - "completion_tokens": 0, - "duration_ms": 0, - "estimated_cost": 0, - "prompt_cache_creation_tokens": 0, - "prompt_cached_tokens": 0, - "prompt_tokens": 0, - "tokens": 0 - } - } - ], - "input": { - "estimatedTokens": 2950, - "reason": "manual" - }, - "output": { - "messagesAfter": 2, - "messagesBefore": 8 - }, - "metadata": { - "flue.compaction_reason": "manual", - "flue.session": "main", - "provider": "flue" - }, - "metrics": { - "duration_ms": 0, - "messages_after": 2, - "messages_before": 8 - } - } - ], - "input": { - "estimatedTokens": 2950, - "reason": "manual" - }, - "output": { - "completed": true - }, - "metadata": { - "flue.operation": "compact", - "flue.session": "main", - "flue.workflow_name": "instrumentation", - "provider": "flue", - "scenario": "flue-instrumentation" - }, - "metrics": { - "duration_ms": 0 - } - } - ], - "input": { - "metadata": { - "scenario": "flue-instrumentation", - "testRunId": "" - }, - "scenario": "flue-instrumentation" - }, - "output": { - "scenario": "flue-instrumentation", - "status": "done" - }, - "metadata": { - "flue.workflow_name": "instrumentation", - "provider": "flue", - "scenario": "flue-instrumentation" - }, - "metrics": { - "duration_ms": 0 - } - }, - { - "name": "flue.prompt", - "type": "task", - "children": [ - { - "name": "flue.turn", - "type": "llm", - "children": [], - "input": [ - { - "content": [ - { - "text": "Complete this instrumented research flow. Call exactly one tool per turn and wait for each tool result before choosing the next tool. Step 1: call lookup with query \"flue instrumentation\". Step 2: use the lookup result id as lookupId and call web_search with query \"Braintrust Flue reasoning stream instrumentation\". Step 3: use the first web_search result url and call summarize_source. After summarize_source returns, reply with exactly PROMPT_DONE and no other text.", - "type": "text" - } - ], - "role": "user" - } - ], - "output": { - "content": [ - { - "thinking": "The user wants me to complete a research flow with exactly three steps:\n1. Call lookup with query \"flue instrumentation\"\n2. Call web_search with the lookupId from step 1 and query \"Braintrust Flue reasoning stream instrumentation\"\n3. Call summarize_source with the first URL from step 2\n4. Reply with exactly \"PROMPT_DONE\"\n\nThe user emphasizes I should call exactly one tool per turn and wait for each result. Let me start with step 1.", - "thinkingSignature": "EoQFCm4IDhgCKkD+AoKMhpXNPUJ5eANwM28uUCJPCE+AXh0ACw7aXniX+YN04zdJl3hlHMbtAuKhhczLiEeOJtu4KtVpea5u5NFdMhpjbGF1ZGUtc29ubmV0LTQtNS0yMDI1MDkyOTgAQgh0aGlua2luZxIMIOTWCNEtsiI9vF4SGgwPXC861uZx8lpOziEiMLsCukGIluD0U44degzYnHFc+BwXNIEB+yatSHlZE+a5smr9crZcft3OLmsIdXuaFSrDAyQxWd9oqfJdnvza9eWIaOCeNkVCjrTc/wotBwh5Xp8m9k/WKt7P4zzUJBuLQ1f6ZnsdWkL8OiL/2E7Up55z2+UuVWsg1Y90/x27YRFdQjv3RnztYp4bZ5oIHAlTJoCzWiBYEh/Z8c2O+wy6MHo1cp0T57OroB/4HAqRZxrjTOXpKYLmYKXAY5tU8DTd3A0/FfEhg3oPSbOhX+112szFCpm3BRNJYooUUhwSu1hO4H/6r7d/Yp9XIM4tBfzuDx83CC/YetdOFgnYkdliw0xaUGaj4o37rWIRctWHAMdlUc4NDA0Ljc1uirHDXkN8EJwWSoIjPSBTA/p/wgLrvRV5Yy9Z0dMoWOpgYF6t1H/tNAyY8ml1CA7IWyd3jnfTZ1/G3Y0xFDwJDFlb4/QKc67w36SSCa+X9mMoTxD/DYN249lsII+XX3lQ7F1zJ07THOtCyRirCaRhmE/kGwl5tiB1QHrWa9bNHxQxf9i92435JGKN053Fe14AR70MtBSzIU2/zbgvhB1vF5lZt2i46hNxfo4lXR495gEwdwC6+8HJnlEJAJkdeHfZKj+RiwXoiYKPKsSbHi83X8Hh32IifuglNxdlxVQYAQ==", - "type": "thinking" - }, - { - "text": "I'll complete this instrumented research flow step by step. Starting with Step 1:", - "type": "text" - }, - { - "arguments": { - "query": "flue instrumentation" - }, - "id": "", - "name": "lookup", - "type": "toolCall" - } - ], - "role": "assistant" - }, - "metadata": { - "flue.api": "anthropic-messages", - "flue.input_mode": "full", - "flue.model": "claude-sonnet-4-5-20250929", - "flue.provider": "anthropic", - "flue.session": "main", - "flue.stop_reason": "toolUse", - "flue.turn_purpose": "agent", - "model": "claude-sonnet-4-5-20250929", - "provider": "anthropic" - }, - "metrics": { - "completion_tokens": 196, - "duration_ms": 0, - "estimated_cost": 0.0113025, - "prompt_cache_creation_tokens": 2222, - "prompt_cached_tokens": 0, - "prompt_tokens": 2232, - "tokens": 2428 - } - }, - { - "name": "tool:lookup", - "type": "tool", - "children": [], - "input": { - "query": "flue instrumentation" - }, - "output": { - "content": [ - { - "text": "{\"id\":\"flue-session-2026\",\"query\":\"flue instrumentation\",\"topic\":\"session instrumentation\"}", - "type": "text" - } - ], - "details": { - "customTool": "lookup" - } - }, - "metadata": { - "flue.session": "main", - "flue.tool_name": "lookup", - "provider": "flue" - }, - "metrics": { - "duration_ms": 0 - } - }, - { - "name": "flue.turn", - "type": "llm", - "children": [], - "input": [ - { - "content": [ - { - "thinking": "The user wants me to complete a research flow with exactly three steps:\n1. Call lookup with query \"flue instrumentation\"\n2. Call web_search with the lookupId from step 1 and query \"Braintrust Flue reasoning stream instrumentation\"\n3. Call summarize_source with the first URL from step 2\n4. Reply with exactly \"PROMPT_DONE\"\n\nThe user emphasizes I should call exactly one tool per turn and wait for each result. Let me start with step 1.", - "thinkingSignature": "EoQFCm4IDhgCKkD+AoKMhpXNPUJ5eANwM28uUCJPCE+AXh0ACw7aXniX+YN04zdJl3hlHMbtAuKhhczLiEeOJtu4KtVpea5u5NFdMhpjbGF1ZGUtc29ubmV0LTQtNS0yMDI1MDkyOTgAQgh0aGlua2luZxIMIOTWCNEtsiI9vF4SGgwPXC861uZx8lpOziEiMLsCukGIluD0U44degzYnHFc+BwXNIEB+yatSHlZE+a5smr9crZcft3OLmsIdXuaFSrDAyQxWd9oqfJdnvza9eWIaOCeNkVCjrTc/wotBwh5Xp8m9k/WKt7P4zzUJBuLQ1f6ZnsdWkL8OiL/2E7Up55z2+UuVWsg1Y90/x27YRFdQjv3RnztYp4bZ5oIHAlTJoCzWiBYEh/Z8c2O+wy6MHo1cp0T57OroB/4HAqRZxrjTOXpKYLmYKXAY5tU8DTd3A0/FfEhg3oPSbOhX+112szFCpm3BRNJYooUUhwSu1hO4H/6r7d/Yp9XIM4tBfzuDx83CC/YetdOFgnYkdliw0xaUGaj4o37rWIRctWHAMdlUc4NDA0Ljc1uirHDXkN8EJwWSoIjPSBTA/p/wgLrvRV5Yy9Z0dMoWOpgYF6t1H/tNAyY8ml1CA7IWyd3jnfTZ1/G3Y0xFDwJDFlb4/QKc67w36SSCa+X9mMoTxD/DYN249lsII+XX3lQ7F1zJ07THOtCyRirCaRhmE/kGwl5tiB1QHrWa9bNHxQxf9i92435JGKN053Fe14AR70MtBSzIU2/zbgvhB1vF5lZt2i46hNxfo4lXR495gEwdwC6+8HJnlEJAJkdeHfZKj+RiwXoiYKPKsSbHi83X8Hh32IifuglNxdlxVQYAQ==", - "type": "thinking" - }, - { - "text": "I'll complete this instrumented research flow step by step. Starting with Step 1:", - "type": "text" - }, - { - "arguments": { - "query": "flue instrumentation" - }, - "id": "", - "name": "lookup", - "type": "toolCall" - } - ], - "role": "assistant" - }, - { - "content": [ - { - "text": "{\"id\":\"flue-session-2026\",\"query\":\"flue instrumentation\",\"topic\":\"session instrumentation\"}", - "type": "text" - } - ], - "isError": false, - "role": "toolResult", - "toolCallId": "", - "toolName": "lookup" - } - ], - "output": { - "content": [ - { - "thinking": "Good, I got the lookup result with id \"flue-session-2026\". Now I need to proceed to step 2: call web_search with this lookupId and the query \"Braintrust Flue reasoning stream instrumentation\".", - "thinkingSignature": "EpEDCm4IDhgCKkAMahve1kB1j4njp1ZSzO8eCK4bb5Ex7MB6tjdoOXfuUDRfkBqvIGdwVly8GiA8GGYC7Y/RVfREXF6A6OXHTwFyMhpjbGF1ZGUtc29ubmV0LTQtNS0yMDI1MDkyOTgAQgh0aGlua2luZxIMLw+fsa4yo7a7ZSoIGgyaTdvH020n2dQj9bUiMB4OokY29w+uZb4rqDnKDbLXUqUj6U0f1QCGQF2ZBYDDfDJyM5Jv1xY3rQiUUjkhBirQAS4XivjvMnWI5zHBmpI/u/pq4aGdrdb3vUsJ/ne6R1d80JA/INGF8vLkQcZ5xxEK0jU4yRE+ldc2uzavmtoxIKAFD6bqKks1Qv6u+TIZtuW7tUB/7jFxDw93KmJNruMIa8c1J6pPUyBy3xE7QwgmU0kDfIfdnljxLoEBiMwtnxjCPj9lT7HHz4FkrhbnQTV4MvAQohCLjaSbOTd6LdLxwtJPm9mc26ID4eYN1gCCzyAxGXhqLHlzc23PafvQjsZliWnLhaiNMnG0YEo78XkuJQsYAQ==", - "type": "thinking" - }, - { - "text": "Now proceeding to Step 2:", - "type": "text" - }, - { - "arguments": { - "lookupId": "flue-session-2026", - "query": "Braintrust Flue reasoning stream instrumentation" - }, - "id": "", - "name": "web_search", - "type": "toolCall" - } - ], - "role": "assistant" - }, - "metadata": { - "flue.api": "anthropic-messages", - "flue.input_message_offset": 1, - "flue.input_mode": "delta", - "flue.model": "claude-sonnet-4-5-20250929", - "flue.provider": "anthropic", - "flue.session": "main", - "flue.stop_reason": "toolUse", - "flue.turn_purpose": "agent", - "model": "claude-sonnet-4-5-20250929", - "provider": "anthropic" - }, - "metrics": { - "completion_tokens": 158, - "duration_ms": 0, - "estimated_cost": 0.0039606, - "prompt_cache_creation_tokens": 236, - "prompt_cached_tokens": 2222, - "prompt_tokens": 2471, - "tokens": 2629 - } - }, - { - "name": "tool:web_search", - "type": "tool", - "children": [], - "input": { - "lookupId": "flue-session-2026", - "query": "Braintrust Flue reasoning stream instrumentation" - }, - "output": { - "content": [ - { - "text": "{\"lookupId\":\"flue-session-2026\",\"query\":\"Braintrust Flue reasoning stream instrumentation\",\"results\":[{\"title\":\"Flue reasoning stream instrumentation\",\"url\":\"https://example.test/flue/reasoning-streams\"}]}", - "type": "text" - } - ], - "details": { - "customTool": "web_search" - } - }, - "metadata": { - "flue.session": "main", - "flue.tool_name": "web_search", - "provider": "flue" - }, - "metrics": { - "duration_ms": 0 - } - }, - { - "name": "flue.turn", - "type": "llm", - "children": [], - "input": [ - { - "content": [ - { - "thinking": "Good, I got the lookup result with id \"flue-session-2026\". Now I need to proceed to step 2: call web_search with this lookupId and the query \"Braintrust Flue reasoning stream instrumentation\".", - "thinkingSignature": "EpEDCm4IDhgCKkAMahve1kB1j4njp1ZSzO8eCK4bb5Ex7MB6tjdoOXfuUDRfkBqvIGdwVly8GiA8GGYC7Y/RVfREXF6A6OXHTwFyMhpjbGF1ZGUtc29ubmV0LTQtNS0yMDI1MDkyOTgAQgh0aGlua2luZxIMLw+fsa4yo7a7ZSoIGgyaTdvH020n2dQj9bUiMB4OokY29w+uZb4rqDnKDbLXUqUj6U0f1QCGQF2ZBYDDfDJyM5Jv1xY3rQiUUjkhBirQAS4XivjvMnWI5zHBmpI/u/pq4aGdrdb3vUsJ/ne6R1d80JA/INGF8vLkQcZ5xxEK0jU4yRE+ldc2uzavmtoxIKAFD6bqKks1Qv6u+TIZtuW7tUB/7jFxDw93KmJNruMIa8c1J6pPUyBy3xE7QwgmU0kDfIfdnljxLoEBiMwtnxjCPj9lT7HHz4FkrhbnQTV4MvAQohCLjaSbOTd6LdLxwtJPm9mc26ID4eYN1gCCzyAxGXhqLHlzc23PafvQjsZliWnLhaiNMnG0YEo78XkuJQsYAQ==", - "type": "thinking" - }, - { - "text": "Now proceeding to Step 2:", - "type": "text" - }, - { - "arguments": { - "lookupId": "flue-session-2026", - "query": "Braintrust Flue reasoning stream instrumentation" - }, - "id": "", - "name": "web_search", - "type": "toolCall" - } - ], - "role": "assistant" - }, - { - "content": [ - { - "text": "{\"lookupId\":\"flue-session-2026\",\"query\":\"Braintrust Flue reasoning stream instrumentation\",\"results\":[{\"title\":\"Flue reasoning stream instrumentation\",\"url\":\"https://example.test/flue/reasoning-streams\"}]}", - "type": "text" - } - ], - "isError": false, - "role": "toolResult", - "toolCallId": "", - "toolName": "web_search" - } - ], - "output": { - "content": [ - { - "thinking": "Perfect! I got the web_search results with one result. The first (and only) result has the URL \"https://example.test/flue/reasoning-streams\". Now I need to proceed to step 3: call summarize_source with this URL.", - "thinkingSignature": "EqQDCm4IDhgCKkAXv7EDAhWMcrBz11bUd5qxlzyy03CAGQfRgLHDE0uKzLATh1o2sedd8GxLwwQqV0CmLRfNxsdK/ZDp3Lks9iysMhpjbGF1ZGUtc29ubmV0LTQtNS0yMDI1MDkyOTgAQgh0aGlua2luZxIMXDMniOy5Dy0KI8x7GgzgHs7BYJMs6/N0ur8iMPe0SwnHhP39OjEu7P8ASwK9ffwa3VP67QOlPzOstRRdEZNd0mq+BVjpmBXB+QcAzCrjAeAh/UO/d7SfFmXjqwA3//thoL/TTlGa6W+fdOCFRikn65JWT4ZdVFetfqD+9/QCP/nXEwMsLnTBAxC4EGryrgs2YT+OWwmEJ7rHKFeDs5djhwSb2J2ZJO1kRvD9s2vCCDkImygYBSIVgx1Y0CZJHhzn2Vf39v0Fbth2wDZa+OqxivV9NmDZEjmgcM+tTRx1CCnaww8LZEJQ68v/dUJy7hhD4MrNcQ62evJYPfT3+4EKKWJ/7HI7KXz91//+YAkph63XstbJMFJ3AldAFiq3ecauXt5r42N5gmCmwZX+sI48ENLGGAE=", - "type": "thinking" - }, - { - "text": "Now proceeding to Step 3:", - "type": "text" - }, - { - "arguments": { - "url": "https://example.test/flue/reasoning-streams" - }, - "id": "", - "name": "summarize_source", - "type": "toolCall" - } - ], - "role": "assistant" - }, - "metadata": { - "flue.api": "anthropic-messages", - "flue.input_message_offset": 3, - "flue.input_mode": "delta", - "flue.model": "claude-sonnet-4-5-20250929", - "flue.provider": "anthropic", - "flue.session": "main", - "flue.stop_reason": "toolUse", - "flue.turn_purpose": "agent", - "model": "claude-sonnet-4-5-20250929", - "provider": "anthropic" - }, - "metrics": { - "completion_tokens": 138, - "duration_ms": 0, - "estimated_cost": 0.0037239000000000005, - "prompt_cache_creation_tokens": 234, - "prompt_cached_tokens": 2458, - "prompt_tokens": 2705, - "tokens": 2843 - } - }, - { - "name": "tool:summarize_source", - "type": "tool", - "children": [], - "input": { - "url": "https://example.test/flue/reasoning-streams" - }, - "output": { - "content": [ - { - "text": "{\"summary\":\"Flue emits reasoning, tool execution, and LLM turn events separately.\",\"url\":\"https://example.test/flue/reasoning-streams\"}", - "type": "text" - } - ], - "details": { - "customTool": "summarize_source" - } - }, - "metadata": { - "flue.session": "main", - "flue.tool_name": "summarize_source", - "provider": "flue" - }, - "metrics": { - "duration_ms": 0 - } - }, - { - "name": "flue.turn", - "type": "llm", - "children": [], - "input": [ - { - "content": [ - { - "thinking": "Perfect! I got the web_search results with one result. The first (and only) result has the URL \"https://example.test/flue/reasoning-streams\". Now I need to proceed to step 3: call summarize_source with this URL.", - "thinkingSignature": "EqQDCm4IDhgCKkAXv7EDAhWMcrBz11bUd5qxlzyy03CAGQfRgLHDE0uKzLATh1o2sedd8GxLwwQqV0CmLRfNxsdK/ZDp3Lks9iysMhpjbGF1ZGUtc29ubmV0LTQtNS0yMDI1MDkyOTgAQgh0aGlua2luZxIMXDMniOy5Dy0KI8x7GgzgHs7BYJMs6/N0ur8iMPe0SwnHhP39OjEu7P8ASwK9ffwa3VP67QOlPzOstRRdEZNd0mq+BVjpmBXB+QcAzCrjAeAh/UO/d7SfFmXjqwA3//thoL/TTlGa6W+fdOCFRikn65JWT4ZdVFetfqD+9/QCP/nXEwMsLnTBAxC4EGryrgs2YT+OWwmEJ7rHKFeDs5djhwSb2J2ZJO1kRvD9s2vCCDkImygYBSIVgx1Y0CZJHhzn2Vf39v0Fbth2wDZa+OqxivV9NmDZEjmgcM+tTRx1CCnaww8LZEJQ68v/dUJy7hhD4MrNcQ62evJYPfT3+4EKKWJ/7HI7KXz91//+YAkph63XstbJMFJ3AldAFiq3ecauXt5r42N5gmCmwZX+sI48ENLGGAE=", - "type": "thinking" - }, - { - "text": "Now proceeding to Step 3:", - "type": "text" - }, - { - "arguments": { - "url": "https://example.test/flue/reasoning-streams" - }, - "id": "", - "name": "summarize_source", - "type": "toolCall" - } - ], - "role": "assistant" - }, - { - "content": [ - { - "text": "{\"summary\":\"Flue emits reasoning, tool execution, and LLM turn events separately.\",\"url\":\"https://example.test/flue/reasoning-streams\"}", - "type": "text" - } - ], - "isError": false, - "role": "toolResult", - "toolCallId": "", - "toolName": "summarize_source" - } - ], - "output": { - "content": [ - { - "thinking": "Perfect! The summarize_source call has completed successfully. According to the instructions, I should now reply with exactly \"PROMPT_DONE\" and no other text.", - "thinkingSignature": "Eu8CCm4IDhgCKkAn2QOIIlYfvcs6ynsmhCslHDtBsrTA4CfmimK7zyz6+k6dZldV2JBtEBET41It0gFcGEKC0n0mAhaYVuLutpDDMhpjbGF1ZGUtc29ubmV0LTQtNS0yMDI1MDkyOTgAQgh0aGlua2luZxIM1xfLptxSF8rKzou8GgwRLuVmfiBYOT4CZIsiMAYz8TpvchMAWodP+T9uZ+dJ47MVdNv+p86nZXmT5d5EWlCroiAOFHHzJH2o0UjJQSquAX6wu+jIC8/DXwurGIIf2ToldPuJWF5HGj8kHMnwh777XS1NUFl1irM24rvP7kTAv3Dp8YEwb8t3g1EsicY7j+C5DzgwxVsPYCVOCgdMqQjtsqpL7AX5KQ9+hR1gK1DQjshv4Gze3hw/+C+9iptd3RlDIuwO0cm8NY+sVqeWVh3TtQujQlVt1rW/HfWjclrtT0LZSGwc6RypmfMMQi6lncIk/EPgy0XbS1plKW0tLBgB", - "type": "thinking" - }, - { - "text": "PROMPT_DONE", - "type": "text" - } - ], - "role": "assistant" - }, - "metadata": { - "flue.api": "anthropic-messages", - "flue.input_message_offset": 5, - "flue.input_mode": "delta", - "flue.model": "claude-sonnet-4-5-20250929", - "flue.provider": "anthropic", - "flue.session": "main", - "flue.stop_reason": "stop", - "flue.turn_purpose": "agent", - "model": "claude-sonnet-4-5-20250929", - "provider": "anthropic" - }, - "metrics": { - "completion_tokens": 51, - "duration_ms": 0, - "estimated_cost": 0.0023391, - "prompt_cache_creation_tokens": 194, - "prompt_cached_tokens": 2692, - "prompt_tokens": 2899, - "tokens": 2950 - } - } - ], - "input": [ - { - "content": [ - { - "text": "Complete this instrumented research flow. Call exactly one tool per turn and wait for each tool result before choosing the next tool. Step 1: call lookup with query \"flue instrumentation\". Step 2: use the lookup result id as lookupId and call web_search with query \"Braintrust Flue reasoning stream instrumentation\". Step 3: use the first web_search result url and call summarize_source. After summarize_source returns, reply with exactly PROMPT_DONE and no other text.", - "type": "text" - } - ], - "role": "user" - } - ], - "output": "PROMPT_DONE", - "metadata": { - "flue.operation": "prompt", - "flue.session": "main", - "flue.workflow_name": "instrumentation", - "provider": "flue", - "scenario": "flue-instrumentation" - }, - "metrics": { - "duration_ms": 0 - } - } - ] -} diff --git a/e2e/scenarios/flue-instrumentation/__snapshots__/flue-v0-8-0-explicit.span-tree.txt b/e2e/scenarios/flue-instrumentation/__snapshots__/flue-v0-8-0-explicit.span-tree.txt deleted file mode 100644 index aaf72f391..000000000 --- a/e2e/scenarios/flue-instrumentation/__snapshots__/flue-v0-8-0-explicit.span-tree.txt +++ /dev/null @@ -1,1335 +0,0 @@ -span_tree: -├── workflow:instrumentation [task] -│ input: { -│ "metadata": { -│ "scenario": "flue-instrumentation", -│ "testRunId": "" -│ }, -│ "scenario": "flue-instrumentation" -│ } -│ output: { -│ "scenario": "flue-instrumentation", -│ "status": "done" -│ } -│ metadata: { -│ "flue.workflow_name": "instrumentation", -│ "provider": "flue", -│ "scenario": "flue-instrumentation" -│ } -│ metrics: { -│ "duration_ms": 0 -│ } -│ ├── flue.skill [task] -│ │ input: [ -│ │ { -│ │ "content": [ -│ │ { -│ │ "text": "Run the skill named \"e2e-flue-skill\".\n\nArguments:\n{\n \"marker\": \"SKILL_DONE\"\n}", -│ │ "type": "text" -│ │ } -│ │ ], -│ │ "role": "user" -│ │ } -│ │ ] -│ │ output: "SKILL_DONE" -│ │ metadata: { -│ │ "flue.operation": "skill", -│ │ "flue.session": "skill", -│ │ "flue.workflow_name": "instrumentation", -│ │ "provider": "flue", -│ │ "scenario": "flue-instrumentation" -│ │ } -│ │ metrics: { -│ │ "duration_ms": 0 -│ │ } -│ │ ├── flue.turn [llm] -│ │ │ input: [ -│ │ │ { -│ │ │ "content": [ -│ │ │ { -│ │ │ "text": "Run the skill named \"e2e-flue-skill\".\n\nArguments:\n{\n \"marker\": \"SKILL_DONE\"\n}", -│ │ │ "type": "text" -│ │ │ } -│ │ │ ], -│ │ │ "role": "user" -│ │ │ } -│ │ │ ] -│ │ │ output: { -│ │ │ "content": [ -│ │ │ { -│ │ │ "text": "I'll run the e2e-flue-skill. Let me first read the skill file to understand its instructions.", -│ │ │ "type": "text" -│ │ │ }, -│ │ │ { -│ │ │ "arguments": { -│ │ │ "path": ".agents/e2e-flue-skill.md" -│ │ │ }, -│ │ │ "id": "", -│ │ │ "name": "read", -│ │ │ "type": "toolCall" -│ │ │ } -│ │ │ ], -│ │ │ "role": "assistant" -│ │ │ } -│ │ │ metadata: { -│ │ │ "flue.api": "anthropic-messages", -│ │ │ "flue.model": "claude-sonnet-4-5-20250929", -│ │ │ "flue.provider": "anthropic", -│ │ │ "flue.session": "skill", -│ │ │ "flue.stop_reason": "toolUse", -│ │ │ "flue.turn_purpose": "agent", -│ │ │ "model": "claude-sonnet-4-5-20250929", -│ │ │ "provider": "anthropic" -│ │ │ } -│ │ │ metrics: { -│ │ │ "completion_tokens": 89, -│ │ │ "duration_ms": 0, -│ │ │ "estimated_cost": 0.0041862, -│ │ │ "prompt_cache_creation_tokens": 670, -│ │ │ "prompt_cached_tokens": 1099, -│ │ │ "prompt_tokens": 1772, -│ │ │ "tokens": 1861 -│ │ │ } -│ │ ├── tool:read [tool] -│ │ │ input: { -│ │ │ "path": ".agents/e2e-flue-skill.md" -│ │ │ } -│ │ │ output: { -│ │ │ "content": [ -│ │ │ { -│ │ │ "text": "ENOENT: no such file or directory, open '/e2e/scenarios/flue-instrumentation/.agents/e2e-flue-skill.md'", -│ │ │ "type": "text" -│ │ │ } -│ │ │ ], -│ │ │ "details": {} -│ │ │ } -│ │ │ metadata: { -│ │ │ "flue.session": "skill", -│ │ │ "flue.tool_name": "read", -│ │ │ "provider": "flue" -│ │ │ } -│ │ │ metrics: { -│ │ │ "duration_ms": 0 -│ │ │ } -│ │ │ error: "{\"content\":[{\"type\":\"text\",\"text\":\"ENOENT: no such file or directory, open '/e2e/scenarios/flue-instrumentation/.agents/e2e-flue-skill.md'\"}],\"details\":{}}" -│ │ ├── flue.turn [llm] -│ │ │ input: [ -│ │ │ { -│ │ │ "content": [ -│ │ │ { -│ │ │ "text": "Run the skill named \"e2e-flue-skill\".\n\nArguments:\n{\n \"marker\": \"SKILL_DONE\"\n}", -│ │ │ "type": "text" -│ │ │ } -│ │ │ ], -│ │ │ "role": "user" -│ │ │ }, -│ │ │ { -│ │ │ "content": [ -│ │ │ { -│ │ │ "text": "I'll run the e2e-flue-skill. Let me first read the skill file to understand its instructions.", -│ │ │ "type": "text" -│ │ │ }, -│ │ │ { -│ │ │ "arguments": { -│ │ │ "path": ".agents/e2e-flue-skill.md" -│ │ │ }, -│ │ │ "id": "", -│ │ │ "name": "read", -│ │ │ "type": "toolCall" -│ │ │ } -│ │ │ ], -│ │ │ "role": "assistant" -│ │ │ }, -│ │ │ { -│ │ │ "content": [ -│ │ │ { -│ │ │ "text": "ENOENT: no such file or directory, open '/e2e/scenarios/flue-instrumentation/.agents/e2e-flue-skill.md'", -│ │ │ "type": "text" -│ │ │ } -│ │ │ ], -│ │ │ "isError": true, -│ │ │ "role": "toolResult", -│ │ │ "toolCallId": "", -│ │ │ "toolName": "read" -│ │ │ } -│ │ │ ] -│ │ │ output: { -│ │ │ "content": [ -│ │ │ { -│ │ │ "arguments": { -│ │ │ "path": ".agents" -│ │ │ }, -│ │ │ "id": "", -│ │ │ "name": "read", -│ │ │ "type": "toolCall" -│ │ │ } -│ │ │ ], -│ │ │ "role": "assistant" -│ │ │ } -│ │ │ metadata: { -│ │ │ "flue.api": "anthropic-messages", -│ │ │ "flue.model": "claude-sonnet-4-5-20250929", -│ │ │ "flue.provider": "anthropic", -│ │ │ "flue.session": "skill", -│ │ │ "flue.stop_reason": "toolUse", -│ │ │ "flue.turn_purpose": "agent", -│ │ │ "model": "claude-sonnet-4-5-20250929", -│ │ │ "provider": "anthropic" -│ │ │ } -│ │ │ metrics: { -│ │ │ "completion_tokens": 52, -│ │ │ "duration_ms": 0, -│ │ │ "estimated_cost": 0.00201795, -│ │ │ "prompt_cache_creation_tokens": 183, -│ │ │ "prompt_cached_tokens": 1769, -│ │ │ "prompt_tokens": 1959, -│ │ │ "tokens": 2011 -│ │ │ } -│ │ ├── tool:read [tool] -│ │ │ input: { -│ │ │ "path": ".agents" -│ │ │ } -│ │ │ output: { -│ │ │ "content": [ -│ │ │ { -│ │ │ "text": "skills", -│ │ │ "type": "text" -│ │ │ } -│ │ │ ], -│ │ │ "details": { -│ │ │ "entries": 1, -│ │ │ "isDirectory": true, -│ │ │ "path": ".agents" -│ │ │ } -│ │ │ } -│ │ │ metadata: { -│ │ │ "flue.session": "skill", -│ │ │ "flue.tool_name": "read", -│ │ │ "provider": "flue" -│ │ │ } -│ │ │ metrics: { -│ │ │ "duration_ms": 0 -│ │ │ } -│ │ ├── flue.turn [llm] -│ │ │ input: [ -│ │ │ { -│ │ │ "content": [ -│ │ │ { -│ │ │ "text": "Run the skill named \"e2e-flue-skill\".\n\nArguments:\n{\n \"marker\": \"SKILL_DONE\"\n}", -│ │ │ "type": "text" -│ │ │ } -│ │ │ ], -│ │ │ "role": "user" -│ │ │ }, -│ │ │ { -│ │ │ "content": [ -│ │ │ { -│ │ │ "text": "I'll run the e2e-flue-skill. Let me first read the skill file to understand its instructions.", -│ │ │ "type": "text" -│ │ │ }, -│ │ │ { -│ │ │ "arguments": { -│ │ │ "path": ".agents/e2e-flue-skill.md" -│ │ │ }, -│ │ │ "id": "", -│ │ │ "name": "read", -│ │ │ "type": "toolCall" -│ │ │ } -│ │ │ ], -│ │ │ "role": "assistant" -│ │ │ }, -│ │ │ { -│ │ │ "content": [ -│ │ │ { -│ │ │ "text": "ENOENT: no such file or directory, open '/e2e/scenarios/flue-instrumentation/.agents/e2e-flue-skill.md'", -│ │ │ "type": "text" -│ │ │ } -│ │ │ ], -│ │ │ "isError": true, -│ │ │ "role": "toolResult", -│ │ │ "toolCallId": "", -│ │ │ "toolName": "read" -│ │ │ }, -│ │ │ { -│ │ │ "content": [ -│ │ │ { -│ │ │ "arguments": { -│ │ │ "path": ".agents" -│ │ │ }, -│ │ │ "id": "", -│ │ │ "name": "read", -│ │ │ "type": "toolCall" -│ │ │ } -│ │ │ ], -│ │ │ "role": "assistant" -│ │ │ }, -│ │ │ { -│ │ │ "content": [ -│ │ │ { -│ │ │ "text": "skills", -│ │ │ "type": "text" -│ │ │ } -│ │ │ ], -│ │ │ "isError": false, -│ │ │ "role": "toolResult", -│ │ │ "toolCallId": "", -│ │ │ "toolName": "read" -│ │ │ } -│ │ │ ] -│ │ │ output: { -│ │ │ "content": [ -│ │ │ { -│ │ │ "arguments": { -│ │ │ "path": ".agents/skills" -│ │ │ }, -│ │ │ "id": "", -│ │ │ "name": "read", -│ │ │ "type": "toolCall" -│ │ │ } -│ │ │ ], -│ │ │ "role": "assistant" -│ │ │ } -│ │ │ metadata: { -│ │ │ "flue.api": "anthropic-messages", -│ │ │ "flue.model": "claude-sonnet-4-5-20250929", -│ │ │ "flue.provider": "anthropic", -│ │ │ "flue.session": "skill", -│ │ │ "flue.stop_reason": "toolUse", -│ │ │ "flue.turn_purpose": "agent", -│ │ │ "model": "claude-sonnet-4-5-20250929", -│ │ │ "provider": "anthropic" -│ │ │ } -│ │ │ metrics: { -│ │ │ "completion_tokens": 54, -│ │ │ "duration_ms": 0, -│ │ │ "estimated_cost": 0.0016611, -│ │ │ "prompt_cache_creation_tokens": 66, -│ │ │ "prompt_cached_tokens": 1952, -│ │ │ "prompt_tokens": 2024, -│ │ │ "tokens": 2078 -│ │ │ } -│ │ ├── tool:read [tool] -│ │ │ input: { -│ │ │ "path": ".agents/skills" -│ │ │ } -│ │ │ output: { -│ │ │ "content": [ -│ │ │ { -│ │ │ "text": "e2e-flue-skill", -│ │ │ "type": "text" -│ │ │ } -│ │ │ ], -│ │ │ "details": { -│ │ │ "entries": 1, -│ │ │ "isDirectory": true, -│ │ │ "path": ".agents/skills" -│ │ │ } -│ │ │ } -│ │ │ metadata: { -│ │ │ "flue.session": "skill", -│ │ │ "flue.tool_name": "read", -│ │ │ "provider": "flue" -│ │ │ } -│ │ │ metrics: { -│ │ │ "duration_ms": 0 -│ │ │ } -│ │ ├── flue.turn [llm] -│ │ │ input: [ -│ │ │ { -│ │ │ "content": [ -│ │ │ { -│ │ │ "text": "Run the skill named \"e2e-flue-skill\".\n\nArguments:\n{\n \"marker\": \"SKILL_DONE\"\n}", -│ │ │ "type": "text" -│ │ │ } -│ │ │ ], -│ │ │ "role": "user" -│ │ │ }, -│ │ │ { -│ │ │ "content": [ -│ │ │ { -│ │ │ "text": "I'll run the e2e-flue-skill. Let me first read the skill file to understand its instructions.", -│ │ │ "type": "text" -│ │ │ }, -│ │ │ { -│ │ │ "arguments": { -│ │ │ "path": ".agents/e2e-flue-skill.md" -│ │ │ }, -│ │ │ "id": "", -│ │ │ "name": "read", -│ │ │ "type": "toolCall" -│ │ │ } -│ │ │ ], -│ │ │ "role": "assistant" -│ │ │ }, -│ │ │ { -│ │ │ "content": [ -│ │ │ { -│ │ │ "text": "ENOENT: no such file or directory, open '/e2e/scenarios/flue-instrumentation/.agents/e2e-flue-skill.md'", -│ │ │ "type": "text" -│ │ │ } -│ │ │ ], -│ │ │ "isError": true, -│ │ │ "role": "toolResult", -│ │ │ "toolCallId": "", -│ │ │ "toolName": "read" -│ │ │ }, -│ │ │ { -│ │ │ "content": [ -│ │ │ { -│ │ │ "arguments": { -│ │ │ "path": ".agents" -│ │ │ }, -│ │ │ "id": "", -│ │ │ "name": "read", -│ │ │ "type": "toolCall" -│ │ │ } -│ │ │ ], -│ │ │ "role": "assistant" -│ │ │ }, -│ │ │ { -│ │ │ "content": [ -│ │ │ { -│ │ │ "text": "skills", -│ │ │ "type": "text" -│ │ │ } -│ │ │ ], -│ │ │ "isError": false, -│ │ │ "role": "toolResult", -│ │ │ "toolCallId": "", -│ │ │ "toolName": "read" -│ │ │ }, -│ │ │ { -│ │ │ "content": [ -│ │ │ { -│ │ │ "arguments": { -│ │ │ "path": ".agents/skills" -│ │ │ }, -│ │ │ "id": "", -│ │ │ "name": "read", -│ │ │ "type": "toolCall" -│ │ │ } -│ │ │ ], -│ │ │ "role": "assistant" -│ │ │ }, -│ │ │ { -│ │ │ "content": [ -│ │ │ { -│ │ │ "text": "e2e-flue-skill", -│ │ │ "type": "text" -│ │ │ } -│ │ │ ], -│ │ │ "isError": false, -│ │ │ "role": "toolResult", -│ │ │ "toolCallId": "", -│ │ │ "toolName": "read" -│ │ │ } -│ │ │ ] -│ │ │ output: { -│ │ │ "content": [ -│ │ │ { -│ │ │ "arguments": { -│ │ │ "path": ".agents/skills/e2e-flue-skill" -│ │ │ }, -│ │ │ "id": "", -│ │ │ "name": "read", -│ │ │ "type": "toolCall" -│ │ │ } -│ │ │ ], -│ │ │ "role": "assistant" -│ │ │ } -│ │ │ metadata: { -│ │ │ "flue.api": "anthropic-messages", -│ │ │ "flue.model": "claude-sonnet-4-5-20250929", -│ │ │ "flue.provider": "anthropic", -│ │ │ "flue.session": "skill", -│ │ │ "flue.stop_reason": "toolUse", -│ │ │ "flue.turn_purpose": "agent", -│ │ │ "model": "claude-sonnet-4-5-20250929", -│ │ │ "provider": "anthropic" -│ │ │ } -│ │ │ metrics: { -│ │ │ "completion_tokens": 63, -│ │ │ "duration_ms": 0, -│ │ │ "estimated_cost": 0.0018459, -│ │ │ "prompt_cache_creation_tokens": 74, -│ │ │ "prompt_cached_tokens": 2018, -│ │ │ "prompt_tokens": 2098, -│ │ │ "tokens": 2161 -│ │ │ } -│ │ ├── tool:read [tool] -│ │ │ input: { -│ │ │ "path": ".agents/skills/e2e-flue-skill" -│ │ │ } -│ │ │ output: { -│ │ │ "content": [ -│ │ │ { -│ │ │ "text": "SKILL.md", -│ │ │ "type": "text" -│ │ │ } -│ │ │ ], -│ │ │ "details": { -│ │ │ "entries": 1, -│ │ │ "isDirectory": true, -│ │ │ "path": ".agents/skills/e2e-flue-skill" -│ │ │ } -│ │ │ } -│ │ │ metadata: { -│ │ │ "flue.session": "skill", -│ │ │ "flue.tool_name": "read", -│ │ │ "provider": "flue" -│ │ │ } -│ │ │ metrics: { -│ │ │ "duration_ms": 0 -│ │ │ } -│ │ ├── flue.turn [llm] -│ │ │ input: [ -│ │ │ { -│ │ │ "content": [ -│ │ │ { -│ │ │ "text": "Run the skill named \"e2e-flue-skill\".\n\nArguments:\n{\n \"marker\": \"SKILL_DONE\"\n}", -│ │ │ "type": "text" -│ │ │ } -│ │ │ ], -│ │ │ "role": "user" -│ │ │ }, -│ │ │ { -│ │ │ "content": [ -│ │ │ { -│ │ │ "text": "I'll run the e2e-flue-skill. Let me first read the skill file to understand its instructions.", -│ │ │ "type": "text" -│ │ │ }, -│ │ │ { -│ │ │ "arguments": { -│ │ │ "path": ".agents/e2e-flue-skill.md" -│ │ │ }, -│ │ │ "id": "", -│ │ │ "name": "read", -│ │ │ "type": "toolCall" -│ │ │ } -│ │ │ ], -│ │ │ "role": "assistant" -│ │ │ }, -│ │ │ { -│ │ │ "content": [ -│ │ │ { -│ │ │ "text": "ENOENT: no such file or directory, open '/e2e/scenarios/flue-instrumentation/.agents/e2e-flue-skill.md'", -│ │ │ "type": "text" -│ │ │ } -│ │ │ ], -│ │ │ "isError": true, -│ │ │ "role": "toolResult", -│ │ │ "toolCallId": "", -│ │ │ "toolName": "read" -│ │ │ }, -│ │ │ { -│ │ │ "content": [ -│ │ │ { -│ │ │ "arguments": { -│ │ │ "path": ".agents" -│ │ │ }, -│ │ │ "id": "", -│ │ │ "name": "read", -│ │ │ "type": "toolCall" -│ │ │ } -│ │ │ ], -│ │ │ "role": "assistant" -│ │ │ }, -│ │ │ { -│ │ │ "content": [ -│ │ │ { -│ │ │ "text": "skills", -│ │ │ "type": "text" -│ │ │ } -│ │ │ ], -│ │ │ "isError": false, -│ │ │ "role": "toolResult", -│ │ │ "toolCallId": "", -│ │ │ "toolName": "read" -│ │ │ }, -│ │ │ { -│ │ │ "content": [ -│ │ │ { -│ │ │ "arguments": { -│ │ │ "path": ".agents/skills" -│ │ │ }, -│ │ │ "id": "", -│ │ │ "name": "read", -│ │ │ "type": "toolCall" -│ │ │ } -│ │ │ ], -│ │ │ "role": "assistant" -│ │ │ }, -│ │ │ { -│ │ │ "content": [ -│ │ │ { -│ │ │ "text": "e2e-flue-skill", -│ │ │ "type": "text" -│ │ │ } -│ │ │ ], -│ │ │ "isError": false, -│ │ │ "role": "toolResult", -│ │ │ "toolCallId": "", -│ │ │ "toolName": "read" -│ │ │ }, -│ │ │ { -│ │ │ "content": [ -│ │ │ { -│ │ │ "arguments": { -│ │ │ "path": ".agents/skills/e2e-flue-skill" -│ │ │ }, -│ │ │ "id": "", -│ │ │ "name": "read", -│ │ │ "type": "toolCall" -│ │ │ } -│ │ │ ], -│ │ │ "role": "assistant" -│ │ │ }, -│ │ │ { -│ │ │ "content": [ -│ │ │ { -│ │ │ "text": "SKILL.md", -│ │ │ "type": "text" -│ │ │ } -│ │ │ ], -│ │ │ "isError": false, -│ │ │ "role": "toolResult", -│ │ │ "toolCallId": "", -│ │ │ "toolName": "read" -│ │ │ } -│ │ │ ] -│ │ │ output: { -│ │ │ "content": [ -│ │ │ { -│ │ │ "arguments": { -│ │ │ "path": ".agents/skills/e2e-flue-skill/SKILL.md" -│ │ │ }, -│ │ │ "id": "", -│ │ │ "name": "read", -│ │ │ "type": "toolCall" -│ │ │ } -│ │ │ ], -│ │ │ "role": "assistant" -│ │ │ } -│ │ │ metadata: { -│ │ │ "flue.api": "anthropic-messages", -│ │ │ "flue.model": "claude-sonnet-4-5-20250929", -│ │ │ "flue.provider": "anthropic", -│ │ │ "flue.session": "skill", -│ │ │ "flue.stop_reason": "toolUse", -│ │ │ "flue.turn_purpose": "agent", -│ │ │ "model": "claude-sonnet-4-5-20250929", -│ │ │ "provider": "anthropic" -│ │ │ } -│ │ │ metrics: { -│ │ │ "completion_tokens": 68, -│ │ │ "duration_ms": 0, -│ │ │ "estimated_cost": 0.0019618500000000002, -│ │ │ "prompt_cache_creation_tokens": 79, -│ │ │ "prompt_cached_tokens": 2092, -│ │ │ "prompt_tokens": 2177, -│ │ │ "tokens": 2245 -│ │ │ } -│ │ ├── tool:read [tool] -│ │ │ input: { -│ │ │ "path": ".agents/skills/e2e-flue-skill/SKILL.md" -│ │ │ } -│ │ │ output: { -│ │ │ "content": [ -│ │ │ { -│ │ │ "text": "---\nname: e2e-flue-skill\ndescription: Deterministic skill used by the Braintrust Flue instrumentation e2e test.\n---\n\nReturn the marker from the provided args exactly once. Output no other text.\n", -│ │ │ "type": "text" -│ │ │ } -│ │ │ ], -│ │ │ "details": { -│ │ │ "lines": 7, -│ │ │ "path": ".agents/skills/e2e-flue-skill/SKILL.md" -│ │ │ } -│ │ │ } -│ │ │ metadata: { -│ │ │ "flue.session": "skill", -│ │ │ "flue.tool_name": "read", -│ │ │ "provider": "flue" -│ │ │ } -│ │ │ metrics: { -│ │ │ "duration_ms": 0 -│ │ │ } -│ │ └── flue.turn [llm] -│ │ input: [ -│ │ { -│ │ "content": [ -│ │ { -│ │ "text": "Run the skill named \"e2e-flue-skill\".\n\nArguments:\n{\n \"marker\": \"SKILL_DONE\"\n}", -│ │ "type": "text" -│ │ } -│ │ ], -│ │ "role": "user" -│ │ }, -│ │ { -│ │ "content": [ -│ │ { -│ │ "text": "I'll run the e2e-flue-skill. Let me first read the skill file to understand its instructions.", -│ │ "type": "text" -│ │ }, -│ │ { -│ │ "arguments": { -│ │ "path": ".agents/e2e-flue-skill.md" -│ │ }, -│ │ "id": "", -│ │ "name": "read", -│ │ "type": "toolCall" -│ │ } -│ │ ], -│ │ "role": "assistant" -│ │ }, -│ │ { -│ │ "content": [ -│ │ { -│ │ "text": "ENOENT: no such file or directory, open '/e2e/scenarios/flue-instrumentation/.agents/e2e-flue-skill.md'", -│ │ "type": "text" -│ │ } -│ │ ], -│ │ "isError": true, -│ │ "role": "toolResult", -│ │ "toolCallId": "", -│ │ "toolName": "read" -│ │ }, -│ │ { -│ │ "content": [ -│ │ { -│ │ "arguments": { -│ │ "path": ".agents" -│ │ }, -│ │ "id": "", -│ │ "name": "read", -│ │ "type": "toolCall" -│ │ } -│ │ ], -│ │ "role": "assistant" -│ │ }, -│ │ { -│ │ "content": [ -│ │ { -│ │ "text": "skills", -│ │ "type": "text" -│ │ } -│ │ ], -│ │ "isError": false, -│ │ "role": "toolResult", -│ │ "toolCallId": "", -│ │ "toolName": "read" -│ │ }, -│ │ { -│ │ "content": [ -│ │ { -│ │ "arguments": { -│ │ "path": ".agents/skills" -│ │ }, -│ │ "id": "", -│ │ "name": "read", -│ │ "type": "toolCall" -│ │ } -│ │ ], -│ │ "role": "assistant" -│ │ }, -│ │ { -│ │ "content": [ -│ │ { -│ │ "text": "e2e-flue-skill", -│ │ "type": "text" -│ │ } -│ │ ], -│ │ "isError": false, -│ │ "role": "toolResult", -│ │ "toolCallId": "", -│ │ "toolName": "read" -│ │ }, -│ │ { -│ │ "content": [ -│ │ { -│ │ "arguments": { -│ │ "path": ".agents/skills/e2e-flue-skill" -│ │ }, -│ │ "id": "", -│ │ "name": "read", -│ │ "type": "toolCall" -│ │ } -│ │ ], -│ │ "role": "assistant" -│ │ }, -│ │ { -│ │ "content": [ -│ │ { -│ │ "text": "SKILL.md", -│ │ "type": "text" -│ │ } -│ │ ], -│ │ "isError": false, -│ │ "role": "toolResult", -│ │ "toolCallId": "", -│ │ "toolName": "read" -│ │ }, -│ │ { -│ │ "content": [ -│ │ { -│ │ "arguments": { -│ │ "path": ".agents/skills/e2e-flue-skill/SKILL.md" -│ │ }, -│ │ "id": "", -│ │ "name": "read", -│ │ "type": "toolCall" -│ │ } -│ │ ], -│ │ "role": "assistant" -│ │ }, -│ │ { -│ │ "content": [ -│ │ { -│ │ "text": "---\nname: e2e-flue-skill\ndescription: Deterministic skill used by the Braintrust Flue instrumentation e2e test.\n---\n\nReturn the marker from the provided args exactly once. Output no other text.\n", -│ │ "type": "text" -│ │ } -│ │ ], -│ │ "isError": false, -│ │ "role": "toolResult", -│ │ "toolCallId": "", -│ │ "toolName": "read" -│ │ } -│ │ ] -│ │ output: { -│ │ "content": [ -│ │ { -│ │ "text": "SKILL_DONE", -│ │ "type": "text" -│ │ } -│ │ ], -│ │ "role": "assistant" -│ │ } -│ │ metadata: { -│ │ "flue.api": "anthropic-messages", -│ │ "flue.model": "claude-sonnet-4-5-20250929", -│ │ "flue.provider": "anthropic", -│ │ "flue.session": "skill", -│ │ "flue.stop_reason": "stop", -│ │ "flue.turn_purpose": "agent", -│ │ "model": "claude-sonnet-4-5-20250929", -│ │ "provider": "anthropic" -│ │ } -│ │ metrics: { -│ │ "completion_tokens": 8, -│ │ "duration_ms": 0, -│ │ "estimated_cost": 0.00129555, -│ │ "prompt_cache_creation_tokens": 135, -│ │ "prompt_cached_tokens": 2171, -│ │ "prompt_tokens": 2312, -│ │ "tokens": 2320 -│ │ } -│ ├── flue.task [task] -│ │ input: "Reply with exactly TASK_DONE and no other text." -│ │ output: "TASK_DONE" -│ │ metadata: { -│ │ "flue.session": "task", -│ │ "provider": "flue" -│ │ } -│ │ metrics: { -│ │ "duration_ms": 0 -│ │ } -│ │ └── flue.turn [llm] -│ │ input: [ -│ │ { -│ │ "content": [ -│ │ { -│ │ "text": "Reply with exactly TASK_DONE and no other text.", -│ │ "type": "text" -│ │ } -│ │ ], -│ │ "role": "user" -│ │ } -│ │ ] -│ │ output: { -│ │ "content": [ -│ │ { -│ │ "text": "TASK_DONE", -│ │ "textSignature": "{\"v\":1,\"id\":\"msg_09b32b2821e9a4d8016a184600019081929d34bb7d4c6a246f\"}", -│ │ "type": "text" -│ │ } -│ │ ], -│ │ "role": "assistant" -│ │ } -│ │ metadata: { -│ │ "flue.api": "openai-responses", -│ │ "flue.input_mode": "full", -│ │ "flue.model": "gpt-4o-mini", -│ │ "flue.provider": "openai", -│ │ "flue.session": "task:task:", -│ │ "flue.stop_reason": "stop", -│ │ "flue.turn_purpose": "agent", -│ │ "model": "gpt-4o-mini", -│ │ "provider": "openai" -│ │ } -│ │ metrics: { -│ │ "completion_tokens": 4, -│ │ "duration_ms": 0, -│ │ "estimated_cost": 0.00012299999999999998, -│ │ "prompt_cache_creation_tokens": 0, -│ │ "prompt_cached_tokens": 0, -│ │ "prompt_tokens": 804, -│ │ "tokens": 808 -│ │ } -│ └── flue.compact [task] -│ input: { -│ "estimatedTokens": 2950, -│ "reason": "manual" -│ } -│ output: { -│ "completed": true -│ } -│ metadata: { -│ "flue.operation": "compact", -│ "flue.session": "main", -│ "flue.workflow_name": "instrumentation", -│ "provider": "flue", -│ "scenario": "flue-instrumentation" -│ } -│ metrics: { -│ "duration_ms": 0 -│ } -│ └── compaction:manual [task] -│ input: { -│ "estimatedTokens": 2950, -│ "reason": "manual" -│ } -│ output: { -│ "messagesAfter": 2, -│ "messagesBefore": 8 -│ } -│ metadata: { -│ "flue.compaction_reason": "manual", -│ "flue.session": "main", -│ "provider": "flue" -│ } -│ metrics: { -│ "duration_ms": 0, -│ "messages_after": 2, -│ "messages_before": 8 -│ } -│ └── flue.turn [llm] -│ input: [ -│ { -│ "content": [ -│ { -│ "text": "\n[User]: Complete this instrumented research flow. Call exactly one tool per turn and wait for each tool result before choosing the next tool. Step 1: call lookup with query \"flue instrumentation\". Step 2: use the lookup result id as lookupId and call web_search with query \"Braintrust Flue reasoning stream instrumentation\". Step 3: use the first web_search result url and call summarize_source. After summarize_source returns, reply with exactly PROMPT_DONE and no other text.\n\n[Assistant thinking]: The user wants me to complete a research flow with exactly three steps:\n1. Call lookup with query \"flue instrumentation\"\n2. Call web_search with the lookupId from step 1 and query \"Braintrust Flue reasoning stream instrumentation\"\n3. Call summarize_source with the first URL from step 2\n4. Reply with exactly \"PROMPT_DONE\"\n\nThe user emphasizes I should call exactly one tool per turn and wait for each result. Let me start with step 1.\n\n[Assistant]: I'll complete this instrumented research flow step by step. Starting with Step 1:\n\n[Assistant tool calls]: lookup(query=\"flue instrumentation\")\n\n[Tool result]: {\"id\":\"flue-session-2026\",\"query\":\"flue instrumentation\",\"topic\":\"session instrumentation\"}\n\n[Assistant thinking]: Good, I got the lookup result with id \"flue-session-2026\". Now I need to proceed to step 2: call web_search with this lookupId and the query \"Braintrust Flue reasoning stream instrumentation\".\n\n[Assistant]: Now proceeding to Step 2:\n\n[Assistant tool calls]: web_search(lookupId=\"flue-session-2026\", query=\"Braintrust Flue reasoning stream instrumentation\")\n\n[Tool result]: {\"lookupId\":\"flue-session-2026\",\"query\":\"Braintrust Flue reasoning stream instrumentation\",\"results\":[{\"title\":\"Flue reasoning stream instrumentation\",\"url\":\"https://example.test/flue/reasoning-streams\"}]}\n\n[Assistant thinking]: Perfect! I got the web_search results with one result. The first (and only) result has the URL \"https://example.test/flue/reasoning-streams\". Now I need to proceed to step 3: call summarize_source with this URL.\n\n[Assistant]: Now proceeding to Step 3:\n\n[Assistant tool calls]: summarize_source(url=\"https://example.test/flue/reasoning-streams\")\n\n[Tool result]: {\"summary\":\"Flue emits reasoning, tool execution, and LLM turn events separately.\",\"url\":\"https://example.test/flue/reasoning-streams\"}\n\n\nThis is the PREFIX of a turn that was too large to keep. The SUFFIX (recent work) is retained.\n\nSummarize the prefix to provide context for the retained suffix:\n\n## Original Request\n[What did the user ask for in this turn?]\n\n## Early Progress\n- [Key decisions and work done in the prefix]\n\n## Context for Suffix\n- [Information needed to understand the retained recent work]\n\nBe concise. Focus on what's needed to understand the kept suffix.", -│ "type": "text" -│ } -│ ], -│ "role": "user" -│ } -│ ] -│ output: { -│ "content": [ -│ { -│ "text": "## Original Request\nThe user requested to complete an instrumented research flow consisting of three specific steps, involving calling various tools in a sequential manner.\n\n## Early Progress", -│ "textSignature": "{\"v\":1,\"id\":\"msg_01ecf68325f9e202016a184601309081a0878ef4054e950d32\"}", -│ "type": "text" -│ } -│ ], -│ "role": "assistant" -│ } -│ metadata: { -│ "flue.api": "openai-responses", -│ "flue.model": "gpt-4o-mini", -│ "flue.provider": "openai", -│ "flue.session": "main", -│ "flue.stop_reason": "stop", -│ "flue.turn_purpose": "compaction_prefix", -│ "model": "gpt-4o-mini", -│ "provider": "openai" -│ } -│ metrics: { -│ "completion_tokens": 0, -│ "duration_ms": 0, -│ "estimated_cost": 0, -│ "prompt_cache_creation_tokens": 0, -│ "prompt_cached_tokens": 0, -│ "prompt_tokens": 0, -│ "tokens": 0 -│ } -└── flue.prompt [task] - input: [ - { - "content": [ - { - "text": "Complete this instrumented research flow. Call exactly one tool per turn and wait for each tool result before choosing the next tool. Step 1: call lookup with query \"flue instrumentation\". Step 2: use the lookup result id as lookupId and call web_search with query \"Braintrust Flue reasoning stream instrumentation\". Step 3: use the first web_search result url and call summarize_source. After summarize_source returns, reply with exactly PROMPT_DONE and no other text.", - "type": "text" - } - ], - "role": "user" - } - ] - output: "PROMPT_DONE" - metadata: { - "flue.operation": "prompt", - "flue.session": "main", - "flue.workflow_name": "instrumentation", - "provider": "flue", - "scenario": "flue-instrumentation" - } - metrics: { - "duration_ms": 0 - } - ├── flue.turn [llm] - │ input: [ - │ { - │ "content": [ - │ { - │ "text": "Complete this instrumented research flow. Call exactly one tool per turn and wait for each tool result before choosing the next tool. Step 1: call lookup with query \"flue instrumentation\". Step 2: use the lookup result id as lookupId and call web_search with query \"Braintrust Flue reasoning stream instrumentation\". Step 3: use the first web_search result url and call summarize_source. After summarize_source returns, reply with exactly PROMPT_DONE and no other text.", - │ "type": "text" - │ } - │ ], - │ "role": "user" - │ } - │ ] - │ output: { - │ "content": [ - │ { - │ "thinking": "The user wants me to complete a research flow with exactly three steps:\n1. Call lookup with query \"flue instrumentation\"\n2. Call web_search with the lookupId from step 1 and query \"Braintrust Flue reasoning stream instrumentation\"\n3. Call summarize_source with the first URL from step 2\n4. Reply with exactly \"PROMPT_DONE\"\n\nThe user emphasizes I should call exactly one tool per turn and wait for each result. Let me start with step 1.", - │ "thinkingSignature": "EoQFCm4IDhgCKkD+AoKMhpXNPUJ5eANwM28uUCJPCE+AXh0ACw7aXniX+YN04zdJl3hlHMbtAuKhhczLiEeOJtu4KtVpea5u5NFdMhpjbGF1ZGUtc29ubmV0LTQtNS0yMDI1MDkyOTgAQgh0aGlua2luZxIMIOTWCNEtsiI9vF4SGgwPXC861uZx8lpOziEiMLsCukGIluD0U44degzYnHFc+BwXNIEB+yatSHlZE+a5smr9crZcft3OLmsIdXuaFSrDAyQxWd9oqfJdnvza9eWIaOCeNkVCjrTc/wotBwh5Xp8m9k/WKt7P4zzUJBuLQ1f6ZnsdWkL8OiL/2E7Up55z2+UuVWsg1Y90/x27YRFdQjv3RnztYp4bZ5oIHAlTJoCzWiBYEh/Z8c2O+wy6MHo1cp0T57OroB/4HAqRZxrjTOXpKYLmYKXAY5tU8DTd3A0/FfEhg3oPSbOhX+112szFCpm3BRNJYooUUhwSu1hO4H/6r7d/Yp9XIM4tBfzuDx83CC/YetdOFgnYkdliw0xaUGaj4o37rWIRctWHAMdlUc4NDA0Ljc1uirHDXkN8EJwWSoIjPSBTA/p/wgLrvRV5Yy9Z0dMoWOpgYF6t1H/tNAyY8ml1CA7IWyd3jnfTZ1/G3Y0xFDwJDFlb4/QKc67w36SSCa+X9mMoTxD/DYN249lsII+XX3lQ7F1zJ07THOtCyRirCaRhmE/kGwl5tiB1QHrWa9bNHxQxf9i92435JGKN053Fe14AR70MtBSzIU2/zbgvhB1vF5lZt2i46hNxfo4lXR495gEwdwC6+8HJnlEJAJkdeHfZKj+RiwXoiYKPKsSbHi83X8Hh32IifuglNxdlxVQYAQ==", - │ "type": "thinking" - │ }, - │ { - │ "text": "I'll complete this instrumented research flow step by step. Starting with Step 1:", - │ "type": "text" - │ }, - │ { - │ "arguments": { - │ "query": "flue instrumentation" - │ }, - │ "id": "", - │ "name": "lookup", - │ "type": "toolCall" - │ } - │ ], - │ "role": "assistant" - │ } - │ metadata: { - │ "flue.api": "anthropic-messages", - │ "flue.input_mode": "full", - │ "flue.model": "claude-sonnet-4-5-20250929", - │ "flue.provider": "anthropic", - │ "flue.session": "main", - │ "flue.stop_reason": "toolUse", - │ "flue.turn_purpose": "agent", - │ "model": "claude-sonnet-4-5-20250929", - │ "provider": "anthropic" - │ } - │ metrics: { - │ "completion_tokens": 196, - │ "duration_ms": 0, - │ "estimated_cost": 0.0113025, - │ "prompt_cache_creation_tokens": 2222, - │ "prompt_cached_tokens": 0, - │ "prompt_tokens": 2232, - │ "tokens": 2428 - │ } - ├── tool:lookup [tool] - │ input: { - │ "query": "flue instrumentation" - │ } - │ output: { - │ "content": [ - │ { - │ "text": "{\"id\":\"flue-session-2026\",\"query\":\"flue instrumentation\",\"topic\":\"session instrumentation\"}", - │ "type": "text" - │ } - │ ], - │ "details": { - │ "customTool": "lookup" - │ } - │ } - │ metadata: { - │ "flue.session": "main", - │ "flue.tool_name": "lookup", - │ "provider": "flue" - │ } - │ metrics: { - │ "duration_ms": 0 - │ } - ├── flue.turn [llm] - │ input: [ - │ { - │ "content": [ - │ { - │ "thinking": "The user wants me to complete a research flow with exactly three steps:\n1. Call lookup with query \"flue instrumentation\"\n2. Call web_search with the lookupId from step 1 and query \"Braintrust Flue reasoning stream instrumentation\"\n3. Call summarize_source with the first URL from step 2\n4. Reply with exactly \"PROMPT_DONE\"\n\nThe user emphasizes I should call exactly one tool per turn and wait for each result. Let me start with step 1.", - │ "thinkingSignature": "EoQFCm4IDhgCKkD+AoKMhpXNPUJ5eANwM28uUCJPCE+AXh0ACw7aXniX+YN04zdJl3hlHMbtAuKhhczLiEeOJtu4KtVpea5u5NFdMhpjbGF1ZGUtc29ubmV0LTQtNS0yMDI1MDkyOTgAQgh0aGlua2luZxIMIOTWCNEtsiI9vF4SGgwPXC861uZx8lpOziEiMLsCukGIluD0U44degzYnHFc+BwXNIEB+yatSHlZE+a5smr9crZcft3OLmsIdXuaFSrDAyQxWd9oqfJdnvza9eWIaOCeNkVCjrTc/wotBwh5Xp8m9k/WKt7P4zzUJBuLQ1f6ZnsdWkL8OiL/2E7Up55z2+UuVWsg1Y90/x27YRFdQjv3RnztYp4bZ5oIHAlTJoCzWiBYEh/Z8c2O+wy6MHo1cp0T57OroB/4HAqRZxrjTOXpKYLmYKXAY5tU8DTd3A0/FfEhg3oPSbOhX+112szFCpm3BRNJYooUUhwSu1hO4H/6r7d/Yp9XIM4tBfzuDx83CC/YetdOFgnYkdliw0xaUGaj4o37rWIRctWHAMdlUc4NDA0Ljc1uirHDXkN8EJwWSoIjPSBTA/p/wgLrvRV5Yy9Z0dMoWOpgYF6t1H/tNAyY8ml1CA7IWyd3jnfTZ1/G3Y0xFDwJDFlb4/QKc67w36SSCa+X9mMoTxD/DYN249lsII+XX3lQ7F1zJ07THOtCyRirCaRhmE/kGwl5tiB1QHrWa9bNHxQxf9i92435JGKN053Fe14AR70MtBSzIU2/zbgvhB1vF5lZt2i46hNxfo4lXR495gEwdwC6+8HJnlEJAJkdeHfZKj+RiwXoiYKPKsSbHi83X8Hh32IifuglNxdlxVQYAQ==", - │ "type": "thinking" - │ }, - │ { - │ "text": "I'll complete this instrumented research flow step by step. Starting with Step 1:", - │ "type": "text" - │ }, - │ { - │ "arguments": { - │ "query": "flue instrumentation" - │ }, - │ "id": "", - │ "name": "lookup", - │ "type": "toolCall" - │ } - │ ], - │ "role": "assistant" - │ }, - │ { - │ "content": [ - │ { - │ "text": "{\"id\":\"flue-session-2026\",\"query\":\"flue instrumentation\",\"topic\":\"session instrumentation\"}", - │ "type": "text" - │ } - │ ], - │ "isError": false, - │ "role": "toolResult", - │ "toolCallId": "", - │ "toolName": "lookup" - │ } - │ ] - │ output: { - │ "content": [ - │ { - │ "thinking": "Good, I got the lookup result with id \"flue-session-2026\". Now I need to proceed to step 2: call web_search with this lookupId and the query \"Braintrust Flue reasoning stream instrumentation\".", - │ "thinkingSignature": "EpEDCm4IDhgCKkAMahve1kB1j4njp1ZSzO8eCK4bb5Ex7MB6tjdoOXfuUDRfkBqvIGdwVly8GiA8GGYC7Y/RVfREXF6A6OXHTwFyMhpjbGF1ZGUtc29ubmV0LTQtNS0yMDI1MDkyOTgAQgh0aGlua2luZxIMLw+fsa4yo7a7ZSoIGgyaTdvH020n2dQj9bUiMB4OokY29w+uZb4rqDnKDbLXUqUj6U0f1QCGQF2ZBYDDfDJyM5Jv1xY3rQiUUjkhBirQAS4XivjvMnWI5zHBmpI/u/pq4aGdrdb3vUsJ/ne6R1d80JA/INGF8vLkQcZ5xxEK0jU4yRE+ldc2uzavmtoxIKAFD6bqKks1Qv6u+TIZtuW7tUB/7jFxDw93KmJNruMIa8c1J6pPUyBy3xE7QwgmU0kDfIfdnljxLoEBiMwtnxjCPj9lT7HHz4FkrhbnQTV4MvAQohCLjaSbOTd6LdLxwtJPm9mc26ID4eYN1gCCzyAxGXhqLHlzc23PafvQjsZliWnLhaiNMnG0YEo78XkuJQsYAQ==", - │ "type": "thinking" - │ }, - │ { - │ "text": "Now proceeding to Step 2:", - │ "type": "text" - │ }, - │ { - │ "arguments": { - │ "lookupId": "flue-session-2026", - │ "query": "Braintrust Flue reasoning stream instrumentation" - │ }, - │ "id": "", - │ "name": "web_search", - │ "type": "toolCall" - │ } - │ ], - │ "role": "assistant" - │ } - │ metadata: { - │ "flue.api": "anthropic-messages", - │ "flue.input_message_offset": 1, - │ "flue.input_mode": "delta", - │ "flue.model": "claude-sonnet-4-5-20250929", - │ "flue.provider": "anthropic", - │ "flue.session": "main", - │ "flue.stop_reason": "toolUse", - │ "flue.turn_purpose": "agent", - │ "model": "claude-sonnet-4-5-20250929", - │ "provider": "anthropic" - │ } - │ metrics: { - │ "completion_tokens": 158, - │ "duration_ms": 0, - │ "estimated_cost": 0.0039606, - │ "prompt_cache_creation_tokens": 236, - │ "prompt_cached_tokens": 2222, - │ "prompt_tokens": 2471, - │ "tokens": 2629 - │ } - ├── tool:web_search [tool] - │ input: { - │ "lookupId": "flue-session-2026", - │ "query": "Braintrust Flue reasoning stream instrumentation" - │ } - │ output: { - │ "content": [ - │ { - │ "text": "{\"lookupId\":\"flue-session-2026\",\"query\":\"Braintrust Flue reasoning stream instrumentation\",\"results\":[{\"title\":\"Flue reasoning stream instrumentation\",\"url\":\"https://example.test/flue/reasoning-streams\"}]}", - │ "type": "text" - │ } - │ ], - │ "details": { - │ "customTool": "web_search" - │ } - │ } - │ metadata: { - │ "flue.session": "main", - │ "flue.tool_name": "web_search", - │ "provider": "flue" - │ } - │ metrics: { - │ "duration_ms": 0 - │ } - ├── flue.turn [llm] - │ input: [ - │ { - │ "content": [ - │ { - │ "thinking": "Good, I got the lookup result with id \"flue-session-2026\". Now I need to proceed to step 2: call web_search with this lookupId and the query \"Braintrust Flue reasoning stream instrumentation\".", - │ "thinkingSignature": "EpEDCm4IDhgCKkAMahve1kB1j4njp1ZSzO8eCK4bb5Ex7MB6tjdoOXfuUDRfkBqvIGdwVly8GiA8GGYC7Y/RVfREXF6A6OXHTwFyMhpjbGF1ZGUtc29ubmV0LTQtNS0yMDI1MDkyOTgAQgh0aGlua2luZxIMLw+fsa4yo7a7ZSoIGgyaTdvH020n2dQj9bUiMB4OokY29w+uZb4rqDnKDbLXUqUj6U0f1QCGQF2ZBYDDfDJyM5Jv1xY3rQiUUjkhBirQAS4XivjvMnWI5zHBmpI/u/pq4aGdrdb3vUsJ/ne6R1d80JA/INGF8vLkQcZ5xxEK0jU4yRE+ldc2uzavmtoxIKAFD6bqKks1Qv6u+TIZtuW7tUB/7jFxDw93KmJNruMIa8c1J6pPUyBy3xE7QwgmU0kDfIfdnljxLoEBiMwtnxjCPj9lT7HHz4FkrhbnQTV4MvAQohCLjaSbOTd6LdLxwtJPm9mc26ID4eYN1gCCzyAxGXhqLHlzc23PafvQjsZliWnLhaiNMnG0YEo78XkuJQsYAQ==", - │ "type": "thinking" - │ }, - │ { - │ "text": "Now proceeding to Step 2:", - │ "type": "text" - │ }, - │ { - │ "arguments": { - │ "lookupId": "flue-session-2026", - │ "query": "Braintrust Flue reasoning stream instrumentation" - │ }, - │ "id": "", - │ "name": "web_search", - │ "type": "toolCall" - │ } - │ ], - │ "role": "assistant" - │ }, - │ { - │ "content": [ - │ { - │ "text": "{\"lookupId\":\"flue-session-2026\",\"query\":\"Braintrust Flue reasoning stream instrumentation\",\"results\":[{\"title\":\"Flue reasoning stream instrumentation\",\"url\":\"https://example.test/flue/reasoning-streams\"}]}", - │ "type": "text" - │ } - │ ], - │ "isError": false, - │ "role": "toolResult", - │ "toolCallId": "", - │ "toolName": "web_search" - │ } - │ ] - │ output: { - │ "content": [ - │ { - │ "thinking": "Perfect! I got the web_search results with one result. The first (and only) result has the URL \"https://example.test/flue/reasoning-streams\". Now I need to proceed to step 3: call summarize_source with this URL.", - │ "thinkingSignature": "EqQDCm4IDhgCKkAXv7EDAhWMcrBz11bUd5qxlzyy03CAGQfRgLHDE0uKzLATh1o2sedd8GxLwwQqV0CmLRfNxsdK/ZDp3Lks9iysMhpjbGF1ZGUtc29ubmV0LTQtNS0yMDI1MDkyOTgAQgh0aGlua2luZxIMXDMniOy5Dy0KI8x7GgzgHs7BYJMs6/N0ur8iMPe0SwnHhP39OjEu7P8ASwK9ffwa3VP67QOlPzOstRRdEZNd0mq+BVjpmBXB+QcAzCrjAeAh/UO/d7SfFmXjqwA3//thoL/TTlGa6W+fdOCFRikn65JWT4ZdVFetfqD+9/QCP/nXEwMsLnTBAxC4EGryrgs2YT+OWwmEJ7rHKFeDs5djhwSb2J2ZJO1kRvD9s2vCCDkImygYBSIVgx1Y0CZJHhzn2Vf39v0Fbth2wDZa+OqxivV9NmDZEjmgcM+tTRx1CCnaww8LZEJQ68v/dUJy7hhD4MrNcQ62evJYPfT3+4EKKWJ/7HI7KXz91//+YAkph63XstbJMFJ3AldAFiq3ecauXt5r42N5gmCmwZX+sI48ENLGGAE=", - │ "type": "thinking" - │ }, - │ { - │ "text": "Now proceeding to Step 3:", - │ "type": "text" - │ }, - │ { - │ "arguments": { - │ "url": "https://example.test/flue/reasoning-streams" - │ }, - │ "id": "", - │ "name": "summarize_source", - │ "type": "toolCall" - │ } - │ ], - │ "role": "assistant" - │ } - │ metadata: { - │ "flue.api": "anthropic-messages", - │ "flue.input_message_offset": 3, - │ "flue.input_mode": "delta", - │ "flue.model": "claude-sonnet-4-5-20250929", - │ "flue.provider": "anthropic", - │ "flue.session": "main", - │ "flue.stop_reason": "toolUse", - │ "flue.turn_purpose": "agent", - │ "model": "claude-sonnet-4-5-20250929", - │ "provider": "anthropic" - │ } - │ metrics: { - │ "completion_tokens": 138, - │ "duration_ms": 0, - │ "estimated_cost": 0.0037239000000000005, - │ "prompt_cache_creation_tokens": 234, - │ "prompt_cached_tokens": 2458, - │ "prompt_tokens": 2705, - │ "tokens": 2843 - │ } - ├── tool:summarize_source [tool] - │ input: { - │ "url": "https://example.test/flue/reasoning-streams" - │ } - │ output: { - │ "content": [ - │ { - │ "text": "{\"summary\":\"Flue emits reasoning, tool execution, and LLM turn events separately.\",\"url\":\"https://example.test/flue/reasoning-streams\"}", - │ "type": "text" - │ } - │ ], - │ "details": { - │ "customTool": "summarize_source" - │ } - │ } - │ metadata: { - │ "flue.session": "main", - │ "flue.tool_name": "summarize_source", - │ "provider": "flue" - │ } - │ metrics: { - │ "duration_ms": 0 - │ } - └── flue.turn [llm] - input: [ - { - "content": [ - { - "thinking": "Perfect! I got the web_search results with one result. The first (and only) result has the URL \"https://example.test/flue/reasoning-streams\". Now I need to proceed to step 3: call summarize_source with this URL.", - "thinkingSignature": "EqQDCm4IDhgCKkAXv7EDAhWMcrBz11bUd5qxlzyy03CAGQfRgLHDE0uKzLATh1o2sedd8GxLwwQqV0CmLRfNxsdK/ZDp3Lks9iysMhpjbGF1ZGUtc29ubmV0LTQtNS0yMDI1MDkyOTgAQgh0aGlua2luZxIMXDMniOy5Dy0KI8x7GgzgHs7BYJMs6/N0ur8iMPe0SwnHhP39OjEu7P8ASwK9ffwa3VP67QOlPzOstRRdEZNd0mq+BVjpmBXB+QcAzCrjAeAh/UO/d7SfFmXjqwA3//thoL/TTlGa6W+fdOCFRikn65JWT4ZdVFetfqD+9/QCP/nXEwMsLnTBAxC4EGryrgs2YT+OWwmEJ7rHKFeDs5djhwSb2J2ZJO1kRvD9s2vCCDkImygYBSIVgx1Y0CZJHhzn2Vf39v0Fbth2wDZa+OqxivV9NmDZEjmgcM+tTRx1CCnaww8LZEJQ68v/dUJy7hhD4MrNcQ62evJYPfT3+4EKKWJ/7HI7KXz91//+YAkph63XstbJMFJ3AldAFiq3ecauXt5r42N5gmCmwZX+sI48ENLGGAE=", - "type": "thinking" - }, - { - "text": "Now proceeding to Step 3:", - "type": "text" - }, - { - "arguments": { - "url": "https://example.test/flue/reasoning-streams" - }, - "id": "", - "name": "summarize_source", - "type": "toolCall" - } - ], - "role": "assistant" - }, - { - "content": [ - { - "text": "{\"summary\":\"Flue emits reasoning, tool execution, and LLM turn events separately.\",\"url\":\"https://example.test/flue/reasoning-streams\"}", - "type": "text" - } - ], - "isError": false, - "role": "toolResult", - "toolCallId": "", - "toolName": "summarize_source" - } - ] - output: { - "content": [ - { - "thinking": "Perfect! The summarize_source call has completed successfully. According to the instructions, I should now reply with exactly \"PROMPT_DONE\" and no other text.", - "thinkingSignature": "Eu8CCm4IDhgCKkAn2QOIIlYfvcs6ynsmhCslHDtBsrTA4CfmimK7zyz6+k6dZldV2JBtEBET41It0gFcGEKC0n0mAhaYVuLutpDDMhpjbGF1ZGUtc29ubmV0LTQtNS0yMDI1MDkyOTgAQgh0aGlua2luZxIM1xfLptxSF8rKzou8GgwRLuVmfiBYOT4CZIsiMAYz8TpvchMAWodP+T9uZ+dJ47MVdNv+p86nZXmT5d5EWlCroiAOFHHzJH2o0UjJQSquAX6wu+jIC8/DXwurGIIf2ToldPuJWF5HGj8kHMnwh777XS1NUFl1irM24rvP7kTAv3Dp8YEwb8t3g1EsicY7j+C5DzgwxVsPYCVOCgdMqQjtsqpL7AX5KQ9+hR1gK1DQjshv4Gze3hw/+C+9iptd3RlDIuwO0cm8NY+sVqeWVh3TtQujQlVt1rW/HfWjclrtT0LZSGwc6RypmfMMQi6lncIk/EPgy0XbS1plKW0tLBgB", - "type": "thinking" - }, - { - "text": "PROMPT_DONE", - "type": "text" - } - ], - "role": "assistant" - } - metadata: { - "flue.api": "anthropic-messages", - "flue.input_message_offset": 5, - "flue.input_mode": "delta", - "flue.model": "claude-sonnet-4-5-20250929", - "flue.provider": "anthropic", - "flue.session": "main", - "flue.stop_reason": "stop", - "flue.turn_purpose": "agent", - "model": "claude-sonnet-4-5-20250929", - "provider": "anthropic" - } - metrics: { - "completion_tokens": 51, - "duration_ms": 0, - "estimated_cost": 0.0023391, - "prompt_cache_creation_tokens": 194, - "prompt_cached_tokens": 2692, - "prompt_tokens": 2899, - "tokens": 2950 - } diff --git a/e2e/scenarios/flue-instrumentation/__snapshots__/flue-v0-8-latest-auto-hook.span-tree.json b/e2e/scenarios/flue-instrumentation/__snapshots__/flue-v0-8-latest-auto-hook.span-tree.json deleted file mode 100644 index 704887bdb..000000000 --- a/e2e/scenarios/flue-instrumentation/__snapshots__/flue-v0-8-latest-auto-hook.span-tree.json +++ /dev/null @@ -1,1662 +0,0 @@ -{ - "span_tree": [ - { - "name": "workflow:instrumentation", - "type": "task", - "children": [ - { - "name": "flue.skill", - "type": "task", - "children": [ - { - "name": "flue.turn", - "type": "llm", - "children": [ - { - "name": "flue.task", - "type": "task", - "children": [ - { - "name": "flue.turn", - "type": "llm", - "children": [], - "input": [ - { - "content": [ - { - "text": "Run the skill named \"e2e-flue-skill\" with arguments {\"marker\":\"SKILL_DONE\"}. Output only the marker and no extra text when finished.", - "type": "text" - } - ], - "role": "user" - } - ], - "output": { - "content": [ - { - "arguments": { - "agent": "e2e-flue-skill", - "cwd": ".", - "description": "Run e2e-flue-skill with marker SKILL_DONE", - "prompt": "Run the skill named \"e2e-flue-skill\" with arguments {\"marker\":\"SKILL_DONE\"}. Ensure completion and report the final output marker only." - }, - "id": "", - "name": "task", - "type": "toolCall" - } - ], - "role": "assistant" - }, - "metadata": { - "flue.api": "openai-responses", - "flue.input_mode": "full", - "flue.model": "gpt-5.4-nano", - "flue.provider": "openai", - "flue.session": "task:skill:", - "flue.stop_reason": "toolUse", - "flue.turn_purpose": "agent", - "model": "gpt-5.4-nano", - "provider": "openai" - }, - "metrics": { - "completion_tokens": 113, - "duration_ms": 0, - "estimated_cost": 0.00031225000000000003, - "prompt_cache_creation_tokens": 0, - "prompt_cached_tokens": 0, - "prompt_tokens": 855, - "tokens": 968 - } - }, - { - "name": "tool:task", - "type": "tool", - "children": [], - "input": { - "agent": "e2e-flue-skill", - "cwd": ".", - "description": "Run e2e-flue-skill with marker SKILL_DONE", - "prompt": "Run the skill named \"e2e-flue-skill\" with arguments {\"marker\":\"SKILL_DONE\"}. Ensure completion and report the final output marker only." - }, - "output": { - "content": [ - { - "text": "[flue] Subagent \"e2e-flue-skill\" is not declared. Available: (none).", - "type": "text" - } - ], - "details": {} - }, - "metadata": { - "flue.session": "task:skill:", - "flue.tool_name": "task", - "provider": "flue" - }, - "metrics": { - "duration_ms": 0 - }, - "error": "{\"content\":[{\"type\":\"text\",\"text\":\"[flue] Subagent \\\"e2e-flue-skill\\\" is not declared. Available: (none).\"}],\"details\":{}}" - }, - { - "name": "flue.turn", - "type": "llm", - "children": [], - "input": [ - { - "content": [ - { - "arguments": { - "agent": "e2e-flue-skill", - "cwd": ".", - "description": "Run e2e-flue-skill with marker SKILL_DONE", - "prompt": "Run the skill named \"e2e-flue-skill\" with arguments {\"marker\":\"SKILL_DONE\"}. Ensure completion and report the final output marker only." - }, - "id": "", - "name": "task", - "type": "toolCall" - } - ], - "role": "assistant" - }, - { - "content": [ - { - "text": "[flue] Subagent \"e2e-flue-skill\" is not declared. Available: (none).", - "type": "text" - } - ], - "isError": true, - "role": "toolResult", - "toolCallId": "", - "toolName": "task" - } - ], - "output": { - "content": [ - { - "arguments": { - "command": "find . -maxdepth 4 -name AGENTS.md -print", - "timeout": 100000 - }, - "id": "", - "name": "bash", - "type": "toolCall" - } - ], - "role": "assistant" - }, - "metadata": { - "flue.api": "openai-responses", - "flue.input_message_offset": 1, - "flue.input_mode": "delta", - "flue.model": "gpt-5.4-nano", - "flue.provider": "openai", - "flue.session": "task:skill:", - "flue.stop_reason": "toolUse", - "flue.turn_purpose": "agent", - "model": "gpt-5.4-nano", - "provider": "openai" - }, - "metrics": { - "completion_tokens": 94, - "duration_ms": 0, - "estimated_cost": 0.00031790000000000003, - "prompt_cache_creation_tokens": 0, - "prompt_cached_tokens": 0, - "prompt_tokens": 1002, - "tokens": 1096 - } - }, - { - "name": "tool:bash", - "type": "tool", - "children": [], - "input": { - "command": "find . -maxdepth 4 -name AGENTS.md -print", - "timeout": 100000 - }, - "output": { - "content": [ - { - "text": "(no output)", - "type": "text" - } - ], - "details": { - "command": "find . -maxdepth 4 -name AGENTS.md -print", - "exitCode": 0 - } - }, - "metadata": { - "flue.session": "task:skill:", - "flue.tool_name": "bash", - "provider": "flue" - }, - "metrics": { - "duration_ms": 0 - } - }, - { - "name": "flue.turn", - "type": "llm", - "children": [], - "input": [ - { - "content": [ - { - "arguments": { - "command": "find . -maxdepth 4 -name AGENTS.md -print", - "timeout": 100000 - }, - "id": "", - "name": "bash", - "type": "toolCall" - } - ], - "role": "assistant" - }, - { - "content": [ - { - "text": "(no output)", - "type": "text" - } - ], - "isError": false, - "role": "toolResult", - "toolCallId": "", - "toolName": "bash" - } - ], - "output": { - "content": [ - { - "arguments": { - "command": "find . -maxdepth 4 -type f -name AGENTS.md -print -exec sed -n '1,200p' {} \\;", - "timeout": 100000 - }, - "id": "", - "name": "bash", - "type": "toolCall" - } - ], - "role": "assistant" - }, - "metadata": { - "flue.api": "openai-responses", - "flue.input_message_offset": 3, - "flue.input_mode": "delta", - "flue.model": "gpt-5.4-nano", - "flue.provider": "openai", - "flue.session": "task:skill:", - "flue.stop_reason": "toolUse", - "flue.turn_purpose": "agent", - "model": "gpt-5.4-nano", - "provider": "openai" - }, - "metrics": { - "completion_tokens": 87, - "duration_ms": 0, - "estimated_cost": 0.00034815000000000003, - "prompt_cache_creation_tokens": 0, - "prompt_cached_tokens": 0, - "prompt_tokens": 1197, - "tokens": 1284 - } - }, - { - "name": "tool:bash", - "type": "tool", - "children": [], - "input": { - "command": "find . -maxdepth 4 -type f -name AGENTS.md -print -exec sed -n '1,200p' {} \\;", - "timeout": 100000 - }, - "output": { - "content": [ - { - "text": "(no output)", - "type": "text" - } - ], - "details": { - "command": "find . -maxdepth 4 -type f -name AGENTS.md -print -exec sed -n '1,200p' {} \\;", - "exitCode": 0 - } - }, - "metadata": { - "flue.session": "task:skill:", - "flue.tool_name": "bash", - "provider": "flue" - }, - "metrics": { - "duration_ms": 0 - } - }, - { - "name": "flue.turn", - "type": "llm", - "children": [], - "input": [ - { - "content": [ - { - "arguments": { - "command": "find . -maxdepth 4 -type f -name AGENTS.md -print -exec sed -n '1,200p' {} \\;", - "timeout": 100000 - }, - "id": "", - "name": "bash", - "type": "toolCall" - } - ], - "role": "assistant" - }, - { - "content": [ - { - "text": "(no output)", - "type": "text" - } - ], - "isError": false, - "role": "toolResult", - "toolCallId": "", - "toolName": "bash" - } - ], - "output": { - "content": [ - { - "arguments": { - "command": "find . -maxdepth 6 -type f -name 'AGENTS.md' -print", - "timeout": 100000 - }, - "id": "", - "name": "bash", - "type": "toolCall" - } - ], - "role": "assistant" - }, - "metadata": { - "flue.api": "openai-responses", - "flue.input_message_offset": 5, - "flue.input_mode": "delta", - "flue.model": "gpt-5.4-nano", - "flue.provider": "openai", - "flue.session": "task:skill:", - "flue.stop_reason": "toolUse", - "flue.turn_purpose": "agent", - "model": "gpt-5.4-nano", - "provider": "openai" - }, - "metrics": { - "completion_tokens": 58, - "duration_ms": 0, - "estimated_cost": 0.0003321, - "prompt_cache_creation_tokens": 0, - "prompt_cached_tokens": 0, - "prompt_tokens": 1298, - "tokens": 1356 - } - }, - { - "name": "tool:bash", - "type": "tool", - "children": [], - "input": { - "command": "find . -maxdepth 6 -type f -name 'AGENTS.md' -print", - "timeout": 100000 - }, - "output": { - "content": [ - { - "text": "(no output)", - "type": "text" - } - ], - "details": { - "command": "find . -maxdepth 6 -type f -name 'AGENTS.md' -print", - "exitCode": 0 - } - }, - "metadata": { - "flue.session": "task:skill:", - "flue.tool_name": "bash", - "provider": "flue" - }, - "metrics": { - "duration_ms": 0 - } - }, - { - "name": "flue.turn", - "type": "llm", - "children": [], - "input": [ - { - "content": [ - { - "arguments": { - "command": "find . -maxdepth 6 -type f -name 'AGENTS.md' -print", - "timeout": 100000 - }, - "id": "", - "name": "bash", - "type": "toolCall" - } - ], - "role": "assistant" - }, - { - "content": [ - { - "text": "(no output)", - "type": "text" - } - ], - "isError": false, - "role": "toolResult", - "toolCallId": "", - "toolName": "bash" - } - ], - "output": { - "content": [ - { - "arguments": { - "command": "find . -maxdepth 5 -type f -name '*flue*skill*' -o -name '*skill*' | head", - "timeout": 100000 - }, - "id": "", - "name": "bash", - "type": "toolCall" - } - ], - "role": "assistant" - }, - "metadata": { - "flue.api": "openai-responses", - "flue.input_message_offset": 7, - "flue.input_mode": "delta", - "flue.model": "gpt-5.4-nano", - "flue.provider": "openai", - "flue.session": "task:skill:", - "flue.stop_reason": "toolUse", - "flue.turn_purpose": "agent", - "model": "gpt-5.4-nano", - "provider": "openai" - }, - "metrics": { - "completion_tokens": 68, - "duration_ms": 0, - "estimated_cost": 0.00035900000000000005, - "prompt_cache_creation_tokens": 0, - "prompt_cached_tokens": 0, - "prompt_tokens": 1370, - "tokens": 1438 - } - }, - { - "name": "tool:bash", - "type": "tool", - "children": [], - "input": { - "command": "find . -maxdepth 5 -type f -name '*flue*skill*' -o -name '*skill*' | head", - "timeout": 100000 - }, - "output": { - "content": [ - { - "text": "./.agents/skills\n./.agents/skills/e2e-flue-skill", - "type": "text" - } - ], - "details": { - "command": "find . -maxdepth 5 -type f -name '*flue*skill*' -o -name '*skill*' | head", - "exitCode": 0 - } - }, - "metadata": { - "flue.session": "task:skill:", - "flue.tool_name": "bash", - "provider": "flue" - }, - "metrics": { - "duration_ms": 0 - } - }, - { - "name": "flue.turn", - "type": "llm", - "children": [], - "input": [ - { - "content": [ - { - "arguments": { - "command": "find . -maxdepth 5 -type f -name '*flue*skill*' -o -name '*skill*' | head", - "timeout": 100000 - }, - "id": "", - "name": "bash", - "type": "toolCall" - } - ], - "role": "assistant" - }, - { - "content": [ - { - "text": "./.agents/skills\n./.agents/skills/e2e-flue-skill", - "type": "text" - } - ], - "isError": false, - "role": "toolResult", - "toolCallId": "", - "toolName": "bash" - } - ], - "output": { - "content": [ - { - "arguments": { - "command": "find ./.agents/skills/e2e-flue-skill -maxdepth 2 -type f -print", - "timeout": 100000 - }, - "id": "", - "name": "bash", - "type": "toolCall" - } - ], - "role": "assistant" - }, - "metadata": { - "flue.api": "openai-responses", - "flue.input_message_offset": 9, - "flue.input_mode": "delta", - "flue.model": "gpt-5.4-nano", - "flue.provider": "openai", - "flue.session": "task:skill:", - "flue.stop_reason": "toolUse", - "flue.turn_purpose": "agent", - "model": "gpt-5.4-nano", - "provider": "openai" - }, - "metrics": { - "completion_tokens": 109, - "duration_ms": 0, - "estimated_cost": 0.00043885000000000007, - "prompt_cache_creation_tokens": 0, - "prompt_cached_tokens": 0, - "prompt_tokens": 1513, - "tokens": 1622 - } - }, - { - "name": "tool:bash", - "type": "tool", - "children": [], - "input": { - "command": "find ./.agents/skills/e2e-flue-skill -maxdepth 2 -type f -print", - "timeout": 100000 - }, - "output": { - "content": [ - { - "text": "./.agents/skills/e2e-flue-skill/SKILL.md", - "type": "text" - } - ], - "details": { - "command": "find ./.agents/skills/e2e-flue-skill -maxdepth 2 -type f -print", - "exitCode": 0 - } - }, - "metadata": { - "flue.session": "task:skill:", - "flue.tool_name": "bash", - "provider": "flue" - }, - "metrics": { - "duration_ms": 0 - } - }, - { - "name": "flue.turn", - "type": "llm", - "children": [], - "input": [ - { - "content": [ - { - "arguments": { - "command": "find ./.agents/skills/e2e-flue-skill -maxdepth 2 -type f -print", - "timeout": 100000 - }, - "id": "", - "name": "bash", - "type": "toolCall" - } - ], - "role": "assistant" - }, - { - "content": [ - { - "text": "./.agents/skills/e2e-flue-skill/SKILL.md", - "type": "text" - } - ], - "isError": false, - "role": "toolResult", - "toolCallId": "", - "toolName": "bash" - } - ], - "output": { - "content": [ - { - "arguments": { - "limit": 200, - "offset": 1, - "path": "./.agents/skills/e2e-flue-skill/SKILL.md" - }, - "id": "", - "name": "read", - "type": "toolCall" - } - ], - "role": "assistant" - }, - "metadata": { - "flue.api": "openai-responses", - "flue.input_message_offset": 11, - "flue.input_mode": "delta", - "flue.model": "gpt-5.4-nano", - "flue.provider": "openai", - "flue.session": "task:skill:", - "flue.stop_reason": "toolUse", - "flue.turn_purpose": "agent", - "model": "gpt-5.4-nano", - "provider": "openai" - }, - "metrics": { - "completion_tokens": 64, - "duration_ms": 0, - "estimated_cost": 0.00015592, - "prompt_cache_creation_tokens": 0, - "prompt_cached_tokens": 1536, - "prompt_tokens": 1762, - "tokens": 1826 - } - }, - { - "name": "tool:read", - "type": "tool", - "children": [], - "input": { - "limit": 200, - "offset": 1, - "path": "./.agents/skills/e2e-flue-skill/SKILL.md" - }, - "output": { - "content": [ - { - "text": "---\nname: e2e-flue-skill\ndescription: Deterministic skill used by the Braintrust Flue instrumentation e2e test.\n---\n\nReturn the marker from the provided args exactly once. Output no other text.\n", - "type": "text" - } - ], - "details": { - "lines": 7, - "path": "./.agents/skills/e2e-flue-skill/SKILL.md" - } - }, - "metadata": { - "flue.session": "task:skill:", - "flue.tool_name": "read", - "provider": "flue" - }, - "metrics": { - "duration_ms": 0 - } - }, - { - "name": "flue.turn", - "type": "llm", - "children": [], - "input": [ - { - "content": [ - { - "arguments": { - "limit": 200, - "offset": 1, - "path": "./.agents/skills/e2e-flue-skill/SKILL.md" - }, - "id": "", - "name": "read", - "type": "toolCall" - } - ], - "role": "assistant" - }, - { - "content": [ - { - "text": "---\nname: e2e-flue-skill\ndescription: Deterministic skill used by the Braintrust Flue instrumentation e2e test.\n---\n\nReturn the marker from the provided args exactly once. Output no other text.\n", - "type": "text" - } - ], - "isError": false, - "role": "toolResult", - "toolCallId": "", - "toolName": "read" - } - ], - "output": { - "content": [ - { - "arguments": { - "command": "cat ./package.json", - "timeout": 100000 - }, - "id": "", - "name": "bash", - "type": "toolCall" - } - ], - "role": "assistant" - }, - "metadata": { - "flue.api": "openai-responses", - "flue.input_message_offset": 13, - "flue.input_mode": "delta", - "flue.model": "gpt-5.4-nano", - "flue.provider": "openai", - "flue.session": "task:skill:", - "flue.stop_reason": "toolUse", - "flue.turn_purpose": "agent", - "model": "gpt-5.4-nano", - "provider": "openai" - }, - "metrics": { - "completion_tokens": 90, - "duration_ms": 0, - "estimated_cost": 0.00021242000000000003, - "prompt_cache_creation_tokens": 0, - "prompt_cached_tokens": 1536, - "prompt_tokens": 1882, - "tokens": 1972 - } - }, - { - "name": "tool:bash", - "type": "tool", - "children": [], - "input": { - "command": "cat ./package.json", - "timeout": 100000 - }, - "output": { - "content": [ - { - "text": "{\n \"name\": \"flue-instrumentation-scenario\",\n \"private\": true,\n \"type\": \"module\",\n \"braintrustScenario\": {\n \"bump\": {\n \"dependencies\": {\n \"flue-cli-v0-8-latest\": {\n \"package\": \"@flue/cli\",\n \"range\": \">=0.8.0 <0.9.0\"\n },\n \"flue-cli-v1-latest\": {\n \"package\": \"@flue/cli\",\n \"range\": \">=1.0.0-beta.3 <2.0.0-0\",\n \"allowPrerelease\": true\n },\n \"flue-runtime-v0-8-latest\": {\n \"package\": \"@flue/runtime\",\n \"range\": \">=0.8.0 <0.9.0\"\n },\n \"flue-runtime-v1-latest\": {\n \"package\": \"@flue/runtime\",\n \"range\": \">=1.0.0-beta.3 <2.0.0-0\",\n \"allowPrerelease\": true\n },\n \"flue-runtime-v2-latest\": {\n \"package\": \"@flue/runtime\",\n \"range\": \">=2.0.0 <3.0.0\"\n }\n }\n }\n },\n \"dependencies\": {\n \"@flue/cli\": \"0.8.0\",\n \"@flue/runtime\": \"0.8.0\",\n \"flue-cli-v0-8-latest\": \"npm:@flue/cli@0.8.1\",\n \"flue-cli-v1\": \"npm:@flue/cli@1.0.0-beta.3\",\n \"flue-cli-v1-latest\": \"npm:@flue/cli@1.0.0-beta.9\",\n \"flue-runtime-v0-8-latest\": \"npm:@flue/runtime@0.8.1\",\n \"flue-runtime-v1\": \"npm:@flue/runtime@1.0.0-beta.3\",\n \"flue-runtime-v1-latest\": \"npm:@flue/runtime@1.0.0-beta.9\",\n \"flue-runtime-v2\": \"npm:@flue/runtime@2.0.0\",\n \"flue-runtime-v2-latest\": \"npm:@flue/runtime@2.0.1\",\n \"pi-ai-v2\": \"npm:@earendil-works/pi-ai@0.83.0\",\n \"valibot\": \"1.4.2\"\n },\n \"pnpm\": {\n \"overrides\": {\n \"@flue/runtime@0.8.0>@earendil-works/pi-agent-core\": \"0.75.4\",\n \"@flue/runtime@0.8.0>@earendil-works/pi-ai\": \"0.75.4\",\n \"@flue/runtime@0.8.1>@earendil-works/pi-agent-core\": \"0.75.4\",\n \"@flue/runtime@0.8.1>@earendil-works/pi-ai\": \"0.75.4\",\n \"@hono/node-server@>=2.0.0 <2.0.10\": \"^2.0.10\",\n \"fast-uri@<3.1.5\": \"3.1.8\",\n \"brace-expansion@>=3.0.0 <5.0.9\": \"5.0.12\",\n \"postcss@<8.5.18\": \"^8.5.18\",\n \"valibot@<1.4.2\": \"^1.4.2\",\n \"ip-address@<10.2.2\": \"10.7.1\",\n \"undici@<7.29.0\": \"7.29.1\",\n \"hono@<4.12.34\": \"4.13.7\",\n \"@hono/node-server@<1.19.15\": \"^1.19.15\",\n \"nanoid@<3.3.18\": \"^3.3.18\",\n \"js-yaml@3\": \"^3.15.1\",\n \"js-yaml@4\": \"^4.3.1\",\n \"sharp@<0.35.0\": \"0.35.5\",\n \"ip-address@>=10.0.0 <10.7.1\": \"10.7.1\",\n \"fast-uri@>=3.0.0 <3.1.8\": \"3.1.8\",\n \"undici@>=7.0.0 <7.29.1\": \"7.29.1\",\n \"js-yaml@>=5.0.0 <5.4.1\": \"5.4.1\",\n \"brace-expansion@>=5.0.0 <5.0.12\": \"5.0.12\",\n \"hono@>=4.0.0 <4.13.7\": \"4.13.7\",\n \"proxy-addr@>=2.0.0 <2.0.8\": \"2.0.8\",\n \"source-map-js@>=1.0.0 <1.2.2\": \"1.2.2\",\n \"smol-toml@>=1.0.0 <1.9.0\": \"1.9.0\",\n \"sharp@>=0.35.0 <0.35.5\": \"0.35.5\",\n \"@modelcontextprotocol/sdk@>=1.0.0 <1.31.0\": \"1.31.0\",\n \"@modelcontextprotocol/client@>=2.0.0 <2.2.0\": \"2.2.0\"\n }\n }\n}", - "type": "text" - } - ], - "details": { - "command": "cat ./package.json", - "exitCode": 0 - } - }, - "metadata": { - "flue.session": "task:skill:", - "flue.tool_name": "bash", - "provider": "flue" - }, - "metrics": { - "duration_ms": 0 - } - }, - { - "name": "flue.turn", - "type": "llm", - "children": [], - "input": [ - { - "content": [ - { - "arguments": { - "command": "cat ./package.json", - "timeout": 100000 - }, - "id": "", - "name": "bash", - "type": "toolCall" - } - ], - "role": "assistant" - }, - { - "content": [ - { - "text": "{\n \"name\": \"flue-instrumentation-scenario\",\n \"private\": true,\n \"type\": \"module\",\n \"braintrustScenario\": {\n \"bump\": {\n \"dependencies\": {\n \"flue-cli-v0-8-latest\": {\n \"package\": \"@flue/cli\",\n \"range\": \">=0.8.0 <0.9.0\"\n },\n \"flue-cli-v1-latest\": {\n \"package\": \"@flue/cli\",\n \"range\": \">=1.0.0-beta.3 <2.0.0-0\",\n \"allowPrerelease\": true\n },\n \"flue-runtime-v0-8-latest\": {\n \"package\": \"@flue/runtime\",\n \"range\": \">=0.8.0 <0.9.0\"\n },\n \"flue-runtime-v1-latest\": {\n \"package\": \"@flue/runtime\",\n \"range\": \">=1.0.0-beta.3 <2.0.0-0\",\n \"allowPrerelease\": true\n },\n \"flue-runtime-v2-latest\": {\n \"package\": \"@flue/runtime\",\n \"range\": \">=2.0.0 <3.0.0\"\n }\n }\n }\n },\n \"dependencies\": {\n \"@flue/cli\": \"0.8.0\",\n \"@flue/runtime\": \"0.8.0\",\n \"flue-cli-v0-8-latest\": \"npm:@flue/cli@0.8.1\",\n \"flue-cli-v1\": \"npm:@flue/cli@1.0.0-beta.3\",\n \"flue-cli-v1-latest\": \"npm:@flue/cli@1.0.0-beta.9\",\n \"flue-runtime-v0-8-latest\": \"npm:@flue/runtime@0.8.1\",\n \"flue-runtime-v1\": \"npm:@flue/runtime@1.0.0-beta.3\",\n \"flue-runtime-v1-latest\": \"npm:@flue/runtime@1.0.0-beta.9\",\n \"flue-runtime-v2\": \"npm:@flue/runtime@2.0.0\",\n \"flue-runtime-v2-latest\": \"npm:@flue/runtime@2.0.1\",\n \"pi-ai-v2\": \"npm:@earendil-works/pi-ai@0.83.0\",\n \"valibot\": \"1.4.2\"\n },\n \"pnpm\": {\n \"overrides\": {\n \"@flue/runtime@0.8.0>@earendil-works/pi-agent-core\": \"0.75.4\",\n \"@flue/runtime@0.8.0>@earendil-works/pi-ai\": \"0.75.4\",\n \"@flue/runtime@0.8.1>@earendil-works/pi-agent-core\": \"0.75.4\",\n \"@flue/runtime@0.8.1>@earendil-works/pi-ai\": \"0.75.4\",\n \"@hono/node-server@>=2.0.0 <2.0.10\": \"^2.0.10\",\n \"fast-uri@<3.1.5\": \"3.1.8\",\n \"brace-expansion@>=3.0.0 <5.0.9\": \"5.0.12\",\n \"postcss@<8.5.18\": \"^8.5.18\",\n \"valibot@<1.4.2\": \"^1.4.2\",\n \"ip-address@<10.2.2\": \"10.7.1\",\n \"undici@<7.29.0\": \"7.29.1\",\n \"hono@<4.12.34\": \"4.13.7\",\n \"@hono/node-server@<1.19.15\": \"^1.19.15\",\n \"nanoid@<3.3.18\": \"^3.3.18\",\n \"js-yaml@3\": \"^3.15.1\",\n \"js-yaml@4\": \"^4.3.1\",\n \"sharp@<0.35.0\": \"0.35.5\",\n \"ip-address@>=10.0.0 <10.7.1\": \"10.7.1\",\n \"fast-uri@>=3.0.0 <3.1.8\": \"3.1.8\",\n \"undici@>=7.0.0 <7.29.1\": \"7.29.1\",\n \"js-yaml@>=5.0.0 <5.4.1\": \"5.4.1\",\n \"brace-expansion@>=5.0.0 <5.0.12\": \"5.0.12\",\n \"hono@>=4.0.0 <4.13.7\": \"4.13.7\",\n \"proxy-addr@>=2.0.0 <2.0.8\": \"2.0.8\",\n \"source-map-js@>=1.0.0 <1.2.2\": \"1.2.2\",\n \"smol-toml@>=1.0.0 <1.9.0\": \"1.9.0\",\n \"sharp@>=0.35.0 <0.35.5\": \"0.35.5\",\n \"@modelcontextprotocol/sdk@>=1.0.0 <1.31.0\": \"1.31.0\",\n \"@modelcontextprotocol/client@>=2.0.0 <2.2.0\": \"2.2.0\"\n }\n }\n}", - "type": "text" - } - ], - "isError": false, - "role": "toolResult", - "toolCallId": "", - "toolName": "bash" - } - ], - "output": { - "content": [ - { - "arguments": { - "command": "printf SKILL_DONE", - "timeout": 100000 - }, - "id": "", - "name": "bash", - "type": "toolCall" - } - ], - "role": "assistant" - }, - "metadata": { - "flue.api": "openai-responses", - "flue.input_message_offset": 15, - "flue.input_mode": "delta", - "flue.model": "gpt-5.4-nano", - "flue.provider": "openai", - "flue.session": "task:skill:", - "flue.stop_reason": "toolUse", - "flue.turn_purpose": "agent", - "model": "gpt-5.4-nano", - "provider": "openai" - }, - "metrics": { - "completion_tokens": 59, - "duration_ms": 0, - "estimated_cost": 0.0004814700000000001, - "prompt_cache_creation_tokens": 0, - "prompt_cached_tokens": 1536, - "prompt_tokens": 3421, - "tokens": 3480 - } - }, - { - "name": "tool:bash", - "type": "tool", - "children": [], - "input": { - "command": "printf SKILL_DONE", - "timeout": 100000 - }, - "output": { - "content": [ - { - "text": "SKILL_DONE", - "type": "text" - } - ], - "details": { - "command": "printf SKILL_DONE", - "exitCode": 0 - } - }, - "metadata": { - "flue.session": "task:skill:", - "flue.tool_name": "bash", - "provider": "flue" - }, - "metrics": { - "duration_ms": 0 - } - }, - { - "name": "flue.turn", - "type": "llm", - "children": [], - "input": [ - { - "content": [ - { - "arguments": { - "command": "printf SKILL_DONE", - "timeout": 100000 - }, - "id": "", - "name": "bash", - "type": "toolCall" - } - ], - "role": "assistant" - }, - { - "content": [ - { - "text": "SKILL_DONE", - "type": "text" - } - ], - "isError": false, - "role": "toolResult", - "toolCallId": "", - "toolName": "bash" - } - ], - "output": { - "content": [ - { - "text": "SKILL_DONE", - "textSignature": "{\"v\":1,\"id\":\"msg_054a16a2f2ea3217016a705b70a41481a38ece40ecddf69512\",\"phase\":\"final_answer\"}", - "type": "text" - } - ], - "role": "assistant" - }, - "metadata": { - "flue.api": "openai-responses", - "flue.input_message_offset": 17, - "flue.input_mode": "delta", - "flue.model": "gpt-5.4-nano", - "flue.provider": "openai", - "flue.session": "task:skill:", - "flue.stop_reason": "stop", - "flue.turn_purpose": "agent", - "model": "gpt-5.4-nano", - "provider": "openai" - }, - "metrics": { - "completion_tokens": 7, - "duration_ms": 0, - "estimated_cost": 0.00031035000000000004, - "prompt_cache_creation_tokens": 0, - "prompt_cached_tokens": 2560, - "prompt_tokens": 3812, - "tokens": 3819 - } - } - ], - "input": "Run the skill named \"e2e-flue-skill\" with arguments {\"marker\":\"SKILL_DONE\"}. Output only the marker and no extra text when finished.", - "output": "SKILL_DONE", - "metadata": { - "flue.session": "skill", - "provider": "flue" - }, - "metrics": { - "duration_ms": 0 - } - } - ], - "input": [ - { - "content": [ - { - "text": "Run the skill named \"e2e-flue-skill\".\n\nArguments:\n{\n \"marker\": \"SKILL_DONE\"\n}", - "type": "text" - } - ], - "role": "user" - } - ], - "output": { - "content": [ - { - "arguments": { - "cwd": ".", - "description": "Run the deterministic Flue instrumentation e2e test skill e2e-flue-skill.", - "prompt": "Run the skill named \"e2e-flue-skill\" with arguments {\"marker\":\"SKILL_DONE\"}. Output only the marker and no extra text when finished." - }, - "id": "", - "name": "task", - "type": "toolCall" - } - ], - "role": "assistant" - }, - "metadata": { - "flue.api": "openai-responses", - "flue.model": "gpt-5.4-nano", - "flue.provider": "openai", - "flue.session": "skill", - "flue.stop_reason": "toolUse", - "flue.turn_purpose": "agent", - "model": "gpt-5.4-nano", - "provider": "openai" - }, - "metrics": { - "completion_tokens": 109, - "duration_ms": 0, - "estimated_cost": 0.00030565000000000003, - "prompt_cache_creation_tokens": 0, - "prompt_cached_tokens": 0, - "prompt_tokens": 847, - "tokens": 956 - } - }, - { - "name": "tool:task", - "type": "tool", - "children": [], - "input": { - "cwd": ".", - "description": "Run the deterministic Flue instrumentation e2e test skill e2e-flue-skill.", - "prompt": "Run the skill named \"e2e-flue-skill\" with arguments {\"marker\":\"SKILL_DONE\"}. Output only the marker and no extra text when finished." - }, - "output": { - "content": [ - { - "text": "SKILL_DONE", - "type": "text" - } - ], - "details": { - "cwd": ".", - "messageId": "", - "session": "task:skill:", - "taskId": "" - } - }, - "metadata": { - "flue.session": "skill", - "flue.tool_name": "task", - "provider": "flue" - }, - "metrics": { - "duration_ms": 0 - } - }, - { - "name": "flue.turn", - "type": "llm", - "children": [], - "input": [ - { - "content": [ - { - "text": "Run the skill named \"e2e-flue-skill\".\n\nArguments:\n{\n \"marker\": \"SKILL_DONE\"\n}", - "type": "text" - } - ], - "role": "user" - }, - { - "content": [ - { - "arguments": { - "cwd": ".", - "description": "Run the deterministic Flue instrumentation e2e test skill e2e-flue-skill.", - "prompt": "Run the skill named \"e2e-flue-skill\" with arguments {\"marker\":\"SKILL_DONE\"}. Output only the marker and no extra text when finished." - }, - "id": "", - "name": "task", - "type": "toolCall" - } - ], - "role": "assistant" - }, - { - "content": [ - { - "text": "SKILL_DONE", - "type": "text" - } - ], - "isError": false, - "role": "toolResult", - "toolCallId": "", - "toolName": "task" - } - ], - "output": { - "content": [ - { - "text": "SKILL_DONE", - "textSignature": "{\"v\":1,\"id\":\"msg_0f099b3fce0721fd016a705b71558881a38c53a767e09d9e31\",\"phase\":\"final_answer\"}", - "type": "text" - } - ], - "role": "assistant" - }, - "metadata": { - "flue.api": "openai-responses", - "flue.model": "gpt-5.4-nano", - "flue.provider": "openai", - "flue.session": "skill", - "flue.stop_reason": "stop", - "flue.turn_purpose": "agent", - "model": "gpt-5.4-nano", - "provider": "openai" - }, - "metrics": { - "completion_tokens": 7, - "duration_ms": 0, - "estimated_cost": 0.00020255000000000002, - "prompt_cache_creation_tokens": 0, - "prompt_cached_tokens": 0, - "prompt_tokens": 969, - "tokens": 976 - } - } - ], - "input": [ - { - "content": [ - { - "text": "Run the skill named \"e2e-flue-skill\".\n\nArguments:\n{\n \"marker\": \"SKILL_DONE\"\n}", - "type": "text" - } - ], - "role": "user" - } - ], - "output": "SKILL_DONE", - "metadata": { - "flue.operation": "skill", - "flue.session": "skill", - "flue.workflow_name": "instrumentation", - "provider": "flue", - "scenario": "flue-instrumentation" - }, - "metrics": { - "duration_ms": 0 - } - }, - { - "name": "flue.task", - "type": "task", - "children": [ - { - "name": "flue.turn", - "type": "llm", - "children": [], - "input": [ - { - "content": [ - { - "text": "Reply with exactly TASK_DONE and no other text.", - "type": "text" - } - ], - "role": "user" - } - ], - "output": { - "content": [ - { - "text": "TASK_DONE", - "textSignature": "{\"v\":1,\"id\":\"msg_0f3adfdb1510c165016a705b72843c81a0b30d242da683a882\"}", - "type": "text" - } - ], - "role": "assistant" - }, - "metadata": { - "flue.api": "openai-responses", - "flue.input_mode": "full", - "flue.model": "gpt-4o-mini", - "flue.provider": "openai", - "flue.session": "task:task:", - "flue.stop_reason": "stop", - "flue.turn_purpose": "agent", - "model": "gpt-4o-mini", - "provider": "openai" - }, - "metrics": { - "completion_tokens": 4, - "duration_ms": 0, - "estimated_cost": 0.0001263, - "prompt_cache_creation_tokens": 0, - "prompt_cached_tokens": 0, - "prompt_tokens": 826, - "tokens": 830 - } - } - ], - "input": "Reply with exactly TASK_DONE and no other text.", - "output": "TASK_DONE", - "metadata": { - "flue.session": "task", - "provider": "flue" - }, - "metrics": { - "duration_ms": 0 - } - }, - { - "name": "flue.compact", - "type": "task", - "children": [ - { - "name": "compaction:manual", - "type": "task", - "children": [ - { - "name": "flue.turn", - "type": "llm", - "children": [], - "input": [ - { - "content": [ - { - "text": "\n[User]: Complete this instrumented research flow. Call exactly one tool per turn and wait for each tool result before choosing the next tool. Step 1: call lookup with query \"flue instrumentation\". Step 2: use the lookup result id as lookupId and call web_search with query \"Braintrust Flue reasoning stream instrumentation\". Step 3: use the first web_search result url and call summarize_source. After summarize_source returns, reply with exactly PROMPT_DONE and no other text.\n\n[Assistant tool calls]: lookup(query=\"flue instrumentation\")\n\n[Tool result]: {\"id\":\"flue-session-2026\",\"query\":\"flue instrumentation\",\"topic\":\"session instrumentation\"}\n\n[Assistant tool calls]: web_search(lookupId=\"flue-session-2026\", query=\"Braintrust Flue reasoning stream instrumentation\")\n\n[Tool result]: {\"lookupId\":\"flue-session-2026\",\"query\":\"Braintrust Flue reasoning stream instrumentation\",\"results\":[{\"title\":\"Flue reasoning stream instrumentation\",\"url\":\"https://example.test/flue/reasoning-streams\"}]}\n\n[Assistant tool calls]: summarize_source(url=\"https://example.test/flue/reasoning-streams\")\n\n[Tool result]: {\"summary\":\"Flue emits reasoning, tool execution, and LLM turn events separately.\",\"url\":\"https://example.test/flue/reasoning-streams\"}\n\n\nThis is the PREFIX of a turn that was too large to keep. The SUFFIX (recent work) is retained.\n\nSummarize the prefix to provide context for the retained suffix:\n\n## Original Request\n[What did the user ask for in this turn?]\n\n## Early Progress\n- [Key decisions and work done in the prefix]\n\n## Context for Suffix\n- [Information needed to understand the retained recent work]\n\nBe concise. Focus on what's needed to understand the kept suffix.", - "type": "text" - } - ], - "role": "user" - } - ], - "output": { - "content": [ - { - "text": "## Original Request\nThe user requested a step-by-step execution of an instrumented research flow involving tool calls to gather information on \"flue instrumentation\" and \"", - "textSignature": "{\"v\":1,\"id\":\"msg_04d28ba03840bb65016a705b7358a081a38e69464057840036\"}", - "type": "text" - } - ], - "role": "assistant" - }, - "metadata": { - "flue.api": "openai-responses", - "flue.model": "gpt-4o-mini", - "flue.provider": "openai", - "flue.session": "main", - "flue.stop_reason": "stop", - "flue.turn_purpose": "compaction_prefix", - "model": "gpt-4o-mini", - "provider": "openai" - }, - "metrics": { - "completion_tokens": 0, - "duration_ms": 0, - "estimated_cost": 0, - "prompt_cache_creation_tokens": 0, - "prompt_cached_tokens": 0, - "prompt_tokens": 0, - "tokens": 0 - } - } - ], - "input": { - "estimatedTokens": 1229, - "reason": "manual" - }, - "output": { - "messagesAfter": 2, - "messagesBefore": 8 - }, - "metadata": { - "flue.compaction_reason": "manual", - "flue.session": "main", - "provider": "flue" - }, - "metrics": { - "duration_ms": 0, - "messages_after": 2, - "messages_before": 8 - } - } - ], - "input": { - "estimatedTokens": 1229, - "reason": "manual" - }, - "output": { - "completed": true - }, - "metadata": { - "flue.operation": "compact", - "flue.session": "main", - "flue.workflow_name": "instrumentation", - "provider": "flue", - "scenario": "flue-instrumentation" - }, - "metrics": { - "duration_ms": 0 - } - } - ], - "input": { - "metadata": { - "scenario": "flue-instrumentation", - "testRunId": "" - }, - "scenario": "flue-instrumentation" - }, - "output": { - "scenario": "flue-instrumentation", - "status": "done" - }, - "metadata": { - "flue.workflow_name": "instrumentation", - "provider": "flue", - "scenario": "flue-instrumentation" - }, - "metrics": { - "duration_ms": 0 - } - }, - { - "name": "flue.prompt", - "type": "task", - "children": [ - { - "name": "flue.turn", - "type": "llm", - "children": [], - "input": [ - { - "content": [ - { - "text": "Complete this instrumented research flow. Call exactly one tool per turn and wait for each tool result before choosing the next tool. Step 1: call lookup with query \"flue instrumentation\". Step 2: use the lookup result id as lookupId and call web_search with query \"Braintrust Flue reasoning stream instrumentation\". Step 3: use the first web_search result url and call summarize_source. After summarize_source returns, reply with exactly PROMPT_DONE and no other text.", - "type": "text" - } - ], - "role": "user" - } - ], - "output": { - "content": [ - { - "arguments": { - "query": "flue instrumentation" - }, - "id": "", - "name": "lookup", - "type": "toolCall" - } - ], - "role": "assistant" - }, - "metadata": { - "flue.api": "openai-responses", - "flue.input_mode": "full", - "flue.model": "gpt-5.4-nano", - "flue.provider": "openai", - "flue.session": "main", - "flue.stop_reason": "toolUse", - "flue.turn_purpose": "agent", - "model": "gpt-5.4-nano", - "provider": "openai" - }, - "metrics": { - "completion_tokens": 19, - "duration_ms": 0, - "estimated_cost": 0.00022475000000000001, - "prompt_cache_creation_tokens": 0, - "prompt_cached_tokens": 0, - "prompt_tokens": 1005, - "tokens": 1024 - } - }, - { - "name": "tool:lookup", - "type": "tool", - "children": [], - "input": { - "query": "flue instrumentation" - }, - "output": { - "content": [ - { - "text": "{\"id\":\"flue-session-2026\",\"query\":\"flue instrumentation\",\"topic\":\"session instrumentation\"}", - "type": "text" - } - ], - "details": { - "customTool": "lookup" - } - }, - "metadata": { - "flue.session": "main", - "flue.tool_name": "lookup", - "provider": "flue" - }, - "metrics": { - "duration_ms": 0 - } - }, - { - "name": "flue.turn", - "type": "llm", - "children": [], - "input": [ - { - "content": [ - { - "arguments": { - "query": "flue instrumentation" - }, - "id": "", - "name": "lookup", - "type": "toolCall" - } - ], - "role": "assistant" - }, - { - "content": [ - { - "text": "{\"id\":\"flue-session-2026\",\"query\":\"flue instrumentation\",\"topic\":\"session instrumentation\"}", - "type": "text" - } - ], - "isError": false, - "role": "toolResult", - "toolCallId": "", - "toolName": "lookup" - } - ], - "output": { - "content": [ - { - "arguments": { - "lookupId": "flue-session-2026", - "query": "Braintrust Flue reasoning stream instrumentation" - }, - "id": "", - "name": "web_search", - "type": "toolCall" - } - ], - "role": "assistant" - }, - "metadata": { - "flue.api": "openai-responses", - "flue.input_message_offset": 1, - "flue.input_mode": "delta", - "flue.model": "gpt-5.4-nano", - "flue.provider": "openai", - "flue.session": "main", - "flue.stop_reason": "toolUse", - "flue.turn_purpose": "agent", - "model": "gpt-5.4-nano", - "provider": "openai" - }, - "metrics": { - "completion_tokens": 34, - "duration_ms": 0, - "estimated_cost": 0.00025350000000000004, - "prompt_cache_creation_tokens": 0, - "prompt_cached_tokens": 0, - "prompt_tokens": 1055, - "tokens": 1089 - } - }, - { - "name": "tool:web_search", - "type": "tool", - "children": [], - "input": { - "lookupId": "flue-session-2026", - "query": "Braintrust Flue reasoning stream instrumentation" - }, - "output": { - "content": [ - { - "text": "{\"lookupId\":\"flue-session-2026\",\"query\":\"Braintrust Flue reasoning stream instrumentation\",\"results\":[{\"title\":\"Flue reasoning stream instrumentation\",\"url\":\"https://example.test/flue/reasoning-streams\"}]}", - "type": "text" - } - ], - "details": { - "customTool": "web_search" - } - }, - "metadata": { - "flue.session": "main", - "flue.tool_name": "web_search", - "provider": "flue" - }, - "metrics": { - "duration_ms": 0 - } - }, - { - "name": "flue.turn", - "type": "llm", - "children": [], - "input": [ - { - "content": [ - { - "arguments": { - "lookupId": "flue-session-2026", - "query": "Braintrust Flue reasoning stream instrumentation" - }, - "id": "", - "name": "web_search", - "type": "toolCall" - } - ], - "role": "assistant" - }, - { - "content": [ - { - "text": "{\"lookupId\":\"flue-session-2026\",\"query\":\"Braintrust Flue reasoning stream instrumentation\",\"results\":[{\"title\":\"Flue reasoning stream instrumentation\",\"url\":\"https://example.test/flue/reasoning-streams\"}]}", - "type": "text" - } - ], - "isError": false, - "role": "toolResult", - "toolCallId": "", - "toolName": "web_search" - } - ], - "output": { - "content": [ - { - "arguments": { - "url": "https://example.test/flue/reasoning-streams" - }, - "id": "", - "name": "summarize_source", - "type": "toolCall" - } - ], - "role": "assistant" - }, - "metadata": { - "flue.api": "openai-responses", - "flue.input_message_offset": 3, - "flue.input_mode": "delta", - "flue.model": "gpt-5.4-nano", - "flue.provider": "openai", - "flue.session": "main", - "flue.stop_reason": "toolUse", - "flue.turn_purpose": "agent", - "model": "gpt-5.4-nano", - "provider": "openai" - }, - "metrics": { - "completion_tokens": 30, - "duration_ms": 0, - "estimated_cost": 0.00026690000000000004, - "prompt_cache_creation_tokens": 0, - "prompt_cached_tokens": 0, - "prompt_tokens": 1147, - "tokens": 1177 - } - }, - { - "name": "tool:summarize_source", - "type": "tool", - "children": [], - "input": { - "url": "https://example.test/flue/reasoning-streams" - }, - "output": { - "content": [ - { - "text": "{\"summary\":\"Flue emits reasoning, tool execution, and LLM turn events separately.\",\"url\":\"https://example.test/flue/reasoning-streams\"}", - "type": "text" - } - ], - "details": { - "customTool": "summarize_source" - } - }, - "metadata": { - "flue.session": "main", - "flue.tool_name": "summarize_source", - "provider": "flue" - }, - "metrics": { - "duration_ms": 0 - } - }, - { - "name": "flue.turn", - "type": "llm", - "children": [], - "input": [ - { - "content": [ - { - "arguments": { - "url": "https://example.test/flue/reasoning-streams" - }, - "id": "", - "name": "summarize_source", - "type": "toolCall" - } - ], - "role": "assistant" - }, - { - "content": [ - { - "text": "{\"summary\":\"Flue emits reasoning, tool execution, and LLM turn events separately.\",\"url\":\"https://example.test/flue/reasoning-streams\"}", - "type": "text" - } - ], - "isError": false, - "role": "toolResult", - "toolCallId": "", - "toolName": "summarize_source" - } - ], - "output": { - "content": [ - { - "text": "PROMPT_DONE", - "textSignature": "{\"v\":1,\"id\":\"msg_045458052dc10379016a705b62dbd8819189b8287061debf12\",\"phase\":\"final_answer\"}", - "type": "text" - } - ], - "role": "assistant" - }, - "metadata": { - "flue.api": "openai-responses", - "flue.input_message_offset": 5, - "flue.input_mode": "delta", - "flue.model": "gpt-5.4-nano", - "flue.provider": "openai", - "flue.session": "main", - "flue.stop_reason": "stop", - "flue.turn_purpose": "agent", - "model": "gpt-5.4-nano", - "provider": "openai" - }, - "metrics": { - "completion_tokens": 7, - "duration_ms": 0, - "estimated_cost": 0.00025315000000000005, - "prompt_cache_creation_tokens": 0, - "prompt_cached_tokens": 0, - "prompt_tokens": 1222, - "tokens": 1229 - } - } - ], - "input": [ - { - "content": [ - { - "text": "Complete this instrumented research flow. Call exactly one tool per turn and wait for each tool result before choosing the next tool. Step 1: call lookup with query \"flue instrumentation\". Step 2: use the lookup result id as lookupId and call web_search with query \"Braintrust Flue reasoning stream instrumentation\". Step 3: use the first web_search result url and call summarize_source. After summarize_source returns, reply with exactly PROMPT_DONE and no other text.", - "type": "text" - } - ], - "role": "user" - } - ], - "output": "PROMPT_DONE", - "metadata": { - "flue.operation": "prompt", - "flue.session": "main", - "flue.workflow_name": "instrumentation", - "provider": "flue", - "scenario": "flue-instrumentation" - }, - "metrics": { - "duration_ms": 0 - } - } - ] -} diff --git a/e2e/scenarios/flue-instrumentation/__snapshots__/flue-v0-8-latest-auto-hook.span-tree.txt b/e2e/scenarios/flue-instrumentation/__snapshots__/flue-v0-8-latest-auto-hook.span-tree.txt deleted file mode 100644 index b4408459c..000000000 --- a/e2e/scenarios/flue-instrumentation/__snapshots__/flue-v0-8-latest-auto-hook.span-tree.txt +++ /dev/null @@ -1,1499 +0,0 @@ -span_tree: -├── workflow:instrumentation [task] -│ input: { -│ "metadata": { -│ "scenario": "flue-instrumentation", -│ "testRunId": "" -│ }, -│ "scenario": "flue-instrumentation" -│ } -│ output: { -│ "scenario": "flue-instrumentation", -│ "status": "done" -│ } -│ metadata: { -│ "flue.workflow_name": "instrumentation", -│ "provider": "flue", -│ "scenario": "flue-instrumentation" -│ } -│ metrics: { -│ "duration_ms": 0 -│ } -│ ├── flue.skill [task] -│ │ input: [ -│ │ { -│ │ "content": [ -│ │ { -│ │ "text": "Run the skill named \"e2e-flue-skill\".\n\nArguments:\n{\n \"marker\": \"SKILL_DONE\"\n}", -│ │ "type": "text" -│ │ } -│ │ ], -│ │ "role": "user" -│ │ } -│ │ ] -│ │ output: "SKILL_DONE" -│ │ metadata: { -│ │ "flue.operation": "skill", -│ │ "flue.session": "skill", -│ │ "flue.workflow_name": "instrumentation", -│ │ "provider": "flue", -│ │ "scenario": "flue-instrumentation" -│ │ } -│ │ metrics: { -│ │ "duration_ms": 0 -│ │ } -│ │ ├── flue.turn [llm] -│ │ │ input: [ -│ │ │ { -│ │ │ "content": [ -│ │ │ { -│ │ │ "text": "Run the skill named \"e2e-flue-skill\".\n\nArguments:\n{\n \"marker\": \"SKILL_DONE\"\n}", -│ │ │ "type": "text" -│ │ │ } -│ │ │ ], -│ │ │ "role": "user" -│ │ │ } -│ │ │ ] -│ │ │ output: { -│ │ │ "content": [ -│ │ │ { -│ │ │ "arguments": { -│ │ │ "cwd": ".", -│ │ │ "description": "Run the deterministic Flue instrumentation e2e test skill e2e-flue-skill.", -│ │ │ "prompt": "Run the skill named \"e2e-flue-skill\" with arguments {\"marker\":\"SKILL_DONE\"}. Output only the marker and no extra text when finished." -│ │ │ }, -│ │ │ "id": "", -│ │ │ "name": "task", -│ │ │ "type": "toolCall" -│ │ │ } -│ │ │ ], -│ │ │ "role": "assistant" -│ │ │ } -│ │ │ metadata: { -│ │ │ "flue.api": "openai-responses", -│ │ │ "flue.model": "gpt-5.4-nano", -│ │ │ "flue.provider": "openai", -│ │ │ "flue.session": "skill", -│ │ │ "flue.stop_reason": "toolUse", -│ │ │ "flue.turn_purpose": "agent", -│ │ │ "model": "gpt-5.4-nano", -│ │ │ "provider": "openai" -│ │ │ } -│ │ │ metrics: { -│ │ │ "completion_tokens": 109, -│ │ │ "duration_ms": 0, -│ │ │ "estimated_cost": 0.00030565000000000003, -│ │ │ "prompt_cache_creation_tokens": 0, -│ │ │ "prompt_cached_tokens": 0, -│ │ │ "prompt_tokens": 847, -│ │ │ "tokens": 956 -│ │ │ } -│ │ │ └── flue.task [task] -│ │ │ input: "Run the skill named \"e2e-flue-skill\" with arguments {\"marker\":\"SKILL_DONE\"}. Output only the marker and no extra text when finished." -│ │ │ output: "SKILL_DONE" -│ │ │ metadata: { -│ │ │ "flue.session": "skill", -│ │ │ "provider": "flue" -│ │ │ } -│ │ │ metrics: { -│ │ │ "duration_ms": 0 -│ │ │ } -│ │ │ ├── flue.turn [llm] -│ │ │ │ input: [ -│ │ │ │ { -│ │ │ │ "content": [ -│ │ │ │ { -│ │ │ │ "text": "Run the skill named \"e2e-flue-skill\" with arguments {\"marker\":\"SKILL_DONE\"}. Output only the marker and no extra text when finished.", -│ │ │ │ "type": "text" -│ │ │ │ } -│ │ │ │ ], -│ │ │ │ "role": "user" -│ │ │ │ } -│ │ │ │ ] -│ │ │ │ output: { -│ │ │ │ "content": [ -│ │ │ │ { -│ │ │ │ "arguments": { -│ │ │ │ "agent": "e2e-flue-skill", -│ │ │ │ "cwd": ".", -│ │ │ │ "description": "Run e2e-flue-skill with marker SKILL_DONE", -│ │ │ │ "prompt": "Run the skill named \"e2e-flue-skill\" with arguments {\"marker\":\"SKILL_DONE\"}. Ensure completion and report the final output marker only." -│ │ │ │ }, -│ │ │ │ "id": "", -│ │ │ │ "name": "task", -│ │ │ │ "type": "toolCall" -│ │ │ │ } -│ │ │ │ ], -│ │ │ │ "role": "assistant" -│ │ │ │ } -│ │ │ │ metadata: { -│ │ │ │ "flue.api": "openai-responses", -│ │ │ │ "flue.input_mode": "full", -│ │ │ │ "flue.model": "gpt-5.4-nano", -│ │ │ │ "flue.provider": "openai", -│ │ │ │ "flue.session": "task:skill:", -│ │ │ │ "flue.stop_reason": "toolUse", -│ │ │ │ "flue.turn_purpose": "agent", -│ │ │ │ "model": "gpt-5.4-nano", -│ │ │ │ "provider": "openai" -│ │ │ │ } -│ │ │ │ metrics: { -│ │ │ │ "completion_tokens": 113, -│ │ │ │ "duration_ms": 0, -│ │ │ │ "estimated_cost": 0.00031225000000000003, -│ │ │ │ "prompt_cache_creation_tokens": 0, -│ │ │ │ "prompt_cached_tokens": 0, -│ │ │ │ "prompt_tokens": 855, -│ │ │ │ "tokens": 968 -│ │ │ │ } -│ │ │ ├── tool:task [tool] -│ │ │ │ input: { -│ │ │ │ "agent": "e2e-flue-skill", -│ │ │ │ "cwd": ".", -│ │ │ │ "description": "Run e2e-flue-skill with marker SKILL_DONE", -│ │ │ │ "prompt": "Run the skill named \"e2e-flue-skill\" with arguments {\"marker\":\"SKILL_DONE\"}. Ensure completion and report the final output marker only." -│ │ │ │ } -│ │ │ │ output: { -│ │ │ │ "content": [ -│ │ │ │ { -│ │ │ │ "text": "[flue] Subagent \"e2e-flue-skill\" is not declared. Available: (none).", -│ │ │ │ "type": "text" -│ │ │ │ } -│ │ │ │ ], -│ │ │ │ "details": {} -│ │ │ │ } -│ │ │ │ metadata: { -│ │ │ │ "flue.session": "task:skill:", -│ │ │ │ "flue.tool_name": "task", -│ │ │ │ "provider": "flue" -│ │ │ │ } -│ │ │ │ metrics: { -│ │ │ │ "duration_ms": 0 -│ │ │ │ } -│ │ │ │ error: "{\"content\":[{\"type\":\"text\",\"text\":\"[flue] Subagent \\\"e2e-flue-skill\\\" is not declared. Available: (none).\"}],\"details\":{}}" -│ │ │ ├── flue.turn [llm] -│ │ │ │ input: [ -│ │ │ │ { -│ │ │ │ "content": [ -│ │ │ │ { -│ │ │ │ "arguments": { -│ │ │ │ "agent": "e2e-flue-skill", -│ │ │ │ "cwd": ".", -│ │ │ │ "description": "Run e2e-flue-skill with marker SKILL_DONE", -│ │ │ │ "prompt": "Run the skill named \"e2e-flue-skill\" with arguments {\"marker\":\"SKILL_DONE\"}. Ensure completion and report the final output marker only." -│ │ │ │ }, -│ │ │ │ "id": "", -│ │ │ │ "name": "task", -│ │ │ │ "type": "toolCall" -│ │ │ │ } -│ │ │ │ ], -│ │ │ │ "role": "assistant" -│ │ │ │ }, -│ │ │ │ { -│ │ │ │ "content": [ -│ │ │ │ { -│ │ │ │ "text": "[flue] Subagent \"e2e-flue-skill\" is not declared. Available: (none).", -│ │ │ │ "type": "text" -│ │ │ │ } -│ │ │ │ ], -│ │ │ │ "isError": true, -│ │ │ │ "role": "toolResult", -│ │ │ │ "toolCallId": "", -│ │ │ │ "toolName": "task" -│ │ │ │ } -│ │ │ │ ] -│ │ │ │ output: { -│ │ │ │ "content": [ -│ │ │ │ { -│ │ │ │ "arguments": { -│ │ │ │ "command": "find . -maxdepth 4 -name AGENTS.md -print", -│ │ │ │ "timeout": 100000 -│ │ │ │ }, -│ │ │ │ "id": "", -│ │ │ │ "name": "bash", -│ │ │ │ "type": "toolCall" -│ │ │ │ } -│ │ │ │ ], -│ │ │ │ "role": "assistant" -│ │ │ │ } -│ │ │ │ metadata: { -│ │ │ │ "flue.api": "openai-responses", -│ │ │ │ "flue.input_message_offset": 1, -│ │ │ │ "flue.input_mode": "delta", -│ │ │ │ "flue.model": "gpt-5.4-nano", -│ │ │ │ "flue.provider": "openai", -│ │ │ │ "flue.session": "task:skill:", -│ │ │ │ "flue.stop_reason": "toolUse", -│ │ │ │ "flue.turn_purpose": "agent", -│ │ │ │ "model": "gpt-5.4-nano", -│ │ │ │ "provider": "openai" -│ │ │ │ } -│ │ │ │ metrics: { -│ │ │ │ "completion_tokens": 94, -│ │ │ │ "duration_ms": 0, -│ │ │ │ "estimated_cost": 0.00031790000000000003, -│ │ │ │ "prompt_cache_creation_tokens": 0, -│ │ │ │ "prompt_cached_tokens": 0, -│ │ │ │ "prompt_tokens": 1002, -│ │ │ │ "tokens": 1096 -│ │ │ │ } -│ │ │ ├── tool:bash [tool] -│ │ │ │ input: { -│ │ │ │ "command": "find . -maxdepth 4 -name AGENTS.md -print", -│ │ │ │ "timeout": 100000 -│ │ │ │ } -│ │ │ │ output: { -│ │ │ │ "content": [ -│ │ │ │ { -│ │ │ │ "text": "(no output)", -│ │ │ │ "type": "text" -│ │ │ │ } -│ │ │ │ ], -│ │ │ │ "details": { -│ │ │ │ "command": "find . -maxdepth 4 -name AGENTS.md -print", -│ │ │ │ "exitCode": 0 -│ │ │ │ } -│ │ │ │ } -│ │ │ │ metadata: { -│ │ │ │ "flue.session": "task:skill:", -│ │ │ │ "flue.tool_name": "bash", -│ │ │ │ "provider": "flue" -│ │ │ │ } -│ │ │ │ metrics: { -│ │ │ │ "duration_ms": 0 -│ │ │ │ } -│ │ │ ├── flue.turn [llm] -│ │ │ │ input: [ -│ │ │ │ { -│ │ │ │ "content": [ -│ │ │ │ { -│ │ │ │ "arguments": { -│ │ │ │ "command": "find . -maxdepth 4 -name AGENTS.md -print", -│ │ │ │ "timeout": 100000 -│ │ │ │ }, -│ │ │ │ "id": "", -│ │ │ │ "name": "bash", -│ │ │ │ "type": "toolCall" -│ │ │ │ } -│ │ │ │ ], -│ │ │ │ "role": "assistant" -│ │ │ │ }, -│ │ │ │ { -│ │ │ │ "content": [ -│ │ │ │ { -│ │ │ │ "text": "(no output)", -│ │ │ │ "type": "text" -│ │ │ │ } -│ │ │ │ ], -│ │ │ │ "isError": false, -│ │ │ │ "role": "toolResult", -│ │ │ │ "toolCallId": "", -│ │ │ │ "toolName": "bash" -│ │ │ │ } -│ │ │ │ ] -│ │ │ │ output: { -│ │ │ │ "content": [ -│ │ │ │ { -│ │ │ │ "arguments": { -│ │ │ │ "command": "find . -maxdepth 4 -type f -name AGENTS.md -print -exec sed -n '1,200p' {} \\;", -│ │ │ │ "timeout": 100000 -│ │ │ │ }, -│ │ │ │ "id": "", -│ │ │ │ "name": "bash", -│ │ │ │ "type": "toolCall" -│ │ │ │ } -│ │ │ │ ], -│ │ │ │ "role": "assistant" -│ │ │ │ } -│ │ │ │ metadata: { -│ │ │ │ "flue.api": "openai-responses", -│ │ │ │ "flue.input_message_offset": 3, -│ │ │ │ "flue.input_mode": "delta", -│ │ │ │ "flue.model": "gpt-5.4-nano", -│ │ │ │ "flue.provider": "openai", -│ │ │ │ "flue.session": "task:skill:", -│ │ │ │ "flue.stop_reason": "toolUse", -│ │ │ │ "flue.turn_purpose": "agent", -│ │ │ │ "model": "gpt-5.4-nano", -│ │ │ │ "provider": "openai" -│ │ │ │ } -│ │ │ │ metrics: { -│ │ │ │ "completion_tokens": 87, -│ │ │ │ "duration_ms": 0, -│ │ │ │ "estimated_cost": 0.00034815000000000003, -│ │ │ │ "prompt_cache_creation_tokens": 0, -│ │ │ │ "prompt_cached_tokens": 0, -│ │ │ │ "prompt_tokens": 1197, -│ │ │ │ "tokens": 1284 -│ │ │ │ } -│ │ │ ├── tool:bash [tool] -│ │ │ │ input: { -│ │ │ │ "command": "find . -maxdepth 4 -type f -name AGENTS.md -print -exec sed -n '1,200p' {} \\;", -│ │ │ │ "timeout": 100000 -│ │ │ │ } -│ │ │ │ output: { -│ │ │ │ "content": [ -│ │ │ │ { -│ │ │ │ "text": "(no output)", -│ │ │ │ "type": "text" -│ │ │ │ } -│ │ │ │ ], -│ │ │ │ "details": { -│ │ │ │ "command": "find . -maxdepth 4 -type f -name AGENTS.md -print -exec sed -n '1,200p' {} \\;", -│ │ │ │ "exitCode": 0 -│ │ │ │ } -│ │ │ │ } -│ │ │ │ metadata: { -│ │ │ │ "flue.session": "task:skill:", -│ │ │ │ "flue.tool_name": "bash", -│ │ │ │ "provider": "flue" -│ │ │ │ } -│ │ │ │ metrics: { -│ │ │ │ "duration_ms": 0 -│ │ │ │ } -│ │ │ ├── flue.turn [llm] -│ │ │ │ input: [ -│ │ │ │ { -│ │ │ │ "content": [ -│ │ │ │ { -│ │ │ │ "arguments": { -│ │ │ │ "command": "find . -maxdepth 4 -type f -name AGENTS.md -print -exec sed -n '1,200p' {} \\;", -│ │ │ │ "timeout": 100000 -│ │ │ │ }, -│ │ │ │ "id": "", -│ │ │ │ "name": "bash", -│ │ │ │ "type": "toolCall" -│ │ │ │ } -│ │ │ │ ], -│ │ │ │ "role": "assistant" -│ │ │ │ }, -│ │ │ │ { -│ │ │ │ "content": [ -│ │ │ │ { -│ │ │ │ "text": "(no output)", -│ │ │ │ "type": "text" -│ │ │ │ } -│ │ │ │ ], -│ │ │ │ "isError": false, -│ │ │ │ "role": "toolResult", -│ │ │ │ "toolCallId": "", -│ │ │ │ "toolName": "bash" -│ │ │ │ } -│ │ │ │ ] -│ │ │ │ output: { -│ │ │ │ "content": [ -│ │ │ │ { -│ │ │ │ "arguments": { -│ │ │ │ "command": "find . -maxdepth 6 -type f -name 'AGENTS.md' -print", -│ │ │ │ "timeout": 100000 -│ │ │ │ }, -│ │ │ │ "id": "", -│ │ │ │ "name": "bash", -│ │ │ │ "type": "toolCall" -│ │ │ │ } -│ │ │ │ ], -│ │ │ │ "role": "assistant" -│ │ │ │ } -│ │ │ │ metadata: { -│ │ │ │ "flue.api": "openai-responses", -│ │ │ │ "flue.input_message_offset": 5, -│ │ │ │ "flue.input_mode": "delta", -│ │ │ │ "flue.model": "gpt-5.4-nano", -│ │ │ │ "flue.provider": "openai", -│ │ │ │ "flue.session": "task:skill:", -│ │ │ │ "flue.stop_reason": "toolUse", -│ │ │ │ "flue.turn_purpose": "agent", -│ │ │ │ "model": "gpt-5.4-nano", -│ │ │ │ "provider": "openai" -│ │ │ │ } -│ │ │ │ metrics: { -│ │ │ │ "completion_tokens": 58, -│ │ │ │ "duration_ms": 0, -│ │ │ │ "estimated_cost": 0.0003321, -│ │ │ │ "prompt_cache_creation_tokens": 0, -│ │ │ │ "prompt_cached_tokens": 0, -│ │ │ │ "prompt_tokens": 1298, -│ │ │ │ "tokens": 1356 -│ │ │ │ } -│ │ │ ├── tool:bash [tool] -│ │ │ │ input: { -│ │ │ │ "command": "find . -maxdepth 6 -type f -name 'AGENTS.md' -print", -│ │ │ │ "timeout": 100000 -│ │ │ │ } -│ │ │ │ output: { -│ │ │ │ "content": [ -│ │ │ │ { -│ │ │ │ "text": "(no output)", -│ │ │ │ "type": "text" -│ │ │ │ } -│ │ │ │ ], -│ │ │ │ "details": { -│ │ │ │ "command": "find . -maxdepth 6 -type f -name 'AGENTS.md' -print", -│ │ │ │ "exitCode": 0 -│ │ │ │ } -│ │ │ │ } -│ │ │ │ metadata: { -│ │ │ │ "flue.session": "task:skill:", -│ │ │ │ "flue.tool_name": "bash", -│ │ │ │ "provider": "flue" -│ │ │ │ } -│ │ │ │ metrics: { -│ │ │ │ "duration_ms": 0 -│ │ │ │ } -│ │ │ ├── flue.turn [llm] -│ │ │ │ input: [ -│ │ │ │ { -│ │ │ │ "content": [ -│ │ │ │ { -│ │ │ │ "arguments": { -│ │ │ │ "command": "find . -maxdepth 6 -type f -name 'AGENTS.md' -print", -│ │ │ │ "timeout": 100000 -│ │ │ │ }, -│ │ │ │ "id": "", -│ │ │ │ "name": "bash", -│ │ │ │ "type": "toolCall" -│ │ │ │ } -│ │ │ │ ], -│ │ │ │ "role": "assistant" -│ │ │ │ }, -│ │ │ │ { -│ │ │ │ "content": [ -│ │ │ │ { -│ │ │ │ "text": "(no output)", -│ │ │ │ "type": "text" -│ │ │ │ } -│ │ │ │ ], -│ │ │ │ "isError": false, -│ │ │ │ "role": "toolResult", -│ │ │ │ "toolCallId": "", -│ │ │ │ "toolName": "bash" -│ │ │ │ } -│ │ │ │ ] -│ │ │ │ output: { -│ │ │ │ "content": [ -│ │ │ │ { -│ │ │ │ "arguments": { -│ │ │ │ "command": "find . -maxdepth 5 -type f -name '*flue*skill*' -o -name '*skill*' | head", -│ │ │ │ "timeout": 100000 -│ │ │ │ }, -│ │ │ │ "id": "", -│ │ │ │ "name": "bash", -│ │ │ │ "type": "toolCall" -│ │ │ │ } -│ │ │ │ ], -│ │ │ │ "role": "assistant" -│ │ │ │ } -│ │ │ │ metadata: { -│ │ │ │ "flue.api": "openai-responses", -│ │ │ │ "flue.input_message_offset": 7, -│ │ │ │ "flue.input_mode": "delta", -│ │ │ │ "flue.model": "gpt-5.4-nano", -│ │ │ │ "flue.provider": "openai", -│ │ │ │ "flue.session": "task:skill:", -│ │ │ │ "flue.stop_reason": "toolUse", -│ │ │ │ "flue.turn_purpose": "agent", -│ │ │ │ "model": "gpt-5.4-nano", -│ │ │ │ "provider": "openai" -│ │ │ │ } -│ │ │ │ metrics: { -│ │ │ │ "completion_tokens": 68, -│ │ │ │ "duration_ms": 0, -│ │ │ │ "estimated_cost": 0.00035900000000000005, -│ │ │ │ "prompt_cache_creation_tokens": 0, -│ │ │ │ "prompt_cached_tokens": 0, -│ │ │ │ "prompt_tokens": 1370, -│ │ │ │ "tokens": 1438 -│ │ │ │ } -│ │ │ ├── tool:bash [tool] -│ │ │ │ input: { -│ │ │ │ "command": "find . -maxdepth 5 -type f -name '*flue*skill*' -o -name '*skill*' | head", -│ │ │ │ "timeout": 100000 -│ │ │ │ } -│ │ │ │ output: { -│ │ │ │ "content": [ -│ │ │ │ { -│ │ │ │ "text": "./.agents/skills\n./.agents/skills/e2e-flue-skill", -│ │ │ │ "type": "text" -│ │ │ │ } -│ │ │ │ ], -│ │ │ │ "details": { -│ │ │ │ "command": "find . -maxdepth 5 -type f -name '*flue*skill*' -o -name '*skill*' | head", -│ │ │ │ "exitCode": 0 -│ │ │ │ } -│ │ │ │ } -│ │ │ │ metadata: { -│ │ │ │ "flue.session": "task:skill:", -│ │ │ │ "flue.tool_name": "bash", -│ │ │ │ "provider": "flue" -│ │ │ │ } -│ │ │ │ metrics: { -│ │ │ │ "duration_ms": 0 -│ │ │ │ } -│ │ │ ├── flue.turn [llm] -│ │ │ │ input: [ -│ │ │ │ { -│ │ │ │ "content": [ -│ │ │ │ { -│ │ │ │ "arguments": { -│ │ │ │ "command": "find . -maxdepth 5 -type f -name '*flue*skill*' -o -name '*skill*' | head", -│ │ │ │ "timeout": 100000 -│ │ │ │ }, -│ │ │ │ "id": "", -│ │ │ │ "name": "bash", -│ │ │ │ "type": "toolCall" -│ │ │ │ } -│ │ │ │ ], -│ │ │ │ "role": "assistant" -│ │ │ │ }, -│ │ │ │ { -│ │ │ │ "content": [ -│ │ │ │ { -│ │ │ │ "text": "./.agents/skills\n./.agents/skills/e2e-flue-skill", -│ │ │ │ "type": "text" -│ │ │ │ } -│ │ │ │ ], -│ │ │ │ "isError": false, -│ │ │ │ "role": "toolResult", -│ │ │ │ "toolCallId": "", -│ │ │ │ "toolName": "bash" -│ │ │ │ } -│ │ │ │ ] -│ │ │ │ output: { -│ │ │ │ "content": [ -│ │ │ │ { -│ │ │ │ "arguments": { -│ │ │ │ "command": "find ./.agents/skills/e2e-flue-skill -maxdepth 2 -type f -print", -│ │ │ │ "timeout": 100000 -│ │ │ │ }, -│ │ │ │ "id": "", -│ │ │ │ "name": "bash", -│ │ │ │ "type": "toolCall" -│ │ │ │ } -│ │ │ │ ], -│ │ │ │ "role": "assistant" -│ │ │ │ } -│ │ │ │ metadata: { -│ │ │ │ "flue.api": "openai-responses", -│ │ │ │ "flue.input_message_offset": 9, -│ │ │ │ "flue.input_mode": "delta", -│ │ │ │ "flue.model": "gpt-5.4-nano", -│ │ │ │ "flue.provider": "openai", -│ │ │ │ "flue.session": "task:skill:", -│ │ │ │ "flue.stop_reason": "toolUse", -│ │ │ │ "flue.turn_purpose": "agent", -│ │ │ │ "model": "gpt-5.4-nano", -│ │ │ │ "provider": "openai" -│ │ │ │ } -│ │ │ │ metrics: { -│ │ │ │ "completion_tokens": 109, -│ │ │ │ "duration_ms": 0, -│ │ │ │ "estimated_cost": 0.00043885000000000007, -│ │ │ │ "prompt_cache_creation_tokens": 0, -│ │ │ │ "prompt_cached_tokens": 0, -│ │ │ │ "prompt_tokens": 1513, -│ │ │ │ "tokens": 1622 -│ │ │ │ } -│ │ │ ├── tool:bash [tool] -│ │ │ │ input: { -│ │ │ │ "command": "find ./.agents/skills/e2e-flue-skill -maxdepth 2 -type f -print", -│ │ │ │ "timeout": 100000 -│ │ │ │ } -│ │ │ │ output: { -│ │ │ │ "content": [ -│ │ │ │ { -│ │ │ │ "text": "./.agents/skills/e2e-flue-skill/SKILL.md", -│ │ │ │ "type": "text" -│ │ │ │ } -│ │ │ │ ], -│ │ │ │ "details": { -│ │ │ │ "command": "find ./.agents/skills/e2e-flue-skill -maxdepth 2 -type f -print", -│ │ │ │ "exitCode": 0 -│ │ │ │ } -│ │ │ │ } -│ │ │ │ metadata: { -│ │ │ │ "flue.session": "task:skill:", -│ │ │ │ "flue.tool_name": "bash", -│ │ │ │ "provider": "flue" -│ │ │ │ } -│ │ │ │ metrics: { -│ │ │ │ "duration_ms": 0 -│ │ │ │ } -│ │ │ ├── flue.turn [llm] -│ │ │ │ input: [ -│ │ │ │ { -│ │ │ │ "content": [ -│ │ │ │ { -│ │ │ │ "arguments": { -│ │ │ │ "command": "find ./.agents/skills/e2e-flue-skill -maxdepth 2 -type f -print", -│ │ │ │ "timeout": 100000 -│ │ │ │ }, -│ │ │ │ "id": "", -│ │ │ │ "name": "bash", -│ │ │ │ "type": "toolCall" -│ │ │ │ } -│ │ │ │ ], -│ │ │ │ "role": "assistant" -│ │ │ │ }, -│ │ │ │ { -│ │ │ │ "content": [ -│ │ │ │ { -│ │ │ │ "text": "./.agents/skills/e2e-flue-skill/SKILL.md", -│ │ │ │ "type": "text" -│ │ │ │ } -│ │ │ │ ], -│ │ │ │ "isError": false, -│ │ │ │ "role": "toolResult", -│ │ │ │ "toolCallId": "", -│ │ │ │ "toolName": "bash" -│ │ │ │ } -│ │ │ │ ] -│ │ │ │ output: { -│ │ │ │ "content": [ -│ │ │ │ { -│ │ │ │ "arguments": { -│ │ │ │ "limit": 200, -│ │ │ │ "offset": 1, -│ │ │ │ "path": "./.agents/skills/e2e-flue-skill/SKILL.md" -│ │ │ │ }, -│ │ │ │ "id": "", -│ │ │ │ "name": "read", -│ │ │ │ "type": "toolCall" -│ │ │ │ } -│ │ │ │ ], -│ │ │ │ "role": "assistant" -│ │ │ │ } -│ │ │ │ metadata: { -│ │ │ │ "flue.api": "openai-responses", -│ │ │ │ "flue.input_message_offset": 11, -│ │ │ │ "flue.input_mode": "delta", -│ │ │ │ "flue.model": "gpt-5.4-nano", -│ │ │ │ "flue.provider": "openai", -│ │ │ │ "flue.session": "task:skill:", -│ │ │ │ "flue.stop_reason": "toolUse", -│ │ │ │ "flue.turn_purpose": "agent", -│ │ │ │ "model": "gpt-5.4-nano", -│ │ │ │ "provider": "openai" -│ │ │ │ } -│ │ │ │ metrics: { -│ │ │ │ "completion_tokens": 64, -│ │ │ │ "duration_ms": 0, -│ │ │ │ "estimated_cost": 0.00015592, -│ │ │ │ "prompt_cache_creation_tokens": 0, -│ │ │ │ "prompt_cached_tokens": 1536, -│ │ │ │ "prompt_tokens": 1762, -│ │ │ │ "tokens": 1826 -│ │ │ │ } -│ │ │ ├── tool:read [tool] -│ │ │ │ input: { -│ │ │ │ "limit": 200, -│ │ │ │ "offset": 1, -│ │ │ │ "path": "./.agents/skills/e2e-flue-skill/SKILL.md" -│ │ │ │ } -│ │ │ │ output: { -│ │ │ │ "content": [ -│ │ │ │ { -│ │ │ │ "text": "---\nname: e2e-flue-skill\ndescription: Deterministic skill used by the Braintrust Flue instrumentation e2e test.\n---\n\nReturn the marker from the provided args exactly once. Output no other text.\n", -│ │ │ │ "type": "text" -│ │ │ │ } -│ │ │ │ ], -│ │ │ │ "details": { -│ │ │ │ "lines": 7, -│ │ │ │ "path": "./.agents/skills/e2e-flue-skill/SKILL.md" -│ │ │ │ } -│ │ │ │ } -│ │ │ │ metadata: { -│ │ │ │ "flue.session": "task:skill:", -│ │ │ │ "flue.tool_name": "read", -│ │ │ │ "provider": "flue" -│ │ │ │ } -│ │ │ │ metrics: { -│ │ │ │ "duration_ms": 0 -│ │ │ │ } -│ │ │ ├── flue.turn [llm] -│ │ │ │ input: [ -│ │ │ │ { -│ │ │ │ "content": [ -│ │ │ │ { -│ │ │ │ "arguments": { -│ │ │ │ "limit": 200, -│ │ │ │ "offset": 1, -│ │ │ │ "path": "./.agents/skills/e2e-flue-skill/SKILL.md" -│ │ │ │ }, -│ │ │ │ "id": "", -│ │ │ │ "name": "read", -│ │ │ │ "type": "toolCall" -│ │ │ │ } -│ │ │ │ ], -│ │ │ │ "role": "assistant" -│ │ │ │ }, -│ │ │ │ { -│ │ │ │ "content": [ -│ │ │ │ { -│ │ │ │ "text": "---\nname: e2e-flue-skill\ndescription: Deterministic skill used by the Braintrust Flue instrumentation e2e test.\n---\n\nReturn the marker from the provided args exactly once. Output no other text.\n", -│ │ │ │ "type": "text" -│ │ │ │ } -│ │ │ │ ], -│ │ │ │ "isError": false, -│ │ │ │ "role": "toolResult", -│ │ │ │ "toolCallId": "", -│ │ │ │ "toolName": "read" -│ │ │ │ } -│ │ │ │ ] -│ │ │ │ output: { -│ │ │ │ "content": [ -│ │ │ │ { -│ │ │ │ "arguments": { -│ │ │ │ "command": "cat ./package.json", -│ │ │ │ "timeout": 100000 -│ │ │ │ }, -│ │ │ │ "id": "", -│ │ │ │ "name": "bash", -│ │ │ │ "type": "toolCall" -│ │ │ │ } -│ │ │ │ ], -│ │ │ │ "role": "assistant" -│ │ │ │ } -│ │ │ │ metadata: { -│ │ │ │ "flue.api": "openai-responses", -│ │ │ │ "flue.input_message_offset": 13, -│ │ │ │ "flue.input_mode": "delta", -│ │ │ │ "flue.model": "gpt-5.4-nano", -│ │ │ │ "flue.provider": "openai", -│ │ │ │ "flue.session": "task:skill:", -│ │ │ │ "flue.stop_reason": "toolUse", -│ │ │ │ "flue.turn_purpose": "agent", -│ │ │ │ "model": "gpt-5.4-nano", -│ │ │ │ "provider": "openai" -│ │ │ │ } -│ │ │ │ metrics: { -│ │ │ │ "completion_tokens": 90, -│ │ │ │ "duration_ms": 0, -│ │ │ │ "estimated_cost": 0.00021242000000000003, -│ │ │ │ "prompt_cache_creation_tokens": 0, -│ │ │ │ "prompt_cached_tokens": 1536, -│ │ │ │ "prompt_tokens": 1882, -│ │ │ │ "tokens": 1972 -│ │ │ │ } -│ │ │ ├── tool:bash [tool] -│ │ │ │ input: { -│ │ │ │ "command": "cat ./package.json", -│ │ │ │ "timeout": 100000 -│ │ │ │ } -│ │ │ │ output: { -│ │ │ │ "content": [ -│ │ │ │ { -│ │ │ │ "text": "{\n \"name\": \"flue-instrumentation-scenario\",\n \"private\": true,\n \"type\": \"module\",\n \"braintrustScenario\": {\n \"bump\": {\n \"dependencies\": {\n \"flue-cli-v0-8-latest\": {\n \"package\": \"@flue/cli\",\n \"range\": \">=0.8.0 <0.9.0\"\n },\n \"flue-cli-v1-latest\": {\n \"package\": \"@flue/cli\",\n \"range\": \">=1.0.0-beta.3 <2.0.0-0\",\n \"allowPrerelease\": true\n },\n \"flue-runtime-v0-8-latest\": {\n \"package\": \"@flue/runtime\",\n \"range\": \">=0.8.0 <0.9.0\"\n },\n \"flue-runtime-v1-latest\": {\n \"package\": \"@flue/runtime\",\n \"range\": \">=1.0.0-beta.3 <2.0.0-0\",\n \"allowPrerelease\": true\n },\n \"flue-runtime-v2-latest\": {\n \"package\": \"@flue/runtime\",\n \"range\": \">=2.0.0 <3.0.0\"\n }\n }\n }\n },\n \"dependencies\": {\n \"@flue/cli\": \"0.8.0\",\n \"@flue/runtime\": \"0.8.0\",\n \"flue-cli-v0-8-latest\": \"npm:@flue/cli@0.8.1\",\n \"flue-cli-v1\": \"npm:@flue/cli@1.0.0-beta.3\",\n \"flue-cli-v1-latest\": \"npm:@flue/cli@1.0.0-beta.9\",\n \"flue-runtime-v0-8-latest\": \"npm:@flue/runtime@0.8.1\",\n \"flue-runtime-v1\": \"npm:@flue/runtime@1.0.0-beta.3\",\n \"flue-runtime-v1-latest\": \"npm:@flue/runtime@1.0.0-beta.9\",\n \"flue-runtime-v2\": \"npm:@flue/runtime@2.0.0\",\n \"flue-runtime-v2-latest\": \"npm:@flue/runtime@2.0.1\",\n \"pi-ai-v2\": \"npm:@earendil-works/pi-ai@0.83.0\",\n \"valibot\": \"1.4.2\"\n },\n \"pnpm\": {\n \"overrides\": {\n \"@flue/runtime@0.8.0>@earendil-works/pi-agent-core\": \"0.75.4\",\n \"@flue/runtime@0.8.0>@earendil-works/pi-ai\": \"0.75.4\",\n \"@flue/runtime@0.8.1>@earendil-works/pi-agent-core\": \"0.75.4\",\n \"@flue/runtime@0.8.1>@earendil-works/pi-ai\": \"0.75.4\",\n \"@hono/node-server@>=2.0.0 <2.0.10\": \"^2.0.10\",\n \"fast-uri@<3.1.5\": \"3.1.8\",\n \"brace-expansion@>=3.0.0 <5.0.9\": \"5.0.12\",\n \"postcss@<8.5.18\": \"^8.5.18\",\n \"valibot@<1.4.2\": \"^1.4.2\",\n \"ip-address@<10.2.2\": \"10.7.1\",\n \"undici@<7.29.0\": \"7.29.1\",\n \"hono@<4.12.34\": \"4.13.7\",\n \"@hono/node-server@<1.19.15\": \"^1.19.15\",\n \"nanoid@<3.3.18\": \"^3.3.18\",\n \"js-yaml@3\": \"^3.15.1\",\n \"js-yaml@4\": \"^4.3.1\",\n \"sharp@<0.35.0\": \"0.35.5\",\n \"ip-address@>=10.0.0 <10.7.1\": \"10.7.1\",\n \"fast-uri@>=3.0.0 <3.1.8\": \"3.1.8\",\n \"undici@>=7.0.0 <7.29.1\": \"7.29.1\",\n \"js-yaml@>=5.0.0 <5.4.1\": \"5.4.1\",\n \"brace-expansion@>=5.0.0 <5.0.12\": \"5.0.12\",\n \"hono@>=4.0.0 <4.13.7\": \"4.13.7\",\n \"proxy-addr@>=2.0.0 <2.0.8\": \"2.0.8\",\n \"source-map-js@>=1.0.0 <1.2.2\": \"1.2.2\",\n \"smol-toml@>=1.0.0 <1.9.0\": \"1.9.0\",\n \"sharp@>=0.35.0 <0.35.5\": \"0.35.5\",\n \"@modelcontextprotocol/sdk@>=1.0.0 <1.31.0\": \"1.31.0\",\n \"@modelcontextprotocol/client@>=2.0.0 <2.2.0\": \"2.2.0\"\n }\n }\n}", -│ │ │ │ "type": "text" -│ │ │ │ } -│ │ │ │ ], -│ │ │ │ "details": { -│ │ │ │ "command": "cat ./package.json", -│ │ │ │ "exitCode": 0 -│ │ │ │ } -│ │ │ │ } -│ │ │ │ metadata: { -│ │ │ │ "flue.session": "task:skill:", -│ │ │ │ "flue.tool_name": "bash", -│ │ │ │ "provider": "flue" -│ │ │ │ } -│ │ │ │ metrics: { -│ │ │ │ "duration_ms": 0 -│ │ │ │ } -│ │ │ ├── flue.turn [llm] -│ │ │ │ input: [ -│ │ │ │ { -│ │ │ │ "content": [ -│ │ │ │ { -│ │ │ │ "arguments": { -│ │ │ │ "command": "cat ./package.json", -│ │ │ │ "timeout": 100000 -│ │ │ │ }, -│ │ │ │ "id": "", -│ │ │ │ "name": "bash", -│ │ │ │ "type": "toolCall" -│ │ │ │ } -│ │ │ │ ], -│ │ │ │ "role": "assistant" -│ │ │ │ }, -│ │ │ │ { -│ │ │ │ "content": [ -│ │ │ │ { -│ │ │ │ "text": "{\n \"name\": \"flue-instrumentation-scenario\",\n \"private\": true,\n \"type\": \"module\",\n \"braintrustScenario\": {\n \"bump\": {\n \"dependencies\": {\n \"flue-cli-v0-8-latest\": {\n \"package\": \"@flue/cli\",\n \"range\": \">=0.8.0 <0.9.0\"\n },\n \"flue-cli-v1-latest\": {\n \"package\": \"@flue/cli\",\n \"range\": \">=1.0.0-beta.3 <2.0.0-0\",\n \"allowPrerelease\": true\n },\n \"flue-runtime-v0-8-latest\": {\n \"package\": \"@flue/runtime\",\n \"range\": \">=0.8.0 <0.9.0\"\n },\n \"flue-runtime-v1-latest\": {\n \"package\": \"@flue/runtime\",\n \"range\": \">=1.0.0-beta.3 <2.0.0-0\",\n \"allowPrerelease\": true\n },\n \"flue-runtime-v2-latest\": {\n \"package\": \"@flue/runtime\",\n \"range\": \">=2.0.0 <3.0.0\"\n }\n }\n }\n },\n \"dependencies\": {\n \"@flue/cli\": \"0.8.0\",\n \"@flue/runtime\": \"0.8.0\",\n \"flue-cli-v0-8-latest\": \"npm:@flue/cli@0.8.1\",\n \"flue-cli-v1\": \"npm:@flue/cli@1.0.0-beta.3\",\n \"flue-cli-v1-latest\": \"npm:@flue/cli@1.0.0-beta.9\",\n \"flue-runtime-v0-8-latest\": \"npm:@flue/runtime@0.8.1\",\n \"flue-runtime-v1\": \"npm:@flue/runtime@1.0.0-beta.3\",\n \"flue-runtime-v1-latest\": \"npm:@flue/runtime@1.0.0-beta.9\",\n \"flue-runtime-v2\": \"npm:@flue/runtime@2.0.0\",\n \"flue-runtime-v2-latest\": \"npm:@flue/runtime@2.0.1\",\n \"pi-ai-v2\": \"npm:@earendil-works/pi-ai@0.83.0\",\n \"valibot\": \"1.4.2\"\n },\n \"pnpm\": {\n \"overrides\": {\n \"@flue/runtime@0.8.0>@earendil-works/pi-agent-core\": \"0.75.4\",\n \"@flue/runtime@0.8.0>@earendil-works/pi-ai\": \"0.75.4\",\n \"@flue/runtime@0.8.1>@earendil-works/pi-agent-core\": \"0.75.4\",\n \"@flue/runtime@0.8.1>@earendil-works/pi-ai\": \"0.75.4\",\n \"@hono/node-server@>=2.0.0 <2.0.10\": \"^2.0.10\",\n \"fast-uri@<3.1.5\": \"3.1.8\",\n \"brace-expansion@>=3.0.0 <5.0.9\": \"5.0.12\",\n \"postcss@<8.5.18\": \"^8.5.18\",\n \"valibot@<1.4.2\": \"^1.4.2\",\n \"ip-address@<10.2.2\": \"10.7.1\",\n \"undici@<7.29.0\": \"7.29.1\",\n \"hono@<4.12.34\": \"4.13.7\",\n \"@hono/node-server@<1.19.15\": \"^1.19.15\",\n \"nanoid@<3.3.18\": \"^3.3.18\",\n \"js-yaml@3\": \"^3.15.1\",\n \"js-yaml@4\": \"^4.3.1\",\n \"sharp@<0.35.0\": \"0.35.5\",\n \"ip-address@>=10.0.0 <10.7.1\": \"10.7.1\",\n \"fast-uri@>=3.0.0 <3.1.8\": \"3.1.8\",\n \"undici@>=7.0.0 <7.29.1\": \"7.29.1\",\n \"js-yaml@>=5.0.0 <5.4.1\": \"5.4.1\",\n \"brace-expansion@>=5.0.0 <5.0.12\": \"5.0.12\",\n \"hono@>=4.0.0 <4.13.7\": \"4.13.7\",\n \"proxy-addr@>=2.0.0 <2.0.8\": \"2.0.8\",\n \"source-map-js@>=1.0.0 <1.2.2\": \"1.2.2\",\n \"smol-toml@>=1.0.0 <1.9.0\": \"1.9.0\",\n \"sharp@>=0.35.0 <0.35.5\": \"0.35.5\",\n \"@modelcontextprotocol/sdk@>=1.0.0 <1.31.0\": \"1.31.0\",\n \"@modelcontextprotocol/client@>=2.0.0 <2.2.0\": \"2.2.0\"\n }\n }\n}", -│ │ │ │ "type": "text" -│ │ │ │ } -│ │ │ │ ], -│ │ │ │ "isError": false, -│ │ │ │ "role": "toolResult", -│ │ │ │ "toolCallId": "", -│ │ │ │ "toolName": "bash" -│ │ │ │ } -│ │ │ │ ] -│ │ │ │ output: { -│ │ │ │ "content": [ -│ │ │ │ { -│ │ │ │ "arguments": { -│ │ │ │ "command": "printf SKILL_DONE", -│ │ │ │ "timeout": 100000 -│ │ │ │ }, -│ │ │ │ "id": "", -│ │ │ │ "name": "bash", -│ │ │ │ "type": "toolCall" -│ │ │ │ } -│ │ │ │ ], -│ │ │ │ "role": "assistant" -│ │ │ │ } -│ │ │ │ metadata: { -│ │ │ │ "flue.api": "openai-responses", -│ │ │ │ "flue.input_message_offset": 15, -│ │ │ │ "flue.input_mode": "delta", -│ │ │ │ "flue.model": "gpt-5.4-nano", -│ │ │ │ "flue.provider": "openai", -│ │ │ │ "flue.session": "task:skill:", -│ │ │ │ "flue.stop_reason": "toolUse", -│ │ │ │ "flue.turn_purpose": "agent", -│ │ │ │ "model": "gpt-5.4-nano", -│ │ │ │ "provider": "openai" -│ │ │ │ } -│ │ │ │ metrics: { -│ │ │ │ "completion_tokens": 59, -│ │ │ │ "duration_ms": 0, -│ │ │ │ "estimated_cost": 0.0004814700000000001, -│ │ │ │ "prompt_cache_creation_tokens": 0, -│ │ │ │ "prompt_cached_tokens": 1536, -│ │ │ │ "prompt_tokens": 3421, -│ │ │ │ "tokens": 3480 -│ │ │ │ } -│ │ │ ├── tool:bash [tool] -│ │ │ │ input: { -│ │ │ │ "command": "printf SKILL_DONE", -│ │ │ │ "timeout": 100000 -│ │ │ │ } -│ │ │ │ output: { -│ │ │ │ "content": [ -│ │ │ │ { -│ │ │ │ "text": "SKILL_DONE", -│ │ │ │ "type": "text" -│ │ │ │ } -│ │ │ │ ], -│ │ │ │ "details": { -│ │ │ │ "command": "printf SKILL_DONE", -│ │ │ │ "exitCode": 0 -│ │ │ │ } -│ │ │ │ } -│ │ │ │ metadata: { -│ │ │ │ "flue.session": "task:skill:", -│ │ │ │ "flue.tool_name": "bash", -│ │ │ │ "provider": "flue" -│ │ │ │ } -│ │ │ │ metrics: { -│ │ │ │ "duration_ms": 0 -│ │ │ │ } -│ │ │ └── flue.turn [llm] -│ │ │ input: [ -│ │ │ { -│ │ │ "content": [ -│ │ │ { -│ │ │ "arguments": { -│ │ │ "command": "printf SKILL_DONE", -│ │ │ "timeout": 100000 -│ │ │ }, -│ │ │ "id": "", -│ │ │ "name": "bash", -│ │ │ "type": "toolCall" -│ │ │ } -│ │ │ ], -│ │ │ "role": "assistant" -│ │ │ }, -│ │ │ { -│ │ │ "content": [ -│ │ │ { -│ │ │ "text": "SKILL_DONE", -│ │ │ "type": "text" -│ │ │ } -│ │ │ ], -│ │ │ "isError": false, -│ │ │ "role": "toolResult", -│ │ │ "toolCallId": "", -│ │ │ "toolName": "bash" -│ │ │ } -│ │ │ ] -│ │ │ output: { -│ │ │ "content": [ -│ │ │ { -│ │ │ "text": "SKILL_DONE", -│ │ │ "textSignature": "{\"v\":1,\"id\":\"msg_054a16a2f2ea3217016a705b70a41481a38ece40ecddf69512\",\"phase\":\"final_answer\"}", -│ │ │ "type": "text" -│ │ │ } -│ │ │ ], -│ │ │ "role": "assistant" -│ │ │ } -│ │ │ metadata: { -│ │ │ "flue.api": "openai-responses", -│ │ │ "flue.input_message_offset": 17, -│ │ │ "flue.input_mode": "delta", -│ │ │ "flue.model": "gpt-5.4-nano", -│ │ │ "flue.provider": "openai", -│ │ │ "flue.session": "task:skill:", -│ │ │ "flue.stop_reason": "stop", -│ │ │ "flue.turn_purpose": "agent", -│ │ │ "model": "gpt-5.4-nano", -│ │ │ "provider": "openai" -│ │ │ } -│ │ │ metrics: { -│ │ │ "completion_tokens": 7, -│ │ │ "duration_ms": 0, -│ │ │ "estimated_cost": 0.00031035000000000004, -│ │ │ "prompt_cache_creation_tokens": 0, -│ │ │ "prompt_cached_tokens": 2560, -│ │ │ "prompt_tokens": 3812, -│ │ │ "tokens": 3819 -│ │ │ } -│ │ ├── tool:task [tool] -│ │ │ input: { -│ │ │ "cwd": ".", -│ │ │ "description": "Run the deterministic Flue instrumentation e2e test skill e2e-flue-skill.", -│ │ │ "prompt": "Run the skill named \"e2e-flue-skill\" with arguments {\"marker\":\"SKILL_DONE\"}. Output only the marker and no extra text when finished." -│ │ │ } -│ │ │ output: { -│ │ │ "content": [ -│ │ │ { -│ │ │ "text": "SKILL_DONE", -│ │ │ "type": "text" -│ │ │ } -│ │ │ ], -│ │ │ "details": { -│ │ │ "cwd": ".", -│ │ │ "messageId": "", -│ │ │ "session": "task:skill:", -│ │ │ "taskId": "" -│ │ │ } -│ │ │ } -│ │ │ metadata: { -│ │ │ "flue.session": "skill", -│ │ │ "flue.tool_name": "task", -│ │ │ "provider": "flue" -│ │ │ } -│ │ │ metrics: { -│ │ │ "duration_ms": 0 -│ │ │ } -│ │ └── flue.turn [llm] -│ │ input: [ -│ │ { -│ │ "content": [ -│ │ { -│ │ "text": "Run the skill named \"e2e-flue-skill\".\n\nArguments:\n{\n \"marker\": \"SKILL_DONE\"\n}", -│ │ "type": "text" -│ │ } -│ │ ], -│ │ "role": "user" -│ │ }, -│ │ { -│ │ "content": [ -│ │ { -│ │ "arguments": { -│ │ "cwd": ".", -│ │ "description": "Run the deterministic Flue instrumentation e2e test skill e2e-flue-skill.", -│ │ "prompt": "Run the skill named \"e2e-flue-skill\" with arguments {\"marker\":\"SKILL_DONE\"}. Output only the marker and no extra text when finished." -│ │ }, -│ │ "id": "", -│ │ "name": "task", -│ │ "type": "toolCall" -│ │ } -│ │ ], -│ │ "role": "assistant" -│ │ }, -│ │ { -│ │ "content": [ -│ │ { -│ │ "text": "SKILL_DONE", -│ │ "type": "text" -│ │ } -│ │ ], -│ │ "isError": false, -│ │ "role": "toolResult", -│ │ "toolCallId": "", -│ │ "toolName": "task" -│ │ } -│ │ ] -│ │ output: { -│ │ "content": [ -│ │ { -│ │ "text": "SKILL_DONE", -│ │ "textSignature": "{\"v\":1,\"id\":\"msg_0f099b3fce0721fd016a705b71558881a38c53a767e09d9e31\",\"phase\":\"final_answer\"}", -│ │ "type": "text" -│ │ } -│ │ ], -│ │ "role": "assistant" -│ │ } -│ │ metadata: { -│ │ "flue.api": "openai-responses", -│ │ "flue.model": "gpt-5.4-nano", -│ │ "flue.provider": "openai", -│ │ "flue.session": "skill", -│ │ "flue.stop_reason": "stop", -│ │ "flue.turn_purpose": "agent", -│ │ "model": "gpt-5.4-nano", -│ │ "provider": "openai" -│ │ } -│ │ metrics: { -│ │ "completion_tokens": 7, -│ │ "duration_ms": 0, -│ │ "estimated_cost": 0.00020255000000000002, -│ │ "prompt_cache_creation_tokens": 0, -│ │ "prompt_cached_tokens": 0, -│ │ "prompt_tokens": 969, -│ │ "tokens": 976 -│ │ } -│ ├── flue.task [task] -│ │ input: "Reply with exactly TASK_DONE and no other text." -│ │ output: "TASK_DONE" -│ │ metadata: { -│ │ "flue.session": "task", -│ │ "provider": "flue" -│ │ } -│ │ metrics: { -│ │ "duration_ms": 0 -│ │ } -│ │ └── flue.turn [llm] -│ │ input: [ -│ │ { -│ │ "content": [ -│ │ { -│ │ "text": "Reply with exactly TASK_DONE and no other text.", -│ │ "type": "text" -│ │ } -│ │ ], -│ │ "role": "user" -│ │ } -│ │ ] -│ │ output: { -│ │ "content": [ -│ │ { -│ │ "text": "TASK_DONE", -│ │ "textSignature": "{\"v\":1,\"id\":\"msg_0f3adfdb1510c165016a705b72843c81a0b30d242da683a882\"}", -│ │ "type": "text" -│ │ } -│ │ ], -│ │ "role": "assistant" -│ │ } -│ │ metadata: { -│ │ "flue.api": "openai-responses", -│ │ "flue.input_mode": "full", -│ │ "flue.model": "gpt-4o-mini", -│ │ "flue.provider": "openai", -│ │ "flue.session": "task:task:", -│ │ "flue.stop_reason": "stop", -│ │ "flue.turn_purpose": "agent", -│ │ "model": "gpt-4o-mini", -│ │ "provider": "openai" -│ │ } -│ │ metrics: { -│ │ "completion_tokens": 4, -│ │ "duration_ms": 0, -│ │ "estimated_cost": 0.0001263, -│ │ "prompt_cache_creation_tokens": 0, -│ │ "prompt_cached_tokens": 0, -│ │ "prompt_tokens": 826, -│ │ "tokens": 830 -│ │ } -│ └── flue.compact [task] -│ input: { -│ "estimatedTokens": 1229, -│ "reason": "manual" -│ } -│ output: { -│ "completed": true -│ } -│ metadata: { -│ "flue.operation": "compact", -│ "flue.session": "main", -│ "flue.workflow_name": "instrumentation", -│ "provider": "flue", -│ "scenario": "flue-instrumentation" -│ } -│ metrics: { -│ "duration_ms": 0 -│ } -│ └── compaction:manual [task] -│ input: { -│ "estimatedTokens": 1229, -│ "reason": "manual" -│ } -│ output: { -│ "messagesAfter": 2, -│ "messagesBefore": 8 -│ } -│ metadata: { -│ "flue.compaction_reason": "manual", -│ "flue.session": "main", -│ "provider": "flue" -│ } -│ metrics: { -│ "duration_ms": 0, -│ "messages_after": 2, -│ "messages_before": 8 -│ } -│ └── flue.turn [llm] -│ input: [ -│ { -│ "content": [ -│ { -│ "text": "\n[User]: Complete this instrumented research flow. Call exactly one tool per turn and wait for each tool result before choosing the next tool. Step 1: call lookup with query \"flue instrumentation\". Step 2: use the lookup result id as lookupId and call web_search with query \"Braintrust Flue reasoning stream instrumentation\". Step 3: use the first web_search result url and call summarize_source. After summarize_source returns, reply with exactly PROMPT_DONE and no other text.\n\n[Assistant tool calls]: lookup(query=\"flue instrumentation\")\n\n[Tool result]: {\"id\":\"flue-session-2026\",\"query\":\"flue instrumentation\",\"topic\":\"session instrumentation\"}\n\n[Assistant tool calls]: web_search(lookupId=\"flue-session-2026\", query=\"Braintrust Flue reasoning stream instrumentation\")\n\n[Tool result]: {\"lookupId\":\"flue-session-2026\",\"query\":\"Braintrust Flue reasoning stream instrumentation\",\"results\":[{\"title\":\"Flue reasoning stream instrumentation\",\"url\":\"https://example.test/flue/reasoning-streams\"}]}\n\n[Assistant tool calls]: summarize_source(url=\"https://example.test/flue/reasoning-streams\")\n\n[Tool result]: {\"summary\":\"Flue emits reasoning, tool execution, and LLM turn events separately.\",\"url\":\"https://example.test/flue/reasoning-streams\"}\n\n\nThis is the PREFIX of a turn that was too large to keep. The SUFFIX (recent work) is retained.\n\nSummarize the prefix to provide context for the retained suffix:\n\n## Original Request\n[What did the user ask for in this turn?]\n\n## Early Progress\n- [Key decisions and work done in the prefix]\n\n## Context for Suffix\n- [Information needed to understand the retained recent work]\n\nBe concise. Focus on what's needed to understand the kept suffix.", -│ "type": "text" -│ } -│ ], -│ "role": "user" -│ } -│ ] -│ output: { -│ "content": [ -│ { -│ "text": "## Original Request\nThe user requested a step-by-step execution of an instrumented research flow involving tool calls to gather information on \"flue instrumentation\" and \"", -│ "textSignature": "{\"v\":1,\"id\":\"msg_04d28ba03840bb65016a705b7358a081a38e69464057840036\"}", -│ "type": "text" -│ } -│ ], -│ "role": "assistant" -│ } -│ metadata: { -│ "flue.api": "openai-responses", -│ "flue.model": "gpt-4o-mini", -│ "flue.provider": "openai", -│ "flue.session": "main", -│ "flue.stop_reason": "stop", -│ "flue.turn_purpose": "compaction_prefix", -│ "model": "gpt-4o-mini", -│ "provider": "openai" -│ } -│ metrics: { -│ "completion_tokens": 0, -│ "duration_ms": 0, -│ "estimated_cost": 0, -│ "prompt_cache_creation_tokens": 0, -│ "prompt_cached_tokens": 0, -│ "prompt_tokens": 0, -│ "tokens": 0 -│ } -└── flue.prompt [task] - input: [ - { - "content": [ - { - "text": "Complete this instrumented research flow. Call exactly one tool per turn and wait for each tool result before choosing the next tool. Step 1: call lookup with query \"flue instrumentation\". Step 2: use the lookup result id as lookupId and call web_search with query \"Braintrust Flue reasoning stream instrumentation\". Step 3: use the first web_search result url and call summarize_source. After summarize_source returns, reply with exactly PROMPT_DONE and no other text.", - "type": "text" - } - ], - "role": "user" - } - ] - output: "PROMPT_DONE" - metadata: { - "flue.operation": "prompt", - "flue.session": "main", - "flue.workflow_name": "instrumentation", - "provider": "flue", - "scenario": "flue-instrumentation" - } - metrics: { - "duration_ms": 0 - } - ├── flue.turn [llm] - │ input: [ - │ { - │ "content": [ - │ { - │ "text": "Complete this instrumented research flow. Call exactly one tool per turn and wait for each tool result before choosing the next tool. Step 1: call lookup with query \"flue instrumentation\". Step 2: use the lookup result id as lookupId and call web_search with query \"Braintrust Flue reasoning stream instrumentation\". Step 3: use the first web_search result url and call summarize_source. After summarize_source returns, reply with exactly PROMPT_DONE and no other text.", - │ "type": "text" - │ } - │ ], - │ "role": "user" - │ } - │ ] - │ output: { - │ "content": [ - │ { - │ "arguments": { - │ "query": "flue instrumentation" - │ }, - │ "id": "", - │ "name": "lookup", - │ "type": "toolCall" - │ } - │ ], - │ "role": "assistant" - │ } - │ metadata: { - │ "flue.api": "openai-responses", - │ "flue.input_mode": "full", - │ "flue.model": "gpt-5.4-nano", - │ "flue.provider": "openai", - │ "flue.session": "main", - │ "flue.stop_reason": "toolUse", - │ "flue.turn_purpose": "agent", - │ "model": "gpt-5.4-nano", - │ "provider": "openai" - │ } - │ metrics: { - │ "completion_tokens": 19, - │ "duration_ms": 0, - │ "estimated_cost": 0.00022475000000000001, - │ "prompt_cache_creation_tokens": 0, - │ "prompt_cached_tokens": 0, - │ "prompt_tokens": 1005, - │ "tokens": 1024 - │ } - ├── tool:lookup [tool] - │ input: { - │ "query": "flue instrumentation" - │ } - │ output: { - │ "content": [ - │ { - │ "text": "{\"id\":\"flue-session-2026\",\"query\":\"flue instrumentation\",\"topic\":\"session instrumentation\"}", - │ "type": "text" - │ } - │ ], - │ "details": { - │ "customTool": "lookup" - │ } - │ } - │ metadata: { - │ "flue.session": "main", - │ "flue.tool_name": "lookup", - │ "provider": "flue" - │ } - │ metrics: { - │ "duration_ms": 0 - │ } - ├── flue.turn [llm] - │ input: [ - │ { - │ "content": [ - │ { - │ "arguments": { - │ "query": "flue instrumentation" - │ }, - │ "id": "", - │ "name": "lookup", - │ "type": "toolCall" - │ } - │ ], - │ "role": "assistant" - │ }, - │ { - │ "content": [ - │ { - │ "text": "{\"id\":\"flue-session-2026\",\"query\":\"flue instrumentation\",\"topic\":\"session instrumentation\"}", - │ "type": "text" - │ } - │ ], - │ "isError": false, - │ "role": "toolResult", - │ "toolCallId": "", - │ "toolName": "lookup" - │ } - │ ] - │ output: { - │ "content": [ - │ { - │ "arguments": { - │ "lookupId": "flue-session-2026", - │ "query": "Braintrust Flue reasoning stream instrumentation" - │ }, - │ "id": "", - │ "name": "web_search", - │ "type": "toolCall" - │ } - │ ], - │ "role": "assistant" - │ } - │ metadata: { - │ "flue.api": "openai-responses", - │ "flue.input_message_offset": 1, - │ "flue.input_mode": "delta", - │ "flue.model": "gpt-5.4-nano", - │ "flue.provider": "openai", - │ "flue.session": "main", - │ "flue.stop_reason": "toolUse", - │ "flue.turn_purpose": "agent", - │ "model": "gpt-5.4-nano", - │ "provider": "openai" - │ } - │ metrics: { - │ "completion_tokens": 34, - │ "duration_ms": 0, - │ "estimated_cost": 0.00025350000000000004, - │ "prompt_cache_creation_tokens": 0, - │ "prompt_cached_tokens": 0, - │ "prompt_tokens": 1055, - │ "tokens": 1089 - │ } - ├── tool:web_search [tool] - │ input: { - │ "lookupId": "flue-session-2026", - │ "query": "Braintrust Flue reasoning stream instrumentation" - │ } - │ output: { - │ "content": [ - │ { - │ "text": "{\"lookupId\":\"flue-session-2026\",\"query\":\"Braintrust Flue reasoning stream instrumentation\",\"results\":[{\"title\":\"Flue reasoning stream instrumentation\",\"url\":\"https://example.test/flue/reasoning-streams\"}]}", - │ "type": "text" - │ } - │ ], - │ "details": { - │ "customTool": "web_search" - │ } - │ } - │ metadata: { - │ "flue.session": "main", - │ "flue.tool_name": "web_search", - │ "provider": "flue" - │ } - │ metrics: { - │ "duration_ms": 0 - │ } - ├── flue.turn [llm] - │ input: [ - │ { - │ "content": [ - │ { - │ "arguments": { - │ "lookupId": "flue-session-2026", - │ "query": "Braintrust Flue reasoning stream instrumentation" - │ }, - │ "id": "", - │ "name": "web_search", - │ "type": "toolCall" - │ } - │ ], - │ "role": "assistant" - │ }, - │ { - │ "content": [ - │ { - │ "text": "{\"lookupId\":\"flue-session-2026\",\"query\":\"Braintrust Flue reasoning stream instrumentation\",\"results\":[{\"title\":\"Flue reasoning stream instrumentation\",\"url\":\"https://example.test/flue/reasoning-streams\"}]}", - │ "type": "text" - │ } - │ ], - │ "isError": false, - │ "role": "toolResult", - │ "toolCallId": "", - │ "toolName": "web_search" - │ } - │ ] - │ output: { - │ "content": [ - │ { - │ "arguments": { - │ "url": "https://example.test/flue/reasoning-streams" - │ }, - │ "id": "", - │ "name": "summarize_source", - │ "type": "toolCall" - │ } - │ ], - │ "role": "assistant" - │ } - │ metadata: { - │ "flue.api": "openai-responses", - │ "flue.input_message_offset": 3, - │ "flue.input_mode": "delta", - │ "flue.model": "gpt-5.4-nano", - │ "flue.provider": "openai", - │ "flue.session": "main", - │ "flue.stop_reason": "toolUse", - │ "flue.turn_purpose": "agent", - │ "model": "gpt-5.4-nano", - │ "provider": "openai" - │ } - │ metrics: { - │ "completion_tokens": 30, - │ "duration_ms": 0, - │ "estimated_cost": 0.00026690000000000004, - │ "prompt_cache_creation_tokens": 0, - │ "prompt_cached_tokens": 0, - │ "prompt_tokens": 1147, - │ "tokens": 1177 - │ } - ├── tool:summarize_source [tool] - │ input: { - │ "url": "https://example.test/flue/reasoning-streams" - │ } - │ output: { - │ "content": [ - │ { - │ "text": "{\"summary\":\"Flue emits reasoning, tool execution, and LLM turn events separately.\",\"url\":\"https://example.test/flue/reasoning-streams\"}", - │ "type": "text" - │ } - │ ], - │ "details": { - │ "customTool": "summarize_source" - │ } - │ } - │ metadata: { - │ "flue.session": "main", - │ "flue.tool_name": "summarize_source", - │ "provider": "flue" - │ } - │ metrics: { - │ "duration_ms": 0 - │ } - └── flue.turn [llm] - input: [ - { - "content": [ - { - "arguments": { - "url": "https://example.test/flue/reasoning-streams" - }, - "id": "", - "name": "summarize_source", - "type": "toolCall" - } - ], - "role": "assistant" - }, - { - "content": [ - { - "text": "{\"summary\":\"Flue emits reasoning, tool execution, and LLM turn events separately.\",\"url\":\"https://example.test/flue/reasoning-streams\"}", - "type": "text" - } - ], - "isError": false, - "role": "toolResult", - "toolCallId": "", - "toolName": "summarize_source" - } - ] - output: { - "content": [ - { - "text": "PROMPT_DONE", - "textSignature": "{\"v\":1,\"id\":\"msg_045458052dc10379016a705b62dbd8819189b8287061debf12\",\"phase\":\"final_answer\"}", - "type": "text" - } - ], - "role": "assistant" - } - metadata: { - "flue.api": "openai-responses", - "flue.input_message_offset": 5, - "flue.input_mode": "delta", - "flue.model": "gpt-5.4-nano", - "flue.provider": "openai", - "flue.session": "main", - "flue.stop_reason": "stop", - "flue.turn_purpose": "agent", - "model": "gpt-5.4-nano", - "provider": "openai" - } - metrics: { - "completion_tokens": 7, - "duration_ms": 0, - "estimated_cost": 0.00025315000000000005, - "prompt_cache_creation_tokens": 0, - "prompt_cached_tokens": 0, - "prompt_tokens": 1222, - "tokens": 1229 - } diff --git a/e2e/scenarios/flue-instrumentation/__snapshots__/flue-v0-8-latest-cli.span-tree.json b/e2e/scenarios/flue-instrumentation/__snapshots__/flue-v0-8-latest-cli.span-tree.json deleted file mode 100644 index 704887bdb..000000000 --- a/e2e/scenarios/flue-instrumentation/__snapshots__/flue-v0-8-latest-cli.span-tree.json +++ /dev/null @@ -1,1662 +0,0 @@ -{ - "span_tree": [ - { - "name": "workflow:instrumentation", - "type": "task", - "children": [ - { - "name": "flue.skill", - "type": "task", - "children": [ - { - "name": "flue.turn", - "type": "llm", - "children": [ - { - "name": "flue.task", - "type": "task", - "children": [ - { - "name": "flue.turn", - "type": "llm", - "children": [], - "input": [ - { - "content": [ - { - "text": "Run the skill named \"e2e-flue-skill\" with arguments {\"marker\":\"SKILL_DONE\"}. Output only the marker and no extra text when finished.", - "type": "text" - } - ], - "role": "user" - } - ], - "output": { - "content": [ - { - "arguments": { - "agent": "e2e-flue-skill", - "cwd": ".", - "description": "Run e2e-flue-skill with marker SKILL_DONE", - "prompt": "Run the skill named \"e2e-flue-skill\" with arguments {\"marker\":\"SKILL_DONE\"}. Ensure completion and report the final output marker only." - }, - "id": "", - "name": "task", - "type": "toolCall" - } - ], - "role": "assistant" - }, - "metadata": { - "flue.api": "openai-responses", - "flue.input_mode": "full", - "flue.model": "gpt-5.4-nano", - "flue.provider": "openai", - "flue.session": "task:skill:", - "flue.stop_reason": "toolUse", - "flue.turn_purpose": "agent", - "model": "gpt-5.4-nano", - "provider": "openai" - }, - "metrics": { - "completion_tokens": 113, - "duration_ms": 0, - "estimated_cost": 0.00031225000000000003, - "prompt_cache_creation_tokens": 0, - "prompt_cached_tokens": 0, - "prompt_tokens": 855, - "tokens": 968 - } - }, - { - "name": "tool:task", - "type": "tool", - "children": [], - "input": { - "agent": "e2e-flue-skill", - "cwd": ".", - "description": "Run e2e-flue-skill with marker SKILL_DONE", - "prompt": "Run the skill named \"e2e-flue-skill\" with arguments {\"marker\":\"SKILL_DONE\"}. Ensure completion and report the final output marker only." - }, - "output": { - "content": [ - { - "text": "[flue] Subagent \"e2e-flue-skill\" is not declared. Available: (none).", - "type": "text" - } - ], - "details": {} - }, - "metadata": { - "flue.session": "task:skill:", - "flue.tool_name": "task", - "provider": "flue" - }, - "metrics": { - "duration_ms": 0 - }, - "error": "{\"content\":[{\"type\":\"text\",\"text\":\"[flue] Subagent \\\"e2e-flue-skill\\\" is not declared. Available: (none).\"}],\"details\":{}}" - }, - { - "name": "flue.turn", - "type": "llm", - "children": [], - "input": [ - { - "content": [ - { - "arguments": { - "agent": "e2e-flue-skill", - "cwd": ".", - "description": "Run e2e-flue-skill with marker SKILL_DONE", - "prompt": "Run the skill named \"e2e-flue-skill\" with arguments {\"marker\":\"SKILL_DONE\"}. Ensure completion and report the final output marker only." - }, - "id": "", - "name": "task", - "type": "toolCall" - } - ], - "role": "assistant" - }, - { - "content": [ - { - "text": "[flue] Subagent \"e2e-flue-skill\" is not declared. Available: (none).", - "type": "text" - } - ], - "isError": true, - "role": "toolResult", - "toolCallId": "", - "toolName": "task" - } - ], - "output": { - "content": [ - { - "arguments": { - "command": "find . -maxdepth 4 -name AGENTS.md -print", - "timeout": 100000 - }, - "id": "", - "name": "bash", - "type": "toolCall" - } - ], - "role": "assistant" - }, - "metadata": { - "flue.api": "openai-responses", - "flue.input_message_offset": 1, - "flue.input_mode": "delta", - "flue.model": "gpt-5.4-nano", - "flue.provider": "openai", - "flue.session": "task:skill:", - "flue.stop_reason": "toolUse", - "flue.turn_purpose": "agent", - "model": "gpt-5.4-nano", - "provider": "openai" - }, - "metrics": { - "completion_tokens": 94, - "duration_ms": 0, - "estimated_cost": 0.00031790000000000003, - "prompt_cache_creation_tokens": 0, - "prompt_cached_tokens": 0, - "prompt_tokens": 1002, - "tokens": 1096 - } - }, - { - "name": "tool:bash", - "type": "tool", - "children": [], - "input": { - "command": "find . -maxdepth 4 -name AGENTS.md -print", - "timeout": 100000 - }, - "output": { - "content": [ - { - "text": "(no output)", - "type": "text" - } - ], - "details": { - "command": "find . -maxdepth 4 -name AGENTS.md -print", - "exitCode": 0 - } - }, - "metadata": { - "flue.session": "task:skill:", - "flue.tool_name": "bash", - "provider": "flue" - }, - "metrics": { - "duration_ms": 0 - } - }, - { - "name": "flue.turn", - "type": "llm", - "children": [], - "input": [ - { - "content": [ - { - "arguments": { - "command": "find . -maxdepth 4 -name AGENTS.md -print", - "timeout": 100000 - }, - "id": "", - "name": "bash", - "type": "toolCall" - } - ], - "role": "assistant" - }, - { - "content": [ - { - "text": "(no output)", - "type": "text" - } - ], - "isError": false, - "role": "toolResult", - "toolCallId": "", - "toolName": "bash" - } - ], - "output": { - "content": [ - { - "arguments": { - "command": "find . -maxdepth 4 -type f -name AGENTS.md -print -exec sed -n '1,200p' {} \\;", - "timeout": 100000 - }, - "id": "", - "name": "bash", - "type": "toolCall" - } - ], - "role": "assistant" - }, - "metadata": { - "flue.api": "openai-responses", - "flue.input_message_offset": 3, - "flue.input_mode": "delta", - "flue.model": "gpt-5.4-nano", - "flue.provider": "openai", - "flue.session": "task:skill:", - "flue.stop_reason": "toolUse", - "flue.turn_purpose": "agent", - "model": "gpt-5.4-nano", - "provider": "openai" - }, - "metrics": { - "completion_tokens": 87, - "duration_ms": 0, - "estimated_cost": 0.00034815000000000003, - "prompt_cache_creation_tokens": 0, - "prompt_cached_tokens": 0, - "prompt_tokens": 1197, - "tokens": 1284 - } - }, - { - "name": "tool:bash", - "type": "tool", - "children": [], - "input": { - "command": "find . -maxdepth 4 -type f -name AGENTS.md -print -exec sed -n '1,200p' {} \\;", - "timeout": 100000 - }, - "output": { - "content": [ - { - "text": "(no output)", - "type": "text" - } - ], - "details": { - "command": "find . -maxdepth 4 -type f -name AGENTS.md -print -exec sed -n '1,200p' {} \\;", - "exitCode": 0 - } - }, - "metadata": { - "flue.session": "task:skill:", - "flue.tool_name": "bash", - "provider": "flue" - }, - "metrics": { - "duration_ms": 0 - } - }, - { - "name": "flue.turn", - "type": "llm", - "children": [], - "input": [ - { - "content": [ - { - "arguments": { - "command": "find . -maxdepth 4 -type f -name AGENTS.md -print -exec sed -n '1,200p' {} \\;", - "timeout": 100000 - }, - "id": "", - "name": "bash", - "type": "toolCall" - } - ], - "role": "assistant" - }, - { - "content": [ - { - "text": "(no output)", - "type": "text" - } - ], - "isError": false, - "role": "toolResult", - "toolCallId": "", - "toolName": "bash" - } - ], - "output": { - "content": [ - { - "arguments": { - "command": "find . -maxdepth 6 -type f -name 'AGENTS.md' -print", - "timeout": 100000 - }, - "id": "", - "name": "bash", - "type": "toolCall" - } - ], - "role": "assistant" - }, - "metadata": { - "flue.api": "openai-responses", - "flue.input_message_offset": 5, - "flue.input_mode": "delta", - "flue.model": "gpt-5.4-nano", - "flue.provider": "openai", - "flue.session": "task:skill:", - "flue.stop_reason": "toolUse", - "flue.turn_purpose": "agent", - "model": "gpt-5.4-nano", - "provider": "openai" - }, - "metrics": { - "completion_tokens": 58, - "duration_ms": 0, - "estimated_cost": 0.0003321, - "prompt_cache_creation_tokens": 0, - "prompt_cached_tokens": 0, - "prompt_tokens": 1298, - "tokens": 1356 - } - }, - { - "name": "tool:bash", - "type": "tool", - "children": [], - "input": { - "command": "find . -maxdepth 6 -type f -name 'AGENTS.md' -print", - "timeout": 100000 - }, - "output": { - "content": [ - { - "text": "(no output)", - "type": "text" - } - ], - "details": { - "command": "find . -maxdepth 6 -type f -name 'AGENTS.md' -print", - "exitCode": 0 - } - }, - "metadata": { - "flue.session": "task:skill:", - "flue.tool_name": "bash", - "provider": "flue" - }, - "metrics": { - "duration_ms": 0 - } - }, - { - "name": "flue.turn", - "type": "llm", - "children": [], - "input": [ - { - "content": [ - { - "arguments": { - "command": "find . -maxdepth 6 -type f -name 'AGENTS.md' -print", - "timeout": 100000 - }, - "id": "", - "name": "bash", - "type": "toolCall" - } - ], - "role": "assistant" - }, - { - "content": [ - { - "text": "(no output)", - "type": "text" - } - ], - "isError": false, - "role": "toolResult", - "toolCallId": "", - "toolName": "bash" - } - ], - "output": { - "content": [ - { - "arguments": { - "command": "find . -maxdepth 5 -type f -name '*flue*skill*' -o -name '*skill*' | head", - "timeout": 100000 - }, - "id": "", - "name": "bash", - "type": "toolCall" - } - ], - "role": "assistant" - }, - "metadata": { - "flue.api": "openai-responses", - "flue.input_message_offset": 7, - "flue.input_mode": "delta", - "flue.model": "gpt-5.4-nano", - "flue.provider": "openai", - "flue.session": "task:skill:", - "flue.stop_reason": "toolUse", - "flue.turn_purpose": "agent", - "model": "gpt-5.4-nano", - "provider": "openai" - }, - "metrics": { - "completion_tokens": 68, - "duration_ms": 0, - "estimated_cost": 0.00035900000000000005, - "prompt_cache_creation_tokens": 0, - "prompt_cached_tokens": 0, - "prompt_tokens": 1370, - "tokens": 1438 - } - }, - { - "name": "tool:bash", - "type": "tool", - "children": [], - "input": { - "command": "find . -maxdepth 5 -type f -name '*flue*skill*' -o -name '*skill*' | head", - "timeout": 100000 - }, - "output": { - "content": [ - { - "text": "./.agents/skills\n./.agents/skills/e2e-flue-skill", - "type": "text" - } - ], - "details": { - "command": "find . -maxdepth 5 -type f -name '*flue*skill*' -o -name '*skill*' | head", - "exitCode": 0 - } - }, - "metadata": { - "flue.session": "task:skill:", - "flue.tool_name": "bash", - "provider": "flue" - }, - "metrics": { - "duration_ms": 0 - } - }, - { - "name": "flue.turn", - "type": "llm", - "children": [], - "input": [ - { - "content": [ - { - "arguments": { - "command": "find . -maxdepth 5 -type f -name '*flue*skill*' -o -name '*skill*' | head", - "timeout": 100000 - }, - "id": "", - "name": "bash", - "type": "toolCall" - } - ], - "role": "assistant" - }, - { - "content": [ - { - "text": "./.agents/skills\n./.agents/skills/e2e-flue-skill", - "type": "text" - } - ], - "isError": false, - "role": "toolResult", - "toolCallId": "", - "toolName": "bash" - } - ], - "output": { - "content": [ - { - "arguments": { - "command": "find ./.agents/skills/e2e-flue-skill -maxdepth 2 -type f -print", - "timeout": 100000 - }, - "id": "", - "name": "bash", - "type": "toolCall" - } - ], - "role": "assistant" - }, - "metadata": { - "flue.api": "openai-responses", - "flue.input_message_offset": 9, - "flue.input_mode": "delta", - "flue.model": "gpt-5.4-nano", - "flue.provider": "openai", - "flue.session": "task:skill:", - "flue.stop_reason": "toolUse", - "flue.turn_purpose": "agent", - "model": "gpt-5.4-nano", - "provider": "openai" - }, - "metrics": { - "completion_tokens": 109, - "duration_ms": 0, - "estimated_cost": 0.00043885000000000007, - "prompt_cache_creation_tokens": 0, - "prompt_cached_tokens": 0, - "prompt_tokens": 1513, - "tokens": 1622 - } - }, - { - "name": "tool:bash", - "type": "tool", - "children": [], - "input": { - "command": "find ./.agents/skills/e2e-flue-skill -maxdepth 2 -type f -print", - "timeout": 100000 - }, - "output": { - "content": [ - { - "text": "./.agents/skills/e2e-flue-skill/SKILL.md", - "type": "text" - } - ], - "details": { - "command": "find ./.agents/skills/e2e-flue-skill -maxdepth 2 -type f -print", - "exitCode": 0 - } - }, - "metadata": { - "flue.session": "task:skill:", - "flue.tool_name": "bash", - "provider": "flue" - }, - "metrics": { - "duration_ms": 0 - } - }, - { - "name": "flue.turn", - "type": "llm", - "children": [], - "input": [ - { - "content": [ - { - "arguments": { - "command": "find ./.agents/skills/e2e-flue-skill -maxdepth 2 -type f -print", - "timeout": 100000 - }, - "id": "", - "name": "bash", - "type": "toolCall" - } - ], - "role": "assistant" - }, - { - "content": [ - { - "text": "./.agents/skills/e2e-flue-skill/SKILL.md", - "type": "text" - } - ], - "isError": false, - "role": "toolResult", - "toolCallId": "", - "toolName": "bash" - } - ], - "output": { - "content": [ - { - "arguments": { - "limit": 200, - "offset": 1, - "path": "./.agents/skills/e2e-flue-skill/SKILL.md" - }, - "id": "", - "name": "read", - "type": "toolCall" - } - ], - "role": "assistant" - }, - "metadata": { - "flue.api": "openai-responses", - "flue.input_message_offset": 11, - "flue.input_mode": "delta", - "flue.model": "gpt-5.4-nano", - "flue.provider": "openai", - "flue.session": "task:skill:", - "flue.stop_reason": "toolUse", - "flue.turn_purpose": "agent", - "model": "gpt-5.4-nano", - "provider": "openai" - }, - "metrics": { - "completion_tokens": 64, - "duration_ms": 0, - "estimated_cost": 0.00015592, - "prompt_cache_creation_tokens": 0, - "prompt_cached_tokens": 1536, - "prompt_tokens": 1762, - "tokens": 1826 - } - }, - { - "name": "tool:read", - "type": "tool", - "children": [], - "input": { - "limit": 200, - "offset": 1, - "path": "./.agents/skills/e2e-flue-skill/SKILL.md" - }, - "output": { - "content": [ - { - "text": "---\nname: e2e-flue-skill\ndescription: Deterministic skill used by the Braintrust Flue instrumentation e2e test.\n---\n\nReturn the marker from the provided args exactly once. Output no other text.\n", - "type": "text" - } - ], - "details": { - "lines": 7, - "path": "./.agents/skills/e2e-flue-skill/SKILL.md" - } - }, - "metadata": { - "flue.session": "task:skill:", - "flue.tool_name": "read", - "provider": "flue" - }, - "metrics": { - "duration_ms": 0 - } - }, - { - "name": "flue.turn", - "type": "llm", - "children": [], - "input": [ - { - "content": [ - { - "arguments": { - "limit": 200, - "offset": 1, - "path": "./.agents/skills/e2e-flue-skill/SKILL.md" - }, - "id": "", - "name": "read", - "type": "toolCall" - } - ], - "role": "assistant" - }, - { - "content": [ - { - "text": "---\nname: e2e-flue-skill\ndescription: Deterministic skill used by the Braintrust Flue instrumentation e2e test.\n---\n\nReturn the marker from the provided args exactly once. Output no other text.\n", - "type": "text" - } - ], - "isError": false, - "role": "toolResult", - "toolCallId": "", - "toolName": "read" - } - ], - "output": { - "content": [ - { - "arguments": { - "command": "cat ./package.json", - "timeout": 100000 - }, - "id": "", - "name": "bash", - "type": "toolCall" - } - ], - "role": "assistant" - }, - "metadata": { - "flue.api": "openai-responses", - "flue.input_message_offset": 13, - "flue.input_mode": "delta", - "flue.model": "gpt-5.4-nano", - "flue.provider": "openai", - "flue.session": "task:skill:", - "flue.stop_reason": "toolUse", - "flue.turn_purpose": "agent", - "model": "gpt-5.4-nano", - "provider": "openai" - }, - "metrics": { - "completion_tokens": 90, - "duration_ms": 0, - "estimated_cost": 0.00021242000000000003, - "prompt_cache_creation_tokens": 0, - "prompt_cached_tokens": 1536, - "prompt_tokens": 1882, - "tokens": 1972 - } - }, - { - "name": "tool:bash", - "type": "tool", - "children": [], - "input": { - "command": "cat ./package.json", - "timeout": 100000 - }, - "output": { - "content": [ - { - "text": "{\n \"name\": \"flue-instrumentation-scenario\",\n \"private\": true,\n \"type\": \"module\",\n \"braintrustScenario\": {\n \"bump\": {\n \"dependencies\": {\n \"flue-cli-v0-8-latest\": {\n \"package\": \"@flue/cli\",\n \"range\": \">=0.8.0 <0.9.0\"\n },\n \"flue-cli-v1-latest\": {\n \"package\": \"@flue/cli\",\n \"range\": \">=1.0.0-beta.3 <2.0.0-0\",\n \"allowPrerelease\": true\n },\n \"flue-runtime-v0-8-latest\": {\n \"package\": \"@flue/runtime\",\n \"range\": \">=0.8.0 <0.9.0\"\n },\n \"flue-runtime-v1-latest\": {\n \"package\": \"@flue/runtime\",\n \"range\": \">=1.0.0-beta.3 <2.0.0-0\",\n \"allowPrerelease\": true\n },\n \"flue-runtime-v2-latest\": {\n \"package\": \"@flue/runtime\",\n \"range\": \">=2.0.0 <3.0.0\"\n }\n }\n }\n },\n \"dependencies\": {\n \"@flue/cli\": \"0.8.0\",\n \"@flue/runtime\": \"0.8.0\",\n \"flue-cli-v0-8-latest\": \"npm:@flue/cli@0.8.1\",\n \"flue-cli-v1\": \"npm:@flue/cli@1.0.0-beta.3\",\n \"flue-cli-v1-latest\": \"npm:@flue/cli@1.0.0-beta.9\",\n \"flue-runtime-v0-8-latest\": \"npm:@flue/runtime@0.8.1\",\n \"flue-runtime-v1\": \"npm:@flue/runtime@1.0.0-beta.3\",\n \"flue-runtime-v1-latest\": \"npm:@flue/runtime@1.0.0-beta.9\",\n \"flue-runtime-v2\": \"npm:@flue/runtime@2.0.0\",\n \"flue-runtime-v2-latest\": \"npm:@flue/runtime@2.0.1\",\n \"pi-ai-v2\": \"npm:@earendil-works/pi-ai@0.83.0\",\n \"valibot\": \"1.4.2\"\n },\n \"pnpm\": {\n \"overrides\": {\n \"@flue/runtime@0.8.0>@earendil-works/pi-agent-core\": \"0.75.4\",\n \"@flue/runtime@0.8.0>@earendil-works/pi-ai\": \"0.75.4\",\n \"@flue/runtime@0.8.1>@earendil-works/pi-agent-core\": \"0.75.4\",\n \"@flue/runtime@0.8.1>@earendil-works/pi-ai\": \"0.75.4\",\n \"@hono/node-server@>=2.0.0 <2.0.10\": \"^2.0.10\",\n \"fast-uri@<3.1.5\": \"3.1.8\",\n \"brace-expansion@>=3.0.0 <5.0.9\": \"5.0.12\",\n \"postcss@<8.5.18\": \"^8.5.18\",\n \"valibot@<1.4.2\": \"^1.4.2\",\n \"ip-address@<10.2.2\": \"10.7.1\",\n \"undici@<7.29.0\": \"7.29.1\",\n \"hono@<4.12.34\": \"4.13.7\",\n \"@hono/node-server@<1.19.15\": \"^1.19.15\",\n \"nanoid@<3.3.18\": \"^3.3.18\",\n \"js-yaml@3\": \"^3.15.1\",\n \"js-yaml@4\": \"^4.3.1\",\n \"sharp@<0.35.0\": \"0.35.5\",\n \"ip-address@>=10.0.0 <10.7.1\": \"10.7.1\",\n \"fast-uri@>=3.0.0 <3.1.8\": \"3.1.8\",\n \"undici@>=7.0.0 <7.29.1\": \"7.29.1\",\n \"js-yaml@>=5.0.0 <5.4.1\": \"5.4.1\",\n \"brace-expansion@>=5.0.0 <5.0.12\": \"5.0.12\",\n \"hono@>=4.0.0 <4.13.7\": \"4.13.7\",\n \"proxy-addr@>=2.0.0 <2.0.8\": \"2.0.8\",\n \"source-map-js@>=1.0.0 <1.2.2\": \"1.2.2\",\n \"smol-toml@>=1.0.0 <1.9.0\": \"1.9.0\",\n \"sharp@>=0.35.0 <0.35.5\": \"0.35.5\",\n \"@modelcontextprotocol/sdk@>=1.0.0 <1.31.0\": \"1.31.0\",\n \"@modelcontextprotocol/client@>=2.0.0 <2.2.0\": \"2.2.0\"\n }\n }\n}", - "type": "text" - } - ], - "details": { - "command": "cat ./package.json", - "exitCode": 0 - } - }, - "metadata": { - "flue.session": "task:skill:", - "flue.tool_name": "bash", - "provider": "flue" - }, - "metrics": { - "duration_ms": 0 - } - }, - { - "name": "flue.turn", - "type": "llm", - "children": [], - "input": [ - { - "content": [ - { - "arguments": { - "command": "cat ./package.json", - "timeout": 100000 - }, - "id": "", - "name": "bash", - "type": "toolCall" - } - ], - "role": "assistant" - }, - { - "content": [ - { - "text": "{\n \"name\": \"flue-instrumentation-scenario\",\n \"private\": true,\n \"type\": \"module\",\n \"braintrustScenario\": {\n \"bump\": {\n \"dependencies\": {\n \"flue-cli-v0-8-latest\": {\n \"package\": \"@flue/cli\",\n \"range\": \">=0.8.0 <0.9.0\"\n },\n \"flue-cli-v1-latest\": {\n \"package\": \"@flue/cli\",\n \"range\": \">=1.0.0-beta.3 <2.0.0-0\",\n \"allowPrerelease\": true\n },\n \"flue-runtime-v0-8-latest\": {\n \"package\": \"@flue/runtime\",\n \"range\": \">=0.8.0 <0.9.0\"\n },\n \"flue-runtime-v1-latest\": {\n \"package\": \"@flue/runtime\",\n \"range\": \">=1.0.0-beta.3 <2.0.0-0\",\n \"allowPrerelease\": true\n },\n \"flue-runtime-v2-latest\": {\n \"package\": \"@flue/runtime\",\n \"range\": \">=2.0.0 <3.0.0\"\n }\n }\n }\n },\n \"dependencies\": {\n \"@flue/cli\": \"0.8.0\",\n \"@flue/runtime\": \"0.8.0\",\n \"flue-cli-v0-8-latest\": \"npm:@flue/cli@0.8.1\",\n \"flue-cli-v1\": \"npm:@flue/cli@1.0.0-beta.3\",\n \"flue-cli-v1-latest\": \"npm:@flue/cli@1.0.0-beta.9\",\n \"flue-runtime-v0-8-latest\": \"npm:@flue/runtime@0.8.1\",\n \"flue-runtime-v1\": \"npm:@flue/runtime@1.0.0-beta.3\",\n \"flue-runtime-v1-latest\": \"npm:@flue/runtime@1.0.0-beta.9\",\n \"flue-runtime-v2\": \"npm:@flue/runtime@2.0.0\",\n \"flue-runtime-v2-latest\": \"npm:@flue/runtime@2.0.1\",\n \"pi-ai-v2\": \"npm:@earendil-works/pi-ai@0.83.0\",\n \"valibot\": \"1.4.2\"\n },\n \"pnpm\": {\n \"overrides\": {\n \"@flue/runtime@0.8.0>@earendil-works/pi-agent-core\": \"0.75.4\",\n \"@flue/runtime@0.8.0>@earendil-works/pi-ai\": \"0.75.4\",\n \"@flue/runtime@0.8.1>@earendil-works/pi-agent-core\": \"0.75.4\",\n \"@flue/runtime@0.8.1>@earendil-works/pi-ai\": \"0.75.4\",\n \"@hono/node-server@>=2.0.0 <2.0.10\": \"^2.0.10\",\n \"fast-uri@<3.1.5\": \"3.1.8\",\n \"brace-expansion@>=3.0.0 <5.0.9\": \"5.0.12\",\n \"postcss@<8.5.18\": \"^8.5.18\",\n \"valibot@<1.4.2\": \"^1.4.2\",\n \"ip-address@<10.2.2\": \"10.7.1\",\n \"undici@<7.29.0\": \"7.29.1\",\n \"hono@<4.12.34\": \"4.13.7\",\n \"@hono/node-server@<1.19.15\": \"^1.19.15\",\n \"nanoid@<3.3.18\": \"^3.3.18\",\n \"js-yaml@3\": \"^3.15.1\",\n \"js-yaml@4\": \"^4.3.1\",\n \"sharp@<0.35.0\": \"0.35.5\",\n \"ip-address@>=10.0.0 <10.7.1\": \"10.7.1\",\n \"fast-uri@>=3.0.0 <3.1.8\": \"3.1.8\",\n \"undici@>=7.0.0 <7.29.1\": \"7.29.1\",\n \"js-yaml@>=5.0.0 <5.4.1\": \"5.4.1\",\n \"brace-expansion@>=5.0.0 <5.0.12\": \"5.0.12\",\n \"hono@>=4.0.0 <4.13.7\": \"4.13.7\",\n \"proxy-addr@>=2.0.0 <2.0.8\": \"2.0.8\",\n \"source-map-js@>=1.0.0 <1.2.2\": \"1.2.2\",\n \"smol-toml@>=1.0.0 <1.9.0\": \"1.9.0\",\n \"sharp@>=0.35.0 <0.35.5\": \"0.35.5\",\n \"@modelcontextprotocol/sdk@>=1.0.0 <1.31.0\": \"1.31.0\",\n \"@modelcontextprotocol/client@>=2.0.0 <2.2.0\": \"2.2.0\"\n }\n }\n}", - "type": "text" - } - ], - "isError": false, - "role": "toolResult", - "toolCallId": "", - "toolName": "bash" - } - ], - "output": { - "content": [ - { - "arguments": { - "command": "printf SKILL_DONE", - "timeout": 100000 - }, - "id": "", - "name": "bash", - "type": "toolCall" - } - ], - "role": "assistant" - }, - "metadata": { - "flue.api": "openai-responses", - "flue.input_message_offset": 15, - "flue.input_mode": "delta", - "flue.model": "gpt-5.4-nano", - "flue.provider": "openai", - "flue.session": "task:skill:", - "flue.stop_reason": "toolUse", - "flue.turn_purpose": "agent", - "model": "gpt-5.4-nano", - "provider": "openai" - }, - "metrics": { - "completion_tokens": 59, - "duration_ms": 0, - "estimated_cost": 0.0004814700000000001, - "prompt_cache_creation_tokens": 0, - "prompt_cached_tokens": 1536, - "prompt_tokens": 3421, - "tokens": 3480 - } - }, - { - "name": "tool:bash", - "type": "tool", - "children": [], - "input": { - "command": "printf SKILL_DONE", - "timeout": 100000 - }, - "output": { - "content": [ - { - "text": "SKILL_DONE", - "type": "text" - } - ], - "details": { - "command": "printf SKILL_DONE", - "exitCode": 0 - } - }, - "metadata": { - "flue.session": "task:skill:", - "flue.tool_name": "bash", - "provider": "flue" - }, - "metrics": { - "duration_ms": 0 - } - }, - { - "name": "flue.turn", - "type": "llm", - "children": [], - "input": [ - { - "content": [ - { - "arguments": { - "command": "printf SKILL_DONE", - "timeout": 100000 - }, - "id": "", - "name": "bash", - "type": "toolCall" - } - ], - "role": "assistant" - }, - { - "content": [ - { - "text": "SKILL_DONE", - "type": "text" - } - ], - "isError": false, - "role": "toolResult", - "toolCallId": "", - "toolName": "bash" - } - ], - "output": { - "content": [ - { - "text": "SKILL_DONE", - "textSignature": "{\"v\":1,\"id\":\"msg_054a16a2f2ea3217016a705b70a41481a38ece40ecddf69512\",\"phase\":\"final_answer\"}", - "type": "text" - } - ], - "role": "assistant" - }, - "metadata": { - "flue.api": "openai-responses", - "flue.input_message_offset": 17, - "flue.input_mode": "delta", - "flue.model": "gpt-5.4-nano", - "flue.provider": "openai", - "flue.session": "task:skill:", - "flue.stop_reason": "stop", - "flue.turn_purpose": "agent", - "model": "gpt-5.4-nano", - "provider": "openai" - }, - "metrics": { - "completion_tokens": 7, - "duration_ms": 0, - "estimated_cost": 0.00031035000000000004, - "prompt_cache_creation_tokens": 0, - "prompt_cached_tokens": 2560, - "prompt_tokens": 3812, - "tokens": 3819 - } - } - ], - "input": "Run the skill named \"e2e-flue-skill\" with arguments {\"marker\":\"SKILL_DONE\"}. Output only the marker and no extra text when finished.", - "output": "SKILL_DONE", - "metadata": { - "flue.session": "skill", - "provider": "flue" - }, - "metrics": { - "duration_ms": 0 - } - } - ], - "input": [ - { - "content": [ - { - "text": "Run the skill named \"e2e-flue-skill\".\n\nArguments:\n{\n \"marker\": \"SKILL_DONE\"\n}", - "type": "text" - } - ], - "role": "user" - } - ], - "output": { - "content": [ - { - "arguments": { - "cwd": ".", - "description": "Run the deterministic Flue instrumentation e2e test skill e2e-flue-skill.", - "prompt": "Run the skill named \"e2e-flue-skill\" with arguments {\"marker\":\"SKILL_DONE\"}. Output only the marker and no extra text when finished." - }, - "id": "", - "name": "task", - "type": "toolCall" - } - ], - "role": "assistant" - }, - "metadata": { - "flue.api": "openai-responses", - "flue.model": "gpt-5.4-nano", - "flue.provider": "openai", - "flue.session": "skill", - "flue.stop_reason": "toolUse", - "flue.turn_purpose": "agent", - "model": "gpt-5.4-nano", - "provider": "openai" - }, - "metrics": { - "completion_tokens": 109, - "duration_ms": 0, - "estimated_cost": 0.00030565000000000003, - "prompt_cache_creation_tokens": 0, - "prompt_cached_tokens": 0, - "prompt_tokens": 847, - "tokens": 956 - } - }, - { - "name": "tool:task", - "type": "tool", - "children": [], - "input": { - "cwd": ".", - "description": "Run the deterministic Flue instrumentation e2e test skill e2e-flue-skill.", - "prompt": "Run the skill named \"e2e-flue-skill\" with arguments {\"marker\":\"SKILL_DONE\"}. Output only the marker and no extra text when finished." - }, - "output": { - "content": [ - { - "text": "SKILL_DONE", - "type": "text" - } - ], - "details": { - "cwd": ".", - "messageId": "", - "session": "task:skill:", - "taskId": "" - } - }, - "metadata": { - "flue.session": "skill", - "flue.tool_name": "task", - "provider": "flue" - }, - "metrics": { - "duration_ms": 0 - } - }, - { - "name": "flue.turn", - "type": "llm", - "children": [], - "input": [ - { - "content": [ - { - "text": "Run the skill named \"e2e-flue-skill\".\n\nArguments:\n{\n \"marker\": \"SKILL_DONE\"\n}", - "type": "text" - } - ], - "role": "user" - }, - { - "content": [ - { - "arguments": { - "cwd": ".", - "description": "Run the deterministic Flue instrumentation e2e test skill e2e-flue-skill.", - "prompt": "Run the skill named \"e2e-flue-skill\" with arguments {\"marker\":\"SKILL_DONE\"}. Output only the marker and no extra text when finished." - }, - "id": "", - "name": "task", - "type": "toolCall" - } - ], - "role": "assistant" - }, - { - "content": [ - { - "text": "SKILL_DONE", - "type": "text" - } - ], - "isError": false, - "role": "toolResult", - "toolCallId": "", - "toolName": "task" - } - ], - "output": { - "content": [ - { - "text": "SKILL_DONE", - "textSignature": "{\"v\":1,\"id\":\"msg_0f099b3fce0721fd016a705b71558881a38c53a767e09d9e31\",\"phase\":\"final_answer\"}", - "type": "text" - } - ], - "role": "assistant" - }, - "metadata": { - "flue.api": "openai-responses", - "flue.model": "gpt-5.4-nano", - "flue.provider": "openai", - "flue.session": "skill", - "flue.stop_reason": "stop", - "flue.turn_purpose": "agent", - "model": "gpt-5.4-nano", - "provider": "openai" - }, - "metrics": { - "completion_tokens": 7, - "duration_ms": 0, - "estimated_cost": 0.00020255000000000002, - "prompt_cache_creation_tokens": 0, - "prompt_cached_tokens": 0, - "prompt_tokens": 969, - "tokens": 976 - } - } - ], - "input": [ - { - "content": [ - { - "text": "Run the skill named \"e2e-flue-skill\".\n\nArguments:\n{\n \"marker\": \"SKILL_DONE\"\n}", - "type": "text" - } - ], - "role": "user" - } - ], - "output": "SKILL_DONE", - "metadata": { - "flue.operation": "skill", - "flue.session": "skill", - "flue.workflow_name": "instrumentation", - "provider": "flue", - "scenario": "flue-instrumentation" - }, - "metrics": { - "duration_ms": 0 - } - }, - { - "name": "flue.task", - "type": "task", - "children": [ - { - "name": "flue.turn", - "type": "llm", - "children": [], - "input": [ - { - "content": [ - { - "text": "Reply with exactly TASK_DONE and no other text.", - "type": "text" - } - ], - "role": "user" - } - ], - "output": { - "content": [ - { - "text": "TASK_DONE", - "textSignature": "{\"v\":1,\"id\":\"msg_0f3adfdb1510c165016a705b72843c81a0b30d242da683a882\"}", - "type": "text" - } - ], - "role": "assistant" - }, - "metadata": { - "flue.api": "openai-responses", - "flue.input_mode": "full", - "flue.model": "gpt-4o-mini", - "flue.provider": "openai", - "flue.session": "task:task:", - "flue.stop_reason": "stop", - "flue.turn_purpose": "agent", - "model": "gpt-4o-mini", - "provider": "openai" - }, - "metrics": { - "completion_tokens": 4, - "duration_ms": 0, - "estimated_cost": 0.0001263, - "prompt_cache_creation_tokens": 0, - "prompt_cached_tokens": 0, - "prompt_tokens": 826, - "tokens": 830 - } - } - ], - "input": "Reply with exactly TASK_DONE and no other text.", - "output": "TASK_DONE", - "metadata": { - "flue.session": "task", - "provider": "flue" - }, - "metrics": { - "duration_ms": 0 - } - }, - { - "name": "flue.compact", - "type": "task", - "children": [ - { - "name": "compaction:manual", - "type": "task", - "children": [ - { - "name": "flue.turn", - "type": "llm", - "children": [], - "input": [ - { - "content": [ - { - "text": "\n[User]: Complete this instrumented research flow. Call exactly one tool per turn and wait for each tool result before choosing the next tool. Step 1: call lookup with query \"flue instrumentation\". Step 2: use the lookup result id as lookupId and call web_search with query \"Braintrust Flue reasoning stream instrumentation\". Step 3: use the first web_search result url and call summarize_source. After summarize_source returns, reply with exactly PROMPT_DONE and no other text.\n\n[Assistant tool calls]: lookup(query=\"flue instrumentation\")\n\n[Tool result]: {\"id\":\"flue-session-2026\",\"query\":\"flue instrumentation\",\"topic\":\"session instrumentation\"}\n\n[Assistant tool calls]: web_search(lookupId=\"flue-session-2026\", query=\"Braintrust Flue reasoning stream instrumentation\")\n\n[Tool result]: {\"lookupId\":\"flue-session-2026\",\"query\":\"Braintrust Flue reasoning stream instrumentation\",\"results\":[{\"title\":\"Flue reasoning stream instrumentation\",\"url\":\"https://example.test/flue/reasoning-streams\"}]}\n\n[Assistant tool calls]: summarize_source(url=\"https://example.test/flue/reasoning-streams\")\n\n[Tool result]: {\"summary\":\"Flue emits reasoning, tool execution, and LLM turn events separately.\",\"url\":\"https://example.test/flue/reasoning-streams\"}\n\n\nThis is the PREFIX of a turn that was too large to keep. The SUFFIX (recent work) is retained.\n\nSummarize the prefix to provide context for the retained suffix:\n\n## Original Request\n[What did the user ask for in this turn?]\n\n## Early Progress\n- [Key decisions and work done in the prefix]\n\n## Context for Suffix\n- [Information needed to understand the retained recent work]\n\nBe concise. Focus on what's needed to understand the kept suffix.", - "type": "text" - } - ], - "role": "user" - } - ], - "output": { - "content": [ - { - "text": "## Original Request\nThe user requested a step-by-step execution of an instrumented research flow involving tool calls to gather information on \"flue instrumentation\" and \"", - "textSignature": "{\"v\":1,\"id\":\"msg_04d28ba03840bb65016a705b7358a081a38e69464057840036\"}", - "type": "text" - } - ], - "role": "assistant" - }, - "metadata": { - "flue.api": "openai-responses", - "flue.model": "gpt-4o-mini", - "flue.provider": "openai", - "flue.session": "main", - "flue.stop_reason": "stop", - "flue.turn_purpose": "compaction_prefix", - "model": "gpt-4o-mini", - "provider": "openai" - }, - "metrics": { - "completion_tokens": 0, - "duration_ms": 0, - "estimated_cost": 0, - "prompt_cache_creation_tokens": 0, - "prompt_cached_tokens": 0, - "prompt_tokens": 0, - "tokens": 0 - } - } - ], - "input": { - "estimatedTokens": 1229, - "reason": "manual" - }, - "output": { - "messagesAfter": 2, - "messagesBefore": 8 - }, - "metadata": { - "flue.compaction_reason": "manual", - "flue.session": "main", - "provider": "flue" - }, - "metrics": { - "duration_ms": 0, - "messages_after": 2, - "messages_before": 8 - } - } - ], - "input": { - "estimatedTokens": 1229, - "reason": "manual" - }, - "output": { - "completed": true - }, - "metadata": { - "flue.operation": "compact", - "flue.session": "main", - "flue.workflow_name": "instrumentation", - "provider": "flue", - "scenario": "flue-instrumentation" - }, - "metrics": { - "duration_ms": 0 - } - } - ], - "input": { - "metadata": { - "scenario": "flue-instrumentation", - "testRunId": "" - }, - "scenario": "flue-instrumentation" - }, - "output": { - "scenario": "flue-instrumentation", - "status": "done" - }, - "metadata": { - "flue.workflow_name": "instrumentation", - "provider": "flue", - "scenario": "flue-instrumentation" - }, - "metrics": { - "duration_ms": 0 - } - }, - { - "name": "flue.prompt", - "type": "task", - "children": [ - { - "name": "flue.turn", - "type": "llm", - "children": [], - "input": [ - { - "content": [ - { - "text": "Complete this instrumented research flow. Call exactly one tool per turn and wait for each tool result before choosing the next tool. Step 1: call lookup with query \"flue instrumentation\". Step 2: use the lookup result id as lookupId and call web_search with query \"Braintrust Flue reasoning stream instrumentation\". Step 3: use the first web_search result url and call summarize_source. After summarize_source returns, reply with exactly PROMPT_DONE and no other text.", - "type": "text" - } - ], - "role": "user" - } - ], - "output": { - "content": [ - { - "arguments": { - "query": "flue instrumentation" - }, - "id": "", - "name": "lookup", - "type": "toolCall" - } - ], - "role": "assistant" - }, - "metadata": { - "flue.api": "openai-responses", - "flue.input_mode": "full", - "flue.model": "gpt-5.4-nano", - "flue.provider": "openai", - "flue.session": "main", - "flue.stop_reason": "toolUse", - "flue.turn_purpose": "agent", - "model": "gpt-5.4-nano", - "provider": "openai" - }, - "metrics": { - "completion_tokens": 19, - "duration_ms": 0, - "estimated_cost": 0.00022475000000000001, - "prompt_cache_creation_tokens": 0, - "prompt_cached_tokens": 0, - "prompt_tokens": 1005, - "tokens": 1024 - } - }, - { - "name": "tool:lookup", - "type": "tool", - "children": [], - "input": { - "query": "flue instrumentation" - }, - "output": { - "content": [ - { - "text": "{\"id\":\"flue-session-2026\",\"query\":\"flue instrumentation\",\"topic\":\"session instrumentation\"}", - "type": "text" - } - ], - "details": { - "customTool": "lookup" - } - }, - "metadata": { - "flue.session": "main", - "flue.tool_name": "lookup", - "provider": "flue" - }, - "metrics": { - "duration_ms": 0 - } - }, - { - "name": "flue.turn", - "type": "llm", - "children": [], - "input": [ - { - "content": [ - { - "arguments": { - "query": "flue instrumentation" - }, - "id": "", - "name": "lookup", - "type": "toolCall" - } - ], - "role": "assistant" - }, - { - "content": [ - { - "text": "{\"id\":\"flue-session-2026\",\"query\":\"flue instrumentation\",\"topic\":\"session instrumentation\"}", - "type": "text" - } - ], - "isError": false, - "role": "toolResult", - "toolCallId": "", - "toolName": "lookup" - } - ], - "output": { - "content": [ - { - "arguments": { - "lookupId": "flue-session-2026", - "query": "Braintrust Flue reasoning stream instrumentation" - }, - "id": "", - "name": "web_search", - "type": "toolCall" - } - ], - "role": "assistant" - }, - "metadata": { - "flue.api": "openai-responses", - "flue.input_message_offset": 1, - "flue.input_mode": "delta", - "flue.model": "gpt-5.4-nano", - "flue.provider": "openai", - "flue.session": "main", - "flue.stop_reason": "toolUse", - "flue.turn_purpose": "agent", - "model": "gpt-5.4-nano", - "provider": "openai" - }, - "metrics": { - "completion_tokens": 34, - "duration_ms": 0, - "estimated_cost": 0.00025350000000000004, - "prompt_cache_creation_tokens": 0, - "prompt_cached_tokens": 0, - "prompt_tokens": 1055, - "tokens": 1089 - } - }, - { - "name": "tool:web_search", - "type": "tool", - "children": [], - "input": { - "lookupId": "flue-session-2026", - "query": "Braintrust Flue reasoning stream instrumentation" - }, - "output": { - "content": [ - { - "text": "{\"lookupId\":\"flue-session-2026\",\"query\":\"Braintrust Flue reasoning stream instrumentation\",\"results\":[{\"title\":\"Flue reasoning stream instrumentation\",\"url\":\"https://example.test/flue/reasoning-streams\"}]}", - "type": "text" - } - ], - "details": { - "customTool": "web_search" - } - }, - "metadata": { - "flue.session": "main", - "flue.tool_name": "web_search", - "provider": "flue" - }, - "metrics": { - "duration_ms": 0 - } - }, - { - "name": "flue.turn", - "type": "llm", - "children": [], - "input": [ - { - "content": [ - { - "arguments": { - "lookupId": "flue-session-2026", - "query": "Braintrust Flue reasoning stream instrumentation" - }, - "id": "", - "name": "web_search", - "type": "toolCall" - } - ], - "role": "assistant" - }, - { - "content": [ - { - "text": "{\"lookupId\":\"flue-session-2026\",\"query\":\"Braintrust Flue reasoning stream instrumentation\",\"results\":[{\"title\":\"Flue reasoning stream instrumentation\",\"url\":\"https://example.test/flue/reasoning-streams\"}]}", - "type": "text" - } - ], - "isError": false, - "role": "toolResult", - "toolCallId": "", - "toolName": "web_search" - } - ], - "output": { - "content": [ - { - "arguments": { - "url": "https://example.test/flue/reasoning-streams" - }, - "id": "", - "name": "summarize_source", - "type": "toolCall" - } - ], - "role": "assistant" - }, - "metadata": { - "flue.api": "openai-responses", - "flue.input_message_offset": 3, - "flue.input_mode": "delta", - "flue.model": "gpt-5.4-nano", - "flue.provider": "openai", - "flue.session": "main", - "flue.stop_reason": "toolUse", - "flue.turn_purpose": "agent", - "model": "gpt-5.4-nano", - "provider": "openai" - }, - "metrics": { - "completion_tokens": 30, - "duration_ms": 0, - "estimated_cost": 0.00026690000000000004, - "prompt_cache_creation_tokens": 0, - "prompt_cached_tokens": 0, - "prompt_tokens": 1147, - "tokens": 1177 - } - }, - { - "name": "tool:summarize_source", - "type": "tool", - "children": [], - "input": { - "url": "https://example.test/flue/reasoning-streams" - }, - "output": { - "content": [ - { - "text": "{\"summary\":\"Flue emits reasoning, tool execution, and LLM turn events separately.\",\"url\":\"https://example.test/flue/reasoning-streams\"}", - "type": "text" - } - ], - "details": { - "customTool": "summarize_source" - } - }, - "metadata": { - "flue.session": "main", - "flue.tool_name": "summarize_source", - "provider": "flue" - }, - "metrics": { - "duration_ms": 0 - } - }, - { - "name": "flue.turn", - "type": "llm", - "children": [], - "input": [ - { - "content": [ - { - "arguments": { - "url": "https://example.test/flue/reasoning-streams" - }, - "id": "", - "name": "summarize_source", - "type": "toolCall" - } - ], - "role": "assistant" - }, - { - "content": [ - { - "text": "{\"summary\":\"Flue emits reasoning, tool execution, and LLM turn events separately.\",\"url\":\"https://example.test/flue/reasoning-streams\"}", - "type": "text" - } - ], - "isError": false, - "role": "toolResult", - "toolCallId": "", - "toolName": "summarize_source" - } - ], - "output": { - "content": [ - { - "text": "PROMPT_DONE", - "textSignature": "{\"v\":1,\"id\":\"msg_045458052dc10379016a705b62dbd8819189b8287061debf12\",\"phase\":\"final_answer\"}", - "type": "text" - } - ], - "role": "assistant" - }, - "metadata": { - "flue.api": "openai-responses", - "flue.input_message_offset": 5, - "flue.input_mode": "delta", - "flue.model": "gpt-5.4-nano", - "flue.provider": "openai", - "flue.session": "main", - "flue.stop_reason": "stop", - "flue.turn_purpose": "agent", - "model": "gpt-5.4-nano", - "provider": "openai" - }, - "metrics": { - "completion_tokens": 7, - "duration_ms": 0, - "estimated_cost": 0.00025315000000000005, - "prompt_cache_creation_tokens": 0, - "prompt_cached_tokens": 0, - "prompt_tokens": 1222, - "tokens": 1229 - } - } - ], - "input": [ - { - "content": [ - { - "text": "Complete this instrumented research flow. Call exactly one tool per turn and wait for each tool result before choosing the next tool. Step 1: call lookup with query \"flue instrumentation\". Step 2: use the lookup result id as lookupId and call web_search with query \"Braintrust Flue reasoning stream instrumentation\". Step 3: use the first web_search result url and call summarize_source. After summarize_source returns, reply with exactly PROMPT_DONE and no other text.", - "type": "text" - } - ], - "role": "user" - } - ], - "output": "PROMPT_DONE", - "metadata": { - "flue.operation": "prompt", - "flue.session": "main", - "flue.workflow_name": "instrumentation", - "provider": "flue", - "scenario": "flue-instrumentation" - }, - "metrics": { - "duration_ms": 0 - } - } - ] -} diff --git a/e2e/scenarios/flue-instrumentation/__snapshots__/flue-v0-8-latest-cli.span-tree.txt b/e2e/scenarios/flue-instrumentation/__snapshots__/flue-v0-8-latest-cli.span-tree.txt deleted file mode 100644 index b4408459c..000000000 --- a/e2e/scenarios/flue-instrumentation/__snapshots__/flue-v0-8-latest-cli.span-tree.txt +++ /dev/null @@ -1,1499 +0,0 @@ -span_tree: -├── workflow:instrumentation [task] -│ input: { -│ "metadata": { -│ "scenario": "flue-instrumentation", -│ "testRunId": "" -│ }, -│ "scenario": "flue-instrumentation" -│ } -│ output: { -│ "scenario": "flue-instrumentation", -│ "status": "done" -│ } -│ metadata: { -│ "flue.workflow_name": "instrumentation", -│ "provider": "flue", -│ "scenario": "flue-instrumentation" -│ } -│ metrics: { -│ "duration_ms": 0 -│ } -│ ├── flue.skill [task] -│ │ input: [ -│ │ { -│ │ "content": [ -│ │ { -│ │ "text": "Run the skill named \"e2e-flue-skill\".\n\nArguments:\n{\n \"marker\": \"SKILL_DONE\"\n}", -│ │ "type": "text" -│ │ } -│ │ ], -│ │ "role": "user" -│ │ } -│ │ ] -│ │ output: "SKILL_DONE" -│ │ metadata: { -│ │ "flue.operation": "skill", -│ │ "flue.session": "skill", -│ │ "flue.workflow_name": "instrumentation", -│ │ "provider": "flue", -│ │ "scenario": "flue-instrumentation" -│ │ } -│ │ metrics: { -│ │ "duration_ms": 0 -│ │ } -│ │ ├── flue.turn [llm] -│ │ │ input: [ -│ │ │ { -│ │ │ "content": [ -│ │ │ { -│ │ │ "text": "Run the skill named \"e2e-flue-skill\".\n\nArguments:\n{\n \"marker\": \"SKILL_DONE\"\n}", -│ │ │ "type": "text" -│ │ │ } -│ │ │ ], -│ │ │ "role": "user" -│ │ │ } -│ │ │ ] -│ │ │ output: { -│ │ │ "content": [ -│ │ │ { -│ │ │ "arguments": { -│ │ │ "cwd": ".", -│ │ │ "description": "Run the deterministic Flue instrumentation e2e test skill e2e-flue-skill.", -│ │ │ "prompt": "Run the skill named \"e2e-flue-skill\" with arguments {\"marker\":\"SKILL_DONE\"}. Output only the marker and no extra text when finished." -│ │ │ }, -│ │ │ "id": "", -│ │ │ "name": "task", -│ │ │ "type": "toolCall" -│ │ │ } -│ │ │ ], -│ │ │ "role": "assistant" -│ │ │ } -│ │ │ metadata: { -│ │ │ "flue.api": "openai-responses", -│ │ │ "flue.model": "gpt-5.4-nano", -│ │ │ "flue.provider": "openai", -│ │ │ "flue.session": "skill", -│ │ │ "flue.stop_reason": "toolUse", -│ │ │ "flue.turn_purpose": "agent", -│ │ │ "model": "gpt-5.4-nano", -│ │ │ "provider": "openai" -│ │ │ } -│ │ │ metrics: { -│ │ │ "completion_tokens": 109, -│ │ │ "duration_ms": 0, -│ │ │ "estimated_cost": 0.00030565000000000003, -│ │ │ "prompt_cache_creation_tokens": 0, -│ │ │ "prompt_cached_tokens": 0, -│ │ │ "prompt_tokens": 847, -│ │ │ "tokens": 956 -│ │ │ } -│ │ │ └── flue.task [task] -│ │ │ input: "Run the skill named \"e2e-flue-skill\" with arguments {\"marker\":\"SKILL_DONE\"}. Output only the marker and no extra text when finished." -│ │ │ output: "SKILL_DONE" -│ │ │ metadata: { -│ │ │ "flue.session": "skill", -│ │ │ "provider": "flue" -│ │ │ } -│ │ │ metrics: { -│ │ │ "duration_ms": 0 -│ │ │ } -│ │ │ ├── flue.turn [llm] -│ │ │ │ input: [ -│ │ │ │ { -│ │ │ │ "content": [ -│ │ │ │ { -│ │ │ │ "text": "Run the skill named \"e2e-flue-skill\" with arguments {\"marker\":\"SKILL_DONE\"}. Output only the marker and no extra text when finished.", -│ │ │ │ "type": "text" -│ │ │ │ } -│ │ │ │ ], -│ │ │ │ "role": "user" -│ │ │ │ } -│ │ │ │ ] -│ │ │ │ output: { -│ │ │ │ "content": [ -│ │ │ │ { -│ │ │ │ "arguments": { -│ │ │ │ "agent": "e2e-flue-skill", -│ │ │ │ "cwd": ".", -│ │ │ │ "description": "Run e2e-flue-skill with marker SKILL_DONE", -│ │ │ │ "prompt": "Run the skill named \"e2e-flue-skill\" with arguments {\"marker\":\"SKILL_DONE\"}. Ensure completion and report the final output marker only." -│ │ │ │ }, -│ │ │ │ "id": "", -│ │ │ │ "name": "task", -│ │ │ │ "type": "toolCall" -│ │ │ │ } -│ │ │ │ ], -│ │ │ │ "role": "assistant" -│ │ │ │ } -│ │ │ │ metadata: { -│ │ │ │ "flue.api": "openai-responses", -│ │ │ │ "flue.input_mode": "full", -│ │ │ │ "flue.model": "gpt-5.4-nano", -│ │ │ │ "flue.provider": "openai", -│ │ │ │ "flue.session": "task:skill:", -│ │ │ │ "flue.stop_reason": "toolUse", -│ │ │ │ "flue.turn_purpose": "agent", -│ │ │ │ "model": "gpt-5.4-nano", -│ │ │ │ "provider": "openai" -│ │ │ │ } -│ │ │ │ metrics: { -│ │ │ │ "completion_tokens": 113, -│ │ │ │ "duration_ms": 0, -│ │ │ │ "estimated_cost": 0.00031225000000000003, -│ │ │ │ "prompt_cache_creation_tokens": 0, -│ │ │ │ "prompt_cached_tokens": 0, -│ │ │ │ "prompt_tokens": 855, -│ │ │ │ "tokens": 968 -│ │ │ │ } -│ │ │ ├── tool:task [tool] -│ │ │ │ input: { -│ │ │ │ "agent": "e2e-flue-skill", -│ │ │ │ "cwd": ".", -│ │ │ │ "description": "Run e2e-flue-skill with marker SKILL_DONE", -│ │ │ │ "prompt": "Run the skill named \"e2e-flue-skill\" with arguments {\"marker\":\"SKILL_DONE\"}. Ensure completion and report the final output marker only." -│ │ │ │ } -│ │ │ │ output: { -│ │ │ │ "content": [ -│ │ │ │ { -│ │ │ │ "text": "[flue] Subagent \"e2e-flue-skill\" is not declared. Available: (none).", -│ │ │ │ "type": "text" -│ │ │ │ } -│ │ │ │ ], -│ │ │ │ "details": {} -│ │ │ │ } -│ │ │ │ metadata: { -│ │ │ │ "flue.session": "task:skill:", -│ │ │ │ "flue.tool_name": "task", -│ │ │ │ "provider": "flue" -│ │ │ │ } -│ │ │ │ metrics: { -│ │ │ │ "duration_ms": 0 -│ │ │ │ } -│ │ │ │ error: "{\"content\":[{\"type\":\"text\",\"text\":\"[flue] Subagent \\\"e2e-flue-skill\\\" is not declared. Available: (none).\"}],\"details\":{}}" -│ │ │ ├── flue.turn [llm] -│ │ │ │ input: [ -│ │ │ │ { -│ │ │ │ "content": [ -│ │ │ │ { -│ │ │ │ "arguments": { -│ │ │ │ "agent": "e2e-flue-skill", -│ │ │ │ "cwd": ".", -│ │ │ │ "description": "Run e2e-flue-skill with marker SKILL_DONE", -│ │ │ │ "prompt": "Run the skill named \"e2e-flue-skill\" with arguments {\"marker\":\"SKILL_DONE\"}. Ensure completion and report the final output marker only." -│ │ │ │ }, -│ │ │ │ "id": "", -│ │ │ │ "name": "task", -│ │ │ │ "type": "toolCall" -│ │ │ │ } -│ │ │ │ ], -│ │ │ │ "role": "assistant" -│ │ │ │ }, -│ │ │ │ { -│ │ │ │ "content": [ -│ │ │ │ { -│ │ │ │ "text": "[flue] Subagent \"e2e-flue-skill\" is not declared. Available: (none).", -│ │ │ │ "type": "text" -│ │ │ │ } -│ │ │ │ ], -│ │ │ │ "isError": true, -│ │ │ │ "role": "toolResult", -│ │ │ │ "toolCallId": "", -│ │ │ │ "toolName": "task" -│ │ │ │ } -│ │ │ │ ] -│ │ │ │ output: { -│ │ │ │ "content": [ -│ │ │ │ { -│ │ │ │ "arguments": { -│ │ │ │ "command": "find . -maxdepth 4 -name AGENTS.md -print", -│ │ │ │ "timeout": 100000 -│ │ │ │ }, -│ │ │ │ "id": "", -│ │ │ │ "name": "bash", -│ │ │ │ "type": "toolCall" -│ │ │ │ } -│ │ │ │ ], -│ │ │ │ "role": "assistant" -│ │ │ │ } -│ │ │ │ metadata: { -│ │ │ │ "flue.api": "openai-responses", -│ │ │ │ "flue.input_message_offset": 1, -│ │ │ │ "flue.input_mode": "delta", -│ │ │ │ "flue.model": "gpt-5.4-nano", -│ │ │ │ "flue.provider": "openai", -│ │ │ │ "flue.session": "task:skill:", -│ │ │ │ "flue.stop_reason": "toolUse", -│ │ │ │ "flue.turn_purpose": "agent", -│ │ │ │ "model": "gpt-5.4-nano", -│ │ │ │ "provider": "openai" -│ │ │ │ } -│ │ │ │ metrics: { -│ │ │ │ "completion_tokens": 94, -│ │ │ │ "duration_ms": 0, -│ │ │ │ "estimated_cost": 0.00031790000000000003, -│ │ │ │ "prompt_cache_creation_tokens": 0, -│ │ │ │ "prompt_cached_tokens": 0, -│ │ │ │ "prompt_tokens": 1002, -│ │ │ │ "tokens": 1096 -│ │ │ │ } -│ │ │ ├── tool:bash [tool] -│ │ │ │ input: { -│ │ │ │ "command": "find . -maxdepth 4 -name AGENTS.md -print", -│ │ │ │ "timeout": 100000 -│ │ │ │ } -│ │ │ │ output: { -│ │ │ │ "content": [ -│ │ │ │ { -│ │ │ │ "text": "(no output)", -│ │ │ │ "type": "text" -│ │ │ │ } -│ │ │ │ ], -│ │ │ │ "details": { -│ │ │ │ "command": "find . -maxdepth 4 -name AGENTS.md -print", -│ │ │ │ "exitCode": 0 -│ │ │ │ } -│ │ │ │ } -│ │ │ │ metadata: { -│ │ │ │ "flue.session": "task:skill:", -│ │ │ │ "flue.tool_name": "bash", -│ │ │ │ "provider": "flue" -│ │ │ │ } -│ │ │ │ metrics: { -│ │ │ │ "duration_ms": 0 -│ │ │ │ } -│ │ │ ├── flue.turn [llm] -│ │ │ │ input: [ -│ │ │ │ { -│ │ │ │ "content": [ -│ │ │ │ { -│ │ │ │ "arguments": { -│ │ │ │ "command": "find . -maxdepth 4 -name AGENTS.md -print", -│ │ │ │ "timeout": 100000 -│ │ │ │ }, -│ │ │ │ "id": "", -│ │ │ │ "name": "bash", -│ │ │ │ "type": "toolCall" -│ │ │ │ } -│ │ │ │ ], -│ │ │ │ "role": "assistant" -│ │ │ │ }, -│ │ │ │ { -│ │ │ │ "content": [ -│ │ │ │ { -│ │ │ │ "text": "(no output)", -│ │ │ │ "type": "text" -│ │ │ │ } -│ │ │ │ ], -│ │ │ │ "isError": false, -│ │ │ │ "role": "toolResult", -│ │ │ │ "toolCallId": "", -│ │ │ │ "toolName": "bash" -│ │ │ │ } -│ │ │ │ ] -│ │ │ │ output: { -│ │ │ │ "content": [ -│ │ │ │ { -│ │ │ │ "arguments": { -│ │ │ │ "command": "find . -maxdepth 4 -type f -name AGENTS.md -print -exec sed -n '1,200p' {} \\;", -│ │ │ │ "timeout": 100000 -│ │ │ │ }, -│ │ │ │ "id": "", -│ │ │ │ "name": "bash", -│ │ │ │ "type": "toolCall" -│ │ │ │ } -│ │ │ │ ], -│ │ │ │ "role": "assistant" -│ │ │ │ } -│ │ │ │ metadata: { -│ │ │ │ "flue.api": "openai-responses", -│ │ │ │ "flue.input_message_offset": 3, -│ │ │ │ "flue.input_mode": "delta", -│ │ │ │ "flue.model": "gpt-5.4-nano", -│ │ │ │ "flue.provider": "openai", -│ │ │ │ "flue.session": "task:skill:", -│ │ │ │ "flue.stop_reason": "toolUse", -│ │ │ │ "flue.turn_purpose": "agent", -│ │ │ │ "model": "gpt-5.4-nano", -│ │ │ │ "provider": "openai" -│ │ │ │ } -│ │ │ │ metrics: { -│ │ │ │ "completion_tokens": 87, -│ │ │ │ "duration_ms": 0, -│ │ │ │ "estimated_cost": 0.00034815000000000003, -│ │ │ │ "prompt_cache_creation_tokens": 0, -│ │ │ │ "prompt_cached_tokens": 0, -│ │ │ │ "prompt_tokens": 1197, -│ │ │ │ "tokens": 1284 -│ │ │ │ } -│ │ │ ├── tool:bash [tool] -│ │ │ │ input: { -│ │ │ │ "command": "find . -maxdepth 4 -type f -name AGENTS.md -print -exec sed -n '1,200p' {} \\;", -│ │ │ │ "timeout": 100000 -│ │ │ │ } -│ │ │ │ output: { -│ │ │ │ "content": [ -│ │ │ │ { -│ │ │ │ "text": "(no output)", -│ │ │ │ "type": "text" -│ │ │ │ } -│ │ │ │ ], -│ │ │ │ "details": { -│ │ │ │ "command": "find . -maxdepth 4 -type f -name AGENTS.md -print -exec sed -n '1,200p' {} \\;", -│ │ │ │ "exitCode": 0 -│ │ │ │ } -│ │ │ │ } -│ │ │ │ metadata: { -│ │ │ │ "flue.session": "task:skill:", -│ │ │ │ "flue.tool_name": "bash", -│ │ │ │ "provider": "flue" -│ │ │ │ } -│ │ │ │ metrics: { -│ │ │ │ "duration_ms": 0 -│ │ │ │ } -│ │ │ ├── flue.turn [llm] -│ │ │ │ input: [ -│ │ │ │ { -│ │ │ │ "content": [ -│ │ │ │ { -│ │ │ │ "arguments": { -│ │ │ │ "command": "find . -maxdepth 4 -type f -name AGENTS.md -print -exec sed -n '1,200p' {} \\;", -│ │ │ │ "timeout": 100000 -│ │ │ │ }, -│ │ │ │ "id": "", -│ │ │ │ "name": "bash", -│ │ │ │ "type": "toolCall" -│ │ │ │ } -│ │ │ │ ], -│ │ │ │ "role": "assistant" -│ │ │ │ }, -│ │ │ │ { -│ │ │ │ "content": [ -│ │ │ │ { -│ │ │ │ "text": "(no output)", -│ │ │ │ "type": "text" -│ │ │ │ } -│ │ │ │ ], -│ │ │ │ "isError": false, -│ │ │ │ "role": "toolResult", -│ │ │ │ "toolCallId": "", -│ │ │ │ "toolName": "bash" -│ │ │ │ } -│ │ │ │ ] -│ │ │ │ output: { -│ │ │ │ "content": [ -│ │ │ │ { -│ │ │ │ "arguments": { -│ │ │ │ "command": "find . -maxdepth 6 -type f -name 'AGENTS.md' -print", -│ │ │ │ "timeout": 100000 -│ │ │ │ }, -│ │ │ │ "id": "", -│ │ │ │ "name": "bash", -│ │ │ │ "type": "toolCall" -│ │ │ │ } -│ │ │ │ ], -│ │ │ │ "role": "assistant" -│ │ │ │ } -│ │ │ │ metadata: { -│ │ │ │ "flue.api": "openai-responses", -│ │ │ │ "flue.input_message_offset": 5, -│ │ │ │ "flue.input_mode": "delta", -│ │ │ │ "flue.model": "gpt-5.4-nano", -│ │ │ │ "flue.provider": "openai", -│ │ │ │ "flue.session": "task:skill:", -│ │ │ │ "flue.stop_reason": "toolUse", -│ │ │ │ "flue.turn_purpose": "agent", -│ │ │ │ "model": "gpt-5.4-nano", -│ │ │ │ "provider": "openai" -│ │ │ │ } -│ │ │ │ metrics: { -│ │ │ │ "completion_tokens": 58, -│ │ │ │ "duration_ms": 0, -│ │ │ │ "estimated_cost": 0.0003321, -│ │ │ │ "prompt_cache_creation_tokens": 0, -│ │ │ │ "prompt_cached_tokens": 0, -│ │ │ │ "prompt_tokens": 1298, -│ │ │ │ "tokens": 1356 -│ │ │ │ } -│ │ │ ├── tool:bash [tool] -│ │ │ │ input: { -│ │ │ │ "command": "find . -maxdepth 6 -type f -name 'AGENTS.md' -print", -│ │ │ │ "timeout": 100000 -│ │ │ │ } -│ │ │ │ output: { -│ │ │ │ "content": [ -│ │ │ │ { -│ │ │ │ "text": "(no output)", -│ │ │ │ "type": "text" -│ │ │ │ } -│ │ │ │ ], -│ │ │ │ "details": { -│ │ │ │ "command": "find . -maxdepth 6 -type f -name 'AGENTS.md' -print", -│ │ │ │ "exitCode": 0 -│ │ │ │ } -│ │ │ │ } -│ │ │ │ metadata: { -│ │ │ │ "flue.session": "task:skill:", -│ │ │ │ "flue.tool_name": "bash", -│ │ │ │ "provider": "flue" -│ │ │ │ } -│ │ │ │ metrics: { -│ │ │ │ "duration_ms": 0 -│ │ │ │ } -│ │ │ ├── flue.turn [llm] -│ │ │ │ input: [ -│ │ │ │ { -│ │ │ │ "content": [ -│ │ │ │ { -│ │ │ │ "arguments": { -│ │ │ │ "command": "find . -maxdepth 6 -type f -name 'AGENTS.md' -print", -│ │ │ │ "timeout": 100000 -│ │ │ │ }, -│ │ │ │ "id": "", -│ │ │ │ "name": "bash", -│ │ │ │ "type": "toolCall" -│ │ │ │ } -│ │ │ │ ], -│ │ │ │ "role": "assistant" -│ │ │ │ }, -│ │ │ │ { -│ │ │ │ "content": [ -│ │ │ │ { -│ │ │ │ "text": "(no output)", -│ │ │ │ "type": "text" -│ │ │ │ } -│ │ │ │ ], -│ │ │ │ "isError": false, -│ │ │ │ "role": "toolResult", -│ │ │ │ "toolCallId": "", -│ │ │ │ "toolName": "bash" -│ │ │ │ } -│ │ │ │ ] -│ │ │ │ output: { -│ │ │ │ "content": [ -│ │ │ │ { -│ │ │ │ "arguments": { -│ │ │ │ "command": "find . -maxdepth 5 -type f -name '*flue*skill*' -o -name '*skill*' | head", -│ │ │ │ "timeout": 100000 -│ │ │ │ }, -│ │ │ │ "id": "", -│ │ │ │ "name": "bash", -│ │ │ │ "type": "toolCall" -│ │ │ │ } -│ │ │ │ ], -│ │ │ │ "role": "assistant" -│ │ │ │ } -│ │ │ │ metadata: { -│ │ │ │ "flue.api": "openai-responses", -│ │ │ │ "flue.input_message_offset": 7, -│ │ │ │ "flue.input_mode": "delta", -│ │ │ │ "flue.model": "gpt-5.4-nano", -│ │ │ │ "flue.provider": "openai", -│ │ │ │ "flue.session": "task:skill:", -│ │ │ │ "flue.stop_reason": "toolUse", -│ │ │ │ "flue.turn_purpose": "agent", -│ │ │ │ "model": "gpt-5.4-nano", -│ │ │ │ "provider": "openai" -│ │ │ │ } -│ │ │ │ metrics: { -│ │ │ │ "completion_tokens": 68, -│ │ │ │ "duration_ms": 0, -│ │ │ │ "estimated_cost": 0.00035900000000000005, -│ │ │ │ "prompt_cache_creation_tokens": 0, -│ │ │ │ "prompt_cached_tokens": 0, -│ │ │ │ "prompt_tokens": 1370, -│ │ │ │ "tokens": 1438 -│ │ │ │ } -│ │ │ ├── tool:bash [tool] -│ │ │ │ input: { -│ │ │ │ "command": "find . -maxdepth 5 -type f -name '*flue*skill*' -o -name '*skill*' | head", -│ │ │ │ "timeout": 100000 -│ │ │ │ } -│ │ │ │ output: { -│ │ │ │ "content": [ -│ │ │ │ { -│ │ │ │ "text": "./.agents/skills\n./.agents/skills/e2e-flue-skill", -│ │ │ │ "type": "text" -│ │ │ │ } -│ │ │ │ ], -│ │ │ │ "details": { -│ │ │ │ "command": "find . -maxdepth 5 -type f -name '*flue*skill*' -o -name '*skill*' | head", -│ │ │ │ "exitCode": 0 -│ │ │ │ } -│ │ │ │ } -│ │ │ │ metadata: { -│ │ │ │ "flue.session": "task:skill:", -│ │ │ │ "flue.tool_name": "bash", -│ │ │ │ "provider": "flue" -│ │ │ │ } -│ │ │ │ metrics: { -│ │ │ │ "duration_ms": 0 -│ │ │ │ } -│ │ │ ├── flue.turn [llm] -│ │ │ │ input: [ -│ │ │ │ { -│ │ │ │ "content": [ -│ │ │ │ { -│ │ │ │ "arguments": { -│ │ │ │ "command": "find . -maxdepth 5 -type f -name '*flue*skill*' -o -name '*skill*' | head", -│ │ │ │ "timeout": 100000 -│ │ │ │ }, -│ │ │ │ "id": "", -│ │ │ │ "name": "bash", -│ │ │ │ "type": "toolCall" -│ │ │ │ } -│ │ │ │ ], -│ │ │ │ "role": "assistant" -│ │ │ │ }, -│ │ │ │ { -│ │ │ │ "content": [ -│ │ │ │ { -│ │ │ │ "text": "./.agents/skills\n./.agents/skills/e2e-flue-skill", -│ │ │ │ "type": "text" -│ │ │ │ } -│ │ │ │ ], -│ │ │ │ "isError": false, -│ │ │ │ "role": "toolResult", -│ │ │ │ "toolCallId": "", -│ │ │ │ "toolName": "bash" -│ │ │ │ } -│ │ │ │ ] -│ │ │ │ output: { -│ │ │ │ "content": [ -│ │ │ │ { -│ │ │ │ "arguments": { -│ │ │ │ "command": "find ./.agents/skills/e2e-flue-skill -maxdepth 2 -type f -print", -│ │ │ │ "timeout": 100000 -│ │ │ │ }, -│ │ │ │ "id": "", -│ │ │ │ "name": "bash", -│ │ │ │ "type": "toolCall" -│ │ │ │ } -│ │ │ │ ], -│ │ │ │ "role": "assistant" -│ │ │ │ } -│ │ │ │ metadata: { -│ │ │ │ "flue.api": "openai-responses", -│ │ │ │ "flue.input_message_offset": 9, -│ │ │ │ "flue.input_mode": "delta", -│ │ │ │ "flue.model": "gpt-5.4-nano", -│ │ │ │ "flue.provider": "openai", -│ │ │ │ "flue.session": "task:skill:", -│ │ │ │ "flue.stop_reason": "toolUse", -│ │ │ │ "flue.turn_purpose": "agent", -│ │ │ │ "model": "gpt-5.4-nano", -│ │ │ │ "provider": "openai" -│ │ │ │ } -│ │ │ │ metrics: { -│ │ │ │ "completion_tokens": 109, -│ │ │ │ "duration_ms": 0, -│ │ │ │ "estimated_cost": 0.00043885000000000007, -│ │ │ │ "prompt_cache_creation_tokens": 0, -│ │ │ │ "prompt_cached_tokens": 0, -│ │ │ │ "prompt_tokens": 1513, -│ │ │ │ "tokens": 1622 -│ │ │ │ } -│ │ │ ├── tool:bash [tool] -│ │ │ │ input: { -│ │ │ │ "command": "find ./.agents/skills/e2e-flue-skill -maxdepth 2 -type f -print", -│ │ │ │ "timeout": 100000 -│ │ │ │ } -│ │ │ │ output: { -│ │ │ │ "content": [ -│ │ │ │ { -│ │ │ │ "text": "./.agents/skills/e2e-flue-skill/SKILL.md", -│ │ │ │ "type": "text" -│ │ │ │ } -│ │ │ │ ], -│ │ │ │ "details": { -│ │ │ │ "command": "find ./.agents/skills/e2e-flue-skill -maxdepth 2 -type f -print", -│ │ │ │ "exitCode": 0 -│ │ │ │ } -│ │ │ │ } -│ │ │ │ metadata: { -│ │ │ │ "flue.session": "task:skill:", -│ │ │ │ "flue.tool_name": "bash", -│ │ │ │ "provider": "flue" -│ │ │ │ } -│ │ │ │ metrics: { -│ │ │ │ "duration_ms": 0 -│ │ │ │ } -│ │ │ ├── flue.turn [llm] -│ │ │ │ input: [ -│ │ │ │ { -│ │ │ │ "content": [ -│ │ │ │ { -│ │ │ │ "arguments": { -│ │ │ │ "command": "find ./.agents/skills/e2e-flue-skill -maxdepth 2 -type f -print", -│ │ │ │ "timeout": 100000 -│ │ │ │ }, -│ │ │ │ "id": "", -│ │ │ │ "name": "bash", -│ │ │ │ "type": "toolCall" -│ │ │ │ } -│ │ │ │ ], -│ │ │ │ "role": "assistant" -│ │ │ │ }, -│ │ │ │ { -│ │ │ │ "content": [ -│ │ │ │ { -│ │ │ │ "text": "./.agents/skills/e2e-flue-skill/SKILL.md", -│ │ │ │ "type": "text" -│ │ │ │ } -│ │ │ │ ], -│ │ │ │ "isError": false, -│ │ │ │ "role": "toolResult", -│ │ │ │ "toolCallId": "", -│ │ │ │ "toolName": "bash" -│ │ │ │ } -│ │ │ │ ] -│ │ │ │ output: { -│ │ │ │ "content": [ -│ │ │ │ { -│ │ │ │ "arguments": { -│ │ │ │ "limit": 200, -│ │ │ │ "offset": 1, -│ │ │ │ "path": "./.agents/skills/e2e-flue-skill/SKILL.md" -│ │ │ │ }, -│ │ │ │ "id": "", -│ │ │ │ "name": "read", -│ │ │ │ "type": "toolCall" -│ │ │ │ } -│ │ │ │ ], -│ │ │ │ "role": "assistant" -│ │ │ │ } -│ │ │ │ metadata: { -│ │ │ │ "flue.api": "openai-responses", -│ │ │ │ "flue.input_message_offset": 11, -│ │ │ │ "flue.input_mode": "delta", -│ │ │ │ "flue.model": "gpt-5.4-nano", -│ │ │ │ "flue.provider": "openai", -│ │ │ │ "flue.session": "task:skill:", -│ │ │ │ "flue.stop_reason": "toolUse", -│ │ │ │ "flue.turn_purpose": "agent", -│ │ │ │ "model": "gpt-5.4-nano", -│ │ │ │ "provider": "openai" -│ │ │ │ } -│ │ │ │ metrics: { -│ │ │ │ "completion_tokens": 64, -│ │ │ │ "duration_ms": 0, -│ │ │ │ "estimated_cost": 0.00015592, -│ │ │ │ "prompt_cache_creation_tokens": 0, -│ │ │ │ "prompt_cached_tokens": 1536, -│ │ │ │ "prompt_tokens": 1762, -│ │ │ │ "tokens": 1826 -│ │ │ │ } -│ │ │ ├── tool:read [tool] -│ │ │ │ input: { -│ │ │ │ "limit": 200, -│ │ │ │ "offset": 1, -│ │ │ │ "path": "./.agents/skills/e2e-flue-skill/SKILL.md" -│ │ │ │ } -│ │ │ │ output: { -│ │ │ │ "content": [ -│ │ │ │ { -│ │ │ │ "text": "---\nname: e2e-flue-skill\ndescription: Deterministic skill used by the Braintrust Flue instrumentation e2e test.\n---\n\nReturn the marker from the provided args exactly once. Output no other text.\n", -│ │ │ │ "type": "text" -│ │ │ │ } -│ │ │ │ ], -│ │ │ │ "details": { -│ │ │ │ "lines": 7, -│ │ │ │ "path": "./.agents/skills/e2e-flue-skill/SKILL.md" -│ │ │ │ } -│ │ │ │ } -│ │ │ │ metadata: { -│ │ │ │ "flue.session": "task:skill:", -│ │ │ │ "flue.tool_name": "read", -│ │ │ │ "provider": "flue" -│ │ │ │ } -│ │ │ │ metrics: { -│ │ │ │ "duration_ms": 0 -│ │ │ │ } -│ │ │ ├── flue.turn [llm] -│ │ │ │ input: [ -│ │ │ │ { -│ │ │ │ "content": [ -│ │ │ │ { -│ │ │ │ "arguments": { -│ │ │ │ "limit": 200, -│ │ │ │ "offset": 1, -│ │ │ │ "path": "./.agents/skills/e2e-flue-skill/SKILL.md" -│ │ │ │ }, -│ │ │ │ "id": "", -│ │ │ │ "name": "read", -│ │ │ │ "type": "toolCall" -│ │ │ │ } -│ │ │ │ ], -│ │ │ │ "role": "assistant" -│ │ │ │ }, -│ │ │ │ { -│ │ │ │ "content": [ -│ │ │ │ { -│ │ │ │ "text": "---\nname: e2e-flue-skill\ndescription: Deterministic skill used by the Braintrust Flue instrumentation e2e test.\n---\n\nReturn the marker from the provided args exactly once. Output no other text.\n", -│ │ │ │ "type": "text" -│ │ │ │ } -│ │ │ │ ], -│ │ │ │ "isError": false, -│ │ │ │ "role": "toolResult", -│ │ │ │ "toolCallId": "", -│ │ │ │ "toolName": "read" -│ │ │ │ } -│ │ │ │ ] -│ │ │ │ output: { -│ │ │ │ "content": [ -│ │ │ │ { -│ │ │ │ "arguments": { -│ │ │ │ "command": "cat ./package.json", -│ │ │ │ "timeout": 100000 -│ │ │ │ }, -│ │ │ │ "id": "", -│ │ │ │ "name": "bash", -│ │ │ │ "type": "toolCall" -│ │ │ │ } -│ │ │ │ ], -│ │ │ │ "role": "assistant" -│ │ │ │ } -│ │ │ │ metadata: { -│ │ │ │ "flue.api": "openai-responses", -│ │ │ │ "flue.input_message_offset": 13, -│ │ │ │ "flue.input_mode": "delta", -│ │ │ │ "flue.model": "gpt-5.4-nano", -│ │ │ │ "flue.provider": "openai", -│ │ │ │ "flue.session": "task:skill:", -│ │ │ │ "flue.stop_reason": "toolUse", -│ │ │ │ "flue.turn_purpose": "agent", -│ │ │ │ "model": "gpt-5.4-nano", -│ │ │ │ "provider": "openai" -│ │ │ │ } -│ │ │ │ metrics: { -│ │ │ │ "completion_tokens": 90, -│ │ │ │ "duration_ms": 0, -│ │ │ │ "estimated_cost": 0.00021242000000000003, -│ │ │ │ "prompt_cache_creation_tokens": 0, -│ │ │ │ "prompt_cached_tokens": 1536, -│ │ │ │ "prompt_tokens": 1882, -│ │ │ │ "tokens": 1972 -│ │ │ │ } -│ │ │ ├── tool:bash [tool] -│ │ │ │ input: { -│ │ │ │ "command": "cat ./package.json", -│ │ │ │ "timeout": 100000 -│ │ │ │ } -│ │ │ │ output: { -│ │ │ │ "content": [ -│ │ │ │ { -│ │ │ │ "text": "{\n \"name\": \"flue-instrumentation-scenario\",\n \"private\": true,\n \"type\": \"module\",\n \"braintrustScenario\": {\n \"bump\": {\n \"dependencies\": {\n \"flue-cli-v0-8-latest\": {\n \"package\": \"@flue/cli\",\n \"range\": \">=0.8.0 <0.9.0\"\n },\n \"flue-cli-v1-latest\": {\n \"package\": \"@flue/cli\",\n \"range\": \">=1.0.0-beta.3 <2.0.0-0\",\n \"allowPrerelease\": true\n },\n \"flue-runtime-v0-8-latest\": {\n \"package\": \"@flue/runtime\",\n \"range\": \">=0.8.0 <0.9.0\"\n },\n \"flue-runtime-v1-latest\": {\n \"package\": \"@flue/runtime\",\n \"range\": \">=1.0.0-beta.3 <2.0.0-0\",\n \"allowPrerelease\": true\n },\n \"flue-runtime-v2-latest\": {\n \"package\": \"@flue/runtime\",\n \"range\": \">=2.0.0 <3.0.0\"\n }\n }\n }\n },\n \"dependencies\": {\n \"@flue/cli\": \"0.8.0\",\n \"@flue/runtime\": \"0.8.0\",\n \"flue-cli-v0-8-latest\": \"npm:@flue/cli@0.8.1\",\n \"flue-cli-v1\": \"npm:@flue/cli@1.0.0-beta.3\",\n \"flue-cli-v1-latest\": \"npm:@flue/cli@1.0.0-beta.9\",\n \"flue-runtime-v0-8-latest\": \"npm:@flue/runtime@0.8.1\",\n \"flue-runtime-v1\": \"npm:@flue/runtime@1.0.0-beta.3\",\n \"flue-runtime-v1-latest\": \"npm:@flue/runtime@1.0.0-beta.9\",\n \"flue-runtime-v2\": \"npm:@flue/runtime@2.0.0\",\n \"flue-runtime-v2-latest\": \"npm:@flue/runtime@2.0.1\",\n \"pi-ai-v2\": \"npm:@earendil-works/pi-ai@0.83.0\",\n \"valibot\": \"1.4.2\"\n },\n \"pnpm\": {\n \"overrides\": {\n \"@flue/runtime@0.8.0>@earendil-works/pi-agent-core\": \"0.75.4\",\n \"@flue/runtime@0.8.0>@earendil-works/pi-ai\": \"0.75.4\",\n \"@flue/runtime@0.8.1>@earendil-works/pi-agent-core\": \"0.75.4\",\n \"@flue/runtime@0.8.1>@earendil-works/pi-ai\": \"0.75.4\",\n \"@hono/node-server@>=2.0.0 <2.0.10\": \"^2.0.10\",\n \"fast-uri@<3.1.5\": \"3.1.8\",\n \"brace-expansion@>=3.0.0 <5.0.9\": \"5.0.12\",\n \"postcss@<8.5.18\": \"^8.5.18\",\n \"valibot@<1.4.2\": \"^1.4.2\",\n \"ip-address@<10.2.2\": \"10.7.1\",\n \"undici@<7.29.0\": \"7.29.1\",\n \"hono@<4.12.34\": \"4.13.7\",\n \"@hono/node-server@<1.19.15\": \"^1.19.15\",\n \"nanoid@<3.3.18\": \"^3.3.18\",\n \"js-yaml@3\": \"^3.15.1\",\n \"js-yaml@4\": \"^4.3.1\",\n \"sharp@<0.35.0\": \"0.35.5\",\n \"ip-address@>=10.0.0 <10.7.1\": \"10.7.1\",\n \"fast-uri@>=3.0.0 <3.1.8\": \"3.1.8\",\n \"undici@>=7.0.0 <7.29.1\": \"7.29.1\",\n \"js-yaml@>=5.0.0 <5.4.1\": \"5.4.1\",\n \"brace-expansion@>=5.0.0 <5.0.12\": \"5.0.12\",\n \"hono@>=4.0.0 <4.13.7\": \"4.13.7\",\n \"proxy-addr@>=2.0.0 <2.0.8\": \"2.0.8\",\n \"source-map-js@>=1.0.0 <1.2.2\": \"1.2.2\",\n \"smol-toml@>=1.0.0 <1.9.0\": \"1.9.0\",\n \"sharp@>=0.35.0 <0.35.5\": \"0.35.5\",\n \"@modelcontextprotocol/sdk@>=1.0.0 <1.31.0\": \"1.31.0\",\n \"@modelcontextprotocol/client@>=2.0.0 <2.2.0\": \"2.2.0\"\n }\n }\n}", -│ │ │ │ "type": "text" -│ │ │ │ } -│ │ │ │ ], -│ │ │ │ "details": { -│ │ │ │ "command": "cat ./package.json", -│ │ │ │ "exitCode": 0 -│ │ │ │ } -│ │ │ │ } -│ │ │ │ metadata: { -│ │ │ │ "flue.session": "task:skill:", -│ │ │ │ "flue.tool_name": "bash", -│ │ │ │ "provider": "flue" -│ │ │ │ } -│ │ │ │ metrics: { -│ │ │ │ "duration_ms": 0 -│ │ │ │ } -│ │ │ ├── flue.turn [llm] -│ │ │ │ input: [ -│ │ │ │ { -│ │ │ │ "content": [ -│ │ │ │ { -│ │ │ │ "arguments": { -│ │ │ │ "command": "cat ./package.json", -│ │ │ │ "timeout": 100000 -│ │ │ │ }, -│ │ │ │ "id": "", -│ │ │ │ "name": "bash", -│ │ │ │ "type": "toolCall" -│ │ │ │ } -│ │ │ │ ], -│ │ │ │ "role": "assistant" -│ │ │ │ }, -│ │ │ │ { -│ │ │ │ "content": [ -│ │ │ │ { -│ │ │ │ "text": "{\n \"name\": \"flue-instrumentation-scenario\",\n \"private\": true,\n \"type\": \"module\",\n \"braintrustScenario\": {\n \"bump\": {\n \"dependencies\": {\n \"flue-cli-v0-8-latest\": {\n \"package\": \"@flue/cli\",\n \"range\": \">=0.8.0 <0.9.0\"\n },\n \"flue-cli-v1-latest\": {\n \"package\": \"@flue/cli\",\n \"range\": \">=1.0.0-beta.3 <2.0.0-0\",\n \"allowPrerelease\": true\n },\n \"flue-runtime-v0-8-latest\": {\n \"package\": \"@flue/runtime\",\n \"range\": \">=0.8.0 <0.9.0\"\n },\n \"flue-runtime-v1-latest\": {\n \"package\": \"@flue/runtime\",\n \"range\": \">=1.0.0-beta.3 <2.0.0-0\",\n \"allowPrerelease\": true\n },\n \"flue-runtime-v2-latest\": {\n \"package\": \"@flue/runtime\",\n \"range\": \">=2.0.0 <3.0.0\"\n }\n }\n }\n },\n \"dependencies\": {\n \"@flue/cli\": \"0.8.0\",\n \"@flue/runtime\": \"0.8.0\",\n \"flue-cli-v0-8-latest\": \"npm:@flue/cli@0.8.1\",\n \"flue-cli-v1\": \"npm:@flue/cli@1.0.0-beta.3\",\n \"flue-cli-v1-latest\": \"npm:@flue/cli@1.0.0-beta.9\",\n \"flue-runtime-v0-8-latest\": \"npm:@flue/runtime@0.8.1\",\n \"flue-runtime-v1\": \"npm:@flue/runtime@1.0.0-beta.3\",\n \"flue-runtime-v1-latest\": \"npm:@flue/runtime@1.0.0-beta.9\",\n \"flue-runtime-v2\": \"npm:@flue/runtime@2.0.0\",\n \"flue-runtime-v2-latest\": \"npm:@flue/runtime@2.0.1\",\n \"pi-ai-v2\": \"npm:@earendil-works/pi-ai@0.83.0\",\n \"valibot\": \"1.4.2\"\n },\n \"pnpm\": {\n \"overrides\": {\n \"@flue/runtime@0.8.0>@earendil-works/pi-agent-core\": \"0.75.4\",\n \"@flue/runtime@0.8.0>@earendil-works/pi-ai\": \"0.75.4\",\n \"@flue/runtime@0.8.1>@earendil-works/pi-agent-core\": \"0.75.4\",\n \"@flue/runtime@0.8.1>@earendil-works/pi-ai\": \"0.75.4\",\n \"@hono/node-server@>=2.0.0 <2.0.10\": \"^2.0.10\",\n \"fast-uri@<3.1.5\": \"3.1.8\",\n \"brace-expansion@>=3.0.0 <5.0.9\": \"5.0.12\",\n \"postcss@<8.5.18\": \"^8.5.18\",\n \"valibot@<1.4.2\": \"^1.4.2\",\n \"ip-address@<10.2.2\": \"10.7.1\",\n \"undici@<7.29.0\": \"7.29.1\",\n \"hono@<4.12.34\": \"4.13.7\",\n \"@hono/node-server@<1.19.15\": \"^1.19.15\",\n \"nanoid@<3.3.18\": \"^3.3.18\",\n \"js-yaml@3\": \"^3.15.1\",\n \"js-yaml@4\": \"^4.3.1\",\n \"sharp@<0.35.0\": \"0.35.5\",\n \"ip-address@>=10.0.0 <10.7.1\": \"10.7.1\",\n \"fast-uri@>=3.0.0 <3.1.8\": \"3.1.8\",\n \"undici@>=7.0.0 <7.29.1\": \"7.29.1\",\n \"js-yaml@>=5.0.0 <5.4.1\": \"5.4.1\",\n \"brace-expansion@>=5.0.0 <5.0.12\": \"5.0.12\",\n \"hono@>=4.0.0 <4.13.7\": \"4.13.7\",\n \"proxy-addr@>=2.0.0 <2.0.8\": \"2.0.8\",\n \"source-map-js@>=1.0.0 <1.2.2\": \"1.2.2\",\n \"smol-toml@>=1.0.0 <1.9.0\": \"1.9.0\",\n \"sharp@>=0.35.0 <0.35.5\": \"0.35.5\",\n \"@modelcontextprotocol/sdk@>=1.0.0 <1.31.0\": \"1.31.0\",\n \"@modelcontextprotocol/client@>=2.0.0 <2.2.0\": \"2.2.0\"\n }\n }\n}", -│ │ │ │ "type": "text" -│ │ │ │ } -│ │ │ │ ], -│ │ │ │ "isError": false, -│ │ │ │ "role": "toolResult", -│ │ │ │ "toolCallId": "", -│ │ │ │ "toolName": "bash" -│ │ │ │ } -│ │ │ │ ] -│ │ │ │ output: { -│ │ │ │ "content": [ -│ │ │ │ { -│ │ │ │ "arguments": { -│ │ │ │ "command": "printf SKILL_DONE", -│ │ │ │ "timeout": 100000 -│ │ │ │ }, -│ │ │ │ "id": "", -│ │ │ │ "name": "bash", -│ │ │ │ "type": "toolCall" -│ │ │ │ } -│ │ │ │ ], -│ │ │ │ "role": "assistant" -│ │ │ │ } -│ │ │ │ metadata: { -│ │ │ │ "flue.api": "openai-responses", -│ │ │ │ "flue.input_message_offset": 15, -│ │ │ │ "flue.input_mode": "delta", -│ │ │ │ "flue.model": "gpt-5.4-nano", -│ │ │ │ "flue.provider": "openai", -│ │ │ │ "flue.session": "task:skill:", -│ │ │ │ "flue.stop_reason": "toolUse", -│ │ │ │ "flue.turn_purpose": "agent", -│ │ │ │ "model": "gpt-5.4-nano", -│ │ │ │ "provider": "openai" -│ │ │ │ } -│ │ │ │ metrics: { -│ │ │ │ "completion_tokens": 59, -│ │ │ │ "duration_ms": 0, -│ │ │ │ "estimated_cost": 0.0004814700000000001, -│ │ │ │ "prompt_cache_creation_tokens": 0, -│ │ │ │ "prompt_cached_tokens": 1536, -│ │ │ │ "prompt_tokens": 3421, -│ │ │ │ "tokens": 3480 -│ │ │ │ } -│ │ │ ├── tool:bash [tool] -│ │ │ │ input: { -│ │ │ │ "command": "printf SKILL_DONE", -│ │ │ │ "timeout": 100000 -│ │ │ │ } -│ │ │ │ output: { -│ │ │ │ "content": [ -│ │ │ │ { -│ │ │ │ "text": "SKILL_DONE", -│ │ │ │ "type": "text" -│ │ │ │ } -│ │ │ │ ], -│ │ │ │ "details": { -│ │ │ │ "command": "printf SKILL_DONE", -│ │ │ │ "exitCode": 0 -│ │ │ │ } -│ │ │ │ } -│ │ │ │ metadata: { -│ │ │ │ "flue.session": "task:skill:", -│ │ │ │ "flue.tool_name": "bash", -│ │ │ │ "provider": "flue" -│ │ │ │ } -│ │ │ │ metrics: { -│ │ │ │ "duration_ms": 0 -│ │ │ │ } -│ │ │ └── flue.turn [llm] -│ │ │ input: [ -│ │ │ { -│ │ │ "content": [ -│ │ │ { -│ │ │ "arguments": { -│ │ │ "command": "printf SKILL_DONE", -│ │ │ "timeout": 100000 -│ │ │ }, -│ │ │ "id": "", -│ │ │ "name": "bash", -│ │ │ "type": "toolCall" -│ │ │ } -│ │ │ ], -│ │ │ "role": "assistant" -│ │ │ }, -│ │ │ { -│ │ │ "content": [ -│ │ │ { -│ │ │ "text": "SKILL_DONE", -│ │ │ "type": "text" -│ │ │ } -│ │ │ ], -│ │ │ "isError": false, -│ │ │ "role": "toolResult", -│ │ │ "toolCallId": "", -│ │ │ "toolName": "bash" -│ │ │ } -│ │ │ ] -│ │ │ output: { -│ │ │ "content": [ -│ │ │ { -│ │ │ "text": "SKILL_DONE", -│ │ │ "textSignature": "{\"v\":1,\"id\":\"msg_054a16a2f2ea3217016a705b70a41481a38ece40ecddf69512\",\"phase\":\"final_answer\"}", -│ │ │ "type": "text" -│ │ │ } -│ │ │ ], -│ │ │ "role": "assistant" -│ │ │ } -│ │ │ metadata: { -│ │ │ "flue.api": "openai-responses", -│ │ │ "flue.input_message_offset": 17, -│ │ │ "flue.input_mode": "delta", -│ │ │ "flue.model": "gpt-5.4-nano", -│ │ │ "flue.provider": "openai", -│ │ │ "flue.session": "task:skill:", -│ │ │ "flue.stop_reason": "stop", -│ │ │ "flue.turn_purpose": "agent", -│ │ │ "model": "gpt-5.4-nano", -│ │ │ "provider": "openai" -│ │ │ } -│ │ │ metrics: { -│ │ │ "completion_tokens": 7, -│ │ │ "duration_ms": 0, -│ │ │ "estimated_cost": 0.00031035000000000004, -│ │ │ "prompt_cache_creation_tokens": 0, -│ │ │ "prompt_cached_tokens": 2560, -│ │ │ "prompt_tokens": 3812, -│ │ │ "tokens": 3819 -│ │ │ } -│ │ ├── tool:task [tool] -│ │ │ input: { -│ │ │ "cwd": ".", -│ │ │ "description": "Run the deterministic Flue instrumentation e2e test skill e2e-flue-skill.", -│ │ │ "prompt": "Run the skill named \"e2e-flue-skill\" with arguments {\"marker\":\"SKILL_DONE\"}. Output only the marker and no extra text when finished." -│ │ │ } -│ │ │ output: { -│ │ │ "content": [ -│ │ │ { -│ │ │ "text": "SKILL_DONE", -│ │ │ "type": "text" -│ │ │ } -│ │ │ ], -│ │ │ "details": { -│ │ │ "cwd": ".", -│ │ │ "messageId": "", -│ │ │ "session": "task:skill:", -│ │ │ "taskId": "" -│ │ │ } -│ │ │ } -│ │ │ metadata: { -│ │ │ "flue.session": "skill", -│ │ │ "flue.tool_name": "task", -│ │ │ "provider": "flue" -│ │ │ } -│ │ │ metrics: { -│ │ │ "duration_ms": 0 -│ │ │ } -│ │ └── flue.turn [llm] -│ │ input: [ -│ │ { -│ │ "content": [ -│ │ { -│ │ "text": "Run the skill named \"e2e-flue-skill\".\n\nArguments:\n{\n \"marker\": \"SKILL_DONE\"\n}", -│ │ "type": "text" -│ │ } -│ │ ], -│ │ "role": "user" -│ │ }, -│ │ { -│ │ "content": [ -│ │ { -│ │ "arguments": { -│ │ "cwd": ".", -│ │ "description": "Run the deterministic Flue instrumentation e2e test skill e2e-flue-skill.", -│ │ "prompt": "Run the skill named \"e2e-flue-skill\" with arguments {\"marker\":\"SKILL_DONE\"}. Output only the marker and no extra text when finished." -│ │ }, -│ │ "id": "", -│ │ "name": "task", -│ │ "type": "toolCall" -│ │ } -│ │ ], -│ │ "role": "assistant" -│ │ }, -│ │ { -│ │ "content": [ -│ │ { -│ │ "text": "SKILL_DONE", -│ │ "type": "text" -│ │ } -│ │ ], -│ │ "isError": false, -│ │ "role": "toolResult", -│ │ "toolCallId": "", -│ │ "toolName": "task" -│ │ } -│ │ ] -│ │ output: { -│ │ "content": [ -│ │ { -│ │ "text": "SKILL_DONE", -│ │ "textSignature": "{\"v\":1,\"id\":\"msg_0f099b3fce0721fd016a705b71558881a38c53a767e09d9e31\",\"phase\":\"final_answer\"}", -│ │ "type": "text" -│ │ } -│ │ ], -│ │ "role": "assistant" -│ │ } -│ │ metadata: { -│ │ "flue.api": "openai-responses", -│ │ "flue.model": "gpt-5.4-nano", -│ │ "flue.provider": "openai", -│ │ "flue.session": "skill", -│ │ "flue.stop_reason": "stop", -│ │ "flue.turn_purpose": "agent", -│ │ "model": "gpt-5.4-nano", -│ │ "provider": "openai" -│ │ } -│ │ metrics: { -│ │ "completion_tokens": 7, -│ │ "duration_ms": 0, -│ │ "estimated_cost": 0.00020255000000000002, -│ │ "prompt_cache_creation_tokens": 0, -│ │ "prompt_cached_tokens": 0, -│ │ "prompt_tokens": 969, -│ │ "tokens": 976 -│ │ } -│ ├── flue.task [task] -│ │ input: "Reply with exactly TASK_DONE and no other text." -│ │ output: "TASK_DONE" -│ │ metadata: { -│ │ "flue.session": "task", -│ │ "provider": "flue" -│ │ } -│ │ metrics: { -│ │ "duration_ms": 0 -│ │ } -│ │ └── flue.turn [llm] -│ │ input: [ -│ │ { -│ │ "content": [ -│ │ { -│ │ "text": "Reply with exactly TASK_DONE and no other text.", -│ │ "type": "text" -│ │ } -│ │ ], -│ │ "role": "user" -│ │ } -│ │ ] -│ │ output: { -│ │ "content": [ -│ │ { -│ │ "text": "TASK_DONE", -│ │ "textSignature": "{\"v\":1,\"id\":\"msg_0f3adfdb1510c165016a705b72843c81a0b30d242da683a882\"}", -│ │ "type": "text" -│ │ } -│ │ ], -│ │ "role": "assistant" -│ │ } -│ │ metadata: { -│ │ "flue.api": "openai-responses", -│ │ "flue.input_mode": "full", -│ │ "flue.model": "gpt-4o-mini", -│ │ "flue.provider": "openai", -│ │ "flue.session": "task:task:", -│ │ "flue.stop_reason": "stop", -│ │ "flue.turn_purpose": "agent", -│ │ "model": "gpt-4o-mini", -│ │ "provider": "openai" -│ │ } -│ │ metrics: { -│ │ "completion_tokens": 4, -│ │ "duration_ms": 0, -│ │ "estimated_cost": 0.0001263, -│ │ "prompt_cache_creation_tokens": 0, -│ │ "prompt_cached_tokens": 0, -│ │ "prompt_tokens": 826, -│ │ "tokens": 830 -│ │ } -│ └── flue.compact [task] -│ input: { -│ "estimatedTokens": 1229, -│ "reason": "manual" -│ } -│ output: { -│ "completed": true -│ } -│ metadata: { -│ "flue.operation": "compact", -│ "flue.session": "main", -│ "flue.workflow_name": "instrumentation", -│ "provider": "flue", -│ "scenario": "flue-instrumentation" -│ } -│ metrics: { -│ "duration_ms": 0 -│ } -│ └── compaction:manual [task] -│ input: { -│ "estimatedTokens": 1229, -│ "reason": "manual" -│ } -│ output: { -│ "messagesAfter": 2, -│ "messagesBefore": 8 -│ } -│ metadata: { -│ "flue.compaction_reason": "manual", -│ "flue.session": "main", -│ "provider": "flue" -│ } -│ metrics: { -│ "duration_ms": 0, -│ "messages_after": 2, -│ "messages_before": 8 -│ } -│ └── flue.turn [llm] -│ input: [ -│ { -│ "content": [ -│ { -│ "text": "\n[User]: Complete this instrumented research flow. Call exactly one tool per turn and wait for each tool result before choosing the next tool. Step 1: call lookup with query \"flue instrumentation\". Step 2: use the lookup result id as lookupId and call web_search with query \"Braintrust Flue reasoning stream instrumentation\". Step 3: use the first web_search result url and call summarize_source. After summarize_source returns, reply with exactly PROMPT_DONE and no other text.\n\n[Assistant tool calls]: lookup(query=\"flue instrumentation\")\n\n[Tool result]: {\"id\":\"flue-session-2026\",\"query\":\"flue instrumentation\",\"topic\":\"session instrumentation\"}\n\n[Assistant tool calls]: web_search(lookupId=\"flue-session-2026\", query=\"Braintrust Flue reasoning stream instrumentation\")\n\n[Tool result]: {\"lookupId\":\"flue-session-2026\",\"query\":\"Braintrust Flue reasoning stream instrumentation\",\"results\":[{\"title\":\"Flue reasoning stream instrumentation\",\"url\":\"https://example.test/flue/reasoning-streams\"}]}\n\n[Assistant tool calls]: summarize_source(url=\"https://example.test/flue/reasoning-streams\")\n\n[Tool result]: {\"summary\":\"Flue emits reasoning, tool execution, and LLM turn events separately.\",\"url\":\"https://example.test/flue/reasoning-streams\"}\n\n\nThis is the PREFIX of a turn that was too large to keep. The SUFFIX (recent work) is retained.\n\nSummarize the prefix to provide context for the retained suffix:\n\n## Original Request\n[What did the user ask for in this turn?]\n\n## Early Progress\n- [Key decisions and work done in the prefix]\n\n## Context for Suffix\n- [Information needed to understand the retained recent work]\n\nBe concise. Focus on what's needed to understand the kept suffix.", -│ "type": "text" -│ } -│ ], -│ "role": "user" -│ } -│ ] -│ output: { -│ "content": [ -│ { -│ "text": "## Original Request\nThe user requested a step-by-step execution of an instrumented research flow involving tool calls to gather information on \"flue instrumentation\" and \"", -│ "textSignature": "{\"v\":1,\"id\":\"msg_04d28ba03840bb65016a705b7358a081a38e69464057840036\"}", -│ "type": "text" -│ } -│ ], -│ "role": "assistant" -│ } -│ metadata: { -│ "flue.api": "openai-responses", -│ "flue.model": "gpt-4o-mini", -│ "flue.provider": "openai", -│ "flue.session": "main", -│ "flue.stop_reason": "stop", -│ "flue.turn_purpose": "compaction_prefix", -│ "model": "gpt-4o-mini", -│ "provider": "openai" -│ } -│ metrics: { -│ "completion_tokens": 0, -│ "duration_ms": 0, -│ "estimated_cost": 0, -│ "prompt_cache_creation_tokens": 0, -│ "prompt_cached_tokens": 0, -│ "prompt_tokens": 0, -│ "tokens": 0 -│ } -└── flue.prompt [task] - input: [ - { - "content": [ - { - "text": "Complete this instrumented research flow. Call exactly one tool per turn and wait for each tool result before choosing the next tool. Step 1: call lookup with query \"flue instrumentation\". Step 2: use the lookup result id as lookupId and call web_search with query \"Braintrust Flue reasoning stream instrumentation\". Step 3: use the first web_search result url and call summarize_source. After summarize_source returns, reply with exactly PROMPT_DONE and no other text.", - "type": "text" - } - ], - "role": "user" - } - ] - output: "PROMPT_DONE" - metadata: { - "flue.operation": "prompt", - "flue.session": "main", - "flue.workflow_name": "instrumentation", - "provider": "flue", - "scenario": "flue-instrumentation" - } - metrics: { - "duration_ms": 0 - } - ├── flue.turn [llm] - │ input: [ - │ { - │ "content": [ - │ { - │ "text": "Complete this instrumented research flow. Call exactly one tool per turn and wait for each tool result before choosing the next tool. Step 1: call lookup with query \"flue instrumentation\". Step 2: use the lookup result id as lookupId and call web_search with query \"Braintrust Flue reasoning stream instrumentation\". Step 3: use the first web_search result url and call summarize_source. After summarize_source returns, reply with exactly PROMPT_DONE and no other text.", - │ "type": "text" - │ } - │ ], - │ "role": "user" - │ } - │ ] - │ output: { - │ "content": [ - │ { - │ "arguments": { - │ "query": "flue instrumentation" - │ }, - │ "id": "", - │ "name": "lookup", - │ "type": "toolCall" - │ } - │ ], - │ "role": "assistant" - │ } - │ metadata: { - │ "flue.api": "openai-responses", - │ "flue.input_mode": "full", - │ "flue.model": "gpt-5.4-nano", - │ "flue.provider": "openai", - │ "flue.session": "main", - │ "flue.stop_reason": "toolUse", - │ "flue.turn_purpose": "agent", - │ "model": "gpt-5.4-nano", - │ "provider": "openai" - │ } - │ metrics: { - │ "completion_tokens": 19, - │ "duration_ms": 0, - │ "estimated_cost": 0.00022475000000000001, - │ "prompt_cache_creation_tokens": 0, - │ "prompt_cached_tokens": 0, - │ "prompt_tokens": 1005, - │ "tokens": 1024 - │ } - ├── tool:lookup [tool] - │ input: { - │ "query": "flue instrumentation" - │ } - │ output: { - │ "content": [ - │ { - │ "text": "{\"id\":\"flue-session-2026\",\"query\":\"flue instrumentation\",\"topic\":\"session instrumentation\"}", - │ "type": "text" - │ } - │ ], - │ "details": { - │ "customTool": "lookup" - │ } - │ } - │ metadata: { - │ "flue.session": "main", - │ "flue.tool_name": "lookup", - │ "provider": "flue" - │ } - │ metrics: { - │ "duration_ms": 0 - │ } - ├── flue.turn [llm] - │ input: [ - │ { - │ "content": [ - │ { - │ "arguments": { - │ "query": "flue instrumentation" - │ }, - │ "id": "", - │ "name": "lookup", - │ "type": "toolCall" - │ } - │ ], - │ "role": "assistant" - │ }, - │ { - │ "content": [ - │ { - │ "text": "{\"id\":\"flue-session-2026\",\"query\":\"flue instrumentation\",\"topic\":\"session instrumentation\"}", - │ "type": "text" - │ } - │ ], - │ "isError": false, - │ "role": "toolResult", - │ "toolCallId": "", - │ "toolName": "lookup" - │ } - │ ] - │ output: { - │ "content": [ - │ { - │ "arguments": { - │ "lookupId": "flue-session-2026", - │ "query": "Braintrust Flue reasoning stream instrumentation" - │ }, - │ "id": "", - │ "name": "web_search", - │ "type": "toolCall" - │ } - │ ], - │ "role": "assistant" - │ } - │ metadata: { - │ "flue.api": "openai-responses", - │ "flue.input_message_offset": 1, - │ "flue.input_mode": "delta", - │ "flue.model": "gpt-5.4-nano", - │ "flue.provider": "openai", - │ "flue.session": "main", - │ "flue.stop_reason": "toolUse", - │ "flue.turn_purpose": "agent", - │ "model": "gpt-5.4-nano", - │ "provider": "openai" - │ } - │ metrics: { - │ "completion_tokens": 34, - │ "duration_ms": 0, - │ "estimated_cost": 0.00025350000000000004, - │ "prompt_cache_creation_tokens": 0, - │ "prompt_cached_tokens": 0, - │ "prompt_tokens": 1055, - │ "tokens": 1089 - │ } - ├── tool:web_search [tool] - │ input: { - │ "lookupId": "flue-session-2026", - │ "query": "Braintrust Flue reasoning stream instrumentation" - │ } - │ output: { - │ "content": [ - │ { - │ "text": "{\"lookupId\":\"flue-session-2026\",\"query\":\"Braintrust Flue reasoning stream instrumentation\",\"results\":[{\"title\":\"Flue reasoning stream instrumentation\",\"url\":\"https://example.test/flue/reasoning-streams\"}]}", - │ "type": "text" - │ } - │ ], - │ "details": { - │ "customTool": "web_search" - │ } - │ } - │ metadata: { - │ "flue.session": "main", - │ "flue.tool_name": "web_search", - │ "provider": "flue" - │ } - │ metrics: { - │ "duration_ms": 0 - │ } - ├── flue.turn [llm] - │ input: [ - │ { - │ "content": [ - │ { - │ "arguments": { - │ "lookupId": "flue-session-2026", - │ "query": "Braintrust Flue reasoning stream instrumentation" - │ }, - │ "id": "", - │ "name": "web_search", - │ "type": "toolCall" - │ } - │ ], - │ "role": "assistant" - │ }, - │ { - │ "content": [ - │ { - │ "text": "{\"lookupId\":\"flue-session-2026\",\"query\":\"Braintrust Flue reasoning stream instrumentation\",\"results\":[{\"title\":\"Flue reasoning stream instrumentation\",\"url\":\"https://example.test/flue/reasoning-streams\"}]}", - │ "type": "text" - │ } - │ ], - │ "isError": false, - │ "role": "toolResult", - │ "toolCallId": "", - │ "toolName": "web_search" - │ } - │ ] - │ output: { - │ "content": [ - │ { - │ "arguments": { - │ "url": "https://example.test/flue/reasoning-streams" - │ }, - │ "id": "", - │ "name": "summarize_source", - │ "type": "toolCall" - │ } - │ ], - │ "role": "assistant" - │ } - │ metadata: { - │ "flue.api": "openai-responses", - │ "flue.input_message_offset": 3, - │ "flue.input_mode": "delta", - │ "flue.model": "gpt-5.4-nano", - │ "flue.provider": "openai", - │ "flue.session": "main", - │ "flue.stop_reason": "toolUse", - │ "flue.turn_purpose": "agent", - │ "model": "gpt-5.4-nano", - │ "provider": "openai" - │ } - │ metrics: { - │ "completion_tokens": 30, - │ "duration_ms": 0, - │ "estimated_cost": 0.00026690000000000004, - │ "prompt_cache_creation_tokens": 0, - │ "prompt_cached_tokens": 0, - │ "prompt_tokens": 1147, - │ "tokens": 1177 - │ } - ├── tool:summarize_source [tool] - │ input: { - │ "url": "https://example.test/flue/reasoning-streams" - │ } - │ output: { - │ "content": [ - │ { - │ "text": "{\"summary\":\"Flue emits reasoning, tool execution, and LLM turn events separately.\",\"url\":\"https://example.test/flue/reasoning-streams\"}", - │ "type": "text" - │ } - │ ], - │ "details": { - │ "customTool": "summarize_source" - │ } - │ } - │ metadata: { - │ "flue.session": "main", - │ "flue.tool_name": "summarize_source", - │ "provider": "flue" - │ } - │ metrics: { - │ "duration_ms": 0 - │ } - └── flue.turn [llm] - input: [ - { - "content": [ - { - "arguments": { - "url": "https://example.test/flue/reasoning-streams" - }, - "id": "", - "name": "summarize_source", - "type": "toolCall" - } - ], - "role": "assistant" - }, - { - "content": [ - { - "text": "{\"summary\":\"Flue emits reasoning, tool execution, and LLM turn events separately.\",\"url\":\"https://example.test/flue/reasoning-streams\"}", - "type": "text" - } - ], - "isError": false, - "role": "toolResult", - "toolCallId": "", - "toolName": "summarize_source" - } - ] - output: { - "content": [ - { - "text": "PROMPT_DONE", - "textSignature": "{\"v\":1,\"id\":\"msg_045458052dc10379016a705b62dbd8819189b8287061debf12\",\"phase\":\"final_answer\"}", - "type": "text" - } - ], - "role": "assistant" - } - metadata: { - "flue.api": "openai-responses", - "flue.input_message_offset": 5, - "flue.input_mode": "delta", - "flue.model": "gpt-5.4-nano", - "flue.provider": "openai", - "flue.session": "main", - "flue.stop_reason": "stop", - "flue.turn_purpose": "agent", - "model": "gpt-5.4-nano", - "provider": "openai" - } - metrics: { - "completion_tokens": 7, - "duration_ms": 0, - "estimated_cost": 0.00025315000000000005, - "prompt_cache_creation_tokens": 0, - "prompt_cached_tokens": 0, - "prompt_tokens": 1222, - "tokens": 1229 - } diff --git a/e2e/scenarios/flue-instrumentation/__snapshots__/flue-v0-8-latest-explicit.span-tree.json b/e2e/scenarios/flue-instrumentation/__snapshots__/flue-v0-8-latest-explicit.span-tree.json deleted file mode 100644 index 704887bdb..000000000 --- a/e2e/scenarios/flue-instrumentation/__snapshots__/flue-v0-8-latest-explicit.span-tree.json +++ /dev/null @@ -1,1662 +0,0 @@ -{ - "span_tree": [ - { - "name": "workflow:instrumentation", - "type": "task", - "children": [ - { - "name": "flue.skill", - "type": "task", - "children": [ - { - "name": "flue.turn", - "type": "llm", - "children": [ - { - "name": "flue.task", - "type": "task", - "children": [ - { - "name": "flue.turn", - "type": "llm", - "children": [], - "input": [ - { - "content": [ - { - "text": "Run the skill named \"e2e-flue-skill\" with arguments {\"marker\":\"SKILL_DONE\"}. Output only the marker and no extra text when finished.", - "type": "text" - } - ], - "role": "user" - } - ], - "output": { - "content": [ - { - "arguments": { - "agent": "e2e-flue-skill", - "cwd": ".", - "description": "Run e2e-flue-skill with marker SKILL_DONE", - "prompt": "Run the skill named \"e2e-flue-skill\" with arguments {\"marker\":\"SKILL_DONE\"}. Ensure completion and report the final output marker only." - }, - "id": "", - "name": "task", - "type": "toolCall" - } - ], - "role": "assistant" - }, - "metadata": { - "flue.api": "openai-responses", - "flue.input_mode": "full", - "flue.model": "gpt-5.4-nano", - "flue.provider": "openai", - "flue.session": "task:skill:", - "flue.stop_reason": "toolUse", - "flue.turn_purpose": "agent", - "model": "gpt-5.4-nano", - "provider": "openai" - }, - "metrics": { - "completion_tokens": 113, - "duration_ms": 0, - "estimated_cost": 0.00031225000000000003, - "prompt_cache_creation_tokens": 0, - "prompt_cached_tokens": 0, - "prompt_tokens": 855, - "tokens": 968 - } - }, - { - "name": "tool:task", - "type": "tool", - "children": [], - "input": { - "agent": "e2e-flue-skill", - "cwd": ".", - "description": "Run e2e-flue-skill with marker SKILL_DONE", - "prompt": "Run the skill named \"e2e-flue-skill\" with arguments {\"marker\":\"SKILL_DONE\"}. Ensure completion and report the final output marker only." - }, - "output": { - "content": [ - { - "text": "[flue] Subagent \"e2e-flue-skill\" is not declared. Available: (none).", - "type": "text" - } - ], - "details": {} - }, - "metadata": { - "flue.session": "task:skill:", - "flue.tool_name": "task", - "provider": "flue" - }, - "metrics": { - "duration_ms": 0 - }, - "error": "{\"content\":[{\"type\":\"text\",\"text\":\"[flue] Subagent \\\"e2e-flue-skill\\\" is not declared. Available: (none).\"}],\"details\":{}}" - }, - { - "name": "flue.turn", - "type": "llm", - "children": [], - "input": [ - { - "content": [ - { - "arguments": { - "agent": "e2e-flue-skill", - "cwd": ".", - "description": "Run e2e-flue-skill with marker SKILL_DONE", - "prompt": "Run the skill named \"e2e-flue-skill\" with arguments {\"marker\":\"SKILL_DONE\"}. Ensure completion and report the final output marker only." - }, - "id": "", - "name": "task", - "type": "toolCall" - } - ], - "role": "assistant" - }, - { - "content": [ - { - "text": "[flue] Subagent \"e2e-flue-skill\" is not declared. Available: (none).", - "type": "text" - } - ], - "isError": true, - "role": "toolResult", - "toolCallId": "", - "toolName": "task" - } - ], - "output": { - "content": [ - { - "arguments": { - "command": "find . -maxdepth 4 -name AGENTS.md -print", - "timeout": 100000 - }, - "id": "", - "name": "bash", - "type": "toolCall" - } - ], - "role": "assistant" - }, - "metadata": { - "flue.api": "openai-responses", - "flue.input_message_offset": 1, - "flue.input_mode": "delta", - "flue.model": "gpt-5.4-nano", - "flue.provider": "openai", - "flue.session": "task:skill:", - "flue.stop_reason": "toolUse", - "flue.turn_purpose": "agent", - "model": "gpt-5.4-nano", - "provider": "openai" - }, - "metrics": { - "completion_tokens": 94, - "duration_ms": 0, - "estimated_cost": 0.00031790000000000003, - "prompt_cache_creation_tokens": 0, - "prompt_cached_tokens": 0, - "prompt_tokens": 1002, - "tokens": 1096 - } - }, - { - "name": "tool:bash", - "type": "tool", - "children": [], - "input": { - "command": "find . -maxdepth 4 -name AGENTS.md -print", - "timeout": 100000 - }, - "output": { - "content": [ - { - "text": "(no output)", - "type": "text" - } - ], - "details": { - "command": "find . -maxdepth 4 -name AGENTS.md -print", - "exitCode": 0 - } - }, - "metadata": { - "flue.session": "task:skill:", - "flue.tool_name": "bash", - "provider": "flue" - }, - "metrics": { - "duration_ms": 0 - } - }, - { - "name": "flue.turn", - "type": "llm", - "children": [], - "input": [ - { - "content": [ - { - "arguments": { - "command": "find . -maxdepth 4 -name AGENTS.md -print", - "timeout": 100000 - }, - "id": "", - "name": "bash", - "type": "toolCall" - } - ], - "role": "assistant" - }, - { - "content": [ - { - "text": "(no output)", - "type": "text" - } - ], - "isError": false, - "role": "toolResult", - "toolCallId": "", - "toolName": "bash" - } - ], - "output": { - "content": [ - { - "arguments": { - "command": "find . -maxdepth 4 -type f -name AGENTS.md -print -exec sed -n '1,200p' {} \\;", - "timeout": 100000 - }, - "id": "", - "name": "bash", - "type": "toolCall" - } - ], - "role": "assistant" - }, - "metadata": { - "flue.api": "openai-responses", - "flue.input_message_offset": 3, - "flue.input_mode": "delta", - "flue.model": "gpt-5.4-nano", - "flue.provider": "openai", - "flue.session": "task:skill:", - "flue.stop_reason": "toolUse", - "flue.turn_purpose": "agent", - "model": "gpt-5.4-nano", - "provider": "openai" - }, - "metrics": { - "completion_tokens": 87, - "duration_ms": 0, - "estimated_cost": 0.00034815000000000003, - "prompt_cache_creation_tokens": 0, - "prompt_cached_tokens": 0, - "prompt_tokens": 1197, - "tokens": 1284 - } - }, - { - "name": "tool:bash", - "type": "tool", - "children": [], - "input": { - "command": "find . -maxdepth 4 -type f -name AGENTS.md -print -exec sed -n '1,200p' {} \\;", - "timeout": 100000 - }, - "output": { - "content": [ - { - "text": "(no output)", - "type": "text" - } - ], - "details": { - "command": "find . -maxdepth 4 -type f -name AGENTS.md -print -exec sed -n '1,200p' {} \\;", - "exitCode": 0 - } - }, - "metadata": { - "flue.session": "task:skill:", - "flue.tool_name": "bash", - "provider": "flue" - }, - "metrics": { - "duration_ms": 0 - } - }, - { - "name": "flue.turn", - "type": "llm", - "children": [], - "input": [ - { - "content": [ - { - "arguments": { - "command": "find . -maxdepth 4 -type f -name AGENTS.md -print -exec sed -n '1,200p' {} \\;", - "timeout": 100000 - }, - "id": "", - "name": "bash", - "type": "toolCall" - } - ], - "role": "assistant" - }, - { - "content": [ - { - "text": "(no output)", - "type": "text" - } - ], - "isError": false, - "role": "toolResult", - "toolCallId": "", - "toolName": "bash" - } - ], - "output": { - "content": [ - { - "arguments": { - "command": "find . -maxdepth 6 -type f -name 'AGENTS.md' -print", - "timeout": 100000 - }, - "id": "", - "name": "bash", - "type": "toolCall" - } - ], - "role": "assistant" - }, - "metadata": { - "flue.api": "openai-responses", - "flue.input_message_offset": 5, - "flue.input_mode": "delta", - "flue.model": "gpt-5.4-nano", - "flue.provider": "openai", - "flue.session": "task:skill:", - "flue.stop_reason": "toolUse", - "flue.turn_purpose": "agent", - "model": "gpt-5.4-nano", - "provider": "openai" - }, - "metrics": { - "completion_tokens": 58, - "duration_ms": 0, - "estimated_cost": 0.0003321, - "prompt_cache_creation_tokens": 0, - "prompt_cached_tokens": 0, - "prompt_tokens": 1298, - "tokens": 1356 - } - }, - { - "name": "tool:bash", - "type": "tool", - "children": [], - "input": { - "command": "find . -maxdepth 6 -type f -name 'AGENTS.md' -print", - "timeout": 100000 - }, - "output": { - "content": [ - { - "text": "(no output)", - "type": "text" - } - ], - "details": { - "command": "find . -maxdepth 6 -type f -name 'AGENTS.md' -print", - "exitCode": 0 - } - }, - "metadata": { - "flue.session": "task:skill:", - "flue.tool_name": "bash", - "provider": "flue" - }, - "metrics": { - "duration_ms": 0 - } - }, - { - "name": "flue.turn", - "type": "llm", - "children": [], - "input": [ - { - "content": [ - { - "arguments": { - "command": "find . -maxdepth 6 -type f -name 'AGENTS.md' -print", - "timeout": 100000 - }, - "id": "", - "name": "bash", - "type": "toolCall" - } - ], - "role": "assistant" - }, - { - "content": [ - { - "text": "(no output)", - "type": "text" - } - ], - "isError": false, - "role": "toolResult", - "toolCallId": "", - "toolName": "bash" - } - ], - "output": { - "content": [ - { - "arguments": { - "command": "find . -maxdepth 5 -type f -name '*flue*skill*' -o -name '*skill*' | head", - "timeout": 100000 - }, - "id": "", - "name": "bash", - "type": "toolCall" - } - ], - "role": "assistant" - }, - "metadata": { - "flue.api": "openai-responses", - "flue.input_message_offset": 7, - "flue.input_mode": "delta", - "flue.model": "gpt-5.4-nano", - "flue.provider": "openai", - "flue.session": "task:skill:", - "flue.stop_reason": "toolUse", - "flue.turn_purpose": "agent", - "model": "gpt-5.4-nano", - "provider": "openai" - }, - "metrics": { - "completion_tokens": 68, - "duration_ms": 0, - "estimated_cost": 0.00035900000000000005, - "prompt_cache_creation_tokens": 0, - "prompt_cached_tokens": 0, - "prompt_tokens": 1370, - "tokens": 1438 - } - }, - { - "name": "tool:bash", - "type": "tool", - "children": [], - "input": { - "command": "find . -maxdepth 5 -type f -name '*flue*skill*' -o -name '*skill*' | head", - "timeout": 100000 - }, - "output": { - "content": [ - { - "text": "./.agents/skills\n./.agents/skills/e2e-flue-skill", - "type": "text" - } - ], - "details": { - "command": "find . -maxdepth 5 -type f -name '*flue*skill*' -o -name '*skill*' | head", - "exitCode": 0 - } - }, - "metadata": { - "flue.session": "task:skill:", - "flue.tool_name": "bash", - "provider": "flue" - }, - "metrics": { - "duration_ms": 0 - } - }, - { - "name": "flue.turn", - "type": "llm", - "children": [], - "input": [ - { - "content": [ - { - "arguments": { - "command": "find . -maxdepth 5 -type f -name '*flue*skill*' -o -name '*skill*' | head", - "timeout": 100000 - }, - "id": "", - "name": "bash", - "type": "toolCall" - } - ], - "role": "assistant" - }, - { - "content": [ - { - "text": "./.agents/skills\n./.agents/skills/e2e-flue-skill", - "type": "text" - } - ], - "isError": false, - "role": "toolResult", - "toolCallId": "", - "toolName": "bash" - } - ], - "output": { - "content": [ - { - "arguments": { - "command": "find ./.agents/skills/e2e-flue-skill -maxdepth 2 -type f -print", - "timeout": 100000 - }, - "id": "", - "name": "bash", - "type": "toolCall" - } - ], - "role": "assistant" - }, - "metadata": { - "flue.api": "openai-responses", - "flue.input_message_offset": 9, - "flue.input_mode": "delta", - "flue.model": "gpt-5.4-nano", - "flue.provider": "openai", - "flue.session": "task:skill:", - "flue.stop_reason": "toolUse", - "flue.turn_purpose": "agent", - "model": "gpt-5.4-nano", - "provider": "openai" - }, - "metrics": { - "completion_tokens": 109, - "duration_ms": 0, - "estimated_cost": 0.00043885000000000007, - "prompt_cache_creation_tokens": 0, - "prompt_cached_tokens": 0, - "prompt_tokens": 1513, - "tokens": 1622 - } - }, - { - "name": "tool:bash", - "type": "tool", - "children": [], - "input": { - "command": "find ./.agents/skills/e2e-flue-skill -maxdepth 2 -type f -print", - "timeout": 100000 - }, - "output": { - "content": [ - { - "text": "./.agents/skills/e2e-flue-skill/SKILL.md", - "type": "text" - } - ], - "details": { - "command": "find ./.agents/skills/e2e-flue-skill -maxdepth 2 -type f -print", - "exitCode": 0 - } - }, - "metadata": { - "flue.session": "task:skill:", - "flue.tool_name": "bash", - "provider": "flue" - }, - "metrics": { - "duration_ms": 0 - } - }, - { - "name": "flue.turn", - "type": "llm", - "children": [], - "input": [ - { - "content": [ - { - "arguments": { - "command": "find ./.agents/skills/e2e-flue-skill -maxdepth 2 -type f -print", - "timeout": 100000 - }, - "id": "", - "name": "bash", - "type": "toolCall" - } - ], - "role": "assistant" - }, - { - "content": [ - { - "text": "./.agents/skills/e2e-flue-skill/SKILL.md", - "type": "text" - } - ], - "isError": false, - "role": "toolResult", - "toolCallId": "", - "toolName": "bash" - } - ], - "output": { - "content": [ - { - "arguments": { - "limit": 200, - "offset": 1, - "path": "./.agents/skills/e2e-flue-skill/SKILL.md" - }, - "id": "", - "name": "read", - "type": "toolCall" - } - ], - "role": "assistant" - }, - "metadata": { - "flue.api": "openai-responses", - "flue.input_message_offset": 11, - "flue.input_mode": "delta", - "flue.model": "gpt-5.4-nano", - "flue.provider": "openai", - "flue.session": "task:skill:", - "flue.stop_reason": "toolUse", - "flue.turn_purpose": "agent", - "model": "gpt-5.4-nano", - "provider": "openai" - }, - "metrics": { - "completion_tokens": 64, - "duration_ms": 0, - "estimated_cost": 0.00015592, - "prompt_cache_creation_tokens": 0, - "prompt_cached_tokens": 1536, - "prompt_tokens": 1762, - "tokens": 1826 - } - }, - { - "name": "tool:read", - "type": "tool", - "children": [], - "input": { - "limit": 200, - "offset": 1, - "path": "./.agents/skills/e2e-flue-skill/SKILL.md" - }, - "output": { - "content": [ - { - "text": "---\nname: e2e-flue-skill\ndescription: Deterministic skill used by the Braintrust Flue instrumentation e2e test.\n---\n\nReturn the marker from the provided args exactly once. Output no other text.\n", - "type": "text" - } - ], - "details": { - "lines": 7, - "path": "./.agents/skills/e2e-flue-skill/SKILL.md" - } - }, - "metadata": { - "flue.session": "task:skill:", - "flue.tool_name": "read", - "provider": "flue" - }, - "metrics": { - "duration_ms": 0 - } - }, - { - "name": "flue.turn", - "type": "llm", - "children": [], - "input": [ - { - "content": [ - { - "arguments": { - "limit": 200, - "offset": 1, - "path": "./.agents/skills/e2e-flue-skill/SKILL.md" - }, - "id": "", - "name": "read", - "type": "toolCall" - } - ], - "role": "assistant" - }, - { - "content": [ - { - "text": "---\nname: e2e-flue-skill\ndescription: Deterministic skill used by the Braintrust Flue instrumentation e2e test.\n---\n\nReturn the marker from the provided args exactly once. Output no other text.\n", - "type": "text" - } - ], - "isError": false, - "role": "toolResult", - "toolCallId": "", - "toolName": "read" - } - ], - "output": { - "content": [ - { - "arguments": { - "command": "cat ./package.json", - "timeout": 100000 - }, - "id": "", - "name": "bash", - "type": "toolCall" - } - ], - "role": "assistant" - }, - "metadata": { - "flue.api": "openai-responses", - "flue.input_message_offset": 13, - "flue.input_mode": "delta", - "flue.model": "gpt-5.4-nano", - "flue.provider": "openai", - "flue.session": "task:skill:", - "flue.stop_reason": "toolUse", - "flue.turn_purpose": "agent", - "model": "gpt-5.4-nano", - "provider": "openai" - }, - "metrics": { - "completion_tokens": 90, - "duration_ms": 0, - "estimated_cost": 0.00021242000000000003, - "prompt_cache_creation_tokens": 0, - "prompt_cached_tokens": 1536, - "prompt_tokens": 1882, - "tokens": 1972 - } - }, - { - "name": "tool:bash", - "type": "tool", - "children": [], - "input": { - "command": "cat ./package.json", - "timeout": 100000 - }, - "output": { - "content": [ - { - "text": "{\n \"name\": \"flue-instrumentation-scenario\",\n \"private\": true,\n \"type\": \"module\",\n \"braintrustScenario\": {\n \"bump\": {\n \"dependencies\": {\n \"flue-cli-v0-8-latest\": {\n \"package\": \"@flue/cli\",\n \"range\": \">=0.8.0 <0.9.0\"\n },\n \"flue-cli-v1-latest\": {\n \"package\": \"@flue/cli\",\n \"range\": \">=1.0.0-beta.3 <2.0.0-0\",\n \"allowPrerelease\": true\n },\n \"flue-runtime-v0-8-latest\": {\n \"package\": \"@flue/runtime\",\n \"range\": \">=0.8.0 <0.9.0\"\n },\n \"flue-runtime-v1-latest\": {\n \"package\": \"@flue/runtime\",\n \"range\": \">=1.0.0-beta.3 <2.0.0-0\",\n \"allowPrerelease\": true\n },\n \"flue-runtime-v2-latest\": {\n \"package\": \"@flue/runtime\",\n \"range\": \">=2.0.0 <3.0.0\"\n }\n }\n }\n },\n \"dependencies\": {\n \"@flue/cli\": \"0.8.0\",\n \"@flue/runtime\": \"0.8.0\",\n \"flue-cli-v0-8-latest\": \"npm:@flue/cli@0.8.1\",\n \"flue-cli-v1\": \"npm:@flue/cli@1.0.0-beta.3\",\n \"flue-cli-v1-latest\": \"npm:@flue/cli@1.0.0-beta.9\",\n \"flue-runtime-v0-8-latest\": \"npm:@flue/runtime@0.8.1\",\n \"flue-runtime-v1\": \"npm:@flue/runtime@1.0.0-beta.3\",\n \"flue-runtime-v1-latest\": \"npm:@flue/runtime@1.0.0-beta.9\",\n \"flue-runtime-v2\": \"npm:@flue/runtime@2.0.0\",\n \"flue-runtime-v2-latest\": \"npm:@flue/runtime@2.0.1\",\n \"pi-ai-v2\": \"npm:@earendil-works/pi-ai@0.83.0\",\n \"valibot\": \"1.4.2\"\n },\n \"pnpm\": {\n \"overrides\": {\n \"@flue/runtime@0.8.0>@earendil-works/pi-agent-core\": \"0.75.4\",\n \"@flue/runtime@0.8.0>@earendil-works/pi-ai\": \"0.75.4\",\n \"@flue/runtime@0.8.1>@earendil-works/pi-agent-core\": \"0.75.4\",\n \"@flue/runtime@0.8.1>@earendil-works/pi-ai\": \"0.75.4\",\n \"@hono/node-server@>=2.0.0 <2.0.10\": \"^2.0.10\",\n \"fast-uri@<3.1.5\": \"3.1.8\",\n \"brace-expansion@>=3.0.0 <5.0.9\": \"5.0.12\",\n \"postcss@<8.5.18\": \"^8.5.18\",\n \"valibot@<1.4.2\": \"^1.4.2\",\n \"ip-address@<10.2.2\": \"10.7.1\",\n \"undici@<7.29.0\": \"7.29.1\",\n \"hono@<4.12.34\": \"4.13.7\",\n \"@hono/node-server@<1.19.15\": \"^1.19.15\",\n \"nanoid@<3.3.18\": \"^3.3.18\",\n \"js-yaml@3\": \"^3.15.1\",\n \"js-yaml@4\": \"^4.3.1\",\n \"sharp@<0.35.0\": \"0.35.5\",\n \"ip-address@>=10.0.0 <10.7.1\": \"10.7.1\",\n \"fast-uri@>=3.0.0 <3.1.8\": \"3.1.8\",\n \"undici@>=7.0.0 <7.29.1\": \"7.29.1\",\n \"js-yaml@>=5.0.0 <5.4.1\": \"5.4.1\",\n \"brace-expansion@>=5.0.0 <5.0.12\": \"5.0.12\",\n \"hono@>=4.0.0 <4.13.7\": \"4.13.7\",\n \"proxy-addr@>=2.0.0 <2.0.8\": \"2.0.8\",\n \"source-map-js@>=1.0.0 <1.2.2\": \"1.2.2\",\n \"smol-toml@>=1.0.0 <1.9.0\": \"1.9.0\",\n \"sharp@>=0.35.0 <0.35.5\": \"0.35.5\",\n \"@modelcontextprotocol/sdk@>=1.0.0 <1.31.0\": \"1.31.0\",\n \"@modelcontextprotocol/client@>=2.0.0 <2.2.0\": \"2.2.0\"\n }\n }\n}", - "type": "text" - } - ], - "details": { - "command": "cat ./package.json", - "exitCode": 0 - } - }, - "metadata": { - "flue.session": "task:skill:", - "flue.tool_name": "bash", - "provider": "flue" - }, - "metrics": { - "duration_ms": 0 - } - }, - { - "name": "flue.turn", - "type": "llm", - "children": [], - "input": [ - { - "content": [ - { - "arguments": { - "command": "cat ./package.json", - "timeout": 100000 - }, - "id": "", - "name": "bash", - "type": "toolCall" - } - ], - "role": "assistant" - }, - { - "content": [ - { - "text": "{\n \"name\": \"flue-instrumentation-scenario\",\n \"private\": true,\n \"type\": \"module\",\n \"braintrustScenario\": {\n \"bump\": {\n \"dependencies\": {\n \"flue-cli-v0-8-latest\": {\n \"package\": \"@flue/cli\",\n \"range\": \">=0.8.0 <0.9.0\"\n },\n \"flue-cli-v1-latest\": {\n \"package\": \"@flue/cli\",\n \"range\": \">=1.0.0-beta.3 <2.0.0-0\",\n \"allowPrerelease\": true\n },\n \"flue-runtime-v0-8-latest\": {\n \"package\": \"@flue/runtime\",\n \"range\": \">=0.8.0 <0.9.0\"\n },\n \"flue-runtime-v1-latest\": {\n \"package\": \"@flue/runtime\",\n \"range\": \">=1.0.0-beta.3 <2.0.0-0\",\n \"allowPrerelease\": true\n },\n \"flue-runtime-v2-latest\": {\n \"package\": \"@flue/runtime\",\n \"range\": \">=2.0.0 <3.0.0\"\n }\n }\n }\n },\n \"dependencies\": {\n \"@flue/cli\": \"0.8.0\",\n \"@flue/runtime\": \"0.8.0\",\n \"flue-cli-v0-8-latest\": \"npm:@flue/cli@0.8.1\",\n \"flue-cli-v1\": \"npm:@flue/cli@1.0.0-beta.3\",\n \"flue-cli-v1-latest\": \"npm:@flue/cli@1.0.0-beta.9\",\n \"flue-runtime-v0-8-latest\": \"npm:@flue/runtime@0.8.1\",\n \"flue-runtime-v1\": \"npm:@flue/runtime@1.0.0-beta.3\",\n \"flue-runtime-v1-latest\": \"npm:@flue/runtime@1.0.0-beta.9\",\n \"flue-runtime-v2\": \"npm:@flue/runtime@2.0.0\",\n \"flue-runtime-v2-latest\": \"npm:@flue/runtime@2.0.1\",\n \"pi-ai-v2\": \"npm:@earendil-works/pi-ai@0.83.0\",\n \"valibot\": \"1.4.2\"\n },\n \"pnpm\": {\n \"overrides\": {\n \"@flue/runtime@0.8.0>@earendil-works/pi-agent-core\": \"0.75.4\",\n \"@flue/runtime@0.8.0>@earendil-works/pi-ai\": \"0.75.4\",\n \"@flue/runtime@0.8.1>@earendil-works/pi-agent-core\": \"0.75.4\",\n \"@flue/runtime@0.8.1>@earendil-works/pi-ai\": \"0.75.4\",\n \"@hono/node-server@>=2.0.0 <2.0.10\": \"^2.0.10\",\n \"fast-uri@<3.1.5\": \"3.1.8\",\n \"brace-expansion@>=3.0.0 <5.0.9\": \"5.0.12\",\n \"postcss@<8.5.18\": \"^8.5.18\",\n \"valibot@<1.4.2\": \"^1.4.2\",\n \"ip-address@<10.2.2\": \"10.7.1\",\n \"undici@<7.29.0\": \"7.29.1\",\n \"hono@<4.12.34\": \"4.13.7\",\n \"@hono/node-server@<1.19.15\": \"^1.19.15\",\n \"nanoid@<3.3.18\": \"^3.3.18\",\n \"js-yaml@3\": \"^3.15.1\",\n \"js-yaml@4\": \"^4.3.1\",\n \"sharp@<0.35.0\": \"0.35.5\",\n \"ip-address@>=10.0.0 <10.7.1\": \"10.7.1\",\n \"fast-uri@>=3.0.0 <3.1.8\": \"3.1.8\",\n \"undici@>=7.0.0 <7.29.1\": \"7.29.1\",\n \"js-yaml@>=5.0.0 <5.4.1\": \"5.4.1\",\n \"brace-expansion@>=5.0.0 <5.0.12\": \"5.0.12\",\n \"hono@>=4.0.0 <4.13.7\": \"4.13.7\",\n \"proxy-addr@>=2.0.0 <2.0.8\": \"2.0.8\",\n \"source-map-js@>=1.0.0 <1.2.2\": \"1.2.2\",\n \"smol-toml@>=1.0.0 <1.9.0\": \"1.9.0\",\n \"sharp@>=0.35.0 <0.35.5\": \"0.35.5\",\n \"@modelcontextprotocol/sdk@>=1.0.0 <1.31.0\": \"1.31.0\",\n \"@modelcontextprotocol/client@>=2.0.0 <2.2.0\": \"2.2.0\"\n }\n }\n}", - "type": "text" - } - ], - "isError": false, - "role": "toolResult", - "toolCallId": "", - "toolName": "bash" - } - ], - "output": { - "content": [ - { - "arguments": { - "command": "printf SKILL_DONE", - "timeout": 100000 - }, - "id": "", - "name": "bash", - "type": "toolCall" - } - ], - "role": "assistant" - }, - "metadata": { - "flue.api": "openai-responses", - "flue.input_message_offset": 15, - "flue.input_mode": "delta", - "flue.model": "gpt-5.4-nano", - "flue.provider": "openai", - "flue.session": "task:skill:", - "flue.stop_reason": "toolUse", - "flue.turn_purpose": "agent", - "model": "gpt-5.4-nano", - "provider": "openai" - }, - "metrics": { - "completion_tokens": 59, - "duration_ms": 0, - "estimated_cost": 0.0004814700000000001, - "prompt_cache_creation_tokens": 0, - "prompt_cached_tokens": 1536, - "prompt_tokens": 3421, - "tokens": 3480 - } - }, - { - "name": "tool:bash", - "type": "tool", - "children": [], - "input": { - "command": "printf SKILL_DONE", - "timeout": 100000 - }, - "output": { - "content": [ - { - "text": "SKILL_DONE", - "type": "text" - } - ], - "details": { - "command": "printf SKILL_DONE", - "exitCode": 0 - } - }, - "metadata": { - "flue.session": "task:skill:", - "flue.tool_name": "bash", - "provider": "flue" - }, - "metrics": { - "duration_ms": 0 - } - }, - { - "name": "flue.turn", - "type": "llm", - "children": [], - "input": [ - { - "content": [ - { - "arguments": { - "command": "printf SKILL_DONE", - "timeout": 100000 - }, - "id": "", - "name": "bash", - "type": "toolCall" - } - ], - "role": "assistant" - }, - { - "content": [ - { - "text": "SKILL_DONE", - "type": "text" - } - ], - "isError": false, - "role": "toolResult", - "toolCallId": "", - "toolName": "bash" - } - ], - "output": { - "content": [ - { - "text": "SKILL_DONE", - "textSignature": "{\"v\":1,\"id\":\"msg_054a16a2f2ea3217016a705b70a41481a38ece40ecddf69512\",\"phase\":\"final_answer\"}", - "type": "text" - } - ], - "role": "assistant" - }, - "metadata": { - "flue.api": "openai-responses", - "flue.input_message_offset": 17, - "flue.input_mode": "delta", - "flue.model": "gpt-5.4-nano", - "flue.provider": "openai", - "flue.session": "task:skill:", - "flue.stop_reason": "stop", - "flue.turn_purpose": "agent", - "model": "gpt-5.4-nano", - "provider": "openai" - }, - "metrics": { - "completion_tokens": 7, - "duration_ms": 0, - "estimated_cost": 0.00031035000000000004, - "prompt_cache_creation_tokens": 0, - "prompt_cached_tokens": 2560, - "prompt_tokens": 3812, - "tokens": 3819 - } - } - ], - "input": "Run the skill named \"e2e-flue-skill\" with arguments {\"marker\":\"SKILL_DONE\"}. Output only the marker and no extra text when finished.", - "output": "SKILL_DONE", - "metadata": { - "flue.session": "skill", - "provider": "flue" - }, - "metrics": { - "duration_ms": 0 - } - } - ], - "input": [ - { - "content": [ - { - "text": "Run the skill named \"e2e-flue-skill\".\n\nArguments:\n{\n \"marker\": \"SKILL_DONE\"\n}", - "type": "text" - } - ], - "role": "user" - } - ], - "output": { - "content": [ - { - "arguments": { - "cwd": ".", - "description": "Run the deterministic Flue instrumentation e2e test skill e2e-flue-skill.", - "prompt": "Run the skill named \"e2e-flue-skill\" with arguments {\"marker\":\"SKILL_DONE\"}. Output only the marker and no extra text when finished." - }, - "id": "", - "name": "task", - "type": "toolCall" - } - ], - "role": "assistant" - }, - "metadata": { - "flue.api": "openai-responses", - "flue.model": "gpt-5.4-nano", - "flue.provider": "openai", - "flue.session": "skill", - "flue.stop_reason": "toolUse", - "flue.turn_purpose": "agent", - "model": "gpt-5.4-nano", - "provider": "openai" - }, - "metrics": { - "completion_tokens": 109, - "duration_ms": 0, - "estimated_cost": 0.00030565000000000003, - "prompt_cache_creation_tokens": 0, - "prompt_cached_tokens": 0, - "prompt_tokens": 847, - "tokens": 956 - } - }, - { - "name": "tool:task", - "type": "tool", - "children": [], - "input": { - "cwd": ".", - "description": "Run the deterministic Flue instrumentation e2e test skill e2e-flue-skill.", - "prompt": "Run the skill named \"e2e-flue-skill\" with arguments {\"marker\":\"SKILL_DONE\"}. Output only the marker and no extra text when finished." - }, - "output": { - "content": [ - { - "text": "SKILL_DONE", - "type": "text" - } - ], - "details": { - "cwd": ".", - "messageId": "", - "session": "task:skill:", - "taskId": "" - } - }, - "metadata": { - "flue.session": "skill", - "flue.tool_name": "task", - "provider": "flue" - }, - "metrics": { - "duration_ms": 0 - } - }, - { - "name": "flue.turn", - "type": "llm", - "children": [], - "input": [ - { - "content": [ - { - "text": "Run the skill named \"e2e-flue-skill\".\n\nArguments:\n{\n \"marker\": \"SKILL_DONE\"\n}", - "type": "text" - } - ], - "role": "user" - }, - { - "content": [ - { - "arguments": { - "cwd": ".", - "description": "Run the deterministic Flue instrumentation e2e test skill e2e-flue-skill.", - "prompt": "Run the skill named \"e2e-flue-skill\" with arguments {\"marker\":\"SKILL_DONE\"}. Output only the marker and no extra text when finished." - }, - "id": "", - "name": "task", - "type": "toolCall" - } - ], - "role": "assistant" - }, - { - "content": [ - { - "text": "SKILL_DONE", - "type": "text" - } - ], - "isError": false, - "role": "toolResult", - "toolCallId": "", - "toolName": "task" - } - ], - "output": { - "content": [ - { - "text": "SKILL_DONE", - "textSignature": "{\"v\":1,\"id\":\"msg_0f099b3fce0721fd016a705b71558881a38c53a767e09d9e31\",\"phase\":\"final_answer\"}", - "type": "text" - } - ], - "role": "assistant" - }, - "metadata": { - "flue.api": "openai-responses", - "flue.model": "gpt-5.4-nano", - "flue.provider": "openai", - "flue.session": "skill", - "flue.stop_reason": "stop", - "flue.turn_purpose": "agent", - "model": "gpt-5.4-nano", - "provider": "openai" - }, - "metrics": { - "completion_tokens": 7, - "duration_ms": 0, - "estimated_cost": 0.00020255000000000002, - "prompt_cache_creation_tokens": 0, - "prompt_cached_tokens": 0, - "prompt_tokens": 969, - "tokens": 976 - } - } - ], - "input": [ - { - "content": [ - { - "text": "Run the skill named \"e2e-flue-skill\".\n\nArguments:\n{\n \"marker\": \"SKILL_DONE\"\n}", - "type": "text" - } - ], - "role": "user" - } - ], - "output": "SKILL_DONE", - "metadata": { - "flue.operation": "skill", - "flue.session": "skill", - "flue.workflow_name": "instrumentation", - "provider": "flue", - "scenario": "flue-instrumentation" - }, - "metrics": { - "duration_ms": 0 - } - }, - { - "name": "flue.task", - "type": "task", - "children": [ - { - "name": "flue.turn", - "type": "llm", - "children": [], - "input": [ - { - "content": [ - { - "text": "Reply with exactly TASK_DONE and no other text.", - "type": "text" - } - ], - "role": "user" - } - ], - "output": { - "content": [ - { - "text": "TASK_DONE", - "textSignature": "{\"v\":1,\"id\":\"msg_0f3adfdb1510c165016a705b72843c81a0b30d242da683a882\"}", - "type": "text" - } - ], - "role": "assistant" - }, - "metadata": { - "flue.api": "openai-responses", - "flue.input_mode": "full", - "flue.model": "gpt-4o-mini", - "flue.provider": "openai", - "flue.session": "task:task:", - "flue.stop_reason": "stop", - "flue.turn_purpose": "agent", - "model": "gpt-4o-mini", - "provider": "openai" - }, - "metrics": { - "completion_tokens": 4, - "duration_ms": 0, - "estimated_cost": 0.0001263, - "prompt_cache_creation_tokens": 0, - "prompt_cached_tokens": 0, - "prompt_tokens": 826, - "tokens": 830 - } - } - ], - "input": "Reply with exactly TASK_DONE and no other text.", - "output": "TASK_DONE", - "metadata": { - "flue.session": "task", - "provider": "flue" - }, - "metrics": { - "duration_ms": 0 - } - }, - { - "name": "flue.compact", - "type": "task", - "children": [ - { - "name": "compaction:manual", - "type": "task", - "children": [ - { - "name": "flue.turn", - "type": "llm", - "children": [], - "input": [ - { - "content": [ - { - "text": "\n[User]: Complete this instrumented research flow. Call exactly one tool per turn and wait for each tool result before choosing the next tool. Step 1: call lookup with query \"flue instrumentation\". Step 2: use the lookup result id as lookupId and call web_search with query \"Braintrust Flue reasoning stream instrumentation\". Step 3: use the first web_search result url and call summarize_source. After summarize_source returns, reply with exactly PROMPT_DONE and no other text.\n\n[Assistant tool calls]: lookup(query=\"flue instrumentation\")\n\n[Tool result]: {\"id\":\"flue-session-2026\",\"query\":\"flue instrumentation\",\"topic\":\"session instrumentation\"}\n\n[Assistant tool calls]: web_search(lookupId=\"flue-session-2026\", query=\"Braintrust Flue reasoning stream instrumentation\")\n\n[Tool result]: {\"lookupId\":\"flue-session-2026\",\"query\":\"Braintrust Flue reasoning stream instrumentation\",\"results\":[{\"title\":\"Flue reasoning stream instrumentation\",\"url\":\"https://example.test/flue/reasoning-streams\"}]}\n\n[Assistant tool calls]: summarize_source(url=\"https://example.test/flue/reasoning-streams\")\n\n[Tool result]: {\"summary\":\"Flue emits reasoning, tool execution, and LLM turn events separately.\",\"url\":\"https://example.test/flue/reasoning-streams\"}\n\n\nThis is the PREFIX of a turn that was too large to keep. The SUFFIX (recent work) is retained.\n\nSummarize the prefix to provide context for the retained suffix:\n\n## Original Request\n[What did the user ask for in this turn?]\n\n## Early Progress\n- [Key decisions and work done in the prefix]\n\n## Context for Suffix\n- [Information needed to understand the retained recent work]\n\nBe concise. Focus on what's needed to understand the kept suffix.", - "type": "text" - } - ], - "role": "user" - } - ], - "output": { - "content": [ - { - "text": "## Original Request\nThe user requested a step-by-step execution of an instrumented research flow involving tool calls to gather information on \"flue instrumentation\" and \"", - "textSignature": "{\"v\":1,\"id\":\"msg_04d28ba03840bb65016a705b7358a081a38e69464057840036\"}", - "type": "text" - } - ], - "role": "assistant" - }, - "metadata": { - "flue.api": "openai-responses", - "flue.model": "gpt-4o-mini", - "flue.provider": "openai", - "flue.session": "main", - "flue.stop_reason": "stop", - "flue.turn_purpose": "compaction_prefix", - "model": "gpt-4o-mini", - "provider": "openai" - }, - "metrics": { - "completion_tokens": 0, - "duration_ms": 0, - "estimated_cost": 0, - "prompt_cache_creation_tokens": 0, - "prompt_cached_tokens": 0, - "prompt_tokens": 0, - "tokens": 0 - } - } - ], - "input": { - "estimatedTokens": 1229, - "reason": "manual" - }, - "output": { - "messagesAfter": 2, - "messagesBefore": 8 - }, - "metadata": { - "flue.compaction_reason": "manual", - "flue.session": "main", - "provider": "flue" - }, - "metrics": { - "duration_ms": 0, - "messages_after": 2, - "messages_before": 8 - } - } - ], - "input": { - "estimatedTokens": 1229, - "reason": "manual" - }, - "output": { - "completed": true - }, - "metadata": { - "flue.operation": "compact", - "flue.session": "main", - "flue.workflow_name": "instrumentation", - "provider": "flue", - "scenario": "flue-instrumentation" - }, - "metrics": { - "duration_ms": 0 - } - } - ], - "input": { - "metadata": { - "scenario": "flue-instrumentation", - "testRunId": "" - }, - "scenario": "flue-instrumentation" - }, - "output": { - "scenario": "flue-instrumentation", - "status": "done" - }, - "metadata": { - "flue.workflow_name": "instrumentation", - "provider": "flue", - "scenario": "flue-instrumentation" - }, - "metrics": { - "duration_ms": 0 - } - }, - { - "name": "flue.prompt", - "type": "task", - "children": [ - { - "name": "flue.turn", - "type": "llm", - "children": [], - "input": [ - { - "content": [ - { - "text": "Complete this instrumented research flow. Call exactly one tool per turn and wait for each tool result before choosing the next tool. Step 1: call lookup with query \"flue instrumentation\". Step 2: use the lookup result id as lookupId and call web_search with query \"Braintrust Flue reasoning stream instrumentation\". Step 3: use the first web_search result url and call summarize_source. After summarize_source returns, reply with exactly PROMPT_DONE and no other text.", - "type": "text" - } - ], - "role": "user" - } - ], - "output": { - "content": [ - { - "arguments": { - "query": "flue instrumentation" - }, - "id": "", - "name": "lookup", - "type": "toolCall" - } - ], - "role": "assistant" - }, - "metadata": { - "flue.api": "openai-responses", - "flue.input_mode": "full", - "flue.model": "gpt-5.4-nano", - "flue.provider": "openai", - "flue.session": "main", - "flue.stop_reason": "toolUse", - "flue.turn_purpose": "agent", - "model": "gpt-5.4-nano", - "provider": "openai" - }, - "metrics": { - "completion_tokens": 19, - "duration_ms": 0, - "estimated_cost": 0.00022475000000000001, - "prompt_cache_creation_tokens": 0, - "prompt_cached_tokens": 0, - "prompt_tokens": 1005, - "tokens": 1024 - } - }, - { - "name": "tool:lookup", - "type": "tool", - "children": [], - "input": { - "query": "flue instrumentation" - }, - "output": { - "content": [ - { - "text": "{\"id\":\"flue-session-2026\",\"query\":\"flue instrumentation\",\"topic\":\"session instrumentation\"}", - "type": "text" - } - ], - "details": { - "customTool": "lookup" - } - }, - "metadata": { - "flue.session": "main", - "flue.tool_name": "lookup", - "provider": "flue" - }, - "metrics": { - "duration_ms": 0 - } - }, - { - "name": "flue.turn", - "type": "llm", - "children": [], - "input": [ - { - "content": [ - { - "arguments": { - "query": "flue instrumentation" - }, - "id": "", - "name": "lookup", - "type": "toolCall" - } - ], - "role": "assistant" - }, - { - "content": [ - { - "text": "{\"id\":\"flue-session-2026\",\"query\":\"flue instrumentation\",\"topic\":\"session instrumentation\"}", - "type": "text" - } - ], - "isError": false, - "role": "toolResult", - "toolCallId": "", - "toolName": "lookup" - } - ], - "output": { - "content": [ - { - "arguments": { - "lookupId": "flue-session-2026", - "query": "Braintrust Flue reasoning stream instrumentation" - }, - "id": "", - "name": "web_search", - "type": "toolCall" - } - ], - "role": "assistant" - }, - "metadata": { - "flue.api": "openai-responses", - "flue.input_message_offset": 1, - "flue.input_mode": "delta", - "flue.model": "gpt-5.4-nano", - "flue.provider": "openai", - "flue.session": "main", - "flue.stop_reason": "toolUse", - "flue.turn_purpose": "agent", - "model": "gpt-5.4-nano", - "provider": "openai" - }, - "metrics": { - "completion_tokens": 34, - "duration_ms": 0, - "estimated_cost": 0.00025350000000000004, - "prompt_cache_creation_tokens": 0, - "prompt_cached_tokens": 0, - "prompt_tokens": 1055, - "tokens": 1089 - } - }, - { - "name": "tool:web_search", - "type": "tool", - "children": [], - "input": { - "lookupId": "flue-session-2026", - "query": "Braintrust Flue reasoning stream instrumentation" - }, - "output": { - "content": [ - { - "text": "{\"lookupId\":\"flue-session-2026\",\"query\":\"Braintrust Flue reasoning stream instrumentation\",\"results\":[{\"title\":\"Flue reasoning stream instrumentation\",\"url\":\"https://example.test/flue/reasoning-streams\"}]}", - "type": "text" - } - ], - "details": { - "customTool": "web_search" - } - }, - "metadata": { - "flue.session": "main", - "flue.tool_name": "web_search", - "provider": "flue" - }, - "metrics": { - "duration_ms": 0 - } - }, - { - "name": "flue.turn", - "type": "llm", - "children": [], - "input": [ - { - "content": [ - { - "arguments": { - "lookupId": "flue-session-2026", - "query": "Braintrust Flue reasoning stream instrumentation" - }, - "id": "", - "name": "web_search", - "type": "toolCall" - } - ], - "role": "assistant" - }, - { - "content": [ - { - "text": "{\"lookupId\":\"flue-session-2026\",\"query\":\"Braintrust Flue reasoning stream instrumentation\",\"results\":[{\"title\":\"Flue reasoning stream instrumentation\",\"url\":\"https://example.test/flue/reasoning-streams\"}]}", - "type": "text" - } - ], - "isError": false, - "role": "toolResult", - "toolCallId": "", - "toolName": "web_search" - } - ], - "output": { - "content": [ - { - "arguments": { - "url": "https://example.test/flue/reasoning-streams" - }, - "id": "", - "name": "summarize_source", - "type": "toolCall" - } - ], - "role": "assistant" - }, - "metadata": { - "flue.api": "openai-responses", - "flue.input_message_offset": 3, - "flue.input_mode": "delta", - "flue.model": "gpt-5.4-nano", - "flue.provider": "openai", - "flue.session": "main", - "flue.stop_reason": "toolUse", - "flue.turn_purpose": "agent", - "model": "gpt-5.4-nano", - "provider": "openai" - }, - "metrics": { - "completion_tokens": 30, - "duration_ms": 0, - "estimated_cost": 0.00026690000000000004, - "prompt_cache_creation_tokens": 0, - "prompt_cached_tokens": 0, - "prompt_tokens": 1147, - "tokens": 1177 - } - }, - { - "name": "tool:summarize_source", - "type": "tool", - "children": [], - "input": { - "url": "https://example.test/flue/reasoning-streams" - }, - "output": { - "content": [ - { - "text": "{\"summary\":\"Flue emits reasoning, tool execution, and LLM turn events separately.\",\"url\":\"https://example.test/flue/reasoning-streams\"}", - "type": "text" - } - ], - "details": { - "customTool": "summarize_source" - } - }, - "metadata": { - "flue.session": "main", - "flue.tool_name": "summarize_source", - "provider": "flue" - }, - "metrics": { - "duration_ms": 0 - } - }, - { - "name": "flue.turn", - "type": "llm", - "children": [], - "input": [ - { - "content": [ - { - "arguments": { - "url": "https://example.test/flue/reasoning-streams" - }, - "id": "", - "name": "summarize_source", - "type": "toolCall" - } - ], - "role": "assistant" - }, - { - "content": [ - { - "text": "{\"summary\":\"Flue emits reasoning, tool execution, and LLM turn events separately.\",\"url\":\"https://example.test/flue/reasoning-streams\"}", - "type": "text" - } - ], - "isError": false, - "role": "toolResult", - "toolCallId": "", - "toolName": "summarize_source" - } - ], - "output": { - "content": [ - { - "text": "PROMPT_DONE", - "textSignature": "{\"v\":1,\"id\":\"msg_045458052dc10379016a705b62dbd8819189b8287061debf12\",\"phase\":\"final_answer\"}", - "type": "text" - } - ], - "role": "assistant" - }, - "metadata": { - "flue.api": "openai-responses", - "flue.input_message_offset": 5, - "flue.input_mode": "delta", - "flue.model": "gpt-5.4-nano", - "flue.provider": "openai", - "flue.session": "main", - "flue.stop_reason": "stop", - "flue.turn_purpose": "agent", - "model": "gpt-5.4-nano", - "provider": "openai" - }, - "metrics": { - "completion_tokens": 7, - "duration_ms": 0, - "estimated_cost": 0.00025315000000000005, - "prompt_cache_creation_tokens": 0, - "prompt_cached_tokens": 0, - "prompt_tokens": 1222, - "tokens": 1229 - } - } - ], - "input": [ - { - "content": [ - { - "text": "Complete this instrumented research flow. Call exactly one tool per turn and wait for each tool result before choosing the next tool. Step 1: call lookup with query \"flue instrumentation\". Step 2: use the lookup result id as lookupId and call web_search with query \"Braintrust Flue reasoning stream instrumentation\". Step 3: use the first web_search result url and call summarize_source. After summarize_source returns, reply with exactly PROMPT_DONE and no other text.", - "type": "text" - } - ], - "role": "user" - } - ], - "output": "PROMPT_DONE", - "metadata": { - "flue.operation": "prompt", - "flue.session": "main", - "flue.workflow_name": "instrumentation", - "provider": "flue", - "scenario": "flue-instrumentation" - }, - "metrics": { - "duration_ms": 0 - } - } - ] -} diff --git a/e2e/scenarios/flue-instrumentation/__snapshots__/flue-v0-8-latest-explicit.span-tree.txt b/e2e/scenarios/flue-instrumentation/__snapshots__/flue-v0-8-latest-explicit.span-tree.txt deleted file mode 100644 index b4408459c..000000000 --- a/e2e/scenarios/flue-instrumentation/__snapshots__/flue-v0-8-latest-explicit.span-tree.txt +++ /dev/null @@ -1,1499 +0,0 @@ -span_tree: -├── workflow:instrumentation [task] -│ input: { -│ "metadata": { -│ "scenario": "flue-instrumentation", -│ "testRunId": "" -│ }, -│ "scenario": "flue-instrumentation" -│ } -│ output: { -│ "scenario": "flue-instrumentation", -│ "status": "done" -│ } -│ metadata: { -│ "flue.workflow_name": "instrumentation", -│ "provider": "flue", -│ "scenario": "flue-instrumentation" -│ } -│ metrics: { -│ "duration_ms": 0 -│ } -│ ├── flue.skill [task] -│ │ input: [ -│ │ { -│ │ "content": [ -│ │ { -│ │ "text": "Run the skill named \"e2e-flue-skill\".\n\nArguments:\n{\n \"marker\": \"SKILL_DONE\"\n}", -│ │ "type": "text" -│ │ } -│ │ ], -│ │ "role": "user" -│ │ } -│ │ ] -│ │ output: "SKILL_DONE" -│ │ metadata: { -│ │ "flue.operation": "skill", -│ │ "flue.session": "skill", -│ │ "flue.workflow_name": "instrumentation", -│ │ "provider": "flue", -│ │ "scenario": "flue-instrumentation" -│ │ } -│ │ metrics: { -│ │ "duration_ms": 0 -│ │ } -│ │ ├── flue.turn [llm] -│ │ │ input: [ -│ │ │ { -│ │ │ "content": [ -│ │ │ { -│ │ │ "text": "Run the skill named \"e2e-flue-skill\".\n\nArguments:\n{\n \"marker\": \"SKILL_DONE\"\n}", -│ │ │ "type": "text" -│ │ │ } -│ │ │ ], -│ │ │ "role": "user" -│ │ │ } -│ │ │ ] -│ │ │ output: { -│ │ │ "content": [ -│ │ │ { -│ │ │ "arguments": { -│ │ │ "cwd": ".", -│ │ │ "description": "Run the deterministic Flue instrumentation e2e test skill e2e-flue-skill.", -│ │ │ "prompt": "Run the skill named \"e2e-flue-skill\" with arguments {\"marker\":\"SKILL_DONE\"}. Output only the marker and no extra text when finished." -│ │ │ }, -│ │ │ "id": "", -│ │ │ "name": "task", -│ │ │ "type": "toolCall" -│ │ │ } -│ │ │ ], -│ │ │ "role": "assistant" -│ │ │ } -│ │ │ metadata: { -│ │ │ "flue.api": "openai-responses", -│ │ │ "flue.model": "gpt-5.4-nano", -│ │ │ "flue.provider": "openai", -│ │ │ "flue.session": "skill", -│ │ │ "flue.stop_reason": "toolUse", -│ │ │ "flue.turn_purpose": "agent", -│ │ │ "model": "gpt-5.4-nano", -│ │ │ "provider": "openai" -│ │ │ } -│ │ │ metrics: { -│ │ │ "completion_tokens": 109, -│ │ │ "duration_ms": 0, -│ │ │ "estimated_cost": 0.00030565000000000003, -│ │ │ "prompt_cache_creation_tokens": 0, -│ │ │ "prompt_cached_tokens": 0, -│ │ │ "prompt_tokens": 847, -│ │ │ "tokens": 956 -│ │ │ } -│ │ │ └── flue.task [task] -│ │ │ input: "Run the skill named \"e2e-flue-skill\" with arguments {\"marker\":\"SKILL_DONE\"}. Output only the marker and no extra text when finished." -│ │ │ output: "SKILL_DONE" -│ │ │ metadata: { -│ │ │ "flue.session": "skill", -│ │ │ "provider": "flue" -│ │ │ } -│ │ │ metrics: { -│ │ │ "duration_ms": 0 -│ │ │ } -│ │ │ ├── flue.turn [llm] -│ │ │ │ input: [ -│ │ │ │ { -│ │ │ │ "content": [ -│ │ │ │ { -│ │ │ │ "text": "Run the skill named \"e2e-flue-skill\" with arguments {\"marker\":\"SKILL_DONE\"}. Output only the marker and no extra text when finished.", -│ │ │ │ "type": "text" -│ │ │ │ } -│ │ │ │ ], -│ │ │ │ "role": "user" -│ │ │ │ } -│ │ │ │ ] -│ │ │ │ output: { -│ │ │ │ "content": [ -│ │ │ │ { -│ │ │ │ "arguments": { -│ │ │ │ "agent": "e2e-flue-skill", -│ │ │ │ "cwd": ".", -│ │ │ │ "description": "Run e2e-flue-skill with marker SKILL_DONE", -│ │ │ │ "prompt": "Run the skill named \"e2e-flue-skill\" with arguments {\"marker\":\"SKILL_DONE\"}. Ensure completion and report the final output marker only." -│ │ │ │ }, -│ │ │ │ "id": "", -│ │ │ │ "name": "task", -│ │ │ │ "type": "toolCall" -│ │ │ │ } -│ │ │ │ ], -│ │ │ │ "role": "assistant" -│ │ │ │ } -│ │ │ │ metadata: { -│ │ │ │ "flue.api": "openai-responses", -│ │ │ │ "flue.input_mode": "full", -│ │ │ │ "flue.model": "gpt-5.4-nano", -│ │ │ │ "flue.provider": "openai", -│ │ │ │ "flue.session": "task:skill:", -│ │ │ │ "flue.stop_reason": "toolUse", -│ │ │ │ "flue.turn_purpose": "agent", -│ │ │ │ "model": "gpt-5.4-nano", -│ │ │ │ "provider": "openai" -│ │ │ │ } -│ │ │ │ metrics: { -│ │ │ │ "completion_tokens": 113, -│ │ │ │ "duration_ms": 0, -│ │ │ │ "estimated_cost": 0.00031225000000000003, -│ │ │ │ "prompt_cache_creation_tokens": 0, -│ │ │ │ "prompt_cached_tokens": 0, -│ │ │ │ "prompt_tokens": 855, -│ │ │ │ "tokens": 968 -│ │ │ │ } -│ │ │ ├── tool:task [tool] -│ │ │ │ input: { -│ │ │ │ "agent": "e2e-flue-skill", -│ │ │ │ "cwd": ".", -│ │ │ │ "description": "Run e2e-flue-skill with marker SKILL_DONE", -│ │ │ │ "prompt": "Run the skill named \"e2e-flue-skill\" with arguments {\"marker\":\"SKILL_DONE\"}. Ensure completion and report the final output marker only." -│ │ │ │ } -│ │ │ │ output: { -│ │ │ │ "content": [ -│ │ │ │ { -│ │ │ │ "text": "[flue] Subagent \"e2e-flue-skill\" is not declared. Available: (none).", -│ │ │ │ "type": "text" -│ │ │ │ } -│ │ │ │ ], -│ │ │ │ "details": {} -│ │ │ │ } -│ │ │ │ metadata: { -│ │ │ │ "flue.session": "task:skill:", -│ │ │ │ "flue.tool_name": "task", -│ │ │ │ "provider": "flue" -│ │ │ │ } -│ │ │ │ metrics: { -│ │ │ │ "duration_ms": 0 -│ │ │ │ } -│ │ │ │ error: "{\"content\":[{\"type\":\"text\",\"text\":\"[flue] Subagent \\\"e2e-flue-skill\\\" is not declared. Available: (none).\"}],\"details\":{}}" -│ │ │ ├── flue.turn [llm] -│ │ │ │ input: [ -│ │ │ │ { -│ │ │ │ "content": [ -│ │ │ │ { -│ │ │ │ "arguments": { -│ │ │ │ "agent": "e2e-flue-skill", -│ │ │ │ "cwd": ".", -│ │ │ │ "description": "Run e2e-flue-skill with marker SKILL_DONE", -│ │ │ │ "prompt": "Run the skill named \"e2e-flue-skill\" with arguments {\"marker\":\"SKILL_DONE\"}. Ensure completion and report the final output marker only." -│ │ │ │ }, -│ │ │ │ "id": "", -│ │ │ │ "name": "task", -│ │ │ │ "type": "toolCall" -│ │ │ │ } -│ │ │ │ ], -│ │ │ │ "role": "assistant" -│ │ │ │ }, -│ │ │ │ { -│ │ │ │ "content": [ -│ │ │ │ { -│ │ │ │ "text": "[flue] Subagent \"e2e-flue-skill\" is not declared. Available: (none).", -│ │ │ │ "type": "text" -│ │ │ │ } -│ │ │ │ ], -│ │ │ │ "isError": true, -│ │ │ │ "role": "toolResult", -│ │ │ │ "toolCallId": "", -│ │ │ │ "toolName": "task" -│ │ │ │ } -│ │ │ │ ] -│ │ │ │ output: { -│ │ │ │ "content": [ -│ │ │ │ { -│ │ │ │ "arguments": { -│ │ │ │ "command": "find . -maxdepth 4 -name AGENTS.md -print", -│ │ │ │ "timeout": 100000 -│ │ │ │ }, -│ │ │ │ "id": "", -│ │ │ │ "name": "bash", -│ │ │ │ "type": "toolCall" -│ │ │ │ } -│ │ │ │ ], -│ │ │ │ "role": "assistant" -│ │ │ │ } -│ │ │ │ metadata: { -│ │ │ │ "flue.api": "openai-responses", -│ │ │ │ "flue.input_message_offset": 1, -│ │ │ │ "flue.input_mode": "delta", -│ │ │ │ "flue.model": "gpt-5.4-nano", -│ │ │ │ "flue.provider": "openai", -│ │ │ │ "flue.session": "task:skill:", -│ │ │ │ "flue.stop_reason": "toolUse", -│ │ │ │ "flue.turn_purpose": "agent", -│ │ │ │ "model": "gpt-5.4-nano", -│ │ │ │ "provider": "openai" -│ │ │ │ } -│ │ │ │ metrics: { -│ │ │ │ "completion_tokens": 94, -│ │ │ │ "duration_ms": 0, -│ │ │ │ "estimated_cost": 0.00031790000000000003, -│ │ │ │ "prompt_cache_creation_tokens": 0, -│ │ │ │ "prompt_cached_tokens": 0, -│ │ │ │ "prompt_tokens": 1002, -│ │ │ │ "tokens": 1096 -│ │ │ │ } -│ │ │ ├── tool:bash [tool] -│ │ │ │ input: { -│ │ │ │ "command": "find . -maxdepth 4 -name AGENTS.md -print", -│ │ │ │ "timeout": 100000 -│ │ │ │ } -│ │ │ │ output: { -│ │ │ │ "content": [ -│ │ │ │ { -│ │ │ │ "text": "(no output)", -│ │ │ │ "type": "text" -│ │ │ │ } -│ │ │ │ ], -│ │ │ │ "details": { -│ │ │ │ "command": "find . -maxdepth 4 -name AGENTS.md -print", -│ │ │ │ "exitCode": 0 -│ │ │ │ } -│ │ │ │ } -│ │ │ │ metadata: { -│ │ │ │ "flue.session": "task:skill:", -│ │ │ │ "flue.tool_name": "bash", -│ │ │ │ "provider": "flue" -│ │ │ │ } -│ │ │ │ metrics: { -│ │ │ │ "duration_ms": 0 -│ │ │ │ } -│ │ │ ├── flue.turn [llm] -│ │ │ │ input: [ -│ │ │ │ { -│ │ │ │ "content": [ -│ │ │ │ { -│ │ │ │ "arguments": { -│ │ │ │ "command": "find . -maxdepth 4 -name AGENTS.md -print", -│ │ │ │ "timeout": 100000 -│ │ │ │ }, -│ │ │ │ "id": "", -│ │ │ │ "name": "bash", -│ │ │ │ "type": "toolCall" -│ │ │ │ } -│ │ │ │ ], -│ │ │ │ "role": "assistant" -│ │ │ │ }, -│ │ │ │ { -│ │ │ │ "content": [ -│ │ │ │ { -│ │ │ │ "text": "(no output)", -│ │ │ │ "type": "text" -│ │ │ │ } -│ │ │ │ ], -│ │ │ │ "isError": false, -│ │ │ │ "role": "toolResult", -│ │ │ │ "toolCallId": "", -│ │ │ │ "toolName": "bash" -│ │ │ │ } -│ │ │ │ ] -│ │ │ │ output: { -│ │ │ │ "content": [ -│ │ │ │ { -│ │ │ │ "arguments": { -│ │ │ │ "command": "find . -maxdepth 4 -type f -name AGENTS.md -print -exec sed -n '1,200p' {} \\;", -│ │ │ │ "timeout": 100000 -│ │ │ │ }, -│ │ │ │ "id": "", -│ │ │ │ "name": "bash", -│ │ │ │ "type": "toolCall" -│ │ │ │ } -│ │ │ │ ], -│ │ │ │ "role": "assistant" -│ │ │ │ } -│ │ │ │ metadata: { -│ │ │ │ "flue.api": "openai-responses", -│ │ │ │ "flue.input_message_offset": 3, -│ │ │ │ "flue.input_mode": "delta", -│ │ │ │ "flue.model": "gpt-5.4-nano", -│ │ │ │ "flue.provider": "openai", -│ │ │ │ "flue.session": "task:skill:", -│ │ │ │ "flue.stop_reason": "toolUse", -│ │ │ │ "flue.turn_purpose": "agent", -│ │ │ │ "model": "gpt-5.4-nano", -│ │ │ │ "provider": "openai" -│ │ │ │ } -│ │ │ │ metrics: { -│ │ │ │ "completion_tokens": 87, -│ │ │ │ "duration_ms": 0, -│ │ │ │ "estimated_cost": 0.00034815000000000003, -│ │ │ │ "prompt_cache_creation_tokens": 0, -│ │ │ │ "prompt_cached_tokens": 0, -│ │ │ │ "prompt_tokens": 1197, -│ │ │ │ "tokens": 1284 -│ │ │ │ } -│ │ │ ├── tool:bash [tool] -│ │ │ │ input: { -│ │ │ │ "command": "find . -maxdepth 4 -type f -name AGENTS.md -print -exec sed -n '1,200p' {} \\;", -│ │ │ │ "timeout": 100000 -│ │ │ │ } -│ │ │ │ output: { -│ │ │ │ "content": [ -│ │ │ │ { -│ │ │ │ "text": "(no output)", -│ │ │ │ "type": "text" -│ │ │ │ } -│ │ │ │ ], -│ │ │ │ "details": { -│ │ │ │ "command": "find . -maxdepth 4 -type f -name AGENTS.md -print -exec sed -n '1,200p' {} \\;", -│ │ │ │ "exitCode": 0 -│ │ │ │ } -│ │ │ │ } -│ │ │ │ metadata: { -│ │ │ │ "flue.session": "task:skill:", -│ │ │ │ "flue.tool_name": "bash", -│ │ │ │ "provider": "flue" -│ │ │ │ } -│ │ │ │ metrics: { -│ │ │ │ "duration_ms": 0 -│ │ │ │ } -│ │ │ ├── flue.turn [llm] -│ │ │ │ input: [ -│ │ │ │ { -│ │ │ │ "content": [ -│ │ │ │ { -│ │ │ │ "arguments": { -│ │ │ │ "command": "find . -maxdepth 4 -type f -name AGENTS.md -print -exec sed -n '1,200p' {} \\;", -│ │ │ │ "timeout": 100000 -│ │ │ │ }, -│ │ │ │ "id": "", -│ │ │ │ "name": "bash", -│ │ │ │ "type": "toolCall" -│ │ │ │ } -│ │ │ │ ], -│ │ │ │ "role": "assistant" -│ │ │ │ }, -│ │ │ │ { -│ │ │ │ "content": [ -│ │ │ │ { -│ │ │ │ "text": "(no output)", -│ │ │ │ "type": "text" -│ │ │ │ } -│ │ │ │ ], -│ │ │ │ "isError": false, -│ │ │ │ "role": "toolResult", -│ │ │ │ "toolCallId": "", -│ │ │ │ "toolName": "bash" -│ │ │ │ } -│ │ │ │ ] -│ │ │ │ output: { -│ │ │ │ "content": [ -│ │ │ │ { -│ │ │ │ "arguments": { -│ │ │ │ "command": "find . -maxdepth 6 -type f -name 'AGENTS.md' -print", -│ │ │ │ "timeout": 100000 -│ │ │ │ }, -│ │ │ │ "id": "", -│ │ │ │ "name": "bash", -│ │ │ │ "type": "toolCall" -│ │ │ │ } -│ │ │ │ ], -│ │ │ │ "role": "assistant" -│ │ │ │ } -│ │ │ │ metadata: { -│ │ │ │ "flue.api": "openai-responses", -│ │ │ │ "flue.input_message_offset": 5, -│ │ │ │ "flue.input_mode": "delta", -│ │ │ │ "flue.model": "gpt-5.4-nano", -│ │ │ │ "flue.provider": "openai", -│ │ │ │ "flue.session": "task:skill:", -│ │ │ │ "flue.stop_reason": "toolUse", -│ │ │ │ "flue.turn_purpose": "agent", -│ │ │ │ "model": "gpt-5.4-nano", -│ │ │ │ "provider": "openai" -│ │ │ │ } -│ │ │ │ metrics: { -│ │ │ │ "completion_tokens": 58, -│ │ │ │ "duration_ms": 0, -│ │ │ │ "estimated_cost": 0.0003321, -│ │ │ │ "prompt_cache_creation_tokens": 0, -│ │ │ │ "prompt_cached_tokens": 0, -│ │ │ │ "prompt_tokens": 1298, -│ │ │ │ "tokens": 1356 -│ │ │ │ } -│ │ │ ├── tool:bash [tool] -│ │ │ │ input: { -│ │ │ │ "command": "find . -maxdepth 6 -type f -name 'AGENTS.md' -print", -│ │ │ │ "timeout": 100000 -│ │ │ │ } -│ │ │ │ output: { -│ │ │ │ "content": [ -│ │ │ │ { -│ │ │ │ "text": "(no output)", -│ │ │ │ "type": "text" -│ │ │ │ } -│ │ │ │ ], -│ │ │ │ "details": { -│ │ │ │ "command": "find . -maxdepth 6 -type f -name 'AGENTS.md' -print", -│ │ │ │ "exitCode": 0 -│ │ │ │ } -│ │ │ │ } -│ │ │ │ metadata: { -│ │ │ │ "flue.session": "task:skill:", -│ │ │ │ "flue.tool_name": "bash", -│ │ │ │ "provider": "flue" -│ │ │ │ } -│ │ │ │ metrics: { -│ │ │ │ "duration_ms": 0 -│ │ │ │ } -│ │ │ ├── flue.turn [llm] -│ │ │ │ input: [ -│ │ │ │ { -│ │ │ │ "content": [ -│ │ │ │ { -│ │ │ │ "arguments": { -│ │ │ │ "command": "find . -maxdepth 6 -type f -name 'AGENTS.md' -print", -│ │ │ │ "timeout": 100000 -│ │ │ │ }, -│ │ │ │ "id": "", -│ │ │ │ "name": "bash", -│ │ │ │ "type": "toolCall" -│ │ │ │ } -│ │ │ │ ], -│ │ │ │ "role": "assistant" -│ │ │ │ }, -│ │ │ │ { -│ │ │ │ "content": [ -│ │ │ │ { -│ │ │ │ "text": "(no output)", -│ │ │ │ "type": "text" -│ │ │ │ } -│ │ │ │ ], -│ │ │ │ "isError": false, -│ │ │ │ "role": "toolResult", -│ │ │ │ "toolCallId": "", -│ │ │ │ "toolName": "bash" -│ │ │ │ } -│ │ │ │ ] -│ │ │ │ output: { -│ │ │ │ "content": [ -│ │ │ │ { -│ │ │ │ "arguments": { -│ │ │ │ "command": "find . -maxdepth 5 -type f -name '*flue*skill*' -o -name '*skill*' | head", -│ │ │ │ "timeout": 100000 -│ │ │ │ }, -│ │ │ │ "id": "", -│ │ │ │ "name": "bash", -│ │ │ │ "type": "toolCall" -│ │ │ │ } -│ │ │ │ ], -│ │ │ │ "role": "assistant" -│ │ │ │ } -│ │ │ │ metadata: { -│ │ │ │ "flue.api": "openai-responses", -│ │ │ │ "flue.input_message_offset": 7, -│ │ │ │ "flue.input_mode": "delta", -│ │ │ │ "flue.model": "gpt-5.4-nano", -│ │ │ │ "flue.provider": "openai", -│ │ │ │ "flue.session": "task:skill:", -│ │ │ │ "flue.stop_reason": "toolUse", -│ │ │ │ "flue.turn_purpose": "agent", -│ │ │ │ "model": "gpt-5.4-nano", -│ │ │ │ "provider": "openai" -│ │ │ │ } -│ │ │ │ metrics: { -│ │ │ │ "completion_tokens": 68, -│ │ │ │ "duration_ms": 0, -│ │ │ │ "estimated_cost": 0.00035900000000000005, -│ │ │ │ "prompt_cache_creation_tokens": 0, -│ │ │ │ "prompt_cached_tokens": 0, -│ │ │ │ "prompt_tokens": 1370, -│ │ │ │ "tokens": 1438 -│ │ │ │ } -│ │ │ ├── tool:bash [tool] -│ │ │ │ input: { -│ │ │ │ "command": "find . -maxdepth 5 -type f -name '*flue*skill*' -o -name '*skill*' | head", -│ │ │ │ "timeout": 100000 -│ │ │ │ } -│ │ │ │ output: { -│ │ │ │ "content": [ -│ │ │ │ { -│ │ │ │ "text": "./.agents/skills\n./.agents/skills/e2e-flue-skill", -│ │ │ │ "type": "text" -│ │ │ │ } -│ │ │ │ ], -│ │ │ │ "details": { -│ │ │ │ "command": "find . -maxdepth 5 -type f -name '*flue*skill*' -o -name '*skill*' | head", -│ │ │ │ "exitCode": 0 -│ │ │ │ } -│ │ │ │ } -│ │ │ │ metadata: { -│ │ │ │ "flue.session": "task:skill:", -│ │ │ │ "flue.tool_name": "bash", -│ │ │ │ "provider": "flue" -│ │ │ │ } -│ │ │ │ metrics: { -│ │ │ │ "duration_ms": 0 -│ │ │ │ } -│ │ │ ├── flue.turn [llm] -│ │ │ │ input: [ -│ │ │ │ { -│ │ │ │ "content": [ -│ │ │ │ { -│ │ │ │ "arguments": { -│ │ │ │ "command": "find . -maxdepth 5 -type f -name '*flue*skill*' -o -name '*skill*' | head", -│ │ │ │ "timeout": 100000 -│ │ │ │ }, -│ │ │ │ "id": "", -│ │ │ │ "name": "bash", -│ │ │ │ "type": "toolCall" -│ │ │ │ } -│ │ │ │ ], -│ │ │ │ "role": "assistant" -│ │ │ │ }, -│ │ │ │ { -│ │ │ │ "content": [ -│ │ │ │ { -│ │ │ │ "text": "./.agents/skills\n./.agents/skills/e2e-flue-skill", -│ │ │ │ "type": "text" -│ │ │ │ } -│ │ │ │ ], -│ │ │ │ "isError": false, -│ │ │ │ "role": "toolResult", -│ │ │ │ "toolCallId": "", -│ │ │ │ "toolName": "bash" -│ │ │ │ } -│ │ │ │ ] -│ │ │ │ output: { -│ │ │ │ "content": [ -│ │ │ │ { -│ │ │ │ "arguments": { -│ │ │ │ "command": "find ./.agents/skills/e2e-flue-skill -maxdepth 2 -type f -print", -│ │ │ │ "timeout": 100000 -│ │ │ │ }, -│ │ │ │ "id": "", -│ │ │ │ "name": "bash", -│ │ │ │ "type": "toolCall" -│ │ │ │ } -│ │ │ │ ], -│ │ │ │ "role": "assistant" -│ │ │ │ } -│ │ │ │ metadata: { -│ │ │ │ "flue.api": "openai-responses", -│ │ │ │ "flue.input_message_offset": 9, -│ │ │ │ "flue.input_mode": "delta", -│ │ │ │ "flue.model": "gpt-5.4-nano", -│ │ │ │ "flue.provider": "openai", -│ │ │ │ "flue.session": "task:skill:", -│ │ │ │ "flue.stop_reason": "toolUse", -│ │ │ │ "flue.turn_purpose": "agent", -│ │ │ │ "model": "gpt-5.4-nano", -│ │ │ │ "provider": "openai" -│ │ │ │ } -│ │ │ │ metrics: { -│ │ │ │ "completion_tokens": 109, -│ │ │ │ "duration_ms": 0, -│ │ │ │ "estimated_cost": 0.00043885000000000007, -│ │ │ │ "prompt_cache_creation_tokens": 0, -│ │ │ │ "prompt_cached_tokens": 0, -│ │ │ │ "prompt_tokens": 1513, -│ │ │ │ "tokens": 1622 -│ │ │ │ } -│ │ │ ├── tool:bash [tool] -│ │ │ │ input: { -│ │ │ │ "command": "find ./.agents/skills/e2e-flue-skill -maxdepth 2 -type f -print", -│ │ │ │ "timeout": 100000 -│ │ │ │ } -│ │ │ │ output: { -│ │ │ │ "content": [ -│ │ │ │ { -│ │ │ │ "text": "./.agents/skills/e2e-flue-skill/SKILL.md", -│ │ │ │ "type": "text" -│ │ │ │ } -│ │ │ │ ], -│ │ │ │ "details": { -│ │ │ │ "command": "find ./.agents/skills/e2e-flue-skill -maxdepth 2 -type f -print", -│ │ │ │ "exitCode": 0 -│ │ │ │ } -│ │ │ │ } -│ │ │ │ metadata: { -│ │ │ │ "flue.session": "task:skill:", -│ │ │ │ "flue.tool_name": "bash", -│ │ │ │ "provider": "flue" -│ │ │ │ } -│ │ │ │ metrics: { -│ │ │ │ "duration_ms": 0 -│ │ │ │ } -│ │ │ ├── flue.turn [llm] -│ │ │ │ input: [ -│ │ │ │ { -│ │ │ │ "content": [ -│ │ │ │ { -│ │ │ │ "arguments": { -│ │ │ │ "command": "find ./.agents/skills/e2e-flue-skill -maxdepth 2 -type f -print", -│ │ │ │ "timeout": 100000 -│ │ │ │ }, -│ │ │ │ "id": "", -│ │ │ │ "name": "bash", -│ │ │ │ "type": "toolCall" -│ │ │ │ } -│ │ │ │ ], -│ │ │ │ "role": "assistant" -│ │ │ │ }, -│ │ │ │ { -│ │ │ │ "content": [ -│ │ │ │ { -│ │ │ │ "text": "./.agents/skills/e2e-flue-skill/SKILL.md", -│ │ │ │ "type": "text" -│ │ │ │ } -│ │ │ │ ], -│ │ │ │ "isError": false, -│ │ │ │ "role": "toolResult", -│ │ │ │ "toolCallId": "", -│ │ │ │ "toolName": "bash" -│ │ │ │ } -│ │ │ │ ] -│ │ │ │ output: { -│ │ │ │ "content": [ -│ │ │ │ { -│ │ │ │ "arguments": { -│ │ │ │ "limit": 200, -│ │ │ │ "offset": 1, -│ │ │ │ "path": "./.agents/skills/e2e-flue-skill/SKILL.md" -│ │ │ │ }, -│ │ │ │ "id": "", -│ │ │ │ "name": "read", -│ │ │ │ "type": "toolCall" -│ │ │ │ } -│ │ │ │ ], -│ │ │ │ "role": "assistant" -│ │ │ │ } -│ │ │ │ metadata: { -│ │ │ │ "flue.api": "openai-responses", -│ │ │ │ "flue.input_message_offset": 11, -│ │ │ │ "flue.input_mode": "delta", -│ │ │ │ "flue.model": "gpt-5.4-nano", -│ │ │ │ "flue.provider": "openai", -│ │ │ │ "flue.session": "task:skill:", -│ │ │ │ "flue.stop_reason": "toolUse", -│ │ │ │ "flue.turn_purpose": "agent", -│ │ │ │ "model": "gpt-5.4-nano", -│ │ │ │ "provider": "openai" -│ │ │ │ } -│ │ │ │ metrics: { -│ │ │ │ "completion_tokens": 64, -│ │ │ │ "duration_ms": 0, -│ │ │ │ "estimated_cost": 0.00015592, -│ │ │ │ "prompt_cache_creation_tokens": 0, -│ │ │ │ "prompt_cached_tokens": 1536, -│ │ │ │ "prompt_tokens": 1762, -│ │ │ │ "tokens": 1826 -│ │ │ │ } -│ │ │ ├── tool:read [tool] -│ │ │ │ input: { -│ │ │ │ "limit": 200, -│ │ │ │ "offset": 1, -│ │ │ │ "path": "./.agents/skills/e2e-flue-skill/SKILL.md" -│ │ │ │ } -│ │ │ │ output: { -│ │ │ │ "content": [ -│ │ │ │ { -│ │ │ │ "text": "---\nname: e2e-flue-skill\ndescription: Deterministic skill used by the Braintrust Flue instrumentation e2e test.\n---\n\nReturn the marker from the provided args exactly once. Output no other text.\n", -│ │ │ │ "type": "text" -│ │ │ │ } -│ │ │ │ ], -│ │ │ │ "details": { -│ │ │ │ "lines": 7, -│ │ │ │ "path": "./.agents/skills/e2e-flue-skill/SKILL.md" -│ │ │ │ } -│ │ │ │ } -│ │ │ │ metadata: { -│ │ │ │ "flue.session": "task:skill:", -│ │ │ │ "flue.tool_name": "read", -│ │ │ │ "provider": "flue" -│ │ │ │ } -│ │ │ │ metrics: { -│ │ │ │ "duration_ms": 0 -│ │ │ │ } -│ │ │ ├── flue.turn [llm] -│ │ │ │ input: [ -│ │ │ │ { -│ │ │ │ "content": [ -│ │ │ │ { -│ │ │ │ "arguments": { -│ │ │ │ "limit": 200, -│ │ │ │ "offset": 1, -│ │ │ │ "path": "./.agents/skills/e2e-flue-skill/SKILL.md" -│ │ │ │ }, -│ │ │ │ "id": "", -│ │ │ │ "name": "read", -│ │ │ │ "type": "toolCall" -│ │ │ │ } -│ │ │ │ ], -│ │ │ │ "role": "assistant" -│ │ │ │ }, -│ │ │ │ { -│ │ │ │ "content": [ -│ │ │ │ { -│ │ │ │ "text": "---\nname: e2e-flue-skill\ndescription: Deterministic skill used by the Braintrust Flue instrumentation e2e test.\n---\n\nReturn the marker from the provided args exactly once. Output no other text.\n", -│ │ │ │ "type": "text" -│ │ │ │ } -│ │ │ │ ], -│ │ │ │ "isError": false, -│ │ │ │ "role": "toolResult", -│ │ │ │ "toolCallId": "", -│ │ │ │ "toolName": "read" -│ │ │ │ } -│ │ │ │ ] -│ │ │ │ output: { -│ │ │ │ "content": [ -│ │ │ │ { -│ │ │ │ "arguments": { -│ │ │ │ "command": "cat ./package.json", -│ │ │ │ "timeout": 100000 -│ │ │ │ }, -│ │ │ │ "id": "", -│ │ │ │ "name": "bash", -│ │ │ │ "type": "toolCall" -│ │ │ │ } -│ │ │ │ ], -│ │ │ │ "role": "assistant" -│ │ │ │ } -│ │ │ │ metadata: { -│ │ │ │ "flue.api": "openai-responses", -│ │ │ │ "flue.input_message_offset": 13, -│ │ │ │ "flue.input_mode": "delta", -│ │ │ │ "flue.model": "gpt-5.4-nano", -│ │ │ │ "flue.provider": "openai", -│ │ │ │ "flue.session": "task:skill:", -│ │ │ │ "flue.stop_reason": "toolUse", -│ │ │ │ "flue.turn_purpose": "agent", -│ │ │ │ "model": "gpt-5.4-nano", -│ │ │ │ "provider": "openai" -│ │ │ │ } -│ │ │ │ metrics: { -│ │ │ │ "completion_tokens": 90, -│ │ │ │ "duration_ms": 0, -│ │ │ │ "estimated_cost": 0.00021242000000000003, -│ │ │ │ "prompt_cache_creation_tokens": 0, -│ │ │ │ "prompt_cached_tokens": 1536, -│ │ │ │ "prompt_tokens": 1882, -│ │ │ │ "tokens": 1972 -│ │ │ │ } -│ │ │ ├── tool:bash [tool] -│ │ │ │ input: { -│ │ │ │ "command": "cat ./package.json", -│ │ │ │ "timeout": 100000 -│ │ │ │ } -│ │ │ │ output: { -│ │ │ │ "content": [ -│ │ │ │ { -│ │ │ │ "text": "{\n \"name\": \"flue-instrumentation-scenario\",\n \"private\": true,\n \"type\": \"module\",\n \"braintrustScenario\": {\n \"bump\": {\n \"dependencies\": {\n \"flue-cli-v0-8-latest\": {\n \"package\": \"@flue/cli\",\n \"range\": \">=0.8.0 <0.9.0\"\n },\n \"flue-cli-v1-latest\": {\n \"package\": \"@flue/cli\",\n \"range\": \">=1.0.0-beta.3 <2.0.0-0\",\n \"allowPrerelease\": true\n },\n \"flue-runtime-v0-8-latest\": {\n \"package\": \"@flue/runtime\",\n \"range\": \">=0.8.0 <0.9.0\"\n },\n \"flue-runtime-v1-latest\": {\n \"package\": \"@flue/runtime\",\n \"range\": \">=1.0.0-beta.3 <2.0.0-0\",\n \"allowPrerelease\": true\n },\n \"flue-runtime-v2-latest\": {\n \"package\": \"@flue/runtime\",\n \"range\": \">=2.0.0 <3.0.0\"\n }\n }\n }\n },\n \"dependencies\": {\n \"@flue/cli\": \"0.8.0\",\n \"@flue/runtime\": \"0.8.0\",\n \"flue-cli-v0-8-latest\": \"npm:@flue/cli@0.8.1\",\n \"flue-cli-v1\": \"npm:@flue/cli@1.0.0-beta.3\",\n \"flue-cli-v1-latest\": \"npm:@flue/cli@1.0.0-beta.9\",\n \"flue-runtime-v0-8-latest\": \"npm:@flue/runtime@0.8.1\",\n \"flue-runtime-v1\": \"npm:@flue/runtime@1.0.0-beta.3\",\n \"flue-runtime-v1-latest\": \"npm:@flue/runtime@1.0.0-beta.9\",\n \"flue-runtime-v2\": \"npm:@flue/runtime@2.0.0\",\n \"flue-runtime-v2-latest\": \"npm:@flue/runtime@2.0.1\",\n \"pi-ai-v2\": \"npm:@earendil-works/pi-ai@0.83.0\",\n \"valibot\": \"1.4.2\"\n },\n \"pnpm\": {\n \"overrides\": {\n \"@flue/runtime@0.8.0>@earendil-works/pi-agent-core\": \"0.75.4\",\n \"@flue/runtime@0.8.0>@earendil-works/pi-ai\": \"0.75.4\",\n \"@flue/runtime@0.8.1>@earendil-works/pi-agent-core\": \"0.75.4\",\n \"@flue/runtime@0.8.1>@earendil-works/pi-ai\": \"0.75.4\",\n \"@hono/node-server@>=2.0.0 <2.0.10\": \"^2.0.10\",\n \"fast-uri@<3.1.5\": \"3.1.8\",\n \"brace-expansion@>=3.0.0 <5.0.9\": \"5.0.12\",\n \"postcss@<8.5.18\": \"^8.5.18\",\n \"valibot@<1.4.2\": \"^1.4.2\",\n \"ip-address@<10.2.2\": \"10.7.1\",\n \"undici@<7.29.0\": \"7.29.1\",\n \"hono@<4.12.34\": \"4.13.7\",\n \"@hono/node-server@<1.19.15\": \"^1.19.15\",\n \"nanoid@<3.3.18\": \"^3.3.18\",\n \"js-yaml@3\": \"^3.15.1\",\n \"js-yaml@4\": \"^4.3.1\",\n \"sharp@<0.35.0\": \"0.35.5\",\n \"ip-address@>=10.0.0 <10.7.1\": \"10.7.1\",\n \"fast-uri@>=3.0.0 <3.1.8\": \"3.1.8\",\n \"undici@>=7.0.0 <7.29.1\": \"7.29.1\",\n \"js-yaml@>=5.0.0 <5.4.1\": \"5.4.1\",\n \"brace-expansion@>=5.0.0 <5.0.12\": \"5.0.12\",\n \"hono@>=4.0.0 <4.13.7\": \"4.13.7\",\n \"proxy-addr@>=2.0.0 <2.0.8\": \"2.0.8\",\n \"source-map-js@>=1.0.0 <1.2.2\": \"1.2.2\",\n \"smol-toml@>=1.0.0 <1.9.0\": \"1.9.0\",\n \"sharp@>=0.35.0 <0.35.5\": \"0.35.5\",\n \"@modelcontextprotocol/sdk@>=1.0.0 <1.31.0\": \"1.31.0\",\n \"@modelcontextprotocol/client@>=2.0.0 <2.2.0\": \"2.2.0\"\n }\n }\n}", -│ │ │ │ "type": "text" -│ │ │ │ } -│ │ │ │ ], -│ │ │ │ "details": { -│ │ │ │ "command": "cat ./package.json", -│ │ │ │ "exitCode": 0 -│ │ │ │ } -│ │ │ │ } -│ │ │ │ metadata: { -│ │ │ │ "flue.session": "task:skill:", -│ │ │ │ "flue.tool_name": "bash", -│ │ │ │ "provider": "flue" -│ │ │ │ } -│ │ │ │ metrics: { -│ │ │ │ "duration_ms": 0 -│ │ │ │ } -│ │ │ ├── flue.turn [llm] -│ │ │ │ input: [ -│ │ │ │ { -│ │ │ │ "content": [ -│ │ │ │ { -│ │ │ │ "arguments": { -│ │ │ │ "command": "cat ./package.json", -│ │ │ │ "timeout": 100000 -│ │ │ │ }, -│ │ │ │ "id": "", -│ │ │ │ "name": "bash", -│ │ │ │ "type": "toolCall" -│ │ │ │ } -│ │ │ │ ], -│ │ │ │ "role": "assistant" -│ │ │ │ }, -│ │ │ │ { -│ │ │ │ "content": [ -│ │ │ │ { -│ │ │ │ "text": "{\n \"name\": \"flue-instrumentation-scenario\",\n \"private\": true,\n \"type\": \"module\",\n \"braintrustScenario\": {\n \"bump\": {\n \"dependencies\": {\n \"flue-cli-v0-8-latest\": {\n \"package\": \"@flue/cli\",\n \"range\": \">=0.8.0 <0.9.0\"\n },\n \"flue-cli-v1-latest\": {\n \"package\": \"@flue/cli\",\n \"range\": \">=1.0.0-beta.3 <2.0.0-0\",\n \"allowPrerelease\": true\n },\n \"flue-runtime-v0-8-latest\": {\n \"package\": \"@flue/runtime\",\n \"range\": \">=0.8.0 <0.9.0\"\n },\n \"flue-runtime-v1-latest\": {\n \"package\": \"@flue/runtime\",\n \"range\": \">=1.0.0-beta.3 <2.0.0-0\",\n \"allowPrerelease\": true\n },\n \"flue-runtime-v2-latest\": {\n \"package\": \"@flue/runtime\",\n \"range\": \">=2.0.0 <3.0.0\"\n }\n }\n }\n },\n \"dependencies\": {\n \"@flue/cli\": \"0.8.0\",\n \"@flue/runtime\": \"0.8.0\",\n \"flue-cli-v0-8-latest\": \"npm:@flue/cli@0.8.1\",\n \"flue-cli-v1\": \"npm:@flue/cli@1.0.0-beta.3\",\n \"flue-cli-v1-latest\": \"npm:@flue/cli@1.0.0-beta.9\",\n \"flue-runtime-v0-8-latest\": \"npm:@flue/runtime@0.8.1\",\n \"flue-runtime-v1\": \"npm:@flue/runtime@1.0.0-beta.3\",\n \"flue-runtime-v1-latest\": \"npm:@flue/runtime@1.0.0-beta.9\",\n \"flue-runtime-v2\": \"npm:@flue/runtime@2.0.0\",\n \"flue-runtime-v2-latest\": \"npm:@flue/runtime@2.0.1\",\n \"pi-ai-v2\": \"npm:@earendil-works/pi-ai@0.83.0\",\n \"valibot\": \"1.4.2\"\n },\n \"pnpm\": {\n \"overrides\": {\n \"@flue/runtime@0.8.0>@earendil-works/pi-agent-core\": \"0.75.4\",\n \"@flue/runtime@0.8.0>@earendil-works/pi-ai\": \"0.75.4\",\n \"@flue/runtime@0.8.1>@earendil-works/pi-agent-core\": \"0.75.4\",\n \"@flue/runtime@0.8.1>@earendil-works/pi-ai\": \"0.75.4\",\n \"@hono/node-server@>=2.0.0 <2.0.10\": \"^2.0.10\",\n \"fast-uri@<3.1.5\": \"3.1.8\",\n \"brace-expansion@>=3.0.0 <5.0.9\": \"5.0.12\",\n \"postcss@<8.5.18\": \"^8.5.18\",\n \"valibot@<1.4.2\": \"^1.4.2\",\n \"ip-address@<10.2.2\": \"10.7.1\",\n \"undici@<7.29.0\": \"7.29.1\",\n \"hono@<4.12.34\": \"4.13.7\",\n \"@hono/node-server@<1.19.15\": \"^1.19.15\",\n \"nanoid@<3.3.18\": \"^3.3.18\",\n \"js-yaml@3\": \"^3.15.1\",\n \"js-yaml@4\": \"^4.3.1\",\n \"sharp@<0.35.0\": \"0.35.5\",\n \"ip-address@>=10.0.0 <10.7.1\": \"10.7.1\",\n \"fast-uri@>=3.0.0 <3.1.8\": \"3.1.8\",\n \"undici@>=7.0.0 <7.29.1\": \"7.29.1\",\n \"js-yaml@>=5.0.0 <5.4.1\": \"5.4.1\",\n \"brace-expansion@>=5.0.0 <5.0.12\": \"5.0.12\",\n \"hono@>=4.0.0 <4.13.7\": \"4.13.7\",\n \"proxy-addr@>=2.0.0 <2.0.8\": \"2.0.8\",\n \"source-map-js@>=1.0.0 <1.2.2\": \"1.2.2\",\n \"smol-toml@>=1.0.0 <1.9.0\": \"1.9.0\",\n \"sharp@>=0.35.0 <0.35.5\": \"0.35.5\",\n \"@modelcontextprotocol/sdk@>=1.0.0 <1.31.0\": \"1.31.0\",\n \"@modelcontextprotocol/client@>=2.0.0 <2.2.0\": \"2.2.0\"\n }\n }\n}", -│ │ │ │ "type": "text" -│ │ │ │ } -│ │ │ │ ], -│ │ │ │ "isError": false, -│ │ │ │ "role": "toolResult", -│ │ │ │ "toolCallId": "", -│ │ │ │ "toolName": "bash" -│ │ │ │ } -│ │ │ │ ] -│ │ │ │ output: { -│ │ │ │ "content": [ -│ │ │ │ { -│ │ │ │ "arguments": { -│ │ │ │ "command": "printf SKILL_DONE", -│ │ │ │ "timeout": 100000 -│ │ │ │ }, -│ │ │ │ "id": "", -│ │ │ │ "name": "bash", -│ │ │ │ "type": "toolCall" -│ │ │ │ } -│ │ │ │ ], -│ │ │ │ "role": "assistant" -│ │ │ │ } -│ │ │ │ metadata: { -│ │ │ │ "flue.api": "openai-responses", -│ │ │ │ "flue.input_message_offset": 15, -│ │ │ │ "flue.input_mode": "delta", -│ │ │ │ "flue.model": "gpt-5.4-nano", -│ │ │ │ "flue.provider": "openai", -│ │ │ │ "flue.session": "task:skill:", -│ │ │ │ "flue.stop_reason": "toolUse", -│ │ │ │ "flue.turn_purpose": "agent", -│ │ │ │ "model": "gpt-5.4-nano", -│ │ │ │ "provider": "openai" -│ │ │ │ } -│ │ │ │ metrics: { -│ │ │ │ "completion_tokens": 59, -│ │ │ │ "duration_ms": 0, -│ │ │ │ "estimated_cost": 0.0004814700000000001, -│ │ │ │ "prompt_cache_creation_tokens": 0, -│ │ │ │ "prompt_cached_tokens": 1536, -│ │ │ │ "prompt_tokens": 3421, -│ │ │ │ "tokens": 3480 -│ │ │ │ } -│ │ │ ├── tool:bash [tool] -│ │ │ │ input: { -│ │ │ │ "command": "printf SKILL_DONE", -│ │ │ │ "timeout": 100000 -│ │ │ │ } -│ │ │ │ output: { -│ │ │ │ "content": [ -│ │ │ │ { -│ │ │ │ "text": "SKILL_DONE", -│ │ │ │ "type": "text" -│ │ │ │ } -│ │ │ │ ], -│ │ │ │ "details": { -│ │ │ │ "command": "printf SKILL_DONE", -│ │ │ │ "exitCode": 0 -│ │ │ │ } -│ │ │ │ } -│ │ │ │ metadata: { -│ │ │ │ "flue.session": "task:skill:", -│ │ │ │ "flue.tool_name": "bash", -│ │ │ │ "provider": "flue" -│ │ │ │ } -│ │ │ │ metrics: { -│ │ │ │ "duration_ms": 0 -│ │ │ │ } -│ │ │ └── flue.turn [llm] -│ │ │ input: [ -│ │ │ { -│ │ │ "content": [ -│ │ │ { -│ │ │ "arguments": { -│ │ │ "command": "printf SKILL_DONE", -│ │ │ "timeout": 100000 -│ │ │ }, -│ │ │ "id": "", -│ │ │ "name": "bash", -│ │ │ "type": "toolCall" -│ │ │ } -│ │ │ ], -│ │ │ "role": "assistant" -│ │ │ }, -│ │ │ { -│ │ │ "content": [ -│ │ │ { -│ │ │ "text": "SKILL_DONE", -│ │ │ "type": "text" -│ │ │ } -│ │ │ ], -│ │ │ "isError": false, -│ │ │ "role": "toolResult", -│ │ │ "toolCallId": "", -│ │ │ "toolName": "bash" -│ │ │ } -│ │ │ ] -│ │ │ output: { -│ │ │ "content": [ -│ │ │ { -│ │ │ "text": "SKILL_DONE", -│ │ │ "textSignature": "{\"v\":1,\"id\":\"msg_054a16a2f2ea3217016a705b70a41481a38ece40ecddf69512\",\"phase\":\"final_answer\"}", -│ │ │ "type": "text" -│ │ │ } -│ │ │ ], -│ │ │ "role": "assistant" -│ │ │ } -│ │ │ metadata: { -│ │ │ "flue.api": "openai-responses", -│ │ │ "flue.input_message_offset": 17, -│ │ │ "flue.input_mode": "delta", -│ │ │ "flue.model": "gpt-5.4-nano", -│ │ │ "flue.provider": "openai", -│ │ │ "flue.session": "task:skill:", -│ │ │ "flue.stop_reason": "stop", -│ │ │ "flue.turn_purpose": "agent", -│ │ │ "model": "gpt-5.4-nano", -│ │ │ "provider": "openai" -│ │ │ } -│ │ │ metrics: { -│ │ │ "completion_tokens": 7, -│ │ │ "duration_ms": 0, -│ │ │ "estimated_cost": 0.00031035000000000004, -│ │ │ "prompt_cache_creation_tokens": 0, -│ │ │ "prompt_cached_tokens": 2560, -│ │ │ "prompt_tokens": 3812, -│ │ │ "tokens": 3819 -│ │ │ } -│ │ ├── tool:task [tool] -│ │ │ input: { -│ │ │ "cwd": ".", -│ │ │ "description": "Run the deterministic Flue instrumentation e2e test skill e2e-flue-skill.", -│ │ │ "prompt": "Run the skill named \"e2e-flue-skill\" with arguments {\"marker\":\"SKILL_DONE\"}. Output only the marker and no extra text when finished." -│ │ │ } -│ │ │ output: { -│ │ │ "content": [ -│ │ │ { -│ │ │ "text": "SKILL_DONE", -│ │ │ "type": "text" -│ │ │ } -│ │ │ ], -│ │ │ "details": { -│ │ │ "cwd": ".", -│ │ │ "messageId": "", -│ │ │ "session": "task:skill:", -│ │ │ "taskId": "" -│ │ │ } -│ │ │ } -│ │ │ metadata: { -│ │ │ "flue.session": "skill", -│ │ │ "flue.tool_name": "task", -│ │ │ "provider": "flue" -│ │ │ } -│ │ │ metrics: { -│ │ │ "duration_ms": 0 -│ │ │ } -│ │ └── flue.turn [llm] -│ │ input: [ -│ │ { -│ │ "content": [ -│ │ { -│ │ "text": "Run the skill named \"e2e-flue-skill\".\n\nArguments:\n{\n \"marker\": \"SKILL_DONE\"\n}", -│ │ "type": "text" -│ │ } -│ │ ], -│ │ "role": "user" -│ │ }, -│ │ { -│ │ "content": [ -│ │ { -│ │ "arguments": { -│ │ "cwd": ".", -│ │ "description": "Run the deterministic Flue instrumentation e2e test skill e2e-flue-skill.", -│ │ "prompt": "Run the skill named \"e2e-flue-skill\" with arguments {\"marker\":\"SKILL_DONE\"}. Output only the marker and no extra text when finished." -│ │ }, -│ │ "id": "", -│ │ "name": "task", -│ │ "type": "toolCall" -│ │ } -│ │ ], -│ │ "role": "assistant" -│ │ }, -│ │ { -│ │ "content": [ -│ │ { -│ │ "text": "SKILL_DONE", -│ │ "type": "text" -│ │ } -│ │ ], -│ │ "isError": false, -│ │ "role": "toolResult", -│ │ "toolCallId": "", -│ │ "toolName": "task" -│ │ } -│ │ ] -│ │ output: { -│ │ "content": [ -│ │ { -│ │ "text": "SKILL_DONE", -│ │ "textSignature": "{\"v\":1,\"id\":\"msg_0f099b3fce0721fd016a705b71558881a38c53a767e09d9e31\",\"phase\":\"final_answer\"}", -│ │ "type": "text" -│ │ } -│ │ ], -│ │ "role": "assistant" -│ │ } -│ │ metadata: { -│ │ "flue.api": "openai-responses", -│ │ "flue.model": "gpt-5.4-nano", -│ │ "flue.provider": "openai", -│ │ "flue.session": "skill", -│ │ "flue.stop_reason": "stop", -│ │ "flue.turn_purpose": "agent", -│ │ "model": "gpt-5.4-nano", -│ │ "provider": "openai" -│ │ } -│ │ metrics: { -│ │ "completion_tokens": 7, -│ │ "duration_ms": 0, -│ │ "estimated_cost": 0.00020255000000000002, -│ │ "prompt_cache_creation_tokens": 0, -│ │ "prompt_cached_tokens": 0, -│ │ "prompt_tokens": 969, -│ │ "tokens": 976 -│ │ } -│ ├── flue.task [task] -│ │ input: "Reply with exactly TASK_DONE and no other text." -│ │ output: "TASK_DONE" -│ │ metadata: { -│ │ "flue.session": "task", -│ │ "provider": "flue" -│ │ } -│ │ metrics: { -│ │ "duration_ms": 0 -│ │ } -│ │ └── flue.turn [llm] -│ │ input: [ -│ │ { -│ │ "content": [ -│ │ { -│ │ "text": "Reply with exactly TASK_DONE and no other text.", -│ │ "type": "text" -│ │ } -│ │ ], -│ │ "role": "user" -│ │ } -│ │ ] -│ │ output: { -│ │ "content": [ -│ │ { -│ │ "text": "TASK_DONE", -│ │ "textSignature": "{\"v\":1,\"id\":\"msg_0f3adfdb1510c165016a705b72843c81a0b30d242da683a882\"}", -│ │ "type": "text" -│ │ } -│ │ ], -│ │ "role": "assistant" -│ │ } -│ │ metadata: { -│ │ "flue.api": "openai-responses", -│ │ "flue.input_mode": "full", -│ │ "flue.model": "gpt-4o-mini", -│ │ "flue.provider": "openai", -│ │ "flue.session": "task:task:", -│ │ "flue.stop_reason": "stop", -│ │ "flue.turn_purpose": "agent", -│ │ "model": "gpt-4o-mini", -│ │ "provider": "openai" -│ │ } -│ │ metrics: { -│ │ "completion_tokens": 4, -│ │ "duration_ms": 0, -│ │ "estimated_cost": 0.0001263, -│ │ "prompt_cache_creation_tokens": 0, -│ │ "prompt_cached_tokens": 0, -│ │ "prompt_tokens": 826, -│ │ "tokens": 830 -│ │ } -│ └── flue.compact [task] -│ input: { -│ "estimatedTokens": 1229, -│ "reason": "manual" -│ } -│ output: { -│ "completed": true -│ } -│ metadata: { -│ "flue.operation": "compact", -│ "flue.session": "main", -│ "flue.workflow_name": "instrumentation", -│ "provider": "flue", -│ "scenario": "flue-instrumentation" -│ } -│ metrics: { -│ "duration_ms": 0 -│ } -│ └── compaction:manual [task] -│ input: { -│ "estimatedTokens": 1229, -│ "reason": "manual" -│ } -│ output: { -│ "messagesAfter": 2, -│ "messagesBefore": 8 -│ } -│ metadata: { -│ "flue.compaction_reason": "manual", -│ "flue.session": "main", -│ "provider": "flue" -│ } -│ metrics: { -│ "duration_ms": 0, -│ "messages_after": 2, -│ "messages_before": 8 -│ } -│ └── flue.turn [llm] -│ input: [ -│ { -│ "content": [ -│ { -│ "text": "\n[User]: Complete this instrumented research flow. Call exactly one tool per turn and wait for each tool result before choosing the next tool. Step 1: call lookup with query \"flue instrumentation\". Step 2: use the lookup result id as lookupId and call web_search with query \"Braintrust Flue reasoning stream instrumentation\". Step 3: use the first web_search result url and call summarize_source. After summarize_source returns, reply with exactly PROMPT_DONE and no other text.\n\n[Assistant tool calls]: lookup(query=\"flue instrumentation\")\n\n[Tool result]: {\"id\":\"flue-session-2026\",\"query\":\"flue instrumentation\",\"topic\":\"session instrumentation\"}\n\n[Assistant tool calls]: web_search(lookupId=\"flue-session-2026\", query=\"Braintrust Flue reasoning stream instrumentation\")\n\n[Tool result]: {\"lookupId\":\"flue-session-2026\",\"query\":\"Braintrust Flue reasoning stream instrumentation\",\"results\":[{\"title\":\"Flue reasoning stream instrumentation\",\"url\":\"https://example.test/flue/reasoning-streams\"}]}\n\n[Assistant tool calls]: summarize_source(url=\"https://example.test/flue/reasoning-streams\")\n\n[Tool result]: {\"summary\":\"Flue emits reasoning, tool execution, and LLM turn events separately.\",\"url\":\"https://example.test/flue/reasoning-streams\"}\n\n\nThis is the PREFIX of a turn that was too large to keep. The SUFFIX (recent work) is retained.\n\nSummarize the prefix to provide context for the retained suffix:\n\n## Original Request\n[What did the user ask for in this turn?]\n\n## Early Progress\n- [Key decisions and work done in the prefix]\n\n## Context for Suffix\n- [Information needed to understand the retained recent work]\n\nBe concise. Focus on what's needed to understand the kept suffix.", -│ "type": "text" -│ } -│ ], -│ "role": "user" -│ } -│ ] -│ output: { -│ "content": [ -│ { -│ "text": "## Original Request\nThe user requested a step-by-step execution of an instrumented research flow involving tool calls to gather information on \"flue instrumentation\" and \"", -│ "textSignature": "{\"v\":1,\"id\":\"msg_04d28ba03840bb65016a705b7358a081a38e69464057840036\"}", -│ "type": "text" -│ } -│ ], -│ "role": "assistant" -│ } -│ metadata: { -│ "flue.api": "openai-responses", -│ "flue.model": "gpt-4o-mini", -│ "flue.provider": "openai", -│ "flue.session": "main", -│ "flue.stop_reason": "stop", -│ "flue.turn_purpose": "compaction_prefix", -│ "model": "gpt-4o-mini", -│ "provider": "openai" -│ } -│ metrics: { -│ "completion_tokens": 0, -│ "duration_ms": 0, -│ "estimated_cost": 0, -│ "prompt_cache_creation_tokens": 0, -│ "prompt_cached_tokens": 0, -│ "prompt_tokens": 0, -│ "tokens": 0 -│ } -└── flue.prompt [task] - input: [ - { - "content": [ - { - "text": "Complete this instrumented research flow. Call exactly one tool per turn and wait for each tool result before choosing the next tool. Step 1: call lookup with query \"flue instrumentation\". Step 2: use the lookup result id as lookupId and call web_search with query \"Braintrust Flue reasoning stream instrumentation\". Step 3: use the first web_search result url and call summarize_source. After summarize_source returns, reply with exactly PROMPT_DONE and no other text.", - "type": "text" - } - ], - "role": "user" - } - ] - output: "PROMPT_DONE" - metadata: { - "flue.operation": "prompt", - "flue.session": "main", - "flue.workflow_name": "instrumentation", - "provider": "flue", - "scenario": "flue-instrumentation" - } - metrics: { - "duration_ms": 0 - } - ├── flue.turn [llm] - │ input: [ - │ { - │ "content": [ - │ { - │ "text": "Complete this instrumented research flow. Call exactly one tool per turn and wait for each tool result before choosing the next tool. Step 1: call lookup with query \"flue instrumentation\". Step 2: use the lookup result id as lookupId and call web_search with query \"Braintrust Flue reasoning stream instrumentation\". Step 3: use the first web_search result url and call summarize_source. After summarize_source returns, reply with exactly PROMPT_DONE and no other text.", - │ "type": "text" - │ } - │ ], - │ "role": "user" - │ } - │ ] - │ output: { - │ "content": [ - │ { - │ "arguments": { - │ "query": "flue instrumentation" - │ }, - │ "id": "", - │ "name": "lookup", - │ "type": "toolCall" - │ } - │ ], - │ "role": "assistant" - │ } - │ metadata: { - │ "flue.api": "openai-responses", - │ "flue.input_mode": "full", - │ "flue.model": "gpt-5.4-nano", - │ "flue.provider": "openai", - │ "flue.session": "main", - │ "flue.stop_reason": "toolUse", - │ "flue.turn_purpose": "agent", - │ "model": "gpt-5.4-nano", - │ "provider": "openai" - │ } - │ metrics: { - │ "completion_tokens": 19, - │ "duration_ms": 0, - │ "estimated_cost": 0.00022475000000000001, - │ "prompt_cache_creation_tokens": 0, - │ "prompt_cached_tokens": 0, - │ "prompt_tokens": 1005, - │ "tokens": 1024 - │ } - ├── tool:lookup [tool] - │ input: { - │ "query": "flue instrumentation" - │ } - │ output: { - │ "content": [ - │ { - │ "text": "{\"id\":\"flue-session-2026\",\"query\":\"flue instrumentation\",\"topic\":\"session instrumentation\"}", - │ "type": "text" - │ } - │ ], - │ "details": { - │ "customTool": "lookup" - │ } - │ } - │ metadata: { - │ "flue.session": "main", - │ "flue.tool_name": "lookup", - │ "provider": "flue" - │ } - │ metrics: { - │ "duration_ms": 0 - │ } - ├── flue.turn [llm] - │ input: [ - │ { - │ "content": [ - │ { - │ "arguments": { - │ "query": "flue instrumentation" - │ }, - │ "id": "", - │ "name": "lookup", - │ "type": "toolCall" - │ } - │ ], - │ "role": "assistant" - │ }, - │ { - │ "content": [ - │ { - │ "text": "{\"id\":\"flue-session-2026\",\"query\":\"flue instrumentation\",\"topic\":\"session instrumentation\"}", - │ "type": "text" - │ } - │ ], - │ "isError": false, - │ "role": "toolResult", - │ "toolCallId": "", - │ "toolName": "lookup" - │ } - │ ] - │ output: { - │ "content": [ - │ { - │ "arguments": { - │ "lookupId": "flue-session-2026", - │ "query": "Braintrust Flue reasoning stream instrumentation" - │ }, - │ "id": "", - │ "name": "web_search", - │ "type": "toolCall" - │ } - │ ], - │ "role": "assistant" - │ } - │ metadata: { - │ "flue.api": "openai-responses", - │ "flue.input_message_offset": 1, - │ "flue.input_mode": "delta", - │ "flue.model": "gpt-5.4-nano", - │ "flue.provider": "openai", - │ "flue.session": "main", - │ "flue.stop_reason": "toolUse", - │ "flue.turn_purpose": "agent", - │ "model": "gpt-5.4-nano", - │ "provider": "openai" - │ } - │ metrics: { - │ "completion_tokens": 34, - │ "duration_ms": 0, - │ "estimated_cost": 0.00025350000000000004, - │ "prompt_cache_creation_tokens": 0, - │ "prompt_cached_tokens": 0, - │ "prompt_tokens": 1055, - │ "tokens": 1089 - │ } - ├── tool:web_search [tool] - │ input: { - │ "lookupId": "flue-session-2026", - │ "query": "Braintrust Flue reasoning stream instrumentation" - │ } - │ output: { - │ "content": [ - │ { - │ "text": "{\"lookupId\":\"flue-session-2026\",\"query\":\"Braintrust Flue reasoning stream instrumentation\",\"results\":[{\"title\":\"Flue reasoning stream instrumentation\",\"url\":\"https://example.test/flue/reasoning-streams\"}]}", - │ "type": "text" - │ } - │ ], - │ "details": { - │ "customTool": "web_search" - │ } - │ } - │ metadata: { - │ "flue.session": "main", - │ "flue.tool_name": "web_search", - │ "provider": "flue" - │ } - │ metrics: { - │ "duration_ms": 0 - │ } - ├── flue.turn [llm] - │ input: [ - │ { - │ "content": [ - │ { - │ "arguments": { - │ "lookupId": "flue-session-2026", - │ "query": "Braintrust Flue reasoning stream instrumentation" - │ }, - │ "id": "", - │ "name": "web_search", - │ "type": "toolCall" - │ } - │ ], - │ "role": "assistant" - │ }, - │ { - │ "content": [ - │ { - │ "text": "{\"lookupId\":\"flue-session-2026\",\"query\":\"Braintrust Flue reasoning stream instrumentation\",\"results\":[{\"title\":\"Flue reasoning stream instrumentation\",\"url\":\"https://example.test/flue/reasoning-streams\"}]}", - │ "type": "text" - │ } - │ ], - │ "isError": false, - │ "role": "toolResult", - │ "toolCallId": "", - │ "toolName": "web_search" - │ } - │ ] - │ output: { - │ "content": [ - │ { - │ "arguments": { - │ "url": "https://example.test/flue/reasoning-streams" - │ }, - │ "id": "", - │ "name": "summarize_source", - │ "type": "toolCall" - │ } - │ ], - │ "role": "assistant" - │ } - │ metadata: { - │ "flue.api": "openai-responses", - │ "flue.input_message_offset": 3, - │ "flue.input_mode": "delta", - │ "flue.model": "gpt-5.4-nano", - │ "flue.provider": "openai", - │ "flue.session": "main", - │ "flue.stop_reason": "toolUse", - │ "flue.turn_purpose": "agent", - │ "model": "gpt-5.4-nano", - │ "provider": "openai" - │ } - │ metrics: { - │ "completion_tokens": 30, - │ "duration_ms": 0, - │ "estimated_cost": 0.00026690000000000004, - │ "prompt_cache_creation_tokens": 0, - │ "prompt_cached_tokens": 0, - │ "prompt_tokens": 1147, - │ "tokens": 1177 - │ } - ├── tool:summarize_source [tool] - │ input: { - │ "url": "https://example.test/flue/reasoning-streams" - │ } - │ output: { - │ "content": [ - │ { - │ "text": "{\"summary\":\"Flue emits reasoning, tool execution, and LLM turn events separately.\",\"url\":\"https://example.test/flue/reasoning-streams\"}", - │ "type": "text" - │ } - │ ], - │ "details": { - │ "customTool": "summarize_source" - │ } - │ } - │ metadata: { - │ "flue.session": "main", - │ "flue.tool_name": "summarize_source", - │ "provider": "flue" - │ } - │ metrics: { - │ "duration_ms": 0 - │ } - └── flue.turn [llm] - input: [ - { - "content": [ - { - "arguments": { - "url": "https://example.test/flue/reasoning-streams" - }, - "id": "", - "name": "summarize_source", - "type": "toolCall" - } - ], - "role": "assistant" - }, - { - "content": [ - { - "text": "{\"summary\":\"Flue emits reasoning, tool execution, and LLM turn events separately.\",\"url\":\"https://example.test/flue/reasoning-streams\"}", - "type": "text" - } - ], - "isError": false, - "role": "toolResult", - "toolCallId": "", - "toolName": "summarize_source" - } - ] - output: { - "content": [ - { - "text": "PROMPT_DONE", - "textSignature": "{\"v\":1,\"id\":\"msg_045458052dc10379016a705b62dbd8819189b8287061debf12\",\"phase\":\"final_answer\"}", - "type": "text" - } - ], - "role": "assistant" - } - metadata: { - "flue.api": "openai-responses", - "flue.input_message_offset": 5, - "flue.input_mode": "delta", - "flue.model": "gpt-5.4-nano", - "flue.provider": "openai", - "flue.session": "main", - "flue.stop_reason": "stop", - "flue.turn_purpose": "agent", - "model": "gpt-5.4-nano", - "provider": "openai" - } - metrics: { - "completion_tokens": 7, - "duration_ms": 0, - "estimated_cost": 0.00025315000000000005, - "prompt_cache_creation_tokens": 0, - "prompt_cached_tokens": 0, - "prompt_tokens": 1222, - "tokens": 1229 - } diff --git a/e2e/scenarios/flue-instrumentation/__snapshots__/flue-v1-0-0-beta-3-explicit.span-tree.json b/e2e/scenarios/flue-instrumentation/__snapshots__/flue-v1-0-0-beta-3-explicit.span-tree.json index a71990916..75749a2e9 100644 --- a/e2e/scenarios/flue-instrumentation/__snapshots__/flue-v1-0-0-beta-3-explicit.span-tree.json +++ b/e2e/scenarios/flue-instrumentation/__snapshots__/flue-v1-0-0-beta-3-explicit.span-tree.json @@ -75,7 +75,7 @@ "input": { "name": "e2e-flue-skill" }, - "output": "Run the skill named \"e2e-flue-skill\".\n\n\nReturn the marker from the provided args exactly once. Output no other text.\n\n\nSupporting skill resources are available relative to this workspace skill directory but are not loaded into context unless needed:\n\n- Base directory: /e2e/scenarios/flue-v1/.agents/skills/e2e-flue-skill\n- Resolve relative resource paths from this directory and read only the files you need.\n", + "output": "Run the skill named \"e2e-flue-skill\".\n\n\nReturn the marker from the provided args exactly once. Output no other text.\n\n\nSupporting skill resources are available relative to this workspace skill directory but are not loaded into context unless needed:\n\n- Base directory: /e2e/scenarios/flue-instrumentation/.agents/skills/e2e-flue-skill\n- Resolve relative resource paths from this directory and read only the files you need.\n", "metadata": { "flue.session": "skill", "flue.tool_name": "activate_skill", @@ -119,7 +119,7 @@ { "content": [ { - "text": "Run the skill named \"e2e-flue-skill\".\n\n\nReturn the marker from the provided args exactly once. Output no other text.\n\n\nSupporting skill resources are available relative to this workspace skill directory but are not loaded into context unless needed:\n\n- Base directory: /e2e/scenarios/flue-v1/.agents/skills/e2e-flue-skill\n- Resolve relative resource paths from this directory and read only the files you need.\n", + "text": "Run the skill named \"e2e-flue-skill\".\n\n\nReturn the marker from the provided args exactly once. Output no other text.\n\n\nSupporting skill resources are available relative to this workspace skill directory but are not loaded into context unless needed:\n\n- Base directory: /e2e/scenarios/flue-instrumentation/.agents/skills/e2e-flue-skill\n- Resolve relative resource paths from this directory and read only the files you need.\n", "type": "text" } ], diff --git a/e2e/scenarios/flue-instrumentation/__snapshots__/flue-v1-0-0-beta-3-explicit.span-tree.txt b/e2e/scenarios/flue-instrumentation/__snapshots__/flue-v1-0-0-beta-3-explicit.span-tree.txt index 5f4039a0b..a53d046f7 100644 --- a/e2e/scenarios/flue-instrumentation/__snapshots__/flue-v1-0-0-beta-3-explicit.span-tree.txt +++ b/e2e/scenarios/flue-instrumentation/__snapshots__/flue-v1-0-0-beta-3-explicit.span-tree.txt @@ -99,7 +99,7 @@ span_tree: │ │ │ input: { │ │ │ "name": "e2e-flue-skill" │ │ │ } -│ │ │ output: "Run the skill named \"e2e-flue-skill\".\n\n\nReturn the marker from the provided args exactly once. Output no other text.\n\n\nSupporting skill resources are available relative to this workspace skill directory but are not loaded into context unless needed:\n\n- Base directory: /e2e/scenarios/flue-v1/.agents/skills/e2e-flue-skill\n- Resolve relative resource paths from this directory and read only the files you need.\n" +│ │ │ output: "Run the skill named \"e2e-flue-skill\".\n\n\nReturn the marker from the provided args exactly once. Output no other text.\n\n\nSupporting skill resources are available relative to this workspace skill directory but are not loaded into context unless needed:\n\n- Base directory: /e2e/scenarios/flue-instrumentation/.agents/skills/e2e-flue-skill\n- Resolve relative resource paths from this directory and read only the files you need.\n" │ │ │ metadata: { │ │ │ "flue.session": "skill", │ │ │ "flue.tool_name": "activate_skill", @@ -139,7 +139,7 @@ span_tree: │ │ { │ │ "content": [ │ │ { -│ │ "text": "Run the skill named \"e2e-flue-skill\".\n\n\nReturn the marker from the provided args exactly once. Output no other text.\n\n\nSupporting skill resources are available relative to this workspace skill directory but are not loaded into context unless needed:\n\n- Base directory: /e2e/scenarios/flue-v1/.agents/skills/e2e-flue-skill\n- Resolve relative resource paths from this directory and read only the files you need.\n", +│ │ "text": "Run the skill named \"e2e-flue-skill\".\n\n\nReturn the marker from the provided args exactly once. Output no other text.\n\n\nSupporting skill resources are available relative to this workspace skill directory but are not loaded into context unless needed:\n\n- Base directory: /e2e/scenarios/flue-instrumentation/.agents/skills/e2e-flue-skill\n- Resolve relative resource paths from this directory and read only the files you need.\n", │ │ "type": "text" │ │ } │ │ ], diff --git a/e2e/scenarios/flue-instrumentation/__snapshots__/flue-v1-latest-explicit.span-tree.json b/e2e/scenarios/flue-instrumentation/__snapshots__/flue-v1-latest-explicit.span-tree.json index ac0646db1..b4336fbab 100644 --- a/e2e/scenarios/flue-instrumentation/__snapshots__/flue-v1-latest-explicit.span-tree.json +++ b/e2e/scenarios/flue-instrumentation/__snapshots__/flue-v1-latest-explicit.span-tree.json @@ -71,7 +71,7 @@ "input": { "name": "e2e-flue-skill" }, - "output": "Run the skill named \"e2e-flue-skill\".\n\n\nReturn the marker from the provided args exactly once. Output no other text.\n\n\nSupporting skill resources are available relative to this workspace skill directory but are not loaded into context unless needed:\n\n- Base directory: /e2e/scenarios/flue-v1/.agents/skills/e2e-flue-skill\n- Resolve relative resource paths from this directory and read only the files you need.\n", + "output": "Run the skill named \"e2e-flue-skill\".\n\n\nReturn the marker from the provided args exactly once. Output no other text.\n\n\nSupporting skill resources are available relative to this workspace skill directory but are not loaded into context unless needed:\n\n- Base directory: /e2e/scenarios/flue-instrumentation/.agents/skills/e2e-flue-skill\n- Resolve relative resource paths from this directory and read only the files you need.\n", "metadata": { "flue.session": "skill", "flue.tool_name": "activate_skill", @@ -111,7 +111,7 @@ { "content": [ { - "text": "Run the skill named \"e2e-flue-skill\".\n\n\nReturn the marker from the provided args exactly once. Output no other text.\n\n\nSupporting skill resources are available relative to this workspace skill directory but are not loaded into context unless needed:\n\n- Base directory: /e2e/scenarios/flue-v1/.agents/skills/e2e-flue-skill\n- Resolve relative resource paths from this directory and read only the files you need.\n", + "text": "Run the skill named \"e2e-flue-skill\".\n\n\nReturn the marker from the provided args exactly once. Output no other text.\n\n\nSupporting skill resources are available relative to this workspace skill directory but are not loaded into context unless needed:\n\n- Base directory: /e2e/scenarios/flue-instrumentation/.agents/skills/e2e-flue-skill\n- Resolve relative resource paths from this directory and read only the files you need.\n", "type": "text" } ], diff --git a/e2e/scenarios/flue-instrumentation/__snapshots__/flue-v1-latest-explicit.span-tree.txt b/e2e/scenarios/flue-instrumentation/__snapshots__/flue-v1-latest-explicit.span-tree.txt index 2a17573af..dd492f42e 100644 --- a/e2e/scenarios/flue-instrumentation/__snapshots__/flue-v1-latest-explicit.span-tree.txt +++ b/e2e/scenarios/flue-instrumentation/__snapshots__/flue-v1-latest-explicit.span-tree.txt @@ -95,7 +95,7 @@ span_tree: │ │ │ input: { │ │ │ "name": "e2e-flue-skill" │ │ │ } -│ │ │ output: "Run the skill named \"e2e-flue-skill\".\n\n\nReturn the marker from the provided args exactly once. Output no other text.\n\n\nSupporting skill resources are available relative to this workspace skill directory but are not loaded into context unless needed:\n\n- Base directory: /e2e/scenarios/flue-v1/.agents/skills/e2e-flue-skill\n- Resolve relative resource paths from this directory and read only the files you need.\n" +│ │ │ output: "Run the skill named \"e2e-flue-skill\".\n\n\nReturn the marker from the provided args exactly once. Output no other text.\n\n\nSupporting skill resources are available relative to this workspace skill directory but are not loaded into context unless needed:\n\n- Base directory: /e2e/scenarios/flue-instrumentation/.agents/skills/e2e-flue-skill\n- Resolve relative resource paths from this directory and read only the files you need.\n" │ │ │ metadata: { │ │ │ "flue.session": "skill", │ │ │ "flue.tool_name": "activate_skill", @@ -131,7 +131,7 @@ span_tree: │ │ { │ │ "content": [ │ │ { -│ │ "text": "Run the skill named \"e2e-flue-skill\".\n\n\nReturn the marker from the provided args exactly once. Output no other text.\n\n\nSupporting skill resources are available relative to this workspace skill directory but are not loaded into context unless needed:\n\n- Base directory: /e2e/scenarios/flue-v1/.agents/skills/e2e-flue-skill\n- Resolve relative resource paths from this directory and read only the files you need.\n", +│ │ "text": "Run the skill named \"e2e-flue-skill\".\n\n\nReturn the marker from the provided args exactly once. Output no other text.\n\n\nSupporting skill resources are available relative to this workspace skill directory but are not loaded into context unless needed:\n\n- Base directory: /e2e/scenarios/flue-instrumentation/.agents/skills/e2e-flue-skill\n- Resolve relative resource paths from this directory and read only the files you need.\n", │ │ "type": "text" │ │ } │ │ ], diff --git a/e2e/scenarios/flue-instrumentation/assertions.ts b/e2e/scenarios/flue-instrumentation/assertions.ts index 53a597fbd..dfb7afbe6 100644 --- a/e2e/scenarios/flue-instrumentation/assertions.ts +++ b/e2e/scenarios/flue-instrumentation/assertions.ts @@ -1,4 +1,4 @@ -import { beforeAll, describe, expect, test } from "vitest"; +import { beforeAll, describe, expect, test } from "vite-plus/test"; import type { CapturedLogEvent } from "../../helpers/mock-braintrust-server"; import { resolveFileSnapshotPath } from "../../helpers/file-snapshot"; import { @@ -20,14 +20,6 @@ import { import { SCENARIO_NAME } from "./constants.mjs"; type RunFlueScenario = (harness: { - runNodeScenarioDir: (options: { - entry: string; - env?: Record; - nodeArgs?: string[]; - runContext?: ScenarioRunContext; - scenarioDir: string; - timeoutMs: number; - }) => Promise; runScenarioDir: (options: { entry: string; env?: Record; @@ -155,40 +147,24 @@ function firstSpanIndex( return index === -1 ? Number.MAX_SAFE_INTEGER : index; } -function findFlueDescendants( - events: CapturedLogEvent[], - flueSpan: CapturedLogEvent | undefined, - predicate: (event: CapturedLogEvent) => boolean, -): CapturedLogEvent[] { - return findMatchingDescendants(events, flueSpan, predicate); -} - -function isFlueChildSpan( - event: CapturedLogEvent, - includeAmbientProbeSpans = true, -): boolean { +function isFlueChildSpan(event: CapturedLogEvent): boolean { return ( event.span.name === "flue.turn" || event.span.name?.startsWith("tool:") === true || event.span.name?.startsWith("task:") === true || event.span.name?.startsWith("compaction:") === true || - (includeAmbientProbeSpans && event.span.name === "flue.toolCurrentProbe") || + event.span.name === "flue.toolCurrentProbe" || event.span.name === "flue.task" ); } -function buildSpanTree( - events: CapturedLogEvent[], - includeAmbientProbeSpans: boolean, -): SpanTreeEntry[] { +function buildSpanTree(events: CapturedLogEvent[]): SpanTreeEntry[] { const workflow = findLatestSpanByPrefix(events, "workflow:"); - const workflowCurrentProbe = includeAmbientProbeSpans - ? findLatestChildSpan( - events, - "flue.workflowCurrentProbe", - workflow?.span.id, - ) - : undefined; + const workflowCurrentProbe = findLatestChildSpan( + events, + "flue.workflowCurrentProbe", + workflow?.span.id, + ); const promptSpan = findFlueOperation(events, "flue.prompt"); const skillSpan = findFlueOperation(events, "flue.skill"); const taskSpan = findFlueOperation(events, "flue.task"); @@ -198,28 +174,19 @@ function buildSpanTree( workflow, workflowCurrentProbe, promptSpan, - ...findFlueDescendants(events, promptSpan, (event) => - isFlueChildSpan(event, includeAmbientProbeSpans), - ), + ...findMatchingDescendants(events, promptSpan, isFlueChildSpan), skillSpan, - ...findFlueDescendants(events, skillSpan, (event) => - isFlueChildSpan(event, includeAmbientProbeSpans), - ), + ...findMatchingDescendants(events, skillSpan, isFlueChildSpan), taskSpan, - ...findFlueDescendants(events, taskSpan, (event) => - isFlueChildSpan(event, includeAmbientProbeSpans), - ), + ...findMatchingDescendants(events, taskSpan, isFlueChildSpan), compactSpan, - ...findFlueDescendants(events, compactSpan, (event) => - isFlueChildSpan(event, includeAmbientProbeSpans), - ), + ...findMatchingDescendants(events, compactSpan, isFlueChildSpan), ].flatMap((event) => event ? [{ event, fields: snapshotFields(event) }] : [], ); } export function defineFlueInstrumentationAssertions(options: { - expectAmbientContext?: boolean; name: string; runScenario: RunFlueScenario; snapshotName: string; @@ -232,9 +199,8 @@ export function defineFlueInstrumentationAssertions(options: { ); const timeoutMs = effectiveScenarioTimeoutMs(options.timeoutMs); const testConfig = { timeout: timeoutMs }; - const expectAmbientContext = options.expectAmbientContext ?? true; - describe.sequential(options.name, () => { + describe(options.name, { concurrent: false }, () => { let events: CapturedLogEvent[] = []; beforeAll(async () => { @@ -257,46 +223,42 @@ export function defineFlueInstrumentationAssertions(options: { }); }); - if (expectAmbientContext) { - test( - "makes the Flue workflow span current for app spans", - testConfig, - () => { - const workflow = findLatestSpanByPrefix(events, "workflow:"); - const appSpan = findLatestChildSpan( - events, - "flue.workflowCurrentProbe", - workflow?.span.id, - ); - - expect(appSpan).toBeDefined(); - expect(appSpan?.span.parentIds).toEqual([workflow?.span.id]); - expect(appSpan?.output).toBe("active"); - }, - ); - - test("makes Flue tool spans current for app spans", testConfig, () => { + test( + "makes the Flue workflow span current for app spans", + testConfig, + () => { const workflow = findLatestSpanByPrefix(events, "workflow:"); - const promptSpan = findFlueOperation(events, "flue.prompt"); - const lookupToolSpan = findFlueDescendants( - events, - promptSpan, - (event) => event.span.name === "tool:lookup", - )[0]; const appSpan = findLatestChildSpan( events, - "flue.toolCurrentProbe", - lookupToolSpan?.span.id, + "flue.workflowCurrentProbe", + workflow?.span.id, ); expect(appSpan).toBeDefined(); - expect(appSpan?.span.parentIds).toEqual([lookupToolSpan?.span.id]); - expect(appSpan?.output).toBe("lookup-active"); - expect(workflow).toBeDefined(); - }); - } + expect(appSpan?.span.parentIds).toEqual([workflow?.span.id]); + expect(appSpan?.output).toBe("active"); + }, + ); + + test("makes Flue tool spans current for app spans", testConfig, () => { + const promptSpan = findFlueOperation(events, "flue.prompt"); + const lookupToolSpan = findMatchingDescendants( + events, + promptSpan, + (event) => event.span.name === "tool:lookup", + )[0]; + const appSpan = findLatestChildSpan( + events, + "flue.toolCurrentProbe", + lookupToolSpan?.span.id, + ); - test("captures observe-based Flue operation spans", testConfig, () => { + expect(appSpan).toBeDefined(); + expect(appSpan?.span.parentIds).toEqual([lookupToolSpan?.span.id]); + expect(appSpan?.output).toBe("lookup-active"); + }); + + test("captures Flue operation spans", testConfig, () => { for (const flueSpanName of [ "flue.prompt", "flue.skill", @@ -334,10 +296,10 @@ export function defineFlueInstrumentationAssertions(options: { testConfig, () => { const promptSpan = findFlueOperation(events, "flue.prompt"); - const promptChildren = findFlueDescendants( + const promptChildren = findMatchingDescendants( events, promptSpan, - (event) => isFlueChildSpan(event, expectAmbientContext), + isFlueChildSpan, ); const promptTurns = promptChildren.filter( (event) => event.span.name === "flue.turn", @@ -349,7 +311,7 @@ export function defineFlueInstrumentationAssertions(options: { const compactSpan = findFlueOperation(events, "flue.compact"); const allLlmSpans = [promptSpan, skillSpan, compactSpan].flatMap( (span) => - findFlueDescendants( + findMatchingDescendants( events, span, (event) => event.span.name === "flue.turn", @@ -359,12 +321,12 @@ export function defineFlueInstrumentationAssertions(options: { (event) => event.span.name === "tool:lookup", ); const taskSpan = findFlueOperation(events, "flue.task"); - const nestedTaskSpans = findFlueDescendants( + const nestedTaskSpans = findMatchingDescendants( events, taskSpan, (event) => event.span.name === "flue.task", ); - const compaction = findFlueDescendants( + const compaction = findMatchingDescendants( events, compactSpan, (event) => event.span.name?.startsWith("compaction:") === true, @@ -414,15 +376,11 @@ export function defineFlueInstrumentationAssertions(options: { }); test("matches the span tree snapshot", testConfig, async () => { - await matchSpanTreeSnapshot( - buildSpanTree(events, expectAmbientContext), - snapshotPath, - { - normalize: { - additionalProviderIdKeys: ["messageId"], - }, + await matchSpanTreeSnapshot(buildSpanTree(events), snapshotPath, { + normalize: { + additionalProviderIdKeys: ["messageId"], }, - ); + }); }); }); } diff --git a/e2e/scenarios/flue-instrumentation/package.json b/e2e/scenarios/flue-instrumentation/package.json index 09c0949e6..8d20cfb75 100644 --- a/e2e/scenarios/flue-instrumentation/package.json +++ b/e2e/scenarios/flue-instrumentation/package.json @@ -5,19 +5,11 @@ "braintrustScenario": { "bump": { "dependencies": { - "flue-cli-v0-8-latest": { - "package": "@flue/cli", - "range": ">=0.8.0 <0.9.0" - }, "flue-cli-v1-latest": { "package": "@flue/cli", "range": ">=1.0.0-beta.3 <2.0.0-0", "allowPrerelease": true }, - "flue-runtime-v0-8-latest": { - "package": "@flue/runtime", - "range": ">=0.8.0 <0.9.0" - }, "flue-runtime-v1-latest": { "package": "@flue/runtime", "range": ">=1.0.0-beta.3 <2.0.0-0", @@ -31,13 +23,9 @@ } }, "dependencies": { - "@flue/cli": "0.8.0", - "@flue/runtime": "0.8.0", - "flue-cli-v0-8-latest": "npm:@flue/cli@0.8.1", - "flue-cli-v1": "npm:@flue/cli@1.0.0-beta.3", + "@flue/cli": "1.0.0-beta.3", + "@flue/runtime": "1.0.0-beta.3", "flue-cli-v1-latest": "npm:@flue/cli@1.0.0-beta.9", - "flue-runtime-v0-8-latest": "npm:@flue/runtime@0.8.1", - "flue-runtime-v1": "npm:@flue/runtime@1.0.0-beta.3", "flue-runtime-v1-latest": "npm:@flue/runtime@1.0.0-beta.9", "flue-runtime-v2": "npm:@flue/runtime@2.0.0", "flue-runtime-v2-latest": "npm:@flue/runtime@2.0.1", @@ -46,10 +34,6 @@ }, "pnpm": { "overrides": { - "@flue/runtime@0.8.0>@earendil-works/pi-agent-core": "0.75.4", - "@flue/runtime@0.8.0>@earendil-works/pi-ai": "0.75.4", - "@flue/runtime@0.8.1>@earendil-works/pi-agent-core": "0.75.4", - "@flue/runtime@0.8.1>@earendil-works/pi-ai": "0.75.4", "@hono/node-server@>=2.0.0 <2.0.10": "^2.0.10", "fast-uri@<3.1.5": "3.1.8", "brace-expansion@>=3.0.0 <5.0.9": "5.0.12", diff --git a/e2e/scenarios/flue-instrumentation/pnpm-lock.yaml b/e2e/scenarios/flue-instrumentation/pnpm-lock.yaml index d0da74ed2..49e5f24ed 100644 --- a/e2e/scenarios/flue-instrumentation/pnpm-lock.yaml +++ b/e2e/scenarios/flue-instrumentation/pnpm-lock.yaml @@ -5,10 +5,6 @@ settings: excludeLinksFromLockfile: false overrides: - '@flue/runtime@0.8.0>@earendil-works/pi-agent-core': 0.75.4 - '@flue/runtime@0.8.0>@earendil-works/pi-ai': 0.75.4 - '@flue/runtime@0.8.1>@earendil-works/pi-agent-core': 0.75.4 - '@flue/runtime@0.8.1>@earendil-works/pi-ai': 0.75.4 '@hono/node-server@>=2.0.0 <2.0.10': ^2.0.10 fast-uri@<3.1.5: 3.1.8 brace-expansion@>=3.0.0 <5.0.9: 5.0.12 @@ -40,38 +36,26 @@ importers: .: dependencies: '@flue/cli': - specifier: 0.8.0 - version: 0.8.0(@standard-schema/spec@1.1.0)(@types/json-schema@7.0.15)(@types/node@25.9.1)(esbuild@0.27.3)(typebox@1.3.7)(workerd@1.20260730.1)(wrangler@4.95.0(@types/node@25.9.1))(yaml@2.9.0)(zod-to-json-schema@3.25.2(zod@4.4.3))(zod@4.4.3) + specifier: 1.0.0-beta.3 + version: 1.0.0-beta.3(@standard-schema/spec@1.1.0)(@types/json-schema@7.0.15)(@types/node@25.9.1)(esbuild@0.27.3)(hono@4.13.7)(typebox@1.3.7)(wrangler@4.95.0(@types/node@25.9.1))(ws@8.21.0)(yaml@2.9.0)(zod-to-json-schema@3.25.2(zod@4.4.3))(zod@4.4.3) '@flue/runtime': - specifier: 0.8.0 - version: 0.8.0(@standard-schema/spec@1.1.0)(@types/json-schema@7.0.15)(typebox@1.3.7)(zod-to-json-schema@3.25.2(zod@4.4.3))(zod@4.4.3) - flue-cli-v0-8-latest: - specifier: npm:@flue/cli@0.8.1 - version: '@flue/cli@0.8.1(@standard-schema/spec@1.1.0)(@types/json-schema@7.0.15)(@types/node@25.9.1)(esbuild@0.27.3)(typebox@1.3.7)(workerd@1.20260730.1)(wrangler@4.95.0(@types/node@25.9.1))(yaml@2.9.0)(zod-to-json-schema@3.25.2(zod@4.4.3))(zod@4.4.3)' - flue-cli-v1: - specifier: npm:@flue/cli@1.0.0-beta.3 - version: '@flue/cli@1.0.0-beta.3(@standard-schema/spec@1.1.0)(@types/json-schema@7.0.15)(@types/node@25.9.1)(esbuild@0.27.3)(hono@4.13.7)(typebox@1.3.7)(wrangler@4.95.0(@types/node@25.9.1))(ws@8.20.1)(yaml@2.9.0)(zod-to-json-schema@3.25.2(zod@4.4.3))(zod@4.4.3)' + specifier: 1.0.0-beta.3 + version: 1.0.0-beta.3(@standard-schema/spec@1.1.0)(@types/json-schema@7.0.15)(typebox@1.3.7)(ws@8.21.0)(zod-to-json-schema@3.25.2(zod@4.4.3))(zod@4.4.3) flue-cli-v1-latest: specifier: npm:@flue/cli@1.0.0-beta.9 - version: '@flue/cli@1.0.0-beta.9(@standard-schema/spec@1.1.0)(@types/json-schema@7.0.15)(@types/node@25.9.1)(esbuild@0.27.3)(hono@4.13.7)(typebox@1.3.7)(wrangler@4.95.0(@types/node@25.9.1))(ws@8.20.1)(yaml@2.9.0)(zod-to-json-schema@3.25.2(zod@4.4.3))(zod@4.4.3)' - flue-runtime-v0-8-latest: - specifier: npm:@flue/runtime@0.8.1 - version: '@flue/runtime@0.8.1(@standard-schema/spec@1.1.0)(@types/json-schema@7.0.15)(typebox@1.3.7)(zod-to-json-schema@3.25.2(zod@4.4.3))(zod@4.4.3)' - flue-runtime-v1: - specifier: npm:@flue/runtime@1.0.0-beta.3 - version: '@flue/runtime@1.0.0-beta.3(@standard-schema/spec@1.1.0)(@types/json-schema@7.0.15)(typebox@1.3.7)(ws@8.20.1)(zod-to-json-schema@3.25.2(zod@4.4.3))(zod@4.4.3)' + version: '@flue/cli@1.0.0-beta.9(@standard-schema/spec@1.1.0)(@types/json-schema@7.0.15)(@types/node@25.9.1)(esbuild@0.27.3)(hono@4.13.7)(typebox@1.3.7)(wrangler@4.95.0(@types/node@25.9.1))(ws@8.21.0)(yaml@2.9.0)(zod-to-json-schema@3.25.2(zod@4.4.3))(zod@4.4.3)' flue-runtime-v1-latest: specifier: npm:@flue/runtime@1.0.0-beta.9 - version: '@flue/runtime@1.0.0-beta.9(@standard-schema/spec@1.1.0)(@types/json-schema@7.0.15)(typebox@1.3.7)(ws@8.20.1)(zod-to-json-schema@3.25.2(zod@4.4.3))(zod@4.4.3)' + version: '@flue/runtime@1.0.0-beta.9(@standard-schema/spec@1.1.0)(@types/json-schema@7.0.15)(typebox@1.3.7)(ws@8.21.0)(zod-to-json-schema@3.25.2(zod@4.4.3))(zod@4.4.3)' flue-runtime-v2: specifier: npm:@flue/runtime@2.0.0 - version: '@flue/runtime@2.0.0(@modelcontextprotocol/sdk@1.31.0(zod@4.4.3))(ws@8.20.1)(zod@4.4.3)' + version: '@flue/runtime@2.0.0(@modelcontextprotocol/sdk@1.31.0(zod@4.4.3))(ws@8.21.0)(zod@4.4.3)' flue-runtime-v2-latest: specifier: npm:@flue/runtime@2.0.1 - version: '@flue/runtime@2.0.1(@modelcontextprotocol/sdk@1.31.0(zod@4.4.3))(ws@8.20.1)(zod@4.4.3)' + version: '@flue/runtime@2.0.1(@modelcontextprotocol/sdk@1.31.0(zod@4.4.3))(ws@8.21.0)(zod@4.4.3)' pi-ai-v2: specifier: npm:@earendil-works/pi-ai@0.83.0 - version: '@earendil-works/pi-ai@0.83.0(@modelcontextprotocol/sdk@1.31.0(zod@4.4.3))(ws@8.20.1)(zod@4.4.3)' + version: '@earendil-works/pi-ai@0.83.0(@modelcontextprotocol/sdk@1.31.0(zod@4.4.3))(ws@8.21.0)(zod@4.4.3)' valibot: specifier: 1.4.2 version: 1.4.2 @@ -208,12 +192,6 @@ packages: workerd: optional: true - '@cloudflare/vite-plugin@1.39.0': - resolution: {integrity: sha512-AHC+KSR+3dtGu7Ab7I0Ode4Whx12TxMEmiZt7w+Fc3/2wYNByIzbb6cndWZ78tnveFdO1xhNLv1YaNngxGtOPg==} - peerDependencies: - vite: ^6.1.0 || ^7.0.0 || ^8.0.0 - wrangler: ^4.95.0 - '@cloudflare/vite-plugin@1.50.0': resolution: {integrity: sha512-zIhZim7Kr7OC22rlOkU81BLj0oRlUOqPRX+E6RU3hyRYg4xU1MbA3yiMIwFONr+jc/uV7Fxs20NlXrB89Fqjqg==} hasBin: true @@ -290,10 +268,6 @@ packages: engines: {node: '>=18.0.0'} hasBin: true - '@earendil-works/pi-agent-core@0.75.4': - resolution: {integrity: sha512-cGYbysb4EqUf0B28OeqFq2ppm1XF3bYBOP71q9dv38yf/UJfzMjiXBeNelrcio+QWIoVrW+xzYm7sMzYIUc9Og==} - engines: {node: '>=22.19.0'} - '@earendil-works/pi-agent-core@0.79.10': resolution: {integrity: sha512-XKxgdjhcPuyjrthCOFSgfzT3xZ1uBrJ1IMVDxci1to6hIN6BIg9J5iY8q0pGXK1DLgATLP23da+1UyZLwA360Q==} engines: {node: '>=22.19.0'} @@ -306,11 +280,6 @@ packages: resolution: {integrity: sha512-RorGp9OH5l3ElpuC5a5ZQ2eWcchZGXflXRzVGkV99y3y6tT+LLNyxoYIdVKvTKWEObwhExeQbTH0fI2tE4iX4g==} engines: {node: '>=22.19.0'} - '@earendil-works/pi-ai@0.75.4': - resolution: {integrity: sha512-m/w8Hh3vQ0rAycwJiJWdzkypkn4295f4eq/966lDRy8aX5sk6bgYXH8TQmL16TO7Uwc7MbJG0QoyFHgX8RqXUQ==} - engines: {node: '>=22.19.0'} - hasBin: true - '@earendil-works/pi-ai@0.79.10': resolution: {integrity: sha512-9jR23tOl0BIUdQMn70Gr72xYBpM7Xgl9Lyv7gAnU1USfkNRuYG/f/edLl+n/Dp/RafDW3JI4DF7y/GhgkORuew==} engines: {node: '>=22.19.0'} @@ -494,26 +463,6 @@ packages: cpu: [x64] os: [win32] - '@flue/cli@0.8.0': - resolution: {integrity: sha512-iWTVoSCZ0/D5T5O4E+dha9IT7yx1/qqjsEGLtedd/wnlp2nK45qTrjutBS2pgenGNzdQ6wlPzMPkP8MA9z49yw==} - engines: {node: '>=22.18.0'} - hasBin: true - peerDependencies: - wrangler: ^4.94.0 - peerDependenciesMeta: - wrangler: - optional: true - - '@flue/cli@0.8.1': - resolution: {integrity: sha512-8aeaSf7RsXj4A4P+eQgTj8l2+17NScrVCK3UcbMuQ2pwAu84v5t6SQR2XvYuqActlidTIFR6+xxWS7dR9T7yZw==} - engines: {node: '>=22.18.0'} - hasBin: true - peerDependencies: - wrangler: ^4.94.0 - peerDependenciesMeta: - wrangler: - optional: true - '@flue/cli@1.0.0-beta.3': resolution: {integrity: sha512-i8Lo0koB0gcy3bbCp+kwA1C522PsxvN8ydexDwR3rMkWisitG3FVmqmsLPkWBetspmXBLDbyDq39+AdSoXf4EQ==} engines: {node: '>=22.19.0'} @@ -524,14 +473,6 @@ packages: engines: {node: '>=22.19.0'} hasBin: true - '@flue/runtime@0.8.0': - resolution: {integrity: sha512-GCXJOZtb6s+HuMBzB4I41uQzxX7pELfdorD3gnWnkYoNFjHW8D/ncjXMTsczyl2tkP1IPK0C1Ffw0zwGqkmO7g==} - engines: {node: '>=22.18.0'} - - '@flue/runtime@0.8.1': - resolution: {integrity: sha512-nhIiNLr4NmsK6xgYgFt+mTFTKhHxMn++4gjzygYQIUqQK0GR4hgIjpvosl/361Xx087+8N0wNWh2MEVStoZCIg==} - engines: {node: '>=22.18.0'} - '@flue/runtime@1.0.0-beta.3': resolution: {integrity: sha512-VkWz6lRaS73fu6Y8Wh+CjAHjrLmICUmDwURQCA7dtXWP2O99UyvDQ/5Cbg9DYpsYZpaFuOhWzOCj+Igc4F/LbA==} engines: {node: '>=22.19.0'} @@ -765,9 +706,6 @@ packages: '@microsoft/fetch-event-source@2.0.1': resolution: {integrity: sha512-W6CLUJ2eBMw3Rec70qrsEW0jOm/3twwJv21mrmj2yORiaVmVYGS4sSS5yUwvQc1ZlDLYGPnClVWmUUMagKNsfA==} - '@mistralai/mistralai@2.2.1': - resolution: {integrity: sha512-uKU8CZmL2RzYKmplsU01hii4p3pe4HqJefpWNRWXm1Tcm0Sm4xXfwSLIy4k7ZCPlbETCGcp69E7hZs+WOJ5itQ==} - '@mistralai/mistralai@2.2.6': resolution: {integrity: sha512-W8pX7zHxjJvMIpw8JMxeJEleapXX0Q9NPszdNzqkM3MIEoIGPObdodujj+WHteXEvGfaP/AMwlNyRfEzSY6dQQ==} peerDependencies: @@ -2407,20 +2345,6 @@ snapshots: optionalDependencies: workerd: 1.20260730.1 - '@cloudflare/vite-plugin@1.39.0(@types/node@25.9.1)(vite@8.0.14(@types/node@25.9.1)(esbuild@0.27.3)(yaml@2.9.0))(workerd@1.20260730.1)(wrangler@4.95.0(@types/node@25.9.1))': - dependencies: - '@cloudflare/unenv-preset': 2.16.1(unenv@2.0.0-rc.24)(workerd@1.20260730.1) - miniflare: 4.20260526.0(@types/node@25.9.1) - unenv: 2.0.0-rc.24 - vite: 8.0.14(@types/node@25.9.1)(esbuild@0.27.3)(yaml@2.9.0) - wrangler: 4.95.0(@types/node@25.9.1) - ws: 8.20.1 - transitivePeerDependencies: - - '@types/node' - - bufferutil - - utf-8-validate - - workerd - '@cloudflare/vite-plugin@1.50.0(@types/node@25.9.1)(vite@8.0.14(@types/node@25.9.1)(esbuild@0.27.3)(yaml@2.9.0))(wrangler@4.95.0(@types/node@25.9.1))': dependencies: '@cloudflare/unenv-preset': 2.16.1(unenv@2.0.0-rc.24)(workerd@1.20260730.1) @@ -2474,23 +2398,9 @@ snapshots: '@microsoft/fetch-event-source': 2.0.1 fastq: 1.20.1 - '@earendil-works/pi-agent-core@0.75.4(@modelcontextprotocol/sdk@1.31.0(zod@4.4.3))(ws@8.20.1)(zod@4.4.3)': - dependencies: - '@earendil-works/pi-ai': 0.75.4(@modelcontextprotocol/sdk@1.31.0(zod@4.4.3))(ws@8.20.1)(zod@4.4.3) - ignore: 7.0.5 - typebox: 1.1.38 - yaml: 2.9.0 - transitivePeerDependencies: - - '@modelcontextprotocol/sdk' - - bufferutil - - supports-color - - utf-8-validate - - ws - - zod - - '@earendil-works/pi-agent-core@0.79.10(@modelcontextprotocol/sdk@1.31.0(zod@4.4.3))(ws@8.20.1)(zod@4.4.3)': + '@earendil-works/pi-agent-core@0.79.10(@modelcontextprotocol/sdk@1.31.0(zod@4.4.3))(ws@8.21.0)(zod@4.4.3)': dependencies: - '@earendil-works/pi-ai': 0.79.10(@modelcontextprotocol/sdk@1.31.0(zod@4.4.3))(ws@8.20.1)(zod@4.4.3) + '@earendil-works/pi-ai': 0.79.10(@modelcontextprotocol/sdk@1.31.0(zod@4.4.3))(ws@8.21.0)(zod@4.4.3) ignore: 7.0.5 typebox: 1.1.38 yaml: 2.9.0 @@ -2502,9 +2412,9 @@ snapshots: - ws - zod - '@earendil-works/pi-agent-core@0.80.10(@modelcontextprotocol/sdk@1.31.0(zod@4.4.3))(ws@8.20.1)(zod@4.4.3)': + '@earendil-works/pi-agent-core@0.80.10(@modelcontextprotocol/sdk@1.31.0(zod@4.4.3))(ws@8.21.0)(zod@4.4.3)': dependencies: - '@earendil-works/pi-ai': 0.80.10(@modelcontextprotocol/sdk@1.31.0(zod@4.4.3))(ws@8.20.1)(zod@4.4.3) + '@earendil-works/pi-ai': 0.80.10(@modelcontextprotocol/sdk@1.31.0(zod@4.4.3))(ws@8.21.0)(zod@4.4.3) ignore: 7.0.5 typebox: 1.1.38 yaml: 2.9.0 @@ -2516,9 +2426,9 @@ snapshots: - ws - zod - '@earendil-works/pi-agent-core@0.83.0(@modelcontextprotocol/sdk@1.31.0(zod@4.4.3))(ws@8.20.1)(zod@4.4.3)': + '@earendil-works/pi-agent-core@0.83.0(@modelcontextprotocol/sdk@1.31.0(zod@4.4.3))(ws@8.21.0)(zod@4.4.3)': dependencies: - '@earendil-works/pi-ai': 0.83.0(@modelcontextprotocol/sdk@1.31.0(zod@4.4.3))(ws@8.20.1)(zod@4.4.3) + '@earendil-works/pi-ai': 0.83.0(@modelcontextprotocol/sdk@1.31.0(zod@4.4.3))(ws@8.21.0)(zod@4.4.3) diff: 8.0.4 ignore: 7.0.5 typebox: 1.3.7 @@ -2531,26 +2441,7 @@ snapshots: - ws - zod - '@earendil-works/pi-ai@0.75.4(@modelcontextprotocol/sdk@1.31.0(zod@4.4.3))(ws@8.20.1)(zod@4.4.3)': - dependencies: - '@anthropic-ai/sdk': 0.91.1(zod@4.4.3) - '@aws-sdk/client-bedrock-runtime': 3.1048.0 - '@google/genai': 1.52.0(@modelcontextprotocol/sdk@1.31.0(zod@4.4.3)) - '@mistralai/mistralai': 2.2.1 - http-proxy-agent: 7.0.2 - https-proxy-agent: 7.0.6 - openai: 6.26.0(ws@8.20.1)(zod@4.4.3) - partial-json: 0.1.7 - typebox: 1.1.38 - transitivePeerDependencies: - - '@modelcontextprotocol/sdk' - - bufferutil - - supports-color - - utf-8-validate - - ws - - zod - - '@earendil-works/pi-ai@0.79.10(@modelcontextprotocol/sdk@1.31.0(zod@4.4.3))(ws@8.20.1)(zod@4.4.3)': + '@earendil-works/pi-ai@0.79.10(@modelcontextprotocol/sdk@1.31.0(zod@4.4.3))(ws@8.21.0)(zod@4.4.3)': dependencies: '@anthropic-ai/sdk': 0.91.1(zod@4.4.3) '@aws-sdk/client-bedrock-runtime': 3.1048.0 @@ -2560,7 +2451,7 @@ snapshots: '@smithy/node-http-handler': 4.7.3 http-proxy-agent: 7.0.2 https-proxy-agent: 7.0.6 - openai: 6.26.0(ws@8.20.1)(zod@4.4.3) + openai: 6.26.0(ws@8.21.0)(zod@4.4.3) partial-json: 0.1.7 typebox: 1.1.38 transitivePeerDependencies: @@ -2571,7 +2462,7 @@ snapshots: - ws - zod - '@earendil-works/pi-ai@0.80.10(@modelcontextprotocol/sdk@1.31.0(zod@4.4.3))(ws@8.20.1)(zod@4.4.3)': + '@earendil-works/pi-ai@0.80.10(@modelcontextprotocol/sdk@1.31.0(zod@4.4.3))(ws@8.21.0)(zod@4.4.3)': dependencies: '@anthropic-ai/sdk': 0.91.1(zod@4.4.3) '@aws-sdk/client-bedrock-runtime': 3.1048.0 @@ -2581,7 +2472,7 @@ snapshots: '@smithy/node-http-handler': 4.7.3 http-proxy-agent: 7.0.2 https-proxy-agent: 7.0.6 - openai: 6.26.0(ws@8.20.1)(zod@4.4.3) + openai: 6.26.0(ws@8.21.0)(zod@4.4.3) partial-json: 0.1.7 typebox: 1.1.38 transitivePeerDependencies: @@ -2592,7 +2483,7 @@ snapshots: - ws - zod - '@earendil-works/pi-ai@0.83.0(@modelcontextprotocol/sdk@1.31.0(zod@4.4.3))(ws@8.20.1)(zod@4.4.3)': + '@earendil-works/pi-ai@0.83.0(@modelcontextprotocol/sdk@1.31.0(zod@4.4.3))(ws@8.21.0)(zod@4.4.3)': dependencies: '@anthropic-ai/sdk': 0.91.1(zod@4.4.3) '@aws-sdk/client-bedrock-runtime': 3.1048.0 @@ -2602,7 +2493,7 @@ snapshots: '@smithy/node-http-handler': 4.7.3 http-proxy-agent: 7.0.2 https-proxy-agent: 7.0.6 - openai: 6.26.0(ws@8.20.1)(zod@4.4.3) + openai: 6.26.0(ws@8.21.0)(zod@4.4.3) partial-json: 0.1.7 typebox: 1.3.7 transitivePeerDependencies: @@ -2712,88 +2603,10 @@ snapshots: '@esbuild/win32-x64@0.27.3': optional: true - '@flue/cli@0.8.0(@standard-schema/spec@1.1.0)(@types/json-schema@7.0.15)(@types/node@25.9.1)(esbuild@0.27.3)(typebox@1.3.7)(workerd@1.20260730.1)(wrangler@4.95.0(@types/node@25.9.1))(yaml@2.9.0)(zod-to-json-schema@3.25.2(zod@4.4.3))(zod@4.4.3)': - dependencies: - '@cloudflare/vite-plugin': 1.39.0(@types/node@25.9.1)(vite@8.0.14(@types/node@25.9.1)(esbuild@0.27.3)(yaml@2.9.0))(workerd@1.20260730.1)(wrangler@4.95.0(@types/node@25.9.1)) - '@flue/runtime': 0.8.0(@standard-schema/spec@1.1.0)(@types/json-schema@7.0.15)(typebox@1.3.7)(zod-to-json-schema@3.25.2(zod@4.4.3))(zod@4.4.3) - '@vercel/detect-agent': 1.2.3 - package-up: 5.0.0 - valibot: 1.4.2 - vite: 8.0.14(@types/node@25.9.1)(esbuild@0.27.3)(yaml@2.9.0) - optionalDependencies: - wrangler: 4.95.0(@types/node@25.9.1) - transitivePeerDependencies: - - '@cfworker/json-schema' - - '@standard-schema/spec' - - '@types/json-schema' - - '@types/node' - - '@vitejs/devtools' - - arktype - - bufferutil - - effect - - esbuild - - jiti - - less - - sass - - sass-embedded - - stylus - - sugarss - - supports-color - - sury - - terser - - tsx - - typebox - - typescript - - utf-8-validate - - workerd - - yaml - - zod - - zod-openapi - - zod-to-json-schema - - '@flue/cli@0.8.1(@standard-schema/spec@1.1.0)(@types/json-schema@7.0.15)(@types/node@25.9.1)(esbuild@0.27.3)(typebox@1.3.7)(workerd@1.20260730.1)(wrangler@4.95.0(@types/node@25.9.1))(yaml@2.9.0)(zod-to-json-schema@3.25.2(zod@4.4.3))(zod@4.4.3)': - dependencies: - '@cloudflare/vite-plugin': 1.39.0(@types/node@25.9.1)(vite@8.0.14(@types/node@25.9.1)(esbuild@0.27.3)(yaml@2.9.0))(workerd@1.20260730.1)(wrangler@4.95.0(@types/node@25.9.1)) - '@flue/runtime': 0.8.1(@standard-schema/spec@1.1.0)(@types/json-schema@7.0.15)(typebox@1.3.7)(zod-to-json-schema@3.25.2(zod@4.4.3))(zod@4.4.3) - '@vercel/detect-agent': 1.2.3 - package-up: 5.0.0 - valibot: 1.4.2 - vite: 8.0.14(@types/node@25.9.1)(esbuild@0.27.3)(yaml@2.9.0) - optionalDependencies: - wrangler: 4.95.0(@types/node@25.9.1) - transitivePeerDependencies: - - '@cfworker/json-schema' - - '@standard-schema/spec' - - '@types/json-schema' - - '@types/node' - - '@vitejs/devtools' - - arktype - - bufferutil - - effect - - esbuild - - jiti - - less - - sass - - sass-embedded - - stylus - - sugarss - - supports-color - - sury - - terser - - tsx - - typebox - - typescript - - utf-8-validate - - workerd - - yaml - - zod - - zod-openapi - - zod-to-json-schema - - '@flue/cli@1.0.0-beta.3(@standard-schema/spec@1.1.0)(@types/json-schema@7.0.15)(@types/node@25.9.1)(esbuild@0.27.3)(hono@4.13.7)(typebox@1.3.7)(wrangler@4.95.0(@types/node@25.9.1))(ws@8.20.1)(yaml@2.9.0)(zod-to-json-schema@3.25.2(zod@4.4.3))(zod@4.4.3)': + '@flue/cli@1.0.0-beta.3(@standard-schema/spec@1.1.0)(@types/json-schema@7.0.15)(@types/node@25.9.1)(esbuild@0.27.3)(hono@4.13.7)(typebox@1.3.7)(wrangler@4.95.0(@types/node@25.9.1))(ws@8.21.0)(yaml@2.9.0)(zod-to-json-schema@3.25.2(zod@4.4.3))(zod@4.4.3)': dependencies: '@cloudflare/vite-plugin': 1.50.0(@types/node@25.9.1)(vite@8.0.14(@types/node@25.9.1)(esbuild@0.27.3)(yaml@2.9.0))(wrangler@4.95.0(@types/node@25.9.1)) - '@flue/runtime': 1.0.0-beta.3(@standard-schema/spec@1.1.0)(@types/json-schema@7.0.15)(typebox@1.3.7)(ws@8.20.1)(zod-to-json-schema@3.25.2(zod@4.4.3))(zod@4.4.3) + '@flue/runtime': 1.0.0-beta.3(@standard-schema/spec@1.1.0)(@types/json-schema@7.0.15)(typebox@1.3.7)(ws@8.21.0)(zod-to-json-schema@3.25.2(zod@4.4.3))(zod@4.4.3) '@flue/sdk': 1.0.0-beta.3 '@hono/node-server': 2.0.11(hono@4.13.7) '@vercel/detect-agent': 1.2.3 @@ -2835,10 +2648,10 @@ snapshots: - zod-openapi - zod-to-json-schema - '@flue/cli@1.0.0-beta.9(@standard-schema/spec@1.1.0)(@types/json-schema@7.0.15)(@types/node@25.9.1)(esbuild@0.27.3)(hono@4.13.7)(typebox@1.3.7)(wrangler@4.95.0(@types/node@25.9.1))(ws@8.20.1)(yaml@2.9.0)(zod-to-json-schema@3.25.2(zod@4.4.3))(zod@4.4.3)': + '@flue/cli@1.0.0-beta.9(@standard-schema/spec@1.1.0)(@types/json-schema@7.0.15)(@types/node@25.9.1)(esbuild@0.27.3)(hono@4.13.7)(typebox@1.3.7)(wrangler@4.95.0(@types/node@25.9.1))(ws@8.21.0)(yaml@2.9.0)(zod-to-json-schema@3.25.2(zod@4.4.3))(zod@4.4.3)': dependencies: '@cloudflare/vite-plugin': 1.50.0(@types/node@25.9.1)(vite@8.0.14(@types/node@25.9.1)(esbuild@0.27.3)(yaml@2.9.0))(wrangler@4.95.0(@types/node@25.9.1)) - '@flue/runtime': 1.0.0-beta.9(@standard-schema/spec@1.1.0)(@types/json-schema@7.0.15)(typebox@1.3.7)(ws@8.20.1)(zod-to-json-schema@3.25.2(zod@4.4.3))(zod@4.4.3) + '@flue/runtime': 1.0.0-beta.9(@standard-schema/spec@1.1.0)(@types/json-schema@7.0.15)(typebox@1.3.7)(ws@8.21.0)(zod-to-json-schema@3.25.2(zod@4.4.3))(zod@4.4.3) '@flue/sdk': 1.0.0-beta.9 '@hono/node-server': 2.0.11(hono@4.13.7) '@vercel/detect-agent': 1.2.3 @@ -2880,80 +2693,10 @@ snapshots: - zod-openapi - zod-to-json-schema - '@flue/runtime@0.8.0(@standard-schema/spec@1.1.0)(@types/json-schema@7.0.15)(typebox@1.3.7)(zod-to-json-schema@3.25.2(zod@4.4.3))(zod@4.4.3)': - dependencies: - '@earendil-works/pi-agent-core': 0.75.4(@modelcontextprotocol/sdk@1.31.0(zod@4.4.3))(ws@8.20.1)(zod@4.4.3) - '@earendil-works/pi-ai': 0.75.4(@modelcontextprotocol/sdk@1.31.0(zod@4.4.3))(ws@8.20.1)(zod@4.4.3) - '@hono/node-server': 2.0.11(hono@4.13.7) - '@hono/standard-validator': 0.2.2(@standard-schema/spec@1.1.0)(hono@4.13.7) - '@modelcontextprotocol/sdk': 1.31.0(zod@4.4.3) - '@standard-community/standard-json': 0.3.5(@standard-schema/spec@1.1.0)(@types/json-schema@7.0.15)(@valibot/to-json-schema@1.7.0(valibot@1.4.2))(quansync@0.2.11)(typebox@1.3.7)(valibot@1.4.2)(zod-to-json-schema@3.25.2(zod@4.4.3))(zod@4.4.3) - '@standard-community/standard-openapi': 0.2.9(@standard-community/standard-json@0.3.5(@standard-schema/spec@1.1.0)(@types/json-schema@7.0.15)(@valibot/to-json-schema@1.7.0(valibot@1.4.2))(quansync@0.2.11)(typebox@1.3.7)(valibot@1.4.2)(zod-to-json-schema@3.25.2(zod@4.4.3))(zod@4.4.3))(@standard-schema/spec@1.1.0)(openapi-types@12.1.3)(typebox@1.3.7)(valibot@1.4.2)(zod@4.4.3) - '@valibot/to-json-schema': 1.7.0(valibot@1.4.2) - hono: 4.13.7 - hono-openapi: 1.3.0(@hono/standard-validator@0.2.2(@standard-schema/spec@1.1.0)(hono@4.13.7))(@standard-community/standard-json@0.3.5(@standard-schema/spec@1.1.0)(@types/json-schema@7.0.15)(@valibot/to-json-schema@1.7.0(valibot@1.4.2))(quansync@0.2.11)(typebox@1.3.7)(valibot@1.4.2)(zod-to-json-schema@3.25.2(zod@4.4.3))(zod@4.4.3))(@standard-community/standard-openapi@0.2.9(@standard-community/standard-json@0.3.5(@standard-schema/spec@1.1.0)(@types/json-schema@7.0.15)(@valibot/to-json-schema@1.7.0(valibot@1.4.2))(quansync@0.2.11)(typebox@1.3.7)(valibot@1.4.2)(zod-to-json-schema@3.25.2(zod@4.4.3))(zod@4.4.3))(@standard-schema/spec@1.1.0)(openapi-types@12.1.3)(typebox@1.3.7)(valibot@1.4.2)(zod@4.4.3))(@types/json-schema@7.0.15)(hono@4.13.7)(openapi-types@12.1.3) - js-yaml: 4.3.2 - just-bash: 3.0.1 - openapi-types: 12.1.3 - quansync: 0.2.11 - ulidx: 2.4.1 - valibot: 1.4.2 - ws: 8.20.1 - transitivePeerDependencies: - - '@cfworker/json-schema' - - '@standard-schema/spec' - - '@types/json-schema' - - arktype - - bufferutil - - effect - - supports-color - - sury - - typebox - - typescript - - utf-8-validate - - zod - - zod-openapi - - zod-to-json-schema - - '@flue/runtime@0.8.1(@standard-schema/spec@1.1.0)(@types/json-schema@7.0.15)(typebox@1.3.7)(zod-to-json-schema@3.25.2(zod@4.4.3))(zod@4.4.3)': + '@flue/runtime@1.0.0-beta.3(@standard-schema/spec@1.1.0)(@types/json-schema@7.0.15)(typebox@1.3.7)(ws@8.21.0)(zod-to-json-schema@3.25.2(zod@4.4.3))(zod@4.4.3)': dependencies: - '@earendil-works/pi-agent-core': 0.75.4(@modelcontextprotocol/sdk@1.31.0(zod@4.4.3))(ws@8.20.1)(zod@4.4.3) - '@earendil-works/pi-ai': 0.75.4(@modelcontextprotocol/sdk@1.31.0(zod@4.4.3))(ws@8.20.1)(zod@4.4.3) - '@hono/node-server': 2.0.11(hono@4.13.7) - '@hono/standard-validator': 0.2.2(@standard-schema/spec@1.1.0)(hono@4.13.7) - '@modelcontextprotocol/sdk': 1.31.0(zod@4.4.3) - '@standard-community/standard-json': 0.3.5(@standard-schema/spec@1.1.0)(@types/json-schema@7.0.15)(@valibot/to-json-schema@1.7.0(valibot@1.4.2))(quansync@0.2.11)(typebox@1.3.7)(valibot@1.4.2)(zod-to-json-schema@3.25.2(zod@4.4.3))(zod@4.4.3) - '@standard-community/standard-openapi': 0.2.9(@standard-community/standard-json@0.3.5(@standard-schema/spec@1.1.0)(@types/json-schema@7.0.15)(@valibot/to-json-schema@1.7.0(valibot@1.4.2))(quansync@0.2.11)(typebox@1.3.7)(valibot@1.4.2)(zod-to-json-schema@3.25.2(zod@4.4.3))(zod@4.4.3))(@standard-schema/spec@1.1.0)(openapi-types@12.1.3)(typebox@1.3.7)(valibot@1.4.2)(zod@4.4.3) - '@valibot/to-json-schema': 1.7.0(valibot@1.4.2) - hono: 4.13.7 - hono-openapi: 1.3.0(@hono/standard-validator@0.2.2(@standard-schema/spec@1.1.0)(hono@4.13.7))(@standard-community/standard-json@0.3.5(@standard-schema/spec@1.1.0)(@types/json-schema@7.0.15)(@valibot/to-json-schema@1.7.0(valibot@1.4.2))(quansync@0.2.11)(typebox@1.3.7)(valibot@1.4.2)(zod-to-json-schema@3.25.2(zod@4.4.3))(zod@4.4.3))(@standard-community/standard-openapi@0.2.9(@standard-community/standard-json@0.3.5(@standard-schema/spec@1.1.0)(@types/json-schema@7.0.15)(@valibot/to-json-schema@1.7.0(valibot@1.4.2))(quansync@0.2.11)(typebox@1.3.7)(valibot@1.4.2)(zod-to-json-schema@3.25.2(zod@4.4.3))(zod@4.4.3))(@standard-schema/spec@1.1.0)(openapi-types@12.1.3)(typebox@1.3.7)(valibot@1.4.2)(zod@4.4.3))(@types/json-schema@7.0.15)(hono@4.13.7)(openapi-types@12.1.3) - js-yaml: 4.3.2 - just-bash: 3.0.1 - openapi-types: 12.1.3 - quansync: 0.2.11 - ulidx: 2.4.1 - valibot: 1.4.2 - ws: 8.20.1 - transitivePeerDependencies: - - '@cfworker/json-schema' - - '@standard-schema/spec' - - '@types/json-schema' - - arktype - - bufferutil - - effect - - supports-color - - sury - - typebox - - typescript - - utf-8-validate - - zod - - zod-openapi - - zod-to-json-schema - - '@flue/runtime@1.0.0-beta.3(@standard-schema/spec@1.1.0)(@types/json-schema@7.0.15)(typebox@1.3.7)(ws@8.20.1)(zod-to-json-schema@3.25.2(zod@4.4.3))(zod@4.4.3)': - dependencies: - '@earendil-works/pi-agent-core': 0.79.10(@modelcontextprotocol/sdk@1.31.0(zod@4.4.3))(ws@8.20.1)(zod@4.4.3) - '@earendil-works/pi-ai': 0.79.10(@modelcontextprotocol/sdk@1.31.0(zod@4.4.3))(ws@8.20.1)(zod@4.4.3) + '@earendil-works/pi-agent-core': 0.79.10(@modelcontextprotocol/sdk@1.31.0(zod@4.4.3))(ws@8.21.0)(zod@4.4.3) + '@earendil-works/pi-ai': 0.79.10(@modelcontextprotocol/sdk@1.31.0(zod@4.4.3))(ws@8.21.0)(zod@4.4.3) '@hono/node-server': 2.0.11(hono@4.13.7) '@hono/standard-validator': 0.2.2(@standard-schema/spec@1.1.0)(hono@4.13.7) '@modelcontextprotocol/sdk': 1.31.0(zod@4.4.3) @@ -2985,10 +2728,10 @@ snapshots: - zod-openapi - zod-to-json-schema - '@flue/runtime@1.0.0-beta.9(@standard-schema/spec@1.1.0)(@types/json-schema@7.0.15)(typebox@1.3.7)(ws@8.20.1)(zod-to-json-schema@3.25.2(zod@4.4.3))(zod@4.4.3)': + '@flue/runtime@1.0.0-beta.9(@standard-schema/spec@1.1.0)(@types/json-schema@7.0.15)(typebox@1.3.7)(ws@8.21.0)(zod-to-json-schema@3.25.2(zod@4.4.3))(zod@4.4.3)': dependencies: - '@earendil-works/pi-agent-core': 0.80.10(@modelcontextprotocol/sdk@1.31.0(zod@4.4.3))(ws@8.20.1)(zod@4.4.3) - '@earendil-works/pi-ai': 0.80.10(@modelcontextprotocol/sdk@1.31.0(zod@4.4.3))(ws@8.20.1)(zod@4.4.3) + '@earendil-works/pi-agent-core': 0.80.10(@modelcontextprotocol/sdk@1.31.0(zod@4.4.3))(ws@8.21.0)(zod@4.4.3) + '@earendil-works/pi-ai': 0.80.10(@modelcontextprotocol/sdk@1.31.0(zod@4.4.3))(ws@8.21.0)(zod@4.4.3) '@hono/node-server': 2.0.11(hono@4.13.7) '@hono/standard-validator': 0.2.2(@standard-schema/spec@1.1.0)(hono@4.13.7) '@modelcontextprotocol/sdk': 1.31.0(zod@4.4.3) @@ -3020,10 +2763,10 @@ snapshots: - zod-openapi - zod-to-json-schema - '@flue/runtime@2.0.0(@modelcontextprotocol/sdk@1.31.0(zod@4.4.3))(ws@8.20.1)(zod@4.4.3)': + '@flue/runtime@2.0.0(@modelcontextprotocol/sdk@1.31.0(zod@4.4.3))(ws@8.21.0)(zod@4.4.3)': dependencies: - '@earendil-works/pi-agent-core': 0.83.0(@modelcontextprotocol/sdk@1.31.0(zod@4.4.3))(ws@8.20.1)(zod@4.4.3) - '@earendil-works/pi-ai': 0.83.0(@modelcontextprotocol/sdk@1.31.0(zod@4.4.3))(ws@8.20.1)(zod@4.4.3) + '@earendil-works/pi-agent-core': 0.83.0(@modelcontextprotocol/sdk@1.31.0(zod@4.4.3))(ws@8.21.0)(zod@4.4.3) + '@earendil-works/pi-ai': 0.83.0(@modelcontextprotocol/sdk@1.31.0(zod@4.4.3))(ws@8.21.0)(zod@4.4.3) '@hono/node-server': 2.0.11(hono@4.13.7) '@modelcontextprotocol/client': 2.2.0 '@valibot/to-json-schema': 1.7.0(valibot@1.4.2) @@ -3040,10 +2783,10 @@ snapshots: - ws - zod - '@flue/runtime@2.0.1(@modelcontextprotocol/sdk@1.31.0(zod@4.4.3))(ws@8.20.1)(zod@4.4.3)': + '@flue/runtime@2.0.1(@modelcontextprotocol/sdk@1.31.0(zod@4.4.3))(ws@8.21.0)(zod@4.4.3)': dependencies: - '@earendil-works/pi-agent-core': 0.83.0(@modelcontextprotocol/sdk@1.31.0(zod@4.4.3))(ws@8.20.1)(zod@4.4.3) - '@earendil-works/pi-ai': 0.83.0(@modelcontextprotocol/sdk@1.31.0(zod@4.4.3))(ws@8.20.1)(zod@4.4.3) + '@earendil-works/pi-agent-core': 0.83.0(@modelcontextprotocol/sdk@1.31.0(zod@4.4.3))(ws@8.21.0)(zod@4.4.3) + '@earendil-works/pi-ai': 0.83.0(@modelcontextprotocol/sdk@1.31.0(zod@4.4.3))(ws@8.21.0)(zod@4.4.3) '@hono/node-server': 2.0.11(hono@4.13.7) '@modelcontextprotocol/client': 2.2.0 '@valibot/to-json-schema': 1.7.0(valibot@1.4.2) @@ -3225,15 +2968,6 @@ snapshots: '@microsoft/fetch-event-source@2.0.1': {} - '@mistralai/mistralai@2.2.1': - dependencies: - ws: 8.20.1 - zod: 4.4.3 - zod-to-json-schema: 3.25.2(zod@4.4.3) - transitivePeerDependencies: - - bufferutil - - utf-8-validate - '@mistralai/mistralai@2.2.6(@opentelemetry/api@1.9.0)': dependencies: '@opentelemetry/semantic-conventions': 1.43.0 @@ -4131,9 +3865,9 @@ snapshots: dependencies: wrappy: 1.0.2 - openai@6.26.0(ws@8.20.1)(zod@4.4.3): + openai@6.26.0(ws@8.21.0)(zod@4.4.3): optionalDependencies: - ws: 8.20.1 + ws: 8.21.0 zod: 4.4.3 openapi-types@12.1.3: {} diff --git a/e2e/scenarios/flue-instrumentation/scenario.cli.mjs b/e2e/scenarios/flue-instrumentation/scenario.cli.mjs deleted file mode 100644 index bbbb961fd..000000000 --- a/e2e/scenarios/flue-instrumentation/scenario.cli.mjs +++ /dev/null @@ -1,4 +0,0 @@ -const { runCliFlueInstrumentation, runMain } = - await import("./scenario.impl.mjs"); - -runMain(runCliFlueInstrumentation); diff --git a/e2e/scenarios/flue-instrumentation/scenario.impl.mjs b/e2e/scenarios/flue-instrumentation/scenario.impl.mjs index e0c4a5ce2..ba30b1543 100644 --- a/e2e/scenarios/flue-instrumentation/scenario.impl.mjs +++ b/e2e/scenarios/flue-instrumentation/scenario.impl.mjs @@ -1,5 +1,4 @@ import { spawn } from "node:child_process"; -import { access, rm } from "node:fs/promises"; import { createServer } from "node:net"; import path from "node:path"; import { fileURLToPath } from "node:url"; @@ -15,7 +14,6 @@ const flueCliPath = path.join( "bin", "flue.mjs", ); -const braintrustHookNodeOption = "--import=braintrust/hook.mjs"; function workflowPayload() { return { @@ -27,27 +25,23 @@ function workflowPayload() { }; } -export async function runNodeFlueInstrumentationScenario(options) { - const env = scenarioEnv(options); - const outputDir = path.join(process.cwd(), ".flue-build", options.outputName); - await runFlueCli( - [ - "build", - "--target", - "node", - "--root", - process.cwd(), - "--output", - outputDir, - ], - env, - ); +export async function runFlueInstrumentationScenario() { + const outputDir = path.join(process.cwd(), ".flue-build", "explicit"); + await runFlueCli([ + "build", + "--target", + "node", + "--root", + process.cwd(), + "--output", + outputDir, + ]); const port = await getFreePort(); const child = spawn(process.execPath, [path.join(outputDir, "server.mjs")], { cwd: process.cwd(), env: { - ...env, + ...process.env, PORT: String(port), }, stdio: ["ignore", "pipe", "pipe"], @@ -96,69 +90,10 @@ export async function runNodeFlueInstrumentationScenario(options) { } } -export async function runCliFlueInstrumentationScenario() { - const flushFile = path.join(process.cwd(), ".flue-build", "cli-flushed"); - await rm(flushFile, { force: true }); - await runFlueCli( - [ - "run", - "instrumentation", - "--target", - "node", - process.env.FLUE_E2E_INPUT_FLAG ?? "--payload", - JSON.stringify(workflowPayload()), - "--root", - process.cwd(), - ], - { - ...scenarioEnv({ autoHook: true, explicitObserve: false }), - FLUE_E2E_FLUSH_FILE: flushFile, - }, - ); - await waitForFile(flushFile); -} - -export function runExplicitFlueInstrumentation() { - return runNodeFlueInstrumentationScenario({ - autoHook: false, - explicitObserve: true, - outputName: "explicit", - }); -} - -export function runAutoFlueInstrumentation() { - return runNodeFlueInstrumentationScenario({ - autoHook: true, - explicitObserve: false, - outputName: "auto-hook", - }); -} - -export function runCliFlueInstrumentation() { - return runCliFlueInstrumentationScenario(); -} - -function scenarioEnv({ autoHook, explicitObserve }) { - const env = { - ...process.env, - FLUE_E2E_EXPLICIT_OBSERVE: explicitObserve ? "1" : "0", - }; - if (!autoHook) { - return env; - } - return { - ...env, - NODE_OPTIONS: [env.NODE_OPTIONS, braintrustHookNodeOption] - .filter(Boolean) - .join(" "), - }; -} - -async function runFlueCli(args, env) { +async function runFlueCli(args) { await new Promise((resolve, reject) => { const child = spawn(process.execPath, [flueCliPath, ...args], { cwd: process.cwd(), - env, stdio: ["ignore", "pipe", "pipe"], }); @@ -245,27 +180,3 @@ async function stopChild(child) { }, 5_000).unref(); }); } - -async function waitForFile(filePath) { - const startedAt = Date.now(); - while (true) { - try { - await access(filePath); - return; - } catch { - if (Date.now() - startedAt > 30_000) { - throw new Error( - `timed out waiting for Flue e2e flush marker: ${filePath}`, - ); - } - await new Promise((resolve) => setTimeout(resolve, 50)); - } - } -} - -export function runMain(main) { - void main().catch((error) => { - console.error(error); - process.exitCode = 1; - }); -} diff --git a/e2e/scenarios/flue-instrumentation/scenario.mjs b/e2e/scenarios/flue-instrumentation/scenario.mjs deleted file mode 100644 index a9bd9d94e..000000000 --- a/e2e/scenarios/flue-instrumentation/scenario.mjs +++ /dev/null @@ -1,8 +0,0 @@ -import "braintrust"; - -await new Promise((resolve) => setImmediate(resolve)); - -const { runAutoFlueInstrumentation, runMain } = - await import("./scenario.impl.mjs"); - -runMain(runAutoFlueInstrumentation); diff --git a/e2e/scenarios/flue-instrumentation/scenario.test.ts b/e2e/scenarios/flue-instrumentation/scenario.test.ts index 73a27bbf7..0c9280c5d 100644 --- a/e2e/scenarios/flue-instrumentation/scenario.test.ts +++ b/e2e/scenarios/flue-instrumentation/scenario.test.ts @@ -1,6 +1,6 @@ import { promises as fs } from "node:fs"; import path from "node:path"; -import { describe } from "vitest"; +import { describe } from "vite-plus/test"; import { prepareScenarioDir, readInstalledPackageVersion, @@ -10,49 +10,19 @@ import { defineFlueInstrumentationAssertions } from "./assertions"; import { defineFlueV2InstrumentationAssertions } from "./v2-assertions"; const originalScenarioDir = resolveScenarioDir(import.meta.url); -const generatedScenarioRoot = path.resolve( - originalScenarioDir, - "../../.bt-tmp/generated-scenarios/flue-instrumentation", -); const TIMEOUT_MS = 120_000; -const flueV1ScenarioSourceDir = await prepareGeneratedFlueV1ScenarioSource(); const flueScenarios = await Promise.all([ prepareFlueScenario({ cliPackageName: "@flue/cli", - expectAmbientContext: false, - label: "v0.8 pinned", - runtimePackageName: "@flue/runtime", - sourceDir: originalScenarioDir, - supportsAutoInstrumentation: true, - variantKey: "flue-v0-8-0", - }), - prepareFlueScenario({ - cliPackageName: "flue-cli-v0-8-latest", - expectAmbientContext: false, - label: "v0.8 latest", - modelName: "openai/gpt-5.4-nano", - runtimePackageName: "flue-runtime-v0-8-latest", - sourceDir: originalScenarioDir, - supportsAutoInstrumentation: true, - variantKey: "flue-v0-8-latest", - }), - prepareFlueScenario({ - cliPackageName: "flue-cli-v1", - expectAmbientContext: true, label: "v1 pinned", - runtimePackageName: "flue-runtime-v1", - sourceDir: flueV1ScenarioSourceDir, - supportsAutoInstrumentation: false, + runtimePackageName: "@flue/runtime", variantKey: "flue-v1-0-0-beta-3", }), prepareFlueScenario({ cliPackageName: "flue-cli-v1-latest", - expectAmbientContext: true, label: "v1 latest", modelName: "openai/gpt-5.4-nano", runtimePackageName: "flue-runtime-v1-latest", - sourceDir: flueV1ScenarioSourceDir, - supportsAutoInstrumentation: false, variantKey: "flue-v1-latest", }), ]); @@ -80,38 +50,18 @@ const flueV2Scenarios = await Promise.all( })), ); -describe.sequential("flue variants", () => { +describe("flue variants", { concurrent: false }, () => { for (const scenario of flueScenarios) { - describe.sequential(`flue ${scenario.label} (${scenario.version})`, () => { - defineFlueInstrumentationAssertions({ - expectAmbientContext: scenario.expectAmbientContext, - name: "explicit instrumentation", - runScenario: async ({ runScenarioDir }) => { - await runScenarioDir({ - entry: "scenario.ts", - env: scenario.env, - runContext: { - originalScenarioDir, - variantKey: scenario.variantKey, - }, - scenarioDir: scenario.scenarioDir, - timeoutMs: TIMEOUT_MS, - }); - }, - snapshotName: `${scenario.variantKey}-explicit`, - testFileUrl: import.meta.url, - timeoutMs: TIMEOUT_MS, - }); - - if (scenario.supportsAutoInstrumentation) { + describe( + `flue ${scenario.label} (${scenario.version})`, + { concurrent: false }, + () => { defineFlueInstrumentationAssertions({ - expectAmbientContext: scenario.expectAmbientContext, - name: "auto-hook instrumentation", - runScenario: async ({ runNodeScenarioDir }) => { - await runNodeScenarioDir({ - entry: "scenario.mjs", + name: "explicit instrumentation", + runScenario: async ({ runScenarioDir }) => { + await runScenarioDir({ + entry: "scenario.ts", env: scenario.env, - nodeArgs: ["--import", "braintrust/hook.mjs"], runContext: { originalScenarioDir, variantKey: scenario.variantKey, @@ -120,66 +70,41 @@ describe.sequential("flue variants", () => { timeoutMs: TIMEOUT_MS, }); }, - snapshotName: `${scenario.variantKey}-auto-hook`, + snapshotName: `${scenario.variantKey}-explicit`, testFileUrl: import.meta.url, timeoutMs: TIMEOUT_MS, }); + }, + ); + } - defineFlueInstrumentationAssertions({ - expectAmbientContext: scenario.expectAmbientContext, - name: "cli instrumentation", - runScenario: async ({ runNodeScenarioDir }) => { - await runNodeScenarioDir({ - entry: "scenario.cli.mjs", - env: { - ...scenario.env, - ...(scenario.inputFlag - ? { FLUE_E2E_INPUT_FLAG: scenario.inputFlag } - : {}), - }, - runContext: { - originalScenarioDir, - variantKey: scenario.variantKey, - }, - scenarioDir: scenario.scenarioDir, - timeoutMs: TIMEOUT_MS, - }); - }, - snapshotName: `${scenario.variantKey}-cli`, + for (const scenario of flueV2Scenarios) { + describe( + `flue ${scenario.label} (${scenario.version})`, + { concurrent: false }, + () => { + defineFlueV2InstrumentationAssertions({ + originalScenarioDir, + runtimePackageName: scenario.dependencyName, + scenarioDir: flueV2ScenarioDir, + snapshotName: scenario.variantKey, testFileUrl: import.meta.url, timeoutMs: TIMEOUT_MS, }); - } - }); - } - - for (const scenario of flueV2Scenarios) { - describe.sequential(`flue ${scenario.label} (${scenario.version})`, () => { - defineFlueV2InstrumentationAssertions({ - originalScenarioDir, - runtimePackageName: scenario.dependencyName, - scenarioDir: flueV2ScenarioDir, - snapshotName: scenario.variantKey, - testFileUrl: import.meta.url, - timeoutMs: TIMEOUT_MS, - }); - }); + }, + ); } }); async function prepareFlueScenario(options: { cliPackageName: string; - expectAmbientContext: boolean; - inputFlag?: string; label: string; modelName?: string; runtimePackageName: string; - sourceDir: string; - supportsAutoInstrumentation: boolean; variantKey: string; }) { const scenarioDir = await prepareScenarioDir({ - scenarioDir: options.sourceDir, + scenarioDir: originalScenarioDir, }); const [cliPackageDir, runtimePackageDir] = await Promise.all([ fs.realpath(path.join(scenarioDir, "node_modules", options.cliPackageName)), @@ -203,7 +128,6 @@ async function prepareFlueScenario(options: { ), ]); return { - ...options, env: { FLUE_CLI_PACKAGE_NAME: options.cliPackageName, FLUE_RUNTIME_PACKAGE_NAME: options.runtimePackageName, @@ -216,39 +140,12 @@ async function prepareFlueScenario(options: { } : {}), }, + label: options.label, scenarioDir, + variantKey: options.variantKey, version: await readInstalledPackageVersion( scenarioDir, options.runtimePackageName, ), }; } - -async function prepareGeneratedFlueV1ScenarioSource() { - const sourceDir = path.join(generatedScenarioRoot, "flue-v1"); - await fs.rm(sourceDir, { force: true, recursive: true }); - await fs.mkdir(sourceDir, { recursive: true }); - await fs.cp(originalScenarioDir, sourceDir, { - filter(source) { - const relative = path.relative(originalScenarioDir, source); - return ( - relative === "" || - !["__cassettes__", "__snapshots__", "node_modules", "versions"].some( - (name) => - relative === name || relative.startsWith(`${name}${path.sep}`), - ) - ); - }, - recursive: true, - }); - await fs.cp(path.join(originalScenarioDir, "versions", "v1"), sourceDir, { - filter(source) { - return ( - source !== - path.join(originalScenarioDir, "versions", "v1", "package.json") - ); - }, - recursive: true, - }); - return sourceDir; -} diff --git a/e2e/scenarios/flue-instrumentation/scenario.ts b/e2e/scenarios/flue-instrumentation/scenario.ts index b96ed4882..a5e57e15d 100644 --- a/e2e/scenarios/flue-instrumentation/scenario.ts +++ b/e2e/scenarios/flue-instrumentation/scenario.ts @@ -1,3 +1,3 @@ -import { runExplicitFlueInstrumentation, runMain } from "./scenario.impl.mjs"; +import { runFlueInstrumentationScenario } from "./scenario.impl.mjs"; -runMain(runExplicitFlueInstrumentation); +await runFlueInstrumentationScenario(); diff --git a/e2e/scenarios/flue-instrumentation/scenario.v2.ts b/e2e/scenarios/flue-instrumentation/scenario.v2.ts index b45091536..6e970f418 100644 --- a/e2e/scenarios/flue-instrumentation/scenario.v2.ts +++ b/e2e/scenarios/flue-instrumentation/scenario.v2.ts @@ -1,10 +1,6 @@ import { openaiProvider } from "pi-ai-v2/providers/openai"; -import { - braintrustFlueInstrumentation, - flush, - initLogger, - traced, -} from "braintrust"; +import { flush, initLogger, traced } from "braintrust"; +import { braintrustFlueInstrumentation } from "braintrust/instrumentation"; import * as v from "valibot"; const SCENARIO_NAME = "flue-instrumentation"; diff --git a/e2e/scenarios/flue-instrumentation/v2-assertions.ts b/e2e/scenarios/flue-instrumentation/v2-assertions.ts index 92f69426f..b6e9d0f62 100644 --- a/e2e/scenarios/flue-instrumentation/v2-assertions.ts +++ b/e2e/scenarios/flue-instrumentation/v2-assertions.ts @@ -1,4 +1,4 @@ -import { beforeAll, describe, expect, test } from "vitest"; +import { beforeAll, describe, expect, test } from "vite-plus/test"; import type { CapturedLogEvent } from "../../helpers/mock-braintrust-server"; import { resolveFileSnapshotPath } from "../../helpers/file-snapshot"; import { @@ -34,7 +34,7 @@ export function defineFlueV2InstrumentationAssertions(options: { `${options.snapshotName}.span-tree.json`, ); - describe.sequential("explicit native instrumentation", () => { + describe("explicit native instrumentation", { concurrent: false }, () => { let events: CapturedLogEvent[] = []; let testRunId: string; diff --git a/e2e/scenarios/flue-instrumentation/versions/v1/.flue/app.mjs b/e2e/scenarios/flue-instrumentation/versions/v1/.flue/app.mjs deleted file mode 100644 index 6680c7ae8..000000000 --- a/e2e/scenarios/flue-instrumentation/versions/v1/.flue/app.mjs +++ /dev/null @@ -1,112 +0,0 @@ -import { flush, initLogger } from "braintrust"; -import { Hono } from "hono"; -import { mkdir, writeFile } from "node:fs/promises"; -import { dirname } from "node:path"; - -const runtimePackageName = - process.env.FLUE_RUNTIME_PACKAGE_NAME ?? "flue-runtime-v1"; -const [{ instrument, registerProvider }, { flue }] = await Promise.all([ - import(runtimePackageName), - import(`${runtimePackageName}/routing`), -]); - -function projectName() { - const configured = process.env.BRAINTRUST_E2E_PROJECT_NAME; - if (configured) { - return configured; - } - const testRunId = process.env.BRAINTRUST_E2E_RUN_ID ?? "local"; - return `e2e-flue-instrumentation-${testRunId.toLowerCase().replace(/[^a-z0-9-]/g, "-")}`; -} - -initLogger({ projectName: projectName() }); - -const exitProcess = process.exit.bind(process); -if (process.env.FLUE_E2E_FLUSH_FILE) { - let isExiting = false; - process.exit = (code) => { - if (isExiting) { - return exitProcess(code); - } - isExiting = true; - const keepAlive = setTimeout(() => {}, 30_000); - void flushBeforeExit() - .catch((error) => { - console.error(error); - }) - .finally(() => { - clearTimeout(keepAlive); - exitProcess(code); - }); - }; -} - -if (process.env.FLUE_E2E_EXPLICIT_OBSERVE === "1") { - const { braintrustFlueInstrumentation } = await import("braintrust"); - instrument(braintrustFlueInstrumentation()); -} - -const openAIBaseUrl = - process.env.OPENAI_BASE_URL ?? process.env.BRAINTRUST_E2E_MODEL_BASE_URL; -if (openAIBaseUrl) { - registerProvider("openai", { baseUrl: openAIBaseUrl }); -} - -const anthropicBaseUrl = process.env.ANTHROPIC_BASE_URL; -if (anthropicBaseUrl) { - registerProvider("anthropic", { - apiKey: process.env.ANTHROPIC_API_KEY ?? "test-key", - baseUrl: anthropicBaseUrl, - }); -} - -let didScheduleFlush = false; -function scheduleFinalFlush(exitAfterFlush = false) { - if (didScheduleFlush) { - return; - } - didScheduleFlush = true; - const keepAlive = setTimeout(() => {}, 30_000); - void flushBeforeExit() - .catch((error) => { - console.error(error); - }) - .finally(() => { - clearTimeout(keepAlive); - if (exitAfterFlush) { - exitProcess(0); - } - }); -} - -process.on("SIGTERM", () => { - scheduleFinalFlush(true); -}); - -process.on("beforeExit", () => { - scheduleFinalFlush(); -}); - -const app = new Hono(); -app.route("/", flue()); - -async function flushBeforeExit() { - await new Promise((resolve) => setTimeout(resolve, 250)); - await flush(); - if (process.env.FLUE_E2E_FLUSH_FILE) { - await mkdir(dirname(process.env.FLUE_E2E_FLUSH_FILE), { - recursive: true, - }); - await writeFile(process.env.FLUE_E2E_FLUSH_FILE, "ok"); - } -} - -export default { - async fetch(request, env, ctx) { - if (new URL(request.url).pathname === "/__braintrust_flush") { - await flush(); - return new Response("ok"); - } - return app.fetch(request, env, ctx); - }, -}; diff --git a/e2e/scenarios/flue-instrumentation/versions/v1/.flue/workflows/instrumentation.mjs b/e2e/scenarios/flue-instrumentation/versions/v1/.flue/workflows/instrumentation.mjs deleted file mode 100644 index 2c8660430..000000000 --- a/e2e/scenarios/flue-instrumentation/versions/v1/.flue/workflows/instrumentation.mjs +++ /dev/null @@ -1,183 +0,0 @@ -import { traced } from "braintrust"; -import * as v from "valibot"; -import { - FLUE_MODEL, - FLUE_REASONING_MODEL, - SCENARIO_NAME, -} from "../../constants.mjs"; - -const runtimePackageName = - process.env.FLUE_RUNTIME_PACKAGE_NAME ?? "flue-runtime-v1"; -const [{ defineAgent, defineTool, defineWorkflow }, { local }] = - await Promise.all([ - import(runtimePackageName), - import(`${runtimePackageName}/node`), - ]); - -function flueModel() { - return process.env.FLUE_E2E_MODEL ?? FLUE_MODEL; -} - -function flueReasoningModel() { - return process.env.FLUE_E2E_REASONING_MODEL ?? FLUE_REASONING_MODEL; -} - -function fluePromptModel() { - return process.env.FLUE_E2E_PROMPT_MODEL ?? flueReasoningModel(); -} - -function fluePromptThinkingLevel() { - return ( - process.env.FLUE_E2E_PROMPT_THINKING_LEVEL ?? flueReasoningThinkingLevel() - ); -} - -function flueReasoningThinkingLevel() { - return process.env.FLUE_E2E_REASONING_THINKING_LEVEL ?? "medium"; -} - -const flueE2EAgent = defineAgent(() => ({ - compaction: { - keepRecentTokens: 1, - reserveTokens: 64, - }, - cwd: process.cwd(), - instructions: [ - "You are a deterministic Flue instrumentation test agent.", - "Follow user instructions exactly.", - "When asked for a marker, output only that marker and no extra text.", - "When running a local skill file, read it yourself and do not delegate it to a task.", - ].join(" "), - model: flueModel(), - sandbox: local({ cwd: process.cwd() }), - thinkingLevel: "off", -})); - -const lookupTool = defineTool({ - description: - "Return a deterministic lookup result with an id needed by web_search.", - input: v.object({ - query: v.string(), - }), - name: "lookup", - run: async ({ input }) => { - await traced( - async (span) => { - span.log({ output: "lookup-active" }); - }, - { - name: "flue.toolCurrentProbe", - event: { - metadata: { - scenario: SCENARIO_NAME, - }, - }, - }, - ); - - return { - id: "flue-session-2026", - query: input.query, - topic: "session instrumentation", - }; - }, -}); - -const webSearchTool = defineTool({ - description: - "Search a deterministic local web index. Requires the id returned by lookup.", - input: v.object({ - lookupId: v.string(), - query: v.string(), - }), - name: "web_search", - run: async ({ input }) => ({ - lookupId: input.lookupId, - query: input.query, - results: [ - { - title: "Flue reasoning stream instrumentation", - url: "https://example.test/flue/reasoning-streams", - }, - ], - }), -}); - -const summarizeSourceTool = defineTool({ - description: - "Summarize the selected deterministic source after web_search returns a URL.", - input: v.object({ - url: v.string(), - }), - name: "summarize_source", - run: async ({ input }) => ({ - summary: - "Flue emits reasoning, tool execution, and LLM turn events separately.", - url: input.url, - }), -}); - -export async function route(_ctx, next) { - await next(); -} - -export default defineWorkflow({ - agent: flueE2EAgent, - input: v.object({ - scenario: v.optional(v.string()), - metadata: v.optional(v.record(v.string(), v.unknown())), - }), - async run({ harness, input }) { - await traced( - async (span) => { - span.log({ output: "active" }); - }, - { - name: "flue.workflowCurrentProbe", - event: { - metadata: { - scenario: SCENARIO_NAME, - }, - }, - }, - ); - - const session = await harness.session("main"); - const skillSession = await harness.session("skill"); - const taskSession = await harness.session("task"); - - await session.prompt( - [ - "Complete this instrumented research flow.", - "Call exactly one tool per turn and wait for each tool result before choosing the next tool.", - 'Step 1: call lookup with query "flue instrumentation".', - 'Step 2: use the lookup result id as lookupId and call web_search with query "Braintrust Flue reasoning stream instrumentation".', - "Step 3: use the first web_search result url and call summarize_source.", - "After summarize_source returns, reply with exactly PROMPT_DONE and no other text.", - ].join(" "), - { - model: fluePromptModel(), - thinkingLevel: fluePromptThinkingLevel(), - tools: [lookupTool, webSearchTool, summarizeSourceTool], - }, - ); - - await skillSession.skill("e2e-flue-skill", { - args: { marker: "SKILL_DONE" }, - model: flueReasoningModel(), - thinkingLevel: "off", - }); - - await taskSession.task("Reply with exactly TASK_DONE and no other text.", { - model: FLUE_MODEL, - thinkingLevel: "off", - }); - - await session.compact(); - - return { - scenario: input.scenario ?? SCENARIO_NAME, - status: "done", - }; - }, -}); diff --git a/e2e/scenarios/genkit-instrumentation/assertions.ts b/e2e/scenarios/genkit-instrumentation/assertions.ts index 749521af1..a92d1ad72 100644 --- a/e2e/scenarios/genkit-instrumentation/assertions.ts +++ b/e2e/scenarios/genkit-instrumentation/assertions.ts @@ -1,4 +1,4 @@ -import { beforeAll, describe, expect, test } from "vitest"; +import { beforeAll, describe, expect, test } from "vite-plus/test"; import type { CapturedLogEvent } from "../../helpers/mock-braintrust-server"; import { resolveFileSnapshotPath } from "../../helpers/file-snapshot"; import { diff --git a/e2e/scenarios/genkit-instrumentation/scenario.test.ts b/e2e/scenarios/genkit-instrumentation/scenario.test.ts index 7147427d0..ba7f8ec7f 100644 --- a/e2e/scenarios/genkit-instrumentation/scenario.test.ts +++ b/e2e/scenarios/genkit-instrumentation/scenario.test.ts @@ -1,4 +1,4 @@ -import { describe } from "vitest"; +import { describe } from "vite-plus/test"; import { describeVariant } from "../../helpers/describe-variant"; import { prepareScenarioDir, diff --git a/e2e/scenarios/git-metadata/scenario.test.ts b/e2e/scenarios/git-metadata/scenario.test.ts index 14f3036f9..8c0179df0 100644 --- a/e2e/scenarios/git-metadata/scenario.test.ts +++ b/e2e/scenarios/git-metadata/scenario.test.ts @@ -1,4 +1,4 @@ -import { expect, test } from "vitest"; +import { expect, test } from "vite-plus/test"; import { prepareScenarioDir, resolveScenarioDir, diff --git a/e2e/scenarios/github-copilot-instrumentation/assertions.ts b/e2e/scenarios/github-copilot-instrumentation/assertions.ts index 671e4a370..38b065e00 100644 --- a/e2e/scenarios/github-copilot-instrumentation/assertions.ts +++ b/e2e/scenarios/github-copilot-instrumentation/assertions.ts @@ -1,4 +1,4 @@ -import { beforeAll, describe, expect, test } from "vitest"; +import { beforeAll, describe, expect, test } from "vite-plus/test"; import type { CapturedLogEvent } from "../../helpers/mock-braintrust-server"; import { resolveFileSnapshotPath } from "../../helpers/file-snapshot"; import { diff --git a/e2e/scenarios/github-copilot-instrumentation/scenario.test.ts b/e2e/scenarios/github-copilot-instrumentation/scenario.test.ts index e0061c3fe..589952c23 100644 --- a/e2e/scenarios/github-copilot-instrumentation/scenario.test.ts +++ b/e2e/scenarios/github-copilot-instrumentation/scenario.test.ts @@ -1,4 +1,4 @@ -import { describe } from "vitest"; +import { describe } from "vite-plus/test"; import { describeVariant } from "../../helpers/describe-variant"; import { prepareScenarioDir, diff --git a/e2e/scenarios/google-adk-instrumentation/assertions.ts b/e2e/scenarios/google-adk-instrumentation/assertions.ts index b66ea93bf..ed931aca6 100644 --- a/e2e/scenarios/google-adk-instrumentation/assertions.ts +++ b/e2e/scenarios/google-adk-instrumentation/assertions.ts @@ -1,4 +1,4 @@ -import { beforeAll, describe, expect, test } from "vitest"; +import { beforeAll, describe, expect, test } from "vite-plus/test"; import type { Json } from "../../helpers/normalize"; import type { CapturedLogEvent } from "../../helpers/mock-braintrust-server"; import { resolveFileSnapshotPath } from "../../helpers/file-snapshot"; diff --git a/e2e/scenarios/google-adk-instrumentation/scenario.test.ts b/e2e/scenarios/google-adk-instrumentation/scenario.test.ts index 35fe51297..f480da82d 100644 --- a/e2e/scenarios/google-adk-instrumentation/scenario.test.ts +++ b/e2e/scenarios/google-adk-instrumentation/scenario.test.ts @@ -1,4 +1,4 @@ -import { describe } from "vitest"; +import { describe } from "vite-plus/test"; import { describeVariant } from "../../helpers/describe-variant"; import { prepareScenarioDir, diff --git a/e2e/scenarios/google-genai-instrumentation/assertions.ts b/e2e/scenarios/google-genai-instrumentation/assertions.ts index 2f4d3ec63..3ddc9ea82 100644 --- a/e2e/scenarios/google-genai-instrumentation/assertions.ts +++ b/e2e/scenarios/google-genai-instrumentation/assertions.ts @@ -2,7 +2,7 @@ import { EMBEDDING_DIMENSIONS, GOOGLE_MULTIMODAL_EMBEDDING_MODEL, } from "./embeddings.mjs"; -import { beforeAll, describe, expect, test } from "vitest"; +import { beforeAll, describe, expect, test } from "vite-plus/test"; import type { Json } from "../../helpers/normalize"; import type { CapturedLogEvent } from "../../helpers/mock-braintrust-server"; import { resolveFileSnapshotPath } from "../../helpers/file-snapshot"; diff --git a/e2e/scenarios/google-genai-instrumentation/scenario.test.ts b/e2e/scenarios/google-genai-instrumentation/scenario.test.ts index 7863a372f..d649ce6d4 100644 --- a/e2e/scenarios/google-genai-instrumentation/scenario.test.ts +++ b/e2e/scenarios/google-genai-instrumentation/scenario.test.ts @@ -1,4 +1,4 @@ -import { describe } from "vitest"; +import { describe } from "vite-plus/test"; import { prepareScenarioDir, readInstalledPackageVersion, @@ -48,15 +48,19 @@ const googleGenAIScenarios = await Promise.all( describe.concurrent("variants", () => { for (const scenario of googleGenAIScenarios) { - describe.sequential( + describe( `${scenario.snapshotName}: google genai sdk ${scenario.version}`, + { concurrent: false }, () => { defineGoogleGenAIInstrumentationAssertions({ name: "wrapped instrumentation", runScenario: async ({ runScenarioDir }) => { await runScenarioDir({ entry: scenario.wrapperEntry, - env: { GOOGLE_GENAI_PACKAGE_NAME: scenario.dependencyName }, + env: { + GOOGLE_GENAI_PACKAGE_NAME: scenario.dependencyName, + BRAINTRUST_AUTO_CAPTURE_ATTACHMENTS: "true", + }, runContext: { variantKey: scenario.snapshotName, originalScenarioDir, @@ -75,7 +79,10 @@ describe.concurrent("variants", () => { runScenario: async ({ runNodeScenarioDir }) => { await runNodeScenarioDir({ entry: scenario.autoEntry, - env: { GOOGLE_GENAI_PACKAGE_NAME: scenario.dependencyName }, + env: { + GOOGLE_GENAI_PACKAGE_NAME: scenario.dependencyName, + BRAINTRUST_AUTO_CAPTURE_ATTACHMENTS: "true", + }, nodeArgs: ["--import", "braintrust/hook.mjs"], runContext: { variantKey: scenario.snapshotName, diff --git a/e2e/scenarios/google-generative-ai-instrumentation/assertions.ts b/e2e/scenarios/google-generative-ai-instrumentation/assertions.ts index 7208902b9..e16310e8a 100644 --- a/e2e/scenarios/google-generative-ai-instrumentation/assertions.ts +++ b/e2e/scenarios/google-generative-ai-instrumentation/assertions.ts @@ -1,4 +1,4 @@ -import { beforeAll, describe, expect, test } from "vitest"; +import { beforeAll, describe, expect, test } from "vite-plus/test"; import type { CapturedLogEvent } from "../../helpers/mock-braintrust-server"; import { effectiveScenarioTimeoutMs, diff --git a/e2e/scenarios/google-generative-ai-instrumentation/scenario.test.ts b/e2e/scenarios/google-generative-ai-instrumentation/scenario.test.ts index a21458f00..2c67e6247 100644 --- a/e2e/scenarios/google-generative-ai-instrumentation/scenario.test.ts +++ b/e2e/scenarios/google-generative-ai-instrumentation/scenario.test.ts @@ -1,4 +1,4 @@ -import { describe } from "vitest"; +import { describe } from "vite-plus/test"; import { describeVariant } from "../../helpers/describe-variant"; import { prepareScenarioDir, @@ -31,7 +31,10 @@ describe.concurrent("variants", () => { runScenario: async ({ runScenarioDir }) => { await runScenarioDir({ entry: "scenario.ts", - env: { GOOGLE_GENERATIVE_AI_PACKAGE_NAME: scenario.dependencyName }, + env: { + BRAINTRUST_AUTO_CAPTURE_ATTACHMENTS: "true", + GOOGLE_GENERATIVE_AI_PACKAGE_NAME: scenario.dependencyName, + }, runContext: { originalScenarioDir, variantKey: scenario.snapshotName, @@ -51,7 +54,10 @@ describe.concurrent("variants", () => { await runNodeScenarioDir({ entry: "scenario.mjs", nodeArgs: ["--import", "braintrust/hook.mjs"], - env: { GOOGLE_GENERATIVE_AI_PACKAGE_NAME: scenario.dependencyName }, + env: { + GOOGLE_GENERATIVE_AI_PACKAGE_NAME: scenario.dependencyName, + BRAINTRUST_AUTO_CAPTURE_ATTACHMENTS: "true", + }, runContext: { originalScenarioDir, variantKey: scenario.snapshotName, diff --git a/e2e/scenarios/groq-instrumentation/assertions.ts b/e2e/scenarios/groq-instrumentation/assertions.ts index 8f22fc555..0fdd2a5bf 100644 --- a/e2e/scenarios/groq-instrumentation/assertions.ts +++ b/e2e/scenarios/groq-instrumentation/assertions.ts @@ -1,4 +1,4 @@ -import { beforeAll, describe, expect, test } from "vitest"; +import { beforeAll, describe, expect, test } from "vite-plus/test"; import type { CapturedLogEvent } from "../../helpers/mock-braintrust-server"; import { resolveFileSnapshotPath } from "../../helpers/file-snapshot"; import { diff --git a/e2e/scenarios/groq-instrumentation/scenario.test.ts b/e2e/scenarios/groq-instrumentation/scenario.test.ts index e0d712c35..653f3cde8 100644 --- a/e2e/scenarios/groq-instrumentation/scenario.test.ts +++ b/e2e/scenarios/groq-instrumentation/scenario.test.ts @@ -1,4 +1,4 @@ -import { describe } from "vitest"; +import { describe } from "vite-plus/test"; import { describeVariant } from "../../helpers/describe-variant"; import { resolve } from "node:path"; import { @@ -45,6 +45,7 @@ describe.concurrent("variants", () => { await runScenarioDir({ entry: "scenario.ts", env: { + BRAINTRUST_AUTO_CAPTURE_ATTACHMENTS: "true", GROQ_AUDIO_FILE: audioFile, GROQ_PACKAGE_NAME: scenario.dependencyName, }, @@ -67,6 +68,7 @@ describe.concurrent("variants", () => { await runNodeScenarioDir({ entry: "scenario.mjs", env: { + BRAINTRUST_AUTO_CAPTURE_ATTACHMENTS: "true", GROQ_AUDIO_FILE: audioFile, GROQ_PACKAGE_NAME: scenario.dependencyName, }, diff --git a/e2e/scenarios/huggingface-instrumentation/assertions.ts b/e2e/scenarios/huggingface-instrumentation/assertions.ts index 8463c3970..ee843dc16 100644 --- a/e2e/scenarios/huggingface-instrumentation/assertions.ts +++ b/e2e/scenarios/huggingface-instrumentation/assertions.ts @@ -1,4 +1,4 @@ -import { beforeAll, describe, expect, test } from "vitest"; +import { beforeAll, describe, expect, test } from "vite-plus/test"; import { type Json } from "../../helpers/normalize"; import type { CapturedLogEvent } from "../../helpers/mock-braintrust-server"; import { resolveFileSnapshotPath } from "../../helpers/file-snapshot"; diff --git a/e2e/scenarios/huggingface-instrumentation/scenario.test.ts b/e2e/scenarios/huggingface-instrumentation/scenario.test.ts index 125fec463..22bc759f9 100644 --- a/e2e/scenarios/huggingface-instrumentation/scenario.test.ts +++ b/e2e/scenarios/huggingface-instrumentation/scenario.test.ts @@ -1,4 +1,4 @@ -import { describe } from "vitest"; +import { describe } from "vite-plus/test"; import { describeVariant } from "../../helpers/describe-variant"; import { prepareScenarioDir, diff --git a/e2e/scenarios/huggingface-transformers-instrumentation/assertions.ts b/e2e/scenarios/huggingface-transformers-instrumentation/assertions.ts index 9e587ece0..c4825ec53 100644 --- a/e2e/scenarios/huggingface-transformers-instrumentation/assertions.ts +++ b/e2e/scenarios/huggingface-transformers-instrumentation/assertions.ts @@ -1,4 +1,4 @@ -import { beforeAll, describe, expect, test } from "vitest"; +import { beforeAll, describe, expect, test } from "vite-plus/test"; import type { CapturedLogEvent } from "../../helpers/mock-braintrust-server"; import { resolveFileSnapshotPath } from "../../helpers/file-snapshot"; import { diff --git a/e2e/scenarios/huggingface-transformers-instrumentation/scenario.test.ts b/e2e/scenarios/huggingface-transformers-instrumentation/scenario.test.ts index 6e958efc9..14f926e1b 100644 --- a/e2e/scenarios/huggingface-transformers-instrumentation/scenario.test.ts +++ b/e2e/scenarios/huggingface-transformers-instrumentation/scenario.test.ts @@ -1,4 +1,4 @@ -import { describe } from "vitest"; +import { describe } from "vite-plus/test"; import { describeVariant } from "../../helpers/describe-variant"; import { prepareScenarioDir, @@ -68,6 +68,28 @@ describe.concurrent("variants", () => { timeoutMs: SCENARIO_TIMEOUT_MS, }); + defineAssertions({ + name: "auto-hook instrumentation ESM", + runScenario: async ({ runNodeScenarioDir }) => { + await runNodeScenarioDir({ + entry: "scenario.auto.mjs", + env: { + HUGGINGFACE_TRANSFORMERS_PACKAGE_NAME: scenario.dependencyName, + }, + nodeArgs: ["--import", "braintrust/hook.mjs"], + runContext: { + originalScenarioDir, + variantKey: scenario.snapshotName, + }, + scenarioDir, + timeoutMs: SCENARIO_TIMEOUT_MS, + }); + }, + snapshotName: `${scenario.snapshotName}-auto-esm`, + testFileUrl: import.meta.url, + timeoutMs: SCENARIO_TIMEOUT_MS, + }); + if (scenario.runCjsAutoInstrumentation) { defineAssertions({ name: "auto-hook instrumentation CJS", diff --git a/e2e/scenarios/langchain-anthropic/scenario.test.ts b/e2e/scenarios/langchain-anthropic/scenario.test.ts index 2b312ae20..29101ba3a 100644 --- a/e2e/scenarios/langchain-anthropic/scenario.test.ts +++ b/e2e/scenarios/langchain-anthropic/scenario.test.ts @@ -1,4 +1,4 @@ -import { expect, test } from "vitest"; +import { expect, test } from "vite-plus/test"; import { resolveFileSnapshotPath } from "../../helpers/file-snapshot"; import { prepareScenarioDir, diff --git a/e2e/scenarios/langgraph-auto-instrumentation/assertions.ts b/e2e/scenarios/langgraph-auto-instrumentation/assertions.ts index cd94843e8..0c9996f16 100644 --- a/e2e/scenarios/langgraph-auto-instrumentation/assertions.ts +++ b/e2e/scenarios/langgraph-auto-instrumentation/assertions.ts @@ -1,4 +1,4 @@ -import { expect } from "vitest"; +import { expect } from "vite-plus/test"; import { normalizeForSnapshot, type Json } from "../../helpers/normalize"; import type { CapturedLogEvent, diff --git a/e2e/scenarios/langgraph-auto-instrumentation/scenario.test.ts b/e2e/scenarios/langgraph-auto-instrumentation/scenario.test.ts index 4b4997d02..2c4a3df67 100644 --- a/e2e/scenarios/langgraph-auto-instrumentation/scenario.test.ts +++ b/e2e/scenarios/langgraph-auto-instrumentation/scenario.test.ts @@ -1,4 +1,4 @@ -import { expect, test } from "vitest"; +import { expect, test } from "vite-plus/test"; import { formatJsonFileSnapshot, resolveFileSnapshotPath, diff --git a/e2e/scenarios/langgraph-sdk-instrumentation/scenario.test.ts b/e2e/scenarios/langgraph-sdk-instrumentation/scenario.test.ts index 6e3dd9f40..7ef1819fd 100644 --- a/e2e/scenarios/langgraph-sdk-instrumentation/scenario.test.ts +++ b/e2e/scenarios/langgraph-sdk-instrumentation/scenario.test.ts @@ -3,7 +3,7 @@ import { findChildSpans, findLatestSpan, } from "../../helpers/trace-selectors"; -import { describe, expect, it } from "vitest"; +import { describe, expect, it } from "vite-plus/test"; import { describeVariant } from "../../helpers/describe-variant"; import { prepareScenarioDir, diff --git a/e2e/scenarios/langsmith-instrumentation/assertions.ts b/e2e/scenarios/langsmith-instrumentation/assertions.ts index c0f42ba73..1d3a3823d 100644 --- a/e2e/scenarios/langsmith-instrumentation/assertions.ts +++ b/e2e/scenarios/langsmith-instrumentation/assertions.ts @@ -1,4 +1,4 @@ -import { beforeAll, describe, expect, test } from "vitest"; +import { beforeAll, describe, expect, test } from "vite-plus/test"; import type { CapturedLogEvent } from "../../helpers/mock-braintrust-server"; import { resolveFileSnapshotPath } from "../../helpers/file-snapshot"; import { withScenarioHarness } from "../../helpers/scenario-harness"; diff --git a/e2e/scenarios/langsmith-instrumentation/scenario.test.ts b/e2e/scenarios/langsmith-instrumentation/scenario.test.ts index 1d2bc5ac4..1cb4b9ca3 100644 --- a/e2e/scenarios/langsmith-instrumentation/scenario.test.ts +++ b/e2e/scenarios/langsmith-instrumentation/scenario.test.ts @@ -1,4 +1,4 @@ -import { describe } from "vitest"; +import { describe } from "vite-plus/test"; import { describeVariant } from "../../helpers/describe-variant"; import { prepareScenarioDir, diff --git a/e2e/scenarios/livekit-instrumentation/assertions.ts b/e2e/scenarios/livekit-instrumentation/assertions.ts index 7e2c1a7ef..43bebbb8e 100644 --- a/e2e/scenarios/livekit-instrumentation/assertions.ts +++ b/e2e/scenarios/livekit-instrumentation/assertions.ts @@ -1,4 +1,4 @@ -import { expect } from "vitest"; +import { expect } from "vite-plus/test"; import type { CapturedLogEvent } from "../../helpers/mock-braintrust-server"; import { findAllSpans } from "../../helpers/trace-selectors"; diff --git a/e2e/scenarios/livekit-instrumentation/scenario.mjs b/e2e/scenarios/livekit-instrumentation/scenario.mjs index 50943b837..9ed252578 100644 --- a/e2e/scenarios/livekit-instrumentation/scenario.mjs +++ b/e2e/scenarios/livekit-instrumentation/scenario.mjs @@ -64,7 +64,7 @@ function oggDurationMs(file) { const view = new DataView(file.buffer, file.byteOffset, file.byteLength); const preSkip = view.getUint16(28 + 10, true); let granule = 0; - for (let at = 0; at < file.length; ) { + for (let at = 0; at < file.length;) { granule = Number(view.getBigUint64(at + 6, true)); const count = file[at + 26]; at += file diff --git a/e2e/scenarios/livekit-instrumentation/scenario.test.ts b/e2e/scenarios/livekit-instrumentation/scenario.test.ts index 8e22e3d4b..11e63863c 100644 --- a/e2e/scenarios/livekit-instrumentation/scenario.test.ts +++ b/e2e/scenarios/livekit-instrumentation/scenario.test.ts @@ -1,6 +1,6 @@ import { promises as fs } from "node:fs"; import path from "node:path"; -import { expect, test } from "vitest"; +import { expect, test } from "vite-plus/test"; import { assertAudioTrace } from "./assertions"; import { prepareScenarioDir, @@ -162,8 +162,9 @@ const spoken = (event: CapturedLogEvent | undefined, pattern: RegExp) => for (const variant of variants) { // Fixture playback through each setup path, including idempotent double wrapping. for (const mode of ["auto", "wrapped", "wrapped-auto"] as const) - test.sequential( + test( `${variant} ${mode} fixture recording`, + { concurrent: false, timeout: 120000 }, async () => { await withScenarioHarness(async (harness) => { await harness.runNodeScenarioDir(scenarioOptions(variant, { mode })); @@ -191,7 +192,6 @@ for (const variant of variants) { await snapshotTrace(events, `${variant}-audio`); }); }, - 120000, ); // With @braintrust/audio installed, recordings upload as Ogg Opus. @@ -202,8 +202,9 @@ for (const variant of variants) { { codec: { name: "opus", options: { bitrate: 16000 } } }, ], ] as const) - test.sequential( + test( `${variant} compressed recording ${name}`, + { concurrent: false, timeout: 120000 }, async () => { await withScenarioHarness(async (harness) => { const result = await harness.runNodeScenarioDir( @@ -244,12 +245,12 @@ for (const variant of variants) { expect(result.stderr).not.toContain("uncompressed PCM"); }); }, - 120000, ); // Turning compression off records raw PCM, without a warning. - test.sequential( + test( `${variant} raw PCM recording with codec pcm`, + { concurrent: false, timeout: 120000 }, async () => { await withScenarioHarness(async (harness) => { const result = await harness.runNodeScenarioDir( @@ -276,18 +277,18 @@ for (const variant of variants) { expect(result.stderr).not.toContain("uncompressed PCM"); }); }, - 120000, ); - test.sequential( + test( `${variant} recording requires attachment capture`, + { concurrent: false, timeout: 120000 }, async () => { await withScenarioHarness(async (harness) => { // The shared attachment flag alone never records calls. await harness.runNodeScenarioDir( scenarioOptions(variant, { recording: false, - env: { BRAINTRUST_CAPTURE_ATTACHMENTS: "true" }, + env: { BRAINTRUST_AUTO_CAPTURE_ATTACHMENTS: "true" }, }), ); const events = assertAudioTrace(harness.events()); @@ -297,12 +298,12 @@ for (const variant of variants) { await snapshotTrace(events, `${variant}-disabled`); }); }, - 120000, ); for (const mode of ["auto", "wrapped"] as const) - test.sequential( + test( `${variant} ${mode} order conversation`, + { concurrent: false, timeout: 120000 }, async () => { await withScenarioHarness(async (harness) => { await harness.runNodeScenarioDir( @@ -316,11 +317,11 @@ for (const variant of variants) { await snapshotTrace(events, `${variant}-order`); }); }, - 120000, ); - test.sequential( + test( `${variant} streaming-cascade order conversation`, + { concurrent: false, timeout: 120000 }, async () => { await withScenarioHarness(async (harness) => { await harness.runNodeScenarioDir( @@ -358,12 +359,12 @@ for (const variant of variants) { await snapshotTrace(events, `${variant}-streaming-cascade`); }); }, - 120000, ); for (const recover of [false, true]) - test.sequential( + test( `${variant} ${recover ? "recovered" : "interrupted"} order answer`, + { concurrent: false, timeout: 120000 }, async () => { await withScenarioHarness(async (harness) => { await harness.runNodeScenarioDir( @@ -393,13 +394,13 @@ for (const variant of variants) { ); }); }, - 120000, ); // Rotation is anchored to session time, so a delayed playback start changes the // number of files. Assert complete, contiguous coverage instead of a snapshot. - test.sequential( + test( `${variant} progressive order recording`, + { concurrent: false, timeout: 120000 }, async () => { await withScenarioHarness(async (harness) => { const result = await harness.runNodeScenarioDir( @@ -482,13 +483,13 @@ for (const variant of variants) { } }); }, - 120000, ); // Automatic setup in other runtimes and next to an application OTel provider. for (const setup of ["unsampled", "integration-off", "worker"] as const) - test.sequential( + test( `${variant} automatic setup ${setup}`, + { concurrent: false, timeout: 90000 }, async () => { await withScenarioHarness(async (harness) => { await harness.runNodeScenarioDir( @@ -510,6 +511,5 @@ for (const variant of variants) { spoken(findLatestSpan(events, "agent_speech"), /Fixture playback/); }); }, - 90000, ); } diff --git a/e2e/scenarios/mastra-instrumentation/scenario.test.ts b/e2e/scenarios/mastra-instrumentation/scenario.test.ts index bd65452bc..028a429dd 100644 --- a/e2e/scenarios/mastra-instrumentation/scenario.test.ts +++ b/e2e/scenarios/mastra-instrumentation/scenario.test.ts @@ -1,4 +1,4 @@ -import { beforeAll, describe, expect, test } from "vitest"; +import { beforeAll, describe, expect, test } from "vite-plus/test"; import { formatJsonFileSnapshot, resolveFileSnapshotPath, diff --git a/e2e/scenarios/mcp-instrumentation/assertions.ts b/e2e/scenarios/mcp-instrumentation/assertions.ts index 1501b26b2..04870cafc 100644 --- a/e2e/scenarios/mcp-instrumentation/assertions.ts +++ b/e2e/scenarios/mcp-instrumentation/assertions.ts @@ -1,4 +1,4 @@ -import { beforeAll, describe, expect, test } from "vitest"; +import { beforeAll, describe, expect, test } from "vite-plus/test"; import type { CapturedLogEvent } from "../../helpers/mock-braintrust-server"; import { resolveFileSnapshotPath } from "../../helpers/file-snapshot"; import { diff --git a/e2e/scenarios/mcp-instrumentation/scenario.test.ts b/e2e/scenarios/mcp-instrumentation/scenario.test.ts index a8d6adf4c..ac58ae6a8 100644 --- a/e2e/scenarios/mcp-instrumentation/scenario.test.ts +++ b/e2e/scenarios/mcp-instrumentation/scenario.test.ts @@ -1,4 +1,4 @@ -import { describe } from "vitest"; +import { describe } from "vite-plus/test"; import { describeVariant } from "../../helpers/describe-variant"; import { prepareScenarioDir, diff --git a/e2e/scenarios/mistral-instrumentation/assertions.ts b/e2e/scenarios/mistral-instrumentation/assertions.ts index 0a4a8d894..7b0ffc16f 100644 --- a/e2e/scenarios/mistral-instrumentation/assertions.ts +++ b/e2e/scenarios/mistral-instrumentation/assertions.ts @@ -1,4 +1,4 @@ -import { beforeAll, describe, expect, test } from "vitest"; +import { beforeAll, describe, expect, test } from "vite-plus/test"; import type { CapturedLogEvent, CapturedLogRow, diff --git a/e2e/scenarios/mistral-instrumentation/scenario.test.ts b/e2e/scenarios/mistral-instrumentation/scenario.test.ts index 6049ae891..4ab73b2c0 100644 --- a/e2e/scenarios/mistral-instrumentation/scenario.test.ts +++ b/e2e/scenarios/mistral-instrumentation/scenario.test.ts @@ -1,4 +1,4 @@ -import { describe } from "vitest"; +import { describe } from "vite-plus/test"; import { describeVariant } from "../../helpers/describe-variant"; import { prepareScenarioDir, diff --git a/e2e/scenarios/nextjs-auto-instrumentation/scenario.test.ts b/e2e/scenarios/nextjs-auto-instrumentation/scenario.test.ts index 42046ad2e..7f614d6dd 100644 --- a/e2e/scenarios/nextjs-auto-instrumentation/scenario.test.ts +++ b/e2e/scenarios/nextjs-auto-instrumentation/scenario.test.ts @@ -1,6 +1,6 @@ import { promises as fs } from "node:fs"; import path from "node:path"; -import { describe, test } from "vitest"; +import { describe, test } from "vite-plus/test"; import { prepareScenarioDir, readInstalledPackageVersion, diff --git a/e2e/scenarios/nextjs-auto-instrumentation/template/app/api/test/route.ts b/e2e/scenarios/nextjs-auto-instrumentation/template/app/api/test/route.ts index e499491cb..7d48e7864 100644 --- a/e2e/scenarios/nextjs-auto-instrumentation/template/app/api/test/route.ts +++ b/e2e/scenarios/nextjs-auto-instrumentation/template/app/api/test/route.ts @@ -1,4 +1,3 @@ -import "braintrust"; // Registers Braintrust's global instrumentation hooks. import OpenAI from "openai"; import { createServer } from "node:http"; import type { AddressInfo } from "node:net"; diff --git a/e2e/scenarios/nextjs-instrumentation/scenario.test.ts b/e2e/scenarios/nextjs-instrumentation/scenario.test.ts index 193719a14..e4c5c7ec1 100644 --- a/e2e/scenarios/nextjs-instrumentation/scenario.test.ts +++ b/e2e/scenarios/nextjs-instrumentation/scenario.test.ts @@ -1,6 +1,6 @@ import { promises as fs } from "node:fs"; import path from "node:path"; -import { expect, test } from "vitest"; +import { expect, test } from "vite-plus/test"; import { formatJsonFileSnapshot, matchFileSnapshot, diff --git a/e2e/scenarios/ollama-instrumentation/assertions.ts b/e2e/scenarios/ollama-instrumentation/assertions.ts index a8dcd1df7..3d29b0947 100644 --- a/e2e/scenarios/ollama-instrumentation/assertions.ts +++ b/e2e/scenarios/ollama-instrumentation/assertions.ts @@ -1,4 +1,4 @@ -import { beforeAll, describe, expect, test } from "vitest"; +import { beforeAll, describe, expect, test } from "vite-plus/test"; import type { CapturedLogEvent } from "../../helpers/mock-braintrust-server"; import { resolveFileSnapshotPath } from "../../helpers/file-snapshot"; import { diff --git a/e2e/scenarios/ollama-instrumentation/scenario.test.ts b/e2e/scenarios/ollama-instrumentation/scenario.test.ts index 525d0f504..a1e2c4ca3 100644 --- a/e2e/scenarios/ollama-instrumentation/scenario.test.ts +++ b/e2e/scenarios/ollama-instrumentation/scenario.test.ts @@ -1,4 +1,4 @@ -import { describe } from "vitest"; +import { describe } from "vite-plus/test"; import { prepareScenarioDir, readInstalledPackageVersion, @@ -30,9 +30,9 @@ const ollamaScenarios = await Promise.all( })), ); -describe.sequential("variants", () => { +describe("variants", { concurrent: false }, () => { for (const scenario of ollamaScenarios) { - describe.sequential(`ollama sdk ${scenario.version}`, () => { + describe(`ollama sdk ${scenario.version}`, { concurrent: false }, () => { defineOllamaInstrumentationAssertions({ name: "wrapped instrumentation", runScenario: async ({ runScenarioDir }) => { diff --git a/e2e/scenarios/openai-agents-api-instrumentation/assertions.ts b/e2e/scenarios/openai-agents-api-instrumentation/assertions.ts index 2e21bbd38..ba53378a6 100644 --- a/e2e/scenarios/openai-agents-api-instrumentation/assertions.ts +++ b/e2e/scenarios/openai-agents-api-instrumentation/assertions.ts @@ -1,4 +1,4 @@ -import { beforeAll, describe, expect, test } from "vitest"; +import { beforeAll, describe, expect, test } from "vite-plus/test"; import { resolveFileSnapshotPath } from "../../helpers/file-snapshot"; import type { CapturedLogEvent } from "../../helpers/mock-braintrust-server"; import { diff --git a/e2e/scenarios/openai-agents-api-instrumentation/scenario.test.ts b/e2e/scenarios/openai-agents-api-instrumentation/scenario.test.ts index 732bf7df8..288d4ac2b 100644 --- a/e2e/scenarios/openai-agents-api-instrumentation/scenario.test.ts +++ b/e2e/scenarios/openai-agents-api-instrumentation/scenario.test.ts @@ -1,4 +1,4 @@ -import { describe } from "vitest"; +import { describe } from "vite-plus/test"; import { prepareScenarioDir, readInstalledPackageVersion, @@ -32,8 +32,9 @@ const scenarios = await Promise.all( describe.concurrent("variants", () => { for (const scenario of scenarios) { - describe.sequential( + describe( `${scenario.variantKey}: OpenAI Agents API SDK ${scenario.version}`, + { concurrent: false }, () => { defineOpenAIAgentsAPIInstrumentationAssertions({ name: "manual instrumentation", diff --git a/e2e/scenarios/openai-agents-instrumentation/assertions.ts b/e2e/scenarios/openai-agents-instrumentation/assertions.ts index 411365004..5b74535dc 100644 --- a/e2e/scenarios/openai-agents-instrumentation/assertions.ts +++ b/e2e/scenarios/openai-agents-instrumentation/assertions.ts @@ -1,4 +1,4 @@ -import { beforeAll, describe, expect, test } from "vitest"; +import { beforeAll, describe, expect, test } from "vite-plus/test"; import { resolveFileSnapshotPath } from "../../helpers/file-snapshot"; import type { CapturedLogEvent } from "../../helpers/mock-braintrust-server"; import { diff --git a/e2e/scenarios/openai-agents-instrumentation/scenario.test.ts b/e2e/scenarios/openai-agents-instrumentation/scenario.test.ts index 4c2f6ed67..911b0adab 100644 --- a/e2e/scenarios/openai-agents-instrumentation/scenario.test.ts +++ b/e2e/scenarios/openai-agents-instrumentation/scenario.test.ts @@ -1,4 +1,4 @@ -import { describe } from "vitest"; +import { describe } from "vite-plus/test"; import { describeVariant } from "../../helpers/describe-variant"; import { prepareScenarioDir, diff --git a/e2e/scenarios/openai-codex-instrumentation/assertions.ts b/e2e/scenarios/openai-codex-instrumentation/assertions.ts index 23dacdbd8..bdbe16f69 100644 --- a/e2e/scenarios/openai-codex-instrumentation/assertions.ts +++ b/e2e/scenarios/openai-codex-instrumentation/assertions.ts @@ -1,4 +1,4 @@ -import { beforeAll, describe, expect, test } from "vitest"; +import { beforeAll, describe, expect, test } from "vite-plus/test"; import { resolveFileSnapshotPath } from "../../helpers/file-snapshot"; import type { CapturedLogEvent } from "../../helpers/mock-braintrust-server"; import { diff --git a/e2e/scenarios/openai-codex-instrumentation/scenario.test.ts b/e2e/scenarios/openai-codex-instrumentation/scenario.test.ts index 398f1f69e..26a3d4312 100644 --- a/e2e/scenarios/openai-codex-instrumentation/scenario.test.ts +++ b/e2e/scenarios/openai-codex-instrumentation/scenario.test.ts @@ -1,4 +1,4 @@ -import { describe } from "vitest"; +import { describe } from "vite-plus/test"; import { describeVariant } from "../../helpers/describe-variant"; import { prepareScenarioDir, diff --git a/e2e/scenarios/openai-instrumentation/__snapshots__/openai-v7-auto-hook.span-tree.json b/e2e/scenarios/openai-instrumentation/__snapshots__/openai-v7-auto-hook.span-tree.json index fe21073cc..3623a2f7a 100644 --- a/e2e/scenarios/openai-instrumentation/__snapshots__/openai-v7-auto-hook.span-tree.json +++ b/e2e/scenarios/openai-instrumentation/__snapshots__/openai-v7-auto-hook.span-tree.json @@ -176,17 +176,9 @@ "text": "Compare these two images.", "type": "input_text" }, - { - "image_url": "", - "type": "input_image" - }, { "text": "Here is the second image.", "type": "input_text" - }, - { - "image_url": "", - "type": "input_image" } ], "role": "user" diff --git a/e2e/scenarios/openai-instrumentation/__snapshots__/openai-v7-auto-hook.span-tree.txt b/e2e/scenarios/openai-instrumentation/__snapshots__/openai-v7-auto-hook.span-tree.txt index 1075ef7be..f92698095 100644 --- a/e2e/scenarios/openai-instrumentation/__snapshots__/openai-v7-auto-hook.span-tree.txt +++ b/e2e/scenarios/openai-instrumentation/__snapshots__/openai-v7-auto-hook.span-tree.txt @@ -171,16 +171,8 @@ span_tree: │ │ "type": "input_text" │ │ }, │ │ { -│ │ "image_url": "", -│ │ "type": "input_image" -│ │ }, -│ │ { │ │ "text": "Here is the second image.", │ │ "type": "input_text" -│ │ }, -│ │ { -│ │ "image_url": "", -│ │ "type": "input_image" │ │ } │ │ ], │ │ "role": "user" diff --git a/e2e/scenarios/openai-instrumentation/__snapshots__/openai-v7-latest-auto-hook.span-tree.json b/e2e/scenarios/openai-instrumentation/__snapshots__/openai-v7-latest-auto-hook.span-tree.json index 9a6b5c897..a04c66ea9 100644 --- a/e2e/scenarios/openai-instrumentation/__snapshots__/openai-v7-latest-auto-hook.span-tree.json +++ b/e2e/scenarios/openai-instrumentation/__snapshots__/openai-v7-latest-auto-hook.span-tree.json @@ -176,17 +176,9 @@ "text": "Compare these two images.", "type": "input_text" }, - { - "image_url": "", - "type": "input_image" - }, { "text": "Here is the second image.", "type": "input_text" - }, - { - "image_url": "", - "type": "input_image" } ], "role": "user" diff --git a/e2e/scenarios/openai-instrumentation/__snapshots__/openai-v7-latest-auto-hook.span-tree.txt b/e2e/scenarios/openai-instrumentation/__snapshots__/openai-v7-latest-auto-hook.span-tree.txt index 0de8fb33c..f9d2adbce 100644 --- a/e2e/scenarios/openai-instrumentation/__snapshots__/openai-v7-latest-auto-hook.span-tree.txt +++ b/e2e/scenarios/openai-instrumentation/__snapshots__/openai-v7-latest-auto-hook.span-tree.txt @@ -171,16 +171,8 @@ span_tree: │ │ "type": "input_text" │ │ }, │ │ { -│ │ "image_url": "", -│ │ "type": "input_image" -│ │ }, -│ │ { │ │ "text": "Here is the second image.", │ │ "type": "input_text" -│ │ }, -│ │ { -│ │ "image_url": "", -│ │ "type": "input_image" │ │ } │ │ ], │ │ "role": "user" diff --git a/e2e/scenarios/openai-instrumentation/__snapshots__/openai-v7-latest-wrapped.span-tree.json b/e2e/scenarios/openai-instrumentation/__snapshots__/openai-v7-latest-wrapped.span-tree.json index 21ab2d59c..f0062a706 100644 --- a/e2e/scenarios/openai-instrumentation/__snapshots__/openai-v7-latest-wrapped.span-tree.json +++ b/e2e/scenarios/openai-instrumentation/__snapshots__/openai-v7-latest-wrapped.span-tree.json @@ -176,17 +176,9 @@ "text": "Compare these two images.", "type": "input_text" }, - { - "image_url": "", - "type": "input_image" - }, { "text": "Here is the second image.", "type": "input_text" - }, - { - "image_url": "", - "type": "input_image" } ], "role": "user" diff --git a/e2e/scenarios/openai-instrumentation/__snapshots__/openai-v7-latest-wrapped.span-tree.txt b/e2e/scenarios/openai-instrumentation/__snapshots__/openai-v7-latest-wrapped.span-tree.txt index 17109981e..4efe68fe7 100644 --- a/e2e/scenarios/openai-instrumentation/__snapshots__/openai-v7-latest-wrapped.span-tree.txt +++ b/e2e/scenarios/openai-instrumentation/__snapshots__/openai-v7-latest-wrapped.span-tree.txt @@ -171,16 +171,8 @@ span_tree: │ │ "type": "input_text" │ │ }, │ │ { -│ │ "image_url": "", -│ │ "type": "input_image" -│ │ }, -│ │ { │ │ "text": "Here is the second image.", │ │ "type": "input_text" -│ │ }, -│ │ { -│ │ "image_url": "", -│ │ "type": "input_image" │ │ } │ │ ], │ │ "role": "user" diff --git a/e2e/scenarios/openai-instrumentation/__snapshots__/openai-v7-wrapped.span-tree.json b/e2e/scenarios/openai-instrumentation/__snapshots__/openai-v7-wrapped.span-tree.json index f760ffcf9..865b8bd4f 100644 --- a/e2e/scenarios/openai-instrumentation/__snapshots__/openai-v7-wrapped.span-tree.json +++ b/e2e/scenarios/openai-instrumentation/__snapshots__/openai-v7-wrapped.span-tree.json @@ -176,17 +176,9 @@ "text": "Compare these two images.", "type": "input_text" }, - { - "image_url": "", - "type": "input_image" - }, { "text": "Here is the second image.", "type": "input_text" - }, - { - "image_url": "", - "type": "input_image" } ], "role": "user" diff --git a/e2e/scenarios/openai-instrumentation/__snapshots__/openai-v7-wrapped.span-tree.txt b/e2e/scenarios/openai-instrumentation/__snapshots__/openai-v7-wrapped.span-tree.txt index e82aa0da5..8acb4772c 100644 --- a/e2e/scenarios/openai-instrumentation/__snapshots__/openai-v7-wrapped.span-tree.txt +++ b/e2e/scenarios/openai-instrumentation/__snapshots__/openai-v7-wrapped.span-tree.txt @@ -171,16 +171,8 @@ span_tree: │ │ "type": "input_text" │ │ }, │ │ { -│ │ "image_url": "", -│ │ "type": "input_image" -│ │ }, -│ │ { │ │ "text": "Here is the second image.", │ │ "type": "input_text" -│ │ }, -│ │ { -│ │ "image_url": "", -│ │ "type": "input_image" │ │ } │ │ ], │ │ "role": "user" diff --git a/e2e/scenarios/openai-instrumentation/assertions.ts b/e2e/scenarios/openai-instrumentation/assertions.ts index c52c15161..c025761f5 100644 --- a/e2e/scenarios/openai-instrumentation/assertions.ts +++ b/e2e/scenarios/openai-instrumentation/assertions.ts @@ -1,4 +1,4 @@ -import { beforeAll, describe, expect, test } from "vitest"; +import { beforeAll, describe, expect, test } from "vite-plus/test"; import type { CapturedLogEvent } from "../../helpers/mock-braintrust-server"; import type { Json } from "../../helpers/normalize"; import { resolveFileSnapshotPath } from "../../helpers/file-snapshot"; @@ -266,7 +266,7 @@ const OPERATION_SPECS: readonly OperationSpec[] = [ key: expect.any(String), } : mode === "images-disabled" - ? "" + ? undefined : "[redacted]"; expect(span?.span.type).toBe("question"); expect(span?.input).toEqual({ @@ -275,11 +275,18 @@ const OPERATION_SPECS: readonly OperationSpec[] = [ role: "user", content: [ { type: "input_text", text: "Compare these two images." }, - { type: "input_image", image_url: image }, - { type: "input_text", text: "Here is the second image." }, - ...(mode === "images-redacted" + ...(image === undefined ? [] : [{ type: "input_image", image_url: image }]), + { type: "input_text", text: "Here is the second image." }, + ...(mode === "images-redacted" || image === undefined + ? [] + : [ + { + type: "input_image", + image_url: image, + }, + ]), ], }, ], diff --git a/e2e/scenarios/openai-instrumentation/configure.mjs b/e2e/scenarios/openai-instrumentation/configure.mjs new file mode 100644 index 000000000..1eda7a80f --- /dev/null +++ b/e2e/scenarios/openai-instrumentation/configure.mjs @@ -0,0 +1,21 @@ +import { configureInstrumentation } from "braintrust/instrumentation"; + +// Register before automatic instrumentation; select the redaction case at export. +configureInstrumentation({ + spanCustomizers: [ + { + onSpanExport(data) { + if ( + process.env.BRAINTRUST_E2E_REDACT_DECISION_IMAGES === "true" && + data.span_attributes?.name === "openai.decisions.create" && + data.input?.state + ) { + const content = data.input.state[0].content; + content[1].image_url = "[redacted]"; + content.splice(3, 1); + } + return data; + }, + }, + ], +}); diff --git a/e2e/scenarios/openai-instrumentation/scenario.capture.mjs b/e2e/scenarios/openai-instrumentation/scenario.capture.mjs new file mode 100644 index 000000000..780f3f1e5 --- /dev/null +++ b/e2e/scenarios/openai-instrumentation/scenario.capture.mjs @@ -0,0 +1,10 @@ +import { runMain } from "../../helpers/provider-runtime.mjs"; +import { runAttachmentCaptureScenario } from "../../helpers/attachment-capture-scenario.mjs"; + +runMain(async () => + runAttachmentCaptureScenario( + "openai", + import.meta.url, + await import(process.env.CAPTURE_PACKAGE_NAME), + ), +); diff --git a/e2e/scenarios/openai-instrumentation/scenario.impl.mjs b/e2e/scenarios/openai-instrumentation/scenario.impl.mjs index fe4d18d4f..7a2add077 100644 --- a/e2e/scenarios/openai-instrumentation/scenario.impl.mjs +++ b/e2e/scenarios/openai-instrumentation/scenario.impl.mjs @@ -14,7 +14,7 @@ import { openaiBatchesRetrieveTraced, openaiFilesCreateTraced, } from "braintrust"; -import { configureInstrumentation } from "braintrust/instrumentation"; +import "./configure.mjs"; import { once } from "node:events"; import { Writable } from "node:stream"; @@ -329,7 +329,7 @@ async function runOpenAIMediaOperations(client, openAIMajorVersion) { throw new Error("Expected a WAV speech response"); } let pcm; - for (let offset = 12; offset + 8 <= wav.length; ) { + for (let offset = 12; offset + 8 <= wav.length;) { const length = wav.readUInt32LE(offset + 4); if (wav.toString("ascii", offset, offset + 4) === "data") { pcm = wav.subarray(offset + 8); @@ -1045,29 +1045,9 @@ export async function runOpenAIInstrumentationScenario(options) { ); for (const mode of ["images", "images-redacted", "images-disabled"]) { await flush(); - const capture = process.env.BRAINTRUST_CAPTURE_ATTACHMENTS; - if (mode === "images-disabled") - process.env.BRAINTRUST_CAPTURE_ATTACHMENTS = "false"; - if (mode === "images-redacted") { - configureInstrumentation({ - spanCustomizers: [ - { - onSpanExport(data) { - if ( - data.span_attributes?.name === - "openai.decisions.create" && - data.input?.state - ) { - const content = data.input.state[0].content; - content[1].image_url = "[redacted]"; - content.splice(3, 1); - } - return data; - }, - }, - ], - }); - } + process.env.BRAINTRUST_E2E_REDACT_DECISION_IMAGES = String( + mode === "images-redacted", + ); try { await runOperation( `openai-decisions-${mode}-operation`, @@ -1119,13 +1099,11 @@ export async function runOpenAIInstrumentationScenario(options) { if (data.answers[0].name !== "same") throw new Error("Decision image answer association changed"); }, + { autoCaptureAttachments: mode !== "images-disabled" }, ); await flush(); } finally { - configureInstrumentation({ spanCustomizers: [] }); - if (capture === undefined) - delete process.env.BRAINTRUST_CAPTURE_ATTACHMENTS; - else process.env.BRAINTRUST_CAPTURE_ATTACHMENTS = capture; + delete process.env.BRAINTRUST_E2E_REDACT_DECISION_IMAGES; } } } diff --git a/e2e/scenarios/openai-instrumentation/scenario.test.ts b/e2e/scenarios/openai-instrumentation/scenario.test.ts index 51d836d28..480d25039 100644 --- a/e2e/scenarios/openai-instrumentation/scenario.test.ts +++ b/e2e/scenarios/openai-instrumentation/scenario.test.ts @@ -1,4 +1,5 @@ -import { describe, it } from "vitest"; +import { defineAttachmentCaptureTests } from "../../helpers/attachment-capture-assertions"; +import { describe, it } from "vite-plus/test"; import { describeVariant } from "../../helpers/describe-variant"; import { prepareScenarioDir, @@ -84,7 +85,12 @@ describe("real HTTP server (undici responses)", () => { async () => { await runNodeScenarioDir({ entry: "scenario.real-http.mjs", - nodeArgs: ["--import", "braintrust/hook.mjs"], + nodeArgs: [ + "--import", + "./configure.mjs", + "--import", + "braintrust/hook.mjs", + ], scenarioDir, timeoutMs: TIMEOUT_MS, }); @@ -106,7 +112,7 @@ describe.concurrent("variants", () => { await runScenarioDir({ entry: scenario.wrapperEntry, env: { - BRAINTRUST_CAPTURE_ATTACHMENTS: "true", + BRAINTRUST_AUTO_CAPTURE_ATTACHMENTS: "true", OPENAI_PACKAGE_NAME: scenario.dependencyName, }, runContext: { @@ -129,10 +135,15 @@ describe.concurrent("variants", () => { await runNodeScenarioDir({ entry: scenario.autoEntry, env: { - BRAINTRUST_CAPTURE_ATTACHMENTS: "true", + BRAINTRUST_AUTO_CAPTURE_ATTACHMENTS: "true", OPENAI_PACKAGE_NAME: scenario.dependencyName, }, - nodeArgs: ["--import", "braintrust/hook.mjs"], + nodeArgs: [ + "--import", + "./configure.mjs", + "--import", + "braintrust/hook.mjs", + ], runContext: { variantKey: scenario.snapshotName, originalScenarioDir, @@ -149,3 +160,13 @@ describe.concurrent("variants", () => { }); } }); + +for (const scenario of openaiScenarios) { + defineAttachmentCaptureTests({ + scenarioDir, + originalScenarioDir, + provider: "openai", + packageName: scenario.dependencyName, + variantKey: scenario.snapshotName, + }); +} diff --git a/e2e/scenarios/openrouter-agent-instrumentation/assertions.ts b/e2e/scenarios/openrouter-agent-instrumentation/assertions.ts index fc56ac333..48db9ed8f 100644 --- a/e2e/scenarios/openrouter-agent-instrumentation/assertions.ts +++ b/e2e/scenarios/openrouter-agent-instrumentation/assertions.ts @@ -1,4 +1,4 @@ -import { beforeAll, describe, expect, test } from "vitest"; +import { beforeAll, describe, expect, test } from "vite-plus/test"; import type { CapturedLogEvent } from "../../helpers/mock-braintrust-server"; import { resolveFileSnapshotPath } from "../../helpers/file-snapshot"; import { diff --git a/e2e/scenarios/openrouter-agent-instrumentation/scenario.test.ts b/e2e/scenarios/openrouter-agent-instrumentation/scenario.test.ts index 2fdbb1053..26b22b5ef 100644 --- a/e2e/scenarios/openrouter-agent-instrumentation/scenario.test.ts +++ b/e2e/scenarios/openrouter-agent-instrumentation/scenario.test.ts @@ -1,4 +1,4 @@ -import { describe } from "vitest"; +import { describe } from "vite-plus/test"; import { describeVariant } from "../../helpers/describe-variant"; import { prepareScenarioDir, diff --git a/e2e/scenarios/openrouter-instrumentation/assertions.ts b/e2e/scenarios/openrouter-instrumentation/assertions.ts index 6a4a01590..d621ceece 100644 --- a/e2e/scenarios/openrouter-instrumentation/assertions.ts +++ b/e2e/scenarios/openrouter-instrumentation/assertions.ts @@ -1,4 +1,4 @@ -import { beforeAll, describe, expect, test } from "vitest"; +import { beforeAll, describe, expect, test } from "vite-plus/test"; import type { CapturedLogEvent } from "../../helpers/mock-braintrust-server"; import { resolveFileSnapshotPath } from "../../helpers/file-snapshot"; import { diff --git a/e2e/scenarios/openrouter-instrumentation/scenario.test.ts b/e2e/scenarios/openrouter-instrumentation/scenario.test.ts index 41de5133f..2287cb4e8 100644 --- a/e2e/scenarios/openrouter-instrumentation/scenario.test.ts +++ b/e2e/scenarios/openrouter-instrumentation/scenario.test.ts @@ -1,4 +1,4 @@ -import { describe } from "vitest"; +import { describe } from "vite-plus/test"; import { describeVariant } from "../../helpers/describe-variant"; import { prepareScenarioDir, diff --git a/e2e/scenarios/otel-compat-mixed-tracing/scenario.test.ts b/e2e/scenarios/otel-compat-mixed-tracing/scenario.test.ts index bd91dc47b..9aa4033e7 100644 --- a/e2e/scenarios/otel-compat-mixed-tracing/scenario.test.ts +++ b/e2e/scenarios/otel-compat-mixed-tracing/scenario.test.ts @@ -1,4 +1,4 @@ -import { expect, test } from "vitest"; +import { expect, test } from "vite-plus/test"; import { resolveFileSnapshotPath } from "../../helpers/file-snapshot"; import { prepareScenarioDir, diff --git a/e2e/scenarios/otel-compat-mixed-tracing/scenario.ts b/e2e/scenarios/otel-compat-mixed-tracing/scenario.ts index f3a2d4bb1..f74828b5e 100644 --- a/e2e/scenarios/otel-compat-mixed-tracing/scenario.ts +++ b/e2e/scenarios/otel-compat-mixed-tracing/scenario.ts @@ -1,7 +1,7 @@ import { context as otelContext, trace } from "@opentelemetry/api"; import { AsyncHooksContextManager } from "@opentelemetry/context-async-hooks"; import { BraintrustSpanProcessor, setupOtelCompat } from "@braintrust/otel"; -import { getContextManager, initLogger } from "braintrust"; +import { initLogger, withCurrent } from "braintrust"; import { createTracerProvider, getTestRunId, @@ -39,9 +39,7 @@ async function main() { }, }, }); - const contextManagerFacade = getContextManager(); - - await contextManagerFacade.runInContext(btRoot, async () => { + await withCurrent(btRoot, async () => { await tracer.startActiveSpan("otel-middle", async (otelSpan) => { const btChild = logger.startSpan({ name: "bt-child-under-otel", diff --git a/e2e/scenarios/otel-span-processor-export/scenario.test.ts b/e2e/scenarios/otel-span-processor-export/scenario.test.ts index 54714f6ad..983805534 100644 --- a/e2e/scenarios/otel-span-processor-export/scenario.test.ts +++ b/e2e/scenarios/otel-span-processor-export/scenario.test.ts @@ -1,4 +1,4 @@ -import { expect, test } from "vitest"; +import { expect, test } from "vite-plus/test"; import { prepareScenarioDir, resolveScenarioDir, diff --git a/e2e/scenarios/otel-v1-span-processor-export/scenario.test.ts b/e2e/scenarios/otel-v1-span-processor-export/scenario.test.ts index 62e2aadf3..5993e3731 100644 --- a/e2e/scenarios/otel-v1-span-processor-export/scenario.test.ts +++ b/e2e/scenarios/otel-v1-span-processor-export/scenario.test.ts @@ -1,6 +1,6 @@ import { readFile } from "node:fs/promises"; import { createRequire } from "node:module"; -import { expect, test } from "vitest"; +import { expect, test } from "vite-plus/test"; import { prepareScenarioDir, readInstalledPackageVersion, diff --git a/e2e/scenarios/pi-coding-agent-instrumentation/assertions.ts b/e2e/scenarios/pi-coding-agent-instrumentation/assertions.ts index 3673f80c0..357601489 100644 --- a/e2e/scenarios/pi-coding-agent-instrumentation/assertions.ts +++ b/e2e/scenarios/pi-coding-agent-instrumentation/assertions.ts @@ -1,4 +1,4 @@ -import { describe, expect, test } from "vitest"; +import { beforeAll, describe, expect, test } from "vite-plus/test"; import type { CapturedLogEvent } from "../../helpers/mock-braintrust-server"; import { resolveFileSnapshotPath } from "../../helpers/file-snapshot"; import { diff --git a/e2e/scenarios/pi-coding-agent-instrumentation/scenario.test.ts b/e2e/scenarios/pi-coding-agent-instrumentation/scenario.test.ts index 57d437390..ce124f18d 100644 --- a/e2e/scenarios/pi-coding-agent-instrumentation/scenario.test.ts +++ b/e2e/scenarios/pi-coding-agent-instrumentation/scenario.test.ts @@ -1,4 +1,4 @@ -import { describe } from "vitest"; +import { describe } from "vite-plus/test"; import { prepareScenarioDir, readInstalledPackageVersion, @@ -39,8 +39,9 @@ const piCodingAgentScenarios = await Promise.all( describe.concurrent("variants", () => { for (const piCodingAgentScenario of piCodingAgentScenarios) { - describe.sequential( - `${piCodingAgentScenario.dependencyName} (${piCodingAgentScenario.version})`, + describe( + `pi coding agent ${piCodingAgentScenario.version}`, + { concurrent: false }, () => { for (const mode of [ { diff --git a/e2e/scenarios/strands-agent-sdk-instrumentation/assertions.ts b/e2e/scenarios/strands-agent-sdk-instrumentation/assertions.ts index bbc8eab09..95413fc02 100644 --- a/e2e/scenarios/strands-agent-sdk-instrumentation/assertions.ts +++ b/e2e/scenarios/strands-agent-sdk-instrumentation/assertions.ts @@ -1,4 +1,4 @@ -import { beforeAll, describe, expect, test } from "vitest"; +import { beforeAll, describe, expect, test } from "vite-plus/test"; import type { CapturedLogEvent } from "../../helpers/mock-braintrust-server"; import type { Json } from "../../helpers/normalize"; import { resolveFileSnapshotPath } from "../../helpers/file-snapshot"; diff --git a/e2e/scenarios/strands-agent-sdk-instrumentation/scenario.test.ts b/e2e/scenarios/strands-agent-sdk-instrumentation/scenario.test.ts index 3b10a1900..f19ec010c 100644 --- a/e2e/scenarios/strands-agent-sdk-instrumentation/scenario.test.ts +++ b/e2e/scenarios/strands-agent-sdk-instrumentation/scenario.test.ts @@ -1,4 +1,4 @@ -import { describe } from "vitest"; +import { describe } from "vite-plus/test"; import { describeVariant } from "../../helpers/describe-variant"; import { prepareScenarioDir, @@ -50,7 +50,10 @@ describe.concurrent("variants", () => { runScenario: async ({ runScenarioDir }) => { await runScenarioDir({ entry: scenario.wrapperEntry, - env: { STRANDS_AGENT_SDK_PACKAGE_NAME: scenario.dependencyName }, + env: { + BRAINTRUST_AUTO_CAPTURE_ATTACHMENTS: "true", + STRANDS_AGENT_SDK_PACKAGE_NAME: scenario.dependencyName, + }, runContext: { variantKey: scenario.variantKey, originalScenarioDir, @@ -69,7 +72,10 @@ describe.concurrent("variants", () => { runScenario: async ({ runNodeScenarioDir }) => { await runNodeScenarioDir({ entry: scenario.autoEntry, - env: { STRANDS_AGENT_SDK_PACKAGE_NAME: scenario.dependencyName }, + env: { + STRANDS_AGENT_SDK_PACKAGE_NAME: scenario.dependencyName, + BRAINTRUST_AUTO_CAPTURE_ATTACHMENTS: "true", + }, nodeArgs: ["--import", "braintrust/hook.mjs"], runContext: { variantKey: scenario.variantKey, diff --git a/e2e/scenarios/test-framework-evals-jest/__snapshots__/jest-v29-latest.request-flow.json b/e2e/scenarios/test-framework-evals-jest/__snapshots__/jest-v29-latest.request-flow.json index a5e943a9d..e07fae8f7 100644 --- a/e2e/scenarios/test-framework-evals-jest/__snapshots__/jest-v29-latest.request-flow.json +++ b/e2e/scenarios/test-framework-evals-jest/__snapshots__/jest-v29-latest.request-flow.json @@ -28,7 +28,7 @@ "rows": [ { "context": { - "caller_filename": "/e2e/scenarios/test-framework-evals-jest/runner.case.cjs", + "caller_filename": "/e2e/scenarios/test-framework-evals-jest/runner.case.mjs", "caller_functionname": "Object.", "caller_lineno": 0 }, @@ -59,7 +59,7 @@ }, { "context": { - "caller_filename": "/e2e/scenarios/test-framework-evals-jest/runner.case.cjs", + "caller_filename": "/e2e/scenarios/test-framework-evals-jest/runner.case.mjs", "caller_functionname": "Object.", "caller_lineno": 0 }, @@ -98,7 +98,7 @@ }, { "context": { - "caller_filename": "/e2e/scenarios/test-framework-evals-jest/runner.case.cjs", + "caller_filename": "/e2e/scenarios/test-framework-evals-jest/runner.case.mjs", "caller_functionname": "Object.", "caller_lineno": 0 }, @@ -133,7 +133,7 @@ }, { "context": { - "caller_filename": "/e2e/scenarios/test-framework-evals-jest/runner.case.cjs", + "caller_filename": "/e2e/scenarios/test-framework-evals-jest/runner.case.mjs", "caller_functionname": "traced.name", "caller_lineno": 0 }, @@ -170,7 +170,7 @@ }, { "context": { - "caller_filename": "/e2e/scenarios/test-framework-evals-jest/runner.case.cjs", + "caller_filename": "/e2e/scenarios/test-framework-evals-jest/runner.case.mjs", "caller_functionname": "Object.", "caller_lineno": 0 }, @@ -197,7 +197,7 @@ }, { "context": { - "caller_filename": "/e2e/scenarios/test-framework-evals-jest/runner.case.cjs", + "caller_filename": "/e2e/scenarios/test-framework-evals-jest/runner.case.mjs", "caller_functionname": "traced.name", "caller_lineno": 0 }, @@ -226,7 +226,7 @@ }, { "context": { - "caller_filename": "/e2e/scenarios/test-framework-evals-jest/runner.case.cjs", + "caller_filename": "/e2e/scenarios/test-framework-evals-jest/runner.case.mjs", "caller_functionname": "traced.name", "caller_lineno": 0 }, @@ -258,7 +258,7 @@ }, { "context": { - "caller_filename": "/e2e/scenarios/test-framework-evals-jest/runner.case.cjs", + "caller_filename": "/e2e/scenarios/test-framework-evals-jest/runner.case.mjs", "caller_functionname": "Object.", "caller_lineno": 0 }, @@ -296,7 +296,7 @@ "rows": [ { "context": { - "caller_filename": "/e2e/scenarios/test-framework-evals-jest/runner.case.cjs", + "caller_filename": "/e2e/scenarios/test-framework-evals-jest/runner.case.mjs", "caller_functionname": "Object.", "caller_lineno": 0 }, @@ -327,7 +327,7 @@ }, { "context": { - "caller_filename": "/e2e/scenarios/test-framework-evals-jest/runner.case.cjs", + "caller_filename": "/e2e/scenarios/test-framework-evals-jest/runner.case.mjs", "caller_functionname": "Object.", "caller_lineno": 0 }, @@ -366,7 +366,7 @@ }, { "context": { - "caller_filename": "/e2e/scenarios/test-framework-evals-jest/runner.case.cjs", + "caller_filename": "/e2e/scenarios/test-framework-evals-jest/runner.case.mjs", "caller_functionname": "Object.", "caller_lineno": 0 }, @@ -401,7 +401,7 @@ }, { "context": { - "caller_filename": "/e2e/scenarios/test-framework-evals-jest/runner.case.cjs", + "caller_filename": "/e2e/scenarios/test-framework-evals-jest/runner.case.mjs", "caller_functionname": "traced.name", "caller_lineno": 0 }, @@ -438,7 +438,7 @@ }, { "context": { - "caller_filename": "/e2e/scenarios/test-framework-evals-jest/runner.case.cjs", + "caller_filename": "/e2e/scenarios/test-framework-evals-jest/runner.case.mjs", "caller_functionname": "Object.", "caller_lineno": 0 }, @@ -465,7 +465,7 @@ }, { "context": { - "caller_filename": "/e2e/scenarios/test-framework-evals-jest/runner.case.cjs", + "caller_filename": "/e2e/scenarios/test-framework-evals-jest/runner.case.mjs", "caller_functionname": "traced.name", "caller_lineno": 0 }, @@ -494,7 +494,7 @@ }, { "context": { - "caller_filename": "/e2e/scenarios/test-framework-evals-jest/runner.case.cjs", + "caller_filename": "/e2e/scenarios/test-framework-evals-jest/runner.case.mjs", "caller_functionname": "traced.name", "caller_lineno": 0 }, @@ -526,7 +526,7 @@ }, { "context": { - "caller_filename": "/e2e/scenarios/test-framework-evals-jest/runner.case.cjs", + "caller_filename": "/e2e/scenarios/test-framework-evals-jest/runner.case.mjs", "caller_functionname": "Object.", "caller_lineno": 0 }, diff --git a/e2e/scenarios/test-framework-evals-jest/__snapshots__/jest-v29.request-flow.json b/e2e/scenarios/test-framework-evals-jest/__snapshots__/jest-v29.request-flow.json index a5e943a9d..e07fae8f7 100644 --- a/e2e/scenarios/test-framework-evals-jest/__snapshots__/jest-v29.request-flow.json +++ b/e2e/scenarios/test-framework-evals-jest/__snapshots__/jest-v29.request-flow.json @@ -28,7 +28,7 @@ "rows": [ { "context": { - "caller_filename": "/e2e/scenarios/test-framework-evals-jest/runner.case.cjs", + "caller_filename": "/e2e/scenarios/test-framework-evals-jest/runner.case.mjs", "caller_functionname": "Object.", "caller_lineno": 0 }, @@ -59,7 +59,7 @@ }, { "context": { - "caller_filename": "/e2e/scenarios/test-framework-evals-jest/runner.case.cjs", + "caller_filename": "/e2e/scenarios/test-framework-evals-jest/runner.case.mjs", "caller_functionname": "Object.", "caller_lineno": 0 }, @@ -98,7 +98,7 @@ }, { "context": { - "caller_filename": "/e2e/scenarios/test-framework-evals-jest/runner.case.cjs", + "caller_filename": "/e2e/scenarios/test-framework-evals-jest/runner.case.mjs", "caller_functionname": "Object.", "caller_lineno": 0 }, @@ -133,7 +133,7 @@ }, { "context": { - "caller_filename": "/e2e/scenarios/test-framework-evals-jest/runner.case.cjs", + "caller_filename": "/e2e/scenarios/test-framework-evals-jest/runner.case.mjs", "caller_functionname": "traced.name", "caller_lineno": 0 }, @@ -170,7 +170,7 @@ }, { "context": { - "caller_filename": "/e2e/scenarios/test-framework-evals-jest/runner.case.cjs", + "caller_filename": "/e2e/scenarios/test-framework-evals-jest/runner.case.mjs", "caller_functionname": "Object.", "caller_lineno": 0 }, @@ -197,7 +197,7 @@ }, { "context": { - "caller_filename": "/e2e/scenarios/test-framework-evals-jest/runner.case.cjs", + "caller_filename": "/e2e/scenarios/test-framework-evals-jest/runner.case.mjs", "caller_functionname": "traced.name", "caller_lineno": 0 }, @@ -226,7 +226,7 @@ }, { "context": { - "caller_filename": "/e2e/scenarios/test-framework-evals-jest/runner.case.cjs", + "caller_filename": "/e2e/scenarios/test-framework-evals-jest/runner.case.mjs", "caller_functionname": "traced.name", "caller_lineno": 0 }, @@ -258,7 +258,7 @@ }, { "context": { - "caller_filename": "/e2e/scenarios/test-framework-evals-jest/runner.case.cjs", + "caller_filename": "/e2e/scenarios/test-framework-evals-jest/runner.case.mjs", "caller_functionname": "Object.", "caller_lineno": 0 }, @@ -296,7 +296,7 @@ "rows": [ { "context": { - "caller_filename": "/e2e/scenarios/test-framework-evals-jest/runner.case.cjs", + "caller_filename": "/e2e/scenarios/test-framework-evals-jest/runner.case.mjs", "caller_functionname": "Object.", "caller_lineno": 0 }, @@ -327,7 +327,7 @@ }, { "context": { - "caller_filename": "/e2e/scenarios/test-framework-evals-jest/runner.case.cjs", + "caller_filename": "/e2e/scenarios/test-framework-evals-jest/runner.case.mjs", "caller_functionname": "Object.", "caller_lineno": 0 }, @@ -366,7 +366,7 @@ }, { "context": { - "caller_filename": "/e2e/scenarios/test-framework-evals-jest/runner.case.cjs", + "caller_filename": "/e2e/scenarios/test-framework-evals-jest/runner.case.mjs", "caller_functionname": "Object.", "caller_lineno": 0 }, @@ -401,7 +401,7 @@ }, { "context": { - "caller_filename": "/e2e/scenarios/test-framework-evals-jest/runner.case.cjs", + "caller_filename": "/e2e/scenarios/test-framework-evals-jest/runner.case.mjs", "caller_functionname": "traced.name", "caller_lineno": 0 }, @@ -438,7 +438,7 @@ }, { "context": { - "caller_filename": "/e2e/scenarios/test-framework-evals-jest/runner.case.cjs", + "caller_filename": "/e2e/scenarios/test-framework-evals-jest/runner.case.mjs", "caller_functionname": "Object.", "caller_lineno": 0 }, @@ -465,7 +465,7 @@ }, { "context": { - "caller_filename": "/e2e/scenarios/test-framework-evals-jest/runner.case.cjs", + "caller_filename": "/e2e/scenarios/test-framework-evals-jest/runner.case.mjs", "caller_functionname": "traced.name", "caller_lineno": 0 }, @@ -494,7 +494,7 @@ }, { "context": { - "caller_filename": "/e2e/scenarios/test-framework-evals-jest/runner.case.cjs", + "caller_filename": "/e2e/scenarios/test-framework-evals-jest/runner.case.mjs", "caller_functionname": "traced.name", "caller_lineno": 0 }, @@ -526,7 +526,7 @@ }, { "context": { - "caller_filename": "/e2e/scenarios/test-framework-evals-jest/runner.case.cjs", + "caller_filename": "/e2e/scenarios/test-framework-evals-jest/runner.case.mjs", "caller_functionname": "Object.", "caller_lineno": 0 }, diff --git a/e2e/scenarios/test-framework-evals-jest/jest.config.cjs b/e2e/scenarios/test-framework-evals-jest/jest.config.cjs index 76108d7e0..9205c0f8a 100644 --- a/e2e/scenarios/test-framework-evals-jest/jest.config.cjs +++ b/e2e/scenarios/test-framework-evals-jest/jest.config.cjs @@ -1,5 +1,6 @@ module.exports = { rootDir: __dirname, testEnvironment: "node", - testMatch: ["/runner.case.cjs"], + transform: {}, + testMatch: ["/runner.case.mjs"], }; diff --git a/e2e/scenarios/test-framework-evals-jest/runner.case.cjs b/e2e/scenarios/test-framework-evals-jest/runner.case.mjs similarity index 93% rename from e2e/scenarios/test-framework-evals-jest/runner.case.cjs rename to e2e/scenarios/test-framework-evals-jest/runner.case.mjs index cffa7756d..24bde97c6 100644 --- a/e2e/scenarios/test-framework-evals-jest/runner.case.cjs +++ b/e2e/scenarios/test-framework-evals-jest/runner.case.mjs @@ -1,13 +1,14 @@ -const { +import * as braintrust from "braintrust"; +import { test, expect, afterAll } from "@jest/globals"; +import { Eval, JSONAttachment, Prompt, - _exportsForTestingOnly, currentSpan, initLogger, startSpan, traced, -} = require("braintrust"); +} from "braintrust"; const testRunId = process.env.BRAINTRUST_E2E_RUN_ID; const scenario = "test-framework-evals-jest"; @@ -33,17 +34,13 @@ function expectNamedExports(module, exportNames) { } } -test("jest exposes the core braintrust runtime surface in CommonJS", () => { - const braintrust = require("braintrust"); - +test("jest exposes the core braintrust runtime surface in ESM", () => { expectNamedExports(braintrust, [ "initLogger", "currentSpan", "startSpan", - "log", "flush", "initDataset", - "initExperiment", "loadPrompt", "Prompt", "Eval", @@ -53,20 +50,12 @@ test("jest exposes the core braintrust runtime surface in CommonJS", () => { "Attachment", "invoke", "initFunction", - "Project", - "PromptBuilder", - "IDGenerator", + "projects", "BraintrustState", "login", - "_exportsForTestingOnly", ]); }); -test("jest resolves the node build in CommonJS mode", () => { - expect(_exportsForTestingOnly).toBeDefined(); - expect(_exportsForTestingOnly.isomorph.buildType).toBe("node"); -}); - test("jest renders mustache prompts and fails clearly for nunjucks without the extra package", () => { const mustachePrompt = new Prompt( { diff --git a/e2e/scenarios/test-framework-evals-jest/scenario.mjs b/e2e/scenarios/test-framework-evals-jest/scenario.mjs index b923325c5..a692f2a23 100644 --- a/e2e/scenarios/test-framework-evals-jest/scenario.mjs +++ b/e2e/scenarios/test-framework-evals-jest/scenario.mjs @@ -16,7 +16,13 @@ async function runJest() { await new Promise((resolve, reject) => { const child = spawn( process.execPath, - [jestCliPath, "--config", "jest.config.cjs", "--runInBand"], + [ + "--experimental-vm-modules", + jestCliPath, + "--config", + "jest.config.cjs", + "--runInBand", + ], { cwd: scenarioDir, env: process.env, diff --git a/e2e/scenarios/test-framework-evals-jest/scenario.test.ts b/e2e/scenarios/test-framework-evals-jest/scenario.test.ts index f19ca7555..02b0e6f43 100644 --- a/e2e/scenarios/test-framework-evals-jest/scenario.test.ts +++ b/e2e/scenarios/test-framework-evals-jest/scenario.test.ts @@ -1,4 +1,4 @@ -import { expect, test } from "vitest"; +import { expect, test } from "vite-plus/test"; import { formatJsonFileSnapshot, matchFileSnapshot, diff --git a/e2e/scenarios/test-framework-evals-node/scenario.test.ts b/e2e/scenarios/test-framework-evals-node/scenario.test.ts index 61f1ff244..432f4123d 100644 --- a/e2e/scenarios/test-framework-evals-node/scenario.test.ts +++ b/e2e/scenarios/test-framework-evals-node/scenario.test.ts @@ -1,4 +1,4 @@ -import { expect, test } from "vitest"; +import { expect, test } from "vite-plus/test"; import { resolveFileSnapshotPath } from "../../helpers/file-snapshot"; import { prepareScenarioDir, diff --git a/e2e/scenarios/test-framework-evals-vitest/__snapshots__/vitest-v2-latest.span-tree.json b/e2e/scenarios/test-framework-evals-vitest/__snapshots__/vitest-v2-latest.span-tree.json index fa83fb077..1a37cab1d 100644 --- a/e2e/scenarios/test-framework-evals-vitest/__snapshots__/vitest-v2-latest.span-tree.json +++ b/e2e/scenarios/test-framework-evals-vitest/__snapshots__/vitest-v2-latest.span-tree.json @@ -90,7 +90,7 @@ "scenario": "test-framework-evals-vitest", "testRunId": "" }, - "error": "expected 'wrong' to be 'right' // Object.is equality\n\nAssertionError: expected 'wrong' to be 'right' // Object.is equality\n at /e2e/scenarios/test-framework-evals-vitest/runner.case.ts:0:0\n at Object.fn (/js/src/wrappers/vitest/wrapper.ts:0:0)\n at args.experiment.traced.name (/js/src/wrappers/shared/traced-eval.ts:0:0)\n at /js/src/logger.ts:0:0\n at AsyncLocalStorage.run (node::0:0)\n at BraintrustContextManager.runInContext (/js/src/logger.ts:0:0)\n at withCurrent (/js/src/logger.ts:0:0)\n at /js/src/logger.ts:0:0\n at Module.runCatchFinally (/js/src/util.ts:0:0)\n at Experiment.traced (/js/src/logger.ts:0:0)" + "error": "expected 'wrong' to be 'right' // Object.is equality\n\nAssertionError: expected 'wrong' to be 'right' // Object.is equality\n at /e2e/scenarios/test-framework-evals-vitest/runner.case.ts:0:0\n at Object.fn (/js/src/wrappers/vitest/wrapper.ts:0:0)\n at args.experiment.traced.name (/js/src/wrappers/shared/traced-eval.ts:0:0)\n at /js/src/logger/core.ts:0:0\n at AsyncLocalStorage.run (node::0:0)\n at BraintrustContextManager.runInContext (/js/src/logger/core.ts:0:0)\n at Module.withCurrent (/js/src/logger/core.ts:0:0)\n at /js/src/logger/experiments.ts:0:0\n at Module.runCatchFinally (/js/src/util.ts:0:0)\n at Experiment.traced (/js/src/logger/experiments.ts:0:0)" } ] } diff --git a/e2e/scenarios/test-framework-evals-vitest/__snapshots__/vitest-v2-latest.span-tree.txt b/e2e/scenarios/test-framework-evals-vitest/__snapshots__/vitest-v2-latest.span-tree.txt index 509669bb4..eaa603b87 100644 --- a/e2e/scenarios/test-framework-evals-vitest/__snapshots__/vitest-v2-latest.span-tree.txt +++ b/e2e/scenarios/test-framework-evals-vitest/__snapshots__/vitest-v2-latest.span-tree.txt @@ -70,4 +70,4 @@ span_tree: "scenario": "test-framework-evals-vitest", "testRunId": "" } - error: "expected 'wrong' to be 'right' // Object.is equality\n\nAssertionError: expected 'wrong' to be 'right' // Object.is equality\n at /e2e/scenarios/test-framework-evals-vitest/runner.case.ts:0:0\n at Object.fn (/js/src/wrappers/vitest/wrapper.ts:0:0)\n at args.experiment.traced.name (/js/src/wrappers/shared/traced-eval.ts:0:0)\n at /js/src/logger.ts:0:0\n at AsyncLocalStorage.run (node::0:0)\n at BraintrustContextManager.runInContext (/js/src/logger.ts:0:0)\n at withCurrent (/js/src/logger.ts:0:0)\n at /js/src/logger.ts:0:0\n at Module.runCatchFinally (/js/src/util.ts:0:0)\n at Experiment.traced (/js/src/logger.ts:0:0)" + error: "expected 'wrong' to be 'right' // Object.is equality\n\nAssertionError: expected 'wrong' to be 'right' // Object.is equality\n at /e2e/scenarios/test-framework-evals-vitest/runner.case.ts:0:0\n at Object.fn (/js/src/wrappers/vitest/wrapper.ts:0:0)\n at args.experiment.traced.name (/js/src/wrappers/shared/traced-eval.ts:0:0)\n at /js/src/logger/core.ts:0:0\n at AsyncLocalStorage.run (node::0:0)\n at BraintrustContextManager.runInContext (/js/src/logger/core.ts:0:0)\n at Module.withCurrent (/js/src/logger/core.ts:0:0)\n at /js/src/logger/experiments.ts:0:0\n at Module.runCatchFinally (/js/src/util.ts:0:0)\n at Experiment.traced (/js/src/logger/experiments.ts:0:0)" diff --git a/e2e/scenarios/test-framework-evals-vitest/__snapshots__/vitest-v2.span-tree.json b/e2e/scenarios/test-framework-evals-vitest/__snapshots__/vitest-v2.span-tree.json index fa83fb077..1a37cab1d 100644 --- a/e2e/scenarios/test-framework-evals-vitest/__snapshots__/vitest-v2.span-tree.json +++ b/e2e/scenarios/test-framework-evals-vitest/__snapshots__/vitest-v2.span-tree.json @@ -90,7 +90,7 @@ "scenario": "test-framework-evals-vitest", "testRunId": "" }, - "error": "expected 'wrong' to be 'right' // Object.is equality\n\nAssertionError: expected 'wrong' to be 'right' // Object.is equality\n at /e2e/scenarios/test-framework-evals-vitest/runner.case.ts:0:0\n at Object.fn (/js/src/wrappers/vitest/wrapper.ts:0:0)\n at args.experiment.traced.name (/js/src/wrappers/shared/traced-eval.ts:0:0)\n at /js/src/logger.ts:0:0\n at AsyncLocalStorage.run (node::0:0)\n at BraintrustContextManager.runInContext (/js/src/logger.ts:0:0)\n at withCurrent (/js/src/logger.ts:0:0)\n at /js/src/logger.ts:0:0\n at Module.runCatchFinally (/js/src/util.ts:0:0)\n at Experiment.traced (/js/src/logger.ts:0:0)" + "error": "expected 'wrong' to be 'right' // Object.is equality\n\nAssertionError: expected 'wrong' to be 'right' // Object.is equality\n at /e2e/scenarios/test-framework-evals-vitest/runner.case.ts:0:0\n at Object.fn (/js/src/wrappers/vitest/wrapper.ts:0:0)\n at args.experiment.traced.name (/js/src/wrappers/shared/traced-eval.ts:0:0)\n at /js/src/logger/core.ts:0:0\n at AsyncLocalStorage.run (node::0:0)\n at BraintrustContextManager.runInContext (/js/src/logger/core.ts:0:0)\n at Module.withCurrent (/js/src/logger/core.ts:0:0)\n at /js/src/logger/experiments.ts:0:0\n at Module.runCatchFinally (/js/src/util.ts:0:0)\n at Experiment.traced (/js/src/logger/experiments.ts:0:0)" } ] } diff --git a/e2e/scenarios/test-framework-evals-vitest/__snapshots__/vitest-v2.span-tree.txt b/e2e/scenarios/test-framework-evals-vitest/__snapshots__/vitest-v2.span-tree.txt index 509669bb4..eaa603b87 100644 --- a/e2e/scenarios/test-framework-evals-vitest/__snapshots__/vitest-v2.span-tree.txt +++ b/e2e/scenarios/test-framework-evals-vitest/__snapshots__/vitest-v2.span-tree.txt @@ -70,4 +70,4 @@ span_tree: "scenario": "test-framework-evals-vitest", "testRunId": "" } - error: "expected 'wrong' to be 'right' // Object.is equality\n\nAssertionError: expected 'wrong' to be 'right' // Object.is equality\n at /e2e/scenarios/test-framework-evals-vitest/runner.case.ts:0:0\n at Object.fn (/js/src/wrappers/vitest/wrapper.ts:0:0)\n at args.experiment.traced.name (/js/src/wrappers/shared/traced-eval.ts:0:0)\n at /js/src/logger.ts:0:0\n at AsyncLocalStorage.run (node::0:0)\n at BraintrustContextManager.runInContext (/js/src/logger.ts:0:0)\n at withCurrent (/js/src/logger.ts:0:0)\n at /js/src/logger.ts:0:0\n at Module.runCatchFinally (/js/src/util.ts:0:0)\n at Experiment.traced (/js/src/logger.ts:0:0)" + error: "expected 'wrong' to be 'right' // Object.is equality\n\nAssertionError: expected 'wrong' to be 'right' // Object.is equality\n at /e2e/scenarios/test-framework-evals-vitest/runner.case.ts:0:0\n at Object.fn (/js/src/wrappers/vitest/wrapper.ts:0:0)\n at args.experiment.traced.name (/js/src/wrappers/shared/traced-eval.ts:0:0)\n at /js/src/logger/core.ts:0:0\n at AsyncLocalStorage.run (node::0:0)\n at BraintrustContextManager.runInContext (/js/src/logger/core.ts:0:0)\n at Module.withCurrent (/js/src/logger/core.ts:0:0)\n at /js/src/logger/experiments.ts:0:0\n at Module.runCatchFinally (/js/src/util.ts:0:0)\n at Experiment.traced (/js/src/logger/experiments.ts:0:0)" diff --git a/e2e/scenarios/test-framework-evals-vitest/__snapshots__/vitest-v3-latest.span-tree.json b/e2e/scenarios/test-framework-evals-vitest/__snapshots__/vitest-v3-latest.span-tree.json index 23e801424..fde5790b2 100644 --- a/e2e/scenarios/test-framework-evals-vitest/__snapshots__/vitest-v3-latest.span-tree.json +++ b/e2e/scenarios/test-framework-evals-vitest/__snapshots__/vitest-v3-latest.span-tree.json @@ -90,7 +90,7 @@ "scenario": "test-framework-evals-vitest", "testRunId": "" }, - "error": "expected 'wrong' to be 'right' // Object.is equality\n\nAssertionError: expected 'wrong' to be 'right' // Object.is equality\n at /e2e/scenarios/test-framework-evals-vitest/runner.case.ts:0:0\n at Object.fn (/js/src/wrappers/vitest/wrapper.ts:0:0)\n at args.experiment.traced.name (/js/src/wrappers/shared/traced-eval.ts:0:0)\n at /js/src/logger.ts:0:0\n at AsyncLocalStorage.run (node::0:0)\n at BraintrustContextManager.runInContext (/js/src/logger.ts:0:0)\n at withCurrent (/js/src/logger.ts:0:0)\n at /js/src/logger.ts:0:0\n at runCatchFinally (/js/src/util.ts:0:0)\n at Experiment.traced (/js/src/logger.ts:0:0)" + "error": "expected 'wrong' to be 'right' // Object.is equality\n\nAssertionError: expected 'wrong' to be 'right' // Object.is equality\n at /e2e/scenarios/test-framework-evals-vitest/runner.case.ts:0:0\n at Object.fn (/js/src/wrappers/vitest/wrapper.ts:0:0)\n at args.experiment.traced.name (/js/src/wrappers/shared/traced-eval.ts:0:0)\n at /js/src/logger/core.ts:0:0\n at AsyncLocalStorage.run (node::0:0)\n at BraintrustContextManager.runInContext (/js/src/logger/core.ts:0:0)\n at withCurrent (/js/src/logger/core.ts:0:0)\n at /js/src/logger/experiments.ts:0:0\n at runCatchFinally (/js/src/util.ts:0:0)\n at Experiment.traced (/js/src/logger/experiments.ts:0:0)" } ] } diff --git a/e2e/scenarios/test-framework-evals-vitest/__snapshots__/vitest-v3-latest.span-tree.txt b/e2e/scenarios/test-framework-evals-vitest/__snapshots__/vitest-v3-latest.span-tree.txt index 6ab1bf413..0a45596ab 100644 --- a/e2e/scenarios/test-framework-evals-vitest/__snapshots__/vitest-v3-latest.span-tree.txt +++ b/e2e/scenarios/test-framework-evals-vitest/__snapshots__/vitest-v3-latest.span-tree.txt @@ -70,4 +70,4 @@ span_tree: "scenario": "test-framework-evals-vitest", "testRunId": "" } - error: "expected 'wrong' to be 'right' // Object.is equality\n\nAssertionError: expected 'wrong' to be 'right' // Object.is equality\n at /e2e/scenarios/test-framework-evals-vitest/runner.case.ts:0:0\n at Object.fn (/js/src/wrappers/vitest/wrapper.ts:0:0)\n at args.experiment.traced.name (/js/src/wrappers/shared/traced-eval.ts:0:0)\n at /js/src/logger.ts:0:0\n at AsyncLocalStorage.run (node::0:0)\n at BraintrustContextManager.runInContext (/js/src/logger.ts:0:0)\n at withCurrent (/js/src/logger.ts:0:0)\n at /js/src/logger.ts:0:0\n at runCatchFinally (/js/src/util.ts:0:0)\n at Experiment.traced (/js/src/logger.ts:0:0)" + error: "expected 'wrong' to be 'right' // Object.is equality\n\nAssertionError: expected 'wrong' to be 'right' // Object.is equality\n at /e2e/scenarios/test-framework-evals-vitest/runner.case.ts:0:0\n at Object.fn (/js/src/wrappers/vitest/wrapper.ts:0:0)\n at args.experiment.traced.name (/js/src/wrappers/shared/traced-eval.ts:0:0)\n at /js/src/logger/core.ts:0:0\n at AsyncLocalStorage.run (node::0:0)\n at BraintrustContextManager.runInContext (/js/src/logger/core.ts:0:0)\n at withCurrent (/js/src/logger/core.ts:0:0)\n at /js/src/logger/experiments.ts:0:0\n at runCatchFinally (/js/src/util.ts:0:0)\n at Experiment.traced (/js/src/logger/experiments.ts:0:0)" diff --git a/e2e/scenarios/test-framework-evals-vitest/__snapshots__/vitest-v3.span-tree.json b/e2e/scenarios/test-framework-evals-vitest/__snapshots__/vitest-v3.span-tree.json index 23e801424..fde5790b2 100644 --- a/e2e/scenarios/test-framework-evals-vitest/__snapshots__/vitest-v3.span-tree.json +++ b/e2e/scenarios/test-framework-evals-vitest/__snapshots__/vitest-v3.span-tree.json @@ -90,7 +90,7 @@ "scenario": "test-framework-evals-vitest", "testRunId": "" }, - "error": "expected 'wrong' to be 'right' // Object.is equality\n\nAssertionError: expected 'wrong' to be 'right' // Object.is equality\n at /e2e/scenarios/test-framework-evals-vitest/runner.case.ts:0:0\n at Object.fn (/js/src/wrappers/vitest/wrapper.ts:0:0)\n at args.experiment.traced.name (/js/src/wrappers/shared/traced-eval.ts:0:0)\n at /js/src/logger.ts:0:0\n at AsyncLocalStorage.run (node::0:0)\n at BraintrustContextManager.runInContext (/js/src/logger.ts:0:0)\n at withCurrent (/js/src/logger.ts:0:0)\n at /js/src/logger.ts:0:0\n at runCatchFinally (/js/src/util.ts:0:0)\n at Experiment.traced (/js/src/logger.ts:0:0)" + "error": "expected 'wrong' to be 'right' // Object.is equality\n\nAssertionError: expected 'wrong' to be 'right' // Object.is equality\n at /e2e/scenarios/test-framework-evals-vitest/runner.case.ts:0:0\n at Object.fn (/js/src/wrappers/vitest/wrapper.ts:0:0)\n at args.experiment.traced.name (/js/src/wrappers/shared/traced-eval.ts:0:0)\n at /js/src/logger/core.ts:0:0\n at AsyncLocalStorage.run (node::0:0)\n at BraintrustContextManager.runInContext (/js/src/logger/core.ts:0:0)\n at withCurrent (/js/src/logger/core.ts:0:0)\n at /js/src/logger/experiments.ts:0:0\n at runCatchFinally (/js/src/util.ts:0:0)\n at Experiment.traced (/js/src/logger/experiments.ts:0:0)" } ] } diff --git a/e2e/scenarios/test-framework-evals-vitest/__snapshots__/vitest-v3.span-tree.txt b/e2e/scenarios/test-framework-evals-vitest/__snapshots__/vitest-v3.span-tree.txt index 6ab1bf413..0a45596ab 100644 --- a/e2e/scenarios/test-framework-evals-vitest/__snapshots__/vitest-v3.span-tree.txt +++ b/e2e/scenarios/test-framework-evals-vitest/__snapshots__/vitest-v3.span-tree.txt @@ -70,4 +70,4 @@ span_tree: "scenario": "test-framework-evals-vitest", "testRunId": "" } - error: "expected 'wrong' to be 'right' // Object.is equality\n\nAssertionError: expected 'wrong' to be 'right' // Object.is equality\n at /e2e/scenarios/test-framework-evals-vitest/runner.case.ts:0:0\n at Object.fn (/js/src/wrappers/vitest/wrapper.ts:0:0)\n at args.experiment.traced.name (/js/src/wrappers/shared/traced-eval.ts:0:0)\n at /js/src/logger.ts:0:0\n at AsyncLocalStorage.run (node::0:0)\n at BraintrustContextManager.runInContext (/js/src/logger.ts:0:0)\n at withCurrent (/js/src/logger.ts:0:0)\n at /js/src/logger.ts:0:0\n at runCatchFinally (/js/src/util.ts:0:0)\n at Experiment.traced (/js/src/logger.ts:0:0)" + error: "expected 'wrong' to be 'right' // Object.is equality\n\nAssertionError: expected 'wrong' to be 'right' // Object.is equality\n at /e2e/scenarios/test-framework-evals-vitest/runner.case.ts:0:0\n at Object.fn (/js/src/wrappers/vitest/wrapper.ts:0:0)\n at args.experiment.traced.name (/js/src/wrappers/shared/traced-eval.ts:0:0)\n at /js/src/logger/core.ts:0:0\n at AsyncLocalStorage.run (node::0:0)\n at BraintrustContextManager.runInContext (/js/src/logger/core.ts:0:0)\n at withCurrent (/js/src/logger/core.ts:0:0)\n at /js/src/logger/experiments.ts:0:0\n at runCatchFinally (/js/src/util.ts:0:0)\n at Experiment.traced (/js/src/logger/experiments.ts:0:0)" diff --git a/e2e/scenarios/test-framework-evals-vitest/__snapshots__/vitest-v4-latest.span-tree.json b/e2e/scenarios/test-framework-evals-vitest/__snapshots__/vitest-v4-latest.span-tree.json index 23e801424..fde5790b2 100644 --- a/e2e/scenarios/test-framework-evals-vitest/__snapshots__/vitest-v4-latest.span-tree.json +++ b/e2e/scenarios/test-framework-evals-vitest/__snapshots__/vitest-v4-latest.span-tree.json @@ -90,7 +90,7 @@ "scenario": "test-framework-evals-vitest", "testRunId": "" }, - "error": "expected 'wrong' to be 'right' // Object.is equality\n\nAssertionError: expected 'wrong' to be 'right' // Object.is equality\n at /e2e/scenarios/test-framework-evals-vitest/runner.case.ts:0:0\n at Object.fn (/js/src/wrappers/vitest/wrapper.ts:0:0)\n at args.experiment.traced.name (/js/src/wrappers/shared/traced-eval.ts:0:0)\n at /js/src/logger.ts:0:0\n at AsyncLocalStorage.run (node::0:0)\n at BraintrustContextManager.runInContext (/js/src/logger.ts:0:0)\n at withCurrent (/js/src/logger.ts:0:0)\n at /js/src/logger.ts:0:0\n at runCatchFinally (/js/src/util.ts:0:0)\n at Experiment.traced (/js/src/logger.ts:0:0)" + "error": "expected 'wrong' to be 'right' // Object.is equality\n\nAssertionError: expected 'wrong' to be 'right' // Object.is equality\n at /e2e/scenarios/test-framework-evals-vitest/runner.case.ts:0:0\n at Object.fn (/js/src/wrappers/vitest/wrapper.ts:0:0)\n at args.experiment.traced.name (/js/src/wrappers/shared/traced-eval.ts:0:0)\n at /js/src/logger/core.ts:0:0\n at AsyncLocalStorage.run (node::0:0)\n at BraintrustContextManager.runInContext (/js/src/logger/core.ts:0:0)\n at withCurrent (/js/src/logger/core.ts:0:0)\n at /js/src/logger/experiments.ts:0:0\n at runCatchFinally (/js/src/util.ts:0:0)\n at Experiment.traced (/js/src/logger/experiments.ts:0:0)" } ] } diff --git a/e2e/scenarios/test-framework-evals-vitest/__snapshots__/vitest-v4-latest.span-tree.txt b/e2e/scenarios/test-framework-evals-vitest/__snapshots__/vitest-v4-latest.span-tree.txt index 6ab1bf413..0a45596ab 100644 --- a/e2e/scenarios/test-framework-evals-vitest/__snapshots__/vitest-v4-latest.span-tree.txt +++ b/e2e/scenarios/test-framework-evals-vitest/__snapshots__/vitest-v4-latest.span-tree.txt @@ -70,4 +70,4 @@ span_tree: "scenario": "test-framework-evals-vitest", "testRunId": "" } - error: "expected 'wrong' to be 'right' // Object.is equality\n\nAssertionError: expected 'wrong' to be 'right' // Object.is equality\n at /e2e/scenarios/test-framework-evals-vitest/runner.case.ts:0:0\n at Object.fn (/js/src/wrappers/vitest/wrapper.ts:0:0)\n at args.experiment.traced.name (/js/src/wrappers/shared/traced-eval.ts:0:0)\n at /js/src/logger.ts:0:0\n at AsyncLocalStorage.run (node::0:0)\n at BraintrustContextManager.runInContext (/js/src/logger.ts:0:0)\n at withCurrent (/js/src/logger.ts:0:0)\n at /js/src/logger.ts:0:0\n at runCatchFinally (/js/src/util.ts:0:0)\n at Experiment.traced (/js/src/logger.ts:0:0)" + error: "expected 'wrong' to be 'right' // Object.is equality\n\nAssertionError: expected 'wrong' to be 'right' // Object.is equality\n at /e2e/scenarios/test-framework-evals-vitest/runner.case.ts:0:0\n at Object.fn (/js/src/wrappers/vitest/wrapper.ts:0:0)\n at args.experiment.traced.name (/js/src/wrappers/shared/traced-eval.ts:0:0)\n at /js/src/logger/core.ts:0:0\n at AsyncLocalStorage.run (node::0:0)\n at BraintrustContextManager.runInContext (/js/src/logger/core.ts:0:0)\n at withCurrent (/js/src/logger/core.ts:0:0)\n at /js/src/logger/experiments.ts:0:0\n at runCatchFinally (/js/src/util.ts:0:0)\n at Experiment.traced (/js/src/logger/experiments.ts:0:0)" diff --git a/e2e/scenarios/test-framework-evals-vitest/__snapshots__/vitest-v4.span-tree.json b/e2e/scenarios/test-framework-evals-vitest/__snapshots__/vitest-v4.span-tree.json index 23e801424..fde5790b2 100644 --- a/e2e/scenarios/test-framework-evals-vitest/__snapshots__/vitest-v4.span-tree.json +++ b/e2e/scenarios/test-framework-evals-vitest/__snapshots__/vitest-v4.span-tree.json @@ -90,7 +90,7 @@ "scenario": "test-framework-evals-vitest", "testRunId": "" }, - "error": "expected 'wrong' to be 'right' // Object.is equality\n\nAssertionError: expected 'wrong' to be 'right' // Object.is equality\n at /e2e/scenarios/test-framework-evals-vitest/runner.case.ts:0:0\n at Object.fn (/js/src/wrappers/vitest/wrapper.ts:0:0)\n at args.experiment.traced.name (/js/src/wrappers/shared/traced-eval.ts:0:0)\n at /js/src/logger.ts:0:0\n at AsyncLocalStorage.run (node::0:0)\n at BraintrustContextManager.runInContext (/js/src/logger.ts:0:0)\n at withCurrent (/js/src/logger.ts:0:0)\n at /js/src/logger.ts:0:0\n at runCatchFinally (/js/src/util.ts:0:0)\n at Experiment.traced (/js/src/logger.ts:0:0)" + "error": "expected 'wrong' to be 'right' // Object.is equality\n\nAssertionError: expected 'wrong' to be 'right' // Object.is equality\n at /e2e/scenarios/test-framework-evals-vitest/runner.case.ts:0:0\n at Object.fn (/js/src/wrappers/vitest/wrapper.ts:0:0)\n at args.experiment.traced.name (/js/src/wrappers/shared/traced-eval.ts:0:0)\n at /js/src/logger/core.ts:0:0\n at AsyncLocalStorage.run (node::0:0)\n at BraintrustContextManager.runInContext (/js/src/logger/core.ts:0:0)\n at withCurrent (/js/src/logger/core.ts:0:0)\n at /js/src/logger/experiments.ts:0:0\n at runCatchFinally (/js/src/util.ts:0:0)\n at Experiment.traced (/js/src/logger/experiments.ts:0:0)" } ] } diff --git a/e2e/scenarios/test-framework-evals-vitest/__snapshots__/vitest-v4.span-tree.txt b/e2e/scenarios/test-framework-evals-vitest/__snapshots__/vitest-v4.span-tree.txt index 6ab1bf413..0a45596ab 100644 --- a/e2e/scenarios/test-framework-evals-vitest/__snapshots__/vitest-v4.span-tree.txt +++ b/e2e/scenarios/test-framework-evals-vitest/__snapshots__/vitest-v4.span-tree.txt @@ -70,4 +70,4 @@ span_tree: "scenario": "test-framework-evals-vitest", "testRunId": "" } - error: "expected 'wrong' to be 'right' // Object.is equality\n\nAssertionError: expected 'wrong' to be 'right' // Object.is equality\n at /e2e/scenarios/test-framework-evals-vitest/runner.case.ts:0:0\n at Object.fn (/js/src/wrappers/vitest/wrapper.ts:0:0)\n at args.experiment.traced.name (/js/src/wrappers/shared/traced-eval.ts:0:0)\n at /js/src/logger.ts:0:0\n at AsyncLocalStorage.run (node::0:0)\n at BraintrustContextManager.runInContext (/js/src/logger.ts:0:0)\n at withCurrent (/js/src/logger.ts:0:0)\n at /js/src/logger.ts:0:0\n at runCatchFinally (/js/src/util.ts:0:0)\n at Experiment.traced (/js/src/logger.ts:0:0)" + error: "expected 'wrong' to be 'right' // Object.is equality\n\nAssertionError: expected 'wrong' to be 'right' // Object.is equality\n at /e2e/scenarios/test-framework-evals-vitest/runner.case.ts:0:0\n at Object.fn (/js/src/wrappers/vitest/wrapper.ts:0:0)\n at args.experiment.traced.name (/js/src/wrappers/shared/traced-eval.ts:0:0)\n at /js/src/logger/core.ts:0:0\n at AsyncLocalStorage.run (node::0:0)\n at BraintrustContextManager.runInContext (/js/src/logger/core.ts:0:0)\n at withCurrent (/js/src/logger/core.ts:0:0)\n at /js/src/logger/experiments.ts:0:0\n at runCatchFinally (/js/src/util.ts:0:0)\n at Experiment.traced (/js/src/logger/experiments.ts:0:0)" diff --git a/e2e/scenarios/test-framework-evals-vitest/__snapshots__/vitest-v5-latest.span-tree.json b/e2e/scenarios/test-framework-evals-vitest/__snapshots__/vitest-v5-latest.span-tree.json new file mode 100644 index 000000000..fde5790b2 --- /dev/null +++ b/e2e/scenarios/test-framework-evals-vitest/__snapshots__/vitest-v5-latest.span-tree.json @@ -0,0 +1,96 @@ +{ + "span_tree": [ + { + "name": "vitest simple pass", + "type": "task", + "children": [], + "output": { + "phase": "simple-pass" + }, + "scores": { + "pass": 1 + }, + "metadata": { + "case": "simple-pass", + "scenario": "test-framework-evals-vitest", + "testRunId": "" + } + }, + { + "name": "vitest configured span", + "type": "task", + "children": [], + "input": { + "value": 5 + }, + "output": { + "phase": "configured-span", + "result": 10 + }, + "expected": 10, + "scores": { + "correctness": 1, + "pass": 1, + "quality": 0.9 + }, + "tags": [ + "math", + "configured" + ], + "metadata": { + "case": "configured-span", + "scenario": "test-framework-evals-vitest", + "testRunId": "" + } + }, + { + "name": "vitest concurrent alpha", + "type": "task", + "children": [], + "output": { + "phase": "concurrent-alpha" + }, + "scores": { + "pass": 1 + }, + "metadata": { + "case": "concurrent-alpha", + "scenario": "test-framework-evals-vitest", + "testRunId": "" + } + }, + { + "name": "vitest concurrent beta", + "type": "task", + "children": [], + "output": { + "phase": "concurrent-beta" + }, + "scores": { + "pass": 1 + }, + "metadata": { + "case": "concurrent-beta", + "scenario": "test-framework-evals-vitest", + "testRunId": "" + } + }, + { + "name": "vitest expected failure", + "type": "task", + "children": [], + "output": { + "phase": "expected-failure" + }, + "scores": { + "pass": 0 + }, + "metadata": { + "case": "expected-failure", + "scenario": "test-framework-evals-vitest", + "testRunId": "" + }, + "error": "expected 'wrong' to be 'right' // Object.is equality\n\nAssertionError: expected 'wrong' to be 'right' // Object.is equality\n at /e2e/scenarios/test-framework-evals-vitest/runner.case.ts:0:0\n at Object.fn (/js/src/wrappers/vitest/wrapper.ts:0:0)\n at args.experiment.traced.name (/js/src/wrappers/shared/traced-eval.ts:0:0)\n at /js/src/logger/core.ts:0:0\n at AsyncLocalStorage.run (node::0:0)\n at BraintrustContextManager.runInContext (/js/src/logger/core.ts:0:0)\n at withCurrent (/js/src/logger/core.ts:0:0)\n at /js/src/logger/experiments.ts:0:0\n at runCatchFinally (/js/src/util.ts:0:0)\n at Experiment.traced (/js/src/logger/experiments.ts:0:0)" + } + ] +} diff --git a/e2e/scenarios/test-framework-evals-vitest/__snapshots__/vitest-v5-latest.span-tree.txt b/e2e/scenarios/test-framework-evals-vitest/__snapshots__/vitest-v5-latest.span-tree.txt new file mode 100644 index 000000000..0a45596ab --- /dev/null +++ b/e2e/scenarios/test-framework-evals-vitest/__snapshots__/vitest-v5-latest.span-tree.txt @@ -0,0 +1,73 @@ +span_tree: +├── vitest simple pass [task] +│ output: { +│ "phase": "simple-pass" +│ } +│ scores: { +│ "pass": 1 +│ } +│ metadata: { +│ "case": "simple-pass", +│ "scenario": "test-framework-evals-vitest", +│ "testRunId": "" +│ } +├── vitest configured span [task] +│ input: { +│ "value": 5 +│ } +│ output: { +│ "phase": "configured-span", +│ "result": 10 +│ } +│ expected: 10 +│ scores: { +│ "correctness": 1, +│ "pass": 1, +│ "quality": 0.9 +│ } +│ tags: [ +│ "math", +│ "configured" +│ ] +│ metadata: { +│ "case": "configured-span", +│ "scenario": "test-framework-evals-vitest", +│ "testRunId": "" +│ } +├── vitest concurrent alpha [task] +│ output: { +│ "phase": "concurrent-alpha" +│ } +│ scores: { +│ "pass": 1 +│ } +│ metadata: { +│ "case": "concurrent-alpha", +│ "scenario": "test-framework-evals-vitest", +│ "testRunId": "" +│ } +├── vitest concurrent beta [task] +│ output: { +│ "phase": "concurrent-beta" +│ } +│ scores: { +│ "pass": 1 +│ } +│ metadata: { +│ "case": "concurrent-beta", +│ "scenario": "test-framework-evals-vitest", +│ "testRunId": "" +│ } +└── vitest expected failure [task] + output: { + "phase": "expected-failure" + } + scores: { + "pass": 0 + } + metadata: { + "case": "expected-failure", + "scenario": "test-framework-evals-vitest", + "testRunId": "" + } + error: "expected 'wrong' to be 'right' // Object.is equality\n\nAssertionError: expected 'wrong' to be 'right' // Object.is equality\n at /e2e/scenarios/test-framework-evals-vitest/runner.case.ts:0:0\n at Object.fn (/js/src/wrappers/vitest/wrapper.ts:0:0)\n at args.experiment.traced.name (/js/src/wrappers/shared/traced-eval.ts:0:0)\n at /js/src/logger/core.ts:0:0\n at AsyncLocalStorage.run (node::0:0)\n at BraintrustContextManager.runInContext (/js/src/logger/core.ts:0:0)\n at withCurrent (/js/src/logger/core.ts:0:0)\n at /js/src/logger/experiments.ts:0:0\n at runCatchFinally (/js/src/util.ts:0:0)\n at Experiment.traced (/js/src/logger/experiments.ts:0:0)" diff --git a/e2e/scenarios/test-framework-evals-vitest/__snapshots__/vitest-v5.span-tree.json b/e2e/scenarios/test-framework-evals-vitest/__snapshots__/vitest-v5.span-tree.json new file mode 100644 index 000000000..fde5790b2 --- /dev/null +++ b/e2e/scenarios/test-framework-evals-vitest/__snapshots__/vitest-v5.span-tree.json @@ -0,0 +1,96 @@ +{ + "span_tree": [ + { + "name": "vitest simple pass", + "type": "task", + "children": [], + "output": { + "phase": "simple-pass" + }, + "scores": { + "pass": 1 + }, + "metadata": { + "case": "simple-pass", + "scenario": "test-framework-evals-vitest", + "testRunId": "" + } + }, + { + "name": "vitest configured span", + "type": "task", + "children": [], + "input": { + "value": 5 + }, + "output": { + "phase": "configured-span", + "result": 10 + }, + "expected": 10, + "scores": { + "correctness": 1, + "pass": 1, + "quality": 0.9 + }, + "tags": [ + "math", + "configured" + ], + "metadata": { + "case": "configured-span", + "scenario": "test-framework-evals-vitest", + "testRunId": "" + } + }, + { + "name": "vitest concurrent alpha", + "type": "task", + "children": [], + "output": { + "phase": "concurrent-alpha" + }, + "scores": { + "pass": 1 + }, + "metadata": { + "case": "concurrent-alpha", + "scenario": "test-framework-evals-vitest", + "testRunId": "" + } + }, + { + "name": "vitest concurrent beta", + "type": "task", + "children": [], + "output": { + "phase": "concurrent-beta" + }, + "scores": { + "pass": 1 + }, + "metadata": { + "case": "concurrent-beta", + "scenario": "test-framework-evals-vitest", + "testRunId": "" + } + }, + { + "name": "vitest expected failure", + "type": "task", + "children": [], + "output": { + "phase": "expected-failure" + }, + "scores": { + "pass": 0 + }, + "metadata": { + "case": "expected-failure", + "scenario": "test-framework-evals-vitest", + "testRunId": "" + }, + "error": "expected 'wrong' to be 'right' // Object.is equality\n\nAssertionError: expected 'wrong' to be 'right' // Object.is equality\n at /e2e/scenarios/test-framework-evals-vitest/runner.case.ts:0:0\n at Object.fn (/js/src/wrappers/vitest/wrapper.ts:0:0)\n at args.experiment.traced.name (/js/src/wrappers/shared/traced-eval.ts:0:0)\n at /js/src/logger/core.ts:0:0\n at AsyncLocalStorage.run (node::0:0)\n at BraintrustContextManager.runInContext (/js/src/logger/core.ts:0:0)\n at withCurrent (/js/src/logger/core.ts:0:0)\n at /js/src/logger/experiments.ts:0:0\n at runCatchFinally (/js/src/util.ts:0:0)\n at Experiment.traced (/js/src/logger/experiments.ts:0:0)" + } + ] +} diff --git a/e2e/scenarios/test-framework-evals-vitest/__snapshots__/vitest-v5.span-tree.txt b/e2e/scenarios/test-framework-evals-vitest/__snapshots__/vitest-v5.span-tree.txt new file mode 100644 index 000000000..0a45596ab --- /dev/null +++ b/e2e/scenarios/test-framework-evals-vitest/__snapshots__/vitest-v5.span-tree.txt @@ -0,0 +1,73 @@ +span_tree: +├── vitest simple pass [task] +│ output: { +│ "phase": "simple-pass" +│ } +│ scores: { +│ "pass": 1 +│ } +│ metadata: { +│ "case": "simple-pass", +│ "scenario": "test-framework-evals-vitest", +│ "testRunId": "" +│ } +├── vitest configured span [task] +│ input: { +│ "value": 5 +│ } +│ output: { +│ "phase": "configured-span", +│ "result": 10 +│ } +│ expected: 10 +│ scores: { +│ "correctness": 1, +│ "pass": 1, +│ "quality": 0.9 +│ } +│ tags: [ +│ "math", +│ "configured" +│ ] +│ metadata: { +│ "case": "configured-span", +│ "scenario": "test-framework-evals-vitest", +│ "testRunId": "" +│ } +├── vitest concurrent alpha [task] +│ output: { +│ "phase": "concurrent-alpha" +│ } +│ scores: { +│ "pass": 1 +│ } +│ metadata: { +│ "case": "concurrent-alpha", +│ "scenario": "test-framework-evals-vitest", +│ "testRunId": "" +│ } +├── vitest concurrent beta [task] +│ output: { +│ "phase": "concurrent-beta" +│ } +│ scores: { +│ "pass": 1 +│ } +│ metadata: { +│ "case": "concurrent-beta", +│ "scenario": "test-framework-evals-vitest", +│ "testRunId": "" +│ } +└── vitest expected failure [task] + output: { + "phase": "expected-failure" + } + scores: { + "pass": 0 + } + metadata: { + "case": "expected-failure", + "scenario": "test-framework-evals-vitest", + "testRunId": "" + } + error: "expected 'wrong' to be 'right' // Object.is equality\n\nAssertionError: expected 'wrong' to be 'right' // Object.is equality\n at /e2e/scenarios/test-framework-evals-vitest/runner.case.ts:0:0\n at Object.fn (/js/src/wrappers/vitest/wrapper.ts:0:0)\n at args.experiment.traced.name (/js/src/wrappers/shared/traced-eval.ts:0:0)\n at /js/src/logger/core.ts:0:0\n at AsyncLocalStorage.run (node::0:0)\n at BraintrustContextManager.runInContext (/js/src/logger/core.ts:0:0)\n at withCurrent (/js/src/logger/core.ts:0:0)\n at /js/src/logger/experiments.ts:0:0\n at runCatchFinally (/js/src/util.ts:0:0)\n at Experiment.traced (/js/src/logger/experiments.ts:0:0)" diff --git a/e2e/scenarios/test-framework-evals-vitest/package.json b/e2e/scenarios/test-framework-evals-vitest/package.json index c7d5e4f36..56778a948 100644 --- a/e2e/scenarios/test-framework-evals-vitest/package.json +++ b/e2e/scenarios/test-framework-evals-vitest/package.json @@ -15,6 +15,10 @@ "vitest-v4-latest": { "package": "vitest", "range": "4" + }, + "vitest-v5-latest": { + "package": "vitest", + "range": "5" } } } @@ -28,7 +32,9 @@ "vitest-v3": "npm:vitest@3.2.4", "vitest-v3-latest": "npm:vitest@3.2.6", "vitest-v4": "npm:vitest@4.1.5", - "vitest-v4-latest": "npm:vitest@4.1.11" + "vitest-v4-latest": "npm:vitest@4.1.11", + "vitest-v5": "npm:vitest@5.0.3", + "vitest-v5-latest": "npm:vitest@5.0.3" }, "pnpm": { "overrides": { diff --git a/e2e/scenarios/test-framework-evals-vitest/pnpm-lock.yaml b/e2e/scenarios/test-framework-evals-vitest/pnpm-lock.yaml index 30efff2e6..e13415135 100644 --- a/e2e/scenarios/test-framework-evals-vitest/pnpm-lock.yaml +++ b/e2e/scenarios/test-framework-evals-vitest/pnpm-lock.yaml @@ -40,6 +40,12 @@ importers: vitest-v4-latest: specifier: npm:vitest@4.1.11 version: vitest@4.1.11(vite@7.3.1) + vitest-v5: + specifier: npm:vitest@5.0.3 + version: vitest@5.0.3(vite@7.3.1) + vitest-v5-latest: + specifier: npm:vitest@5.0.3 + version: vitest@5.0.3(vite@7.3.1) packages: @@ -337,9 +343,19 @@ packages: cpu: [x64] os: [win32] + '@jridgewell/resolve-uri@3.1.2': + resolution: {integrity: sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==} + engines: {node: '>=6.0.0'} + '@jridgewell/sourcemap-codec@1.5.5': resolution: {integrity: sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og==} + '@jridgewell/sourcemap-codec@1.6.0': + resolution: {integrity: sha512-T7jf+5zgsZHwNJ4lvQ7/aezbyk0nNX+zJVWpmHA7VYsEx7a7qr5Rg5IbtJFqkgze5Y2sruq1RUY8Q837Od7iFw==} + + '@jridgewell/trace-mapping@0.3.31': + resolution: {integrity: sha512-zzNR+SdQSDJzc8joaeP8QQoCQr8NuYx2dIIytl1QeBEZHJ9uW6hebsrYgbz8hJwUQao3TWCMtmfV8Nu1twOLAw==} + '@rollup/rollup-android-arm-eabi@4.59.0': resolution: {integrity: sha512-upnNBkA6ZH2VKGcBj9Fyl9IGNPULcjXRlg0LLeaioQWueH30p6IXtJEbKAgvyv+mJaMxSm1l6xwDXYjpEMiLMg==} cpu: [arm] @@ -566,6 +582,17 @@ packages: vite: optional: true + '@vitest/mocker@5.0.3': + resolution: {integrity: sha512-T8sWAIbkSyAjkwTcaEc3Iu0o9A27X1/kdXrizhZkGuSKScRQtRzclfAMpOTcGdXCsqxeWlpGy3XjqaW8CpLORg==} + peerDependencies: + msw: ^2.4.9 + vite: ^6.0.0 || ^7.0.0 || ^8.0.0 + peerDependenciesMeta: + msw: + optional: true + vite: + optional: true + '@vitest/pretty-format@2.1.9': resolution: {integrity: sha512-KhRIdGV2U9HOUzxfiHmY8IFHTdqtOhIzCpd8WRdJiE7D/HUcZVD0EgQCVjm+Q9gkUXWgBvMmTtZgIG48wq7sOQ==} @@ -629,6 +656,9 @@ packages: '@vitest/spy@4.1.5': resolution: {integrity: sha512-2lNOsh6+R2Idnf1TCZqSwYlKN2E/iDlD8sgU59kYVl+OMDmvldO1VDk39smRfpUNwYpNRVn3w4YfuC7KfbBnkQ==} + '@vitest/spy@5.0.3': + resolution: {integrity: sha512-XhFysQTB8AZ+P4gMi+Lpo99vg2AZi0qKpaB9yXQl37+CaMEAPO3iH/wGVnSyL5MPERiLezpqTVtrR6UZH5GCXg==} + '@vitest/utils@2.1.9': resolution: {integrity: sha512-v0psaMSkNJ3A2NMrUEHFRzJtDPFn+/VWZ5WxImB21T9fjucJRmS7xCS3ppEnARb9y11OAzaD+P2Ps+b+BGX5iQ==} @@ -733,6 +763,9 @@ packages: magic-string@0.30.21: resolution: {integrity: sha512-vd2F4YUyEXKGcLHoq+TEyCjxueSeHnFxyyjNp80yg0XV4vUhnDer/lvvlqM/arB5bXQN5K2/3oinyCRyx8T2CQ==} + magic-string@1.4.3: + resolution: {integrity: sha512-z12OxmaPGE0F4xlpGdjuAUckipLpQlTAuAmyGhNoD7ODpYUzvDfvtxUbjM/jwznyP178oju/KDdyi220wNJ0RQ==} + ms@2.1.3: resolution: {integrity: sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==} @@ -803,6 +836,10 @@ packages: tinybench@2.9.0: resolution: {integrity: sha512-0+DUvqWMValLmha6lr4kD8iAMK1HzV0/aKnCtWb9v9641TnP/MFb7Pc2bxoxQjTXAErryXVgUOfv2YqNllqGeg==} + tinybench@6.2.2: + resolution: {integrity: sha512-nIZ2N2dFpjBsg2hgitKu7mA+FvsIJuGJOwysRngmVRRmWgR/UyPjwJj8c/fT/dhm8EkHnF5EOogRf+0Z2qrimA==} + engines: {node: '>=20.0.0'} + tinyexec@0.3.2: resolution: {integrity: sha512-KQQR9yN7R5+OSwaK0XQoj22pwHoTlgYqmUscPYoknOoWCWfj/5/ABTMRi69FrKU5ffPVh5QcFikpWJI/P1ocHA==} @@ -1104,11 +1141,57 @@ packages: jsdom: optional: true + vitest@5.0.3: + resolution: {integrity: sha512-xMw97S3rjdtj5dkVat7jCsqWBpvchs3RlpQctUqwJD0KkERk40vz2fJ77lDwW/Vzh/pk18eItYAzkodhSes3jQ==} + engines: {node: ^22.12.0 || ^24.0.0 || >=26.0.0} + hasBin: true + peerDependencies: + '@edge-runtime/vm': '*' + '@opentelemetry/api': ^1.9.0 + '@types/node': ^22.0.0 || >=24.0.0 + '@vitest/browser-playwright': 5.0.3 + '@vitest/browser-preview': 5.0.3 + '@vitest/browser-webdriverio': ^5.0.0-beta.5 || >=5.0.0 + '@vitest/coverage-istanbul': 5.0.3 + '@vitest/coverage-v8': 5.0.3 + '@vitest/ui': 5.0.3 + happy-dom: '*' + jsdom: '*' + vite: ^6.4.0 || ^7.0.0 || ^8.0.0 + peerDependenciesMeta: + '@edge-runtime/vm': + optional: true + '@opentelemetry/api': + optional: true + '@types/node': + optional: true + '@vitest/browser-playwright': + optional: true + '@vitest/browser-preview': + optional: true + '@vitest/browser-webdriverio': + optional: true + '@vitest/coverage-istanbul': + optional: true + '@vitest/coverage-v8': + optional: true + '@vitest/ui': + optional: true + happy-dom: + optional: true + jsdom: + optional: true + why-is-node-running@2.3.0: resolution: {integrity: sha512-hUrmaWBdVDcxvYqnyh09zunKzROWjbZTiNy8dBEjkS7ehEDQibXJ7XvlmtbwuTclUiIyN+CyXQD4Vmko8fNm8w==} engines: {node: '>=8'} hasBin: true + why-is-node-running@3.2.1: + resolution: {integrity: sha512-Tb2FUhB4vUsGQlfSquQLYkApkuPAFQXGFzxWKHHumVz2dK+X1RUm/HnID4+TfIGYJ1kTcwOaCk/buYCEJr6YjQ==} + engines: {node: '>=20.11'} + hasBin: true + zod@4.4.3: resolution: {integrity: sha512-ytENFjIJFl2UwYglde2jchW2Hwm4GJFLDiSXWdTrJQBIN9Fcyp7n4DhxJEiWNAJMV1/BqWfW/kkg71UDcHJyTQ==} @@ -1261,8 +1344,17 @@ snapshots: '@esbuild/win32-x64@0.27.4': optional: true + '@jridgewell/resolve-uri@3.1.2': {} + '@jridgewell/sourcemap-codec@1.5.5': {} + '@jridgewell/sourcemap-codec@1.6.0': {} + + '@jridgewell/trace-mapping@0.3.31': + dependencies: + '@jridgewell/resolve-uri': 3.1.2 + '@jridgewell/sourcemap-codec': 1.5.5 + '@rollup/rollup-android-arm-eabi@4.59.0': optional: true @@ -1438,6 +1530,15 @@ snapshots: optionalDependencies: vite: 7.3.1 + '@vitest/mocker@5.0.3(vite@7.3.1)': + dependencies: + '@jridgewell/trace-mapping': 0.3.31 + '@vitest/spy': 5.0.3 + estree-walker: 3.0.3 + magic-string: 1.4.3 + optionalDependencies: + vite: 7.3.1 + '@vitest/pretty-format@2.1.9': dependencies: tinyrainbow: 1.2.0 @@ -1537,6 +1638,8 @@ snapshots: '@vitest/spy@4.1.5': {} + '@vitest/spy@5.0.3': {} + '@vitest/utils@2.1.9': dependencies: '@vitest/pretty-format': 2.1.9 @@ -1679,6 +1782,10 @@ snapshots: dependencies: '@jridgewell/sourcemap-codec': 1.5.5 + magic-string@1.4.3: + dependencies: + '@jridgewell/sourcemap-codec': 1.6.0 + ms@2.1.3: {} nanoid@3.3.18: {} @@ -1754,6 +1861,8 @@ snapshots: tinybench@2.9.0: {} + tinybench@6.2.2: {} + tinyexec@0.3.2: {} tinyexec@1.0.4: {} @@ -2010,9 +2119,30 @@ snapshots: transitivePeerDependencies: - msw + vitest@5.0.3(vite@7.3.1): + dependencies: + '@types/chai': 5.2.3 + '@vitest/mocker': 5.0.3(vite@7.3.1) + chai: 6.2.2 + es-module-lexer: 2.3.2 + expect-type: 1.4.0 + magic-string: 1.4.3 + obug: 2.2.1 + picomatch: 4.0.7 + std-env: 4.2.0 + tinybench: 6.2.2 + tinyexec: 1.3.1 + tinyglobby: 0.2.17 + vite: 7.3.1 + why-is-node-running: 3.2.1 + transitivePeerDependencies: + - msw + why-is-node-running@2.3.0: dependencies: siginfo: 2.0.0 stackback: 0.0.2 + why-is-node-running@3.2.1: {} + zod@4.4.3: {} diff --git a/e2e/scenarios/test-framework-evals-vitest/runner.case.ts b/e2e/scenarios/test-framework-evals-vitest/runner.case.ts index 8e75a95c9..7cac7f359 100644 --- a/e2e/scenarios/test-framework-evals-vitest/runner.case.ts +++ b/e2e/scenarios/test-framework-evals-vitest/runner.case.ts @@ -1,4 +1,3 @@ -import * as path from "node:path"; import { afterAll as vitestAfterAll, beforeAll as vitestBeforeAll, @@ -8,20 +7,7 @@ import { } from "vitest"; import { getTestRunId, scopedName } from "../../helpers/scenario-runtime"; -const repoRoot = process.env.BRAINTRUST_E2E_REPO_ROOT; -if (!repoRoot) { - throw new Error("BRAINTRUST_E2E_REPO_ROOT is not set"); -} - -const { login } = await import(path.join(repoRoot, "js/src/logger.ts")); -const { configureNode } = await import( - path.join(repoRoot, "js/src/node/config.ts") -); -const { wrapVitest } = await import( - path.join(repoRoot, "js/src/wrappers/vitest/index.ts") -); - -configureNode(); +import { login, wrapVitest } from "braintrust"; const testRunId = getTestRunId(); const scenario = "test-framework-evals-vitest"; diff --git a/e2e/scenarios/test-framework-evals-vitest/scenario.test.ts b/e2e/scenarios/test-framework-evals-vitest/scenario.test.ts index 75968b70e..94e42ce08 100644 --- a/e2e/scenarios/test-framework-evals-vitest/scenario.test.ts +++ b/e2e/scenarios/test-framework-evals-vitest/scenario.test.ts @@ -1,4 +1,4 @@ -import { expect, test } from "vitest"; +import { expect, test } from "vite-plus/test"; import { resolveFileSnapshotPath } from "../../helpers/file-snapshot"; import { prepareScenarioDir, @@ -21,6 +21,12 @@ interface VitestScenario { } const scenarios: VitestScenario[] = [ + ...["vitest-v5", "vitest-v5-latest"].map((name) => ({ + dependencyName: name, + entry: "scenario.vitest-v5.ts", + label: name, + variantKey: name, + })), { dependencyName: "vitest-v2", entry: "scenario.ts", diff --git a/e2e/scenarios/test-framework-evals-vitest/scenario.vitest-v5.ts b/e2e/scenarios/test-framework-evals-vitest/scenario.vitest-v5.ts new file mode 100644 index 000000000..f4a9092df --- /dev/null +++ b/e2e/scenarios/test-framework-evals-vitest/scenario.vitest-v5.ts @@ -0,0 +1,48 @@ +import { createRequire } from "node:module"; +import { promises as fs } from "node:fs"; +import * as path from "node:path"; +import { resolveScenarioDir } from "../../helpers/scenario-harness"; +import { + getTestRunId, + runMain, + runNodeSubprocess, +} from "../../helpers/scenario-runtime"; + +const require = createRequire(import.meta.url); +const scenarioDir = resolveScenarioDir(import.meta.url); + +// Resolve the vitest.mjs bin by finding the package root via the main entry. +async function findVitestBin(packageName: string): Promise { + const entryPath = require.resolve(packageName); + let dir = path.dirname(entryPath); + while (dir !== path.dirname(dir)) { + const candidate = path.join(dir, "vitest.mjs"); + try { + await fs.access(candidate); + return candidate; + } catch { + // Keep walking upward. + } + dir = path.dirname(dir); + } + throw new Error(`Could not find vitest.mjs for ${packageName}`); +} + +async function main() { + const vitestPackageName = + process.env.VITEST_PACKAGE_NAME ?? "vitest-v5-latest"; + const vitestCliPath = await findVitestBin(vitestPackageName); + const testRunId = getTestRunId(); + + await runNodeSubprocess({ + args: [vitestCliPath, "run", "--config", "vitest.runner-v5.config.mts"], + cwd: scenarioDir, + env: { + BRAINTRUST_E2E_RUN_ID: testRunId, + VITEST_CONFIG_PACKAGE_NAME: vitestPackageName, + }, + timeoutMs: 60_000, + }); +} + +runMain(main); diff --git a/e2e/scenarios/test-framework-evals-vitest/vitest.runner-evals-reporter.config.mts b/e2e/scenarios/test-framework-evals-vitest/vitest.runner-evals-reporter.config.mts index f2a1afe65..4b33922a2 100644 --- a/e2e/scenarios/test-framework-evals-vitest/vitest.runner-evals-reporter.config.mts +++ b/e2e/scenarios/test-framework-evals-vitest/vitest.runner-evals-reporter.config.mts @@ -1,20 +1,11 @@ -import * as path from "node:path"; -import { pathToFileURL } from "node:url"; import { defineConfig } from "vitest/config"; -const repoRoot = process.env.BRAINTRUST_E2E_REPO_ROOT; -if (!repoRoot) { - throw new Error("BRAINTRUST_E2E_REPO_ROOT is not set"); -} - const testRunId = process.env.BRAINTRUST_E2E_RUN_ID; if (!testRunId) { throw new Error("BRAINTRUST_E2E_RUN_ID is not set"); } -const { default: BraintrustVitestEvalsReporter } = await import( - pathToFileURL(path.join(repoRoot, "js/dist/vitest-evals-reporter.mjs")).href -); +import BraintrustVitestEvalsReporter from "braintrust/vitest-evals-reporter"; export default defineConfig({ test: { diff --git a/e2e/scenarios/test-framework-evals-vitest/vitest.runner-v5.config.mts b/e2e/scenarios/test-framework-evals-vitest/vitest.runner-v5.config.mts new file mode 100644 index 000000000..4e000c5a8 --- /dev/null +++ b/e2e/scenarios/test-framework-evals-vitest/vitest.runner-v5.config.mts @@ -0,0 +1,13 @@ +const vitestConfigPackageName = + process.env.VITEST_CONFIG_PACKAGE_NAME ?? "vitest-v5-latest"; +const { defineConfig } = await import(`${vitestConfigPackageName}/config`); + +export default defineConfig({ + test: { + clearMocks: false, + sharedViteServer: false, + hookTimeout: 30_000, + include: ["runner.case.ts"], + testTimeout: 20_000, + }, +}); diff --git a/e2e/scenarios/trace-context-and-continuation/scenario.test.ts b/e2e/scenarios/trace-context-and-continuation/scenario.test.ts index f386798e2..49dafc0b9 100644 --- a/e2e/scenarios/trace-context-and-continuation/scenario.test.ts +++ b/e2e/scenarios/trace-context-and-continuation/scenario.test.ts @@ -1,4 +1,4 @@ -import { expect, test } from "vitest"; +import { expect, test } from "vite-plus/test"; import { formatJsonFileSnapshot, matchFileSnapshot, diff --git a/e2e/scenarios/trace-context-and-continuation/scenario.ts b/e2e/scenarios/trace-context-and-continuation/scenario.ts index c0bb88ec3..bcf1371ba 100644 --- a/e2e/scenarios/trace-context-and-continuation/scenario.ts +++ b/e2e/scenarios/trace-context-and-continuation/scenario.ts @@ -1,5 +1,6 @@ import { flush, + extractTraceContextFromHeaders, initLogger, startSpan, traced, @@ -28,7 +29,7 @@ async function main() { }, }, }); - const exportedRoot = await rootSpan.export(); + const propagatedRoot = extractTraceContextFromHeaders(rootSpan.inject()); await withCurrent(rootSpan, async () => { const currentChild = startSpan({ @@ -50,7 +51,7 @@ async function main() { rootSpan.end(); - await withParent(exportedRoot, async () => { + await withParent(propagatedRoot, async () => { await traced( (span) => { span.log({ diff --git a/e2e/scenarios/trace-primitives-basic/__snapshots__/request-flow.json b/e2e/scenarios/trace-primitives-basic/__snapshots__/request-flow.json index 34b80ef8d..cc13d90d0 100644 --- a/e2e/scenarios/trace-primitives-basic/__snapshots__/request-flow.json +++ b/e2e/scenarios/trace-primitives-basic/__snapshots__/request-flow.json @@ -109,7 +109,7 @@ "caller_lineno": 0 }, "created": "", - "error": "basic boom\n\nError: basic boom\n at logger.traced.name (/e2e/scenarios/trace-primitives-basic/scenario.ts:0:0)\n at /js/dist/index.mjs:0:0\n at AsyncLocalStorage.run (node::0:0)\n at BraintrustContextManager.runInContext (/js/dist/index.mjs:0:0)\n at withCurrent (/js/dist/index.mjs:0:0)\n at /js/dist/index.mjs:0:0\n at runCatchFinally (/js/dist/index.mjs:0:0)\n at Logger.traced (/js/dist/index.mjs:0:0)\n at main (/e2e/scenarios/trace-primitives-basic/scenario.ts:0:0)\n at process.processTicksAndRejections (node::0:0)", + "error": "basic boom\n\nError: basic boom\n at logger.traced.name (/e2e/scenarios/trace-primitives-basic/scenario.ts:0:0)\n at (/js/src/logger/core.ts:0:0)\n at AsyncLocalStorage.run (node::0:0)\n at BraintrustContextManager.runInContext (/js/src/logger/core.ts:0:0)\n at withCurrent (/js/src/logger/core.ts:0:0)\n at (/js/src/logger/core.ts:0:0)\n at runCatchFinally (/js/src/util.ts:0:0)\n at Logger.traced (/js/src/logger/core.ts:0:0)\n at main (/e2e/scenarios/trace-primitives-basic/scenario.ts:0:0)\n at process.processTicksAndRejections (node::0:0)", "id": "", "log_id": "g", "metadata": { @@ -214,7 +214,7 @@ "caller_lineno": 0 }, "created": "", - "error": "basic boom\n\nError: basic boom\n at logger.traced.name (/e2e/scenarios/trace-primitives-basic/scenario.ts:0:0)\n at /js/dist/index.mjs:0:0\n at AsyncLocalStorage.run (node::0:0)\n at BraintrustContextManager.runInContext (/js/dist/index.mjs:0:0)\n at withCurrent (/js/dist/index.mjs:0:0)\n at /js/dist/index.mjs:0:0\n at runCatchFinally (/js/dist/index.mjs:0:0)\n at Logger.traced (/js/dist/index.mjs:0:0)\n at main (/e2e/scenarios/trace-primitives-basic/scenario.ts:0:0)\n at process.processTicksAndRejections (node::0:0)", + "error": "basic boom\n\nError: basic boom\n at logger.traced.name (/e2e/scenarios/trace-primitives-basic/scenario.ts:0:0)\n at (/js/src/logger/core.ts:0:0)\n at AsyncLocalStorage.run (node::0:0)\n at BraintrustContextManager.runInContext (/js/src/logger/core.ts:0:0)\n at withCurrent (/js/src/logger/core.ts:0:0)\n at (/js/src/logger/core.ts:0:0)\n at runCatchFinally (/js/src/util.ts:0:0)\n at Logger.traced (/js/src/logger/core.ts:0:0)\n at main (/e2e/scenarios/trace-primitives-basic/scenario.ts:0:0)\n at process.processTicksAndRejections (node::0:0)", "id": "", "log_id": "g", "metadata": { diff --git a/e2e/scenarios/trace-primitives-basic/__snapshots__/span-tree.json b/e2e/scenarios/trace-primitives-basic/__snapshots__/span-tree.json index 852eddd49..2dd9ee811 100644 --- a/e2e/scenarios/trace-primitives-basic/__snapshots__/span-tree.json +++ b/e2e/scenarios/trace-primitives-basic/__snapshots__/span-tree.json @@ -26,7 +26,7 @@ "kind": "basic-error", "testRunId": "" }, - "error": "basic boom\n\nError: basic boom\n at logger.traced.name (/e2e/scenarios/trace-primitives-basic/scenario.ts:0:0)\n at /js/dist/index.mjs:0:0\n at AsyncLocalStorage.run (node::0:0)\n at BraintrustContextManager.runInContext (/js/dist/index.mjs:0:0)\n at withCurrent (/js/dist/index.mjs:0:0)\n at /js/dist/index.mjs:0:0\n at runCatchFinally (/js/dist/index.mjs:0:0)\n at Logger.traced (/js/dist/index.mjs:0:0)\n at main (/e2e/scenarios/trace-primitives-basic/scenario.ts:0:0)\n at process.processTicksAndRejections (node::0:0)" + "error": "basic boom\n\nError: basic boom\n at logger.traced.name (/e2e/scenarios/trace-primitives-basic/scenario.ts:0:0)\n at (/js/src/logger/core.ts:0:0)\n at AsyncLocalStorage.run (node::0:0)\n at BraintrustContextManager.runInContext (/js/src/logger/core.ts:0:0)\n at withCurrent (/js/src/logger/core.ts:0:0)\n at (/js/src/logger/core.ts:0:0)\n at runCatchFinally (/js/src/util.ts:0:0)\n at Logger.traced (/js/src/logger/core.ts:0:0)\n at main (/e2e/scenarios/trace-primitives-basic/scenario.ts:0:0)\n at process.processTicksAndRejections (node::0:0)" } ], "input": { diff --git a/e2e/scenarios/trace-primitives-basic/__snapshots__/span-tree.txt b/e2e/scenarios/trace-primitives-basic/__snapshots__/span-tree.txt index dd6da9af4..296f115b4 100644 --- a/e2e/scenarios/trace-primitives-basic/__snapshots__/span-tree.txt +++ b/e2e/scenarios/trace-primitives-basic/__snapshots__/span-tree.txt @@ -28,4 +28,4 @@ span_tree: "kind": "basic-error", "testRunId": "" } - error: "basic boom\n\nError: basic boom\n at logger.traced.name (/e2e/scenarios/trace-primitives-basic/scenario.ts:0:0)\n at /js/dist/index.mjs:0:0\n at AsyncLocalStorage.run (node::0:0)\n at BraintrustContextManager.runInContext (/js/dist/index.mjs:0:0)\n at withCurrent (/js/dist/index.mjs:0:0)\n at /js/dist/index.mjs:0:0\n at runCatchFinally (/js/dist/index.mjs:0:0)\n at Logger.traced (/js/dist/index.mjs:0:0)\n at main (/e2e/scenarios/trace-primitives-basic/scenario.ts:0:0)\n at process.processTicksAndRejections (node::0:0)" + error: "basic boom\n\nError: basic boom\n at logger.traced.name (/e2e/scenarios/trace-primitives-basic/scenario.ts:0:0)\n at (/js/src/logger/core.ts:0:0)\n at AsyncLocalStorage.run (node::0:0)\n at BraintrustContextManager.runInContext (/js/src/logger/core.ts:0:0)\n at withCurrent (/js/src/logger/core.ts:0:0)\n at (/js/src/logger/core.ts:0:0)\n at runCatchFinally (/js/src/util.ts:0:0)\n at Logger.traced (/js/src/logger/core.ts:0:0)\n at main (/e2e/scenarios/trace-primitives-basic/scenario.ts:0:0)\n at process.processTicksAndRejections (node::0:0)" diff --git a/e2e/scenarios/trace-primitives-basic/scenario.test.ts b/e2e/scenarios/trace-primitives-basic/scenario.test.ts index ac13928d2..7a8d89d6e 100644 --- a/e2e/scenarios/trace-primitives-basic/scenario.test.ts +++ b/e2e/scenarios/trace-primitives-basic/scenario.test.ts @@ -1,4 +1,4 @@ -import { expect, test } from "vitest"; +import { expect, test } from "vite-plus/test"; import { formatJsonFileSnapshot, matchFileSnapshot, diff --git a/e2e/scenarios/ts-exec-loader/scenario.test.ts b/e2e/scenarios/ts-exec-loader/scenario.test.ts index af9786886..75f707fd7 100644 --- a/e2e/scenarios/ts-exec-loader/scenario.test.ts +++ b/e2e/scenarios/ts-exec-loader/scenario.test.ts @@ -1,4 +1,4 @@ -import { expect, test } from "vitest"; +import { expect, test } from "vite-plus/test"; import { prepareScenarioDir, resolveScenarioDir, diff --git a/e2e/scenarios/typesafe-instrumentation/assertions.ts b/e2e/scenarios/typesafe-instrumentation/assertions.ts index 6e0699c14..849f5ad2a 100644 --- a/e2e/scenarios/typesafe-instrumentation/assertions.ts +++ b/e2e/scenarios/typesafe-instrumentation/assertions.ts @@ -1,4 +1,4 @@ -import { beforeAll, describe, expect, test } from "vitest"; +import { beforeAll, describe, expect, test } from "vite-plus/test"; import type { CapturedLogEvent } from "../../helpers/mock-braintrust-server"; import { resolveFileSnapshotPath } from "../../helpers/file-snapshot"; import { diff --git a/e2e/scenarios/typesafe-instrumentation/scenario.impl.mjs b/e2e/scenarios/typesafe-instrumentation/scenario.impl.mjs index 39c04c67b..de6c230c0 100644 --- a/e2e/scenarios/typesafe-instrumentation/scenario.impl.mjs +++ b/e2e/scenarios/typesafe-instrumentation/scenario.impl.mjs @@ -84,16 +84,19 @@ export async function runTypeSafeInstrumentationScenario( const bytes = await readFile( new URL("./test-image.png", import.meta.url), ); - process.env.BRAINTRUST_CAPTURE_ATTACHMENTS = "false"; - await runOperation("typesafe-image-operation", "image", async () => { - await client.systemOne({ - model: "jev-preview", - state: typesafe.image(bytes), - questions: { ship: typesafe.noul("Is there a sailing ship?") }, - }); - }); + await runOperation( + "typesafe-image-operation", + "image", + async () => { + await client.systemOne({ + model: "jev-preview", + state: typesafe.image(bytes), + questions: { ship: typesafe.noul("Is there a sailing ship?") }, + }); + }, + { autoCaptureAttachments: false }, + ); - process.env.BRAINTRUST_CAPTURE_ATTACHMENTS = "true"; await runOperation( "typesafe-template-operation", "template", @@ -117,36 +120,47 @@ export async function runTypeSafeInstrumentationScenario( }) .withResponse(); }, + { autoCaptureAttachments: true }, ); - await runOperation("typesafe-box-operation", "box", async () => { - const photo = typesafe.image(new Blob([bytes], { type: "image/png" })); - const response = await client - .systemOne({ - model: "jev-preview", - state: { "photos~/": [""], description: "Locate the ship." }, - assets: { "": photo }, - questions: { - ship: typesafe.box("The sailing ship"), - mast: typesafe.box("The tallest mast on the ship"), - present: typesafe.noul("Is there a sailing ship?"), - weather: typesafe.choice("What weather is shown?", { - stormy: null, - clear: null, - }), - waves: typesafe.score("How rough are the waves?", [ - "calm", - "rough", - ]), - }, - }) - .asResponse(); - if (response.bodyUsed) - throw new Error("Instrumentation consumed the image response"); - const result = await response.json(); - if (result.answers.ship.type !== "box") - throw new Error("Expected a box answer"); - }); + await runOperation( + "typesafe-box-operation", + "box", + async () => { + const photo = typesafe.image( + new Blob([bytes], { type: "image/png" }), + ); + const response = await client + .systemOne({ + model: "jev-preview", + state: { + "photos~/": [""], + description: "Locate the ship.", + }, + assets: { "": photo }, + questions: { + ship: typesafe.box("The sailing ship"), + mast: typesafe.box("The tallest mast on the ship"), + present: typesafe.noul("Is there a sailing ship?"), + weather: typesafe.choice("What weather is shown?", { + stormy: null, + clear: null, + }), + waves: typesafe.score("How rough are the waves?", [ + "calm", + "rough", + ]), + }, + }) + .asResponse(); + if (response.bodyUsed) + throw new Error("Instrumentation consumed the image response"); + const result = await response.json(); + if (result.answers.ship.type !== "box") + throw new Error("Expected a box answer"); + }, + { autoCaptureAttachments: true }, + ); }, metadata: { scenario: SCENARIO_NAME }, projectNameBase: "tmp-luca-e2e-typesafe-instrumentation", diff --git a/e2e/scenarios/typesafe-instrumentation/scenario.test.ts b/e2e/scenarios/typesafe-instrumentation/scenario.test.ts index ff6883276..d947988fb 100644 --- a/e2e/scenarios/typesafe-instrumentation/scenario.test.ts +++ b/e2e/scenarios/typesafe-instrumentation/scenario.test.ts @@ -1,4 +1,4 @@ -import { describe } from "vitest"; +import { describe } from "vite-plus/test"; import { describeVariant } from "../../helpers/describe-variant"; import { prepareScenarioDir, diff --git a/e2e/scenarios/voyageai-instrumentation/assertions.ts b/e2e/scenarios/voyageai-instrumentation/assertions.ts index 0966f59d5..807defd50 100644 --- a/e2e/scenarios/voyageai-instrumentation/assertions.ts +++ b/e2e/scenarios/voyageai-instrumentation/assertions.ts @@ -1,4 +1,4 @@ -import { beforeAll, describe, expect, test } from "vitest"; +import { beforeAll, describe, expect, test } from "vite-plus/test"; import type { CapturedLogEvent } from "../../helpers/mock-braintrust-server"; import { resolveFileSnapshotPath } from "../../helpers/file-snapshot"; import { diff --git a/e2e/scenarios/voyageai-instrumentation/scenario.test.ts b/e2e/scenarios/voyageai-instrumentation/scenario.test.ts index 1033863d4..77f91d3e4 100644 --- a/e2e/scenarios/voyageai-instrumentation/scenario.test.ts +++ b/e2e/scenarios/voyageai-instrumentation/scenario.test.ts @@ -1,4 +1,4 @@ -import { describe } from "vitest"; +import { describe } from "vite-plus/test"; import { describeVariant } from "../../helpers/describe-variant"; import { prepareScenarioDir, diff --git a/e2e/scenarios/workers-ai-instrumentation/scenario.test.ts b/e2e/scenarios/workers-ai-instrumentation/scenario.test.ts index 009f9b31b..de69ea090 100644 --- a/e2e/scenarios/workers-ai-instrumentation/scenario.test.ts +++ b/e2e/scenarios/workers-ai-instrumentation/scenario.test.ts @@ -1,4 +1,4 @@ -import { describe, expect, test } from "vitest"; +import { describe, expect, test } from "vite-plus/test"; import { prepareScenarioDir, readInstalledPackageVersion, @@ -72,7 +72,7 @@ for (const dependency of ["miniflare-v4", "miniflare-v4-latest"]) { scenarioDir, env: { MINIFLARE_PACKAGE_NAME: dependency, - BRAINTRUST_CAPTURE_ATTACHMENTS: String(capture), + BRAINTRUST_AUTO_CAPTURE_ATTACHMENTS: String(capture), }, runContext: { originalScenarioDir, variantKey: dependency }, timeoutMs: 180_000, diff --git a/e2e/scenarios/workers-ai-instrumentation/scenario.ts b/e2e/scenarios/workers-ai-instrumentation/scenario.ts index 5dd05ba10..a57efcc53 100644 --- a/e2e/scenarios/workers-ai-instrumentation/scenario.ts +++ b/e2e/scenarios/workers-ai-instrumentation/scenario.ts @@ -92,7 +92,7 @@ runMain(async () => { "BRAINTRUST_API_URL", "BRAINTRUST_E2E_RUN_ID", "BRAINTRUST_E2E_PROJECT_NAME", - "BRAINTRUST_CAPTURE_ATTACHMENTS", + "BRAINTRUST_AUTO_CAPTURE_ATTACHMENTS", "WORKERS_AI_BINDING_ONLY", ]; mf = new Miniflare({ diff --git a/e2e/scenarios/workflow-eval-batch/scenario.test.ts b/e2e/scenarios/workflow-eval-batch/scenario.test.ts index 0062d4a16..7c21ede1c 100644 --- a/e2e/scenarios/workflow-eval-batch/scenario.test.ts +++ b/e2e/scenarios/workflow-eval-batch/scenario.test.ts @@ -1,4 +1,4 @@ -import { expect, test } from "vitest"; +import { expect, test } from "vite-plus/test"; import { prepareScenarioDir, resolveScenarioDir, diff --git a/e2e/scenarios/workflow-eval-webhook/scenario.test.ts b/e2e/scenarios/workflow-eval-webhook/scenario.test.ts index a81e7785c..66c8a448d 100644 --- a/e2e/scenarios/workflow-eval-webhook/scenario.test.ts +++ b/e2e/scenarios/workflow-eval-webhook/scenario.test.ts @@ -1,4 +1,4 @@ -import { expect, test } from "vitest"; +import { expect, test } from "vite-plus/test"; import { prepareScenarioDir, resolveScenarioDir, diff --git a/e2e/scenarios/wrap-langchain-js-traces/__cassettes__/wrap-langchain-js-traces-v1-latest.cassette.json b/e2e/scenarios/wrap-langchain-js-traces/__cassettes__/wrap-langchain-js-traces-v1-latest.cassette.json deleted file mode 100644 index bce5a3c83..000000000 --- a/e2e/scenarios/wrap-langchain-js-traces/__cassettes__/wrap-langchain-js-traces-v1-latest.cassette.json +++ /dev/null @@ -1,673 +0,0 @@ -{ - "entries": [ - { - "callIndex": 0, - "id": "86e93f0671afd2fb", - "matchKey": "POST api.openai.com/v1/chat/completions", - "recordedAt": "2026-08-24T12:20:32.278Z", - "request": { - "body": { - "kind": "json", - "value": { - "max_completion_tokens": 512, - "messages": [ - { - "content": "Reply with exactly OK.", - "role": "user" - } - ], - "model": "gpt-5-nano", - "stream": false - } - }, - "headers": {}, - "method": "POST", - "url": "https://api.openai.com/v1/chat/completions" - }, - "response": { - "body": { - "kind": "json", - "value": { - "choices": [ - { - "finish_reason": "stop", - "index": 0, - "message": { - "annotations": [], - "content": "OK", - "refusal": null, - "role": "assistant" - } - } - ], - "created": 1787574030, - "id": "chatcmpl-EGNx8tOIBqW4PL9inY296btKeUCr1", - "model": "gpt-5-nano-2025-08-07", - "object": "chat.completion", - "service_tier": "default", - "system_fingerprint": null, - "usage": { - "completion_tokens": 266, - "completion_tokens_details": { - "accepted_prediction_tokens": 0, - "audio_tokens": 0, - "reasoning_tokens": 256, - "rejected_prediction_tokens": 0 - }, - "prompt_tokens": 11, - "prompt_tokens_details": { - "audio_tokens": 0, - "cached_tokens": 0 - }, - "total_tokens": 277 - } - } - }, - "headers": { - "access-control-expose-headers": "X-Request-ID, CF-Ray, CF-Ray", - "alt-svc": "h3=\":443\"; ma=86400", - "cf-cache-status": "DYNAMIC", - "cf-ray": "a3024fb8088a2fd0-IAD", - "connection": "keep-alive", - "content-encoding": "gzip", - "content-type": "application/json", - "date": "Mon, 24 Aug 2026 12:20:32 GMT", - "openai-organization": "braintrust-data", - "openai-processing-ms": "2073", - "openai-project": "proj_vsCSXafhhByzWOThMrJcZiw9", - "openai-version": "2020-10-01", - "server": "cloudflare", - "set-cookie": "[REDACTED]", - "strict-transport-security": "max-age=31536000; includeSubDomains; preload", - "transfer-encoding": "chunked", - "x-content-type-options": "nosniff", - "x-openai-proxy-wasm": "v0.1", - "x-ratelimit-limit-requests": "30000", - "x-ratelimit-limit-tokens": "180000000", - "x-ratelimit-remaining-requests": "29999", - "x-ratelimit-remaining-tokens": "179999994", - "x-ratelimit-reset-requests": "2ms", - "x-ratelimit-reset-tokens": "0s", - "x-request-id": "req_4371b279cf104e0697b031338add78f2" - }, - "status": 200, - "statusText": "OK" - } - }, - { - "callIndex": 1, - "id": "a613a13df4910706", - "matchKey": "POST api.openai.com/v1/chat/completions", - "recordedAt": "2026-08-24T12:20:32.699Z", - "request": { - "body": { - "kind": "json", - "value": { - "max_tokens": 32, - "messages": [ - { - "content": "Reply with the single word PARIS and nothing else.", - "role": "user" - } - ], - "model": "gpt-4o-mini-2024-07-18", - "stream": false, - "temperature": 0 - } - }, - "headers": {}, - "method": "POST", - "url": "https://api.openai.com/v1/chat/completions" - }, - "response": { - "body": { - "kind": "json", - "value": { - "choices": [ - { - "finish_reason": "stop", - "index": 0, - "logprobs": null, - "message": { - "annotations": [], - "content": "PARIS", - "refusal": null, - "role": "assistant" - } - } - ], - "created": 1787574032, - "id": "chatcmpl-EGNxAPgSJsXEVlK5YEAjNpYLSUFML", - "model": "gpt-4o-mini-2024-07-18", - "object": "chat.completion", - "service_tier": "default", - "system_fingerprint": "fp_fabbcdf5d9", - "usage": { - "completion_tokens": 2, - "completion_tokens_details": { - "accepted_prediction_tokens": 0, - "audio_tokens": 0, - "reasoning_tokens": 0, - "rejected_prediction_tokens": 0 - }, - "prompt_tokens": 18, - "prompt_tokens_details": { - "audio_tokens": 0, - "cached_tokens": 0 - }, - "total_tokens": 20 - } - } - }, - "headers": { - "access-control-expose-headers": "X-Request-ID, CF-Ray, CF-Ray", - "alt-svc": "h3=\":443\"; ma=86400", - "cf-cache-status": "DYNAMIC", - "cf-ray": "a3024fc59a5b2fd0-IAD", - "connection": "keep-alive", - "content-encoding": "gzip", - "content-type": "application/json", - "date": "Mon, 24 Aug 2026 12:20:32 GMT", - "openai-organization": "braintrust-data", - "openai-processing-ms": "338", - "openai-project": "proj_vsCSXafhhByzWOThMrJcZiw9", - "openai-version": "2020-10-01", - "server": "cloudflare", - "set-cookie": "[REDACTED]", - "strict-transport-security": "max-age=31536000; includeSubDomains; preload", - "transfer-encoding": "chunked", - "x-content-type-options": "nosniff", - "x-openai-proxy-wasm": "v0.1", - "x-ratelimit-limit-requests": "30000", - "x-ratelimit-limit-tokens": "150000000", - "x-ratelimit-remaining-requests": "29999", - "x-ratelimit-remaining-tokens": "149999985", - "x-ratelimit-reset-requests": "2ms", - "x-ratelimit-reset-tokens": "0s", - "x-request-id": "req_6de919b320864952a77010c3682478ac" - }, - "status": 200, - "statusText": "OK" - } - }, - { - "callIndex": 2, - "id": "394c0994869e6e1f", - "matchKey": "POST api.openai.com/v1/chat/completions", - "recordedAt": "2026-08-24T12:20:33.104Z", - "request": { - "body": { - "kind": "json", - "value": { - "max_tokens": 32, - "messages": [ - { - "content": "Count from 1 to 3 and include the words one two three.", - "role": "user" - } - ], - "model": "gpt-4o-mini-2024-07-18", - "stream": true, - "stream_options": { - "include_usage": true - }, - "temperature": 0 - } - }, - "headers": {}, - "method": "POST", - "url": "https://api.openai.com/v1/chat/completions" - }, - "response": { - "body": { - "chunks": [ - "data: {\"id\":\"chatcmpl-EGNxAtVgtjTY51DEVaYfresrdBMst\",\"object\":\"chat.completion.chunk\",\"created\":1787574032,\"model\":\"gpt-4o-mini-2024-07-18\",\"service_tier\":\"default\",\"system_fingerprint\":\"fp_0950b7f384\",\"choices\":[{\"index\":0,\"delta\":{\"role\":\"assistant\",\"content\":\"\",\"refusal\":null},\"logprobs\":null,\"finish_reason\":null}],\"usage\":null,\"obfuscation\":\"wxWQ0eCDL\"}", - "data: {\"id\":\"chatcmpl-EGNxAtVgtjTY51DEVaYfresrdBMst\",\"object\":\"chat.completion.chunk\",\"created\":1787574032,\"model\":\"gpt-4o-mini-2024-07-18\",\"service_tier\":\"default\",\"system_fingerprint\":\"fp_0950b7f384\",\"choices\":[{\"index\":0,\"delta\":{\"content\":\"One\"},\"logprobs\":null,\"finish_reason\":null}],\"usage\":null,\"obfuscation\":\"uje9Teyd\"}", - "data: {\"id\":\"chatcmpl-EGNxAtVgtjTY51DEVaYfresrdBMst\",\"object\":\"chat.completion.chunk\",\"created\":1787574032,\"model\":\"gpt-4o-mini-2024-07-18\",\"service_tier\":\"default\",\"system_fingerprint\":\"fp_0950b7f384\",\"choices\":[{\"index\":0,\"delta\":{\"content\":\",\"},\"logprobs\":null,\"finish_reason\":null}],\"usage\":null,\"obfuscation\":\"B0K64QetnP\"}", - "data: {\"id\":\"chatcmpl-EGNxAtVgtjTY51DEVaYfresrdBMst\",\"object\":\"chat.completion.chunk\",\"created\":1787574032,\"model\":\"gpt-4o-mini-2024-07-18\",\"service_tier\":\"default\",\"system_fingerprint\":\"fp_0950b7f384\",\"choices\":[{\"index\":0,\"delta\":{\"content\":\" two\"},\"logprobs\":null,\"finish_reason\":null}],\"usage\":null,\"obfuscation\":\"S4dypzF\"}", - "data: {\"id\":\"chatcmpl-EGNxAtVgtjTY51DEVaYfresrdBMst\",\"object\":\"chat.completion.chunk\",\"created\":1787574032,\"model\":\"gpt-4o-mini-2024-07-18\",\"service_tier\":\"default\",\"system_fingerprint\":\"fp_0950b7f384\",\"choices\":[{\"index\":0,\"delta\":{\"content\":\",\"},\"logprobs\":null,\"finish_reason\":null}],\"usage\":null,\"obfuscation\":\"XdqizbjE0P\"}", - "data: {\"id\":\"chatcmpl-EGNxAtVgtjTY51DEVaYfresrdBMst\",\"object\":\"chat.completion.chunk\",\"created\":1787574032,\"model\":\"gpt-4o-mini-2024-07-18\",\"service_tier\":\"default\",\"system_fingerprint\":\"fp_0950b7f384\",\"choices\":[{\"index\":0,\"delta\":{\"content\":\" three\"},\"logprobs\":null,\"finish_reason\":null}],\"usage\":null,\"obfuscation\":\"Bq0Q1\"}", - "data: {\"id\":\"chatcmpl-EGNxAtVgtjTY51DEVaYfresrdBMst\",\"object\":\"chat.completion.chunk\",\"created\":1787574032,\"model\":\"gpt-4o-mini-2024-07-18\",\"service_tier\":\"default\",\"system_fingerprint\":\"fp_0950b7f384\",\"choices\":[{\"index\":0,\"delta\":{\"content\":\".\"},\"logprobs\":null,\"finish_reason\":null}],\"usage\":null,\"obfuscation\":\"BoOsWxwVOZ\"}", - "data: {\"id\":\"chatcmpl-EGNxAtVgtjTY51DEVaYfresrdBMst\",\"object\":\"chat.completion.chunk\",\"created\":1787574032,\"model\":\"gpt-4o-mini-2024-07-18\",\"service_tier\":\"default\",\"system_fingerprint\":\"fp_0950b7f384\",\"choices\":[{\"index\":0,\"delta\":{},\"logprobs\":null,\"finish_reason\":\"stop\"}],\"usage\":null,\"obfuscation\":\"OgqTv\"}", - "data: {\"id\":\"chatcmpl-EGNxAtVgtjTY51DEVaYfresrdBMst\",\"object\":\"chat.completion.chunk\",\"created\":1787574032,\"model\":\"gpt-4o-mini-2024-07-18\",\"service_tier\":\"default\",\"system_fingerprint\":\"fp_0950b7f384\",\"choices\":[],\"usage\":{\"prompt_tokens\":22,\"completion_tokens\":6,\"total_tokens\":28,\"prompt_tokens_details\":{\"cached_tokens\":0,\"audio_tokens\":0},\"completion_tokens_details\":{\"reasoning_tokens\":0,\"audio_tokens\":0,\"accepted_prediction_tokens\":0,\"rejected_prediction_tokens\":0}},\"obfuscation\":\"3mFwIC92nf0\"}", - "data: [DONE]" - ], - "kind": "sse" - }, - "headers": { - "access-control-expose-headers": "X-Request-ID, CF-Ray, CF-Ray", - "alt-svc": "h3=\":443\"; ma=86400", - "cf-cache-status": "DYNAMIC", - "cf-ray": "a3024fc82b622fd0-IAD", - "connection": "keep-alive", - "content-type": "text/event-stream; charset=utf-8", - "date": "Mon, 24 Aug 2026 12:20:32 GMT", - "openai-organization": "braintrust-data", - "openai-processing-ms": "244", - "openai-project": "proj_vsCSXafhhByzWOThMrJcZiw9", - "openai-version": "2020-10-01", - "server": "cloudflare", - "set-cookie": "[REDACTED]", - "strict-transport-security": "max-age=31536000; includeSubDomains; preload", - "transfer-encoding": "chunked", - "x-content-type-options": "nosniff", - "x-openai-proxy-wasm": "v0.1", - "x-ratelimit-limit-requests": "30000", - "x-ratelimit-limit-tokens": "150000000", - "x-ratelimit-remaining-requests": "29999", - "x-ratelimit-remaining-tokens": "149999982", - "x-ratelimit-reset-requests": "2ms", - "x-ratelimit-reset-tokens": "0s", - "x-request-id": "req_0be6fc8fedbe4d70805e3b1ab80f25cf" - }, - "status": 200, - "statusText": "OK" - } - }, - { - "callIndex": 3, - "id": "7f1e5cd685579c33", - "matchKey": "POST api.openai.com/v1/chat/completions", - "recordedAt": "2026-08-24T12:20:33.721Z", - "request": { - "body": { - "kind": "json", - "value": { - "max_tokens": 128, - "messages": [ - { - "content": "Use the get_weather tool for Paris, France. Do not answer from memory.", - "role": "user" - } - ], - "model": "gpt-4o-mini-2024-07-18", - "stream": false, - "temperature": 0, - "tools": [ - { - "function": { - "description": "Get the current weather in a given location", - "name": "get_weather", - "parameters": { - "$schema": "http://json-schema.org/draft-07/schema#", - "additionalProperties": false, - "properties": { - "location": { - "description": "The city and state or city and country", - "type": "string" - } - }, - "required": ["location"], - "type": "object" - } - }, - "type": "function" - } - ] - } - }, - "headers": {}, - "method": "POST", - "url": "https://api.openai.com/v1/chat/completions" - }, - "response": { - "body": { - "kind": "json", - "value": { - "choices": [ - { - "finish_reason": "tool_calls", - "index": 0, - "logprobs": null, - "message": { - "annotations": [], - "content": null, - "refusal": null, - "role": "assistant", - "tool_calls": [ - { - "function": { - "arguments": "{\"location\":\"Paris, France\"}", - "name": "get_weather" - }, - "id": "call_qjYJazVqODvAan4YaPgZZ8ra", - "type": "function" - } - ] - } - } - ], - "created": 1787574033, - "id": "chatcmpl-EGNxB4gTTflGWk1BHpKwKrYsdlH75", - "model": "gpt-4o-mini-2024-07-18", - "object": "chat.completion", - "service_tier": "default", - "system_fingerprint": "fp_02ae59e84c", - "usage": { - "completion_tokens": 16, - "completion_tokens_details": { - "accepted_prediction_tokens": 0, - "audio_tokens": 0, - "reasoning_tokens": 0, - "rejected_prediction_tokens": 0 - }, - "prompt_tokens": 72, - "prompt_tokens_details": { - "audio_tokens": 0, - "cached_tokens": 0 - }, - "total_tokens": 88 - } - } - }, - "headers": { - "access-control-expose-headers": "X-Request-ID, CF-Ray, CF-Ray", - "alt-svc": "h3=\":443\"; ma=86400", - "cf-cache-status": "DYNAMIC", - "cf-ray": "a3024fcabbd32fd0-IAD", - "connection": "keep-alive", - "content-encoding": "gzip", - "content-type": "application/json", - "date": "Mon, 24 Aug 2026 12:20:33 GMT", - "openai-organization": "braintrust-data", - "openai-processing-ms": "546", - "openai-project": "proj_vsCSXafhhByzWOThMrJcZiw9", - "openai-version": "2020-10-01", - "server": "cloudflare", - "set-cookie": "[REDACTED]", - "strict-transport-security": "max-age=31536000; includeSubDomains; preload", - "transfer-encoding": "chunked", - "x-content-type-options": "nosniff", - "x-openai-proxy-wasm": "v0.1", - "x-ratelimit-limit-requests": "30000", - "x-ratelimit-limit-tokens": "150000000", - "x-ratelimit-remaining-requests": "29999", - "x-ratelimit-remaining-tokens": "149999980", - "x-ratelimit-reset-requests": "2ms", - "x-ratelimit-reset-tokens": "0s", - "x-request-id": "req_ded6c40040e749c49af0e74794677f16" - }, - "status": 200, - "statusText": "OK" - } - }, - { - "callIndex": 4, - "id": "92b304bbb4ce5170", - "matchKey": "POST api.openai.com/v1/chat/completions", - "recordedAt": "2026-08-24T12:20:34.417Z", - "request": { - "body": { - "kind": "json", - "value": { - "max_tokens": 128, - "messages": [ - { - "content": "What is 127 multiplied by 49? Use the calculate tool.", - "role": "user" - } - ], - "model": "gpt-4o-mini-2024-07-18", - "stream": false, - "temperature": 0, - "tools": [ - { - "function": { - "description": "Perform a mathematical calculation", - "name": "calculate", - "parameters": { - "$schema": "http://json-schema.org/draft-07/schema#", - "additionalProperties": false, - "properties": { - "a": { - "type": "number" - }, - "b": { - "type": "number" - }, - "operation": { - "enum": ["add", "subtract", "multiply", "divide"], - "type": "string" - } - }, - "required": ["operation", "a", "b"], - "type": "object" - } - }, - "type": "function" - } - ] - } - }, - "headers": {}, - "method": "POST", - "url": "https://api.openai.com/v1/chat/completions" - }, - "response": { - "body": { - "kind": "json", - "value": { - "choices": [ - { - "finish_reason": "tool_calls", - "index": 0, - "logprobs": null, - "message": { - "annotations": [], - "content": null, - "refusal": null, - "role": "assistant", - "tool_calls": [ - { - "function": { - "arguments": "{\"operation\":\"multiply\",\"a\":127,\"b\":49}", - "name": "calculate" - }, - "id": "call_BMkQ0blzGQeVPEC7pIElPdRs", - "type": "function" - } - ] - } - } - ], - "created": 1787574033, - "id": "chatcmpl-EGNxBqL6zQDAnMT6RdDBaUwkEjbWf", - "model": "gpt-4o-mini-2024-07-18", - "object": "chat.completion", - "service_tier": "default", - "system_fingerprint": "fp_0950b7f384", - "usage": { - "completion_tokens": 21, - "completion_tokens_details": { - "accepted_prediction_tokens": 0, - "audio_tokens": 0, - "reasoning_tokens": 0, - "rejected_prediction_tokens": 0 - }, - "prompt_tokens": 76, - "prompt_tokens_details": { - "audio_tokens": 0, - "cached_tokens": 0 - }, - "total_tokens": 97 - } - } - }, - "headers": { - "access-control-expose-headers": "X-Request-ID, CF-Ray, CF-Ray", - "alt-svc": "h3=\":443\"; ma=86400", - "cf-cache-status": "DYNAMIC", - "cf-ray": "a3024fce88f42fd0-IAD", - "connection": "keep-alive", - "content-encoding": "gzip", - "content-type": "application/json", - "date": "Mon, 24 Aug 2026 12:20:34 GMT", - "openai-organization": "braintrust-data", - "openai-processing-ms": "617", - "openai-project": "proj_vsCSXafhhByzWOThMrJcZiw9", - "openai-version": "2020-10-01", - "server": "cloudflare", - "set-cookie": "[REDACTED]", - "strict-transport-security": "max-age=31536000; includeSubDomains; preload", - "transfer-encoding": "chunked", - "x-content-type-options": "nosniff", - "x-openai-proxy-wasm": "v0.1", - "x-ratelimit-limit-requests": "30000", - "x-ratelimit-limit-tokens": "150000000", - "x-ratelimit-remaining-requests": "29999", - "x-ratelimit-remaining-tokens": "149999985", - "x-ratelimit-reset-requests": "2ms", - "x-ratelimit-reset-tokens": "0s", - "x-request-id": "req_d320b09ebbc6405cab6edb5dc6a5e0c1" - }, - "status": 200, - "statusText": "OK" - } - }, - { - "callIndex": 5, - "id": "6fa54a31e7345249", - "matchKey": "POST api.openai.com/v1/chat/completions", - "recordedAt": "2026-08-24T12:20:34.924Z", - "request": { - "body": { - "kind": "json", - "value": { - "max_tokens": 128, - "messages": [ - { - "content": "What is 127 multiplied by 49? Use the calculate tool.", - "role": "user" - }, - { - "content": "", - "role": "assistant", - "tool_calls": [ - { - "function": { - "arguments": "{\"operation\":\"multiply\",\"a\":127,\"b\":49}", - "name": "calculate" - }, - "id": "call_BMkQ0blzGQeVPEC7pIElPdRs", - "type": "function" - } - ] - }, - { - "content": "6223", - "role": "tool", - "tool_call_id": "call_BMkQ0blzGQeVPEC7pIElPdRs" - } - ], - "model": "gpt-4o-mini-2024-07-18", - "stream": false, - "temperature": 0, - "tools": [ - { - "function": { - "description": "Perform a mathematical calculation", - "name": "calculate", - "parameters": { - "$schema": "http://json-schema.org/draft-07/schema#", - "additionalProperties": false, - "properties": { - "a": { - "type": "number" - }, - "b": { - "type": "number" - }, - "operation": { - "enum": ["add", "subtract", "multiply", "divide"], - "type": "string" - } - }, - "required": ["operation", "a", "b"], - "type": "object" - } - }, - "type": "function" - } - ] - } - }, - "headers": {}, - "method": "POST", - "url": "https://api.openai.com/v1/chat/completions" - }, - "response": { - "body": { - "kind": "json", - "value": { - "choices": [ - { - "finish_reason": "stop", - "index": 0, - "logprobs": null, - "message": { - "annotations": [], - "content": "127 multiplied by 49 is 6223.", - "refusal": null, - "role": "assistant" - } - } - ], - "created": 1787574034, - "id": "chatcmpl-EGNxCT0hwMHK5XDv3ViqjOdQ3sShs", - "model": "gpt-4o-mini-2024-07-18", - "object": "chat.completion", - "service_tier": "default", - "system_fingerprint": "fp_0950b7f384", - "usage": { - "completion_tokens": 11, - "completion_tokens_details": { - "accepted_prediction_tokens": 0, - "audio_tokens": 0, - "reasoning_tokens": 0, - "rejected_prediction_tokens": 0 - }, - "prompt_tokens": 106, - "prompt_tokens_details": { - "audio_tokens": 0, - "cached_tokens": 0 - }, - "total_tokens": 117 - } - } - }, - "headers": { - "access-control-expose-headers": "X-Request-ID, CF-Ray, CF-Ray", - "alt-svc": "h3=\":443\"; ma=86400", - "cf-cache-status": "DYNAMIC", - "cf-ray": "a3024fd2e94a2fd0-IAD", - "connection": "keep-alive", - "content-encoding": "gzip", - "content-type": "application/json", - "date": "Mon, 24 Aug 2026 12:20:34 GMT", - "openai-organization": "braintrust-data", - "openai-processing-ms": "444", - "openai-project": "proj_vsCSXafhhByzWOThMrJcZiw9", - "openai-version": "2020-10-01", - "server": "cloudflare", - "set-cookie": "[REDACTED]", - "strict-transport-security": "max-age=31536000; includeSubDomains; preload", - "transfer-encoding": "chunked", - "x-content-type-options": "nosniff", - "x-openai-proxy-wasm": "v0.1", - "x-ratelimit-limit-requests": "30000", - "x-ratelimit-limit-tokens": "150000000", - "x-ratelimit-remaining-requests": "29999", - "x-ratelimit-remaining-tokens": "149999980", - "x-ratelimit-reset-requests": "2ms", - "x-ratelimit-reset-tokens": "0s", - "x-request-id": "req_149ef64cc6b543eaa8b7c0f602c66bea" - }, - "status": 200, - "statusText": "OK" - } - } - ], - "meta": { - "createdAt": "2026-07-06T14:01:28.480Z" - } -} diff --git a/e2e/scenarios/wrap-langchain-js-traces/__cassettes__/wrap-langchain-js-traces-v1.cassette.json b/e2e/scenarios/wrap-langchain-js-traces/__cassettes__/wrap-langchain-js-traces-v1.cassette.json deleted file mode 100644 index 604a9bcdd..000000000 --- a/e2e/scenarios/wrap-langchain-js-traces/__cassettes__/wrap-langchain-js-traces-v1.cassette.json +++ /dev/null @@ -1,673 +0,0 @@ -{ - "entries": [ - { - "callIndex": 0, - "id": "86e93f0671afd2fb", - "matchKey": "POST api.openai.com/v1/chat/completions", - "recordedAt": "2026-08-24T12:20:22.627Z", - "request": { - "body": { - "kind": "json", - "value": { - "max_completion_tokens": 512, - "messages": [ - { - "content": "Reply with exactly OK.", - "role": "user" - } - ], - "model": "gpt-5-nano", - "stream": false - } - }, - "headers": {}, - "method": "POST", - "url": "https://api.openai.com/v1/chat/completions" - }, - "response": { - "body": { - "kind": "json", - "value": { - "choices": [ - { - "finish_reason": "stop", - "index": 0, - "message": { - "annotations": [], - "content": "OK", - "refusal": null, - "role": "assistant" - } - } - ], - "created": 1787574021, - "id": "chatcmpl-EGNwzUCt2YhtMXdmxJ7ea1qvFvjhj", - "model": "gpt-5-nano-2025-08-07", - "object": "chat.completion", - "service_tier": "default", - "system_fingerprint": null, - "usage": { - "completion_tokens": 138, - "completion_tokens_details": { - "accepted_prediction_tokens": 0, - "audio_tokens": 0, - "reasoning_tokens": 128, - "rejected_prediction_tokens": 0 - }, - "prompt_tokens": 11, - "prompt_tokens_details": { - "audio_tokens": 0, - "cached_tokens": 0 - }, - "total_tokens": 149 - } - } - }, - "headers": { - "access-control-expose-headers": "X-Request-ID, CF-Ray, CF-Ray", - "alt-svc": "h3=\":443\"; ma=86400", - "cf-cache-status": "DYNAMIC", - "cf-ray": "a3024f76c8462fd0-IAD", - "connection": "keep-alive", - "content-encoding": "gzip", - "content-type": "application/json", - "date": "Mon, 24 Aug 2026 12:20:22 GMT", - "openai-organization": "braintrust-data", - "openai-processing-ms": "1516", - "openai-project": "proj_vsCSXafhhByzWOThMrJcZiw9", - "openai-version": "2020-10-01", - "server": "cloudflare", - "set-cookie": "[REDACTED]", - "strict-transport-security": "max-age=31536000; includeSubDomains; preload", - "transfer-encoding": "chunked", - "x-content-type-options": "nosniff", - "x-openai-proxy-wasm": "v0.1", - "x-ratelimit-limit-requests": "30000", - "x-ratelimit-limit-tokens": "180000000", - "x-ratelimit-remaining-requests": "29999", - "x-ratelimit-remaining-tokens": "179999994", - "x-ratelimit-reset-requests": "2ms", - "x-ratelimit-reset-tokens": "0s", - "x-request-id": "req_35266a070f524ced863bd688a979fb9c" - }, - "status": 200, - "statusText": "OK" - } - }, - { - "callIndex": 1, - "id": "a613a13df4910706", - "matchKey": "POST api.openai.com/v1/chat/completions", - "recordedAt": "2026-08-24T12:20:23.323Z", - "request": { - "body": { - "kind": "json", - "value": { - "max_tokens": 32, - "messages": [ - { - "content": "Reply with the single word PARIS and nothing else.", - "role": "user" - } - ], - "model": "gpt-4o-mini-2024-07-18", - "stream": false, - "temperature": 0 - } - }, - "headers": {}, - "method": "POST", - "url": "https://api.openai.com/v1/chat/completions" - }, - "response": { - "body": { - "kind": "json", - "value": { - "choices": [ - { - "finish_reason": "stop", - "index": 0, - "logprobs": null, - "message": { - "annotations": [], - "content": "PARIS", - "refusal": null, - "role": "assistant" - } - } - ], - "created": 1787574023, - "id": "chatcmpl-EGNx181W8ybisMsX1olUNegho55Z4", - "model": "gpt-4o-mini-2024-07-18", - "object": "chat.completion", - "service_tier": "default", - "system_fingerprint": "fp_fabbcdf5d9", - "usage": { - "completion_tokens": 2, - "completion_tokens_details": { - "accepted_prediction_tokens": 0, - "audio_tokens": 0, - "reasoning_tokens": 0, - "rejected_prediction_tokens": 0 - }, - "prompt_tokens": 18, - "prompt_tokens_details": { - "audio_tokens": 0, - "cached_tokens": 0 - }, - "total_tokens": 20 - } - } - }, - "headers": { - "access-control-expose-headers": "X-Request-ID, CF-Ray, CF-Ray", - "alt-svc": "h3=\":443\"; ma=86400", - "cf-cache-status": "DYNAMIC", - "cf-ray": "a3024f894d132fd0-IAD", - "connection": "keep-alive", - "content-encoding": "gzip", - "content-type": "application/json", - "date": "Mon, 24 Aug 2026 12:20:23 GMT", - "openai-organization": "braintrust-data", - "openai-processing-ms": "273", - "openai-project": "proj_vsCSXafhhByzWOThMrJcZiw9", - "openai-version": "2020-10-01", - "server": "cloudflare", - "set-cookie": "[REDACTED]", - "strict-transport-security": "max-age=31536000; includeSubDomains; preload", - "transfer-encoding": "chunked", - "x-content-type-options": "nosniff", - "x-openai-proxy-wasm": "v0.1", - "x-ratelimit-limit-requests": "30000", - "x-ratelimit-limit-tokens": "150000000", - "x-ratelimit-remaining-requests": "29999", - "x-ratelimit-remaining-tokens": "149999985", - "x-ratelimit-reset-requests": "2ms", - "x-ratelimit-reset-tokens": "0s", - "x-request-id": "req_06fe5d162d7f463bb59e50d729b4d20c" - }, - "status": 200, - "statusText": "OK" - } - }, - { - "callIndex": 2, - "id": "394c0994869e6e1f", - "matchKey": "POST api.openai.com/v1/chat/completions", - "recordedAt": "2026-08-24T12:20:24.024Z", - "request": { - "body": { - "kind": "json", - "value": { - "max_tokens": 32, - "messages": [ - { - "content": "Count from 1 to 3 and include the words one two three.", - "role": "user" - } - ], - "model": "gpt-4o-mini-2024-07-18", - "stream": true, - "stream_options": { - "include_usage": true - }, - "temperature": 0 - } - }, - "headers": {}, - "method": "POST", - "url": "https://api.openai.com/v1/chat/completions" - }, - "response": { - "body": { - "chunks": [ - "data: {\"id\":\"chatcmpl-EGNx1I8x3YZKvOGhuC0S5biZ26Gwl\",\"object\":\"chat.completion.chunk\",\"created\":1787574023,\"model\":\"gpt-4o-mini-2024-07-18\",\"service_tier\":\"default\",\"system_fingerprint\":\"fp_0950b7f384\",\"choices\":[{\"index\":0,\"delta\":{\"role\":\"assistant\",\"content\":\"\",\"refusal\":null},\"logprobs\":null,\"finish_reason\":null}],\"usage\":null,\"obfuscation\":\"Oj4Hh3j31\"}", - "data: {\"id\":\"chatcmpl-EGNx1I8x3YZKvOGhuC0S5biZ26Gwl\",\"object\":\"chat.completion.chunk\",\"created\":1787574023,\"model\":\"gpt-4o-mini-2024-07-18\",\"service_tier\":\"default\",\"system_fingerprint\":\"fp_0950b7f384\",\"choices\":[{\"index\":0,\"delta\":{\"content\":\"One\"},\"logprobs\":null,\"finish_reason\":null}],\"usage\":null,\"obfuscation\":\"Il4NOQeI\"}", - "data: {\"id\":\"chatcmpl-EGNx1I8x3YZKvOGhuC0S5biZ26Gwl\",\"object\":\"chat.completion.chunk\",\"created\":1787574023,\"model\":\"gpt-4o-mini-2024-07-18\",\"service_tier\":\"default\",\"system_fingerprint\":\"fp_0950b7f384\",\"choices\":[{\"index\":0,\"delta\":{\"content\":\",\"},\"logprobs\":null,\"finish_reason\":null}],\"usage\":null,\"obfuscation\":\"rVAPk2Cs7X\"}", - "data: {\"id\":\"chatcmpl-EGNx1I8x3YZKvOGhuC0S5biZ26Gwl\",\"object\":\"chat.completion.chunk\",\"created\":1787574023,\"model\":\"gpt-4o-mini-2024-07-18\",\"service_tier\":\"default\",\"system_fingerprint\":\"fp_0950b7f384\",\"choices\":[{\"index\":0,\"delta\":{\"content\":\" two\"},\"logprobs\":null,\"finish_reason\":null}],\"usage\":null,\"obfuscation\":\"sFT4iNt\"}", - "data: {\"id\":\"chatcmpl-EGNx1I8x3YZKvOGhuC0S5biZ26Gwl\",\"object\":\"chat.completion.chunk\",\"created\":1787574023,\"model\":\"gpt-4o-mini-2024-07-18\",\"service_tier\":\"default\",\"system_fingerprint\":\"fp_0950b7f384\",\"choices\":[{\"index\":0,\"delta\":{\"content\":\",\"},\"logprobs\":null,\"finish_reason\":null}],\"usage\":null,\"obfuscation\":\"tSa35AR216\"}", - "data: {\"id\":\"chatcmpl-EGNx1I8x3YZKvOGhuC0S5biZ26Gwl\",\"object\":\"chat.completion.chunk\",\"created\":1787574023,\"model\":\"gpt-4o-mini-2024-07-18\",\"service_tier\":\"default\",\"system_fingerprint\":\"fp_0950b7f384\",\"choices\":[{\"index\":0,\"delta\":{\"content\":\" three\"},\"logprobs\":null,\"finish_reason\":null}],\"usage\":null,\"obfuscation\":\"oXtaZ\"}", - "data: {\"id\":\"chatcmpl-EGNx1I8x3YZKvOGhuC0S5biZ26Gwl\",\"object\":\"chat.completion.chunk\",\"created\":1787574023,\"model\":\"gpt-4o-mini-2024-07-18\",\"service_tier\":\"default\",\"system_fingerprint\":\"fp_0950b7f384\",\"choices\":[{\"index\":0,\"delta\":{\"content\":\".\"},\"logprobs\":null,\"finish_reason\":null}],\"usage\":null,\"obfuscation\":\"pfwlwCc1UG\"}", - "data: {\"id\":\"chatcmpl-EGNx1I8x3YZKvOGhuC0S5biZ26Gwl\",\"object\":\"chat.completion.chunk\",\"created\":1787574023,\"model\":\"gpt-4o-mini-2024-07-18\",\"service_tier\":\"default\",\"system_fingerprint\":\"fp_0950b7f384\",\"choices\":[{\"index\":0,\"delta\":{},\"logprobs\":null,\"finish_reason\":\"stop\"}],\"usage\":null,\"obfuscation\":\"0pszW\"}", - "data: {\"id\":\"chatcmpl-EGNx1I8x3YZKvOGhuC0S5biZ26Gwl\",\"object\":\"chat.completion.chunk\",\"created\":1787574023,\"model\":\"gpt-4o-mini-2024-07-18\",\"service_tier\":\"default\",\"system_fingerprint\":\"fp_0950b7f384\",\"choices\":[],\"usage\":{\"prompt_tokens\":22,\"completion_tokens\":6,\"total_tokens\":28,\"prompt_tokens_details\":{\"cached_tokens\":0,\"audio_tokens\":0},\"completion_tokens_details\":{\"reasoning_tokens\":0,\"audio_tokens\":0,\"accepted_prediction_tokens\":0,\"rejected_prediction_tokens\":0}},\"obfuscation\":\"kalzk5DmKv4\"}", - "data: [DONE]" - ], - "kind": "sse" - }, - "headers": { - "access-control-expose-headers": "X-Request-ID, CF-Ray, CF-Ray", - "alt-svc": "h3=\":443\"; ma=86400", - "cf-cache-status": "DYNAMIC", - "cf-ray": "a3024f8d9d6b2fd0-IAD", - "connection": "keep-alive", - "content-type": "text/event-stream; charset=utf-8", - "date": "Mon, 24 Aug 2026 12:20:23 GMT", - "openai-organization": "braintrust-data", - "openai-processing-ms": "396", - "openai-project": "proj_vsCSXafhhByzWOThMrJcZiw9", - "openai-version": "2020-10-01", - "server": "cloudflare", - "set-cookie": "[REDACTED]", - "strict-transport-security": "max-age=31536000; includeSubDomains; preload", - "transfer-encoding": "chunked", - "x-content-type-options": "nosniff", - "x-openai-proxy-wasm": "v0.1", - "x-ratelimit-limit-requests": "30000", - "x-ratelimit-limit-tokens": "150000000", - "x-ratelimit-remaining-requests": "29999", - "x-ratelimit-remaining-tokens": "149999985", - "x-ratelimit-reset-requests": "2ms", - "x-ratelimit-reset-tokens": "0s", - "x-request-id": "req_a0055ad7660e45bb855ae0977d3a9b46" - }, - "status": 200, - "statusText": "OK" - } - }, - { - "callIndex": 3, - "id": "7f1e5cd685579c33", - "matchKey": "POST api.openai.com/v1/chat/completions", - "recordedAt": "2026-08-24T12:20:24.773Z", - "request": { - "body": { - "kind": "json", - "value": { - "max_tokens": 128, - "messages": [ - { - "content": "Use the get_weather tool for Paris, France. Do not answer from memory.", - "role": "user" - } - ], - "model": "gpt-4o-mini-2024-07-18", - "stream": false, - "temperature": 0, - "tools": [ - { - "function": { - "description": "Get the current weather in a given location", - "name": "get_weather", - "parameters": { - "$schema": "http://json-schema.org/draft-07/schema#", - "additionalProperties": false, - "properties": { - "location": { - "description": "The city and state or city and country", - "type": "string" - } - }, - "required": ["location"], - "type": "object" - } - }, - "type": "function" - } - ] - } - }, - "headers": {}, - "method": "POST", - "url": "https://api.openai.com/v1/chat/completions" - }, - "response": { - "body": { - "kind": "json", - "value": { - "choices": [ - { - "finish_reason": "tool_calls", - "index": 0, - "logprobs": null, - "message": { - "annotations": [], - "content": null, - "refusal": null, - "role": "assistant", - "tool_calls": [ - { - "function": { - "arguments": "{\"location\":\"Paris, France\"}", - "name": "get_weather" - }, - "id": "call_vFUhlOlK88FODFbm0RCSfovD", - "type": "function" - } - ] - } - } - ], - "created": 1787574024, - "id": "chatcmpl-EGNx23GX8lOwzBQnZTCKTXwLeUyRG", - "model": "gpt-4o-mini-2024-07-18", - "object": "chat.completion", - "service_tier": "default", - "system_fingerprint": "fp_02ae59e84c", - "usage": { - "completion_tokens": 16, - "completion_tokens_details": { - "accepted_prediction_tokens": 0, - "audio_tokens": 0, - "reasoning_tokens": 0, - "rejected_prediction_tokens": 0 - }, - "prompt_tokens": 72, - "prompt_tokens_details": { - "audio_tokens": 0, - "cached_tokens": 0 - }, - "total_tokens": 88 - } - } - }, - "headers": { - "access-control-expose-headers": "X-Request-ID, CF-Ray, CF-Ray", - "alt-svc": "h3=\":443\"; ma=86400", - "cf-cache-status": "DYNAMIC", - "cf-ray": "a3024f91fd8b2fd0-IAD", - "connection": "keep-alive", - "content-encoding": "gzip", - "content-type": "application/json", - "date": "Mon, 24 Aug 2026 12:20:24 GMT", - "openai-organization": "braintrust-data", - "openai-processing-ms": "649", - "openai-project": "proj_vsCSXafhhByzWOThMrJcZiw9", - "openai-version": "2020-10-01", - "server": "cloudflare", - "set-cookie": "[REDACTED]", - "strict-transport-security": "max-age=31536000; includeSubDomains; preload", - "transfer-encoding": "chunked", - "x-content-type-options": "nosniff", - "x-openai-proxy-wasm": "v0.1", - "x-ratelimit-limit-requests": "30000", - "x-ratelimit-limit-tokens": "150000000", - "x-ratelimit-remaining-requests": "29999", - "x-ratelimit-remaining-tokens": "149999977", - "x-ratelimit-reset-requests": "2ms", - "x-ratelimit-reset-tokens": "0s", - "x-request-id": "req_27d9929b8cfa4c40b969771b87ffd0bf" - }, - "status": 200, - "statusText": "OK" - } - }, - { - "callIndex": 4, - "id": "92b304bbb4ce5170", - "matchKey": "POST api.openai.com/v1/chat/completions", - "recordedAt": "2026-08-24T12:20:27.515Z", - "request": { - "body": { - "kind": "json", - "value": { - "max_tokens": 128, - "messages": [ - { - "content": "What is 127 multiplied by 49? Use the calculate tool.", - "role": "user" - } - ], - "model": "gpt-4o-mini-2024-07-18", - "stream": false, - "temperature": 0, - "tools": [ - { - "function": { - "description": "Perform a mathematical calculation", - "name": "calculate", - "parameters": { - "$schema": "http://json-schema.org/draft-07/schema#", - "additionalProperties": false, - "properties": { - "a": { - "type": "number" - }, - "b": { - "type": "number" - }, - "operation": { - "enum": ["add", "subtract", "multiply", "divide"], - "type": "string" - } - }, - "required": ["operation", "a", "b"], - "type": "object" - } - }, - "type": "function" - } - ] - } - }, - "headers": {}, - "method": "POST", - "url": "https://api.openai.com/v1/chat/completions" - }, - "response": { - "body": { - "kind": "json", - "value": { - "choices": [ - { - "finish_reason": "tool_calls", - "index": 0, - "logprobs": null, - "message": { - "annotations": [], - "content": null, - "refusal": null, - "role": "assistant", - "tool_calls": [ - { - "function": { - "arguments": "{\"operation\":\"multiply\",\"a\":127,\"b\":49}", - "name": "calculate" - }, - "id": "call_dXQI5d9UAYuRygDqNMunj2W2", - "type": "function" - } - ] - } - } - ], - "created": 1787574027, - "id": "chatcmpl-EGNx52wzdb9GL8hdL3YZCMSLvJkQ4", - "model": "gpt-4o-mini-2024-07-18", - "object": "chat.completion", - "service_tier": "default", - "system_fingerprint": "fp_0950b7f384", - "usage": { - "completion_tokens": 21, - "completion_tokens_details": { - "accepted_prediction_tokens": 0, - "audio_tokens": 0, - "reasoning_tokens": 0, - "rejected_prediction_tokens": 0 - }, - "prompt_tokens": 76, - "prompt_tokens_details": { - "audio_tokens": 0, - "cached_tokens": 0 - }, - "total_tokens": 97 - } - } - }, - "headers": { - "access-control-expose-headers": "X-Request-ID, CF-Ray, CF-Ray", - "alt-svc": "h3=\":443\"; ma=86400", - "cf-cache-status": "DYNAMIC", - "cf-ray": "a3024f969f342fd0-IAD", - "connection": "keep-alive", - "content-encoding": "gzip", - "content-type": "application/json", - "date": "Mon, 24 Aug 2026 12:20:27 GMT", - "openai-organization": "braintrust-data", - "openai-processing-ms": "729", - "openai-project": "proj_vsCSXafhhByzWOThMrJcZiw9", - "openai-version": "2020-10-01", - "server": "cloudflare", - "set-cookie": "[REDACTED]", - "strict-transport-security": "max-age=31536000; includeSubDomains; preload", - "transfer-encoding": "chunked", - "x-content-type-options": "nosniff", - "x-openai-proxy-wasm": "v0.1", - "x-ratelimit-limit-requests": "30000", - "x-ratelimit-limit-tokens": "150000000", - "x-ratelimit-remaining-requests": "29999", - "x-ratelimit-remaining-tokens": "149999985", - "x-ratelimit-reset-requests": "2ms", - "x-ratelimit-reset-tokens": "0s", - "x-request-id": "req_86b70fed7a674d028ec1293c0e2cd0ec" - }, - "status": 200, - "statusText": "OK" - } - }, - { - "callIndex": 5, - "id": "a57d0dfe4d90c4bd", - "matchKey": "POST api.openai.com/v1/chat/completions", - "recordedAt": "2026-08-24T12:20:28.216Z", - "request": { - "body": { - "kind": "json", - "value": { - "max_tokens": 128, - "messages": [ - { - "content": "What is 127 multiplied by 49? Use the calculate tool.", - "role": "user" - }, - { - "content": "", - "role": "assistant", - "tool_calls": [ - { - "function": { - "arguments": "{\"operation\":\"multiply\",\"a\":127,\"b\":49}", - "name": "calculate" - }, - "id": "call_dXQI5d9UAYuRygDqNMunj2W2", - "type": "function" - } - ] - }, - { - "content": "6223", - "role": "tool", - "tool_call_id": "call_dXQI5d9UAYuRygDqNMunj2W2" - } - ], - "model": "gpt-4o-mini-2024-07-18", - "stream": false, - "temperature": 0, - "tools": [ - { - "function": { - "description": "Perform a mathematical calculation", - "name": "calculate", - "parameters": { - "$schema": "http://json-schema.org/draft-07/schema#", - "additionalProperties": false, - "properties": { - "a": { - "type": "number" - }, - "b": { - "type": "number" - }, - "operation": { - "enum": ["add", "subtract", "multiply", "divide"], - "type": "string" - } - }, - "required": ["operation", "a", "b"], - "type": "object" - } - }, - "type": "function" - } - ] - } - }, - "headers": {}, - "method": "POST", - "url": "https://api.openai.com/v1/chat/completions" - }, - "response": { - "body": { - "kind": "json", - "value": { - "choices": [ - { - "finish_reason": "stop", - "index": 0, - "logprobs": null, - "message": { - "annotations": [], - "content": "127 multiplied by 49 is 6223.", - "refusal": null, - "role": "assistant" - } - } - ], - "created": 1787574027, - "id": "chatcmpl-EGNx5rDFm8HaHv7GCMw3OzQyu24L9", - "model": "gpt-4o-mini-2024-07-18", - "object": "chat.completion", - "service_tier": "default", - "system_fingerprint": "fp_0950b7f384", - "usage": { - "completion_tokens": 11, - "completion_tokens_details": { - "accepted_prediction_tokens": 0, - "audio_tokens": 0, - "reasoning_tokens": 0, - "rejected_prediction_tokens": 0 - }, - "prompt_tokens": 106, - "prompt_tokens_details": { - "audio_tokens": 0, - "cached_tokens": 0 - }, - "total_tokens": 117 - } - } - }, - "headers": { - "access-control-expose-headers": "X-Request-ID, CF-Ray, CF-Ray", - "alt-svc": "h3=\":443\"; ma=86400", - "cf-cache-status": "DYNAMIC", - "cf-ray": "a3024fa7be702fd0-IAD", - "connection": "keep-alive", - "content-encoding": "gzip", - "content-type": "application/json", - "date": "Mon, 24 Aug 2026 12:20:28 GMT", - "openai-organization": "braintrust-data", - "openai-processing-ms": "592", - "openai-project": "proj_vsCSXafhhByzWOThMrJcZiw9", - "openai-version": "2020-10-01", - "server": "cloudflare", - "set-cookie": "[REDACTED]", - "strict-transport-security": "max-age=31536000; includeSubDomains; preload", - "transfer-encoding": "chunked", - "x-content-type-options": "nosniff", - "x-openai-proxy-wasm": "v0.1", - "x-ratelimit-limit-requests": "30000", - "x-ratelimit-limit-tokens": "150000000", - "x-ratelimit-remaining-requests": "29999", - "x-ratelimit-remaining-tokens": "149999980", - "x-ratelimit-reset-requests": "2ms", - "x-ratelimit-reset-tokens": "0s", - "x-request-id": "req_80ec4ce95cf14b989fddb3c82140dab8" - }, - "status": 200, - "statusText": "OK" - } - } - ], - "meta": { - "createdAt": "2026-07-06T14:01:28.206Z" - } -} diff --git a/e2e/scenarios/wrap-langchain-js-traces/__snapshots__/wrap-langchain-js-traces-v1-latest.span-tree.json b/e2e/scenarios/wrap-langchain-js-traces/__snapshots__/wrap-langchain-js-traces-v1-latest.span-tree.json deleted file mode 100644 index 9d47ae91c..000000000 --- a/e2e/scenarios/wrap-langchain-js-traces/__snapshots__/wrap-langchain-js-traces-v1-latest.span-tree.json +++ /dev/null @@ -1,1511 +0,0 @@ -{ - "span_tree": [ - { - "name": "langchain-wrapper-root", - "type": "task", - "children": [ - { - "name": "langchain-invoke-operation", - "children": [ - { - "name": "ChatOpenAI", - "type": "llm", - "children": [], - "input": [ - [ - { - "id": [ - "langchain_core", - "messages", - "HumanMessage" - ], - "kwargs": { - "additional_kwargs": {}, - "content": "Reply with exactly OK.", - "response_metadata": {} - }, - "lc": 1, - "type": "constructor" - } - ] - ], - "output": { - "generations": [ - [ - { - "generationInfo": { - "finish_reason": "stop" - }, - "message": { - "id": [ - "langchain_core", - "messages", - "AIMessage" - ], - "kwargs": { - "additional_kwargs": {}, - "content": "OK", - "id": "", - "invalid_tool_calls": [], - "response_metadata": { - "finish_reason": "stop", - "model_name": "gpt-5-nano-2025-08-07", - "model_provider": "openai", - "tokenUsage": { - "completionTokens": 266, - "promptTokens": 11, - "totalTokens": 277 - } - }, - "tool_calls": [], - "type": "ai", - "usage_metadata": { - "input_token_details": { - "audio": 0, - "cache_read": 0 - }, - "input_tokens": 11, - "output_token_details": { - "audio": 0, - "reasoning": 256 - }, - "output_tokens": 266, - "total_tokens": 277 - } - }, - "lc": 1, - "type": "constructor" - }, - "text": "OK" - } - ] - ], - "llmOutput": { - "tokenUsage": { - "completionTokens": 266, - "promptTokens": 11, - "totalTokens": 277 - } - } - }, - "metadata": { - "batch_size": 1, - "braintrust": { - "integration_name": "langchain-js", - "sdk_language": "javascript" - }, - "invocation_params": { - "max_completion_tokens": 512, - "model": "gpt-5-nano", - "stream": false - }, - "metadata": { - "ls_integration": "langchain_chat_model", - "ls_model_name": "gpt-5-nano", - "ls_model_type": "chat", - "ls_provider": "openai", - "versions": { - "@langchain/core": "1.2.1", - "@langchain/openai": "1.5.3" - } - }, - "model": "gpt-5-nano-2025-08-07", - "options": {}, - "run_id": "", - "serialized": { - "id": [ - "langchain", - "chat_models", - "openai", - "ChatOpenAI" - ], - "kwargs": { - "max_tokens": 512, - "model": "gpt-5-nano", - "openai_api_key": { - "id": [ - "OPENAI_API_KEY" - ], - "lc": 1, - "type": "secret" - } - }, - "lc": 1, - "type": "constructor" - }, - "tags": [] - }, - "metrics": { - "completion_reasoning_tokens": 256, - "completion_tokens": 266, - "prompt_cached_tokens": 0, - "prompt_tokens": 11, - "tokens": 277 - } - } - ], - "metadata": { - "operation": "invoke", - "testRunId": "" - } - }, - { - "name": "langchain-chain-operation", - "children": [ - { - "name": "RunnableSequence", - "type": "task", - "children": [ - { - "name": "ChatPromptTemplate", - "type": "task", - "children": [], - "input": { - "word": "PARIS" - }, - "output": { - "id": [ - "langchain_core", - "prompt_values", - "ChatPromptValue" - ], - "kwargs": { - "messages": [ - { - "id": [ - "langchain_core", - "messages", - "HumanMessage" - ], - "kwargs": { - "additional_kwargs": {}, - "content": "Reply with the single word PARIS and nothing else.", - "response_metadata": {} - }, - "lc": 1, - "type": "constructor" - } - ] - }, - "lc": 1, - "type": "constructor" - }, - "metadata": { - "braintrust": { - "integration_name": "langchain-js", - "sdk_language": "javascript" - }, - "metadata": {}, - "name": "ChatPromptTemplate", - "parent_run_id": "", - "run_id": "", - "run_type": "prompt", - "serialized": { - "id": [ - "langchain_core", - "prompts", - "chat", - "ChatPromptTemplate" - ], - "kwargs": { - "input_variables": [ - "word" - ], - "messages": [ - { - "id": [ - "langchain_core", - "prompts", - "chat", - "HumanMessagePromptTemplate" - ], - "kwargs": { - "prompt": { - "id": [ - "langchain_core", - "prompts", - "prompt", - "PromptTemplate" - ], - "kwargs": { - "input_variables": [ - "word" - ], - "template": "Reply with the single word {word} and nothing else.", - "template_format": "f-string" - }, - "lc": 1, - "type": "constructor" - } - }, - "lc": 1, - "type": "constructor" - } - ] - }, - "lc": 1, - "type": "constructor" - }, - "tags": [ - "seq:step:1" - ] - } - }, - { - "name": "ChatOpenAI", - "type": "llm", - "children": [], - "input": [ - [ - { - "id": [ - "langchain_core", - "messages", - "HumanMessage" - ], - "kwargs": { - "additional_kwargs": {}, - "content": "Reply with the single word PARIS and nothing else.", - "response_metadata": {} - }, - "lc": 1, - "type": "constructor" - } - ] - ], - "output": { - "generations": [ - [ - { - "generationInfo": { - "finish_reason": "stop" - }, - "message": { - "id": [ - "langchain_core", - "messages", - "AIMessage" - ], - "kwargs": { - "additional_kwargs": {}, - "content": "PARIS", - "id": "", - "invalid_tool_calls": [], - "response_metadata": { - "finish_reason": "stop", - "model_name": "gpt-4o-mini-2024-07-18", - "model_provider": "openai", - "system_fingerprint": "", - "tokenUsage": { - "completionTokens": 2, - "promptTokens": 18, - "totalTokens": 20 - }, - "usage": { - "completion_tokens": 2, - "completion_tokens_details": { - "accepted_prediction_tokens": 0, - "audio_tokens": 0, - "reasoning_tokens": 0, - "rejected_prediction_tokens": 0 - }, - "prompt_tokens": 18, - "prompt_tokens_details": { - "audio_tokens": 0, - "cached_tokens": 0 - }, - "total_tokens": 20 - } - }, - "tool_calls": [], - "type": "ai", - "usage_metadata": { - "input_token_details": { - "audio": 0, - "cache_read": 0 - }, - "input_tokens": 18, - "output_token_details": { - "audio": 0, - "reasoning": 0 - }, - "output_tokens": 2, - "total_tokens": 20 - } - }, - "lc": 1, - "type": "constructor" - }, - "text": "PARIS" - } - ] - ], - "llmOutput": { - "tokenUsage": { - "completionTokens": 2, - "promptTokens": 18, - "totalTokens": 20 - } - } - }, - "metadata": { - "batch_size": 1, - "braintrust": { - "integration_name": "langchain-js", - "sdk_language": "javascript" - }, - "invocation_params": { - "max_tokens": 32, - "model": "gpt-4o-mini-2024-07-18", - "stream": false, - "temperature": 0 - }, - "metadata": { - "ls_integration": "langchain_chat_model", - "ls_max_tokens": 32, - "ls_model_name": "gpt-4o-mini-2024-07-18", - "ls_model_type": "chat", - "ls_provider": "openai", - "ls_temperature": 0, - "versions": { - "@langchain/core": "1.2.1", - "@langchain/openai": "1.5.3" - } - }, - "model": "gpt-4o-mini-2024-07-18", - "options": {}, - "parent_run_id": "", - "run_id": "", - "serialized": { - "id": [ - "langchain", - "chat_models", - "openai", - "ChatOpenAI" - ], - "kwargs": { - "max_tokens": 32, - "model": "gpt-4o-mini-2024-07-18", - "openai_api_key": { - "id": [ - "OPENAI_API_KEY" - ], - "lc": 1, - "type": "secret" - }, - "temperature": 0 - }, - "lc": 1, - "type": "constructor" - }, - "tags": [ - "seq:step:2" - ] - }, - "metrics": { - "completion_reasoning_tokens": 0, - "completion_tokens": 2, - "prompt_cached_tokens": 0, - "prompt_tokens": 18, - "tokens": 20 - } - } - ], - "input": { - "word": "PARIS" - }, - "output": { - "id": [ - "langchain_core", - "messages", - "AIMessage" - ], - "kwargs": { - "additional_kwargs": {}, - "content": "PARIS", - "id": "", - "invalid_tool_calls": [], - "response_metadata": { - "finish_reason": "stop", - "model_name": "gpt-4o-mini-2024-07-18", - "model_provider": "openai", - "system_fingerprint": "", - "tokenUsage": { - "completionTokens": 2, - "promptTokens": 18, - "totalTokens": 20 - }, - "usage": { - "completion_tokens": 2, - "completion_tokens_details": { - "accepted_prediction_tokens": 0, - "audio_tokens": 0, - "reasoning_tokens": 0, - "rejected_prediction_tokens": 0 - }, - "prompt_tokens": 18, - "prompt_tokens_details": { - "audio_tokens": 0, - "cached_tokens": 0 - }, - "total_tokens": 20 - } - }, - "tool_calls": [], - "type": "ai", - "usage_metadata": { - "input_token_details": { - "audio": 0, - "cache_read": 0 - }, - "input_tokens": 18, - "output_token_details": { - "audio": 0, - "reasoning": 0 - }, - "output_tokens": 2, - "total_tokens": 20 - } - }, - "lc": 1, - "type": "constructor" - }, - "metadata": { - "braintrust": { - "integration_name": "langchain-js", - "sdk_language": "javascript" - }, - "metadata": {}, - "run_id": "", - "serialized": { - "id": [ - "langchain_core", - "runnables", - "RunnableSequence" - ], - "kwargs": { - "first": { - "id": [ - "langchain_core", - "prompts", - "chat", - "ChatPromptTemplate" - ], - "kwargs": { - "input_variables": [ - "word" - ], - "messages": [ - { - "id": [ - "langchain_core", - "prompts", - "chat", - "HumanMessagePromptTemplate" - ], - "kwargs": { - "prompt": { - "id": [ - "langchain_core", - "prompts", - "prompt", - "PromptTemplate" - ], - "kwargs": { - "input_variables": [ - "word" - ], - "template": "Reply with the single word {word} and nothing else.", - "template_format": "f-string" - }, - "lc": 1, - "type": "constructor" - } - }, - "lc": 1, - "type": "constructor" - } - ] - }, - "lc": 1, - "type": "constructor" - }, - "last": { - "id": [ - "langchain", - "chat_models", - "openai", - "ChatOpenAI" - ], - "kwargs": { - "max_tokens": 32, - "model": "gpt-4o-mini-2024-07-18", - "openai_api_key": { - "id": [ - "OPENAI_API_KEY" - ], - "lc": 1, - "type": "secret" - }, - "temperature": 0 - }, - "lc": 1, - "type": "constructor" - } - }, - "lc": 1, - "type": "constructor" - }, - "tags": [] - } - } - ], - "metadata": { - "operation": "chain", - "testRunId": "" - } - }, - { - "name": "langchain-stream-operation", - "children": [ - { - "name": "ChatOpenAI", - "type": "llm", - "children": [], - "input": [ - [ - { - "id": [ - "langchain_core", - "messages", - "HumanMessage" - ], - "kwargs": { - "additional_kwargs": {}, - "content": "Count from 1 to 3 and include the words one two three.", - "response_metadata": {} - }, - "lc": 1, - "type": "constructor" - } - ] - ], - "output": { - "generations": [ - [ - { - "generationInfo": { - "completion": 0, - "finish_reason": "stop", - "model_name": "gpt-4o-mini-2024-07-18", - "prompt": 0, - "service_tier": "default", - "system_fingerprint": "" - }, - "message": { - "id": [ - "langchain_core", - "messages", - "AIMessageChunk" - ], - "kwargs": { - "additional_kwargs": {}, - "content": "One, two, three.", - "id": "", - "invalid_tool_calls": [], - "response_metadata": { - "completion": 0, - "finish_reason": "stop", - "model_name": "gpt-4o-mini-2024-07-18", - "model_provider": "openai", - "prompt": 0, - "service_tier": "default", - "system_fingerprint": "", - "usage": { - "completion_tokens": 6, - "completion_tokens_details": { - "accepted_prediction_tokens": 0, - "audio_tokens": 0, - "reasoning_tokens": 0, - "rejected_prediction_tokens": 0 - }, - "prompt_tokens": 22, - "prompt_tokens_details": { - "audio_tokens": 0, - "cached_tokens": 0 - }, - "total_tokens": 28 - } - }, - "tool_call_chunks": [], - "tool_calls": [], - "usage_metadata": { - "input_token_details": { - "audio": 0, - "cache_read": 0 - }, - "input_tokens": 22, - "output_token_details": { - "audio": 0, - "reasoning": 0 - }, - "output_tokens": 6, - "total_tokens": 28 - } - }, - "lc": 1, - "type": "constructor" - }, - "text": "One, two, three." - } - ] - ], - "llmOutput": { - "tokenUsage": { - "completionTokens": 6, - "promptTokens": 22, - "totalTokens": 28 - } - } - }, - "metadata": { - "batch_size": 1, - "braintrust": { - "integration_name": "langchain-js", - "sdk_language": "javascript" - }, - "invocation_params": { - "max_tokens": 32, - "model": "gpt-4o-mini-2024-07-18", - "stream": true, - "stream_options": { - "include_usage": true - }, - "temperature": 0 - }, - "metadata": { - "ls_integration": "langchain_chat_model", - "ls_max_tokens": 32, - "ls_model_name": "gpt-4o-mini-2024-07-18", - "ls_model_type": "chat", - "ls_provider": "openai", - "ls_temperature": 0, - "versions": { - "@langchain/core": "1.2.1", - "@langchain/openai": "1.5.3" - } - }, - "model": "gpt-4o-mini-2024-07-18", - "options": {}, - "run_id": "", - "serialized": { - "id": [ - "langchain", - "chat_models", - "openai", - "ChatOpenAI" - ], - "kwargs": { - "max_tokens": 32, - "model": "gpt-4o-mini-2024-07-18", - "openai_api_key": { - "id": [ - "OPENAI_API_KEY" - ], - "lc": 1, - "type": "secret" - }, - "streaming": true, - "temperature": 0 - }, - "lc": 1, - "type": "constructor" - }, - "tags": [] - }, - "metrics": { - "completion_reasoning_tokens": 0, - "completion_tokens": 6, - "prompt_cached_tokens": 0, - "prompt_tokens": 22, - "time_to_first_token": 0, - "tokens": 28 - } - } - ], - "metadata": { - "operation": "stream", - "testRunId": "" - } - }, - { - "name": "langchain-tool-operation", - "children": [ - { - "name": "ChatOpenAI", - "type": "llm", - "children": [], - "input": [ - [ - { - "id": [ - "langchain_core", - "messages", - "HumanMessage" - ], - "kwargs": { - "additional_kwargs": {}, - "content": "Use the get_weather tool for Paris, France. Do not answer from memory.", - "response_metadata": {} - }, - "lc": 1, - "type": "constructor" - } - ] - ], - "output": { - "generations": [ - [ - { - "generationInfo": { - "finish_reason": "tool_calls" - }, - "message": { - "id": [ - "langchain_core", - "messages", - "AIMessage" - ], - "kwargs": { - "additional_kwargs": { - "tool_calls": [ - { - "function": { - "arguments": "{\"location\":\"Paris, France\"}", - "name": "get_weather" - }, - "id": "", - "type": "function" - } - ] - }, - "content": "", - "id": "", - "invalid_tool_calls": [], - "response_metadata": { - "finish_reason": "tool_calls", - "model_name": "gpt-4o-mini-2024-07-18", - "model_provider": "openai", - "system_fingerprint": "", - "tokenUsage": { - "completionTokens": 16, - "promptTokens": 72, - "totalTokens": 88 - }, - "usage": { - "completion_tokens": 16, - "completion_tokens_details": { - "accepted_prediction_tokens": 0, - "audio_tokens": 0, - "reasoning_tokens": 0, - "rejected_prediction_tokens": 0 - }, - "prompt_tokens": 72, - "prompt_tokens_details": { - "audio_tokens": 0, - "cached_tokens": 0 - }, - "total_tokens": 88 - } - }, - "tool_calls": [ - { - "args": { - "location": "Paris, France" - }, - "id": "", - "name": "get_weather", - "type": "tool_call" - } - ], - "type": "ai", - "usage_metadata": { - "input_token_details": { - "audio": 0, - "cache_read": 0 - }, - "input_tokens": 72, - "output_token_details": { - "audio": 0, - "reasoning": 0 - }, - "output_tokens": 16, - "total_tokens": 88 - } - }, - "lc": 1, - "type": "constructor" - }, - "text": "" - } - ] - ], - "llmOutput": { - "tokenUsage": { - "completionTokens": 16, - "promptTokens": 72, - "totalTokens": 88 - } - } - }, - "metadata": { - "batch_size": 1, - "braintrust": { - "integration_name": "langchain-js", - "sdk_language": "javascript" - }, - "invocation_params": { - "max_tokens": 128, - "model": "gpt-4o-mini-2024-07-18", - "stream": false, - "temperature": 0, - "tools": [ - { - "function": { - "description": "Get the current weather in a given location", - "name": "get_weather", - "parameters": { - "$schema": "http://json-schema.org/draft-07/schema#", - "additionalProperties": false, - "properties": { - "location": { - "description": "The city and state or city and country", - "type": "string" - } - }, - "required": [ - "location" - ], - "type": "object" - } - }, - "type": "function" - } - ] - }, - "metadata": { - "ls_integration": "langchain_chat_model", - "ls_max_tokens": 128, - "ls_model_name": "gpt-4o-mini-2024-07-18", - "ls_model_type": "chat", - "ls_provider": "openai", - "ls_temperature": 0, - "versions": { - "@langchain/core": "1.2.1", - "@langchain/openai": "1.5.3" - } - }, - "model": "gpt-4o-mini-2024-07-18", - "options": { - "tools": [ - { - "function": { - "description": "Get the current weather in a given location", - "name": "get_weather", - "parameters": { - "$schema": "http://json-schema.org/draft-07/schema#", - "additionalProperties": false, - "properties": { - "location": { - "description": "The city and state or city and country", - "type": "string" - } - }, - "required": [ - "location" - ], - "type": "object" - } - }, - "type": "function" - } - ] - }, - "run_id": "", - "serialized": { - "id": [ - "langchain", - "chat_models", - "openai", - "ChatOpenAI" - ], - "kwargs": { - "max_tokens": 128, - "model": "gpt-4o-mini-2024-07-18", - "openai_api_key": { - "id": [ - "OPENAI_API_KEY" - ], - "lc": 1, - "type": "secret" - }, - "temperature": 0 - }, - "lc": 1, - "type": "constructor" - }, - "tags": [] - }, - "metrics": { - "completion_reasoning_tokens": 0, - "completion_tokens": 16, - "prompt_cached_tokens": 0, - "prompt_tokens": 72, - "tokens": 88 - } - } - ], - "metadata": { - "operation": "tool", - "testRunId": "" - } - }, - { - "name": "langchain-tool-result-operation", - "children": [ - { - "name": "ChatOpenAI", - "type": "llm", - "children": [], - "input": [ - [ - { - "id": [ - "langchain_core", - "messages", - "HumanMessage" - ], - "kwargs": { - "additional_kwargs": {}, - "content": "What is 127 multiplied by 49? Use the calculate tool.", - "response_metadata": {} - }, - "lc": 1, - "type": "constructor" - } - ] - ], - "output": { - "generations": [ - [ - { - "generationInfo": { - "finish_reason": "tool_calls" - }, - "message": { - "id": [ - "langchain_core", - "messages", - "AIMessage" - ], - "kwargs": { - "additional_kwargs": { - "tool_calls": [ - { - "function": { - "arguments": "{\"operation\":\"multiply\",\"a\":127,\"b\":49}", - "name": "calculate" - }, - "id": "", - "type": "function" - } - ] - }, - "content": "", - "id": "", - "invalid_tool_calls": [], - "response_metadata": { - "finish_reason": "tool_calls", - "model_name": "gpt-4o-mini-2024-07-18", - "model_provider": "openai", - "system_fingerprint": "", - "tokenUsage": { - "completionTokens": 21, - "promptTokens": 76, - "totalTokens": 97 - }, - "usage": { - "completion_tokens": 21, - "completion_tokens_details": { - "accepted_prediction_tokens": 0, - "audio_tokens": 0, - "reasoning_tokens": 0, - "rejected_prediction_tokens": 0 - }, - "prompt_tokens": 76, - "prompt_tokens_details": { - "audio_tokens": 0, - "cached_tokens": 0 - }, - "total_tokens": 97 - } - }, - "tool_calls": [ - { - "args": { - "a": 127, - "b": 49, - "operation": "multiply" - }, - "id": "", - "name": "calculate", - "type": "tool_call" - } - ], - "type": "ai", - "usage_metadata": { - "input_token_details": { - "audio": 0, - "cache_read": 0 - }, - "input_tokens": 76, - "output_token_details": { - "audio": 0, - "reasoning": 0 - }, - "output_tokens": 21, - "total_tokens": 97 - } - }, - "lc": 1, - "type": "constructor" - }, - "text": "" - } - ] - ], - "llmOutput": { - "tokenUsage": { - "completionTokens": 21, - "promptTokens": 76, - "totalTokens": 97 - } - } - }, - "metadata": { - "batch_size": 1, - "braintrust": { - "integration_name": "langchain-js", - "sdk_language": "javascript" - }, - "invocation_params": { - "max_tokens": 128, - "model": "gpt-4o-mini-2024-07-18", - "stream": false, - "temperature": 0, - "tools": [ - { - "function": { - "description": "Perform a mathematical calculation", - "name": "calculate", - "parameters": { - "$schema": "http://json-schema.org/draft-07/schema#", - "additionalProperties": false, - "properties": { - "a": { - "type": "number" - }, - "b": { - "type": "number" - }, - "operation": { - "enum": [ - "add", - "subtract", - "multiply", - "divide" - ], - "type": "string" - } - }, - "required": [ - "operation", - "a", - "b" - ], - "type": "object" - } - }, - "type": "function" - } - ] - }, - "metadata": { - "ls_integration": "langchain_chat_model", - "ls_max_tokens": 128, - "ls_model_name": "gpt-4o-mini-2024-07-18", - "ls_model_type": "chat", - "ls_provider": "openai", - "ls_temperature": 0, - "versions": { - "@langchain/core": "1.2.1", - "@langchain/openai": "1.5.3" - } - }, - "model": "gpt-4o-mini-2024-07-18", - "options": { - "tools": [ - { - "function": { - "description": "Perform a mathematical calculation", - "name": "calculate", - "parameters": { - "$schema": "http://json-schema.org/draft-07/schema#", - "additionalProperties": false, - "properties": { - "a": { - "type": "number" - }, - "b": { - "type": "number" - }, - "operation": { - "enum": [ - "add", - "subtract", - "multiply", - "divide" - ], - "type": "string" - } - }, - "required": [ - "operation", - "a", - "b" - ], - "type": "object" - } - }, - "type": "function" - } - ] - }, - "run_id": "", - "serialized": { - "id": [ - "langchain", - "chat_models", - "openai", - "ChatOpenAI" - ], - "kwargs": { - "max_tokens": 128, - "model": "gpt-4o-mini-2024-07-18", - "openai_api_key": { - "id": [ - "OPENAI_API_KEY" - ], - "lc": 1, - "type": "secret" - }, - "temperature": 0 - }, - "lc": 1, - "type": "constructor" - }, - "tags": [] - }, - "metrics": { - "completion_reasoning_tokens": 0, - "completion_tokens": 21, - "prompt_cached_tokens": 0, - "prompt_tokens": 76, - "tokens": 97 - } - }, - { - "name": "ChatOpenAI", - "type": "llm", - "children": [], - "input": [ - [ - { - "id": [ - "langchain_core", - "messages", - "HumanMessage" - ], - "kwargs": { - "additional_kwargs": {}, - "content": "What is 127 multiplied by 49? Use the calculate tool.", - "response_metadata": {} - }, - "lc": 1, - "type": "constructor" - }, - { - "id": [ - "langchain_core", - "messages", - "AIMessage" - ], - "kwargs": { - "additional_kwargs": {}, - "content": "", - "invalid_tool_calls": [], - "response_metadata": {}, - "tool_calls": [ - { - "args": { - "a": 127, - "b": 49, - "operation": "multiply" - }, - "id": "", - "name": "calculate", - "type": "tool_call" - } - ] - }, - "lc": 1, - "type": "constructor" - }, - { - "id": [ - "langchain_core", - "messages", - "ToolMessage" - ], - "kwargs": { - "additional_kwargs": {}, - "content": "6223", - "response_metadata": {}, - "tool_call_id": "call_BMkQ0blzGQeVPEC7pIElPdRs" - }, - "lc": 1, - "type": "constructor" - } - ] - ], - "output": { - "generations": [ - [ - { - "generationInfo": { - "finish_reason": "stop" - }, - "message": { - "id": [ - "langchain_core", - "messages", - "AIMessage" - ], - "kwargs": { - "additional_kwargs": {}, - "content": "127 multiplied by 49 is 6223.", - "id": "", - "invalid_tool_calls": [], - "response_metadata": { - "finish_reason": "stop", - "model_name": "gpt-4o-mini-2024-07-18", - "model_provider": "openai", - "system_fingerprint": "", - "tokenUsage": { - "completionTokens": 11, - "promptTokens": 106, - "totalTokens": 117 - }, - "usage": { - "completion_tokens": 11, - "completion_tokens_details": { - "accepted_prediction_tokens": 0, - "audio_tokens": 0, - "reasoning_tokens": 0, - "rejected_prediction_tokens": 0 - }, - "prompt_tokens": 106, - "prompt_tokens_details": { - "audio_tokens": 0, - "cached_tokens": 0 - }, - "total_tokens": 117 - } - }, - "tool_calls": [], - "type": "ai", - "usage_metadata": { - "input_token_details": { - "audio": 0, - "cache_read": 0 - }, - "input_tokens": 106, - "output_token_details": { - "audio": 0, - "reasoning": 0 - }, - "output_tokens": 11, - "total_tokens": 117 - } - }, - "lc": 1, - "type": "constructor" - }, - "text": "127 multiplied by 49 is 6223." - } - ] - ], - "llmOutput": { - "tokenUsage": { - "completionTokens": 11, - "promptTokens": 106, - "totalTokens": 117 - } - } - }, - "metadata": { - "batch_size": 1, - "braintrust": { - "integration_name": "langchain-js", - "sdk_language": "javascript" - }, - "invocation_params": { - "max_tokens": 128, - "model": "gpt-4o-mini-2024-07-18", - "stream": false, - "temperature": 0, - "tools": [ - { - "function": { - "description": "Perform a mathematical calculation", - "name": "calculate", - "parameters": { - "$schema": "http://json-schema.org/draft-07/schema#", - "additionalProperties": false, - "properties": { - "a": { - "type": "number" - }, - "b": { - "type": "number" - }, - "operation": { - "enum": [ - "add", - "subtract", - "multiply", - "divide" - ], - "type": "string" - } - }, - "required": [ - "operation", - "a", - "b" - ], - "type": "object" - } - }, - "type": "function" - } - ] - }, - "metadata": { - "ls_integration": "langchain_chat_model", - "ls_max_tokens": 128, - "ls_model_name": "gpt-4o-mini-2024-07-18", - "ls_model_type": "chat", - "ls_provider": "openai", - "ls_temperature": 0, - "versions": { - "@langchain/core": "1.2.1", - "@langchain/openai": "1.5.3" - } - }, - "model": "gpt-4o-mini-2024-07-18", - "options": { - "tools": [ - { - "function": { - "description": "Perform a mathematical calculation", - "name": "calculate", - "parameters": { - "$schema": "http://json-schema.org/draft-07/schema#", - "additionalProperties": false, - "properties": { - "a": { - "type": "number" - }, - "b": { - "type": "number" - }, - "operation": { - "enum": [ - "add", - "subtract", - "multiply", - "divide" - ], - "type": "string" - } - }, - "required": [ - "operation", - "a", - "b" - ], - "type": "object" - } - }, - "type": "function" - } - ] - }, - "run_id": "", - "serialized": { - "id": [ - "langchain", - "chat_models", - "openai", - "ChatOpenAI" - ], - "kwargs": { - "max_tokens": 128, - "model": "gpt-4o-mini-2024-07-18", - "openai_api_key": { - "id": [ - "OPENAI_API_KEY" - ], - "lc": 1, - "type": "secret" - }, - "temperature": 0 - }, - "lc": 1, - "type": "constructor" - }, - "tags": [] - }, - "metrics": { - "completion_reasoning_tokens": 0, - "completion_tokens": 11, - "prompt_cached_tokens": 0, - "prompt_tokens": 106, - "tokens": 117 - } - } - ], - "metadata": { - "operation": "tool-result", - "testRunId": "" - } - } - ], - "metadata": { - "scenario": "wrap-langchain-js-traces", - "testRunId": "" - } - } - ] -} diff --git a/e2e/scenarios/wrap-langchain-js-traces/__snapshots__/wrap-langchain-js-traces-v1-latest.span-tree.txt b/e2e/scenarios/wrap-langchain-js-traces/__snapshots__/wrap-langchain-js-traces-v1-latest.span-tree.txt deleted file mode 100644 index d19c42372..000000000 --- a/e2e/scenarios/wrap-langchain-js-traces/__snapshots__/wrap-langchain-js-traces-v1-latest.span-tree.txt +++ /dev/null @@ -1,1450 +0,0 @@ -span_tree: -└── langchain-wrapper-root [task] - metadata: { - "scenario": "wrap-langchain-js-traces", - "testRunId": "" - } - ├── langchain-invoke-operation - │ metadata: { - │ "operation": "invoke", - │ "testRunId": "" - │ } - │ └── ChatOpenAI [llm] - │ input: [ - │ [ - │ { - │ "id": [ - │ "langchain_core", - │ "messages", - │ "HumanMessage" - │ ], - │ "kwargs": { - │ "additional_kwargs": {}, - │ "content": "Reply with exactly OK.", - │ "response_metadata": {} - │ }, - │ "lc": 1, - │ "type": "constructor" - │ } - │ ] - │ ] - │ output: { - │ "generations": [ - │ [ - │ { - │ "generationInfo": { - │ "finish_reason": "stop" - │ }, - │ "message": { - │ "id": [ - │ "langchain_core", - │ "messages", - │ "AIMessage" - │ ], - │ "kwargs": { - │ "additional_kwargs": {}, - │ "content": "OK", - │ "id": "", - │ "invalid_tool_calls": [], - │ "response_metadata": { - │ "finish_reason": "stop", - │ "model_name": "gpt-5-nano-2025-08-07", - │ "model_provider": "openai", - │ "tokenUsage": { - │ "completionTokens": 266, - │ "promptTokens": 11, - │ "totalTokens": 277 - │ } - │ }, - │ "tool_calls": [], - │ "type": "ai", - │ "usage_metadata": { - │ "input_token_details": { - │ "audio": 0, - │ "cache_read": 0 - │ }, - │ "input_tokens": 11, - │ "output_token_details": { - │ "audio": 0, - │ "reasoning": 256 - │ }, - │ "output_tokens": 266, - │ "total_tokens": 277 - │ } - │ }, - │ "lc": 1, - │ "type": "constructor" - │ }, - │ "text": "OK" - │ } - │ ] - │ ], - │ "llmOutput": { - │ "tokenUsage": { - │ "completionTokens": 266, - │ "promptTokens": 11, - │ "totalTokens": 277 - │ } - │ } - │ } - │ metadata: { - │ "batch_size": 1, - │ "braintrust": { - │ "integration_name": "langchain-js", - │ "sdk_language": "javascript" - │ }, - │ "invocation_params": { - │ "max_completion_tokens": 512, - │ "model": "gpt-5-nano", - │ "stream": false - │ }, - │ "metadata": { - │ "ls_integration": "langchain_chat_model", - │ "ls_model_name": "gpt-5-nano", - │ "ls_model_type": "chat", - │ "ls_provider": "openai", - │ "versions": { - │ "@langchain/core": "1.2.1", - │ "@langchain/openai": "1.5.3" - │ } - │ }, - │ "model": "gpt-5-nano-2025-08-07", - │ "options": {}, - │ "run_id": "", - │ "serialized": { - │ "id": [ - │ "langchain", - │ "chat_models", - │ "openai", - │ "ChatOpenAI" - │ ], - │ "kwargs": { - │ "max_tokens": 512, - │ "model": "gpt-5-nano", - │ "openai_api_key": { - │ "id": [ - │ "OPENAI_API_KEY" - │ ], - │ "lc": 1, - │ "type": "secret" - │ } - │ }, - │ "lc": 1, - │ "type": "constructor" - │ }, - │ "tags": [] - │ } - │ metrics: { - │ "completion_reasoning_tokens": 256, - │ "completion_tokens": 266, - │ "prompt_cached_tokens": 0, - │ "prompt_tokens": 11, - │ "tokens": 277 - │ } - ├── langchain-chain-operation - │ metadata: { - │ "operation": "chain", - │ "testRunId": "" - │ } - │ └── RunnableSequence [task] - │ input: { - │ "word": "PARIS" - │ } - │ output: { - │ "id": [ - │ "langchain_core", - │ "messages", - │ "AIMessage" - │ ], - │ "kwargs": { - │ "additional_kwargs": {}, - │ "content": "PARIS", - │ "id": "", - │ "invalid_tool_calls": [], - │ "response_metadata": { - │ "finish_reason": "stop", - │ "model_name": "gpt-4o-mini-2024-07-18", - │ "model_provider": "openai", - │ "system_fingerprint": "", - │ "tokenUsage": { - │ "completionTokens": 2, - │ "promptTokens": 18, - │ "totalTokens": 20 - │ }, - │ "usage": { - │ "completion_tokens": 2, - │ "completion_tokens_details": { - │ "accepted_prediction_tokens": 0, - │ "audio_tokens": 0, - │ "reasoning_tokens": 0, - │ "rejected_prediction_tokens": 0 - │ }, - │ "prompt_tokens": 18, - │ "prompt_tokens_details": { - │ "audio_tokens": 0, - │ "cached_tokens": 0 - │ }, - │ "total_tokens": 20 - │ } - │ }, - │ "tool_calls": [], - │ "type": "ai", - │ "usage_metadata": { - │ "input_token_details": { - │ "audio": 0, - │ "cache_read": 0 - │ }, - │ "input_tokens": 18, - │ "output_token_details": { - │ "audio": 0, - │ "reasoning": 0 - │ }, - │ "output_tokens": 2, - │ "total_tokens": 20 - │ } - │ }, - │ "lc": 1, - │ "type": "constructor" - │ } - │ metadata: { - │ "braintrust": { - │ "integration_name": "langchain-js", - │ "sdk_language": "javascript" - │ }, - │ "metadata": {}, - │ "run_id": "", - │ "serialized": { - │ "id": [ - │ "langchain_core", - │ "runnables", - │ "RunnableSequence" - │ ], - │ "kwargs": { - │ "first": { - │ "id": [ - │ "langchain_core", - │ "prompts", - │ "chat", - │ "ChatPromptTemplate" - │ ], - │ "kwargs": { - │ "input_variables": [ - │ "word" - │ ], - │ "messages": [ - │ { - │ "id": [ - │ "langchain_core", - │ "prompts", - │ "chat", - │ "HumanMessagePromptTemplate" - │ ], - │ "kwargs": { - │ "prompt": { - │ "id": [ - │ "langchain_core", - │ "prompts", - │ "prompt", - │ "PromptTemplate" - │ ], - │ "kwargs": { - │ "input_variables": [ - │ "word" - │ ], - │ "template": "Reply with the single word {word} and nothing else.", - │ "template_format": "f-string" - │ }, - │ "lc": 1, - │ "type": "constructor" - │ } - │ }, - │ "lc": 1, - │ "type": "constructor" - │ } - │ ] - │ }, - │ "lc": 1, - │ "type": "constructor" - │ }, - │ "last": { - │ "id": [ - │ "langchain", - │ "chat_models", - │ "openai", - │ "ChatOpenAI" - │ ], - │ "kwargs": { - │ "max_tokens": 32, - │ "model": "gpt-4o-mini-2024-07-18", - │ "openai_api_key": { - │ "id": [ - │ "OPENAI_API_KEY" - │ ], - │ "lc": 1, - │ "type": "secret" - │ }, - │ "temperature": 0 - │ }, - │ "lc": 1, - │ "type": "constructor" - │ } - │ }, - │ "lc": 1, - │ "type": "constructor" - │ }, - │ "tags": [] - │ } - │ ├── ChatPromptTemplate [task] - │ │ input: { - │ │ "word": "PARIS" - │ │ } - │ │ output: { - │ │ "id": [ - │ │ "langchain_core", - │ │ "prompt_values", - │ │ "ChatPromptValue" - │ │ ], - │ │ "kwargs": { - │ │ "messages": [ - │ │ { - │ │ "id": [ - │ │ "langchain_core", - │ │ "messages", - │ │ "HumanMessage" - │ │ ], - │ │ "kwargs": { - │ │ "additional_kwargs": {}, - │ │ "content": "Reply with the single word PARIS and nothing else.", - │ │ "response_metadata": {} - │ │ }, - │ │ "lc": 1, - │ │ "type": "constructor" - │ │ } - │ │ ] - │ │ }, - │ │ "lc": 1, - │ │ "type": "constructor" - │ │ } - │ │ metadata: { - │ │ "braintrust": { - │ │ "integration_name": "langchain-js", - │ │ "sdk_language": "javascript" - │ │ }, - │ │ "metadata": {}, - │ │ "name": "ChatPromptTemplate", - │ │ "parent_run_id": "", - │ │ "run_id": "", - │ │ "run_type": "prompt", - │ │ "serialized": { - │ │ "id": [ - │ │ "langchain_core", - │ │ "prompts", - │ │ "chat", - │ │ "ChatPromptTemplate" - │ │ ], - │ │ "kwargs": { - │ │ "input_variables": [ - │ │ "word" - │ │ ], - │ │ "messages": [ - │ │ { - │ │ "id": [ - │ │ "langchain_core", - │ │ "prompts", - │ │ "chat", - │ │ "HumanMessagePromptTemplate" - │ │ ], - │ │ "kwargs": { - │ │ "prompt": { - │ │ "id": [ - │ │ "langchain_core", - │ │ "prompts", - │ │ "prompt", - │ │ "PromptTemplate" - │ │ ], - │ │ "kwargs": { - │ │ "input_variables": [ - │ │ "word" - │ │ ], - │ │ "template": "Reply with the single word {word} and nothing else.", - │ │ "template_format": "f-string" - │ │ }, - │ │ "lc": 1, - │ │ "type": "constructor" - │ │ } - │ │ }, - │ │ "lc": 1, - │ │ "type": "constructor" - │ │ } - │ │ ] - │ │ }, - │ │ "lc": 1, - │ │ "type": "constructor" - │ │ }, - │ │ "tags": [ - │ │ "seq:step:1" - │ │ ] - │ │ } - │ └── ChatOpenAI [llm] - │ input: [ - │ [ - │ { - │ "id": [ - │ "langchain_core", - │ "messages", - │ "HumanMessage" - │ ], - │ "kwargs": { - │ "additional_kwargs": {}, - │ "content": "Reply with the single word PARIS and nothing else.", - │ "response_metadata": {} - │ }, - │ "lc": 1, - │ "type": "constructor" - │ } - │ ] - │ ] - │ output: { - │ "generations": [ - │ [ - │ { - │ "generationInfo": { - │ "finish_reason": "stop" - │ }, - │ "message": { - │ "id": [ - │ "langchain_core", - │ "messages", - │ "AIMessage" - │ ], - │ "kwargs": { - │ "additional_kwargs": {}, - │ "content": "PARIS", - │ "id": "", - │ "invalid_tool_calls": [], - │ "response_metadata": { - │ "finish_reason": "stop", - │ "model_name": "gpt-4o-mini-2024-07-18", - │ "model_provider": "openai", - │ "system_fingerprint": "", - │ "tokenUsage": { - │ "completionTokens": 2, - │ "promptTokens": 18, - │ "totalTokens": 20 - │ }, - │ "usage": { - │ "completion_tokens": 2, - │ "completion_tokens_details": { - │ "accepted_prediction_tokens": 0, - │ "audio_tokens": 0, - │ "reasoning_tokens": 0, - │ "rejected_prediction_tokens": 0 - │ }, - │ "prompt_tokens": 18, - │ "prompt_tokens_details": { - │ "audio_tokens": 0, - │ "cached_tokens": 0 - │ }, - │ "total_tokens": 20 - │ } - │ }, - │ "tool_calls": [], - │ "type": "ai", - │ "usage_metadata": { - │ "input_token_details": { - │ "audio": 0, - │ "cache_read": 0 - │ }, - │ "input_tokens": 18, - │ "output_token_details": { - │ "audio": 0, - │ "reasoning": 0 - │ }, - │ "output_tokens": 2, - │ "total_tokens": 20 - │ } - │ }, - │ "lc": 1, - │ "type": "constructor" - │ }, - │ "text": "PARIS" - │ } - │ ] - │ ], - │ "llmOutput": { - │ "tokenUsage": { - │ "completionTokens": 2, - │ "promptTokens": 18, - │ "totalTokens": 20 - │ } - │ } - │ } - │ metadata: { - │ "batch_size": 1, - │ "braintrust": { - │ "integration_name": "langchain-js", - │ "sdk_language": "javascript" - │ }, - │ "invocation_params": { - │ "max_tokens": 32, - │ "model": "gpt-4o-mini-2024-07-18", - │ "stream": false, - │ "temperature": 0 - │ }, - │ "metadata": { - │ "ls_integration": "langchain_chat_model", - │ "ls_max_tokens": 32, - │ "ls_model_name": "gpt-4o-mini-2024-07-18", - │ "ls_model_type": "chat", - │ "ls_provider": "openai", - │ "ls_temperature": 0, - │ "versions": { - │ "@langchain/core": "1.2.1", - │ "@langchain/openai": "1.5.3" - │ } - │ }, - │ "model": "gpt-4o-mini-2024-07-18", - │ "options": {}, - │ "parent_run_id": "", - │ "run_id": "", - │ "serialized": { - │ "id": [ - │ "langchain", - │ "chat_models", - │ "openai", - │ "ChatOpenAI" - │ ], - │ "kwargs": { - │ "max_tokens": 32, - │ "model": "gpt-4o-mini-2024-07-18", - │ "openai_api_key": { - │ "id": [ - │ "OPENAI_API_KEY" - │ ], - │ "lc": 1, - │ "type": "secret" - │ }, - │ "temperature": 0 - │ }, - │ "lc": 1, - │ "type": "constructor" - │ }, - │ "tags": [ - │ "seq:step:2" - │ ] - │ } - │ metrics: { - │ "completion_reasoning_tokens": 0, - │ "completion_tokens": 2, - │ "prompt_cached_tokens": 0, - │ "prompt_tokens": 18, - │ "tokens": 20 - │ } - ├── langchain-stream-operation - │ metadata: { - │ "operation": "stream", - │ "testRunId": "" - │ } - │ └── ChatOpenAI [llm] - │ input: [ - │ [ - │ { - │ "id": [ - │ "langchain_core", - │ "messages", - │ "HumanMessage" - │ ], - │ "kwargs": { - │ "additional_kwargs": {}, - │ "content": "Count from 1 to 3 and include the words one two three.", - │ "response_metadata": {} - │ }, - │ "lc": 1, - │ "type": "constructor" - │ } - │ ] - │ ] - │ output: { - │ "generations": [ - │ [ - │ { - │ "generationInfo": { - │ "completion": 0, - │ "finish_reason": "stop", - │ "model_name": "gpt-4o-mini-2024-07-18", - │ "prompt": 0, - │ "service_tier": "default", - │ "system_fingerprint": "" - │ }, - │ "message": { - │ "id": [ - │ "langchain_core", - │ "messages", - │ "AIMessageChunk" - │ ], - │ "kwargs": { - │ "additional_kwargs": {}, - │ "content": "One, two, three.", - │ "id": "", - │ "invalid_tool_calls": [], - │ "response_metadata": { - │ "completion": 0, - │ "finish_reason": "stop", - │ "model_name": "gpt-4o-mini-2024-07-18", - │ "model_provider": "openai", - │ "prompt": 0, - │ "service_tier": "default", - │ "system_fingerprint": "", - │ "usage": { - │ "completion_tokens": 6, - │ "completion_tokens_details": { - │ "accepted_prediction_tokens": 0, - │ "audio_tokens": 0, - │ "reasoning_tokens": 0, - │ "rejected_prediction_tokens": 0 - │ }, - │ "prompt_tokens": 22, - │ "prompt_tokens_details": { - │ "audio_tokens": 0, - │ "cached_tokens": 0 - │ }, - │ "total_tokens": 28 - │ } - │ }, - │ "tool_call_chunks": [], - │ "tool_calls": [], - │ "usage_metadata": { - │ "input_token_details": { - │ "audio": 0, - │ "cache_read": 0 - │ }, - │ "input_tokens": 22, - │ "output_token_details": { - │ "audio": 0, - │ "reasoning": 0 - │ }, - │ "output_tokens": 6, - │ "total_tokens": 28 - │ } - │ }, - │ "lc": 1, - │ "type": "constructor" - │ }, - │ "text": "One, two, three." - │ } - │ ] - │ ], - │ "llmOutput": { - │ "tokenUsage": { - │ "completionTokens": 6, - │ "promptTokens": 22, - │ "totalTokens": 28 - │ } - │ } - │ } - │ metadata: { - │ "batch_size": 1, - │ "braintrust": { - │ "integration_name": "langchain-js", - │ "sdk_language": "javascript" - │ }, - │ "invocation_params": { - │ "max_tokens": 32, - │ "model": "gpt-4o-mini-2024-07-18", - │ "stream": true, - │ "stream_options": { - │ "include_usage": true - │ }, - │ "temperature": 0 - │ }, - │ "metadata": { - │ "ls_integration": "langchain_chat_model", - │ "ls_max_tokens": 32, - │ "ls_model_name": "gpt-4o-mini-2024-07-18", - │ "ls_model_type": "chat", - │ "ls_provider": "openai", - │ "ls_temperature": 0, - │ "versions": { - │ "@langchain/core": "1.2.1", - │ "@langchain/openai": "1.5.3" - │ } - │ }, - │ "model": "gpt-4o-mini-2024-07-18", - │ "options": {}, - │ "run_id": "", - │ "serialized": { - │ "id": [ - │ "langchain", - │ "chat_models", - │ "openai", - │ "ChatOpenAI" - │ ], - │ "kwargs": { - │ "max_tokens": 32, - │ "model": "gpt-4o-mini-2024-07-18", - │ "openai_api_key": { - │ "id": [ - │ "OPENAI_API_KEY" - │ ], - │ "lc": 1, - │ "type": "secret" - │ }, - │ "streaming": true, - │ "temperature": 0 - │ }, - │ "lc": 1, - │ "type": "constructor" - │ }, - │ "tags": [] - │ } - │ metrics: { - │ "completion_reasoning_tokens": 0, - │ "completion_tokens": 6, - │ "prompt_cached_tokens": 0, - │ "prompt_tokens": 22, - │ "time_to_first_token": 0, - │ "tokens": 28 - │ } - ├── langchain-tool-operation - │ metadata: { - │ "operation": "tool", - │ "testRunId": "" - │ } - │ └── ChatOpenAI [llm] - │ input: [ - │ [ - │ { - │ "id": [ - │ "langchain_core", - │ "messages", - │ "HumanMessage" - │ ], - │ "kwargs": { - │ "additional_kwargs": {}, - │ "content": "Use the get_weather tool for Paris, France. Do not answer from memory.", - │ "response_metadata": {} - │ }, - │ "lc": 1, - │ "type": "constructor" - │ } - │ ] - │ ] - │ output: { - │ "generations": [ - │ [ - │ { - │ "generationInfo": { - │ "finish_reason": "tool_calls" - │ }, - │ "message": { - │ "id": [ - │ "langchain_core", - │ "messages", - │ "AIMessage" - │ ], - │ "kwargs": { - │ "additional_kwargs": { - │ "tool_calls": [ - │ { - │ "function": { - │ "arguments": "{\"location\":\"Paris, France\"}", - │ "name": "get_weather" - │ }, - │ "id": "", - │ "type": "function" - │ } - │ ] - │ }, - │ "content": "", - │ "id": "", - │ "invalid_tool_calls": [], - │ "response_metadata": { - │ "finish_reason": "tool_calls", - │ "model_name": "gpt-4o-mini-2024-07-18", - │ "model_provider": "openai", - │ "system_fingerprint": "", - │ "tokenUsage": { - │ "completionTokens": 16, - │ "promptTokens": 72, - │ "totalTokens": 88 - │ }, - │ "usage": { - │ "completion_tokens": 16, - │ "completion_tokens_details": { - │ "accepted_prediction_tokens": 0, - │ "audio_tokens": 0, - │ "reasoning_tokens": 0, - │ "rejected_prediction_tokens": 0 - │ }, - │ "prompt_tokens": 72, - │ "prompt_tokens_details": { - │ "audio_tokens": 0, - │ "cached_tokens": 0 - │ }, - │ "total_tokens": 88 - │ } - │ }, - │ "tool_calls": [ - │ { - │ "args": { - │ "location": "Paris, France" - │ }, - │ "id": "", - │ "name": "get_weather", - │ "type": "tool_call" - │ } - │ ], - │ "type": "ai", - │ "usage_metadata": { - │ "input_token_details": { - │ "audio": 0, - │ "cache_read": 0 - │ }, - │ "input_tokens": 72, - │ "output_token_details": { - │ "audio": 0, - │ "reasoning": 0 - │ }, - │ "output_tokens": 16, - │ "total_tokens": 88 - │ } - │ }, - │ "lc": 1, - │ "type": "constructor" - │ }, - │ "text": "" - │ } - │ ] - │ ], - │ "llmOutput": { - │ "tokenUsage": { - │ "completionTokens": 16, - │ "promptTokens": 72, - │ "totalTokens": 88 - │ } - │ } - │ } - │ metadata: { - │ "batch_size": 1, - │ "braintrust": { - │ "integration_name": "langchain-js", - │ "sdk_language": "javascript" - │ }, - │ "invocation_params": { - │ "max_tokens": 128, - │ "model": "gpt-4o-mini-2024-07-18", - │ "stream": false, - │ "temperature": 0, - │ "tools": [ - │ { - │ "function": { - │ "description": "Get the current weather in a given location", - │ "name": "get_weather", - │ "parameters": { - │ "$schema": "http://json-schema.org/draft-07/schema#", - │ "additionalProperties": false, - │ "properties": { - │ "location": { - │ "description": "The city and state or city and country", - │ "type": "string" - │ } - │ }, - │ "required": [ - │ "location" - │ ], - │ "type": "object" - │ } - │ }, - │ "type": "function" - │ } - │ ] - │ }, - │ "metadata": { - │ "ls_integration": "langchain_chat_model", - │ "ls_max_tokens": 128, - │ "ls_model_name": "gpt-4o-mini-2024-07-18", - │ "ls_model_type": "chat", - │ "ls_provider": "openai", - │ "ls_temperature": 0, - │ "versions": { - │ "@langchain/core": "1.2.1", - │ "@langchain/openai": "1.5.3" - │ } - │ }, - │ "model": "gpt-4o-mini-2024-07-18", - │ "options": { - │ "tools": [ - │ { - │ "function": { - │ "description": "Get the current weather in a given location", - │ "name": "get_weather", - │ "parameters": { - │ "$schema": "http://json-schema.org/draft-07/schema#", - │ "additionalProperties": false, - │ "properties": { - │ "location": { - │ "description": "The city and state or city and country", - │ "type": "string" - │ } - │ }, - │ "required": [ - │ "location" - │ ], - │ "type": "object" - │ } - │ }, - │ "type": "function" - │ } - │ ] - │ }, - │ "run_id": "", - │ "serialized": { - │ "id": [ - │ "langchain", - │ "chat_models", - │ "openai", - │ "ChatOpenAI" - │ ], - │ "kwargs": { - │ "max_tokens": 128, - │ "model": "gpt-4o-mini-2024-07-18", - │ "openai_api_key": { - │ "id": [ - │ "OPENAI_API_KEY" - │ ], - │ "lc": 1, - │ "type": "secret" - │ }, - │ "temperature": 0 - │ }, - │ "lc": 1, - │ "type": "constructor" - │ }, - │ "tags": [] - │ } - │ metrics: { - │ "completion_reasoning_tokens": 0, - │ "completion_tokens": 16, - │ "prompt_cached_tokens": 0, - │ "prompt_tokens": 72, - │ "tokens": 88 - │ } - └── langchain-tool-result-operation - metadata: { - "operation": "tool-result", - "testRunId": "" - } - ├── ChatOpenAI [llm] - │ input: [ - │ [ - │ { - │ "id": [ - │ "langchain_core", - │ "messages", - │ "HumanMessage" - │ ], - │ "kwargs": { - │ "additional_kwargs": {}, - │ "content": "What is 127 multiplied by 49? Use the calculate tool.", - │ "response_metadata": {} - │ }, - │ "lc": 1, - │ "type": "constructor" - │ } - │ ] - │ ] - │ output: { - │ "generations": [ - │ [ - │ { - │ "generationInfo": { - │ "finish_reason": "tool_calls" - │ }, - │ "message": { - │ "id": [ - │ "langchain_core", - │ "messages", - │ "AIMessage" - │ ], - │ "kwargs": { - │ "additional_kwargs": { - │ "tool_calls": [ - │ { - │ "function": { - │ "arguments": "{\"operation\":\"multiply\",\"a\":127,\"b\":49}", - │ "name": "calculate" - │ }, - │ "id": "", - │ "type": "function" - │ } - │ ] - │ }, - │ "content": "", - │ "id": "", - │ "invalid_tool_calls": [], - │ "response_metadata": { - │ "finish_reason": "tool_calls", - │ "model_name": "gpt-4o-mini-2024-07-18", - │ "model_provider": "openai", - │ "system_fingerprint": "", - │ "tokenUsage": { - │ "completionTokens": 21, - │ "promptTokens": 76, - │ "totalTokens": 97 - │ }, - │ "usage": { - │ "completion_tokens": 21, - │ "completion_tokens_details": { - │ "accepted_prediction_tokens": 0, - │ "audio_tokens": 0, - │ "reasoning_tokens": 0, - │ "rejected_prediction_tokens": 0 - │ }, - │ "prompt_tokens": 76, - │ "prompt_tokens_details": { - │ "audio_tokens": 0, - │ "cached_tokens": 0 - │ }, - │ "total_tokens": 97 - │ } - │ }, - │ "tool_calls": [ - │ { - │ "args": { - │ "a": 127, - │ "b": 49, - │ "operation": "multiply" - │ }, - │ "id": "", - │ "name": "calculate", - │ "type": "tool_call" - │ } - │ ], - │ "type": "ai", - │ "usage_metadata": { - │ "input_token_details": { - │ "audio": 0, - │ "cache_read": 0 - │ }, - │ "input_tokens": 76, - │ "output_token_details": { - │ "audio": 0, - │ "reasoning": 0 - │ }, - │ "output_tokens": 21, - │ "total_tokens": 97 - │ } - │ }, - │ "lc": 1, - │ "type": "constructor" - │ }, - │ "text": "" - │ } - │ ] - │ ], - │ "llmOutput": { - │ "tokenUsage": { - │ "completionTokens": 21, - │ "promptTokens": 76, - │ "totalTokens": 97 - │ } - │ } - │ } - │ metadata: { - │ "batch_size": 1, - │ "braintrust": { - │ "integration_name": "langchain-js", - │ "sdk_language": "javascript" - │ }, - │ "invocation_params": { - │ "max_tokens": 128, - │ "model": "gpt-4o-mini-2024-07-18", - │ "stream": false, - │ "temperature": 0, - │ "tools": [ - │ { - │ "function": { - │ "description": "Perform a mathematical calculation", - │ "name": "calculate", - │ "parameters": { - │ "$schema": "http://json-schema.org/draft-07/schema#", - │ "additionalProperties": false, - │ "properties": { - │ "a": { - │ "type": "number" - │ }, - │ "b": { - │ "type": "number" - │ }, - │ "operation": { - │ "enum": [ - │ "add", - │ "subtract", - │ "multiply", - │ "divide" - │ ], - │ "type": "string" - │ } - │ }, - │ "required": [ - │ "operation", - │ "a", - │ "b" - │ ], - │ "type": "object" - │ } - │ }, - │ "type": "function" - │ } - │ ] - │ }, - │ "metadata": { - │ "ls_integration": "langchain_chat_model", - │ "ls_max_tokens": 128, - │ "ls_model_name": "gpt-4o-mini-2024-07-18", - │ "ls_model_type": "chat", - │ "ls_provider": "openai", - │ "ls_temperature": 0, - │ "versions": { - │ "@langchain/core": "1.2.1", - │ "@langchain/openai": "1.5.3" - │ } - │ }, - │ "model": "gpt-4o-mini-2024-07-18", - │ "options": { - │ "tools": [ - │ { - │ "function": { - │ "description": "Perform a mathematical calculation", - │ "name": "calculate", - │ "parameters": { - │ "$schema": "http://json-schema.org/draft-07/schema#", - │ "additionalProperties": false, - │ "properties": { - │ "a": { - │ "type": "number" - │ }, - │ "b": { - │ "type": "number" - │ }, - │ "operation": { - │ "enum": [ - │ "add", - │ "subtract", - │ "multiply", - │ "divide" - │ ], - │ "type": "string" - │ } - │ }, - │ "required": [ - │ "operation", - │ "a", - │ "b" - │ ], - │ "type": "object" - │ } - │ }, - │ "type": "function" - │ } - │ ] - │ }, - │ "run_id": "", - │ "serialized": { - │ "id": [ - │ "langchain", - │ "chat_models", - │ "openai", - │ "ChatOpenAI" - │ ], - │ "kwargs": { - │ "max_tokens": 128, - │ "model": "gpt-4o-mini-2024-07-18", - │ "openai_api_key": { - │ "id": [ - │ "OPENAI_API_KEY" - │ ], - │ "lc": 1, - │ "type": "secret" - │ }, - │ "temperature": 0 - │ }, - │ "lc": 1, - │ "type": "constructor" - │ }, - │ "tags": [] - │ } - │ metrics: { - │ "completion_reasoning_tokens": 0, - │ "completion_tokens": 21, - │ "prompt_cached_tokens": 0, - │ "prompt_tokens": 76, - │ "tokens": 97 - │ } - └── ChatOpenAI [llm] - input: [ - [ - { - "id": [ - "langchain_core", - "messages", - "HumanMessage" - ], - "kwargs": { - "additional_kwargs": {}, - "content": "What is 127 multiplied by 49? Use the calculate tool.", - "response_metadata": {} - }, - "lc": 1, - "type": "constructor" - }, - { - "id": [ - "langchain_core", - "messages", - "AIMessage" - ], - "kwargs": { - "additional_kwargs": {}, - "content": "", - "invalid_tool_calls": [], - "response_metadata": {}, - "tool_calls": [ - { - "args": { - "a": 127, - "b": 49, - "operation": "multiply" - }, - "id": "", - "name": "calculate", - "type": "tool_call" - } - ] - }, - "lc": 1, - "type": "constructor" - }, - { - "id": [ - "langchain_core", - "messages", - "ToolMessage" - ], - "kwargs": { - "additional_kwargs": {}, - "content": "6223", - "response_metadata": {}, - "tool_call_id": "call_BMkQ0blzGQeVPEC7pIElPdRs" - }, - "lc": 1, - "type": "constructor" - } - ] - ] - output: { - "generations": [ - [ - { - "generationInfo": { - "finish_reason": "stop" - }, - "message": { - "id": [ - "langchain_core", - "messages", - "AIMessage" - ], - "kwargs": { - "additional_kwargs": {}, - "content": "127 multiplied by 49 is 6223.", - "id": "", - "invalid_tool_calls": [], - "response_metadata": { - "finish_reason": "stop", - "model_name": "gpt-4o-mini-2024-07-18", - "model_provider": "openai", - "system_fingerprint": "", - "tokenUsage": { - "completionTokens": 11, - "promptTokens": 106, - "totalTokens": 117 - }, - "usage": { - "completion_tokens": 11, - "completion_tokens_details": { - "accepted_prediction_tokens": 0, - "audio_tokens": 0, - "reasoning_tokens": 0, - "rejected_prediction_tokens": 0 - }, - "prompt_tokens": 106, - "prompt_tokens_details": { - "audio_tokens": 0, - "cached_tokens": 0 - }, - "total_tokens": 117 - } - }, - "tool_calls": [], - "type": "ai", - "usage_metadata": { - "input_token_details": { - "audio": 0, - "cache_read": 0 - }, - "input_tokens": 106, - "output_token_details": { - "audio": 0, - "reasoning": 0 - }, - "output_tokens": 11, - "total_tokens": 117 - } - }, - "lc": 1, - "type": "constructor" - }, - "text": "127 multiplied by 49 is 6223." - } - ] - ], - "llmOutput": { - "tokenUsage": { - "completionTokens": 11, - "promptTokens": 106, - "totalTokens": 117 - } - } - } - metadata: { - "batch_size": 1, - "braintrust": { - "integration_name": "langchain-js", - "sdk_language": "javascript" - }, - "invocation_params": { - "max_tokens": 128, - "model": "gpt-4o-mini-2024-07-18", - "stream": false, - "temperature": 0, - "tools": [ - { - "function": { - "description": "Perform a mathematical calculation", - "name": "calculate", - "parameters": { - "$schema": "http://json-schema.org/draft-07/schema#", - "additionalProperties": false, - "properties": { - "a": { - "type": "number" - }, - "b": { - "type": "number" - }, - "operation": { - "enum": [ - "add", - "subtract", - "multiply", - "divide" - ], - "type": "string" - } - }, - "required": [ - "operation", - "a", - "b" - ], - "type": "object" - } - }, - "type": "function" - } - ] - }, - "metadata": { - "ls_integration": "langchain_chat_model", - "ls_max_tokens": 128, - "ls_model_name": "gpt-4o-mini-2024-07-18", - "ls_model_type": "chat", - "ls_provider": "openai", - "ls_temperature": 0, - "versions": { - "@langchain/core": "1.2.1", - "@langchain/openai": "1.5.3" - } - }, - "model": "gpt-4o-mini-2024-07-18", - "options": { - "tools": [ - { - "function": { - "description": "Perform a mathematical calculation", - "name": "calculate", - "parameters": { - "$schema": "http://json-schema.org/draft-07/schema#", - "additionalProperties": false, - "properties": { - "a": { - "type": "number" - }, - "b": { - "type": "number" - }, - "operation": { - "enum": [ - "add", - "subtract", - "multiply", - "divide" - ], - "type": "string" - } - }, - "required": [ - "operation", - "a", - "b" - ], - "type": "object" - } - }, - "type": "function" - } - ] - }, - "run_id": "", - "serialized": { - "id": [ - "langchain", - "chat_models", - "openai", - "ChatOpenAI" - ], - "kwargs": { - "max_tokens": 128, - "model": "gpt-4o-mini-2024-07-18", - "openai_api_key": { - "id": [ - "OPENAI_API_KEY" - ], - "lc": 1, - "type": "secret" - }, - "temperature": 0 - }, - "lc": 1, - "type": "constructor" - }, - "tags": [] - } - metrics: { - "completion_reasoning_tokens": 0, - "completion_tokens": 11, - "prompt_cached_tokens": 0, - "prompt_tokens": 106, - "tokens": 117 - } diff --git a/e2e/scenarios/wrap-langchain-js-traces/__snapshots__/wrap-langchain-js-traces-v1.span-tree.json b/e2e/scenarios/wrap-langchain-js-traces/__snapshots__/wrap-langchain-js-traces-v1.span-tree.json deleted file mode 100644 index f9106045f..000000000 --- a/e2e/scenarios/wrap-langchain-js-traces/__snapshots__/wrap-langchain-js-traces-v1.span-tree.json +++ /dev/null @@ -1,1511 +0,0 @@ -{ - "span_tree": [ - { - "name": "langchain-wrapper-root", - "type": "task", - "children": [ - { - "name": "langchain-invoke-operation", - "children": [ - { - "name": "ChatOpenAI", - "type": "llm", - "children": [], - "input": [ - [ - { - "id": [ - "langchain_core", - "messages", - "HumanMessage" - ], - "kwargs": { - "additional_kwargs": {}, - "content": "Reply with exactly OK.", - "response_metadata": {} - }, - "lc": 1, - "type": "constructor" - } - ] - ], - "output": { - "generations": [ - [ - { - "generationInfo": { - "finish_reason": "stop" - }, - "message": { - "id": [ - "langchain_core", - "messages", - "AIMessage" - ], - "kwargs": { - "additional_kwargs": {}, - "content": "OK", - "id": "", - "invalid_tool_calls": [], - "response_metadata": { - "finish_reason": "stop", - "model_name": "gpt-5-nano-2025-08-07", - "model_provider": "openai", - "tokenUsage": { - "completionTokens": 138, - "promptTokens": 11, - "totalTokens": 149 - } - }, - "tool_calls": [], - "type": "ai", - "usage_metadata": { - "input_token_details": { - "audio": 0, - "cache_read": 0 - }, - "input_tokens": 11, - "output_token_details": { - "audio": 0, - "reasoning": 128 - }, - "output_tokens": 138, - "total_tokens": 149 - } - }, - "lc": 1, - "type": "constructor" - }, - "text": "OK" - } - ] - ], - "llmOutput": { - "tokenUsage": { - "completionTokens": 138, - "promptTokens": 11, - "totalTokens": 149 - } - } - }, - "metadata": { - "batch_size": 1, - "braintrust": { - "integration_name": "langchain-js", - "sdk_language": "javascript" - }, - "invocation_params": { - "max_completion_tokens": 512, - "model": "gpt-5-nano", - "stream": false - }, - "metadata": { - "ls_integration": "langchain_chat_model", - "ls_model_name": "gpt-5-nano", - "ls_model_type": "chat", - "ls_provider": "openai", - "versions": { - "@langchain/core": "1.2.1", - "@langchain/openai": "1.3.0" - } - }, - "model": "gpt-5-nano-2025-08-07", - "options": {}, - "run_id": "", - "serialized": { - "id": [ - "langchain", - "chat_models", - "openai", - "ChatOpenAI" - ], - "kwargs": { - "max_tokens": 512, - "model": "gpt-5-nano", - "openai_api_key": { - "id": [ - "OPENAI_API_KEY" - ], - "lc": 1, - "type": "secret" - } - }, - "lc": 1, - "type": "constructor" - }, - "tags": [] - }, - "metrics": { - "completion_reasoning_tokens": 128, - "completion_tokens": 138, - "prompt_cached_tokens": 0, - "prompt_tokens": 11, - "tokens": 149 - } - } - ], - "metadata": { - "operation": "invoke", - "testRunId": "" - } - }, - { - "name": "langchain-chain-operation", - "children": [ - { - "name": "RunnableSequence", - "type": "task", - "children": [ - { - "name": "ChatPromptTemplate", - "type": "task", - "children": [], - "input": { - "word": "PARIS" - }, - "output": { - "id": [ - "langchain_core", - "prompt_values", - "ChatPromptValue" - ], - "kwargs": { - "messages": [ - { - "id": [ - "langchain_core", - "messages", - "HumanMessage" - ], - "kwargs": { - "additional_kwargs": {}, - "content": "Reply with the single word PARIS and nothing else.", - "response_metadata": {} - }, - "lc": 1, - "type": "constructor" - } - ] - }, - "lc": 1, - "type": "constructor" - }, - "metadata": { - "braintrust": { - "integration_name": "langchain-js", - "sdk_language": "javascript" - }, - "metadata": {}, - "name": "ChatPromptTemplate", - "parent_run_id": "", - "run_id": "", - "run_type": "prompt", - "serialized": { - "id": [ - "langchain_core", - "prompts", - "chat", - "ChatPromptTemplate" - ], - "kwargs": { - "input_variables": [ - "word" - ], - "messages": [ - { - "id": [ - "langchain_core", - "prompts", - "chat", - "HumanMessagePromptTemplate" - ], - "kwargs": { - "prompt": { - "id": [ - "langchain_core", - "prompts", - "prompt", - "PromptTemplate" - ], - "kwargs": { - "input_variables": [ - "word" - ], - "template": "Reply with the single word {word} and nothing else.", - "template_format": "f-string" - }, - "lc": 1, - "type": "constructor" - } - }, - "lc": 1, - "type": "constructor" - } - ] - }, - "lc": 1, - "type": "constructor" - }, - "tags": [ - "seq:step:1" - ] - } - }, - { - "name": "ChatOpenAI", - "type": "llm", - "children": [], - "input": [ - [ - { - "id": [ - "langchain_core", - "messages", - "HumanMessage" - ], - "kwargs": { - "additional_kwargs": {}, - "content": "Reply with the single word PARIS and nothing else.", - "response_metadata": {} - }, - "lc": 1, - "type": "constructor" - } - ] - ], - "output": { - "generations": [ - [ - { - "generationInfo": { - "finish_reason": "stop" - }, - "message": { - "id": [ - "langchain_core", - "messages", - "AIMessage" - ], - "kwargs": { - "additional_kwargs": {}, - "content": "PARIS", - "id": "", - "invalid_tool_calls": [], - "response_metadata": { - "finish_reason": "stop", - "model_name": "gpt-4o-mini-2024-07-18", - "model_provider": "openai", - "system_fingerprint": "", - "tokenUsage": { - "completionTokens": 2, - "promptTokens": 18, - "totalTokens": 20 - }, - "usage": { - "completion_tokens": 2, - "completion_tokens_details": { - "accepted_prediction_tokens": 0, - "audio_tokens": 0, - "reasoning_tokens": 0, - "rejected_prediction_tokens": 0 - }, - "prompt_tokens": 18, - "prompt_tokens_details": { - "audio_tokens": 0, - "cached_tokens": 0 - }, - "total_tokens": 20 - } - }, - "tool_calls": [], - "type": "ai", - "usage_metadata": { - "input_token_details": { - "audio": 0, - "cache_read": 0 - }, - "input_tokens": 18, - "output_token_details": { - "audio": 0, - "reasoning": 0 - }, - "output_tokens": 2, - "total_tokens": 20 - } - }, - "lc": 1, - "type": "constructor" - }, - "text": "PARIS" - } - ] - ], - "llmOutput": { - "tokenUsage": { - "completionTokens": 2, - "promptTokens": 18, - "totalTokens": 20 - } - } - }, - "metadata": { - "batch_size": 1, - "braintrust": { - "integration_name": "langchain-js", - "sdk_language": "javascript" - }, - "invocation_params": { - "max_tokens": 32, - "model": "gpt-4o-mini-2024-07-18", - "stream": false, - "temperature": 0 - }, - "metadata": { - "ls_integration": "langchain_chat_model", - "ls_max_tokens": 32, - "ls_model_name": "gpt-4o-mini-2024-07-18", - "ls_model_type": "chat", - "ls_provider": "openai", - "ls_temperature": 0, - "versions": { - "@langchain/core": "1.2.1", - "@langchain/openai": "1.3.0" - } - }, - "model": "gpt-4o-mini-2024-07-18", - "options": {}, - "parent_run_id": "", - "run_id": "", - "serialized": { - "id": [ - "langchain", - "chat_models", - "openai", - "ChatOpenAI" - ], - "kwargs": { - "max_tokens": 32, - "model": "gpt-4o-mini-2024-07-18", - "openai_api_key": { - "id": [ - "OPENAI_API_KEY" - ], - "lc": 1, - "type": "secret" - }, - "temperature": 0 - }, - "lc": 1, - "type": "constructor" - }, - "tags": [ - "seq:step:2" - ] - }, - "metrics": { - "completion_reasoning_tokens": 0, - "completion_tokens": 2, - "prompt_cached_tokens": 0, - "prompt_tokens": 18, - "tokens": 20 - } - } - ], - "input": { - "word": "PARIS" - }, - "output": { - "id": [ - "langchain_core", - "messages", - "AIMessage" - ], - "kwargs": { - "additional_kwargs": {}, - "content": "PARIS", - "id": "", - "invalid_tool_calls": [], - "response_metadata": { - "finish_reason": "stop", - "model_name": "gpt-4o-mini-2024-07-18", - "model_provider": "openai", - "system_fingerprint": "", - "tokenUsage": { - "completionTokens": 2, - "promptTokens": 18, - "totalTokens": 20 - }, - "usage": { - "completion_tokens": 2, - "completion_tokens_details": { - "accepted_prediction_tokens": 0, - "audio_tokens": 0, - "reasoning_tokens": 0, - "rejected_prediction_tokens": 0 - }, - "prompt_tokens": 18, - "prompt_tokens_details": { - "audio_tokens": 0, - "cached_tokens": 0 - }, - "total_tokens": 20 - } - }, - "tool_calls": [], - "type": "ai", - "usage_metadata": { - "input_token_details": { - "audio": 0, - "cache_read": 0 - }, - "input_tokens": 18, - "output_token_details": { - "audio": 0, - "reasoning": 0 - }, - "output_tokens": 2, - "total_tokens": 20 - } - }, - "lc": 1, - "type": "constructor" - }, - "metadata": { - "braintrust": { - "integration_name": "langchain-js", - "sdk_language": "javascript" - }, - "metadata": {}, - "run_id": "", - "serialized": { - "id": [ - "langchain_core", - "runnables", - "RunnableSequence" - ], - "kwargs": { - "first": { - "id": [ - "langchain_core", - "prompts", - "chat", - "ChatPromptTemplate" - ], - "kwargs": { - "input_variables": [ - "word" - ], - "messages": [ - { - "id": [ - "langchain_core", - "prompts", - "chat", - "HumanMessagePromptTemplate" - ], - "kwargs": { - "prompt": { - "id": [ - "langchain_core", - "prompts", - "prompt", - "PromptTemplate" - ], - "kwargs": { - "input_variables": [ - "word" - ], - "template": "Reply with the single word {word} and nothing else.", - "template_format": "f-string" - }, - "lc": 1, - "type": "constructor" - } - }, - "lc": 1, - "type": "constructor" - } - ] - }, - "lc": 1, - "type": "constructor" - }, - "last": { - "id": [ - "langchain", - "chat_models", - "openai", - "ChatOpenAI" - ], - "kwargs": { - "max_tokens": 32, - "model": "gpt-4o-mini-2024-07-18", - "openai_api_key": { - "id": [ - "OPENAI_API_KEY" - ], - "lc": 1, - "type": "secret" - }, - "temperature": 0 - }, - "lc": 1, - "type": "constructor" - } - }, - "lc": 1, - "type": "constructor" - }, - "tags": [] - } - } - ], - "metadata": { - "operation": "chain", - "testRunId": "" - } - }, - { - "name": "langchain-stream-operation", - "children": [ - { - "name": "ChatOpenAI", - "type": "llm", - "children": [], - "input": [ - [ - { - "id": [ - "langchain_core", - "messages", - "HumanMessage" - ], - "kwargs": { - "additional_kwargs": {}, - "content": "Count from 1 to 3 and include the words one two three.", - "response_metadata": {} - }, - "lc": 1, - "type": "constructor" - } - ] - ], - "output": { - "generations": [ - [ - { - "generationInfo": { - "completion": 0, - "finish_reason": "stop", - "model_name": "gpt-4o-mini-2024-07-18", - "prompt": 0, - "service_tier": "default", - "system_fingerprint": "" - }, - "message": { - "id": [ - "langchain_core", - "messages", - "AIMessageChunk" - ], - "kwargs": { - "additional_kwargs": {}, - "content": "One, two, three.", - "id": "", - "invalid_tool_calls": [], - "response_metadata": { - "completion": 0, - "finish_reason": "stop", - "model_name": "gpt-4o-mini-2024-07-18", - "model_provider": "openai", - "prompt": 0, - "service_tier": "default", - "system_fingerprint": "", - "usage": { - "completion_tokens": 6, - "completion_tokens_details": { - "accepted_prediction_tokens": 0, - "audio_tokens": 0, - "reasoning_tokens": 0, - "rejected_prediction_tokens": 0 - }, - "prompt_tokens": 22, - "prompt_tokens_details": { - "audio_tokens": 0, - "cached_tokens": 0 - }, - "total_tokens": 28 - } - }, - "tool_call_chunks": [], - "tool_calls": [], - "usage_metadata": { - "input_token_details": { - "audio": 0, - "cache_read": 0 - }, - "input_tokens": 22, - "output_token_details": { - "audio": 0, - "reasoning": 0 - }, - "output_tokens": 6, - "total_tokens": 28 - } - }, - "lc": 1, - "type": "constructor" - }, - "text": "One, two, three." - } - ] - ], - "llmOutput": { - "tokenUsage": { - "completionTokens": 6, - "promptTokens": 22, - "totalTokens": 28 - } - } - }, - "metadata": { - "batch_size": 1, - "braintrust": { - "integration_name": "langchain-js", - "sdk_language": "javascript" - }, - "invocation_params": { - "max_tokens": 32, - "model": "gpt-4o-mini-2024-07-18", - "stream": true, - "stream_options": { - "include_usage": true - }, - "temperature": 0 - }, - "metadata": { - "ls_integration": "langchain_chat_model", - "ls_max_tokens": 32, - "ls_model_name": "gpt-4o-mini-2024-07-18", - "ls_model_type": "chat", - "ls_provider": "openai", - "ls_temperature": 0, - "versions": { - "@langchain/core": "1.2.1", - "@langchain/openai": "1.3.0" - } - }, - "model": "gpt-4o-mini-2024-07-18", - "options": {}, - "run_id": "", - "serialized": { - "id": [ - "langchain", - "chat_models", - "openai", - "ChatOpenAI" - ], - "kwargs": { - "max_tokens": 32, - "model": "gpt-4o-mini-2024-07-18", - "openai_api_key": { - "id": [ - "OPENAI_API_KEY" - ], - "lc": 1, - "type": "secret" - }, - "streaming": true, - "temperature": 0 - }, - "lc": 1, - "type": "constructor" - }, - "tags": [] - }, - "metrics": { - "completion_reasoning_tokens": 0, - "completion_tokens": 6, - "prompt_cached_tokens": 0, - "prompt_tokens": 22, - "time_to_first_token": 0, - "tokens": 28 - } - } - ], - "metadata": { - "operation": "stream", - "testRunId": "" - } - }, - { - "name": "langchain-tool-operation", - "children": [ - { - "name": "ChatOpenAI", - "type": "llm", - "children": [], - "input": [ - [ - { - "id": [ - "langchain_core", - "messages", - "HumanMessage" - ], - "kwargs": { - "additional_kwargs": {}, - "content": "Use the get_weather tool for Paris, France. Do not answer from memory.", - "response_metadata": {} - }, - "lc": 1, - "type": "constructor" - } - ] - ], - "output": { - "generations": [ - [ - { - "generationInfo": { - "finish_reason": "tool_calls" - }, - "message": { - "id": [ - "langchain_core", - "messages", - "AIMessage" - ], - "kwargs": { - "additional_kwargs": { - "tool_calls": [ - { - "function": { - "arguments": "{\"location\":\"Paris, France\"}", - "name": "get_weather" - }, - "id": "", - "type": "function" - } - ] - }, - "content": "", - "id": "", - "invalid_tool_calls": [], - "response_metadata": { - "finish_reason": "tool_calls", - "model_name": "gpt-4o-mini-2024-07-18", - "model_provider": "openai", - "system_fingerprint": "", - "tokenUsage": { - "completionTokens": 16, - "promptTokens": 72, - "totalTokens": 88 - }, - "usage": { - "completion_tokens": 16, - "completion_tokens_details": { - "accepted_prediction_tokens": 0, - "audio_tokens": 0, - "reasoning_tokens": 0, - "rejected_prediction_tokens": 0 - }, - "prompt_tokens": 72, - "prompt_tokens_details": { - "audio_tokens": 0, - "cached_tokens": 0 - }, - "total_tokens": 88 - } - }, - "tool_calls": [ - { - "args": { - "location": "Paris, France" - }, - "id": "", - "name": "get_weather", - "type": "tool_call" - } - ], - "type": "ai", - "usage_metadata": { - "input_token_details": { - "audio": 0, - "cache_read": 0 - }, - "input_tokens": 72, - "output_token_details": { - "audio": 0, - "reasoning": 0 - }, - "output_tokens": 16, - "total_tokens": 88 - } - }, - "lc": 1, - "type": "constructor" - }, - "text": "" - } - ] - ], - "llmOutput": { - "tokenUsage": { - "completionTokens": 16, - "promptTokens": 72, - "totalTokens": 88 - } - } - }, - "metadata": { - "batch_size": 1, - "braintrust": { - "integration_name": "langchain-js", - "sdk_language": "javascript" - }, - "invocation_params": { - "max_tokens": 128, - "model": "gpt-4o-mini-2024-07-18", - "stream": false, - "temperature": 0, - "tools": [ - { - "function": { - "description": "Get the current weather in a given location", - "name": "get_weather", - "parameters": { - "$schema": "http://json-schema.org/draft-07/schema#", - "additionalProperties": false, - "properties": { - "location": { - "description": "The city and state or city and country", - "type": "string" - } - }, - "required": [ - "location" - ], - "type": "object" - } - }, - "type": "function" - } - ] - }, - "metadata": { - "ls_integration": "langchain_chat_model", - "ls_max_tokens": 128, - "ls_model_name": "gpt-4o-mini-2024-07-18", - "ls_model_type": "chat", - "ls_provider": "openai", - "ls_temperature": 0, - "versions": { - "@langchain/core": "1.2.1", - "@langchain/openai": "1.3.0" - } - }, - "model": "gpt-4o-mini-2024-07-18", - "options": { - "tools": [ - { - "function": { - "description": "Get the current weather in a given location", - "name": "get_weather", - "parameters": { - "$schema": "http://json-schema.org/draft-07/schema#", - "additionalProperties": false, - "properties": { - "location": { - "description": "The city and state or city and country", - "type": "string" - } - }, - "required": [ - "location" - ], - "type": "object" - } - }, - "type": "function" - } - ] - }, - "run_id": "", - "serialized": { - "id": [ - "langchain", - "chat_models", - "openai", - "ChatOpenAI" - ], - "kwargs": { - "max_tokens": 128, - "model": "gpt-4o-mini-2024-07-18", - "openai_api_key": { - "id": [ - "OPENAI_API_KEY" - ], - "lc": 1, - "type": "secret" - }, - "temperature": 0 - }, - "lc": 1, - "type": "constructor" - }, - "tags": [] - }, - "metrics": { - "completion_reasoning_tokens": 0, - "completion_tokens": 16, - "prompt_cached_tokens": 0, - "prompt_tokens": 72, - "tokens": 88 - } - } - ], - "metadata": { - "operation": "tool", - "testRunId": "" - } - }, - { - "name": "langchain-tool-result-operation", - "children": [ - { - "name": "ChatOpenAI", - "type": "llm", - "children": [], - "input": [ - [ - { - "id": [ - "langchain_core", - "messages", - "HumanMessage" - ], - "kwargs": { - "additional_kwargs": {}, - "content": "What is 127 multiplied by 49? Use the calculate tool.", - "response_metadata": {} - }, - "lc": 1, - "type": "constructor" - } - ] - ], - "output": { - "generations": [ - [ - { - "generationInfo": { - "finish_reason": "tool_calls" - }, - "message": { - "id": [ - "langchain_core", - "messages", - "AIMessage" - ], - "kwargs": { - "additional_kwargs": { - "tool_calls": [ - { - "function": { - "arguments": "{\"operation\":\"multiply\",\"a\":127,\"b\":49}", - "name": "calculate" - }, - "id": "", - "type": "function" - } - ] - }, - "content": "", - "id": "", - "invalid_tool_calls": [], - "response_metadata": { - "finish_reason": "tool_calls", - "model_name": "gpt-4o-mini-2024-07-18", - "model_provider": "openai", - "system_fingerprint": "", - "tokenUsage": { - "completionTokens": 21, - "promptTokens": 76, - "totalTokens": 97 - }, - "usage": { - "completion_tokens": 21, - "completion_tokens_details": { - "accepted_prediction_tokens": 0, - "audio_tokens": 0, - "reasoning_tokens": 0, - "rejected_prediction_tokens": 0 - }, - "prompt_tokens": 76, - "prompt_tokens_details": { - "audio_tokens": 0, - "cached_tokens": 0 - }, - "total_tokens": 97 - } - }, - "tool_calls": [ - { - "args": { - "a": 127, - "b": 49, - "operation": "multiply" - }, - "id": "", - "name": "calculate", - "type": "tool_call" - } - ], - "type": "ai", - "usage_metadata": { - "input_token_details": { - "audio": 0, - "cache_read": 0 - }, - "input_tokens": 76, - "output_token_details": { - "audio": 0, - "reasoning": 0 - }, - "output_tokens": 21, - "total_tokens": 97 - } - }, - "lc": 1, - "type": "constructor" - }, - "text": "" - } - ] - ], - "llmOutput": { - "tokenUsage": { - "completionTokens": 21, - "promptTokens": 76, - "totalTokens": 97 - } - } - }, - "metadata": { - "batch_size": 1, - "braintrust": { - "integration_name": "langchain-js", - "sdk_language": "javascript" - }, - "invocation_params": { - "max_tokens": 128, - "model": "gpt-4o-mini-2024-07-18", - "stream": false, - "temperature": 0, - "tools": [ - { - "function": { - "description": "Perform a mathematical calculation", - "name": "calculate", - "parameters": { - "$schema": "http://json-schema.org/draft-07/schema#", - "additionalProperties": false, - "properties": { - "a": { - "type": "number" - }, - "b": { - "type": "number" - }, - "operation": { - "enum": [ - "add", - "subtract", - "multiply", - "divide" - ], - "type": "string" - } - }, - "required": [ - "operation", - "a", - "b" - ], - "type": "object" - } - }, - "type": "function" - } - ] - }, - "metadata": { - "ls_integration": "langchain_chat_model", - "ls_max_tokens": 128, - "ls_model_name": "gpt-4o-mini-2024-07-18", - "ls_model_type": "chat", - "ls_provider": "openai", - "ls_temperature": 0, - "versions": { - "@langchain/core": "1.2.1", - "@langchain/openai": "1.3.0" - } - }, - "model": "gpt-4o-mini-2024-07-18", - "options": { - "tools": [ - { - "function": { - "description": "Perform a mathematical calculation", - "name": "calculate", - "parameters": { - "$schema": "http://json-schema.org/draft-07/schema#", - "additionalProperties": false, - "properties": { - "a": { - "type": "number" - }, - "b": { - "type": "number" - }, - "operation": { - "enum": [ - "add", - "subtract", - "multiply", - "divide" - ], - "type": "string" - } - }, - "required": [ - "operation", - "a", - "b" - ], - "type": "object" - } - }, - "type": "function" - } - ] - }, - "run_id": "", - "serialized": { - "id": [ - "langchain", - "chat_models", - "openai", - "ChatOpenAI" - ], - "kwargs": { - "max_tokens": 128, - "model": "gpt-4o-mini-2024-07-18", - "openai_api_key": { - "id": [ - "OPENAI_API_KEY" - ], - "lc": 1, - "type": "secret" - }, - "temperature": 0 - }, - "lc": 1, - "type": "constructor" - }, - "tags": [] - }, - "metrics": { - "completion_reasoning_tokens": 0, - "completion_tokens": 21, - "prompt_cached_tokens": 0, - "prompt_tokens": 76, - "tokens": 97 - } - }, - { - "name": "ChatOpenAI", - "type": "llm", - "children": [], - "input": [ - [ - { - "id": [ - "langchain_core", - "messages", - "HumanMessage" - ], - "kwargs": { - "additional_kwargs": {}, - "content": "What is 127 multiplied by 49? Use the calculate tool.", - "response_metadata": {} - }, - "lc": 1, - "type": "constructor" - }, - { - "id": [ - "langchain_core", - "messages", - "AIMessage" - ], - "kwargs": { - "additional_kwargs": {}, - "content": "", - "invalid_tool_calls": [], - "response_metadata": {}, - "tool_calls": [ - { - "args": { - "a": 127, - "b": 49, - "operation": "multiply" - }, - "id": "", - "name": "calculate", - "type": "tool_call" - } - ] - }, - "lc": 1, - "type": "constructor" - }, - { - "id": [ - "langchain_core", - "messages", - "ToolMessage" - ], - "kwargs": { - "additional_kwargs": {}, - "content": "6223", - "response_metadata": {}, - "tool_call_id": "call_dXQI5d9UAYuRygDqNMunj2W2" - }, - "lc": 1, - "type": "constructor" - } - ] - ], - "output": { - "generations": [ - [ - { - "generationInfo": { - "finish_reason": "stop" - }, - "message": { - "id": [ - "langchain_core", - "messages", - "AIMessage" - ], - "kwargs": { - "additional_kwargs": {}, - "content": "127 multiplied by 49 is 6223.", - "id": "", - "invalid_tool_calls": [], - "response_metadata": { - "finish_reason": "stop", - "model_name": "gpt-4o-mini-2024-07-18", - "model_provider": "openai", - "system_fingerprint": "", - "tokenUsage": { - "completionTokens": 11, - "promptTokens": 106, - "totalTokens": 117 - }, - "usage": { - "completion_tokens": 11, - "completion_tokens_details": { - "accepted_prediction_tokens": 0, - "audio_tokens": 0, - "reasoning_tokens": 0, - "rejected_prediction_tokens": 0 - }, - "prompt_tokens": 106, - "prompt_tokens_details": { - "audio_tokens": 0, - "cached_tokens": 0 - }, - "total_tokens": 117 - } - }, - "tool_calls": [], - "type": "ai", - "usage_metadata": { - "input_token_details": { - "audio": 0, - "cache_read": 0 - }, - "input_tokens": 106, - "output_token_details": { - "audio": 0, - "reasoning": 0 - }, - "output_tokens": 11, - "total_tokens": 117 - } - }, - "lc": 1, - "type": "constructor" - }, - "text": "127 multiplied by 49 is 6223." - } - ] - ], - "llmOutput": { - "tokenUsage": { - "completionTokens": 11, - "promptTokens": 106, - "totalTokens": 117 - } - } - }, - "metadata": { - "batch_size": 1, - "braintrust": { - "integration_name": "langchain-js", - "sdk_language": "javascript" - }, - "invocation_params": { - "max_tokens": 128, - "model": "gpt-4o-mini-2024-07-18", - "stream": false, - "temperature": 0, - "tools": [ - { - "function": { - "description": "Perform a mathematical calculation", - "name": "calculate", - "parameters": { - "$schema": "http://json-schema.org/draft-07/schema#", - "additionalProperties": false, - "properties": { - "a": { - "type": "number" - }, - "b": { - "type": "number" - }, - "operation": { - "enum": [ - "add", - "subtract", - "multiply", - "divide" - ], - "type": "string" - } - }, - "required": [ - "operation", - "a", - "b" - ], - "type": "object" - } - }, - "type": "function" - } - ] - }, - "metadata": { - "ls_integration": "langchain_chat_model", - "ls_max_tokens": 128, - "ls_model_name": "gpt-4o-mini-2024-07-18", - "ls_model_type": "chat", - "ls_provider": "openai", - "ls_temperature": 0, - "versions": { - "@langchain/core": "1.2.1", - "@langchain/openai": "1.3.0" - } - }, - "model": "gpt-4o-mini-2024-07-18", - "options": { - "tools": [ - { - "function": { - "description": "Perform a mathematical calculation", - "name": "calculate", - "parameters": { - "$schema": "http://json-schema.org/draft-07/schema#", - "additionalProperties": false, - "properties": { - "a": { - "type": "number" - }, - "b": { - "type": "number" - }, - "operation": { - "enum": [ - "add", - "subtract", - "multiply", - "divide" - ], - "type": "string" - } - }, - "required": [ - "operation", - "a", - "b" - ], - "type": "object" - } - }, - "type": "function" - } - ] - }, - "run_id": "", - "serialized": { - "id": [ - "langchain", - "chat_models", - "openai", - "ChatOpenAI" - ], - "kwargs": { - "max_tokens": 128, - "model": "gpt-4o-mini-2024-07-18", - "openai_api_key": { - "id": [ - "OPENAI_API_KEY" - ], - "lc": 1, - "type": "secret" - }, - "temperature": 0 - }, - "lc": 1, - "type": "constructor" - }, - "tags": [] - }, - "metrics": { - "completion_reasoning_tokens": 0, - "completion_tokens": 11, - "prompt_cached_tokens": 0, - "prompt_tokens": 106, - "tokens": 117 - } - } - ], - "metadata": { - "operation": "tool-result", - "testRunId": "" - } - } - ], - "metadata": { - "scenario": "wrap-langchain-js-traces", - "testRunId": "" - } - } - ] -} diff --git a/e2e/scenarios/wrap-langchain-js-traces/__snapshots__/wrap-langchain-js-traces-v1.span-tree.txt b/e2e/scenarios/wrap-langchain-js-traces/__snapshots__/wrap-langchain-js-traces-v1.span-tree.txt deleted file mode 100644 index 7677c0bdd..000000000 --- a/e2e/scenarios/wrap-langchain-js-traces/__snapshots__/wrap-langchain-js-traces-v1.span-tree.txt +++ /dev/null @@ -1,1450 +0,0 @@ -span_tree: -└── langchain-wrapper-root [task] - metadata: { - "scenario": "wrap-langchain-js-traces", - "testRunId": "" - } - ├── langchain-invoke-operation - │ metadata: { - │ "operation": "invoke", - │ "testRunId": "" - │ } - │ └── ChatOpenAI [llm] - │ input: [ - │ [ - │ { - │ "id": [ - │ "langchain_core", - │ "messages", - │ "HumanMessage" - │ ], - │ "kwargs": { - │ "additional_kwargs": {}, - │ "content": "Reply with exactly OK.", - │ "response_metadata": {} - │ }, - │ "lc": 1, - │ "type": "constructor" - │ } - │ ] - │ ] - │ output: { - │ "generations": [ - │ [ - │ { - │ "generationInfo": { - │ "finish_reason": "stop" - │ }, - │ "message": { - │ "id": [ - │ "langchain_core", - │ "messages", - │ "AIMessage" - │ ], - │ "kwargs": { - │ "additional_kwargs": {}, - │ "content": "OK", - │ "id": "", - │ "invalid_tool_calls": [], - │ "response_metadata": { - │ "finish_reason": "stop", - │ "model_name": "gpt-5-nano-2025-08-07", - │ "model_provider": "openai", - │ "tokenUsage": { - │ "completionTokens": 138, - │ "promptTokens": 11, - │ "totalTokens": 149 - │ } - │ }, - │ "tool_calls": [], - │ "type": "ai", - │ "usage_metadata": { - │ "input_token_details": { - │ "audio": 0, - │ "cache_read": 0 - │ }, - │ "input_tokens": 11, - │ "output_token_details": { - │ "audio": 0, - │ "reasoning": 128 - │ }, - │ "output_tokens": 138, - │ "total_tokens": 149 - │ } - │ }, - │ "lc": 1, - │ "type": "constructor" - │ }, - │ "text": "OK" - │ } - │ ] - │ ], - │ "llmOutput": { - │ "tokenUsage": { - │ "completionTokens": 138, - │ "promptTokens": 11, - │ "totalTokens": 149 - │ } - │ } - │ } - │ metadata: { - │ "batch_size": 1, - │ "braintrust": { - │ "integration_name": "langchain-js", - │ "sdk_language": "javascript" - │ }, - │ "invocation_params": { - │ "max_completion_tokens": 512, - │ "model": "gpt-5-nano", - │ "stream": false - │ }, - │ "metadata": { - │ "ls_integration": "langchain_chat_model", - │ "ls_model_name": "gpt-5-nano", - │ "ls_model_type": "chat", - │ "ls_provider": "openai", - │ "versions": { - │ "@langchain/core": "1.2.1", - │ "@langchain/openai": "1.3.0" - │ } - │ }, - │ "model": "gpt-5-nano-2025-08-07", - │ "options": {}, - │ "run_id": "", - │ "serialized": { - │ "id": [ - │ "langchain", - │ "chat_models", - │ "openai", - │ "ChatOpenAI" - │ ], - │ "kwargs": { - │ "max_tokens": 512, - │ "model": "gpt-5-nano", - │ "openai_api_key": { - │ "id": [ - │ "OPENAI_API_KEY" - │ ], - │ "lc": 1, - │ "type": "secret" - │ } - │ }, - │ "lc": 1, - │ "type": "constructor" - │ }, - │ "tags": [] - │ } - │ metrics: { - │ "completion_reasoning_tokens": 128, - │ "completion_tokens": 138, - │ "prompt_cached_tokens": 0, - │ "prompt_tokens": 11, - │ "tokens": 149 - │ } - ├── langchain-chain-operation - │ metadata: { - │ "operation": "chain", - │ "testRunId": "" - │ } - │ └── RunnableSequence [task] - │ input: { - │ "word": "PARIS" - │ } - │ output: { - │ "id": [ - │ "langchain_core", - │ "messages", - │ "AIMessage" - │ ], - │ "kwargs": { - │ "additional_kwargs": {}, - │ "content": "PARIS", - │ "id": "", - │ "invalid_tool_calls": [], - │ "response_metadata": { - │ "finish_reason": "stop", - │ "model_name": "gpt-4o-mini-2024-07-18", - │ "model_provider": "openai", - │ "system_fingerprint": "", - │ "tokenUsage": { - │ "completionTokens": 2, - │ "promptTokens": 18, - │ "totalTokens": 20 - │ }, - │ "usage": { - │ "completion_tokens": 2, - │ "completion_tokens_details": { - │ "accepted_prediction_tokens": 0, - │ "audio_tokens": 0, - │ "reasoning_tokens": 0, - │ "rejected_prediction_tokens": 0 - │ }, - │ "prompt_tokens": 18, - │ "prompt_tokens_details": { - │ "audio_tokens": 0, - │ "cached_tokens": 0 - │ }, - │ "total_tokens": 20 - │ } - │ }, - │ "tool_calls": [], - │ "type": "ai", - │ "usage_metadata": { - │ "input_token_details": { - │ "audio": 0, - │ "cache_read": 0 - │ }, - │ "input_tokens": 18, - │ "output_token_details": { - │ "audio": 0, - │ "reasoning": 0 - │ }, - │ "output_tokens": 2, - │ "total_tokens": 20 - │ } - │ }, - │ "lc": 1, - │ "type": "constructor" - │ } - │ metadata: { - │ "braintrust": { - │ "integration_name": "langchain-js", - │ "sdk_language": "javascript" - │ }, - │ "metadata": {}, - │ "run_id": "", - │ "serialized": { - │ "id": [ - │ "langchain_core", - │ "runnables", - │ "RunnableSequence" - │ ], - │ "kwargs": { - │ "first": { - │ "id": [ - │ "langchain_core", - │ "prompts", - │ "chat", - │ "ChatPromptTemplate" - │ ], - │ "kwargs": { - │ "input_variables": [ - │ "word" - │ ], - │ "messages": [ - │ { - │ "id": [ - │ "langchain_core", - │ "prompts", - │ "chat", - │ "HumanMessagePromptTemplate" - │ ], - │ "kwargs": { - │ "prompt": { - │ "id": [ - │ "langchain_core", - │ "prompts", - │ "prompt", - │ "PromptTemplate" - │ ], - │ "kwargs": { - │ "input_variables": [ - │ "word" - │ ], - │ "template": "Reply with the single word {word} and nothing else.", - │ "template_format": "f-string" - │ }, - │ "lc": 1, - │ "type": "constructor" - │ } - │ }, - │ "lc": 1, - │ "type": "constructor" - │ } - │ ] - │ }, - │ "lc": 1, - │ "type": "constructor" - │ }, - │ "last": { - │ "id": [ - │ "langchain", - │ "chat_models", - │ "openai", - │ "ChatOpenAI" - │ ], - │ "kwargs": { - │ "max_tokens": 32, - │ "model": "gpt-4o-mini-2024-07-18", - │ "openai_api_key": { - │ "id": [ - │ "OPENAI_API_KEY" - │ ], - │ "lc": 1, - │ "type": "secret" - │ }, - │ "temperature": 0 - │ }, - │ "lc": 1, - │ "type": "constructor" - │ } - │ }, - │ "lc": 1, - │ "type": "constructor" - │ }, - │ "tags": [] - │ } - │ ├── ChatPromptTemplate [task] - │ │ input: { - │ │ "word": "PARIS" - │ │ } - │ │ output: { - │ │ "id": [ - │ │ "langchain_core", - │ │ "prompt_values", - │ │ "ChatPromptValue" - │ │ ], - │ │ "kwargs": { - │ │ "messages": [ - │ │ { - │ │ "id": [ - │ │ "langchain_core", - │ │ "messages", - │ │ "HumanMessage" - │ │ ], - │ │ "kwargs": { - │ │ "additional_kwargs": {}, - │ │ "content": "Reply with the single word PARIS and nothing else.", - │ │ "response_metadata": {} - │ │ }, - │ │ "lc": 1, - │ │ "type": "constructor" - │ │ } - │ │ ] - │ │ }, - │ │ "lc": 1, - │ │ "type": "constructor" - │ │ } - │ │ metadata: { - │ │ "braintrust": { - │ │ "integration_name": "langchain-js", - │ │ "sdk_language": "javascript" - │ │ }, - │ │ "metadata": {}, - │ │ "name": "ChatPromptTemplate", - │ │ "parent_run_id": "", - │ │ "run_id": "", - │ │ "run_type": "prompt", - │ │ "serialized": { - │ │ "id": [ - │ │ "langchain_core", - │ │ "prompts", - │ │ "chat", - │ │ "ChatPromptTemplate" - │ │ ], - │ │ "kwargs": { - │ │ "input_variables": [ - │ │ "word" - │ │ ], - │ │ "messages": [ - │ │ { - │ │ "id": [ - │ │ "langchain_core", - │ │ "prompts", - │ │ "chat", - │ │ "HumanMessagePromptTemplate" - │ │ ], - │ │ "kwargs": { - │ │ "prompt": { - │ │ "id": [ - │ │ "langchain_core", - │ │ "prompts", - │ │ "prompt", - │ │ "PromptTemplate" - │ │ ], - │ │ "kwargs": { - │ │ "input_variables": [ - │ │ "word" - │ │ ], - │ │ "template": "Reply with the single word {word} and nothing else.", - │ │ "template_format": "f-string" - │ │ }, - │ │ "lc": 1, - │ │ "type": "constructor" - │ │ } - │ │ }, - │ │ "lc": 1, - │ │ "type": "constructor" - │ │ } - │ │ ] - │ │ }, - │ │ "lc": 1, - │ │ "type": "constructor" - │ │ }, - │ │ "tags": [ - │ │ "seq:step:1" - │ │ ] - │ │ } - │ └── ChatOpenAI [llm] - │ input: [ - │ [ - │ { - │ "id": [ - │ "langchain_core", - │ "messages", - │ "HumanMessage" - │ ], - │ "kwargs": { - │ "additional_kwargs": {}, - │ "content": "Reply with the single word PARIS and nothing else.", - │ "response_metadata": {} - │ }, - │ "lc": 1, - │ "type": "constructor" - │ } - │ ] - │ ] - │ output: { - │ "generations": [ - │ [ - │ { - │ "generationInfo": { - │ "finish_reason": "stop" - │ }, - │ "message": { - │ "id": [ - │ "langchain_core", - │ "messages", - │ "AIMessage" - │ ], - │ "kwargs": { - │ "additional_kwargs": {}, - │ "content": "PARIS", - │ "id": "", - │ "invalid_tool_calls": [], - │ "response_metadata": { - │ "finish_reason": "stop", - │ "model_name": "gpt-4o-mini-2024-07-18", - │ "model_provider": "openai", - │ "system_fingerprint": "", - │ "tokenUsage": { - │ "completionTokens": 2, - │ "promptTokens": 18, - │ "totalTokens": 20 - │ }, - │ "usage": { - │ "completion_tokens": 2, - │ "completion_tokens_details": { - │ "accepted_prediction_tokens": 0, - │ "audio_tokens": 0, - │ "reasoning_tokens": 0, - │ "rejected_prediction_tokens": 0 - │ }, - │ "prompt_tokens": 18, - │ "prompt_tokens_details": { - │ "audio_tokens": 0, - │ "cached_tokens": 0 - │ }, - │ "total_tokens": 20 - │ } - │ }, - │ "tool_calls": [], - │ "type": "ai", - │ "usage_metadata": { - │ "input_token_details": { - │ "audio": 0, - │ "cache_read": 0 - │ }, - │ "input_tokens": 18, - │ "output_token_details": { - │ "audio": 0, - │ "reasoning": 0 - │ }, - │ "output_tokens": 2, - │ "total_tokens": 20 - │ } - │ }, - │ "lc": 1, - │ "type": "constructor" - │ }, - │ "text": "PARIS" - │ } - │ ] - │ ], - │ "llmOutput": { - │ "tokenUsage": { - │ "completionTokens": 2, - │ "promptTokens": 18, - │ "totalTokens": 20 - │ } - │ } - │ } - │ metadata: { - │ "batch_size": 1, - │ "braintrust": { - │ "integration_name": "langchain-js", - │ "sdk_language": "javascript" - │ }, - │ "invocation_params": { - │ "max_tokens": 32, - │ "model": "gpt-4o-mini-2024-07-18", - │ "stream": false, - │ "temperature": 0 - │ }, - │ "metadata": { - │ "ls_integration": "langchain_chat_model", - │ "ls_max_tokens": 32, - │ "ls_model_name": "gpt-4o-mini-2024-07-18", - │ "ls_model_type": "chat", - │ "ls_provider": "openai", - │ "ls_temperature": 0, - │ "versions": { - │ "@langchain/core": "1.2.1", - │ "@langchain/openai": "1.3.0" - │ } - │ }, - │ "model": "gpt-4o-mini-2024-07-18", - │ "options": {}, - │ "parent_run_id": "", - │ "run_id": "", - │ "serialized": { - │ "id": [ - │ "langchain", - │ "chat_models", - │ "openai", - │ "ChatOpenAI" - │ ], - │ "kwargs": { - │ "max_tokens": 32, - │ "model": "gpt-4o-mini-2024-07-18", - │ "openai_api_key": { - │ "id": [ - │ "OPENAI_API_KEY" - │ ], - │ "lc": 1, - │ "type": "secret" - │ }, - │ "temperature": 0 - │ }, - │ "lc": 1, - │ "type": "constructor" - │ }, - │ "tags": [ - │ "seq:step:2" - │ ] - │ } - │ metrics: { - │ "completion_reasoning_tokens": 0, - │ "completion_tokens": 2, - │ "prompt_cached_tokens": 0, - │ "prompt_tokens": 18, - │ "tokens": 20 - │ } - ├── langchain-stream-operation - │ metadata: { - │ "operation": "stream", - │ "testRunId": "" - │ } - │ └── ChatOpenAI [llm] - │ input: [ - │ [ - │ { - │ "id": [ - │ "langchain_core", - │ "messages", - │ "HumanMessage" - │ ], - │ "kwargs": { - │ "additional_kwargs": {}, - │ "content": "Count from 1 to 3 and include the words one two three.", - │ "response_metadata": {} - │ }, - │ "lc": 1, - │ "type": "constructor" - │ } - │ ] - │ ] - │ output: { - │ "generations": [ - │ [ - │ { - │ "generationInfo": { - │ "completion": 0, - │ "finish_reason": "stop", - │ "model_name": "gpt-4o-mini-2024-07-18", - │ "prompt": 0, - │ "service_tier": "default", - │ "system_fingerprint": "" - │ }, - │ "message": { - │ "id": [ - │ "langchain_core", - │ "messages", - │ "AIMessageChunk" - │ ], - │ "kwargs": { - │ "additional_kwargs": {}, - │ "content": "One, two, three.", - │ "id": "", - │ "invalid_tool_calls": [], - │ "response_metadata": { - │ "completion": 0, - │ "finish_reason": "stop", - │ "model_name": "gpt-4o-mini-2024-07-18", - │ "model_provider": "openai", - │ "prompt": 0, - │ "service_tier": "default", - │ "system_fingerprint": "", - │ "usage": { - │ "completion_tokens": 6, - │ "completion_tokens_details": { - │ "accepted_prediction_tokens": 0, - │ "audio_tokens": 0, - │ "reasoning_tokens": 0, - │ "rejected_prediction_tokens": 0 - │ }, - │ "prompt_tokens": 22, - │ "prompt_tokens_details": { - │ "audio_tokens": 0, - │ "cached_tokens": 0 - │ }, - │ "total_tokens": 28 - │ } - │ }, - │ "tool_call_chunks": [], - │ "tool_calls": [], - │ "usage_metadata": { - │ "input_token_details": { - │ "audio": 0, - │ "cache_read": 0 - │ }, - │ "input_tokens": 22, - │ "output_token_details": { - │ "audio": 0, - │ "reasoning": 0 - │ }, - │ "output_tokens": 6, - │ "total_tokens": 28 - │ } - │ }, - │ "lc": 1, - │ "type": "constructor" - │ }, - │ "text": "One, two, three." - │ } - │ ] - │ ], - │ "llmOutput": { - │ "tokenUsage": { - │ "completionTokens": 6, - │ "promptTokens": 22, - │ "totalTokens": 28 - │ } - │ } - │ } - │ metadata: { - │ "batch_size": 1, - │ "braintrust": { - │ "integration_name": "langchain-js", - │ "sdk_language": "javascript" - │ }, - │ "invocation_params": { - │ "max_tokens": 32, - │ "model": "gpt-4o-mini-2024-07-18", - │ "stream": true, - │ "stream_options": { - │ "include_usage": true - │ }, - │ "temperature": 0 - │ }, - │ "metadata": { - │ "ls_integration": "langchain_chat_model", - │ "ls_max_tokens": 32, - │ "ls_model_name": "gpt-4o-mini-2024-07-18", - │ "ls_model_type": "chat", - │ "ls_provider": "openai", - │ "ls_temperature": 0, - │ "versions": { - │ "@langchain/core": "1.2.1", - │ "@langchain/openai": "1.3.0" - │ } - │ }, - │ "model": "gpt-4o-mini-2024-07-18", - │ "options": {}, - │ "run_id": "", - │ "serialized": { - │ "id": [ - │ "langchain", - │ "chat_models", - │ "openai", - │ "ChatOpenAI" - │ ], - │ "kwargs": { - │ "max_tokens": 32, - │ "model": "gpt-4o-mini-2024-07-18", - │ "openai_api_key": { - │ "id": [ - │ "OPENAI_API_KEY" - │ ], - │ "lc": 1, - │ "type": "secret" - │ }, - │ "streaming": true, - │ "temperature": 0 - │ }, - │ "lc": 1, - │ "type": "constructor" - │ }, - │ "tags": [] - │ } - │ metrics: { - │ "completion_reasoning_tokens": 0, - │ "completion_tokens": 6, - │ "prompt_cached_tokens": 0, - │ "prompt_tokens": 22, - │ "time_to_first_token": 0, - │ "tokens": 28 - │ } - ├── langchain-tool-operation - │ metadata: { - │ "operation": "tool", - │ "testRunId": "" - │ } - │ └── ChatOpenAI [llm] - │ input: [ - │ [ - │ { - │ "id": [ - │ "langchain_core", - │ "messages", - │ "HumanMessage" - │ ], - │ "kwargs": { - │ "additional_kwargs": {}, - │ "content": "Use the get_weather tool for Paris, France. Do not answer from memory.", - │ "response_metadata": {} - │ }, - │ "lc": 1, - │ "type": "constructor" - │ } - │ ] - │ ] - │ output: { - │ "generations": [ - │ [ - │ { - │ "generationInfo": { - │ "finish_reason": "tool_calls" - │ }, - │ "message": { - │ "id": [ - │ "langchain_core", - │ "messages", - │ "AIMessage" - │ ], - │ "kwargs": { - │ "additional_kwargs": { - │ "tool_calls": [ - │ { - │ "function": { - │ "arguments": "{\"location\":\"Paris, France\"}", - │ "name": "get_weather" - │ }, - │ "id": "", - │ "type": "function" - │ } - │ ] - │ }, - │ "content": "", - │ "id": "", - │ "invalid_tool_calls": [], - │ "response_metadata": { - │ "finish_reason": "tool_calls", - │ "model_name": "gpt-4o-mini-2024-07-18", - │ "model_provider": "openai", - │ "system_fingerprint": "", - │ "tokenUsage": { - │ "completionTokens": 16, - │ "promptTokens": 72, - │ "totalTokens": 88 - │ }, - │ "usage": { - │ "completion_tokens": 16, - │ "completion_tokens_details": { - │ "accepted_prediction_tokens": 0, - │ "audio_tokens": 0, - │ "reasoning_tokens": 0, - │ "rejected_prediction_tokens": 0 - │ }, - │ "prompt_tokens": 72, - │ "prompt_tokens_details": { - │ "audio_tokens": 0, - │ "cached_tokens": 0 - │ }, - │ "total_tokens": 88 - │ } - │ }, - │ "tool_calls": [ - │ { - │ "args": { - │ "location": "Paris, France" - │ }, - │ "id": "", - │ "name": "get_weather", - │ "type": "tool_call" - │ } - │ ], - │ "type": "ai", - │ "usage_metadata": { - │ "input_token_details": { - │ "audio": 0, - │ "cache_read": 0 - │ }, - │ "input_tokens": 72, - │ "output_token_details": { - │ "audio": 0, - │ "reasoning": 0 - │ }, - │ "output_tokens": 16, - │ "total_tokens": 88 - │ } - │ }, - │ "lc": 1, - │ "type": "constructor" - │ }, - │ "text": "" - │ } - │ ] - │ ], - │ "llmOutput": { - │ "tokenUsage": { - │ "completionTokens": 16, - │ "promptTokens": 72, - │ "totalTokens": 88 - │ } - │ } - │ } - │ metadata: { - │ "batch_size": 1, - │ "braintrust": { - │ "integration_name": "langchain-js", - │ "sdk_language": "javascript" - │ }, - │ "invocation_params": { - │ "max_tokens": 128, - │ "model": "gpt-4o-mini-2024-07-18", - │ "stream": false, - │ "temperature": 0, - │ "tools": [ - │ { - │ "function": { - │ "description": "Get the current weather in a given location", - │ "name": "get_weather", - │ "parameters": { - │ "$schema": "http://json-schema.org/draft-07/schema#", - │ "additionalProperties": false, - │ "properties": { - │ "location": { - │ "description": "The city and state or city and country", - │ "type": "string" - │ } - │ }, - │ "required": [ - │ "location" - │ ], - │ "type": "object" - │ } - │ }, - │ "type": "function" - │ } - │ ] - │ }, - │ "metadata": { - │ "ls_integration": "langchain_chat_model", - │ "ls_max_tokens": 128, - │ "ls_model_name": "gpt-4o-mini-2024-07-18", - │ "ls_model_type": "chat", - │ "ls_provider": "openai", - │ "ls_temperature": 0, - │ "versions": { - │ "@langchain/core": "1.2.1", - │ "@langchain/openai": "1.3.0" - │ } - │ }, - │ "model": "gpt-4o-mini-2024-07-18", - │ "options": { - │ "tools": [ - │ { - │ "function": { - │ "description": "Get the current weather in a given location", - │ "name": "get_weather", - │ "parameters": { - │ "$schema": "http://json-schema.org/draft-07/schema#", - │ "additionalProperties": false, - │ "properties": { - │ "location": { - │ "description": "The city and state or city and country", - │ "type": "string" - │ } - │ }, - │ "required": [ - │ "location" - │ ], - │ "type": "object" - │ } - │ }, - │ "type": "function" - │ } - │ ] - │ }, - │ "run_id": "", - │ "serialized": { - │ "id": [ - │ "langchain", - │ "chat_models", - │ "openai", - │ "ChatOpenAI" - │ ], - │ "kwargs": { - │ "max_tokens": 128, - │ "model": "gpt-4o-mini-2024-07-18", - │ "openai_api_key": { - │ "id": [ - │ "OPENAI_API_KEY" - │ ], - │ "lc": 1, - │ "type": "secret" - │ }, - │ "temperature": 0 - │ }, - │ "lc": 1, - │ "type": "constructor" - │ }, - │ "tags": [] - │ } - │ metrics: { - │ "completion_reasoning_tokens": 0, - │ "completion_tokens": 16, - │ "prompt_cached_tokens": 0, - │ "prompt_tokens": 72, - │ "tokens": 88 - │ } - └── langchain-tool-result-operation - metadata: { - "operation": "tool-result", - "testRunId": "" - } - ├── ChatOpenAI [llm] - │ input: [ - │ [ - │ { - │ "id": [ - │ "langchain_core", - │ "messages", - │ "HumanMessage" - │ ], - │ "kwargs": { - │ "additional_kwargs": {}, - │ "content": "What is 127 multiplied by 49? Use the calculate tool.", - │ "response_metadata": {} - │ }, - │ "lc": 1, - │ "type": "constructor" - │ } - │ ] - │ ] - │ output: { - │ "generations": [ - │ [ - │ { - │ "generationInfo": { - │ "finish_reason": "tool_calls" - │ }, - │ "message": { - │ "id": [ - │ "langchain_core", - │ "messages", - │ "AIMessage" - │ ], - │ "kwargs": { - │ "additional_kwargs": { - │ "tool_calls": [ - │ { - │ "function": { - │ "arguments": "{\"operation\":\"multiply\",\"a\":127,\"b\":49}", - │ "name": "calculate" - │ }, - │ "id": "", - │ "type": "function" - │ } - │ ] - │ }, - │ "content": "", - │ "id": "", - │ "invalid_tool_calls": [], - │ "response_metadata": { - │ "finish_reason": "tool_calls", - │ "model_name": "gpt-4o-mini-2024-07-18", - │ "model_provider": "openai", - │ "system_fingerprint": "", - │ "tokenUsage": { - │ "completionTokens": 21, - │ "promptTokens": 76, - │ "totalTokens": 97 - │ }, - │ "usage": { - │ "completion_tokens": 21, - │ "completion_tokens_details": { - │ "accepted_prediction_tokens": 0, - │ "audio_tokens": 0, - │ "reasoning_tokens": 0, - │ "rejected_prediction_tokens": 0 - │ }, - │ "prompt_tokens": 76, - │ "prompt_tokens_details": { - │ "audio_tokens": 0, - │ "cached_tokens": 0 - │ }, - │ "total_tokens": 97 - │ } - │ }, - │ "tool_calls": [ - │ { - │ "args": { - │ "a": 127, - │ "b": 49, - │ "operation": "multiply" - │ }, - │ "id": "", - │ "name": "calculate", - │ "type": "tool_call" - │ } - │ ], - │ "type": "ai", - │ "usage_metadata": { - │ "input_token_details": { - │ "audio": 0, - │ "cache_read": 0 - │ }, - │ "input_tokens": 76, - │ "output_token_details": { - │ "audio": 0, - │ "reasoning": 0 - │ }, - │ "output_tokens": 21, - │ "total_tokens": 97 - │ } - │ }, - │ "lc": 1, - │ "type": "constructor" - │ }, - │ "text": "" - │ } - │ ] - │ ], - │ "llmOutput": { - │ "tokenUsage": { - │ "completionTokens": 21, - │ "promptTokens": 76, - │ "totalTokens": 97 - │ } - │ } - │ } - │ metadata: { - │ "batch_size": 1, - │ "braintrust": { - │ "integration_name": "langchain-js", - │ "sdk_language": "javascript" - │ }, - │ "invocation_params": { - │ "max_tokens": 128, - │ "model": "gpt-4o-mini-2024-07-18", - │ "stream": false, - │ "temperature": 0, - │ "tools": [ - │ { - │ "function": { - │ "description": "Perform a mathematical calculation", - │ "name": "calculate", - │ "parameters": { - │ "$schema": "http://json-schema.org/draft-07/schema#", - │ "additionalProperties": false, - │ "properties": { - │ "a": { - │ "type": "number" - │ }, - │ "b": { - │ "type": "number" - │ }, - │ "operation": { - │ "enum": [ - │ "add", - │ "subtract", - │ "multiply", - │ "divide" - │ ], - │ "type": "string" - │ } - │ }, - │ "required": [ - │ "operation", - │ "a", - │ "b" - │ ], - │ "type": "object" - │ } - │ }, - │ "type": "function" - │ } - │ ] - │ }, - │ "metadata": { - │ "ls_integration": "langchain_chat_model", - │ "ls_max_tokens": 128, - │ "ls_model_name": "gpt-4o-mini-2024-07-18", - │ "ls_model_type": "chat", - │ "ls_provider": "openai", - │ "ls_temperature": 0, - │ "versions": { - │ "@langchain/core": "1.2.1", - │ "@langchain/openai": "1.3.0" - │ } - │ }, - │ "model": "gpt-4o-mini-2024-07-18", - │ "options": { - │ "tools": [ - │ { - │ "function": { - │ "description": "Perform a mathematical calculation", - │ "name": "calculate", - │ "parameters": { - │ "$schema": "http://json-schema.org/draft-07/schema#", - │ "additionalProperties": false, - │ "properties": { - │ "a": { - │ "type": "number" - │ }, - │ "b": { - │ "type": "number" - │ }, - │ "operation": { - │ "enum": [ - │ "add", - │ "subtract", - │ "multiply", - │ "divide" - │ ], - │ "type": "string" - │ } - │ }, - │ "required": [ - │ "operation", - │ "a", - │ "b" - │ ], - │ "type": "object" - │ } - │ }, - │ "type": "function" - │ } - │ ] - │ }, - │ "run_id": "", - │ "serialized": { - │ "id": [ - │ "langchain", - │ "chat_models", - │ "openai", - │ "ChatOpenAI" - │ ], - │ "kwargs": { - │ "max_tokens": 128, - │ "model": "gpt-4o-mini-2024-07-18", - │ "openai_api_key": { - │ "id": [ - │ "OPENAI_API_KEY" - │ ], - │ "lc": 1, - │ "type": "secret" - │ }, - │ "temperature": 0 - │ }, - │ "lc": 1, - │ "type": "constructor" - │ }, - │ "tags": [] - │ } - │ metrics: { - │ "completion_reasoning_tokens": 0, - │ "completion_tokens": 21, - │ "prompt_cached_tokens": 0, - │ "prompt_tokens": 76, - │ "tokens": 97 - │ } - └── ChatOpenAI [llm] - input: [ - [ - { - "id": [ - "langchain_core", - "messages", - "HumanMessage" - ], - "kwargs": { - "additional_kwargs": {}, - "content": "What is 127 multiplied by 49? Use the calculate tool.", - "response_metadata": {} - }, - "lc": 1, - "type": "constructor" - }, - { - "id": [ - "langchain_core", - "messages", - "AIMessage" - ], - "kwargs": { - "additional_kwargs": {}, - "content": "", - "invalid_tool_calls": [], - "response_metadata": {}, - "tool_calls": [ - { - "args": { - "a": 127, - "b": 49, - "operation": "multiply" - }, - "id": "", - "name": "calculate", - "type": "tool_call" - } - ] - }, - "lc": 1, - "type": "constructor" - }, - { - "id": [ - "langchain_core", - "messages", - "ToolMessage" - ], - "kwargs": { - "additional_kwargs": {}, - "content": "6223", - "response_metadata": {}, - "tool_call_id": "call_dXQI5d9UAYuRygDqNMunj2W2" - }, - "lc": 1, - "type": "constructor" - } - ] - ] - output: { - "generations": [ - [ - { - "generationInfo": { - "finish_reason": "stop" - }, - "message": { - "id": [ - "langchain_core", - "messages", - "AIMessage" - ], - "kwargs": { - "additional_kwargs": {}, - "content": "127 multiplied by 49 is 6223.", - "id": "", - "invalid_tool_calls": [], - "response_metadata": { - "finish_reason": "stop", - "model_name": "gpt-4o-mini-2024-07-18", - "model_provider": "openai", - "system_fingerprint": "", - "tokenUsage": { - "completionTokens": 11, - "promptTokens": 106, - "totalTokens": 117 - }, - "usage": { - "completion_tokens": 11, - "completion_tokens_details": { - "accepted_prediction_tokens": 0, - "audio_tokens": 0, - "reasoning_tokens": 0, - "rejected_prediction_tokens": 0 - }, - "prompt_tokens": 106, - "prompt_tokens_details": { - "audio_tokens": 0, - "cached_tokens": 0 - }, - "total_tokens": 117 - } - }, - "tool_calls": [], - "type": "ai", - "usage_metadata": { - "input_token_details": { - "audio": 0, - "cache_read": 0 - }, - "input_tokens": 106, - "output_token_details": { - "audio": 0, - "reasoning": 0 - }, - "output_tokens": 11, - "total_tokens": 117 - } - }, - "lc": 1, - "type": "constructor" - }, - "text": "127 multiplied by 49 is 6223." - } - ] - ], - "llmOutput": { - "tokenUsage": { - "completionTokens": 11, - "promptTokens": 106, - "totalTokens": 117 - } - } - } - metadata: { - "batch_size": 1, - "braintrust": { - "integration_name": "langchain-js", - "sdk_language": "javascript" - }, - "invocation_params": { - "max_tokens": 128, - "model": "gpt-4o-mini-2024-07-18", - "stream": false, - "temperature": 0, - "tools": [ - { - "function": { - "description": "Perform a mathematical calculation", - "name": "calculate", - "parameters": { - "$schema": "http://json-schema.org/draft-07/schema#", - "additionalProperties": false, - "properties": { - "a": { - "type": "number" - }, - "b": { - "type": "number" - }, - "operation": { - "enum": [ - "add", - "subtract", - "multiply", - "divide" - ], - "type": "string" - } - }, - "required": [ - "operation", - "a", - "b" - ], - "type": "object" - } - }, - "type": "function" - } - ] - }, - "metadata": { - "ls_integration": "langchain_chat_model", - "ls_max_tokens": 128, - "ls_model_name": "gpt-4o-mini-2024-07-18", - "ls_model_type": "chat", - "ls_provider": "openai", - "ls_temperature": 0, - "versions": { - "@langchain/core": "1.2.1", - "@langchain/openai": "1.3.0" - } - }, - "model": "gpt-4o-mini-2024-07-18", - "options": { - "tools": [ - { - "function": { - "description": "Perform a mathematical calculation", - "name": "calculate", - "parameters": { - "$schema": "http://json-schema.org/draft-07/schema#", - "additionalProperties": false, - "properties": { - "a": { - "type": "number" - }, - "b": { - "type": "number" - }, - "operation": { - "enum": [ - "add", - "subtract", - "multiply", - "divide" - ], - "type": "string" - } - }, - "required": [ - "operation", - "a", - "b" - ], - "type": "object" - } - }, - "type": "function" - } - ] - }, - "run_id": "", - "serialized": { - "id": [ - "langchain", - "chat_models", - "openai", - "ChatOpenAI" - ], - "kwargs": { - "max_tokens": 128, - "model": "gpt-4o-mini-2024-07-18", - "openai_api_key": { - "id": [ - "OPENAI_API_KEY" - ], - "lc": 1, - "type": "secret" - }, - "temperature": 0 - }, - "lc": 1, - "type": "constructor" - }, - "tags": [] - } - metrics: { - "completion_reasoning_tokens": 0, - "completion_tokens": 11, - "prompt_cached_tokens": 0, - "prompt_tokens": 106, - "tokens": 117 - } diff --git a/e2e/scenarios/wrap-langchain-js-traces/assertions.ts b/e2e/scenarios/wrap-langchain-js-traces/assertions.ts deleted file mode 100644 index aed4ce95b..000000000 --- a/e2e/scenarios/wrap-langchain-js-traces/assertions.ts +++ /dev/null @@ -1,113 +0,0 @@ -import { expect } from "vitest"; -import type { CapturedLogEvent } from "../../helpers/mock-braintrust-server"; -import { findChildSpans, findLatestSpan } from "../../helpers/trace-selectors"; - -function findNamedChildSpan( - capturedEvents: CapturedLogEvent[], - names: string[], - parentId: string | undefined, -) { - for (const name of names) { - const span = findChildSpans(capturedEvents, name, parentId)[0]; - if (span) { - return span; - } - } - - return undefined; -} - -export function assertLangchainTraces(options: { - capturedEvents: CapturedLogEvent[]; - rootName: string; - scenarioName: string; -}): CapturedLogEvent[] { - const root = findLatestSpan(options.capturedEvents, options.rootName); - const invokeOperation = findLatestSpan( - options.capturedEvents, - "langchain-invoke-operation", - ); - const chainOperation = findLatestSpan( - options.capturedEvents, - "langchain-chain-operation", - ); - const streamOperation = findLatestSpan( - options.capturedEvents, - "langchain-stream-operation", - ); - const toolOperation = findLatestSpan( - options.capturedEvents, - "langchain-tool-operation", - ); - const toolResultOperation = findLatestSpan( - options.capturedEvents, - "langchain-tool-result-operation", - ); - - expect(root).toBeDefined(); - expect(invokeOperation).toBeDefined(); - expect(chainOperation).toBeDefined(); - expect(streamOperation).toBeDefined(); - expect(toolOperation).toBeDefined(); - expect(toolResultOperation).toBeDefined(); - - expect(root?.row.metadata).toMatchObject({ - scenario: options.scenarioName, - }); - - for (const operation of [ - invokeOperation, - chainOperation, - streamOperation, - toolOperation, - toolResultOperation, - ]) { - expect(operation?.span.parentIds).toEqual([root?.span.id ?? ""]); - } - - const invokeSpan = findNamedChildSpan( - options.capturedEvents, - ["ChatOpenAI"], - invokeOperation?.span.id, - ); - expect(invokeSpan).toBeDefined(); - expect(invokeSpan?.span.type).toBe("llm"); - expect(invokeSpan?.metrics?.completion_reasoning_tokens).toBeGreaterThan(0); - - const chainChildren = findChildSpans( - options.capturedEvents, - "RunnableSequence", - chainOperation?.span.id, - ); - expect(chainChildren.length).toBeGreaterThanOrEqual(1); - - const streamSpan = findNamedChildSpan( - options.capturedEvents, - ["ChatOpenAI"], - streamOperation?.span.id, - ); - expect(streamSpan).toBeDefined(); - expect(streamSpan?.span.type).toBe("llm"); - expect(streamSpan?.metrics).toMatchObject({ - time_to_first_token: expect.any(Number), - }); - - const toolSpan = findNamedChildSpan( - options.capturedEvents, - ["ChatOpenAI"], - toolOperation?.span.id, - ); - expect(toolSpan).toBeDefined(); - - const toolOutputStr = JSON.stringify(toolSpan?.output ?? {}); - expect(toolOutputStr).toContain("get_weather"); - - const toolResultSpans = findChildSpans( - options.capturedEvents, - "ChatOpenAI", - toolResultOperation?.span.id, - ); - expect(toolResultSpans.length).toBeGreaterThanOrEqual(2); - - return options.capturedEvents; -} diff --git a/e2e/scenarios/wrap-langchain-js-traces/package.json b/e2e/scenarios/wrap-langchain-js-traces/package.json deleted file mode 100644 index 7ef2d95ee..000000000 --- a/e2e/scenarios/wrap-langchain-js-traces/package.json +++ /dev/null @@ -1,31 +0,0 @@ -{ - "name": "@braintrust/e2e-wrap-langchain-js-traces", - "private": true, - "braintrustScenario": { - "bump": { - "dependencies": { - "langchain-core-v1-latest": { - "package": "@langchain/core", - "range": "1" - }, - "langchain-openai-v1-latest": { - "package": "@langchain/openai", - "range": "1" - } - } - } - }, - "dependencies": { - "zod": "3.25.76", - "langchain-core-v1": "npm:@langchain/core@1.1.35", - "langchain-core-v1-latest": "npm:@langchain/core@1.2.1", - "langchain-openai-v1": "npm:@langchain/openai@1.3.0", - "langchain-openai-v1-latest": "npm:@langchain/openai@1.5.3" - }, - "pnpm": { - "overrides": { - "langsmith@<0.3.30": "^0.3.30", - "uuid": "^11.1.1" - } - } -} diff --git a/e2e/scenarios/wrap-langchain-js-traces/pnpm-lock.yaml b/e2e/scenarios/wrap-langchain-js-traces/pnpm-lock.yaml deleted file mode 100644 index 5dcbf1544..000000000 --- a/e2e/scenarios/wrap-langchain-js-traces/pnpm-lock.yaml +++ /dev/null @@ -1,296 +0,0 @@ -lockfileVersion: '9.0' - -settings: - autoInstallPeers: true - excludeLinksFromLockfile: false - -overrides: - langsmith@<0.3.30: ^0.3.30 - uuid: ^11.1.1 - -importers: - - .: - dependencies: - langchain-core-v1-latest: - specifier: npm:@langchain/core@1.2.1 - version: '@langchain/core@1.2.1(openai@6.45.0(zod@3.25.76))' - langchain-core-v1: - specifier: npm:@langchain/core@1.1.35 - version: '@langchain/core@1.1.35(openai@6.45.0(zod@3.25.76))' - langchain-openai-v1-latest: - specifier: npm:@langchain/openai@1.5.3 - version: '@langchain/openai@1.5.3(@langchain/core@1.2.1(openai@6.45.0(zod@3.25.76)))' - langchain-openai-v1: - specifier: npm:@langchain/openai@1.3.0 - version: '@langchain/openai@1.3.0(@langchain/core@1.2.1(openai@6.45.0(zod@3.25.76)))' - zod: - specifier: 3.25.76 - version: 3.25.76 - -packages: - - '@cfworker/json-schema@4.1.1': - resolution: {integrity: sha512-gAmrUZSGtKc3AiBL71iNWxDsyUC5uMaKKGdvzYsBoTW/xi42JQHl7eKV2OYzCUqvc+D2RCcf7EXY2iCyFIk6og==} - - '@langchain/core@1.1.35': - resolution: {integrity: sha512-TM0idLSAx17IEMqEIN7F8MDdXpN53p4A4vhhjZxf7LedwShB/8Zx1F5D5Nq1Bbn0zh9QDzSHpWyfCrTtggKf7g==} - engines: {node: '>=20'} - - '@langchain/core@1.2.1': - resolution: {integrity: sha512-NNG/cC5FGuHDOAP56h0ddp8Rfk8p+othWzEK5RV9JIG6RvnF5vGa5r0AEGtKfQieed7s1kC42GuIzVOBvMBL/g==} - engines: {node: '>=20'} - - '@langchain/openai@1.3.0': - resolution: {integrity: sha512-FDsF6xKCvFduiZcX57fL2Md+DZ+fJubcUN1iwUaEwJOQnq7zFFYj3a/KuQ7EiOFR3hEsnhPilSfxO1VW85wMZw==} - engines: {node: '>=20'} - peerDependencies: - '@langchain/core': ^1.1.33 - - '@langchain/openai@1.5.3': - resolution: {integrity: sha512-OStS2AUvy9oe/hEf/3ndBOFztUDOfuJYLNXh89m3iiJAI2Cp5Dp0n/pvpO27MO0b+VgENd+xSHVyQZ7fe+ulxg==} - engines: {node: '>=20'} - peerDependencies: - '@langchain/core': ^1.2.1 - - '@standard-schema/spec@1.1.0': - resolution: {integrity: sha512-l2aFy5jALhniG5HgqrD6jXLi/rUWrKvqN/qJx6yoJsgKhblVd+iqqU4RCXavm/jPityDo5TCvKMnpjKnOriy0w==} - - '@types/uuid@10.0.0': - resolution: {integrity: sha512-7gqG38EyHgyP1S+7+xomFtL+ZNHcKv6DwNaCZmJmo1vgMugyF3TCnXVg4t1uk89mLNwnLtnY3TpOpCOyp1/xHQ==} - - ansi-styles@5.2.0: - resolution: {integrity: sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==} - engines: {node: '>=10'} - - base64-js@1.5.1: - resolution: {integrity: sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==} - - camelcase@6.3.0: - resolution: {integrity: sha512-Gmy6FhYlCY7uOElZUSbxo2UCDH8owEk996gkbrpsgGtrJLM3J7jGxl9Ic7Qwwj4ivOE5AWZWRMecDdF7hqGjFA==} - engines: {node: '>=10'} - - chalk@5.6.2: - resolution: {integrity: sha512-7NzBL0rN6fMUW+f7A6Io4h40qQlG+xGmtMxfbnH/K7TAtt8JQWVQK+6g0UXKMeVJoyV5EkkNsErQ8pVD3bLHbA==} - engines: {node: ^12.17.0 || ^14.13 || >=16.0.0} - - console-table-printer@2.15.0: - resolution: {integrity: sha512-SrhBq4hYVjLCkBVOWaTzceJalvn5K1Zq5aQA6wXC/cYjI3frKWNPEMK3sZsJfNNQApvCQmgBcc13ZKmFj8qExw==} - - decamelize@1.2.0: - resolution: {integrity: sha512-z2S+W9X73hAUUki+N+9Za2lBlun89zigOyGrsax+KUQ6wKW4ZoWpEYBkGhQjwAjjDCkWxhY0VKEhk8wzY7F5cA==} - engines: {node: '>=0.10.0'} - - eventemitter3@4.0.7: - resolution: {integrity: sha512-8guHBZCwKnFhYdHr2ysuRWErTwhoN2X8XELRlrRwpmfeY2jjuUN4taQMsULKUVo1K4DvZl+0pgfyoysHxvmvEw==} - - js-tiktoken@1.0.21: - resolution: {integrity: sha512-biOj/6M5qdgx5TKjDnFT1ymSpM5tbd3ylwDtrQvFQSu0Z7bBYko2dF+W/aUkXUPuk6IVpRxk/3Q2sHOzGlS36g==} - - langsmith@0.5.11: - resolution: {integrity: sha512-Yio502Ow2vbVt16P1sybNMNpMsr5BMqoeonoi4flrcDsP55No/aCe2zydtBNOv0+kjKQw4WSKAzTsNwenDeD5w==} - peerDependencies: - '@opentelemetry/api': '*' - '@opentelemetry/exporter-trace-otlp-proto': '*' - '@opentelemetry/sdk-trace-base': '*' - openai: '*' - ws: '>=7' - peerDependenciesMeta: - '@opentelemetry/api': - optional: true - '@opentelemetry/exporter-trace-otlp-proto': - optional: true - '@opentelemetry/sdk-trace-base': - optional: true - openai: - optional: true - ws: - optional: true - - mustache@4.2.0: - resolution: {integrity: sha512-71ippSywq5Yb7/tVYyGbkBggbU8H3u5Rz56fH60jGFgr8uHwxs+aSKeqmluIVzM0m0kB7xQjKS6qPfd0b2ZoqQ==} - hasBin: true - - openai@6.32.0: - resolution: {integrity: sha512-j3k+BjydAf8yQlcOI7WUQMQTbbF5GEIMAE2iZYCOzwwB3S2pCheaWYp+XZRNAch4jWVc52PMDGRRjutao3lLCg==} - hasBin: true - peerDependencies: - ws: ^8.18.0 - zod: ^3.25 || ^4.0 - peerDependenciesMeta: - ws: - optional: true - zod: - optional: true - - openai@6.45.0: - resolution: {integrity: sha512-5DQVNErssk0afNpTTHUm/qZPU4iKR9OYdNid8Ib4puq4gHNNvGWZht2zY4h9a8JMF949Ik6m8gQutllVPbjdnw==} - peerDependencies: - '@aws-sdk/credential-provider-node': '>=3.972.0 <4' - '@smithy/hash-node': '>=4.3.0 <5' - '@smithy/signature-v4': '>=5.4.0 <6' - ws: ^8.18.0 - zod: ^3.25 || ^4.0 - peerDependenciesMeta: - '@aws-sdk/credential-provider-node': - optional: true - '@smithy/hash-node': - optional: true - '@smithy/signature-v4': - optional: true - ws: - optional: true - zod: - optional: true - - p-finally@1.0.0: - resolution: {integrity: sha512-LICb2p9CB7FS+0eR1oqWnHhp0FljGLZCWBE9aix0Uye9W8LTQPwMTYVGWQWIw9RdQiDg4+epXQODwIYJtSJaow==} - engines: {node: '>=4'} - - p-queue@6.6.2: - resolution: {integrity: sha512-RwFpb72c/BhQLEXIZ5K2e+AhgNVmIejGlTgiB9MzZ0e93GRvqZ7uSi0dvRF7/XIXDeNkra2fNHBxTyPDGySpjQ==} - engines: {node: '>=8'} - - p-timeout@3.2.0: - resolution: {integrity: sha512-rhIwUycgwwKcP9yTOOFK/AKsAopjjCakVqLHePO3CC6Mir1Z99xT+R63jZxAT5lFZLa2inS5h+ZS2GvR99/FBg==} - engines: {node: '>=8'} - - semver@7.7.4: - resolution: {integrity: sha512-vFKC2IEtQnVhpT78h1Yp8wzwrf8CM+MzKMHGJZfBtzhZNycRFnXsHk6E5TxIkkMsgNS7mdX3AGB7x2QM2di4lA==} - engines: {node: '>=10'} - hasBin: true - - simple-wcswidth@1.1.2: - resolution: {integrity: sha512-j7piyCjAeTDSjzTSQ7DokZtMNwNlEAyxqSZeCS+CXH7fJ4jx3FuJ/mTW3mE+6JLs4VJBbcll0Kjn+KXI5t21Iw==} - - uuid@11.1.1: - resolution: {integrity: sha512-vIYxrBCC/N/K+Js3qSN88go7kIfNPssr/hHCesKCQNAjmgvYS2oqr69kIufEG+O4+PfezOH4EbIeHCfFov8ZgQ==} - hasBin: true - - zod@3.25.76: - resolution: {integrity: sha512-gzUt/qt81nXsFGKIFcC3YnfEAx5NkunCfnDlvuBSSFS02bcXu4Lmea0AFIUwbLWxWPx3d9p8S5QoaujKcNQxcQ==} - -snapshots: - - '@cfworker/json-schema@4.1.1': {} - - '@langchain/core@1.1.35(openai@6.45.0(zod@3.25.76))': - dependencies: - '@cfworker/json-schema': 4.1.1 - '@standard-schema/spec': 1.1.0 - ansi-styles: 5.2.0 - camelcase: 6.3.0 - decamelize: 1.2.0 - js-tiktoken: 1.0.21 - langsmith: 0.5.11(openai@6.45.0(zod@3.25.76)) - mustache: 4.2.0 - p-queue: 6.6.2 - uuid: 11.1.1 - zod: 3.25.76 - transitivePeerDependencies: - - '@opentelemetry/api' - - '@opentelemetry/exporter-trace-otlp-proto' - - '@opentelemetry/sdk-trace-base' - - openai - - ws - - '@langchain/core@1.2.1(openai@6.45.0(zod@3.25.76))': - dependencies: - '@cfworker/json-schema': 4.1.1 - '@standard-schema/spec': 1.1.0 - js-tiktoken: 1.0.21 - langsmith: 0.5.11(openai@6.45.0(zod@3.25.76)) - mustache: 4.2.0 - p-queue: 6.6.2 - zod: 3.25.76 - transitivePeerDependencies: - - '@opentelemetry/api' - - '@opentelemetry/exporter-trace-otlp-proto' - - '@opentelemetry/sdk-trace-base' - - openai - - ws - - '@langchain/openai@1.3.0(@langchain/core@1.2.1(openai@6.45.0(zod@3.25.76)))': - dependencies: - '@langchain/core': 1.2.1(openai@6.45.0(zod@3.25.76)) - js-tiktoken: 1.0.21 - openai: 6.32.0(zod@3.25.76) - zod: 3.25.76 - transitivePeerDependencies: - - ws - - '@langchain/openai@1.5.3(@langchain/core@1.2.1(openai@6.45.0(zod@3.25.76)))': - dependencies: - '@langchain/core': 1.2.1(openai@6.45.0(zod@3.25.76)) - js-tiktoken: 1.0.21 - openai: 6.45.0(zod@3.25.76) - zod: 3.25.76 - transitivePeerDependencies: - - '@aws-sdk/credential-provider-node' - - '@smithy/hash-node' - - '@smithy/signature-v4' - - ws - - '@standard-schema/spec@1.1.0': {} - - '@types/uuid@10.0.0': {} - - ansi-styles@5.2.0: {} - - base64-js@1.5.1: {} - - camelcase@6.3.0: {} - - chalk@5.6.2: {} - - console-table-printer@2.15.0: - dependencies: - simple-wcswidth: 1.1.2 - - decamelize@1.2.0: {} - - eventemitter3@4.0.7: {} - - js-tiktoken@1.0.21: - dependencies: - base64-js: 1.5.1 - - langsmith@0.5.11(openai@6.45.0(zod@3.25.76)): - dependencies: - '@types/uuid': 10.0.0 - chalk: 5.6.2 - console-table-printer: 2.15.0 - p-queue: 6.6.2 - semver: 7.7.4 - uuid: 11.1.1 - optionalDependencies: - openai: 6.45.0(zod@3.25.76) - - mustache@4.2.0: {} - - openai@6.32.0(zod@3.25.76): - optionalDependencies: - zod: 3.25.76 - - openai@6.45.0(zod@3.25.76): - optionalDependencies: - zod: 3.25.76 - - p-finally@1.0.0: {} - - p-queue@6.6.2: - dependencies: - eventemitter3: 4.0.7 - p-timeout: 3.2.0 - - p-timeout@3.2.0: - dependencies: - p-finally: 1.0.0 - - semver@7.7.4: {} - - simple-wcswidth@1.1.2: {} - - uuid@11.1.1: {} - - zod@3.25.76: {} diff --git a/e2e/scenarios/wrap-langchain-js-traces/scenario.test.ts b/e2e/scenarios/wrap-langchain-js-traces/scenario.test.ts deleted file mode 100644 index db2ed6d51..000000000 --- a/e2e/scenarios/wrap-langchain-js-traces/scenario.test.ts +++ /dev/null @@ -1,76 +0,0 @@ -import { expect, test } from "vitest"; -import { resolveFileSnapshotPath } from "../../helpers/file-snapshot"; -import { - prepareScenarioDir, - readInstalledPackageVersion, - resolveScenarioDir, - withScenarioHarness, -} from "../../helpers/scenario-harness"; -import { matchSpanTreeSnapshot } from "../../helpers/span-tree"; - -import { assertLangchainTraces } from "./assertions"; - -const originalScenarioDir = resolveScenarioDir(import.meta.url); -const scenarioDir = await prepareScenarioDir({ - scenarioDir: originalScenarioDir, -}); -const TIMEOUT_MS = 90_000; -const scenarios = await Promise.all( - [ - { - coreDependencyName: "langchain-core-v1", - openAIDependencyName: "langchain-openai-v1", - variantKey: "wrap-langchain-js-traces-v1", - }, - { - coreDependencyName: "langchain-core-v1-latest", - openAIDependencyName: "langchain-openai-v1-latest", - variantKey: "wrap-langchain-js-traces-v1-latest", - }, - ].map(async (scenario) => ({ - ...scenario, - version: await readInstalledPackageVersion( - scenarioDir, - scenario.openAIDependencyName, - ), - })), -); - -for (const scenario of scenarios) { - test( - `wrap-langchain-js-traces captures invoke, chain, stream, and tool spans via BraintrustCallbackHandler (${scenario.version})`, - { - timeout: TIMEOUT_MS, - }, - async () => { - await withScenarioHarness(async ({ events, runScenarioDir }) => { - await runScenarioDir({ - scenarioDir, - timeoutMs: TIMEOUT_MS, - env: { - LANGCHAIN_CORE_PACKAGE_NAME: scenario.coreDependencyName, - LANGCHAIN_OPENAI_PACKAGE_NAME: scenario.openAIDependencyName, - }, - runContext: { - variantKey: scenario.variantKey, - originalScenarioDir, - }, - }); - - const spanTree = assertLangchainTraces({ - capturedEvents: events(), - rootName: "langchain-wrapper-root", - scenarioName: "wrap-langchain-js-traces", - }); - - await matchSpanTreeSnapshot( - spanTree, - resolveFileSnapshotPath( - import.meta.url, - `${scenario.variantKey}.span-tree.json`, - ), - ); - }); - }, - ); -} diff --git a/e2e/scenarios/wrap-langchain-js-traces/scenario.ts b/e2e/scenarios/wrap-langchain-js-traces/scenario.ts deleted file mode 100644 index ffa07bf2e..000000000 --- a/e2e/scenarios/wrap-langchain-js-traces/scenario.ts +++ /dev/null @@ -1,162 +0,0 @@ -import { BraintrustCallbackHandler } from "@braintrust/langchain-js"; -const langchainCorePackageName = - process.env.LANGCHAIN_CORE_PACKAGE_NAME ?? "langchain-core-v1-latest"; -const langchainOpenAIPackageName = - process.env.LANGCHAIN_OPENAI_PACKAGE_NAME ?? "langchain-openai-v1-latest"; -import { z } from "zod"; -import { - collectAsync, - runOperation, - runTracedScenario, -} from "../../helpers/provider-runtime.mjs"; -import { runMain } from "../../helpers/scenario-runtime"; - -const OPENAI_MODEL = "gpt-4o-mini-2024-07-18"; -const OPENAI_REASONING_MODEL = "gpt-5-nano"; - -runMain(async () => { - const { AIMessage, HumanMessage, ToolMessage } = await import( - `${langchainCorePackageName}/messages` - ); - const { ChatPromptTemplate } = await import( - `${langchainCorePackageName}/prompts` - ); - const { DynamicStructuredTool } = await import( - `${langchainCorePackageName}/tools` - ); - const { ChatOpenAI } = await import(langchainOpenAIPackageName); - - await runTracedScenario({ - callback: async () => { - // The handler attaches as a child of the current traced span via - // the parent option resolved by runTracedScenario. - const handler = new BraintrustCallbackHandler(); - - await runOperation("langchain-invoke-operation", "invoke", async () => { - const model = new ChatOpenAI({ - model: OPENAI_REASONING_MODEL, - maxTokens: 512, - callbacks: [handler], - }); - await model.invoke([new HumanMessage("Reply with exactly OK.")]); - }); - - await runOperation("langchain-chain-operation", "chain", async () => { - const model = new ChatOpenAI({ - model: OPENAI_MODEL, - maxTokens: 32, - temperature: 0, - }); - const prompt = ChatPromptTemplate.fromTemplate( - "Reply with the single word {word} and nothing else.", - ); - const chain = prompt.pipe(model); - await chain.invoke({ word: "PARIS" }, { callbacks: [handler] }); - }); - - await runOperation("langchain-stream-operation", "stream", async () => { - const model = new ChatOpenAI({ - model: OPENAI_MODEL, - maxTokens: 32, - temperature: 0, - streaming: true, - callbacks: [handler], - }); - const stream = await model.stream([ - new HumanMessage( - "Count from 1 to 3 and include the words one two three.", - ), - ]); - await collectAsync(stream); - }); - - await runOperation("langchain-tool-operation", "tool", async () => { - const getWeatherTool = new DynamicStructuredTool({ - name: "get_weather", - description: "Get the current weather in a given location", - schema: z.object({ - location: z - .string() - .describe("The city and state or city and country"), - }), - func: async ({ location }) => { - return JSON.stringify({ - condition: "sunny", - location, - temperatureC: 22, - }); - }, - }); - - const model = new ChatOpenAI({ - model: OPENAI_MODEL, - maxTokens: 128, - temperature: 0, - callbacks: [handler], - }); - const modelWithTools = model.bindTools([getWeatherTool]); - await modelWithTools.invoke( - "Use the get_weather tool for Paris, France. Do not answer from memory.", - ); - }); - - await runOperation( - "langchain-tool-result-operation", - "tool-result", - async () => { - const calculateTool = new DynamicStructuredTool({ - name: "calculate", - description: "Perform a mathematical calculation", - schema: z.object({ - operation: z.enum(["add", "subtract", "multiply", "divide"]), - a: z.number(), - b: z.number(), - }), - func: async ({ operation, a, b }) => { - const ops = { - add: a + b, - subtract: a - b, - multiply: a * b, - divide: b !== 0 ? a / b : 0, - }; - return String(ops[operation]); - }, - }); - - const model = new ChatOpenAI({ - model: OPENAI_MODEL, - maxTokens: 128, - temperature: 0, - callbacks: [handler], - }); - const modelWithTools = model.bindTools([calculateTool]); - - const firstResult = await modelWithTools.invoke( - "What is 127 multiplied by 49? Use the calculate tool.", - ); - - if (firstResult.tool_calls && firstResult.tool_calls.length > 0) { - const toolCall = firstResult.tool_calls[0]; - const result = String(127 * 49); - - await modelWithTools.invoke([ - new HumanMessage( - "What is 127 multiplied by 49? Use the calculate tool.", - ), - new AIMessage({ content: "", tool_calls: [toolCall] }), - new ToolMessage({ - content: result, - tool_call_id: toolCall.id, - }), - ]); - } - }, - ); - }, - metadata: { - scenario: "wrap-langchain-js-traces", - }, - projectNameBase: "e2e-wrap-langchain", - rootName: "langchain-wrapper-root", - }); -}); diff --git a/e2e/scripts/run-e2e-tests.mjs b/e2e/scripts/run-e2e-tests.mjs index 3c5990de5..4186a3b01 100644 --- a/e2e/scripts/run-e2e-tests.mjs +++ b/e2e/scripts/run-e2e-tests.mjs @@ -13,7 +13,7 @@ const VITEST_COMMAND = path.join( E2E_DIR, "node_modules", ".bin", - process.platform === "win32" ? "vitest.cmd" : "vitest", + process.platform === "win32" ? "vp.cmd" : "vp", ); const DEFAULT_OPENAI_CODEX_E2E_MODEL = "gpt-5.3-codex"; @@ -70,8 +70,9 @@ const reportPath = auditDir && path.join(auditDir, "results.json"); const testTargets = scenarioArgs.length > 0 ? scenarioArgs.map((arg) => scenarioPathArg(arg)) - : await defaultScenarioTestPaths(); + : [...(await defaultScenarioTestPaths()), "helpers"]; const vitestArgs = [ + "test", "run", "--run", ...testTargets, diff --git a/e2e/scripts/run-record-tests.mjs b/e2e/scripts/run-record-tests.mjs index 3a752c6b3..e0335c8ce 100644 --- a/e2e/scripts/run-record-tests.mjs +++ b/e2e/scripts/run-record-tests.mjs @@ -19,7 +19,7 @@ const VITEST_COMMAND = path.join( E2E_DIR, "node_modules", ".bin", - process.platform === "win32" ? "vitest.cmd" : "vitest", + process.platform === "win32" ? "vp.cmd" : "vp", ); const rawArgs = process.argv.slice(2).filter((arg) => arg !== "--"); @@ -116,7 +116,7 @@ async function runVitest(scenarioNames, runContextDir) { `--outputFile=${path.join(runContextDir, "results.json")}`, ); - return await runProcess(VITEST_COMMAND, vitestArgs, { + return await runProcess(VITEST_COMMAND, ["test", ...vitestArgs], { cwd: E2E_DIR, env, }); diff --git a/e2e/vitest.config.mts b/e2e/vite.config.ts similarity index 66% rename from e2e/vitest.config.mts rename to e2e/vite.config.ts index 0e89f07c3..c225346c6 100644 --- a/e2e/vitest.config.mts +++ b/e2e/vite.config.ts @@ -1,7 +1,13 @@ -import { defineConfig } from "vitest/config"; +import { defineConfig } from "vite-plus"; export default defineConfig({ test: { + // Vitest v4 compatibility: retain until the complete Harness Docker matrix + // can validate both changes. Local replay passes without clearMocks, but the + // remote Docker host cannot reach this workspace's cassette server. + // https://viteplus.dev/guide/vitest-v5#remove-unneeded-compatibility-settings + clearMocks: false, + sharedViteServer: false, reporters: ["default"], include: ["scenarios/**/*.test.ts", "helpers/**/*.test.ts"], // We run the scenarios in the hooks. diff --git a/integrations/audio/package.json b/integrations/audio/package.json index db0013f3b..bf3ce0505 100644 --- a/integrations/audio/package.json +++ b/integrations/audio/package.json @@ -23,7 +23,7 @@ "check:typings": "tsc --noEmit", "watch": "tsup --watch", "clean": "rm -r dist/*", - "test": "vitest run" + "test": "vp test run" }, "author": "Braintrust Data Inc.", "license": "MIT", @@ -36,7 +36,7 @@ "ogg-opus-decoder": "1.7.5", "tsup": "^8.5.1", "typescript": "^5.3.3", - "vitest": "4.1.11" + "vite-plus": "catalog:" }, "peerDependencies": { "braintrust": ">=3.41.0" diff --git a/integrations/audio/src/ogg.test.ts b/integrations/audio/src/ogg.test.ts index 6247fae5c..9379e0147 100644 --- a/integrations/audio/src/ogg.test.ts +++ b/integrations/audio/src/ogg.test.ts @@ -1,4 +1,4 @@ -import { expect, test } from "vitest"; +import { expect, test } from "vite-plus/test"; import oggOpus from "@audio/encode-opus"; import { OggOpusDecoder } from "ogg-opus-decoder"; import type { AudioSegmentMeta } from "./types"; @@ -59,7 +59,7 @@ async function decode(file: Uint8Array) { function pages(file: Uint8Array) { const view = new DataView(file.buffer, file.byteOffset, file.byteLength); const result: { flags: number; granule: number; crcValid: boolean }[] = []; - for (let at = 0; at < file.length; ) { + for (let at = 0; at < file.length;) { expect(new TextDecoder().decode(file.subarray(at, at + 4))).toBe("OggS"); const count = file[at + 26]; const size = file diff --git a/integrations/audio/src/opus.test.ts b/integrations/audio/src/opus.test.ts index 586791dbe..9794aefd6 100644 --- a/integrations/audio/src/opus.test.ts +++ b/integrations/audio/src/opus.test.ts @@ -1,4 +1,4 @@ -import { expect, test } from "vitest"; +import { expect, test } from "vite-plus/test"; import oggOpus from "@audio/encode-opus"; import type { AudioSegmentMeta } from "./types"; import { opus, type OpusOptions } from "./opus"; @@ -53,7 +53,7 @@ function oggPackets(file: Uint8Array) { const packets: Uint8Array[] = []; let partial: number[] = []; let granule = 0; - for (let at = 0; at < file.length; ) { + for (let at = 0; at < file.length;) { granule = Number(view.getBigUint64(at + 6, true)); const count = file[at + 26]; let data = at + 27 + count; diff --git a/integrations/audio/src/stack.test.ts b/integrations/audio/src/stack.test.ts index 8d7ab6496..015e69bf9 100644 --- a/integrations/audio/src/stack.test.ts +++ b/integrations/audio/src/stack.test.ts @@ -1,4 +1,4 @@ -import { afterEach, expect, test, vi } from "vitest"; +import { afterEach, expect, test, vi } from "vite-plus/test"; import { createStack } from "./stack"; import type { AudioMiddleware, AudioSegmentMeta } from "./types"; diff --git a/integrations/audio/tsconfig.json b/integrations/audio/tsconfig.json index ea6ad1861..2738c5b6d 100644 --- a/integrations/audio/tsconfig.json +++ b/integrations/audio/tsconfig.json @@ -2,9 +2,9 @@ "compilerOptions": { "declaration": true, "lib": ["es2022"], - "module": "commonjs", + "module": "esnext", "target": "es2022", - "moduleResolution": "node", + "moduleResolution": "bundler", "strict": true, "noUnusedLocals": true, "esModuleInterop": true, diff --git a/integrations/audio/vitest.config.ts b/integrations/audio/vite.config.ts similarity index 71% rename from integrations/audio/vitest.config.ts rename to integrations/audio/vite.config.ts index 85dceba75..73ac92cdb 100644 --- a/integrations/audio/vitest.config.ts +++ b/integrations/audio/vite.config.ts @@ -1,4 +1,4 @@ -import { defineConfig } from "vitest/config"; +import { defineConfig } from "vite-plus"; export default defineConfig({ test: { diff --git a/integrations/browser-js/package.json b/integrations/browser-js/package.json index e74dc1c1b..93facc836 100644 --- a/integrations/browser-js/package.json +++ b/integrations/browser-js/package.json @@ -8,7 +8,6 @@ "exports": { ".": { "types": "./dist/index.d.ts", - "import": "./dist/index.js", "default": "./dist/index.js" } }, @@ -17,9 +16,9 @@ "README.md" ], "scripts": { - "build": "tsup", + "build": "vp run compile", "check:typings": "tsc --noEmit", - "watch": "tsup --watch", + "watch": "vp pack --watch", "clean": "rm -rf dist", "yalc:publish": "yalc publish" }, @@ -29,8 +28,8 @@ }, "devDependencies": { "@types/node": "^20.10.5", - "tsup": "^8.5.1", - "typescript": "^5.3.3" + "typescript": "^5.3.3", + "vite-plus": "catalog:" }, "peerDependencies": { "zod": "^3.25.34 || ^4.0" @@ -51,5 +50,12 @@ "access": "public", "registry": "https://registry.npmjs.org/", "provenance": true - } + }, + "engines": { + "node": ">=22.18.0" + }, + "sideEffects": [ + "./dist/index.js" + ], + "main": "./dist/index.js" } diff --git a/integrations/browser-js/src/browser-config.ts b/integrations/browser-js/src/browser-config.ts index ddf239c33..ae9c788a6 100644 --- a/integrations/browser-js/src/browser-config.ts +++ b/integrations/browser-js/src/browser-config.ts @@ -1,41 +1,35 @@ -import { _internalIso as iso, _internalSetInitialState } from "braintrust"; +import { + configureContextManager, + ContextManager, + type ContextParentSpanIds, + type CurrentSpanStore, + type Span, +} from "braintrust"; import { AsyncLocalStorage as BrowserAsyncLocalStorage } from "als-browser"; -export function configureBrowser() { - // Set build type indicator - iso.buildType = "browser-js" as "browser"; +class BrowserContextManager extends ContextManager { + private readonly currentSpan = new BrowserAsyncLocalStorage(); - iso.newAsyncLocalStorage = () => new BrowserAsyncLocalStorage(); + getParentSpanIds(): ContextParentSpanIds | undefined { + const span = this.currentSpan.getStore(); + return span + ? { rootSpanId: span.rootSpanId, spanParents: [span.spanId] } + : undefined; + } - iso.getEnv = (name: string) => { - if (typeof process === "undefined" || typeof process.env === "undefined") { - return undefined; - } - return process.env[name]; - }; + runInContext(span: Span, callback: () => R): R { + return this.currentSpan.run(span, callback); + } - // noop implementations for git config - iso.getRepoInfo = async () => ({ - commit: null, - branch: null, - tag: null, - dirty: false, - }); - iso.getCallerLocation = () => undefined; + getCurrentSpan(): Span | undefined { + return this.currentSpan.getStore(); + } - // Implement browser-compatible hash function using a simple hash algorithm - iso.hash = (data: string): string => { - // Simple hash function for browser compatibility - let hash = 0; - for (let i = 0; i < data.length; i++) { - const char = data.charCodeAt(i); - hash = (hash << 5) - hash + char; - hash = hash & hash; // Convert to 32bit integer - } - // Convert to hex string - const hashHex = (hash >>> 0).toString(16).padStart(8, "0"); - return hashHex.repeat(8).substring(0, 64); // Make it look like a SHA-256 hash length - }; + getCurrentSpanStore(): CurrentSpanStore { + return this.currentSpan; + } +} - _internalSetInitialState(); +export function configureBrowser(): void { + configureContextManager(BrowserContextManager); } diff --git a/integrations/browser-js/tsup.config.ts b/integrations/browser-js/tsup.config.ts deleted file mode 100644 index b532d4d5c..000000000 --- a/integrations/browser-js/tsup.config.ts +++ /dev/null @@ -1,14 +0,0 @@ -import { defineConfig } from "tsup"; - -export default defineConfig({ - entry: ["src/index.ts"], - format: ["esm"], - dts: true, - sourcemap: true, - clean: true, - external: ["braintrust", "zod"], - target: "es2022", - platform: "browser", - outDir: "./dist", - treeshake: true, -}); diff --git a/integrations/browser-js/turbo.json b/integrations/browser-js/turbo.json deleted file mode 100644 index 6b90853c5..000000000 --- a/integrations/browser-js/turbo.json +++ /dev/null @@ -1,10 +0,0 @@ -{ - "$schema": "https://turbo.build/schema.json", - "extends": ["//"], - "tasks": { - "build": { - "dependsOn": ["braintrust#build"], - "outputs": ["dist/**"] - } - } -} diff --git a/integrations/browser-js/vite.config.ts b/integrations/browser-js/vite.config.ts new file mode 100644 index 000000000..4fced1cad --- /dev/null +++ b/integrations/browser-js/vite.config.ts @@ -0,0 +1,24 @@ +import { defineConfig } from "vite-plus"; + +export default defineConfig({ + run: { + tasks: { + compile: { + command: "vp pack", + dependsOn: [ + { task: "compile", from: ["dependencies", "devDependencies"] }, + ], + cache: { + input: [{ auto: true }, "!dist/**"], + untrackedEnv: ["SHELL", "BASH_ENV"], + output: ["dist/**"], + }, + }, + }, + }, + pack: { + dts: { generator: "oxc" }, + target: "es2022", + platform: "browser", + }, +}); diff --git a/integrations/deepseek-harness/.gitignore b/integrations/deepseek-harness/.gitignore new file mode 100644 index 000000000..7a6c09645 --- /dev/null +++ b/integrations/deepseek-harness/.gitignore @@ -0,0 +1 @@ +.vitest/ diff --git a/integrations/deepseek-harness/README.md b/integrations/deepseek-harness/README.md index 339c40783..f8e45adfc 100644 --- a/integrations/deepseek-harness/README.md +++ b/integrations/deepseek-harness/README.md @@ -30,8 +30,10 @@ To override the defaults, add the following entry to the profile's ``` The optional configuration fields are `apiKey`, `projectName`, `metadata`, -`orgName`, and `appUrl`. `metadata` is added to every top-level user-turn trace, -and `projectName` defaults to `DeepSeek Harness`. You can set `apiKey` -under **Settings > Plugins** in Harness; it is declared as a secret field so -the settings UI handles it as a credential. A configured `apiKey` takes -precedence over the `BRAINTRUST_API_KEY` environment variable. +`orgName`, `appUrl`, and `autoCaptureAttachments`. `metadata` is added to every +top-level user-turn trace, and `projectName` defaults to `DeepSeek Harness`. +You can set `apiKey` under **Settings > Plugins** in Harness; it is declared as +a secret field so the settings UI handles it as a credential. A configured +`apiKey` takes precedence over the `BRAINTRUST_API_KEY` environment variable. +Set `autoCaptureAttachments: true` to upload Harness images as attachments; when it +is omitted, the `BRAINTRUST_AUTO_CAPTURE_ATTACHMENTS` environment variable decides. diff --git a/integrations/deepseek-harness/package.json b/integrations/deepseek-harness/package.json index cd2fb2aad..84f3775e6 100644 --- a/integrations/deepseek-harness/package.json +++ b/integrations/deepseek-harness/package.json @@ -3,15 +3,13 @@ "version": "1.0.0", "description": "Braintrust tracing plugin for DeepSeek Harness", "main": "./dist/index.js", - "module": "./dist/index.mjs", + "module": "./dist/index.js", "types": "./dist/index.d.ts", "exports": { "./package.json": "./package.json", ".": { "types": "./dist/index.d.ts", - "import": "./dist/index.mjs", - "module": "./dist/index.mjs", - "require": "./dist/index.js" + "default": "./dist/index.js" }, "./cordis.patch.yml": "./cordis.patch.yml" }, @@ -21,11 +19,11 @@ "README.md" ], "scripts": { - "build": "tsup", + "build": "vp run compile", "check:typings": "tsc --noEmit", - "watch": "tsup --watch", + "watch": "vp pack --watch", "clean": "rm -r dist/*", - "test": "vitest run", + "test": "vp test run", "yalc:publish": "yalc publish" }, "dsh": { @@ -65,9 +63,8 @@ "@deepseek-ai/dsh-session": "0.1.0-rc.6", "@deepseek-ai/dsh-tools": "0.1.0-rc.6", "@types/node": "^22.15.21", - "tsup": "^8.5.1", "typescript": "^5.5.4", - "vitest": "4.1.11" + "vite-plus": "catalog:" }, "repository": { "type": "git", @@ -79,5 +76,10 @@ "access": "public", "registry": "https://registry.npmjs.org/", "provenance": true - } + }, + "type": "module", + "engines": { + "node": ">=22.18.0" + }, + "sideEffects": false } diff --git a/integrations/deepseek-harness/src/index.test.ts b/integrations/deepseek-harness/src/index.test.ts index 6d2c6dcf8..5f4e00790 100644 --- a/integrations/deepseek-harness/src/index.test.ts +++ b/integrations/deepseek-harness/src/index.test.ts @@ -1,4 +1,4 @@ -import { beforeEach, describe, expect, test, vi } from "vitest"; +import { beforeEach, describe, expect, test, vi } from "vite-plus/test"; const mock = vi.hoisted(() => { let nextId = 0; @@ -49,10 +49,12 @@ const mock = vi.hoisted(() => { flush: vi.fn(async () => undefined), }; const initLogger = vi.fn(() => logger); + const captureAttachmentsEnabled = vi.fn(() => true); return { Attachment: MockAttachment, attachments, + captureAttachmentsEnabled, initLogger, logger, reset() { @@ -61,12 +63,14 @@ const mock = vi.hoisted(() => { spans.length = 0; initLogger.mockClear(); logger.flush.mockClear(); + captureAttachmentsEnabled.mockReturnValue(true); }, spans, }; }); vi.mock("braintrust", () => ({ + _internalCaptureAttachmentsEnabled: mock.captureAttachmentsEnabled, Attachment: mock.Attachment, initLogger: mock.initLogger, NOOP_SPAN: {}, @@ -148,6 +152,14 @@ describe("DeepSeek Harness plugin", () => { expect(ConfigSchema.dict?.apiKey?.meta.role).toBe("secret"); }); + test("passes the attachment capture setting to its logger", () => { + createContext({ autoCaptureAttachments: true }); + + expect(mock.initLogger).toHaveBeenCalledWith( + expect.objectContaining({ autoCaptureAttachments: true }), + ); + }); + test("only logs the supported tool definition fields", async () => { const harness = createContext(); const stream = harness.listener("llm/stream")( @@ -228,88 +240,104 @@ describe("DeepSeek Harness plugin", () => { } }); - test("converts Harness image references to Braintrust attachments", async () => { - const harness = createContext(); - const currentSession = session(); - const image = { - attachmentId: "image-1", - mediaType: "image/png", - bytes: 3, - width: 1, - height: 1, - name: "diagram.png", - }; - const message = { - role: "user", - content: [ - { type: "text", text: "describe this" }, - { type: "image", attachment: image }, - ], - source: { kind: "user" }, - }; - - harness.listener("session/event")(currentSession, { - type: "turn/start", - data: { turn: 1 }, - }); - harness.listener("session/event")(currentSession, { - type: "user/message", - data: message, - }); - await consume( - harness.listener("llm/stream")( - { - provider: "replay", - model: "replay-model", - messages: [message], - sessionId: currentSession.id, - }, - () => - (async function* () { - yield { type: "finish", reason: { kind: "stop" } }; - })(), - ), - ); - await harness.listener("session/flush")(currentSession); - - expect(harness.readImage).toHaveBeenCalledOnce(); - expect(harness.readImage).toHaveBeenCalledWith(image); - expect(mock.attachments).toHaveLength(1); - expect(mock.attachments[0]?.params).toMatchObject({ - filename: "diagram.png", - contentType: "image/png", - }); - expect(new Uint8Array(mock.attachments[0]?.params.data ?? [])).toEqual( - new Uint8Array([1, 2, 3]), - ); + test.each([true, false])( + "processes Harness images with capture=%s", + async (autoCaptureAttachments) => { + mock.captureAttachmentsEnabled.mockReturnValue(autoCaptureAttachments); + const harness = createContext(); + const currentSession = session(); + const image = { + attachmentId: "image-1", + mediaType: "image/png", + bytes: 3, + width: 1, + height: 1, + name: "diagram.png", + }; + const message = { + role: "user", + content: [ + { type: "text", text: "describe this" }, + { type: "image", attachment: image }, + ], + source: { kind: "user" }, + }; - const expectedImagePart = { - type: "image_url", - image_url: { url: mock.attachments[0] }, - }; - const turn = mock.spans.find( - (span) => span.args.name === "deepseek_harness.turn", - ); - const model = mock.spans.find( - (span) => span.args.name === "deepseek_harness.step", - ); - expect(turn?.logs).toContainEqual({ - input: [ - { - role: "user", - content: [{ type: "text", text: "describe this" }, expectedImagePart], - }, - ], - }); - expect(model?.logs).toContainEqual({ - input: [ - { - role: "user", - content: [{ type: "text", text: "describe this" }, expectedImagePart], - }, - ], - }); - }); + harness.listener("session/event")(currentSession, { + type: "turn/start", + data: { turn: 1 }, + }); + harness.listener("session/event")(currentSession, { + type: "user/message", + data: message, + }); + await consume( + harness.listener("llm/stream")( + { + provider: "replay", + model: "replay-model", + messages: [message], + sessionId: currentSession.id, + }, + () => + (async function* () { + yield { type: "finish", reason: { kind: "stop" } }; + })(), + ), + ); + await harness.listener("session/flush")(currentSession); + + let expectedImagePart: Record; + if (autoCaptureAttachments) { + expect(harness.readImage).toHaveBeenCalledOnce(); + expect(harness.readImage).toHaveBeenCalledWith(image); + expect(mock.attachments).toHaveLength(1); + expect(mock.attachments[0]?.params).toMatchObject({ + filename: "diagram.png", + contentType: "image/png", + }); + expect(new Uint8Array(mock.attachments[0]?.params.data ?? [])).toEqual( + new Uint8Array([1, 2, 3]), + ); + expectedImagePart = { + type: "image_url", + image_url: { url: mock.attachments[0] }, + }; + } else { + expect(harness.readImage).not.toHaveBeenCalled(); + expect(mock.attachments).toHaveLength(0); + expectedImagePart = { type: "image", attachment: image }; + } + const turn = mock.spans.find( + (span) => span.args.name === "deepseek_harness.turn", + ); + const model = mock.spans.find( + (span) => span.args.name === "deepseek_harness.step", + ); + expect(turn?.logs).toContainEqual({ + input: [ + { + role: "user", + content: [ + { type: "text", text: "describe this" }, + expectedImagePart, + ], + }, + ], + }); + expect(model?.logs).toContainEqual({ + input: [ + { + role: "user", + content: [ + { type: "text", text: "describe this" }, + expectedImagePart, + ], + }, + ], + }); + }, + ); test("preserves Harness image references when attachment conversion fails", async () => { const harness = createContext(); diff --git a/integrations/deepseek-harness/src/index.ts b/integrations/deepseek-harness/src/index.ts index e790a35d8..cce1f6c4a 100644 --- a/integrations/deepseek-harness/src/index.ts +++ b/integrations/deepseek-harness/src/index.ts @@ -7,6 +7,7 @@ import type {} from "@deepseek-ai/dsh-session"; import type {} from "@deepseek-ai/dsh-tools"; import z from "@deepseek-ai/schemastery"; import { + _internalCaptureAttachmentsEnabled, Attachment, initLogger, NOOP_SPAN, @@ -36,7 +37,7 @@ const INSTRUMENTATION_SYMBOL = Symbol.for("braintrust.spanInstrumentationName"); export const name = "braintrust"; /** Harness services whose event seams this plugin instruments. */ -export const inject = ["sessions", "llm", "tools"]; +export const inject: string[] = ["sessions", "llm", "tools"]; export interface Config { /** Braintrust API key. Falls back to BRAINTRUST_API_KEY when omitted. */ @@ -49,6 +50,11 @@ export interface Config { orgName?: string; /** Optional Braintrust deployment URL. */ appUrl?: string; + /** + * Capture inline Harness images as attachments. + * Falls back to BRAINTRUST_AUTO_CAPTURE_ATTACHMENTS when omitted. + */ + autoCaptureAttachments?: boolean; } export const Config: z = z.object({ @@ -57,6 +63,7 @@ export const Config: z = z.object({ metadata: z.dict(z.any()), orgName: z.string(), appUrl: z.string(), + autoCaptureAttachments: z.boolean(), }); type TokenMetrics = Record & { @@ -373,6 +380,7 @@ export function apply(ctx: Context, config: Config = {}): void { projectName: config.projectName ?? DEFAULT_PROJECT_NAME, ...(config.orgName ? { orgName: config.orgName } : {}), ...(config.appUrl ? { appUrl: config.appUrl } : {}), + autoCaptureAttachments: config.autoCaptureAttachments, setCurrent: false, }); const turns = new Map>(); @@ -394,6 +402,7 @@ export function apply(ctx: Context, config: Config = {}): void { const resolveImage = async ( ref: HarnessImageAttachmentRef, ): Promise => { + if (!_internalCaptureAttachmentsEnabled(logger)) return undefined; const attachmentStore = ( ctx as Context & { attachments?: HarnessAttachmentStore } ).attachments; diff --git a/integrations/deepseek-harness/tsconfig.json b/integrations/deepseek-harness/tsconfig.json index ea6ad1861..72b024bb4 100644 --- a/integrations/deepseek-harness/tsconfig.json +++ b/integrations/deepseek-harness/tsconfig.json @@ -2,14 +2,14 @@ "compilerOptions": { "declaration": true, "lib": ["es2022"], - "module": "commonjs", + "module": "esnext", "target": "es2022", - "moduleResolution": "node", + "moduleResolution": "bundler", "strict": true, "noUnusedLocals": true, "esModuleInterop": true, "skipLibCheck": true }, - "include": ["."], + "include": ["src"], "exclude": ["node_modules/**", "**/dist/**"] } diff --git a/integrations/deepseek-harness/tsup.config.ts b/integrations/deepseek-harness/tsup.config.ts deleted file mode 100644 index 7d69621aa..000000000 --- a/integrations/deepseek-harness/tsup.config.ts +++ /dev/null @@ -1,10 +0,0 @@ -import { defineConfig } from "tsup"; - -export default defineConfig([ - { - entry: ["src/index.ts"], - format: ["cjs", "esm"], - outDir: "dist", - dts: true, - }, -]); diff --git a/integrations/deepseek-harness/vite.config.ts b/integrations/deepseek-harness/vite.config.ts new file mode 100644 index 000000000..aa7f69ca2 --- /dev/null +++ b/integrations/deepseek-harness/vite.config.ts @@ -0,0 +1,24 @@ +import { defineConfig } from "vite-plus"; + +export default defineConfig({ + run: { + tasks: { + compile: { + command: "vp pack", + dependsOn: [ + { task: "compile", from: ["dependencies", "devDependencies"] }, + ], + cache: { + input: [{ auto: true }, "!dist/**"], + untrackedEnv: ["SHELL", "BASH_ENV"], + output: ["dist/**"], + }, + }, + }, + }, + pack: { + dts: { generator: "oxc" }, + sourcemap: true, + fixedExtension: false, + }, +}); diff --git a/integrations/langchain-js/CHANGELOG.md b/integrations/langchain-js/CHANGELOG.md deleted file mode 100644 index 2ab7af78b..000000000 --- a/integrations/langchain-js/CHANGELOG.md +++ /dev/null @@ -1,31 +0,0 @@ -# @braintrust/langchain-js - -## 0.2.5 - -- `@braintrust/langchain-js` is now deprecated. This release marks the last release for this package. (https://github.com/braintrustdata/braintrust-sdk-javascript/pull/2383) -- Updated dependencies: braintrust@3.29.0 - -### Migration - -To migrate, install `braintrust` and import `BraintrustLangChainCallbackHandler` -from `braintrust` instead. See the -[LangChain integration guide](https://www.braintrust.dev/docs/integrations/sdk-integrations/langchain) -for setup examples. For application-wide instrumentation, use the Braintrust -runtime hook described in the -[auto-instrumentation guide](https://www.braintrust.dev/docs/instrument/trace-llm-calls#auto-instrumentation). -`setGlobalHandler` has no direct replacement; use automatic instrumentation or -pass the callback handler explicitly. - -## 0.2.4 - -### Patch Changes - -- feat: Add LangChain and LangGraph auto-instrumentation (https://github.com/braintrustdata/braintrust-sdk-javascript/pull/1897) -- Updated dependencies: braintrust@3.12.0 - -## 0.2.3 - -### Patch Changes - -- Added prompt caching token tracking for LangChain usage metadata. -- Mapped nested `input_token_details` metrics into Braintrust's standard cached-token fields, including cache reads and cache creation tokens. diff --git a/integrations/langchain-js/README.md b/integrations/langchain-js/README.md deleted file mode 100644 index 93f716031..000000000 --- a/integrations/langchain-js/README.md +++ /dev/null @@ -1,59 +0,0 @@ -# @braintrust/langchain-js - -[![npm version](https://img.shields.io/npm/v/%40braintrust%2Flangchain-js.svg)](https://www.npmjs.com/package/@braintrust/langchain-js) - -> [!WARNING] -> This package and all of its exports are deprecated and will stop being -> published after the next release. Use the LangChain integration in -> `braintrust` instead. - -## Installation - -```bash -npm install braintrust -# or -yarn add braintrust -# or -pnpm add braintrust -``` - -## Automatic instrumentation - -For application-wide instrumentation, use the Braintrust runtime hook: - -```bash -node --import braintrust/hook.mjs app.js -``` - -Braintrust also supports Vite, Webpack, esbuild, and Rollup. See the -[Braintrust SDK documentation](../../js/README.md#auto-instrumentation) for -bundler setup. - -## Manual instrumentation - -For scoped instrumentation, pass `BraintrustLangChainCallbackHandler` from -`braintrust` through LangChain's `callbacks` option: - -```typescript -import { ChatOpenAI } from "@langchain/openai"; -import { BraintrustLangChainCallbackHandler } from "braintrust"; - -const handler = new BraintrustLangChainCallbackHandler(); - -const model = new ChatOpenAI({ - callbacks: [handler], -}); - -const response = await model.invoke("Tell me a joke about bears", { - callbacks: [handler], -}); -``` - -`setGlobalHandler` does not have a direct replacement in `braintrust`. Use -automatic instrumentation for application-wide coverage, or pass the handler -explicitly for scoped instrumentation. - -## Documentation - -- Braintrust docs: [https://www.braintrust.dev/docs](https://www.braintrust.dev/docs) -- LangChain callback docs: [https://js.langchain.com/docs/how_to/#callbacks](https://js.langchain.com/docs/how_to/#callbacks) diff --git a/integrations/langchain-js/package.json b/integrations/langchain-js/package.json deleted file mode 100644 index 2664255e8..000000000 --- a/integrations/langchain-js/package.json +++ /dev/null @@ -1,64 +0,0 @@ -{ - "name": "@braintrust/langchain-js", - "version": "0.2.5", - "description": "SDK for integrating Braintrust with LangChain.js", - "main": "./dist/index.js", - "module": "./dist/index.mjs", - "types": "./dist/index.d.ts", - "exports": { - "./package.json": "./package.json", - ".": { - "types": "./dist/index.d.ts", - "import": "./dist/index.mjs", - "module": "./dist/index.mjs", - "require": "./dist/index.js" - } - }, - "scripts": { - "build": "tsup", - "check:typings": "tsc --noEmit", - "watch": "tsup --watch", - "clean": "rm -r dist/*", - "test": "vitest run", - "yalc:publish": "yalc publish" - }, - "author": "Braintrust Data Inc.", - "license": "MIT", - "devDependencies": { - "@langchain/anthropic": "^1.3.1", - "@langchain/core": "^1.1.6", - "@langchain/langgraph": "^1.0.7", - "@langchain/openai": "^1.2.0", - "@types/node": "^20.10.5", - "braintrust": "workspace:*", - "msw": "^2.6.6", - "tsup": "^8.5.1", - "typescript": "^5.3.3", - "vitest": "4.1.11", - "zod": "^3.25.34", - "zod-to-json-schema": "^3.22.5" - }, - "peerDependencies": { - "@langchain/core": ">=0.3.42", - "braintrust": ">=0.4.0" - }, - "peerDependenciesMeta": { - "braintrust": { - "optional": false - }, - "@langchain/core": { - "optional": false - } - }, - "repository": { - "type": "git", - "url": "git+https://github.com/braintrustdata/braintrust-sdk-javascript.git", - "directory": "integrations/langchain-js" - }, - "homepage": "https://www.braintrust.dev/docs", - "publishConfig": { - "access": "public", - "registry": "https://registry.npmjs.org/", - "provenance": true - } -} diff --git a/integrations/langchain-js/src/BraintrustCallbackHandler.fixtures.ts b/integrations/langchain-js/src/BraintrustCallbackHandler.fixtures.ts deleted file mode 100644 index f7ec70434..000000000 --- a/integrations/langchain-js/src/BraintrustCallbackHandler.fixtures.ts +++ /dev/null @@ -1,234 +0,0 @@ -export const CHAT_MATH = { - id: "chatcmpl-Aao716hWOt9HBihjWh9iAPGWRpkFd", - object: "chat.completion", - created: 1733335803, - model: "gpt-4o-mini-2024-07-18", - choices: [ - { - index: 0, - message: { - role: "assistant", - content: "1 + 2 equals 3.", - refusal: null, - }, - logprobs: null, - finish_reason: "stop", - }, - ], - usage: { - prompt_tokens: 15, - completion_tokens: 8, - total_tokens: 23, - prompt_tokens_details: { - cached_tokens: 0, - audio_tokens: 0, - }, - completion_tokens_details: { - reasoning_tokens: 0, - audio_tokens: 0, - accepted_prediction_tokens: 0, - rejected_prediction_tokens: 0, - }, - }, - system_fingerprint: "fp_0705bf87c0", -}; - -export const CHAT_STREAM_PARROT = [ - `data: {"id":"chatcmpl-Ab9p7esnOlnH4ywBeHQOp4ScoCcde","object":"chat.completion.chunk","created":1733419261,"model":"gpt-4o-mini-2024-07-18","system_fingerprint":"fp_0705bf87c0","choices":[{"index":0,"delta":{"role":"assistant","content":"","refusal":null},"logprobs":null,"finish_reason":null}],"usage":null}`, - `data: {"id":"chatcmpl-Ab9p7esnOlnH4ywBeHQOp4ScoCcde","object":"chat.completion.chunk","created":1733419261,"model":"gpt-4o-mini-2024-07-18","system_fingerprint":"fp_0705bf87c0","choices":[{"index":0,"delta":{"content":"Pol"},"logprobs":null,"finish_reason":null}],"usage":null}`, - `data: {"id":"chatcmpl-Ab9p7esnOlnH4ywBeHQOp4ScoCcde","object":"chat.completion.chunk","created":1733419261,"model":"gpt-4o-mini-2024-07-18","system_fingerprint":"fp_0705bf87c0","choices":[{"index":0,"delta":{"content":"ly"},"logprobs":null,"finish_reason":null}],"usage":null}`, - `data: {"id":"chatcmpl-Ab9p7esnOlnH4ywBeHQOp4ScoCcde","object":"chat.completion.chunk","created":1733419261,"model":"gpt-4o-mini-2024-07-18","system_fingerprint":"fp_0705bf87c0","choices":[{"index":0,"delta":{"content":" wants"},"logprobs":null,"finish_reason":null}],"usage":null}`, - `data: {"id":"chatcmpl-Ab9p7esnOlnH4ywBeHQOp4ScoCcde","object":"chat.completion.chunk","created":1733419261,"model":"gpt-4o-mini-2024-07-18","system_fingerprint":"fp_0705bf87c0","choices":[{"index":0,"delta":{"content":" more"},"logprobs":null,"finish_reason":null}],"usage":null}`, - `data: {"id":"chatcmpl-Ab9p7esnOlnH4ywBeHQOp4ScoCcde","object":"chat.completion.chunk","created":1733419261,"model":"gpt-4o-mini-2024-07-18","system_fingerprint":"fp_0705bf87c0","choices":[{"index":0,"delta":{"content":" crackers"},"logprobs":null,"finish_reason":null}],"usage":null}`, - `data: {"id":"chatcmpl-Ab9p7esnOlnH4ywBeHQOp4ScoCcde","object":"chat.completion.chunk","created":1733419261,"model":"gpt-4o-mini-2024-07-18","system_fingerprint":"fp_0705bf87c0","choices":[{"index":0,"delta":{"content":"!"},"logprobs":null,"finish_reason":null}],"usage":null}`, - `data: {"id":"chatcmpl-Ab9p7esnOlnH4ywBeHQOp4ScoCcde","object":"chat.completion.chunk","created":1733419261,"model":"gpt-4o-mini-2024-07-18","system_fingerprint":"fp_0705bf87c0","choices":[{"index":0,"delta":{},"logprobs":null,"finish_reason":"stop"}],"usage":null}`, - `data: {"id":"chatcmpl-Ab9p7esnOlnH4ywBeHQOp4ScoCcde","object":"chat.completion.chunk","created":1733419261,"model":"gpt-4o-mini-2024-07-18","system_fingerprint":"fp_0705bf87c0","choices":[],"usage":{"prompt_tokens":16,"completion_tokens":6,"total_tokens":22,"prompt_tokens_details":{"cached_tokens":0,"audio_tokens":0},"completion_tokens_details":{"reasoning_tokens":0,"audio_tokens":0,"accepted_prediction_tokens":0,"rejected_prediction_tokens":0}}}`, - `data: [DONE]`, -]; - -export const CHAT_CHAIN_MEMORY = { - id: "chatcmpl-AbAHIqtiUXMz849pZPWxB7RKF9wPh", - object: "chat.completion", - created: 1733421008, - model: "gpt-4o-mini-2024-07-18", - choices: [ - { - index: 0, - message: { - role: "assistant", - content: "Assistant: I'm called Assistant! How can I help you today?", - refusal: null, - }, - logprobs: null, - finish_reason: "stop", - }, - ], - usage: { - prompt_tokens: 24, - completion_tokens: 13, - total_tokens: 37, - prompt_tokens_details: { - cached_tokens: 0, - audio_tokens: 0, - }, - completion_tokens_details: { - reasoning_tokens: 0, - audio_tokens: 0, - accepted_prediction_tokens: 0, - rejected_prediction_tokens: 0, - }, - }, - system_fingerprint: "fp_0705bf87c0", -}; - -export const CHAT_TOOL_CALCULATOR = { - id: "chatcmpl-AbAVR1TojvbDgXRLlDyhz9NYZVitz", - object: "chat.completion", - created: 1733421885, - model: "gpt-4o-mini-2024-07-18", - choices: [ - { - index: 0, - message: { - role: "assistant", - content: null, - tool_calls: [ - { - id: "call_G2Qd8HzTMyFUiMafz5H4fBIi", - type: "function", - function: { - name: "calculator", - arguments: '{"operation":"multiply","number1":3,"number2":12}', - }, - }, - ], - refusal: null, - }, - logprobs: null, - finish_reason: "tool_calls", - }, - ], - usage: { - prompt_tokens: 93, - completion_tokens: 24, - total_tokens: 117, - prompt_tokens_details: { - cached_tokens: 0, - audio_tokens: 0, - }, - completion_tokens_details: { - reasoning_tokens: 0, - audio_tokens: 0, - accepted_prediction_tokens: 0, - rejected_prediction_tokens: 0, - }, - }, - system_fingerprint: "fp_0705bf87c0", -}; - -export const CHAT_BEAR_JOKE = { - id: "chatcmpl-AbCj2kznx4QsGpaocNir4GWdLYYqj", - object: "chat.completion", - created: 1733430416, - model: "gpt-4o-mini-2024-07-18", - choices: [ - { - index: 0, - message: { - role: "assistant", - content: - 'Why did the bear sit on the log?\n\nBecause it wanted to be a "bear-ly" seated customer! 🐻', - refusal: null, - }, - logprobs: null, - finish_reason: "stop", - }, - ], - usage: { - prompt_tokens: 13, - completion_tokens: 26, - total_tokens: 39, - prompt_tokens_details: { - cached_tokens: 0, - audio_tokens: 0, - }, - completion_tokens_details: { - reasoning_tokens: 0, - audio_tokens: 0, - accepted_prediction_tokens: 0, - rejected_prediction_tokens: 0, - }, - }, - system_fingerprint: "fp_bba3c8e70b", -}; - -export const CHAT_BEAR_POEM = { - id: "chatcmpl-AbClwtnbeqLRiWwoe21On10TRqqsW", - object: "chat.completion", - created: 1733430596, - model: "gpt-4o-mini-2024-07-18", - choices: [ - { - index: 0, - message: { - role: "assistant", - content: - "In the forest's hush, a shadow moves near, \nA gentle giant roams, the wise old bear.", - refusal: null, - }, - logprobs: null, - finish_reason: "stop", - }, - ], - usage: { - prompt_tokens: 15, - completion_tokens: 23, - total_tokens: 38, - prompt_tokens_details: { - cached_tokens: 0, - audio_tokens: 0, - }, - completion_tokens_details: { - reasoning_tokens: 0, - audio_tokens: 0, - accepted_prediction_tokens: 0, - rejected_prediction_tokens: 0, - }, - }, - system_fingerprint: "fp_bba3c8e70b", -}; - -export const CHAT_SAY_HELLO = { - id: "chatcmpl-AbFX2khtngBETl7qtntuXawF6RFPt", - object: "chat.completion", - created: 1733441204, - model: "gpt-4o-mini-2024-07-18", - choices: [ - { - index: 0, - message: { - role: "assistant", - content: "Hello! How can I assist you today?", - refusal: null, - }, - logprobs: null, - finish_reason: "stop", - }, - ], - usage: { - prompt_tokens: 9, - completion_tokens: 9, - total_tokens: 18, - prompt_tokens_details: { - cached_tokens: 0, - audio_tokens: 0, - }, - completion_tokens_details: { - reasoning_tokens: 0, - audio_tokens: 0, - accepted_prediction_tokens: 0, - rejected_prediction_tokens: 0, - }, - }, - system_fingerprint: "fp_818c284075", -}; diff --git a/integrations/langchain-js/src/BraintrustCallbackHandler.test.ts b/integrations/langchain-js/src/BraintrustCallbackHandler.test.ts deleted file mode 100644 index e45afe2e6..000000000 --- a/integrations/langchain-js/src/BraintrustCallbackHandler.test.ts +++ /dev/null @@ -1,1239 +0,0 @@ -import { ChatAnthropic } from "@langchain/anthropic"; -import { HumanMessage, SystemMessage } from "@langchain/core/messages"; -import { ChatPromptTemplate, PromptTemplate } from "@langchain/core/prompts"; -import { RunnableMap } from "@langchain/core/runnables"; -import { tool } from "@langchain/core/tools"; -import { Annotation, END, START, StateGraph } from "@langchain/langgraph"; -import { ChatOpenAI } from "@langchain/openai"; -import { flush, initLogger, NOOP_SPAN } from "braintrust"; -import { http, HttpResponse } from "msw"; -import { ReadableStream } from "stream/web"; -import { describe, expect, it } from "vitest"; -import { z } from "zod/v3"; - -import { BraintrustCallbackHandler } from "./BraintrustCallbackHandler"; -import { - CHAT_BEAR_JOKE, - CHAT_BEAR_POEM, - CHAT_CHAIN_MEMORY, - CHAT_MATH, - CHAT_SAY_HELLO, - CHAT_STREAM_PARROT, - CHAT_TOOL_CALCULATOR, -} from "./BraintrustCallbackHandler.fixtures"; -import { server } from "./test/setup"; -import { LogsRequest } from "./test/types"; -import { logsToSpans, withLogging } from "./test/utils"; - -initLogger({ - projectName: "langchain", -}); - -const handler = withLogging(new BraintrustCallbackHandler({ debug: true })); - -const encoder = new TextEncoder(); - -describe("BraintrustCallbackHandler", () => { - it("should handle LLM calls", async () => { - const logs: LogsRequest[] = []; - - server.use( - http.post("https://api.openai.com/v1/chat/completions", () => { - return HttpResponse.json(CHAT_MATH); - }), - - http.post(/.+logs/, async ({ request }) => { - // eslint-disable-next-line @typescript-eslint/consistent-type-assertions - logs.push((await request.json()) as LogsRequest); - return HttpResponse.json(["4bc6305f-2175-4481-bc84-7c55a456b7ea"]); - }), - ); - - const prompt = ChatPromptTemplate.fromTemplate(`What is 1 + {number}?`); - const model = new ChatOpenAI({ - model: "gpt-4o-mini-2024-07-18", - }); - - const chain = prompt.pipe(model); - - const message = await chain.invoke( - { number: "2" }, - { callbacks: [handler] }, - ); - - await flush(); - - const { spans, trace_id, root_span_id } = logsToSpans(logs); - - expect(spans).toMatchObject([ - { - span_id: root_span_id, - root_span_id: trace_id, - span_attributes: { - name: "RunnableSequence", - type: "task", - }, - input: { - number: "2", - }, - metadata: { - tags: [], - }, - output: { - kwargs: { - content: expect.any(String), // LLM response text - additional_kwargs: expect.any(Object), - response_metadata: expect.any(Object), - tool_calls: expect.any(Array), - invalid_tool_calls: expect.any(Array), - usage_metadata: expect.any(Object), - }, - }, - }, - { - span_attributes: { - name: "ChatPromptTemplate", - }, - input: { - number: "2", - }, - metadata: { - tags: ["seq:step:1"], - }, - output: { - kwargs: { - messages: [ - { - kwargs: { - content: expect.any(String), // Formatted prompt text - additional_kwargs: {}, - response_metadata: {}, - }, - }, - ], - }, - }, - root_span_id: trace_id, - span_parents: [root_span_id], - }, - { - span_attributes: { - name: "ChatOpenAI", - type: "llm", - }, - input: [ - [ - { - kwargs: { - content: expect.any(String), // Prompt message content - additional_kwargs: {}, - response_metadata: {}, - }, - }, - ], - ], - metrics: { - start: expect.any(Number), - tokens: expect.any(Number), - prompt_tokens: expect.any(Number), - completion_tokens: expect.any(Number), - end: expect.any(Number), - }, - metadata: { - tags: ["seq:step:2"], - model: "gpt-4o-mini-2024-07-18", - }, - output: { - generations: [ - [ - { - text: expect.any(String), // Generated text - message: { - kwargs: { - content: expect.any(String), // Message content - additional_kwargs: expect.any(Object), - response_metadata: expect.any(Object), - tool_calls: expect.any(Array), - invalid_tool_calls: expect.any(Array), - usage_metadata: expect.any(Object), - }, - }, - }, - ], - ], - llmOutput: { - tokenUsage: { - promptTokens: expect.any(Number), - completionTokens: expect.any(Number), - totalTokens: expect.any(Number), - }, - }, - }, - root_span_id: trace_id, - span_parents: [root_span_id], - }, - ]); - - expect(message.content).toEqual(expect.stringContaining("3")); - }); -}); - -it("should handle streaming LLM calls", async () => { - const logs: LogsRequest[] = []; - - server.use( - http.post("https://api.openai.com/v1/chat/completions", async () => { - const stream = new ReadableStream({ - start(controller) { - const chunks = CHAT_STREAM_PARROT; - - for (const chunk of chunks) { - controller.enqueue(encoder.encode(chunk + "\n\n")); - } - controller.close(); - }, - }); - - return new Response(stream, { - headers: { - "Content-Type": "text/event-stream; charset=utf-8", - "Transfer-Encoding": "chunked", - }, - }); - }), - - http.post(/.+logs/, async ({ request }) => { - // eslint-disable-next-line @typescript-eslint/consistent-type-assertions - logs.push((await request.json()) as LogsRequest); - return HttpResponse.json(["stream-span-id"]); - }), - ); - - const prompt = ChatPromptTemplate.fromTemplate( - `tell me a four word joke about {topic}`, - ); - const model = new ChatOpenAI({ - model: "gpt-4o-mini-2024-07-18", - streaming: true, - }); - - const chain = prompt.pipe(model); - - const stream = await chain.stream( - { topic: "parrot" }, - { callbacks: [handler] }, - ); - - const chunks = []; - for await (const chunk of stream) { - chunks.push(chunk); - } - - await flush(); - - const { spans, trace_id, root_span_id } = logsToSpans(logs); - - expect(spans).toMatchObject([ - { - span_attributes: { - name: "RunnableSequence", - type: "task", - }, - input: { - topic: "parrot", - }, - metadata: { - tags: [], - }, - span_id: root_span_id, - root_span_id: trace_id, - }, - { - span_attributes: { name: "ChatPromptTemplate", type: "task" }, - input: { topic: "parrot" }, - output: expect.objectContaining({ - kwargs: expect.objectContaining({ - messages: expect.arrayContaining([ - expect.objectContaining({ - kwargs: expect.objectContaining({ - content: "tell me a four word joke about parrot", - }), - }), - ]), - }), - }), - metadata: { tags: ["seq:step:1"] }, - root_span_id: trace_id, - span_parents: [root_span_id], - }, - { - span_attributes: { name: "ChatOpenAI", type: "llm" }, - input: expect.arrayContaining([ - expect.arrayContaining([ - expect.objectContaining({ - kwargs: expect.objectContaining({ - content: "tell me a four word joke about parrot", - }), - }), - ]), - ]), - output: expect.objectContaining({ - generations: expect.arrayContaining([ - expect.arrayContaining([ - expect.objectContaining({ - message: expect.objectContaining({ - kwargs: expect.objectContaining({ - content: "Polly wants more crackers!", - }), - }), - }), - ]), - ]), - }), - metrics: { - completion_tokens: 6, - end: expect.any(Number), - prompt_tokens: 16, - start: expect.any(Number), - tokens: 22, - }, - metadata: { - tags: ["seq:step:2"], - model: "gpt-4o-mini-2024-07-18", - }, - root_span_id: trace_id, - span_parents: [root_span_id], - }, - ]); - - expect(chunks.length).toBeGreaterThan(0); -}); - -it("should track time-to-first-token in streaming calls", async () => { - const logs: LogsRequest[] = []; - - server.use( - http.post("https://api.openai.com/v1/chat/completions", async () => { - const stream = new ReadableStream({ - start(controller) { - const chunks = CHAT_STREAM_PARROT; - - for (const chunk of chunks) { - controller.enqueue(encoder.encode(chunk + "\n\n")); - } - controller.close(); - }, - }); - - return new Response(stream, { - headers: { - "Content-Type": "text/event-stream; charset=utf-8", - "Transfer-Encoding": "chunked", - }, - }); - }), - - http.post(/.+logs/, async ({ request }) => { - // eslint-disable-next-line @typescript-eslint/consistent-type-assertions - logs.push((await request.json()) as LogsRequest); - return HttpResponse.json(["stream-span-id"]); - }), - ); - - const prompt = ChatPromptTemplate.fromTemplate("Count from 1 to 5."); - const model = new ChatOpenAI({ - model: "gpt-4o-mini", - streaming: true, - }); - - const chain = prompt.pipe(model); - - const chunks = []; - const stream = await chain.stream({}, { callbacks: [handler] }); - for await (const chunk of stream) { - chunks.push(chunk); - } - - // Verify we got streaming chunks - expect(chunks.length).toBeGreaterThan(0); - - await flush(); - - const { spans } = logsToSpans(logs); - - // Find the LLM span - const llmSpan = spans.find( - (s) => - s.span_attributes?.name === "ChatOpenAI" && - s.span_attributes?.type === "llm", - ); - - expect(llmSpan).toBeDefined(); - expect(llmSpan?.metrics).toMatchObject({ - time_to_first_token: expect.any(Number), - prompt_tokens: expect.any(Number), - completion_tokens: expect.any(Number), - tokens: expect.any(Number), - }); - - // Verify TTFT is a reasonable value (positive and less than total time) - expect(llmSpan?.metrics?.time_to_first_token).toBeGreaterThan(0); -}); - -it("should handle multi-step chains with memory", async () => { - const logs: LogsRequest[] = []; - - server.use( - http.post("https://api.openai.com/v1/chat/completions", () => { - return HttpResponse.json(CHAT_CHAIN_MEMORY); - }), - - http.post(/.+logs/, async ({ request }) => { - // eslint-disable-next-line @typescript-eslint/consistent-type-assertions - logs.push((await request.json()) as LogsRequest); - return HttpResponse.json(["memory-span-id"]); - }), - ); - - const prompt = ChatPromptTemplate.fromTemplate(`{history} User: {input}`); - const model = new ChatOpenAI({ - model: "gpt-4o-mini-2024-07-18", - }); - - const chain = prompt.pipe(model); - - const memory = { history: "Assistant: Hello! How can I assist you today?" }; - await chain.invoke( - { input: "What's your name?", ...memory }, - { callbacks: [handler], tags: ["test"] }, - ); - - await flush(); - - const { spans, trace_id, root_span_id } = logsToSpans(logs); - - debugger; - - expect(spans).toMatchObject([ - { - span_attributes: { - name: "RunnableSequence", - type: "task", - }, - input: { - history: "Assistant: Hello! How can I assist you today?", - input: "What's your name?", - }, - metadata: { - tags: ["test"], - }, - span_id: root_span_id, - root_span_id: trace_id, - }, - { - span_attributes: { name: "ChatPromptTemplate", type: "task" }, - input: { - history: "Assistant: Hello! How can I assist you today?", - input: "What's your name?", - }, - output: expect.objectContaining({ - kwargs: expect.objectContaining({ - messages: expect.arrayContaining([ - expect.objectContaining({ - kwargs: expect.objectContaining({ - content: - "Assistant: Hello! How can I assist you today? User: What's your name?", - }), - }), - ]), - }), - }), - metadata: { tags: ["seq:step:1", "test"] }, - root_span_id: trace_id, - span_parents: [root_span_id], - }, - { - span_attributes: { name: "ChatOpenAI", type: "llm" }, - input: expect.arrayContaining([ - expect.arrayContaining([ - expect.objectContaining({ - kwargs: expect.objectContaining({ - content: - "Assistant: Hello! How can I assist you today? User: What's your name?", - }), - }), - ]), - ]), - output: expect.objectContaining({ - generations: expect.arrayContaining([ - expect.arrayContaining([ - expect.objectContaining({ - message: expect.objectContaining({ - kwargs: expect.objectContaining({ - content: expect.stringContaining("Assistant"), - }), - }), - }), - ]), - ]), - }), - metrics: { - completion_tokens: expect.any(Number), - end: expect.any(Number), - prompt_tokens: expect.any(Number), - start: expect.any(Number), - tokens: expect.any(Number), - }, - metadata: { - tags: ["seq:step:2", "test"], - model: "gpt-4o-mini-2024-07-18", - }, - root_span_id: trace_id, - span_parents: [root_span_id], - }, - ]); -}); - -it("should handle tool/agent usage", async () => { - const logs: LogsRequest[] = []; - - server.use( - http.post("https://api.openai.com/v1/chat/completions", async () => { - return HttpResponse.json(CHAT_TOOL_CALCULATOR); - }), - - http.post(/.+logs/, async ({ request }) => { - // eslint-disable-next-line @typescript-eslint/consistent-type-assertions - logs.push((await request.json()) as LogsRequest); - return HttpResponse.json(["tool-span-id"]); - }), - ); - - const llm = new ChatOpenAI({ - model: "gpt-4o-mini-2024-07-18", - }); - - const calculatorSchema = z.object({ - operation: z - .enum(["add", "subtract", "multiply", "divide"]) - .describe("The type of operation to execute."), - number1: z.number().describe("The first number to operate on."), - number2: z.number().describe("The second number to operate on."), - }); - - const calculatorTool = tool( - ({ operation, number1, number2 }) => { - // Functions must return strings - if (operation === "add") { - return `${number1 + number2}`; - } else if (operation === "subtract") { - return `${number1 - number2}`; - } else if (operation === "multiply") { - return `${number1 * number2}`; - } else if (operation === "divide") { - return `${number1 / number2}`; - } else { - throw new Error("Invalid operation."); - } - }, - { - name: "calculator", - description: "Can perform mathematical operations.", - schema: calculatorSchema, - }, - ); - - const llmWithTools = llm.bindTools([calculatorTool]); - - await llmWithTools.invoke("What is 3 * 12", { - callbacks: [handler], - }); - - await flush(); - - const { spans, trace_id, root_span_id } = logsToSpans(logs); - - debugger; - - expect(spans).toMatchObject([ - { - span_attributes: { - name: "ChatOpenAI", - type: "llm", - }, - input: expect.arrayContaining([ - expect.arrayContaining([ - expect.objectContaining({ - kwargs: expect.objectContaining({ - content: "What is 3 * 12", - }), - }), - ]), - ]), - output: expect.objectContaining({ - generations: expect.arrayContaining([ - expect.arrayContaining([ - expect.objectContaining({ - message: expect.objectContaining({ - kwargs: expect.objectContaining({ - tool_calls: expect.arrayContaining([ - expect.objectContaining({ - name: "calculator", - args: { - operation: "multiply", - number1: 3, - number2: 12, - }, - }), - ]), - }), - }), - }), - ]), - ]), - }), - metrics: { - completion_tokens: expect.any(Number), - end: expect.any(Number), - prompt_tokens: expect.any(Number), - start: expect.any(Number), - tokens: expect.any(Number), - }, - metadata: { - model: "gpt-4o-mini-2024-07-18", - tags: [], - }, - span_id: root_span_id, - root_span_id: trace_id, - }, - ]); -}); - -it("should handle parallel runnable execution", async () => { - const logs: LogsRequest[] = []; - - const calls = [ - HttpResponse.json(CHAT_BEAR_JOKE), - HttpResponse.json(CHAT_BEAR_POEM), - ]; - - server.use( - http.post("https://api.openai.com/v1/chat/completions", () => { - return calls.shift() || HttpResponse.json({ ok: false }, { status: 500 }); - }), - - http.post(/.+logs/, async ({ request }) => { - // eslint-disable-next-line @typescript-eslint/consistent-type-assertions - logs.push((await request.json()) as LogsRequest); - return HttpResponse.json(["parallel-span-id"]); - }), - ); - - const model = new ChatOpenAI({ model: "gpt-4o-mini-2024-07-18" }); - - const jokeChain = PromptTemplate.fromTemplate( - "Tell me a joke about {topic}", - ).pipe(model); - const poemChain = PromptTemplate.fromTemplate( - "write a 2-line poem about {topic}", - ).pipe(model); - - const mapChain = RunnableMap.from({ - joke: jokeChain, - poem: poemChain, - }); - - await mapChain.invoke({ topic: "bear" }, { callbacks: [handler] }); - - await flush(); - - const { spans, trace_id, root_span_id } = logsToSpans(logs); - - // Check that we have the expected structure - expect(spans).toMatchObject([ - { - span_attributes: { - name: "RunnableMap", - type: "task", - }, - input: { - input: { - topic: "bear", - }, - }, - metadata: { - tags: [], - }, - span_id: root_span_id, - root_span_id: trace_id, - }, - { - // Updated for LangGraph 1.x API using Annotation - span_attributes: { name: "RunnableSequence", type: "task" }, - input: { topic: "bear" }, - root_span_id: trace_id, - span_parents: [root_span_id], - }, - { - span_attributes: { name: "RunnableSequence", type: "task" }, - input: { topic: "bear" }, - root_span_id: trace_id, - span_parents: [root_span_id], - }, - // Additional spans for the prompts and models - expect.anything(), - expect.anything(), - expect.anything(), - expect.anything(), - ]); -}); - -it("should handle LangGraph state management", async () => { - const logs: LogsRequest[] = []; - - server.use( - http.post("https://api.openai.com/v1/chat/completions", async () => { - return HttpResponse.json(CHAT_SAY_HELLO); - }), - - http.post(/.+logs/, async ({ request }) => { - // eslint-disable-next-line @typescript-eslint/consistent-type-assertions - logs.push((await request.json()) as LogsRequest); - return HttpResponse.json(["graph-span-id"]); - }), - ); - - const GraphState = Annotation.Root({ - message: Annotation({ - reducer: (_, y) => y, - default: () => "", - }), - }); - - type State = typeof GraphState.State; - type StateUpdate = typeof GraphState.Update; - - const model = new ChatOpenAI({ - model: "gpt-4o-mini-2024-07-18", - callbacks: [handler], - }); - - async function sayHello(_: State): Promise { - const res = await model.invoke("Say hello"); - return { message: typeof res.content === "string" ? res.content : "" }; - } - - function sayBye(_: State): StateUpdate { - console.log(`From the 'sayBye' node: Bye world!`); - return {}; - } - - const graphBuilder = new StateGraph(GraphState) // Add our nodes to the Graph - .addNode("sayHello", sayHello) - .addNode("sayBye", sayBye) // Add the edges between nodes - .addEdge(START, "sayHello") - .addEdge("sayHello", "sayBye") - .addEdge("sayBye", END); - - const helloWorldGraph = graphBuilder.compile(); - - await helloWorldGraph.invoke({}, { callbacks: [handler] }); - - await flush(); - - const { spans, trace_id, root_span_id } = logsToSpans(logs); - - expect(spans).toMatchObject([ - { - span_attributes: { - name: "LangGraph", - type: "task", - }, - input: {}, - span_id: root_span_id, - root_span_id: trace_id, - }, - { - span_attributes: { name: "sayHello", type: "task" }, - root_span_id: trace_id, - span_parents: [root_span_id], - }, - { - span_attributes: { name: "ChatOpenAI", type: "llm" }, - input: expect.arrayContaining([ - expect.arrayContaining([ - expect.objectContaining({ - kwargs: expect.objectContaining({ - content: "Say hello", - }), - }), - ]), - ]), - output: expect.objectContaining({ - generations: expect.anything(), - }), - root_span_id: trace_id, - span_parents: expect.anything(), - }, - { - span_attributes: { name: "sayBye", type: "task" }, - root_span_id: trace_id, - span_parents: [root_span_id], - }, - ]); -}); - -it("should have correctly typed constructor parameters", async () => { - const logs: LogsRequest[] = []; - - server.use( - http.post("https://api.openai.com/v1/chat/completions", () => { - return HttpResponse.json(CHAT_MATH); - }), - - http.post(/.+logs/, async ({ request }) => { - // eslint-disable-next-line @typescript-eslint/consistent-type-assertions - logs.push((await request.json()) as LogsRequest); - return HttpResponse.json(["4bc6305f-2175-4481-bc84-7c55a456b7ea"]); - }), - ); - - const handler = new BraintrustCallbackHandler({ - logger: NOOP_SPAN, - }); - - handler.handleLLMStart( - { - name: "test", - lc: 1, - type: "secret", - id: ["test"], - }, - ["test"], - "test", - "test", - ); - - await flush(); - - expect(logs).toEqual([]); -}); - -it("should handle chain inputs/outputs with null/undefined values", async () => { - const logs: LogsRequest[] = []; - - server.use( - http.post(/.+logs/, async ({ request }) => { - // eslint-disable-next-line @typescript-eslint/consistent-type-assertions - logs.push((await request.json()) as LogsRequest); - return HttpResponse.json(["null-span-id"]); - }), - ); - - // Test chain with null/undefined inputs - await handler.handleChainStart( - { id: ["TestChain"], lc: 1, type: "not_implemented" }, - { input1: "value1", input2: null, input3: undefined }, - "run-1", - undefined, - ["test"], - ); - - await handler.handleChainEnd( - { output1: "value1", output2: null, output3: undefined }, - "run-1", - undefined, - ["test"], - ); - - await flush(); - - const { spans, trace_id, root_span_id } = logsToSpans(logs); - - expect(spans).toMatchObject([ - { - span_attributes: { - name: "TestChain", - type: "task", - }, - input: { - input1: "value1", - input2: null, - }, - output: { - output1: "value1", - output2: null, - }, - metadata: { - tags: ["test"], - }, - span_id: root_span_id, - root_span_id: trace_id, - }, - ]); -}); - -it("should handle agent action and agent end callbacks", async () => { - const logs: LogsRequest[] = []; - - server.use( - http.post(/.+logs/, async ({ request }) => { - // eslint-disable-next-line @typescript-eslint/consistent-type-assertions - logs.push((await request.json()) as LogsRequest); - return HttpResponse.json(["agent-span-id"]); - }), - ); - - const agentAction = { - tool: "calculator", - toolInput: { operation: "multiply", a: 3, b: 12 }, - log: "Invoking calculator with multiply 3 * 12", - }; - - const agentFinish = { - returnValues: { output: "The result is 36" }, - log: "Final answer: 36", - }; - - await handler.handleAgentAction(agentAction, "agent-run-1", undefined, [ - "agent-test", - ]); - - await handler.handleAgentEnd(agentFinish, "agent-run-1", undefined, [ - "agent-test", - ]); - - await flush(); - - const { spans, trace_id, root_span_id } = logsToSpans(logs); - - expect(spans).toMatchObject([ - { - span_attributes: { - name: "calculator", - type: "llm", - }, - input: { - tool: "calculator", - toolInput: { operation: "multiply", a: 3, b: 12 }, - log: "Invoking calculator with multiply 3 * 12", - }, - output: { - returnValues: { output: "The result is 36" }, - log: "Final answer: 36", - }, - metadata: { - tags: ["agent-test"], - }, - span_id: root_span_id, - root_span_id: trace_id, - }, - ]); -}); - -it("should handle agent action with string toolInput", async () => { - const logs: LogsRequest[] = []; - - server.use( - http.post(/.+logs/, async ({ request }) => { - // eslint-disable-next-line @typescript-eslint/consistent-type-assertions - logs.push((await request.json()) as LogsRequest); - return HttpResponse.json(["agent-string-span-id"]); - }), - ); - - const agentAction = { - tool: "search", - toolInput: "What is the weather in Paris?", - log: "Searching for weather information", - }; - - const agentFinish = { - returnValues: { output: "It is sunny and 22 degrees in Paris" }, - log: "Search complete", - }; - - await handler.handleAgentAction(agentAction, "agent-run-2", undefined, [ - "search-test", - ]); - - await handler.handleAgentEnd(agentFinish, "agent-run-2", undefined, [ - "search-test", - ]); - - await flush(); - - const { spans, trace_id, root_span_id } = logsToSpans(logs); - - expect(spans).toMatchObject([ - { - span_attributes: { - name: "search", - type: "llm", - }, - input: { - tool: "search", - toolInput: "What is the weather in Paris?", - log: "Searching for weather information", - }, - output: { - returnValues: { output: "It is sunny and 22 degrees in Paris" }, - log: "Search complete", - }, - metadata: { - tags: ["search-test"], - }, - span_id: root_span_id, - root_span_id: trace_id, - }, - ]); -}); - -it("should handle nested agent action with parent run id", async () => { - const logs: LogsRequest[] = []; - - server.use( - http.post(/.+logs/, async ({ request }) => { - // eslint-disable-next-line @typescript-eslint/consistent-type-assertions - logs.push((await request.json()) as LogsRequest); - return HttpResponse.json(["nested-agent-span-id"]); - }), - ); - - await handler.handleChainStart( - { id: ["AgentExecutor"], lc: 1, type: "not_implemented" }, - { input: "Calculate 3 * 12" }, - "parent-run-1", - undefined, - ["agent-executor"], - ); - - const agentAction = { - tool: "calculator", - toolInput: { a: 3, b: 12, operation: "multiply" }, - log: "Using calculator tool", - }; - - await handler.handleAgentAction( - agentAction, - "child-agent-run", - "parent-run-1", - ["agent-action"], - ); - - const agentFinish = { - returnValues: { output: "36" }, - log: "Calculation complete", - }; - - await handler.handleAgentEnd(agentFinish, "child-agent-run", "parent-run-1", [ - "agent-action", - ]); - - await handler.handleChainEnd( - { output: "The answer is 36" }, - "parent-run-1", - undefined, - ["agent-executor"], - ); - - await flush(); - - const { spans, trace_id, root_span_id } = logsToSpans(logs); - - expect(spans.length).toBe(2); - - expect(spans[0]).toMatchObject({ - span_attributes: { - name: "AgentExecutor", - type: "task", - }, - input: { input: "Calculate 3 * 12" }, - output: { output: "The answer is 36" }, - metadata: { - tags: ["agent-executor"], - }, - span_id: root_span_id, - root_span_id: trace_id, - }); - - expect(spans[1]).toMatchObject({ - span_attributes: { - name: "calculator", - type: "llm", - }, - input: { - tool: "calculator", - toolInput: { a: 3, b: 12, operation: "multiply" }, - log: "Using calculator tool", - }, - output: { - returnValues: { output: "36" }, - log: "Calculation complete", - }, - metadata: { - tags: ["agent-action"], - }, - root_span_id: trace_id, - span_parents: [root_span_id], - }); -}); - -it("should extract prompt caching tokens from Anthropic response", async () => { - const logs: LogsRequest[] = []; - - const ANTHROPIC_RESPONSE_WITH_CACHE = { - model: "claude-sonnet-4-5-20250929", - id: "msg_01PKL7azhWzdzdXC5yojHG6V", - type: "message", - role: "assistant", - content: [ - { - type: "text", - text: 'The first type of testing mentioned is **Unit Testing**, which is described as "Testing individual components or functions in isolation."', - }, - ], - stop_reason: "end_turn", - stop_sequence: null, - usage: { - input_tokens: 14, - cache_creation_input_tokens: 2042, - cache_read_input_tokens: 0, - cache_creation: { - ephemeral_5m_input_tokens: 0, - ephemeral_1h_input_tokens: 0, - }, - output_tokens: 27, - service_tier: "standard", - }, - }; - - const ANTHROPIC_RESPONSE_WITH_CACHE_READ = { - model: "claude-sonnet-4-5-20250929", - id: "msg_01PjHqPkFstyG2Bcqum9XKD8", - type: "message", - role: "assistant", - content: [ - { - type: "text", - text: 'Based on the document:\n\n1. **First type of testing mentioned:** Unit Testing - described as "Testing individual components or functions in isolation"\n\n2. **Python testing frameworks mentioned:** \n - pytest (described as "Feature-rich testing framework")\n - unittest (described as "Built-in Python testing module")', - }, - ], - stop_reason: "end_turn", - stop_sequence: null, - usage: { - input_tokens: 23, - cache_creation_input_tokens: 0, - cache_read_input_tokens: 2042, - cache_creation: { - ephemeral_5m_input_tokens: 0, - ephemeral_1h_input_tokens: 0, - }, - output_tokens: 71, - service_tier: "standard", - }, - }; - - const anthropicResponses = [ - ANTHROPIC_RESPONSE_WITH_CACHE, - ANTHROPIC_RESPONSE_WITH_CACHE_READ, - ]; - - server.use( - http.post("https://api.anthropic.com/v1/messages", ({ request }) => { - const message = anthropicResponses.shift(); - return HttpResponse.json(message); - }), - - http.post(/.+logs.+/, async ({ request }) => { - // eslint-disable-next-line @typescript-eslint/consistent-type-assertions - logs.push((await request.json()) as LogsRequest); - console.log(logs.slice(-1)[0]); - return HttpResponse.json(["cache-tokens-span-id"]); - }), - ); - - const model = new ChatAnthropic({ - model: "claude-sonnet-4-5-20250929", - anthropicApiKey: "test-key", - }); - - // Use a long system message to simulate cache-eligible content - const longText = ` -# Comprehensive Guide to Software Testing Methods - -Software testing is a critical component of the software development lifecycle. -This guide covers various testing methodologies, best practices, and tools. - -## Types of Testing -- Unit Testing: Testing individual components or functions in isolation -- Integration Testing: Testing how components work together -- End-to-End Testing: Testing the entire application flow - -## Python Testing Tools -- pytest: Feature-rich testing framework -- unittest: Built-in Python testing module -`.repeat(20); // Repeat to ensure enough tokens for caching - - const messages = [ - new SystemMessage({ - content: [ - { - type: "text", - text: longText, - cache_control: { type: "ephemeral" }, - }, - ], - }), - new HumanMessage("What is the first type of testing mentioned?"), - ]; - - // First invocation - should create cache - await model.invoke(messages, { callbacks: [handler] }); - - await flush(); - - const { spans: firstSpans } = logsToSpans(logs); - logs.length = 0; // Clear logs for next invocation - - const firstLlmSpan = firstSpans.find( - (s) => s.span_attributes?.type === "llm", - ); - - expect(firstLlmSpan).toBeDefined(); - expect(firstLlmSpan?.metrics).toMatchObject({ - prompt_tokens: 14, - completion_tokens: 27, - }); - - console.log(firstLlmSpan?.metrics); - - // After fix: should have cache creation tokens - expect(firstLlmSpan?.metrics?.prompt_cache_creation_tokens).toBe(2042); - - // Second invocation - should read from cache - await model.invoke( - [ - ...messages, - new HumanMessage("What testing framework is mentioned for Python?"), - ], - { callbacks: [handler] }, - ); - - await flush(); - - const { spans: secondSpans } = logsToSpans(logs); - - const secondLlmSpan = secondSpans.find( - (s) => s.span_attributes?.type === "llm", - ); - - expect(secondLlmSpan).toBeDefined(); - expect(secondLlmSpan?.metrics).toMatchObject({ - prompt_tokens: 23, - completion_tokens: 71, - }); - - // After fix: should have cache read tokens - expect(secondLlmSpan?.metrics?.prompt_cached_tokens).toBe(2042); -}); diff --git a/integrations/langchain-js/src/BraintrustCallbackHandler.ts b/integrations/langchain-js/src/BraintrustCallbackHandler.ts deleted file mode 100644 index fa88fea7e..000000000 --- a/integrations/langchain-js/src/BraintrustCallbackHandler.ts +++ /dev/null @@ -1,259 +0,0 @@ -import { BaseCallbackHandler } from "@langchain/core/callbacks/base"; -import { AgentAction, AgentFinish } from "@langchain/core/dist/agents"; -import { DocumentInterface } from "@langchain/core/dist/documents/document"; -import { Serialized } from "@langchain/core/dist/load/serializable"; -import { BaseMessage } from "@langchain/core/dist/messages/base"; -import { ChatResult, LLMResult } from "@langchain/core/dist/outputs"; -import { ChainValues } from "@langchain/core/dist/utils/types"; -import { RunnableConfig } from "@langchain/core/runnables"; -import { - BraintrustLangChainCallbackHandler, - type LangChainCallbackHandlerOptions, -} from "braintrust"; - -type BraintrustCallbackHandlerOptions = - LangChainCallbackHandlerOptions; - -/** - * A Braintrust tracer for LangChain.js that logs LLM calls, chains, and tools. - * - * @deprecated Use automatic LangChain instrumentation from `braintrust`, or - * `BraintrustLangChainCallbackHandler` from `braintrust` for manual callbacks. - * This package will stop being published after the next release. - */ -export class BraintrustCallbackHandler< - IsAsyncFlush extends boolean, -> extends BaseCallbackHandler { - name = "BraintrustCallbackHandler"; - private inner: BraintrustLangChainCallbackHandler; - - constructor( - options?: Partial>, - ) { - super(); - this.inner = new BraintrustLangChainCallbackHandler(options); - } - - handleLLMStart( - llm: Serialized, - prompts: string[], - runId: string, - parentRunId?: string, - extraParams?: { - options: RunnableConfig; - invocation_params?: Record; - batch_size: number; - cache?: boolean; - }, - tags?: string[], - metadata?: Record, - runName?: string, - ): Promise { - return this.inner.handleLLMStart( - llm, - prompts, - runId, - parentRunId, - extraParams, - tags, - metadata, - runName, - ); - } - - handleLLMError( - err: Error, - runId: string, - parentRunId?: string, - tags?: string[], - ): Promise { - return this.inner.handleLLMError(err, runId, parentRunId, tags); - } - - handleLLMEnd( - output: LLMResult | ChatResult, - runId: string, - parentRunId?: string, - tags?: string[], - ): Promise { - return this.inner.handleLLMEnd(output, runId, parentRunId, tags); - } - - handleLLMNewToken( - token: string, - idx: { prompt: number; completion: number }, - runId: string, - parentRunId?: string, - tags?: string[], - ): Promise { - return this.inner.handleLLMNewToken(token, idx, runId, parentRunId, tags); - } - - handleChatModelStart( - llm: Serialized, - messages: BaseMessage[][], - runId: string, - parentRunId?: string, - extraParams?: { - options: RunnableConfig; - invocation_params?: Record; - batch_size: number; - cache?: boolean; - }, - tags?: string[], - metadata?: Record, - runName?: string, - ): Promise { - return this.inner.handleChatModelStart( - llm, - messages, - runId, - parentRunId, - extraParams, - tags, - metadata, - runName, - ); - } - - handleChainStart( - chain: Serialized, - inputs: ChainValues, - runId: string, - parentRunId?: string, - tags?: string[], - metadata?: Record, - runType?: string, - runName?: string, - ): Promise { - return this.inner.handleChainStart( - chain, - inputs, - runId, - parentRunId, - tags, - metadata, - runType, - runName, - ); - } - - handleChainError( - err: Error, - runId: string, - parentRunId?: string, - tags?: string[], - kwargs?: { inputs?: Record }, - ): Promise { - return this.inner.handleChainError(err, runId, parentRunId, tags, kwargs); - } - - handleChainEnd( - outputs: ChainValues, - runId: string, - parentRunId?: string, - tags?: string[], - kwargs?: { inputs?: Record }, - ): Promise { - return this.inner.handleChainEnd(outputs, runId, parentRunId, tags, kwargs); - } - - handleToolStart( - tool: Serialized, - input: string, - runId: string, - parentRunId?: string, - tags?: string[], - metadata?: Record, - runName?: string, - ): Promise { - return this.inner.handleToolStart( - tool, - input, - runId, - parentRunId, - tags, - metadata, - runName, - ); - } - - handleToolError( - err: Error, - runId: string, - parentRunId?: string, - tags?: string[], - ): Promise { - return this.inner.handleToolError(err, runId, parentRunId, tags); - } - - handleToolEnd( - output: unknown, - runId: string, - parentRunId?: string, - tags?: string[], - ): Promise { - return this.inner.handleToolEnd(output, runId, parentRunId, tags); - } - - handleAgentAction( - action: AgentAction, - runId: string, - parentRunId?: string, - tags?: string[], - ): Promise { - return this.inner.handleAgentAction( - action as unknown as Record, - runId, - parentRunId, - tags, - ); - } - - handleAgentEnd( - action: AgentFinish, - runId: string, - parentRunId?: string, - tags?: string[], - ): Promise { - return this.inner.handleAgentEnd(action, runId, parentRunId, tags); - } - - handleRetrieverStart( - retriever: Serialized, - query: string, - runId: string, - parentRunId?: string, - tags?: string[], - metadata?: Record, - name?: string, - ): Promise { - return this.inner.handleRetrieverStart( - retriever, - query, - runId, - parentRunId, - tags, - metadata, - name, - ); - } - - handleRetrieverEnd( - documents: DocumentInterface[], - runId: string, - parentRunId?: string, - tags?: string[], - ): Promise { - return this.inner.handleRetrieverEnd(documents, runId, parentRunId, tags); - } - - handleRetrieverError( - err: Error, - runId: string, - parentRunId?: string, - tags?: string[], - ): Promise { - return this.inner.handleRetrieverError(err, runId, parentRunId, tags); - } -} diff --git a/integrations/langchain-js/src/index.ts b/integrations/langchain-js/src/index.ts deleted file mode 100644 index ad7af8003..000000000 --- a/integrations/langchain-js/src/index.ts +++ /dev/null @@ -1,2 +0,0 @@ -export { BraintrustCallbackHandler } from "./BraintrustCallbackHandler"; -export { setGlobalHandler } from "./setGlobalHandler"; diff --git a/integrations/langchain-js/src/setGlobalHandler.test.ts b/integrations/langchain-js/src/setGlobalHandler.test.ts deleted file mode 100644 index 42cdffb96..000000000 --- a/integrations/langchain-js/src/setGlobalHandler.test.ts +++ /dev/null @@ -1,138 +0,0 @@ -import { CallbackManager } from "@langchain/core/callbacks/manager"; -import { ChatPromptTemplate } from "@langchain/core/prompts"; -import { ChatOpenAI } from "@langchain/openai"; -import { flush as flushBraintrustLogs } from "braintrust"; -import { http, HttpResponse } from "msw"; -import { afterEach, describe, expect, it } from "vitest"; -import { BraintrustCallbackHandler } from "./BraintrustCallbackHandler"; -import { CHAT_MATH } from "./BraintrustCallbackHandler.fixtures"; -import { clearGlobalHandler, setGlobalHandler } from "./setGlobalHandler"; -import { server } from "./test/setup"; -import { LogsRequest } from "./test/types"; -import { logsToSpans, withLogging } from "./test/utils"; - -const handler = withLogging(new BraintrustCallbackHandler({ debug: true })); - -describe("setGlobalHandler", () => { - afterEach(() => { - clearGlobalHandler(); - }); - - it("should register the BraintrustCallbackHandler", async () => { - setGlobalHandler(handler); - - // Make sure the handler is registered in the LangChain.js library. - const manager = CallbackManager.configure(); - expect( - manager?.handlers.filter( - (handler) => handler instanceof BraintrustCallbackHandler, - )[0], - ).toBe(handler); - - const logs: LogsRequest[] = []; - - // Intercept calls to confirm our tracing is working. - server.use( - http.post("https://api.openai.com/v1/chat/completions", () => { - return HttpResponse.json(CHAT_MATH); - }), - - http.post(/.+logs/, async ({ request }) => { - // eslint-disable-next-line @typescript-eslint/consistent-type-assertions - logs.push((await request.json()) as LogsRequest); - return HttpResponse.json(["4bc6305f-2175-4481-bc84-7c55a456b7ea"]); - }), - ); - - // Here's what a typical user would do. - const prompt = ChatPromptTemplate.fromTemplate(`What is 1 + {number}?`); - const model = new ChatOpenAI({ - model: "gpt-4o-mini-2024-07-18", - }); - - const chain = prompt.pipe(model); - - const message = await chain.invoke({ number: "2" }); - - // Not normally needed by users, but we need it for our tests. - await flushBraintrustLogs(); - - const { spans, trace_id, root_span_id } = logsToSpans(logs); - - // Spans would be empty if the handler was not registered, let's make sure it logged what we expect. - expect(spans).toMatchObject([ - { - span_attributes: { - name: "RunnableSequence", - type: "task", - }, - input: { - number: "2", - }, - metadata: { - tags: [], - }, - span_id: root_span_id, - root_span_id: trace_id, - }, - { - span_attributes: { name: "ChatPromptTemplate", type: "task" }, - input: { number: "2" }, - output: expect.objectContaining({ - kwargs: expect.objectContaining({ - messages: expect.arrayContaining([ - expect.objectContaining({ - kwargs: expect.objectContaining({ - content: "What is 1 + 2?", - }), - }), - ]), - }), - }), - metadata: { tags: ["seq:step:1"] }, - root_span_id: trace_id, - span_parents: [root_span_id], - }, - { - span_attributes: { name: "ChatOpenAI", type: "llm" }, - input: expect.arrayContaining([ - expect.arrayContaining([ - expect.objectContaining({ - kwargs: expect.objectContaining({ - content: "What is 1 + 2?", - }), - }), - ]), - ]), - output: expect.objectContaining({ - generations: expect.arrayContaining([ - expect.arrayContaining([ - expect.objectContaining({ - message: expect.objectContaining({ - kwargs: expect.objectContaining({ - content: "1 + 2 equals 3.", - }), - }), - }), - ]), - ]), - }), - metrics: { - completion_tokens: 8, - end: expect.any(Number), - prompt_tokens: 15, - start: expect.any(Number), - tokens: 23, - }, - metadata: { - tags: ["seq:step:2"], - model: "gpt-4o-mini-2024-07-18", - }, - root_span_id: trace_id, - span_parents: [root_span_id], - }, - ]); - - expect(message.content).toBe("1 + 2 equals 3."); - }); -}); diff --git a/integrations/langchain-js/src/setGlobalHandler.ts b/integrations/langchain-js/src/setGlobalHandler.ts deleted file mode 100644 index e109d5e91..000000000 --- a/integrations/langchain-js/src/setGlobalHandler.ts +++ /dev/null @@ -1,24 +0,0 @@ -import { BaseCallbackHandler } from "@langchain/core/callbacks/base"; -import { - registerConfigureHook, - setContextVariable, -} from "@langchain/core/context"; - -const BT_HANDLER = "BT_HANDLER"; - -/** - * @deprecated Use automatic LangChain instrumentation from `braintrust` for - * application-wide coverage. This package will stop being published after the - * next release. - */ -export const setGlobalHandler = (handler: BaseCallbackHandler) => { - setContextVariable(BT_HANDLER, handler); - - registerConfigureHook({ - contextVar: BT_HANDLER, - }); -}; - -export const clearGlobalHandler = () => { - setContextVariable(BT_HANDLER, undefined); -}; diff --git a/integrations/langchain-js/src/test/setup.ts b/integrations/langchain-js/src/test/setup.ts deleted file mode 100644 index 440a1435b..000000000 --- a/integrations/langchain-js/src/test/setup.ts +++ /dev/null @@ -1,51 +0,0 @@ -import { http, HttpResponse } from "msw"; -import { setupServer } from "msw/node"; -import { afterAll, afterEach, beforeAll } from "vitest"; - -export const server = setupServer( - http.post(/.+\/api\/apikey\/login/, () => { - return HttpResponse.json({ - org_info: [ - { - id: "abb9f3e4-7fdd-4ccc-af40-f7e894fd4125", - name: "braintrustdata.com", - api_url: "http://0.0.0.0:8000", - git_metadata: null, - is_universal_api: null, - proxy_url: "http://0.0.0.0:8000", - realtime_url: "ws://0.0.0.0:8788", - }, - ], - }); - }), - - http.post(/.+\/api\/project\/register/, () => { - return HttpResponse.json({ - project: { - id: "209220fc-d3bd-4fab-b1a4-af5827d69200", - org_id: "abb9f3e4-7fdd-4ccc-af40-f7e894fd4125", - name: "Global", - created: "2024-12-04T16:14:11.122Z", - deleted_at: null, - user_id: "fac36c53-c882-458b-bf80-60d06c3e8a0d", - settings: null, - }, - }); - }), -); - -beforeAll(() => { - process.env.BRAINTRUST_API_KEY = "braintrust-api-key"; - process.env.BRAINTRUST_APP_URL = "http://braintrust.local"; - process.env.BRAINTRUST_ORG_NAME = "braintrustdata.com"; - process.env.OPENAI_API_KEY = "openai-api-key"; - - server.listen({ - onUnhandledRequest: (req) => { - throw new Error(`Unhandled request ${req.method}, ${req.url}`); - }, - }); -}); - -afterEach(() => server.resetHandlers()); -afterAll(() => server.close()); diff --git a/integrations/langchain-js/src/test/types.ts b/integrations/langchain-js/src/test/types.ts deleted file mode 100644 index 10b42c2df..000000000 --- a/integrations/langchain-js/src/test/types.ts +++ /dev/null @@ -1,32 +0,0 @@ -type SpanAttributes = { - name: string; - exec_counter: number; - type?: string; -}; - -type Metadata = { - tags: string[]; - params: Record; - [key: string]: unknown; -}; - -type Output = { - parsed: string; - raw: Record; -}; - -type LogRow = { - span_attributes?: SpanAttributes; - input?: Record; - metadata?: Metadata; - output?: Output; - metrics?: Record; - span_id: string; - root_span_id: string; - span_parents: string[]; -}; - -export type LogsRequest = { - rows: LogRow[]; - api_version: number; -}; diff --git a/integrations/langchain-js/src/test/utils.ts b/integrations/langchain-js/src/test/utils.ts deleted file mode 100644 index 823dc5b71..000000000 --- a/integrations/langchain-js/src/test/utils.ts +++ /dev/null @@ -1,72 +0,0 @@ -import { mergeDicts } from "braintrust/util"; -import { BaseCallbackHandler } from "@langchain/core/callbacks/base"; -import { LogsRequest } from "./types"; - -export const logsToSpans = (logs: LogsRequest[]) => { - if (logs.length === 0) { - throw new Error("No logs to convert to spans"); - } - - // Logs include partial updates (merges) for previous rows. - // We need to dedupe these and merge them. So we can see the final state like - // we do in the UI. - - const seenIds = new Set(); - const spans = logs - .flatMap((log) => log.rows) - .reduce( - (acc, row) => { - if (!seenIds.has(row.span_id)) { - seenIds.add(row.span_id); - acc.push(row); - } else { - const existingSpan = acc.find((span) => span.span_id === row.span_id); - if (existingSpan) { - mergeDicts(existingSpan, row); - } - } - return acc; - }, - // eslint-disable-next-line @typescript-eslint/consistent-type-assertions - [] as LogsRequest["rows"], - ); - - return { - spans, - trace_id: spans[0].root_span_id, - root_span_id: spans[0].span_id, - root_run_id: spans[0].metadata?.runId, - }; -}; - -export const withLogging = (handler: T): T => { - if (process.env.VERBOSE === "false") { - return handler; - } - - let depth = 0; - return new Proxy(handler, { - get(target, prop) { - // @ts-expect-error - const originalMethod = target[prop]; - if (typeof originalMethod === "function") { - // @ts-expect-error - return function (...args) { - const propString = String(prop); - if (!["startSpan", "endSpan"].includes(propString)) { - console.log(new Array(depth * 2).join(" ") + propString); - - if (propString.endsWith("Start")) { - depth++; - } else if (propString.endsWith("End")) { - depth = Math.max(0, depth - 1); - } - } - // @ts-expect-error - return originalMethod.apply(this, args); - }; - } - return originalMethod; - }, - }); -}; diff --git a/integrations/langchain-js/tsconfig.json b/integrations/langchain-js/tsconfig.json deleted file mode 100644 index ea6ad1861..000000000 --- a/integrations/langchain-js/tsconfig.json +++ /dev/null @@ -1,15 +0,0 @@ -{ - "compilerOptions": { - "declaration": true, - "lib": ["es2022"], - "module": "commonjs", - "target": "es2022", - "moduleResolution": "node", - "strict": true, - "noUnusedLocals": true, - "esModuleInterop": true, - "skipLibCheck": true - }, - "include": ["."], - "exclude": ["node_modules/**", "**/dist/**"] -} diff --git a/integrations/langchain-js/tsup.config.ts b/integrations/langchain-js/tsup.config.ts deleted file mode 100644 index 4c5a7bd9b..000000000 --- a/integrations/langchain-js/tsup.config.ts +++ /dev/null @@ -1,11 +0,0 @@ -import { defineConfig } from "tsup"; - -export default defineConfig([ - { - entry: ["src/index.ts"], - format: ["cjs", "esm"], - outDir: "dist", - external: ["braintrust", "@langchain/core", "@langchain/langgraph"], - dts: true, - }, -]); diff --git a/integrations/langchain-js/turbo.json b/integrations/langchain-js/turbo.json deleted file mode 100644 index 3e50dc890..000000000 --- a/integrations/langchain-js/turbo.json +++ /dev/null @@ -1,8 +0,0 @@ -{ - "extends": ["//"], - "tasks": { - "build": { - "outputs": ["**/dist/**"] - } - } -} diff --git a/integrations/langchain-js/vitest.config.ts b/integrations/langchain-js/vitest.config.ts deleted file mode 100644 index 9c517c86c..000000000 --- a/integrations/langchain-js/vitest.config.ts +++ /dev/null @@ -1,9 +0,0 @@ -import { defineConfig } from "vitest/config"; - -export default defineConfig({ - test: { - reporters: ["default"], - setupFiles: ["./src/test/setup.ts"], - include: ["tests/**/*.test.ts", "src/**/*.test.ts"], - }, -}); diff --git a/integrations/openai-agents-js/CHANGELOG.md b/integrations/openai-agents-js/CHANGELOG.md deleted file mode 100644 index 40a6b306b..000000000 --- a/integrations/openai-agents-js/CHANGELOG.md +++ /dev/null @@ -1,27 +0,0 @@ -# @braintrust/openai-agents - -## 0.1.7 - -### Patch Changes - -- fix: Add span provenance for temporal and openai agents sdk (https://github.com/braintrustdata/braintrust-sdk-javascript/pull/2518) -- Updated dependencies: braintrust@3.36.0 - -## 0.1.6 - -- `@braintrust/openai-agents` is now deprecated. This release marks the last release for this package. (https://github.com/braintrustdata/braintrust-sdk-javascript/pull/2383) -- Updated dependencies: braintrust@3.29.0 - -### Migration - -To migrate, install `braintrust` and import `OpenAIAgentsTraceProcessor` from -`braintrust/instrumentation` instead. See the -[OpenAI Agents SDK integration guide](https://www.braintrust.dev/docs/integrations/agent-frameworks/openai-agents-sdk) -for setup examples. - -## 0.1.5 - -### Patch Changes - -- Ensured the root span is flushed during `onTraceEnd()` so traces are marked complete even in short-lived and serverless processes. -- Added test coverage around trace completion and root span flushing behavior. diff --git a/integrations/openai-agents-js/README.md b/integrations/openai-agents-js/README.md deleted file mode 100644 index fe0e3b011..000000000 --- a/integrations/openai-agents-js/README.md +++ /dev/null @@ -1,74 +0,0 @@ -# @braintrust/openai-agents - -[![npm version](https://img.shields.io/npm/v/%40braintrust%2Fopenai-agents.svg)](https://www.npmjs.com/package/@braintrust/openai-agents) - -SDK for integrating Braintrust with OpenAI Agents. - -> [!WARNING] -> This package is deprecated and will stop being published after the next -> release. Use the OpenAI Agents integration in `braintrust` instead. - -## Installation - -```bash -npm install braintrust @openai/agents -# or -yarn add braintrust @openai/agents -# or -pnpm add braintrust @openai/agents -``` - -## Requirements - -- `braintrust` -- `@openai/agents` - -## Quickstart - -```typescript -import { initLogger } from "braintrust"; -import { OpenAIAgentsTraceProcessor } from "braintrust/instrumentation"; -import { Agent, run, addTraceProcessor } from "@openai/agents"; - -// Initialize Braintrust logger -const logger = initLogger({ - projectName: "my-agents-project", -}); - -// Create the tracing processor -const processor = new OpenAIAgentsTraceProcessor({ logger }); - -// Add the processor to OpenAI Agents -addTraceProcessor(processor); - -// Create and run your agent -const agent = new Agent({ - name: "my-agent", - model: "gpt-4o-mini", - instructions: "You are a helpful assistant.", -}); - -const result = await run(agent, "Hello, world!"); -``` - -## API - -### `OpenAIAgentsTraceProcessor` - -A tracing processor that logs traces from the OpenAI Agents SDK to Braintrust. - -#### Constructor - -```typescript -new OpenAIAgentsTraceProcessor(options?: OpenAIAgentsTraceProcessorOptions) -``` - -**Options:** - -- `logger?: Logger` - A Braintrust `Span`, `Experiment`, or `Logger` to use for logging. If undefined, the current span, experiment, or logger will be selected exactly as in `startSpan`. -- `maxTraces?: number` - Maximum number of concurrent traces to keep in memory (default: 10000). When exceeded, oldest traces are evicted using LRU policy to prevent memory leaks. - -## Documentation - -- Braintrust docs: [https://www.braintrust.dev/docs](https://www.braintrust.dev/docs) -- OpenAI Agents docs: [https://openai.github.io/openai-agents-js/](https://openai.github.io/openai-agents-js/) diff --git a/integrations/openai-agents-js/package.json b/integrations/openai-agents-js/package.json deleted file mode 100644 index 436d4f3a4..000000000 --- a/integrations/openai-agents-js/package.json +++ /dev/null @@ -1,50 +0,0 @@ -{ - "name": "@braintrust/openai-agents", - "version": "0.1.7", - "description": "SDK for integrating Braintrust with OpenAI Agents", - "main": "./dist/index.js", - "module": "./dist/index.mjs", - "types": "./dist/index.d.ts", - "exports": { - "./package.json": "./package.json", - ".": { - "types": "./dist/index.d.ts", - "import": "./dist/index.mjs", - "module": "./dist/index.mjs", - "require": "./dist/index.js" - } - }, - "scripts": { - "build": "tsup", - "check:typings": "tsc --noEmit", - "watch": "tsup --watch", - "clean": "rm -r dist/*", - "test": "vitest run", - "yalc:publish": "yalc publish" - }, - "author": "Braintrust Data Inc.", - "license": "MIT", - "devDependencies": { - "@openai/agents": "^0.0.15", - "@types/node": "^20.10.5", - "braintrust": "workspace:^", - "tsup": "^8.5.1", - "typescript": "^5.3.3", - "vitest": "4.1.11", - "zod": "^3.25.34" - }, - "peerDependencies": { - "braintrust": ">=0.4.0" - }, - "repository": { - "type": "git", - "url": "git+https://github.com/braintrustdata/braintrust-sdk-javascript.git", - "directory": "integrations/openai-agents-js" - }, - "homepage": "https://www.braintrust.dev/docs", - "publishConfig": { - "access": "public", - "registry": "https://registry.npmjs.org/", - "provenance": true - } -} diff --git a/integrations/openai-agents-js/src/index.ts b/integrations/openai-agents-js/src/index.ts deleted file mode 100644 index 167660f5f..000000000 --- a/integrations/openai-agents-js/src/index.ts +++ /dev/null @@ -1,670 +0,0 @@ -/* eslint-disable @typescript-eslint/no-explicit-any */ -import { SpanTypeAttribute } from "braintrust/util"; -import { - Span as BraintrustSpan, - startSpan, - Logger, - currentSpan, - NOOP_SPAN, - Attachment, -} from "braintrust"; -import { - SpanType, - AgentsTrace, - AgentsSpan, - SpanInput, - SpanOutput, - TraceMetadata, - OpenAIAgentsTraceProcessorOptions, - isResponseSpanData, - isGenerationSpanData, - isAgentSpanData, - isFunctionSpanData, - isHandoffSpanData, - isGuardrailSpanData, - isCustomSpanData, - isMCPListToolsSpanData, - isTranscriptionSpanData, - isSpeechSpanData, - isSpeechGroupSpanData, -} from "./types"; - -function spanTypeFromAgents(span: AgentsSpan): SpanTypeAttribute { - const spanType = span.spanData.type; - - if ( - spanType === SpanType.AGENT || - spanType === SpanType.HANDOFF || - spanType === SpanType.CUSTOM || - spanType === SpanType.SPEECH_GROUP - ) { - return SpanTypeAttribute.TASK; - } - - if ( - spanType === SpanType.FUNCTION || - spanType === SpanType.GUARDRAIL || - spanType === SpanType.MCP_TOOLS - ) { - return SpanTypeAttribute.TOOL; - } - - if ( - spanType === SpanType.GENERATION || - spanType === SpanType.RESPONSE || - spanType === SpanType.TRANSCRIPTION || - spanType === SpanType.SPEECH - ) { - return SpanTypeAttribute.LLM; - } - - return SpanTypeAttribute.TASK; -} - -function spanNameFromAgents(span: AgentsSpan): string { - const spanData = span.spanData; - - if ("name" in spanData && spanData.name) { - return spanData.name; - } - - switch (spanData.type) { - case SpanType.GENERATION: - return "Generation"; - case SpanType.RESPONSE: - return "Response"; - case SpanType.HANDOFF: - return "Handoff"; - case SpanType.MCP_TOOLS: - if (isMCPListToolsSpanData(spanData) && spanData.server) { - return `List Tools (${spanData.server})`; - } - return "MCP List Tools"; - case SpanType.TRANSCRIPTION: - return "Transcription"; - case SpanType.SPEECH: - return "Speech"; - case SpanType.SPEECH_GROUP: - return "Speech Group"; - case SpanType.AGENT: - case SpanType.FUNCTION: - case SpanType.GUARDRAIL: - case SpanType.CUSTOM: - return "name" in spanData && spanData.name ? spanData.name : "Unknown"; - default: - return "Unknown"; - } -} - -function getTimeElapsed(end?: string, start?: string): number | undefined { - if (!start || !end) return undefined; - const startTime = new Date(start).getTime(); - const endTime = new Date(end).getTime(); - if (isNaN(startTime) || isNaN(endTime)) return undefined; - return (endTime - startTime) / 1000; -} - -/** - * `OpenAIAgentsTraceProcessor` is a tracing processor that logs traces from the OpenAI Agents SDK to Braintrust. - * - * Args: - * options: Configuration options including: - * - logger: A `Span`, `Experiment`, or `Logger` to use for logging. - * If `undefined`, the current span, experiment, or logger will be selected exactly as in `startSpan`. - * - maxTraces: Maximum number of concurrent traces to keep in memory (default: 1000). - * When exceeded, oldest traces are evicted using LRU policy. - * - * @deprecated Import `OpenAIAgentsTraceProcessor` from `braintrust/instrumentation` instead. This package will stop being published after the next release. - * */ - -export class OpenAIAgentsTraceProcessor { - private static readonly DEFAULT_MAX_TRACES = 10000; - - private logger?: Logger; - private maxTraces: number; - private traceSpans = new Map< - string, - { - rootSpan: BraintrustSpan; - childSpans: Map; - metadata: TraceMetadata; - } - >(); - private traceOrder: string[] = []; - - // Expose for testing purposes - public readonly _traceSpans = this.traceSpans; - - constructor(options: OpenAIAgentsTraceProcessorOptions = {}) { - this.logger = options.logger; - this.maxTraces = - options.maxTraces ?? OpenAIAgentsTraceProcessor.DEFAULT_MAX_TRACES; - } - - private processInputImages(input: any): any { - if (Array.isArray(input)) { - return input.map((item) => this.processInputImages(item)); - } - - if (input && typeof input === "object") { - // Handle input_image type with base64 image data - if (input.type === "input_image" && typeof input.image === "string") { - let imageData = input.image; - - // Strip data URI prefix if present (e.g., "data:image/png;base64,") - const dataUriMatch = imageData.match(/^data:image\/(\w+);base64,(.*)$/); - let contentType = "image/png"; - let fileExtension = "png"; - - if (dataUriMatch) { - fileExtension = dataUriMatch[1]; - contentType = `image/${fileExtension}`; - imageData = dataUriMatch[2]; // Extract just the base64 part - } - - const filename = `input_image.${fileExtension}`; - - try { - // Convert base64 string to Blob - const binaryString = atob(imageData); - const bytes = new Uint8Array(binaryString.length); - for (let i = 0; i < binaryString.length; i++) { - bytes[i] = binaryString.charCodeAt(i); - } - const blob = new Blob([bytes], { type: contentType }); - - const attachment = new Attachment({ - data: blob, - filename: filename, - contentType: contentType, - }); - - return { - ...input, - image: attachment, - }; - } catch (e) { - console.error("Failed to process input image:", e); - console.error("Image data sample:", input.image.substring(0, 200)); - return input; - } - } - - // Recursively process nested objects - const result: any = {}; - for (const [key, value] of Object.entries(input)) { - result[key] = this.processInputImages(value); - } - return result; - } - - return input; - } - - private processOutputImages(output: any): any { - if (Array.isArray(output)) { - return output.map((item) => this.processOutputImages(item)); - } - - if (output && typeof output === "object") { - // Handle image_generation_call type - convert result to attachment - if (output.type === "image_generation_call" && output.result) { - let resultData = output.result; - - // Use output_format from the response - const fileExtension = output.output_format || "png"; - const contentType = `image/${fileExtension}`; - - // Strip data URI prefix if present (e.g., "data:image/png;base64,") - const dataUriMatch = resultData.match(/^data:image\/\w+;base64,(.*)$/); - if (dataUriMatch) { - resultData = dataUriMatch[1]; // Extract just the base64 part - } - - const baseFilename = - output.revised_prompt && typeof output.revised_prompt === "string" - ? output.revised_prompt.slice(0, 50).replace(/[^a-zA-Z0-9]/g, "_") - : "generated_image"; - const filename = `${baseFilename}.${fileExtension}`; - - try { - // Convert base64 string to Blob - const binaryString = atob(resultData); - const bytes = new Uint8Array(binaryString.length); - for (let i = 0; i < binaryString.length; i++) { - bytes[i] = binaryString.charCodeAt(i); - } - const blob = new Blob([bytes], { type: contentType }); - - const attachment = new Attachment({ - data: blob, - filename: filename, - contentType: contentType, - }); - - return { - ...output, - result: attachment, - }; - } catch (e) { - console.error("Failed to process output image:", e); - console.error("Result data sample:", output.result.substring(0, 200)); - return output; - } - } - - // Recursively process nested objects - const result: any = {}; - for (const [key, value] of Object.entries(output)) { - result[key] = this.processOutputImages(value); - } - return result; - } - - return output; - } - - private evictOldestTrace(): void { - if (this.traceOrder.length === 0) return; - - const oldestTraceId = this.traceOrder.shift()!; - this.traceSpans.delete(oldestTraceId); - } - - onTraceStart(trace: AgentsTrace): Promise { - if (this.traceOrder.length >= this.maxTraces) { - this.evictOldestTrace(); - } - - // Detect parent span from current execution context - let span: BraintrustSpan; - const current = currentSpan(); - const spanArgs = { - name: trace.name, - type: SpanTypeAttribute.TASK, - [Symbol.for("braintrust.spanInstrumentationName")]: "openai-agents", - }; - - if (current && current !== NOOP_SPAN) { - // Create as child of current span - span = current.startSpan(spanArgs); - } else { - // No parent span available, create as root - span = this.logger - ? this.logger.startSpan(spanArgs) - : startSpan(spanArgs); - } - - span.log({ - input: "Agent workflow started", - metadata: { - group_id: trace.groupId, - ...(trace.metadata || {}), - }, - }); - - this.traceSpans.set(trace.traceId, { - rootSpan: span, - childSpans: new Map(), - metadata: { - firstInput: null, - lastOutput: null, - }, - }); - this.traceOrder.push(trace.traceId); - - return Promise.resolve(); - } - - async onTraceEnd(trace: AgentsTrace): Promise { - const traceData = this.traceSpans.get(trace.traceId); - - if (traceData) { - for (const [spanId, braintrustSpan] of traceData.childSpans) { - braintrustSpan.end(); - traceData.childSpans.delete(spanId); - } - - try { - traceData.rootSpan.log({ - input: traceData.metadata.firstInput, - output: traceData.metadata.lastOutput, - }); - traceData.rootSpan.end(); - await traceData.rootSpan.flush(); - } finally { - this.traceSpans.delete(trace.traceId); - const orderIndex = this.traceOrder.indexOf(trace.traceId); - if (orderIndex > -1) { - this.traceOrder.splice(orderIndex, 1); - } - } - } - } - - private extractAgentLogData(span: AgentsSpan): Record { - const spanData = span.spanData; - if (!isAgentSpanData(spanData)) { - return {}; - } - - const data: Record = { - metadata: { - tools: spanData.tools, - handoffs: spanData.handoffs, - output_type: spanData.output_type, - }, - }; - - return data; - } - - private extractResponseLogData(span: AgentsSpan): Record { - const spanData = span.spanData; - const data: Record = {}; - - if (!isResponseSpanData(spanData)) { - return data; - } - - if (spanData._input !== undefined) { - data.input = this.processInputImages(spanData._input); - } - - if (spanData._response !== undefined) { - data.output = this.processOutputImages(spanData._response.output); - } - - if (spanData._response) { - const { output, metadata, usage, output_text, ...otherFields } = - spanData._response; - data.metadata = otherFields; - } - - data.metrics = {}; - const ttft = getTimeElapsed( - span.endedAt ?? undefined, - span.startedAt ?? undefined, - ); - if (ttft !== undefined) { - data.metrics.time_to_first_token = ttft; - } - - let usage: any = null; - if (spanData._response?.usage) { - usage = spanData._response.usage; - } - - if (usage) { - if (usage.total_tokens) data.metrics.tokens = usage.total_tokens; - if (usage.input_tokens) data.metrics.prompt_tokens = usage.input_tokens; - if (usage.output_tokens) - data.metrics.completion_tokens = usage.output_tokens; - - if (!data.metrics.tokens && usage.totalTokens) - data.metrics.tokens = usage.totalTokens; - if (!data.metrics.prompt_tokens && usage.inputTokens) - data.metrics.prompt_tokens = usage.inputTokens; - if (!data.metrics.prompt_tokens && usage.promptTokens) - data.metrics.prompt_tokens = usage.promptTokens; - if (!data.metrics.completion_tokens && usage.outputTokens) - data.metrics.completion_tokens = usage.outputTokens; - if (!data.metrics.completion_tokens && usage.completionTokens) - data.metrics.completion_tokens = usage.completionTokens; - if (usage.input_tokens_details?.cached_tokens != null) - data.metrics.prompt_cached_tokens = - usage.input_tokens_details.cached_tokens; - if (usage.input_tokens_details?.cache_write_tokens != null) - data.metrics.prompt_cache_creation_tokens = - usage.input_tokens_details.cache_write_tokens; - } - - return data; - } - - private extractFunctionLogData(span: AgentsSpan): Record { - const spanData = span.spanData; - if (!isFunctionSpanData(spanData)) { - return {}; - } - - return { - input: spanData.input, - output: spanData.output, - }; - } - - private extractHandoffLogData(span: AgentsSpan): Record { - const spanData = span.spanData; - if (!isHandoffSpanData(spanData)) { - return {}; - } - return { - metadata: { - from_agent: spanData.from_agent, - to_agent: spanData.to_agent, - }, - }; - } - - private extractGuardrailLogData(span: AgentsSpan): Record { - const spanData = span.spanData; - if (!isGuardrailSpanData(spanData)) { - return {}; - } - return { - metadata: { - triggered: spanData.triggered, - }, - }; - } - - private extractGenerationLogData(span: AgentsSpan): Record { - const spanData = span.spanData; - if (!isGenerationSpanData(spanData)) { - return {}; - } - - const metrics: Record = {}; - - const ttft = getTimeElapsed( - span.endedAt ?? undefined, - span.startedAt ?? undefined, - ); - if (ttft !== undefined) { - metrics.time_to_first_token = ttft; - } - - const usage = spanData.usage || {}; - if (usage.prompt_tokens) metrics.prompt_tokens = usage.prompt_tokens; - else if (usage.input_tokens) metrics.prompt_tokens = usage.input_tokens; - - if (usage.completion_tokens) - metrics.completion_tokens = usage.completion_tokens; - else if (usage.output_tokens) - metrics.completion_tokens = usage.output_tokens; - - if (usage.total_tokens) metrics.tokens = usage.total_tokens; - else if (usage.input_tokens && usage.output_tokens) { - metrics.tokens = usage.input_tokens + usage.output_tokens; - } - - if (usage.input_tokens_details?.cached_tokens != null) - metrics.prompt_cached_tokens = usage.input_tokens_details.cached_tokens; - - return { - input: spanData.input, - output: spanData.output, - metadata: { - model: spanData.model, - model_config: spanData.model_config, - }, - metrics, - }; - } - - private extractCustomLogData(span: AgentsSpan): Record { - const spanData = span.spanData; - if (!isCustomSpanData(spanData)) { - return {}; - } - return spanData.data || {}; - } - - private extractMCPListToolsLogData( - span: AgentsSpan, - ): Record { - const spanData = span.spanData; - if (!isMCPListToolsSpanData(spanData)) { - return {}; - } - return { - output: spanData.result, - metadata: { - server: spanData.server, - }, - }; - } - - private extractTranscriptionLogData( - span: AgentsSpan, - ): Record { - const spanData = span.spanData; - if (!isTranscriptionSpanData(spanData)) { - return {}; - } - return { - input: spanData.input, - output: spanData.output, - metadata: { - model: spanData.model, - model_config: spanData.model_config, - }, - }; - } - - private extractSpeechLogData(span: AgentsSpan): Record { - const spanData = span.spanData; - if (!isSpeechSpanData(spanData)) { - return {}; - } - return { - input: spanData.input, - output: spanData.output, - metadata: { - model: spanData.model, - model_config: spanData.model_config, - }, - }; - } - - private extractSpeechGroupLogData(span: AgentsSpan): Record { - const spanData = span.spanData; - if (!isSpeechGroupSpanData(spanData)) { - return {}; - } - return { - input: spanData.input, - }; - } - - private extractLogData(span: AgentsSpan): Record { - const spanType = span.spanData?.type; - - switch (spanType) { - case SpanType.AGENT: - return this.extractAgentLogData(span); - case SpanType.RESPONSE: - return this.extractResponseLogData(span); - case SpanType.FUNCTION: - return this.extractFunctionLogData(span); - case SpanType.HANDOFF: - return this.extractHandoffLogData(span); - case SpanType.GUARDRAIL: - return this.extractGuardrailLogData(span); - case SpanType.GENERATION: - return this.extractGenerationLogData(span); - case SpanType.CUSTOM: - return this.extractCustomLogData(span); - case SpanType.MCP_TOOLS: - return this.extractMCPListToolsLogData(span); - case SpanType.TRANSCRIPTION: - return this.extractTranscriptionLogData(span); - case SpanType.SPEECH: - return this.extractSpeechLogData(span); - case SpanType.SPEECH_GROUP: - return this.extractSpeechGroupLogData(span); - default: - return {}; - } - } - - onSpanStart(span: AgentsSpan): Promise { - if (!span.spanId || !span.traceId) return Promise.resolve(); - - const traceData = this.traceSpans.get(span.traceId); - if (!traceData) return Promise.resolve(); - - let parentSpan: BraintrustSpan | undefined; - if (span.parentId) { - parentSpan = traceData.childSpans.get(span.parentId); - } else { - parentSpan = traceData.rootSpan; - } - - if (parentSpan) { - const childSpan = parentSpan.startSpan({ - name: spanNameFromAgents(span), - type: spanTypeFromAgents(span), - [Symbol.for("braintrust.spanInstrumentationName")]: "openai-agents", - }); - traceData.childSpans.set(span.spanId, childSpan); - } - return Promise.resolve(); - } - - onSpanEnd(span: AgentsSpan): Promise { - if (!span.spanId || !span.traceId) return Promise.resolve(); - - const traceData = this.traceSpans.get(span.traceId); - if (!traceData) return Promise.resolve(); - - const braintrustSpan = traceData.childSpans.get(span.spanId); - - if (braintrustSpan) { - const logData = this.extractLogData(span); - braintrustSpan.log({ - error: span.error, - ...logData, - }); - braintrustSpan.end(); - traceData.childSpans.delete(span.spanId); - - const input = logData.input as SpanInput; - const output = logData.output as SpanOutput; - - if (traceData.metadata.firstInput === null && input != null) { - traceData.metadata.firstInput = input; - } - - if (output != null) { - traceData.metadata.lastOutput = output; - } - } else { - console.warn(`No span found for ID: ${span.spanId}`); - } - return Promise.resolve(); - } - - async shutdown(): Promise { - if (this.logger && typeof this.logger.flush === "function") { - await this.logger.flush(); - } - } - - async forceFlush(): Promise { - if (this.logger && typeof this.logger.flush === "function") { - await this.logger.flush(); - } - } -} diff --git a/integrations/openai-agents-js/src/openai-agents-integration.test.ts b/integrations/openai-agents-js/src/openai-agents-integration.test.ts deleted file mode 100644 index 317d04b7e..000000000 --- a/integrations/openai-agents-js/src/openai-agents-integration.test.ts +++ /dev/null @@ -1,1262 +0,0 @@ -import { - test, - assert, - beforeEach, - beforeAll, - afterEach, - describe, -} from "vitest"; -import { OpenAIAgentsTraceProcessor } from "./index"; -import { z } from "zod/v3"; - -// Import necessary types and functions from braintrust -import { - _exportsForTestingOnly, - initLogger, - Logger, - TestBackgroundLogger, - Span as BraintrustSpan, - wrapTraced, - currentSpan, -} from "braintrust"; - -// Test helper functions for backward compatibility -function getSpansMap( - processor: OpenAIAgentsTraceProcessor, -): Map { - const spans = new Map(); - for (const [traceId, traceData] of processor._traceSpans) { - spans.set(traceId, traceData.rootSpan); - for (const [spanId, span] of traceData.childSpans) { - spans.set(`${traceId}:${spanId}`, span); - } - } - return spans; -} - -function getTraceMetadataMap(processor: OpenAIAgentsTraceProcessor) { - const metadata = new Map(); - for (const [traceId, traceData] of processor._traceSpans) { - metadata.set(traceId, traceData.metadata); - } - return metadata; -} - -const TEST_SUITE_OPTIONS = { timeout: 30000, retry: 3 }; - -// Simple test model for OpenAI Agents -const TEST_MODEL = "gpt-4o-mini"; - -// Node.js configuration is automatically handled when importing from braintrust - -// Test with real @openai/agents SDK calls (requires OPENAI_API_KEY) -describe( - "OpenAI Agents tracing processor integration tests", - TEST_SUITE_OPTIONS, - () => { - let backgroundLogger: TestBackgroundLogger; - let _logger: Logger; - let Agent: any; - let run: any; - let tool: any; - let setTraceProcessors: any; - let addTraceProcessor: any; - let setTracingDisabled: any; - - beforeAll(async () => { - await _exportsForTestingOnly.simulateLoginForTests(); - - // Dynamically import @openai/agents to handle cases where it's not available - try { - const agentsModule = await import("@openai/agents"); - Agent = agentsModule.Agent; - run = agentsModule.run; - tool = agentsModule.tool; - setTraceProcessors = agentsModule.setTraceProcessors; - addTraceProcessor = agentsModule.addTraceProcessor; - setTracingDisabled = agentsModule.setTracingDisabled; - } catch (error) { - console.warn( - "@openai/agents not available, skipping integration tests", - ); - } - }); - - beforeEach(() => { - backgroundLogger = _exportsForTestingOnly.useTestBackgroundLogger(); - _logger = initLogger({ - projectName: "openai-agents.test.ts", - projectId: "test-openai-agents", - }); - }); - - afterEach(() => { - if (_logger) { - _logger.flush(); - } - if (setTraceProcessors) { - setTraceProcessors([]); - } - _exportsForTestingOnly.clearTestBackgroundLogger(); - }); - - test("OpenAIAgentsTraceProcessor is instantiable", () => { - const processor = new OpenAIAgentsTraceProcessor({ - logger: _logger as any, - }); - assert.ok(processor); - - // Test methods exist - assert.ok(typeof processor.onTraceStart === "function"); - assert.ok(typeof processor.onTraceEnd === "function"); - assert.ok(typeof processor.onSpanStart === "function"); - assert.ok(typeof processor.onSpanEnd === "function"); - assert.ok(typeof processor.shutdown === "function"); - assert.ok(typeof processor.forceFlush === "function"); - }); - - test("simple agent run with tracing", async (context) => { - assert.lengthOf(await backgroundLogger.drain(), 0); - - // Set up the OpenAI Agents tracing processor - const processor = new OpenAIAgentsTraceProcessor({ - logger: _logger as any, - }); - - setTracingDisabled(false); - addTraceProcessor(processor); - - try { - // Create a simple agent - const agent = new Agent({ - name: "test-agent", - model: TEST_MODEL, - instructions: "You are a helpful assistant. Be concise.", - }); - - // Run the agent with a simple prompt using the run() function - const result = await run(agent, "What is 2+2? Just give the number."); - assert.ok(result); - assert.ok(result.finalOutput); - - // Verify spans were created - const spans = await backgroundLogger.drain(); - assert.isTrue( - spans.length > 0, - "Expected at least one span to be created", - ); - - // Verify span structure - const traceSpan = spans.find( - (s: any) => s.span_attributes?.type === "task", - ); - assert.ok( - traceSpan, - "Expected to find a task-type span for the agent trace", - ); - assert.equal((traceSpan as any).span_attributes.name, "Agent workflow"); - } finally { - processor.shutdown(); - } - }); - - test("agent with function calling", async (context) => { - assert.lengthOf(await backgroundLogger.drain(), 0); - - const processor = new OpenAIAgentsTraceProcessor({ - logger: _logger as any, - }); - - setTracingDisabled(false); - addTraceProcessor(processor); - - try { - // Create a tool using the proper tool() helper - const getWeatherTool = tool({ - name: "get_weather", - description: "Get the current weather for a city", - parameters: z.object({ city: z.string() }), - execute: async (input: { city: string }) => { - return `The weather in ${input.city} is sunny with temperature 72°F`; - }, - }); - - // Create agent with the tool - const agent = new Agent({ - name: "weather-agent", - model: TEST_MODEL, - instructions: - "You can get the weather for any city. Use the get_weather tool when asked about weather.", - tools: [getWeatherTool], - }); - - const result = await run(agent, "What's the weather in San Francisco?"); - assert.ok(result); - assert.ok(result.finalOutput); - - // Verify spans were created - const spans = await backgroundLogger.drain(); - assert.isTrue(spans.length > 0); - - // Verify span structure - const taskSpans = spans.filter( - (s: any) => s.span_attributes?.type === "task", - ); - assert.isTrue(taskSpans.length > 0, "Expected task-type spans"); - - // Verify tool spans if function calling occurred - const toolSpans = spans.filter( - (s: any) => s.span_attributes?.type === "tool", - ); - if (toolSpans.length > 0) { - const toolSpan = toolSpans[0] as any; - assert.ok( - toolSpan.span_attributes.name, - "Tool span should have a name", - ); - } - } finally { - processor.shutdown(); - } - }); - - test("Cleanup behavior - traces are cleaned up properly and orphaned spans are handled gracefully", async () => { - const processor = new OpenAIAgentsTraceProcessor({ - logger: _logger as any, - }); - - const trace: any = { - traceId: "test-trace-cleanup", - name: "cleanup-test", - metadata: {}, - }; - - // Start trace and verify it's stored - await processor.onTraceStart(trace); - assert.isTrue( - getSpansMap(processor).has(trace.traceId), - "Root span should be stored", - ); - assert.isTrue( - getTraceMetadataMap(processor).has(trace.traceId), - "Trace metadata should be stored", - ); - - // Add a child span - const span = { - spanId: "test-span-cleanup", - traceId: trace.traceId, - spanData: { type: "agent", name: "test-agent" }, - error: null, - } as any; - - await processor.onSpanStart(span); - const childSpanKey = `${trace.traceId}:${span.spanId}`; - assert.isTrue( - getSpansMap(processor).has(childSpanKey), - "Child span should be stored", - ); - - // End the child span first - await processor.onSpanEnd(span); - - // End the trace normally - await processor.onTraceEnd(trace); - - // Verify cleanup happened - assert.isFalse( - getSpansMap(processor).has(trace.traceId), - "Root span should be removed", - ); - assert.isFalse( - getTraceMetadataMap(processor).has(trace.traceId), - "Trace metadata should be removed", - ); - - // Test that operations with orphaned spans are handled gracefully - const orphanedSpan = { - spanId: "orphaned-span", - traceId: "test-trace-cleanup", // Same traceId but trace is now gone - spanData: { type: "agent", name: "orphaned" }, - error: null, - } as any; - - // These should be no-ops and not throw - await processor.onSpanStart(orphanedSpan); - await processor.onSpanEnd(orphanedSpan); - - // Verify the orphaned operations didn't create any trace data - assert.isFalse( - getSpansMap(processor).has("test-trace-cleanup"), - "Orphaned operations shouldn't recreate root span", - ); - assert.isFalse( - getTraceMetadataMap(processor).has("test-trace-cleanup"), - "Orphaned operations shouldn't recreate metadata", - ); - assert.equal( - getSpansMap(processor).size, - 0, - "No spans should exist after cleanup", - ); - assert.equal( - getTraceMetadataMap(processor).size, - 0, - "No metadata should exist after cleanup", - ); - }); - - test("LRU eviction behavior - oldest traces are evicted when maxTraces is exceeded", async () => { - // Use a small maxTraces for fast testing - const maxTraces = 3; - const processor = new OpenAIAgentsTraceProcessor({ maxTraces }); - - // Create traces up to the limit - const traces: any[] = []; - for (let i = 0; i < maxTraces; i++) { - const trace = { - traceId: `test-trace-${i}`, - name: `test-${i}`, - metadata: {}, - } as any; - traces.push(trace); - await processor.onTraceStart(trace); - } - - // Verify all traces are stored - assert.equal( - getTraceMetadataMap(processor).size, - maxTraces, - "All trace metadata should be stored", - ); - assert.isTrue( - getSpansMap(processor).has("test-trace-0"), - "First trace root span should exist", - ); - assert.isTrue( - getSpansMap(processor).has("test-trace-1"), - "Second trace root span should exist", - ); - assert.isTrue( - getSpansMap(processor).has("test-trace-2"), - "Third trace root span should exist", - ); - - // Add one more trace - this should trigger LRU eviction - const newTrace = { - traceId: "test-trace-new", - name: "test-new", - metadata: {}, - } as any; - await processor.onTraceStart(newTrace); - - // Metadata should still be at max size - assert.equal( - getTraceMetadataMap(processor).size, - maxTraces, - "Metadata should remain at max size after eviction", - ); - - // First trace should be evicted, new trace should exist - assert.isFalse( - getSpansMap(processor).has("test-trace-0"), - "First (oldest) trace should be evicted", - ); - assert.isFalse( - getTraceMetadataMap(processor).has("test-trace-0"), - "First trace metadata should be evicted", - ); - assert.isTrue( - getSpansMap(processor).has("test-trace-new"), - "New trace should exist", - ); - assert.isTrue( - getTraceMetadataMap(processor).has("test-trace-new"), - "New trace metadata should exist", - ); - assert.isTrue( - getSpansMap(processor).has("test-trace-1"), - "Second trace should still exist", - ); - assert.isTrue( - getSpansMap(processor).has("test-trace-2"), - "Third trace should still exist", - ); - }); - - test("Span hierarchy and storage validation - ensures proper parent-child relationships", async () => { - const processor = new OpenAIAgentsTraceProcessor({ - logger: _logger as any, - }); - - // Create a trace - const trace: any = { - traceId: "test-hierarchy-trace", - name: "hierarchy-test", - metadata: {}, - }; - - await processor.onTraceStart(trace); - assert.isTrue( - getSpansMap(processor).has(trace.traceId), - "Root span should be stored by traceId", - ); - assert.isTrue( - getTraceMetadataMap(processor).has(trace.traceId), - "Trace metadata should be stored", - ); - - // Create parent span (no parentId, should attach to root) - const parentSpan = { - spanId: "parent-span-1", - traceId: trace.traceId, - parentId: null, - spanData: { type: "agent", name: "parent-agent" }, - error: null, - } as any; - - await processor.onSpanStart(parentSpan); - const parentSpanKey = `${trace.traceId}:${parentSpan.spanId}`; - assert.isTrue( - getSpansMap(processor).has(parentSpanKey), - "Parent span should be stored with composite key", - ); - - // Create child span (with parentId, should attach to parent) - const childSpan = { - spanId: "child-span-1", - traceId: trace.traceId, - parentId: parentSpan.spanId, - spanData: { - type: "function", - name: "child-function", - input: "test input", - output: "test output", - }, - error: null, - } as any; - - await processor.onSpanStart(childSpan); - const childSpanKey = `${trace.traceId}:${childSpan.spanId}`; - assert.isTrue( - getSpansMap(processor).has(childSpanKey), - "Child span should be stored with composite key", - ); - - // Create grandchild span - const grandchildSpan = { - spanId: "grandchild-span-1", - traceId: trace.traceId, - parentId: childSpan.spanId, - spanData: { - type: "generation", - name: "grandchild-generation", - input: [{ role: "user", content: "test" }], - output: [{ role: "assistant", content: "response" }], - }, - error: null, - } as any; - - await processor.onSpanStart(grandchildSpan); - const grandchildSpanKey = `${trace.traceId}:${grandchildSpan.spanId}`; - assert.isTrue( - getSpansMap(processor).has(grandchildSpanKey), - "Grandchild span should be stored with composite key", - ); - - // Verify we have the expected number of spans - const allSpanKeys = Array.from(getSpansMap(processor).keys()); - const traceSpans = allSpanKeys.filter((key) => - key.startsWith(trace.traceId), - ); - assert.equal( - traceSpans.length, - 4, - "Should have 4 spans total: 1 root + 3 child spans", - ); - - // End spans in reverse order (grandchild -> child -> parent) - await processor.onSpanEnd(grandchildSpan); - assert.isFalse( - getSpansMap(processor).has(grandchildSpanKey), - "Grandchild span should be removed after ending", - ); - - await processor.onSpanEnd(childSpan); - assert.isFalse( - getSpansMap(processor).has(childSpanKey), - "Child span should be removed after ending", - ); - - await processor.onSpanEnd(parentSpan); - assert.isFalse( - getSpansMap(processor).has(parentSpanKey), - "Parent span should be removed after ending", - ); - - // Root span should still exist - assert.isTrue( - getSpansMap(processor).has(trace.traceId), - "Root span should still exist", - ); - assert.isTrue( - getTraceMetadataMap(processor).has(trace.traceId), - "Trace metadata should still exist", - ); - - // End the trace - await processor.onTraceEnd(trace); - assert.isFalse( - getSpansMap(processor).has(trace.traceId), - "Root span should be removed after trace end", - ); - assert.isFalse( - getTraceMetadataMap(processor).has(trace.traceId), - "Trace metadata should be removed after trace end", - ); - - // Verify all spans are cleaned up - const remainingSpans = Array.from(getSpansMap(processor).keys()).filter( - (key) => key.startsWith(trace.traceId), - ); - assert.equal( - remainingSpans.length, - 0, - "No spans should remain for this trace", - ); - }); - - test("currentSpan() detection creates proper span hierarchy with actual OpenAI SDK", async () => { - // This tests that currentSpan() detection works with the real OpenAI SDK - assert.lengthOf(await backgroundLogger.drain(), 0); - - const testFunction = wrapTraced( - async (instructions: string) => { - // Verify we're in a traced context - const detectedParent = currentSpan(); - assert.ok( - detectedParent, - "Parent span should exist in traced context", - ); - - // Create processor WITHOUT parentSpan - should auto-detect via currentSpan() - const processor = new OpenAIAgentsTraceProcessor({ - logger: _logger as any, - }); - - setTracingDisabled(false); - addTraceProcessor(processor); - - try { - // Create a simple agent - const agent = new Agent({ - name: "test-agent", - model: TEST_MODEL, - instructions: "You are a helpful assistant. Be very concise.", - }); - - // Run the agent - this should create spans as children of detected parent - const result = await run(agent, instructions); - assert.ok(result, "Agent should return a result"); - assert.ok(result.finalOutput, "Result should have finalOutput"); - - return result; - } finally { - processor.shutdown(); - } - }, - { name: "parent_span_test" }, - ); - - // Execute the wrapped function - const result = await testFunction("What is 2+2? Just the number."); - assert.ok(result, "Test function should return a result"); - - // Verify span hierarchy in logged spans - const spans = await backgroundLogger.drain(); - assert.isTrue( - spans.length >= 2, - "Should have at least parent and child spans", - ); - - // Find parent and child spans - const parentSpan = spans.find( - (s: any) => s.span_attributes?.name === "parent_span_test", - ); - const childSpan = spans.find( - (s: any) => s.span_attributes?.name === "Agent workflow", - ); - - assert.ok( - parentSpan, - "Should find parent span with name 'parent_span_test'", - ); - assert.ok(childSpan, "Should find child span with name 'Agent workflow'"); - - // Verify the child span has the parent as its parent - if (childSpan && parentSpan) { - // In Braintrust, parent-child relationships are represented by span_parents array - const childSpanParents = (childSpan as any).span_parents || []; - const parentSpanId = (parentSpan as any).span_id; - - assert.ok( - Array.isArray(childSpanParents) && childSpanParents.length > 0, - "Child span should have span_parents array", - ); - assert.isTrue( - childSpanParents.includes(parentSpanId), - "Child span should include parent span_id in its span_parents array (currentSpan detection)", - ); - - // Verify both spans have the same root_span_id - assert.equal( - (childSpan as any).root_span_id, - (parentSpan as any).root_span_id, - "Parent and child should share the same root_span_id", - ); - } - - // Verify input/output are properly logged on parent span - assert.ok( - (parentSpan as any).input, - "Parent span should have input logged", - ); - assert.ok( - (parentSpan as any).output, - "Parent span should have output logged", - ); - - // Verify that we have child spans beyond just "Agent workflow" - // The OpenAI SDK should generate multiple span types (generation, response, etc.) - const allChildSpans = spans.filter((s: any) => - (s as any).span_parents?.includes((parentSpan as any).span_id), - ); - - assert.isTrue( - allChildSpans.length >= 1, - `Should have at least 1 child span, but found ${allChildSpans.length}`, - ); - - // We should see spans like Generation, Response, etc. from the OpenAI SDK - const spanTypes = allChildSpans.map((s: any) => s.span_attributes?.type); - const hasLLMSpans = spanTypes.includes("llm"); - const hasTaskSpans = spanTypes.includes("task"); - - assert.isTrue( - hasLLMSpans || hasTaskSpans, - "Should have LLM or task type spans from OpenAI SDK", - ); - }); - - test("processor without parentSpan creates root spans (backward compatibility)", async () => { - // This ensures backward compatibility when parentSpan is not provided - assert.lengthOf(await backgroundLogger.drain(), 0); - - const processor = new OpenAIAgentsTraceProcessor({ - logger: _logger as any, - // No parentSpan provided - should create root spans - }); - - setTracingDisabled(false); - addTraceProcessor(processor); - - try { - const agent = new Agent({ - name: "root-agent", - model: TEST_MODEL, - instructions: "Be concise.", - }); - - const result = await run(agent, "What is 2+2?"); - assert.ok(result); - - const spans = await backgroundLogger.drain(); - assert.isTrue(spans.length > 0, "Should create spans"); - - // Find the Agent workflow span - const agentSpan = spans.find( - (s: any) => s.span_attributes?.name === "Agent workflow", - ); - assert.ok(agentSpan, "Should find Agent workflow span"); - - // Verify it's a root span (no parent_id or parent_id is null) - const isRootSpan = - !(agentSpan as any).parent_id || - (agentSpan as any).parent_id === null; - assert.isTrue( - isRootSpan, - "Agent workflow should be a root span when no parentSpan provided", - ); - } finally { - processor.shutdown(); - } - }); - - test("onTraceEnd logs trace metadata properly", async () => { - const processor = new OpenAIAgentsTraceProcessor({ - logger: _logger as any, - }); - - const trace: any = { - traceId: "test-trace-metadata", - name: "metadata-test", - metadata: { userId: "test-user-123", sessionId: "session-456" }, - }; - - const span = { - spanId: "test-span", - traceId: trace.traceId, - spanData: { - type: "response", - name: "test-response", - _input: "test input", - _response: { output: "test output" }, - }, - error: null, - } as any; - - // Execute trace lifecycle - await processor.onTraceStart(trace); - await processor.onSpanStart(span); - await processor.onSpanEnd(span); - await processor.onTraceEnd(trace); - - // Verify metadata was logged to root span - const spans = await backgroundLogger.drain(); - const rootSpan = spans.find( - (s: any) => s.span_attributes?.name === "metadata-test", - ); - - assert.ok(rootSpan, "Should find root span"); - const spanMetadata = (rootSpan as any).metadata; - assert.ok(spanMetadata, "Root span should have metadata"); - assert.equal( - spanMetadata.userId, - "test-user-123", - "Should log trace metadata", - ); - assert.equal( - spanMetadata.sessionId, - "session-456", - "Should log trace metadata", - ); - }); - - test("response span with image generation converts base64 to attachments", async () => { - const processor = new OpenAIAgentsTraceProcessor({ - logger: _logger as any, - }); - - const trace: any = { - traceId: "test-trace-images", - name: "image-test", - metadata: {}, - }; - - // Create a small base64-encoded PNG (1x1 red pixel) - const smallBase64Image = - "iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVR42mP8z8DwHwAFBQIAX8jx0gAAAABJRU5ErkJggg=="; - - // Simulate response span with image input and output - const span = { - spanId: "test-span-images", - traceId: trace.traceId, - spanData: { - type: "response", - name: "test-response-images", - _input: [ - { - role: "user", - content: [ - { - type: "input_text", - text: "Change the background to Japanese style", - }, - { - type: "input_image", - image: `data:image/png;base64,${smallBase64Image}`, - }, - ], - }, - ], - _response: { - output: [ - { - id: "ig_test123", - type: "image_generation_call", - status: "completed", - output_format: "png", - result: smallBase64Image, - revised_prompt: "A Japanese style background", - }, - { - id: "msg_test456", - type: "message", - status: "completed", - content: [ - { - type: "output_text", - text: "Here is the image with a Japanese style background", - }, - ], - role: "assistant", - }, - ], - usage: { - input_tokens: 1555, - output_tokens: 113, - total_tokens: 1668, - }, - }, - }, - error: null, - } as any; - - // Execute trace lifecycle - await processor.onTraceStart(trace); - await processor.onSpanStart(span); - await processor.onSpanEnd(span); - await processor.onTraceEnd(trace); - - // Verify spans were created - const spans = await backgroundLogger.drain(); - const responseSpan = spans.find( - (s: any) => s.span_attributes?.name === "test-response-images", - ); - - assert.ok(responseSpan, "Should find response span"); - - // Verify input image was converted to attachment - const input = (responseSpan as any).input; - assert.ok(input, "Response span should have input"); - assert.ok(Array.isArray(input), "Input should be an array"); - - const userMessage = input[0]; - assert.ok(userMessage.content, "User message should have content"); - - const imageContent = userMessage.content.find( - (c: any) => c.type === "input_image", - ); - assert.ok(imageContent, "Should find input_image in content"); - assert.ok(imageContent.image, "Image content should have image field"); - - // Check if it's an Attachment (has the Braintrust attachment structure) - const imageAttachment = imageContent.image; - assert.ok( - imageAttachment.filename || imageAttachment._data, - "Image should be converted to Attachment object", - ); - - // Verify output image was converted to attachment - const output = (responseSpan as any).output; - assert.ok(output, "Response span should have output"); - assert.ok(Array.isArray(output), "Output should be an array"); - - const imageGenCall = output.find( - (o: any) => o.type === "image_generation_call", - ); - assert.ok(imageGenCall, "Should find image_generation_call in output"); - assert.ok(imageGenCall.result, "Image generation should have result"); - - // Check if result is an Attachment - const resultAttachment = imageGenCall.result; - assert.ok( - resultAttachment.filename || resultAttachment._data, - "Result should be converted to Attachment object", - ); - - // Verify metrics were extracted - const metrics = (responseSpan as any).metrics; - assert.ok(metrics, "Response span should have metrics"); - assert.equal(metrics.prompt_tokens, 1555, "Should have prompt_tokens"); - assert.equal( - metrics.completion_tokens, - 113, - "Should have completion_tokens", - ); - assert.equal(metrics.tokens, 1668, "Should have total tokens"); - }); - - test("Response span extracts cached tokens from usage", async () => { - const processor = new OpenAIAgentsTraceProcessor({ - logger: _logger as any, - }); - - // Create a mock trace - const trace: any = { - traceId: "test-trace-cached-response", - name: "cached-response-test", - metadata: {}, - }; - - await processor.onTraceStart(trace); - - // Create a mock Response span with cached tokens - const responseSpan: any = { - spanId: "test-response-span", - traceId: trace.traceId, - parentId: null, - startedAt: new Date().toISOString(), - endedAt: new Date(Date.now() + 1000).toISOString(), - spanData: { - type: "response", - _input: "test input", - _response: { - output: "test output", - usage: { - input_tokens: 100, - output_tokens: 50, - total_tokens: 150, - input_tokens_details: { - cached_tokens: 80, // check for this later - }, - }, - }, - }, - error: null, - }; - - // Process the span - await processor.onSpanStart(responseSpan); - await processor.onSpanEnd(responseSpan); - - // Get the logged spans - const spans = await backgroundLogger.drain(); - - // Find the response span - const responseSpanLog = spans.find( - (s: any) => s.span_attributes?.type === "llm", - ); - - assert.ok(responseSpanLog, "Response span should be logged"); - - // Verify metrics were extracted - const metrics = (responseSpanLog as any).metrics; - assert.ok(metrics, "Response span should have metrics"); - assert.equal(metrics.prompt_tokens, 100, "Should have prompt_tokens"); - assert.equal( - metrics.completion_tokens, - 50, - "Should have completion_tokens", - ); - assert.equal(metrics.tokens, 150, "Should have total tokens"); - assert.equal( - metrics.prompt_cached_tokens, - 80, - "Should extract cached_tokens to prompt_cached_tokens", - ); - }); - - test("Response span handles zero cached tokens correctly", async () => { - const processor = new OpenAIAgentsTraceProcessor({ - logger: _logger as any, - }); - - const trace: any = { - traceId: "test-trace-zero-cached", - name: "zero-cached-test", - metadata: {}, - }; - - await processor.onTraceStart(trace); - - const responseSpan: any = { - spanId: "test-response-span-zero", - traceId: trace.traceId, - parentId: null, - startedAt: new Date().toISOString(), - endedAt: new Date(Date.now() + 1000).toISOString(), - spanData: { - type: "response", - _response: { - usage: { - input_tokens: 100, - output_tokens: 50, - input_tokens_details: { - cached_tokens: 0, // Zero is a valid value - }, - }, - }, - }, - error: null, - }; - - await processor.onSpanStart(responseSpan); - await processor.onSpanEnd(responseSpan); - - const spans = await backgroundLogger.drain(); - const responseSpanLog = spans.find( - (s: any) => s.span_attributes?.type === "llm", - ); - const metrics = (responseSpanLog as any).metrics; - - // Zero should be logged, not skipped - assert.equal( - metrics.prompt_cached_tokens, - 0, - "Should log cached_tokens even when zero", - ); - }); - - test("Response span extracts cache write tokens from usage", async () => { - const processor = new OpenAIAgentsTraceProcessor({ - logger: _logger as any, - }); - - const trace: any = { - traceId: "test-trace-cache-write-response", - name: "cache-write-response-test", - metadata: {}, - }; - - await processor.onTraceStart(trace); - - const responseSpan: any = { - spanId: "test-response-span-cache-write", - traceId: trace.traceId, - parentId: null, - startedAt: new Date().toISOString(), - endedAt: new Date(Date.now() + 1000).toISOString(), - spanData: { - type: "response", - _response: { - usage: { - input_tokens: 100, - output_tokens: 50, - total_tokens: 150, - input_tokens_details: { - cache_write_tokens: 25, - }, - }, - }, - }, - error: null, - }; - - await processor.onSpanStart(responseSpan); - await processor.onSpanEnd(responseSpan); - - const spans = await backgroundLogger.drain(); - const responseSpanLog = spans.find( - (s: any) => s.span_attributes?.type === "llm", - ); - const metrics = (responseSpanLog as any).metrics; - - assert.equal( - metrics.prompt_cache_creation_tokens, - 25, - "Should extract cache_write_tokens to prompt_cache_creation_tokens", - ); - }); - - test("Response span handles zero cache write tokens correctly", async () => { - const processor = new OpenAIAgentsTraceProcessor({ - logger: _logger as any, - }); - - const trace: any = { - traceId: "test-trace-zero-cache-write", - name: "zero-cache-write-test", - metadata: {}, - }; - - await processor.onTraceStart(trace); - - const responseSpan: any = { - spanId: "test-response-span-zero-cache-write", - traceId: trace.traceId, - parentId: null, - startedAt: new Date().toISOString(), - endedAt: new Date(Date.now() + 1000).toISOString(), - spanData: { - type: "response", - _response: { - usage: { - input_tokens: 100, - output_tokens: 50, - input_tokens_details: { - cache_write_tokens: 0, - }, - }, - }, - }, - error: null, - }; - - await processor.onSpanStart(responseSpan); - await processor.onSpanEnd(responseSpan); - - const spans = await backgroundLogger.drain(); - const responseSpanLog = spans.find( - (s: any) => s.span_attributes?.type === "llm", - ); - const metrics = (responseSpanLog as any).metrics; - - // Zero should be logged, not skipped - assert.equal( - metrics.prompt_cache_creation_tokens, - 0, - "Should log cache_write_tokens even when zero", - ); - }); - - test("Response span handles missing cache write tokens gracefully", async () => { - const processor = new OpenAIAgentsTraceProcessor({ - logger: _logger as any, - }); - - const trace: any = { - traceId: "test-trace-no-cache-write", - name: "no-cache-write-test", - metadata: {}, - }; - - await processor.onTraceStart(trace); - - const responseSpan: any = { - spanId: "test-response-span-no-cache-write", - traceId: trace.traceId, - parentId: null, - startedAt: new Date().toISOString(), - endedAt: new Date(Date.now() + 1000).toISOString(), - spanData: { - type: "response", - _response: { - usage: { - input_tokens: 100, - output_tokens: 50, - input_tokens_details: { - cached_tokens: 40, - // No cache_write_tokens - }, - }, - }, - }, - error: null, - }; - - await processor.onSpanStart(responseSpan); - await processor.onSpanEnd(responseSpan); - - const spans = await backgroundLogger.drain(); - const responseSpanLog = spans.find( - (s: any) => s.span_attributes?.type === "llm", - ); - const metrics = (responseSpanLog as any).metrics; - - assert.isUndefined( - metrics.prompt_cache_creation_tokens, - "Should not add prompt_cache_creation_tokens if not in usage", - ); - }); - - test("Response span handles missing cached tokens gracefully", async () => { - const processor = new OpenAIAgentsTraceProcessor({ - logger: _logger as any, - }); - - const trace: any = { - traceId: "test-trace-no-cached", - name: "no-cached-test", - metadata: {}, - }; - - await processor.onTraceStart(trace); - - const responseSpan: any = { - spanId: "test-response-span-no-cached", - traceId: trace.traceId, - parentId: null, - startedAt: new Date().toISOString(), - endedAt: new Date(Date.now() + 1000).toISOString(), - spanData: { - type: "response", - _response: { - usage: { - input_tokens: 100, - output_tokens: 50, - // No input_tokens_details at all - }, - }, - }, - error: null, - }; - - await processor.onSpanStart(responseSpan); - await processor.onSpanEnd(responseSpan); - - const spans = await backgroundLogger.drain(); - const responseSpanLog = spans.find( - (s: any) => s.span_attributes?.type === "llm", - ); - const metrics = (responseSpanLog as any).metrics; - - // Should not have prompt_cached_tokens if not present in usage - assert.isUndefined( - metrics.prompt_cached_tokens, - "Should not add prompt_cached_tokens if not in usage", - ); - }); - - test("Generation span extracts cached tokens from usage", async () => { - const processor = new OpenAIAgentsTraceProcessor({ - logger: _logger as any, - }); - - const trace: any = { - traceId: "test-trace-cached-generation", - name: "cached-generation-test", - metadata: {}, - }; - - await processor.onTraceStart(trace); - - const generationSpan: any = { - spanId: "test-generation-span", - traceId: trace.traceId, - parentId: null, - startedAt: new Date().toISOString(), - endedAt: new Date(Date.now() + 1000).toISOString(), - spanData: { - type: "generation", - input: [{ role: "user", content: "test" }], - output: [{ role: "assistant", content: "response" }], - model: "gpt-4o-mini", - usage: { - input_tokens: 200, - output_tokens: 75, - total_tokens: 275, - input_tokens_details: { - cached_tokens: 150, // Test Generation span extraction - }, - }, - }, - error: null, - }; - - await processor.onSpanStart(generationSpan); - await processor.onSpanEnd(generationSpan); - - const spans = await backgroundLogger.drain(); - const generationSpanLog = spans.find( - (s: any) => s.span_attributes?.type === "llm", - ); - - assert.ok(generationSpanLog, "Generation span should be logged"); - - const metrics = (generationSpanLog as any).metrics; - assert.ok(metrics, "Generation span should have metrics"); - assert.equal(metrics.prompt_tokens, 200, "Should have prompt_tokens"); - assert.equal( - metrics.completion_tokens, - 75, - "Should have completion_tokens", - ); - assert.equal( - metrics.prompt_cached_tokens, - 150, - "Should extract cached_tokens from Generation span", - ); - }); - }, -); diff --git a/integrations/openai-agents-js/src/openai-agents-trace-processor.test.ts b/integrations/openai-agents-js/src/openai-agents-trace-processor.test.ts deleted file mode 100644 index 3b7fcbdda..000000000 --- a/integrations/openai-agents-js/src/openai-agents-trace-processor.test.ts +++ /dev/null @@ -1,288 +0,0 @@ -import { assert, describe, test } from "vitest"; -import { OpenAIAgentsTraceProcessor } from "./index"; - -function createDeferredPromise(): { - promise: Promise; - resolve: (value: T | PromiseLike) => void; - reject: (reason?: unknown) => void; -} { - let resolve!: (value: T | PromiseLike) => void; - let reject!: (reason?: unknown) => void; - const promise = new Promise((resolvePromise, rejectPromise) => { - resolve = resolvePromise; - reject = rejectPromise; - }); - return { promise, resolve, reject }; -} - -function trackAsyncCompletion(promise: Promise): { - promise: Promise; - isResolved: () => boolean; -} { - let resolved = false; - return { - promise: promise.finally(() => { - resolved = true; - }), - isResolved: () => resolved, - }; -} - -describe("OpenAIAgentsTraceProcessor flush behavior", () => { - test("onTraceEnd waits for root span flush to complete", async () => { - let flushCalls = 0; - let endCalls = 0; - const deferred = createDeferredPromise(); - let rootSpanInput: unknown; - let rootSpanOutput: unknown; - - const childSpan = { - log: () => {}, - end: () => {}, - }; - - const rootSpan = { - log: (data: Record) => { - rootSpanInput = data.input; - rootSpanOutput = data.output; - }, - startSpan: () => childSpan, - end: () => { - endCalls += 1; - }, - flush: () => { - flushCalls += 1; - return deferred.promise; - }, - }; - - const processor = new OpenAIAgentsTraceProcessor({ - logger: { - startSpan: () => rootSpan, - } as any, - }); - - const trace = { - traceId: "trace-1", - name: "test-trace", - groupId: "group-1", - metadata: {}, - } as any; - - await processor.onTraceStart(trace); - - const childOpenAIAgentsSpan = { - spanId: "span-1", - traceId: trace.traceId, - spanData: { - type: "generation", - input: "first-input", - output: "last-output", - }, - error: null, - } as any; - await processor.onSpanStart(childOpenAIAgentsSpan); - await processor.onSpanEnd(childOpenAIAgentsSpan); - - const onTraceEndCompletion = trackAsyncCompletion( - processor.onTraceEnd(trace), - ); - - await Promise.resolve(); - - assert.equal(endCalls, 1, "onTraceEnd should end the root span"); - assert.equal(flushCalls, 1, "onTraceEnd should flush the root span once"); - assert.equal( - rootSpanInput, - "first-input", - "onTraceEnd should log first input", - ); - assert.equal( - rootSpanOutput, - "last-output", - "onTraceEnd should log last output", - ); - assert.isFalse( - onTraceEndCompletion.isResolved(), - "onTraceEnd should not resolve before root span flush resolves", - ); - - deferred.resolve(); - await onTraceEndCompletion.promise; - - assert.isTrue( - onTraceEndCompletion.isResolved(), - "onTraceEnd should resolve after root span flush resolves", - ); - assert.isFalse( - processor._traceSpans.has(trace.traceId), - "onTraceEnd should remove trace state after finishing", - ); - }); - - test("onTraceEnd propagates root span flush failure after cleanup", async () => { - let flushCalls = 0; - let endCalls = 0; - const deferred = createDeferredPromise(); - const failure = new Error("flush failed"); - let rootSpanInput: unknown; - let rootSpanOutput: unknown; - - const childSpan = { - log: () => {}, - end: () => {}, - }; - - const rootSpan = { - log: (data: Record) => { - rootSpanInput = data.input; - rootSpanOutput = data.output; - }, - startSpan: () => childSpan, - end: () => { - endCalls += 1; - }, - flush: () => { - flushCalls += 1; - return deferred.promise; - }, - }; - - const processor = new OpenAIAgentsTraceProcessor({ - logger: { - startSpan: () => rootSpan, - } as any, - }); - - const trace = { - traceId: "trace-2", - name: "test-trace-fail", - groupId: "group-1", - metadata: {}, - } as any; - - await processor.onTraceStart(trace); - - const childOpenAIAgentsSpan = { - spanId: "span-1", - traceId: trace.traceId, - spanData: { - type: "generation", - input: "first-input", - output: "last-output", - }, - error: null, - } as any; - await processor.onSpanStart(childOpenAIAgentsSpan); - await processor.onSpanEnd(childOpenAIAgentsSpan); - - const onTraceEndCompletion = trackAsyncCompletion( - processor.onTraceEnd(trace), - ); - - await Promise.resolve(); - - assert.equal(endCalls, 1, "onTraceEnd should end the root span"); - assert.equal(flushCalls, 1, "onTraceEnd should flush the root span once"); - assert.equal( - rootSpanInput, - "first-input", - "root span log should include first input", - ); - assert.equal( - rootSpanOutput, - "last-output", - "root span log should include last output", - ); - assert.isFalse( - onTraceEndCompletion.isResolved(), - "onTraceEnd should wait for root span flush promise", - ); - - deferred.reject(failure); - try { - await onTraceEndCompletion.promise; - assert.fail("onTraceEnd should reject when root span flush rejects"); - } catch (error) { - assert.instanceOf(error as Error, Error); - assert.equal((error as Error).message, "flush failed"); - } - - assert.isFalse( - processor._traceSpans.has(trace.traceId), - "onTraceEnd should remove trace state even when flush fails", - ); - }); - - test("forceFlush waits for logger.flush to complete", async () => { - let flushCalls = 0; - const deferred = createDeferredPromise(); - - const processor = new OpenAIAgentsTraceProcessor({ - logger: { - flush: () => { - flushCalls += 1; - return deferred.promise; - }, - } as any, - }); - - const forceFlushCompletion = trackAsyncCompletion(processor.forceFlush()); - - await Promise.resolve(); - - assert.equal( - flushCalls, - 1, - "forceFlush should call logger.flush exactly once", - ); - assert.isFalse( - forceFlushCompletion.isResolved(), - "forceFlush should not resolve before logger.flush resolves", - ); - - deferred.resolve(); - await forceFlushCompletion.promise; - - assert.isTrue( - forceFlushCompletion.isResolved(), - "forceFlush should resolve after logger.flush", - ); - }); - - test("shutdown waits for logger.flush to complete", async () => { - let flushCalls = 0; - const deferred = createDeferredPromise(); - - const processor = new OpenAIAgentsTraceProcessor({ - logger: { - flush: () => { - flushCalls += 1; - return deferred.promise; - }, - } as any, - }); - - const shutdownCompletion = trackAsyncCompletion(processor.shutdown()); - - await Promise.resolve(); - - assert.equal( - flushCalls, - 1, - "shutdown should call logger.flush exactly once", - ); - assert.isFalse( - shutdownCompletion.isResolved(), - "shutdown should not resolve before logger.flush resolves", - ); - - deferred.resolve(); - await shutdownCompletion.promise; - - assert.isTrue( - shutdownCompletion.isResolved(), - "shutdown should resolve after logger.flush", - ); - }); -}); diff --git a/integrations/openai-agents-js/src/openai-agents-types.ts b/integrations/openai-agents-js/src/openai-agents-types.ts deleted file mode 100644 index 33a00a7d9..000000000 --- a/integrations/openai-agents-js/src/openai-agents-types.ts +++ /dev/null @@ -1,150 +0,0 @@ -/** - * Type definitions copied from @openai/agents-core. - * - * Original source: https://github.com/openai/openai-agents-js - * - * MIT License - * - * Copyright (c) 2025 OpenAI - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in all - * copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. - */ - -type SpanDataBase = { - type: string; -}; - -export type AgentSpanData = SpanDataBase & { - type: "agent"; - name: string; - handoffs?: string[]; - tools?: string[]; - output_type?: string; -}; - -export type FunctionSpanData = SpanDataBase & { - type: "function"; - name: string; - input: string; - output: string; - mcp_data?: string; -}; - -export type GenerationSpanData = SpanDataBase & { - type: "generation"; - input?: Array>; - output?: Array>; - model?: string; - model_config?: Record; - usage?: Record; -}; - -export type ResponseSpanData = SpanDataBase & { - type: "response"; - response_id?: string; - /** - * Not used by the OpenAI tracing provider but helpful for other tracing providers. - */ - _input?: string | Record[]; - _response?: Record; -}; - -export type HandoffSpanData = SpanDataBase & { - type: "handoff"; - from_agent?: string; - to_agent?: string; -}; - -export type CustomSpanData = SpanDataBase & { - type: "custom"; - name: string; - data: Record; -}; - -export type GuardrailSpanData = SpanDataBase & { - type: "guardrail"; - name: string; - triggered: boolean; -}; - -export type TranscriptionSpanData = SpanDataBase & { - type: "transcription"; - input: { - data: string; - format: "pcm" | string; - }; - output?: string; - model?: string; - model_config?: Record; -}; - -export type SpeechSpanData = SpanDataBase & { - type: "speech"; - input?: string; - output: { - data: string; - format: "pcm" | string; - }; - model?: string; - model_config?: Record; -}; - -export type SpeechGroupSpanData = SpanDataBase & { - type: "speech_group"; - input?: string; -}; - -export type MCPListToolsSpanData = SpanDataBase & { - type: "mcp_tools"; - server?: string; - result?: string[]; -}; - -export type SpanData = - | AgentSpanData - | FunctionSpanData - | GenerationSpanData - | ResponseSpanData - | HandoffSpanData - | CustomSpanData - | GuardrailSpanData - | TranscriptionSpanData - | SpeechSpanData - | SpeechGroupSpanData - | MCPListToolsSpanData; - -// Simplified versions of Trace and Span types - only including what we actually use -export type Trace = { - type: "trace"; - traceId: string; - name: string; - groupId: string | null; - metadata?: Record; -}; - -export type Span = { - type: "trace.span"; - traceId: string; - spanData: TData; - spanId: string; - parentId: string | null; - startedAt: string | null; - endedAt: string | null; - error: { message: string; data?: Record } | null; -}; diff --git a/integrations/openai-agents-js/src/types.ts b/integrations/openai-agents-js/src/types.ts deleted file mode 100644 index 6834f5111..000000000 --- a/integrations/openai-agents-js/src/types.ts +++ /dev/null @@ -1,120 +0,0 @@ -import { Logger } from "braintrust"; -import type { - SpanData, - AgentSpanData, - FunctionSpanData, - GenerationSpanData, - ResponseSpanData, - HandoffSpanData, - CustomSpanData, - GuardrailSpanData, - MCPListToolsSpanData, - TranscriptionSpanData, - SpeechSpanData, - SpeechGroupSpanData, - Trace, - Span, -} from "./openai-agents-types"; - -export enum SpanType { - AGENT = "agent", - RESPONSE = "response", - FUNCTION = "function", - HANDOFF = "handoff", - GUARDRAIL = "guardrail", - GENERATION = "generation", - CUSTOM = "custom", - MCP_TOOLS = "mcp_tools", - TRANSCRIPTION = "transcription", - SPEECH = "speech", - SPEECH_GROUP = "speech_group", -} - -export type AgentsTrace = Trace; - -export type AgentsSpan = Span; - -export type SpanInput = - | string - | Array> - | Record[]; - -export type SpanOutput = - | string - | Array> - | Record; - -export type TraceMetadata = { - firstInput: SpanInput | null; - lastOutput: SpanOutput | null; -}; - -export interface OpenAIAgentsTraceProcessorOptions { - logger?: Logger; - maxTraces?: number; -} - -// Type guard functions -export function isResponseSpanData( - spanData: SpanData, -): spanData is ResponseSpanData { - return spanData.type === SpanType.RESPONSE; -} - -export function isGenerationSpanData( - spanData: SpanData, -): spanData is GenerationSpanData { - return spanData.type === SpanType.GENERATION; -} - -export function isAgentSpanData(spanData: SpanData): spanData is AgentSpanData { - return spanData.type === SpanType.AGENT; -} - -export function isFunctionSpanData( - spanData: SpanData, -): spanData is FunctionSpanData { - return spanData.type === SpanType.FUNCTION; -} - -export function isHandoffSpanData( - spanData: SpanData, -): spanData is HandoffSpanData { - return spanData.type === SpanType.HANDOFF; -} - -export function isGuardrailSpanData( - spanData: SpanData, -): spanData is GuardrailSpanData { - return spanData.type === SpanType.GUARDRAIL; -} - -export function isCustomSpanData( - spanData: SpanData, -): spanData is CustomSpanData { - return spanData.type === SpanType.CUSTOM; -} - -export function isMCPListToolsSpanData( - spanData: SpanData, -): spanData is MCPListToolsSpanData { - return spanData.type === SpanType.MCP_TOOLS; -} - -export function isTranscriptionSpanData( - spanData: SpanData, -): spanData is TranscriptionSpanData { - return spanData.type === SpanType.TRANSCRIPTION; -} - -export function isSpeechSpanData( - spanData: SpanData, -): spanData is SpeechSpanData { - return spanData.type === SpanType.SPEECH; -} - -export function isSpeechGroupSpanData( - spanData: SpanData, -): spanData is SpeechGroupSpanData { - return spanData.type === SpanType.SPEECH_GROUP; -} diff --git a/integrations/openai-agents-js/tsconfig.json b/integrations/openai-agents-js/tsconfig.json deleted file mode 100644 index ea6ad1861..000000000 --- a/integrations/openai-agents-js/tsconfig.json +++ /dev/null @@ -1,15 +0,0 @@ -{ - "compilerOptions": { - "declaration": true, - "lib": ["es2022"], - "module": "commonjs", - "target": "es2022", - "moduleResolution": "node", - "strict": true, - "noUnusedLocals": true, - "esModuleInterop": true, - "skipLibCheck": true - }, - "include": ["."], - "exclude": ["node_modules/**", "**/dist/**"] -} diff --git a/integrations/openai-agents-js/tsup.config.ts b/integrations/openai-agents-js/tsup.config.ts deleted file mode 100644 index 7d69621aa..000000000 --- a/integrations/openai-agents-js/tsup.config.ts +++ /dev/null @@ -1,10 +0,0 @@ -import { defineConfig } from "tsup"; - -export default defineConfig([ - { - entry: ["src/index.ts"], - format: ["cjs", "esm"], - outDir: "dist", - dts: true, - }, -]); diff --git a/integrations/openai-agents-js/turbo.json b/integrations/openai-agents-js/turbo.json deleted file mode 100644 index 69a3e1241..000000000 --- a/integrations/openai-agents-js/turbo.json +++ /dev/null @@ -1,12 +0,0 @@ -{ - "extends": ["//"], - "tasks": { - "build": { - "dependsOn": ["^build"], - "outputs": ["dist/**"] - }, - "test": { - "dependsOn": ["build"] - } - } -} diff --git a/integrations/openai-agents-js/vitest.config.ts b/integrations/openai-agents-js/vitest.config.ts deleted file mode 100644 index 9b3c6bb43..000000000 --- a/integrations/openai-agents-js/vitest.config.ts +++ /dev/null @@ -1,9 +0,0 @@ -import { defineConfig } from "vitest/config"; - -export default defineConfig({ - test: { - reporters: ["default"], - include: ["tests/**/*.test.ts", "src/**/*.test.ts"], - // Add any specific test configuration if needed - }, -}); diff --git a/integrations/otel-js/.gitignore b/integrations/otel-js/.gitignore new file mode 100644 index 000000000..7a6c09645 --- /dev/null +++ b/integrations/otel-js/.gitignore @@ -0,0 +1 @@ +.vitest/ diff --git a/integrations/otel-js/CLAUDE.md b/integrations/otel-js/CLAUDE.md index 5c83827fd..a11b4ca9f 100644 --- a/integrations/otel-js/CLAUDE.md +++ b/integrations/otel-js/CLAUDE.md @@ -20,13 +20,13 @@ sdk/integrations/otel-js/ ├── otel-v1/ # Test environment for OTel 1.x │ ├── src -> ../src # SYMLINK to parent src/ │ ├── package.json # Pins OTel 1.x versions -│ ├── vitest.config.ts # Aliases to local node_modules +│ ├── vite.config.ts # Aliases to local node_modules │ ├── pnpm-lock.yaml # Independent lockfile │ └── node_modules/ # Isolated v1.x dependencies └── otel-v2/ # Test environment for OTel 2.x ├── src -> ../src # SYMLINK to parent src/ ├── package.json # Pins OTel 2.x versions - ├── vitest.config.ts # Aliases to local node_modules + ├── vite.config.ts # Aliases to local node_modules ├── pnpm-lock.yaml # Independent lockfile └── node_modules/ # Isolated v2.x dependencies ``` @@ -162,10 +162,10 @@ This is rarely needed but useful for troubleshooting or regenerating dependencie ### 5. Vitest Configuration -Each test environment has a `vitest.config.ts` that creates module aliases: +Each test environment has a `vite.config.ts` that creates module aliases: ```typescript -import { defineConfig } from "vitest/config"; +import { defineConfig } from "vite-plus"; import { detectOtelVersion, logOtelVersions, diff --git a/integrations/otel-js/Dockerfile.test b/integrations/otel-js/Dockerfile.test index 84c76f791..eaee521f6 100644 --- a/integrations/otel-js/Dockerfile.test +++ b/integrations/otel-js/Dockerfile.test @@ -33,17 +33,10 @@ WORKDIR /workspace # Copy the entire SDK repository (build context is SDK repo root) COPY . . -# Build braintrust SDK first (required peer dependency) -WORKDIR /workspace/js -RUN pnpm install --ignore-workspace -RUN pnpm run build - -# Build @braintrust/otel package -WORKDIR /workspace/integrations/otel-js -# Replace workspace:* with file: path for braintrust dependency -RUN sed -i.bak 's/"braintrust": "workspace:\*"/"braintrust": "file:..\/..\/js"/' package.json && rm -f package.json.bak -RUN pnpm install --ignore-workspace -RUN pnpm run build +# Build SDK packages with the workspace catalog and lockfile. The test packages +# still install independently to keep their OpenTelemetry versions isolated. +RUN pnpm install --frozen-lockfile +RUN pnpm exec vp run --filter=braintrust --filter=@braintrust/otel build # Set the test directory as environment variable (available at runtime) ENV TEST_DIR=${TEST_DIR} diff --git a/integrations/otel-js/README.md b/integrations/otel-js/README.md index f5adf45c2..e7afa2da5 100644 --- a/integrations/otel-js/README.md +++ b/integrations/otel-js/README.md @@ -118,28 +118,23 @@ const logger = initLogger({ projectName: "my_project" }); **What it does**: - Sets Braintrust to use OpenTelemetry's context manager for parent-child span relationships -- Configures Braintrust to use OpenTelemetry-compatible span and trace IDs - Enables seamless mixing of Braintrust and OpenTelemetry spans in the same trace ## Distributed Tracing Helpers These utilities enable distributed tracing across services that use different tracing systems (Braintrust and OpenTelemetry). -### `contextFromSpanExport(exportStr: string)` +### `contextFromSpan(span: Span)` -Creates an OpenTelemetry context from a Braintrust span export string. +Creates an OpenTelemetry context from a Braintrust span using W3C Trace Context. -**When to use it**: When Service A uses Braintrust and sends a span export to Service B that uses OpenTelemetry. This allows Service B to create spans as children of Service A's span. +**When to use it**: When Braintrust and OpenTelemetry code run in the same process and an OpenTelemetry operation should be a child of a Braintrust span. Across services, propagate the headers returned by `span.inject()` instead. ```typescript -import { contextFromSpanExport } from "@braintrust/otel"; +import { contextFromSpan } from "@braintrust/otel"; import { context } from "@opentelemetry/api"; -// Service A (Braintrust) exports span -const exportedSpan = await spanA.export(); - -// Service B (OpenTelemetry) imports context -const ctx = contextFromSpanExport(exportedSpan); +const ctx = contextFromSpan(spanA); await context.with(ctx, async () => { // OpenTelemetry spans created here will be children of Service A's span await tracer.startActiveSpan("service_b_operation", async (span) => { diff --git a/integrations/otel-js/otel-v1/package.json b/integrations/otel-js/otel-v1/package.json index 61e0897b3..e5b6cd127 100644 --- a/integrations/otel-js/otel-v1/package.json +++ b/integrations/otel-js/otel-v1/package.json @@ -3,24 +3,10 @@ "version": "0.0.1-test", "private": true, "description": "Test package for @braintrust/otel with OpenTelemetry 1.x", - "main": "./dist/index.js", - "module": "./dist/index.mjs", - "types": "./dist/index.d.ts", - "exports": { - "./package.json": "./package.json", - ".": { - "types": "./dist/index.d.ts", - "import": "./dist/index.mjs", - "module": "./dist/index.mjs", - "require": "./dist/index.js" - } - }, "scripts": { - "build": "tsup", - "watch": "tsup --watch", "clean": "rm -rf dist/*", - "build:deps": "(cd ../../.. && pnpm turbo run build --filter=braintrust --filter=@braintrust/otel)", - "test": "pnpm run build:deps && vitest run" + "build:deps": "(cd ../../.. && pnpm exec vp run --filter=braintrust --filter=@braintrust/otel build)", + "test": "pnpm run build:deps && vp test run" }, "author": "Braintrust Data Inc.", "license": "MIT", @@ -38,9 +24,10 @@ "@types/node": "^22.15.21", "braintrust": "file:../../../js", "msw": "^2.6.6", - "tsup": "^8.5.0", "typescript": "5.5.4", - "vitest": "4.1.11" + "vitest": "5.0.3", + "vite-plus": "1.1.0", + "vite": "npm:@voidzero-dev/vite-plus-core@1.1.0" }, "pnpm": { "overrides": { diff --git a/integrations/otel-js/otel-v1/pnpm-lock.yaml b/integrations/otel-js/otel-v1/pnpm-lock.yaml index ab10127f4..e5448217b 100644 --- a/integrations/otel-js/otel-v1/pnpm-lock.yaml +++ b/integrations/otel-js/otel-v1/pnpm-lock.yaml @@ -69,58 +69,39 @@ importers: msw: specifier: ^2.6.6 version: 2.14.6(@types/node@22.19.19)(typescript@5.5.4) - tsup: - specifier: ^8.5.0 - version: 8.5.1(postcss@8.5.28)(typescript@5.5.4) typescript: specifier: 5.5.4 version: 5.5.4 + vite: + specifier: npm:@voidzero-dev/vite-plus-core@1.1.0 + version: '@voidzero-dev/vite-plus-core@1.1.0(@types/node@22.19.19)(esbuild@0.28.1)(typescript@5.5.4)' + vite-plus: + specifier: 1.1.0 + version: 1.1.0(@opentelemetry/api@1.9.0)(@types/node@22.19.19)(@vitest/ui@5.0.3(vitest@5.0.3))(esbuild@0.28.1)(msw@2.14.6(@types/node@22.19.19)(typescript@5.5.4))(typescript@5.5.4) vitest: - specifier: 4.1.11 - version: 4.1.11(@opentelemetry/api@1.9.0)(@types/node@22.19.19)(msw@2.14.6(@types/node@22.19.19)(typescript@5.5.4))(vite@8.0.14(@types/node@22.19.19)(esbuild@0.27.7)) + specifier: 5.0.3 + version: 5.0.3(@opentelemetry/api@1.9.0)(@types/node@22.19.19)(@vitest/browser-preview@5.0.3)(@vitest/ui@5.0.3)(@voidzero-dev/vite-plus-core@1.1.0(@types/node@22.19.19)(esbuild@0.28.1)(typescript@5.5.4))(msw@2.14.6(@types/node@22.19.19)(typescript@5.5.4)) packages: - '@braintrust/bt-darwin-arm64@0.12.0': - resolution: {integrity: sha512-mY6VW/3VwcOQOGN8sYHS6F0xzHTFwgZcNlj7zlQttI6OXOCGt/bhonGIqd03QBhxmj0M31ymSS7TqSeCK/RYIQ==} - cpu: [arm64] - os: [darwin] - - '@braintrust/bt-darwin-x64@0.12.0': - resolution: {integrity: sha512-woyRyDv2DfCF8+von+3X9f1cddAbFnKLSfCbEkrBQVc0ZsAM60as8nehYv7DkafJF/67yKFx/sUraV65sukesQ==} - cpu: [x64] - os: [darwin] - - '@braintrust/bt-linux-arm64@0.12.0': - resolution: {integrity: sha512-J9/7f3EIMKmFmSSQHQnAQLpUgB8YJENrOlkABjlTprBgsIYVgz7tSH7yg2P3ur+2Jem7PpGnrDxHGh/UjRfjsg==} - cpu: [arm64] - os: [linux] - libc: [glibc] - - '@braintrust/bt-linux-x64-musl@0.12.0': - resolution: {integrity: sha512-KnLgENOoztBXcH+mLFJe4bYzi67kSOuELOQrm4Hler35GjgaBMI1fFLIUjKRPAmyT9VIhBMhy1ICfGEFLueMEQ==} - cpu: [x64] - os: [linux] - libc: [musl] + '@babel/code-frame@7.29.7': + resolution: {integrity: sha512-Aup7aUOfpbAUg2ROOJN6Iw5f9DMBlzu0mIkm/malLQFN/YQgO48wCj0Kxa3sEHJvPVFg7siR+qRInwXd2qhQKw==} + engines: {node: '>=6.9.0'} - '@braintrust/bt-linux-x64@0.12.0': - resolution: {integrity: sha512-HJFbUl3HYYY1Ivw8OYBeIMcIsU9r7+fC9BzLWB5FdH7881ToKee2wbbLZssMCxFbMVeUxzEo+SzGD/1Z9Gk9CA==} - cpu: [x64] - os: [linux] - libc: [glibc] + '@babel/helper-validator-identifier@7.29.7': + resolution: {integrity: sha512-qehxGkRj55h/ff8EMaJ+cYhyaKlHIxqYDn682wQD7RNp9UujOQsHog2uS0r2vzr4pW+sXf90NeeayjcNaX3fFg==} + engines: {node: '>=6.9.0'} - '@braintrust/bt-win32-arm64@0.12.0': - resolution: {integrity: sha512-+7PkdBAmiqEJsWteGHP/Zs62F75PkHPA8m/ej4TOz/mHGKulUU0bZibw91KRqIB7J7jGi5ItvCiqkiGaLKLnyw==} - cpu: [arm64] - os: [win32] + '@babel/runtime@7.29.10': + resolution: {integrity: sha512-NpugTzbKgGrNe45Ui32cN8vCFFMxVtP/9rtfwv0nP5FRDAjvYBxJcLVpYA9mr1jxP6cnIT7x9LUzuDTbWYYNRA==} + engines: {node: '>=6.9.0'} - '@braintrust/bt-win32-x64@0.12.0': - resolution: {integrity: sha512-Q0c+pVUGm82n/7N8QJ5s6j/G/Q0GFzqOaTipHjkmElLbAOOEcfRH/uljdtIPNBiEQcrzqirS0GmOgiFkeQ3Txg==} - cpu: [x64] - os: [win32] + '@blazediff/core@1.10.0': + resolution: {integrity: sha512-AOQff0zgR7cGsZL+4E7hVkmujoPUpm0J9xzWGWZj5wCjd3gmxESXAPfKyuzs93VdpQNFhHlBhfOjrcZ+XTERtQ==} '@braintrust/otel@file:..': resolution: {directory: .., type: directory} + engines: {node: '>=22.18.0'} peerDependencies: '@opentelemetry/api': '>=1.9.0' '@opentelemetry/core': '>=1.9.0' @@ -128,325 +109,156 @@ packages: '@opentelemetry/sdk-trace-base': '>=1.9.0' braintrust: '>=1.0.0-0' - '@colors/colors@1.5.0': - resolution: {integrity: sha512-ooWCrlZP11i8GImSjTHYHLkvFDP48nS4+204nGb1RiX/WXYHmJA2III9/e2DWVabCESdW7hBAEzHRqUn9OUVvQ==} - engines: {node: '>=0.1.90'} - - '@emnapi/core@1.10.0': - resolution: {integrity: sha512-yq6OkJ4p82CAfPl0u9mQebQHKPJkY7WrIuk205cTYnYe+k2Z8YBh11FrbRG/H6ihirqcacOgl2BIO8oyMQLeXw==} - - '@emnapi/runtime@1.10.0': - resolution: {integrity: sha512-ewvYlk86xUoGI0zQRNq/mC+16R1QeDlKQy21Ki3oSYXNgLb45GV1P6A0M+/s6nyCuNDqe5VpaY84BzXGwVbwFA==} - - '@emnapi/wasi-threads@1.2.1': - resolution: {integrity: sha512-uTII7OYF+/Mes/MrcIOYp5yOtSMLBWSIoLPpcgwipoiKbli6k322tcoFsxoIIxPDqW01SQGAgko4EzZi2BNv2w==} - - '@esbuild/aix-ppc64@0.27.7': - resolution: {integrity: sha512-EKX3Qwmhz1eMdEJokhALr0YiD0lhQNwDqkPYyPhiSwKrh7/4KRjQc04sZ8db+5DVVnZ1LmbNDI1uAMPEUBnQPg==} - engines: {node: '>=18'} - cpu: [ppc64] - os: [aix] - '@esbuild/aix-ppc64@0.28.1': resolution: {integrity: sha512-Svl7tq8k/08+p6CXPpRjQ1fKX+1odH/BQbb48fV6fj3CWHhsoIOoY87w1oHXm0qEpkIK3ZfVgp0hed3XBXzXMQ==} engines: {node: '>=18'} cpu: [ppc64] os: [aix] - '@esbuild/android-arm64@0.27.7': - resolution: {integrity: sha512-62dPZHpIXzvChfvfLJow3q5dDtiNMkwiRzPylSCfriLvZeq0a1bWChrGx/BbUbPwOrsWKMn8idSllklzBy+dgQ==} - engines: {node: '>=18'} - cpu: [arm64] - os: [android] - '@esbuild/android-arm64@0.28.1': resolution: {integrity: sha512-34EGEbCIAgosYz6goLcopX6Mo7NyGv9tfwEM2/7Ce2VcVRk568iSvniGWcUXIy7wEDR1wzolcxcriFVrWYcwBg==} engines: {node: '>=18'} cpu: [arm64] os: [android] - '@esbuild/android-arm@0.27.7': - resolution: {integrity: sha512-jbPXvB4Yj2yBV7HUfE2KHe4GJX51QplCN1pGbYjvsyCZbQmies29EoJbkEc+vYuU5o45AfQn37vZlyXy4YJ8RQ==} - engines: {node: '>=18'} - cpu: [arm] - os: [android] - '@esbuild/android-arm@0.28.1': resolution: {integrity: sha512-0k2F129Xdio1TdJfzJ8sy1Q47vUD2NnwdhiAf7drUN1EBTfPf4hsFCtmMgu/6m8JSzsBrlmVjudMBQqOfG8usQ==} engines: {node: '>=18'} cpu: [arm] os: [android] - '@esbuild/android-x64@0.27.7': - resolution: {integrity: sha512-x5VpMODneVDb70PYV2VQOmIUUiBtY3D3mPBG8NxVk5CogneYhkR7MmM3yR/uMdITLrC1ml/NV1rj4bMJuy9MCg==} - engines: {node: '>=18'} - cpu: [x64] - os: [android] - '@esbuild/android-x64@0.28.1': resolution: {integrity: sha512-dbwY7ltSMDWsRatcRpCnES4F+im88OCUgGZjy52shC7GqHRE/cYlxNbB4Z4UpJswpcc4Qxd2oE/ufM0p61IKng==} engines: {node: '>=18'} cpu: [x64] os: [android] - '@esbuild/darwin-arm64@0.27.7': - resolution: {integrity: sha512-5lckdqeuBPlKUwvoCXIgI2D9/ABmPq3Rdp7IfL70393YgaASt7tbju3Ac+ePVi3KDH6N2RqePfHnXkaDtY9fkw==} - engines: {node: '>=18'} - cpu: [arm64] - os: [darwin] - '@esbuild/darwin-arm64@0.28.1': resolution: {integrity: sha512-TZbWkQY7kvTAXbXUT7uVACR5cMHsDiSz9z7ZKAX/RTq/WJEk3QyRr0wZpNhBDX+/0CtdqUIJlOiodQcta6tY3Q==} engines: {node: '>=18'} cpu: [arm64] os: [darwin] - '@esbuild/darwin-x64@0.27.7': - resolution: {integrity: sha512-rYnXrKcXuT7Z+WL5K980jVFdvVKhCHhUwid+dDYQpH+qu+TefcomiMAJpIiC2EM3Rjtq0sO3StMV/+3w3MyyqQ==} - engines: {node: '>=18'} - cpu: [x64] - os: [darwin] - '@esbuild/darwin-x64@0.28.1': resolution: {integrity: sha512-zfdzgK9ACBNZLI/CyHTOx81SyNbM6YXn7rxSgX97VjyiPl9W1i4Ka4fgKECEoFCKGpvBj5qArWIGgQjOwkgskQ==} engines: {node: '>=18'} cpu: [x64] os: [darwin] - '@esbuild/freebsd-arm64@0.27.7': - resolution: {integrity: sha512-B48PqeCsEgOtzME2GbNM2roU29AMTuOIN91dsMO30t+Ydis3z/3Ngoj5hhnsOSSwNzS+6JppqWsuhTp6E82l2w==} - engines: {node: '>=18'} - cpu: [arm64] - os: [freebsd] - '@esbuild/freebsd-arm64@0.28.1': resolution: {integrity: sha512-wG2EA8ENdEI0qhkSZMjfqrdY+ziCYCPMmtZjjIwOmXFjmyzEHn+UUxk5of+SYsjtfs3VpnlC7QLzSI5hY/rOAw==} engines: {node: '>=18'} cpu: [arm64] os: [freebsd] - '@esbuild/freebsd-x64@0.27.7': - resolution: {integrity: sha512-jOBDK5XEjA4m5IJK3bpAQF9/Lelu/Z9ZcdhTRLf4cajlB+8VEhFFRjWgfy3M1O4rO2GQ/b2dLwCUGpiF/eATNQ==} - engines: {node: '>=18'} - cpu: [x64] - os: [freebsd] - '@esbuild/freebsd-x64@0.28.1': resolution: {integrity: sha512-i7dZ9vQgnvSCzi/rYCXNgtF/U+eKZNJBzu3eTQbRgHnM7tNSizLOkRFAl3qzVc/Op/u5YkHHa4pf/3DOYHthLQ==} engines: {node: '>=18'} cpu: [x64] os: [freebsd] - '@esbuild/linux-arm64@0.27.7': - resolution: {integrity: sha512-RZPHBoxXuNnPQO9rvjh5jdkRmVizktkT7TCDkDmQ0W2SwHInKCAV95GRuvdSvA7w4VMwfCjUiPwDi0ZO6Nfe9A==} - engines: {node: '>=18'} - cpu: [arm64] - os: [linux] - '@esbuild/linux-arm64@0.28.1': resolution: {integrity: sha512-yHs+0uc8+nvEAfAfxrWQKK5peSNzBc4PegcMO0EJ2hT71uA7vB8Ihg2e77R2P7SG5uYjPbHlLLmve4LLLRCf0g==} engines: {node: '>=18'} cpu: [arm64] os: [linux] - '@esbuild/linux-arm@0.27.7': - resolution: {integrity: sha512-RkT/YXYBTSULo3+af8Ib0ykH8u2MBh57o7q/DAs3lTJlyVQkgQvlrPTnjIzzRPQyavxtPtfg0EopvDyIt0j1rA==} - engines: {node: '>=18'} - cpu: [arm] - os: [linux] - '@esbuild/linux-arm@0.28.1': resolution: {integrity: sha512-qVXBOHQS+d5Y722GwJzJUtOLlX7km3CraOaGormF1pDtPd2C/l1SHRPgjLunLGe51Sh5YYWKMFDyV4SxgMQYTQ==} engines: {node: '>=18'} cpu: [arm] os: [linux] - '@esbuild/linux-ia32@0.27.7': - resolution: {integrity: sha512-GA48aKNkyQDbd3KtkplYWT102C5sn/EZTY4XROkxONgruHPU72l+gW+FfF8tf2cFjeHaRbWpOYa/uRBz/Xq1Pg==} - engines: {node: '>=18'} - cpu: [ia32] - os: [linux] - '@esbuild/linux-ia32@0.28.1': resolution: {integrity: sha512-d1z4ZuP0ajrfz/FhGT4vv278rX8KnPPJx8i5+AtK7TYbx9Le9F1hyzurZpkEyjkGa9dUGhQow4C1NmeGvqxN2w==} engines: {node: '>=18'} cpu: [ia32] os: [linux] - '@esbuild/linux-loong64@0.27.7': - resolution: {integrity: sha512-a4POruNM2oWsD4WKvBSEKGIiWQF8fZOAsycHOt6JBpZ+JN2n2JH9WAv56SOyu9X5IqAjqSIPTaJkqN8F7XOQ5Q==} - engines: {node: '>=18'} - cpu: [loong64] - os: [linux] - '@esbuild/linux-loong64@0.28.1': resolution: {integrity: sha512-M5sRjUVZrkm1OAPR3dlOYzNmN+loZKGVi1VUQGrwuqLcbR6qeAz+famMhjASeH3YVKvZz+zT1jlh/keC3Rj/lg==} engines: {node: '>=18'} cpu: [loong64] os: [linux] - '@esbuild/linux-mips64el@0.27.7': - resolution: {integrity: sha512-KabT5I6StirGfIz0FMgl1I+R1H73Gp0ofL9A3nG3i/cYFJzKHhouBV5VWK1CSgKvVaG4q1RNpCTR2LuTVB3fIw==} - engines: {node: '>=18'} - cpu: [mips64el] - os: [linux] - '@esbuild/linux-mips64el@0.28.1': resolution: {integrity: sha512-mRObBZeHh2OxcBFPWE/FjylkRgZdYuiTR3vaTozquCGOH14iP9oN4x4Ge81CoIDYQrXmIxpFumJBu5MtZpnQJQ==} engines: {node: '>=18'} cpu: [mips64el] os: [linux] - '@esbuild/linux-ppc64@0.27.7': - resolution: {integrity: sha512-gRsL4x6wsGHGRqhtI+ifpN/vpOFTQtnbsupUF5R5YTAg+y/lKelYR1hXbnBdzDjGbMYjVJLJTd2OFmMewAgwlQ==} - engines: {node: '>=18'} - cpu: [ppc64] - os: [linux] - '@esbuild/linux-ppc64@0.28.1': resolution: {integrity: sha512-slScBsMAb3GFDcdrCgLwZtPYRoH2H/youv10QiZyRjmsP48fznoveWytSgCI/R0ZcUgpc0ZhIUEx6LHts8yrfQ==} engines: {node: '>=18'} cpu: [ppc64] os: [linux] - '@esbuild/linux-riscv64@0.27.7': - resolution: {integrity: sha512-hL25LbxO1QOngGzu2U5xeXtxXcW+/GvMN3ejANqXkxZ/opySAZMrc+9LY/WyjAan41unrR3YrmtTsUpwT66InQ==} - engines: {node: '>=18'} - cpu: [riscv64] - os: [linux] - '@esbuild/linux-riscv64@0.28.1': resolution: {integrity: sha512-kw0owk1o0GFETUJyW0jc0G4Yzs0BHZn0JDZ8JRT088vjJYX777BAs1fDGxAC+q831qOs2DTC96mNsG2opdfyyQ==} engines: {node: '>=18'} cpu: [riscv64] os: [linux] - '@esbuild/linux-s390x@0.27.7': - resolution: {integrity: sha512-2k8go8Ycu1Kb46vEelhu1vqEP+UeRVj2zY1pSuPdgvbd5ykAw82Lrro28vXUrRmzEsUV0NzCf54yARIK8r0fdw==} - engines: {node: '>=18'} - cpu: [s390x] - os: [linux] - '@esbuild/linux-s390x@0.28.1': resolution: {integrity: sha512-/lAIjX8aYFRByhh6L5rYtPEDRqa9de/4V/juOXcta5frjvzXO4/sqEtyytse0g3zZFuWu5cDN0MkLz2qRDD2Ag==} engines: {node: '>=18'} cpu: [s390x] os: [linux] - '@esbuild/linux-x64@0.27.7': - resolution: {integrity: sha512-hzznmADPt+OmsYzw1EE33ccA+HPdIqiCRq7cQeL1Jlq2gb1+OyWBkMCrYGBJ+sxVzve2ZJEVeePbLM2iEIZSxA==} - engines: {node: '>=18'} - cpu: [x64] - os: [linux] - '@esbuild/linux-x64@0.28.1': resolution: {integrity: sha512-u/anNYF2mmVOEDwLtnQ1wOr3EZ9sTNGLWrsYGYwHWzGA3Si84IOkHXlbWTD1NB+9/1lcnweYKO54uhxZydNzfA==} engines: {node: '>=18'} cpu: [x64] os: [linux] - '@esbuild/netbsd-arm64@0.27.7': - resolution: {integrity: sha512-b6pqtrQdigZBwZxAn1UpazEisvwaIDvdbMbmrly7cDTMFnw/+3lVxxCTGOrkPVnsYIosJJXAsILG9XcQS+Yu6w==} - engines: {node: '>=18'} - cpu: [arm64] - os: [netbsd] - '@esbuild/netbsd-arm64@0.28.1': resolution: {integrity: sha512-oks0DYbLwWMmaakTsCb+zL4E+aHRVLom9IJZOAthMQEPiQmydXHkziYEsGYRx0uNV/IjEKGAV941JzH02pflqw==} engines: {node: '>=18'} cpu: [arm64] os: [netbsd] - '@esbuild/netbsd-x64@0.27.7': - resolution: {integrity: sha512-OfatkLojr6U+WN5EDYuoQhtM+1xco+/6FSzJJnuWiUw5eVcicbyK3dq5EeV/QHT1uy6GoDhGbFpprUiHUYggrw==} - engines: {node: '>=18'} - cpu: [x64] - os: [netbsd] - '@esbuild/netbsd-x64@0.28.1': resolution: {integrity: sha512-aeL6lAnN89Hz43Mlh1G8ARasbuoYvSITDEx0tHh5b7jJnHcssqgjy9Yx430GDpmCa6OyrKoS0aNRjKundRizGg==} engines: {node: '>=18'} cpu: [x64] os: [netbsd] - '@esbuild/openbsd-arm64@0.27.7': - resolution: {integrity: sha512-AFuojMQTxAz75Fo8idVcqoQWEHIXFRbOc1TrVcFSgCZtQfSdc1RXgB3tjOn/krRHENUB4j00bfGjyl2mJrU37A==} - engines: {node: '>=18'} - cpu: [arm64] - os: [openbsd] - '@esbuild/openbsd-arm64@0.28.1': resolution: {integrity: sha512-MEFJe5C3R8pwXdZ5Y21oo6m7ePiS0d9pWucn99O/wvyJZChoIQKrQDxKrGeW8F5+T0okTHesAmDeiHDTIq0V/Q==} engines: {node: '>=18'} cpu: [arm64] os: [openbsd] - '@esbuild/openbsd-x64@0.27.7': - resolution: {integrity: sha512-+A1NJmfM8WNDv5CLVQYJ5PshuRm/4cI6WMZRg1by1GwPIQPCTs1GLEUHwiiQGT5zDdyLiRM/l1G0Pv54gvtKIg==} - engines: {node: '>=18'} - cpu: [x64] - os: [openbsd] - '@esbuild/openbsd-x64@0.28.1': resolution: {integrity: sha512-i/ZLIOafE0Z8cI/XANJAixoJL/uRAoS2xOA3rb0xN+KK0K177cMAsQYkzHtBrtMXAKuAc7HGgcWiZ/sRC1Nxgw==} engines: {node: '>=18'} cpu: [x64] os: [openbsd] - '@esbuild/openharmony-arm64@0.27.7': - resolution: {integrity: sha512-+KrvYb/C8zA9CU/g0sR6w2RBw7IGc5J2BPnc3dYc5VJxHCSF1yNMxTV5LQ7GuKteQXZtspjFbiuW5/dOj7H4Yw==} - engines: {node: '>=18'} - cpu: [arm64] - os: [openharmony] - '@esbuild/openharmony-arm64@0.28.1': resolution: {integrity: sha512-ge+Z7EXFNt2BO1oAMsVpiQ8EwndV9i1xXerAeTIK7AtPs3bKFXQM7nlRxDSIUIMeueR1CNXxqztLzdNeReKBJg==} engines: {node: '>=18'} cpu: [arm64] os: [openharmony] - '@esbuild/sunos-x64@0.27.7': - resolution: {integrity: sha512-ikktIhFBzQNt/QDyOL580ti9+5mL/YZeUPKU2ivGtGjdTYoqz6jObj6nOMfhASpS4GU4Q/Clh1QtxWAvcYKamA==} - engines: {node: '>=18'} - cpu: [x64] - os: [sunos] - '@esbuild/sunos-x64@0.28.1': resolution: {integrity: sha512-BEjgtECkL3vY+SaSQ6nzVfiALUeFxpawyp8Jmf5PtYhf1Ug40N1h/hxlhts+f1FvSvarEigdxS3BlSMI2PJLcQ==} engines: {node: '>=18'} cpu: [x64] os: [sunos] - '@esbuild/win32-arm64@0.27.7': - resolution: {integrity: sha512-7yRhbHvPqSpRUV7Q20VuDwbjW5kIMwTHpptuUzV+AA46kiPze5Z7qgt6CLCK3pWFrHeNfDd1VKgyP4O+ng17CA==} - engines: {node: '>=18'} - cpu: [arm64] - os: [win32] - '@esbuild/win32-arm64@0.28.1': resolution: {integrity: sha512-lCv9eK/H6ZJWbE7bh2nw54CZ9M2nupBxJcTsdk/QQnWkdSjKGuxmmH8/GWrlT1eMmZfn4dGcCjRte397WqfQXA==} engines: {node: '>=18'} cpu: [arm64] os: [win32] - '@esbuild/win32-ia32@0.27.7': - resolution: {integrity: sha512-SmwKXe6VHIyZYbBLJrhOoCJRB/Z1tckzmgTLfFYOfpMAx63BJEaL9ExI8x7v0oAO3Zh6D/Oi1gVxEYr5oUCFhw==} - engines: {node: '>=18'} - cpu: [ia32] - os: [win32] - '@esbuild/win32-ia32@0.28.1': resolution: {integrity: sha512-zvb/mB2bSCoJOpoCBgYKKpX6YM6mJBlBUVUtVj41DlZJVEB6/0CKlRYxP5wWl1C1ILiCoAU5wZZ4q1P3qeS6Eg==} engines: {node: '>=18'} cpu: [ia32] os: [win32] - '@esbuild/win32-x64@0.27.7': - resolution: {integrity: sha512-56hiAJPhwQ1R4i+21FVF7V8kSD5zZTdHcVuRFMW0hn753vVfQN8xlx4uOPT4xoGH0Z/oVATuR82AiqSTDIpaHg==} - engines: {node: '>=18'} - cpu: [x64] - os: [win32] - '@esbuild/win32-x64@0.28.1': resolution: {integrity: sha512-bm4Mowrv+GXMlpWX++EcXw/iLyd1o3+bJkC2DkWXYVvgZCqD/bSj9ctZeAMC3cIxgjRVR2Dufaiu4YPxr5gW1A==} engines: {node: '>=18'} @@ -525,16 +337,6 @@ packages: resolution: {integrity: sha512-VVPPgHyQ6ShqnrmDWuxjmUIsO9gWyOZFmuOfLd9LfBGQJwZfy0gvv9pbHSJuoFNIYC7ZDX9aoFwowjcdSC4E8w==} engines: {node: '>=18'} - '@napi-rs/wasm-runtime@1.2.4': - resolution: {integrity: sha512-AJxoUD2/15ESHbvpcyjU274nsAPLuOtPHCk0vKJM5pj//Fg/B1FXNWjPnXTT9PymCYYiHo4zPj0ZomXBKhoy7g==} - engines: {node: ^20.19.0 || ^22.13.0 || >=23.5.0} - peerDependencies: - '@emnapi/core': ^1.7.1 || ^2.0.0-alpha.4 - '@emnapi/runtime': ^1.7.1 || ^2.0.0-alpha.4 - - '@next/env@14.2.35': - resolution: {integrity: sha512-DuhvCtj4t9Gwrx80dmz2F4t/zKQ4ktN8WrMwOuVzkJfBilwAwGr6v16M5eI8yCuZ63H9TTuEU09Iu2HqkzFPVQ==} - '@open-draft/deferred-promise@2.2.0': resolution: {integrity: sha512-CecwLWx3rhxVQF6V4bAgPS5t+So2sTbPgAzafKkVizyi7tlwpcFpdFqq+wqF2OwNBmqFuu6tOyouTuxgpMfzmA==} @@ -576,7 +378,7 @@ packages: '@opentelemetry/exporter-jaeger@1.9.0': resolution: {integrity: sha512-XLsDLuTqQyw08s0n03zeqmz2ike/iQHiep819SJliJnJJKMbGta+JvaWWgrh/YUEYlbBLc/mQP1cndXtd8lbaA==} engines: {node: '>=14'} - deprecated: Package no longer supported. Contact Support at https://www.npmjs.com/support for more info. + deprecated: Package no longer supported. See https://github.com/open-telemetry/opentelemetry-js/pull/7098 peerDependencies: '@opentelemetry/api': ^1.0.0 @@ -662,6 +464,7 @@ packages: '@opentelemetry/propagator-jaeger@1.9.0': resolution: {integrity: sha512-oo8RyuyzEbbXdIfeEG9iA5vmTH4Kld+dZMIZICd5G5SmeNcNes3sLrparpunIGms41wIP2mWiIlcOelDCmGceg==} engines: {node: '>=14'} + deprecated: Package no longer supported. See https://github.com/open-telemetry/opentelemetry-js/pull/7077 peerDependencies: '@opentelemetry/api': '>=1.0.0 <1.5.0' @@ -704,18 +507,21 @@ packages: '@opentelemetry/sdk-trace-base@1.30.1': resolution: {integrity: sha512-jVPgBbH1gCy2Lb7X0AVQ8XAfgg0pJ4nvl8/IiQA6nxOsPvS+0zMJaFSs2ltXe0J6C8dqjcnpyqINDJmU30+uOg==} engines: {node: '>=14'} + deprecated: Use @opentelemetry/sdk-trace package peerDependencies: '@opentelemetry/api': '>=1.0.0 <1.10.0' '@opentelemetry/sdk-trace-base@1.9.0': resolution: {integrity: sha512-glNgtJjxAIrDku8DG5Xu3nBK25rT+hkyg7yuXh8RUurp/4BcsXjMyVqpyJvb2kg+lxAX73VJBhncRKGHn9t8QQ==} engines: {node: '>=14'} + deprecated: Use @opentelemetry/sdk-trace package peerDependencies: '@opentelemetry/api': '>=1.0.0 <1.5.0' '@opentelemetry/sdk-trace-node@1.9.0': resolution: {integrity: sha512-VTpjiqGQ4s8f0/szgZmVrtNSSmXFNoHwC4PNVwXyNeEqQcqyAygHvobpUG6m7qCiBFh6ZtrCuLdhhqWE04Objw==} engines: {node: '>=14'} + deprecated: Use @opentelemetry/sdk-trace package peerDependencies: '@opentelemetry/api': '>=1.0.0 <1.5.0' @@ -727,292 +533,345 @@ packages: resolution: {integrity: sha512-po7penSfQ/Z8352lRVDpaBrd9znwA5mHGqXR7nDEiVnxkDFkBIhVf/tKeAJDIq/erFpcRowKFeCsr5eqqcSyFQ==} engines: {node: '>=14'} - '@oxc-project/types@0.132.0': - resolution: {integrity: sha512-FESMOxil5Se014ui/Eq8fT5uHJo6nIRwH0PfJrZJXs6Gek3ZVFOrpUv3YIZT20m+extU98Hg1Ym72U58rlsxUQ==} - - '@protobufjs/aspromise@1.1.2': - resolution: {integrity: sha512-j+gKExEuLmKwvz3OgROXtrJ2UG2x8Ch2YZUxahh+s1F2HZ+wAceUNLkvy6zKCPVRkU++ZWQrdxsUeQXmcg4uoQ==} - - '@protobufjs/base64@1.1.2': - resolution: {integrity: sha512-AZkcAA5vnN/v4PDqKyMR5lx7hZttPDgClv83E//FMNhR2TMcLUhfRUBHCmSl0oi9zMgDDqRUJkSxO3wm85+XLg==} - - '@protobufjs/codegen@2.0.5': - resolution: {integrity: sha512-zgXFLzW3Ap33e6d0Wlj4MGIm6Ce8O89n/apUaGNB/jx+hw+ruWEp7EwGUshdLKVRCxZW12fp9r40E1mQrf/34g==} - - '@protobufjs/eventemitter@1.1.1': - resolution: {integrity: sha512-vW1GmwMZNnL+gMRaovlh9yZX74kc+TTU3FObkkurpMaRtBfLP3ldjS9KQWlwZgraRE0+dheEEoAxdzcJQ8eXZg==} - - '@protobufjs/fetch@1.1.1': - resolution: {integrity: sha512-GpptLrs57adMSuHi3VNj0mAF8dwh36LMaYF6XyJ6JMWlVsc+t42tm1HSEDmOs3A8fC9yyeisgLhsTVQokOZ0zw==} - - '@protobufjs/float@1.0.2': - resolution: {integrity: sha512-Ddb+kVXlXst9d+R9PfTIxh1EdNkgoRe5tOX6t01f1lYWOvJnSPDBlG241QLzcyPdoNTsblLUdujGSE4RzrTZGQ==} - - '@protobufjs/path@1.1.2': - resolution: {integrity: sha512-6JOcJ5Tm08dOHAbdR3GrvP+yUUfkjG5ePsHYczMFLq3ZmMkAD98cDgcT2iA1lJ9NVwFd4tH/iSSoe44YWkltEA==} + '@oxc-project/runtime@0.153.0': + resolution: {integrity: sha512-rXJvtlsVG4xJNq64hiaUTIatE7jbBgpa6l4QgmEa2jLgT3ULH9scUBkpsC9N1DfmqixLEyT6DuK6OSXozziIlw==} + engines: {node: ^20.19.0 || >=22.12.0} - '@protobufjs/pool@1.1.0': - resolution: {integrity: sha512-0kELaGSIDBKvcgS4zkjz1PeddatrjYcmMWOlAuAPwAeccUrPHdUqo/J6LiymHHEiJT5NrF1UVwxY14f+fy4WQw==} + '@oxc-project/types@0.153.0': + resolution: {integrity: sha512-jSNrRIyvPVLL5E1z/tS5zLAprosGz/FuI1PWwEOz59j3E4TTTzSpqKb/RDGqJhhW0QHYISGXxq582wGx6T11Tw==} - '@protobufjs/utf8@1.1.1': - resolution: {integrity: sha512-oOAWABowe8EAbMyWKM0tYDKi8Yaox52D+HWZhAIJqQXbqe0xI/GV7FhLWqlEKreMkfDjshR5FKgi3mnle0h6Eg==} + '@oxfmt/binding-android-arm-eabi@0.72.0': + resolution: {integrity: sha512-u4U1uaDOVBuorGNC/07NLm48nECHOiJJka002YGML16Md2DRvf38RGBt01lH60EOGvNlFVmbLoksQedNBoZHKw==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [arm] + os: [android] - '@rolldown/binding-android-arm64@1.0.2': - resolution: {integrity: sha512-ZS4D1JPGn/MYQN/SYDWftIE/nVsM8j/AFOYEzAoOE2O3NktQOZru+/vYXGbR/qtdLdIfGCP0lcoJiYVzsEz+iQ==} + '@oxfmt/binding-android-arm64@0.72.0': + resolution: {integrity: sha512-GCnM+Ae2mWOKC+92y3FOb6eZJeVnBAVz82e3lITUUg+kxukYKzBNAvJ/hhHIfa2A0md55SYYhdWPddSSy3KPVA==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [arm64] os: [android] - '@rolldown/binding-darwin-arm64@1.0.2': - resolution: {integrity: sha512-vdFA9+C/rekyGce7WqHs/xoT0ioZEWaOFyZLIV1mEeNFaFDUQrPIo8Vs2GvJ6eetb3rzDUtUBgzto3ExpXJB3w==} + '@oxfmt/binding-darwin-arm64@0.72.0': + resolution: {integrity: sha512-LjQ8tlevdtwraFlWC6AnQ1Forzde0clRmnsP3QmK+ydSElDt/J+5Jc+m+//Ly1G97aP7WCNUw110QMIUw2K7xA==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [arm64] os: [darwin] - '@rolldown/binding-darwin-x64@1.0.2': - resolution: {integrity: sha512-BewSOwTHazv77DTYiAZXSqqKZ4KP/KonFisDMVU7PImxoWfB2aepnPhd2E4SWz3zDzYgDNbs6jBmTdgNnF02GA==} + '@oxfmt/binding-darwin-x64@0.72.0': + resolution: {integrity: sha512-+ZbcxhB1C34RQR5IldSyY6Z/yalJIxHXhP36PGE4sEq0f/lgL5jCLmOKoDeVm8dqd9gFGDsd1bNWHSGCAwdSkw==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [x64] os: [darwin] - '@rolldown/binding-freebsd-x64@1.0.2': - resolution: {integrity: sha512-m41o7M0YWtUdqk61Tb+jnKb2rN++iRdIASlExkUoKfIAH30DOHCB8fVLzSUpbWHHU8esmEioY62PxzexE8MBuA==} + '@oxfmt/binding-freebsd-x64@0.72.0': + resolution: {integrity: sha512-yGPhA8sePEBFV9iGcprhyOk3Bz3fZshswMH3DpYIPZFLT1DG5LjiZ0j6AJQpSueaL6bUK0av1K+5+fe+ldy5EQ==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [x64] os: [freebsd] - '@rolldown/binding-linux-arm-gnueabihf@1.0.2': - resolution: {integrity: sha512-jcojB9H7W/jS29pMKWAK1N+fU99vXodHDTatS3b3y/XSOCiHo0kkA74pL3jJmkoQtYpOCxDvaKs1fo2Ij/1X5w==} + '@oxfmt/binding-linux-arm-gnueabihf@0.72.0': + resolution: {integrity: sha512-/g5axuqFMcAtoQrFLR5EJEMDjR5Ea4GuzpdDmMpPWAgrF1DmL4kD1NPNVlpXEOxpFJnD6lPvLKYj1lzdlrfifA==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [arm] + os: [linux] + + '@oxfmt/binding-linux-arm-musleabihf@0.72.0': + resolution: {integrity: sha512-InVitdHbS09PM+w+WinIiyX37rC9R3L7lUP9mKy9uKTEDSUFaLjWsLoreRIxKivnv7Ndqc6Trvi+K+b6ZzhiJg==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [arm] os: [linux] - '@rolldown/binding-linux-arm64-gnu@1.0.2': - resolution: {integrity: sha512-1jn6qDU5iiOgFgygDzKUuKP0maTi0/f1+sBLgvij/76C77Nm3ts6ufz9Bjg5q5dduxiUIxtq86JIoBvo1xQ4Ig==} + '@oxfmt/binding-linux-arm64-gnu@0.72.0': + resolution: {integrity: sha512-WKjlyRAxpPjXoqeuwFBim5LscgvY+RNEiZ971C9JlRQZONhJTsXDki/oHFYQE+fzzRpzXh3owZvOdT3kNL2g+g==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [arm64] os: [linux] libc: [glibc] - '@rolldown/binding-linux-arm64-musl@1.0.2': - resolution: {integrity: sha512-QVLO/czFMdoMFSqlX3bcswcJNm/23r+qoa/jgtmFc/qEp6/jXmIkDjF/XIo8dPfGaiwy1xfQn8o77L79GeXFgw==} + '@oxfmt/binding-linux-arm64-musl@0.72.0': + resolution: {integrity: sha512-poafnbsPpJhR+5Mbwpf8CscugTyiRAzjTOxRVn8ra3X2jnURs2xL5Du2xl5g2iW85lICMglpmLf8jDjZEbNgzQ==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [arm64] os: [linux] libc: [musl] - '@rolldown/binding-linux-ppc64-gnu@1.0.2': - resolution: {integrity: sha512-hgO5Abm0w5UL6FEa2iFnZqo2KlK7TQ5QhV5x09hujBf7t5KzHQ1VmfPuTpqRy/rNlSxua3eWH374xxiVrP+lcA==} + '@oxfmt/binding-linux-ppc64-gnu@0.72.0': + resolution: {integrity: sha512-zNUi0gKRsO7S3IfPzsTbnm88OedPuX32hmZFWZRWHvWJlAoZRwVbeO6Nb3hng4vDgL7qUKp4TGqzPz1mhsycYg==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [ppc64] os: [linux] libc: [glibc] - '@rolldown/binding-linux-s390x-gnu@1.0.2': - resolution: {integrity: sha512-fy8rXxuYEu602abC8MUNaPjYLIFzReOaEIEMKMUa0rFEUxNpVXhs15KSSQ4qlqSaM7B6rcj9rDZgADh/IGDzLQ==} + '@oxfmt/binding-linux-riscv64-gnu@0.72.0': + resolution: {integrity: sha512-qTY7z/iyF/rWI2HWwWMevXqZW5PZUOofUy2afK7v6rseXnpld3yjgyIk6pCux3DqWQ0lB+f5atvLJNBwzLvFsw==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [riscv64] + os: [linux] + libc: [glibc] + + '@oxfmt/binding-linux-riscv64-musl@0.72.0': + resolution: {integrity: sha512-w18qfo8vEMD6EKEHtzTvjc6jH8yCyXIT1Bn7PFWJd0PpZyzH/VQDx4xvjCh0LGz6GRzqiezbisTdG9AoXtpkZQ==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [riscv64] + os: [linux] + libc: [musl] + + '@oxfmt/binding-linux-s390x-gnu@0.72.0': + resolution: {integrity: sha512-grW8HwEir+vK7fCcq1ZgL3NHQCfZ1PidiERE9Hbf+24xGys+DoDaoqTht4ccfn0PcBwbViLkwkda/KNpJu2KRQ==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [s390x] os: [linux] libc: [glibc] - '@rolldown/binding-linux-x64-gnu@1.0.2': - resolution: {integrity: sha512-0+bOkiQ779+r1WpoHOWHqncvyySci0vKph+myNDYb+im6meJAzHQXay6oEgnkHuUGouM1LKTZwqKpBow6Kj7CQ==} + '@oxfmt/binding-linux-x64-gnu@0.72.0': + resolution: {integrity: sha512-wQJRQfWBRIn88Pkr449EUViwRayrYUScg7ty+Ihiem21mQ9BUA6S9e/4RAgyNk9lMag0KBouyKskjq61rOC0WQ==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [x64] os: [linux] libc: [glibc] - '@rolldown/binding-linux-x64-musl@1.0.2': - resolution: {integrity: sha512-mjSkrzZK5Qsl0a9d1JgILOiuZOSDTVdKENcSXBoqbzSrspLR/4/IRVDo5wd2GgZjNss/viBFJdeq+j7qH2nypw==} + '@oxfmt/binding-linux-x64-musl@0.72.0': + resolution: {integrity: sha512-T1B0uQzafxuGPjjaPrZJ497nxoi1BiI65JfsYc56nNpmev3msfb/iaTmH2kQuNFqa8PKxpMlf3GhWrWBvBPORQ==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [x64] os: [linux] libc: [musl] - '@rolldown/binding-openharmony-arm64@1.0.2': - resolution: {integrity: sha512-1v5vHasdfQAZoEHakBV72LIFAC9JjnymsiKxp+GEr/ma3+NJCPSaYK+qavInOovJkgwFrs7GccX2d6IgDA3Z5w==} + '@oxfmt/binding-openharmony-arm64@0.72.0': + resolution: {integrity: sha512-OKxxgTurl7+hypVplO8QRAbbQlwEs0j1Y6FZv+ZN2qNbdxQk2muMlzuJNBGTCWwxQ0rkkOD1uK/DW1fvzoUSJA==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [arm64] os: [openharmony] - '@rolldown/binding-wasm32-wasi@1.0.2': - resolution: {integrity: sha512-mb1VobWn6NheziTk5/WEaR6AKVbrwT5sOi6C7zk3gy/pD1qtJfU1j4PgTo2NJnOtbL9Dl3Aeei8w9jJ7qC2jZQ==} + '@oxfmt/binding-win32-arm64-msvc@0.72.0': + resolution: {integrity: sha512-L78TKzURqvjuZOCo88ixMbPXf/RP7u31Y0cmjJ/KEmrHmfYhmhEvsDaTYPiyTRne64jUgCbADfY0YeoQk4/Xyw==} engines: {node: ^20.19.0 || >=22.12.0} - cpu: [wasm32] + cpu: [arm64] + os: [win32] - '@rolldown/binding-win32-arm64-msvc@1.0.2': - resolution: {integrity: sha512-SqKonF56vA/L2yHwHYcEp2P34URpOZ7d1fS635cTkpDnUtEGdUbhI6NzsPdqeSWvAAeGDrxjWjNmibDIdFf9/A==} + '@oxfmt/binding-win32-ia32-msvc@0.72.0': + resolution: {integrity: sha512-gQtFW+Ii8BWgtJTAD17TY/WsdNS5gUfAPov8iI1jeq4r9lRRYImmxPL08UU00/RLaCfBJwh2UgINnDY0iOiZ+A==} engines: {node: ^20.19.0 || >=22.12.0} - cpu: [arm64] + cpu: [ia32] os: [win32] - '@rolldown/binding-win32-x64-msvc@1.0.2': - resolution: {integrity: sha512-v7qRI7gXLRINcOGXt+7YmAZ6iFuyZVMIoXAxhd8oP+DR9dLfL9GfNIx7PLMxmhZdvq8waUJBQiWN9EKNy+TRBQ==} + '@oxfmt/binding-win32-x64-msvc@0.72.0': + resolution: {integrity: sha512-uhooIN+DCzWX5uH7mQkSVCsODqsGluGJ4n3ch4+mITtWKpESMDjUJUSDBAVFuYQDwqY2McPDKdKJbM0oZVW/Qw==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [x64] os: [win32] - '@rolldown/pluginutils@1.0.1': - resolution: {integrity: sha512-2j9bGt5Jh8hj+vPtgzPtl72j0yRxHAyumoo6TNfAjsLB04UtpSvPbPcDcBMxz7n+9CYB0c1GxQFxYRg2jimqGw==} + '@oxlint-tsgolint/darwin-arm64@7.0.2003': + resolution: {integrity: sha512-TgV33rXr6ueXBwvc+0nssUkTBSXHJxv77I8p4RCjDjCnvexHtmoPiudVRDfj3S3puMDdeQdHW6jdUgUPy3nr/w==} + cpu: [arm64] + os: [darwin] + + '@oxlint-tsgolint/darwin-x64@7.0.2003': + resolution: {integrity: sha512-hY3FMAjIaPDdK3FNxyRXRfHPOFeoBn6dmnLyKZgQ2IbTTD1adXhl6PfCIqHhCPvurigv7nf7mYuWZZ19MmJzmg==} + cpu: [x64] + os: [darwin] + + '@oxlint-tsgolint/linux-arm64@7.0.2003': + resolution: {integrity: sha512-eAET4JpyfBbg8SO0K74o4R55tEjVC292pIyxGOw2XGf8x/HrPNyxwQ478jMYOM54FIVOHc8sJ6VRHxluy6lucw==} + cpu: [arm64] + os: [linux] + + '@oxlint-tsgolint/linux-x64@7.0.2003': + resolution: {integrity: sha512-GXdyO/XyqDJ3s/llR/oOktLsYNjZtWSQBy0JMc+/0gsNPvTcseKPhn9c6KcFyWmnr6H4vljYALbujonqSzzEGw==} + cpu: [x64] + os: [linux] + + '@oxlint-tsgolint/win32-arm64@7.0.2003': + resolution: {integrity: sha512-TWauXnPfet0VgpmrstoAK58eJ4gbuwPUuUTQmYQAzE/RG1CpnxXtrKUJULf6lyerPE4KN3gM+DflIA1hOH+38Q==} + cpu: [arm64] + os: [win32] + + '@oxlint-tsgolint/win32-x64@7.0.2003': + resolution: {integrity: sha512-DoRmfe7j8VqNlukp8liRVW45GQDhzRccNenjD/pdzelgtffW47pCMd1xbJLkPaPbKnTwID3onn3VZlL7JbebEA==} + cpu: [x64] + os: [win32] - '@rollup/rollup-android-arm-eabi@4.60.4': - resolution: {integrity: sha512-F5QXMSiFebS9hKZj02XhWLLnRpJ3B3AROP0tWbFBSj+6kCbg5m9j5JoHKd4mmSVy5mS/IMQloYgYxCuJC0fxEQ==} + '@oxlint/binding-android-arm-eabi@1.87.0': + resolution: {integrity: sha512-22tVIHlB9898OYsBKmYB6He1MCSqdTmK1BVWuSNUTVrpS0KWNege+U9uEYqGJ+KatQag0r/oU44i3OOs46z7HQ==} + engines: {node: ^20.19.0 || >=22.12.0} cpu: [arm] os: [android] - '@rollup/rollup-android-arm64@4.60.4': - resolution: {integrity: sha512-GxxTKApUpzRhof7poWvCJHRF51C67u1R7D6DiluBE8wKU1u5GWE8t+v81JvJYtbawoBFX1hLv5Ei4eVjkWokaw==} + '@oxlint/binding-android-arm64@1.87.0': + resolution: {integrity: sha512-JfZQ3MWf74n8ov0DojZpkOduIubYJvZL5tbTMMhDutsal6u1xH1QuNpN+lK+EQwF8AeAfgwTR2fgh5U2br27XA==} + engines: {node: ^20.19.0 || >=22.12.0} cpu: [arm64] os: [android] - '@rollup/rollup-darwin-arm64@4.60.4': - resolution: {integrity: sha512-tua0TaJxMOB1R0V0RS1jFZ/RpURFDJIOR2A6jWwQeawuFyS4gBW+rntLRaQd0EQ4bd6Vp44Z2rXW+YYDBsj6IA==} + '@oxlint/binding-darwin-arm64@1.87.0': + resolution: {integrity: sha512-rkx6Xvvw93z2GImVOyvbZwAZcoY0Z6MJW177XNsSB+i/CBsH7ANyelTp01VZyIkM3iSXtqKVb4+ptBgPHap5pQ==} + engines: {node: ^20.19.0 || >=22.12.0} cpu: [arm64] os: [darwin] - '@rollup/rollup-darwin-x64@4.60.4': - resolution: {integrity: sha512-CSKq7MsP+5PFIcydhAiR1K0UhEI1A2jWXVKHPCBZ151yOutENwvnPocgVHkivu2kviURtCEB6zUQw0vs8RrhMg==} + '@oxlint/binding-darwin-x64@1.87.0': + resolution: {integrity: sha512-cw5nYw7L/pQ2SgGBgOcg/Qo89llQre3vIc/kuBLhSICzu1r5s8L76H3ug6AIFsD98WpROj8/z5JVzkf4daxHaQ==} + engines: {node: ^20.19.0 || >=22.12.0} cpu: [x64] os: [darwin] - '@rollup/rollup-freebsd-arm64@4.60.4': - resolution: {integrity: sha512-+O8OkVdyvXMtJEciu2wS/pzm1IxntEEQx3z5TAVy4l32G0etZn+RsA48ARRrFm6Ri8fvqPQfgrvNxSjKAbnd3g==} - cpu: [arm64] - os: [freebsd] - - '@rollup/rollup-freebsd-x64@4.60.4': - resolution: {integrity: sha512-Iw3oMskH3AfNuhU0MSN7vNbdi4me/NiYo2azqPz/Le16zHSa+3RRmliCMWWQmh4lcndccU40xcJuTYJZxNo/lw==} + '@oxlint/binding-freebsd-x64@1.87.0': + resolution: {integrity: sha512-x/SuznE6loRZK9gz9AS4ZmzNzSu+5KdeoCS+sVHhnrE8HM6AD8UyTjwldQTTqlXGvkwHASgB0NiTNOc3o3PdAg==} + engines: {node: ^20.19.0 || >=22.12.0} cpu: [x64] os: [freebsd] - '@rollup/rollup-linux-arm-gnueabihf@4.60.4': - resolution: {integrity: sha512-EIPRXTVQpHyF8WOo219AD2yEltPehLTcTMz2fn6JsatLYSzQf00hj3rulF+yauOlF9/FtM2WpkT/hJh/KJFGhA==} + '@oxlint/binding-linux-arm-gnueabihf@1.87.0': + resolution: {integrity: sha512-+JEDmCAXiiFmKhAjZhgAOwaHWY6WENOQ5QA+lFuY59tgsUCDOTRi46B+lENK6CWvtxsXpwkZXNOgpjAQcpIpnA==} + engines: {node: ^20.19.0 || >=22.12.0} cpu: [arm] os: [linux] - libc: [glibc] - '@rollup/rollup-linux-arm-musleabihf@4.60.4': - resolution: {integrity: sha512-J3Yh9PzzF1Ovah2At+lHiGQdsYgArxBbXv/zHfSyaiFQEqvNv7DcW98pCrmdjCZBrqBiKrKKe2V+aaSGWuBe/w==} + '@oxlint/binding-linux-arm-musleabihf@1.87.0': + resolution: {integrity: sha512-/uC9XqXLAC9AHFLNycwmSQEvKqGrOCgU+uXALI7gNW0DG81TJ/3T2eHnKdRvcb5R1gFEfjxDwI4e4zIa29rICQ==} + engines: {node: ^20.19.0 || >=22.12.0} cpu: [arm] os: [linux] - libc: [musl] - '@rollup/rollup-linux-arm64-gnu@4.60.4': - resolution: {integrity: sha512-BFDEZMYfUvLn37ONE1yMBojPxnMlTFsdyNoqncT0qFq1mAfllL+ATMMJd8TeuVMiX84s1KbcxcZbXInmcO2mRg==} + '@oxlint/binding-linux-arm64-gnu@1.87.0': + resolution: {integrity: sha512-FTRYPsBy/M728vnUv7l+KAwSAa3Qi4BQbhVvy7NYuMTZ0sUa/OWsbMx79HVFw6u/5KWcaYayDsoOiv7xbevzDg==} + engines: {node: ^20.19.0 || >=22.12.0} cpu: [arm64] os: [linux] libc: [glibc] - '@rollup/rollup-linux-arm64-musl@4.60.4': - resolution: {integrity: sha512-pc9EYOSlOgdQ2uPl1o9PF6/kLSgaUosia7gOuS8mB69IxJvlclko1MECXysjs5ryez1/5zjYqx3+xYU0TU6R1A==} + '@oxlint/binding-linux-arm64-musl@1.87.0': + resolution: {integrity: sha512-6sC+N7IY0h1egd8SrYUGNN7CUyKJZ4bIZakKa7V7hfinf4jfi8Kq4+klJvFp5DqXcwMn44I5MeBwmJluO1odFg==} + engines: {node: ^20.19.0 || >=22.12.0} cpu: [arm64] os: [linux] libc: [musl] - '@rollup/rollup-linux-loong64-gnu@4.60.4': - resolution: {integrity: sha512-NxnomyxYerDh5n4iLrNa+sH+Z+U4BMEE46V2PgQ/hoB909i8gV1M5wPojWg9fk1jWpO3IQnOs20K4wyZuFLEFQ==} - cpu: [loong64] - os: [linux] - libc: [glibc] - - '@rollup/rollup-linux-loong64-musl@4.60.4': - resolution: {integrity: sha512-nbJnQ8a3z1mtmrwImCYhc6BGpThAyYVRQxw9uKSKG4wR6aAYno9sVjJ0zaZcW9BPJX1GbrDPf+SvdWjgTuDmnw==} - cpu: [loong64] - os: [linux] - libc: [musl] - - '@rollup/rollup-linux-ppc64-gnu@4.60.4': - resolution: {integrity: sha512-2EU6acNrQLd8tYvo/LXW535wupT3m6fo7HKo6lr7ktQoItxTyOL1ZCR/GfGCuXl2vR+zmfI6eRXkSemafv+iVg==} + '@oxlint/binding-linux-ppc64-gnu@1.87.0': + resolution: {integrity: sha512-d5Mnn+ICbhoJPi37Ccjy+86T5qfH2+mSTrTMBXNPfB+wqSWOayBBLLIrCXdUy1YssluCVdGkl+DO9w5r5ODmUA==} + engines: {node: ^20.19.0 || >=22.12.0} cpu: [ppc64] os: [linux] libc: [glibc] - '@rollup/rollup-linux-ppc64-musl@4.60.4': - resolution: {integrity: sha512-WeBtoMuaMxiiIrO2IYP3xs6GMWkJP2C0EoT8beTLkUPmzV1i/UcOSVw1d5r9KBODtHKilG5yFxsGRnBbK3wJ4A==} - cpu: [ppc64] - os: [linux] - libc: [musl] - - '@rollup/rollup-linux-riscv64-gnu@4.60.4': - resolution: {integrity: sha512-FJHFfqpKUI3A10WrWKiFbBZ7yVbGT4q4B5o1qKFFojqpaYoh9LrQgqWCmmcxQzVSXYtyB5bzkXrYzlHTs21MYA==} + '@oxlint/binding-linux-riscv64-gnu@1.87.0': + resolution: {integrity: sha512-sf5YWuUo1Dx9loqWCgF8P278MycfcD4JHjDC/LV5j11L3Lqa4PFiwCjD6B8kP4AjsgZZkLn3xbpJEtZBaccs4Q==} + engines: {node: ^20.19.0 || >=22.12.0} cpu: [riscv64] os: [linux] libc: [glibc] - '@rollup/rollup-linux-riscv64-musl@4.60.4': - resolution: {integrity: sha512-mcEl6CUT5IAUmQf1m9FYSmVqCJlpQ8r8eyftFUHG8i9OhY7BkBXSUdnLH5DOf0wCOjcP9v/QO93zpmF1SptCCw==} + '@oxlint/binding-linux-riscv64-musl@1.87.0': + resolution: {integrity: sha512-+aCraYbmK3uU3P0rFojjeaK9pGY6JvBDXh/0CM+gPyWKmNmXmnFe0qj4qj02U4ibFpbd9Pa9CmDvIdjJUd29rA==} + engines: {node: ^20.19.0 || >=22.12.0} cpu: [riscv64] os: [linux] libc: [musl] - '@rollup/rollup-linux-s390x-gnu@4.60.4': - resolution: {integrity: sha512-ynt3JxVd2w2buzoKDWIyiV1pJW93xlQic1THVLXilz429oijRpSHivZAgp65KBu+cMcgf1eVVjdnTLvPxgCuoQ==} + '@oxlint/binding-linux-s390x-gnu@1.87.0': + resolution: {integrity: sha512-7rzbZQIljo6VLWVPqUyLa2P/WV3VFsJB4M52R4NCUJn1A50yjtUsHxiMHFL9opEKGFIiTbZ3VQzmtcJOPUq4NQ==} + engines: {node: ^20.19.0 || >=22.12.0} cpu: [s390x] os: [linux] libc: [glibc] - '@rollup/rollup-linux-x64-gnu@4.60.4': - resolution: {integrity: sha512-Boiz5+MsaROEWDf+GGEwF8VMHGhlUoQMtIPjOgA5fv4osupqTVnJteQNKJwUcnUog2G55jYXH7KZFFiJe0TEzQ==} + '@oxlint/binding-linux-x64-gnu@1.87.0': + resolution: {integrity: sha512-Dry46Fa+AXE/xt9J/YriujU1pU9mzfNWHGQwtOYibAFH8HLrKWCi93AhIGnuUMiwn2JbuBkRyz51f50Ly4r4ow==} + engines: {node: ^20.19.0 || >=22.12.0} cpu: [x64] os: [linux] libc: [glibc] - '@rollup/rollup-linux-x64-musl@4.60.4': - resolution: {integrity: sha512-+qfSY27qIrFfI/Hom04KYFw3GKZSGU4lXus51wsb5EuySfFlWRwjkKWoE9emgRw/ukoT4Udsj4W/+xxG8VbPKg==} + '@oxlint/binding-linux-x64-musl@1.87.0': + resolution: {integrity: sha512-f/HMZez74gJvnRpGz+b5sdGyWrEgRLmFTXg9sNoqjPWogGxYHUhXKTKyMiYsu9rUGR2ciYqL3fk+Ua4JhMJbRA==} + engines: {node: ^20.19.0 || >=22.12.0} cpu: [x64] os: [linux] libc: [musl] - '@rollup/rollup-openbsd-x64@4.60.4': - resolution: {integrity: sha512-VpTfOPHgVXEBeeR8hZ2O0F3aSso+JDWqTWmTmzcQKted54IAdUVbxE+j/MVxUsKa8L20HJhv3vUezVPoquqWjA==} - cpu: [x64] - os: [openbsd] - - '@rollup/rollup-openharmony-arm64@4.60.4': - resolution: {integrity: sha512-IPOsh5aRYuLv/nkU51X10Bf75Bsf6+gZdx1X+QP5QM6lIJFHHqbHLG0uJn/hWthzo13UAc2umiUorqZy3axoZg==} + '@oxlint/binding-openharmony-arm64@1.87.0': + resolution: {integrity: sha512-lEC6ZfPZm/07JlB7FsOWX9o1RiHlXiBW594LSUGuXmekC8LZmvUPP9XzE2WZcNxJi5cHA/Fw56o02c3JxgMA5g==} + engines: {node: ^20.19.0 || >=22.12.0} cpu: [arm64] os: [openharmony] - '@rollup/rollup-win32-arm64-msvc@4.60.4': - resolution: {integrity: sha512-4QzE9E81OohJ/HKzHhsqU+zcYYojVOXlFMs1DdyMT6qXl/niOH7AVElmmEdUNHHS/oRkc++d5k6Vy85zFs0DEw==} + '@oxlint/binding-win32-arm64-msvc@1.87.0': + resolution: {integrity: sha512-afBWKOD36dUiA9AenaiWpCg8xfyUVPYznyAAVXu413brK4RA5UI/Xoc4yfeGIT8G1a44MV+JR7hioryerBDzGQ==} + engines: {node: ^20.19.0 || >=22.12.0} cpu: [arm64] os: [win32] - '@rollup/rollup-win32-ia32-msvc@4.60.4': - resolution: {integrity: sha512-zTPgT1YuHHcd+Tmx7h8aml0FWFVelV5N54oHow9SLj+GfoDy/huQ+UV396N/C7KpMDMiPspRktzM1/0r1usYEA==} + '@oxlint/binding-win32-ia32-msvc@1.87.0': + resolution: {integrity: sha512-daptNsEOuru4/nJTxV4DBHNJ5GepzmaBVQf+jjgKQGi9NRlZKik+RFq4hUL7ewsF1pwjKM+SkYaMbF82UwFGnw==} + engines: {node: ^20.19.0 || >=22.12.0} cpu: [ia32] os: [win32] - '@rollup/rollup-win32-x64-gnu@4.60.4': - resolution: {integrity: sha512-DRS4G7mi9lJxqEDezIkKCaUIKCrLUUDCUaCsTPCi/rtqaC6D/jjwslMQyiDU50Ka0JKpeXeRBFBAXwArY52vBw==} + '@oxlint/binding-win32-x64-msvc@1.87.0': + resolution: {integrity: sha512-k+m/EShyXE29J/r1fjk0HlAzhN5JhIvj044oSJkXTEkCLrWZQgXb/3r4q5LTw/eaKW/4GuMXG2xmiJs3s5rPNw==} + engines: {node: ^20.19.0 || >=22.12.0} cpu: [x64] os: [win32] - '@rollup/rollup-win32-x64-msvc@4.60.4': - resolution: {integrity: sha512-QVTUovf40zgTqlFVrKA1uXMVvU2QWEFWfAH8Wdc48IxLvrJMQVMBRjuQyUpzZCDkakImib9eVazbWlC6ksWtJw==} - cpu: [x64] - os: [win32] + '@oxlint/plugins@1.79.0': + resolution: {integrity: sha512-S0uyoxakDINJ4DPgqxGlEEvrdSMeQb7Z2lKVjxoY2gwsbZbfg2Xr8Klfeo5ZeraHmmdBCELFUHkSe6KEmBpMvg==} + engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} - '@standard-schema/spec@1.1.0': - resolution: {integrity: sha512-l2aFy5jALhniG5HgqrD6jXLi/rUWrKvqN/qJx6yoJsgKhblVd+iqqU4RCXavm/jPityDo5TCvKMnpjKnOriy0w==} + '@polka/url@1.0.0-next.29': + resolution: {integrity: sha512-wwQAWhWSuHaag8c4q/KN/vCoeOJYshAIvMQwD4GpSb3OiZklFfvAgmj0VCBBImRpuF/aFgIRzllXlVX93Jevww==} - '@tybys/wasm-util@0.10.4': - resolution: {integrity: sha512-W3c4gRigFS0T/Ma4qIYF3GDAc5AQdHb1yL5znJT1Zv1YaD9Kitx656wBjvr19qbiosmZT8lWDM5BEMynUqX65A==} + '@protobufjs/aspromise@1.1.2': + resolution: {integrity: sha512-j+gKExEuLmKwvz3OgROXtrJ2UG2x8Ch2YZUxahh+s1F2HZ+wAceUNLkvy6zKCPVRkU++ZWQrdxsUeQXmcg4uoQ==} - '@types/chai@5.2.3': - resolution: {integrity: sha512-Mw558oeA9fFbv65/y4mHtXDs9bPnFMZAL/jxdPFUpOHHIXX91mcgEHbS5Lahr+pwZFR8A7GQleRWeI6cGFC2UA==} + '@protobufjs/base64@1.1.2': + resolution: {integrity: sha512-AZkcAA5vnN/v4PDqKyMR5lx7hZttPDgClv83E//FMNhR2TMcLUhfRUBHCmSl0oi9zMgDDqRUJkSxO3wm85+XLg==} - '@types/deep-eql@4.0.2': - resolution: {integrity: sha512-c9h9dVVMigMPc4bwTvC5dxqtqJZwQPePsWjPlpSOnojbor6pGqdk541lfA7AqFQr5pB1BRdq0juY9db81BwyFw==} + '@protobufjs/codegen@2.0.5': + resolution: {integrity: sha512-zgXFLzW3Ap33e6d0Wlj4MGIm6Ce8O89n/apUaGNB/jx+hw+ruWEp7EwGUshdLKVRCxZW12fp9r40E1mQrf/34g==} - '@types/estree@1.0.8': - resolution: {integrity: sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w==} + '@protobufjs/eventemitter@1.1.1': + resolution: {integrity: sha512-vW1GmwMZNnL+gMRaovlh9yZX74kc+TTU3FObkkurpMaRtBfLP3ldjS9KQWlwZgraRE0+dheEEoAxdzcJQ8eXZg==} - '@types/estree@1.0.9': - resolution: {integrity: sha512-GhdPgy1el4/ImP05X05Uw4cw2/M93BCUmnEvWZNStlCzEKME4Fkk+YpoA5OiHNQmoS7Cafb8Xa3Pya8m1Qrzeg==} + '@protobufjs/fetch@1.1.1': + resolution: {integrity: sha512-GpptLrs57adMSuHi3VNj0mAF8dwh36LMaYF6XyJ6JMWlVsc+t42tm1HSEDmOs3A8fC9yyeisgLhsTVQokOZ0zw==} - '@types/node@22.19.19': - resolution: {integrity: sha512-dyh/xO2Fh5bYrfWaaqGrRQQGkNdmYw6AmaAUvYeUMNTWQtvb796ikLdmTchRmOlOiIJ1TDXfWgVx1QkUlQ6Hew==} + '@protobufjs/float@1.0.2': + resolution: {integrity: sha512-Ddb+kVXlXst9d+R9PfTIxh1EdNkgoRe5tOX6t01f1lYWOvJnSPDBlG241QLzcyPdoNTsblLUdujGSE4RzrTZGQ==} + + '@protobufjs/path@1.1.2': + resolution: {integrity: sha512-6JOcJ5Tm08dOHAbdR3GrvP+yUUfkjG5ePsHYczMFLq3ZmMkAD98cDgcT2iA1lJ9NVwFd4tH/iSSoe44YWkltEA==} + + '@protobufjs/pool@1.1.0': + resolution: {integrity: sha512-0kELaGSIDBKvcgS4zkjz1PeddatrjYcmMWOlAuAPwAeccUrPHdUqo/J6LiymHHEiJT5NrF1UVwxY14f+fy4WQw==} + + '@protobufjs/utf8@1.1.1': + resolution: {integrity: sha512-oOAWABowe8EAbMyWKM0tYDKi8Yaox52D+HWZhAIJqQXbqe0xI/GV7FhLWqlEKreMkfDjshR5FKgi3mnle0h6Eg==} + + '@testing-library/dom@10.4.2': + resolution: {integrity: sha512-yzr2S9HyAIdhz2/6qHgbs665Q7PKVcDF05vsOlHPxG1mo36gKVesdYVeDLnXgfjJ03CrKRk08knc6+E/9m8v2Q==} + engines: {node: '>=18'} + + '@testing-library/user-event@14.6.8': + resolution: {integrity: sha512-vgSSqnANMnkcYR/vUNDbwS7aM3OErRLNUG8vCVVlj4oc6cKffUrccX7UAS5AmJQQNZTSibi9z5GvvRZ0aYgUCQ==} + engines: {node: '>=12', npm: '>=6'} + peerDependencies: + '@testing-library/dom': '>=7.21.4' + + '@types/aria-query@5.0.4': + resolution: {integrity: sha512-rfT93uj5s0PRL7EzccGMs3brplhcrghnDoV26NqKhCAS1hVo+WdNsPvE/yb6ilfr5hi2MEk6d5EWJTKdxg8jVw==} + + '@types/chai@5.2.3': + resolution: {integrity: sha512-Mw558oeA9fFbv65/y4mHtXDs9bPnFMZAL/jxdPFUpOHHIXX91mcgEHbS5Lahr+pwZFR8A7GQleRWeI6cGFC2UA==} + + '@types/deep-eql@4.0.2': + resolution: {integrity: sha512-c9h9dVVMigMPc4bwTvC5dxqtqJZwQPePsWjPlpSOnojbor6pGqdk541lfA7AqFQr5pB1BRdq0juY9db81BwyFw==} + + '@types/estree@1.0.9': + resolution: {integrity: sha512-GhdPgy1el4/ImP05X05Uw4cw2/M93BCUmnEvWZNStlCzEKME4Fkk+YpoA5OiHNQmoS7Cafb8Xa3Pya8m1Qrzeg==} + + '@types/node@22.19.19': + resolution: {integrity: sha512-dyh/xO2Fh5bYrfWaaqGrRQQGkNdmYw6AmaAUvYeUMNTWQtvb796ikLdmTchRmOlOiIJ1TDXfWgVx1QkUlQ6Hew==} '@types/set-cookie-parser@2.4.10': resolution: {integrity: sha512-GGmQVGpQWUe5qglJozEjZV/5dyxbOOZ0LHe/lqyWssB88Y4svNfst0uqBVscdDeIKl5Jy5+aPSvy7mI9tYRguw==} @@ -1029,11 +888,18 @@ packages: '@aws-sdk/credential-provider-web-identity': optional: true - '@vitest/expect@4.1.11': - resolution: {integrity: sha512-VX2x5vNJXET47KAFzwERI+KRMtTTCSWTfSMKsW7JsUsXV4psq++e3DvZpuTDOpHcxytiDs6p2nhVb2tVDiiUYw==} + '@vitest/browser-preview@5.0.3': + resolution: {integrity: sha512-jfnEBv5yDRPR1MeiTlTW5bcZoPsiI7MBhpZR8IJiSxVhdK6Pa4gPk3tLHTRxVHy24kkWxY2TnUbnszU3J1bQJw==} + peerDependencies: + vitest: 5.0.3 + + '@vitest/browser@5.0.3': + resolution: {integrity: sha512-7KHIkVA/gTRxQV9qPa9qLK+g5INZG6iRrlwXSFG5EKsOomW3UzzW7wsdt1Yp5PufzQ43lniuONlb4lUVCC2qSg==} + peerDependencies: + vitest: 5.0.3 - '@vitest/mocker@4.1.11': - resolution: {integrity: sha512-2XJVD55d1o5AZous5CCGKS74g/riOj9odEt2bQpCVZeblHyHdnMeFl4jl0XjU21stf4mbjUkew2eXQZt65g5CQ==} + '@vitest/mocker@5.0.3': + resolution: {integrity: sha512-T8sWAIbkSyAjkwTcaEc3Iu0o9A27X1/kdXrizhZkGuSKScRQtRzclfAMpOTcGdXCsqxeWlpGy3XjqaW8CpLORg==} peerDependencies: msw: ^2.4.9 vite: ^8.0.5 @@ -1043,24 +909,302 @@ packages: vite: optional: true - '@vitest/pretty-format@4.1.11': - resolution: {integrity: sha512-yiZzPbGTS9Sr/JpFl8zHrcIkAofNbFV6k21vIgQN/cY/oxZeXhJv5sc/MBJ5jFKWmWs+oJHw0UXLZjmf931+Vw==} + '@vitest/pretty-format@5.0.3': + resolution: {integrity: sha512-3hr5eV5z2ff0l2Q1vaKQbB4j3M3tElw8stDokEOTqXJB3Es5VrT8gBRIMY59gGuFp8Y61sWlWFK1aNUH5dHDYw==} + + '@vitest/snapshot@5.0.3': + resolution: {integrity: sha512-SK4YTg9jL+jwakHIZxfciuvyOQsbY15AueR3USGtsXzmy+3FbJgqjAbC+ac20mRLwfEDtO5sfTqK6bUCDTRVPA==} + + '@vitest/spy@5.0.3': + resolution: {integrity: sha512-XhFysQTB8AZ+P4gMi+Lpo99vg2AZi0qKpaB9yXQl37+CaMEAPO3iH/wGVnSyL5MPERiLezpqTVtrR6UZH5GCXg==} - '@vitest/runner@4.1.11': - resolution: {integrity: sha512-LztvUgdwMNJMIkj3hQnnxiC2Xy1zNxq928W/xhjCLaNCzqTZOudjwbQf6v9IntZGPw132i2Lq2rgTRZHD3JHNw==} + '@vitest/ui@5.0.3': + resolution: {integrity: sha512-9dfvqDBvSbHM6y9YVXxAypkl2z7mrWozK/JFo237qI0cj/3M/kzDKiVZqzi3TE54Ler7Jditud7OMBZCyUvRHA==} + peerDependencies: + vitest: 5.0.3 - '@vitest/snapshot@4.1.11': - resolution: {integrity: sha512-pN7ikn1ON7h8ee4gIAp4AzyK+zBtJPzVbqOgu5LCEh4VaJVbPQcgYQYJIMGQPXVeJJq1fnfazis7a5pFNPahog==} + '@vitest/utils@5.0.3': + resolution: {integrity: sha512-77fDTZv8eu1CzlIfgPU2yrl5MXSUaUAOvgdZEeVm3X3WIxUubIX2SforqS6176lT32HCF6uBVMYVqfHxw7o6eg==} - '@vitest/spy@4.1.11': - resolution: {integrity: sha512-apNa/prQy2qCeywhnixOHPRCgGNhvg7T4Dapfl1GahLp/R+uhBm5cPyFoNVyqsNd2h1nJxL6BqqdIjiABL60YA==} + '@voidzero-dev/vite-plus-core@1.1.0': + resolution: {integrity: sha512-Ai6HQP1zG0H3ZrmStEaMVKtDzW0xU3VgMzm3LFaXDQMEp8nAqvp0VGoU8HmpB9xv9vDSl+UxiSNOpuY9T714OQ==} + engines: {node: ^20.19.0 || ^22.18.0 || >=24.11.0} + peerDependencies: + '@arethetypeswrong/core': ^0.18.1 + '@types/node': ^20.19.0 || >=22.12.0 + '@typescript/native-preview': '*' + '@vitejs/devtools': ^0.7.1 + '@volar/typescript': ~2.4.0 + '@vue/language-core': ~3.2.0 || ~3.3.0 + esbuild: ^0.27.0 || ^0.28.0 + jiti: '>=1.21.0' + less: ^4.0.0 + publint: ^0.3.8 + sass: ^1.70.0 + sass-embedded: ^1.70.0 + stylus: '>=0.54.8' + sugarss: ^5.0.0 + terser: ^5.16.0 + tsx: ^4.8.1 + typescript: ^5.0.0 || ^6.0.0 || ^7.0.0 + unplugin-unused: '>=0.5.0' + unrun: '*' + vue-tsc: ~3.2.0 || ~3.3.0 + yaml: ^2.4.2 + peerDependenciesMeta: + '@arethetypeswrong/core': + optional: true + '@types/node': + optional: true + '@typescript/native-preview': + optional: true + '@vitejs/devtools': + optional: true + '@volar/typescript': + optional: true + '@vue/language-core': + optional: true + esbuild: + optional: true + jiti: + optional: true + less: + optional: true + publint: + optional: true + sass: + optional: true + sass-embedded: + optional: true + stylus: + optional: true + sugarss: + optional: true + terser: + optional: true + tsx: + optional: true + typescript: + optional: true + unplugin-unused: + optional: true + unrun: + optional: true + vue-tsc: + optional: true + yaml: + optional: true + + '@voidzero-dev/vite-plus-darwin-arm64@1.1.0': + resolution: {integrity: sha512-iWJdQZBRZWB6X1Aadk0Rrs/NJeP32Qb0OGeWnu1AYL0g6ZCj1yy3l9Oa+HasDyG97s+4UYo523LdBAAv3dlFwg==} + engines: {node: '>=20.0.0'} + cpu: [arm64] + os: [darwin] + + '@voidzero-dev/vite-plus-darwin-x64@1.1.0': + resolution: {integrity: sha512-IYSQ2KdigRMuVkKCC+e5uNliCTSHa8j3ajHsFG2cfXoiGHS1f4ausbMEzNYtn9Tcn0Xsyo7l0LN8lvB7MapJhA==} + engines: {node: '>=20.0.0'} + cpu: [x64] + os: [darwin] + + '@voidzero-dev/vite-plus-linux-arm64-gnu@1.1.0': + resolution: {integrity: sha512-PmrJG8Md5KCWi/akWJz5GRjei2QfRf48R4rPB9CfualzAAlpMOmaMGeV3eloWVAFye85uhdtzCmIAqN5OUVBJA==} + engines: {node: '>=20.0.0'} + cpu: [arm64] + os: [linux] + libc: [glibc] + + '@voidzero-dev/vite-plus-linux-arm64-musl@1.1.0': + resolution: {integrity: sha512-zFr57++m5U474XEMcFGV025TOv9V2I5d87yylcuI/SvxQwDE9P7T5iRl55vGzQOzzInSnQuru6ByNIfmGa+84A==} + engines: {node: '>=20.0.0'} + cpu: [arm64] + os: [linux] + libc: [musl] + + '@voidzero-dev/vite-plus-linux-x64-gnu@1.1.0': + resolution: {integrity: sha512-DXeRuTMylEbIVCQX+pSBM27lSPQwAKO6PDnGBJ6nFGhu37AmNeVl7cIChtcniXOuykbFduhSt3eIr/6CZHoEag==} + engines: {node: '>=20.0.0'} + cpu: [x64] + os: [linux] + libc: [glibc] + + '@voidzero-dev/vite-plus-linux-x64-musl@1.1.0': + resolution: {integrity: sha512-J7/Mb/kxtENUsL5Gh+8SSr/p054fSqU4oj0GEthr/2VEFSwZouOUigk6CzE7ANKFeTNJm40n8QCnjm7Oiy09pQ==} + engines: {node: '>=20.0.0'} + cpu: [x64] + os: [linux] + libc: [musl] + + '@voidzero-dev/vite-plus-win32-arm64-msvc@1.1.0': + resolution: {integrity: sha512-EXWz8Tt7skmI0fLMSq5zYcuHo6SCF0reG55qDB/NMvWVIAca6Dborq7D1HyKA8v6p39zOl8r07gR7yXuYv7eeQ==} + engines: {node: '>=20.0.0'} + cpu: [arm64] + os: [win32] + + '@voidzero-dev/vite-plus-win32-x64-msvc@1.1.0': + resolution: {integrity: sha512-K9k77Mj/TszFHKsZePKsyfsIPo8HbjKeJUxaSJ6YZKAmSh9QvDsMFe4i4/HALwyii/PR1EQ8N0iN1nf9cNimGQ==} + engines: {node: '>=20.0.0'} + cpu: [x64] + os: [win32] + + '@yuku-codegen/binding-android-arm64@0.9.5': + resolution: {integrity: sha512-jrOY5WM+AaAqkv51fHP1x28ifto4WgcZVzodLUyMU1jMWn5Sq+VciRdk/n8E0Ey5w4p4cWWE+m5GfXWOYh7Kzw==} + cpu: [arm64] + os: [android] + deprecated: yuku-codegen is pure JavaScript since 0.14 + + '@yuku-codegen/binding-darwin-arm64@0.9.5': + resolution: {integrity: sha512-4O4lkCQIzPZGjiNB1GORSI6hBECbiiSBS/APZXPvNKYH+nhY4uuqv03LNXA+SET3hoBjvr95P5rIhY8KQaQUBA==} + cpu: [arm64] + os: [darwin] + deprecated: yuku-codegen is pure JavaScript since 0.14 + + '@yuku-codegen/binding-darwin-x64@0.9.5': + resolution: {integrity: sha512-9EvoUO0SEhD6/d8VHdWuPerepPMSR1y84+UEgz8Un1Ope14Oe7xMvePpEQLTLovoIFZ8Zg3iZ8z8E11pZMqC3g==} + cpu: [x64] + os: [darwin] + deprecated: yuku-codegen is pure JavaScript since 0.14 + + '@yuku-codegen/binding-freebsd-x64@0.9.5': + resolution: {integrity: sha512-HqT78WwgHTmp8lwujoUa9CrIortX4DdpuiVC18ZPSGvuuJf4ylpIEI6QrQEM78Zwz3muhAWAOXZ5irdiYY+AyA==} + cpu: [x64] + os: [freebsd] + deprecated: yuku-codegen is pure JavaScript since 0.14 + + '@yuku-codegen/binding-linux-arm-gnu@0.9.5': + resolution: {integrity: sha512-QJXwIW6Ms3QIawbcBIedmrmXnfdpuAOjZJ/eAABq5XTzWvSxZ7lutu9W5yIHahlaTaFnBo7ikrVMD1szLTpPUw==} + cpu: [arm] + os: [linux] + libc: [glibc] + deprecated: yuku-codegen is pure JavaScript since 0.14 + + '@yuku-codegen/binding-linux-arm-musl@0.9.5': + resolution: {integrity: sha512-eHbFy3IHGb+IYarrYwAo0yWSEQV3eAmEn6VrsKA6I1Wy1YPJxWqSJlV69JhqsHtJuJlljUIF3ex0y46yaKIu9w==} + cpu: [arm] + os: [linux] + libc: [musl] + deprecated: yuku-codegen is pure JavaScript since 0.14 + + '@yuku-codegen/binding-linux-arm64-gnu@0.9.5': + resolution: {integrity: sha512-ByoJMbySTaDhjAXSu8q6Lh7HKg3YoesXpcT72aYk0Aiw4PCznmY4ybpLTq0RCvp0RIPhFm/6yECFiGBwyCC1nw==} + cpu: [arm64] + os: [linux] + libc: [glibc] + deprecated: yuku-codegen is pure JavaScript since 0.14 + + '@yuku-codegen/binding-linux-arm64-musl@0.9.5': + resolution: {integrity: sha512-gD9vfXIoBw1toSxhO9rgmSu/FEfy3PMznJAxVjIuH8DrWEiDKXmJO0pJKfj1Ltbe/TmtWVZR+TjISqeSIGQzMg==} + cpu: [arm64] + os: [linux] + libc: [musl] + deprecated: yuku-codegen is pure JavaScript since 0.14 + + '@yuku-codegen/binding-linux-x64-gnu@0.9.5': + resolution: {integrity: sha512-BARvdnvqMGjOr5Iel2JH+9H5vAIE0R6H0Z2fsT02xrvmI/1ZXNB8lkuX+ZGevPhZb3zJ9WeC/R8JZstrsUOK5g==} + cpu: [x64] + os: [linux] + libc: [glibc] + deprecated: yuku-codegen is pure JavaScript since 0.14 + + '@yuku-codegen/binding-linux-x64-musl@0.9.5': + resolution: {integrity: sha512-x8flcevS1fbb7ESrmpOY/pON4eInSaE/7Ktjqx2udOE2W33BNSZmJuwpyUgo54AoHNqrcaM7szqydyJn5fNvew==} + cpu: [x64] + os: [linux] + libc: [musl] + deprecated: yuku-codegen is pure JavaScript since 0.14 + + '@yuku-codegen/binding-win32-arm64@0.9.5': + resolution: {integrity: sha512-KOL/rBatWqH4ZpCNoF8ZtSNdOJbAxBJLmk/VeRciepGROPTbPum1A9t67GpFgOU7qrkUWlPJdJ+KHKbvbmOt+w==} + cpu: [arm64] + os: [win32] + deprecated: yuku-codegen is pure JavaScript since 0.14 + + '@yuku-codegen/binding-win32-x64@0.9.5': + resolution: {integrity: sha512-FxENahEjWSan59Syh/us/Kf4wNq8qrJLFJ3R2N4Oiwtb6yNdz/rWLNTIoNSssnsp7IoWJdUP6o/Z8ppg7lXMcg==} + cpu: [x64] + os: [win32] + deprecated: yuku-codegen is pure JavaScript since 0.14 + + '@yuku-parser/binding-android-arm64@0.9.5': + resolution: {integrity: sha512-A2JCFCSHfnficqYEw4Iujpx7XrkMM3UfFcgJFmYpZSo9zM4nyhmdIIE0FogSduuU60lhM0/UcfuUBXIVNBMlGQ==} + cpu: [arm64] + os: [android] + deprecated: yuku-parser runs on yuku-core since 0.14 + + '@yuku-parser/binding-darwin-arm64@0.9.5': + resolution: {integrity: sha512-3PiyU+Eare4YuKaQ22N98/yAiROPY5o/NQJHraICzDvk4pgS+m+bgLKOvkGBRn33OnV95Vdv1Mn38b+MQHpULQ==} + cpu: [arm64] + os: [darwin] + deprecated: yuku-parser runs on yuku-core since 0.14 + + '@yuku-parser/binding-darwin-x64@0.9.5': + resolution: {integrity: sha512-blFMAFI7AInI83XaiOF8cIeiRM46Nz9EfpZtZPRLbKxSA9aAr5v8aHYpmfN6NoyXxAv/10pwS4gsAuc1W6fy5Q==} + cpu: [x64] + os: [darwin] + deprecated: yuku-parser runs on yuku-core since 0.14 + + '@yuku-parser/binding-freebsd-x64@0.9.5': + resolution: {integrity: sha512-TsNuL4qsZdO0tHp5GW43Y5fHeLPpPHA675wdcPNTcdq2ZO5AvsQWFFoiDg8CH4oBktfsQ9tdvKhjLnLYwKvp4g==} + cpu: [x64] + os: [freebsd] + deprecated: yuku-parser runs on yuku-core since 0.14 + + '@yuku-parser/binding-linux-arm-gnu@0.9.5': + resolution: {integrity: sha512-b0afYK5gHeV8RdmOcqAlgM8ONsye4cax4DMnIBaoJyPMd1UTGvTbpkqQcPVdhmHlcfcGVGMV1laeVovlr/dscw==} + cpu: [arm] + os: [linux] + libc: [glibc] + deprecated: yuku-parser runs on yuku-core since 0.14 + + '@yuku-parser/binding-linux-arm-musl@0.9.5': + resolution: {integrity: sha512-fD3lKzl+r6j6n8DwiMY53qnh5DLqD8KJjCp+NbVud54Nnh3Q9Wprqss3YzyMHP3NSJk663Wlg1E1X5qOuFVFig==} + cpu: [arm] + os: [linux] + libc: [musl] + deprecated: yuku-parser runs on yuku-core since 0.14 + + '@yuku-parser/binding-linux-arm64-gnu@0.9.5': + resolution: {integrity: sha512-aRU/aCphV1MWCl/lvI6NX8LgucHQ68Fx+vz7NBb/5MEr2EuzCxiPOQqeFcMdWh+2AED/p0AvAREch0c+cSnlhA==} + cpu: [arm64] + os: [linux] + libc: [glibc] + deprecated: yuku-parser runs on yuku-core since 0.14 + + '@yuku-parser/binding-linux-arm64-musl@0.9.5': + resolution: {integrity: sha512-5+Guro0l8H473YXlEjVNBRLN/IbPbJdnQh1zo0OLt49xxnON+XMJRRaEyTOTfkn9QSRg0+BhEKGR4W9Gu2uZJQ==} + cpu: [arm64] + os: [linux] + libc: [musl] + deprecated: yuku-parser runs on yuku-core since 0.14 + + '@yuku-parser/binding-linux-x64-gnu@0.9.5': + resolution: {integrity: sha512-pwwSyV9q+GlvzSXlsMZBMlgk22L5bud714/vqZCdeUTivzeUVltQzUaf3IQXOGXdpc59JYTeuMCtbGquaAUoCA==} + cpu: [x64] + os: [linux] + libc: [glibc] + deprecated: yuku-parser runs on yuku-core since 0.14 + + '@yuku-parser/binding-linux-x64-musl@0.9.5': + resolution: {integrity: sha512-z18j6JN3lBHH8vzN7gG1M8fI0nlgItSfnC9PfbmUbt97iHViKfw2iA2G9fGwRMe5LyPRZBrejIlapbygPbpdaw==} + cpu: [x64] + os: [linux] + libc: [musl] + deprecated: yuku-parser runs on yuku-core since 0.14 + + '@yuku-parser/binding-win32-arm64@0.9.5': + resolution: {integrity: sha512-s6Gwttb1dQvtPX6Bgkw+UPC9IO3UBDXc6Zezog8MMgvu3UfJBBB9TQqKBX/2quu0Eyh+lLSAyNlIyyecaagUPg==} + cpu: [arm64] + os: [win32] + deprecated: yuku-parser runs on yuku-core since 0.14 - '@vitest/utils@4.1.11': - resolution: {integrity: sha512-zTCVGpyFsGWBhllOyKlTw/vnr6D9qxsfSDyfbyZmTyjHw5N/VuvzHpHoQjm2ZJzn4RJgx5w4r7V0er69CmLgPQ==} + '@yuku-parser/binding-win32-x64@0.9.5': + resolution: {integrity: sha512-FrERt9YWatY3bJfSUdi4YWY+6iQcyo3MzCC821BxlWM5TFZEHijnpz/bknR79VHlXY/9CvXz8ZlaAGPsTtN3nw==} + cpu: [x64] + os: [win32] + deprecated: yuku-parser runs on yuku-core since 0.14 - accepts@2.0.0: - resolution: {integrity: sha512-5cvg6CtKwfgdmVqY1WIiXKc3Q1bkRqGLi+2W/6ao+6Y7gu/RCwRuAhGEzh5B4KlszSuTLgZYuqFqo5bImjNKng==} - engines: {node: '>= 0.6'} + '@yuku-toolchain/types@0.9.5': + resolution: {integrity: sha512-KiuLNNgX9uNealaWAR+G3/cMXnRk9x4TY2EkYe/KIag+UPdwiA0RRf1hr1WAxzTP8KGzCTkqUdLPvqh32sEO3w==} acorn-import-attributes@1.9.5: resolution: {integrity: sha512-n02Vykv5uA3eHGM/Z2dQrcD56kL8TyDb2p1+0P83PClMnC/nc+anbQRhIOWnSq4Ke/KvDPrY3C9hDtC/A3eHnQ==} @@ -1086,11 +1230,12 @@ packages: resolution: {integrity: sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==} engines: {node: '>=8'} - any-promise@1.3.0: - resolution: {integrity: sha512-7UvmKalWRt1wgjL1RrGxoSJW/0QZFIegpeGvZG9kjp8vrRu55XTHbwnqq2GpXm9uLbcuhxm3IqX9OB4MZR1b2A==} + ansi-styles@5.2.0: + resolution: {integrity: sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==} + engines: {node: '>=10'} - argparse@2.0.1: - resolution: {integrity: sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==} + aria-query@5.3.0: + resolution: {integrity: sha512-b0P0sZPKtyu8HkeRAfCq0IfURZK+SuwMjY1UXGBU27wpAiTwQAIlq56IbIO+ytk/JjS1fMR14ee5WBBfKi5J6A==} assertion-error@2.0.1: resolution: {integrity: sha512-Izi8RQcffqCeNVgFigKli1ssklIbpHnCYc6AknXGYoB6grJqyeby7jv12JUQgmTAnIDnbck1uxksT4dzN3PWBA==} @@ -1100,20 +1245,9 @@ packages: resolution: {integrity: sha512-LElXdjswlqjWrPpJFg1Fx4wpkOCxj1TDHlSV4PlaRxHGWko024xICaa97ZkMfs6DRKlCguiAI+rbXv5GWwXIkg==} hasBin: true - balanced-match@4.0.4: - resolution: {integrity: sha512-BLrgEcRTwX2o6gGxGOCNyMvGSp35YofuYzw9h1IMTRmKqttAZZVU67bdb9Pr2vUHA8+j3i2tJfjO6C6+4myGTA==} - engines: {node: 18 || 20 || >=22} - - body-parser@2.3.0: - resolution: {integrity: sha512-2cGmJupaNgg+QUwVLAucDuWuoMZ6EX9iHDRswZ5lsNYEmwPaRknMPCLZz07yTzVq/83p4o/wzbDZbBrTvGGTIw==} - engines: {node: '>=18'} - - brace-expansion@5.0.12: - resolution: {integrity: sha512-YovQ3rzhaLMIrDjNDMkNS01tea93qhEhG5xy8f6+R0l+dw3Ki+5sCoIoI942iuLZTHWogWktgwVDhU09iNEimQ==} - engines: {node: 20 || >=22} - braintrust@file:../../../js: resolution: {directory: ../../../js, type: directory} + engines: {node: '>=22.18.0'} hasBin: true peerDependencies: '@types/react': '>=18' @@ -1126,47 +1260,13 @@ packages: resolution: {integrity: sha512-sWm8iPbqvL9+5SiYxXH73UOkyEbGQg7kyHQmReF89WJHQJw2eV4P/yZ0E+b71cczJ4pPobVhXxgQcmfSTgGHxQ==} engines: {node: '>= 0.10.x'} - bundle-require@5.1.0: - resolution: {integrity: sha512-3WrrOuZiyaaZPWiEt4G3+IffISVC9HYlWueJEBWED4ZH4aIAC2PnkdnuRrR94M+w6yGWn4AglWtJtBI8YqvgoA==} - engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0} - peerDependencies: - esbuild: '>=0.18' - - bytes@3.1.2: - resolution: {integrity: sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg==} - engines: {node: '>= 0.8'} - - cac@6.7.14: - resolution: {integrity: sha512-b6Ilus+c3RrdDk+JhLKUAQfzzgLEPy6wcXqS7f/xe1EETvsDP6GORG7SFuOs6cID5YkqchW/LXZbX5bc8j7ZcQ==} - engines: {node: '>=8'} - - call-bind-apply-helpers@1.0.2: - resolution: {integrity: sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ==} - engines: {node: '>= 0.4'} - - call-bound@1.0.4: - resolution: {integrity: sha512-+ys997U96po4Kx/ABpBCqhA9EuxJaQWDQg7295H4hBphv3IZg0boBKuwYpt4YXp6MZ5AmZQnU/tyMTlRpaSejg==} - engines: {node: '>= 0.4'} - chai@6.2.2: resolution: {integrity: sha512-NUPRluOfOiTKBKvWPtSD4PhFvWCqOi0BGStNWs57X9js7XGTprSmFoz5F0tWhR4WPjNeR9jXqdC7/UpSJTnlRg==} engines: {node: '>=18'} - chokidar@4.0.3: - resolution: {integrity: sha512-Qgzu8kfBvo+cA4962jnP1KkS6Dop5NS6g7R5LFYJr4b8Ub94PPQXUksCw9PvXoeXPRRddRNC5C1JQUR2SMGtnA==} - engines: {node: '>= 14.16.0'} - cjs-module-lexer@2.2.0: resolution: {integrity: sha512-4bHTS2YuzUvtoLjdy+98ykbNB5jS0+07EvFNXerqZQJ89F7DI6ET7OQo/HJuW6K0aVsKA9hj9/RVb2kQVOrPDQ==} - cli-progress@3.12.0: - resolution: {integrity: sha512-tRkV3HJ1ASwm19THiiLIXLO7Im7wlTuKnvkYaTkyoAPefqjNg7W7DHKUlGRxy9vxDvbyCYQkQozvptuMkGCg8A==} - engines: {node: '>=4'} - - cli-table3@0.6.5: - resolution: {integrity: sha512-+W/5efTR7y5HRD7gACw9yQjqMVvEMLBHmboM/kPWam+H+Hmyrgjh6YncVKK122YZkXrLudzTuAukUw9FnMf7IQ==} - engines: {node: 10.* || >= 12.*} - cli-width@4.1.0: resolution: {integrity: sha512-ouuZd4/dm2Sw5Gmqy6bGyNNNe1qt9RpmxveLSO7KcgsTnU7RXfsw+/bukWGo1abgBiMAic068rclZsO4IWmmxQ==} engines: {node: '>= 12'} @@ -1182,48 +1282,13 @@ packages: color-name@1.1.4: resolution: {integrity: sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==} - commander@4.1.1: - resolution: {integrity: sha512-NOKm8xhkzAjzFx8B2v5OAHT+u5pRQc2UCa2Vq9jYL/31o2wi9mxBA7LIFs3sV5VSC49z6pEhfbMULvShKj26WA==} - engines: {node: '>= 6'} - - confbox@0.1.8: - resolution: {integrity: sha512-RMtmw0iFkeR4YV+fUOSucriAQNb9g8zFR52MWCtl+cCZOFRNL6zeB395vPzFhEjjn4fMxXudmELnl/KF/WrK6w==} - - consola@3.4.2: - resolution: {integrity: sha512-5IKcdX0nnYavi6G7TtOhwkYzyjfJlatbjMjuLSfE2kYT5pMDOilZ4OvMhi637CcDICTmz3wARPoyhqyX1Y+XvA==} - engines: {node: ^14.18.0 || >=16.10.0} - - content-disposition@1.1.0: - resolution: {integrity: sha512-5jRCH9Z/+DRP7rkvY83B+yGIGX96OYdJmzngqnw2SBSxqCFPd0w2km3s5iawpGX8krnwSGmF0FW5Nhr0Hfai3g==} - engines: {node: '>=18'} - - content-type@1.0.5: - resolution: {integrity: sha512-nTjqfcBFEipKdXCv4YDQWCfmcLZKm81ldF0pAopTvyrFGVbcR6P/VAAd5G7N+0tTr8QqiU0tFadD6FK4NtJwOA==} - engines: {node: '>= 0.6'} - - content-type@2.0.0: - resolution: {integrity: sha512-j/O/d7GcZCyNl7/hwZAb606rzqkyvaDctLmckbxLzHvFBzTJHuGEdodATcP3yIRoDrLHkIATJuvzbFlp/ki2cQ==} - engines: {node: '>=18'} - convert-source-map@2.0.0: resolution: {integrity: sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==} - cookie-signature@1.2.2: - resolution: {integrity: sha512-D76uU73ulSXrD1UXF4KE2TMxVVwhsnCgfAyTg9k8P6KGZjlXKrOLe4dJQKI3Bxi5wjesZoFXJWElNWBjPZMbhg==} - engines: {node: '>=6.6.0'} - - cookie@0.7.2: - resolution: {integrity: sha512-yki5XnKuf750l50uGTllt6kKILY4nQ1eNIQatoXEByZ5dWgnKqbnqmTrBE5B4N7lrMJKQ2ytWMiTO2o0v6Ew/w==} - engines: {node: '>= 0.6'} - cookie@1.1.1: resolution: {integrity: sha512-ei8Aos7ja0weRpFzJnEA9UHJ/7XQmqglbRwnf2ATjcB9Wq874VKH9kfjjirM6UhU2/E5fFYadylyhFldcqSidQ==} engines: {node: '>=18'} - cors@2.8.6: - resolution: {integrity: sha512-tJtZBBHA6vjIAaF6EnIaq6laBBP9aq/Y3ouVJjEfoHbRBcHBAHYcMh/w8LDrk2PvIMMq8gmopa5D4V8RmbrxGw==} - engines: {node: '>= 0.10'} - debug@4.4.3: resolution: {integrity: sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==} engines: {node: '>=6.0'} @@ -1233,39 +1298,27 @@ packages: supports-color: optional: true - depd@2.0.0: - resolution: {integrity: sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw==} - engines: {node: '>= 0.8'} + dequal@2.0.3: + resolution: {integrity: sha512-0je+qPKHEMohvfRTCEo3CrPG6cAzAYgmzKyxRiYSSDkS6eGJdyVJm7WaYA5ECaAD9wLB2T4EEeymA5aFVcYXCA==} + engines: {node: '>=6'} detect-libc@2.1.2: resolution: {integrity: sha512-Btj2BOOO83o3WyH59e8MgXsxEQVcarkUOpEYrubB0urwnN10yQ364rsiByU11nZlqWYZm05i/of7io4mzihBtQ==} engines: {node: '>=8'} + dom-accessibility-api@0.5.16: + resolution: {integrity: sha512-X7BJ2yElsnOJ30pZF4uIIDfBEVgF4XEBxL9Bxhy6dnrm5hkzqmsWHGTiHqRiITNhMyFLyAiWndIJP7Z1NTteDg==} + dotenv@16.6.1: resolution: {integrity: sha512-uBq4egWHTcTt33a72vpSG0z3HnPuIl6NqYcTrKEg2azoEyl2hpW0zqlxysq2pK9HlDIHyHyakeYaYnSAwd8bow==} engines: {node: '>=12'} - dunder-proto@1.0.1: - resolution: {integrity: sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==} - engines: {node: '>= 0.4'} - - ee-first@1.1.1: - resolution: {integrity: sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow==} - emoji-regex@8.0.0: resolution: {integrity: sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==} - encodeurl@2.0.0: - resolution: {integrity: sha512-Q0n9HRi4m6JuGIV1eFlmvJB7ZEVxu93IrMyiMsGC0lrMJMWzRgx6WGquyfQgZVb31vhGgXnfmPNNXmxnOkRBrg==} - engines: {node: '>= 0.8'} - error@7.0.2: resolution: {integrity: sha512-UtVv4l5MhijsYUxPJo4390gzfZvAnTHreNnDjnTZaKIiZ/SemXxAhBkYSKtWa5RtBXbLP8tMgn/n0RUa/H7jXw==} - es-define-property@1.0.1: - resolution: {integrity: sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g==} - engines: {node: '>= 0.4'} - es-errors@1.3.0: resolution: {integrity: sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==} engines: {node: '>= 0.4'} @@ -1273,15 +1326,6 @@ packages: es-module-lexer@2.3.2: resolution: {integrity: sha512-poHGpORABojJJucnV9KbOavETW8lBVnphkW77ER5/BQ5Fz7oXSoCNek7IH3vR5nRjdsEz926ibFYX8KtLQmdyw==} - es-object-atoms@1.1.2: - resolution: {integrity: sha512-HWcBoN6NileqtSydK2FqHbS/LoDd2pqrnQHLyJzBj4kOp/ky2MWMN694xOfkK8/SnUsW2DH7EfyVlydKCsm1Zw==} - engines: {node: '>= 0.4'} - - esbuild@0.27.7: - resolution: {integrity: sha512-IxpibTjyVnmrIQo5aqNpCgoACA/dTKLTlhMHihVHhdkxKyPO1uBBthumT0rdHmcsk9uMonIWS0m4FljWzILh3w==} - engines: {node: '>=18'} - hasBin: true - esbuild@0.28.1: resolution: {integrity: sha512-HrJrvZv5ayxBzPfwphOoNzkzOIIlifzk0KJrGK2c8R4+LKpMtpYLQeUdjnwjWv/LZlkH2laZk+4w78pi99D4Vw==} engines: {node: '>=18'} @@ -1291,9 +1335,6 @@ packages: resolution: {integrity: sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==} engines: {node: '>=6'} - escape-html@1.0.3: - resolution: {integrity: sha512-NiSupZ4OeuGwr68lGIeym/ksIZMJodUGOSCZ/FSnTxcrekbvqrgdUxlJOMpijaKZVjAJrWrGs/6Jy8OMuyj9ow==} - esquery@1.7.0: resolution: {integrity: sha512-Ap6G0WQwcU/LHsvLwON1fAQX9Zp0A2Y6Y/cJBl9r/JbW90Zyg4/zbG6zzKa2OTALELarYHmKu0GhpM5EO+7T0g==} engines: {node: '>=0.10'} @@ -1305,10 +1346,6 @@ packages: estree-walker@3.0.3: resolution: {integrity: sha512-7RUKfXgSMMkzt6ZuXmqapOurLGPPfgj6l9uRZ7lRGolvk0y2yocc35LdcxKC5PQZdn2DMqioAQ2NoWcrTKmm6g==} - etag@1.8.1: - resolution: {integrity: sha512-aIL5Fx7mawVa300al2BnEE4iNvo1qETxLrPI/o05L7z6go7fCw1J6EQmbK4FmJ2AS7kgVF/KEZWufBfdClMcPg==} - engines: {node: '>= 0.6'} - eventsource-parser@1.1.2: resolution: {integrity: sha512-v0eOBUbiaFojBu2s2NPBfYUoRR9GjcDNvCXVaqEf5vVfpIAh9f8RCo4vXTP8c63QRKCFwoLpMpTdPwwhEKVgzA==} engines: {node: '>=14.18'} @@ -1317,10 +1354,6 @@ packages: resolution: {integrity: sha512-KfYbmpRm0VbLjEvVa9yGwCi9GI34xvi7A/HXYWQO65CSD2u3MczUJSuwXKFIxlGsgBQizV9q5J9NHj4VG0n+pA==} engines: {node: '>=12.0.0'} - express@5.2.1: - resolution: {integrity: sha512-hIS4idWWai69NezIdRt2xFVofaF4j+6INOpJlVOLDO8zXGpUVEVzIYk12UUi2JzjEzWL3IOAxcTubgz9Po0yXw==} - engines: {node: '>= 18'} - fast-deep-equal@3.1.3: resolution: {integrity: sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==} @@ -1330,8 +1363,8 @@ packages: fast-string-width@3.0.2: resolution: {integrity: sha512-gX8LrtNEI5hq8DVUfRQMbr5lpaS4nMIWV+7XEbXk2b8kiQIizgnlr12B4dA3ZEx3308ze0O4Q1R+cHts8kyUJg==} - fast-uri@3.1.8: - resolution: {integrity: sha512-GZMtZUTNRpOVIECoXwLNZS5xUGE+mVNbTB8h/7Rwh2TFWcBQiPzTgyZi05BF9UMZKkLJv8XBRJTlU7zg8+ZfMg==} + fast-uri@3.1.9: + resolution: {integrity: sha512-hS/0Wt3Ya043oKXs0sIxrMpKQITXhMLIrVAKKXi4gozGC9v7YRWVnXIZzjNPTO02M9BdxLCWqK1yRXgNziVyJQ==} fast-wrap-ansi@0.2.2: resolution: {integrity: sha512-7F2Fl+TjRSenLqlU3UjSH0iyqopqoZIu7eZVpEirP2g1GtWa2G/ecEmBdgz31+Mxr+ELclgg6sokpSFIQiZ02Q==} @@ -1345,20 +1378,11 @@ packages: picomatch: optional: true - finalhandler@2.1.1: - resolution: {integrity: sha512-S8KoZgRZN+a5rNwqTxlZZePjT/4cnm0ROV70LedRHZ0p8u9fRID0hJUZQpkKLzro8LfmC8sx23bY6tVNxv8pQA==} - engines: {node: '>= 18.0.0'} - - fix-dts-default-cjs-exports@1.0.1: - resolution: {integrity: sha512-pVIECanWFC61Hzl2+oOCtoJ3F17kglZC/6N94eRWycFgBH35hHx0Li604ZIzhseh97mf2p0cv7vVrOZGoqhlEg==} + fflate@0.8.3: + resolution: {integrity: sha512-tbZNuJrLwGUp3zshBtdy4W+ORxZuIh8a5ilyIEQDC5rY1f3U20JMry0Ll3WBzU58EZKsEuJFXhb5gwv8CsPvgA==} - forwarded@0.2.0: - resolution: {integrity: sha512-buRG0fpBtRHSTCOASe6hD258tEubFoRLb4ZNA6NxMVHNw2gOcwHo9wyablzMzOA5z9xA9L1KNjk/Nt6MT9aYow==} - engines: {node: '>= 0.6'} - - fresh@2.0.0: - resolution: {integrity: sha512-Rx/WycZ60HOaqLKAi6cHRKKI7zxWbJ31MhntmtwMoaTeF7XFH9hhBp8vITaMidfljRQ6eYWCKkaTK+ykVJHP2A==} - engines: {node: '>= 0.8'} + flatted@3.4.4: + resolution: {integrity: sha512-5+ybhBZANEJxaH3X5evAFatUxLfEHSr7n6kYJ+1Qd0mUqr4eu9gIf6GDbWHf8RJijHrjjO8G+la14SlL2SeS1Q==} fsevents@2.3.3: resolution: {integrity: sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==} @@ -1372,26 +1396,10 @@ packages: resolution: {integrity: sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==} engines: {node: 6.* || 8.* || >= 10.*} - get-intrinsic@1.3.0: - resolution: {integrity: sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ==} - engines: {node: '>= 0.4'} - - get-proto@1.0.1: - resolution: {integrity: sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g==} - engines: {node: '>= 0.4'} - - gopd@1.2.0: - resolution: {integrity: sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg==} - engines: {node: '>= 0.4'} - graphql@16.14.0: resolution: {integrity: sha512-BBvQ/406p+4CZbTpCbVPSxfzrZrbnuWSP1ELYgyS6B+hNeKzgrdB4JczCa5VZUBQrDa9hUngm0KnexY6pJRN5Q==} engines: {node: ^12.22.0 || ^14.16.0 || ^16.0.0 || >=17.0.0} - has-symbols@1.1.0: - resolution: {integrity: sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ==} - engines: {node: '>= 0.4'} - hasown@2.0.3: resolution: {integrity: sha512-ej4AhfhfL2Q2zpMmLo7U1Uv9+PyhIZpgQLGT1F9miIGmiCJIoCgSmczFdrc97mWT4kVY72KA+WnnhJ5pghSvSg==} engines: {node: '>= 0.4'} @@ -1404,21 +1412,6 @@ packages: engines: {node: '>= 0.10.x'} hasBin: true - http-errors@2.0.1: - resolution: {integrity: sha512-4FbRdAX+bSdmo4AUFuS0WNiPz8NgFt+r8ThgNWmlrjQjt1Q7ZR9+zTlce2859x4KSXrwIsaeTqDoKQmtP8pLmQ==} - engines: {node: '>= 0.8'} - - iconv-lite@0.7.2: - resolution: {integrity: sha512-im9DjEDQ55s9fL4EYzOAv0yMqmMBSZp6G0VvFyTMPKWxiSBHUj9NW/qqLmXUwXrrM7AvqSlTCfvqRb0cM8yYqw==} - engines: {node: '>=0.10.0'} - - inherits@2.0.4: - resolution: {integrity: sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==} - - ipaddr.js@1.9.1: - resolution: {integrity: sha512-0KI/607xoxSToH7GjN1FfSbLoU0+btTicjsQSWQlh/hZykN8KpmMf7uYwPW3R+akZ6R/w18ZlXSHBYXiYUPO3g==} - engines: {node: '>= 0.10'} - is-core-module@2.16.2: resolution: {integrity: sha512-evOr8xfXKxE6qSR0hSXL2r3sd7ALj8+7jQEUvPYcm5sgZFdJ+AYzT6yNmJenvIYQBgIGwfwz08sL8zoL7yq2BA==} engines: {node: '>= 0.4'} @@ -1430,16 +1423,12 @@ packages: is-node-process@1.2.0: resolution: {integrity: sha512-Vg4o6/fqPxIjtxgUH5QLJhwZ7gW5diGCVlXpuUfELC62CuxM1iHcRe51f2W1FDy04Ai4KJkagKjx3XaqyfRKXw==} - is-promise@4.0.0: - resolution: {integrity: sha512-hvpoI6korhJMnej285dSg6nu1+e6uxs7zG3BYAm5byqDsgJNWwxzM6z6iZiAgQR4TJ30JmBTOwqZUw3WlyH3AQ==} - jaeger-client@3.19.0: resolution: {integrity: sha512-M0c7cKHmdyEUtjemnJyx/y9uX16XHocL46yQvyqDlPdvAcwPDbHrIbKjQdBqtiE4apQ/9dmr+ZLJYYPGnurgpw==} engines: {node: '>=10'} - joycon@3.1.1: - resolution: {integrity: sha512-34wB/Y7MW7bzjKRjUKTa46I2Z7eV62Rkhva+KkopW7Qvv/OSWBqvkSY7vusOPrNuZcUG3tApvdVgNB8POj3SPw==} - engines: {node: '>=10'} + js-tokens@4.0.0: + resolution: {integrity: sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==} json-schema-traverse@1.0.0: resolution: {integrity: sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==} @@ -1518,17 +1507,6 @@ packages: resolution: {integrity: sha512-WkUDrojuJs0xkgGf2udWxa3yGBRxPtxUkB79i6aCZLRgc7PM8fZe9TosfPDcvEpQZbuFASnHYmRLBLUbmLOIIA==} engines: {node: '>= 12.0.0'} - lilconfig@3.1.3: - resolution: {integrity: sha512-/vlFKAoH5Cgt3Ie+JLhRbwOsCQePABiU3tJ1egGvyQ+33R/vcwM2Zl2QR/LzjsBeItPt3oSVXapn+m4nQDvpzw==} - engines: {node: '>=14'} - - lines-and-columns@1.2.4: - resolution: {integrity: sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg==} - - load-tsconfig@0.2.5: - resolution: {integrity: sha512-IXO6OCs9yg8tMKzfPZ1YmheJbZCiEsnBdcB03l0OcfK9prKnJb96siuHCr5Fl37/yo9DnKU+TLpxzTUspw9shg==} - engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0} - lodash.camelcase@4.3.0: resolution: {integrity: sha512-TwuEnCnxbc3rAvhf/LbG7tJUDzhqXyFnv3dtzLOPgCG/hODL7WFnsbwktkD7yUV0RrreP/l1PALq/YSg6VvjlA==} @@ -1542,46 +1520,27 @@ packages: long@5.3.2: resolution: {integrity: sha512-mNAgZ1GmyNhD7AuqnTG3/VQ26o760+ZYBPKjPvugO8+nLbYfX6TVpJPseBvopbdY+qpZ/lKUnmEc1LeZYS3QAA==} - magic-string@0.30.21: - resolution: {integrity: sha512-vd2F4YUyEXKGcLHoq+TEyCjxueSeHnFxyyjNp80yg0XV4vUhnDer/lvvlqM/arB5bXQN5K2/3oinyCRyx8T2CQ==} - - math-intrinsics@1.1.0: - resolution: {integrity: sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==} - engines: {node: '>= 0.4'} - - media-typer@1.1.0: - resolution: {integrity: sha512-aisnrDP4GNe06UcKFnV5bfMNPBUw4jsLGaWwWfnH3v02GnBuXX2MCVn5RbrWo0j3pczUilYblq7fQ7Nw2t5XKw==} - engines: {node: '>= 0.8'} + lz-string@1.5.0: + resolution: {integrity: sha512-h5bgJWpxJNswbU7qCrV0tIKQCaS3blPDrqKWx+QxzuzL1zGUzij9XCWLrSLsJPu5t+eWA/ycetzYAO5IOMcWAQ==} + hasBin: true - merge-descriptors@2.0.0: - resolution: {integrity: sha512-Snk314V5ayFLhp3fkUREub6WtjBfPdCPY1Ln8/8munuLuiYhsABgBVWsozAG+MWMbVEvcdcpbi9R7ww22l9Q3g==} - engines: {node: '>=18'} + magic-string@1.4.3: + resolution: {integrity: sha512-z12OxmaPGE0F4xlpGdjuAUckipLpQlTAuAmyGhNoD7ODpYUzvDfvtxUbjM/jwznyP178oju/KDdyi220wNJ0RQ==} meriyah@6.1.4: resolution: {integrity: sha512-Sz8FzjzI0kN13GK/6MVEsVzMZEPvOhnmmI1lU5+/1cGOiK3QUahntrNNtdVeihrO7t9JpoH75iMNXg6R6uWflQ==} engines: {node: '>=18.0.0'} - mime-db@1.54.0: - resolution: {integrity: sha512-aU5EJuIN2WDemCcAp2vFBfp/m4EAhWJnUNSSw0ixs7/kXbd6Pg64EmwJkNdFhB8aWt1sH2CTXrLxo/iAGV3oPQ==} - engines: {node: '>= 0.6'} - - mime-types@3.0.2: - resolution: {integrity: sha512-Lbgzdk0h4juoQ9fCKXW4by0UJqj+nOOrI9MJ1sSj4nI8aI2eo1qmvQEie4VD1glsS250n15LsWsYtCugiStS5A==} - engines: {node: '>=18'} - - minimatch@10.2.5: - resolution: {integrity: sha512-MULkVLfKGYDFYejP07QOurDLLQpcjk7Fw+7jXS2R2czRQzR56yHRveU5NDJEOviH+hETZKSkIk5c+T23GjFUMg==} - engines: {node: 18 || 20 || >=22} - minimist@1.2.8: resolution: {integrity: sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA==} - mlly@1.8.2: - resolution: {integrity: sha512-d+ObxMQFmbt10sretNDytwt85VrbkhhUA/JBGm1MPaWJ65Cl4wOgLaB1NYvJSZ0Ef03MMEU/0xpPMXUIQ29UfA==} - module-details-from-path@1.0.4: resolution: {integrity: sha512-EGWKgxALGMgzvxYF1UyGTy0HXX/2vHLkw6+NvDKW2jypWbHpjQuj4UMcqQWXHERJhVGKikolT06G3bcKe4fi7w==} + mrmime@2.0.1: + resolution: {integrity: sha512-Y3wQdFg2Va6etvQ5I82yUhGdsKrcYox6p7FfL1LbK2J4V01F9TGlepTIhnK24t7koZibmg82KGglhA1XK5IsLQ==} + engines: {node: '>=10'} + ms@2.1.3: resolution: {integrity: sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==} @@ -1603,40 +1562,18 @@ packages: resolution: {integrity: sha512-gSrprq0fJ3EiOErzjdIZrjysVVmJ4uu1QWfCDss5LypA5OXvrMje5Ym5z6V6RLyJ2eF87lasX7t6a0AnFvZblg==} engines: {node: ^22.22.2 || ^24.15.0 || >=26.0.0} - mz@2.7.0: - resolution: {integrity: sha512-z81GNO7nnYMEhrGh9LeymoE4+Yr0Wn5McHIZMK5cfQCl+NDX08sCZgUc9/6MHni9IWuFLm1Z3HTCXu2z9fN62Q==} - nanoid@3.3.19: resolution: {integrity: sha512-Y2tUNy4ouw6tq5oDSKeQYGOyhkUBhNOcGV/02KC+6kd9eDGqdZd++mjMiIDilrBYvjEnCYvVtsuHCuP+okSfug==} engines: {node: ^10 || ^12 || ^13.7 || ^14 || >=15.0.1} hasBin: true - negotiator@1.0.0: - resolution: {integrity: sha512-8Ofs/AUQh8MaEcrlq5xOX0CQ9ypTF5dl78mjlMNfOK08fzpgTHQRQPBxcPlEtIw0yRpws+Zo/3r+5WRby7u3Gg==} - engines: {node: '>= 0.6'} - node-int64@0.4.0: resolution: {integrity: sha512-O5lz91xSOeoXP6DulyHfllpq+Eg00MWitZIbtPfoSEvqIHdl5gfcY6hYzDWnj0qD5tz52PI08u9qUvSVeUBeHw==} - object-assign@4.1.1: - resolution: {integrity: sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==} - engines: {node: '>=0.10.0'} - - object-inspect@1.13.4: - resolution: {integrity: sha512-W67iLl4J2EXEGTbfeHCffrjDfitvLANg0UlX3wFUUSTx92KXRFegMHUVgSqE+wvhAbi4WqjGg9czysTV2Epbew==} - engines: {node: '>= 0.4'} - obug@2.2.1: resolution: {integrity: sha512-XrsrhT5sybtKI6wakr2SPOlGZWWYbUXZ7a0jT8/QOeAPau+1X/bSegNe5YR75oJmEZQbKningirmGOEJCIk61Q==} engines: {node: '>=12.20.0'} - on-finished@2.4.1: - resolution: {integrity: sha512-oVlzkg3ENAhCk2zdv7IJwd/QUD4z2RxRwpkcGY8psCVcCYZNq4wYnVWALHM+brtuJjePWiYF/ClmuDr8Ch5+kg==} - engines: {node: '>= 0.8'} - - once@1.4.0: - resolution: {integrity: sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==} - opentracing@0.14.7: resolution: {integrity: sha512-vz9iS7MJ5+Bp1URw8Khvdyw1H/hGvzHWlKQ7eRrQojSCDL1/SrWfrY9QebLw97n2deyRtzHRC3MkQfVNUCo91Q==} engines: {node: '>=0.10'} @@ -1644,9 +1581,35 @@ packages: outvariant@1.4.3: resolution: {integrity: sha512-+Sl2UErvtsoajRDKCE5/dBz4DIvHXQQnAxtQTF04OJxY0+DyZXSo5P5Bb7XYWOh81syohlYL24hbDwxedPUJCA==} - parseurl@1.3.3: - resolution: {integrity: sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ==} - engines: {node: '>= 0.8'} + oxfmt@0.72.0: + resolution: {integrity: sha512-16OQeL0uhPZbfmT9S9WWM0Nv9+BmVi8Ra1uTYaSKj/qSCs6FB30KOhz9JoNYw6thT+K5bVxsk/tSEOu497i34g==} + engines: {node: ^20.19.0 || >=22.12.0} + hasBin: true + peerDependencies: + svelte: ^5.0.0 + vite-plus: '*' + peerDependenciesMeta: + svelte: + optional: true + vite-plus: + optional: true + + oxlint-tsgolint@7.0.2003: + resolution: {integrity: sha512-VnK4zlqgmgq/7ZcjzCk/WpN8kKFsYGcB85Io9qT3wL4K8Un3RKmJkp793crNETieMusPMKOA4a+Mw2wbteI6TQ==} + hasBin: true + + oxlint@1.87.0: + resolution: {integrity: sha512-drxVlaW7Oqq7wSy1CN25znRu00aRq8n57Q2wniJRxCmtYVz07ufCm7EVDPQFZURrTgQIjzKrVEHAD/HcvJXObg==} + engines: {node: ^20.19.0 || >=22.12.0} + hasBin: true + peerDependencies: + oxlint-tsgolint: '>=7.0.2003' + vite-plus: '*' + peerDependenciesMeta: + oxlint-tsgolint: + optional: true + vite-plus: + optional: true path-parse@1.0.7: resolution: {integrity: sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==} @@ -1654,9 +1617,6 @@ packages: path-to-regexp@6.3.0: resolution: {integrity: sha512-Yhpw4T9C6hPpgPeA28us07OJeqZ5EzQTkbfwuhsUg0c237RomFoETJgmp2sa3F/41gfLE6G5cqcYwznmeEeOlQ==} - path-to-regexp@8.4.2: - resolution: {integrity: sha512-qRcuIdP69NPm4qbACK+aDogI5CBDMi1jKe0ry5rSQJz8JVLsC7jV8XpiJjGRLLol3N+R5ihGYcrPLTno6pAdBA==} - pathe@2.0.3: resolution: {integrity: sha512-WUjGcAqP1gQacoQe+OBJsFA7Ld4DyXuUIjZ5cc75cLHvJ7dtNsTugphxIADwspS+AraAUePCKrSVtPLFj/F88w==} @@ -1667,39 +1627,18 @@ packages: resolution: {integrity: sha512-qcJu88Q2IWqJsDD529JKMdwGm/dvInW4HvQnRwiH9JtihJvzGOscDtHE3x1pBKeUOTysQ8kVmLnJ2kJu7yhcGA==} engines: {node: '>=12'} - pirates@4.0.7: - resolution: {integrity: sha512-TfySrs/5nm8fQJDcBDuUng3VOUKsd7S+zqvbOTiGXHfxX4wK31ard+hoNuvkicM/2YFzlpDgABOevKSsB4G/FA==} - engines: {node: '>= 6'} - - pkg-types@1.3.1: - resolution: {integrity: sha512-/Jm5M4RvtBFVkKWRu2BLUTNP8/M2a+UwuAX+ae4770q1qVGtfjG+WTCupoZixokjmHiry8uI+dlY8KXYV5HVVQ==} - - pluralize@8.0.0: - resolution: {integrity: sha512-Nc3IT5yHzflTfbjgqWcCPpo7DaKy4FnpB0l/zCAW0Tc7jxAiuqSxHasntB3D7887LSrA93kDJ9IXovxJYxyLCA==} - engines: {node: '>=4'} - - postcss-load-config@6.0.1: - resolution: {integrity: sha512-oPtTM4oerL+UXmx+93ytZVN82RrlY/wPUV8IeDxFrzIjXOLF1pN+EmKPLbubvKHT2HC20xXsCAH2Z+CKV6Oz/g==} - engines: {node: '>= 18'} - peerDependencies: - jiti: '>=1.21.0' - postcss: ^8.5.18 - tsx: ^4.8.1 - yaml: ^2.4.2 - peerDependenciesMeta: - jiti: - optional: true - postcss: - optional: true - tsx: - optional: true - yaml: - optional: true + pngjs@7.0.0: + resolution: {integrity: sha512-LKWqWJRhstyYo9pGvgor/ivk2w94eSjE3RGVuzLGlr3NmD8bf7RcYGze1mNdEHRP6TRP6rMuDHk5t44hnTRyow==} + engines: {node: '>=14.19.0'} postcss@8.5.28: resolution: {integrity: sha512-RRuzqDtt5Y9h3quz5hWhK+TPnsmVs6WwSU6LkJMeY4HstUEDuYTG8UJSdawMRzmzAtV+KEoG8N3Qg2qLy5vM/A==} engines: {node: ^10 || ^12 || >=14} + pretty-format@27.5.1: + resolution: {integrity: sha512-Qb1gy5OrP5+zDf2Bvnzdl3jsTf1qXVMazbvCoKhtKqVs4/YK4ozX4gKQJJVyNe+cajNPn0KoC0MC3FUmaHWEmQ==} + engines: {node: ^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0} + process@0.10.1: resolution: {integrity: sha512-dyIett8dgGIZ/TXKUzeYExt7WA6ldDzys9vTDU/cCA9L17Ypme+KzS+NjQCjpn9xsvi/shbMC+yP/BcFMBz0NA==} engines: {node: '>= 0.6.0'} @@ -1708,25 +1647,8 @@ packages: resolution: {integrity: sha512-/FPD0nUc9jH6rfFjji9IBqOz4pcSE3CsT1m7Ep6Mdb0LxSUMj8hgl6GomOvZzpNpAqqGaXA0P3VSrZLFzIhQrw==} engines: {node: '>=12.0.0'} - proxy-addr@2.0.8: - resolution: {integrity: sha512-5nnx0yGyVUcY6t9RnWcARWtwT9F1D8O9rt08htPvnd49W1IgZtmLkhu9WfMzQj1cFxjHIO6connUNVW5k7AVyQ==} - engines: {node: '>= 0.10'} - - qs@6.15.2: - resolution: {integrity: sha512-Rzq0KEyX/w/tEybncDgdkZrJgVUsUMk3xjh3t5bv3S1HTAtg+uOYt72+ZfwiQwKdysThkTBdL/rTi6HDmX9Ddw==} - engines: {node: '>=0.6'} - - range-parser@1.2.1: - resolution: {integrity: sha512-Hrgsx+orqoygnmhFbKaHE6c296J+HTAQXoxEF6gNupROmmGJRoyzfG3ccAveqCBrwr/2yxQ5BVd/GTl5agOwSg==} - engines: {node: '>= 0.6'} - - raw-body@3.0.2: - resolution: {integrity: sha512-K5zQjDllxWkf7Z5xJdV0/B0WTNqx6vxG70zJE4N0kBs4LovmEYWJzQGxC9bS9RAKu3bgM40lrd5zoLJ12MQ5BA==} - engines: {node: '>= 0.10'} - - readdirp@4.1.2: - resolution: {integrity: sha512-GDhwkLfywWL2s6vEjyhri+eXmfH6j1L7JE27WhqLeYzoh/A3DBaYGEj2H/HFZCn/kMfim73FXxEJTw06WtxQwg==} - engines: {node: '>= 14.18.0'} + react-is@17.0.2: + resolution: {integrity: sha512-w2GsyukL62IJnlaff/nRegPQR94C/XXamvMWmSHRJ4y7Ts/4ocGRmTHvOs8PSE6pB3dWOrD/nueuU5sduBsQ4w==} require-directory@2.1.1: resolution: {integrity: sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q==} @@ -1740,10 +1662,6 @@ packages: resolution: {integrity: sha512-efCx3b+0Z69/LGJmm9Yvi4cqEdxnoGnxYxGxBghkkTTFeXRtTCmmhO0AnAfHz59k957uTSuy8WaHqOs8wbYUWg==} engines: {node: '>=6'} - resolve-from@5.0.0: - resolution: {integrity: sha512-qYg9KP24dD5qka9J47d0aVky0N+b4fTU89LN9iDnjB5waksiC49rvMB0PrUJQGoTmH50XPiqOvAjDfaijGxYZw==} - engines: {node: '>=8'} - resolve@1.22.12: resolution: {integrity: sha512-TyeJ1zif53BPfHootBGwPRYT1RUt6oGWsaQr8UyZW/eAm9bKoijtvruSDEmZHm92CwS9nj7/fWttqPCgzep8CA==} engines: {node: '>= 0.4'} @@ -1752,23 +1670,6 @@ packages: rettime@0.11.11: resolution: {integrity: sha512-ILJRqVWBCTlg9r42fFgwVZx1gnFAcQF8mRoMkbgQfIrjEDf9nbBFDFx00oloOa+Q869FUtaYDXZvEfnecQSCoQ==} - rolldown@1.0.2: - resolution: {integrity: sha512-oZx5zVDtVB44AW3eaifgDml1gWRDZGvjcfdxonE4swNPG98PrrXjaO/KrnUjzlMnztCCRVlUueA1kCXhARGk6g==} - engines: {node: ^20.19.0 || >=22.12.0} - hasBin: true - - rollup@4.60.4: - resolution: {integrity: sha512-WHeFSbZYsPu3+bLoNRUuAO+wavNlocOPf3wSHTP7hcFKVnJeWsYlCDbr3mTS14FCizf9ccIxXA8sGL8zKeQN3g==} - engines: {node: '>=18.0.0', npm: '>=8.0.0'} - hasBin: true - - router@2.2.0: - resolution: {integrity: sha512-nLTrUKm2UyiL7rlhapu/Zl45FwNgkZGaCpZbIHajDYgwlJCOzLSk+cIPAnsEqV955GjILJnKbdQC1nVPz+gAYQ==} - engines: {node: '>= 18'} - - safer-buffer@2.1.2: - resolution: {integrity: sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==} - semifies@1.0.0: resolution: {integrity: sha512-xXR3KGeoxTNWPD4aBvL5NUpMTT7WMANr3EWnaS190QVkY52lqqcVRD7Q05UVbBhiWDGWMlJEUam9m7uFFGVScw==} @@ -1777,46 +1678,20 @@ packages: engines: {node: '>=10'} hasBin: true - send@1.2.1: - resolution: {integrity: sha512-1gnZf7DFcoIcajTjTwjwuDjzuz4PPcY2StKPlsGAQ1+YH20IRVrBaXSWmdjowTJ6u8Rc01PoYOGHXfP1mYcZNQ==} - engines: {node: '>= 18'} - - serve-static@2.2.1: - resolution: {integrity: sha512-xRXBn0pPqQTVQiC8wyQrKs2MOlX24zQ0POGaj0kultvoOCstBQM5yvOhAVSUwOMjQtTvsPWoNCHfPGwaaQJhTw==} - engines: {node: '>= 18'} - set-cookie-parser@3.1.0: resolution: {integrity: sha512-kjnC1DXBHcxaOaOXBHBeRtltsDG2nUiUni+jP92M9gYdW12rsmx92UsfpH7o5tDRs7I1ZZPSQJQGv3UaRfCiuw==} - setprototypeof@1.2.0: - resolution: {integrity: sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw==} - shimmer@1.2.1: resolution: {integrity: sha512-sQTKC1Re/rM6XyFM6fIAGHRPVGvyXfgzIDvzoq608vM+jeyVD0Tu1E6Np0Kc2zAIFWIj963V2800iF/9LPieQw==} - side-channel-list@1.0.1: - resolution: {integrity: sha512-mjn/0bi/oUURjc5Xl7IaWi/OJJJumuoJFQJfDDyO46+hBWsfaVM65TBHq2eoZBhzl9EchxOijpkbRC8SVBQU0w==} - engines: {node: '>= 0.4'} - - side-channel-map@1.0.1: - resolution: {integrity: sha512-VCjCNfgMsby3tTdo02nbjtM/ewra6jPHmpThenkTYh8pG9ucZ/1P8So4u4FGBek/BjpOVsDCMoLA/iuBKIFXRA==} - engines: {node: '>= 0.4'} - - side-channel-weakmap@1.0.2: - resolution: {integrity: sha512-WPS/HvHQTYnHisLo9McqBHOJk2FkHO/tlpvldyrnem4aeQp4hai3gythswg6p01oSoTl58rcpiFAjF2br2Ak2A==} - engines: {node: '>= 0.4'} - - side-channel@1.1.0: - resolution: {integrity: sha512-ZX99e6tRweoUXqR+VBrslhda51Nh5MTQwou5tnUDgbtyM0dBgmhEDtWGP/xbKn6hqfPRHujUNwz5fy/wbbhnpw==} - engines: {node: '>= 0.4'} - - siginfo@2.0.0: - resolution: {integrity: sha512-ybx0WO1/8bSBLEWXZvEd7gMW3Sn3JFlW3TvX1nREbDLRNQNaeNN8WK0meBwPdAaOI7TtRRRJn/Es1zhrrCHu7g==} - signal-exit@4.1.0: resolution: {integrity: sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw==} engines: {node: '>=14'} + sirv@3.0.2: + resolution: {integrity: sha512-2wcC/oGxHis/BoHkkPwldgiPSYcpZK3JU28WoMVv55yHJgcZ8rlXvuG9iZggz+sU1d4bRgIGASwyWqjxu3FM0g==} + engines: {node: '>=18'} + source-map-js@1.2.2: resolution: {integrity: sha512-KGj/8Y43x35aZVDtt+J4mK1hoLGHULMYfSkODJNQjNDC3oW1PqPoxMwo0pLUsWM/UEGzON/NxeHywEfNXNP3Vw==} engines: {node: '>=0.10.0'} @@ -1825,9 +1700,6 @@ packages: resolution: {integrity: sha512-i5uvt8C3ikiWeNZSVZNWcfZPItFQOsYTUAOkcUPGd8DqDy1uOUikjt5dG+uRlwyvR108Fb9DOd4GvXfT0N2/uQ==} engines: {node: '>= 12'} - stackback@0.0.2: - resolution: {integrity: sha512-1XMJE5fQo1jGH6Y/7ebnwPOBEkIEnT4QF32d5R1+VXdXveM0IBMJt8zfaxX1P3QhVwrYe+576+jkANtSS2mBbw==} - statuses@2.0.2: resolution: {integrity: sha512-DvEy55V3DB7uknRo+4iOGT5fP1slR8wQohVdknigZPMpMstaKJQWhwiYBACJE3Ul2pTnATihhBYnRhZQHGBiRw==} engines: {node: '>= 0.8'} @@ -1849,11 +1721,6 @@ packages: resolution: {integrity: sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==} engines: {node: '>=8'} - sucrase@3.35.1: - resolution: {integrity: sha512-DhuTmvZWux4H1UOnWMB3sk0sbaCVOoQZjv8u1rDoTV0HTdGem9hkAZtl4JZy8P2z4Bg0nT+YMeOFyVr4zcG5Tw==} - engines: {node: '>=16 || 14 >=14.17'} - hasBin: true - supports-preserve-symlinks-flag@1.0.0: resolution: {integrity: sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==} engines: {node: '>= 0.4'} @@ -1862,40 +1729,27 @@ packages: resolution: {integrity: sha512-yEFYrVhod+hdNyx7g5Bnkkb0G6si8HJurOoOEgC8B/O0uXLHlaey/65KRv6cuWBNhBgHKAROVpc7QyYqE5gFng==} engines: {node: '>=20'} - termi-link@1.1.0: - resolution: {integrity: sha512-2qSN6TnomHgVLtk+htSWbaYs4Rd2MH/RU7VpHTy6MBstyNyWbM4yKd1DCYpE3fDg8dmGWojXCngNi/MHCzGuAA==} - engines: {node: '>=12'} - - thenify-all@1.6.0: - resolution: {integrity: sha512-RNxQH/qI8/t3thXJDwcstUO4zeqo64+Uy/+sNVRBx4Xn2OX+OZ9oP+iJnNFqplFra2ZUVeKCSa2oVWi3T4uVmA==} - engines: {node: '>=0.8'} - - thenify@3.3.1: - resolution: {integrity: sha512-RVZSIV5IG10Hk3enotrhvz0T9em6cyHBLkH/YAZuKqd8hRkKhSfCGIcP2KUY0EPxndzANBmNllzWPwak+bheSw==} - thriftrw@3.11.4: resolution: {integrity: sha512-UcuBd3eanB3T10nXWRRMwfwoaC6VMk7qe3/5YIWP2Jtw+EbHqJ0p1/K3x8ixiR5dozKSSfcg1W+0e33G1Di3XA==} engines: {node: '>= 0.10.x'} hasBin: true - tinybench@2.9.0: - resolution: {integrity: sha512-0+DUvqWMValLmha6lr4kD8iAMK1HzV0/aKnCtWb9v9641TnP/MFb7Pc2bxoxQjTXAErryXVgUOfv2YqNllqGeg==} - - tinyexec@0.3.2: - resolution: {integrity: sha512-KQQR9yN7R5+OSwaK0XQoj22pwHoTlgYqmUscPYoknOoWCWfj/5/ABTMRi69FrKU5ffPVh5QcFikpWJI/P1ocHA==} + tinybench@6.2.2: + resolution: {integrity: sha512-nIZ2N2dFpjBsg2hgitKu7mA+FvsIJuGJOwysRngmVRRmWgR/UyPjwJj8c/fT/dhm8EkHnF5EOogRf+0Z2qrimA==} + engines: {node: '>=20.0.0'} tinyexec@1.3.1: resolution: {integrity: sha512-GCvB3aoys96IuDFBMcTB46JOR6mdMtAToqwiW8JlWhsoh1mhHi/xn9ss/Dg7N555GiJyEt2qzoG/NHCwM6h1EA==} engines: {node: '>=18'} - tinyglobby@0.2.16: - resolution: {integrity: sha512-pn99VhoACYR8nFHhxqix+uvsbXineAasWm5ojXoN8xEwK5Kd3/TrhNn1wByuD52UxWRLy8pu+kRMniEi6Eq9Zg==} - engines: {node: '>=12.0.0'} - tinyglobby@0.2.17: resolution: {integrity: sha512-wXR/dYpcqKmfWpEdZjiKJOwCNFndD0DMnrW/cYjVGttEkBfVgcLFHoNrlj47mjOVic9yyNu65alsgF4NQyTa2g==} engines: {node: '>=12.0.0'} + tinypool@2.2.0: + resolution: {integrity: sha512-jBrmx4lYmaC9k/mgPbylxs7kBUxHtD8256up+HjLaDFfXScKJQyil+SWXSvhAtT7XHo+yTVlpB81PGmHP8oLSQ==} + engines: {node: ^20.0.0 || >=22.0.0} + tinyrainbow@3.1.1: resolution: {integrity: sha512-yau8yJdTt989Mm0Bd/236QnzEiPf2xLLTqUZRUJOo/3CB078LSwzei343DgtJVmfJKJE3TMINY1u42SQsP6mXw==} engines: {node: '>=14.0.0'} @@ -1907,66 +1761,26 @@ packages: resolution: {integrity: sha512-yHBe+zVfzNZ3QfTPW/Z6KK1G2t340gFjMHqI/4KKSt/abzYydzuCnpqdaF5gCCABby+9Yfbj59oR5F2Fd5CBzg==} hasBin: true - toidentifier@1.0.1: - resolution: {integrity: sha512-o5sSPKEkg/DIQNmH43V0/uerLrpzVedkUh8tGNvaeXpfpuwjKenlSox/2O/BTlZUtEe+JG7s5YhEz608PlAHRA==} - engines: {node: '>=0.6'} + totalist@3.0.1: + resolution: {integrity: sha512-sf4i37nQ2LBx4m3wB74y+ubopq6W/dIzXg0FDGjsYnZHVa1Da8FH853wlL2gtUhg+xJXjfk3kUZS3BRoQeoQBQ==} + engines: {node: '>=6'} tough-cookie@6.0.1: resolution: {integrity: sha512-LktZQb3IeoUWB9lqR5EWTHgW/VTITCXg4D21M+lvybRVdylLrRMnqaIONLVb5mav8vM19m44HIcGq4qASeu2Qw==} engines: {node: '>=16'} - tree-kill@1.2.2: - resolution: {integrity: sha512-L0Orpi8qGpRG//Nd+H90vFB+3iHnue1zSSGmNOOCh1GLJ7rUKVwV2HvijphGQS2UmhUZewS9VgvxYIdgr+fG1A==} - hasBin: true - - ts-interface-checker@0.1.13: - resolution: {integrity: sha512-Y/arvbn+rrz3JCKl9C4kVNfTfSm2/mEp5FSz5EsZSANGPSlQrpRI5M4PKF+mJnE52jOO90PnPSc3Ur3bTQw0gA==} - - tslib@2.8.1: - resolution: {integrity: sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==} - - tsup@8.5.1: - resolution: {integrity: sha512-xtgkqwdhpKWr3tKPmCkvYmS9xnQK3m3XgxZHwSUjvfTjp7YfXe5tT3GgWi0F2N+ZSMsOeWeZFh7ZZFg5iPhing==} - engines: {node: '>=18'} - hasBin: true - peerDependencies: - '@microsoft/api-extractor': ^7.36.0 - '@swc/core': ^1 - postcss: ^8.5.18 - typescript: '>=4.5.0' - peerDependenciesMeta: - '@microsoft/api-extractor': - optional: true - '@swc/core': - optional: true - postcss: - optional: true - typescript: - optional: true - type-fest@5.6.0: resolution: {integrity: sha512-8ZiHFm91orbSAe2PSAiSVBVko18pbhbiB3U9GglSzF/zCGkR+rxpHx6sEMCUm4kxY4LjDIUGgCfUMtwfZfjfUA==} engines: {node: '>=20'} - type-is@2.1.0: - resolution: {integrity: sha512-faYHw0anBbc/kWF3zFTEnxSFOAGUX9GFbOBthvDdLsIlEoWOFOtS0zgCiQYwIskL9iGXZL3kAXD8OoZ4GmMATA==} - engines: {node: '>= 18'} - typescript@5.5.4: resolution: {integrity: sha512-Mtq29sKDAEYP7aljRgtPOpTvOfbwRWlS6dPRzwjdE+C0R4brX/GUyhHSecbHMFLNBLcJIPt9nl9yG5TZ1weH+Q==} engines: {node: '>=14.17'} hasBin: true - ufo@1.6.4: - resolution: {integrity: sha512-JFNbkD1Svwe0KvGi8GOeLcP4kAWQ609twvCdcHxq1oSL8svv39ZuSvajcD8B+5D0eL4+s1Is2D/O6KN3qcTeRA==} - undici-types@6.21.0: resolution: {integrity: sha512-iwDZqg0QAGrg9Rav5H4n0M64c3mkR59cJ6wQp+7C4nI0gsmExaedaYLNO44eT4AtBBwjbTiGPMlt2Md0T9H9JQ==} - unpipe@1.0.0: - resolution: {integrity: sha512-pjy2bYhSsufwWlKwPc+l3cN7+wuJlK6uz0YdJEOlQDbl6jo/YlPi4mb8agUkVC8BF7V8NuzeyPNqRksA3hztKQ==} - engines: {node: '>= 0.8'} - unplugin@2.3.11: resolution: {integrity: sha512-5uKD0nqiYVzlmCRs01Fhs2BdkEgBS3SAVP6ndrBsuK42iC2+JHyxM05Rm9G8+5mkmRtzMZGY8Ct5+mliZxU/Ww==} engines: {node: '>=18.12.0'} @@ -1978,67 +1792,36 @@ packages: resolution: {integrity: sha512-vIYxrBCC/N/K+Js3qSN88go7kIfNPssr/hHCesKCQNAjmgvYS2oqr69kIufEG+O4+PfezOH4EbIeHCfFov8ZgQ==} hasBin: true - vary@1.1.2: - resolution: {integrity: sha512-BNGbWLfd0eUPabhkXUVm0j8uuvREyTh5ovRa/dyow/BqAbZJyC+5fU+IzQOzmAKzYqYRAISoRhdQr3eIZ/PXqg==} - engines: {node: '>= 0.8'} - - vite@8.0.14: - resolution: {integrity: sha512-s4BJJ+5y1pYL6Otw51FHhVJQhPnuRinKig64g/1+EUNaJsd3gCKdD31IPFvswUgW9/60QT9oFHbZHbQK5imcxw==} - engines: {node: ^20.19.0 || >=22.12.0} + vite-plus@1.1.0: + resolution: {integrity: sha512-khkRlY70qqbBV0QmF9QKXSrhxIRYftb1H05AJMowMoq7iouchmAoXYeReZ8pKKMOadqrWVxFa3S9BA2m7w59iA==} + engines: {node: ^22.18.0 || ^24.11.0 || >=26.0.0} hasBin: true peerDependencies: - '@types/node': ^20.19.0 || >=22.12.0 - '@vitejs/devtools': ^0.1.18 - esbuild: ^0.27.0 || ^0.28.0 - jiti: '>=1.21.0' - less: ^4.0.0 - sass: ^1.70.0 - sass-embedded: ^1.70.0 - stylus: '>=0.54.8' - sugarss: ^5.0.0 - terser: ^5.16.0 - tsx: ^4.8.1 - yaml: ^2.4.2 + '@vitest/browser-playwright': 5.0.3 + '@vitest/browser-webdriverio': ^5.0.0-beta.5 || >=5.0.0 + playwright: '*' peerDependenciesMeta: - '@types/node': - optional: true - '@vitejs/devtools': - optional: true - esbuild: - optional: true - jiti: - optional: true - less: - optional: true - sass: - optional: true - sass-embedded: - optional: true - stylus: - optional: true - sugarss: - optional: true - terser: + '@vitest/browser-playwright': optional: true - tsx: + '@vitest/browser-webdriverio': optional: true - yaml: + playwright: optional: true - vitest@4.1.11: - resolution: {integrity: sha512-fhACrNXUidIbGSBr5FlbuBkO7VWC1ZyLl0DO4CU2DrQoAPxX84Ysxs+HeGQpii5lZWV1Q4gBZTTu49mF+A6Edw==} - engines: {node: ^20.0.0 || ^22.0.0 || >=24.0.0} + vitest@5.0.3: + resolution: {integrity: sha512-xMw97S3rjdtj5dkVat7jCsqWBpvchs3RlpQctUqwJD0KkERk40vz2fJ77lDwW/Vzh/pk18eItYAzkodhSes3jQ==} + engines: {node: ^22.12.0 || ^24.0.0 || >=26.0.0} hasBin: true peerDependencies: '@edge-runtime/vm': '*' '@opentelemetry/api': ^1.9.0 - '@types/node': ^20.0.0 || ^22.0.0 || >=24.0.0 - '@vitest/browser-playwright': 4.1.11 - '@vitest/browser-preview': 4.1.11 - '@vitest/browser-webdriverio': 4.1.11 - '@vitest/coverage-istanbul': 4.1.11 - '@vitest/coverage-v8': 4.1.11 - '@vitest/ui': 4.1.11 + '@types/node': ^22.0.0 || >=24.0.0 + '@vitest/browser-playwright': 5.0.3 + '@vitest/browser-preview': 5.0.3 + '@vitest/browser-webdriverio': ^5.0.0-beta.5 || >=5.0.0 + '@vitest/coverage-istanbul': 5.0.3 + '@vitest/coverage-v8': 5.0.3 + '@vitest/ui': 5.0.3 happy-dom: '*' jsdom: '*' vite: ^8.0.5 @@ -2069,17 +1852,26 @@ packages: webpack-virtual-modules@0.6.2: resolution: {integrity: sha512-66/V2i5hQanC51vBQKPH4aI8NMAcBW59FVBs+rC7eGHupMyfn34q7rZIE+ETlJ+XTevqfUhVVBgSUNSW2flEUQ==} - why-is-node-running@2.3.0: - resolution: {integrity: sha512-hUrmaWBdVDcxvYqnyh09zunKzROWjbZTiNy8dBEjkS7ehEDQibXJ7XvlmtbwuTclUiIyN+CyXQD4Vmko8fNm8w==} - engines: {node: '>=8'} + why-is-node-running@3.2.1: + resolution: {integrity: sha512-Tb2FUhB4vUsGQlfSquQLYkApkuPAFQXGFzxWKHHumVz2dK+X1RUm/HnID4+TfIGYJ1kTcwOaCk/buYCEJr6YjQ==} + engines: {node: '>=20.11'} hasBin: true wrap-ansi@7.0.0: resolution: {integrity: sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==} engines: {node: '>=10'} - wrappy@1.0.2: - resolution: {integrity: sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==} + ws@8.22.0: + resolution: {integrity: sha512-Ydggc987+RO0AnWtZ/7Wq9FtNvcrL1b/RO0ud9mWjUPgDrsAAwQSF51sm2hm1XofbU/4jkpGEsLFsZZxU+1DOg==} + engines: {node: '>=10.0.0'} + peerDependencies: + bufferutil: ^4.0.1 + utf-8-validate: '>=5.0.2' + peerDependenciesMeta: + bufferutil: + optional: true + utf-8-validate: + optional: true xorshift@1.2.0: resolution: {integrity: sha512-iYgNnGyeeJ4t6U11NpA/QiKy+PXn5Aa3Azg5qkwIFz1tBLllQrjjsk9yzD7IAK0naNU4JxdeDgqW9ov4u/hc4g==} @@ -2100,6 +1892,15 @@ packages: resolution: {integrity: sha512-7dSzzRQ++CKnNI/krKnYRV7JKKPUXMEh61soaHKg9mrWEhzFWhFnxPxGl+69cD1Ou63C13NUPCnmIcrvqCuM6w==} engines: {node: '>=12'} + yuku-ast@0.9.5: + resolution: {integrity: sha512-Q8qW8WwQnN5Cm0ZZivdRIfv0sRLTjUq0YumXJkw8CYN1aCdICH9rk4C47/4n6kA5EqDtlj+F608twoTSV2MwdQ==} + + yuku-codegen@0.9.5: + resolution: {integrity: sha512-zGUVyDpSK4b6c9B0yyxi2P0wujn1XZTbG9dCb7d6gyAoP63EMgyLzUwPUvwxPG5jgYqhlI7w+S3oCyapqGr4PA==} + + yuku-parser@0.9.5: + resolution: {integrity: sha512-IBnAdNVMswWbJcM7woPluOudectJzFjJ1N8jVRxP9Uq4CIv5hZdBIcdmtRbFDYF/Ph2j2gVup4YJ4N9mh0jYFA==} + zod-to-json-schema@3.25.2: resolution: {integrity: sha512-O/PgfnpT1xKSDeQYSCfRI5Gy3hPf91mKVDuYLUHZJMiDFptvP41MSnWofm8dnCm0256ZNfZIM7DSzuSMAFnjHA==} peerDependencies: @@ -2110,26 +1911,17 @@ packages: snapshots: - '@braintrust/bt-darwin-arm64@0.12.0': - optional: true - - '@braintrust/bt-darwin-x64@0.12.0': - optional: true - - '@braintrust/bt-linux-arm64@0.12.0': - optional: true - - '@braintrust/bt-linux-x64-musl@0.12.0': - optional: true + '@babel/code-frame@7.29.7': + dependencies: + '@babel/helper-validator-identifier': 7.29.7 + js-tokens: 4.0.0 + picocolors: 1.1.1 - '@braintrust/bt-linux-x64@0.12.0': - optional: true + '@babel/helper-validator-identifier@7.29.7': {} - '@braintrust/bt-win32-arm64@0.12.0': - optional: true + '@babel/runtime@7.29.10': {} - '@braintrust/bt-win32-x64@0.12.0': - optional: true + '@blazediff/core@1.10.0': {} '@braintrust/otel@file:..(@opentelemetry/api@1.9.0)(@opentelemetry/core@1.9.0(@opentelemetry/api@1.9.0))(@opentelemetry/exporter-trace-otlp-http@0.57.2(@opentelemetry/api@1.9.0))(@opentelemetry/sdk-trace-base@1.9.0(@opentelemetry/api@1.9.0))(braintrust@file:../../../js(zod@4.4.3))': dependencies: @@ -2139,178 +1931,81 @@ snapshots: '@opentelemetry/sdk-trace-base': 1.9.0(@opentelemetry/api@1.9.0) braintrust: file:../../../js(zod@4.4.3) - '@colors/colors@1.5.0': - optional: true - - '@emnapi/core@1.10.0': - dependencies: - '@emnapi/wasi-threads': 1.2.1 - tslib: 2.8.1 - optional: true - - '@emnapi/runtime@1.10.0': - dependencies: - tslib: 2.8.1 - optional: true - - '@emnapi/wasi-threads@1.2.1': - dependencies: - tslib: 2.8.1 - optional: true - - '@esbuild/aix-ppc64@0.27.7': - optional: true - '@esbuild/aix-ppc64@0.28.1': optional: true - '@esbuild/android-arm64@0.27.7': - optional: true - '@esbuild/android-arm64@0.28.1': optional: true - '@esbuild/android-arm@0.27.7': - optional: true - '@esbuild/android-arm@0.28.1': optional: true - '@esbuild/android-x64@0.27.7': - optional: true - '@esbuild/android-x64@0.28.1': optional: true - '@esbuild/darwin-arm64@0.27.7': - optional: true - '@esbuild/darwin-arm64@0.28.1': optional: true - '@esbuild/darwin-x64@0.27.7': - optional: true - '@esbuild/darwin-x64@0.28.1': optional: true - '@esbuild/freebsd-arm64@0.27.7': - optional: true - '@esbuild/freebsd-arm64@0.28.1': optional: true - '@esbuild/freebsd-x64@0.27.7': - optional: true - '@esbuild/freebsd-x64@0.28.1': optional: true - '@esbuild/linux-arm64@0.27.7': - optional: true - '@esbuild/linux-arm64@0.28.1': optional: true - '@esbuild/linux-arm@0.27.7': - optional: true - '@esbuild/linux-arm@0.28.1': optional: true - '@esbuild/linux-ia32@0.27.7': - optional: true - '@esbuild/linux-ia32@0.28.1': optional: true - '@esbuild/linux-loong64@0.27.7': - optional: true - '@esbuild/linux-loong64@0.28.1': optional: true - '@esbuild/linux-mips64el@0.27.7': - optional: true - '@esbuild/linux-mips64el@0.28.1': optional: true - '@esbuild/linux-ppc64@0.27.7': - optional: true - '@esbuild/linux-ppc64@0.28.1': optional: true - '@esbuild/linux-riscv64@0.27.7': - optional: true - '@esbuild/linux-riscv64@0.28.1': optional: true - '@esbuild/linux-s390x@0.27.7': - optional: true - '@esbuild/linux-s390x@0.28.1': optional: true - '@esbuild/linux-x64@0.27.7': - optional: true - '@esbuild/linux-x64@0.28.1': optional: true - '@esbuild/netbsd-arm64@0.27.7': - optional: true - '@esbuild/netbsd-arm64@0.28.1': optional: true - '@esbuild/netbsd-x64@0.27.7': - optional: true - '@esbuild/netbsd-x64@0.28.1': optional: true - '@esbuild/openbsd-arm64@0.27.7': - optional: true - '@esbuild/openbsd-arm64@0.28.1': optional: true - '@esbuild/openbsd-x64@0.27.7': - optional: true - '@esbuild/openbsd-x64@0.28.1': optional: true - '@esbuild/openharmony-arm64@0.27.7': - optional: true - '@esbuild/openharmony-arm64@0.28.1': optional: true - '@esbuild/sunos-x64@0.27.7': - optional: true - '@esbuild/sunos-x64@0.28.1': optional: true - '@esbuild/win32-arm64@0.27.7': - optional: true - '@esbuild/win32-arm64@0.28.1': optional: true - '@esbuild/win32-ia32@0.27.7': - optional: true - '@esbuild/win32-ia32@0.28.1': optional: true - '@esbuild/win32-x64@0.27.7': - optional: true - '@esbuild/win32-x64@0.28.1': optional: true @@ -2390,15 +2085,6 @@ snapshots: outvariant: 1.4.3 strict-event-emitter: 0.5.1 - '@napi-rs/wasm-runtime@1.2.4(@emnapi/core@1.10.0)(@emnapi/runtime@1.10.0)': - dependencies: - '@emnapi/core': 1.10.0 - '@emnapi/runtime': 1.10.0 - '@tybys/wasm-util': 0.10.4 - optional: true - - '@next/env@14.2.35': {} - '@open-draft/deferred-promise@2.2.0': {} '@open-draft/deferred-promise@3.0.0': {} @@ -2626,160 +2312,182 @@ snapshots: '@opentelemetry/semantic-conventions@1.9.0': {} - '@oxc-project/types@0.132.0': {} - - '@protobufjs/aspromise@1.1.2': {} - - '@protobufjs/base64@1.1.2': {} - - '@protobufjs/codegen@2.0.5': {} - - '@protobufjs/eventemitter@1.1.1': {} - - '@protobufjs/fetch@1.1.1': - dependencies: - '@protobufjs/aspromise': 1.1.2 + '@oxc-project/runtime@0.153.0': {} - '@protobufjs/float@1.0.2': {} + '@oxc-project/types@0.153.0': {} - '@protobufjs/path@1.1.2': {} + '@oxfmt/binding-android-arm-eabi@0.72.0': + optional: true - '@protobufjs/pool@1.1.0': {} + '@oxfmt/binding-android-arm64@0.72.0': + optional: true - '@protobufjs/utf8@1.1.1': {} + '@oxfmt/binding-darwin-arm64@0.72.0': + optional: true - '@rolldown/binding-android-arm64@1.0.2': + '@oxfmt/binding-darwin-x64@0.72.0': optional: true - '@rolldown/binding-darwin-arm64@1.0.2': + '@oxfmt/binding-freebsd-x64@0.72.0': optional: true - '@rolldown/binding-darwin-x64@1.0.2': + '@oxfmt/binding-linux-arm-gnueabihf@0.72.0': optional: true - '@rolldown/binding-freebsd-x64@1.0.2': + '@oxfmt/binding-linux-arm-musleabihf@0.72.0': optional: true - '@rolldown/binding-linux-arm-gnueabihf@1.0.2': + '@oxfmt/binding-linux-arm64-gnu@0.72.0': optional: true - '@rolldown/binding-linux-arm64-gnu@1.0.2': + '@oxfmt/binding-linux-arm64-musl@0.72.0': optional: true - '@rolldown/binding-linux-arm64-musl@1.0.2': + '@oxfmt/binding-linux-ppc64-gnu@0.72.0': optional: true - '@rolldown/binding-linux-ppc64-gnu@1.0.2': + '@oxfmt/binding-linux-riscv64-gnu@0.72.0': optional: true - '@rolldown/binding-linux-s390x-gnu@1.0.2': + '@oxfmt/binding-linux-riscv64-musl@0.72.0': optional: true - '@rolldown/binding-linux-x64-gnu@1.0.2': + '@oxfmt/binding-linux-s390x-gnu@0.72.0': optional: true - '@rolldown/binding-linux-x64-musl@1.0.2': + '@oxfmt/binding-linux-x64-gnu@0.72.0': optional: true - '@rolldown/binding-openharmony-arm64@1.0.2': + '@oxfmt/binding-linux-x64-musl@0.72.0': optional: true - '@rolldown/binding-wasm32-wasi@1.0.2': - dependencies: - '@emnapi/core': 1.10.0 - '@emnapi/runtime': 1.10.0 - '@napi-rs/wasm-runtime': 1.2.4(@emnapi/core@1.10.0)(@emnapi/runtime@1.10.0) + '@oxfmt/binding-openharmony-arm64@0.72.0': optional: true - '@rolldown/binding-win32-arm64-msvc@1.0.2': + '@oxfmt/binding-win32-arm64-msvc@0.72.0': optional: true - '@rolldown/binding-win32-x64-msvc@1.0.2': + '@oxfmt/binding-win32-ia32-msvc@0.72.0': optional: true - '@rolldown/pluginutils@1.0.1': {} + '@oxfmt/binding-win32-x64-msvc@0.72.0': + optional: true - '@rollup/rollup-android-arm-eabi@4.60.4': + '@oxlint-tsgolint/darwin-arm64@7.0.2003': optional: true - '@rollup/rollup-android-arm64@4.60.4': + '@oxlint-tsgolint/darwin-x64@7.0.2003': optional: true - '@rollup/rollup-darwin-arm64@4.60.4': + '@oxlint-tsgolint/linux-arm64@7.0.2003': optional: true - '@rollup/rollup-darwin-x64@4.60.4': + '@oxlint-tsgolint/linux-x64@7.0.2003': optional: true - '@rollup/rollup-freebsd-arm64@4.60.4': + '@oxlint-tsgolint/win32-arm64@7.0.2003': optional: true - '@rollup/rollup-freebsd-x64@4.60.4': + '@oxlint-tsgolint/win32-x64@7.0.2003': optional: true - '@rollup/rollup-linux-arm-gnueabihf@4.60.4': + '@oxlint/binding-android-arm-eabi@1.87.0': optional: true - '@rollup/rollup-linux-arm-musleabihf@4.60.4': + '@oxlint/binding-android-arm64@1.87.0': optional: true - '@rollup/rollup-linux-arm64-gnu@4.60.4': + '@oxlint/binding-darwin-arm64@1.87.0': optional: true - '@rollup/rollup-linux-arm64-musl@4.60.4': + '@oxlint/binding-darwin-x64@1.87.0': optional: true - '@rollup/rollup-linux-loong64-gnu@4.60.4': + '@oxlint/binding-freebsd-x64@1.87.0': optional: true - '@rollup/rollup-linux-loong64-musl@4.60.4': + '@oxlint/binding-linux-arm-gnueabihf@1.87.0': optional: true - '@rollup/rollup-linux-ppc64-gnu@4.60.4': + '@oxlint/binding-linux-arm-musleabihf@1.87.0': optional: true - '@rollup/rollup-linux-ppc64-musl@4.60.4': + '@oxlint/binding-linux-arm64-gnu@1.87.0': optional: true - '@rollup/rollup-linux-riscv64-gnu@4.60.4': + '@oxlint/binding-linux-arm64-musl@1.87.0': optional: true - '@rollup/rollup-linux-riscv64-musl@4.60.4': + '@oxlint/binding-linux-ppc64-gnu@1.87.0': optional: true - '@rollup/rollup-linux-s390x-gnu@4.60.4': + '@oxlint/binding-linux-riscv64-gnu@1.87.0': optional: true - '@rollup/rollup-linux-x64-gnu@4.60.4': + '@oxlint/binding-linux-riscv64-musl@1.87.0': optional: true - '@rollup/rollup-linux-x64-musl@4.60.4': + '@oxlint/binding-linux-s390x-gnu@1.87.0': optional: true - '@rollup/rollup-openbsd-x64@4.60.4': + '@oxlint/binding-linux-x64-gnu@1.87.0': optional: true - '@rollup/rollup-openharmony-arm64@4.60.4': + '@oxlint/binding-linux-x64-musl@1.87.0': optional: true - '@rollup/rollup-win32-arm64-msvc@4.60.4': + '@oxlint/binding-openharmony-arm64@1.87.0': optional: true - '@rollup/rollup-win32-ia32-msvc@4.60.4': + '@oxlint/binding-win32-arm64-msvc@1.87.0': optional: true - '@rollup/rollup-win32-x64-gnu@4.60.4': + '@oxlint/binding-win32-ia32-msvc@1.87.0': optional: true - '@rollup/rollup-win32-x64-msvc@4.60.4': + '@oxlint/binding-win32-x64-msvc@1.87.0': optional: true - '@standard-schema/spec@1.1.0': {} + '@oxlint/plugins@1.79.0': {} + + '@polka/url@1.0.0-next.29': {} - '@tybys/wasm-util@0.10.4': + '@protobufjs/aspromise@1.1.2': {} + + '@protobufjs/base64@1.1.2': {} + + '@protobufjs/codegen@2.0.5': {} + + '@protobufjs/eventemitter@1.1.1': {} + + '@protobufjs/fetch@1.1.1': dependencies: - tslib: 2.8.1 - optional: true + '@protobufjs/aspromise': 1.1.2 + + '@protobufjs/float@1.0.2': {} + + '@protobufjs/path@1.1.2': {} + + '@protobufjs/pool@1.1.0': {} + + '@protobufjs/utf8@1.1.1': {} + + '@testing-library/dom@10.4.2': + dependencies: + '@babel/code-frame': 7.29.7 + '@babel/runtime': 7.29.10 + '@types/aria-query': 5.0.4 + aria-query: 5.3.0 + dom-accessibility-api: 0.5.16 + lz-string: 1.5.0 + picocolors: 1.1.1 + pretty-format: 27.5.1 + + '@testing-library/user-event@14.6.8(@testing-library/dom@10.4.2)': + dependencies: + '@testing-library/dom': 10.4.2 + + '@types/aria-query@5.0.4': {} '@types/chai@5.2.3': dependencies: @@ -2788,8 +2496,6 @@ snapshots: '@types/deep-eql@4.0.2': {} - '@types/estree@1.0.8': {} - '@types/estree@1.0.9': {} '@types/node@22.19.19': @@ -2804,52 +2510,194 @@ snapshots: '@vercel/functions@1.6.0': {} - '@vitest/expect@4.1.11': + '@vitest/browser-preview@5.0.3(@voidzero-dev/vite-plus-core@1.1.0(@types/node@22.19.19)(esbuild@0.28.1)(typescript@5.5.4))(msw@2.14.6(@types/node@22.19.19)(typescript@5.5.4))(vitest@5.0.3)': dependencies: - '@standard-schema/spec': 1.1.0 - '@types/chai': 5.2.3 - '@vitest/spy': 4.1.11 - '@vitest/utils': 4.1.11 - chai: 6.2.2 + '@testing-library/dom': 10.4.2 + '@testing-library/user-event': 14.6.8(@testing-library/dom@10.4.2) + '@vitest/browser': 5.0.3(@voidzero-dev/vite-plus-core@1.1.0(@types/node@22.19.19)(esbuild@0.28.1)(typescript@5.5.4))(msw@2.14.6(@types/node@22.19.19)(typescript@5.5.4))(vitest@5.0.3) + vitest: 5.0.3(@opentelemetry/api@1.9.0)(@types/node@22.19.19)(@vitest/browser-preview@5.0.3)(@vitest/ui@5.0.3)(@voidzero-dev/vite-plus-core@1.1.0(@types/node@22.19.19)(esbuild@0.28.1)(typescript@5.5.4))(msw@2.14.6(@types/node@22.19.19)(typescript@5.5.4)) + transitivePeerDependencies: + - bufferutil + - msw + - utf-8-validate + - vite + + '@vitest/browser@5.0.3(@voidzero-dev/vite-plus-core@1.1.0(@types/node@22.19.19)(esbuild@0.28.1)(typescript@5.5.4))(msw@2.14.6(@types/node@22.19.19)(typescript@5.5.4))(vitest@5.0.3)': + dependencies: + '@blazediff/core': 1.10.0 + '@vitest/mocker': 5.0.3(@voidzero-dev/vite-plus-core@1.1.0(@types/node@22.19.19)(esbuild@0.28.1)(typescript@5.5.4))(msw@2.14.6(@types/node@22.19.19)(typescript@5.5.4)) + '@vitest/ui': 5.0.3(vitest@5.0.3) + '@vitest/utils': 5.0.3 + magic-string: 1.4.3 + pngjs: 7.0.0 + sirv: 3.0.2 + tinyrainbow: 3.1.1 + vitest: 5.0.3(@opentelemetry/api@1.9.0)(@types/node@22.19.19)(@vitest/browser-preview@5.0.3)(@vitest/ui@5.0.3)(@voidzero-dev/vite-plus-core@1.1.0(@types/node@22.19.19)(esbuild@0.28.1)(typescript@5.5.4))(msw@2.14.6(@types/node@22.19.19)(typescript@5.5.4)) + ws: 8.22.0 + transitivePeerDependencies: + - bufferutil + - msw + - utf-8-validate + - vite + + '@vitest/mocker@5.0.3(@voidzero-dev/vite-plus-core@1.1.0(@types/node@22.19.19)(esbuild@0.28.1)(typescript@5.5.4))(msw@2.14.6(@types/node@22.19.19)(typescript@5.5.4))': + dependencies: + '@jridgewell/trace-mapping': 0.3.31 + '@vitest/spy': 5.0.3 + estree-walker: 3.0.3 + magic-string: 1.4.3 + optionalDependencies: + msw: 2.14.6(@types/node@22.19.19)(typescript@5.5.4) + vite: '@voidzero-dev/vite-plus-core@1.1.0(@types/node@22.19.19)(esbuild@0.28.1)(typescript@5.5.4)' + + '@vitest/pretty-format@5.0.3': + dependencies: + tinyrainbow: 3.1.1 + + '@vitest/snapshot@5.0.3': + dependencies: + '@vitest/pretty-format': 5.0.3 + '@vitest/utils': 5.0.3 + magic-string: 1.4.3 + pathe: 2.0.3 + + '@vitest/spy@5.0.3': {} + + '@vitest/ui@5.0.3(vitest@5.0.3)': + dependencies: + '@vitest/utils': 5.0.3 + fflate: 0.8.3 + flatted: 3.4.4 + pathe: 2.0.3 + sirv: 3.0.2 + tinyrainbow: 3.1.1 + vitest: 5.0.3(@opentelemetry/api@1.9.0)(@types/node@22.19.19)(@vitest/browser-preview@5.0.3)(@vitest/ui@5.0.3)(@voidzero-dev/vite-plus-core@1.1.0(@types/node@22.19.19)(esbuild@0.28.1)(typescript@5.5.4))(msw@2.14.6(@types/node@22.19.19)(typescript@5.5.4)) + + '@vitest/utils@5.0.3': + dependencies: + '@vitest/pretty-format': 5.0.3 + convert-source-map: 2.0.0 tinyrainbow: 3.1.1 - '@vitest/mocker@4.1.11(msw@2.14.6(@types/node@22.19.19)(typescript@5.5.4))(vite@8.0.14(@types/node@22.19.19)(esbuild@0.27.7))': - dependencies: - '@vitest/spy': 4.1.11 - estree-walker: 3.0.3 - magic-string: 0.30.21 - optionalDependencies: - msw: 2.14.6(@types/node@22.19.19)(typescript@5.5.4) - vite: 8.0.14(@types/node@22.19.19)(esbuild@0.27.7) + '@voidzero-dev/vite-plus-core@1.1.0(@types/node@22.19.19)(esbuild@0.28.1)(typescript@5.5.4)': + dependencies: + '@oxc-project/runtime': 0.153.0 + '@oxc-project/types': 0.153.0 + lightningcss: 1.33.0 + postcss: 8.5.28 + yuku-codegen: 0.9.5 + yuku-parser: 0.9.5 + optionalDependencies: + '@types/node': 22.19.19 + '@voidzero-dev/vite-plus-darwin-arm64': 1.1.0 + '@voidzero-dev/vite-plus-darwin-x64': 1.1.0 + '@voidzero-dev/vite-plus-linux-arm64-gnu': 1.1.0 + '@voidzero-dev/vite-plus-linux-arm64-musl': 1.1.0 + '@voidzero-dev/vite-plus-linux-x64-gnu': 1.1.0 + '@voidzero-dev/vite-plus-linux-x64-musl': 1.1.0 + '@voidzero-dev/vite-plus-win32-arm64-msvc': 1.1.0 + '@voidzero-dev/vite-plus-win32-x64-msvc': 1.1.0 + esbuild: 0.28.1 + fsevents: 2.3.3 + typescript: 5.5.4 + + '@voidzero-dev/vite-plus-darwin-arm64@1.1.0': + optional: true + + '@voidzero-dev/vite-plus-darwin-x64@1.1.0': + optional: true + + '@voidzero-dev/vite-plus-linux-arm64-gnu@1.1.0': + optional: true + + '@voidzero-dev/vite-plus-linux-arm64-musl@1.1.0': + optional: true + + '@voidzero-dev/vite-plus-linux-x64-gnu@1.1.0': + optional: true + + '@voidzero-dev/vite-plus-linux-x64-musl@1.1.0': + optional: true + + '@voidzero-dev/vite-plus-win32-arm64-msvc@1.1.0': + optional: true + + '@voidzero-dev/vite-plus-win32-x64-msvc@1.1.0': + optional: true + + '@yuku-codegen/binding-android-arm64@0.9.5': + optional: true + + '@yuku-codegen/binding-darwin-arm64@0.9.5': + optional: true + + '@yuku-codegen/binding-darwin-x64@0.9.5': + optional: true + + '@yuku-codegen/binding-freebsd-x64@0.9.5': + optional: true + + '@yuku-codegen/binding-linux-arm-gnu@0.9.5': + optional: true + + '@yuku-codegen/binding-linux-arm-musl@0.9.5': + optional: true + + '@yuku-codegen/binding-linux-arm64-gnu@0.9.5': + optional: true + + '@yuku-codegen/binding-linux-arm64-musl@0.9.5': + optional: true + + '@yuku-codegen/binding-linux-x64-gnu@0.9.5': + optional: true + + '@yuku-codegen/binding-linux-x64-musl@0.9.5': + optional: true + + '@yuku-codegen/binding-win32-arm64@0.9.5': + optional: true + + '@yuku-codegen/binding-win32-x64@0.9.5': + optional: true + + '@yuku-parser/binding-android-arm64@0.9.5': + optional: true + + '@yuku-parser/binding-darwin-arm64@0.9.5': + optional: true + + '@yuku-parser/binding-darwin-x64@0.9.5': + optional: true + + '@yuku-parser/binding-freebsd-x64@0.9.5': + optional: true + + '@yuku-parser/binding-linux-arm-gnu@0.9.5': + optional: true + + '@yuku-parser/binding-linux-arm-musl@0.9.5': + optional: true - '@vitest/pretty-format@4.1.11': - dependencies: - tinyrainbow: 3.1.1 + '@yuku-parser/binding-linux-arm64-gnu@0.9.5': + optional: true - '@vitest/runner@4.1.11': - dependencies: - '@vitest/utils': 4.1.11 - pathe: 2.0.3 + '@yuku-parser/binding-linux-arm64-musl@0.9.5': + optional: true - '@vitest/snapshot@4.1.11': - dependencies: - '@vitest/pretty-format': 4.1.11 - '@vitest/utils': 4.1.11 - magic-string: 0.30.21 - pathe: 2.0.3 + '@yuku-parser/binding-linux-x64-gnu@0.9.5': + optional: true - '@vitest/spy@4.1.11': {} + '@yuku-parser/binding-linux-x64-musl@0.9.5': + optional: true - '@vitest/utils@4.1.11': - dependencies: - '@vitest/pretty-format': 4.1.11 - convert-source-map: 2.0.0 - tinyrainbow: 3.1.1 + '@yuku-parser/binding-win32-arm64@0.9.5': + optional: true - accepts@2.0.0: - dependencies: - mime-types: 3.0.2 - negotiator: 1.0.0 + '@yuku-parser/binding-win32-x64@0.9.5': + optional: true + + '@yuku-toolchain/types@0.9.5': {} acorn-import-attributes@1.9.5(acorn@8.16.0): dependencies: @@ -2860,7 +2708,7 @@ snapshots: ajv@8.20.0: dependencies: fast-deep-equal: 3.1.3 - fast-uri: 3.1.8 + fast-uri: 3.1.9 json-schema-traverse: 1.0.0 require-from-string: 2.0.2 @@ -2872,76 +2720,38 @@ snapshots: dependencies: color-convert: 2.0.1 - any-promise@1.3.0: {} + ansi-styles@5.2.0: {} - argparse@2.0.1: {} + aria-query@5.3.0: + dependencies: + dequal: 2.0.3 assertion-error@2.0.1: {} astring@1.9.0: {} - balanced-match@4.0.4: {} - - body-parser@2.3.0: - dependencies: - bytes: 3.1.2 - content-type: 2.0.0 - debug: 4.4.3 - http-errors: 2.0.1 - iconv-lite: 0.7.2 - on-finished: 2.4.1 - qs: 6.15.2 - raw-body: 3.0.2 - type-is: 2.1.0 - transitivePeerDependencies: - - supports-color - - brace-expansion@5.0.12: - dependencies: - balanced-match: 4.0.4 - braintrust@file:../../../js(zod@4.4.3): dependencies: - '@next/env': 14.2.35 '@vercel/functions': 1.6.0 acorn: 8.16.0 acorn-import-attributes: 1.9.5(acorn@8.16.0) ajv: 8.20.0 - argparse: 2.0.1 astring: 1.9.0 cjs-module-lexer: 2.2.0 - cli-progress: 3.12.0 - cli-table3: 0.6.5 - cors: 2.8.6 dotenv: 16.6.1 - esbuild: 0.28.1 esquery: 1.7.0 eventsource-parser: 1.1.2 - express: 5.2.1 - http-errors: 2.0.1 meriyah: 6.1.4 - minimatch: 10.2.5 module-details-from-path: 1.0.4 mustache: 4.2.0 - pluralize: 8.0.0 semifies: 1.0.0 source-map: 0.7.6 - termi-link: 1.1.0 unplugin: 2.3.11 uuid: 11.1.1 zod: 4.4.3 zod-to-json-schema: 3.25.2(zod@4.4.3) - optionalDependencies: - '@braintrust/bt-darwin-arm64': 0.12.0 - '@braintrust/bt-darwin-x64': 0.12.0 - '@braintrust/bt-linux-arm64': 0.12.0 - '@braintrust/bt-linux-x64': 0.12.0 - '@braintrust/bt-linux-x64-musl': 0.12.0 - '@braintrust/bt-win32-arm64': 0.12.0 - '@braintrust/bt-win32-x64': 0.12.0 transitivePeerDependencies: - '@aws-sdk/credential-provider-web-identity' - - supports-color bufrw@1.4.0: dependencies: @@ -2950,43 +2760,10 @@ snapshots: hexer: 1.5.0 xtend: 4.0.2 - bundle-require@5.1.0(esbuild@0.27.7): - dependencies: - esbuild: 0.27.7 - load-tsconfig: 0.2.5 - - bytes@3.1.2: {} - - cac@6.7.14: {} - - call-bind-apply-helpers@1.0.2: - dependencies: - es-errors: 1.3.0 - function-bind: 1.1.2 - - call-bound@1.0.4: - dependencies: - call-bind-apply-helpers: 1.0.2 - get-intrinsic: 1.3.0 - chai@6.2.2: {} - chokidar@4.0.3: - dependencies: - readdirp: 4.1.2 - cjs-module-lexer@2.2.0: {} - cli-progress@3.12.0: - dependencies: - string-width: 4.2.3 - - cli-table3@0.6.5: - dependencies: - string-width: 4.2.3 - optionalDependencies: - '@colors/colors': 1.5.0 - cli-width@4.1.0: {} cliui@8.0.1: @@ -3001,97 +2778,33 @@ snapshots: color-name@1.1.4: {} - commander@4.1.1: {} - - confbox@0.1.8: {} - - consola@3.4.2: {} - - content-disposition@1.1.0: {} - - content-type@1.0.5: {} - - content-type@2.0.0: {} - convert-source-map@2.0.0: {} - cookie-signature@1.2.2: {} - - cookie@0.7.2: {} - cookie@1.1.1: {} - cors@2.8.6: - dependencies: - object-assign: 4.1.1 - vary: 1.1.2 - debug@4.4.3: dependencies: ms: 2.1.3 - depd@2.0.0: {} + dequal@2.0.3: {} detect-libc@2.1.2: {} - dotenv@16.6.1: {} - - dunder-proto@1.0.1: - dependencies: - call-bind-apply-helpers: 1.0.2 - es-errors: 1.3.0 - gopd: 1.2.0 + dom-accessibility-api@0.5.16: {} - ee-first@1.1.1: {} + dotenv@16.6.1: {} emoji-regex@8.0.0: {} - encodeurl@2.0.0: {} - error@7.0.2: dependencies: string-template: 0.2.1 xtend: 4.0.2 - es-define-property@1.0.1: {} - es-errors@1.3.0: {} es-module-lexer@2.3.2: {} - es-object-atoms@1.1.2: - dependencies: - es-errors: 1.3.0 - - esbuild@0.27.7: - optionalDependencies: - '@esbuild/aix-ppc64': 0.27.7 - '@esbuild/android-arm': 0.27.7 - '@esbuild/android-arm64': 0.27.7 - '@esbuild/android-x64': 0.27.7 - '@esbuild/darwin-arm64': 0.27.7 - '@esbuild/darwin-x64': 0.27.7 - '@esbuild/freebsd-arm64': 0.27.7 - '@esbuild/freebsd-x64': 0.27.7 - '@esbuild/linux-arm': 0.27.7 - '@esbuild/linux-arm64': 0.27.7 - '@esbuild/linux-ia32': 0.27.7 - '@esbuild/linux-loong64': 0.27.7 - '@esbuild/linux-mips64el': 0.27.7 - '@esbuild/linux-ppc64': 0.27.7 - '@esbuild/linux-riscv64': 0.27.7 - '@esbuild/linux-s390x': 0.27.7 - '@esbuild/linux-x64': 0.27.7 - '@esbuild/netbsd-arm64': 0.27.7 - '@esbuild/netbsd-x64': 0.27.7 - '@esbuild/openbsd-arm64': 0.27.7 - '@esbuild/openbsd-x64': 0.27.7 - '@esbuild/openharmony-arm64': 0.27.7 - '@esbuild/sunos-x64': 0.27.7 - '@esbuild/win32-arm64': 0.27.7 - '@esbuild/win32-ia32': 0.27.7 - '@esbuild/win32-x64': 0.27.7 - esbuild@0.28.1: optionalDependencies: '@esbuild/aix-ppc64': 0.28.1 @@ -3120,11 +2833,10 @@ snapshots: '@esbuild/win32-arm64': 0.28.1 '@esbuild/win32-ia32': 0.28.1 '@esbuild/win32-x64': 0.28.1 + optional: true escalade@3.2.0: {} - escape-html@1.0.3: {} - esquery@1.7.0: dependencies: estraverse: 5.3.0 @@ -3135,45 +2847,10 @@ snapshots: dependencies: '@types/estree': 1.0.9 - etag@1.8.1: {} - eventsource-parser@1.1.2: {} expect-type@1.4.0: {} - express@5.2.1: - dependencies: - accepts: 2.0.0 - body-parser: 2.3.0 - content-disposition: 1.1.0 - content-type: 1.0.5 - cookie: 0.7.2 - cookie-signature: 1.2.2 - debug: 4.4.3 - depd: 2.0.0 - encodeurl: 2.0.0 - escape-html: 1.0.3 - etag: 1.8.1 - finalhandler: 2.1.1 - fresh: 2.0.0 - http-errors: 2.0.1 - merge-descriptors: 2.0.0 - mime-types: 3.0.2 - on-finished: 2.4.1 - once: 1.4.0 - parseurl: 1.3.3 - proxy-addr: 2.0.8 - qs: 6.15.2 - range-parser: 1.2.1 - router: 2.2.0 - send: 1.2.1 - serve-static: 2.2.1 - statuses: 2.0.2 - type-is: 2.1.0 - vary: 1.1.2 - transitivePeerDependencies: - - supports-color - fast-deep-equal@3.1.3: {} fast-string-truncated-width@3.0.3: {} @@ -3182,7 +2859,7 @@ snapshots: dependencies: fast-string-truncated-width: 3.0.3 - fast-uri@3.1.8: {} + fast-uri@3.1.9: {} fast-wrap-ansi@0.2.2: dependencies: @@ -3192,26 +2869,9 @@ snapshots: optionalDependencies: picomatch: 4.0.7 - finalhandler@2.1.1: - dependencies: - debug: 4.4.3 - encodeurl: 2.0.0 - escape-html: 1.0.3 - on-finished: 2.4.1 - parseurl: 1.3.3 - statuses: 2.0.2 - transitivePeerDependencies: - - supports-color - - fix-dts-default-cjs-exports@1.0.1: - dependencies: - magic-string: 0.30.21 - mlly: 1.8.2 - rollup: 4.60.4 - - forwarded@0.2.0: {} + fflate@0.8.3: {} - fresh@2.0.0: {} + flatted@3.4.4: {} fsevents@2.3.3: optional: true @@ -3220,30 +2880,8 @@ snapshots: get-caller-file@2.0.5: {} - get-intrinsic@1.3.0: - dependencies: - call-bind-apply-helpers: 1.0.2 - es-define-property: 1.0.1 - es-errors: 1.3.0 - es-object-atoms: 1.1.2 - function-bind: 1.1.2 - get-proto: 1.0.1 - gopd: 1.2.0 - has-symbols: 1.1.0 - hasown: 2.0.3 - math-intrinsics: 1.1.0 - - get-proto@1.0.1: - dependencies: - dunder-proto: 1.0.1 - es-object-atoms: 1.1.2 - - gopd@1.2.0: {} - graphql@16.14.0: {} - has-symbols@1.1.0: {} - hasown@2.0.3: dependencies: function-bind: 1.1.2 @@ -3260,22 +2898,6 @@ snapshots: process: 0.10.1 xtend: 4.0.2 - http-errors@2.0.1: - dependencies: - depd: 2.0.0 - inherits: 2.0.4 - setprototypeof: 1.2.0 - statuses: 2.0.2 - toidentifier: 1.0.1 - - iconv-lite@0.7.2: - dependencies: - safer-buffer: 2.1.2 - - inherits@2.0.4: {} - - ipaddr.js@1.9.1: {} - is-core-module@2.16.2: dependencies: hasown: 2.0.3 @@ -3284,8 +2906,6 @@ snapshots: is-node-process@1.2.0: {} - is-promise@4.0.0: {} - jaeger-client@3.19.0: dependencies: node-int64: 0.4.0 @@ -3294,7 +2914,7 @@ snapshots: uuid: 11.1.1 xorshift: 1.2.0 - joycon@3.1.1: {} + js-tokens@4.0.0: {} json-schema-traverse@1.0.0: {} @@ -3347,12 +2967,6 @@ snapshots: lightningcss-win32-arm64-msvc: 1.33.0 lightningcss-win32-x64-msvc: 1.33.0 - lilconfig@3.1.3: {} - - lines-and-columns@1.2.4: {} - - load-tsconfig@0.2.5: {} - lodash.camelcase@4.3.0: {} lodash.merge@4.6.2: {} @@ -3361,39 +2975,20 @@ snapshots: long@5.3.2: {} - magic-string@0.30.21: + lz-string@1.5.0: {} + + magic-string@1.4.3: dependencies: '@jridgewell/sourcemap-codec': 1.6.0 - math-intrinsics@1.1.0: {} - - media-typer@1.1.0: {} - - merge-descriptors@2.0.0: {} - meriyah@6.1.4: {} - mime-db@1.54.0: {} - - mime-types@3.0.2: - dependencies: - mime-db: 1.54.0 - - minimatch@10.2.5: - dependencies: - brace-expansion: 5.0.12 - minimist@1.2.8: {} - mlly@1.8.2: - dependencies: - acorn: 8.16.0 - pathe: 2.0.3 - pkg-types: 1.3.1 - ufo: 1.6.4 - module-details-from-path@1.0.4: {} + mrmime@2.0.1: {} + ms@2.1.3: {} msw@2.14.6(@types/node@22.19.19)(typescript@5.5.4): @@ -3425,65 +3020,85 @@ snapshots: mute-stream@4.0.0: {} - mz@2.7.0: - dependencies: - any-promise: 1.3.0 - object-assign: 4.1.1 - thenify-all: 1.6.0 - nanoid@3.3.19: {} - negotiator@1.0.0: {} - node-int64@0.4.0: {} - object-assign@4.1.1: {} - - object-inspect@1.13.4: {} - obug@2.2.1: {} - on-finished@2.4.1: - dependencies: - ee-first: 1.1.1 - - once@1.4.0: - dependencies: - wrappy: 1.0.2 - opentracing@0.14.7: {} outvariant@1.4.3: {} - parseurl@1.3.3: {} + oxfmt@0.72.0(vite-plus@1.1.0(@opentelemetry/api@1.9.0)(@types/node@22.19.19)(@vitest/ui@5.0.3(vitest@5.0.3))(esbuild@0.28.1)(msw@2.14.6(@types/node@22.19.19)(typescript@5.5.4))(typescript@5.5.4)): + dependencies: + tinypool: 2.2.0 + optionalDependencies: + '@oxfmt/binding-android-arm-eabi': 0.72.0 + '@oxfmt/binding-android-arm64': 0.72.0 + '@oxfmt/binding-darwin-arm64': 0.72.0 + '@oxfmt/binding-darwin-x64': 0.72.0 + '@oxfmt/binding-freebsd-x64': 0.72.0 + '@oxfmt/binding-linux-arm-gnueabihf': 0.72.0 + '@oxfmt/binding-linux-arm-musleabihf': 0.72.0 + '@oxfmt/binding-linux-arm64-gnu': 0.72.0 + '@oxfmt/binding-linux-arm64-musl': 0.72.0 + '@oxfmt/binding-linux-ppc64-gnu': 0.72.0 + '@oxfmt/binding-linux-riscv64-gnu': 0.72.0 + '@oxfmt/binding-linux-riscv64-musl': 0.72.0 + '@oxfmt/binding-linux-s390x-gnu': 0.72.0 + '@oxfmt/binding-linux-x64-gnu': 0.72.0 + '@oxfmt/binding-linux-x64-musl': 0.72.0 + '@oxfmt/binding-openharmony-arm64': 0.72.0 + '@oxfmt/binding-win32-arm64-msvc': 0.72.0 + '@oxfmt/binding-win32-ia32-msvc': 0.72.0 + '@oxfmt/binding-win32-x64-msvc': 0.72.0 + vite-plus: 1.1.0(@opentelemetry/api@1.9.0)(@types/node@22.19.19)(@vitest/ui@5.0.3(vitest@5.0.3))(esbuild@0.28.1)(msw@2.14.6(@types/node@22.19.19)(typescript@5.5.4))(typescript@5.5.4) + + oxlint-tsgolint@7.0.2003: + optionalDependencies: + '@oxlint-tsgolint/darwin-arm64': 7.0.2003 + '@oxlint-tsgolint/darwin-x64': 7.0.2003 + '@oxlint-tsgolint/linux-arm64': 7.0.2003 + '@oxlint-tsgolint/linux-x64': 7.0.2003 + '@oxlint-tsgolint/win32-arm64': 7.0.2003 + '@oxlint-tsgolint/win32-x64': 7.0.2003 + + oxlint@1.87.0(oxlint-tsgolint@7.0.2003)(vite-plus@1.1.0(@opentelemetry/api@1.9.0)(@types/node@22.19.19)(@vitest/ui@5.0.3(vitest@5.0.3))(esbuild@0.28.1)(msw@2.14.6(@types/node@22.19.19)(typescript@5.5.4))(typescript@5.5.4)): + optionalDependencies: + '@oxlint/binding-android-arm-eabi': 1.87.0 + '@oxlint/binding-android-arm64': 1.87.0 + '@oxlint/binding-darwin-arm64': 1.87.0 + '@oxlint/binding-darwin-x64': 1.87.0 + '@oxlint/binding-freebsd-x64': 1.87.0 + '@oxlint/binding-linux-arm-gnueabihf': 1.87.0 + '@oxlint/binding-linux-arm-musleabihf': 1.87.0 + '@oxlint/binding-linux-arm64-gnu': 1.87.0 + '@oxlint/binding-linux-arm64-musl': 1.87.0 + '@oxlint/binding-linux-ppc64-gnu': 1.87.0 + '@oxlint/binding-linux-riscv64-gnu': 1.87.0 + '@oxlint/binding-linux-riscv64-musl': 1.87.0 + '@oxlint/binding-linux-s390x-gnu': 1.87.0 + '@oxlint/binding-linux-x64-gnu': 1.87.0 + '@oxlint/binding-linux-x64-musl': 1.87.0 + '@oxlint/binding-openharmony-arm64': 1.87.0 + '@oxlint/binding-win32-arm64-msvc': 1.87.0 + '@oxlint/binding-win32-ia32-msvc': 1.87.0 + '@oxlint/binding-win32-x64-msvc': 1.87.0 + oxlint-tsgolint: 7.0.2003 + vite-plus: 1.1.0(@opentelemetry/api@1.9.0)(@types/node@22.19.19)(@vitest/ui@5.0.3(vitest@5.0.3))(esbuild@0.28.1)(msw@2.14.6(@types/node@22.19.19)(typescript@5.5.4))(typescript@5.5.4) path-parse@1.0.7: {} path-to-regexp@6.3.0: {} - path-to-regexp@8.4.2: {} - pathe@2.0.3: {} picocolors@1.1.1: {} picomatch@4.0.7: {} - pirates@4.0.7: {} - - pkg-types@1.3.1: - dependencies: - confbox: 0.1.8 - mlly: 1.8.2 - pathe: 2.0.3 - - pluralize@8.0.0: {} - - postcss-load-config@6.0.1(postcss@8.5.28): - dependencies: - lilconfig: 3.1.3 - optionalDependencies: - postcss: 8.5.28 + pngjs@7.0.0: {} postcss@8.5.28: dependencies: @@ -3491,6 +3106,12 @@ snapshots: picocolors: 1.1.1 source-map-js: 1.2.2 + pretty-format@27.5.1: + dependencies: + ansi-regex: 5.0.1 + ansi-styles: 5.2.0 + react-is: 17.0.2 + process@0.10.1: {} protobufjs@7.6.5: @@ -3507,25 +3128,7 @@ snapshots: '@types/node': 22.19.19 long: 5.3.2 - proxy-addr@2.0.8: - dependencies: - forwarded: 0.2.0 - ipaddr.js: 1.9.1 - - qs@6.15.2: - dependencies: - side-channel: 1.1.0 - - range-parser@1.2.1: {} - - raw-body@3.0.2: - dependencies: - bytes: 3.1.2 - http-errors: 2.0.1 - iconv-lite: 0.7.2 - unpipe: 1.0.0 - - readdirp@4.1.2: {} + react-is@17.0.2: {} require-directory@2.1.1: {} @@ -3539,8 +3142,6 @@ snapshots: transitivePeerDependencies: - supports-color - resolve-from@5.0.0: {} - resolve@1.22.12: dependencies: es-errors: 1.3.0 @@ -3550,143 +3151,26 @@ snapshots: rettime@0.11.11: {} - rolldown@1.0.2: - dependencies: - '@oxc-project/types': 0.132.0 - '@rolldown/pluginutils': 1.0.1 - optionalDependencies: - '@rolldown/binding-android-arm64': 1.0.2 - '@rolldown/binding-darwin-arm64': 1.0.2 - '@rolldown/binding-darwin-x64': 1.0.2 - '@rolldown/binding-freebsd-x64': 1.0.2 - '@rolldown/binding-linux-arm-gnueabihf': 1.0.2 - '@rolldown/binding-linux-arm64-gnu': 1.0.2 - '@rolldown/binding-linux-arm64-musl': 1.0.2 - '@rolldown/binding-linux-ppc64-gnu': 1.0.2 - '@rolldown/binding-linux-s390x-gnu': 1.0.2 - '@rolldown/binding-linux-x64-gnu': 1.0.2 - '@rolldown/binding-linux-x64-musl': 1.0.2 - '@rolldown/binding-openharmony-arm64': 1.0.2 - '@rolldown/binding-wasm32-wasi': 1.0.2 - '@rolldown/binding-win32-arm64-msvc': 1.0.2 - '@rolldown/binding-win32-x64-msvc': 1.0.2 - - rollup@4.60.4: - dependencies: - '@types/estree': 1.0.8 - optionalDependencies: - '@rollup/rollup-android-arm-eabi': 4.60.4 - '@rollup/rollup-android-arm64': 4.60.4 - '@rollup/rollup-darwin-arm64': 4.60.4 - '@rollup/rollup-darwin-x64': 4.60.4 - '@rollup/rollup-freebsd-arm64': 4.60.4 - '@rollup/rollup-freebsd-x64': 4.60.4 - '@rollup/rollup-linux-arm-gnueabihf': 4.60.4 - '@rollup/rollup-linux-arm-musleabihf': 4.60.4 - '@rollup/rollup-linux-arm64-gnu': 4.60.4 - '@rollup/rollup-linux-arm64-musl': 4.60.4 - '@rollup/rollup-linux-loong64-gnu': 4.60.4 - '@rollup/rollup-linux-loong64-musl': 4.60.4 - '@rollup/rollup-linux-ppc64-gnu': 4.60.4 - '@rollup/rollup-linux-ppc64-musl': 4.60.4 - '@rollup/rollup-linux-riscv64-gnu': 4.60.4 - '@rollup/rollup-linux-riscv64-musl': 4.60.4 - '@rollup/rollup-linux-s390x-gnu': 4.60.4 - '@rollup/rollup-linux-x64-gnu': 4.60.4 - '@rollup/rollup-linux-x64-musl': 4.60.4 - '@rollup/rollup-openbsd-x64': 4.60.4 - '@rollup/rollup-openharmony-arm64': 4.60.4 - '@rollup/rollup-win32-arm64-msvc': 4.60.4 - '@rollup/rollup-win32-ia32-msvc': 4.60.4 - '@rollup/rollup-win32-x64-gnu': 4.60.4 - '@rollup/rollup-win32-x64-msvc': 4.60.4 - fsevents: 2.3.3 - - router@2.2.0: - dependencies: - debug: 4.4.3 - depd: 2.0.0 - is-promise: 4.0.0 - parseurl: 1.3.3 - path-to-regexp: 8.4.2 - transitivePeerDependencies: - - supports-color - - safer-buffer@2.1.2: {} - semifies@1.0.0: {} semver@7.8.1: {} - send@1.2.1: - dependencies: - debug: 4.4.3 - encodeurl: 2.0.0 - escape-html: 1.0.3 - etag: 1.8.1 - fresh: 2.0.0 - http-errors: 2.0.1 - mime-types: 3.0.2 - ms: 2.1.3 - on-finished: 2.4.1 - range-parser: 1.2.1 - statuses: 2.0.2 - transitivePeerDependencies: - - supports-color - - serve-static@2.2.1: - dependencies: - encodeurl: 2.0.0 - escape-html: 1.0.3 - parseurl: 1.3.3 - send: 1.2.1 - transitivePeerDependencies: - - supports-color - set-cookie-parser@3.1.0: {} - setprototypeof@1.2.0: {} - shimmer@1.2.1: {} - side-channel-list@1.0.1: - dependencies: - es-errors: 1.3.0 - object-inspect: 1.13.4 - - side-channel-map@1.0.1: - dependencies: - call-bound: 1.0.4 - es-errors: 1.3.0 - get-intrinsic: 1.3.0 - object-inspect: 1.13.4 - - side-channel-weakmap@1.0.2: - dependencies: - call-bound: 1.0.4 - es-errors: 1.3.0 - get-intrinsic: 1.3.0 - object-inspect: 1.13.4 - side-channel-map: 1.0.1 + signal-exit@4.1.0: {} - side-channel@1.1.0: + sirv@3.0.2: dependencies: - es-errors: 1.3.0 - object-inspect: 1.13.4 - side-channel-list: 1.0.1 - side-channel-map: 1.0.1 - side-channel-weakmap: 1.0.2 - - siginfo@2.0.0: {} - - signal-exit@4.1.0: {} + '@polka/url': 1.0.0-next.29 + mrmime: 2.0.1 + totalist: 3.0.1 source-map-js@1.2.2: {} source-map@0.7.6: {} - stackback@0.0.2: {} - statuses@2.0.2: {} std-env@4.2.0: {} @@ -3705,52 +3189,27 @@ snapshots: dependencies: ansi-regex: 5.0.1 - sucrase@3.35.1: - dependencies: - '@jridgewell/gen-mapping': 0.3.13 - commander: 4.1.1 - lines-and-columns: 1.2.4 - mz: 2.7.0 - pirates: 4.0.7 - tinyglobby: 0.2.16 - ts-interface-checker: 0.1.13 - supports-preserve-symlinks-flag@1.0.0: {} tagged-tag@1.0.0: {} - termi-link@1.1.0: {} - - thenify-all@1.6.0: - dependencies: - thenify: 3.3.1 - - thenify@3.3.1: - dependencies: - any-promise: 1.3.0 - thriftrw@3.11.4: dependencies: bufrw: 1.4.0 error: 7.0.2 long: 2.4.0 - tinybench@2.9.0: {} - - tinyexec@0.3.2: {} + tinybench@6.2.2: {} tinyexec@1.3.1: {} - tinyglobby@0.2.16: - dependencies: - fdir: 6.5.0(picomatch@4.0.7) - picomatch: 4.0.7 - tinyglobby@0.2.17: dependencies: fdir: 6.5.0(picomatch@4.0.7) picomatch: 4.0.7 + tinypool@2.2.0: {} + tinyrainbow@3.1.1: {} tldts-core@7.4.0: {} @@ -3759,65 +3218,20 @@ snapshots: dependencies: tldts-core: 7.4.0 - toidentifier@1.0.1: {} + totalist@3.0.1: {} tough-cookie@6.0.1: dependencies: tldts: 7.4.0 - tree-kill@1.2.2: {} - - ts-interface-checker@0.1.13: {} - - tslib@2.8.1: - optional: true - - tsup@8.5.1(postcss@8.5.28)(typescript@5.5.4): - dependencies: - bundle-require: 5.1.0(esbuild@0.27.7) - cac: 6.7.14 - chokidar: 4.0.3 - consola: 3.4.2 - debug: 4.4.3 - esbuild: 0.27.7 - fix-dts-default-cjs-exports: 1.0.1 - joycon: 3.1.1 - picocolors: 1.1.1 - postcss-load-config: 6.0.1(postcss@8.5.28) - resolve-from: 5.0.0 - rollup: 4.60.4 - source-map: 0.7.6 - sucrase: 3.35.1 - tinyexec: 0.3.2 - tinyglobby: 0.2.16 - tree-kill: 1.2.2 - optionalDependencies: - postcss: 8.5.28 - typescript: 5.5.4 - transitivePeerDependencies: - - jiti - - supports-color - - tsx - - yaml - type-fest@5.6.0: dependencies: tagged-tag: 1.0.0 - type-is@2.1.0: - dependencies: - content-type: 2.0.0 - media-typer: 1.1.0 - mime-types: 3.0.2 - typescript@5.5.4: {} - ufo@1.6.4: {} - undici-types@6.21.0: {} - unpipe@1.0.0: {} - unplugin@2.3.11: dependencies: '@jridgewell/remapping': 2.3.5 @@ -3829,54 +3243,92 @@ snapshots: uuid@11.1.1: {} - vary@1.1.2: {} - - vite@8.0.14(@types/node@22.19.19)(esbuild@0.27.7): - dependencies: - lightningcss: 1.33.0 - picomatch: 4.0.7 - postcss: 8.5.28 - rolldown: 1.0.2 - tinyglobby: 0.2.17 + vite-plus@1.1.0(@opentelemetry/api@1.9.0)(@types/node@22.19.19)(@vitest/ui@5.0.3(vitest@5.0.3))(esbuild@0.28.1)(msw@2.14.6(@types/node@22.19.19)(typescript@5.5.4))(typescript@5.5.4): + dependencies: + '@oxc-project/types': 0.153.0 + '@oxlint/plugins': 1.79.0 + '@vitest/browser': 5.0.3(@voidzero-dev/vite-plus-core@1.1.0(@types/node@22.19.19)(esbuild@0.28.1)(typescript@5.5.4))(msw@2.14.6(@types/node@22.19.19)(typescript@5.5.4))(vitest@5.0.3) + '@vitest/browser-preview': 5.0.3(@voidzero-dev/vite-plus-core@1.1.0(@types/node@22.19.19)(esbuild@0.28.1)(typescript@5.5.4))(msw@2.14.6(@types/node@22.19.19)(typescript@5.5.4))(vitest@5.0.3) + '@vitest/mocker': 5.0.3(@voidzero-dev/vite-plus-core@1.1.0(@types/node@22.19.19)(esbuild@0.28.1)(typescript@5.5.4))(msw@2.14.6(@types/node@22.19.19)(typescript@5.5.4)) + '@vitest/pretty-format': 5.0.3 + '@vitest/snapshot': 5.0.3 + '@vitest/spy': 5.0.3 + '@vitest/utils': 5.0.3 + oxfmt: 0.72.0(vite-plus@1.1.0(@opentelemetry/api@1.9.0)(@types/node@22.19.19)(@vitest/ui@5.0.3(vitest@5.0.3))(esbuild@0.28.1)(msw@2.14.6(@types/node@22.19.19)(typescript@5.5.4))(typescript@5.5.4)) + oxlint: 1.87.0(oxlint-tsgolint@7.0.2003)(vite-plus@1.1.0(@opentelemetry/api@1.9.0)(@types/node@22.19.19)(@vitest/ui@5.0.3(vitest@5.0.3))(esbuild@0.28.1)(msw@2.14.6(@types/node@22.19.19)(typescript@5.5.4))(typescript@5.5.4)) + oxlint-tsgolint: 7.0.2003 + vite: '@voidzero-dev/vite-plus-core@1.1.0(@types/node@22.19.19)(esbuild@0.28.1)(typescript@5.5.4)' + vitest: 5.0.3(@opentelemetry/api@1.9.0)(@types/node@22.19.19)(@vitest/browser-preview@5.0.3)(@vitest/ui@5.0.3)(@voidzero-dev/vite-plus-core@1.1.0(@types/node@22.19.19)(esbuild@0.28.1)(typescript@5.5.4))(msw@2.14.6(@types/node@22.19.19)(typescript@5.5.4)) optionalDependencies: - '@types/node': 22.19.19 - esbuild: 0.27.7 - fsevents: 2.3.3 + '@voidzero-dev/vite-plus-darwin-arm64': 1.1.0 + '@voidzero-dev/vite-plus-darwin-x64': 1.1.0 + '@voidzero-dev/vite-plus-linux-arm64-gnu': 1.1.0 + '@voidzero-dev/vite-plus-linux-arm64-musl': 1.1.0 + '@voidzero-dev/vite-plus-linux-x64-gnu': 1.1.0 + '@voidzero-dev/vite-plus-linux-x64-musl': 1.1.0 + '@voidzero-dev/vite-plus-win32-arm64-msvc': 1.1.0 + '@voidzero-dev/vite-plus-win32-x64-msvc': 1.1.0 + transitivePeerDependencies: + - '@arethetypeswrong/core' + - '@edge-runtime/vm' + - '@opentelemetry/api' + - '@types/node' + - '@typescript/native-preview' + - '@vitejs/devtools' + - '@vitest/coverage-istanbul' + - '@vitest/coverage-v8' + - '@vitest/ui' + - '@volar/typescript' + - '@vue/language-core' + - bufferutil + - esbuild + - happy-dom + - jiti + - jsdom + - less + - msw + - publint + - sass + - sass-embedded + - stylus + - sugarss + - svelte + - terser + - tsx + - typescript + - unplugin-unused + - unrun + - utf-8-validate + - vue-tsc + - yaml - vitest@4.1.11(@opentelemetry/api@1.9.0)(@types/node@22.19.19)(msw@2.14.6(@types/node@22.19.19)(typescript@5.5.4))(vite@8.0.14(@types/node@22.19.19)(esbuild@0.27.7)): + vitest@5.0.3(@opentelemetry/api@1.9.0)(@types/node@22.19.19)(@vitest/browser-preview@5.0.3)(@vitest/ui@5.0.3)(@voidzero-dev/vite-plus-core@1.1.0(@types/node@22.19.19)(esbuild@0.28.1)(typescript@5.5.4))(msw@2.14.6(@types/node@22.19.19)(typescript@5.5.4)): dependencies: - '@vitest/expect': 4.1.11 - '@vitest/mocker': 4.1.11(msw@2.14.6(@types/node@22.19.19)(typescript@5.5.4))(vite@8.0.14(@types/node@22.19.19)(esbuild@0.27.7)) - '@vitest/pretty-format': 4.1.11 - '@vitest/runner': 4.1.11 - '@vitest/snapshot': 4.1.11 - '@vitest/spy': 4.1.11 - '@vitest/utils': 4.1.11 + '@types/chai': 5.2.3 + '@vitest/mocker': 5.0.3(@voidzero-dev/vite-plus-core@1.1.0(@types/node@22.19.19)(esbuild@0.28.1)(typescript@5.5.4))(msw@2.14.6(@types/node@22.19.19)(typescript@5.5.4)) + chai: 6.2.2 es-module-lexer: 2.3.2 expect-type: 1.4.0 - magic-string: 0.30.21 + magic-string: 1.4.3 obug: 2.2.1 - pathe: 2.0.3 picomatch: 4.0.7 std-env: 4.2.0 - tinybench: 2.9.0 + tinybench: 6.2.2 tinyexec: 1.3.1 tinyglobby: 0.2.17 - tinyrainbow: 3.1.1 - vite: 8.0.14(@types/node@22.19.19)(esbuild@0.27.7) - why-is-node-running: 2.3.0 + vite: '@voidzero-dev/vite-plus-core@1.1.0(@types/node@22.19.19)(esbuild@0.28.1)(typescript@5.5.4)' + why-is-node-running: 3.2.1 optionalDependencies: '@opentelemetry/api': 1.9.0 '@types/node': 22.19.19 + '@vitest/browser-preview': 5.0.3(@voidzero-dev/vite-plus-core@1.1.0(@types/node@22.19.19)(esbuild@0.28.1)(typescript@5.5.4))(msw@2.14.6(@types/node@22.19.19)(typescript@5.5.4))(vitest@5.0.3) + '@vitest/ui': 5.0.3(vitest@5.0.3) transitivePeerDependencies: - msw webpack-virtual-modules@0.6.2: {} - why-is-node-running@2.3.0: - dependencies: - siginfo: 2.0.0 - stackback: 0.0.2 + why-is-node-running@3.2.1: {} wrap-ansi@7.0.0: dependencies: @@ -3884,7 +3336,7 @@ snapshots: string-width: 4.2.3 strip-ansi: 6.0.1 - wrappy@1.0.2: {} + ws@8.22.0: {} xorshift@1.2.0: {} @@ -3904,6 +3356,45 @@ snapshots: y18n: 5.0.8 yargs-parser: 21.1.1 + yuku-ast@0.9.5: + dependencies: + '@yuku-toolchain/types': 0.9.5 + + yuku-codegen@0.9.5: + dependencies: + '@yuku-toolchain/types': 0.9.5 + optionalDependencies: + '@yuku-codegen/binding-android-arm64': 0.9.5 + '@yuku-codegen/binding-darwin-arm64': 0.9.5 + '@yuku-codegen/binding-darwin-x64': 0.9.5 + '@yuku-codegen/binding-freebsd-x64': 0.9.5 + '@yuku-codegen/binding-linux-arm-gnu': 0.9.5 + '@yuku-codegen/binding-linux-arm-musl': 0.9.5 + '@yuku-codegen/binding-linux-arm64-gnu': 0.9.5 + '@yuku-codegen/binding-linux-arm64-musl': 0.9.5 + '@yuku-codegen/binding-linux-x64-gnu': 0.9.5 + '@yuku-codegen/binding-linux-x64-musl': 0.9.5 + '@yuku-codegen/binding-win32-arm64': 0.9.5 + '@yuku-codegen/binding-win32-x64': 0.9.5 + + yuku-parser@0.9.5: + dependencies: + '@yuku-toolchain/types': 0.9.5 + yuku-ast: 0.9.5 + optionalDependencies: + '@yuku-parser/binding-android-arm64': 0.9.5 + '@yuku-parser/binding-darwin-arm64': 0.9.5 + '@yuku-parser/binding-darwin-x64': 0.9.5 + '@yuku-parser/binding-freebsd-x64': 0.9.5 + '@yuku-parser/binding-linux-arm-gnu': 0.9.5 + '@yuku-parser/binding-linux-arm-musl': 0.9.5 + '@yuku-parser/binding-linux-arm64-gnu': 0.9.5 + '@yuku-parser/binding-linux-arm64-musl': 0.9.5 + '@yuku-parser/binding-linux-x64-gnu': 0.9.5 + '@yuku-parser/binding-linux-x64-musl': 0.9.5 + '@yuku-parser/binding-win32-arm64': 0.9.5 + '@yuku-parser/binding-win32-x64': 0.9.5 + zod-to-json-schema@3.25.2(zod@4.4.3): dependencies: zod: 4.4.3 diff --git a/integrations/otel-js/otel-v1/tests/otel-v1.test.ts b/integrations/otel-js/otel-v1/tests/otel-v1.test.ts index a6f7e8517..c31b8e320 100644 --- a/integrations/otel-js/otel-v1/tests/otel-v1.test.ts +++ b/integrations/otel-js/otel-v1/tests/otel-v1.test.ts @@ -1,6 +1,6 @@ /* eslint-disable @typescript-eslint/no-explicit-any */ /* eslint-disable @typescript-eslint/consistent-type-assertions */ -import { describe, it, expect, vi } from "vitest"; +import { describe, it, expect, vi } from "vite-plus/test"; import { BasicTracerProvider } from "@opentelemetry/sdk-trace-base"; import { BraintrustExporter } from "../src/otel"; diff --git a/integrations/otel-js/otel-v1/vitest.config.ts b/integrations/otel-js/otel-v1/vite.config.ts similarity index 93% rename from integrations/otel-js/otel-v1/vitest.config.ts rename to integrations/otel-js/otel-v1/vite.config.ts index 6c805c533..3d8be82c0 100644 --- a/integrations/otel-js/otel-v1/vitest.config.ts +++ b/integrations/otel-js/otel-v1/vite.config.ts @@ -1,5 +1,5 @@ import { readFileSync } from "node:fs"; -import { defineConfig } from "vitest/config"; +import { defineConfig } from "vite-plus"; import { detectOtelVersion, logOtelVersions, diff --git a/integrations/otel-js/otel-v2/package.json b/integrations/otel-js/otel-v2/package.json index 2eadf654f..0a1fdda01 100644 --- a/integrations/otel-js/otel-v2/package.json +++ b/integrations/otel-js/otel-v2/package.json @@ -3,24 +3,10 @@ "version": "0.0.1-test", "private": true, "description": "Test package for @braintrust/otel with OpenTelemetry 2.x", - "main": "./dist/index.js", - "module": "./dist/index.mjs", - "types": "./dist/index.d.ts", - "exports": { - "./package.json": "./package.json", - ".": { - "types": "./dist/index.d.ts", - "import": "./dist/index.mjs", - "module": "./dist/index.mjs", - "require": "./dist/index.js" - } - }, "scripts": { - "build": "tsup", - "watch": "tsup --watch", "clean": "rm -rf dist/*", - "build:deps": "(cd ../../.. && pnpm turbo run build --filter=braintrust --filter=@braintrust/otel)", - "test": "pnpm run build:deps && vitest run" + "build:deps": "(cd ../../.. && pnpm exec vp run --filter=braintrust --filter=@braintrust/otel build)", + "test": "pnpm run build:deps && vp test run" }, "author": "Braintrust Data Inc.", "license": "MIT", @@ -36,9 +22,10 @@ "@types/node": "^22.15.21", "braintrust": "file:../../../js", "msw": "^2.6.6", - "tsup": "^8.5.0", "typescript": "5.5.4", - "vitest": "4.1.11" + "vitest": "5.0.3", + "vite-plus": "1.1.0", + "vite": "npm:@voidzero-dev/vite-plus-core@1.1.0" }, "pnpm": { "overrides": { diff --git a/integrations/otel-js/otel-v2/pnpm-lock.yaml b/integrations/otel-js/otel-v2/pnpm-lock.yaml index e48e96509..8a6e2cfa0 100644 --- a/integrations/otel-js/otel-v2/pnpm-lock.yaml +++ b/integrations/otel-js/otel-v2/pnpm-lock.yaml @@ -63,58 +63,39 @@ importers: msw: specifier: ^2.6.6 version: 2.14.6(@types/node@22.19.19)(typescript@5.5.4) - tsup: - specifier: ^8.5.0 - version: 8.5.1(postcss@8.5.28)(typescript@5.5.4) typescript: specifier: 5.5.4 version: 5.5.4 + vite: + specifier: npm:@voidzero-dev/vite-plus-core@1.1.0 + version: '@voidzero-dev/vite-plus-core@1.1.0(@types/node@22.19.19)(esbuild@0.28.1)(typescript@5.5.4)' + vite-plus: + specifier: 1.1.0 + version: 1.1.0(@opentelemetry/api@1.9.0)(@types/node@22.19.19)(@vitest/ui@5.0.3(vitest@5.0.3))(esbuild@0.28.1)(msw@2.14.6(@types/node@22.19.19)(typescript@5.5.4))(typescript@5.5.4) vitest: - specifier: 4.1.11 - version: 4.1.11(@opentelemetry/api@1.9.0)(@types/node@22.19.19)(msw@2.14.6(@types/node@22.19.19)(typescript@5.5.4))(vite@8.0.14(@types/node@22.19.19)(esbuild@0.27.7)) + specifier: 5.0.3 + version: 5.0.3(@opentelemetry/api@1.9.0)(@types/node@22.19.19)(@vitest/browser-preview@5.0.3)(@vitest/ui@5.0.3)(@voidzero-dev/vite-plus-core@1.1.0(@types/node@22.19.19)(esbuild@0.28.1)(typescript@5.5.4))(msw@2.14.6(@types/node@22.19.19)(typescript@5.5.4)) packages: - '@braintrust/bt-darwin-arm64@0.12.0': - resolution: {integrity: sha512-mY6VW/3VwcOQOGN8sYHS6F0xzHTFwgZcNlj7zlQttI6OXOCGt/bhonGIqd03QBhxmj0M31ymSS7TqSeCK/RYIQ==} - cpu: [arm64] - os: [darwin] - - '@braintrust/bt-darwin-x64@0.12.0': - resolution: {integrity: sha512-woyRyDv2DfCF8+von+3X9f1cddAbFnKLSfCbEkrBQVc0ZsAM60as8nehYv7DkafJF/67yKFx/sUraV65sukesQ==} - cpu: [x64] - os: [darwin] - - '@braintrust/bt-linux-arm64@0.12.0': - resolution: {integrity: sha512-J9/7f3EIMKmFmSSQHQnAQLpUgB8YJENrOlkABjlTprBgsIYVgz7tSH7yg2P3ur+2Jem7PpGnrDxHGh/UjRfjsg==} - cpu: [arm64] - os: [linux] - libc: [glibc] - - '@braintrust/bt-linux-x64-musl@0.12.0': - resolution: {integrity: sha512-KnLgENOoztBXcH+mLFJe4bYzi67kSOuELOQrm4Hler35GjgaBMI1fFLIUjKRPAmyT9VIhBMhy1ICfGEFLueMEQ==} - cpu: [x64] - os: [linux] - libc: [musl] + '@babel/code-frame@7.29.7': + resolution: {integrity: sha512-Aup7aUOfpbAUg2ROOJN6Iw5f9DMBlzu0mIkm/malLQFN/YQgO48wCj0Kxa3sEHJvPVFg7siR+qRInwXd2qhQKw==} + engines: {node: '>=6.9.0'} - '@braintrust/bt-linux-x64@0.12.0': - resolution: {integrity: sha512-HJFbUl3HYYY1Ivw8OYBeIMcIsU9r7+fC9BzLWB5FdH7881ToKee2wbbLZssMCxFbMVeUxzEo+SzGD/1Z9Gk9CA==} - cpu: [x64] - os: [linux] - libc: [glibc] + '@babel/helper-validator-identifier@7.29.7': + resolution: {integrity: sha512-qehxGkRj55h/ff8EMaJ+cYhyaKlHIxqYDn682wQD7RNp9UujOQsHog2uS0r2vzr4pW+sXf90NeeayjcNaX3fFg==} + engines: {node: '>=6.9.0'} - '@braintrust/bt-win32-arm64@0.12.0': - resolution: {integrity: sha512-+7PkdBAmiqEJsWteGHP/Zs62F75PkHPA8m/ej4TOz/mHGKulUU0bZibw91KRqIB7J7jGi5ItvCiqkiGaLKLnyw==} - cpu: [arm64] - os: [win32] + '@babel/runtime@7.29.10': + resolution: {integrity: sha512-NpugTzbKgGrNe45Ui32cN8vCFFMxVtP/9rtfwv0nP5FRDAjvYBxJcLVpYA9mr1jxP6cnIT7x9LUzuDTbWYYNRA==} + engines: {node: '>=6.9.0'} - '@braintrust/bt-win32-x64@0.12.0': - resolution: {integrity: sha512-Q0c+pVUGm82n/7N8QJ5s6j/G/Q0GFzqOaTipHjkmElLbAOOEcfRH/uljdtIPNBiEQcrzqirS0GmOgiFkeQ3Txg==} - cpu: [x64] - os: [win32] + '@blazediff/core@1.10.0': + resolution: {integrity: sha512-AOQff0zgR7cGsZL+4E7hVkmujoPUpm0J9xzWGWZj5wCjd3gmxESXAPfKyuzs93VdpQNFhHlBhfOjrcZ+XTERtQ==} '@braintrust/otel@file:..': resolution: {directory: .., type: directory} + engines: {node: '>=22.18.0'} peerDependencies: '@opentelemetry/api': '>=1.9.0' '@opentelemetry/core': '>=1.9.0' @@ -122,325 +103,156 @@ packages: '@opentelemetry/sdk-trace-base': '>=1.9.0' braintrust: '>=1.0.0-0' - '@colors/colors@1.5.0': - resolution: {integrity: sha512-ooWCrlZP11i8GImSjTHYHLkvFDP48nS4+204nGb1RiX/WXYHmJA2III9/e2DWVabCESdW7hBAEzHRqUn9OUVvQ==} - engines: {node: '>=0.1.90'} - - '@emnapi/core@1.10.0': - resolution: {integrity: sha512-yq6OkJ4p82CAfPl0u9mQebQHKPJkY7WrIuk205cTYnYe+k2Z8YBh11FrbRG/H6ihirqcacOgl2BIO8oyMQLeXw==} - - '@emnapi/runtime@1.10.0': - resolution: {integrity: sha512-ewvYlk86xUoGI0zQRNq/mC+16R1QeDlKQy21Ki3oSYXNgLb45GV1P6A0M+/s6nyCuNDqe5VpaY84BzXGwVbwFA==} - - '@emnapi/wasi-threads@1.2.1': - resolution: {integrity: sha512-uTII7OYF+/Mes/MrcIOYp5yOtSMLBWSIoLPpcgwipoiKbli6k322tcoFsxoIIxPDqW01SQGAgko4EzZi2BNv2w==} - - '@esbuild/aix-ppc64@0.27.7': - resolution: {integrity: sha512-EKX3Qwmhz1eMdEJokhALr0YiD0lhQNwDqkPYyPhiSwKrh7/4KRjQc04sZ8db+5DVVnZ1LmbNDI1uAMPEUBnQPg==} - engines: {node: '>=18'} - cpu: [ppc64] - os: [aix] - '@esbuild/aix-ppc64@0.28.1': resolution: {integrity: sha512-Svl7tq8k/08+p6CXPpRjQ1fKX+1odH/BQbb48fV6fj3CWHhsoIOoY87w1oHXm0qEpkIK3ZfVgp0hed3XBXzXMQ==} engines: {node: '>=18'} cpu: [ppc64] os: [aix] - '@esbuild/android-arm64@0.27.7': - resolution: {integrity: sha512-62dPZHpIXzvChfvfLJow3q5dDtiNMkwiRzPylSCfriLvZeq0a1bWChrGx/BbUbPwOrsWKMn8idSllklzBy+dgQ==} - engines: {node: '>=18'} - cpu: [arm64] - os: [android] - '@esbuild/android-arm64@0.28.1': resolution: {integrity: sha512-34EGEbCIAgosYz6goLcopX6Mo7NyGv9tfwEM2/7Ce2VcVRk568iSvniGWcUXIy7wEDR1wzolcxcriFVrWYcwBg==} engines: {node: '>=18'} cpu: [arm64] os: [android] - '@esbuild/android-arm@0.27.7': - resolution: {integrity: sha512-jbPXvB4Yj2yBV7HUfE2KHe4GJX51QplCN1pGbYjvsyCZbQmies29EoJbkEc+vYuU5o45AfQn37vZlyXy4YJ8RQ==} - engines: {node: '>=18'} - cpu: [arm] - os: [android] - '@esbuild/android-arm@0.28.1': resolution: {integrity: sha512-0k2F129Xdio1TdJfzJ8sy1Q47vUD2NnwdhiAf7drUN1EBTfPf4hsFCtmMgu/6m8JSzsBrlmVjudMBQqOfG8usQ==} engines: {node: '>=18'} cpu: [arm] os: [android] - '@esbuild/android-x64@0.27.7': - resolution: {integrity: sha512-x5VpMODneVDb70PYV2VQOmIUUiBtY3D3mPBG8NxVk5CogneYhkR7MmM3yR/uMdITLrC1ml/NV1rj4bMJuy9MCg==} - engines: {node: '>=18'} - cpu: [x64] - os: [android] - '@esbuild/android-x64@0.28.1': resolution: {integrity: sha512-dbwY7ltSMDWsRatcRpCnES4F+im88OCUgGZjy52shC7GqHRE/cYlxNbB4Z4UpJswpcc4Qxd2oE/ufM0p61IKng==} engines: {node: '>=18'} cpu: [x64] os: [android] - '@esbuild/darwin-arm64@0.27.7': - resolution: {integrity: sha512-5lckdqeuBPlKUwvoCXIgI2D9/ABmPq3Rdp7IfL70393YgaASt7tbju3Ac+ePVi3KDH6N2RqePfHnXkaDtY9fkw==} - engines: {node: '>=18'} - cpu: [arm64] - os: [darwin] - '@esbuild/darwin-arm64@0.28.1': resolution: {integrity: sha512-TZbWkQY7kvTAXbXUT7uVACR5cMHsDiSz9z7ZKAX/RTq/WJEk3QyRr0wZpNhBDX+/0CtdqUIJlOiodQcta6tY3Q==} engines: {node: '>=18'} cpu: [arm64] os: [darwin] - '@esbuild/darwin-x64@0.27.7': - resolution: {integrity: sha512-rYnXrKcXuT7Z+WL5K980jVFdvVKhCHhUwid+dDYQpH+qu+TefcomiMAJpIiC2EM3Rjtq0sO3StMV/+3w3MyyqQ==} - engines: {node: '>=18'} - cpu: [x64] - os: [darwin] - '@esbuild/darwin-x64@0.28.1': resolution: {integrity: sha512-zfdzgK9ACBNZLI/CyHTOx81SyNbM6YXn7rxSgX97VjyiPl9W1i4Ka4fgKECEoFCKGpvBj5qArWIGgQjOwkgskQ==} engines: {node: '>=18'} cpu: [x64] os: [darwin] - '@esbuild/freebsd-arm64@0.27.7': - resolution: {integrity: sha512-B48PqeCsEgOtzME2GbNM2roU29AMTuOIN91dsMO30t+Ydis3z/3Ngoj5hhnsOSSwNzS+6JppqWsuhTp6E82l2w==} - engines: {node: '>=18'} - cpu: [arm64] - os: [freebsd] - '@esbuild/freebsd-arm64@0.28.1': resolution: {integrity: sha512-wG2EA8ENdEI0qhkSZMjfqrdY+ziCYCPMmtZjjIwOmXFjmyzEHn+UUxk5of+SYsjtfs3VpnlC7QLzSI5hY/rOAw==} engines: {node: '>=18'} cpu: [arm64] os: [freebsd] - '@esbuild/freebsd-x64@0.27.7': - resolution: {integrity: sha512-jOBDK5XEjA4m5IJK3bpAQF9/Lelu/Z9ZcdhTRLf4cajlB+8VEhFFRjWgfy3M1O4rO2GQ/b2dLwCUGpiF/eATNQ==} - engines: {node: '>=18'} - cpu: [x64] - os: [freebsd] - '@esbuild/freebsd-x64@0.28.1': resolution: {integrity: sha512-i7dZ9vQgnvSCzi/rYCXNgtF/U+eKZNJBzu3eTQbRgHnM7tNSizLOkRFAl3qzVc/Op/u5YkHHa4pf/3DOYHthLQ==} engines: {node: '>=18'} cpu: [x64] os: [freebsd] - '@esbuild/linux-arm64@0.27.7': - resolution: {integrity: sha512-RZPHBoxXuNnPQO9rvjh5jdkRmVizktkT7TCDkDmQ0W2SwHInKCAV95GRuvdSvA7w4VMwfCjUiPwDi0ZO6Nfe9A==} - engines: {node: '>=18'} - cpu: [arm64] - os: [linux] - '@esbuild/linux-arm64@0.28.1': resolution: {integrity: sha512-yHs+0uc8+nvEAfAfxrWQKK5peSNzBc4PegcMO0EJ2hT71uA7vB8Ihg2e77R2P7SG5uYjPbHlLLmve4LLLRCf0g==} engines: {node: '>=18'} cpu: [arm64] os: [linux] - '@esbuild/linux-arm@0.27.7': - resolution: {integrity: sha512-RkT/YXYBTSULo3+af8Ib0ykH8u2MBh57o7q/DAs3lTJlyVQkgQvlrPTnjIzzRPQyavxtPtfg0EopvDyIt0j1rA==} - engines: {node: '>=18'} - cpu: [arm] - os: [linux] - '@esbuild/linux-arm@0.28.1': resolution: {integrity: sha512-qVXBOHQS+d5Y722GwJzJUtOLlX7km3CraOaGormF1pDtPd2C/l1SHRPgjLunLGe51Sh5YYWKMFDyV4SxgMQYTQ==} engines: {node: '>=18'} cpu: [arm] os: [linux] - '@esbuild/linux-ia32@0.27.7': - resolution: {integrity: sha512-GA48aKNkyQDbd3KtkplYWT102C5sn/EZTY4XROkxONgruHPU72l+gW+FfF8tf2cFjeHaRbWpOYa/uRBz/Xq1Pg==} - engines: {node: '>=18'} - cpu: [ia32] - os: [linux] - '@esbuild/linux-ia32@0.28.1': resolution: {integrity: sha512-d1z4ZuP0ajrfz/FhGT4vv278rX8KnPPJx8i5+AtK7TYbx9Le9F1hyzurZpkEyjkGa9dUGhQow4C1NmeGvqxN2w==} engines: {node: '>=18'} cpu: [ia32] os: [linux] - '@esbuild/linux-loong64@0.27.7': - resolution: {integrity: sha512-a4POruNM2oWsD4WKvBSEKGIiWQF8fZOAsycHOt6JBpZ+JN2n2JH9WAv56SOyu9X5IqAjqSIPTaJkqN8F7XOQ5Q==} - engines: {node: '>=18'} - cpu: [loong64] - os: [linux] - '@esbuild/linux-loong64@0.28.1': resolution: {integrity: sha512-M5sRjUVZrkm1OAPR3dlOYzNmN+loZKGVi1VUQGrwuqLcbR6qeAz+famMhjASeH3YVKvZz+zT1jlh/keC3Rj/lg==} engines: {node: '>=18'} cpu: [loong64] os: [linux] - '@esbuild/linux-mips64el@0.27.7': - resolution: {integrity: sha512-KabT5I6StirGfIz0FMgl1I+R1H73Gp0ofL9A3nG3i/cYFJzKHhouBV5VWK1CSgKvVaG4q1RNpCTR2LuTVB3fIw==} - engines: {node: '>=18'} - cpu: [mips64el] - os: [linux] - '@esbuild/linux-mips64el@0.28.1': resolution: {integrity: sha512-mRObBZeHh2OxcBFPWE/FjylkRgZdYuiTR3vaTozquCGOH14iP9oN4x4Ge81CoIDYQrXmIxpFumJBu5MtZpnQJQ==} engines: {node: '>=18'} cpu: [mips64el] os: [linux] - '@esbuild/linux-ppc64@0.27.7': - resolution: {integrity: sha512-gRsL4x6wsGHGRqhtI+ifpN/vpOFTQtnbsupUF5R5YTAg+y/lKelYR1hXbnBdzDjGbMYjVJLJTd2OFmMewAgwlQ==} - engines: {node: '>=18'} - cpu: [ppc64] - os: [linux] - '@esbuild/linux-ppc64@0.28.1': resolution: {integrity: sha512-slScBsMAb3GFDcdrCgLwZtPYRoH2H/youv10QiZyRjmsP48fznoveWytSgCI/R0ZcUgpc0ZhIUEx6LHts8yrfQ==} engines: {node: '>=18'} cpu: [ppc64] os: [linux] - '@esbuild/linux-riscv64@0.27.7': - resolution: {integrity: sha512-hL25LbxO1QOngGzu2U5xeXtxXcW+/GvMN3ejANqXkxZ/opySAZMrc+9LY/WyjAan41unrR3YrmtTsUpwT66InQ==} - engines: {node: '>=18'} - cpu: [riscv64] - os: [linux] - '@esbuild/linux-riscv64@0.28.1': resolution: {integrity: sha512-kw0owk1o0GFETUJyW0jc0G4Yzs0BHZn0JDZ8JRT088vjJYX777BAs1fDGxAC+q831qOs2DTC96mNsG2opdfyyQ==} engines: {node: '>=18'} cpu: [riscv64] os: [linux] - '@esbuild/linux-s390x@0.27.7': - resolution: {integrity: sha512-2k8go8Ycu1Kb46vEelhu1vqEP+UeRVj2zY1pSuPdgvbd5ykAw82Lrro28vXUrRmzEsUV0NzCf54yARIK8r0fdw==} - engines: {node: '>=18'} - cpu: [s390x] - os: [linux] - '@esbuild/linux-s390x@0.28.1': resolution: {integrity: sha512-/lAIjX8aYFRByhh6L5rYtPEDRqa9de/4V/juOXcta5frjvzXO4/sqEtyytse0g3zZFuWu5cDN0MkLz2qRDD2Ag==} engines: {node: '>=18'} cpu: [s390x] os: [linux] - '@esbuild/linux-x64@0.27.7': - resolution: {integrity: sha512-hzznmADPt+OmsYzw1EE33ccA+HPdIqiCRq7cQeL1Jlq2gb1+OyWBkMCrYGBJ+sxVzve2ZJEVeePbLM2iEIZSxA==} - engines: {node: '>=18'} - cpu: [x64] - os: [linux] - '@esbuild/linux-x64@0.28.1': resolution: {integrity: sha512-u/anNYF2mmVOEDwLtnQ1wOr3EZ9sTNGLWrsYGYwHWzGA3Si84IOkHXlbWTD1NB+9/1lcnweYKO54uhxZydNzfA==} engines: {node: '>=18'} cpu: [x64] os: [linux] - '@esbuild/netbsd-arm64@0.27.7': - resolution: {integrity: sha512-b6pqtrQdigZBwZxAn1UpazEisvwaIDvdbMbmrly7cDTMFnw/+3lVxxCTGOrkPVnsYIosJJXAsILG9XcQS+Yu6w==} - engines: {node: '>=18'} - cpu: [arm64] - os: [netbsd] - '@esbuild/netbsd-arm64@0.28.1': resolution: {integrity: sha512-oks0DYbLwWMmaakTsCb+zL4E+aHRVLom9IJZOAthMQEPiQmydXHkziYEsGYRx0uNV/IjEKGAV941JzH02pflqw==} engines: {node: '>=18'} cpu: [arm64] os: [netbsd] - '@esbuild/netbsd-x64@0.27.7': - resolution: {integrity: sha512-OfatkLojr6U+WN5EDYuoQhtM+1xco+/6FSzJJnuWiUw5eVcicbyK3dq5EeV/QHT1uy6GoDhGbFpprUiHUYggrw==} - engines: {node: '>=18'} - cpu: [x64] - os: [netbsd] - '@esbuild/netbsd-x64@0.28.1': resolution: {integrity: sha512-aeL6lAnN89Hz43Mlh1G8ARasbuoYvSITDEx0tHh5b7jJnHcssqgjy9Yx430GDpmCa6OyrKoS0aNRjKundRizGg==} engines: {node: '>=18'} cpu: [x64] os: [netbsd] - '@esbuild/openbsd-arm64@0.27.7': - resolution: {integrity: sha512-AFuojMQTxAz75Fo8idVcqoQWEHIXFRbOc1TrVcFSgCZtQfSdc1RXgB3tjOn/krRHENUB4j00bfGjyl2mJrU37A==} - engines: {node: '>=18'} - cpu: [arm64] - os: [openbsd] - '@esbuild/openbsd-arm64@0.28.1': resolution: {integrity: sha512-MEFJe5C3R8pwXdZ5Y21oo6m7ePiS0d9pWucn99O/wvyJZChoIQKrQDxKrGeW8F5+T0okTHesAmDeiHDTIq0V/Q==} engines: {node: '>=18'} cpu: [arm64] os: [openbsd] - '@esbuild/openbsd-x64@0.27.7': - resolution: {integrity: sha512-+A1NJmfM8WNDv5CLVQYJ5PshuRm/4cI6WMZRg1by1GwPIQPCTs1GLEUHwiiQGT5zDdyLiRM/l1G0Pv54gvtKIg==} - engines: {node: '>=18'} - cpu: [x64] - os: [openbsd] - '@esbuild/openbsd-x64@0.28.1': resolution: {integrity: sha512-i/ZLIOafE0Z8cI/XANJAixoJL/uRAoS2xOA3rb0xN+KK0K177cMAsQYkzHtBrtMXAKuAc7HGgcWiZ/sRC1Nxgw==} engines: {node: '>=18'} cpu: [x64] os: [openbsd] - '@esbuild/openharmony-arm64@0.27.7': - resolution: {integrity: sha512-+KrvYb/C8zA9CU/g0sR6w2RBw7IGc5J2BPnc3dYc5VJxHCSF1yNMxTV5LQ7GuKteQXZtspjFbiuW5/dOj7H4Yw==} - engines: {node: '>=18'} - cpu: [arm64] - os: [openharmony] - '@esbuild/openharmony-arm64@0.28.1': resolution: {integrity: sha512-ge+Z7EXFNt2BO1oAMsVpiQ8EwndV9i1xXerAeTIK7AtPs3bKFXQM7nlRxDSIUIMeueR1CNXxqztLzdNeReKBJg==} engines: {node: '>=18'} cpu: [arm64] os: [openharmony] - '@esbuild/sunos-x64@0.27.7': - resolution: {integrity: sha512-ikktIhFBzQNt/QDyOL580ti9+5mL/YZeUPKU2ivGtGjdTYoqz6jObj6nOMfhASpS4GU4Q/Clh1QtxWAvcYKamA==} - engines: {node: '>=18'} - cpu: [x64] - os: [sunos] - '@esbuild/sunos-x64@0.28.1': resolution: {integrity: sha512-BEjgtECkL3vY+SaSQ6nzVfiALUeFxpawyp8Jmf5PtYhf1Ug40N1h/hxlhts+f1FvSvarEigdxS3BlSMI2PJLcQ==} engines: {node: '>=18'} cpu: [x64] os: [sunos] - '@esbuild/win32-arm64@0.27.7': - resolution: {integrity: sha512-7yRhbHvPqSpRUV7Q20VuDwbjW5kIMwTHpptuUzV+AA46kiPze5Z7qgt6CLCK3pWFrHeNfDd1VKgyP4O+ng17CA==} - engines: {node: '>=18'} - cpu: [arm64] - os: [win32] - '@esbuild/win32-arm64@0.28.1': resolution: {integrity: sha512-lCv9eK/H6ZJWbE7bh2nw54CZ9M2nupBxJcTsdk/QQnWkdSjKGuxmmH8/GWrlT1eMmZfn4dGcCjRte397WqfQXA==} engines: {node: '>=18'} cpu: [arm64] os: [win32] - '@esbuild/win32-ia32@0.27.7': - resolution: {integrity: sha512-SmwKXe6VHIyZYbBLJrhOoCJRB/Z1tckzmgTLfFYOfpMAx63BJEaL9ExI8x7v0oAO3Zh6D/Oi1gVxEYr5oUCFhw==} - engines: {node: '>=18'} - cpu: [ia32] - os: [win32] - '@esbuild/win32-ia32@0.28.1': resolution: {integrity: sha512-zvb/mB2bSCoJOpoCBgYKKpX6YM6mJBlBUVUtVj41DlZJVEB6/0CKlRYxP5wWl1C1ILiCoAU5wZZ4q1P3qeS6Eg==} engines: {node: '>=18'} cpu: [ia32] os: [win32] - '@esbuild/win32-x64@0.27.7': - resolution: {integrity: sha512-56hiAJPhwQ1R4i+21FVF7V8kSD5zZTdHcVuRFMW0hn753vVfQN8xlx4uOPT4xoGH0Z/oVATuR82AiqSTDIpaHg==} - engines: {node: '>=18'} - cpu: [x64] - os: [win32] - '@esbuild/win32-x64@0.28.1': resolution: {integrity: sha512-bm4Mowrv+GXMlpWX++EcXw/iLyd1o3+bJkC2DkWXYVvgZCqD/bSj9ctZeAMC3cIxgjRVR2Dufaiu4YPxr5gW1A==} engines: {node: '>=18'} @@ -504,6 +316,9 @@ packages: '@jridgewell/sourcemap-codec@1.5.5': resolution: {integrity: sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og==} + '@jridgewell/sourcemap-codec@1.6.0': + resolution: {integrity: sha512-T7jf+5zgsZHwNJ4lvQ7/aezbyk0nNX+zJVWpmHA7VYsEx7a7qr5Rg5IbtJFqkgze5Y2sruq1RUY8Q837Od7iFw==} + '@jridgewell/trace-mapping@0.3.31': resolution: {integrity: sha512-zzNR+SdQSDJzc8joaeP8QQoCQr8NuYx2dIIytl1QeBEZHJ9uW6hebsrYgbz8hJwUQao3TWCMtmfV8Nu1twOLAw==} @@ -514,16 +329,6 @@ packages: resolution: {integrity: sha512-VVPPgHyQ6ShqnrmDWuxjmUIsO9gWyOZFmuOfLd9LfBGQJwZfy0gvv9pbHSJuoFNIYC7ZDX9aoFwowjcdSC4E8w==} engines: {node: '>=18'} - '@napi-rs/wasm-runtime@1.2.4': - resolution: {integrity: sha512-AJxoUD2/15ESHbvpcyjU274nsAPLuOtPHCk0vKJM5pj//Fg/B1FXNWjPnXTT9PymCYYiHo4zPj0ZomXBKhoy7g==} - engines: {node: ^20.19.0 || ^22.13.0 || >=23.5.0} - peerDependencies: - '@emnapi/core': ^1.7.1 || ^2.0.0-alpha.4 - '@emnapi/runtime': ^1.7.1 || ^2.0.0-alpha.4 - - '@next/env@14.2.35': - resolution: {integrity: sha512-DuhvCtj4t9Gwrx80dmz2F4t/zKQ4ktN8WrMwOuVzkJfBilwAwGr6v16M5eI8yCuZ63H9TTuEU09Iu2HqkzFPVQ==} - '@open-draft/deferred-promise@2.2.0': resolution: {integrity: sha512-CecwLWx3rhxVQF6V4bAgPS5t+So2sTbPgAzafKkVizyi7tlwpcFpdFqq+wqF2OwNBmqFuu6tOyouTuxgpMfzmA==} @@ -661,6 +466,7 @@ packages: '@opentelemetry/propagator-jaeger@2.0.0': resolution: {integrity: sha512-Mbm/LSFyAtQKP0AQah4AfGgsD+vsZcyreZoQ5okFBk33hU7AquU4TltgyL9dvaO8/Zkoud8/0gEvwfOZ5d7EPA==} engines: {node: ^18.19.0 || >=20.6.0} + deprecated: Package no longer supported. See https://github.com/open-telemetry/opentelemetry-js/pull/7077 peerDependencies: '@opentelemetry/api': '>=1.0.0 <1.10.0' @@ -691,12 +497,14 @@ packages: '@opentelemetry/sdk-trace-base@2.0.0': resolution: {integrity: sha512-qQnYdX+ZCkonM7tA5iU4fSRsVxbFGml8jbxOgipRGMFHKaXKHQ30js03rTobYjKjIfnOsZSbHKWF0/0v0OQGfw==} engines: {node: ^18.19.0 || >=20.6.0} + deprecated: Use @opentelemetry/sdk-trace package peerDependencies: '@opentelemetry/api': '>=1.3.0 <1.10.0' '@opentelemetry/sdk-trace-node@2.0.0': resolution: {integrity: sha512-omdilCZozUjQwY3uZRBwbaRMJ3p09l4t187Lsdf0dGMye9WKD4NGcpgZRvqhI1dwcH6og+YXQEtoO9Wx3ykilg==} engines: {node: ^18.19.0 || >=20.6.0} + deprecated: Use @opentelemetry/sdk-trace package peerDependencies: '@opentelemetry/api': '>=1.0.0 <1.10.0' @@ -704,292 +512,345 @@ packages: resolution: {integrity: sha512-/UhIkaZgPutTFmQ7RnIJGgDXZmtEJ7Dvi86xNTFWcnRxVRNk/aotsqDJYeEvDP+FSMB2SdW+pQzNMcWP0rwuNA==} engines: {node: '>=14'} - '@oxc-project/types@0.132.0': - resolution: {integrity: sha512-FESMOxil5Se014ui/Eq8fT5uHJo6nIRwH0PfJrZJXs6Gek3ZVFOrpUv3YIZT20m+extU98Hg1Ym72U58rlsxUQ==} - - '@protobufjs/aspromise@1.1.2': - resolution: {integrity: sha512-j+gKExEuLmKwvz3OgROXtrJ2UG2x8Ch2YZUxahh+s1F2HZ+wAceUNLkvy6zKCPVRkU++ZWQrdxsUeQXmcg4uoQ==} - - '@protobufjs/base64@1.1.2': - resolution: {integrity: sha512-AZkcAA5vnN/v4PDqKyMR5lx7hZttPDgClv83E//FMNhR2TMcLUhfRUBHCmSl0oi9zMgDDqRUJkSxO3wm85+XLg==} - - '@protobufjs/codegen@2.0.5': - resolution: {integrity: sha512-zgXFLzW3Ap33e6d0Wlj4MGIm6Ce8O89n/apUaGNB/jx+hw+ruWEp7EwGUshdLKVRCxZW12fp9r40E1mQrf/34g==} - - '@protobufjs/eventemitter@1.1.1': - resolution: {integrity: sha512-vW1GmwMZNnL+gMRaovlh9yZX74kc+TTU3FObkkurpMaRtBfLP3ldjS9KQWlwZgraRE0+dheEEoAxdzcJQ8eXZg==} - - '@protobufjs/fetch@1.1.1': - resolution: {integrity: sha512-GpptLrs57adMSuHi3VNj0mAF8dwh36LMaYF6XyJ6JMWlVsc+t42tm1HSEDmOs3A8fC9yyeisgLhsTVQokOZ0zw==} - - '@protobufjs/float@1.0.2': - resolution: {integrity: sha512-Ddb+kVXlXst9d+R9PfTIxh1EdNkgoRe5tOX6t01f1lYWOvJnSPDBlG241QLzcyPdoNTsblLUdujGSE4RzrTZGQ==} - - '@protobufjs/path@1.1.2': - resolution: {integrity: sha512-6JOcJ5Tm08dOHAbdR3GrvP+yUUfkjG5ePsHYczMFLq3ZmMkAD98cDgcT2iA1lJ9NVwFd4tH/iSSoe44YWkltEA==} + '@oxc-project/runtime@0.153.0': + resolution: {integrity: sha512-rXJvtlsVG4xJNq64hiaUTIatE7jbBgpa6l4QgmEa2jLgT3ULH9scUBkpsC9N1DfmqixLEyT6DuK6OSXozziIlw==} + engines: {node: ^20.19.0 || >=22.12.0} - '@protobufjs/pool@1.1.0': - resolution: {integrity: sha512-0kELaGSIDBKvcgS4zkjz1PeddatrjYcmMWOlAuAPwAeccUrPHdUqo/J6LiymHHEiJT5NrF1UVwxY14f+fy4WQw==} + '@oxc-project/types@0.153.0': + resolution: {integrity: sha512-jSNrRIyvPVLL5E1z/tS5zLAprosGz/FuI1PWwEOz59j3E4TTTzSpqKb/RDGqJhhW0QHYISGXxq582wGx6T11Tw==} - '@protobufjs/utf8@1.1.1': - resolution: {integrity: sha512-oOAWABowe8EAbMyWKM0tYDKi8Yaox52D+HWZhAIJqQXbqe0xI/GV7FhLWqlEKreMkfDjshR5FKgi3mnle0h6Eg==} + '@oxfmt/binding-android-arm-eabi@0.72.0': + resolution: {integrity: sha512-u4U1uaDOVBuorGNC/07NLm48nECHOiJJka002YGML16Md2DRvf38RGBt01lH60EOGvNlFVmbLoksQedNBoZHKw==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [arm] + os: [android] - '@rolldown/binding-android-arm64@1.0.2': - resolution: {integrity: sha512-ZS4D1JPGn/MYQN/SYDWftIE/nVsM8j/AFOYEzAoOE2O3NktQOZru+/vYXGbR/qtdLdIfGCP0lcoJiYVzsEz+iQ==} + '@oxfmt/binding-android-arm64@0.72.0': + resolution: {integrity: sha512-GCnM+Ae2mWOKC+92y3FOb6eZJeVnBAVz82e3lITUUg+kxukYKzBNAvJ/hhHIfa2A0md55SYYhdWPddSSy3KPVA==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [arm64] os: [android] - '@rolldown/binding-darwin-arm64@1.0.2': - resolution: {integrity: sha512-vdFA9+C/rekyGce7WqHs/xoT0ioZEWaOFyZLIV1mEeNFaFDUQrPIo8Vs2GvJ6eetb3rzDUtUBgzto3ExpXJB3w==} + '@oxfmt/binding-darwin-arm64@0.72.0': + resolution: {integrity: sha512-LjQ8tlevdtwraFlWC6AnQ1Forzde0clRmnsP3QmK+ydSElDt/J+5Jc+m+//Ly1G97aP7WCNUw110QMIUw2K7xA==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [arm64] os: [darwin] - '@rolldown/binding-darwin-x64@1.0.2': - resolution: {integrity: sha512-BewSOwTHazv77DTYiAZXSqqKZ4KP/KonFisDMVU7PImxoWfB2aepnPhd2E4SWz3zDzYgDNbs6jBmTdgNnF02GA==} + '@oxfmt/binding-darwin-x64@0.72.0': + resolution: {integrity: sha512-+ZbcxhB1C34RQR5IldSyY6Z/yalJIxHXhP36PGE4sEq0f/lgL5jCLmOKoDeVm8dqd9gFGDsd1bNWHSGCAwdSkw==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [x64] os: [darwin] - '@rolldown/binding-freebsd-x64@1.0.2': - resolution: {integrity: sha512-m41o7M0YWtUdqk61Tb+jnKb2rN++iRdIASlExkUoKfIAH30DOHCB8fVLzSUpbWHHU8esmEioY62PxzexE8MBuA==} + '@oxfmt/binding-freebsd-x64@0.72.0': + resolution: {integrity: sha512-yGPhA8sePEBFV9iGcprhyOk3Bz3fZshswMH3DpYIPZFLT1DG5LjiZ0j6AJQpSueaL6bUK0av1K+5+fe+ldy5EQ==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [x64] os: [freebsd] - '@rolldown/binding-linux-arm-gnueabihf@1.0.2': - resolution: {integrity: sha512-jcojB9H7W/jS29pMKWAK1N+fU99vXodHDTatS3b3y/XSOCiHo0kkA74pL3jJmkoQtYpOCxDvaKs1fo2Ij/1X5w==} + '@oxfmt/binding-linux-arm-gnueabihf@0.72.0': + resolution: {integrity: sha512-/g5axuqFMcAtoQrFLR5EJEMDjR5Ea4GuzpdDmMpPWAgrF1DmL4kD1NPNVlpXEOxpFJnD6lPvLKYj1lzdlrfifA==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [arm] + os: [linux] + + '@oxfmt/binding-linux-arm-musleabihf@0.72.0': + resolution: {integrity: sha512-InVitdHbS09PM+w+WinIiyX37rC9R3L7lUP9mKy9uKTEDSUFaLjWsLoreRIxKivnv7Ndqc6Trvi+K+b6ZzhiJg==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [arm] os: [linux] - '@rolldown/binding-linux-arm64-gnu@1.0.2': - resolution: {integrity: sha512-1jn6qDU5iiOgFgygDzKUuKP0maTi0/f1+sBLgvij/76C77Nm3ts6ufz9Bjg5q5dduxiUIxtq86JIoBvo1xQ4Ig==} + '@oxfmt/binding-linux-arm64-gnu@0.72.0': + resolution: {integrity: sha512-WKjlyRAxpPjXoqeuwFBim5LscgvY+RNEiZ971C9JlRQZONhJTsXDki/oHFYQE+fzzRpzXh3owZvOdT3kNL2g+g==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [arm64] os: [linux] libc: [glibc] - '@rolldown/binding-linux-arm64-musl@1.0.2': - resolution: {integrity: sha512-QVLO/czFMdoMFSqlX3bcswcJNm/23r+qoa/jgtmFc/qEp6/jXmIkDjF/XIo8dPfGaiwy1xfQn8o77L79GeXFgw==} + '@oxfmt/binding-linux-arm64-musl@0.72.0': + resolution: {integrity: sha512-poafnbsPpJhR+5Mbwpf8CscugTyiRAzjTOxRVn8ra3X2jnURs2xL5Du2xl5g2iW85lICMglpmLf8jDjZEbNgzQ==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [arm64] os: [linux] libc: [musl] - '@rolldown/binding-linux-ppc64-gnu@1.0.2': - resolution: {integrity: sha512-hgO5Abm0w5UL6FEa2iFnZqo2KlK7TQ5QhV5x09hujBf7t5KzHQ1VmfPuTpqRy/rNlSxua3eWH374xxiVrP+lcA==} + '@oxfmt/binding-linux-ppc64-gnu@0.72.0': + resolution: {integrity: sha512-zNUi0gKRsO7S3IfPzsTbnm88OedPuX32hmZFWZRWHvWJlAoZRwVbeO6Nb3hng4vDgL7qUKp4TGqzPz1mhsycYg==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [ppc64] os: [linux] libc: [glibc] - '@rolldown/binding-linux-s390x-gnu@1.0.2': - resolution: {integrity: sha512-fy8rXxuYEu602abC8MUNaPjYLIFzReOaEIEMKMUa0rFEUxNpVXhs15KSSQ4qlqSaM7B6rcj9rDZgADh/IGDzLQ==} + '@oxfmt/binding-linux-riscv64-gnu@0.72.0': + resolution: {integrity: sha512-qTY7z/iyF/rWI2HWwWMevXqZW5PZUOofUy2afK7v6rseXnpld3yjgyIk6pCux3DqWQ0lB+f5atvLJNBwzLvFsw==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [riscv64] + os: [linux] + libc: [glibc] + + '@oxfmt/binding-linux-riscv64-musl@0.72.0': + resolution: {integrity: sha512-w18qfo8vEMD6EKEHtzTvjc6jH8yCyXIT1Bn7PFWJd0PpZyzH/VQDx4xvjCh0LGz6GRzqiezbisTdG9AoXtpkZQ==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [riscv64] + os: [linux] + libc: [musl] + + '@oxfmt/binding-linux-s390x-gnu@0.72.0': + resolution: {integrity: sha512-grW8HwEir+vK7fCcq1ZgL3NHQCfZ1PidiERE9Hbf+24xGys+DoDaoqTht4ccfn0PcBwbViLkwkda/KNpJu2KRQ==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [s390x] os: [linux] libc: [glibc] - '@rolldown/binding-linux-x64-gnu@1.0.2': - resolution: {integrity: sha512-0+bOkiQ779+r1WpoHOWHqncvyySci0vKph+myNDYb+im6meJAzHQXay6oEgnkHuUGouM1LKTZwqKpBow6Kj7CQ==} + '@oxfmt/binding-linux-x64-gnu@0.72.0': + resolution: {integrity: sha512-wQJRQfWBRIn88Pkr449EUViwRayrYUScg7ty+Ihiem21mQ9BUA6S9e/4RAgyNk9lMag0KBouyKskjq61rOC0WQ==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [x64] os: [linux] libc: [glibc] - '@rolldown/binding-linux-x64-musl@1.0.2': - resolution: {integrity: sha512-mjSkrzZK5Qsl0a9d1JgILOiuZOSDTVdKENcSXBoqbzSrspLR/4/IRVDo5wd2GgZjNss/viBFJdeq+j7qH2nypw==} + '@oxfmt/binding-linux-x64-musl@0.72.0': + resolution: {integrity: sha512-T1B0uQzafxuGPjjaPrZJ497nxoi1BiI65JfsYc56nNpmev3msfb/iaTmH2kQuNFqa8PKxpMlf3GhWrWBvBPORQ==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [x64] os: [linux] libc: [musl] - '@rolldown/binding-openharmony-arm64@1.0.2': - resolution: {integrity: sha512-1v5vHasdfQAZoEHakBV72LIFAC9JjnymsiKxp+GEr/ma3+NJCPSaYK+qavInOovJkgwFrs7GccX2d6IgDA3Z5w==} + '@oxfmt/binding-openharmony-arm64@0.72.0': + resolution: {integrity: sha512-OKxxgTurl7+hypVplO8QRAbbQlwEs0j1Y6FZv+ZN2qNbdxQk2muMlzuJNBGTCWwxQ0rkkOD1uK/DW1fvzoUSJA==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [arm64] os: [openharmony] - '@rolldown/binding-wasm32-wasi@1.0.2': - resolution: {integrity: sha512-mb1VobWn6NheziTk5/WEaR6AKVbrwT5sOi6C7zk3gy/pD1qtJfU1j4PgTo2NJnOtbL9Dl3Aeei8w9jJ7qC2jZQ==} + '@oxfmt/binding-win32-arm64-msvc@0.72.0': + resolution: {integrity: sha512-L78TKzURqvjuZOCo88ixMbPXf/RP7u31Y0cmjJ/KEmrHmfYhmhEvsDaTYPiyTRne64jUgCbADfY0YeoQk4/Xyw==} engines: {node: ^20.19.0 || >=22.12.0} - cpu: [wasm32] + cpu: [arm64] + os: [win32] - '@rolldown/binding-win32-arm64-msvc@1.0.2': - resolution: {integrity: sha512-SqKonF56vA/L2yHwHYcEp2P34URpOZ7d1fS635cTkpDnUtEGdUbhI6NzsPdqeSWvAAeGDrxjWjNmibDIdFf9/A==} + '@oxfmt/binding-win32-ia32-msvc@0.72.0': + resolution: {integrity: sha512-gQtFW+Ii8BWgtJTAD17TY/WsdNS5gUfAPov8iI1jeq4r9lRRYImmxPL08UU00/RLaCfBJwh2UgINnDY0iOiZ+A==} engines: {node: ^20.19.0 || >=22.12.0} - cpu: [arm64] + cpu: [ia32] os: [win32] - '@rolldown/binding-win32-x64-msvc@1.0.2': - resolution: {integrity: sha512-v7qRI7gXLRINcOGXt+7YmAZ6iFuyZVMIoXAxhd8oP+DR9dLfL9GfNIx7PLMxmhZdvq8waUJBQiWN9EKNy+TRBQ==} + '@oxfmt/binding-win32-x64-msvc@0.72.0': + resolution: {integrity: sha512-uhooIN+DCzWX5uH7mQkSVCsODqsGluGJ4n3ch4+mITtWKpESMDjUJUSDBAVFuYQDwqY2McPDKdKJbM0oZVW/Qw==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [x64] os: [win32] - '@rolldown/pluginutils@1.0.1': - resolution: {integrity: sha512-2j9bGt5Jh8hj+vPtgzPtl72j0yRxHAyumoo6TNfAjsLB04UtpSvPbPcDcBMxz7n+9CYB0c1GxQFxYRg2jimqGw==} + '@oxlint-tsgolint/darwin-arm64@7.0.2003': + resolution: {integrity: sha512-TgV33rXr6ueXBwvc+0nssUkTBSXHJxv77I8p4RCjDjCnvexHtmoPiudVRDfj3S3puMDdeQdHW6jdUgUPy3nr/w==} + cpu: [arm64] + os: [darwin] + + '@oxlint-tsgolint/darwin-x64@7.0.2003': + resolution: {integrity: sha512-hY3FMAjIaPDdK3FNxyRXRfHPOFeoBn6dmnLyKZgQ2IbTTD1adXhl6PfCIqHhCPvurigv7nf7mYuWZZ19MmJzmg==} + cpu: [x64] + os: [darwin] + + '@oxlint-tsgolint/linux-arm64@7.0.2003': + resolution: {integrity: sha512-eAET4JpyfBbg8SO0K74o4R55tEjVC292pIyxGOw2XGf8x/HrPNyxwQ478jMYOM54FIVOHc8sJ6VRHxluy6lucw==} + cpu: [arm64] + os: [linux] + + '@oxlint-tsgolint/linux-x64@7.0.2003': + resolution: {integrity: sha512-GXdyO/XyqDJ3s/llR/oOktLsYNjZtWSQBy0JMc+/0gsNPvTcseKPhn9c6KcFyWmnr6H4vljYALbujonqSzzEGw==} + cpu: [x64] + os: [linux] + + '@oxlint-tsgolint/win32-arm64@7.0.2003': + resolution: {integrity: sha512-TWauXnPfet0VgpmrstoAK58eJ4gbuwPUuUTQmYQAzE/RG1CpnxXtrKUJULf6lyerPE4KN3gM+DflIA1hOH+38Q==} + cpu: [arm64] + os: [win32] + + '@oxlint-tsgolint/win32-x64@7.0.2003': + resolution: {integrity: sha512-DoRmfe7j8VqNlukp8liRVW45GQDhzRccNenjD/pdzelgtffW47pCMd1xbJLkPaPbKnTwID3onn3VZlL7JbebEA==} + cpu: [x64] + os: [win32] - '@rollup/rollup-android-arm-eabi@4.60.4': - resolution: {integrity: sha512-F5QXMSiFebS9hKZj02XhWLLnRpJ3B3AROP0tWbFBSj+6kCbg5m9j5JoHKd4mmSVy5mS/IMQloYgYxCuJC0fxEQ==} + '@oxlint/binding-android-arm-eabi@1.87.0': + resolution: {integrity: sha512-22tVIHlB9898OYsBKmYB6He1MCSqdTmK1BVWuSNUTVrpS0KWNege+U9uEYqGJ+KatQag0r/oU44i3OOs46z7HQ==} + engines: {node: ^20.19.0 || >=22.12.0} cpu: [arm] os: [android] - '@rollup/rollup-android-arm64@4.60.4': - resolution: {integrity: sha512-GxxTKApUpzRhof7poWvCJHRF51C67u1R7D6DiluBE8wKU1u5GWE8t+v81JvJYtbawoBFX1hLv5Ei4eVjkWokaw==} + '@oxlint/binding-android-arm64@1.87.0': + resolution: {integrity: sha512-JfZQ3MWf74n8ov0DojZpkOduIubYJvZL5tbTMMhDutsal6u1xH1QuNpN+lK+EQwF8AeAfgwTR2fgh5U2br27XA==} + engines: {node: ^20.19.0 || >=22.12.0} cpu: [arm64] os: [android] - '@rollup/rollup-darwin-arm64@4.60.4': - resolution: {integrity: sha512-tua0TaJxMOB1R0V0RS1jFZ/RpURFDJIOR2A6jWwQeawuFyS4gBW+rntLRaQd0EQ4bd6Vp44Z2rXW+YYDBsj6IA==} + '@oxlint/binding-darwin-arm64@1.87.0': + resolution: {integrity: sha512-rkx6Xvvw93z2GImVOyvbZwAZcoY0Z6MJW177XNsSB+i/CBsH7ANyelTp01VZyIkM3iSXtqKVb4+ptBgPHap5pQ==} + engines: {node: ^20.19.0 || >=22.12.0} cpu: [arm64] os: [darwin] - '@rollup/rollup-darwin-x64@4.60.4': - resolution: {integrity: sha512-CSKq7MsP+5PFIcydhAiR1K0UhEI1A2jWXVKHPCBZ151yOutENwvnPocgVHkivu2kviURtCEB6zUQw0vs8RrhMg==} + '@oxlint/binding-darwin-x64@1.87.0': + resolution: {integrity: sha512-cw5nYw7L/pQ2SgGBgOcg/Qo89llQre3vIc/kuBLhSICzu1r5s8L76H3ug6AIFsD98WpROj8/z5JVzkf4daxHaQ==} + engines: {node: ^20.19.0 || >=22.12.0} cpu: [x64] os: [darwin] - '@rollup/rollup-freebsd-arm64@4.60.4': - resolution: {integrity: sha512-+O8OkVdyvXMtJEciu2wS/pzm1IxntEEQx3z5TAVy4l32G0etZn+RsA48ARRrFm6Ri8fvqPQfgrvNxSjKAbnd3g==} - cpu: [arm64] - os: [freebsd] - - '@rollup/rollup-freebsd-x64@4.60.4': - resolution: {integrity: sha512-Iw3oMskH3AfNuhU0MSN7vNbdi4me/NiYo2azqPz/Le16zHSa+3RRmliCMWWQmh4lcndccU40xcJuTYJZxNo/lw==} + '@oxlint/binding-freebsd-x64@1.87.0': + resolution: {integrity: sha512-x/SuznE6loRZK9gz9AS4ZmzNzSu+5KdeoCS+sVHhnrE8HM6AD8UyTjwldQTTqlXGvkwHASgB0NiTNOc3o3PdAg==} + engines: {node: ^20.19.0 || >=22.12.0} cpu: [x64] os: [freebsd] - '@rollup/rollup-linux-arm-gnueabihf@4.60.4': - resolution: {integrity: sha512-EIPRXTVQpHyF8WOo219AD2yEltPehLTcTMz2fn6JsatLYSzQf00hj3rulF+yauOlF9/FtM2WpkT/hJh/KJFGhA==} + '@oxlint/binding-linux-arm-gnueabihf@1.87.0': + resolution: {integrity: sha512-+JEDmCAXiiFmKhAjZhgAOwaHWY6WENOQ5QA+lFuY59tgsUCDOTRi46B+lENK6CWvtxsXpwkZXNOgpjAQcpIpnA==} + engines: {node: ^20.19.0 || >=22.12.0} cpu: [arm] os: [linux] - libc: [glibc] - '@rollup/rollup-linux-arm-musleabihf@4.60.4': - resolution: {integrity: sha512-J3Yh9PzzF1Ovah2At+lHiGQdsYgArxBbXv/zHfSyaiFQEqvNv7DcW98pCrmdjCZBrqBiKrKKe2V+aaSGWuBe/w==} + '@oxlint/binding-linux-arm-musleabihf@1.87.0': + resolution: {integrity: sha512-/uC9XqXLAC9AHFLNycwmSQEvKqGrOCgU+uXALI7gNW0DG81TJ/3T2eHnKdRvcb5R1gFEfjxDwI4e4zIa29rICQ==} + engines: {node: ^20.19.0 || >=22.12.0} cpu: [arm] os: [linux] - libc: [musl] - '@rollup/rollup-linux-arm64-gnu@4.60.4': - resolution: {integrity: sha512-BFDEZMYfUvLn37ONE1yMBojPxnMlTFsdyNoqncT0qFq1mAfllL+ATMMJd8TeuVMiX84s1KbcxcZbXInmcO2mRg==} + '@oxlint/binding-linux-arm64-gnu@1.87.0': + resolution: {integrity: sha512-FTRYPsBy/M728vnUv7l+KAwSAa3Qi4BQbhVvy7NYuMTZ0sUa/OWsbMx79HVFw6u/5KWcaYayDsoOiv7xbevzDg==} + engines: {node: ^20.19.0 || >=22.12.0} cpu: [arm64] os: [linux] libc: [glibc] - '@rollup/rollup-linux-arm64-musl@4.60.4': - resolution: {integrity: sha512-pc9EYOSlOgdQ2uPl1o9PF6/kLSgaUosia7gOuS8mB69IxJvlclko1MECXysjs5ryez1/5zjYqx3+xYU0TU6R1A==} + '@oxlint/binding-linux-arm64-musl@1.87.0': + resolution: {integrity: sha512-6sC+N7IY0h1egd8SrYUGNN7CUyKJZ4bIZakKa7V7hfinf4jfi8Kq4+klJvFp5DqXcwMn44I5MeBwmJluO1odFg==} + engines: {node: ^20.19.0 || >=22.12.0} cpu: [arm64] os: [linux] libc: [musl] - '@rollup/rollup-linux-loong64-gnu@4.60.4': - resolution: {integrity: sha512-NxnomyxYerDh5n4iLrNa+sH+Z+U4BMEE46V2PgQ/hoB909i8gV1M5wPojWg9fk1jWpO3IQnOs20K4wyZuFLEFQ==} - cpu: [loong64] - os: [linux] - libc: [glibc] - - '@rollup/rollup-linux-loong64-musl@4.60.4': - resolution: {integrity: sha512-nbJnQ8a3z1mtmrwImCYhc6BGpThAyYVRQxw9uKSKG4wR6aAYno9sVjJ0zaZcW9BPJX1GbrDPf+SvdWjgTuDmnw==} - cpu: [loong64] - os: [linux] - libc: [musl] - - '@rollup/rollup-linux-ppc64-gnu@4.60.4': - resolution: {integrity: sha512-2EU6acNrQLd8tYvo/LXW535wupT3m6fo7HKo6lr7ktQoItxTyOL1ZCR/GfGCuXl2vR+zmfI6eRXkSemafv+iVg==} + '@oxlint/binding-linux-ppc64-gnu@1.87.0': + resolution: {integrity: sha512-d5Mnn+ICbhoJPi37Ccjy+86T5qfH2+mSTrTMBXNPfB+wqSWOayBBLLIrCXdUy1YssluCVdGkl+DO9w5r5ODmUA==} + engines: {node: ^20.19.0 || >=22.12.0} cpu: [ppc64] os: [linux] libc: [glibc] - '@rollup/rollup-linux-ppc64-musl@4.60.4': - resolution: {integrity: sha512-WeBtoMuaMxiiIrO2IYP3xs6GMWkJP2C0EoT8beTLkUPmzV1i/UcOSVw1d5r9KBODtHKilG5yFxsGRnBbK3wJ4A==} - cpu: [ppc64] - os: [linux] - libc: [musl] - - '@rollup/rollup-linux-riscv64-gnu@4.60.4': - resolution: {integrity: sha512-FJHFfqpKUI3A10WrWKiFbBZ7yVbGT4q4B5o1qKFFojqpaYoh9LrQgqWCmmcxQzVSXYtyB5bzkXrYzlHTs21MYA==} + '@oxlint/binding-linux-riscv64-gnu@1.87.0': + resolution: {integrity: sha512-sf5YWuUo1Dx9loqWCgF8P278MycfcD4JHjDC/LV5j11L3Lqa4PFiwCjD6B8kP4AjsgZZkLn3xbpJEtZBaccs4Q==} + engines: {node: ^20.19.0 || >=22.12.0} cpu: [riscv64] os: [linux] libc: [glibc] - '@rollup/rollup-linux-riscv64-musl@4.60.4': - resolution: {integrity: sha512-mcEl6CUT5IAUmQf1m9FYSmVqCJlpQ8r8eyftFUHG8i9OhY7BkBXSUdnLH5DOf0wCOjcP9v/QO93zpmF1SptCCw==} + '@oxlint/binding-linux-riscv64-musl@1.87.0': + resolution: {integrity: sha512-+aCraYbmK3uU3P0rFojjeaK9pGY6JvBDXh/0CM+gPyWKmNmXmnFe0qj4qj02U4ibFpbd9Pa9CmDvIdjJUd29rA==} + engines: {node: ^20.19.0 || >=22.12.0} cpu: [riscv64] os: [linux] libc: [musl] - '@rollup/rollup-linux-s390x-gnu@4.60.4': - resolution: {integrity: sha512-ynt3JxVd2w2buzoKDWIyiV1pJW93xlQic1THVLXilz429oijRpSHivZAgp65KBu+cMcgf1eVVjdnTLvPxgCuoQ==} + '@oxlint/binding-linux-s390x-gnu@1.87.0': + resolution: {integrity: sha512-7rzbZQIljo6VLWVPqUyLa2P/WV3VFsJB4M52R4NCUJn1A50yjtUsHxiMHFL9opEKGFIiTbZ3VQzmtcJOPUq4NQ==} + engines: {node: ^20.19.0 || >=22.12.0} cpu: [s390x] os: [linux] libc: [glibc] - '@rollup/rollup-linux-x64-gnu@4.60.4': - resolution: {integrity: sha512-Boiz5+MsaROEWDf+GGEwF8VMHGhlUoQMtIPjOgA5fv4osupqTVnJteQNKJwUcnUog2G55jYXH7KZFFiJe0TEzQ==} + '@oxlint/binding-linux-x64-gnu@1.87.0': + resolution: {integrity: sha512-Dry46Fa+AXE/xt9J/YriujU1pU9mzfNWHGQwtOYibAFH8HLrKWCi93AhIGnuUMiwn2JbuBkRyz51f50Ly4r4ow==} + engines: {node: ^20.19.0 || >=22.12.0} cpu: [x64] os: [linux] libc: [glibc] - '@rollup/rollup-linux-x64-musl@4.60.4': - resolution: {integrity: sha512-+qfSY27qIrFfI/Hom04KYFw3GKZSGU4lXus51wsb5EuySfFlWRwjkKWoE9emgRw/ukoT4Udsj4W/+xxG8VbPKg==} + '@oxlint/binding-linux-x64-musl@1.87.0': + resolution: {integrity: sha512-f/HMZez74gJvnRpGz+b5sdGyWrEgRLmFTXg9sNoqjPWogGxYHUhXKTKyMiYsu9rUGR2ciYqL3fk+Ua4JhMJbRA==} + engines: {node: ^20.19.0 || >=22.12.0} cpu: [x64] os: [linux] libc: [musl] - '@rollup/rollup-openbsd-x64@4.60.4': - resolution: {integrity: sha512-VpTfOPHgVXEBeeR8hZ2O0F3aSso+JDWqTWmTmzcQKted54IAdUVbxE+j/MVxUsKa8L20HJhv3vUezVPoquqWjA==} - cpu: [x64] - os: [openbsd] - - '@rollup/rollup-openharmony-arm64@4.60.4': - resolution: {integrity: sha512-IPOsh5aRYuLv/nkU51X10Bf75Bsf6+gZdx1X+QP5QM6lIJFHHqbHLG0uJn/hWthzo13UAc2umiUorqZy3axoZg==} + '@oxlint/binding-openharmony-arm64@1.87.0': + resolution: {integrity: sha512-lEC6ZfPZm/07JlB7FsOWX9o1RiHlXiBW594LSUGuXmekC8LZmvUPP9XzE2WZcNxJi5cHA/Fw56o02c3JxgMA5g==} + engines: {node: ^20.19.0 || >=22.12.0} cpu: [arm64] os: [openharmony] - '@rollup/rollup-win32-arm64-msvc@4.60.4': - resolution: {integrity: sha512-4QzE9E81OohJ/HKzHhsqU+zcYYojVOXlFMs1DdyMT6qXl/niOH7AVElmmEdUNHHS/oRkc++d5k6Vy85zFs0DEw==} + '@oxlint/binding-win32-arm64-msvc@1.87.0': + resolution: {integrity: sha512-afBWKOD36dUiA9AenaiWpCg8xfyUVPYznyAAVXu413brK4RA5UI/Xoc4yfeGIT8G1a44MV+JR7hioryerBDzGQ==} + engines: {node: ^20.19.0 || >=22.12.0} cpu: [arm64] os: [win32] - '@rollup/rollup-win32-ia32-msvc@4.60.4': - resolution: {integrity: sha512-zTPgT1YuHHcd+Tmx7h8aml0FWFVelV5N54oHow9SLj+GfoDy/huQ+UV396N/C7KpMDMiPspRktzM1/0r1usYEA==} + '@oxlint/binding-win32-ia32-msvc@1.87.0': + resolution: {integrity: sha512-daptNsEOuru4/nJTxV4DBHNJ5GepzmaBVQf+jjgKQGi9NRlZKik+RFq4hUL7ewsF1pwjKM+SkYaMbF82UwFGnw==} + engines: {node: ^20.19.0 || >=22.12.0} cpu: [ia32] os: [win32] - '@rollup/rollup-win32-x64-gnu@4.60.4': - resolution: {integrity: sha512-DRS4G7mi9lJxqEDezIkKCaUIKCrLUUDCUaCsTPCi/rtqaC6D/jjwslMQyiDU50Ka0JKpeXeRBFBAXwArY52vBw==} + '@oxlint/binding-win32-x64-msvc@1.87.0': + resolution: {integrity: sha512-k+m/EShyXE29J/r1fjk0HlAzhN5JhIvj044oSJkXTEkCLrWZQgXb/3r4q5LTw/eaKW/4GuMXG2xmiJs3s5rPNw==} + engines: {node: ^20.19.0 || >=22.12.0} cpu: [x64] os: [win32] - '@rollup/rollup-win32-x64-msvc@4.60.4': - resolution: {integrity: sha512-QVTUovf40zgTqlFVrKA1uXMVvU2QWEFWfAH8Wdc48IxLvrJMQVMBRjuQyUpzZCDkakImib9eVazbWlC6ksWtJw==} - cpu: [x64] - os: [win32] + '@oxlint/plugins@1.79.0': + resolution: {integrity: sha512-S0uyoxakDINJ4DPgqxGlEEvrdSMeQb7Z2lKVjxoY2gwsbZbfg2Xr8Klfeo5ZeraHmmdBCELFUHkSe6KEmBpMvg==} + engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} - '@standard-schema/spec@1.1.0': - resolution: {integrity: sha512-l2aFy5jALhniG5HgqrD6jXLi/rUWrKvqN/qJx6yoJsgKhblVd+iqqU4RCXavm/jPityDo5TCvKMnpjKnOriy0w==} + '@polka/url@1.0.0-next.29': + resolution: {integrity: sha512-wwQAWhWSuHaag8c4q/KN/vCoeOJYshAIvMQwD4GpSb3OiZklFfvAgmj0VCBBImRpuF/aFgIRzllXlVX93Jevww==} - '@tybys/wasm-util@0.10.4': - resolution: {integrity: sha512-W3c4gRigFS0T/Ma4qIYF3GDAc5AQdHb1yL5znJT1Zv1YaD9Kitx656wBjvr19qbiosmZT8lWDM5BEMynUqX65A==} + '@protobufjs/aspromise@1.1.2': + resolution: {integrity: sha512-j+gKExEuLmKwvz3OgROXtrJ2UG2x8Ch2YZUxahh+s1F2HZ+wAceUNLkvy6zKCPVRkU++ZWQrdxsUeQXmcg4uoQ==} - '@types/chai@5.2.3': - resolution: {integrity: sha512-Mw558oeA9fFbv65/y4mHtXDs9bPnFMZAL/jxdPFUpOHHIXX91mcgEHbS5Lahr+pwZFR8A7GQleRWeI6cGFC2UA==} + '@protobufjs/base64@1.1.2': + resolution: {integrity: sha512-AZkcAA5vnN/v4PDqKyMR5lx7hZttPDgClv83E//FMNhR2TMcLUhfRUBHCmSl0oi9zMgDDqRUJkSxO3wm85+XLg==} - '@types/deep-eql@4.0.2': - resolution: {integrity: sha512-c9h9dVVMigMPc4bwTvC5dxqtqJZwQPePsWjPlpSOnojbor6pGqdk541lfA7AqFQr5pB1BRdq0juY9db81BwyFw==} + '@protobufjs/codegen@2.0.5': + resolution: {integrity: sha512-zgXFLzW3Ap33e6d0Wlj4MGIm6Ce8O89n/apUaGNB/jx+hw+ruWEp7EwGUshdLKVRCxZW12fp9r40E1mQrf/34g==} - '@types/estree@1.0.8': - resolution: {integrity: sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w==} + '@protobufjs/eventemitter@1.1.1': + resolution: {integrity: sha512-vW1GmwMZNnL+gMRaovlh9yZX74kc+TTU3FObkkurpMaRtBfLP3ldjS9KQWlwZgraRE0+dheEEoAxdzcJQ8eXZg==} - '@types/estree@1.0.9': - resolution: {integrity: sha512-GhdPgy1el4/ImP05X05Uw4cw2/M93BCUmnEvWZNStlCzEKME4Fkk+YpoA5OiHNQmoS7Cafb8Xa3Pya8m1Qrzeg==} + '@protobufjs/fetch@1.1.1': + resolution: {integrity: sha512-GpptLrs57adMSuHi3VNj0mAF8dwh36LMaYF6XyJ6JMWlVsc+t42tm1HSEDmOs3A8fC9yyeisgLhsTVQokOZ0zw==} - '@types/node@22.19.19': - resolution: {integrity: sha512-dyh/xO2Fh5bYrfWaaqGrRQQGkNdmYw6AmaAUvYeUMNTWQtvb796ikLdmTchRmOlOiIJ1TDXfWgVx1QkUlQ6Hew==} + '@protobufjs/float@1.0.2': + resolution: {integrity: sha512-Ddb+kVXlXst9d+R9PfTIxh1EdNkgoRe5tOX6t01f1lYWOvJnSPDBlG241QLzcyPdoNTsblLUdujGSE4RzrTZGQ==} + + '@protobufjs/path@1.1.2': + resolution: {integrity: sha512-6JOcJ5Tm08dOHAbdR3GrvP+yUUfkjG5ePsHYczMFLq3ZmMkAD98cDgcT2iA1lJ9NVwFd4tH/iSSoe44YWkltEA==} + + '@protobufjs/pool@1.1.0': + resolution: {integrity: sha512-0kELaGSIDBKvcgS4zkjz1PeddatrjYcmMWOlAuAPwAeccUrPHdUqo/J6LiymHHEiJT5NrF1UVwxY14f+fy4WQw==} + + '@protobufjs/utf8@1.1.1': + resolution: {integrity: sha512-oOAWABowe8EAbMyWKM0tYDKi8Yaox52D+HWZhAIJqQXbqe0xI/GV7FhLWqlEKreMkfDjshR5FKgi3mnle0h6Eg==} + + '@testing-library/dom@10.4.2': + resolution: {integrity: sha512-yzr2S9HyAIdhz2/6qHgbs665Q7PKVcDF05vsOlHPxG1mo36gKVesdYVeDLnXgfjJ03CrKRk08knc6+E/9m8v2Q==} + engines: {node: '>=18'} + + '@testing-library/user-event@14.6.8': + resolution: {integrity: sha512-vgSSqnANMnkcYR/vUNDbwS7aM3OErRLNUG8vCVVlj4oc6cKffUrccX7UAS5AmJQQNZTSibi9z5GvvRZ0aYgUCQ==} + engines: {node: '>=12', npm: '>=6'} + peerDependencies: + '@testing-library/dom': '>=7.21.4' + + '@types/aria-query@5.0.4': + resolution: {integrity: sha512-rfT93uj5s0PRL7EzccGMs3brplhcrghnDoV26NqKhCAS1hVo+WdNsPvE/yb6ilfr5hi2MEk6d5EWJTKdxg8jVw==} + + '@types/chai@5.2.3': + resolution: {integrity: sha512-Mw558oeA9fFbv65/y4mHtXDs9bPnFMZAL/jxdPFUpOHHIXX91mcgEHbS5Lahr+pwZFR8A7GQleRWeI6cGFC2UA==} + + '@types/deep-eql@4.0.2': + resolution: {integrity: sha512-c9h9dVVMigMPc4bwTvC5dxqtqJZwQPePsWjPlpSOnojbor6pGqdk541lfA7AqFQr5pB1BRdq0juY9db81BwyFw==} + + '@types/estree@1.0.9': + resolution: {integrity: sha512-GhdPgy1el4/ImP05X05Uw4cw2/M93BCUmnEvWZNStlCzEKME4Fkk+YpoA5OiHNQmoS7Cafb8Xa3Pya8m1Qrzeg==} + + '@types/node@22.19.19': + resolution: {integrity: sha512-dyh/xO2Fh5bYrfWaaqGrRQQGkNdmYw6AmaAUvYeUMNTWQtvb796ikLdmTchRmOlOiIJ1TDXfWgVx1QkUlQ6Hew==} '@types/set-cookie-parser@2.4.10': resolution: {integrity: sha512-GGmQVGpQWUe5qglJozEjZV/5dyxbOOZ0LHe/lqyWssB88Y4svNfst0uqBVscdDeIKl5Jy5+aPSvy7mI9tYRguw==} @@ -1009,11 +870,18 @@ packages: '@aws-sdk/credential-provider-web-identity': optional: true - '@vitest/expect@4.1.11': - resolution: {integrity: sha512-VX2x5vNJXET47KAFzwERI+KRMtTTCSWTfSMKsW7JsUsXV4psq++e3DvZpuTDOpHcxytiDs6p2nhVb2tVDiiUYw==} + '@vitest/browser-preview@5.0.3': + resolution: {integrity: sha512-jfnEBv5yDRPR1MeiTlTW5bcZoPsiI7MBhpZR8IJiSxVhdK6Pa4gPk3tLHTRxVHy24kkWxY2TnUbnszU3J1bQJw==} + peerDependencies: + vitest: 5.0.3 + + '@vitest/browser@5.0.3': + resolution: {integrity: sha512-7KHIkVA/gTRxQV9qPa9qLK+g5INZG6iRrlwXSFG5EKsOomW3UzzW7wsdt1Yp5PufzQ43lniuONlb4lUVCC2qSg==} + peerDependencies: + vitest: 5.0.3 - '@vitest/mocker@4.1.11': - resolution: {integrity: sha512-2XJVD55d1o5AZous5CCGKS74g/riOj9odEt2bQpCVZeblHyHdnMeFl4jl0XjU21stf4mbjUkew2eXQZt65g5CQ==} + '@vitest/mocker@5.0.3': + resolution: {integrity: sha512-T8sWAIbkSyAjkwTcaEc3Iu0o9A27X1/kdXrizhZkGuSKScRQtRzclfAMpOTcGdXCsqxeWlpGy3XjqaW8CpLORg==} peerDependencies: msw: ^2.4.9 vite: ^8.0.5 @@ -1023,24 +891,302 @@ packages: vite: optional: true - '@vitest/pretty-format@4.1.11': - resolution: {integrity: sha512-yiZzPbGTS9Sr/JpFl8zHrcIkAofNbFV6k21vIgQN/cY/oxZeXhJv5sc/MBJ5jFKWmWs+oJHw0UXLZjmf931+Vw==} + '@vitest/pretty-format@5.0.3': + resolution: {integrity: sha512-3hr5eV5z2ff0l2Q1vaKQbB4j3M3tElw8stDokEOTqXJB3Es5VrT8gBRIMY59gGuFp8Y61sWlWFK1aNUH5dHDYw==} + + '@vitest/snapshot@5.0.3': + resolution: {integrity: sha512-SK4YTg9jL+jwakHIZxfciuvyOQsbY15AueR3USGtsXzmy+3FbJgqjAbC+ac20mRLwfEDtO5sfTqK6bUCDTRVPA==} + + '@vitest/spy@5.0.3': + resolution: {integrity: sha512-XhFysQTB8AZ+P4gMi+Lpo99vg2AZi0qKpaB9yXQl37+CaMEAPO3iH/wGVnSyL5MPERiLezpqTVtrR6UZH5GCXg==} + + '@vitest/ui@5.0.3': + resolution: {integrity: sha512-9dfvqDBvSbHM6y9YVXxAypkl2z7mrWozK/JFo237qI0cj/3M/kzDKiVZqzi3TE54Ler7Jditud7OMBZCyUvRHA==} + peerDependencies: + vitest: 5.0.3 + + '@vitest/utils@5.0.3': + resolution: {integrity: sha512-77fDTZv8eu1CzlIfgPU2yrl5MXSUaUAOvgdZEeVm3X3WIxUubIX2SforqS6176lT32HCF6uBVMYVqfHxw7o6eg==} + + '@voidzero-dev/vite-plus-core@1.1.0': + resolution: {integrity: sha512-Ai6HQP1zG0H3ZrmStEaMVKtDzW0xU3VgMzm3LFaXDQMEp8nAqvp0VGoU8HmpB9xv9vDSl+UxiSNOpuY9T714OQ==} + engines: {node: ^20.19.0 || ^22.18.0 || >=24.11.0} + peerDependencies: + '@arethetypeswrong/core': ^0.18.1 + '@types/node': ^20.19.0 || >=22.12.0 + '@typescript/native-preview': '*' + '@vitejs/devtools': ^0.7.1 + '@volar/typescript': ~2.4.0 + '@vue/language-core': ~3.2.0 || ~3.3.0 + esbuild: ^0.27.0 || ^0.28.0 + jiti: '>=1.21.0' + less: ^4.0.0 + publint: ^0.3.8 + sass: ^1.70.0 + sass-embedded: ^1.70.0 + stylus: '>=0.54.8' + sugarss: ^5.0.0 + terser: ^5.16.0 + tsx: ^4.8.1 + typescript: ^5.0.0 || ^6.0.0 || ^7.0.0 + unplugin-unused: '>=0.5.0' + unrun: '*' + vue-tsc: ~3.2.0 || ~3.3.0 + yaml: ^2.4.2 + peerDependenciesMeta: + '@arethetypeswrong/core': + optional: true + '@types/node': + optional: true + '@typescript/native-preview': + optional: true + '@vitejs/devtools': + optional: true + '@volar/typescript': + optional: true + '@vue/language-core': + optional: true + esbuild: + optional: true + jiti: + optional: true + less: + optional: true + publint: + optional: true + sass: + optional: true + sass-embedded: + optional: true + stylus: + optional: true + sugarss: + optional: true + terser: + optional: true + tsx: + optional: true + typescript: + optional: true + unplugin-unused: + optional: true + unrun: + optional: true + vue-tsc: + optional: true + yaml: + optional: true + + '@voidzero-dev/vite-plus-darwin-arm64@1.1.0': + resolution: {integrity: sha512-iWJdQZBRZWB6X1Aadk0Rrs/NJeP32Qb0OGeWnu1AYL0g6ZCj1yy3l9Oa+HasDyG97s+4UYo523LdBAAv3dlFwg==} + engines: {node: '>=20.0.0'} + cpu: [arm64] + os: [darwin] + + '@voidzero-dev/vite-plus-darwin-x64@1.1.0': + resolution: {integrity: sha512-IYSQ2KdigRMuVkKCC+e5uNliCTSHa8j3ajHsFG2cfXoiGHS1f4ausbMEzNYtn9Tcn0Xsyo7l0LN8lvB7MapJhA==} + engines: {node: '>=20.0.0'} + cpu: [x64] + os: [darwin] + + '@voidzero-dev/vite-plus-linux-arm64-gnu@1.1.0': + resolution: {integrity: sha512-PmrJG8Md5KCWi/akWJz5GRjei2QfRf48R4rPB9CfualzAAlpMOmaMGeV3eloWVAFye85uhdtzCmIAqN5OUVBJA==} + engines: {node: '>=20.0.0'} + cpu: [arm64] + os: [linux] + libc: [glibc] + + '@voidzero-dev/vite-plus-linux-arm64-musl@1.1.0': + resolution: {integrity: sha512-zFr57++m5U474XEMcFGV025TOv9V2I5d87yylcuI/SvxQwDE9P7T5iRl55vGzQOzzInSnQuru6ByNIfmGa+84A==} + engines: {node: '>=20.0.0'} + cpu: [arm64] + os: [linux] + libc: [musl] + + '@voidzero-dev/vite-plus-linux-x64-gnu@1.1.0': + resolution: {integrity: sha512-DXeRuTMylEbIVCQX+pSBM27lSPQwAKO6PDnGBJ6nFGhu37AmNeVl7cIChtcniXOuykbFduhSt3eIr/6CZHoEag==} + engines: {node: '>=20.0.0'} + cpu: [x64] + os: [linux] + libc: [glibc] + + '@voidzero-dev/vite-plus-linux-x64-musl@1.1.0': + resolution: {integrity: sha512-J7/Mb/kxtENUsL5Gh+8SSr/p054fSqU4oj0GEthr/2VEFSwZouOUigk6CzE7ANKFeTNJm40n8QCnjm7Oiy09pQ==} + engines: {node: '>=20.0.0'} + cpu: [x64] + os: [linux] + libc: [musl] + + '@voidzero-dev/vite-plus-win32-arm64-msvc@1.1.0': + resolution: {integrity: sha512-EXWz8Tt7skmI0fLMSq5zYcuHo6SCF0reG55qDB/NMvWVIAca6Dborq7D1HyKA8v6p39zOl8r07gR7yXuYv7eeQ==} + engines: {node: '>=20.0.0'} + cpu: [arm64] + os: [win32] + + '@voidzero-dev/vite-plus-win32-x64-msvc@1.1.0': + resolution: {integrity: sha512-K9k77Mj/TszFHKsZePKsyfsIPo8HbjKeJUxaSJ6YZKAmSh9QvDsMFe4i4/HALwyii/PR1EQ8N0iN1nf9cNimGQ==} + engines: {node: '>=20.0.0'} + cpu: [x64] + os: [win32] + + '@yuku-codegen/binding-android-arm64@0.9.5': + resolution: {integrity: sha512-jrOY5WM+AaAqkv51fHP1x28ifto4WgcZVzodLUyMU1jMWn5Sq+VciRdk/n8E0Ey5w4p4cWWE+m5GfXWOYh7Kzw==} + cpu: [arm64] + os: [android] + deprecated: yuku-codegen is pure JavaScript since 0.14 - '@vitest/runner@4.1.11': - resolution: {integrity: sha512-LztvUgdwMNJMIkj3hQnnxiC2Xy1zNxq928W/xhjCLaNCzqTZOudjwbQf6v9IntZGPw132i2Lq2rgTRZHD3JHNw==} + '@yuku-codegen/binding-darwin-arm64@0.9.5': + resolution: {integrity: sha512-4O4lkCQIzPZGjiNB1GORSI6hBECbiiSBS/APZXPvNKYH+nhY4uuqv03LNXA+SET3hoBjvr95P5rIhY8KQaQUBA==} + cpu: [arm64] + os: [darwin] + deprecated: yuku-codegen is pure JavaScript since 0.14 - '@vitest/snapshot@4.1.11': - resolution: {integrity: sha512-pN7ikn1ON7h8ee4gIAp4AzyK+zBtJPzVbqOgu5LCEh4VaJVbPQcgYQYJIMGQPXVeJJq1fnfazis7a5pFNPahog==} + '@yuku-codegen/binding-darwin-x64@0.9.5': + resolution: {integrity: sha512-9EvoUO0SEhD6/d8VHdWuPerepPMSR1y84+UEgz8Un1Ope14Oe7xMvePpEQLTLovoIFZ8Zg3iZ8z8E11pZMqC3g==} + cpu: [x64] + os: [darwin] + deprecated: yuku-codegen is pure JavaScript since 0.14 + + '@yuku-codegen/binding-freebsd-x64@0.9.5': + resolution: {integrity: sha512-HqT78WwgHTmp8lwujoUa9CrIortX4DdpuiVC18ZPSGvuuJf4ylpIEI6QrQEM78Zwz3muhAWAOXZ5irdiYY+AyA==} + cpu: [x64] + os: [freebsd] + deprecated: yuku-codegen is pure JavaScript since 0.14 + + '@yuku-codegen/binding-linux-arm-gnu@0.9.5': + resolution: {integrity: sha512-QJXwIW6Ms3QIawbcBIedmrmXnfdpuAOjZJ/eAABq5XTzWvSxZ7lutu9W5yIHahlaTaFnBo7ikrVMD1szLTpPUw==} + cpu: [arm] + os: [linux] + libc: [glibc] + deprecated: yuku-codegen is pure JavaScript since 0.14 + + '@yuku-codegen/binding-linux-arm-musl@0.9.5': + resolution: {integrity: sha512-eHbFy3IHGb+IYarrYwAo0yWSEQV3eAmEn6VrsKA6I1Wy1YPJxWqSJlV69JhqsHtJuJlljUIF3ex0y46yaKIu9w==} + cpu: [arm] + os: [linux] + libc: [musl] + deprecated: yuku-codegen is pure JavaScript since 0.14 + + '@yuku-codegen/binding-linux-arm64-gnu@0.9.5': + resolution: {integrity: sha512-ByoJMbySTaDhjAXSu8q6Lh7HKg3YoesXpcT72aYk0Aiw4PCznmY4ybpLTq0RCvp0RIPhFm/6yECFiGBwyCC1nw==} + cpu: [arm64] + os: [linux] + libc: [glibc] + deprecated: yuku-codegen is pure JavaScript since 0.14 + + '@yuku-codegen/binding-linux-arm64-musl@0.9.5': + resolution: {integrity: sha512-gD9vfXIoBw1toSxhO9rgmSu/FEfy3PMznJAxVjIuH8DrWEiDKXmJO0pJKfj1Ltbe/TmtWVZR+TjISqeSIGQzMg==} + cpu: [arm64] + os: [linux] + libc: [musl] + deprecated: yuku-codegen is pure JavaScript since 0.14 + + '@yuku-codegen/binding-linux-x64-gnu@0.9.5': + resolution: {integrity: sha512-BARvdnvqMGjOr5Iel2JH+9H5vAIE0R6H0Z2fsT02xrvmI/1ZXNB8lkuX+ZGevPhZb3zJ9WeC/R8JZstrsUOK5g==} + cpu: [x64] + os: [linux] + libc: [glibc] + deprecated: yuku-codegen is pure JavaScript since 0.14 + + '@yuku-codegen/binding-linux-x64-musl@0.9.5': + resolution: {integrity: sha512-x8flcevS1fbb7ESrmpOY/pON4eInSaE/7Ktjqx2udOE2W33BNSZmJuwpyUgo54AoHNqrcaM7szqydyJn5fNvew==} + cpu: [x64] + os: [linux] + libc: [musl] + deprecated: yuku-codegen is pure JavaScript since 0.14 + + '@yuku-codegen/binding-win32-arm64@0.9.5': + resolution: {integrity: sha512-KOL/rBatWqH4ZpCNoF8ZtSNdOJbAxBJLmk/VeRciepGROPTbPum1A9t67GpFgOU7qrkUWlPJdJ+KHKbvbmOt+w==} + cpu: [arm64] + os: [win32] + deprecated: yuku-codegen is pure JavaScript since 0.14 + + '@yuku-codegen/binding-win32-x64@0.9.5': + resolution: {integrity: sha512-FxENahEjWSan59Syh/us/Kf4wNq8qrJLFJ3R2N4Oiwtb6yNdz/rWLNTIoNSssnsp7IoWJdUP6o/Z8ppg7lXMcg==} + cpu: [x64] + os: [win32] + deprecated: yuku-codegen is pure JavaScript since 0.14 + + '@yuku-parser/binding-android-arm64@0.9.5': + resolution: {integrity: sha512-A2JCFCSHfnficqYEw4Iujpx7XrkMM3UfFcgJFmYpZSo9zM4nyhmdIIE0FogSduuU60lhM0/UcfuUBXIVNBMlGQ==} + cpu: [arm64] + os: [android] + deprecated: yuku-parser runs on yuku-core since 0.14 + + '@yuku-parser/binding-darwin-arm64@0.9.5': + resolution: {integrity: sha512-3PiyU+Eare4YuKaQ22N98/yAiROPY5o/NQJHraICzDvk4pgS+m+bgLKOvkGBRn33OnV95Vdv1Mn38b+MQHpULQ==} + cpu: [arm64] + os: [darwin] + deprecated: yuku-parser runs on yuku-core since 0.14 + + '@yuku-parser/binding-darwin-x64@0.9.5': + resolution: {integrity: sha512-blFMAFI7AInI83XaiOF8cIeiRM46Nz9EfpZtZPRLbKxSA9aAr5v8aHYpmfN6NoyXxAv/10pwS4gsAuc1W6fy5Q==} + cpu: [x64] + os: [darwin] + deprecated: yuku-parser runs on yuku-core since 0.14 + + '@yuku-parser/binding-freebsd-x64@0.9.5': + resolution: {integrity: sha512-TsNuL4qsZdO0tHp5GW43Y5fHeLPpPHA675wdcPNTcdq2ZO5AvsQWFFoiDg8CH4oBktfsQ9tdvKhjLnLYwKvp4g==} + cpu: [x64] + os: [freebsd] + deprecated: yuku-parser runs on yuku-core since 0.14 + + '@yuku-parser/binding-linux-arm-gnu@0.9.5': + resolution: {integrity: sha512-b0afYK5gHeV8RdmOcqAlgM8ONsye4cax4DMnIBaoJyPMd1UTGvTbpkqQcPVdhmHlcfcGVGMV1laeVovlr/dscw==} + cpu: [arm] + os: [linux] + libc: [glibc] + deprecated: yuku-parser runs on yuku-core since 0.14 + + '@yuku-parser/binding-linux-arm-musl@0.9.5': + resolution: {integrity: sha512-fD3lKzl+r6j6n8DwiMY53qnh5DLqD8KJjCp+NbVud54Nnh3Q9Wprqss3YzyMHP3NSJk663Wlg1E1X5qOuFVFig==} + cpu: [arm] + os: [linux] + libc: [musl] + deprecated: yuku-parser runs on yuku-core since 0.14 + + '@yuku-parser/binding-linux-arm64-gnu@0.9.5': + resolution: {integrity: sha512-aRU/aCphV1MWCl/lvI6NX8LgucHQ68Fx+vz7NBb/5MEr2EuzCxiPOQqeFcMdWh+2AED/p0AvAREch0c+cSnlhA==} + cpu: [arm64] + os: [linux] + libc: [glibc] + deprecated: yuku-parser runs on yuku-core since 0.14 + + '@yuku-parser/binding-linux-arm64-musl@0.9.5': + resolution: {integrity: sha512-5+Guro0l8H473YXlEjVNBRLN/IbPbJdnQh1zo0OLt49xxnON+XMJRRaEyTOTfkn9QSRg0+BhEKGR4W9Gu2uZJQ==} + cpu: [arm64] + os: [linux] + libc: [musl] + deprecated: yuku-parser runs on yuku-core since 0.14 + + '@yuku-parser/binding-linux-x64-gnu@0.9.5': + resolution: {integrity: sha512-pwwSyV9q+GlvzSXlsMZBMlgk22L5bud714/vqZCdeUTivzeUVltQzUaf3IQXOGXdpc59JYTeuMCtbGquaAUoCA==} + cpu: [x64] + os: [linux] + libc: [glibc] + deprecated: yuku-parser runs on yuku-core since 0.14 + + '@yuku-parser/binding-linux-x64-musl@0.9.5': + resolution: {integrity: sha512-z18j6JN3lBHH8vzN7gG1M8fI0nlgItSfnC9PfbmUbt97iHViKfw2iA2G9fGwRMe5LyPRZBrejIlapbygPbpdaw==} + cpu: [x64] + os: [linux] + libc: [musl] + deprecated: yuku-parser runs on yuku-core since 0.14 - '@vitest/spy@4.1.11': - resolution: {integrity: sha512-apNa/prQy2qCeywhnixOHPRCgGNhvg7T4Dapfl1GahLp/R+uhBm5cPyFoNVyqsNd2h1nJxL6BqqdIjiABL60YA==} + '@yuku-parser/binding-win32-arm64@0.9.5': + resolution: {integrity: sha512-s6Gwttb1dQvtPX6Bgkw+UPC9IO3UBDXc6Zezog8MMgvu3UfJBBB9TQqKBX/2quu0Eyh+lLSAyNlIyyecaagUPg==} + cpu: [arm64] + os: [win32] + deprecated: yuku-parser runs on yuku-core since 0.14 - '@vitest/utils@4.1.11': - resolution: {integrity: sha512-zTCVGpyFsGWBhllOyKlTw/vnr6D9qxsfSDyfbyZmTyjHw5N/VuvzHpHoQjm2ZJzn4RJgx5w4r7V0er69CmLgPQ==} + '@yuku-parser/binding-win32-x64@0.9.5': + resolution: {integrity: sha512-FrERt9YWatY3bJfSUdi4YWY+6iQcyo3MzCC821BxlWM5TFZEHijnpz/bknR79VHlXY/9CvXz8ZlaAGPsTtN3nw==} + cpu: [x64] + os: [win32] + deprecated: yuku-parser runs on yuku-core since 0.14 - accepts@2.0.0: - resolution: {integrity: sha512-5cvg6CtKwfgdmVqY1WIiXKc3Q1bkRqGLi+2W/6ao+6Y7gu/RCwRuAhGEzh5B4KlszSuTLgZYuqFqo5bImjNKng==} - engines: {node: '>= 0.6'} + '@yuku-toolchain/types@0.9.5': + resolution: {integrity: sha512-KiuLNNgX9uNealaWAR+G3/cMXnRk9x4TY2EkYe/KIag+UPdwiA0RRf1hr1WAxzTP8KGzCTkqUdLPvqh32sEO3w==} acorn-import-attributes@1.9.5: resolution: {integrity: sha512-n02Vykv5uA3eHGM/Z2dQrcD56kL8TyDb2p1+0P83PClMnC/nc+anbQRhIOWnSq4Ke/KvDPrY3C9hDtC/A3eHnQ==} @@ -1063,11 +1209,12 @@ packages: resolution: {integrity: sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==} engines: {node: '>=8'} - any-promise@1.3.0: - resolution: {integrity: sha512-7UvmKalWRt1wgjL1RrGxoSJW/0QZFIegpeGvZG9kjp8vrRu55XTHbwnqq2GpXm9uLbcuhxm3IqX9OB4MZR1b2A==} + ansi-styles@5.2.0: + resolution: {integrity: sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==} + engines: {node: '>=10'} - argparse@2.0.1: - resolution: {integrity: sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==} + aria-query@5.3.0: + resolution: {integrity: sha512-b0P0sZPKtyu8HkeRAfCq0IfURZK+SuwMjY1UXGBU27wpAiTwQAIlq56IbIO+ytk/JjS1fMR14ee5WBBfKi5J6A==} assertion-error@2.0.1: resolution: {integrity: sha512-Izi8RQcffqCeNVgFigKli1ssklIbpHnCYc6AknXGYoB6grJqyeby7jv12JUQgmTAnIDnbck1uxksT4dzN3PWBA==} @@ -1077,20 +1224,9 @@ packages: resolution: {integrity: sha512-LElXdjswlqjWrPpJFg1Fx4wpkOCxj1TDHlSV4PlaRxHGWko024xICaa97ZkMfs6DRKlCguiAI+rbXv5GWwXIkg==} hasBin: true - balanced-match@4.0.4: - resolution: {integrity: sha512-BLrgEcRTwX2o6gGxGOCNyMvGSp35YofuYzw9h1IMTRmKqttAZZVU67bdb9Pr2vUHA8+j3i2tJfjO6C6+4myGTA==} - engines: {node: 18 || 20 || >=22} - - body-parser@2.3.0: - resolution: {integrity: sha512-2cGmJupaNgg+QUwVLAucDuWuoMZ6EX9iHDRswZ5lsNYEmwPaRknMPCLZz07yTzVq/83p4o/wzbDZbBrTvGGTIw==} - engines: {node: '>=18'} - - brace-expansion@5.0.12: - resolution: {integrity: sha512-YovQ3rzhaLMIrDjNDMkNS01tea93qhEhG5xy8f6+R0l+dw3Ki+5sCoIoI942iuLZTHWogWktgwVDhU09iNEimQ==} - engines: {node: 20 || >=22} - braintrust@file:../../../js: resolution: {directory: ../../../js, type: directory} + engines: {node: '>=22.18.0'} hasBin: true peerDependencies: '@types/react': '>=18' @@ -1099,50 +1235,16 @@ packages: '@types/react': optional: true - bundle-require@5.1.0: - resolution: {integrity: sha512-3WrrOuZiyaaZPWiEt4G3+IffISVC9HYlWueJEBWED4ZH4aIAC2PnkdnuRrR94M+w6yGWn4AglWtJtBI8YqvgoA==} - engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0} - peerDependencies: - esbuild: '>=0.18' - - bytes@3.1.2: - resolution: {integrity: sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg==} - engines: {node: '>= 0.8'} - - cac@6.7.14: - resolution: {integrity: sha512-b6Ilus+c3RrdDk+JhLKUAQfzzgLEPy6wcXqS7f/xe1EETvsDP6GORG7SFuOs6cID5YkqchW/LXZbX5bc8j7ZcQ==} - engines: {node: '>=8'} - - call-bind-apply-helpers@1.0.2: - resolution: {integrity: sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ==} - engines: {node: '>= 0.4'} - - call-bound@1.0.4: - resolution: {integrity: sha512-+ys997U96po4Kx/ABpBCqhA9EuxJaQWDQg7295H4hBphv3IZg0boBKuwYpt4YXp6MZ5AmZQnU/tyMTlRpaSejg==} - engines: {node: '>= 0.4'} - chai@6.2.2: resolution: {integrity: sha512-NUPRluOfOiTKBKvWPtSD4PhFvWCqOi0BGStNWs57X9js7XGTprSmFoz5F0tWhR4WPjNeR9jXqdC7/UpSJTnlRg==} engines: {node: '>=18'} - chokidar@4.0.3: - resolution: {integrity: sha512-Qgzu8kfBvo+cA4962jnP1KkS6Dop5NS6g7R5LFYJr4b8Ub94PPQXUksCw9PvXoeXPRRddRNC5C1JQUR2SMGtnA==} - engines: {node: '>= 14.16.0'} - cjs-module-lexer@1.4.3: resolution: {integrity: sha512-9z8TZaGM1pfswYeXrUpzPrkx8UnWYdhJclsiYMm6x/w5+nN+8Tf/LnAgfLGQCm59qAOxU8WwHEq2vNwF6i4j+Q==} cjs-module-lexer@2.2.0: resolution: {integrity: sha512-4bHTS2YuzUvtoLjdy+98ykbNB5jS0+07EvFNXerqZQJ89F7DI6ET7OQo/HJuW6K0aVsKA9hj9/RVb2kQVOrPDQ==} - cli-progress@3.12.0: - resolution: {integrity: sha512-tRkV3HJ1ASwm19THiiLIXLO7Im7wlTuKnvkYaTkyoAPefqjNg7W7DHKUlGRxy9vxDvbyCYQkQozvptuMkGCg8A==} - engines: {node: '>=4'} - - cli-table3@0.6.5: - resolution: {integrity: sha512-+W/5efTR7y5HRD7gACw9yQjqMVvEMLBHmboM/kPWam+H+Hmyrgjh6YncVKK122YZkXrLudzTuAukUw9FnMf7IQ==} - engines: {node: 10.* || >= 12.*} - cli-width@4.1.0: resolution: {integrity: sha512-ouuZd4/dm2Sw5Gmqy6bGyNNNe1qt9RpmxveLSO7KcgsTnU7RXfsw+/bukWGo1abgBiMAic068rclZsO4IWmmxQ==} engines: {node: '>= 12'} @@ -1158,48 +1260,13 @@ packages: color-name@1.1.4: resolution: {integrity: sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==} - commander@4.1.1: - resolution: {integrity: sha512-NOKm8xhkzAjzFx8B2v5OAHT+u5pRQc2UCa2Vq9jYL/31o2wi9mxBA7LIFs3sV5VSC49z6pEhfbMULvShKj26WA==} - engines: {node: '>= 6'} - - confbox@0.1.8: - resolution: {integrity: sha512-RMtmw0iFkeR4YV+fUOSucriAQNb9g8zFR52MWCtl+cCZOFRNL6zeB395vPzFhEjjn4fMxXudmELnl/KF/WrK6w==} - - consola@3.4.2: - resolution: {integrity: sha512-5IKcdX0nnYavi6G7TtOhwkYzyjfJlatbjMjuLSfE2kYT5pMDOilZ4OvMhi637CcDICTmz3wARPoyhqyX1Y+XvA==} - engines: {node: ^14.18.0 || >=16.10.0} - - content-disposition@1.1.0: - resolution: {integrity: sha512-5jRCH9Z/+DRP7rkvY83B+yGIGX96OYdJmzngqnw2SBSxqCFPd0w2km3s5iawpGX8krnwSGmF0FW5Nhr0Hfai3g==} - engines: {node: '>=18'} - - content-type@1.0.5: - resolution: {integrity: sha512-nTjqfcBFEipKdXCv4YDQWCfmcLZKm81ldF0pAopTvyrFGVbcR6P/VAAd5G7N+0tTr8QqiU0tFadD6FK4NtJwOA==} - engines: {node: '>= 0.6'} - - content-type@2.0.0: - resolution: {integrity: sha512-j/O/d7GcZCyNl7/hwZAb606rzqkyvaDctLmckbxLzHvFBzTJHuGEdodATcP3yIRoDrLHkIATJuvzbFlp/ki2cQ==} - engines: {node: '>=18'} - convert-source-map@2.0.0: resolution: {integrity: sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==} - cookie-signature@1.2.2: - resolution: {integrity: sha512-D76uU73ulSXrD1UXF4KE2TMxVVwhsnCgfAyTg9k8P6KGZjlXKrOLe4dJQKI3Bxi5wjesZoFXJWElNWBjPZMbhg==} - engines: {node: '>=6.6.0'} - - cookie@0.7.2: - resolution: {integrity: sha512-yki5XnKuf750l50uGTllt6kKILY4nQ1eNIQatoXEByZ5dWgnKqbnqmTrBE5B4N7lrMJKQ2ytWMiTO2o0v6Ew/w==} - engines: {node: '>= 0.6'} - cookie@1.1.1: resolution: {integrity: sha512-ei8Aos7ja0weRpFzJnEA9UHJ/7XQmqglbRwnf2ATjcB9Wq874VKH9kfjjirM6UhU2/E5fFYadylyhFldcqSidQ==} engines: {node: '>=18'} - cors@2.8.6: - resolution: {integrity: sha512-tJtZBBHA6vjIAaF6EnIaq6laBBP9aq/Y3ouVJjEfoHbRBcHBAHYcMh/w8LDrk2PvIMMq8gmopa5D4V8RmbrxGw==} - engines: {node: '>= 0.10'} - debug@4.4.3: resolution: {integrity: sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==} engines: {node: '>=6.0'} @@ -1209,36 +1276,24 @@ packages: supports-color: optional: true - depd@2.0.0: - resolution: {integrity: sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw==} - engines: {node: '>= 0.8'} + dequal@2.0.3: + resolution: {integrity: sha512-0je+qPKHEMohvfRTCEo3CrPG6cAzAYgmzKyxRiYSSDkS6eGJdyVJm7WaYA5ECaAD9wLB2T4EEeymA5aFVcYXCA==} + engines: {node: '>=6'} detect-libc@2.1.2: resolution: {integrity: sha512-Btj2BOOO83o3WyH59e8MgXsxEQVcarkUOpEYrubB0urwnN10yQ364rsiByU11nZlqWYZm05i/of7io4mzihBtQ==} engines: {node: '>=8'} + dom-accessibility-api@0.5.16: + resolution: {integrity: sha512-X7BJ2yElsnOJ30pZF4uIIDfBEVgF4XEBxL9Bxhy6dnrm5hkzqmsWHGTiHqRiITNhMyFLyAiWndIJP7Z1NTteDg==} + dotenv@16.6.1: resolution: {integrity: sha512-uBq4egWHTcTt33a72vpSG0z3HnPuIl6NqYcTrKEg2azoEyl2hpW0zqlxysq2pK9HlDIHyHyakeYaYnSAwd8bow==} engines: {node: '>=12'} - dunder-proto@1.0.1: - resolution: {integrity: sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==} - engines: {node: '>= 0.4'} - - ee-first@1.1.1: - resolution: {integrity: sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow==} - emoji-regex@8.0.0: resolution: {integrity: sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==} - encodeurl@2.0.0: - resolution: {integrity: sha512-Q0n9HRi4m6JuGIV1eFlmvJB7ZEVxu93IrMyiMsGC0lrMJMWzRgx6WGquyfQgZVb31vhGgXnfmPNNXmxnOkRBrg==} - engines: {node: '>= 0.8'} - - es-define-property@1.0.1: - resolution: {integrity: sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g==} - engines: {node: '>= 0.4'} - es-errors@1.3.0: resolution: {integrity: sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==} engines: {node: '>= 0.4'} @@ -1246,15 +1301,6 @@ packages: es-module-lexer@2.3.2: resolution: {integrity: sha512-poHGpORABojJJucnV9KbOavETW8lBVnphkW77ER5/BQ5Fz7oXSoCNek7IH3vR5nRjdsEz926ibFYX8KtLQmdyw==} - es-object-atoms@1.1.2: - resolution: {integrity: sha512-HWcBoN6NileqtSydK2FqHbS/LoDd2pqrnQHLyJzBj4kOp/ky2MWMN694xOfkK8/SnUsW2DH7EfyVlydKCsm1Zw==} - engines: {node: '>= 0.4'} - - esbuild@0.27.7: - resolution: {integrity: sha512-IxpibTjyVnmrIQo5aqNpCgoACA/dTKLTlhMHihVHhdkxKyPO1uBBthumT0rdHmcsk9uMonIWS0m4FljWzILh3w==} - engines: {node: '>=18'} - hasBin: true - esbuild@0.28.1: resolution: {integrity: sha512-HrJrvZv5ayxBzPfwphOoNzkzOIIlifzk0KJrGK2c8R4+LKpMtpYLQeUdjnwjWv/LZlkH2laZk+4w78pi99D4Vw==} engines: {node: '>=18'} @@ -1264,9 +1310,6 @@ packages: resolution: {integrity: sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==} engines: {node: '>=6'} - escape-html@1.0.3: - resolution: {integrity: sha512-NiSupZ4OeuGwr68lGIeym/ksIZMJodUGOSCZ/FSnTxcrekbvqrgdUxlJOMpijaKZVjAJrWrGs/6Jy8OMuyj9ow==} - esquery@1.7.0: resolution: {integrity: sha512-Ap6G0WQwcU/LHsvLwON1fAQX9Zp0A2Y6Y/cJBl9r/JbW90Zyg4/zbG6zzKa2OTALELarYHmKu0GhpM5EO+7T0g==} engines: {node: '>=0.10'} @@ -1278,10 +1321,6 @@ packages: estree-walker@3.0.3: resolution: {integrity: sha512-7RUKfXgSMMkzt6ZuXmqapOurLGPPfgj6l9uRZ7lRGolvk0y2yocc35LdcxKC5PQZdn2DMqioAQ2NoWcrTKmm6g==} - etag@1.8.1: - resolution: {integrity: sha512-aIL5Fx7mawVa300al2BnEE4iNvo1qETxLrPI/o05L7z6go7fCw1J6EQmbK4FmJ2AS7kgVF/KEZWufBfdClMcPg==} - engines: {node: '>= 0.6'} - eventsource-parser@1.1.2: resolution: {integrity: sha512-v0eOBUbiaFojBu2s2NPBfYUoRR9GjcDNvCXVaqEf5vVfpIAh9f8RCo4vXTP8c63QRKCFwoLpMpTdPwwhEKVgzA==} engines: {node: '>=14.18'} @@ -1290,10 +1329,6 @@ packages: resolution: {integrity: sha512-KfYbmpRm0VbLjEvVa9yGwCi9GI34xvi7A/HXYWQO65CSD2u3MczUJSuwXKFIxlGsgBQizV9q5J9NHj4VG0n+pA==} engines: {node: '>=12.0.0'} - express@5.2.1: - resolution: {integrity: sha512-hIS4idWWai69NezIdRt2xFVofaF4j+6INOpJlVOLDO8zXGpUVEVzIYk12UUi2JzjEzWL3IOAxcTubgz9Po0yXw==} - engines: {node: '>= 18'} - fast-deep-equal@3.1.3: resolution: {integrity: sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==} @@ -1303,8 +1338,8 @@ packages: fast-string-width@3.0.2: resolution: {integrity: sha512-gX8LrtNEI5hq8DVUfRQMbr5lpaS4nMIWV+7XEbXk2b8kiQIizgnlr12B4dA3ZEx3308ze0O4Q1R+cHts8kyUJg==} - fast-uri@3.1.8: - resolution: {integrity: sha512-GZMtZUTNRpOVIECoXwLNZS5xUGE+mVNbTB8h/7Rwh2TFWcBQiPzTgyZi05BF9UMZKkLJv8XBRJTlU7zg8+ZfMg==} + fast-uri@3.1.9: + resolution: {integrity: sha512-hS/0Wt3Ya043oKXs0sIxrMpKQITXhMLIrVAKKXi4gozGC9v7YRWVnXIZzjNPTO02M9BdxLCWqK1yRXgNziVyJQ==} fast-wrap-ansi@0.2.2: resolution: {integrity: sha512-7F2Fl+TjRSenLqlU3UjSH0iyqopqoZIu7eZVpEirP2g1GtWa2G/ecEmBdgz31+Mxr+ELclgg6sokpSFIQiZ02Q==} @@ -1318,20 +1353,11 @@ packages: picomatch: optional: true - finalhandler@2.1.1: - resolution: {integrity: sha512-S8KoZgRZN+a5rNwqTxlZZePjT/4cnm0ROV70LedRHZ0p8u9fRID0hJUZQpkKLzro8LfmC8sx23bY6tVNxv8pQA==} - engines: {node: '>= 18.0.0'} + fflate@0.8.3: + resolution: {integrity: sha512-tbZNuJrLwGUp3zshBtdy4W+ORxZuIh8a5ilyIEQDC5rY1f3U20JMry0Ll3WBzU58EZKsEuJFXhb5gwv8CsPvgA==} - fix-dts-default-cjs-exports@1.0.1: - resolution: {integrity: sha512-pVIECanWFC61Hzl2+oOCtoJ3F17kglZC/6N94eRWycFgBH35hHx0Li604ZIzhseh97mf2p0cv7vVrOZGoqhlEg==} - - forwarded@0.2.0: - resolution: {integrity: sha512-buRG0fpBtRHSTCOASe6hD258tEubFoRLb4ZNA6NxMVHNw2gOcwHo9wyablzMzOA5z9xA9L1KNjk/Nt6MT9aYow==} - engines: {node: '>= 0.6'} - - fresh@2.0.0: - resolution: {integrity: sha512-Rx/WycZ60HOaqLKAi6cHRKKI7zxWbJ31MhntmtwMoaTeF7XFH9hhBp8vITaMidfljRQ6eYWCKkaTK+ykVJHP2A==} - engines: {node: '>= 0.8'} + flatted@3.4.4: + resolution: {integrity: sha512-5+ybhBZANEJxaH3X5evAFatUxLfEHSr7n6kYJ+1Qd0mUqr4eu9gIf6GDbWHf8RJijHrjjO8G+la14SlL2SeS1Q==} fsevents@2.3.3: resolution: {integrity: sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==} @@ -1345,26 +1371,10 @@ packages: resolution: {integrity: sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==} engines: {node: 6.* || 8.* || >= 10.*} - get-intrinsic@1.3.0: - resolution: {integrity: sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ==} - engines: {node: '>= 0.4'} - - get-proto@1.0.1: - resolution: {integrity: sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g==} - engines: {node: '>= 0.4'} - - gopd@1.2.0: - resolution: {integrity: sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg==} - engines: {node: '>= 0.4'} - graphql@16.14.0: resolution: {integrity: sha512-BBvQ/406p+4CZbTpCbVPSxfzrZrbnuWSP1ELYgyS6B+hNeKzgrdB4JczCa5VZUBQrDa9hUngm0KnexY6pJRN5Q==} engines: {node: ^12.22.0 || ^14.16.0 || ^16.0.0 || >=17.0.0} - has-symbols@1.1.0: - resolution: {integrity: sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ==} - engines: {node: '>= 0.4'} - hasown@2.0.3: resolution: {integrity: sha512-ej4AhfhfL2Q2zpMmLo7U1Uv9+PyhIZpgQLGT1F9miIGmiCJIoCgSmczFdrc97mWT4kVY72KA+WnnhJ5pghSvSg==} engines: {node: '>= 0.4'} @@ -1372,24 +1382,9 @@ packages: headers-polyfill@5.0.1: resolution: {integrity: sha512-1TJ6Fih/b8h5TIcv+1+Hw0PDQWJTKDKzFZzcKOiW1wJza3XoAQlkCuXLbymPYB8+ZQyw8mHvdw560e8zVFIWyA==} - http-errors@2.0.1: - resolution: {integrity: sha512-4FbRdAX+bSdmo4AUFuS0WNiPz8NgFt+r8ThgNWmlrjQjt1Q7ZR9+zTlce2859x4KSXrwIsaeTqDoKQmtP8pLmQ==} - engines: {node: '>= 0.8'} - - iconv-lite@0.7.2: - resolution: {integrity: sha512-im9DjEDQ55s9fL4EYzOAv0yMqmMBSZp6G0VvFyTMPKWxiSBHUj9NW/qqLmXUwXrrM7AvqSlTCfvqRb0cM8yYqw==} - engines: {node: '>=0.10.0'} - import-in-the-middle@1.15.0: resolution: {integrity: sha512-bpQy+CrsRmYmoPMAE/0G33iwRqwW4ouqdRg8jgbH3aKuCtOc8lxgmYXg2dMM92CRiGP660EtBcymH/eVUpCSaA==} - inherits@2.0.4: - resolution: {integrity: sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==} - - ipaddr.js@1.9.1: - resolution: {integrity: sha512-0KI/607xoxSToH7GjN1FfSbLoU0+btTicjsQSWQlh/hZykN8KpmMf7uYwPW3R+akZ6R/w18ZlXSHBYXiYUPO3g==} - engines: {node: '>= 0.10'} - is-core-module@2.16.2: resolution: {integrity: sha512-evOr8xfXKxE6qSR0hSXL2r3sd7ALj8+7jQEUvPYcm5sgZFdJ+AYzT6yNmJenvIYQBgIGwfwz08sL8zoL7yq2BA==} engines: {node: '>= 0.4'} @@ -1401,12 +1396,8 @@ packages: is-node-process@1.2.0: resolution: {integrity: sha512-Vg4o6/fqPxIjtxgUH5QLJhwZ7gW5diGCVlXpuUfELC62CuxM1iHcRe51f2W1FDy04Ai4KJkagKjx3XaqyfRKXw==} - is-promise@4.0.0: - resolution: {integrity: sha512-hvpoI6korhJMnej285dSg6nu1+e6uxs7zG3BYAm5byqDsgJNWwxzM6z6iZiAgQR4TJ30JmBTOwqZUw3WlyH3AQ==} - - joycon@3.1.1: - resolution: {integrity: sha512-34wB/Y7MW7bzjKRjUKTa46I2Z7eV62Rkhva+KkopW7Qvv/OSWBqvkSY7vusOPrNuZcUG3tApvdVgNB8POj3SPw==} - engines: {node: '>=10'} + js-tokens@4.0.0: + resolution: {integrity: sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==} json-schema-traverse@1.0.0: resolution: {integrity: sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==} @@ -1485,60 +1476,30 @@ packages: resolution: {integrity: sha512-WkUDrojuJs0xkgGf2udWxa3yGBRxPtxUkB79i6aCZLRgc7PM8fZe9TosfPDcvEpQZbuFASnHYmRLBLUbmLOIIA==} engines: {node: '>= 12.0.0'} - lilconfig@3.1.3: - resolution: {integrity: sha512-/vlFKAoH5Cgt3Ie+JLhRbwOsCQePABiU3tJ1egGvyQ+33R/vcwM2Zl2QR/LzjsBeItPt3oSVXapn+m4nQDvpzw==} - engines: {node: '>=14'} - - lines-and-columns@1.2.4: - resolution: {integrity: sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg==} - - load-tsconfig@0.2.5: - resolution: {integrity: sha512-IXO6OCs9yg8tMKzfPZ1YmheJbZCiEsnBdcB03l0OcfK9prKnJb96siuHCr5Fl37/yo9DnKU+TLpxzTUspw9shg==} - engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0} - lodash.camelcase@4.3.0: resolution: {integrity: sha512-TwuEnCnxbc3rAvhf/LbG7tJUDzhqXyFnv3dtzLOPgCG/hODL7WFnsbwktkD7yUV0RrreP/l1PALq/YSg6VvjlA==} long@5.3.2: resolution: {integrity: sha512-mNAgZ1GmyNhD7AuqnTG3/VQ26o760+ZYBPKjPvugO8+nLbYfX6TVpJPseBvopbdY+qpZ/lKUnmEc1LeZYS3QAA==} - magic-string@0.30.21: - resolution: {integrity: sha512-vd2F4YUyEXKGcLHoq+TEyCjxueSeHnFxyyjNp80yg0XV4vUhnDer/lvvlqM/arB5bXQN5K2/3oinyCRyx8T2CQ==} - - math-intrinsics@1.1.0: - resolution: {integrity: sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==} - engines: {node: '>= 0.4'} - - media-typer@1.1.0: - resolution: {integrity: sha512-aisnrDP4GNe06UcKFnV5bfMNPBUw4jsLGaWwWfnH3v02GnBuXX2MCVn5RbrWo0j3pczUilYblq7fQ7Nw2t5XKw==} - engines: {node: '>= 0.8'} + lz-string@1.5.0: + resolution: {integrity: sha512-h5bgJWpxJNswbU7qCrV0tIKQCaS3blPDrqKWx+QxzuzL1zGUzij9XCWLrSLsJPu5t+eWA/ycetzYAO5IOMcWAQ==} + hasBin: true - merge-descriptors@2.0.0: - resolution: {integrity: sha512-Snk314V5ayFLhp3fkUREub6WtjBfPdCPY1Ln8/8munuLuiYhsABgBVWsozAG+MWMbVEvcdcpbi9R7ww22l9Q3g==} - engines: {node: '>=18'} + magic-string@1.4.3: + resolution: {integrity: sha512-z12OxmaPGE0F4xlpGdjuAUckipLpQlTAuAmyGhNoD7ODpYUzvDfvtxUbjM/jwznyP178oju/KDdyi220wNJ0RQ==} meriyah@6.1.4: resolution: {integrity: sha512-Sz8FzjzI0kN13GK/6MVEsVzMZEPvOhnmmI1lU5+/1cGOiK3QUahntrNNtdVeihrO7t9JpoH75iMNXg6R6uWflQ==} engines: {node: '>=18.0.0'} - mime-db@1.54.0: - resolution: {integrity: sha512-aU5EJuIN2WDemCcAp2vFBfp/m4EAhWJnUNSSw0ixs7/kXbd6Pg64EmwJkNdFhB8aWt1sH2CTXrLxo/iAGV3oPQ==} - engines: {node: '>= 0.6'} - - mime-types@3.0.2: - resolution: {integrity: sha512-Lbgzdk0h4juoQ9fCKXW4by0UJqj+nOOrI9MJ1sSj4nI8aI2eo1qmvQEie4VD1glsS250n15LsWsYtCugiStS5A==} - engines: {node: '>=18'} - - minimatch@10.2.5: - resolution: {integrity: sha512-MULkVLfKGYDFYejP07QOurDLLQpcjk7Fw+7jXS2R2czRQzR56yHRveU5NDJEOviH+hETZKSkIk5c+T23GjFUMg==} - engines: {node: 18 || 20 || >=22} - - mlly@1.8.2: - resolution: {integrity: sha512-d+ObxMQFmbt10sretNDytwt85VrbkhhUA/JBGm1MPaWJ65Cl4wOgLaB1NYvJSZ0Ef03MMEU/0xpPMXUIQ29UfA==} - module-details-from-path@1.0.4: resolution: {integrity: sha512-EGWKgxALGMgzvxYF1UyGTy0HXX/2vHLkw6+NvDKW2jypWbHpjQuj4UMcqQWXHERJhVGKikolT06G3bcKe4fi7w==} + mrmime@2.0.1: + resolution: {integrity: sha512-Y3wQdFg2Va6etvQ5I82yUhGdsKrcYox6p7FfL1LbK2J4V01F9TGlepTIhnK24t7koZibmg82KGglhA1XK5IsLQ==} + engines: {node: '>=10'} + ms@2.1.3: resolution: {integrity: sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==} @@ -1560,43 +1521,47 @@ packages: resolution: {integrity: sha512-gSrprq0fJ3EiOErzjdIZrjysVVmJ4uu1QWfCDss5LypA5OXvrMje5Ym5z6V6RLyJ2eF87lasX7t6a0AnFvZblg==} engines: {node: ^22.22.2 || ^24.15.0 || >=26.0.0} - mz@2.7.0: - resolution: {integrity: sha512-z81GNO7nnYMEhrGh9LeymoE4+Yr0Wn5McHIZMK5cfQCl+NDX08sCZgUc9/6MHni9IWuFLm1Z3HTCXu2z9fN62Q==} - nanoid@3.3.19: resolution: {integrity: sha512-Y2tUNy4ouw6tq5oDSKeQYGOyhkUBhNOcGV/02KC+6kd9eDGqdZd++mjMiIDilrBYvjEnCYvVtsuHCuP+okSfug==} engines: {node: ^10 || ^12 || ^13.7 || ^14 || >=15.0.1} hasBin: true - negotiator@1.0.0: - resolution: {integrity: sha512-8Ofs/AUQh8MaEcrlq5xOX0CQ9ypTF5dl78mjlMNfOK08fzpgTHQRQPBxcPlEtIw0yRpws+Zo/3r+5WRby7u3Gg==} - engines: {node: '>= 0.6'} - - object-assign@4.1.1: - resolution: {integrity: sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==} - engines: {node: '>=0.10.0'} - - object-inspect@1.13.4: - resolution: {integrity: sha512-W67iLl4J2EXEGTbfeHCffrjDfitvLANg0UlX3wFUUSTx92KXRFegMHUVgSqE+wvhAbi4WqjGg9czysTV2Epbew==} - engines: {node: '>= 0.4'} - obug@2.2.1: resolution: {integrity: sha512-XrsrhT5sybtKI6wakr2SPOlGZWWYbUXZ7a0jT8/QOeAPau+1X/bSegNe5YR75oJmEZQbKningirmGOEJCIk61Q==} engines: {node: '>=12.20.0'} - on-finished@2.4.1: - resolution: {integrity: sha512-oVlzkg3ENAhCk2zdv7IJwd/QUD4z2RxRwpkcGY8psCVcCYZNq4wYnVWALHM+brtuJjePWiYF/ClmuDr8Ch5+kg==} - engines: {node: '>= 0.8'} - - once@1.4.0: - resolution: {integrity: sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==} - outvariant@1.4.3: resolution: {integrity: sha512-+Sl2UErvtsoajRDKCE5/dBz4DIvHXQQnAxtQTF04OJxY0+DyZXSo5P5Bb7XYWOh81syohlYL24hbDwxedPUJCA==} - parseurl@1.3.3: - resolution: {integrity: sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ==} - engines: {node: '>= 0.8'} + oxfmt@0.72.0: + resolution: {integrity: sha512-16OQeL0uhPZbfmT9S9WWM0Nv9+BmVi8Ra1uTYaSKj/qSCs6FB30KOhz9JoNYw6thT+K5bVxsk/tSEOu497i34g==} + engines: {node: ^20.19.0 || >=22.12.0} + hasBin: true + peerDependencies: + svelte: ^5.0.0 + vite-plus: '*' + peerDependenciesMeta: + svelte: + optional: true + vite-plus: + optional: true + + oxlint-tsgolint@7.0.2003: + resolution: {integrity: sha512-VnK4zlqgmgq/7ZcjzCk/WpN8kKFsYGcB85Io9qT3wL4K8Un3RKmJkp793crNETieMusPMKOA4a+Mw2wbteI6TQ==} + hasBin: true + + oxlint@1.87.0: + resolution: {integrity: sha512-drxVlaW7Oqq7wSy1CN25znRu00aRq8n57Q2wniJRxCmtYVz07ufCm7EVDPQFZURrTgQIjzKrVEHAD/HcvJXObg==} + engines: {node: ^20.19.0 || >=22.12.0} + hasBin: true + peerDependencies: + oxlint-tsgolint: '>=7.0.2003' + vite-plus: '*' + peerDependenciesMeta: + oxlint-tsgolint: + optional: true + vite-plus: + optional: true path-parse@1.0.7: resolution: {integrity: sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==} @@ -1604,9 +1569,6 @@ packages: path-to-regexp@6.3.0: resolution: {integrity: sha512-Yhpw4T9C6hPpgPeA28us07OJeqZ5EzQTkbfwuhsUg0c237RomFoETJgmp2sa3F/41gfLE6G5cqcYwznmeEeOlQ==} - path-to-regexp@8.4.2: - resolution: {integrity: sha512-qRcuIdP69NPm4qbACK+aDogI5CBDMi1jKe0ry5rSQJz8JVLsC7jV8XpiJjGRLLol3N+R5ihGYcrPLTno6pAdBA==} - pathe@2.0.3: resolution: {integrity: sha512-WUjGcAqP1gQacoQe+OBJsFA7Ld4DyXuUIjZ5cc75cLHvJ7dtNsTugphxIADwspS+AraAUePCKrSVtPLFj/F88w==} @@ -1621,62 +1583,24 @@ packages: resolution: {integrity: sha512-qcJu88Q2IWqJsDD529JKMdwGm/dvInW4HvQnRwiH9JtihJvzGOscDtHE3x1pBKeUOTysQ8kVmLnJ2kJu7yhcGA==} engines: {node: '>=12'} - pirates@4.0.7: - resolution: {integrity: sha512-TfySrs/5nm8fQJDcBDuUng3VOUKsd7S+zqvbOTiGXHfxX4wK31ard+hoNuvkicM/2YFzlpDgABOevKSsB4G/FA==} - engines: {node: '>= 6'} - - pkg-types@1.3.1: - resolution: {integrity: sha512-/Jm5M4RvtBFVkKWRu2BLUTNP8/M2a+UwuAX+ae4770q1qVGtfjG+WTCupoZixokjmHiry8uI+dlY8KXYV5HVVQ==} - - pluralize@8.0.0: - resolution: {integrity: sha512-Nc3IT5yHzflTfbjgqWcCPpo7DaKy4FnpB0l/zCAW0Tc7jxAiuqSxHasntB3D7887LSrA93kDJ9IXovxJYxyLCA==} - engines: {node: '>=4'} - - postcss-load-config@6.0.1: - resolution: {integrity: sha512-oPtTM4oerL+UXmx+93ytZVN82RrlY/wPUV8IeDxFrzIjXOLF1pN+EmKPLbubvKHT2HC20xXsCAH2Z+CKV6Oz/g==} - engines: {node: '>= 18'} - peerDependencies: - jiti: '>=1.21.0' - postcss: ^8.5.18 - tsx: ^4.8.1 - yaml: ^2.4.2 - peerDependenciesMeta: - jiti: - optional: true - postcss: - optional: true - tsx: - optional: true - yaml: - optional: true + pngjs@7.0.0: + resolution: {integrity: sha512-LKWqWJRhstyYo9pGvgor/ivk2w94eSjE3RGVuzLGlr3NmD8bf7RcYGze1mNdEHRP6TRP6rMuDHk5t44hnTRyow==} + engines: {node: '>=14.19.0'} postcss@8.5.28: resolution: {integrity: sha512-RRuzqDtt5Y9h3quz5hWhK+TPnsmVs6WwSU6LkJMeY4HstUEDuYTG8UJSdawMRzmzAtV+KEoG8N3Qg2qLy5vM/A==} engines: {node: ^10 || ^12 || >=14} + pretty-format@27.5.1: + resolution: {integrity: sha512-Qb1gy5OrP5+zDf2Bvnzdl3jsTf1qXVMazbvCoKhtKqVs4/YK4ozX4gKQJJVyNe+cajNPn0KoC0MC3FUmaHWEmQ==} + engines: {node: ^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0} + protobufjs@7.6.5: resolution: {integrity: sha512-/FPD0nUc9jH6rfFjji9IBqOz4pcSE3CsT1m7Ep6Mdb0LxSUMj8hgl6GomOvZzpNpAqqGaXA0P3VSrZLFzIhQrw==} engines: {node: '>=12.0.0'} - proxy-addr@2.0.8: - resolution: {integrity: sha512-5nnx0yGyVUcY6t9RnWcARWtwT9F1D8O9rt08htPvnd49W1IgZtmLkhu9WfMzQj1cFxjHIO6connUNVW5k7AVyQ==} - engines: {node: '>= 0.10'} - - qs@6.15.2: - resolution: {integrity: sha512-Rzq0KEyX/w/tEybncDgdkZrJgVUsUMk3xjh3t5bv3S1HTAtg+uOYt72+ZfwiQwKdysThkTBdL/rTi6HDmX9Ddw==} - engines: {node: '>=0.6'} - - range-parser@1.2.1: - resolution: {integrity: sha512-Hrgsx+orqoygnmhFbKaHE6c296J+HTAQXoxEF6gNupROmmGJRoyzfG3ccAveqCBrwr/2yxQ5BVd/GTl5agOwSg==} - engines: {node: '>= 0.6'} - - raw-body@3.0.2: - resolution: {integrity: sha512-K5zQjDllxWkf7Z5xJdV0/B0WTNqx6vxG70zJE4N0kBs4LovmEYWJzQGxC9bS9RAKu3bgM40lrd5zoLJ12MQ5BA==} - engines: {node: '>= 0.10'} - - readdirp@4.1.2: - resolution: {integrity: sha512-GDhwkLfywWL2s6vEjyhri+eXmfH6j1L7JE27WhqLeYzoh/A3DBaYGEj2H/HFZCn/kMfim73FXxEJTw06WtxQwg==} - engines: {node: '>= 14.18.0'} + react-is@17.0.2: + resolution: {integrity: sha512-w2GsyukL62IJnlaff/nRegPQR94C/XXamvMWmSHRJ4y7Ts/4ocGRmTHvOs8PSE6pB3dWOrD/nueuU5sduBsQ4w==} require-directory@2.1.1: resolution: {integrity: sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q==} @@ -1690,10 +1614,6 @@ packages: resolution: {integrity: sha512-gAZ+kLqBdHarXB64XpAe2VCjB7rIRv+mU8tfRWziHRJ5umKsIHN2tLLv6EtMw7WCdP19S0ERVMldNvxYCHnhSQ==} engines: {node: '>=8.6.0'} - resolve-from@5.0.0: - resolution: {integrity: sha512-qYg9KP24dD5qka9J47d0aVky0N+b4fTU89LN9iDnjB5waksiC49rvMB0PrUJQGoTmH50XPiqOvAjDfaijGxYZw==} - engines: {node: '>=8'} - resolve@1.22.12: resolution: {integrity: sha512-TyeJ1zif53BPfHootBGwPRYT1RUt6oGWsaQr8UyZW/eAm9bKoijtvruSDEmZHm92CwS9nj7/fWttqPCgzep8CA==} engines: {node: '>= 0.4'} @@ -1702,66 +1622,23 @@ packages: rettime@0.11.11: resolution: {integrity: sha512-ILJRqVWBCTlg9r42fFgwVZx1gnFAcQF8mRoMkbgQfIrjEDf9nbBFDFx00oloOa+Q869FUtaYDXZvEfnecQSCoQ==} - rolldown@1.0.2: - resolution: {integrity: sha512-oZx5zVDtVB44AW3eaifgDml1gWRDZGvjcfdxonE4swNPG98PrrXjaO/KrnUjzlMnztCCRVlUueA1kCXhARGk6g==} - engines: {node: ^20.19.0 || >=22.12.0} - hasBin: true - - rollup@4.60.4: - resolution: {integrity: sha512-WHeFSbZYsPu3+bLoNRUuAO+wavNlocOPf3wSHTP7hcFKVnJeWsYlCDbr3mTS14FCizf9ccIxXA8sGL8zKeQN3g==} - engines: {node: '>=18.0.0', npm: '>=8.0.0'} - hasBin: true - - router@2.2.0: - resolution: {integrity: sha512-nLTrUKm2UyiL7rlhapu/Zl45FwNgkZGaCpZbIHajDYgwlJCOzLSk+cIPAnsEqV955GjILJnKbdQC1nVPz+gAYQ==} - engines: {node: '>= 18'} - - safer-buffer@2.1.2: - resolution: {integrity: sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==} - semifies@1.0.0: resolution: {integrity: sha512-xXR3KGeoxTNWPD4aBvL5NUpMTT7WMANr3EWnaS190QVkY52lqqcVRD7Q05UVbBhiWDGWMlJEUam9m7uFFGVScw==} - send@1.2.1: - resolution: {integrity: sha512-1gnZf7DFcoIcajTjTwjwuDjzuz4PPcY2StKPlsGAQ1+YH20IRVrBaXSWmdjowTJ6u8Rc01PoYOGHXfP1mYcZNQ==} - engines: {node: '>= 18'} - - serve-static@2.2.1: - resolution: {integrity: sha512-xRXBn0pPqQTVQiC8wyQrKs2MOlX24zQ0POGaj0kultvoOCstBQM5yvOhAVSUwOMjQtTvsPWoNCHfPGwaaQJhTw==} - engines: {node: '>= 18'} - set-cookie-parser@3.1.0: resolution: {integrity: sha512-kjnC1DXBHcxaOaOXBHBeRtltsDG2nUiUni+jP92M9gYdW12rsmx92UsfpH7o5tDRs7I1ZZPSQJQGv3UaRfCiuw==} - setprototypeof@1.2.0: - resolution: {integrity: sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw==} - shimmer@1.2.1: resolution: {integrity: sha512-sQTKC1Re/rM6XyFM6fIAGHRPVGvyXfgzIDvzoq608vM+jeyVD0Tu1E6Np0Kc2zAIFWIj963V2800iF/9LPieQw==} - side-channel-list@1.0.1: - resolution: {integrity: sha512-mjn/0bi/oUURjc5Xl7IaWi/OJJJumuoJFQJfDDyO46+hBWsfaVM65TBHq2eoZBhzl9EchxOijpkbRC8SVBQU0w==} - engines: {node: '>= 0.4'} - - side-channel-map@1.0.1: - resolution: {integrity: sha512-VCjCNfgMsby3tTdo02nbjtM/ewra6jPHmpThenkTYh8pG9ucZ/1P8So4u4FGBek/BjpOVsDCMoLA/iuBKIFXRA==} - engines: {node: '>= 0.4'} - - side-channel-weakmap@1.0.2: - resolution: {integrity: sha512-WPS/HvHQTYnHisLo9McqBHOJk2FkHO/tlpvldyrnem4aeQp4hai3gythswg6p01oSoTl58rcpiFAjF2br2Ak2A==} - engines: {node: '>= 0.4'} - - side-channel@1.1.0: - resolution: {integrity: sha512-ZX99e6tRweoUXqR+VBrslhda51Nh5MTQwou5tnUDgbtyM0dBgmhEDtWGP/xbKn6hqfPRHujUNwz5fy/wbbhnpw==} - engines: {node: '>= 0.4'} - - siginfo@2.0.0: - resolution: {integrity: sha512-ybx0WO1/8bSBLEWXZvEd7gMW3Sn3JFlW3TvX1nREbDLRNQNaeNN8WK0meBwPdAaOI7TtRRRJn/Es1zhrrCHu7g==} - signal-exit@4.1.0: resolution: {integrity: sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw==} engines: {node: '>=14'} + sirv@3.0.2: + resolution: {integrity: sha512-2wcC/oGxHis/BoHkkPwldgiPSYcpZK3JU28WoMVv55yHJgcZ8rlXvuG9iZggz+sU1d4bRgIGASwyWqjxu3FM0g==} + engines: {node: '>=18'} + source-map-js@1.2.2: resolution: {integrity: sha512-KGj/8Y43x35aZVDtt+J4mK1hoLGHULMYfSkODJNQjNDC3oW1PqPoxMwo0pLUsWM/UEGzON/NxeHywEfNXNP3Vw==} engines: {node: '>=0.10.0'} @@ -1770,9 +1647,6 @@ packages: resolution: {integrity: sha512-i5uvt8C3ikiWeNZSVZNWcfZPItFQOsYTUAOkcUPGd8DqDy1uOUikjt5dG+uRlwyvR108Fb9DOd4GvXfT0N2/uQ==} engines: {node: '>= 12'} - stackback@0.0.2: - resolution: {integrity: sha512-1XMJE5fQo1jGH6Y/7ebnwPOBEkIEnT4QF32d5R1+VXdXveM0IBMJt8zfaxX1P3QhVwrYe+576+jkANtSS2mBbw==} - statuses@2.0.2: resolution: {integrity: sha512-DvEy55V3DB7uknRo+4iOGT5fP1slR8wQohVdknigZPMpMstaKJQWhwiYBACJE3Ul2pTnATihhBYnRhZQHGBiRw==} engines: {node: '>= 0.8'} @@ -1791,11 +1665,6 @@ packages: resolution: {integrity: sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==} engines: {node: '>=8'} - sucrase@3.35.1: - resolution: {integrity: sha512-DhuTmvZWux4H1UOnWMB3sk0sbaCVOoQZjv8u1rDoTV0HTdGem9hkAZtl4JZy8P2z4Bg0nT+YMeOFyVr4zcG5Tw==} - engines: {node: '>=16 || 14 >=14.17'} - hasBin: true - supports-preserve-symlinks-flag@1.0.0: resolution: {integrity: sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==} engines: {node: '>= 0.4'} @@ -1804,35 +1673,22 @@ packages: resolution: {integrity: sha512-yEFYrVhod+hdNyx7g5Bnkkb0G6si8HJurOoOEgC8B/O0uXLHlaey/65KRv6cuWBNhBgHKAROVpc7QyYqE5gFng==} engines: {node: '>=20'} - termi-link@1.1.0: - resolution: {integrity: sha512-2qSN6TnomHgVLtk+htSWbaYs4Rd2MH/RU7VpHTy6MBstyNyWbM4yKd1DCYpE3fDg8dmGWojXCngNi/MHCzGuAA==} - engines: {node: '>=12'} - - thenify-all@1.6.0: - resolution: {integrity: sha512-RNxQH/qI8/t3thXJDwcstUO4zeqo64+Uy/+sNVRBx4Xn2OX+OZ9oP+iJnNFqplFra2ZUVeKCSa2oVWi3T4uVmA==} - engines: {node: '>=0.8'} - - thenify@3.3.1: - resolution: {integrity: sha512-RVZSIV5IG10Hk3enotrhvz0T9em6cyHBLkH/YAZuKqd8hRkKhSfCGIcP2KUY0EPxndzANBmNllzWPwak+bheSw==} - - tinybench@2.9.0: - resolution: {integrity: sha512-0+DUvqWMValLmha6lr4kD8iAMK1HzV0/aKnCtWb9v9641TnP/MFb7Pc2bxoxQjTXAErryXVgUOfv2YqNllqGeg==} - - tinyexec@0.3.2: - resolution: {integrity: sha512-KQQR9yN7R5+OSwaK0XQoj22pwHoTlgYqmUscPYoknOoWCWfj/5/ABTMRi69FrKU5ffPVh5QcFikpWJI/P1ocHA==} + tinybench@6.2.2: + resolution: {integrity: sha512-nIZ2N2dFpjBsg2hgitKu7mA+FvsIJuGJOwysRngmVRRmWgR/UyPjwJj8c/fT/dhm8EkHnF5EOogRf+0Z2qrimA==} + engines: {node: '>=20.0.0'} tinyexec@1.3.1: resolution: {integrity: sha512-GCvB3aoys96IuDFBMcTB46JOR6mdMtAToqwiW8JlWhsoh1mhHi/xn9ss/Dg7N555GiJyEt2qzoG/NHCwM6h1EA==} engines: {node: '>=18'} - tinyglobby@0.2.16: - resolution: {integrity: sha512-pn99VhoACYR8nFHhxqix+uvsbXineAasWm5ojXoN8xEwK5Kd3/TrhNn1wByuD52UxWRLy8pu+kRMniEi6Eq9Zg==} - engines: {node: '>=12.0.0'} - tinyglobby@0.2.17: resolution: {integrity: sha512-wXR/dYpcqKmfWpEdZjiKJOwCNFndD0DMnrW/cYjVGttEkBfVgcLFHoNrlj47mjOVic9yyNu65alsgF4NQyTa2g==} engines: {node: '>=12.0.0'} + tinypool@2.2.0: + resolution: {integrity: sha512-jBrmx4lYmaC9k/mgPbylxs7kBUxHtD8256up+HjLaDFfXScKJQyil+SWXSvhAtT7XHo+yTVlpB81PGmHP8oLSQ==} + engines: {node: ^20.0.0 || >=22.0.0} + tinyrainbow@3.1.1: resolution: {integrity: sha512-yau8yJdTt989Mm0Bd/236QnzEiPf2xLLTqUZRUJOo/3CB078LSwzei343DgtJVmfJKJE3TMINY1u42SQsP6mXw==} engines: {node: '>=14.0.0'} @@ -1844,66 +1700,26 @@ packages: resolution: {integrity: sha512-yHBe+zVfzNZ3QfTPW/Z6KK1G2t340gFjMHqI/4KKSt/abzYydzuCnpqdaF5gCCABby+9Yfbj59oR5F2Fd5CBzg==} hasBin: true - toidentifier@1.0.1: - resolution: {integrity: sha512-o5sSPKEkg/DIQNmH43V0/uerLrpzVedkUh8tGNvaeXpfpuwjKenlSox/2O/BTlZUtEe+JG7s5YhEz608PlAHRA==} - engines: {node: '>=0.6'} + totalist@3.0.1: + resolution: {integrity: sha512-sf4i37nQ2LBx4m3wB74y+ubopq6W/dIzXg0FDGjsYnZHVa1Da8FH853wlL2gtUhg+xJXjfk3kUZS3BRoQeoQBQ==} + engines: {node: '>=6'} tough-cookie@6.0.1: resolution: {integrity: sha512-LktZQb3IeoUWB9lqR5EWTHgW/VTITCXg4D21M+lvybRVdylLrRMnqaIONLVb5mav8vM19m44HIcGq4qASeu2Qw==} engines: {node: '>=16'} - tree-kill@1.2.2: - resolution: {integrity: sha512-L0Orpi8qGpRG//Nd+H90vFB+3iHnue1zSSGmNOOCh1GLJ7rUKVwV2HvijphGQS2UmhUZewS9VgvxYIdgr+fG1A==} - hasBin: true - - ts-interface-checker@0.1.13: - resolution: {integrity: sha512-Y/arvbn+rrz3JCKl9C4kVNfTfSm2/mEp5FSz5EsZSANGPSlQrpRI5M4PKF+mJnE52jOO90PnPSc3Ur3bTQw0gA==} - - tslib@2.8.1: - resolution: {integrity: sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==} - - tsup@8.5.1: - resolution: {integrity: sha512-xtgkqwdhpKWr3tKPmCkvYmS9xnQK3m3XgxZHwSUjvfTjp7YfXe5tT3GgWi0F2N+ZSMsOeWeZFh7ZZFg5iPhing==} - engines: {node: '>=18'} - hasBin: true - peerDependencies: - '@microsoft/api-extractor': ^7.36.0 - '@swc/core': ^1 - postcss: ^8.5.18 - typescript: '>=4.5.0' - peerDependenciesMeta: - '@microsoft/api-extractor': - optional: true - '@swc/core': - optional: true - postcss: - optional: true - typescript: - optional: true - type-fest@5.6.0: resolution: {integrity: sha512-8ZiHFm91orbSAe2PSAiSVBVko18pbhbiB3U9GglSzF/zCGkR+rxpHx6sEMCUm4kxY4LjDIUGgCfUMtwfZfjfUA==} engines: {node: '>=20'} - type-is@2.1.0: - resolution: {integrity: sha512-faYHw0anBbc/kWF3zFTEnxSFOAGUX9GFbOBthvDdLsIlEoWOFOtS0zgCiQYwIskL9iGXZL3kAXD8OoZ4GmMATA==} - engines: {node: '>= 18'} - typescript@5.5.4: resolution: {integrity: sha512-Mtq29sKDAEYP7aljRgtPOpTvOfbwRWlS6dPRzwjdE+C0R4brX/GUyhHSecbHMFLNBLcJIPt9nl9yG5TZ1weH+Q==} engines: {node: '>=14.17'} hasBin: true - ufo@1.6.4: - resolution: {integrity: sha512-JFNbkD1Svwe0KvGi8GOeLcP4kAWQ609twvCdcHxq1oSL8svv39ZuSvajcD8B+5D0eL4+s1Is2D/O6KN3qcTeRA==} - undici-types@6.21.0: resolution: {integrity: sha512-iwDZqg0QAGrg9Rav5H4n0M64c3mkR59cJ6wQp+7C4nI0gsmExaedaYLNO44eT4AtBBwjbTiGPMlt2Md0T9H9JQ==} - unpipe@1.0.0: - resolution: {integrity: sha512-pjy2bYhSsufwWlKwPc+l3cN7+wuJlK6uz0YdJEOlQDbl6jo/YlPi4mb8agUkVC8BF7V8NuzeyPNqRksA3hztKQ==} - engines: {node: '>= 0.8'} - unplugin@2.3.11: resolution: {integrity: sha512-5uKD0nqiYVzlmCRs01Fhs2BdkEgBS3SAVP6ndrBsuK42iC2+JHyxM05Rm9G8+5mkmRtzMZGY8Ct5+mliZxU/Ww==} engines: {node: '>=18.12.0'} @@ -1915,67 +1731,36 @@ packages: resolution: {integrity: sha512-vIYxrBCC/N/K+Js3qSN88go7kIfNPssr/hHCesKCQNAjmgvYS2oqr69kIufEG+O4+PfezOH4EbIeHCfFov8ZgQ==} hasBin: true - vary@1.1.2: - resolution: {integrity: sha512-BNGbWLfd0eUPabhkXUVm0j8uuvREyTh5ovRa/dyow/BqAbZJyC+5fU+IzQOzmAKzYqYRAISoRhdQr3eIZ/PXqg==} - engines: {node: '>= 0.8'} - - vite@8.0.14: - resolution: {integrity: sha512-s4BJJ+5y1pYL6Otw51FHhVJQhPnuRinKig64g/1+EUNaJsd3gCKdD31IPFvswUgW9/60QT9oFHbZHbQK5imcxw==} - engines: {node: ^20.19.0 || >=22.12.0} + vite-plus@1.1.0: + resolution: {integrity: sha512-khkRlY70qqbBV0QmF9QKXSrhxIRYftb1H05AJMowMoq7iouchmAoXYeReZ8pKKMOadqrWVxFa3S9BA2m7w59iA==} + engines: {node: ^22.18.0 || ^24.11.0 || >=26.0.0} hasBin: true peerDependencies: - '@types/node': ^20.19.0 || >=22.12.0 - '@vitejs/devtools': ^0.1.18 - esbuild: ^0.27.0 || ^0.28.0 - jiti: '>=1.21.0' - less: ^4.0.0 - sass: ^1.70.0 - sass-embedded: ^1.70.0 - stylus: '>=0.54.8' - sugarss: ^5.0.0 - terser: ^5.16.0 - tsx: ^4.8.1 - yaml: ^2.4.2 + '@vitest/browser-playwright': 5.0.3 + '@vitest/browser-webdriverio': ^5.0.0-beta.5 || >=5.0.0 + playwright: '*' peerDependenciesMeta: - '@types/node': - optional: true - '@vitejs/devtools': - optional: true - esbuild: - optional: true - jiti: - optional: true - less: - optional: true - sass: - optional: true - sass-embedded: - optional: true - stylus: - optional: true - sugarss: - optional: true - terser: + '@vitest/browser-playwright': optional: true - tsx: + '@vitest/browser-webdriverio': optional: true - yaml: + playwright: optional: true - vitest@4.1.11: - resolution: {integrity: sha512-fhACrNXUidIbGSBr5FlbuBkO7VWC1ZyLl0DO4CU2DrQoAPxX84Ysxs+HeGQpii5lZWV1Q4gBZTTu49mF+A6Edw==} - engines: {node: ^20.0.0 || ^22.0.0 || >=24.0.0} + vitest@5.0.3: + resolution: {integrity: sha512-xMw97S3rjdtj5dkVat7jCsqWBpvchs3RlpQctUqwJD0KkERk40vz2fJ77lDwW/Vzh/pk18eItYAzkodhSes3jQ==} + engines: {node: ^22.12.0 || ^24.0.0 || >=26.0.0} hasBin: true peerDependencies: '@edge-runtime/vm': '*' '@opentelemetry/api': ^1.9.0 - '@types/node': ^20.0.0 || ^22.0.0 || >=24.0.0 - '@vitest/browser-playwright': 4.1.11 - '@vitest/browser-preview': 4.1.11 - '@vitest/browser-webdriverio': 4.1.11 - '@vitest/coverage-istanbul': 4.1.11 - '@vitest/coverage-v8': 4.1.11 - '@vitest/ui': 4.1.11 + '@types/node': ^22.0.0 || >=24.0.0 + '@vitest/browser-playwright': 5.0.3 + '@vitest/browser-preview': 5.0.3 + '@vitest/browser-webdriverio': ^5.0.0-beta.5 || >=5.0.0 + '@vitest/coverage-istanbul': 5.0.3 + '@vitest/coverage-v8': 5.0.3 + '@vitest/ui': 5.0.3 happy-dom: '*' jsdom: '*' vite: ^8.0.5 @@ -2006,17 +1791,26 @@ packages: webpack-virtual-modules@0.6.2: resolution: {integrity: sha512-66/V2i5hQanC51vBQKPH4aI8NMAcBW59FVBs+rC7eGHupMyfn34q7rZIE+ETlJ+XTevqfUhVVBgSUNSW2flEUQ==} - why-is-node-running@2.3.0: - resolution: {integrity: sha512-hUrmaWBdVDcxvYqnyh09zunKzROWjbZTiNy8dBEjkS7ehEDQibXJ7XvlmtbwuTclUiIyN+CyXQD4Vmko8fNm8w==} - engines: {node: '>=8'} + why-is-node-running@3.2.1: + resolution: {integrity: sha512-Tb2FUhB4vUsGQlfSquQLYkApkuPAFQXGFzxWKHHumVz2dK+X1RUm/HnID4+TfIGYJ1kTcwOaCk/buYCEJr6YjQ==} + engines: {node: '>=20.11'} hasBin: true wrap-ansi@7.0.0: resolution: {integrity: sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==} engines: {node: '>=10'} - wrappy@1.0.2: - resolution: {integrity: sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==} + ws@8.22.0: + resolution: {integrity: sha512-Ydggc987+RO0AnWtZ/7Wq9FtNvcrL1b/RO0ud9mWjUPgDrsAAwQSF51sm2hm1XofbU/4jkpGEsLFsZZxU+1DOg==} + engines: {node: '>=10.0.0'} + peerDependencies: + bufferutil: ^4.0.1 + utf-8-validate: '>=5.0.2' + peerDependenciesMeta: + bufferutil: + optional: true + utf-8-validate: + optional: true y18n@5.0.8: resolution: {integrity: sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==} @@ -2030,6 +1824,15 @@ packages: resolution: {integrity: sha512-7dSzzRQ++CKnNI/krKnYRV7JKKPUXMEh61soaHKg9mrWEhzFWhFnxPxGl+69cD1Ou63C13NUPCnmIcrvqCuM6w==} engines: {node: '>=12'} + yuku-ast@0.9.5: + resolution: {integrity: sha512-Q8qW8WwQnN5Cm0ZZivdRIfv0sRLTjUq0YumXJkw8CYN1aCdICH9rk4C47/4n6kA5EqDtlj+F608twoTSV2MwdQ==} + + yuku-codegen@0.9.5: + resolution: {integrity: sha512-zGUVyDpSK4b6c9B0yyxi2P0wujn1XZTbG9dCb7d6gyAoP63EMgyLzUwPUvwxPG5jgYqhlI7w+S3oCyapqGr4PA==} + + yuku-parser@0.9.5: + resolution: {integrity: sha512-IBnAdNVMswWbJcM7woPluOudectJzFjJ1N8jVRxP9Uq4CIv5hZdBIcdmtRbFDYF/Ph2j2gVup4YJ4N9mh0jYFA==} + zod-to-json-schema@3.25.2: resolution: {integrity: sha512-O/PgfnpT1xKSDeQYSCfRI5Gy3hPf91mKVDuYLUHZJMiDFptvP41MSnWofm8dnCm0256ZNfZIM7DSzuSMAFnjHA==} peerDependencies: @@ -2040,26 +1843,17 @@ packages: snapshots: - '@braintrust/bt-darwin-arm64@0.12.0': - optional: true - - '@braintrust/bt-darwin-x64@0.12.0': - optional: true - - '@braintrust/bt-linux-arm64@0.12.0': - optional: true - - '@braintrust/bt-linux-x64-musl@0.12.0': - optional: true + '@babel/code-frame@7.29.7': + dependencies: + '@babel/helper-validator-identifier': 7.29.7 + js-tokens: 4.0.0 + picocolors: 1.1.1 - '@braintrust/bt-linux-x64@0.12.0': - optional: true + '@babel/helper-validator-identifier@7.29.7': {} - '@braintrust/bt-win32-arm64@0.12.0': - optional: true + '@babel/runtime@7.29.10': {} - '@braintrust/bt-win32-x64@0.12.0': - optional: true + '@blazediff/core@1.10.0': {} '@braintrust/otel@file:..(@opentelemetry/api@1.9.0)(@opentelemetry/core@2.0.0(@opentelemetry/api@1.9.0))(@opentelemetry/exporter-trace-otlp-http@0.200.0(@opentelemetry/api@1.9.0))(@opentelemetry/sdk-trace-base@2.0.0(@opentelemetry/api@1.9.0))(braintrust@file:../../../js(zod@4.4.3))': dependencies: @@ -2069,178 +1863,81 @@ snapshots: '@opentelemetry/sdk-trace-base': 2.0.0(@opentelemetry/api@1.9.0) braintrust: file:../../../js(zod@4.4.3) - '@colors/colors@1.5.0': - optional: true - - '@emnapi/core@1.10.0': - dependencies: - '@emnapi/wasi-threads': 1.2.1 - tslib: 2.8.1 - optional: true - - '@emnapi/runtime@1.10.0': - dependencies: - tslib: 2.8.1 - optional: true - - '@emnapi/wasi-threads@1.2.1': - dependencies: - tslib: 2.8.1 - optional: true - - '@esbuild/aix-ppc64@0.27.7': - optional: true - '@esbuild/aix-ppc64@0.28.1': optional: true - '@esbuild/android-arm64@0.27.7': - optional: true - '@esbuild/android-arm64@0.28.1': optional: true - '@esbuild/android-arm@0.27.7': - optional: true - '@esbuild/android-arm@0.28.1': optional: true - '@esbuild/android-x64@0.27.7': - optional: true - '@esbuild/android-x64@0.28.1': optional: true - '@esbuild/darwin-arm64@0.27.7': - optional: true - '@esbuild/darwin-arm64@0.28.1': optional: true - '@esbuild/darwin-x64@0.27.7': - optional: true - '@esbuild/darwin-x64@0.28.1': optional: true - '@esbuild/freebsd-arm64@0.27.7': - optional: true - '@esbuild/freebsd-arm64@0.28.1': optional: true - '@esbuild/freebsd-x64@0.27.7': - optional: true - '@esbuild/freebsd-x64@0.28.1': optional: true - '@esbuild/linux-arm64@0.27.7': - optional: true - '@esbuild/linux-arm64@0.28.1': optional: true - '@esbuild/linux-arm@0.27.7': - optional: true - '@esbuild/linux-arm@0.28.1': optional: true - '@esbuild/linux-ia32@0.27.7': - optional: true - '@esbuild/linux-ia32@0.28.1': optional: true - '@esbuild/linux-loong64@0.27.7': - optional: true - '@esbuild/linux-loong64@0.28.1': optional: true - '@esbuild/linux-mips64el@0.27.7': - optional: true - '@esbuild/linux-mips64el@0.28.1': optional: true - '@esbuild/linux-ppc64@0.27.7': - optional: true - '@esbuild/linux-ppc64@0.28.1': optional: true - '@esbuild/linux-riscv64@0.27.7': - optional: true - '@esbuild/linux-riscv64@0.28.1': optional: true - '@esbuild/linux-s390x@0.27.7': - optional: true - '@esbuild/linux-s390x@0.28.1': optional: true - '@esbuild/linux-x64@0.27.7': - optional: true - '@esbuild/linux-x64@0.28.1': optional: true - '@esbuild/netbsd-arm64@0.27.7': - optional: true - '@esbuild/netbsd-arm64@0.28.1': optional: true - '@esbuild/netbsd-x64@0.27.7': - optional: true - '@esbuild/netbsd-x64@0.28.1': optional: true - '@esbuild/openbsd-arm64@0.27.7': - optional: true - '@esbuild/openbsd-arm64@0.28.1': optional: true - '@esbuild/openbsd-x64@0.27.7': - optional: true - '@esbuild/openbsd-x64@0.28.1': optional: true - '@esbuild/openharmony-arm64@0.27.7': - optional: true - '@esbuild/openharmony-arm64@0.28.1': optional: true - '@esbuild/sunos-x64@0.27.7': - optional: true - '@esbuild/sunos-x64@0.28.1': optional: true - '@esbuild/win32-arm64@0.27.7': - optional: true - '@esbuild/win32-arm64@0.28.1': optional: true - '@esbuild/win32-ia32@0.27.7': - optional: true - '@esbuild/win32-ia32@0.28.1': optional: true - '@esbuild/win32-x64@0.27.7': - optional: true - '@esbuild/win32-x64@0.28.1': optional: true @@ -2297,6 +1994,8 @@ snapshots: '@jridgewell/sourcemap-codec@1.5.5': {} + '@jridgewell/sourcemap-codec@1.6.0': {} + '@jridgewell/trace-mapping@0.3.31': dependencies: '@jridgewell/resolve-uri': 3.1.2 @@ -2313,15 +2012,6 @@ snapshots: outvariant: 1.4.3 strict-event-emitter: 0.5.1 - '@napi-rs/wasm-runtime@1.2.4(@emnapi/core@1.10.0)(@emnapi/runtime@1.10.0)': - dependencies: - '@emnapi/core': 1.10.0 - '@emnapi/runtime': 1.10.0 - '@tybys/wasm-util': 0.10.4 - optional: true - - '@next/env@14.2.35': {} - '@open-draft/deferred-promise@2.2.0': {} '@open-draft/deferred-promise@3.0.0': {} @@ -2566,160 +2256,182 @@ snapshots: '@opentelemetry/semantic-conventions@1.41.1': {} - '@oxc-project/types@0.132.0': {} - - '@protobufjs/aspromise@1.1.2': {} - - '@protobufjs/base64@1.1.2': {} - - '@protobufjs/codegen@2.0.5': {} - - '@protobufjs/eventemitter@1.1.1': {} - - '@protobufjs/fetch@1.1.1': - dependencies: - '@protobufjs/aspromise': 1.1.2 + '@oxc-project/runtime@0.153.0': {} - '@protobufjs/float@1.0.2': {} + '@oxc-project/types@0.153.0': {} - '@protobufjs/path@1.1.2': {} + '@oxfmt/binding-android-arm-eabi@0.72.0': + optional: true - '@protobufjs/pool@1.1.0': {} + '@oxfmt/binding-android-arm64@0.72.0': + optional: true - '@protobufjs/utf8@1.1.1': {} + '@oxfmt/binding-darwin-arm64@0.72.0': + optional: true - '@rolldown/binding-android-arm64@1.0.2': + '@oxfmt/binding-darwin-x64@0.72.0': optional: true - '@rolldown/binding-darwin-arm64@1.0.2': + '@oxfmt/binding-freebsd-x64@0.72.0': optional: true - '@rolldown/binding-darwin-x64@1.0.2': + '@oxfmt/binding-linux-arm-gnueabihf@0.72.0': optional: true - '@rolldown/binding-freebsd-x64@1.0.2': + '@oxfmt/binding-linux-arm-musleabihf@0.72.0': optional: true - '@rolldown/binding-linux-arm-gnueabihf@1.0.2': + '@oxfmt/binding-linux-arm64-gnu@0.72.0': optional: true - '@rolldown/binding-linux-arm64-gnu@1.0.2': + '@oxfmt/binding-linux-arm64-musl@0.72.0': optional: true - '@rolldown/binding-linux-arm64-musl@1.0.2': + '@oxfmt/binding-linux-ppc64-gnu@0.72.0': optional: true - '@rolldown/binding-linux-ppc64-gnu@1.0.2': + '@oxfmt/binding-linux-riscv64-gnu@0.72.0': optional: true - '@rolldown/binding-linux-s390x-gnu@1.0.2': + '@oxfmt/binding-linux-riscv64-musl@0.72.0': optional: true - '@rolldown/binding-linux-x64-gnu@1.0.2': + '@oxfmt/binding-linux-s390x-gnu@0.72.0': optional: true - '@rolldown/binding-linux-x64-musl@1.0.2': + '@oxfmt/binding-linux-x64-gnu@0.72.0': optional: true - '@rolldown/binding-openharmony-arm64@1.0.2': + '@oxfmt/binding-linux-x64-musl@0.72.0': optional: true - '@rolldown/binding-wasm32-wasi@1.0.2': - dependencies: - '@emnapi/core': 1.10.0 - '@emnapi/runtime': 1.10.0 - '@napi-rs/wasm-runtime': 1.2.4(@emnapi/core@1.10.0)(@emnapi/runtime@1.10.0) + '@oxfmt/binding-openharmony-arm64@0.72.0': optional: true - '@rolldown/binding-win32-arm64-msvc@1.0.2': + '@oxfmt/binding-win32-arm64-msvc@0.72.0': optional: true - '@rolldown/binding-win32-x64-msvc@1.0.2': + '@oxfmt/binding-win32-ia32-msvc@0.72.0': optional: true - '@rolldown/pluginutils@1.0.1': {} + '@oxfmt/binding-win32-x64-msvc@0.72.0': + optional: true - '@rollup/rollup-android-arm-eabi@4.60.4': + '@oxlint-tsgolint/darwin-arm64@7.0.2003': optional: true - '@rollup/rollup-android-arm64@4.60.4': + '@oxlint-tsgolint/darwin-x64@7.0.2003': optional: true - '@rollup/rollup-darwin-arm64@4.60.4': + '@oxlint-tsgolint/linux-arm64@7.0.2003': optional: true - '@rollup/rollup-darwin-x64@4.60.4': + '@oxlint-tsgolint/linux-x64@7.0.2003': optional: true - '@rollup/rollup-freebsd-arm64@4.60.4': + '@oxlint-tsgolint/win32-arm64@7.0.2003': optional: true - '@rollup/rollup-freebsd-x64@4.60.4': + '@oxlint-tsgolint/win32-x64@7.0.2003': optional: true - '@rollup/rollup-linux-arm-gnueabihf@4.60.4': + '@oxlint/binding-android-arm-eabi@1.87.0': optional: true - '@rollup/rollup-linux-arm-musleabihf@4.60.4': + '@oxlint/binding-android-arm64@1.87.0': optional: true - '@rollup/rollup-linux-arm64-gnu@4.60.4': + '@oxlint/binding-darwin-arm64@1.87.0': optional: true - '@rollup/rollup-linux-arm64-musl@4.60.4': + '@oxlint/binding-darwin-x64@1.87.0': optional: true - '@rollup/rollup-linux-loong64-gnu@4.60.4': + '@oxlint/binding-freebsd-x64@1.87.0': optional: true - '@rollup/rollup-linux-loong64-musl@4.60.4': + '@oxlint/binding-linux-arm-gnueabihf@1.87.0': optional: true - '@rollup/rollup-linux-ppc64-gnu@4.60.4': + '@oxlint/binding-linux-arm-musleabihf@1.87.0': optional: true - '@rollup/rollup-linux-ppc64-musl@4.60.4': + '@oxlint/binding-linux-arm64-gnu@1.87.0': optional: true - '@rollup/rollup-linux-riscv64-gnu@4.60.4': + '@oxlint/binding-linux-arm64-musl@1.87.0': optional: true - '@rollup/rollup-linux-riscv64-musl@4.60.4': + '@oxlint/binding-linux-ppc64-gnu@1.87.0': optional: true - '@rollup/rollup-linux-s390x-gnu@4.60.4': + '@oxlint/binding-linux-riscv64-gnu@1.87.0': optional: true - '@rollup/rollup-linux-x64-gnu@4.60.4': + '@oxlint/binding-linux-riscv64-musl@1.87.0': optional: true - '@rollup/rollup-linux-x64-musl@4.60.4': + '@oxlint/binding-linux-s390x-gnu@1.87.0': optional: true - '@rollup/rollup-openbsd-x64@4.60.4': + '@oxlint/binding-linux-x64-gnu@1.87.0': optional: true - '@rollup/rollup-openharmony-arm64@4.60.4': + '@oxlint/binding-linux-x64-musl@1.87.0': optional: true - '@rollup/rollup-win32-arm64-msvc@4.60.4': + '@oxlint/binding-openharmony-arm64@1.87.0': optional: true - '@rollup/rollup-win32-ia32-msvc@4.60.4': + '@oxlint/binding-win32-arm64-msvc@1.87.0': optional: true - '@rollup/rollup-win32-x64-gnu@4.60.4': + '@oxlint/binding-win32-ia32-msvc@1.87.0': optional: true - '@rollup/rollup-win32-x64-msvc@4.60.4': + '@oxlint/binding-win32-x64-msvc@1.87.0': optional: true - '@standard-schema/spec@1.1.0': {} + '@oxlint/plugins@1.79.0': {} + + '@polka/url@1.0.0-next.29': {} + + '@protobufjs/aspromise@1.1.2': {} + + '@protobufjs/base64@1.1.2': {} + + '@protobufjs/codegen@2.0.5': {} + + '@protobufjs/eventemitter@1.1.1': {} - '@tybys/wasm-util@0.10.4': + '@protobufjs/fetch@1.1.1': dependencies: - tslib: 2.8.1 - optional: true + '@protobufjs/aspromise': 1.1.2 + + '@protobufjs/float@1.0.2': {} + + '@protobufjs/path@1.1.2': {} + + '@protobufjs/pool@1.1.0': {} + + '@protobufjs/utf8@1.1.1': {} + + '@testing-library/dom@10.4.2': + dependencies: + '@babel/code-frame': 7.29.7 + '@babel/runtime': 7.29.10 + '@types/aria-query': 5.0.4 + aria-query: 5.3.0 + dom-accessibility-api: 0.5.16 + lz-string: 1.5.0 + picocolors: 1.1.1 + pretty-format: 27.5.1 + + '@testing-library/user-event@14.6.8(@testing-library/dom@10.4.2)': + dependencies: + '@testing-library/dom': 10.4.2 + + '@types/aria-query@5.0.4': {} '@types/chai@5.2.3': dependencies: @@ -2728,8 +2440,6 @@ snapshots: '@types/deep-eql@4.0.2': {} - '@types/estree@1.0.8': {} - '@types/estree@1.0.9': {} '@types/node@22.19.19': @@ -2746,52 +2456,194 @@ snapshots: '@vercel/functions@1.6.0': {} - '@vitest/expect@4.1.11': + '@vitest/browser-preview@5.0.3(@voidzero-dev/vite-plus-core@1.1.0(@types/node@22.19.19)(esbuild@0.28.1)(typescript@5.5.4))(msw@2.14.6(@types/node@22.19.19)(typescript@5.5.4))(vitest@5.0.3)': dependencies: - '@standard-schema/spec': 1.1.0 - '@types/chai': 5.2.3 - '@vitest/spy': 4.1.11 - '@vitest/utils': 4.1.11 - chai: 6.2.2 + '@testing-library/dom': 10.4.2 + '@testing-library/user-event': 14.6.8(@testing-library/dom@10.4.2) + '@vitest/browser': 5.0.3(@voidzero-dev/vite-plus-core@1.1.0(@types/node@22.19.19)(esbuild@0.28.1)(typescript@5.5.4))(msw@2.14.6(@types/node@22.19.19)(typescript@5.5.4))(vitest@5.0.3) + vitest: 5.0.3(@opentelemetry/api@1.9.0)(@types/node@22.19.19)(@vitest/browser-preview@5.0.3)(@vitest/ui@5.0.3)(@voidzero-dev/vite-plus-core@1.1.0(@types/node@22.19.19)(esbuild@0.28.1)(typescript@5.5.4))(msw@2.14.6(@types/node@22.19.19)(typescript@5.5.4)) + transitivePeerDependencies: + - bufferutil + - msw + - utf-8-validate + - vite + + '@vitest/browser@5.0.3(@voidzero-dev/vite-plus-core@1.1.0(@types/node@22.19.19)(esbuild@0.28.1)(typescript@5.5.4))(msw@2.14.6(@types/node@22.19.19)(typescript@5.5.4))(vitest@5.0.3)': + dependencies: + '@blazediff/core': 1.10.0 + '@vitest/mocker': 5.0.3(@voidzero-dev/vite-plus-core@1.1.0(@types/node@22.19.19)(esbuild@0.28.1)(typescript@5.5.4))(msw@2.14.6(@types/node@22.19.19)(typescript@5.5.4)) + '@vitest/ui': 5.0.3(vitest@5.0.3) + '@vitest/utils': 5.0.3 + magic-string: 1.4.3 + pngjs: 7.0.0 + sirv: 3.0.2 tinyrainbow: 3.1.1 + vitest: 5.0.3(@opentelemetry/api@1.9.0)(@types/node@22.19.19)(@vitest/browser-preview@5.0.3)(@vitest/ui@5.0.3)(@voidzero-dev/vite-plus-core@1.1.0(@types/node@22.19.19)(esbuild@0.28.1)(typescript@5.5.4))(msw@2.14.6(@types/node@22.19.19)(typescript@5.5.4)) + ws: 8.22.0 + transitivePeerDependencies: + - bufferutil + - msw + - utf-8-validate + - vite - '@vitest/mocker@4.1.11(msw@2.14.6(@types/node@22.19.19)(typescript@5.5.4))(vite@8.0.14(@types/node@22.19.19)(esbuild@0.27.7))': + '@vitest/mocker@5.0.3(@voidzero-dev/vite-plus-core@1.1.0(@types/node@22.19.19)(esbuild@0.28.1)(typescript@5.5.4))(msw@2.14.6(@types/node@22.19.19)(typescript@5.5.4))': dependencies: - '@vitest/spy': 4.1.11 + '@jridgewell/trace-mapping': 0.3.31 + '@vitest/spy': 5.0.3 estree-walker: 3.0.3 - magic-string: 0.30.21 + magic-string: 1.4.3 optionalDependencies: msw: 2.14.6(@types/node@22.19.19)(typescript@5.5.4) - vite: 8.0.14(@types/node@22.19.19)(esbuild@0.27.7) + vite: '@voidzero-dev/vite-plus-core@1.1.0(@types/node@22.19.19)(esbuild@0.28.1)(typescript@5.5.4)' + + '@vitest/pretty-format@5.0.3': + dependencies: + tinyrainbow: 3.1.1 + + '@vitest/snapshot@5.0.3': + dependencies: + '@vitest/pretty-format': 5.0.3 + '@vitest/utils': 5.0.3 + magic-string: 1.4.3 + pathe: 2.0.3 + + '@vitest/spy@5.0.3': {} + + '@vitest/ui@5.0.3(vitest@5.0.3)': + dependencies: + '@vitest/utils': 5.0.3 + fflate: 0.8.3 + flatted: 3.4.4 + pathe: 2.0.3 + sirv: 3.0.2 + tinyrainbow: 3.1.1 + vitest: 5.0.3(@opentelemetry/api@1.9.0)(@types/node@22.19.19)(@vitest/browser-preview@5.0.3)(@vitest/ui@5.0.3)(@voidzero-dev/vite-plus-core@1.1.0(@types/node@22.19.19)(esbuild@0.28.1)(typescript@5.5.4))(msw@2.14.6(@types/node@22.19.19)(typescript@5.5.4)) + + '@vitest/utils@5.0.3': + dependencies: + '@vitest/pretty-format': 5.0.3 + convert-source-map: 2.0.0 + tinyrainbow: 3.1.1 + + '@voidzero-dev/vite-plus-core@1.1.0(@types/node@22.19.19)(esbuild@0.28.1)(typescript@5.5.4)': + dependencies: + '@oxc-project/runtime': 0.153.0 + '@oxc-project/types': 0.153.0 + lightningcss: 1.33.0 + postcss: 8.5.28 + yuku-codegen: 0.9.5 + yuku-parser: 0.9.5 + optionalDependencies: + '@types/node': 22.19.19 + '@voidzero-dev/vite-plus-darwin-arm64': 1.1.0 + '@voidzero-dev/vite-plus-darwin-x64': 1.1.0 + '@voidzero-dev/vite-plus-linux-arm64-gnu': 1.1.0 + '@voidzero-dev/vite-plus-linux-arm64-musl': 1.1.0 + '@voidzero-dev/vite-plus-linux-x64-gnu': 1.1.0 + '@voidzero-dev/vite-plus-linux-x64-musl': 1.1.0 + '@voidzero-dev/vite-plus-win32-arm64-msvc': 1.1.0 + '@voidzero-dev/vite-plus-win32-x64-msvc': 1.1.0 + esbuild: 0.28.1 + fsevents: 2.3.3 + typescript: 5.5.4 + + '@voidzero-dev/vite-plus-darwin-arm64@1.1.0': + optional: true + + '@voidzero-dev/vite-plus-darwin-x64@1.1.0': + optional: true + + '@voidzero-dev/vite-plus-linux-arm64-gnu@1.1.0': + optional: true + + '@voidzero-dev/vite-plus-linux-arm64-musl@1.1.0': + optional: true + + '@voidzero-dev/vite-plus-linux-x64-gnu@1.1.0': + optional: true + + '@voidzero-dev/vite-plus-linux-x64-musl@1.1.0': + optional: true + + '@voidzero-dev/vite-plus-win32-arm64-msvc@1.1.0': + optional: true + + '@voidzero-dev/vite-plus-win32-x64-msvc@1.1.0': + optional: true + + '@yuku-codegen/binding-android-arm64@0.9.5': + optional: true + + '@yuku-codegen/binding-darwin-arm64@0.9.5': + optional: true + + '@yuku-codegen/binding-darwin-x64@0.9.5': + optional: true + + '@yuku-codegen/binding-freebsd-x64@0.9.5': + optional: true + + '@yuku-codegen/binding-linux-arm-gnu@0.9.5': + optional: true + + '@yuku-codegen/binding-linux-arm-musl@0.9.5': + optional: true + + '@yuku-codegen/binding-linux-arm64-gnu@0.9.5': + optional: true + + '@yuku-codegen/binding-linux-arm64-musl@0.9.5': + optional: true + + '@yuku-codegen/binding-linux-x64-gnu@0.9.5': + optional: true + + '@yuku-codegen/binding-linux-x64-musl@0.9.5': + optional: true + + '@yuku-codegen/binding-win32-arm64@0.9.5': + optional: true + + '@yuku-codegen/binding-win32-x64@0.9.5': + optional: true + + '@yuku-parser/binding-android-arm64@0.9.5': + optional: true + + '@yuku-parser/binding-darwin-arm64@0.9.5': + optional: true + + '@yuku-parser/binding-darwin-x64@0.9.5': + optional: true + + '@yuku-parser/binding-freebsd-x64@0.9.5': + optional: true + + '@yuku-parser/binding-linux-arm-gnu@0.9.5': + optional: true - '@vitest/pretty-format@4.1.11': - dependencies: - tinyrainbow: 3.1.1 + '@yuku-parser/binding-linux-arm-musl@0.9.5': + optional: true - '@vitest/runner@4.1.11': - dependencies: - '@vitest/utils': 4.1.11 - pathe: 2.0.3 + '@yuku-parser/binding-linux-arm64-gnu@0.9.5': + optional: true - '@vitest/snapshot@4.1.11': - dependencies: - '@vitest/pretty-format': 4.1.11 - '@vitest/utils': 4.1.11 - magic-string: 0.30.21 - pathe: 2.0.3 + '@yuku-parser/binding-linux-arm64-musl@0.9.5': + optional: true - '@vitest/spy@4.1.11': {} + '@yuku-parser/binding-linux-x64-gnu@0.9.5': + optional: true - '@vitest/utils@4.1.11': - dependencies: - '@vitest/pretty-format': 4.1.11 - convert-source-map: 2.0.0 - tinyrainbow: 3.1.1 + '@yuku-parser/binding-linux-x64-musl@0.9.5': + optional: true - accepts@2.0.0: - dependencies: - mime-types: 3.0.2 - negotiator: 1.0.0 + '@yuku-parser/binding-win32-arm64@0.9.5': + optional: true + + '@yuku-parser/binding-win32-x64@0.9.5': + optional: true + + '@yuku-toolchain/types@0.9.5': {} acorn-import-attributes@1.9.5(acorn@8.16.0): dependencies: @@ -2802,7 +2654,7 @@ snapshots: ajv@8.20.0: dependencies: fast-deep-equal: 3.1.3 - fast-uri: 3.1.8 + fast-uri: 3.1.9 json-schema-traverse: 1.0.0 require-from-string: 2.0.2 @@ -2812,116 +2664,45 @@ snapshots: dependencies: color-convert: 2.0.1 - any-promise@1.3.0: {} + ansi-styles@5.2.0: {} - argparse@2.0.1: {} + aria-query@5.3.0: + dependencies: + dequal: 2.0.3 assertion-error@2.0.1: {} astring@1.9.0: {} - balanced-match@4.0.4: {} - - body-parser@2.3.0: - dependencies: - bytes: 3.1.2 - content-type: 2.0.0 - debug: 4.4.3 - http-errors: 2.0.1 - iconv-lite: 0.7.2 - on-finished: 2.4.1 - qs: 6.15.2 - raw-body: 3.0.2 - type-is: 2.1.0 - transitivePeerDependencies: - - supports-color - - brace-expansion@5.0.12: - dependencies: - balanced-match: 4.0.4 - braintrust@file:../../../js(zod@4.4.3): dependencies: - '@next/env': 14.2.35 '@vercel/functions': 1.6.0 acorn: 8.16.0 acorn-import-attributes: 1.9.5(acorn@8.16.0) ajv: 8.20.0 - argparse: 2.0.1 astring: 1.9.0 cjs-module-lexer: 2.2.0 - cli-progress: 3.12.0 - cli-table3: 0.6.5 - cors: 2.8.6 dotenv: 16.6.1 - esbuild: 0.28.1 esquery: 1.7.0 eventsource-parser: 1.1.2 - express: 5.2.1 - http-errors: 2.0.1 meriyah: 6.1.4 - minimatch: 10.2.5 module-details-from-path: 1.0.4 mustache: 4.2.0 - pluralize: 8.0.0 semifies: 1.0.0 source-map: 0.7.6 - termi-link: 1.1.0 unplugin: 2.3.11 uuid: 11.1.1 zod: 4.4.3 zod-to-json-schema: 3.25.2(zod@4.4.3) - optionalDependencies: - '@braintrust/bt-darwin-arm64': 0.12.0 - '@braintrust/bt-darwin-x64': 0.12.0 - '@braintrust/bt-linux-arm64': 0.12.0 - '@braintrust/bt-linux-x64': 0.12.0 - '@braintrust/bt-linux-x64-musl': 0.12.0 - '@braintrust/bt-win32-arm64': 0.12.0 - '@braintrust/bt-win32-x64': 0.12.0 transitivePeerDependencies: - '@aws-sdk/credential-provider-web-identity' - - supports-color - - bundle-require@5.1.0(esbuild@0.27.7): - dependencies: - esbuild: 0.27.7 - load-tsconfig: 0.2.5 - - bytes@3.1.2: {} - - cac@6.7.14: {} - - call-bind-apply-helpers@1.0.2: - dependencies: - es-errors: 1.3.0 - function-bind: 1.1.2 - - call-bound@1.0.4: - dependencies: - call-bind-apply-helpers: 1.0.2 - get-intrinsic: 1.3.0 chai@6.2.2: {} - chokidar@4.0.3: - dependencies: - readdirp: 4.1.2 - cjs-module-lexer@1.4.3: {} cjs-module-lexer@2.2.0: {} - cli-progress@3.12.0: - dependencies: - string-width: 4.2.3 - - cli-table3@0.6.5: - dependencies: - string-width: 4.2.3 - optionalDependencies: - '@colors/colors': 1.5.0 - cli-width@4.1.0: {} cliui@8.0.1: @@ -2936,92 +2717,28 @@ snapshots: color-name@1.1.4: {} - commander@4.1.1: {} - - confbox@0.1.8: {} - - consola@3.4.2: {} - - content-disposition@1.1.0: {} - - content-type@1.0.5: {} - - content-type@2.0.0: {} - convert-source-map@2.0.0: {} - cookie-signature@1.2.2: {} - - cookie@0.7.2: {} - cookie@1.1.1: {} - cors@2.8.6: - dependencies: - object-assign: 4.1.1 - vary: 1.1.2 - debug@4.4.3: dependencies: ms: 2.1.3 - depd@2.0.0: {} + dequal@2.0.3: {} detect-libc@2.1.2: {} - dotenv@16.6.1: {} - - dunder-proto@1.0.1: - dependencies: - call-bind-apply-helpers: 1.0.2 - es-errors: 1.3.0 - gopd: 1.2.0 + dom-accessibility-api@0.5.16: {} - ee-first@1.1.1: {} + dotenv@16.6.1: {} emoji-regex@8.0.0: {} - encodeurl@2.0.0: {} - - es-define-property@1.0.1: {} - es-errors@1.3.0: {} es-module-lexer@2.3.2: {} - es-object-atoms@1.1.2: - dependencies: - es-errors: 1.3.0 - - esbuild@0.27.7: - optionalDependencies: - '@esbuild/aix-ppc64': 0.27.7 - '@esbuild/android-arm': 0.27.7 - '@esbuild/android-arm64': 0.27.7 - '@esbuild/android-x64': 0.27.7 - '@esbuild/darwin-arm64': 0.27.7 - '@esbuild/darwin-x64': 0.27.7 - '@esbuild/freebsd-arm64': 0.27.7 - '@esbuild/freebsd-x64': 0.27.7 - '@esbuild/linux-arm': 0.27.7 - '@esbuild/linux-arm64': 0.27.7 - '@esbuild/linux-ia32': 0.27.7 - '@esbuild/linux-loong64': 0.27.7 - '@esbuild/linux-mips64el': 0.27.7 - '@esbuild/linux-ppc64': 0.27.7 - '@esbuild/linux-riscv64': 0.27.7 - '@esbuild/linux-s390x': 0.27.7 - '@esbuild/linux-x64': 0.27.7 - '@esbuild/netbsd-arm64': 0.27.7 - '@esbuild/netbsd-x64': 0.27.7 - '@esbuild/openbsd-arm64': 0.27.7 - '@esbuild/openbsd-x64': 0.27.7 - '@esbuild/openharmony-arm64': 0.27.7 - '@esbuild/sunos-x64': 0.27.7 - '@esbuild/win32-arm64': 0.27.7 - '@esbuild/win32-ia32': 0.27.7 - '@esbuild/win32-x64': 0.27.7 - esbuild@0.28.1: optionalDependencies: '@esbuild/aix-ppc64': 0.28.1 @@ -3050,11 +2767,10 @@ snapshots: '@esbuild/win32-arm64': 0.28.1 '@esbuild/win32-ia32': 0.28.1 '@esbuild/win32-x64': 0.28.1 + optional: true escalade@3.2.0: {} - escape-html@1.0.3: {} - esquery@1.7.0: dependencies: estraverse: 5.3.0 @@ -3065,45 +2781,10 @@ snapshots: dependencies: '@types/estree': 1.0.9 - etag@1.8.1: {} - eventsource-parser@1.1.2: {} expect-type@1.4.0: {} - express@5.2.1: - dependencies: - accepts: 2.0.0 - body-parser: 2.3.0 - content-disposition: 1.1.0 - content-type: 1.0.5 - cookie: 0.7.2 - cookie-signature: 1.2.2 - debug: 4.4.3 - depd: 2.0.0 - encodeurl: 2.0.0 - escape-html: 1.0.3 - etag: 1.8.1 - finalhandler: 2.1.1 - fresh: 2.0.0 - http-errors: 2.0.1 - merge-descriptors: 2.0.0 - mime-types: 3.0.2 - on-finished: 2.4.1 - once: 1.4.0 - parseurl: 1.3.3 - proxy-addr: 2.0.8 - qs: 6.15.2 - range-parser: 1.2.1 - router: 2.2.0 - send: 1.2.1 - serve-static: 2.2.1 - statuses: 2.0.2 - type-is: 2.1.0 - vary: 1.1.2 - transitivePeerDependencies: - - supports-color - fast-deep-equal@3.1.3: {} fast-string-truncated-width@3.0.3: {} @@ -3112,40 +2793,19 @@ snapshots: dependencies: fast-string-truncated-width: 3.0.3 - fast-uri@3.1.8: {} + fast-uri@3.1.9: {} fast-wrap-ansi@0.2.2: dependencies: fast-string-width: 3.0.2 - fdir@6.5.0(picomatch@4.0.4): - optionalDependencies: - picomatch: 4.0.4 - fdir@6.5.0(picomatch@4.0.7): optionalDependencies: picomatch: 4.0.7 - finalhandler@2.1.1: - dependencies: - debug: 4.4.3 - encodeurl: 2.0.0 - escape-html: 1.0.3 - on-finished: 2.4.1 - parseurl: 1.3.3 - statuses: 2.0.2 - transitivePeerDependencies: - - supports-color - - fix-dts-default-cjs-exports@1.0.1: - dependencies: - magic-string: 0.30.21 - mlly: 1.8.2 - rollup: 4.60.4 - - forwarded@0.2.0: {} + fflate@0.8.3: {} - fresh@2.0.0: {} + flatted@3.4.4: {} fsevents@2.3.3: optional: true @@ -3154,30 +2814,8 @@ snapshots: get-caller-file@2.0.5: {} - get-intrinsic@1.3.0: - dependencies: - call-bind-apply-helpers: 1.0.2 - es-define-property: 1.0.1 - es-errors: 1.3.0 - es-object-atoms: 1.1.2 - function-bind: 1.1.2 - get-proto: 1.0.1 - gopd: 1.2.0 - has-symbols: 1.1.0 - hasown: 2.0.3 - math-intrinsics: 1.1.0 - - get-proto@1.0.1: - dependencies: - dunder-proto: 1.0.1 - es-object-atoms: 1.1.2 - - gopd@1.2.0: {} - graphql@16.14.0: {} - has-symbols@1.1.0: {} - hasown@2.0.3: dependencies: function-bind: 1.1.2 @@ -3187,18 +2825,6 @@ snapshots: '@types/set-cookie-parser': 2.4.10 set-cookie-parser: 3.1.0 - http-errors@2.0.1: - dependencies: - depd: 2.0.0 - inherits: 2.0.4 - setprototypeof: 1.2.0 - statuses: 2.0.2 - toidentifier: 1.0.1 - - iconv-lite@0.7.2: - dependencies: - safer-buffer: 2.1.2 - import-in-the-middle@1.15.0: dependencies: acorn: 8.16.0 @@ -3206,10 +2832,6 @@ snapshots: cjs-module-lexer: 1.4.3 module-details-from-path: 1.0.4 - inherits@2.0.4: {} - - ipaddr.js@1.9.1: {} - is-core-module@2.16.2: dependencies: hasown: 2.0.3 @@ -3218,9 +2840,7 @@ snapshots: is-node-process@1.2.0: {} - is-promise@4.0.0: {} - - joycon@3.1.1: {} + js-tokens@4.0.0: {} json-schema-traverse@1.0.0: {} @@ -3273,47 +2893,22 @@ snapshots: lightningcss-win32-arm64-msvc: 1.33.0 lightningcss-win32-x64-msvc: 1.33.0 - lilconfig@3.1.3: {} - - lines-and-columns@1.2.4: {} - - load-tsconfig@0.2.5: {} - lodash.camelcase@4.3.0: {} long@5.3.2: {} - magic-string@0.30.21: - dependencies: - '@jridgewell/sourcemap-codec': 1.5.5 - - math-intrinsics@1.1.0: {} - - media-typer@1.1.0: {} - - merge-descriptors@2.0.0: {} - - meriyah@6.1.4: {} - - mime-db@1.54.0: {} + lz-string@1.5.0: {} - mime-types@3.0.2: + magic-string@1.4.3: dependencies: - mime-db: 1.54.0 + '@jridgewell/sourcemap-codec': 1.6.0 - minimatch@10.2.5: - dependencies: - brace-expansion: 5.0.12 - - mlly@1.8.2: - dependencies: - acorn: 8.16.0 - pathe: 2.0.3 - pkg-types: 1.3.1 - ufo: 1.6.4 + meriyah@6.1.4: {} module-details-from-path@1.0.4: {} + mrmime@2.0.1: {} + ms@2.1.3: {} msw@2.14.6(@types/node@22.19.19)(typescript@5.5.4): @@ -3345,40 +2940,74 @@ snapshots: mute-stream@4.0.0: {} - mz@2.7.0: - dependencies: - any-promise: 1.3.0 - object-assign: 4.1.1 - thenify-all: 1.6.0 - nanoid@3.3.19: {} - negotiator@1.0.0: {} - - object-assign@4.1.1: {} - - object-inspect@1.13.4: {} - obug@2.2.1: {} - on-finished@2.4.1: - dependencies: - ee-first: 1.1.1 - - once@1.4.0: - dependencies: - wrappy: 1.0.2 - outvariant@1.4.3: {} - parseurl@1.3.3: {} + oxfmt@0.72.0(vite-plus@1.1.0(@opentelemetry/api@1.9.0)(@types/node@22.19.19)(@vitest/ui@5.0.3(vitest@5.0.3))(esbuild@0.28.1)(msw@2.14.6(@types/node@22.19.19)(typescript@5.5.4))(typescript@5.5.4)): + dependencies: + tinypool: 2.2.0 + optionalDependencies: + '@oxfmt/binding-android-arm-eabi': 0.72.0 + '@oxfmt/binding-android-arm64': 0.72.0 + '@oxfmt/binding-darwin-arm64': 0.72.0 + '@oxfmt/binding-darwin-x64': 0.72.0 + '@oxfmt/binding-freebsd-x64': 0.72.0 + '@oxfmt/binding-linux-arm-gnueabihf': 0.72.0 + '@oxfmt/binding-linux-arm-musleabihf': 0.72.0 + '@oxfmt/binding-linux-arm64-gnu': 0.72.0 + '@oxfmt/binding-linux-arm64-musl': 0.72.0 + '@oxfmt/binding-linux-ppc64-gnu': 0.72.0 + '@oxfmt/binding-linux-riscv64-gnu': 0.72.0 + '@oxfmt/binding-linux-riscv64-musl': 0.72.0 + '@oxfmt/binding-linux-s390x-gnu': 0.72.0 + '@oxfmt/binding-linux-x64-gnu': 0.72.0 + '@oxfmt/binding-linux-x64-musl': 0.72.0 + '@oxfmt/binding-openharmony-arm64': 0.72.0 + '@oxfmt/binding-win32-arm64-msvc': 0.72.0 + '@oxfmt/binding-win32-ia32-msvc': 0.72.0 + '@oxfmt/binding-win32-x64-msvc': 0.72.0 + vite-plus: 1.1.0(@opentelemetry/api@1.9.0)(@types/node@22.19.19)(@vitest/ui@5.0.3(vitest@5.0.3))(esbuild@0.28.1)(msw@2.14.6(@types/node@22.19.19)(typescript@5.5.4))(typescript@5.5.4) + + oxlint-tsgolint@7.0.2003: + optionalDependencies: + '@oxlint-tsgolint/darwin-arm64': 7.0.2003 + '@oxlint-tsgolint/darwin-x64': 7.0.2003 + '@oxlint-tsgolint/linux-arm64': 7.0.2003 + '@oxlint-tsgolint/linux-x64': 7.0.2003 + '@oxlint-tsgolint/win32-arm64': 7.0.2003 + '@oxlint-tsgolint/win32-x64': 7.0.2003 + + oxlint@1.87.0(oxlint-tsgolint@7.0.2003)(vite-plus@1.1.0(@opentelemetry/api@1.9.0)(@types/node@22.19.19)(@vitest/ui@5.0.3(vitest@5.0.3))(esbuild@0.28.1)(msw@2.14.6(@types/node@22.19.19)(typescript@5.5.4))(typescript@5.5.4)): + optionalDependencies: + '@oxlint/binding-android-arm-eabi': 1.87.0 + '@oxlint/binding-android-arm64': 1.87.0 + '@oxlint/binding-darwin-arm64': 1.87.0 + '@oxlint/binding-darwin-x64': 1.87.0 + '@oxlint/binding-freebsd-x64': 1.87.0 + '@oxlint/binding-linux-arm-gnueabihf': 1.87.0 + '@oxlint/binding-linux-arm-musleabihf': 1.87.0 + '@oxlint/binding-linux-arm64-gnu': 1.87.0 + '@oxlint/binding-linux-arm64-musl': 1.87.0 + '@oxlint/binding-linux-ppc64-gnu': 1.87.0 + '@oxlint/binding-linux-riscv64-gnu': 1.87.0 + '@oxlint/binding-linux-riscv64-musl': 1.87.0 + '@oxlint/binding-linux-s390x-gnu': 1.87.0 + '@oxlint/binding-linux-x64-gnu': 1.87.0 + '@oxlint/binding-linux-x64-musl': 1.87.0 + '@oxlint/binding-openharmony-arm64': 1.87.0 + '@oxlint/binding-win32-arm64-msvc': 1.87.0 + '@oxlint/binding-win32-ia32-msvc': 1.87.0 + '@oxlint/binding-win32-x64-msvc': 1.87.0 + oxlint-tsgolint: 7.0.2003 + vite-plus: 1.1.0(@opentelemetry/api@1.9.0)(@types/node@22.19.19)(@vitest/ui@5.0.3(vitest@5.0.3))(esbuild@0.28.1)(msw@2.14.6(@types/node@22.19.19)(typescript@5.5.4))(typescript@5.5.4) path-parse@1.0.7: {} path-to-regexp@6.3.0: {} - path-to-regexp@8.4.2: {} - pathe@2.0.3: {} picocolors@1.1.1: {} @@ -3387,21 +3016,7 @@ snapshots: picomatch@4.0.7: {} - pirates@4.0.7: {} - - pkg-types@1.3.1: - dependencies: - confbox: 0.1.8 - mlly: 1.8.2 - pathe: 2.0.3 - - pluralize@8.0.0: {} - - postcss-load-config@6.0.1(postcss@8.5.28): - dependencies: - lilconfig: 3.1.3 - optionalDependencies: - postcss: 8.5.28 + pngjs@7.0.0: {} postcss@8.5.28: dependencies: @@ -3409,6 +3024,12 @@ snapshots: picocolors: 1.1.1 source-map-js: 1.2.2 + pretty-format@27.5.1: + dependencies: + ansi-regex: 5.0.1 + ansi-styles: 5.2.0 + react-is: 17.0.2 + protobufjs@7.6.5: dependencies: '@protobufjs/aspromise': 1.1.2 @@ -3423,25 +3044,7 @@ snapshots: '@types/node': 22.19.19 long: 5.3.2 - proxy-addr@2.0.8: - dependencies: - forwarded: 0.2.0 - ipaddr.js: 1.9.1 - - qs@6.15.2: - dependencies: - side-channel: 1.1.0 - - range-parser@1.2.1: {} - - raw-body@3.0.2: - dependencies: - bytes: 3.1.2 - http-errors: 2.0.1 - iconv-lite: 0.7.2 - unpipe: 1.0.0 - - readdirp@4.1.2: {} + react-is@17.0.2: {} require-directory@2.1.1: {} @@ -3455,8 +3058,6 @@ snapshots: transitivePeerDependencies: - supports-color - resolve-from@5.0.0: {} - resolve@1.22.12: dependencies: es-errors: 1.3.0 @@ -3466,141 +3067,24 @@ snapshots: rettime@0.11.11: {} - rolldown@1.0.2: - dependencies: - '@oxc-project/types': 0.132.0 - '@rolldown/pluginutils': 1.0.1 - optionalDependencies: - '@rolldown/binding-android-arm64': 1.0.2 - '@rolldown/binding-darwin-arm64': 1.0.2 - '@rolldown/binding-darwin-x64': 1.0.2 - '@rolldown/binding-freebsd-x64': 1.0.2 - '@rolldown/binding-linux-arm-gnueabihf': 1.0.2 - '@rolldown/binding-linux-arm64-gnu': 1.0.2 - '@rolldown/binding-linux-arm64-musl': 1.0.2 - '@rolldown/binding-linux-ppc64-gnu': 1.0.2 - '@rolldown/binding-linux-s390x-gnu': 1.0.2 - '@rolldown/binding-linux-x64-gnu': 1.0.2 - '@rolldown/binding-linux-x64-musl': 1.0.2 - '@rolldown/binding-openharmony-arm64': 1.0.2 - '@rolldown/binding-wasm32-wasi': 1.0.2 - '@rolldown/binding-win32-arm64-msvc': 1.0.2 - '@rolldown/binding-win32-x64-msvc': 1.0.2 - - rollup@4.60.4: - dependencies: - '@types/estree': 1.0.8 - optionalDependencies: - '@rollup/rollup-android-arm-eabi': 4.60.4 - '@rollup/rollup-android-arm64': 4.60.4 - '@rollup/rollup-darwin-arm64': 4.60.4 - '@rollup/rollup-darwin-x64': 4.60.4 - '@rollup/rollup-freebsd-arm64': 4.60.4 - '@rollup/rollup-freebsd-x64': 4.60.4 - '@rollup/rollup-linux-arm-gnueabihf': 4.60.4 - '@rollup/rollup-linux-arm-musleabihf': 4.60.4 - '@rollup/rollup-linux-arm64-gnu': 4.60.4 - '@rollup/rollup-linux-arm64-musl': 4.60.4 - '@rollup/rollup-linux-loong64-gnu': 4.60.4 - '@rollup/rollup-linux-loong64-musl': 4.60.4 - '@rollup/rollup-linux-ppc64-gnu': 4.60.4 - '@rollup/rollup-linux-ppc64-musl': 4.60.4 - '@rollup/rollup-linux-riscv64-gnu': 4.60.4 - '@rollup/rollup-linux-riscv64-musl': 4.60.4 - '@rollup/rollup-linux-s390x-gnu': 4.60.4 - '@rollup/rollup-linux-x64-gnu': 4.60.4 - '@rollup/rollup-linux-x64-musl': 4.60.4 - '@rollup/rollup-openbsd-x64': 4.60.4 - '@rollup/rollup-openharmony-arm64': 4.60.4 - '@rollup/rollup-win32-arm64-msvc': 4.60.4 - '@rollup/rollup-win32-ia32-msvc': 4.60.4 - '@rollup/rollup-win32-x64-gnu': 4.60.4 - '@rollup/rollup-win32-x64-msvc': 4.60.4 - fsevents: 2.3.3 - - router@2.2.0: - dependencies: - debug: 4.4.3 - depd: 2.0.0 - is-promise: 4.0.0 - parseurl: 1.3.3 - path-to-regexp: 8.4.2 - transitivePeerDependencies: - - supports-color - - safer-buffer@2.1.2: {} - semifies@1.0.0: {} - send@1.2.1: - dependencies: - debug: 4.4.3 - encodeurl: 2.0.0 - escape-html: 1.0.3 - etag: 1.8.1 - fresh: 2.0.0 - http-errors: 2.0.1 - mime-types: 3.0.2 - ms: 2.1.3 - on-finished: 2.4.1 - range-parser: 1.2.1 - statuses: 2.0.2 - transitivePeerDependencies: - - supports-color - - serve-static@2.2.1: - dependencies: - encodeurl: 2.0.0 - escape-html: 1.0.3 - parseurl: 1.3.3 - send: 1.2.1 - transitivePeerDependencies: - - supports-color - set-cookie-parser@3.1.0: {} - setprototypeof@1.2.0: {} - shimmer@1.2.1: {} - side-channel-list@1.0.1: - dependencies: - es-errors: 1.3.0 - object-inspect: 1.13.4 - - side-channel-map@1.0.1: - dependencies: - call-bound: 1.0.4 - es-errors: 1.3.0 - get-intrinsic: 1.3.0 - object-inspect: 1.13.4 - - side-channel-weakmap@1.0.2: - dependencies: - call-bound: 1.0.4 - es-errors: 1.3.0 - get-intrinsic: 1.3.0 - object-inspect: 1.13.4 - side-channel-map: 1.0.1 + signal-exit@4.1.0: {} - side-channel@1.1.0: + sirv@3.0.2: dependencies: - es-errors: 1.3.0 - object-inspect: 1.13.4 - side-channel-list: 1.0.1 - side-channel-map: 1.0.1 - side-channel-weakmap: 1.0.2 - - siginfo@2.0.0: {} - - signal-exit@4.1.0: {} + '@polka/url': 1.0.0-next.29 + mrmime: 2.0.1 + totalist: 3.0.1 source-map-js@1.2.2: {} source-map@0.7.6: {} - stackback@0.0.2: {} - statuses@2.0.2: {} std-env@4.2.0: {} @@ -3617,46 +3101,21 @@ snapshots: dependencies: ansi-regex: 5.0.1 - sucrase@3.35.1: - dependencies: - '@jridgewell/gen-mapping': 0.3.13 - commander: 4.1.1 - lines-and-columns: 1.2.4 - mz: 2.7.0 - pirates: 4.0.7 - tinyglobby: 0.2.16 - ts-interface-checker: 0.1.13 - supports-preserve-symlinks-flag@1.0.0: {} tagged-tag@1.0.0: {} - termi-link@1.1.0: {} - - thenify-all@1.6.0: - dependencies: - thenify: 3.3.1 - - thenify@3.3.1: - dependencies: - any-promise: 1.3.0 - - tinybench@2.9.0: {} - - tinyexec@0.3.2: {} + tinybench@6.2.2: {} tinyexec@1.3.1: {} - tinyglobby@0.2.16: - dependencies: - fdir: 6.5.0(picomatch@4.0.4) - picomatch: 4.0.4 - tinyglobby@0.2.17: dependencies: fdir: 6.5.0(picomatch@4.0.7) picomatch: 4.0.7 + tinypool@2.2.0: {} + tinyrainbow@3.1.1: {} tldts-core@7.4.0: {} @@ -3665,65 +3124,20 @@ snapshots: dependencies: tldts-core: 7.4.0 - toidentifier@1.0.1: {} + totalist@3.0.1: {} tough-cookie@6.0.1: dependencies: tldts: 7.4.0 - tree-kill@1.2.2: {} - - ts-interface-checker@0.1.13: {} - - tslib@2.8.1: - optional: true - - tsup@8.5.1(postcss@8.5.28)(typescript@5.5.4): - dependencies: - bundle-require: 5.1.0(esbuild@0.27.7) - cac: 6.7.14 - chokidar: 4.0.3 - consola: 3.4.2 - debug: 4.4.3 - esbuild: 0.27.7 - fix-dts-default-cjs-exports: 1.0.1 - joycon: 3.1.1 - picocolors: 1.1.1 - postcss-load-config: 6.0.1(postcss@8.5.28) - resolve-from: 5.0.0 - rollup: 4.60.4 - source-map: 0.7.6 - sucrase: 3.35.1 - tinyexec: 0.3.2 - tinyglobby: 0.2.16 - tree-kill: 1.2.2 - optionalDependencies: - postcss: 8.5.28 - typescript: 5.5.4 - transitivePeerDependencies: - - jiti - - supports-color - - tsx - - yaml - type-fest@5.6.0: dependencies: tagged-tag: 1.0.0 - type-is@2.1.0: - dependencies: - content-type: 2.0.0 - media-typer: 1.1.0 - mime-types: 3.0.2 - typescript@5.5.4: {} - ufo@1.6.4: {} - undici-types@6.21.0: {} - unpipe@1.0.0: {} - unplugin@2.3.11: dependencies: '@jridgewell/remapping': 2.3.5 @@ -3735,54 +3149,92 @@ snapshots: uuid@11.1.1: {} - vary@1.1.2: {} - - vite@8.0.14(@types/node@22.19.19)(esbuild@0.27.7): - dependencies: - lightningcss: 1.33.0 - picomatch: 4.0.7 - postcss: 8.5.28 - rolldown: 1.0.2 - tinyglobby: 0.2.17 + vite-plus@1.1.0(@opentelemetry/api@1.9.0)(@types/node@22.19.19)(@vitest/ui@5.0.3(vitest@5.0.3))(esbuild@0.28.1)(msw@2.14.6(@types/node@22.19.19)(typescript@5.5.4))(typescript@5.5.4): + dependencies: + '@oxc-project/types': 0.153.0 + '@oxlint/plugins': 1.79.0 + '@vitest/browser': 5.0.3(@voidzero-dev/vite-plus-core@1.1.0(@types/node@22.19.19)(esbuild@0.28.1)(typescript@5.5.4))(msw@2.14.6(@types/node@22.19.19)(typescript@5.5.4))(vitest@5.0.3) + '@vitest/browser-preview': 5.0.3(@voidzero-dev/vite-plus-core@1.1.0(@types/node@22.19.19)(esbuild@0.28.1)(typescript@5.5.4))(msw@2.14.6(@types/node@22.19.19)(typescript@5.5.4))(vitest@5.0.3) + '@vitest/mocker': 5.0.3(@voidzero-dev/vite-plus-core@1.1.0(@types/node@22.19.19)(esbuild@0.28.1)(typescript@5.5.4))(msw@2.14.6(@types/node@22.19.19)(typescript@5.5.4)) + '@vitest/pretty-format': 5.0.3 + '@vitest/snapshot': 5.0.3 + '@vitest/spy': 5.0.3 + '@vitest/utils': 5.0.3 + oxfmt: 0.72.0(vite-plus@1.1.0(@opentelemetry/api@1.9.0)(@types/node@22.19.19)(@vitest/ui@5.0.3(vitest@5.0.3))(esbuild@0.28.1)(msw@2.14.6(@types/node@22.19.19)(typescript@5.5.4))(typescript@5.5.4)) + oxlint: 1.87.0(oxlint-tsgolint@7.0.2003)(vite-plus@1.1.0(@opentelemetry/api@1.9.0)(@types/node@22.19.19)(@vitest/ui@5.0.3(vitest@5.0.3))(esbuild@0.28.1)(msw@2.14.6(@types/node@22.19.19)(typescript@5.5.4))(typescript@5.5.4)) + oxlint-tsgolint: 7.0.2003 + vite: '@voidzero-dev/vite-plus-core@1.1.0(@types/node@22.19.19)(esbuild@0.28.1)(typescript@5.5.4)' + vitest: 5.0.3(@opentelemetry/api@1.9.0)(@types/node@22.19.19)(@vitest/browser-preview@5.0.3)(@vitest/ui@5.0.3)(@voidzero-dev/vite-plus-core@1.1.0(@types/node@22.19.19)(esbuild@0.28.1)(typescript@5.5.4))(msw@2.14.6(@types/node@22.19.19)(typescript@5.5.4)) optionalDependencies: - '@types/node': 22.19.19 - esbuild: 0.27.7 - fsevents: 2.3.3 + '@voidzero-dev/vite-plus-darwin-arm64': 1.1.0 + '@voidzero-dev/vite-plus-darwin-x64': 1.1.0 + '@voidzero-dev/vite-plus-linux-arm64-gnu': 1.1.0 + '@voidzero-dev/vite-plus-linux-arm64-musl': 1.1.0 + '@voidzero-dev/vite-plus-linux-x64-gnu': 1.1.0 + '@voidzero-dev/vite-plus-linux-x64-musl': 1.1.0 + '@voidzero-dev/vite-plus-win32-arm64-msvc': 1.1.0 + '@voidzero-dev/vite-plus-win32-x64-msvc': 1.1.0 + transitivePeerDependencies: + - '@arethetypeswrong/core' + - '@edge-runtime/vm' + - '@opentelemetry/api' + - '@types/node' + - '@typescript/native-preview' + - '@vitejs/devtools' + - '@vitest/coverage-istanbul' + - '@vitest/coverage-v8' + - '@vitest/ui' + - '@volar/typescript' + - '@vue/language-core' + - bufferutil + - esbuild + - happy-dom + - jiti + - jsdom + - less + - msw + - publint + - sass + - sass-embedded + - stylus + - sugarss + - svelte + - terser + - tsx + - typescript + - unplugin-unused + - unrun + - utf-8-validate + - vue-tsc + - yaml - vitest@4.1.11(@opentelemetry/api@1.9.0)(@types/node@22.19.19)(msw@2.14.6(@types/node@22.19.19)(typescript@5.5.4))(vite@8.0.14(@types/node@22.19.19)(esbuild@0.27.7)): + vitest@5.0.3(@opentelemetry/api@1.9.0)(@types/node@22.19.19)(@vitest/browser-preview@5.0.3)(@vitest/ui@5.0.3)(@voidzero-dev/vite-plus-core@1.1.0(@types/node@22.19.19)(esbuild@0.28.1)(typescript@5.5.4))(msw@2.14.6(@types/node@22.19.19)(typescript@5.5.4)): dependencies: - '@vitest/expect': 4.1.11 - '@vitest/mocker': 4.1.11(msw@2.14.6(@types/node@22.19.19)(typescript@5.5.4))(vite@8.0.14(@types/node@22.19.19)(esbuild@0.27.7)) - '@vitest/pretty-format': 4.1.11 - '@vitest/runner': 4.1.11 - '@vitest/snapshot': 4.1.11 - '@vitest/spy': 4.1.11 - '@vitest/utils': 4.1.11 + '@types/chai': 5.2.3 + '@vitest/mocker': 5.0.3(@voidzero-dev/vite-plus-core@1.1.0(@types/node@22.19.19)(esbuild@0.28.1)(typescript@5.5.4))(msw@2.14.6(@types/node@22.19.19)(typescript@5.5.4)) + chai: 6.2.2 es-module-lexer: 2.3.2 expect-type: 1.4.0 - magic-string: 0.30.21 + magic-string: 1.4.3 obug: 2.2.1 - pathe: 2.0.3 picomatch: 4.0.7 std-env: 4.2.0 - tinybench: 2.9.0 + tinybench: 6.2.2 tinyexec: 1.3.1 tinyglobby: 0.2.17 - tinyrainbow: 3.1.1 - vite: 8.0.14(@types/node@22.19.19)(esbuild@0.27.7) - why-is-node-running: 2.3.0 + vite: '@voidzero-dev/vite-plus-core@1.1.0(@types/node@22.19.19)(esbuild@0.28.1)(typescript@5.5.4)' + why-is-node-running: 3.2.1 optionalDependencies: '@opentelemetry/api': 1.9.0 '@types/node': 22.19.19 + '@vitest/browser-preview': 5.0.3(@voidzero-dev/vite-plus-core@1.1.0(@types/node@22.19.19)(esbuild@0.28.1)(typescript@5.5.4))(msw@2.14.6(@types/node@22.19.19)(typescript@5.5.4))(vitest@5.0.3) + '@vitest/ui': 5.0.3(vitest@5.0.3) transitivePeerDependencies: - msw webpack-virtual-modules@0.6.2: {} - why-is-node-running@2.3.0: - dependencies: - siginfo: 2.0.0 - stackback: 0.0.2 + why-is-node-running@3.2.1: {} wrap-ansi@7.0.0: dependencies: @@ -3790,7 +3242,7 @@ snapshots: string-width: 4.2.3 strip-ansi: 6.0.1 - wrappy@1.0.2: {} + ws@8.22.0: {} y18n@5.0.8: {} @@ -3806,6 +3258,45 @@ snapshots: y18n: 5.0.8 yargs-parser: 21.1.1 + yuku-ast@0.9.5: + dependencies: + '@yuku-toolchain/types': 0.9.5 + + yuku-codegen@0.9.5: + dependencies: + '@yuku-toolchain/types': 0.9.5 + optionalDependencies: + '@yuku-codegen/binding-android-arm64': 0.9.5 + '@yuku-codegen/binding-darwin-arm64': 0.9.5 + '@yuku-codegen/binding-darwin-x64': 0.9.5 + '@yuku-codegen/binding-freebsd-x64': 0.9.5 + '@yuku-codegen/binding-linux-arm-gnu': 0.9.5 + '@yuku-codegen/binding-linux-arm-musl': 0.9.5 + '@yuku-codegen/binding-linux-arm64-gnu': 0.9.5 + '@yuku-codegen/binding-linux-arm64-musl': 0.9.5 + '@yuku-codegen/binding-linux-x64-gnu': 0.9.5 + '@yuku-codegen/binding-linux-x64-musl': 0.9.5 + '@yuku-codegen/binding-win32-arm64': 0.9.5 + '@yuku-codegen/binding-win32-x64': 0.9.5 + + yuku-parser@0.9.5: + dependencies: + '@yuku-toolchain/types': 0.9.5 + yuku-ast: 0.9.5 + optionalDependencies: + '@yuku-parser/binding-android-arm64': 0.9.5 + '@yuku-parser/binding-darwin-arm64': 0.9.5 + '@yuku-parser/binding-darwin-x64': 0.9.5 + '@yuku-parser/binding-freebsd-x64': 0.9.5 + '@yuku-parser/binding-linux-arm-gnu': 0.9.5 + '@yuku-parser/binding-linux-arm-musl': 0.9.5 + '@yuku-parser/binding-linux-arm64-gnu': 0.9.5 + '@yuku-parser/binding-linux-arm64-musl': 0.9.5 + '@yuku-parser/binding-linux-x64-gnu': 0.9.5 + '@yuku-parser/binding-linux-x64-musl': 0.9.5 + '@yuku-parser/binding-win32-arm64': 0.9.5 + '@yuku-parser/binding-win32-x64': 0.9.5 + zod-to-json-schema@3.25.2(zod@4.4.3): dependencies: zod: 4.4.3 diff --git a/integrations/otel-js/otel-v2/tests/otel-v2.test.ts b/integrations/otel-js/otel-v2/tests/otel-v2.test.ts index e9fd88494..988188dee 100644 --- a/integrations/otel-js/otel-v2/tests/otel-v2.test.ts +++ b/integrations/otel-js/otel-v2/tests/otel-v2.test.ts @@ -1,6 +1,6 @@ /* eslint-disable @typescript-eslint/no-explicit-any */ /* eslint-disable @typescript-eslint/consistent-type-assertions */ -import { describe, it, expect, vi } from "vitest"; +import { describe, it, expect, vi } from "vite-plus/test"; import { BasicTracerProvider } from "@opentelemetry/sdk-trace-base"; import { BraintrustExporter } from "../src/otel"; diff --git a/integrations/otel-js/otel-v2/vitest.config.ts b/integrations/otel-js/otel-v2/vite.config.ts similarity index 93% rename from integrations/otel-js/otel-v2/vitest.config.ts rename to integrations/otel-js/otel-v2/vite.config.ts index 6c805c533..3d8be82c0 100644 --- a/integrations/otel-js/otel-v2/vitest.config.ts +++ b/integrations/otel-js/otel-v2/vite.config.ts @@ -1,5 +1,5 @@ import { readFileSync } from "node:fs"; -import { defineConfig } from "vitest/config"; +import { defineConfig } from "vite-plus"; import { detectOtelVersion, logOtelVersions, diff --git a/integrations/otel-js/package.json b/integrations/otel-js/package.json index a33527b00..a947e98e8 100644 --- a/integrations/otel-js/package.json +++ b/integrations/otel-js/package.json @@ -3,21 +3,19 @@ "version": "1.1.0", "description": "SDK for integrating Braintrust with OpenTelemetry", "main": "./dist/index.js", - "module": "./dist/index.mjs", + "module": "./dist/index.js", "types": "./dist/index.d.ts", "exports": { "./package.json": "./package.json", ".": { "types": "./dist/index.d.ts", - "import": "./dist/index.mjs", - "module": "./dist/index.mjs", - "require": "./dist/index.js" + "default": "./dist/index.js" } }, "scripts": { - "build": "tsup", + "build": "vp run compile", "check:typings": "tsc --noEmit && tsc --noEmit -p tsconfig.test.json", - "watch": "tsup --watch", + "watch": "vp pack --watch", "clean": "rm -r dist/*", "test": "pnpm test:v1 && pnpm test:v2", "test:v1": "cd otel-v1 && pnpm test", @@ -30,9 +28,8 @@ "@opentelemetry/context-async-hooks": "2.6.1", "@types/node": "^22.15.21", "braintrust": "workspace:*", - "tsup": "^8.5.0", "typescript": "5.5.4", - "vitest": "4.1.11" + "vite-plus": "catalog:" }, "peerDependencies": { "@opentelemetry/api": ">=1.9.0", @@ -68,5 +65,10 @@ "access": "public", "registry": "https://registry.npmjs.org/", "provenance": true - } + }, + "type": "module", + "engines": { + "node": ">=22.18.0" + }, + "sideEffects": false } diff --git a/integrations/otel-js/run-tests.sh b/integrations/otel-js/run-tests.sh index c3a7b4d0f..59b97c916 100644 --- a/integrations/otel-js/run-tests.sh +++ b/integrations/otel-js/run-tests.sh @@ -2,4 +2,4 @@ set -e pnpm install --ignore-workspace -pnpm exec vitest run +pnpm exec vp test run diff --git a/integrations/otel-js/smoke/scenarios/otel-v1/Makefile b/integrations/otel-js/smoke/scenarios/otel-v1/Makefile index 7654613f1..f5c7f8159 100644 --- a/integrations/otel-js/smoke/scenarios/otel-v1/Makefile +++ b/integrations/otel-js/smoke/scenarios/otel-v1/Makefile @@ -13,7 +13,7 @@ setup: echo "==> Using BRAINTRUST_TAR: $(BRAINTRUST_TAR)"; \ else \ echo "==> Building SDK"; \ - cd ../../../.. && pnpm exec turbo build --filter=braintrust && mkdir -p artifacts && pnpm pack --pack-destination artifacts; \ + cd ../../../.. && pnpm --filter=braintrust run build && mkdir -p artifacts && pnpm pack --pack-destination artifacts; \ \ for f in artifacts/braintrust-*.tgz; do \ if [ "$$(basename $$f)" != "braintrust-latest.tgz" ] && \ @@ -35,7 +35,7 @@ setup: echo "==> Using BRAINTRUST_OTEL_TAR: $(BRAINTRUST_OTEL_TAR)"; \ else \ echo "==> Building @braintrust/otel package"; \ - cd ../../../../../integrations/otel-js && pnpm exec turbo build --filter=@braintrust/otel && pnpm pack --pack-destination ../../js/artifacts; \ + cd ../../../../../integrations/otel-js && pnpm --filter=@braintrust/otel run build && pnpm pack --pack-destination ../../js/artifacts; \ \ for f in ../../../js/artifacts/braintrust-otel-*.tgz; do \ if [ "$$(basename $$f)" != "braintrust-otel-latest.tgz" ]; then \ diff --git a/integrations/otel-js/smoke/scenarios/otel-v1/tests/basic.test.ts b/integrations/otel-js/smoke/scenarios/otel-v1/tests/basic.test.ts index 1cdb05f89..d7a307a04 100644 --- a/integrations/otel-js/smoke/scenarios/otel-v1/tests/basic.test.ts +++ b/integrations/otel-js/smoke/scenarios/otel-v1/tests/basic.test.ts @@ -8,7 +8,7 @@ import { displayTestResults, hasFailures, type TestResult, -} from "../../../../../../js/smoke/shared/dist/index.mjs"; +} from "../../../../../../js/smoke/shared/dist/index.js"; async function main() { const results: TestResult[] = []; diff --git a/integrations/otel-js/smoke/scenarios/otel-v1/tests/filtering.test.ts b/integrations/otel-js/smoke/scenarios/otel-v1/tests/filtering.test.ts index e460a81a7..bd0362f91 100644 --- a/integrations/otel-js/smoke/scenarios/otel-v1/tests/filtering.test.ts +++ b/integrations/otel-js/smoke/scenarios/otel-v1/tests/filtering.test.ts @@ -8,7 +8,7 @@ import { displayTestResults, hasFailures, type TestResult, -} from "../../../../../../js/smoke/shared/dist/index.mjs"; +} from "../../../../../../js/smoke/shared/dist/index.js"; type OtelPayload = { resourceSpans?: Array<{ diff --git a/integrations/otel-js/src/context.ts b/integrations/otel-js/src/context.ts index 6db697c67..b61416e41 100644 --- a/integrations/otel-js/src/context.ts +++ b/integrations/otel-js/src/context.ts @@ -1,11 +1,10 @@ import { ContextManager, - BRAINTRUST_CURRENT_SPAN_STORE, - _internalIso as iso, type ContextParentSpanIds, type CurrentSpanStore, type Span, } from "braintrust"; +import { AsyncLocalStorage } from "node:async_hooks"; import { trace as otelTrace, context as otelContext } from "@opentelemetry/api"; import { getOtelParentFromSpan } from "./otel"; @@ -91,29 +90,11 @@ export class OtelContextManager extends ContextManager { return (otelContext as any)._getContextManager?.()._asyncLocalStorage; } - constructor() { - super(); - // Expose whichever ALS is in use via BRAINTRUST_CURRENT_SPAN_STORE so that - // TracingChannel's bindStore can propagate span context. We prefer OTEL's own - // ALS (AsyncLocalStorageContextManager._asyncLocalStorage) so that spans - // stored by runStores are visible to OTEL's context APIs. If the active OTEL - // context manager doesn't expose an ALS (e.g. AsyncHooksContextManager), we - // fall back to our own IsoAsyncLocalStorage and behave like the default - // BraintrustContextManager for TracingChannel binding. - // - // A lazy getter is required because the global OTEL context manager may not be - // registered until after this instance is constructed. - const self = this; - Object.defineProperty(this, BRAINTRUST_CURRENT_SPAN_STORE, { - get(): CurrentSpanStore { - const otelAls = self._getOtelAls(); - if (otelAls) return otelAls; - if (!self._ownAls) self._ownAls = iso.newAsyncLocalStorage(); - return self._ownAls; - }, - configurable: true, - enumerable: false, - }); + getCurrentSpanStore(): CurrentSpanStore { + const otelAls = this._getOtelAls(); + if (otelAls) return otelAls; + if (!this._ownAls) this._ownAls = new AsyncLocalStorage(); + return this._ownAls; } wrapSpanForStore(span: Span): unknown { diff --git a/integrations/otel-js/src/exporter.test.ts b/integrations/otel-js/src/exporter.test.ts index 94168963a..0a0ab6d33 100644 --- a/integrations/otel-js/src/exporter.test.ts +++ b/integrations/otel-js/src/exporter.test.ts @@ -7,7 +7,7 @@ import { beforeEach, afterEach, vi, -} from "vitest"; +} from "vite-plus/test"; import { createServer, type Server } from "node:http"; import type { AddressInfo } from "node:net"; import { @@ -22,7 +22,7 @@ import { } from "@opentelemetry/sdk-trace-base"; import { BraintrustSpanProcessor } from "./otel"; import { createTracerProvider } from "../tests/utils"; -import { _exportsForTestingOnly } from "braintrust"; +import { _exportsForTestingOnly } from "../../../js/src/logger/testing"; type OtlpTraceRequest = { resourceSpans?: Array<{ diff --git a/integrations/otel-js/src/id-gen.test.ts b/integrations/otel-js/src/id-gen.test.ts index b38e0afbc..ecad4fd19 100644 --- a/integrations/otel-js/src/id-gen.test.ts +++ b/integrations/otel-js/src/id-gen.test.ts @@ -1,7 +1,10 @@ -import { expect, test, describe, beforeEach, afterEach } from "vitest"; -import { UUIDGenerator, getIdGenerator } from "braintrust"; -import { OTELIDGenerator } from "./otel"; -import { setupOtelCompat, resetOtelCompat } from "."; +import { expect, test, describe, beforeEach, afterEach } from "vite-plus/test"; +import { + OTELIDGenerator, + UUIDGenerator, + getIdGenerator, +} from "../../../js/src/id-gen"; +import { setupOtelCompat, resetOtelCompat } from "./"; describe("ID Generation", () => { beforeEach(() => { diff --git a/integrations/otel-js/src/index.ts b/integrations/otel-js/src/index.ts index 9555cd32c..a96c89551 100644 --- a/integrations/otel-js/src/index.ts +++ b/integrations/otel-js/src/index.ts @@ -1,48 +1,20 @@ import { OtelContextManager } from "./context"; -import { _internalGetGlobalState } from "braintrust"; +import { configureContextManager } from "braintrust"; export { - contextFromSpanExport, + contextFromSpan, addSpanParentToBaggage, addParentToBaggage, parentFromHeaders, isRootSpan, } from "./otel"; -import { OTELIDGenerator } from "./otel"; -import { SpanComponentsV4 } from "braintrust/util"; - export { BraintrustSpanProcessor, BraintrustExporter } from "./otel"; -function resetBraintrustCompatCaches() { - const state = _internalGetGlobalState(); - if (!state) { - return; - } - - // Node/browser package initialization can cache native context and ID state - // before setupOtelCompat() runs. Reset both so subsequent lookups honor the - // compat-mode globals we are about to install. - (state as unknown as { _contextManager: unknown })._contextManager = null; - (state as unknown as { _idGenerator: unknown })._idGenerator = null; -} - -export const setupOtelCompat = () => { - // eslint-disable-next-line @typescript-eslint/no-explicit-any, @typescript-eslint/consistent-type-assertions - (globalThis as any).BRAINTRUST_CONTEXT_MANAGER = OtelContextManager; - // eslint-disable-next-line @typescript-eslint/no-explicit-any, @typescript-eslint/consistent-type-assertions - (globalThis as any).BRAINTRUST_ID_GENERATOR = OTELIDGenerator; - // eslint-disable-next-line @typescript-eslint/no-explicit-any, @typescript-eslint/consistent-type-assertions - (globalThis as any).BRAINTRUST_SPAN_COMPONENT = SpanComponentsV4; - resetBraintrustCompatCaches(); +export const setupOtelCompat = (): void => { + configureContextManager(OtelContextManager); }; -export const resetOtelCompat = () => { - // eslint-disable-next-line @typescript-eslint/no-explicit-any, @typescript-eslint/consistent-type-assertions - (globalThis as any).BRAINTRUST_CONTEXT_MANAGER = undefined; - // eslint-disable-next-line @typescript-eslint/no-explicit-any, @typescript-eslint/consistent-type-assertions - (globalThis as any).BRAINTRUST_ID_GENERATOR = undefined; - // eslint-disable-next-line @typescript-eslint/no-explicit-any, @typescript-eslint/consistent-type-assertions - (globalThis as any).BRAINTRUST_SPAN_COMPONENT = undefined; - resetBraintrustCompatCaches(); +export const resetOtelCompat = (): void => { + configureContextManager(undefined); }; diff --git a/integrations/otel-js/src/otel-compat.test.ts b/integrations/otel-js/src/otel-compat.test.ts index 6564da531..70161e73c 100644 --- a/integrations/otel-js/src/otel-compat.test.ts +++ b/integrations/otel-js/src/otel-compat.test.ts @@ -5,15 +5,14 @@ * when created in mixed contexts. */ -import { beforeEach, afterEach, describe, expect, test } from "vitest"; +import { beforeEach, afterEach, describe, expect, test } from "vite-plus/test"; +import { initLogger, currentSpan, Eval } from "braintrust"; +import { _exportsForTestingOnly } from "../../../js/src/logger/testing"; import { - initLogger, - currentSpan, + _internalStartSpan, + _internalWithParent, getContextManager, - BRAINTRUST_CURRENT_SPAN_STORE, - _exportsForTestingOnly, - runEvaluator, -} from "braintrust"; +} from "../../../js/src/logger/core"; import { BasicTracerProvider, InMemorySpanExporter, @@ -264,15 +263,13 @@ describe("OTEL compatibility mode", () => { expect(cm.getCurrentSpan).toBeDefined(); }); - test("uses OtelContextManager when OTEL enabled", async () => { + test("uses OtelContextManager when OTEL enabled", () => { // Test that when OTEL is available and env var is set, we get OtelContextManager // Note: In test environment, OTEL packages may not be available even though // we checked OTEL_AVAILABLE. If the require fails in getContextManager, // it falls back to BraintrustContextManager, which is correct behavior. - // Clear module cache and re-import to get fresh context manager - const loggerModule = await import("braintrust?t=" + Date.now()); - const cm = loggerModule.getContextManager(); + const cm = getContextManager(); // If OTEL is truly available, we should get OtelContextManager // Otherwise, fallback to BraintrustContextManager is acceptable @@ -371,7 +368,7 @@ describe("OTEL compatibility mode", () => { expect(trace2Id).not.toBe(trace3Id); }); - test("OTEL spans in experiment Eval() inherit experiment_id parent", async () => { + test("OTEL spans in Eval() inherit Braintrust parent context", async () => { const { tracer, exporter, processor } = setupOtelFixture( "experiment-eval-test", ); @@ -379,12 +376,11 @@ describe("OTEL compatibility mode", () => { // Capture BT span info from inside the task const btSpanInfo: Array<{ traceId: string; spanId: string }> = []; - // Use runEvaluator with null experiment to avoid API calls - const result = await runEvaluator( - null, + // Use the public Eval API without making Braintrust API calls. + const result = await Eval( + "test-eval-project", { - projectName: "test-eval-project", - evalName: "otel-eval-test", + experimentName: "otel-eval-test", data: [{ input: 1 }, { input: 2 }], task: async (input: number) => { // Capture the current BT span info @@ -408,9 +404,10 @@ describe("OTEL compatibility mode", () => { }, scores: [], }, - new NoopProgressReporter(), - [], - undefined, + { + noSendLogs: true, + progress: new NoopProgressReporter(), + }, ); // Verify we captured BT span info @@ -652,7 +649,7 @@ describe("OTEL compatibility mode", () => { parentSpan.end(); - const childSpan = logger.startSpan({ + const childSpan = _internalStartSpan({ name: "child-span-v4", parent: exported, }); @@ -678,11 +675,9 @@ describe("OTEL compatibility mode", () => { parentSpan.end(); // Use withParent helper with logger.startSpan to properly handle the exported parent - const { withParent } = await import("braintrust"); - - withParent(exported, () => { + _internalWithParent(exported, () => { // Pass parent explicitly in addition to withParent context - const childSpan = logger.startSpan({ + const childSpan = _internalStartSpan({ name: "child-span-v4-with-parent", parent: exported, }); @@ -720,12 +715,11 @@ describe("OtelContextManager TracingChannel integration", () => { resetOtelCompat(); }); - test("OtelContextManager exposes OTEL ALS via BRAINTRUST_CURRENT_SPAN_STORE", () => { + test("OtelContextManager exposes the OTEL ALS", () => { const cm = getContextManager(); expect(cm.constructor.name).toBe("OtelContextManager"); - // eslint-disable-next-line @typescript-eslint/no-explicit-any - const store = (cm as any)[BRAINTRUST_CURRENT_SPAN_STORE]; + const store = cm.getCurrentSpanStore()!; expect(store).toBeDefined(); expect(typeof store.run).toBe("function"); expect(typeof store.getStore).toBe("function"); @@ -756,8 +750,7 @@ describe("OtelContextManager TracingChannel integration", () => { test("store.run() with wrapSpanForStore output propagates span to currentSpan()", () => { const cm = getContextManager(); - // eslint-disable-next-line @typescript-eslint/no-explicit-any - const store = (cm as any)[BRAINTRUST_CURRENT_SPAN_STORE]; + const store = cm.getCurrentSpanStore()!; expect(store).toBeDefined(); const mockSpan = { @@ -778,8 +771,7 @@ describe("OtelContextManager TracingChannel integration", () => { test("nested store.run() calls maintain correct span chain", () => { const cm = getContextManager(); - // eslint-disable-next-line @typescript-eslint/no-explicit-any - const store = (cm as any)[BRAINTRUST_CURRENT_SPAN_STORE]; + const store = cm.getCurrentSpanStore()!; const parentSpan = { spanId: "1111111111111111", @@ -863,10 +855,9 @@ describe("OtelContextManager fallback ALS (AsyncHooksContextManager)", () => { resetOtelCompat(); }); - test("exposes fallback ALS via BRAINTRUST_CURRENT_SPAN_STORE when OTEL ALS is unavailable", () => { + test("exposes fallback ALS when OTEL ALS is unavailable", () => { const cm = getContextManager(); - // eslint-disable-next-line @typescript-eslint/no-explicit-any - const store = (cm as any)[BRAINTRUST_CURRENT_SPAN_STORE]; + const store = cm.getCurrentSpanStore()!; expect(store).toBeDefined(); expect(typeof store.run).toBe("function"); expect(typeof store.getStore).toBe("function"); @@ -889,8 +880,7 @@ describe("OtelContextManager fallback ALS (AsyncHooksContextManager)", () => { test("store.run() with span in fallback mode makes getCurrentSpan() work", () => { const cm = getContextManager(); - // eslint-disable-next-line @typescript-eslint/no-explicit-any - const store = (cm as any)[BRAINTRUST_CURRENT_SPAN_STORE]; + const store = cm.getCurrentSpanStore()!; const mockSpan = { spanId: "fedcba0987654321", @@ -938,29 +928,16 @@ describe("Distributed Tracing (BT → OTEL)", () => { resetOtelCompat(); }); - test("otelContextFromSpanExport parses BT span and creates OTEL context", async () => { - const { contextFromSpanExport: otelContextFromSpanExport } = - await import("./"); - const { SpanComponentsV4 } = await import("braintrust/util"); - const { SpanObjectTypeV3 } = await import("braintrust/util"); - - // Create a sample span export string + test("contextFromSpan creates an OTEL context", async () => { + const { contextFromSpan } = await import("./"); const rootSpanId = "a1b2c3d4e5f6789012345678abcdef01"; // 32 hex chars (16 bytes) const spanId = "a1b2c3d4e5f67890"; // 16 hex chars (8 bytes) - const objectId = "proj-123"; - - const components = new SpanComponentsV4({ - object_type: SpanObjectTypeV3.PROJECT_LOGS, - object_id: objectId, - row_id: "row-123", - span_id: spanId, - root_span_id: rootSpanId, - propagated_event: undefined, - }); - - const exportStr = components.toStr(); - - const ctx = otelContextFromSpanExport(exportStr); + const ctx = contextFromSpan({ + inject: () => ({ + traceparent: `00-${rootSpanId}-${spanId}-01`, + baggage: "braintrust.parent=project_id%3Aproj-123", + }), + } as any); // Verify that a valid context was created expect(ctx).toBeDefined(); @@ -985,8 +962,7 @@ describe("Distributed Tracing (BT → OTEL)", () => { test("BT span in Service A can be parent of OTEL span in Service B", async () => { const { tracer, exporter, processor } = setupOtelFixture("service-a-project"); - const { contextFromSpanExport: otelContextFromSpanExport } = - await import("./"); + const { contextFromSpan } = await import("./"); const projectName = "service-a-project"; const logger = initLogger({ projectName }); @@ -994,25 +970,23 @@ describe("Distributed Tracing (BT → OTEL)", () => { // ===== Service A: Create BT span and export ===== let serviceATraceId: string | undefined; let serviceASpanId: string | undefined; - let exportedContext: string | undefined; + let sourceSpan: import("braintrust").Span | undefined; await logger.traced( async (serviceASpan) => { serviceATraceId = serviceASpan.rootSpanId; serviceASpanId = serviceASpan.spanId; - - // Export context for sending to Service B (e.g., via HTTP header) - exportedContext = await serviceASpan.export(); + sourceSpan = serviceASpan; }, { name: "service_a_span" }, ); - expect(exportedContext).toBeDefined(); + expect(sourceSpan).toBeDefined(); expect(serviceATraceId).toBeDefined(); expect(serviceASpanId).toBeDefined(); // ===== Service B: Import context and create OTEL child span ===== - const ctx = otelContextFromSpanExport(exportedContext!); + const ctx = contextFromSpan(sourceSpan!); // Use context.with() to run code in the imported context await context.with(ctx, async () => { diff --git a/integrations/otel-js/src/otel.test.ts b/integrations/otel-js/src/otel.test.ts index 94dc5e088..08efa0586 100644 --- a/integrations/otel-js/src/otel.test.ts +++ b/integrations/otel-js/src/otel.test.ts @@ -1,7 +1,15 @@ /* eslint-disable @typescript-eslint/no-explicit-any */ /* eslint-disable @typescript-eslint/consistent-type-assertions */ import { readFileSync } from "node:fs"; -import { describe, it, expect, beforeEach, afterEach, vi, test } from "vitest"; +import { + describe, + it, + expect, + beforeEach, + afterEach, + vi, + test, +} from "vite-plus/test"; import { mkdtemp, rm, writeFile } from "node:fs/promises"; import { tmpdir } from "node:os"; import path from "node:path"; @@ -26,14 +34,15 @@ import { parentFromHeaders, isRootSpan, } from "./otel"; -import { _exportsForTestingOnly, initLogger } from "braintrust"; +import { initLogger } from "braintrust"; +import { SpanComponentsV3, SpanComponentsV4 } from "../../../js/util"; +import { _exportsForTestingOnly } from "../../../js/src/logger/testing"; import { base64ToUint8Array, getExportVersion, createTracerProvider, } from "../tests/utils"; -import { SpanComponentsV3, SpanComponentsV4 } from "braintrust/util"; -import { setupOtelCompat, resetOtelCompat } from "."; +import { setupOtelCompat, resetOtelCompat } from "./"; const packageJson = JSON.parse( readFileSync(path.join(__dirname, "../package.json"), "utf8"), @@ -61,6 +70,13 @@ async function withEmptyBraintrustEnvFile( return withBraintrustEnvFile("", fn); } +function restoreEnvironment(snapshot: NodeJS.ProcessEnv): void { + for (const name of Object.keys(process.env)) { + if (!(name in snapshot)) delete process.env[name]; + } + Object.assign(process.env, snapshot); +} + describe("AISpanProcessor", () => { let memoryExporter: InMemorySpanExporter; let provider: BasicTracerProvider; @@ -605,7 +621,7 @@ describe("BraintrustSpanProcessor", () => { }); afterEach(() => { - process.env = originalEnv; + restoreEnvironment(originalEnv); vi.restoreAllMocks(); _exportsForTestingOnly.clearTestBackgroundLogger(); _exportsForTestingOnly.simulateLogoutForTests(); @@ -1008,7 +1024,7 @@ describe("BraintrustExporter", () => { }); afterEach(() => { - process.env = originalEnv; + restoreEnvironment(originalEnv); vi.restoreAllMocks(); _exportsForTestingOnly.clearTestBackgroundLogger(); _exportsForTestingOnly.simulateLogoutForTests(); @@ -1392,211 +1408,58 @@ describe("otel namespace helpers", () => { }); describe("parentFromHeaders", () => { - describe("valid inputs", () => { - it("should extract parent from headers with valid traceparent and braintrust.parent baggage", () => { - const headers = { - traceparent: - "00-12345678901234567890123456789012-1234567890123456-01", - baggage: "braintrust.parent=project_name:test", - }; - - const parent = parentFromHeaders(headers); - expect(parent).toBeDefined(); - // Parent string is base64-encoded SpanComponentsV4 - expect(typeof parent).toBe("string"); - expect(parent!.length).toBeGreaterThan(0); - }); - - it("should extract parent with project_id", () => { - const headers = { - traceparent: - "00-abcdef12345678901234567890123456-fedcba9876543210-01", - baggage: "braintrust.parent=project_id:my-project-id", - }; - - const parent = parentFromHeaders(headers); - expect(parent).toBeDefined(); - expect(typeof parent).toBe("string"); - expect(parent!.length).toBeGreaterThan(0); - }); - - it("should extract parent with experiment_id", () => { - const headers = { - traceparent: - "00-11111111111111111111111111111111-2222222222222222-01", - baggage: "braintrust.parent=experiment_id:my-experiment-id", - }; - - const parent = parentFromHeaders(headers); - expect(parent).toBeDefined(); - expect(typeof parent).toBe("string"); - expect(parent!.length).toBeGreaterThan(0); - }); + it("returns the opaque W3C context", () => { + const headers = { + traceparent: "00-12345678901234567890123456789012-1234567890123456-01", + tracestate: "vendor=value", + baggage: "braintrust.parent=project_name%3Atest", + }; + expect(parentFromHeaders(headers)).toEqual(headers); }); - describe("invalid inputs", () => { - it("should return undefined when traceparent is missing", () => { - const consoleSpy = vi - .spyOn(console, "error") - .mockImplementation(() => {}); - const headers = { - baggage: "braintrust.parent=project_name:test", - }; - - const parent = parentFromHeaders(headers); - expect(parent).toBeUndefined(); - expect(consoleSpy).toHaveBeenCalledWith( - "parentFromHeaders: No valid span context in headers", - ); - - consoleSpy.mockRestore(); - }); - - it("should return undefined when baggage is missing", () => { - const consoleSpy = vi - .spyOn(console, "warn") - .mockImplementation(() => {}); - const headers = { - traceparent: - "00-12345678901234567890123456789012-1234567890123456-01", - }; - - const parent = parentFromHeaders(headers); - expect(parent).toBeUndefined(); - expect(consoleSpy).toHaveBeenCalled(); - expect(consoleSpy.mock.calls[0][0]).toContain( - "braintrust.parent not found", - ); - - consoleSpy.mockRestore(); - }); - - it("should return undefined when braintrust.parent is missing from baggage", () => { - const consoleSpy = vi - .spyOn(console, "warn") - .mockImplementation(() => {}); - const headers = { - traceparent: - "00-12345678901234567890123456789012-1234567890123456-01", - baggage: "foo=bar,baz=qux", - }; - - const parent = parentFromHeaders(headers); - expect(parent).toBeUndefined(); - expect(consoleSpy).toHaveBeenCalled(); - expect(consoleSpy.mock.calls[0][0]).toContain( - "braintrust.parent not found", - ); - - consoleSpy.mockRestore(); - }); - - it("should reject oversized baggage before propagation", () => { - const extractSpy = vi.spyOn(propagation, "extract"); - const headers = { - traceparent: - "00-12345678901234567890123456789012-1234567890123456-01", - baggage: `braintrust.parent=project_name:test,extra=${"x".repeat(8192)}`, - }; - - expect(parentFromHeaders(headers)).toBeUndefined(); - expect(extractSpy).not.toHaveBeenCalled(); - - extractSpy.mockRestore(); - }); + it("keeps a valid trace context without Braintrust baggage", () => { + const headers = { + traceparent: "00-12345678901234567890123456789012-1234567890123456-01", + }; + expect(parentFromHeaders(headers)).toEqual(headers); + }); - it("should reject baggage with too many members before propagation", () => { + it.each([ + `braintrust.parent=project_name%3Atest,extra=${"x".repeat(8192)}`, + Array.from({ length: 181 }, (_, index) => `k${index}=v`).join(","), + `braintrust.parent=project_name%3Atest,extra=${"é".repeat(4096)}`, + ])( + "keeps oversized baggage opaque without invoking the OTEL parser", + (baggage) => { const extractSpy = vi.spyOn(propagation, "extract"); - const headers = { - traceparent: - "00-12345678901234567890123456789012-1234567890123456-01", - baggage: Array.from( - { length: 181 }, - (_, index) => `k${index}=v`, - ).join(","), - }; - - expect(parentFromHeaders(headers)).toBeUndefined(); - expect(extractSpy).not.toHaveBeenCalled(); - - extractSpy.mockRestore(); - }); - - it("should return undefined when traceparent format is invalid", () => { - const consoleSpy = vi - .spyOn(console, "error") - .mockImplementation(() => {}); - const headers = { - traceparent: "invalid-traceparent", - baggage: "braintrust.parent=project_name:test", - }; - - const parent = parentFromHeaders(headers); - expect(parent).toBeUndefined(); - expect(consoleSpy).toHaveBeenCalledWith( - "parentFromHeaders: No valid span context in headers", - ); - - consoleSpy.mockRestore(); - }); - - it("should return undefined when trace_id is all zeros", () => { - const consoleSpy = vi - .spyOn(console, "error") - .mockImplementation(() => {}); - const headers = { - traceparent: - "00-00000000000000000000000000000000-1234567890123456-01", - baggage: "braintrust.parent=project_name:test", - }; - - const parent = parentFromHeaders(headers); - expect(parent).toBeUndefined(); - // OTEL's extract() validates and rejects invalid trace_id - expect(consoleSpy).toHaveBeenCalledWith( - "parentFromHeaders: No valid span context in headers", - ); - - consoleSpy.mockRestore(); - }); - - it("should return undefined when span_id is all zeros", () => { - const consoleSpy = vi - .spyOn(console, "error") - .mockImplementation(() => {}); - const headers = { - traceparent: - "00-12345678901234567890123456789012-0000000000000000-01", - baggage: "braintrust.parent=project_name:test", - }; - - const parent = parentFromHeaders(headers); - expect(parent).toBeUndefined(); - // OTEL's extract() validates and rejects invalid span_id - expect(consoleSpy).toHaveBeenCalledWith( - "parentFromHeaders: No valid span context in headers", - ); - - consoleSpy.mockRestore(); - }); - - it("should return undefined when braintrust.parent format is invalid", () => { - const consoleSpy = vi - .spyOn(console, "error") - .mockImplementation(() => {}); - const headers = { - traceparent: - "00-12345678901234567890123456789012-1234567890123456-01", - baggage: "braintrust.parent=invalid", - }; - - const parent = parentFromHeaders(headers); - expect(parent).toBeUndefined(); - // Should reach our validation if span context is valid, otherwise OTEL rejects it - expect(consoleSpy).toHaveBeenCalled(); + try { + const parent = parentFromHeaders({ + traceparent: + "00-12345678901234567890123456789012-1234567890123456-01", + baggage, + }); + expect(parent?.traceparent).toBeDefined(); + expect(parent?.baggage).toBe(baggage); + expect(extractSpy).not.toHaveBeenCalled(); + } finally { + extractSpy.mockRestore(); + } + }, + ); - consoleSpy.mockRestore(); - }); + it.each([ + {}, + { traceparent: "invalid-traceparent" }, + { + traceparent: "00-00000000000000000000000000000000-1234567890123456-01", + }, + { + traceparent: "00-12345678901234567890123456789012-0000000000000000-01", + }, + ])("returns undefined for an invalid traceparent", (headers) => { + expect( + parentFromHeaders(headers as Record), + ).toBeUndefined(); }); }); }); @@ -1766,7 +1629,9 @@ describe("Otel Compat tests Integration", () => { expect(uuidSpan.spanId).toMatch(uuidRegex); uuidSpan.end(); - // Switch to OTEL compat (hex, wins over legacy). + // Switch to the default hex ID mode. OTEL compat only changes context + // management now; it does not override the core ID generator. + delete process.env.BRAINTRUST_LEGACY_IDS; setupOtelCompat(); _exportsForTestingOnly.resetIdGenStateForTests(); @@ -1972,26 +1837,6 @@ describe("export() format selection based on if otel is initialized", () => { expect(v4Parsed.data.object_type).toBeDefined(); }); - test("exported V4 span can be used as parent", async () => { - const testLogger = initLogger({ - projectName: "test-v4-parent", - apiKey: "test-key", - }); - - const parentSpan = testLogger.startSpan({ name: "parent-span-v4" }); - const exported = await parentSpan.export(); - parentSpan.end(); - - // Should be able to use V4 exported string as parent - const childSpan = testLogger.startSpan({ - name: "child-span-v4", - parent: exported, - }); - - expect(childSpan.rootSpanId).toBe(parentSpan.rootSpanId); - childSpan.end(); - }); - test("V4 format uses hex IDs (not UUIDs) when otel is initialized", async () => { _exportsForTestingOnly.resetIdGenStateForTests(); diff --git a/integrations/otel-js/src/otel.ts b/integrations/otel-js/src/otel.ts index fa1b4a0fe..ce431834d 100644 --- a/integrations/otel-js/src/otel.ts +++ b/integrations/otel-js/src/otel.ts @@ -1,4 +1,4 @@ -import { SpanComponentsV4, SpanObjectTypeV3 } from "braintrust/util"; +import { registerOtelFlush } from "braintrust/instrumentation"; import { customizeOtelSpans, isCustomizedOtelSpan, @@ -6,14 +6,19 @@ import { } from "./span-customizer"; import { OTLPTraceExporter } from "@opentelemetry/exporter-trace-otlp-http"; +import { + CompositePropagator, + W3CBaggagePropagator, + W3CTraceContextPropagator, +} from "@opentelemetry/core"; import { context, Context, diag, trace, - TraceFlags, propagation, Span, + type TextMapGetter, } from "@opentelemetry/api"; import { SpanProcessor, @@ -23,10 +28,9 @@ import { type SpanExporter, } from "@opentelemetry/sdk-trace-base"; import { - IDGenerator, - _internalIso, currentSpan, - registerOtelFlush, + extractTraceContextFromHeaders, + type PropagationContext, type Span as BraintrustSpan, } from "braintrust"; @@ -50,9 +54,13 @@ const SYSTEM_ATTRIBUTE_NAMES = new Set([ "braintrust.context_json", ]); -const MAX_BAGGAGE_LENGTH = 8192; -const MAX_BAGGAGE_MEMBERS = 180; -const utf8Encoder = new TextEncoder(); +const braintrustW3CPropagator = new CompositePropagator({ + propagators: [new W3CTraceContextPropagator(), new W3CBaggagePropagator()], +}); +const traceContextGetter: TextMapGetter> = { + keys: Object.keys, + get: (carrier, key) => carrier[key], +}; /** * Custom filter function type for span filtering. @@ -286,45 +294,100 @@ function withSpanOriginAttributes( }); } +const BRAINTRUST_ENV_SEARCH_PARENT_LIMIT = 64; + +function getEnv(name: string): string | undefined { + if (typeof process === "undefined") return undefined; + const value = process.env[name]; + if (value?.trim()) return value; + if ( + name !== "BRAINTRUST_API_KEY" && + name !== "BRAINTRUST_ENVIRONMENT_TYPE" && + name !== "BRAINTRUST_ENVIRONMENT_NAME" + ) { + return value; + } + + if (typeof process.loadEnvFile !== "function") return undefined; + + let dir: string; + try { + dir = process.cwd(); + } catch { + return undefined; + } + + for (let depth = 0; depth <= BRAINTRUST_ENV_SEARCH_PARENT_LIMIT; depth++) { + try { + const separator = dir.includes("\\") ? "\\" : "/"; + process.loadEnvFile( + `${dir}${dir.endsWith(separator) ? "" : separator}.env.braintrust`, + ); + const fileValue = process.env[name]; + return fileValue?.trim() ? fileValue : undefined; + } catch (error) { + if ( + typeof error !== "object" || + error === null || + !("code" in error) || + error.code !== "ENOENT" + ) { + return undefined; + } + } + + const trimmed = dir.replace(/[\\/]+$/, ""); + const separatorIndex = Math.max( + trimmed.lastIndexOf("/"), + trimmed.lastIndexOf("\\"), + ); + const parent = + separatorIndex < 0 + ? dir + : separatorIndex === 0 + ? trimmed.slice(0, 1) + : trimmed.slice(0, separatorIndex); + if (parent === dir) break; + dir = parent; + } + return undefined; +} + function detectEnvironment(explicit?: { type?: string; name?: string; }): { type?: string; name?: string } | undefined { if (explicit) return explicit; - const envType = _internalIso.getEnv("BRAINTRUST_ENVIRONMENT_TYPE"); - const envName = _internalIso.getEnv("BRAINTRUST_ENVIRONMENT_NAME"); + const envType = getEnv("BRAINTRUST_ENVIRONMENT_TYPE"); + const envName = getEnv("BRAINTRUST_ENVIRONMENT_NAME"); if (envType || envName) { return { ...(envType ? { type: envType } : {}), ...(envName ? { name: envName } : {}), }; } - if (_internalIso.getEnv("GITHUB_ACTIONS")) - return { type: "ci", name: "github_actions" }; - if (_internalIso.getEnv("GITLAB_CI")) - return { type: "ci", name: "gitlab_ci" }; - if (_internalIso.getEnv("CIRCLECI")) return { type: "ci", name: "circleci" }; - if (_internalIso.getEnv("BUILDKITE")) - return { type: "ci", name: "buildkite" }; - if (_internalIso.getEnv("CI")) return { type: "ci", name: "ci" }; - if (_internalIso.getEnv("VERCEL")) return { type: "server", name: "vercel" }; - if (_internalIso.getEnv("NETLIFY")) - return { type: "server", name: "netlify" }; - const awsExecutionEnv = _internalIso.getEnv("AWS_EXECUTION_ENV"); + if (getEnv("GITHUB_ACTIONS")) return { type: "ci", name: "github_actions" }; + if (getEnv("GITLAB_CI")) return { type: "ci", name: "gitlab_ci" }; + if (getEnv("CIRCLECI")) return { type: "ci", name: "circleci" }; + if (getEnv("BUILDKITE")) return { type: "ci", name: "buildkite" }; + if (getEnv("CI")) return { type: "ci", name: "ci" }; + if (getEnv("VERCEL")) return { type: "server", name: "vercel" }; + if (getEnv("NETLIFY")) return { type: "server", name: "netlify" }; + const awsExecutionEnv = getEnv("AWS_EXECUTION_ENV"); if ( - _internalIso.getEnv("ECS_CONTAINER_METADATA_URI") || - _internalIso.getEnv("ECS_CONTAINER_METADATA_URI_V4") || + getEnv("ECS_CONTAINER_METADATA_URI") || + getEnv("ECS_CONTAINER_METADATA_URI_V4") || awsExecutionEnv?.startsWith("AWS_ECS_") ) { return { type: "server", name: "ecs" }; } if ( - _internalIso.getEnv("AWS_LAMBDA_FUNCTION_NAME") || + getEnv("AWS_LAMBDA_FUNCTION_NAME") || awsExecutionEnv?.startsWith("AWS_Lambda_") ) { return { type: "server", name: "aws_lambda" }; } - const nodeEnv = _internalIso.getEnv("NODE_ENV"); + const nodeEnv = getEnv("NODE_ENV"); if (!nodeEnv) return undefined; const normalizedNodeEnv = nodeEnv.toLowerCase(); if (normalizedNodeEnv === "production" || normalizedNodeEnv === "staging") { @@ -434,7 +497,7 @@ class LazyBraintrustOTLPTraceExporter implements SpanExporter { const apiKey = this.apiKey !== undefined ? this.apiKey - : await _internalIso.getBraintrustApiKey(); + : getEnv("BRAINTRUST_API_KEY"); if (!apiKey?.trim()) { throw new Error( "Braintrust API key is required. Set BRAINTRUST_API_KEY, define it in .env.braintrust, or pass apiKey option.", @@ -525,7 +588,7 @@ export class BraintrustSpanProcessor implements SpanProcessor { const apiKey = options.apiKey !== undefined ? options.apiKey - : _internalIso.getEnv("BRAINTRUST_API_KEY"); + : getEnv("BRAINTRUST_API_KEY"); // Get API URL from options or environment let apiUrl = @@ -664,132 +727,13 @@ export class BraintrustSpanProcessor implements SpanProcessor { } } -/** - * Create an OTEL context from a Braintrust span export string. - * - * Used for distributed tracing scenarios where a Braintrust span in one service - * needs to be the parent of an OTEL span in another service. - * - * @param exportStr - The string returned from span.export() - * @returns OTEL context that can be used when creating child spans - * - * @example - * ```typescript - * // Service A: Create BT span and export - * const span = logger.startSpan({ name: "service-a" }); - * const exportStr = await span.export(); - * // Send exportStr to Service B (e.g., via HTTP header) - * - * // Service B: Import context and create OTEL child - * import * as api from '@opentelemetry/api'; - * const ctx = contextFromSpanExport(exportStr); - * await api.context.with(ctx, async () => { - * await tracer.startActiveSpan("service-b", async (span) => { - * // This span is now a child of the Service A span - * span.end(); - * }); - * }); - * ``` - */ -export function contextFromSpanExport(exportStr: string): unknown { - // Parse the export string - const components = SpanComponentsV4.fromStr(exportStr); - - // Get trace and span IDs (already in hex format) - const traceIdHex = components.data.root_span_id; // 32 hex chars - const spanIdHex = components.data.span_id; // 16 hex chars - - if (!traceIdHex || !spanIdHex) { - throw new Error( - "Export string must contain root_span_id and span_id for distributed tracing", - ); - } - - // Create SpanContext marked as remote (critical for distributed tracing) - const spanContext = { - traceId: traceIdHex, - spanId: spanIdHex, - isRemote: true, - traceFlags: TraceFlags?.SAMPLED ?? 1, // SAMPLED flag - }; - - // Create NonRecordingSpan using wrapSpanContext and set in context - const nonRecordingSpan = trace.wrapSpanContext(spanContext); - let ctx = trace.setSpan(context.active(), nonRecordingSpan); - - // Construct braintrust.parent identifier - const braintrustParent = getBraintrustParent( - components.data.object_type, - components.data.object_id, - components.data.compute_object_metadata_args, +/** Create an OTEL context from a Braintrust span using W3C propagation. */ +export function contextFromSpan(span: BraintrustSpan): Context { + return braintrustW3CPropagator.extract( + context.active(), + span.inject(), + traceContextGetter, ); - - // Set braintrust.parent in baggage so it propagates automatically - if (braintrustParent) { - try { - // Try to set baggage if available - if (propagation) { - const baggage = - propagation.getBaggage(ctx) || propagation.createBaggage(); - ctx = propagation.setBaggage( - ctx, - baggage.setEntry("braintrust.parent", { - value: braintrustParent, - }), - ); - } - } catch (error) { - console.error( - "Failed to set braintrust.parent in baggage during context import:", - error, - ); - } - } - - return ctx; -} - -/** - * Construct a braintrust.parent identifier string from span components. - * - * @param objectType - Type of parent object (PROJECT_LOGS or EXPERIMENT) - * @param objectId - Resolved object ID (project_id or experiment_id) - * @param computeArgs - Optional dict with project_name/project_id for unresolved cases - * @returns String like "project_id:abc", "project_name:my-proj", "experiment_id:exp-123", or undefined - */ -function getBraintrustParent( - objectType: number, - objectId?: string | null, - computeArgs?: Record | null, -): string | undefined { - if (!objectType) { - return undefined; - } - - if (objectType === SpanObjectTypeV3.PROJECT_LOGS) { - if (objectId) { - return `project_id:${objectId}`; - } else if (computeArgs) { - const projectId = computeArgs["project_id"]; - const projectName = computeArgs["project_name"]; - if (typeof projectId === "string") { - return `project_id:${projectId}`; - } else if (typeof projectName === "string") { - return `project_name:${projectName}`; - } - } - } else if (objectType === SpanObjectTypeV3.EXPERIMENT) { - if (objectId) { - return `experiment_id:${objectId}`; - } else if (computeArgs) { - const experimentId = computeArgs["experiment_id"]; - if (typeof experimentId === "string") { - return `experiment_id:${experimentId}`; - } - } - } - - return undefined; } /** @@ -802,46 +746,16 @@ function getBraintrustParent( export function getOtelParentFromSpan( span: BraintrustSpan, ): string | undefined { - const parent = span.getParentInfo(); - - if (!parent || !parent.objectType || !parent.objectId) { - return undefined; - } - try { - if (parent.objectType === SpanObjectTypeV3.PROJECT_LOGS) { - const syncResult = parent.objectId.getSync(); - const id = syncResult?.value; - const args = parent.computeObjectMetadataArgs; - - if (id) { - return `project_id:${id}`; - } - - const projectName = args?.project_name; - if (typeof projectName === "string") { - return `project_name:${projectName}`; - } - } else if (parent.objectType === SpanObjectTypeV3.EXPERIMENT) { - const syncResult = parent.objectId.getSync(); - const id = syncResult?.value; - - // Debug details for experiment - // eslint-disable-next-line no-console - console.debug("[getOtelParentFromSpan] EXPERIMENT", { id }); - - if (id) { - // eslint-disable-next-line no-console - console.debug( - "[getOtelParentFromSpan] EXPERIMENT using experiment_id", - { id }, - ); - return `experiment_id:${id}`; + if (typeof span.inject !== "function") return undefined; + const baggage = span.inject().baggage; + for (const member of baggage?.split(",") ?? []) { + const [rawKey, rawValue] = member.trim().split("=", 2); + if (rawKey === "braintrust.parent" && rawValue) { + return decodeURIComponent(rawValue.split(";", 1)[0]); } } } catch (e) { - // Debug: unexpected error reading parent info - // eslint-disable-next-line no-console console.warn("[getOtelParentFromSpan] error extracting parent", e); } return undefined; @@ -1041,216 +955,9 @@ export function addSpanParentToBaggage( return addParentToBaggage(parentValue, ctx); } -/** - * Extract a Braintrust-compatible parent string from W3C Trace Context headers. - * - * This converts OTEL trace context headers (traceparent/baggage) into a format - * that can be passed as the 'parent' parameter to Braintrust's traced() method. - * - * @param headers - Dictionary with 'traceparent' and optionally 'baggage' keys - * @returns Braintrust V4 export string that can be used as parent parameter, - * or undefined if no valid span context is found or braintrust.parent is missing. - * - * @example - * ```typescript - * import { initLogger } from "braintrust"; - * import { parentFromHeaders } from "@braintrust/otel"; - * - * // Service C receives headers from Service B - * const headers = { traceparent: '00-trace_id-span_id-01', baggage: '...' }; - * const parent = parentFromHeaders(headers); - * - * const logger = initLogger({ projectName: "my-project" }); - * await logger.traced(async (span) => { - * span.log({ input: "BT span as child of OTEL parent" }); - * }, { name: "service_c", parent }); - * ``` - */ +/** Extract an opaque Braintrust parent context from W3C headers. */ export function parentFromHeaders( headers: Record, -): string | undefined { - try { - if (!propagation) { - console.error("OTEL propagation API not available"); - return undefined; - } - - // Older @opentelemetry/core versions do not bound inbound baggage before - // splitting it into entries. Keep this public helper safe for callers that - // use those supported peer versions, including non-HTTP transports without - // a server-level header-size limit. - for (const name of Object.keys(headers)) { - if (name.toLowerCase() !== "baggage") { - continue; - } - const value = headers[name]; - if ( - typeof value !== "string" || - value.length > MAX_BAGGAGE_LENGTH || - utf8Encoder.encode(value).length > MAX_BAGGAGE_LENGTH - ) { - return undefined; - } - let memberCount = 1; - for (let i = 0; i < value.length; i++) { - // 0x2c is the ASCII code for the comma separating baggage members. - if (value.charCodeAt(i) === 0x2c) { - memberCount++; - if (memberCount > MAX_BAGGAGE_MEMBERS) { - return undefined; - } - } - } - } - - // Extract context from headers using W3C Trace Context propagator - // This parses both traceparent and baggage headers - const ctx = propagation.extract(context.active(), headers); - - // Get span context directly from the extracted context - const spanContext = trace.getSpanContext(ctx); - if (!spanContext) { - console.error("parentFromHeaders: No valid span context in headers"); - return undefined; - } - - // Get trace_id and span_id from span context - // eslint-disable-next-line @typescript-eslint/consistent-type-assertions - const traceIdHex = spanContext.traceId as string; - // eslint-disable-next-line @typescript-eslint/consistent-type-assertions - const spanIdHex = spanContext.spanId as string; - - // Validate trace_id and span_id are not all zeros - if ( - !traceIdHex || - typeof traceIdHex !== "string" || - traceIdHex === "00000000000000000000000000000000" - ) { - console.error("parentFromHeaders: Invalid trace_id (all zeros)"); - return undefined; - } - if ( - !spanIdHex || - typeof spanIdHex !== "string" || - spanIdHex === "0000000000000000" - ) { - console.error("parentFromHeaders: Invalid span_id (all zeros)"); - return undefined; - } - - // Get braintrust.parent from baggage - const baggage = propagation.getBaggage(ctx); - const braintrustParent = baggage?.getEntry("braintrust.parent")?.value; - - if (!braintrustParent) { - console.warn( - "parentFromHeaders: braintrust.parent not found in OTEL baggage. " + - "Cannot create Braintrust parent without project information. " + - "Ensure the OTEL span sets braintrust.parent in baggage before exporting headers.", - ); - return undefined; - } - - // Parse braintrust.parent to extract object_type and object_id - let objectType: number | undefined; - let objectId: string | undefined; - let computeArgs: Record | undefined; - - // Parse braintrust.parent format: "project_id:abc", "project_name:xyz", or "experiment_id:123" - if (braintrustParent.startsWith("project_id:")) { - objectType = SpanObjectTypeV3.PROJECT_LOGS; - objectId = braintrustParent.substring("project_id:".length); - if (!objectId) { - console.error( - `parentFromHeaders: Invalid braintrust.parent format (empty project_id): ${braintrustParent}`, - ); - return undefined; - } - } else if (braintrustParent.startsWith("project_name:")) { - objectType = SpanObjectTypeV3.PROJECT_LOGS; - const projectName = braintrustParent.substring("project_name:".length); - if (!projectName) { - console.error( - `parentFromHeaders: Invalid braintrust.parent format (empty project_name): ${braintrustParent}`, - ); - return undefined; - } - computeArgs = { project_name: projectName }; - } else if (braintrustParent.startsWith("experiment_id:")) { - objectType = SpanObjectTypeV3.EXPERIMENT; - objectId = braintrustParent.substring("experiment_id:".length); - if (!objectId) { - console.error( - `parentFromHeaders: Invalid braintrust.parent format (empty experiment_id): ${braintrustParent}`, - ); - return undefined; - } - } else { - console.error( - `parentFromHeaders: Invalid braintrust.parent format: ${braintrustParent}. ` + - "Expected format: 'project_id:ID', 'project_name:NAME', or 'experiment_id:ID'", - ); - return undefined; - } - - // Create SpanComponentsV4 and export as string - const componentsData: { - object_type: number; - object_id?: string | null; - compute_object_metadata_args?: Record | null; - row_id: string; - span_id: string; - root_span_id: string; - } = { - object_type: objectType, - row_id: "otel", // Dummy row_id to enable span_id/root_span_id fields - span_id: spanIdHex, - root_span_id: traceIdHex, - }; - - // Add either object_id or compute_object_metadata_args, not both - if (computeArgs) { - componentsData.compute_object_metadata_args = computeArgs; - } else { - componentsData.object_id = objectId; - } - - // eslint-disable-next-line @typescript-eslint/consistent-type-assertions, @typescript-eslint/no-explicit-any - const components = new SpanComponentsV4(componentsData as any); - - return components.toStr(); - } catch (error) { - console.error("parentFromHeaders: Error parsing headers:", error); - return undefined; - } -} - -function generateHexId(bytes: number): string { - let result = ""; - for (let i = 0; i < bytes; i++) { - result += Math.floor(Math.random() * 256) - .toString(16) - .padStart(2, "0"); - } - return result; -} - -/** - * ID generator that generates OpenTelemetry-compatible IDs - * Uses hex strings for compatibility with OpenTelemetry systems - */ -export class OTELIDGenerator extends IDGenerator { - getSpanId(): string { - // Generate 8 random bytes and convert to hex (16 characters) - return generateHexId(8); - } - - getTraceId(): string { - // Generate 16 random bytes and convert to hex (32 characters) - return generateHexId(16); - } - - shareRootSpanId(): boolean { - return false; - } +): PropagationContext | undefined { + return extractTraceContextFromHeaders(headers); } diff --git a/integrations/otel-js/src/span-customizers.test.ts b/integrations/otel-js/src/span-customizers.test.ts index 7585b8f5a..0268bd38c 100644 --- a/integrations/otel-js/src/span-customizers.test.ts +++ b/integrations/otel-js/src/span-customizers.test.ts @@ -1,3 +1,4 @@ +import { _exportsForTestingOnly } from "../../../js/src/logger/testing"; import { afterAll, afterEach, @@ -7,7 +8,7 @@ import { expect, test, vi, -} from "vitest"; +} from "vite-plus/test"; import { createServer, type Server } from "node:http"; import { createRequire } from "node:module"; import type { AddressInfo } from "node:net"; @@ -28,7 +29,7 @@ import { resetOtelCompat, BraintrustSpanProcessor, BraintrustExporter, -} from "."; +} from "./"; import { createTracerProvider } from "../tests/utils"; type Data = Record; @@ -526,15 +527,15 @@ describe("OpenTelemetry span customizers at the HTTP boundary", () => { : { output: "redacted" }, ); shared[customizersKey] = [{ onSpanExport: hook }]; - await braintrust._exportsForTestingOnly.simulateLoginForTests(); - const memory = braintrust._exportsForTestingOnly.useTestBackgroundLogger(); + await _exportsForTestingOnly.simulateLoginForTests(); + const memory = _exportsForTestingOnly.useTestBackgroundLogger(); setupOtelCompat(); try { const logger = braintrust.initLogger({ projectId: "test", projectName: "test", }); - const experiment = braintrust._exportsForTestingOnly.initTestExperiment( + const experiment = _exportsForTestingOnly.initTestExperiment( "experiment", "test", ); @@ -566,8 +567,8 @@ describe("OpenTelemetry span customizers at the HTTP boundary", () => { expect(JSON.stringify(bodies)).not.toContain("PRIVATE"); } finally { await provider.shutdown(); - braintrust._exportsForTestingOnly.clearTestBackgroundLogger(); - braintrust._exportsForTestingOnly.simulateLogoutForTests(); + _exportsForTestingOnly.clearTestBackgroundLogger(); + _exportsForTestingOnly.simulateLogoutForTests(); } }); @@ -583,8 +584,8 @@ describe("OpenTelemetry span customizers at the HTTP boundary", () => { }, }, ]; - await bt._exportsForTestingOnly.simulateLoginForTests(); - const memory = bt._exportsForTestingOnly.useTestBackgroundLogger(); + await _exportsForTestingOnly.simulateLoginForTests(); + const memory = _exportsForTestingOnly.useTestBackgroundLogger(); const native = async () => { const span = bt .initLogger({ projectName: "native", projectId: "native" }) @@ -606,8 +607,8 @@ describe("OpenTelemetry span customizers at the HTTP boundary", () => { expect(received()).toHaveLength(2); expect(JSON.stringify(bodies)).not.toContain("PRIVATE"); } finally { - bt._exportsForTestingOnly.clearTestBackgroundLogger(); - bt._exportsForTestingOnly.simulateLogoutForTests(); + _exportsForTestingOnly.clearTestBackgroundLogger(); + _exportsForTestingOnly.simulateLogoutForTests(); } }, ); diff --git a/integrations/otel-js/tests/utils.ts b/integrations/otel-js/tests/utils.ts index 95d30b3b9..f7eb32b4e 100644 --- a/integrations/otel-js/tests/utils.ts +++ b/integrations/otel-js/tests/utils.ts @@ -139,7 +139,21 @@ export function logOtelVersions(version: "v1" | "v2" | "parent"): void { * which is critical for testing with different OpenTelemetry versions. */ export function createOtelAliases(cwd: string): Record { + const repositoryRoot = + detectOtelVersion(cwd) === "parent" + ? path.resolve(cwd, "../..") + : path.resolve(cwd, "../../.."); return { + "#braintrust-platform": path.resolve(repositoryRoot, "js/src/isomorph.ts"), + "@braintrust/otel": path.resolve( + repositoryRoot, + "integrations/otel-js/src/index.ts", + ), + "braintrust/instrumentation": path.resolve( + repositoryRoot, + "js/src/instrumentation/index.ts", + ), + braintrust: path.resolve(repositoryRoot, "js/src/node/index.ts"), "@opentelemetry/api": path.resolve(cwd, "node_modules/@opentelemetry/api"), "@opentelemetry/core": path.resolve( cwd, diff --git a/integrations/otel-js/tsconfig.json b/integrations/otel-js/tsconfig.json index 8cfc6d9ae..352d6b9f7 100644 --- a/integrations/otel-js/tsconfig.json +++ b/integrations/otel-js/tsconfig.json @@ -2,9 +2,9 @@ "compilerOptions": { "declaration": true, "lib": ["es2022"], - "module": "commonjs", + "module": "esnext", "target": "es2022", - "moduleResolution": "node", + "moduleResolution": "bundler", "strict": true, "noUnusedLocals": true, "esModuleInterop": true, diff --git a/integrations/otel-js/tsconfig.test.json b/integrations/otel-js/tsconfig.test.json index eff2845e0..ce7070daf 100644 --- a/integrations/otel-js/tsconfig.test.json +++ b/integrations/otel-js/tsconfig.test.json @@ -6,7 +6,10 @@ "types": ["node"], "baseUrl": ".", "paths": { - "@braintrust/otel": ["src/index.ts"] + "@braintrust/otel": ["src/index.ts"], + "#braintrust-platform": ["../../js/src/isomorph.ts"], + "braintrust": ["../../js/src/node/index.ts"], + "braintrust/instrumentation": ["../../js/src/instrumentation/index.ts"] } }, "include": ["src/**/*.test.ts", "tests/**/*.ts"], diff --git a/integrations/otel-js/tsup.config.ts b/integrations/otel-js/tsup.config.ts deleted file mode 100644 index 05319102e..000000000 --- a/integrations/otel-js/tsup.config.ts +++ /dev/null @@ -1,26 +0,0 @@ -import { defineConfig } from "tsup"; -import { readFileSync } from "node:fs"; - -const packageJson = JSON.parse( - readFileSync(new URL("./package.json", import.meta.url), "utf8"), -) as { version: string }; - -export default defineConfig([ - { - entry: ["src/index.ts"], - format: ["cjs", "esm"], - outDir: "dist", - external: [ - "braintrust", - "@opentelemetry/api", - "@opentelemetry/core", - "@opentelemetry/exporter-trace-otlp-http", - "@opentelemetry/sdk-trace-base", - "@opentelemetry/context-async-hooks", - ], - define: { - __BRAINTRUST_OTEL_VERSION__: JSON.stringify(packageJson.version), - }, - dts: true, - }, -]); diff --git a/integrations/otel-js/vite.config.ts b/integrations/otel-js/vite.config.ts new file mode 100644 index 000000000..0789d1703 --- /dev/null +++ b/integrations/otel-js/vite.config.ts @@ -0,0 +1,39 @@ +import { defineConfig } from "vite-plus"; +import { readFileSync } from "node:fs"; +import { createOtelAliases } from "./tests/utils"; + +const packageJson = JSON.parse( + readFileSync(new URL("./package.json", import.meta.url), "utf8"), +) as { version: string }; + +export default defineConfig({ + define: { __BRAINTRUST_OTEL_VERSION__: JSON.stringify(packageJson.version) }, + resolve: { alias: createOtelAliases(process.cwd()) }, + test: { + reporters: ["default"], + include: ["tests/**/*.test.ts", "src/**/*.test.ts"], + }, + run: { + tasks: { + compile: { + command: "vp pack", + dependsOn: [ + { task: "compile", from: ["dependencies", "devDependencies"] }, + ], + cache: { + input: [{ auto: true }, "!dist/**"], + untrackedEnv: ["SHELL", "BASH_ENV"], + output: ["dist/**"], + }, + }, + }, + }, + pack: { + define: { + __BRAINTRUST_OTEL_VERSION__: JSON.stringify(packageJson.version), + }, + dts: { generator: "oxc" }, + sourcemap: true, + fixedExtension: false, + }, +}); diff --git a/integrations/otel-js/vitest.config.ts b/integrations/otel-js/vitest.config.ts deleted file mode 100644 index 5da49856a..000000000 --- a/integrations/otel-js/vitest.config.ts +++ /dev/null @@ -1,16 +0,0 @@ -import { defineConfig } from "vitest/config"; -import { readFileSync } from "node:fs"; - -const packageJson = JSON.parse( - readFileSync(new URL("./package.json", import.meta.url), "utf8"), -) as { version: string }; - -export default defineConfig({ - define: { - __BRAINTRUST_OTEL_VERSION__: JSON.stringify(packageJson.version), - }, - test: { - reporters: ["default"], - include: ["tests/**/*.test.ts", "src/**/*.test.ts"], - }, -}); diff --git a/integrations/templates-nunjucks/.gitignore b/integrations/templates-nunjucks/.gitignore new file mode 100644 index 000000000..7a6c09645 --- /dev/null +++ b/integrations/templates-nunjucks/.gitignore @@ -0,0 +1 @@ +.vitest/ diff --git a/integrations/templates-nunjucks/package.json b/integrations/templates-nunjucks/package.json index 794c10e20..25db0b0e9 100644 --- a/integrations/templates-nunjucks/package.json +++ b/integrations/templates-nunjucks/package.json @@ -3,26 +3,24 @@ "version": "1.0.0", "description": "Nunjucks templating support for the Braintrust JS SDK", "main": "./dist/index.js", - "module": "./dist/index.mjs", + "module": "./dist/index.js", "types": "./dist/index.d.ts", "exports": { "./package.json": "./package.json", ".": { "types": "./dist/index.d.ts", - "import": "./dist/index.mjs", - "module": "./dist/index.mjs", - "require": "./dist/index.js" + "default": "./dist/index.js" } }, "files": [ "dist/**/*" ], "scripts": { - "build": "tsup", + "build": "vp run compile", "check:typings": "tsc --noEmit && tsc --noEmit -p tsconfig.test.json", - "watch": "tsup --watch", + "watch": "vp pack --watch", "clean": "rm -r dist/*", - "test": "vitest run --exclude 'smoke/scenarios/**'", + "test": "vp test run --exclude 'smoke/scenarios/**'", "yalc:publish": "yalc publish" }, "author": "Braintrust Data Inc.", @@ -37,9 +35,9 @@ "@types/node": "^20.10.5", "@types/nunjucks": "^3.2.6", "braintrust": "workspace:*", - "tsup": "^8.5.1", "typescript": "5.5.4", - "vitest": "4.1.11" + "vitest": "catalog:", + "vite-plus": "catalog:" }, "repository": { "type": "git", @@ -51,5 +49,10 @@ "access": "public", "registry": "https://registry.npmjs.org/", "provenance": true - } + }, + "type": "module", + "engines": { + "node": ">=22.18.0" + }, + "sideEffects": false } diff --git a/integrations/templates-nunjucks/smoke/scenarios/jest/Makefile b/integrations/templates-nunjucks/smoke/scenarios/jest/Makefile index 8ee5b0c0c..d9c37e89c 100644 --- a/integrations/templates-nunjucks/smoke/scenarios/jest/Makefile +++ b/integrations/templates-nunjucks/smoke/scenarios/jest/Makefile @@ -9,7 +9,7 @@ setup: echo "==> Using BRAINTRUST_TAR: $(BRAINTRUST_TAR)"; \ else \ echo "==> Building SDK"; \ - cd ../../../../.. && pnpm exec turbo build --filter=braintrust && mkdir -p js/artifacts && pnpm --filter=braintrust pack --pack-destination js/artifacts; \ + cd ../../../../.. && pnpm --filter=braintrust run build && mkdir -p js/artifacts && pnpm --filter=braintrust pack --pack-destination js/artifacts; \ for f in js/artifacts/braintrust-*.tgz; do \ if [ "$$(basename $$f)" != "braintrust-latest.tgz" ] && \ [ "$$(basename $$f)" != "braintrust-otel-latest.tgz" ] && \ @@ -31,7 +31,7 @@ setup: echo "==> Using BRAINTRUST_TEMPLATES_NUNJUCKS_JS_TAR: $(BRAINTRUST_TEMPLATES_NUNJUCKS_JS_TAR)"; \ else \ echo "==> Building @braintrust/templates-nunjucks-js package"; \ - cd ../../../../.. && pnpm exec turbo build --filter=@braintrust/templates-nunjucks-js && cd integrations/templates-nunjucks && pnpm pack --pack-destination ../js/artifacts; \ + cd ../../../../.. && pnpm --filter=@braintrust/templates-nunjucks-js run build && cd integrations/templates-nunjucks && pnpm pack --pack-destination ../../js/artifacts; \ for f in ../../js/artifacts/braintrust-templates-nunjucks-*.tgz; do \ if [ "$$(basename $$f)" != "braintrust-templates-nunjucks-js-latest.tgz" ]; then \ cp "$$f" ../../js/artifacts/braintrust-templates-nunjucks-js-latest.tgz; \ @@ -45,4 +45,4 @@ setup: test: setup @echo "==> Running jest tests" @# Run only tests under the local tests/ directory to avoid workspace-wide discovery - pnpm exec jest --passWithNoTests --roots ./tests + NODE_OPTIONS="--experimental-vm-modules" pnpm exec jest --roots ./tests diff --git a/integrations/templates-nunjucks/smoke/scenarios/jest/package.json b/integrations/templates-nunjucks/smoke/scenarios/jest/package.json index 703b21d4d..5936d026f 100644 --- a/integrations/templates-nunjucks/smoke/scenarios/jest/package.json +++ b/integrations/templates-nunjucks/smoke/scenarios/jest/package.json @@ -1,7 +1,7 @@ { "name": "smoke-templates-nunjucks-jest", "private": true, - "type": "commonjs", + "type": "module", "dependencies": { "braintrust": "file:../../../../../js/artifacts/braintrust-latest.tgz", "@braintrust/templates-nunjucks-js": "file:../../../../../js/artifacts/braintrust-templates-nunjucks-js-latest.tgz" @@ -10,7 +10,9 @@ "@types/jest": "^29.5.0", "@types/node": "^20.10.5", "jest": "^29.7.0", - "ts-jest": "^29.1.0", "typescript": "^5.4.4" + }, + "jest": { + "transform": {} } } diff --git a/integrations/templates-nunjucks/smoke/scenarios/jest/tests/jest-basic.test.js b/integrations/templates-nunjucks/smoke/scenarios/jest/tests/jest-basic.test.js index 98aa16d55..57784ebca 100644 --- a/integrations/templates-nunjucks/smoke/scenarios/jest/tests/jest-basic.test.js +++ b/integrations/templates-nunjucks/smoke/scenarios/jest/tests/jest-basic.test.js @@ -1,10 +1,10 @@ -const { +import { runTests, testMustacheTemplate, testNunjucksTemplate, -} = require("../../../../../../js/smoke/shared/dist/index.js"); -const braintrust = require("braintrust"); -const { nunjucksPlugin } = require("@braintrust/templates-nunjucks-js"); +} from "../../../../../../js/smoke/shared/dist/index.js"; +import * as braintrust from "braintrust"; +import { nunjucksPlugin } from "@braintrust/templates-nunjucks-js"; test("Templates-Nunjucks basic behavior", async () => { // Register nunjucks plugin before running tests diff --git a/integrations/templates-nunjucks/smoke/scenarios/nextjs/Makefile b/integrations/templates-nunjucks/smoke/scenarios/nextjs/Makefile index c933a85ef..362577264 100644 --- a/integrations/templates-nunjucks/smoke/scenarios/nextjs/Makefile +++ b/integrations/templates-nunjucks/smoke/scenarios/nextjs/Makefile @@ -9,7 +9,7 @@ setup: echo "==> Using BRAINTRUST_TAR: $(BRAINTRUST_TAR)"; \ else \ echo "==> Building SDK"; \ - cd ../../../../.. && pnpm exec turbo build --filter=braintrust && mkdir -p js/artifacts && pnpm --filter=braintrust pack --pack-destination js/artifacts; \ + cd ../../../../.. && pnpm --filter=braintrust run build && mkdir -p js/artifacts && pnpm --filter=braintrust pack --pack-destination js/artifacts; \ for f in js/artifacts/braintrust-*.tgz; do \ if [ "$$(basename $$f)" != "braintrust-latest.tgz" ] && \ [ "$$(basename $$f)" != "braintrust-otel-latest.tgz" ] && \ @@ -32,7 +32,7 @@ setup: else \ echo "==> Building @braintrust/templates-nunjucks-js package"; \ cd ../../../../.. && \ - pnpm exec turbo build --filter=@braintrust/templates-nunjucks-js && \ + pnpm --filter=@braintrust/templates-nunjucks-js run build && \ cd integrations/templates-nunjucks && \ pnpm pack --pack-destination ../../js/artifacts && \ cd ../../js/artifacts && \ diff --git a/integrations/templates-nunjucks/smoke/scenarios/node-esm/Makefile b/integrations/templates-nunjucks/smoke/scenarios/node-esm/Makefile index f3416ca1d..1d2234310 100644 --- a/integrations/templates-nunjucks/smoke/scenarios/node-esm/Makefile +++ b/integrations/templates-nunjucks/smoke/scenarios/node-esm/Makefile @@ -13,7 +13,7 @@ setup: echo "==> Using BRAINTRUST_TAR: $(BRAINTRUST_TAR)"; \ else \ echo "==> Building SDK"; \ - cd ../../../../.. && pnpm exec turbo build --filter=braintrust && mkdir -p js/artifacts && pnpm --filter=braintrust pack --pack-destination js/artifacts; \ + cd ../../../../.. && pnpm --filter=braintrust run build && mkdir -p js/artifacts && pnpm --filter=braintrust pack --pack-destination js/artifacts; \ for f in js/artifacts/braintrust-*.tgz; do \ if [ "$$(basename $$f)" != "braintrust-latest.tgz" ] && \ [ "$$(basename $$f)" != "braintrust-otel-latest.tgz" ] && \ @@ -35,7 +35,7 @@ setup: echo "==> Using BRAINTRUST_TEMPLATES_NUNJUCKS_JS_TAR: $(BRAINTRUST_TEMPLATES_NUNJUCKS_JS_TAR)"; \ else \ echo "==> Building @braintrust/templates-nunjucks-js package"; \ - cd ../../../../.. && pnpm exec turbo build --filter=@braintrust/templates-nunjucks-js && cd integrations/templates-nunjucks && pnpm pack --pack-destination ../js/artifacts; \ + cd ../../../../.. && pnpm --filter=@braintrust/templates-nunjucks-js run build && cd integrations/templates-nunjucks && pnpm pack --pack-destination ../../js/artifacts; \ for f in ../../js/artifacts/braintrust-templates-nunjucks-*.tgz; do \ if [ "$$(basename $$f)" != "braintrust-templates-nunjucks-js-latest.tgz" ]; then \ cp "$$f" ../../js/artifacts/braintrust-templates-nunjucks-js-latest.tgz; \ diff --git a/integrations/templates-nunjucks/smoke/scenarios/node-esm/tests/basic.test.ts b/integrations/templates-nunjucks/smoke/scenarios/node-esm/tests/basic.test.ts index 0e53dce22..1580fd347 100644 --- a/integrations/templates-nunjucks/smoke/scenarios/node-esm/tests/basic.test.ts +++ b/integrations/templates-nunjucks/smoke/scenarios/node-esm/tests/basic.test.ts @@ -2,7 +2,7 @@ import { runTests, testMustacheTemplate, testNunjucksTemplate, -} from "../../../../../../js/smoke/shared/dist/index.mjs"; +} from "../../../../../../js/smoke/shared/dist/index.js"; import * as braintrust from "braintrust"; import { nunjucksPlugin } from "@braintrust/templates-nunjucks-js"; diff --git a/integrations/templates-nunjucks/src/index.test.ts b/integrations/templates-nunjucks/src/index.test.ts index 7323d1224..06bda0c4c 100644 --- a/integrations/templates-nunjucks/src/index.test.ts +++ b/integrations/templates-nunjucks/src/index.test.ts @@ -1,4 +1,4 @@ -import { describe, test, expect, beforeAll } from "vitest"; +import { describe, test, expect, beforeAll } from "vite-plus/test"; import { registerTemplatePlugin, Prompt } from "braintrust"; import { nunjucksPlugin } from "./index"; diff --git a/integrations/templates-nunjucks/tsconfig.json b/integrations/templates-nunjucks/tsconfig.json index e0b228935..36f6bb648 100644 --- a/integrations/templates-nunjucks/tsconfig.json +++ b/integrations/templates-nunjucks/tsconfig.json @@ -4,7 +4,7 @@ "lib": ["es2022"], "module": "esnext", "target": "es2022", - "moduleResolution": "node", + "moduleResolution": "bundler", "strict": true, "noUnusedLocals": true, "esModuleInterop": true, diff --git a/integrations/templates-nunjucks/tsup.config.ts b/integrations/templates-nunjucks/tsup.config.ts deleted file mode 100644 index 41099bc7a..000000000 --- a/integrations/templates-nunjucks/tsup.config.ts +++ /dev/null @@ -1,10 +0,0 @@ -import { defineConfig } from "tsup"; - -export default defineConfig({ - entry: ["src/index.ts"], - format: ["cjs", "esm"], - outDir: "dist", - dts: true, - splitting: true, - clean: true, -}); diff --git a/integrations/templates-nunjucks/vite.config.ts b/integrations/templates-nunjucks/vite.config.ts new file mode 100644 index 000000000..0740d0c48 --- /dev/null +++ b/integrations/templates-nunjucks/vite.config.ts @@ -0,0 +1,28 @@ +import { defineConfig } from "vite-plus"; + +export default defineConfig({ + test: { + reporters: ["default"], + include: ["tests/**/*.test.ts", "src/**/*.test.ts"], + }, + run: { + tasks: { + compile: { + command: "vp pack", + dependsOn: [ + { task: "compile", from: ["dependencies", "devDependencies"] }, + ], + cache: { + input: [{ auto: true }, "!dist/**"], + untrackedEnv: ["SHELL", "BASH_ENV"], + output: ["dist/**"], + }, + }, + }, + }, + pack: { + dts: { generator: "oxc" }, + sourcemap: true, + fixedExtension: false, + }, +}); diff --git a/integrations/templates-nunjucks/vitest.config.ts b/integrations/templates-nunjucks/vitest.config.ts deleted file mode 100644 index 744610c82..000000000 --- a/integrations/templates-nunjucks/vitest.config.ts +++ /dev/null @@ -1,8 +0,0 @@ -import { defineConfig } from "vitest/config"; - -export default defineConfig({ - test: { - reporters: ["default"], - include: ["tests/**/*.test.ts", "src/**/*.test.ts"], - }, -}); diff --git a/integrations/temporal-js/.gitignore b/integrations/temporal-js/.gitignore new file mode 100644 index 000000000..7a6c09645 --- /dev/null +++ b/integrations/temporal-js/.gitignore @@ -0,0 +1 @@ +.vitest/ diff --git a/integrations/temporal-js/examples/temporal-ai-sdk/mise.toml b/integrations/temporal-js/examples/temporal-ai-sdk/mise.toml index 779401728..f905457b0 100644 --- a/integrations/temporal-js/examples/temporal-ai-sdk/mise.toml +++ b/integrations/temporal-js/examples/temporal-ai-sdk/mise.toml @@ -1,5 +1,5 @@ [settings] -experimental=true +experimental = true [env] _.file = ".env" diff --git a/integrations/temporal-js/examples/temporal-cjs/mise.toml b/integrations/temporal-js/examples/temporal-cjs/mise.toml index f7cfa1570..14fed2359 100644 --- a/integrations/temporal-js/examples/temporal-cjs/mise.toml +++ b/integrations/temporal-js/examples/temporal-cjs/mise.toml @@ -1,6 +1,6 @@ # Mise will automatically read and use .tool-versions files as well as this file. [settings] -experimental=true +experimental = true [env] # See env.example to configure API keys. diff --git a/integrations/temporal-js/examples/temporal-esm/mise.toml b/integrations/temporal-js/examples/temporal-esm/mise.toml index 53cd879dc..7f6bf10b8 100644 --- a/integrations/temporal-js/examples/temporal-esm/mise.toml +++ b/integrations/temporal-js/examples/temporal-esm/mise.toml @@ -1,5 +1,5 @@ [settings] -experimental=true +experimental = true [env] _.file = ".env" diff --git a/integrations/temporal-js/package.json b/integrations/temporal-js/package.json index 569528886..d5926dadb 100644 --- a/integrations/temporal-js/package.json +++ b/integrations/temporal-js/package.json @@ -3,29 +3,25 @@ "version": "1.0.1", "description": "SDK for integrating Braintrust with Temporal", "main": "./dist/index.js", - "module": "./dist/index.mjs", + "module": "./dist/index.js", "types": "./dist/index.d.ts", "exports": { "./package.json": "./package.json", ".": { "types": "./dist/index.d.ts", - "import": "./dist/index.mjs", - "module": "./dist/index.mjs", - "require": "./dist/index.js" + "default": "./dist/index.js" }, "./workflow-interceptors": { "types": "./dist/workflow-interceptors.d.ts", - "import": "./dist/workflow-interceptors.mjs", - "module": "./dist/workflow-interceptors.mjs", - "require": "./dist/workflow-interceptors.js" + "default": "./dist/workflow-interceptors.js" } }, "scripts": { - "build": "tsup", + "build": "vp run compile", "check:typings": "tsc --noEmit && tsc --noEmit -p tsconfig.test.json", - "watch": "tsup --watch", + "watch": "vp pack --watch", "clean": "rm -r dist/*", - "test": "vitest run", + "test": "vp test run", "yalc:publish": "yalc publish" }, "author": "Braintrust Data Inc.", @@ -38,9 +34,9 @@ "@temporalio/workflow": "^1.14.1", "@types/node": "^22.15.21", "braintrust": "workspace:*", - "tsup": "^8.5.0", "typescript": "5.5.4", - "vitest": "4.1.11" + "vitest": "catalog:", + "vite-plus": "catalog:" }, "peerDependencies": { "@temporalio/activity": ">=1.11.0", @@ -80,5 +76,10 @@ "access": "public", "registry": "https://registry.npmjs.org/", "provenance": true - } + }, + "type": "module", + "engines": { + "node": ">=22.18.0" + }, + "sideEffects": false } diff --git a/integrations/temporal-js/src/interceptors.ts b/integrations/temporal-js/src/interceptors.ts index 285230c59..6d732e76a 100644 --- a/integrations/temporal-js/src/interceptors.ts +++ b/integrations/temporal-js/src/interceptors.ts @@ -6,15 +6,9 @@ import type { ActivityInterceptors, } from "@temporalio/worker"; import type { WorkflowClientInterceptor } from "@temporalio/client"; -import { defaultPayloadConverter } from "@temporalio/common"; import * as braintrust from "braintrust"; -import { SpanComponentsV4 } from "braintrust/util"; -import { getWorkflowSpanExport } from "./sinks"; -import { - BRAINTRUST_SPAN_HEADER, - BRAINTRUST_WORKFLOW_SPAN_ID_HEADER, - deserializeHeaderValue, -} from "./utils"; +import { getWorkflowTraceContext } from "./sinks"; +import { deserializeTraceContext, serializeTraceContext } from "./utils"; /** * Create a client interceptor that propagates Braintrust span context to workflows. @@ -25,19 +19,13 @@ export function createBraintrustClientInterceptor(): WorkflowClientInterceptor { async start(input, next) { const span = braintrust.currentSpan(); if (span) { - const exported = await span.export(); - if (exported) { - const payload = defaultPayloadConverter.toPayload(exported); - if (payload) { - return next({ - ...input, - headers: { - ...input.headers, - [BRAINTRUST_SPAN_HEADER]: payload, - }, - }); - } - } + return next({ + ...input, + headers: { + ...input.headers, + ...serializeTraceContext(braintrust.injectTraceContext()), + }, + }); } return next(input); }, @@ -47,19 +35,13 @@ export function createBraintrustClientInterceptor(): WorkflowClientInterceptor { async signalWithStart(input, next) { const span = braintrust.currentSpan(); if (span) { - const exported = await span.export(); - if (exported) { - const payload = defaultPayloadConverter.toPayload(exported); - if (payload) { - return next({ - ...input, - headers: { - ...input.headers, - [BRAINTRUST_SPAN_HEADER]: payload, - }, - }); - } - } + return next({ + ...input, + headers: { + ...input.headers, + ...serializeTraceContext(braintrust.injectTraceContext()), + }, + }); } return next(input); }, @@ -79,76 +61,13 @@ class BraintrustActivityInterceptor implements ActivityInboundCallsInterceptor { const info = this.ctx.info; const runId = info.workflowExecution.runId; - // Try to get workflow span export - first check local Map, then headers - let parent: string | undefined; + // Prefer the live workflow span on the same worker, then fall back to the + // W3C context propagated through Temporal headers. + let parent: braintrust.PropagationContext | undefined; // Check if we have the workflow span export locally (same worker as workflow) - const spanExportPromise = getWorkflowSpanExport(runId); - if (spanExportPromise) { - try { - parent = await spanExportPromise; - } catch { - // Ignore errors, fall through to header check - } - } - - // For cross-worker activities: construct parent from workflow span ID + client context - if (!parent && input.headers) { - const workflowSpanId = deserializeHeaderValue( - input.headers[BRAINTRUST_WORKFLOW_SPAN_ID_HEADER], - ); - const clientContext = deserializeHeaderValue( - input.headers[BRAINTRUST_SPAN_HEADER], - ); - - if (workflowSpanId && clientContext) { - try { - const clientComponents = SpanComponentsV4.fromStr(clientContext); - const clientData = clientComponents.data; - - // We can only construct a workflow parent if we have: - // 1. Tracing context (root_span_id) - // 2. Object metadata (object_id or compute_object_metadata_args) - const hasTracingContext = !!clientData.root_span_id; - const hasObjectMetadata = - !!clientData.object_id || !!clientData.compute_object_metadata_args; - - if (hasTracingContext && hasObjectMetadata) { - // Construct workflow parent with the workflow's span ID - // IMPORTANT: row_id must match span_id for the parent span - // Must provide EITHER object_id OR compute_object_metadata_args, not both - const workflowParentBase = { - object_type: clientData.object_type, - propagated_event: clientData.propagated_event, - row_id: workflowSpanId, // Use workflow's row_id, not client's - span_id: workflowSpanId, // Use workflow's span_id, not client's - root_span_id: clientData.root_span_id, // Keep same trace - }; - const workflowComponents = clientData.object_id - ? new SpanComponentsV4({ - ...workflowParentBase, - object_id: clientData.object_id, - }) - : new SpanComponentsV4({ - ...workflowParentBase, - compute_object_metadata_args: - clientData.compute_object_metadata_args!, - }); - - parent = workflowComponents.toStr(); - } else { - // Client context doesn't have root_span_id, use it directly - parent = clientContext; - } - } catch { - // Fall back to client context if parsing fails - parent = clientContext; - } - } else if (clientContext) { - // No workflow span ID, use client context directly - parent = clientContext; - } - } + parent = + getWorkflowTraceContext(runId) ?? deserializeTraceContext(input.headers); const span = braintrust.startSpan({ [Symbol.for("braintrust.spanInstrumentationName")]: "temporal", diff --git a/integrations/temporal-js/src/sinks.ts b/integrations/temporal-js/src/sinks.ts index 5ae0099be..679717285 100644 --- a/integrations/temporal-js/src/sinks.ts +++ b/integrations/temporal-js/src/sinks.ts @@ -6,24 +6,26 @@ import * as braintrust from "braintrust"; // NOTE: WorkflowInfo is NOT included here - it's automatically injected by the runtime export interface BraintrustSinks extends Sinks { braintrust: { - workflowStarted(parentContext?: string, workflowSpanId?: string): void; + workflowStarted( + parentContext?: braintrust.PropagationContext, + workflowSpanId?: string, + ): void; workflowCompleted(error?: string): void; }; } // Active workflow spans tracked by run ID const workflowSpans = new Map(); -// Workflow span exports tracked by run ID (as promises for async export) -const workflowSpanExports = new Map>(); +const workflowTraceContexts = new Map(); /** * Get the exported span context for a workflow by run ID. * Activities on the same worker can use this to parent to the workflow span. */ -export function getWorkflowSpanExport( +export function getWorkflowTraceContext( runId: string, -): Promise | undefined { - return workflowSpanExports.get(runId); +): braintrust.PropagationContext | undefined { + return workflowTraceContexts.get(runId); } /** @@ -36,7 +38,7 @@ export function createBraintrustSinks(): InjectedSinks { workflowStarted: { fn: ( info: WorkflowInfo, - parentContext?: string, + parentContext?: braintrust.PropagationContext, workflowSpanId?: string, ) => { const span = braintrust.startSpan({ @@ -54,7 +56,7 @@ export function createBraintrustSinks(): InjectedSinks { }, }); workflowSpans.set(info.runId, span); - workflowSpanExports.set(info.runId, span.export()); + workflowTraceContexts.set(info.runId, span.inject()); }, callDuringReplay: false, }, @@ -67,7 +69,7 @@ export function createBraintrustSinks(): InjectedSinks { } span.end(); workflowSpans.delete(info.runId); - workflowSpanExports.delete(info.runId); + workflowTraceContexts.delete(info.runId); } }, callDuringReplay: false, diff --git a/integrations/temporal-js/src/temporal.test.ts b/integrations/temporal-js/src/temporal.test.ts index 04b9c43c0..28fc9cd6e 100644 --- a/integrations/temporal-js/src/temporal.test.ts +++ b/integrations/temporal-js/src/temporal.test.ts @@ -1,16 +1,16 @@ -import { expect, test, describe } from "vitest"; +import { expect, test, describe } from "vite-plus/test"; import { serializeHeaderValue, deserializeHeaderValue, - BRAINTRUST_SPAN_HEADER, - BRAINTRUST_WORKFLOW_SPAN_HEADER, - BRAINTRUST_WORKFLOW_SPAN_ID_HEADER, + deserializeTraceContext, + serializeTraceContext, + withParentSpanId, } from "./utils"; import { SpanComponentsV3, SpanComponentsV4, SpanObjectTypeV3, -} from "braintrust/util"; +} from "../../../js/util"; import { BraintrustTemporalPlugin, createBraintrustTemporalPlugin, @@ -69,12 +69,29 @@ describe("temporal header utilities", () => { } }); - test("header constants are defined", () => { - expect(BRAINTRUST_SPAN_HEADER).toBe("_braintrust-span"); - expect(BRAINTRUST_WORKFLOW_SPAN_HEADER).toBe("_braintrust-workflow-span"); - expect(BRAINTRUST_WORKFLOW_SPAN_ID_HEADER).toBe( - "_braintrust-workflow-span-id", + test("round-trips W3C trace context through Temporal payloads", () => { + const context = { + traceparent: "00-12345678901234567890123456789012-1234567890123456-01", + tracestate: "vendor=value", + baggage: "braintrust.parent=project_name%3Atest", + }; + expect(deserializeTraceContext(serializeTraceContext(context))).toEqual( + context, + ); + }); + + test("reparents W3C context to the workflow span", () => { + const context = withParentSpanId( + { + traceparent: "00-12345678901234567890123456789012-1234567890123456-01", + baggage: "braintrust.parent=project_name%3Atest", + }, + "abcdefabcdefabcd", ); + expect(context).toEqual({ + traceparent: "00-12345678901234567890123456789012-abcdefabcdefabcd-01", + baggage: "braintrust.parent=project_name%3Atest", + }); }); }); diff --git a/integrations/temporal-js/src/utils.ts b/integrations/temporal-js/src/utils.ts index dc5add88a..51ffcae33 100644 --- a/integrations/temporal-js/src/utils.ts +++ b/integrations/temporal-js/src/utils.ts @@ -1,9 +1,7 @@ import type { Payload } from "@temporalio/common"; +import type { PropagationContext } from "braintrust"; -export const BRAINTRUST_SPAN_HEADER = "_braintrust-span"; -export const BRAINTRUST_WORKFLOW_SPAN_HEADER = "_braintrust-workflow-span"; -export const BRAINTRUST_WORKFLOW_SPAN_ID_HEADER = - "_braintrust-workflow-span-id"; +const TRACE_CONTEXT_HEADERS = ["traceparent", "tracestate", "baggage"] as const; export function serializeHeaderValue(value: string): Payload { return { @@ -27,3 +25,41 @@ export function deserializeHeaderValue( return undefined; } } + +export function serializeTraceContext( + context: PropagationContext, +): Record { + return Object.fromEntries( + TRACE_CONTEXT_HEADERS.flatMap((name) => { + const value = context[name]; + return value ? [[name, serializeHeaderValue(value)]] : []; + }), + ); +} + +export function deserializeTraceContext( + headers: Record | undefined, +): PropagationContext | undefined { + if (!headers) return undefined; + const context = Object.fromEntries( + TRACE_CONTEXT_HEADERS.flatMap((name) => { + const value = deserializeHeaderValue(headers[name]); + return value ? [[name, value]] : []; + }), + ); + return context.traceparent ? context : undefined; +} + +export function withParentSpanId( + context: PropagationContext, + spanId: string, +): PropagationContext | undefined { + const match = context.traceparent.match( + /^([0-9a-f]{2})-([0-9a-f]{32})-[0-9a-f]{16}-([0-9a-f]{2})$/, + ); + if (!match) return undefined; + return { + ...context, + traceparent: `${match[1]}-${match[2]}-${spanId}-${match[3]}`, + }; +} diff --git a/integrations/temporal-js/src/workflow-interceptors.ts b/integrations/temporal-js/src/workflow-interceptors.ts index 914dcc154..b7bc0a2b8 100644 --- a/integrations/temporal-js/src/workflow-interceptors.ts +++ b/integrations/temporal-js/src/workflow-interceptors.ts @@ -12,7 +12,6 @@ import { WorkflowExecuteInput, Next, proxySinks, - workflowInfo, uuid4, } from "@temporalio/workflow"; import type { @@ -21,13 +20,12 @@ import type { StartChildWorkflowExecutionInput, } from "@temporalio/workflow"; import type { Payload } from "@temporalio/common"; +import type { PropagationContext } from "braintrust"; import type { BraintrustSinks } from "./sinks"; import { - BRAINTRUST_SPAN_HEADER, - BRAINTRUST_WORKFLOW_SPAN_HEADER, - BRAINTRUST_WORKFLOW_SPAN_ID_HEADER, - serializeHeaderValue, - deserializeHeaderValue, + deserializeTraceContext, + serializeTraceContext, + withParentSpanId, } from "./utils"; const { braintrust } = proxySinks(); @@ -37,7 +35,7 @@ const { braintrust } = proxySinks(); * Created per-workflow by the factory function to avoid global state issues. */ interface WorkflowSpanState { - parentContext: string | undefined; + parentContext: PropagationContext | undefined; spanId: string | undefined; } @@ -49,15 +47,13 @@ class BraintrustWorkflowInboundInterceptor implements WorkflowInboundCallsInterc next: Next, ): Promise { // Extract parent context from headers - const parentContext = input.headers - ? deserializeHeaderValue(input.headers[BRAINTRUST_SPAN_HEADER]) - : undefined; + const parentContext = deserializeTraceContext(input.headers); // Store for the outbound interceptor to forward to activities this.state.parentContext = parentContext; // Generate a deterministic spanId for the workflow span - this.state.spanId = uuid4(); + this.state.spanId = uuid4().replace(/-/g, "").slice(0, 16); // Create workflow span via sink (only called if not replaying) // NOTE: WorkflowInfo is injected automatically by the runtime @@ -78,27 +74,11 @@ class BraintrustWorkflowOutboundInterceptor implements WorkflowOutboundCallsInte constructor(private state: WorkflowSpanState) {} private getHeaders(): Record { - const info = workflowInfo(); - const headers: Record = {}; - - // Pass runId so activity can look up workflow span on same worker - headers[BRAINTRUST_WORKFLOW_SPAN_HEADER] = serializeHeaderValue(info.runId); - - // Pass workflow span ID for cross-worker activities to construct parent - if (this.state.spanId) { - headers[BRAINTRUST_WORKFLOW_SPAN_ID_HEADER] = serializeHeaderValue( - this.state.spanId, - ); - } - - // Pass client context for cross-worker activities to construct parent - if (this.state.parentContext) { - headers[BRAINTRUST_SPAN_HEADER] = serializeHeaderValue( - this.state.parentContext, - ); - } - - return headers; + const context = + this.state.parentContext && this.state.spanId + ? withParentSpanId(this.state.parentContext, this.state.spanId) + : undefined; + return context ? serializeTraceContext(context) : {}; } scheduleActivity( diff --git a/integrations/temporal-js/tsconfig.json b/integrations/temporal-js/tsconfig.json index 8cfc6d9ae..352d6b9f7 100644 --- a/integrations/temporal-js/tsconfig.json +++ b/integrations/temporal-js/tsconfig.json @@ -2,9 +2,9 @@ "compilerOptions": { "declaration": true, "lib": ["es2022"], - "module": "commonjs", + "module": "esnext", "target": "es2022", - "moduleResolution": "node", + "moduleResolution": "bundler", "strict": true, "noUnusedLocals": true, "esModuleInterop": true, diff --git a/integrations/temporal-js/tsup.config.ts b/integrations/temporal-js/tsup.config.ts deleted file mode 100644 index e37c36c4b..000000000 --- a/integrations/temporal-js/tsup.config.ts +++ /dev/null @@ -1,20 +0,0 @@ -import { defineConfig } from "tsup"; - -export default defineConfig([ - { - entry: ["src/index.ts", "src/workflow-interceptors.ts"], - format: ["cjs", "esm"], - outDir: "dist", - external: [ - "braintrust", - "braintrust/util", - "@braintrust/temporal/workflow-interceptors", - "@temporalio/activity", - "@temporalio/client", - "@temporalio/common", - "@temporalio/worker", - "@temporalio/workflow", - ], - dts: true, - }, -]); diff --git a/integrations/temporal-js/vite.config.ts b/integrations/temporal-js/vite.config.ts new file mode 100644 index 000000000..c93c1918c --- /dev/null +++ b/integrations/temporal-js/vite.config.ts @@ -0,0 +1,30 @@ +import { defineConfig } from "vite-plus"; + +export default defineConfig({ + test: { + reporters: ["default"], + include: ["tests/**/*.test.ts", "src/**/*.test.ts"], + }, + run: { + tasks: { + compile: { + command: "vp pack", + dependsOn: [ + { task: "compile", from: ["dependencies", "devDependencies"] }, + ], + cache: { + input: [{ auto: true }, "!dist/**"], + untrackedEnv: ["SHELL", "BASH_ENV"], + output: ["dist/**"], + }, + }, + }, + }, + pack: { + entry: ["src/index.ts", "src/workflow-interceptors.ts"], + deps: { neverBundle: ["@braintrust/temporal/workflow-interceptors"] }, + dts: { generator: "oxc" }, + sourcemap: true, + fixedExtension: false, + }, +}); diff --git a/integrations/temporal-js/vitest.config.ts b/integrations/temporal-js/vitest.config.ts deleted file mode 100644 index 744610c82..000000000 --- a/integrations/temporal-js/vitest.config.ts +++ /dev/null @@ -1,8 +0,0 @@ -import { defineConfig } from "vitest/config"; - -export default defineConfig({ - test: { - reporters: ["default"], - include: ["tests/**/*.test.ts", "src/**/*.test.ts"], - }, -}); diff --git a/integrations/val.town/deno.json b/integrations/val.town/deno.json index f21f43c25..c2fedfe7f 100644 --- a/integrations/val.town/deno.json +++ b/integrations/val.town/deno.json @@ -8,7 +8,6 @@ "@std/fs": "jsr:@std/fs@^1.0.14", "@std/path": "jsr:@std/path@^1.0.8", "autoevals": "npm:autoevals@^0.0.122", - "braintrust": "npm:braintrust@^0.0.187", - "zod": "npm:zod@^3.24.2" + "zod": "npm:zod@^3.25.34" } } diff --git a/integrations/val.town/deno.lock b/integrations/val.town/deno.lock index 50df406a5..62619ceb0 100644 --- a/integrations/val.town/deno.lock +++ b/integrations/val.town/deno.lock @@ -1,5 +1,5 @@ { - "version": "4", + "version": "5", "specifiers": { "jsr:@cliffy/ansi@^1.0.0-rc.7": "1.0.0-rc.7", "jsr:@cliffy/command@^1.0.0-rc.7": "1.0.0-rc.7", @@ -16,9 +16,8 @@ "jsr:@std/text@~1.0.7": "1.0.11", "npm:autoevals@*": "0.0.122_zod@3.24.2", "npm:autoevals@^0.0.122": "0.0.122_zod@3.24.2", - "npm:braintrust@*": "0.0.187_zod@3.24.2", - "npm:braintrust@^0.0.187": "0.0.187_zod@3.24.2", - "npm:zod@^3.24.2": "3.24.2" + "npm:braintrust@^3.30.0": "3.32.0_zod@3.25.76", + "npm:zod@^3.25.34": "3.25.76" }, "jsr": { "@cliffy/ansi@1.0.0-rc.7": { @@ -78,340 +77,256 @@ } }, "npm": { - "@ai-sdk/provider-utils@1.0.22_zod@3.24.2": { - "integrity": "sha512-YHK2rpj++wnLVc9vPGzGFP3Pjeld2MwhKinetA0zKXOoHAT/Jit5O8kZsxcSlJPu9wvcGT1UGZEjZrtO7PfFOQ==", + "@asteasolutions/zod-to-openapi@6.4.0_zod@3.24.2": { + "integrity": "sha512-8cxfF7AHHx2PqnN4Cd8/O8CBu/nVYJP9DpnfVLW3BFb66VJDnqI/CczZnkqMc3SNh6J9GiX7JbJ5T4BSP4HZ2Q==", "dependencies": [ - "@ai-sdk/provider@0.0.26", - "eventsource-parser", - "nanoid", - "secure-json-parse", - "zod" + "openapi3-ts" ] }, - "@ai-sdk/provider@0.0.26": { - "integrity": "sha512-dQkfBDs2lTYpKM8389oopPdQgIU007GQyCbuPPrV+K6MtSII3HBfE0stUIMXUb44L+LK1t6GXPP7wjSzjO6uKg==", - "dependencies": [ - "json-schema" - ] + "@braintrust/bt-darwin-arm64@0.12.0": { + "integrity": "sha512-mY6VW/3VwcOQOGN8sYHS6F0xzHTFwgZcNlj7zlQttI6OXOCGt/bhonGIqd03QBhxmj0M31ymSS7TqSeCK/RYIQ==", + "os": ["darwin"], + "cpu": ["arm64"] }, - "@ai-sdk/provider@1.0.10": { - "integrity": "sha512-pco8Zl9U0xwXI+nCLc0woMtxbvjU8hRmGTseAUiPHFLYAAL8trRPCukg69IDeinOvIeo1SmXxAIdWWPZOLb4Cg==", - "dependencies": [ - "json-schema" - ] + "@braintrust/bt-darwin-x64@0.12.0": { + "integrity": "sha512-woyRyDv2DfCF8+von+3X9f1cddAbFnKLSfCbEkrBQVc0ZsAM60as8nehYv7DkafJF/67yKFx/sUraV65sukesQ==", + "os": ["darwin"], + "cpu": ["x64"] }, - "@ai-sdk/react@0.0.70_zod@3.24.2": { - "integrity": "sha512-GnwbtjW4/4z7MleLiW+TOZC2M29eCg1tOUpuEiYFMmFNZK8mkrqM0PFZMo6UsYeUYMWqEOOcPOU9OQVJMJh7IQ==", - "dependencies": [ - "@ai-sdk/provider-utils", - "@ai-sdk/ui-utils", - "swr", - "throttleit", - "zod" - ] + "@braintrust/bt-linux-arm64@0.12.0": { + "integrity": "sha512-J9/7f3EIMKmFmSSQHQnAQLpUgB8YJENrOlkABjlTprBgsIYVgz7tSH7yg2P3ur+2Jem7PpGnrDxHGh/UjRfjsg==", + "os": ["linux"], + "cpu": ["arm64"] }, - "@ai-sdk/solid@0.0.54_zod@3.24.2": { - "integrity": "sha512-96KWTVK+opdFeRubqrgaJXoNiDP89gNxFRWUp0PJOotZW816AbhUf4EnDjBjXTLjXL1n0h8tGSE9sZsRkj9wQQ==", - "dependencies": [ - "@ai-sdk/provider-utils", - "@ai-sdk/ui-utils" - ] + "@braintrust/bt-linux-x64-musl@0.12.0": { + "integrity": "sha512-KnLgENOoztBXcH+mLFJe4bYzi67kSOuELOQrm4Hler35GjgaBMI1fFLIUjKRPAmyT9VIhBMhy1ICfGEFLueMEQ==", + "os": ["linux"], + "cpu": ["x64"] }, - "@ai-sdk/svelte@0.0.57_zod@3.24.2": { - "integrity": "sha512-SyF9ItIR9ALP9yDNAD+2/5Vl1IT6kchgyDH8xkmhysfJI6WrvJbtO1wdQ0nylvPLcsPoYu+cAlz1krU4lFHcYw==", - "dependencies": [ - "@ai-sdk/provider-utils", - "@ai-sdk/ui-utils", - "sswr" - ] + "@braintrust/bt-linux-x64@0.12.0": { + "integrity": "sha512-HJFbUl3HYYY1Ivw8OYBeIMcIsU9r7+fC9BzLWB5FdH7881ToKee2wbbLZssMCxFbMVeUxzEo+SzGD/1Z9Gk9CA==", + "os": ["linux"], + "cpu": ["x64"] }, - "@ai-sdk/ui-utils@0.0.50_zod@3.24.2": { - "integrity": "sha512-Z5QYJVW+5XpSaJ4jYCCAVG7zIAuKOOdikhgpksneNmKvx61ACFaf98pmOd+xnjahl0pIlc/QIe6O4yVaJ1sEaw==", - "dependencies": [ - "@ai-sdk/provider@0.0.26", - "@ai-sdk/provider-utils", - "json-schema", - "secure-json-parse", - "zod", - "zod-to-json-schema" - ] - }, - "@ai-sdk/vue@0.0.59_zod@3.24.2": { - "integrity": "sha512-+ofYlnqdc8c4F6tM0IKF0+7NagZRAiqBJpGDJ+6EYhDW8FHLUP/JFBgu32SjxSxC6IKFZxEnl68ZoP/Z38EMlw==", - "dependencies": [ - "@ai-sdk/provider-utils", - "@ai-sdk/ui-utils", - "swrv" - ] + "@braintrust/bt-win32-arm64@0.12.0": { + "integrity": "sha512-+7PkdBAmiqEJsWteGHP/Zs62F75PkHPA8m/ej4TOz/mHGKulUU0bZibw91KRqIB7J7jGi5ItvCiqkiGaLKLnyw==", + "os": ["win32"], + "cpu": ["arm64"] }, - "@ampproject/remapping@2.3.0": { - "integrity": "sha512-30iZtAPgz+LTIYoeivqYo853f02jBYSd5uGnGpkFV0M3xOt9aN73erkgYAmZU43x4VfqcnLxW9Kpg3R5LC4YYw==", - "dependencies": [ - "@jridgewell/gen-mapping", - "@jridgewell/trace-mapping" - ] - }, - "@asteasolutions/zod-to-openapi@6.4.0_zod@3.24.2": { - "integrity": "sha512-8cxfF7AHHx2PqnN4Cd8/O8CBu/nVYJP9DpnfVLW3BFb66VJDnqI/CczZnkqMc3SNh6J9GiX7JbJ5T4BSP4HZ2Q==", - "dependencies": [ - "openapi3-ts", - "zod" - ] - }, - "@babel/helper-string-parser@7.25.9": { - "integrity": "sha512-4A/SCr/2KLd5jrtOMFzaKjVtAei3+2r/NChoBNoZ3EyP/+GlhoaEGoWOZUmFmoITP7zOJyHIMm+DYRd8o3PvHA==" - }, - "@babel/helper-validator-identifier@7.25.9": { - "integrity": "sha512-Ed61U6XJc3CVRfkERJWDz4dJwKe7iLmmJsbOGu9wSloNSFttHV0I8g6UAgb7qnK5ly5bGLPd4oXZlxCdANBOWQ==" - }, - "@babel/parser@7.26.9": { - "integrity": "sha512-81NWa1njQblgZbQHxWHpxxCzNsa3ZwvFqpUg7P+NNUU6f3UU2jBEg4OlF/J6rl8+PQGh1q6/zWScd001YwcA5A==", - "dependencies": [ - "@babel/types" - ] - }, - "@babel/types@7.26.9": { - "integrity": "sha512-Y3IR1cRnOxOCDvMmNiym7XpXQ93iGDDPHx+Zj+NM+rg0fBaShfQLkg+hKPaZCEvg5N/LeCo4+Rj/i3FuJsIQaw==", - "dependencies": [ - "@babel/helper-string-parser", - "@babel/helper-validator-identifier" - ] + "@braintrust/bt-win32-x64@0.12.0": { + "integrity": "sha512-Q0c+pVUGm82n/7N8QJ5s6j/G/Q0GFzqOaTipHjkmElLbAOOEcfRH/uljdtIPNBiEQcrzqirS0GmOgiFkeQ3Txg==", + "os": ["win32"], + "cpu": ["x64"] }, "@braintrust/core@0.0.81_zod@3.24.2": { "integrity": "sha512-izwl3I31sQu6qeDHZB+gYvqpKXNIHNXc7idBgrAolDXyQX2MtK9Wdnw1lnkjiXl5y2ww3gcLiy0jHDB3zaiOrw==", "dependencies": [ "@asteasolutions/zod-to-openapi", - "uuid", + "uuid@9.0.1", "zod" ] }, - "@esbuild/aix-ppc64@0.25.0": { - "integrity": "sha512-O7vun9Sf8DFjH2UtqK8Ku3LkquL9SZL8OLY1T5NZkA34+wG3OQF7cl4Ql8vdNzM6fzBbYfLaiRLIOZ+2FOCgBQ==" - }, - "@esbuild/android-arm64@0.25.0": { - "integrity": "sha512-grvv8WncGjDSyUBjN9yHXNt+cq0snxXbDxy5pJtzMKGmmpPxeAmAhWxXI+01lU5rwZomDgD3kJwulEnhTRUd6g==" - }, - "@esbuild/android-arm@0.25.0": { - "integrity": "sha512-PTyWCYYiU0+1eJKmw21lWtC+d08JDZPQ5g+kFyxP0V+es6VPPSUhM6zk8iImp2jbV6GwjX4pap0JFbUQN65X1g==" - }, - "@esbuild/android-x64@0.25.0": { - "integrity": "sha512-m/ix7SfKG5buCnxasr52+LI78SQ+wgdENi9CqyCXwjVR2X4Jkz+BpC3le3AoBPYTC9NHklwngVXvbJ9/Akhrfg==" - }, - "@esbuild/darwin-arm64@0.25.0": { - "integrity": "sha512-mVwdUb5SRkPayVadIOI78K7aAnPamoeFR2bT5nszFUZ9P8UpK4ratOdYbZZXYSqPKMHfS1wdHCJk1P1EZpRdvw==" - }, - "@esbuild/darwin-x64@0.25.0": { - "integrity": "sha512-DgDaYsPWFTS4S3nWpFcMn/33ZZwAAeAFKNHNa1QN0rI4pUjgqf0f7ONmXf6d22tqTY+H9FNdgeaAa+YIFUn2Rg==" - }, - "@esbuild/freebsd-arm64@0.25.0": { - "integrity": "sha512-VN4ocxy6dxefN1MepBx/iD1dH5K8qNtNe227I0mnTRjry8tj5MRk4zprLEdG8WPyAPb93/e4pSgi1SoHdgOa4w==" - }, - "@esbuild/freebsd-x64@0.25.0": { - "integrity": "sha512-mrSgt7lCh07FY+hDD1TxiTyIHyttn6vnjesnPoVDNmDfOmggTLXRv8Id5fNZey1gl/V2dyVK1VXXqVsQIiAk+A==" - }, - "@esbuild/linux-arm64@0.25.0": { - "integrity": "sha512-9QAQjTWNDM/Vk2bgBl17yWuZxZNQIF0OUUuPZRKoDtqF2k4EtYbpyiG5/Dk7nqeK6kIJWPYldkOcBqjXjrUlmg==" - }, - "@esbuild/linux-arm@0.25.0": { - "integrity": "sha512-vkB3IYj2IDo3g9xX7HqhPYxVkNQe8qTK55fraQyTzTX/fxaDtXiEnavv9geOsonh2Fd2RMB+i5cbhu2zMNWJwg==" - }, - "@esbuild/linux-ia32@0.25.0": { - "integrity": "sha512-43ET5bHbphBegyeqLb7I1eYn2P/JYGNmzzdidq/w0T8E2SsYL1U6un2NFROFRg1JZLTzdCoRomg8Rvf9M6W6Gg==" - }, - "@esbuild/linux-loong64@0.25.0": { - "integrity": "sha512-fC95c/xyNFueMhClxJmeRIj2yrSMdDfmqJnyOY4ZqsALkDrrKJfIg5NTMSzVBr5YW1jf+l7/cndBfP3MSDpoHw==" - }, - "@esbuild/linux-mips64el@0.25.0": { - "integrity": "sha512-nkAMFju7KDW73T1DdH7glcyIptm95a7Le8irTQNO/qtkoyypZAnjchQgooFUDQhNAy4iu08N79W4T4pMBwhPwQ==" - }, - "@esbuild/linux-ppc64@0.25.0": { - "integrity": "sha512-NhyOejdhRGS8Iwv+KKR2zTq2PpysF9XqY+Zk77vQHqNbo/PwZCzB5/h7VGuREZm1fixhs4Q/qWRSi5zmAiO4Fw==" - }, - "@esbuild/linux-riscv64@0.25.0": { - "integrity": "sha512-5S/rbP5OY+GHLC5qXp1y/Mx//e92L1YDqkiBbO9TQOvuFXM+iDqUNG5XopAnXoRH3FjIUDkeGcY1cgNvnXp/kA==" - }, - "@esbuild/linux-s390x@0.25.0": { - "integrity": "sha512-XM2BFsEBz0Fw37V0zU4CXfcfuACMrppsMFKdYY2WuTS3yi8O1nFOhil/xhKTmE1nPmVyvQJjJivgDT+xh8pXJA==" - }, - "@esbuild/linux-x64@0.25.0": { - "integrity": "sha512-9yl91rHw/cpwMCNytUDxwj2XjFpxML0y9HAOH9pNVQDpQrBxHy01Dx+vaMu0N1CKa/RzBD2hB4u//nfc+Sd3Cw==" - }, - "@esbuild/netbsd-arm64@0.25.0": { - "integrity": "sha512-RuG4PSMPFfrkH6UwCAqBzauBWTygTvb1nxWasEJooGSJ/NwRw7b2HOwyRTQIU97Hq37l3npXoZGYMy3b3xYvPw==" - }, - "@esbuild/netbsd-x64@0.25.0": { - "integrity": "sha512-jl+qisSB5jk01N5f7sPCsBENCOlPiS/xptD5yxOx2oqQfyourJwIKLRA2yqWdifj3owQZCL2sn6o08dBzZGQzA==" - }, - "@esbuild/openbsd-arm64@0.25.0": { - "integrity": "sha512-21sUNbq2r84YE+SJDfaQRvdgznTD8Xc0oc3p3iW/a1EVWeNj/SdUCbm5U0itZPQYRuRTW20fPMWMpcrciH2EJw==" - }, - "@esbuild/openbsd-x64@0.25.0": { - "integrity": "sha512-2gwwriSMPcCFRlPlKx3zLQhfN/2WjJ2NSlg5TKLQOJdV0mSxIcYNTMhk3H3ulL/cak+Xj0lY1Ym9ysDV1igceg==" - }, - "@esbuild/sunos-x64@0.25.0": { - "integrity": "sha512-bxI7ThgLzPrPz484/S9jLlvUAHYMzy6I0XiU1ZMeAEOBcS0VePBFxh1JjTQt3Xiat5b6Oh4x7UC7IwKQKIJRIg==" - }, - "@esbuild/win32-arm64@0.25.0": { - "integrity": "sha512-ZUAc2YK6JW89xTbXvftxdnYy3m4iHIkDtK3CLce8wg8M2L+YZhIvO1DKpxrd0Yr59AeNNkTiic9YLf6FTtXWMw==" - }, - "@esbuild/win32-ia32@0.25.0": { - "integrity": "sha512-eSNxISBu8XweVEWG31/JzjkIGbGIJN/TrRoiSVZwZ6pkC6VX4Im/WV2cz559/TXLcYbcrDN8JtKgd9DJVIo8GA==" - }, - "@esbuild/win32-x64@0.25.0": { - "integrity": "sha512-ZENoHJBxA20C2zFzh6AI4fT6RraMzjYw4xKWemRTRmRVtN9c5DcH9r/f2ihEkMjOW5eGgrwCslG/+Y/3bL+DHQ==" - }, - "@jridgewell/gen-mapping@0.3.8": { - "integrity": "sha512-imAbBGkb+ebQyxKgzv5Hu2nmROxoDOXHh80evxdoXNOrvAnVx7zimzc1Oo5h9RlfV4vPXaE2iM5pOFbvOCClWA==", + "@colors/colors@1.5.0": { + "integrity": "sha512-ooWCrlZP11i8GImSjTHYHLkvFDP48nS4+204nGb1RiX/WXYHmJA2III9/e2DWVabCESdW7hBAEzHRqUn9OUVvQ==" + }, + "@esbuild/aix-ppc64@0.28.1": { + "integrity": "sha512-Svl7tq8k/08+p6CXPpRjQ1fKX+1odH/BQbb48fV6fj3CWHhsoIOoY87w1oHXm0qEpkIK3ZfVgp0hed3XBXzXMQ==", + "os": ["aix"], + "cpu": ["ppc64"] + }, + "@esbuild/android-arm64@0.28.1": { + "integrity": "sha512-34EGEbCIAgosYz6goLcopX6Mo7NyGv9tfwEM2/7Ce2VcVRk568iSvniGWcUXIy7wEDR1wzolcxcriFVrWYcwBg==", + "os": ["android"], + "cpu": ["arm64"] + }, + "@esbuild/android-arm@0.28.1": { + "integrity": "sha512-0k2F129Xdio1TdJfzJ8sy1Q47vUD2NnwdhiAf7drUN1EBTfPf4hsFCtmMgu/6m8JSzsBrlmVjudMBQqOfG8usQ==", + "os": ["android"], + "cpu": ["arm"] + }, + "@esbuild/android-x64@0.28.1": { + "integrity": "sha512-dbwY7ltSMDWsRatcRpCnES4F+im88OCUgGZjy52shC7GqHRE/cYlxNbB4Z4UpJswpcc4Qxd2oE/ufM0p61IKng==", + "os": ["android"], + "cpu": ["x64"] + }, + "@esbuild/darwin-arm64@0.28.1": { + "integrity": "sha512-TZbWkQY7kvTAXbXUT7uVACR5cMHsDiSz9z7ZKAX/RTq/WJEk3QyRr0wZpNhBDX+/0CtdqUIJlOiodQcta6tY3Q==", + "os": ["darwin"], + "cpu": ["arm64"] + }, + "@esbuild/darwin-x64@0.28.1": { + "integrity": "sha512-zfdzgK9ACBNZLI/CyHTOx81SyNbM6YXn7rxSgX97VjyiPl9W1i4Ka4fgKECEoFCKGpvBj5qArWIGgQjOwkgskQ==", + "os": ["darwin"], + "cpu": ["x64"] + }, + "@esbuild/freebsd-arm64@0.28.1": { + "integrity": "sha512-wG2EA8ENdEI0qhkSZMjfqrdY+ziCYCPMmtZjjIwOmXFjmyzEHn+UUxk5of+SYsjtfs3VpnlC7QLzSI5hY/rOAw==", + "os": ["freebsd"], + "cpu": ["arm64"] + }, + "@esbuild/freebsd-x64@0.28.1": { + "integrity": "sha512-i7dZ9vQgnvSCzi/rYCXNgtF/U+eKZNJBzu3eTQbRgHnM7tNSizLOkRFAl3qzVc/Op/u5YkHHa4pf/3DOYHthLQ==", + "os": ["freebsd"], + "cpu": ["x64"] + }, + "@esbuild/linux-arm64@0.28.1": { + "integrity": "sha512-yHs+0uc8+nvEAfAfxrWQKK5peSNzBc4PegcMO0EJ2hT71uA7vB8Ihg2e77R2P7SG5uYjPbHlLLmve4LLLRCf0g==", + "os": ["linux"], + "cpu": ["arm64"] + }, + "@esbuild/linux-arm@0.28.1": { + "integrity": "sha512-qVXBOHQS+d5Y722GwJzJUtOLlX7km3CraOaGormF1pDtPd2C/l1SHRPgjLunLGe51Sh5YYWKMFDyV4SxgMQYTQ==", + "os": ["linux"], + "cpu": ["arm"] + }, + "@esbuild/linux-ia32@0.28.1": { + "integrity": "sha512-d1z4ZuP0ajrfz/FhGT4vv278rX8KnPPJx8i5+AtK7TYbx9Le9F1hyzurZpkEyjkGa9dUGhQow4C1NmeGvqxN2w==", + "os": ["linux"], + "cpu": ["ia32"] + }, + "@esbuild/linux-loong64@0.28.1": { + "integrity": "sha512-M5sRjUVZrkm1OAPR3dlOYzNmN+loZKGVi1VUQGrwuqLcbR6qeAz+famMhjASeH3YVKvZz+zT1jlh/keC3Rj/lg==", + "os": ["linux"], + "cpu": ["loong64"] + }, + "@esbuild/linux-mips64el@0.28.1": { + "integrity": "sha512-mRObBZeHh2OxcBFPWE/FjylkRgZdYuiTR3vaTozquCGOH14iP9oN4x4Ge81CoIDYQrXmIxpFumJBu5MtZpnQJQ==", + "os": ["linux"], + "cpu": ["mips64el"] + }, + "@esbuild/linux-ppc64@0.28.1": { + "integrity": "sha512-slScBsMAb3GFDcdrCgLwZtPYRoH2H/youv10QiZyRjmsP48fznoveWytSgCI/R0ZcUgpc0ZhIUEx6LHts8yrfQ==", + "os": ["linux"], + "cpu": ["ppc64"] + }, + "@esbuild/linux-riscv64@0.28.1": { + "integrity": "sha512-kw0owk1o0GFETUJyW0jc0G4Yzs0BHZn0JDZ8JRT088vjJYX777BAs1fDGxAC+q831qOs2DTC96mNsG2opdfyyQ==", + "os": ["linux"], + "cpu": ["riscv64"] + }, + "@esbuild/linux-s390x@0.28.1": { + "integrity": "sha512-/lAIjX8aYFRByhh6L5rYtPEDRqa9de/4V/juOXcta5frjvzXO4/sqEtyytse0g3zZFuWu5cDN0MkLz2qRDD2Ag==", + "os": ["linux"], + "cpu": ["s390x"] + }, + "@esbuild/linux-x64@0.28.1": { + "integrity": "sha512-u/anNYF2mmVOEDwLtnQ1wOr3EZ9sTNGLWrsYGYwHWzGA3Si84IOkHXlbWTD1NB+9/1lcnweYKO54uhxZydNzfA==", + "os": ["linux"], + "cpu": ["x64"] + }, + "@esbuild/netbsd-arm64@0.28.1": { + "integrity": "sha512-oks0DYbLwWMmaakTsCb+zL4E+aHRVLom9IJZOAthMQEPiQmydXHkziYEsGYRx0uNV/IjEKGAV941JzH02pflqw==", + "os": ["netbsd"], + "cpu": ["arm64"] + }, + "@esbuild/netbsd-x64@0.28.1": { + "integrity": "sha512-aeL6lAnN89Hz43Mlh1G8ARasbuoYvSITDEx0tHh5b7jJnHcssqgjy9Yx430GDpmCa6OyrKoS0aNRjKundRizGg==", + "os": ["netbsd"], + "cpu": ["x64"] + }, + "@esbuild/openbsd-arm64@0.28.1": { + "integrity": "sha512-MEFJe5C3R8pwXdZ5Y21oo6m7ePiS0d9pWucn99O/wvyJZChoIQKrQDxKrGeW8F5+T0okTHesAmDeiHDTIq0V/Q==", + "os": ["openbsd"], + "cpu": ["arm64"] + }, + "@esbuild/openbsd-x64@0.28.1": { + "integrity": "sha512-i/ZLIOafE0Z8cI/XANJAixoJL/uRAoS2xOA3rb0xN+KK0K177cMAsQYkzHtBrtMXAKuAc7HGgcWiZ/sRC1Nxgw==", + "os": ["openbsd"], + "cpu": ["x64"] + }, + "@esbuild/openharmony-arm64@0.28.1": { + "integrity": "sha512-ge+Z7EXFNt2BO1oAMsVpiQ8EwndV9i1xXerAeTIK7AtPs3bKFXQM7nlRxDSIUIMeueR1CNXxqztLzdNeReKBJg==", + "os": ["openharmony"], + "cpu": ["arm64"] + }, + "@esbuild/sunos-x64@0.28.1": { + "integrity": "sha512-BEjgtECkL3vY+SaSQ6nzVfiALUeFxpawyp8Jmf5PtYhf1Ug40N1h/hxlhts+f1FvSvarEigdxS3BlSMI2PJLcQ==", + "os": ["sunos"], + "cpu": ["x64"] + }, + "@esbuild/win32-arm64@0.28.1": { + "integrity": "sha512-lCv9eK/H6ZJWbE7bh2nw54CZ9M2nupBxJcTsdk/QQnWkdSjKGuxmmH8/GWrlT1eMmZfn4dGcCjRte397WqfQXA==", + "os": ["win32"], + "cpu": ["arm64"] + }, + "@esbuild/win32-ia32@0.28.1": { + "integrity": "sha512-zvb/mB2bSCoJOpoCBgYKKpX6YM6mJBlBUVUtVj41DlZJVEB6/0CKlRYxP5wWl1C1ILiCoAU5wZZ4q1P3qeS6Eg==", + "os": ["win32"], + "cpu": ["ia32"] + }, + "@esbuild/win32-x64@0.28.1": { + "integrity": "sha512-bm4Mowrv+GXMlpWX++EcXw/iLyd1o3+bJkC2DkWXYVvgZCqD/bSj9ctZeAMC3cIxgjRVR2Dufaiu4YPxr5gW1A==", + "os": ["win32"], + "cpu": ["x64"] + }, + "@jridgewell/gen-mapping@0.3.13": { + "integrity": "sha512-2kkt/7niJ6MgEPxF0bYdQ6etZaA+fQvDcLKckhy1yIQOzaoKjBBjSj63/aLVjYE3qhRt5dvM+uUyfCg6UKCBbA==", "dependencies": [ - "@jridgewell/set-array", "@jridgewell/sourcemap-codec", "@jridgewell/trace-mapping" ] }, + "@jridgewell/remapping@2.3.5": { + "integrity": "sha512-LI9u/+laYG4Ds1TDKSJW2YPrIlcVYOwi2fUC6xB43lueCjgxV4lffOCZCtYFiH6TNOX+tQKXx97T4IKHbhyHEQ==", + "dependencies": [ + "@jridgewell/gen-mapping", + "@jridgewell/trace-mapping" + ] + }, "@jridgewell/resolve-uri@3.1.2": { "integrity": "sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==" }, - "@jridgewell/set-array@1.2.1": { - "integrity": "sha512-R8gLRTZeyp03ymzP/6Lil/28tGeGEzhx1q2k703KGWRAI1VdvPIXdG70VJc2pAMw3NA6JKL5hhFu1sJX0Mnn/A==" + "@jridgewell/sourcemap-codec@1.6.0": { + "integrity": "sha512-T7jf+5zgsZHwNJ4lvQ7/aezbyk0nNX+zJVWpmHA7VYsEx7a7qr5Rg5IbtJFqkgze5Y2sruq1RUY8Q837Od7iFw==" }, - "@jridgewell/sourcemap-codec@1.5.0": { - "integrity": "sha512-gv3ZRaISU3fjPAgNsriBRqGWQL6quFx04YMPW/zD8XMLsU32mhCCbfbO6KZFLjvYpCZ8zyDEgqsgf+PwPaM7GQ==" - }, - "@jridgewell/trace-mapping@0.3.25": { - "integrity": "sha512-vNk6aEwybGtawWmy/PzwnGDOjCkLWSD2wqvjGGAgOAwCGWySYXfYoxt00IJkTF+8Lb57DwOb3Aa0o9CApepiYQ==", + "@jridgewell/trace-mapping@0.3.31": { + "integrity": "sha512-zzNR+SdQSDJzc8joaeP8QQoCQr8NuYx2dIIytl1QeBEZHJ9uW6hebsrYgbz8hJwUQao3TWCMtmfV8Nu1twOLAw==", "dependencies": [ "@jridgewell/resolve-uri", "@jridgewell/sourcemap-codec" ] }, - "@kwsites/file-exists@1.1.1": { - "integrity": "sha512-m9/5YGR18lIwxSFDwfE3oA7bWuq9kdau6ugN4H2rJeyhFQZcG9AgSHkQtSD15a8WvTgfz9aikZMrKPHvbpqFiw==", - "dependencies": [ - "debug" - ] - }, - "@kwsites/promise-deferred@1.1.1": { - "integrity": "sha512-GaHYm+c0O9MjZRu0ongGBRbinu8gVAMd2UZjji6jVmqKtZluZnptXGWhz1E8j8D2HJ3f/yMxKAUC0b+57wncIw==" - }, - "@next/env@14.2.24": { - "integrity": "sha512-LAm0Is2KHTNT6IT16lxT+suD0u+VVfYNQqM+EJTKuFRRuY2z+zj01kueWXPCxbMBDt0B5vONYzabHGUNbZYAhA==" - }, - "@opentelemetry/api@1.9.0": { - "integrity": "sha512-3giAOQvZiH5F9bMlMiv8+GSPMeqg0dbaeo58/0SlA9sxSqZhnUtxzX9/2FzyhS9sWQf5S0GJE0AKBrFqjpeYcg==" - }, - "@sveltejs/acorn-typescript@1.0.5_acorn@8.14.1": { - "integrity": "sha512-IwQk4yfwLdibDlrXVE04jTZYlLnwsTT2PIOQQGNLWfjavGifnk1JD1LcZjZaBTRcxZu2FfPfNLOE04DSu9lqtQ==", - "dependencies": [ - "acorn" - ] - }, - "@types/diff-match-patch@1.0.36": { - "integrity": "sha512-xFdR6tkm0MWvBfO8xXCSsinYxHcqkQUlcHeSpMC2ukzOb6lwQAfDmW+Qt0AvlGd8HpsS28qKsB+oPeJn9I39jg==" - }, - "@types/estree@1.0.6": { - "integrity": "sha512-AYnb1nQyY49te+VRAVgmzfcgjYS91mY5P0TKUDCLEM+gNnA+3T6rWITXRLYCpahpqSQbN5cE+gHpnPyXjHWxcw==" + "@next/env@14.2.35": { + "integrity": "sha512-DuhvCtj4t9Gwrx80dmz2F4t/zKQ4ktN8WrMwOuVzkJfBilwAwGr6v16M5eI8yCuZ63H9TTuEU09Iu2HqkzFPVQ==" }, "@types/node-fetch@2.6.12": { "integrity": "sha512-8nneRWKCg3rMtF69nLQJnOYUcbafYeFSjqkw3jCRLsqkWFlHaoQrr5mXmofFGOx3DKn7UfmBMyov8ySvLRVldA==", "dependencies": [ - "@types/node@22.12.0", + "@types/node", "form-data" ] }, "@types/node@18.19.80": { "integrity": "sha512-kEWeMwMeIvxYkeg1gTc01awpwLbfMRZXdIhwRcakd/KlK53jmRC26LqcbIt7fnAQTu5GzlnWmzA3H6+l1u6xxQ==", "dependencies": [ - "undici-types@5.26.5" - ] - }, - "@types/node@22.12.0": { - "integrity": "sha512-Fll2FZ1riMjNmlmJOdAyY5pUbkftXslB5DgEzlIuNaiWhXd00FhWxVC/r4yV/4wBb9JfImTu+jiSvXTkJ7F/gA==", - "dependencies": [ - "undici-types@6.20.0" + "undici-types" ] }, "@vercel/functions@1.6.0": { "integrity": "sha512-R6FKQrYT5MZs5IE1SqeCJWxMuBdHawFcCZboKKw8p7s+6/mcd55Gx6tWmyKnQTyrSEA04NH73Tc9CbqpEle8RA==" }, - "@vue/compiler-core@3.5.13": { - "integrity": "sha512-oOdAkwqUfW1WqpwSYJce06wvt6HljgY3fGeM9NcVA1HaYOij3mZG9Rkysn0OHuyUAGMbEbARIpsG+LPVlBJ5/Q==", - "dependencies": [ - "@babel/parser", - "@vue/shared", - "entities", - "estree-walker", - "source-map-js" - ] - }, - "@vue/compiler-dom@3.5.13": { - "integrity": "sha512-ZOJ46sMOKUjO3e94wPdCzQ6P1Lx/vhp2RSvfaab88Ajexs0AHeV0uasYhi99WPaogmBlRHNRuly8xV75cNTMDA==", - "dependencies": [ - "@vue/compiler-core", - "@vue/shared" - ] - }, - "@vue/compiler-sfc@3.5.13": { - "integrity": "sha512-6VdaljMpD82w6c2749Zhf5T9u5uLBWKnVue6XWxprDobftnletJ8+oel7sexFfM3qIxNmVE7LSFGTpv6obNyaQ==", - "dependencies": [ - "@babel/parser", - "@vue/compiler-core", - "@vue/compiler-dom", - "@vue/compiler-ssr", - "@vue/shared", - "estree-walker", - "magic-string", - "postcss", - "source-map-js" - ] - }, - "@vue/compiler-ssr@3.5.13": { - "integrity": "sha512-wMH6vrYHxQl/IybKJagqbquvxpWCuVYpoUJfCqFZwa/JY1GdATAQ+TgVtgrwwMZ0D07QhA99rs/EAAWfvG6KpA==", - "dependencies": [ - "@vue/compiler-dom", - "@vue/shared" - ] - }, - "@vue/reactivity@3.5.13": { - "integrity": "sha512-NaCwtw8o48B9I6L1zl2p41OHo/2Z4wqYGGIK1Khu5T7yxrn+ATOixn/Udn2m+6kZKB/J7cuT9DbWWhRxqixACg==", - "dependencies": [ - "@vue/shared" - ] - }, - "@vue/runtime-core@3.5.13": { - "integrity": "sha512-Fj4YRQ3Az0WTZw1sFe+QDb0aXCerigEpw418pw1HBUKFtnQHWzwojaukAs2X/c9DQz4MQ4bsXTGlcpGxU/RCIw==", - "dependencies": [ - "@vue/reactivity", - "@vue/shared" - ] - }, - "@vue/runtime-dom@3.5.13": { - "integrity": "sha512-dLaj94s93NYLqjLiyFzVs9X6dWhTdAlEAciC3Moq7gzAc13VJUdCnjjRurNM6uTLFATRHexHCTu/Xp3eW6yoog==", + "abort-controller@3.0.0": { + "integrity": "sha512-h8lQ8tacZYnR3vNQTgibj+tODHI5/+l06Au2Pcriv/Gmet0eaj4TwWH41sO9wnHDiQsEj19q0drzdWdeAHtweg==", "dependencies": [ - "@vue/reactivity", - "@vue/runtime-core", - "@vue/shared", - "csstype" + "event-target-shim" ] }, - "@vue/server-renderer@3.5.13_vue@3.5.13": { - "integrity": "sha512-wAi4IRJV/2SAW3htkTlB+dHeRmpTiVIK1OGLWV1yeStVSebSQQOwGwIq0D3ZIoBj2C2qpgz5+vX9iEBkTdk5YA==", + "accepts@2.0.0": { + "integrity": "sha512-5cvg6CtKwfgdmVqY1WIiXKc3Q1bkRqGLi+2W/6ao+6Y7gu/RCwRuAhGEzh5B4KlszSuTLgZYuqFqo5bImjNKng==", "dependencies": [ - "@vue/compiler-ssr", - "@vue/shared", - "vue" + "mime-types@3.0.2", + "negotiator" ] }, - "@vue/shared@3.5.13": { - "integrity": "sha512-/hnE/qP5ZoGpol0a5mDi45bOd7t3tjYJBjsgCsivow7D48cJeV5l05RD82lPqi7gRiphZM37rnhW1l6ZoCNNnQ==" - }, - "abort-controller@3.0.0": { - "integrity": "sha512-h8lQ8tacZYnR3vNQTgibj+tODHI5/+l06Au2Pcriv/Gmet0eaj4TwWH41sO9wnHDiQsEj19q0drzdWdeAHtweg==", + "acorn-import-attributes@1.9.5_acorn@8.18.0": { + "integrity": "sha512-n02Vykv5uA3eHGM/Z2dQrcD56kL8TyDb2p1+0P83PClMnC/nc+anbQRhIOWnSq4Ke/KvDPrY3C9hDtC/A3eHnQ==", "dependencies": [ - "event-target-shim" + "acorn" ] }, - "acorn@8.14.1": { - "integrity": "sha512-OvQ/2pUDKmgfCg++xsTX1wGxfTaszcHVcTctW4UJB4hibJx2HXxxO5UmVgyjMa+ZDsiaf5wWLXYpRWMmBI0QHg==" + "acorn@8.18.0": { + "integrity": "sha512-lGq+9yr1/GuAWaVYIHRjvvySG5/4VfKIvC8EWxStPdcDh/Ka7FG3twP6v4d5BkravUilhIAsG4Qj83t02LWUPQ==", + "bin": true }, "agentkeepalive@4.6.0": { "integrity": "sha512-kja8j7PjmncONqaTsB8fQ+wE2mSU2DJ9D4XKoJ5PFWIdRMa6SLSN1ff4mOr4jCbfRSsxR4keIiySJU0N9T5hIQ==", @@ -419,27 +334,8 @@ "humanize-ms" ] }, - "ai@3.4.33_zod@3.24.2": { - "integrity": "sha512-plBlrVZKwPoRTmM8+D1sJac9Bq8eaa2jiZlHLZIWekKWI1yMWYZvCCEezY9ASPwRhULYDJB2VhKOBUUeg3S5JQ==", - "dependencies": [ - "@ai-sdk/provider@0.0.26", - "@ai-sdk/provider-utils", - "@ai-sdk/react", - "@ai-sdk/solid", - "@ai-sdk/svelte", - "@ai-sdk/ui-utils", - "@ai-sdk/vue", - "@opentelemetry/api", - "eventsource-parser", - "json-schema", - "jsondiffpatch", - "secure-json-parse", - "zod", - "zod-to-json-schema" - ] - }, - "ajv@8.17.1": { - "integrity": "sha512-B/gBuNg5SiMTrPkC+A2+cW0RszwxYmn6VYxB/inlBStS5nx6xHIt/ehKRhIMhqusl7a8LjQoZnjCs5vhwxOQ1g==", + "ajv@8.20.0": { + "integrity": "sha512-Thbli+OlOj+iMPYFBVBfJ3OmCAnaSyNn4M1vz9T6Gka5Jt9ba/HIR56joy65tY6kx/FCF5VXNB819Y7/GUrBGA==", "dependencies": [ "fast-deep-equal", "fast-uri", @@ -450,17 +346,12 @@ "ansi-regex@5.0.1": { "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==" }, - "ansi-styles@4.3.0": { - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "dependencies": [ - "color-convert" - ] - }, "argparse@2.0.1": { "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==" }, - "aria-query@5.3.2": { - "integrity": "sha512-COROpnaoap1E2F000S62r6A60uHZnmlvomhfyT2DlTcrY1OrBKn2UhH7qn5wTC9zMvD0AY7csdPSNwKP+7WiQw==" + "astring@1.9.0": { + "integrity": "sha512-LElXdjswlqjWrPpJFg1Fx4wpkOCxj1TDHlSV4PlaRxHGWko024xICaa97ZkMfs6DRKlCguiAI+rbXv5GWwXIkg==", + "bin": true }, "asynckit@0.4.0": { "integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==" @@ -477,49 +368,82 @@ "mustache", "openai", "zod", - "zod-to-json-schema" + "zod-to-json-schema@3.25.2_zod@3.24.2" ] }, - "axobject-query@4.1.0": { - "integrity": "sha512-qIj0G9wZbMGNLjLmg1PT6v2mE9AH2zlnADJD/2tC6E00hgmhUOfEB6greHPAfLRSufHqROIUTkw6E+M3lH0PTQ==" - }, - "balanced-match@1.0.2": { - "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==" + "balanced-match@4.0.4": { + "integrity": "sha512-BLrgEcRTwX2o6gGxGOCNyMvGSp35YofuYzw9h1IMTRmKqttAZZVU67bdb9Pr2vUHA8+j3i2tJfjO6C6+4myGTA==" }, "binary-search@1.3.6": { "integrity": "sha512-nbE1WxOTTrUWIfsfZ4aHGYu5DOuNkbxGokjV6Z2kxfJK3uaAb8zNK1muzOeipoLHZjInT4Br88BHpzevc681xA==" }, - "brace-expansion@2.0.1": { - "integrity": "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==", + "body-parser@2.3.0": { + "integrity": "sha512-2cGmJupaNgg+QUwVLAucDuWuoMZ6EX9iHDRswZ5lsNYEmwPaRknMPCLZz07yTzVq/83p4o/wzbDZbBrTvGGTIw==", + "dependencies": [ + "bytes", + "content-type@2.1.0", + "debug", + "http-errors", + "iconv-lite", + "on-finished", + "qs", + "raw-body", + "type-is" + ] + }, + "brace-expansion@5.0.9": { + "integrity": "sha512-ScQ4IuvIEF1TMlP7Zt+vjJ//9zlPb2SDcxWxM3bk8s6t6GGdJ7KO1dCcTidOPJKePW30LE/2cT7wCyPho9/Wxg==", "dependencies": [ "balanced-match" ] }, - "braintrust@0.0.187_zod@3.24.2": { - "integrity": "sha512-sDq/IHFqsbA6iJoMnf747uXT7SRCS0x925+h3+DSQIPdtxK0xp41/k/jTaVjpKaeCQjdp3jyQ8lpGiYqVtE6rQ==", + "braintrust@3.32.0_zod@3.25.76": { + "integrity": "sha512-dAY6Q3XYMyqDW5A9+8RNUKBqDhBbDVB55xrKpMaCXvnTz1psXYSlkzFta6sox+WHs6PHZR539+lufOexoUfRNg==", "dependencies": [ - "@ai-sdk/provider@1.0.10", - "@braintrust/core", "@next/env", "@vercel/functions", - "ai", + "acorn", + "acorn-import-attributes", + "ajv", "argparse", - "chalk@4.1.2", + "astring", + "cjs-module-lexer", "cli-progress", + "cli-table3", + "cors", "dotenv", "esbuild", + "esquery", "eventsource-parser", - "graceful-fs", + "express", + "http-errors", + "meriyah", "minimatch", + "module-details-from-path", "mustache", "pluralize", - "simple-git", - "slugify", + "semifies", "source-map", - "uuid", + "termi-link", + "unplugin", + "uuid@11.1.1", "zod", - "zod-to-json-schema" - ] + "zod-to-json-schema@3.25.2_zod@3.25.76" + ], + "optionalDependencies": [ + "@braintrust/bt-darwin-arm64", + "@braintrust/bt-darwin-x64", + "@braintrust/bt-linux-arm64", + "@braintrust/bt-linux-x64", + "@braintrust/bt-linux-x64-musl", + "@braintrust/bt-win32-arm64", + "@braintrust/bt-win32-x64" + ], + "scripts": true, + "bin": true + }, + "bytes@3.1.2": { + "integrity": "sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg==" }, "call-bind-apply-helpers@1.0.2": { "integrity": "sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ==", @@ -528,37 +452,34 @@ "function-bind" ] }, - "chalk@4.1.2": { - "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "call-bound@1.0.4": { + "integrity": "sha512-+ys997U96po4Kx/ABpBCqhA9EuxJaQWDQg7295H4hBphv3IZg0boBKuwYpt4YXp6MZ5AmZQnU/tyMTlRpaSejg==", "dependencies": [ - "ansi-styles", - "supports-color" + "call-bind-apply-helpers", + "get-intrinsic" ] }, - "chalk@5.4.1": { - "integrity": "sha512-zgVZuo2WcZgfUEmsn6eO3kINexW8RAE4maiQ8QNs8CtpPCSyMiYsULR3HQYkm3w8FIA3SberyMJMSldGsW+U3w==" - }, "cheminfo-types@1.8.1": { "integrity": "sha512-FRcpVkox+cRovffgqNdDFQ1eUav+i/Vq/CUd1hcfEl2bevntFlzznL+jE8g4twl6ElB7gZjCko6pYpXyMn+6dA==" }, + "cjs-module-lexer@2.2.1": { + "integrity": "sha512-Ca8swihM+/4yKecYHY52kgJd300hi2lADU/a1RxNTRe+RJ9jvqQlESpbz9DnG9mowez8qwXHB8qYdIUw9e+F5Q==" + }, "cli-progress@3.12.0": { "integrity": "sha512-tRkV3HJ1ASwm19THiiLIXLO7Im7wlTuKnvkYaTkyoAPefqjNg7W7DHKUlGRxy9vxDvbyCYQkQozvptuMkGCg8A==", "dependencies": [ "string-width" ] }, - "clsx@2.1.1": { - "integrity": "sha512-eYm0QWBtUrBWZWG0d386OGAw16Z995PiOVo2B7bjWSbHedGl5e0ZWaq65kOGgUSNesEIDkB9ISbTg/JK9dhCZA==" - }, - "color-convert@2.0.1": { - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "cli-table3@0.6.5": { + "integrity": "sha512-+W/5efTR7y5HRD7gACw9yQjqMVvEMLBHmboM/kPWam+H+Hmyrgjh6YncVKK122YZkXrLudzTuAukUw9FnMf7IQ==", "dependencies": [ - "color-name" + "string-width" + ], + "optionalDependencies": [ + "@colors/colors" ] }, - "color-name@1.1.4": { - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==" - }, "combined-stream@1.0.8": { "integrity": "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==", "dependencies": [ @@ -588,11 +509,30 @@ "validate.io-function" ] }, - "csstype@3.1.3": { - "integrity": "sha512-M1uQkMl8rQK/szD0LNhtqxIPLpimGm8sOBwU7lLnCpSbTyY3yeU1Vc7l4KT5zT4s/yOxHH5O7tIuuLOCnLADRw==" + "content-disposition@1.1.0": { + "integrity": "sha512-5jRCH9Z/+DRP7rkvY83B+yGIGX96OYdJmzngqnw2SBSxqCFPd0w2km3s5iawpGX8krnwSGmF0FW5Nhr0Hfai3g==" + }, + "content-type@1.0.5": { + "integrity": "sha512-nTjqfcBFEipKdXCv4YDQWCfmcLZKm81ldF0pAopTvyrFGVbcR6P/VAAd5G7N+0tTr8QqiU0tFadD6FK4NtJwOA==" + }, + "content-type@2.1.0": { + "integrity": "sha512-mj7UPXE0jaqaOsukNZRUEfEi2AcL7C/vwmwcHV0O97eO1E1pxBZuyjlZrx5seTaNBg1U6+o35wpa35Qfcc+7ag==" }, - "debug@4.4.0": { - "integrity": "sha512-6WTZ/IxCY/T6BALoZHaE4ctp9xm+Z5kY/pzYaCHRFeyVhojxlrm+46y68HA6hr0TcwEssoxNiDEUJQjfPZ/RYA==", + "cookie-signature@1.2.2": { + "integrity": "sha512-D76uU73ulSXrD1UXF4KE2TMxVVwhsnCgfAyTg9k8P6KGZjlXKrOLe4dJQKI3Bxi5wjesZoFXJWElNWBjPZMbhg==" + }, + "cookie@0.7.2": { + "integrity": "sha512-yki5XnKuf750l50uGTllt6kKILY4nQ1eNIQatoXEByZ5dWgnKqbnqmTrBE5B4N7lrMJKQ2ytWMiTO2o0v6Ew/w==" + }, + "cors@2.8.6": { + "integrity": "sha512-tJtZBBHA6vjIAaF6EnIaq6laBBP9aq/Y3ouVJjEfoHbRBcHBAHYcMh/w8LDrk2PvIMMq8gmopa5D4V8RmbrxGw==", + "dependencies": [ + "object-assign", + "vary" + ] + }, + "debug@4.4.3": { + "integrity": "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==", "dependencies": [ "ms" ] @@ -600,14 +540,11 @@ "delayed-stream@1.0.0": { "integrity": "sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==" }, - "dequal@2.0.3": { - "integrity": "sha512-0je+qPKHEMohvfRTCEo3CrPG6cAzAYgmzKyxRiYSSDkS6eGJdyVJm7WaYA5ECaAD9wLB2T4EEeymA5aFVcYXCA==" - }, - "diff-match-patch@1.0.5": { - "integrity": "sha512-IayShXAgj/QMXgB0IWmKx+rOPuGMhqm5w6jvFxmVenXKIzRqTAAsbBPT3kWQeGANj3jGgvcvv4yK6SxqYmikgw==" + "depd@2.0.0": { + "integrity": "sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw==" }, - "dotenv@16.4.7": { - "integrity": "sha512-47qPchRCykZC03FhkYAhrvwU4xDBFIj1QPqaarj6mdM/hgUzfPHcpkHJOn3mJAufFeeAxAzeGsr5X0M4k6fLZQ==" + "dotenv@16.6.1": { + "integrity": "sha512-uBq4egWHTcTt33a72vpSG0z3HnPuIl6NqYcTrKEg2azoEyl2hpW0zqlxysq2pK9HlDIHyHyakeYaYnSAwd8bow==" }, "dunder-proto@1.0.1": { "integrity": "sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==", @@ -617,11 +554,14 @@ "gopd" ] }, + "ee-first@1.1.1": { + "integrity": "sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow==" + }, "emoji-regex@8.0.0": { "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==" }, - "entities@4.5.0": { - "integrity": "sha512-V0hjH4dGPh9Ao5p0MoRY6BVqtwCjhz6vI5LT8AJ55H+4g9/4vbHx1I54fS0XuclLhDHArPQCiMjDxjaL8fPxhw==" + "encodeurl@2.0.0": { + "integrity": "sha512-Q0n9HRi4m6JuGIV1eFlmvJB7ZEVxu93IrMyiMsGC0lrMJMWzRgx6WGquyfQgZVb31vhGgXnfmPNNXmxnOkRBrg==" }, "es-define-property@1.0.1": { "integrity": "sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g==" @@ -644,9 +584,9 @@ "hasown" ] }, - "esbuild@0.25.0": { - "integrity": "sha512-BXq5mqc8ltbaN34cDqWuYKyNhX8D/Z0J1xdtdQ8UcIIIyJyz+ZMKUt58tF3SrZ85jcfN/PZYhjR5uDQAYNVbuw==", - "dependencies": [ + "esbuild@0.28.1": { + "integrity": "sha512-HrJrvZv5ayxBzPfwphOoNzkzOIIlifzk0KJrGK2c8R4+LKpMtpYLQeUdjnwjWv/LZlkH2laZk+4w78pi99D4Vw==", + "optionalDependencies": [ "@esbuild/aix-ppc64", "@esbuild/android-arm", "@esbuild/android-arm64", @@ -668,23 +608,29 @@ "@esbuild/netbsd-x64", "@esbuild/openbsd-arm64", "@esbuild/openbsd-x64", + "@esbuild/openharmony-arm64", "@esbuild/sunos-x64", "@esbuild/win32-arm64", "@esbuild/win32-ia32", "@esbuild/win32-x64" - ] + ], + "scripts": true, + "bin": true }, - "esm-env@1.2.2": { - "integrity": "sha512-Epxrv+Nr/CaL4ZcFGPJIYLWFom+YeV1DqMLHJoEd9SYRxNbaFruBwfEX/kkHUJf55j2+TUbmDcmuilbP1TmXHA==" + "escape-html@1.0.3": { + "integrity": "sha512-NiSupZ4OeuGwr68lGIeym/ksIZMJodUGOSCZ/FSnTxcrekbvqrgdUxlJOMpijaKZVjAJrWrGs/6Jy8OMuyj9ow==" }, - "esrap@1.4.5": { - "integrity": "sha512-CjNMjkBWWZeHn+VX+gS8YvFwJ5+NDhg8aWZBSFJPR8qQduDNjbJodA2WcwCm7uQa5Rjqj+nZvVmceg1RbHFB9g==", + "esquery@1.7.0": { + "integrity": "sha512-Ap6G0WQwcU/LHsvLwON1fAQX9Zp0A2Y6Y/cJBl9r/JbW90Zyg4/zbG6zzKa2OTALELarYHmKu0GhpM5EO+7T0g==", "dependencies": [ - "@jridgewell/sourcemap-codec" + "estraverse" ] }, - "estree-walker@2.0.2": { - "integrity": "sha512-Rfkk/Mp/DL7JVje3u18FxFujQlTNR2q6QfMSMB7AvCBx91NGj/ba3kCfza0f6dVDbw7YlRf/nDrn7pQrCCyQ/w==" + "estraverse@5.3.0": { + "integrity": "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==" + }, + "etag@1.8.1": { + "integrity": "sha512-aIL5Fx7mawVa300al2BnEE4iNvo1qETxLrPI/o05L7z6go7fCw1J6EQmbK4FmJ2AS7kgVF/KEZWufBfdClMcPg==" }, "event-target-shim@5.0.1": { "integrity": "sha512-i/2XbnSz/uxRCU6+NdVJgKWDTM427+MqYbkQzD321DuCQJUqOuJKIA0IM2+W2xtYHdKOmZ4dR6fExsd4SXL+WQ==" @@ -692,6 +638,39 @@ "eventsource-parser@1.1.2": { "integrity": "sha512-v0eOBUbiaFojBu2s2NPBfYUoRR9GjcDNvCXVaqEf5vVfpIAh9f8RCo4vXTP8c63QRKCFwoLpMpTdPwwhEKVgzA==" }, + "express@5.2.1": { + "integrity": "sha512-hIS4idWWai69NezIdRt2xFVofaF4j+6INOpJlVOLDO8zXGpUVEVzIYk12UUi2JzjEzWL3IOAxcTubgz9Po0yXw==", + "dependencies": [ + "accepts", + "body-parser", + "content-disposition", + "content-type@1.0.5", + "cookie", + "cookie-signature", + "debug", + "depd", + "encodeurl", + "escape-html", + "etag", + "finalhandler", + "fresh", + "http-errors", + "merge-descriptors", + "mime-types@3.0.2", + "on-finished", + "once", + "parseurl", + "proxy-addr", + "qs", + "range-parser", + "router", + "send", + "serve-static", + "statuses", + "type-is", + "vary" + ] + }, "fast-deep-equal@3.1.3": { "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==" }, @@ -701,6 +680,17 @@ "fft.js@4.0.4": { "integrity": "sha512-f9c00hphOgeQTlDyavwTtu6RiK8AIFjD6+jvXkNkpeQ7rirK3uFWVpalkoS4LAwbdX7mfZ8aoBfFVQX1Re/8aw==" }, + "finalhandler@2.1.1": { + "integrity": "sha512-S8KoZgRZN+a5rNwqTxlZZePjT/4cnm0ROV70LedRHZ0p8u9fRID0hJUZQpkKLzro8LfmC8sx23bY6tVNxv8pQA==", + "dependencies": [ + "debug", + "encodeurl", + "escape-html", + "on-finished", + "parseurl", + "statuses" + ] + }, "form-data-encoder@1.7.2": { "integrity": "sha512-qfqtYan3rxrnCk1VYaA4H+Ms9xdpPqvLZa6xmMgFvhO32x7/3J/ExcTd6qpxM0vH2GdMI+poehyBZvqfMTto8A==" }, @@ -710,7 +700,7 @@ "asynckit", "combined-stream", "es-set-tostringtag", - "mime-types" + "mime-types@2.1.35" ] }, "formdata-node@4.4.1": { @@ -720,6 +710,12 @@ "web-streams-polyfill@4.0.0-beta.3" ] }, + "forwarded@0.2.0": { + "integrity": "sha512-buRG0fpBtRHSTCOASe6hD258tEubFoRLb4ZNA6NxMVHNw2gOcwHo9wyablzMzOA5z9xA9L1KNjk/Nt6MT9aYow==" + }, + "fresh@2.0.0": { + "integrity": "sha512-Rx/WycZ60HOaqLKAi6cHRKKI7zxWbJ31MhntmtwMoaTeF7XFH9hhBp8vITaMidfljRQ6eYWCKkaTK+ykVJHP2A==" + }, "function-bind@1.1.2": { "integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==" }, @@ -748,12 +744,6 @@ "gopd@1.2.0": { "integrity": "sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg==" }, - "graceful-fs@4.2.11": { - "integrity": "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==" - }, - "has-flag@4.0.0": { - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==" - }, "has-symbols@1.1.0": { "integrity": "sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ==" }, @@ -769,26 +759,45 @@ "function-bind" ] }, + "http-errors@2.0.1": { + "integrity": "sha512-4FbRdAX+bSdmo4AUFuS0WNiPz8NgFt+r8ThgNWmlrjQjt1Q7ZR9+zTlce2859x4KSXrwIsaeTqDoKQmtP8pLmQ==", + "dependencies": [ + "depd", + "inherits", + "setprototypeof", + "statuses", + "toidentifier" + ] + }, "humanize-ms@1.2.1": { "integrity": "sha512-Fl70vYtsAFb/C06PTS9dZBo7ihau+Tu/DNCk/OyHhea07S+aeMWpFFkUaXRa8fI+ScZbEI8dfSxwY7gxZ9SAVQ==", "dependencies": [ "ms" ] }, + "iconv-lite@0.7.3": { + "integrity": "sha512-IKXpvIzjnC9XTAUbVBcMfGS0EPaIXtW6v+zr+RRp+hqULEpo0owZax6wyRwPOJbWbzjYspQwusTsfVr0ifh4uQ==", + "dependencies": [ + "safer-buffer" + ] + }, + "inherits@2.0.4": { + "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==" + }, "install@0.13.0": { "integrity": "sha512-zDml/jzr2PKU9I8J/xyZBQn8rPCAY//UOYNmR01XwNwyfhEWObo2SWfSl1+0tm1u6PhxLwDnfsT/6jB7OUxqFA==" }, + "ipaddr.js@1.9.1": { + "integrity": "sha512-0KI/607xoxSToH7GjN1FfSbLoU0+btTicjsQSWQlh/hZykN8KpmMf7uYwPW3R+akZ6R/w18ZlXSHBYXiYUPO3g==" + }, "is-any-array@2.0.1": { "integrity": "sha512-UtilS7hLRu++wb/WBAw9bNuP1Eg04Ivn1vERJck8zJthEvXCBEBpGR/33u/xLKWEQf95803oalHrVDptcAvFdQ==" }, "is-fullwidth-code-point@3.0.0": { "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==" }, - "is-reference@3.0.3": { - "integrity": "sha512-ixkJoqQvAP88E6wLydLGGqCJsrFUnqoH6HnaczB8XmDH1oaWU+xxdptvikTgaEhtZ53Ky6YXiBuUI2WXLMCwjw==", - "dependencies": [ - "@types/estree" - ] + "is-promise@4.0.0": { + "integrity": "sha512-hvpoI6korhJMnej285dSg6nu1+e6uxs7zG3BYAm5byqDsgJNWwxzM6z6iZiAgQR4TJ30JmBTOwqZUw3WlyH3AQ==" }, "js-levenshtein@1.1.6": { "integrity": "sha512-X2BB11YZtrRqY4EnQcLX5Rh373zbK4alC1FW7D7MBhL2gtcC17cTnr6DmfHZeS0s2rTHjUTMMHfG7gO8SSdw+g==" @@ -797,22 +806,12 @@ "integrity": "sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==", "dependencies": [ "argparse" - ] + ], + "bin": true }, "json-schema-traverse@1.0.0": { "integrity": "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==" }, - "json-schema@0.4.0": { - "integrity": "sha512-es94M3nTIfsEPisRafak+HDLfHXnKBhV3vU5eqPcS3flIWqcxJWgXHXiey3YrpaNsanY5ei1VoYEbOzijuq9BA==" - }, - "jsondiffpatch@0.6.0": { - "integrity": "sha512-3QItJOXp2AP1uv7waBkao5nCvhEv+QmJAd38Ybq7wNI74Q+BBmnLn4EDKz6yI9xGAIQoUF87qHt+kc1IVxB4zQ==", - "dependencies": [ - "@types/diff-match-patch", - "chalk@5.4.1", - "diff-match-patch" - ] - }, "linear-sum-assignment@1.0.7": { "integrity": "sha512-jfLoSGwZNyjfY8eK4ayhjfcIu3BfWvP6sWieYzYI3AWldwXVoWEz1gtrQL10v/8YltYLBunqNjeVFXPMUs+MJg==", "dependencies": [ @@ -822,29 +821,38 @@ "ml-spectra-processing" ] }, - "locate-character@3.0.0": { - "integrity": "sha512-SW13ws7BjaeJ6p7Q6CO2nchbYEc3X3J6WrmTTDto7yMPqVSZTUyY5Tjbid+Ab8gLnATtygYtiDIJGQRRn2ZOiA==" - }, - "magic-string@0.30.17": { - "integrity": "sha512-sNPKHvyjVf7gyjwS4xGTaW/mCnF8wnjtifKBEhxfZ7E/S8tQ0rssrwGNn6q8JH/ohItJfSQp9mBtQYuTlH5QnA==", - "dependencies": [ - "@jridgewell/sourcemap-codec" - ] - }, "math-intrinsics@1.1.0": { "integrity": "sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==" }, + "media-typer@1.1.1": { + "integrity": "sha512-yz3xRaG20c6/BOzvYoDaGtPmGscs7YivItZEEqe6GbwNfHuxu9YNmvnEkMzKldAGY4/80pRcQRZSEnhquk9XuQ==" + }, + "merge-descriptors@2.0.0": { + "integrity": "sha512-Snk314V5ayFLhp3fkUREub6WtjBfPdCPY1Ln8/8munuLuiYhsABgBVWsozAG+MWMbVEvcdcpbi9R7ww22l9Q3g==" + }, + "meriyah@6.1.4": { + "integrity": "sha512-Sz8FzjzI0kN13GK/6MVEsVzMZEPvOhnmmI1lU5+/1cGOiK3QUahntrNNtdVeihrO7t9JpoH75iMNXg6R6uWflQ==" + }, "mime-db@1.52.0": { "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==" }, + "mime-db@1.54.0": { + "integrity": "sha512-aU5EJuIN2WDemCcAp2vFBfp/m4EAhWJnUNSSw0ixs7/kXbd6Pg64EmwJkNdFhB8aWt1sH2CTXrLxo/iAGV3oPQ==" + }, "mime-types@2.1.35": { "integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==", "dependencies": [ - "mime-db" + "mime-db@1.52.0" ] }, - "minimatch@9.0.5": { - "integrity": "sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow==", + "mime-types@3.0.2": { + "integrity": "sha512-Lbgzdk0h4juoQ9fCKXW4by0UJqj+nOOrI9MJ1sSj4nI8aI2eo1qmvQEie4VD1glsS250n15LsWsYtCugiStS5A==", + "dependencies": [ + "mime-db@1.54.0" + ] + }, + "minimatch@10.2.6": { + "integrity": "sha512-vpLQEs+VLCr1nU0BXS07maYoFwlDAH0gngQuuttxIwutDFEMHq2blX+8vpgxDdK3J1PwjCJiep77OitTZ4Ll1A==", "dependencies": [ "brace-expansion" ] @@ -890,17 +898,25 @@ "ml-xsadd@3.0.1": { "integrity": "sha512-Fz2q6dwgzGM8wYKGArTUTZDGa4lQFA2Vi6orjGeTVRy22ZnQFKlJuwS9n8NRviqz1KHAHAzdKJwbnYhdo38uYg==" }, + "module-details-from-path@1.0.4": { + "integrity": "sha512-EGWKgxALGMgzvxYF1UyGTy0HXX/2vHLkw6+NvDKW2jypWbHpjQuj4UMcqQWXHERJhVGKikolT06G3bcKe4fi7w==" + }, "ms@2.1.3": { "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==" }, "mustache@4.2.0": { - "integrity": "sha512-71ippSywq5Yb7/tVYyGbkBggbU8H3u5Rz56fH60jGFgr8uHwxs+aSKeqmluIVzM0m0kB7xQjKS6qPfd0b2ZoqQ==" + "integrity": "sha512-71ippSywq5Yb7/tVYyGbkBggbU8H3u5Rz56fH60jGFgr8uHwxs+aSKeqmluIVzM0m0kB7xQjKS6qPfd0b2ZoqQ==", + "bin": true }, - "nanoid@3.3.9": { - "integrity": "sha512-SppoicMGpZvbF1l3z4x7No3OlIjP7QJvC9XR7AhZr1kL133KHnKPztkKDc+Ir4aJ/1VhTySrtKhrsycmrMQfvg==" + "negotiator@1.1.0": { + "integrity": "sha512-NMPBRMJgiQHjbd8phG3Vebdx4kZ1H121rbl5IkMqeOsahptB9BKo/d7oJ3zTXqTgagn2bWlNSXkh0QUGM31RYg==", + "dependencies": [ + "content-type@2.1.0" + ] }, "node-domexception@1.0.0": { - "integrity": "sha512-/jKZoMpw0F8GRwl4/eLROPA3cfcXtLApP0QzLmUT/HuPCZWyB7IY9ZrMeKw2O/nFIqPQB3PVM9aYm0F312AXDQ==" + "integrity": "sha512-/jKZoMpw0F8GRwl4/eLROPA3cfcXtLApP0QzLmUT/HuPCZWyB7IY9ZrMeKw2O/nFIqPQB3PVM9aYm0F312AXDQ==", + "deprecated": true }, "node-fetch@2.7.0": { "integrity": "sha512-c4FRfUm/dbcWZ7U+1Wq0AwCyFL+3nt2bEw05wfxSz+DWpWsitgmSgYmy2dQdWyKC1694ELPqMs/YzUSNozLt8A==", @@ -908,10 +924,28 @@ "whatwg-url" ] }, + "object-assign@4.1.1": { + "integrity": "sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==" + }, + "object-inspect@1.13.4": { + "integrity": "sha512-W67iLl4J2EXEGTbfeHCffrjDfitvLANg0UlX3wFUUSTx92KXRFegMHUVgSqE+wvhAbi4WqjGg9czysTV2Epbew==" + }, + "on-finished@2.4.1": { + "integrity": "sha512-oVlzkg3ENAhCk2zdv7IJwd/QUD4z2RxRwpkcGY8psCVcCYZNq4wYnVWALHM+brtuJjePWiYF/ClmuDr8Ch5+kg==", + "dependencies": [ + "ee-first" + ] + }, + "once@1.4.0": { + "integrity": "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==", + "dependencies": [ + "wrappy" + ] + }, "openai@4.47.1": { "integrity": "sha512-WWSxhC/69ZhYWxH/OBsLEirIjUcfpQ5+ihkXKp06hmeYXgBBIUCa9IptMzYx6NdkiOCsSGYCnTIsxaic3AjRCQ==", "dependencies": [ - "@types/node@18.19.80", + "@types/node", "@types/node-fetch", "abort-controller", "agentkeepalive", @@ -919,7 +953,8 @@ "formdata-node", "node-fetch", "web-streams-polyfill@3.3.3" - ] + ], + "bin": true }, "openapi3-ts@4.4.0": { "integrity": "sha512-9asTNB9IkKEzWMcHmVZE7Ts3kC9G7AFHfs8i7caD8HbI76gEjdkId4z/AkP83xdZsH7PLAnnbl47qZkXuxpArw==", @@ -927,129 +962,187 @@ "yaml" ] }, - "picocolors@1.1.1": { - "integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==" + "parseurl@1.3.3": { + "integrity": "sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ==" + }, + "path-to-regexp@8.4.2": { + "integrity": "sha512-qRcuIdP69NPm4qbACK+aDogI5CBDMi1jKe0ry5rSQJz8JVLsC7jV8XpiJjGRLLol3N+R5ihGYcrPLTno6pAdBA==" + }, + "picomatch@4.0.7": { + "integrity": "sha512-qcJu88Q2IWqJsDD529JKMdwGm/dvInW4HvQnRwiH9JtihJvzGOscDtHE3x1pBKeUOTysQ8kVmLnJ2kJu7yhcGA==" }, "pluralize@8.0.0": { "integrity": "sha512-Nc3IT5yHzflTfbjgqWcCPpo7DaKy4FnpB0l/zCAW0Tc7jxAiuqSxHasntB3D7887LSrA93kDJ9IXovxJYxyLCA==" }, - "postcss@8.5.3": { - "integrity": "sha512-dle9A3yYxlBSrt8Fu+IpjGT8SY8hN0mlaA6GY8t0P5PjIOZemULz/E2Bnm/2dcUOena75OTNkHI76uZBNUUq3A==", + "proxy-addr@2.0.7": { + "integrity": "sha512-llQsMLSUDUPT44jdrU/O37qlnifitDP+ZwrmmZcoSKyLKvtZxpyV0n2/bD/N4tBAAZ/gJEdZU7KMraoK1+XYAg==", + "dependencies": [ + "forwarded", + "ipaddr.js" + ] + }, + "qs@6.16.0": { + "integrity": "sha512-h6fhOIaRrID2CbEY2fqs+7t+UXZo+MLAnU5gRIq85uFtdiUPCdsApMlHhXogKVM4HM2DVbIjGNTTYH2OcmP1vA==", "dependencies": [ - "nanoid", - "picocolors", - "source-map-js" + "es-define-property", + "side-channel" ] }, - "react@19.0.0": { - "integrity": "sha512-V8AVnmPIICiWpGfm6GLzCR/W5FXLchHop40W4nXBmdlEceh16rCN8O8LNWm5bh5XUX91fh7KpA+W0TgMKmgTpQ==" + "range-parser@1.3.0": { + "integrity": "sha512-hek2mFQpPuI4E1BBKrSto+BU3e3x4xuarsbiwr3+lf7p44juvFMV0XFWQAP3xUyqXA4RrXLIoaSUGbSt056ZMw==" + }, + "raw-body@3.0.2": { + "integrity": "sha512-K5zQjDllxWkf7Z5xJdV0/B0WTNqx6vxG70zJE4N0kBs4LovmEYWJzQGxC9bS9RAKu3bgM40lrd5zoLJ12MQ5BA==", + "dependencies": [ + "bytes", + "http-errors", + "iconv-lite", + "unpipe" + ] }, "require-from-string@2.0.2": { "integrity": "sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw==" }, - "secure-json-parse@2.7.0": { - "integrity": "sha512-6aU+Rwsezw7VR8/nyvKTx8QpWH9FrcYiXXlqC4z5d5XQBDRqtbfsRjnwGyqbi3gddNtWHuEk9OANUotL26qKUw==" - }, - "simple-git@3.27.0": { - "integrity": "sha512-ivHoFS9Yi9GY49ogc6/YAi3Fl9ROnF4VyubNylgCkA+RVqLaKWnDSzXOVzya8csELIaWaYNutsEuAhZrtOjozA==", + "router@2.2.0": { + "integrity": "sha512-nLTrUKm2UyiL7rlhapu/Zl45FwNgkZGaCpZbIHajDYgwlJCOzLSk+cIPAnsEqV955GjILJnKbdQC1nVPz+gAYQ==", "dependencies": [ - "@kwsites/file-exists", - "@kwsites/promise-deferred", - "debug" + "debug", + "depd", + "is-promise", + "parseurl", + "path-to-regexp" ] }, - "slugify@1.6.6": { - "integrity": "sha512-h+z7HKHYXj6wJU+AnS/+IH8Uh9fdcX1Lrhg1/VMdf9PwoBQXFcXiAdsy2tSK0P6gKwJLXp02r90ahUCqHk9rrw==" - }, - "source-map-js@1.2.1": { - "integrity": "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==" + "safer-buffer@2.1.2": { + "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==" }, - "source-map@0.7.4": { - "integrity": "sha512-l3BikUxvPOcn5E74dZiq5BGsTb5yEwhaTSzccU6t4sDOH8NWJCstKO5QT2CvtFoK6F0saL7p9xHAqHOlCPJygA==" + "semifies@1.0.0": { + "integrity": "sha512-xXR3KGeoxTNWPD4aBvL5NUpMTT7WMANr3EWnaS190QVkY52lqqcVRD7Q05UVbBhiWDGWMlJEUam9m7uFFGVScw==" }, - "sswr@2.2.0_svelte@5.22.6__acorn@8.14.1": { - "integrity": "sha512-clTszLPZkmycALTHD1mXGU+mOtA/MIoLgS1KGTTzFNVm9rytQVykgRaP+z1zl572cz0bTqj4rFVoC2N+IGK4Sg==", + "send@1.2.1": { + "integrity": "sha512-1gnZf7DFcoIcajTjTwjwuDjzuz4PPcY2StKPlsGAQ1+YH20IRVrBaXSWmdjowTJ6u8Rc01PoYOGHXfP1mYcZNQ==", "dependencies": [ - "svelte", - "swrev" + "debug", + "encodeurl", + "escape-html", + "etag", + "fresh", + "http-errors", + "mime-types@3.0.2", + "ms", + "on-finished", + "range-parser", + "statuses" ] }, - "string-width@4.2.3": { - "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", + "serve-static@2.2.1": { + "integrity": "sha512-xRXBn0pPqQTVQiC8wyQrKs2MOlX24zQ0POGaj0kultvoOCstBQM5yvOhAVSUwOMjQtTvsPWoNCHfPGwaaQJhTw==", "dependencies": [ - "emoji-regex", - "is-fullwidth-code-point", - "strip-ansi" + "encodeurl", + "escape-html", + "parseurl", + "send" ] }, - "strip-ansi@6.0.1": { - "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "setprototypeof@1.2.0": { + "integrity": "sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw==" + }, + "side-channel-list@1.0.1": { + "integrity": "sha512-mjn/0bi/oUURjc5Xl7IaWi/OJJJumuoJFQJfDDyO46+hBWsfaVM65TBHq2eoZBhzl9EchxOijpkbRC8SVBQU0w==", "dependencies": [ - "ansi-regex" + "es-errors", + "object-inspect" ] }, - "supports-color@7.2.0": { - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "side-channel-map@1.0.1": { + "integrity": "sha512-VCjCNfgMsby3tTdo02nbjtM/ewra6jPHmpThenkTYh8pG9ucZ/1P8So4u4FGBek/BjpOVsDCMoLA/iuBKIFXRA==", "dependencies": [ - "has-flag" + "call-bound", + "es-errors", + "get-intrinsic", + "object-inspect" ] }, - "svelte@5.22.6_acorn@8.14.1": { - "integrity": "sha512-dxHyh3USJyayafSt5I5QD7KuoCM5ZGdIOtLQiKHEro7tymdh0jMcNkiSBVHW+LOA2jEqZEHhyfwN6/pCjx0Fug==", + "side-channel-weakmap@1.0.2": { + "integrity": "sha512-WPS/HvHQTYnHisLo9McqBHOJk2FkHO/tlpvldyrnem4aeQp4hai3gythswg6p01oSoTl58rcpiFAjF2br2Ak2A==", "dependencies": [ - "@ampproject/remapping", - "@jridgewell/sourcemap-codec", - "@sveltejs/acorn-typescript", - "@types/estree", - "acorn", - "aria-query", - "axobject-query", - "clsx", - "esm-env", - "esrap", - "is-reference", - "locate-character", - "magic-string", - "zimmerframe" + "call-bound", + "es-errors", + "get-intrinsic", + "object-inspect", + "side-channel-map" ] }, - "swr@2.3.3_react@19.0.0": { - "integrity": "sha512-dshNvs3ExOqtZ6kJBaAsabhPdHyeY4P2cKwRCniDVifBMoG/SVI7tfLWqPXriVspf2Rg4tPzXJTnwaihIeFw2A==", + "side-channel@1.1.1": { + "integrity": "sha512-6x6dK6zJdpTzF4sQeNYxwtvBzf6Eg4GtlesS94HOvTudUeyK2WXAaIfmDgsyslYrRBeFIlsi54AYsFGUuhmvrQ==", "dependencies": [ - "dequal", - "react", - "use-sync-external-store" + "es-errors", + "object-inspect", + "side-channel-list", + "side-channel-map", + "side-channel-weakmap" ] }, - "swrev@4.0.0": { - "integrity": "sha512-LqVcOHSB4cPGgitD1riJ1Hh4vdmITOp+BkmfmXRh4hSF/t7EnS4iD+SOTmq7w5pPm/SiPeto4ADbKS6dHUDWFA==" + "source-map@0.7.6": { + "integrity": "sha512-i5uvt8C3ikiWeNZSVZNWcfZPItFQOsYTUAOkcUPGd8DqDy1uOUikjt5dG+uRlwyvR108Fb9DOd4GvXfT0N2/uQ==" + }, + "statuses@2.0.2": { + "integrity": "sha512-DvEy55V3DB7uknRo+4iOGT5fP1slR8wQohVdknigZPMpMstaKJQWhwiYBACJE3Ul2pTnATihhBYnRhZQHGBiRw==" + }, + "string-width@4.2.3": { + "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", + "dependencies": [ + "emoji-regex", + "is-fullwidth-code-point", + "strip-ansi" + ] }, - "swrv@1.1.0_vue@3.5.13": { - "integrity": "sha512-pjllRDr2s0iTwiE5Isvip51dZGR7GjLH1gCSVyE8bQnbAx6xackXsFdojau+1O5u98yHF5V73HQGOFxKUXO9gQ==", + "strip-ansi@6.0.1": { + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", "dependencies": [ - "vue" + "ansi-regex" ] }, - "throttleit@2.1.0": { - "integrity": "sha512-nt6AMGKW1p/70DF/hGBdJB57B8Tspmbp5gfJ8ilhLnt7kkr2ye7hzD6NVG8GGErk2HWF34igrL2CXmNIkzKqKw==" + "termi-link@1.1.0": { + "integrity": "sha512-2qSN6TnomHgVLtk+htSWbaYs4Rd2MH/RU7VpHTy6MBstyNyWbM4yKd1DCYpE3fDg8dmGWojXCngNi/MHCzGuAA==" + }, + "toidentifier@1.0.1": { + "integrity": "sha512-o5sSPKEkg/DIQNmH43V0/uerLrpzVedkUh8tGNvaeXpfpuwjKenlSox/2O/BTlZUtEe+JG7s5YhEz608PlAHRA==" }, "tr46@0.0.3": { "integrity": "sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw==" }, + "type-is@2.1.0": { + "integrity": "sha512-faYHw0anBbc/kWF3zFTEnxSFOAGUX9GFbOBthvDdLsIlEoWOFOtS0zgCiQYwIskL9iGXZL3kAXD8OoZ4GmMATA==", + "dependencies": [ + "content-type@2.1.0", + "media-typer", + "mime-types@3.0.2" + ] + }, "undici-types@5.26.5": { "integrity": "sha512-JlCMO+ehdEIKqlFxk6IfVoAUVmgz7cU7zD/h9XZ0qzeosSHmUJVOzSQvvYSYWXkFXC+IfLKSIffhv0sVZup6pA==" }, - "undici-types@6.20.0": { - "integrity": "sha512-Ny6QZ2Nju20vw1SRHe3d9jVu6gJ+4e3+MMpqu7pqE5HT6WsTSlce++GQmK5UXS8mzV8DSYHrQH+Xrf2jVcuKNg==" + "unpipe@1.0.0": { + "integrity": "sha512-pjy2bYhSsufwWlKwPc+l3cN7+wuJlK6uz0YdJEOlQDbl6jo/YlPi4mb8agUkVC8BF7V8NuzeyPNqRksA3hztKQ==" }, - "use-sync-external-store@1.4.0_react@19.0.0": { - "integrity": "sha512-9WXSPC5fMv61vaupRkCKCxsPxBocVnwakBEkMIHHpkTTg6icbJtg6jzgtLDm4bl3cSHAca52rYWih0k4K3PfHw==", + "unplugin@2.3.11": { + "integrity": "sha512-5uKD0nqiYVzlmCRs01Fhs2BdkEgBS3SAVP6ndrBsuK42iC2+JHyxM05Rm9G8+5mkmRtzMZGY8Ct5+mliZxU/Ww==", "dependencies": [ - "react" + "@jridgewell/remapping", + "acorn", + "picomatch", + "webpack-virtual-modules" ] }, + "uuid@11.1.1": { + "integrity": "sha512-vIYxrBCC/N/K+Js3qSN88go7kIfNPssr/hHCesKCQNAjmgvYS2oqr69kIufEG+O4+PfezOH4EbIeHCfFov8ZgQ==", + "bin": true + }, "uuid@9.0.1": { - "integrity": "sha512-b+1eJOlsR9K8HJpow9Ok3fiWOWSIcIzXodvv0rQjVoOVNpWMpxf1wZNpt4y9h10odCNrqnYp1OBzRktckBe3sA==" + "integrity": "sha512-b+1eJOlsR9K8HJpow9Ok3fiWOWSIcIzXodvv0rQjVoOVNpWMpxf1wZNpt4y9h10odCNrqnYp1OBzRktckBe3sA==", + "deprecated": true, + "bin": true }, "validate.io-array@1.0.6": { "integrity": "sha512-DeOy7CnPEziggrOO5CZhVKJw6S3Yi7e9e65R1Nl/RTN1vTQKnzjfvks0/8kQ40FP/dsjRAOd4hxmJ7uLa6vxkg==" @@ -1057,15 +1150,8 @@ "validate.io-function@1.0.2": { "integrity": "sha512-LlFybRJEriSuBnUhQyG5bwglhh50EpTL2ul23MPIuR1odjO7XaMLFV8vHGwp7AZciFxtYOeiSCT5st+XSPONiQ==" }, - "vue@3.5.13": { - "integrity": "sha512-wmeiSMxkZCSc+PM2w2VRsOYAZC8GdipNFRTsLSfodVqI9mbejKeXEGr8SckuLnrQPGe3oJN5c3K0vpoU9q/wCQ==", - "dependencies": [ - "@vue/compiler-dom", - "@vue/compiler-sfc", - "@vue/runtime-dom", - "@vue/server-renderer", - "@vue/shared" - ] + "vary@1.1.2": { + "integrity": "sha512-BNGbWLfd0eUPabhkXUVm0j8uuvREyTh5ovRa/dyow/BqAbZJyC+5fU+IzQOzmAKzYqYRAISoRhdQr3eIZ/PXqg==" }, "web-streams-polyfill@3.3.3": { "integrity": "sha512-d2JWLCivmZYTSIoge9MsgFCZrt571BikcWGYkjC1khllbTeDlGqZ2D8vD8E/lJa8WGWbb7Plm8/XJYV7IJHZZw==" @@ -1076,6 +1162,9 @@ "webidl-conversions@3.0.1": { "integrity": "sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ==" }, + "webpack-virtual-modules@0.6.2": { + "integrity": "sha512-66/V2i5hQanC51vBQKPH4aI8NMAcBW59FVBs+rC7eGHupMyfn34q7rZIE+ETlJ+XTevqfUhVVBgSUNSW2flEUQ==" + }, "whatwg-url@5.0.0": { "integrity": "sha512-saE57nupxk6v3HY35+jzBwYa0rKSy0XR8JSxZPwgLr7ys0IBzhGviA1/TUGJLmSVqs8pb9AnvICXEuOHLprYTw==", "dependencies": [ @@ -1083,20 +1172,24 @@ "webidl-conversions" ] }, + "wrappy@1.0.2": { + "integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==" + }, "yaml@2.7.0": { - "integrity": "sha512-+hSoy/QHluxmC9kCIJyL/uyFmLmc+e5CFR5Wa+bpIhIj85LVb9ZH2nVnqrHoSvKogwODv0ClqZkmiSSaIH5LTA==" + "integrity": "sha512-+hSoy/QHluxmC9kCIJyL/uyFmLmc+e5CFR5Wa+bpIhIj85LVb9ZH2nVnqrHoSvKogwODv0ClqZkmiSSaIH5LTA==", + "bin": true }, - "zimmerframe@1.1.2": { - "integrity": "sha512-rAbqEGa8ovJy4pyBxZM70hg4pE6gDgaQ0Sl9M3enG3I0d6H4XSAM3GeNGLKnsBpuijUow064sf7ww1nutC5/3w==" + "zod-to-json-schema@3.25.2_zod@3.24.2": { + "integrity": "sha512-O/PgfnpT1xKSDeQYSCfRI5Gy3hPf91mKVDuYLUHZJMiDFptvP41MSnWofm8dnCm0256ZNfZIM7DSzuSMAFnjHA==" }, - "zod-to-json-schema@3.24.3_zod@3.24.2": { - "integrity": "sha512-HIAfWdYIt1sssHfYZFCXp4rU1w2r8hVVXYIlmoa0r0gABLs5di3RCqPU5DDROogVz1pAdYBaz7HK5n9pSUNs3A==", + "zod-to-json-schema@3.25.2_zod@3.25.76": { + "integrity": "sha512-O/PgfnpT1xKSDeQYSCfRI5Gy3hPf91mKVDuYLUHZJMiDFptvP41MSnWofm8dnCm0256ZNfZIM7DSzuSMAFnjHA==", "dependencies": [ "zod" ] }, - "zod@3.24.2": { - "integrity": "sha512-lY7CDW43ECgW9u1TcT3IoXHflywfVqDYze4waEz812jR/bZ8FHDsl7pFQoSZTz5N+2NqRXs8GBwnAwo3ZNxqhQ==" + "zod@3.25.76": { + "integrity": "sha512-gzUt/qt81nXsFGKIFcC3YnfEAx5NkunCfnDlvuBSSFS02bcXu4Lmea0AFIUwbLWxWPx3d9p8S5QoaujKcNQxcQ==" } }, "redirects": { @@ -1114,8 +1207,7 @@ "jsr:@std/fs@^1.0.14", "jsr:@std/path@^1.0.8", "npm:autoevals@^0.0.122", - "npm:braintrust@^0.0.187", - "npm:zod@^3.24.2" + "npm:zod@^3.25.34" ] } } diff --git a/integrations/val.town/vals/tutorial/code.ts b/integrations/val.town/vals/tutorial/code.ts index d4f8e830c..14d1f98fd 100644 --- a/integrations/val.town/vals/tutorial/code.ts +++ b/integrations/val.town/vals/tutorial/code.ts @@ -1,5 +1,5 @@ import { LevenshteinScorer } from "npm:autoevals"; -import { Eval } from "npm:braintrust"; +import { Eval } from "npm:braintrust@^3.30.0"; Eval("Say Hi Bot", { data: () => { diff --git a/integrations/vercel-ai-sdk/CHANGELOG.md b/integrations/vercel-ai-sdk/CHANGELOG.md deleted file mode 100644 index 131233302..000000000 --- a/integrations/vercel-ai-sdk/CHANGELOG.md +++ /dev/null @@ -1,35 +0,0 @@ -# @braintrust/vercel-ai-sdk - -## 0.0.7 - -- `@braintrust/vercel-ai-sdk` is now deprecated. This release marks the last release for this package. (https://github.com/braintrustdata/braintrust-sdk-javascript/pull/2383) -- Updated dependencies: braintrust@3.29.0 - -### Migration - -To migrate manual instrumentation, install `braintrust` and use `wrapAISDK` -from `braintrust` as described in the -[Vercel AI SDK integration guide](https://www.braintrust.dev/docs/integrations/sdk-integrations/vercel). -Automatic AI SDK instrumentation is also available through the Braintrust -runtime hook and supported bundler integrations; see the -[auto-instrumentation guide](https://www.braintrust.dev/docs/instrument/trace-llm-calls#auto-instrumentation) -for setup instructions. The legacy `BraintrustAdapter` stream conversion -helpers have no direct replacement; migrate those usages to `BraintrustStream` -from `braintrust`, documented in the -[TypeScript SDK reference](https://www.braintrust.dev/docs/reference/libs/nodejs), -and the current AI SDK stream response APIs. - -## 0.0.6 - -### Patch Changes - -- Update the AI SDK dependency to a patched version and preserve stream adapter compatibility. (https://github.com/braintrustdata/braintrust-sdk-javascript/pull/1992) -- Updated dependencies: braintrust@3.11.0 - -## 0.0.5 - -### Patch Changes - -- Added `toDataStreamResponse()` as the preferred response helper for Braintrust streams. -- Kept `toAIStreamResponse()` as a deprecated alias for compatibility. -- Updated the adapter for compatibility with newer Zod versions. diff --git a/integrations/vercel-ai-sdk/README.md b/integrations/vercel-ai-sdk/README.md deleted file mode 100644 index 73e42908e..000000000 --- a/integrations/vercel-ai-sdk/README.md +++ /dev/null @@ -1,24 +0,0 @@ -# @braintrust/vercel-ai-sdk - -> [!WARNING] -> This package and all of its exports are deprecated and will stop being -> published after the next release. Use the Vercel AI SDK integration in -> `braintrust` instead. - -For manual instrumentation, use `wrapAISDK` from `braintrust`: - -```ts -import * as ai from "ai"; -import { wrapAISDK } from "braintrust"; - -const { generateText, streamText } = wrapAISDK(ai); -``` - -Braintrust also supports automatic AI SDK instrumentation. See the -[Braintrust SDK documentation](../../js/README.md#auto-instrumentation) for -runtime-hook and bundler setup. - -The legacy `BraintrustAdapter` stream conversion helpers do not have a direct -replacement. Migrate those usages to `BraintrustStream` from `braintrust` and -the current AI SDK stream response APIs before this package stops being -published. diff --git a/integrations/vercel-ai-sdk/package.json b/integrations/vercel-ai-sdk/package.json deleted file mode 100644 index 0a6009d12..000000000 --- a/integrations/vercel-ai-sdk/package.json +++ /dev/null @@ -1,48 +0,0 @@ -{ - "name": "@braintrust/vercel-ai-sdk", - "version": "0.0.7", - "description": "SDK for integrating Braintrust with Vercel AI SDK", - "main": "./dist/index.js", - "module": "./dist/index.mjs", - "types": "./dist/index.d.ts", - "exports": { - "./package.json": "./package.json", - ".": { - "types": "./dist/index.d.ts", - "import": "./dist/index.mjs", - "module": "./dist/index.mjs", - "require": "./dist/index.js" - } - }, - "scripts": { - "build": "tsup", - "check:typings": "tsc --noEmit", - "watch": "tsup --watch", - "clean": "rm -r dist/*", - "test": "vitest run", - "yalc:publish": "yalc publish" - }, - "author": "Braintrust Data Inc.", - "license": "MIT", - "devDependencies": { - "@types/node": "^20.10.5", - "braintrust": "workspace:*", - "tsup": "^8.5.1", - "typescript": "^5.3.3", - "vitest": "4.1.11" - }, - "peerDependencies": { - "braintrust": ">=0.0.141" - }, - "repository": { - "type": "git", - "url": "git+https://github.com/braintrustdata/braintrust-sdk-javascript.git", - "directory": "integrations/vercel-ai-sdk" - }, - "homepage": "https://www.braintrust.dev/docs", - "publishConfig": { - "access": "public", - "registry": "https://registry.npmjs.org/", - "provenance": true - } -} diff --git a/integrations/vercel-ai-sdk/src/adapter.test.ts b/integrations/vercel-ai-sdk/src/adapter.test.ts deleted file mode 100644 index 14d9e698e..000000000 --- a/integrations/vercel-ai-sdk/src/adapter.test.ts +++ /dev/null @@ -1,80 +0,0 @@ -import { BraintrustStreamChunk } from "braintrust"; -import { ReadableStream, WritableStream } from "stream/web"; -import { expect, test } from "vitest"; -import { BraintrustAdapter } from "."; - -test("text callbacks", async () => { - const textStream = new ReadableStream({ - start(controller) { - controller.enqueue({ type: "text_delta", data: "Hello, " }); - controller.enqueue({ type: "text_delta", data: "world!" }); - controller.close(); - }, - }); - - const { started, completion, final, tokens, text } = - await streamWithCallbacks(textStream); - - expect(started).toBe(true); - expect(completion).toBe("Hello, world!"); - expect(final).toBe("Hello, world!"); - expect(tokens).toEqual(["Hello, ", "world!"]); - expect(text).toEqual(["Hello, ", "world!"]); -}); - -test("json callbacks", async () => { - const toolStream = new ReadableStream({ - start(controller) { - controller.enqueue({ type: "json_delta", data: `{"a":` }); - controller.enqueue({ type: "json_delta", data: ` 1}` }); - controller.close(); - }, - }); - - const { started, completion, final, tokens, text } = - await streamWithCallbacks(toolStream); - - expect(started).toBe(true); - expect(completion).toBe(null); - expect(final && JSON.parse(final)).toEqual({ a: 1 }); - expect(tokens).toEqual([]); - expect(text).toEqual([]); -}); - -async function streamWithCallbacks( - stream: ReadableStream, -) { - let started = false; - let completion: string | null = null; - let final: string | null = null; - let tokens: string[] = []; - let text: string[] = []; - - const vercelStream = BraintrustAdapter.toAIStream(stream, { - onStart: () => { - started = true; - }, - onCompletion: (c) => { - completion = c; - }, - onFinal: (f) => { - final = f; - }, - onToken: (t) => { - tokens.push(t); - }, - onText: (t) => { - text.push(t); - }, - }); - - await vercelStream.pipeTo( - new WritableStream({ - write(chunk) { - console.log(new TextDecoder().decode(chunk)); - }, - }), - ); - - return { started, completion, final, tokens, text }; -} diff --git a/integrations/vercel-ai-sdk/src/adapter.ts b/integrations/vercel-ai-sdk/src/adapter.ts deleted file mode 100644 index 06c091add..000000000 --- a/integrations/vercel-ai-sdk/src/adapter.ts +++ /dev/null @@ -1,131 +0,0 @@ -import { BraintrustStream, BraintrustStreamChunk } from "braintrust"; - -/** - * @deprecated Use the AI SDK integration exported by `braintrust` instead. This package will stop being published after the next release. - */ -export interface AIStreamCallbacksAndOptions { - onStart?: () => void | Promise; - onCompletion?: (completion: string) => void | Promise; - onFinal?: (completion: string) => void | Promise; - onToken?: (token: string) => void | Promise; - onText?: (text: string) => void | Promise; -} - -function formatStreamPart(type: "text" | "data", value: unknown): string { - const code = type === "text" ? "0" : "2"; - return `${code}:${JSON.stringify(value)}\n`; -} - -import { ReadableStream, TransformStream } from "stream/web"; - -/** - * @deprecated Use `BraintrustStream` from `braintrust` with the current AI SDK response APIs instead. This package will stop being published after the next release. - */ -export type BraintrustStreamOrReadable = - | BraintrustStream - | ReadableStream - | ReadableStream - | ReadableStream; - -/** - * @deprecated Use `BraintrustStream` from `braintrust` with the current AI SDK stream APIs instead. This package will stop being published after the next release. - */ -export function toAIStream( - stream: BraintrustStreamOrReadable, - callbacks?: AIStreamCallbacksAndOptions, -): ReadableStream { - const btStream = - stream instanceof BraintrustStream - ? stream - : new BraintrustStream(stream as ReadableStream); - - return btStream - .toReadableStream() - .pipeThrough(btStreamToAISDKTransformStream(callbacks)); -} - -/** - * @deprecated Use `BraintrustStream` from `braintrust` with the current AI SDK response APIs instead. This package will stop being published after the next release. - */ -export function toDataStreamResponse( - stream: BraintrustStreamOrReadable, - init?: ResponseInit, -): Response { - return new Response(toAIStream(stream), { - headers: { - "Content-Type": "text/plain; charset=utf-8", - }, - ...init, - }); -} - -/** - * @deprecated Use `BraintrustStream` from `braintrust` with the current AI SDK response APIs instead. This package will stop being published after the next release. - */ -export function toAIStreamResponse( - stream: BraintrustStreamOrReadable, - init?: ResponseInit, -): Response { - return toDataStreamResponse(stream, init); -} - -function btStreamToAISDKTransformStream( - callbacks?: AIStreamCallbacksAndOptions, -) { - const encoder = new TextEncoder(); - const jsonChunks: string[] = []; - const textChunks: string[] = []; - return new TransformStream({ - async start(controller) { - if (callbacks?.onStart) { - await callbacks.onStart(); - } - }, - async transform(chunk, controller) { - switch (chunk.type) { - case "text_delta": - controller.enqueue( - encoder.encode(formatStreamPart("text", chunk.data)), - ); - - // Call me old fashioned, but I think it's worth checking the existence of - // each function to avoid unnecessary context switches. - if (callbacks?.onToken) { - await callbacks.onToken(chunk.data); - } - if (callbacks?.onText) { - await callbacks.onText(chunk.data); - } - - if (callbacks?.onCompletion || callbacks?.onFinal) { - textChunks.push(chunk.data); - } - - break; - case "json_delta": - jsonChunks.push(chunk.data); - break; - } - }, - async flush(controller) { - if (jsonChunks.length > 0) { - const jsonString = jsonChunks.join(""); - const data = JSON.parse(jsonString); - controller.enqueue(encoder.encode(formatStreamPart("data", data))); - if (callbacks?.onFinal) { - await callbacks.onFinal(jsonString); - } - } else { - const textData = textChunks.join(""); - if (callbacks?.onCompletion) { - await callbacks.onCompletion(textData); - } - if (callbacks?.onFinal) { - await callbacks.onFinal(textData); - } - } - - controller.terminate(); - }, - }); -} diff --git a/integrations/vercel-ai-sdk/src/index.ts b/integrations/vercel-ai-sdk/src/index.ts deleted file mode 100644 index 55a019e33..000000000 --- a/integrations/vercel-ai-sdk/src/index.ts +++ /dev/null @@ -1,6 +0,0 @@ -import * as adapter from "./adapter"; - -/** - * @deprecated Use the AI SDK integration exported by `braintrust` instead. This package will stop being published after the next release. - */ -export const BraintrustAdapter = adapter; diff --git a/integrations/vercel-ai-sdk/tsconfig.json b/integrations/vercel-ai-sdk/tsconfig.json deleted file mode 100644 index ea6ad1861..000000000 --- a/integrations/vercel-ai-sdk/tsconfig.json +++ /dev/null @@ -1,15 +0,0 @@ -{ - "compilerOptions": { - "declaration": true, - "lib": ["es2022"], - "module": "commonjs", - "target": "es2022", - "moduleResolution": "node", - "strict": true, - "noUnusedLocals": true, - "esModuleInterop": true, - "skipLibCheck": true - }, - "include": ["."], - "exclude": ["node_modules/**", "**/dist/**"] -} diff --git a/integrations/vercel-ai-sdk/tsup.config.ts b/integrations/vercel-ai-sdk/tsup.config.ts deleted file mode 100644 index 7d69621aa..000000000 --- a/integrations/vercel-ai-sdk/tsup.config.ts +++ /dev/null @@ -1,10 +0,0 @@ -import { defineConfig } from "tsup"; - -export default defineConfig([ - { - entry: ["src/index.ts"], - format: ["cjs", "esm"], - outDir: "dist", - dts: true, - }, -]); diff --git a/integrations/vercel-ai-sdk/turbo.json b/integrations/vercel-ai-sdk/turbo.json deleted file mode 100644 index 3e50dc890..000000000 --- a/integrations/vercel-ai-sdk/turbo.json +++ /dev/null @@ -1,8 +0,0 @@ -{ - "extends": ["//"], - "tasks": { - "build": { - "outputs": ["**/dist/**"] - } - } -} diff --git a/integrations/vercel-ai-sdk/vitest.config.ts b/integrations/vercel-ai-sdk/vitest.config.ts deleted file mode 100644 index 744610c82..000000000 --- a/integrations/vercel-ai-sdk/vitest.config.ts +++ /dev/null @@ -1,8 +0,0 @@ -import { defineConfig } from "vitest/config"; - -export default defineConfig({ - test: { - reporters: ["default"], - include: ["tests/**/*.test.ts", "src/**/*.test.ts"], - }, -}); diff --git a/js/.gitignore b/js/.gitignore index ccf7d9ef2..a2982dec2 100644 --- a/js/.gitignore +++ b/js/.gitignore @@ -13,3 +13,4 @@ braintrust-*.tgz vendor/ playground.ts +.vitest/ diff --git a/js/NOTICE b/js/NOTICE index 71d1b1af2..5c78d8304 100644 --- a/js/NOTICE +++ b/js/NOTICE @@ -24,3 +24,8 @@ diagnostics_channel TracingChannel implementation. Copyright Node.js contributors. All rights reserved. It is licensed under the MIT License. See licenses/node-diagnostics-channel/LICENSE. + +This package also bundles async 3.2.5. +Copyright (c) 2010-2018 Caolan McMahon. +async is licensed under the MIT License. +See licenses/async/LICENSE. diff --git a/js/README.md b/js/README.md index 9c5e70094..0249838d3 100644 --- a/js/README.md +++ b/js/README.md @@ -14,6 +14,9 @@ Install the SDK: npm install braintrust ``` +The `bt` CLI is installed separately from the SDK. +See the standalone installation instructions in the [CLI quickstart](https://www.braintrust.dev/docs/reference/cli/quickstart). + ## Quickstart Run a simple experiment (replace `YOUR_API_KEY` with your Braintrust API key): @@ -104,13 +107,56 @@ If you use TypeScript or other transpilation plugins, place the Braintrust plugi For deeper details, see the [auto-instrumentation architecture docs](src/auto-instrumentations/README.md). +### LangSmith tracing + +Braintrust supports LangSmith `>=0.3.30 <1.0.0`. LangSmith tracing remains authoritative: LangSmith must be enabled, and it continues exporting traces to LangSmith while Braintrust mirrors the same run lifecycle. This integration covers tracing only; LangSmith eval, Jest, and Vitest APIs are not instrumented. + +For automatic Node.js instrumentation, use the standard hook before importing LangSmith: + +```bash +node --import braintrust/hook.mjs app.js +``` + +The Vite, Webpack, esbuild, and Rollup plugins shown above apply the same automatic instrumentation in bundled applications. To instrument explicit namespaces instead, wrap the three LangSmith entrypoints you use: + +```typescript +import { + wrapLangSmithClient, + wrapLangSmithRunTrees, + wrapLangSmithTraceable, +} from "braintrust"; +import * as clientNamespace from "langsmith/client"; +import * as runTreesNamespace from "langsmith/run_trees"; +import * as traceableNamespace from "langsmith/traceable"; + +const { Client } = wrapLangSmithClient(clientNamespace); +const { RunTree } = wrapLangSmithRunTrees(runTreesNamespace); +const { traceable } = wrapLangSmithTraceable(traceableNamespace); +``` + +The wrappers are composable and idempotent. They preserve LangSmith behavior, including its network export and `on_end` callbacks. Automatic and explicit instrumentation can safely be used together. + +Disable LangSmith instrumentation in code or through the environment: + +```typescript +import { configureInstrumentation } from "braintrust/instrumentation"; + +configureInstrumentation({ integrations: { langsmith: false } }); +``` + +```bash +BRAINTRUST_DISABLE_INSTRUMENTATION=langsmith node --import braintrust/hook.mjs app.js +``` + +When Braintrust LangChain/LangGraph instrumentation is enabled, LangSmith runs serialized by LangChain are ignored to avoid duplicate spans. Set `langchain: false` (and use LangSmith instrumentation) when LangSmith should be the source for those runs instead. + ## Migration Guides ### Upgrading from 2.x to 3.x See the [Migrate from v2.x to v3.x guide](https://www.braintrust.dev/docs/reference/sdks/typescript/migrations/v2-to-v3). -In 3.x, browser usage should move to `@braintrust/browser` instead of relying on the legacy `braintrust/browser` path. +For direct browser imports, use `@braintrust/browser`. The package root still selects its browser build through export conditions, but `braintrust/browser` is no longer published. ### Upgrading from 1.x to 2.x diff --git a/js/bin/bt b/js/bin/bt deleted file mode 100755 index 5741f84d4..000000000 --- a/js/bin/bt +++ /dev/null @@ -1,41 +0,0 @@ -#!/usr/bin/env node -"use strict"; - -// Runtime launcher for the `bt` CLI. Resolves the platform-specific binary -// (installed via the matching `@braintrust/bt-*` optionalDependency, or -// downloaded by `scripts/install.js` as a fallback) and forwards argv, -// stdio, signals, and the exit code. - -const childProcess = require("node:child_process"); -const { getBinaryPath } = require("../scripts/bt-helper"); - -let binaryPath; -try { - binaryPath = getBinaryPath(); -} catch (err) { - console.error(err.message); - process.exit(1); -} - -const child = childProcess - .spawn(binaryPath, process.argv.slice(2), { - stdio: "inherit", - windowsHide: true, - }) - .on("error", (err) => { - console.error(err); - process.exit(1); - }) - .on("exit", (code, signal) => { - if (signal) { - // Detach our forwarding listener so the re-raised signal hits Node's - // default handler; otherwise we'd intercept it again and exit 0. - process.removeAllListeners(signal); - process.kill(process.pid, signal); - return; - } - process.exit(code ?? 1); - }); - -process.on("SIGTERM", () => child.kill("SIGTERM")); -process.on("SIGINT", () => child.kill("SIGINT")); diff --git a/js/dev/authorize.ts b/js/dev/authorize.ts deleted file mode 100644 index eb91a2aaf..000000000 --- a/js/dev/authorize.ts +++ /dev/null @@ -1,218 +0,0 @@ -import { debugLogger } from "../src/debug-logger"; -import { Request, Response, NextFunction } from "express"; -import { IncomingHttpHeaders } from "http"; -import createError from "http-errors"; -import { LRUCache } from "../src/lru-cache"; -import { BraintrustState, LoginOptions, loginToState } from "../src/logger"; - -export interface RequestContext { - appOrigin: string; - token: string | undefined; - state: BraintrustState | undefined; - projectId: string | undefined; -} -declare module "express" { - interface Request { - ctx?: RequestContext; - } -} - -export function authorizeRequest( - req: Request, - res: Response, - next: NextFunction, -) { - try { - const ctx: RequestContext = { - appOrigin: extractAllowedOrigin(req.headers[ORIGIN_HEADER]), - token: undefined, - state: undefined, - projectId: parseHeader(req.headers, PROJECT_ID_HEADER), - }; - - // Extract token and data from request - if ( - req.headers.authorization || - req.headers[BRAINTRUST_AUTH_TOKEN_HEADER] - ) { - const tokenText = parseBraintrustAuthHeader(req.headers); - if (!tokenText) { - return next(createError(400, "Invalid authorization token format")); - } - ctx.token = tokenText.toLowerCase() === "null" ? undefined : tokenText; - } - - req.ctx = ctx; - - next(); // Proceed to next middleware/controller - } catch (e) { - next(e); - } -} - -const loginCache = new LRUCache({ - max: 32, // TODO: Make this configurable -}); - -async function cachedLogin(options: LoginOptions): Promise { - const key = JSON.stringify(options); - const cached = loginCache.get(key); - if (cached) { - return cached; - } - - const state = await loginToState(options); - loginCache.set(key, state); - return state; -} - -export function makeCheckAuthorized(allowedOrgName: string | undefined) { - return async (req: Request, _res: Response, next: NextFunction) => { - if (!req.ctx?.token) { - return next(createError(401, "Unauthorized")); - } - - try { - const orgName = parseHeader(req.headers, "x-bt-org-name"); - - if (!orgName) { - return next(createError(400, "Missing x-bt-org-name header")); - } - - if (allowedOrgName && allowedOrgName !== orgName) { - const errorMessage = `Org '${orgName}' is not allowed. Only org '${allowedOrgName}' is allowed.`; - return next(createError(403, errorMessage)); - } - - const state = await cachedLogin({ - apiKey: req.ctx?.token, - orgName: orgName, - }); - req.ctx.state = state; - next(); - } catch (e) { - debugLogger.notice.error("Authorization error:", e); - return next(createError(401, "Unauthorized")); - } - }; -} - -function parseBraintrustAuthHeader( - headers: IncomingHttpHeaders | Record, -): string | undefined { - const tokenString = parseHeader(headers, BRAINTRUST_AUTH_TOKEN_HEADER); - return tokenString ?? parseAuthHeader(headers) ?? undefined; -} - -function parseHeader( - headers: IncomingHttpHeaders | Record, - headerName: string, -): string | undefined { - const token = headers[headerName]; - let tokenString; - if (typeof token === "string") { - tokenString = token; - } else if (Array.isArray(token) && token.length > 0) { - tokenString = token[0]; - } - - return tokenString; -} - -export type StaticOrigin = - | boolean - | string - | RegExp - | Array; - -export function checkOrigin( - requestOrigin: string | undefined, - callback: (err: Error | null, origin?: StaticOrigin) => void, -) { - if (!requestOrigin) { - return callback(null, true); - } - - // the origins can be glob patterns - for (const origin of WHITELISTED_ORIGINS || []) { - if ( - (origin instanceof RegExp && origin.test(requestOrigin)) || - origin === requestOrigin - ) { - return callback(null, requestOrigin); - } - } - - return callback(null, false); -} - -const BRAINTRUST_AUTH_TOKEN_HEADER = "x-bt-auth-token"; -const ORIGIN_HEADER = "origin"; -const PROJECT_ID_HEADER = "x-bt-project-id"; - -function extractAllowedOrigin(originHeader: string | undefined): string { - let allowedOrigin: string = MAIN_ORIGIN; - checkOrigin(originHeader, (err, origin) => { - if (!err && originHeader && origin) { - allowedOrigin = originHeader; - } - }); - return allowedOrigin; -} - -const MAIN_ORIGIN = "https://www.braintrust.dev"; -const WHITELISTED_ORIGINS = [ - MAIN_ORIGIN, - "https://www.braintrustdata.com", - new RegExp("^https://[^/]+\\.preview\\.braintrust\\.dev$"), -] - .concat( - process.env.WHITELISTED_ORIGIN ? [process.env.WHITELISTED_ORIGIN] : [], - ) - .concat( - process.env.BRAINTRUST_APP_URL ? [process.env.BRAINTRUST_APP_URL] : [], - ); - -function parseAuthHeader( - headers: Record, -) { - const authHeader = headers["authorization"]; - let authValue = null; - if (Array.isArray(authHeader)) { - authValue = authHeader[authHeader.length - 1]; - } else { - authValue = authHeader; - } - - if (!authValue) { - return null; - } - - const parts = authValue.split(" "); - if (parts.length !== 2) { - return null; - } - return parts[1]; -} - -export const baseAllowedHeaders = [ - "Content-Type", - "X-Amz-Date", - "Authorization", - "X-Api-Key", - "X-Amz-Security-Token", - "x-bt-auth-token", - "x-bt-parent", - // These are eval-specific - "x-bt-org-name", - "x-bt-project-id", - "x-bt-stream-fmt", - "x-bt-use-cache", - "x-stainless-os", - "x-stainless-lang", - "x-stainless-package-version", - "x-stainless-runtime", - "x-stainless-runtime-version", - "x-stainless-arch", - "x-bt-use-gateway", -]; diff --git a/js/dev/errorHandler.ts b/js/dev/errorHandler.ts deleted file mode 100644 index 04f8d8fc6..000000000 --- a/js/dev/errorHandler.ts +++ /dev/null @@ -1,38 +0,0 @@ -import { debugLogger } from "../src/debug-logger"; -import { z } from "zod/v3"; -import { Request, Response, ErrorRequestHandler, NextFunction } from "express"; -import { HttpError } from "http-errors"; - -export const errorHandler: ErrorRequestHandler = ( - err: Error | HttpError, - req: Request, - res: Response, - next: NextFunction, -) => { - if ("status" in err) { - res.status(err.status).json({ - error: { - message: err.message, - status: err.status, - }, - }); - return; - } - if (err instanceof z.ZodError) { - res.status(400).json({ - error: { - message: "Invalid request", - errors: err.errors, - }, - }); - return; - } - - debugLogger.notice.error("Internal server error", err); - res.status(500).json({ - error: { - message: "Internal server error", - status: 500, - }, - }); -}; diff --git a/js/dev/index.ts b/js/dev/index.ts deleted file mode 100644 index 7ac9af952..000000000 --- a/js/dev/index.ts +++ /dev/null @@ -1,5 +0,0 @@ -import { configureNode } from "../src/node/config"; - -configureNode(); - -export { runDevServer } from "./server"; diff --git a/js/dev/server.test.ts b/js/dev/server.test.ts deleted file mode 100644 index 2e1461a7e..000000000 --- a/js/dev/server.test.ts +++ /dev/null @@ -1,21 +0,0 @@ -import { describe, expect, test } from "vitest"; -import { BraintrustState } from "../src/logger"; -import { _exportsForTestingOnly } from "./server"; -import { evalBodySchema } from "./types"; - -describe("remote eval data", () => { - test("uses a named experiment as the data source", async () => { - const request = evalBodySchema.parse({ - name: "remote-evaluator", - data: { experiment_name: "source-experiment" }, - }); - const state = new BraintrustState({}); - - await expect( - _exportsForTestingOnly.getDataset(state, request.data), - ).resolves.toEqual({ - _type: "BaseExperiment", - name: "source-experiment", - }); - }); -}); diff --git a/js/dev/server.ts b/js/dev/server.ts deleted file mode 100644 index 1ae70a61c..000000000 --- a/js/dev/server.ts +++ /dev/null @@ -1,382 +0,0 @@ -import { debugLogger } from "../src/debug-logger"; -import express, { NextFunction, Request, Response } from "express"; -import cors from "cors"; -import { - BaseExperiment, - callEvaluatorData, - Eval, - EvalData, - EvalHooks, - EvalScorer, - EvaluatorDef, - OneOrMoreScores, - classifierName, - scorerName, -} from "../src/framework"; -import { errorHandler } from "./errorHandler"; -import { - authorizeRequest, - baseAllowedHeaders, - makeCheckAuthorized, - checkOrigin, -} from "./authorize"; -import { - type FunctionIdType as FunctionId, - type InvokeFunctionType as InvokeFunctionRequest, - type RunEvalType as RunEvalRequest, - type SSEProgressEventDataType as SSEProgressEventData, -} from "../src/generated_types"; -import { - BaseMetadata, - BraintrustState, - EvalCase, - getSpanParentObject, - initDataset, -} from "../src/logger"; -import { - BT_CURSOR_HEADER, - BT_FOUND_EXISTING_HEADER, - parseParent, -} from "../util/index"; -import { serializeSSEEvent } from "./stream"; -import { - evalBodySchema, - EvaluatorDefinitions, - EvaluatorManifest, - type SerializedParametersContainer, -} from "./types"; -import { EvalParameters, validateParameters } from "../src/eval-parameters"; -import { z } from "zod/v3"; -import { ValidationError } from "ajv"; -import { serializeRemoteEvalParametersContainer } from "../src/framework2"; - -export interface DevServerOpts { - host: string; - port: number; - orgName?: string; -} - -export function runDevServer( - // eslint-disable-next-line @typescript-eslint/no-explicit-any - evaluators: EvaluatorDef[], - opts: DevServerOpts, -) { - // eslint-disable-next-line @typescript-eslint/consistent-type-assertions - const allEvaluators: EvaluatorManifest = Object.fromEntries( - evaluators.map((evaluator) => [evaluator.evalName, evaluator]), - ) as EvaluatorManifest; - - globalThis._lazy_load = false; - - const app = express(); - - app.use(express.json({ limit: "1gb" })); - console.log("Starting server"); - app.use((req, res, next) => { - if (req.headers["access-control-request-private-network"]) { - res.setHeader("Access-Control-Allow-Private-Network", "true"); - } - next(); - }); - - const checkAuthorized = makeCheckAuthorized(opts.orgName); - - app.use( - cors({ - origin: checkOrigin, - methods: ["GET", "PATCH", "POST", "PUT", "DELETE", "OPTIONS"], - allowedHeaders: baseAllowedHeaders, - credentials: true, - exposedHeaders: [ - BT_CURSOR_HEADER, - BT_FOUND_EXISTING_HEADER, - "x-bt-span-id", - "x-bt-span-export", - ], - maxAge: 86400, - }), - ); - - app.use(authorizeRequest); - - app.get("/", (req, res) => { - res.send("Hello, world!"); - }); - - // List endpoint - returns all available evaluators and their metadata - app.get( - "/list", - checkAuthorized, - asyncHandler(async (req, res) => { - const evalDefs: EvaluatorDefinitions = {}; - - for (const [name, evaluator] of Object.entries(allEvaluators)) { - let parameters: SerializedParametersContainer | undefined; - - if (evaluator.parameters) { - const resolvedParams = await Promise.resolve(evaluator.parameters); - parameters = serializeRemoteEvalParametersContainer(resolvedParams); - } - - evalDefs[name] = { - parameters, - scores: (evaluator.scores ?? []).map((score, idx) => ({ - name: scorerName(score, idx), - })), - classifiers: (evaluator.classifiers ?? []).map((classifier, idx) => ({ - name: classifierName(classifier, idx), - })), - }; - } - - res.json(evalDefs); - }), - ); - - app.post( - "/eval", - checkAuthorized, - asyncHandler(async (req, res) => { - const { - name, - parameters, - parent, - experiment_name, - project_id, - data, - scores, - stream, - } = evalBodySchema.parse(req.body); - - if (!req.ctx?.state) { - res - .status(500) - .json({ error: "Braintrust state not initialized in request" }); - return; - } - const state = req.ctx.state; - - const evaluator = allEvaluators[name]; - if (!evaluator) { - res.status(404).json({ error: `Evaluator '${name}' not found` }); - return; - } - - if (evaluator.parameters) { - try { - await validateParameters(parameters ?? {}, evaluator.parameters); - } catch (e) { - debugLogger.notice.error("Error validating parameters", e); - if ( - e instanceof z.ZodError || - e instanceof ValidationError || - e instanceof Error - ) { - res.status(400).json({ - error: e.message, - }); - return; - } - throw e; - } - } - - const resolvedData = await getDataset(state, data); - const evalData = callEvaluatorData(resolvedData); - console.log("Starting eval", evaluator.evalName); - - // Set up SSE headers - if (stream) { - res.setHeader("Content-Type", "text/event-stream"); - res.setHeader("Cache-Control", "no-cache"); - res.setHeader("Connection", "keep-alive"); - } else { - res.setHeader("Content-Type", "application/json"); - } - - const task = async ( - input: unknown, - hooks: EvalHooks, - ) => { - const result = await evaluator.task(input, hooks); - - hooks.reportProgress({ - format: "code", - output_type: "completion", - event: "json_delta", - data: JSON.stringify(result), - }); - return result; - }; - - try { - const summary = await Eval( - "worker-thread", - { - ...evaluator, - data: evalData.data, - scores: (evaluator.scores ?? []).concat( - scores?.map((score) => - makeScorer( - state, - score.name, - score.function_id, - req.ctx?.projectId, - ), - ) ?? [], - ), - task, - state, - experimentName: experiment_name ?? undefined, - projectId: project_id ?? undefined, - }, - { - // Avoid printing the bar to the console. - progress: { - start: () => {}, - stop: () => { - console.log("Finished running experiment"); - }, - increment: () => {}, - }, - stream: (data: SSEProgressEventData) => { - if (stream) { - res.write( - serializeSSEEvent({ - event: "progress", - data: JSON.stringify(data), - }), - ); - } - }, - onStart: (metadata) => { - if (stream) { - res.write( - serializeSSEEvent({ - event: "start", - data: JSON.stringify(metadata), - }), - ); - } - }, - parent: parseParent(parent), - parameters: parameters ?? {}, - }, - ); - - if (stream) { - res.write( - serializeSSEEvent({ - event: "summary", - data: JSON.stringify(summary.summary), - }), - ); - res.write( - serializeSSEEvent({ - event: "done", - data: "", - }), - ); - } else { - res.json(summary.summary); - } - } catch (e) { - debugLogger.notice.error("Error running eval", e); - if (stream) { - res.write( - serializeSSEEvent({ - event: "error", - data: JSON.stringify(e), - }), - ); - } else { - res.status(500).json({ error: e }); - } - } finally { - res.end(); - } - }), - ); - - app.use(errorHandler); - - // Start the server - app.listen(opts.port, opts.host, () => { - console.log(`Dev server running at http://${opts.host}:${opts.port}`); - }); -} -const asyncHandler = - (fn: (req: Request, res: Response, next: NextFunction) => Promise) => - (req: Request, res: Response, next: NextFunction) => { - Promise.resolve(fn(req, res, next)).catch(next); - }; - -async function getDataset( - state: BraintrustState, - data: RunEvalRequest["data"], -): Promise> { - if ("project_name" in data) { - return initDataset({ - state, - project: data.project_name, - dataset: data.dataset_name, - version: data.dataset_version ?? undefined, - environment: data.dataset_environment ?? undefined, - _internal_btql: data._internal_btql ?? undefined, - }); - } else if ("dataset_id" in data) { - return initDataset({ - state, - datasetId: data.dataset_id, - version: data.dataset_version ?? undefined, - environment: data.dataset_environment ?? undefined, - _internal_btql: data._internal_btql ?? undefined, - }); - } else if ("experiment_name" in data) { - return BaseExperiment({ name: data.experiment_name }); - } else { - // eslint-disable-next-line @typescript-eslint/consistent-type-assertions - return data.data as EvalCase[]; - } -} - -function makeScorer( - state: BraintrustState, - name: string, - score: FunctionId, - projectId: string | undefined, -): EvalScorer { - const ret = async (input: EvalCase) => { - const request: InvokeFunctionRequest = { - ...score, - input, - parent: await getSpanParentObject().export(), - stream: false, - mode: "auto", - strict: true, - }; - const headers: Record = { - Accept: "application/json", - }; - if (projectId) { - headers["x-bt-project-id"] = projectId; - } - const result = await state.proxyConn().post(`function/invoke`, request, { - headers, - }); - const data = await result.json(); - // NOTE: Ideally we can parse this value with a zod schema. - // eslint-disable-next-line @typescript-eslint/consistent-type-assertions - return data as OneOrMoreScores; - }; - - Object.defineProperties(ret, { - name: { value: `Remote eval scorer (${name})` }, - }); - - return ret; -} - -export const _exportsForTestingOnly = { - getDataset, -}; diff --git a/js/dev/stream.ts b/js/dev/stream.ts deleted file mode 100644 index 7bca3612b..000000000 --- a/js/dev/stream.ts +++ /dev/null @@ -1,14 +0,0 @@ -export interface RawSSEEvent { - id?: string; - event?: string; - data: string; -} - -export function serializeSSEEvent(event: RawSSEEvent): string { - return ( - Object.entries(event) - .filter(([_key, value]) => value !== undefined) - .map(([key, value]) => `${key}: ${value}`) - .join("\n") + "\n\n" - ); -} diff --git a/js/dev/types.ts b/js/dev/types.ts deleted file mode 100644 index da992efea..000000000 --- a/js/dev/types.ts +++ /dev/null @@ -1,123 +0,0 @@ -import { - FunctionId as functionIdSchema, - InvokeParent as invokeParentSchema, - RunEval as runEvalSchema, - PromptData as promptDataSchema, -} from "../src/generated_types"; -import { z } from "zod/v3"; -import { EvaluatorDef } from "../src/framework"; -import { BaseMetadata } from "../src/logger"; - -export const evalBodySchema = z.object({ - name: z.string(), - parameters: z.record(z.string(), z.unknown()).nullish(), - data: runEvalSchema.shape.data, - scores: z - .array( - z.object({ - function_id: functionIdSchema, - name: z.string(), - }), - ) - .nullish(), - experiment_name: z.string().nullish(), - project_id: z.string().nullish(), - parent: invokeParentSchema.optional(), - stream: z.boolean().optional(), -}); - -export type EvaluatorManifest = Record< - string, - EvaluatorDef ->; - -export const staticParametersSchema = z.record( - z.string(), - z.union([ - z.object({ - type: z.literal("prompt"), - default: promptDataSchema.optional(), - description: z.string().optional(), - }), - z.object({ - type: z.literal("model"), - default: z.string().optional(), - description: z.string().optional(), - }), - z.object({ - type: z.literal("data"), - schema: z.record(z.unknown()), - default: z.unknown().optional(), - description: z.string().optional(), - }), - ]), -); - -export type StaticParametersSchema = z.infer; - -const evalParametersSerializedSchema = staticParametersSchema; -export type EvalParameterSerializedSchema = z.infer< - typeof evalParametersSerializedSchema ->; - -export const parametersSchema = z.object({ - type: z.literal("object"), - properties: z.record(z.string(), z.record(z.unknown())), - required: z.array(z.string()).optional(), - additionalProperties: z.boolean().optional(), -}); - -export type ParametersSchema = z.infer; - -const parametersSourceSchema = z.object({ - parametersId: z.string().optional(), - slug: z.string(), - name: z.string(), - projectId: z.string().optional(), - version: z.string().optional(), -}); - -export type ParametersSource = z.infer; - -const parametersContainerSchema = z.object({ - type: z.literal("braintrust.parameters"), - schema: parametersSchema, - source: parametersSourceSchema, -}); - -export type ParametersContainer = z.infer; - -const staticParametersContainerSchema = z.object({ - type: z.literal("braintrust.staticParameters"), - schema: staticParametersSchema, - source: z.null().nullish(), -}); - -export type StaticParametersContainer = z.infer< - typeof staticParametersContainerSchema ->; - -export const serializedParametersContainerSchema = z.union([ - parametersContainerSchema, - staticParametersContainerSchema, - // keeping this type here since old versions of the SDK will still pass the unwrapped schema and we need to handle this in the app - staticParametersSchema, -]); - -export type SerializedParametersContainer = z.infer< - typeof serializedParametersContainerSchema ->; - -export const evaluatorDefinitionSchema = z.object({ - parameters: serializedParametersContainerSchema.nullish(), - scores: z.array(z.object({ name: z.string() })).optional(), - classifiers: z.array(z.object({ name: z.string() })).optional(), -}); -export type EvaluatorDefinition = z.infer; - -export const evaluatorDefinitionsSchema = z.record( - z.string(), - evaluatorDefinitionSchema, -); - -export type EvaluatorDefinitions = z.infer; diff --git a/js/eslint.config.ts b/js/eslint.config.ts deleted file mode 100644 index 04e2b8f78..000000000 --- a/js/eslint.config.ts +++ /dev/null @@ -1,241 +0,0 @@ -import tseslint from "@typescript-eslint/eslint-plugin"; -import tsparser from "@typescript-eslint/parser"; -import esX from "eslint-plugin-es-x"; -import n from "eslint-plugin-n"; -import nodeImport from "eslint-plugin-node-import"; -import tsupConfigImport from "./tsup.config"; - -const supportedNodeVersionRange = "^20.12.0 || ^21.7.0 || ^22.13.0 || >=23.5.0"; - -// Handle both ESM and CJS module formats -const tsupConfig = Array.isArray(tsupConfigImport) - ? tsupConfigImport - : tsupConfigImport.default || []; - -// Extract entry points from tsup config, excluding CLI -const entryFiles = tsupConfig - .flatMap((config) => { - const entries = Array.isArray(config.entry) - ? config.entry - : Object.values(config.entry); - return entries; - }) - .filter((entry) => !entry.includes("cli")); - -export default [ - { - ignores: [ - "dist/**", - "dev/dist/**", - "util/dist/**", - "node_modules/**", - "vendor/**", - "examples/**", - "**/generated*_types.ts", - "scripts/**", - ".turbo/**", - "docs/**", - "test-ai-sdk-wrapper/**", - "vercel/**", - // TODO: Add these back once tsconfig.json includes them, so that - // typed linting (and all other config blocks) can run on them too. - "**/*.test.ts", - "**/*.test.tsx", - "src/auto-instrumentations/**", - ], - }, - { - files: ["src/**/*.ts", "src/**/*.tsx"], - languageOptions: { - parser: tsparser, - parserOptions: { - ecmaVersion: "latest", - sourceType: "module", - project: "./tsconfig.json", - }, - }, - plugins: { - "@typescript-eslint": tseslint, - "es-x": esX, - n, - "node-import": nodeImport, - }, - rules: { - // Base TypeScript rules - ...tseslint.configs.recommended.rules, - ...esX.configs["flat/restrict-to-es2022"].rules, - // TODO: Fix violations and re-enable as "error" - "@typescript-eslint/no-explicit-any": "warn", - "@typescript-eslint/no-unused-vars": [ - "error", - { - vars: "all", - args: "none", - ignoreRestSiblings: false, - argsIgnorePattern: "^_", - varsIgnorePattern: "^_", - }, - ], - "prefer-const": "error", - "@typescript-eslint/ban-types": "off", - "@typescript-eslint/ban-ts-comment": "off", - "@typescript-eslint/no-require-imports": "off", - // TODO: Fix violations and re-enable as "error" - "@typescript-eslint/consistent-type-assertions": [ - "warn", - { assertionStyle: "never" }, - ], - "no-unused-expressions": ["error", { allowShortCircuit: true }], - "@typescript-eslint/no-unused-expressions": "off", - "@typescript-eslint/no-empty-object-type": "error", - "@typescript-eslint/no-unsafe-function-type": "error", - "@typescript-eslint/prefer-as-const": "error", - "no-restricted-properties": [ - "error", - { - object: "console", - property: "log", - message: "Use debugLogger instead of console for SDK logging.", - }, - { - object: "console", - property: "warn", - message: "Use debugLogger instead of console for SDK logging.", - }, - { - object: "console", - property: "error", - message: "Use debugLogger instead of console for SDK logging.", - }, - { - object: "console", - property: "debug", - message: "Use debugLogger instead of console for SDK logging.", - }, - { - object: "console", - property: "info", - message: "Use debugLogger instead of console for SDK logging.", - }, - { - object: "console", - property: "trace", - message: "Use debugLogger instead of console for SDK logging.", - }, - ], - "no-restricted-syntax": [ - "error", - { - selector: - "CallExpression[callee.type='MemberExpression'][callee.property.type='Identifier'][callee.property.name='enterWith']", - message: - "AsyncLocalStorage.enterWith() is not supported in Cloudflare Workers. Use AsyncLocalStorage.run() to scope context instead.", - }, - { - selector: - "CallExpression[callee.type='MemberExpression'][callee.property.type='Literal'][callee.property.value='enterWith']", - message: - "AsyncLocalStorage.enterWith() is not supported in Cloudflare Workers. Use AsyncLocalStorage.run() to scope context instead.", - }, - ], - "n/no-unsupported-features/node-builtins": [ - "error", - { - version: supportedNodeVersionRange, - allowExperimental: true, - }, - ], - "n/no-unsupported-features/es-builtins": [ - "error", - { version: supportedNodeVersionRange }, - ], - "n/no-unsupported-features/es-syntax": [ - "error", - { version: supportedNodeVersionRange }, - ], - // Require node: protocol for Node.js built-in imports (for Deno compatibility) - // This plugin automatically detects ALL Node.js built-ins - no manual list needed! - "node-import/prefer-node-protocol": "error", - }, - }, - { - files: ["src/queue.bench.ts"], - rules: { - "no-restricted-properties": "off", - }, - }, - { - files: ["src/**/*.ts", "src/**/*.tsx"], - ignores: [ - "src/cli/**", - "src/debug-logger.ts", - "src/framework.ts", - "src/framework2.ts", - "src/isomorph.ts", - "src/sandbox.ts", - "src/template/**", - "src/reporters/**", - "src/prompt-cache/**", - "src/eval-parameters.ts", - "src/wrappers/**", - "src/instrumentation/**", - "src/auto-instrumentations/**", - ], - rules: { - "no-restricted-imports": [ - "error", - { - patterns: [ - { - group: ["**/cli", "**/cli/**", "./cli", "./cli/**"], - message: - "Importing from 'cli' directory is not allowed. CLI code should not be imported by SDK code.", - }, - ], - }, - ], - }, - }, - { - files: ["src/**/*.ts", "src/**/*.tsx"], - ignores: [...entryFiles, "**/*.test.ts", "**/*.test.tsx"], - rules: { - "no-restricted-imports": [ - "error", - { - patterns: [ - { - group: [ - "./exports", - "./exports.ts", - "../exports", - "../exports.ts", - ], - message: - "Direct imports from 'exports.ts' are not allowed. Import from the specific module instead. Only entry points (index.ts, browser.ts) should import from exports.ts.", - }, - ], - }, - ], - }, - }, - { - files: [ - ...entryFiles, - // Also include exports files which are imported by entry points - "src/exports.ts", - ], - ignores: ["**/*.test.ts", "**/*.test.tsx"], - languageOptions: { - parser: tsparser, - parserOptions: { - ecmaVersion: "latest", - sourceType: "module", - project: "./tsconfig.json", - }, - }, - plugins: { - "@typescript-eslint": tseslint, - }, - }, -]; diff --git a/js/licenses/async/LICENSE b/js/licenses/async/LICENSE new file mode 100644 index 000000000..b18aed692 --- /dev/null +++ b/js/licenses/async/LICENSE @@ -0,0 +1,19 @@ +Copyright (c) 2010-2018 Caolan McMahon + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. diff --git a/js/package.json b/js/package.json index 4597a61de..82fc5c4ba 100644 --- a/js/package.json +++ b/js/package.json @@ -8,251 +8,178 @@ "directory": "js" }, "homepage": "https://www.braintrust.dev/docs", - "main": "./dist/index.js", - "module": "./dist/index.mjs", - "types": "./dist/index.d.ts", - "sideEffects": true, - "browser": { - "./dist/index.js": "./dist/browser.js", - "./dist/index.d.ts": "./dist/browser.d.ts", - "./dist/index.mjs": "./dist/browser.mjs", - "./dist/index.d.mts": "./dist/browser.d.mts" - }, - "bin": { - "braintrust": "./dist/cli.js", - "bt": "./bin/bt" - }, + "main": "./dist/node/index.js", + "module": "./dist/node/index.js", + "types": "./dist/node/index.d.ts", + "sideEffects": [ + "./dist/*/auto.js", + "./dist/node/apply-auto-instrumentation.js", + "./dist/hooks/hook.mjs" + ], "exports": { - "./package.json": "./package.json", ".": { - "types": "./dist/index.d.ts", - "edge-light": "./dist/edge-light.mjs", - "workerd": "./dist/workerd.mjs", - "node": { - "import": "./dist/index.mjs", - "require": "./dist/index.js" - }, - "browser": "./dist/browser.mjs", - "import": "./dist/index.mjs", - "require": "./dist/index.js", - "default": "./dist/index.mjs" + "types": "./dist/node/index.d.ts", + "workerd": "./dist/workerd/index.js", + "edge-light": "./dist/edge-light/index.js", + "browser": "./dist/browser/index.js", + "node": "./dist/node/index.js", + "default": "./dist/browser/index.js" + }, + "./package.json": "./package.json", + "./auto": { + "types": "./dist/node/auto.d.ts", + "workerd": "./dist/workerd/auto.js", + "edge-light": "./dist/edge-light/auto.js", + "browser": "./dist/browser/auto.js", + "node": "./dist/node/auto.js", + "default": "./dist/browser/auto.js" }, "./custom-views": { - "types": "./dist/custom-views.d.ts", - "import": "./dist/custom-views.mjs", - "module": "./dist/custom-views.mjs", - "require": "./dist/custom-views.js", - "default": "./dist/custom-views.mjs" + "types": "./dist/node/custom-views.d.ts", + "workerd": "./dist/workerd/custom-views.js", + "edge-light": "./dist/edge-light/custom-views.js", + "browser": "./dist/browser/custom-views.js", + "node": "./dist/node/custom-views.js", + "default": "./dist/browser/custom-views.js" }, "./preprocessors": { - "types": "./dist/preprocessors.d.ts", - "import": "./dist/preprocessors.mjs", - "module": "./dist/preprocessors.mjs", - "require": "./dist/preprocessors.js", - "default": "./dist/preprocessors.mjs" + "types": "./dist/node/preprocessors.d.ts", + "workerd": "./dist/workerd/preprocessors.js", + "edge-light": "./dist/edge-light/preprocessors.js", + "browser": "./dist/browser/preprocessors.js", + "node": "./dist/node/preprocessors.js", + "default": "./dist/browser/preprocessors.js" + }, + "./instrumentation": { + "types": "./dist/node/instrumentation.d.ts", + "workerd": "./dist/workerd/instrumentation.js", + "edge-light": "./dist/edge-light/instrumentation.js", + "browser": "./dist/browser/instrumentation.js", + "node": "./dist/node/instrumentation.js", + "default": "./dist/browser/instrumentation.js" + }, + "./node": { + "types": "./dist/node/index.d.ts", + "default": "./dist/node/index.js" }, "./workerd": { - "import": "./dist/workerd.mjs", - "require": "./dist/workerd.js", - "default": "./dist/workerd.mjs" + "types": "./dist/workerd/index.d.ts", + "default": "./dist/workerd/index.js" }, "./edge-light": { - "import": "./dist/edge-light.mjs", - "require": "./dist/edge-light.js", - "default": "./dist/edge-light.mjs" - }, - "./browser": { - "import": "./dist/browser.mjs", - "require": "./dist/browser.js", - "default": "./dist/browser.mjs" + "types": "./dist/edge-light/index.d.ts", + "default": "./dist/edge-light/index.js" }, "./apply-auto-instrumentation": { - "types": "./dist/apply-auto-instrumentation.d.ts", - "edge-light": "./dist/apply-auto-instrumentation.browser.mjs", - "workerd": "./dist/apply-auto-instrumentation.browser.mjs", - "node": { - "import": "./dist/apply-auto-instrumentation.mjs", - "require": "./dist/apply-auto-instrumentation.js" - }, - "browser": "./dist/apply-auto-instrumentation.browser.mjs", - "import": "./dist/apply-auto-instrumentation.mjs", - "require": "./dist/apply-auto-instrumentation.js", - "default": "./dist/apply-auto-instrumentation.mjs" + "types": "./dist/node/apply-auto-instrumentation.d.ts", + "workerd": "./dist/workerd/apply-auto-instrumentation.js", + "edge-light": "./dist/edge-light/apply-auto-instrumentation.js", + "browser": "./dist/browser/apply-auto-instrumentation.js", + "node": "./dist/node/apply-auto-instrumentation.js", + "default": "./dist/browser/apply-auto-instrumentation.js" }, "./vitest-evals-reporter": { - "types": "./dist/vitest-evals-reporter.d.ts", - "import": "./dist/vitest-evals-reporter.mjs", - "module": "./dist/vitest-evals-reporter.mjs", - "require": "./dist/vitest-evals-reporter.js" + "types": "./dist/node/vitest-evals-reporter.d.ts", + "default": "./dist/node/vitest-evals-reporter.js" }, - "./node": { - "types": "./dist/index.d.ts", - "import": "./dist/index.mjs", - "module": "./dist/index.mjs", - "require": "./dist/index.js" - }, - "./dev": { - "types": "./dev/dist/index.d.ts", - "import": "./dev/dist/index.mjs", - "module": "./dev/dist/index.mjs", - "require": "./dev/dist/index.js" - }, - "./util": { - "types": "./util/dist/index.d.ts", - "import": "./util/dist/index.mjs", - "module": "./util/dist/index.mjs", - "require": "./util/dist/index.js" - }, - "./instrumentation": { - "types": "./dist/instrumentation/index.d.ts", - "import": "./dist/instrumentation/index.mjs", - "module": "./dist/instrumentation/index.mjs", - "require": "./dist/instrumentation/index.js" - }, - "./hook.mjs": "./dist/auto-instrumentations/hook.mjs", + "./hook.mjs": "./dist/hooks/hook.mjs", "./vite": { - "types": "./dist/auto-instrumentations/bundler/vite.d.ts", - "import": "./dist/auto-instrumentations/bundler/vite.mjs", - "module": "./dist/auto-instrumentations/bundler/vite.mjs", - "require": "./dist/auto-instrumentations/bundler/vite.cjs" + "types": "./dist/tooling/bundler/vite.d.ts", + "default": "./dist/tooling/bundler/vite.js" }, "./webpack": { - "types": "./dist/auto-instrumentations/bundler/webpack.d.ts", - "import": "./dist/auto-instrumentations/bundler/webpack.mjs", - "module": "./dist/auto-instrumentations/bundler/webpack.mjs", - "require": "./dist/auto-instrumentations/bundler/webpack.cjs" + "types": "./dist/tooling/bundler/webpack.d.ts", + "default": "./dist/tooling/bundler/webpack.js" }, "./next": { - "types": "./dist/auto-instrumentations/bundler/next.d.ts", - "import": "./dist/auto-instrumentations/bundler/next.mjs", - "module": "./dist/auto-instrumentations/bundler/next.mjs", - "require": "./dist/auto-instrumentations/bundler/next.cjs" - }, - "./webpack-loader": { - "types": "./dist/auto-instrumentations/bundler/webpack-loader.d.ts", - "require": "./dist/auto-instrumentations/bundler/webpack-loader.cjs" + "types": "./dist/tooling/bundler/next.d.ts", + "default": "./dist/tooling/bundler/next.js" }, "./esbuild": { - "types": "./dist/auto-instrumentations/bundler/esbuild.d.ts", - "import": "./dist/auto-instrumentations/bundler/esbuild.mjs", - "module": "./dist/auto-instrumentations/bundler/esbuild.mjs", - "require": "./dist/auto-instrumentations/bundler/esbuild.cjs" + "types": "./dist/tooling/bundler/esbuild.d.ts", + "default": "./dist/tooling/bundler/esbuild.js" }, "./rollup": { - "types": "./dist/auto-instrumentations/bundler/rollup.d.ts", - "import": "./dist/auto-instrumentations/bundler/rollup.mjs", - "module": "./dist/auto-instrumentations/bundler/rollup.mjs", - "require": "./dist/auto-instrumentations/bundler/rollup.cjs" + "types": "./dist/tooling/bundler/rollup.d.ts", + "default": "./dist/tooling/bundler/rollup.js" } }, "files": [ "dist/**/*", - "dev/dist/**/*", - "util/dist/**/*", - "bin/bt", - "scripts/bt-helper.js", - "scripts/install.js", "NOTICE", "licenses/**/*" ], "scripts": { - "postinstall": "node ./scripts/install.js", - "build": "cross-env NODE_OPTIONS=\"--max-old-space-size=8192\" tsup", + "build": "vp run compile", "check:typings": "tsc --noEmit && tsc --noEmit -p tsconfig.test.json", - "watch": "tsup --watch", - "clean": "rm -r dist/* && rm -r dev/dist/* && rm -r util/dist/*", + "watch": "vp pack --watch", + "clean": "rm -r dist/*", "docs": "typedoc --options typedoc.json src/node/index.ts", - "test": "vitest run --exclude \"src/wrappers/**/*.test.ts\" --exclude \"src/otel/**/*.test.ts\" --exclude \"smoke/**/*.test.ts\" --exclude \"src/zod/**/*.test.ts\" --exclude \"tests/api-compatibility/**\"", + "test": "vp test run --exclude \"src/wrappers/**/*.test.ts\" --exclude \"src/otel/**/*.test.ts\" --exclude \"smoke/**/*.test.ts\" --exclude \"tests/api-compatibility/**\"", "test:core": "pnpm prune && pnpm test", "test:checks": "pnpm run test:core && pnpm run test:vitest", "test:all": "pnpm run test:checks", - "test:api-compat": "vitest run tests/api-compatibility/api-compatibility.test.ts", - "test:otel": "vitest run --dir src/otel", - "test:otel-no-deps": "vitest run src/otel/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:zod-v3": "vitest run src/zod/zod-v3-serialization.test.ts", - "test:zod-v4": "vitest run src/zod/zod-v4-serialization.test.ts", - "test:mastra": "vitest run src/wrappers/mastra.test.ts", + "test:api-compat": "vp test run tests/api-compatibility/api-compatibility.test.ts", + "test:otel": "vp test run --dir src/otel", + "test:otel-no-deps": "vp test run src/otel/otel-no-deps.test.ts --reporter=verbose", + "test:ai-sdk-v1": "vp test run src/wrappers/ai-sdk-v1.test.ts", + "test:ai-sdk-v2": "vp test run src/wrappers/ai-sdk-v2.test.ts src/wrappers/ai-sdk-v1.test.ts", + "test:mastra": "vp test run src/wrappers/mastra.test.ts", "test:vitest": "pnpm --filter @braintrust/vitest-wrapper-tests test", - "test:output": "tsx scripts/test-output.ts --with-comparison --with-metrics --with-progress", "bench": "tsx src/queue.bench.ts", - "lint": "eslint .", - "fix:lint": "eslint --fix .", + "lint": "vp lint .", + "fix:lint": "vp lint --fix .", "playground": "tsx playground.ts", - "playground:auto": "mkdir -p .context && pnpm exec esbuild playground.ts --platform=node --format=esm --outfile=.context/playground.auto.mjs && node --import ./dist/auto-instrumentations/hook.mjs ./.context/playground.auto.mjs", - "playground:cli:push": "node dist/cli.js push playground.ts", - "playground:cli:eval": "node dist/cli.js eval playground.ts", + "playground:auto": "mkdir -p .context && pnpm exec esbuild playground.ts --platform=node --format=esm --outfile=.context/playground.auto.mjs && node --import ./dist/hooks/hook.mjs ./.context/playground.auto.mjs", "yalc:publish": "yalc publish" }, "author": "", "license": "MIT", + "engines": { + "node": ">=22.18.0" + }, "devDependencies": { "@anthropic-ai/sdk": "^0.60.0", - "@nodelib/fs.walk": "^1.2.8", - "@types/argparse": "^2.0.14", "@types/async": "^3.2.24", - "@types/cli-progress": "^3.11.5", - "@types/cors": "^2.8.17", "@types/esquery": "^1.5.4", - "@types/express": "^5.0.0", - "@types/http-errors": "^2.0.4", "@types/mustache": "^4.2.5", "@types/node": "^20.10.5", - "@types/pluralize": "^0.0.30", "@types/react": "^18.3.1", - "@typescript-eslint/eslint-plugin": "^8.49.0", - "@typescript-eslint/parser": "^8.49.0", "ai": "^6.0.0", "async": "^3.2.5", "cross-env": "^7.0.3", - "eslint": "^9.39.2", - "eslint-plugin-es-x": "9.7.0", + "esbuild": "0.28.1", "eslint-plugin-n": "^18.2.1", - "eslint-plugin-node-import": "^1.0.5", "openai": "6.25.0", "rollup": "^4.60.3", "tar": "^7.5.22", "tinybench": "^4.0.1", "ts-declaration-location": "^1.0.7", - "tsup": "^8.5.1", "tsx": "^4.21.0", "typedoc": "^0.25.13", "typedoc-plugin-markdown": "^3.17.1", "typescript": "5.4.4", - "vite": "^8.1.5", - "vite-tsconfig-paths": "^4.3.2", - "vitest": "4.1.11", + "vite": "catalog:", + "vite-plus": "catalog:", "webpack": "^5.106.2", - "zod": "^3.25.34" + "zod": "^3.25.34", + "vitest": "catalog:" }, "dependencies": { - "@next/env": "^14.2.3", "@vercel/functions": "^1.0.2", "acorn": "^8.16.0", "acorn-import-attributes": "^1.9.5", "ajv": "^8.20.0", - "argparse": "^2.0.1", "astring": "^1.9.0", "cjs-module-lexer": "^2.2.0", - "cli-progress": "^3.12.0", - "cli-table3": "^0.6.5", - "cors": "^2.8.5", "dotenv": "^16.4.5", - "esbuild": "0.28.1", "esquery": "^1.7.0", "eventsource-parser": "^1.1.2", - "express": "^5.2.1", - "http-errors": "^2.0.0", "meriyah": "^6.1.4", - "minimatch": "^10.2.5", "module-details-from-path": "^1.0.4", "mustache": "^4.2.0", - "pluralize": "^8.0.0", "semifies": "^1.0.0", "source-map": "^0.7.4", - "termi-link": "^1.0.1", "unplugin": "^2.3.5", "uuid": "^11.1.1", "zod-to-json-schema": "^3.25.0" @@ -266,18 +193,10 @@ "optional": true } }, - "optionalDependencies": { - "@braintrust/bt-darwin-arm64": "0.12.0", - "@braintrust/bt-darwin-x64": "0.12.0", - "@braintrust/bt-linux-arm64": "0.12.0", - "@braintrust/bt-linux-x64": "0.12.0", - "@braintrust/bt-linux-x64-musl": "0.12.0", - "@braintrust/bt-win32-arm64": "0.12.0", - "@braintrust/bt-win32-x64": "0.12.0" - }, "publishConfig": { "access": "public", "registry": "https://registry.npmjs.org/", "provenance": true - } + }, + "type": "module" } diff --git a/js/scripts/benchmark-imports.mjs b/js/scripts/benchmark-imports.mjs new file mode 100644 index 000000000..e4870e49a --- /dev/null +++ b/js/scripts/benchmark-imports.mjs @@ -0,0 +1,85 @@ +import { execFileSync } from "node:child_process"; +import { mkdir, writeFile, symlink } from "node:fs/promises"; +import { fileURLToPath } from "node:url"; +import path from "node:path"; +import { build } from "esbuild"; +import { gzipSync, brotliCompressSync } from "node:zlib"; + +const root = fileURLToPath(new URL("../../", import.meta.url)); +const outputDir = path.join(root, ".context/bundling"); +await mkdir(outputDir, { recursive: true }); +const baseline = process.argv[2]; +const entries = { + root: path.join(root, "js/dist/node/index.js"), + ...(baseline ? { baseline: path.resolve(baseline) } : {}), +}; +// An optional baseline build uses the same installed runtime dependency versions. +if (baseline) { + try { + await symlink( + path.join(root, "js/node_modules"), + path.join(path.dirname(baseline), "node_modules"), + "dir", + ); + } catch (error) { + if (error.code !== "EEXIST") throw error; + } +} +const report = {}; +for (const [name, entry] of Object.entries(entries)) { + const samples = []; + for (let index = 0; index < 15; index++) { + const sample = execFileSync( + process.execPath, + [ + "--input-type=module", + "-e", + ` + const start = performance.now(); + const sdk = await import(${JSON.stringify(entry)}); + const imported = performance.now(); + sdk.currentSpan(); + const used = performance.now(); + console.log(JSON.stringify({ importMs: imported - start, firstUseMs: used - imported, rss: process.memoryUsage().rss })); + process.exit(0); + `, + ], + { encoding: "utf8", env: { ...process.env, BRAINTRUST_DEBUG: "false" } }, + ); + samples.push(JSON.parse(sample)); + } + const sorted = (key) => + samples.map((sample) => sample[key]).sort((a, b) => a - b); + report[name] = { + importMedianMs: sorted("importMs")[7], + importP95Ms: sorted("importMs")[14], + firstUseMedianMs: sorted("firstUseMs")[7], + rssMedianBytes: sorted("rss")[7], + samples, + }; + const bundle = await build({ + stdin: { + contents: `import { traced } from ${JSON.stringify(entry)}; globalThis.run = traced;`, + resolveDir: root, + }, + nodePaths: [path.join(root, "js/node_modules")], + bundle: true, + minify: true, + platform: "node", + format: "esm", + write: false, + logLevel: "silent", + }); + const bytes = bundle.outputFiles[0].contents; + report[name].bundle = { + bytes: bytes.length, + gzip: gzipSync(bytes).length, + brotli: brotliCompressSync(bytes).length, + }; +} +await writeFile( + path.join(outputDir, "import-benchmark.json"), + JSON.stringify(report, null, 2) + "\n", +); +for (const [name, { samples, ...summary }] of Object.entries(report)) + console.log(name, summary); diff --git a/js/scripts/bt-helper.js b/js/scripts/bt-helper.js deleted file mode 100644 index 3502e5e07..000000000 --- a/js/scripts/bt-helper.js +++ /dev/null @@ -1,139 +0,0 @@ -"use strict"; - -// Shared helpers for locating the `bt` binary. Used by both `bin/bt` -// (the runtime launcher) and `scripts/install.js` (the postinstall -// fallback downloader). - -const fs = require("node:fs"); -const os = require("node:os"); -const path = require("node:path"); - -const BINARY_DISTRIBUTIONS = [ - { packageName: "@braintrust/bt-darwin-arm64", subpath: "bin/bt" }, - { packageName: "@braintrust/bt-darwin-x64", subpath: "bin/bt" }, - { packageName: "@braintrust/bt-linux-arm64", subpath: "bin/bt" }, - { packageName: "@braintrust/bt-linux-x64", subpath: "bin/bt" }, - { packageName: "@braintrust/bt-linux-x64-musl", subpath: "bin/bt" }, - { packageName: "@braintrust/bt-win32-arm64", subpath: "bin/bt.exe" }, - { packageName: "@braintrust/bt-win32-x64", subpath: "bin/bt.exe" }, -]; - -function detectLibc() { - if (process.platform !== "linux") return null; - try { - const report = process.report && process.report.getReport(); - if (report && report.header && report.header.glibcVersionRuntime) { - return "glibc"; - } - return "musl"; - } catch { - return "glibc"; - } -} - -function binaryName() { - return process.platform === "win32" ? "bt.exe" : "bt"; -} - -function getDistributionForThisPlatform() { - const arch = os.arch(); - const platform = os.platform(); - const subpath = `bin/${binaryName()}`; - - let packageName; - if (platform === "darwin") { - if (arch === "arm64") packageName = "@braintrust/bt-darwin-arm64"; - else if (arch === "x64") packageName = "@braintrust/bt-darwin-x64"; - } else if (platform === "linux") { - if (arch === "arm64") { - packageName = "@braintrust/bt-linux-arm64"; - } else if (arch === "x64") { - packageName = - detectLibc() === "musl" - ? "@braintrust/bt-linux-x64-musl" - : "@braintrust/bt-linux-x64"; - } - } else if (platform === "win32") { - if (arch === "arm64") packageName = "@braintrust/bt-win32-arm64"; - else if (arch === "x64") packageName = "@braintrust/bt-win32-x64"; - } - - return { packageName, subpath }; -} - -function throwUnsupportedPlatformError() { - throw new Error( - `Unsupported operating system or architecture! The bt CLI does not work on ${process.platform}-${process.arch}. - -bt supports: -- macOS (darwin) on arm64 and x64 -- Linux on arm64 and x64 (glibc and musl) -- Windows on arm64 and x64`, - ); -} - -// Constructed indirectly so bundlers (e.g. @vercel/nft) don't statically -// detect the fallback binary path as an asset to trace. -function getFallbackBinaryPath() { - const parts = [__dirname, binaryName()]; - return path.resolve(...parts); -} - -function getBinaryPath() { - if (process.env.BT_BINARY_PATH) { - return process.env.BT_BINARY_PATH; - } - - const { packageName, subpath } = getDistributionForThisPlatform(); - - if (packageName === undefined) { - throwUnsupportedPlatformError(); - } - - // Prefer the optional dep so a stale fallback from a prior - // `--omit=optional` install can't shadow a newer optional dep on upgrade. - try { - return require.resolve(`${packageName}/${subpath}`); - } catch (e) { - const fallbackBinaryPath = getFallbackBinaryPath(); - if (fs.existsSync(fallbackBinaryPath)) { - return fallbackBinaryPath; - } - - const otherInstalled = BINARY_DISTRIBUTIONS.find((dist) => { - try { - require.resolve(`${dist.packageName}/${dist.subpath}`); - return true; - } catch { - return false; - } - }); - - // Error messages inspired by esbuild: - // https://github.com/evanw/esbuild/blob/f3d535262e3998d845d0f102b944ecd5a9efda57/lib/npm/node-platform.ts#L150 - if (otherInstalled) { - throw new Error( - `bt binary for this platform/architecture not found! - -The "${otherInstalled.packageName}" package is installed, but for the current platform you should have the "${packageName}" package installed instead. This usually happens if "braintrust" is installed on one platform (for example macOS or Windows) and the "node_modules" folder is then reused on another (for example Linux in Docker). - -To fix this, avoid copying the "node_modules" folder, and instead freshly install your dependencies on the target system. You can also configure your package manager to install the right package. For example, yarn has the "supportedArchitectures" feature: https://yarnpkg.com/configuration/yarnrc/#supportedArchitecture.`, - ); - } - - throw new Error( - `bt binary for this platform/architecture not found! - -It seems like none of the "braintrust" package's optional dependencies got installed. Please make sure your package manager is configured to install optional dependencies. If you are using npm, don't set the "--no-optional", "--ignore-optional", or "--omit=optional" flags. The "braintrust" package needs the "optionalDependencies" feature in order to install the bt binary.`, - ); - } -} - -module.exports = { - BINARY_DISTRIBUTIONS, - binaryName, - getBinaryPath, - getDistributionForThisPlatform, - getFallbackBinaryPath, - throwUnsupportedPlatformError, -}; diff --git a/js/scripts/install.js b/js/scripts/install.js deleted file mode 100644 index 0f200f140..000000000 --- a/js/scripts/install.js +++ /dev/null @@ -1,153 +0,0 @@ -"use strict"; - -// Postinstall script for the `bt` CLI binary. -// -// The native binary ships in a per-platform `@braintrust/bt-*` package listed -// in `optionalDependencies`; npm/pnpm install only the one matching the host. -// If a package manager is run with `--no-optional`, `--ignore-optional`, or -// `--omit=optional`, none of those packages get installed and `bt` would be -// unusable. As a workaround, we manually fetch the matching tarball from the -// npm registry and extract the binary so the launcher can find it. - -const fs = require("node:fs"); -const https = require("node:https"); -const path = require("node:path"); -const zlib = require("node:zlib"); - -const helper = require("./bt-helper"); -const pkg = require("../package.json"); - -if (process.env.BT_SKIP_DOWNLOAD === "1") { - console.log( - "bt: skipping post-install binary download because BT_SKIP_DOWNLOAD=1 is set.", - ); - process.exit(0); -} - -const { packageName, subpath } = helper.getDistributionForThisPlatform(); - -if (packageName === undefined) { - // Don't fail the install; the launcher will surface the unsupported-platform - // error if/when the user actually tries to run `bt`. - console.error( - `bt: no prebuilt binary available for ${process.platform}-${process.arch}; the bt CLI will not be available.`, - ); - process.exit(0); -} - -try { - require.resolve(`${packageName}/${subpath}`); - // Optional dependency was installed successfully. Nothing to do. - process.exit(0); -} catch (e) { - // Fall through to the manual download path below. - console.log( - `bt: failed to locate the "${packageName}" package after installation. - -This can happen if you use an option to disable optional dependencies during installation, like "--no-optional", "--ignore-optional", or "--omit=optional". The "braintrust" package uses the "optionalDependencies" package.json feature to install the correct bt binary for your platform and operating system. This post-install script will now try to work around that by manually downloading the bt binary from the npm registry. If this fails, you need to remove the "--no-optional", "--ignore-optional", and "--omit=optional" flags for bt to work.`, - ); -} - -const version = (pkg.optionalDependencies || {})[packageName]; -if (!version) { - // Don't fail the parent install: the SDK works without `bt`, and the - // launcher errors clearly if it's actually invoked. - console.error( - `bt: cannot determine which version of "${packageName}" to download — it is not listed in the "braintrust" package's optionalDependencies. The bt CLI will not be available; the rest of the braintrust SDK is unaffected.`, - ); - process.exit(0); -} - -function fetchBuffer(url, redirectsRemaining = 5) { - return new Promise((resolve, reject) => { - https - .get(url, (response) => { - const { statusCode = 0, headers } = response; - if (statusCode >= 200 && statusCode < 300) { - const chunks = []; - response.on("data", (chunk) => chunks.push(chunk)); - response.on("end", () => resolve(Buffer.concat(chunks))); - response.on("error", reject); - return; - } - if ( - statusCode >= 300 && - statusCode < 400 && - headers.location && - redirectsRemaining > 0 - ) { - response.resume(); - fetchBuffer(headers.location, redirectsRemaining - 1).then( - resolve, - reject, - ); - return; - } - response.resume(); - reject( - new Error( - `npm registry responded with status code ${statusCode} when downloading ${url}`, - ), - ); - }) - .on("error", reject); - }); -} - -// Extracts a single file from an uncompressed tar archive. Tar archives are -// organized in 512-byte blocks: a header block (file name in bytes 0-99, -// file size in bytes 124-135 as an octal string) followed by data blocks -// padded out to the next multiple of 512. -function extractFileFromTarball(tarball, target) { - let offset = 0; - while (offset + 512 <= tarball.length) { - const header = tarball.subarray(offset, offset + 512); - offset += 512; - const fileName = header.toString("utf-8", 0, 100).replace(/\0.*/g, ""); - if (!fileName) break; - const fileSize = parseInt( - header.toString("utf-8", 124, 136).replace(/\0.*/g, ""), - 8, - ); - if (fileName === target) { - return tarball.subarray(offset, offset + fileSize); - } - offset = (offset + fileSize + 511) & ~511; - } - return null; -} - -async function downloadFallback() { - // npm tarball URLs look like: - // https://registry.npmjs.org///-/-.tgz - // where is the unscoped package name. - const tarballName = packageName.split("/").pop(); - const url = `https://registry.npmjs.org/${packageName}/-/${tarballName}-${version}.tgz`; - console.log(`bt: downloading ${packageName}@${version} from ${url}`); - - const gzipped = await fetchBuffer(url); - const tarball = zlib.gunzipSync(gzipped); - const binary = extractFileFromTarball(tarball, `package/${subpath}`); - - if (!binary) { - throw new Error( - `could not find "package/${subpath}" inside ${packageName}@${version} tarball`, - ); - } - - const fallbackBinaryPath = helper.getFallbackBinaryPath(); - fs.mkdirSync(path.dirname(fallbackBinaryPath), { recursive: true }); - fs.writeFileSync(fallbackBinaryPath, binary); - fs.chmodSync(fallbackBinaryPath, 0o755); - console.log(`bt: installed fallback binary at ${fallbackBinaryPath}`); -} - -downloadFallback().catch((err) => { - // Don't fail the parent install: airgapped/proxied CI may not reach the - // npm registry, and the SDK works without `bt`. - console.error( - `bt: failed to download fallback binary for ${packageName}@${version}: ${err.message} -The bt CLI will not be available; the rest of the braintrust SDK is unaffected.`, - ); - process.exit(0); -}); diff --git a/js/scripts/pull-vendor.sh b/js/scripts/pull-vendor.sh index d5c7844a9..2fee7812f 100755 --- a/js/scripts/pull-vendor.sh +++ b/js/scripts/pull-vendor.sh @@ -31,7 +31,7 @@ show_usage() { echo " versions - Comma-separated list of major versions to track (e.g., v3,v4,v5)" echo "" echo "Examples:" - echo " $0 https://github.com/vercel/ai.git v3,v4,v5" + echo " $0 https://github.com/vercel/ai.git v4,v5,v6,v7" echo " $0 https://github.com/openai/openai-node.git v3,v4" echo " $0 https://github.com/anthropics/anthropic-sdk-typescript.git v0.20,v0.21" echo "" @@ -285,7 +285,7 @@ Run the pull script to update to the latest tags: cd sdk/js # Example for vercel/ai (uses ai@X.Y.Z tag format) -./scripts/pull-vendor.sh https://github.com/vercel/ai.git v3,v4,v5,v6 +./scripts/pull-vendor.sh https://github.com/vercel/ai.git v4,v5,v6,v7 # Example for OpenAI SDK ./scripts/pull-vendor.sh https://github.com/openai/openai-node.git v3,v4 diff --git a/js/scripts/test-output.ts b/js/scripts/test-output.ts deleted file mode 100644 index 8a9ca0858..000000000 --- a/js/scripts/test-output.ts +++ /dev/null @@ -1,194 +0,0 @@ -#!/usr/bin/env tsx - -/** - * Test script to preview CLI output formatting without running actual experiments. - * - * Usage: - * pnpm tsx scripts/test-output.ts - * pnpm tsx scripts/test-output.ts --with-comparison - * pnpm tsx scripts/test-output.ts --with-metrics - * pnpm tsx scripts/test-output.ts --with-progress - */ - -import { formatExperimentSummary } from "../src/framework"; -import type { ExperimentSummary } from "../src/logger"; -import { BarProgressReporter } from "../src/cli/progress"; - -function createSampleSummary(options: { - withComparison?: boolean; - withMetrics?: boolean; -}): ExperimentSummary { - const summary: ExperimentSummary = { - projectName: "my-project", - experimentName: "test-experiment", - experimentId: "exp-123", - projectId: "proj-456", - experimentUrl: - "https://www.braintrust.dev/app/my-org/p/my-project/experiments/test-experiment", - projectUrl: "https://www.braintrust.dev/app/my-org/p/my-project", - scores: { - accuracy: { - name: "Accuracy", - score: 0.95, - diff: options.withComparison ? 0.02 : undefined, - improvements: options.withComparison ? 15 : 0, - regressions: options.withComparison ? 3 : 0, - }, - factuality: { - name: "Factuality", - score: 0.87, - diff: options.withComparison ? -0.01 : undefined, - improvements: options.withComparison ? 8 : 0, - regressions: options.withComparison ? 12 : 0, - }, - }, - }; - - if (options.withMetrics) { - summary.metrics = { - duration: { - name: "Duration", - metric: 1.23, - unit: "s", - diff: options.withComparison ? -0.15 : undefined, - improvements: options.withComparison ? 10 : 0, - regressions: options.withComparison ? 5 : 0, - }, - llm_duration: { - name: "LLM duration", - metric: 0.45, - unit: "s", - diff: options.withComparison ? -0.08 : undefined, - improvements: options.withComparison ? 12 : 0, - regressions: options.withComparison ? 3 : 0, - }, - prompt_tokens: { - name: "Prompt tokens", - metric: 4282, - unit: "", - diff: options.withComparison ? 0.035 : undefined, - improvements: options.withComparison ? 0 : 0, - regressions: options.withComparison ? 0 : 0, - }, - completion_tokens: { - name: "Completion tokens", - metric: 310, - unit: "", - diff: options.withComparison ? -0.08 : undefined, - improvements: options.withComparison ? 0 : 0, - regressions: options.withComparison ? 0 : 0, - }, - total_tokens: { - name: "Total tokens", - metric: 4592, - unit: "", - diff: options.withComparison ? 0.027 : undefined, - improvements: options.withComparison ? 0 : 0, - regressions: options.withComparison ? 0 : 0, - }, - estimated_cost: { - name: "Estimated cost", - metric: 0.01, - unit: "$", - diff: options.withComparison ? 0.2 : undefined, - improvements: options.withComparison ? 0 : 0, - regressions: options.withComparison ? 0 : 0, - }, - }; - } - - if (options.withComparison) { - summary.comparisonExperimentName = "baseline-experiment"; - } - - return summary; -} - -async function simulateProgress(evaluatorName: string, total: number) { - const progressReporter = new BarProgressReporter(); - progressReporter.start(evaluatorName, total); - - // Simulate progress with random delays - for (let i = 0; i < total; i++) { - await new Promise((resolve) => - setTimeout(resolve, 50 + Math.random() * 100), - ); - progressReporter.increment(evaluatorName); - } - - progressReporter.stop(); -} - -async function main() { - const args = process.argv.slice(2); - const withComparison = args.includes("--with-comparison"); - const withMetrics = args.includes("--with-metrics"); - const withProgress = args.includes("--with-progress"); - - console.log("\n" + "=".repeat(60)); - console.log("Testing CLI Output Formatting"); - console.log("=".repeat(60) + "\n"); - - // Test 1: Basic summary - console.log("1. Basic Summary (scores only):"); - console.log("-".repeat(60)); - - if (withProgress) { - await simulateProgress("test-experiment", 10); - } - - const basicSummary = createSampleSummary({}); - console.log(formatExperimentSummary(basicSummary)); - console.log("\n"); - - // Test 2: With comparison - if (withComparison) { - console.log("2. Summary with Comparison:"); - console.log("-".repeat(60)); - - if (withProgress) { - await simulateProgress("test-experiment", 15); - } - - const comparisonSummary = createSampleSummary({ withComparison: true }); - console.log(formatExperimentSummary(comparisonSummary)); - console.log("\n"); - } - - // Test 3: With metrics - if (withMetrics) { - console.log("3. Summary with Metrics:"); - console.log("-".repeat(60)); - - if (withProgress) { - await simulateProgress("test-experiment", 20); - } - - const metricsSummary = createSampleSummary({ withMetrics: true }); - console.log(formatExperimentSummary(metricsSummary)); - console.log("\n"); - } - - // Test 4: Full summary - console.log("4. Full Summary (scores + metrics + comparison):"); - console.log("-".repeat(60)); - - if (withProgress) { - await simulateProgress("test-experiment", 25); - } - - const fullSummary = createSampleSummary({ - withComparison: true, - withMetrics: true, - }); - console.log(formatExperimentSummary(fullSummary)); - console.log("\n"); - - console.log("=".repeat(60)); - console.log( - "Done! Use --with-comparison, --with-metrics, and --with-progress flags to see more variations.", - ); - console.log("=".repeat(60) + "\n"); -} - -main(); diff --git a/js/smoke/Makefile b/js/smoke/Makefile index c610ebee5..4a8e9fe5c 100644 --- a/js/smoke/Makefile +++ b/js/smoke/Makefile @@ -57,10 +57,10 @@ setup: echo "==> Using provided BRAINTRUST_TAR: $(BRAINTRUST_TAR)"; \ else \ echo "==> Building SDK and creating tarball"; \ - cd ../../.. && pnpm exec turbo build --filter=braintrust && pnpm --filter=braintrust pack --pack-destination sdk/js/artifacts; \ - for f in sdk/js/artifacts/braintrust-*.tgz; do \ + cd ../.. && pnpm --filter=braintrust run build && pnpm --filter=braintrust pack --pack-destination js/artifacts; \ + for f in js/artifacts/braintrust-*.tgz; do \ if [ "$$(basename $$f)" != "braintrust-latest.tgz" ] && [ "$$(basename $$f)" != "braintrust-otel-latest.tgz" ] && [ "$$(basename $$f)" != "braintrust-browser-latest.tgz" ]; then \ - cp "$$f" sdk/js/artifacts/braintrust-latest.tgz; \ + cp "$$f" js/artifacts/braintrust-latest.tgz; \ break; \ fi; \ done; \ @@ -71,10 +71,10 @@ setup: echo "==> Using provided BRAINTRUST_BROWSER_TAR: $(BRAINTRUST_BROWSER_TAR)"; \ else \ echo "==> Building browser package and creating tarball"; \ - cd ../../.. && pnpm exec turbo build --filter=@braintrust/browser && pnpm --filter=@braintrust/browser pack --pack-destination sdk/js/artifacts; \ - for f in sdk/js/artifacts/braintrust-browser-*.tgz; do \ + cd ../.. && pnpm --filter=@braintrust/browser run build && pnpm --filter=@braintrust/browser pack --pack-destination js/artifacts; \ + for f in js/artifacts/braintrust-browser-*.tgz; do \ if [ "$$(basename $$f)" != "braintrust-browser-latest.tgz" ]; then \ - cp "$$f" sdk/js/artifacts/braintrust-browser-latest.tgz; \ + cp "$$f" js/artifacts/braintrust-browser-latest.tgz; \ break; \ fi; \ done; \ diff --git a/js/smoke/README.md b/js/smoke/README.md index 72a0fd530..2b47615e8 100644 --- a/js/smoke/README.md +++ b/js/smoke/README.md @@ -29,7 +29,7 @@ import { testBasicSpanLogging, testMultipleSpans, // ... other test functions -} from "../../shared/dist/index.mjs"; +} from "../../shared/dist/index.js"; import * as braintrust from "braintrust"; diff --git a/js/smoke/scenarios/browser-main-package/README.md b/js/smoke/scenarios/browser-main-package/README.md index 1b78d3562..58a6f72fb 100644 --- a/js/smoke/scenarios/browser-main-package/README.md +++ b/js/smoke/scenarios/browser-main-package/README.md @@ -1,6 +1,6 @@ # Browser Main Package Smoke Test -This smoke test verifies that the informational message appears when using the browser build from the main `braintrust` package. +This smoke test verifies the curated browser API exposed by the main `braintrust` package. ## What This Tests @@ -10,11 +10,12 @@ When a user imports from the main `braintrust` package in a browser environment: import * as braintrust from "braintrust"; ``` -The bundler (via the `"browser"` field in package.json) will resolve to the browser build (`dist/browser.mjs`), which should: +The package's browser export condition resolves to `dist/browser.mjs`, which should: -1. Show an informational console message suggesting `@braintrust/browser` for optimal use -2. Provide working browser-safe implementations -3. Not include Node.js modules +1. Provide working browser-safe implementations +2. Expose the supported root API +3. Omit removed legacy and internal exports +4. Not include Node.js modules ## Test Structure @@ -44,21 +45,6 @@ pnpm exec playwright test ## What Gets Verified ✓ Import from main package works in browser -✓ Basic functions are available (init, newId, traceable) -✓ Informational message appears in console -✓ Message mentions "@braintrust/browser" package +✓ Supported functions are available (`init`, `flush`) +✓ Legacy exports such as `newId` and `traceable` are absent ✓ No Node.js module errors - -## Expected Console Output - -When the test runs, you should see: - -``` -Braintrust SDK Browser Build -You are using a browser-compatible build from the main package. -For optimal browser support consider: - npm install @braintrust/browser - import * as braintrust from "@braintrust/browser" -``` - -This message guides users toward the optimized `@braintrust/browser` package while ensuring the main package works correctly in browsers. diff --git a/js/smoke/scenarios/browser-main-package/src/browser-message-test.ts b/js/smoke/scenarios/browser-main-package/src/browser-message-test.ts index dd728e468..92f570a50 100644 --- a/js/smoke/scenarios/browser-main-package/src/browser-message-test.ts +++ b/js/smoke/scenarios/browser-main-package/src/browser-message-test.ts @@ -5,8 +5,8 @@ declare global { consoleMessages: string[]; importSuccessful: boolean; hasInit: boolean; - hasNewId: boolean; - hasTraceable: boolean; + hasFlush: boolean; + removedLegacyExports: boolean; }; } } @@ -22,13 +22,14 @@ console.info = (...args: any[]) => { // Import from main package browser export AFTER setting up console capture // This must be done dynamically to ensure console.info is overridden first -const braintrust = await import("braintrust/browser"); +const braintrust = await import("braintrust"); // Test that imports work const importSuccessful = true; const hasInit = typeof braintrust.init === "function"; -const hasNewId = typeof braintrust.newId === "function"; -const hasTraceable = typeof braintrust.traceable === "function"; +const hasFlush = typeof braintrust.flush === "function"; +const removedLegacyExports = + !("newId" in braintrust) && !("traceable" in braintrust); // Store results window.__btBrowserMessageTest = { @@ -36,8 +37,8 @@ window.__btBrowserMessageTest = { consoleMessages: capturedMessages, importSuccessful, hasInit, - hasNewId, - hasTraceable, + hasFlush, + removedLegacyExports, }; // Display results @@ -48,8 +49,8 @@ if (output) {
  • Import successful: ${importSuccessful ? "✓" : "✗"}
  • Has init function: ${hasInit ? "✓" : "✗"}
  • -
  • Has newId function: ${hasNewId ? "✓" : "✗"}
  • -
  • Has traceable function: ${hasTraceable ? "✓" : "✗"}
  • +
  • Has flush function: ${hasFlush ? "✓" : "✗"}
  • +
  • Legacy exports removed: ${removedLegacyExports ? "✓" : "✗"}
  • Console messages captured: ${capturedMessages.length}

Console Messages:

diff --git a/js/smoke/scenarios/browser-main-package/tests/browser-message.test.ts b/js/smoke/scenarios/browser-main-package/tests/browser-message.test.ts index 8c5d9fb7e..3a48a7f60 100644 --- a/js/smoke/scenarios/browser-main-package/tests/browser-message.test.ts +++ b/js/smoke/scenarios/browser-main-package/tests/browser-message.test.ts @@ -1,7 +1,7 @@ import { test, expect } from "@playwright/test"; test.describe("Braintrust Main Package Browser Build", () => { - test("should display informational message when using browser build", async ({ + test("should expose the curated root API in a browser", async ({ page, baseURL, }) => { @@ -55,7 +55,7 @@ test.describe("Braintrust Main Package Browser Build", () => { expect(testResults.completed).toBe(true); expect(testResults.importSuccessful).toBe(true); expect(testResults.hasInit).toBe(true); - expect(testResults.hasNewId).toBe(true); - expect(testResults.hasTraceable).toBe(true); + expect(testResults.hasFlush).toBe(true); + expect(testResults.removedLegacyExports).toBe(true); }); }); diff --git a/js/smoke/scenarios/cloudflare-vite-hono-vite-dev-node-esm/Makefile b/js/smoke/scenarios/cloudflare-vite-hono-vite-dev-node-esm/Makefile index cdc0573f2..1f9f42d3a 100644 --- a/js/smoke/scenarios/cloudflare-vite-hono-vite-dev-node-esm/Makefile +++ b/js/smoke/scenarios/cloudflare-vite-hono-vite-dev-node-esm/Makefile @@ -8,7 +8,7 @@ setup: echo "==> Using BRAINTRUST_TAR: $(BRAINTRUST_TAR)"; \ else \ echo "==> Building SDK"; \ - cd ../../.. && pnpm exec turbo build --filter=braintrust && mkdir -p artifacts && pnpm pack --pack-destination artifacts; \ + cd ../../.. && pnpm --filter=braintrust run build && mkdir -p artifacts && pnpm pack --pack-destination artifacts; \ \ for f in artifacts/braintrust-*.tgz; do \ [ "$$(basename $$f)" = "braintrust-latest.tgz" ] && continue; \ diff --git a/js/smoke/scenarios/cloudflare-vite-hono-vite-dev-node-esm/tests/node-esm-import.test.mjs b/js/smoke/scenarios/cloudflare-vite-hono-vite-dev-node-esm/tests/node-esm-import.test.mjs index 848eaef79..9314d2ac1 100644 --- a/js/smoke/scenarios/cloudflare-vite-hono-vite-dev-node-esm/tests/node-esm-import.test.mjs +++ b/js/smoke/scenarios/cloudflare-vite-hono-vite-dev-node-esm/tests/node-esm-import.test.mjs @@ -17,10 +17,7 @@ import { spawn } from "node:child_process"; import { rmSync } from "node:fs"; import { join, dirname } from "node:path"; import { fileURLToPath } from "node:url"; -import { - displayTestResults, - hasFailures, -} from "../../../shared/dist/index.mjs"; +import { displayTestResults, hasFailures } from "../../../shared/dist/index.js"; const __filename = fileURLToPath(import.meta.url); const __dirname = dirname(__filename); @@ -86,7 +83,7 @@ async function testViteDevServerWithNodeEsm() { details: "Vite's dependency pre-bundler cannot handle Nunjucks' use of Object.setPrototypeOf when pre-bundling braintrust/node", recommendation: - "Use 'braintrust/browser' import or configure Vite to exclude Nunjucks from optimization", + "Import 'braintrust' through its browser condition or configure Vite to exclude Nunjucks from optimization", }; } @@ -163,7 +160,7 @@ async function testViteDevServerWithNodeEsm() { "Expected failure: Nunjucks incompatibility with Vite bundler when using braintrust/node. " + "Root cause: Nunjucks uses Object.setPrototypeOf in ways incompatible with Vite's ESM bundler. " + "The Node.js ESM build includes Nunjucks, which causes this error during Vite's dependency pre-bundling. " + - "Recommendation: Use 'braintrust/browser' import or exclude Nunjucks from Vite optimization", + "Recommendation: Import 'braintrust' through its browser condition or exclude Nunjucks from Vite optimization", error: errorStack ? { message: testResult.error, diff --git a/js/smoke/scenarios/cloudflare-vite-hono-vite-dev/Makefile b/js/smoke/scenarios/cloudflare-vite-hono-vite-dev/Makefile index d947f67c2..666033c40 100644 --- a/js/smoke/scenarios/cloudflare-vite-hono-vite-dev/Makefile +++ b/js/smoke/scenarios/cloudflare-vite-hono-vite-dev/Makefile @@ -8,7 +8,7 @@ setup: echo "==> Using BRAINTRUST_TAR: $(BRAINTRUST_TAR)"; \ else \ echo "==> Building SDK"; \ - cd ../../.. && pnpm exec turbo build --filter=braintrust && mkdir -p artifacts && pnpm pack --pack-destination artifacts; \ + cd ../../.. && pnpm --filter=braintrust run build && mkdir -p artifacts && pnpm pack --pack-destination artifacts; \ \ for f in artifacts/braintrust-*.tgz; do \ [ "$$(basename $$f)" = "braintrust-latest.tgz" ] && continue; \ diff --git a/js/smoke/scenarios/cloudflare-vite-hono-vite-dev/README.md b/js/smoke/scenarios/cloudflare-vite-hono-vite-dev/README.md index 2485bd93a..88af0b7e8 100644 --- a/js/smoke/scenarios/cloudflare-vite-hono-vite-dev/README.md +++ b/js/smoke/scenarios/cloudflare-vite-hono-vite-dev/README.md @@ -10,7 +10,7 @@ Tests whether the Braintrust SDK (browser build) can be loaded in Vite's dev ser **Hono framework:** Uses Hono for elegant routing instead of raw Worker API. -**Browser build:** Uses `braintrust/browser` import since Cloudflare Workers don't support Node.js APIs. +**Browser build:** Imports `braintrust`; the package's browser condition selects the browser-safe build because Cloudflare Workers don't support Node.js APIs. ## Test Suite diff --git a/js/smoke/scenarios/cloudflare-vite-hono-vite-dev/tests/vite-dev.test.mjs b/js/smoke/scenarios/cloudflare-vite-hono-vite-dev/tests/vite-dev.test.mjs index e9c1a0dc8..7cce14856 100755 --- a/js/smoke/scenarios/cloudflare-vite-hono-vite-dev/tests/vite-dev.test.mjs +++ b/js/smoke/scenarios/cloudflare-vite-hono-vite-dev/tests/vite-dev.test.mjs @@ -9,7 +9,7 @@ import { spawn, execSync } from "node:child_process"; import { rmSync } from "node:fs"; -import { displayTestResults } from "../../../shared/dist/index.mjs"; +import { displayTestResults } from "../../../shared/dist/index.js"; const PORT = 5173; const MAX_RETRIES = 40; diff --git a/js/smoke/scenarios/cloudflare-vite-hono-wrangler-dev/Makefile b/js/smoke/scenarios/cloudflare-vite-hono-wrangler-dev/Makefile index e27f0b111..f698ea410 100644 --- a/js/smoke/scenarios/cloudflare-vite-hono-wrangler-dev/Makefile +++ b/js/smoke/scenarios/cloudflare-vite-hono-wrangler-dev/Makefile @@ -8,7 +8,7 @@ setup: echo "==> Using BRAINTRUST_TAR: $(BRAINTRUST_TAR)"; \ else \ echo "==> Building SDK"; \ - cd ../../.. && pnpm exec turbo build --filter=braintrust && mkdir -p artifacts && pnpm pack --pack-destination artifacts; \ + cd ../../.. && pnpm --filter=braintrust run build && mkdir -p artifacts && pnpm pack --pack-destination artifacts; \ \ for f in artifacts/braintrust-*.tgz; do \ [ "$$(basename $$f)" = "braintrust-latest.tgz" ] && continue; \ diff --git a/js/smoke/scenarios/cloudflare-vite-hono-wrangler-dev/README.md b/js/smoke/scenarios/cloudflare-vite-hono-wrangler-dev/README.md index 77b51ac01..de82ebdfb 100644 --- a/js/smoke/scenarios/cloudflare-vite-hono-wrangler-dev/README.md +++ b/js/smoke/scenarios/cloudflare-vite-hono-wrangler-dev/README.md @@ -10,7 +10,7 @@ Tests Braintrust SDK in a Cloudflare Workers environment with Vite bundling via **Hono framework:** Uses Hono for elegant routing instead of raw Worker API. -**Browser build:** Uses `braintrust/browser` import since Cloudflare Workers don't support Node.js APIs. +**Browser build:** Imports `braintrust`; the package's browser condition selects the browser-safe build because Cloudflare Workers don't support Node.js APIs. **Wrangler dev:** Tests via Wrangler dev server, which uses Vite to build the worker. diff --git a/js/smoke/scenarios/cloudflare-vite-hono-wrangler-dev/tests/worker.test.mjs b/js/smoke/scenarios/cloudflare-vite-hono-wrangler-dev/tests/worker.test.mjs index 953386606..dbf0d8c9a 100644 --- a/js/smoke/scenarios/cloudflare-vite-hono-wrangler-dev/tests/worker.test.mjs +++ b/js/smoke/scenarios/cloudflare-vite-hono-wrangler-dev/tests/worker.test.mjs @@ -1,8 +1,5 @@ import { spawn, execSync } from "node:child_process"; -import { - displayTestResults, - hasFailures, -} from "../../../shared/dist/index.mjs"; +import { displayTestResults, hasFailures } from "../../../shared/dist/index.js"; const PORT = 8800; const MAX_RETRIES = 20; diff --git a/js/smoke/scenarios/cloudflare-worker-browser-compat/Makefile b/js/smoke/scenarios/cloudflare-worker-browser-compat/Makefile index 4d52e473f..57dd745e8 100644 --- a/js/smoke/scenarios/cloudflare-worker-browser-compat/Makefile +++ b/js/smoke/scenarios/cloudflare-worker-browser-compat/Makefile @@ -8,7 +8,7 @@ setup: echo "==> Using BRAINTRUST_TAR: $(BRAINTRUST_TAR)"; \ else \ echo "==> Building SDK"; \ - cd ../../.. && pnpm exec turbo build --filter=braintrust && mkdir -p artifacts && pnpm pack --pack-destination artifacts; \ + cd ../../.. && pnpm --filter=braintrust run build && mkdir -p artifacts && pnpm pack --pack-destination artifacts; \ \ for f in artifacts/braintrust-*.tgz; do \ [ "$$(basename $$f)" = "braintrust-latest.tgz" ] && continue; \ diff --git a/js/smoke/scenarios/cloudflare-worker-browser-compat/tests/worker.test.mjs b/js/smoke/scenarios/cloudflare-worker-browser-compat/tests/worker.test.mjs index bd006d9ee..ca9177e89 100644 --- a/js/smoke/scenarios/cloudflare-worker-browser-compat/tests/worker.test.mjs +++ b/js/smoke/scenarios/cloudflare-worker-browser-compat/tests/worker.test.mjs @@ -1,8 +1,5 @@ import { spawn, execSync } from "node:child_process"; -import { - displayTestResults, - hasFailures, -} from "../../../shared/dist/index.mjs"; +import { displayTestResults, hasFailures } from "../../../shared/dist/index.js"; const PORT = 8801; const MAX_RETRIES = 20; diff --git a/js/smoke/scenarios/cloudflare-worker-browser-no-compat/Makefile b/js/smoke/scenarios/cloudflare-worker-browser-no-compat/Makefile index a019fa3a3..a07495d3c 100644 --- a/js/smoke/scenarios/cloudflare-worker-browser-no-compat/Makefile +++ b/js/smoke/scenarios/cloudflare-worker-browser-no-compat/Makefile @@ -8,7 +8,7 @@ setup: echo "==> Using BRAINTRUST_TAR: $(BRAINTRUST_TAR)"; \ else \ echo "==> Building SDK"; \ - cd ../../.. && pnpm exec turbo build --filter=braintrust && mkdir -p artifacts && pnpm pack --pack-destination artifacts; \ + cd ../../.. && pnpm --filter=braintrust run build && mkdir -p artifacts && pnpm pack --pack-destination artifacts; \ \ for f in artifacts/braintrust-*.tgz; do \ [ "$$(basename $$f)" = "braintrust-latest.tgz" ] && continue; \ diff --git a/js/smoke/scenarios/cloudflare-worker-browser-no-compat/tests/worker.test.mjs b/js/smoke/scenarios/cloudflare-worker-browser-no-compat/tests/worker.test.mjs index 568bf0df7..d7f5df23b 100644 --- a/js/smoke/scenarios/cloudflare-worker-browser-no-compat/tests/worker.test.mjs +++ b/js/smoke/scenarios/cloudflare-worker-browser-no-compat/tests/worker.test.mjs @@ -1,8 +1,5 @@ import { spawn, execSync } from "node:child_process"; -import { - displayTestResults, - hasFailures, -} from "../../../shared/dist/index.mjs"; +import { displayTestResults, hasFailures } from "../../../shared/dist/index.js"; const PORT = 8800; const MAX_RETRIES = 20; diff --git a/js/smoke/scenarios/cloudflare-worker-node-compat/Makefile b/js/smoke/scenarios/cloudflare-worker-node-compat/Makefile index a03e18c1c..e66aac769 100644 --- a/js/smoke/scenarios/cloudflare-worker-node-compat/Makefile +++ b/js/smoke/scenarios/cloudflare-worker-node-compat/Makefile @@ -8,7 +8,7 @@ setup: echo "==> Using BRAINTRUST_TAR: $(BRAINTRUST_TAR)"; \ else \ echo "==> Building SDK"; \ - cd ../../.. && pnpm exec turbo build --filter=braintrust && mkdir -p artifacts && pnpm pack --pack-destination artifacts; \ + cd ../../.. && pnpm --filter=braintrust run build && mkdir -p artifacts && pnpm pack --pack-destination artifacts; \ \ for f in artifacts/braintrust-*.tgz; do \ [ "$$(basename $$f)" = "braintrust-latest.tgz" ] && continue; \ diff --git a/js/smoke/scenarios/cloudflare-worker-node-compat/tests/worker.test.mjs b/js/smoke/scenarios/cloudflare-worker-node-compat/tests/worker.test.mjs index 123f81e3c..3362712a8 100644 --- a/js/smoke/scenarios/cloudflare-worker-node-compat/tests/worker.test.mjs +++ b/js/smoke/scenarios/cloudflare-worker-node-compat/tests/worker.test.mjs @@ -1,8 +1,5 @@ import { spawn, execSync } from "node:child_process"; -import { - displayTestResults, - hasFailures, -} from "../../../shared/dist/index.mjs"; +import { displayTestResults, hasFailures } from "../../../shared/dist/index.js"; const PORT = 8799; const MAX_RETRIES = 20; diff --git a/js/smoke/scenarios/cloudflare-worker-node-no-compat/Makefile b/js/smoke/scenarios/cloudflare-worker-node-no-compat/Makefile index bf866c692..7fb8a9bc2 100644 --- a/js/smoke/scenarios/cloudflare-worker-node-no-compat/Makefile +++ b/js/smoke/scenarios/cloudflare-worker-node-no-compat/Makefile @@ -8,7 +8,7 @@ setup: echo "==> Using BRAINTRUST_TAR: $(BRAINTRUST_TAR)"; \ else \ echo "==> Building SDK"; \ - cd ../../.. && pnpm exec turbo build --filter=braintrust && mkdir -p artifacts && pnpm pack --pack-destination artifacts; \ + cd ../../.. && pnpm --filter=braintrust run build && mkdir -p artifacts && pnpm pack --pack-destination artifacts; \ \ for f in artifacts/braintrust-*.tgz; do \ [ "$$(basename $$f)" = "braintrust-latest.tgz" ] && continue; \ diff --git a/js/smoke/scenarios/cloudflare-worker-node-no-compat/tests/worker.test.mjs b/js/smoke/scenarios/cloudflare-worker-node-no-compat/tests/worker.test.mjs index 7150abd08..71ce8f789 100644 --- a/js/smoke/scenarios/cloudflare-worker-node-no-compat/tests/worker.test.mjs +++ b/js/smoke/scenarios/cloudflare-worker-node-no-compat/tests/worker.test.mjs @@ -1,8 +1,5 @@ import { spawn, execSync } from "node:child_process"; -import { - displayTestResults, - hasFailures, -} from "../../../shared/dist/index.mjs"; +import { displayTestResults, hasFailures } from "../../../shared/dist/index.js"; const PORT = 8802; const MAX_RETRIES = 20; diff --git a/js/smoke/scenarios/playwright-browser/Makefile b/js/smoke/scenarios/playwright-browser/Makefile index 98f43e1ff..f64fd9ff8 100644 --- a/js/smoke/scenarios/playwright-browser/Makefile +++ b/js/smoke/scenarios/playwright-browser/Makefile @@ -13,7 +13,7 @@ setup: echo "==> Using BRAINTRUST_TAR: $(BRAINTRUST_TAR)"; \ else \ echo "==> Building SDK"; \ - cd ../../.. && pnpm exec turbo build --filter=braintrust && mkdir -p artifacts && pnpm pack --pack-destination artifacts; \ + cd ../../.. && pnpm --filter=braintrust run build && mkdir -p artifacts && pnpm pack --pack-destination artifacts; \ \ for f in artifacts/braintrust-*.tgz; do \ [ "$$(basename $$f)" = "braintrust-latest.tgz" ] && continue; \ diff --git a/js/smoke/scenarios/playwright-browser/src/browser-tests.ts b/js/smoke/scenarios/playwright-browser/src/browser-tests.ts index 403f1abc7..706b13cfd 100644 --- a/js/smoke/scenarios/playwright-browser/src/browser-tests.ts +++ b/js/smoke/scenarios/playwright-browser/src/browser-tests.ts @@ -44,15 +44,6 @@ const harness = createBrowserHarness(output); window.__btBrowserSmokeResults = harness.results; async function runAllTestSuites() { - if (!braintrust._exportsForTestingOnly) { - harness.fail( - "runtime", - "preflight", - new Error("_exportsForTestingOnly not available"), - ); - return; - } - harness.log("=== Running test suites ==="); const { passed, failed, xfail } = await runTests({ diff --git a/js/smoke/scenarios/playwright-browser/tests/browser.test.ts b/js/smoke/scenarios/playwright-browser/tests/browser.test.ts index 42dd1a9c5..3fbe0bfb8 100644 --- a/js/smoke/scenarios/playwright-browser/tests/browser.test.ts +++ b/js/smoke/scenarios/playwright-browser/tests/browser.test.ts @@ -2,7 +2,7 @@ import { test, expect } from "@playwright/test"; import { displayTestResults, type TestResult, -} from "../../../shared/dist/index.mjs"; +} from "../../../shared/dist/index.js"; test.describe("Braintrust SDK Browser Tests", () => { test("should run all browser tests (shared + eval + prompt)", async ({ diff --git a/js/smoke/shared/README.md b/js/smoke/shared/README.md index 608d19173..bc52b618d 100644 --- a/js/smoke/shared/README.md +++ b/js/smoke/shared/README.md @@ -17,8 +17,7 @@ This package provides reusable test logic that can be imported and run in differ The package is built with `tsup` to produce both CommonJS and ESM formats: -- `dist/index.js` - CommonJS build -- `dist/index.mjs` - ESM build +- `dist/index.js` - ESM build - `dist/index.d.ts` / `dist/index.d.mts` - TypeScript declarations This allows the same test code to be imported in both CJS and ESM environments. @@ -66,7 +65,7 @@ import { testBasicSpanLogging, testMultipleSpans, testNunjucksTemplate, -} from "../../shared/dist/index.mjs"; +} from "../../shared/dist/index.js"; import * as braintrust from "braintrust"; @@ -105,7 +104,7 @@ const { testMultipleSpans, } = require("../../shared"); -const braintrust = require("braintrust"); +import * as braintrust from "braintrust"; async function main() { const { all, passed, failed, xfail } = await runTests({ @@ -414,7 +413,7 @@ Tests basic eval functionality: All scenarios use `displayTestResults()` for consistent output: ```typescript -import { displayTestResults } from "../../shared/dist/index.mjs"; +import { displayTestResults } from "../../shared/dist/index.js"; // Called automatically by runTests(), but can also be used standalone displayTestResults({ diff --git a/js/smoke/shared/package.json b/js/smoke/shared/package.json index 1e9535fbf..523a19ffd 100644 --- a/js/smoke/shared/package.json +++ b/js/smoke/shared/package.json @@ -4,26 +4,26 @@ "private": true, "description": "Shared test utilities for Braintrust smoke tests", "main": "./dist/index.js", - "module": "./dist/index.mjs", "types": "./dist/index.d.ts", "exports": { ".": { "types": "./dist/index.d.ts", - "import": "./dist/index.mjs", - "require": "./dist/index.js" + "default": "./dist/index.js" } }, "files": [ "dist/**/*" ], "scripts": { - "build": "tsup", + "build": "vp pack", "clean": "rm -rf dist", - "watch": "tsup --watch" + "watch": "vp pack --watch" }, "devDependencies": { "@types/node": "^20.10.5", - "tsup": "^8.5.1", - "typescript": "^5.4.4" - } + "typescript": "^5.4.4", + "vite-plus": "1.1.0", + "vite": "npm:@voidzero-dev/vite-plus-core@1.1.0" + }, + "type": "module" } diff --git a/js/smoke/shared/pnpm-lock.yaml b/js/smoke/shared/pnpm-lock.yaml index 0417dd584..356dc8f06 100644 --- a/js/smoke/shared/pnpm-lock.yaml +++ b/js/smoke/shared/pnpm-lock.yaml @@ -11,15 +11,33 @@ importers: '@types/node': specifier: ^20.10.5 version: 20.19.37 - tsup: - specifier: ^8.5.1 - version: 8.5.1(typescript@5.9.3) typescript: specifier: ^5.4.4 version: 5.9.3 + vite: + specifier: npm:@voidzero-dev/vite-plus-core@1.1.0 + version: '@voidzero-dev/vite-plus-core@1.1.0(@types/node@20.19.37)(esbuild@0.27.4)(typescript@5.9.3)' + vite-plus: + specifier: 1.1.0 + version: 1.1.0(@types/node@20.19.37)(@vitest/ui@5.0.3(vitest@5.0.3))(esbuild@0.27.4)(typescript@5.9.3) packages: + '@babel/code-frame@7.29.7': + resolution: {integrity: sha512-Aup7aUOfpbAUg2ROOJN6Iw5f9DMBlzu0mIkm/malLQFN/YQgO48wCj0Kxa3sEHJvPVFg7siR+qRInwXd2qhQKw==} + engines: {node: '>=6.9.0'} + + '@babel/helper-validator-identifier@7.29.7': + resolution: {integrity: sha512-qehxGkRj55h/ff8EMaJ+cYhyaKlHIxqYDn682wQD7RNp9UujOQsHog2uS0r2vzr4pW+sXf90NeeayjcNaX3fFg==} + engines: {node: '>=6.9.0'} + + '@babel/runtime@7.29.10': + resolution: {integrity: sha512-NpugTzbKgGrNe45Ui32cN8vCFFMxVtP/9rtfwv0nP5FRDAjvYBxJcLVpYA9mr1jxP6cnIT7x9LUzuDTbWYYNRA==} + engines: {node: '>=6.9.0'} + + '@blazediff/core@1.10.0': + resolution: {integrity: sha512-AOQff0zgR7cGsZL+4E7hVkmujoPUpm0J9xzWGWZj5wCjd3gmxESXAPfKyuzs93VdpQNFhHlBhfOjrcZ+XTERtQ==} + '@esbuild/aix-ppc64@0.27.4': resolution: {integrity: sha512-cQPwL2mp2nSmHHJlCyoXgHGhbEPMrEEU5xhkcy3Hs/O7nGZqEpZ2sUtLaL9MORLtDfRvVl2/3PAuEkYZH0Ty8Q==} engines: {node: '>=18'} @@ -176,9 +194,6 @@ packages: cpu: [x64] os: [win32] - '@jridgewell/gen-mapping@0.3.13': - resolution: {integrity: sha512-2kkt/7niJ6MgEPxF0bYdQ6etZaA+fQvDcLKckhy1yIQOzaoKjBBjSj63/aLVjYE3qhRt5dvM+uUyfCg6UKCBbA==} - '@jridgewell/resolve-uri@3.1.2': resolution: {integrity: sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==} engines: {node: '>=6.0.0'} @@ -186,381 +201,1026 @@ packages: '@jridgewell/sourcemap-codec@1.5.5': resolution: {integrity: sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og==} + '@jridgewell/sourcemap-codec@1.6.0': + resolution: {integrity: sha512-T7jf+5zgsZHwNJ4lvQ7/aezbyk0nNX+zJVWpmHA7VYsEx7a7qr5Rg5IbtJFqkgze5Y2sruq1RUY8Q837Od7iFw==} + '@jridgewell/trace-mapping@0.3.31': resolution: {integrity: sha512-zzNR+SdQSDJzc8joaeP8QQoCQr8NuYx2dIIytl1QeBEZHJ9uW6hebsrYgbz8hJwUQao3TWCMtmfV8Nu1twOLAw==} - '@rollup/rollup-android-arm-eabi@4.60.1': - resolution: {integrity: sha512-d6FinEBLdIiK+1uACUttJKfgZREXrF0Qc2SmLII7W2AD8FfiZ9Wjd+rD/iRuf5s5dWrr1GgwXCvPqOuDquOowA==} + '@oxc-project/runtime@0.153.0': + resolution: {integrity: sha512-rXJvtlsVG4xJNq64hiaUTIatE7jbBgpa6l4QgmEa2jLgT3ULH9scUBkpsC9N1DfmqixLEyT6DuK6OSXozziIlw==} + engines: {node: ^20.19.0 || >=22.12.0} + + '@oxc-project/types@0.153.0': + resolution: {integrity: sha512-jSNrRIyvPVLL5E1z/tS5zLAprosGz/FuI1PWwEOz59j3E4TTTzSpqKb/RDGqJhhW0QHYISGXxq582wGx6T11Tw==} + + '@oxfmt/binding-android-arm-eabi@0.72.0': + resolution: {integrity: sha512-u4U1uaDOVBuorGNC/07NLm48nECHOiJJka002YGML16Md2DRvf38RGBt01lH60EOGvNlFVmbLoksQedNBoZHKw==} + engines: {node: ^20.19.0 || >=22.12.0} cpu: [arm] os: [android] - '@rollup/rollup-android-arm64@4.60.1': - resolution: {integrity: sha512-YjG/EwIDvvYI1YvYbHvDz/BYHtkY4ygUIXHnTdLhG+hKIQFBiosfWiACWortsKPKU/+dUwQQCKQM3qrDe8c9BA==} + '@oxfmt/binding-android-arm64@0.72.0': + resolution: {integrity: sha512-GCnM+Ae2mWOKC+92y3FOb6eZJeVnBAVz82e3lITUUg+kxukYKzBNAvJ/hhHIfa2A0md55SYYhdWPddSSy3KPVA==} + engines: {node: ^20.19.0 || >=22.12.0} cpu: [arm64] os: [android] - '@rollup/rollup-darwin-arm64@4.60.1': - resolution: {integrity: sha512-mjCpF7GmkRtSJwon+Rq1N8+pI+8l7w5g9Z3vWj4T7abguC4Czwi3Yu/pFaLvA3TTeMVjnu3ctigusqWUfjZzvw==} + '@oxfmt/binding-darwin-arm64@0.72.0': + resolution: {integrity: sha512-LjQ8tlevdtwraFlWC6AnQ1Forzde0clRmnsP3QmK+ydSElDt/J+5Jc+m+//Ly1G97aP7WCNUw110QMIUw2K7xA==} + engines: {node: ^20.19.0 || >=22.12.0} cpu: [arm64] os: [darwin] - '@rollup/rollup-darwin-x64@4.60.1': - resolution: {integrity: sha512-haZ7hJ1JT4e9hqkoT9R/19XW2QKqjfJVv+i5AGg57S+nLk9lQnJ1F/eZloRO3o9Scy9CM3wQ9l+dkXtcBgN5Ew==} + '@oxfmt/binding-darwin-x64@0.72.0': + resolution: {integrity: sha512-+ZbcxhB1C34RQR5IldSyY6Z/yalJIxHXhP36PGE4sEq0f/lgL5jCLmOKoDeVm8dqd9gFGDsd1bNWHSGCAwdSkw==} + engines: {node: ^20.19.0 || >=22.12.0} cpu: [x64] os: [darwin] - '@rollup/rollup-freebsd-arm64@4.60.1': - resolution: {integrity: sha512-czw90wpQq3ZsAVBlinZjAYTKduOjTywlG7fEeWKUA7oCmpA8xdTkxZZlwNJKWqILlq0wehoZcJYfBvOyhPTQ6w==} - cpu: [arm64] - os: [freebsd] - - '@rollup/rollup-freebsd-x64@4.60.1': - resolution: {integrity: sha512-KVB2rqsxTHuBtfOeySEyzEOB7ltlB/ux38iu2rBQzkjbwRVlkhAGIEDiiYnO2kFOkJp+Z7pUXKyrRRFuFUKt+g==} + '@oxfmt/binding-freebsd-x64@0.72.0': + resolution: {integrity: sha512-yGPhA8sePEBFV9iGcprhyOk3Bz3fZshswMH3DpYIPZFLT1DG5LjiZ0j6AJQpSueaL6bUK0av1K+5+fe+ldy5EQ==} + engines: {node: ^20.19.0 || >=22.12.0} cpu: [x64] os: [freebsd] - '@rollup/rollup-linux-arm-gnueabihf@4.60.1': - resolution: {integrity: sha512-L+34Qqil+v5uC0zEubW7uByo78WOCIrBvci69E7sFASRl0X7b/MB6Cqd1lky/CtcSVTydWa2WZwFuWexjS5o6g==} + '@oxfmt/binding-linux-arm-gnueabihf@0.72.0': + resolution: {integrity: sha512-/g5axuqFMcAtoQrFLR5EJEMDjR5Ea4GuzpdDmMpPWAgrF1DmL4kD1NPNVlpXEOxpFJnD6lPvLKYj1lzdlrfifA==} + engines: {node: ^20.19.0 || >=22.12.0} cpu: [arm] os: [linux] - libc: [glibc] - '@rollup/rollup-linux-arm-musleabihf@4.60.1': - resolution: {integrity: sha512-n83O8rt4v34hgFzlkb1ycniJh7IR5RCIqt6mz1VRJD6pmhRi0CXdmfnLu9dIUS6buzh60IvACM842Ffb3xd6Gg==} + '@oxfmt/binding-linux-arm-musleabihf@0.72.0': + resolution: {integrity: sha512-InVitdHbS09PM+w+WinIiyX37rC9R3L7lUP9mKy9uKTEDSUFaLjWsLoreRIxKivnv7Ndqc6Trvi+K+b6ZzhiJg==} + engines: {node: ^20.19.0 || >=22.12.0} cpu: [arm] os: [linux] - libc: [musl] - '@rollup/rollup-linux-arm64-gnu@4.60.1': - resolution: {integrity: sha512-Nql7sTeAzhTAja3QXeAI48+/+GjBJ+QmAH13snn0AJSNL50JsDqotyudHyMbO2RbJkskbMbFJfIJKWA6R1LCJQ==} + '@oxfmt/binding-linux-arm64-gnu@0.72.0': + resolution: {integrity: sha512-WKjlyRAxpPjXoqeuwFBim5LscgvY+RNEiZ971C9JlRQZONhJTsXDki/oHFYQE+fzzRpzXh3owZvOdT3kNL2g+g==} + engines: {node: ^20.19.0 || >=22.12.0} cpu: [arm64] os: [linux] libc: [glibc] - '@rollup/rollup-linux-arm64-musl@4.60.1': - resolution: {integrity: sha512-+pUymDhd0ys9GcKZPPWlFiZ67sTWV5UU6zOJat02M1+PiuSGDziyRuI/pPue3hoUwm2uGfxdL+trT6Z9rxnlMA==} + '@oxfmt/binding-linux-arm64-musl@0.72.0': + resolution: {integrity: sha512-poafnbsPpJhR+5Mbwpf8CscugTyiRAzjTOxRVn8ra3X2jnURs2xL5Du2xl5g2iW85lICMglpmLf8jDjZEbNgzQ==} + engines: {node: ^20.19.0 || >=22.12.0} cpu: [arm64] os: [linux] libc: [musl] - '@rollup/rollup-linux-loong64-gnu@4.60.1': - resolution: {integrity: sha512-VSvgvQeIcsEvY4bKDHEDWcpW4Yw7BtlKG1GUT4FzBUlEKQK0rWHYBqQt6Fm2taXS+1bXvJT6kICu5ZwqKCnvlQ==} - cpu: [loong64] - os: [linux] - libc: [glibc] - - '@rollup/rollup-linux-loong64-musl@4.60.1': - resolution: {integrity: sha512-4LqhUomJqwe641gsPp6xLfhqWMbQV04KtPp7/dIp0nzPxAkNY1AbwL5W0MQpcalLYk07vaW9Kp1PBhdpZYYcEw==} - cpu: [loong64] - os: [linux] - libc: [musl] - - '@rollup/rollup-linux-ppc64-gnu@4.60.1': - resolution: {integrity: sha512-tLQQ9aPvkBxOc/EUT6j3pyeMD6Hb8QF2BTBnCQWP/uu1lhc9AIrIjKnLYMEroIz/JvtGYgI9dF3AxHZNaEH0rw==} + '@oxfmt/binding-linux-ppc64-gnu@0.72.0': + resolution: {integrity: sha512-zNUi0gKRsO7S3IfPzsTbnm88OedPuX32hmZFWZRWHvWJlAoZRwVbeO6Nb3hng4vDgL7qUKp4TGqzPz1mhsycYg==} + engines: {node: ^20.19.0 || >=22.12.0} cpu: [ppc64] os: [linux] libc: [glibc] - '@rollup/rollup-linux-ppc64-musl@4.60.1': - resolution: {integrity: sha512-RMxFhJwc9fSXP6PqmAz4cbv3kAyvD1etJFjTx4ONqFP9DkTkXsAMU4v3Vyc5BgzC+anz7nS/9tp4obsKfqkDHg==} - cpu: [ppc64] - os: [linux] - libc: [musl] - - '@rollup/rollup-linux-riscv64-gnu@4.60.1': - resolution: {integrity: sha512-QKgFl+Yc1eEk6MmOBfRHYF6lTxiiiV3/z/BRrbSiW2I7AFTXoBFvdMEyglohPj//2mZS4hDOqeB0H1ACh3sBbg==} + '@oxfmt/binding-linux-riscv64-gnu@0.72.0': + resolution: {integrity: sha512-qTY7z/iyF/rWI2HWwWMevXqZW5PZUOofUy2afK7v6rseXnpld3yjgyIk6pCux3DqWQ0lB+f5atvLJNBwzLvFsw==} + engines: {node: ^20.19.0 || >=22.12.0} cpu: [riscv64] os: [linux] libc: [glibc] - '@rollup/rollup-linux-riscv64-musl@4.60.1': - resolution: {integrity: sha512-RAjXjP/8c6ZtzatZcA1RaQr6O1TRhzC+adn8YZDnChliZHviqIjmvFwHcxi4JKPSDAt6Uhf/7vqcBzQJy0PDJg==} + '@oxfmt/binding-linux-riscv64-musl@0.72.0': + resolution: {integrity: sha512-w18qfo8vEMD6EKEHtzTvjc6jH8yCyXIT1Bn7PFWJd0PpZyzH/VQDx4xvjCh0LGz6GRzqiezbisTdG9AoXtpkZQ==} + engines: {node: ^20.19.0 || >=22.12.0} cpu: [riscv64] os: [linux] libc: [musl] - '@rollup/rollup-linux-s390x-gnu@4.60.1': - resolution: {integrity: sha512-wcuocpaOlaL1COBYiA89O6yfjlp3RwKDeTIA0hM7OpmhR1Bjo9j31G1uQVpDlTvwxGn2nQs65fBFL5UFd76FcQ==} + '@oxfmt/binding-linux-s390x-gnu@0.72.0': + resolution: {integrity: sha512-grW8HwEir+vK7fCcq1ZgL3NHQCfZ1PidiERE9Hbf+24xGys+DoDaoqTht4ccfn0PcBwbViLkwkda/KNpJu2KRQ==} + engines: {node: ^20.19.0 || >=22.12.0} cpu: [s390x] os: [linux] libc: [glibc] - '@rollup/rollup-linux-x64-gnu@4.60.1': - resolution: {integrity: sha512-77PpsFQUCOiZR9+LQEFg9GClyfkNXj1MP6wRnzYs0EeWbPcHs02AXu4xuUbM1zhwn3wqaizle3AEYg5aeoohhg==} + '@oxfmt/binding-linux-x64-gnu@0.72.0': + resolution: {integrity: sha512-wQJRQfWBRIn88Pkr449EUViwRayrYUScg7ty+Ihiem21mQ9BUA6S9e/4RAgyNk9lMag0KBouyKskjq61rOC0WQ==} + engines: {node: ^20.19.0 || >=22.12.0} cpu: [x64] os: [linux] libc: [glibc] - '@rollup/rollup-linux-x64-musl@4.60.1': - resolution: {integrity: sha512-5cIATbk5vynAjqqmyBjlciMJl1+R/CwX9oLk/EyiFXDWd95KpHdrOJT//rnUl4cUcskrd0jCCw3wpZnhIHdD9w==} + '@oxfmt/binding-linux-x64-musl@0.72.0': + resolution: {integrity: sha512-T1B0uQzafxuGPjjaPrZJ497nxoi1BiI65JfsYc56nNpmev3msfb/iaTmH2kQuNFqa8PKxpMlf3GhWrWBvBPORQ==} + engines: {node: ^20.19.0 || >=22.12.0} cpu: [x64] os: [linux] libc: [musl] - '@rollup/rollup-openbsd-x64@4.60.1': - resolution: {integrity: sha512-cl0w09WsCi17mcmWqqglez9Gk8isgeWvoUZ3WiJFYSR3zjBQc2J5/ihSjpl+VLjPqjQ/1hJRcqBfLjssREQILw==} - cpu: [x64] - os: [openbsd] - - '@rollup/rollup-openharmony-arm64@4.60.1': - resolution: {integrity: sha512-4Cv23ZrONRbNtbZa37mLSueXUCtN7MXccChtKpUnQNgF010rjrjfHx3QxkS2PI7LqGT5xXyYs1a7LbzAwT0iCA==} + '@oxfmt/binding-openharmony-arm64@0.72.0': + resolution: {integrity: sha512-OKxxgTurl7+hypVplO8QRAbbQlwEs0j1Y6FZv+ZN2qNbdxQk2muMlzuJNBGTCWwxQ0rkkOD1uK/DW1fvzoUSJA==} + engines: {node: ^20.19.0 || >=22.12.0} cpu: [arm64] os: [openharmony] - '@rollup/rollup-win32-arm64-msvc@4.60.1': - resolution: {integrity: sha512-i1okWYkA4FJICtr7KpYzFpRTHgy5jdDbZiWfvny21iIKky5YExiDXP+zbXzm3dUcFpkEeYNHgQ5fuG236JPq0g==} + '@oxfmt/binding-win32-arm64-msvc@0.72.0': + resolution: {integrity: sha512-L78TKzURqvjuZOCo88ixMbPXf/RP7u31Y0cmjJ/KEmrHmfYhmhEvsDaTYPiyTRne64jUgCbADfY0YeoQk4/Xyw==} + engines: {node: ^20.19.0 || >=22.12.0} cpu: [arm64] os: [win32] - '@rollup/rollup-win32-ia32-msvc@4.60.1': - resolution: {integrity: sha512-u09m3CuwLzShA0EYKMNiFgcjjzwqtUMLmuCJLeZWjjOYA3IT2Di09KaxGBTP9xVztWyIWjVdsB2E9goMjZvTQg==} + '@oxfmt/binding-win32-ia32-msvc@0.72.0': + resolution: {integrity: sha512-gQtFW+Ii8BWgtJTAD17TY/WsdNS5gUfAPov8iI1jeq4r9lRRYImmxPL08UU00/RLaCfBJwh2UgINnDY0iOiZ+A==} + engines: {node: ^20.19.0 || >=22.12.0} cpu: [ia32] os: [win32] - '@rollup/rollup-win32-x64-gnu@4.60.1': - resolution: {integrity: sha512-k+600V9Zl1CM7eZxJgMyTUzmrmhB/0XZnF4pRypKAlAgxmedUA+1v9R+XOFv56W4SlHEzfeMtzujLJD22Uz5zg==} + '@oxfmt/binding-win32-x64-msvc@0.72.0': + resolution: {integrity: sha512-uhooIN+DCzWX5uH7mQkSVCsODqsGluGJ4n3ch4+mITtWKpESMDjUJUSDBAVFuYQDwqY2McPDKdKJbM0oZVW/Qw==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [x64] + os: [win32] + + '@oxlint-tsgolint/darwin-arm64@7.0.2003': + resolution: {integrity: sha512-TgV33rXr6ueXBwvc+0nssUkTBSXHJxv77I8p4RCjDjCnvexHtmoPiudVRDfj3S3puMDdeQdHW6jdUgUPy3nr/w==} + cpu: [arm64] + os: [darwin] + + '@oxlint-tsgolint/darwin-x64@7.0.2003': + resolution: {integrity: sha512-hY3FMAjIaPDdK3FNxyRXRfHPOFeoBn6dmnLyKZgQ2IbTTD1adXhl6PfCIqHhCPvurigv7nf7mYuWZZ19MmJzmg==} + cpu: [x64] + os: [darwin] + + '@oxlint-tsgolint/linux-arm64@7.0.2003': + resolution: {integrity: sha512-eAET4JpyfBbg8SO0K74o4R55tEjVC292pIyxGOw2XGf8x/HrPNyxwQ478jMYOM54FIVOHc8sJ6VRHxluy6lucw==} + cpu: [arm64] + os: [linux] + + '@oxlint-tsgolint/linux-x64@7.0.2003': + resolution: {integrity: sha512-GXdyO/XyqDJ3s/llR/oOktLsYNjZtWSQBy0JMc+/0gsNPvTcseKPhn9c6KcFyWmnr6H4vljYALbujonqSzzEGw==} cpu: [x64] + os: [linux] + + '@oxlint-tsgolint/win32-arm64@7.0.2003': + resolution: {integrity: sha512-TWauXnPfet0VgpmrstoAK58eJ4gbuwPUuUTQmYQAzE/RG1CpnxXtrKUJULf6lyerPE4KN3gM+DflIA1hOH+38Q==} + cpu: [arm64] os: [win32] - '@rollup/rollup-win32-x64-msvc@4.60.1': - resolution: {integrity: sha512-lWMnixq/QzxyhTV6NjQJ4SFo1J6PvOX8vUx5Wb4bBPsEb+8xZ89Bz6kOXpfXj9ak9AHTQVQzlgzBEc1SyM27xQ==} + '@oxlint-tsgolint/win32-x64@7.0.2003': + resolution: {integrity: sha512-DoRmfe7j8VqNlukp8liRVW45GQDhzRccNenjD/pdzelgtffW47pCMd1xbJLkPaPbKnTwID3onn3VZlL7JbebEA==} cpu: [x64] os: [win32] - '@types/estree@1.0.8': - resolution: {integrity: sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w==} + '@oxlint/binding-android-arm-eabi@1.87.0': + resolution: {integrity: sha512-22tVIHlB9898OYsBKmYB6He1MCSqdTmK1BVWuSNUTVrpS0KWNege+U9uEYqGJ+KatQag0r/oU44i3OOs46z7HQ==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [arm] + os: [android] - '@types/node@20.19.37': - resolution: {integrity: sha512-8kzdPJ3FsNsVIurqBs7oodNnCEVbni9yUEkaHbgptDACOPW04jimGagZ51E6+lXUwJjgnBw+hyko/lkFWCldqw==} + '@oxlint/binding-android-arm64@1.87.0': + resolution: {integrity: sha512-JfZQ3MWf74n8ov0DojZpkOduIubYJvZL5tbTMMhDutsal6u1xH1QuNpN+lK+EQwF8AeAfgwTR2fgh5U2br27XA==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [arm64] + os: [android] - acorn@8.16.0: - resolution: {integrity: sha512-UVJyE9MttOsBQIDKw1skb9nAwQuR5wuGD3+82K6JgJlm/Y+KI92oNsMNGZCYdDsVtRHSak0pcV5Dno5+4jh9sw==} - engines: {node: '>=0.4.0'} - hasBin: true + '@oxlint/binding-darwin-arm64@1.87.0': + resolution: {integrity: sha512-rkx6Xvvw93z2GImVOyvbZwAZcoY0Z6MJW177XNsSB+i/CBsH7ANyelTp01VZyIkM3iSXtqKVb4+ptBgPHap5pQ==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [arm64] + os: [darwin] + + '@oxlint/binding-darwin-x64@1.87.0': + resolution: {integrity: sha512-cw5nYw7L/pQ2SgGBgOcg/Qo89llQre3vIc/kuBLhSICzu1r5s8L76H3ug6AIFsD98WpROj8/z5JVzkf4daxHaQ==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [x64] + os: [darwin] - any-promise@1.3.0: - resolution: {integrity: sha512-7UvmKalWRt1wgjL1RrGxoSJW/0QZFIegpeGvZG9kjp8vrRu55XTHbwnqq2GpXm9uLbcuhxm3IqX9OB4MZR1b2A==} + '@oxlint/binding-freebsd-x64@1.87.0': + resolution: {integrity: sha512-x/SuznE6loRZK9gz9AS4ZmzNzSu+5KdeoCS+sVHhnrE8HM6AD8UyTjwldQTTqlXGvkwHASgB0NiTNOc3o3PdAg==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [x64] + os: [freebsd] - bundle-require@5.1.0: - resolution: {integrity: sha512-3WrrOuZiyaaZPWiEt4G3+IffISVC9HYlWueJEBWED4ZH4aIAC2PnkdnuRrR94M+w6yGWn4AglWtJtBI8YqvgoA==} - engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0} - peerDependencies: - esbuild: '>=0.18' + '@oxlint/binding-linux-arm-gnueabihf@1.87.0': + resolution: {integrity: sha512-+JEDmCAXiiFmKhAjZhgAOwaHWY6WENOQ5QA+lFuY59tgsUCDOTRi46B+lENK6CWvtxsXpwkZXNOgpjAQcpIpnA==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [arm] + os: [linux] - cac@6.7.14: - resolution: {integrity: sha512-b6Ilus+c3RrdDk+JhLKUAQfzzgLEPy6wcXqS7f/xe1EETvsDP6GORG7SFuOs6cID5YkqchW/LXZbX5bc8j7ZcQ==} - engines: {node: '>=8'} + '@oxlint/binding-linux-arm-musleabihf@1.87.0': + resolution: {integrity: sha512-/uC9XqXLAC9AHFLNycwmSQEvKqGrOCgU+uXALI7gNW0DG81TJ/3T2eHnKdRvcb5R1gFEfjxDwI4e4zIa29rICQ==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [arm] + os: [linux] - chokidar@4.0.3: - resolution: {integrity: sha512-Qgzu8kfBvo+cA4962jnP1KkS6Dop5NS6g7R5LFYJr4b8Ub94PPQXUksCw9PvXoeXPRRddRNC5C1JQUR2SMGtnA==} - engines: {node: '>= 14.16.0'} + '@oxlint/binding-linux-arm64-gnu@1.87.0': + resolution: {integrity: sha512-FTRYPsBy/M728vnUv7l+KAwSAa3Qi4BQbhVvy7NYuMTZ0sUa/OWsbMx79HVFw6u/5KWcaYayDsoOiv7xbevzDg==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [arm64] + os: [linux] + libc: [glibc] - commander@4.1.1: - resolution: {integrity: sha512-NOKm8xhkzAjzFx8B2v5OAHT+u5pRQc2UCa2Vq9jYL/31o2wi9mxBA7LIFs3sV5VSC49z6pEhfbMULvShKj26WA==} - engines: {node: '>= 6'} + '@oxlint/binding-linux-arm64-musl@1.87.0': + resolution: {integrity: sha512-6sC+N7IY0h1egd8SrYUGNN7CUyKJZ4bIZakKa7V7hfinf4jfi8Kq4+klJvFp5DqXcwMn44I5MeBwmJluO1odFg==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [arm64] + os: [linux] + libc: [musl] - confbox@0.1.8: - resolution: {integrity: sha512-RMtmw0iFkeR4YV+fUOSucriAQNb9g8zFR52MWCtl+cCZOFRNL6zeB395vPzFhEjjn4fMxXudmELnl/KF/WrK6w==} + '@oxlint/binding-linux-ppc64-gnu@1.87.0': + resolution: {integrity: sha512-d5Mnn+ICbhoJPi37Ccjy+86T5qfH2+mSTrTMBXNPfB+wqSWOayBBLLIrCXdUy1YssluCVdGkl+DO9w5r5ODmUA==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [ppc64] + os: [linux] + libc: [glibc] - consola@3.4.2: - resolution: {integrity: sha512-5IKcdX0nnYavi6G7TtOhwkYzyjfJlatbjMjuLSfE2kYT5pMDOilZ4OvMhi637CcDICTmz3wARPoyhqyX1Y+XvA==} - engines: {node: ^14.18.0 || >=16.10.0} + '@oxlint/binding-linux-riscv64-gnu@1.87.0': + resolution: {integrity: sha512-sf5YWuUo1Dx9loqWCgF8P278MycfcD4JHjDC/LV5j11L3Lqa4PFiwCjD6B8kP4AjsgZZkLn3xbpJEtZBaccs4Q==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [riscv64] + os: [linux] + libc: [glibc] - debug@4.4.3: - resolution: {integrity: sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==} - engines: {node: '>=6.0'} - peerDependencies: - supports-color: '*' - peerDependenciesMeta: - supports-color: - optional: true + '@oxlint/binding-linux-riscv64-musl@1.87.0': + resolution: {integrity: sha512-+aCraYbmK3uU3P0rFojjeaK9pGY6JvBDXh/0CM+gPyWKmNmXmnFe0qj4qj02U4ibFpbd9Pa9CmDvIdjJUd29rA==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [riscv64] + os: [linux] + libc: [musl] - esbuild@0.27.4: - resolution: {integrity: sha512-Rq4vbHnYkK5fws5NF7MYTU68FPRE1ajX7heQ/8QXXWqNgqqJ/GkmmyxIzUnf2Sr/bakf8l54716CcMGHYhMrrQ==} - engines: {node: '>=18'} - hasBin: true + '@oxlint/binding-linux-s390x-gnu@1.87.0': + resolution: {integrity: sha512-7rzbZQIljo6VLWVPqUyLa2P/WV3VFsJB4M52R4NCUJn1A50yjtUsHxiMHFL9opEKGFIiTbZ3VQzmtcJOPUq4NQ==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [s390x] + os: [linux] + libc: [glibc] - fdir@6.5.0: - resolution: {integrity: sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg==} - engines: {node: '>=12.0.0'} - peerDependencies: - picomatch: ^3 || ^4 - peerDependenciesMeta: - picomatch: - optional: true + '@oxlint/binding-linux-x64-gnu@1.87.0': + resolution: {integrity: sha512-Dry46Fa+AXE/xt9J/YriujU1pU9mzfNWHGQwtOYibAFH8HLrKWCi93AhIGnuUMiwn2JbuBkRyz51f50Ly4r4ow==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [x64] + os: [linux] + libc: [glibc] - fix-dts-default-cjs-exports@1.0.1: - resolution: {integrity: sha512-pVIECanWFC61Hzl2+oOCtoJ3F17kglZC/6N94eRWycFgBH35hHx0Li604ZIzhseh97mf2p0cv7vVrOZGoqhlEg==} + '@oxlint/binding-linux-x64-musl@1.87.0': + resolution: {integrity: sha512-f/HMZez74gJvnRpGz+b5sdGyWrEgRLmFTXg9sNoqjPWogGxYHUhXKTKyMiYsu9rUGR2ciYqL3fk+Ua4JhMJbRA==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [x64] + os: [linux] + libc: [musl] - fsevents@2.3.3: - resolution: {integrity: sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==} - engines: {node: ^8.16.0 || ^10.6.0 || >=11.0.0} - os: [darwin] + '@oxlint/binding-openharmony-arm64@1.87.0': + resolution: {integrity: sha512-lEC6ZfPZm/07JlB7FsOWX9o1RiHlXiBW594LSUGuXmekC8LZmvUPP9XzE2WZcNxJi5cHA/Fw56o02c3JxgMA5g==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [arm64] + os: [openharmony] - joycon@3.1.1: - resolution: {integrity: sha512-34wB/Y7MW7bzjKRjUKTa46I2Z7eV62Rkhva+KkopW7Qvv/OSWBqvkSY7vusOPrNuZcUG3tApvdVgNB8POj3SPw==} - engines: {node: '>=10'} + '@oxlint/binding-win32-arm64-msvc@1.87.0': + resolution: {integrity: sha512-afBWKOD36dUiA9AenaiWpCg8xfyUVPYznyAAVXu413brK4RA5UI/Xoc4yfeGIT8G1a44MV+JR7hioryerBDzGQ==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [arm64] + os: [win32] - lilconfig@3.1.3: - resolution: {integrity: sha512-/vlFKAoH5Cgt3Ie+JLhRbwOsCQePABiU3tJ1egGvyQ+33R/vcwM2Zl2QR/LzjsBeItPt3oSVXapn+m4nQDvpzw==} - engines: {node: '>=14'} + '@oxlint/binding-win32-ia32-msvc@1.87.0': + resolution: {integrity: sha512-daptNsEOuru4/nJTxV4DBHNJ5GepzmaBVQf+jjgKQGi9NRlZKik+RFq4hUL7ewsF1pwjKM+SkYaMbF82UwFGnw==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [ia32] + os: [win32] - lines-and-columns@1.2.4: - resolution: {integrity: sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg==} + '@oxlint/binding-win32-x64-msvc@1.87.0': + resolution: {integrity: sha512-k+m/EShyXE29J/r1fjk0HlAzhN5JhIvj044oSJkXTEkCLrWZQgXb/3r4q5LTw/eaKW/4GuMXG2xmiJs3s5rPNw==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [x64] + os: [win32] - load-tsconfig@0.2.5: - resolution: {integrity: sha512-IXO6OCs9yg8tMKzfPZ1YmheJbZCiEsnBdcB03l0OcfK9prKnJb96siuHCr5Fl37/yo9DnKU+TLpxzTUspw9shg==} - engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0} + '@oxlint/plugins@1.79.0': + resolution: {integrity: sha512-S0uyoxakDINJ4DPgqxGlEEvrdSMeQb7Z2lKVjxoY2gwsbZbfg2Xr8Klfeo5ZeraHmmdBCELFUHkSe6KEmBpMvg==} + engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} - magic-string@0.30.21: - resolution: {integrity: sha512-vd2F4YUyEXKGcLHoq+TEyCjxueSeHnFxyyjNp80yg0XV4vUhnDer/lvvlqM/arB5bXQN5K2/3oinyCRyx8T2CQ==} + '@polka/url@1.0.0-next.29': + resolution: {integrity: sha512-wwQAWhWSuHaag8c4q/KN/vCoeOJYshAIvMQwD4GpSb3OiZklFfvAgmj0VCBBImRpuF/aFgIRzllXlVX93Jevww==} - mlly@1.8.2: - resolution: {integrity: sha512-d+ObxMQFmbt10sretNDytwt85VrbkhhUA/JBGm1MPaWJ65Cl4wOgLaB1NYvJSZ0Ef03MMEU/0xpPMXUIQ29UfA==} + '@testing-library/dom@10.4.2': + resolution: {integrity: sha512-yzr2S9HyAIdhz2/6qHgbs665Q7PKVcDF05vsOlHPxG1mo36gKVesdYVeDLnXgfjJ03CrKRk08knc6+E/9m8v2Q==} + engines: {node: '>=18'} - ms@2.1.3: - resolution: {integrity: sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==} + '@testing-library/user-event@14.6.8': + resolution: {integrity: sha512-vgSSqnANMnkcYR/vUNDbwS7aM3OErRLNUG8vCVVlj4oc6cKffUrccX7UAS5AmJQQNZTSibi9z5GvvRZ0aYgUCQ==} + engines: {node: '>=12', npm: '>=6'} + peerDependencies: + '@testing-library/dom': '>=7.21.4' - mz@2.7.0: - resolution: {integrity: sha512-z81GNO7nnYMEhrGh9LeymoE4+Yr0Wn5McHIZMK5cfQCl+NDX08sCZgUc9/6MHni9IWuFLm1Z3HTCXu2z9fN62Q==} + '@types/aria-query@5.0.4': + resolution: {integrity: sha512-rfT93uj5s0PRL7EzccGMs3brplhcrghnDoV26NqKhCAS1hVo+WdNsPvE/yb6ilfr5hi2MEk6d5EWJTKdxg8jVw==} - object-assign@4.1.1: - resolution: {integrity: sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==} - engines: {node: '>=0.10.0'} + '@types/chai@5.2.3': + resolution: {integrity: sha512-Mw558oeA9fFbv65/y4mHtXDs9bPnFMZAL/jxdPFUpOHHIXX91mcgEHbS5Lahr+pwZFR8A7GQleRWeI6cGFC2UA==} - pathe@2.0.3: - resolution: {integrity: sha512-WUjGcAqP1gQacoQe+OBJsFA7Ld4DyXuUIjZ5cc75cLHvJ7dtNsTugphxIADwspS+AraAUePCKrSVtPLFj/F88w==} + '@types/deep-eql@4.0.2': + resolution: {integrity: sha512-c9h9dVVMigMPc4bwTvC5dxqtqJZwQPePsWjPlpSOnojbor6pGqdk541lfA7AqFQr5pB1BRdq0juY9db81BwyFw==} - picocolors@1.1.1: - resolution: {integrity: sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==} + '@types/estree@1.0.8': + resolution: {integrity: sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w==} - picomatch@4.0.4: - resolution: {integrity: sha512-QP88BAKvMam/3NxH6vj2o21R6MjxZUAd6nlwAS/pnGvN9IVLocLHxGYIzFhg6fUQ+5th6P4dv4eW9jX3DSIj7A==} - engines: {node: '>=12'} + '@types/node@20.19.37': + resolution: {integrity: sha512-8kzdPJ3FsNsVIurqBs7oodNnCEVbni9yUEkaHbgptDACOPW04jimGagZ51E6+lXUwJjgnBw+hyko/lkFWCldqw==} + + '@vitest/browser-preview@5.0.3': + resolution: {integrity: sha512-jfnEBv5yDRPR1MeiTlTW5bcZoPsiI7MBhpZR8IJiSxVhdK6Pa4gPk3tLHTRxVHy24kkWxY2TnUbnszU3J1bQJw==} + peerDependencies: + vitest: 5.0.3 + + '@vitest/browser@5.0.3': + resolution: {integrity: sha512-7KHIkVA/gTRxQV9qPa9qLK+g5INZG6iRrlwXSFG5EKsOomW3UzzW7wsdt1Yp5PufzQ43lniuONlb4lUVCC2qSg==} + peerDependencies: + vitest: 5.0.3 + + '@vitest/mocker@5.0.3': + resolution: {integrity: sha512-T8sWAIbkSyAjkwTcaEc3Iu0o9A27X1/kdXrizhZkGuSKScRQtRzclfAMpOTcGdXCsqxeWlpGy3XjqaW8CpLORg==} + peerDependencies: + msw: ^2.4.9 + vite: ^6.0.0 || ^7.0.0 || ^8.0.0 + peerDependenciesMeta: + msw: + optional: true + vite: + optional: true + + '@vitest/pretty-format@5.0.3': + resolution: {integrity: sha512-3hr5eV5z2ff0l2Q1vaKQbB4j3M3tElw8stDokEOTqXJB3Es5VrT8gBRIMY59gGuFp8Y61sWlWFK1aNUH5dHDYw==} + + '@vitest/snapshot@5.0.3': + resolution: {integrity: sha512-SK4YTg9jL+jwakHIZxfciuvyOQsbY15AueR3USGtsXzmy+3FbJgqjAbC+ac20mRLwfEDtO5sfTqK6bUCDTRVPA==} - pirates@4.0.7: - resolution: {integrity: sha512-TfySrs/5nm8fQJDcBDuUng3VOUKsd7S+zqvbOTiGXHfxX4wK31ard+hoNuvkicM/2YFzlpDgABOevKSsB4G/FA==} - engines: {node: '>= 6'} + '@vitest/spy@5.0.3': + resolution: {integrity: sha512-XhFysQTB8AZ+P4gMi+Lpo99vg2AZi0qKpaB9yXQl37+CaMEAPO3iH/wGVnSyL5MPERiLezpqTVtrR6UZH5GCXg==} - pkg-types@1.3.1: - resolution: {integrity: sha512-/Jm5M4RvtBFVkKWRu2BLUTNP8/M2a+UwuAX+ae4770q1qVGtfjG+WTCupoZixokjmHiry8uI+dlY8KXYV5HVVQ==} + '@vitest/ui@5.0.3': + resolution: {integrity: sha512-9dfvqDBvSbHM6y9YVXxAypkl2z7mrWozK/JFo237qI0cj/3M/kzDKiVZqzi3TE54Ler7Jditud7OMBZCyUvRHA==} + peerDependencies: + vitest: 5.0.3 + + '@vitest/utils@5.0.3': + resolution: {integrity: sha512-77fDTZv8eu1CzlIfgPU2yrl5MXSUaUAOvgdZEeVm3X3WIxUubIX2SforqS6176lT32HCF6uBVMYVqfHxw7o6eg==} - postcss-load-config@6.0.1: - resolution: {integrity: sha512-oPtTM4oerL+UXmx+93ytZVN82RrlY/wPUV8IeDxFrzIjXOLF1pN+EmKPLbubvKHT2HC20xXsCAH2Z+CKV6Oz/g==} - engines: {node: '>= 18'} + '@voidzero-dev/vite-plus-core@1.1.0': + resolution: {integrity: sha512-Ai6HQP1zG0H3ZrmStEaMVKtDzW0xU3VgMzm3LFaXDQMEp8nAqvp0VGoU8HmpB9xv9vDSl+UxiSNOpuY9T714OQ==} + engines: {node: ^20.19.0 || ^22.18.0 || >=24.11.0} peerDependencies: + '@arethetypeswrong/core': ^0.18.1 + '@types/node': ^20.19.0 || >=22.12.0 + '@typescript/native-preview': '*' + '@vitejs/devtools': ^0.7.1 + '@volar/typescript': ~2.4.0 + '@vue/language-core': ~3.2.0 || ~3.3.0 + esbuild: ^0.27.0 || ^0.28.0 jiti: '>=1.21.0' - postcss: '>=8.0.9' + less: ^4.0.0 + publint: ^0.3.8 + sass: ^1.70.0 + sass-embedded: ^1.70.0 + stylus: '>=0.54.8' + sugarss: ^5.0.0 + terser: ^5.16.0 tsx: ^4.8.1 + typescript: ^5.0.0 || ^6.0.0 || ^7.0.0 + unplugin-unused: '>=0.5.0' + unrun: '*' + vue-tsc: ~3.2.0 || ~3.3.0 yaml: ^2.4.2 peerDependenciesMeta: + '@arethetypeswrong/core': + optional: true + '@types/node': + optional: true + '@typescript/native-preview': + optional: true + '@vitejs/devtools': + optional: true + '@volar/typescript': + optional: true + '@vue/language-core': + optional: true + esbuild: + optional: true jiti: optional: true - postcss: + less: + optional: true + publint: + optional: true + sass: + optional: true + sass-embedded: + optional: true + stylus: + optional: true + sugarss: + optional: true + terser: optional: true tsx: optional: true + typescript: + optional: true + unplugin-unused: + optional: true + unrun: + optional: true + vue-tsc: + optional: true yaml: optional: true - readdirp@4.1.2: - resolution: {integrity: sha512-GDhwkLfywWL2s6vEjyhri+eXmfH6j1L7JE27WhqLeYzoh/A3DBaYGEj2H/HFZCn/kMfim73FXxEJTw06WtxQwg==} - engines: {node: '>= 14.18.0'} + '@voidzero-dev/vite-plus-darwin-arm64@1.1.0': + resolution: {integrity: sha512-iWJdQZBRZWB6X1Aadk0Rrs/NJeP32Qb0OGeWnu1AYL0g6ZCj1yy3l9Oa+HasDyG97s+4UYo523LdBAAv3dlFwg==} + engines: {node: '>=20.0.0'} + cpu: [arm64] + os: [darwin] - resolve-from@5.0.0: - resolution: {integrity: sha512-qYg9KP24dD5qka9J47d0aVky0N+b4fTU89LN9iDnjB5waksiC49rvMB0PrUJQGoTmH50XPiqOvAjDfaijGxYZw==} - engines: {node: '>=8'} + '@voidzero-dev/vite-plus-darwin-x64@1.1.0': + resolution: {integrity: sha512-IYSQ2KdigRMuVkKCC+e5uNliCTSHa8j3ajHsFG2cfXoiGHS1f4ausbMEzNYtn9Tcn0Xsyo7l0LN8lvB7MapJhA==} + engines: {node: '>=20.0.0'} + cpu: [x64] + os: [darwin] - rollup@4.60.1: - resolution: {integrity: sha512-VmtB2rFU/GroZ4oL8+ZqXgSA38O6GR8KSIvWmEFv63pQ0G6KaBH9s07PO8XTXP4vI+3UJUEypOfjkGfmSBBR0w==} - engines: {node: '>=18.0.0', npm: '>=8.0.0'} - hasBin: true + '@voidzero-dev/vite-plus-linux-arm64-gnu@1.1.0': + resolution: {integrity: sha512-PmrJG8Md5KCWi/akWJz5GRjei2QfRf48R4rPB9CfualzAAlpMOmaMGeV3eloWVAFye85uhdtzCmIAqN5OUVBJA==} + engines: {node: '>=20.0.0'} + cpu: [arm64] + os: [linux] + libc: [glibc] - source-map@0.7.6: - resolution: {integrity: sha512-i5uvt8C3ikiWeNZSVZNWcfZPItFQOsYTUAOkcUPGd8DqDy1uOUikjt5dG+uRlwyvR108Fb9DOd4GvXfT0N2/uQ==} - engines: {node: '>= 12'} + '@voidzero-dev/vite-plus-linux-arm64-musl@1.1.0': + resolution: {integrity: sha512-zFr57++m5U474XEMcFGV025TOv9V2I5d87yylcuI/SvxQwDE9P7T5iRl55vGzQOzzInSnQuru6ByNIfmGa+84A==} + engines: {node: '>=20.0.0'} + cpu: [arm64] + os: [linux] + libc: [musl] - sucrase@3.35.1: - resolution: {integrity: sha512-DhuTmvZWux4H1UOnWMB3sk0sbaCVOoQZjv8u1rDoTV0HTdGem9hkAZtl4JZy8P2z4Bg0nT+YMeOFyVr4zcG5Tw==} - engines: {node: '>=16 || 14 >=14.17'} - hasBin: true + '@voidzero-dev/vite-plus-linux-x64-gnu@1.1.0': + resolution: {integrity: sha512-DXeRuTMylEbIVCQX+pSBM27lSPQwAKO6PDnGBJ6nFGhu37AmNeVl7cIChtcniXOuykbFduhSt3eIr/6CZHoEag==} + engines: {node: '>=20.0.0'} + cpu: [x64] + os: [linux] + libc: [glibc] - thenify-all@1.6.0: - resolution: {integrity: sha512-RNxQH/qI8/t3thXJDwcstUO4zeqo64+Uy/+sNVRBx4Xn2OX+OZ9oP+iJnNFqplFra2ZUVeKCSa2oVWi3T4uVmA==} - engines: {node: '>=0.8'} + '@voidzero-dev/vite-plus-linux-x64-musl@1.1.0': + resolution: {integrity: sha512-J7/Mb/kxtENUsL5Gh+8SSr/p054fSqU4oj0GEthr/2VEFSwZouOUigk6CzE7ANKFeTNJm40n8QCnjm7Oiy09pQ==} + engines: {node: '>=20.0.0'} + cpu: [x64] + os: [linux] + libc: [musl] - thenify@3.3.1: - resolution: {integrity: sha512-RVZSIV5IG10Hk3enotrhvz0T9em6cyHBLkH/YAZuKqd8hRkKhSfCGIcP2KUY0EPxndzANBmNllzWPwak+bheSw==} + '@voidzero-dev/vite-plus-win32-arm64-msvc@1.1.0': + resolution: {integrity: sha512-EXWz8Tt7skmI0fLMSq5zYcuHo6SCF0reG55qDB/NMvWVIAca6Dborq7D1HyKA8v6p39zOl8r07gR7yXuYv7eeQ==} + engines: {node: '>=20.0.0'} + cpu: [arm64] + os: [win32] - tinyexec@0.3.2: - resolution: {integrity: sha512-KQQR9yN7R5+OSwaK0XQoj22pwHoTlgYqmUscPYoknOoWCWfj/5/ABTMRi69FrKU5ffPVh5QcFikpWJI/P1ocHA==} + '@voidzero-dev/vite-plus-win32-x64-msvc@1.1.0': + resolution: {integrity: sha512-K9k77Mj/TszFHKsZePKsyfsIPo8HbjKeJUxaSJ6YZKAmSh9QvDsMFe4i4/HALwyii/PR1EQ8N0iN1nf9cNimGQ==} + engines: {node: '>=20.0.0'} + cpu: [x64] + os: [win32] - tinyglobby@0.2.15: - resolution: {integrity: sha512-j2Zq4NyQYG5XMST4cbs02Ak8iJUdxRM0XI5QyxXuZOzKOINmWurp3smXu3y5wDcJrptwpSjgXHzIQxR0omXljQ==} - engines: {node: '>=12.0.0'} + '@yuku-codegen/binding-android-arm64@0.9.5': + resolution: {integrity: sha512-jrOY5WM+AaAqkv51fHP1x28ifto4WgcZVzodLUyMU1jMWn5Sq+VciRdk/n8E0Ey5w4p4cWWE+m5GfXWOYh7Kzw==} + cpu: [arm64] + os: [android] + deprecated: yuku-codegen is pure JavaScript since 0.14 - tree-kill@1.2.2: - resolution: {integrity: sha512-L0Orpi8qGpRG//Nd+H90vFB+3iHnue1zSSGmNOOCh1GLJ7rUKVwV2HvijphGQS2UmhUZewS9VgvxYIdgr+fG1A==} - hasBin: true + '@yuku-codegen/binding-darwin-arm64@0.9.5': + resolution: {integrity: sha512-4O4lkCQIzPZGjiNB1GORSI6hBECbiiSBS/APZXPvNKYH+nhY4uuqv03LNXA+SET3hoBjvr95P5rIhY8KQaQUBA==} + cpu: [arm64] + os: [darwin] + deprecated: yuku-codegen is pure JavaScript since 0.14 - ts-interface-checker@0.1.13: - resolution: {integrity: sha512-Y/arvbn+rrz3JCKl9C4kVNfTfSm2/mEp5FSz5EsZSANGPSlQrpRI5M4PKF+mJnE52jOO90PnPSc3Ur3bTQw0gA==} + '@yuku-codegen/binding-darwin-x64@0.9.5': + resolution: {integrity: sha512-9EvoUO0SEhD6/d8VHdWuPerepPMSR1y84+UEgz8Un1Ope14Oe7xMvePpEQLTLovoIFZ8Zg3iZ8z8E11pZMqC3g==} + cpu: [x64] + os: [darwin] + deprecated: yuku-codegen is pure JavaScript since 0.14 - tsup@8.5.1: - resolution: {integrity: sha512-xtgkqwdhpKWr3tKPmCkvYmS9xnQK3m3XgxZHwSUjvfTjp7YfXe5tT3GgWi0F2N+ZSMsOeWeZFh7ZZFg5iPhing==} - engines: {node: '>=18'} - hasBin: true - peerDependencies: - '@microsoft/api-extractor': ^7.36.0 - '@swc/core': ^1 - postcss: ^8.4.12 - typescript: '>=4.5.0' - peerDependenciesMeta: - '@microsoft/api-extractor': - optional: true - '@swc/core': - optional: true - postcss: - optional: true - typescript: - optional: true + '@yuku-codegen/binding-freebsd-x64@0.9.5': + resolution: {integrity: sha512-HqT78WwgHTmp8lwujoUa9CrIortX4DdpuiVC18ZPSGvuuJf4ylpIEI6QrQEM78Zwz3muhAWAOXZ5irdiYY+AyA==} + cpu: [x64] + os: [freebsd] + deprecated: yuku-codegen is pure JavaScript since 0.14 - typescript@5.9.3: - resolution: {integrity: sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==} - engines: {node: '>=14.17'} - hasBin: true + '@yuku-codegen/binding-linux-arm-gnu@0.9.5': + resolution: {integrity: sha512-QJXwIW6Ms3QIawbcBIedmrmXnfdpuAOjZJ/eAABq5XTzWvSxZ7lutu9W5yIHahlaTaFnBo7ikrVMD1szLTpPUw==} + cpu: [arm] + os: [linux] + libc: [glibc] + deprecated: yuku-codegen is pure JavaScript since 0.14 - ufo@1.6.3: - resolution: {integrity: sha512-yDJTmhydvl5lJzBmy/hyOAA0d+aqCBuwl818haVdYCRrWV84o7YyeVm4QlVHStqNrrJSTb6jKuFAVqAFsr+K3Q==} + '@yuku-codegen/binding-linux-arm-musl@0.9.5': + resolution: {integrity: sha512-eHbFy3IHGb+IYarrYwAo0yWSEQV3eAmEn6VrsKA6I1Wy1YPJxWqSJlV69JhqsHtJuJlljUIF3ex0y46yaKIu9w==} + cpu: [arm] + os: [linux] + libc: [musl] + deprecated: yuku-codegen is pure JavaScript since 0.14 - undici-types@6.21.0: - resolution: {integrity: sha512-iwDZqg0QAGrg9Rav5H4n0M64c3mkR59cJ6wQp+7C4nI0gsmExaedaYLNO44eT4AtBBwjbTiGPMlt2Md0T9H9JQ==} + '@yuku-codegen/binding-linux-arm64-gnu@0.9.5': + resolution: {integrity: sha512-ByoJMbySTaDhjAXSu8q6Lh7HKg3YoesXpcT72aYk0Aiw4PCznmY4ybpLTq0RCvp0RIPhFm/6yECFiGBwyCC1nw==} + cpu: [arm64] + os: [linux] + libc: [glibc] + deprecated: yuku-codegen is pure JavaScript since 0.14 -snapshots: + '@yuku-codegen/binding-linux-arm64-musl@0.9.5': + resolution: {integrity: sha512-gD9vfXIoBw1toSxhO9rgmSu/FEfy3PMznJAxVjIuH8DrWEiDKXmJO0pJKfj1Ltbe/TmtWVZR+TjISqeSIGQzMg==} + cpu: [arm64] + os: [linux] + libc: [musl] + deprecated: yuku-codegen is pure JavaScript since 0.14 - '@esbuild/aix-ppc64@0.27.4': - optional: true + '@yuku-codegen/binding-linux-x64-gnu@0.9.5': + resolution: {integrity: sha512-BARvdnvqMGjOr5Iel2JH+9H5vAIE0R6H0Z2fsT02xrvmI/1ZXNB8lkuX+ZGevPhZb3zJ9WeC/R8JZstrsUOK5g==} + cpu: [x64] + os: [linux] + libc: [glibc] + deprecated: yuku-codegen is pure JavaScript since 0.14 - '@esbuild/android-arm64@0.27.4': - optional: true + '@yuku-codegen/binding-linux-x64-musl@0.9.5': + resolution: {integrity: sha512-x8flcevS1fbb7ESrmpOY/pON4eInSaE/7Ktjqx2udOE2W33BNSZmJuwpyUgo54AoHNqrcaM7szqydyJn5fNvew==} + cpu: [x64] + os: [linux] + libc: [musl] + deprecated: yuku-codegen is pure JavaScript since 0.14 - '@esbuild/android-arm@0.27.4': - optional: true + '@yuku-codegen/binding-win32-arm64@0.9.5': + resolution: {integrity: sha512-KOL/rBatWqH4ZpCNoF8ZtSNdOJbAxBJLmk/VeRciepGROPTbPum1A9t67GpFgOU7qrkUWlPJdJ+KHKbvbmOt+w==} + cpu: [arm64] + os: [win32] + deprecated: yuku-codegen is pure JavaScript since 0.14 - '@esbuild/android-x64@0.27.4': - optional: true + '@yuku-codegen/binding-win32-x64@0.9.5': + resolution: {integrity: sha512-FxENahEjWSan59Syh/us/Kf4wNq8qrJLFJ3R2N4Oiwtb6yNdz/rWLNTIoNSssnsp7IoWJdUP6o/Z8ppg7lXMcg==} + cpu: [x64] + os: [win32] + deprecated: yuku-codegen is pure JavaScript since 0.14 - '@esbuild/darwin-arm64@0.27.4': - optional: true + '@yuku-parser/binding-android-arm64@0.9.5': + resolution: {integrity: sha512-A2JCFCSHfnficqYEw4Iujpx7XrkMM3UfFcgJFmYpZSo9zM4nyhmdIIE0FogSduuU60lhM0/UcfuUBXIVNBMlGQ==} + cpu: [arm64] + os: [android] + deprecated: yuku-parser runs on yuku-core since 0.14 - '@esbuild/darwin-x64@0.27.4': - optional: true + '@yuku-parser/binding-darwin-arm64@0.9.5': + resolution: {integrity: sha512-3PiyU+Eare4YuKaQ22N98/yAiROPY5o/NQJHraICzDvk4pgS+m+bgLKOvkGBRn33OnV95Vdv1Mn38b+MQHpULQ==} + cpu: [arm64] + os: [darwin] + deprecated: yuku-parser runs on yuku-core since 0.14 - '@esbuild/freebsd-arm64@0.27.4': - optional: true + '@yuku-parser/binding-darwin-x64@0.9.5': + resolution: {integrity: sha512-blFMAFI7AInI83XaiOF8cIeiRM46Nz9EfpZtZPRLbKxSA9aAr5v8aHYpmfN6NoyXxAv/10pwS4gsAuc1W6fy5Q==} + cpu: [x64] + os: [darwin] + deprecated: yuku-parser runs on yuku-core since 0.14 - '@esbuild/freebsd-x64@0.27.4': - optional: true + '@yuku-parser/binding-freebsd-x64@0.9.5': + resolution: {integrity: sha512-TsNuL4qsZdO0tHp5GW43Y5fHeLPpPHA675wdcPNTcdq2ZO5AvsQWFFoiDg8CH4oBktfsQ9tdvKhjLnLYwKvp4g==} + cpu: [x64] + os: [freebsd] + deprecated: yuku-parser runs on yuku-core since 0.14 + + '@yuku-parser/binding-linux-arm-gnu@0.9.5': + resolution: {integrity: sha512-b0afYK5gHeV8RdmOcqAlgM8ONsye4cax4DMnIBaoJyPMd1UTGvTbpkqQcPVdhmHlcfcGVGMV1laeVovlr/dscw==} + cpu: [arm] + os: [linux] + libc: [glibc] + deprecated: yuku-parser runs on yuku-core since 0.14 + + '@yuku-parser/binding-linux-arm-musl@0.9.5': + resolution: {integrity: sha512-fD3lKzl+r6j6n8DwiMY53qnh5DLqD8KJjCp+NbVud54Nnh3Q9Wprqss3YzyMHP3NSJk663Wlg1E1X5qOuFVFig==} + cpu: [arm] + os: [linux] + libc: [musl] + deprecated: yuku-parser runs on yuku-core since 0.14 + + '@yuku-parser/binding-linux-arm64-gnu@0.9.5': + resolution: {integrity: sha512-aRU/aCphV1MWCl/lvI6NX8LgucHQ68Fx+vz7NBb/5MEr2EuzCxiPOQqeFcMdWh+2AED/p0AvAREch0c+cSnlhA==} + cpu: [arm64] + os: [linux] + libc: [glibc] + deprecated: yuku-parser runs on yuku-core since 0.14 + + '@yuku-parser/binding-linux-arm64-musl@0.9.5': + resolution: {integrity: sha512-5+Guro0l8H473YXlEjVNBRLN/IbPbJdnQh1zo0OLt49xxnON+XMJRRaEyTOTfkn9QSRg0+BhEKGR4W9Gu2uZJQ==} + cpu: [arm64] + os: [linux] + libc: [musl] + deprecated: yuku-parser runs on yuku-core since 0.14 + + '@yuku-parser/binding-linux-x64-gnu@0.9.5': + resolution: {integrity: sha512-pwwSyV9q+GlvzSXlsMZBMlgk22L5bud714/vqZCdeUTivzeUVltQzUaf3IQXOGXdpc59JYTeuMCtbGquaAUoCA==} + cpu: [x64] + os: [linux] + libc: [glibc] + deprecated: yuku-parser runs on yuku-core since 0.14 + + '@yuku-parser/binding-linux-x64-musl@0.9.5': + resolution: {integrity: sha512-z18j6JN3lBHH8vzN7gG1M8fI0nlgItSfnC9PfbmUbt97iHViKfw2iA2G9fGwRMe5LyPRZBrejIlapbygPbpdaw==} + cpu: [x64] + os: [linux] + libc: [musl] + deprecated: yuku-parser runs on yuku-core since 0.14 + + '@yuku-parser/binding-win32-arm64@0.9.5': + resolution: {integrity: sha512-s6Gwttb1dQvtPX6Bgkw+UPC9IO3UBDXc6Zezog8MMgvu3UfJBBB9TQqKBX/2quu0Eyh+lLSAyNlIyyecaagUPg==} + cpu: [arm64] + os: [win32] + deprecated: yuku-parser runs on yuku-core since 0.14 + + '@yuku-parser/binding-win32-x64@0.9.5': + resolution: {integrity: sha512-FrERt9YWatY3bJfSUdi4YWY+6iQcyo3MzCC821BxlWM5TFZEHijnpz/bknR79VHlXY/9CvXz8ZlaAGPsTtN3nw==} + cpu: [x64] + os: [win32] + deprecated: yuku-parser runs on yuku-core since 0.14 + + '@yuku-toolchain/types@0.9.5': + resolution: {integrity: sha512-KiuLNNgX9uNealaWAR+G3/cMXnRk9x4TY2EkYe/KIag+UPdwiA0RRf1hr1WAxzTP8KGzCTkqUdLPvqh32sEO3w==} + + ansi-regex@5.0.1: + resolution: {integrity: sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==} + engines: {node: '>=8'} + + ansi-styles@5.2.0: + resolution: {integrity: sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==} + engines: {node: '>=10'} + + aria-query@5.3.0: + resolution: {integrity: sha512-b0P0sZPKtyu8HkeRAfCq0IfURZK+SuwMjY1UXGBU27wpAiTwQAIlq56IbIO+ytk/JjS1fMR14ee5WBBfKi5J6A==} + + assertion-error@2.0.1: + resolution: {integrity: sha512-Izi8RQcffqCeNVgFigKli1ssklIbpHnCYc6AknXGYoB6grJqyeby7jv12JUQgmTAnIDnbck1uxksT4dzN3PWBA==} + engines: {node: '>=12'} + + chai@6.3.0: + resolution: {integrity: sha512-XWAtwJ6OHO+tj0EKCs0Y2UamnyOxseZWltU4x2U2wh8g4AigdjwvtUjvLP2tqkA/avxHEtzxNaqGq/YGNwckKg==} + engines: {node: '>=18'} + + convert-source-map@2.0.0: + resolution: {integrity: sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==} + + dequal@2.0.3: + resolution: {integrity: sha512-0je+qPKHEMohvfRTCEo3CrPG6cAzAYgmzKyxRiYSSDkS6eGJdyVJm7WaYA5ECaAD9wLB2T4EEeymA5aFVcYXCA==} + engines: {node: '>=6'} + + detect-libc@2.1.2: + resolution: {integrity: sha512-Btj2BOOO83o3WyH59e8MgXsxEQVcarkUOpEYrubB0urwnN10yQ364rsiByU11nZlqWYZm05i/of7io4mzihBtQ==} + engines: {node: '>=8'} + + dom-accessibility-api@0.5.16: + resolution: {integrity: sha512-X7BJ2yElsnOJ30pZF4uIIDfBEVgF4XEBxL9Bxhy6dnrm5hkzqmsWHGTiHqRiITNhMyFLyAiWndIJP7Z1NTteDg==} + + es-module-lexer@2.3.2: + resolution: {integrity: sha512-poHGpORABojJJucnV9KbOavETW8lBVnphkW77ER5/BQ5Fz7oXSoCNek7IH3vR5nRjdsEz926ibFYX8KtLQmdyw==} + + esbuild@0.27.4: + resolution: {integrity: sha512-Rq4vbHnYkK5fws5NF7MYTU68FPRE1ajX7heQ/8QXXWqNgqqJ/GkmmyxIzUnf2Sr/bakf8l54716CcMGHYhMrrQ==} + engines: {node: '>=18'} + hasBin: true + + estree-walker@3.0.3: + resolution: {integrity: sha512-7RUKfXgSMMkzt6ZuXmqapOurLGPPfgj6l9uRZ7lRGolvk0y2yocc35LdcxKC5PQZdn2DMqioAQ2NoWcrTKmm6g==} + + expect-type@1.4.0: + resolution: {integrity: sha512-KfYbmpRm0VbLjEvVa9yGwCi9GI34xvi7A/HXYWQO65CSD2u3MczUJSuwXKFIxlGsgBQizV9q5J9NHj4VG0n+pA==} + engines: {node: '>=12.0.0'} + + fdir@6.5.0: + resolution: {integrity: sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg==} + engines: {node: '>=12.0.0'} + peerDependencies: + picomatch: ^3 || ^4 + peerDependenciesMeta: + picomatch: + optional: true + + fflate@0.8.3: + resolution: {integrity: sha512-tbZNuJrLwGUp3zshBtdy4W+ORxZuIh8a5ilyIEQDC5rY1f3U20JMry0Ll3WBzU58EZKsEuJFXhb5gwv8CsPvgA==} + + flatted@3.4.4: + resolution: {integrity: sha512-5+ybhBZANEJxaH3X5evAFatUxLfEHSr7n6kYJ+1Qd0mUqr4eu9gIf6GDbWHf8RJijHrjjO8G+la14SlL2SeS1Q==} + + fsevents@2.3.3: + resolution: {integrity: sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==} + engines: {node: ^8.16.0 || ^10.6.0 || >=11.0.0} + os: [darwin] + + js-tokens@4.0.0: + resolution: {integrity: sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==} + + lightningcss-android-arm64@1.33.0: + resolution: {integrity: sha512-gEpRTalKdosp4Bb8qWtc2iOgE5SeIHlpS1up9bFq2wAyYhl1UdTObYiHe98zEM9SQvSoqQZ1IQD0JNpg3Ml5pg==} + engines: {node: '>= 12.0.0'} + cpu: [arm64] + os: [android] + + lightningcss-darwin-arm64@1.33.0: + resolution: {integrity: sha512-Sciaz8eenNTKn9b3t7+xr0ipTp9YxKQY4npwQ3mrRuL0BAVHBLyZxofhaKBAVtzmtRZ/zTyo0/to4B1uWG/Djg==} + engines: {node: '>= 12.0.0'} + cpu: [arm64] + os: [darwin] + + lightningcss-darwin-x64@1.33.0: + resolution: {integrity: sha512-Z5UPAxzrjlWNNyGy6i65cJzzvgJ5D3T6wMvs+gWpY9d7qRhANrxqAp6LhxIgZhWEw18RfJTGcRxjuLIBr+m8XQ==} + engines: {node: '>= 12.0.0'} + cpu: [x64] + os: [darwin] + + lightningcss-freebsd-x64@1.33.0: + resolution: {integrity: sha512-QQM/Ti/hQajJwCY+RiWuCZ9sdtI/XQk7nDK5vC8kkdwixezOlDgvDx7+RT+QjK6FcFT4MpsuoBnHIo/O3StRRg==} + engines: {node: '>= 12.0.0'} + cpu: [x64] + os: [freebsd] + + lightningcss-linux-arm-gnueabihf@1.33.0: + resolution: {integrity: sha512-N7FVBe6iS24MlM6R/4RBTxGhQheZGs7tiQ9U32UtF75NzP5Q7xWPRqLBCKxlRQRk3rY1jCIPLzx7WzOhuUIRLQ==} + engines: {node: '>= 12.0.0'} + cpu: [arm] + os: [linux] + + lightningcss-linux-arm64-gnu@1.33.0: + resolution: {integrity: sha512-j2v/itmy4HlNxlc6voKXYgBqNi0Ng2LShg4z7GufpEgs05P+2suBVyi9I6YHq5uoVFx9ETin3eCEhLVyXGQnKg==} + engines: {node: '>= 12.0.0'} + cpu: [arm64] + os: [linux] + libc: [glibc] + + lightningcss-linux-arm64-musl@1.33.0: + resolution: {integrity: sha512-yiO5ROMuYQgXbC60yjZU5CYSFZGKXL0HFATXt9mHJn1+zW55oCtMI9NfcVhYLMFDL7gV7oBPon/EmMMGg2OvtQ==} + engines: {node: '>= 12.0.0'} + cpu: [arm64] + os: [linux] + libc: [musl] + + lightningcss-linux-x64-gnu@1.33.0: + resolution: {integrity: sha512-ar+Ju7LmcN0Jo4FpL4hpFybwNG9/3A/Br5KW2n2jyODg3MEZXaDYADdemoNS+BDNfMgKvylJLj4S5tyRActuAg==} + engines: {node: '>= 12.0.0'} + cpu: [x64] + os: [linux] + libc: [glibc] + + lightningcss-linux-x64-musl@1.33.0: + resolution: {integrity: sha512-RYiYbkokw0trfKqqzfF55lginwEPrD3OJDfTuJzFs1MK6iFnDenaz1fqLLtX4ITG3OktJQXOeTaw1awrBAlZPw==} + engines: {node: '>= 12.0.0'} + cpu: [x64] + os: [linux] + libc: [musl] + + lightningcss-win32-arm64-msvc@1.33.0: + resolution: {integrity: sha512-1K+MPfLSFVpphzpdbfkhlWk6wBrTObBzS2T6db10PNOZgR9GoVsAWzwNyuhUYYbTp23j+4RrncfujZ4uAzXvwA==} + engines: {node: '>= 12.0.0'} + cpu: [arm64] + os: [win32] + + lightningcss-win32-x64-msvc@1.33.0: + resolution: {integrity: sha512-OlEICDx/Xl0FqSp4bry8zFnCvGpig3Gl4gCquvYwHuqJKEC1+n9NgDniFvqHGmMv1ZkqDJrDqKKSykTDX+ehuA==} + engines: {node: '>= 12.0.0'} + cpu: [x64] + os: [win32] + + lightningcss@1.33.0: + resolution: {integrity: sha512-WkUDrojuJs0xkgGf2udWxa3yGBRxPtxUkB79i6aCZLRgc7PM8fZe9TosfPDcvEpQZbuFASnHYmRLBLUbmLOIIA==} + engines: {node: '>= 12.0.0'} + + lz-string@1.5.0: + resolution: {integrity: sha512-h5bgJWpxJNswbU7qCrV0tIKQCaS3blPDrqKWx+QxzuzL1zGUzij9XCWLrSLsJPu5t+eWA/ycetzYAO5IOMcWAQ==} + hasBin: true + + magic-string@1.4.3: + resolution: {integrity: sha512-z12OxmaPGE0F4xlpGdjuAUckipLpQlTAuAmyGhNoD7ODpYUzvDfvtxUbjM/jwznyP178oju/KDdyi220wNJ0RQ==} + + mrmime@2.0.1: + resolution: {integrity: sha512-Y3wQdFg2Va6etvQ5I82yUhGdsKrcYox6p7FfL1LbK2J4V01F9TGlepTIhnK24t7koZibmg82KGglhA1XK5IsLQ==} + engines: {node: '>=10'} + + nanoid@3.3.20: + resolution: {integrity: sha512-uKdg2G3GNCKQn9byYOpxbGqrT2fGO5KRt5J/8b3pok8rT6qxGWF6hxMyJiEYtAf+FVyYuD9hRaDqX5uPFYJ4ZQ==} + engines: {node: ^10 || ^12 || ^13.7 || ^14 || >=15.0.1} + hasBin: true + + obug@2.2.1: + resolution: {integrity: sha512-XrsrhT5sybtKI6wakr2SPOlGZWWYbUXZ7a0jT8/QOeAPau+1X/bSegNe5YR75oJmEZQbKningirmGOEJCIk61Q==} + engines: {node: '>=12.20.0'} + + oxfmt@0.72.0: + resolution: {integrity: sha512-16OQeL0uhPZbfmT9S9WWM0Nv9+BmVi8Ra1uTYaSKj/qSCs6FB30KOhz9JoNYw6thT+K5bVxsk/tSEOu497i34g==} + engines: {node: ^20.19.0 || >=22.12.0} + hasBin: true + peerDependencies: + svelte: ^5.0.0 + vite-plus: '*' + peerDependenciesMeta: + svelte: + optional: true + vite-plus: + optional: true + + oxlint-tsgolint@7.0.2003: + resolution: {integrity: sha512-VnK4zlqgmgq/7ZcjzCk/WpN8kKFsYGcB85Io9qT3wL4K8Un3RKmJkp793crNETieMusPMKOA4a+Mw2wbteI6TQ==} + hasBin: true + + oxlint@1.87.0: + resolution: {integrity: sha512-drxVlaW7Oqq7wSy1CN25znRu00aRq8n57Q2wniJRxCmtYVz07ufCm7EVDPQFZURrTgQIjzKrVEHAD/HcvJXObg==} + engines: {node: ^20.19.0 || >=22.12.0} + hasBin: true + peerDependencies: + oxlint-tsgolint: '>=7.0.2003' + vite-plus: '*' + peerDependenciesMeta: + oxlint-tsgolint: + optional: true + vite-plus: + optional: true + + pathe@2.0.3: + resolution: {integrity: sha512-WUjGcAqP1gQacoQe+OBJsFA7Ld4DyXuUIjZ5cc75cLHvJ7dtNsTugphxIADwspS+AraAUePCKrSVtPLFj/F88w==} + + picocolors@1.1.1: + resolution: {integrity: sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==} + + picomatch@4.0.7: + resolution: {integrity: sha512-qcJu88Q2IWqJsDD529JKMdwGm/dvInW4HvQnRwiH9JtihJvzGOscDtHE3x1pBKeUOTysQ8kVmLnJ2kJu7yhcGA==} + engines: {node: '>=12'} + + pngjs@7.0.0: + resolution: {integrity: sha512-LKWqWJRhstyYo9pGvgor/ivk2w94eSjE3RGVuzLGlr3NmD8bf7RcYGze1mNdEHRP6TRP6rMuDHk5t44hnTRyow==} + engines: {node: '>=14.19.0'} + + postcss@8.5.30: + resolution: {integrity: sha512-zkd+e9S1AQHhmaBKye9zlFHWjOwVgqXl1q3ECYy8561K7qxR2cREf/STLskulBFd78k6AOFeA+ff1TtEzk9bAA==} + engines: {node: ^10 || ^12 || >=14} + + pretty-format@27.5.1: + resolution: {integrity: sha512-Qb1gy5OrP5+zDf2Bvnzdl3jsTf1qXVMazbvCoKhtKqVs4/YK4ozX4gKQJJVyNe+cajNPn0KoC0MC3FUmaHWEmQ==} + engines: {node: ^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0} + + react-is@17.0.2: + resolution: {integrity: sha512-w2GsyukL62IJnlaff/nRegPQR94C/XXamvMWmSHRJ4y7Ts/4ocGRmTHvOs8PSE6pB3dWOrD/nueuU5sduBsQ4w==} + + sirv@3.0.2: + resolution: {integrity: sha512-2wcC/oGxHis/BoHkkPwldgiPSYcpZK3JU28WoMVv55yHJgcZ8rlXvuG9iZggz+sU1d4bRgIGASwyWqjxu3FM0g==} + engines: {node: '>=18'} + + source-map-js@1.2.2: + resolution: {integrity: sha512-KGj/8Y43x35aZVDtt+J4mK1hoLGHULMYfSkODJNQjNDC3oW1PqPoxMwo0pLUsWM/UEGzON/NxeHywEfNXNP3Vw==} + engines: {node: '>=0.10.0'} + + std-env@4.3.0: + resolution: {integrity: sha512-OtU/EgQ1kIm5KwqQpBC6ZEMXrZRui11w8zgfTWp8cdO9B8OaPsbA8bTHO2P+HNo1VlUTGMVBwPhydu6poeXiag==} + + tinybench@6.2.2: + resolution: {integrity: sha512-nIZ2N2dFpjBsg2hgitKu7mA+FvsIJuGJOwysRngmVRRmWgR/UyPjwJj8c/fT/dhm8EkHnF5EOogRf+0Z2qrimA==} + engines: {node: '>=20.0.0'} + + tinyexec@1.3.1: + resolution: {integrity: sha512-GCvB3aoys96IuDFBMcTB46JOR6mdMtAToqwiW8JlWhsoh1mhHi/xn9ss/Dg7N555GiJyEt2qzoG/NHCwM6h1EA==} + engines: {node: '>=18'} + + tinyglobby@0.2.17: + resolution: {integrity: sha512-wXR/dYpcqKmfWpEdZjiKJOwCNFndD0DMnrW/cYjVGttEkBfVgcLFHoNrlj47mjOVic9yyNu65alsgF4NQyTa2g==} + engines: {node: '>=12.0.0'} + + tinypool@2.2.0: + resolution: {integrity: sha512-jBrmx4lYmaC9k/mgPbylxs7kBUxHtD8256up+HjLaDFfXScKJQyil+SWXSvhAtT7XHo+yTVlpB81PGmHP8oLSQ==} + engines: {node: ^20.0.0 || >=22.0.0} + + tinyrainbow@3.2.0: + resolution: {integrity: sha512-LgO3D9yZJjApUiuUfl9iFAwrtaX4+lok3wJIqttGoKCHlWUqHqbQpnxCf82L8FjgKsh4iGo78hqJwgL8F6To2A==} + engines: {node: '>=14.0.0'} + + totalist@3.0.1: + resolution: {integrity: sha512-sf4i37nQ2LBx4m3wB74y+ubopq6W/dIzXg0FDGjsYnZHVa1Da8FH853wlL2gtUhg+xJXjfk3kUZS3BRoQeoQBQ==} + engines: {node: '>=6'} + + typescript@5.9.3: + resolution: {integrity: sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==} + engines: {node: '>=14.17'} + hasBin: true + + undici-types@6.21.0: + resolution: {integrity: sha512-iwDZqg0QAGrg9Rav5H4n0M64c3mkR59cJ6wQp+7C4nI0gsmExaedaYLNO44eT4AtBBwjbTiGPMlt2Md0T9H9JQ==} + + vite-plus@1.1.0: + resolution: {integrity: sha512-khkRlY70qqbBV0QmF9QKXSrhxIRYftb1H05AJMowMoq7iouchmAoXYeReZ8pKKMOadqrWVxFa3S9BA2m7w59iA==} + engines: {node: ^22.18.0 || ^24.11.0 || >=26.0.0} + hasBin: true + peerDependencies: + '@vitest/browser-playwright': 5.0.3 + '@vitest/browser-webdriverio': ^5.0.0-beta.5 || >=5.0.0 + playwright: '*' + peerDependenciesMeta: + '@vitest/browser-playwright': + optional: true + '@vitest/browser-webdriverio': + optional: true + playwright: + optional: true + + vitest@5.0.3: + resolution: {integrity: sha512-xMw97S3rjdtj5dkVat7jCsqWBpvchs3RlpQctUqwJD0KkERk40vz2fJ77lDwW/Vzh/pk18eItYAzkodhSes3jQ==} + engines: {node: ^22.12.0 || ^24.0.0 || >=26.0.0} + hasBin: true + peerDependencies: + '@edge-runtime/vm': '*' + '@opentelemetry/api': ^1.9.0 + '@types/node': ^22.0.0 || >=24.0.0 + '@vitest/browser-playwright': 5.0.3 + '@vitest/browser-preview': 5.0.3 + '@vitest/browser-webdriverio': ^5.0.0-beta.5 || >=5.0.0 + '@vitest/coverage-istanbul': 5.0.3 + '@vitest/coverage-v8': 5.0.3 + '@vitest/ui': 5.0.3 + happy-dom: '*' + jsdom: '*' + vite: ^6.4.0 || ^7.0.0 || ^8.0.0 + peerDependenciesMeta: + '@edge-runtime/vm': + optional: true + '@opentelemetry/api': + optional: true + '@types/node': + optional: true + '@vitest/browser-playwright': + optional: true + '@vitest/browser-preview': + optional: true + '@vitest/browser-webdriverio': + optional: true + '@vitest/coverage-istanbul': + optional: true + '@vitest/coverage-v8': + optional: true + '@vitest/ui': + optional: true + happy-dom: + optional: true + jsdom: + optional: true + + why-is-node-running@3.2.1: + resolution: {integrity: sha512-Tb2FUhB4vUsGQlfSquQLYkApkuPAFQXGFzxWKHHumVz2dK+X1RUm/HnID4+TfIGYJ1kTcwOaCk/buYCEJr6YjQ==} + engines: {node: '>=20.11'} + hasBin: true + + ws@8.22.0: + resolution: {integrity: sha512-Ydggc987+RO0AnWtZ/7Wq9FtNvcrL1b/RO0ud9mWjUPgDrsAAwQSF51sm2hm1XofbU/4jkpGEsLFsZZxU+1DOg==} + engines: {node: '>=10.0.0'} + peerDependencies: + bufferutil: ^4.0.1 + utf-8-validate: '>=5.0.2' + peerDependenciesMeta: + bufferutil: + optional: true + utf-8-validate: + optional: true + + yuku-ast@0.9.5: + resolution: {integrity: sha512-Q8qW8WwQnN5Cm0ZZivdRIfv0sRLTjUq0YumXJkw8CYN1aCdICH9rk4C47/4n6kA5EqDtlj+F608twoTSV2MwdQ==} + + yuku-codegen@0.9.5: + resolution: {integrity: sha512-zGUVyDpSK4b6c9B0yyxi2P0wujn1XZTbG9dCb7d6gyAoP63EMgyLzUwPUvwxPG5jgYqhlI7w+S3oCyapqGr4PA==} + + yuku-parser@0.9.5: + resolution: {integrity: sha512-IBnAdNVMswWbJcM7woPluOudectJzFjJ1N8jVRxP9Uq4CIv5hZdBIcdmtRbFDYF/Ph2j2gVup4YJ4N9mh0jYFA==} + +snapshots: + + '@babel/code-frame@7.29.7': + dependencies: + '@babel/helper-validator-identifier': 7.29.7 + js-tokens: 4.0.0 + picocolors: 1.1.1 + + '@babel/helper-validator-identifier@7.29.7': {} + + '@babel/runtime@7.29.10': {} + + '@blazediff/core@1.10.0': {} + + '@esbuild/aix-ppc64@0.27.4': + optional: true + + '@esbuild/android-arm64@0.27.4': + optional: true + + '@esbuild/android-arm@0.27.4': + optional: true + + '@esbuild/android-x64@0.27.4': + optional: true + + '@esbuild/darwin-arm64@0.27.4': + optional: true + + '@esbuild/darwin-x64@0.27.4': + optional: true + + '@esbuild/freebsd-arm64@0.27.4': + optional: true + + '@esbuild/freebsd-x64@0.27.4': + optional: true '@esbuild/linux-arm64@0.27.4': optional: true @@ -616,125 +1276,396 @@ snapshots: '@esbuild/win32-x64@0.27.4': optional: true - '@jridgewell/gen-mapping@0.3.13': - dependencies: - '@jridgewell/sourcemap-codec': 1.5.5 - '@jridgewell/trace-mapping': 0.3.31 - '@jridgewell/resolve-uri@3.1.2': {} '@jridgewell/sourcemap-codec@1.5.5': {} + '@jridgewell/sourcemap-codec@1.6.0': {} + '@jridgewell/trace-mapping@0.3.31': dependencies: '@jridgewell/resolve-uri': 3.1.2 '@jridgewell/sourcemap-codec': 1.5.5 - '@rollup/rollup-android-arm-eabi@4.60.1': + '@oxc-project/runtime@0.153.0': {} + + '@oxc-project/types@0.153.0': {} + + '@oxfmt/binding-android-arm-eabi@0.72.0': + optional: true + + '@oxfmt/binding-android-arm64@0.72.0': + optional: true + + '@oxfmt/binding-darwin-arm64@0.72.0': + optional: true + + '@oxfmt/binding-darwin-x64@0.72.0': + optional: true + + '@oxfmt/binding-freebsd-x64@0.72.0': + optional: true + + '@oxfmt/binding-linux-arm-gnueabihf@0.72.0': + optional: true + + '@oxfmt/binding-linux-arm-musleabihf@0.72.0': + optional: true + + '@oxfmt/binding-linux-arm64-gnu@0.72.0': + optional: true + + '@oxfmt/binding-linux-arm64-musl@0.72.0': + optional: true + + '@oxfmt/binding-linux-ppc64-gnu@0.72.0': + optional: true + + '@oxfmt/binding-linux-riscv64-gnu@0.72.0': + optional: true + + '@oxfmt/binding-linux-riscv64-musl@0.72.0': + optional: true + + '@oxfmt/binding-linux-s390x-gnu@0.72.0': + optional: true + + '@oxfmt/binding-linux-x64-gnu@0.72.0': + optional: true + + '@oxfmt/binding-linux-x64-musl@0.72.0': + optional: true + + '@oxfmt/binding-openharmony-arm64@0.72.0': + optional: true + + '@oxfmt/binding-win32-arm64-msvc@0.72.0': + optional: true + + '@oxfmt/binding-win32-ia32-msvc@0.72.0': optional: true - '@rollup/rollup-android-arm64@4.60.1': + '@oxfmt/binding-win32-x64-msvc@0.72.0': optional: true - '@rollup/rollup-darwin-arm64@4.60.1': + '@oxlint-tsgolint/darwin-arm64@7.0.2003': optional: true - '@rollup/rollup-darwin-x64@4.60.1': + '@oxlint-tsgolint/darwin-x64@7.0.2003': optional: true - '@rollup/rollup-freebsd-arm64@4.60.1': + '@oxlint-tsgolint/linux-arm64@7.0.2003': optional: true - '@rollup/rollup-freebsd-x64@4.60.1': + '@oxlint-tsgolint/linux-x64@7.0.2003': optional: true - '@rollup/rollup-linux-arm-gnueabihf@4.60.1': + '@oxlint-tsgolint/win32-arm64@7.0.2003': optional: true - '@rollup/rollup-linux-arm-musleabihf@4.60.1': + '@oxlint-tsgolint/win32-x64@7.0.2003': optional: true - '@rollup/rollup-linux-arm64-gnu@4.60.1': + '@oxlint/binding-android-arm-eabi@1.87.0': optional: true - '@rollup/rollup-linux-arm64-musl@4.60.1': + '@oxlint/binding-android-arm64@1.87.0': optional: true - '@rollup/rollup-linux-loong64-gnu@4.60.1': + '@oxlint/binding-darwin-arm64@1.87.0': optional: true - '@rollup/rollup-linux-loong64-musl@4.60.1': + '@oxlint/binding-darwin-x64@1.87.0': optional: true - '@rollup/rollup-linux-ppc64-gnu@4.60.1': + '@oxlint/binding-freebsd-x64@1.87.0': optional: true - '@rollup/rollup-linux-ppc64-musl@4.60.1': + '@oxlint/binding-linux-arm-gnueabihf@1.87.0': optional: true - '@rollup/rollup-linux-riscv64-gnu@4.60.1': + '@oxlint/binding-linux-arm-musleabihf@1.87.0': optional: true - '@rollup/rollup-linux-riscv64-musl@4.60.1': + '@oxlint/binding-linux-arm64-gnu@1.87.0': optional: true - '@rollup/rollup-linux-s390x-gnu@4.60.1': + '@oxlint/binding-linux-arm64-musl@1.87.0': optional: true - '@rollup/rollup-linux-x64-gnu@4.60.1': + '@oxlint/binding-linux-ppc64-gnu@1.87.0': optional: true - '@rollup/rollup-linux-x64-musl@4.60.1': + '@oxlint/binding-linux-riscv64-gnu@1.87.0': optional: true - '@rollup/rollup-openbsd-x64@4.60.1': + '@oxlint/binding-linux-riscv64-musl@1.87.0': optional: true - '@rollup/rollup-openharmony-arm64@4.60.1': + '@oxlint/binding-linux-s390x-gnu@1.87.0': optional: true - '@rollup/rollup-win32-arm64-msvc@4.60.1': + '@oxlint/binding-linux-x64-gnu@1.87.0': optional: true - '@rollup/rollup-win32-ia32-msvc@4.60.1': + '@oxlint/binding-linux-x64-musl@1.87.0': optional: true - '@rollup/rollup-win32-x64-gnu@4.60.1': + '@oxlint/binding-openharmony-arm64@1.87.0': optional: true - '@rollup/rollup-win32-x64-msvc@4.60.1': + '@oxlint/binding-win32-arm64-msvc@1.87.0': optional: true + '@oxlint/binding-win32-ia32-msvc@1.87.0': + optional: true + + '@oxlint/binding-win32-x64-msvc@1.87.0': + optional: true + + '@oxlint/plugins@1.79.0': {} + + '@polka/url@1.0.0-next.29': {} + + '@testing-library/dom@10.4.2': + dependencies: + '@babel/code-frame': 7.29.7 + '@babel/runtime': 7.29.10 + '@types/aria-query': 5.0.4 + aria-query: 5.3.0 + dom-accessibility-api: 0.5.16 + lz-string: 1.5.0 + picocolors: 1.1.1 + pretty-format: 27.5.1 + + '@testing-library/user-event@14.6.8(@testing-library/dom@10.4.2)': + dependencies: + '@testing-library/dom': 10.4.2 + + '@types/aria-query@5.0.4': {} + + '@types/chai@5.2.3': + dependencies: + '@types/deep-eql': 4.0.2 + assertion-error: 2.0.1 + + '@types/deep-eql@4.0.2': {} + '@types/estree@1.0.8': {} '@types/node@20.19.37': dependencies: undici-types: 6.21.0 - acorn@8.16.0: {} + '@vitest/browser-preview@5.0.3(@voidzero-dev/vite-plus-core@1.1.0(@types/node@20.19.37)(esbuild@0.27.4)(typescript@5.9.3))(vitest@5.0.3)': + dependencies: + '@testing-library/dom': 10.4.2 + '@testing-library/user-event': 14.6.8(@testing-library/dom@10.4.2) + '@vitest/browser': 5.0.3(@voidzero-dev/vite-plus-core@1.1.0(@types/node@20.19.37)(esbuild@0.27.4)(typescript@5.9.3))(vitest@5.0.3) + vitest: 5.0.3(@types/node@20.19.37)(@vitest/browser-preview@5.0.3)(@vitest/ui@5.0.3(vitest@5.0.3))(@voidzero-dev/vite-plus-core@1.1.0(@types/node@20.19.37)(esbuild@0.27.4)(typescript@5.9.3)) + transitivePeerDependencies: + - bufferutil + - msw + - utf-8-validate + - vite - any-promise@1.3.0: {} + '@vitest/browser@5.0.3(@voidzero-dev/vite-plus-core@1.1.0(@types/node@20.19.37)(esbuild@0.27.4)(typescript@5.9.3))(vitest@5.0.3)': + dependencies: + '@blazediff/core': 1.10.0 + '@vitest/mocker': 5.0.3(@voidzero-dev/vite-plus-core@1.1.0(@types/node@20.19.37)(esbuild@0.27.4)(typescript@5.9.3)) + '@vitest/ui': 5.0.3(vitest@5.0.3) + '@vitest/utils': 5.0.3 + magic-string: 1.4.3 + pngjs: 7.0.0 + sirv: 3.0.2 + tinyrainbow: 3.2.0 + vitest: 5.0.3(@types/node@20.19.37)(@vitest/browser-preview@5.0.3)(@vitest/ui@5.0.3(vitest@5.0.3))(@voidzero-dev/vite-plus-core@1.1.0(@types/node@20.19.37)(esbuild@0.27.4)(typescript@5.9.3)) + ws: 8.22.0 + transitivePeerDependencies: + - bufferutil + - msw + - utf-8-validate + - vite - bundle-require@5.1.0(esbuild@0.27.4): + '@vitest/mocker@5.0.3(@voidzero-dev/vite-plus-core@1.1.0(@types/node@20.19.37)(esbuild@0.27.4)(typescript@5.9.3))': dependencies: - esbuild: 0.27.4 - load-tsconfig: 0.2.5 + '@jridgewell/trace-mapping': 0.3.31 + '@vitest/spy': 5.0.3 + estree-walker: 3.0.3 + magic-string: 1.4.3 + optionalDependencies: + vite: '@voidzero-dev/vite-plus-core@1.1.0(@types/node@20.19.37)(esbuild@0.27.4)(typescript@5.9.3)' - cac@6.7.14: {} + '@vitest/pretty-format@5.0.3': + dependencies: + tinyrainbow: 3.2.0 - chokidar@4.0.3: + '@vitest/snapshot@5.0.3': dependencies: - readdirp: 4.1.2 + '@vitest/pretty-format': 5.0.3 + '@vitest/utils': 5.0.3 + magic-string: 1.4.3 + pathe: 2.0.3 - commander@4.1.1: {} + '@vitest/spy@5.0.3': {} - confbox@0.1.8: {} + '@vitest/ui@5.0.3(vitest@5.0.3)': + dependencies: + '@vitest/utils': 5.0.3 + fflate: 0.8.3 + flatted: 3.4.4 + pathe: 2.0.3 + sirv: 3.0.2 + tinyrainbow: 3.2.0 + vitest: 5.0.3(@types/node@20.19.37)(@vitest/browser-preview@5.0.3)(@vitest/ui@5.0.3(vitest@5.0.3))(@voidzero-dev/vite-plus-core@1.1.0(@types/node@20.19.37)(esbuild@0.27.4)(typescript@5.9.3)) - consola@3.4.2: {} + '@vitest/utils@5.0.3': + dependencies: + '@vitest/pretty-format': 5.0.3 + convert-source-map: 2.0.0 + tinyrainbow: 3.2.0 - debug@4.4.3: + '@voidzero-dev/vite-plus-core@1.1.0(@types/node@20.19.37)(esbuild@0.27.4)(typescript@5.9.3)': dependencies: - ms: 2.1.3 + '@oxc-project/runtime': 0.153.0 + '@oxc-project/types': 0.153.0 + lightningcss: 1.33.0 + postcss: 8.5.30 + yuku-codegen: 0.9.5 + yuku-parser: 0.9.5 + optionalDependencies: + '@types/node': 20.19.37 + '@voidzero-dev/vite-plus-darwin-arm64': 1.1.0 + '@voidzero-dev/vite-plus-darwin-x64': 1.1.0 + '@voidzero-dev/vite-plus-linux-arm64-gnu': 1.1.0 + '@voidzero-dev/vite-plus-linux-arm64-musl': 1.1.0 + '@voidzero-dev/vite-plus-linux-x64-gnu': 1.1.0 + '@voidzero-dev/vite-plus-linux-x64-musl': 1.1.0 + '@voidzero-dev/vite-plus-win32-arm64-msvc': 1.1.0 + '@voidzero-dev/vite-plus-win32-x64-msvc': 1.1.0 + esbuild: 0.27.4 + fsevents: 2.3.3 + typescript: 5.9.3 + + '@voidzero-dev/vite-plus-darwin-arm64@1.1.0': + optional: true + + '@voidzero-dev/vite-plus-darwin-x64@1.1.0': + optional: true + + '@voidzero-dev/vite-plus-linux-arm64-gnu@1.1.0': + optional: true + + '@voidzero-dev/vite-plus-linux-arm64-musl@1.1.0': + optional: true + + '@voidzero-dev/vite-plus-linux-x64-gnu@1.1.0': + optional: true + + '@voidzero-dev/vite-plus-linux-x64-musl@1.1.0': + optional: true + + '@voidzero-dev/vite-plus-win32-arm64-msvc@1.1.0': + optional: true + + '@voidzero-dev/vite-plus-win32-x64-msvc@1.1.0': + optional: true + + '@yuku-codegen/binding-android-arm64@0.9.5': + optional: true + + '@yuku-codegen/binding-darwin-arm64@0.9.5': + optional: true + + '@yuku-codegen/binding-darwin-x64@0.9.5': + optional: true + + '@yuku-codegen/binding-freebsd-x64@0.9.5': + optional: true + + '@yuku-codegen/binding-linux-arm-gnu@0.9.5': + optional: true + + '@yuku-codegen/binding-linux-arm-musl@0.9.5': + optional: true + + '@yuku-codegen/binding-linux-arm64-gnu@0.9.5': + optional: true + + '@yuku-codegen/binding-linux-arm64-musl@0.9.5': + optional: true + + '@yuku-codegen/binding-linux-x64-gnu@0.9.5': + optional: true + + '@yuku-codegen/binding-linux-x64-musl@0.9.5': + optional: true + + '@yuku-codegen/binding-win32-arm64@0.9.5': + optional: true + + '@yuku-codegen/binding-win32-x64@0.9.5': + optional: true + + '@yuku-parser/binding-android-arm64@0.9.5': + optional: true + + '@yuku-parser/binding-darwin-arm64@0.9.5': + optional: true + + '@yuku-parser/binding-darwin-x64@0.9.5': + optional: true + + '@yuku-parser/binding-freebsd-x64@0.9.5': + optional: true + + '@yuku-parser/binding-linux-arm-gnu@0.9.5': + optional: true + + '@yuku-parser/binding-linux-arm-musl@0.9.5': + optional: true + + '@yuku-parser/binding-linux-arm64-gnu@0.9.5': + optional: true + + '@yuku-parser/binding-linux-arm64-musl@0.9.5': + optional: true + + '@yuku-parser/binding-linux-x64-gnu@0.9.5': + optional: true + + '@yuku-parser/binding-linux-x64-musl@0.9.5': + optional: true + + '@yuku-parser/binding-win32-arm64@0.9.5': + optional: true + + '@yuku-parser/binding-win32-x64@0.9.5': + optional: true + + '@yuku-toolchain/types@0.9.5': {} + + ansi-regex@5.0.1: {} + + ansi-styles@5.2.0: {} + + aria-query@5.3.0: + dependencies: + dequal: 2.0.3 + + assertion-error@2.0.1: {} + + chai@6.3.0: {} + + convert-source-map@2.0.0: {} + + dequal@2.0.3: {} + + detect-libc@2.1.2: {} + + dom-accessibility-api@0.5.16: {} + + es-module-lexer@2.3.2: {} esbuild@0.27.4: optionalDependencies: @@ -764,162 +1695,318 @@ snapshots: '@esbuild/win32-arm64': 0.27.4 '@esbuild/win32-ia32': 0.27.4 '@esbuild/win32-x64': 0.27.4 + optional: true + + estree-walker@3.0.3: + dependencies: + '@types/estree': 1.0.8 - fdir@6.5.0(picomatch@4.0.4): + expect-type@1.4.0: {} + + fdir@6.5.0(picomatch@4.0.7): optionalDependencies: - picomatch: 4.0.4 + picomatch: 4.0.7 - fix-dts-default-cjs-exports@1.0.1: - dependencies: - magic-string: 0.30.21 - mlly: 1.8.2 - rollup: 4.60.1 + fflate@0.8.3: {} + + flatted@3.4.4: {} fsevents@2.3.3: optional: true - joycon@3.1.1: {} + js-tokens@4.0.0: {} - lilconfig@3.1.3: {} - - lines-and-columns@1.2.4: {} + lightningcss-android-arm64@1.33.0: + optional: true - load-tsconfig@0.2.5: {} + lightningcss-darwin-arm64@1.33.0: + optional: true - magic-string@0.30.21: - dependencies: - '@jridgewell/sourcemap-codec': 1.5.5 + lightningcss-darwin-x64@1.33.0: + optional: true - mlly@1.8.2: - dependencies: - acorn: 8.16.0 - pathe: 2.0.3 - pkg-types: 1.3.1 - ufo: 1.6.3 + lightningcss-freebsd-x64@1.33.0: + optional: true - ms@2.1.3: {} + lightningcss-linux-arm-gnueabihf@1.33.0: + optional: true - mz@2.7.0: - dependencies: - any-promise: 1.3.0 - object-assign: 4.1.1 - thenify-all: 1.6.0 + lightningcss-linux-arm64-gnu@1.33.0: + optional: true - object-assign@4.1.1: {} + lightningcss-linux-arm64-musl@1.33.0: + optional: true - pathe@2.0.3: {} + lightningcss-linux-x64-gnu@1.33.0: + optional: true - picocolors@1.1.1: {} + lightningcss-linux-x64-musl@1.33.0: + optional: true - picomatch@4.0.4: {} + lightningcss-win32-arm64-msvc@1.33.0: + optional: true - pirates@4.0.7: {} + lightningcss-win32-x64-msvc@1.33.0: + optional: true - pkg-types@1.3.1: + lightningcss@1.33.0: dependencies: - confbox: 0.1.8 - mlly: 1.8.2 - pathe: 2.0.3 - - postcss-load-config@6.0.1: + detect-libc: 2.1.2 + optionalDependencies: + lightningcss-android-arm64: 1.33.0 + lightningcss-darwin-arm64: 1.33.0 + lightningcss-darwin-x64: 1.33.0 + lightningcss-freebsd-x64: 1.33.0 + lightningcss-linux-arm-gnueabihf: 1.33.0 + lightningcss-linux-arm64-gnu: 1.33.0 + lightningcss-linux-arm64-musl: 1.33.0 + lightningcss-linux-x64-gnu: 1.33.0 + lightningcss-linux-x64-musl: 1.33.0 + lightningcss-win32-arm64-msvc: 1.33.0 + lightningcss-win32-x64-msvc: 1.33.0 + + lz-string@1.5.0: {} + + magic-string@1.4.3: dependencies: - lilconfig: 3.1.3 + '@jridgewell/sourcemap-codec': 1.6.0 + + mrmime@2.0.1: {} - readdirp@4.1.2: {} + nanoid@3.3.20: {} - resolve-from@5.0.0: {} + obug@2.2.1: {} - rollup@4.60.1: + oxfmt@0.72.0(vite-plus@1.1.0(@types/node@20.19.37)(@vitest/ui@5.0.3(vitest@5.0.3))(esbuild@0.27.4)(typescript@5.9.3)): dependencies: - '@types/estree': 1.0.8 + tinypool: 2.2.0 optionalDependencies: - '@rollup/rollup-android-arm-eabi': 4.60.1 - '@rollup/rollup-android-arm64': 4.60.1 - '@rollup/rollup-darwin-arm64': 4.60.1 - '@rollup/rollup-darwin-x64': 4.60.1 - '@rollup/rollup-freebsd-arm64': 4.60.1 - '@rollup/rollup-freebsd-x64': 4.60.1 - '@rollup/rollup-linux-arm-gnueabihf': 4.60.1 - '@rollup/rollup-linux-arm-musleabihf': 4.60.1 - '@rollup/rollup-linux-arm64-gnu': 4.60.1 - '@rollup/rollup-linux-arm64-musl': 4.60.1 - '@rollup/rollup-linux-loong64-gnu': 4.60.1 - '@rollup/rollup-linux-loong64-musl': 4.60.1 - '@rollup/rollup-linux-ppc64-gnu': 4.60.1 - '@rollup/rollup-linux-ppc64-musl': 4.60.1 - '@rollup/rollup-linux-riscv64-gnu': 4.60.1 - '@rollup/rollup-linux-riscv64-musl': 4.60.1 - '@rollup/rollup-linux-s390x-gnu': 4.60.1 - '@rollup/rollup-linux-x64-gnu': 4.60.1 - '@rollup/rollup-linux-x64-musl': 4.60.1 - '@rollup/rollup-openbsd-x64': 4.60.1 - '@rollup/rollup-openharmony-arm64': 4.60.1 - '@rollup/rollup-win32-arm64-msvc': 4.60.1 - '@rollup/rollup-win32-ia32-msvc': 4.60.1 - '@rollup/rollup-win32-x64-gnu': 4.60.1 - '@rollup/rollup-win32-x64-msvc': 4.60.1 - fsevents: 2.3.3 + '@oxfmt/binding-android-arm-eabi': 0.72.0 + '@oxfmt/binding-android-arm64': 0.72.0 + '@oxfmt/binding-darwin-arm64': 0.72.0 + '@oxfmt/binding-darwin-x64': 0.72.0 + '@oxfmt/binding-freebsd-x64': 0.72.0 + '@oxfmt/binding-linux-arm-gnueabihf': 0.72.0 + '@oxfmt/binding-linux-arm-musleabihf': 0.72.0 + '@oxfmt/binding-linux-arm64-gnu': 0.72.0 + '@oxfmt/binding-linux-arm64-musl': 0.72.0 + '@oxfmt/binding-linux-ppc64-gnu': 0.72.0 + '@oxfmt/binding-linux-riscv64-gnu': 0.72.0 + '@oxfmt/binding-linux-riscv64-musl': 0.72.0 + '@oxfmt/binding-linux-s390x-gnu': 0.72.0 + '@oxfmt/binding-linux-x64-gnu': 0.72.0 + '@oxfmt/binding-linux-x64-musl': 0.72.0 + '@oxfmt/binding-openharmony-arm64': 0.72.0 + '@oxfmt/binding-win32-arm64-msvc': 0.72.0 + '@oxfmt/binding-win32-ia32-msvc': 0.72.0 + '@oxfmt/binding-win32-x64-msvc': 0.72.0 + vite-plus: 1.1.0(@types/node@20.19.37)(@vitest/ui@5.0.3(vitest@5.0.3))(esbuild@0.27.4)(typescript@5.9.3) + + oxlint-tsgolint@7.0.2003: + optionalDependencies: + '@oxlint-tsgolint/darwin-arm64': 7.0.2003 + '@oxlint-tsgolint/darwin-x64': 7.0.2003 + '@oxlint-tsgolint/linux-arm64': 7.0.2003 + '@oxlint-tsgolint/linux-x64': 7.0.2003 + '@oxlint-tsgolint/win32-arm64': 7.0.2003 + '@oxlint-tsgolint/win32-x64': 7.0.2003 + + oxlint@1.87.0(oxlint-tsgolint@7.0.2003)(vite-plus@1.1.0(@types/node@20.19.37)(@vitest/ui@5.0.3(vitest@5.0.3))(esbuild@0.27.4)(typescript@5.9.3)): + optionalDependencies: + '@oxlint/binding-android-arm-eabi': 1.87.0 + '@oxlint/binding-android-arm64': 1.87.0 + '@oxlint/binding-darwin-arm64': 1.87.0 + '@oxlint/binding-darwin-x64': 1.87.0 + '@oxlint/binding-freebsd-x64': 1.87.0 + '@oxlint/binding-linux-arm-gnueabihf': 1.87.0 + '@oxlint/binding-linux-arm-musleabihf': 1.87.0 + '@oxlint/binding-linux-arm64-gnu': 1.87.0 + '@oxlint/binding-linux-arm64-musl': 1.87.0 + '@oxlint/binding-linux-ppc64-gnu': 1.87.0 + '@oxlint/binding-linux-riscv64-gnu': 1.87.0 + '@oxlint/binding-linux-riscv64-musl': 1.87.0 + '@oxlint/binding-linux-s390x-gnu': 1.87.0 + '@oxlint/binding-linux-x64-gnu': 1.87.0 + '@oxlint/binding-linux-x64-musl': 1.87.0 + '@oxlint/binding-openharmony-arm64': 1.87.0 + '@oxlint/binding-win32-arm64-msvc': 1.87.0 + '@oxlint/binding-win32-ia32-msvc': 1.87.0 + '@oxlint/binding-win32-x64-msvc': 1.87.0 + oxlint-tsgolint: 7.0.2003 + vite-plus: 1.1.0(@types/node@20.19.37)(@vitest/ui@5.0.3(vitest@5.0.3))(esbuild@0.27.4)(typescript@5.9.3) + + pathe@2.0.3: {} + + picocolors@1.1.1: {} - source-map@0.7.6: {} + picomatch@4.0.7: {} - sucrase@3.35.1: + pngjs@7.0.0: {} + + postcss@8.5.30: dependencies: - '@jridgewell/gen-mapping': 0.3.13 - commander: 4.1.1 - lines-and-columns: 1.2.4 - mz: 2.7.0 - pirates: 4.0.7 - tinyglobby: 0.2.15 - ts-interface-checker: 0.1.13 - - thenify-all@1.6.0: + nanoid: 3.3.20 + picocolors: 1.1.1 + source-map-js: 1.2.2 + + pretty-format@27.5.1: dependencies: - thenify: 3.3.1 + ansi-regex: 5.0.1 + ansi-styles: 5.2.0 + react-is: 17.0.2 + + react-is@17.0.2: {} - thenify@3.3.1: + sirv@3.0.2: dependencies: - any-promise: 1.3.0 + '@polka/url': 1.0.0-next.29 + mrmime: 2.0.1 + totalist: 3.0.1 + + source-map-js@1.2.2: {} + + std-env@4.3.0: {} + + tinybench@6.2.2: {} - tinyexec@0.3.2: {} + tinyexec@1.3.1: {} - tinyglobby@0.2.15: + tinyglobby@0.2.17: dependencies: - fdir: 6.5.0(picomatch@4.0.4) - picomatch: 4.0.4 + fdir: 6.5.0(picomatch@4.0.7) + picomatch: 4.0.7 - tree-kill@1.2.2: {} + tinypool@2.2.0: {} - ts-interface-checker@0.1.13: {} + tinyrainbow@3.2.0: {} - tsup@8.5.1(typescript@5.9.3): + totalist@3.0.1: {} + + typescript@5.9.3: {} + + undici-types@6.21.0: {} + + vite-plus@1.1.0(@types/node@20.19.37)(@vitest/ui@5.0.3(vitest@5.0.3))(esbuild@0.27.4)(typescript@5.9.3): dependencies: - bundle-require: 5.1.0(esbuild@0.27.4) - cac: 6.7.14 - chokidar: 4.0.3 - consola: 3.4.2 - debug: 4.4.3 - esbuild: 0.27.4 - fix-dts-default-cjs-exports: 1.0.1 - joycon: 3.1.1 - picocolors: 1.1.1 - postcss-load-config: 6.0.1 - resolve-from: 5.0.0 - rollup: 4.60.1 - source-map: 0.7.6 - sucrase: 3.35.1 - tinyexec: 0.3.2 - tinyglobby: 0.2.15 - tree-kill: 1.2.2 + '@oxc-project/types': 0.153.0 + '@oxlint/plugins': 1.79.0 + '@vitest/browser': 5.0.3(@voidzero-dev/vite-plus-core@1.1.0(@types/node@20.19.37)(esbuild@0.27.4)(typescript@5.9.3))(vitest@5.0.3) + '@vitest/browser-preview': 5.0.3(@voidzero-dev/vite-plus-core@1.1.0(@types/node@20.19.37)(esbuild@0.27.4)(typescript@5.9.3))(vitest@5.0.3) + '@vitest/mocker': 5.0.3(@voidzero-dev/vite-plus-core@1.1.0(@types/node@20.19.37)(esbuild@0.27.4)(typescript@5.9.3)) + '@vitest/pretty-format': 5.0.3 + '@vitest/snapshot': 5.0.3 + '@vitest/spy': 5.0.3 + '@vitest/utils': 5.0.3 + oxfmt: 0.72.0(vite-plus@1.1.0(@types/node@20.19.37)(@vitest/ui@5.0.3(vitest@5.0.3))(esbuild@0.27.4)(typescript@5.9.3)) + oxlint: 1.87.0(oxlint-tsgolint@7.0.2003)(vite-plus@1.1.0(@types/node@20.19.37)(@vitest/ui@5.0.3(vitest@5.0.3))(esbuild@0.27.4)(typescript@5.9.3)) + oxlint-tsgolint: 7.0.2003 + vite: '@voidzero-dev/vite-plus-core@1.1.0(@types/node@20.19.37)(esbuild@0.27.4)(typescript@5.9.3)' + vitest: 5.0.3(@types/node@20.19.37)(@vitest/browser-preview@5.0.3)(@vitest/ui@5.0.3(vitest@5.0.3))(@voidzero-dev/vite-plus-core@1.1.0(@types/node@20.19.37)(esbuild@0.27.4)(typescript@5.9.3)) optionalDependencies: - typescript: 5.9.3 + '@voidzero-dev/vite-plus-darwin-arm64': 1.1.0 + '@voidzero-dev/vite-plus-darwin-x64': 1.1.0 + '@voidzero-dev/vite-plus-linux-arm64-gnu': 1.1.0 + '@voidzero-dev/vite-plus-linux-arm64-musl': 1.1.0 + '@voidzero-dev/vite-plus-linux-x64-gnu': 1.1.0 + '@voidzero-dev/vite-plus-linux-x64-musl': 1.1.0 + '@voidzero-dev/vite-plus-win32-arm64-msvc': 1.1.0 + '@voidzero-dev/vite-plus-win32-x64-msvc': 1.1.0 transitivePeerDependencies: + - '@arethetypeswrong/core' + - '@edge-runtime/vm' + - '@opentelemetry/api' + - '@types/node' + - '@typescript/native-preview' + - '@vitejs/devtools' + - '@vitest/coverage-istanbul' + - '@vitest/coverage-v8' + - '@vitest/ui' + - '@volar/typescript' + - '@vue/language-core' + - bufferutil + - esbuild + - happy-dom - jiti - - supports-color + - jsdom + - less + - msw + - publint + - sass + - sass-embedded + - stylus + - sugarss + - svelte + - terser - tsx + - typescript + - unplugin-unused + - unrun + - utf-8-validate + - vue-tsc - yaml - typescript@5.9.3: {} + vitest@5.0.3(@types/node@20.19.37)(@vitest/browser-preview@5.0.3)(@vitest/ui@5.0.3(vitest@5.0.3))(@voidzero-dev/vite-plus-core@1.1.0(@types/node@20.19.37)(esbuild@0.27.4)(typescript@5.9.3)): + dependencies: + '@types/chai': 5.2.3 + '@vitest/mocker': 5.0.3(@voidzero-dev/vite-plus-core@1.1.0(@types/node@20.19.37)(esbuild@0.27.4)(typescript@5.9.3)) + chai: 6.3.0 + es-module-lexer: 2.3.2 + expect-type: 1.4.0 + magic-string: 1.4.3 + obug: 2.2.1 + picomatch: 4.0.7 + std-env: 4.3.0 + tinybench: 6.2.2 + tinyexec: 1.3.1 + tinyglobby: 0.2.17 + vite: '@voidzero-dev/vite-plus-core@1.1.0(@types/node@20.19.37)(esbuild@0.27.4)(typescript@5.9.3)' + why-is-node-running: 3.2.1 + optionalDependencies: + '@types/node': 20.19.37 + '@vitest/browser-preview': 5.0.3(@voidzero-dev/vite-plus-core@1.1.0(@types/node@20.19.37)(esbuild@0.27.4)(typescript@5.9.3))(vitest@5.0.3) + '@vitest/ui': 5.0.3(vitest@5.0.3) + transitivePeerDependencies: + - msw - ufo@1.6.3: {} + why-is-node-running@3.2.1: {} - undici-types@6.21.0: {} + ws@8.22.0: {} + + yuku-ast@0.9.5: + dependencies: + '@yuku-toolchain/types': 0.9.5 + + yuku-codegen@0.9.5: + dependencies: + '@yuku-toolchain/types': 0.9.5 + optionalDependencies: + '@yuku-codegen/binding-android-arm64': 0.9.5 + '@yuku-codegen/binding-darwin-arm64': 0.9.5 + '@yuku-codegen/binding-darwin-x64': 0.9.5 + '@yuku-codegen/binding-freebsd-x64': 0.9.5 + '@yuku-codegen/binding-linux-arm-gnu': 0.9.5 + '@yuku-codegen/binding-linux-arm-musl': 0.9.5 + '@yuku-codegen/binding-linux-arm64-gnu': 0.9.5 + '@yuku-codegen/binding-linux-arm64-musl': 0.9.5 + '@yuku-codegen/binding-linux-x64-gnu': 0.9.5 + '@yuku-codegen/binding-linux-x64-musl': 0.9.5 + '@yuku-codegen/binding-win32-arm64': 0.9.5 + '@yuku-codegen/binding-win32-x64': 0.9.5 + + yuku-parser@0.9.5: + dependencies: + '@yuku-toolchain/types': 0.9.5 + yuku-ast: 0.9.5 + optionalDependencies: + '@yuku-parser/binding-android-arm64': 0.9.5 + '@yuku-parser/binding-darwin-arm64': 0.9.5 + '@yuku-parser/binding-darwin-x64': 0.9.5 + '@yuku-parser/binding-freebsd-x64': 0.9.5 + '@yuku-parser/binding-linux-arm-gnu': 0.9.5 + '@yuku-parser/binding-linux-arm-musl': 0.9.5 + '@yuku-parser/binding-linux-arm64-gnu': 0.9.5 + '@yuku-parser/binding-linux-arm64-musl': 0.9.5 + '@yuku-parser/binding-linux-x64-gnu': 0.9.5 + '@yuku-parser/binding-linux-x64-musl': 0.9.5 + '@yuku-parser/binding-win32-arm64': 0.9.5 + '@yuku-parser/binding-win32-x64': 0.9.5 diff --git a/js/smoke/shared/src/helpers/register.ts b/js/smoke/shared/src/helpers/register.ts index 9b45d0126..9c0cce707 100644 --- a/js/smoke/shared/src/helpers/register.ts +++ b/js/smoke/shared/src/helpers/register.ts @@ -18,7 +18,10 @@ export interface TestContext { backgroundLogger: BackgroundLogger; } -export type TestFn = (braintrust: BraintrustModule) => Promise; +export type TestFn = ((braintrust: BraintrustModule) => Promise) & { + testName: string; + requiresTestHarness?: boolean; +}; type RegisteredTestFn = ( braintrust: BraintrustModule, @@ -35,24 +38,32 @@ type RegisteredTestFn = ( * - Return a partial TestResult -> merged with pass status * - Throw an error -> fail with error details */ -export function register(name: string, fn: RegisteredTestFn): TestFn { +export function register( + name: string, + fn: RegisteredTestFn, + { requiresTestHarness = false }: { requiresTestHarness?: boolean } = {}, +): TestFn { registeredTests.add(name); - return async (braintrust: BraintrustModule): Promise => { - const testing = braintrust._exportsForTestingOnly as { - setInitialTestState: () => void; - simulateLoginForTests: () => Promise | unknown; - simulateLogoutForTests?: () => Promise | unknown; - useTestBackgroundLogger: () => BackgroundLogger; - clearTestBackgroundLogger: () => void; - }; + const test = async (braintrust: BraintrustModule): Promise => { + const testing = braintrust._exportsForTestingOnly as + | { + setInitialTestState: () => void; + simulateLoginForTests: () => Promise | unknown; + simulateLogoutForTests?: () => Promise | unknown; + useTestBackgroundLogger: () => BackgroundLogger; + clearTestBackgroundLogger: () => void; + } + | undefined; - testing.setInitialTestState(); - await testing.simulateLoginForTests(); - const backgroundLogger = testing.useTestBackgroundLogger(); + testing?.setInitialTestState(); + await testing?.simulateLoginForTests(); + const backgroundLogger = testing?.useTestBackgroundLogger(); try { - const result = await fn(braintrust, { backgroundLogger }); + const result = await fn(braintrust, { + backgroundLogger: backgroundLogger!, + }); if (typeof result === "string") { return { status: "pass", name, message: result }; @@ -71,12 +82,15 @@ export function register(name: string, fn: RegisteredTestFn): TestFn { }, }; } finally { - testing.clearTestBackgroundLogger(); - if (typeof testing.simulateLogoutForTests === "function") { + testing?.clearTestBackgroundLogger(); + if (typeof testing?.simulateLogoutForTests === "function") { await testing.simulateLogoutForTests(); } } }; + test.testName = name; + test.requiresTestHarness = requiresTestHarness; + return test; } /** @@ -146,9 +160,21 @@ export async function runTests({ skipCoverage = false, }: RunTestsOptions): Promise { const results: TestResult[] = []; + const testModule: BraintrustModule = { + ...braintrust, + __publicModule: braintrust, + }; for (const test of tests) { - results.push(await test(braintrust)); + if (!testModule._exportsForTestingOnly && test.requiresTestHarness) { + results.push({ + status: "xfail", + name: test.testName, + message: "Requires the private SDK test harness", + }); + } else { + results.push(await test(testModule)); + } } if (!skipCoverage) { @@ -186,7 +212,7 @@ export function expectFailure( predicate: ErrorPredicate, reason: string, ): TestFn { - return async (braintrust: BraintrustModule): Promise => { + const wrapped = async (braintrust: BraintrustModule): Promise => { const result = await test(braintrust); if (result.status === "fail" && result.error) { @@ -200,4 +226,7 @@ export function expectFailure( } return result; }; + wrapped.testName = test.testName; + wrapped.requiresTestHarness = test.requiresTestHarness; + return wrapped; } diff --git a/js/smoke/shared/src/suites/basic-logging.ts b/js/smoke/shared/src/suites/basic-logging.ts index 9bd174022..0103bb74d 100644 --- a/js/smoke/shared/src/suites/basic-logging.ts +++ b/js/smoke/shared/src/suites/basic-logging.ts @@ -5,7 +5,7 @@ import type { LoggerInstance } from "../helpers/types"; import { assert, assertEqual, assertNotEmpty } from "../helpers/assertions"; -import { register } from "../helpers/register"; +import { register, type TestFn } from "../helpers/register"; const PROJECT_ID = "test-project-id"; @@ -14,7 +14,7 @@ type InitLoggerFn = (options: { projectId?: string; }) => LoggerInstance; -export const testBasicSpanLogging = register( +export const testBasicSpanLogging: TestFn = register( "testBasicSpanLogging", async (braintrust, { backgroundLogger }) => { const initLogger = braintrust.initLogger as InitLoggerFn; @@ -47,9 +47,10 @@ export const testBasicSpanLogging = register( return "Basic span logging test passed"; }, + { requiresTestHarness: true }, ); -export const testMultipleSpans = register( +export const testMultipleSpans: TestFn = register( "testMultipleSpans", async (braintrust, { backgroundLogger }) => { const initLogger = braintrust.initLogger as InitLoggerFn; @@ -78,9 +79,10 @@ export const testMultipleSpans = register( return `Multiple spans test passed (${events.length} events captured)`; }, + { requiresTestHarness: true }, ); -export const testDirectLogging = register( +export const testDirectLogging: TestFn = register( "testDirectLogging", async (braintrust, { backgroundLogger }) => { const initLogger = braintrust.initLogger as InitLoggerFn; @@ -102,9 +104,10 @@ export const testDirectLogging = register( return "Direct logging test passed"; }, + { requiresTestHarness: true }, ); -export const testJSONAttachment = register( +export const testJSONAttachment: TestFn = register( "testJSONAttachment", async (braintrust, { backgroundLogger }) => { const initLogger = braintrust.initLogger as InitLoggerFn; @@ -159,9 +162,10 @@ export const testJSONAttachment = register( return "JSONAttachment test passed"; }, + { requiresTestHarness: true }, ); -export const testAsyncLocalStorageTraced = register( +export const testAsyncLocalStorageTraced: TestFn = register( "testAsyncLocalStorageTraced", async (braintrust, { backgroundLogger }) => { const initLogger = braintrust.initLogger as InitLoggerFn; @@ -221,9 +225,10 @@ export const testAsyncLocalStorageTraced = register( ); } }, + { requiresTestHarness: true }, ); -export const testNestedTraced = register( +export const testNestedTraced: TestFn = register( "testNestedTraced", async (braintrust, { backgroundLogger }) => { const initLogger = braintrust.initLogger as InitLoggerFn; @@ -301,9 +306,10 @@ export const testNestedTraced = register( return "ALS not available in this environment, test skipped"; } }, + { requiresTestHarness: true }, ); -export const testCurrentSpan = register( +export const testCurrentSpan: TestFn = register( "testCurrentSpan", async (braintrust, { backgroundLogger }) => { const initLogger = braintrust.initLogger as InitLoggerFn; @@ -355,4 +361,5 @@ export const testCurrentSpan = register( throw new Error("currentSpan() returned a span ID that was not logged"); } }, + { requiresTestHarness: true }, ); diff --git a/js/smoke/shared/src/suites/eval-smoke.ts b/js/smoke/shared/src/suites/eval-smoke.ts index ae9e89dcd..9cd559830 100644 --- a/js/smoke/shared/src/suites/eval-smoke.ts +++ b/js/smoke/shared/src/suites/eval-smoke.ts @@ -9,7 +9,7 @@ import { assertHasProperty, assertType, } from "../helpers/assertions"; -import { register } from "../helpers/register"; +import { register, type TestFn } from "../helpers/register"; function exactMatchScore({ output, @@ -21,77 +21,80 @@ function exactMatchScore({ return { name: "exact_match", score: output === expected ? 1 : 0 }; } -export const testEvalSmoke = register("testEvalSmoke", async (braintrust) => { - // eslint-disable-next-line @typescript-eslint/no-explicit-any - const Eval = braintrust.Eval as any; +export const testEvalSmoke: TestFn = register( + "testEvalSmoke", + async (braintrust) => { + // eslint-disable-next-line @typescript-eslint/no-explicit-any + const Eval = braintrust.Eval as any; - assertDefined(Eval, "Eval must exist"); - assertType(Eval, "function", "Eval must be a function"); + assertDefined(Eval, "Eval must exist"); + assertType(Eval, "function", "Eval must be a function"); - const evalData = [ - { input: "Alice", expected: "Hi Alice" }, - { input: "Bob", expected: "Hi Bob" }, - { input: "Charlie", expected: "Hi Charlie" }, - ]; + const evalData = [ + { input: "Alice", expected: "Hi Alice" }, + { input: "Bob", expected: "Hi Bob" }, + { input: "Charlie", expected: "Hi Charlie" }, + ]; - const result = await Eval( - "smoke-eval-test", - { - data: evalData, - task: async (input: string) => `Hi ${input}`, - scores: [exactMatchScore], - }, - { - noSendLogs: true, - returnResults: true, - }, - ); - - assertDefined(result, "Eval should return a result"); - const r = result as Record; + const result = await Eval( + "smoke-eval-test", + { + data: evalData, + task: async (input: string) => `Hi ${input}`, + scores: [exactMatchScore], + }, + { + noSendLogs: true, + returnResults: true, + }, + ); - assertHasProperty(r, "summary", "Eval result missing summary"); - assertHasProperty(r, "results", "Eval result missing results"); + assertDefined(result, "Eval should return a result"); + const r = result as Record; - const summary = r.summary as Record; - const results = r.results as Array>; + assertHasProperty(r, "summary", "Eval result missing summary"); + assertHasProperty(r, "results", "Eval result missing results"); - assertEqual( - results.length, - evalData.length, - `Expected ${evalData.length} eval results`, - ); + const summary = r.summary as Record; + const results = r.results as Array>; - for (const row of results) { - assertDefined(row.input, "Eval result missing input"); - assertDefined(row.expected, "Eval result missing expected"); - assertDefined(row.output, "Eval result missing output"); assertEqual( - row.output as string, - row.expected as string, - "Output should equal expected", + results.length, + evalData.length, + `Expected ${evalData.length} eval results`, ); - assertHasProperty(row, "scores", "Eval result missing scores"); - const scores = row.scores as Record; - assertDefined(scores.exact_match, "Missing exact_match score"); - assertEqual(scores.exact_match as number, 1, "exact_match should be 1"); - } + for (const row of results) { + assertDefined(row.input, "Eval result missing input"); + assertDefined(row.expected, "Eval result missing expected"); + assertDefined(row.output, "Eval result missing output"); + assertEqual( + row.output as string, + row.expected as string, + "Output should equal expected", + ); + + assertHasProperty(row, "scores", "Eval result missing scores"); + const scores = row.scores as Record; + assertDefined(scores.exact_match, "Missing exact_match score"); + assertEqual(scores.exact_match as number, 1, "exact_match should be 1"); + } - assertHasProperty(summary, "scores", "Summary missing scores"); - const summaryScores = summary.scores as Record; - assertHasProperty( - summaryScores, - "exact_match", - "Summary missing exact_match scorer", - ); - const exact = summaryScores.exact_match as Record; - assertDefined(exact.score, "Summary exact_match missing score"); - assertEqual( - exact.score as number, - 1, - "Summary exact_match.score should be 1", - ); + assertHasProperty(summary, "scores", "Summary missing scores"); + const summaryScores = summary.scores as Record; + assertHasProperty( + summaryScores, + "exact_match", + "Summary missing exact_match scorer", + ); + const exact = summaryScores.exact_match as Record; + assertDefined(exact.score, "Summary exact_match missing score"); + assertEqual( + exact.score as number, + 1, + "Summary exact_match.score should be 1", + ); - return `Eval smoke test passed (${evalData.length} cases)`; -}); + return `Eval smoke test passed (${evalData.length} cases)`; + }, +); diff --git a/js/smoke/shared/src/suites/import-verification.ts b/js/smoke/shared/src/suites/import-verification.ts index 492ba2f0e..2fb8a54ec 100644 --- a/js/smoke/shared/src/suites/import-verification.ts +++ b/js/smoke/shared/src/suites/import-verification.ts @@ -24,6 +24,7 @@ import { register, type TestFn } from "../helpers/register"; * Interface for the Braintrust module based on exports.ts */ export interface BraintrustModule { + __publicModule?: BraintrustModule; // Core logging (REQUIRED) initLogger?: unknown; Logger?: unknown; @@ -54,9 +55,6 @@ export interface BraintrustModule { Eval?: unknown; EvalResultWithSummary?: unknown; Reporter?: unknown; - runEvaluator?: unknown; - buildLocalSummary?: unknown; - reportFailures?: unknown; defaultErrorScoreHandler?: unknown; // Tracing (REQUIRED) @@ -90,10 +88,8 @@ export interface BraintrustModule { projects?: unknown; PromptBuilder?: unknown; - // ID Generation (REQUIRED) + // Legacy private smoke-test harness fields IDGenerator?: unknown; - - // Testing (REQUIRED) _exportsForTestingOnly?: unknown; // State management (REQUIRED) @@ -103,7 +99,7 @@ export interface BraintrustModule { [key: string]: unknown; } -export const testCoreLoggingExports = register( +export const testCoreLoggingExports: TestFn = register( "testCoreLoggingExports", async (module) => { assertDefined(module.initLogger, "initLogger must exist"); @@ -129,17 +125,34 @@ export const testCoreLoggingExports = register( assertDefined(module.startSpan, "startSpan must exist"); assertType(module.startSpan, "function", "startSpan must be a function"); - assertDefined(module.log, "log must exist"); - assertType(module.log, "function", "log must be a function"); - assertDefined(module.flush, "flush must exist"); assertType(module.flush, "function", "flush must be a function"); - return "Core logging exports verified (7 exports)"; + const publicModule = module.__publicModule ?? module; + for (const removed of [ + "default", + "log", + "summarize", + "initExperiment", + "traceable", + "EvalResultWithSummary", + "SpanImpl", + "_exportsForTestingOnly", + "AttachmentReference", + "promptDefinitionSchema", + "braintrustEveInstrumentation", + "braintrustFlueInstrumentation", + ]) { + if (removed in publicModule) { + throw new Error(`${removed} must not be publicly exported`); + } + } + + return "Core logging exports and removals verified"; }, ); -export const testDatasetExports = register( +export const testDatasetExports: TestFn = register( "testDatasetExports", async (module) => { assertDefined(module.initDataset, "initDataset must exist"); @@ -156,7 +169,7 @@ export const testDatasetExports = register( }, ); -export const testPromptExports = register( +export const testPromptExports: TestFn = register( "testPromptExports", async (module) => { assertDefined(module.loadPrompt, "loadPrompt must exist"); @@ -176,15 +189,11 @@ export const testPromptExports = register( }, ); -export const testExperimentExports = register( +export const testExperimentExports: TestFn = register( "testExperimentExports", async (module) => { - assertDefined(module.initExperiment, "initExperiment must exist"); - assertType( - module.initExperiment, - "function", - "initExperiment must be a function", - ); + assertDefined(module.init, "init must exist"); + assertType(module.init, "function", "init must be a function"); assertDefined(module.Experiment, "Experiment must exist"); assertType( @@ -204,66 +213,35 @@ export const testExperimentExports = register( }, ); -export const testEvalExports = register("testEvalExports", async (module) => { - assertDefined(module.Eval, "Eval must exist"); - assertType(module.Eval, "function", "Eval must be a function"); - - assertDefined( - module.EvalResultWithSummary, - "EvalResultWithSummary must exist", - ); - assertType( - module.EvalResultWithSummary, - "function", - "EvalResultWithSummary must be a function/class", - ); - - assertDefined(module.Reporter, "Reporter must exist"); - assertType(module.Reporter, "function", "Reporter must be a function"); - - assertDefined(module.runEvaluator, "runEvaluator must exist"); - assertType( - module.runEvaluator, - "function", - "runEvaluator must be a function", - ); - - assertDefined(module.buildLocalSummary, "buildLocalSummary must exist"); - assertType( - module.buildLocalSummary, - "function", - "buildLocalSummary must be a function", - ); +export const testEvalExports: TestFn = register( + "testEvalExports", + async (module) => { + assertDefined(module.Eval, "Eval must exist"); + assertType(module.Eval, "function", "Eval must be a function"); - assertDefined(module.reportFailures, "reportFailures must exist"); - assertType( - module.reportFailures, - "function", - "reportFailures must be a function", - ); + assertDefined(module.Reporter, "Reporter must exist"); + assertType(module.Reporter, "function", "Reporter must be a function"); - assertDefined( - module.defaultErrorScoreHandler, - "defaultErrorScoreHandler must exist", - ); - assertType( - module.defaultErrorScoreHandler, - "function", - "defaultErrorScoreHandler must be a function", - ); + assertDefined( + module.defaultErrorScoreHandler, + "defaultErrorScoreHandler must exist", + ); + assertType( + module.defaultErrorScoreHandler, + "function", + "defaultErrorScoreHandler must be a function", + ); - return "Eval exports verified (7 runtime exports)"; -}); + return "Eval exports verified (3 runtime exports)"; + }, +); -export const testTracingExports = register( +export const testTracingExports: TestFn = register( "testTracingExports", async (module) => { assertDefined(module.traced, "traced must exist"); assertType(module.traced, "function", "traced must be a function"); - assertDefined(module.traceable, "traceable must exist"); - assertType(module.traceable, "function", "traceable must be a function"); - assertDefined(module.wrapTraced, "wrapTraced must exist"); assertType(module.wrapTraced, "function", "wrapTraced must be a function"); @@ -284,11 +262,11 @@ export const testTracingExports = register( "withCurrent must be a function", ); - return "Tracing exports verified (6 exports)"; + return "Tracing exports verified (5 exports)"; }, ); -export const testClientWrapperExports = register( +export const testClientWrapperExports: TestFn = register( "testClientWrapperExports", async (module) => { assertDefined(module.wrapOpenAI, "wrapOpenAI must exist"); @@ -300,7 +278,6 @@ export const testClientWrapperExports = register( "wrapGoogleGenAI", "wrapOpenRouter", "wrapAISDK", - "wrapMastraAgent", "wrapClaudeAgentSDK", ]; @@ -319,7 +296,7 @@ export const testClientWrapperExports = register( }, ); -export const testUtilityExports = register( +export const testUtilityExports: TestFn = register( "testUtilityExports", async (module) => { assertDefined(module.JSONAttachment, "JSONAttachment must exist"); @@ -336,17 +313,11 @@ export const testUtilityExports = register( "Attachment must be a function/class", ); - assertDefined(module.newId, "newId must exist"); - assertType(module.newId, "function", "newId must be a function"); - - assertDefined(module.permalink, "permalink must exist"); - assertType(module.permalink, "function", "permalink must be a function"); - - return "Utility exports verified (4 exports)"; + return "Attachment exports verified (2 exports)"; }, ); -export const testFunctionExports = register( +export const testFunctionExports: TestFn = register( "testFunctionExports", async (module) => { assertDefined(module.invoke, "invoke must exist"); @@ -363,27 +334,17 @@ export const testFunctionExports = register( }, ); -export const testFramework2Exports = register( +export const testFramework2Exports: TestFn = register( "testFramework2Exports", async (module) => { - assertDefined(module.Project, "Project must exist"); - assertType(module.Project, "function", "Project must be a function/class"); - assertDefined(module.projects, "projects must exist"); assertType(module.projects, "object", "projects must be an object"); - assertDefined(module.PromptBuilder, "PromptBuilder must exist"); - assertType( - module.PromptBuilder, - "function", - "PromptBuilder must be a function/class", - ); - - return "Framework2 exports verified (3 exports)"; + return "Project facade export verified"; }, ); -export const testIDGeneratorExports = register( +export const testIDGeneratorExports: TestFn = register( "testIDGeneratorExports", async (module) => { assertDefined(module.IDGenerator, "IDGenerator must exist"); @@ -393,11 +354,12 @@ export const testIDGeneratorExports = register( "IDGenerator must be a function/class", ); - return "ID generator exports verified (1 export)"; + return "Internal ID generator export verified"; }, + { requiresTestHarness: true }, ); -export const testTestingExports = register( +export const testTestingExports: TestFn = register( "testTestingExports", async (module) => { assertDefined( @@ -410,11 +372,12 @@ export const testTestingExports = register( "_exportsForTestingOnly must be an object", ); - return "Testing exports verified (1 export)"; + return "Internal testing export verified"; }, + { requiresTestHarness: true }, ); -export const testStateManagementExports = register( +export const testStateManagementExports: TestFn = register( "testStateManagementExports", async (module) => { assertDefined(module.BraintrustState, "BraintrustState must exist"); @@ -439,34 +402,39 @@ export function testBuildResolution( | "workerd" | "node", ): TestFn { - return register("testBuildResolution", async (module) => { - const { buildType: detectedBuild, buildDetails } = detectBuildType(module); - const detectedFormat = detectModuleFormat(); - - if (detectedBuild === "unknown") { - throw new Error( - `Build type is unknown - configureBrowser() or configureNode() was not called. ${buildDetails || ""}`, - ); - } + return register( + "testBuildResolution", + async (module) => { + const { buildType: detectedBuild, buildDetails } = + detectBuildType(module); + const detectedFormat = detectModuleFormat(); + + if (detectedBuild === "unknown") { + throw new Error( + `Build type is unknown - configureBrowser() or configureNode() was not called. ${buildDetails || ""}`, + ); + } - if (expectedBuildType && detectedBuild !== expectedBuildType) { - throw new Error( - `Expected build type "${expectedBuildType}" but detected "${detectedBuild}"`, - ); - } + if (expectedBuildType && detectedBuild !== expectedBuildType) { + throw new Error( + `Expected build type "${expectedBuildType}" but detected "${detectedBuild}"`, + ); + } - const parts: string[] = [`Detected ${detectedBuild} build`]; + const parts: string[] = [`Detected ${detectedBuild} build`]; - if (expectedBuildType) { - parts.push(`(expected: ${expectedBuildType})`); - } + if (expectedBuildType) { + parts.push(`(expected: ${expectedBuildType})`); + } - if (detectedFormat !== "unknown") { - parts.push(`${detectedFormat} format`); - } + if (detectedFormat !== "unknown") { + parts.push(`${detectedFormat} format`); + } - return parts.join(", "); - }); + return parts.join(", "); + }, + { requiresTestHarness: true }, + ); } function detectBuildType(module: BraintrustModule): { diff --git a/js/smoke/shared/src/suites/prompt-templating.ts b/js/smoke/shared/src/suites/prompt-templating.ts index dcbcc4380..d10cd870f 100644 --- a/js/smoke/shared/src/suites/prompt-templating.ts +++ b/js/smoke/shared/src/suites/prompt-templating.ts @@ -4,9 +4,9 @@ */ import { assertEqual } from "../helpers/assertions"; -import { register } from "../helpers/register"; +import { register, type TestFn } from "../helpers/register"; -export const testMustacheTemplate = register( +export const testMustacheTemplate: TestFn = register( "testMustacheTemplate", async (module) => { // eslint-disable-next-line @typescript-eslint/no-explicit-any @@ -50,7 +50,7 @@ export const testMustacheTemplate = register( }, ); -export const testNunjucksTemplate = register( +export const testNunjucksTemplate: TestFn = register( "testNunjucksTemplate", async (module) => { // eslint-disable-next-line @typescript-eslint/no-explicit-any diff --git a/js/smoke/shared/tsup.config.ts b/js/smoke/shared/tsup.config.ts deleted file mode 100644 index a7ac7c9b8..000000000 --- a/js/smoke/shared/tsup.config.ts +++ /dev/null @@ -1,12 +0,0 @@ -import { defineConfig } from "tsup"; - -export default defineConfig({ - entry: ["src/index.ts"], - format: ["cjs", "esm"], - outDir: "dist", - dts: true, - splitting: false, - clean: true, - sourcemap: true, - // No external dependencies to bundle - this is a standalone test utility package -}); diff --git a/js/smoke/shared/vite.config.ts b/js/smoke/shared/vite.config.ts new file mode 100644 index 000000000..b94d9e910 --- /dev/null +++ b/js/smoke/shared/vite.config.ts @@ -0,0 +1,10 @@ +import { defineConfig } from "vite-plus"; + +export default defineConfig({ + pack: { + platform: "neutral", + target: "es2022", + dts: { generator: "oxc" }, + sourcemap: true, + }, +}); diff --git a/js/src/agent-assertions.test.ts b/js/src/agent-assertions.test.ts index 0e3cb5b2e..c9ee9a842 100644 --- a/js/src/agent-assertions.test.ts +++ b/js/src/agent-assertions.test.ts @@ -1,4 +1,4 @@ -import { beforeAll, expect, expectTypeOf, test, vi } from "vitest"; +import { beforeAll, expect, expectTypeOf, test, vi } from "vite-plus/test"; import { z } from "zod"; import { agentAssertionScorer, Eval } from "./exports"; diff --git a/js/src/agent-assertions.ts b/js/src/agent-assertions.ts index 93824cb60..4f3c65b9a 100644 --- a/js/src/agent-assertions.ts +++ b/js/src/agent-assertions.ts @@ -1,6 +1,6 @@ import type { EvalScorer, EvalScorerArgs } from "./framework"; import type { SpanData } from "./trace"; -import type { BaseMetadata, DefaultMetadataType } from "./logger"; +import type { BaseMetadata, DefaultMetadataType } from "./logger/core"; import type { Score } from "../util"; type MaybePromise = T | Promise; diff --git a/js/src/ai-sdk-batch.ts b/js/src/ai-sdk-batch.ts index a1a32f1e4..67ee5c88f 100644 --- a/js/src/ai-sdk-batch.ts +++ b/js/src/ai-sdk-batch.ts @@ -1,4 +1,5 @@ -import { aiSDKBatchChannels } from "./instrumentation/plugins/ai-sdk-batch-interceptors"; +import { registerInstrumentation } from "./instrumentation/registrations/aisdk"; +import { aiSDKBatchChannels } from "./instrumentation/providers/ai-sdk-batch-interceptors"; import type { CompleteAISDKBatchTraceArgs, StartAISDKBatchTraceArgs, @@ -18,6 +19,7 @@ import type { export function startAISDKBatchTrace( args: StartAISDKBatchTraceArgs, ): Promise { + registerInstrumentation(); return aiSDKBatchChannels.start.invoke( async () => undefined, undefined, @@ -40,6 +42,7 @@ export function startAISDKBatchTrace( export function completeAISDKBatchTrace( args: CompleteAISDKBatchTraceArgs, ): Promise { + registerInstrumentation(); return aiSDKBatchChannels.complete.invoke( async () => undefined, undefined, diff --git a/js/src/attachment-capture.test.ts b/js/src/attachment-capture.test.ts new file mode 100644 index 000000000..1d8a99e83 --- /dev/null +++ b/js/src/attachment-capture.test.ts @@ -0,0 +1,1091 @@ +import { + afterEach, + beforeAll, + beforeEach, + expect, + it, + vi, +} from "vite-plus/test"; +import { + _internalCaptureAttachmentsEnabled, + Attachment, + BraintrustState, + CAPTURE_ATTACHMENTS, + ExternalAttachment, + startSpan, + _internalGetGlobalState, + initLogger, +} from "./logger/core"; +import { _exportsForTestingOnly } from "./logger/testing"; +import { configureNode } from "./node/config"; +import iso from "#braintrust-platform"; +import { processInputAttachments } from "./wrappers/attachment-utils"; +import { processAttachmentsInInput } from "./instrumentation/providers/anthropic-instrumentation"; +import { + processAISDKCallInput, + processAISDKGenerateImageInput, + processAISDKGenerateImageOutput, + processAISDKOutput, + patchAISDKStreamingResult, +} from "./instrumentation/providers/ai-sdk-instrumentation"; +import { OpenAIAgentsTraceProcessor } from "./instrumentation/providers/openai-agents-trace-processor"; +import type { OpenAIAgentsSpan } from "./vendor-sdk-types/openai-agents"; +import { extractOllamaChatInput } from "./instrumentation/providers/ollama-instrumentation"; +import { openAIChannels } from "./instrumentation/providers/openai-interceptors"; +import { registerInstrumentation } from "./instrumentation/registrations/openai"; + +configureNode(); +const state = _internalGetGlobalState(); +let background: ReturnType< + typeof _exportsForTestingOnly.useTestBackgroundLogger +>; +beforeAll(() => { + registerInstrumentation(); + return _exportsForTestingOnly.simulateLoginForTests(); +}); +beforeEach(() => { + vi.stubEnv("BRAINTRUST_AUTO_CAPTURE_ATTACHMENTS", undefined); + state.autoCaptureAttachments = undefined; + state.currentLogger = undefined; + background = _exportsForTestingOnly.useTestBackgroundLogger(); +}); +afterEach(async () => { + await background.drain(); + _exportsForTestingOnly.clearTestBackgroundLogger(); + state.autoCaptureAttachments = undefined; + state.currentLogger = undefined; + vi.restoreAllMocks(); + vi.unstubAllEnvs(); +}); +const loggerOptions = { + projectName: "tmp-luca-capture-tests", + projectId: "test-project-id", +}; +const inlineImage = { + type: "image_url", + image_url: { url: "data:image/png;base64,AQID" }, +}; + +it.each([false, true])( + "processes native OpenAI media with capture=%s", + (captureAttachments) => { + const input = [ + { + type: "input_image", + image_url: "data:image/png;base64,AQID", + detail: "low", + }, + { type: "input_image", image: "data:image/png;base64,AQID" }, + { type: "input_file", file_data: "AQID", filename: "document.pdf" }, + { type: "input_audio", input_audio: { data: "AQID", format: "wav" } }, + { type: "input_image", image_url: "https://example.com/image.png" }, + { type: "input_file", file_id: "file-123" }, + ]; + const result = processInputAttachments(input, captureAttachments); + expect(result).toEqual([ + { + type: "input_image", + detail: "low", + ...(captureAttachments ? { image_url: expect.any(Attachment) } : {}), + }, + ...(captureAttachments + ? [{ type: "input_image", image: expect.any(Attachment) }] + : []), + { + type: "input_file", + filename: "document.pdf", + ...(captureAttachments ? { file_data: expect.any(Attachment) } : {}), + }, + { + type: "input_audio", + input_audio: { + format: "wav", + ...(captureAttachments ? { data: expect.any(Attachment) } : {}), + }, + }, + ...input.slice(4), + ]); + expect(input[2].file_data).toBe("AQID"); + }, +); + +it.each([false, true])( + "applies the owning policy to AI SDK tool-result media (%s)", + async (captureAttachments) => { + initLogger({ + ...loggerOptions, + autoCaptureAttachments: !captureAttachments, + }); + const local = initLogger({ + ...loggerOptions, + setCurrent: false, + autoCaptureAttachments: captureAttachments, + }); + const media = ["image-data", "file-data", "media"].map((type) => ({ + type, + data: "AQID", + mediaType: "image/png", + })); + const references = [ + { type: "text", text: "Screenshot captured" }, + { type: "image-url", url: "https://example.com/image.png" }, + { type: "file-id", fileId: "file-123" }, + ]; + const toolResult = { + type: "tool-result", + toolCallId: "call-1", + toolName: "screenshot", + output: { type: "content", value: [...media, ...references] }, + }; + const messages = [{ role: "tool", content: [toolResult] }]; + const decode = vi.spyOn(globalThis, "atob"); + await local.traced(() => { + const input = processAISDKCallInput({ + model: { modelId: "model", provider: "test" }, + messages, + }).input; + const output = processAISDKOutput({ response: { messages } }, []); + const expected = [ + ...media.map(({ data: _data, ...part }) => ({ + ...part, + ...(captureAttachments ? { data: expect.any(Attachment) } : {}), + })), + ...references, + ]; + expect(input).toHaveProperty( + "messages.0.content.0.output.value", + expected, + ); + expect(output).toHaveProperty( + "response.messages.0.content.0.output.value", + expected, + ); + }); + if (!captureAttachments) expect(decode).not.toHaveBeenCalled(); + expect(toolResult.output.value).toEqual([...media, ...references]); + expect(media.every((part) => part.data === "AQID")).toBe(true); + }, +); + +it.each([false, true])( + "processes AI SDK media throughout output containers with capture=%s", + (captureAttachments) => { + initLogger({ + ...loggerOptions, + autoCaptureAttachments: captureAttachments, + }); + const readBytes = vi.fn(() => "AQID"); + const generatedFile = { + mediaType: "image/png", + get base64() { + return readBytes(); + }, + }; + const file = { type: "file", file: generatedFile }; + const message = { + role: "assistant", + content: [ + { type: "file", mediaType: "image/png", data: "AQID" }, + { type: "text", text: "Done" }, + ], + }; + const result = processAISDKOutput( + { + content: [file], + steps: [{ content: [file], response: { messages: [message] } }], + response: { + messages: [message], + timestamp: new Date("2026-09-28T00:00:00Z"), + }, + usage: { inputTokens: 1, outputTokens: 2 } as Parameters< + typeof processAISDKOutput + >[0]["usage"], + }, + [], + ); + expect(result).toHaveProperty( + "content", + captureAttachments + ? [{ type: "file", file: expect.any(Attachment) }] + : [], + ); + expect(result).toHaveProperty( + "steps.0.content", + captureAttachments + ? [{ type: "file", file: expect.any(Attachment) }] + : [], + ); + for (const path of [ + "response.messages.0.content", + "steps.0.response.messages.0.content", + ]) { + expect(result).toHaveProperty(path, [ + { + type: "file", + mediaType: "image/png", + ...(captureAttachments ? { data: expect.any(Attachment) } : {}), + }, + { type: "text", text: "Done" }, + ]); + } + expect(result).toHaveProperty("usage", { inputTokens: 1, outputTokens: 2 }); + expect(result).toHaveProperty( + "response.timestamp", + "2026-09-28T00:00:00.000Z", + ); + if (!captureAttachments) expect(readBytes).not.toHaveBeenCalled(); + expect(message.content[0].data).toBe("AQID"); + }, +); + +it.each([false, true])( + "uses the active Agents processor's owning policy (%s)", + async (captureAttachments) => { + const logger = initLogger({ + ...loggerOptions, + setCurrent: false, + autoCaptureAttachments: captureAttachments, + }); + const processor = new OpenAIAgentsTraceProcessor({ logger }); + const trace = { + type: "trace" as const, + traceId: "media-trace", + name: "media trace", + groupId: null, + }; + const span: OpenAIAgentsSpan = { + type: "trace.span", + traceId: trace.traceId, + spanId: "media-span", + parentId: null, + startedAt: null, + endedAt: null, + error: null, + spanData: { + type: "response", + _input: [ + { type: "input_image", image: "data:image/png;base64,AQID" }, + { type: "input_file", file: "data:application/pdf;base64,AQID" }, + { type: "input_file", file: "https://example.com/report.pdf" }, + { type: "input_file", file: { id: "file-123" } }, + { + type: "input_file", + file: { url: "https://example.com/report.pdf" }, + }, + ], + _response: { + output: [{ type: "image_generation_call", result: "AQID" }], + }, + }, + }; + await processor.onTraceStart(trace); + await processor.onSpanStart(span); + initLogger({ + ...loggerOptions, + autoCaptureAttachments: !captureAttachments, + }); + await processor.onSpanEnd(span); + const rows = await background.drain(); + const output = rows.find( + (row) => "output" in row && row.output !== undefined, + ); + expect(output).toHaveProperty( + "output", + captureAttachments + ? [{ type: "image_generation_call", result: expect.any(Attachment) }] + : [], + ); + expect(output).toHaveProperty("input", [ + ...(captureAttachments + ? [ + { type: "input_image", image: expect.any(Attachment) }, + { type: "input_file", file: expect.any(Attachment) }, + ] + : []), + { type: "input_file", file: "https://example.com/report.pdf" }, + { type: "input_file", file: { id: "file-123" } }, + { type: "input_file", file: { url: "https://example.com/report.pdf" } }, + ]); + expect( + processor._traceSpans.get(trace.traceId)?.metadata.lastOutput, + ).toEqual(output && "output" in output ? output.output : undefined); + }, +); + +it.each([false, true])( + "keeps AI SDK streamed media on its owning policy (%s)", + async (captureAttachments) => { + const local = initLogger({ + ...loggerOptions, + setCurrent: false, + autoCaptureAttachments: captureAttachments, + }); + const span = local.startSpan({ name: "streamed media" }); + const part = { + type: "file", + data: new Uint8Array([1, 2, 3]), + mediaType: "image/png", + }; + const result = { + fullStream: (async function* () { + yield part; + })(), + content: [part], + text: "Done", + }; + expect( + patchAISDKStreamingResult({ + defaultDenyOutputPaths: [], + endEvent: {}, + result, + span, + startTime: Date.now() / 1000, + }), + ).toBe(true); + initLogger({ + ...loggerOptions, + autoCaptureAttachments: !captureAttachments, + }); + for await (const chunk of result.fullStream) expect(chunk).toBe(part); + const rows = await background.drain(); + expect(rows.find((row) => "output" in row)).toHaveProperty("output", { + content: [ + { + type: "file", + mediaType: "image/png", + ...(captureAttachments ? { data: expect.any(Attachment) } : {}), + }, + ], + text: "Done", + }); + expect(part.data).toEqual(new Uint8Array([1, 2, 3])); + }, +); + +it.each([false, true])( + "applies the active Agents policy to speech and transcription (%s)", + async (captureAttachments) => { + const local = initLogger({ + ...loggerOptions, + setCurrent: false, + autoCaptureAttachments: captureAttachments, + }); + const processor = new OpenAIAgentsTraceProcessor({ logger: local }); + const trace = { + type: "trace" as const, + traceId: "audio-trace", + name: "audio trace", + groupId: null, + }; + await processor.onTraceStart(trace); + for (const type of ["speech", "transcription"] as const) { + const audio = { data: "AQID", format: "pcm" }; + const span: OpenAIAgentsSpan = { + type: "trace.span", + traceId: trace.traceId, + spanId: type, + parentId: null, + startedAt: null, + endedAt: null, + error: null, + spanData: + type === "speech" + ? { type, input: "Hello", output: audio } + : { type, input: audio, output: "Hello" }, + }; + await processor.onSpanStart(span); + initLogger({ + ...loggerOptions, + autoCaptureAttachments: !captureAttachments, + }); + await processor.onSpanEnd(span); + const payload = (await background.drain()).find((row) => "output" in row); + expect(payload).toHaveProperty(type === "speech" ? "output" : "input", { + format: "pcm", + ...(captureAttachments ? { data: expect.any(Attachment) } : {}), + }); + expect(audio.data).toBe("AQID"); + } + }, +); + +it.each([ + [false, false], + [false, true], + [true, false], + [true, true], +])( + "applies capture=%s to AI SDK image-edit inputs and masks (URL object: %s)", + async (captureAttachments, maskAsURL) => { + initLogger({ + ...loggerOptions, + autoCaptureAttachments: !captureAttachments, + }); + const local = initLogger({ + ...loggerOptions, + setCurrent: false, + autoCaptureAttachments: captureAttachments, + }); + const data = new URL("data:image/png;base64,AQID"); + const remote = new URL("https://example.com/image.png"); + const manual = new Attachment({ + data: "manual", + filename: "manual.png", + contentType: "image/png", + }); + const prompt = { + images: [ + "AQID", + new Uint8Array([1, 2, 3]), + data, + remote, + remote.href, + manual, + ], + mask: maskAsURL ? data : data.href, + text: "Edit these images", + }; + const decode = vi.spyOn(globalThis, "atob"); + const result = await local.traced(() => + processAISDKGenerateImageInput({ + model: { modelId: "image-model", provider: "test" }, + prompt, + }), + ); + expect(result.input).toHaveProperty( + "prompt", + captureAttachments + ? { + images: [ + expect.any(Attachment), + expect.any(Attachment), + expect.any(Attachment), + remote, + remote.href, + manual, + ], + mask: expect.any(Attachment), + text: prompt.text, + } + : { images: [remote, remote.href, manual], text: prompt.text }, + ); + if (!captureAttachments) expect(decode).not.toHaveBeenCalled(); + expect(prompt.images).toHaveLength(6); + expect(prompt.mask).toBe(maskAsURL ? data : data.href); + }, +); + +it("preserves AI SDK image-edit inputs when conversion fails", () => { + initLogger({ ...loggerOptions, autoCaptureAttachments: true }); + const prompt = { + images: ["not valid base64!"], + mask: "not valid base64!", + text: "Edit", + }; + const result = processAISDKGenerateImageInput({ + model: { modelId: "model", provider: "test" }, + prompt, + }); + expect(result.input).toHaveProperty("prompt", prompt); +}); + +it.each([false, true])( + "applies capture=%s to AI SDK content parts containing data URL objects", + (captureAttachments) => { + initLogger({ + ...loggerOptions, + autoCaptureAttachments: captureAttachments, + }); + const data = new URL("data:image/png;base64,AQID"); + const content = [ + { type: "image", image: data }, + { type: "file", data, mediaType: "image/png" }, + { type: "image_url", image_url: { url: data } }, + ]; + const result = processAISDKCallInput({ + model: { modelId: "model", provider: "test" }, + messages: [{ role: "user", content }], + }); + expect(result.input).toHaveProperty("messages", [ + { + role: "user", + content: captureAttachments + ? [ + { type: "image", image: expect.any(Attachment) }, + { + type: "file", + data: expect.any(Attachment), + mediaType: "image/png", + }, + { type: "image_url", image_url: { url: expect.any(Attachment) } }, + ] + : [{ type: "file", mediaType: "image/png" }], + }, + ]); + }, +); + +it("keeps explicit AI SDK attachments with capture disabled", () => { + initLogger({ ...loggerOptions, autoCaptureAttachments: false }); + const external = new ExternalAttachment({ + url: "s3://bucket/image.png", + filename: "image.png", + contentType: "image/png", + }); + const content = [ + { type: "image", image: external }, + { type: "file", data: external, mediaType: "image/png" }, + ]; + const { input } = processAISDKCallInput({ + model: { modelId: "model", provider: "test" }, + messages: [{ role: "user", content }], + }); + expect(input).toHaveProperty("messages.0.content", content); +}); + +it.each([true, false])( + "explicit %s overrides an opposing environment setting", + (captureAttachments) => { + vi.stubEnv( + "BRAINTRUST_AUTO_CAPTURE_ATTACHMENTS", + String(!captureAttachments), + ); + const logger = initLogger({ + ...loggerOptions, + autoCaptureAttachments: captureAttachments, + }); + expect(_internalCaptureAttachmentsEnabled(logger)).toBe(captureAttachments); + expect(_internalCaptureAttachmentsEnabled()).toBe(captureAttachments); + initLogger(loggerOptions); + expect(_internalCaptureAttachmentsEnabled()).toBe(captureAttachments); + }, +); + +it("local loggers use the environment, and never inherit or update the global override", () => { + initLogger({ ...loggerOptions, autoCaptureAttachments: true }); + const local = initLogger({ ...loggerOptions, setCurrent: false }); + expect(_internalCaptureAttachmentsEnabled(local)).toBe(false); + initLogger({ + ...loggerOptions, + setCurrent: false, + autoCaptureAttachments: false, + }); + expect(_internalCaptureAttachmentsEnabled()).toBe(true); + vi.stubEnv("BRAINTRUST_AUTO_CAPTURE_ATTACHMENTS", "true"); + initLogger({ ...loggerOptions, autoCaptureAttachments: false }); + expect(_internalCaptureAttachmentsEnabled(local)).toBe(true); +}); + +it("isolates SDK state defaults", () => { + const other = new BraintrustState({}); + initLogger({ ...loggerOptions, autoCaptureAttachments: true }); + const otherLogger = initLogger({ + ...loggerOptions, + state: other, + autoCaptureAttachments: false, + }); + expect(_internalCaptureAttachmentsEnabled(otherLogger)).toBe(false); + expect(other.autoCaptureAttachments).toBe(false); + expect(_internalCaptureAttachmentsEnabled()).toBe(true); +}); + +it("keeps concurrent local traces and descendants isolated after global replacement", async () => { + const enabled = initLogger({ + ...loggerOptions, + setCurrent: false, + autoCaptureAttachments: true, + }); + const disabled = initLogger({ + ...loggerOptions, + setCurrent: false, + autoCaptureAttachments: false, + }); + await Promise.all( + [enabled, disabled].map(async (logger, index) => { + await logger.traced(async (parent) => { + await Promise.resolve(); + initLogger({ ...loggerOptions, autoCaptureAttachments: index !== 0 }); + await parent.traced(async () => { + await Promise.resolve(); + const output = processInputAttachments(inlineImage); + if (index === 0) + expect(output.image_url.url).toBeInstanceOf(Attachment); + else expect(output).toBeUndefined(); + }); + }); + }), + ); +}); + +it("does not decode inline media, read local images, or evaluate generated binary getters when disabled", () => { + initLogger({ ...loggerOptions, autoCaptureAttachments: false }); + const decode = vi.spyOn(globalThis, "atob"); + const stat = vi.spyOn(iso, "statSync"); + const binary = vi.fn(() => { + throw new Error("must not access media"); + }); + expect( + processAttachmentsInInput([ + { + type: "image", + source: { type: "base64", media_type: "image/png", data: "AQID" }, + }, + ]), + ).toEqual([ + { + type: "image", + source: { type: "base64", media_type: "image/png" }, + }, + ]); + const source = Object.defineProperty( + { type: "base64", media_type: "image/png" }, + "data", + { get: binary, enumerable: true }, + ); + expect(processAttachmentsInInput({ type: "image", source })).toEqual({ + type: "image", + source: { type: "base64", media_type: "image/png" }, + }); + const image = Object.defineProperty({ mediaType: "image/png" }, "base64", { + get: binary, + }); + expect(processAISDKGenerateImageOutput({ images: [image] }, [])).toEqual({}); + expect( + extractOllamaChatInput([ + { + model: "model", + messages: [ + { role: "user", content: "look", images: ["/tmp/private.png"] }, + ], + }, + ]).input, + ).toEqual([ + { + role: "user", + content: "look", + }, + ]); + expect(decode).not.toHaveBeenCalled(); + expect(stat).not.toHaveBeenCalled(); + expect(binary).not.toHaveBeenCalled(); +}); + +it("preserves explicit attachments and remote references with capture disabled", async () => { + const logger = initLogger({ + ...loggerOptions, + autoCaptureAttachments: false, + }); + const attachment = new Attachment({ + data: new Blob(["explicit"]), + filename: "explicit.txt", + contentType: "text/plain", + }); + logger.log({ input: { attachment } }); + const rows = (await background.drain()) as Array<{ + input?: unknown; + output?: unknown; + metrics?: Record; + }>; + expect( + rows.some( + (row) => + (row.input as { attachment?: unknown })?.attachment === attachment, + ), + ).toBe(true); + const attachments: Attachment[] = []; + const event = { input: { attachment } }; + _exportsForTestingOnly.extractAttachments(event, attachments); + expect(attachments).toEqual([attachment]); + expect( + processInputAttachments({ + ...inlineImage, + image_url: { url: "https://example.com/image.png" }, + }).image_url.url, + ).toBe("https://example.com/image.png"); +}); + +it("preserves a deferred instrumentation policy when a different logger supplies the span parent", async () => { + initLogger({ ...loggerOptions, autoCaptureAttachments: true }); + const args = { name: "deferred", [CAPTURE_ATTACHMENTS]: false }; + const deferred = startSpan(args); + const child = deferred.startSpan(); + expect(_internalCaptureAttachmentsEnabled(deferred)).toBe(false); + expect(_internalCaptureAttachmentsEnabled(child)).toBe(false); + child.end(); + deferred.end(); +}); + +it.each([false, true])( + "preserves AI SDK application JSON that resembles media (capture=%s)", + async (captureAttachments) => { + const logger = initLogger({ + ...loggerOptions, + autoCaptureAttachments: captureAttachments, + }); + const object = { type: "file", data: "report text" }; + const toolCall = { + type: "tool-call", + toolCallId: "call", + toolName: "report", + input: object, + args: object, + }; + const toolResult = { + type: "tool-result", + toolCallId: "call", + toolName: "report", + input: object, + output: { type: "json", value: object }, + result: object, + }; + const messages = [ + { role: "assistant", content: [toolCall] }, + { role: "tool", content: [toolResult] }, + ]; + const result = { + object, + output: object, + content: [toolCall, toolResult], + toolCalls: [toolCall], + toolResults: [toolResult], + response: { messages }, + steps: [{ content: [toolCall, toolResult], providerMetadata: object }], + providerMetadata: object, + }; + expect(processAISDKOutput(result, [], captureAttachments)).toEqual(result); + expect( + processAISDKCallInput({ + model: { modelId: "test", provider: "test" }, + messages, + }).input, + ).toHaveProperty("messages", messages); + + const span = logger.startSpan({ name: "structured stream" }); + const streamResult = { + partialObjectStream: (async function* () { + yield object; + })(), + object: Promise.resolve(object), + }; + expect( + patchAISDKStreamingResult({ + defaultDenyOutputPaths: [], + endEvent: {}, + result: streamResult, + span, + startTime: Date.now() / 1000, + }), + ).toBe(true); + for await (const chunk of streamResult.partialObjectStream) + expect(chunk).toEqual(object); + const rows = await background.drain(); + expect(rows.find((row) => "output" in row)).toHaveProperty( + "output.object", + object, + ); + }, +); + +it.each([false, true])( + "captures Agents audio output using the owning policy (%s)", + async (captureAttachments) => { + const logger = initLogger({ + ...loggerOptions, + autoCaptureAttachments: captureAttachments, + setCurrent: false, + }); + const processor = new OpenAIAgentsTraceProcessor({ logger }); + const trace = { + type: "trace" as const, + traceId: "audio-output", + name: "audio output", + groupId: null, + }; + const audio = { + data: "AQID", + transcript: "Hello", + id: "audio-1", + expires_at: 123, + }; + const output = [{ choices: [{ message: { role: "assistant", audio } }] }]; + const span: OpenAIAgentsSpan = { + type: "trace.span", + traceId: trace.traceId, + spanId: "generation", + parentId: null, + startedAt: null, + endedAt: null, + error: null, + spanData: { type: "generation", output }, + }; + await processor.onTraceStart(trace); + await processor.onSpanStart(span); + initLogger({ + ...loggerOptions, + autoCaptureAttachments: !captureAttachments, + }); + await processor.onSpanEnd(span); + const rows = await background.drain(); + const row = rows.find((row) => "output" in row && row.output !== undefined); + expect(row).toHaveProperty("output.0.choices.0.message.audio", { + transcript: "Hello", + id: "audio-1", + expires_at: 123, + ...(captureAttachments ? { data: expect.any(Attachment) } : {}), + }); + expect(audio.data).toBe("AQID"); + const protocolAudio = { + type: "audio", + audio: "AQID", + format: "mp3", + transcript: "Hello", + }; + expect(processInputAttachments(protocolAudio, captureAttachments)).toEqual({ + type: "audio", + format: "mp3", + transcript: "Hello", + ...(captureAttachments ? { audio: expect.any(Attachment) } : {}), + }); + const remoteAudio = { type: "audio", audio: { id: "file-123" } }; + expect(processInputAttachments(remoteAudio, captureAttachments)).toEqual( + remoteAudio, + ); + }, +); + +it.each([false, true])( + "preserves custom Agents JSON without mutating SDK span data (%s)", + async (captureAttachments) => { + const logger = initLogger({ + ...loggerOptions, + autoCaptureAttachments: captureAttachments, + }); + const processor = new OpenAIAgentsTraceProcessor({ logger }); + const trace = { + type: "trace" as const, + traceId: "custom-json", + name: "custom JSON", + groupId: null, + }; + const data = Object.freeze({ + input: Object.freeze({ type: "file", data: "report text" }), + output: Object.freeze({ + type: "image_generation_call", + result: "ordinary result", + }), + }); + const span: OpenAIAgentsSpan = { + type: "trace.span", + traceId: trace.traceId, + spanId: "custom", + parentId: null, + startedAt: null, + endedAt: null, + error: null, + spanData: { type: "custom", name: "report", data }, + }; + await processor.onTraceStart(trace); + await processor.onSpanStart(span); + await processor.onSpanEnd(span); + const rows = await background.drain(); + expect( + rows.find((row) => "output" in row && row.output !== undefined), + ).toMatchObject(data); + expect(span.spanData).toHaveProperty("data", data); + }, +); + +it.each([false, true])( + "applies capture policy to Chat Completions audio (%s)", + async (captureAttachments) => { + initLogger({ + ...loggerOptions, + autoCaptureAttachments: captureAttachments, + }); + const audio = { data: "AQID", transcript: "Hello", id: "audio-1" }; + const result = { + choices: [ + { + index: 0, + message: { role: "assistant" as const, content: null, audio }, + finish_reason: "stop", + }, + ], + }; + await openAIChannels.chatCompletionsCreate.invoke( + async () => result, + undefined, + [{ model: "audio-model", messages: [] }], + {}, + ); + const rows = await background.drain(); + expect(rows.find((row) => "output" in row)).toHaveProperty( + "output.0.message.audio", + { + transcript: "Hello", + id: "audio-1", + ...(captureAttachments ? { data: expect.any(Attachment) } : {}), + }, + ); + expect(audio.data).toBe("AQID"); + }, +); + +it.each([false, true])( + "handles raw base64 chat files without changing references (capture=%s)", + async (captureAttachments) => { + initLogger({ + ...loggerOptions, + autoCaptureAttachments: captureAttachments, + }); + const messages = [ + { + role: "user", + content: [ + { + type: "file", + file: { file_data: "AQID", filename: "document.pdf" }, + }, + { type: "file", file: { file_id: "file-123" } }, + { + type: "file", + file: { file_data: "https://example.com/document.pdf" }, + }, + { type: "text", text: "Summarize this document." }, + ], + }, + ]; + await openAIChannels.chatCompletionsCreate.invoke( + async () => ({ choices: [] }), + undefined, + [{ model: "model", messages }], + {}, + ); + const rows = await background.drain(); + expect(rows.find((row) => "input" in row)).toHaveProperty("input", [ + { + role: "user", + content: [ + { + type: "file", + file: { + filename: "document.pdf", + ...(captureAttachments + ? { file_data: expect.any(Attachment) } + : {}), + }, + }, + ...messages[0].content.slice(1), + ], + }, + ]); + expect(messages[0].content[0].file?.file_data).toBe("AQID"); + if (!captureAttachments) expect(JSON.stringify(rows)).not.toContain("AQID"); + }, +); + +it.each([false, true])( + "preserves parsed OpenAI output and tool arguments that resemble media (capture=%s)", + async (captureAttachments) => { + initLogger({ + ...loggerOptions, + autoCaptureAttachments: captureAttachments, + }); + const parsed = { type: "file", data: "report text" }; + const parsedArguments = { + type: "input_image", + image_url: "ordinary input", + }; + const choices = [ + { + index: 0, + message: { + role: "assistant", + content: null, + parsed, + tool_calls: [ + { + type: "function", + id: "call-1", + function: { + name: "report", + arguments: JSON.stringify(parsedArguments), + parsed_arguments: parsedArguments, + }, + }, + ], + }, + }, + ]; + for (const channel of [ + openAIChannels.chatCompletionsCreate, + openAIChannels.betaChatCompletionsParse, + ]) { + await channel.invoke( + async () => ({ choices }), + undefined, + [{ model: "model", messages: [] }], + {}, + ); + const rows = await background.drain(); + expect(rows.find((row) => "output" in row)).toHaveProperty( + "output", + choices, + ); + } + expect(choices[0].message.parsed).toBe(parsed); + expect(choices[0].message.tool_calls[0].function.parsed_arguments).toBe( + parsedArguments, + ); + }, +); + +it.each([ + ["mp3", "audio/mpeg", "mp3"], + ["wav", "audio/wav", "wav"], + ["flac", "audio/flac", "flac"], + ["aac", "audio/aac", "aac"], + ["opus", "audio/ogg", "ogg"], + ["pcm16", "audio/pcm", "pcm"], +] as const)( + "uses the requested %s format for generated chat audio", + async (format, contentType, extension) => { + initLogger({ ...loggerOptions, autoCaptureAttachments: true }); + const audio = { data: "AQID", transcript: "Hello", id: "audio-1" }; + const choices = [ + { index: 0, message: { role: "assistant", content: null, audio } }, + ]; + for (const channel of [ + openAIChannels.chatCompletionsCreate, + openAIChannels.betaChatCompletionsParse, + ]) { + await channel.invoke( + async () => ({ choices }), + undefined, + [ + { + model: "audio-model", + messages: [], + audio: { format, voice: "alloy" }, + }, + ], + {}, + ); + const rows = await background.drain(); + expect(rows.find((row) => "output" in row)).toHaveProperty( + "output.0.message.audio", + { + transcript: "Hello", + id: "audio-1", + data: expect.objectContaining({ + reference: expect.objectContaining({ + content_type: contentType, + filename: `audio.${extension}`, + }), + }), + }, + ); + } + expect(audio).toEqual({ data: "AQID", transcript: "Hello", id: "audio-1" }); + }, +); diff --git a/js/src/audio-compression.test.ts b/js/src/audio-compression.test.ts index 26450683b..e7714b712 100644 --- a/js/src/audio-compression.test.ts +++ b/js/src/audio-compression.test.ts @@ -1,11 +1,11 @@ -import { afterEach, beforeEach, expect, test, vi } from "vitest"; +import { afterEach, beforeEach, expect, test, vi } from "vite-plus/test"; import { _exportsForTestingOnly, audioEncodingStack, type AudioEncodingDefaults, } from "./audio-compression"; import type { AudioMiddleware } from "./audio-middleware"; -import iso from "./isomorph"; +import iso from "#braintrust-platform"; import { configureNode } from "./node/config"; configureNode(); diff --git a/js/src/audio-compression.ts b/js/src/audio-compression.ts index c4003554b..055aa4974 100644 --- a/js/src/audio-compression.ts +++ b/js/src/audio-compression.ts @@ -1,7 +1,7 @@ import type { AudioMiddlewareStack } from "./audio-middleware"; import { debugLogger } from "./debug-logger"; import type { AudioEncodingConfig } from "./instrumentation/config"; -import iso from "./isomorph"; +import iso from "#braintrust-platform"; import { isObject } from "../util"; const AUDIO_PACKAGE = "@braintrust/audio"; diff --git a/js/src/audio-middleware.test.ts b/js/src/audio-middleware.test.ts index d2a0ea219..2abc1f3fe 100644 --- a/js/src/audio-middleware.test.ts +++ b/js/src/audio-middleware.test.ts @@ -1,4 +1,4 @@ -import { expect, test, vi } from "vitest"; +import { expect, test, vi } from "vite-plus/test"; import { runMiddleware, type AudioMiddleware, diff --git a/js/src/audio-recorder.test.ts b/js/src/audio-recorder.test.ts index 0477c1be4..f8899b2c2 100644 --- a/js/src/audio-recorder.test.ts +++ b/js/src/audio-recorder.test.ts @@ -1,4 +1,4 @@ -import { expect, test, vi } from "vitest"; +import { expect, test, vi } from "vite-plus/test"; import type { AudioMiddleware, AudioSegmentMeta } from "./audio-middleware"; import { AudioRecorder } from "./audio-recorder"; diff --git a/js/src/auto-instrumentations/bundler/esbuild.ts b/js/src/auto-instrumentations/bundler/esbuild.ts index 6e7455a6d..9d274a0a6 100644 --- a/js/src/auto-instrumentations/bundler/esbuild.ts +++ b/js/src/auto-instrumentations/bundler/esbuild.ts @@ -6,10 +6,3 @@ export function braintrustEsbuildPlugin( ): EsbuildPlugin { return unplugin.esbuild(options); } - -export type EsbuildPluginOptions = BundlerPluginOptions; - -/** - * @deprecated Use {@link braintrustEsbuildPlugin} instead. - */ -export const esbuildPlugin = unplugin.esbuild; diff --git a/js/src/auto-instrumentations/bundler/next.ts b/js/src/auto-instrumentations/bundler/next.ts index 8dff24e55..639672703 100644 --- a/js/src/auto-instrumentations/bundler/next.ts +++ b/js/src/auto-instrumentations/bundler/next.ts @@ -1,6 +1,6 @@ import { createRequire } from "node:module"; -import { join } from "node:path"; -import { webpackPlugin } from "./webpack"; +import { dirname, join } from "node:path"; +import { braintrustWebpackPlugin } from "./webpack"; type MaybePromise = T | Promise; type NextConfigFunction = ( @@ -155,7 +155,7 @@ function wrapWebpackConfig( : "server" : "client"; - const plugin = webpackPlugin({ + const plugin = braintrustWebpackPlugin({ browser: runtime === "client" || runtime === "edge", }); @@ -244,11 +244,8 @@ function addBraintrustTurbopackRule( } function getWebpackLoaderPath(): string { - try { - return requireFromProject.resolve("braintrust/webpack-loader"); - } catch { - return "braintrust/webpack-loader"; - } + const packageJsonPath = requireFromProject.resolve("braintrust/package.json"); + return join(dirname(packageJsonPath), "dist/loaders/webpack-loader.cjs"); } function getNextMajorVersion(): number | undefined { diff --git a/js/src/auto-instrumentations/bundler/plugin.test.ts b/js/src/auto-instrumentations/bundler/plugin.test.ts index 355a90b76..5b1b74fa8 100644 --- a/js/src/auto-instrumentations/bundler/plugin.test.ts +++ b/js/src/auto-instrumentations/bundler/plugin.test.ts @@ -1,5 +1,5 @@ import { createRequire } from "node:module"; -import { describe, expect, it } from "vitest"; +import { describe, expect, it } from "vite-plus/test"; import { unplugin } from "./plugin"; const require = createRequire(import.meta.url); diff --git a/js/src/auto-instrumentations/bundler/plugin.ts b/js/src/auto-instrumentations/bundler/plugin.ts index ff61dac11..0160436ef 100644 --- a/js/src/auto-instrumentations/bundler/plugin.ts +++ b/js/src/auto-instrumentations/bundler/plugin.ts @@ -1,5 +1,5 @@ import { debugLogger } from "../../debug-logger"; -import { createUnplugin } from "unplugin"; +import { createUnplugin, type UnpluginInstance } from "unplugin"; import { InstrumentationMatcher } from "../orchestrion-js"; import { isAbsolute } from "node:path"; import { fileURLToPath } from "node:url"; @@ -17,69 +17,58 @@ export interface BundlerPluginOptions { * @default false */ browser?: boolean; - - /** - * Marks transformed source as targeting a browser or edge-like environment. - * - * This retains the previous browser-target behavior of the option. Global - * instrumentation hooks are runtime-independent, so no diagnostics-channel - * compatibility shim is injected. - * - * @deprecated Use `browser` instead. - */ - useDiagnosticChannelCompatShim?: boolean; } -export const unplugin = createUnplugin((options = {}) => { - const browser = - options.browser ?? options.useDiagnosticChannelCompatShim ?? false; - const matcher = new InstrumentationMatcher( - getDefaultAutoInstrumentationConfigs(), - ); +export const unplugin: UnpluginInstance = + createUnplugin((options = {}) => { + const browser = options.browser ?? false; + const matcher = new InstrumentationMatcher( + getDefaultAutoInstrumentationConfigs(), + ); - return { - name: "code-transformer", - enforce: "pre", - transformInclude(id: string) { - const pathWithoutQuery = id.split("?", 1)[0]; - if (!pathWithoutQuery) { - return false; - } - if (pathWithoutQuery.startsWith("file:")) { - try { - return isAbsolute(fileURLToPath(pathWithoutQuery)); - } catch { + return { + name: "code-transformer", + enforce: "pre", + transformInclude(id: string) { + const pathWithoutQuery = id.split("?", 1)[0]; + if (!pathWithoutQuery) { return false; } - } - return isAbsolute(pathWithoutQuery); - }, - transform(code: string, id: string) { - if (!id) { - // Some modules apparently don't have an id? - return null; - } + if (pathWithoutQuery.startsWith("file:")) { + try { + return isAbsolute(fileURLToPath(pathWithoutQuery)); + } catch { + return false; + } + } + return isAbsolute(pathWithoutQuery); + }, + transform(code: string, id: string) { + if (!id) { + // Some modules apparently don't have an id? + return null; + } - // Vite and Rollup may append cache-busting queries or fragments to - // real filesystem module IDs. They are not part of the package path - // and prevent exact instrumentation file paths from matching. - const cleanId = id.replace(/[?#].*$/, ""); + // Vite and Rollup may append cache-busting queries or fragments to + // real filesystem module IDs. They are not part of the package path + // and prevent exact instrumentation file paths from matching. + const cleanId = id.replace(/[?#].*$/, ""); - // Convert file:// URLs to regular paths at entry point - // Node.js ESM loader hooks provide file:// URLs, but downstream code expects paths - const filePath = cleanId.startsWith("file:") - ? fileURLToPath(cleanId) - : cleanId; + // Convert file:// URLs to regular paths at entry point + // Node.js ESM loader hooks provide file:// URLs, but downstream code expects paths + const filePath = cleanId.startsWith("file:") + ? fileURLToPath(cleanId) + : cleanId; - try { - return transformBundledModule(code, filePath, matcher, browser); - } catch (error) { - // If transformation fails, warn and return original code - debugLogger.notice.warn( - `Code transformation failed for ${id}: ${error}`, - ); - return null; - } - }, - }; -}); + try { + return transformBundledModule(code, filePath, matcher, browser); + } catch (error) { + // If transformation fails, warn and return original code + debugLogger.notice.warn( + `Code transformation failed for ${id}: ${error}`, + ); + return null; + } + }, + }; + }); diff --git a/js/src/auto-instrumentations/bundler/rollup.ts b/js/src/auto-instrumentations/bundler/rollup.ts index 179e2a057..85d1ffdd9 100644 --- a/js/src/auto-instrumentations/bundler/rollup.ts +++ b/js/src/auto-instrumentations/bundler/rollup.ts @@ -6,10 +6,3 @@ export function braintrustRollupPlugin( ): RollupPlugin | RollupPlugin[] { return unplugin.rollup(options); } - -export type RollupPluginOptions = BundlerPluginOptions; - -/** - * @deprecated Use {@link braintrustRollupPlugin} instead. - */ -export const rollupPlugin = unplugin.rollup; diff --git a/js/src/auto-instrumentations/bundler/transform.ts b/js/src/auto-instrumentations/bundler/transform.ts index e8cd56857..a487a4a05 100644 --- a/js/src/auto-instrumentations/bundler/transform.ts +++ b/js/src/auto-instrumentations/bundler/transform.ts @@ -1,5 +1,6 @@ import { extname, sep } from "node:path"; import moduleDetailsFromPath from "module-details-from-path"; +import { Transformer } from "../orchestrion-js/transformer"; import type { InstrumentationMatcher } from "../orchestrion-js"; import { getPackageInfo } from "../loader/get-package-info"; import { applySpecialCasePatch } from "../loader/special-case-patches"; @@ -36,7 +37,16 @@ export function transformBundledModule( format, browser, }); - if (patched !== null) return { code: patched, map: null }; + if (patched !== null) { + const result = new Transformer( + packageName, + packageInfo?.version ?? "", + modulePath, + [], + ).transform(patched, format, "braintrust/auto"); + // The patch changes source positions before the transformer sees the file. + return result ? { code: result.code } : { code: patched }; + } if (!packageInfo?.version) return null; const transformer = matcher.getTransformer( @@ -44,5 +54,7 @@ export function transformBundledModule( packageInfo.version, modulePath, ); - return transformer ? transformer.transform(code, format) : null; + return transformer + ? transformer.transform(code, format, "braintrust/auto") + : null; } diff --git a/js/src/auto-instrumentations/bundler/vite.ts b/js/src/auto-instrumentations/bundler/vite.ts index f41997efb..4661fb2da 100644 --- a/js/src/auto-instrumentations/bundler/vite.ts +++ b/js/src/auto-instrumentations/bundler/vite.ts @@ -57,10 +57,3 @@ export function braintrustVitePlugin( ...(Array.isArray(transformPlugin) ? transformPlugin : [transformPlugin]), ]; } - -export type VitePluginOptions = BundlerPluginOptions; - -/** - * @deprecated Use {@link braintrustVitePlugin} instead. - */ -export const vitePlugin = unplugin.vite; diff --git a/js/src/auto-instrumentations/bundler/webpack-loader.ts b/js/src/auto-instrumentations/bundler/webpack-loader.ts index 67e1c3484..78d456170 100644 --- a/js/src/auto-instrumentations/bundler/webpack-loader.ts +++ b/js/src/auto-instrumentations/bundler/webpack-loader.ts @@ -4,21 +4,8 @@ * This is a webpack loader (not a plugin) for compatibility with tools that only support loaders, * such as Next.js Turbopack. * - * Usage in next.config.js / next.config.ts: - * ```javascript - * const nextConfig: NextConfig = { - * turbopack: { - * rules: { - * // Apply the loader to all JS/MJS/CJS files from node_modules. - * // condition: "foreign" restricts the rule to third-party packages only. - * "*.{js,mjs,cjs}": { - * condition: "foreign", - * loaders: [{ loader: require.resolve("braintrust/webpack-loader") }], - * }, - * }, - * }, - * }; - * ``` + * The `braintrust/next` entrypoint resolves this implementation directly when + * configuring Turbopack; it is not a user-facing package entrypoint. */ import { debugLogger } from "../../debug-logger"; @@ -75,7 +62,7 @@ function codeTransformerLoader( code, resourcePath, getMatcher(), - options.browser ?? options.useDiagnosticChannelCompatShim ?? false, + options.browser ?? false, ); if (result) { callback(null, result.code, result.map ?? undefined); diff --git a/js/src/auto-instrumentations/bundler/webpack.ts b/js/src/auto-instrumentations/bundler/webpack.ts index 89a1c1802..994666759 100644 --- a/js/src/auto-instrumentations/bundler/webpack.ts +++ b/js/src/auto-instrumentations/bundler/webpack.ts @@ -6,10 +6,3 @@ export function braintrustWebpackPlugin( ): WebpackPluginInstance { return unplugin.webpack(options); } - -export type WebpackPluginOptions = BundlerPluginOptions; - -/** - * @deprecated Use {@link braintrustWebpackPlugin} instead. - */ -export const webpackPlugin = unplugin.webpack; diff --git a/js/src/auto-instrumentations/configs/ai-sdk.ts b/js/src/auto-instrumentations/configs/ai-sdk.ts index 2d75ccd7c..694031e23 100644 --- a/js/src/auto-instrumentations/configs/ai-sdk.ts +++ b/js/src/auto-instrumentations/configs/ai-sdk.ts @@ -3,7 +3,7 @@ import { aiSDKChannels, codeModeChannels, harnessAgentChannels, -} from "../../instrumentation/plugins/ai-sdk-interceptors"; +} from "../../instrumentation/providers/ai-sdk-interceptors"; /** * Instrumentation configurations for the Vercel AI SDK. @@ -74,29 +74,25 @@ export const aiSDKConfigs: InstrumentationConfig[] = [ }, })), - // generateText - async function - { - channelName: aiSDKChannels.generateText.channelName, - module: { - name: "ai", - versionRange: ">=3.0.0 <7.0.0", - filePath: "dist/index.mjs", - }, - functionQuery: { - functionName: "generateText", - }, - }, - { - channelName: aiSDKChannels.generateText.channelName, - module: { - name: "ai", - versionRange: ">=3.0.0 <7.0.0", - filePath: "dist/index.js", - }, - functionQuery: { - functionName: "generateText", - }, - }, + // Async generation and embedding functions supported by AI SDK v4-v6. + ...[ + ["generateText", aiSDKChannels.generateText.channelName], + ["generateObject", aiSDKChannels.generateObject.channelName], + ["embed", aiSDKChannels.embed.channelName], + ["embedMany", aiSDKChannels.embedMany.channelName], + ].flatMap(([functionName, channelName]) => + ["dist/index.mjs", "dist/index.js"].map((filePath) => ({ + channelName, + module: { + name: "ai", + versionRange: ">=4.0.0 <7.0.0", + filePath, + }, + functionQuery: { + functionName, + }, + })), + ), // generateImage - stable in v6+, experimental alias in v5 ...[ @@ -125,125 +121,23 @@ export const aiSDKConfigs: InstrumentationConfig[] = [ })), ), - // streamText - async function (v3 only, before the sync refactor in v4) - { - channelName: aiSDKChannels.streamText.channelName, - module: { - name: "ai", - versionRange: ">=3.0.0 <4.0.0", - filePath: "dist/index.mjs", - }, - functionQuery: { - functionName: "streamText", - }, - }, - - // streamText - sync function returning stream (v4+) - { - channelName: aiSDKChannels.streamTextSync.channelName, - module: { - name: "ai", - versionRange: ">=4.0.0 <7.0.0", - filePath: "dist/index.mjs", - }, - functionQuery: { - functionName: "streamText", - }, - }, - { - channelName: aiSDKChannels.streamText.channelName, - module: { - name: "ai", - versionRange: ">=3.0.0 <4.0.0", - filePath: "dist/index.js", - }, - functionQuery: { - functionName: "streamText", - }, - }, - { - channelName: aiSDKChannels.streamTextSync.channelName, - module: { - name: "ai", - versionRange: ">=4.0.0 <7.0.0", - filePath: "dist/index.js", - }, - functionQuery: { - functionName: "streamText", - }, - }, - - // generateObject - async function - { - channelName: aiSDKChannels.generateObject.channelName, - module: { - name: "ai", - versionRange: ">=3.0.0 <7.0.0", - filePath: "dist/index.mjs", - }, - functionQuery: { - functionName: "generateObject", - }, - }, - { - channelName: aiSDKChannels.generateObject.channelName, - module: { - name: "ai", - versionRange: ">=3.0.0 <7.0.0", - filePath: "dist/index.js", - }, - functionQuery: { - functionName: "generateObject", - }, - }, - - // embed - async function - { - channelName: aiSDKChannels.embed.channelName, - module: { - name: "ai", - versionRange: ">=3.0.0 <7.0.0", - filePath: "dist/index.mjs", - }, - functionQuery: { - functionName: "embed", - }, - }, - { - channelName: aiSDKChannels.embed.channelName, - module: { - name: "ai", - versionRange: ">=3.0.0 <7.0.0", - filePath: "dist/index.js", - }, - functionQuery: { - functionName: "embed", - }, - }, - - // embedMany - async function - { - channelName: aiSDKChannels.embedMany.channelName, - module: { - name: "ai", - versionRange: ">=3.0.0 <7.0.0", - filePath: "dist/index.mjs", - }, - functionQuery: { - functionName: "embedMany", - }, - }, - { - channelName: aiSDKChannels.embedMany.channelName, - module: { - name: "ai", - versionRange: ">=3.0.0 <7.0.0", - filePath: "dist/index.js", - }, - functionQuery: { - functionName: "embedMany", - }, - }, + // Streaming functions are synchronous starting in AI SDK v4. + ...[ + ["streamText", aiSDKChannels.streamTextSync.channelName], + ["streamObject", aiSDKChannels.streamObjectSync.channelName], + ].flatMap(([functionName, channelName]) => + ["dist/index.mjs", "dist/index.js"].map((filePath) => ({ + channelName, + module: { + name: "ai", + versionRange: ">=4.0.0 <7.0.0", + filePath, + }, + functionQuery: { + functionName, + }, + })), + ), // rerank - async function { @@ -270,7 +164,7 @@ export const aiSDKConfigs: InstrumentationConfig[] = [ }, // AI SDK v7 exposes its telemetry lifecycle through a dispatcher created for - // each operation. We patch that dispatcher in the plugin instead of rewriting + // each operation. We patch that dispatcher in the consumer instead of rewriting // the module to call registerTelemetry(). { channelName: aiSDKChannels.v7CreateTelemetryDispatcher.channelName, @@ -295,54 +189,6 @@ export const aiSDKConfigs: InstrumentationConfig[] = [ }, }, - // streamObject - async function (v3 only, before the sync refactor in v4) - { - channelName: aiSDKChannels.streamObject.channelName, - module: { - name: "ai", - versionRange: ">=3.0.0 <4.0.0", - filePath: "dist/index.mjs", - }, - functionQuery: { - functionName: "streamObject", - }, - }, - - // streamObject - sync function returning stream (v4+) - { - channelName: aiSDKChannels.streamObjectSync.channelName, - module: { - name: "ai", - versionRange: ">=4.0.0 <7.0.0", - filePath: "dist/index.mjs", - }, - functionQuery: { - functionName: "streamObject", - }, - }, - { - channelName: aiSDKChannels.streamObject.channelName, - module: { - name: "ai", - versionRange: ">=3.0.0 <4.0.0", - filePath: "dist/index.js", - }, - functionQuery: { - functionName: "streamObject", - }, - }, - { - channelName: aiSDKChannels.streamObjectSync.channelName, - module: { - name: "ai", - versionRange: ">=4.0.0 <7.0.0", - filePath: "dist/index.js", - }, - functionQuery: { - functionName: "streamObject", - }, - }, - // Agent.generate - async method (v5 only) // The compiled AI SDK bundle emits this as an anonymous class method, so we // target the first async `generate` method in the file instead of a class name. diff --git a/js/src/auto-instrumentations/configs/all.ts b/js/src/auto-instrumentations/configs/all.ts index 751e0b8ac..fb7045852 100644 --- a/js/src/auto-instrumentations/configs/all.ts +++ b/js/src/auto-instrumentations/configs/all.ts @@ -13,7 +13,6 @@ import { cloudflareAgentsConfigs } from "./cloudflare-agents"; import { cloudflareThinkConfigs } from "./cloudflare-think"; import { cohereConfigs } from "./cohere"; import { cursorSDKConfigs } from "./cursor-sdk"; -import { flueConfigs } from "./flue"; import { genkitConfigs } from "./genkit"; import { gitHubCopilotConfigs } from "./github-copilot"; import { googleADKConfigs } from "./google-adk"; @@ -54,11 +53,11 @@ const defaultInstrumentationConfigGroups: readonly InstrumentationConfigGroup[] }, { integrations: ["anthropic"], configs: anthropicConfigs }, { - integrations: ["bedrock", "awsBedrock", "awsBedrockRuntime"], + integrations: ["awsBedrockRuntime"], configs: bedrockRuntimeConfigs, }, { - integrations: ["aisdk", "vercel"], + integrations: ["aisdk"], configs: aiSDKConfigs, }, { @@ -77,7 +76,7 @@ const defaultInstrumentationConfigGroups: readonly InstrumentationConfigGroup[] integrations: ["cloudflareThink"], configs: cloudflareThinkConfigs, }, - { integrations: ["cursor", "cursorSDK"], configs: cursorSDKConfigs }, + { integrations: ["cursorSDK"], configs: cursorSDKConfigs }, { integrations: ["openAIAgents"], configs: openAIAgentsCoreConfigs, @@ -87,7 +86,7 @@ const defaultInstrumentationConfigGroups: readonly InstrumentationConfigGroup[] configs: googleGenerativeAIConfigs, }, { - integrations: ["google", "googleGenAI"], + integrations: ["googleGenAI"], configs: googleGenAIConfigs, }, { @@ -130,10 +129,6 @@ const defaultInstrumentationConfigGroups: readonly InstrumentationConfigGroup[] integrations: ["livekit"], configs: livekitConfigs, }, - { - integrations: ["flue"], - configs: flueConfigs, - }, { integrations: ["elevenlabs"], configs: elevenLabsConfigs, diff --git a/js/src/auto-instrumentations/configs/anthropic.ts b/js/src/auto-instrumentations/configs/anthropic.ts index b5b391993..0d57189d5 100644 --- a/js/src/auto-instrumentations/configs/anthropic.ts +++ b/js/src/auto-instrumentations/configs/anthropic.ts @@ -1,5 +1,5 @@ import type { InstrumentationConfig } from "../orchestrion-js"; -import { anthropicChannels } from "../../instrumentation/plugins/anthropic-interceptors"; +import { anthropicChannels } from "../../instrumentation/providers/anthropic-interceptors"; /** * Instrumentation configurations for the Anthropic SDK. @@ -125,33 +125,28 @@ export const anthropicConfigs: InstrumentationConfig[] = [ // Managed Agents Sessions event streams are discovered automatically, but // remain passive until the user calls collectAnthropicSession() on the // returned stream. - ...["mjs", "js"].map( - (extension): InstrumentationConfig => ({ - channelName: anthropicChannels.betaSessionsEventsStream.channelName, - module: { - name: "@anthropic-ai/sdk", - versionRange: ">=0.86.0", - filePath: `resources/beta/sessions/events.${extension}`, - }, - functionQuery: { - className: "Events", - methodName: "stream", - }, - }), - ), - ...["mjs", "js"].map( - (extension): InstrumentationConfig => ({ - channelName: - anthropicChannels.betaSessionsThreadsEventsStream.channelName, - module: { - name: "@anthropic-ai/sdk", - versionRange: ">=0.86.0", - filePath: `resources/beta/sessions/threads/events.${extension}`, - }, - functionQuery: { - className: "Events", - methodName: "stream", - }, - }), - ), + ...["mjs", "js"].map((extension): InstrumentationConfig => ({ + channelName: anthropicChannels.betaSessionsEventsStream.channelName, + module: { + name: "@anthropic-ai/sdk", + versionRange: ">=0.86.0", + filePath: `resources/beta/sessions/events.${extension}`, + }, + functionQuery: { + className: "Events", + methodName: "stream", + }, + })), + ...["mjs", "js"].map((extension): InstrumentationConfig => ({ + channelName: anthropicChannels.betaSessionsThreadsEventsStream.channelName, + module: { + name: "@anthropic-ai/sdk", + versionRange: ">=0.86.0", + filePath: `resources/beta/sessions/threads/events.${extension}`, + }, + functionQuery: { + className: "Events", + methodName: "stream", + }, + })), ]; diff --git a/js/src/auto-instrumentations/configs/bedrock-runtime.test.ts b/js/src/auto-instrumentations/configs/bedrock-runtime.test.ts index a786a0f69..bf8e2d96b 100644 --- a/js/src/auto-instrumentations/configs/bedrock-runtime.test.ts +++ b/js/src/auto-instrumentations/configs/bedrock-runtime.test.ts @@ -1,5 +1,5 @@ import { InstrumentationMatcher, type ModuleType } from "../orchestrion-js"; -import { describe, expect, it } from "vitest"; +import { describe, expect, it } from "vite-plus/test"; import { bedrockRuntimeConfigs } from "./bedrock-runtime"; const CJS_CLIENT_SOURCE = ` diff --git a/js/src/auto-instrumentations/configs/bedrock-runtime.ts b/js/src/auto-instrumentations/configs/bedrock-runtime.ts index fb5f0c904..5ee261101 100644 --- a/js/src/auto-instrumentations/configs/bedrock-runtime.ts +++ b/js/src/auto-instrumentations/configs/bedrock-runtime.ts @@ -2,7 +2,7 @@ import type { InstrumentationConfig } from "../orchestrion-js"; import { smithyClientChannels, smithyCoreChannels, -} from "../../instrumentation/plugins/bedrock-runtime-interceptors"; +} from "../../instrumentation/providers/bedrock-runtime-interceptors"; export const bedrockRuntimeConfigs: InstrumentationConfig[] = [ { diff --git a/js/src/auto-instrumentations/configs/claude-agent-sdk.ts b/js/src/auto-instrumentations/configs/claude-agent-sdk.ts index 26abbfaf4..8b859dc25 100644 --- a/js/src/auto-instrumentations/configs/claude-agent-sdk.ts +++ b/js/src/auto-instrumentations/configs/claude-agent-sdk.ts @@ -1,5 +1,5 @@ import type { InstrumentationConfig } from "../orchestrion-js"; -import { claudeAgentSDKChannels } from "../../instrumentation/plugins/claude-agent-sdk-interceptors"; +import { claudeAgentSDKChannels } from "../../instrumentation/providers/claude-agent-sdk-interceptors"; /** * Instrumentation configuration for the Claude Agent SDK. diff --git a/js/src/auto-instrumentations/configs/cloudflare-agents.ts b/js/src/auto-instrumentations/configs/cloudflare-agents.ts index 2d63c4eb9..982a28888 100644 --- a/js/src/auto-instrumentations/configs/cloudflare-agents.ts +++ b/js/src/auto-instrumentations/configs/cloudflare-agents.ts @@ -1,5 +1,5 @@ import type { InstrumentationConfig } from "../orchestrion-js"; -import { cloudflareAgentsChannels } from "../../instrumentation/plugins/cloudflare-agents-interceptors"; +import { cloudflareAgentsChannels } from "../../instrumentation/providers/cloudflare-agents-interceptors"; const cloudflareAgentsVersionRange = ">=0.17.0 <0.18.0"; diff --git a/js/src/auto-instrumentations/configs/cloudflare-ai-chat.ts b/js/src/auto-instrumentations/configs/cloudflare-ai-chat.ts index c16442406..4e4e7d650 100644 --- a/js/src/auto-instrumentations/configs/cloudflare-ai-chat.ts +++ b/js/src/auto-instrumentations/configs/cloudflare-ai-chat.ts @@ -1,5 +1,5 @@ import type { InstrumentationConfig } from "../orchestrion-js"; -import { cloudflareAIChatChannels } from "../../instrumentation/plugins/cloudflare-ai-chat-interceptors"; +import { cloudflareAIChatChannels } from "../../instrumentation/providers/cloudflare-ai-chat-interceptors"; const cloudflareAIChatVersionRange = ">=0.9.0 <0.10.0"; diff --git a/js/src/auto-instrumentations/configs/cloudflare-think.test.ts b/js/src/auto-instrumentations/configs/cloudflare-think.test.ts index e60876819..b04f8ae12 100644 --- a/js/src/auto-instrumentations/configs/cloudflare-think.test.ts +++ b/js/src/auto-instrumentations/configs/cloudflare-think.test.ts @@ -1,5 +1,5 @@ import { InstrumentationMatcher } from "../orchestrion-js"; -import { describe, expect, it } from "vitest"; +import { describe, expect, it } from "vite-plus/test"; import { cloudflareThinkConfigs } from "./cloudflare-think"; describe("Cloudflare Think transformation", () => { diff --git a/js/src/auto-instrumentations/configs/cloudflare-think.ts b/js/src/auto-instrumentations/configs/cloudflare-think.ts index 9a1e76753..e6ba81a06 100644 --- a/js/src/auto-instrumentations/configs/cloudflare-think.ts +++ b/js/src/auto-instrumentations/configs/cloudflare-think.ts @@ -1,5 +1,5 @@ import type { InstrumentationConfig } from "../orchestrion-js"; -import { cloudflareThinkChannels } from "../../instrumentation/plugins/cloudflare-think-interceptors"; +import { cloudflareThinkChannels } from "../../instrumentation/providers/cloudflare-think-interceptors"; const cloudflareThinkVersionRange = ">=0.13.0 <0.21.0"; diff --git a/js/src/auto-instrumentations/configs/cohere.ts b/js/src/auto-instrumentations/configs/cohere.ts index f7472dcc9..2eeb1e1b1 100644 --- a/js/src/auto-instrumentations/configs/cohere.ts +++ b/js/src/auto-instrumentations/configs/cohere.ts @@ -1,5 +1,5 @@ import type { InstrumentationConfig } from "../orchestrion-js"; -import { cohereChannels } from "../../instrumentation/plugins/cohere-interceptors"; +import { cohereChannels } from "../../instrumentation/providers/cohere-interceptors"; // Keep major-version ranges separate to preserve prerelease matching. export const cohereConfigs: InstrumentationConfig[] = ( diff --git a/js/src/auto-instrumentations/configs/cursor-sdk.ts b/js/src/auto-instrumentations/configs/cursor-sdk.ts index 65d8d12ed..80e6d59f1 100644 --- a/js/src/auto-instrumentations/configs/cursor-sdk.ts +++ b/js/src/auto-instrumentations/configs/cursor-sdk.ts @@ -1,5 +1,5 @@ import type { InstrumentationConfig } from "../orchestrion-js"; -import { cursorSDKChannels } from "../../instrumentation/plugins/cursor-sdk-interceptors"; +import { cursorSDKChannels } from "../../instrumentation/providers/cursor-sdk-interceptors"; const cursorSDKVersionRange = ">=1.0.7 <2.0.0"; diff --git a/js/src/auto-instrumentations/configs/elevenlabs.ts b/js/src/auto-instrumentations/configs/elevenlabs.ts index 9b114fae9..44e7f1d7f 100644 --- a/js/src/auto-instrumentations/configs/elevenlabs.ts +++ b/js/src/auto-instrumentations/configs/elevenlabs.ts @@ -1,5 +1,5 @@ import type { InstrumentationConfig } from "../orchestrion-js"; -import { elevenLabsChannels } from "../../instrumentation/plugins/elevenlabs-interceptors"; +import { elevenLabsChannels } from "../../instrumentation/providers/elevenlabs-interceptors"; export const elevenLabsConfigs: InstrumentationConfig[] = [ ...( diff --git a/js/src/auto-instrumentations/configs/flue.test.ts b/js/src/auto-instrumentations/configs/flue.test.ts deleted file mode 100644 index f480b8911..000000000 --- a/js/src/auto-instrumentations/configs/flue.test.ts +++ /dev/null @@ -1,63 +0,0 @@ -import { InstrumentationMatcher } from "../orchestrion-js"; -import { describe, expect, it } from "vitest"; -import { flueConfigs, flueVersionRange } from "./flue"; - -describe("flue auto-instrumentation configs", () => { - it("targets the stable Flue 0.8 context factory", () => { - const matcher = new InstrumentationMatcher(flueConfigs); - const flue08Transformer = matcher.getTransformer( - "@flue/runtime", - "0.8.0", - "dist/internal.mjs", - ); - const flue10BetaTransformer = matcher.getTransformer( - "@flue/runtime", - "1.0.0-beta.3", - "dist/internal.mjs", - ); - const flue10Transformer = matcher.getTransformer( - "@flue/runtime", - "1.0.0", - "dist/internal.mjs", - ); - - expect(flueVersionRange).toBe(">=0.8.0 <1.0.0"); - expect(flue08Transformer).toBeDefined(); - expect(flue10BetaTransformer).toBeUndefined(); - expect(flue10Transformer).toBeUndefined(); - const transformed = flue08Transformer!.transform( - ` -function createFlueContext(config) { - return { config }; -} -`, - "esm", - ).code; - - expect(transformed).toContain( - "orchestrion:@flue/runtime:createFlueContext", - ); - }); - - it("does not target Flue content-hashed workflow or tool chunks", () => { - const matcher = new InstrumentationMatcher(flueConfigs); - - expect( - matcher.getTransformer( - "@flue/runtime", - "0.8.0", - "dist/handle-agent-DcUclCE2.mjs", - ), - ).toBeUndefined(); - expect( - matcher.getTransformer( - "@flue/runtime", - "0.8.0", - "dist/sandbox-DNEJXjr_.mjs", - ), - ).toBeUndefined(); - expect( - matcher.getTransformer("@flue/runtime", "1.0.0-beta.3", "dist/index.mjs"), - ).toBeUndefined(); - }); -}); diff --git a/js/src/auto-instrumentations/configs/flue.ts b/js/src/auto-instrumentations/configs/flue.ts deleted file mode 100644 index 73e1a1d7d..000000000 --- a/js/src/auto-instrumentations/configs/flue.ts +++ /dev/null @@ -1,18 +0,0 @@ -import type { InstrumentationConfig } from "../orchestrion-js"; -import { flueChannels } from "../../instrumentation/plugins/flue-interceptors"; - -export const flueVersionRange = ">=0.8.0 <1.0.0"; - -export const flueConfigs: InstrumentationConfig[] = [ - { - channelName: flueChannels.createContext.channelName, - module: { - name: "@flue/runtime", - versionRange: flueVersionRange, - filePath: "dist/internal.mjs", - }, - functionQuery: { - functionName: "createFlueContext", - }, - }, -]; diff --git a/js/src/auto-instrumentations/configs/genkit.ts b/js/src/auto-instrumentations/configs/genkit.ts index 2668fbeff..fb82efaa1 100644 --- a/js/src/auto-instrumentations/configs/genkit.ts +++ b/js/src/auto-instrumentations/configs/genkit.ts @@ -2,7 +2,7 @@ import type { InstrumentationConfig } from "../orchestrion-js"; import { genkitChannels, genkitCoreChannels, -} from "../../instrumentation/plugins/genkit-interceptors"; +} from "../../instrumentation/providers/genkit-interceptors"; const genkitVersionRange = ">=1.0.0 <2.0.0"; diff --git a/js/src/auto-instrumentations/configs/github-copilot.ts b/js/src/auto-instrumentations/configs/github-copilot.ts index c17796bc5..17de82869 100644 --- a/js/src/auto-instrumentations/configs/github-copilot.ts +++ b/js/src/auto-instrumentations/configs/github-copilot.ts @@ -1,5 +1,5 @@ import type { InstrumentationConfig } from "../orchestrion-js"; -import { gitHubCopilotChannels } from "../../instrumentation/plugins/github-copilot-interceptors"; +import { gitHubCopilotChannels } from "../../instrumentation/providers/github-copilot-interceptors"; export const gitHubCopilotConfigs: InstrumentationConfig[] = [ // ESM: CopilotClient.createSession diff --git a/js/src/auto-instrumentations/configs/google-adk.ts b/js/src/auto-instrumentations/configs/google-adk.ts index c783011e6..39e008ebe 100644 --- a/js/src/auto-instrumentations/configs/google-adk.ts +++ b/js/src/auto-instrumentations/configs/google-adk.ts @@ -1,5 +1,5 @@ import type { InstrumentationConfig } from "../orchestrion-js"; -import { googleADKChannels } from "../../instrumentation/plugins/google-adk-interceptors"; +import { googleADKChannels } from "../../instrumentation/providers/google-adk-interceptors"; const googleADKVersionRange = ">=0.1.0"; const googleADKBundledIndexV06VersionRange = ">=0.6.1 <0.7.0"; diff --git a/js/src/auto-instrumentations/configs/google-genai.ts b/js/src/auto-instrumentations/configs/google-genai.ts index 4941eb5cf..2066999ef 100644 --- a/js/src/auto-instrumentations/configs/google-genai.ts +++ b/js/src/auto-instrumentations/configs/google-genai.ts @@ -1,5 +1,5 @@ import type { InstrumentationConfig } from "../orchestrion-js"; -import { googleGenAIChannels } from "../../instrumentation/plugins/google-genai-interceptors"; +import { googleGenAIChannels } from "../../instrumentation/providers/google-genai-interceptors"; /** * Instrumentation configurations for the Google GenAI SDK. diff --git a/js/src/auto-instrumentations/configs/google-generative-ai.ts b/js/src/auto-instrumentations/configs/google-generative-ai.ts index 4d1b5ffc7..e33569abc 100644 --- a/js/src/auto-instrumentations/configs/google-generative-ai.ts +++ b/js/src/auto-instrumentations/configs/google-generative-ai.ts @@ -1,5 +1,5 @@ import type { InstrumentationConfig } from "../orchestrion-js"; -import { googleGenerativeAIChannels } from "../../instrumentation/plugins/google-generative-ai-interceptors"; +import { googleGenerativeAIChannels } from "../../instrumentation/providers/google-generative-ai-interceptors"; export const googleGenerativeAIConfigs: InstrumentationConfig[] = [ "dist/index.js", diff --git a/js/src/auto-instrumentations/configs/groq.ts b/js/src/auto-instrumentations/configs/groq.ts index cbdfd3855..7527a7b9d 100644 --- a/js/src/auto-instrumentations/configs/groq.ts +++ b/js/src/auto-instrumentations/configs/groq.ts @@ -1,5 +1,5 @@ import type { InstrumentationConfig } from "../orchestrion-js"; -import { groqChannels } from "../../instrumentation/plugins/groq-interceptors"; +import { groqChannels } from "../../instrumentation/providers/groq-interceptors"; export const groqConfigs: InstrumentationConfig[] = [ { diff --git a/js/src/auto-instrumentations/configs/huggingface-transformers.ts b/js/src/auto-instrumentations/configs/huggingface-transformers.ts index d5431444f..b2929330b 100644 --- a/js/src/auto-instrumentations/configs/huggingface-transformers.ts +++ b/js/src/auto-instrumentations/configs/huggingface-transformers.ts @@ -1,5 +1,5 @@ import type { InstrumentationConfig } from "../orchestrion-js"; -import { huggingFaceTransformersChannels } from "../../instrumentation/plugins/huggingface-transformers-interceptors"; +import { huggingFaceTransformersChannels } from "../../instrumentation/providers/huggingface-transformers-interceptors"; const moduleFiles = [ { diff --git a/js/src/auto-instrumentations/configs/huggingface.ts b/js/src/auto-instrumentations/configs/huggingface.ts index 897579a0e..b8239921d 100644 --- a/js/src/auto-instrumentations/configs/huggingface.ts +++ b/js/src/auto-instrumentations/configs/huggingface.ts @@ -1,5 +1,5 @@ import type { InstrumentationConfig } from "../orchestrion-js"; -import { huggingFaceChannels } from "../../instrumentation/plugins/huggingface-interceptors"; +import { huggingFaceChannels } from "../../instrumentation/providers/huggingface-interceptors"; export const huggingFaceConfigs: InstrumentationConfig[] = [ { diff --git a/js/src/auto-instrumentations/configs/langchain.ts b/js/src/auto-instrumentations/configs/langchain.ts index e1483b8fc..f3d74ecc0 100644 --- a/js/src/auto-instrumentations/configs/langchain.ts +++ b/js/src/auto-instrumentations/configs/langchain.ts @@ -1,5 +1,5 @@ import type { InstrumentationConfig } from "../orchestrion-js"; -import { langChainChannels } from "../../instrumentation/plugins/langchain-interceptors"; +import { langChainChannels } from "../../instrumentation/providers/langchain-interceptors"; const langChainCoreVersionRange = ">=0.3.42"; const langChainCallbackManagerFilePath = "dist/callbacks/manager.js"; diff --git a/js/src/auto-instrumentations/configs/langgraph-sdk.ts b/js/src/auto-instrumentations/configs/langgraph-sdk.ts index cb629186f..7dc22d30a 100644 --- a/js/src/auto-instrumentations/configs/langgraph-sdk.ts +++ b/js/src/auto-instrumentations/configs/langgraph-sdk.ts @@ -1,5 +1,5 @@ import type { InstrumentationConfig } from "../orchestrion-js"; -import { langGraphSDKChannels } from "../../instrumentation/plugins/langgraph-sdk-interceptors"; +import { langGraphSDKChannels } from "../../instrumentation/providers/langgraph-sdk-interceptors"; // These public methods live in shared subclients for both package entrypoints. export const langGraphSDKConfigs: InstrumentationConfig[] = [ diff --git a/js/src/auto-instrumentations/configs/langsmith.ts b/js/src/auto-instrumentations/configs/langsmith.ts index 812b9441d..4129c4279 100644 --- a/js/src/auto-instrumentations/configs/langsmith.ts +++ b/js/src/auto-instrumentations/configs/langsmith.ts @@ -1,5 +1,5 @@ import type { InstrumentationConfig } from "../orchestrion-js"; -import { langSmithChannels } from "../../instrumentation/plugins/langsmith-interceptors"; +import { langSmithChannels } from "../../instrumentation/providers/langsmith-interceptors"; const versionRange = ">=0.3.30 <1.0.0"; diff --git a/js/src/auto-instrumentations/configs/livekit.ts b/js/src/auto-instrumentations/configs/livekit.ts index a73a5bf33..fdcd8ee7a 100644 --- a/js/src/auto-instrumentations/configs/livekit.ts +++ b/js/src/auto-instrumentations/configs/livekit.ts @@ -1,5 +1,5 @@ import type { InstrumentationConfig } from "../orchestrion-js"; -import { livekitChannels } from "../../instrumentation/plugins/livekit-interceptors"; +import { livekitChannels } from "../../instrumentation/providers/livekit-interceptors"; // Private lifecycle hooks: widen only after validating the supported release matrix. const versionRange = ">=1.9.1 <1.10.0"; diff --git a/js/src/auto-instrumentations/configs/mcp.ts b/js/src/auto-instrumentations/configs/mcp.ts index c1aee411c..cab228490 100644 --- a/js/src/auto-instrumentations/configs/mcp.ts +++ b/js/src/auto-instrumentations/configs/mcp.ts @@ -2,7 +2,7 @@ import type { InstrumentationConfig } from "../orchestrion-js"; import { mcpSDKChannels, mcpServerChannels, -} from "../../instrumentation/plugins/mcp-interceptors"; +} from "../../instrumentation/providers/mcp-interceptors"; export const mcpConfigs: InstrumentationConfig[] = ( ["connect", "setRequestHandler"] as const diff --git a/js/src/auto-instrumentations/configs/mistral.ts b/js/src/auto-instrumentations/configs/mistral.ts index 31e32cf9c..7842271d1 100644 --- a/js/src/auto-instrumentations/configs/mistral.ts +++ b/js/src/auto-instrumentations/configs/mistral.ts @@ -1,5 +1,5 @@ import type { InstrumentationConfig } from "../orchestrion-js"; -import { mistralChannels } from "../../instrumentation/plugins/mistral-interceptors"; +import { mistralChannels } from "../../instrumentation/providers/mistral-interceptors"; export const mistralConfigs: InstrumentationConfig[] = [ { diff --git a/js/src/auto-instrumentations/configs/ollama.ts b/js/src/auto-instrumentations/configs/ollama.ts index c2786123b..30845a03c 100644 --- a/js/src/auto-instrumentations/configs/ollama.ts +++ b/js/src/auto-instrumentations/configs/ollama.ts @@ -1,5 +1,5 @@ import type { InstrumentationConfig } from "../orchestrion-js"; -import { ollamaChannels } from "../../instrumentation/plugins/ollama-interceptors"; +import { ollamaChannels } from "../../instrumentation/providers/ollama-interceptors"; const methods = [ ["chat", ollamaChannels.chat.channelName], diff --git a/js/src/auto-instrumentations/configs/openai-agents.ts b/js/src/auto-instrumentations/configs/openai-agents.ts index 7e7368111..172d9b827 100644 --- a/js/src/auto-instrumentations/configs/openai-agents.ts +++ b/js/src/auto-instrumentations/configs/openai-agents.ts @@ -1,5 +1,5 @@ import type { InstrumentationConfig } from "../orchestrion-js"; -import { openAIAgentsCoreChannels } from "../../instrumentation/plugins/openai-agents-interceptors"; +import { openAIAgentsCoreChannels } from "../../instrumentation/providers/openai-agents-interceptors"; const lifecycleMethods = [ ["onTraceStart", openAIAgentsCoreChannels.onTraceStart.channelName], diff --git a/js/src/auto-instrumentations/configs/openai-codex.ts b/js/src/auto-instrumentations/configs/openai-codex.ts index fd2624303..66c8ed428 100644 --- a/js/src/auto-instrumentations/configs/openai-codex.ts +++ b/js/src/auto-instrumentations/configs/openai-codex.ts @@ -1,5 +1,5 @@ import type { InstrumentationConfig } from "../orchestrion-js"; -import { openAICodexChannels } from "../../instrumentation/plugins/openai-codex-interceptors"; +import { openAICodexChannels } from "../../instrumentation/providers/openai-codex-interceptors"; const openAICodexVersionRange = ">=0.128.0 <1.0.0"; diff --git a/js/src/auto-instrumentations/configs/openai.ts b/js/src/auto-instrumentations/configs/openai.ts index 3defeedd8..be14daf33 100644 --- a/js/src/auto-instrumentations/configs/openai.ts +++ b/js/src/auto-instrumentations/configs/openai.ts @@ -1,5 +1,5 @@ import type { InstrumentationConfig } from "../orchestrion-js"; -import { openAIChannels } from "../../instrumentation/plugins/openai-interceptors"; +import { openAIChannels } from "../../instrumentation/providers/openai-interceptors"; /** * Instrumentation configurations for the OpenAI SDK. diff --git a/js/src/auto-instrumentations/configs/openrouter-agent.ts b/js/src/auto-instrumentations/configs/openrouter-agent.ts index 308b08af8..b5a00f5f4 100644 --- a/js/src/auto-instrumentations/configs/openrouter-agent.ts +++ b/js/src/auto-instrumentations/configs/openrouter-agent.ts @@ -1,5 +1,5 @@ import type { InstrumentationConfig } from "../orchestrion-js"; -import { openRouterAgentChannels } from "../../instrumentation/plugins/openrouter-agent-interceptors"; +import { openRouterAgentChannels } from "../../instrumentation/providers/openrouter-agent-interceptors"; export const openRouterAgentConfigs: InstrumentationConfig[] = [ { diff --git a/js/src/auto-instrumentations/configs/openrouter.ts b/js/src/auto-instrumentations/configs/openrouter.ts index 573dd41bb..26a90f22a 100644 --- a/js/src/auto-instrumentations/configs/openrouter.ts +++ b/js/src/auto-instrumentations/configs/openrouter.ts @@ -1,5 +1,5 @@ import type { InstrumentationConfig } from "../orchestrion-js"; -import { openRouterChannels } from "../../instrumentation/plugins/openrouter-interceptors"; +import { openRouterChannels } from "../../instrumentation/providers/openrouter-interceptors"; export const openRouterConfigs: InstrumentationConfig[] = [ { diff --git a/js/src/auto-instrumentations/configs/pi-coding-agent.test.ts b/js/src/auto-instrumentations/configs/pi-coding-agent.test.ts index e5b42c518..04a9cce1e 100644 --- a/js/src/auto-instrumentations/configs/pi-coding-agent.test.ts +++ b/js/src/auto-instrumentations/configs/pi-coding-agent.test.ts @@ -1,4 +1,4 @@ -import { describe, expect, it } from "vitest"; +import { describe, expect, it } from "vite-plus/test"; import { readDisabledInstrumentationEnvConfig } from "../../instrumentation/config"; import { getDefaultInstrumentationConfigs } from "./all"; diff --git a/js/src/auto-instrumentations/configs/pi-coding-agent.ts b/js/src/auto-instrumentations/configs/pi-coding-agent.ts index 79c144cdc..9b8923f4f 100644 --- a/js/src/auto-instrumentations/configs/pi-coding-agent.ts +++ b/js/src/auto-instrumentations/configs/pi-coding-agent.ts @@ -1,5 +1,5 @@ import type { InstrumentationConfig } from "../orchestrion-js"; -import { piCodingAgentChannels } from "../../instrumentation/plugins/pi-coding-agent-interceptors"; +import { piCodingAgentChannels } from "../../instrumentation/providers/pi-coding-agent-interceptors"; const piCodingAgentVersionRange = ">=0.79.0 <2.0.0"; diff --git a/js/src/auto-instrumentations/configs/strands-agent-sdk.test.ts b/js/src/auto-instrumentations/configs/strands-agent-sdk.test.ts index 53c4031e2..6df7783a1 100644 --- a/js/src/auto-instrumentations/configs/strands-agent-sdk.test.ts +++ b/js/src/auto-instrumentations/configs/strands-agent-sdk.test.ts @@ -1,4 +1,4 @@ -import { describe, expect, it } from "vitest"; +import { describe, expect, it } from "vite-plus/test"; import { readDisabledInstrumentationEnvConfig } from "../../instrumentation/config"; import { getDefaultInstrumentationConfigs } from "./all"; import { strandsAgentSDKConfigs } from "./strands-agent-sdk"; diff --git a/js/src/auto-instrumentations/configs/strands-agent-sdk.ts b/js/src/auto-instrumentations/configs/strands-agent-sdk.ts index a0eb9611b..78bf17e78 100644 --- a/js/src/auto-instrumentations/configs/strands-agent-sdk.ts +++ b/js/src/auto-instrumentations/configs/strands-agent-sdk.ts @@ -1,5 +1,5 @@ import type { InstrumentationConfig } from "../orchestrion-js"; -import { strandsAgentSDKChannels } from "../../instrumentation/plugins/strands-agent-sdk-interceptors"; +import { strandsAgentSDKChannels } from "../../instrumentation/providers/strands-agent-sdk-interceptors"; const strandsAgentSDKVersionRange = ">=1.0.0 <2.0.0"; diff --git a/js/src/auto-instrumentations/configs/typesafe.ts b/js/src/auto-instrumentations/configs/typesafe.ts index 4248b7a7f..b950f7e2b 100644 --- a/js/src/auto-instrumentations/configs/typesafe.ts +++ b/js/src/auto-instrumentations/configs/typesafe.ts @@ -1,5 +1,5 @@ import type { InstrumentationConfig } from "../orchestrion-js"; -import { typeSafeChannels } from "../../instrumentation/plugins/typesafe-interceptors"; +import { typeSafeChannels } from "../../instrumentation/providers/typesafe-interceptors"; export const typeSafeConfigs: InstrumentationConfig[] = [ "dist/index.mjs", diff --git a/js/src/auto-instrumentations/configs/voyageai.ts b/js/src/auto-instrumentations/configs/voyageai.ts index 3c67d8df3..ba41f3b8a 100644 --- a/js/src/auto-instrumentations/configs/voyageai.ts +++ b/js/src/auto-instrumentations/configs/voyageai.ts @@ -1,5 +1,5 @@ import type { InstrumentationConfig } from "../orchestrion-js"; -import { voyageAIChannels } from "../../instrumentation/plugins/voyageai-interceptors"; +import { voyageAIChannels } from "../../instrumentation/providers/voyageai-interceptors"; const GENERATED_CLIENT_MODULES = [ "dist/cjs/Client.js", diff --git a/js/src/auto-instrumentations/hook.mts b/js/src/auto-instrumentations/hook.mts index 67cd139ea..a1edaf793 100644 --- a/js/src/auto-instrumentations/hook.mts +++ b/js/src/auto-instrumentations/hook.mts @@ -1,3 +1,4 @@ +import "../auto.js"; /** * Unified loader hook for auto-instrumentation (ESM + CJS). * diff --git a/js/src/auto-instrumentations/import-in-the-middle/package.json b/js/src/auto-instrumentations/import-in-the-middle/package.json new file mode 100644 index 000000000..0fef86e33 --- /dev/null +++ b/js/src/auto-instrumentations/import-in-the-middle/package.json @@ -0,0 +1,4 @@ +{ + "private": true, + "type": "commonjs" +} diff --git a/js/src/auto-instrumentations/loader/get-package-info.test.ts b/js/src/auto-instrumentations/loader/get-package-info.test.ts index c7bbf8b6c..b0573e7e9 100644 --- a/js/src/auto-instrumentations/loader/get-package-info.test.ts +++ b/js/src/auto-instrumentations/loader/get-package-info.test.ts @@ -1,4 +1,4 @@ -import { afterEach, expect, it } from "vitest"; +import { afterEach, expect, it } from "vite-plus/test"; import { mkdtempSync, mkdirSync, diff --git a/js/src/auto-instrumentations/loader/mastra-observability-patch.test.ts b/js/src/auto-instrumentations/loader/mastra-observability-patch.test.ts index 60ee5aac7..389ae0568 100644 --- a/js/src/auto-instrumentations/loader/mastra-observability-patch.test.ts +++ b/js/src/auto-instrumentations/loader/mastra-observability-patch.test.ts @@ -1,4 +1,4 @@ -import { describe, expect, it } from "vitest"; +import { describe, expect, it } from "vite-plus/test"; import { classifyMastraTarget, patchMastraSource, diff --git a/js/src/auto-instrumentations/loader/module-hooks.test.ts b/js/src/auto-instrumentations/loader/module-hooks.test.ts index 6d88372fa..af10f5cd0 100644 --- a/js/src/auto-instrumentations/loader/module-hooks.test.ts +++ b/js/src/auto-instrumentations/loader/module-hooks.test.ts @@ -1,4 +1,4 @@ -import { afterEach, beforeEach, expect, it } from "vitest"; +import { afterEach, beforeEach, expect, it } from "vite-plus/test"; import { mkdtempSync, mkdirSync, rmSync, writeFileSync } from "node:fs"; import { tmpdir } from "node:os"; import { join } from "node:path"; diff --git a/js/src/auto-instrumentations/loader/special-case-patches.ts b/js/src/auto-instrumentations/loader/special-case-patches.ts index e28c16b8c..d13652e19 100644 --- a/js/src/auto-instrumentations/loader/special-case-patches.ts +++ b/js/src/auto-instrumentations/loader/special-case-patches.ts @@ -6,8 +6,8 @@ * Every entry in this file represents a target SDK that doesn't expose a * stable extension point we can hook through global instrumentation hooks + the * internal Orchestrion matcher. New integrations should - * **prefer the standard channel-handler / `BasePlugin` pattern** used by - * every other integration in `js/src/instrumentation/plugins/*-plugin.ts`. + * **prefer the standard channel-consumer pattern** used by the integrations in + * `js/src/instrumentation/providers/*-plugin.ts`. * Only add an entry here when the target SDK gives us no other option (e.g. * the SDK relies on side-effectful module initialization, exposes its API * exclusively as re-exports from content-hashed chunks, or otherwise can't diff --git a/js/src/auto-instrumentations/orchestrion-js/matcher.test.ts b/js/src/auto-instrumentations/orchestrion-js/matcher.test.ts index 997b36144..ecd8da597 100644 --- a/js/src/auto-instrumentations/orchestrion-js/matcher.test.ts +++ b/js/src/auto-instrumentations/orchestrion-js/matcher.test.ts @@ -1,4 +1,4 @@ -import { describe, expect, it } from "vitest"; +import { describe, expect, it } from "vite-plus/test"; import { InstrumentationMatcher } from "./matcher"; describe("InstrumentationMatcher", () => { diff --git a/js/src/auto-instrumentations/orchestrion-js/transformer.ts b/js/src/auto-instrumentations/orchestrion-js/transformer.ts index d05ee0085..6736a0acf 100644 --- a/js/src/auto-instrumentations/orchestrion-js/transformer.ts +++ b/js/src/auto-instrumentations/orchestrion-js/transformer.ts @@ -45,8 +45,12 @@ export class Transformer { * Instruments `code` by injecting global invocation hooks around the * target functions defined by this transformer's configs. */ - transform(code: string, moduleType: ModuleType): TransformOutput { - if (!code || this.configs.length === 0) { + transform( + code: string, + moduleType: ModuleType, + runtimeImport?: string, + ): TransformOutput { + if (!code || (this.configs.length === 0 && !runtimeImport)) { return { code }; } @@ -95,7 +99,7 @@ export class Transformer { }); } - if (injectionCount === 0) { + if (injectionCount === 0 && this.configs.length > 0) { const names = this.configs.map(({ functionQuery }) => { const resolvedQuery = this.resolveExportAlias(functionQuery, aliases); const originalName = this.functionQueryLabel(functionQuery); @@ -115,6 +119,31 @@ export class Transformer { ); } + if (runtimeImport) { + // Bundlers need the consumers in the application, whereas Node hooks + // register them in their preload. Insert into the AST to preserve both + // directive prologues (especially CJS strict mode) and source mappings. + ast ??= parse(code, { + loc: true, + ranges: true, + raw: true, + module: moduleType === "esm", + }); + const statement = + moduleType === "esm" + ? `import ${JSON.stringify(runtimeImport)};` + : `require(${JSON.stringify(runtimeImport)});`; + const initialization = parse(statement, { module: moduleType === "esm" }); + let index = 0; + while ( + ast.body[index]?.type === "ExpressionStatement" && + typeof ast.body[index].expression.value === "string" + ) { + index++; + } + ast.body.splice(index, 0, ...initialization.body); + } + const sourceMap = new SourceMapGenerator({ file: `${this.moduleName}/${this.filePath}`, }); diff --git a/js/src/auto-instrumentations/orchestrion-js/transforms.test.ts b/js/src/auto-instrumentations/orchestrion-js/transforms.test.ts index 67b6bcdf7..abb22d01e 100644 --- a/js/src/auto-instrumentations/orchestrion-js/transforms.test.ts +++ b/js/src/auto-instrumentations/orchestrion-js/transforms.test.ts @@ -1,4 +1,4 @@ -import { describe, expect, it } from "vitest"; +import { describe, expect, it } from "vite-plus/test"; import { newGlobalInvocationHook } from "../../global-instrumentation-hooks"; import { Transformer } from "./transformer"; diff --git a/js/src/auto-instrumentations/require-in-the-middle/package.json b/js/src/auto-instrumentations/require-in-the-middle/package.json new file mode 100644 index 000000000..0fef86e33 --- /dev/null +++ b/js/src/auto-instrumentations/require-in-the-middle/package.json @@ -0,0 +1,4 @@ +{ + "private": true, + "type": "commonjs" +} diff --git a/js/src/auto.ts b/js/src/auto.ts new file mode 100644 index 000000000..950dc59e2 --- /dev/null +++ b/js/src/auto.ts @@ -0,0 +1,6 @@ +import { registerInstrumentationConsumers } from "./instrumentation/instrumentation-consumers"; +import { wrapOpenAI } from "./wrappers/oai"; + +registerInstrumentationConsumers(); +// Compatibility with OpenAI clients created by the remote function runtime. +globalThis.__inherited_braintrust_wrap_openai = wrapOpenAI; diff --git a/js/src/background-logger.test.ts b/js/src/background-logger.test.ts index a2701e048..1db286d1c 100644 --- a/js/src/background-logger.test.ts +++ b/js/src/background-logger.test.ts @@ -1,5 +1,12 @@ -import { afterEach, beforeEach, describe, expect, test, vi } from "vitest"; -import { BraintrustState } from "./logger"; +import { + afterEach, + beforeEach, + describe, + expect, + test, + vi, +} from "vite-plus/test"; +import { BraintrustState } from "./logger/core"; import { configureNode } from "./node/config"; import { Queue } from "./queue"; import { LazyValue } from "./util"; diff --git a/js/src/browser/config.ts b/js/src/browser/config.ts index c2d649bdb..34e579d3f 100644 --- a/js/src/browser/config.ts +++ b/js/src/browser/config.ts @@ -1,63 +1,7 @@ -import iso from "../isomorph"; -import { _internalSetInitialState } from "../logger"; -import { registry } from "../instrumentation/registry"; +import iso from "#braintrust-platform"; +import { createBrowserIsomorph } from "./platform"; -// This is copied from next.js. It seems they define AsyncLocalStorage in the edge -// environment, even though it's not defined in the browser. -import type { AsyncLocalStorage as NodeAsyncLocalStorage } from "node:async_hooks"; - -declare global { - var AsyncLocalStorage: typeof NodeAsyncLocalStorage; - var __BRAINTRUST_BROWSER_PKG__: boolean | undefined; -} - -let browserConfigured = false; - -/** - * Configure the isomorph for browser environments. - */ +/** @internal Configure source-level tests and compatibility integrations. */ export function configureBrowser(): void { - if (browserConfigured) { - return; - } - - iso.buildType = "browser"; - - // Try to use global AsyncLocalStorage (edge runtime like Next.js) - try { - if (typeof AsyncLocalStorage !== "undefined") { - iso.newAsyncLocalStorage = () => new AsyncLocalStorage(); - } - } catch { - // Ignore - } - - iso.getEnv = (name: string) => { - if (typeof process === "undefined" || typeof process.env === "undefined") { - return undefined; - } - return process.env[name]; - }; - iso.getBraintrustApiKey = async () => { - const value = iso.getEnv("BRAINTRUST_API_KEY"); - return value?.trim() ? value : undefined; - }; - - iso.hash = (data: string): string => { - let hash = 0; - for (let i = 0; i < data.length; i++) { - const char = data.charCodeAt(i); - hash = (hash << 5) - hash + char; - hash = hash & hash; - } - const hashHex = (hash >>> 0).toString(16).padStart(8, "0"); - return hashHex.repeat(8).substring(0, 64); - }; - - _internalSetInitialState(); - - // Enable auto-instrumentation - registry.enable(); - - browserConfigured = true; + Object.assign(iso, createBrowserIsomorph()); } diff --git a/js/src/browser/index.ts b/js/src/browser/index.ts index f478444b7..376bc1cf9 100644 --- a/js/src/browser/index.ts +++ b/js/src/browser/index.ts @@ -9,9 +9,4 @@ * import * as braintrust from '@braintrust/browser'; */ -import { configureBrowser } from "./config"; - -configureBrowser(); - export * from "../exports"; -export * as default from "../exports"; diff --git a/js/src/browser/isomorph.ts b/js/src/browser/isomorph.ts new file mode 100644 index 000000000..58e093313 --- /dev/null +++ b/js/src/browser/isomorph.ts @@ -0,0 +1,5 @@ +import type { Common } from "../isomorph-base"; +export type { CallerLocation, IsoAsyncLocalStorage } from "../isomorph-base"; +import { createBrowserIsomorph } from "./platform"; +const iso: Common = /* @__PURE__ */ createBrowserIsomorph(); +export default iso; diff --git a/js/src/browser/platform.ts b/js/src/browser/platform.ts new file mode 100644 index 000000000..aeedacdb8 --- /dev/null +++ b/js/src/browser/platform.ts @@ -0,0 +1,49 @@ +import { createIsomorph, type Common } from "../isomorph-base"; + +// This is copied from next.js. It seems they define AsyncLocalStorage in the edge +// environment, even though it's not defined in the browser. +import type { AsyncLocalStorage as NodeAsyncLocalStorage } from "node:async_hooks"; + +declare global { + var AsyncLocalStorage: typeof NodeAsyncLocalStorage; + var __BRAINTRUST_BROWSER_PKG__: boolean | undefined; +} + +/** + * Configure the isomorph for browser environments. + */ +export function createBrowserIsomorph(): Common { + const iso = createIsomorph(); + + iso.buildType = "browser"; + + const fallbackAsyncLocalStorage = iso.newAsyncLocalStorage; + iso.newAsyncLocalStorage = () => + typeof AsyncLocalStorage !== "undefined" + ? new AsyncLocalStorage() + : fallbackAsyncLocalStorage(); + + iso.getEnv = (name: string) => { + if (typeof process === "undefined" || typeof process.env === "undefined") { + return undefined; + } + return process.env[name]; + }; + iso.getBraintrustApiKey = async () => { + const value = iso.getEnv("BRAINTRUST_API_KEY"); + return value?.trim() ? value : undefined; + }; + + iso.hash = (data: string): string => { + let hash = 0; + for (let i = 0; i < data.length; i++) { + const char = data.charCodeAt(i); + hash = (hash << 5) - hash + char; + hash = hash & hash; + } + const hashHex = (hash >>> 0).toString(16).padStart(8, "0"); + return hashHex.repeat(8).substring(0, 64); + }; + + return iso; +} diff --git a/js/src/cli/auto-instrumentation.test.ts b/js/src/cli/auto-instrumentation.test.ts deleted file mode 100644 index fa44068e0..000000000 --- a/js/src/cli/auto-instrumentation.test.ts +++ /dev/null @@ -1,435 +0,0 @@ -import { - afterAll, - afterEach, - beforeAll, - beforeEach, - describe, - expect, - it, - vi, -} from "vitest"; -import { build } from "esbuild"; -import * as fs from "node:fs/promises"; -import * as os from "node:os"; -import * as path from "node:path"; -import { execFile } from "node:child_process"; -import { promisify } from "node:util"; -import { fileURLToPath } from "node:url"; -import { newGlobalInvocationHook } from "../global-instrumentation-hooks"; -import { initializeHandles } from "./index"; -import type { FileHandle } from "./types"; - -vi.mock("./functions/load-module", () => ({ - loadModule: () => ({ - evaluators: {}, - functions: [], - parameters: [], - prompts: [], - reporters: {}, - }), -})); - -const googleGenAIChannel = "orchestrion:@google/genai:models.generateContent"; -const anthropicChannel = "orchestrion:@anthropic-ai/sdk:messages.create"; -const execFileAsync = promisify(execFile); -const loadModuleSourcePath = fileURLToPath( - new URL("./functions/load-module.ts", import.meta.url), -); -const globalHooksSourcePath = fileURLToPath( - new URL("../global-instrumentation-hooks.ts", import.meta.url), -); -const debugLoggerSourcePath = fileURLToPath( - new URL("../debug-logger.ts", import.meta.url), -); - -describe("eval auto-instrumentation", () => { - let fixtureDir: string; - let runtimeDir: string; - let loadModulePath: string; - let globalHooksPath: string; - let debugLoggerPath: string; - let handles: Record = {}; - - beforeAll(async () => { - runtimeDir = await fs.mkdtemp( - path.join(os.tmpdir(), "braintrust-eval-runtime-"), - ); - loadModulePath = path.join(runtimeDir, "load-module.cjs"); - globalHooksPath = path.join(runtimeDir, "global-hooks.cjs"); - debugLoggerPath = path.join(runtimeDir, "debug-logger.cjs"); - // Exercise the bundled CLI without tsx's CommonJS extension hooks replacing - // Node's module loading. Include the real loader for the async fallback. - await build({ - entryPoints: { - "load-module": loadModuleSourcePath, - "global-hooks": globalHooksSourcePath, - "debug-logger": debugLoggerSourcePath, - }, - outdir: runtimeDir, - outExtension: { ".js": ".cjs" }, - bundle: true, - platform: "node", - format: "cjs", - logLevel: "silent", - }); - await build({ - entryPoints: [ - fileURLToPath( - new URL( - "../auto-instrumentations/loader/module-hooks.mts", - import.meta.url, - ), - ), - ], - outfile: path.join( - runtimeDir, - "auto-instrumentations/loader/module-hooks.mjs", - ), - bundle: true, - platform: "node", - format: "esm", - banner: { - js: 'import { createRequire } from "node:module"; const require = createRequire(import.meta.url);', - }, - logLevel: "silent", - }); - }); - - afterAll(async () => { - await fs.rm(runtimeDir, { recursive: true, force: true }); - }); - - beforeEach(async () => { - fixtureDir = await fs.mkdtemp( - path.join(os.tmpdir(), "braintrust-eval-instrumentation-"), - ); - await writeFixturePackages(fixtureDir); - }); - - afterEach(async () => { - vi.unstubAllEnvs(); - await Promise.all(Object.values(handles).map((handle) => handle.destroy())); - await Promise.all( - [ - ...new Set( - Object.values(handles) - .map((handle) => handle.bundleFile) - .filter( - (bundleFile): bundleFile is string => bundleFile !== undefined, - ) - .map((bundleFile) => path.dirname(bundleFile)), - ), - ].map((directory) => fs.rm(directory, { recursive: true, force: true })), - ); - handles = {}; - await fs.rm(fixtureDir, { recursive: true, force: true }); - }); - - it.each([ - [ - "directly", - `export { Models } from "@google/genai";`, - (exports: Record) => { - const Models = exports.Models as new () => { - generateContentInternal(input: string): Promise<{ text: string }>; - }; - return new Models().generateContentInternal("payload"); - }, - ], - [ - "through a dependency", - `export { invoke } from "google-genai-client";`, - (exports: Record) => - (exports.invoke as (input: string) => Promise<{ text: string }>)( - "payload", - ), - ], - ])( - "instruments @google/genai when imported %s", - async (_label, source, invoke) => { - const output = await buildEval(fixtureDir, source); - - expect(output).toContain(googleGenAIChannel); - - const lifecycle: string[] = []; - const hook = newGlobalInvocationHook(googleGenAIChannel); - const remove = hook.intercept((target, receiver, args) => { - lifecycle.push("called"); - const result = Reflect.apply(target, receiver, args); - result.then(() => lifecycle.push("resolved")); - return result; - }); - - try { - const loadedModule = { exports: {} as Record }; - Function( - "module", - "exports", - output, - )(loadedModule, loadedModule.exports); - - await expect(invoke(loadedModule.exports)).resolves.toEqual({ - text: "payload", - }); - expect(lifecycle).toEqual(["called", "resolved"]); - } finally { - remove(); - } - }, - ); - - it("respects BRAINTRUST_DISABLE_INSTRUMENTATION", async () => { - vi.stubEnv("BRAINTRUST_DISABLE_INSTRUMENTATION", "google-genai"); - - const output = await buildEval( - fixtureDir, - `export { Models } from "@google/genai";`, - ); - - expect(output).not.toContain(googleGenAIChannel); - }); - - it("instruments the final uploaded bundle", async () => { - await buildEval(fixtureDir, `export { Models } from "@google/genai";`); - - await handles[path.join(fixtureDir, "instrumentation.eval.ts")].bundle(); - const output = await fs.readFile( - handles[path.join(fixtureDir, "instrumentation.eval.ts")].bundleFile!, - "utf8", - ); - - expect(output).toContain(googleGenAIChannel); - }); - - it("inlines the Braintrust SDK in the final uploaded bundle", async () => { - const sourceFile = path.join(fixtureDir, "braintrust-scorer.ts"); - await fs.writeFile( - sourceFile, - `import { marker } from "braintrust"; - export const value = marker;`, - ); - - handles = await initializeHandles({ - files: [sourceFile], - mode: "bundle", - }); - await handles[sourceFile].bundle(); - const output = await fs.readFile(handles[sourceFile].bundleFile!, "utf8"); - - expect(output).toContain("bundled-braintrust"); - expect(output).not.toMatch(/require\(["']braintrust["']\)/); - }); - - it("keeps explicitly external packages external in the final uploaded bundle", async () => { - const sourceFile = path.join(fixtureDir, "external-package-scorer.ts"); - await fs.writeFile( - sourceFile, - `import { value } from "some-native-package"; - export const result = value;`, - ); - - handles = await initializeHandles({ - files: [sourceFile], - mode: "bundle", - externalPackages: ["some-native-package"], - }); - await handles[sourceFile].bundle(); - const output = await fs.readFile(handles[sourceFile].bundleFile!, "utf8"); - - expect(output).toMatch(/require\(["']some-native-package["']\)/); - }); - - for (const mode of ["default", "async fallback"]) { - it.each([ - ["instruments", undefined, ["called", "resolved"]], - ["respects opt-out for", "anthropic", []], - ])( - `%s external eval dependencies (${mode})`, - async (_label, disabled, expected) => { - const runnerPath = path.join(fixtureDir, "external-eval-runner.cjs"); - const evalFile = path.join(fixtureDir, "external.eval.ts"); - await fs.writeFile(evalFile, ""); - await fs.writeFile( - runnerPath, - `${mode === "async fallback" ? 'require("node:module").registerHooks = undefined;' : ""} -const { loadModule } = require(${JSON.stringify(loadModulePath)}); -const { newGlobalInvocationHook } = require(${JSON.stringify(globalHooksPath)}); -const lifecycle = []; -const channel = newGlobalInvocationHook(${JSON.stringify(anthropicChannel)}); -const remove = channel.intercept((target, receiver, args) => { - lifecycle.push("called"); - const result = Reflect.apply(target, receiver, args); - result.then(() => lifecycle.push("resolved")); - return result; -}); -loadModule({ - inFile: ${JSON.stringify(evalFile)}, - moduleText: 'const { Messages } = require("@anthropic-ai/sdk/resources/messages/messages.js"); globalThis.__externalEvalResult = new Messages().create("payload");', -}); -Promise.resolve(globalThis.__externalEvalResult).then((result) => { - remove(); - process.stdout.write(JSON.stringify({ lifecycle, result })); -});`, - ); - - const env = { ...process.env }; - if (disabled) { - env.BRAINTRUST_DISABLE_INSTRUMENTATION = disabled; - } else { - delete env.BRAINTRUST_DISABLE_INSTRUMENTATION; - } - const { stdout, stderr } = await execFileAsync( - process.execPath, - [runnerPath], - { - env: { ...env, BRAINTRUST_DEBUG_LOG_LEVEL: "warn" }, - }, - ); - - expect(stderr).not.toContain("Failed to enable auto-instrumentation"); - expect(JSON.parse(stdout)).toEqual({ - lifecycle: expected, - result: { text: "payload" }, - }); - }, - ); - - it(`reports unexpected runtime instrumentation failures (${mode})`, async () => { - const runnerPath = path.join( - fixtureDir, - "failed-instrumentation-runner.cjs", - ); - const evalFile = path.join(fixtureDir, "failed-instrumentation.eval.ts"); - await fs.writeFile(evalFile, ""); - await fs.writeFile( - runnerPath, - `${mode === "async fallback" ? 'require("node:module").registerHooks = undefined;' : ""} -const nodeModule = require("node:module"); -nodeModule.register = () => { throw new Error("setup failed"); }; -if (typeof nodeModule.registerHooks === "function") { - nodeModule.registerHooks = () => { throw new Error("setup failed"); }; -} -require(${JSON.stringify(debugLoggerPath)}).setGlobalDebugLogLevel("warn"); -const { loadModule } = require(${JSON.stringify(loadModulePath)}); -loadModule({ inFile: ${JSON.stringify(evalFile)}, moduleText: "" }); -process.stdout.write("loaded");`, - ); - - const { stderr, stdout } = await execFileAsync( - process.execPath, - [runnerPath], - { - env: { ...process.env, BRAINTRUST_DEBUG_LOG_LEVEL: "warn" }, - }, - ); - - expect(stdout).toBe("loaded"); - expect(stderr).toContain( - "Failed to enable auto-instrumentation for external eval dependencies", - ); - expect(stderr).toContain("setup failed"); - }); - } - - async function buildEval(fixtureDir: string, source: string) { - const evalFile = path.join(fixtureDir, "instrumentation.eval.ts"); - await fs.writeFile(evalFile, source); - handles = await initializeHandles({ files: [evalFile], mode: "eval" }); - - const result = await handles[evalFile].rebuild(); - if (result.type !== "success") { - throw result.error; - } - - return result.result.outputFiles?.[0].text ?? ""; - } -}); - -async function writeFixturePackages(fixtureDir: string) { - const braintrustPackageDir = path.join(fixtureDir, "node_modules/braintrust"); - const googlePackageDir = path.join(fixtureDir, "node_modules/@google/genai"); - const indirectPackageDir = path.join( - fixtureDir, - "node_modules/google-genai-client", - ); - const anthropicPackageDir = path.join( - fixtureDir, - "node_modules/@anthropic-ai/sdk", - ); - await fs.mkdir(braintrustPackageDir, { recursive: true }); - await fs.mkdir(path.join(googlePackageDir, "dist/node"), { - recursive: true, - }); - await fs.mkdir(indirectPackageDir, { recursive: true }); - await fs.mkdir(path.join(anthropicPackageDir, "resources/messages"), { - recursive: true, - }); - - await Promise.all([ - fs.writeFile( - path.join(braintrustPackageDir, "package.json"), - JSON.stringify({ - name: "braintrust", - version: "1.0.0", - main: "./index.js", - }), - ), - fs.writeFile( - path.join(braintrustPackageDir, "index.js"), - `exports.marker = "bundled-braintrust";`, - ), - fs.writeFile( - path.join(googlePackageDir, "package.json"), - JSON.stringify({ - name: "@google/genai", - version: "1.50.0", - type: "module", - exports: "./dist/node/index.mjs", - }), - ), - fs.writeFile( - path.join(googlePackageDir, "dist/node/index.mjs"), - `export class Models { - async generateContentInternal(input) { - return { text: input }; - } - }`, - ), - fs.writeFile( - path.join(indirectPackageDir, "package.json"), - JSON.stringify({ - name: "google-genai-client", - version: "1.0.0", - type: "module", - exports: "./index.mjs", - }), - ), - fs.writeFile( - path.join(indirectPackageDir, "index.mjs"), - `import { Models } from "@google/genai"; - export function invoke(input) { - return new Models().generateContentInternal(input); - }`, - ), - fs.writeFile( - path.join(anthropicPackageDir, "package.json"), - JSON.stringify({ - name: "@anthropic-ai/sdk", - version: "0.60.0", - exports: { - "./resources/messages/messages.js": - "./resources/messages/messages.js", - }, - }), - ), - fs.writeFile( - path.join(anthropicPackageDir, "resources/messages/messages.js"), - `class Messages { - async create(input) { - return { text: input }; - } - } - module.exports = { Messages };`, - ), - ]); -} diff --git a/js/src/cli/debug-logging.test.ts b/js/src/cli/debug-logging.test.ts deleted file mode 100644 index 49e352679..000000000 --- a/js/src/cli/debug-logging.test.ts +++ /dev/null @@ -1,71 +0,0 @@ -import { beforeEach, describe, expect, test, vi } from "vitest"; - -vi.mock("@next/env", () => ({ - loadEnvConfig: vi.fn(), -})); - -vi.mock("dotenv", () => ({ - config: vi.fn(() => ({})), -})); - -vi.mock("./index", () => ({ - handleBuildFailure: vi.fn(), - initializeHandles: vi.fn(), -})); - -vi.mock("../logger", async (importOriginal) => { - const actual = await importOriginal(); - return { - ...actual, - login: vi.fn().mockResolvedValue(undefined), - }; -}); - -import { login } from "../logger"; -import { resetDebugLoggingArgsForTests } from "./util/debug-logging"; -import { loadCLIEnv } from "./util/bundle"; - -describe("CLI debug logging", () => { - beforeEach(() => { - vi.clearAllMocks(); - resetDebugLoggingArgsForTests(); - }); - - test("loadCLIEnv forwards debugLogLevel to login", async () => { - await loadCLIEnv({ - verbose: false, - api_key: "test-key", - org_name: "test-org", - app_url: "https://braintrust.dev", - debug_logging: "debug", - }); - - expect(login).toHaveBeenCalledWith({ - apiKey: "test-key", - orgName: "test-org", - appUrl: "https://braintrust.dev", - debugLogLevel: "debug", - }); - }); - - test("loadCLIEnv treats --verbose as a deprecated alias for --debug-logging debug", async () => { - const warnSpy = vi.spyOn(console, "warn").mockImplementation(() => {}); - - await loadCLIEnv({ - verbose: true, - api_key: "test-key", - org_name: "test-org", - app_url: "https://braintrust.dev", - }); - - expect(login).toHaveBeenCalledWith({ - apiKey: "test-key", - orgName: "test-org", - appUrl: "https://braintrust.dev", - debugLogLevel: "debug", - }); - expect(warnSpy).toHaveBeenCalledWith( - "Warning: --verbose is deprecated and will be removed in a future version of braintrust. Use --debug-logging debug to see full stack traces and troubleshooting details.", - ); - }); -}); diff --git a/js/src/cli/functions/infer-source.ts b/js/src/cli/functions/infer-source.ts deleted file mode 100644 index 0d480c621..000000000 --- a/js/src/cli/functions/infer-source.ts +++ /dev/null @@ -1,212 +0,0 @@ -import { SourceMapConsumer } from "source-map"; -import * as fs from "node:fs/promises"; -import { EvaluatorFile, warning } from "../../framework"; -import { loadModule } from "./load-module"; -import { type CodeBundleType as CodeBundle } from "../../generated_types"; -import path from "node:path"; -import type { Node } from "typescript"; - -interface SourceMapContext { - inFiles: Record; - outFileModule: EvaluatorFile; - outFileLines: string[]; - sourceMapDir: string; - sourceMap: SourceMapConsumer; -} - -export async function makeSourceMapContext({ - inFile, - outFile, - sourceMapFile, -}: { - inFile: string; - outFile: string; - sourceMapFile: string; -}): Promise { - const [inFileContents, outFileContents, sourceMap] = await Promise.all([ - fs.readFile(inFile, "utf8"), - fs.readFile(outFile, "utf8"), - (async () => { - const sourceMap = await fs.readFile(sourceMapFile, "utf8"); - const sourceMapJSON = JSON.parse(sourceMap); - return new SourceMapConsumer(sourceMapJSON); - })(), - ]); - return { - inFiles: { [inFile]: inFileContents.split("\n") }, - outFileModule: loadModule({ inFile, moduleText: outFileContents }), - outFileLines: outFileContents.split("\n"), - sourceMapDir: path.dirname(sourceMapFile), - sourceMap, - }; -} - -// eslint-disable-next-line @typescript-eslint/no-unsafe-function-type -function isNative(fn: Function): boolean { - return /\{\s*\[native code\]\s*\}/.test(Function.prototype.toString.call(fn)); -} - -function locationToString(location: CodeBundle["location"]): string { - if (location.type === "experiment") { - return `eval ${location.eval_name} -> ${location.position.type}`; - } else if (location.type === "function") { - return `task ${location.index}`; - } else if (location.type === "sandbox") { - return `sandbox eval ${location.eval_name}`; - } else { - throw new Error(`Unknown location type`); - } -} - -export async function findCodeDefinition({ - location, - ctx: { inFiles, outFileModule, outFileLines, sourceMapDir, sourceMap }, -}: { - location: CodeBundle["location"]; - ctx: SourceMapContext; -}): Promise { - // eslint-disable-next-line @typescript-eslint/no-unsafe-function-type - let fn: Function | undefined = undefined; - - if (location.type === "experiment" || location.type === "sandbox") { - const evaluator = outFileModule.evaluators[location.eval_name]?.evaluator; - if (!evaluator) { - // eslint-disable-next-line no-restricted-properties -- preserving intentional console usage. - console.warn( - warning( - `Warning: failed to find evaluator for ${location.eval_name}. Will not display preview.`, - ), - ); - return undefined; - } - - if (location.type === "sandbox") { - fn = evaluator.task; - } else { - fn = - location.position.type === "task" - ? evaluator.task - : location.position.type === "scorer" - ? (evaluator.scores ?? [])[location.position.index] - : (evaluator.classifiers ?? [])[location.position.index]; - } - } else if (location.type === "function") { - fn = outFileModule.functions[location.index].handler; - } else { - throw new Error(`Unknown location type`); - } - - if (!fn) { - // eslint-disable-next-line no-restricted-properties -- preserving intentional console usage. - console.warn( - warning( - `Warning: failed to find ${locationToString(location)}. Will not display preview.`, - ), - ); - return undefined; - } - - const sourceCode = fn.toString(); - if (isNative(fn)) { - return undefined; - } - let lineNumber = 0; - let columnNumber = -1; - for (const line of outFileLines) { - const sourceDefinition = line.indexOf(sourceCode); - if (sourceDefinition !== -1) { - columnNumber = sourceDefinition; - break; - } - lineNumber++; - } - - if (columnNumber === -1) { - // eslint-disable-next-line no-restricted-properties -- preserving intentional console usage. - console.warn( - warning( - `Warning: failed to find code definition for ${fn.name}. Will not display preview.`, - ), - ); - return undefined; - } - const originalPosition = sourceMap.originalPositionFor({ - line: lineNumber + 1, - column: columnNumber + 1, - }); - - if (originalPosition.source === null || originalPosition.line === null) { - return undefined; - } - - if (!inFiles[originalPosition.source]) { - const originalFile = path.join(sourceMapDir, originalPosition.source); - inFiles[originalPosition.source] = ( - await fs.readFile(originalFile, "utf-8") - ).split("\n"); - } - - const originalLines = inFiles[originalPosition.source]; - - // Parse the file with Typescript to find the function definition - const ts = await getTsModule(); - if (!ts) { - return undefined; - } - const sourceFile = ts.createSourceFile( - originalPosition.source, - originalLines.join("\n"), - ts.ScriptTarget.Latest, - true, - ); - let functionNode: Node | undefined = undefined; - const targetPosition = ts.getPositionOfLineAndCharacter( - sourceFile, - originalPosition.line - 1, - originalPosition.column || 0, - ); - - ts.forEachChild(sourceFile, function visit(node) { - if (node.pos <= targetPosition && targetPosition < node.end) { - if ( - ts.isFunctionDeclaration(node) || - ts.isFunctionExpression(node) || - ts.isArrowFunction(node) - ) { - functionNode = node; - } else { - ts.forEachChild(node, visit); - } - } - }); - - if (!functionNode) { - return undefined; - } - - const printer = ts.createPrinter(); - const functionDefinition = printer.printNode( - ts.EmitHint.Unspecified, - functionNode, - sourceFile, - ); - - return functionDefinition; -} - -let tsModule: typeof import("typescript") | undefined = undefined; -async function getTsModule() { - if (!tsModule) { - try { - tsModule = require("typescript"); - } catch { - // eslint-disable-next-line no-restricted-properties -- preserving intentional console usage. - console.warn( - warning( - "Failed to load TypeScript module. Will not use TypeScript to derive preview.", - ), - ); - } - } - return tsModule; -} diff --git a/js/src/cli/functions/load-module.ts b/js/src/cli/functions/load-module.ts deleted file mode 100644 index 1fa7296b0..000000000 --- a/js/src/cli/functions/load-module.ts +++ /dev/null @@ -1,54 +0,0 @@ -import nodeModulesPaths from "../jest/nodeModulesPaths"; -import path, { dirname } from "node:path"; -import { _internalGetGlobalState } from "../../logger"; -import { EvaluatorFile } from "../../framework"; -import { applyAutoInstrumentation } from "../../node/apply-auto-instrumentation"; -import { debugLogger } from "../../debug-logger"; - -function evalWithModuleContext(inFile: string, evalFn: () => T): T { - const modulePaths = [...module.paths]; - try { - module.paths = nodeModulesPaths(path.dirname(inFile), {}); - return evalFn(); - } finally { - module.paths = modulePaths; - } -} - -export function loadModule({ - inFile, - moduleText, -}: { - inFile: string; - moduleText: string; -}): EvaluatorFile { - return evalWithModuleContext(inFile, () => { - globalThis._evals = { - functions: [], - prompts: [], - parameters: [], - evaluators: {}, - reporters: {}, - }; - globalThis._lazy_load = true; - const state = _internalGetGlobalState(); - (globalThis as any)[Symbol.for("braintrust-state")] = state; - const __filename = inFile; - const __dirname = dirname(__filename); - try { - applyAutoInstrumentation(); - } catch (error) { - debugLogger.warn( - "Failed to enable auto-instrumentation for external eval dependencies; bundled dependencies remain instrumented:", - error, - ); - } - new Function("require", "module", "__filename", "__dirname", moduleText)( - require, - module, - __filename, - __dirname, - ); - return { ...globalThis._evals }; - }); -} diff --git a/js/src/cli/functions/upload.test.ts b/js/src/cli/functions/upload.test.ts deleted file mode 100644 index f0ecde997..000000000 --- a/js/src/cli/functions/upload.test.ts +++ /dev/null @@ -1,119 +0,0 @@ -import { describe, expect, test, vi } from "vitest"; -import { buildBundledFunctionEntry } from "./upload"; - -vi.mock("./infer-source", () => ({ - findCodeDefinition: vi.fn(), - makeSourceMapContext: vi.fn(), -})); - -describe("buildBundledFunctionEntry", () => { - test("preserves tags and existing function fields", async () => { - const spec = { - project_id: "proj-123", - name: "test-tool", - slug: "test-tool", - description: "Test tool", - location: { - type: "function" as const, - index: 0, - }, - function_type: "tool" as const, - origin: { - object_type: "experiment" as const, - object_id: "exp-123", - internal: false, - }, - function_schema: { - parameters: { type: "object" }, - returns: { type: "string" }, - }, - if_exists: "replace" as const, - tags: ["ci", "sdk"], - metadata: { owner: "sdk" }, - }; - - const entry = await buildBundledFunctionEntry({ - spec, - runtime_context: { runtime: "node", version: "22.0.0" }, - bundleId: "bundle-123", - sourceMapContext: undefined, - }); - - expect(entry).toMatchObject({ - project_id: "proj-123", - name: "test-tool", - slug: "test-tool", - description: "Test tool", - origin: spec.origin, - function_type: "tool", - function_schema: spec.function_schema, - if_exists: "replace", - tags: ["ci", "sdk"], - metadata: { owner: "sdk" }, - function_data: { - type: "code", - data: { - type: "bundle", - runtime_context: { runtime: "node", version: "22.0.0" }, - location: spec.location, - bundle_id: "bundle-123", - }, - }, - }); - expect(((entry.function_data as any).data as any).preview).toBeUndefined(); - }); - - test("does not invent tags when they are omitted", async () => { - const entry = await buildBundledFunctionEntry({ - spec: { - project_id: "proj-123", - name: "test-tool", - slug: "test-tool", - description: "Test tool", - location: { - type: "function" as const, - index: 0, - }, - function_type: "tool" as const, - }, - runtime_context: { runtime: "node", version: "22.0.0" }, - bundleId: "bundle-123", - sourceMapContext: undefined, - }); - - expect(entry.tags).toBeUndefined(); - }); - - test("preserves classifier experiment locations", async () => { - const entry = await buildBundledFunctionEntry({ - spec: { - project_id: "proj-123", - name: "test-classifier", - slug: "test-classifier", - description: "Test classifier", - location: { - type: "experiment" as const, - eval_name: "eval-1", - position: { - type: "classifier" as const, - index: 0, - }, - }, - function_type: "classifier" as const, - }, - runtime_context: { runtime: "node", version: "22.0.0" }, - bundleId: "bundle-123", - sourceMapContext: undefined, - }); - - expect(entry.function_type).toBe("classifier"); - expect(((entry.function_data as any).data as any).location).toEqual({ - type: "experiment", - eval_name: "eval-1", - position: { - type: "classifier", - index: 0, - }, - }); - }); -}); diff --git a/js/src/cli/functions/upload.ts b/js/src/cli/functions/upload.ts deleted file mode 100644 index da513f503..000000000 --- a/js/src/cli/functions/upload.ts +++ /dev/null @@ -1,516 +0,0 @@ -import { - type CodeBundleType as CodeBundle, - type FunctionType as FunctionObject, - type IfExistsType as IfExists, -} from "../../generated_types"; -import type { BuildSuccess, FileHandle } from "../types"; -import { classifierName, scorerName, warning } from "../../framework"; -import { - _internalGetGlobalState, - Experiment, - FailedHTTPResponse, -} from "../../logger"; -import * as esbuild from "esbuild"; -import fs from "node:fs"; -import path from "node:path"; -import { createGzip } from "node:zlib"; -import { addAzureBlobHeaders, isEmpty } from "../../util"; -import { z } from "zod/v3"; -import { capitalize } from "../../../util/index"; -import { findCodeDefinition, makeSourceMapContext } from "./infer-source"; -import { slugify } from "../../../util/string_util"; -import { zodToJsonSchema } from "../../zod/utils"; -import pluralize from "pluralize"; -import { - FunctionEvent, - ProjectNameIdMap, - serializeRemoteEvalParametersContainer, -} from "../../framework2"; - -interface BundledFunctionSpec { - project_id: string; - name: string; - slug: string; - description: string; - location: CodeBundle["location"]; - function_type: FunctionObject["function_type"]; - origin?: FunctionObject["origin"]; - function_schema?: FunctionObject["function_schema"]; - if_exists?: IfExists; - tags?: string[]; - metadata?: Record; -} - -type BundledFunctionEntry = FunctionEvent & { - origin?: FunctionObject["origin"]; - function_schema?: FunctionObject["function_schema"]; -}; - -const SANDBOX_GROUP_NAME_METADATA_KEY = "_bt_sandbox_group_name"; - -const pathInfoSchema = z - .strictObject({ - url: z.string(), - bundleId: z.string(), - }) - .strip(); - -export async function uploadHandleBundles({ - buildResults, - evalToExperiment, - bundlePromises, - handles, - setCurrent, - showDetailedErrors, - defaultIfExists, -}: { - buildResults: BuildSuccess[]; - evalToExperiment?: Record>; - bundlePromises: { - [k: string]: Promise>; - }; - handles: Record; - showDetailedErrors: boolean; - setCurrent: boolean; - defaultIfExists: IfExists; -}) { - // eslint-disable-next-line no-restricted-properties -- preserving intentional console usage. - console.error( - `Processing ${buildResults.length} ${pluralize("file", buildResults.length)}...`, - ); - - const projectNameToId = new ProjectNameIdMap(); - - const uploadPromises = buildResults.map(async (result) => { - if (result.type !== "success") { - return; - } - const sourceFile = result.sourceFile; - - const bundleSpecs: BundledFunctionSpec[] = []; - const prompts: FunctionEvent[] = []; - - if (setCurrent) { - for (let i = 0; i < result.evaluator.functions.length; i++) { - const fn = result.evaluator.functions[i]; - const project_id = await projectNameToId.resolve(fn.project); - - bundleSpecs.push({ - project_id: project_id, - name: fn.name, - slug: fn.slug, - description: fn.description ?? "", - function_type: fn.type, - location: { - type: "function", - index: i, - }, - function_schema: - fn.parameters || fn.returns - ? { - parameters: fn.parameters - ? zodToJsonSchema(fn.parameters) - : undefined, - returns: fn.returns ? zodToJsonSchema(fn.returns) : undefined, - } - : undefined, - if_exists: fn.ifExists, - tags: fn.tags, - metadata: fn.metadata, - }); - } - - for (const prompt of result.evaluator.prompts) { - prompts.push(await prompt.toFunctionDefinition(projectNameToId)); - } - - if (result.evaluator.parameters != null) { - for (const param of result.evaluator.parameters) { - prompts.push(await param.toFunctionDefinition(projectNameToId)); - } - } - } - - for (const evaluator of Object.values(result.evaluator.evaluators)) { - const experiment = - evalToExperiment?.[sourceFile]?.[evaluator.evaluator.evalName]; - - const baseInfo = { - project_id: experiment - ? (await experiment.project).id - : await projectNameToId.getId(evaluator.evaluator.projectName), - }; - - const namePrefix = setCurrent - ? evaluator.evaluator.experimentName - ? `${evaluator.evaluator.experimentName}` - : evaluator.evaluator.evalName - : experiment - ? `${await experiment.name}` - : evaluator.evaluator.evalName; - - const experimentId = experiment ? await experiment.id : undefined; - const origin: FunctionObject["origin"] = experimentId - ? { - object_type: "experiment", - object_id: experimentId, - internal: !setCurrent, - } - : undefined; - - const fileSpecs: BundledFunctionSpec[] = [ - { - ...baseInfo, - // There is a very small chance that someone names a function with the same convention, but - // let's assume it's low enough that it doesn't matter. - ...formatNameAndSlug(["eval", namePrefix, "task"]), - description: `Task for eval ${namePrefix}`, - location: { - type: "experiment", - eval_name: evaluator.evaluator.evalName, - position: { type: "task" }, - }, - function_type: "task", - origin, - }, - ...(evaluator.evaluator.scores ?? []).map( - (score, i): BundledFunctionSpec => { - const name = scorerName(score, i); - return { - ...baseInfo, - // There is a very small chance that someone names a function with the same convention, but - // let's assume it's low enough that it doesn't matter. - ...formatNameAndSlug(["eval", namePrefix, "scorer", name]), - description: `Score ${name} for eval ${namePrefix}`, - location: { - type: "experiment", - eval_name: evaluator.evaluator.evalName, - position: { type: "scorer", index: i }, - }, - function_type: "scorer", - origin, - }; - }, - ), - ...(evaluator.evaluator.classifiers ?? []).map( - (classifier, i): BundledFunctionSpec => { - const name = classifierName(classifier, i); - return { - ...baseInfo, - ...formatNameAndSlug(["eval", namePrefix, "classifier", name]), - description: `Classifier ${name} for eval ${namePrefix}`, - location: { - type: "experiment", - eval_name: evaluator.evaluator.evalName, - position: { type: "classifier", index: i }, - }, - function_type: "classifier", - origin, - }; - }, - ), - ]; - - bundleSpecs.push(...fileSpecs); - - if (setCurrent) { - const sourceStem = path - .basename(sourceFile, path.extname(sourceFile)) - .replace(/\.eval$/, ""); - const evalName = evaluator.evaluator.evalName; - const sandboxGroupName = sourceStem; - - const resolvedParameters = evaluator.evaluator.parameters - ? await Promise.resolve(evaluator.evaluator.parameters) - : undefined; - - const evaluatorDefinition = { - ...(resolvedParameters - ? { - parameters: - serializeRemoteEvalParametersContainer(resolvedParameters), - } - : {}), - scores: (evaluator.evaluator.scores ?? []).map((score, i) => ({ - name: scorerName(score, i), - })), - classifiers: (evaluator.evaluator.classifiers ?? []).map( - (classifier, i) => ({ - name: classifierName(classifier, i), - }), - ), - }; - - bundleSpecs.push({ - ...baseInfo, - name: `Eval ${evalName} sandbox`, - slug: slugify(`${sourceStem}-${evalName}-sandbox`), - description: `Sandbox eval ${evalName}`, - location: { - type: "sandbox", - sandbox_spec: { - provider: "lambda", - }, - entrypoints: [sourceFile], - eval_name: evalName, - evaluator_definition: evaluatorDefinition, - }, - function_type: "sandbox", - metadata: { - [SANDBOX_GROUP_NAME_METADATA_KEY]: sandboxGroupName, - }, - origin, - }); - } - } - - const slugs: Set = new Set(); - for (const spec of bundleSpecs) { - if (slugs.has(spec.slug)) { - throw new Error(`Duplicate slug: ${spec.slug}`); - } - slugs.add(spec.slug); - } - for (const prompt of prompts) { - if (slugs.has(prompt.slug)) { - throw new Error(`Duplicate slug: ${prompt.slug}`); - } - slugs.add(prompt.slug); - } - - return await uploadBundles({ - sourceFile, - prompts, - bundleSpecs, - bundlePromises, - handles, - defaultIfExists, - showDetailedErrors, - }); - }); - - const uploadResults = await Promise.all(uploadPromises); - const numUploaded = uploadResults.length; - const numFailed = uploadResults.filter((result) => !result).length; - - // eslint-disable-next-line no-restricted-properties -- preserving intentional console usage. - console.error( - `${numUploaded} ${pluralize("file", numUploaded)} uploaded ${ - numFailed > 0 - ? `with ${numFailed} error${numFailed > 1 ? "s" : ""}` - : "successfully" - }.`, - ); - - return { - numTotal: buildResults.length, - numUploaded, - numFailed, - }; -} - -async function uploadBundles({ - sourceFile, - prompts, - bundleSpecs, - bundlePromises, - handles, - defaultIfExists, - showDetailedErrors, -}: { - sourceFile: string; - prompts: FunctionEvent[]; - bundleSpecs: BundledFunctionSpec[]; - bundlePromises: { - [k: string]: Promise>; - }; - handles: Record; - defaultIfExists: IfExists; - showDetailedErrors: boolean; -}): Promise { - const orgId = _internalGetGlobalState().orgId; - if (!orgId) { - throw new Error("No organization ID found"); - } - - const loggerConn = _internalGetGlobalState().apiConn(); - const runtime_context = { - runtime: "node", - version: process.version.slice(1), - } as const; - - const bundle = await bundlePromises[sourceFile]; - const bundleFileName = handles[sourceFile].bundleFile; - if (!bundle || !bundleFileName) { - return false; - } - - const sourceMapContextPromise = makeSourceMapContext({ - inFile: sourceFile, - outFile: bundleFileName, - sourceMapFile: bundleFileName + ".map", - }); - - let pathInfo: z.infer | undefined = undefined; - if (bundleSpecs.length > 0) { - try { - pathInfo = pathInfoSchema.parse( - await loggerConn.post_json("function/code", { - org_id: orgId, - runtime_context, - }), - ); - } catch (e) { - if (showDetailedErrors) { - // eslint-disable-next-line no-restricted-properties -- preserving intentional console usage. - console.error(e); - } - const msg = - e instanceof FailedHTTPResponse - ? `Unable to upload your code. ${e.status} (${e.text}): ${e.data}` - : `Unable to upload your code. You most likely need to update the API: ${e}`; - // eslint-disable-next-line no-restricted-properties -- preserving intentional console usage. - console.error(warning(msg)); - return false; - } - } - - // Upload bundleFile to pathInfo.url - if (isEmpty(bundleFileName)) { - throw new Error("No bundle file found"); - } - const bundleFile = path.resolve(bundleFileName); - const uploadPromise = (async (): Promise => { - if (!pathInfo) { - return true; - } - const bundleStream = fs.createReadStream(bundleFile).pipe(createGzip()); - const bundleData = await new Promise((resolve, reject) => { - const chunks: Buffer[] = []; - bundleStream.on("data", (chunk) => { - chunks.push(chunk); - }); - bundleStream.on("end", () => { - resolve(Buffer.concat(chunks)); - }); - bundleStream.on("error", reject); - }); - - const headers = { - "Content-Encoding": "gzip", - }; - addAzureBlobHeaders(headers, pathInfo.url); - - const resp = await fetch(pathInfo.url, { - method: "PUT", - body: bundleData, - headers, - }); - if (!resp.ok) { - throw new Error( - `Failed to upload bundle: ${resp.status} ${await resp.text()}`, - ); - } - return true; - })(); - - const sourceMapContext = await sourceMapContextPromise; - - // Insert the spec as prompt data - const functionEntries: FunctionEvent[] = [ - ...prompts, - // eslint-disable-next-line @typescript-eslint/consistent-type-assertions - ...((await Promise.all( - bundleSpecs.map((spec) => - buildBundledFunctionEntry({ - spec, - runtime_context, - bundleId: pathInfo!.bundleId, - sourceMapContext, - }), - ), - )) as FunctionEvent[]), - ].map((fn) => ({ - ...fn, - if_exists: fn.if_exists ?? defaultIfExists, - })); - - const logPromise = (async (): Promise => { - try { - await _internalGetGlobalState().apiConn().post_json("insert-functions", { - functions: functionEntries, - }); - } catch (e) { - if (showDetailedErrors) { - // eslint-disable-next-line no-restricted-properties -- preserving intentional console usage. - console.error(e); - } - const msg = - e instanceof FailedHTTPResponse - ? `Failed to save function definitions for '${sourceFile}'. ${e.status} (${e.text}): ${e.data}` - : `Failed to save function definitions for '${sourceFile}'. You most likely need to update the API: ${e}`; - // eslint-disable-next-line no-restricted-properties -- preserving intentional console usage. - console.warn(warning(msg)); - return false; - } - return true; - })(); - - const [uploadSuccess, logSuccess] = await Promise.all([ - uploadPromise, - logPromise, - ]); - - return uploadSuccess && logSuccess; -} - -function formatNameAndSlug(pieces: string[]) { - const nonEmptyPieces = pieces.filter((piece) => piece.trim() !== ""); - return { - name: capitalize(nonEmptyPieces.join(" ")), - slug: slugify(nonEmptyPieces.join("-")), - }; -} - -export async function buildBundledFunctionEntry({ - spec, - runtime_context, - bundleId, - sourceMapContext, -}: { - spec: BundledFunctionSpec; - runtime_context: { - runtime: "node"; - version: string; - }; - bundleId: string; - sourceMapContext?: Awaited>; -}): Promise { - return { - project_id: spec.project_id, - name: spec.name, - slug: spec.slug, - description: spec.description, - function_data: { - type: "code", - data: { - type: "bundle", - runtime_context, - location: spec.location, - bundle_id: bundleId, - preview: sourceMapContext - ? await findCodeDefinition({ - location: spec.location, - ctx: sourceMapContext, - }) - : undefined, - }, - }, - origin: spec.origin, - function_type: spec.function_type ?? undefined, - function_schema: spec.function_schema, - if_exists: spec.if_exists, - tags: spec.tags, - metadata: spec.metadata, - }; -} diff --git a/js/src/cli/index.ts b/js/src/cli/index.ts deleted file mode 100755 index b3ed92d13..000000000 --- a/js/src/cli/index.ts +++ /dev/null @@ -1,1199 +0,0 @@ -#!/usr/bin/env node - -import * as esbuild from "esbuild"; -import * as dotenv from "dotenv"; -import fs from "node:fs"; -import os from "node:os"; -import path from "node:path"; -import util, { styleText } from "node:util"; -import * as fsWalk from "@nodelib/fs.walk"; -import { minimatch } from "minimatch"; -import { ArgumentParser } from "argparse"; -import { v4 as uuidv4 } from "uuid"; -import pluralize from "pluralize"; -import { - login, - init as _initExperiment, - Experiment, - BaseMetadata, - Dataset, - type ParametersRef, - RemoteEvalParameters, -} from "../logger"; -import type { ProgressReporter } from "../reporters/types"; -import { - BarProgressReporter, - SimpleProgressReporter, -} from "./reporters/progress"; -import { terminalLink } from "termi-link"; - -// Re-use the module resolution logic from Jest -import type { ReporterDef } from "../reporters/types"; -import { - EvalData, - EvaluatorDef, - EvaluatorFile, - Filter, - callEvaluatorData, - error, - logError, - parseFilters, - runEvaluator, -} from "../framework"; -import { fancyReporter, warning } from "./reporters/eval"; -import { configureNode } from "../node/config"; -import { isEmpty } from "../util"; -import { loadEnvConfig } from "@next/env"; -import type { - BuildSuccess, - BuildFailure, - BtBuildResult, - FileHandle, - EvaluatorState, -} from "./types"; -import { uploadHandleBundles } from "./functions/upload"; -import { loadModule } from "./functions/load-module"; -import { bundleCommand } from "./util/bundle"; -import { RunArgs } from "./util/types"; -import { - normalizeDebugLoggingArgs, - shouldShowDetailedErrors, -} from "./util/debug-logging"; -import { pullCommand } from "./util/pull"; -import { runDevServer } from "../../dev/server"; -import { braintrustEsbuildPlugin } from "../auto-instrumentations/bundler/esbuild"; - -// This requires require -// https://stackoverflow.com/questions/50822310/how-to-import-package-json-in-typescript -const { version } = require("../../package.json"); - -// TODO: This could be loaded from configuration -const INCLUDE_EVAL = [ - "**/*.eval.ts", - "**/*.eval.tsx", - "**/*.eval.js", - "**/*.eval.jsx", -]; -const INCLUDE_BUNDLE = ["**/*.ts", "**/*.tsx", "**/*.js", "**/*.jsx"]; -const EXCLUDE = ["**/node_modules/**", "**/dist/**", "**/build/**"]; -const OUT_EXT = "js"; - -configureNode(); - -function evaluateBuildResults( - inFile: string, - buildResult: esbuild.BuildResult, -): EvaluatorFile | null { - if (!buildResult.outputFiles) { - return null; - } - const moduleText = buildResult.outputFiles[0].text; - return loadModule({ inFile, moduleText }); -} - -async function initExperiment( - evaluator: EvaluatorDef, - evaluatorData: { - data: EvalData; - baseExperiment: string | undefined; - }, -) { - const { data, baseExperiment: defaultBaseExperiment } = evaluatorData; - const parameters = await getExperimentParametersRef(evaluator.parameters); - // NOTE: This code is duplicated with initExperiment in js/src/framework.ts. - // Make sure to update that if you change this. - const logger = _initExperiment({ - state: evaluator.state, - ...(evaluator.projectId - ? { projectId: evaluator.projectId } - : { project: evaluator.projectName }), - experiment: evaluator.experimentName, - description: evaluator.description, - metadata: evaluator.metadata, - tags: evaluator.tags, - isPublic: evaluator.isPublic, - update: evaluator.update, - baseExperiment: evaluator.baseExperimentName ?? defaultBaseExperiment, - baseExperimentId: evaluator.baseExperimentId, - gitMetadataSettings: evaluator.gitMetadataSettings, - repoInfo: evaluator.repoInfo, - dataset: Dataset.isDataset(data) ? data : undefined, - parameters, - setCurrent: false, - }); - const info = await logger.summarize({ summarizeScores: false }); - const linkText = info.experimentUrl - ? terminalLink(info.experimentUrl, info.experimentUrl, { - fallback: (_text: string, url: string) => url, - }) - : "locally"; - // eslint-disable-next-line no-restricted-properties -- preserving intentional console usage. - console.error( - styleText("cyan", "▶") + - ` Experiment ${styleText("bold", info.experimentName)} is running at ${linkText}`, - ); - return logger; -} - -async function getExperimentParametersRef( - parameters: - | Record - | RemoteEvalParameters - | Promise> - | undefined, -): Promise { - if (!parameters) { - return undefined; - } - - const resolvedParameters = - parameters instanceof Promise ? await parameters : parameters; - - if (!RemoteEvalParameters.isParameters(resolvedParameters)) { - return undefined; - } - - if (resolvedParameters.id === undefined) { - return undefined; - } - - return { - id: resolvedParameters.id, - version: resolvedParameters.version, - }; -} - -function resolveReporter( - reporter: string | ReporterDef | undefined, - reporters: Record>, -) { - if (typeof reporter === "string") { - if (!reporters[reporter]) { - throw new Error(`Reporter ${reporter} not found`); - } - return reporters[reporter]; - } else if (!isEmpty(reporter)) { - return reporter; - } else if (Object.keys(reporters).length === 0) { - return fancyReporter; - } else if (Object.keys(reporters).length === 1) { - return reporters[Object.keys(reporters)[0]]; - } else { - const reporterNames = Object.keys(reporters).join(", "); - throw new Error( - `Multiple reporters found (${reporterNames}). Please specify a reporter explicitly.`, - ); - } -} - -type AllReports = Record< - string, - { - reporter: ReporterDef; - results: (unknown | Promise)[]; - } ->; - -function addReport( - evalReports: AllReports, - reporter: ReporterDef, - report: unknown, -) { - if (!evalReports[reporter.name]) { - evalReports[reporter.name] = { - reporter, - results: [], - }; - } - evalReports[reporter.name].results.push(report); -} - -function buildWatchPluginForEvaluator( - inFile: string, - opts: EvaluatorOpts, -): esbuild.Plugin { - const evaluators: EvaluatorState = { - evaluators: [], - reporters: {}, - }; - const plugin = { - name: "run-evalutator-on-end", - setup(build: esbuild.PluginBuild) { - build.onEnd(async (result) => { - // eslint-disable-next-line no-restricted-properties -- preserving intentional console usage. - console.error(`Done building ${inFile}`); - - if (!result.outputFiles) { - if (opts.showDetailedErrors) { - // eslint-disable-next-line no-restricted-properties -- preserving intentional console usage. - console.warn(`Failed to compile ${inFile}`); - // eslint-disable-next-line no-restricted-properties -- preserving intentional console usage. - console.warn(result.errors); - } else { - // eslint-disable-next-line no-restricted-properties -- preserving intentional console usage. - console.warn(`Failed to compile ${inFile}: ${result.errors}`); - } - return; - } - - const evalResult = evaluateBuildResults(inFile, result); - if (!evalResult) { - return; - } - - evaluators.evaluators = evaluators.evaluators.filter( - (e) => e.sourceFile !== inFile, - ); - - // Update the evaluators and reporters - for (const evaluator of Object.values(evalResult.evaluators)) { - evaluators.evaluators.push({ - sourceFile: inFile, - // eslint-disable-next-line @typescript-eslint/consistent-type-assertions - evaluator: evaluator.evaluator as EvaluatorDef< - unknown, - unknown, - unknown, - BaseMetadata - >, - reporter: evaluator.reporter, - }); - } - for (const [reporterName, reporter] of Object.entries( - evalResult.reporters, - )) { - evaluators.reporters[reporterName] = reporter; - } - - const evalReports: Record< - string, - { - reporter: ReporterDef; - results: unknown[]; - } - > = {}; - for (const evaluatorDef of Object.values(evalResult.evaluators)) { - const { evaluator, reporter } = evaluatorDef; - const evalData = callEvaluatorData(evaluator.data); - const logger = opts.noSendLogs - ? null - : await initExperiment(evaluator, evalData); - const evaluatorResult = await runEvaluator( - logger, - { - ...evaluator, - data: evalData.data, - }, - opts.progressReporter, - opts.filters, - undefined, - undefined, - ); - const resolvedReporter = resolveReporter( - reporter, - evaluators.reporters, // Let these accumulate across all files. - ); - - const report = resolvedReporter.reportEval( - evaluator, - evaluatorResult, - { - verbose: opts.showDetailedErrors, - jsonl: opts.jsonl, - }, - ); - - addReport(evalReports, resolvedReporter, report); - } - - for (const [reporterName, { reporter, results }] of Object.entries( - evalReports, - )) { - const success = await reporter.reportRun(await Promise.all(results)); - if (!success) { - // eslint-disable-next-line no-restricted-properties -- preserving intentional console usage. - console.error(error(`Reporter ${reporterName} failed.`)); - } - } - }); - }, - }; - - return plugin; -} - -async function initFile({ - inFile, - outFile, - bundleFile, - tsconfig, - plugins, - externalPackages, -}: { - inFile: string; - outFile: string; - bundleFile: string; - tsconfig?: string; - plugins?: PluginMaker[]; - externalPackages?: string[]; -}): Promise { - const buildOptions = buildOpts({ - fileName: inFile, - outFile, - tsconfig, - plugins, - externalPackages, - }); - const ctx = await esbuild.context(buildOptions); - - return { - inFile, - outFile, - bundleFile, - rebuild: async () => { - try { - const result = await ctx.rebuild(); - if (!result.outputFiles) { - return { - type: "failure", - error: new Error("No output file generated"), - sourceFile: inFile, - }; - } - const evaluator = evaluateBuildResults(inFile, result) || { - functions: [], - prompts: [], - parameters: [], - evaluators: {}, - reporters: {}, - }; - return { type: "success", result, evaluator, sourceFile: inFile }; - } catch (e) { - // eslint-disable-next-line @typescript-eslint/consistent-type-assertions - return { type: "failure", error: e as Error, sourceFile: inFile }; - } - }, - bundle: async () => { - const buildOptions: esbuild.BuildOptions = { - ...buildOpts({ - fileName: inFile, - outFile: bundleFile, - tsconfig, - plugins: [], - externalPackages, - markKnownPackagesExternal: false, - }), - external: ["fsevents", "chokidar", ...(externalPackages ?? [])], - write: true, - minify: true, - sourcemap: true, - }; - return await esbuild.build(buildOptions); - }, - watch: () => { - ctx.watch(); - }, - destroy: async () => { - await ctx.dispose(); - }, - }; -} - -interface EvaluatorOpts { - showDetailedErrors: boolean; - apiKey?: string; - orgName?: string; - appUrl?: string; - noSendLogs: boolean; - bundle: boolean; - setCurrent: boolean; - terminateOnFailure: boolean; - watch: boolean; - list: boolean; - jsonl: boolean; - filters: Filter[]; - progressReporter: ProgressReporter; -} - -export function handleBuildFailure({ - result, - terminateOnFailure, - showDetailedErrors, -}: { - result: BuildFailure; - terminateOnFailure: boolean; - showDetailedErrors: boolean; -}) { - if (terminateOnFailure) { - throw result.error; - } else if (showDetailedErrors) { - // eslint-disable-next-line no-restricted-properties -- preserving intentional console usage. - console.warn(`Failed to compile ${result.sourceFile}`); - // eslint-disable-next-line no-restricted-properties -- preserving intentional console usage. - console.warn(result.error); - } else { - // eslint-disable-next-line no-restricted-properties -- preserving intentional console usage. - console.warn( - `Failed to compile ${result.sourceFile}: ${result.error.message}`, - ); - } -} - -function updateEvaluators( - evaluators: EvaluatorState, - buildResults: BtBuildResult[], - opts: EvaluatorOpts, -) { - for (const result of buildResults) { - if (result.type === "failure") { - handleBuildFailure({ - result, - terminateOnFailure: opts.terminateOnFailure, - showDetailedErrors: opts.showDetailedErrors, - }); - continue; - } - - for (const evaluator of Object.values(result.evaluator.evaluators)) { - evaluators.evaluators.push({ - sourceFile: result.sourceFile, - // eslint-disable-next-line @typescript-eslint/consistent-type-assertions - evaluator: evaluator.evaluator as EvaluatorDef< - unknown, - unknown, - unknown, - BaseMetadata - >, - reporter: evaluator.reporter, - }); - } - - for (const [reporterName, reporter] of Object.entries( - result.evaluator.reporters, - )) { - if ( - evaluators.reporters[reporterName] && - evaluators.reporters[reporterName] !== reporter - ) { - // eslint-disable-next-line no-restricted-properties -- preserving intentional console usage. - console.warn( - warning( - `Reporter '${reporterName}' already exists. Will skip '${reporterName}' from ${result.sourceFile}.`, - ), - ); - continue; - } - evaluators.reporters[reporterName] = reporter; - } - } -} - -async function runAndWatch({ - handles, - onExit, -}: { - handles: Record; - onExit?: () => void; -}) { - const count = Object.keys(handles).length; - // eslint-disable-next-line no-restricted-properties -- preserving intentional console usage. - console.error(`Watching ${pluralize("file", count, true)}...`); - - Object.values(handles).map((handle) => handle.watch()); - - ["SIGINT", "SIGTERM"].forEach((signal: string) => { - process.on(signal, function () { - // eslint-disable-next-line no-restricted-properties -- preserving intentional console usage. - console.error("Stopped watching."); - for (const handle of Object.values(handles)) { - handle.destroy(); - } - onExit?.(); - process.exit(0); - }); - }); - - // Wait forever while we watch. - await new Promise(() => {}); -} - -export async function buildEvaluators( - handles: Record, - opts: EvaluatorOpts, -): Promise<{ evaluators: EvaluatorState; buildResults: BtBuildResult[] }> { - const buildPromises = Object.values(handles).map((handle) => - handle.rebuild(), - ); - - const buildResults = await Promise.all(buildPromises); - - const evaluators: EvaluatorState = { - evaluators: [], - reporters: {}, - }; - updateEvaluators(evaluators, buildResults, opts); - return { evaluators, buildResults }; -} - -async function runOnce( - handles: Record, - opts: EvaluatorOpts, -) { - const bundlePromises = opts.bundle - ? Object.fromEntries( - Object.entries(handles).map(([inFile, handle]) => [ - inFile, - handle.bundle(), - ]), - ) - : null; - - const { evaluators, buildResults } = await buildEvaluators(handles, opts); - - if (opts.list) { - for (const evaluator of evaluators.evaluators) { - // eslint-disable-next-line no-restricted-properties -- preserving intentional console usage. - console.log(evaluator.evaluator.evalName); - } - return true; - } - - // map from file name -> eval name -> experiment - const evalToExperiment: Record> = {}; - - const resultPromises = evaluators.evaluators.map(async (evaluator) => { - const evalData = callEvaluatorData(evaluator.evaluator.data); - // TODO: For now, use the eval name as the project. However, we need to evolve - // the definition of a project and create a new concept called run, so that we - // can name the experiment/evaluation within the run the evaluator's name. - const logger = opts.noSendLogs - ? null - : await initExperiment(evaluator.evaluator, evalData); - try { - return await runEvaluator( - logger, - { - ...evaluator.evaluator, - data: evalData.data, - }, - opts.progressReporter, - opts.filters, - undefined, - undefined, - ); - } finally { - if (logger) { - if (!evalToExperiment[evaluator.sourceFile]) { - evalToExperiment[evaluator.sourceFile] = {}; - } - evalToExperiment[evaluator.sourceFile][evaluator.evaluator.evalName] = - logger; - - await logger.flush(); - } - } - }); - - // eslint-disable-next-line no-restricted-properties -- preserving intentional console usage. - console.error( - styleText( - "dim", - `Processing ${styleText("bold", String(resultPromises.length))} evaluator${resultPromises.length === 1 ? "" : "s"}...`, - ), - ); - const allEvalsResults = await Promise.all(resultPromises); - opts.progressReporter.stop(); - // eslint-disable-next-line no-restricted-properties -- preserving intentional console usage. - console.error(""); - - const evalReports: Record< - string, - { - reporter: ReporterDef; - results: unknown[]; - } - > = {}; - for (let idx = 0; idx < evaluators.evaluators.length; idx++) { - const evaluator = evaluators.evaluators[idx]; - const resolvedReporter = resolveReporter( - evaluator.reporter, - evaluators.reporters, - ); - - const report = resolvedReporter.reportEval( - evaluator.evaluator, - // eslint-disable-next-line @typescript-eslint/consistent-type-assertions - allEvalsResults[idx as number], - { - verbose: opts.showDetailedErrors, - jsonl: opts.jsonl, - }, - ); - - addReport(evalReports, resolvedReporter, report); - } - - if (bundlePromises !== null && Object.entries(evalToExperiment).length > 0) { - await uploadHandleBundles({ - buildResults: buildResults.filter( - // We handle errors above, so it's fine to filter down to successes here. - (result): result is BuildSuccess => result.type === "success", - ), - evalToExperiment, - bundlePromises, - handles, - setCurrent: opts.setCurrent, - defaultIfExists: "replace", - showDetailedErrors: opts.showDetailedErrors, - }); - } - - let allSuccess = true; - for (const [_reporterName, { reporter, results }] of Object.entries( - evalReports, - )) { - const success = await reporter.reportRun(await Promise.all(results)); - allSuccess = allSuccess && success; - } - - return allSuccess; -} - -function checkMatch( - pathInput: string, - include_patterns: string[] | null, - exclude_patterns: string[] | null, -): boolean { - const p = path.resolve(pathInput); - if (include_patterns !== null) { - let include = false; - for (const pattern of include_patterns) { - if (minimatch(p, pattern)) { - include = true; - break; - } - } - if (!include) { - return false; - } - } - - if (exclude_patterns !== null) { - let exclude = false; - for (const pattern of exclude_patterns) { - if (minimatch(p, pattern)) { - exclude = true; - break; - } - } - - return !exclude; - } - - return true; -} - -async function collectFiles( - inputPath: string, - mode: "eval" | "bundle", -): Promise { - let pathStat = null; - try { - pathStat = fs.lstatSync(inputPath); - } catch (e) { - // eslint-disable-next-line no-restricted-properties -- preserving intentional console usage. - console.error(error(`Error reading ${inputPath}: ${e}`)); - process.exit(1); - } - - let files: string[] = []; - if (!pathStat.isDirectory()) { - if ( - !checkMatch( - inputPath, - mode === "eval" ? INCLUDE_EVAL : INCLUDE_BUNDLE, - EXCLUDE, - ) - ) { - const prefix = mode === "eval" ? ".eval" : ""; - // eslint-disable-next-line no-restricted-properties -- preserving intentional console usage. - console.warn( - warning( - `Reading ${inputPath} because it was specified directly. Rename it to end in ${prefix}.ts or ` + - `${prefix}.js to include it automatically when you specify a directory.`, - ), - ); - } - files.push(inputPath); - } else { - const walked = await util.promisify(fsWalk.walk)(inputPath, { - deepFilter: (entry) => { - return checkMatch(entry.path, null, EXCLUDE); - }, - entryFilter: (entry) => { - return ( - entry.dirent.isFile() && - checkMatch( - entry.path, - mode === "eval" ? INCLUDE_EVAL : INCLUDE_BUNDLE, - EXCLUDE, - ) - ); - }, - }); - - files = files.concat(walked.map((entry) => entry.path)); - } - - return files; -} - -import { createMarkKnownPackagesExternalPlugin } from "./util/external-packages-plugin"; - -// Inspired by and modified from https://github.com/evanw/esbuild/issues/1051 -const nativeNodeModulesPlugin = { - name: "native-node-modules", - setup(build: esbuild.PluginBuild) { - // Keep track of packages that contain .node files - const nativePackages = new Set(); - - // Helper to add a package and its platform-specific variants - const addNativePackage = (pkgName: string) => { - nativePackages.add(pkgName); - if (pkgName.includes("@")) { - const [scope, name] = pkgName.split("/"); - const platformPkgs = [ - `${scope}/${name}-darwin-arm64`, - `${scope}/${name}-darwin-x64`, - `${scope}/${name}-linux-x64-gnu`, - `${scope}/${name}-win32-x64-msvc`, - ]; - platformPkgs.forEach((pkg) => nativePackages.add(pkg)); - } - }; - - // When a .node file is imported, mark its package as native - build.onResolve({ filter: /\.node$/ }, (args) => { - try { - const path = require.resolve(args.path, { paths: [args.resolveDir] }); - const match = path.match( - /node_modules[/\\]((?:@[^/\\]+[/\\])?[^/\\]+)/, - ); - if (match) { - addNativePackage(match[1]); - } - } catch { - // Ignore errors - } - return { path: args.path, external: true }; - }); - - // Handle direct imports of native packages - build.onResolve( - { filter: /@[^/]+\/[^/]+-(?:darwin|linux|win32)/ }, - (args) => { - const match = args.path.match(/^(@[^/]+\/[^/]+)/); - if (match) { - addNativePackage(match[1]); - } - return { path: args.path, external: true }; - }, - ); - - // Mark all imports from native packages as external - build.onResolve({ filter: /.*/ }, (args) => { - if (!args.path.startsWith(".") && !args.path.startsWith("/")) { - const match = args.path.match(/^(?:@[^/]+\/)?[^/]+/); - if (match && nativePackages.has(match[0])) { - return { path: require.resolve(args.path), external: true }; - } - } - return null; - }); - }, -}; - -export type PluginMaker = (fileName: string) => esbuild.Plugin; - -function buildOpts({ - fileName, - outFile, - tsconfig, - plugins: argPlugins, - externalPackages, - markKnownPackagesExternal = true, -}: { - fileName: string; - outFile: string; - tsconfig?: string; - plugins?: PluginMaker[]; - externalPackages?: string[]; - markKnownPackagesExternal?: boolean; -}): esbuild.BuildOptions { - const plugins = [ - braintrustEsbuildPlugin(), - nativeNodeModulesPlugin, - ...(markKnownPackagesExternal - ? [createMarkKnownPackagesExternalPlugin(externalPackages)] - : []), - ...(argPlugins || []).map((fn) => fn(fileName)), - ]; - return { - entryPoints: [fileName], - bundle: true, - treeShaking: true, - outfile: outFile, - platform: "node", - write: false, - // Remove the leading "v" from process.version - target: `node${process.version.slice(1)}`, - tsconfig, - external: ["node_modules/*", "fsevents"], - plugins: plugins, - }; -} - -export async function initializeHandles({ - files: inputFiles, - mode, - plugins, - tsconfig, - externalPackages, -}: { - files: string[]; - mode: "eval" | "bundle"; - plugins?: PluginMaker[]; - tsconfig?: string; - externalPackages?: string[]; -}): Promise> { - const files: Record = {}; - const inputPaths = inputFiles.length > 0 ? inputFiles : ["."]; - for (const inputPath of inputPaths) { - const newFiles = await collectFiles(inputPath, mode); - if (newFiles.length == 0) { - // eslint-disable-next-line no-restricted-properties -- preserving intentional console usage. - console.warn( - warning( - `Provided path ${inputPath} is not an eval file or a directory containing eval files, skipping...`, - ), - ); - } - for (const file of newFiles) { - files[path.resolve(file)] = true; - } - } - - if (Object.keys(files).length == 0) { - // eslint-disable-next-line no-restricted-properties -- preserving intentional console usage. - console.warn( - warning("No eval files were found in any of the provided paths."), - ); - process.exit(0); - } - - const tmpDir = path.join(os.tmpdir(), `btevals-${uuidv4().slice(0, 8)}`); - // fs.mkdirSync(tmpDir, { recursive: true }); - - const initPromises = []; - for (const file of Object.keys(files)) { - const baseName = `${path.basename( - file, - path.extname(file), - )}-${uuidv4().slice(0, 8)}`; - const outFile = path.join(tmpDir, `${baseName}.${OUT_EXT}`); - const bundleFile = path.join(tmpDir, `${baseName}.bundle.js`); - initPromises.push( - initFile({ - inFile: file, - outFile, - bundleFile, - plugins, - tsconfig, - externalPackages, - }), - ); - } - - const handles: Record = {}; - const initResults = await Promise.all(initPromises); - for (const result of initResults) { - handles[result.inFile] = result; - } - return handles; -} - -async function run(args: RunArgs) { - normalizeDebugLoggingArgs(args); - // Load the environment variables from the .env files using the same rules as Next.js - loadEnvConfig(process.cwd(), true); - - if (args.env_file) { - // Load via dotenv library - const loaded = dotenv.config({ path: args.env_file }); - if (loaded.error) { - // eslint-disable-next-line no-restricted-properties -- preserving intentional console usage. - console.error(error(`Error loading ${args.env_file}: ${loaded.error}`)); - process.exit(1); - } - } - - const evaluatorOpts: EvaluatorOpts = { - showDetailedErrors: shouldShowDetailedErrors(args.debug_logging), - apiKey: args.api_key, - orgName: args.org_name, - appUrl: args.app_url, - noSendLogs: !!args.no_send_logs, - bundle: !!args.bundle || !!args.push, - setCurrent: !!args.push, - terminateOnFailure: !!args.terminate_on_failure, - watch: !!args.watch, - jsonl: args.jsonl, - progressReporter: args.no_progress_bars - ? new SimpleProgressReporter() - : new BarProgressReporter(), - filters: args.filter ? parseFilters(args.filter) : [], - list: !!args.list, - }; - - if (args.list && args.watch) { - // eslint-disable-next-line no-restricted-properties -- preserving intentional console usage. - console.error(error("Cannot specify both --list and --watch.")); - process.exit(1); - } - - const plugins = evaluatorOpts.watch - ? [ - (fileName: string) => - buildWatchPluginForEvaluator(fileName, evaluatorOpts), - ] - : []; - - const handles = await initializeHandles({ - files: args.files, - mode: "eval", - tsconfig: args.tsconfig, - plugins, - externalPackages: args.external_packages, - }); - - if (args.dev) { - // XXX We should watch these files (or support a --watch flag). - const { evaluators } = await buildEvaluators(handles, evaluatorOpts); - const allEvaluators = Object.values(evaluators.evaluators).map( - (e) => e.evaluator, - ); - runDevServer(allEvaluators, { - host: args.dev_host, - port: args.dev_port, - orgName: args.dev_org_name, - }); - return; - } - - let success = true; - try { - if (!evaluatorOpts.noSendLogs) { - await login({ - apiKey: args.api_key, - orgName: args.org_name, - appUrl: args.app_url, - debugLogLevel: args.debug_logging, - }); - } - - if (args.watch) { - await runAndWatch({ - handles, - onExit: () => { - evaluatorOpts.progressReporter.stop(); - }, - }); - } else { - success = await runOnce(handles, evaluatorOpts); - } - } finally { - // ESBuild can freeze up if you do not clean up the handles properly - for (const handle of Object.values(handles)) { - await handle.destroy(); - } - } - - if (!success) { - process.exit(1); - } -} - -function addAuthArgs(parser: ArgumentParser) { - parser.add_argument("--api-key", { - help: "Specify a braintrust api key. If the parameter is not specified, BRAINTRUST_API_KEY or the nearest .env.braintrust file will be used.", - }); - parser.add_argument("--org-name", { - help: "The name of a specific organization to connect to. This is useful if you belong to multiple.", - }); - parser.add_argument("--app-url", { - help: "Specify a custom braintrust app url. Defaults to https://www.braintrust.dev. This is only necessary if you are using an experimental version of Braintrust", - }); - parser.add_argument("--env-file", { - help: "A path to a .env file containing environment variables to load (via dotenv).", - }); -} - -function addDebugLoggingArg(parser: ArgumentParser) { - parser.add_argument("--debug-logging", { - choices: ["error", "warn", "info", "debug"], - help: "Enable internal Braintrust SDK troubleshooting output. Use 'error', 'warn', 'info', or 'debug' to control the log level.", - }); -} - -function addCompileArgs(parser: ArgumentParser) { - parser.add_argument("--terminate-on-failure", { - action: "store_true", - help: "If provided, terminates on a failing eval, instead of the default (moving onto the next one).", - }); - parser.add_argument("--tsconfig", { - help: "Specify a custom tsconfig.json file to use.", - }); - parser.add_argument("--external-packages", { - nargs: "*", - help: "Additional packages to mark as external during bundling. These packages will not be included in the bundle and must be available at runtime. Use this to resolve bundling errors with native modules or problematic dependencies. Example: --external-packages sqlite3 fsevents @mapbox/node-pre-gyp", - }); -} - -async function main() { - const parser = new ArgumentParser({ - description: "Braintrust CLI", - }); - - parser.add_argument("-v", "--version", { action: "version", version }); - - const parentParser = new ArgumentParser({ add_help: false }); - parentParser.add_argument("--verbose", { - action: "store_true", - help: "Deprecated alias for --debug-logging debug. Use --debug-logging debug to include full stack traces and detailed troubleshooting output.", - }); - - const subparser = parser.add_subparsers({ - required: true, - }); - - const parser_run = subparser.add_parser("eval", { - help: "Run evals locally.", - parents: [parentParser], - }); - addAuthArgs(parser_run); - addDebugLoggingArg(parser_run); - parser_run.add_argument("--filter", { - help: "Only run evaluators that match these filters. Each filter is a regular expression (https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/RegExp). For example, --filter metadata.priority='^P0$' input.name='foo.*bar' will only run evaluators that have metadata.priority equal to 'P0' and input.name matching the regular expression 'foo.*bar'.", - nargs: "*", - }); - parser_run.add_argument("--list", { - help: "List, but do not execute, evaluators.", - action: "store_true", - }); - parser_run.add_argument("--jsonl", { - action: "store_true", - help: "Format score summaries as jsonl, i.e. one JSON-formatted line per summary.", - }); - addCompileArgs(parser_run); - parser_run.add_argument("--watch", { - action: "store_true", - help: "Watch files for changes and rerun evals when changes are detected", - }); - parser_run.add_argument("--no-send-logs", { - action: "store_true", - help: "Do not send logs to Braintrust. Useful for testing evaluators without uploading results.", - }); - parser_run.add_argument("--no-progress-bars", { - action: "store_true", - help: "Do not show progress bars when processing evaluators.", - }); - parser_run.add_argument("--bundle", { - action: "store_true", - help: "Experimental (do not use unless you know what you're doing)", - }); - parser_run.add_argument("--push", { - action: "store_true", - help: "Push the scorers from the current run to Braintrust. This will mark the current run's scorers as the latest version in the project.", - }); - parser_run.add_argument("files", { - nargs: "*", - help: "A list of files or directories to run. If no files are specified, the current directory is used.", - }); - parser_run.add_argument("--dev", { - action: "store_true", - help: "Run the evaluators in dev mode. This will start a dev server which you can connect to via the playground's remote evals feature.", - }); - parser_run.add_argument("--dev-host", { - help: "The host to bind the dev server to. Defaults to localhost. Set to 0.0.0.0 to bind to all interfaces.", - type: String, - default: "localhost", - }); - parser_run.add_argument("--dev-port", { - help: "The port to bind the dev server to. Defaults to 8300.", - type: Number, - default: 8300, - }); - parser_run.add_argument("--dev-org-name", { - help: "Only allow users that belong this to this org name to run remote evals.", - type: String, - }); - parser_run.set_defaults({ func: run }); - - const parser_push = subparser.add_parser("push", { - help: "Bundle prompts, tools, scorers, and other resources into Braintrust", - }); - addAuthArgs(parser_push); - addDebugLoggingArg(parser_push); - addCompileArgs(parser_push); - parser_push.add_argument("files", { - nargs: "*", - help: "A list of files or directories containing functions to bundle. If no files are specified, the current directory is used.", - }); - parser_push.add_argument("--if-exists", { - choices: ["error", "replace", "ignore"], - default: "error", - help: "What to do if a function with the same slug already exists. 'error' will cause an error and abort. 'replace' will overwrite the existing function. 'ignore' will ignore the push for this function and continue.", - }); - parser_push.set_defaults({ func: bundleCommand }); - - const parser_pull = subparser.add_parser("pull", { - help: "Pull prompts, tools, scorers, and other resources from Braintrust to save in your codebase.", - }); - addDebugLoggingArg(parser_pull); - parser_pull.add_argument("--output-dir", { - help: "The directory to output the pulled resources to. If not specified, the current directory is used.", - }); - parser_pull.add_argument("--project-name", { - help: "The name of the project to pull from. If not specified, all projects are pulled.", - }); - parser_pull.add_argument("--project-id", { - help: "The id of the project to pull from. If not specified, all projects are pulled.", - }); - parser_pull.add_argument("--id", { - help: "The id of a specific function to pull.", - }); - parser_pull.add_argument("--slug", { - help: "The slug of a specific function to pull.", - }); - parser_pull.add_argument("--version", { - help: "The version to pull. Will pull the latest version of each prompt that is at or before this version.", - }); - parser_pull.add_argument("--force", { - action: "store_true", - help: "Overwrite local files if they have uncommitted changes.", - }); - parser_pull.set_defaults({ func: pullCommand }); - - const parsed = normalizeDebugLoggingArgs(parser.parse_args()); - - try { - await parsed.func(parsed); - } catch (e) { - logError(e, shouldShowDetailedErrors(parsed.debug_logging)); - process.exit(1); - } -} - -if (require.main === module) { - void main(); -} diff --git a/js/src/cli/integration.test.ts b/js/src/cli/integration.test.ts deleted file mode 100644 index ae20ec41f..000000000 --- a/js/src/cli/integration.test.ts +++ /dev/null @@ -1,285 +0,0 @@ -import { describe, it, expect } from "vitest"; -import { CompileArgs, RunArgs, BundleArgs, PullArgs } from "./util/types"; - -// Test the type definitions for external packages -describe("External Packages Type Definitions", () => { - describe("CompileArgs interface", () => { - it("should have external_packages property", () => { - const args: CompileArgs = { - tsconfig: "tsconfig.json", - terminate_on_failure: false, - external_packages: ["sqlite3", "fsevents"], - }; - - expect(args.external_packages).toEqual(["sqlite3", "fsevents"]); - }); - - it("should allow undefined external_packages", () => { - const args: CompileArgs = { - tsconfig: "tsconfig.json", - terminate_on_failure: false, - external_packages: undefined, - }; - - expect(args.external_packages).toBeUndefined(); - }); - - it("should allow empty external_packages array", () => { - const args: CompileArgs = { - tsconfig: "tsconfig.json", - terminate_on_failure: false, - external_packages: [], - }; - - expect(args.external_packages).toEqual([]); - }); - }); - - describe("RunArgs interface", () => { - it("should inherit external_packages from CompileArgs", () => { - const args: RunArgs = { - verbose: false, - api_key: "test-key", - org_name: "test-org", - app_url: "https://test.com", - env_file: ".env", - debug_logging: "info", - tsconfig: "tsconfig.json", - terminate_on_failure: false, - external_packages: ["@mapbox/node-pre-gyp"], - files: ["test.eval.ts"], - watch: false, - list: false, - jsonl: false, - filter: undefined, - no_send_logs: false, - no_progress_bars: false, - bundle: false, - push: false, - dev: false, - dev_host: "localhost", - dev_port: 8300, - }; - - expect(args.external_packages).toEqual(["@mapbox/node-pre-gyp"]); - expect(args.debug_logging).toBe("info"); - }); - }); - - describe("BundleArgs interface", () => { - it("should inherit external_packages from CompileArgs", () => { - const args: BundleArgs = { - verbose: false, - api_key: "test-key", - org_name: "test-org", - app_url: "https://test.com", - env_file: ".env", - debug_logging: "debug", - tsconfig: "tsconfig.json", - terminate_on_failure: false, - external_packages: ["sqlite3", "sharp"], - files: ["functions.ts"], - if_exists: "error", - }; - - expect(args.external_packages).toEqual(["sqlite3", "sharp"]); - expect(args.debug_logging).toBe("debug"); - }); - }); - - describe("PullArgs interface", () => { - it("should include debug_logging from CommonArgs", () => { - const args: PullArgs = { - verbose: false, - output_dir: "./braintrust", - project_name: "test-project", - force: false, - debug_logging: "info", - }; - - expect(args.debug_logging).toBe("info"); - }); - }); -}); - -// Test the buildOpts function parameter flow -describe("buildOpts Function Integration", () => { - // This is a simplified version of the buildOpts function signature - interface BuildOptsParams { - fileName: string; - outFile: string; - tsconfig?: string; - plugins?: any[]; - externalPackages?: string[]; - } - - it("should accept externalPackages parameter", () => { - const params: BuildOptsParams = { - fileName: "test.ts", - outFile: "test.js", - tsconfig: "tsconfig.json", - plugins: [], - externalPackages: ["sqlite3", "fsevents"], - }; - - expect(params.externalPackages).toEqual(["sqlite3", "fsevents"]); - }); - - it("should handle undefined externalPackages", () => { - const params: BuildOptsParams = { - fileName: "test.ts", - outFile: "test.js", - tsconfig: "tsconfig.json", - plugins: [], - externalPackages: undefined, - }; - - expect(params.externalPackages).toBeUndefined(); - }); - - it("should handle empty externalPackages array", () => { - const params: BuildOptsParams = { - fileName: "test.ts", - outFile: "test.js", - tsconfig: "tsconfig.json", - plugins: [], - externalPackages: [], - }; - - expect(params.externalPackages).toEqual([]); - }); -}); - -// Test the parameter flow from CLI to buildOpts -describe("Parameter Flow Integration", () => { - it("should flow external_packages from RunArgs to buildOpts", () => { - // Simulate the parameter flow: RunArgs -> initializeHandles -> initFile -> buildOpts - const runArgs: Partial = { - external_packages: ["sqlite3", "@mapbox/node-pre-gyp"], - }; - - // This would be passed to initializeHandles - const initializeHandlesParams = { - files: ["test.eval.ts"], - mode: "eval" as const, - tsconfig: undefined, - plugins: undefined, - externalPackages: runArgs.external_packages, - }; - - expect(initializeHandlesParams.externalPackages).toEqual([ - "sqlite3", - "@mapbox/node-pre-gyp", - ]); - - // This would be passed to initFile - const initFileParams = { - inFile: "test.eval.ts", - outFile: "test.js", - bundleFile: "test.bundle.js", - tsconfig: undefined, - plugins: undefined, - externalPackages: initializeHandlesParams.externalPackages, - }; - - expect(initFileParams.externalPackages).toEqual([ - "sqlite3", - "@mapbox/node-pre-gyp", - ]); - - // This would be passed to buildOpts - const buildOptsParams = { - fileName: initFileParams.inFile, - outFile: initFileParams.outFile, - tsconfig: initFileParams.tsconfig, - plugins: initFileParams.plugins, - externalPackages: initFileParams.externalPackages, - }; - - expect(buildOptsParams.externalPackages).toEqual([ - "sqlite3", - "@mapbox/node-pre-gyp", - ]); - }); - - it("should flow external_packages from BundleArgs to buildOpts", () => { - // Simulate the parameter flow: BundleArgs -> initializeHandles -> initFile -> buildOpts - const bundleArgs: Partial = { - external_packages: ["sharp", "canvas"], - }; - - // This would be passed to initializeHandles - const initializeHandlesParams = { - files: ["functions.ts"], - mode: "bundle" as const, - tsconfig: undefined, - plugins: undefined, - externalPackages: bundleArgs.external_packages, - }; - - expect(initializeHandlesParams.externalPackages).toEqual([ - "sharp", - "canvas", - ]); - - // This would be passed to buildOpts - const buildOptsParams = { - fileName: "functions.ts", - outFile: "functions.js", - tsconfig: undefined, - plugins: undefined, - externalPackages: initializeHandlesParams.externalPackages, - }; - - expect(buildOptsParams.externalPackages).toEqual(["sharp", "canvas"]); - }); -}); - -// Test real-world usage scenarios -describe("Real-world Usage Scenarios", () => { - it("should handle autoevals dependency issue scenario", () => { - const args: Partial = { - files: ["autoevals-test.eval.ts"], - external_packages: ["@mapbox/node-pre-gyp", "mock-aws-s3", "aws-sdk"], - }; - - // These packages would be added to the external list - const expectedExternals = [ - // Hardcoded externals - "braintrust", - "autoevals", - "@braintrust/", - "config", - "lightningcss", - "@mapbox/node-pre-gyp", - // CLI-specified externals - ...args.external_packages!, - ]; - - expect(expectedExternals).toContain("@mapbox/node-pre-gyp"); - expect(expectedExternals).toContain("mock-aws-s3"); - expect(expectedExternals).toContain("aws-sdk"); - }); - - it("should handle database packages scenario", () => { - const args: Partial = { - files: ["database-test.eval.ts"], - external_packages: ["sqlite3", "better-sqlite3", "mysql2"], - }; - - expect(args.external_packages).toEqual([ - "sqlite3", - "better-sqlite3", - "mysql2", - ]); - }); - - it("should handle native modules scenario", () => { - const args: Partial = { - files: ["image-processing.eval.ts"], - external_packages: ["sharp", "canvas", "node-sass"], - }; - - expect(args.external_packages).toEqual(["sharp", "canvas", "node-sass"]); - }); -}); diff --git a/js/src/cli/jest/nodeModulesPaths.ts b/js/src/cli/jest/nodeModulesPaths.ts deleted file mode 100644 index f04748e9a..000000000 --- a/js/src/cli/jest/nodeModulesPaths.ts +++ /dev/null @@ -1,74 +0,0 @@ -/** - * Copyright (c) Meta Platforms, Inc. and affiliates. - * - * This source code is licensed under the MIT license found in the - * LICENSE file in the root directory of this source tree. - * - * Adapted from: https://github.com/substack/node-resolve - */ - -import * as path from "node:path"; -import { realpathSync } from "node:fs"; - -type NodeModulesPathsOptions = { - moduleDirectory?: Array; - paths?: Array; -}; - -export default function nodeModulesPaths( - basedir: string, - options: NodeModulesPathsOptions, -): Array { - const modules = - options && options.moduleDirectory - ? Array.from(options.moduleDirectory) - : ["node_modules"]; - - // ensure that `basedir` is an absolute path at this point, - // resolving against the process' current working directory - const basedirAbs = path.resolve(basedir); - - let prefix = "/"; - if (/^([A-Za-z]:)/.test(basedirAbs)) { - prefix = ""; - } else if (/^\\\\/.test(basedirAbs)) { - prefix = "\\\\"; - } - - // The node resolution algorithm (as implemented by NodeJS and TypeScript) - // traverses parents of the physical path, not the symlinked path - let physicalBasedir; - try { - physicalBasedir = realpathSync.native(basedirAbs); - } catch { - // realpath can throw, e.g. on mapped drives - physicalBasedir = basedirAbs; - } - - const paths: Array = [physicalBasedir]; - let parsed = path.parse(physicalBasedir); - while (parsed.dir !== paths[paths.length - 1]) { - paths.push(parsed.dir); - parsed = path.parse(parsed.dir); - } - - const dirs = paths.reduce>((dirs, aPath) => { - for (const moduleDir of modules) { - if (path.isAbsolute(moduleDir)) { - if (aPath === basedirAbs && moduleDir) { - dirs.push(moduleDir); - } - } else { - dirs.push(path.join(prefix, aPath, moduleDir)); - } - } - - return dirs; - }, []); - - if (options.paths) { - dirs.push(...options.paths); - } - - return dirs; -} diff --git a/js/src/cli/progress.ts b/js/src/cli/progress.ts deleted file mode 100644 index befab92f6..000000000 --- a/js/src/cli/progress.ts +++ /dev/null @@ -1 +0,0 @@ -export { BarProgressReporter } from "./reporters/progress"; diff --git a/js/src/cli/reporters/eval.ts b/js/src/cli/reporters/eval.ts deleted file mode 100644 index 8f7ff8708..000000000 --- a/js/src/cli/reporters/eval.ts +++ /dev/null @@ -1,227 +0,0 @@ -import { stripVTControlCharacters, styleText } from "node:util"; -import { terminalLink } from "termi-link"; -import Table from "cli-table3"; -import pluralize from "pluralize"; - -import { ExperimentSummary, ScoreSummary, MetricSummary } from "../../logger"; -import type { ReporterDef } from "../../reporters/types"; -import { EvaluatorDef, EvalResultWithSummary } from "../../framework"; -import { isEmpty } from "../../util"; - -function visibleLength(text: string) { - return stripVTControlCharacters(text).length; -} - -function padEndVisible(text: string, targetLength: number) { - return text + " ".repeat(Math.max(0, targetLength - visibleLength(text))); -} - -function formatSummaryBox(content: string) { - const title = styleText("gray", " Experiment summary "); - const lines = content.split("\n"); - const contentWidth = Math.max( - visibleLength(title), - ...lines.map((line) => visibleLength(line) + 2), - ); - - const horizontal = "─"; - const top = - styleText("gray", "╭") + - title + - styleText( - "gray", - horizontal.repeat(contentWidth - visibleLength(title)) + "╮", - ); - const body = lines - .map( - (line) => - `${styleText("gray", "│")} ${padEndVisible(line, contentWidth - 2)} ${styleText("gray", "│")}`, - ) - .join("\n"); - const bottom = styleText("gray", "╰" + horizontal.repeat(contentWidth) + "╯"); - - return top + "\n" + body + "\n" + bottom; -} - -function formatExperimentSummaryFancy(summary: ExperimentSummary) { - let comparisonLine = ""; - if (summary.comparisonExperimentName) { - comparisonLine = `${summary.comparisonExperimentName} ${styleText("gray", "(baseline)")} ← ${summary.experimentName} ${styleText("gray", "(comparison)")}\n\n`; - } - - const tableParts: string[] = []; - - const hasScores = Object.keys(summary.scores).length > 0; - const hasMetrics = Object.keys(summary.metrics ?? {}).length > 0; - const hasComparison = !!summary.comparisonExperimentName; - - if (hasScores || hasMetrics) { - const headers = [styleText("gray", "Name"), styleText("gray", "Value")]; - - if (hasComparison) { - headers.push( - styleText("gray", "Change"), - styleText("gray", "Improvements"), - styleText("gray", "Regressions"), - ); - } - - const combinedTable = new Table({ - head: hasComparison ? headers : [], - style: { head: [], "padding-left": 0, "padding-right": 0, border: [] }, - chars: { - top: "", - "top-mid": "", - "top-left": "", - "top-right": "", - bottom: "", - "bottom-mid": "", - "bottom-left": "", - "bottom-right": "", - left: "", - "left-mid": "", - mid: "", - "mid-mid": "", - right: "", - "right-mid": "", - middle: " ", - }, - colWidths: hasComparison ? [18, 10, 10, 13, 12] : [20, 15], - colAligns: hasComparison - ? ["left", "right", "right", "right", "right"] - : ["left", "right"], - wordWrap: false, - }); - - const scoreValues: ScoreSummary[] = Object.values(summary.scores); - for (const score of scoreValues) { - const scorePercent = (score.score * 100).toFixed(2); - const scoreValue = styleText("white", `${scorePercent}%`); - - let diffString = ""; - if (!isEmpty(score.diff)) { - const diffPercent = (score.diff! * 100).toFixed(2); - const diffSign = score.diff! > 0 ? "+" : ""; - const diffColor = score.diff! > 0 ? "green" : "red"; - diffString = styleText(diffColor, `${diffSign}${diffPercent}%`); - } else { - diffString = styleText("gray", "-"); - } - - const improvements = - score.improvements > 0 - ? styleText(["dim", "green"], String(score.improvements)) - : styleText("gray", "-"); - const regressions = - score.regressions > 0 - ? styleText(["dim", "red"], String(score.regressions)) - : styleText("gray", "-"); - - const row = [`${styleText("blue", "◯")} ${score.name}`, scoreValue]; - if (hasComparison) { - row.push(diffString, improvements, regressions); - } - combinedTable.push(row); - } - - const metricValues: MetricSummary[] = Object.values(summary.metrics ?? {}); - for (const metric of metricValues) { - const fractionDigits = Number.isInteger(metric.metric) ? 0 : 2; - const formattedValue = metric.metric.toFixed(fractionDigits); - const metricValue = styleText( - "white", - metric.unit === "$" - ? `${metric.unit}${formattedValue}` - : `${formattedValue}${metric.unit}`, - ); - - let diffString = ""; - if (!isEmpty(metric.diff)) { - const diffPercent = (metric.diff! * 100).toFixed(2); - const diffSign = metric.diff! > 0 ? "+" : ""; - const diffColor = metric.diff! > 0 ? "green" : "red"; - diffString = styleText(diffColor, `${diffSign}${diffPercent}%`); - } else { - diffString = styleText("gray", "-"); - } - - const improvements = - metric.improvements > 0 - ? styleText(["dim", "green"], String(metric.improvements)) - : styleText("gray", "-"); - const regressions = - metric.regressions > 0 - ? styleText(["dim", "red"], String(metric.regressions)) - : styleText("gray", "-"); - - const row = [`${styleText("magenta", "◯")} ${metric.name}`, metricValue]; - if (hasComparison) { - row.push(diffString, improvements, regressions); - } - combinedTable.push(row); - } - - tableParts.push(combinedTable.toString()); - } - - const content = [comparisonLine, ...tableParts].filter(Boolean).join("\n"); - - const footer = summary.experimentUrl - ? terminalLink( - `View results for ${summary.experimentName}`, - summary.experimentUrl, - { fallback: () => `See results at ${summary.experimentUrl}` }, - ) - : ""; - - const boxContent = [content, footer].filter(Boolean).join("\n\n"); - - return "\n" + formatSummaryBox(boxContent); -} - -export const warning = (text: string) => styleText("yellow", text); - -export const fancyReporter: ReporterDef = { - name: "Braintrust fancy reporter", - async reportEval( - // eslint-disable-next-line @typescript-eslint/no-explicit-any - evaluator: EvaluatorDef, - // eslint-disable-next-line @typescript-eslint/no-explicit-any - result: EvalResultWithSummary, - { verbose, jsonl }: { verbose: boolean; jsonl?: boolean }, - ) { - const { results, summary } = result; - const failingResults = results.filter( - (r: { error: unknown }) => r.error !== undefined, - ); - - if (failingResults.length > 0) { - // eslint-disable-next-line no-restricted-properties -- preserving intentional console usage. - console.error( - warning( - `Evaluator ${evaluator.evalName} failed with ${pluralize("error", failingResults.length, true)}. This evaluation ("${evaluator.evalName}") will not be fully logged.`, - ), - ); - if (jsonl) { - for (const result of failingResults) { - process.stdout.write(JSON.stringify(result)); - process.stdout.write("\n"); - } - } else if (verbose) { - for (const result of failingResults) { - // eslint-disable-next-line no-restricted-properties -- preserving intentional console usage. - console.error(result); - } - } - } - - process.stdout.write( - jsonl ? JSON.stringify(summary) : formatExperimentSummaryFancy(summary), - ); - process.stdout.write("\n"); - return failingResults.length === 0; - }, - async reportRun(evalReports: boolean[]) { - return evalReports.every((r) => r); - }, -}; diff --git a/js/src/cli/reporters/progress.ts b/js/src/cli/reporters/progress.ts deleted file mode 100644 index e764709f3..000000000 --- a/js/src/cli/reporters/progress.ts +++ /dev/null @@ -1,51 +0,0 @@ -import { styleText } from "node:util"; -import * as cliProgress from "cli-progress"; - -import type { ProgressReporter } from "../../reporters/types"; - -export { SimpleProgressReporter } from "../../reporters/progress"; - -const MAX_NAME_LENGTH = 40; - -function fitNameToSpaces(name: string, length: number) { - const padded = name.padEnd(length); - if (padded.length <= length) { - return padded; - } - return padded.substring(0, length - 3) + "..."; -} - -export class BarProgressReporter implements ProgressReporter { - private multiBar: cliProgress.MultiBar; - private bars: Record = {}; - - constructor() { - this.multiBar = new cliProgress.MultiBar( - { - format: `${styleText("blueBright", "{bar}")} ${styleText("blue", "{evaluator}")} {percentage}% ${styleText("gray", "{value}/{total} {eta_formatted}")}`, - hideCursor: true, - barsize: 10, - }, - cliProgress.Presets.shades_grey, - ); - } - - public start(name: string, total: number) { - const bar = this.multiBar.create(total, 0); - this.bars[name] = bar; - } - - public stop() { - this.multiBar.stop(); - } - - public increment(name: string) { - this.bars[name].increment({ - evaluator: fitNameToSpaces(name, MAX_NAME_LENGTH), - }); - } - - public setTotal(name: string, total: number) { - this.bars[name].setTotal(total); - } -} diff --git a/js/src/cli/types.ts b/js/src/cli/types.ts deleted file mode 100644 index 15184a247..000000000 --- a/js/src/cli/types.ts +++ /dev/null @@ -1,40 +0,0 @@ -import type * as esbuild from "esbuild"; -import type { BaseMetadata } from "../logger"; -import type { EvaluatorDef, EvaluatorFile } from "../framework"; -import type { ReporterDef } from "../reporters/types"; - -export interface BuildSuccess { - type: "success"; - result: esbuild.BuildResult; - evaluator: EvaluatorFile; - sourceFile: string; -} - -export interface BuildFailure { - type: "failure"; - error: Error; - sourceFile: string; -} - -export type BtBuildResult = BuildSuccess | BuildFailure; - -export interface FileHandle { - inFile: string; - outFile: string; - bundleFile?: string; - rebuild: () => Promise; - bundle: () => Promise; - watch: () => void; - destroy: () => Promise; -} - -export interface EvaluatorState { - evaluators: { - sourceFile: string; - evaluator: EvaluatorDef; - reporter: string | ReporterDef | undefined; - }[]; - reporters: { - [reporter: string]: ReporterDef; - }; -} diff --git a/js/src/cli/util/bundle.ts b/js/src/cli/util/bundle.ts deleted file mode 100644 index 9eb18bced..000000000 --- a/js/src/cli/util/bundle.ts +++ /dev/null @@ -1,91 +0,0 @@ -import { loadEnvConfig } from "@next/env"; -import * as dotenv from "dotenv"; -import { AuthArgs, BundleArgs, CommonArgs } from "./types"; -import { error } from "../../framework"; -import type { BtBuildResult } from "../types"; -import { handleBuildFailure, initializeHandles } from "../index"; -import { login } from "../../logger"; -import { uploadHandleBundles } from "../functions/upload"; -import { - normalizeDebugLoggingArgs, - shouldShowDetailedErrors, -} from "./debug-logging"; - -export async function loadCLIEnv(args: AuthArgs & CommonArgs) { - normalizeDebugLoggingArgs(args); - // Load the environment variables from the .env files using the same rules as Next.js - loadEnvConfig(process.cwd(), true); - - if (args.env_file) { - // Load via dotenv library - const loaded = dotenv.config({ path: args.env_file }); - if (loaded.error) { - // eslint-disable-next-line no-restricted-properties -- preserving intentional console usage. - console.error(error(`Error loading ${args.env_file}: ${loaded.error}`)); - process.exit(1); - } - } - - await login({ - apiKey: args.api_key, - orgName: args.org_name, - appUrl: args.app_url, - debugLogLevel: args.debug_logging, - }); -} - -export async function bundleCommand(args: BundleArgs) { - await loadCLIEnv(args); - const showDetailedErrors = shouldShowDetailedErrors(args.debug_logging); - - const handles = await initializeHandles({ - mode: "bundle", - files: args.files, - tsconfig: args.tsconfig, - externalPackages: args.external_packages, - }); - - try { - const allBuildResultsP: Promise[] = Object.values( - handles, - ).map((handle) => handle.rebuild()); - - const bundlePromises = Object.fromEntries( - Object.entries(handles).map(([inFile, handle]) => [ - inFile, - handle.bundle(), - ]), - ); - - const allBuildResults = await Promise.all(allBuildResultsP); - const buildResults = []; - for (const buildResult of allBuildResults) { - if (buildResult.type === "failure") { - handleBuildFailure({ - result: buildResult, - terminateOnFailure: args.terminate_on_failure, - showDetailedErrors, - }); - } else { - buildResults.push(buildResult); - } - } - - const { numFailed } = await uploadHandleBundles({ - buildResults, - bundlePromises, - handles, - setCurrent: true, - showDetailedErrors, - defaultIfExists: args.if_exists, - }); - - if (numFailed > 0) { - process.exit(1); - } - } finally { - for (const handle of Object.values(handles)) { - await handle.destroy(); - } - } -} diff --git a/js/src/cli/util/debug-logging.test.ts b/js/src/cli/util/debug-logging.test.ts deleted file mode 100644 index 320911e5e..000000000 --- a/js/src/cli/util/debug-logging.test.ts +++ /dev/null @@ -1,53 +0,0 @@ -import { beforeEach, describe, expect, test, vi } from "vitest"; - -import { - normalizeDebugLoggingArgs, - resetDebugLoggingArgsForTests, - shouldShowDetailedErrors, - VERBOSE_DEPRECATION_MESSAGE, -} from "./debug-logging"; - -describe("CLI debug logging helpers", () => { - beforeEach(() => { - vi.restoreAllMocks(); - resetDebugLoggingArgsForTests(); - }); - - test("shouldShowDetailedErrors only enables detailed errors for debug", () => { - expect(shouldShowDetailedErrors(undefined)).toBe(false); - expect(shouldShowDetailedErrors("info")).toBe(false); - expect(shouldShowDetailedErrors("debug")).toBe(true); - }); - - test("normalizeDebugLoggingArgs upgrades verbose to debug and warns once", () => { - const warnSpy = vi.spyOn(console, "warn").mockImplementation(() => {}); - const args = { - verbose: true, - debug_logging: undefined as - | "error" - | "warn" - | "info" - | "debug" - | undefined, - }; - - normalizeDebugLoggingArgs(args); - normalizeDebugLoggingArgs(args); - - expect(args.debug_logging).toBe("debug"); - expect(warnSpy).toHaveBeenCalledTimes(1); - expect(warnSpy).toHaveBeenCalledWith( - `Warning: ${VERBOSE_DEPRECATION_MESSAGE}`, - ); - }); - - test("normalizeDebugLoggingArgs preserves explicit debug logging", () => { - const warnSpy = vi.spyOn(console, "warn").mockImplementation(() => {}); - const args = { verbose: true, debug_logging: "info" as const }; - - normalizeDebugLoggingArgs(args); - - expect(args.debug_logging).toBe("info"); - expect(warnSpy).toHaveBeenCalledTimes(1); - }); -}); diff --git a/js/src/cli/util/debug-logging.ts b/js/src/cli/util/debug-logging.ts deleted file mode 100644 index 544514a11..000000000 --- a/js/src/cli/util/debug-logging.ts +++ /dev/null @@ -1,37 +0,0 @@ -import { warning } from "../../framework"; -import type { CommonArgs } from "./types"; - -export const VERBOSE_DEPRECATION_MESSAGE = - "--verbose is deprecated and will be removed in a future version of braintrust. Use --debug-logging debug to see full stack traces and troubleshooting details."; - -let hasWarnedAboutVerboseFlag = false; - -export function shouldShowDetailedErrors( - debugLogLevel: CommonArgs["debug_logging"] | undefined, -): boolean { - return debugLogLevel === "debug"; -} - -export function normalizeDebugLoggingArgs< - T extends Pick, ->(args: T): T { - if (!args.verbose) { - return args; - } - - if (!hasWarnedAboutVerboseFlag) { - hasWarnedAboutVerboseFlag = true; - // eslint-disable-next-line no-restricted-properties -- preserving intentional console usage. - console.warn(warning(VERBOSE_DEPRECATION_MESSAGE)); - } - - if (!args.debug_logging) { - args.debug_logging = "debug"; - } - - return args; -} - -export function resetDebugLoggingArgsForTests(): void { - hasWarnedAboutVerboseFlag = false; -} diff --git a/js/src/cli/util/external-packages-plugin.test.ts b/js/src/cli/util/external-packages-plugin.test.ts deleted file mode 100644 index 34011d05f..000000000 --- a/js/src/cli/util/external-packages-plugin.test.ts +++ /dev/null @@ -1,319 +0,0 @@ -import { describe, it, expect, vi } from "vitest"; -import { createMarkKnownPackagesExternalPlugin } from "./external-packages-plugin"; - -describe("External Packages Plugin", () => { - describe("createMarkKnownPackagesExternalPlugin", () => { - it("should create a plugin with the correct name", () => { - const plugin = createMarkKnownPackagesExternalPlugin(); - expect(plugin.name).toBe("make-known-packages-external"); - }); - - it("should mark hardcoded packages as external", () => { - const plugin = createMarkKnownPackagesExternalPlugin(); - const mockBuild = { - onResolve: vi.fn(), - }; - - plugin.setup(mockBuild as any); - - expect(mockBuild.onResolve).toHaveBeenCalledWith( - { filter: expect.any(RegExp) }, - expect.any(Function), - ); - - // Get the filter regex that was passed - const [{ filter }] = mockBuild.onResolve.mock.calls[0]; - const resolveFunction = mockBuild.onResolve.mock.calls[0][1]; - - // Test hardcoded packages - expect(filter.test("braintrust")).toBe(true); - expect(filter.test("autoevals")).toBe(true); - expect(filter.test("@mapbox/node-pre-gyp")).toBe(true); - expect(filter.test("config")).toBe(true); - expect(filter.test("lightningcss")).toBe(true); - - // Test that the resolve function returns external: true - expect(resolveFunction({ path: "braintrust" })).toEqual({ - path: "braintrust", - external: true, - }); - }); - - it("should not mark non-matching packages as external", () => { - const plugin = createMarkKnownPackagesExternalPlugin(); - const mockBuild = { - onResolve: vi.fn(), - }; - - plugin.setup(mockBuild as any); - - const [{ filter }] = mockBuild.onResolve.mock.calls[0]; - - // Test packages that should NOT match - expect(filter.test("react")).toBe(false); - expect(filter.test("lodash")).toBe(false); - expect(filter.test("@types/node")).toBe(false); - expect(filter.test("some-random-package")).toBe(false); - }); - - it("should include additional packages from CLI", () => { - const additionalPackages = ["sqlite3", "fsevents", "@scope/package"]; - const plugin = createMarkKnownPackagesExternalPlugin(additionalPackages); - const mockBuild = { - onResolve: vi.fn(), - }; - - plugin.setup(mockBuild as any); - - const [{ filter }] = mockBuild.onResolve.mock.calls[0]; - - // Test that additional packages are included - expect(filter.test("sqlite3")).toBe(true); - expect(filter.test("fsevents")).toBe(true); - expect(filter.test("@scope/package")).toBe(true); - - // Test that hardcoded packages still work - expect(filter.test("braintrust")).toBe(true); - expect(filter.test("@mapbox/node-pre-gyp")).toBe(true); - }); - - it("should handle packages with subpaths", () => { - const plugin = createMarkKnownPackagesExternalPlugin(); - const mockBuild = { - onResolve: vi.fn(), - }; - - plugin.setup(mockBuild as any); - - const [{ filter }] = mockBuild.onResolve.mock.calls[0]; - - // Test subpaths - expect(filter.test("braintrust/core")).toBe(true); - expect(filter.test("braintrust/dist/index.js")).toBe(true); - expect( - filter.test("@mapbox/node-pre-gyp/lib/util/nw-pre-gyp/index.html"), - ).toBe(true); - }); - - it("should handle special characters in package names", () => { - const additionalPackages = [ - "@scope/package-with-dashes", - "package.with.dots", - ]; - const plugin = createMarkKnownPackagesExternalPlugin(additionalPackages); - const mockBuild = { - onResolve: vi.fn(), - }; - - plugin.setup(mockBuild as any); - - const [{ filter }] = mockBuild.onResolve.mock.calls[0]; - - // Test packages with special characters - expect(filter.test("@scope/package-with-dashes")).toBe(true); - expect(filter.test("package.with.dots")).toBe(true); - }); - - it("should resolve the original @mapbox/node-pre-gyp bundling issue", () => { - const plugin = createMarkKnownPackagesExternalPlugin(); - const mockBuild = { - onResolve: vi.fn(), - }; - - plugin.setup(mockBuild as any); - - const [{ filter }] = mockBuild.onResolve.mock.calls[0]; - const resolveFunction = mockBuild.onResolve.mock.calls[0][1]; - - // Test the specific problematic paths from the original issue - const problematicPaths = [ - "@mapbox/node-pre-gyp", - "@mapbox/node-pre-gyp/lib/util/nw-pre-gyp/index.html", - "@mapbox/node-pre-gyp/lib/util/s3_setup.js", - "@mapbox/node-pre-gyp/lib/node-pre-gyp.js", - ]; - - problematicPaths.forEach((path) => { - expect(filter.test(path)).toBe(true); - expect(resolveFunction({ path })).toEqual({ - path, - external: true, - }); - }); - }); - - it("should prevent false positives with similar package names", () => { - const plugin = createMarkKnownPackagesExternalPlugin(); - const mockBuild = { - onResolve: vi.fn(), - }; - - plugin.setup(mockBuild as any); - - const [{ filter }] = mockBuild.onResolve.mock.calls[0]; - - // Test cases that should NOT match (false positives) - const falsePositives = [ - "braintrust-extended", - "my-braintrust", - "config-loader", - "lightningcss-plugin", - "sqlite3-wrapper", - "better-sqlite3", - "node-pre-gyp", // Similar to @mapbox/node-pre-gyp but different - "@other/braintrust", - ]; - - falsePositives.forEach((packageName) => { - expect(filter.test(packageName)).toBe(false); - }); - - // Test cases that SHOULD match (true positives) - const truePositives = [ - "braintrust", - "braintrust/core", - "braintrust/dist/index.js", - "@mapbox/node-pre-gyp", - "@mapbox/node-pre-gyp/lib/util/nw-pre-gyp/index.html", - "config", - "lightningcss", - ]; - - truePositives.forEach((packageName) => { - expect(filter.test(packageName)).toBe(true); - }); - }); - - it("should handle prefix matching vs exact matching correctly", () => { - const plugin = createMarkKnownPackagesExternalPlugin(); - const mockBuild = { - onResolve: vi.fn(), - }; - - plugin.setup(mockBuild as any); - - const [{ filter }] = mockBuild.onResolve.mock.calls[0]; - - // Test prefix matching for @braintrust/ (ends with /) - const braintrustPrefixTests = [ - { - input: "@braintrust/utils", - expected: true, - description: "Should match @braintrust/ prefix", - }, - { - input: "@braintrust-other/core", - expected: false, - description: "Should not match similar but different scope", - }, - ]; - - // Test exact matching for config (doesn't end with /) - const configExactTests = [ - { - input: "config", - expected: true, - description: "Should match config exactly", - }, - { - input: "config/local", - expected: true, - description: "Should match config with subpath", - }, - { - input: "config-loader", - expected: false, - description: "Should not match config with suffix", - }, - { - input: "my-config", - expected: false, - description: "Should not match config with prefix", - }, - ]; - - // Test exact matching for braintrust (doesn't end with /) - const braintrustExactTests = [ - { - input: "braintrust", - expected: true, - description: "Should match braintrust exactly", - }, - { - input: "braintrust/core", - expected: true, - description: "Should match braintrust with subpath", - }, - { - input: "braintrust-extended", - expected: false, - description: "Should not match braintrust with suffix", - }, - { - input: "my-braintrust", - expected: false, - description: "Should not match braintrust with prefix", - }, - ]; - - const allTests = [ - ...braintrustPrefixTests, - ...configExactTests, - ...braintrustExactTests, - ]; - - allTests.forEach((test) => { - const result = filter.test(test.input); - expect(result).toBe(test.expected); - }); - }); - }); - - describe("Regex Edge Cases", () => { - it("should handle empty additional packages array", () => { - const plugin = createMarkKnownPackagesExternalPlugin([]); - const mockBuild = { - onResolve: vi.fn(), - }; - - plugin.setup(mockBuild as any); - - const [{ filter }] = mockBuild.onResolve.mock.calls[0]; - - // Should still work with hardcoded packages - expect(filter.test("braintrust")).toBe(true); - expect(filter.test("@mapbox/node-pre-gyp")).toBe(true); - }); - - it("should handle undefined additional packages", () => { - const plugin = createMarkKnownPackagesExternalPlugin(undefined); - const mockBuild = { - onResolve: vi.fn(), - }; - - plugin.setup(mockBuild as any); - - const [{ filter }] = mockBuild.onResolve.mock.calls[0]; - - // Should still work with hardcoded packages - expect(filter.test("braintrust")).toBe(true); - expect(filter.test("@mapbox/node-pre-gyp")).toBe(true); - }); - - it("should handle duplicate packages", () => { - const additionalPackages = ["braintrust", "sqlite3", "braintrust"]; // duplicate - const plugin = createMarkKnownPackagesExternalPlugin(additionalPackages); - const mockBuild = { - onResolve: vi.fn(), - }; - - plugin.setup(mockBuild as any); - - const [{ filter }] = mockBuild.onResolve.mock.calls[0]; - - // Should still work correctly - expect(filter.test("braintrust")).toBe(true); - expect(filter.test("sqlite3")).toBe(true); - }); - }); -}); diff --git a/js/src/cli/util/external-packages-plugin.ts b/js/src/cli/util/external-packages-plugin.ts deleted file mode 100644 index 115a98bff..000000000 --- a/js/src/cli/util/external-packages-plugin.ts +++ /dev/null @@ -1,43 +0,0 @@ -import * as esbuild from "esbuild"; - -// Inspired by https://github.com/evanw/esbuild/issues/619 -// In addition to marking node_modules external, explicitly mark -// our packages (braintrust and autoevals) external, in case they're -// installed in a relative path. -export function createMarkKnownPackagesExternalPlugin( - additionalPackages: string[] = [], -) { - return { - name: "make-known-packages-external", - setup(build: esbuild.PluginBuild) { - // Mark known packages as external - const knownPackages = [ - "braintrust", - "autoevals", - "@braintrust/", - "config", - "lightningcss", - "@mapbox/node-pre-gyp", - "fsevents", - "chokidar", - ...additionalPackages, - ]; - const escapedPackages = knownPackages.map((pkg) => { - const escaped = pkg.replace(/[.*+?^${}()|[\]\\]/g, "\\$&"); - // For packages ending with /, match anything with that prefix - if (pkg.endsWith("/")) { - return escaped + ".*"; - } - // For regular packages, match exact name or name followed by / - return escaped + "(?:\\/.*)?"; - }); - const knownPackagesFilter = new RegExp( - `^(${escapedPackages.join("|")})$`, - ); - build.onResolve({ filter: knownPackagesFilter }, (args) => ({ - path: args.path, - external: true, - })); - }, - }; -} diff --git a/js/src/cli/util/external-packages.test.ts b/js/src/cli/util/external-packages.test.ts deleted file mode 100644 index 210279917..000000000 --- a/js/src/cli/util/external-packages.test.ts +++ /dev/null @@ -1,199 +0,0 @@ -import { describe, it, expect } from "vitest"; -import { ArgumentParser } from "argparse"; - -// Test the CLI argument parsing for external packages -describe("External Packages CLI Arguments", () => { - describe("--external-packages flag", () => { - it("should parse single package", () => { - const parser = new ArgumentParser(); - parser.add_argument("--external-packages", { - nargs: "*", - help: "Additional packages to mark as external during bundling.", - }); - - const args = parser.parse_args(["--external-packages", "sqlite3"]); - expect(args.external_packages).toEqual(["sqlite3"]); - }); - - it("should parse multiple packages", () => { - const parser = new ArgumentParser(); - parser.add_argument("--external-packages", { - nargs: "*", - help: "Additional packages to mark as external during bundling.", - }); - - const args = parser.parse_args([ - "--external-packages", - "sqlite3", - "fsevents", - "@mapbox/node-pre-gyp", - ]); - expect(args.external_packages).toEqual([ - "sqlite3", - "fsevents", - "@mapbox/node-pre-gyp", - ]); - }); - - it("should handle scoped packages", () => { - const parser = new ArgumentParser(); - parser.add_argument("--external-packages", { - nargs: "*", - help: "Additional packages to mark as external during bundling.", - }); - - const args = parser.parse_args([ - "--external-packages", - "@scope/package", - "@another/scoped-package", - ]); - expect(args.external_packages).toEqual([ - "@scope/package", - "@another/scoped-package", - ]); - }); - - it("should handle packages with special characters", () => { - const parser = new ArgumentParser(); - parser.add_argument("--external-packages", { - nargs: "*", - help: "Additional packages to mark as external during bundling.", - }); - - const args = parser.parse_args([ - "--external-packages", - "package-with-dashes", - "package.with.dots", - "@scope/package-with-dashes", - ]); - expect(args.external_packages).toEqual([ - "package-with-dashes", - "package.with.dots", - "@scope/package-with-dashes", - ]); - }); - - it("should handle empty flag (no packages)", () => { - const parser = new ArgumentParser(); - parser.add_argument("--external-packages", { - nargs: "*", - help: "Additional packages to mark as external during bundling.", - }); - - const args = parser.parse_args(["--external-packages"]); - expect(args.external_packages).toEqual([]); - }); - - it("should handle flag not being used", () => { - const parser = new ArgumentParser(); - parser.add_argument("--external-packages", { - nargs: "*", - help: "Additional packages to mark as external during bundling.", - }); - - const args = parser.parse_args([]); - expect(args.external_packages).toBeUndefined(); - }); - - it("should handle mixed with other arguments", () => { - const parser = new ArgumentParser(); - parser.add_argument("--external-packages", { - nargs: "*", - help: "Additional packages to mark as external during bundling.", - }); - parser.add_argument("--verbose", { - action: "store_true", - help: "Verbose output", - }); - parser.add_argument("files", { - nargs: "*", - help: "Files to process", - }); - - const args = parser.parse_args([ - "--verbose", - "--external-packages", - "sqlite3", - "fsevents", - "--", - "file1.ts", - "file2.ts", - ]); - - expect(args.external_packages).toEqual(["sqlite3", "fsevents"]); - expect(args.verbose).toBe(true); - expect(args.files).toEqual(["file1.ts", "file2.ts"]); - }); - }); - - describe("Real-world scenarios", () => { - it("should handle database packages", () => { - const parser = new ArgumentParser(); - parser.add_argument("--external-packages", { - nargs: "*", - help: "Additional packages to mark as external during bundling.", - }); - - const args = parser.parse_args([ - "--external-packages", - "sqlite3", - "better-sqlite3", - "mysql2", - "pg", - ]); - - expect(args.external_packages).toEqual([ - "sqlite3", - "better-sqlite3", - "mysql2", - "pg", - ]); - }); - - it("should handle native modules", () => { - const parser = new ArgumentParser(); - parser.add_argument("--external-packages", { - nargs: "*", - help: "Additional packages to mark as external during bundling.", - }); - - const args = parser.parse_args([ - "--external-packages", - "sharp", - "canvas", - "node-sass", - "fsevents", - ]); - - expect(args.external_packages).toEqual([ - "sharp", - "canvas", - "node-sass", - "fsevents", - ]); - }); - - it("should handle problematic bundling packages", () => { - const parser = new ArgumentParser(); - parser.add_argument("--external-packages", { - nargs: "*", - help: "Additional packages to mark as external during bundling.", - }); - - const args = parser.parse_args([ - "--external-packages", - "@mapbox/node-pre-gyp", - "mock-aws-s3", - "aws-sdk", - "node-pre-gyp", - ]); - - expect(args.external_packages).toEqual([ - "@mapbox/node-pre-gyp", - "mock-aws-s3", - "aws-sdk", - "node-pre-gyp", - ]); - }); - }); -}); diff --git a/js/src/cli/util/pull.ts b/js/src/cli/util/pull.ts deleted file mode 100644 index 76d214237..000000000 --- a/js/src/cli/util/pull.ts +++ /dev/null @@ -1,394 +0,0 @@ -import { - Function as functionSchema, - type FunctionType as FunctionObject, - type SavedFunctionIdType as SavedFunctionId, - type ToolFunctionDefinitionType as ToolFunctionDefinition, -} from "../../generated_types"; -import { _internalGetGlobalState } from "../../logger"; -import { loadCLIEnv } from "./bundle"; -import { PullArgs } from "./types"; -import { warning } from "../../framework"; -import { z } from "zod/v3"; -import fs from "node:fs/promises"; -import util from "node:util"; -import { slugify } from "../../../util/string_util"; -import path from "node:path"; -import { runGitCommand } from "../../git-command"; -import { currentRepoPath } from "../../gitutil"; -import { isEmpty, loadPrettyXact, prettifyXact } from "../../../util/index"; -import { - ProjectNameIdMap, - toolFunctionDefinitionSchema, -} from "../../framework2"; -import pluralize from "pluralize"; - -export async function pullCommand(args: PullArgs) { - await loadCLIEnv(args); - - const loggerConn = _internalGetGlobalState().apiConn(); - const functions = await loggerConn.get_json("/v1/function", { - ...(args.project_id ? { project_id: args.project_id } : {}), - ...(args.project_name ? { project_name: args.project_name } : {}), - ...(args.slug ? { slug: args.slug } : {}), - ...(args.id ? { ids: [args.id] } : {}), - ...(args.version ? { version: loadPrettyXact(args.version) } : {}), - }); - const functionObjects = z - .object({ objects: z.array(z.unknown()) }) - .parse(functions); - - const projectNameToFunctions: Record = {}; - const projectNameIdMap = new ProjectNameIdMap(); - - for (const rawFunc of functionObjects.objects) { - const parsedFunc = functionSchema.safeParse(rawFunc); - if (!parsedFunc.success) { - const id = - typeof rawFunc === "object" && rawFunc && "id" in rawFunc - ? ` ${rawFunc.id}` - : ""; - // eslint-disable-next-line no-restricted-properties -- preserving intentional console usage. - console.warn( - warning(`Failed to parse function${id}: ${parsedFunc.error.message}`), - ); - continue; - } - - const func = parsedFunc.data; - const projectName = await projectNameIdMap.getName(func.project_id); - if (!projectNameToFunctions[projectName]) { - projectNameToFunctions[projectName] = []; - } - projectNameToFunctions[projectName].push(func); - } - - // eslint-disable-next-line no-restricted-properties -- preserving intentional console usage. - console.log("Found functions in the following projects:"); - for (const projectName of Object.keys(projectNameToFunctions)) { - // eslint-disable-next-line no-restricted-properties -- preserving intentional console usage. - console.log(` * ${projectName}`); - } - - const outputDir = args.output_dir ?? "./braintrust"; - await fs.mkdir(outputDir, { recursive: true }); - - const repoRoot = await currentRepoPath(); - const dirtyFileOutput = repoRoot - ? await runGitCommand(["diff", "--name-only", "-z", "HEAD"], { - cwd: repoRoot, - }) - : ""; - const dirtyFiles = new Set( - dirtyFileOutput - .split("\0") - .filter(Boolean) - .map((file) => path.resolve(repoRoot ?? ".", file)), - ); - - for (const projectName of Object.keys(projectNameToFunctions)) { - const projectFile = path.join( - outputDir, - `${slugify(projectName, { lower: true, strict: true, trim: true })}.ts`, - ); - const resolvedProjectFile = path.resolve(projectFile); - const fileExists = await fs.stat(projectFile).then( - () => true, - () => false, - ); - if (args.force) { - if (fileExists) { - // eslint-disable-next-line no-restricted-properties -- preserving intentional console usage. - console.warn( - warning( - `Overwriting ${doubleQuote(projectFile)} because --force is set.`, - ), - ); - } - } else if (dirtyFiles.has(resolvedProjectFile)) { - // eslint-disable-next-line no-restricted-properties -- preserving intentional console usage. - console.warn( - warning( - `Skipping project ${projectName} because ${doubleQuote(projectFile)} has uncommitted changes.`, - ), - ); - continue; - } else if (fileExists) { - if (!repoRoot) { - // eslint-disable-next-line no-restricted-properties -- preserving intentional console usage. - console.warn( - warning( - `Project ${projectName} already exists in ${doubleQuote(projectFile)}. Skipping since this is not a git repository...`, - ), - ); - continue; - } else { - // eslint-disable-next-line no-restricted-properties -- preserving intentional console usage. - console.warn( - warning( - `Project ${projectName} already exists in ${doubleQuote(projectFile)}. Overwriting...`, - ), - ); - } - } - - const projectFileContents = await makeProjectFile({ - projectName, - projectId: await projectNameIdMap.getId(projectName), - fileName: projectFile, - functions: projectNameToFunctions[projectName], - hasSpecifiedFunction: !!args.slug || !!args.id, - }); - await fs.writeFile(projectFile, projectFileContents || ""); - // eslint-disable-next-line no-restricted-properties -- preserving intentional console usage. - console.log(`Wrote ${projectName} to ${doubleQuote(projectFile)}`); - } -} - -async function makeProjectFile({ - projectName, - projectId, - fileName, - functions, - hasSpecifiedFunction, -}: { - projectName: string; - projectId: string; - fileName: string; - functions: FunctionObject[]; - hasSpecifiedFunction: boolean; -}) { - const varNames = {}; - const functionDefinitions = functions - .map((f) => - makeFunctionDefinition({ func: f, varNames, hasSpecifiedFunction }), - ) - .filter((f) => f !== null); - const fileDef = `// This file was automatically generated by braintrust pull. You can -// generate it again by running: -// $ braintrust pull --project-name ${doubleQuote(projectName)} -// Feel free to edit this file manually, but once you do, you should make sure to -// sync your changes with Braintrust by running: -// $ braintrust push ${doubleQuote(fileName)} - -import braintrust from "braintrust"; - -const project = braintrust.projects.create({ - id: ${doubleQuote(projectId)}, - name: ${doubleQuote(projectName)}, -}); - -${functionDefinitions.join("\n")} -`; - - const prettier = await getPrettierModule(); - if (prettier) { - try { - const formatted = prettier.format(fileDef, { - parser: "typescript", - }); - return formatted; - } catch (error) { - // eslint-disable-next-line no-restricted-properties -- preserving intentional console usage. - console.warn( - warning( - `Failed to format with prettier (${error instanceof Error ? error.message : error}). Using unformatted output.`, - ), - ); - } - } - return fileDef; -} - -function makeFunctionDefinition({ - func, - varNames, - hasSpecifiedFunction, -}: { - func: FunctionObject; - varNames: Record; - hasSpecifiedFunction: boolean; -}): string | null { - if (func.function_data.type !== "prompt") { - if (hasSpecifiedFunction) { - // eslint-disable-next-line no-restricted-properties -- preserving intentional console usage. - console.warn( - warning( - `Skipping function ${doubleQuote(func.name)} because it is not a prompt.`, - ), - ); - } - return null; - } - - const baseVarName = slugToVarName(func.slug); - let varName = baseVarName; - let suffix = 1; - while (varName in varNames) { - varName = `${varName}${suffix}`; - suffix++; - } - varNames[varName] = func.slug; - - if (!func.prompt_data || !func.prompt_data.prompt) { - // eslint-disable-next-line no-restricted-properties -- preserving intentional console usage. - console.warn( - warning( - `Prompt ${doubleQuote(func.name)} has an invalid (empty) prompt definition.`, - ), - ); - return null; - } - const objectType = "prompt"; - const prompt = func.prompt_data.prompt; - const promptContents = - prompt.type === "completion" - ? `prompt: ${doubleQuote(prompt.content)}` - : `messages: ${safeStringify(prompt.messages).trimStart()}`; - - const rawToolsParsed = - prompt.type === "chat" && prompt.tools && prompt.tools.length > 0 - ? z - .array(toolFunctionDefinitionSchema) - .safeParse(JSON.parse(prompt.tools)) - : undefined; - - if (rawToolsParsed && !rawToolsParsed.success) { - // eslint-disable-next-line no-restricted-properties -- preserving intentional console usage. - console.warn( - warning( - `Prompt ${doubleQuote(func.name)} has an invalid tools definition: ${rawToolsParsed.error.message}. Skipping...`, - ), - ); - return null; - } - - const rawTools = rawToolsParsed ? rawToolsParsed.data : []; - - const { model, params } = func.prompt_data.options ?? {}; - - const paramsString = - params && Object.keys(params).length > 0 - ? `params: ${safeStringify(params).trimStart()},` - : ""; - - const tools: (SavedFunctionId | ToolFunctionDefinition)[] = [ - ...(func.prompt_data.tool_functions ?? []), - ...rawTools, - ]; - - const toolsString = - tools.length > 0 ? `tools: ${safeStringify(tools).trimStart()},` : ""; - - return `export const ${varName} = project.${pluralize(objectType)}.create({ - id: ${doubleQuote(func.id)}, - name: ${doubleQuote(func.name)}, - slug: ${doubleQuote(func.slug)}, - version: ${doubleQuote(prettifyXact(func._xact_id))}, ${printOptionalField("description", func.description)}${printOptionalField("model", model)} -${indent(promptContents, 2)}, -${indent(paramsString, 2)} -${indent(toolsString, 2)} -}); -`; -} - -function doubleQuote(str: string) { - return JSON.stringify(str); -} - -function slugToVarName(slug: string) { - let varName = slug.replace(/^[^a-zA-Z_$]|[^a-zA-Z0-9_$]/g, "_"); - varName = varName.replace(/_([a-z])/g, (_, letter) => letter.toUpperCase()); - varName = varName.charAt(0).toLowerCase() + varName.slice(1); - return varName; -} - -function indent(str: string, numSpaces: number) { - return str.replace(/^/gm, " ".repeat(numSpaces)); -} - -function printOptionalField( - fieldName: string, - fieldValue: string | undefined | null, -) { - return !isEmpty(fieldValue) - ? ` - ${fieldName}: ${doubleQuote(fieldValue)},` - : ""; -} - -let prettierImportAttempted = false; - -let prettierModule: typeof import("prettier") | undefined = undefined; - -async function getPrettierModule() { - if (!prettierModule && !prettierImportAttempted) { - prettierImportAttempted = true; - - try { - // First try require() which is more stable in npx environments - prettierModule = require("prettier"); - } catch { - try { - // Fallback to dynamic import with error boundary - const importWithTimeout = () => { - return new Promise((resolve, reject) => { - let resolved = false; - - // Set a timeout to prevent infinite hanging - const timeoutId = setTimeout(() => { - if (!resolved) { - resolved = true; - reject(new Error("Prettier import timeout")); - } - }, 3000); - - import("prettier") - .then((module) => { - if (!resolved) { - resolved = true; - clearTimeout(timeoutId); - resolve(module); - } - }) - .catch((error) => { - if (!resolved) { - resolved = true; - clearTimeout(timeoutId); - reject(error); - } - }); - }); - }; - - prettierModule = await importWithTimeout(); - } catch { - // eslint-disable-next-line no-restricted-properties -- preserving intentional console usage. - console.warn( - warning( - "Failed to load prettier module. Will not use prettier to format output.", - ), - ); - prettierModule = undefined; - } - } - } - return prettierModule; -} - -function safeStringify(obj: unknown): string { - try { - return JSON.stringify(obj, null, 2); - } catch (error) { - // Fallback for circular references or other JSON.stringify issues - try { - return util.inspect(obj, { - depth: 5, - maxStringLength: 1000, - breakLength: 80, - compact: false, - }); - } catch { - return `[Object: Unable to serialize - ${error instanceof Error ? error.message : error}]`; - } - } -} diff --git a/js/src/cli/util/types.ts b/js/src/cli/util/types.ts deleted file mode 100644 index 2909a607e..000000000 --- a/js/src/cli/util/types.ts +++ /dev/null @@ -1,51 +0,0 @@ -import { type IfExistsType as IfExists } from "../../generated_types"; - -export interface CommonArgs { - /** @deprecated Use debug_logging: "debug" for detailed troubleshooting output. */ - verbose: boolean; - debug_logging?: "error" | "warn" | "info" | "debug"; -} - -export interface AuthArgs { - api_key?: string; - org_name?: string; - app_url?: string; - env_file?: string; -} - -export interface CompileArgs { - tsconfig?: string; - terminate_on_failure: boolean; - external_packages?: string[]; -} - -export interface RunArgs extends CommonArgs, AuthArgs, CompileArgs { - files: string[]; - watch: boolean; - list: boolean; - jsonl: boolean; - filter?: string[]; - no_send_logs: boolean; - no_progress_bars: boolean; - bundle: boolean; - push: boolean; - dev: boolean; - dev_host: string; - dev_port: number; - dev_org_name?: string; -} - -export interface BundleArgs extends CommonArgs, AuthArgs, CompileArgs { - files: string[]; - if_exists: IfExists; -} - -export interface PullArgs extends CommonArgs, AuthArgs { - output_dir: string; - project_name?: string; - project_id?: string; - id?: string; - slug?: string; - version?: string; - force: boolean; -} diff --git a/js/src/debug-logger.test.ts b/js/src/debug-logger.test.ts index 88f29c051..6d88f840d 100644 --- a/js/src/debug-logger.test.ts +++ b/js/src/debug-logger.test.ts @@ -1,4 +1,12 @@ -import { afterEach, beforeEach, describe, expect, test, vi } from "vitest"; +import { installGlobalHookErrorReporter } from "./debug-logger"; +import { + afterEach, + beforeEach, + describe, + expect, + test, + vi, +} from "vite-plus/test"; import { debugLogger, @@ -9,14 +17,10 @@ import { newGlobalInvocationHook, GLOBAL_INSTRUMENTATION_HOOKS_KEY, } from "./global-instrumentation-hooks"; -import { - BraintrustState, - _exportsForTestingOnly, - initLogger, - login, -} from "./logger"; +import { BraintrustState, initLogger, login } from "./logger/core"; +import { _exportsForTestingOnly } from "./logger/testing"; import { configureNode } from "./node/config"; -import iso from "./isomorph"; +import iso from "#braintrust-platform"; configureNode(); @@ -79,6 +83,7 @@ describe("debug logger", () => { test("invalid global hooks are reported without changing the provider call", () => { process.env.BRAINTRUST_DEBUG_LOG_LEVEL = "error"; const errorSpy = vi.spyOn(console, "error").mockImplementation(() => {}); + installGlobalHookErrorReporter(); const name = `test:debug-logger:${Math.random()}`; newGlobalInvocationHook(name); const registry = ( diff --git a/js/src/debug-logger.ts b/js/src/debug-logger.ts index 8fa3a0c49..ca06e86d7 100644 --- a/js/src/debug-logger.ts +++ b/js/src/debug-logger.ts @@ -1,4 +1,4 @@ -import iso from "./isomorph"; +import iso from "#braintrust-platform"; import { sdkConsole } from "./logs/console"; import { setGlobalHookErrorReporter } from "./global-instrumentation-hooks"; @@ -185,13 +185,17 @@ function createDebugLogger(state?: DebugLoggerStateLike): DebugLoggerMethods { }; } -export const debugLogger = { +export const debugLogger: DebugLoggerMethods & { + forState(state: DebugLoggerStateLike | undefined): DebugLoggerMethods; +} = { ...createDebugLogger(), forState(state: DebugLoggerStateLike | undefined): DebugLoggerMethods { return createDebugLogger(state); }, }; -setGlobalHookErrorReporter((error) => { - debugLogger.error("Global instrumentation hook error:", error); -}); +export function installGlobalHookErrorReporter(): void { + setGlobalHookErrorReporter((error) => { + debugLogger.error("Global instrumentation hook error:", error); + }); +} diff --git a/js/src/edge-light/config.ts b/js/src/edge-light/config.ts index 20302d2cd..d67f54433 100644 --- a/js/src/edge-light/config.ts +++ b/js/src/edge-light/config.ts @@ -1,49 +1,7 @@ -import iso from "../isomorph"; -import { _internalSetInitialState } from "../logger"; -import { resolveRuntimeAsyncLocalStorage } from "../runtime-async-local-storage"; -import { registry } from "../instrumentation/registry"; +import iso from "#braintrust-platform"; +import { createEdgeLightIsomorph } from "./platform"; -let edgeLightConfigured = false; - -/** - * Configure the isomorph for edge-light runtime environments (Vercel Edge, Next.js Edge, etc.). - */ +/** @internal Configure source-level tests and compatibility integrations. */ export function configureEdgeLight(): void { - if (edgeLightConfigured) { - return; - } - - iso.buildType = "edge-light"; - - const runtimeAsyncLocalStorage = resolveRuntimeAsyncLocalStorage(); - - if (runtimeAsyncLocalStorage) { - iso.newAsyncLocalStorage = () => new runtimeAsyncLocalStorage(); - } - - iso.getEnv = (name: string) => { - if (typeof process === "undefined" || typeof process.env === "undefined") { - return undefined; - } - return process.env[name]; - }; - iso.getBraintrustApiKey = async () => { - const value = iso.getEnv("BRAINTRUST_API_KEY"); - return value?.trim() ? value : undefined; - }; - - iso.hash = (data: string): string => { - let hash = 0; - for (let i = 0; i < data.length; i++) { - const char = data.charCodeAt(i); - hash = (hash << 5) - hash + char; - hash = hash & hash; - } - const hashHex = (hash >>> 0).toString(16).padStart(8, "0"); - return hashHex.repeat(8).substring(0, 64); - }; - - _internalSetInitialState(); - registry.enable(); - edgeLightConfigured = true; + Object.assign(iso, createEdgeLightIsomorph()); } diff --git a/js/src/edge-light/index.ts b/js/src/edge-light/index.ts index 4c6ef7bbf..a6697332a 100644 --- a/js/src/edge-light/index.ts +++ b/js/src/edge-light/index.ts @@ -5,9 +5,4 @@ * Next.js Edge Runtime, and other edge platforms that support AsyncLocalStorage. */ -import { configureEdgeLight } from "./config"; - -configureEdgeLight(); - export * from "../exports"; -export * as default from "../exports"; diff --git a/js/src/edge-light/isomorph.ts b/js/src/edge-light/isomorph.ts new file mode 100644 index 000000000..8f6e71bc9 --- /dev/null +++ b/js/src/edge-light/isomorph.ts @@ -0,0 +1,5 @@ +import type { Common } from "../isomorph-base"; +export type { CallerLocation, IsoAsyncLocalStorage } from "../isomorph-base"; +import { createEdgeLightIsomorph } from "./platform"; +const iso: Common = /* @__PURE__ */ createEdgeLightIsomorph(); +export default iso; diff --git a/js/src/edge-light/platform.ts b/js/src/edge-light/platform.ts new file mode 100644 index 000000000..5fb9a5827 --- /dev/null +++ b/js/src/edge-light/platform.ts @@ -0,0 +1,43 @@ +import { createIsomorph, type Common } from "../isomorph-base"; +import { resolveRuntimeAsyncLocalStorage } from "../runtime-async-local-storage"; + +/** + * Configure the isomorph for edge-light runtime environments (Vercel Edge, Next.js Edge, etc.). + */ +export function createEdgeLightIsomorph(): Common { + const iso = createIsomorph(); + + iso.buildType = "edge-light"; + + const fallbackAsyncLocalStorage = iso.newAsyncLocalStorage; + iso.newAsyncLocalStorage = () => { + const RuntimeAsyncLocalStorage = resolveRuntimeAsyncLocalStorage(); + return RuntimeAsyncLocalStorage + ? new RuntimeAsyncLocalStorage() + : fallbackAsyncLocalStorage(); + }; + + iso.getEnv = (name: string) => { + if (typeof process === "undefined" || typeof process.env === "undefined") { + return undefined; + } + return process.env[name]; + }; + iso.getBraintrustApiKey = async () => { + const value = iso.getEnv("BRAINTRUST_API_KEY"); + return value?.trim() ? value : undefined; + }; + + iso.hash = (data: string): string => { + let hash = 0; + for (let i = 0; i < data.length; i++) { + const char = data.charCodeAt(i); + hash = (hash << 5) - hash + char; + hash = hash & hash; + } + const hashHex = (hash >>> 0).toString(16).padStart(8, "0"); + return hashHex.repeat(8).substring(0, 64); + }; + + return iso; +} diff --git a/js/src/edge-runtime-bootstrap.test.ts b/js/src/edge-runtime-bootstrap.test.ts index 39f772620..47b6e2669 100644 --- a/js/src/edge-runtime-bootstrap.test.ts +++ b/js/src/edge-runtime-bootstrap.test.ts @@ -1,4 +1,12 @@ -import { afterEach, beforeEach, describe, expect, test, vi } from "vitest"; +import { resetInstrumentationForTest } from "./instrumentation/test-utils/reset"; +import { + afterEach, + beforeEach, + describe, + expect, + test, + vi, +} from "vite-plus/test"; type EdgeEntrypoint = "./edge-light/index" | "./workerd/index"; const braintrustStateSymbol = Symbol.for("braintrust-state"); @@ -23,6 +31,7 @@ describe.each([ vi.restoreAllMocks(); Reflect.deleteProperty(globalThis, braintrustStateSymbol); + resetInstrumentationForTest(); if (originalAsyncLocalStorageDescriptor) { Object.defineProperty( @@ -66,20 +75,22 @@ describe.each([ get: () => getBuiltinModule, }); + const platform = await import(entrypoint.replace("/index", "/config")); + (platform.configureWorkerd ?? platform.configureEdgeLight)(); const braintrust = await import(entrypoint); + const { _exportsForTestingOnly } = await import("./logger/testing"); - braintrust._exportsForTestingOnly.setInitialTestState(); - await braintrust._exportsForTestingOnly.simulateLoginForTests(); + _exportsForTestingOnly.setInitialTestState(); + await _exportsForTestingOnly.simulateLoginForTests(); - const backgroundLogger = - braintrust._exportsForTestingOnly.useTestBackgroundLogger(); + const backgroundLogger = _exportsForTestingOnly.useTestBackgroundLogger(); const logger = braintrust.initLogger({ projectId: "test-project-id", projectName, }); const root = logger.startSpan({ name: "root", type: "task" }); - const parent = await root.export(); + const parent = braintrust.extractTraceContextFromHeaders(root.inject())!; const result = await braintrust.traced( async (span: unknown) => { @@ -112,13 +123,15 @@ describe.each([ }); test("wrapAISDK logs spans in edge runtimes", async () => { + const platform = await import(entrypoint.replace("/index", "/config")); + (platform.configureWorkerd ?? platform.configureEdgeLight)(); const braintrust = await import(entrypoint); + const { _exportsForTestingOnly } = await import("./logger/testing"); - braintrust._exportsForTestingOnly.setInitialTestState(); - await braintrust._exportsForTestingOnly.simulateLoginForTests(); + _exportsForTestingOnly.setInitialTestState(); + await _exportsForTestingOnly.simulateLoginForTests(); - const backgroundLogger = - braintrust._exportsForTestingOnly.useTestBackgroundLogger(); + const backgroundLogger = _exportsForTestingOnly.useTestBackgroundLogger(); braintrust.initLogger({ projectId: "test-project-id", projectName, diff --git a/js/src/eval-parameters.ts b/js/src/eval-parameters.ts index 433b9991b..1069d7b85 100644 --- a/js/src/eval-parameters.ts +++ b/js/src/eval-parameters.ts @@ -1,36 +1,28 @@ import { debugLogger } from "./debug-logger"; -import { z } from "zod/v3"; -import * as z4 from "zod/v4"; import Ajv from "ajv"; -import { Prompt, RemoteEvalParameters } from "./logger"; +import { Prompt, RemoteEvalParameters } from "./logger/prompts"; import { - promptDefinitionWithToolsSchema, promptDefinitionToPromptData, + type PromptDefinitionWithTools, } from "./prompt-schemas"; -import { PromptData as promptDataSchema } from "./generated_types"; +import { PromptData as promptDataSchema } from "./sdk-schemas"; + +export type EvalParameters = Record< + string, + | { + type: "prompt"; + default?: PromptDefinitionWithTools; + description?: string; + } + | { + type: "model"; + default?: string; + description?: string; + } + | ZodSchema +>; import type { ZodSchema } from "./zod/utils"; -// Schema for evaluation parameters -export const evalParametersSchema = z.record( - z.string(), - z.union([ - z.object({ - type: z.literal("prompt"), - default: promptDefinitionWithToolsSchema.optional(), - description: z.string().optional(), - }), - z.object({ - type: z.literal("model"), - default: z.string().optional(), - description: z.string().optional(), - }), - z.instanceof(z.ZodType), - z.instanceof(z4.ZodType), - ]), -); - -export type EvalParameters = z.infer; - // Type helper to infer the type of a parameter value type InferParameterValue = T extends { type: "prompt" } ? Prompt diff --git a/js/src/exports.ts b/js/src/exports.ts index 9cf8b897b..5f3112740 100644 --- a/js/src/exports.ts +++ b/js/src/exports.ts @@ -1,133 +1,58 @@ +/** + * The public `braintrust` package surface. + * + * Keep this file as an explicit allowlist. Implementation details should stay + * private; integrations belong on narrow, intentional extension points. + */ + export type { - AnyDataset, - AttachmentParams, - BackgroundLoggerOpts, - BaseMetadata, - ChatPrompt, - CompiledPrompt, - CompiledPromptParams, - CompletionPrompt, ContextParentSpanIds, - DatasetRestorePreviewResult, - DatasetRestoreResult, - DatasetSnapshot, - DataSummary, - DatasetSummary, - DefaultMetadataType, - DefaultPromptArgs, - EndSpanArgs, - EvalCase, - ExperimentSummary, - Exportable, - ExternalAttachmentParams, - FullInitDatasetOptions, - FullInitOptions, - FullLoginOptions, - InitDatasetOptions, - InitLoggerOptions, - InitOptions, - LoadPromptOptions, - LogOptions, - LoginOptions, - Logs3OverflowInputRow, - Logs3OverflowUpload, - MetricSummary, - ObjectMetadata, - PromiseUnless, + CurrentSpanStore, PropagationContext, - PromptRowWithId, - ScoreSummary, - SerializedBraintrustState, - SetCurrentArg, Span, StartSpanArgs, - WithTransactionId, -} from "./logger"; +} from "./logger/core"; export { wrapConsole } from "./logs"; - -export type { - SandboxConfig, - RegisterSandboxOptions, - RegisterSandboxResult, -} from "./sandbox"; - export { Attachment, BaseAttachment, - BRAINTRUST_CURRENT_SPAN_STORE, BraintrustState, - ContextManager, - CurrentSpanStore, - DEFAULT_FETCH_BATCH_SIZE, - DEFAULT_MAX_REQUEST_SIZE, - Dataset, - ObjectFetcher, - ERR_PERMALINK, - Experiment, ExternalAttachment, - FailedHTTPResponse, JSONAttachment, - LOGS3_OVERFLOW_REFERENCE_TYPE, Logger, - LoginInvalidOrgError, NOOP_SPAN, - NOOP_SPAN_PERMALINK, - NoopSpan, - Prompt, ReadonlyAttachment, - ReadonlyExperiment, - SpanImpl, - TestBackgroundLogger, - _exportsForTestingOnly, - _internalGetGlobalState, - _internalSetInitialState, - constructLogs3OverflowRequest, + _internalCaptureAttachmentsEnabled, currentExperiment, currentLogger, currentSpan, - deepCopyEvent, - deserializePlainStringAsJSON, + configureContextManager, + ContextManager, extractTraceContextFromHeaders, flush, - getContextManager, - getPromptVersions, - getSpanParentObject, - init, injectTraceContext, - initDataset, - initExperiment, initLogger, - loadParameters, - loadPrompt, - log, logError, login, loginToState, - logs3OverflowUploadSchema, - newId, - permalink, - pickLogs3OverflowObjectIds, - uploadLogs3OverflowPayload, - utf8ByteLength, - renderMessage, - renderPromptParams, setFetch, setMaskingFunction, - spanComponentsToObjectId, startSpan, - summarize, - traceable, traced, updateSpan, withCurrent, - withDataset, - withExperiment, - withLogger, withParent, wrapTraced, - registerOtelFlush, -} from "./logger"; +} from "./logger/core"; +export { Dataset, initDataset } from "./logger/datasets"; +export { Experiment, ReadonlyExperiment, init } from "./logger/experiments"; +export { + Prompt, + getPromptVersions, + loadParameters, + loadPrompt, +} from "./logger/prompts"; export { registerSandbox } from "./sandbox"; @@ -139,88 +64,32 @@ export { openaiFilesCreateTraced, } from "./openai-batch"; +export type { TemplateRendererPlugin } from "./template/registry"; +export { registerTemplatePlugin } from "./template/registry"; export { failOpenAIAgentsTrace, startOpenAIAgentsTrace, updateOpenAIAgentsTrace, } from "./openai-agents-api"; -// Internal isomorph layer for platform-specific implementations -import _internalIso from "./isomorph"; -export { _internalIso }; - export { isTemplateFormat, parseTemplateFormat, renderTemplateContent, } from "./template/renderer"; -export type { TemplateFormat } from "./template/registry"; - -export type { - TemplateRenderer, - TemplateRendererPlugin, -} from "./template/registry"; -export { - registerTemplatePlugin, - getTemplateRenderer, - templateRegistry, -} from "./template/registry"; -export type { InvokeFunctionArgs, InvokeReturn } from "./functions/invoke"; export { initFunction, invoke } from "./functions/invoke"; +export { BraintrustStream } from "./functions/stream"; -export type { BraintrustStreamChunk } from "./functions/stream"; -export { - BraintrustStream, - braintrustStreamChunkSchema, - createFinalValuePassThroughStream, - devNullWritableStream, -} from "./functions/stream"; - -export { - IDGenerator, - UUIDGenerator, - OTELIDGenerator, - getIdGenerator, -} from "./id-gen"; - -export { - TRACEPARENT_HEADER, - TRACESTATE_HEADER, - BAGGAGE_HEADER, - BRAINTRUST_PARENT_KEY, -} from "./propagation"; -export type { - ParsedTraceparent, - PropagatedState, - TraceContextCarrier, - TraceContextHeaders, -} from "./propagation"; - -export { - LEGACY_CACHED_HEADER, - X_CACHED_HEADER, - parseCachedHeader, - wrapOpenAI, - wrapOpenAIv4, -} from "./wrappers/oai"; - +export { wrapOpenAI } from "./wrappers/oai"; export { braintrustAISDKTelemetry, wrapAISDK, wrapAgentClass, - BraintrustMiddleware, - wrapAISDKModel, } from "./wrappers/ai-sdk"; -export { braintrustEveHook } from "./instrumentation/plugins/eve-plugin"; -export { braintrustEveInstrumentation } from "./instrumentation/plugins/eve-instrumentation"; export { collectAnthropicSession } from "./wrappers/anthropic-session-collector"; export { wrapAnthropic } from "./wrappers/anthropic"; -export { - BraintrustObservabilityExporter, - wrapMastraAgent, -} from "./wrappers/mastra"; -export type { MastraObservabilityExporter } from "./wrappers/mastra"; +export { BraintrustObservabilityExporter } from "./wrappers/mastra"; export { wrapClaudeAgentSDK } from "./wrappers/claude-agent-sdk/claude-agent-sdk"; export { wrapCloudflareThink } from "./wrappers/cloudflare-think"; export { wrapOpenAICodexSDK } from "./wrappers/openai-codex"; @@ -256,41 +125,24 @@ export { } from "./wrappers/langsmith"; export { wrapVitest } from "./wrappers/vitest"; export { initNodeTestSuite } from "./wrappers/node-test"; -export { - BRAINTRUST_LANGCHAIN_CALLBACK_HANDLER_NAME, - BraintrustLangChainCallbackHandler, -} from "./wrappers/langchain/callback-handler"; +export { BraintrustLangChainCallbackHandler } from "./wrappers/langchain/callback-handler"; export type { LangChainCallbackHandlerOptions } from "./vendor-sdk-types/langchain"; -export * as graph from "./graph-framework"; - export type { Evaluator, EvalTask, - EvalHooks, - EvalResult, - EvalScorerArgs, EvalScorer, EvalClassifier, - EvaluatorDef, - EvaluatorFile, ReporterBody, - SpanContext, } from "./framework"; - export { BaseExperiment, Eval, - EvalResultWithSummary, Reporter, - buildLocalSummary, - reportFailures, - runEvaluator, defaultErrorScoreHandler, } from "./framework"; export type { WorkflowEvalStore } from "./workflow-eval"; - export { WorkflowBatchScorer, WorkflowBatchTask, @@ -302,91 +154,10 @@ export { } from "./workflow-eval"; export { agentAssertionScorer } from "./agent-assertions"; - export { DatasetPipeline } from "./dataset-pipeline"; +export { projects } from "./framework2"; -export type { - CodeOpts, - CreateProjectOpts, - FunctionEvent, - PromptOpts, - ScorerOpts, -} from "./framework2"; - -export { - CodeFunction, - CodePrompt, - Project, - ProjectNameIdMap, - PromptBuilder, - ScorerBuilder, - ToolBuilder, - projects, - toolFunctionDefinitionSchema, -} from "./framework2"; - -export { - promptContentsSchema, - promptDefinitionSchema, - promptDefinitionToPromptData, - promptDefinitionWithToolsSchema, - PromptContents, - PromptDefinition, - PromptDefinitionWithTools, -} from "./prompt-schemas"; - -export type { - Trace, - SpanData, - SpanDurationFilter, - SpanFilters, - GetThreadOptions, -} from "./trace"; -export { SpanFetcher, CachedSpanFetcher, LocalTrace } from "./trace"; - -export type { - ParentExperimentIds, - ParentProjectLogIds, - IdField, - InputField, - OtherExperimentLogFields, - ExperimentLogPartialArgs, - ExperimentLogFullArgs, - LogFeedbackFullArgs, - LogCommentFullArgs, - CommentEvent, - DatasetRecord, -} from "../util"; - -export { addAzureBlobHeaders, LazyValue } from "./util"; - -export { AttachmentReference } from "./generated_types"; - -export type { - EvaluatorManifest, - EvalParameterSerializedSchema, - EvaluatorDefinition, - EvaluatorDefinitions, - ParametersSource, -} from "../dev/types"; - -export type { EvalParameters } from "./eval-parameters"; - -export { - evaluatorDefinitionSchema, - evaluatorDefinitionsSchema, -} from "../dev/types"; - -// Auto-instrumentation configuration -export { configureInstrumentation } from "./instrumentation"; -export { - braintrustFlueObserver, - braintrustFlueInstrumentation, -} from "./instrumentation"; -export type { - InstrumentationConfig, - SpanCustomizer, - SpanExportData, -} from "./instrumentation"; +export type { Trace } from "./trace"; +export { LocalTrace } from "./trace"; export { wrapElevenLabs } from "./wrappers/elevenlabs"; diff --git a/js/src/framework-types.ts b/js/src/framework-types.ts index b5183cc7d..27279c8b0 100644 --- a/js/src/framework-types.ts +++ b/js/src/framework-types.ts @@ -1,4 +1,4 @@ -import { type IfExistsType as IfExists } from "./generated_types"; +import type { IfExists } from "./sdk-types"; export type GenericFunction = | ((input: Input) => Output) diff --git a/js/src/framework.test.ts b/js/src/framework.test.ts index 366dd9397..c63e60f33 100644 --- a/js/src/framework.test.ts +++ b/js/src/framework.test.ts @@ -6,7 +6,7 @@ import { beforeEach, afterEach, vi, -} from "vitest"; +} from "vite-plus/test"; import { type ExperimentEvent } from "../util"; import { defaultErrorScoreHandler, @@ -16,14 +16,14 @@ import { _internalPrepareEvaluatorScore, type OneOrMoreScores, } from "./framework"; +import { _exportsForTestingOnly } from "./logger/testing"; import { - _exportsForTestingOnly, BraintrustState, initLogger, injectTraceContext, TestBackgroundLogger, - withParent, -} from "./logger"; + _internalWithParent, +} from "./logger/core"; import { parseBaggage } from "./propagation"; import { configureNode } from "./node/config"; import type { ProgressReporter } from "./reporters/types"; @@ -53,16 +53,13 @@ test("meta (write) is passed to task", async () => { projectName: "proj", evalName: "eval", data: [{ input: 1, metadata }], - task: async (input: number, { meta }) => { - meta({ - foo: "barbar", - }); + task: async (input: number, { metadata }) => { + metadata.foo = "barbar"; return input * 2; }, scores: [], }, new NoopProgressReporter(), - [], undefined, undefined, true, @@ -100,7 +97,6 @@ test("metadata (read/write) is passed to task", async () => { scores: [], }, new NoopProgressReporter(), - [], undefined, undefined, true, @@ -140,7 +136,6 @@ test("expected (read/write) is passed to task", async () => { scores: [], }, new NoopProgressReporter(), - [], undefined, undefined, true, @@ -183,7 +178,6 @@ test("EvalCase id and tags are passed to scorers", async () => { ], }, new NoopProgressReporter(), - [], undefined, undefined, true, @@ -267,7 +261,6 @@ describe("runEvaluator", () => { scores: [], }, new NoopProgressReporter(), - [], undefined, ); @@ -300,7 +293,6 @@ describe("runEvaluator", () => { scores: [], }, new NoopProgressReporter(), - [], undefined, ); @@ -339,7 +331,6 @@ describe("runEvaluator", () => { scores: [], }, new NoopProgressReporter(), - [], undefined, ); @@ -377,7 +368,6 @@ describe("runEvaluator", () => { scores: [], }, new NoopProgressReporter(), - [], (event) => streamEvents.push(event), ); @@ -416,7 +406,6 @@ describe("runEvaluator", () => { scores: [], }, new NoopProgressReporter(), - [], undefined, ); @@ -440,7 +429,6 @@ describe("runEvaluator", () => { ), }, new NoopProgressReporter(), - [], undefined, ); @@ -467,7 +455,6 @@ describe("runEvaluator", () => { errorScoreHandler: defaultErrorScoreHandler, }, new NoopProgressReporter(), - [], undefined, ); @@ -496,7 +483,6 @@ describe("runEvaluator", () => { errorScoreHandler: defaultErrorScoreHandler, }, new NoopProgressReporter(), - [], undefined, ); @@ -529,7 +515,6 @@ describe("runEvaluator", () => { errorScoreHandler: () => undefined, }, new NoopProgressReporter(), - [], undefined, ); @@ -554,7 +539,6 @@ describe("runEvaluator", () => { errorScoreHandler: () => ({ error_score: 1 }), }, new NoopProgressReporter(), - [], undefined, ); @@ -587,7 +571,6 @@ describe("runEvaluator", () => { }, }, new NoopProgressReporter(), - [], undefined, undefined, true, @@ -695,7 +678,6 @@ describe("runEvaluator", () => { ...options, }, new NoopProgressReporter(), - [], undefined, ), taskCompletions, @@ -740,7 +722,6 @@ describe("runEvaluator", () => { ...options, }, new NoopProgressReporter(), - [], undefined, ), taskCompletions, @@ -776,7 +757,6 @@ describe("runEvaluator", () => { ...options, }, new NoopProgressReporter(), - [], undefined, ), new Set(), @@ -810,7 +790,6 @@ describe("runEvaluator", () => { ...options, }, new NoopProgressReporter(), - [], undefined, ), new Set(), @@ -857,7 +836,6 @@ describe("runEvaluator", () => { ...options, }, new NoopProgressReporter(), - [], undefined, ), new Set(), @@ -903,7 +881,6 @@ describe("runEvaluator", () => { timeout: 5_000, }, new NoopProgressReporter(), - [], undefined, ), ).rejects.toThrow(new InternalAbortError("Evaluator aborted")); @@ -928,7 +905,6 @@ describe("runEvaluator", () => { timeout: 0, }, new NoopProgressReporter(), - [], undefined, ), ).rejects.toThrow(new InternalAbortError("Evaluator timed out")); @@ -952,7 +928,6 @@ describe("runEvaluator", () => { scores: [], }, new NoopProgressReporter(), - [], undefined, ); @@ -992,7 +967,6 @@ describe("runEvaluator", () => { signal: controller.signal, }, new NoopProgressReporter(), - [], undefined, ); @@ -1029,7 +1003,6 @@ describe("runEvaluator", () => { signal: controller.signal, }, new NoopProgressReporter(), - [], undefined, ), ).rejects.toThrow(new InternalAbortError("Evaluator aborted")); @@ -1065,7 +1038,6 @@ test("trialIndex is passed to task", async () => { trialCount: 3, }, new NoopProgressReporter(), - [], undefined, undefined, true, @@ -1111,7 +1083,7 @@ describe.each([false, true])( let previousIds: string[] = []; for (const input of [1, 2]) { - await withParent(parent, () => + await _internalWithParent(parent, () => runEvaluator( useParent ? null : experiment, { @@ -1127,7 +1099,6 @@ describe.each([false, true])( summarizeScores: false, }, new NoopProgressReporter(), - [], undefined, undefined, true, @@ -1196,7 +1167,6 @@ test("trialIndex with multiple inputs", async () => { trialCount: 2, }, new NoopProgressReporter(), - [], undefined, undefined, true, @@ -1242,7 +1212,6 @@ test("per-input trialCount overrides global trialCount", async () => { trialCount: 2, }, new NoopProgressReporter(), - [], undefined, undefined, true, @@ -1289,7 +1258,6 @@ test("per-input trialCount works without global trialCount", async () => { scores: [], }, new NoopProgressReporter(), - [], undefined, undefined, true, @@ -1456,7 +1424,6 @@ test("runEvaluator forwards baseExperimentId to summary", async () => { baseExperimentId: "base-exp-id", }, new NoopProgressReporter(), - [], undefined, undefined, true, @@ -1499,7 +1466,6 @@ test("runEvaluator forwards persisted baseExperimentName id to summary", async ( baseExperimentName: "base-exp", }, new NoopProgressReporter(), - [], undefined, undefined, true, @@ -1541,7 +1507,6 @@ test("tags can be appended and logged to root span", async () => { summarizeScores: false, }, new NoopProgressReporter(), - [], undefined, undefined, true, @@ -1590,7 +1555,6 @@ test.each([ summarizeScores: false, }, new NoopProgressReporter(), - [], undefined, undefined, true, @@ -1630,7 +1594,6 @@ test("tags are persisted with a failing scorer", async () => { summarizeScores: false, }, new NoopProgressReporter(), - [], undefined, undefined, true, @@ -1663,7 +1626,6 @@ test("tags remain empty when not set", async () => { summarizeScores: false, }, new NoopProgressReporter(), - [], undefined, undefined, true, @@ -1699,7 +1661,6 @@ test("scorer spans have purpose='scorer' attribute", async () => { ], }, new NoopProgressReporter(), - [], undefined, undefined, true, diff --git a/js/src/framework.ts b/js/src/framework.ts index a56f0d693..66c09aa72 100644 --- a/js/src/framework.ts +++ b/js/src/framework.ts @@ -9,17 +9,17 @@ import { spanObjectTypeV3ToTypedString, } from "../util/index"; import type { SingleScore } from "../util/score"; -import { - type GitMetadataSettingsType as GitMetadataSettings, - ObjectReference as ObjectReferenceSchema, - type ObjectReferenceType as ObjectReference, - type RepoInfoType as RepoInfo, - type SSEProgressEventDataType as SSEProgressEventData, -} from "./generated_types"; +import { ObjectReference as ObjectReferenceSchema } from "./sdk-schemas"; +import type { + GitMetadataSettings, + ObjectReference, + RepoInfo, + SSEProgressEventData, +} from "./sdk-types"; import { queue } from "async"; import { v5 as uuidv5 } from "uuid"; -import iso from "./isomorph"; +import iso from "#braintrust-platform"; import { debugLogger } from "./debug-logger"; import { GenericFunction } from "./framework-types"; import { CodeFunction, CodePrompt, CodeParameters } from "./framework2"; @@ -27,27 +27,29 @@ import { Trace, LocalTrace } from "./trace"; import { BaseMetadata, BraintrustState, - Dataset, DefaultMetadataType, EvalCase, - Experiment, - ExperimentSummary, - FullInitOptions, NOOP_SPAN, - type ParametersRef, - RemoteEvalParameters, Span, StartSpanArgs, - init as _initExperiment, currentSpan, flush, logError as logSpanError, startSpan, traced, withCurrent, - withParent, + _internalWithParent, _internalGetGlobalState, -} from "./logger"; +} from "./logger/core"; +import { Dataset } from "./logger/datasets"; +import { + Experiment, + ExperimentSummary, + FullInitOptions, + type ParametersRef, + init as _initExperiment, +} from "./logger/experiments"; +import { RemoteEvalParameters } from "./logger/prompts"; import type { ProgressReporter } from "./reporters/types"; import { SimpleProgressReporter } from "./reporters/progress"; import type { @@ -100,13 +102,13 @@ export type EvalData< Metadata extends BaseMetadata = DefaultMetadataType, > = | EvalCase[] - | (() => EvalCase[]) | Promise[]> - | (() => Promise[]>) - | AsyncGenerator> | AsyncIterable> | BaseExperiment - | (() => BaseExperiment); + | (() => + | EvalCase[] + | Promise[]> + | BaseExperiment); export type EvalTask< Input, @@ -114,17 +116,12 @@ export type EvalTask< Expected, Metadata extends BaseMetadata, Parameters extends EvalParameters, -> = - | (( - input: Input, - hooks: EvalHooks, - ) => Promise) - | (( - input: Input, - hooks: EvalHooks, - ) => Output); - -export type TaskProgressEvent = Omit< +> = ( + input: Input, + hooks: EvalHooks, +) => Output | Promise; + +type TaskProgressEvent = Omit< SSEProgressEventData, "id" | "origin" | "object_type" | "name" >; @@ -134,10 +131,6 @@ export interface EvalHooks< Metadata extends BaseMetadata, Parameters extends EvalParameters, > { - /** - * @deprecated Use `metadata` instead. - */ - meta: (info: Metadata) => void; /** * The metadata object for the current evaluation. You can mutate this object to add or remove metadata. */ @@ -391,40 +384,17 @@ export interface Evaluator< flushBeforeScoring?: boolean; } -export class EvalResultWithSummary< +export interface EvalResultWithSummary< Input, Output, Expected, Metadata extends BaseMetadata = DefaultMetadataType, > { - constructor( - public summary: ExperimentSummary, - public results: EvalResult[], - ) {} - - /** - * @deprecated Use `summary` instead. - */ - toString(): string { - return JSON.stringify(this.summary); - } - - [Symbol.for("nodejs.util.inspect.custom")](): string { - return `EvalResultWithSummary(summary="...", results=[...])`; - } - - toJSON(): { - summary: ExperimentSummary; - results: EvalResult[]; - } { - return { - summary: this.summary, - results: this.results, - }; - } + summary: ExperimentSummary; + results: EvalResult[]; } -export type { ReporterBody, ReporterDef } from "./reporters/types"; +export type { ReporterBody } from "./reporters/types"; async function getPersistedBaseExperimentId( experiment: Experiment, @@ -455,7 +425,7 @@ export type EvaluatorDef< evalName: string; } & Evaluator; -export type EvaluatorFile = { +type EvaluatorFile = { functions: CodeFunction< unknown, unknown, @@ -572,7 +542,7 @@ export function callEvaluatorData< }; } -export type SpanContext = { +type SpanContext = { currentSpan: typeof currentSpan; startSpan: typeof startSpan; withCurrent: typeof withCurrent; @@ -664,13 +634,15 @@ declare global { var _lazy_load: boolean; } -globalThis._evals = { - functions: [], - prompts: [], - parameters: [], - evaluators: {}, - reporters: {}, -}; +export function getEvaluatorRegistry(): EvaluatorFile { + return (globalThis._evals ??= { + functions: [], + prompts: [], + parameters: [], + evaluators: {}, + reporters: {}, + }); +} interface EvalOptions { /** @@ -750,7 +722,7 @@ interface EvalOptions { enableCache?: boolean; } -export function _initializeSpanContext() { +export function _initializeSpanContext(): void { // This only needs to be set once, but Eval(), Task(), etc. are the only time // we get to run code while importing a module, so use it to // grab these values. @@ -781,11 +753,11 @@ export async function Eval< : reporterOrOpts; let evalName = makeEvalName(name, evaluator.experimentName); - if (globalThis._evals.evaluators[evalName]) { - evalName = `${evalName}_${Object.keys(_evals).length}`; + if (getEvaluatorRegistry().evaluators[evalName]) { + evalName = `${evalName}_${Object.keys(getEvaluatorRegistry().evaluators).length}`; } if (globalThis._lazy_load) { - globalThis._evals.evaluators[evalName] = { + getEvaluatorRegistry().evaluators[evalName] = { // eslint-disable-next-line @typescript-eslint/consistent-type-assertions evaluator: { evalName, @@ -804,15 +776,15 @@ export async function Eval< _initializeSpanContext(); // Better to return this empty object than have an annoying-to-use signature - return new EvalResultWithSummary( - { + return { + summary: { scores: {}, metrics: {}, projectName: "", experimentName: "", }, - [], - ); + results: [], + }; } const progressReporter = options.progress ?? new SimpleProgressReporter(); @@ -820,7 +792,7 @@ export async function Eval< if (typeof options.reporter === "string") { throw new Error( - "Must specify a reporter object, not a name. Can only specify reporter names when running 'braintrust eval'", + "Must specify a reporter object, not a name. Can only specify reporter names when running 'bt eval'", ); } @@ -859,14 +831,13 @@ export async function Eval< const enableCache = options.enableCache ?? true; let ret; if (options.parent) { - ret = await withParent( + ret = await _internalWithParent( options.parent, () => runEvaluator( null, evalDef, progressReporter, - [], options.stream, options.parameters, shouldCollectResults, @@ -879,7 +850,6 @@ export async function Eval< experiment, evalDef, progressReporter, - [], options.stream, options.parameters, shouldCollectResults, @@ -909,75 +879,18 @@ export function Reporter( reporter: ReporterBody, ): ReporterDef { const ret = { name, ...reporter }; - if (_evals.reporters[name]) { + if (getEvaluatorRegistry().reporters[name]) { throw new Error(`Reporter ${name} already exists`); } if (globalThis._lazy_load) { - _evals.reporters[name] = ret; + getEvaluatorRegistry().reporters[name] = ret; } return ret; } -export interface Filter { - path: string[]; - pattern: RegExp; -} - -function serializeJSONWithPlainString(v: unknown) { - if (typeof v === "string") { - return v; - } else { - return JSON.stringify(v); - } -} - -function deserializePlainStringAsJSON(s: string) { - try { - return { value: JSON.parse(s), error: undefined }; - } catch (e) { - return { value: s, error: e }; - } -} - -export function parseFilters(filters: string[]): Filter[] { - const result: Filter[] = []; - for (const f of filters) { - const equalsIdx = f.indexOf("="); - if (equalsIdx === -1) { - throw new Error(`Invalid filter ${f}`); - } - const [path, value] = [f.slice(0, equalsIdx), f.slice(equalsIdx + 1)]; - let deserializedValue = deserializePlainStringAsJSON(value).value; - if (typeof deserializedValue !== "string") { - deserializedValue = value; // Just fall back to the original input - } - result.push({ - path: path.split("."), - pattern: new RegExp(deserializedValue), - }); - } - return result; -} - -function evaluateFilter(object: unknown, filter: Filter) { - const { path, pattern } = filter; - const key = path.reduce( - (acc, p) => - typeof acc === "object" && acc !== null - ? // eslint-disable-next-line @typescript-eslint/consistent-type-assertions - (acc as Record)[p] - : undefined, - object, - ); - if (key === undefined) { - return false; - } - return pattern.test(serializeJSONWithPlainString(key)); -} - -export function scorerName( +function scorerName( // eslint-disable-next-line @typescript-eslint/no-explicit-any scorer: EvalScorer, scorer_idx: number, @@ -988,7 +901,7 @@ export function scorerName( export function classifierName( classifier: EvalClassifier, classifier_idx: number, -) { +): string { return classifier.name || `classifier_${classifier_idx}`; } @@ -1009,9 +922,6 @@ export async function _internalRunEvaluatorTask( ...("metadata" in datum ? datum.metadata : {}), }; const hooks: EvalHooks, EvalParameters> = { - meta(value) { - Object.assign(metadata, value); - }, metadata, expected: "expected" in datum ? datum.expected : undefined, span, @@ -1236,7 +1146,6 @@ export async function runEvaluator( // eslint-disable-next-line @typescript-eslint/no-explicit-any evaluator: EvaluatorDef, progressReporter: ProgressReporter, - filters: Filter[], stream: ((data: SSEProgressEventData) => void) | undefined, parameters?: InferParameters, collectResults = true, @@ -1252,7 +1161,6 @@ export async function runEvaluator( experiment, evaluator, progressReporter, - filters, stream, parameters, collectResults, @@ -1275,7 +1183,6 @@ async function runEvaluatorInternal( // eslint-disable-next-line @typescript-eslint/no-explicit-any evaluator: EvaluatorDef, progressReporter: ProgressReporter, - filters: Filter[], stream: ((data: SSEProgressEventData) => void) | undefined, parameters: InferParameters | undefined, collectResults: boolean, @@ -1628,12 +1535,14 @@ async function runEvaluatorInternal( if (!experiment) { // This will almost always be a no-op span, but it means that if the Eval // is run in the context of a different type of span, it will be logged. + const { parent: _ignoredParent, ...spanEvent } = baseEvent; return await traced(callback, { - ...baseEvent, + ...spanEvent, state: evaluator.state, }); } else { - const result = await experiment.traced(callback, baseEvent); + const { parent: _ignoredParent, ...spanEvent } = baseEvent; + const result = await experiment.traced(callback, spanEvent); // Flush logs to provide backpressure and prevent memory accumulation // when maxConcurrency is set. Only flush when pending data exceeds the // byte threshold, avoiding excessive sequential round-trips for small @@ -1680,9 +1589,6 @@ async function runEvaluatorInternal( if (abortSignal.aborted) { break; } - if (!filters.every((f) => evaluateFilter(datum, f))) { - continue; - } const trialCount = datum.trialCount ?? evaluator.trialCount ?? 1; for (let trialIndex = 0; trialIndex < trialCount; trialIndex++) { if (abortSignal.aborted) { @@ -1765,10 +1671,10 @@ async function runEvaluatorInternal( localScoreAccumulator ?? undefined, ); - return new EvalResultWithSummary( + return { summary, - collectResults ? collectedResults : [], - ); + results: collectResults ? collectedResults : [], + }; } finally { // Clean up disk-based span cache after eval completes and stop caching // Only if it was enabled @@ -1781,10 +1687,9 @@ async function runEvaluatorInternal( } } -export const error = (text: string) => `Error: ${text}`; -export const warning = (text: string) => `Warning: ${text}`; +const warning = (text: string) => `Warning: ${text}`; -export function logError(e: unknown, verbose: boolean) { +export function logError(e: unknown, verbose: boolean): void { if (!verbose) { sdkConsole.error(`${e}`); } else { @@ -1852,12 +1757,7 @@ export function buildLocalSummary( }; } -export function reportFailures< - Input, - Output, - Expected, - Metadata extends BaseMetadata, ->( +function reportFailures( evaluator: EvaluatorDef, failingResults: EvalResult[], { verbose, jsonl }: ReporterOpts, diff --git a/js/src/framework2.test.ts b/js/src/framework2.test.ts index 35297ab36..bf31b2ea2 100644 --- a/js/src/framework2.test.ts +++ b/js/src/framework2.test.ts @@ -1,13 +1,20 @@ -import { afterEach, beforeAll, expect, expectTypeOf, test, vi } from "vitest"; +import { + afterEach, + beforeAll, + expect, + expectTypeOf, + test, + vi, +} from "vite-plus/test"; import * as z3 from "zod/v3"; import * as z4 from "zod/v4"; import { CodeFunction, ProjectNameIdMap, projects, - serializeEvalParametersToStaticParametersSchema, + CodeParameters, } from "./framework2"; -import { _internalGetGlobalState } from "./logger"; +import { _internalGetGlobalState } from "./logger/core"; import { configureNode } from "./node/config"; import { zodToJsonSchema } from "./zod/utils"; @@ -112,8 +119,8 @@ test.each([ expect(tool.handler({ text: "hello" })).toBe(5); }); -test("parameter builders serialize mixed Zod v3 and v4 schemas", () => { - const project = projects.create({ name: "test-zod-parameters" }); +test("parameter builders serialize mixed Zod v3 and v4 schemas", async () => { + const project = projects.create({ id: "test-zod-parameters" }); const schema = project.parameters.create({ name: "mixed", schema: { @@ -122,10 +129,18 @@ test("parameter builders serialize mixed Zod v3 and v4 schemas", () => { }, }); expectTypeOf(schema.suffix).toEqualTypeOf>(); - expect(serializeEvalParametersToStaticParametersSchema(schema)).toMatchObject( - { - prefix: { type: "data", schema: { type: "string" }, default: "start:" }, - suffix: { type: "data", schema: { type: "string" }, default: ":end" }, + const definition = await new CodeParameters(project, { + name: "mixed", + slug: "mixed", + schema, + }).toFunctionDefinition(new ProjectNameIdMap()); + expect(definition.function_data).toMatchObject({ + data: { prefix: "start:", suffix: ":end" }, + __schema: { + properties: { + prefix: { type: "string", default: "start:" }, + suffix: { type: "string", default: ":end" }, + }, }, - ); + }); }); diff --git a/js/src/framework2.ts b/js/src/framework2.ts index 458f1d278..757209c0e 100644 --- a/js/src/framework2.ts +++ b/js/src/framework2.ts @@ -1,29 +1,22 @@ +import { _initializeSpanContext, getEvaluatorRegistry } from "./framework"; import { debugLogger } from "./debug-logger"; -import { _initializeSpanContext } from "./framework"; import type { Trace } from "./trace"; -import iso from "./isomorph"; +import iso from "#braintrust-platform"; import { slugify } from "../util/string_util"; import { z } from "zod/v3"; -import { - type FunctionTypeEnumType as FunctionType, - type IfExistsType as IfExists, - type SavedFunctionIdType as SavedFunctionId, - type PromptBlockDataType as PromptBlockData, - type PromptDataType as PromptData, - ToolFunctionDefinition as toolFunctionDefinitionSchema, - type ToolFunctionDefinitionType as ToolFunctionDefinition, - FunctionData as functionDataSchema, - Project as projectSchema, - type ExtendedSavedFunctionIdType as ExtendedSavedFunctionId, -} from "./generated_types"; +import { Project as projectSchema } from "./sdk-schemas"; +import type { + FunctionTypeEnum as FunctionType, + IfExists, + SavedFunctionId, + PromptBlockData, + PromptData, + ToolFunctionDefinition, + ExtendedSavedFunctionId, +} from "./sdk-types"; import { loadPrettyXact, TransactionId } from "../util/index"; -import { - _internalGetGlobalState, - login, - Prompt, - PromptRowWithId, - RemoteEvalParameters, -} from "./logger"; +import { _internalGetGlobalState, login } from "./logger/core"; +import { Prompt, PromptRowWithId } from "./logger/prompts"; import type { BaseFnOpts, GenericFunction } from "./framework-types"; import type { EvalParameters } from "./eval-parameters"; import { @@ -31,20 +24,18 @@ import { type PromptDefinition, } from "./prompt-schemas"; import { zodToJsonSchema, type ZodSchema } from "./zod/utils"; -import type { - ParametersSchema, - StaticParametersSchema, - SerializedParametersContainer, -} from "../dev/types"; - -export { toolFunctionDefinitionSchema }; -// ToolFunctionDefinition exported as type-only from main index to avoid namespace issues +type ParametersSchema = { + type: "object"; + properties: Record>; + required?: string[]; + additionalProperties?: boolean; +}; // Safe access to __filename (only exists in Node.js CJS) const currentFilename = typeof __filename !== "undefined" ? __filename : "unknown"; -export type CreateProjectOpts = +type CreateProjectOpts = | { name: string; /** @@ -55,13 +46,13 @@ export type CreateProjectOpts = } | { id: string }; class ProjectBuilder { - create(opts: CreateProjectOpts) { + create(opts: CreateProjectOpts): Project { return new Project(opts); } } -export const projects = new ProjectBuilder(); +export const projects: ProjectBuilder = new ProjectBuilder(); -export class Project { +class Project { public readonly name?: string; public readonly id?: string; public readonly projectGroupName?: string; @@ -94,20 +85,18 @@ export class Project { this.classifiers = new ClassifierBuilder(this); } - public addPrompt(prompt: CodePrompt) { + public addPrompt(prompt: CodePrompt): void { this._publishablePrompts.push(prompt); if (globalThis._lazy_load) { - globalThis._evals.prompts.push(prompt); + getEvaluatorRegistry().prompts.push(prompt); } } - public addParameters(parameters: CodeParameters) { + public addParameters(parameters: CodeParameters): void { this._publishableParameters.push(parameters); if (globalThis._lazy_load) { - if (globalThis._evals.parameters == null) - globalThis._evals.parameters = []; - - globalThis._evals.parameters.push(parameters); + const registry = getEvaluatorRegistry(); + (registry.parameters ??= []).push(parameters); } } @@ -120,26 +109,24 @@ export class Project { // eslint-disable-next-line @typescript-eslint/no-explicit-any GenericFunction >, - ) { + ): void { this._publishableCodeFunctions.push(fn); if (globalThis._lazy_load) { - globalThis._evals.functions.push(fn); + getEvaluatorRegistry().functions.push(fn); } } - async publish() { + async publish(): Promise { if (globalThis._lazy_load) { - debugLogger.notice.warn( - "publish() is a no-op when running `braintrust push`.", - ); + debugLogger.notice.warn("publish() is a no-op when running `bt push`."); return; } await login(); const projectMap = new ProjectNameIdMap(); - const functionDefinitions: FunctionEvent[] = []; + const functionDefinitions: PromptFunctionEvent[] = []; if (this._publishableCodeFunctions.length > 0) { debugLogger.notice.warn( - "Code functions cannot be published directly. Use `braintrust push` instead.", + "Code functions cannot be published directly. Use `bt push` instead.", ); } if (this._publishablePrompts.length > 0) { @@ -156,7 +143,7 @@ export class Project { } } -export class ToolBuilder { +class ToolBuilder { private taskCounter = 0; constructor(private readonly project: Project) {} @@ -226,7 +213,7 @@ export class ToolBuilder { } } -export class ScorerBuilder { +class ScorerBuilder { private taskCounter = 0; constructor(private readonly project: Project) {} @@ -239,7 +226,7 @@ export class ScorerBuilder { Exact>, Returns >, - >(opts: ScorerOpts) { + >(opts: ScorerOpts): void { this.taskCounter++; let resolvedName = opts.name; @@ -319,7 +306,9 @@ class ClassifierBuilder { Exact>, Returns >, - >(opts: ClassifierOpts) { + >( + opts: ClassifierOpts, + ): CodeFunction>, Returns, Fn> { this.taskCounter++; let resolvedName = opts.name ?? opts.handler.name; @@ -344,7 +333,7 @@ class ClassifierBuilder { } } -export type CodeOpts< +type CodeOpts< Params, Returns, Fn extends GenericFunction, @@ -387,7 +376,7 @@ type ScorerOptsUnion< | CodeOpts>, Returns, Fn> | ScorerPromptOpts; -export type ScorerOpts< +type ScorerOpts< Output, Input, Params, @@ -510,7 +499,7 @@ export class CodePrompt { async toFunctionDefinition( projectNameToId: ProjectNameIdMap, - ): Promise { + ): Promise { const prompt_data = { ...this.prompt, }; @@ -572,7 +561,7 @@ interface PromptNoTrace { noTrace: boolean; } -export type PromptOpts< +type PromptOpts< HasId extends boolean, HasVersion extends boolean, HasTools extends boolean = true, @@ -586,7 +575,7 @@ export type PromptOpts< (HasNoTrace extends true ? Partial : {}) & PromptDefinition; -export class PromptBuilder { +class PromptBuilder { constructor(private readonly project: Project) {} public create< @@ -677,7 +666,7 @@ export class CodeParameters { async toFunctionDefinition( projectNameToId: ProjectNameIdMap, - ): Promise { + ): Promise { const schema = serializeEvalParameterstoParametersSchema(this.schema); return { project_id: await projectNameToId.resolve(this.project), @@ -717,47 +706,6 @@ class ParametersBuilder { } } -export function serializeEvalParametersToStaticParametersSchema( - parameters: EvalParameters, -): StaticParametersSchema { - return Object.fromEntries( - Object.entries(parameters).map(([name, value]) => { - if ("type" in value && value.type === "prompt") { - return [ - name, - { - type: "prompt", - default: value.default - ? promptDefinitionToPromptData(value.default) - : undefined, - description: value.description, - }, - ]; - } else if ("type" in value && value.type === "model") { - return [ - name, - { - type: "model", - default: value.default, - description: value.description, - }, - ]; - } else { - const jsonSchema = zodToJsonSchema(value); - return [ - name, - { - type: "data", - schema: jsonSchema, - default: jsonSchema.default, - description: jsonSchema.description, - }, - ]; - } - }), - ); -} - function serializeEvalParameterstoParametersSchema( parameters: EvalParameters, ): ParametersSchema { @@ -824,38 +772,11 @@ function getDefaultDataFromParametersSchema( ); } -export function serializeRemoteEvalParametersContainer( - parameters: EvalParameters | RemoteEvalParameters, -): SerializedParametersContainer { - if (RemoteEvalParameters.isParameters(parameters)) { - return { - type: "braintrust.parameters", - // eslint-disable-next-line @typescript-eslint/consistent-type-assertions - schema: parameters.schema as ParametersSchema, - source: { - parametersId: parameters.id, - slug: parameters.slug, - name: parameters.name, - projectId: parameters.projectId, - version: parameters.version, - }, - }; - } - - return { - type: "braintrust.staticParameters", - schema: serializeEvalParametersToStaticParametersSchema(parameters), - source: null, - }; -} - -export interface FunctionEvent { +interface FunctionEventBase { project_id: string; slug: string; name: string; description: string; - prompt_data?: PromptData; - function_data: z.infer; function_type?: FunctionType; if_exists?: IfExists; tags?: string[]; @@ -863,6 +784,20 @@ export interface FunctionEvent { environments?: { slug: string }[]; } +interface PromptFunctionEvent extends FunctionEventBase { + prompt_data: PromptData; + function_data: { type: "prompt" }; +} + +interface ParametersFunctionEvent extends FunctionEventBase { + function_type: "parameters"; + function_data: { + type: "parameters"; + data: Record; + __schema: ParametersSchema; + }; +} + export class ProjectNameIdMap { private nameToId: Record = {}; private idToName: Record = {}; diff --git a/js/src/functions/invoke.test.ts b/js/src/functions/invoke.test.ts index 4dd5deef6..3272151e7 100644 --- a/js/src/functions/invoke.test.ts +++ b/js/src/functions/invoke.test.ts @@ -1,6 +1,7 @@ -import { describe, expect, test, beforeEach, afterEach } from "vitest"; +import { describe, expect, test, beforeEach, afterEach } from "vite-plus/test"; import { initFunction } from "./invoke"; -import { _internalGetGlobalState, _exportsForTestingOnly } from "../logger"; +import { _internalGetGlobalState } from "../logger/core"; +import { _exportsForTestingOnly } from "../logger/testing"; import { configureNode } from "../node/config"; describe("initFunction", () => { @@ -59,7 +60,7 @@ describe("registerOtelFlush", () => { }); test("should register OTEL flush callback", async () => { - const { registerOtelFlush } = await import("../logger"); + const { registerOtelFlush } = await import("../logger/core"); const state = _internalGetGlobalState(); let flushed = false; diff --git a/js/src/functions/invoke.ts b/js/src/functions/invoke.ts index f7bbb35f5..fdc97f807 100644 --- a/js/src/functions/invoke.ts +++ b/js/src/functions/invoke.ts @@ -1,28 +1,24 @@ -import { - FunctionId as functionIdSchema, - type InvokeFunctionType as InvokeFunctionRequest, - type ChatCompletionMessageParamType as Message, - type StreamingModeType as StreamingMode, - type FunctionTypeEnumType as FunctionType, -} from "../generated_types"; +import { FunctionId as functionIdSchema } from "../sdk-schemas"; +import type { + InvokeFunction as InvokeFunctionRequest, + ChatCompletionMessageParam as Message, + StreamingMode, + FunctionTypeEnum as FunctionType, +} from "../sdk-types"; import { _internalGetGlobalState, BraintrustState, Exportable, FullLoginOptions, getSpanParentObject, -} from "../logger"; +} from "../logger/core"; import { BraintrustStream } from "./stream"; import { z } from "zod/v3"; /** * Arguments for the `invoke` function. */ -export interface InvokeFunctionArgs< - Input, - Output, - Stream extends boolean = false, -> { +interface InvokeFunctionArgs { // These parameters are duplicated from FunctionId, so that we can document them. /** @@ -136,7 +132,7 @@ export interface InvokeFunctionArgs< * if `stream` is true, otherwise returns the output of the function using the Zod schema's * type if present. */ -export type InvokeReturn = Stream extends true +type InvokeReturn = Stream extends true ? BraintrustStream : Output; @@ -283,7 +279,7 @@ export function initFunction({ slug: string; version?: string; state?: BraintrustState; -}) { +}): (input: any) => Promise { // Disable span cache since remote function spans won't be in the local cache const s = state ?? _internalGetGlobalState(); s?.spanCache?.disable(); diff --git a/js/src/functions/stream.test.ts b/js/src/functions/stream.test.ts index 235c5dbcb..d3585aa9e 100644 --- a/js/src/functions/stream.test.ts +++ b/js/src/functions/stream.test.ts @@ -1,4 +1,4 @@ -import { expect, test } from "vitest"; +import { expect, test } from "vite-plus/test"; import { BraintrustStream, BraintrustStreamChunk, diff --git a/js/src/functions/stream.ts b/js/src/functions/stream.ts index 300895075..ae0ba64e1 100644 --- a/js/src/functions/stream.ts +++ b/js/src/functions/stream.ts @@ -1,9 +1,13 @@ import { - type CallEventType as CallEventSchema, CallEvent as callEventSchema, SSEConsoleEventData as sseConsoleEventDataSchema, SSEProgressEventData as sseProgressEventDataSchema, -} from "../generated_types"; +} from "../sdk-schemas"; +import type { + CallEvent, + SSEConsoleEventData, + SSEProgressEventData, +} from "../sdk-types"; import { createParser, EventSourceParser, @@ -12,7 +16,21 @@ import { } from "eventsource-parser"; import { z } from "zod/v3"; -export const braintrustStreamChunkSchema = z.union([ +/** + * A chunk of data from a Braintrust stream. Each chunk type matches + * an SSE event type. + */ +export type BraintrustStreamChunk = + | { type: "text_delta"; data: string } + | { type: "reasoning_delta"; data: string } + | { type: "json_delta"; data: string } + | { type: "error"; data: string } + | { type: "console"; data: SSEConsoleEventData } + | { type: "progress"; data: SSEProgressEventData } + | { type: "start"; data: string } + | { type: "done"; data: string }; + +const braintrustStreamChunkSchema: z.ZodType = z.union([ z.object({ type: z.literal("text_delta"), data: z.string(), @@ -47,12 +65,6 @@ export const braintrustStreamChunkSchema = z.union([ }), ]); -/** - * A chunk of data from a Braintrust stream. Each chunk type matches - * an SSE event type. - */ -export type BraintrustStreamChunk = z.infer; - /** * A Braintrust stream. This is a wrapper around a ReadableStream of `BraintrustStreamChunk`, * with some utility methods to make them easy to log and convert into various formats. @@ -163,7 +175,7 @@ export class BraintrustStream { return this.memoizedFinalValue; } - static parseRawEvent(event: CallEventSchema): BraintrustStreamChunk { + static parseRawEvent(event: CallEvent): BraintrustStreamChunk { switch (event.event) { case "text_delta": return { @@ -212,7 +224,7 @@ export class BraintrustStream { } } - static serializeRawEvent(event: BraintrustStreamChunk): CallEventSchema { + static serializeRawEvent(event: BraintrustStreamChunk): CallEvent { switch (event.type) { case "text_delta": return { diff --git a/js/src/generated_types.ts b/js/src/generated_types.ts index ea2bc045b..75f0a42ce 100644 --- a/js/src/generated_types.ts +++ b/js/src/generated_types.ts @@ -1,6 +1,6 @@ // Auto-generated file (content hash 387034529ea006a9) -- do not modify -import { z } from "zod/v3"; +import {z} from "zod/v3"; export const AclObjectType = z.union([ z.enum([ diff --git a/js/src/git-command.test.ts b/js/src/git-command.test.ts index 4298b1b00..441f83d1a 100644 --- a/js/src/git-command.test.ts +++ b/js/src/git-command.test.ts @@ -2,7 +2,7 @@ import { execFile } from "node:child_process"; import { mkdtemp, rm, writeFile } from "node:fs/promises"; import { tmpdir } from "node:os"; import * as path from "node:path"; -import { afterEach, beforeEach, expect, test, vi } from "vitest"; +import { afterEach, beforeEach, expect, test, vi } from "vite-plus/test"; import { findGitExecutable, resetGitExecutableCacheForTests, diff --git a/js/src/git-command.ts b/js/src/git-command.ts index 58969cb50..4eec1ba4b 100644 --- a/js/src/git-command.ts +++ b/js/src/git-command.ts @@ -16,7 +16,7 @@ function executableSearchPath(): string | undefined { } export async function findGitExecutable( - searchPath = executableSearchPath(), + searchPath: string | undefined = executableSearchPath(), ): Promise { if (!searchPath) { return undefined; diff --git a/js/src/gitutil.ts b/js/src/gitutil.ts index 7b23999af..0f1f70422 100644 --- a/js/src/gitutil.ts +++ b/js/src/gitutil.ts @@ -1,7 +1,4 @@ -import { - type GitMetadataSettingsType as GitMetadataSettings, - type RepoInfoType as RepoInfo, -} from "./generated_types"; +import type { GitMetadataSettings, RepoInfo } from "./sdk-types"; import { debugLogger } from "./debug-logger"; import { runGitCommand } from "./git-command"; @@ -108,7 +105,7 @@ async function getBaseBranchAncestor(remote: string | undefined = undefined) { export async function getPastNAncestors( n: number = 1000, remote: string | undefined = undefined, -) { +): Promise { const repoPath = await currentRepoPath(); if (!repoPath) { return []; @@ -153,7 +150,9 @@ function truncateToByteLimit(s: string, byteLimit: number = 65536): string { return new TextDecoder().decode(truncated); } -export async function getRepoInfo(settings?: GitMetadataSettings) { +export async function getRepoInfo( + settings?: GitMetadataSettings, +): Promise { if (!settings || settings.collect === "none") { return undefined; } @@ -171,7 +170,7 @@ export async function getRepoInfo(settings?: GitMetadataSettings) { return sanitized; } -export async function currentRepoPath(): Promise { +async function currentRepoPath(): Promise { return await attempt(async () => (await runGitCommand(["rev-parse", "--show-toplevel"])).trim(), ); diff --git a/js/src/global-instrumentation-hooks.test.ts b/js/src/global-instrumentation-hooks.test.ts index d0e93d577..978ec1cbc 100644 --- a/js/src/global-instrumentation-hooks.test.ts +++ b/js/src/global-instrumentation-hooks.test.ts @@ -1,6 +1,6 @@ import { AsyncLocalStorage } from "node:async_hooks"; import { randomUUID } from "node:crypto"; -import { describe, expect, it, vi } from "vitest"; +import { describe, expect, it, vi } from "vite-plus/test"; import { GLOBAL_INSTRUMENTATION_HOOKS_KEY, GLOBAL_INSTRUMENTATION_HOOKS_PROTOCOL_VERSION, diff --git a/js/src/graph-framework.ts b/js/src/graph-framework.ts deleted file mode 100644 index f60ff3fba..000000000 --- a/js/src/graph-framework.ts +++ /dev/null @@ -1,499 +0,0 @@ -import { newId, Prompt } from "./logger"; -import { - type FunctionIdType as FunctionId, - type GraphDataType as GraphData, - type GraphNodeType as GraphNode, - type GraphEdgeType as GraphEdge, - type PromptBlockDataType as PromptBlockData, -} from "./generated_types"; - -export interface BuildContext { - getFunctionId(functionObj: unknown): Promise; -} - -// Base interface for all node types -export interface Node { - readonly id: string; - __type: "node"; - build(context: BuildContext): Promise; - addDependency(dependency: Dependency): void; -} - -// type CallArgs = ProxyVariable | Node | Record; - -export type NodeLike = Node | Prompt | ProxyVariable; - -export type LazyGraphNode = { - type: "lazy"; - id: string; -}; - -// Graph builder class to convert functional chains to GraphData -export class GraphBuilder { - private nodes = new Map(); - private edges: Record = {}; - - private nodeLikeNodes = new Map(); // Maps node-like objects, like prompts, to their nodes - - // Special nodes - public readonly IN: InputNode; - public readonly OUT: OutputNode; - - constructor() { - // Create input and output nodes - this.IN = this.createInputNode(); - this.OUT = this.createOutputNode(); - } - - // Create the final GraphData object - public async build(context: BuildContext): Promise { - const nodes = await Promise.all( - Array.from(this.nodes.values()).map(async (node) => [ - node.id, - await node.build(context), - ]), - ); - - return { - type: "graph", - nodes: Object.fromEntries(nodes), // XXX Need to resolve the lazy nodes - edges: this.edges, - }; - } - - public addEdge({ - source, - sourceVar, - target, - targetVar, - expr, - purpose, - }: { - source: NodeLike; - sourceVar?: string; - target: NodeLike; - targetVar?: string; - expr?: string; - purpose: GraphEdge["purpose"]; - }) { - const [sourceNode, sourcePath] = this.resolveNode(source); - if (sourcePath.length > 0) { - // XXX Maybe we can remove these paths? - throw new Error("Source path must be empty"); - } - const [targetNode, targetPath] = this.resolveNode(target); - if (targetPath.length > 0) { - throw new Error("Target path must be empty"); - } - const id = this.generateId(); - - sourceVar = sourceVar ?? "output"; - targetVar = - targetVar ?? (purpose === "data" ? "input" : this.generateId("control")); - - // Make sure this variable name doesn't already exist as a target variable - for (const edge of Object.values(this.edges)) { - if ( - edge.target.node === targetNode.id && - edge.target.variable === targetVar - ) { - throw new Error( - `Variable name ${targetVar} already set on ${targetNode.id}`, - ); - } - } - - this.edges[id] = { - source: { node: sourceNode.id, variable: sourceVar }, - target: { node: targetNode.id, variable: targetVar }, - purpose, - }; - } - - public resolveNode(node: NodeLike): [Node, string[]] { - if (node instanceof Prompt) { - const cached = this.nodeLikeNodes.get(node); - if (cached) { - return [cached, []]; - } - const promptNode = this.createPromptNode(node); - this.nodeLikeNodes.set(node, promptNode); - return [promptNode, []]; - } else if (isProxyVariable(node)) { - return proxyVariableToNode(node); - } else { - return [node, []]; - } - } - - // Create a literal node - public literal(value: T): LiteralNode { - const preview = ( - typeof value === "string" ? value : JSON.stringify(value) - ).slice(0, 16); - const id = this.generateId(`literal-${preview}`); - const literalNode = new LiteralNode(this, id, value); - this.nodes.set(id, literalNode); - return literalNode; - } - - public gate(options: { condition: string }): GateNode { - const id = this.generateId("gate"); - const gateNode = new GateNode(this, id, options.condition); - this.nodes.set(id, gateNode); - return gateNode; - } - - public aggregator(): AggregatorNode { - const id = this.generateId("aggregator"); - const aggregatorNode = new AggregatorNode(this, id); - this.nodes.set(id, aggregatorNode); - return aggregatorNode; - } - - public promptTemplate(options: { - prompt: PromptBlockData; - }): PromptTemplateNode { - const id = this.generateId("prompt-template"); - const promptTemplateNode = new PromptTemplateNode(this, id, options.prompt); - this.nodes.set(id, promptTemplateNode); - return promptTemplateNode; - } - - // public call(node: NodeLike, input: CallArgs): Node { - // const [resolvedNode, path] = this.resolveNode(node); - // if (resolvedNode instanceof SingleInputNode) { - // return resolvedNode.call(input, path); - // } else { - // throw new Error("Node must be a SingleInputNode"); - // } - // } - - // Helper to generate node IDs - private generateId(name?: string): string { - const uuid = newId(); - if (name) { - return `${name}-${uuid.slice(0, 8)}`; - } else { - return uuid; - } - } - - // Create an input node - private createInputNode(): InputNode { - const id = this.generateId("input"); - const inputNode = new InputNode(this, id); - this.nodes.set(id, inputNode); - return inputNode; - } - - // Create an output node - private createOutputNode(): OutputNode { - const id = this.generateId("output"); - const outputNode = new OutputNode(this, id); - this.nodes.set(id, outputNode); - return outputNode; - } - - // Create a prompt node from a CodePrompt - private createPromptNode(prompt: Prompt): PromptNode { - const id = this.generateId(`prompt-${prompt.slug}`); - - const promptNode = new PromptNode(this, id, prompt); - this.nodes.set(id, promptNode); - return promptNode; - } -} - -export type ProxyVariable = { - [key: string]: ProxyVariable; -}; - -function isProxyVariable(node: unknown): node is ProxyVariable { - return ( - typeof node === "object" && - node !== null && - "__type" in node && - // @ts-ignore - node.__type === "proxy-variable" - ); -} - -function proxyVariableToNode(proxy: ProxyVariable): [Node, string[]] { - // @ts-ignore - return [proxy.__node, proxy.__path]; -} - -// // Create a proxy handler that captures property access paths -// function createVariableProxy({ -// path, -// node, -// }: { -// path: string[]; -// node: Node; -// }): ProxyVariable { -// // eslint-disable-next-line @typescript-eslint/consistent-type-assertions -// return new Proxy({} as ProxyVariable, { -// get(target, prop) { -// if (typeof prop === "string") { -// if (prop === "__type") { -// return "proxy-variable"; -// } else if (prop === "__node") { -// return node; -// } else if (prop === "__path") { -// return path; -// } - -// const newPath = [...path, prop]; - -// // Return a variable reference for terminal properties -// // or a new proxy for further chaining -// return createVariableProxy({ path: newPath, node }); -// } -// return undefined; -// }, -// has(target, prop) { -// return typeof prop === "string"; -// }, -// }); -// } - -// Type for transform functions -export type TransformFn = (input: ProxyVariable) => Node; - -interface Dependency { - node: Node; - sourceVar?: string; - targetVar?: string; - expr?: string; -} - -// Base Node class for common functionality -abstract class BaseNode implements Node { - public readonly __type = "node"; - public dependencies: Dependency[] = []; - - constructor( - protected graph: GraphBuilder, - public readonly id: string, - ) {} - - public addDependency(dependency: Dependency) { - this.dependencies.push(dependency); - } - - abstract build(context: BuildContext): Promise; -} - -// abstract class SingleOutputNode extends BaseNode { -// // Connect this node to another node -// public then(...args: Array): Node { -// // eslint-disable-next-line @typescript-eslint/consistent-type-assertions -// const callableThis = this as unknown as Node; -// let lastNode: Node = callableThis; - -// // Connect each arg to this node -// for (const arg of args) { -// // Handle different types of arguments -// if (typeof arg === "function") { -// // This function is expected to take dependencies as needed on the argsProxy. -// const argsProxy = createVariableProxy({ path: [], node: callableThis }); -// const result = arg(argsProxy); -// lastNode = result; -// } else { -// const [node, path] = this.graph.resolveNode(arg); -// lastNode = node; -// node.addDependency({ node: callableThis, expr: escapePath(path) }); -// } -// } - -// return lastNode; -// } -// } - -// abstract class SingleInputNode extends BaseNode { -// public call(input: CallArgs, path?: string[]): Node { -// if (isProxyVariable(input)) { -// const [sourceNode, sourcePath] = proxyVariableToNode(input); -// this.addDependency({ node: sourceNode, expr: escapePath(sourcePath) }); -// } else if (isNode(input)) { -// this.addDependency({ -// node: input, -// expr: path ? escapePath(path) : undefined, -// }); -// } else { -// for (const [targetVar, source] of Object.entries(input)) { -// const [sourceNode, sourcePath] = this.graph.resolveNode(source); -// this.addDependency({ -// node: sourceNode, -// expr: sourcePath ? escapePath(sourcePath) : undefined, -// targetVar, -// }); -// } -// } -// return this; -// } -// } - -// function isNode(node: unknown): node is Node { -// return ( -// typeof node === "object" && -// node !== null && -// "__type" in node && -// // @ts-ignore -// node.__type === "node" -// ); -// } - -// Input node (entry point to the graph) -export class InputNode extends BaseNode implements Node { - constructor(graph: GraphBuilder, id: string) { - super(graph, id); - } - - public async build(context: BuildContext): Promise { - return { - type: "input", - description: "Input to the graph", - }; - } -} - -// Output node (exit point from the graph) -export class OutputNode extends BaseNode implements Node { - constructor(graph: GraphBuilder, id: string) { - super(graph, id); - } - - public async build(context: BuildContext): Promise { - return { - type: "output", - description: "Output of the graph", - }; - } -} - -// Prompt node (wrapper for CodePrompt) -export class PromptNode extends BaseNode implements Node { - constructor( - graph: GraphBuilder, - id: string, - private prompt: Prompt, - ) { - super(graph, id); - } - - public async build(context: BuildContext): Promise { - return { - type: "function", - function: await context.getFunctionId(this.prompt), - }; - } -} - -// Gate node for conditional branching -export class GateNode extends BaseNode implements Node { - constructor( - graph: GraphBuilder, - id: string, - private condition: string, - ) { - super(graph, id); - } - - public async build(context: BuildContext): Promise { - return { - type: "gate", - description: "Conditional gate", - condition: this.condition, - }; - } -} - -export class AggregatorNode extends BaseNode implements Node { - constructor(graph: GraphBuilder, id: string) { - super(graph, id); - } - - public async build(context: BuildContext): Promise { - return { - type: "aggregator", - description: "Aggregator", - }; - } -} - -export class PromptTemplateNode extends BaseNode implements Node { - constructor( - graph: GraphBuilder, - id: string, - private prompt: PromptBlockData, - ) { - super(graph, id); - } - - public async build(context: BuildContext): Promise { - return { - type: "prompt_template", - prompt: this.prompt, - }; - } -} - -// Literal node for constant values -export class LiteralNode extends BaseNode implements Node { - constructor( - graph: GraphBuilder, - id: string, - private value: T, - ) { - super(graph, id); - } - - public async build(context: BuildContext): Promise { - return { - type: "literal", - value: this.value, - }; - } -} - -// Create a graph instance with IN and OUT nodes -export function createGraph(): GraphBuilder { - const graphBuilder = new GraphBuilder(); - return graphBuilder; -} - -// XXX write tests -export function escapePath(parts: string[]): string | undefined { - if (parts.length === 0) { - return undefined; - } - return parts - .map((part) => { - if (/[^\w-]/.test(part)) { - // Escape special characters properly - const escaped = part.replace(/["\\]/g, "\\$&"); - return `"${escaped}"`; - } - return part; - }) - .join("."); -} - -export function unescapePath(path: string): string[] { - const regex = /"((?:\\["\\]|[^"\\])*)"|([^\.]+)/g; - const matches = path.match(regex); - return matches - ? matches.map((match) => { - if (match.startsWith('"')) { - // Remove surrounding quotes and unescape special characters - return match.slice(1, -1).replace(/\\(["\\])/g, "$1"); - } - return match; - }) - : []; -} - -// Export the graph constructor -export default { createGraph }; diff --git a/js/src/id-gen.test.ts b/js/src/id-gen.test.ts index 2ea2ae85b..e21473663 100644 --- a/js/src/id-gen.test.ts +++ b/js/src/id-gen.test.ts @@ -1,4 +1,4 @@ -import { expect, test, describe, beforeEach, afterEach } from "vitest"; +import { expect, test, describe, beforeEach, afterEach } from "vite-plus/test"; import { UUIDGenerator, OTELIDGenerator, getIdGenerator } from "./id-gen"; import { configureNode } from "./node/config"; diff --git a/js/src/id-gen.ts b/js/src/id-gen.ts index 67925916a..237dc0fec 100644 --- a/js/src/id-gen.ts +++ b/js/src/id-gen.ts @@ -7,7 +7,7 @@ import { v4 as uuidv4 } from "uuid"; import { debugLogger } from "./debug-logger"; -import iso from "./isomorph"; +import iso from "#braintrust-platform"; /** * Abstract base class for ID generators @@ -136,15 +136,10 @@ export function resolveUseLegacyUuidIds(): boolean { * This eliminates global state and makes tests parallelizable. * Each caller gets their own generator instance. * - * Honors an explicitly-installed `globalThis.BRAINTRUST_ID_GENERATOR` (e.g. set - * by `@braintrust/otel`'s `setupOtelCompat()`). Otherwise it defaults to - * OpenTelemetry-compatible hex IDs, falling back to legacy UUID IDs when - * BRAINTRUST_LEGACY_IDS is set. + * Defaults to OpenTelemetry-compatible hex IDs, falling back to legacy UUID + * IDs when BRAINTRUST_LEGACY_IDS is set. */ export function getIdGenerator(): IDGenerator { - if (globalThis.BRAINTRUST_ID_GENERATOR !== undefined) { - return new globalThis.BRAINTRUST_ID_GENERATOR(); - } return resolveUseLegacyUuidIds() ? new UUIDGenerator() : new OTELIDGenerator(); diff --git a/js/src/imports.test.ts b/js/src/imports.test.ts index 96a4851d5..11ef3190e 100644 --- a/js/src/imports.test.ts +++ b/js/src/imports.test.ts @@ -1,89 +1,8 @@ -import { describe, it, expect } from "vitest"; +import { describe, it, expect } from "vite-plus/test"; import * as fs from "fs"; import * as path from "path"; -describe("CLI import restrictions", () => { - it("should not import from cli directory in non-cli code", () => { - const srcDir = path.join(__dirname); - const violations: string[] = []; - - function walkDirectory(dir: string) { - const entries = fs.readdirSync(dir, { withFileTypes: true }); - - for (const entry of entries) { - const fullPath = path.join(dir, entry.name); - const relativePath = path.relative(srcDir, fullPath); - - // Skip CLI directory and test files - if (relativePath.startsWith("cli/") || relativePath === "cli") { - continue; - } - - if (entry.isDirectory() && entry.name === "node_modules") { - continue; - } - - if (entry.isDirectory()) { - walkDirectory(fullPath); - } else if ( - entry.isFile() && - (entry.name.endsWith(".ts") || entry.name.endsWith(".tsx")) && - !entry.name.endsWith(".d.ts") && - !entry.name.endsWith(".d.tsx") - ) { - checkFileForCliImports(fullPath, relativePath); - } - } - } - - function checkFileForCliImports(filePath: string, relativePath: string) { - const content = fs.readFileSync(filePath, "utf-8"); - const lines = content.split("\n"); - - lines.forEach((line, index) => { - // Check for import statements that reference CLI - const importMatch = - line.match(/from\s+["']([^"']+)["']/) || - line.match(/import\s*\(\s*["']([^"']+)["']\s*\)/); - - if (importMatch) { - const importPath = importMatch[1]; - - // Check if the import path references the CLI directory - // Only match actual /cli/ directory paths, not package names containing "cli" - // (e.g., @temporalio/client should not match) - if ( - importPath.includes("/cli/") || - importPath === "./cli" || - importPath === "../cli" || - importPath.endsWith("/cli") || - importPath.match(/^\.\.\/.*\/cli$/) || - importPath.match(/^\.\.\/.*\/cli\//) - ) { - violations.push( - `${relativePath}:${index + 1} - Illegal import from CLI: "${importPath}"`, - ); - } - } - }); - } - - walkDirectory(srcDir); - - if (violations.length > 0) { - const message = [ - "Found illegal imports from CLI directory in SDK code:", - "", - ...violations, - "", - "SDK code (src/**) must not import from CLI code (src/cli/**).", - "CLI code can import from SDK code, but not vice versa.", - ].join("\n"); - - expect.fail(message); - } - }); - +describe("Import restrictions", () => { it("should not allow eslint-disable comments for no-restricted-imports", () => { const srcDir = path.join(__dirname); const violations: string[] = []; @@ -95,11 +14,6 @@ describe("CLI import restrictions", () => { const fullPath = path.join(dir, entry.name); const relativePath = path.relative(srcDir, fullPath); - // Skip CLI directory and test files - if (relativePath.startsWith("cli/") || relativePath === "cli") { - continue; - } - if (entry.isDirectory() && entry.name === "node_modules") { continue; } @@ -153,7 +67,7 @@ describe("CLI import restrictions", () => { ...violations, "", "Disabling the no-restricted-imports rule is not allowed.", - "This rule prevents SDK code from importing CLI code and cannot be bypassed.", + "This rule protects SDK module boundaries and cannot be bypassed.", "If you believe you have a legitimate need for this import, please discuss with the team.", ].join("\n"); @@ -172,11 +86,6 @@ describe("CLI import restrictions", () => { const fullPath = path.join(dir, entry.name); const relativePath = path.relative(srcDir, fullPath); - // Skip the cli directory - CLI code is allowed to use require() and dynamic imports - if (entry.isDirectory() && entry.name === "cli") { - continue; - } - // Skip node_modules directories (test fixture deps, not SDK source) if (entry.isDirectory() && entry.name === "node_modules") { continue; @@ -204,19 +113,17 @@ describe("CLI import restrictions", () => { const content = fs.readFileSync(filePath, "utf-8"); const lines = content.split("\n"); - // Exception: the Mastra observability patch *generates* CJS source code - // (a string of JavaScript that is appended to @mastra/core's - // dist/mastra/index.cjs). That source legitimately contains - // `require(...)` because it runs inside a CommonJS module's evaluation - // — it isn't a require() call from our SDK code itself. Normalize the - // separator so the exemption matches on Windows (`\\`) too. - const isMastraPatchGenerator = relativePath - .replaceAll(path.sep, "/") - .includes("auto-instrumentations/loader/mastra-observability-patch.ts"); + // These generators emit CJS source containing require() calls for the + // instrumented module to execute, rather than calling require() in the SDK. + // Normalize separators so the exceptions also match on Windows. + const isCommonJsGenerator = [ + "auto-instrumentations/loader/mastra-observability-patch.ts", + "auto-instrumentations/orchestrion-js/transformer.ts", + ].includes(relativePath.replaceAll(path.sep, "/")); lines.forEach((line, index) => { // Check for require() calls - if (/\brequire\s*\(/.test(line) && !isMastraPatchGenerator) { + if (/\brequire\s*\(/.test(line) && !isCommonJsGenerator) { violations.push( `${relativePath}:${index + 1} - Found require() statement: "${line.trim()}"`, ); @@ -224,11 +131,13 @@ describe("CLI import restrictions", () => { // Check for dynamic import() statements // Match import(...) but not static import statements - // Exception: allow dynamic import in anthropic-plugin for APIPromise patching + // Exception: allow dynamic import in anthropic-instrumentation for APIPromise patching if ( /\bimport\s*\(/.test(line) && !/^import\s+/.test(line.trim()) && - !relativePath.includes("instrumentation/plugins/anthropic-plugin.ts") + !relativePath.includes( + "instrumentation/providers/anthropic-instrumentation.ts", + ) ) { violations.push( `${relativePath}:${index + 1} - Found dynamic import() statement: "${line.trim()}"`, diff --git a/js/src/instrumentation/README.md b/js/src/instrumentation/README.md index a8be26ee3..9416d44f5 100644 --- a/js/src/instrumentation/README.md +++ b/js/src/instrumentation/README.md @@ -1,4 +1,4 @@ -# Writing Braintrust Instrumentation Plugins +# Braintrust Instrumentation Consumers API wrapping and span instrumentation are separate layers. Invocation hooks work independently of the SDK; provider plugins explicitly connect them to tracing functions. @@ -174,7 +174,7 @@ shape above; OTel hooks receive an isolated plain snapshot, not a live `spanContext` is an object, not a method. Times are OTel `[seconds, nanoseconds]` arrays; trace state is a string. - `resource: { attributes, schemaUrl? }`, `instrumentationScope: -{ name, version?, schemaUrl?, attributes? }`, and dropped attribute/event/link + { name, version?, schemaUrl?, attributes? }`, and dropped attribute/event/link counts. OTel 1.x's instrumentation library is exposed as `instrumentationScope`. - Events contain `name`, `time`, optional attributes and dropped count; links contain a plain `context` (the same shape as `spanContext`), optional attributes diff --git a/js/src/instrumentation/auto-instrumentation-suppression.test.ts b/js/src/instrumentation/auto-instrumentation-suppression.test.ts index f3439ac2e..0dd9566bb 100644 --- a/js/src/instrumentation/auto-instrumentation-suppression.test.ts +++ b/js/src/instrumentation/auto-instrumentation-suppression.test.ts @@ -1,4 +1,4 @@ -import { beforeAll, describe, expect, it } from "vitest"; +import { beforeAll, describe, expect, it } from "vite-plus/test"; import { configureNode } from "../node/config"; import { isAutoInstrumentationSuppressed, diff --git a/js/src/instrumentation/auto-instrumentation-suppression.ts b/js/src/instrumentation/auto-instrumentation-suppression.ts index 8bb6b4fa7..c93af54aa 100644 --- a/js/src/instrumentation/auto-instrumentation-suppression.ts +++ b/js/src/instrumentation/auto-instrumentation-suppression.ts @@ -1,4 +1,4 @@ -import iso, { type IsoAsyncLocalStorage } from "../isomorph"; +import iso, { type IsoAsyncLocalStorage } from "#braintrust-platform"; let autoInstrumentationSuppressionStore: | IsoAsyncLocalStorage diff --git a/js/src/instrumentation/braintrust-plugin.test.ts b/js/src/instrumentation/braintrust-plugin.test.ts deleted file mode 100644 index c979690ec..000000000 --- a/js/src/instrumentation/braintrust-plugin.test.ts +++ /dev/null @@ -1,1887 +0,0 @@ -import { describe, it, expect, vi, beforeEach } from "vitest"; -import { BraintrustPlugin } from "./braintrust-plugin"; -import { OpenAIPlugin } from "./plugins/openai-plugin"; -import { OpenAICodexPlugin } from "./plugins/openai-codex-plugin"; -import { AnthropicPlugin } from "./plugins/anthropic-plugin"; -import { AISDKPlugin } from "./plugins/ai-sdk-plugin"; -import { ClaudeAgentSDKPlugin } from "./plugins/claude-agent-sdk-plugin"; -import { CloudflareThinkPlugin } from "./plugins/cloudflare-think-plugin"; -import { OpenAIAgentsPlugin } from "./plugins/openai-agents-plugin"; -import { GoogleGenerativeAIPlugin } from "./plugins/google-generative-ai-plugin"; -import { GoogleGenAIPlugin } from "./plugins/google-genai-plugin"; -import { HuggingFacePlugin } from "./plugins/huggingface-plugin"; -import { HuggingFaceTransformersPlugin } from "./plugins/huggingface-transformers-plugin"; -import { OpenRouterAgentPlugin } from "./plugins/openrouter-agent-plugin"; -import { OpenRouterPlugin } from "./plugins/openrouter-plugin"; -import { MistralPlugin } from "./plugins/mistral-plugin"; -import { OllamaPlugin } from "./plugins/ollama-plugin"; -import { CoherePlugin } from "./plugins/cohere-plugin"; -import { GroqPlugin } from "./plugins/groq-plugin"; -import { GitHubCopilotPlugin } from "./plugins/github-copilot-plugin"; -import { LangChainPlugin } from "./plugins/langchain-plugin"; -import { LangSmithPlugin } from "./plugins/langsmith-plugin"; -import { PiCodingAgentPlugin } from "./plugins/pi-coding-agent-plugin"; -import { StrandsAgentSDKPlugin } from "./plugins/strands-agent-sdk-plugin"; -import { VoyageAIPlugin } from "./plugins/voyageai-plugin"; -import { MCPPlugin } from "./plugins/mcp-plugin"; -import { TypeSafePlugin } from "./plugins/typesafe-plugin"; -import { CloudflareAIChatPlugin } from "./plugins/cloudflare-ai-chat-plugin"; -import { CloudflareAgentsPlugin } from "./plugins/cloudflare-agents-plugin"; - -function createPluginClassMock() { - return vi.fn(function MockPlugin(this: { - enable: ReturnType; - disable: ReturnType; - }) { - this.enable = vi.fn(); - this.disable = vi.fn(); - }); -} - -// Preserve the re-exported utility functions (parseMetricsFromUsage, etc.) -// while mocking out the OpenAIPlugin class — those utilities are also -// imported and tested at the bottom of this file via braintrust-plugin's -// re-exports. -vi.mock("./plugins/openai-plugin", async () => { - const actual = await vi.importActual< - typeof import("./plugins/openai-plugin") - >("./plugins/openai-plugin"); - return { - ...actual, - OpenAIPlugin: createPluginClassMock(), - }; -}); - -vi.mock("./plugins/anthropic-plugin", () => ({ - AnthropicPlugin: createPluginClassMock(), -})); - -vi.mock("./plugins/openai-codex-plugin", () => ({ - OpenAICodexPlugin: createPluginClassMock(), -})); - -vi.mock("./plugins/ai-sdk-plugin", () => ({ - AISDKPlugin: createPluginClassMock(), -})); - -vi.mock("./plugins/claude-agent-sdk-plugin", () => ({ - ClaudeAgentSDKPlugin: createPluginClassMock(), -})); - -vi.mock("./plugins/cloudflare-think-plugin", () => ({ - CloudflareThinkPlugin: createPluginClassMock(), -})); - -vi.mock("./plugins/openai-agents-plugin", () => ({ - OpenAIAgentsPlugin: createPluginClassMock(), -})); - -vi.mock("./plugins/google-generative-ai-plugin", () => ({ - GoogleGenerativeAIPlugin: createPluginClassMock(), -})); - -vi.mock("./plugins/google-genai-plugin", () => ({ - GoogleGenAIPlugin: createPluginClassMock(), -})); - -vi.mock("./plugins/huggingface-plugin", () => ({ - HuggingFacePlugin: createPluginClassMock(), -})); - -vi.mock("./plugins/huggingface-transformers-plugin", () => ({ - HuggingFaceTransformersPlugin: createPluginClassMock(), -})); - -vi.mock("./plugins/openrouter-plugin", () => ({ - OpenRouterPlugin: createPluginClassMock(), -})); - -vi.mock("./plugins/openrouter-agent-plugin", () => ({ - OpenRouterAgentPlugin: createPluginClassMock(), -})); - -vi.mock("./plugins/mistral-plugin", () => ({ - MistralPlugin: createPluginClassMock(), -})); - -vi.mock("./plugins/ollama-plugin", () => ({ - OllamaPlugin: createPluginClassMock(), -})); - -vi.mock("./plugins/cohere-plugin", () => ({ - CoherePlugin: createPluginClassMock(), -})); - -vi.mock("./plugins/groq-plugin", () => ({ - GroqPlugin: createPluginClassMock(), -})); - -vi.mock("./plugins/github-copilot-plugin", () => ({ - GitHubCopilotPlugin: createPluginClassMock(), -})); - -vi.mock("./plugins/langchain-plugin", () => ({ - LangChainPlugin: createPluginClassMock(), -})); - -vi.mock("./plugins/langsmith-plugin", () => ({ - LangSmithPlugin: createPluginClassMock(), -})); - -vi.mock("./plugins/pi-coding-agent-plugin", () => ({ - PiCodingAgentPlugin: createPluginClassMock(), -})); - -vi.mock("./plugins/strands-agent-sdk-plugin", () => ({ - StrandsAgentSDKPlugin: createPluginClassMock(), -})); - -vi.mock("./plugins/voyageai-plugin", () => ({ - VoyageAIPlugin: createPluginClassMock(), -})); - -vi.mock("./plugins/mcp-plugin", () => ({ - MCPPlugin: createPluginClassMock(), -})); - -vi.mock("./plugins/typesafe-plugin", () => ({ - TypeSafePlugin: createPluginClassMock(), -})); - -vi.mock("./plugins/cloudflare-ai-chat-plugin", () => ({ - CloudflareAIChatPlugin: createPluginClassMock(), -})); - -vi.mock("./plugins/cloudflare-agents-plugin", () => ({ - CloudflareAgentsPlugin: createPluginClassMock(), -})); - -describe("BraintrustPlugin", () => { - beforeEach(() => { - vi.clearAllMocks(); - }); - - describe("sub-plugin creation (default behavior)", () => { - it("should create and enable OpenAI plugin by default", () => { - const plugin = new BraintrustPlugin(); - plugin.enable(); - - expect(OpenAIPlugin).toHaveBeenCalledTimes(1); - const mockInstance = vi.mocked(OpenAIPlugin).mock.results[0].value; - expect(mockInstance.enable).toHaveBeenCalledTimes(1); - }); - - it("should create and enable Anthropic plugin by default", () => { - const plugin = new BraintrustPlugin(); - plugin.enable(); - - expect(AnthropicPlugin).toHaveBeenCalledTimes(1); - const mockInstance = vi.mocked(AnthropicPlugin).mock.results[0].value; - expect(mockInstance.enable).toHaveBeenCalledTimes(1); - }); - - it("should create and enable OpenAI Codex plugin by default", () => { - const plugin = new BraintrustPlugin(); - plugin.enable(); - - expect(OpenAICodexPlugin).toHaveBeenCalledTimes(1); - const mockInstance = vi.mocked(OpenAICodexPlugin).mock.results[0].value; - expect(mockInstance.enable).toHaveBeenCalledTimes(1); - }); - - it("should create and enable AI SDK plugin by default", () => { - const plugin = new BraintrustPlugin(); - plugin.enable(); - - expect(AISDKPlugin).toHaveBeenCalledTimes(1); - const mockInstance = vi.mocked(AISDKPlugin).mock.results[0].value; - expect(mockInstance.enable).toHaveBeenCalledTimes(1); - }); - - it("should create and enable Claude Agent SDK plugin by default", () => { - const plugin = new BraintrustPlugin(); - plugin.enable(); - - expect(ClaudeAgentSDKPlugin).toHaveBeenCalledTimes(1); - const mockInstance = - vi.mocked(ClaudeAgentSDKPlugin).mock.results[0].value; - expect(mockInstance.enable).toHaveBeenCalledTimes(1); - }); - - it("should create and enable Cloudflare Think plugin by default", () => { - const plugin = new BraintrustPlugin(); - plugin.enable(); - - expect(CloudflareThinkPlugin).toHaveBeenCalledTimes(1); - const mockInstance = vi.mocked(CloudflareThinkPlugin).mock.results[0] - .value; - expect(mockInstance.enable).toHaveBeenCalledTimes(1); - }); - - it("should create and enable OpenAI Agents plugin by default", () => { - const plugin = new BraintrustPlugin(); - plugin.enable(); - - expect(OpenAIAgentsPlugin).toHaveBeenCalledTimes(1); - const mockInstance = vi.mocked(OpenAIAgentsPlugin).mock.results[0].value; - expect(mockInstance.enable).toHaveBeenCalledTimes(1); - }); - - it("should create and enable Google GenAI plugin by default", () => { - const plugin = new BraintrustPlugin(); - plugin.enable(); - - expect(GoogleGenAIPlugin).toHaveBeenCalledTimes(1); - const mockInstance = vi.mocked(GoogleGenAIPlugin).mock.results[0].value; - expect(mockInstance.enable).toHaveBeenCalledTimes(1); - }); - - it("should create and enable HuggingFace plugin by default", () => { - const plugin = new BraintrustPlugin(); - plugin.enable(); - - expect(HuggingFacePlugin).toHaveBeenCalledTimes(1); - const mockInstance = vi.mocked(HuggingFacePlugin).mock.results[0].value; - expect(mockInstance.enable).toHaveBeenCalledTimes(1); - expect(HuggingFaceTransformersPlugin).toHaveBeenCalledTimes(1); - const transformersMockInstance = vi.mocked(HuggingFaceTransformersPlugin) - .mock.results[0].value; - expect(transformersMockInstance.enable).toHaveBeenCalledTimes(1); - }); - - it("should create and enable OpenRouter plugin by default", () => { - const plugin = new BraintrustPlugin(); - plugin.enable(); - - expect(OpenRouterPlugin).toHaveBeenCalledTimes(1); - expect(OpenRouterAgentPlugin).toHaveBeenCalledTimes(1); - const mockInstance = vi.mocked(OpenRouterPlugin).mock.results[0].value; - expect(mockInstance.enable).toHaveBeenCalledTimes(1); - }); - - it("should create and enable OpenRouter Agent plugin by default", () => { - const plugin = new BraintrustPlugin(); - plugin.enable(); - - expect(OpenRouterAgentPlugin).toHaveBeenCalledTimes(1); - const mockInstance = vi.mocked(OpenRouterAgentPlugin).mock.results[0] - .value; - expect(mockInstance.enable).toHaveBeenCalledTimes(1); - }); - - it("should create and enable Mistral plugin by default", () => { - const plugin = new BraintrustPlugin(); - plugin.enable(); - - expect(MistralPlugin).toHaveBeenCalledTimes(1); - const mockInstance = vi.mocked(MistralPlugin).mock.results[0].value; - expect(mockInstance.enable).toHaveBeenCalledTimes(1); - }); - - it("should create and enable Cohere plugin by default", () => { - const plugin = new BraintrustPlugin(); - plugin.enable(); - - expect(CoherePlugin).toHaveBeenCalledTimes(1); - const mockInstance = vi.mocked(CoherePlugin).mock.results[0].value; - expect(mockInstance.enable).toHaveBeenCalledTimes(1); - }); - - it("should create and enable Voyage AI plugin by default", () => { - const plugin = new BraintrustPlugin(); - plugin.enable(); - - expect(VoyageAIPlugin).toHaveBeenCalledTimes(1); - const mockInstance = vi.mocked(VoyageAIPlugin).mock.results[0].value; - expect(mockInstance.enable).toHaveBeenCalledTimes(1); - }); - - it("should create and enable MCP plugin by default", () => { - const plugin = new BraintrustPlugin(); - plugin.enable(); - - expect(MCPPlugin).toHaveBeenCalledTimes(1); - const mockInstance = vi.mocked(MCPPlugin).mock.results[0].value; - expect(mockInstance.enable).toHaveBeenCalledTimes(1); - }); - - it("should create and enable TypeSafe plugin by default", () => { - const plugin = new BraintrustPlugin(); - plugin.enable(); - - expect(TypeSafePlugin).toHaveBeenCalledTimes(1); - const mockInstance = vi.mocked(TypeSafePlugin).mock.results[0].value; - expect(mockInstance.enable).toHaveBeenCalledTimes(1); - }); - - it("should create and enable Groq plugin by default", () => { - const plugin = new BraintrustPlugin(); - plugin.enable(); - - expect(GroqPlugin).toHaveBeenCalledTimes(1); - const mockInstance = vi.mocked(GroqPlugin).mock.results[0].value; - expect(mockInstance.enable).toHaveBeenCalledTimes(1); - }); - - it("should create and enable GitHubCopilot plugin by default", () => { - const plugin = new BraintrustPlugin(); - plugin.enable(); - - expect(GitHubCopilotPlugin).toHaveBeenCalledTimes(1); - const mockInstance = vi.mocked(GitHubCopilotPlugin).mock.results[0].value; - expect(mockInstance.enable).toHaveBeenCalledTimes(1); - }); - - it("should create and enable LangChain plugin by default", () => { - const plugin = new BraintrustPlugin(); - plugin.enable(); - - expect(LangChainPlugin).toHaveBeenCalledTimes(1); - const mockInstance = vi.mocked(LangChainPlugin).mock.results[0].value; - expect(mockInstance.enable).toHaveBeenCalledTimes(1); - }); - - it("should create and enable Strands Agent SDK plugin by default", () => { - const plugin = new BraintrustPlugin(); - plugin.enable(); - - expect(StrandsAgentSDKPlugin).toHaveBeenCalledTimes(1); - const mockInstance = vi.mocked(StrandsAgentSDKPlugin).mock.results[0] - .value; - expect(mockInstance.enable).toHaveBeenCalledTimes(1); - }); - - it("should create and enable Cloudflare AI Chat plugin by default", () => { - const plugin = new BraintrustPlugin(); - plugin.enable(); - - expect(CloudflareAIChatPlugin).toHaveBeenCalledTimes(1); - const mockInstance = vi.mocked(CloudflareAIChatPlugin).mock.results[0] - .value; - expect(mockInstance.enable).toHaveBeenCalledTimes(1); - }); - - it("should create and enable Cloudflare Agents plugin by default", () => { - const plugin = new BraintrustPlugin(); - plugin.enable(); - - expect(CloudflareAgentsPlugin).toHaveBeenCalledTimes(1); - const mockInstance = vi.mocked(CloudflareAgentsPlugin).mock.results[0] - .value; - expect(mockInstance.enable).toHaveBeenCalledTimes(1); - }); - - it("should create all plugins when enabled with no config", () => { - const plugin = new BraintrustPlugin(); - plugin.enable(); - - expect(OpenAIPlugin).toHaveBeenCalledTimes(1); - expect(OpenAICodexPlugin).toHaveBeenCalledTimes(1); - expect(AnthropicPlugin).toHaveBeenCalledTimes(1); - expect(AISDKPlugin).toHaveBeenCalledTimes(1); - expect(ClaudeAgentSDKPlugin).toHaveBeenCalledTimes(1); - expect(OpenAIAgentsPlugin).toHaveBeenCalledTimes(1); - expect(GoogleGenAIPlugin).toHaveBeenCalledTimes(1); - expect(HuggingFacePlugin).toHaveBeenCalledTimes(1); - expect(OpenRouterPlugin).toHaveBeenCalledTimes(1); - expect(OpenRouterAgentPlugin).toHaveBeenCalledTimes(1); - expect(MistralPlugin).toHaveBeenCalledTimes(1); - expect(CoherePlugin).toHaveBeenCalledTimes(1); - expect(GroqPlugin).toHaveBeenCalledTimes(1); - expect(GitHubCopilotPlugin).toHaveBeenCalledTimes(1); - expect(StrandsAgentSDKPlugin).toHaveBeenCalledTimes(1); - expect(LangChainPlugin).toHaveBeenCalledTimes(1); - }); - - it("should create all plugins when enabled with empty config", () => { - const plugin = new BraintrustPlugin({}); - plugin.enable(); - - expect(OpenAIPlugin).toHaveBeenCalledTimes(1); - expect(OpenAICodexPlugin).toHaveBeenCalledTimes(1); - expect(AnthropicPlugin).toHaveBeenCalledTimes(1); - expect(AISDKPlugin).toHaveBeenCalledTimes(1); - expect(ClaudeAgentSDKPlugin).toHaveBeenCalledTimes(1); - expect(OpenAIAgentsPlugin).toHaveBeenCalledTimes(1); - expect(GoogleGenAIPlugin).toHaveBeenCalledTimes(1); - expect(HuggingFacePlugin).toHaveBeenCalledTimes(1); - expect(OpenRouterPlugin).toHaveBeenCalledTimes(1); - expect(OpenRouterAgentPlugin).toHaveBeenCalledTimes(1); - expect(MistralPlugin).toHaveBeenCalledTimes(1); - expect(CoherePlugin).toHaveBeenCalledTimes(1); - expect(GroqPlugin).toHaveBeenCalledTimes(1); - expect(GitHubCopilotPlugin).toHaveBeenCalledTimes(1); - expect(StrandsAgentSDKPlugin).toHaveBeenCalledTimes(1); - expect(LangChainPlugin).toHaveBeenCalledTimes(1); - }); - - it("should create all plugins when enabled with empty integrations config", () => { - const plugin = new BraintrustPlugin({ integrations: {} }); - plugin.enable(); - - expect(OpenAIPlugin).toHaveBeenCalledTimes(1); - expect(OpenAICodexPlugin).toHaveBeenCalledTimes(1); - expect(AnthropicPlugin).toHaveBeenCalledTimes(1); - expect(AISDKPlugin).toHaveBeenCalledTimes(1); - expect(ClaudeAgentSDKPlugin).toHaveBeenCalledTimes(1); - expect(OpenAIAgentsPlugin).toHaveBeenCalledTimes(1); - expect(GoogleGenAIPlugin).toHaveBeenCalledTimes(1); - expect(HuggingFacePlugin).toHaveBeenCalledTimes(1); - expect(OpenRouterPlugin).toHaveBeenCalledTimes(1); - expect(OpenRouterAgentPlugin).toHaveBeenCalledTimes(1); - expect(MistralPlugin).toHaveBeenCalledTimes(1); - expect(CoherePlugin).toHaveBeenCalledTimes(1); - expect(GroqPlugin).toHaveBeenCalledTimes(1); - expect(StrandsAgentSDKPlugin).toHaveBeenCalledTimes(1); - expect(LangChainPlugin).toHaveBeenCalledTimes(1); - }); - }); - - describe("config-based disabling", () => { - it("should not create OpenAI plugin when openai: false", () => { - const plugin = new BraintrustPlugin({ - integrations: { openai: false }, - }); - plugin.enable(); - - expect(OpenAIPlugin).not.toHaveBeenCalled(); - // Other plugins should still be created - expect(AnthropicPlugin).toHaveBeenCalledTimes(1); - expect(AISDKPlugin).toHaveBeenCalledTimes(1); - expect(ClaudeAgentSDKPlugin).toHaveBeenCalledTimes(1); - expect(OpenAIAgentsPlugin).toHaveBeenCalledTimes(1); - expect(GoogleGenAIPlugin).toHaveBeenCalledTimes(1); - expect(HuggingFacePlugin).toHaveBeenCalledTimes(1); - expect(OpenRouterPlugin).toHaveBeenCalledTimes(1); - expect(OpenRouterAgentPlugin).toHaveBeenCalledTimes(1); - }); - - it("should not create Anthropic plugin when anthropic: false", () => { - const plugin = new BraintrustPlugin({ - integrations: { anthropic: false }, - }); - plugin.enable(); - - expect(AnthropicPlugin).not.toHaveBeenCalled(); - // Other plugins should still be created - expect(OpenAIPlugin).toHaveBeenCalledTimes(1); - expect(OpenAICodexPlugin).toHaveBeenCalledTimes(1); - expect(AISDKPlugin).toHaveBeenCalledTimes(1); - expect(ClaudeAgentSDKPlugin).toHaveBeenCalledTimes(1); - expect(OpenAIAgentsPlugin).toHaveBeenCalledTimes(1); - expect(GoogleGenAIPlugin).toHaveBeenCalledTimes(1); - expect(HuggingFacePlugin).toHaveBeenCalledTimes(1); - expect(OpenRouterPlugin).toHaveBeenCalledTimes(1); - expect(OpenRouterAgentPlugin).toHaveBeenCalledTimes(1); - }); - - it("should not create OpenAI Codex plugin when openaiCodexSDK: false", () => { - const plugin = new BraintrustPlugin({ - integrations: { openaiCodexSDK: false }, - }); - plugin.enable(); - - expect(OpenAICodexPlugin).not.toHaveBeenCalled(); - expect(OpenAIPlugin).toHaveBeenCalledTimes(1); - expect(AnthropicPlugin).toHaveBeenCalledTimes(1); - expect(AISDKPlugin).toHaveBeenCalledTimes(1); - }); - - it("should not create AI SDK plugin when aisdk: false", () => { - const plugin = new BraintrustPlugin({ - integrations: { aisdk: false }, - }); - plugin.enable(); - - expect(AISDKPlugin).not.toHaveBeenCalled(); - // Other plugins should still be created - expect(OpenAIPlugin).toHaveBeenCalledTimes(1); - expect(AnthropicPlugin).toHaveBeenCalledTimes(1); - expect(ClaudeAgentSDKPlugin).toHaveBeenCalledTimes(1); - expect(GoogleGenAIPlugin).toHaveBeenCalledTimes(1); - expect(HuggingFacePlugin).toHaveBeenCalledTimes(1); - expect(OpenRouterPlugin).toHaveBeenCalledTimes(1); - expect(OpenRouterAgentPlugin).toHaveBeenCalledTimes(1); - expect(MistralPlugin).toHaveBeenCalledTimes(1); - }); - - it("should not create Claude Agent SDK plugin when claudeAgentSDK: false", () => { - const plugin = new BraintrustPlugin({ - integrations: { claudeAgentSDK: false }, - }); - plugin.enable(); - - expect(ClaudeAgentSDKPlugin).not.toHaveBeenCalled(); - // Other plugins should still be created - expect(OpenAIPlugin).toHaveBeenCalledTimes(1); - expect(AnthropicPlugin).toHaveBeenCalledTimes(1); - expect(AISDKPlugin).toHaveBeenCalledTimes(1); - expect(GoogleGenAIPlugin).toHaveBeenCalledTimes(1); - expect(OpenAIAgentsPlugin).toHaveBeenCalledTimes(1); - expect(HuggingFacePlugin).toHaveBeenCalledTimes(1); - expect(OpenRouterPlugin).toHaveBeenCalledTimes(1); - expect(OpenRouterAgentPlugin).toHaveBeenCalledTimes(1); - expect(MistralPlugin).toHaveBeenCalledTimes(1); - }); - - it("should not create Cloudflare Think plugin when cloudflareThink: false", () => { - const plugin = new BraintrustPlugin({ - integrations: { cloudflareThink: false }, - }); - plugin.enable(); - - expect(CloudflareThinkPlugin).not.toHaveBeenCalled(); - expect(AISDKPlugin).toHaveBeenCalledTimes(1); - }); - - it("should not create OpenAI Agents plugin when openAIAgents: false", () => { - const plugin = new BraintrustPlugin({ - integrations: { openAIAgents: false }, - }); - plugin.enable(); - - expect(OpenAIAgentsPlugin).not.toHaveBeenCalled(); - expect(OpenAIPlugin).toHaveBeenCalledTimes(1); - expect(AnthropicPlugin).toHaveBeenCalledTimes(1); - expect(AISDKPlugin).toHaveBeenCalledTimes(1); - expect(ClaudeAgentSDKPlugin).toHaveBeenCalledTimes(1); - expect(GoogleGenAIPlugin).toHaveBeenCalledTimes(1); - expect(HuggingFacePlugin).toHaveBeenCalledTimes(1); - expect(OpenRouterPlugin).toHaveBeenCalledTimes(1); - expect(OpenRouterAgentPlugin).toHaveBeenCalledTimes(1); - expect(MistralPlugin).toHaveBeenCalledTimes(1); - }); - - it("should not create HuggingFace plugin when huggingface: false", () => { - const plugin = new BraintrustPlugin({ - integrations: { huggingface: false }, - }); - plugin.enable(); - - expect(HuggingFacePlugin).not.toHaveBeenCalled(); - expect(HuggingFaceTransformersPlugin).not.toHaveBeenCalled(); - expect(OpenAIPlugin).toHaveBeenCalledTimes(1); - expect(AnthropicPlugin).toHaveBeenCalledTimes(1); - expect(AISDKPlugin).toHaveBeenCalledTimes(1); - expect(ClaudeAgentSDKPlugin).toHaveBeenCalledTimes(1); - expect(GoogleGenAIPlugin).toHaveBeenCalledTimes(1); - expect(OpenRouterPlugin).toHaveBeenCalledTimes(1); - expect(OpenRouterAgentPlugin).toHaveBeenCalledTimes(1); - expect(MistralPlugin).toHaveBeenCalledTimes(1); - }); - - it("enables and disables legacy Google instrumentation idempotently", () => { - const plugin = new BraintrustPlugin(); - plugin.enable(); - plugin.enable(); - expect(GoogleGenerativeAIPlugin).toHaveBeenCalledOnce(); - const instance = vi.mocked(GoogleGenerativeAIPlugin).mock.results[0] - .value; - expect(instance.enable).toHaveBeenCalledOnce(); - plugin.disable(); - plugin.disable(); - expect(instance.disable).toHaveBeenCalledOnce(); - }); - - it("can disable legacy Google instrumentation independently", () => { - const plugin = new BraintrustPlugin({ - integrations: { googleGenerativeAI: false }, - }); - plugin.enable(); - expect(GoogleGenerativeAIPlugin).not.toHaveBeenCalled(); - expect(GoogleGenAIPlugin).toHaveBeenCalledOnce(); - plugin.disable(); - }); - - it("should not create Google GenAI plugin when googleGenAI: false", () => { - const plugin = new BraintrustPlugin({ - integrations: { googleGenAI: false }, - }); - plugin.enable(); - - expect(GoogleGenAIPlugin).not.toHaveBeenCalled(); - // Other plugins should still be created - expect(OpenAIPlugin).toHaveBeenCalledTimes(1); - expect(AnthropicPlugin).toHaveBeenCalledTimes(1); - expect(AISDKPlugin).toHaveBeenCalledTimes(1); - expect(ClaudeAgentSDKPlugin).toHaveBeenCalledTimes(1); - expect(OpenRouterPlugin).toHaveBeenCalledTimes(1); - expect(OpenRouterAgentPlugin).toHaveBeenCalledTimes(1); - expect(MistralPlugin).toHaveBeenCalledTimes(1); - }); - - it("should not create OpenRouter plugin when openrouter: false", () => { - const plugin = new BraintrustPlugin({ - integrations: { openrouter: false }, - }); - plugin.enable(); - - expect(OpenRouterPlugin).not.toHaveBeenCalled(); - expect(OpenRouterAgentPlugin).toHaveBeenCalledTimes(1); - expect(OpenAIPlugin).toHaveBeenCalledTimes(1); - expect(AnthropicPlugin).toHaveBeenCalledTimes(1); - expect(AISDKPlugin).toHaveBeenCalledTimes(1); - expect(ClaudeAgentSDKPlugin).toHaveBeenCalledTimes(1); - expect(GoogleGenAIPlugin).toHaveBeenCalledTimes(1); - expect(HuggingFacePlugin).toHaveBeenCalledTimes(1); - expect(MistralPlugin).toHaveBeenCalledTimes(1); - }); - - it("should not create Mistral plugin when mistral: false", () => { - const plugin = new BraintrustPlugin({ - integrations: { mistral: false }, - }); - plugin.enable(); - - expect(MistralPlugin).not.toHaveBeenCalled(); - expect(OpenAIPlugin).toHaveBeenCalledTimes(1); - expect(AnthropicPlugin).toHaveBeenCalledTimes(1); - expect(AISDKPlugin).toHaveBeenCalledTimes(1); - expect(ClaudeAgentSDKPlugin).toHaveBeenCalledTimes(1); - expect(GoogleGenAIPlugin).toHaveBeenCalledTimes(1); - expect(HuggingFacePlugin).toHaveBeenCalledTimes(1); - expect(OpenRouterPlugin).toHaveBeenCalledTimes(1); - }); - - it("should not create Ollama plugin when ollama: false", () => { - const plugin = new BraintrustPlugin({ - integrations: { ollama: false }, - }); - plugin.enable(); - - expect(OllamaPlugin).not.toHaveBeenCalled(); - expect(OpenAIPlugin).toHaveBeenCalledTimes(1); - expect(AnthropicPlugin).toHaveBeenCalledTimes(1); - expect(MistralPlugin).toHaveBeenCalledTimes(1); - }); - - it("should not create Cohere plugin when cohere: false", () => { - const plugin = new BraintrustPlugin({ - integrations: { cohere: false }, - }); - plugin.enable(); - - expect(CoherePlugin).not.toHaveBeenCalled(); - expect(OpenAIPlugin).toHaveBeenCalledTimes(1); - expect(AnthropicPlugin).toHaveBeenCalledTimes(1); - expect(AISDKPlugin).toHaveBeenCalledTimes(1); - expect(ClaudeAgentSDKPlugin).toHaveBeenCalledTimes(1); - expect(GoogleGenAIPlugin).toHaveBeenCalledTimes(1); - expect(OpenRouterPlugin).toHaveBeenCalledTimes(1); - expect(MistralPlugin).toHaveBeenCalledTimes(1); - }); - - it("should not create Groq plugin when groq: false", () => { - const plugin = new BraintrustPlugin({ - integrations: { groq: false }, - }); - plugin.enable(); - - expect(GroqPlugin).not.toHaveBeenCalled(); - expect(OpenAIPlugin).toHaveBeenCalledTimes(1); - expect(AnthropicPlugin).toHaveBeenCalledTimes(1); - expect(AISDKPlugin).toHaveBeenCalledTimes(1); - expect(ClaudeAgentSDKPlugin).toHaveBeenCalledTimes(1); - expect(GoogleGenAIPlugin).toHaveBeenCalledTimes(1); - expect(OpenRouterPlugin).toHaveBeenCalledTimes(1); - expect(MistralPlugin).toHaveBeenCalledTimes(1); - }); - - it("should not create GitHubCopilot plugin when gitHubCopilot: false", () => { - const plugin = new BraintrustPlugin({ - integrations: { gitHubCopilot: false }, - }); - plugin.enable(); - - expect(GitHubCopilotPlugin).not.toHaveBeenCalled(); - expect(OpenAIPlugin).toHaveBeenCalledTimes(1); - expect(GroqPlugin).toHaveBeenCalledTimes(1); - }); - - it("should not create LangChain plugin when langchain: false", () => { - const plugin = new BraintrustPlugin({ - integrations: { langchain: false }, - }); - plugin.enable(); - - expect(LangChainPlugin).not.toHaveBeenCalled(); - expect(OpenAIPlugin).toHaveBeenCalledTimes(1); - expect(AnthropicPlugin).toHaveBeenCalledTimes(1); - }); - - it("should create LangSmith with LangChain deduplication by default", () => { - const plugin = new BraintrustPlugin(); - plugin.enable(); - - expect(LangSmithPlugin).toHaveBeenCalledWith({ - skipLangChainRuns: true, - }); - const mockInstance = vi.mocked(LangSmithPlugin).mock.results[0].value; - expect(mockInstance.enable).toHaveBeenCalledTimes(1); - }); - - it("should disable LangSmith or let it capture LangChain runs", () => { - const disabled = new BraintrustPlugin({ - integrations: { langsmith: false }, - }); - disabled.enable(); - expect(LangSmithPlugin).not.toHaveBeenCalled(); - - const withoutLangChain = new BraintrustPlugin({ - integrations: { langchain: false }, - }); - withoutLangChain.enable(); - expect(LangSmithPlugin).toHaveBeenCalledWith({ - skipLangChainRuns: false, - }); - }); - - it("should not create OpenRouter Agent plugin when openrouterAgent: false", () => { - const plugin = new BraintrustPlugin({ - integrations: { openrouterAgent: false }, - }); - plugin.enable(); - - expect(OpenRouterPlugin).toHaveBeenCalledTimes(1); - expect(OpenRouterAgentPlugin).not.toHaveBeenCalled(); - }); - - it("should not create any plugins when all are disabled", () => { - const plugin = new BraintrustPlugin({ - integrations: { - openai: false, - openaiCodexSDK: false, - anthropic: false, - aisdk: false, - claudeAgentSDK: false, - cloudflareThink: false, - openAIAgents: false, - googleGenAI: false, - googleGenerativeAI: false, - huggingface: false, - openrouter: false, - openrouterAgent: false, - mistral: false, - ollama: false, - cohere: false, - groq: false, - gitHubCopilot: false, - langchain: false, - langsmith: false, - piCodingAgent: false, - strandsAgentSDK: false, - cloudflareAIChat: false, - cloudflareAgents: false, - }, - }); - plugin.enable(); - - expect(OpenAIPlugin).not.toHaveBeenCalled(); - expect(OpenAICodexPlugin).not.toHaveBeenCalled(); - expect(AnthropicPlugin).not.toHaveBeenCalled(); - expect(AISDKPlugin).not.toHaveBeenCalled(); - expect(ClaudeAgentSDKPlugin).not.toHaveBeenCalled(); - expect(CloudflareThinkPlugin).not.toHaveBeenCalled(); - expect(OpenAIAgentsPlugin).not.toHaveBeenCalled(); - expect(GoogleGenAIPlugin).not.toHaveBeenCalled(); - expect(HuggingFacePlugin).not.toHaveBeenCalled(); - expect(OpenRouterPlugin).not.toHaveBeenCalled(); - expect(OpenRouterAgentPlugin).not.toHaveBeenCalled(); - expect(MistralPlugin).not.toHaveBeenCalled(); - expect(OllamaPlugin).not.toHaveBeenCalled(); - expect(CoherePlugin).not.toHaveBeenCalled(); - expect(GroqPlugin).not.toHaveBeenCalled(); - expect(GitHubCopilotPlugin).not.toHaveBeenCalled(); - expect(LangChainPlugin).not.toHaveBeenCalled(); - expect(LangSmithPlugin).not.toHaveBeenCalled(); - expect(PiCodingAgentPlugin).not.toHaveBeenCalled(); - expect(StrandsAgentSDKPlugin).not.toHaveBeenCalled(); - expect(CloudflareAIChatPlugin).not.toHaveBeenCalled(); - expect(CloudflareAgentsPlugin).not.toHaveBeenCalled(); - }); - - it("should not create Pi Coding Agent plugin when piCodingAgent: false", () => { - const plugin = new BraintrustPlugin({ - integrations: { piCodingAgent: false }, - }); - plugin.enable(); - - expect(PiCodingAgentPlugin).not.toHaveBeenCalled(); - expect(OpenAIPlugin).toHaveBeenCalledTimes(1); - expect(AnthropicPlugin).toHaveBeenCalledTimes(1); - }); - - it("should not create Strands Agent SDK plugin when strandsAgentSDK: false", () => { - const plugin = new BraintrustPlugin({ - integrations: { strandsAgentSDK: false }, - }); - plugin.enable(); - - expect(StrandsAgentSDKPlugin).not.toHaveBeenCalled(); - expect(OpenAIPlugin).toHaveBeenCalledTimes(1); - }); - - it("should not create Cloudflare AI Chat plugin when cloudflareAIChat: false", () => { - const plugin = new BraintrustPlugin({ - integrations: { cloudflareAIChat: false }, - }); - plugin.enable(); - - expect(CloudflareAIChatPlugin).not.toHaveBeenCalled(); - expect(OpenAIPlugin).toHaveBeenCalledTimes(1); - }); - - it("should not create Cloudflare Agents plugin when cloudflareAgents: false", () => { - const plugin = new BraintrustPlugin({ - integrations: { cloudflareAgents: false }, - }); - plugin.enable(); - - expect(CloudflareAgentsPlugin).not.toHaveBeenCalled(); - expect(OpenAIPlugin).toHaveBeenCalledTimes(1); - }); - - it("should allow selective enabling of plugins", () => { - const plugin = new BraintrustPlugin({ - integrations: { - openai: true, - anthropic: false, - aisdk: false, - claudeAgentSDK: true, - openAIAgents: true, - googleGenAI: false, - googleGenerativeAI: false, - huggingface: true, - openrouter: true, - mistral: false, - }, - }); - plugin.enable(); - - expect(OpenAIPlugin).toHaveBeenCalledTimes(1); - expect(ClaudeAgentSDKPlugin).toHaveBeenCalledTimes(1); - expect(OpenAIAgentsPlugin).toHaveBeenCalledTimes(1); - expect(HuggingFacePlugin).toHaveBeenCalledTimes(1); - expect(OpenRouterPlugin).toHaveBeenCalledTimes(1); - expect(OpenRouterAgentPlugin).toHaveBeenCalledTimes(1); - expect(AnthropicPlugin).not.toHaveBeenCalled(); - expect(AISDKPlugin).not.toHaveBeenCalled(); - expect(GoogleGenAIPlugin).not.toHaveBeenCalled(); - expect(MistralPlugin).not.toHaveBeenCalled(); - }); - }); - - describe("backward compatibility (legacy config keys)", () => { - it("should not create AI SDK plugin when vercel: false (legacy)", () => { - const plugin = new BraintrustPlugin({ - integrations: { vercel: false }, - }); - plugin.enable(); - - expect(AISDKPlugin).not.toHaveBeenCalled(); - // Other plugins should still be created - expect(OpenAIPlugin).toHaveBeenCalledTimes(1); - expect(AnthropicPlugin).toHaveBeenCalledTimes(1); - expect(ClaudeAgentSDKPlugin).toHaveBeenCalledTimes(1); - expect(OpenAIAgentsPlugin).toHaveBeenCalledTimes(1); - expect(GoogleGenAIPlugin).toHaveBeenCalledTimes(1); - expect(HuggingFacePlugin).toHaveBeenCalledTimes(1); - expect(OpenRouterPlugin).toHaveBeenCalledTimes(1); - expect(MistralPlugin).toHaveBeenCalledTimes(1); - }); - - it("should not create Google GenAI plugin when google: false (legacy)", () => { - const plugin = new BraintrustPlugin({ - integrations: { google: false }, - }); - plugin.enable(); - - expect(GoogleGenAIPlugin).not.toHaveBeenCalled(); - // Other plugins should still be created - expect(OpenAIPlugin).toHaveBeenCalledTimes(1); - expect(AnthropicPlugin).toHaveBeenCalledTimes(1); - expect(AISDKPlugin).toHaveBeenCalledTimes(1); - expect(ClaudeAgentSDKPlugin).toHaveBeenCalledTimes(1); - expect(HuggingFacePlugin).toHaveBeenCalledTimes(1); - expect(OpenRouterPlugin).toHaveBeenCalledTimes(1); - expect(MistralPlugin).toHaveBeenCalledTimes(1); - }); - - it("should not create LangChain plugin when langgraph: false (alias)", () => { - const plugin = new BraintrustPlugin({ - integrations: { langgraph: false }, - }); - plugin.enable(); - - expect(LangChainPlugin).not.toHaveBeenCalled(); - expect(LangSmithPlugin).toHaveBeenCalledWith({ - skipLangChainRuns: true, - }); - }); - - it("should not create AI SDK plugin when both aisdk and vercel are false", () => { - const plugin = new BraintrustPlugin({ - integrations: { aisdk: false, vercel: false }, - }); - plugin.enable(); - - expect(AISDKPlugin).not.toHaveBeenCalled(); - }); - - it("should not create Google GenAI plugin when both googleGenAI and google are false", () => { - const plugin = new BraintrustPlugin({ - integrations: { googleGenAI: false, google: false }, - }); - plugin.enable(); - - expect(GoogleGenAIPlugin).not.toHaveBeenCalled(); - }); - - it("should not create AI SDK plugin when aisdk is true but vercel is false", () => { - const plugin = new BraintrustPlugin({ - integrations: { aisdk: true, vercel: false }, - }); - plugin.enable(); - - expect(AISDKPlugin).not.toHaveBeenCalled(); - }); - - it("should not create Google GenAI plugin when googleGenAI is true but google is false", () => { - const plugin = new BraintrustPlugin({ - integrations: { googleGenAI: true, google: false }, - }); - plugin.enable(); - - expect(GoogleGenAIPlugin).not.toHaveBeenCalled(); - }); - - it("should create AI SDK plugin when vercel is true and aisdk is not set", () => { - const plugin = new BraintrustPlugin({ - integrations: { vercel: true }, - }); - plugin.enable(); - - expect(AISDKPlugin).toHaveBeenCalledTimes(1); - }); - - it("should create Google GenAI plugin when google is true and googleGenAI is not set", () => { - const plugin = new BraintrustPlugin({ - integrations: { google: true }, - }); - plugin.enable(); - - expect(GoogleGenAIPlugin).toHaveBeenCalledTimes(1); - }); - }); - - describe("enable/disable lifecycle", () => { - it("should enable all sub-plugins when enabled", () => { - const plugin = new BraintrustPlugin(); - plugin.enable(); - - const openaiMock = vi.mocked(OpenAIPlugin).mock.results[0].value; - const openAICodexMock = - vi.mocked(OpenAICodexPlugin).mock.results[0].value; - const anthropicMock = vi.mocked(AnthropicPlugin).mock.results[0].value; - const aiSDKMock = vi.mocked(AISDKPlugin).mock.results[0].value; - const claudeAgentSDKMock = - vi.mocked(ClaudeAgentSDKPlugin).mock.results[0].value; - const openAIAgentsMock = - vi.mocked(OpenAIAgentsPlugin).mock.results[0].value; - const googleGenAIMock = - vi.mocked(GoogleGenAIPlugin).mock.results[0].value; - const huggingFaceMock = - vi.mocked(HuggingFacePlugin).mock.results[0].value; - const huggingFaceTransformersMock = vi.mocked( - HuggingFaceTransformersPlugin, - ).mock.results[0].value; - const openRouterMock = vi.mocked(OpenRouterPlugin).mock.results[0].value; - const openRouterAgentMock = vi.mocked(OpenRouterAgentPlugin).mock - .results[0].value; - const mistralMock = vi.mocked(MistralPlugin).mock.results[0].value; - const ollamaMock = vi.mocked(OllamaPlugin).mock.results[0].value; - const cohereMock = vi.mocked(CoherePlugin).mock.results[0].value; - const groqMock = vi.mocked(GroqPlugin).mock.results[0].value; - const piCodingAgentMock = - vi.mocked(PiCodingAgentPlugin).mock.results[0].value; - const strandsAgentSDKMock = vi.mocked(StrandsAgentSDKPlugin).mock - .results[0].value; - const cloudflareAgentsMock = vi.mocked(CloudflareAgentsPlugin).mock - .results[0].value; - const langChainMock = vi.mocked(LangChainPlugin).mock.results[0].value; - - expect(openaiMock.enable).toHaveBeenCalledTimes(1); - expect(openAICodexMock.enable).toHaveBeenCalledTimes(1); - expect(anthropicMock.enable).toHaveBeenCalledTimes(1); - expect(aiSDKMock.enable).toHaveBeenCalledTimes(1); - expect(claudeAgentSDKMock.enable).toHaveBeenCalledTimes(1); - expect(openAIAgentsMock.enable).toHaveBeenCalledTimes(1); - expect(googleGenAIMock.enable).toHaveBeenCalledTimes(1); - expect(huggingFaceMock.enable).toHaveBeenCalledTimes(1); - expect(huggingFaceTransformersMock.enable).toHaveBeenCalledTimes(1); - expect(openRouterMock.enable).toHaveBeenCalledTimes(1); - expect(openRouterAgentMock.enable).toHaveBeenCalledTimes(1); - expect(mistralMock.enable).toHaveBeenCalledTimes(1); - expect(ollamaMock.enable).toHaveBeenCalledTimes(1); - expect(cohereMock.enable).toHaveBeenCalledTimes(1); - expect(groqMock.enable).toHaveBeenCalledTimes(1); - expect(piCodingAgentMock.enable).toHaveBeenCalledTimes(1); - expect(strandsAgentSDKMock.enable).toHaveBeenCalledTimes(1); - expect(cloudflareAgentsMock.enable).toHaveBeenCalledTimes(1); - expect(langChainMock.enable).toHaveBeenCalledTimes(1); - }); - - it("should disable and nullify all sub-plugins when disabled", () => { - const plugin = new BraintrustPlugin(); - plugin.enable(); - - const openaiMock = vi.mocked(OpenAIPlugin).mock.results[0].value; - const openAICodexMock = - vi.mocked(OpenAICodexPlugin).mock.results[0].value; - const anthropicMock = vi.mocked(AnthropicPlugin).mock.results[0].value; - const aiSDKMock = vi.mocked(AISDKPlugin).mock.results[0].value; - const claudeAgentSDKMock = - vi.mocked(ClaudeAgentSDKPlugin).mock.results[0].value; - const openAIAgentsMock = - vi.mocked(OpenAIAgentsPlugin).mock.results[0].value; - const googleGenAIMock = - vi.mocked(GoogleGenAIPlugin).mock.results[0].value; - const huggingFaceMock = - vi.mocked(HuggingFacePlugin).mock.results[0].value; - const huggingFaceTransformersMock = vi.mocked( - HuggingFaceTransformersPlugin, - ).mock.results[0].value; - const openRouterMock = vi.mocked(OpenRouterPlugin).mock.results[0].value; - const openRouterAgentMock = vi.mocked(OpenRouterAgentPlugin).mock - .results[0].value; - const mistralMock = vi.mocked(MistralPlugin).mock.results[0].value; - const ollamaMock = vi.mocked(OllamaPlugin).mock.results[0].value; - const cohereMock = vi.mocked(CoherePlugin).mock.results[0].value; - const groqMock = vi.mocked(GroqPlugin).mock.results[0].value; - const piCodingAgentMock = - vi.mocked(PiCodingAgentPlugin).mock.results[0].value; - const strandsAgentSDKMock = vi.mocked(StrandsAgentSDKPlugin).mock - .results[0].value; - const cloudflareAgentsMock = vi.mocked(CloudflareAgentsPlugin).mock - .results[0].value; - const langChainMock = vi.mocked(LangChainPlugin).mock.results[0].value; - - plugin.disable(); - - expect(openaiMock.disable).toHaveBeenCalledTimes(1); - expect(openAICodexMock.disable).toHaveBeenCalledTimes(1); - expect(anthropicMock.disable).toHaveBeenCalledTimes(1); - expect(aiSDKMock.disable).toHaveBeenCalledTimes(1); - expect(claudeAgentSDKMock.disable).toHaveBeenCalledTimes(1); - expect(openAIAgentsMock.disable).toHaveBeenCalledTimes(1); - expect(googleGenAIMock.disable).toHaveBeenCalledTimes(1); - expect(huggingFaceMock.disable).toHaveBeenCalledTimes(1); - expect(huggingFaceTransformersMock.disable).toHaveBeenCalledTimes(1); - expect(openRouterMock.disable).toHaveBeenCalledTimes(1); - expect(openRouterAgentMock.disable).toHaveBeenCalledTimes(1); - expect(mistralMock.disable).toHaveBeenCalledTimes(1); - expect(ollamaMock.disable).toHaveBeenCalledTimes(1); - expect(cohereMock.disable).toHaveBeenCalledTimes(1); - expect(groqMock.disable).toHaveBeenCalledTimes(1); - expect(piCodingAgentMock.disable).toHaveBeenCalledTimes(1); - expect(strandsAgentSDKMock.disable).toHaveBeenCalledTimes(1); - expect(cloudflareAgentsMock.disable).toHaveBeenCalledTimes(1); - expect(langChainMock.disable).toHaveBeenCalledTimes(1); - }); - - it("should be idempotent on multiple enable calls", () => { - const plugin = new BraintrustPlugin(); - plugin.enable(); - plugin.enable(); - plugin.enable(); - - // Should only create and enable plugins once - expect(OpenAIPlugin).toHaveBeenCalledTimes(1); - const mockInstance = vi.mocked(OpenAIPlugin).mock.results[0].value; - expect(mockInstance.enable).toHaveBeenCalledTimes(1); - }); - - it("should be idempotent on multiple disable calls", () => { - const plugin = new BraintrustPlugin(); - plugin.enable(); - - const openaiMock = vi.mocked(OpenAIPlugin).mock.results[0].value; - - plugin.disable(); - plugin.disable(); - plugin.disable(); - - // Should only disable plugins once - expect(openaiMock.disable).toHaveBeenCalledTimes(1); - }); - - it("should not disable plugins if never enabled", () => { - const plugin = new BraintrustPlugin(); - plugin.disable(); - - // Should not create any plugins - expect(OpenAIPlugin).not.toHaveBeenCalled(); - expect(OpenAICodexPlugin).not.toHaveBeenCalled(); - expect(AnthropicPlugin).not.toHaveBeenCalled(); - expect(AISDKPlugin).not.toHaveBeenCalled(); - expect(ClaudeAgentSDKPlugin).not.toHaveBeenCalled(); - expect(OpenAIAgentsPlugin).not.toHaveBeenCalled(); - expect(GoogleGenAIPlugin).not.toHaveBeenCalled(); - expect(HuggingFacePlugin).not.toHaveBeenCalled(); - expect(OpenRouterPlugin).not.toHaveBeenCalled(); - expect(OpenRouterAgentPlugin).not.toHaveBeenCalled(); - expect(MistralPlugin).not.toHaveBeenCalled(); - expect(OllamaPlugin).not.toHaveBeenCalled(); - expect(CoherePlugin).not.toHaveBeenCalled(); - expect(GroqPlugin).not.toHaveBeenCalled(); - expect(PiCodingAgentPlugin).not.toHaveBeenCalled(); - expect(StrandsAgentSDKPlugin).not.toHaveBeenCalled(); - }); - - it("should allow re-enabling after disable", () => { - const plugin = new BraintrustPlugin(); - plugin.enable(); - plugin.disable(); - - vi.clearAllMocks(); - - plugin.enable(); - - expect(OpenAIPlugin).toHaveBeenCalledTimes(1); - expect(OpenAICodexPlugin).toHaveBeenCalledTimes(1); - expect(AnthropicPlugin).toHaveBeenCalledTimes(1); - expect(AISDKPlugin).toHaveBeenCalledTimes(1); - expect(ClaudeAgentSDKPlugin).toHaveBeenCalledTimes(1); - expect(OpenAIAgentsPlugin).toHaveBeenCalledTimes(1); - expect(GoogleGenAIPlugin).toHaveBeenCalledTimes(1); - expect(HuggingFacePlugin).toHaveBeenCalledTimes(1); - expect(OpenRouterPlugin).toHaveBeenCalledTimes(1); - expect(OpenRouterAgentPlugin).toHaveBeenCalledTimes(1); - expect(MistralPlugin).toHaveBeenCalledTimes(1); - expect(OllamaPlugin).toHaveBeenCalledTimes(1); - expect(CoherePlugin).toHaveBeenCalledTimes(1); - expect(GroqPlugin).toHaveBeenCalledTimes(1); - expect(PiCodingAgentPlugin).toHaveBeenCalledTimes(1); - expect(StrandsAgentSDKPlugin).toHaveBeenCalledTimes(1); - expect(LangChainPlugin).toHaveBeenCalledTimes(1); - expect(LangSmithPlugin).toHaveBeenCalledTimes(1); - }); - - it("should only disable plugins that were enabled", () => { - const plugin = new BraintrustPlugin({ - integrations: { - openai: true, - anthropic: false, - aisdk: true, - claudeAgentSDK: false, - openAIAgents: true, - googleGenAI: true, - huggingface: true, - openrouter: true, - openrouterAgent: true, - mistral: false, - ollama: false, - cohere: false, - groq: true, - langchain: true, - }, - }); - plugin.enable(); - - const openaiMock = vi.mocked(OpenAIPlugin).mock.results[0].value; - const aiSDKMock = vi.mocked(AISDKPlugin).mock.results[0].value; - const openAIAgentsMock = - vi.mocked(OpenAIAgentsPlugin).mock.results[0].value; - const googleGenAIMock = - vi.mocked(GoogleGenAIPlugin).mock.results[0].value; - const huggingFaceMock = - vi.mocked(HuggingFacePlugin).mock.results[0].value; - const openRouterMock = vi.mocked(OpenRouterPlugin).mock.results[0].value; - const openRouterAgentMock = vi.mocked(OpenRouterAgentPlugin).mock - .results[0].value; - const groqMock = vi.mocked(GroqPlugin).mock.results[0].value; - const langChainMock = vi.mocked(LangChainPlugin).mock.results[0].value; - - plugin.disable(); - - expect(openaiMock.disable).toHaveBeenCalledTimes(1); - expect(aiSDKMock.disable).toHaveBeenCalledTimes(1); - expect(openAIAgentsMock.disable).toHaveBeenCalledTimes(1); - expect(googleGenAIMock.disable).toHaveBeenCalledTimes(1); - expect(huggingFaceMock.disable).toHaveBeenCalledTimes(1); - expect(openRouterMock.disable).toHaveBeenCalledTimes(1); - expect(openRouterAgentMock.disable).toHaveBeenCalledTimes(1); - expect(groqMock.disable).toHaveBeenCalledTimes(1); - expect(langChainMock.disable).toHaveBeenCalledTimes(1); - expect(MistralPlugin).not.toHaveBeenCalled(); - expect(OllamaPlugin).not.toHaveBeenCalled(); - expect(CoherePlugin).not.toHaveBeenCalled(); - }); - - it("should not create Voyage AI plugin when voyageai: false", () => { - const plugin = new BraintrustPlugin({ - integrations: { voyageai: false }, - }); - plugin.enable(); - - expect(VoyageAIPlugin).not.toHaveBeenCalled(); - }); - - it("should not create MCP plugin when mcp: false", () => { - const plugin = new BraintrustPlugin({ - integrations: { mcp: false }, - }); - plugin.enable(); - - expect(MCPPlugin).not.toHaveBeenCalled(); - }); - - it("should not create TypeSafe plugin when typesafe: false", () => { - const plugin = new BraintrustPlugin({ - integrations: { typesafe: false }, - }); - plugin.enable(); - - expect(TypeSafePlugin).not.toHaveBeenCalled(); - }); - }); -}); - -// Re-exported utility function tests from OpenAI plugin -import { - parseMetricsFromUsage, - processImagesInOutput as processImagesInOutputWithFlag, - aggregateChatCompletionChunks, -} from "./braintrust-plugin"; -import { Attachment } from "../logger"; - -const processImagesInOutput = (output: unknown) => - processImagesInOutputWithFlag(output, true); - -describe("parseMetricsFromUsage", () => { - it("should return empty object for null usage", () => { - expect(parseMetricsFromUsage(null)).toEqual({}); - }); - - it("should return empty object for undefined usage", () => { - expect(parseMetricsFromUsage(undefined)).toEqual({}); - }); - - it("should parse legacy token names", () => { - const usage = { - prompt_tokens: 10, - completion_tokens: 20, - total_tokens: 30, - }; - - const result = parseMetricsFromUsage(usage); - - expect(result).toEqual({ - prompt_tokens: 10, - completion_tokens: 20, - tokens: 30, // total_tokens is mapped to tokens - }); - }); - - it("should parse new API token names and map them", () => { - const usage = { - input_tokens: 15, - output_tokens: 25, - total_tokens: 40, - }; - - const result = parseMetricsFromUsage(usage); - - expect(result).toEqual({ - prompt_tokens: 15, // input_tokens mapped to prompt_tokens - completion_tokens: 25, // output_tokens mapped to completion_tokens - tokens: 40, // total_tokens mapped to tokens - }); - }); - - it("should parse token details fields", () => { - const usage = { - input_tokens: 100, - input_tokens_details: { - cached_tokens: 50, - audio_tokens: 10, - }, - output_tokens: 80, - output_tokens_details: { - reasoning_tokens: 20, - }, - }; - - const result = parseMetricsFromUsage(usage); - - expect(result).toEqual({ - prompt_tokens: 100, - prompt_cached_tokens: 50, // input -> prompt prefix - prompt_audio_tokens: 10, - completion_tokens: 80, - completion_reasoning_tokens: 20, // output -> completion prefix - }); - }); - - it("should handle mixed legacy and new token names", () => { - const usage = { - prompt_tokens: 10, - input_tokens: 15, - completion_tokens: 20, - total_tokens: 30, - }; - - const result = parseMetricsFromUsage(usage); - - expect(result).toEqual({ - prompt_tokens: 15, // Last one wins (input_tokens mapped) - completion_tokens: 20, - tokens: 30, // total_tokens is mapped to tokens - }); - }); - - it("should ignore non-number token values", () => { - const usage = { - prompt_tokens: "not a number", - completion_tokens: 20, - total_tokens: null, - }; - - const result = parseMetricsFromUsage(usage); - - expect(result).toEqual({ - completion_tokens: 20, - }); - }); - - it("should ignore non-object token details", () => { - const usage = { - input_tokens: 100, - input_tokens_details: "not an object", - output_tokens: 80, - }; - - const result = parseMetricsFromUsage(usage); - - expect(result).toEqual({ - prompt_tokens: 100, - completion_tokens: 80, - }); - }); - - it("should ignore non-number values in token details", () => { - const usage = { - input_tokens: 100, - input_tokens_details: { - cached_tokens: 50, - invalid: "not a number", - also_invalid: null, - }, - }; - - const result = parseMetricsFromUsage(usage); - - expect(result).toEqual({ - prompt_tokens: 100, - prompt_cached_tokens: 50, - }); - }); - - it("should handle empty usage object", () => { - expect(parseMetricsFromUsage({})).toEqual({}); - }); - - it("should handle unknown token prefix in details", () => { - const usage = { - custom_tokens: 100, - custom_tokens_details: { - special: 25, - }, - }; - - const result = parseMetricsFromUsage(usage); - - expect(result).toEqual({ - custom_tokens: 100, - custom_special: 25, // custom prefix preserved - }); - }); -}); - -describe("processImagesInOutput", () => { - it("should convert image_generation_call to attachment", () => { - // Create a small 1x1 red PNG base64 - const base64Image = - "iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVR42mP8z8DwHwAFBQIAX8jx0gAAAABJRU5ErkJggg=="; - - const output = { - type: "image_generation_call", - result: base64Image, - output_format: "png", - revised_prompt: "A red pixel", - }; - - const result = processImagesInOutput(output); - - expect(result.type).toBe("image_generation_call"); - expect(result.output_format).toBe("png"); - expect(result.revised_prompt).toBe("A red pixel"); - expect(result.result).toBeInstanceOf(Attachment); - - const attachment = result.result as Attachment; - expect(attachment.reference.filename).toContain(".png"); - expect(attachment.reference.content_type).toBe("image/png"); - }); - - it("should handle image_generation_call with long revised_prompt", () => { - const longPrompt = - "This is a very long prompt that should be truncated to 50 characters when used as filename"; - const base64Image = - "iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVR42mP8z8DwHwAFBQIAX8jx0gAAAABJRU5ErkJggg=="; - - const output = { - type: "image_generation_call", - result: base64Image, - revised_prompt: longPrompt, - }; - - const result = processImagesInOutput(output); - - const attachment = result.result as Attachment; - // Should be truncated to 50 chars + sanitized + extension - expect(attachment.reference.filename.length).toBeLessThanOrEqual(54); // 50 + ".png" - }); - - it("should handle image_generation_call without revised_prompt", () => { - const base64Image = - "iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVR42mP8z8DwHwAFBQIAX8jx0gAAAABJRU5ErkJggg=="; - - const output = { - type: "image_generation_call", - result: base64Image, - output_format: "jpg", - }; - - const result = processImagesInOutput(output); - - const attachment = result.result as Attachment; - expect(attachment.reference.filename).toBe("generated_image.jpg"); - expect(attachment.reference.content_type).toBe("image/jpg"); - }); - - it("should use png as default format", () => { - const base64Image = - "iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVR42mP8z8DwHwAFBQIAX8jx0gAAAABJRU5ErkJggg=="; - - const output = { - type: "image_generation_call", - result: base64Image, - }; - - const result = processImagesInOutput(output); - - const attachment = result.result as Attachment; - expect(attachment.reference.filename).toBe("generated_image.png"); - expect(attachment.reference.content_type).toBe("image/png"); - }); - - it("should process arrays recursively", () => { - const base64Image = - "iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVR42mP8z8DwHwAFBQIAX8jx0gAAAABJRU5ErkJggg=="; - - const output = [ - { type: "text", content: "Hello" }, - { - type: "image_generation_call", - result: base64Image, - }, - { type: "text", content: "World" }, - ]; - - const result = processImagesInOutput(output); - - expect(Array.isArray(result)).toBe(true); - expect(result[0]).toEqual({ type: "text", content: "Hello" }); - expect(result[1].result).toBeInstanceOf(Attachment); - expect(result[2]).toEqual({ type: "text", content: "World" }); - }); - - it("should pass through non-image objects unchanged", () => { - const output = { - type: "text", - content: "Hello world", - }; - - const result = processImagesInOutput(output); - expect(result).toEqual(output); - }); - - it("should pass through primitive values unchanged", () => { - expect(processImagesInOutput("string")).toBe("string"); - expect(processImagesInOutput(42)).toBe(42); - expect(processImagesInOutput(true)).toBe(true); - expect(processImagesInOutput(null)).toBe(null); - }); - - it("should not process image_generation_call with non-string result", () => { - const output = { - type: "image_generation_call", - result: 12345, // Not a string - }; - - const result = processImagesInOutput(output); - expect(result).toEqual(output); - expect(result.result).toBe(12345); - }); -}); - -describe("aggregateChatCompletionChunks", () => { - it("should aggregate simple text chunks", () => { - const chunks = [ - { - choices: [{ index: 0, delta: { role: "assistant", content: "Hello" } }], - }, - { - choices: [{ index: 0, delta: { content: " world" } }], - }, - { - choices: [{ index: 0, delta: { content: "!" } }], - }, - ]; - - const result = aggregateChatCompletionChunks(chunks as any); - - expect(result.output).toEqual([ - { - index: 0, - message: { - role: "assistant", - content: "Hello world!", - tool_calls: undefined, - }, - logprobs: null, - finish_reason: undefined, - }, - ]); - expect(result.metrics).toEqual({}); - }); - - it("should extract role from first chunk", () => { - const chunks = [ - { - choices: [{ index: 0, delta: { role: "assistant" } }], - }, - { - choices: [{ index: 0, delta: { content: "Hi" } }], - }, - ]; - - const result = aggregateChatCompletionChunks(chunks as any); - - expect(result.output[0].message.role).toBe("assistant"); - }); - - it("should extract finish_reason from last chunk with it", () => { - const chunks = [ - { - choices: [{ index: 0, delta: { role: "assistant", content: "Done" } }], - }, - { - choices: [{ index: 0, delta: { finish_reason: "stop" } }], - }, - ]; - - const result = aggregateChatCompletionChunks(chunks); - - expect(result.output[0].finish_reason).toBe("stop"); - }); - - it("should aggregate tool calls by id", () => { - const chunks = [ - { - choices: [ - { - index: 0, - delta: { - role: "assistant", - tool_calls: [ - { - index: 0, - id: "call_1", - type: "function", - function: { name: "get_weather", arguments: '{"loc' }, - }, - ], - }, - }, - ], - }, - { - choices: [ - { - index: 0, - delta: { - tool_calls: [ - { - index: 0, - function: { arguments: 'ation":"' }, - }, - ], - }, - }, - ], - }, - { - choices: [ - { - index: 0, - delta: { - tool_calls: [ - { - index: 0, - function: { arguments: 'NYC"}' }, - }, - ], - }, - }, - ], - }, - ]; - - const result = aggregateChatCompletionChunks(chunks); - - expect(result.output[0].message.tool_calls).toEqual([ - { - id: "call_1", - type: "function", - function: { name: "get_weather", arguments: '{"location":"NYC"}' }, - }, - ]); - }); - - it("should handle multiple tool calls", () => { - const chunks = [ - { - choices: [ - { - index: 0, - delta: { - role: "assistant", - tool_calls: [ - { - index: 0, - id: "call_1", - type: "function", - function: { name: "tool1", arguments: '{"a":' }, - }, - ], - }, - }, - ], - }, - { - choices: [ - { - index: 0, - delta: { - tool_calls: [ - { - index: 0, - function: { arguments: "1}" }, - }, - ], - }, - }, - ], - }, - { - choices: [ - { - index: 0, - delta: { - tool_calls: [ - { - index: 1, - id: "call_2", - type: "function", - function: { name: "tool2", arguments: '{"b":' }, - }, - ], - }, - }, - ], - }, - { - choices: [ - { - index: 0, - delta: { - tool_calls: [ - { - index: 1, - function: { arguments: "2}" }, - }, - ], - }, - }, - ], - }, - ]; - - const result = aggregateChatCompletionChunks(chunks as any); - const output = result.output as any[]; - - expect(output[0].message.tool_calls).toHaveLength(2); - expect(output[0].message.tool_calls[0]).toEqual({ - id: "call_1", - type: "function", - function: { name: "tool1", arguments: '{"a":1}' }, - }); - expect(output[0].message.tool_calls[1]).toEqual({ - id: "call_2", - type: "function", - function: { name: "tool2", arguments: '{"b":2}' }, - }); - }); - - it("should parse usage metrics from chunks", () => { - const chunks = [ - { - choices: [{ index: 0, delta: { role: "assistant", content: "Hi" } }], - }, - { - choices: [{ index: 0, delta: { content: "!" } }], - usage: { - prompt_tokens: 10, - completion_tokens: 2, - total_tokens: 12, - }, - }, - ]; - - const result = aggregateChatCompletionChunks(chunks as any); - - expect(result.metrics).toEqual({ - prompt_tokens: 10, - completion_tokens: 2, - tokens: 12, // total_tokens is mapped to tokens - }); - }); - - it("should merge usage from multiple chunks", () => { - const chunks = [ - { - choices: [{ index: 0, delta: { role: "assistant" } }], - usage: { - prompt_tokens: 10, - }, - }, - { - choices: [{ index: 0, delta: { content: "Hi" } }], - usage: { - completion_tokens: 5, - total_tokens: 15, - }, - }, - ]; - - const result = aggregateChatCompletionChunks(chunks); - - expect(result.metrics).toEqual({ - prompt_tokens: 10, - completion_tokens: 5, - tokens: 15, // total_tokens is mapped to tokens - }); - }); - - it("should handle empty chunks array", () => { - const result = aggregateChatCompletionChunks([]); - - expect(result.output).toEqual([ - { - index: 0, - message: { - role: undefined, - content: undefined, - tool_calls: undefined, - }, - logprobs: null, - finish_reason: undefined, - }, - ]); - expect(result.metrics).toEqual({}); - }); - - it("should handle chunks without choices", () => { - const chunks = [ - {}, - { choices: null }, - { choices: [] }, - { choices: [{ index: 0, delta: { content: "Hi" } }] }, - ]; - - const result = aggregateChatCompletionChunks(chunks as any); - - expect(result.output[0].message.content).toBe("Hi"); - }); - - it("should handle chunks with only usage", () => { - const chunks = [ - { - usage: { - prompt_tokens: 10, - completion_tokens: 5, - }, - }, - ]; - - const result = aggregateChatCompletionChunks(chunks); - - expect(result.metrics).toEqual({ - prompt_tokens: 10, - completion_tokens: 5, - }); - }); - - it("should handle mixed content and tool calls", () => { - const chunks = [ - { - choices: [ - { - index: 0, - delta: { - role: "assistant", - content: "Let me check", - }, - }, - ], - }, - { - choices: [ - { - index: 0, - delta: { - tool_calls: [ - { - index: 0, - id: "call_1", - type: "function", - function: { name: "check", arguments: "{}" }, - }, - ], - }, - }, - ], - }, - { - choices: [{ index: 0, delta: { finish_reason: "tool_calls" } }], - }, - ]; - - const result = aggregateChatCompletionChunks(chunks); - - expect(result.output[0].message.content).toBe("Let me check"); - expect(result.output[0].message.tool_calls).toHaveLength(1); - expect(result.output[0].finish_reason).toBe("tool_calls"); - }); -}); diff --git a/js/src/instrumentation/braintrust-plugin.ts b/js/src/instrumentation/braintrust-plugin.ts deleted file mode 100644 index 5de60a49a..000000000 --- a/js/src/instrumentation/braintrust-plugin.ts +++ /dev/null @@ -1,506 +0,0 @@ -import { BasePlugin } from "./core"; -import { OpenAIPlugin } from "./plugins/openai-plugin"; -import { OpenAICodexPlugin } from "./plugins/openai-codex-plugin"; -import { AnthropicPlugin } from "./plugins/anthropic-plugin"; -import { AISDKPlugin } from "./plugins/ai-sdk-plugin"; -import { ClaudeAgentSDKPlugin } from "./plugins/claude-agent-sdk-plugin"; -import { CloudflareThinkPlugin } from "./plugins/cloudflare-think-plugin"; -import { CursorSDKPlugin } from "./plugins/cursor-sdk-plugin"; -import { OpenAIAgentsPlugin } from "./plugins/openai-agents-plugin"; -import { GoogleGenerativeAIPlugin } from "./plugins/google-generative-ai-plugin"; -import { GoogleGenAIPlugin } from "./plugins/google-genai-plugin"; -import { HuggingFacePlugin } from "./plugins/huggingface-plugin"; -import { HuggingFaceTransformersPlugin } from "./plugins/huggingface-transformers-plugin"; -import { OpenRouterAgentPlugin } from "./plugins/openrouter-agent-plugin"; -import { OpenRouterPlugin } from "./plugins/openrouter-plugin"; -import { MistralPlugin } from "./plugins/mistral-plugin"; -import { LangGraphSDKPlugin } from "./plugins/langgraph-sdk-plugin"; -import { OllamaPlugin } from "./plugins/ollama-plugin"; -import { GoogleADKPlugin } from "./plugins/google-adk-plugin"; -import { CoherePlugin } from "./plugins/cohere-plugin"; -import { GroqPlugin } from "./plugins/groq-plugin"; -import { BedrockRuntimePlugin } from "./plugins/bedrock-runtime-plugin"; -import { GenkitPlugin } from "./plugins/genkit-plugin"; -import { GitHubCopilotPlugin } from "./plugins/github-copilot-plugin"; -import { FluePlugin } from "./plugins/flue-plugin"; -import { LangChainPlugin } from "./plugins/langchain-plugin"; -import { LangSmithPlugin } from "./plugins/langsmith-plugin"; -import { PiCodingAgentPlugin } from "./plugins/pi-coding-agent-plugin"; -import { StrandsAgentSDKPlugin } from "./plugins/strands-agent-sdk-plugin"; -import { ElevenLabsPlugin } from "./plugins/elevenlabs-plugin"; -import { LiveKitPlugin } from "./plugins/livekit-plugin"; -import { MCPPlugin } from "./plugins/mcp-plugin"; -import { VoyageAIPlugin } from "./plugins/voyageai-plugin"; -import { WorkersAIPlugin } from "./plugins/workers-ai-plugin"; -import { TypeSafePlugin } from "./plugins/typesafe-plugin"; -import { CloudflareAIChatPlugin } from "./plugins/cloudflare-ai-chat-plugin"; -import { CloudflareAgentsPlugin } from "./plugins/cloudflare-agents-plugin"; -import type { InstrumentationIntegrationsConfig } from "./config"; - -export interface BraintrustPluginConfig { - integrations?: InstrumentationIntegrationsConfig; - streamingAttachmentBufferBytes?: number; -} - -/** - * Default Braintrust plugin that manages all AI provider instrumentation plugins. - * - * This plugin orchestrates: - * - OpenAI SDK (chat completions, embeddings, etc.) - * - Anthropic SDK (messages) - * - Claude Agent SDK (agent interactions) - * - Vercel AI SDK (generateText, streamText, etc.) - * - Google GenAI SDK - * - HuggingFace Inference SDK - * - LangChain.js and LangGraph - * - Mistral SDK - * - Ollama SDK - * - Cohere SDK - * - Voyage AI SDK - * - Model Context Protocol servers - * - * The plugin is automatically enabled when the Braintrust library is loaded. - * Individual integrations can be disabled via configuration. - */ -export class BraintrustPlugin extends BasePlugin { - private config: BraintrustPluginConfig; - private openaiPlugin: OpenAIPlugin | null = null; - private openAICodexPlugin: OpenAICodexPlugin | null = null; - private anthropicPlugin: AnthropicPlugin | null = null; - private aiSDKPlugin: AISDKPlugin | null = null; - private claudeAgentSDKPlugin: ClaudeAgentSDKPlugin | null = null; - private cloudflareThinkPlugin: CloudflareThinkPlugin | null = null; - private cursorSDKPlugin: CursorSDKPlugin | null = null; - private openAIAgentsPlugin: OpenAIAgentsPlugin | null = null; - private googleGenerativeAIPlugin: GoogleGenerativeAIPlugin | null = null; - private googleGenAIPlugin: GoogleGenAIPlugin | null = null; - private huggingFacePlugin: HuggingFacePlugin | null = null; - private huggingFaceTransformersPlugin: HuggingFaceTransformersPlugin | null = - null; - private openRouterPlugin: OpenRouterPlugin | null = null; - private openRouterAgentPlugin: OpenRouterAgentPlugin | null = null; - private mistralPlugin: MistralPlugin | null = null; - private langGraphSDKPlugin: LangGraphSDKPlugin | null = null; - private ollamaPlugin: OllamaPlugin | null = null; - private googleADKPlugin: GoogleADKPlugin | null = null; - private coherePlugin: CoherePlugin | null = null; - private groqPlugin: GroqPlugin | null = null; - private bedrockRuntimePlugin: BedrockRuntimePlugin | null = null; - private genkitPlugin: GenkitPlugin | null = null; - private gitHubCopilotPlugin: GitHubCopilotPlugin | null = null; - private fluePlugin: FluePlugin | null = null; - private langChainPlugin: LangChainPlugin | null = null; - private langSmithPlugin: LangSmithPlugin | null = null; - private piCodingAgentPlugin: PiCodingAgentPlugin | null = null; - private strandsAgentSDKPlugin: StrandsAgentSDKPlugin | null = null; - private elevenLabsPlugin: ElevenLabsPlugin | null = null; - private liveKitPlugin: LiveKitPlugin | null = null; - private voyageAIPlugin: VoyageAIPlugin | null = null; - private mcpPlugin: MCPPlugin | null = null; - private workersAIPlugin: WorkersAIPlugin | null = null; - private typeSafePlugin: TypeSafePlugin | null = null; - private cloudflareAIChatPlugin: CloudflareAIChatPlugin | null = null; - private cloudflareAgentsPlugin: CloudflareAgentsPlugin | null = null; - - constructor(config: BraintrustPluginConfig = {}) { - super(); - this.config = config; - } - - protected onEnable(): void { - const integrations = this.config.integrations ?? {}; - - // Enable OpenAI integration (default: true) - if (integrations.openai !== false) { - this.openaiPlugin = new OpenAIPlugin(); - this.openaiPlugin.enable(); - } - - if (integrations.openaiCodexSDK !== false) { - this.openAICodexPlugin = new OpenAICodexPlugin(); - this.openAICodexPlugin.enable(); - } - - // Enable Anthropic integration (default: true) - if (integrations.anthropic !== false) { - this.anthropicPlugin = new AnthropicPlugin(); - this.anthropicPlugin.enable(); - } - - // Enable AI SDK integration (default: true) - // Support both 'aisdk' and legacy 'vercel' config keys - if (integrations.aisdk !== false && integrations.vercel !== false) { - this.aiSDKPlugin = new AISDKPlugin(); - this.aiSDKPlugin.enable(); - } - - // Enable Claude Agent SDK integration (default: true) - if (integrations.claudeAgentSDK !== false) { - this.claudeAgentSDKPlugin = new ClaudeAgentSDKPlugin(); - this.claudeAgentSDKPlugin.enable(); - } - - if (integrations.cloudflareThink !== false) { - this.cloudflareThinkPlugin = new CloudflareThinkPlugin(); - this.cloudflareThinkPlugin.enable(); - } - - if (integrations.cursorSDK !== false && integrations.cursor !== false) { - this.cursorSDKPlugin = new CursorSDKPlugin(); - this.cursorSDKPlugin.enable(); - } - - // Enable OpenAI Agents SDK integration (default: true) - if (integrations.openAIAgents !== false) { - this.openAIAgentsPlugin = new OpenAIAgentsPlugin(); - this.openAIAgentsPlugin.enable(); - } - - // Enable Google GenAI integration (default: true) - if (integrations.googleGenerativeAI !== false) { - this.googleGenerativeAIPlugin = new GoogleGenerativeAIPlugin(); - this.googleGenerativeAIPlugin.enable(); - } - - // Support both 'googleGenAI' and legacy 'google' config keys - if (integrations.googleGenAI !== false && integrations.google !== false) { - this.googleGenAIPlugin = new GoogleGenAIPlugin(); - this.googleGenAIPlugin.enable(); - } - - if (integrations.huggingface !== false) { - this.huggingFacePlugin = new HuggingFacePlugin(); - this.huggingFacePlugin.enable(); - this.huggingFaceTransformersPlugin = new HuggingFaceTransformersPlugin(); - this.huggingFaceTransformersPlugin.enable(); - } - - if (integrations.openrouter !== false) { - this.openRouterPlugin = new OpenRouterPlugin(); - this.openRouterPlugin.enable(); - } - - if (integrations.openrouterAgent !== false) { - this.openRouterAgentPlugin = new OpenRouterAgentPlugin(); - this.openRouterAgentPlugin.enable(); - } - - if (integrations.mistral !== false) { - this.mistralPlugin = new MistralPlugin(); - this.mistralPlugin.enable(); - } - - if (integrations.langgraphSDK !== false) { - this.langGraphSDKPlugin = new LangGraphSDKPlugin(); - this.langGraphSDKPlugin.enable(); - } - - if (integrations.ollama !== false) { - this.ollamaPlugin = new OllamaPlugin(); - this.ollamaPlugin.enable(); - } - - // Enable Google ADK integration (default: true) - if (integrations.googleADK !== false) { - this.googleADKPlugin = new GoogleADKPlugin(); - this.googleADKPlugin.enable(); - } - - if (integrations.cohere !== false) { - this.coherePlugin = new CoherePlugin(); - this.coherePlugin.enable(); - } - - if (integrations.elevenlabs !== false) { - this.elevenLabsPlugin = new ElevenLabsPlugin(); - this.elevenLabsPlugin.enable(); - } - - if (integrations.livekit !== false) { - this.liveKitPlugin = new LiveKitPlugin( - this.config.streamingAttachmentBufferBytes, - typeof integrations.livekit === "object" - ? integrations.livekit?.audio - : undefined, - ); - this.liveKitPlugin.enable(); - } - - if (integrations.voyageai !== false) { - this.voyageAIPlugin = new VoyageAIPlugin(); - this.voyageAIPlugin.enable(); - } - - if (integrations.mcp !== false) { - this.mcpPlugin = new MCPPlugin( - typeof integrations.mcp === "object" ? integrations.mcp : {}, - ); - this.mcpPlugin.enable(); - } - - if (integrations.workersAI !== false) { - this.workersAIPlugin = new WorkersAIPlugin(); - this.workersAIPlugin.enable(); - } - - if (integrations.typesafe !== false) { - this.typeSafePlugin = new TypeSafePlugin(); - this.typeSafePlugin.enable(); - } - - if (integrations.groq !== false) { - this.groqPlugin = new GroqPlugin(); - this.groqPlugin.enable(); - } - - if ( - integrations.bedrock !== false && - integrations.awsBedrock !== false && - integrations.awsBedrockRuntime !== false - ) { - this.bedrockRuntimePlugin = new BedrockRuntimePlugin(); - this.bedrockRuntimePlugin.enable(); - } - - if (integrations.genkit !== false) { - this.genkitPlugin = new GenkitPlugin(); - this.genkitPlugin.enable(); - } - - if (integrations.gitHubCopilot !== false) { - this.gitHubCopilotPlugin = new GitHubCopilotPlugin(); - this.gitHubCopilotPlugin.enable(); - } - - if (integrations.piCodingAgent !== false) { - this.piCodingAgentPlugin = new PiCodingAgentPlugin(); - this.piCodingAgentPlugin.enable(); - } - - if (integrations.strandsAgentSDK !== false) { - this.strandsAgentSDKPlugin = new StrandsAgentSDKPlugin(); - this.strandsAgentSDKPlugin.enable(); - } - - if (integrations.cloudflareAIChat !== false) { - this.cloudflareAIChatPlugin = new CloudflareAIChatPlugin(); - this.cloudflareAIChatPlugin.enable(); - } - - if (integrations.cloudflareAgents !== false) { - this.cloudflareAgentsPlugin = new CloudflareAgentsPlugin(); - this.cloudflareAgentsPlugin.enable(); - } - - if (integrations.flue !== false) { - this.fluePlugin = new FluePlugin(); - this.fluePlugin.enable(); - } - - if (integrations.langchain !== false && integrations.langgraph !== false) { - this.langChainPlugin = new LangChainPlugin(); - this.langChainPlugin.enable(); - } - - if (integrations.langsmith !== false) { - this.langSmithPlugin = new LangSmithPlugin({ - skipLangChainRuns: integrations.langchain !== false, - }); - this.langSmithPlugin.enable(); - } - - // Mastra is intentionally not wired here: `@mastra/core` ships its own - // ObservabilityExporter contract, and `BraintrustObservabilityExporter` - // (wrappers/mastra.ts) is auto-installed by the loader patch in - // `auto-instrumentations/loader/mastra-observability-patch.ts` rather than - // by a BasePlugin / global hook subscription. - } - - protected onDisable(): void { - if (this.openaiPlugin) { - this.openaiPlugin.disable(); - this.openaiPlugin = null; - } - - if (this.openAICodexPlugin) { - this.openAICodexPlugin.disable(); - this.openAICodexPlugin = null; - } - - if (this.anthropicPlugin) { - this.anthropicPlugin.disable(); - this.anthropicPlugin = null; - } - - if (this.aiSDKPlugin) { - this.aiSDKPlugin.disable(); - this.aiSDKPlugin = null; - } - - if (this.claudeAgentSDKPlugin) { - this.claudeAgentSDKPlugin.disable(); - this.claudeAgentSDKPlugin = null; - } - - if (this.cloudflareThinkPlugin) { - this.cloudflareThinkPlugin.disable(); - this.cloudflareThinkPlugin = null; - } - - if (this.cursorSDKPlugin) { - this.cursorSDKPlugin.disable(); - this.cursorSDKPlugin = null; - } - - if (this.openAIAgentsPlugin) { - this.openAIAgentsPlugin.disable(); - this.openAIAgentsPlugin = null; - } - - if (this.googleGenerativeAIPlugin) { - this.googleGenerativeAIPlugin.disable(); - this.googleGenerativeAIPlugin = null; - } - if (this.googleGenAIPlugin) { - this.googleGenAIPlugin.disable(); - this.googleGenAIPlugin = null; - } - - if (this.huggingFacePlugin) { - this.huggingFacePlugin.disable(); - this.huggingFacePlugin = null; - } - - if (this.huggingFaceTransformersPlugin) { - this.huggingFaceTransformersPlugin.disable(); - this.huggingFaceTransformersPlugin = null; - } - - if (this.openRouterPlugin) { - this.openRouterPlugin.disable(); - this.openRouterPlugin = null; - } - - if (this.openRouterAgentPlugin) { - this.openRouterAgentPlugin.disable(); - this.openRouterAgentPlugin = null; - } - - if (this.mistralPlugin) { - this.mistralPlugin.disable(); - this.mistralPlugin = null; - } - - if (this.langGraphSDKPlugin) { - this.langGraphSDKPlugin.disable(); - this.langGraphSDKPlugin = null; - } - - if (this.ollamaPlugin) { - this.ollamaPlugin.disable(); - this.ollamaPlugin = null; - } - - if (this.googleADKPlugin) { - this.googleADKPlugin.disable(); - this.googleADKPlugin = null; - } - - if (this.coherePlugin) { - this.coherePlugin.disable(); - this.coherePlugin = null; - } - - if (this.elevenLabsPlugin) { - this.elevenLabsPlugin.disable(); - this.elevenLabsPlugin = null; - } - - if (this.liveKitPlugin) { - this.liveKitPlugin.disable(); - this.liveKitPlugin = null; - } - - if (this.voyageAIPlugin) { - this.voyageAIPlugin.disable(); - this.voyageAIPlugin = null; - } - - if (this.mcpPlugin) { - this.mcpPlugin.disable(); - this.mcpPlugin = null; - } - - if (this.workersAIPlugin) { - this.workersAIPlugin.disable(); - this.workersAIPlugin = null; - } - - if (this.typeSafePlugin) { - this.typeSafePlugin.disable(); - this.typeSafePlugin = null; - } - - if (this.groqPlugin) { - this.groqPlugin.disable(); - this.groqPlugin = null; - } - - if (this.bedrockRuntimePlugin) { - this.bedrockRuntimePlugin.disable(); - this.bedrockRuntimePlugin = null; - } - - if (this.genkitPlugin) { - this.genkitPlugin.disable(); - this.genkitPlugin = null; - } - - if (this.gitHubCopilotPlugin) { - this.gitHubCopilotPlugin.disable(); - this.gitHubCopilotPlugin = null; - } - - if (this.piCodingAgentPlugin) { - this.piCodingAgentPlugin.disable(); - this.piCodingAgentPlugin = null; - } - - if (this.strandsAgentSDKPlugin) { - this.strandsAgentSDKPlugin.disable(); - this.strandsAgentSDKPlugin = null; - } - - if (this.cloudflareAIChatPlugin) { - this.cloudflareAIChatPlugin.disable(); - this.cloudflareAIChatPlugin = null; - } - - if (this.cloudflareAgentsPlugin) { - this.cloudflareAgentsPlugin.disable(); - this.cloudflareAgentsPlugin = null; - } - - if (this.fluePlugin) { - this.fluePlugin.disable(); - this.fluePlugin = null; - } - - if (this.langChainPlugin) { - this.langChainPlugin.disable(); - this.langChainPlugin = null; - } - - if (this.langSmithPlugin) { - this.langSmithPlugin.disable(); - this.langSmithPlugin = null; - } - } -} - -// Re-export OpenAI instrumentation utilities from their canonical modules. -export { - parseMetricsFromUsage, - aggregateChatCompletionChunks, -} from "./plugins/openai-plugin"; -export { processImagesInOutput } from "./plugins/openai-span-data"; diff --git a/js/src/instrumentation/config.ts b/js/src/instrumentation/config.ts index 5f336cf3b..edbc22c0a 100644 --- a/js/src/instrumentation/config.ts +++ b/js/src/instrumentation/config.ts @@ -25,9 +25,7 @@ export interface SpanCustomizer { export interface InstrumentationIntegrationsConfig { openai?: boolean; anthropic?: boolean; - vercel?: boolean; aisdk?: boolean; - google?: boolean; googleGenAI?: boolean; googleGenerativeAI?: boolean; googleADK?: boolean; @@ -35,9 +33,7 @@ export interface InstrumentationIntegrationsConfig { claudeAgentSDK?: boolean; cloudflareAIChat?: boolean; cloudflareThink?: boolean; - cursor?: boolean; cursorSDK?: boolean; - flue?: boolean; mastra?: boolean; openAIAgents?: boolean; openrouter?: boolean; @@ -46,8 +42,6 @@ export interface InstrumentationIntegrationsConfig { ollama?: boolean; cohere?: boolean; groq?: boolean; - bedrock?: boolean; - awsBedrock?: boolean; awsBedrockRuntime?: boolean; genkit?: boolean; gitHubCopilot?: boolean; @@ -115,7 +109,8 @@ export interface AudioEncodingConfig { container?: { name: "ogg" }; } -export const DEFAULT_STREAMING_ATTACHMENT_BUFFER_BYTES = 4 * 1024 * 1024; +export const DEFAULT_STREAMING_ATTACHMENT_BUFFER_BYTES: number = + 4 * 1024 * 1024; export interface InstrumentationConfig { /** @@ -172,24 +167,22 @@ const envIntegrationAliases: Record< aisdk: "aisdk", "ai-sdk": "aisdk", "vercel-ai": "aisdk", - vercel: "vercel", + vercel: "aisdk", claudeagentsdk: "claudeAgentSDK", "claude-agent-sdk": "claudeAgentSDK", cloudflareaichat: "cloudflareAIChat", "cloudflare-ai-chat": "cloudflareAIChat", "@cloudflare/ai-chat": "cloudflareAIChat", cloudflarethink: "cloudflareThink", - cursor: "cursor", + cursor: "cursorSDK", "cursor-sdk": "cursorSDK", cursorsdk: "cursorSDK", - flue: "flue", - "flue-runtime": "flue", mastra: "mastra", "openai-agents": "openAIAgents", openaiagents: "openAIAgents", "openai-agents-core": "openAIAgents", openaiagentscore: "openAIAgents", - google: "google", + google: "googleGenAI", "google-generative-ai": "googleGenerativeAI", googlegenerativeai: "googleGenerativeAI", "google-genai": "googleGenAI", @@ -207,9 +200,9 @@ const envIntegrationAliases: Record< cohere: "cohere", groq: "groq", "groq-sdk": "groq", - bedrock: "bedrock", - "aws-bedrock": "awsBedrock", - awsbedrock: "awsBedrock", + bedrock: "awsBedrockRuntime", + "aws-bedrock": "awsBedrockRuntime", + awsbedrock: "awsBedrockRuntime", "aws-bedrock-runtime": "awsBedrockRuntime", awsbedrockruntime: "awsBedrockRuntime", "@aws-sdk/client-bedrock-runtime": "awsBedrockRuntime", @@ -244,56 +237,6 @@ const envIntegrationAliases: Record< "@modelcontextprotocol/server": "mcp", }; -export function getDefaultInstrumentationIntegrations(): Record< - keyof InstrumentationIntegrationsConfig, - boolean -> { - return { - openai: true, - openaiCodexSDK: true, - anthropic: true, - vercel: true, - aisdk: true, - google: true, - googleGenAI: true, - googleGenerativeAI: true, - googleADK: true, - huggingface: true, - claudeAgentSDK: true, - cloudflareAIChat: true, - cloudflareThink: true, - cursor: true, - cursorSDK: true, - flue: true, - mastra: true, - openAIAgents: true, - openrouter: true, - openrouterAgent: true, - mistral: true, - ollama: true, - cohere: true, - groq: true, - bedrock: true, - awsBedrock: true, - awsBedrockRuntime: true, - genkit: true, - gitHubCopilot: true, - langchain: true, - langgraph: true, - langgraphSDK: true, - langsmith: true, - voyageai: true, - typesafe: true, - workersAI: true, - elevenlabs: true, - livekit: true, - mcp: true, - piCodingAgent: true, - strandsAgentSDK: true, - cloudflareAgents: true, - }; -} - export function readDisabledInstrumentationEnvConfig( disabledList: string | undefined, ): InstrumentationConfig { diff --git a/js/src/instrumentation/core/channel.ts b/js/src/instrumentation/core/channel.ts deleted file mode 100644 index 8cbf253d6..000000000 --- a/js/src/instrumentation/core/channel.ts +++ /dev/null @@ -1,55 +0,0 @@ -/** - * Utilities for instrumentation hook naming and management. - */ - -/** - * Channel naming convention: braintrust:{component}:{operation} - * - * Examples: - * - braintrust:openai:chat.completions.create - * - braintrust:anthropic:messages.create - * - braintrust:ai-sdk:generateText - */ - -/** - * Creates a standardized channel name. - * - * @param component - The SDK/library being instrumented (e.g., 'openai', 'anthropic') - * @param operation - The operation being traced (e.g., 'chat.completions.create') - * @returns The full channel name - */ -export function createChannelName( - component: string, - operation: string, -): string { - return `braintrust:${component}:${operation}`; -} - -/** - * Parses a channel name into its component parts. - * - * @param channelName - The full channel name - * @returns Object with component and operation, or null if invalid - */ -export function parseChannelName( - channelName: string, -): { component: string; operation: string } | null { - const match = channelName.match(/^braintrust:([^:]+):(.+)$/); - if (!match) { - return null; - } - return { - component: match[1], - operation: match[2], - }; -} - -/** - * Validates a channel name follows the expected convention. - * - * @param channelName - The channel name to validate - * @returns True if valid - */ -export function isValidChannelName(channelName: string): boolean { - return /^braintrust:[^:]+:.+$/.test(channelName); -} diff --git a/js/src/instrumentation/core/index.ts b/js/src/instrumentation/core/index.ts index 06c91c9b1..edfe17a89 100644 --- a/js/src/instrumentation/core/index.ts +++ b/js/src/instrumentation/core/index.ts @@ -1,26 +1,3 @@ -/** - * Core utilities for building auto-instrumentation plugins. - * - * Provides BasePlugin class and channel utilities following the OpenTelemetry - * InstrumentationBase pattern - core infrastructure lives here, but individual - * instrumentations can be separate packages. - * - * Note: auto-instrumentation config types are exposed from the Braintrust - * bundler subpaths, such as `braintrust/vite`. - */ +/** Internal utilities shared by Braintrust instrumentation consumers. */ -export { - createChannelName, - isValidChannelName, - parseChannelName, -} from "./channel"; export { toLoggedError } from "./logging"; -export { BasePlugin } from "./plugin"; -export type { - AsyncEndEvent, - AsyncStartEvent, - BaseContext, - EndEvent, - ErrorEvent, - StartEvent, -} from "./types"; diff --git a/js/src/instrumentation/core/interceptor-definitions.test.ts b/js/src/instrumentation/core/interceptor-definitions.test.ts index cd7c0a186..dbeddecf6 100644 --- a/js/src/instrumentation/core/interceptor-definitions.test.ts +++ b/js/src/instrumentation/core/interceptor-definitions.test.ts @@ -1,5 +1,5 @@ import { randomUUID } from "node:crypto"; -import { expect, it } from "vitest"; +import { expect, it } from "vite-plus/test"; import { channel, defineInterceptor } from "./interceptor-definitions"; it("defines sync and async wrapping contracts without tracing configuration", async () => { diff --git a/js/src/instrumentation/core/logging.test.ts b/js/src/instrumentation/core/logging.test.ts index 706d9bd50..9e086bc7b 100644 --- a/js/src/instrumentation/core/logging.test.ts +++ b/js/src/instrumentation/core/logging.test.ts @@ -1,4 +1,4 @@ -import { describe, expect, it } from "vitest"; +import { describe, expect, it } from "vite-plus/test"; import { toLoggedError } from "./logging"; describe("toLoggedError", () => { diff --git a/js/src/instrumentation/core/observe-result.test.ts b/js/src/instrumentation/core/observe-result.test.ts index b04ee7e60..3df99e43f 100644 --- a/js/src/instrumentation/core/observe-result.test.ts +++ b/js/src/instrumentation/core/observe-result.test.ts @@ -1,4 +1,4 @@ -import { describe, expect, it, vi } from "vitest"; +import { describe, expect, it, vi } from "vite-plus/test"; import { observeResult } from "./observe-result"; vi.mock("../../debug-logger", () => ({ debugLogger: { error: vi.fn() } })); diff --git a/js/src/instrumentation/core/plugin.ts b/js/src/instrumentation/core/plugin.ts deleted file mode 100644 index 9aacf97fa..000000000 --- a/js/src/instrumentation/core/plugin.ts +++ /dev/null @@ -1,38 +0,0 @@ -export abstract class BasePlugin { - protected enabled = false; - protected unsubscribers: Array<() => void> = []; - - /** - * Enables the plugin. Registers the plugin’s invocation interceptors. - */ - enable(): void { - if (this.enabled) { - return; - } - this.enabled = true; - this.onEnable(); - } - - /** - * Disables the plugin. Removes the plugin’s invocation interceptors. - */ - disable(): void { - if (!this.enabled) { - return; - } - this.enabled = false; - this.onDisable(); - } - - /** - * Called when the plugin is enabled. - * Override this to register interceptors. - */ - protected abstract onEnable(): void; - - /** - * Called when the plugin is disabled. - * Override this to remove interceptors. - */ - protected abstract onDisable(): void; -} diff --git a/js/src/instrumentation/core/stream-patcher.test.ts b/js/src/instrumentation/core/stream-patcher.test.ts index 195fe7435..8151a7692 100644 --- a/js/src/instrumentation/core/stream-patcher.test.ts +++ b/js/src/instrumentation/core/stream-patcher.test.ts @@ -1,5 +1,5 @@ import { debugLogger } from "../../debug-logger"; -import { describe, it, expect, vi } from "vitest"; +import { describe, it, expect, vi } from "vite-plus/test"; import { isAsyncIterable, observeByteStream, diff --git a/js/src/instrumentation/core/tracing-utils.test.ts b/js/src/instrumentation/core/tracing-utils.test.ts index 53e3605ed..7c5d830c0 100644 --- a/js/src/instrumentation/core/tracing-utils.test.ts +++ b/js/src/instrumentation/core/tracing-utils.test.ts @@ -1,4 +1,4 @@ -import { describe, expect, it } from "vitest"; +import { describe, expect, it } from "vite-plus/test"; import { buildStartSpanArgs } from "./tracing-utils"; describe("buildStartSpanArgs", () => { diff --git a/js/src/instrumentation/core/tracing-utils.ts b/js/src/instrumentation/core/tracing-utils.ts index edf7ea7c6..201077209 100644 --- a/js/src/instrumentation/core/tracing-utils.ts +++ b/js/src/instrumentation/core/tracing-utils.ts @@ -1,6 +1,6 @@ import type { ChannelSpanInfo, SpanInfoCarrier, StartEvent } from "./types"; import { debugLogger } from "../../debug-logger"; -import type { Span, StartSpanArgs } from "../../logger"; +import type { Span, StartSpanArgs } from "../../logger/core"; import { isObject, mergeDicts } from "../../util"; type ChannelConfigName = diff --git a/js/src/instrumentation/core/tracing.test.ts b/js/src/instrumentation/core/tracing.test.ts index 1a11d7af3..4aa5211fc 100644 --- a/js/src/instrumentation/core/tracing.test.ts +++ b/js/src/instrumentation/core/tracing.test.ts @@ -6,15 +6,15 @@ import { expect, it, vi, -} from "vitest"; +} from "vite-plus/test"; import { NOOP_SPAN, - _exportsForTestingOnly, currentSpan, initLogger, type Span, type TestBackgroundLogger, -} from "../../logger"; +} from "../../logger/core"; +import { _exportsForTestingOnly } from "../../logger/testing"; import { configureNode } from "../../node/config"; import { INSTRUMENTATION_NAMES, diff --git a/js/src/instrumentation/core/tracing.ts b/js/src/instrumentation/core/tracing.ts index 2b374434b..e54d95f3d 100644 --- a/js/src/instrumentation/core/tracing.ts +++ b/js/src/instrumentation/core/tracing.ts @@ -1,6 +1,6 @@ import { debugLogger } from "../../debug-logger"; -import type { Span } from "../../logger"; -import { startSpan, withCurrent } from "../../logger"; +import type { Span } from "../../logger/core"; +import { startSpan, withCurrent } from "../../logger/core"; import { withSpanInstrumentationName, type SpanInstrumentationName, @@ -781,12 +781,3 @@ export function traceSyncStreamCall( ); return result; } -export function unsubscribeAll( - unsubscribers: Array<() => void>, -): Array<() => void> { - for (const unsubscribe of unsubscribers) { - unsubscribe(); - } - - return []; -} diff --git a/js/src/instrumentation/core/types.ts b/js/src/instrumentation/core/types.ts index f6b2ab594..7b5e214e5 100644 --- a/js/src/instrumentation/core/types.ts +++ b/js/src/instrumentation/core/types.ts @@ -17,7 +17,7 @@ export type SpanInfoCarrier< /** * Per-call context passed to tracing callbacks. */ -export interface BaseContext { +interface BaseContext { /** * Unique identifier for this trace. */ @@ -39,37 +39,6 @@ export interface StartEvent extends BaseContext { arguments: TInput[]; } -/** - * Context containing a returned value. - */ -export interface EndEvent extends BaseContext { - /** - * The result of the synchronous portion. - * For async functions, this is the promise (not the resolved value). - */ - result: TResult; - - /** - * Arguments passed to the function (also available in StartEvent). - */ - arguments?: unknown[]; -} - -/** - * Context containing a thrown error or rejected promise. - */ -export interface ErrorEvent extends BaseContext { - /** - * The error that was thrown or the rejection reason. - */ - error: Error; - - /** - * Arguments passed to the function (also available in StartEvent). - */ - arguments?: unknown[]; -} - /** * Input context preserving the callable argument tuple. */ @@ -97,15 +66,6 @@ export interface TypedErrorEvent< arguments?: [...TArguments, ...any[]]; } -// eslint-disable-next-line @typescript-eslint/no-empty-object-type -export interface AsyncStartEvent extends StartEvent {} - -/** - * Context containing a resolved value for output extraction and finalization. - */ -// eslint-disable-next-line @typescript-eslint/no-empty-object-type -export interface AsyncEndEvent extends EndEvent {} - export type StartEventWith< TArguments extends EventArguments = unknown[], TExtra extends object = Record, diff --git a/js/src/instrumentation/index.test.ts b/js/src/instrumentation/index.test.ts new file mode 100644 index 000000000..86dd82fb6 --- /dev/null +++ b/js/src/instrumentation/index.test.ts @@ -0,0 +1,14 @@ +import { describe, expect, it } from "vite-plus/test"; +import * as instrumentation from "./index"; + +describe("instrumentation public API", () => { + it("exposes only the curated instrumentation APIs", () => { + expect(Object.keys(instrumentation).sort()).toEqual([ + "OpenAIAgentsTraceProcessor", + "braintrustEveInstrumentation", + "braintrustFlueInstrumentation", + "configureInstrumentation", + "registerOtelFlush", + ]); + }); +}); diff --git a/js/src/instrumentation/index.ts b/js/src/instrumentation/index.ts index cc7f85e8b..050fb2620 100644 --- a/js/src/instrumentation/index.ts +++ b/js/src/instrumentation/index.ts @@ -1,47 +1,21 @@ /** * Instrumentation APIs for auto-instrumentation. * - * This module provides the core plugin infrastructure for registering invocation - * interceptors that trace provider calls. - * - * Following the OpenTelemetry pattern, BasePlugin (like InstrumentationBase) - * lives in the core SDK, while individual instrumentation implementations - * can be separate packages. - * * For auto-instrumentation config types, import InstrumentationConfig from the * relevant Braintrust bundler subpath such as `braintrust/vite`. * * @module instrumentation */ -export { BraintrustPlugin } from "./braintrust-plugin"; -export type { BraintrustPluginConfig } from "./braintrust-plugin"; -export { BasePlugin } from "./core"; -export { braintrustEveInstrumentation } from "./plugins/eve-instrumentation"; -export { braintrustEveHook } from "./plugins/eve-plugin"; -export { - braintrustFlueInstrumentation, - braintrustFlueObserver, -} from "./plugins/flue-plugin"; -export { OpenAIAgentsTraceProcessor } from "./plugins/openai-agents-trace-processor"; -export type { OpenAIAgentsTraceProcessorOptions } from "./plugins/openai-agents-trace-processor"; - -// Re-export core types for external instrumentation packages -export { - createChannelName, - isValidChannelName, - parseChannelName, -} from "./core"; -export type { - AsyncEndEvent, - AsyncStartEvent, - BaseContext, - EndEvent, - ErrorEvent, - StartEvent, -} from "./core"; +export { OpenAIAgentsTraceProcessor } from "./providers/openai-agents-trace-processor"; +export type { OpenAIAgentsTraceProcessorOptions } from "./providers/openai-agents-trace-processor"; +export { braintrustFlueInstrumentation } from "./providers/flue-instrumentation"; +export { braintrustEveInstrumentation } from "./providers/eve-instrumentation"; // Configuration API -export type { SpanCustomizer, SpanExportData } from "./config"; export { configureInstrumentation } from "./registry"; export type { InstrumentationConfig } from "./registry"; + +// Integration hook used by @braintrust/otel so core flushes also drain OTEL. +export { registerOtelFlush } from "../logger/core"; +export type { SpanCustomizer, SpanExportData } from "./config"; diff --git a/js/src/instrumentation/instrumentation-consumers.test.ts b/js/src/instrumentation/instrumentation-consumers.test.ts new file mode 100644 index 000000000..a48eecffa --- /dev/null +++ b/js/src/instrumentation/instrumentation-consumers.test.ts @@ -0,0 +1,224 @@ +import { configureInstrumentation } from "./registry"; +import { GLOBAL_INSTRUMENTATION_HOOKS_PROTOCOL_VERSION } from "../global-instrumentation-hooks"; +import { beforeEach, describe, expect, it, vi } from "vite-plus/test"; +import type { InstrumentationIntegrationsConfig } from "./config"; + +const registrations = vi.hoisted(() => ({ + aiSDK: vi.fn(), + anthropic: vi.fn(), + bedrockRuntime: vi.fn(), + claudeAgentSDK: vi.fn(), + cloudflareAgents: vi.fn(), + cloudflareAIChat: vi.fn(), + cloudflareThink: vi.fn(), + cohere: vi.fn(), + cursorSDK: vi.fn(), + genkit: vi.fn(), + gitHubCopilot: vi.fn(), + googleADK: vi.fn(), + googleGenAI: vi.fn(), + groq: vi.fn(), + huggingFace: vi.fn(), + huggingFaceTransformers: vi.fn(), + langChain: vi.fn(), + langSmith: vi.fn(), + mistral: vi.fn(), + ollama: vi.fn(), + openAI: vi.fn(), + openAIAgents: vi.fn(), + openAICodex: vi.fn(), + openRouter: vi.fn(), + openRouterAgent: vi.fn(), + piCodingAgent: vi.fn(), + strandsAgentSDK: vi.fn(), + voyageAI: vi.fn(), +})); + +vi.mock("./providers/ai-sdk-instrumentation", () => ({ + registerAISDKInstrumentation: registrations.aiSDK, +})); +vi.mock("./providers/anthropic-instrumentation", () => ({ + registerAnthropicInstrumentation: registrations.anthropic, +})); +vi.mock("./providers/bedrock-runtime-instrumentation", () => ({ + registerBedrockRuntimeInstrumentation: registrations.bedrockRuntime, +})); +vi.mock("./providers/claude-agent-sdk-instrumentation", () => ({ + registerClaudeAgentSDKInstrumentation: registrations.claudeAgentSDK, +})); +vi.mock("./providers/cloudflare-agents-instrumentation", () => ({ + registerCloudflareAgentsInstrumentation: registrations.cloudflareAgents, +})); +vi.mock("./providers/cloudflare-ai-chat-consumer", () => ({ + registerCloudflareAIChatInstrumentation: registrations.cloudflareAIChat, +})); +vi.mock("./providers/cloudflare-think-instrumentation", () => ({ + registerCloudflareThinkInstrumentation: registrations.cloudflareThink, +})); +vi.mock("./providers/cohere-instrumentation", () => ({ + registerCohereInstrumentation: registrations.cohere, +})); +vi.mock("./providers/cursor-sdk-instrumentation", () => ({ + registerCursorSDKInstrumentation: registrations.cursorSDK, +})); +vi.mock("./providers/genkit-instrumentation", () => ({ + registerGenkitInstrumentation: registrations.genkit, +})); +vi.mock("./providers/github-copilot-instrumentation", () => ({ + registerGitHubCopilotInstrumentation: registrations.gitHubCopilot, +})); +vi.mock("./providers/google-adk-instrumentation", () => ({ + registerGoogleADKInstrumentation: registrations.googleADK, +})); +vi.mock("./providers/google-genai-instrumentation", () => ({ + registerGoogleGenAIInstrumentation: registrations.googleGenAI, +})); +vi.mock("./providers/groq-instrumentation", () => ({ + registerGroqInstrumentation: registrations.groq, +})); +vi.mock("./providers/huggingface-instrumentation", () => ({ + registerHuggingFaceInstrumentation: registrations.huggingFace, +})); +vi.mock("./providers/huggingface-transformers-instrumentation", () => ({ + registerHuggingFaceTransformersInstrumentation: + registrations.huggingFaceTransformers, +})); +vi.mock("./providers/langchain-instrumentation", () => ({ + registerLangChainInstrumentation: registrations.langChain, +})); +vi.mock("./providers/langsmith-instrumentation", () => ({ + registerLangSmithInstrumentation: registrations.langSmith, +})); +vi.mock("./providers/mistral-instrumentation", () => ({ + registerMistralInstrumentation: registrations.mistral, +})); +vi.mock("./providers/ollama-instrumentation", () => ({ + registerOllamaInstrumentation: registrations.ollama, +})); +vi.mock("./providers/openai-instrumentation", () => ({ + registerOpenAIInstrumentation: registrations.openAI, +})); +vi.mock("./providers/openai-agents-instrumentation", () => ({ + registerOpenAIAgentsInstrumentation: registrations.openAIAgents, +})); +vi.mock("./providers/openai-codex-instrumentation", () => ({ + registerOpenAICodexInstrumentation: registrations.openAICodex, +})); +vi.mock("./providers/openrouter-instrumentation", () => ({ + registerOpenRouterInstrumentation: registrations.openRouter, +})); +vi.mock("./providers/openrouter-agent-instrumentation", () => ({ + registerOpenRouterAgentInstrumentation: registrations.openRouterAgent, +})); +vi.mock("./providers/pi-coding-agent-instrumentation", () => ({ + registerPiCodingAgentInstrumentation: registrations.piCodingAgent, +})); +vi.mock("./providers/strands-agent-sdk-instrumentation", () => ({ + registerStrandsAgentSDKInstrumentation: registrations.strandsAgentSDK, +})); +vi.mock("./providers/voyageai-instrumentation", () => ({ + registerVoyageAIInstrumentation: registrations.voyageAI, +})); + +import { registerInstrumentationConsumers } from "./instrumentation-consumers"; + +const integrationCases: Array< + [ + keyof InstrumentationIntegrationsConfig, + Array<(typeof registrations)[keyof typeof registrations]>, + ] +> = [ + ["aisdk", [registrations.aiSDK]], + ["anthropic", [registrations.anthropic]], + ["awsBedrockRuntime", [registrations.bedrockRuntime]], + ["claudeAgentSDK", [registrations.claudeAgentSDK]], + ["cloudflareAgents", [registrations.cloudflareAgents]], + ["cloudflareAIChat", [registrations.cloudflareAIChat]], + ["cloudflareThink", [registrations.cloudflareThink]], + ["cohere", [registrations.cohere]], + ["cursorSDK", [registrations.cursorSDK]], + ["genkit", [registrations.genkit]], + ["gitHubCopilot", [registrations.gitHubCopilot]], + ["googleADK", [registrations.googleADK]], + ["googleGenAI", [registrations.googleGenAI]], + ["groq", [registrations.groq]], + [ + "huggingface", + [registrations.huggingFace, registrations.huggingFaceTransformers], + ], + ["langchain", [registrations.langChain]], + ["langsmith", [registrations.langSmith]], + ["mistral", [registrations.mistral]], + ["ollama", [registrations.ollama]], + ["openai", [registrations.openAI]], + ["openAIAgents", [registrations.openAIAgents]], + ["openaiCodexSDK", [registrations.openAICodex]], + ["openrouter", [registrations.openRouter]], + ["openrouterAgent", [registrations.openRouterAgent]], + ["piCodingAgent", [registrations.piCodingAgent]], + ["strandsAgentSDK", [registrations.strandsAgentSDK]], + ["voyageai", [registrations.voyageAI]], +]; + +describe("registerInstrumentationConsumers", () => { + beforeEach(() => { + vi.clearAllMocks(); + delete (globalThis as Record)[ + Symbol.for( + `braintrust.registry.global-hooks.v${GLOBAL_INSTRUMENTATION_HOOKS_PROTOCOL_VERSION}`, + ) + ]; + }); + + it("registers every instrumentation consumer by default", () => { + registerInstrumentationConsumers(); + + for (const register of Object.values(registrations)) { + expect(register).toHaveBeenCalledTimes(1); + } + expect(registrations.langSmith).toHaveBeenCalledWith({ + skipLangChainRuns: expect.any(Function), + }); + expect(registrations.langSmith.mock.calls[0][0].skipLangChainRuns()).toBe( + true, + ); + }); + + it.each(integrationCases)( + "does not register the %s integration when disabled", + (integration, disabledRegistrations) => { + configureInstrumentation({ + integrations: { [integration]: false }, + }); + registerInstrumentationConsumers(); + + for (const register of disabledRegistrations) { + expect(register).not.toHaveBeenCalled(); + } + }, + ); + + it.each([["langgraph", registrations.langChain]] as const)( + "honors the related %s disable flag", + (integration, register) => { + configureInstrumentation({ + integrations: { [integration]: false }, + }); + registerInstrumentationConsumers(); + + expect(register).not.toHaveBeenCalled(); + }, + ); + + it("tells LangSmith not to suppress LangChain runs when LangChain is disabled", () => { + configureInstrumentation({ integrations: { langchain: false } }); + registerInstrumentationConsumers(); + + expect(registrations.langSmith).toHaveBeenCalledWith({ + skipLangChainRuns: expect.any(Function), + }); + expect(registrations.langSmith.mock.calls[0][0].skipLangChainRuns()).toBe( + false, + ); + }); +}); diff --git a/js/src/instrumentation/instrumentation-consumers.ts b/js/src/instrumentation/instrumentation-consumers.ts new file mode 100644 index 000000000..17759c137 --- /dev/null +++ b/js/src/instrumentation/instrumentation-consumers.ts @@ -0,0 +1,74 @@ +import { registerInstrumentation as mcp } from "./registrations/mcp"; +import { registerInstrumentation as livekit } from "./registrations/livekit"; +import { registerInstrumentation as workersAI } from "./registrations/workersAI"; +import { registerInstrumentation as openai } from "./registrations/openai"; +import { registerInstrumentation as openaiCodexSDK } from "./registrations/openaiCodexSDK"; +import { registerInstrumentation as anthropic } from "./registrations/anthropic"; +import { registerInstrumentation as aisdk } from "./registrations/aisdk"; +import { registerInstrumentation as claudeAgentSDK } from "./registrations/claudeAgentSDK"; +import { registerInstrumentation as cloudflareThink } from "./registrations/cloudflareThink"; +import { registerInstrumentation as cursorSDK } from "./registrations/cursorSDK"; +import { registerInstrumentation as openAIAgents } from "./registrations/openAIAgents"; +import { registerInstrumentation as googleGenAI } from "./registrations/googleGenAI"; +import { registerInstrumentation as huggingface } from "./registrations/huggingface"; +import { registerInstrumentation as openrouter } from "./registrations/openrouter"; +import { registerInstrumentation as openrouterAgent } from "./registrations/openrouterAgent"; +import { registerInstrumentation as mistral } from "./registrations/mistral"; +import { registerInstrumentation as ollama } from "./registrations/ollama"; +import { registerInstrumentation as googleADK } from "./registrations/googleADK"; +import { registerInstrumentation as cohere } from "./registrations/cohere"; +import { registerInstrumentation as voyageai } from "./registrations/voyageai"; +import { registerInstrumentation as groq } from "./registrations/groq"; +import { registerInstrumentation as awsBedrockRuntime } from "./registrations/awsBedrockRuntime"; +import { registerInstrumentation as genkit } from "./registrations/genkit"; +import { registerInstrumentation as gitHubCopilot } from "./registrations/gitHubCopilot"; +import { registerInstrumentation as piCodingAgent } from "./registrations/piCodingAgent"; +import { registerInstrumentation as strandsAgentSDK } from "./registrations/strandsAgentSDK"; +import { registerInstrumentation as cloudflareAIChat } from "./registrations/cloudflareAIChat"; +import { registerInstrumentation as cloudflareAgents } from "./registrations/cloudflareAgents"; +import { registerInstrumentation as langchain } from "./registrations/langchain"; +import { registerInstrumentation as langsmith } from "./registrations/langsmith"; +import { registerInstrumentation as elevenlabs } from "./registrations/elevenlabs"; +import { registerInstrumentation as googleGenerativeAI } from "./registrations/googleGenerativeAI"; +import { registerInstrumentation as langgraphSDK } from "./registrations/langgraphSDK"; +import { registerInstrumentation as typesafe } from "./registrations/typesafe"; +import { registerInstrumentation as mastra } from "./registrations/mastra"; + +/** Register every enabled provider. Only explicit auto setup imports this module. */ +export function registerInstrumentationConsumers(): void { + mcp(); + livekit(); + workersAI(); + openai(); + openaiCodexSDK(); + anthropic(); + aisdk(); + claudeAgentSDK(); + cloudflareThink(); + cursorSDK(); + openAIAgents(); + googleGenAI(); + huggingface(); + openrouter(); + openrouterAgent(); + mistral(); + ollama(); + googleADK(); + cohere(); + voyageai(); + groq(); + awsBedrockRuntime(); + genkit(); + gitHubCopilot(); + piCodingAgent(); + strandsAgentSDK(); + cloudflareAIChat(); + cloudflareAgents(); + langchain(); + langsmith(); + elevenlabs(); + googleGenerativeAI(); + langgraphSDK(); + typesafe(); + mastra(); +} diff --git a/js/src/instrumentation/plugins/cloudflare-agents-plugin.ts b/js/src/instrumentation/plugins/cloudflare-agents-plugin.ts deleted file mode 100644 index 4f3ffd599..000000000 --- a/js/src/instrumentation/plugins/cloudflare-agents-plugin.ts +++ /dev/null @@ -1,171 +0,0 @@ -import { SpanTypeAttribute } from "../../../util/index"; -import { debugLogger } from "../../debug-logger"; -import type { Span } from "../../logger"; -import { _internalStartSpanWithContext } from "../../logger"; -import { - INSTRUMENTATION_NAMES, - withSpanInstrumentationName, -} from "../../span-origin"; -import { BasePlugin } from "../core"; -import { observeResult, runInstrumentation } from "../core/observe-result"; - -import type { ChannelMessage } from "../core/tracing-types"; -import { cloudflareAgentsChannels } from "./cloudflare-agents-interceptors"; - -const CLOUDFLARE_WORKERS_CONTEXT = { - span_origin: { - environment: { type: "server", name: "cloudflare_workers" }, - }, -}; - -export class CloudflareAgentsPlugin extends BasePlugin { - protected onEnable(): void { - const channel = cloudflareAgentsChannels.runAgentTool; - const spans = new WeakMap(); - - const removeHandlers = channel.intercept( - (target, receiver, args, additional) => { - const event = { - ...additional, - arguments: args, - self: receiver, - } as ChannelMessage; - const prepare = ( - event: ChannelMessage, - ) => { - try { - const agentClass = event.arguments[0]; - const options = event.arguments[1]; - if (ownValue(options, "detached")) { - return; - } - - const name = ownValue(agentClass, "name"); - if (typeof name !== "string" || name.length === 0) { - debugLogger.warn( - "Skipping Cloudflare Agents runAgentTool span because the child agent class has no name.", - ); - return; - } - - const span = _internalStartSpanWithContext( - withSpanInstrumentationName( - { - name, - spanAttributes: { type: SpanTypeAttribute.TOOL }, - event: { - input: ownValue(options, "input"), - }, - }, - INSTRUMENTATION_NAMES.CLOUDFLARE_AGENTS, - ), - CLOUDFLARE_WORKERS_CONTEXT, - ); - spans.set(event, span); - } catch (error) { - logInstrumentationError("start", error); - } - }; - const resolved = ( - event: ChannelMessage, - ) => { - const span = spans.get(event); - if (!span) { - return; - } - spans.delete(event); - - try { - const status = ownValue(event.result, "status"); - if (status === "completed") { - span.log({ output: ownValue(event.result, "output") }); - } else { - const error = ownValue(event.result, "error"); - if (typeof error === "string") { - span.log({ error }); - } - } - } catch (error) { - logInstrumentationError("completion", error); - } finally { - safelyEndSpan(span); - } - }; - const failed = ( - event: ChannelMessage, - ) => { - const span = spans.get(event); - if (!span) { - return; - } - spans.delete(event); - - try { - span.log({ error: event.error }); - } catch (error) { - logInstrumentationError("rejection", error); - } finally { - safelyEndSpan(span); - } - }; - runInstrumentation(() => prepare(event)); - let result; - try { - result = Reflect.apply(target, receiver, args); - } catch (error) { - Object.assign(event, { error }); - runInstrumentation(() => failed(event)); - throw error; - } - return observeResult( - result, - (value) => { - Object.assign(event, { result: value }); - resolved(event); - }, - (error) => { - Object.assign(event, { error }); - failed(event); - }, - ); - }, - ); - this.unsubscribers.push(removeHandlers); - } - - protected onDisable(): void { - for (const unsubscribe of this.unsubscribers) { - unsubscribe(); - } - this.unsubscribers = []; - } -} - -function ownValue(value: unknown, key: PropertyKey): unknown { - if (!isObjectLike(value)) { - return undefined; - } - const descriptor = Object.getOwnPropertyDescriptor(value, key); - return descriptor && "value" in descriptor ? descriptor.value : undefined; -} - -function isObjectLike(value: unknown): value is object { - return ( - (typeof value === "object" && value !== null) || typeof value === "function" - ); -} - -function safelyEndSpan(span: Span): void { - try { - span.end(); - } catch (error) { - logInstrumentationError("span end", error); - } -} - -function logInstrumentationError(operation: string, error: unknown): void { - debugLogger.error( - `Failed to process Cloudflare Agents ${operation} instrumentation:`, - error, - ); -} diff --git a/js/src/instrumentation/plugins/eve-instrumentation.ts b/js/src/instrumentation/plugins/eve-instrumentation.ts deleted file mode 100644 index b22a71dad..000000000 --- a/js/src/instrumentation/plugins/eve-instrumentation.ts +++ /dev/null @@ -1,65 +0,0 @@ -import type { - EveInstrumentationDefinition, - EveProviderDefinition, -} from "../../vendor-sdk-types/eve"; -import { - createLegacyEveInstrumentation, - type EveDefineState, -} from "./eve-plugin"; -import { createEveInstrumentationProvider } from "./eve-provider"; - -const EVE_INSTRUMENTATION_PROVIDER = Symbol.for("eve.instrumentation.provider"); - -type LegacyEveInstrumentationOptions = { - defineState: EveDefineState; - setup?: EveInstrumentationDefinition["setup"]; -}; - -type EveProviderInstrumentationOptions = { - metadata?: Record; - setup?: EveProviderDefinition["setup"]; -}; - -/** - * Braintrust instrumentation for Eve. - * - * Pass `defineState` when using Eve's legacy authored instrumentation API. - * With Eve 0.34+, omit `defineState` to use the instrumentation-provider - * lifecycle. Eve before 0.62 also requires - * `experimental.instrumentationProviders` on the agent. - * The result is ready to export directly from the instrumentation module. - */ -/* eslint-disable @typescript-eslint/no-explicit-any -- Eve compatibility boundary. */ -export function braintrustEveInstrumentation( - options: EveProviderInstrumentationOptions, -): any; -// Keep the legacy overload last so utility types retain the existing signature. -export function braintrustEveInstrumentation(options: { - defineState: EveDefineState; - setup?: EveInstrumentationDefinition["setup"]; -}): any; -export function braintrustEveInstrumentation( - options: LegacyEveInstrumentationOptions | EveProviderInstrumentationOptions, -): any { - if (!options || typeof options !== "object") { - throw new TypeError( - "braintrustEveInstrumentation requires an options object", - ); - } - if ("defineState" in options) { - return { - ...createLegacyEveInstrumentation(options), - [EVE_INSTRUMENTATION_PROVIDER]: true, - }; - } - // Eve before 0.47.4 only reads `capture` to decide whether provider events - // include content, while eve 0.62+ rejects providers that own a `capture` - // key. Inheriting it keeps content for old versions without tripping that - // own-property check. - return Object.assign( - Object.create({ capture: "content" }), - createEveInstrumentationProvider(options), - { [EVE_INSTRUMENTATION_PROVIDER]: true }, - ); -} -/* eslint-enable @typescript-eslint/no-explicit-any */ diff --git a/js/src/instrumentation/plugins/eve-plugin.test.ts b/js/src/instrumentation/plugins/eve-plugin.test.ts deleted file mode 100644 index 71f34134b..000000000 --- a/js/src/instrumentation/plugins/eve-plugin.test.ts +++ /dev/null @@ -1,2357 +0,0 @@ -import { - afterEach, - beforeAll, - beforeEach, - describe, - expect, - expectTypeOf, - it, - vi, -} from "vitest"; -import { createHash } from "node:crypto"; -import * as braintrustExports from "../../exports"; -import { configureNode } from "../../node/config"; -import { - _exportsForTestingOnly, - initLogger, - startSpan, - withCurrent, -} from "../../logger"; -import * as instrumentationExports from "../index"; -import { braintrustEveInstrumentation } from "./eve-instrumentation"; -import { braintrustEveHook, capturedModelInput } from "./eve-plugin"; -import type { - EveHandleMessageStreamEvent, - EveHookContext, -} from "../../vendor-sdk-types/eve"; -import { mergeRowBatch } from "../../../util/index"; - -function deterministicEveIdForTest(...parts: string[]): string { - return createHash("sha256") - .update(parts.map((part) => `${part.length}:${part}`).join("\0")) - .digest("hex") - .slice(0, 32) - .replace(/^(.{8})(.{4})(.{4})(.{4})(.{12})$/, "$1-$2-$3-$4-$5"); -} - -function createFakeDefineState() { - const values = new Map(); - return { - defineState(name: string, initial: () => T) { - return { - get: () => (values.has(name) ? (values.get(name) as T) : initial()), - update: (fn: (current: T) => T) => { - values.set( - name, - fn(values.has(name) ? (values.get(name) as T) : initial()), - ); - }, - }; - }, - values, - }; -} - -try { - configureNode(); -} catch { - // Best-effort initialization for test environments. -} - -describe("braintrustEveHook", () => { - let backgroundLogger: ReturnType< - typeof _exportsForTestingOnly.useTestBackgroundLogger - >; - let defineState: ReturnType["defineState"]; - - beforeAll(async () => { - await _exportsForTestingOnly.simulateLoginForTests(); - }); - - beforeEach(() => { - defineState = createFakeDefineState().defineState; - backgroundLogger = _exportsForTestingOnly.useTestBackgroundLogger(); - initLogger({ - projectName: "eve-plugin.test.ts", - projectId: "test-project-id", - }); - }); - - afterEach(() => { - vi.restoreAllMocks(); - _exportsForTestingOnly.clearTestBackgroundLogger(); - }); - - it("returns an Eve hook definition", () => { - const hook = braintrustEveHook({ defineState }); - - expectTypeOf(hook).toBeAny(); - expect(Object.keys(hook)).toEqual(["events"]); - expect(typeof hook.events?.["*"]).toBe("function"); - }); - - it("returns an Eve instrumentation definition", () => { - const setup = vi.fn(); - const instrumentation = braintrustEveInstrumentation({ - defineState, - setup, - }); - - expectTypeOf(instrumentation).toBeAny(); - expect(instrumentation).toMatchObject({ - recordInputs: false, - recordOutputs: false, - setup, - }); - expect( - Reflect.get(instrumentation, Symbol.for("eve.instrumentation.provider")), - ).toBe(true); - expect(typeof instrumentation.events?.["step.started"]).toBe("function"); - }); - - it("requires Eve's defineState API", () => { - expect(() => braintrustEveHook(undefined as never)).toThrow( - "braintrustEveHook requires Eve's defineState function", - ); - expect(() => braintrustEveInstrumentation(undefined as never)).toThrow( - "braintrustEveInstrumentation requires an options object", - ); - }); - - it("exports Eve APIs from root and instrumentation entrypoints", () => { - expect(braintrustExports.braintrustEveHook).toBe(braintrustEveHook); - expect(braintrustExports.braintrustEveInstrumentation).toBe( - braintrustEveInstrumentation, - ); - expect(instrumentationExports.braintrustEveHook).toBe(braintrustEveHook); - expect(instrumentationExports.braintrustEveInstrumentation).toBe( - braintrustEveInstrumentation, - ); - }); - - it("captures stable model input without provider options", async () => { - const fakeEve = createFakeDefineState(); - defineState = fakeEve.defineState; - const instrumentation = braintrustEveInstrumentation({ defineState }); - const wildcard = braintrustEveHook({ defineState }).events?.["*"]; - expect(wildcard).toBeDefined(); - - const ctx: EveHookContext = { - session: { id: "session-captured-input" }, - }; - const emit = (event: EveHandleMessageStreamEvent) => wildcard?.(event, ctx); - const modelInput = { - instructions: [ - { - content: "Answer with the relevant Eve instrumentation detail.", - providerOptions: { - openai: { reasoningEncryptedContent: "system-provider-secret" }, - }, - role: "system", - }, - { - content: "Keep the answer concise.", - role: "system", - }, - ], - messages: [ - { - content: [ - { - providerOptions: { - openai: { - itemId: "reasoning-item", - reasoningEncryptedContent: "must-not-be-logged", - }, - }, - text: "Readable reasoning", - type: "reasoning", - }, - { - input: { - providerOptions: { application: "tool-input" }, - reasoningEncryptedContent: "legitimate-tool-input", - }, - providerOptions: { - openai: { reasoningEncryptedContent: "tool-provider-secret" }, - }, - toolCallId: "call-1", - toolName: "lookup", - type: "tool-call", - }, - { - output: { - providerOptions: { - openai: { - reasoningEncryptedContent: "output-provider-secret", - }, - }, - type: "json", - value: { - reasoningEncryptedContent: "legitimate-tool-output", - }, - }, - providerOptions: { - openai: { - reasoningEncryptedContent: "result-provider-secret", - }, - }, - toolCallId: "call-1", - toolName: "lookup", - type: "tool-result", - }, - ], - providerOptions: { - openai: { reasoningEncryptedContent: "message-provider-secret" }, - }, - role: "assistant", - }, - ], - } as const; - - await emit({ - data: { - runtime: { - agentId: "agent-captured-input", - eveVersion: "0.22.1", - modelId: "dynamic:anthropic/claude-sonnet-5", - }, - }, - type: "session.started", - }); - await emit({ - data: { sequence: 0, turnId: "turn-captured-input" }, - type: "turn.started", - }); - instrumentation.events?.["step.started"]?.({ - modelInput, - session: { id: "session-captured-input" }, - step: { index: 0 }, - turn: { id: "turn-captured-input", sequence: 0 }, - }); - await emit({ - data: { sequence: 0, stepIndex: 0, turnId: "turn-captured-input" }, - type: "step.started", - }); - await emit({ - data: { - finishReason: "stop", - message: "Capture the model input.", - sequence: 0, - stepIndex: 0, - turnId: "turn-captured-input", - }, - type: "message.completed", - }); - await emit({ - data: { - finishReason: "stop", - sequence: 0, - stepIndex: 0, - turnId: "turn-captured-input", - }, - type: "step.completed", - }); - await emit({ - data: { sequence: 0, turnId: "turn-captured-input" }, - type: "turn.completed", - }); - - const spans = (await backgroundLogger.drain()) as Array< - Record - >; - const step = spans.find( - (span) => span.span_attributes?.name === "eve.step", - ); - const session = spans.find( - (span) => span.span_attributes?.name === "eve.session", - ); - const turn = spans.find( - (span) => span.span_attributes?.name === "eve.turn", - ); - for (const span of [turn, step]) { - expect(span?.context?.span_origin).toMatchObject({ - instrumentation: { name: "eve" }, - }); - } - expect(step?.input).toEqual([ - { - content: "Answer with the relevant Eve instrumentation detail.", - role: "system", - }, - { - content: "Keep the answer concise.", - role: "system", - }, - { - content: [ - { - text: "Readable reasoning", - type: "reasoning", - }, - { - input: { - providerOptions: { application: "tool-input" }, - reasoningEncryptedContent: "legitimate-tool-input", - }, - toolCallId: "call-1", - toolName: "lookup", - type: "tool-call", - }, - { - output: { - type: "json", - value: { - reasoningEncryptedContent: "legitimate-tool-output", - }, - }, - toolCallId: "call-1", - toolName: "lookup", - type: "tool-result", - }, - ], - role: "assistant", - }, - ]); - expect(JSON.stringify(step?.input)).not.toContain("provider-secret"); - expect(JSON.stringify(step?.input)).not.toContain("must-not-be-logged"); - expect(step?.metadata).toEqual({ - "eve.session_id": "session-captured-input", - }); - expect(session).toBeUndefined(); - expect(turn?.metadata).toEqual({ - "eve.session_id": "session-captured-input", - }); - expect(step?.metadata).not.toHaveProperty("model"); - expect(step?.metadata).not.toHaveProperty("provider"); - expect(turn?.metadata).not.toHaveProperty("model"); - expect(turn?.metadata).not.toHaveProperty("provider"); - expect(fakeEve.values.get("braintrust.eve.tracing")).toMatchObject({ - llmInputs: [], - }); - }); - - it("captures only runtime-validated model messages and content", () => { - expect( - capturedModelInput({ - instructions: [ - { content: "Keep this", role: "system" }, - { content: "Not an instruction", role: "user" }, - { content: 42, role: "system" }, - ], - messages: [ - null, - { content: "Ignore unknown roles", role: "future" }, - { content: 42, role: "user" }, - { - content: [ - { text: "Keep this text", type: "text" }, - { text: 42, type: "reasoning" }, - { payload: "ignore", type: "future-part" }, - { - input: { query: "eve" }, - providerExecuted: "yes", - toolCallId: "call-1", - toolName: "search", - type: "tool-call", - }, - ], - role: "assistant", - }, - ], - }), - ).toEqual([ - { content: "Keep this", role: "system" }, - { - content: [ - { text: "Keep this text", type: "text" }, - { - input: { query: "eve" }, - toolCallId: "call-1", - toolName: "search", - type: "tool-call", - }, - ], - role: "assistant", - }, - ]); - - const circular: Record = {}; - circular.self = circular; - expect( - capturedModelInput({ - messages: [ - { - content: [ - { - input: circular, - toolCallId: "call-circular", - toolName: "search", - type: "tool-call", - }, - ], - role: "assistant", - }, - ], - }), - ).toBeUndefined(); - }); - - it("skips missing or malformed Eve instrumentation state without throwing", async () => { - const instrumentation = braintrustEveInstrumentation({ defineState }); - expect(() => - instrumentation.events?.["step.started"]?.({ bad: true } as never), - ).not.toThrow(); - - const fakeEve = createFakeDefineState(); - defineState = fakeEve.defineState; - fakeEve.values.set("braintrust.eve.tracing", { - llmInputs: [{ input: { content: "not an array" }, key: "bad" }], - }); - expect(() => - instrumentation.events?.["step.started"]?.({ - modelInput: { - messages: [{ content: "hello", role: "user" }], - }, - session: { id: "session-malformed-state" }, - step: { index: 0 }, - turn: { id: "turn-malformed-state", sequence: 0 }, - }), - ).not.toThrow(); - - const wildcard = braintrustEveHook({ defineState }).events?.["*"]; - const ctx: EveHookContext = { session: { id: "session-no-state" } }; - await wildcard?.( - { - data: { sequence: 0, turnId: "turn-no-state" }, - type: "turn.started", - }, - ctx, - ); - await wildcard?.( - { - data: { sequence: 0, stepIndex: 0, turnId: "turn-no-state" }, - type: "step.started", - }, - ctx, - ); - await wildcard?.( - { - data: { - finishReason: "stop", - sequence: 0, - stepIndex: 0, - turnId: "turn-no-state", - }, - type: "step.completed", - }, - ctx, - ); - await wildcard?.( - { - data: { sequence: 0, turnId: "turn-no-state" }, - type: "turn.completed", - }, - ctx, - ); - - const spans = (await backgroundLogger.drain()) as Array< - Record - >; - const step = spans.find( - (span) => span.span_attributes?.name === "eve.step", - ); - expect(step?.input).toBeUndefined(); - }); - - it("bounds pre-existing durable trace state", async () => { - const fakeEve = createFakeDefineState(); - const oversizedEntryCount = 10_001; - fakeEve.values.set("braintrust.eve.tracing", { - llmInputs: [], - metadata: {}, - spanReferences: Array.from( - { length: oversizedEntryCount }, - (_, index) => ({ - exported: `exported-${index}`, - rootSpanId: `root-${index}`, - rowId: `row-${index}`, - spanId: `span-${index}`, - }), - ), - stepStarts: Array.from({ length: oversizedEntryCount }, (_, index) => ({ - open: false, - ordinal: index, - stepIndex: index, - turnId: `turn-${index}`, - })), - }); - const wildcard = braintrustEveHook({ - defineState: fakeEve.defineState, - }).events?.["*"]; - - await wildcard?.( - { - data: { - runtime: { - agentId: "agent-bounded-state", - eveVersion: "0.20.0", - modelId: "openai/gpt-5.4-mini", - }, - }, - type: "session.started", - }, - { session: { id: "session-bounded-state" } }, - ); - - const state = fakeEve.values.get("braintrust.eve.tracing") as { - spanReferences: unknown[]; - stepStarts: unknown[]; - }; - expect(state.spanReferences).toHaveLength(10_000); - expect(state.stepStarts).toHaveLength(10_000); - }); - - it("does not emit a span for session lifecycle metadata alone", async () => { - const wildcard = braintrustEveHook({ defineState }).events?.["*"]; - - await wildcard?.( - { - data: { - runtime: { - agentId: "agent-session-only", - eveVersion: "0.20.0", - modelId: "openai/gpt-5.4-mini", - }, - }, - type: "session.started", - }, - { session: { id: "session-only" } }, - ); - - expect(await backgroundLogger.drain()).toEqual([]); - }); - - it("records a flat Eve turn with session model metadata", async () => { - const wildcard = braintrustEveHook({ - defineState, - metadata: { - scenario: "eve-plugin-unit", - testRunId: "test-run-flat-tree", - }, - }).events?.["*"]; - expect(wildcard).toBeDefined(); - - const ctx: EveHookContext = { - session: { id: "session-flat-tree" }, - }; - const emit = (event: EveHandleMessageStreamEvent) => wildcard?.(event, ctx); - const expectedModelMetadata = { - model: "braintrust-eve-mock", - provider: "eve-mock", - }; - - await emit({ - data: { - runtime: { - agentId: "agent-id", - agentName: "eve-test-agent", - eveVersion: "0.20.0", - modelId: "eve-mock/braintrust-eve-mock", - }, - }, - meta: { at: "2026-01-01T00:00:00.000Z" }, - type: "session.started", - }); - await emit({ - data: { sequence: 0, turnId: "turn-flat-tree" }, - meta: { at: "2026-01-01T00:00:00.010Z" }, - type: "turn.started", - }); - await emit({ - data: { - message: "Search then read", - sequence: 0, - turnId: "turn-flat-tree", - }, - meta: { at: "2026-01-01T00:00:00.020Z" }, - type: "message.received", - }); - await emit({ - data: { sequence: 0, stepIndex: 0, turnId: "turn-flat-tree" }, - meta: { at: "2026-01-01T00:00:00.030Z" }, - type: "step.started", - }); - await emit({ - data: { - actions: [ - { - callId: "call-search", - input: { query: "Eve instrumentation" }, - kind: "tool-call", - toolName: "search", - }, - ], - sequence: 0, - stepIndex: 0, - turnId: "turn-flat-tree", - }, - meta: { at: "2026-01-01T00:00:00.040Z" }, - type: "actions.requested", - }); - await emit({ - data: { - error: undefined, - result: { - callId: "call-search", - kind: "tool-result", - output: { hits: ["eve.dev/docs"] }, - toolName: "search", - }, - sequence: 0, - status: "completed", - stepIndex: 0, - turnId: "turn-flat-tree", - }, - meta: { at: "2026-01-01T00:00:00.050Z" }, - type: "action.result", - }); - await emit({ - data: { - finishReason: "tool-calls", - sequence: 0, - stepIndex: 0, - turnId: "turn-flat-tree", - usage: { - cacheReadTokens: 3, - cacheWriteTokens: 2, - costUsd: 0.001, - inputTokens: 10, - outputTokens: 5, - }, - }, - meta: { at: "2026-01-01T00:00:00.060Z" }, - type: "step.completed", - }); - await emit({ - data: { sequence: 0, stepIndex: 1, turnId: "turn-flat-tree" }, - meta: { at: "2026-01-01T00:00:00.070Z" }, - type: "step.started", - }); - await emit({ - data: { - finishReason: "stop", - message: "Here is the Eve instrumentation guide.", - sequence: 0, - stepIndex: 1, - turnId: "turn-flat-tree", - }, - meta: { at: "2026-01-01T00:00:00.080Z" }, - type: "message.completed", - }); - await emit({ - data: { - finishReason: "stop", - sequence: 0, - stepIndex: 1, - turnId: "turn-flat-tree", - usage: { - inputTokens: 20, - outputTokens: 8, - }, - }, - meta: { at: "2026-01-01T00:00:00.090Z" }, - type: "step.completed", - }); - await emit({ - data: { sequence: 0, turnId: "turn-flat-tree" }, - meta: { at: "2026-01-01T00:00:00.100Z" }, - type: "turn.completed", - }); - - const spans = (await backgroundLogger.drain()) as Array< - Record - >; - const root = spans.find( - (span) => span.span_attributes?.name === "eve.turn", - ); - const steps = spans.filter((span) => - String(span.span_attributes?.name).startsWith("eve.step"), - ); - const tool = spans.find((span) => span.span_attributes?.name === "search"); - - expect(spans.map((span) => span.span_attributes?.name)).toEqual([ - "eve.turn", - "eve.step", - "search", - "eve.step", - ]); - expect(root).toMatchObject({ - input: [{ content: "Search then read", role: "user" }], - metadata: { - ...expectedModelMetadata, - "eve.session_id": "session-flat-tree", - scenario: "eve-plugin-unit", - testRunId: "test-run-flat-tree", - }, - metrics: { - completion_tokens: 13, - estimated_cost: 0.001, - prompt_cached_tokens: 3, - prompt_cache_creation_tokens: 2, - prompt_tokens: 30, - tokens: 43, - }, - output: "Here is the Eve instrumentation guide.", - span_attributes: { - name: "eve.turn", - type: "task", - }, - root_span_id: deterministicEveIdForTest( - "eve:root", - "session-flat-tree", - "turn-flat-tree", - ), - span_parents: [], - }); - expect(steps).toHaveLength(2); - expect(steps.map((span) => span.span_attributes?.name)).toEqual([ - "eve.step", - "eve.step", - ]); - expect(steps.map((span) => span.span_attributes?.type)).toEqual([ - "llm", - "llm", - ]); - expect(steps.map((span) => span.span_parents)).toEqual([ - [root?.span_id], - [root?.span_id], - ]); - for (const step of steps) { - expect(step.metadata).toEqual({ - ...expectedModelMetadata, - "eve.session_id": "session-flat-tree", - scenario: "eve-plugin-unit", - testRunId: "test-run-flat-tree", - }); - } - expect(steps[0]?.input).toBeUndefined(); - expect(steps[1]?.input).toBeUndefined(); - expect(tool).toMatchObject({ - input: { query: "Eve instrumentation" }, - metadata: { - "eve.session_id": "session-flat-tree", - scenario: "eve-plugin-unit", - testRunId: "test-run-flat-tree", - }, - output: { hits: ["eve.dev/docs"] }, - span_attributes: { - name: "search", - type: "tool", - }, - span_parents: [root?.span_id], - }); - expect(tool?.metadata).not.toHaveProperty("model"); - expect(tool?.metadata).not.toHaveProperty("provider"); - expect(steps[0]?.output).toMatchObject([ - { - finish_reason: "tool_calls", - message: { - tool_calls: [ - { - function: { - arguments: JSON.stringify({ query: "Eve instrumentation" }), - name: "search", - }, - id: "call-search", - type: "function", - }, - ], - }, - }, - ]); - expect(steps[1]?.output).toMatchObject([ - { - finish_reason: "stop", - message: { - content: "Here is the Eve instrumentation guide.", - role: "assistant", - }, - }, - ]); - }); - - it("records each user message as a separate turn in one session", async () => { - const wildcard = braintrustEveHook({ defineState }).events?.["*"]; - expect(wildcard).toBeDefined(); - - const ctx: EveHookContext = { session: { id: "session-multi-turn" } }; - for (const [sequence, message] of [ - [0, "First user message"], - [1, "Second user message"], - ] as const) { - const turnId = `turn-${sequence}`; - await wildcard?.( - { data: { sequence, turnId }, type: "turn.started" }, - ctx, - ); - await wildcard?.( - { data: { message, sequence, turnId }, type: "message.received" }, - ctx, - ); - await wildcard?.( - { data: { sequence, turnId }, type: "turn.completed" }, - ctx, - ); - } - - const spans = (await backgroundLogger.drain()) as Array< - Record - >; - const turns = spans.filter( - (span) => span.span_attributes?.name === "eve.turn", - ); - - expect( - spans.some((span) => span.span_attributes?.name === "eve.session"), - ).toBe(false); - expect(turns).toHaveLength(2); - expect(turns.map((turn) => turn.span_parents)).toEqual([[], []]); - expect(turns.map((turn) => turn.root_span_id)).toEqual([ - deterministicEveIdForTest("eve:root", "session-multi-turn", "turn-0"), - deterministicEveIdForTest("eve:root", "session-multi-turn", "turn-1"), - ]); - expect(turns[0]?.root_span_id).not.toBe(turns[1]?.root_span_id); - expect(turns.map((turn) => turn.input)).toEqual([ - [{ content: "First user message", role: "user" }], - [{ content: "Second user message", role: "user" }], - ]); - }); - - it("merges incremental tool-call batches without reconstructing later LLM inputs", async () => { - const wildcard = braintrustEveHook({ defineState }).events?.["*"]; - expect(wildcard).toBeDefined(); - - const ctx: EveHookContext = { - session: { id: "session-incremental-tools" }, - }; - const emit = (event: EveHandleMessageStreamEvent) => wildcard?.(event, ctx); - - await emit({ - data: { sequence: 0, turnId: "turn-incremental-tools" }, - type: "turn.started", - }); - await emit({ - data: { - message: "Search then read", - sequence: 0, - turnId: "turn-incremental-tools", - }, - type: "message.received", - }); - await emit({ - data: { sequence: 0, stepIndex: 0, turnId: "turn-incremental-tools" }, - type: "step.started", - }); - await emit({ - data: { - actions: [ - { - callId: "call-search", - input: { query: "Eve instrumentation" }, - kind: "tool-call", - toolName: "search", - }, - ], - sequence: 0, - stepIndex: 0, - turnId: "turn-incremental-tools", - }, - type: "actions.requested", - }); - await emit({ - data: { - actions: [ - { - callId: "call-search", - input: { query: "Updated Eve instrumentation" }, - kind: "tool-call", - toolName: "search", - }, - ], - sequence: 0, - stepIndex: 0, - turnId: "turn-incremental-tools", - }, - type: "actions.requested", - }); - await emit({ - data: { - actions: [ - { - callId: "call-read", - input: { url: "https://eve.dev/docs/guides/instrumentation" }, - kind: "tool-call", - toolName: "read", - }, - ], - sequence: 0, - stepIndex: 0, - turnId: "turn-incremental-tools", - }, - type: "actions.requested", - }); - await emit({ - data: { - result: { - callId: "call-search", - kind: "tool-result", - output: { url: "https://eve.dev/docs/guides/instrumentation" }, - toolName: "search", - }, - sequence: 0, - status: "completed", - stepIndex: 0, - turnId: "turn-incremental-tools", - }, - type: "action.result", - }); - await emit({ - data: { - result: { - callId: "call-read", - kind: "tool-result", - output: { excerpt: "Eve hooks expose runtime stream events." }, - toolName: "read", - }, - sequence: 0, - status: "completed", - stepIndex: 0, - turnId: "turn-incremental-tools", - }, - type: "action.result", - }); - await emit({ - data: { - finishReason: "tool-calls", - sequence: 0, - stepIndex: 0, - turnId: "turn-incremental-tools", - }, - type: "step.completed", - }); - await emit({ - data: { sequence: 0, stepIndex: 1, turnId: "turn-incremental-tools" }, - type: "step.started", - }); - await emit({ - data: { - finishReason: "stop", - message: "Done.", - sequence: 0, - stepIndex: 1, - turnId: "turn-incremental-tools", - }, - type: "message.completed", - }); - await emit({ - data: { - finishReason: "stop", - sequence: 0, - stepIndex: 1, - turnId: "turn-incremental-tools", - }, - type: "step.completed", - }); - await emit({ - data: { sequence: 0, turnId: "turn-incremental-tools" }, - type: "turn.completed", - }); - - const spans = (await backgroundLogger.drain()) as Array< - Record - >; - const steps = spans.filter((span) => - String(span.span_attributes?.name).startsWith("eve.step"), - ); - - expect(steps[0]?.output).toMatchObject([ - { - finish_reason: "tool_calls", - message: { - tool_calls: [ - { - function: { - arguments: JSON.stringify({ - query: "Updated Eve instrumentation", - }), - name: "search", - }, - id: "call-search", - type: "function", - }, - { - function: { - arguments: JSON.stringify({ - url: "https://eve.dev/docs/guides/instrumentation", - }), - name: "read", - }, - id: "call-read", - type: "function", - }, - ], - }, - }, - ]); - expect(steps[1]?.input).toBeUndefined(); - }); - - it("preserves reasoning across tool, text, and structured outputs", async () => { - const wildcard = braintrustEveHook({ defineState }).events?.["*"]; - const ctx: EveHookContext = { - session: { id: "session-reasoning-outputs" }, - }; - const emit = (event: EveHandleMessageStreamEvent) => wildcard?.(event, ctx); - const turnId = "turn-reasoning-outputs"; - - await emit({ data: { sequence: 0, turnId }, type: "turn.started" }); - - await emit({ - data: { sequence: 0, stepIndex: 0, turnId }, - type: "step.started", - }); - await emit({ - data: { - reasoning: "I should inspect the available tools.", - sequence: 0, - stepIndex: 0, - turnId, - }, - meta: { at: "2026-01-01T00:00:00.010Z" }, - type: "reasoning.completed", - }); - await emit({ - data: { - finishReason: "tool-calls", - message: null, - sequence: 0, - stepIndex: 0, - turnId, - }, - type: "message.completed", - }); - await emit({ - data: { - actions: [ - { - callId: "call-reasoning-search", - input: { query: "Eve reasoning" }, - kind: "tool-call", - toolName: "search", - }, - ], - sequence: 0, - stepIndex: 0, - turnId, - }, - type: "actions.requested", - }); - await emit({ - data: { finishReason: "tool-calls", sequence: 0, stepIndex: 0, turnId }, - type: "step.completed", - }); - - await emit({ - data: { sequence: 0, stepIndex: 1, turnId }, - type: "step.started", - }); - await emit({ - data: { - reasoning: "The tool returned the relevant guide.", - sequence: 0, - stepIndex: 1, - turnId, - }, - meta: { at: "2026-01-01T00:00:00.020Z" }, - type: "reasoning.completed", - }); - await emit({ - data: { - reasoning: "I can now answer concisely.", - sequence: 0, - stepIndex: 1, - turnId, - }, - meta: { at: "2026-01-01T00:00:00.030Z" }, - type: "reasoning.completed", - }); - await emit({ - data: { - finishReason: "stop", - message: "Eve exposes completed reasoning events.", - sequence: 0, - stepIndex: 1, - turnId, - }, - type: "message.completed", - }); - await emit({ - data: { finishReason: "stop", sequence: 0, stepIndex: 1, turnId }, - type: "step.completed", - }); - - await emit({ - data: { sequence: 0, stepIndex: 2, turnId }, - type: "step.started", - }); - await emit({ - data: { - reasoning: "The response must match the requested schema.", - sequence: 0, - stepIndex: 2, - turnId, - }, - meta: { at: "2026-01-01T00:00:00.040Z" }, - type: "reasoning.completed", - }); - await emit({ - data: { - result: { supported: true }, - sequence: 0, - stepIndex: 2, - turnId, - }, - type: "result.completed", - }); - await emit({ - data: { finishReason: "stop", sequence: 0, stepIndex: 2, turnId }, - type: "step.completed", - }); - await emit({ data: { sequence: 0, turnId }, type: "turn.completed" }); - - const spans = (await backgroundLogger.drain()) as Array< - Record - >; - const steps = spans.filter( - (span) => span.span_attributes?.name === "eve.step", - ); - expect(steps).toHaveLength(3); - expect(steps[0]?.output).toMatchObject([ - { - finish_reason: "tool_calls", - message: { - content: null, - reasoning: [{ content: "I should inspect the available tools." }], - tool_calls: [ - { - function: { name: "search" }, - id: "call-reasoning-search", - type: "function", - }, - ], - }, - }, - ]); - expect(steps[1]?.output).toMatchObject([ - { - finish_reason: "stop", - message: { - content: "Eve exposes completed reasoning events.", - reasoning: [ - { content: "The tool returned the relevant guide." }, - { content: "I can now answer concisely." }, - ], - }, - }, - ]); - expect(steps[2]?.output).toMatchObject([ - { - finish_reason: "stop", - message: { - content: { supported: true }, - reasoning: [ - { content: "The response must match the requested schema." }, - ], - }, - }, - ]); - }); - - it("rehydrates reasoning idempotently across Vercel Workflow steps", async () => { - const eveState = createFakeDefineState(); - const ctx: EveHookContext = { - session: { id: "session-reasoning-replay" }, - }; - const turnId = "turn-reasoning-replay"; - const reasoningEvent = { - data: { - reasoning: "Use the search tool before answering.", - sequence: 0, - stepIndex: 0, - turnId, - }, - meta: { at: "2026-01-01T00:00:00.020Z" }, - type: "reasoning.completed", - } as const satisfies EveHandleMessageStreamEvent; - const initialHook = braintrustEveHook({ - defineState: eveState.defineState, - }).events?.["*"]; - - await initialHook?.( - { - data: { sequence: 0, turnId }, - meta: { at: "2026-01-01T00:00:00.000Z" }, - type: "turn.started", - }, - ctx, - ); - await initialHook?.( - { - data: { sequence: 0, stepIndex: 0, turnId }, - meta: { at: "2026-01-01T00:00:00.010Z" }, - type: "step.started", - }, - ctx, - ); - await initialHook?.(reasoningEvent, ctx); - - const initialWrites = (await backgroundLogger.drain()) as Array< - Record & { id: string } - >; - expect( - initialWrites - .filter((span) => span.span_attributes?.name) - .every( - (span) => span.context?.span_origin?.instrumentation?.name === "eve", - ), - ).toBe(true); - for (const [key, value] of eveState.values) { - eveState.values.set(key, JSON.parse(JSON.stringify(value))); - } - - const resumedHook = braintrustEveHook({ - defineState: eveState.defineState, - }).events?.["*"]; - await resumedHook?.( - { - data: { sequence: 0, turnId }, - meta: { at: "2026-01-01T00:00:00.000Z" }, - type: "turn.started", - }, - ctx, - ); - await resumedHook?.( - { - data: { sequence: 0, stepIndex: 0, turnId }, - meta: { at: "2026-01-01T00:00:00.010Z" }, - type: "step.started", - }, - ctx, - ); - const resumedStartWrites = (await backgroundLogger.drain()) as Array< - Record & { id: string } - >; - await resumedHook?.(reasoningEvent, ctx); - await resumedHook?.( - { - data: { - finishReason: "tool-calls", - message: null, - sequence: 0, - stepIndex: 0, - turnId, - }, - type: "message.completed", - }, - ctx, - ); - await resumedHook?.( - { - data: { - actions: [ - { - callId: "call-replayed-search", - input: { query: "Vercel Workflow reasoning" }, - kind: "tool-call", - toolName: "search", - }, - ], - sequence: 0, - stepIndex: 0, - turnId, - }, - type: "actions.requested", - }, - ctx, - ); - const resumedIntermediateWrites = (await backgroundLogger.drain()) as Array< - Record & { id: string } - >; - await resumedHook?.( - { - data: { finishReason: "tool-calls", sequence: 0, stepIndex: 0, turnId }, - type: "step.completed", - }, - ctx, - ); - await resumedHook?.( - { data: { sequence: 0, turnId }, type: "turn.completed" }, - ctx, - ); - - const resumedWrites = (await backgroundLogger.drain()) as Array< - Record & { id: string } - >; - const allWrites = [ - ...initialWrites, - ...resumedStartWrites, - ...resumedIntermediateWrites, - ...resumedWrites, - ]; - const stepRowId = deterministicEveIdForTest( - "eve:row:step", - "session-reasoning-replay", - turnId, - "0", - ); - expect( - allWrites.filter( - (write) => write.id === stepRowId && write.output !== undefined, - ), - ).toHaveLength(1); - - const spans = mergeRowBatch([...allWrites].reverse()); - const steps = spans.filter( - (span) => span.span_attributes?.name === "eve.step", - ); - expect(steps).toHaveLength(1); - expect(steps[0]?.span_id).toBe( - deterministicEveIdForTest( - "eve:step", - "session-reasoning-replay", - turnId, - "0", - ), - ); - expect(steps[0]?.output?.[0]?.message).toMatchObject({ - content: null, - reasoning: [{ content: "Use the search tool before answering." }], - tool_calls: [ - { - function: { name: "search" }, - id: "call-replayed-search", - type: "function", - }, - ], - }); - expect(steps[0]?.output?.[0]?.message?.reasoning).toHaveLength(1); - expect(eveState.values.get("braintrust.eve.tracing")).toMatchObject({ - reasoningBlocks: [], - stepStarts: [], - }); - }); - - it("merges late tool results into tool spans closed by turn completion", async () => { - const eveState = createFakeDefineState(); - const wildcard = braintrustEveHook({ - defineState: eveState.defineState, - metadata: { - scenario: "eve-plugin-unit", - testRunId: "test-run-late-tool-result", - }, - }).events?.["*"]; - expect(wildcard).toBeDefined(); - - const ctx: EveHookContext = { - session: { id: "session-late-tool-result" }, - }; - const emit = (event: EveHandleMessageStreamEvent) => wildcard?.(event, ctx); - - await emit({ - data: { sequence: 0, turnId: "turn-late-tool-result" }, - meta: { at: "2026-01-01T00:00:00.000Z" }, - type: "turn.started", - }); - await emit({ - data: { sequence: 0, stepIndex: 0, turnId: "turn-late-tool-result" }, - meta: { at: "2026-01-01T00:00:00.010Z" }, - type: "step.started", - }); - await emit({ - data: { - actions: [ - { - callId: "call-late-search", - input: { query: "late tool result" }, - kind: "tool-call", - toolName: "search", - }, - ], - sequence: 0, - stepIndex: 0, - turnId: "turn-late-tool-result", - }, - meta: { at: "2026-01-01T00:00:00.020Z" }, - type: "actions.requested", - }); - await emit({ - data: { sequence: 0, turnId: "turn-late-tool-result" }, - meta: { at: "2026-01-01T00:00:00.030Z" }, - type: "turn.completed", - }); - expect(eveState.values.get("braintrust.eve.tracing")).toMatchObject({ - spanReferences: expect.arrayContaining([ - expect.objectContaining({ - exported: expect.any(String), - rootSpanId: deterministicEveIdForTest( - "eve:root", - "session-late-tool-result", - "turn-late-tool-result", - ), - rowId: deterministicEveIdForTest( - "eve:row:tool", - "session-late-tool-result", - "turn-late-tool-result", - "call-late-search", - ), - spanId: deterministicEveIdForTest( - "eve:tool", - "session-late-tool-result", - "turn-late-tool-result", - "call-late-search", - ), - }), - ]), - stepStarts: [], - }); - const initialWrites = (await backgroundLogger.drain()) as Array< - Record & { id: string } - >; - const resumedWildcard = braintrustEveHook({ - defineState: eveState.defineState, - metadata: { - scenario: "eve-plugin-unit", - testRunId: "test-run-late-tool-result", - }, - }).events?.["*"]; - const flushSpy = vi.spyOn(backgroundLogger, "flush"); - flushSpy.mockClear(); - await resumedWildcard?.( - { - data: { - result: { - callId: "call-late-search", - kind: "tool-result", - output: { title: "Late result" }, - toolName: "search", - }, - sequence: 0, - status: "completed", - stepIndex: 0, - turnId: "turn-late-tool-result", - }, - meta: { at: "2026-01-01T00:00:00.040Z" }, - type: "action.result", - }, - ctx, - ); - expect(flushSpy).not.toHaveBeenCalled(); - - const resumedWrites = (await backgroundLogger.drain()) as Array< - Record & { id: string } - >; - expect( - [...initialWrites, ...resumedWrites].every( - (span) => span._is_merge === true, - ), - ).toBe(true); - - // The backend may ingest separate workflow uploads out of order. Because - // every write is a merge, a delayed initial write cannot erase the result. - const spans = mergeRowBatch([...resumedWrites, ...initialWrites]); - const turns = spans.filter( - (span) => span.span_attributes?.name === "eve.turn", - ); - const tool = spans.find((span) => span.span_attributes?.name === "search"); - - expect(turns).toHaveLength(1); - expect( - spans.filter((span) => span.span_attributes?.name === "search"), - ).toHaveLength(1); - expect(tool).toMatchObject({ - input: { query: "late tool result" }, - metadata: { - scenario: "eve-plugin-unit", - testRunId: "test-run-late-tool-result", - }, - output: { title: "Late result" }, - span_attributes: { - name: "search", - type: "tool", - }, - span_parents: [turns[0]?.span_id], - }); - expect(tool?.metrics?.end).toEqual(expect.any(Number)); - }); - - it("lets action results complete sparse subagent events across workflow steps", async () => { - const eveState = createFakeDefineState(); - const ctx: EveHookContext = { - session: { id: "session-sparse-subagent" }, - }; - const firstWildcard = braintrustEveHook({ - defineState: eveState.defineState, - }).events?.["*"]; - - await firstWildcard?.( - { - data: { sequence: 0, turnId: "turn-sparse-subagent" }, - type: "turn.started", - }, - ctx, - ); - await firstWildcard?.( - { - data: { - actions: [ - { - callId: "call-sparse-subagent", - input: { message: "Research Eve" }, - kind: "subagent-call", - subagentName: "researcher", - }, - ], - sequence: 0, - stepIndex: 0, - turnId: "turn-sparse-subagent", - }, - type: "actions.requested", - }, - ctx, - ); - await firstWildcard?.( - { - data: { - callId: "call-sparse-subagent", - sequence: 0, - subagentName: "researcher", - turnId: "turn-sparse-subagent", - }, - meta: { at: "2026-01-01T00:00:00.100Z" }, - type: "subagent.completed", - }, - ctx, - ); - - const resumedWildcard = braintrustEveHook({ - defineState: eveState.defineState, - }).events?.["*"]; - await resumedWildcard?.( - { - data: { - result: { - callId: "call-sparse-subagent", - kind: "subagent-result", - output: { answer: "Authoritative result" }, - subagentName: "researcher", - }, - sequence: 0, - status: "completed", - stepIndex: 0, - turnId: "turn-sparse-subagent", - }, - meta: { at: "2026-01-01T00:00:00.500Z" }, - type: "action.result", - }, - ctx, - ); - - const spans = (await backgroundLogger.drain()) as Array< - Record - >; - const subagents = spans.filter( - (span) => span.span_attributes?.name === "researcher", - ); - expect(subagents).toHaveLength(1); - expect(subagents[0]).toMatchObject({ - output: { answer: "Authoritative result" }, - metrics: { end: Date.parse("2026-01-01T00:00:00.100Z") / 1000 }, - span_attributes: { type: "tool" }, - }); - }); - - it("evicts tracing state after session completion", async () => { - const fakeEve = createFakeDefineState(); - defineState = fakeEve.defineState; - const wildcard = braintrustEveHook({ - defineState, - metadata: { - scenario: "eve-plugin-unit", - testRunId: "test-run-late-after-session", - }, - }).events?.["*"]; - expect(wildcard).toBeDefined(); - - const ctx: EveHookContext = { - session: { id: "session-late-after-session" }, - }; - const emit = (event: EveHandleMessageStreamEvent) => wildcard?.(event, ctx); - - await emit({ - data: { sequence: 0, turnId: "turn-late-after-session" }, - meta: { at: "2026-01-01T00:00:00.000Z" }, - type: "turn.started", - }); - await emit({ - data: { sequence: 0, stepIndex: 0, turnId: "turn-late-after-session" }, - meta: { at: "2026-01-01T00:00:00.010Z" }, - type: "step.started", - }); - await emit({ - data: { - actions: [ - { - callId: "call-after-session", - input: { query: "after session" }, - kind: "tool-call", - toolName: "search", - }, - ], - sequence: 0, - stepIndex: 0, - turnId: "turn-late-after-session", - }, - meta: { at: "2026-01-01T00:00:00.020Z" }, - type: "actions.requested", - }); - await emit({ - meta: { at: "2026-01-01T00:00:00.030Z" }, - type: "session.completed", - }); - - const spans = (await backgroundLogger.drain()) as Array< - Record - >; - const tool = spans.find((span) => span.span_attributes?.name === "search"); - expect(tool).toMatchObject({ - input: { query: "after session" }, - }); - expect(tool?.metrics?.end).toEqual(expect.any(Number)); - expect(fakeEve.values.get("braintrust.eve.tracing")).toEqual({ - llmInputs: [], - metadata: {}, - reasoningBlocks: [], - spanReferences: [], - stepStarts: [], - }); - }); - - it("records result-only tool events without invented input", async () => { - const wildcard = braintrustEveHook({ - defineState, - metadata: { - scenario: "eve-plugin-unit", - testRunId: "test-run-result-only", - }, - }).events?.["*"]; - expect(wildcard).toBeDefined(); - - const ctx: EveHookContext = { - session: { id: "session-result-only" }, - }; - const emit = (event: EveHandleMessageStreamEvent) => wildcard?.(event, ctx); - - await emit({ - data: { - result: { - callId: "call-result-only", - kind: "tool-result", - output: { title: "Result only" }, - toolName: "search", - }, - sequence: 0, - status: "completed", - stepIndex: 0, - turnId: "turn-result-only", - }, - type: "action.result", - }); - await emit({ - data: { sequence: 0, turnId: "turn-result-only" }, - type: "turn.completed", - }); - - const spans = (await backgroundLogger.drain()) as Array< - Record - >; - const tool = spans.find((span) => span.span_attributes?.name === "search"); - expect(tool).toMatchObject({ - output: { title: "Result only" }, - span_attributes: { name: "search", type: "tool" }, - }); - expect(tool?.input).toBeUndefined(); - }); - - it("evicts tracing state after session failure", async () => { - const fakeEve = createFakeDefineState(); - const wildcard = braintrustEveHook({ - defineState: fakeEve.defineState, - }).events?.["*"]; - const ctx: EveHookContext = { - session: { id: "session-failed-cleanup" }, - }; - - await wildcard?.( - { - data: { sequence: 0, turnId: "turn-failed-cleanup" }, - type: "turn.started", - }, - ctx, - ); - await wildcard?.( - { - data: { - sequence: 0, - stepIndex: 0, - turnId: "turn-failed-cleanup", - }, - type: "step.started", - }, - ctx, - ); - await wildcard?.( - { - data: { - code: "session_failed", - message: "Session failed", - sessionId: "session-failed-cleanup", - }, - type: "session.failed", - }, - ctx, - ); - - expect(fakeEve.values.get("braintrust.eve.tracing")).toEqual({ - llmInputs: [], - metadata: {}, - reasoningBlocks: [], - spanReferences: [], - stepStarts: [], - }); - }); - - it("flushes final session events but not ordinary or ignored events", async () => { - const wildcard = braintrustEveHook({ defineState }).events?.["*"]; - const ctx: EveHookContext = { - session: { id: "session-selective-flush" }, - }; - - await wildcard?.( - { - data: { sequence: 0, turnId: "turn-selective-flush" }, - type: "turn.started", - }, - ctx, - ); - const flushSpy = vi - .spyOn(backgroundLogger, "flush") - .mockResolvedValue(undefined); - - await wildcard?.( - { - data: { wait: "next-user-message" }, - type: "session.waiting", - }, - ctx, - ); - expect(flushSpy).not.toHaveBeenCalled(); - - await wildcard?.( - { - data: { - finishReason: "stop", - message: null, - sequence: 0, - stepIndex: 0, - turnId: "turn-selective-flush", - }, - type: "message.completed", - }, - ctx, - ); - expect(flushSpy).not.toHaveBeenCalled(); - - await wildcard?.( - { - type: "session.completed", - }, - ctx, - ); - expect(flushSpy).toHaveBeenCalledTimes(1); - }); - - it("serializes events per session without blocking other sessions", async () => { - const wildcard = braintrustEveHook({ defineState }).events?.["*"]; - expect(wildcard).toBeDefined(); - - const sessionA: EveHookContext = { session: { id: "session-queue-a" } }; - const sessionB: EveHookContext = { session: { id: "session-queue-b" } }; - const emitA = (event: EveHandleMessageStreamEvent) => - wildcard?.(event, sessionA); - const emitB = (event: EveHandleMessageStreamEvent) => - wildcard?.(event, sessionB); - - await emitA({ - data: { sequence: 0, turnId: "turn-a" }, - type: "turn.started", - }); - await emitB({ - data: { sequence: 0, turnId: "turn-b" }, - type: "turn.started", - }); - - let releaseFirstFlush: (() => void) | undefined; - const firstFlush = new Promise((resolve) => { - releaseFirstFlush = resolve; - }); - const flushSpy = vi - .spyOn(backgroundLogger, "flush") - .mockImplementationOnce(() => firstFlush) - .mockResolvedValue(undefined); - - const doneA = emitA({ - type: "session.completed", - }); - for (let i = 0; i < 10 && flushSpy.mock.calls.length < 1; i++) { - await new Promise((resolve) => setTimeout(resolve, 0)); - } - expect(flushSpy).toHaveBeenCalledTimes(1); - - let queuedEventFinished = false; - const queuedA = Promise.resolve( - emitA({ - data: { - finishReason: "stop", - message: "queued", - sequence: 0, - stepIndex: 0, - turnId: "turn-a", - }, - type: "message.completed", - }), - ).then(() => { - queuedEventFinished = true; - }); - await Promise.resolve(); - expect(queuedEventFinished).toBe(false); - - await emitB({ - data: { sequence: 0, turnId: "turn-b" }, - type: "turn.completed", - }); - expect(queuedEventFinished).toBe(false); - - releaseFirstFlush?.(); - await Promise.all([doneA, queuedA]); - expect(queuedEventFinished).toBe(true); - }); - - it("uses deterministic ids and attaches local subagent turns to their tool span", async () => { - const parentEveState = createFakeDefineState(); - const childEveState = createFakeDefineState(); - const parentWildcard = braintrustEveHook({ - defineState: parentEveState.defineState, - }).events?.["*"]; - const childWildcard = braintrustEveHook({ - defineState: childEveState.defineState, - }).events?.["*"]; - expect(parentWildcard).toBeDefined(); - expect(childWildcard).toBeDefined(); - - const parentCtx: EveHookContext = { - session: { id: "session-parent" }, - }; - const childCtx: EveHookContext = { - session: { - id: "session-child", - parent: { - callId: "call-researcher", - sessionId: "session-parent", - turn: { id: "turn-parent" }, - }, - }, - }; - const emitParent = (event: EveHandleMessageStreamEvent) => - parentWildcard?.(event, parentCtx); - const emitChild = (event: EveHandleMessageStreamEvent) => - childWildcard?.(event, childCtx); - - await emitParent({ - data: { sequence: 0, turnId: "turn-parent" }, - type: "turn.started", - }); - await emitParent({ - data: { - message: "Research Eve tracing", - sequence: 0, - turnId: "turn-parent", - }, - type: "message.received", - }); - await emitParent({ - data: { sequence: 0, stepIndex: 0, turnId: "turn-parent" }, - type: "step.started", - }); - await emitParent({ - data: { - actions: [ - { - callId: "call-researcher", - input: { message: "Find the relevant section" }, - kind: "subagent-call", - name: "researcher", - subagentName: "researcher", - }, - ], - sequence: 0, - stepIndex: 0, - turnId: "turn-parent", - }, - type: "actions.requested", - }); - await emitParent({ - data: { - callId: "call-researcher", - childSessionId: "session-child", - name: "researcher", - sequence: 0, - toolName: "researcher", - turnId: "turn-parent", - }, - type: "subagent.called", - }); - - await emitChild({ - data: { sequence: 0, turnId: "turn-child" }, - type: "turn.started", - }); - await emitChild({ - data: { - message: "Find the relevant section", - sequence: 0, - turnId: "turn-child", - }, - type: "message.received", - }); - await emitChild({ - data: { sequence: 0, stepIndex: 0, turnId: "turn-child" }, - type: "step.started", - }); - await emitChild({ - data: { - actions: [ - { - callId: "call-search", - input: { query: "nested eve" }, - kind: "tool-call", - toolName: "search", - }, - ], - sequence: 0, - stepIndex: 1, - turnId: "turn-child", - }, - type: "actions.requested", - }); - await emitChild({ - data: { - result: { - callId: "call-search", - kind: "tool-result", - output: { title: "Nested Eve" }, - toolName: "search", - }, - sequence: 0, - status: "completed", - stepIndex: 1, - turnId: "turn-child", - }, - type: "action.result", - }); - await emitChild({ - data: { - finishReason: "stop", - message: "Child found Nested Eve.", - sequence: 0, - stepIndex: 0, - turnId: "turn-child", - }, - type: "message.completed", - }); - await emitChild({ - data: { - finishReason: "stop", - sequence: 0, - stepIndex: 0, - turnId: "turn-child", - }, - type: "step.completed", - }); - await emitChild({ - data: { sequence: 0, turnId: "turn-child" }, - type: "turn.completed", - }); - - await emitParent({ - data: { - callId: "call-researcher", - output: "Child found Nested Eve.", - sequence: 0, - status: "completed", - subagentName: "researcher", - turnId: "turn-parent", - }, - type: "subagent.completed", - }); - await emitParent({ - data: { - result: { - callId: "call-researcher", - kind: "subagent-result", - output: "Child found Nested Eve.", - subagentName: "researcher", - }, - sequence: 0, - status: "completed", - stepIndex: 0, - turnId: "turn-parent", - }, - type: "action.result", - }); - await emitParent({ - data: { sequence: 0, stepIndex: 1, turnId: "turn-parent" }, - type: "step.started", - }); - await emitParent({ - data: { - actions: [ - { - callId: "call-read", - input: { url: "https://eve.dev/docs/guides/instrumentation" }, - kind: "tool-call", - toolName: "read", - }, - ], - sequence: 0, - stepIndex: 1, - turnId: "turn-parent", - }, - type: "actions.requested", - }); - await emitParent({ - data: { - result: { - callId: "call-read", - kind: "tool-result", - output: { title: "Runtime context" }, - toolName: "read", - }, - sequence: 0, - status: "completed", - stepIndex: 1, - turnId: "turn-parent", - }, - type: "action.result", - }); - await emitParent({ - data: { - finishReason: "tool-calls", - sequence: 0, - stepIndex: 1, - turnId: "turn-parent", - }, - type: "step.completed", - }); - await emitParent({ - data: { sequence: 0, stepIndex: 2, turnId: "turn-parent" }, - type: "step.started", - }); - await emitParent({ - data: { - finishReason: "stop", - message: "Parent used the child result.", - sequence: 0, - stepIndex: 2, - turnId: "turn-parent", - }, - type: "message.completed", - }); - await emitParent({ - data: { - finishReason: "stop", - sequence: 0, - stepIndex: 2, - turnId: "turn-parent", - }, - type: "step.completed", - }); - await emitParent({ - data: { sequence: 0, turnId: "turn-parent" }, - type: "turn.completed", - }); - - const spans = (await backgroundLogger.drain()) as Array< - Record - >; - const parentTurnId = deterministicEveIdForTest( - "eve:turn", - "session-parent", - "turn-parent", - ); - const childTurnId = deterministicEveIdForTest( - "eve:turn", - "session-child", - "turn-child", - ); - const subagentSpanId = deterministicEveIdForTest( - "eve:subagent", - "session-parent", - "call-researcher", - ); - const parentTurn = spans.find( - (span) => - span.span_attributes?.name === "eve.turn" && - span.span_id === parentTurnId, - ); - const subagentSpans = spans.filter( - (span) => span.span_attributes?.name === "researcher", - ); - const childTurn = spans.find( - (span) => - span.span_attributes?.name === "eve.turn" && - span.span_id === childTurnId, - ); - const childSearch = spans.find( - (span) => - span.span_attributes?.name === "search" && - span.span_parents?.[0] === childTurnId, - ); - const parentRead = spans.find( - (span) => - span.span_attributes?.name === "read" && - span.span_parents?.[0] === parentTurnId, - ); - const parentSteps = spans.filter( - (span) => - span.span_attributes?.name === "eve.step" && - span.span_parents?.[0] === parentTurnId, - ); - - expect(parentTurn).toBeDefined(); - expect(subagentSpans).toHaveLength(1); - expect(subagentSpans[0]?.span_id).toBe(subagentSpanId); - expect(subagentSpans[0]?.input).toEqual({ - message: "Find the relevant section", - }); - expect(childTurn).toBeDefined(); - expect(childSearch).toBeDefined(); - expect(parentRead).toBeDefined(); - expect(parentSteps).toHaveLength(3); - expect(parentTurn?.span_parents).toEqual([]); - expect(parentTurn?.span_id).toBe(parentTurnId); - expect(parentTurn?.span_id).toMatch( - /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/, - ); - expect(parentTurn?.root_span_id).toMatch( - /^([0-9a-f]{32}|[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12})$/, - ); - expect(parentTurn?.root_span_id).toBe( - deterministicEveIdForTest("eve:root", "session-parent", "turn-parent"), - ); - expect(subagentSpans[0]?.span_parents).toEqual([parentTurn?.span_id]); - expect(childTurn?.span_parents).toEqual([subagentSpanId]); - expect(childTurn?.root_span_id).toBe(parentTurn?.root_span_id); - expect(parentTurn?.metadata).toEqual({ - "eve.session_id": "session-parent", - }); - expect(subagentSpans[0]?.metadata).toEqual({ - "eve.session_id": "session-parent", - }); - expect(childTurn?.metadata).toEqual({ - "eve.session_id": "session-child", - }); - expect(childSearch?.metadata).toEqual({ - "eve.session_id": "session-child", - }); - expect(parentRead?.metadata).toEqual({ - "eve.session_id": "session-parent", - }); - expect(childSearch?.span_parents).toEqual([childTurn?.span_id]); - expect(childSearch?.span_id).toBe( - deterministicEveIdForTest( - "eve:tool", - "session-child", - "turn-child", - "call-search", - ), - ); - expect(parentRead?.span_id).toBe( - deterministicEveIdForTest( - "eve:tool", - "session-parent", - "turn-parent", - "call-read", - ), - ); - expect(parentSteps.map((span) => span.span_id)).toEqual([ - deterministicEveIdForTest( - "eve:step", - "session-parent", - "turn-parent", - "0", - ), - deterministicEveIdForTest( - "eve:step", - "session-parent", - "turn-parent", - "1", - ), - deterministicEveIdForTest( - "eve:step", - "session-parent", - "turn-parent", - "2", - ), - ]); - expect(spans.map((span) => span.span_attributes?.name)).toEqual([ - "eve.turn", - "eve.step", - "researcher", - "eve.turn", - "eve.step", - "search", - "eve.step", - "read", - "eve.step", - ]); - - backgroundLogger = _exportsForTestingOnly.useTestBackgroundLogger(); - initLogger({ - projectName: "eve-plugin.test.ts", - projectId: "test-project-id", - }); - const replay = braintrustEveHook({ - defineState: parentEveState.defineState, - }).events?.["*"]; - await replay?.( - { - data: { sequence: 0, turnId: "turn-parent" }, - type: "turn.started", - }, - parentCtx, - ); - await replay?.( - { - data: { sequence: 0, turnId: "turn-parent" }, - type: "turn.completed", - }, - parentCtx, - ); - const replaySpans = (await backgroundLogger.drain()) as Array< - Record - >; - expect( - replaySpans.find((span) => span.span_id === parentTurn?.span_id), - ).toMatchObject({ _is_merge: true }); - }); - - it("does not parent an Eve turn under the active Braintrust span", async () => { - const wildcard = braintrustEveHook({ defineState }).events?.["*"]; - expect(wildcard).toBeDefined(); - - const ctx: EveHookContext = { - session: { id: "session-wrapped" }, - }; - const parent = startSpan({ name: "workflow" }); - await withCurrent(parent, async () => { - await wildcard?.( - { - data: { sequence: 0, turnId: "turn-wrapped" }, - type: "turn.started", - }, - ctx, - ); - await wildcard?.( - { - data: { sequence: 0, turnId: "turn-wrapped" }, - type: "turn.completed", - }, - ctx, - ); - }); - parent.end(); - - const spans = (await backgroundLogger.drain()) as Array< - Record - >; - const turn = spans.find( - (span) => span.span_attributes?.name === "eve.turn", - ); - expect(turn?.span_id).toBe( - deterministicEveIdForTest("eve:turn", "session-wrapped", "turn-wrapped"), - ); - expect(turn?.span_parents).toEqual([]); - expect(turn?.root_span_id).toBe( - deterministicEveIdForTest("eve:root", "session-wrapped", "turn-wrapped"), - ); - expect(turn?.root_span_id).not.toBe(parent.rootSpanId); - }); - - it("does not throw when Eve emits malformed events or failures", async () => { - const wildcard = braintrustEveHook({ defineState }).events?.["*"]; - expect(wildcard).toBeDefined(); - - await expect( - wildcard?.({ bad: true } as never, {} as never), - ).resolves.toBeUndefined(); - await expect( - wildcard?.({ type: "step.started" }, {}), - ).resolves.toBeUndefined(); - const throwingEvent = Object.defineProperty({}, "type", { - get() { - throw new Error("hostile event getter"); - }, - }); - await expect(wildcard?.(throwingEvent, {})).resolves.toBeUndefined(); - await expect( - wildcard?.( - { - data: { - code: "boom", - message: "step failed", - sequence: 0, - stepIndex: 0, - turnId: "turn-missing-session", - }, - type: "step.failed", - }, - {} as never, - ), - ).resolves.toBeUndefined(); - await expect( - wildcard?.( - { - data: { runtime: { modelId: 123 } }, - type: "session.started", - } as never, - { session: { id: "session-malformed-runtime" } }, - ), - ).resolves.toBeUndefined(); - - const spans = (await backgroundLogger.drain()) as Array< - Record - >; - expect(spans).toEqual([]); - }); -}); diff --git a/js/src/instrumentation/plugins/eve-plugin.ts b/js/src/instrumentation/plugins/eve-plugin.ts deleted file mode 100644 index de466daea..000000000 --- a/js/src/instrumentation/plugins/eve-plugin.ts +++ /dev/null @@ -1,2633 +0,0 @@ -import { debugLogger } from "../../debug-logger"; -import { - NOOP_SPAN, - _internalStartSpanWithInitialMerge, - flush, - updateSpan, - withCurrent, -} from "../../logger"; -import type { Span } from "../../logger"; -import { LRUCache } from "../../lru-cache"; -import { - INSTRUMENTATION_NAMES, - withSpanInstrumentationName, -} from "../../span-origin"; -import { SpanTypeAttribute, isObject } from "../../../util/index"; -import { getCurrentUnixTimestamp } from "../../util"; -import type { - EveAssistantStepFinishReason, - EveActionResultError, - EveHandleMessageStreamEvent, - EveInstrumentationDefinition, - EveInstrumentationStepStartedEventInput, - EveModelMessage, - EveRuntimeActionRequest, - EveRuntimeActionResult, - EveRuntimeToolCallActionRequest, - EveRuntimeToolResultActionResult, -} from "../../vendor-sdk-types/eve"; - -type SpanState = { - metadata: Record; - span: EveSpan; -}; - -type EveSpan = Pick; - -type EveSpanStartEvent = { - readonly created: string; - readonly metrics: { readonly start: number }; - readonly span_attributes: Record; - readonly span_parents: string[]; -}; - -type EveSpanReference = { - readonly endTime?: number; - readonly exported: string; - readonly rootSpanId: string; - readonly rowId: string; - readonly spanId: string; - readonly startEvent?: EveSpanStartEvent; -}; - -type StepState = SpanState & { - input?: unknown; - metrics: Record; - output?: unknown; - reasoning: readonly EveReasoningBlock[]; -}; - -type TurnState = SpanState & { - key: string; - metrics: Record; - output?: unknown; - sessionId: string; - stepsByIndex: Map; - turnId: string; -}; - -type ToolState = SpanState & { - endedByTurn?: boolean; - turnKey: string; -}; - -type EveEntityKind = "step" | "subagent" | "tool" | "turn"; - -type EveStateHandle = { - get(): T; - update(fn: (current: T) => T): void; -}; - -export type EveDefineState = ( - name: string, - initial: () => T, -) => EveStateHandle; - -type EveTraceState = { - metadata: Record; - reasoningBlocks: readonly (EveReasoningBlock & { key: string })[]; - spanReferences: readonly EveSpanReference[]; - stepStarts: readonly { - ordinal: number; - open: boolean; - stepIndex: number; - turnId: string; - }[]; - llmInputs: readonly { - input: CapturedEveModelInput; - key: string; - }[]; -}; - -type EveReasoningBlock = { - content: string; - eventAt?: string; -}; - -const EVE_TRACE_STATE_KEY = "braintrust.eve.tracing"; -const MAX_EVE_CACHE_ENTRIES = 10_000; -const MAX_STORED_LLM_INPUTS = 100; -const MAX_STORED_REASONING_BLOCKS = 100; -const MAX_STORED_SPAN_REFERENCES = 10_000; -const MAX_STORED_STEP_STARTS = 10_000; -const EVE_HANDLED_EVENT_TYPES = new Set([ - "action.result", - "actions.requested", - "message.completed", - "message.received", - "reasoning.completed", - "result.completed", - "session.completed", - "session.failed", - "session.started", - "step.completed", - "step.failed", - "step.started", - "subagent.called", - "subagent.completed", - "turn.completed", - "turn.failed", - "turn.started", -]); - -type CapturedEveModelMessage = { - content: string | readonly Record[]; - role: EveModelMessage["role"]; -}; - -type CapturedEveModelInput = readonly CapturedEveModelMessage[]; - -/** Manual hook instrumentation for eve runtime stream events. */ -/* eslint-disable @typescript-eslint/no-explicit-any -- Eve compatibility boundary. */ -export function braintrustEveHook(options: { - defineState: EveDefineState; - metadata?: Record; -}): any { - if (!options || typeof options.defineState !== "function") { - throw new TypeError( - "braintrustEveHook requires Eve's defineState function", - ); - } - const state = options.defineState(EVE_TRACE_STATE_KEY, emptyEveTraceState); - const bridge = new EveBridge(state); - return { - events: { - "*": async (event: unknown, ctx: unknown) => { - await bridge.handle(event, ctx, options.metadata); - }, - }, - }; -} -/* eslint-enable @typescript-eslint/no-explicit-any */ - -/** Legacy Eve instrumentation helper for durable LLM input capture. */ -export function createLegacyEveInstrumentation(options: { - defineState: EveDefineState; - setup?: EveInstrumentationDefinition["setup"]; -}): EveInstrumentationDefinition { - if (!options || typeof options.defineState !== "function") { - throw new TypeError( - "braintrustEveInstrumentation requires Eve's defineState function", - ); - } - const state = options.defineState(EVE_TRACE_STATE_KEY, emptyEveTraceState); - return { - events: { - "step.started": (input: unknown) => { - try { - if (!isEveInstrumentationStepStartedEventInput(input)) { - debugLogger.warn( - "Ignoring malformed Eve instrumentation step.started event", - ); - return undefined; - } - captureEveModelInput(state, input); - } catch (error) { - debugLogger.warn("Error in Eve LLM input capture:", error); - } - return undefined; - }, - }, - recordInputs: false, - recordOutputs: false, - setup: options.setup, - }; -} - -function isEveHandleMessageStreamEvent( - event: unknown, -): event is EveHandleMessageStreamEvent { - if (!isObject(event) || typeof event["type"] !== "string") { - return false; - } - const type = event["type"]; - if (type === "session.completed") { - return true; - } - if (!isObject(event["data"])) { - return false; - } - const data = event["data"]; - if (type === "session.started") { - return true; - } - if (type === "session.failed") { - return ( - typeof data["sessionId"] === "string" && - typeof data["code"] === "string" && - typeof data["message"] === "string" - ); - } - if (type === "session.waiting") { - return data["wait"] === "next-user-message"; - } - if ( - typeof data["turnId"] !== "string" || - typeof data["sequence"] !== "number" || - !Number.isFinite(data["sequence"]) - ) { - return false; - } - if ( - type === "turn.started" || - type === "turn.completed" || - type === "subagent.called" || - type === "subagent.completed" - ) { - if (type === "subagent.called") { - return ( - typeof data["callId"] === "string" && - typeof data["childSessionId"] === "string" && - typeof data["name"] === "string" - ); - } - if (type === "subagent.completed") { - return ( - typeof data["callId"] === "string" && - typeof data["subagentName"] === "string" - ); - } - return true; - } - if (type === "turn.failed") { - return ( - typeof data["code"] === "string" && typeof data["message"] === "string" - ); - } - if (type === "message.received") { - return typeof data["message"] === "string"; - } - if ( - typeof data["stepIndex"] !== "number" || - !Number.isFinite(data["stepIndex"]) || - data["stepIndex"] < 0 - ) { - return false; - } - switch (type) { - case "step.started": - return true; - case "message.completed": - return ( - typeof data["finishReason"] === "string" && - (typeof data["message"] === "string" || data["message"] === null) - ); - case "reasoning.completed": - return typeof data["reasoning"] === "string"; - case "result.completed": - return true; - case "step.completed": - return typeof data["finishReason"] === "string"; - case "step.failed": - return ( - typeof data["code"] === "string" && typeof data["message"] === "string" - ); - case "actions.requested": - return Array.isArray(data["actions"]); - case "action.result": - return ( - isObject(data["result"]) && - (data["status"] === "completed" || - data["status"] === "failed" || - data["status"] === "rejected") - ); - default: - return false; - } -} - -function isEveInstrumentationStepStartedEventInput( - input: unknown, -): input is EveInstrumentationStepStartedEventInput { - if (!isObject(input)) { - return false; - } - const modelInput = input["modelInput"]; - const session = input["session"]; - const step = input["step"]; - const turn = input["turn"]; - return ( - isObject(modelInput) && - Array.isArray(modelInput["messages"]) && - isObject(session) && - typeof session["id"] === "string" && - isObject(step) && - typeof step["index"] === "number" && - Number.isFinite(step["index"]) && - step["index"] >= 0 && - isObject(turn) && - typeof turn["id"] === "string" && - typeof turn["sequence"] === "number" && - Number.isFinite(turn["sequence"]) - ); -} - -class ResumedEveSpan implements EveSpan { - private endTime: number | undefined; - - constructor(private readonly reference: EveSpanReference) { - this.endTime = reference.endTime; - } - - get rootSpanId(): string { - return this.reference.rootSpanId; - } - - get spanId(): string { - return this.reference.spanId; - } - - log(event: Parameters[0]): void { - const metrics = { - ...this.reference.startEvent?.metrics, - ...(this.endTime === undefined ? {} : { end: this.endTime }), - ...event.metrics, - }; - updateSpan({ - exported: this.reference.exported, - ...this.reference.startEvent, - ...event, - ...(Object.keys(metrics).length > 0 ? { metrics } : {}), - }); - } - - end(args?: Parameters[0]): number { - if (this.endTime === undefined) { - this.endTime = args?.endTime ?? getCurrentUnixTimestamp(); - this.log({ metrics: { end: this.endTime } }); - } - return this.endTime; - } -} - -class EveBridge { - constructor(private readonly state: EveStateHandle) {} - - private eventQueuesBySession = new Map>(); - private completedToolKeys = new LRUCache({ - max: MAX_EVE_CACHE_ENTRIES, - }); - private toolsByCallKey = new LRUCache({ - max: MAX_EVE_CACHE_ENTRIES, - }); - private turnsByKey = new LRUCache({ - max: MAX_EVE_CACHE_ENTRIES, - }); - - private async startEveSpan( - args: Parameters[0], - ): Promise { - const rowId = args?.event?.id; - const reference = - typeof rowId === "string" && - readEveTraceState(this.state).spanReferences.find( - (candidate) => candidate.rowId === rowId, - ); - if (reference) { - return new ResumedEveSpan(reference); - } - - const startTime = args?.startTime ?? getCurrentUnixTimestamp(); - const parentSpanIds = args?.parentSpanIds; - const startEvent: EveSpanStartEvent = { - created: new Date().toISOString(), - metrics: { start: startTime }, - span_attributes: { - ...(args?.name ? { name: args.name } : {}), - ...(args?.type ? { type: args.type } : {}), - ...args?.spanAttributes, - }, - span_parents: parentSpanIds - ? "spanId" in parentSpanIds - ? [parentSpanIds.spanId] - : parentSpanIds.parentSpanIds - : [], - }; - const span = withCurrent(NOOP_SPAN, () => - _internalStartSpanWithInitialMerge( - withSpanInstrumentationName( - { ...args, startTime }, - INSTRUMENTATION_NAMES.EVE, - ), - ), - ); - if (typeof rowId !== "string") { - return span; - } - - try { - const exported = await span.export(); - const reference = { - exported, - rootSpanId: span.rootSpanId, - rowId, - spanId: span.spanId, - startEvent, - }; - this.state.update((current) => { - const normalized = normalizeEveTraceState(current); - return normalized.spanReferences.some( - (candidate) => candidate.rowId === rowId, - ) - ? normalized - : { - ...normalized, - spanReferences: [...normalized.spanReferences, reference].slice( - -MAX_STORED_SPAN_REFERENCES, - ), - }; - }); - } catch (error) { - debugLogger.warn("Error exporting Eve span for resumption:", error); - } - return span; - } - - private async startEveChildSpan( - parent: EveSpan, - args: Parameters[0], - ): Promise { - return await this.startEveSpan({ - ...args, - parentSpanIds: { - rootSpanId: parent.rootSpanId, - spanId: parent.spanId, - }, - }); - } - - private stepOrdinal( - event: Extract, - ): number { - let ordinal = 0; - this.state.update((current) => { - const state = normalizeEveTraceState(current); - const previous = state.stepStarts - .filter( - (entry) => - entry.turnId === event.data.turnId && - entry.stepIndex === event.data.stepIndex, - ) - .at(-1); - if (previous?.open) { - ordinal = previous.ordinal; - return state; - } - - ordinal = state.stepStarts.filter( - (entry) => entry.turnId === event.data.turnId, - ).length; - return { - ...state, - stepStarts: [ - ...state.stepStarts, - { - open: true, - ordinal, - stepIndex: event.data.stepIndex, - turnId: event.data.turnId, - }, - ].slice(-MAX_STORED_STEP_STARTS), - }; - }); - return ordinal; - } - - private markStepEnded(turnId: string, stepIndex: number): void { - this.state.update((current) => { - const state = normalizeEveTraceState(current); - let index = -1; - for (let i = state.stepStarts.length - 1; i >= 0; i--) { - const entry = state.stepStarts[i]; - if (entry?.turnId === turnId && entry.stepIndex === stepIndex) { - index = i; - break; - } - } - if (index < 0 || !state.stepStarts[index]?.open) { - return state; - } - return { - ...state, - stepStarts: state.stepStarts.map((entry, entryIndex) => - entryIndex === index ? { ...entry, open: false } : entry, - ), - }; - }); - } - - async handle( - event: unknown, - ctx: unknown, - hookMetadata?: Record, - ): Promise { - try { - await this.handleUnchecked(event, ctx, hookMetadata); - } catch (error) { - debugLogger.warn("Error in Eve hook instrumentation:", error); - } - } - - private async handleUnchecked( - event: unknown, - ctx: unknown, - hookMetadata?: Record, - ): Promise { - if (!isEveHandleMessageStreamEvent(event)) { - if ( - isObject(event) && - typeof event["type"] === "string" && - EVE_HANDLED_EVENT_TYPES.has(event["type"]) - ) { - debugLogger.warn(`Ignoring malformed Eve hook ${event["type"]} event`); - } - return; - } - const run = async () => { - try { - if (!(await this.handleEvent(event, ctx, hookMetadata))) { - return; - } - if (event.type === "session.failed") { - const sessionId = event.data.sessionId || sessionIdFromContext(ctx); - await this.flushInstrumentation(); - if (sessionId) { - this.cleanupSession(sessionId); - } - } else if (event.type === "session.completed") { - const sessionId = sessionIdFromContext(ctx); - await this.flushInstrumentation(); - if (sessionId) { - this.cleanupSession(sessionId); - } - } - } catch (error) { - debugLogger.warn("Error in Eve hook instrumentation:", error); - } - }; - - const sessionId = - event.type === "session.failed" - ? event.data.sessionId || sessionIdFromContext(ctx) - : sessionIdFromContext(ctx); - if (!sessionId) { - await run(); - return; - } - - const previous = this.eventQueuesBySession.get(sessionId); - const queued = previous ? previous.then(run) : run(); - this.eventQueuesBySession.set(sessionId, queued); - try { - await queued; - } finally { - if (this.eventQueuesBySession.get(sessionId) === queued) { - this.eventQueuesBySession.delete(sessionId); - } - } - } - - private async handleEvent( - event: EveHandleMessageStreamEvent, - ctx: unknown, - hookMetadata?: Record, - ): Promise { - switch (event.type) { - case "session.started": - this.handleSessionStarted(event, ctx, hookMetadata); - return true; - case "turn.started": - await this.handleTurnStarted(event, ctx, hookMetadata); - return true; - case "message.received": - await this.handleMessageReceived(event, ctx, hookMetadata); - return true; - case "step.started": - await this.handleStepStarted(event, ctx, hookMetadata); - return true; - case "reasoning.completed": - this.handleReasoningCompleted(event, ctx); - return true; - case "message.completed": - this.handleMessageCompleted(event, ctx); - return true; - case "result.completed": - this.handleResultCompleted(event, ctx); - return true; - case "actions.requested": - await this.handleActionsRequested(event, ctx, hookMetadata); - return true; - case "action.result": - await this.handleActionResult(event, ctx, hookMetadata); - return true; - case "subagent.called": - await this.handleSubagentCalled(event, ctx, hookMetadata); - return true; - case "subagent.completed": - await this.handleSubagentCompleted(event, ctx, hookMetadata); - return true; - case "step.completed": - this.handleStepCompleted(event, ctx); - return true; - case "step.failed": - this.handleStepFailed(event, ctx); - return true; - case "turn.completed": - this.handleTurnCompleted(event, ctx); - return true; - case "turn.failed": - this.handleTurnFailed(event, ctx); - return true; - case "session.failed": - this.handleSessionFailed(event, ctx); - return true; - case "session.completed": - this.handleSessionCompleted(event, ctx); - return true; - default: - return false; - } - } - - private handleSessionStarted( - event: Extract, - ctx: unknown, - hookMetadata?: Record, - ): void { - const sessionId = sessionIdFromContext(ctx); - if (!sessionId) { - return; - } - - const metadata = { - ...(hookMetadata ?? {}), - ...modelMetadataFromRuntime(event.data.runtime), - }; - this.state.update((current) => { - const normalized = normalizeEveTraceState(current); - return { - ...normalized, - metadata: { ...normalized.metadata, ...metadata }, - }; - }); - for (const [key, turn] of this.turnsByKey) { - if (!key.startsWith(`${sessionId}:`)) { - continue; - } - - turn.metadata = { ...turn.metadata, ...metadata }; - turn.span.log({ metadata: turn.metadata }); - for (const step of turn.stepsByIndex.values()) { - step.metadata = { ...step.metadata, ...metadata }; - step.span.log({ metadata: step.metadata }); - } - } - } - - private async handleTurnStarted( - event: Extract, - ctx: unknown, - hookMetadata?: Record, - ): Promise { - const sessionId = sessionIdFromContext(ctx); - if (!sessionId) { - return; - } - - const key = turnKey(sessionId, event.data.turnId); - const metadata = { - ...readEveTraceState(this.state).metadata, - ...(hookMetadata ?? {}), - "eve.session_id": sessionId, - }; - const existing = this.turnsByKey.get(key); - if (existing) { - existing.metadata = { ...existing.metadata, ...metadata }; - existing.span.log({ metadata: existing.metadata }); - return; - } - - const span = await this.startTurnSpan(sessionId, event, ctx, metadata); - span.log({ metadata }); - this.turnsByKey.set(key, { - key, - metadata, - metrics: {}, - sessionId, - span, - stepsByIndex: new Map(), - turnId: event.data.turnId, - }); - } - - private async handleMessageReceived( - event: Extract, - ctx: unknown, - hookMetadata?: Record, - ): Promise { - const turn = await this.ensureTurn(event, ctx, hookMetadata); - if (!turn) { - return; - } - - const input = [{ content: event.data.message, role: "user" }]; - turn.span.log({ input }); - } - - private async handleStepStarted( - event: Extract, - ctx: unknown, - hookMetadata?: Record, - ): Promise { - const turn = await this.ensureTurn(event, ctx, hookMetadata); - const sessionId = sessionIdFromContext(ctx); - if (!turn || !sessionId) { - return; - } - - const existing = turn.stepsByIndex.get(event.data.stepIndex); - if (existing) { - existing.span.log({ - ...(existing.input !== undefined ? { input: existing.input } : {}), - metadata: existing.metadata, - metrics: existing.metrics, - output: existing.output, - }); - const endTime = eventTime(event); - existing.span.end(endTime === undefined ? undefined : { endTime }); - this.markStepEnded(event.data.turnId, event.data.stepIndex); - clearStoredEveReasoning( - this.state, - sessionId, - event.data.turnId, - event.data.stepIndex, - ); - } - - const stepOrdinal = this.stepOrdinal(event); - const metadata = { ...turn.metadata }; - const input = consumeCapturedEveModelInput( - this.state, - sessionId, - event.data.turnId, - event.data.stepIndex, - ); - const reasoning = readStoredEveReasoning( - this.state, - sessionId, - event.data.turnId, - event.data.stepIndex, - ); - const output = mergeEveReasoning(undefined, reasoning); - const { rowId: eventId, spanId } = await generateEveIds( - "step", - sessionId, - event.data.turnId, - String(stepOrdinal), - ); - const span = await this.startEveChildSpan(turn.span, { - event: { - id: eventId, - ...(input !== undefined ? { input } : {}), - metadata, - }, - name: "eve.step", - spanAttributes: { type: SpanTypeAttribute.LLM }, - spanId, - startTime: eventTime(event), - }); - span.log({ - ...(input !== undefined ? { input } : {}), - metadata, - }); - - turn.stepsByIndex.set(event.data.stepIndex, { - ...(input !== undefined ? { input } : {}), - metadata, - metrics: {}, - ...(output !== undefined ? { output } : {}), - reasoning, - span, - }); - } - - private handleReasoningCompleted( - event: Extract< - EveHandleMessageStreamEvent, - { type: "reasoning.completed" } - >, - ctx: unknown, - ): void { - const sessionId = sessionIdFromContext(ctx); - if (!sessionId) { - return; - } - - const reasoning = storeEveReasoning(this.state, sessionId, event); - const step = this.stepForEvent(event, ctx); - if (step) { - step.reasoning = reasoning; - step.output = mergeEveReasoning(step.output, reasoning); - } - } - - private handleMessageCompleted( - event: Extract, - ctx: unknown, - ): void { - const step = this.stepForEvent(event, ctx); - if (!step) { - return; - } - - const existingMessage = eveOutputMessage(step.output); - const existingToolCalls = isObject(existingMessage) - ? existingMessage.tool_calls - : undefined; - step.output = mergeEveReasoning( - [ - { - finish_reason: normalizedFinishReason(event.data.finishReason), - index: 0, - message: { - content: event.data.message, - role: "assistant", - ...(Array.isArray(existingToolCalls) - ? { tool_calls: existingToolCalls } - : {}), - }, - }, - ], - step.reasoning, - ); - - const turn = this.turnForEvent(event, ctx); - if (turn && event.data.finishReason !== "tool-calls") { - turn.output = event.data.message; - } - } - - private handleResultCompleted( - event: Extract, - ctx: unknown, - ): void { - const step = this.stepForEvent(event, ctx); - if (step) { - step.output = mergeEveReasoning( - [ - { - finish_reason: "stop", - index: 0, - message: { - content: event.data.result, - role: "assistant", - }, - }, - ], - step.reasoning, - ); - } - - const turn = this.turnForEvent(event, ctx); - if (turn) { - turn.output = event.data.result; - } - } - - private async handleActionsRequested( - event: Extract, - ctx: unknown, - hookMetadata?: Record, - ): Promise { - const turn = await this.ensureTurn(event, ctx, hookMetadata); - const sessionId = sessionIdFromContext(ctx); - if (!turn || !sessionId) { - return; - } - - const traceActions = event.data.actions.filter(isTraceableActionRequest); - if (traceActions.length === 0) { - return; - } - - for (const action of traceActions) { - if (isToolCallAction(action)) { - await this.startRequestedTool(event, turn, sessionId, action); - } else if (isLocalSubagentCallAction(action)) { - await this.startRequestedSubagent(event, turn, sessionId, action); - } - } - - const step = turn.stepsByIndex.get(event.data.stepIndex); - if (!step) { - return; - } - - const toolCallsById = new Map(); - if (Array.isArray(step.output) && isObject(step.output[0])) { - const message = step.output[0]["message"]; - if (isObject(message) && Array.isArray(message["tool_calls"])) { - for (const toolCall of message["tool_calls"]) { - if (isObject(toolCall) && typeof toolCall["id"] === "string") { - toolCallsById.set(toolCall["id"], toolCall); - } - } - } - } - for (const action of traceActions) { - const name = - action.kind === "tool-call" - ? action.toolName - : (action.subagentName ?? action.name ?? "agent"); - let args = "null"; - try { - args = JSON.stringify(action.input) ?? "null"; - } catch { - // Preserve the tool call while omitting malformed, non-JSON input. - } - toolCallsById.set(action.callId, { - function: { - arguments: args, - name, - }, - id: action.callId, - type: "function", - }); - } - - step.output = mergeEveReasoning( - [ - { - finish_reason: "tool_calls", - index: 0, - message: { - content: null, - role: "assistant", - tool_calls: [...toolCallsById.values()], - }, - }, - ], - step.reasoning, - ); - } - - private async handleActionResult( - event: Extract, - ctx: unknown, - hookMetadata?: Record, - ): Promise { - if (isToolResult(event.data.result)) { - await this.handleToolResult(event, ctx, event.data.result, hookMetadata); - return; - } - if (isSubagentResult(event.data.result)) { - await this.handleSubagentResult( - event, - ctx, - event.data.result, - hookMetadata, - ); - } - } - - private async handleToolResult( - event: Extract, - ctx: unknown, - result: EveRuntimeToolResultActionResult, - hookMetadata?: Record, - ): Promise { - const sessionId = sessionIdFromContext(ctx); - if (!sessionId) { - return; - } - - const key = toolKey(sessionId, result.callId); - if (this.completedToolKeys.has(key)) { - return; - } - const tool = - this.toolsByCallKey.get(key) ?? - (await this.startSyntheticTool(event, ctx, result, hookMetadata)); - if (!tool) { - return; - } - const failed = - event.data.status === "failed" || - result.isError === true || - event.data.error !== undefined; - tool.span.log({ - ...(failed - ? { - error: actionResultError(event.data.error, result.output), - } - : {}), - metadata: tool.metadata, - output: result.output, - }); - - const endTime = eventTime(event); - tool.span.end(endTime === undefined ? undefined : { endTime }); - this.toolsByCallKey.delete(key); - this.completedToolKeys.set(key, true); - } - - private async handleSubagentCalled( - event: Extract, - ctx: unknown, - hookMetadata?: Record, - ): Promise { - if (event.data.remote?.url) { - return; - } - - const turn = await this.ensureTurn(event, ctx, hookMetadata); - const sessionId = sessionIdFromContext(ctx); - if (!turn || !sessionId) { - return; - } - - const key = toolKey(sessionId, event.data.callId); - const metadata = toolMetadataFromTurn(turn); - const existing = this.toolsByCallKey.get(key); - if (existing) { - existing.metadata = { ...existing.metadata, ...metadata }; - existing.span.log({ metadata: existing.metadata }); - return; - } - if (this.completedToolKeys.has(key)) { - return; - } - - const { rowId: eventId, spanId } = await generateEveIds( - "subagent", - sessionId, - event.data.callId, - ); - const pending = this.toolsByCallKey.get(key); - if (pending || this.completedToolKeys.has(key)) { - if (pending) { - pending.metadata = { ...pending.metadata, ...metadata }; - pending.span.log({ metadata: pending.metadata }); - } - return; - } - const span = await this.startEveChildSpan(turn.span, { - event: { - id: eventId, - metadata, - }, - name: event.data.toolName ?? event.data.name, - spanAttributes: { type: SpanTypeAttribute.TOOL }, - spanId, - startTime: eventTime(event), - }); - span.log({ metadata }); - this.toolsByCallKey.set(key, { - metadata, - span, - turnKey: turnKey(sessionId, event.data.turnId), - }); - } - - private async handleSubagentCompleted( - event: Extract, - ctx: unknown, - hookMetadata?: Record, - ): Promise { - const sessionId = sessionIdFromContext(ctx); - if (!sessionId) { - return; - } - - const key = toolKey(sessionId, event.data.callId); - if (this.completedToolKeys.has(key)) { - return; - } - const subagent = - this.toolsByCallKey.get(key) ?? - (await this.startSyntheticSubagent(event, ctx, hookMetadata)); - if (!subagent) { - return; - } - subagent.span.log({ - ...(event.data.status === "failed" - ? { - error: actionResultError(event.data.error, event.data.output), - } - : {}), - metadata: subagent.metadata, - ...(event.data.output !== undefined ? { output: event.data.output } : {}), - }); - const endTime = eventTime(event); - const recordedEndTime = subagent.span.end( - endTime === undefined ? undefined : { endTime }, - ); - this.state.update((current) => { - const normalized = normalizeEveTraceState(current); - return { - ...normalized, - spanReferences: normalized.spanReferences.map((reference) => - reference.spanId === subagent.span.spanId - ? { ...reference, endTime: recordedEndTime } - : reference, - ), - }; - }); - } - - private async handleSubagentResult( - event: Extract, - ctx: unknown, - result: Extract, - hookMetadata?: Record, - ): Promise { - const sessionId = sessionIdFromContext(ctx); - if (!sessionId) { - return; - } - - const key = toolKey(sessionId, result.callId); - if (this.completedToolKeys.has(key)) { - return; - } - const subagent = - this.toolsByCallKey.get(key) ?? - (await this.startSyntheticSubagentResult( - event, - ctx, - result, - hookMetadata, - )); - if (!subagent) { - return; - } - const isError = - event.data.status === "failed" || - result.isError === true || - event.data.error !== undefined; - subagent.span.log({ - ...(isError - ? { - error: actionResultError(event.data.error, result.output), - } - : {}), - metadata: subagent.metadata, - output: result.output, - }); - const endTime = eventTime(event); - subagent.span.end(endTime === undefined ? undefined : { endTime }); - - this.toolsByCallKey.delete(key); - this.completedToolKeys.set(key, true); - } - - private handleStepCompleted( - event: Extract, - ctx: unknown, - ): void { - const step = this.stepForEvent(event, ctx); - if (!step) { - return; - } - - const usage = event.data.usage; - const inputTokens = - typeof usage?.inputTokens === "number" && - Number.isFinite(usage.inputTokens) && - usage.inputTokens >= 0 - ? usage.inputTokens - : undefined; - const outputTokens = - typeof usage?.outputTokens === "number" && - Number.isFinite(usage.outputTokens) && - usage.outputTokens >= 0 - ? usage.outputTokens - : undefined; - const cacheReadTokens = - typeof usage?.cacheReadTokens === "number" && - Number.isFinite(usage.cacheReadTokens) && - usage.cacheReadTokens >= 0 - ? usage.cacheReadTokens - : undefined; - const cacheWriteTokens = - typeof usage?.cacheWriteTokens === "number" && - Number.isFinite(usage.cacheWriteTokens) && - usage.cacheWriteTokens >= 0 - ? usage.cacheWriteTokens - : undefined; - const costUsd = - typeof usage?.costUsd === "number" && - Number.isFinite(usage.costUsd) && - usage.costUsd >= 0 - ? usage.costUsd - : undefined; - const total = - inputTokens !== undefined && outputTokens !== undefined - ? inputTokens + outputTokens - : undefined; - const metrics = { - ...(inputTokens !== undefined ? { prompt_tokens: inputTokens } : {}), - ...(outputTokens !== undefined - ? { completion_tokens: outputTokens } - : {}), - ...(total !== undefined ? { tokens: total } : {}), - ...(cacheReadTokens !== undefined - ? { prompt_cached_tokens: cacheReadTokens } - : {}), - ...(cacheWriteTokens !== undefined - ? { prompt_cache_creation_tokens: cacheWriteTokens } - : {}), - ...(costUsd !== undefined ? { estimated_cost: costUsd } : {}), - }; - step.metrics = { ...step.metrics, ...metrics }; - const sessionId = sessionIdFromContext(ctx); - if (Array.isArray(step.output) && isObject(step.output[0])) { - const finishReason = step.output[0].finish_reason; - if (typeof finishReason !== "string") { - step.output[0].finish_reason = normalizedFinishReason( - event.data.finishReason, - ); - } - } - step.span.log({ - ...(step.input !== undefined ? { input: step.input } : {}), - metadata: step.metadata, - metrics, - output: step.output, - }); - const endTime = eventTime(event); - step.span.end(endTime === undefined ? undefined : { endTime }); - - const turn = this.turnForEvent(event, ctx); - if (turn) { - for (const [key, value] of Object.entries(metrics)) { - turn.metrics[key] = (turn.metrics[key] ?? 0) + value; - } - turn.stepsByIndex.delete(event.data.stepIndex); - } - this.markStepEnded(event.data.turnId, event.data.stepIndex); - if (sessionId) { - clearStoredEveReasoning( - this.state, - sessionId, - event.data.turnId, - event.data.stepIndex, - ); - } - } - - private handleStepFailed( - event: Extract, - ctx: unknown, - ): void { - const step = this.stepForEvent(event, ctx); - if (step) { - step.span.log({ - error: errorFromMessage( - event.data.message, - event.data.code, - event.data.details, - ), - }); - const endTime = eventTime(event); - step.span.end(endTime === undefined ? undefined : { endTime }); - } - - const turn = this.turnForEvent(event, ctx); - turn?.stepsByIndex.delete(event.data.stepIndex); - this.markStepEnded(event.data.turnId, event.data.stepIndex); - const sessionId = sessionIdFromContext(ctx); - if (sessionId) { - clearStoredEveReasoning( - this.state, - sessionId, - event.data.turnId, - event.data.stepIndex, - ); - } - } - - private handleTurnCompleted( - event: Extract, - ctx: unknown, - ): void { - const turn = this.turnForEvent(event, ctx); - if (!turn) { - return; - } - - this.finalizeTurn(turn, { - endTime: eventTime(event), - }); - } - - private handleTurnFailed( - event: Extract, - ctx: unknown, - ): void { - const turn = this.turnForEvent(event, ctx); - if (!turn) { - return; - } - - this.finalizeTurn(turn, { - endTime: eventTime(event), - error: errorFromMessage( - event.data.message, - event.data.code, - event.data.details, - ), - }); - } - - private handleSessionFailed( - event: Extract, - ctx: unknown, - ): void { - const sessionId = event.data.sessionId || sessionIdFromContext(ctx); - if (!sessionId) { - return; - } - const error = errorFromMessage( - event.data.message, - event.data.code, - event.data.details, - ); - for (const [key, turn] of this.turnsByKey) { - if (!key.startsWith(`${sessionId}:`)) { - continue; - } - this.finalizeTurn(turn, { - endTime: eventTime(event), - error, - }); - } - - for (const [key, tool] of this.toolsByCallKey) { - if (key.startsWith(`${sessionId}:`)) { - const endTime = eventTime(event); - if (!tool.endedByTurn) { - tool.span.log({ metadata: tool.metadata }); - tool.span.end(endTime === undefined ? undefined : { endTime }); - tool.endedByTurn = true; - } - } - } - } - - private handleSessionCompleted( - event: Extract, - ctx: unknown, - ): void { - const sessionId = sessionIdFromContext(ctx); - if (!sessionId) { - return; - } - for (const [key, turn] of this.turnsByKey) { - if (!key.startsWith(`${sessionId}:`)) { - continue; - } - this.finalizeTurn(turn, { - endTime: eventTime(event), - }); - } - - for (const [key, tool] of this.toolsByCallKey) { - if (key.startsWith(`${sessionId}:`) && !tool.endedByTurn) { - const endTime = eventTime(event); - tool.span.log({ metadata: tool.metadata }); - tool.span.end(endTime === undefined ? undefined : { endTime }); - tool.endedByTurn = true; - } - } - } - - private async ensureTurn( - event: Extract< - EveHandleMessageStreamEvent, - { - data: { readonly sequence: number; readonly turnId: string }; - } - >, - ctx: unknown, - hookMetadata?: Record, - ): Promise { - const sessionId = sessionIdFromContext(ctx); - if (!sessionId) { - return undefined; - } - - const key = turnKey(sessionId, event.data.turnId); - const existing = this.turnsByKey.get(key); - if (existing) { - return existing; - } - - const metadata = { - ...readEveTraceState(this.state).metadata, - ...(hookMetadata ?? {}), - "eve.session_id": sessionId, - }; - const span = await this.startTurnSpan(sessionId, event, ctx, metadata); - span.log({ metadata }); - const state = { - key, - metadata, - metrics: {}, - sessionId, - span, - stepsByIndex: new Map(), - turnId: event.data.turnId, - }; - this.turnsByKey.set(key, state); - return state; - } - - private async startRequestedTool( - event: Extract, - turn: TurnState, - sessionId: string, - action: EveRuntimeToolCallActionRequest, - ): Promise { - const key = toolKey(sessionId, action.callId); - if (this.toolsByCallKey.has(key) || this.completedToolKeys.has(key)) { - return; - } - - const metadata = toolMetadataFromTurn(turn); - const { rowId: eventId, spanId } = await generateEveIds( - "tool", - sessionId, - event.data.turnId, - action.callId, - ); - if (this.toolsByCallKey.has(key) || this.completedToolKeys.has(key)) { - return; - } - const span = await this.startEveChildSpan(turn.span, { - event: { - id: eventId, - input: action.input, - metadata, - }, - name: action.toolName, - spanAttributes: { type: SpanTypeAttribute.TOOL }, - spanId, - startTime: eventTime(event), - }); - span.log({ input: action.input, metadata }); - this.toolsByCallKey.set(key, { - metadata, - span, - turnKey: turnKey(sessionId, event.data.turnId), - }); - } - - private async startRequestedSubagent( - event: Extract, - turn: TurnState, - sessionId: string, - action: Extract, - ): Promise { - const key = toolKey(sessionId, action.callId); - if (this.toolsByCallKey.has(key) || this.completedToolKeys.has(key)) { - return; - } - - const name = action.subagentName ?? action.name ?? "agent"; - const metadata = toolMetadataFromTurn(turn); - const { rowId: eventId, spanId } = await generateEveIds( - "subagent", - sessionId, - action.callId, - ); - if (this.toolsByCallKey.has(key) || this.completedToolKeys.has(key)) { - return; - } - const span = await this.startEveChildSpan(turn.span, { - event: { - id: eventId, - input: action.input, - metadata, - }, - name, - spanAttributes: { type: SpanTypeAttribute.TOOL }, - spanId, - startTime: eventTime(event), - }); - span.log({ input: action.input, metadata }); - this.toolsByCallKey.set(key, { - metadata, - span, - turnKey: turnKey(sessionId, event.data.turnId), - }); - } - - private async startSyntheticTool( - event: Extract, - ctx: unknown, - result: EveRuntimeToolResultActionResult, - hookMetadata?: Record, - ): Promise { - const turn = await this.ensureTurn(event, ctx, hookMetadata); - const sessionId = sessionIdFromContext(ctx); - if (!turn || !sessionId) { - return undefined; - } - - const metadata = toolMetadataFromTurn(turn); - const { rowId: eventId, spanId } = await generateEveIds( - "tool", - sessionId, - event.data.turnId, - result.callId, - ); - const existing = this.toolsByCallKey.get(toolKey(sessionId, result.callId)); - if (existing) { - return existing; - } - const span = await this.startEveChildSpan(turn.span, { - event: { - id: eventId, - metadata, - }, - name: result.toolName, - spanAttributes: { type: SpanTypeAttribute.TOOL }, - spanId, - startTime: eventTime(event), - }); - span.log({ metadata }); - const state = { - metadata, - span, - turnKey: turnKey(sessionId, event.data.turnId), - }; - this.toolsByCallKey.set(toolKey(sessionId, result.callId), state); - return state; - } - - private async startSyntheticSubagent( - event: Extract, - ctx: unknown, - hookMetadata?: Record, - ): Promise { - const turn = await this.ensureTurn(event, ctx, hookMetadata); - const sessionId = sessionIdFromContext(ctx); - if (!turn || !sessionId) { - return undefined; - } - - const metadata = toolMetadataFromTurn(turn); - const { rowId: eventId, spanId } = await generateEveIds( - "subagent", - sessionId, - event.data.callId, - ); - const existing = this.toolsByCallKey.get( - toolKey(sessionId, event.data.callId), - ); - if (existing) { - return existing; - } - const span = await this.startEveChildSpan(turn.span, { - event: { - id: eventId, - metadata, - }, - name: event.data.subagentName, - spanAttributes: { type: SpanTypeAttribute.TOOL }, - spanId, - startTime: eventTime(event), - }); - span.log({ metadata }); - const state = { - metadata, - span, - turnKey: turnKey(sessionId, event.data.turnId), - }; - this.toolsByCallKey.set(toolKey(sessionId, event.data.callId), state); - return state; - } - - private async startSyntheticSubagentResult( - event: Extract, - ctx: unknown, - result: Extract, - hookMetadata?: Record, - ): Promise { - const turn = await this.ensureTurn(event, ctx, hookMetadata); - const sessionId = sessionIdFromContext(ctx); - if (!turn || !sessionId) { - return undefined; - } - - const metadata = toolMetadataFromTurn(turn); - const { rowId: eventId, spanId } = await generateEveIds( - "subagent", - sessionId, - result.callId, - ); - const existing = this.toolsByCallKey.get(toolKey(sessionId, result.callId)); - if (existing) { - return existing; - } - const span = await this.startEveChildSpan(turn.span, { - event: { - id: eventId, - metadata, - }, - name: result.subagentName, - spanAttributes: { type: SpanTypeAttribute.TOOL }, - spanId, - startTime: eventTime(event), - }); - span.log({ metadata }); - const state = { - metadata, - span, - turnKey: turnKey(sessionId, event.data.turnId), - }; - this.toolsByCallKey.set(toolKey(sessionId, result.callId), state); - return state; - } - - private async startTurnSpan( - sessionId: string, - event: Extract< - EveHandleMessageStreamEvent, - { data: { readonly sequence: number; readonly turnId: string } } - >, - ctx: unknown, - metadata: Record, - ): Promise { - const session = isObject(ctx) ? ctx["session"] : undefined; - const parent = isObject(session) ? session["parent"] : undefined; - const parentTurn = isObject(parent) ? parent["turn"] : undefined; - const parentLineage = - isObject(parent) && - typeof parent["callId"] === "string" && - typeof parent["sessionId"] === "string" && - isObject(parentTurn) && - typeof parentTurn["id"] === "string" - ? { - callId: parent["callId"], - sessionId: parent["sessionId"], - turnId: parentTurn["id"], - } - : undefined; - const [{ rowId: eventId, spanId }, rootSpanId, parentSpanId] = - await Promise.all([ - generateEveIds("turn", sessionId, event.data.turnId), - deterministicEveId( - "eve:root", - parentLineage?.sessionId ?? sessionId, - parentLineage?.turnId ?? event.data.turnId, - ), - parentLineage - ? deterministicEveId( - "eve:subagent", - parentLineage.sessionId, - parentLineage.callId, - ) - : Promise.resolve(undefined), - ]); - - return await this.startEveSpan({ - event: { - id: eventId, - metadata, - }, - name: "eve.turn", - parentSpanIds: parentSpanId - ? { rootSpanId, spanId: parentSpanId } - : { parentSpanIds: [], rootSpanId }, - spanAttributes: { type: SpanTypeAttribute.TASK }, - spanId, - startTime: eventTime(event), - }); - } - - private turnForEvent( - event: Extract< - EveHandleMessageStreamEvent, - { data: { readonly turnId: string } } - >, - ctx: unknown, - ): TurnState | undefined { - const sessionId = sessionIdFromContext(ctx); - return sessionId - ? this.turnsByKey.get(turnKey(sessionId, event.data.turnId)) - : undefined; - } - - private stepForEvent( - event: Extract< - EveHandleMessageStreamEvent, - { data: { readonly stepIndex: number; readonly turnId: string } } - >, - ctx: unknown, - ): StepState | undefined { - return this.turnForEvent(event, ctx)?.stepsByIndex.get( - event.data.stepIndex, - ); - } - - private finalizeTurn( - turn: TurnState, - args: { endTime: number | undefined; error?: Error }, - ): void { - const { endTime } = args; - for (const step of turn.stepsByIndex.values()) { - step.span.log({ - ...(step.input !== undefined ? { input: step.input } : {}), - metadata: step.metadata, - metrics: step.metrics, - output: step.output, - }); - step.span.end(endTime === undefined ? undefined : { endTime }); - } - turn.stepsByIndex.clear(); - - for (const tool of this.toolsByCallKey.values()) { - if (tool.turnKey !== turn.key) { - continue; - } - if (tool.endedByTurn) { - continue; - } - tool.span.log({ metadata: tool.metadata }); - tool.span.end(endTime === undefined ? undefined : { endTime }); - tool.endedByTurn = true; - } - - if (args.error) { - turn.span.log({ error: args.error }); - } else { - turn.span.log({ - metadata: turn.metadata, - metrics: turn.metrics, - output: turn.output, - }); - } - turn.span.end(endTime === undefined ? undefined : { endTime }); - this.turnsByKey.delete(turn.key); - this.state.update((current) => { - const normalized = normalizeEveTraceState(current); - return { - ...normalized, - reasoningBlocks: normalized.reasoningBlocks.filter( - (entry) => - !entry.key.startsWith(`${turn.sessionId}\0${turn.turnId}\0`), - ), - stepStarts: normalized.stepStarts.filter( - (entry) => entry.turnId !== turn.turnId, - ), - }; - }); - } - - private cleanupSession(sessionId: string): void { - const keyPrefix = `${sessionId}:`; - for (const key of this.turnsByKey.keys()) { - if (key.startsWith(keyPrefix)) { - this.turnsByKey.delete(key); - } - } - for (const key of this.toolsByCallKey.keys()) { - if (key.startsWith(keyPrefix)) { - this.toolsByCallKey.delete(key); - } - } - for (const key of this.completedToolKeys.keys()) { - if (key.startsWith(keyPrefix)) { - this.completedToolKeys.delete(key); - } - } - this.state.update(() => emptyEveTraceState()); - } - - private async flushInstrumentation(): Promise { - try { - await flush(); - return true; - } catch (error) { - debugLogger.warn("Error in Eve flush instrumentation:", error); - return false; - } - } -} - -function emptyEveTraceState(): EveTraceState { - return { - llmInputs: [], - metadata: {}, - reasoningBlocks: [], - spanReferences: [], - stepStarts: [], - }; -} - -function normalizeEveTraceState(state: unknown): EveTraceState { - if (!isObject(state)) { - return emptyEveTraceState(); - } - const metadata = isObject(state["metadata"]) ? state["metadata"] : {}; - const spanReferences = Array.isArray(state["spanReferences"]) - ? state["spanReferences"] - .flatMap((entry): EveTraceState["spanReferences"] => { - if (!isObject(entry)) { - return []; - } - const exported = entry["exported"]; - const endTime = entry["endTime"]; - const rootSpanId = entry["rootSpanId"]; - const rowId = entry["rowId"]; - const spanId = entry["spanId"]; - const startEvent = entry["startEvent"]; - const startEventCreated = isObject(startEvent) - ? startEvent["created"] - : undefined; - const startEventMetrics = isObject(startEvent) - ? startEvent["metrics"] - : undefined; - const startEventSpanAttributes = isObject(startEvent) - ? startEvent["span_attributes"] - : undefined; - const startEventSpanParents = isObject(startEvent) - ? startEvent["span_parents"] - : undefined; - const normalizedStartEvent = - typeof startEventCreated === "string" && - isObject(startEventMetrics) && - typeof startEventMetrics["start"] === "number" && - Number.isFinite(startEventMetrics["start"]) && - isObject(startEventSpanAttributes) && - Array.isArray(startEventSpanParents) && - startEventSpanParents.every( - (parent): parent is string => typeof parent === "string", - ) - ? { - created: startEventCreated, - metrics: { start: startEventMetrics["start"] }, - span_attributes: { ...startEventSpanAttributes }, - span_parents: [...startEventSpanParents], - } - : undefined; - return typeof exported === "string" && - typeof rootSpanId === "string" && - typeof rowId === "string" && - typeof spanId === "string" - ? [ - { - ...(typeof endTime === "number" && Number.isFinite(endTime) - ? { endTime } - : {}), - exported, - rootSpanId, - rowId, - spanId, - ...(normalizedStartEvent - ? { startEvent: normalizedStartEvent } - : {}), - }, - ] - : []; - }) - .slice(-MAX_STORED_SPAN_REFERENCES) - : []; - const llmInputs = Array.isArray(state["llmInputs"]) - ? state["llmInputs"] - .flatMap((entry): EveTraceState["llmInputs"] => { - if (!isObject(entry)) { - return []; - } - const key = entry["key"]; - const input = entry["input"]; - return typeof key === "string" && isCapturedModelInput(input) - ? [{ input, key }] - : []; - }) - .slice(-MAX_STORED_LLM_INPUTS) - : []; - const reasoningBlocks = Array.isArray(state["reasoningBlocks"]) - ? state["reasoningBlocks"] - .flatMap((entry): EveTraceState["reasoningBlocks"] => { - if (!isObject(entry)) { - return []; - } - const content = entry["content"]; - const eventAt = entry["eventAt"]; - const key = entry["key"]; - return typeof content === "string" && - (eventAt === undefined || typeof eventAt === "string") && - typeof key === "string" - ? [ - { - content, - ...(typeof eventAt === "string" ? { eventAt } : {}), - key, - }, - ] - : []; - }) - .slice(-MAX_STORED_REASONING_BLOCKS) - : []; - const stepStarts = Array.isArray(state["stepStarts"]) - ? state["stepStarts"] - .flatMap((entry): EveTraceState["stepStarts"] => { - if (!isObject(entry)) { - return []; - } - const ordinal = entry["ordinal"]; - const open = entry["open"]; - const stepIndex = entry["stepIndex"]; - const turnId = entry["turnId"]; - return typeof ordinal === "number" && - Number.isInteger(ordinal) && - ordinal >= 0 && - typeof open === "boolean" && - typeof stepIndex === "number" && - Number.isInteger(stepIndex) && - typeof turnId === "string" - ? [{ open, ordinal, stepIndex, turnId }] - : []; - }) - .slice(-MAX_STORED_STEP_STARTS) - : []; - return { - llmInputs, - metadata: { ...metadata }, - reasoningBlocks, - spanReferences, - stepStarts, - }; -} - -function readEveTraceState( - state: EveStateHandle, -): EveTraceState { - try { - return normalizeEveTraceState(state.get()); - } catch { - return emptyEveTraceState(); - } -} - -function storeEveReasoning( - state: EveStateHandle, - sessionId: string, - event: Extract, -): readonly EveReasoningBlock[] { - const eventAt = event.meta?.at; - const key = llmInputKey(sessionId, event.data.turnId, event.data.stepIndex); - let stored: readonly EveReasoningBlock[] = []; - state.update((current) => { - const normalized = normalizeEveTraceState(current); - const alreadyStored = normalized.reasoningBlocks.some( - (entry) => - entry.content === event.data.reasoning && - entry.eventAt === eventAt && - entry.key === key, - ); - const reasoningBlocks = alreadyStored - ? normalized.reasoningBlocks - : [ - ...normalized.reasoningBlocks, - { - content: event.data.reasoning, - ...(eventAt ? { eventAt } : {}), - key, - }, - ].slice(-MAX_STORED_REASONING_BLOCKS); - stored = reasoningBlocks.flatMap((entry) => - entry.key === key - ? [ - { - content: entry.content, - ...(entry.eventAt ? { eventAt: entry.eventAt } : {}), - }, - ] - : [], - ); - return alreadyStored ? normalized : { ...normalized, reasoningBlocks }; - }); - return stored; -} - -function readStoredEveReasoning( - state: EveStateHandle, - sessionId: string, - turnId: string, - stepIndex: number, -): readonly EveReasoningBlock[] { - const key = llmInputKey(sessionId, turnId, stepIndex); - return readEveTraceState(state).reasoningBlocks.flatMap((entry) => - entry.key === key - ? [ - { - content: entry.content, - ...(entry.eventAt ? { eventAt: entry.eventAt } : {}), - }, - ] - : [], - ); -} - -function clearStoredEveReasoning( - state: EveStateHandle, - sessionId: string, - turnId: string, - stepIndex: number, -): void { - const key = llmInputKey(sessionId, turnId, stepIndex); - state.update((current) => { - const normalized = normalizeEveTraceState(current); - return { - ...normalized, - reasoningBlocks: normalized.reasoningBlocks.filter( - (entry) => entry.key !== key, - ), - }; - }); -} - -function eveOutputMessage(output: unknown): unknown { - return Array.isArray(output) && isObject(output[0]) - ? output[0]["message"] - : undefined; -} - -function mergeEveReasoning( - output: unknown, - reasoning: readonly { content: string }[], -): unknown | undefined { - if (reasoning.length === 0) { - return output; - } - - const choice = Array.isArray(output) && isObject(output[0]) ? output[0] : {}; - const message = isObject(choice["message"]) ? choice["message"] : {}; - return [ - { - ...choice, - index: typeof choice["index"] === "number" ? choice["index"] : 0, - message: { - ...message, - content: "content" in message ? message["content"] : null, - reasoning: reasoning.map((block) => ({ content: block.content })), - role: - typeof message["role"] === "string" ? message["role"] : "assistant", - }, - }, - ]; -} - -function captureEveModelInput( - state: EveStateHandle, - input: EveInstrumentationStepStartedEventInput, -): void { - const sessionId = input.session.id; - const turnId = input.turn.id; - const stepIndex = input.step.index; - const captured = capturedModelInput(input.modelInput); - if (!captured) { - return; - } - - const key = llmInputKey(sessionId, turnId, stepIndex); - state.update((current) => { - const normalized = normalizeEveTraceState(current); - const llmInputs = [...normalized.llmInputs, { input: captured, key }]; - return { - ...normalized, - llmInputs: llmInputs.slice(-MAX_STORED_LLM_INPUTS), - }; - }); -} - -function consumeCapturedEveModelInput( - state: EveStateHandle, - sessionId: string, - turnId: string, - stepIndex: number, -): CapturedEveModelInput | undefined { - try { - const key = llmInputKey(sessionId, turnId, stepIndex); - let input: CapturedEveModelInput | undefined; - state.update((current) => { - const normalized = normalizeEveTraceState(current); - const index = normalized.llmInputs.findIndex( - (candidate) => candidate.key === key, - ); - if (index < 0) { - return normalized; - } - input = normalized.llmInputs[index]?.input; - return { - ...normalized, - llmInputs: normalized.llmInputs.filter( - (_, candidateIndex) => candidateIndex !== index, - ), - }; - }); - return input; - } catch (error) { - debugLogger.warn("Error in Eve LLM input consumption:", error); - return undefined; - } -} - -export function capturedModelInput( - modelInput: unknown, -): CapturedEveModelInput | undefined { - try { - if (!isObject(modelInput) || !Array.isArray(modelInput["messages"])) { - return undefined; - } - const value: CapturedEveModelMessage[] = []; - const instructions = modelInput["instructions"]; - if (typeof instructions === "string") { - value.push({ content: instructions, role: "system" }); - } else if (Array.isArray(instructions)) { - for (const instruction of instructions) { - const captured = capturedEveModelMessage(instruction); - if (captured?.role === "system") { - value.push(captured); - } - } - } else { - const captured = capturedEveModelMessage(instructions); - if (captured?.role === "system") { - value.push(captured); - } - } - for (const message of modelInput["messages"]) { - const captured = capturedEveModelMessage(message); - if (captured) { - value.push(captured); - } - } - - const cloned: unknown = JSON.parse(JSON.stringify(value)); - if (!Array.isArray(cloned)) { - return undefined; - } - return cloned; - } catch { - return undefined; - } -} - -function capturedEveModelMessage( - message: unknown, -): CapturedEveModelMessage | undefined { - if (!isObject(message)) { - return undefined; - } - const role = message["role"]; - if ( - role !== "system" && - role !== "user" && - role !== "assistant" && - role !== "tool" - ) { - return undefined; - } - const content = message["content"]; - if (typeof content === "string") { - return { content, role }; - } - if (!Array.isArray(content)) { - return undefined; - } - const capturedContent: Record[] = []; - for (const part of content) { - const captured = capturedEveModelContentPart(part); - if (captured) { - capturedContent.push(captured); - } - } - return { content: capturedContent, role }; -} - -function capturedEveModelContentPart( - part: unknown, -): Record | undefined { - if (!isObject(part) || typeof part["type"] !== "string") { - return undefined; - } - switch (part["type"]) { - case "text": - case "reasoning": { - const text = part["text"]; - return typeof text === "string" - ? { text, type: part["type"] } - : undefined; - } - case "image": - if (!Object.hasOwn(part, "image")) { - return undefined; - } - return { - image: part["image"], - ...(typeof part["mediaType"] === "string" - ? { mediaType: part["mediaType"] } - : {}), - type: "image", - }; - case "file": - case "reasoning-file": { - if ( - !Object.hasOwn(part, "data") || - typeof part["mediaType"] !== "string" - ) { - return undefined; - } - return { - data: part["data"], - ...(part["type"] === "file" && typeof part["filename"] === "string" - ? { filename: part["filename"] } - : {}), - mediaType: part["mediaType"], - type: part["type"], - }; - } - case "custom": - return { - ...(typeof part["kind"] === "string" ? { kind: part["kind"] } : {}), - type: "custom", - }; - case "tool-call": { - if ( - typeof part["toolCallId"] !== "string" || - typeof part["toolName"] !== "string" - ) { - return undefined; - } - return { - input: part["input"], - ...(typeof part["providerExecuted"] === "boolean" - ? { providerExecuted: part["providerExecuted"] } - : {}), - toolCallId: part["toolCallId"], - toolName: part["toolName"], - type: "tool-call", - }; - } - case "tool-result": { - const output = part["output"]; - if ( - typeof part["toolCallId"] !== "string" || - typeof part["toolName"] !== "string" || - !isObject(output) || - typeof output["type"] !== "string" - ) { - return undefined; - } - let capturedOutput: Record; - switch (output["type"]) { - case "text": - case "error-text": { - if (typeof output["value"] !== "string") { - return undefined; - } - capturedOutput = { - type: output["type"], - value: output["value"], - }; - break; - } - case "json": - case "error-json": - capturedOutput = { - type: output["type"], - value: output["value"], - }; - break; - case "execution-denied": - capturedOutput = { - ...(typeof output["reason"] === "string" - ? { reason: output["reason"] } - : {}), - type: "execution-denied", - }; - break; - case "content": { - if (!Array.isArray(output["value"])) { - return undefined; - } - const value: Record[] = []; - for (const outputPart of output["value"]) { - const captured = capturedEveModelContentPart(outputPart); - if (captured) { - value.push(captured); - } - } - capturedOutput = { - type: "content", - value, - }; - break; - } - default: - return undefined; - } - return { - output: capturedOutput, - toolCallId: part["toolCallId"], - toolName: part["toolName"], - type: "tool-result", - }; - } - case "tool-approval-request": { - if ( - typeof part["approvalId"] !== "string" || - typeof part["toolCallId"] !== "string" - ) { - return undefined; - } - return { - approvalId: part["approvalId"], - ...(typeof part["isAutomatic"] === "boolean" - ? { isAutomatic: part["isAutomatic"] } - : {}), - ...(typeof part["signature"] === "string" - ? { signature: part["signature"] } - : {}), - toolCallId: part["toolCallId"], - type: "tool-approval-request", - }; - } - case "tool-approval-response": { - if ( - typeof part["approvalId"] !== "string" || - typeof part["approved"] !== "boolean" - ) { - return undefined; - } - return { - approvalId: part["approvalId"], - approved: part["approved"], - ...(typeof part["providerExecuted"] === "boolean" - ? { providerExecuted: part["providerExecuted"] } - : {}), - ...(typeof part["reason"] === "string" - ? { reason: part["reason"] } - : {}), - type: "tool-approval-response", - }; - } - case "file-data": - case "image-data": { - if ( - typeof part["data"] !== "string" || - typeof part["mediaType"] !== "string" - ) { - return undefined; - } - return { - data: part["data"], - ...(part["type"] === "file-data" && typeof part["filename"] === "string" - ? { filename: part["filename"] } - : {}), - mediaType: part["mediaType"], - type: part["type"], - }; - } - case "file-url": - case "image-url": { - if (typeof part["url"] !== "string") { - return undefined; - } - return { - ...(part["type"] === "file-url" && typeof part["mediaType"] === "string" - ? { mediaType: part["mediaType"] } - : {}), - type: part["type"], - url: part["url"], - }; - } - case "file-id": - case "image-file-id": { - const fileId = part["fileId"]; - if (typeof fileId !== "string" && !isObject(fileId)) { - return undefined; - } - return { fileId, type: part["type"] }; - } - case "file-reference": - case "image-file-reference": { - if (!isObject(part["providerReference"])) { - return undefined; - } - return { - providerReference: part["providerReference"], - type: part["type"], - }; - } - default: - return undefined; - } -} - -function isCapturedModelInput(input: unknown): input is CapturedEveModelInput { - return ( - Array.isArray(input) && - input.every( - (message) => - isObject(message) && - (message["role"] === "system" || - message["role"] === "user" || - message["role"] === "assistant" || - message["role"] === "tool") && - (typeof message["content"] === "string" || - (Array.isArray(message["content"]) && - message["content"].every(isObject))), - ) - ); -} - -function llmInputKey( - sessionId: string, - turnId: string, - stepIndex: number, -): string { - return `${sessionId}\0${turnId}\0${stepIndex}`; -} - -function modelMetadataFromRuntime(runtime: unknown): Record { - if (!isObject(runtime)) { - return {}; - } - const modelId = runtime["modelId"]; - return typeof modelId === "string" && !modelId.trim().startsWith("dynamic:") - ? modelMetadataFromModelId(modelId) - : {}; -} - -function modelMetadataFromModelId(modelId: string): Record { - const normalized = modelId.trim(); - if (!normalized) { - return {}; - } - - const slashIndex = normalized.indexOf("/"); - if (slashIndex > 0 && slashIndex < normalized.length - 1) { - return { - model: normalized.slice(slashIndex + 1), - provider: normalized.slice(0, slashIndex), - }; - } - - return { - model: normalized, - }; -} - -function sessionIdFromContext(ctx: unknown): string | undefined { - if (!isObject(ctx)) { - return undefined; - } - const session = ctx["session"]; - if (!isObject(session)) { - return undefined; - } - const id = session["id"]; - return typeof id === "string" ? id : undefined; -} - -function toolMetadataFromTurn(turn: TurnState): Record { - const { model: _model, provider: _provider, ...metadata } = turn.metadata; - return metadata; -} - -function isToolCallAction( - action: unknown, -): action is EveRuntimeToolCallActionRequest { - return ( - isObject(action) && - action["kind"] === "tool-call" && - typeof action["callId"] === "string" && - typeof action["toolName"] === "string" && - isObject(action["input"]) - ); -} - -function isLocalSubagentCallAction( - action: unknown, -): action is Extract { - return ( - isObject(action) && - action["kind"] === "subagent-call" && - typeof action["callId"] === "string" && - isObject(action["input"]) - ); -} - -function isTraceableActionRequest( - action: unknown, -): action is - | EveRuntimeToolCallActionRequest - | Extract { - return isToolCallAction(action) || isLocalSubagentCallAction(action); -} - -function isToolResult( - result: unknown, -): result is EveRuntimeToolResultActionResult { - return ( - isObject(result) && - result["kind"] === "tool-result" && - typeof result["callId"] === "string" && - typeof result["toolName"] === "string" - ); -} - -function isSubagentResult( - result: unknown, -): result is Extract { - return ( - isObject(result) && - result["kind"] === "subagent-result" && - typeof result["callId"] === "string" && - typeof result["subagentName"] === "string" - ); -} - -function normalizedFinishReason( - finishReason: EveAssistantStepFinishReason, -): string { - switch (finishReason) { - case "content-filter": - return "content_filter"; - case "tool-calls": - return "tool_calls"; - default: - return finishReason; - } -} - -function errorFromMessage( - message: string, - code: string, - details?: unknown, -): Error { - const error = new Error(`${code}: ${message}`); - if (details !== undefined) { - error.cause = details; - } - return error; -} - -function actionResultError( - error: EveActionResultError | undefined, - output: unknown, -): Error { - if (error) { - return errorFromMessage(error.message, error.code); - } - const result = new Error("Eve action failed"); - if (output !== undefined) { - result.cause = output; - } - return result; -} - -function eventTime(event: { - readonly meta?: { readonly at: string }; -}): number | undefined { - if (!event.meta?.at) { - return undefined; - } - const timestamp = Date.parse(event.meta.at); - return Number.isFinite(timestamp) ? timestamp / 1000 : undefined; -} - -function turnKey(sessionId: string, turnId: string): string { - return `${sessionId}:${turnId}`; -} - -function toolKey(sessionId: string, callId: string): string { - return `${sessionId}:${callId}`; -} - -export async function generateEveIds( - kind: EveEntityKind, - ...parts: string[] -): Promise<{ rowId: string; spanId: string }> { - const [rowId, spanId] = await Promise.all([ - deterministicEveId(`eve:row:${kind}`, ...parts), - deterministicEveId(`eve:${kind}`, ...parts), - ]); - return { rowId, spanId }; -} - -async function deterministicEveId(...parts: string[]): Promise { - const data = new TextEncoder().encode( - parts.map((part) => `${part.length}:${part}`).join("\0"), - ); - const digest = await globalThis.crypto.subtle.digest("SHA-256", data); - const bytes = Array.from(new Uint8Array(digest, 0, 16)); - const hex = bytes.map((byte) => byte.toString(16).padStart(2, "0")).join(""); - return `${hex.slice(0, 8)}-${hex.slice(8, 12)}-${hex.slice(12, 16)}-${hex.slice(16, 20)}-${hex.slice(20)}`; -} diff --git a/js/src/instrumentation/plugins/eve-provider.test.ts b/js/src/instrumentation/plugins/eve-provider.test.ts deleted file mode 100644 index fd26185a9..000000000 --- a/js/src/instrumentation/plugins/eve-provider.test.ts +++ /dev/null @@ -1,905 +0,0 @@ -import { - afterEach, - beforeAll, - beforeEach, - describe, - expect, - expectTypeOf, - it, - vi, -} from "vitest"; -import * as braintrustExports from "../../exports"; -import { configureNode } from "../../node/config"; -import { _exportsForTestingOnly, initLogger } from "../../logger"; -import type { - EveJsonValue, - EveProviderContext, -} from "../../vendor-sdk-types/eve"; -import { mergeRowBatch } from "../../../util/index"; -import * as instrumentationExports from "../index"; -import { braintrustEveInstrumentation } from "./eve-instrumentation"; - -function providerContext(): EveProviderContext { - let value: EveJsonValue | undefined; - return { - state: { - get: () => value, - set: (next) => { - value = next; - }, - }, - }; -} - -try { - configureNode(); -} catch { - // Best-effort initialization for test environments. -} - -describe("braintrustEveInstrumentation provider lifecycle", () => { - let backgroundLogger: ReturnType< - typeof _exportsForTestingOnly.useTestBackgroundLogger - >; - - beforeAll(async () => { - await _exportsForTestingOnly.simulateLoginForTests(); - }); - - beforeEach(() => { - backgroundLogger = _exportsForTestingOnly.useTestBackgroundLogger(); - initLogger({ - projectName: "eve-provider.test.ts", - projectId: "test-project-id", - }); - }); - - afterEach(() => { - _exportsForTestingOnly.clearTestBackgroundLogger(); - }); - - it("exports an Eve content provider from public entrypoints", async () => { - const setup = vi.fn(async () => undefined); - const instrumentation = braintrustEveInstrumentation({ setup }); - expectTypeOf(instrumentation).toBeAny(); - expect(instrumentation).toMatchObject({ - setup: expect.any(Function), - tracePolicy: expect.any(Function), - }); - const context = { agentName: "test", frameworkVersion: "0.72.0" }; - await instrumentation.setup(context); - expect(setup).toHaveBeenCalledExactlyOnceWith(context); - // Eve 0.62+ rejects an own `capture` key, older versions still read it. - expect(Object.hasOwn(instrumentation, "capture")).toBe(false); - expect(instrumentation.capture).toBe("content"); - expect(instrumentation.tracePolicy?.({ audience: "private" })).toEqual({ - emit: true, - recordInputs: true, - recordOutputs: true, - }); - expect( - Reflect.get(instrumentation, Symbol.for("eve.instrumentation.provider")), - ).toBe(true); - expect(braintrustExports.braintrustEveInstrumentation).toBe( - braintrustEveInstrumentation, - ); - expect(instrumentationExports.braintrustEveInstrumentation).toBe( - braintrustEveInstrumentation, - ); - expect(braintrustExports).not.toHaveProperty("braintrustEveProvider"); - expect(instrumentationExports).not.toHaveProperty("braintrustEveProvider"); - }); - - it("records durable task, model, and action spans", async () => { - const provider = braintrustEveInstrumentation({ - metadata: { scenario: "eve-provider-unit" }, - }); - const turnContext = providerContext(); - const modelContext = providerContext(); - const actionContext = providerContext(); - const scope = { - attemptId: "session-1:turn-1:0:0", - attemptIndex: 0, - rootSessionId: "session-1", - sessionId: "session-1", - stepIndex: 0, - turnId: "turn-1", - }; - - await provider.events?.["turn.started"]?.( - { - idempotencyKey: "turn:session-1:turn-1", - rootSessionId: "session-1", - sequence: 0, - sessionId: "session-1", - turnId: "turn-1", - type: "turn.started", - }, - turnContext, - ); - await provider.events?.["model.call.started"]?.( - { - idempotencyKey: "model:session-1:turn-1:0:0:0", - input: { - instructions: "Be concise", - messages: [{ content: "Find Eve", role: "user" }], - }, - model: { modelId: "qwen/qwen3", provider: "openrouter" }, - scope, - type: "model.call.started", - }, - modelContext, - ); - - // A terminal can run in a replacement process with only Eve's durable - // operation state available. - const replacement = braintrustEveInstrumentation({ - metadata: { scenario: "eve-provider-unit" }, - }); - await replacement.events?.["model.call.completed"]?.( - { - content: [ - { text: "Thinking", type: "reasoning" }, - { - callId: "call-search", - input: { query: "Eve" }, - toolName: "search", - type: "tool-call", - }, - ], - finishReason: "tool-calls", - idempotencyKey: "model:session-1:turn-1:0:0:0", - scope, - type: "model.call.completed", - usage: { - inputTokenDetails: { - cacheReadTokens: 3, - cacheWriteTokens: 4, - }, - inputTokens: 10, - outputTokens: 5, - }, - }, - modelContext, - ); - await provider.events?.["action.started"]?.( - { - callId: "call-search", - idempotencyKey: "action:session-1:turn-1:call-search", - input: { query: "Eve" }, - kind: "tool-call", - name: "search", - scope, - type: "action.started", - }, - actionContext, - ); - const replayedActionContext = providerContext(); - await provider.events?.["action.started"]?.( - { - callId: "call-search", - idempotencyKey: "action:session-1:turn-1:call-search", - input: { query: "Eve" }, - kind: "tool-call", - name: "search", - scope, - type: "action.started", - }, - replayedActionContext, - ); - await provider.events?.["action.completed"]?.( - { - acceptedAtMs: Date.now(), - idempotencyKey: "action:session-1:turn-1:call-search", - outcome: "completed", - output: { output: { title: "Eve" }, type: "result" }, - scope, - type: "action.completed", - }, - actionContext, - ); - await provider.events?.["action.failed"]?.( - { - error: new Error("stale terminal replay"), - errorCode: "ACTION_CANCELLED", - idempotencyKey: "action:session-1:turn-1:call-search", - outcome: "cancelled", - scope, - type: "action.failed", - }, - replayedActionContext, - ); - await replacement.events?.["turn.completed"]?.( - { - idempotencyKey: "turn:session-1:turn-1", - sessionId: "session-1", - turnId: "turn-1", - type: "turn.completed", - }, - turnContext, - ); - - const writes = (await backgroundLogger.drain()) as Array< - Record & { id: string } - >; - const spans = mergeRowBatch([...writes].reverse()); - const turn = spans.find( - (span) => span.span_attributes?.name === "eve.turn", - ); - const model = spans.find( - (span) => span.span_attributes?.name === "eve.step", - ); - const action = spans.find( - (span) => span.span_attributes?.name === "search", - ); - - expect(turn).toMatchObject({ - input: [{ content: "Find Eve", role: "user" }], - metadata: { - "eve.session_id": "session-1", - scenario: "eve-provider-unit", - }, - span_attributes: { type: "task" }, - span_parents: [], - }); - // The only model reply requested tool calls, so the turn has no answer. - expect(turn).not.toHaveProperty("output"); - expect(model).toMatchObject({ - input: [ - { content: "Be concise", role: "system" }, - { content: "Find Eve", role: "user" }, - ], - metadata: { - "eve.session_id": "session-1", - model: "qwen/qwen3", - provider: "openrouter", - }, - metrics: { - completion_tokens: 5, - prompt_cache_creation_tokens: 4, - prompt_cached_tokens: 3, - prompt_tokens: 10, - tokens: 15, - }, - output: [ - { - finish_reason: "tool_calls", - message: { - reasoning: [{ content: "Thinking" }], - tool_calls: [ - { - function: { - arguments: '{"query":"Eve"}', - name: "search", - }, - }, - ], - }, - }, - ], - span_attributes: { type: "llm" }, - span_parents: [turn?.span_id], - }); - expect(action).toMatchObject({ - input: { query: "Eve" }, - metadata: { - "eve.action_kind": "tool-call", - "eve.session_id": "session-1", - }, - output: { title: "Eve" }, - span_attributes: { type: "tool" }, - span_parents: [turn?.span_id], - }); - expect(action).not.toHaveProperty("error"); - }); - - it.each(["0.34.0", "0.70.0", "0.71.0"])( - "ignores duplicate AI SDK tool events on Eve %s", - async (frameworkVersion) => { - const provider = braintrustEveInstrumentation({}); - await provider.setup({ agentName: "test", frameworkVersion }); - const context = providerContext(); - const scope = { - attemptId: "session:turn:0:0", - attemptIndex: 0, - sessionId: "session", - stepIndex: 0, - turnId: "turn", - }; - const idempotencyKey = "tool:session:turn:0:0:call-search"; - await provider.events["tool.call.started"]( - { - callId: "call-search", - idempotencyKey, - input: { query: "Eve" }, - scope, - toolName: "search", - type: "tool.call.started", - }, - context, - ); - await provider.events["tool.call.completed"]( - { - idempotencyKey, - scope, - output: { type: "result", output: "Eve" }, - type: "tool.call.completed", - }, - context, - ); - await provider.events["tool.call.failed"]( - { - idempotencyKey, - scope, - error: new Error("stale replay"), - type: "tool.call.failed", - }, - context, - ); - expect(context.state.get()).toBeUndefined(); - expect(await backgroundLogger.drain()).toEqual([]); - }, - ); - - it.each([false, true])( - "records Eve 0.72+ tool calls with replacement process: %s", - async (replaceProvider) => { - const provider = braintrustEveInstrumentation({}); - await provider.setup({ agentName: "test", frameworkVersion: "0.72.0" }); - const scope = { - attemptId: "session:turn:0:0", - attemptIndex: 0, - rootSessionId: "session", - sessionId: "session", - stepIndex: 0, - turnId: "turn", - }; - await provider.events["turn.started"]( - { - idempotencyKey: "turn:session:turn", - rootSessionId: "session", - sequence: 0, - sessionId: "session", - turnId: "turn", - type: "turn.started", - }, - providerContext(), - ); - const outcomes = ["result", "error", "failed", "cancelled"] as const; - for (const outcome of outcomes) { - const context = providerContext(); - const idempotencyKey = `action:session:turn:${outcome}`; - await provider.events["tool.call.started"]( - { - callId: outcome, - idempotencyKey, - input: { query: outcome }, - scope, - toolName: outcome, - ...(outcome === "error" ? { parentCallId: "result" } : {}), - startedAtMs: 122_000, - type: "tool.call.started", - }, - context, - ); - const terminal = replaceProvider - ? braintrustEveInstrumentation({}) - : provider; - const type = - outcome === "failed" || outcome === "cancelled" - ? "tool.call.failed" - : "tool.call.completed"; - await terminal.events[type]( - { - idempotencyKey, - scope, - type, - completedAtMs: 123_000, - ...(outcome === "result" ? { acceptedAtMs: 124_000 } : {}), - ...(outcome === "failed" - ? { error: new Error("tool failed") } - : outcome === "cancelled" - ? { outcome, errorCode: "TOOL_CANCELLED" } - : { - output: - outcome === "error" - ? { - type: "error", - error: new Error("tool returned error"), - } - : { type: "result", output: { title: "Eve" } }, - }), - }, - context, - ); - // Replayed terminals must not overwrite the successful result. - await terminal.events["tool.call.failed"]( - { - idempotencyKey, - scope, - type: "tool.call.failed", - error: new Error("stale replay"), - }, - context, - ); - expect(context.state.get()).toBeUndefined(); - } - const writes = (await backgroundLogger.drain()) as Array< - Record & { id: string } - >; - const spans = mergeRowBatch([...writes].reverse()); - const turn = spans.find( - (span) => span.span_attributes?.name === "eve.turn", - ); - expect(spans).toHaveLength(5); - for (const outcome of outcomes) { - const tool = spans.find( - (span) => span.span_attributes?.name === outcome, - ); - expect(tool).toMatchObject({ - input: { query: outcome }, - metadata: { "eve.action_kind": "tool-call" }, - metrics: { start: 122, end: outcome === "result" ? 124 : 123 }, - root_span_id: turn?.root_span_id, - span_parents: [ - outcome === "error" - ? spans.find((span) => span.span_attributes?.name === "result") - ?.span_id - : turn?.span_id, - ], - span_attributes: { name: outcome, type: "tool" }, - }); - if (outcome === "result") { - expect(tool?.output).toEqual({ title: "Eve" }); - expect(tool).not.toHaveProperty("error"); - } else { - expect(tool?.error).toContain( - outcome === "error" - ? "tool returned error" - : outcome === "failed" - ? "tool failed" - : "TOOL_CANCELLED", - ); - } - } - }, - ); - - it("records the turn's new user message and final reply on the turn span", async () => { - const provider = braintrustEveInstrumentation({}); - const scope = (stepIndex: number) => ({ - attemptId: `session-2:turn-2:${stepIndex}:0`, - attemptIndex: 0, - rootSessionId: "session-2", - sessionId: "session-2", - stepIndex, - turnId: "turn-2", - }); - const modelCall = async ( - stepIndex: number, - messages: unknown[], - finishReason: string, - content: unknown[], - ) => { - const context = providerContext(); - const idempotencyKey = `model:session-2:turn-2:${stepIndex}:0:0`; - await provider.events?.["model.call.started"]?.( - { - idempotencyKey, - input: { instructions: "Be concise", messages }, - model: { modelId: "qwen/qwen3", provider: "openrouter" }, - scope: scope(stepIndex), - type: "model.call.started", - }, - context, - ); - await provider.events?.["model.call.completed"]?.( - { - content, - finishReason, - idempotencyKey, - scope: scope(stepIndex), - type: "model.call.completed", - usage: { inputTokens: 1, outputTokens: 1 }, - }, - context, - ); - }; - const history = [ - { content: "Earlier question", role: "user" }, - { content: "Earlier answer", role: "assistant" }, - { content: "Find Eve again", role: "user" }, - ]; - const turnContext = providerContext(); - - await provider.events?.["turn.started"]?.( - { - idempotencyKey: "turn:session-2:turn-2", - rootSessionId: "session-2", - sequence: 1, - sessionId: "session-2", - turnId: "turn-2", - type: "turn.started", - }, - turnContext, - ); - await modelCall(0, history, "tool-calls", [ - { text: "Checking", type: "text" }, - { - callId: "call-search", - input: { query: "Eve" }, - toolName: "search", - type: "tool-call", - }, - ]); - await modelCall( - 1, - [ - ...history, - { content: "Checking", role: "assistant" }, - { content: "[Agents]", role: "user" }, - ], - "stop", - [{ text: "Eve is a framework", type: "text" }], - ); - await provider.events?.["turn.completed"]?.( - { - idempotencyKey: "turn:session-2:turn-2", - sessionId: "session-2", - turnId: "turn-2", - type: "turn.completed", - }, - turnContext, - ); - - const writes = (await backgroundLogger.drain()) as Array< - Record & { id: string } - >; - const turn = mergeRowBatch([...writes].reverse()).find( - (span) => span.span_attributes?.name === "eve.turn", - ); - expect(turn).toMatchObject({ - input: [{ content: "Find Eve again", role: "user" }], - output: "Eve is a framework", - }); - }); - - it("nests turns under the caller's propagated trace context", async () => { - const traceId = "4bf92f3577b34da6a3ce929d0e0e4736"; - const callerSpanId = "00f067aa0ba902b7"; - const turnContext = providerContext(); - const modelContext = providerContext(); - const scope = { - attemptId: "traced-session:turn_0:0:0", - attemptIndex: 0, - rootSessionId: "traced-session", - sessionId: "traced-session", - stepIndex: 0, - turnId: "turn_0", - }; - const turnStarted = { - idempotencyKey: "turn:traced-session:turn_0", - parentTraceContext: { - isRemote: true, - spanId: callerSpanId, - traceFlags: 1, - traceId, - }, - rootSessionId: "traced-session", - sequence: 0, - sessionId: "traced-session", - turnId: "turn_0", - type: "turn.started" as const, - }; - - const provider = braintrustEveInstrumentation({}); - await provider.events?.["turn.started"]?.(turnStarted, turnContext); - - // A replacement process replays the turn start from Eve's durable state - // before its model calls run. - const replacement = braintrustEveInstrumentation({}); - await replacement.events?.["turn.started"]?.(turnStarted, turnContext); - await replacement.events?.["model.call.started"]?.( - { - idempotencyKey: "model:traced-session:turn_0:0:0:0", - input: { messages: [{ content: "Hi", role: "user" }] }, - model: { modelId: "qwen/qwen3", provider: "openrouter" }, - scope, - type: "model.call.started", - }, - modelContext, - ); - - const writes = (await backgroundLogger.drain()) as Array< - Record & { id: string } - >; - const spans = mergeRowBatch([...writes].reverse()); - const turns = spans.filter( - (span) => span.span_attributes?.name === "eve.turn", - ); - const model = spans.find( - (span) => span.span_attributes?.name === "eve.step", - ); - - expect(turns).toHaveLength(1); - expect(turns[0]).toMatchObject({ - root_span_id: traceId, - span_parents: [callerSpanId], - }); - expect(model).toMatchObject({ - root_span_id: traceId, - span_parents: [turns[0]?.span_id], - }); - }); - - it.each(["action", "tool.call"] as const)( - "preserves the trace root for depth-two subagents after provider replacement (%s)", - async (eventFamily) => { - const metadata = { scenario: "eve-provider-replacement" }; - const provider = braintrustEveInstrumentation({ metadata }); - const rootContext = providerContext(); - const childContext = providerContext(); - const grandchildContext = providerContext(); - const childActionContext = providerContext(); - const grandchildActionContext = providerContext(); - const childTurn = { - idempotencyKey: "turn:child-session:turn_0", - parentLineage: { - callId: "call-child", - sessionId: "root-session", - turnId: "turn_0", - }, - rootSessionId: "root-session", - sequence: 0, - sessionId: "child-session", - turnId: "turn_0", - type: "turn.started" as const, - }; - - await provider.events?.["turn.started"]?.( - { - idempotencyKey: "turn:root-session:turn_0", - rootSessionId: "root-session", - sequence: 0, - sessionId: "root-session", - turnId: "turn_0", - type: "turn.started", - }, - rootContext, - ); - await provider.events?.[`${eventFamily}.started`]?.( - { - callId: "call-child", - idempotencyKey: "action:root-session:turn_0:call-child", - input: { message: "delegate once" }, - kind: "subagent-call", - ...(eventFamily === "action" - ? { name: "child" } - : { toolName: "child" }), - scope: { - attemptId: "root-attempt", - attemptIndex: 0, - rootSessionId: "root-session", - sessionId: "root-session", - stepIndex: 0, - turnId: "turn_0", - }, - type: `${eventFamily}.started`, - }, - childActionContext, - ); - await provider.events?.["turn.started"]?.(childTurn, childContext); - await provider.events?.[`${eventFamily}.started`]?.( - { - callId: "call-grandchild", - idempotencyKey: "action:child-session:turn_0:call-grandchild", - input: { message: "delegate twice" }, - // Eve 0.70+ reports agent calls as tool calls. - kind: "tool-call", - ...(eventFamily === "action" - ? { name: "grandchild" } - : { toolName: "grandchild" }), - scope: { - attemptId: "child-attempt", - attemptIndex: 0, - rootSessionId: "root-session", - sessionId: "child-session", - stepIndex: 0, - turnId: "turn_0", - }, - type: `${eventFamily}.started`, - }, - grandchildActionContext, - ); - - // The grandchild can start in a replacement process without replaying its - // parent turn through this provider instance. - const replacement = braintrustEveInstrumentation({ metadata }); - await replacement.events?.["turn.started"]?.( - { - idempotencyKey: "turn:grandchild-session:turn_0", - parentLineage: { - callId: "call-grandchild", - sessionId: "child-session", - turnId: "turn_0", - }, - rootSessionId: "root-session", - sequence: 0, - sessionId: "grandchild-session", - turnId: "turn_0", - type: "turn.started", - }, - grandchildContext, - ); - - const writes = (await backgroundLogger.drain()) as Array< - Record & { id: string } - >; - const spans = mergeRowBatch([...writes].reverse()); - const turnForSession = (sessionId: string) => - spans.find( - (span) => - span.span_attributes?.name === "eve.turn" && - span.metadata?.["eve.session_id"] === sessionId, - ); - const root = turnForSession("root-session"); - const child = turnForSession("child-session"); - const grandchild = turnForSession("grandchild-session"); - const childAction = spans.find( - (span) => span.span_attributes?.name === "child", - ); - const grandchildAction = spans.find( - (span) => span.span_attributes?.name === "grandchild", - ); - - expect(root).toBeDefined(); - expect(childContext.state.get()).toMatchObject({ - exported: expect.any(String), - rootSpanId: root?.root_span_id, - }); - expect(child?.span_parents).toEqual([childAction?.span_id]); - expect(child?.root_span_id).toBe(root?.root_span_id); - expect(grandchild?.span_parents).toEqual([grandchildAction?.span_id]); - expect(grandchild?.root_span_id).toBe(root?.root_span_id); - }, - ); - - it("skips skill loads and contains malformed or failed lifecycle data", async () => { - const provider = braintrustEveInstrumentation({}); - const context = providerContext(); - const scope = { - attemptId: "attempt-failed", - attemptIndex: 0, - sessionId: "session-failed", - stepIndex: 0, - turnId: "turn-failed", - }; - - await expect( - provider.events?.["action.started"]?.( - { - callId: "skill", - idempotencyKey: "action:session-failed:turn-failed:skill", - kind: "load-skill", - name: "load", - scope, - type: "action.started", - }, - context, - ), - ).resolves.toBeUndefined(); - await expect( - provider.events?.["action.failed"]?.( - { - errorCode: "rejected", - idempotencyKey: "action:session-failed:turn-failed:skill", - outcome: "rejected", - scope, - type: "action.failed", - }, - context, - ), - ).resolves.toBeUndefined(); - await expect( - provider.events?.["model.call.failed"]?.( - { - error: new Error("model failed"), - idempotencyKey: "missing-model", - scope, - type: "model.call.failed", - }, - providerContext(), - ), - ).resolves.toBeUndefined(); - - expect(await backgroundLogger.drain()).toEqual([]); - }); - - it("validates every provider handler before dispatch", async () => { - const provider = braintrustEveInstrumentation({}); - for (const [type, handler] of Object.entries(provider.events)) { - expect(typeof handler).toBe("function"); - if (typeof handler !== "function") { - throw new Error(`Expected an Eve provider handler for ${type}`); - } - await expect(handler({ type }, {})).resolves.toBeUndefined(); - } - - const throwingEvent = Object.defineProperty({}, "idempotencyKey", { - get() { - throw new Error("hostile event getter"); - }, - }); - await expect( - provider.events["turn.started"](throwingEvent, providerContext()), - ).resolves.toBeUndefined(); - expect(await backgroundLogger.drain()).toEqual([]); - }); - - it("omits malformed provider payload fields while keeping valid content", async () => { - const provider = braintrustEveInstrumentation({}); - const context = providerContext(); - const scope = { - attemptId: "attempt-partial", - attemptIndex: 0, - sessionId: "session-partial", - stepIndex: 0, - turnId: "turn-partial", - }; - - await provider.events["model.call.started"]( - { - idempotencyKey: "model-partial", - input: { - messages: [ - { content: 42, role: "user" }, - { content: "Keep this input", role: "user" }, - ], - }, - model: { modelId: "qwen/qwen3", provider: "openrouter" }, - scope, - type: "model.call.started", - }, - context, - ); - await provider.events["model.call.completed"]( - { - content: [ - null, - { text: 42, type: "reasoning" }, - { text: "Keep this output", type: "text" }, - { type: "future-content" }, - ], - finishReason: "stop", - idempotencyKey: "model-partial", - scope, - type: "model.call.completed", - usage: { inputTokens: "invalid", outputTokens: -1 }, - }, - context, - ); - - const writes = (await backgroundLogger.drain()) as Array< - Record & { id: string } - >; - const spans = mergeRowBatch([...writes].reverse()); - const model = spans.find( - (span) => span.span_attributes?.name === "eve.step", - ); - expect(model?.input).toEqual([ - { content: "Keep this input", role: "user" }, - ]); - expect(model?.output).toEqual([ - { - finish_reason: "stop", - index: 0, - message: { - content: "Keep this output", - role: "assistant", - }, - }, - ]); - expect(model?.metrics).not.toHaveProperty("prompt_tokens"); - expect(model?.metrics).not.toHaveProperty("completion_tokens"); - }); -}); diff --git a/js/src/instrumentation/plugins/eve-provider.ts b/js/src/instrumentation/plugins/eve-provider.ts deleted file mode 100644 index 911a4f9b0..000000000 --- a/js/src/instrumentation/plugins/eve-provider.ts +++ /dev/null @@ -1,962 +0,0 @@ -import { debugLogger } from "../../debug-logger"; -import { - NOOP_SPAN, - _internalStartSpanWithInitialMerge, - flush, - updateSpan, - withCurrent, -} from "../../logger"; -import type { Span } from "../../logger"; -import { LRUCache } from "../../lru-cache"; -import { - INSTRUMENTATION_NAMES, - withSpanInstrumentationName, -} from "../../span-origin"; -import { SpanTypeAttribute, isObject } from "../../../util/index"; -import type { - EveInstrumentationAttemptScope, - EveProviderActionStartedEvent, - EveProviderActionTerminalEvent, - EveProviderContext, - EveProviderDefinition, - EveProviderModelCallStartedEvent, - EveProviderModelCallTerminalEvent, - EveProviderStepAttemptTerminalEvent, - EveProviderToolCallStartedEvent, - EveProviderToolCallTerminalEvent, - EveProviderTurnStartedEvent, - EveProviderTurnTerminalEvent, - EveProviderUsage, -} from "../../vendor-sdk-types/eve"; -import { capturedModelInput, generateEveIds } from "./eve-plugin"; - -const MAX_EVE_PROVIDER_CACHE_ENTRIES = 10_000; - -type EveProviderOptions = { - metadata?: Record; - setup?: EveProviderDefinition["setup"]; -}; - -type StoredSpan = - | { exported: string; rootSpanId?: string; skip?: never } - | { skip: true }; - -type TurnContext = { - inputRecorded?: boolean; - rootSpanId: string; - // Only set when the turn span was opened by this process. - span?: Span; - spanId: string; -}; - -type ActiveSpan = { - span: Span; - turnKey: string; -}; - -type EveProviderEvent = - | EveProviderActionStartedEvent - | EveProviderActionTerminalEvent - | EveProviderModelCallStartedEvent - | EveProviderModelCallTerminalEvent - | EveProviderStepAttemptTerminalEvent - | EveProviderToolCallStartedEvent - | EveProviderToolCallTerminalEvent - | EveProviderTurnStartedEvent - | EveProviderTurnTerminalEvent; - -type EveProviderEventForType = - EveProviderEvent extends infer TEvent - ? TEvent extends { type: infer TEventType } - ? TType extends TEventType - ? TEvent & { type: TType } - : never - : never - : never; - -/** - * Braintrust-native instrumentation for Eve's provider lifecycle (eve@0.34+). - * - * Used by `braintrustEveInstrumentation` for Eve's instrumentation-provider - * lifecycle. Kept separate from the legacy event bridge so the public helper - * can support both APIs without exposing a second integration name. - */ -export function createEveInstrumentationProvider( - options: EveProviderOptions = {}, -): EveProviderDefinition { - const bridge = new EveProviderBridge(options.metadata); - return { - tracePolicy: () => ({ - emit: true, - recordInputs: true, - recordOutputs: true, - }), - events: { - "action.completed": providerHandler( - "action.completed", - bridge.handleActionTerminal.bind(bridge), - ), - "action.failed": providerHandler( - "action.failed", - bridge.handleActionTerminal.bind(bridge), - ), - "action.started": providerHandler( - "action.started", - bridge.handleActionStarted.bind(bridge), - ), - "model.call.completed": providerHandler( - "model.call.completed", - bridge.handleModelTerminal.bind(bridge), - ), - "model.call.failed": providerHandler( - "model.call.failed", - bridge.handleModelTerminal.bind(bridge), - ), - "model.call.started": providerHandler( - "model.call.started", - bridge.handleModelStarted.bind(bridge), - ), - "step.attempt.completed": providerHandler( - "step.attempt.completed", - (event) => bridge.handleStepAttemptTerminal(event), - ), - "step.attempt.failed": providerHandler("step.attempt.failed", (event) => - bridge.handleStepAttemptTerminal(event), - ), - "tool.call.started": providerHandler( - "tool.call.started", - bridge.handleActionStarted.bind(bridge), - ), - "tool.call.completed": providerHandler( - "tool.call.completed", - bridge.handleActionTerminal.bind(bridge), - ), - "tool.call.failed": providerHandler( - "tool.call.failed", - bridge.handleActionTerminal.bind(bridge), - ), - "turn.cancelled": providerHandler( - "turn.cancelled", - bridge.handleTurnTerminal.bind(bridge), - ), - "turn.completed": providerHandler( - "turn.completed", - bridge.handleTurnTerminal.bind(bridge), - ), - "turn.failed": providerHandler( - "turn.failed", - bridge.handleTurnTerminal.bind(bridge), - ), - "turn.started": providerHandler( - "turn.started", - bridge.handleTurnStarted.bind(bridge), - ), - }, - flush, - setup: (context) => { - // Before 0.72, Eve publishes both the durable action events and separate - // AI SDK tool events with different IDs for the same tool execution. - const [major, minor] = - context.frameworkVersion?.split(".").map(Number) ?? []; - bridge.useLegacyActions = major === 0 && minor < 72; - return options.setup?.(context); - }, - }; -} - -class EveProviderBridge { - useLegacyActions = false; - private readonly activeActions = new Map(); - private readonly activeModels = new Map(); - private readonly modelsByAttempt = new Map>(); - private readonly settledOperations = new LRUCache({ - max: MAX_EVE_PROVIDER_CACHE_ENTRIES, - }); - private readonly turns = new LRUCache({ - max: MAX_EVE_PROVIDER_CACHE_ENTRIES, - }); - - constructor(private readonly metadata?: Record) {} - - async handleTurnStarted( - event: EveProviderTurnStartedEvent, - context: EveProviderContext, - ): Promise { - await this.contain("turn start", async () => { - if (this.settledOperations.has(event.idempotencyKey)) { - context.state.set({ skip: true }); - return; - } - const { rowId, spanId } = await generateEveIds( - "turn", - event.idempotencyKey, - ); - const key = turnKey(event.sessionId, event.turnId); - const stored = storedSpan(context); - // Eve persists provider state by operation. A replay in a replacement - // process can rebuild the local lineage cache without reopening the span. - if (stored && "exported" in stored && stored.rootSpanId) { - this.turns.set(key, { rootSpanId: stored.rootSpanId, spanId }); - return; - } - let rootSpanId = spanId; - let parentSpanId: string | undefined; - if (event.parentLineage) { - const parentTurnKey = turnKey( - event.parentLineage.sessionId, - event.parentLineage.turnId, - ); - const parentTurn = this.turns.get(parentTurnKey); - rootSpanId = - parentTurn?.rootSpanId ?? - // Eve propagates the root session through every nested subagent. Use - // it instead of the immediate parent session when no ancestor ran - // in-process. - ( - await generateEveIds( - "turn", - turnIdempotencyKey( - event.rootSessionId, - event.parentLineage.turnId, - ), - ) - ).spanId; - parentSpanId = ( - await generateEveIds( - "tool", - actionIdempotencyKey( - event.parentLineage.sessionId, - event.parentLineage.turnId, - event.parentLineage.callId, - ), - ) - ).spanId; - } else if (event.parentTraceContext) { - // Eve forwards the caller's W3C trace context, e.g. from a - // `traceparent` header, so the turn nests under the caller's span. - rootSpanId = event.parentTraceContext.traceId; - parentSpanId = event.parentTraceContext.spanId; - } - - const metadata = this.spanMetadata(event.sessionId); - const span = await this.startSpan( - context, - { - event: { id: rowId, metadata }, - name: "eve.turn", - parentSpanIds: parentSpanId - ? { rootSpanId, spanId: parentSpanId } - : { parentSpanIds: [], rootSpanId }, - spanAttributes: { type: SpanTypeAttribute.TASK }, - spanId, - }, - rootSpanId, - ); - span?.log({ metadata }); - this.turns.set(key, { - rootSpanId, - span, - spanId, - }); - }); - } - - async handleTurnTerminal( - event: EveProviderTurnTerminalEvent, - context: EveProviderContext, - ): Promise { - await this.contain("turn terminal", async () => { - if (this.settledOperations.has(event.idempotencyKey)) { - context.state.set(undefined); - return; - } - const error = event.type === "turn.failed" ? event.error : undefined; - this.drainActionsForTurn(event.sessionId, event.turnId, error); - const stored = storedSpan(context); - if (stored && "exported" in stored) { - updateSpan({ - exported: stored.exported, - ...(error !== undefined ? { error } : {}), - metrics: { end: Date.now() / 1000 }, - }); - } - this.settledOperations.set(event.idempotencyKey, true); - context.state.set(undefined); - this.turns.delete(turnKey(event.sessionId, event.turnId)); - }); - } - - async handleModelStarted( - event: EveProviderModelCallStartedEvent, - context: EveProviderContext, - ): Promise { - await this.contain("model start", async () => { - if (this.settledOperations.has(event.idempotencyKey)) { - context.state.set({ skip: true }); - return; - } - const parent = await this.parentForScope(event.scope); - const { rowId, spanId } = await generateEveIds( - "step", - event.idempotencyKey, - ); - const input = event.input ? capturedModelInput(event.input) : undefined; - const metadata = { - ...this.spanMetadata(event.scope.sessionId), - model: event.model.modelId, - provider: event.model.provider, - }; - const span = await this.startSpan(context, { - event: { - id: rowId, - ...(input !== undefined ? { input } : {}), - metadata, - }, - name: "eve.step", - parentSpanIds: parent, - spanAttributes: { type: SpanTypeAttribute.LLM }, - spanId, - }); - if (!span) return; - span.log({ ...(input !== undefined ? { input } : {}), metadata }); - const turn = this.turns.get( - turnKey(event.scope.sessionId, event.scope.turnId), - ); - if (turn?.span && !turn.inputRecorded && input) { - // Eve's turn events carry no content. The first model call of a turn - // ends with the messages that started it, after any prior history. - turn.inputRecorded = true; - let start = input.length; - while (start > 0 && input[start - 1]?.role === "user") start--; - if (start < input.length) turn.span.log({ input: input.slice(start) }); - } - this.activeModels.set(event.idempotencyKey, { - span, - turnKey: turnKey(event.scope.sessionId, event.scope.turnId), - }); - const keys = this.modelsByAttempt.get(event.scope.attemptId) ?? new Set(); - keys.add(event.idempotencyKey); - this.modelsByAttempt.set(event.scope.attemptId, keys); - }); - } - - async handleModelTerminal( - event: EveProviderModelCallTerminalEvent, - context: EveProviderContext, - ): Promise { - await this.contain("model terminal", async () => { - if (this.settledOperations.has(event.idempotencyKey)) { - context.state.set(undefined); - return; - } - const output = - event.type === "model.call.completed" ? modelOutput(event) : undefined; - const reply = output?.[0]?.message.content; - if ( - reply && - event.type === "model.call.completed" && - event.finishReason !== "tool-calls" - ) { - // The latest reply that does not continue with tool calls is the - // turn's answer, matching the legacy hook integration. - this.turns - .get(turnKey(event.scope.sessionId, event.scope.turnId)) - ?.span?.log({ output: reply }); - } - const active = this.activeModels.get(event.idempotencyKey); - if (active) { - const span = active.span; - if (event.type === "model.call.failed") { - if (event.error !== undefined) span.log({ error: event.error }); - } else { - span.log({ - metrics: usageMetrics(event.usage), - output, - }); - } - span.end(); - } else { - const stored = storedSpan(context); - if (stored && "exported" in stored) { - updateSpan({ - exported: stored.exported, - ...(event.type === "model.call.failed" - ? event.error !== undefined - ? { error: event.error } - : {} - : { output }), - metrics: { - ...(event.type === "model.call.completed" - ? usageMetrics(event.usage) - : {}), - end: Date.now() / 1000, - }, - }); - } - } - this.settledOperations.set(event.idempotencyKey, true); - this.forgetModel(event.scope.attemptId, event.idempotencyKey); - context.state.set(undefined); - }); - } - - async handleActionStarted( - event: EveProviderActionStartedEvent | EveProviderToolCallStartedEvent, - context: EveProviderContext, - ): Promise { - if (event.type === "tool.call.started" && this.useLegacyActions) return; - await this.contain("action start", async () => { - if ( - event.kind === "load-skill" || - this.settledOperations.has(event.idempotencyKey) - ) { - context.state.set({ skip: true }); - return; - } - const parent = await this.parentForScope(event.scope); - if (event.type === "tool.call.started" && event.parentCallId) { - parent.spanId = ( - await generateEveIds( - "tool", - actionIdempotencyKey( - event.scope.sessionId, - event.scope.turnId, - event.parentCallId, - ), - ) - ).spanId; - } - // Child turns derive their parent from this id without knowing the - // action kind, and Eve 0.70+ reports agent calls as tool calls. - const { rowId, spanId } = await generateEveIds( - "tool", - event.idempotencyKey, - ); - const metadata = { - ...this.spanMetadata(event.scope.sessionId), - "eve.action_kind": event.kind ?? "tool-call", - }; - const start = - event.type === "tool.call.started" - ? finiteTimestamp(event.startedAtMs) - : undefined; - const span = await this.startSpan(context, { - event: { - id: rowId, - ...(event.input !== undefined ? { input: event.input } : {}), - metadata, - }, - name: event.type === "tool.call.started" ? event.toolName : event.name, - ...(start !== undefined ? { startTime: start / 1000 } : {}), - parentSpanIds: parent, - spanAttributes: { type: SpanTypeAttribute.TOOL }, - spanId, - }); - if (!span) return; - span.log({ - ...(event.input !== undefined ? { input: event.input } : {}), - metadata, - }); - this.activeActions.set(event.idempotencyKey, { - span, - turnKey: turnKey(event.scope.sessionId, event.scope.turnId), - }); - }); - } - - async handleActionTerminal( - event: EveProviderActionTerminalEvent | EveProviderToolCallTerminalEvent, - context: EveProviderContext, - ): Promise { - if ( - this.useLegacyActions && - (event.type === "tool.call.completed" || - event.type === "tool.call.failed") - ) - return; - await this.contain("action terminal", async () => { - if (this.settledOperations.has(event.idempotencyKey)) { - context.state.set(undefined); - return; - } - const stored = storedSpan(context); - if (stored?.skip) { - context.state.set(undefined); - return; - } - const active = this.activeActions.get(event.idempotencyKey); - const end = - finiteTimestamp(event.acceptedAtMs) ?? - (event.type === "tool.call.completed" || - event.type === "tool.call.failed" - ? finiteTimestamp(event.completedAtMs) - : undefined) ?? - Date.now(); - if (active) { - const span = active.span; - if ( - event.type === "action.failed" || - event.type === "tool.call.failed" - ) { - span.log({ - error: - event.error ?? - new Error( - event.errorCode ?? `Eve action ${event.outcome ?? "failed"}`, - ), - }); - } else if (event.output.type === "error") { - span.log({ - error: - event.output.error ?? new Error("Eve action returned an error"), - }); - } else { - span.log({ output: event.output.output }); - } - span.end({ endTime: end / 1000 }); - } else { - const stored = storedSpan(context); - if (stored && "exported" in stored) { - updateSpan({ - exported: stored.exported, - ...(event.type === "action.failed" || - event.type === "tool.call.failed" - ? { - error: - event.error ?? - new Error( - event.errorCode ?? - `Eve action ${event.outcome ?? "failed"}`, - ), - } - : event.output.type === "error" - ? { - error: - event.output.error ?? - new Error("Eve action returned an error"), - } - : { output: event.output.output }), - metrics: { end: end / 1000 }, - }); - } - } - this.settledOperations.set(event.idempotencyKey, true); - this.activeActions.delete(event.idempotencyKey); - context.state.set(undefined); - }); - } - - handleStepAttemptTerminal(event: EveProviderStepAttemptTerminalEvent): void { - void this.contain("step attempt terminal", () => { - const keys = this.modelsByAttempt.get(event.scope.attemptId); - if (!keys) return; - for (const key of keys) { - const active = this.activeModels.get(key); - if (!active) continue; - if (event.type === "step.attempt.failed" && event.error !== undefined) { - active.span.log({ error: event.error }); - } - active.span.end(); - this.activeModels.delete(key); - } - this.modelsByAttempt.delete(event.scope.attemptId); - }); - } - - private async parentForScope(scope: EveInstrumentationAttemptScope) { - const key = turnKey(scope.sessionId, scope.turnId); - const known = this.turns.get(key); - if (known) { - return { rootSpanId: known.rootSpanId, spanId: known.spanId }; - } - const [{ spanId }, { spanId: rootSpanId }] = await Promise.all([ - generateEveIds("turn", turnIdempotencyKey(scope.sessionId, scope.turnId)), - generateEveIds( - "turn", - turnIdempotencyKey( - scope.rootSessionId ?? scope.sessionId, - scope.turnId, - ), - ), - ]); - return { rootSpanId, spanId }; - } - - private async startSpan( - context: EveProviderContext, - args: Parameters[0], - rootSpanId?: string, - ): Promise { - const span = withCurrent(NOOP_SPAN, () => - _internalStartSpanWithInitialMerge( - withSpanInstrumentationName(args ?? {}, INSTRUMENTATION_NAMES.EVE), - ), - ); - try { - context.state.set({ - exported: await span.export(), - ...(rootSpanId ? { rootSpanId } : {}), - }); - } catch (error) { - debugLogger.warn("Error exporting Eve provider span:", error); - } - return span; - } - - private drainActionsForTurn( - sessionId: string, - turnId: string, - error: unknown, - ): void { - const key = turnKey(sessionId, turnId); - for (const [idempotencyKey, active] of this.activeActions) { - if (active.turnKey !== key) continue; - if (error !== undefined) active.span.log({ error }); - active.span.end(); - this.activeActions.delete(idempotencyKey); - } - } - - private forgetModel(attemptId: string, idempotencyKey: string): void { - this.activeModels.delete(idempotencyKey); - const keys = this.modelsByAttempt.get(attemptId); - keys?.delete(idempotencyKey); - if (keys?.size === 0) this.modelsByAttempt.delete(attemptId); - } - - private spanMetadata(sessionId: string): Record { - return { - ...(this.metadata ?? {}), - "eve.session_id": sessionId, - }; - } - - private async contain( - operation: string, - fn: () => void | Promise, - ): Promise { - try { - await fn(); - } catch (error) { - debugLogger.warn(`Error in Eve provider ${operation}:`, error); - } - } -} - -function storedSpan(context: EveProviderContext): StoredSpan | undefined { - const value = context.state.get(); - if (!isObject(value)) return undefined; - if (value["skip"] === true) return { skip: true }; - return typeof value["exported"] === "string" - ? { - exported: value["exported"], - ...(typeof value["rootSpanId"] === "string" && - value["rootSpanId"].length > 0 - ? { rootSpanId: value["rootSpanId"] } - : {}), - } - : undefined; -} - -function modelOutput( - event: Extract< - EveProviderModelCallTerminalEvent, - { type: "model.call.completed" } - >, -) { - const content = event.content ?? []; - let text = ""; - const reasoning: { content: string }[] = []; - const toolCalls: { - function: { arguments: string; name: string }; - id: string; - type: "function"; - }[] = []; - for (const part of content) { - if (!isObject(part)) { - continue; - } - if (part["type"] === "text" && typeof part["text"] === "string") { - text += part["text"]; - } else if ( - part["type"] === "reasoning" && - typeof part["text"] === "string" && - part["text"].trim().length > 0 - ) { - reasoning.push({ content: part["text"] }); - } else if ( - part["type"] === "tool-call" && - typeof part["toolName"] === "string" && - typeof part["callId"] === "string" - ) { - toolCalls.push({ - function: { - arguments: safeJsonStringify(part["input"]), - name: part["toolName"], - }, - id: part["callId"], - type: "function", - }); - } - } - return [ - { - finish_reason: normalizeFinishReason(event.finishReason), - index: 0, - message: { - content: text || null, - ...(reasoning.length > 0 ? { reasoning } : {}), - role: "assistant", - ...(toolCalls.length > 0 ? { tool_calls: toolCalls } : {}), - }, - }, - ]; -} - -function usageMetrics(usage: EveProviderUsage): Record { - const promptTokens = nonNegativeNumber(usage?.inputTokens); - const completionTokens = nonNegativeNumber(usage?.outputTokens); - const cachedTokens = nonNegativeNumber( - usage?.inputTokenDetails?.cacheReadTokens, - ); - const cacheCreationTokens = nonNegativeNumber( - usage?.inputTokenDetails?.cacheWriteTokens, - ); - return { - ...(promptTokens !== undefined ? { prompt_tokens: promptTokens } : {}), - ...(completionTokens !== undefined - ? { completion_tokens: completionTokens } - : {}), - ...(promptTokens !== undefined && completionTokens !== undefined - ? { tokens: promptTokens + completionTokens } - : {}), - ...(cachedTokens !== undefined - ? { prompt_cached_tokens: cachedTokens } - : {}), - ...(cacheCreationTokens !== undefined - ? { prompt_cache_creation_tokens: cacheCreationTokens } - : {}), - }; -} - -function nonNegativeNumber(value: unknown): number | undefined { - return typeof value === "number" && Number.isFinite(value) && value >= 0 - ? value - : undefined; -} - -function finiteTimestamp(value: unknown): number | undefined { - return typeof value === "number" && Number.isFinite(value) - ? value - : undefined; -} - -function normalizeFinishReason(value: string): string { - if (value === "content-filter") return "content_filter"; - if (value === "tool-calls") return "tool_calls"; - return value; -} - -function safeJsonStringify(value: unknown): string { - try { - return JSON.stringify(value) ?? "null"; - } catch { - return "null"; - } -} - -function turnKey(sessionId: string, turnId: string): string { - return `${sessionId}:${turnId}`; -} - -function turnIdempotencyKey(sessionId: string, turnId: string): string { - return `turn:${sessionId}:${turnId}`; -} - -function actionIdempotencyKey( - sessionId: string, - turnId: string, - callId: string, -): string { - return `action:${sessionId}:${turnId}:${callId}`; -} - -function providerHandler( - type: TType, - handler: ( - event: EveProviderEventForType, - context: EveProviderContext, - ) => void | Promise, -): (event: unknown, context: unknown) => Promise { - return async (event, context) => { - try { - if (!isEveProviderEvent(event, type) || !isEveProviderContext(context)) { - debugLogger.warn(`Ignoring malformed Eve provider ${type} event`); - return; - } - await handler(event, context); - } catch (error) { - debugLogger.warn(`Error in Eve provider ${type}:`, error); - } - }; -} - -function isEveProviderEvent( - event: unknown, - type: TType, -): event is EveProviderEventForType { - if ( - !isObject(event) || - event["type"] !== type || - typeof event["idempotencyKey"] !== "string" || - event["idempotencyKey"].length === 0 - ) { - return false; - } - if (type === "turn.started") { - const parentLineage = event["parentLineage"]; - const parentTraceContext = event["parentTraceContext"]; - return ( - typeof event["rootSessionId"] === "string" && - typeof event["sessionId"] === "string" && - typeof event["turnId"] === "string" && - typeof event["sequence"] === "number" && - Number.isFinite(event["sequence"]) && - (parentLineage === undefined || - isEveProviderParentLineage(parentLineage)) && - (parentTraceContext === undefined || - isEveProviderTraceContext(parentTraceContext)) - ); - } - if ( - type === "turn.cancelled" || - type === "turn.completed" || - type === "turn.failed" - ) { - return ( - typeof event["sessionId"] === "string" && - typeof event["turnId"] === "string" - ); - } - if (!isEveProviderScope(event["scope"])) { - return false; - } - if (type === "model.call.started") { - const model = event["model"]; - return ( - isObject(model) && - typeof model["modelId"] === "string" && - typeof model["provider"] === "string" - ); - } - if (type === "model.call.completed") { - return ( - typeof event["finishReason"] === "string" && - isObject(event["usage"]) && - (event["content"] === undefined || Array.isArray(event["content"])) - ); - } - if (type === "model.call.failed") { - return true; - } - if (type === "tool.call.started") { - return ( - typeof event["callId"] === "string" && - typeof event["toolName"] === "string" && - (event["parentCallId"] === undefined || - typeof event["parentCallId"] === "string") && - (event["kind"] === undefined || - event["kind"] === "load-skill" || - event["kind"] === "remote-agent-call" || - event["kind"] === "subagent-call" || - event["kind"] === "tool-call") - ); - } - if (type === "tool.call.completed") { - const output = event["output"]; - return ( - isObject(output) && - (output["type"] === "result" || output["type"] === "error") - ); - } - if (type === "tool.call.failed") { - return true; - } - if (type === "action.started") { - return ( - typeof event["callId"] === "string" && - typeof event["name"] === "string" && - (event["kind"] === "load-skill" || - event["kind"] === "remote-agent-call" || - event["kind"] === "subagent-call" || - event["kind"] === "tool-call") - ); - } - if (type === "action.completed") { - const output = event["output"]; - return ( - event["outcome"] === "completed" && - isObject(output) && - (output["type"] === "result" || output["type"] === "error") - ); - } - if (type === "action.failed") { - return ( - event["outcome"] === "abandoned" || - event["outcome"] === "cancelled" || - event["outcome"] === "failed" || - event["outcome"] === "rejected" - ); - } - return type === "step.attempt.completed" || type === "step.attempt.failed"; -} - -function isEveProviderContext(value: unknown): value is EveProviderContext { - if (!isObject(value) || !isObject(value["state"])) { - return false; - } - return ( - typeof value["state"]["get"] === "function" && - typeof value["state"]["set"] === "function" - ); -} - -function isEveProviderScope( - value: unknown, -): value is EveInstrumentationAttemptScope { - return ( - isObject(value) && - typeof value["attemptId"] === "string" && - typeof value["attemptIndex"] === "number" && - Number.isFinite(value["attemptIndex"]) && - typeof value["sessionId"] === "string" && - typeof value["stepIndex"] === "number" && - Number.isFinite(value["stepIndex"]) && - typeof value["turnId"] === "string" && - (value["rootSessionId"] === undefined || - typeof value["rootSessionId"] === "string") - ); -} - -function isEveProviderParentLineage( - value: unknown, -): value is EveProviderTurnStartedEvent["parentLineage"] { - return ( - isObject(value) && - typeof value["callId"] === "string" && - typeof value["sessionId"] === "string" && - typeof value["turnId"] === "string" - ); -} - -function isEveProviderTraceContext( - value: unknown, -): value is EveProviderTurnStartedEvent["parentTraceContext"] { - return ( - isObject(value) && - typeof value["spanId"] === "string" && - value["spanId"].length > 0 && - typeof value["traceId"] === "string" && - value["traceId"].length > 0 - ); -} diff --git a/js/src/instrumentation/plugins/flue-interceptors.ts b/js/src/instrumentation/plugins/flue-interceptors.ts deleted file mode 100644 index 5784099f8..000000000 --- a/js/src/instrumentation/plugins/flue-interceptors.ts +++ /dev/null @@ -1,9 +0,0 @@ -import { channel, defineInterceptor } from "../core/interceptor-definitions"; - -import type { FlueObservableContext } from "../../vendor-sdk-types/flue"; - -export const flueChannels = defineInterceptor("@flue/runtime", { - createContext: channel<[unknown], FlueObservableContext>({ - channelName: "createFlueContext", - }), -}); diff --git a/js/src/instrumentation/plugins/mcp-plugin.ts b/js/src/instrumentation/plugins/mcp-plugin.ts deleted file mode 100644 index 51db6f273..000000000 --- a/js/src/instrumentation/plugins/mcp-plugin.ts +++ /dev/null @@ -1,127 +0,0 @@ -import type { - MCPProtocol, - MCPRequestHandler, -} from "../../vendor-sdk-types/mcp"; -import { BasePlugin } from "../core"; -import type { MCPIntegrationConfig } from "../config"; -import { unsubscribeAll } from "../core/tracing"; -import { runInstrumentation } from "../core/observe-result"; -import { - mcpChannels, - mcpSDKChannels, - mcpServerChannels, - wrapMCPProtocol, - wrapMCPTransport, - wrapMCPRequestHandler, -} from "./mcp-interceptors"; -import { - traceMCPMessage, - traceMCPSend, - traceMCPClose, - traceMCPHandler, - traceMCPProtocolError, - traceMCPNotificationHandler, - traceMCPHttpRequest, -} from "./mcp-tracing"; - -export class MCPPlugin extends BasePlugin { - constructor(private readonly config: MCPIntegrationConfig = {}) { - super(); - } - - protected onEnable(): void { - this.unsubscribers.push( - ...[ - mcpChannels.connect, - mcpSDKChannels.connect, - mcpServerChannels.connect, - ].map((channel) => - channel.intercept((target, receiver, args) => { - const protocol = receiver as MCPProtocol; - // Protocol is shared with clients. Only servers have a server identity; - // the manual wrapper's channel is an explicit server opt-in. - if ( - channel === mcpChannels.connect || - protocol._serverInfo !== undefined - ) { - runInstrumentation(() => { - wrapMCPProtocol(protocol); - wrapMCPTransport(args[0], protocol); - }); - } - return Reflect.apply(target, receiver, args); - }), - ), - ...[ - mcpSDKChannels.setRequestHandler, - mcpServerChannels.setRequestHandler, - ].map((channel) => - channel.intercept((target, receiver, args) => { - const wrappedArgs = [...args]; - const index = wrappedArgs.length - 1; - if (typeof wrappedArgs[index] === "function") { - wrappedArgs[index] = wrapMCPRequestHandler( - wrappedArgs[index] as MCPRequestHandler, - receiver as MCPProtocol, - ); - } - return Reflect.apply(target, receiver, wrappedArgs); - }), - ), - mcpChannels.message.intercept((target, receiver, args, { transport }) => - traceMCPMessage( - () => Reflect.apply(target, receiver, args), - transport, - args[0], - this.config, - ), - ), - mcpChannels.send.intercept((target, receiver, args, { transport }) => - traceMCPSend( - () => Reflect.apply(target, receiver, args), - transport, - args[0], - args[1], - this.config, - ), - ), - mcpChannels.close.intercept((target, receiver, args, { transport }) => - traceMCPClose(() => Reflect.apply(target, receiver, args), transport), - ), - mcpChannels.requestHandler.intercept( - (target, receiver, args, { protocol }) => - traceMCPHandler( - () => Reflect.apply(target, receiver, args), - protocol, - args[0], - args[1], - ), - ), - mcpChannels.protocolError.intercept((target, receiver, args) => - traceMCPProtocolError( - () => Reflect.apply(target, receiver, args), - args[0], - ), - ), - mcpChannels.notificationHandler.intercept( - (target, receiver, args, { protocol }) => - traceMCPNotificationHandler( - () => Reflect.apply(target, receiver, args), - protocol, - ), - ), - mcpChannels.httpRequest.intercept( - (target, receiver, args, { transport }) => - traceMCPHttpRequest( - () => Reflect.apply(target, receiver, args), - transport, - args[0], - ), - ), - ); - } - - protected onDisable(): void { - this.unsubscribers = unsubscribeAll(this.unsubscribers); - } -} diff --git a/js/src/instrumentation/plugins/openai-agents-plugin.ts b/js/src/instrumentation/plugins/openai-agents-plugin.ts deleted file mode 100644 index 1a59e593d..000000000 --- a/js/src/instrumentation/plugins/openai-agents-plugin.ts +++ /dev/null @@ -1,126 +0,0 @@ -import { isObject } from "../../../util/index"; -import type { - OpenAIAgentsSpan, - OpenAIAgentsTrace, -} from "../../vendor-sdk-types/openai-agents"; -import { BasePlugin } from "../core"; -import { unsubscribeAll } from "../core/tracing"; -import { runInstrumentation } from "../core/observe-result"; -import { openAIAgentsCoreChannels } from "./openai-agents-interceptors"; -import { OpenAIAgentsTraceProcessor } from "./openai-agents-trace-processor"; - -function firstArgument(args: unknown): unknown { - if (Array.isArray(args)) { - return args[0]; - } - if ( - isObject(args) && - "length" in args && - typeof args.length === "number" && - Number.isInteger(args.length) && - args.length >= 0 - ) { - return Array.from(args as unknown as ArrayLike)[0]; - } - return undefined; -} - -function isOpenAIAgentsTrace(value: unknown): value is OpenAIAgentsTrace { - return ( - isObject(value) && - value.type === "trace" && - typeof value.traceId === "string" - ); -} - -function isOpenAIAgentsSpan(value: unknown): value is OpenAIAgentsSpan { - return ( - isObject(value) && - value.type === "trace.span" && - typeof value.traceId === "string" && - typeof value.spanId === "string" - ); -} - -export class OpenAIAgentsPlugin extends BasePlugin { - private processor = new OpenAIAgentsTraceProcessor(); - - protected onEnable(): void { - this.subscribeToTraceLifecycle(); - } - - protected onDisable(): void { - this.unsubscribers = unsubscribeAll(this.unsubscribers); - void this.processor.shutdown(); - } - - private subscribeToTraceLifecycle(): void { - const traceStartChannel = openAIAgentsCoreChannels.onTraceStart; - - const removetraceStartHandlers = traceStartChannel.intercept( - (target, receiver, args) => { - runInstrumentation(() => - ((event: { arguments: unknown }) => { - const trace = firstArgument(event.arguments); - if (isOpenAIAgentsTrace(trace)) { - void this.processor.onTraceStart(trace); - } - })({ arguments: args }), - ); - return Reflect.apply(target, receiver, args); - }, - ); - this.unsubscribers.push(removetraceStartHandlers); - - const traceEndChannel = openAIAgentsCoreChannels.onTraceEnd; - - const removetraceEndHandlers = traceEndChannel.intercept( - (target, receiver, args) => { - runInstrumentation(() => - ((event: { arguments: unknown }) => { - const trace = firstArgument(event.arguments); - if (isOpenAIAgentsTrace(trace)) { - void this.processor.onTraceEnd(trace); - } - })({ arguments: args }), - ); - return Reflect.apply(target, receiver, args); - }, - ); - this.unsubscribers.push(removetraceEndHandlers); - - const spanStartChannel = openAIAgentsCoreChannels.onSpanStart; - - const removespanStartHandlers = spanStartChannel.intercept( - (target, receiver, args) => { - runInstrumentation(() => - ((event: { arguments: unknown }) => { - const span = firstArgument(event.arguments); - if (isOpenAIAgentsSpan(span)) { - void this.processor.onSpanStart(span); - } - })({ arguments: args }), - ); - return Reflect.apply(target, receiver, args); - }, - ); - this.unsubscribers.push(removespanStartHandlers); - - const spanEndChannel = openAIAgentsCoreChannels.onSpanEnd; - - const removespanEndHandlers = spanEndChannel.intercept( - (target, receiver, args) => { - runInstrumentation(() => - ((event: { arguments: unknown }) => { - const span = firstArgument(event.arguments); - if (isOpenAIAgentsSpan(span)) { - void this.processor.onSpanEnd(span); - } - })({ arguments: args }), - ); - return Reflect.apply(target, receiver, args); - }, - ); - this.unsubscribers.push(removespanEndHandlers); - } -} diff --git a/js/src/instrumentation/plugins/openai-plugin.ts b/js/src/instrumentation/plugins/openai-plugin.ts deleted file mode 100644 index 7db400678..000000000 --- a/js/src/instrumentation/plugins/openai-plugin.ts +++ /dev/null @@ -1,872 +0,0 @@ -import { SpanTypeAttribute, isObject } from "../../../util/index"; -import { - BRAINTRUST_CACHED_STREAM_METRIC, - getCachedMetricFromHeaders, - parseMetricsFromUsage, -} from "../../openai-utils"; -import { INSTRUMENTATION_NAMES } from "../../span-origin"; -import { getCurrentUnixTimestamp } from "../../util"; -import type { - OpenAIChatChoice, - OpenAIChatCompletionChunk, - OpenAIChatLogprobs, - OpenAIResponseStreamEvent, -} from "../../vendor-sdk-types/openai"; -import { BasePlugin } from "../core"; -import { - traceAsyncCall, - traceStreamingCall, - traceSyncStreamCall, - unsubscribeAll, -} from "../core/tracing"; -import { - interceptOpenAIAgentsTraceCapture, - interceptOpenAIAgentsTraceFail, - interceptOpenAIAgentsTraceStart, -} from "./openai-agents-api-instrumentation"; -import { - interceptOpenAIBatchTraceComplete, - interceptOpenAIBatchesRetrieveTraced, - interceptOpenAIFilesCreateTraced, -} from "./openai-batch-instrumentation"; -import { openAIChannels } from "./openai-interceptors"; -import { traceOpenAIDecisions } from "./openai-decisions"; -import { traceOpenAIMedia } from "./openai-media"; -import { - extractOpenAIChatInput, - extractOpenAIResponsesInput, - extractOpenAIResponsesMetadata, - processImagesInOutput, -} from "./openai-span-data"; - -/** - * Plugin for OpenAI SDK instrumentation. - * - * Handles instrumentation for: - * - Chat completions (streaming and non-streaming) - * - Embeddings - * - Moderations - * - Beta API (parse, stream) - * - Responses API (create, stream, parse, compact) - */ -export class OpenAIPlugin extends BasePlugin { - constructor() { - super(); - } - - protected onEnable(): void { - this.unsubscribers.push( - openAIChannels.decisionsCreate.intercept( - (target, receiver, args, additional) => - traceOpenAIDecisions(() => Reflect.apply(target, receiver, args), { - arguments: args, - self: receiver, - additional, - }), - ), - ); - this.unsubscribers.push( - openAIChannels.agentsTraceStart.intercept( - interceptOpenAIAgentsTraceStart, - ), - openAIChannels.agentsTraceCapture.intercept( - interceptOpenAIAgentsTraceCapture, - ), - openAIChannels.agentsTraceFail.intercept(interceptOpenAIAgentsTraceFail), - openAIChannels.filesCreateTraced.intercept( - interceptOpenAIFilesCreateTraced, - ), - openAIChannels.batchesRetrieveTraced.intercept( - interceptOpenAIBatchesRetrieveTraced, - ), - openAIChannels.batchesCompleteTrace.intercept( - interceptOpenAIBatchTraceComplete, - ), - ); - - this.unsubscribers.push( - openAIChannels.imagesGenerate.intercept( - (target, receiver, args, additional) => - traceOpenAIMedia( - () => Reflect.apply(target, receiver, args), - { arguments: args, self: receiver, additional }, - openAIChannels.imagesGenerate.channelName, - "generate", - ), - ), - openAIChannels.imagesEdit.intercept( - (target, receiver, args, additional) => - traceOpenAIMedia( - () => Reflect.apply(target, receiver, args), - { arguments: args, self: receiver, additional }, - openAIChannels.imagesEdit.channelName, - "edit", - ), - ), - openAIChannels.imagesCreateVariation.intercept( - (target, receiver, args, additional) => - traceOpenAIMedia( - () => Reflect.apply(target, receiver, args), - { arguments: args, self: receiver, additional }, - openAIChannels.imagesCreateVariation.channelName, - "variation", - ), - ), - openAIChannels.audioSpeechCreate.intercept( - (target, receiver, args, additional) => - traceOpenAIMedia( - () => Reflect.apply(target, receiver, args), - { arguments: args, self: receiver, additional }, - openAIChannels.audioSpeechCreate.channelName, - "speech", - ), - ), - openAIChannels.audioTranscriptionsCreate.intercept( - (target, receiver, args, additional) => - traceOpenAIMedia( - () => Reflect.apply(target, receiver, args), - { arguments: args, self: receiver, additional }, - openAIChannels.audioTranscriptionsCreate.channelName, - "transcribe", - ), - ), - openAIChannels.audioTranslationsCreate.intercept( - (target, receiver, args, additional) => - traceOpenAIMedia( - () => Reflect.apply(target, receiver, args), - { arguments: args, self: receiver, additional }, - openAIChannels.audioTranslationsCreate.channelName, - "translate", - ), - ), - ); - - // Chat Completions - supports streaming - this.unsubscribers.push( - openAIChannels.chatCompletionsCreate.intercept( - (target, receiver, args, additional) => - traceStreamingCall( - () => Reflect.apply(target, receiver, args), - { - ...additional, - arguments: args, - self: receiver, - get response() { - return additional.responseInfo?.response ?? additional.response; - }, - }, - { - instrumentationName: INSTRUMENTATION_NAMES.OPENAI, - name: "Chat Completion", - type: SpanTypeAttribute.LLM, - extractInput: ([params]) => extractOpenAIChatInput(params), - extractOutput: (result) => { - return result?.choices; - }, - extractMetrics: (result, startTime, endEvent) => { - const metrics = withCachedMetric( - parseMetricsFromUsage(result?.usage), - result, - endEvent, - ); - if (startTime) { - metrics.time_to_first_token = - getCurrentUnixTimestamp() - startTime; - } - return metrics; - }, - aggregateChunks: aggregateChatCompletionChunks, - }, - ), - ), - ); - - // Embeddings - this.unsubscribers.push( - openAIChannels.embeddingsCreate.intercept( - (target, receiver, args, additional) => - traceAsyncCall( - () => Reflect.apply(target, receiver, args), - { - ...additional, - arguments: args, - self: receiver, - get response() { - return additional.responseInfo?.response ?? additional.response; - }, - }, - { - instrumentationName: INSTRUMENTATION_NAMES.OPENAI, - name: "Embedding", - type: SpanTypeAttribute.LLM, - extractInput: ([params]) => { - const { input, ...metadata } = params; - return { - input, - metadata: { ...metadata, provider: "openai" }, - }; - }, - extractOutput: (result) => { - const embedding = result?.data?.[0]?.embedding; - return Array.isArray(embedding) - ? { embedding_length: embedding.length } - : undefined; - }, - extractMetrics: (result, _startTime, endEvent) => { - return withCachedMetric( - parseMetricsFromUsage(result?.usage), - result, - endEvent, - ); - }, - }, - ), - ), - ); - - // Beta Chat Completions Parse - this.unsubscribers.push( - openAIChannels.betaChatCompletionsParse.intercept( - (target, receiver, args, additional) => - traceStreamingCall( - () => Reflect.apply(target, receiver, args), - { - ...additional, - arguments: args, - self: receiver, - get response() { - return additional.responseInfo?.response ?? additional.response; - }, - }, - { - instrumentationName: INSTRUMENTATION_NAMES.OPENAI, - name: "Chat Completion", - type: SpanTypeAttribute.LLM, - extractInput: ([params]) => extractOpenAIChatInput(params), - extractOutput: (result) => { - return result?.choices; - }, - extractMetrics: (result, startTime, endEvent) => { - const metrics = withCachedMetric( - parseMetricsFromUsage(result?.usage), - result, - endEvent, - ); - if (startTime) { - metrics.time_to_first_token = - getCurrentUnixTimestamp() - startTime; - } - return metrics; - }, - aggregateChunks: aggregateChatCompletionChunks, - }, - ), - ), - ); - - // Beta Chat Completions Stream (sync method returning event-based stream) - this.unsubscribers.push( - openAIChannels.betaChatCompletionsStream.intercept( - (target, receiver, args, additional) => - traceSyncStreamCall( - () => Reflect.apply(target, receiver, args), - { - ...additional, - arguments: args, - self: receiver, - get response() { - return additional.responseInfo?.response ?? additional.response; - }, - }, - { - instrumentationName: INSTRUMENTATION_NAMES.OPENAI, - name: "Chat Completion", - type: SpanTypeAttribute.LLM, - extractInput: ([params]) => extractOpenAIChatInput(params), - }, - ), - ), - ); - - // Moderations - this.unsubscribers.push( - openAIChannels.moderationsCreate.intercept( - (target, receiver, args, additional) => - traceAsyncCall( - () => Reflect.apply(target, receiver, args), - { - ...additional, - arguments: args, - self: receiver, - get response() { - return additional.responseInfo?.response ?? additional.response; - }, - }, - { - instrumentationName: INSTRUMENTATION_NAMES.OPENAI, - name: "Moderation", - type: SpanTypeAttribute.LLM, - extractInput: ([params]) => { - const { input, ...metadata } = params; - return { - input, - metadata: { ...metadata, provider: "openai" }, - }; - }, - extractOutput: (result) => { - return result?.results; - }, - extractMetrics: (result, _startTime, endEvent) => { - return withCachedMetric( - parseMetricsFromUsage(result?.usage), - result, - endEvent, - ); - }, - }, - ), - ), - ); - - // Responses API - create (supports streaming via stream=true param) - this.unsubscribers.push( - openAIChannels.responsesCreate.intercept( - (target, receiver, args, additional) => - traceStreamingCall( - () => Reflect.apply(target, receiver, args), - { - ...additional, - arguments: args, - self: receiver, - get response() { - return additional.responseInfo?.response ?? additional.response; - }, - }, - { - instrumentationName: INSTRUMENTATION_NAMES.OPENAI, - name: "openai.responses.create", - type: SpanTypeAttribute.LLM, - extractInput: ([params]) => extractOpenAIResponsesInput(params), - extractOutput: (result) => { - return processImagesInOutput(result?.output); - }, - extractMetadata: (result) => - extractOpenAIResponsesMetadata(result), - extractMetrics: (result, startTime, endEvent) => { - const metrics = withCachedMetric( - parseMetricsFromUsage(result?.usage), - result, - endEvent, - ); - if (startTime) { - metrics.time_to_first_token = - getCurrentUnixTimestamp() - startTime; - } - return metrics; - }, - aggregateChunks: aggregateResponseStreamEvents, - }, - ), - ), - ); - - // Responses API - stream (sync method returning event-based stream) - this.unsubscribers.push( - openAIChannels.responsesStream.intercept( - (target, receiver, args, additional) => - traceSyncStreamCall( - () => Reflect.apply(target, receiver, args), - { - ...additional, - arguments: args, - self: receiver, - get response() { - return additional.responseInfo?.response ?? additional.response; - }, - }, - { - instrumentationName: INSTRUMENTATION_NAMES.OPENAI, - name: "openai.responses.create", - type: SpanTypeAttribute.LLM, - extractInput: ([params]) => extractOpenAIResponsesInput(params), - extractFromEvent: (event) => - TERMINAL_RESPONSE_EVENT_TYPES.has(event.type) - ? aggregateResponseStreamEvents([event]) - : {}, - }, - ), - ), - ); - - // Responses API - parse - this.unsubscribers.push( - openAIChannels.responsesParse.intercept( - (target, receiver, args, additional) => - traceStreamingCall( - () => Reflect.apply(target, receiver, args), - { - ...additional, - arguments: args, - self: receiver, - get response() { - return additional.responseInfo?.response ?? additional.response; - }, - }, - { - instrumentationName: INSTRUMENTATION_NAMES.OPENAI, - name: "openai.responses.parse", - type: SpanTypeAttribute.LLM, - extractInput: ([params]) => extractOpenAIResponsesInput(params), - extractOutput: (result) => { - return processImagesInOutput(result?.output); - }, - extractMetadata: (result) => - extractOpenAIResponsesMetadata(result), - extractMetrics: (result, startTime, endEvent) => { - const metrics = withCachedMetric( - parseMetricsFromUsage(result?.usage), - result, - endEvent, - ); - if (startTime) { - metrics.time_to_first_token = - getCurrentUnixTimestamp() - startTime; - } - return metrics; - }, - aggregateChunks: aggregateResponseStreamEvents, - }, - ), - ), - ); - - // Responses API - compact - this.unsubscribers.push( - openAIChannels.responsesCompact.intercept( - (target, receiver, args, additional) => - traceAsyncCall( - () => Reflect.apply(target, receiver, args), - { - ...additional, - arguments: args, - self: receiver, - get response() { - return additional.responseInfo?.response ?? additional.response; - }, - }, - { - instrumentationName: INSTRUMENTATION_NAMES.OPENAI, - name: "openai.responses.compact", - type: SpanTypeAttribute.LLM, - extractInput: ([params]) => extractOpenAIResponsesInput(params), - extractOutput: (result) => { - return processImagesInOutput(result?.output); - }, - extractMetadata: (result) => - extractOpenAIResponsesMetadata(result), - extractMetrics: (result, startTime, endEvent) => { - const metrics = withCachedMetric( - parseMetricsFromUsage(result?.usage), - result, - endEvent, - ); - if (startTime) { - metrics.time_to_first_token = - getCurrentUnixTimestamp() - startTime; - } - return metrics; - }, - }, - ), - ), - ); - } - - protected onDisable(): void { - this.unsubscribers = unsubscribeAll(this.unsubscribers); - } -} - -function getCachedMetricFromEndEvent(endEvent: unknown): number | undefined { - if (!isObject(endEvent)) { - return undefined; - } - - const response = (endEvent as Record).response; - if (!isObject(response)) { - return undefined; - } - - const headers = (response as { headers?: unknown }).headers; - if (!headers || typeof (headers as Headers).get !== "function") { - return undefined; - } - - return getCachedMetricFromHeaders(headers as Headers); -} - -function withCachedMetric( - metrics: Record, - result: unknown, - endEvent?: unknown, -): Record { - if (metrics.cached !== undefined) { - return metrics; - } - - const cachedFromEvent = getCachedMetricFromEndEvent(endEvent); - if (cachedFromEvent !== undefined) { - return { - ...metrics, - cached: cachedFromEvent, - }; - } - - if (!isObject(result)) { - return metrics; - } - - const cached = (result as Record)[ - BRAINTRUST_CACHED_STREAM_METRIC - ]; - - if (typeof cached !== "number") { - return metrics; - } - - return { - ...metrics, - cached, - }; -} - -function mergeLogprobTokens( - existing: OpenAIChatLogprobs["content"] | OpenAIChatLogprobs["refusal"], - incoming: OpenAIChatLogprobs["content"] | OpenAIChatLogprobs["refusal"], -): OpenAIChatLogprobs["content"] | OpenAIChatLogprobs["refusal"] { - if (incoming === undefined) { - return existing; - } - - if (incoming === null) { - return existing ?? null; - } - - if (Array.isArray(existing)) { - return [...existing, ...incoming]; - } - - return [...incoming]; -} - -function aggregateChatLogprobs( - existing: OpenAIChatLogprobs | null | undefined, - incoming: OpenAIChatLogprobs | null | undefined, -): OpenAIChatLogprobs | null | undefined { - if (incoming === undefined) { - return existing; - } - - if (incoming === null) { - return existing ?? null; - } - - const aggregated: OpenAIChatLogprobs = - existing && existing !== null - ? { ...existing, ...incoming } - : { ...incoming }; - - const content = mergeLogprobTokens(existing?.content, incoming.content); - if (content !== undefined) { - aggregated.content = content; - } - - const refusal = mergeLogprobTokens(existing?.refusal, incoming.refusal); - if (refusal !== undefined) { - aggregated.refusal = refusal; - } - - return aggregated; -} - -type AggregatedChatChoice = { - index: number; - role: string | undefined; - content: string | undefined; - reasoning_content: string | null | undefined; - refusal: string | undefined; - audio: NonNullable | undefined; - toolCallsByIndex: Map< - number, - NonNullable[number] - >; - logprobs: OpenAIChatLogprobs | null | undefined; - finish_reason: string | null | undefined; -}; - -function createAggregatedChatChoice(index: number): AggregatedChatChoice { - return { - index, - role: undefined, - content: undefined, - reasoning_content: undefined, - refusal: undefined, - audio: undefined, - toolCallsByIndex: new Map(), - logprobs: undefined, - finish_reason: undefined, - }; -} - -function toChatChoice(choice: AggregatedChatChoice): OpenAIChatChoice { - const toolCalls = Array.from(choice.toolCallsByIndex.entries()) - .sort(([left], [right]) => left - right) - .map(([, toolCall]) => toolCall); - - return { - index: choice.index, - message: { - role: choice.role, - content: choice.content, - ...(choice.reasoning_content !== undefined - ? { reasoning_content: choice.reasoning_content } - : {}), - ...(choice.refusal !== undefined ? { refusal: choice.refusal } : {}), - ...(choice.audio !== undefined ? { audio: choice.audio } : {}), - tool_calls: toolCalls.length > 0 ? toolCalls : undefined, - }, - logprobs: choice.logprobs ?? null, - finish_reason: choice.finish_reason, - }; -} - -// Some OpenAI-compatible providers omit `index` on streamed tool call deltas. -// Match existing ids before starting a new call; a delta without an id -// continues the latest one. -function resolveToolCallIndex( - toolCallsByIndex: AggregatedChatChoice["toolCallsByIndex"], - toolDelta: { index?: number | null; id?: string }, -): number { - if (typeof toolDelta.index === "number") { - return toolDelta.index; - } - if (toolDelta.id) { - for (const [index, toolCall] of toolCallsByIndex) { - if (toolCall.id === toolDelta.id) { - return index; - } - } - } - if (toolCallsByIndex.size === 0) { - return 0; - } - const lastIndex = Math.max(...toolCallsByIndex.keys()); - const lastId = toolCallsByIndex.get(lastIndex)?.id; - return toolDelta.id && lastId && lastId !== toolDelta.id - ? lastIndex + 1 - : lastIndex; -} - -/** - * Aggregate chat completion chunks into a single response. - * Combines role (first), content, reasoning_content and audio transcripts (concatenated), - * tool_calls (by index), finish_reason (last), and usage (last chunk). - */ -export function aggregateChatCompletionChunks( - chunks: OpenAIChatCompletionChunk[], - streamResult?: unknown, - endEvent?: unknown, -): { - output: OpenAIChatChoice[]; - metrics: Record; -} { - const choicesByIndex = new Map(); - let metrics: Record = {}; - - for (const chunk of chunks) { - if (chunk.usage) { - metrics = { - ...metrics, - ...parseMetricsFromUsage(chunk.usage), - }; - } - - const choices = chunk.choices; - if (!choices?.length) { - continue; - } - - for (const choice of choices) { - const choiceIndex = choice.index; - let aggregatedChoice = choicesByIndex.get(choiceIndex); - if (!aggregatedChoice) { - aggregatedChoice = createAggregatedChatChoice(choiceIndex); - choicesByIndex.set(choiceIndex, aggregatedChoice); - } - - if (choice.finish_reason) { - aggregatedChoice.finish_reason = choice.finish_reason; - } - - aggregatedChoice.logprobs = aggregateChatLogprobs( - aggregatedChoice.logprobs, - choice.logprobs, - ); - - const delta = choice.delta; - if (!delta) { - continue; - } - - if (delta.finish_reason) { - aggregatedChoice.finish_reason = delta.finish_reason; - } - - if (!aggregatedChoice.role && delta.role) { - aggregatedChoice.role = delta.role; - } - - if (delta.content) { - aggregatedChoice.content = - (aggregatedChoice.content || "") + delta.content; - } - - if (typeof delta.reasoning_content === "string") { - aggregatedChoice.reasoning_content = - (aggregatedChoice.reasoning_content ?? "") + delta.reasoning_content; - } else if (delta.reasoning_content === null) { - // Retain explicit nulls, but never discard text from earlier chunks. - aggregatedChoice.reasoning_content ??= null; - } - - if (delta.refusal) { - aggregatedChoice.refusal = - (aggregatedChoice.refusal || "") + delta.refusal; - } - - if (delta.audio) { - const { id, expires_at: expiresAt, transcript } = delta.audio; - if ( - id !== undefined || - expiresAt !== undefined || - transcript !== undefined - ) { - aggregatedChoice.audio ??= {}; - if (id !== undefined) { - aggregatedChoice.audio.id = id; - } - if (expiresAt !== undefined) { - aggregatedChoice.audio.expires_at = expiresAt; - } - if (transcript !== undefined) { - aggregatedChoice.audio.transcript = - (aggregatedChoice.audio.transcript || "") + transcript; - } - } - } - - if (delta.tool_calls) { - for (const toolDelta of delta.tool_calls) { - const toolIndex = resolveToolCallIndex( - aggregatedChoice.toolCallsByIndex, - toolDelta, - ); - let aggregatedToolCall = - aggregatedChoice.toolCallsByIndex.get(toolIndex); - if (!aggregatedToolCall) { - aggregatedToolCall = { - function: { arguments: "" }, - }; - aggregatedChoice.toolCallsByIndex.set( - toolIndex, - aggregatedToolCall, - ); - } - - if (toolDelta.id !== undefined) { - aggregatedToolCall.id = toolDelta.id; - } - if (toolDelta.type !== undefined) { - aggregatedToolCall.type = toolDelta.type; - } - if (toolDelta.function?.name !== undefined) { - aggregatedToolCall.function.name = toolDelta.function.name; - } - if (toolDelta.function?.arguments !== undefined) { - aggregatedToolCall.function.arguments += - toolDelta.function.arguments; - } - } - } - } - } - - metrics = withCachedMetric(metrics, streamResult, endEvent); - const output = Array.from(choicesByIndex.values()) - .sort((left, right) => left.index - right.index) - .map(toChatChoice); - - return { - metrics, - output: - output.length > 0 - ? output - : [toChatChoice(createAggregatedChatChoice(0))], - }; -} - -// A Responses API stream ends with one of these events. Streams that hit -// `max_output_tokens` or a content filter end with `response.incomplete`, and -// streams that error server-side end with `response.failed`. All of them carry -// the final `response` object with output and usage. -const TERMINAL_RESPONSE_EVENT_TYPES = new Set([ - "response.completed", - "response.incomplete", - "response.failed", -]); - -export function aggregateResponseStreamEvents( - chunks: OpenAIResponseStreamEvent[], - _streamResult?: unknown, - endEvent?: unknown, -): { - output: any; - metrics: Record; - metadata?: Record; - error?: string; -} { - let output: any = undefined; - let metrics: Record = {}; - let metadata: Record | undefined = undefined; - let error: string | undefined = undefined; - - for (const chunk of chunks) { - if (!chunk?.response || !TERMINAL_RESPONSE_EVENT_TYPES.has(chunk.type)) { - continue; - } - - const response = chunk.response; - if (response.output !== undefined) { - output = processImagesInOutput(response.output); - } - - const { usage: _usage, output: _output, ...rest } = response; - if (Object.keys(rest).length > 0) { - metadata = rest; - } - - metrics = parseMetricsFromUsage(response.usage); - if (chunk.type === "response.failed" && response.error) { - error = `${response.error.code}: ${response.error.message}`; - } - } - - return { - output, - metrics: withCachedMetric(metrics, undefined, endEvent), - ...(metadata !== undefined ? { metadata } : {}), - ...(error !== undefined ? { error } : {}), - }; -} - -export { parseMetricsFromUsage }; diff --git a/js/src/instrumentation/plugins/ai-sdk-batch-instrumentation.test.ts b/js/src/instrumentation/providers/ai-sdk-batch-instrumentation.test.ts similarity index 95% rename from js/src/instrumentation/plugins/ai-sdk-batch-instrumentation.test.ts rename to js/src/instrumentation/providers/ai-sdk-batch-instrumentation.test.ts index 57c1f2135..d18023c79 100644 --- a/js/src/instrumentation/plugins/ai-sdk-batch-instrumentation.test.ts +++ b/js/src/instrumentation/providers/ai-sdk-batch-instrumentation.test.ts @@ -6,17 +6,13 @@ import { expect, it, vi, -} from "vitest"; +} from "vite-plus/test"; import { startAISDKBatchTrace, completeAISDKBatchTrace, } from "../../ai-sdk-batch"; -import { - _exportsForTestingOnly, - initLogger, - startSpan, - withCurrent, -} from "../../logger"; +import { _exportsForTestingOnly } from "../../logger/testing"; +import { initLogger, startSpan, withCurrent } from "../../logger/core"; import { configureNode } from "../../node/config"; import type { AISDKBatchItem, @@ -24,8 +20,9 @@ import type { CompleteAISDKBatchTraceArgs, } from "../../vendor-sdk-types/ai-sdk-batch"; import { wrapAISDK } from "../../wrappers/ai-sdk"; -import { AISDKPlugin } from "./ai-sdk-plugin"; -import { registry } from "../registry"; +import { registerInstrumentation } from "../registrations/aisdk"; +import { resetInstrumentationForTest } from "../test-utils/reset"; +import { configureInstrumentation } from "../registry"; import { SpanComponentsV4 } from "../../../util/span_identifier_v4"; @@ -72,7 +69,6 @@ describe("manual AI SDK batch capture", () => { let background: ReturnType< typeof _exportsForTestingOnly.useTestBackgroundLogger >; - let plugin: AISDKPlugin; let batch: { version: 2; id: string; provider: string }; beforeAll(async () => { await _exportsForTestingOnly.simulateLoginForTests(); @@ -83,13 +79,12 @@ describe("manual AI SDK batch capture", () => { projectName: "ai-sdk-batch.test", projectId: "test-project-id", }); - registry.disable(); - plugin = new AISDKPlugin(); - plugin.enable(); + resetInstrumentationForTest(); + registerInstrumentation(); batch = { version: 2, id: `batch-${sequence++}`, provider: "gateway" }; }); afterEach(() => { - plugin.disable(); + resetInstrumentationForTest(); vi.unstubAllEnvs(); _exportsForTestingOnly.clearTestBackgroundLogger(); }); @@ -185,9 +180,8 @@ describe("manual AI SDK batch capture", () => { it("resumes in a different parent without process-local tracking and merges repeated completion", async () => { const context = await startAISDKBatchTrace({ batch, requests }); const initial = (await background.drain()) as TestRow[]; - plugin.disable(); - plugin = new AISDKPlugin(); - plugin.enable(); + resetInstrumentationForTest(); + registerInstrumentation(); const worker = startSpan({ name: "worker" }); const args = { batch, @@ -493,7 +487,7 @@ describe("manual AI SDK batch capture", () => { }); it("preserves remote images and omits inline bytes by default", async () => { - vi.stubEnv("BRAINTRUST_CAPTURE_ATTACHMENTS", "false"); + vi.stubEnv("BRAINTRUST_AUTO_CAPTURE_ATTACHMENTS", "false"); await startAISDKBatchTrace({ batch, requests: [ @@ -533,7 +527,8 @@ describe("manual AI SDK batch capture", () => { requests: [{ ...requests[0], type: "image" }], }), ).toBeUndefined(); - plugin.disable(); + resetInstrumentationForTest(); + configureInstrumentation({ integrations: { aisdk: false } }); expect(await startAISDKBatchTrace({ batch, requests })).toBeUndefined(); await completeAISDKBatchTrace({ batch, diff --git a/js/src/instrumentation/plugins/ai-sdk-batch-instrumentation.ts b/js/src/instrumentation/providers/ai-sdk-batch-instrumentation.ts similarity index 99% rename from js/src/instrumentation/plugins/ai-sdk-batch-instrumentation.ts rename to js/src/instrumentation/providers/ai-sdk-batch-instrumentation.ts index ad3acc25c..72472bcb1 100644 --- a/js/src/instrumentation/plugins/ai-sdk-batch-instrumentation.ts +++ b/js/src/instrumentation/providers/ai-sdk-batch-instrumentation.ts @@ -8,7 +8,7 @@ import { NOOP_SPAN, withCurrent, type Span, -} from "../../logger"; +} from "../../logger/core"; import { INSTRUMENTATION_NAMES, withSpanInstrumentationName, diff --git a/js/src/instrumentation/plugins/ai-sdk-batch-interceptors.ts b/js/src/instrumentation/providers/ai-sdk-batch-interceptors.ts similarity index 100% rename from js/src/instrumentation/plugins/ai-sdk-batch-interceptors.ts rename to js/src/instrumentation/providers/ai-sdk-batch-interceptors.ts diff --git a/js/src/instrumentation/plugins/ai-sdk-batch-span-data.ts b/js/src/instrumentation/providers/ai-sdk-batch-span-data.ts similarity index 98% rename from js/src/instrumentation/plugins/ai-sdk-batch-span-data.ts rename to js/src/instrumentation/providers/ai-sdk-batch-span-data.ts index a704bfa69..f8ba3cd92 100644 --- a/js/src/instrumentation/plugins/ai-sdk-batch-span-data.ts +++ b/js/src/instrumentation/providers/ai-sdk-batch-span-data.ts @@ -5,7 +5,10 @@ import type { AISDKBatchRequest, } from "../../vendor-sdk-types/ai-sdk-batch"; import { serializeAISDKToolsForLogging } from "../../wrappers/ai-sdk/tool-serialization"; -import { processAISDKCallInput, processAISDKOutput } from "./ai-sdk-plugin"; +import { + processAISDKCallInput, + processAISDKOutput, +} from "./ai-sdk-instrumentation"; /** Translate AI SDK content into the completion format understood by the UI. */ function batchMessages(messages: AISDKMessage[]): Record[] { diff --git a/js/src/instrumentation/plugins/ai-sdk-code-mode.test.ts b/js/src/instrumentation/providers/ai-sdk-code-mode.test.ts similarity index 98% rename from js/src/instrumentation/plugins/ai-sdk-code-mode.test.ts rename to js/src/instrumentation/providers/ai-sdk-code-mode.test.ts index beca0c617..dc3595f54 100644 --- a/js/src/instrumentation/plugins/ai-sdk-code-mode.test.ts +++ b/js/src/instrumentation/providers/ai-sdk-code-mode.test.ts @@ -1,13 +1,14 @@ +import { registerInstrumentation } from "../registrations/aisdk"; import { AsyncResource } from "node:async_hooks"; -import { afterEach, beforeAll, beforeEach, expect, it } from "vitest"; +import { afterEach, beforeAll, beforeEach, expect, it } from "vite-plus/test"; +import { _exportsForTestingOnly } from "../../logger/testing"; import { - _exportsForTestingOnly, NOOP_SPAN, currentSpan, initLogger, startSpan, withCurrent, -} from "../../logger"; +} from "../../logger/core"; import { braintrustAISDKTelemetry } from "../../wrappers/ai-sdk/telemetry"; import { configureNode } from "../../node/config"; import { aiSDKChannels } from "./ai-sdk-interceptors"; @@ -24,6 +25,7 @@ configureNode(); let logger: ReturnType; beforeAll(async () => { await _exportsForTestingOnly.simulateLoginForTests(); + registerInstrumentation(); }); beforeEach(() => { logger = _exportsForTestingOnly.useTestBackgroundLogger(); diff --git a/js/src/instrumentation/plugins/ai-sdk-code-mode.ts b/js/src/instrumentation/providers/ai-sdk-code-mode.ts similarity index 99% rename from js/src/instrumentation/plugins/ai-sdk-code-mode.ts rename to js/src/instrumentation/providers/ai-sdk-code-mode.ts index ca35a9653..02ce7965c 100644 --- a/js/src/instrumentation/plugins/ai-sdk-code-mode.ts +++ b/js/src/instrumentation/providers/ai-sdk-code-mode.ts @@ -1,4 +1,4 @@ -import { currentSpan, withCurrent, type Span } from "../../logger"; +import { currentSpan, withCurrent, type Span } from "../../logger/core"; import { SpanTypeAttribute } from "../../util"; import type { AISDKRunCodeModeInput } from "../../vendor-sdk-types/ai-sdk-code-mode"; import { isAutoInstrumentationSuppressed } from "../auto-instrumentation-suppression"; diff --git a/js/src/instrumentation/plugins/ai-sdk-plugin.streaming.test.ts b/js/src/instrumentation/providers/ai-sdk-instrumentation.streaming.test.ts similarity index 91% rename from js/src/instrumentation/plugins/ai-sdk-plugin.streaming.test.ts rename to js/src/instrumentation/providers/ai-sdk-instrumentation.streaming.test.ts index e55d2b15e..4cc32d270 100644 --- a/js/src/instrumentation/plugins/ai-sdk-plugin.streaming.test.ts +++ b/js/src/instrumentation/providers/ai-sdk-instrumentation.streaming.test.ts @@ -1,3 +1,4 @@ +import { registerInstrumentation } from "../registrations/aisdk"; /* eslint-disable @typescript-eslint/no-explicit-any */ /* eslint-disable @typescript-eslint/consistent-type-assertions */ import * as ai from "ai"; @@ -8,22 +9,17 @@ import { describe, expect, test, -} from "vitest"; -import { - TestBackgroundLogger, - _exportsForTestingOnly, - initLogger, -} from "../../logger"; +} from "vite-plus/test"; +import { TestBackgroundLogger, initLogger } from "../../logger/core"; +import { _exportsForTestingOnly } from "../../logger/testing"; import { configureNode } from "../../node/config"; import { wrapAISDK, wrapAgentClass } from "../../wrappers/ai-sdk"; -import { BraintrustMiddleware } from "../../wrappers/ai-sdk/deprecated/BraintrustMiddleware"; import { captureHarnessCreateSessionParent, harnessContinuationParent, } from "../../wrappers/ai-sdk/harness-agent-context"; import { workflowAgentWrapperSpanCountForTesting } from "../../wrappers/ai-sdk/workflow-agent-context"; import { aiSDKChannels } from "./ai-sdk-interceptors"; -import { AISDKPlugin } from "./ai-sdk-plugin"; try { configureNode(); @@ -42,7 +38,7 @@ describe("AI SDK streaming instrumentation", () => { beforeEach(() => { backgroundLogger = _exportsForTestingOnly.useTestBackgroundLogger(); initLogger({ - projectName: "ai-sdk-plugin.streaming.test.ts", + projectName: "ai-sdk-instrumentation.streaming.test.ts", projectId: "test-project-id", }); }); @@ -137,34 +133,6 @@ describe("AI SDK streaming instrumentation", () => { }); }); - test("BraintrustMiddleware captures nested AI SDK usage", async () => { - await BraintrustMiddleware({}).wrapGenerate!({ - doGenerate: async () => ({ - content: [{ type: "text", text: "hello" }], - finishReason: "stop", - providerMetadata: { openai: {} }, - usage: { - inputTokens: { total: 8, cacheRead: 3, cacheWrite: 1 }, - outputTokens: { total: 2, reasoning: 1 }, - }, - }), - doStream: async () => ({}), - model: { modelId: "gpt-4.1-mini" }, - params: { prompt: "Say hello" }, - }); - - const spans = (await backgroundLogger.drain()) as any[]; - expect(spans).toHaveLength(1); - expect(spans[0].metrics).toMatchObject({ - prompt_tokens: 8, - completion_tokens: 2, - tokens: 10, - prompt_cached_tokens: 3, - prompt_cache_creation_tokens: 1, - completion_reasoning_tokens: 1, - }); - }); - test("wrapped models compose with higher-level AI SDK tracing without duplicate LLM spans", async () => { const model = wrapAISDK({ specificationVersion: "v3", @@ -1157,144 +1125,132 @@ describe("AI SDK streaming instrumentation", () => { test("baseStream patch preserves derived stream getters", async () => { expect(await backgroundLogger.drain()).toHaveLength(0); - const plugin = new AISDKPlugin(); - plugin.enable(); + registerInstrumentation(); + let chunkSent = false; + const result = (await aiSDKChannels.streamText.invoke( + async () => { + const resultRecord = { + baseStream: new ReadableStream({ + pull(controller) { + if (chunkSent) { + controller.close(); + return; + } - try { - let chunkSent = false; - const result = (await aiSDKChannels.streamText.invoke( - async () => { - const resultRecord = { - baseStream: new ReadableStream({ - pull(controller) { - if (chunkSent) { - controller.close(); - return; - } - - chunkSent = true; - controller.enqueue({ - type: "text-delta", - id: "text-1", - delta: "fresh", - }); - }, - }), - text: Promise.resolve("fresh"), - } as any; - - Object.defineProperty(resultRecord, "textStream", { - configurable: true, - enumerable: true, - get() { - const [textBranch, baseBranch] = this.baseStream.tee(); - this.baseStream = baseBranch; - return textBranch.pipeThrough( - new TransformStream({ - transform(chunk: any, controller) { - if (chunk.type === "text-delta") { - controller.enqueue(chunk.delta); - } - }, - }), - ); + chunkSent = true; + controller.enqueue({ + type: "text-delta", + id: "text-1", + delta: "fresh", + }); }, - }); - - return resultRecord; - }, - undefined, - [ - { - model: "mock-stream-model", - prompt: "Reply with fresh.", + }), + text: Promise.resolve("fresh"), + } as any; + + Object.defineProperty(resultRecord, "textStream", { + configurable: true, + enumerable: true, + get() { + const [textBranch, baseBranch] = this.baseStream.tee(); + this.baseStream = baseBranch; + return textBranch.pipeThrough( + new TransformStream({ + transform(chunk: any, controller) { + if (chunk.type === "text-delta") { + controller.enqueue(chunk.delta); + } + }, + }), + ); }, - ], - {}, - )) as any; + }); - expect( - Object.getOwnPropertyDescriptor(result, "textStream")?.get, - ).toEqual(expect.any(Function)); + return resultRecord; + }, + undefined, + [ + { + model: "mock-stream-model", + prompt: "Reply with fresh.", + }, + ], + {}, + )) as any; - let firstText = ""; - for await (const chunk of result.textStream) { - firstText += chunk; - } + expect(Object.getOwnPropertyDescriptor(result, "textStream")?.get).toEqual( + expect.any(Function), + ); - let secondText = ""; - for await (const chunk of result.textStream) { - secondText += chunk; - } + let firstText = ""; + for await (const chunk of result.textStream) { + firstText += chunk; + } - expect(firstText).toBe("fresh"); - expect(secondText).toBe("fresh"); - } finally { - plugin.disable(); + let secondText = ""; + for await (const chunk of result.textStream) { + secondText += chunk; } + + expect(firstText).toBe("fresh"); + expect(secondText).toBe("fresh"); }); test("async iterable stream accessors preserve ReadableStream methods", async () => { expect(await backgroundLogger.drain()).toHaveLength(0); - const plugin = new AISDKPlugin(); - plugin.enable(); - - try { - const result = (await aiSDKChannels.streamText.invoke( - async () => { - const resultRecord = { - stream: new ReadableStream({ - start(controller) { - controller.enqueue("v7"); - controller.close(); - }, - }), - text: Promise.resolve("v7"), - } as any; - - Object.defineProperty(resultRecord, "textStream", { - configurable: true, - enumerable: true, - get() { - return this.stream.pipeThrough( - new TransformStream({ - transform(chunk: string, controller) { - controller.enqueue(chunk.toUpperCase()); - }, - }), - ); + registerInstrumentation(); + const result = (await aiSDKChannels.streamText.invoke( + async () => { + const resultRecord = { + stream: new ReadableStream({ + start(controller) { + controller.enqueue("v7"); + controller.close(); }, - }); + }), + text: Promise.resolve("v7"), + } as any; + + Object.defineProperty(resultRecord, "textStream", { + configurable: true, + enumerable: true, + get() { + return this.stream.pipeThrough( + new TransformStream({ + transform(chunk: string, controller) { + controller.enqueue(chunk.toUpperCase()); + }, + }), + ); + }, + }); - return resultRecord; + return resultRecord; + }, + undefined, + [ + { + model: "mock-v7-stream-model", + prompt: "Reply with v7.", }, - undefined, - [ - { - model: "mock-v7-stream-model", - prompt: "Reply with v7.", - }, - ], - {}, - )) as any; + ], + {}, + )) as any; - expect(result.stream.pipeThrough).toEqual(expect.any(Function)); - expect(result.stream.getReader).toEqual(expect.any(Function)); + expect(result.stream.pipeThrough).toEqual(expect.any(Function)); + expect(result.stream.getReader).toEqual(expect.any(Function)); - const textStream = result.textStream; - expect(textStream.pipeThrough).toEqual(expect.any(Function)); - expect(textStream.getReader).toEqual(expect.any(Function)); + const textStream = result.textStream; + expect(textStream.pipeThrough).toEqual(expect.any(Function)); + expect(textStream.getReader).toEqual(expect.any(Function)); - const reader = textStream.getReader(); - const first = await reader.read(); - const second = await reader.read(); + const reader = textStream.getReader(); + const first = await reader.read(); + const second = await reader.read(); - expect(first).toEqual({ done: false, value: "V7" }); - expect(second).toEqual({ done: true, value: undefined }); - } finally { - plugin.disable(); - } + expect(first).toEqual({ done: false, value: "V7" }); + expect(second).toEqual({ done: true, value: undefined }); }); test("wrapAgentClass instruments all HarnessAgent turn methods without serializing sessions", async () => { diff --git a/js/src/instrumentation/plugins/ai-sdk-plugin.test.ts b/js/src/instrumentation/providers/ai-sdk-instrumentation.test.ts similarity index 94% rename from js/src/instrumentation/plugins/ai-sdk-plugin.test.ts rename to js/src/instrumentation/providers/ai-sdk-instrumentation.test.ts index 36191e8bc..36a66676e 100644 --- a/js/src/instrumentation/plugins/ai-sdk-plugin.test.ts +++ b/js/src/instrumentation/providers/ai-sdk-instrumentation.test.ts @@ -1,4 +1,4 @@ -import { afterEach, beforeEach, describe, expect, it, vi } from "vitest"; +import { beforeEach, describe, expect, it, vi } from "vite-plus/test"; import { newGlobalInvocationHook } from "../../global-instrumentation-hooks"; vi.mock("../../global-instrumentation-hooks", async (importOriginal) => ({ ...(await importOriginal< @@ -19,8 +19,11 @@ const telemetryMocks = vi.hoisted(() => ({ })); // Mock platform context independently of invocation hooks. -vi.mock("../../isomorph", () => ({ - default: {}, +vi.mock("#braintrust-platform", () => ({ + default: { + getEnv: (name: string) => + name === "BRAINTRUST_AUTO_CAPTURE_ATTACHMENTS" ? "true" : undefined, + }, })); vi.mock("../../wrappers/ai-sdk/telemetry", () => ({ @@ -33,7 +36,7 @@ import { } from "../../vendor-sdk-types/ai-sdk-v7-telemetry"; import { serializeAISDKToolsForLogging } from "../../wrappers/ai-sdk/tool-serialization"; import { - AISDKPlugin, + registerAISDKInstrumentation, DEFAULT_DENY_OUTPUT_PATHS, extractTokenMetrics, processAISDKCallInput, @@ -43,7 +46,7 @@ import { processAISDKWorkflowAgentCallInput, processAISDKWorkflowAgentModelCallInput, serializeModelWithProvider, -} from "./ai-sdk-plugin"; +} from "./ai-sdk-instrumentation"; const mockNewInvocationHook = newGlobalInvocationHook as ReturnType< typeof vi.fn @@ -53,7 +56,6 @@ type MockTracingChannel = { hasSubscribers: boolean; intercept: ReturnType; subscribe: ReturnType; - unsubscribe: ReturnType; }; const mockChannels = new Map(); @@ -61,9 +63,7 @@ const mockChannels = new Map(); // Since these are private, we'll test them through the public API // But we'll also add some tests for the exported utility functions -describe("AISDKPlugin", () => { - let plugin: AISDKPlugin; - +describe("registerAISDKInstrumentation", () => { beforeEach(() => { mockChannels.clear(); telemetryMocks.telemetry = { @@ -101,39 +101,10 @@ describe("AISDKPlugin", () => { channel.handlers.push(handlers); channel.hasSubscribers = true; }), - unsubscribe: vi.fn((handlers: any) => { - channel.handlers = channel.handlers.filter( - (candidate) => candidate !== handlers, - ); - channel.hasSubscribers = channel.handlers.length > 0; - return true; - }), }; mockChannels.set(name, channel); return channel; }); - - plugin = new AISDKPlugin(); - }); - - afterEach(() => { - if (plugin) { - plugin.disable(); - } - }); - - describe("constructor", () => { - it("should create plugin with default config", () => { - const defaultPlugin = new AISDKPlugin(); - expect(defaultPlugin).toBeInstanceOf(AISDKPlugin); - }); - - it("should create plugin with custom config", () => { - const customPlugin = new AISDKPlugin({ - denyOutputPaths: ["custom.path"], - }); - expect(customPlugin).toBeInstanceOf(AISDKPlugin); - }); }); describe("WorkflowAgent input extraction", () => { @@ -223,39 +194,15 @@ describe("AISDKPlugin", () => { }); }); - describe("enable/disable", () => { - it("should enable plugin", () => { - expect(() => plugin.enable()).not.toThrow(); - }); - - it("should disable plugin", () => { - plugin.enable(); - expect(() => plugin.disable()).not.toThrow(); - }); - - it("should handle multiple enable calls", () => { - plugin.enable(); - expect(() => plugin.enable()).not.toThrow(); - }); - - it("should handle multiple disable calls", () => { - plugin.enable(); - plugin.disable(); - expect(() => plugin.disable()).not.toThrow(); - }); - - it("should unsubscribe from channels on disable", () => { - plugin.enable(); - plugin.disable(); - // Verify that unsubscribers were called - // This is tested indirectly - if it doesn't throw, unsubscribe worked - expect(true).toBe(true); + describe("registration", () => { + it("registers without throwing", () => { + expect(() => registerAISDKInstrumentation()).not.toThrow(); }); }); describe("generateImage", () => { it("subscribes to the image generation channel", () => { - plugin.enable(); + registerAISDKInstrumentation(); expect( mockChannels.get("orchestrion:ai:generateImage")?.intercept, @@ -398,7 +345,7 @@ describe("AISDKPlugin", () => { onStepEnd: existingOnStepEnd, }; - plugin.enable(); + registerAISDKInstrumentation(); const channel = mockChannels.get( "orchestrion:ai:createTelemetryDispatcher", @@ -469,7 +416,7 @@ describe("AISDKPlugin", () => { onStart: existingOnStart, }; - plugin.enable(); + registerAISDKInstrumentation(); const channel = mockChannels.get( "orchestrion:ai:createTelemetryDispatcher", @@ -528,7 +475,7 @@ describe("AISDKPlugin", () => { onStart: vi.fn(), }; - plugin.enable(); + registerAISDKInstrumentation(); const channel = mockChannels.get( "orchestrion:ai:createTelemetryDispatcher", @@ -592,7 +539,7 @@ describe("AISDKPlugin", () => { onStart: existingOnStart, }; - plugin.enable(); + registerAISDKInstrumentation(); const channel = mockChannels.get( "orchestrion:ai:createTelemetryDispatcher", @@ -619,7 +566,7 @@ describe("AISDKPlugin", () => { const explicit = { [BRAINTRUST_AI_SDK_V7_TELEMETRY]: true }; vi.stubGlobal("AI_SDK_TELEMETRY_INTEGRATIONS", [explicit]); try { - plugin.enable(); + registerAISDKInstrumentation(); const channel = mockChannels.get( "orchestrion:ai:createTelemetryDispatcher", ); @@ -649,7 +596,7 @@ describe("AISDKPlugin", () => { onStart: vi.fn(), }; - plugin.enable(); + registerAISDKInstrumentation(); const channel = mockChannels.get( "orchestrion:ai:createTelemetryDispatcher", @@ -1004,32 +951,6 @@ describe("AI SDK utility functions", () => { const result = omit(obj, []); expect(result).toEqual({ a: 1, b: 2 }); }); - - it("should handle complex AI SDK paths", () => { - const obj = { - roundtrips: [ - { - request: { body: "sensitive" }, - response: { headers: "sensitive" }, - }, - ], - rawResponse: { headers: "sensitive" }, - }; - const result = omit(obj, [ - "roundtrips[].request.body", - "roundtrips[].response.headers", - "rawResponse.headers", - ]); - expect(result).toEqual({ - roundtrips: [ - { - request: { body: "" }, - response: { headers: "" }, - }, - ], - rawResponse: { headers: "" }, - }); - }); }); describe("extractGetterValues", () => { @@ -1694,16 +1615,16 @@ describe("AI SDK utility functions", () => { it("should omit specified paths", () => { const output = { text: "Hello", - roundtrips: [ + steps: [ { request: { body: "sensitive" }, response: { data: "ok" }, }, ], }; - const result = processAISDKOutput(output, ["roundtrips[].request.body"]); - expect(result.roundtrips[0].request.body).toBe(""); - expect(result.roundtrips[0].response.data).toBe("ok"); + const result = processAISDKOutput(output, ["steps[].request.body"]); + expect(result.steps[0].request.body).toBe(""); + expect(result.steps[0].response.data).toBe("ok"); }); it("preserves user headers fields while omitting configured transport headers", () => { @@ -1756,21 +1677,6 @@ describe("AI SDK utility functions", () => { id: "provider-response-id", }, ], - roundtrips: [ - { - request: { - headers: { authorization: "secret" }, - body: "secret-roundtrip-request-body", - providerPayload: { - headers: { authorization: "nested-roundtrip-request-secret" }, - }, - }, - response: { - headers: { authorization: "secret" }, - id: "roundtrip-response-id", - }, - }, - ], steps: [ { request: { @@ -1824,7 +1730,6 @@ describe("AI SDK utility functions", () => { }); const serializedResult = JSON.stringify(result); expect(serializedResult).not.toContain("secret-request-body"); - expect(serializedResult).not.toContain("secret-roundtrip-request-body"); expect(serializedResult).not.toContain("secret-step-request-body"); expect(serializedResult).not.toContain("authorization"); expect(result.request).not.toHaveProperty("headers"); @@ -1840,13 +1745,6 @@ describe("AI SDK utility functions", () => { ); expect(result.rawResponse).not.toHaveProperty("headers"); expect(result.responses[0]).not.toHaveProperty("headers"); - if (result.roundtrips) { - expect(result.roundtrips[0].request).not.toHaveProperty("headers"); - expect(result.roundtrips[0].request.providerPayload).not.toHaveProperty( - "headers", - ); - expect(result.roundtrips[0].response).not.toHaveProperty("headers"); - } expect(result.steps[0].request).not.toHaveProperty("headers"); expect(result.steps[0].request.providerPayload).not.toHaveProperty( "headers", diff --git a/js/src/instrumentation/plugins/ai-sdk-plugin.ts b/js/src/instrumentation/providers/ai-sdk-instrumentation.ts similarity index 81% rename from js/src/instrumentation/plugins/ai-sdk-plugin.ts rename to js/src/instrumentation/providers/ai-sdk-instrumentation.ts index bee9e31af..684555ce7 100644 --- a/js/src/instrumentation/plugins/ai-sdk-plugin.ts +++ b/js/src/instrumentation/providers/ai-sdk-instrumentation.ts @@ -8,12 +8,11 @@ import { } from "./ai-sdk-batch-instrumentation"; import type { ReturnOf } from "../core/interceptor-definitions"; import { debugLogger } from "../../debug-logger"; -import { BasePlugin, toLoggedError } from "../core"; +import { toLoggedError } from "../core"; import { traceAsyncCall, traceStreamingCall, traceSyncStreamCall, - unsubscribeAll, } from "../core/tracing"; import { observeResult, runInstrumentation } from "../core/observe-result"; @@ -23,17 +22,55 @@ import { isPromiseLike, } from "../../../util/index"; import { - Attachment, + _internalCaptureAttachmentsEnabled, _internalStartSpanWithInitialMerge, + Attachment, + BaseAttachment, + CAPTURE_ATTACHMENTS, currentSpan, + deepCopyEvent, startSpan, withCurrent, type Span, -} from "../../logger"; +} from "../../logger/core"; import { INSTRUMENTATION_NAMES, withSpanInstrumentationName, } from "../../span-origin"; +import { + convertDataToBlob, + getExtensionFromMediaType, + omitMediaData, + processInputAttachments, +} from "../../wrappers/attachment-utils"; +import { normalizeAISDKLoggedOutput } from "../../wrappers/ai-sdk/normalize-logged-output"; +import { serializeAISDKToolsForLogging } from "../../wrappers/ai-sdk/tool-serialization"; +import { braintrustAISDKTelemetry } from "../../wrappers/ai-sdk/telemetry"; +import { + runWithHarnessTurnParent, + captureHarnessCreateSessionParent, + endHarnessTurn, + harnessContinuationParent, + registerHarnessSessionParent, + registerHarnessTurnSpan, + updateHarnessTurn, + type HarnessTurnParent, +} from "../../wrappers/ai-sdk/harness-agent-context"; +import { + registerWorkflowAgentWrapperSpan, + unregisterWorkflowAgentWrapperSpan, +} from "../../wrappers/ai-sdk/workflow-agent-context"; +import { zodToJsonSchema } from "../../zod/utils"; +import { + aiSDKChannels, + BRAINTRUST_WRAPPED_AI_SDK_MODEL, + harnessAgentChannels, +} from "./ai-sdk-interceptors"; +import { extractTokenMetrics } from "./ai-sdk-metrics"; +import { + isAutoInstrumentationSuppressed, + runWithAutoInstrumentationSuppressed, +} from "../auto-instrumentation-suppression"; import { getCurrentUnixTimestamp } from "../../util"; import type { AISDK, @@ -68,52 +105,23 @@ import { BRAINTRUST_AI_SDK_V7_TELEMETRY, BRAINTRUST_AI_SDK_V7_OPERATION_KEY as AI_SDK_V7_OPERATION_KEY, } from "../../vendor-sdk-types/ai-sdk-v7-telemetry"; -import { - captureHarnessCreateSessionParent, - endHarnessTurn, - harnessContinuationParent, - registerHarnessSessionParent, - registerHarnessTurnSpan, - runWithHarnessTurnParent, - updateHarnessTurn, - type HarnessTurnParent, -} from "../../wrappers/ai-sdk/harness-agent-context"; -import { normalizeAISDKLoggedOutput } from "../../wrappers/ai-sdk/normalize-logged-output"; + import { aiSDKModelOutputToolNames, applyAISDKToolModelOutputs, hasAISDKToolModelOutput, } from "../../wrappers/ai-sdk/tool-model-output"; -import { braintrustAISDKTelemetry } from "../../wrappers/ai-sdk/telemetry"; -import { serializeAISDKToolsForLogging } from "../../wrappers/ai-sdk/tool-serialization"; -import { - registerWorkflowAgentWrapperSpan, - unregisterWorkflowAgentWrapperSpan, -} from "../../wrappers/ai-sdk/workflow-agent-context"; -import { - convertDataToBlob, - getExtensionFromMediaType, -} from "../../wrappers/attachment-utils"; -import { zodToJsonSchema } from "../../zod/utils"; -import { - isAutoInstrumentationSuppressed, - runWithAutoInstrumentationSuppressed, -} from "../auto-instrumentation-suppression"; + import { isAsyncIterable, patchStreamIfNeeded } from "../core/stream-patcher"; import type { ChannelMessage } from "../core/tracing-types"; -import { - BRAINTRUST_WRAPPED_AI_SDK_MODEL, - aiSDKChannels, - codeModeChannels, - harnessAgentChannels, -} from "./ai-sdk-interceptors"; -import { extractTokenMetrics } from "./ai-sdk-metrics"; +import { codeModeChannels } from "./ai-sdk-interceptors"; + import { currentCloudflareThinkRun } from "./cloudflare-think-context"; -interface AISDKPluginConfig { +interface AISDKInstrumentationConfig { /** * List of JSON paths to remove from output field. - * Uses dot notation with array wildcards: "roundtrips[].request.body" + * Uses dot notation with array wildcards: "steps[].request.body" */ denyOutputPaths?: string[]; } @@ -123,10 +131,6 @@ interface AISDKPluginConfig { * These contain redundant or verbose data that's not useful for tracing. */ export const DEFAULT_DENY_OUTPUT_PATHS: string[] = [ - // v3 - "roundtrips[].request.body", - "roundtrips[].request.headers", - "roundtrips[].response.headers", "rawResponse.headers", "responseMessages", // v5 @@ -156,8 +160,6 @@ const TRANSPORT_PAYLOAD_ROOT_PATHS = [ "request", "response", "responses[]", - "roundtrips[].request", - "roundtrips[].response", "steps[].request", "steps[].response", "steps[].responses[]", @@ -189,10 +191,10 @@ const AI_SDK_V7_TELEMETRY_CALLBACKS = [ ] as const; /** - * AI SDK plugin that subscribes to instrumentation channels + * Internal AI SDK consumer that subscribes to instrumentation channels * and creates Braintrust spans. * - * This plugin handles: + * This consumer handles: * - generateText (async function) * - streamText (function returning stream) * - generateObject (async function) @@ -206,134 +208,141 @@ const AI_SDK_V7_TELEMETRY_CALLBACKS = [ * - ToolLoopAgent.stream (async method returning stream) * - WorkflowAgent.stream (async method returning stream) * - * The plugin automatically extracts: + * The consumer automatically extracts: * - Model and provider information * - Token usage metrics * - Tool calls and structured outputs * - Streaming responses with time-to-first-token */ -export class AISDKPlugin extends BasePlugin { - private config: AISDKPluginConfig; +class AISDKInstrumentationConsumer { + private config: AISDKInstrumentationConfig; - constructor(config: AISDKPluginConfig = {}) { - super(); + constructor(config: AISDKInstrumentationConfig = {}) { this.config = config; } - protected onEnable(): void { + public register(): void { this.subscribeToAISDK(); } - protected onDisable(): void { - this.unsubscribers = unsubscribeAll(this.unsubscribers); - } - private subscribeToAISDK(): void { const denyOutputPaths = this.config.denyOutputPaths || DEFAULT_DENY_OUTPUT_PATHS; - this.unsubscribers.push( - aiSDKBatchChannels.start.intercept(async (target, receiver, args) => { - await Reflect.apply(target, receiver, args); - return captureAISDKBatchStart(args[0]); - }), - aiSDKBatchChannels.complete.intercept(async (target, receiver, args) => { - await Reflect.apply(target, receiver, args); - await captureAISDKBatchCompletion(args[0], denyOutputPaths); - }), - ); + aiSDKBatchChannels.start.intercept(async (target, receiver, args) => { + await Reflect.apply(target, receiver, args); + return captureAISDKBatchStart(args[0]); + }); + aiSDKBatchChannels.complete.intercept(async (target, receiver, args) => { + await Reflect.apply(target, receiver, args); + await captureAISDKBatchCompletion(args[0], denyOutputPaths); + }); - this.unsubscribers.push( - codeModeChannels.run.intercept((target, receiver, [input]) => - traceAISDKCodeMode( - (params) => Reflect.apply(target, receiver, [params]), - input, - ), + codeModeChannels.run.intercept((target, receiver, [input]) => + traceAISDKCodeMode( + (params) => Reflect.apply(target, receiver, [params]), + input, ), - interceptAISDKV7TelemetryDispatcher(), - interceptAISDKEvaluate(denyOutputPaths), - interceptAISDKModelGenerate(denyOutputPaths), - interceptAISDKModelStream(denyOutputPaths), - subscribeToHarnessAgentCreateSession(), ); - this.unsubscribers.push( - harnessAgentChannels.continueGenerate.intercept( - (target, receiver, args, additional) => - traceHarnessContinuation( - () => Reflect.apply(target, receiver, args), - { ...additional, arguments: args, self: receiver }, - denyOutputPaths, - ), - ), - harnessAgentChannels.continueStream.intercept( - (target, receiver, args, additional) => - traceHarnessContinuation( - () => Reflect.apply(target, receiver, args), - { ...additional, arguments: args, self: receiver }, - denyOutputPaths, - ), - ), + interceptAISDKV7TelemetryDispatcher(); + interceptAISDKEvaluate(denyOutputPaths); + interceptAISDKModelGenerate(denyOutputPaths); + interceptAISDKModelStream(denyOutputPaths); + subscribeToHarnessAgentCreateSession(); + harnessAgentChannels.continueGenerate.intercept( + (target, receiver, args, additional) => + traceHarnessContinuation( + () => Reflect.apply(target, receiver, args), + { ...additional, arguments: args, self: receiver }, + denyOutputPaths, + ), + ); + harnessAgentChannels.continueStream.intercept( + (target, receiver, args, additional) => + traceHarnessContinuation( + () => Reflect.apply(target, receiver, args), + { ...additional, arguments: args, self: receiver }, + denyOutputPaths, + ), ); // generateText - async function that may return streams - this.unsubscribers.push( - aiSDKChannels.generateText.intercept( - (target, receiver, args, additional) => - traceStreamingCall( - () => Reflect.apply(target, receiver, args), - { ...additional, arguments: args, self: receiver }, - { - instrumentationName: INSTRUMENTATION_NAMES.AI_SDK, - name: "generateText", - type: SpanTypeAttribute.FUNCTION, - extractInput: ([params], event, span) => - prepareAISDKCallInput(params, event, span, denyOutputPaths), - extractOutput: (result, endEvent) => { - finalizeAISDKChildTracing( - endEvent as { [key: string]: unknown }, - ); - return processAISDKCallOutput( - result, - endEvent, - denyOutputPaths, - ); - }, - extractMetrics: (result, _startTime, endEvent) => - extractTopLevelAISDKMetrics(result, endEvent), - aggregateChunks: aggregateAISDKChunks, - }, - ), + aiSDKChannels.generateText.intercept((target, receiver, args, additional) => + traceStreamingCall( + () => Reflect.apply(target, receiver, args), + { ...additional, arguments: args, self: receiver }, + { + instrumentationName: INSTRUMENTATION_NAMES.AI_SDK, + name: "generateText", + type: SpanTypeAttribute.FUNCTION, + extractInput: ([params], event, span) => + prepareAISDKCallInput(params, event, span, denyOutputPaths), + extractOutput: (result, endEvent) => { + finalizeAISDKChildTracing(endEvent as { [key: string]: unknown }); + return processAISDKCallOutput(result, endEvent, denyOutputPaths); + }, + extractMetrics: (result, _startTime, endEvent) => + extractTopLevelAISDKMetrics(result, endEvent), + aggregateChunks: aggregateAISDKChunks, + }, ), ); // generateImage - async image generation function (v5 experimental, v6+ stable) - this.unsubscribers.push( - aiSDKChannels.generateImage.intercept( - (target, receiver, args, additional) => - traceAsyncCall( - () => Reflect.apply(target, receiver, args), - { ...additional, arguments: args, self: receiver }, - { - instrumentationName: INSTRUMENTATION_NAMES.AI_SDK, - name: "generateImage", - type: SpanTypeAttribute.LLM, - extractInput: ([params], event) => - prepareAISDKGenerateImageInput(params, event.self), - extractOutput: (result, endEvent) => - processAISDKGenerateImageOutput( - result, - resolveDenyOutputPaths(endEvent, denyOutputPaths), - ), - extractMetrics: (result) => extractTokenMetrics(result), - }, - ), - ), + aiSDKChannels.generateImage.intercept( + (target, receiver, args, additional) => + traceAsyncCall( + () => Reflect.apply(target, receiver, args), + { ...additional, arguments: args, self: receiver }, + { + instrumentationName: INSTRUMENTATION_NAMES.AI_SDK, + name: "generateImage", + type: SpanTypeAttribute.LLM, + extractInput: ([params], event) => + prepareAISDKGenerateImageInput(params, event.self), + extractOutput: (result, endEvent) => + processAISDKGenerateImageOutput( + result, + resolveDenyOutputPaths(endEvent, denyOutputPaths), + ), + extractMetrics: (result) => extractTokenMetrics(result), + }, + ), ); // streamText - function returning stream - this.unsubscribers.push( - aiSDKChannels.streamText.intercept((target, receiver, args, additional) => - traceStreamingCall( + aiSDKChannels.streamText.intercept((target, receiver, args, additional) => + traceStreamingCall( + () => Reflect.apply(target, receiver, args), + { ...additional, arguments: args, self: receiver }, + { + instrumentationName: INSTRUMENTATION_NAMES.AI_SDK, + name: "streamText", + type: SpanTypeAttribute.FUNCTION, + shouldTrace: () => currentCloudflareThinkRun() === undefined, + extractInput: ([params], event, span) => + prepareAISDKCallInput(params, event, span, denyOutputPaths), + extractOutput: (result, endEvent) => + processAISDKCallOutput(result, endEvent, denyOutputPaths), + extractMetrics: (result, startTime, endEvent) => + extractTopLevelAISDKMetrics(result, endEvent, startTime), + aggregateChunks: aggregateAISDKChunks, + patchResult: ({ endEvent, result, span, startTime }) => + patchAISDKStreamingResult({ + defaultDenyOutputPaths: denyOutputPaths, + endEvent, + result, + span, + startTime, + }), + }, + ), + ); + + // streamText - sync function returning stream (v4+, used by auto-hook) + aiSDKChannels.streamTextSync.intercept( + (target, receiver, args, additional) => + traceSyncStreamCall( () => Reflect.apply(target, receiver, args), { ...additional, arguments: args, self: receiver }, { @@ -343,11 +352,6 @@ export class AISDKPlugin extends BasePlugin { shouldTrace: () => currentCloudflareThinkRun() === undefined, extractInput: ([params], event, span) => prepareAISDKCallInput(params, event, span, denyOutputPaths), - extractOutput: (result, endEvent) => - processAISDKCallOutput(result, endEvent, denyOutputPaths), - extractMetrics: (result, startTime, endEvent) => - extractTopLevelAISDKMetrics(result, endEvent, startTime), - aggregateChunks: aggregateAISDKChunks, patchResult: ({ endEvent, result, span, startTime }) => patchAISDKStreamingResult({ defaultDenyOutputPaths: denyOutputPaths, @@ -358,538 +362,450 @@ export class AISDKPlugin extends BasePlugin { }), }, ), - ), - ); - - // streamText - sync function returning stream (v4+, used by auto-hook) - this.unsubscribers.push( - aiSDKChannels.streamTextSync.intercept( - (target, receiver, args, additional) => - traceSyncStreamCall( - () => Reflect.apply(target, receiver, args), - { ...additional, arguments: args, self: receiver }, - { - instrumentationName: INSTRUMENTATION_NAMES.AI_SDK, - name: "streamText", - type: SpanTypeAttribute.FUNCTION, - shouldTrace: () => currentCloudflareThinkRun() === undefined, - extractInput: ([params], event, span) => - prepareAISDKCallInput(params, event, span, denyOutputPaths), - patchResult: ({ endEvent, result, span, startTime }) => - patchAISDKStreamingResult({ - defaultDenyOutputPaths: denyOutputPaths, - endEvent, - result, - span, - startTime, - }), - }, - ), - ), ); // generateObject - async function that may return streams - this.unsubscribers.push( - aiSDKChannels.generateObject.intercept( - (target, receiver, args, additional) => - traceStreamingCall( - () => Reflect.apply(target, receiver, args), - { ...additional, arguments: args, self: receiver }, - { - instrumentationName: INSTRUMENTATION_NAMES.AI_SDK, - name: "generateObject", - type: SpanTypeAttribute.FUNCTION, - extractInput: ([params], event, span) => - prepareAISDKCallInput(params, event, span, denyOutputPaths), - extractOutput: (result, endEvent) => { - finalizeAISDKChildTracing( - endEvent as { [key: string]: unknown }, - ); - return processAISDKCallOutput( - result, - endEvent, - denyOutputPaths, - ); - }, - extractMetrics: (result, _startTime, endEvent) => - extractTopLevelAISDKMetrics(result, endEvent), - aggregateChunks: aggregateAISDKChunks, - }, - ), - ), - ); - - // streamObject - function returning stream - this.unsubscribers.push( - aiSDKChannels.streamObject.intercept( - (target, receiver, args, additional) => - traceStreamingCall( - () => Reflect.apply(target, receiver, args), - { ...additional, arguments: args, self: receiver }, - { - instrumentationName: INSTRUMENTATION_NAMES.AI_SDK, - name: "streamObject", - type: SpanTypeAttribute.FUNCTION, - extractInput: ([params], event, span) => - prepareAISDKCallInput(params, event, span, denyOutputPaths), - extractOutput: (result, endEvent) => - processAISDKCallOutput(result, endEvent, denyOutputPaths), - extractMetrics: (result, startTime, endEvent) => - extractTopLevelAISDKMetrics(result, endEvent, startTime), - aggregateChunks: aggregateAISDKChunks, - patchResult: ({ endEvent, result, span, startTime }) => - patchAISDKStreamingResult({ - defaultDenyOutputPaths: denyOutputPaths, - endEvent, - result, - span, - startTime, - }), - }, - ), - ), - ); - - // streamObject - sync function returning stream (v4+, used by auto-hook) - this.unsubscribers.push( - aiSDKChannels.streamObjectSync.intercept( - (target, receiver, args, additional) => - traceSyncStreamCall( - () => Reflect.apply(target, receiver, args), - { ...additional, arguments: args, self: receiver }, - { - instrumentationName: INSTRUMENTATION_NAMES.AI_SDK, - name: "streamObject", - type: SpanTypeAttribute.FUNCTION, - extractInput: ([params], event, span) => - prepareAISDKCallInput(params, event, span, denyOutputPaths), - patchResult: ({ endEvent, result, span, startTime }) => - patchAISDKStreamingResult({ - defaultDenyOutputPaths: denyOutputPaths, - endEvent, - result, - span, - startTime, - }), - }, - ), - ), - ); - - // embed - async embedding function - this.unsubscribers.push( - aiSDKChannels.embed.intercept((target, receiver, args, additional) => - traceAsyncCall( + aiSDKChannels.generateObject.intercept( + (target, receiver, args, additional) => + traceStreamingCall( () => Reflect.apply(target, receiver, args), { ...additional, arguments: args, self: receiver }, { instrumentationName: INSTRUMENTATION_NAMES.AI_SDK, - name: "embed", + name: "generateObject", type: SpanTypeAttribute.FUNCTION, - extractInput: ([params], event) => - prepareAISDKEmbedInput(params, event.self), - extractOutput: (result, endEvent) => - processAISDKEmbeddingOutput( - result, - resolveDenyOutputPaths(endEvent, denyOutputPaths), - ), + extractInput: ([params], event, span) => + prepareAISDKCallInput(params, event, span, denyOutputPaths), + extractOutput: (result, endEvent) => { + finalizeAISDKChildTracing(endEvent as { [key: string]: unknown }); + return processAISDKCallOutput(result, endEvent, denyOutputPaths); + }, extractMetrics: (result, _startTime, endEvent) => extractTopLevelAISDKMetrics(result, endEvent), + aggregateChunks: aggregateAISDKChunks, }, ), + ); + + // streamObject - function returning stream + aiSDKChannels.streamObject.intercept((target, receiver, args, additional) => + traceStreamingCall( + () => Reflect.apply(target, receiver, args), + { ...additional, arguments: args, self: receiver }, + { + instrumentationName: INSTRUMENTATION_NAMES.AI_SDK, + name: "streamObject", + type: SpanTypeAttribute.FUNCTION, + extractInput: ([params], event, span) => + prepareAISDKCallInput(params, event, span, denyOutputPaths), + extractOutput: (result, endEvent) => + processAISDKCallOutput(result, endEvent, denyOutputPaths), + extractMetrics: (result, startTime, endEvent) => + extractTopLevelAISDKMetrics(result, endEvent, startTime), + aggregateChunks: aggregateAISDKChunks, + patchResult: ({ endEvent, result, span, startTime }) => + patchAISDKStreamingResult({ + defaultDenyOutputPaths: denyOutputPaths, + endEvent, + result, + span, + startTime, + }), + }, ), ); - // embedMany - async embedding batch function - this.unsubscribers.push( - aiSDKChannels.embedMany.intercept((target, receiver, args, additional) => - traceAsyncCall( + // streamObject - sync function returning stream (v4+, used by auto-hook) + aiSDKChannels.streamObjectSync.intercept( + (target, receiver, args, additional) => + traceSyncStreamCall( () => Reflect.apply(target, receiver, args), { ...additional, arguments: args, self: receiver }, { instrumentationName: INSTRUMENTATION_NAMES.AI_SDK, - name: "embedMany", + name: "streamObject", type: SpanTypeAttribute.FUNCTION, - extractInput: ([params], event) => - prepareAISDKEmbedInput(params, event.self), - extractOutput: (result, endEvent) => - processAISDKEmbeddingOutput( + extractInput: ([params], event, span) => + prepareAISDKCallInput(params, event, span, denyOutputPaths), + patchResult: ({ endEvent, result, span, startTime }) => + patchAISDKStreamingResult({ + defaultDenyOutputPaths: denyOutputPaths, + endEvent, result, - resolveDenyOutputPaths(endEvent, denyOutputPaths), - ), - extractMetrics: (result, _startTime, endEvent) => - extractTopLevelAISDKMetrics(result, endEvent), + span, + startTime, + }), }, ), + ); + + // embed - async embedding function + aiSDKChannels.embed.intercept((target, receiver, args, additional) => + traceAsyncCall( + () => Reflect.apply(target, receiver, args), + { ...additional, arguments: args, self: receiver }, + { + instrumentationName: INSTRUMENTATION_NAMES.AI_SDK, + name: "embed", + type: SpanTypeAttribute.FUNCTION, + extractInput: ([params], event) => + prepareAISDKEmbedInput(params, event.self), + extractOutput: (result, endEvent) => + processAISDKEmbeddingOutput( + result, + resolveDenyOutputPaths(endEvent, denyOutputPaths), + ), + extractMetrics: (result, _startTime, endEvent) => + extractTopLevelAISDKMetrics(result, endEvent), + }, + ), + ); + + // embedMany - async embedding batch function + aiSDKChannels.embedMany.intercept((target, receiver, args, additional) => + traceAsyncCall( + () => Reflect.apply(target, receiver, args), + { ...additional, arguments: args, self: receiver }, + { + instrumentationName: INSTRUMENTATION_NAMES.AI_SDK, + name: "embedMany", + type: SpanTypeAttribute.FUNCTION, + extractInput: ([params], event) => + prepareAISDKEmbedInput(params, event.self), + extractOutput: (result, endEvent) => + processAISDKEmbeddingOutput( + result, + resolveDenyOutputPaths(endEvent, denyOutputPaths), + ), + extractMetrics: (result, _startTime, endEvent) => + extractTopLevelAISDKMetrics(result, endEvent), + }, ), ); // rerank - async reranking function - this.unsubscribers.push( - aiSDKChannels.rerank.intercept((target, receiver, args, additional) => - traceAsyncCall( + aiSDKChannels.rerank.intercept((target, receiver, args, additional) => + traceAsyncCall( + () => Reflect.apply(target, receiver, args), + { ...additional, arguments: args, self: receiver }, + { + instrumentationName: INSTRUMENTATION_NAMES.AI_SDK, + name: "rerank", + type: SpanTypeAttribute.FUNCTION, + extractInput: ([params], event) => + prepareAISDKRerankInput(params, event.self), + extractOutput: (result, endEvent) => + processAISDKRerankOutput( + result, + resolveDenyOutputPaths(endEvent, denyOutputPaths), + ), + extractMetrics: (result, _startTime, endEvent) => + extractTopLevelAISDKMetrics(result, endEvent), + }, + ), + ); + + // Agent.generate - async method + aiSDKChannels.agentGenerate.intercept( + (target, receiver, args, additional) => + traceStreamingCall( () => Reflect.apply(target, receiver, args), { ...additional, arguments: args, self: receiver }, { instrumentationName: INSTRUMENTATION_NAMES.AI_SDK, - name: "rerank", + name: "Agent.generate", type: SpanTypeAttribute.FUNCTION, - extractInput: ([params], event) => - prepareAISDKRerankInput(params, event.self), - extractOutput: (result, endEvent) => - processAISDKRerankOutput( - result, - resolveDenyOutputPaths(endEvent, denyOutputPaths), - ), + extractInput: ([params], event, span) => + prepareAISDKCallInput(params, event, span, denyOutputPaths, { + agentOwner: true, + }), + extractOutput: (result, endEvent) => { + finalizeAISDKChildTracing(endEvent as { [key: string]: unknown }); + return processAISDKCallOutput(result, endEvent, denyOutputPaths); + }, extractMetrics: (result, _startTime, endEvent) => extractTopLevelAISDKMetrics(result, endEvent), + aggregateChunks: aggregateAISDKChunks, }, ), - ), - ); - - // Agent.generate - async method - this.unsubscribers.push( - aiSDKChannels.agentGenerate.intercept( - (target, receiver, args, additional) => - traceStreamingCall( - () => Reflect.apply(target, receiver, args), - { ...additional, arguments: args, self: receiver }, - { - instrumentationName: INSTRUMENTATION_NAMES.AI_SDK, - name: "Agent.generate", - type: SpanTypeAttribute.FUNCTION, - extractInput: ([params], event, span) => - prepareAISDKCallInput(params, event, span, denyOutputPaths, { - agentOwner: true, - }), - extractOutput: (result, endEvent) => { - finalizeAISDKChildTracing( - endEvent as { [key: string]: unknown }, - ); - return processAISDKCallOutput( - result, - endEvent, - denyOutputPaths, - ); - }, - extractMetrics: (result, _startTime, endEvent) => - extractTopLevelAISDKMetrics(result, endEvent), - aggregateChunks: aggregateAISDKChunks, - }, - ), - ), ); // Agent.stream - async method returning stream (v5, used by wrapAISDK) - this.unsubscribers.push( - aiSDKChannels.agentStream.intercept( - (target, receiver, args, additional) => - traceStreamingCall( - () => Reflect.apply(target, receiver, args), - { ...additional, arguments: args, self: receiver }, - { - instrumentationName: INSTRUMENTATION_NAMES.AI_SDK, - name: "Agent.stream", - type: SpanTypeAttribute.FUNCTION, - extractInput: ([params], event, span) => - prepareAISDKCallInput(params, event, span, denyOutputPaths, { - agentOwner: true, - }), - extractOutput: (result, endEvent) => - processAISDKCallOutput(result, endEvent, denyOutputPaths), - extractMetrics: (result, startTime, endEvent) => - extractTopLevelAISDKMetrics(result, endEvent, startTime), - aggregateChunks: aggregateAISDKChunks, - patchResult: ({ endEvent, result, span, startTime }) => - patchAISDKStreamingResult({ - defaultDenyOutputPaths: denyOutputPaths, - endEvent, - result, - span, - startTime, - }), - }, - ), + aiSDKChannels.agentStream.intercept((target, receiver, args, additional) => + traceStreamingCall( + () => Reflect.apply(target, receiver, args), + { ...additional, arguments: args, self: receiver }, + { + instrumentationName: INSTRUMENTATION_NAMES.AI_SDK, + name: "Agent.stream", + type: SpanTypeAttribute.FUNCTION, + extractInput: ([params], event, span) => + prepareAISDKCallInput(params, event, span, denyOutputPaths, { + agentOwner: true, + }), + extractOutput: (result, endEvent) => + processAISDKCallOutput(result, endEvent, denyOutputPaths), + extractMetrics: (result, startTime, endEvent) => + extractTopLevelAISDKMetrics(result, endEvent, startTime), + aggregateChunks: aggregateAISDKChunks, + patchResult: ({ endEvent, result, span, startTime }) => + patchAISDKStreamingResult({ + defaultDenyOutputPaths: denyOutputPaths, + endEvent, + result, + span, + startTime, + }), + }, ), ); // Agent.stream - sync method returning stream (v5, used by auto-hook) - this.unsubscribers.push( - aiSDKChannels.agentStreamSync.intercept( - (target, receiver, args, additional) => - traceSyncStreamCall( - () => Reflect.apply(target, receiver, args), - { ...additional, arguments: args, self: receiver }, - { - instrumentationName: INSTRUMENTATION_NAMES.AI_SDK, - name: "Agent.stream", - type: SpanTypeAttribute.FUNCTION, - extractInput: ([params], event, span) => - prepareAISDKCallInput(params, event, span, denyOutputPaths, { - agentOwner: true, - }), - patchResult: ({ endEvent, result, span, startTime }) => - patchAISDKStreamingResult({ - defaultDenyOutputPaths: denyOutputPaths, - endEvent, - result, - span, - startTime, - }), - }, - ), - ), + aiSDKChannels.agentStreamSync.intercept( + (target, receiver, args, additional) => + traceSyncStreamCall( + () => Reflect.apply(target, receiver, args), + { ...additional, arguments: args, self: receiver }, + { + instrumentationName: INSTRUMENTATION_NAMES.AI_SDK, + name: "Agent.stream", + type: SpanTypeAttribute.FUNCTION, + extractInput: ([params], event, span) => + prepareAISDKCallInput(params, event, span, denyOutputPaths, { + agentOwner: true, + }), + patchResult: ({ endEvent, result, span, startTime }) => + patchAISDKStreamingResult({ + defaultDenyOutputPaths: denyOutputPaths, + endEvent, + result, + span, + startTime, + }), + }, + ), ); // HarnessAgent.generate - one task span per agent turn - this.unsubscribers.push( - harnessAgentChannels.generate.intercept( - (target, receiver, args, additional) => - traceStreamingCall( - () => Reflect.apply(target, receiver, args), - { ...additional, arguments: args, self: receiver }, - { - instrumentationName: INSTRUMENTATION_NAMES.AI_SDK, - name: "HarnessAgent.generate", - startSpan: _internalStartSpanWithInitialMerge, - type: SpanTypeAttribute.TASK, - extractInput: ([params], event, span) => - prepareAISDKHarnessAgentInput(params, event.self, span), - extractOutput: (result, endEvent) => - processAISDKCallOutput(result, endEvent, denyOutputPaths), - extractMetrics: (result) => extractTokenMetrics(result), - aggregateChunks: aggregateAISDKChunks, - }, - ), - ), + harnessAgentChannels.generate.intercept( + (target, receiver, args, additional) => + traceStreamingCall( + () => Reflect.apply(target, receiver, args), + { ...additional, arguments: args, self: receiver }, + { + instrumentationName: INSTRUMENTATION_NAMES.AI_SDK, + name: "HarnessAgent.generate", + startSpan: _internalStartSpanWithInitialMerge, + type: SpanTypeAttribute.TASK, + extractInput: ([params], event, span) => + prepareAISDKHarnessAgentInput(params, event.self, span), + extractOutput: (result, endEvent) => + processAISDKCallOutput(result, endEvent, denyOutputPaths), + extractMetrics: (result) => extractTokenMetrics(result), + aggregateChunks: aggregateAISDKChunks, + }, + ), ); // HarnessAgent.stream - async method returning an AI SDK stream result - this.unsubscribers.push( - harnessAgentChannels.stream.intercept( - (target, receiver, args, additional) => - traceStreamingCall( - () => Reflect.apply(target, receiver, args), - { ...additional, arguments: args, self: receiver }, - { - instrumentationName: INSTRUMENTATION_NAMES.AI_SDK, - name: "HarnessAgent.stream", - startSpan: _internalStartSpanWithInitialMerge, - type: SpanTypeAttribute.TASK, - extractInput: ([params], event, span) => - prepareAISDKHarnessAgentInput(params, event.self, span), - extractOutput: (result, endEvent) => - processAISDKCallOutput(result, endEvent, denyOutputPaths), - extractMetrics: (result, startTime) => ({ - ...extractTokenMetrics(result), - ...(startTime === undefined - ? {} - : { - time_to_first_token: - getCurrentUnixTimestamp() - startTime, - }), + harnessAgentChannels.stream.intercept( + (target, receiver, args, additional) => + traceStreamingCall( + () => Reflect.apply(target, receiver, args), + { ...additional, arguments: args, self: receiver }, + { + instrumentationName: INSTRUMENTATION_NAMES.AI_SDK, + name: "HarnessAgent.stream", + startSpan: _internalStartSpanWithInitialMerge, + type: SpanTypeAttribute.TASK, + extractInput: ([params], event, span) => + prepareAISDKHarnessAgentInput(params, event.self, span), + extractOutput: (result, endEvent) => + processAISDKCallOutput(result, endEvent, denyOutputPaths), + extractMetrics: (result, startTime) => ({ + ...extractTokenMetrics(result), + ...(startTime === undefined + ? {} + : { + time_to_first_token: getCurrentUnixTimestamp() - startTime, + }), + }), + aggregateChunks: aggregateAISDKChunks, + patchResult: ({ endEvent, result, span, startTime }) => + patchAISDKStreamingResult({ + defaultDenyOutputPaths: denyOutputPaths, + endEvent, + result, + resolvePromiseUsage: true, + span, + startTime, }), - aggregateChunks: aggregateAISDKChunks, - patchResult: ({ endEvent, result, span, startTime }) => - patchAISDKStreamingResult({ - defaultDenyOutputPaths: denyOutputPaths, - endEvent, - result, - resolvePromiseUsage: true, - span, - startTime, - }), - }, - ), - ), + }, + ), ); // Trace a continuation as its own task only when its original turn cannot // be recovered. Known continuations extend the original Harness task. - this.unsubscribers.push( - harnessAgentChannels.continueGenerate.intercept( - (target, receiver, args, additional) => - traceStreamingCall( - () => Reflect.apply(target, receiver, args), - { ...additional, arguments: args, self: receiver }, - { - instrumentationName: INSTRUMENTATION_NAMES.AI_SDK, - name: "HarnessAgent.continueGenerate", - shouldTrace: (args) => - !harnessContinuationParent(harnessSessionFromArguments(args)), - startSpan: _internalStartSpanWithInitialMerge, - type: SpanTypeAttribute.TASK, - extractInput: ([params], event, span) => - prepareAISDKHarnessAgentInput(params, event.self, span), - extractOutput: (result, endEvent) => - processAISDKCallOutput(result, endEvent, denyOutputPaths), - extractMetrics: (result) => extractTokenMetrics(result), - aggregateChunks: aggregateAISDKChunks, - }, - ), - ), + harnessAgentChannels.continueGenerate.intercept( + (target, receiver, args, additional) => + traceStreamingCall( + () => Reflect.apply(target, receiver, args), + { ...additional, arguments: args, self: receiver }, + { + instrumentationName: INSTRUMENTATION_NAMES.AI_SDK, + name: "HarnessAgent.continueGenerate", + shouldTrace: (args) => + !harnessContinuationParent(harnessSessionFromArguments(args)), + startSpan: _internalStartSpanWithInitialMerge, + type: SpanTypeAttribute.TASK, + extractInput: ([params], event, span) => + prepareAISDKHarnessAgentInput(params, event.self, span), + extractOutput: (result, endEvent) => + processAISDKCallOutput(result, endEvent, denyOutputPaths), + extractMetrics: (result) => extractTokenMetrics(result), + aggregateChunks: aggregateAISDKChunks, + }, + ), ); - this.unsubscribers.push( - harnessAgentChannels.continueStream.intercept( - (target, receiver, args, additional) => - traceStreamingCall( - () => Reflect.apply(target, receiver, args), - { ...additional, arguments: args, self: receiver }, - { - instrumentationName: INSTRUMENTATION_NAMES.AI_SDK, - name: "HarnessAgent.continueStream", - shouldTrace: (args) => - !harnessContinuationParent(harnessSessionFromArguments(args)), - startSpan: _internalStartSpanWithInitialMerge, - type: SpanTypeAttribute.TASK, - extractInput: ([params], event, span) => - prepareAISDKHarnessAgentInput(params, event.self, span), - extractOutput: (result, endEvent) => - processAISDKCallOutput(result, endEvent, denyOutputPaths), - extractMetrics: (result, startTime) => ({ - ...extractTokenMetrics(result), - ...(startTime === undefined - ? {} - : { - time_to_first_token: - getCurrentUnixTimestamp() - startTime, - }), + harnessAgentChannels.continueStream.intercept( + (target, receiver, args, additional) => + traceStreamingCall( + () => Reflect.apply(target, receiver, args), + { ...additional, arguments: args, self: receiver }, + { + instrumentationName: INSTRUMENTATION_NAMES.AI_SDK, + name: "HarnessAgent.continueStream", + shouldTrace: (args) => + !harnessContinuationParent(harnessSessionFromArguments(args)), + startSpan: _internalStartSpanWithInitialMerge, + type: SpanTypeAttribute.TASK, + extractInput: ([params], event, span) => + prepareAISDKHarnessAgentInput(params, event.self, span), + extractOutput: (result, endEvent) => + processAISDKCallOutput(result, endEvent, denyOutputPaths), + extractMetrics: (result, startTime) => ({ + ...extractTokenMetrics(result), + ...(startTime === undefined + ? {} + : { + time_to_first_token: getCurrentUnixTimestamp() - startTime, + }), + }), + aggregateChunks: aggregateAISDKChunks, + patchResult: ({ endEvent, result, span, startTime }) => + patchAISDKStreamingResult({ + defaultDenyOutputPaths: denyOutputPaths, + endEvent, + result, + resolvePromiseUsage: true, + span, + startTime, }), - aggregateChunks: aggregateAISDKChunks, - patchResult: ({ endEvent, result, span, startTime }) => - patchAISDKStreamingResult({ - defaultDenyOutputPaths: denyOutputPaths, - endEvent, - result, - resolvePromiseUsage: true, - span, - startTime, - }), - }, - ), - ), + }, + ), ); // ToolLoopAgent.generate - async method - this.unsubscribers.push( - aiSDKChannels.toolLoopAgentGenerate.intercept( - (target, receiver, args, additional) => - traceStreamingCall( - () => Reflect.apply(target, receiver, args), - { ...additional, arguments: args, self: receiver }, - { - instrumentationName: INSTRUMENTATION_NAMES.AI_SDK, - name: "ToolLoopAgent.generate", - type: SpanTypeAttribute.FUNCTION, - extractInput: ([params], event, span) => - prepareAISDKCallInput(params, event, span, denyOutputPaths, { - agentOwner: true, - }), - extractOutput: (result, endEvent) => { - finalizeAISDKChildTracing( - endEvent as { [key: string]: unknown }, - ); - return processAISDKCallOutput( - result, - endEvent, - denyOutputPaths, - ); - }, - extractMetrics: (result, _startTime, endEvent) => - extractTopLevelAISDKMetrics(result, endEvent), - aggregateChunks: aggregateAISDKChunks, + aiSDKChannels.toolLoopAgentGenerate.intercept( + (target, receiver, args, additional) => + traceStreamingCall( + () => Reflect.apply(target, receiver, args), + { ...additional, arguments: args, self: receiver }, + { + instrumentationName: INSTRUMENTATION_NAMES.AI_SDK, + name: "ToolLoopAgent.generate", + type: SpanTypeAttribute.FUNCTION, + extractInput: ([params], event, span) => + prepareAISDKCallInput(params, event, span, denyOutputPaths, { + agentOwner: true, + }), + extractOutput: (result, endEvent) => { + finalizeAISDKChildTracing(endEvent as { [key: string]: unknown }); + return processAISDKCallOutput(result, endEvent, denyOutputPaths); }, - ), - ), + extractMetrics: (result, _startTime, endEvent) => + extractTopLevelAISDKMetrics(result, endEvent), + aggregateChunks: aggregateAISDKChunks, + }, + ), ); // ToolLoopAgent.stream - async method returning stream - this.unsubscribers.push( - aiSDKChannels.toolLoopAgentStream.intercept( - (target, receiver, args, additional) => - traceStreamingCall( - () => Reflect.apply(target, receiver, args), - { ...additional, arguments: args, self: receiver }, - { - instrumentationName: INSTRUMENTATION_NAMES.AI_SDK, - name: "ToolLoopAgent.stream", - type: SpanTypeAttribute.FUNCTION, - extractInput: ([params], event, span) => - prepareAISDKCallInput(params, event, span, denyOutputPaths, { - agentOwner: true, - }), - extractOutput: (result, endEvent) => - processAISDKCallOutput(result, endEvent, denyOutputPaths), - extractMetrics: (result, startTime, endEvent) => - extractTopLevelAISDKMetrics(result, endEvent, startTime), - aggregateChunks: aggregateAISDKChunks, - patchResult: ({ endEvent, result, span, startTime }) => - patchAISDKStreamingResult({ - defaultDenyOutputPaths: denyOutputPaths, - endEvent, - result, - span, - startTime, - }), - }, - ), - ), + aiSDKChannels.toolLoopAgentStream.intercept( + (target, receiver, args, additional) => + traceStreamingCall( + () => Reflect.apply(target, receiver, args), + { ...additional, arguments: args, self: receiver }, + { + instrumentationName: INSTRUMENTATION_NAMES.AI_SDK, + name: "ToolLoopAgent.stream", + type: SpanTypeAttribute.FUNCTION, + extractInput: ([params], event, span) => + prepareAISDKCallInput(params, event, span, denyOutputPaths, { + agentOwner: true, + }), + extractOutput: (result, endEvent) => + processAISDKCallOutput(result, endEvent, denyOutputPaths), + extractMetrics: (result, startTime, endEvent) => + extractTopLevelAISDKMetrics(result, endEvent, startTime), + aggregateChunks: aggregateAISDKChunks, + patchResult: ({ endEvent, result, span, startTime }) => + patchAISDKStreamingResult({ + defaultDenyOutputPaths: denyOutputPaths, + endEvent, + result, + span, + startTime, + }), + }, + ), ); // WorkflowAgent.stream - async method returning stream - this.unsubscribers.push( - aiSDKChannels.workflowAgentStream.intercept( - (target, receiver, args, additional) => - traceStreamingCall( - () => Reflect.apply(target, receiver, args), - { ...additional, arguments: args, self: receiver }, - { - instrumentationName: INSTRUMENTATION_NAMES.AI_SDK, - name: "WorkflowAgent.stream", - type: SpanTypeAttribute.FUNCTION, - extractInput: ([params], event, span) => - prepareAISDKWorkflowAgentStreamInput( - params, - event, - span, - denyOutputPaths, - ), - extractOutput: (result, endEvent) => { - finalizeAISDKChildTracing( - endEvent as { [key: string]: unknown }, - ); - return processAISDKCallOutput( - result, - endEvent, - denyOutputPaths, - ); - }, - extractMetrics: (result, _startTime, endEvent) => - extractTopLevelAISDKMetrics(result, endEvent), - aggregateChunks: aggregateAISDKChunks, - onComplete: ({ span }) => { - unregisterWorkflowAgentWrapperSpan(span); - }, - onError: ({ event, span }) => { - finalizeAISDKChildTracing(event as { [key: string]: unknown }); - unregisterWorkflowAgentWrapperSpan(span); - }, - patchResult: ({ endEvent, result, span, startTime }) => - patchAISDKStreamingResult({ - defaultDenyOutputPaths: denyOutputPaths, - endEvent, - onComplete: () => unregisterWorkflowAgentWrapperSpan(span), - onCancel: () => unregisterWorkflowAgentWrapperSpan(span), - onError: () => unregisterWorkflowAgentWrapperSpan(span), - result, - span, - startTime, - }), + aiSDKChannels.workflowAgentStream.intercept( + (target, receiver, args, additional) => + traceStreamingCall( + () => Reflect.apply(target, receiver, args), + { ...additional, arguments: args, self: receiver }, + { + instrumentationName: INSTRUMENTATION_NAMES.AI_SDK, + name: "WorkflowAgent.stream", + type: SpanTypeAttribute.FUNCTION, + extractInput: ([params], event, span) => + prepareAISDKWorkflowAgentStreamInput( + params, + event, + span, + denyOutputPaths, + ), + extractOutput: (result, endEvent) => { + finalizeAISDKChildTracing(endEvent as { [key: string]: unknown }); + return processAISDKCallOutput(result, endEvent, denyOutputPaths); }, - ), - ), + extractMetrics: (result, _startTime, endEvent) => + extractTopLevelAISDKMetrics(result, endEvent), + aggregateChunks: aggregateAISDKChunks, + onComplete: ({ span }) => { + unregisterWorkflowAgentWrapperSpan(span); + }, + onError: ({ event, span }) => { + finalizeAISDKChildTracing(event as { [key: string]: unknown }); + unregisterWorkflowAgentWrapperSpan(span); + }, + patchResult: ({ endEvent, result, span, startTime }) => + patchAISDKStreamingResult({ + defaultDenyOutputPaths: denyOutputPaths, + endEvent, + onComplete: () => unregisterWorkflowAgentWrapperSpan(span), + onCancel: () => unregisterWorkflowAgentWrapperSpan(span), + onError: () => unregisterWorkflowAgentWrapperSpan(span), + result, + span, + startTime, + }), + }, + ), ); } } @@ -1054,55 +970,52 @@ function subscribeToHarnessAgentCreateSession(): () => void { const channel = harnessAgentChannels.createSession; const parents = new WeakMap(); - const removeHandlers = channel.intercept( - (target, receiver, args, additional) => { - const event = { - ...additional, - arguments: args, - self: receiver, - } as ChannelMessage; - const prepare = ( - event: ChannelMessage, - ) => { - const parent = captureHarnessCreateSessionParent(event.arguments?.[0]); - if (parent) { - parents.set(event, parent); - } - }; - const resolved = ( - event: ChannelMessage, - ) => { - registerHarnessSessionParent(event.result, parents.get(event)); - parents.delete(event); - }; - const failed = ( - event: ChannelMessage, - ) => { - parents.delete(event); - }; - runInstrumentation(() => prepare(event)); - let result; - try { - result = Reflect.apply(target, receiver, args); - } catch (error) { - Object.assign(event, { error }); - runInstrumentation(() => failed(event)); - throw error; + return channel.intercept((target, receiver, args, additional) => { + const event = { + ...additional, + arguments: args, + self: receiver, + } as ChannelMessage; + const prepare = ( + event: ChannelMessage, + ) => { + const parent = captureHarnessCreateSessionParent(event.arguments?.[0]); + if (parent) { + parents.set(event, parent); } - return observeResult( - result, - (value) => { - Object.assign(event, { result: value }); - resolved(event); - }, - (error) => { - Object.assign(event, { error }); - failed(event); - }, - ); - }, - ); - return () => removeHandlers(); + }; + const resolved = ( + event: ChannelMessage, + ) => { + registerHarnessSessionParent(event.result, parents.get(event)); + parents.delete(event); + }; + const failed = ( + event: ChannelMessage, + ) => { + parents.delete(event); + }; + runInstrumentation(() => prepare(event)); + let result; + try { + result = Reflect.apply(target, receiver, args); + } catch (error) { + Object.assign(event, { error }); + runInstrumentation(() => failed(event)); + throw error; + } + return observeResult( + result, + (value) => { + Object.assign(event, { result: value }); + resolved(event); + }, + (error) => { + Object.assign(event, { error }); + failed(event); + }, + ); + }); } type HarnessContinuationChannel = typeof harnessAgentChannels.continueGenerate; @@ -1160,6 +1073,9 @@ function traceHarnessContinuation( result: AISDKResult | AsyncIterable; }; const span = { + [CAPTURE_ATTACHMENTS]: _internalCaptureAttachmentsEnabled( + typeof parent === "string" ? undefined : parent, + ), end: () => endHarnessTurn(parent), log: (update: Parameters[0]) => updateHarnessTurn( @@ -1273,9 +1189,9 @@ function traceHarnessContinuation( ); } -function interceptAISDKV7TelemetryDispatcher(): () => void { +function interceptAISDKV7TelemetryDispatcher(): void { const telemetry = braintrustAISDKTelemetry(); - return aiSDKChannels.v7CreateTelemetryDispatcher.intercept( + aiSDKChannels.v7CreateTelemetryDispatcher.intercept( (target, thisArg, args) => { const dispatcher = Reflect.apply(target, thisArg, args); const telemetryOptions = args[0]?.telemetry; @@ -1880,7 +1796,9 @@ const processMessage = (message: any): any => { if (Array.isArray(message.content)) { return { ...message, - content: message.content.map(processContentPart), + content: message.content + .map(processContentPart) + .filter((part: unknown) => part !== undefined), }; } @@ -1896,14 +1814,18 @@ const processMessage = (message: any): any => { const processPromptContent = (prompt: any): any => { if (Array.isArray(prompt)) { - return prompt.map(processContentPart); + return prompt + .map(processContentPart) + .filter((part: unknown) => part !== undefined); } if (prompt.content) { if (Array.isArray(prompt.content)) { return { ...prompt, - content: prompt.content.map(processContentPart), + content: prompt.content + .map(processContentPart) + .filter((part: unknown) => part !== undefined), }; } else if (typeof prompt.content === "object") { return { @@ -1920,7 +1842,12 @@ const processContentPart = (part: any): any => { if (!part || typeof part !== "object") return part; try { + if (part.type === "tool-result") return processInputAttachments(part); + const captureAttachments = _internalCaptureAttachmentsEnabled(); + if (part.type === "image" && part.image) { + if (!captureAttachments) + return isInlineMedia(part.image) ? omitMediaData(part, "image") : part; const imageAttachment = convertImageToAttachment( part.image, part.mimeType || part.mediaType, @@ -1933,11 +1860,9 @@ const processContentPart = (part: any): any => { } } - if ( - part.type === "file" && - part.data && - (part.mimeType || part.mediaType) - ) { + if (part.type === "file" && part.data) { + if (!captureAttachments) + return isInlineMedia(part.data) ? omitMediaData(part, "data") : part; const fileAttachment = convertDataToAttachment( part.data, part.mimeType || part.mediaType, @@ -1953,6 +1878,13 @@ const processContentPart = (part: any): any => { if (part.type === "image_url" && part.image_url) { if (typeof part.image_url === "object" && part.image_url.url) { + if (!captureAttachments) + return isInlineMedia(part.image_url.url) + ? omitMediaData({ + ...part, + image_url: omitMediaData(part.image_url, "url"), + }) + : part; const imageAttachment = convertImageToAttachment(part.image_url.url); if (imageAttachment) { return { @@ -1972,11 +1904,21 @@ const processContentPart = (part: any): any => { return part; }; +// Explicit attachments and remote URLs are references, not inline media. +const isInlineMedia = (value: unknown): boolean => + !(value instanceof BaseAttachment) && + !(value instanceof URL && value.protocol !== "data:") && + !(typeof value === "string" && /^https?:/i.test(value)); + const convertImageToAttachment = ( image: any, explicitMimeType?: string, ): Attachment | null => { try { + if (image instanceof URL) { + if (image.protocol !== "data:") return null; + image = image.href; + } if (typeof image === "string" && image.startsWith("data:")) { const [mimeTypeSection, base64Data] = image.split(","); const mimeType = mimeTypeSection.match(/data:(.*?);/)?.[1]; @@ -2029,6 +1971,10 @@ const convertDataToAttachment = ( if (!mimeType) return null; try { + if (data instanceof URL) { + if (data.protocol !== "data:") return null; + data = data.href; + } let blob: Blob | null = null; if (typeof data === "string" && data.startsWith("data:")) { @@ -2039,9 +1985,9 @@ const convertDataToAttachment = ( } else if (typeof data === "string" && data.length > 0) { blob = convertDataToBlob(data, mimeType); } else if (data instanceof Uint8Array) { - blob = new Blob([data], { type: mimeType }); + blob = new Blob([Uint8Array.from(data)], { type: mimeType }); } else if (typeof Buffer !== "undefined" && Buffer.isBuffer(data)) { - blob = new Blob([data], { type: mimeType }); + blob = new Blob([Uint8Array.from(data)], { type: mimeType }); } else if (data instanceof Blob) { blob = data; } @@ -2078,14 +2024,19 @@ export function processAISDKGenerateImageInput( } const processedPrompt = { ...prompt }; + const captureAttachments = _internalCaptureAttachmentsEnabled(); if (Array.isArray(prompt.images)) { - processedPrompt.images = prompt.images.map( - (image) => convertImageToAttachment(image, "image/png") ?? image, - ); + processedPrompt.images = captureAttachments + ? prompt.images.map( + (image) => convertImageToAttachment(image, "image/png") ?? image, + ) + : prompt.images.filter((image) => !isInlineMedia(image)); } if (prompt.mask !== undefined) { - processedPrompt.mask = - convertImageToAttachment(prompt.mask, "image/png") ?? prompt.mask; + if (captureAttachments) + processedPrompt.mask = + convertImageToAttachment(prompt.mask, "image/png") ?? prompt.mask; + else if (isInlineMedia(prompt.mask)) delete processedPrompt.mask; } return processAISDKCallInput({ @@ -2529,7 +2480,11 @@ function sanitizeAISDKCallInputValue(value: unknown, depth = 0): unknown { return undefined; } - if (value === null || typeof value !== "object") { + if ( + value === null || + typeof value !== "object" || + value instanceof BaseAttachment + ) { return value; } @@ -2626,7 +2581,11 @@ function sanitizeAISDKMetadataValue(value: unknown, depth = 0): unknown { return "[Function]"; } - if (value === null || typeof value !== "object") { + if ( + value === null || + typeof value !== "object" || + value instanceof BaseAttachment + ) { return value; } @@ -3140,6 +3099,7 @@ function prepareAISDKChildTracing( output: processAISDKOutput( output as AISDKResult, activeEntry.denyOutputPaths, + _internalCaptureAttachmentsEnabled(span), ), metrics, ...mergeMetadataPayload(metadataPayload, missingUsageMetadata), @@ -3616,6 +3576,7 @@ export function patchAISDKStreamingResult(args: { } const resultRecord = result as Record; + const captureAttachments = _internalCaptureAttachmentsEnabled(span); attachKnownResultPromiseHandlers(resultRecord); let finalized = false; const finalize = ( @@ -3699,6 +3660,7 @@ export function patchAISDKStreamingResult(args: { await processAISDKStreamingOutput( result, resolveDenyOutputPaths(endEvent, defaultDenyOutputPaths), + captureAttachments, ), aiSDKModelOutputToolNamesFromEvent(endEvent), ); @@ -4056,8 +4018,13 @@ function createPatchedAsyncIterable( async function processAISDKStreamingOutput( result: AISDKResult, denyOutputPaths: string[], + captureAttachments: boolean, ): Promise | AISDKResult> { - const output = processAISDKOutput(result, denyOutputPaths); + const output = processAISDKOutput( + result, + denyOutputPaths, + captureAttachments, + ); if (!output || typeof output !== "object") { return output; @@ -4241,14 +4208,48 @@ function extractTextDelta(chunk: AISDKModelStreamChunk): string { export function processAISDKOutput( output: AISDKResult, denyOutputPaths: string[], + captureAttachments = _internalCaptureAttachmentsEnabled(), ): Record | AISDKResult { if (!output) return output; const merged = extractSerializableOutputFields(output); + // Only provider content containers contain media. Structured outputs, tool + // arguments, and metadata can use the same field names for ordinary JSON. + const processMediaContainers = ( + container: Record, + ): Record => + Object.fromEntries( + Object.entries(container).map(([key, value]) => { + if ( + ["content", "messages", "responseMessages", "toolResults"].includes( + key, + ) && + Array.isArray(value) + ) { + return [key, processInputAttachments(value, captureAttachments)]; + } + if (key === "steps" && Array.isArray(value)) { + return [ + key, + value.map((step) => + isObject(step) ? processMediaContainers(step) : step, + ), + ]; + } + if (key === "response" && isObject(value)) { + return [key, processMediaContainers(value)]; + } + return [key, value]; + }), + ); const deleteOutputPaths = denyOutputPaths.filter((path) => path.toLowerCase().endsWith("headers"), ); - const sanitized = omit(merged, denyOutputPaths, deleteOutputPaths); + const sanitized = omit( + processMediaContainers(merged), + denyOutputPaths, + deleteOutputPaths, + ); // Transport payloads can contain nested provider request/response headers; keep // user/model/tool payload fields named "headers" outside these roots intact. @@ -4341,9 +4342,10 @@ export function processAISDKGenerateImageOutput( omit(summarized, denyOutputPaths), ) as Record; if (generatedFiles.length > 0) { - loggedOutput.images = generatedFiles.map((file, index) => - convertAISDKGeneratedFileToAttachment(file, index), - ); + const images = generatedFiles + .map((file, index) => convertAISDKGeneratedFileToAttachment(file, index)) + .filter((image) => image !== undefined); + if (images.length) loggedOutput.images = images; } return loggedOutput; @@ -4357,6 +4359,8 @@ function convertAISDKGeneratedFileToAttachment( return file; } + if (!_internalCaptureAttachmentsEnabled()) return undefined; + const generatedFile = file as AISDKGeneratedFile & Record; const generatedMediaType = safeSerializableFieldRead( generatedFile, @@ -4724,13 +4728,6 @@ function extractGatewayRoutingInfo(result: AISDKResult): { return null; } -/** - * Deep copy an object via JSON serialization. - */ -function deepCopy(obj: Record): Record { - return JSON.parse(JSON.stringify(obj)); -} - /** * Parse a JSON path string into an array of keys. */ @@ -4830,7 +4827,7 @@ function omit( paths: string[], deletePaths: string[] = [], ): Record { - const result = deepCopy(obj); + const result = deepCopyEvent({ output: obj }).output; const deletePathSet = new Set(deletePaths); for (const path of paths) { @@ -4840,3 +4837,12 @@ function omit( return result; } + +let aiSDKInstrumentationConsumer: AISDKInstrumentationConsumer | undefined; + +export function registerAISDKInstrumentation( + config: AISDKInstrumentationConfig = {}, +): void { + aiSDKInstrumentationConsumer ??= new AISDKInstrumentationConsumer(config); + aiSDKInstrumentationConsumer.register(); +} diff --git a/js/src/instrumentation/plugins/ai-sdk-interceptors.ts b/js/src/instrumentation/providers/ai-sdk-interceptors.ts similarity index 100% rename from js/src/instrumentation/plugins/ai-sdk-interceptors.ts rename to js/src/instrumentation/providers/ai-sdk-interceptors.ts diff --git a/js/src/instrumentation/plugins/ai-sdk-metrics.ts b/js/src/instrumentation/providers/ai-sdk-metrics.ts similarity index 100% rename from js/src/instrumentation/plugins/ai-sdk-metrics.ts rename to js/src/instrumentation/providers/ai-sdk-metrics.ts diff --git a/js/src/instrumentation/plugins/ai-sdk-tool-tracing.ts b/js/src/instrumentation/providers/ai-sdk-tool-tracing.ts similarity index 97% rename from js/src/instrumentation/plugins/ai-sdk-tool-tracing.ts rename to js/src/instrumentation/providers/ai-sdk-tool-tracing.ts index 5ca38a4d2..8571cb17d 100644 --- a/js/src/instrumentation/plugins/ai-sdk-tool-tracing.ts +++ b/js/src/instrumentation/providers/ai-sdk-tool-tracing.ts @@ -1,4 +1,9 @@ -import { currentSpan, startSpan, withCurrent, type Span } from "../../logger"; +import { + currentSpan, + startSpan, + withCurrent, + type Span, +} from "../../logger/core"; import { INSTRUMENTATION_NAMES, withSpanInstrumentationName, diff --git a/js/src/instrumentation/plugins/ai-sdk-v7-telemetry.test.ts b/js/src/instrumentation/providers/ai-sdk-v7-telemetry.test.ts similarity index 95% rename from js/src/instrumentation/plugins/ai-sdk-v7-telemetry.test.ts rename to js/src/instrumentation/providers/ai-sdk-v7-telemetry.test.ts index 928191bad..2a7c0823f 100644 --- a/js/src/instrumentation/plugins/ai-sdk-v7-telemetry.test.ts +++ b/js/src/instrumentation/providers/ai-sdk-v7-telemetry.test.ts @@ -6,14 +6,15 @@ import { expect, it, vi, -} from "vitest"; +} from "vite-plus/test"; +import { _exportsForTestingOnly } from "../../logger/testing"; import { - _exportsForTestingOnly, + Attachment, currentSpan, initLogger, startSpan, withCurrent, -} from "../../logger"; +} from "../../logger/core"; import { configureNode } from "../../node/config"; import { braintrustAISDKTelemetry } from "../../wrappers/ai-sdk/telemetry"; import { @@ -22,7 +23,7 @@ import { } from "../../wrappers/ai-sdk/workflow-agent-context"; import { BRAINTRUST_AI_SDK_V7_OPERATION_KEY as AI_SDK_V7_OPERATION_KEY } from "../../vendor-sdk-types/ai-sdk-v7-telemetry"; import { runWithAutoInstrumentationSuppressed } from "../auto-instrumentation-suppression"; -import { processAISDKDecisionInput } from "./ai-sdk-plugin"; +import { processAISDKDecisionInput } from "./ai-sdk-instrumentation"; try { configureNode(); @@ -169,7 +170,7 @@ describe("braintrustAISDKTelemetry", () => { }); it("omits decision file bytes by default and preserves remote references", () => { - vi.stubEnv("BRAINTRUST_CAPTURE_ATTACHMENTS", "false"); + vi.stubEnv("BRAINTRUST_AUTO_CAPTURE_ATTACHMENTS", "false"); const input = processAISDKDecisionInput({ questions: {}, state: [ @@ -194,7 +195,7 @@ describe("braintrustAISDKTelemetry", () => { }); it("snapshots decision bytes and defers attachments until export", () => { - vi.stubEnv("BRAINTRUST_CAPTURE_ATTACHMENTS", "true"); + vi.stubEnv("BRAINTRUST_AUTO_CAPTURE_ATTACHMENTS", "true"); const state = [ { type: "file", @@ -208,6 +209,61 @@ describe("braintrustAISDKTelemetry", () => { expect(state[0].data).toEqual(new Uint8Array([1, 2])); }); + it.each([false, true])( + "keeps the owning media policy in delayed telemetry callbacks (%s)", + async (captureAttachments) => { + initLogger({ + projectName: "tmp-luca-telemetry-capture", + projectId: "test-project-id", + autoCaptureAttachments: captureAttachments, + }); + const telemetry = braintrustAISDKTelemetry(); + const callId = "delayed-media"; + telemetry.onStart({ callId, operationId: "ai.generateText" }); + initLogger({ + projectName: "tmp-luca-telemetry-capture", + projectId: "test-project-id", + autoCaptureAttachments: !captureAttachments, + }); + const messages = [ + { + role: "user", + content: [{ type: "image", image: "data:image/png;base64,AQID" }], + }, + ]; + telemetry.onLanguageModelCallStart({ callId, messages }); + const content = [{ type: "file", mediaType: "image/png", data: "AQID" }]; + telemetry.onLanguageModelCallEnd({ callId, content }); + telemetry.onEnd({ callId, operationId: "ai.generateText", content }); + const rows = await backgroundLogger.drain(); + const outputs = rows.filter( + (row) => "output" in row && row.output !== undefined, + ); + expect(outputs).toHaveLength(2); + for (const row of outputs) + expect(row).toHaveProperty("output.content", [ + { + type: "file", + mediaType: "image/png", + ...(captureAttachments ? { data: expect.any(Attachment) } : {}), + }, + ]); + const inputRow = rows.find( + (row) => + "input" in row && + row.input && + typeof row.input === "object" && + "messages" in row.input, + ); + expect(inputRow).toHaveProperty( + "input.messages.0.content", + captureAttachments + ? [{ type: "image", image: expect.any(Attachment) }] + : [], + ); + }, + ); + it.each(["generateText", "streamText"])( "captures provider tools in %s alongside local tools", async (operation) => { diff --git a/js/src/instrumentation/plugins/anthropic-plugin.test.ts b/js/src/instrumentation/providers/anthropic-instrumentation.test.ts similarity index 96% rename from js/src/instrumentation/plugins/anthropic-plugin.test.ts rename to js/src/instrumentation/providers/anthropic-instrumentation.test.ts index 45ba05661..6d9512796 100644 --- a/js/src/instrumentation/plugins/anthropic-plugin.test.ts +++ b/js/src/instrumentation/providers/anthropic-instrumentation.test.ts @@ -1,7 +1,12 @@ -import { describe, it, expect, vi } from "vitest"; - -vi.mock("../../isomorph", () => ({ - default: {}, +import { describe, it, expect, vi } from "vite-plus/test"; + +// Mock iso's newTracingChannel - must be before any imports that use it +vi.mock("#braintrust-platform", () => ({ + default: { + getEnv: vi.fn((name: string) => + name === "BRAINTRUST_AUTO_CAPTURE_ATTACHMENTS" ? "true" : undefined, + ), + }, })); import { @@ -9,8 +14,8 @@ import { aggregateAnthropicStreamChunks, processAttachmentsInInput, coalesceInput, -} from "./anthropic-plugin"; -import { Attachment } from "../../logger"; +} from "./anthropic-instrumentation"; +import { Attachment } from "../../logger/core"; const parseMetricsFromUsageForTest = (usage: unknown) => parseMetricsFromUsage(usage as any); @@ -18,22 +23,28 @@ const aggregateAnthropicStreamChunksForTest = (chunks: unknown[]) => aggregateAnthropicStreamChunks(chunks as any); // Mock startSpan from logger -vi.mock("../../logger", () => ({ - startSpan: vi.fn(() => ({ - log: vi.fn(), - end: vi.fn(), - })), - _internalGetGlobalState: vi.fn(() => undefined), - Attachment: class Attachment { - reference: any; - constructor(opts: any) { - this.reference = { - filename: opts.filename, - content_type: opts.contentType, - }; - } - }, -})); +vi.mock("../../logger/core", async () => { + const { default: iso } = await import("#braintrust-platform"); + return { + _internalCaptureAttachmentsEnabled: () => + iso.getEnv("BRAINTRUST_AUTO_CAPTURE_ATTACHMENTS") === "true", + withCurrent: (_span: unknown, callback: () => unknown) => callback(), + startSpan: vi.fn(() => ({ + log: vi.fn(), + end: vi.fn(), + })), + _internalGetGlobalState: vi.fn(() => undefined), + Attachment: class Attachment { + reference: any; + constructor(opts: any) { + this.reference = { + filename: opts.filename, + content_type: opts.contentType, + }; + } + }, + }; +}); describe("parseMetricsFromUsage", () => { it("should return empty object for null usage", () => { diff --git a/js/src/instrumentation/plugins/anthropic-plugin.ts b/js/src/instrumentation/providers/anthropic-instrumentation.ts similarity index 88% rename from js/src/instrumentation/plugins/anthropic-plugin.ts rename to js/src/instrumentation/providers/anthropic-instrumentation.ts index 32190f385..56e0e5931 100644 --- a/js/src/instrumentation/plugins/anthropic-plugin.ts +++ b/js/src/instrumentation/providers/anthropic-instrumentation.ts @@ -1,17 +1,20 @@ -import type { Span } from "../../logger"; +import type { Span } from "../../logger/core"; +import { omitMediaData } from "../../wrappers/attachment-utils"; +import { toLoggedError } from "../core"; +import { isAsyncIterable, patchStreamIfNeeded } from "../core/stream-patcher"; import { + _internalCaptureAttachmentsEnabled, Attachment, startSpan as startBaseSpan, withCurrent, -} from "../../logger"; +} from "../../logger/core"; import { INSTRUMENTATION_NAMES, withSpanInstrumentationName, } from "../../span-origin"; -import { BasePlugin, toLoggedError } from "../core"; -import { traceStreamingCall, unsubscribeAll } from "../core/tracing"; + +import { traceStreamingCall } from "../core/tracing"; import { observeResult, runInstrumentation } from "../core/observe-result"; -import { isAsyncIterable, patchStreamIfNeeded } from "../core/stream-patcher"; import { SpanTypeAttribute, @@ -94,30 +97,26 @@ const ANTHROPIC_TOOL_RUNNER_TOOL_WRAPPED = Symbol.for( ); /** - * Auto-instrumentation plugin for the Anthropic SDK. + * Internal auto-instrumentation consumer for the Anthropic SDK. * - * This plugin subscribes to orchestrion channels for Anthropic SDK methods + * This consumer subscribes to orchestrion channels for Anthropic SDK methods * and creates Braintrust spans to track: * - messages.create (streaming and non-streaming) * - beta.messages.create (streaming and non-streaming) * - * The plugin handles: + * The consumer handles: * - Anthropic-specific token metrics (including cache tokens) * - Processing message streams * - Converting base64 attachments to Attachment objects * - Streaming and non-streaming responses */ -export class AnthropicPlugin extends BasePlugin { - protected onEnable(): void { +class AnthropicInstrumentationConsumer { + public register(): void { this.subscribeToAnthropicChannels(); this.subscribeToAnthropicToolRunner(); this.subscribeToAnthropicSessionStreams(); } - protected onDisable(): void { - this.unsubscribers = unsubscribeAll(this.unsubscribers); - } - private subscribeToAnthropicChannels(): void { const anthropicConfig = { name: "anthropic.messages.create", @@ -164,34 +163,30 @@ export class AnthropicPlugin extends BasePlugin { }; // Messages API - supports streaming via stream=true parameter - this.unsubscribers.push( - anthropicChannels.messagesCreate.intercept( - (target, receiver, args, additional) => - traceStreamingCall( - () => Reflect.apply(target, receiver, args), - { ...additional, arguments: args, self: receiver }, - { - ...anthropicConfig, - instrumentationName: INSTRUMENTATION_NAMES.ANTHROPIC, - }, - ), - ), + anthropicChannels.messagesCreate.intercept( + (target, receiver, args, additional) => + traceStreamingCall( + () => Reflect.apply(target, receiver, args), + { ...additional, arguments: args, self: receiver }, + { + ...anthropicConfig, + instrumentationName: INSTRUMENTATION_NAMES.ANTHROPIC, + }, + ), ); // Beta Messages API - supports streaming via stream=true parameter - this.unsubscribers.push( - anthropicChannels.betaMessagesCreate.intercept( - (target, receiver, args, additional) => - traceStreamingCall( - () => Reflect.apply(target, receiver, args), - { ...additional, arguments: args, self: receiver }, - { - instrumentationName: INSTRUMENTATION_NAMES.ANTHROPIC, - ...anthropicConfig, - name: "anthropic.messages.create", - }, - ), - ), + anthropicChannels.betaMessagesCreate.intercept( + (target, receiver, args, additional) => + traceStreamingCall( + () => Reflect.apply(target, receiver, args), + { ...additional, arguments: args, self: receiver }, + { + instrumentationName: INSTRUMENTATION_NAMES.ANTHROPIC, + ...anthropicConfig, + name: "anthropic.messages.create", + }, + ), ); } @@ -199,100 +194,90 @@ export class AnthropicPlugin extends BasePlugin { const invocationHook = anthropicChannels.betaMessagesToolRunner; const states = new WeakMap(); - const removeHandlers = invocationHook.intercept( - (target, receiver, args, additional) => { - const event = { - ...additional, - arguments: args, - self: receiver, - } as ChannelMessage; - const prepare = ( - event: ChannelMessage< - typeof anthropicChannels.betaMessagesToolRunner - >, - ) => { - if (isAutoInstrumentationSuppressed()) { - return; - } + invocationHook.intercept((target, receiver, args, additional) => { + const event = { + ...additional, + arguments: args, + self: receiver, + } as ChannelMessage; + const prepare = ( + event: ChannelMessage, + ) => { + if (isAutoInstrumentationSuppressed()) { + return; + } - const params = (event.arguments[0] ?? - {}) as AnthropicToolRunnerParams; - const span = startBaseSpan( - withSpanInstrumentationName( - { - name: "anthropic.beta.messages.toolRunner", - spanAttributes: { - type: SpanTypeAttribute.TASK, - }, + const params = (event.arguments[0] ?? {}) as AnthropicToolRunnerParams; + const span = startBaseSpan( + withSpanInstrumentationName( + { + name: "anthropic.beta.messages.toolRunner", + spanAttributes: { + type: SpanTypeAttribute.TASK, }, - INSTRUMENTATION_NAMES.ANTHROPIC, - ), - ); - - span.log({ - input: processAttachmentsInInput( - coalesceInput(params.messages ?? [], params.system), - ), - metadata: { - ...extractAnthropicToolRunnerMetadata(params), - provider: "anthropic", }, - }); - - const state = { - aggregatedMetrics: {}, - finalized: false, - iterationCount: 0, - seenMessages: new WeakSet(), - span, - startTime: getCurrentUnixTimestamp(), - } satisfies AnthropicToolRunnerState; - - states.set(event as object, state); - }; - const returned = ( - event: ChannelMessage< - typeof anthropicChannels.betaMessagesToolRunner - >, - ) => { - const state = states.get(event as object); - if (!state) { - return; - } + INSTRUMENTATION_NAMES.ANTHROPIC, + ), + ); - patchAnthropicToolRunner({ - runner: event.result as unknown as AnthropicToolRunner, - state, - }); - }; - const failed = ( - event: ChannelMessage< - typeof anthropicChannels.betaMessagesToolRunner - >, - ) => { - const state = states.get(event as object); - if (!state || !event.error) { - return; - } + span.log({ + input: processAttachmentsInInput( + coalesceInput(params.messages ?? [], params.system), + ), + metadata: { + ...extractAnthropicToolRunnerMetadata(params), + provider: "anthropic", + }, + }); + + const state = { + aggregatedMetrics: {}, + finalized: false, + iterationCount: 0, + seenMessages: new WeakSet(), + span, + startTime: getCurrentUnixTimestamp(), + } satisfies AnthropicToolRunnerState; + + states.set(event as object, state); + }; + const returned = ( + event: ChannelMessage, + ) => { + const state = states.get(event as object); + if (!state) { + return; + } - finalizeAnthropicToolRunnerError(state, event.error); - states.delete(event as object); - }; - runInstrumentation(() => prepare(event)); - let result; - try { - result = Reflect.apply(target, receiver, args); - } catch (error) { - Object.assign(event, { error }); - runInstrumentation(() => failed(event)); - throw error; + patchAnthropicToolRunner({ + runner: event.result as unknown as AnthropicToolRunner, + state, + }); + }; + const failed = ( + event: ChannelMessage, + ) => { + const state = states.get(event as object); + if (!state || !event.error) { + return; } - Object.assign(event, { result }); - runInstrumentation(() => returned(event)); - return result; - }, - ); - this.unsubscribers.push(removeHandlers); + + finalizeAnthropicToolRunnerError(state, event.error); + states.delete(event as object); + }; + runInstrumentation(() => prepare(event)); + let result; + try { + result = Reflect.apply(target, receiver, args); + } catch (error) { + Object.assign(event, { error }); + runInstrumentation(() => failed(event)); + throw error; + } + Object.assign(event, { result }); + runInstrumentation(() => returned(event)); + return result; + }); } private subscribeToAnthropicSessionStreams(): void { @@ -318,59 +303,56 @@ export class AnthropicPlugin extends BasePlugin { const invocationHook = channel; const pending = new WeakSet(); - const removeHandlers = invocationHook.intercept( - (target, receiver, args, additional) => { - const event = { - ...additional, - arguments: args, - self: receiver, - } as SessionChannelMessage; - - const prepare = (event: SessionChannelMessage) => { - if (isAutoInstrumentationSuppressed()) { - return; - } - pending.add(event as object); - }; - const resolved = (event: SessionChannelMessage) => { - if (!pending.delete(event as object)) { - return; - } + invocationHook.intercept((target, receiver, args, additional) => { + const event = { + ...additional, + arguments: args, + self: receiver, + } as SessionChannelMessage; - const stream = event.result as AnthropicSessionEventStream; - if (!isAsyncIterable(stream)) { - return; - } - registerAnthropicSessionStreamCollector(stream, () => { - wrapAnthropicSessionEventStream(stream, isThread); - }); - }; - const failed = (event: SessionChannelMessage) => { - pending.delete(event as object); - }; - runInstrumentation(() => prepare(event)); - let result; - try { - result = Reflect.apply(target, receiver, args); - } catch (error) { - Object.assign(event, { error }); - runInstrumentation(() => failed(event)); - throw error; + const prepare = (event: SessionChannelMessage) => { + if (isAutoInstrumentationSuppressed()) { + return; } - return observeResult( - result, - (value) => { - Object.assign(event, { result: value }); - resolved(event); - }, - (error) => { - Object.assign(event, { error }); - failed(event); - }, - ); - }, - ); - this.unsubscribers.push(removeHandlers); + pending.add(event as object); + }; + const resolved = (event: SessionChannelMessage) => { + if (!pending.delete(event as object)) { + return; + } + + const stream = event.result as AnthropicSessionEventStream; + if (!isAsyncIterable(stream)) { + return; + } + registerAnthropicSessionStreamCollector(stream, () => { + wrapAnthropicSessionEventStream(stream, isThread); + }); + }; + const failed = (event: SessionChannelMessage) => { + pending.delete(event as object); + }; + runInstrumentation(() => prepare(event)); + let result; + try { + result = Reflect.apply(target, receiver, args); + } catch (error) { + Object.assign(event, { error }); + runInstrumentation(() => failed(event)); + throw error; + } + return observeResult( + result, + (value) => { + Object.assign(event, { result: value }); + resolved(event); + }, + (error) => { + Object.assign(event, { error }); + failed(event); + }, + ); + }); } } @@ -1716,15 +1698,25 @@ function convertBase64ToAttachment( /** * Process input to convert base64 attachments (images, PDFs, etc.) to Attachment objects. */ -export function processAttachmentsInInput(input: unknown): unknown { +export function processAttachmentsInInput( + input: unknown, + captureAttachments = _internalCaptureAttachmentsEnabled(), +): unknown { if (Array.isArray(input)) { - return input.map(processAttachmentsInInput); + return input + .map((value) => processAttachmentsInInput(value, captureAttachments)) + .filter((value) => value !== undefined); } if (isObject(input)) { // Check for Anthropic's content blocks with base64 data // Supports both "image" and "document" types (for PDFs, etc.) if (isAnthropicBase64ContentBlock(input)) { + if (!captureAttachments) + return omitMediaData({ + ...input, + source: omitMediaData(input.source, "data"), + }); return { ...input, source: convertBase64ToAttachment(input.source, input.type), @@ -1734,7 +1726,8 @@ export function processAttachmentsInInput(input: unknown): unknown { // Recursively process nested objects const processed: Record = {}; for (const [key, value] of Object.entries(input)) { - processed[key] = processAttachmentsInInput(value); + const result = processAttachmentsInInput(value, captureAttachments); + if (result !== undefined) processed[key] = result; } return processed; } @@ -1759,3 +1752,12 @@ export function coalesceInput( } return input; } + +let anthropicInstrumentationConsumer: + | AnthropicInstrumentationConsumer + | undefined; + +export function registerAnthropicInstrumentation(): void { + anthropicInstrumentationConsumer ??= new AnthropicInstrumentationConsumer(); + anthropicInstrumentationConsumer.register(); +} diff --git a/js/src/instrumentation/plugins/anthropic-interceptors.ts b/js/src/instrumentation/providers/anthropic-interceptors.ts similarity index 100% rename from js/src/instrumentation/plugins/anthropic-interceptors.ts rename to js/src/instrumentation/providers/anthropic-interceptors.ts diff --git a/js/src/instrumentation/plugins/anthropic-sessions-plugin.test.ts b/js/src/instrumentation/providers/anthropic-sessions-instrumentation.test.ts similarity index 95% rename from js/src/instrumentation/plugins/anthropic-sessions-plugin.test.ts rename to js/src/instrumentation/providers/anthropic-sessions-instrumentation.test.ts index 32ef8bdba..e407dc070 100644 --- a/js/src/instrumentation/plugins/anthropic-sessions-plugin.test.ts +++ b/js/src/instrumentation/providers/anthropic-sessions-instrumentation.test.ts @@ -1,4 +1,11 @@ -import { afterEach, beforeEach, describe, expect, it, vi } from "vitest"; +import { + afterEach, + beforeEach, + describe, + expect, + it, + vi, +} from "vite-plus/test"; import { newGlobalInvocationHook } from "../../global-instrumentation-hooks"; import { invocationController } from "../test-utils/invocation"; vi.mock("../../global-instrumentation-hooks", async (importOriginal) => ({ @@ -13,7 +20,7 @@ const { mockStartSpan, mockWithCurrent } = vi.hoisted(() => ({ mockWithCurrent: vi.fn(), })); -vi.mock("../../isomorph", () => ({ +vi.mock("#braintrust-platform", () => ({ default: { getEnv: vi.fn(), newAsyncLocalStorage: vi.fn(() => ({ @@ -23,8 +30,8 @@ vi.mock("../../isomorph", () => ({ }, })); -vi.mock("../../logger", async (importOriginal) => { - const actual = await importOriginal(); +vi.mock("../../logger/core", async (importOriginal) => { + const actual = await importOriginal(); return { ...actual, startSpan: (...args: unknown[]) => mockStartSpan(...args), @@ -33,13 +40,13 @@ vi.mock("../../logger", async (importOriginal) => { }); import { collectAnthropicSession } from "../../wrappers/anthropic-session-collector"; -import { AnthropicPlugin } from "./anthropic-plugin"; +import { registerAnthropicInstrumentation } from "./anthropic-instrumentation"; const mockNewInvocationHook = newGlobalInvocationHook as ReturnType< typeof vi.fn >; -describe("AnthropicPlugin Sessions instrumentation", () => { +describe("registerAnthropicInstrumentation Sessions instrumentation", () => { let currentSpan: TestSpan | undefined; let handlersByName: Map; let spans: TestSpan[]; @@ -87,7 +94,7 @@ describe("AnthropicPlugin Sessions instrumentation", () => { }); it("records a session turn with model and tool children", async () => { - new AnthropicPlugin().enable(); + registerAnthropicInstrumentation(); const handlers = handlersByName.get( "orchestrion:@anthropic-ai/sdk:beta.sessions.events.stream", ); @@ -271,7 +278,7 @@ describe("AnthropicPlugin Sessions instrumentation", () => { }); it("does not record an uncollected session stream", async () => { - new AnthropicPlugin().enable(); + registerAnthropicInstrumentation(); const handlers = handlersByName.get( "orchestrion:@anthropic-ai/sdk:beta.sessions.events.stream", ); @@ -295,7 +302,7 @@ describe("AnthropicPlugin Sessions instrumentation", () => { }); it("uses a distinct task name for thread streams and records denials", async () => { - new AnthropicPlugin().enable(); + registerAnthropicInstrumentation(); const handlers = handlersByName.get( "orchestrion:@anthropic-ai/sdk:beta.sessions.threads.events.stream", ); diff --git a/js/src/instrumentation/plugins/bedrock-runtime-common.ts b/js/src/instrumentation/providers/bedrock-runtime-common.ts similarity index 100% rename from js/src/instrumentation/plugins/bedrock-runtime-common.ts rename to js/src/instrumentation/providers/bedrock-runtime-common.ts diff --git a/js/src/instrumentation/plugins/bedrock-runtime-plugin.test.ts b/js/src/instrumentation/providers/bedrock-runtime-instrumentation.test.ts similarity index 98% rename from js/src/instrumentation/plugins/bedrock-runtime-plugin.test.ts rename to js/src/instrumentation/providers/bedrock-runtime-instrumentation.test.ts index 1d09680ce..faf9480da 100644 --- a/js/src/instrumentation/plugins/bedrock-runtime-plugin.test.ts +++ b/js/src/instrumentation/providers/bedrock-runtime-instrumentation.test.ts @@ -1,5 +1,14 @@ -import { afterEach, beforeAll, beforeEach, describe, expect, it } from "vitest"; -import { _exportsForTestingOnly, initLogger } from "../../logger"; +import { registerInstrumentation } from "../registrations/awsBedrockRuntime"; +import { + afterEach, + beforeAll, + beforeEach, + describe, + expect, + it, +} from "vite-plus/test"; +import { _exportsForTestingOnly } from "../../logger/testing"; +import { initLogger } from "../../logger/core"; import { configureNode } from "../../node/config"; import { smithyClientChannels, @@ -9,10 +18,11 @@ import { aggregateBedrockConverseStreamChunks, aggregateInvokeModelResponseStreamChunks, parseBedrockRuntimeMetrics, -} from "./bedrock-runtime-plugin"; +} from "./bedrock-runtime-instrumentation"; try { configureNode(); + registerInstrumentation(); } catch { // Best-effort initialization for test environments. } @@ -29,7 +39,7 @@ class GetObjectCommand { constructor(public input: Record) {} } -describe("BedrockRuntimePlugin", () => { +describe("registerBedrockRuntimeInstrumentation", () => { let backgroundLogger: ReturnType< typeof _exportsForTestingOnly.useTestBackgroundLogger >; @@ -41,7 +51,7 @@ describe("BedrockRuntimePlugin", () => { beforeEach(() => { backgroundLogger = _exportsForTestingOnly.useTestBackgroundLogger(); initLogger({ - projectName: "bedrock-runtime-plugin.test.ts", + projectName: "bedrock-runtime-instrumentation.test.ts", projectId: "test-project-id", }); }); diff --git a/js/src/instrumentation/plugins/bedrock-runtime-plugin.ts b/js/src/instrumentation/providers/bedrock-runtime-instrumentation.ts similarity index 96% rename from js/src/instrumentation/plugins/bedrock-runtime-plugin.ts rename to js/src/instrumentation/providers/bedrock-runtime-instrumentation.ts index 5e4d3987b..81c9e6a2e 100644 --- a/js/src/instrumentation/plugins/bedrock-runtime-plugin.ts +++ b/js/src/instrumentation/providers/bedrock-runtime-instrumentation.ts @@ -1,12 +1,12 @@ import type { ReturnOf } from "../core/interceptor-definitions"; import type { StartOf } from "../core/tracing-types"; import { SpanTypeAttribute, isObject } from "../../../util/index"; -import type { Span } from "../../logger"; +import type { Span } from "../../logger/core"; import type { OpenAIChatCompletionChunk } from "../../vendor-sdk-types/openai"; import { INSTRUMENTATION_NAMES } from "../../span-origin"; import { getCurrentUnixTimestamp } from "../../util"; -import { BasePlugin } from "../core"; -import { traceStreamingCall, unsubscribeAll } from "../core/tracing"; + +import { traceStreamingCall } from "../core/tracing"; import { isAsyncIterable, patchStreamIfNeeded } from "../core/stream-patcher"; import type { @@ -28,29 +28,21 @@ import { getBedrockRuntimeCommandName, getBedrockRuntimeOperation, } from "./bedrock-runtime-common"; -import { aggregateChatCompletionChunks } from "./openai-plugin"; - -export class BedrockRuntimePlugin extends BasePlugin { - protected onEnable(): void { - this.unsubscribers.push( - ...[ - bedrockRuntimeChannels.clientSend, - smithyCoreChannels.clientSend, - smithyClientChannels.clientSend, - ].map((channel) => - channel.intercept((target, receiver, args, additional) => - traceBedrockRuntimeClientSend( - () => Reflect.apply(target, receiver, args), - { ...additional, arguments: args, self: receiver }, - ), - ), +import { aggregateChatCompletionChunks } from "./openai-instrumentation"; + +export function registerBedrockRuntimeInstrumentation(): void { + [ + bedrockRuntimeChannels.clientSend, + smithyCoreChannels.clientSend, + smithyClientChannels.clientSend, + ].forEach((channel) => + channel.intercept((target, receiver, args, additional) => + traceBedrockRuntimeClientSend( + () => Reflect.apply(target, receiver, args), + { ...additional, arguments: args, self: receiver }, ), - ); - } - - protected onDisable(): void { - this.unsubscribers = unsubscribeAll(this.unsubscribers); - } + ), + ); } function traceBedrockRuntimeClientSend( diff --git a/js/src/instrumentation/plugins/bedrock-runtime-interceptors.ts b/js/src/instrumentation/providers/bedrock-runtime-interceptors.ts similarity index 100% rename from js/src/instrumentation/plugins/bedrock-runtime-interceptors.ts rename to js/src/instrumentation/providers/bedrock-runtime-interceptors.ts diff --git a/js/src/instrumentation/plugins/claude-agent-sdk-instrumentation-constants.ts b/js/src/instrumentation/providers/claude-agent-sdk-instrumentation-constants.ts similarity index 100% rename from js/src/instrumentation/plugins/claude-agent-sdk-instrumentation-constants.ts rename to js/src/instrumentation/providers/claude-agent-sdk-instrumentation-constants.ts diff --git a/js/src/instrumentation/plugins/claude-agent-sdk-plugin.streaming.test.ts b/js/src/instrumentation/providers/claude-agent-sdk-instrumentation.streaming.test.ts similarity index 98% rename from js/src/instrumentation/plugins/claude-agent-sdk-plugin.streaming.test.ts rename to js/src/instrumentation/providers/claude-agent-sdk-instrumentation.streaming.test.ts index 8c68e6d3e..608ee29a6 100644 --- a/js/src/instrumentation/plugins/claude-agent-sdk-plugin.streaming.test.ts +++ b/js/src/instrumentation/providers/claude-agent-sdk-instrumentation.streaming.test.ts @@ -1,10 +1,14 @@ -import { afterEach, beforeAll, beforeEach, describe, expect, it } from "vitest"; -import type { BackgroundLogEvent } from "../../../util/index"; import { - _exportsForTestingOnly, - initLogger, - type TestBackgroundLogger, -} from "../../logger"; + afterEach, + beforeAll, + beforeEach, + describe, + expect, + it, +} from "vite-plus/test"; +import type { BackgroundLogEvent } from "../../../util/index"; +import { _exportsForTestingOnly } from "../../logger/testing"; +import { initLogger, type TestBackgroundLogger } from "../../logger/core"; import { configureNode } from "../../node/config"; import type { ClaudeAgentSDKMessage, @@ -190,7 +194,7 @@ describe("Claude Agent SDK streaming instrumentation", () => { backgroundLogger = _exportsForTestingOnly.useTestBackgroundLogger(); initLogger({ projectId: "test-project-id", - projectName: "claude-agent-sdk-plugin.streaming.test.ts", + projectName: "claude-agent-sdk-instrumentation.streaming.test.ts", }); }); diff --git a/js/src/instrumentation/plugins/claude-agent-sdk-plugin.test.ts b/js/src/instrumentation/providers/claude-agent-sdk-instrumentation.test.ts similarity index 85% rename from js/src/instrumentation/plugins/claude-agent-sdk-plugin.test.ts rename to js/src/instrumentation/providers/claude-agent-sdk-instrumentation.test.ts index c0728dda1..1d58b2485 100644 --- a/js/src/instrumentation/plugins/claude-agent-sdk-plugin.test.ts +++ b/js/src/instrumentation/providers/claude-agent-sdk-instrumentation.test.ts @@ -1,5 +1,12 @@ import { AsyncLocalStorage } from "node:async_hooks"; -import { afterEach, beforeEach, describe, expect, it, vi } from "vitest"; +import { + afterEach, + beforeEach, + describe, + expect, + it, + vi, +} from "vite-plus/test"; import { newGlobalInvocationHook } from "../../global-instrumentation-hooks"; vi.mock("../../global-instrumentation-hooks", async (importOriginal) => ({ ...(await importOriginal< @@ -18,7 +25,7 @@ const streamPatcherMock = vi.hoisted(() => ({ | undefined, })); -vi.mock("../../isomorph", () => ({ +vi.mock("#braintrust-platform", () => ({ default: { newAsyncLocalStorage: () => new AsyncLocalStorage(), }, @@ -38,21 +45,25 @@ vi.mock("../core/stream-patcher", () => ({ }), })); -import { startSpan } from "../../logger"; -import { ClaudeAgentSDKPlugin } from "./claude-agent-sdk-plugin"; +import { registerClaudeAgentSDKInstrumentation } from "./claude-agent-sdk-instrumentation"; +import { _internalStartSpan as startSpan } from "../../logger/core"; const mockNewInvocationHook = newGlobalInvocationHook as ReturnType< typeof vi.fn >; // Mock the logger module -vi.mock("../../logger", () => ({ - startSpan: vi.fn(() => ({ +vi.mock("../../logger/core", () => { + const startSpan = vi.fn(() => ({ log: vi.fn(), end: vi.fn(), export: vi.fn(() => Promise.resolve({})), - })), -})); + })); + return { + startSpan, + _internalStartSpan: startSpan, + }; +}); // Mock utility modules vi.mock("../../../util/index", () => ({ @@ -81,84 +92,31 @@ vi.mock("../../wrappers/attachment-utils", () => ({ // rules, so these tests run the real implementation rather than a stand-in that // could drift from it. -vi.mock("../core", async (importOriginal) => { - const actual = await importOriginal(); - - return { - ...actual, - BasePlugin: class BasePlugin { - protected enabled = false; - protected unsubscribers: Array<() => void> = []; - - enable(): void { - if (this.enabled) { - return; - } - this.enabled = true; - this.onEnable(); - } - - disable(): void { - if (!this.enabled) { - return; - } - this.enabled = false; - this.onDisable(); - } - - protected onEnable(): void { - // To be implemented by subclass - } - - protected onDisable(): void { - // To be implemented by subclass - } - }, - isAsyncIterable: vi.fn( - (val: unknown) => - val !== null && - typeof val === "object" && - Symbol.asyncIterator in val && - typeof (val as any)[Symbol.asyncIterator] === "function", - ), - patchStreamIfNeeded: vi.fn((stream, _callbacks) => { - // Return the stream unchanged for simple tests - return stream; - }), - }; -}); - -describe("ClaudeAgentSDKPlugin", () => { - let plugin: ClaudeAgentSDKPlugin; +describe("registerClaudeAgentSDKInstrumentation", () => { let mockChannel: any; - let mockUnsubscribe: any; let queryInterceptor: any; beforeEach(() => { streamPatcherMock.options = undefined; - mockUnsubscribe = vi.fn(); mockChannel = { intercept: vi.fn((interceptor) => { queryInterceptor = interceptor; - return mockUnsubscribe; + return vi.fn(); }), subscribe: vi.fn(), - unsubscribe: mockUnsubscribe, hasSubscribers: false, }; mockNewInvocationHook.mockReturnValue(mockChannel); - - plugin = new ClaudeAgentSDKPlugin(); }); afterEach(() => { vi.clearAllMocks(); }); - describe("enable", () => { - it("should enable the plugin and subscribe to channels", () => { - plugin.enable(); + describe("registration", () => { + it("subscribes to the channel", () => { + registerClaudeAgentSDKInstrumentation(); expect(mockNewInvocationHook).toHaveBeenCalledWith( "orchestrion:@anthropic-ai/claude-agent-sdk:query", @@ -166,50 +124,13 @@ describe("ClaudeAgentSDKPlugin", () => { expect(mockChannel.intercept).toHaveBeenCalledTimes(1); expect(mockChannel.intercept).toHaveBeenCalledWith(expect.any(Function)); }); - - it("should not subscribe twice if already enabled", () => { - plugin.enable(); - plugin.enable(); - - expect(mockChannel.intercept).toHaveBeenCalledTimes(1); - }); - - it("should store unsubscribe function", () => { - plugin.enable(); - - expect((plugin as any).unsubscribers).toHaveLength(1); - expect((plugin as any).unsubscribers[0]).toBeInstanceOf(Function); - }); - }); - - describe("disable", () => { - it("should unsubscribe from all channels", () => { - plugin.enable(); - plugin.disable(); - - expect(mockUnsubscribe).toHaveBeenCalledTimes(1); - expect((plugin as any).unsubscribers).toHaveLength(0); - }); - - it("should not unsubscribe if not enabled", () => { - plugin.disable(); - - expect(mockUnsubscribe).not.toHaveBeenCalled(); - }); - - it("should clear unsubscribers array", () => { - plugin.enable(); - plugin.disable(); - - expect((plugin as any).unsubscribers).toHaveLength(0); - }); }); describe("channel subscription handlers", () => { let handlers: any; beforeEach(() => { - plugin.enable(); + registerClaudeAgentSDKInstrumentation(); handlers = { start: (event: any) => queryInterceptor( @@ -780,24 +701,4 @@ describe("ClaudeAgentSDKPlugin", () => { }); }); }); - - describe("enable/disable lifecycle", () => { - it("should allow re-enabling after disable", () => { - plugin.enable(); - plugin.disable(); - plugin.enable(); - - expect(mockChannel.intercept).toHaveBeenCalledTimes(2); - }); - - it("should properly clean up on multiple enable/disable cycles", () => { - plugin.enable(); - plugin.disable(); - plugin.enable(); - plugin.disable(); - - expect(mockUnsubscribe).toHaveBeenCalledTimes(2); - expect((plugin as any).unsubscribers).toHaveLength(0); - }); - }); }); diff --git a/js/src/instrumentation/plugins/claude-agent-sdk-plugin.ts b/js/src/instrumentation/providers/claude-agent-sdk-instrumentation.ts similarity index 96% rename from js/src/instrumentation/plugins/claude-agent-sdk-plugin.ts rename to js/src/instrumentation/providers/claude-agent-sdk-instrumentation.ts index 17c2f1db9..a185c225c 100644 --- a/js/src/instrumentation/plugins/claude-agent-sdk-plugin.ts +++ b/js/src/instrumentation/providers/claude-agent-sdk-instrumentation.ts @@ -1,8 +1,7 @@ -import { BasePlugin } from "../core"; import { isAsyncIterable, patchStreamIfNeeded } from "../core/stream-patcher"; import { debugLogger } from "../../debug-logger"; -import { startSpan as startBaseSpan } from "../../logger"; -import type { Span } from "../../logger"; +import { _internalStartSpan as startBaseSpan } from "../../logger/core"; +import type { Span } from "../../logger/core"; import { INSTRUMENTATION_NAMES, withSpanInstrumentationName, @@ -1778,18 +1777,11 @@ async function finalizeQuerySpan(state: QueryState): Promise { } } -export class ClaudeAgentSDKPlugin extends BasePlugin { - protected onEnable(): void { +class ClaudeAgentSDKInstrumentationConsumer { + public register(): void { this.interceptQuery(); } - protected onDisable(): void { - for (const unsubscribe of this.unsubscribers) { - unsubscribe(); - } - this.unsubscribers = []; - } - private interceptQuery(): void { const startQuery = (params: ClaudeAgentSDKQueryParams): QueryState => { const originalPrompt = params.prompt; @@ -2015,55 +2007,63 @@ export class ClaudeAgentSDKPlugin extends BasePlugin { } }; - this.unsubscribers.push( - claudeAgentSDKChannels.query.intercept((target, thisArg, args) => { - let state: QueryState | undefined; - try { - args[0] ??= {}; - state = startQuery(args[0]); - } catch (error) { - debugLogger.error( - "Error starting Claude Agent SDK instrumentation:", - error, - ); - } + claudeAgentSDKChannels.query.intercept((target, thisArg, args) => { + let state: QueryState | undefined; + try { + args[0] ??= {}; + state = startQuery(args[0]); + } catch (error) { + debugLogger.error( + "Error starting Claude Agent SDK instrumentation:", + error, + ); + } - const invokeTarget = () => Reflect.apply(target, thisArg, args); - try { - const result = state - ? runWithClaudeLocalToolContext( - invokeTarget, - state.localToolParentResolver, - ) - : invokeTarget(); - if (state) { - try { - finishQuery(state, result); - } catch (error) { - debugLogger.error( - "Error finalizing Claude Agent SDK instrumentation:", - error, - ); - } + const invokeTarget = () => Reflect.apply(target, thisArg, args); + try { + const result = state + ? runWithClaudeLocalToolContext( + invokeTarget, + state.localToolParentResolver, + ) + : invokeTarget(); + if (state) { + try { + finishQuery(state, result); + } catch (error) { + debugLogger.error( + "Error finalizing Claude Agent SDK instrumentation:", + error, + ); } - return result; - } catch (error) { - if (state) { - try { - state.span.log({ - error: error instanceof Error ? error.message : String(error), - }); - state.span.end(); - } catch (instrumentationError) { - debugLogger.error( - "Error handling Claude Agent SDK instrumentation failure:", - instrumentationError, - ); - } + } + return result; + } catch (error) { + if (state) { + try { + state.span.log({ + error: error instanceof Error ? error.message : String(error), + }); + state.span.end(); + } catch (instrumentationError) { + debugLogger.error( + "Error handling Claude Agent SDK instrumentation failure:", + instrumentationError, + ); } - throw error; } - }), - ); + throw error; + } + }); } } + +let claudeAgentSDKInstrumentationConsumer: + | ClaudeAgentSDKInstrumentationConsumer + | undefined; + +export function registerClaudeAgentSDKInstrumentation(): void { + claudeAgentSDKInstrumentationConsumer ??= + new ClaudeAgentSDKInstrumentationConsumer(); + claudeAgentSDKInstrumentationConsumer.register(); +} diff --git a/js/src/instrumentation/plugins/claude-agent-sdk-interceptors.ts b/js/src/instrumentation/providers/claude-agent-sdk-interceptors.ts similarity index 100% rename from js/src/instrumentation/plugins/claude-agent-sdk-interceptors.ts rename to js/src/instrumentation/providers/claude-agent-sdk-interceptors.ts diff --git a/js/src/instrumentation/plugins/claude-agent-sdk-local-tool-context.ts b/js/src/instrumentation/providers/claude-agent-sdk-local-tool-context.ts similarity index 96% rename from js/src/instrumentation/plugins/claude-agent-sdk-local-tool-context.ts rename to js/src/instrumentation/providers/claude-agent-sdk-local-tool-context.ts index e0c2129c3..597479368 100644 --- a/js/src/instrumentation/plugins/claude-agent-sdk-local-tool-context.ts +++ b/js/src/instrumentation/providers/claude-agent-sdk-local-tool-context.ts @@ -1,4 +1,4 @@ -import iso from "../../isomorph"; +import iso from "#braintrust-platform"; type ClaudeLocalToolParentResolver = (toolUseId: string) => Promise; diff --git a/js/src/instrumentation/plugins/claude-agent-sdk-local-tool-spans.ts b/js/src/instrumentation/providers/claude-agent-sdk-local-tool-spans.ts similarity index 98% rename from js/src/instrumentation/plugins/claude-agent-sdk-local-tool-spans.ts rename to js/src/instrumentation/providers/claude-agent-sdk-local-tool-spans.ts index 843309851..27f3a8642 100644 --- a/js/src/instrumentation/plugins/claude-agent-sdk-local-tool-spans.ts +++ b/js/src/instrumentation/providers/claude-agent-sdk-local-tool-spans.ts @@ -1,4 +1,7 @@ -import { startSpan as startBaseSpan, withCurrent } from "../../logger"; +import { + _internalStartSpan as startBaseSpan, + withCurrent, +} from "../../logger/core"; import { INSTRUMENTATION_NAMES, withSpanInstrumentationName, diff --git a/js/src/instrumentation/plugins/cloudflare-agents-plugin.test.ts b/js/src/instrumentation/providers/cloudflare-agents-instrumentation.test.ts similarity index 89% rename from js/src/instrumentation/plugins/cloudflare-agents-plugin.test.ts rename to js/src/instrumentation/providers/cloudflare-agents-instrumentation.test.ts index 8f98f079e..bd1050505 100644 --- a/js/src/instrumentation/plugins/cloudflare-agents-plugin.test.ts +++ b/js/src/instrumentation/providers/cloudflare-agents-instrumentation.test.ts @@ -6,9 +6,9 @@ import { expectTypeOf, it, vi, -} from "vitest"; +} from "vite-plus/test"; import { newGlobalInvocationHook } from "../../global-instrumentation-hooks"; -import type { StartSpanArgs } from "../../logger"; +import type { StartSpanArgs } from "../../logger/core"; import { INSTRUMENTATION_NAMES, getSpanInstrumentationName, @@ -25,23 +25,23 @@ const { mockStartSpan } = vi.hoisted(() => ({ mockStartSpan: vi.fn(), })); -vi.mock("../../logger", () => ({ +vi.mock("../../logger/core", () => ({ _internalStartSpanWithContext: (...args: unknown[]) => mockStartSpan(...args), })); -vi.mock("../../isomorph", () => ({ +vi.mock("#braintrust-platform", () => ({ default: { getEnv: vi.fn(), }, })); -import { CloudflareAgentsPlugin } from "./cloudflare-agents-plugin"; +import { registerCloudflareAgentsInstrumentation } from "./cloudflare-agents-instrumentation"; const mockNewInvocationHook = newGlobalInvocationHook as ReturnType< typeof vi.fn >; -describe("CloudflareAgentsPlugin", () => { +describe("registerCloudflareAgentsInstrumentation", () => { let handlers: any; let subscribe: ReturnType void>>; let unsubscribe: ReturnType; @@ -75,19 +75,13 @@ describe("CloudflareAgentsPlugin", () => { vi.clearAllMocks(); }); - it("subscribes idempotently to Agent.runAgentTool", () => { - const plugin = new CloudflareAgentsPlugin(); - plugin.enable(); - plugin.enable(); + it("subscribes to the process-lifetime channel", () => { + registerCloudflareAgentsInstrumentation(); expect(mockNewInvocationHook).toHaveBeenCalledWith( "orchestrion:agents:Agent.runAgentTool", ); expect(subscribe).toHaveBeenCalledTimes(1); - - plugin.disable(); - plugin.disable(); - expect(unsubscribe).toHaveBeenCalledTimes(1); }); it("keeps SDK-controlled context out of the public start-span arguments", () => { @@ -99,7 +93,7 @@ describe("CloudflareAgentsPlugin", () => { }); it("records only the child class name, input, and completed output", () => { - new CloudflareAgentsPlugin().enable(); + registerCloudflareAgentsInstrumentation(); class ResearchAgent {} const event = { arguments: [ @@ -155,7 +149,7 @@ describe("CloudflareAgentsPlugin", () => { }); it("records returned terminal error strings", () => { - new CloudflareAgentsPlugin().enable(); + registerCloudflareAgentsInstrumentation(); class FailingAgent {} const event = { arguments: [FailingAgent, { input: "fail" }] }; @@ -177,7 +171,7 @@ describe("CloudflareAgentsPlugin", () => { }); it("records the original rejection and preserves concurrent span state", () => { - new CloudflareAgentsPlugin().enable(); + registerCloudflareAgentsInstrumentation(); class FirstAgent {} class SecondAgent {} const first = { arguments: [FirstAgent, { input: 1 }] }; @@ -201,7 +195,7 @@ describe("CloudflareAgentsPlugin", () => { }); it("skips detached runs and does not invoke getters", () => { - new CloudflareAgentsPlugin().enable(); + registerCloudflareAgentsInstrumentation(); const nameGetter = vi.fn(() => "GetterAgent"); const inputGetter = vi.fn(() => "getter-input"); const AgentWithGetter = Object.defineProperty(function () {}, "name", { diff --git a/js/src/instrumentation/providers/cloudflare-agents-instrumentation.ts b/js/src/instrumentation/providers/cloudflare-agents-instrumentation.ts new file mode 100644 index 000000000..867ba3f76 --- /dev/null +++ b/js/src/instrumentation/providers/cloudflare-agents-instrumentation.ts @@ -0,0 +1,159 @@ +import { SpanTypeAttribute } from "../../../util/index"; +import { debugLogger } from "../../debug-logger"; +import type { Span } from "../../logger/core"; +import { _internalStartSpanWithContext } from "../../logger/core"; +import { + INSTRUMENTATION_NAMES, + withSpanInstrumentationName, +} from "../../span-origin"; + +import { observeResult, runInstrumentation } from "../core/observe-result"; + +import type { ChannelMessage } from "../core/tracing-types"; +import { cloudflareAgentsChannels } from "./cloudflare-agents-interceptors"; + +const CLOUDFLARE_WORKERS_CONTEXT = { + span_origin: { + environment: { type: "server", name: "cloudflare_workers" }, + }, +}; + +export function registerCloudflareAgentsInstrumentation(): void { + const channel = cloudflareAgentsChannels.runAgentTool; + const spans = new WeakMap(); + + channel.intercept((target, receiver, args, additional) => { + const event = { + ...additional, + arguments: args, + self: receiver, + } as ChannelMessage; + const prepare = ( + event: ChannelMessage, + ) => { + try { + const agentClass = event.arguments[0]; + const options = event.arguments[1]; + if (ownValue(options, "detached")) { + return; + } + + const name = ownValue(agentClass, "name"); + if (typeof name !== "string" || name.length === 0) { + debugLogger.warn( + "Skipping Cloudflare Agents runAgentTool span because the child agent class has no name.", + ); + return; + } + + const span = _internalStartSpanWithContext( + withSpanInstrumentationName( + { + name, + spanAttributes: { type: SpanTypeAttribute.TOOL }, + event: { + input: ownValue(options, "input"), + }, + }, + INSTRUMENTATION_NAMES.CLOUDFLARE_AGENTS, + ), + CLOUDFLARE_WORKERS_CONTEXT, + ); + spans.set(event, span); + } catch (error) { + logInstrumentationError("start", error); + } + }; + const resolved = ( + event: ChannelMessage, + ) => { + const span = spans.get(event); + if (!span) { + return; + } + spans.delete(event); + + try { + const status = ownValue(event.result, "status"); + if (status === "completed") { + span.log({ output: ownValue(event.result, "output") }); + } else { + const error = ownValue(event.result, "error"); + if (typeof error === "string") { + span.log({ error }); + } + } + } catch (error) { + logInstrumentationError("completion", error); + } finally { + safelyEndSpan(span); + } + }; + const failed = ( + event: ChannelMessage, + ) => { + const span = spans.get(event); + if (!span) { + return; + } + spans.delete(event); + + try { + span.log({ error: event.error }); + } catch (error) { + logInstrumentationError("rejection", error); + } finally { + safelyEndSpan(span); + } + }; + runInstrumentation(() => prepare(event)); + let result; + try { + result = Reflect.apply(target, receiver, args); + } catch (error) { + Object.assign(event, { error }); + runInstrumentation(() => failed(event)); + throw error; + } + return observeResult( + result, + (value) => { + Object.assign(event, { result: value }); + resolved(event); + }, + (error) => { + Object.assign(event, { error }); + failed(event); + }, + ); + }); +} + +function ownValue(value: unknown, key: PropertyKey): unknown { + if (!isObjectLike(value)) { + return undefined; + } + const descriptor = Object.getOwnPropertyDescriptor(value, key); + return descriptor && "value" in descriptor ? descriptor.value : undefined; +} + +function isObjectLike(value: unknown): value is object { + return ( + (typeof value === "object" && value !== null) || typeof value === "function" + ); +} + +function safelyEndSpan(span: Span): void { + try { + span.end(); + } catch (error) { + logInstrumentationError("span end", error); + } +} + +function logInstrumentationError(operation: string, error: unknown): void { + debugLogger.error( + `Failed to process Cloudflare Agents ${operation} instrumentation:`, + error, + ); +} diff --git a/js/src/instrumentation/plugins/cloudflare-agents-interceptors.ts b/js/src/instrumentation/providers/cloudflare-agents-interceptors.ts similarity index 100% rename from js/src/instrumentation/plugins/cloudflare-agents-interceptors.ts rename to js/src/instrumentation/providers/cloudflare-agents-interceptors.ts diff --git a/js/src/instrumentation/plugins/cloudflare-ai-chat-plugin.test.ts b/js/src/instrumentation/providers/cloudflare-ai-chat-consumer.test.ts similarity index 90% rename from js/src/instrumentation/plugins/cloudflare-ai-chat-plugin.test.ts rename to js/src/instrumentation/providers/cloudflare-ai-chat-consumer.test.ts index f1d869037..b7f46fa2e 100644 --- a/js/src/instrumentation/plugins/cloudflare-ai-chat-plugin.test.ts +++ b/js/src/instrumentation/providers/cloudflare-ai-chat-consumer.test.ts @@ -1,4 +1,11 @@ -import { afterEach, beforeEach, describe, expect, it, vi } from "vitest"; +import { + afterEach, + beforeEach, + describe, + expect, + it, + vi, +} from "vite-plus/test"; import { newGlobalInvocationHook } from "../../global-instrumentation-hooks"; import { invocationController } from "../test-utils/invocation"; vi.mock("../../global-instrumentation-hooks", async (importOriginal) => ({ @@ -8,26 +15,21 @@ vi.mock("../../global-instrumentation-hooks", async (importOriginal) => ({ newGlobalInvocationHook: vi.fn(), })); -const { - mockInternalGetGlobalState, - mockStartSpan, - mockWithCurrent, - mockCurrentSpanStoreSymbol, -} = vi.hoisted(() => ({ - mockCurrentSpanStoreSymbol: Symbol.for("braintrust.currentSpanStore"), - mockInternalGetGlobalState: vi.fn(() => undefined), - mockStartSpan: vi.fn(), - mockWithCurrent: vi.fn((_span: unknown, callback: () => unknown) => - callback(), - ), -})); - -vi.mock("../../isomorph", () => ({ +const { mockInternalGetGlobalState, mockStartSpan, mockWithCurrent } = + vi.hoisted(() => ({ + mockCurrentSpanStoreSymbol: Symbol.for("braintrust.currentSpanStore"), + mockInternalGetGlobalState: vi.fn(() => undefined), + mockStartSpan: vi.fn(), + mockWithCurrent: vi.fn((_span: unknown, callback: () => unknown) => + callback(), + ), + })); + +vi.mock("#braintrust-platform", () => ({ default: {}, })); -vi.mock("../../logger", () => ({ - BRAINTRUST_CURRENT_SPAN_STORE: mockCurrentSpanStoreSymbol, +vi.mock("../../logger/core", () => ({ _internalGetGlobalState: () => mockInternalGetGlobalState(), startSpan: (...args: unknown[]) => (mockStartSpan as any)(...args), withCurrent: (...args: unknown[]) => (mockWithCurrent as any)(...args), @@ -37,14 +39,13 @@ import { INSTRUMENTATION_NAMES, INTERNAL_SPAN_INSTRUMENTATION_NAME, } from "../../span-origin"; -import { CloudflareAIChatPlugin } from "./cloudflare-ai-chat-plugin"; +import { registerCloudflareAIChatInstrumentation } from "./cloudflare-ai-chat-consumer"; const mockNewInvocationHook = newGlobalInvocationHook as ReturnType< typeof vi.fn >; -describe("CloudflareAIChatPlugin", () => { - let plugin: CloudflareAIChatPlugin; +describe("registerCloudflareAIChatInstrumentation", () => { let channels: Map>; beforeEach(() => { @@ -63,16 +64,14 @@ describe("CloudflareAIChatPlugin", () => { log: vi.fn(), })); mockInternalGetGlobalState.mockReturnValue(undefined); - plugin = new CloudflareAIChatPlugin(); }); afterEach(() => { - plugin.disable(); vi.clearAllMocks(); }); it("captures the full successful turn and binds queued work", async () => { - plugin.enable(); + registerCloudflareAIChatInstrumentation(); const turnHandlers = turnChannel().handlers(); const callback = vi.fn(async () => "callback-result"); const agent = { @@ -141,7 +140,7 @@ describe("CloudflareAIChatPlugin", () => { }); it("correlates response errors and preserves partial output", async () => { - plugin.enable(); + registerCloudflareAIChatInstrumentation(); const turnHandlers = turnChannel().handlers(); const responseHandlers = responseChannel().handlers(); const agent = { messages: [], onChatResponse() {} }; @@ -175,7 +174,7 @@ describe("CloudflareAIChatPlugin", () => { }); it("retains a settled turn until its queued response is observed", async () => { - plugin.enable(); + registerCloudflareAIChatInstrumentation(); const handlers = turnChannel().handlers(); const agent = { messages: [ @@ -231,7 +230,7 @@ describe("CloudflareAIChatPlugin", () => { it("drops retained turns that never produce a response", () => { vi.useFakeTimers(); try { - plugin.enable(); + registerCloudflareAIChatInstrumentation(); const handlers = turnChannel().handlers(); const agent = { messages: [], @@ -269,7 +268,7 @@ describe("CloudflareAIChatPlugin", () => { }); it("preserves pre-turn input when a continuation reuses its output id", async () => { - plugin.enable(); + registerCloudflareAIChatInstrumentation(); const handlers = turnChannel().handlers(); const agent = { messages: [ @@ -330,7 +329,7 @@ describe("CloudflareAIChatPlugin", () => { }); it("deduplicates nested manual and automatic turn events", () => { - plugin.enable(); + registerCloudflareAIChatInstrumentation(); const handlers = turnChannel().handlers(); const agent = { messages: [], onChatResponse() {} }; const outer = { @@ -354,8 +353,8 @@ describe("CloudflareAIChatPlugin", () => { expect(span.end).toHaveBeenCalledTimes(1); }); - it("logs original errors and closes outstanding spans on disable", () => { - plugin.enable(); + it("logs original errors without closing unrelated outstanding spans", () => { + registerCloudflareAIChatInstrumentation(); const handlers = turnChannel().handlers(); const failure = new Error("turn failed"); const failedEvent = { @@ -376,8 +375,7 @@ describe("CloudflareAIChatPlugin", () => { } as any; handlers.begin?.(pendingEvent); const pendingSpan = mockStartSpan.mock.results[1].value; - plugin.disable(); - expect(pendingSpan.end).toHaveBeenCalledTimes(1); + expect(pendingSpan.end).not.toHaveBeenCalled(); }); function turnChannel() { diff --git a/js/src/instrumentation/plugins/cloudflare-ai-chat-plugin.ts b/js/src/instrumentation/providers/cloudflare-ai-chat-consumer.ts similarity index 73% rename from js/src/instrumentation/plugins/cloudflare-ai-chat-plugin.ts rename to js/src/instrumentation/providers/cloudflare-ai-chat-consumer.ts index 1600f0833..aed322ca0 100644 --- a/js/src/instrumentation/plugins/cloudflare-ai-chat-plugin.ts +++ b/js/src/instrumentation/providers/cloudflare-ai-chat-consumer.ts @@ -1,10 +1,9 @@ -import { BasePlugin } from "../core"; import { observeResult, runInstrumentation } from "../core/observe-result"; import { SpanTypeAttribute } from "../../../util/index"; import { debugLogger } from "../../debug-logger"; -import type { Span } from "../../logger"; -import { startSpan as startBaseSpan, withCurrent } from "../../logger"; +import type { Span } from "../../logger/core"; +import { startSpan as startBaseSpan, withCurrent } from "../../logger/core"; import { INSTRUMENTATION_NAMES, withSpanInstrumentationName, @@ -37,7 +36,7 @@ type TurnState = { const BLOCKED_KEYS = new Set(["__proto__", "constructor", "prototype"]); const RESPONSE_STATE_RETENTION_MS = 60_000; -export class CloudflareAIChatPlugin extends BasePlugin { +class CloudflareAIChatInstrumentationConsumer { private readonly activeStates = new Set(); private readonly activeTurns = new WeakMap< object, @@ -45,139 +44,118 @@ export class CloudflareAIChatPlugin extends BasePlugin { >(); private readonly eventStates = new WeakMap(); - protected onEnable(): void { + public register(): void { this.subscribeToResponseHook(); this.subscribeToTurnRunner(); } - protected onDisable(): void { - for (const unsubscribe of this.unsubscribers) { - unsubscribe(); - } - this.unsubscribers = []; - - for (const state of this.activeStates) { - this.cleanupState(state); - } - this.activeStates.clear(); - } - private subscribeToTurnRunner(): void { const invocationHook = cloudflareAIChatChannels.runExclusiveChatTurn; - const removeHandlers = invocationHook.intercept( - (target, receiver, args, additional) => { - const event = { - ...additional, - arguments: args, - self: receiver, - } as ChannelMessage; - const spanState = runInstrumentation(() => - this.ensureEventState(event), - ); - const prepare = (event: ChannelMessage) => { - this.ensureEventState(event); - }; - const resolved = (event: ChannelMessage) => { - this.finishEvent(event); - }; - const failed = (event: ChannelMessage) => { - this.finishEvent(event, event.error); - }; - const invoke = () => { - runInstrumentation(() => prepare(event)); - let result; - try { - result = Reflect.apply(target, receiver, args); - } catch (error) { - Object.assign(event, { error }); - runInstrumentation(() => failed(event)); - throw error; - } + invocationHook.intercept((target, receiver, args, additional) => { + const event = { + ...additional, + arguments: args, + self: receiver, + } as ChannelMessage; + const spanState = runInstrumentation(() => this.ensureEventState(event)); + const prepare = (event: ChannelMessage) => { + this.ensureEventState(event); + }; + const resolved = (event: ChannelMessage) => { + this.finishEvent(event); + }; + const failed = (event: ChannelMessage) => { + this.finishEvent(event, event.error); + }; + const invoke = () => { + runInstrumentation(() => prepare(event)); + let result; + try { + result = Reflect.apply(target, receiver, args); + } catch (error) { + Object.assign(event, { error }); + runInstrumentation(() => failed(event)); + throw error; + } - return observeResult( - result, - (value) => { - Object.assign(event, { result: value }); - resolved(event); - }, - (error) => { - Object.assign(event, { error }); - failed(event); - }, - ); - }; - return spanState ? withCurrent(spanState.span, invoke) : invoke(); - }, - ); - this.unsubscribers.push(removeHandlers); + return observeResult( + result, + (value) => { + Object.assign(event, { result: value }); + resolved(event); + }, + (error) => { + Object.assign(event, { error }); + failed(event); + }, + ); + }; + return spanState ? withCurrent(spanState.span, invoke) : invoke(); + }); } private subscribeToResponseHook(): void { const invocationHook = cloudflareAIChatChannels.onChatResponse; - const removeHandlers = invocationHook.intercept( - (target, receiver, args, additional) => { - const event = { - ...additional, - arguments: args, - self: receiver, - } as ChannelMessage; + invocationHook.intercept((target, receiver, args, additional) => { + const event = { + ...additional, + arguments: args, + self: receiver, + } as ChannelMessage; - const prepare = (event: ChannelMessage) => { - let state: TurnState | undefined; - try { - const agent = asObject(event.self); - const result = event.arguments[0]; - state = agent - ? this.findResponseState( - agent, - stringValue(ownValue(result, "requestId")), - ) - : undefined; - if (!state) { - return; - } - state.responseObserved = true; - - const output = serializeMessage(ownValue(result, "message")); - const status = stringValue(ownValue(result, "status")); - const error = ownValue(result, "error"); - const input = - ownValue(result, "continuation") === true - ? state.input - : serializeMessages(readProperty(agent, "messages"))?.filter( - (message) => - typeof output?.id !== "string" || - message.id !== output.id, - ); - state.span.log({ - ...(input !== undefined ? { input } : {}), - ...(output !== undefined ? { output } : {}), - ...(status === "error" && error !== undefined ? { error } : {}), - }); - } catch (error) { - debugLogger.debug( - "Failed to process @cloudflare/ai-chat response hook:", - error, - ); - } finally { - if (state?.settled) { - this.cleanupState(state); - } - } - }; - runInstrumentation(() => prepare(event)); - let result; + const prepare = (event: ChannelMessage) => { + let state: TurnState | undefined; try { - result = Reflect.apply(target, receiver, args); + const agent = asObject(event.self); + const result = event.arguments[0]; + state = agent + ? this.findResponseState( + agent, + stringValue(ownValue(result, "requestId")), + ) + : undefined; + if (!state) { + return; + } + state.responseObserved = true; + + const output = serializeMessage(ownValue(result, "message")); + const status = stringValue(ownValue(result, "status")); + const error = ownValue(result, "error"); + const input = + ownValue(result, "continuation") === true + ? state.input + : serializeMessages(readProperty(agent, "messages"))?.filter( + (message) => + typeof output?.id !== "string" || message.id !== output.id, + ); + state.span.log({ + ...(input !== undefined ? { input } : {}), + ...(output !== undefined ? { output } : {}), + ...(status === "error" && error !== undefined ? { error } : {}), + }); } catch (error) { - throw error; + debugLogger.debug( + "Failed to process @cloudflare/ai-chat response hook:", + error, + ); + } finally { + if (state?.settled) { + this.cleanupState(state); + } } - return result; - }, - ); - this.unsubscribers.push(removeHandlers); + }; + runInstrumentation(() => prepare(event)); + let result; + try { + result = Reflect.apply(target, receiver, args); + } catch (error) { + throw error; + } + return result; + }); } private ensureEventState( @@ -487,3 +465,13 @@ function sanitizeLoggedValue( } return Object.fromEntries(entries); } + +let cloudflareAIChatInstrumentationConsumer: + | CloudflareAIChatInstrumentationConsumer + | undefined; + +export function registerCloudflareAIChatInstrumentation(): void { + cloudflareAIChatInstrumentationConsumer ??= + new CloudflareAIChatInstrumentationConsumer(); + cloudflareAIChatInstrumentationConsumer.register(); +} diff --git a/js/src/instrumentation/plugins/cloudflare-ai-chat-instrumentation.ts b/js/src/instrumentation/providers/cloudflare-ai-chat-instrumentation.ts similarity index 100% rename from js/src/instrumentation/plugins/cloudflare-ai-chat-instrumentation.ts rename to js/src/instrumentation/providers/cloudflare-ai-chat-instrumentation.ts diff --git a/js/src/instrumentation/plugins/cloudflare-ai-chat-interceptors.ts b/js/src/instrumentation/providers/cloudflare-ai-chat-interceptors.ts similarity index 100% rename from js/src/instrumentation/plugins/cloudflare-ai-chat-interceptors.ts rename to js/src/instrumentation/providers/cloudflare-ai-chat-interceptors.ts diff --git a/js/src/instrumentation/providers/cloudflare-think-context.test.ts b/js/src/instrumentation/providers/cloudflare-think-context.test.ts new file mode 100644 index 000000000..f97ba9a43 --- /dev/null +++ b/js/src/instrumentation/providers/cloudflare-think-context.test.ts @@ -0,0 +1,25 @@ +import { expect, test, vi } from "vite-plus/test"; +import { _internalGetGlobalState, NoopSpan } from "../../logger/core"; + +test("shares active Think spans between separately loaded provider consumers", async () => { + const first = await import("./cloudflare-think-context"); + vi.resetModules(); + const second = await import("./cloudflare-think-context"); + const span = new NoopSpan(); + span.spanId = "think-cross-bundle-test"; + + first.registerCloudflareThinkSpan({ + span, + startTelemetry: vi.fn(), + finishTelemetry: vi.fn(), + }); + try { + _internalGetGlobalState().contextManager.runInContext(span, () => { + expect(second.currentCloudflareThinkRun()?.span).toBe(span); + }); + second.unregisterCloudflareThinkSpan(span); + expect(first.cloudflareThinkSpanCountForTesting()).toBe(0); + } finally { + first.unregisterCloudflareThinkSpan(span); + } +}); diff --git a/js/src/instrumentation/plugins/cloudflare-think-context.ts b/js/src/instrumentation/providers/cloudflare-think-context.ts similarity index 58% rename from js/src/instrumentation/plugins/cloudflare-think-context.ts rename to js/src/instrumentation/providers/cloudflare-think-context.ts index 8d5d59ec8..d58dc5668 100644 --- a/js/src/instrumentation/plugins/cloudflare-think-context.ts +++ b/js/src/instrumentation/providers/cloudflare-think-context.ts @@ -1,4 +1,4 @@ -import { _internalGetGlobalState, type Span } from "../../logger"; +import { _internalGetGlobalState, type Span } from "../../logger/core"; export type CloudflareThinkRun = { span: Span; @@ -13,17 +13,26 @@ export type CloudflareThinkRun = { ) => void; }; -const cloudflareThinkSpans = new Map(); +const THINK_SPANS = Symbol.for("braintrust.cloudflare-think.spans.v1"); + +// The AI SDK and Think consumers may be registered by different bundled copies. +// Keep their active spans together, just like the global instrumentation registry. +function getThinkSpans(): Map { + const root = globalThis as typeof globalThis & { + [THINK_SPANS]?: Map; + }; + return (root[THINK_SPANS] ??= new Map()); +} export function registerCloudflareThinkSpan(run: CloudflareThinkRun): void { if (run.span.spanId) { - cloudflareThinkSpans.set(run.span.spanId, run); + getThinkSpans().set(run.span.spanId, run); } } export function unregisterCloudflareThinkSpan(span: Span): void { if (span.spanId) { - cloudflareThinkSpans.delete(span.spanId); + getThinkSpans().delete(span.spanId); } } @@ -32,7 +41,7 @@ export function currentCloudflareThinkRun(): CloudflareThinkRun | undefined { _internalGetGlobalState()?.contextManager.getParentSpanIds()?.spanParents ?? []; for (const parentSpanId of parentSpanIds) { - const span = cloudflareThinkSpans.get(parentSpanId); + const span = getThinkSpans().get(parentSpanId); if (span) { return span; } @@ -41,5 +50,5 @@ export function currentCloudflareThinkRun(): CloudflareThinkRun | undefined { } export function cloudflareThinkSpanCountForTesting(): number { - return cloudflareThinkSpans.size; + return getThinkSpans().size; } diff --git a/js/src/instrumentation/plugins/cloudflare-think-plugin.test.ts b/js/src/instrumentation/providers/cloudflare-think-instrumentation.test.ts similarity index 95% rename from js/src/instrumentation/plugins/cloudflare-think-plugin.test.ts rename to js/src/instrumentation/providers/cloudflare-think-instrumentation.test.ts index 1409bd790..fc236867d 100644 --- a/js/src/instrumentation/plugins/cloudflare-think-plugin.test.ts +++ b/js/src/instrumentation/providers/cloudflare-think-instrumentation.test.ts @@ -1,14 +1,23 @@ +import { resetInstrumentationForTest } from "../test-utils/reset"; +import { registerInstrumentation as registerAISDKInstrumentation } from "../registrations/aisdk"; /* eslint-disable @typescript-eslint/no-explicit-any */ -import { afterEach, beforeAll, beforeEach, describe, expect, it } from "vitest"; +import { + afterEach, + beforeAll, + beforeEach, + describe, + expect, + it, +} from "vite-plus/test"; import * as ai from "ai"; import { configureNode } from "../../node/config"; +import { _exportsForTestingOnly } from "../../logger/testing"; import { - _exportsForTestingOnly, currentSpan, initLogger, withCurrent, type TestBackgroundLogger, -} from "../../logger"; +} from "../../logger/core"; import { wrapAISDK } from "../../wrappers/ai-sdk"; import { wrapCloudflareThink } from "../../wrappers/cloudflare-think"; import { braintrustAISDKTelemetry } from "../../wrappers/ai-sdk/telemetry"; @@ -32,7 +41,7 @@ describe("Cloudflare Think instrumentation", () => { beforeEach(() => { backgroundLogger = _exportsForTestingOnly.useTestBackgroundLogger(); initLogger({ - projectName: "cloudflare-think-plugin.test.ts", + projectName: "cloudflare-think-instrumentation.test.ts", projectId: "test-project-id", }); }); @@ -336,9 +345,9 @@ describe("Cloudflare Think instrumentation", () => { ])( "traces v7 dispatchers once with aisdk=$aisdk and explicit=$explicit", async ({ aisdk, explicit }) => { - registry.disable(); + resetInstrumentationForTest(); registry.configure({ integrations: { aisdk } }); - registry.enable(); + registerAISDKInstrumentation(); try { const explicitTelemetry = explicit ? braintrustAISDKTelemetry() @@ -392,9 +401,9 @@ describe("Cloudflare Think instrumentation", () => { "doStream", ]); } finally { - registry.disable(); + resetInstrumentationForTest(); registry.configure({}); - registry.enable(); + registerAISDKInstrumentation(); } }, ); diff --git a/js/src/instrumentation/plugins/cloudflare-think-plugin.ts b/js/src/instrumentation/providers/cloudflare-think-instrumentation.ts similarity index 64% rename from js/src/instrumentation/plugins/cloudflare-think-plugin.ts rename to js/src/instrumentation/providers/cloudflare-think-instrumentation.ts index 04c27d84e..cd69a25f1 100644 --- a/js/src/instrumentation/plugins/cloudflare-think-plugin.ts +++ b/js/src/instrumentation/providers/cloudflare-think-instrumentation.ts @@ -1,11 +1,11 @@ -import { withCurrent } from "../../logger"; -import { BasePlugin } from "../core"; +import { withCurrent } from "../../logger/core"; + import { observeResult, runInstrumentation } from "../core/observe-result"; import { SpanTypeAttribute, isObject } from "../../../util/index"; import { debugLogger } from "../../debug-logger"; -import type { Span } from "../../logger"; -import { _internalGetGlobalState, startSpan } from "../../logger"; +import type { Span } from "../../logger/core"; +import { _internalGetGlobalState, startSpan } from "../../logger/core"; import { INSTRUMENTATION_NAMES, withSpanInstrumentationName, @@ -27,7 +27,7 @@ import { patchAISDKStreamingResult, patchAISDKV7TelemetryDispatcher, prepareAISDKAgentCallInput, -} from "./ai-sdk-plugin"; +} from "./ai-sdk-instrumentation"; import { cloudflareThinkChannels } from "./cloudflare-think-interceptors"; import { registerCloudflareThinkSpan, @@ -45,60 +45,46 @@ type ThinkRunState = { startTime: number; }; -type AISDKStreamEvent = - | ChannelMessage - | ChannelMessage; +type AISDKStreamEvent = ChannelMessage< + typeof aiSDKChannels.streamTextSync | typeof aiSDKChannels.streamText +>; const THINK_STATE_ID = Symbol.for("braintrust.cloudflare-think.state-id"); -export class CloudflareThinkPlugin extends BasePlugin { +class CloudflareThinkInstrumentationConsumer { private readonly statesBySpanId = new Map(); - protected onEnable(): void { + public register(): void { const telemetry = braintrustAISDKTelemetry(); this.subscribeToThinkRuns(); this.subscribeToAISDKStreamTextSync(); this.subscribeToAISDKStreamTextAsync(); - this.unsubscribers.push( - aiSDKChannels.v7CreateTelemetryDispatcher.intercept( - (target, receiver, args) => { - const dispatcher = Reflect.apply(target, receiver, args); - if (this.currentState() && args[0]?.telemetry?.isEnabled !== false) { - // Think must also work when the standalone AI SDK integration is - // disabled. Dispatcher patching is shared and idempotent. - runInstrumentation(() => - patchAISDKV7TelemetryDispatcher( - dispatcher, - telemetry, - args[0]?.telemetry, - ), - ); - } - return dispatcher; - }, - ), - aiSDKChannels.streamTextV7.intercept((target, receiver, args) => { - const state = this.currentState(); - const result = Reflect.apply(target, receiver, args); - const telemetry = args[0]?.telemetry ?? args[0]?.experimental_telemetry; - if (state && telemetry?.isEnabled !== false) { - state.aiResultPatched = true; + aiSDKChannels.v7CreateTelemetryDispatcher.intercept( + (target, receiver, args) => { + const dispatcher = Reflect.apply(target, receiver, args); + if (this.currentState() && args[0]?.telemetry?.isEnabled !== false) { + // Think must also work when the standalone AI SDK integration is + // disabled. Dispatcher patching is shared and idempotent. + runInstrumentation(() => + patchAISDKV7TelemetryDispatcher( + dispatcher, + telemetry, + args[0]?.telemetry, + ), + ); } - return result; - }), + return dispatcher; + }, ); - } - - protected onDisable(): void { - for (const unsubscribe of this.unsubscribers) { - unsubscribe(); - } - this.unsubscribers = []; - - for (const state of this.statesBySpanId.values()) { - this.finishState(state); - } - this.statesBySpanId.clear(); + aiSDKChannels.streamTextV7.intercept((target, receiver, args) => { + const state = this.currentState(); + const result = Reflect.apply(target, receiver, args); + const telemetry = args[0]?.telemetry ?? args[0]?.experimental_telemetry; + if (state && telemetry?.isEnabled !== false) { + state.aiResultPatched = true; + } + return result; + }); } private subscribeToThinkRuns(): void { @@ -183,140 +169,37 @@ export class CloudflareThinkPlugin extends BasePlugin { return runState; }; - const removeHandlers = channel.intercept( - (target, receiver, args, additional) => { - const event = { - ...additional, - arguments: args, - self: receiver, - } as ChannelMessage; - const prepare = ( - event: ChannelMessage< - typeof cloudflareThinkChannels.runInferenceLoop - >, - ) => { - ensureState(event); - }; - const resolved = ( - event: ChannelMessage< - typeof cloudflareThinkChannels.runInferenceLoop - >, - ) => { - const runState = states.get(event); - states.delete(event); - if (!runState || runState.finalized || runState.aiResultPatched) { - return; - } - this.finishState(runState, undefined, event.result); - }; - const failed = ( - event: ChannelMessage< - typeof cloudflareThinkChannels.runInferenceLoop - >, - ) => { - const runState = states.get(event); - states.delete(event); - if (runState) { - this.finishState(runState, event.error); - } - }; - const invoke = () => { - runInstrumentation(() => prepare(event)); - let result; - try { - result = Reflect.apply(target, receiver, args); - } catch (error) { - Object.assign(event, { error }); - runInstrumentation(() => failed(event)); - throw error; - } - - return observeResult( - result, - (value) => { - Object.assign(event, { result: value }); - resolved(event); - }, - (error) => { - Object.assign(event, { error }); - failed(event); - }, - ); - }; - const spanState = runInstrumentation(() => ensureState(event)); - return spanState ? withCurrent(spanState.span, invoke) : invoke(); - }, - ); - this.unsubscribers.push(removeHandlers); - } - - private subscribeToAISDKStreamTextSync(): void { - const channel = aiSDKChannels.streamTextSync; - - const removeHandlers = channel.intercept( - (target, receiver, args, additional) => { - const event = { - ...additional, - arguments: args, - self: receiver, - } as ChannelMessage; - const prepare = ( - event: ChannelMessage, - ) => { - this.startAISDKStream(event); - }; - const returned = ( - event: ChannelMessage, - ) => { - this.endAISDKStream(event); - }; - const failed = ( - event: ChannelMessage, - ) => { - this.errorAISDKStream(event); - }; - runInstrumentation(() => prepare(event)); - let result; - try { - result = Reflect.apply(target, receiver, args); - } catch (error) { - Object.assign(event, { error }); - runInstrumentation(() => failed(event)); - throw error; + channel.intercept((target, receiver, args, additional) => { + const event = { + ...additional, + arguments: args, + self: receiver, + } as ChannelMessage; + const prepare = ( + event: ChannelMessage, + ) => { + ensureState(event); + }; + const resolved = ( + event: ChannelMessage, + ) => { + const runState = states.get(event); + states.delete(event); + if (!runState || runState.finalized || runState.aiResultPatched) { + return; } - Object.assign(event, { result }); - runInstrumentation(() => returned(event)); - return result; - }, - ); - this.unsubscribers.push(removeHandlers); - } - - private subscribeToAISDKStreamTextAsync(): void { - const channel = aiSDKChannels.streamText; - - const removeHandlers = channel.intercept( - (target, receiver, args, additional) => { - const event = { - ...additional, - arguments: args, - self: receiver, - } as ChannelMessage; - const prepare = ( - event: ChannelMessage, - ) => { - this.startAISDKStream(event); - }; - const resolved = ( - event: ChannelMessage, - ) => { - this.endAISDKStream(event); - }; - const failed = ( - event: ChannelMessage, - ) => { - this.errorAISDKStream(event); - }; + this.finishState(runState, undefined, event.result); + }; + const failed = ( + event: ChannelMessage, + ) => { + const runState = states.get(event); + states.delete(event); + if (runState) { + this.finishState(runState, event.error); + } + }; + const invoke = () => { runInstrumentation(() => prepare(event)); let result; try { @@ -326,6 +209,7 @@ export class CloudflareThinkPlugin extends BasePlugin { runInstrumentation(() => failed(event)); throw error; } + return observeResult( result, (value) => { @@ -337,11 +221,96 @@ export class CloudflareThinkPlugin extends BasePlugin { failed(event); }, ); - }, - ); - this.unsubscribers.push(removeHandlers); + }; + const spanState = runInstrumentation(() => ensureState(event)); + return spanState ? withCurrent(spanState.span, invoke) : invoke(); + }); + } + + private subscribeToAISDKStreamTextSync(): void { + const channel = aiSDKChannels.streamTextSync; + + channel.intercept((target, receiver, args, additional) => { + const event = { + ...additional, + arguments: args, + self: receiver, + } as ChannelMessage; + const prepare = ( + event: ChannelMessage, + ) => { + this.startAISDKStream(event); + }; + const returned = ( + event: ChannelMessage, + ) => { + this.endAISDKStream(event); + }; + const failed = ( + event: ChannelMessage, + ) => { + this.errorAISDKStream(event); + }; + runInstrumentation(() => prepare(event)); + let result; + try { + result = Reflect.apply(target, receiver, args); + } catch (error) { + Object.assign(event, { error }); + runInstrumentation(() => failed(event)); + throw error; + } + Object.assign(event, { result }); + runInstrumentation(() => returned(event)); + return result; + }); } + private subscribeToAISDKStreamTextAsync(): void { + const channel = aiSDKChannels.streamText; + channel.intercept((target, receiver, args, additional) => { + const event = { + ...additional, + arguments: args, + self: receiver, + } as ChannelMessage; + const prepare = ( + event: ChannelMessage, + ) => { + this.startAISDKStream(event); + }; + const resolved = ( + event: ChannelMessage, + ) => { + this.endAISDKStream(event); + }; + const failed = ( + event: ChannelMessage, + ) => { + this.errorAISDKStream(event); + }; + runInstrumentation(() => prepare(event)); + let result; + try { + result = Reflect.apply(target, receiver, args); + } catch (error) { + Object.assign(event, { error }); + runInstrumentation(() => failed(event)); + throw error; + } + return observeResult( + result, + (value) => { + Object.assign(event, { result: value }); + resolved(event); + }, + (error) => { + Object.assign(event, { error }); + failed(event); + }, + ); + }); + } private startAISDKStream(event: AISDKStreamEvent): void { const runState = this.currentState(); if (!runState || runState.finalized) { @@ -515,3 +484,13 @@ function extractThinkTaskInput(input: unknown): unknown { } return input; } + +let cloudflareThinkInstrumentationConsumer: + | CloudflareThinkInstrumentationConsumer + | undefined; + +export function registerCloudflareThinkInstrumentation(): void { + cloudflareThinkInstrumentationConsumer ??= + new CloudflareThinkInstrumentationConsumer(); + cloudflareThinkInstrumentationConsumer.register(); +} diff --git a/js/src/instrumentation/plugins/cloudflare-think-interceptors.ts b/js/src/instrumentation/providers/cloudflare-think-interceptors.ts similarity index 100% rename from js/src/instrumentation/plugins/cloudflare-think-interceptors.ts rename to js/src/instrumentation/providers/cloudflare-think-interceptors.ts diff --git a/js/src/instrumentation/plugins/cohere-plugin.test.ts b/js/src/instrumentation/providers/cohere-instrumentation.test.ts similarity index 98% rename from js/src/instrumentation/plugins/cohere-plugin.test.ts rename to js/src/instrumentation/providers/cohere-instrumentation.test.ts index c2ea4fbf1..e36fbd518 100644 --- a/js/src/instrumentation/plugins/cohere-plugin.test.ts +++ b/js/src/instrumentation/providers/cohere-instrumentation.test.ts @@ -1,9 +1,9 @@ -import { describe, expect, it } from "vitest"; +import { describe, expect, it } from "vite-plus/test"; import { aggregateCohereChatStreamChunks, extractCohereResponseMetadata, parseCohereMetricsFromUsage, -} from "./cohere-plugin"; +} from "./cohere-instrumentation"; describe("parseCohereMetricsFromUsage", () => { it("parses usage and meta token counters", () => { diff --git a/js/src/instrumentation/plugins/cohere-plugin.ts b/js/src/instrumentation/providers/cohere-instrumentation.ts similarity index 84% rename from js/src/instrumentation/plugins/cohere-plugin.ts rename to js/src/instrumentation/providers/cohere-instrumentation.ts index 502ae60cc..79c32eaa5 100644 --- a/js/src/instrumentation/plugins/cohere-plugin.ts +++ b/js/src/instrumentation/providers/cohere-instrumentation.ts @@ -9,115 +9,98 @@ import type { CohereUsageLike, } from "../../vendor-sdk-types/cohere"; import { processInputAttachments } from "../../wrappers/attachment-utils"; -import { BasePlugin } from "../core"; -import { - traceAsyncCall, - traceStreamingCall, - unsubscribeAll, -} from "../core/tracing"; + +import { traceAsyncCall, traceStreamingCall } from "../core/tracing"; import { cohereChannels } from "./cohere-interceptors"; -export class CoherePlugin extends BasePlugin { - protected onEnable(): void { +class CohereInstrumentationConsumer { + public register(): void { this.subscribeToCohereChannels(); } - protected onDisable(): void { - this.unsubscribers = unsubscribeAll(this.unsubscribers); - } - private subscribeToCohereChannels(): void { - this.unsubscribers.push( - cohereChannels.chat.intercept((target, receiver, args, additional) => - traceStreamingCall( - () => Reflect.apply(target, receiver, args), - { ...additional, arguments: args, self: receiver }, - { - instrumentationName: INSTRUMENTATION_NAMES.COHERE, - name: "cohere.chat", - type: SpanTypeAttribute.LLM, - extractInput: extractChatInputWithMetadata, - extractOutput: (result) => extractCohereChatOutput(result), - extractMetadata: (result) => extractCohereResponseMetadata(result), - extractMetrics: (result, startTime) => { - const metrics = parseCohereMetricsFromUsage(result); - if (startTime) { - metrics.time_to_first_token = - getCurrentUnixTimestamp() - startTime; - } - return metrics; - }, + cohereChannels.chat.intercept((target, receiver, args, additional) => + traceStreamingCall( + () => Reflect.apply(target, receiver, args), + { ...additional, arguments: args, self: receiver }, + { + instrumentationName: INSTRUMENTATION_NAMES.COHERE, + name: "cohere.chat", + type: SpanTypeAttribute.LLM, + extractInput: extractChatInputWithMetadata, + extractOutput: (result) => extractCohereChatOutput(result), + extractMetadata: (result) => extractCohereResponseMetadata(result), + extractMetrics: (result, startTime) => { + const metrics = parseCohereMetricsFromUsage(result); + if (startTime) { + metrics.time_to_first_token = + getCurrentUnixTimestamp() - startTime; + } + return metrics; }, - ), + }, ), ); - this.unsubscribers.push( - cohereChannels.chatStream.intercept( - (target, receiver, args, additional) => - traceStreamingCall( - () => Reflect.apply(target, receiver, args), - { ...additional, arguments: args, self: receiver }, - { - instrumentationName: INSTRUMENTATION_NAMES.COHERE, - name: "cohere.chatStream", - type: SpanTypeAttribute.LLM, - extractInput: extractChatInputWithMetadata, - extractOutput: () => undefined, - extractMetadata: () => undefined, - extractMetrics: () => ({}), - aggregateChunks: aggregateCohereChatStreamChunks, - }, - ), + cohereChannels.chatStream.intercept((target, receiver, args, additional) => + traceStreamingCall( + () => Reflect.apply(target, receiver, args), + { ...additional, arguments: args, self: receiver }, + { + instrumentationName: INSTRUMENTATION_NAMES.COHERE, + name: "cohere.chatStream", + type: SpanTypeAttribute.LLM, + extractInput: extractChatInputWithMetadata, + extractOutput: () => undefined, + extractMetadata: () => undefined, + extractMetrics: () => ({}), + aggregateChunks: aggregateCohereChatStreamChunks, + }, ), ); - this.unsubscribers.push( - cohereChannels.embed.intercept((target, receiver, args, additional) => - traceAsyncCall( - () => Reflect.apply(target, receiver, args), - { ...additional, arguments: args, self: receiver }, - { - instrumentationName: INSTRUMENTATION_NAMES.COHERE, - name: "cohere.embed", - type: SpanTypeAttribute.LLM, - extractInput: extractEmbedInputWithMetadata, - extractOutput: extractCohereEmbeddingOutput, - extractMetadata: (result) => extractCohereResponseMetadata(result), - extractMetrics: (result) => parseCohereMetricsFromUsage(result), - }, - ), + cohereChannels.embed.intercept((target, receiver, args, additional) => + traceAsyncCall( + () => Reflect.apply(target, receiver, args), + { ...additional, arguments: args, self: receiver }, + { + instrumentationName: INSTRUMENTATION_NAMES.COHERE, + name: "cohere.embed", + type: SpanTypeAttribute.LLM, + extractInput: extractEmbedInputWithMetadata, + extractOutput: extractCohereEmbeddingOutput, + extractMetadata: (result) => extractCohereResponseMetadata(result), + extractMetrics: (result) => parseCohereMetricsFromUsage(result), + }, ), ); - this.unsubscribers.push( - cohereChannels.rerank.intercept((target, receiver, args, additional) => - traceAsyncCall( - () => Reflect.apply(target, receiver, args), - { ...additional, arguments: args, self: receiver }, - { - instrumentationName: INSTRUMENTATION_NAMES.COHERE, - name: "cohere.rerank", - type: SpanTypeAttribute.LLM, - extractInput: extractRerankInputWithMetadata, - extractOutput: (result) => { - if (!isObject(result) || !Array.isArray(result.results)) { - return undefined; - } - - return result.results.slice(0, 100).map((item) => ({ - index: isObject(item) ? item.index : undefined, - relevance_score: isObject(item) - ? ((typeof item.relevanceScore === "number" - ? item.relevanceScore - : item.relevance_score) ?? null) - : null, - })); - }, - extractMetadata: (result) => extractCohereResponseMetadata(result), - extractMetrics: (result) => parseCohereMetricsFromUsage(result), + cohereChannels.rerank.intercept((target, receiver, args, additional) => + traceAsyncCall( + () => Reflect.apply(target, receiver, args), + { ...additional, arguments: args, self: receiver }, + { + instrumentationName: INSTRUMENTATION_NAMES.COHERE, + name: "cohere.rerank", + type: SpanTypeAttribute.LLM, + extractInput: extractRerankInputWithMetadata, + extractOutput: (result) => { + if (!isObject(result) || !Array.isArray(result.results)) { + return undefined; + } + + return result.results.slice(0, 100).map((item) => ({ + index: isObject(item) ? item.index : undefined, + relevance_score: isObject(item) + ? ((typeof item.relevanceScore === "number" + ? item.relevanceScore + : item.relevance_score) ?? null) + : null, + })); }, - ), + extractMetadata: (result) => extractCohereResponseMetadata(result), + extractMetrics: (result) => parseCohereMetricsFromUsage(result), + }, ), ); } @@ -980,3 +963,10 @@ export function aggregateCohereChatStreamChunks( output, }; } + +let cohereInstrumentationConsumer: CohereInstrumentationConsumer | undefined; + +export function registerCohereInstrumentation(): void { + cohereInstrumentationConsumer ??= new CohereInstrumentationConsumer(); + cohereInstrumentationConsumer.register(); +} diff --git a/js/src/instrumentation/plugins/cohere-interceptors.ts b/js/src/instrumentation/providers/cohere-interceptors.ts similarity index 100% rename from js/src/instrumentation/plugins/cohere-interceptors.ts rename to js/src/instrumentation/providers/cohere-interceptors.ts diff --git a/js/src/instrumentation/plugins/cursor-sdk-plugin.test.ts b/js/src/instrumentation/providers/cursor-sdk-instrumentation.test.ts similarity index 92% rename from js/src/instrumentation/plugins/cursor-sdk-plugin.test.ts rename to js/src/instrumentation/providers/cursor-sdk-instrumentation.test.ts index 7005d5b97..b8106826f 100644 --- a/js/src/instrumentation/plugins/cursor-sdk-plugin.test.ts +++ b/js/src/instrumentation/providers/cursor-sdk-instrumentation.test.ts @@ -1,4 +1,11 @@ -import { afterEach, beforeEach, describe, expect, it, vi } from "vitest"; +import { + afterEach, + beforeEach, + describe, + expect, + it, + vi, +} from "vite-plus/test"; import { newGlobalInvocationHook } from "../../global-instrumentation-hooks"; import { invocationController } from "../test-utils/invocation"; vi.mock("../../global-instrumentation-hooks", async (importOriginal) => ({ @@ -12,21 +19,22 @@ const { mockStartSpan } = vi.hoisted(() => ({ mockStartSpan: vi.fn(), })); -vi.mock("../../isomorph", () => ({ +vi.mock("#braintrust-platform", () => ({ default: {}, })); -vi.mock("../../logger", () => ({ +vi.mock("../../logger/core", () => ({ startSpan: (...args: unknown[]) => mockStartSpan(...args), + _internalStartSpan: (...args: unknown[]) => mockStartSpan(...args), })); -import { CursorSDKPlugin } from "./cursor-sdk-plugin"; +import { registerCursorSDKInstrumentation } from "./cursor-sdk-instrumentation"; const mockNewInvocationHook = newGlobalInvocationHook as ReturnType< typeof vi.fn >; -describe("CursorSDKPlugin", () => { +describe("registerCursorSDKInstrumentation", () => { let handlersByName: Map; let spans: Array<{ end: ReturnType; @@ -66,9 +74,7 @@ describe("CursorSDKPlugin", () => { }); it("subscribes to Cursor SDK channels", () => { - const plugin = new CursorSDKPlugin(); - - plugin.enable(); + registerCursorSDKInstrumentation(); expect(handlersByName.has("orchestrion:@cursor/sdk:Agent.create")).toBe( true, @@ -83,8 +89,7 @@ describe("CursorSDKPlugin", () => { }); it("patches agents returned by Agent.create and traces send/wait", async () => { - const plugin = new CursorSDKPlugin(); - plugin.enable(); + registerCursorSDKInstrumentation(); const createHandlers = handlersByName.get( "orchestrion:@cursor/sdk:Agent.create", @@ -141,8 +146,7 @@ describe("CursorSDKPlugin", () => { }); it("captures stream tool calls and usage", async () => { - const plugin = new CursorSDKPlugin(); - plugin.enable(); + registerCursorSDKInstrumentation(); const sendHandlers = handlersByName.get( "orchestrion:@cursor/sdk:agent.send", @@ -224,8 +228,7 @@ describe("CursorSDKPlugin", () => { }); it("traces Agent.prompt without a nested send span", () => { - const plugin = new CursorSDKPlugin(); - plugin.enable(); + registerCursorSDKInstrumentation(); const promptHandlers = handlersByName.get( "orchestrion:@cursor/sdk:Agent.prompt", diff --git a/js/src/instrumentation/plugins/cursor-sdk-plugin.ts b/js/src/instrumentation/providers/cursor-sdk-instrumentation.ts similarity index 79% rename from js/src/instrumentation/plugins/cursor-sdk-plugin.ts rename to js/src/instrumentation/providers/cursor-sdk-instrumentation.ts index c4e70c17d..5739596a0 100644 --- a/js/src/instrumentation/plugins/cursor-sdk-plugin.ts +++ b/js/src/instrumentation/providers/cursor-sdk-instrumentation.ts @@ -1,10 +1,10 @@ -import { BasePlugin, toLoggedError } from "../core"; +import { toLoggedError } from "../core"; import { observeResult, runInstrumentation } from "../core/observe-result"; import { SpanTypeAttribute } from "../../../util/index"; import { debugLogger } from "../../debug-logger"; -import type { Span } from "../../logger"; -import { startSpan as startBaseSpan } from "../../logger"; +import type { Span } from "../../logger/core"; +import { _internalStartSpan as startBaseSpan } from "../../logger/core"; import { INSTRUMENTATION_NAMES, withSpanInstrumentationName, @@ -65,23 +65,15 @@ type PromptState = { startTime: number; }; -export class CursorSDKPlugin extends BasePlugin { +class CursorSDKInstrumentationConsumer { private promptDepth = 0; - protected onEnable(): void { + public register(): void { this.subscribeToAgentFactories(); this.subscribeToPrompt(); this.subscribeToSend(); } - protected onDisable(): void { - for (const unsubscribe of this.unsubscribers) { - unsubscribe(); - } - this.unsubscribers = []; - this.promptDepth = 0; - } - private subscribeToAgentFactories(): void { this.subscribeToAgentFactory(cursorSDKChannels.create); this.subscribeToAgentFactory(cursorSDKChannels.resume); @@ -90,13 +82,11 @@ export class CursorSDKPlugin extends BasePlugin { private subscribeToAgentFactory( channel: typeof cursorSDKChannels.create | typeof cursorSDKChannels.resume, ): void { - this.unsubscribers.push( - channel.intercept((target, receiver, args) => - observeResult( - Reflect.apply(target, receiver, args), - patchCursorAgentInPlace, - () => {}, - ), + channel.intercept((target, receiver, args) => + observeResult( + Reflect.apply(target, receiver, args), + patchCursorAgentInPlace, + () => {}, ), ); } @@ -105,223 +95,215 @@ export class CursorSDKPlugin extends BasePlugin { const channel = cursorSDKChannels.prompt; const states = new WeakMap(); - const removeHandlers = channel.intercept( - (target, receiver, args, additional) => { - const event = { - ...additional, - arguments: args, - self: receiver, - } as ChannelMessage; - const prepare = ( - event: ChannelMessage, - ) => { - this.promptDepth += 1; - const message = event.arguments[0]; - const options = event.arguments[1]; - const metadata = { - ...extractAgentOptionsMetadata(options), - "cursor_sdk.operation": "Agent.prompt", - provider: "cursor", - ...(event.moduleVersion - ? { "cursor_sdk.version": event.moduleVersion } - : {}), - }; - const span = startBaseSpan( - withSpanInstrumentationName( - { - name: "Cursor Agent", - spanAttributes: { type: SpanTypeAttribute.TASK }, - }, - INSTRUMENTATION_NAMES.CURSOR_SDK, - ), - ); - const startTime = getCurrentUnixTimestamp(); - safeLog(span, { - input: sanitizeUserMessage(message), - metadata, - }); - states.set(event, { metadata, span, startTime }); - }; - const resolved = ( - event: ChannelMessage, - ) => { - this.promptDepth = Math.max(0, this.promptDepth - 1); - const state = states.get(event); - if (!state) { - return; - } - try { - safeLog(state.span, { - metadata: { - ...state.metadata, - ...extractRunResultMetadata(event.result), - }, - metrics: buildDurationMetrics(state.startTime), - output: event.result?.result ?? event.result, - }); - } finally { - state.span.end(); - states.delete(event); - } + channel.intercept((target, receiver, args, additional) => { + const event = { + ...additional, + arguments: args, + self: receiver, + } as ChannelMessage; + const prepare = ( + event: ChannelMessage, + ) => { + this.promptDepth += 1; + const message = event.arguments[0]; + const options = event.arguments[1]; + const metadata = { + ...extractAgentOptionsMetadata(options), + "cursor_sdk.operation": "Agent.prompt", + provider: "cursor", + ...(event.moduleVersion + ? { "cursor_sdk.version": event.moduleVersion } + : {}), }; - const failed = ( - event: ChannelMessage, - ) => { - this.promptDepth = Math.max(0, this.promptDepth - 1); - const state = states.get(event); - if (!state || !event.error) { - return; - } - safeLog(state.span, { error: event.error.message }); + const span = startBaseSpan( + withSpanInstrumentationName( + { + name: "Cursor Agent", + spanAttributes: { type: SpanTypeAttribute.TASK }, + }, + INSTRUMENTATION_NAMES.CURSOR_SDK, + ), + ); + const startTime = getCurrentUnixTimestamp(); + safeLog(span, { + input: sanitizeUserMessage(message), + metadata, + }); + states.set(event, { metadata, span, startTime }); + }; + const resolved = ( + event: ChannelMessage, + ) => { + this.promptDepth = Math.max(0, this.promptDepth - 1); + const state = states.get(event); + if (!state) { + return; + } + try { + safeLog(state.span, { + metadata: { + ...state.metadata, + ...extractRunResultMetadata(event.result), + }, + metrics: buildDurationMetrics(state.startTime), + output: event.result?.result ?? event.result, + }); + } finally { state.span.end(); states.delete(event); - }; - runInstrumentation(() => prepare(event)); - let result; - try { - result = Reflect.apply(target, receiver, args); - } catch (error) { - Object.assign(event, { error }); - runInstrumentation(() => failed(event)); - throw error; } - return observeResult( - result, - (value) => { - Object.assign(event, { result: value }); - resolved(event); - }, - (error) => { - Object.assign(event, { error }); - failed(event); - }, - ); - }, - ); - this.unsubscribers.push(removeHandlers); + }; + const failed = ( + event: ChannelMessage, + ) => { + this.promptDepth = Math.max(0, this.promptDepth - 1); + const state = states.get(event); + if (!state || !event.error) { + return; + } + safeLog(state.span, { error: event.error.message }); + state.span.end(); + states.delete(event); + }; + runInstrumentation(() => prepare(event)); + let result; + try { + result = Reflect.apply(target, receiver, args); + } catch (error) { + Object.assign(event, { error }); + runInstrumentation(() => failed(event)); + throw error; + } + return observeResult( + result, + (value) => { + Object.assign(event, { result: value }); + resolved(event); + }, + (error) => { + Object.assign(event, { error }); + failed(event); + }, + ); + }); } private subscribeToSend(): void { const channel = cursorSDKChannels.send; const states = new WeakMap(); - const removeHandlers = channel.intercept( - (target, receiver, args, additional) => { - const event = { - ...additional, - arguments: args, - self: receiver, - } as ChannelMessage; - const prepare = ( - event: ChannelMessage, - ) => { - if (this.promptDepth > 0) { - return; - } - - const message = event.arguments[0]; - const sendOptions = event.arguments[1]; - const agent = event.agent; - const metadata = { - ...extractSendMetadata(sendOptions), - ...(agent ? extractAgentMetadata(agent) : {}), - "cursor_sdk.operation": "agent.send", - provider: "cursor", - ...(event.moduleVersion - ? { "cursor_sdk.version": event.moduleVersion } - : {}), - }; - const span = startBaseSpan( - withSpanInstrumentationName( - { - name: "Cursor Agent", - spanAttributes: { type: SpanTypeAttribute.TASK }, - }, - INSTRUMENTATION_NAMES.CURSOR_SDK, - ), - ); - const startTime = getCurrentUnixTimestamp(); - safeLog(span, { - input: sanitizeUserMessage(message), - metadata, - }); + channel.intercept((target, receiver, args, additional) => { + const event = { + ...additional, + arguments: args, + self: receiver, + } as ChannelMessage; + const prepare = ( + event: ChannelMessage, + ) => { + if (this.promptDepth > 0) { + return; + } - const state: CursorRunState = { - activeToolSpans: new Map(), - agent, - conversationText: [], - deltaText: [], - finalized: false, - input: message, - metadata, - metrics: {}, - span, - startTime, - streamMessages: [], - streamText: [], - stepText: [], - taskText: [], - }; - - if (hasCursorCallbacks(sendOptions)) { - event.arguments[1] = wrapSendOptionsCallbacks(sendOptions, state); - } - states.set(event, state); + const message = event.arguments[0]; + const sendOptions = event.arguments[1]; + const agent = event.agent; + const metadata = { + ...extractSendMetadata(sendOptions), + ...(agent ? extractAgentMetadata(agent) : {}), + "cursor_sdk.operation": "agent.send", + provider: "cursor", + ...(event.moduleVersion + ? { "cursor_sdk.version": event.moduleVersion } + : {}), }; - const resolved = ( - event: ChannelMessage, - ) => { - const state = states.get(event); - if (!state) { - return; - } + const span = startBaseSpan( + withSpanInstrumentationName( + { + name: "Cursor Agent", + spanAttributes: { type: SpanTypeAttribute.TASK }, + }, + INSTRUMENTATION_NAMES.CURSOR_SDK, + ), + ); + const startTime = getCurrentUnixTimestamp(); + safeLog(span, { + input: sanitizeUserMessage(message), + metadata, + }); - if (!event.result) { - return; - } - state.run = event.result; - state.metadata = { - ...state.metadata, - ...extractRunMetadata(event.result), - }; - patchCursorRun(event.result, state); + const state: CursorRunState = { + activeToolSpans: new Map(), + agent, + conversationText: [], + deltaText: [], + finalized: false, + input: message, + metadata, + metrics: {}, + span, + startTime, + streamMessages: [], + streamText: [], + stepText: [], + taskText: [], }; - const failed = ( - event: ChannelMessage, - ) => { - const state = states.get(event); - if (!state || !event.error) { - return; - } - safeLog(state.span, { error: event.error.message }); - endOpenToolSpans(state, event.error.message); - state.span.end(); - state.finalized = true; - states.delete(event); + + if (hasCursorCallbacks(sendOptions)) { + event.arguments[1] = wrapSendOptionsCallbacks(sendOptions, state); + } + states.set(event, state); + }; + const resolved = ( + event: ChannelMessage, + ) => { + const state = states.get(event); + if (!state) { + return; + } + + if (!event.result) { + return; + } + state.run = event.result; + state.metadata = { + ...state.metadata, + ...extractRunMetadata(event.result), }; - runInstrumentation(() => prepare(event)); - let result; - try { - result = Reflect.apply(target, receiver, args); - } catch (error) { - Object.assign(event, { error }); - runInstrumentation(() => failed(event)); - throw error; + patchCursorRun(event.result, state); + }; + const failed = (event: ChannelMessage) => { + const state = states.get(event); + if (!state || !event.error) { + return; } - return observeResult( - result, - (value) => { - Object.assign(event, { result: value }); - resolved(event); - }, - (error) => { - Object.assign(event, { error }); - failed(event); - }, - ); - }, - ); - this.unsubscribers.push(removeHandlers); + safeLog(state.span, { error: event.error.message }); + endOpenToolSpans(state, event.error.message); + state.span.end(); + state.finalized = true; + states.delete(event); + }; + runInstrumentation(() => prepare(event)); + let result; + try { + result = Reflect.apply(target, receiver, args); + } catch (error) { + Object.assign(event, { error }); + runInstrumentation(() => failed(event)); + throw error; + } + return observeResult( + result, + (value) => { + Object.assign(event, { result: value }); + resolved(event); + }, + (error) => { + Object.assign(event, { error }); + failed(event); + }, + ); + }); } } @@ -1235,3 +1217,12 @@ function cleanMetrics(metrics: Record): Record { } return cleaned; } + +let cursorSDKInstrumentationConsumer: + | CursorSDKInstrumentationConsumer + | undefined; + +export function registerCursorSDKInstrumentation(): void { + cursorSDKInstrumentationConsumer ??= new CursorSDKInstrumentationConsumer(); + cursorSDKInstrumentationConsumer.register(); +} diff --git a/js/src/instrumentation/plugins/cursor-sdk-interceptors.ts b/js/src/instrumentation/providers/cursor-sdk-interceptors.ts similarity index 100% rename from js/src/instrumentation/plugins/cursor-sdk-interceptors.ts rename to js/src/instrumentation/providers/cursor-sdk-interceptors.ts diff --git a/js/src/instrumentation/plugins/elevenlabs-plugin.test.ts b/js/src/instrumentation/providers/elevenlabs-instrumentation.test.ts similarity index 78% rename from js/src/instrumentation/plugins/elevenlabs-plugin.test.ts rename to js/src/instrumentation/providers/elevenlabs-instrumentation.test.ts index 788a8dd8e..6ea62773f 100644 --- a/js/src/instrumentation/plugins/elevenlabs-plugin.test.ts +++ b/js/src/instrumentation/providers/elevenlabs-instrumentation.test.ts @@ -1,16 +1,26 @@ -import { afterEach, beforeAll, beforeEach, describe, expect, it } from "vitest"; +import { registerInstrumentation } from "../registrations/elevenlabs"; +import { + afterEach, + beforeAll, + beforeEach, + describe, + expect, + it, +} from "vite-plus/test"; import { Readable } from "node:stream"; import { ReadableStream, WritableStream, TransformStream, } from "node:stream/web"; -import { Attachment, _exportsForTestingOnly, initLogger } from "../../logger"; +import { Attachment, initLogger } from "../../logger/core"; +import { _exportsForTestingOnly } from "../../logger/testing"; import { configureNode } from "../../node/config"; import { elevenLabsChannels } from "./elevenlabs-interceptors"; import { wrapElevenLabs } from "../../wrappers/elevenlabs"; configureNode(); +registerInstrumentation(); const request = { text: "Hello", modelId: "eleven_flash_v2_5" }; describe("ElevenLabs instrumentation", () => { @@ -21,6 +31,7 @@ describe("ElevenLabs instrumentation", () => { beforeEach(() => { logger = _exportsForTestingOnly.useTestBackgroundLogger(); initLogger({ + autoCaptureAttachments: true, projectName: "tmp-luca-elevenlabs-unit", projectId: "test-project-id", }); @@ -29,44 +40,56 @@ describe("ElevenLabs instrumentation", () => { _exportsForTestingOnly.clearTestBackgroundLogger(); }); - it("keeps promise helpers and stream identity, and copies bytes before the caller can mutate them", async () => { - const bytes = new Uint8Array([0, 1, 2, 3]); - const stream = new ReadableStream({ - start(controller) { - controller.enqueue(bytes.subarray(1, 3)); - controller.close(); - }, - }); - const promise = Object.assign(Promise.resolve(stream), { - withRawResponse: () => - Promise.resolve({ - data: stream, - rawResponse: { - headers: new Headers({ "content-type": "audio/mpeg" }), - }, - }), - }); - const result = elevenLabsChannels.convert.invoke( - () => promise, - undefined, - ["voice", request], - {}, - ); - expect(result).toBe(promise); - expect((await result.withRawResponse()).data).toBe(stream); - const reader = stream.getReader(); - expect((await reader.read()).value).toEqual(new Uint8Array([1, 2])); - bytes.fill(9); - await reader.read(); - const spans = (await logger.drain()) as Record[]; - const output = spans.find((row: any) => row.output)?.output as any; - const attachment = output.content[0].file.file_data as Attachment; - expect(attachment).toBeInstanceOf(Attachment); - expect(output.content[0].file.byte_size).toBe(2); - expect( - new Uint8Array(await (await attachment.data()).arrayBuffer()), - ).toEqual(new Uint8Array([1, 2])); - }); + it.each([false, true])( + "keeps promise helpers and stream identity with capture=%s", + async (captureAttachments) => { + initLogger({ + projectName: "tmp-luca-elevenlabs-unit", + projectId: "test-project-id", + autoCaptureAttachments: captureAttachments, + }); + const bytes = new Uint8Array([0, 1, 2, 3]); + const stream = new ReadableStream({ + start(controller) { + controller.enqueue(bytes.subarray(1, 3)); + controller.close(); + }, + }); + const promise = Object.assign(Promise.resolve(stream), { + withRawResponse: () => + Promise.resolve({ + data: stream, + rawResponse: { + headers: new Headers({ "content-type": "audio/mpeg" }), + }, + }), + }); + const result = elevenLabsChannels.convert.invoke( + () => promise, + undefined, + ["voice", request], + {}, + ); + expect(result).toBe(promise); + expect((await result.withRawResponse()).data).toBe(stream); + const reader = stream.getReader(); + expect((await reader.read()).value).toEqual(new Uint8Array([1, 2])); + bytes.fill(9); + await reader.read(); + const spans = (await logger.drain()) as Record[]; + const output = spans.find((row: any) => row.output)?.output as any; + if (!captureAttachments) { + expect(output.content).toEqual([]); + return; + } + const attachment = output.content[0].file.file_data as Attachment; + expect(attachment).toBeInstanceOf(Attachment); + expect(output.content[0].file.byte_size).toBe(2); + expect( + new Uint8Array(await (await attachment.data()).arrayBuffer()), + ).toEqual(new Uint8Array([1, 2])); + }, + ); it.each(["cancel", "error", "break"])( "omits partial audio on %s", diff --git a/js/src/instrumentation/plugins/elevenlabs-plugin.ts b/js/src/instrumentation/providers/elevenlabs-instrumentation.ts similarity index 52% rename from js/src/instrumentation/plugins/elevenlabs-plugin.ts rename to js/src/instrumentation/providers/elevenlabs-instrumentation.ts index 6745604ff..c0186e758 100644 --- a/js/src/instrumentation/plugins/elevenlabs-plugin.ts +++ b/js/src/instrumentation/providers/elevenlabs-instrumentation.ts @@ -1,6 +1,12 @@ import { isObject, SpanTypeAttribute } from "../../../util"; import { debugLogger } from "../../debug-logger"; -import { Attachment, startSpan, withCurrent, type Span } from "../../logger"; +import { + _internalCaptureAttachmentsEnabled, + Attachment, + startSpan, + withCurrent, + type Span, +} from "../../logger/core"; import { INSTRUMENTATION_NAMES, withSpanInstrumentationName, @@ -15,8 +21,6 @@ import { isAutoInstrumentationSuppressed, runWithAutoInstrumentationSuppressed, } from "../auto-instrumentation-suppression"; -import { BasePlugin } from "../core"; -import { unsubscribeAll } from "../core/tracing"; import { isAsyncIterable, observeByteStream, @@ -24,173 +28,160 @@ import { } from "../core/stream-patcher"; import { elevenLabsChannels } from "./elevenlabs-interceptors"; -export class ElevenLabsPlugin extends BasePlugin { - protected onEnable(): void { - for (const method of [ - "convert", - "stream", - "convertWithTimestamps", - "streamWithTimestamps", - ] as const) { - this.unsubscribers.push( - elevenLabsChannels[method].intercept( - (target, receiver, args, additional) => - traceElevenLabsCall( - () => Reflect.apply(target, receiver, args), - { arguments: args, self: receiver, additional }, - `elevenlabs.textToSpeech.${method}`, - ([voice, request]) => ({ - input: { - operation: "speech", - prompt: request.text, - parameters: { - voice, - format: request.outputFormat, - language: request.languageCode, - speed: request.voiceSettings?.speed, - }, - }, - metadata: { - provider: "elevenlabs", - model: request.modelId ?? "eleven_multilingual_v2", - }, - }), - (value, args, span, finish, headers, started) => - captureSpeech( - value, - args[1], - method, - span, - finish, - started, - headers, - ), - ), - ), - ); - } - this.unsubscribers.push( - elevenLabsChannels.transcribe.intercept( - (target, receiver, args, additional) => - traceElevenLabsCall( - () => Reflect.apply(target, receiver, args), - { arguments: args, self: receiver, additional }, - "elevenlabs.speechToText.convert", - ([request]) => { - // Webhook requests return an acknowledgement; the transcription arrives - // separately and cannot be captured by this request/response span. - if (request.webhook) return undefined; - const file = request.file; - const filename = - typeof File !== "undefined" && file instanceof File - ? file.name - : "audio"; - const contentType = - file instanceof Blob - ? file.type || "application/octet-stream" - : "application/octet-stream"; - const blob = - file instanceof Blob - ? file - : file instanceof Uint8Array - ? new Blob([new Uint8Array(file)], { type: contentType }) - : file instanceof ArrayBuffer - ? new Blob([file.slice(0)], { type: contentType }) - : undefined; - const fileData = blob - ? new Attachment({ data: blob, filename, contentType }) - : request.cloudStorageUrl; - return { - input: { - operation: "transcribe", - content: fileData - ? [ - { - type: "file", - file: { filename, file_data: fileData }, - }, - ] - : [], - parameters: { - language: request.languageCode, - timestamp_granularities: request.timestampsGranularity, - }, - }, - metadata: { provider: "elevenlabs", model: request.modelId }, - }; +export function registerElevenLabsInstrumentation(): void { + for (const method of [ + "convert", + "stream", + "convertWithTimestamps", + "streamWithTimestamps", + ] as const) { + elevenLabsChannels[method].intercept((target, receiver, args, additional) => + traceElevenLabsCall( + () => Reflect.apply(target, receiver, args), + { arguments: args, self: receiver, additional }, + `elevenlabs.textToSpeech.${method}`, + ([voice, request]) => ({ + input: { + operation: "speech", + prompt: request.text, + parameters: { + voice, + format: request.outputFormat, + language: request.languageCode, + speed: request.voiceSettings?.speed, }, - (value, _args, span, finish) => { - const result = value as ElevenLabsTranscription; - const transcripts = result.transcripts ?? [result]; - span.log({ - output: { - content: transcripts.flatMap((transcript) => - typeof transcript.text === "string" - ? [{ type: "text", text: transcript.text }] - : [], - ), - annotations: { - language: result.languageCode, - words: - result.words ?? - (result.transcripts - ? result.transcripts.flatMap( - (transcript) => transcript.words ?? [], - ) - : undefined), - }, - }, - }); - finish(); + }, + metadata: { + provider: "elevenlabs", + model: request.modelId ?? "eleven_multilingual_v2", + }, + }), + (value, args, span, finish, headers, started) => + captureSpeech(value, args[1], method, span, finish, started, headers), + ), + ); + } + elevenLabsChannels.transcribe.intercept( + (target, receiver, args, additional) => + traceElevenLabsCall( + () => Reflect.apply(target, receiver, args), + { arguments: args, self: receiver, additional }, + "elevenlabs.speechToText.convert", + ([request]) => { + // Webhook requests return an acknowledgement; the transcription arrives + // separately and cannot be captured by this request/response span. + if (request.webhook) return undefined; + const file = request.file; + const filename = + typeof File !== "undefined" && file instanceof File + ? file.name + : "audio"; + const contentType = + file instanceof Blob + ? file.type || "application/octet-stream" + : "application/octet-stream"; + const blob = !_internalCaptureAttachmentsEnabled() + ? undefined + : file instanceof Blob + ? file + : file instanceof Uint8Array + ? new Blob([new Uint8Array(file)], { type: contentType }) + : file instanceof ArrayBuffer + ? new Blob([file.slice(0)], { type: contentType }) + : undefined; + const fileData = blob + ? new Attachment({ data: blob, filename, contentType }) + : request.cloudStorageUrl; + return { + input: { + operation: "transcribe", + content: fileData + ? [ + { + type: "file", + file: { filename, file_data: fileData }, + }, + ] + : [], + parameters: { + language: request.languageCode, + timestamp_granularities: request.timestampsGranularity, + }, }, - ([request], span) => { - const file = request.file; - if (!isAsyncIterable(file)) return; - const chunks: Uint8Array[] = []; - const filename = - isObject(file) && typeof file.path === "string" - ? file.path.split(/[\\/]/).pop() || "audio" - : "audio"; - observeByteStream(file, { - onChunk: (chunk) => chunks.push(new Uint8Array(chunk)), - onComplete: () => { - const contentType = "application/octet-stream"; - const data = new Blob(chunks as BlobPart[], { - type: contentType, - }); - chunks.length = 0; - span.log({ - input: { - content: [ - { - type: "file", - file: { - filename, - file_data: new Attachment({ - data, - filename, - contentType, - }), - }, - }, - ], + metadata: { provider: "elevenlabs", model: request.modelId }, + }; + }, + (value, _args, span, finish) => { + const result = value as ElevenLabsTranscription; + const transcripts = result.transcripts ?? [result]; + span.log({ + output: { + content: transcripts.flatMap((transcript) => + typeof transcript.text === "string" + ? [{ type: "text", text: transcript.text }] + : [], + ), + annotations: { + language: result.languageCode, + words: + result.words ?? + (result.transcripts + ? result.transcripts.flatMap( + (transcript) => transcript.words ?? [], + ) + : undefined), + }, + }, + }); + finish(); + }, + ([request], span) => { + const file = request.file; + if ( + !_internalCaptureAttachmentsEnabled(span) || + !isAsyncIterable(file) + ) + return; + const chunks: Uint8Array[] = []; + const filename = + isObject(file) && typeof file.path === "string" + ? file.path.split(/[\\/]/).pop() || "audio" + : "audio"; + observeByteStream(file, { + onChunk: (chunk) => chunks.push(new Uint8Array(chunk)), + onComplete: () => { + const contentType = "application/octet-stream"; + const data = new Blob(chunks as BlobPart[], { + type: contentType, + }); + chunks.length = 0; + span.log({ + input: { + content: [ + { + type: "file", + file: { + filename, + file_data: new Attachment({ + data, + filename, + contentType, + }), + }, }, - }); - }, - onCancel: () => { - chunks.length = 0; + ], }, - aroundRead: (next) => withCurrent(span, next), - debugLabel: "ElevenLabs audio", }); }, - ), + onCancel: () => { + chunks.length = 0; + }, + aroundRead: (next) => withCurrent(span, next), + debugLabel: "ElevenLabs audio", + }); + }, ), - ); - } - protected onDisable(): void { - this.unsubscribers = unsubscribeAll(this.unsubscribers); - } + ); } type Finish = (error?: unknown) => void; @@ -302,6 +293,7 @@ function captureSpeech( started: number, headers?: Headers, ): void { + const captureAttachments = _internalCaptureAttachmentsEnabled(span); const format = request.outputFormat ?? "mp3_44100_128"; const formatType = format.startsWith("mp3_") ? "audio/mpeg" @@ -339,8 +331,10 @@ function captureSpeech( }); } first = false; - chunks.push(new Uint8Array(chunk)); - bytes += chunk.byteLength; + if (captureAttachments) { + chunks.push(new Uint8Array(chunk)); + bytes += chunk.byteLength; + } }; const complete = () => { if (stopped) return; @@ -384,8 +378,17 @@ function captureSpeech( finish(error); }; const timestampChunk = (chunk: ElevenLabsTimestampAudio) => { - const binary = atob(chunk.audioBase64); - observe(Uint8Array.from(binary, (character) => character.charCodeAt(0))); + if (captureAttachments) { + const binary = atob(chunk.audioBase64); + observe(Uint8Array.from(binary, (character) => character.charCodeAt(0))); + } else if (first && chunk.audioBase64) { + if (method.startsWith("stream")) { + span.log({ + metrics: { time_to_first_token: Date.now() / 1000 - started }, + }); + } + first = false; + } if (chunk.alignment || chunk.normalizedAlignment) alignments.push({ alignment: chunk.alignment, diff --git a/js/src/instrumentation/plugins/elevenlabs-interceptors.ts b/js/src/instrumentation/providers/elevenlabs-interceptors.ts similarity index 100% rename from js/src/instrumentation/plugins/elevenlabs-interceptors.ts rename to js/src/instrumentation/providers/elevenlabs-interceptors.ts diff --git a/js/src/instrumentation/providers/eve-instrumentation.test.ts b/js/src/instrumentation/providers/eve-instrumentation.test.ts new file mode 100644 index 000000000..5c48345e6 --- /dev/null +++ b/js/src/instrumentation/providers/eve-instrumentation.test.ts @@ -0,0 +1,1529 @@ +import { + afterEach, + beforeAll, + beforeEach, + describe, + expect, + it, + vi, +} from "vite-plus/test"; +import * as braintrustExports from "../../exports"; +import { configureNode } from "../../node/config"; +import { Attachment, initLogger } from "../../logger/core"; +import { _exportsForTestingOnly } from "../../logger/testing"; +import * as instrumentationExports from "../index"; +import { braintrustEveInstrumentation } from "./eve-instrumentation"; +import type { + EveInstrumentationAttemptScope, + EveInstrumentationHandlerContext, + EveInstrumentationModelCallCompletedEvent, + EveJsonValue, +} from "../../vendor-sdk-types/eve"; + +const ATTEMPT_SCOPE: EveInstrumentationAttemptScope = { + attemptId: "attempt-0", + attemptIndex: 0, + rootSessionId: "session-root", + sessionId: "session-root", + stepIndex: 0, + turnId: "turn-0", +}; + +function providerContext( + initial?: EveJsonValue, +): EveInstrumentationHandlerContext { + let value = initial; + return { + state: { + get: () => value, + set: (next) => { + value = next; + }, + }, + }; +} + +try { + configureNode(); +} catch { + // Best-effort initialization for test environments. +} + +describe("braintrustEveInstrumentation", () => { + let backgroundLogger: ReturnType< + typeof _exportsForTestingOnly.useTestBackgroundLogger + >; + + beforeAll(async () => { + await _exportsForTestingOnly.simulateLoginForTests(); + }); + + beforeEach(() => { + backgroundLogger = _exportsForTestingOnly.useTestBackgroundLogger(); + initLogger({ + projectName: "eve-instrumentation.test.ts", + projectId: "test-project-id", + autoCaptureAttachments: false, + }); + }); + + afterEach(() => { + vi.restoreAllMocks(); + vi.unstubAllEnvs(); + _exportsForTestingOnly.clearTestBackgroundLogger(); + }); + + it("returns an Eve 0.34+ instrumentation provider", () => { + const setup = vi.fn(); + const provider = braintrustEveInstrumentation({ setup }); + + expect(provider.capture).toBe("content"); + expect(Object.hasOwn(provider, "capture")).toBe(false); + expect( + Reflect.get(provider, Symbol.for("eve.instrumentation.provider")), + ).toBe(true); + expect(provider.tracePolicy?.({ audience: "private" })).toEqual({ + emit: true, + recordInputs: true, + recordOutputs: true, + }); + provider.setup?.({ agentName: "test", frameworkVersion: "0.72.0" }); + expect(setup).toHaveBeenCalledWith({ + agentName: "test", + frameworkVersion: "0.72.0", + }); + expect(typeof provider.flush).toBe("function"); + expect(Object.keys(provider.events).sort()).toEqual([ + "action.completed", + "action.failed", + "action.started", + "model.call.completed", + "model.call.failed", + "model.call.started", + "step.attempt.completed", + "step.attempt.failed", + "tool.call.completed", + "tool.call.failed", + "tool.call.started", + "turn.cancelled", + "turn.completed", + "turn.failed", + "turn.started", + ]); + }); + + it.each(["0.70.0", "0.72.0"])( + "traces nested tools once with Eve %s", + async (version) => { + const provider = braintrustEveInstrumentation(); + await provider.setup?.({ agentName: "test", frameworkVersion: version }); + const legacy = version === "0.70.0"; + for (const [callId, parentCallId] of [ + ["outer", undefined], + ["inner", "outer"], + ] as const) { + const context = providerContext(); + if (legacy) { + await provider.events["action.started"]( + { + type: "action.started", + callId, + name: callId, + idempotencyKey: callId, + scope: ATTEMPT_SCOPE, + }, + context, + ); + } + await provider.events["tool.call.started"]( + { + type: "tool.call.started", + callId, + parentCallId, + toolName: callId, + idempotencyKey: callId, + scope: ATTEMPT_SCOPE, + }, + context, + ); + await provider.events["tool.call.completed"]( + { + type: "tool.call.completed", + idempotencyKey: callId, + scope: ATTEMPT_SCOPE, + output: { type: "result", output: "done" }, + }, + context, + ); + if (legacy) { + await provider.events["action.completed"]( + { + type: "action.completed", + idempotencyKey: callId, + scope: ATTEMPT_SCOPE, + output: { type: "result", output: "done" }, + }, + context, + ); + } + } + const spans = (await backgroundLogger.drain()) as Array< + Record + >; + const tools = spans.filter((span) => + ["outer", "inner"].includes(span.span_attributes?.name), + ); + expect(tools).toHaveLength(2); + expect( + tools.every((span) => span.output === "done" && span.metrics.end), + ).toBe(true); + if (!legacy) { + expect(tools[1].span_parents).toEqual([tools[0].span_id]); + } + }, + ); + + it("exports the Eve provider only from the instrumentation entrypoint", () => { + expect(braintrustExports).not.toHaveProperty( + "braintrustEveInstrumentation", + ); + expect(instrumentationExports.braintrustEveInstrumentation).toBe( + braintrustEveInstrumentation, + ); + expect(braintrustExports).not.toHaveProperty("braintrustEveHook"); + expect(instrumentationExports).not.toHaveProperty("braintrustEveHook"); + }); + + it("records a turn with LLM and action spans", async () => { + const provider = braintrustEveInstrumentation({ + metadata: { + scenario: "eve-instrumentation-unit", + testRunId: "test-run-flat-tree", + }, + }); + const turnContext = providerContext(); + const firstModelContext = providerContext(); + const actionContext = providerContext(); + const secondModelContext = providerContext(); + const scope = ATTEMPT_SCOPE; + + await provider.events["turn.started"]( + { + idempotencyKey: "turn:session-root:turn-0", + rootSessionId: "session-root", + sequence: 0, + sessionId: "session-root", + turnId: "turn-0", + type: "turn.started", + }, + turnContext, + ); + await provider.events["model.call.started"]( + { + idempotencyKey: "model:attempt-0:0", + input: { + instructions: "You are a research agent.", + messages: [{ content: "Search then read", role: "user" }], + }, + model: { + modelId: "qwen/qwen3-30b-a3b", + provider: "openrouter.chat", + }, + scope, + type: "model.call.started", + }, + firstModelContext, + ); + await provider.events["model.call.completed"]( + { + content: [ + { text: "I should search first.", type: "reasoning" }, + { + callId: "call-search", + input: { query: "Eve instrumentation" }, + toolName: "search", + type: "tool-call", + }, + ], + finishReason: "tool-calls", + idempotencyKey: "model:attempt-0:0", + scope, + type: "model.call.completed", + usage: { + inputTokenDetails: { + cacheReadTokens: 3, + cacheWriteTokens: 2, + }, + inputTokens: 10, + outputTokens: 5, + }, + }, + firstModelContext, + ); + await provider.events["action.started"]( + { + callId: "call-search", + idempotencyKey: "action:session-root:turn-0:call-search", + input: { query: "Eve instrumentation" }, + name: "search", + scope, + type: "action.started", + }, + actionContext, + ); + await provider.events["action.completed"]( + { + idempotencyKey: "action:session-root:turn-0:call-search", + output: { + output: { hits: ["eve.dev/docs"] }, + type: "result", + }, + scope, + type: "action.completed", + }, + actionContext, + ); + await provider.events["model.call.started"]( + { + idempotencyKey: "model:attempt-1:0", + input: { + instructions: "You are a research agent.", + messages: [ + { content: "Search then read", role: "user" }, + { + content: [ + { + input: { query: "Eve instrumentation" }, + toolCallId: "call-search", + toolName: "search", + type: "tool-call", + }, + ], + role: "assistant", + }, + { content: "Task status update", role: "user" }, + ], + }, + model: { + modelId: "qwen/qwen3-30b-a3b", + provider: "openrouter.chat", + }, + scope, + type: "model.call.started", + }, + secondModelContext, + ); + await provider.events["model.call.completed"]( + { + content: [ + { + text: "Here is the Eve instrumentation guide.", + type: "text", + }, + ], + finishReason: "stop", + idempotencyKey: "model:attempt-1:0", + scope, + type: "model.call.completed", + usage: { + inputTokens: 20, + outputTokens: 8, + }, + }, + secondModelContext, + ); + await provider.events["turn.completed"]( + { + idempotencyKey: "turn:session-root:turn-0", + sessionId: "session-root", + turnId: "turn-0", + type: "turn.completed", + }, + turnContext, + ); + + const spans = (await backgroundLogger.drain()) as Array< + Record + >; + const root = spans.find( + (span) => span.span_attributes?.name === "eve.turn", + ); + const steps = spans.filter( + (span) => span.span_attributes?.name === "eve.step", + ); + const tool = spans.find((span) => span.span_attributes?.name === "search"); + + expect(spans.map((span) => span.span_attributes?.name)).toEqual([ + "eve.turn", + "eve.step", + "search", + "eve.step", + ]); + expect(root).toMatchObject({ + input: [{ content: "Search then read", role: "user" }], + metadata: { + "eve.session_id": "session-root", + scenario: "eve-instrumentation-unit", + testRunId: "test-run-flat-tree", + }, + output: "Here is the Eve instrumentation guide.", + metrics: { + completion_tokens: 13, + prompt_cache_creation_tokens: 2, + prompt_cached_tokens: 3, + prompt_tokens: 30, + tokens: 43, + }, + span_attributes: { + name: "eve.turn", + type: "task", + }, + span_parents: [], + }); + expect(root?.root_span_id).not.toBe(root?.span_id); + expect(root?.metadata).not.toHaveProperty("model"); + expect(root?.metadata).not.toHaveProperty("provider"); + + expect(steps).toHaveLength(2); + for (const step of steps) { + expect(step).toMatchObject({ + metadata: { + "eve.session_id": "session-root", + model: "qwen/qwen3-30b-a3b", + provider: "openrouter", + scenario: "eve-instrumentation-unit", + testRunId: "test-run-flat-tree", + }, + span_attributes: { + name: "eve.step", + type: "llm", + }, + span_parents: [root?.span_id], + }); + } + expect(steps[0]?.metadata).not.toHaveProperty("tools"); + expect(steps[0]?.output).toMatchObject([ + { + finish_reason: "tool_calls", + message: { + content: null, + reasoning: [{ content: "I should search first." }], + tool_calls: [ + { + function: { + arguments: JSON.stringify({ + query: "Eve instrumentation", + }), + name: "search", + }, + id: "call-search", + type: "function", + }, + ], + }, + }, + ]); + expect(steps[1]?.output).toMatchObject([ + { + finish_reason: "stop", + message: { + content: "Here is the Eve instrumentation guide.", + role: "assistant", + }, + }, + ]); + expect(tool).toMatchObject({ + input: { query: "Eve instrumentation" }, + metadata: { + "eve.session_id": "session-root", + scenario: "eve-instrumentation-unit", + testRunId: "test-run-flat-tree", + }, + output: { hits: ["eve.dev/docs"] }, + span_attributes: { + name: "search", + type: "tool", + }, + span_parents: [root?.span_id], + }); + expect(tool?.metadata).not.toHaveProperty("model"); + expect(tool?.metadata).not.toHaveProperty("provider"); + }); + + it("attaches a subagent turn beneath its action span", async () => { + const provider = braintrustEveInstrumentation(); + const parentTurnContext = providerContext(); + const actionContext = providerContext(); + const childTurnContext = providerContext(); + const childModelContext = providerContext(); + const parentScope = ATTEMPT_SCOPE; + + await provider.events["turn.started"]( + { + idempotencyKey: "turn:session-root:turn-0", + rootSessionId: "session-root", + sequence: 0, + sessionId: "session-root", + turnId: "turn-0", + type: "turn.started", + }, + parentTurnContext, + ); + await provider.events["action.started"]( + { + callId: "call-researcher", + idempotencyKey: "action:session-root:turn-0:call-researcher", + input: { message: "Research Eve" }, + name: "researcher", + scope: parentScope, + type: "action.started", + }, + actionContext, + ); + await Promise.all([ + provider.events["turn.started"]( + { + idempotencyKey: "turn:session-child:turn-0", + parentLineage: { + callId: "call-researcher", + sessionId: "session-root", + turnId: "turn-0", + }, + rootSessionId: "session-root", + sequence: 0, + sessionId: "session-child", + turnId: "turn-0", + type: "turn.started", + }, + childTurnContext, + ), + provider.events["model.call.started"]( + { + idempotencyKey: "model:child-attempt-0:0", + model: { + modelId: "qwen/qwen3-30b-a3b", + provider: "openrouter.chat", + }, + scope: { + attemptId: "child-attempt-0", + attemptIndex: 0, + rootSessionId: "session-root", + sessionId: "session-child", + stepIndex: 0, + turnId: "turn-0", + }, + type: "model.call.started", + }, + childModelContext, + ), + ]); + + const spans = (await backgroundLogger.drain()) as Array< + Record + >; + const parent = spans.find( + (span) => + span.span_attributes?.name === "eve.turn" && + span.metadata?.["eve.session_id"] === "session-root", + ); + const action = spans.find( + (span) => span.span_attributes?.name === "researcher", + ); + const child = spans.find( + (span) => + span.span_attributes?.name === "eve.turn" && + span.metadata?.["eve.session_id"] === "session-child", + ); + const childModel = spans.find( + (span) => span.span_attributes?.name === "eve.step", + ); + + expect(action?.span_parents).toEqual([parent?.span_id]); + expect(child?.span_parents).toEqual([action?.span_id]); + expect(child?.root_span_id).toBe(parent?.root_span_id); + expect(childModel?.span_parents).toEqual([child?.span_id]); + expect(childModel?.root_span_id).toBe(parent?.root_span_id); + }); + + it("keeps a stable root across nested subagents with different turn ids", async () => { + const provider = braintrustEveInstrumentation(); + const rootContext = providerContext(); + const rootScope = { ...ATTEMPT_SCOPE, turnId: "root-turn" }; + const childScope: EveInstrumentationAttemptScope = { + attemptId: "child-attempt", + attemptIndex: 0, + rootSessionId: "session-root", + sessionId: "session-child", + stepIndex: 0, + turnId: "child-turn", + }; + + await provider.events["turn.started"]( + { + idempotencyKey: "turn:session-root:root-turn", + rootSessionId: "session-root", + sequence: 4, + sessionId: "session-root", + turnId: "root-turn", + type: "turn.started", + }, + rootContext, + ); + await provider.events["action.started"]( + { + callId: "call-child", + idempotencyKey: "action:session-root:root-turn:call-child", + name: "child", + scope: rootScope, + type: "action.started", + }, + providerContext(), + ); + await provider.events["turn.started"]( + { + idempotencyKey: "turn:session-child:child-turn", + parentLineage: { + callId: "call-child", + sessionId: "session-root", + turnId: "root-turn", + }, + rootSessionId: "session-root", + sequence: 2, + sessionId: "session-child", + turnId: "child-turn", + type: "turn.started", + }, + providerContext(), + ); + await provider.events["action.started"]( + { + callId: "call-grandchild", + idempotencyKey: "action:session-child:child-turn:call-grandchild", + name: "grandchild", + scope: childScope, + type: "action.started", + }, + providerContext(), + ); + const rootReference = rootContext.state.get(); + if ( + typeof rootReference !== "object" || + rootReference === null || + Array.isArray(rootReference) || + !("rootSpanId" in rootReference) || + typeof rootReference.rootSpanId !== "string" + ) { + throw new Error("Expected the root turn trace context to be persisted"); + } + const resumedProvider = braintrustEveInstrumentation(); + await resumedProvider.events["turn.started"]( + { + idempotencyKey: "turn:session-grandchild:grandchild-turn", + parentLineage: { + callId: "call-grandchild", + sessionId: "session-child", + turnId: "child-turn", + }, + parentTraceContext: { + spanId: "1111111111111111", + traceFlags: 1, + traceId: rootReference.rootSpanId, + }, + rootSessionId: "session-root", + sequence: 7, + sessionId: "session-grandchild", + turnId: "grandchild-turn", + type: "turn.started", + }, + providerContext(), + ); + + const spans = (await backgroundLogger.drain()) as Array< + Record + >; + const turns = spans.filter( + (span) => span.span_attributes?.name === "eve.turn", + ); + const root = turns.find( + (turn) => turn.metadata?.["eve.session_id"] === "session-root", + ); + const child = turns.find( + (turn) => turn.metadata?.["eve.session_id"] === "session-child", + ); + const grandchild = turns.find( + (turn) => turn.metadata?.["eve.session_id"] === "session-grandchild", + ); + const childAction = spans.find( + (span) => span.span_attributes?.name === "child", + ); + const grandchildAction = spans.find( + (span) => span.span_attributes?.name === "grandchild", + ); + + expect(child?.span_parents).toEqual([childAction?.span_id]); + expect(grandchild?.span_parents).toEqual([grandchildAction?.span_id]); + expect(turns.map((turn) => turn.root_span_id)).toEqual([ + root?.root_span_id, + root?.root_span_id, + root?.root_span_id, + ]); + }); + + it("parents a top-level turn to the caller's trace context", async () => { + const provider = braintrustEveInstrumentation(); + const parentTraceContext = { + traceFlags: 1, + traceId: "0123456789abcdef0123456789abcdef", + spanId: "0123456789abcdef", + }; + await provider.events["turn.started"]( + { + idempotencyKey: "turn:external-session:turn-0", + rootSessionId: "external-session", + sequence: 0, + sessionId: "external-session", + turnId: "turn-0", + type: "turn.started", + parentTraceContext, + }, + providerContext(), + ); + const [turn] = await backgroundLogger.drain(); + expect(turn).toMatchObject({ + root_span_id: parentTraceContext.traceId, + span_parents: [parentTraceContext.spanId], + }); + }); + + it("creates a separate trace for each top-level turn in one session", async () => { + const provider = braintrustEveInstrumentation(); + + await provider.events["turn.started"]( + { + idempotencyKey: "turn:shared-session:turn-0", + rootSessionId: "shared-session", + sequence: 0, + sessionId: "shared-session", + turnId: "turn-0", + type: "turn.started", + }, + providerContext(), + ); + await provider.events["turn.started"]( + { + idempotencyKey: "turn:shared-session:turn-1", + rootSessionId: "shared-session", + sequence: 1, + sessionId: "shared-session", + turnId: "turn-1", + type: "turn.started", + }, + providerContext(), + ); + + const turns = (await backgroundLogger.drain()) as Array< + Record + >; + expect(turns).toHaveLength(2); + expect(turns.map((turn) => turn.metadata?.["eve.session_id"])).toEqual([ + "shared-session", + "shared-session", + ]); + expect(new Set(turns.map((turn) => turn.root_span_id)).size).toBe(2); + }); + + it("preserves a durable subagent turn across provider instances", async () => { + const firstProvider = braintrustEveInstrumentation(); + const parentTurnContext = providerContext(); + const childTurnContext = providerContext(); + + await firstProvider.events["turn.started"]( + { + idempotencyKey: "turn:session-root:turn-0", + rootSessionId: "session-root", + sequence: 0, + sessionId: "session-root", + turnId: "turn-0", + type: "turn.started", + }, + parentTurnContext, + ); + await firstProvider.events["action.started"]( + { + callId: "call-researcher", + idempotencyKey: "action:session-root:turn-0:call-researcher", + name: "researcher", + scope: ATTEMPT_SCOPE, + type: "action.started", + }, + providerContext(), + ); + await firstProvider.events["turn.started"]( + { + idempotencyKey: "turn:session-child:turn-0", + parentLineage: { + callId: "call-researcher", + sessionId: "session-root", + turnId: "turn-0", + }, + rootSessionId: "session-root", + sequence: 0, + sessionId: "session-child", + turnId: "turn-0", + type: "turn.started", + }, + childTurnContext, + ); + + const resumedProvider = braintrustEveInstrumentation(); + const childScope: EveInstrumentationAttemptScope = { + attemptId: "child-attempt-0", + attemptIndex: 0, + rootSessionId: "session-root", + sessionId: "session-child", + stepIndex: 0, + turnId: "turn-0", + }; + const modelContext = providerContext(); + await resumedProvider.events["model.call.started"]( + { + idempotencyKey: "model:child-attempt-0:0", + input: { + messages: [{ content: "Continue durably", role: "user" }], + }, + model: { modelId: "gpt-5.4-mini", provider: "openai.responses" }, + scope: childScope, + type: "model.call.started", + }, + modelContext, + ); + await resumedProvider.events["model.call.completed"]( + { + content: [{ text: "Durable answer", type: "text" }], + finishReason: "stop", + idempotencyKey: "model:child-attempt-0:0", + scope: childScope, + type: "model.call.completed", + usage: { inputTokens: 3, outputTokens: 2 }, + }, + modelContext, + ); + await resumedProvider.events["turn.completed"]( + { + idempotencyKey: "turn:session-child:turn-0", + sessionId: "session-child", + turnId: "turn-0", + type: "turn.completed", + }, + providerContext(childTurnContext.state.get()), + ); + + const spans = (await backgroundLogger.drain()) as Array< + Record + >; + const parent = spans.find( + (span) => + span.span_attributes?.name === "eve.turn" && + span.metadata?.["eve.session_id"] === "session-root", + ); + const action = spans.find( + (span) => span.span_attributes?.name === "researcher", + ); + const child = spans.find( + (span) => + span.span_attributes?.name === "eve.turn" && + span.metadata?.["eve.session_id"] === "session-child", + ); + const step = spans.find( + (span) => span.span_attributes?.name === "eve.step", + ); + expect(child).toMatchObject({ + input: [{ content: "Continue durably", role: "user" }], + metrics: { + completion_tokens: 2, + prompt_tokens: 3, + tokens: 5, + }, + output: "Durable answer", + span_parents: [action?.span_id], + }); + expect(child?.root_span_id).toBe(parent?.root_span_id); + expect(step?.span_parents).toEqual([child?.span_id]); + expect(step?.root_span_id).toBe(parent?.root_span_id); + }); + + it.each([false, true])( + "resumes spans and turn metrics from Eve operation state (legacy: %s)", + async (legacy) => { + const firstProvider = braintrustEveInstrumentation(); + const turnContext = providerContext(); + const modelContext = providerContext(); + const scope = ATTEMPT_SCOPE; + + await firstProvider.events["turn.started"]( + { + idempotencyKey: "turn:session-root:turn-0", + rootSessionId: "session-root", + sequence: 0, + sessionId: "session-root", + turnId: "turn-0", + type: "turn.started", + }, + turnContext, + ); + await firstProvider.events["model.call.started"]( + { + idempotencyKey: "model:attempt-0:0", + input: { + messages: [{ content: "Resume this call", role: "user" }], + }, + model: { + modelId: "gpt-5.4-mini", + provider: "openai.responses", + }, + scope, + type: "model.call.started", + }, + modelContext, + ); + + const persisted = { + ...(modelContext.state.get() as Record), + }; + if (legacy) delete persisted.captureAttachments; + expect(persisted).toMatchObject({ + exported: expect.any(String), + rootSpanId: expect.any(String), + spanId: expect.any(String), + }); + + const resumedProvider = braintrustEveInstrumentation(); + await resumedProvider.events["model.call.completed"]( + { + content: [{ text: "Resumed output", type: "text" }], + finishReason: "stop", + idempotencyKey: "model:attempt-0:0", + scope, + type: "model.call.completed", + usage: { inputTokens: 4, outputTokens: 2 }, + }, + providerContext(persisted), + ); + const secondModelContext = providerContext(); + await resumedProvider.events["model.call.started"]( + { + idempotencyKey: "model:attempt-0:1", + input: { + messages: [{ content: "Continue this call", role: "user" }], + }, + model: { + modelId: "gpt-5.4-mini", + provider: "openai.responses", + }, + scope: { ...scope, stepIndex: 1 }, + type: "model.call.started", + }, + secondModelContext, + ); + const secondPersisted = { + ...(secondModelContext.state.get() as Record), + }; + if (legacy) delete secondPersisted.captureAttachments; + expect(secondPersisted).toMatchObject({ + turnMetricContributions: { + "model:attempt-0:0": { + completion_tokens: 2, + prompt_tokens: 4, + tokens: 6, + }, + }, + }); + const finalProvider = braintrustEveInstrumentation(); + await finalProvider.events["model.call.completed"]( + { + content: [{ text: "Final resumed output", type: "text" }], + finishReason: "stop", + idempotencyKey: "model:attempt-0:1", + scope: { ...scope, stepIndex: 1 }, + type: "model.call.completed", + usage: { inputTokens: 3, outputTokens: 1 }, + }, + providerContext(secondPersisted), + ); + + const spans = (await backgroundLogger.drain()) as Array< + Record + >; + const steps = spans.filter( + (span) => span.span_attributes?.name === "eve.step", + ); + expect(steps).toHaveLength(2); + expect(steps[0]).toMatchObject({ + input: [{ content: "Resume this call", role: "user" }], + metrics: { + completion_tokens: 2, + prompt_tokens: 4, + tokens: 6, + }, + output: [ + { + finish_reason: "stop", + message: { + content: "Resumed output", + role: "assistant", + }, + }, + ], + }); + const turn = spans.find( + (span) => span.span_attributes?.name === "eve.turn", + ); + expect(turn?.metrics).toMatchObject({ + completion_tokens: 3, + prompt_tokens: 7, + tokens: 10, + }); + }, + ); + + it("does not double-count replayed model completions", async () => { + const provider = braintrustEveInstrumentation(); + const modelContext = providerContext(); + await provider.events["turn.started"]( + { + idempotencyKey: "turn:replay-session:turn-0", + rootSessionId: "replay-session", + sequence: 0, + sessionId: "replay-session", + turnId: "turn-0", + type: "turn.started", + }, + providerContext(), + ); + const scope = { + ...ATTEMPT_SCOPE, + attemptId: "replay-attempt", + rootSessionId: "replay-session", + sessionId: "replay-session", + }; + await provider.events["model.call.started"]( + { + idempotencyKey: "model:replay-attempt:0", + model: { modelId: "gpt-5.4-mini", provider: "openai.responses" }, + scope, + type: "model.call.started", + }, + modelContext, + ); + const completed: EveInstrumentationModelCallCompletedEvent = { + content: [{ text: "Done", type: "text" }], + finishReason: "stop", + idempotencyKey: "model:replay-attempt:0", + scope, + type: "model.call.completed", + usage: { inputTokens: 3, outputTokens: 2 }, + }; + await provider.events["model.call.completed"](completed, modelContext); + await provider.events["model.call.completed"](completed, modelContext); + + const spans = (await backgroundLogger.drain()) as Array< + Record + >; + const turn = spans.find( + (span) => span.span_attributes?.name === "eve.turn", + ); + expect(turn?.metrics).toMatchObject({ + completion_tokens: 2, + prompt_tokens: 3, + tokens: 5, + }); + }); + + it("records model, action, and turn failures as errors", async () => { + const provider = braintrustEveInstrumentation(); + const turnContext = providerContext(); + const modelContext = providerContext(); + const actionContext = providerContext(); + const scope = ATTEMPT_SCOPE; + + await provider.events["turn.started"]( + { + idempotencyKey: "turn:session-root:turn-0", + rootSessionId: "session-root", + sequence: 0, + sessionId: "session-root", + turnId: "turn-0", + type: "turn.started", + }, + turnContext, + ); + await provider.events["model.call.started"]( + { + idempotencyKey: "model:attempt-0:0", + model: { modelId: "gpt-5.4-mini", provider: "openai.responses" }, + scope, + type: "model.call.started", + }, + modelContext, + ); + await provider.events["model.call.failed"]( + { + error: new Error("model exploded"), + idempotencyKey: "model:attempt-0:0", + scope, + type: "model.call.failed", + }, + modelContext, + ); + await provider.events["action.started"]( + { + callId: "call-failing", + idempotencyKey: "action:session-root:turn-0:call-failing", + name: "failing-tool", + scope, + type: "action.started", + }, + actionContext, + ); + await provider.events["action.failed"]( + { + error: new Error("tool exploded"), + errorCode: "TOOL_FAILED", + idempotencyKey: "action:session-root:turn-0:call-failing", + outcome: "failed", + scope, + type: "action.failed", + }, + actionContext, + ); + await provider.events["turn.failed"]( + { + error: new Error("turn exploded"), + idempotencyKey: "turn:session-root:turn-0", + sessionId: "session-root", + turnId: "turn-0", + type: "turn.failed", + }, + turnContext, + ); + + const spans = (await backgroundLogger.drain()) as Array< + Record + >; + expect( + spans.find((span) => span.span_attributes?.name === "eve.step")?.error, + ).toContain("model exploded"); + expect( + spans.find((span) => span.span_attributes?.name === "failing-tool") + ?.error, + ).toContain("tool exploded"); + expect( + spans.find((span) => span.span_attributes?.name === "eve.turn")?.error, + ).toContain("turn exploded"); + }); + + it("projects model input without provider-private fields", async () => { + const provider = braintrustEveInstrumentation(); + const turnContext = providerContext(); + const modelContext = providerContext(); + + await provider.events["turn.started"]( + { + idempotencyKey: "turn:session-root:turn-0", + rootSessionId: "session-root", + sequence: 0, + sessionId: "session-root", + turnId: "turn-0", + type: "turn.started", + }, + turnContext, + ); + await provider.events["model.call.started"]( + { + idempotencyKey: "model:attempt-0:0", + input: { + instructions: { + content: "Object system instruction", + providerOptions: { encrypted: "instruction-secret" }, + role: "system", + }, + messages: [ + { + content: "Hello", + providerOptions: { reasoning_details: "message-secret" }, + role: "user", + }, + { + content: [ + { + providerOptions: { encrypted: "part-secret" }, + text: "Thinking", + type: "reasoning", + }, + { + input: { query: "Eve" }, + providerOptions: { encrypted: "tool-secret" }, + toolCallId: "call-0", + toolName: "search", + type: "tool-call", + }, + { + providerOptions: { encrypted: "custom-secret" }, + type: "custom", + }, + ], + providerOptions: { reasoning_details: "assistant-secret" }, + role: "assistant", + }, + ], + }, + model: { modelId: "gpt-5.4-mini", provider: "openai.responses" }, + scope: ATTEMPT_SCOPE, + type: "model.call.started", + }, + modelContext, + ); + + const spans = (await backgroundLogger.drain()) as Array< + Record + >; + const step = spans.find( + (span) => span.span_attributes?.name === "eve.step", + ); + expect(step?.input).toEqual([ + { content: "Object system instruction", role: "system" }, + { content: "Hello", role: "user" }, + { + content: [ + { text: "Thinking", type: "reasoning" }, + { + input: { query: "Eve" }, + toolCallId: "call-0", + toolName: "search", + type: "tool-call", + }, + ], + role: "assistant", + }, + ]); + expect(JSON.stringify(step?.input)).not.toContain("providerOptions"); + expect(JSON.stringify(step?.input)).not.toContain("secret"); + }); + + it.each( + [ + { option: false, env: "true", enabled: false }, + { option: true, env: "false", enabled: true }, + { option: undefined, env: undefined, enabled: false }, + { option: undefined, env: "true", enabled: true }, + ].flatMap((policy) => [ + { ...policy, legacy: false }, + { ...policy, legacy: true }, + ]), + )( + "applies the persisted or current policy to model media and preserves action JSON ($option, $env, legacy: $legacy)", + async ({ option, env, enabled, legacy }) => { + const captureAttachments = legacy ? !enabled : enabled; + vi.stubEnv("BRAINTRUST_AUTO_CAPTURE_ATTACHMENTS", env); + const logger = initLogger({ + projectName: "eve-instrumentation.test.ts", + projectId: "test-project-id", + setCurrent: false, + autoCaptureAttachments: option, + }); + const provider = braintrustEveInstrumentation(); + const actionContext = providerContext(); + const turnContext = providerContext(); + await logger.traced(() => + provider.events["turn.started"]( + { + idempotencyKey: "turn:session-root:turn-0", + rootSessionId: "session-root", + sequence: 0, + sessionId: "session-root", + turnId: "turn-0", + type: "turn.started", + }, + turnContext, + ), + ); + initLogger({ + projectName: "eve-instrumentation.test.ts", + projectId: "test-project-id", + autoCaptureAttachments: !enabled, + }); + const persistedTurn = { + ...(turnContext.state.get() as Record), + }; + if (legacy) delete persistedTurn.captureAttachments; + // Recreate the bridge from persisted state, as Eve does across workers. + const resumedProvider = braintrustEveInstrumentation(); + await resumedProvider.events["turn.started"]( + { + idempotencyKey: "turn:session-root:turn-0", + rootSessionId: "session-root", + sequence: 0, + sessionId: "session-root", + turnId: "turn-0", + type: "turn.started", + }, + providerContext(persistedTurn), + ); + const decode = vi.spyOn(globalThis, "atob"); + await resumedProvider.events["model.call.started"]( + { + idempotencyKey: "model:attempt-0:0", + input: { + messages: [ + { + content: [ + { + data: "AQID", + filename: "tiny.png", + mediaType: "image/png", + type: "file", + }, + { + image: "DQ4P", + mediaType: "image/png", + type: "image", + }, + { + data: "not valid base64!", + filename: "invalid.png", + mediaType: "image/png", + type: "file", + }, + { type: "image", image: "https://example.com/image.png" }, + ], + role: "user", + }, + { + content: [ + { + output: { + type: "content", + value: [ + { + data: "BAUG", + mediaType: "image/png", + type: "image-data", + }, + ], + }, + toolCallId: "call-image", + toolName: "render", + type: "tool-result", + }, + ], + role: "tool", + }, + ], + }, + model: { modelId: "gpt-5.4-mini", provider: "openai.responses" }, + scope: ATTEMPT_SCOPE, + type: "model.call.started", + }, + providerContext(), + ); + await provider.events["action.started"]( + { + callId: "call-render", + idempotencyKey: "action:session-root:turn-0:call-render", + input: { + data: "report text", + type: "file", + }, + name: "render", + scope: ATTEMPT_SCOPE, + type: "action.started", + }, + actionContext, + ); + await provider.events["action.completed"]( + { + idempotencyKey: "action:session-root:turn-0:call-render", + output: { + output: { + type: "content", + value: [ + { + data: "ordinary JSON data", + mediaType: "image/png", + type: "image-data", + }, + ], + }, + type: "result", + }, + scope: ATTEMPT_SCOPE, + type: "action.completed", + }, + actionContext, + ); + + const spans = (await backgroundLogger.drain()) as Array< + Record + >; + const step = spans.find( + (span) => span.span_attributes?.name === "eve.step", + ); + expect(step).toMatchObject({ + root_span_id: persistedTurn.rootSpanId, + span_parents: [persistedTurn.spanId], + }); + const inputAttachment = step?.input?.[0]?.content?.[0]?.data; + const imageAttachment = step?.input?.[0]?.content?.[1]?.image; + const unconvertedFile = step?.input?.[0]?.content?.[2]?.data; + const outputAttachment = + step?.input?.[1]?.content?.[0]?.output?.value?.[0]?.data; + const action = spans.find( + (span) => span.span_attributes?.name === "render", + ); + const actionInputAttachment = action?.input?.data; + const actionOutputAttachment = action?.output?.value?.[0]?.data; + if (captureAttachments) { + expect(inputAttachment).toBeInstanceOf(Attachment); + expect(inputAttachment.reference).toMatchObject({ + content_type: "image/png", + filename: "tiny.png", + type: "braintrust_attachment", + }); + expect(imageAttachment).toBeInstanceOf(Attachment); + expect(imageAttachment.reference).toMatchObject({ + content_type: "image/png", + type: "braintrust_attachment", + }); + expect(unconvertedFile).toBe("not valid base64!"); + expect(outputAttachment).toBeInstanceOf(Attachment); + expect(outputAttachment.reference).toMatchObject({ + content_type: "image/png", + filename: "attachment.png", + type: "braintrust_attachment", + }); + } else { + expect(inputAttachment).toBeUndefined(); + expect(imageAttachment).toBeUndefined(); + expect(unconvertedFile).toBeUndefined(); + expect(outputAttachment).toBeUndefined(); + for (const payload of ["AQID", "DQ4P", "BAUG", "not valid base64!"]) { + expect(decode).not.toHaveBeenCalledWith(payload); + } + } + expect(step?.input?.[0]?.content?.[3]).toEqual({ + type: "image", + image: "https://example.com/image.png", + }); + expect(action?.input).toEqual({ type: "file", data: "report text" }); + expect(actionInputAttachment).toBe("report text"); + expect(actionOutputAttachment).toBe("ordinary JSON data"); + expect(JSON.stringify(step?.input)).not.toContain("AQID"); + expect(JSON.stringify(step?.input)).not.toContain("BAUG"); + }, + ); + + it("preserves arbitrary action outputs with type fields", async () => { + const provider = braintrustEveInstrumentation(); + const actionContext = providerContext(); + await provider.events["turn.started"]( + { + idempotencyKey: "turn:session-root:turn-0", + rootSessionId: "session-root", + sequence: 0, + sessionId: "session-root", + turnId: "turn-0", + type: "turn.started", + }, + providerContext(), + ); + await provider.events["action.started"]( + { + callId: "call-text", + idempotencyKey: "action:session-root:turn-0:call-text", + name: "text-result", + scope: ATTEMPT_SCOPE, + type: "action.started", + }, + actionContext, + ); + await provider.events["action.completed"]( + { + idempotencyKey: "action:session-root:turn-0:call-text", + output: { + output: { text: "preserve me", type: "text" }, + type: "result", + }, + scope: ATTEMPT_SCOPE, + type: "action.completed", + }, + actionContext, + ); + + const spans = (await backgroundLogger.drain()) as Array< + Record + >; + expect( + spans.find((span) => span.span_attributes?.name === "text-result") + ?.output, + ).toEqual({ text: "preserve me", type: "text" }); + }); + + it.each([ + ["step.attempt.completed", undefined], + ["step.attempt.failed", new Error("attempt exploded")], + ] as const)("closes an open model span on %s", async (type, error) => { + const provider = braintrustEveInstrumentation(); + await provider.events["turn.started"]( + { + idempotencyKey: "turn:session-root:turn-0", + rootSessionId: "session-root", + sequence: 0, + sessionId: "session-root", + turnId: "turn-0", + type: "turn.started", + }, + providerContext(), + ); + await provider.events["model.call.started"]( + { + idempotencyKey: "model:attempt-0:0", + model: { modelId: "gpt-5.4-mini", provider: "openai.responses" }, + scope: ATTEMPT_SCOPE, + type: "model.call.started", + }, + providerContext(), + ); + + if (type === "step.attempt.completed") { + await provider.events[type]( + { + idempotencyKey: "step:attempt-0", + scope: ATTEMPT_SCOPE, + type, + }, + providerContext(), + ); + } else { + await provider.events[type]( + { + error, + idempotencyKey: "step:attempt-0", + scope: ATTEMPT_SCOPE, + type, + }, + providerContext(), + ); + } + + const spans = (await backgroundLogger.drain()) as Array< + Record + >; + const step = spans.find( + (span) => span.span_attributes?.name === "eve.step", + ); + expect(step?.metrics?.end).toEqual(expect.any(Number)); + if (error !== undefined) { + expect(step?.error).toContain("attempt exploded"); + } + }); +}); diff --git a/js/src/instrumentation/providers/eve-instrumentation.ts b/js/src/instrumentation/providers/eve-instrumentation.ts new file mode 100644 index 000000000..76c7f12db --- /dev/null +++ b/js/src/instrumentation/providers/eve-instrumentation.ts @@ -0,0 +1,1324 @@ +import { debugLogger } from "../../debug-logger"; +import { + Attachment, + BaseAttachment, + CAPTURE_ATTACHMENTS, + _internalCaptureAttachmentsEnabled, + NOOP_SPAN, + _internalStartSpanWithInitialMerge, + currentLogger, + flush, + updateSpan, + withCurrent, +} from "../../logger/core"; +import type { Span } from "../../logger/core"; +import { LRUCache } from "../../lru-cache"; +import { + INSTRUMENTATION_NAMES, + withSpanInstrumentationName, +} from "../../span-origin"; +import { SpanTypeAttribute, isObject } from "../../../util/index"; +import { getCurrentUnixTimestamp } from "../../util"; +import { + convertDataToBlob, + getExtensionFromMediaType, +} from "../../wrappers/attachment-utils"; +import type { + EveInstrumentationToolCallStartedEvent, + EveInstrumentationToolCallTerminalEvent, + EveInstrumentationActionCompletedEvent, + EveInstrumentationActionFailedEvent, + EveInstrumentationActionStartedEvent, + EveInstrumentationContentPart, + EveInstrumentationDefinition, + EveInstrumentationHandlerContext, + EveInstrumentationModelCallCompletedEvent, + EveInstrumentationModelCallFailedEvent, + EveInstrumentationModelCallStartedEvent, + EveInstrumentationModelInput, + EveInstrumentationStepAttemptCompletedEvent, + EveInstrumentationStepAttemptFailedEvent, + EveInstrumentationTurnFailedEvent, + EveInstrumentationTurnSettledEvent, + EveInstrumentationTurnStartedEvent, + EveInstrumentationUsage, +} from "../../vendor-sdk-types/eve"; + +type EveSpan = Pick; + +type EveSpanReference = { + readonly captureAttachments: boolean; + readonly exported: string; + readonly rootSpanId: string; + readonly spanId: string; + readonly turnMetricContributions?: Record>; +}; + +type TurnState = { + inputRecorded?: boolean; + key: string; + metadata: Record; + rootSpanId: string; + rowId: string; + span: EveSpan; + captureAttachments: boolean; + spanId: string; +}; + +type EveInstrumentationEvent = + | EveInstrumentationToolCallStartedEvent + | EveInstrumentationToolCallTerminalEvent + | EveInstrumentationActionCompletedEvent + | EveInstrumentationActionFailedEvent + | EveInstrumentationActionStartedEvent + | EveInstrumentationModelCallCompletedEvent + | EveInstrumentationModelCallFailedEvent + | EveInstrumentationModelCallStartedEvent + | EveInstrumentationStepAttemptCompletedEvent + | EveInstrumentationStepAttemptFailedEvent + | EveInstrumentationTurnFailedEvent + | EveInstrumentationTurnSettledEvent + | EveInstrumentationTurnStartedEvent; + +const MAX_EVE_CACHE_ENTRIES = 10_000; +const eveRootSpanIdsByTurnKey = new LRUCache({ + max: MAX_EVE_CACHE_ENTRIES, +}); + +/** + * Creates an Eve instrumentation provider. + * + * Eve 0.34.0 introduced the provider event API used here. Older Eve hook and + * single-file instrumentation layouts are intentionally unsupported. + * Export the result directly from the Eve instrumentation module. + */ +export function braintrustEveInstrumentation( + options: { + metadata?: Record; + setup?: EveInstrumentationDefinition["setup"]; + } = {}, +): EveInstrumentationDefinition { + const bridge = new EveBridge(options.metadata); + const provider: Omit = { + tracePolicy: () => ({ + emit: true, + recordInputs: true, + recordOutputs: true, + }), + events: { + "tool.call.started": (event, context) => bridge.handle(event, context), + "tool.call.completed": (event, context) => bridge.handle(event, context), + "tool.call.failed": (event, context) => bridge.handle(event, context), + "action.completed": (event, context) => bridge.handle(event, context), + "action.failed": (event, context) => bridge.handle(event, context), + "action.started": (event, context) => bridge.handle(event, context), + "model.call.completed": (event, context) => bridge.handle(event, context), + "model.call.failed": (event, context) => bridge.handle(event, context), + "model.call.started": (event, context) => bridge.handle(event, context), + "step.attempt.completed": (event, context) => + bridge.handle(event, context), + "step.attempt.failed": (event, context) => bridge.handle(event, context), + "turn.cancelled": (event, context) => bridge.handle(event, context), + "turn.completed": (event, context) => bridge.handle(event, context), + "turn.failed": (event, context) => bridge.handle(event, context), + "turn.started": (event, context) => bridge.handle(event, context), + }, + flush: () => flush(), + setup: (context) => { + const [major, minor] = (context.frameworkVersion ?? "0.72") + .split(".") + .map(Number); + bridge.useLegacyActions = major === 0 && minor < 72; + return options.setup?.(context); + }, + }; + // Eve before 0.47.4 reads capture, while Eve 0.62+ rejects providers with + // an own capture property. Inherit it to support both version lines. + return Object.assign( + Object.create({ capture: "content" }) as Pick< + EveInstrumentationDefinition, + "capture" + >, + provider, + { [Symbol.for("eve.instrumentation.provider")]: true }, + ); +} + +class ResumedEveSpan implements EveSpan { + private endTime: number | undefined; + + constructor(private readonly reference: EveSpanReference) {} + + get [CAPTURE_ATTACHMENTS](): boolean { + return this.reference.captureAttachments; + } + + get rootSpanId(): string { + return this.reference.rootSpanId; + } + + get spanId(): string { + return this.reference.spanId; + } + + log(event: Parameters[0]): void { + updateSpan({ + exported: this.reference.exported, + ...event, + }); + } + + end(args?: Parameters[0]): number { + if (this.endTime === undefined) { + this.endTime = args?.endTime ?? getCurrentUnixTimestamp(); + this.log({ metrics: { end: this.endTime } }); + } + return this.endTime; + } +} + +class EveBridge { + useLegacyActions = false; + private turnsByKey = new LRUCache({ + max: MAX_EVE_CACHE_ENTRIES, + }); + private turnsStartingByKey = new Map>(); + private openModelsByAttempt = new Map>(); + private turnMetricContributionsByKey = new Map< + string, + Record> + >(); + + constructor(private readonly configuredMetadata?: Record) {} + + async handle( + event: EveInstrumentationEvent, + context: EveInstrumentationHandlerContext, + ): Promise { + try { + switch (event.type) { + case "turn.started": + await this.getOrStartTurn(event, context); + return; + case "model.call.started": + await this.handleModelCallStarted(event, context); + return; + case "model.call.completed": + await this.handleModelCallCompleted(event, context); + return; + case "model.call.failed": + await this.handleModelCallFailed(event, context); + return; + case "step.attempt.completed": + case "step.attempt.failed": + this.handleStepAttemptTerminal(event); + return; + case "tool.call.started": + if (this.useLegacyActions) return; + case "action.started": + await this.handleActionStarted(event, context); + return; + case "tool.call.completed": + if (this.useLegacyActions) return; + case "action.completed": + await this.handleActionCompleted(event, context); + return; + case "tool.call.failed": + if (this.useLegacyActions) return; + case "action.failed": + await this.handleActionFailed(event, context); + return; + case "turn.cancelled": + case "turn.completed": + await this.handleTurnSettled(event, context); + return; + case "turn.failed": + await this.handleTurnFailed(event, context); + return; + } + } catch (error) { + debugLogger.warn("Error in Eve instrumentation provider:", error); + } + } + + private async handleModelCallStarted( + event: EveInstrumentationModelCallStartedEvent, + context: EveInstrumentationHandlerContext, + ): Promise { + const turn = await this.turnForScope(event.scope, context); + const metadata = { + ...this.metadata(event.scope.sessionId), + ...modelMetadata(event.model.modelId, event.model.provider), + }; + const input = capturedModelInput(event.input, turn.captureAttachments); + const ids = await generateEveIds("step", event.idempotencyKey); + const span = await this.startEveSpan(context, { + event: { + id: ids.rowId, + ...(input === undefined ? {} : { input }), + metadata, + }, + name: "eve.step", + [CAPTURE_ATTACHMENTS]: turn.captureAttachments, + parentSpanIds: { + rootSpanId: turn.rootSpanId, + spanId: turn.spanId, + }, + spanAttributes: { type: SpanTypeAttribute.LLM }, + spanId: ids.spanId, + }); + const reference = readSpanReference(context.state.get()); + if (reference !== undefined) { + context.state.set({ + ...reference, + turnMetricContributions: + this.turnMetricContributionsByKey.get(turn.key) ?? {}, + }); + } + const openModels = + this.openModelsByAttempt.get(event.scope.attemptId) ?? new Map(); + openModels.set(event.idempotencyKey, span); + this.openModelsByAttempt.set(event.scope.attemptId, openModels); + + if (input !== undefined && !turn.inputRecorded) { + const initialInput = initialTurnInput(input); + if (initialInput !== undefined) { + turn.inputRecorded = true; + this.updateTurn(turn, { input: initialInput }); + } + } + } + + private async handleModelCallCompleted( + event: EveInstrumentationModelCallCompletedEvent, + context: EveInstrumentationHandlerContext, + ): Promise { + const turn = await this.turnForScope(event.scope, context); + this.restoreTurnMetricContributions(turn.key, context.state.get()); + const span = await this.resumeModelSpan(event, context, turn); + const metrics = usageMetrics(event.usage); + const output = modelOutput(event.finishReason, event.content); + span.log({ metrics, output }); + span.end(); + this.closeOpenModel(event.scope.attemptId, event.idempotencyKey); + this.updateTurnMetrics(turn, event.idempotencyKey, metrics); + + const finalOutput = finalText(event.content); + if ( + finalOutput !== undefined && + normalizedFinishReason(event.finishReason) !== "tool_calls" + ) { + this.updateTurn(turn, { output: finalOutput }); + } + } + + private async handleModelCallFailed( + event: EveInstrumentationModelCallFailedEvent, + context: EveInstrumentationHandlerContext, + ): Promise { + const turn = await this.turnForScope(event.scope, context); + const span = await this.resumeModelSpan(event, context, turn); + span.log({ error: toError(event.error, "Eve model call failed") }); + span.end(); + this.closeOpenModel(event.scope.attemptId, event.idempotencyKey); + } + + private handleStepAttemptTerminal( + event: + | EveInstrumentationStepAttemptCompletedEvent + | EveInstrumentationStepAttemptFailedEvent, + ): void { + const openModels = this.openModelsByAttempt.get(event.scope.attemptId); + if (openModels === undefined) { + return; + } + for (const span of openModels.values()) { + if (event.type === "step.attempt.failed") { + span.log({ + error: toError(event.error, "Eve step attempt failed"), + }); + } + span.end(); + } + this.openModelsByAttempt.delete(event.scope.attemptId); + } + + private async handleActionStarted( + event: + | EveInstrumentationActionStartedEvent + | EveInstrumentationToolCallStartedEvent, + context: EveInstrumentationHandlerContext, + ): Promise { + const turn = await this.turnForScope(event.scope, context); + const metadata = turn.metadata; + const input = event.input; + const ids = await generateEveIds( + "action", + event.scope.sessionId, + event.scope.turnId, + event.callId, + ); + await this.startEveSpan(context, { + event: { + id: ids.rowId, + ...(input === undefined ? {} : { input }), + metadata, + }, + name: event.type === "tool.call.started" ? event.toolName : event.name, + [CAPTURE_ATTACHMENTS]: turn.captureAttachments, + parentSpanIds: { + rootSpanId: turn.rootSpanId, + spanId: + event.type === "tool.call.started" && event.parentCallId + ? ( + await generateEveIds( + "action", + event.scope.sessionId, + event.scope.turnId, + event.parentCallId, + ) + ).spanId + : turn.spanId, + }, + spanAttributes: { type: SpanTypeAttribute.TOOL }, + spanId: ids.spanId, + }); + } + + private async handleActionCompleted( + event: + | EveInstrumentationActionCompletedEvent + | Extract< + EveInstrumentationToolCallTerminalEvent, + { type: "tool.call.completed" } + >, + context: EveInstrumentationHandlerContext, + ): Promise { + const span = await this.resumeActionSpan(event, context); + if (event.output.type === "error") { + span.log({ + error: toError(event.output.error, "Eve action failed"), + }); + } else if (event.output.output !== undefined) { + span.log({ output: event.output.output }); + } + span.end( + actionEndArgs( + "completedAtMs" in event + ? (event.completedAtMs ?? event.acceptedAtMs) + : event.acceptedAtMs, + ), + ); + } + + private async handleActionFailed( + event: + | EveInstrumentationActionFailedEvent + | Extract< + EveInstrumentationToolCallTerminalEvent, + { type: "tool.call.failed" } + >, + context: EveInstrumentationHandlerContext, + ): Promise { + const span = await this.resumeActionSpan(event, context); + span.log({ + error: toError( + event.error, + event.errorCode === undefined + ? "Eve action " + event.outcome + : event.errorCode + ": Eve action " + event.outcome, + ), + }); + span.end( + actionEndArgs( + "completedAtMs" in event + ? (event.completedAtMs ?? event.acceptedAtMs) + : event.acceptedAtMs, + ), + ); + } + + private async handleTurnSettled( + event: EveInstrumentationTurnSettledEvent, + context: EveInstrumentationHandlerContext, + ): Promise { + const turn = await this.resumeTurn(event, context); + turn.span.end(); + this.turnsByKey.delete(turn.key); + this.turnMetricContributionsByKey.delete(turn.key); + } + + private async handleTurnFailed( + event: EveInstrumentationTurnFailedEvent, + context: EveInstrumentationHandlerContext, + ): Promise { + const turn = await this.resumeTurn(event, context); + turn.span.log({ + error: toError(event.error, "Eve turn failed"), + }); + turn.span.end(); + this.turnsByKey.delete(turn.key); + this.turnMetricContributionsByKey.delete(turn.key); + } + + private async startTurnSpan( + event: EveInstrumentationTurnStartedEvent, + context: EveInstrumentationHandlerContext, + metadata: Record, + ): Promise { + const captureAttachments = _internalCaptureAttachmentsEnabled(); + const ids = await generateEveIds("turn", event.sessionId, event.turnId); + const parentSpanId = + event.parentLineage === undefined + ? event.parentTraceContext?.spanId + : await deterministicEveId( + "eve:action", + event.parentLineage.sessionId, + event.parentLineage.turnId, + event.parentLineage.callId, + ); + const parentTurn = + event.parentLineage === undefined + ? undefined + : this.turnsByKey.get( + turnKey(event.parentLineage.sessionId, event.parentLineage.turnId), + ); + const parentRootSpanId = + event.parentLineage === undefined + ? undefined + : eveRootSpanIdsByTurnKey.get( + turnKey(event.parentLineage.sessionId, event.parentLineage.turnId), + ); + const rootSpanId = + event.parentLineage === undefined + ? (event.parentTraceContext?.traceId ?? + (await deterministicEveId("eve:root", event.sessionId, event.turnId))) + : (parentTurn?.rootSpanId ?? + parentRootSpanId ?? + (typeof event.parentTraceContext?.traceId === "string" && + event.parentTraceContext.traceId.length > 0 + ? event.parentTraceContext.traceId + : await deterministicEveId( + "eve:root", + event.parentLineage.sessionId, + event.parentLineage.turnId, + ))); + return await this.startEveSpan(context, { + event: { id: ids.rowId, metadata }, + name: "eve.turn", + [CAPTURE_ATTACHMENTS]: + parentTurn?.captureAttachments ?? captureAttachments, + parentSpanIds: + parentSpanId === undefined + ? { parentSpanIds: [], rootSpanId } + : { rootSpanId, spanId: parentSpanId }, + spanAttributes: { type: SpanTypeAttribute.TASK }, + spanId: ids.spanId, + }); + } + + private async startEveSpan( + context: EveInstrumentationHandlerContext, + args: Parameters[0] & { + [CAPTURE_ATTACHMENTS]: boolean; + }, + ): Promise { + const reference = readSpanReference(context.state.get()); + if (reference !== undefined) { + return new ResumedEveSpan(reference); + } + + const startTime = args?.startTime ?? getCurrentUnixTimestamp(); + const span = withCurrent(NOOP_SPAN, () => + _internalStartSpanWithInitialMerge( + withSpanInstrumentationName( + { ...args, startTime }, + INSTRUMENTATION_NAMES.EVE, + ), + ), + ); + + try { + const reference: EveSpanReference = { + captureAttachments: _internalCaptureAttachmentsEnabled(span), + exported: await span.export(), + rootSpanId: span.rootSpanId, + spanId: span.spanId, + }; + context.state.set(reference); + } catch (error) { + debugLogger.warn("Error exporting Eve span for resumption:", error); + } + return span; + } + + private async getOrStartTurn( + event: EveInstrumentationTurnStartedEvent, + context: EveInstrumentationHandlerContext, + ): Promise { + const key = turnKey(event.sessionId, event.turnId); + const existing = this.turnsByKey.get(key); + if (existing !== undefined) { + return existing; + } + + const starting = this.turnsStartingByKey.get(key); + if (starting !== undefined) { + return await starting; + } + + const metadata = this.metadata(event.sessionId); + const start = (async () => { + const ids = await generateEveIds("turn", event.sessionId, event.turnId); + const span = await this.startTurnSpan(event, context, metadata); + const turn = { + key, + metadata, + rootSpanId: span.rootSpanId, + rowId: ids.rowId, + span, + captureAttachments: _internalCaptureAttachmentsEnabled(span), + spanId: span.spanId, + }; + this.turnsByKey.set(key, turn); + eveRootSpanIdsByTurnKey.set(key, turn.rootSpanId); + return turn; + })(); + this.turnsStartingByKey.set(key, start); + try { + return await start; + } finally { + if (this.turnsStartingByKey.get(key) === start) { + this.turnsStartingByKey.delete(key); + } + } + } + + private async resumeTurn( + event: + | EveInstrumentationTurnSettledEvent + | EveInstrumentationTurnFailedEvent, + context: EveInstrumentationHandlerContext, + ): Promise { + const key = turnKey(event.sessionId, event.turnId); + const existing = this.turnsByKey.get(key); + if (existing !== undefined) { + return existing; + } + const starting = this.turnsStartingByKey.get(key); + if (starting !== undefined) { + return await starting; + } + + const metadata = this.metadata(event.sessionId); + const span = await this.startTurnSpan( + { + idempotencyKey: event.idempotencyKey, + rootSessionId: event.sessionId, + sequence: 0, + sessionId: event.sessionId, + turnId: event.turnId, + type: "turn.started", + }, + context, + metadata, + ); + const ids = await generateEveIds("turn", event.sessionId, event.turnId); + return { + key, + metadata, + rootSpanId: span.rootSpanId, + rowId: ids.rowId, + span, + captureAttachments: _internalCaptureAttachmentsEnabled(span), + spanId: span.spanId, + }; + } + + private async turnForScope( + scope: EveInstrumentationModelCallStartedEvent["scope"], + context: EveInstrumentationHandlerContext, + ): Promise { + const key = turnKey(scope.sessionId, scope.turnId); + const existing = this.turnsByKey.get(key); + if (existing !== undefined) { + return existing; + } + const starting = this.turnsStartingByKey.get(key); + if (starting !== undefined) { + return await starting; + } + + const ids = await generateEveIds("turn", scope.sessionId, scope.turnId); + const reference = readSpanReference(context.state.get()); + const persistedRootSpanId = reference?.rootSpanId; + const rememberedRootSpanId = eveRootSpanIdsByTurnKey.get(key); + return { + key, + metadata: this.metadata(scope.sessionId), + rootSpanId: + persistedRootSpanId ?? + rememberedRootSpanId ?? + (await deterministicEveId( + "eve:root", + scope.rootSessionId ?? scope.sessionId, + scope.turnId, + )), + rowId: ids.rowId, + captureAttachments: + reference?.captureAttachments ?? _internalCaptureAttachmentsEnabled(), + span: NOOP_SPAN, + spanId: ids.spanId, + }; + } + + private updateTurn( + turn: TurnState, + event: { input?: unknown; output?: unknown }, + ): void { + currentLogger()?.updateSpan({ + id: turn.rowId, + root_span_id: turn.rootSpanId, + span_id: turn.spanId, + ...event, + }); + } + + private closeOpenModel(attemptId: string, idempotencyKey: string): void { + const openModels = this.openModelsByAttempt.get(attemptId); + if (openModels === undefined) { + return; + } + openModels.delete(idempotencyKey); + if (openModels.size === 0) { + this.openModelsByAttempt.delete(attemptId); + } + } + + private restoreTurnMetricContributions( + turnKey: string, + state: unknown, + ): void { + const persisted = readSpanReference(state)?.turnMetricContributions; + if ( + persisted === undefined || + this.turnMetricContributionsByKey.has(turnKey) + ) { + return; + } + this.turnMetricContributionsByKey.set(turnKey, { ...persisted }); + } + + private updateTurnMetrics( + turn: TurnState, + idempotencyKey: string, + metrics: Record, + ): void { + const contributions = { + ...(this.turnMetricContributionsByKey.get(turn.key) ?? {}), + [idempotencyKey]: metrics, + }; + this.turnMetricContributionsByKey.set(turn.key, contributions); + const totals: Record = {}; + for (const contribution of Object.values(contributions)) { + for (const [name, value] of Object.entries(contribution)) { + totals[name] = (totals[name] ?? 0) + value; + } + } + if (Object.keys(totals).length === 0) { + return; + } + currentLogger()?.updateSpan({ + id: turn.rowId, + metrics: totals, + root_span_id: turn.rootSpanId, + span_id: turn.spanId, + }); + } + + private async resumeModelSpan( + event: + | EveInstrumentationModelCallCompletedEvent + | EveInstrumentationModelCallFailedEvent, + context: EveInstrumentationHandlerContext, + turn: TurnState, + ): Promise { + const ids = await generateEveIds("step", event.idempotencyKey); + return await this.startEveSpan(context, { + event: { + id: ids.rowId, + metadata: this.metadata(event.scope.sessionId), + }, + name: "eve.step", + [CAPTURE_ATTACHMENTS]: turn.captureAttachments, + parentSpanIds: { + rootSpanId: turn.rootSpanId, + spanId: turn.spanId, + }, + spanAttributes: { type: SpanTypeAttribute.LLM }, + spanId: ids.spanId, + }); + } + + private async resumeActionSpan( + event: + | EveInstrumentationActionCompletedEvent + | EveInstrumentationActionFailedEvent + | EveInstrumentationToolCallTerminalEvent, + context: EveInstrumentationHandlerContext, + ): Promise { + const turn = await this.turnForScope(event.scope, context); + const separator = event.idempotencyKey.lastIndexOf(":"); + const callId = + separator < 0 + ? event.idempotencyKey + : event.idempotencyKey.slice(separator + 1); + const ids = await generateEveIds( + "action", + event.scope.sessionId, + event.scope.turnId, + callId, + ); + return await this.startEveSpan(context, { + event: { id: ids.rowId, metadata: turn.metadata }, + name: "eve.action", + [CAPTURE_ATTACHMENTS]: turn.captureAttachments, + parentSpanIds: { + rootSpanId: turn.rootSpanId, + spanId: turn.spanId, + }, + spanAttributes: { type: SpanTypeAttribute.TOOL }, + spanId: ids.spanId, + }); + } + + private metadata(sessionId: string): Record { + return { + ...(this.configuredMetadata ?? {}), + "eve.session_id": sessionId, + }; + } +} + +function readSpanReference(value: unknown): EveSpanReference | undefined { + if (!isObject(value)) { + return undefined; + } + const exported = value["exported"]; + const rootSpanId = value["rootSpanId"]; + const spanId = value["spanId"]; + const captureAttachments = value["captureAttachments"]; + if ( + (captureAttachments !== undefined && + typeof captureAttachments !== "boolean") || + typeof exported !== "string" || + typeof rootSpanId !== "string" || + typeof spanId !== "string" + ) { + return undefined; + } + return { + // State written before attachment capture was configurable has no policy. + captureAttachments: + captureAttachments ?? _internalCaptureAttachmentsEnabled(), + exported, + rootSpanId, + spanId, + ...(isObject(value["turnMetricContributions"]) + ? { + turnMetricContributions: numericMetricContributions( + value["turnMetricContributions"], + ), + } + : {}), + }; +} + +function capturedModelInput( + input: EveInstrumentationModelInput | undefined, + captureAttachments: boolean, +): unknown[] | undefined { + if (input === undefined) { + return undefined; + } + const value: unknown[] = []; + if (typeof input.instructions === "string") { + value.push({ content: input.instructions, role: "system" }); + } else if (Array.isArray(input.instructions)) { + for (const instruction of input.instructions) { + const projected = projectedModelMessage(instruction, captureAttachments); + if (projected?.role === "system") { + value.push(projected); + } + } + } else if (isObject(input.instructions)) { + const instruction = projectedModelMessage( + input.instructions, + captureAttachments, + ); + if (instruction?.role === "system") { + value.push(instruction); + } + } + for (const message of input.messages) { + const projected = projectedModelMessage(message, captureAttachments); + if (projected !== undefined) { + value.push(projected); + } + } + return value; +} + +function projectedModelMessage( + value: unknown, + captureAttachments: boolean, +): { content: unknown; role: string } | undefined { + if (!isObject(value)) { + return undefined; + } + const role = value["role"]; + if ( + role !== "system" && + role !== "user" && + role !== "assistant" && + role !== "tool" + ) { + return undefined; + } + const content = value["content"]; + if (typeof content === "string") { + return { content, role }; + } + if (!Array.isArray(content)) { + return undefined; + } + const projected: unknown[] = []; + for (const part of content) { + if (!isObject(part) || typeof part["type"] !== "string") { + continue; + } + switch (part["type"]) { + case "text": + case "reasoning": + if (typeof part["text"] === "string") { + projected.push({ text: part["text"], type: part["type"] }); + } + break; + case "image": { + if (part["image"] === undefined) { + break; + } + const mediaType = + typeof part["mediaType"] === "string" + ? part["mediaType"] + : "image/png"; + projected.push({ + image: mediaAttachment( + part["image"], + mediaType, + undefined, + captureAttachments, + ), + ...(typeof part["mediaType"] === "string" ? { mediaType } : {}), + type: "image", + }); + break; + } + case "file": { + const mediaType = + typeof part["mediaType"] === "string" + ? part["mediaType"] + : "application/octet-stream"; + const data = + part["data"] === undefined + ? undefined + : mediaAttachment( + part["data"], + mediaType, + typeof part["filename"] === "string" + ? part["filename"] + : undefined, + captureAttachments, + ); + projected.push({ + ...(data === undefined ? {} : { data }), + ...(typeof part["filename"] === "string" + ? { filename: part["filename"] } + : {}), + mediaType, + type: "file", + }); + break; + } + case "tool-call": + if ( + typeof part["toolCallId"] === "string" && + typeof part["toolName"] === "string" + ) { + projected.push({ + input: part["input"], + toolCallId: part["toolCallId"], + toolName: part["toolName"], + type: "tool-call", + }); + } + break; + case "tool-result": + if ( + typeof part["toolCallId"] === "string" && + typeof part["toolName"] === "string" + ) { + projected.push({ + output: projectedToolOutput(part["output"], captureAttachments), + toolCallId: part["toolCallId"], + toolName: part["toolName"], + type: "tool-result", + }); + } + break; + case "tool-approval-response": + if ( + typeof part["approvalId"] === "string" && + typeof part["approved"] === "boolean" + ) { + projected.push({ + approvalId: part["approvalId"], + approved: part["approved"], + ...(typeof part["reason"] === "string" + ? { reason: part["reason"] } + : {}), + type: "tool-approval-response", + }); + } + break; + default: + break; + } + } + return { content: projected, role }; +} + +function projectedToolOutput( + value: unknown, + captureAttachments: boolean, +): unknown { + if (!isObject(value)) { + return value; + } + if (typeof value["type"] !== "string") { + return undefined; + } + switch (value["type"]) { + case "text": + case "json": + case "error-text": + case "error-json": + return { type: value["type"], value: value["value"] }; + case "execution-denied": + return { + ...(typeof value["reason"] === "string" + ? { reason: value["reason"] } + : {}), + type: "execution-denied", + }; + case "content": { + if (!Array.isArray(value["value"])) { + return { type: "content", value: [] }; + } + const content: unknown[] = []; + for (const part of value["value"]) { + if (!isObject(part) || typeof part["type"] !== "string") { + continue; + } + if (part["type"] === "text" && typeof part["text"] === "string") { + content.push({ text: part["text"], type: "text" }); + } else if ( + (part["type"] === "file-data" || part["type"] === "image-data") && + part["data"] !== undefined && + typeof part["mediaType"] === "string" + ) { + const data = mediaAttachment( + part["data"], + part["mediaType"], + typeof part["filename"] === "string" ? part["filename"] : undefined, + captureAttachments, + ); + content.push({ + ...(data === undefined ? {} : { data }), + ...(typeof part["filename"] === "string" + ? { filename: part["filename"] } + : {}), + mediaType: part["mediaType"], + type: part["type"], + }); + } else if ( + (part["type"] === "file-url" || part["type"] === "image-url") && + typeof part["url"] === "string" + ) { + content.push({ type: part["type"], url: part["url"] }); + } + } + return { + type: "content", + value: content, + }; + } + default: + return undefined; + } +} + +function mediaAttachment( + data: unknown, + contentType: string, + filename: string | undefined, + captureAttachments: boolean, +): unknown { + if ( + data instanceof BaseAttachment || + (data instanceof URL && data.protocol !== "data:") || + (typeof data === "string" && /^https?:/i.test(data)) + ) + return data; + if (!captureAttachments) return undefined; + const blob = convertDataToBlob(data, contentType); + if (blob === null) { + return data; + } + return new Attachment({ + contentType, + data: blob, + filename: + filename ?? `attachment.${getExtensionFromMediaType(contentType)}`, + }); +} + +function initialTurnInput(input: readonly unknown[]): unknown[] | undefined { + for (let index = input.length - 1; index >= 0; index--) { + const message = input[index]; + if (isObject(message) && message["role"] === "user") { + return [message]; + } + } + return undefined; +} + +function modelOutput( + finishReason: string, + content: readonly EveInstrumentationContentPart[] | undefined, +): unknown { + const parts = content ?? []; + const text = parts + .filter( + ( + part, + ): part is Extract => + part.type === "text", + ) + .map((part) => part.text) + .join(""); + const reasoning = parts + .filter( + ( + part, + ): part is Extract< + EveInstrumentationContentPart, + { type: "reasoning" } + > => part.type === "reasoning", + ) + .map((part) => ({ content: part.text })); + const toolCalls = parts.flatMap((part) => { + if (part.type !== "tool-call") { + return []; + } + return [ + { + function: { + arguments: jsonString(part.input), + name: part.toolName, + }, + id: part.callId, + type: "function", + }, + ]; + }); + return [ + { + finish_reason: normalizedFinishReason(finishReason), + index: 0, + message: { + content: text.length === 0 ? null : text, + ...(reasoning.length === 0 ? {} : { reasoning }), + role: "assistant", + ...(toolCalls.length === 0 ? {} : { tool_calls: toolCalls }), + }, + }, + ]; +} + +function finalText( + content: readonly EveInstrumentationContentPart[] | undefined, +): string | undefined { + const text = (content ?? []) + .filter( + ( + part, + ): part is Extract => + part.type === "text", + ) + .map((part) => part.text) + .join(""); + return text.length === 0 ? undefined : text; +} + +function jsonString(value: unknown): string { + try { + return JSON.stringify(value) ?? "null"; + } catch { + return "null"; + } +} + +function normalizedFinishReason(finishReason: string): string { + if (finishReason === "content-filter") { + return "content_filter"; + } + if (finishReason === "tool-calls") { + return "tool_calls"; + } + return finishReason; +} + +function usageMetrics(usage: EveInstrumentationUsage): Record { + const inputTokens = validMetric(usage.inputTokens); + const outputTokens = validMetric(usage.outputTokens); + const cacheReadTokens = validMetric(usage.inputTokenDetails?.cacheReadTokens); + const cacheWriteTokens = validMetric( + usage.inputTokenDetails?.cacheWriteTokens, + ); + return { + ...(inputTokens === undefined ? {} : { prompt_tokens: inputTokens }), + ...(outputTokens === undefined ? {} : { completion_tokens: outputTokens }), + ...(inputTokens === undefined || outputTokens === undefined + ? {} + : { tokens: inputTokens + outputTokens }), + ...(cacheReadTokens === undefined + ? {} + : { prompt_cached_tokens: cacheReadTokens }), + ...(cacheWriteTokens === undefined + ? {} + : { prompt_cache_creation_tokens: cacheWriteTokens }), + }; +} + +function validMetric(value: unknown): number | undefined { + return typeof value === "number" && Number.isFinite(value) && value >= 0 + ? value + : undefined; +} + +function numericMetrics( + value: Record, +): Record { + const metrics: Record = {}; + for (const name of [ + "completion_tokens", + "prompt_cache_creation_tokens", + "prompt_cached_tokens", + "prompt_tokens", + "tokens", + ]) { + const metric = validMetric(value[name]); + if (metric !== undefined) { + metrics[name] = metric; + } + } + return metrics; +} + +function numericMetricContributions( + value: Record, +): Record> { + const contributions: Record> = {}; + for (const [idempotencyKey, contribution] of Object.entries(value)) { + if (isObject(contribution)) { + contributions[idempotencyKey] = numericMetrics(contribution); + } + } + return contributions; +} + +function modelMetadata( + modelId: string, + providerId: string, +): Record { + const provider = providerId.split(".")[0]?.trim(); + const model = modelId.trim(); + return { + ...(model.length === 0 ? {} : { model }), + ...(provider === undefined || provider.length === 0 ? {} : { provider }), + }; +} + +function toError(error: unknown, fallback: string): Error { + if (error instanceof Error) { + return error; + } + if (typeof error === "string" && error.length > 0) { + return new Error(error); + } + if (isObject(error) && typeof error["message"] === "string") { + const code = typeof error["code"] === "string" ? error["code"] + ": " : ""; + const result = new Error(code + error["message"]); + result.cause = error; + return result; + } + const result = new Error(fallback); + if (error !== undefined) { + result.cause = error; + } + return result; +} + +function actionEndArgs( + acceptedAtMs: number | undefined, +): { endTime: number } | undefined { + return typeof acceptedAtMs === "number" && Number.isFinite(acceptedAtMs) + ? { endTime: acceptedAtMs / 1000 } + : undefined; +} + +function turnKey(sessionId: string, turnId: string): string { + return sessionId + ":" + turnId; +} + +async function generateEveIds( + kind: "action" | "step" | "turn", + ...parts: string[] +): Promise<{ rowId: string; spanId: string }> { + const [rowId, spanId] = await Promise.all([ + deterministicEveId("eve:row:" + kind, ...parts), + deterministicEveId("eve:" + kind, ...parts), + ]); + return { rowId, spanId }; +} + +async function deterministicEveId(...parts: string[]): Promise { + const data = new TextEncoder().encode( + parts.map((part) => part.length + ":" + part).join("\0"), + ); + const digest = await globalThis.crypto.subtle.digest("SHA-256", data); + const bytes = Array.from(new Uint8Array(digest, 0, 16)); + const hex = bytes.map((byte) => byte.toString(16).padStart(2, "0")).join(""); + return ( + hex.slice(0, 8) + + "-" + + hex.slice(8, 12) + + "-" + + hex.slice(12, 16) + + "-" + + hex.slice(16, 20) + + "-" + + hex.slice(20) + ); +} diff --git a/js/src/instrumentation/plugins/flue-plugin.test.ts b/js/src/instrumentation/providers/flue-instrumentation.test.ts similarity index 69% rename from js/src/instrumentation/plugins/flue-plugin.test.ts rename to js/src/instrumentation/providers/flue-instrumentation.test.ts index 33399913c..e490dc68e 100644 --- a/js/src/instrumentation/plugins/flue-plugin.test.ts +++ b/js/src/instrumentation/providers/flue-instrumentation.test.ts @@ -1,19 +1,23 @@ -import { afterEach, beforeEach, describe, expect, it, vi } from "vitest"; +import { + afterEach, + beforeEach, + describe, + expect, + it, + vi, +} from "vite-plus/test"; const { mockDebugLog, mockCurrentParentSpan, - mockCurrentSpanStoreSymbol, mockCurrentSpanStore, mockFlush, mockStartSpan, } = vi.hoisted(() => { const currentParentSpan = { current: undefined as any }; - const currentSpanStoreSymbol = Symbol.for("braintrust.currentSpanStore"); return { mockDebugLog: vi.fn(), mockCurrentParentSpan: currentParentSpan, - mockCurrentSpanStoreSymbol: currentSpanStoreSymbol, mockCurrentSpanStore: { getStore: vi.fn(() => currentParentSpan.current), run: vi.fn((span: unknown, callback: () => unknown) => { @@ -37,41 +41,12 @@ vi.mock("../../debug-logger", () => ({ }, })); -const { mockNewInvocationHook, mockInvocationHooks } = vi.hoisted(() => { - const invocationHooks = new Map(); - - function invocationHook(name: string) { - const existing = invocationHooks.get(name); - if (existing) { - return existing; - } - - const handlers = new Set(); - const channel = { - __handlers: handlers, - remove: vi.fn((handler: any) => handlers.delete(handler)), - intercept: vi.fn((handler: any) => { - handlers.add(handler); - return () => channel.remove(handler); - }), - }; - invocationHooks.set(name, channel); - return channel; - } - - return { - mockNewInvocationHook: vi.fn((name: string) => invocationHook(name)), - mockInvocationHooks: invocationHooks, - }; -}); - -vi.mock("../../logger", () => ({ - BRAINTRUST_CURRENT_SPAN_STORE: mockCurrentSpanStoreSymbol, +vi.mock("../../logger/core", () => ({ NOOP_SPAN: {}, flush: (...args: unknown[]) => mockFlush(...args), _internalGetGlobalState: () => ({ contextManager: { - [mockCurrentSpanStoreSymbol]: mockCurrentSpanStore, + getCurrentSpanStore: () => mockCurrentSpanStore, wrapSpanForStore: (span: unknown) => span, }, idGenerator: { @@ -81,6 +56,7 @@ vi.mock("../../logger", () => ({ }, }), startSpan: (...args: unknown[]) => mockStartSpan(...args), + _internalStartSpan: (...args: unknown[]) => mockStartSpan(...args), withCurrent: (span: unknown, callback: () => unknown) => { const previous = mockCurrentParentSpan.current; mockCurrentParentSpan.current = span; @@ -92,25 +68,9 @@ vi.mock("../../logger", () => ({ }, })); -vi.mock("../../global-instrumentation-hooks", async (importOriginal) => ({ - ...(await importOriginal< - typeof import("../../global-instrumentation-hooks") - >()), - newGlobalInvocationHook: mockNewInvocationHook, -})); +import { braintrustFlueInstrumentation } from "./flue-instrumentation"; -import { - FluePlugin, - braintrustFlueInstrumentation, - braintrustFlueObserver, -} from "./flue-plugin"; - -type Subscriber = typeof braintrustFlueObserver; - -const CREATE_CONTEXT_CHANNEL_NAME = - "orchestrion:@flue/runtime:createFlueContext"; - -describe("Flue observe instrumentation", () => { +describe("Flue instrumentation", () => { let spans: Array<{ args: any; end: ReturnType; @@ -152,51 +112,20 @@ describe("Flue observe instrumentation", () => { }); afterEach(() => { - delete (globalThis as Record)[ - Symbol.for("braintrust.flue.auto-state") - ]; delete (globalThis as Record)[ Symbol.for("braintrust.flue.observe-bridge") ]; - for (const channel of mockInvocationHooks.values()) { - channel.__handlers.clear(); - } vi.clearAllMocks(); }); - it("exports a subscriber that can be passed directly to Flue observe", () => { - const subscribers: Subscriber[] = []; - const unsubscribe = vi.fn(); - const observe = vi.fn((subscriber: Subscriber) => { - subscribers.push(subscriber); - return unsubscribe; - }); - - const unregister = observe(braintrustFlueObserver); - - expect(observe).toHaveBeenCalledTimes(1); - expect(observe).toHaveBeenCalledWith(braintrustFlueObserver); - expect(subscribers).toHaveLength(1); - - subscribers[0]?.({ - runId: "run-1", - type: "run_start", - workflowName: "research", - }); - expect(findSpan("workflow:research")).toBeDefined(); - - unregister(); - expect(unsubscribe).toHaveBeenCalledTimes(1); - }); - - it("exports a Flue 1.0 instrumentation factory", async () => { + it("exports a Flue instrumentation factory", async () => { const instrumentation = braintrustFlueInstrumentation(); const instrument = vi.fn((value: typeof instrumentation) => value); const registered = instrument(instrumentation); expect(instrument).toHaveBeenCalledWith(instrumentation); - expect(registered.observe).toBe(braintrustFlueObserver); + expect(typeof registered.observe).toBe("function"); expect(registered.key).toBe(Symbol.for("braintrust.flue.instrumentation")); expect(typeof registered.interceptor).toBe("function"); expect(() => registered.dispose()).not.toThrow(); @@ -209,7 +138,7 @@ describe("Flue observe instrumentation", () => { type: "workflow", workflowName: "research", }, - { eventContext: { id: "ctx-1", runId: "run-1" } }, + { eventContext: { id: "ctx-1" } }, async () => mockStartSpan({ name: "app.phase" }), ); const workflowSpan = findSpan("workflow:research"); @@ -223,37 +152,7 @@ describe("Flue observe instrumentation", () => { expect("enterWith" in mockCurrentSpanStore).toBe(false); }); - it("keeps the legacy observer compatible with Flue 1.0 instrumentation", async () => { - expect(braintrustFlueObserver.observe).toBe(braintrustFlueObserver); - expect(braintrustFlueObserver.key).toBe( - Symbol.for("braintrust.flue.instrumentation"), - ); - expect(typeof braintrustFlueObserver.interceptor).toBe("function"); - expect(() => braintrustFlueObserver.dispose()).not.toThrow(); - - const appSpan = await braintrustFlueObserver.interceptor( - { - phase: "start", - runId: "run-1", - startedAt: "2026-05-27T05:12:31.000Z", - type: "workflow", - workflowName: "research", - }, - { eventContext: { id: "ctx-1", runId: "run-1" } }, - async () => mockStartSpan({ name: "app.phase" }), - ); - const workflowSpan = findSpan("workflow:research"); - - expect(workflowSpan).toBeDefined(); - expect(appSpan.spanParents).toEqual([workflowSpan?.spanId]); - expect(mockCurrentSpanStore.run).toHaveBeenCalledWith( - workflowSpan, - expect.any(Function), - ); - expect("enterWith" in mockCurrentSpanStore).toBe(false); - }); - - it("maps Flue 0.8 observe events into semantic Braintrust spans", () => { + it("maps Flue 1 workflow observations into semantic Braintrust spans", () => { const emit = observeEvents(); const usage = flueUsage(); const startedAt = "2026-05-27T05:12:31.000Z"; @@ -261,8 +160,7 @@ describe("Flue observe instrumentation", () => { emit( { instanceId: "instance-1", - owner: { kind: "workflow", workflowName: "research" }, - payload: { + input: { metadata: { scenario: "flue-instrumentation", testRunId: "e2e-run-1", @@ -275,7 +173,7 @@ describe("Flue observe instrumentation", () => { type: "run_start", workflowName: "research", }, - { id: "ctx-1", runId: "run-1" }, + { id: "ctx-1" }, ); emit({ operationId: "op-1", @@ -286,17 +184,19 @@ describe("Flue observe instrumentation", () => { type: "operation_start", }); emit({ - api: "responses", - input: { - messages: [{ content: "Find Flue changes", role: "user" }], - systemPrompt: "Be precise", - tools: [{ name: "lookup", parameters: {} }], - }, - model: "claude-test", operationId: "op-1", - provider: "anthropic", purpose: "agent", - reasoning: "medium", + request: { + api: "responses", + input: { + messages: [{ content: "Find Flue changes", role: "user" }], + systemPrompt: "Be precise", + tools: [{ name: "lookup", parameters: {} }], + }, + providerName: "anthropic", + reasoningLevel: "medium", + requestedModel: "claude-test", + }, runId: "run-1", timestamp: "2026-05-27T05:12:33.000Z", turnId: "turn-1", @@ -322,23 +222,30 @@ describe("Flue observe instrumentation", () => { toolCallId: "tool-1", toolName: "lookup", turnId: "turn-1", - type: "tool_call", + type: "tool", }); emit({ - api: "responses", durationMs: 12, isError: false, - model: "claude-test", operationId: "op-1", - output: { content: [{ text: "done", type: "text" }], role: "assistant" }, - provider: "anthropic", purpose: "agent", + request: { + api: "responses", + providerName: "anthropic", + requestedModel: "claude-test", + }, + response: { + output: { + content: [{ text: "done", type: "text" }], + role: "assistant", + }, + finishReason: "stop", + usage, + }, runId: "run-1", - stopReason: "stop", timestamp: "2026-05-27T05:12:36.000Z", turnId: "turn-1", type: "turn", - usage, }); emit({ agent: "worker", @@ -484,150 +391,6 @@ describe("Flue observe instrumentation", () => { expect(mockFlush).toHaveBeenCalledTimes(1); }); - it("maps Flue 1.0 observations into semantic Braintrust spans", () => { - const emit = observeEvents(); - const usage = flueUsage(); - - emit( - { - eventIndex: 0, - input: { - metadata: { scenario: "flue-v1" }, - topic: "native instrumentation", - }, - runId: "run-1", - timestamp: "2026-05-27T05:12:31.000Z", - type: "run_start", - v: 3, - workflowName: "research", - }, - { id: "ctx-1", runId: "run-1" }, - ); - emit({ - eventIndex: 1, - operationId: "op-1", - operationKind: "prompt", - runId: "run-1", - type: "operation_start", - v: 3, - }); - emit({ - eventIndex: 2, - operationId: "op-1", - purpose: "agent", - request: { - api: "responses", - input: { - messages: [{ content: "Find native hooks", role: "user" }], - systemPrompt: "Be exact", - tools: [{ name: "lookup" }], - }, - model: "claude-test", - providerId: "anthropic", - providerName: "anthropic", - reasoning: "medium", - }, - runId: "run-1", - turnId: "turn-1", - type: "turn_request", - v: 3, - }); - emit({ - args: { query: "native flue instrumentation" }, - eventIndex: 3, - operationId: "op-1", - runId: "run-1", - toolCallId: "tool-1", - toolName: "lookup", - turnId: "turn-1", - type: "tool_start", - v: 3, - }); - emit({ - durationMs: 4, - eventIndex: 4, - isError: false, - operationId: "op-1", - output: { ok: true }, - runId: "run-1", - toolCallId: "tool-1", - toolName: "lookup", - turnId: "turn-1", - type: "tool", - v: 3, - }); - emit({ - durationMs: 12, - eventIndex: 5, - isError: false, - operationId: "op-1", - purpose: "agent", - request: { - api: "responses", - model: "claude-test", - providerId: "anthropic", - providerName: "anthropic", - }, - response: { - output: { content: [{ text: "done", type: "text" }] }, - stopReason: "stop", - usage, - }, - runId: "run-1", - turnId: "turn-1", - type: "turn", - v: 3, - }); - emit({ - durationMs: 50, - eventIndex: 6, - isError: false, - operationId: "op-1", - operationKind: "prompt", - result: { text: "PROMPT_DONE", usage }, - runId: "run-1", - type: "operation", - usage, - v: 3, - }); - - const workflowSpan = findSpan("workflow:research"); - const turnSpan = findSpan("flue.turn"); - const toolSpan = findSpan("tool:lookup"); - const operationSpan = findSpan("flue.prompt"); - - expect(workflowSpan?.args.event.input).toMatchObject({ - metadata: { scenario: "flue-v1" }, - topic: "native instrumentation", - }); - expect(turnSpan?.args.event).toMatchObject({ - input: [{ content: "Find native hooks", role: "user" }], - metadata: { - "flue.api": "responses", - "flue.model": "claude-test", - "flue.provider": "anthropic", - "flue.system_prompt": "Be exact", - provider: "anthropic", - reasoning: "medium", - tools: [{ name: "lookup" }], - }, - }); - expect(toolSpan?.log).toHaveBeenCalledWith( - expect.objectContaining({ output: { ok: true } }), - ); - expect(turnSpan?.log).toHaveBeenCalledWith( - expect.objectContaining({ - metadata: expect.objectContaining({ - "flue.stop_reason": "stop", - }), - output: { content: [{ text: "done", type: "text" }] }, - }), - ); - expect(operationSpan?.log).toHaveBeenCalledWith( - expect.objectContaining({ output: "PROMPT_DONE" }), - ); - }); - it("maps released Flue 2.0 observations and retains the final agent output", () => { const emit = observeEvents(); const usage = flueUsage(); @@ -781,59 +544,65 @@ describe("Flue observe instrumentation", () => { type: "operation_start", }); emit({ - input: { - messages: [previousUser, previousAssistant, currentUser], - systemPrompt: "Be precise", - tools: [{ name: "lookup" }], - }, operationId: "op-1", purpose: "agent", + request: { + input: { + messages: [previousUser, previousAssistant, currentUser], + systemPrompt: "Be precise", + tools: [{ name: "lookup" }], + }, + }, runId: "run-1", turnId: "turn-1", type: "turn_request", }); emit({ operationId: "op-1", - output: toolCall, purpose: "agent", + response: { output: toolCall }, runId: "run-1", turnId: "turn-1", type: "turn", }); emit({ - input: { - messages: [ - previousUser, - previousAssistant, - currentUser, - toolCall, - toolResult, - ], - systemPrompt: "Be precise", - tools: [{ name: "lookup" }], - }, operationId: "op-1", purpose: "agent", + request: { + input: { + messages: [ + previousUser, + previousAssistant, + currentUser, + toolCall, + toolResult, + ], + systemPrompt: "Be precise", + tools: [{ name: "lookup" }], + }, + }, runId: "run-1", turnId: "turn-1", type: "turn_request", }); emit({ operationId: "op-1", - output: { content: "done", role: "assistant" }, purpose: "agent", + response: { output: { content: "done", role: "assistant" } }, runId: "run-1", turnId: "turn-1", type: "turn", }); emit({ - input: { - messages: [currentUser], - systemPrompt: "Use compacted context", - tools: [{ name: "search" }], - }, operationId: "op-1", purpose: "agent", + request: { + input: { + messages: [currentUser], + systemPrompt: "Use compacted context", + tools: [{ name: "search" }], + }, + }, runId: "run-1", turnId: "turn-1", type: "turn_request", @@ -883,10 +652,12 @@ describe("Flue observe instrumentation", () => { it("logs only end-event data on later span rows", () => { const emit = observeEvents(); const turnRequest = (turnId: string, content: string) => ({ - input: { - messages: [{ content, role: "user" }], - systemPrompt: "Be precise", - tools: [{ name: "lookup" }], + request: { + input: { + messages: [{ content, role: "user" }], + systemPrompt: turnId === "turn-1" ? "Be precise" : "Use new context", + tools: [{ name: turnId === "turn-1" ? "lookup" : "search" }], + }, }, operationId: "op-1", purpose: "agent", @@ -910,7 +681,10 @@ describe("Flue observe instrumentation", () => { emit(turnRequest("turn-1", "Research Flue")); emit({ operationId: "op-1", - output: { content: "done", role: "assistant" }, + response: { + output: { content: "done", role: "assistant" }, + finishReason: "stop", + }, purpose: "agent", runId: "run-1", stopReason: "stop", @@ -939,8 +713,9 @@ describe("Flue observe instrumentation", () => { ); for (const turn of [completedTurn, abandonedTurn]) { expect(turn?.args.event.metadata).toMatchObject({ - "flue.system_prompt": "Be precise", - tools: [{ name: "lookup" }], + "flue.system_prompt": + turn === completedTurn ? "Be precise" : "Use new context", + tools: [{ name: turn === completedTurn ? "lookup" : "search" }], }); expect(turn?.log).toHaveBeenCalledTimes(1); const endMetadata = turn?.log.mock.calls[0]?.[0].metadata ?? {}; @@ -1000,13 +775,15 @@ describe("Flue observe instrumentation", () => { type: "operation_start", }); emit({ - input: { - messages: [{ content: "finish", role: "user" }], - }, - model: "claude-test", operationId: "op-1", - provider: "anthropic", purpose: "agent", + request: { + input: { + messages: [{ content: "finish", role: "user" }], + }, + providerName: "anthropic", + requestedModel: "claude-test", + }, runId: "run-1", turnId: "turn-1", type: "turn_request", @@ -1081,13 +858,15 @@ describe("Flue observe instrumentation", () => { type: "compaction_start", }); emit({ - input: { - messages: [{ content: "summarize", role: "user" }], - }, - model: "gpt-test", operationId: "op-compact", - provider: "openai", purpose: "compaction_prefix", + request: { + input: { + messages: [{ content: "summarize", role: "user" }], + }, + providerName: "openai", + requestedModel: "gpt-test", + }, runId: "run-1", session: "main", turnId: "turn-compact", @@ -1293,7 +1072,7 @@ describe("Flue observe instrumentation", () => { runId: "run-1", toolCallId: "tool-1", toolName: "lookup", - type: "tool_call", + type: "tool", }); expect(mockCurrentParentSpan.current).toBeUndefined(); @@ -1309,10 +1088,10 @@ describe("Flue observe instrumentation", () => { type: "run_start", workflowName: "research", }, - { id: "ctx-1", runId: "run-1" }, + { id: "ctx-1" }, ); const workflowSpan = findSpan("workflow:research"); - const appSpan = await braintrustFlueObserver.interceptor( + const appSpan = await braintrustFlueInstrumentation().interceptor( { phase: "start", runId: "run-1", @@ -1320,7 +1099,7 @@ describe("Flue observe instrumentation", () => { type: "workflow", workflowName: "research", }, - { eventContext: { id: "ctx-1", runId: "run-1" } }, + { eventContext: { id: "ctx-1" } }, async () => mockStartSpan({ name: "app.phase" }), ); @@ -1356,7 +1135,7 @@ describe("Flue observe instrumentation", () => { }); const toolSpan = findSpan("tool:lookup"); - const appSpan = await braintrustFlueObserver.interceptor( + const appSpan = await braintrustFlueInstrumentation().interceptor( { toolCallId: "tool-1", toolName: "lookup", type: "tool" }, { operationId: "op-prompt", runId: "run-1" }, async () => mockStartSpan({ name: "app.tool-phase" }), @@ -1385,11 +1164,13 @@ describe("Flue observe instrumentation", () => { type: "operation_start", }); emit({ - input: { messages: [{ content: "hello", role: "user" }] }, - model: "claude-test", operationId: "op-prompt", - provider: "anthropic", purpose: "agent", + request: { + input: { messages: [{ content: "hello", role: "user" }] }, + providerName: "anthropic", + requestedModel: "claude-test", + }, runId: "run-1", turnId: "turn-1", type: "turn_request", @@ -1405,17 +1186,17 @@ describe("Flue observe instrumentation", () => { const operationSpan = findSpan("flue.prompt"); const turnSpan = findSpan("flue.turn"); const taskSpan = findSpan("flue.task"); - const agentAppSpan = await braintrustFlueObserver.interceptor( + const agentAppSpan = await braintrustFlueInstrumentation().interceptor( { operationId: "op-prompt", operationKind: "prompt", type: "agent" }, { operationId: "op-prompt", runId: "run-1" }, async () => mockStartSpan({ name: "app.agent-phase" }), ); - const modelAppSpan = await braintrustFlueObserver.interceptor( + const modelAppSpan = await braintrustFlueInstrumentation().interceptor( { turnId: "turn-1", type: "model" }, { operationId: "op-prompt", runId: "run-1", turnId: "turn-1" }, async () => mockStartSpan({ name: "app.model-phase" }), ); - const taskAppSpan = await braintrustFlueObserver.interceptor( + const taskAppSpan = await braintrustFlueInstrumentation().interceptor( { taskId: "task-1", type: "task" }, { operationId: "op-prompt", runId: "run-1", taskId: "task-1" }, async () => mockStartSpan({ name: "app.task-phase" }), @@ -1427,149 +1208,9 @@ describe("Flue observe instrumentation", () => { expect(mockCurrentParentSpan.current).toBeUndefined(); }); - it("subscribes transformed Flue contexts for auto instrumentation", () => { - const plugin = new FluePlugin(); - const contextSubscribers: Array<(event: unknown) => unknown> = []; - const unsubscribeContext = vi.fn(); - const context = { - id: "ctx-1", - runId: "run-1", - subscribeEvent: vi.fn((subscriber: (event: unknown) => unknown) => { - contextSubscribers.push(subscriber); - return unsubscribeContext; - }), - }; - - plugin.enable(); - expect(mockNewInvocationHook).toHaveBeenCalledWith( - CREATE_CONTEXT_CHANNEL_NAME, - ); - expect( - invocationHook(CREATE_CONTEXT_CHANNEL_NAME).intercept, - ).toHaveBeenCalledTimes(1); - - emitCreateContextEnd(context); - - expect(context.subscribeEvent).toHaveBeenCalledTimes(1); - contextSubscribers[0]?.({ - runId: "run-1", - type: "run_start", - workflowName: "auto-research", - }); - - expect( - findSpan("workflow:auto-research")?.args.event.metadata, - ).toMatchObject({ - "flue.context_id": "ctx-1", - "flue.context_run_id": "run-1", - }); - contextSubscribers[0]?.({ - durationMs: 1, - isError: false, - result: "done", - runId: "run-1", - type: "run_end", - }); - expect(unsubscribeContext).toHaveBeenCalledTimes(1); - - plugin.disable(); - - expect( - invocationHook(CREATE_CONTEXT_CHANNEL_NAME).remove, - ).toHaveBeenCalledTimes(1); - expect(unsubscribeContext).toHaveBeenCalledTimes(1); - }); - - it("keeps auto instrumentation idempotent across plugin instances", () => { - const first = new FluePlugin(); - const second = new FluePlugin(); - const contextSubscribers: Array<(event: unknown) => unknown> = []; - const unsubscribeContext = vi.fn(); - const context = { - subscribeEvent: vi.fn((subscriber: (event: unknown) => unknown) => { - contextSubscribers.push(subscriber); - return unsubscribeContext; - }), - }; - - first.enable(); - second.enable(); - emitCreateContextEnd(context); - emitCreateContextEnd(context); - - expect( - invocationHook(CREATE_CONTEXT_CHANNEL_NAME).intercept, - ).toHaveBeenCalledTimes(1); - expect(context.subscribeEvent).toHaveBeenCalledTimes(1); - - first.disable(); - expect( - invocationHook(CREATE_CONTEXT_CHANNEL_NAME).remove, - ).not.toHaveBeenCalled(); - expect(unsubscribeContext).not.toHaveBeenCalled(); - - second.disable(); - expect( - invocationHook(CREATE_CONTEXT_CHANNEL_NAME).remove, - ).toHaveBeenCalledTimes(1); - - contextSubscribers[0]?.({ - runId: "run-after-disable", - type: "run_start", - workflowName: "after-disable", - }); - expect(unsubscribeContext).toHaveBeenCalledTimes(1); - expect(findSpan("workflow:after-disable")).toBeUndefined(); - }); - - it("unsubscribes direct Flue contexts on terminal operation events", () => { - const plugin = new FluePlugin(); - const contextSubscribers: Array<(event: unknown) => unknown> = []; - const unsubscribeContext = vi.fn(); - const context = { - id: "direct-agent-1", - subscribeEvent: vi.fn((subscriber: (event: unknown) => unknown) => { - contextSubscribers.push(subscriber); - return unsubscribeContext; - }), - }; - - plugin.enable(); - emitCreateContextEnd(context); - contextSubscribers[0]?.({ - durationMs: 2, - instanceId: "direct-agent-1", - isError: false, - operationId: "op-1", - operationKind: "prompt", - result: "done", - type: "operation", - }); - - expect(unsubscribeContext).toHaveBeenCalledTimes(1); - expect(findSpan("flue.prompt")).toBeDefined(); - - plugin.disable(); - }); - function observeEvents() { return (event: unknown, ctx?: unknown) => - braintrustFlueObserver(event, ctx); - } - - function emitCreateContextEnd(result: unknown) { - for (const handlers of invocationHook(CREATE_CONTEXT_CHANNEL_NAME) - .__handlers) { - handlers(() => result, undefined, [], {}); - } - } - - function invocationHook(channelName: string) { - const channel = mockInvocationHooks.get(channelName); - if (!channel) { - throw new Error(`Missing mocked tracing channel: ${channelName}`); - } - return channel; + braintrustFlueInstrumentation().observe(event, ctx); } function findSpan(name: string) { diff --git a/js/src/instrumentation/plugins/flue-plugin.ts b/js/src/instrumentation/providers/flue-instrumentation.ts similarity index 80% rename from js/src/instrumentation/plugins/flue-plugin.ts rename to js/src/instrumentation/providers/flue-instrumentation.ts index 17e873cdc..f1321ad30 100644 --- a/js/src/instrumentation/plugins/flue-plugin.ts +++ b/js/src/instrumentation/providers/flue-instrumentation.ts @@ -1,21 +1,18 @@ +import { flush } from "../../logger/core"; import { debugLogger } from "../../debug-logger"; -import { BasePlugin, toLoggedError } from "../core"; -import { runInstrumentation } from "../core/observe-result"; - -import { SpanTypeAttribute } from "../../../util/index"; -import type { CurrentSpanStore, Span, StartSpanArgs } from "../../logger"; +import { toLoggedError } from "../core"; import { - BRAINTRUST_CURRENT_SPAN_STORE, NOOP_SPAN, _internalGetGlobalState, - flush, - startSpan as startBaseSpan, + _internalStartSpan as startBaseSpan, withCurrent, -} from "../../logger"; +} from "../../logger/core"; +import type { Span, StartSpanArgs } from "../../logger/core"; import { INSTRUMENTATION_NAMES, withSpanInstrumentationName, } from "../../span-origin"; +import { SpanTypeAttribute } from "../../../util/index"; import type { FlueBaseEvent, FlueCompactionEvent, @@ -26,7 +23,6 @@ import type { FlueExecutionInterceptor, FlueExecutionOperation, FlueInstrumentation, - FlueObservableContext, FlueOperationEvent, FlueOperationKind, FlueOperationStartEvent, @@ -41,17 +37,6 @@ import type { FlueTurnEvent, FlueTurnRequestEvent, } from "../../vendor-sdk-types/flue"; -import type { ChannelMessage } from "../core/tracing-types"; -import { flueChannels } from "./flue-interceptors"; - -type FlueObserver = (event: unknown, ctx?: unknown) => void; -type BraintrustFlueObserver = FlueObserver & FlueInstrumentation; - -type FlueAutoState = { - removeCreateContextInterceptor?: () => void; - contexts: WeakSet; - refCount: number; -}; type SpanState = { latestAgentOutput?: unknown; @@ -68,7 +53,6 @@ type FlueTurnInputState = { toolsFingerprint?: string; }; -const FLUE_AUTO_STATE = Symbol.for("braintrust.flue.auto-state"); const FLUE_INSTRUMENTATION_KEY = Symbol.for("braintrust.flue.instrumentation"); const FLUE_OBSERVE_BRIDGE = Symbol.for("braintrust.flue.observe-bridge"); @@ -83,9 +67,9 @@ const interceptFlueExecution: FlueExecutionInterceptor = ( ) => getObserveBridge().intercept(operation, ctx, next); /** - * Manual instrumentation for flue. + * Instrumentation for flue. * - * This should be passed to flue's `instrument()` API if not using auto-instrumentation: `instrument(braintrustFlueInstrumentation())` + * Pass this to flue's `instrument()` API: `instrument(braintrustFlueInstrumentation())` */ export function braintrustFlueInstrumentation(): FlueInstrumentation { return { @@ -96,85 +80,6 @@ export function braintrustFlueInstrumentation(): FlueInstrumentation { }; } -/** - * Observer for flue pre version 1.0.0. - * - * This observer should be passed to flue's `observe()` API if not using auto-instrumentation. - */ -export const braintrustFlueObserver: BraintrustFlueObserver = Object.assign( - observeFlue, - braintrustFlueInstrumentation(), -); - -export class FluePlugin extends BasePlugin { - protected onEnable(): void { - this.unsubscribers.push(enableFlueAutoInstrumentation()); - } - - protected onDisable(): void { - for (const unsubscribe of this.unsubscribers) { - unsubscribe(); - } - this.unsubscribers = []; - } -} - -function enableFlueAutoInstrumentation(): () => void { - const state = getAutoState(); - state.refCount += 1; - - if (!state.removeCreateContextInterceptor) { - const createContextChannel = flueChannels.createContext; - - const removeHandlers = createContextChannel.intercept( - (target, receiver, args, additional) => { - const event = { - ...additional, - arguments: args, - self: receiver, - } as ChannelMessage; - const returned = ( - event: ChannelMessage, - ) => { - subscribeToFlueContext(event.result, state); - }; - let result; - try { - result = Reflect.apply(target, receiver, args); - } catch (error) { - throw error; - } - Object.assign(event, { result }); - runInstrumentation(() => returned(event)); - return result; - }, - ); - state.removeCreateContextInterceptor = removeHandlers; - } - - let released = false; - return () => { - if (released) { - return; - } - released = true; - releaseAutoState(state); - }; -} - -function getAutoState(): FlueAutoState { - const existing = Reflect.get(globalThis, FLUE_AUTO_STATE); - if (isAutoState(existing)) { - return existing; - } - const state: FlueAutoState = { - contexts: new WeakSet(), - refCount: 0, - }; - Reflect.set(globalThis, FLUE_AUTO_STATE, state); - return state; -} - function getObserveBridge(): FlueObserveBridge { const existing = Reflect.get(globalThis, FLUE_OBSERVE_BRIDGE); if (isFlueObserveBridge(existing)) { @@ -189,92 +94,7 @@ function isFlueObserveBridge(value: unknown): value is FlueObserveBridge { return ( isObjectLike(value) && typeof Reflect.get(value, "handle") === "function" && - typeof Reflect.get(value, "reset") === "function" - ); -} - -function isAutoState(value: unknown): value is FlueAutoState { - return ( - isObjectLike(value) && - Reflect.get(value, "contexts") instanceof WeakSet && - typeof Reflect.get(value, "refCount") === "number" - ); -} - -function releaseAutoState(state: FlueAutoState): void { - state.refCount -= 1; - if (state.refCount > 0) { - return; - } - - try { - state.removeCreateContextInterceptor?.(); - } finally { - Reflect.deleteProperty(globalThis, FLUE_AUTO_STATE); - } -} - -function subscribeToFlueContext(value: unknown, state: FlueAutoState): void { - if (!isObservableFlueContext(value) || state.contexts.has(value)) { - return; - } - - const ctx = flueContextFromUnknown(value); - let released = false; - let unsubscribe: (() => void) | undefined; - const release = () => { - if (released) { - return; - } - released = true; - try { - unsubscribe?.(); - } catch (error) { - logInstrumentationError("Flue context unsubscribe", error); - } - }; - - try { - unsubscribe = value.subscribeEvent((event) => { - if (state.refCount <= 0) { - release(); - return; - } - - braintrustFlueObserver(event, ctx); - if (isAutoContextTerminalEvent(event, ctx)) { - release(); - } - }); - state.contexts.add(value); - } catch (error) { - logInstrumentationError("Flue context subscription", error); - } -} - -function isAutoContextTerminalEvent( - event: unknown, - ctx: FlueContext | undefined, -): boolean { - if (!isObjectLike(event)) { - return false; - } - const type = Reflect.get(event, "type"); - if (type === "run_end") { - return true; - } - if (type !== "operation") { - return false; - } - return !ctx?.runId && typeof Reflect.get(event, "runId") !== "string"; -} - -function isObservableFlueContext( - value: unknown, -): value is FlueObservableContext { - return ( - isObjectLike(value) && - typeof Reflect.get(value, "subscribeEvent") === "function" + typeof Reflect.get(value, "intercept") === "function" ); } @@ -289,7 +109,6 @@ function isFlueEvent(event: object): event is FlueEvent { type === "turn_request" || type === "turn" || type === "tool_start" || - type === "tool_call" || type === "tool" || type === "task_start" || type === "task" || @@ -303,11 +122,7 @@ function flueContextFromUnknown(ctx: unknown): FlueContext | undefined { return undefined; } const id = Reflect.get(ctx, "id"); - const runId = Reflect.get(ctx, "runId"); - return { - ...(typeof id === "string" ? { id } : {}), - ...(typeof runId === "string" ? { runId } : {}), - }; + return typeof id === "string" ? { id } : undefined; } function isObjectLike(value: unknown): value is object { @@ -339,16 +154,6 @@ class FlueObserveBridge { } } - reset(): void { - this.compactionsByKey.clear(); - this.operationsById.clear(); - this.runsById.clear(); - this.seenEvents = new WeakSet(); - this.tasksById.clear(); - this.toolsByKey.clear(); - this.turnsByKey.clear(); - } - intercept( operation: FlueExecutionOperation, executionContext: FlueExecutionContext, @@ -396,7 +201,6 @@ class FlueObserveBridge { flueContextFromUnknown(executionContext.eventContext) ?? flueContextFromUnknown({ id: operation.workflowName, - runId: operation.runId, }); if (operation.phase === "resume") { this.handleRunResume( @@ -406,7 +210,6 @@ class FlueObserveBridge { startedAt: operation.startedAt, timestamp: new Date().toISOString(), type: "run_resume", - v: 3, workflowName: operation.workflowName, }, ctx, @@ -420,7 +223,6 @@ class FlueObserveBridge { startedAt: operation.startedAt, timestamp: new Date().toISOString(), type: "run_start", - v: 3, workflowName: operation.workflowName, }, ctx, @@ -479,7 +281,6 @@ class FlueObserveBridge { case "tool_start": this.handleToolStart(event); return; - case "tool_call": case "tool": this.handleToolCall(event); return; @@ -508,15 +309,12 @@ class FlueObserveBridge { return; } - const workflowName = - event.workflowName ?? - event.owner?.workflowName ?? - (typeof ctx?.id === "string" ? ctx.id : "unknown"); - const input = flueRunInput(event); + const workflowName = event.workflowName ?? ctx?.id ?? "unknown"; + const input = event.input; const metadata = { ...extractPayloadMetadata(input), ...extractEventMetadata(event, ctx), - ...(workflowName ? { "flue.workflow_name": workflowName } : {}), + "flue.workflow_name": workflowName, provider: "flue", }; const existing = this.runsById.get(event.runId); @@ -550,11 +348,10 @@ class FlueObserveBridge { return; } - const workflowName = - event.workflowName ?? (typeof ctx?.id === "string" ? ctx.id : "unknown"); + const workflowName = event.workflowName ?? ctx?.id ?? "unknown"; const metadata = { ...extractEventMetadata(event, ctx), - ...(workflowName ? { "flue.workflow_name": workflowName } : {}), + "flue.workflow_name": workflowName, "flue.workflow_phase": "resume", provider: "flue", }; @@ -669,20 +466,20 @@ class FlueObserveBridge { } private handleTurnRequest(event: FlueTurnRequestEvent): void { - const key = turnKey(event); + const key = event.turnId; if (!key) { return; } - const input = flueTurnRequestInput(event); + const input = event.request?.input; const operation = event.operationId ? this.operationsById.get(event.operationId) : undefined; const turnInput = prepareFlueTurnInput(event, input, operation); - const model = flueTurnRequestModel(event); - const provider = flueTurnRequestProvider(event); - const api = flueTurnRequestApi(event); - const reasoning = flueTurnRequestReasoning(event); + const model = event.request?.requestedModel; + const provider = event.request?.providerName ?? event.request?.providerId; + const api = event.request?.api; + const reasoning = event.request?.reasoningLevel; const metadata = { ...extractEventMetadata(event), ...(api ? { "flue.api": api } : {}), @@ -711,19 +508,20 @@ class FlueObserveBridge { } private handleTurn(event: FlueTurnEvent): void { - const key = turnKey(event); + const key = event.turnId; if (!key) { return; } const state = this.turnsByKey.get(key) ?? this.startSyntheticTurn(event); - const model = flueTurnModel(event); - const provider = flueTurnProvider(event); - const api = flueTurnApi(event); - const stopReason = flueTurnStopReason(event); - const usage = flueTurnUsage(event); - const output = flueTurnOutput(event); - const error = flueTurnError(event); + const model = + event.response?.responseModel ?? event.request?.requestedModel; + const provider = event.request?.providerName ?? event.request?.providerId; + const api = event.request?.api; + const stopReason = event.response?.finishReason; + const usage = event.response?.usage; + const output = event.response?.output; + const error = event.response?.error; const metadata = { ...extractEventMetadata(event), ...(api ? { "flue.api": api } : {}), @@ -761,7 +559,7 @@ class FlueObserveBridge { return; } - const input = flueToolInput(event); + const input = event.args; const metadata = { ...extractEventMetadata(event), ...(event.toolName ? { "flue.tool_name": event.toolName } : {}), @@ -933,7 +731,7 @@ class FlueObserveBridge { } private parentSpanForEvent(event: FlueBaseEvent): Span | undefined { - const turn = turnKey(event); + const turn = event.turnId; if (turn) { const turnState = this.turnsByKey.get(turn); if (turnState) { @@ -1017,9 +815,10 @@ class FlueObserveBridge { } private startSyntheticTurn(event: FlueTurnEvent): SpanState { - const model = flueTurnModel(event); - const provider = flueTurnProvider(event); - const api = flueTurnApi(event); + const model = + event.response?.responseModel ?? event.request?.requestedModel; + const provider = event.request?.providerName ?? event.request?.providerId; + const api = event.request?.api; const metadata = { ...extractEventMetadata(event), ...(api ? { "flue.api": api } : {}), @@ -1200,10 +999,7 @@ function extractEventMetadata( ...(event.taskId ? { "flue.task_id": event.taskId } : {}), ...(event.operationId ? { "flue.operation_id": event.operationId } : {}), ...(event.turnId ? { "flue.turn_id": event.turnId } : {}), - ...(typeof ctx?.id === "string" ? { "flue.context_id": ctx.id } : {}), - ...(typeof ctx?.runId === "string" - ? { "flue.context_run_id": ctx.runId } - : {}), + ...(ctx ? { "flue.context_id": ctx.id } : {}), }; } @@ -1218,19 +1014,9 @@ function extractPayloadMetadata(payload: unknown): Record { return Object.fromEntries(Object.entries(metadata)); } -function flueRunInput(event: FlueRunStartEvent): unknown { - return event.input !== undefined ? event.input : event.payload; -} - -function flueTurnRequestInput( - event: FlueTurnRequestEvent, -): FlueTurnRequestEvent["input"] { - return event.request?.input ?? event.input; -} - function prepareFlueTurnInput( event: FlueTurnRequestEvent, - input: FlueTurnRequestEvent["input"], + input: NonNullable["input"], operation: SpanState | undefined, ): { messages: unknown[] | undefined; metadata: Record } { const messages = input?.messages; @@ -1339,90 +1125,15 @@ function flueOperationInput(event: FlueOperationEvent): unknown { : undefined; } -function flueTurnRequestModel(event: FlueTurnRequestEvent): string | undefined { - return event.request?.requestedModel ?? event.request?.model ?? event.model; -} - -function flueTurnRequestProvider( - event: FlueTurnRequestEvent, -): string | undefined { - return ( - event.request?.providerName ?? event.provider ?? event.request?.providerId - ); -} - -function flueTurnRequestApi(event: FlueTurnRequestEvent): string | undefined { - return event.request?.api ?? event.api; -} - -function flueTurnRequestReasoning( - event: FlueTurnRequestEvent, -): string | undefined { - return ( - event.request?.reasoningLevel ?? event.request?.reasoning ?? event.reasoning - ); -} - -function flueTurnModel(event: FlueTurnEvent): string | undefined { - return ( - event.response?.responseModel ?? - event.request?.requestedModel ?? - event.request?.model ?? - event.model - ); -} - -function flueTurnProvider(event: FlueTurnEvent): string | undefined { - return ( - event.request?.providerName ?? event.provider ?? event.request?.providerId - ); -} - -function flueTurnApi(event: FlueTurnEvent): string | undefined { - return event.request?.api ?? event.api; -} - -function flueTurnUsage(event: FlueTurnEvent): unknown { - return event.response?.usage ?? event.usage; -} - -function flueTurnOutput(event: FlueTurnEvent): unknown { - return event.response?.output ?? event.output; -} - -function flueTurnStopReason(event: FlueTurnEvent): string | undefined { - return ( - event.response?.finishReason ?? - event.response?.stopReason ?? - event.stopReason - ); -} - -function flueTurnError(event: FlueTurnEvent): unknown { - return ( - event.response?.error ?? event.response?.errorInfo?.message ?? event.error - ); -} - -function flueToolInput(event: FlueToolStartEvent): unknown { - if (event.args !== undefined) { - return event.args; - } - if (event.arguments !== undefined) { - return event.arguments; - } - return event.input; -} - function flueToolOutput(event: FlueToolCallEvent): unknown { if (Object.hasOwn(event, "effectiveResult")) { return event.effectiveResult; } - return event.output !== undefined ? event.output : event.result; + return event.result; } function flueToolError(event: FlueToolCallEvent): unknown { - return event.error ?? event.errorInfo?.message ?? flueToolOutput(event); + return event.errorInfo?.message ?? flueToolOutput(event); } function operationOutput(event: FlueOperationEvent): unknown { @@ -1530,10 +1241,6 @@ function eventTime(value: unknown): number | undefined { return Number.isFinite(timestamp) ? timestamp / 1000 : undefined; } -function turnKey(event: FlueBaseEvent): string | undefined { - return event.turnId; -} - function toolKey(event: FlueBaseEvent & { toolCallId?: string }): string { return `${event.turnId ?? event.operationId ?? event.taskId ?? event.runId ?? "unknown"}:${event.toolCallId ?? "unknown"}`; } @@ -1594,13 +1301,7 @@ function runWithCurrentSpanStore( ): Promise { const state = _internalGetGlobalState(); const contextManager = state?.contextManager; - const currentSpanStore = contextManager - ? ( - contextManager as { - [BRAINTRUST_CURRENT_SPAN_STORE]?: CurrentSpanStore; - } - )[BRAINTRUST_CURRENT_SPAN_STORE] - : undefined; + const currentSpanStore = contextManager?.getCurrentSpanStore(); if (contextManager && typeof currentSpanStore?.run === "function") { return currentSpanStore.run(contextManager.wrapSpanForStore(span), next); diff --git a/js/src/instrumentation/plugins/genkit-plugin.test.ts b/js/src/instrumentation/providers/genkit-instrumentation.test.ts similarity index 83% rename from js/src/instrumentation/plugins/genkit-plugin.test.ts rename to js/src/instrumentation/providers/genkit-instrumentation.test.ts index 4a5af907e..066c3a3a4 100644 --- a/js/src/instrumentation/plugins/genkit-plugin.test.ts +++ b/js/src/instrumentation/providers/genkit-instrumentation.test.ts @@ -1,7 +1,15 @@ -import { afterEach, beforeAll, beforeEach, describe, expect, it } from "vitest"; -import { _exportsForTestingOnly, initLogger } from "../../logger"; +import { + afterEach, + beforeAll, + beforeEach, + describe, + expect, + it, +} from "vite-plus/test"; +import { _exportsForTestingOnly } from "../../logger/testing"; +import { initLogger } from "../../logger/core"; +import { registerGenkitInstrumentation } from "./genkit-instrumentation"; import { genkitChannels } from "./genkit-interceptors"; -import { GenkitPlugin } from "./genkit-plugin"; function singleQueueStream( chunks: T[], @@ -35,9 +43,7 @@ async function collectAsync(stream: AsyncIterable): Promise { return chunks; } -describe("GenkitPlugin stream patching", () => { - const plugin = new GenkitPlugin(); - +describe("registerGenkitInstrumentation stream patching", () => { beforeAll(async () => { await _exportsForTestingOnly.simulateLoginForTests(); }); @@ -45,18 +51,17 @@ describe("GenkitPlugin stream patching", () => { beforeEach(() => { _exportsForTestingOnly.useTestBackgroundLogger(); initLogger({ - projectName: "genkit-plugin.test.ts", + projectName: "genkit-instrumentation.test.ts", projectId: "test-project-id", }); }); afterEach(() => { - plugin.disable(); _exportsForTestingOnly.clearTestBackgroundLogger(); }); it("does not consume generateStream chunks before user code reads them", async () => { - plugin.enable(); + registerGenkitInstrumentation(); const stream = singleQueueStream([{ text: "hello" }, { text: " world" }]); const result = genkitChannels.generateStream.invoke( @@ -85,7 +90,7 @@ describe("GenkitPlugin stream patching", () => { }); it("does not consume action.stream chunks before user code reads them", async () => { - plugin.enable(); + registerGenkitInstrumentation(); const stream = singleQueueStream(["first", "second"]); const action = Object.assign(() => Promise.resolve(), { __action: { diff --git a/js/src/instrumentation/plugins/genkit-plugin.ts b/js/src/instrumentation/providers/genkit-instrumentation.ts similarity index 64% rename from js/src/instrumentation/plugins/genkit-plugin.ts rename to js/src/instrumentation/providers/genkit-instrumentation.ts index e273fab7b..902abcebe 100644 --- a/js/src/instrumentation/plugins/genkit-plugin.ts +++ b/js/src/instrumentation/providers/genkit-instrumentation.ts @@ -1,16 +1,12 @@ -import { withCurrent } from "../../logger"; -import { BasePlugin, toLoggedError } from "../core"; -import { - traceAsyncCall, - traceSyncStreamCall, - unsubscribeAll, -} from "../core/tracing"; +import { withCurrent } from "../../logger/core"; +import { toLoggedError } from "../core"; +import { traceAsyncCall, traceSyncStreamCall } from "../core/tracing"; import { observeResult, runInstrumentation } from "../core/observe-result"; import { isAsyncIterable, patchStreamIfNeeded } from "../core/stream-patcher"; import { SpanTypeAttribute } from "../../../util/index"; -import type { Span } from "../../logger"; -import { startSpan as startBaseSpan } from "../../logger"; +import type { Span } from "../../logger/core"; +import { startSpan as startBaseSpan } from "../../logger/core"; import { INSTRUMENTATION_NAMES, withSpanInstrumentationName, @@ -36,88 +32,75 @@ type SpanState = { startTime: number; }; -export class GenkitPlugin extends BasePlugin { - protected onEnable(): void { +class GenkitInstrumentationConsumer { + public register(): void { this.subscribeToGenkitChannels(); } - - protected onDisable(): void { - this.unsubscribers = unsubscribeAll(this.unsubscribers); - } - private subscribeToGenkitChannels(): void { - this.unsubscribers.push( - genkitChannels.generate.intercept((target, receiver, args, additional) => - traceAsyncCall( + genkitChannels.generate.intercept((target, receiver, args, additional) => + traceAsyncCall( + () => Reflect.apply(target, receiver, args), + { ...additional, arguments: args, self: receiver }, + { + instrumentationName: INSTRUMENTATION_NAMES.GENKIT, + name: "genkit.generate", + type: SpanTypeAttribute.LLM, + extractInput: ([input]) => extractGenerateInput(input), + extractOutput: extractGenerateOutput, + extractMetadata: (result, event) => + extractGenerateResponseMetadata(result, event?.arguments?.[0]), + extractMetrics: (result) => parseGenkitUsageMetrics(result?.usage), + }, + ), + ); + + genkitChannels.generateStream.intercept( + (target, receiver, args, additional) => + traceSyncStreamCall( () => Reflect.apply(target, receiver, args), { ...additional, arguments: args, self: receiver }, { instrumentationName: INSTRUMENTATION_NAMES.GENKIT, - name: "genkit.generate", + name: "genkit.generateStream", type: SpanTypeAttribute.LLM, extractInput: ([input]) => extractGenerateInput(input), - extractOutput: extractGenerateOutput, - extractMetadata: (result, event) => - extractGenerateResponseMetadata(result, event?.arguments?.[0]), - extractMetrics: (result) => parseGenkitUsageMetrics(result?.usage), + patchResult: ({ result, span, startTime }) => + patchGenerateStreamResult(result, span, startTime), }, ), - ), - ); - - this.unsubscribers.push( - genkitChannels.generateStream.intercept( - (target, receiver, args, additional) => - traceSyncStreamCall( - () => Reflect.apply(target, receiver, args), - { ...additional, arguments: args, self: receiver }, - { - instrumentationName: INSTRUMENTATION_NAMES.GENKIT, - name: "genkit.generateStream", - type: SpanTypeAttribute.LLM, - extractInput: ([input]) => extractGenerateInput(input), - patchResult: ({ result, span, startTime }) => - patchGenerateStreamResult(result, span, startTime), - }, - ), - ), ); - this.unsubscribers.push( - genkitChannels.embed.intercept((target, receiver, args, additional) => - traceAsyncCall( - () => Reflect.apply(target, receiver, args), - { ...additional, arguments: args, self: receiver }, - { - instrumentationName: INSTRUMENTATION_NAMES.GENKIT, - name: "genkit.embed", - type: SpanTypeAttribute.FUNCTION, - extractInput: ([params]) => extractEmbedInput(params), - extractOutput: (result) => summarizeEmbeddingResult(result), - extractMetadata: (_result, event) => - extractEmbedMetadata(event?.arguments?.[0]), - extractMetrics: () => ({}), - }, - ), + genkitChannels.embed.intercept((target, receiver, args, additional) => + traceAsyncCall( + () => Reflect.apply(target, receiver, args), + { ...additional, arguments: args, self: receiver }, + { + instrumentationName: INSTRUMENTATION_NAMES.GENKIT, + name: "genkit.embed", + type: SpanTypeAttribute.FUNCTION, + extractInput: ([params]) => extractEmbedInput(params), + extractOutput: (result) => summarizeEmbeddingResult(result), + extractMetadata: (_result, event) => + extractEmbedMetadata(event?.arguments?.[0]), + extractMetrics: () => ({}), + }, ), ); - this.unsubscribers.push( - genkitChannels.embedMany.intercept((target, receiver, args, additional) => - traceAsyncCall( - () => Reflect.apply(target, receiver, args), - { ...additional, arguments: args, self: receiver }, - { - instrumentationName: INSTRUMENTATION_NAMES.GENKIT, - name: "genkit.embedMany", - type: SpanTypeAttribute.FUNCTION, - extractInput: ([params]) => extractEmbedManyInput(params), - extractOutput: summarizeEmbeddingResult, - extractMetadata: (_result, event) => - extractEmbedMetadata(event?.arguments?.[0]), - extractMetrics: () => ({}), - }, - ), + genkitChannels.embedMany.intercept((target, receiver, args, additional) => + traceAsyncCall( + () => Reflect.apply(target, receiver, args), + { ...additional, arguments: args, self: receiver }, + { + instrumentationName: INSTRUMENTATION_NAMES.GENKIT, + name: "genkit.embedMany", + type: SpanTypeAttribute.FUNCTION, + extractInput: ([params]) => extractEmbedManyInput(params), + extractOutput: summarizeEmbeddingResult, + extractMetadata: (_result, event) => + extractEmbedMetadata(event?.arguments?.[0]), + extractMetrics: () => ({}), + }, ), ); @@ -130,186 +113,176 @@ export class GenkitPlugin extends BasePlugin { const invocationHook = genkitChannels.actionRun; const states = new WeakMap(); - const removeHandlers = invocationHook.intercept( - (target, receiver, args, additional) => { - const event = { - ...additional, - arguments: args, - self: receiver, - } as ChannelMessage; - const spanState = runInstrumentation( - () => - states.get(event) ?? ((event) => startActionRunSpan(event))(event), + invocationHook.intercept((target, receiver, args, additional) => { + const event = { + ...additional, + arguments: args, + self: receiver, + } as ChannelMessage; + const spanState = runInstrumentation( + () => + states.get(event) ?? ((event) => startActionRunSpan(event))(event), + ); + if (spanState) states.set(event, spanState); + const prepare = ( + event: ChannelMessage, + ) => { + ensureActionSpanState(states, event as object, () => + startActionRunSpan(event), ); - if (spanState) states.set(event, spanState); - const prepare = ( - event: ChannelMessage, - ) => { - ensureActionSpanState(states, event as object, () => - startActionRunSpan(event), - ); - }; - const resolved = ( - event: ChannelMessage, - ) => { - const state = states.get(event); - if (!state) { - return; - } - - try { - state.span.log({ - output: extractActionOutput(event.result), - metrics: durationMetrics(state.startTime), - }); - } finally { - state.span.end(); - states.delete(event); - } - }; - const failed = ( - event: ChannelMessage, - ) => { - const state = states.get(event); - if (!state || !event.error) { - return; - } - state.span.log({ error: event.error.message }); + }; + const resolved = ( + event: ChannelMessage, + ) => { + const state = states.get(event); + if (!state) { + return; + } + + try { + state.span.log({ + output: extractActionOutput(event.result), + metrics: durationMetrics(state.startTime), + }); + } finally { state.span.end(); states.delete(event); - }; - const invoke = () => { - runInstrumentation(() => prepare(event)); - let result; - try { - result = Reflect.apply(target, receiver, args); - } catch (error) { + } + }; + const failed = ( + event: ChannelMessage, + ) => { + const state = states.get(event); + if (!state || !event.error) { + return; + } + state.span.log({ error: event.error.message }); + state.span.end(); + states.delete(event); + }; + const invoke = () => { + runInstrumentation(() => prepare(event)); + let result; + try { + result = Reflect.apply(target, receiver, args); + } catch (error) { + Object.assign(event, { error }); + runInstrumentation(() => failed(event)); + throw error; + } + + return observeResult( + result, + (value) => { + Object.assign(event, { result: value }); + resolved(event); + }, + (error) => { Object.assign(event, { error }); - runInstrumentation(() => failed(event)); - throw error; - } - - return observeResult( - result, - (value) => { - Object.assign(event, { result: value }); - resolved(event); - }, - (error) => { - Object.assign(event, { error }); - failed(event); - }, - ); - }; - return spanState ? withCurrent(spanState.span, invoke) : invoke(); - }, - ); - this.unsubscribers.push(removeHandlers); + failed(event); + }, + ); + }; + return spanState ? withCurrent(spanState.span, invoke) : invoke(); + }); } private subscribeToActionSpan(): void { const invocationHook = genkitCoreChannels.actionSpan; const states = new WeakMap(); - const removeHandlers = invocationHook.intercept( - (target, receiver, args, additional) => { - const event = { - ...additional, - arguments: args, - self: receiver, - } as ChannelMessage; - const spanState = runInstrumentation( - () => states.get(event) ?? ((event) => startActionSpan(event))(event), + invocationHook.intercept((target, receiver, args, additional) => { + const event = { + ...additional, + arguments: args, + self: receiver, + } as ChannelMessage; + const spanState = runInstrumentation( + () => states.get(event) ?? ((event) => startActionSpan(event))(event), + ); + if (spanState) states.set(event, spanState); + const prepare = ( + event: ChannelMessage, + ) => { + ensureActionSpanState(states, event as object, () => + startActionSpan(event), ); - if (spanState) states.set(event, spanState); - const prepare = ( - event: ChannelMessage, - ) => { - ensureActionSpanState(states, event as object, () => - startActionSpan(event), - ); - }; - const resolved = ( - event: ChannelMessage, - ) => { - const state = states.get(event as object); - if (!state) { - return; - } - - try { - state.span.log({ - input: extractActionSpanInput(event.arguments), - output: extractActionOutput(event.result), - metrics: durationMetrics(state.startTime), - }); - } finally { - state.span.end(); - states.delete(event as object); - } - }; - const failed = ( - event: ChannelMessage, - ) => { - const state = states.get(event as object); - if (!state || !event.error) { - return; - } - state.span.log({ error: event.error.message }); + }; + const resolved = ( + event: ChannelMessage, + ) => { + const state = states.get(event as object); + if (!state) { + return; + } + + try { + state.span.log({ + input: extractActionSpanInput(event.arguments), + output: extractActionOutput(event.result), + metrics: durationMetrics(state.startTime), + }); + } finally { state.span.end(); states.delete(event as object); - }; - const invoke = () => { - runInstrumentation(() => prepare(event)); - let result; - try { - result = Reflect.apply(target, receiver, args); - } catch (error) { + } + }; + const failed = ( + event: ChannelMessage, + ) => { + const state = states.get(event as object); + if (!state || !event.error) { + return; + } + state.span.log({ error: event.error.message }); + state.span.end(); + states.delete(event as object); + }; + const invoke = () => { + runInstrumentation(() => prepare(event)); + let result; + try { + result = Reflect.apply(target, receiver, args); + } catch (error) { + Object.assign(event, { error }); + runInstrumentation(() => failed(event)); + throw error; + } + + return observeResult( + result, + (value) => { + Object.assign(event, { result: value }); + resolved(event); + }, + (error) => { Object.assign(event, { error }); - runInstrumentation(() => failed(event)); - throw error; - } - - return observeResult( - result, - (value) => { - Object.assign(event, { result: value }); - resolved(event); - }, - (error) => { - Object.assign(event, { error }); - failed(event); - }, - ); - }; - return spanState ? withCurrent(spanState.span, invoke) : invoke(); - }, - ); - this.unsubscribers.push(removeHandlers); + failed(event); + }, + ); + }; + return spanState ? withCurrent(spanState.span, invoke) : invoke(); + }); } private subscribeToActionStream(): void { - this.unsubscribers.push( - genkitChannels.actionStream.intercept( - (target, receiver, args, additional) => - traceSyncStreamCall( - () => Reflect.apply(target, receiver, args), - { ...additional, arguments: args, self: receiver }, - { - instrumentationName: INSTRUMENTATION_NAMES.GENKIT, - name: "genkit.action.stream", - type: SpanTypeAttribute.TASK, - extractInput: ([input], event) => ({ - input, - metadata: actionMetadataForLog( - extractActionMetadata(event.self), - ), - }), - patchResult: ({ result, span, startTime }) => - patchActionStreamResult(result, span, startTime), - }, - ), - ), + genkitChannels.actionStream.intercept( + (target, receiver, args, additional) => + traceSyncStreamCall( + () => Reflect.apply(target, receiver, args), + { ...additional, arguments: args, self: receiver }, + { + instrumentationName: INSTRUMENTATION_NAMES.GENKIT, + name: "genkit.action.stream", + type: SpanTypeAttribute.TASK, + extractInput: ([input], event) => ({ + input, + metadata: actionMetadataForLog(extractActionMetadata(event.self)), + }), + patchResult: ({ result, span, startTime }) => + patchActionStreamResult(result, span, startTime), + }, + ), ); } } @@ -815,3 +788,10 @@ function pickNumberMetrics( function stringValue(value: unknown): string | undefined { return typeof value === "string" ? value : undefined; } + +let genkitInstrumentationConsumer: GenkitInstrumentationConsumer | undefined; + +export function registerGenkitInstrumentation(): void { + genkitInstrumentationConsumer ??= new GenkitInstrumentationConsumer(); + genkitInstrumentationConsumer.register(); +} diff --git a/js/src/instrumentation/plugins/genkit-interceptors.ts b/js/src/instrumentation/providers/genkit-interceptors.ts similarity index 100% rename from js/src/instrumentation/plugins/genkit-interceptors.ts rename to js/src/instrumentation/providers/genkit-interceptors.ts diff --git a/js/src/instrumentation/plugins/github-copilot-plugin.test.ts b/js/src/instrumentation/providers/github-copilot-instrumentation.test.ts similarity index 78% rename from js/src/instrumentation/plugins/github-copilot-plugin.test.ts rename to js/src/instrumentation/providers/github-copilot-instrumentation.test.ts index 17ecb38d8..5f81a9969 100644 --- a/js/src/instrumentation/plugins/github-copilot-plugin.test.ts +++ b/js/src/instrumentation/providers/github-copilot-instrumentation.test.ts @@ -1,8 +1,8 @@ -import { describe, expect, it } from "vitest"; +import { describe, expect, it } from "vite-plus/test"; import { - GitHubCopilotPlugin, + registerGitHubCopilotInstrumentation, extractMetricsFromUsage, -} from "./github-copilot-plugin"; +} from "./github-copilot-instrumentation"; describe("extractMetricsFromUsage", () => { it("maps input/output tokens to standard metric keys", () => { @@ -119,38 +119,8 @@ describe("extractMetricsFromUsage", () => { }); }); -describe("GitHubCopilotPlugin lifecycle", () => { - it("enables without throwing", () => { - const plugin = new GitHubCopilotPlugin(); - expect(() => plugin.enable()).not.toThrow(); - plugin.disable(); - }); - - it("disables without throwing", () => { - const plugin = new GitHubCopilotPlugin(); - plugin.enable(); - expect(() => plugin.disable()).not.toThrow(); - }); - - it("enable is idempotent", () => { - const plugin = new GitHubCopilotPlugin(); - plugin.enable(); - expect(() => plugin.enable()).not.toThrow(); - plugin.disable(); - }); - - it("disable is idempotent", () => { - const plugin = new GitHubCopilotPlugin(); - plugin.enable(); - plugin.disable(); - expect(() => plugin.disable()).not.toThrow(); - }); - - it("can be re-enabled after disable", () => { - const plugin = new GitHubCopilotPlugin(); - plugin.enable(); - plugin.disable(); - expect(() => plugin.enable()).not.toThrow(); - plugin.disable(); +describe("registerGitHubCopilotInstrumentation", () => { + it("registers without throwing", () => { + expect(() => registerGitHubCopilotInstrumentation()).not.toThrow(); }); }); diff --git a/js/src/instrumentation/plugins/github-copilot-plugin.ts b/js/src/instrumentation/providers/github-copilot-instrumentation.ts similarity index 95% rename from js/src/instrumentation/plugins/github-copilot-plugin.ts rename to js/src/instrumentation/providers/github-copilot-instrumentation.ts index a8e43a085..608de8e4b 100644 --- a/js/src/instrumentation/plugins/github-copilot-plugin.ts +++ b/js/src/instrumentation/providers/github-copilot-instrumentation.ts @@ -1,7 +1,7 @@ import { debugLogger } from "../../debug-logger"; import { SpanTypeAttribute } from "../../../util/index"; -import type { Span } from "../../logger"; -import { startSpan as startBaseSpan } from "../../logger"; +import type { Span } from "../../logger/core"; +import { _internalStartSpan as startBaseSpan } from "../../logger/core"; import { INSTRUMENTATION_NAMES, withSpanInstrumentationName, @@ -18,7 +18,7 @@ import { extractAnthropicCacheTokens, finalizeAnthropicTokens, } from "../../wrappers/anthropic-tokens-util"; -import { BasePlugin } from "../core"; + import { observeResult, runInstrumentation } from "../core/observe-result"; import { gitHubCopilotChannels } from "./github-copilot-interceptors"; @@ -669,7 +669,7 @@ function isGitHubCopilotSession(value: unknown): value is GitHubCopilotSession { } // --------------------------------------------------------------------------- -// Plugin and handler factory +// Consumer and handler factory // --------------------------------------------------------------------------- // eslint-disable-next-line @typescript-eslint/no-explicit-any @@ -774,36 +774,37 @@ function traceSessionCreation( ); } -export class GitHubCopilotPlugin extends BasePlugin { - protected onEnable(): void { +class GitHubCopilotInstrumentationConsumer { + public register(): void { this.subscribeToSessionChannels(); } - protected onDisable(): void { - for (const unsubscribe of this.unsubscribers) { - unsubscribe(); - } - this.unsubscribers = []; - } - private subscribeToSessionChannels(): void { - this.unsubscribers.push( - gitHubCopilotChannels.createSession.intercept((target, receiver, args) => - traceSessionCreation( - () => Reflect.apply(target, receiver, args), - args, - 0, - true, - ), + gitHubCopilotChannels.createSession.intercept((target, receiver, args) => + traceSessionCreation( + () => Reflect.apply(target, receiver, args), + args, + 0, + true, ), - gitHubCopilotChannels.resumeSession.intercept((target, receiver, args) => - traceSessionCreation( - () => Reflect.apply(target, receiver, args), - args, - 1, - false, - ), + ); + gitHubCopilotChannels.resumeSession.intercept((target, receiver, args) => + traceSessionCreation( + () => Reflect.apply(target, receiver, args), + args, + 1, + false, ), ); } } + +let gitHubCopilotInstrumentationConsumer: + | GitHubCopilotInstrumentationConsumer + | undefined; + +export function registerGitHubCopilotInstrumentation(): void { + gitHubCopilotInstrumentationConsumer ??= + new GitHubCopilotInstrumentationConsumer(); + gitHubCopilotInstrumentationConsumer.register(); +} diff --git a/js/src/instrumentation/plugins/github-copilot-interceptors.ts b/js/src/instrumentation/providers/github-copilot-interceptors.ts similarity index 100% rename from js/src/instrumentation/plugins/github-copilot-interceptors.ts rename to js/src/instrumentation/providers/github-copilot-interceptors.ts diff --git a/js/src/instrumentation/plugins/google-adk-plugin.test.ts b/js/src/instrumentation/providers/google-adk-instrumentation.test.ts similarity index 90% rename from js/src/instrumentation/plugins/google-adk-plugin.test.ts rename to js/src/instrumentation/providers/google-adk-instrumentation.test.ts index fa20490c2..97d8732a7 100644 --- a/js/src/instrumentation/plugins/google-adk-plugin.test.ts +++ b/js/src/instrumentation/providers/google-adk-instrumentation.test.ts @@ -1,4 +1,11 @@ -import { afterEach, beforeEach, describe, expect, it, vi } from "vitest"; +import { + afterEach, + beforeEach, + describe, + expect, + it, + vi, +} from "vite-plus/test"; import { newGlobalInvocationHook } from "../../global-instrumentation-hooks"; import { invocationController } from "../test-utils/invocation"; vi.mock("../../global-instrumentation-hooks", async (importOriginal) => ({ @@ -18,11 +25,11 @@ const { mockInternalGetGlobalState: vi.fn(() => undefined), })); -vi.mock("../../isomorph", () => ({ +vi.mock("#braintrust-platform", () => ({ default: {}, })); -import { GoogleADKPlugin } from "./google-adk-plugin"; +import { registerGoogleADKInstrumentation } from "./google-adk-instrumentation"; const mockNewInvocationHook = newGlobalInvocationHook as ReturnType< typeof vi.fn @@ -35,7 +42,7 @@ const mockStartSpan = vi.fn(() => ({ export: vi.fn(() => Promise.resolve("mock-span-export")), })); -vi.mock("../../logger", () => ({ +vi.mock("../../logger/core", () => ({ startSpan: (...args: any[]) => (mockStartSpan as any)(...args), _internalGetGlobalState: (...args: any[]) => (mockInternalGetGlobalState as any)(...args), @@ -52,29 +59,23 @@ vi.mock("../../logger", () => ({ }, })); -describe("GoogleADKPlugin", () => { - let plugin: GoogleADKPlugin; +describe("registerGoogleADKInstrumentation", () => { let mockChannel: any; let subscribeSpy: any; - let unsubscribeSpy: any; let bindStoreSpy: any; - let unbindStoreSpy: any; beforeEach(() => { subscribeSpy = vi.fn(); - unsubscribeSpy = vi.fn(); bindStoreSpy = vi.fn(); - unbindStoreSpy = vi.fn(); mockChannel = { intercept: (interceptor: any) => { subscribeSpy(invocationController(interceptor)); - return unsubscribeSpy; + return vi.fn(); }, - unsubscribe: unsubscribeSpy, + hasSubscribers: false, start: { bindStore: bindStoreSpy, - unbindStore: unbindStoreSpy, }, }; @@ -82,16 +83,15 @@ describe("GoogleADKPlugin", () => { mockStartSpan.mockClear(); mockInternalGetGlobalState.mockReset(); mockInternalGetGlobalState.mockReturnValue(undefined); - plugin = new GoogleADKPlugin(); }); afterEach(() => { vi.clearAllMocks(); }); - describe("enable/disable lifecycle", () => { - it("should subscribe to channels when enabled", () => { - plugin.enable(); + describe("registration", () => { + it("subscribes to channels", () => { + registerGoogleADKInstrumentation(); // Should subscribe to 3 channels: runner.runAsync, agent.runAsync, tool.runAsync expect(mockNewInvocationHook).toHaveBeenCalledWith( @@ -105,43 +105,11 @@ describe("GoogleADKPlugin", () => { ); expect(subscribeSpy).toHaveBeenCalledTimes(3); }); - - it("should not subscribe multiple times if enabled twice", () => { - plugin.enable(); - const firstCallCount = subscribeSpy.mock.calls.length; - - plugin.enable(); - const secondCallCount = subscribeSpy.mock.calls.length; - - expect(firstCallCount).toBe(secondCallCount); - }); - - it("should unsubscribe from channels when disabled", () => { - plugin.enable(); - plugin.disable(); - - expect(unsubscribeSpy).toHaveBeenCalled(); - }); - - it("should clear unsubscribers array after disable", () => { - plugin.enable(); - plugin.disable(); - - // Enable again should re-subscribe - subscribeSpy.mockClear(); - plugin.enable(); - - expect(subscribeSpy).toHaveBeenCalledTimes(3); - }); - - it("should not crash when disabled without being enabled", () => { - expect(() => plugin.disable()).not.toThrow(); - }); }); describe("runner.runAsync channel", () => { it("should create a TASK span with runner metadata on start", () => { - plugin.enable(); + registerGoogleADKInstrumentation(); // Find the first subscribe call (runner channel) const handlers = subscribeSpy.mock.calls[0][0]; @@ -174,7 +142,7 @@ describe("GoogleADKPlugin", () => { }); it("should handle stream end with async iterable result", () => { - plugin.enable(); + registerGoogleADKInstrumentation(); const handlers = subscribeSpy.mock.calls[0][0]; const event: any = { @@ -205,7 +173,7 @@ describe("GoogleADKPlugin", () => { }); it("should handle error events", () => { - plugin.enable(); + registerGoogleADKInstrumentation(); const handlers = subscribeSpy.mock.calls[0][0]; const event: any = { @@ -226,7 +194,7 @@ describe("GoogleADKPlugin", () => { }); it("runs runner calls with the current span without creating duplicate spans", () => { - plugin.enable(); + registerGoogleADKInstrumentation(); const handlers = subscribeSpy.mock.calls[0][0]; const event = { @@ -357,7 +325,7 @@ describe("GoogleADKPlugin", () => { ])( "normalizes $name", async ({ usageMetadata, expectedMetrics, absentMetrics }) => { - plugin.enable(); + registerGoogleADKInstrumentation(); const handlers = subscribeSpy.mock.calls[0][0]; const event: any = { @@ -388,7 +356,7 @@ describe("GoogleADKPlugin", () => { ); it("preserves explicitly reported zero usage", async () => { - plugin.enable(); + registerGoogleADKInstrumentation(); const handlers = subscribeSpy.mock.calls[0][0]; const event: any = { @@ -437,7 +405,7 @@ describe("GoogleADKPlugin", () => { describe("agent.runAsync channel", () => { it("should create a TASK span with agent metadata on start", () => { - plugin.enable(); + registerGoogleADKInstrumentation(); // Agent channel is the second subscribe call const handlers = subscribeSpy.mock.calls[1][0]; @@ -464,7 +432,7 @@ describe("GoogleADKPlugin", () => { }); it("uses the invoked agent instance for names when parent context still points at a parent agent", () => { - plugin.enable(); + registerGoogleADKInstrumentation(); const handlers = subscribeSpy.mock.calls[1][0]; const event = { @@ -501,7 +469,7 @@ describe("GoogleADKPlugin", () => { }); it("should handle agent without a name gracefully", () => { - plugin.enable(); + registerGoogleADKInstrumentation(); const handlers = subscribeSpy.mock.calls[1][0]; const event = { @@ -518,7 +486,7 @@ describe("GoogleADKPlugin", () => { }); it("runs agent calls with the current span without creating duplicate spans", () => { - plugin.enable(); + registerGoogleADKInstrumentation(); const handlers = subscribeSpy.mock.calls[1][0]; const event = { @@ -561,7 +529,7 @@ describe("GoogleADKPlugin", () => { .mockImplementationOnce(() => runnerSpan) .mockImplementationOnce(() => agentSpan); - plugin.enable(); + registerGoogleADKInstrumentation(); const runnerHandlers = subscribeSpy.mock.calls[0][0]; const agentHandlers = subscribeSpy.mock.calls[1][0]; @@ -613,7 +581,7 @@ describe("GoogleADKPlugin", () => { describe("tool.runAsync channel", () => { it("should create a TOOL span on start", () => { - plugin.enable(); + registerGoogleADKInstrumentation(); // Tool channel is the third subscribe call const handlers = subscribeSpy.mock.calls[2][0]; @@ -650,7 +618,7 @@ describe("GoogleADKPlugin", () => { }); it("should log output and metrics on asyncEnd", () => { - plugin.enable(); + registerGoogleADKInstrumentation(); const handlers = subscribeSpy.mock.calls[2][0]; const event: any = { @@ -681,7 +649,7 @@ describe("GoogleADKPlugin", () => { }); it("should handle tool execution errors", () => { - plugin.enable(); + registerGoogleADKInstrumentation(); const handlers = subscribeSpy.mock.calls[2][0]; const event: any = { diff --git a/js/src/instrumentation/plugins/google-adk-plugin.ts b/js/src/instrumentation/providers/google-adk-instrumentation.ts similarity index 70% rename from js/src/instrumentation/plugins/google-adk-plugin.ts rename to js/src/instrumentation/providers/google-adk-instrumentation.ts index 7b694d108..ed4726bc9 100644 --- a/js/src/instrumentation/plugins/google-adk-plugin.ts +++ b/js/src/instrumentation/providers/google-adk-instrumentation.ts @@ -1,9 +1,8 @@ -import { BasePlugin } from "../core"; import { observeResult, runInstrumentation } from "../core/observe-result"; import { SpanTypeAttribute } from "../../../util/index"; -import type { Span } from "../../logger"; -import { startSpan as startBaseSpan, withCurrent } from "../../logger"; +import type { Span } from "../../logger/core"; +import { startSpan as startBaseSpan, withCurrent } from "../../logger/core"; import { INSTRUMENTATION_NAMES, withSpanInstrumentationName, @@ -43,9 +42,9 @@ type ToolState = { }; /** - * Auto-instrumentation plugin for the Google ADK. + * Internal auto-instrumentation consumer for the Google ADK. * - * This plugin subscribes to orchestrion channels for Google ADK methods + * This consumer subscribes to orchestrion channels for Google ADK methods * and creates Braintrust spans to track: * - Runner.runAsync — top-level agent execution (TASK span) * - BaseAgent.runAsync — individual agent invocations (TASK span) @@ -54,25 +53,16 @@ type ToolState = { * LLM calls made through ADK are automatically captured by the existing * @google/genai instrumentation since ADK uses GenAI internally. */ -export class GoogleADKPlugin extends BasePlugin { +class GoogleADKInstrumentationConsumer { private activeRunnerSpans = new Map(); private activeAgentSpans = new Map(); - protected onEnable(): void { + public register(): void { this.subscribeToRunnerRunAsync(); this.subscribeToAgentRunAsync(); this.subscribeToToolRunAsync(); } - protected onDisable(): void { - for (const unsubscribe of this.unsubscribers) { - unsubscribe(); - } - this.unsubscribers = []; - this.activeRunnerSpans.clear(); - this.activeAgentSpans.clear(); - } - private subscribeToRunnerRunAsync(): void { const invocationHook = googleADKChannels.runnerRunAsync; const states = new WeakMap(); @@ -115,90 +105,87 @@ export class GoogleADKPlugin extends BasePlugin { return { span, startTime, events: [], contextKey }; }; - const removeHandlers = invocationHook.intercept( - (target, receiver, args, additional) => { - const event = { - ...additional, - arguments: args, - self: receiver, - } as ChannelMessage; - const spanState = runInstrumentation( - () => states.get(event) ?? createState(event), - ); - if (spanState) states.set(event, spanState); - const prepare = ( - event: ChannelMessage, - ) => { - ensureState(states, event, () => createState(event)); - }; - const returned = ( - event: ChannelMessage, - ) => { - const state = states.get(event); - if (!state) { - return; - } - - const result = event.result; - if (isAsyncIterable(result)) { - bindAsyncIterableToCurrentSpan(result, state.span); - patchStreamIfNeeded(result, { - onChunk: (adkEvent: GoogleADKEvent) => { - state.events.push(adkEvent); - }, - onComplete: () => { - finalizeRunnerSpan(state, this.activeRunnerSpans); - states.delete(event); - }, - onError: (error: Error) => { - cleanupActiveRunnerSpan(state, this.activeRunnerSpans); - state.span.log({ error: error.message }); - state.span.end(); - states.delete(event); - }, - }); - return; - } - - // Non-streaming case (unlikely for runners but handle gracefully) - try { - state.span.log({ output: result }); - } finally { - cleanupActiveRunnerSpan(state, this.activeRunnerSpans); - state.span.end(); - states.delete(event); - } - }; - const failed = ( - event: ChannelMessage, - ) => { - const state = states.get(event); - if (!state || !event.error) { - return; - } + invocationHook.intercept((target, receiver, args, additional) => { + const event = { + ...additional, + arguments: args, + self: receiver, + } as ChannelMessage; + const spanState = runInstrumentation( + () => states.get(event) ?? createState(event), + ); + if (spanState) states.set(event, spanState); + const prepare = ( + event: ChannelMessage, + ) => { + ensureState(states, event, () => createState(event)); + }; + const returned = ( + event: ChannelMessage, + ) => { + const state = states.get(event); + if (!state) { + return; + } + + const result = event.result; + if (isAsyncIterable(result)) { + bindAsyncIterableToCurrentSpan(result, state.span); + patchStreamIfNeeded(result, { + onChunk: (adkEvent: GoogleADKEvent) => { + state.events.push(adkEvent); + }, + onComplete: () => { + finalizeRunnerSpan(state, this.activeRunnerSpans); + states.delete(event); + }, + onError: (error: Error) => { + cleanupActiveRunnerSpan(state, this.activeRunnerSpans); + state.span.log({ error: error.message }); + state.span.end(); + states.delete(event); + }, + }); + return; + } + + // Non-streaming case (unlikely for runners but handle gracefully) + try { + state.span.log({ output: result }); + } finally { cleanupActiveRunnerSpan(state, this.activeRunnerSpans); - state.span.log({ error: event.error.message }); state.span.end(); states.delete(event); - }; - const invoke = () => { - runInstrumentation(() => prepare(event)); - let result; - try { - result = Reflect.apply(target, receiver, args); - } catch (error) { - Object.assign(event, { error }); - runInstrumentation(() => failed(event)); - throw error; - } - Object.assign(event, { result }); - runInstrumentation(() => returned(event)); - return result; - }; - return spanState ? withCurrent(spanState.span, invoke) : invoke(); - }, - ); - this.unsubscribers.push(removeHandlers); + } + }; + const failed = ( + event: ChannelMessage, + ) => { + const state = states.get(event); + if (!state || !event.error) { + return; + } + cleanupActiveRunnerSpan(state, this.activeRunnerSpans); + state.span.log({ error: event.error.message }); + state.span.end(); + states.delete(event); + }; + const invoke = () => { + runInstrumentation(() => prepare(event)); + let result; + try { + result = Reflect.apply(target, receiver, args); + } catch (error) { + Object.assign(event, { error }); + runInstrumentation(() => failed(event)); + throw error; + } + Object.assign(event, { result }); + runInstrumentation(() => returned(event)); + return result; + }; + return spanState ? withCurrent(spanState.span, invoke) : invoke(); + }); } private subscribeToAgentRunAsync(): void { @@ -265,202 +252,196 @@ export class GoogleADKPlugin extends BasePlugin { return { span, startTime, events: [], contextKey, name: agentName }; }; - const removeHandlers = invocationHook.intercept( - (target, receiver, args, additional) => { - const event = { - ...additional, - arguments: args, - self: receiver, - } as ChannelMessage; - const spanState = runInstrumentation( - () => states.get(event) ?? createState(event), - ); - if (spanState) states.set(event, spanState); - const prepare = ( - event: ChannelMessage, - ) => { - ensureState(states, event, () => createState(event)); - }; - const returned = ( - event: ChannelMessage, - ) => { - const state = states.get(event); - if (!state) { - return; - } - - const result = event.result; - if (isAsyncIterable(result)) { - bindAsyncIterableToCurrentSpan(result, state.span); - patchStreamIfNeeded(result, { - onChunk: (adkEvent: GoogleADKEvent) => { - state.events.push(adkEvent); - }, - onComplete: () => { - finalizeAgentSpan(state, this.activeAgentSpans); - states.delete(event); - }, - onError: (error: Error) => { - cleanupActiveAgentSpan(state, this.activeAgentSpans); - state.span.log({ error: error.message }); - state.span.end(); - states.delete(event); - }, - }); - return; - } - - try { - state.span.log({ output: result }); - } finally { - cleanupActiveAgentSpan(state, this.activeAgentSpans); - state.span.end(); - states.delete(event); - } - }; - const failed = ( - event: ChannelMessage, - ) => { - const state = states.get(event); - if (!state || !event.error) { - return; - } + invocationHook.intercept((target, receiver, args, additional) => { + const event = { + ...additional, + arguments: args, + self: receiver, + } as ChannelMessage; + const spanState = runInstrumentation( + () => states.get(event) ?? createState(event), + ); + if (spanState) states.set(event, spanState); + const prepare = ( + event: ChannelMessage, + ) => { + ensureState(states, event, () => createState(event)); + }; + const returned = ( + event: ChannelMessage, + ) => { + const state = states.get(event); + if (!state) { + return; + } + + const result = event.result; + if (isAsyncIterable(result)) { + bindAsyncIterableToCurrentSpan(result, state.span); + patchStreamIfNeeded(result, { + onChunk: (adkEvent: GoogleADKEvent) => { + state.events.push(adkEvent); + }, + onComplete: () => { + finalizeAgentSpan(state, this.activeAgentSpans); + states.delete(event); + }, + onError: (error: Error) => { + cleanupActiveAgentSpan(state, this.activeAgentSpans); + state.span.log({ error: error.message }); + state.span.end(); + states.delete(event); + }, + }); + return; + } + + try { + state.span.log({ output: result }); + } finally { cleanupActiveAgentSpan(state, this.activeAgentSpans); - state.span.log({ error: event.error.message }); state.span.end(); states.delete(event); - }; - const invoke = () => { - runInstrumentation(() => prepare(event)); - let result; - try { - result = Reflect.apply(target, receiver, args); - } catch (error) { - Object.assign(event, { error }); - runInstrumentation(() => failed(event)); - throw error; - } - Object.assign(event, { result }); - runInstrumentation(() => returned(event)); - return result; - }; - return spanState ? withCurrent(spanState.span, invoke) : invoke(); - }, - ); - this.unsubscribers.push(removeHandlers); + } + }; + const failed = ( + event: ChannelMessage, + ) => { + const state = states.get(event); + if (!state || !event.error) { + return; + } + cleanupActiveAgentSpan(state, this.activeAgentSpans); + state.span.log({ error: event.error.message }); + state.span.end(); + states.delete(event); + }; + const invoke = () => { + runInstrumentation(() => prepare(event)); + let result; + try { + result = Reflect.apply(target, receiver, args); + } catch (error) { + Object.assign(event, { error }); + runInstrumentation(() => failed(event)); + throw error; + } + Object.assign(event, { result }); + runInstrumentation(() => returned(event)); + return result; + }; + return spanState ? withCurrent(spanState.span, invoke) : invoke(); + }); } private subscribeToToolRunAsync(): void { const invocationHook = googleADKChannels.toolRunAsync; const states = new WeakMap(); - const removeHandlers = invocationHook.intercept( - (target, receiver, args, additional) => { - const event = { - ...additional, - arguments: args, - self: receiver, - } as ChannelMessage; - const prepare = ( - event: ChannelMessage, - ) => { - const req = (event.arguments[0] ?? {}) as GoogleADKToolRunRequest; - const tool = event.self as GoogleADKBaseTool | undefined; - - const toolName = extractToolName(req, tool); - const parentSpan = findToolParentSpan( - req, - this.activeAgentSpans, - this.activeRunnerSpans, - ); + invocationHook.intercept((target, receiver, args, additional) => { + const event = { + ...additional, + arguments: args, + self: receiver, + } as ChannelMessage; + const prepare = ( + event: ChannelMessage, + ) => { + const req = (event.arguments[0] ?? {}) as GoogleADKToolRunRequest; + const tool = event.self as GoogleADKBaseTool | undefined; + + const toolName = extractToolName(req, tool); + const parentSpan = findToolParentSpan( + req, + this.activeAgentSpans, + this.activeRunnerSpans, + ); - const createSpan = () => - startBaseSpan( - withSpanInstrumentationName( - { - name: toolName ? `tool: ${toolName}` : "Google ADK Tool", - spanAttributes: { - type: SpanTypeAttribute.TOOL, - }, - event: { - input: req.args, - metadata: { - provider: "google-adk", - ...(toolName && { "google_adk.tool_name": toolName }), - ...(extractToolCallId(req) && { - "google_adk.tool_call_id": extractToolCallId(req), - }), - }, + const createSpan = () => + startBaseSpan( + withSpanInstrumentationName( + { + name: toolName ? `tool: ${toolName}` : "Google ADK Tool", + spanAttributes: { + type: SpanTypeAttribute.TOOL, + }, + event: { + input: req.args, + metadata: { + provider: "google-adk", + ...(toolName && { "google_adk.tool_name": toolName }), + ...(extractToolCallId(req) && { + "google_adk.tool_call_id": extractToolCallId(req), + }), }, }, - INSTRUMENTATION_NAMES.GOOGLE_ADK, - ), - ); - const span = parentSpan - ? withCurrent(parentSpan, () => createSpan()) - : createSpan(); - const startTime = getCurrentUnixTimestamp(); - - states.set(event, { span, startTime }); - }; - const resolved = ( - event: ChannelMessage, - ) => { - const state = states.get(event); - if (!state) { - return; - } - - try { - const metrics: Record = {}; - const end = getCurrentUnixTimestamp(); - metrics.start = state.startTime; - metrics.end = end; - metrics.duration = end - state.startTime; - - state.span.log({ - output: event.result, - metrics: cleanMetrics(metrics), - }); - } finally { - state.span.end(); - states.delete(event); - } - }; - const failed = ( - event: ChannelMessage, - ) => { - const state = states.get(event); - if (!state || !event.error) { - return; - } - state.span.log({ error: event.error.message }); + }, + INSTRUMENTATION_NAMES.GOOGLE_ADK, + ), + ); + const span = parentSpan + ? withCurrent(parentSpan, () => createSpan()) + : createSpan(); + const startTime = getCurrentUnixTimestamp(); + + states.set(event, { span, startTime }); + }; + const resolved = ( + event: ChannelMessage, + ) => { + const state = states.get(event); + if (!state) { + return; + } + + try { + const metrics: Record = {}; + const end = getCurrentUnixTimestamp(); + metrics.start = state.startTime; + metrics.end = end; + metrics.duration = end - state.startTime; + + state.span.log({ + output: event.result, + metrics: cleanMetrics(metrics), + }); + } finally { state.span.end(); states.delete(event); - }; - runInstrumentation(() => prepare(event)); - let result; - try { - result = Reflect.apply(target, receiver, args); - } catch (error) { - Object.assign(event, { error }); - runInstrumentation(() => failed(event)); - throw error; } - return observeResult( - result, - (value) => { - Object.assign(event, { result: value }); - resolved(event); - }, - (error) => { - Object.assign(event, { error }); - failed(event); - }, - ); - }, - ); - this.unsubscribers.push(removeHandlers); + }; + const failed = ( + event: ChannelMessage, + ) => { + const state = states.get(event); + if (!state || !event.error) { + return; + } + state.span.log({ error: event.error.message }); + state.span.end(); + states.delete(event); + }; + runInstrumentation(() => prepare(event)); + let result; + try { + result = Reflect.apply(target, receiver, args); + } catch (error) { + Object.assign(event, { error }); + runInstrumentation(() => failed(event)); + throw error; + } + return observeResult( + result, + (value) => { + Object.assign(event, { result: value }); + resolved(event); + }, + (error) => { + Object.assign(event, { error }); + failed(event); + }, + ); + }); } } @@ -1028,3 +1009,12 @@ function cleanMetrics(metrics: Record): Record { } return cleaned; } + +let googleADKInstrumentationConsumer: + | GoogleADKInstrumentationConsumer + | undefined; + +export function registerGoogleADKInstrumentation(): void { + googleADKInstrumentationConsumer ??= new GoogleADKInstrumentationConsumer(); + googleADKInstrumentationConsumer.register(); +} diff --git a/js/src/instrumentation/plugins/google-adk-interceptors.ts b/js/src/instrumentation/providers/google-adk-interceptors.ts similarity index 100% rename from js/src/instrumentation/plugins/google-adk-interceptors.ts rename to js/src/instrumentation/providers/google-adk-interceptors.ts diff --git a/js/src/instrumentation/plugins/google-genai-embedding-usage.test.ts b/js/src/instrumentation/providers/google-genai-embedding-usage.test.ts similarity index 95% rename from js/src/instrumentation/plugins/google-genai-embedding-usage.test.ts rename to js/src/instrumentation/providers/google-genai-embedding-usage.test.ts index 35a1b726b..b491d143b 100644 --- a/js/src/instrumentation/plugins/google-genai-embedding-usage.test.ts +++ b/js/src/instrumentation/providers/google-genai-embedding-usage.test.ts @@ -1,5 +1,13 @@ -import { afterEach, beforeAll, beforeEach, describe, expect, it } from "vitest"; -import { _exportsForTestingOnly, initLogger, traced } from "../../logger"; +import { + afterEach, + beforeAll, + beforeEach, + describe, + expect, + it, +} from "vite-plus/test"; +import { _exportsForTestingOnly } from "../../logger/testing"; +import { initLogger, traced } from "../../logger/core"; import { configureNode } from "../../node/config"; import { wrapGoogleGenAI } from "../../wrappers/google-genai"; import type { GoogleGenAIEmbedContentResponse } from "../../vendor-sdk-types/google-genai"; diff --git a/js/src/instrumentation/plugins/google-genai-plugin.test.ts b/js/src/instrumentation/providers/google-genai-instrumentation.test.ts similarity index 90% rename from js/src/instrumentation/plugins/google-genai-plugin.test.ts rename to js/src/instrumentation/providers/google-genai-instrumentation.test.ts index 177f1fbf7..ab5d78871 100644 --- a/js/src/instrumentation/plugins/google-genai-plugin.test.ts +++ b/js/src/instrumentation/providers/google-genai-instrumentation.test.ts @@ -1,4 +1,12 @@ -import { afterEach, beforeEach, describe, expect, it, vi } from "vitest"; +import iso from "#braintrust-platform"; +import { + afterEach, + beforeEach, + describe, + expect, + it, + vi, +} from "vite-plus/test"; import { newGlobalInvocationHook } from "../../global-instrumentation-hooks"; import { invocationController } from "../test-utils/invocation"; vi.mock("../../global-instrumentation-hooks", async (importOriginal) => ({ @@ -9,9 +17,8 @@ vi.mock("../../global-instrumentation-hooks", async (importOriginal) => ({ })); // Mock platform context independently of invocation hooks. -vi.mock("../../isomorph", () => ({ +vi.mock("#braintrust-platform", () => ({ default: { - getEnv: () => undefined, newAsyncLocalStorage: vi.fn(() => { let current: unknown; return { @@ -27,11 +34,15 @@ vi.mock("../../isomorph", () => ({ }), }; }), + newTracingChannel: vi.fn(), + getEnv: vi.fn((name: string) => + name === "BRAINTRUST_AUTO_CAPTURE_ATTACHMENTS" ? "true" : undefined, + ), }, })); -import { startSpan } from "../../logger"; -import { GoogleGenAIPlugin } from "./google-genai-plugin"; +import { registerGoogleGenAIInstrumentation } from "./google-genai-instrumentation"; +import { startSpan } from "../../logger/core"; const mockNewInvocationHook = newGlobalInvocationHook as ReturnType< typeof vi.fn @@ -39,31 +50,36 @@ const mockNewInvocationHook = newGlobalInvocationHook as ReturnType< const mockStartSpan = vi.mocked(startSpan); // Mock logger -vi.mock("../../logger", () => ({ - startSpan: vi.fn(() => ({ - log: vi.fn(), - end: vi.fn(), - })), - _internalGetGlobalState: vi.fn(() => undefined), - currentSpan: vi.fn(() => undefined), - withCurrent: vi.fn((_span: unknown, callback: () => unknown) => callback()), - Attachment: class MockAttachment { - reference: any; - constructor(params: any) { - this.reference = { - filename: params.filename, - content_type: params.contentType, - }; - } - }, -})); +vi.mock("../../logger/core", async () => { + const { default: iso } = await import("#braintrust-platform"); + return { + _internalCaptureAttachmentsEnabled: () => + iso.getEnv("BRAINTRUST_AUTO_CAPTURE_ATTACHMENTS") === "true", + BaseAttachment: class {}, + CAPTURE_ATTACHMENTS: Symbol.for("braintrust.captureAttachments"), + startSpan: vi.fn(() => ({ + log: vi.fn(), + end: vi.fn(), + })), + _internalGetGlobalState: vi.fn(() => undefined), + currentSpan: vi.fn(() => undefined), + withCurrent: vi.fn((_span: unknown, callback: () => unknown) => callback()), + Attachment: class MockAttachment { + reference: any; + constructor(params: any) { + this.reference = { + filename: params.filename, + content_type: params.contentType, + }; + } + }, + }; +}); -describe("GoogleGenAIPlugin", () => { - let plugin: GoogleGenAIPlugin; +describe("registerGoogleGenAIInstrumentation", () => { let mockChannel: any; let interceptSpy: any; let subscribeSpy: any; - let unsubscribeSpy: any; beforeEach(() => { interceptSpy = vi.fn((interceptor: unknown) => { @@ -71,11 +87,9 @@ describe("GoogleGenAIPlugin", () => { return vi.fn(); }); subscribeSpy = vi.fn(); - unsubscribeSpy = vi.fn(); mockChannel = { intercept: interceptSpy, subscribe: subscribeSpy, - unsubscribe: unsubscribeSpy, hasSubscribers: false, }; @@ -96,55 +110,20 @@ describe("GoogleGenAIPlugin", () => { } else { interceptSpy(interceptor); } - return unsubscribeSpy; + return vi.fn(); }, }; return mockChannel; }); - plugin = new GoogleGenAIPlugin(); }); afterEach(() => { vi.clearAllMocks(); }); - describe("enable/disable lifecycle", () => { - it("should not subscribe multiple times if enabled twice", () => { - plugin.enable(); - const firstCallCount = subscribeSpy.mock.calls.length; - - plugin.enable(); - const secondCallCount = subscribeSpy.mock.calls.length; - - expect(firstCallCount).toBe(secondCallCount); - }); - - it("should unsubscribe from channels when disabled", () => { - plugin.enable(); - plugin.disable(); - - expect(unsubscribeSpy).toHaveBeenCalled(); - }); - - it("should clear unsubscribers array after disable", () => { - plugin.enable(); - plugin.disable(); - - // Enable again should re-subscribe - subscribeSpy.mockClear(); - plugin.enable(); - - expect(subscribeSpy).toHaveBeenCalled(); - }); - - it("should not crash when disabled without being enabled", () => { - expect(() => plugin.disable()).not.toThrow(); - }); - }); - describe("generateContent channel subscription", () => { it("should extract input correctly", () => { - plugin.enable(); + registerGoogleGenAIInstrumentation(); expect(subscribeSpy).toHaveBeenCalled(); @@ -227,7 +206,7 @@ describe("GoogleGenAIPlugin", () => { ])( "normalizes $name", ({ usageMetadata, expectedMetrics, absentMetrics }) => { - plugin.enable(); + registerGoogleGenAIInstrumentation(); const handlers = subscribeSpy.mock.calls[0][0]; const event: any = { @@ -255,7 +234,7 @@ describe("GoogleGenAIPlugin", () => { ); it("preserves explicitly reported zero usage", () => { - plugin.enable(); + registerGoogleGenAIInstrumentation(); const handlers = subscribeSpy.mock.calls[0][0]; const event: any = { @@ -301,7 +280,7 @@ describe("GoogleGenAIPlugin", () => { }); it("converts generated inline images to attachments", () => { - plugin.enable(); + registerGoogleGenAIInstrumentation(); const handlers = subscribeSpy.mock.calls[0][0]; const event: any = { arguments: [ @@ -363,7 +342,7 @@ describe("GoogleGenAIPlugin", () => { }); it("converts generated inline video to a file attachment", () => { - plugin.enable(); + registerGoogleGenAIInstrumentation(); const handlers = subscribeSpy.mock.calls[0][0]; const event: any = { arguments: [ @@ -428,7 +407,7 @@ describe("GoogleGenAIPlugin", () => { describe("generateContentStream channel subscription", () => { it("aggregates streamed inline image and audio parts as attachments", async () => { - plugin.enable(); + registerGoogleGenAIInstrumentation(); const handlers = subscribeSpy.mock.calls[1][0]; const event: any = { arguments: [ @@ -544,7 +523,7 @@ describe("GoogleGenAIPlugin", () => { }); it("keeps the finish reason of a stream that ends without content", async () => { - plugin.enable(); + registerGoogleGenAIInstrumentation(); const handlers = subscribeSpy.mock.calls[1][0]; const event: any = { arguments: [ @@ -600,7 +579,7 @@ describe("GoogleGenAIPlugin", () => { }); it("keeps the prompt feedback of a stream whose prompt was blocked", async () => { - plugin.enable(); + registerGoogleGenAIInstrumentation(); const handlers = subscribeSpy.mock.calls[1][0]; const event: any = { arguments: [ @@ -638,7 +617,7 @@ describe("GoogleGenAIPlugin", () => { describe("generateImages channel interception", () => { it("captures canonical image generation input and all returned images", async () => { - plugin.enable(); + registerGoogleGenAIInstrumentation(); const interceptor = interceptSpy.mock.calls[1]?.[0]; const params = { model: "imagen-4.0-generate-001", @@ -731,7 +710,7 @@ describe("GoogleGenAIPlugin", () => { }); it("logs provider rejections without changing them", async () => { - plugin.enable(); + registerGoogleGenAIInstrumentation(); const interceptor = interceptSpy.mock.calls[1]?.[0]; const providerError = new Error("Imagen is unavailable"); const providerPromise = Promise.reject(providerError); @@ -762,7 +741,7 @@ describe("GoogleGenAIPlugin", () => { describe("editImage channel interception", () => { it("captures reference images, masks, parameters, and edited images", async () => { - plugin.enable(); + registerGoogleGenAIInstrumentation(); const interceptor = interceptSpy.mock.calls[2]?.[0]; const params = { model: "imagen-3.0-capability-001", @@ -885,7 +864,7 @@ describe("GoogleGenAIPlugin", () => { describe("generateVideos channel interception", () => { it("captures submission input and immediately returned videos", async () => { - plugin.enable(); + registerGoogleGenAIInstrumentation(); const interceptor = interceptSpy.mock.calls[3]?.[0]; const params = { model: "veo-3.1-fast-generate-preview", @@ -986,7 +965,7 @@ describe("GoogleGenAIPlugin", () => { }); it("does not poll a pending video operation", async () => { - plugin.enable(); + registerGoogleGenAIInstrumentation(); const interceptor = interceptSpy.mock.calls[3]?.[0]; const response = { done: false, name: "operations/video-1" }; const providerPromise = Promise.resolve(response); @@ -1045,7 +1024,7 @@ describe("GoogleGenAIPlugin", () => { ])( "preserves embedding boundaries for %s with %j", (model, contents, inputs) => { - plugin.enable(); + registerGoogleGenAIInstrumentation(); const handlers = subscribeSpy.mock.calls[2][0]; handlers.begin({ arguments: [ @@ -1074,7 +1053,7 @@ describe("GoogleGenAIPlugin", () => { ); it("normalizes inline and remote media without mutating the request", () => { - plugin.enable(); + registerGoogleGenAIInstrumentation(); const handlers = subscribeSpy.mock.calls[2][0]; const params = { model: "gemini-embedding-2-preview", @@ -1137,7 +1116,7 @@ describe("GoogleGenAIPlugin", () => { }); it("retains all inline media when any attachment conversion fails", () => { - plugin.enable(); + registerGoogleGenAIInstrumentation(); const handlers = subscribeSpy.mock.calls[2][0]; handlers.begin({ arguments: [ @@ -1202,7 +1181,7 @@ describe("GoogleGenAIPlugin", () => { ])( "logs count and only reported embedding usage for %j", (result, count, metrics) => { - plugin.enable(); + registerGoogleGenAIInstrumentation(); const handlers = subscribeSpy.mock.calls[2][0]; const event = { arguments: [ @@ -1226,7 +1205,7 @@ describe("GoogleGenAIPlugin", () => { ); it("logs provider errors without exposing vectors", () => { - plugin.enable(); + registerGoogleGenAIInstrumentation(); const handlers = subscribeSpy.mock.calls[2][0]; const error = new Error("unsupported input"); const event = { @@ -1242,8 +1221,134 @@ describe("GoogleGenAIPlugin", () => { }); describe("interactions.create channel subscription", () => { + it.each([ + [false, false], + [false, true], + [true, false], + [true, true], + ])( + "preserves application JSON while processing media with capture=%s, streaming=%s", + async (captureAttachments, streaming) => { + const getEnv = vi.mocked(iso.getEnv); + const originalGetEnv = getEnv.getMockImplementation()!; + getEnv.mockImplementation((name) => + name === "BRAINTRUST_AUTO_CAPTURE_ATTACHMENTS" + ? String(captureAttachments) + : undefined, + ); + try { + registerGoogleGenAIInstrumentation(); + const handlers = subscribeSpy.mock.calls[3][0]; + const data = { mimeType: "application/json", data: { count: 1 } }; + const mediaShapedJSON = { + type: "image", + mime_type: "image/png", + data: "ordinary application data", + }; + const media = { type: "image", mime_type: "image/png", data: "AQID" }; + const content = [ + { + type: "function_call", + name: "report", + arguments: { data, mediaShapedJSON }, + }, + { + type: "function_result", + name: "report", + result: { data, mediaShapedJSON }, + }, + media, + ]; + const result = { + id: "interaction-json", + input: content, + steps: [ + ...content.slice(0, 2), + { type: "model_output", content: [media] }, + ], + metadata: { data, mediaShapedJSON }, + }; + const event: any = { + arguments: [ + { + model: "gemini-2.5-flash", + input: content, + agent_config: { data, mediaShapedJSON }, + }, + ], + result: streaming + ? (async function* () { + for (const [index, step] of result.steps.entries()) { + yield { event_type: "step.start", index, step }; + } + yield { + event_type: "interaction.completed", + interaction: result, + }; + })() + : result, + }; + handlers.begin(event); + const span = mockStartSpan.mock.results.at(-1)!.value; + handlers.resolve(event); + if (streaming) { + for await (const _chunk of event.result) { + // Consume the provider stream to trigger final trace aggregation. + } + } + const expectedContent = [ + ...content.slice(0, 2), + { + type: "image", + mime_type: "image/png", + ...(captureAttachments + ? { + data: expect.objectContaining({ + reference: { + filename: "file.png", + content_type: "image/png", + }, + }), + } + : {}), + }, + ]; + expect(span.log).toHaveBeenNthCalledWith( + 1, + expect.objectContaining({ + input: expect.objectContaining({ + input: expectedContent, + agent_config: { data, mediaShapedJSON }, + }), + metadata: expect.objectContaining({ + agent_config: { data, mediaShapedJSON }, + }), + }), + ); + const expectedSteps = [ + ...content.slice(0, 2), + { type: "model_output", content: [expectedContent[2]] }, + ]; + expect(span.log).toHaveBeenLastCalledWith( + expect.objectContaining({ + output: { + ...result, + input: expectedContent, + steps: streaming + ? expectedSteps.map((step, index) => ({ ...step, index })) + : expectedSteps, + }, + }), + ); + expect(media.data).toBe("AQID"); + } finally { + getEnv.mockImplementation(originalGetEnv); + } + }, + ); + it("subscribes to the interactions.create channel", () => { - plugin.enable(); + registerGoogleGenAIInstrumentation(); expect(mockNewInvocationHook).toHaveBeenCalledWith( "orchestrion:@google/genai:interactions.create", @@ -1252,7 +1357,7 @@ describe("GoogleGenAIPlugin", () => { }); it("logs non-streaming interaction output and metrics", () => { - plugin.enable(); + registerGoogleGenAIInstrumentation(); const handlers = subscribeSpy.mock.calls[3][0]; const scheduledAt = new Date("2026-01-02T03:04:05.000Z"); @@ -1380,7 +1485,7 @@ describe("GoogleGenAIPlugin", () => { }); it("captures direct interaction video generation canonically", () => { - plugin.enable(); + registerGoogleGenAIInstrumentation(); const handlers = subscribeSpy.mock.calls[3][0]; const event: any = { @@ -1481,7 +1586,7 @@ describe("GoogleGenAIPlugin", () => { }); it("preserves zero and missing interaction usage values", () => { - plugin.enable(); + registerGoogleGenAIInstrumentation(); const handlers = subscribeSpy.mock.calls[3][0]; const event: any = { @@ -1557,7 +1662,7 @@ describe("GoogleGenAIPlugin", () => { }); it("does not trace background interaction tasks", () => { - plugin.enable(); + registerGoogleGenAIInstrumentation(); const handlers = subscribeSpy.mock.calls[3][0]; const event: any = { @@ -1581,7 +1686,7 @@ describe("GoogleGenAIPlugin", () => { }); it("aggregates streaming interaction events when consumed", async () => { - plugin.enable(); + registerGoogleGenAIInstrumentation(); async function* stream() { yield { @@ -1668,7 +1773,7 @@ describe("GoogleGenAIPlugin", () => { }); it("ends the interaction span when a stream errors", async () => { - plugin.enable(); + registerGoogleGenAIInstrumentation(); const streamError = new Error("stream failed"); async function* stream() { @@ -1720,7 +1825,7 @@ describe("Google GenAI serialization functions", () => { contents: "Hello world", }; - // Since these are private functions, we'll test them through the plugin behavior + // Since these are private functions, test them through consumer behavior. // For now, we'll verify the structure by examining what gets logged expect(params.model).toBe("gemini-pro"); expect(params.contents).toBe("Hello world"); diff --git a/js/src/instrumentation/plugins/google-genai-plugin.ts b/js/src/instrumentation/providers/google-genai-instrumentation.ts similarity index 78% rename from js/src/instrumentation/plugins/google-genai-plugin.ts rename to js/src/instrumentation/providers/google-genai-instrumentation.ts index eeb4aebac..4e74c7145 100644 --- a/js/src/instrumentation/plugins/google-genai-plugin.ts +++ b/js/src/instrumentation/providers/google-genai-instrumentation.ts @@ -2,21 +2,24 @@ import { uint8ArrayToBase64 } from "../../../util/bytes"; import { debugLogger } from "../../debug-logger"; import { getExtensionFromMediaType, + omitMediaData, processInputAttachments, } from "../../wrappers/attachment-utils"; -import { BasePlugin } from "../core"; -import { traceStreamingCall, unsubscribeAll } from "../core/tracing"; + +import { traceStreamingCall } from "../core/tracing"; import { observeResult, runInstrumentation } from "../core/observe-result"; import { SpanTypeAttribute } from "../../../util/index"; import { + _internalCaptureAttachmentsEnabled, Attachment, + CAPTURE_ATTACHMENTS, currentSpan, startSpan as startBaseSpan, withCurrent, type Span, type StartSpanArgs, -} from "../../logger"; +} from "../../logger/core"; import { INSTRUMENTATION_NAMES, withSpanInstrumentationName, @@ -63,6 +66,7 @@ type GenerateContentStreamEvent = googleGenAIInput?: Record; googleGenAIMetadata?: Record; googleGenAIStartTime?: number; + captureAttachments?: boolean; }; type SpanState = { @@ -105,15 +109,11 @@ function createWrapperParityEvent(args: { * - Converting inline data (images) to Attachment objects * - Tool calls (functionCall, functionResponse) and executable code results */ -export class GoogleGenAIPlugin extends BasePlugin { - protected onEnable(): void { +class GoogleGenAIInstrumentationConsumer { + public register(): void { this.subscribeToGoogleGenAIChannels(); } - protected onDisable(): void { - this.unsubscribers = unsubscribeAll(this.unsubscribers); - } - private subscribeToGoogleGenAIChannels(): void { this.subscribeToGenerateContentChannel(); this.subscribeToGenerateContentStreamChannel(); @@ -128,42 +128,16 @@ export class GoogleGenAIPlugin extends BasePlugin { const invocationHook = googleGenAIChannels.generateContent; const states = new WeakMap(); - const removeHandlers = invocationHook.intercept( - (target, receiver, args, additional) => { - const event = { - ...additional, - arguments: args, - self: receiver, - } as ChannelMessage; - const spanState = runInstrumentation( - () => - states.get(event) ?? - ((event) => { - const params = event.arguments[0]; - const input = serializeGenerateContentInput(params); - const metadata = extractGenerateContentMetadata(params); - const span = startBaseSpan( - withSpanInstrumentationName( - { - name: "generate_content", - spanAttributes: { - type: SpanTypeAttribute.LLM, - }, - event: createWrapperParityEvent({ input, metadata }), - }, - INSTRUMENTATION_NAMES.GOOGLE_GENAI, - ), - ); - - return { - span, - startTime: getCurrentUnixTimestamp(), - }; - })(event), - ); - if (spanState) states.set(event, spanState); - const prepare = (event: ChannelMessage) => { - ensureSpanState(states, event, () => { + invocationHook.intercept((target, receiver, args, additional) => { + const event = { + ...additional, + arguments: args, + self: receiver, + } as ChannelMessage; + const spanState = runInstrumentation( + () => + states.get(event) ?? + ((event) => { const params = event.arguments[0]; const input = serializeGenerateContentInput(params); const metadata = extractGenerateContentMetadata(params); @@ -184,185 +158,170 @@ export class GoogleGenAIPlugin extends BasePlugin { span, startTime: getCurrentUnixTimestamp(), }; - }); - }; - const resolved = (event: ChannelMessage) => { - const spanState = states.get(event as object); - if (!spanState) { - return; - } - - try { - const responseMetadata = extractResponseMetadata(event.result); - spanState.span.log({ - ...(responseMetadata ? { metadata: responseMetadata } : {}), - metrics: cleanMetrics( - extractGenerateContentMetrics( - event.result, - spanState.startTime, - ), - ), - output: serializeGenerateContentOutput(event.result), - }); - } finally { - spanState.span.end(); - states.delete(event as object); - } - }; - const failed = (event: ChannelMessage) => { - logErrorAndEndSpan(states, event as ErrorOf); - }; - const invoke = () => { - runInstrumentation(() => prepare(event)); - let result; - try { - result = Reflect.apply(target, receiver, args); - } catch (error) { - Object.assign(event, { error }); - runInstrumentation(() => failed(event)); - throw error; - } - - return observeResult( - result, - (value) => { - Object.assign(event, { result: value }); - resolved(event); - }, - (error) => { - Object.assign(event, { error }); - failed(event); - }, + })(event), + ); + if (spanState) states.set(event, spanState); + const prepare = (event: ChannelMessage) => { + ensureSpanState(states, event, () => { + const params = event.arguments[0]; + const input = serializeGenerateContentInput(params); + const metadata = extractGenerateContentMetadata(params); + const span = startBaseSpan( + withSpanInstrumentationName( + { + name: "generate_content", + spanAttributes: { + type: SpanTypeAttribute.LLM, + }, + event: createWrapperParityEvent({ input, metadata }), + }, + INSTRUMENTATION_NAMES.GOOGLE_GENAI, + ), ); - }; - return spanState ? withCurrent(spanState.span, invoke) : invoke(); - }, - ); - this.unsubscribers.push(removeHandlers); - } - private subscribeToGenerateContentStreamChannel(): void { - const invocationHook = googleGenAIChannels.generateContentStream; + return { + span, + startTime: getCurrentUnixTimestamp(), + }; + }); + }; + const resolved = (event: ChannelMessage) => { + const spanState = states.get(event as object); + if (!spanState) { + return; + } - const removeHandlers = invocationHook.intercept( - (target, receiver, args, additional) => { - const event = { - ...additional, - arguments: args, - self: receiver, - } as ChannelMessage; - const prepare = ( - event: ChannelMessage, - ) => { - const streamEvent = event as GenerateContentStreamEvent; - const params = event.arguments[0]; - streamEvent.googleGenAIInput = serializeGenerateContentInput(params); - streamEvent.googleGenAIMetadata = - extractGenerateContentMetadata(params); - streamEvent.googleGenAIStartTime = getCurrentUnixTimestamp(); - }; - const resolved = ( - event: ChannelMessage, - ) => { - const streamEvent = event as GenerateContentStreamEvent; - patchGoogleGenAIStreamingResult({ - input: streamEvent.googleGenAIInput, - metadata: streamEvent.googleGenAIMetadata, - startTime: streamEvent.googleGenAIStartTime, - result: streamEvent.result, + try { + const responseMetadata = extractResponseMetadata(event.result); + spanState.span.log({ + ...(responseMetadata ? { metadata: responseMetadata } : {}), + metrics: cleanMetrics( + extractGenerateContentMetrics(event.result, spanState.startTime), + ), + output: withCurrent(spanState.span, () => + serializeGenerateContentOutput(event.result), + ), }); - }; + } finally { + spanState.span.end(); + states.delete(event as object); + } + }; + const failed = (event: ChannelMessage) => { + logErrorAndEndSpan(states, event as ErrorOf); + }; + const invoke = () => { runInstrumentation(() => prepare(event)); - const result = Reflect.apply(target, receiver, args); + let result; + try { + result = Reflect.apply(target, receiver, args); + } catch (error) { + Object.assign(event, { error }); + runInstrumentation(() => failed(event)); + throw error; + } + return observeResult( result, (value) => { Object.assign(event, { result: value }); resolved(event); }, - () => {}, + (error) => { + Object.assign(event, { error }); + failed(event); + }, ); - }, - ); - this.unsubscribers.push(removeHandlers); + }; + return spanState ? withCurrent(spanState.span, invoke) : invoke(); + }); + } + + private subscribeToGenerateContentStreamChannel(): void { + const invocationHook = googleGenAIChannels.generateContentStream; + + invocationHook.intercept((target, receiver, args, additional) => { + const event = { + ...additional, + arguments: args, + self: receiver, + } as ChannelMessage; + const prepare = (event: ChannelMessage) => { + const streamEvent = event as GenerateContentStreamEvent; + const params = event.arguments[0]; + streamEvent.googleGenAIInput = serializeGenerateContentInput(params); + streamEvent.googleGenAIMetadata = + extractGenerateContentMetadata(params); + streamEvent.googleGenAIStartTime = getCurrentUnixTimestamp(); + streamEvent.captureAttachments = _internalCaptureAttachmentsEnabled(); + }; + const resolved = ( + event: ChannelMessage, + ) => { + const streamEvent = event as GenerateContentStreamEvent; + patchGoogleGenAIStreamingResult({ + captureAttachments: streamEvent.captureAttachments, + input: streamEvent.googleGenAIInput, + metadata: streamEvent.googleGenAIMetadata, + startTime: streamEvent.googleGenAIStartTime, + result: streamEvent.result, + }); + }; + runInstrumentation(() => prepare(event)); + const result = Reflect.apply(target, receiver, args); + return observeResult( + result, + (value) => { + Object.assign(event, { result: value }); + resolved(event); + }, + () => {}, + ); + }); } private subscribeToEmbedContentChannel(): void { const invocationHook = googleGenAIChannels.embedContent; const states = new WeakMap(); const embeddingSpans = new WeakSet(); - this.unsubscribers.push( - googleGenAIChannels.httpResponseJson.intercept( - (target, thisArg, args) => { - const span = currentSpan(); - const result = Reflect.apply(target, thisArg, args); - if (embeddingSpans.has(span)) { - // Observe the SDK's own JSON parsing, without cloning/consuming the - // response again or retaining the embedding vectors. - void Promise.resolve(result).then( - (response) => { - try { - const metrics = cleanMetrics( - extractEmbedContentMetrics(response), - ); - if ( - embeddingSpans.has(span) && - Object.keys(metrics).length > 0 - ) { - span.log({ metrics }); - } - } catch (error) { - debugLogger.error( - "Error reading Google GenAI embedding usage:", - error, - ); - } - }, - () => {}, // The embedding channel handles the original rejection. - ); - } - return result; - }, - ), - ); - - const removeHandlers = invocationHook.intercept( - (target, receiver, args, additional) => { - const event = { - ...additional, - arguments: args, - self: receiver, - } as ChannelMessage; - const spanState = runInstrumentation( - () => - states.get(event) ?? - ((event) => { - const params = event.arguments[0]; - const input = serializeEmbedContentInput(params); - const metadata = { provider: "google", model: params.model }; - const span = startBaseSpan( - withSpanInstrumentationName( - { - name: "embed_content", - spanAttributes: { - type: SpanTypeAttribute.LLM, - }, - event: createWrapperParityEvent({ input, metadata }), - }, - INSTRUMENTATION_NAMES.GOOGLE_GENAI, - ), + googleGenAIChannels.httpResponseJson.intercept((target, thisArg, args) => { + const span = currentSpan(); + const result = Reflect.apply(target, thisArg, args); + if (embeddingSpans.has(span)) { + // Observe the SDK's own JSON parsing, without cloning/consuming the + // response again or retaining the embedding vectors. + void Promise.resolve(result).then( + (response) => { + try { + const metrics = cleanMetrics( + extractEmbedContentMetrics(response), ); - - embeddingSpans.add(span); - return { - span, - startTime: getCurrentUnixTimestamp(), - }; - })(event), + if (embeddingSpans.has(span) && Object.keys(metrics).length > 0) { + span.log({ metrics }); + } + } catch (error) { + debugLogger.error( + "Error reading Google GenAI embedding usage:", + error, + ); + } + }, + () => {}, // The embedding channel handles the original rejection. ); - if (spanState) states.set(event, spanState); - const prepare = (event: ChannelMessage) => { - ensureSpanState(states, event, () => { + } + return result; + }); + + invocationHook.intercept((target, receiver, args, additional) => { + const event = { + ...additional, + arguments: args, + self: receiver, + } as ChannelMessage; + const spanState = runInstrumentation( + () => + states.get(event) ?? + ((event) => { const params = event.arguments[0]; const input = serializeEmbedContentInput(params); const metadata = { provider: "google", model: params.model }; @@ -384,149 +343,165 @@ export class GoogleGenAIPlugin extends BasePlugin { span, startTime: getCurrentUnixTimestamp(), }; + })(event), + ); + if (spanState) states.set(event, spanState); + const prepare = (event: ChannelMessage) => { + ensureSpanState(states, event, () => { + const params = event.arguments[0]; + const input = serializeEmbedContentInput(params); + const metadata = { provider: "google", model: params.model }; + const span = startBaseSpan( + withSpanInstrumentationName( + { + name: "embed_content", + spanAttributes: { + type: SpanTypeAttribute.LLM, + }, + event: createWrapperParityEvent({ input, metadata }), + }, + INSTRUMENTATION_NAMES.GOOGLE_GENAI, + ), + ); + + embeddingSpans.add(span); + return { + span, + startTime: getCurrentUnixTimestamp(), + }; + }); + }; + const resolved = (event: ChannelMessage) => { + const spanState = states.get(event as object); + if (!spanState) { + return; + } + + try { + spanState.span.log({ + output: summarizeEmbedContentOutput(event.result), + metrics: cleanMetrics( + extractEmbedContentMetrics(event.result, spanState.startTime), + ), }); - }; - const resolved = (event: ChannelMessage) => { - const spanState = states.get(event as object); - if (!spanState) { - return; - } + } finally { + embeddingSpans.delete(spanState.span); + spanState.span.end(); + states.delete(event as object); + } + }; + const failed = (event: ChannelMessage) => { + const spanState = states.get(event as object); + if (!spanState) return; + try { + spanState.span.log({ error: event.error, output: { count: 0 } }); + } finally { + embeddingSpans.delete(spanState.span); + spanState.span.end(); + states.delete(event as object); + } + }; + const invoke = () => { + runInstrumentation(() => prepare(event)); + let result; + try { + result = Reflect.apply(target, receiver, args); + } catch (error) { + Object.assign(event, { error }); + runInstrumentation(() => failed(event)); + throw error; + } - try { - spanState.span.log({ - output: summarizeEmbedContentOutput(event.result), - metrics: cleanMetrics( - extractEmbedContentMetrics(event.result, spanState.startTime), - ), - }); - } finally { - embeddingSpans.delete(spanState.span); - spanState.span.end(); - states.delete(event as object); - } - }; - const failed = (event: ChannelMessage) => { - const spanState = states.get(event as object); - if (!spanState) return; - try { - spanState.span.log({ error: event.error, output: { count: 0 } }); - } finally { - embeddingSpans.delete(spanState.span); - spanState.span.end(); - states.delete(event as object); - } - }; - const invoke = () => { - runInstrumentation(() => prepare(event)); - let result; - try { - result = Reflect.apply(target, receiver, args); - } catch (error) { + return observeResult( + result, + (value) => { + Object.assign(event, { result: value }); + resolved(event); + }, + (error) => { Object.assign(event, { error }); - runInstrumentation(() => failed(event)); - throw error; - } - - return observeResult( - result, - (value) => { - Object.assign(event, { result: value }); - resolved(event); - }, - (error) => { - Object.assign(event, { error }); - failed(event); - }, - ); - }; - return spanState ? withCurrent(spanState.span, invoke) : invoke(); - }, - ); - this.unsubscribers.push(removeHandlers); + failed(event); + }, + ); + }; + return spanState ? withCurrent(spanState.span, invoke) : invoke(); + }); } private subscribeToInteractionsCreateChannel(): void { - this.unsubscribers.push( - googleGenAIChannels.interactionsCreate.intercept( - (target, receiver, args, additional) => - traceStreamingCall( - () => Reflect.apply(target, receiver, args), - { ...additional, arguments: args, self: receiver }, - { - instrumentationName: INSTRUMENTATION_NAMES.GOOGLE_GENAI, - name: ([params]) => - isVideoInteractionCreate(params) - ? "generate_video" - : "create_interaction", - shouldTrace: ([params]) => !isBackgroundInteractionCreate(params), - type: SpanTypeAttribute.LLM, - extractInput: ([params]) => ({ - input: isVideoInteractionCreate(params) - ? serializeVideoInteractionInput(params) - : serializeInteractionInput(params), - metadata: isVideoInteractionCreate(params) - ? { model: params.model, provider: "google" } - : extractInteractionMetadata(params), - }), - extractOutput: (result, event) => - isVideoInteractionCreate(event?.arguments?.[0]) || - getInteractionVideoOutput(result).length > 0 - ? serializeVideoInteractionOutput(result) - : serializeInteractionValue(result), - extractMetadata: (result) => - extractInteractionResponseMetadata(result), - extractMetrics: (result, startTime) => - cleanMetrics(extractInteractionMetrics(result, startTime)), - aggregateChunks: (chunks, _result, _event, startTime) => - aggregateInteractionEvents(chunks, startTime), - }, - ), - ), + googleGenAIChannels.interactionsCreate.intercept( + (target, receiver, args, additional) => + traceStreamingCall( + () => Reflect.apply(target, receiver, args), + { ...additional, arguments: args, self: receiver }, + { + instrumentationName: INSTRUMENTATION_NAMES.GOOGLE_GENAI, + name: ([params]) => + isVideoInteractionCreate(params) + ? "generate_video" + : "create_interaction", + shouldTrace: ([params]) => !isBackgroundInteractionCreate(params), + type: SpanTypeAttribute.LLM, + extractInput: ([params]) => ({ + input: isVideoInteractionCreate(params) + ? serializeVideoInteractionInput(params) + : serializeInteractionInput(params), + metadata: isVideoInteractionCreate(params) + ? { model: params.model, provider: "google" } + : extractInteractionMetadata(params), + }), + extractOutput: (result, event) => + isVideoInteractionCreate(event?.arguments?.[0]) || + getInteractionVideoOutput(result).length > 0 + ? serializeVideoInteractionOutput(result) + : serializeInteractionValue(result), + extractMetadata: (result) => + extractInteractionResponseMetadata(result), + extractMetrics: (result, startTime) => + cleanMetrics(extractInteractionMetrics(result, startTime)), + aggregateChunks: (chunks, _result, _event, startTime) => + aggregateInteractionEvents(chunks, startTime), + }, + ), ); } private subscribeToGenerateImagesChannel(): void { - this.unsubscribers.push( - googleGenAIChannels.generateImages.intercept( - (target, receiver, args, additional) => - traceGoogleGenAIMediaCall( - () => Reflect.apply(target, receiver, args), - { arguments: args, self: receiver, additional }, - "generate_images", - serializeGenerateImagesInput, - serializeGenerateImagesOutput, - ), - ), + googleGenAIChannels.generateImages.intercept( + (target, receiver, args, additional) => + traceGoogleGenAIMediaCall( + () => Reflect.apply(target, receiver, args), + { arguments: args, self: receiver, additional }, + "generate_images", + serializeGenerateImagesInput, + serializeGenerateImagesOutput, + ), ); } private subscribeToEditImageChannel(): void { - this.unsubscribers.push( - googleGenAIChannels.editImage.intercept( - (target, receiver, args, additional) => - traceGoogleGenAIMediaCall( - () => Reflect.apply(target, receiver, args), - { arguments: args, self: receiver, additional }, - "edit_image", - serializeEditImageInput, - serializeGenerateImagesOutput, - ), - ), + googleGenAIChannels.editImage.intercept( + (target, receiver, args, additional) => + traceGoogleGenAIMediaCall( + () => Reflect.apply(target, receiver, args), + { arguments: args, self: receiver, additional }, + "edit_image", + serializeEditImageInput, + serializeGenerateImagesOutput, + ), ); } private subscribeToGenerateVideosChannel(): void { - this.unsubscribers.push( - googleGenAIChannels.generateVideos.intercept( - (target, receiver, args, additional) => - traceGoogleGenAIMediaCall( - () => Reflect.apply(target, receiver, args), - { arguments: args, self: receiver, additional }, - "generate_videos", - serializeGenerateVideosInput, - serializeGenerateVideosOutput, - ), - ), + googleGenAIChannels.generateVideos.intercept( + (target, receiver, args, additional) => + traceGoogleGenAIMediaCall( + () => Reflect.apply(target, receiver, args), + { arguments: args, self: receiver, additional }, + "generate_videos", + serializeGenerateVideosInput, + serializeGenerateVideosOutput, + ), ); } } @@ -596,7 +571,9 @@ function traceGoogleGenAIMediaCall( try { void Promise.resolve(result).then((response) => { try { - span.log({ output: serializeOutput(response, params) }); + span.log({ + output: withCurrent(span, () => serializeOutput(response, params)), + }); } catch (error) { debugLogger.error( `Error capturing Google GenAI ${name} output:`, @@ -649,12 +626,19 @@ function logErrorAndEndSpan( } function patchGoogleGenAIStreamingResult(args: { + captureAttachments?: boolean; input: Record | undefined; metadata: Record | undefined; startTime: number | undefined; result: unknown; }): boolean { - const { input, metadata, result, startTime } = args; + const { + input, + metadata, + result, + startTime, + captureAttachments = _internalCaptureAttachmentsEnabled(), + } = args; if ( !input || @@ -679,6 +663,7 @@ function patchGoogleGenAIStreamingResult(args: { withSpanInstrumentationName( { name: "generate_content_stream", + [CAPTURE_ATTACHMENTS]: captureAttachments, spanAttributes: { type: SpanTypeAttribute.LLM, }, @@ -800,7 +785,35 @@ function patchGoogleGenAIStreamingResult(args: { if (firstTokenTime === null) { firstTokenTime = getCurrentUnixTimestamp(); } - chunks.push(nextResult.value); + chunks.push( + captureAttachments + ? nextResult.value + : { + ...nextResult.value, + candidates: nextResult.value.candidates?.map( + (candidate) => ({ + ...candidate, + content: candidate.content + ? { + ...candidate.content, + parts: candidate.content.parts?.map((part) => + part.inlineData + ? { + ...part, + // This copy is only retained for trace aggregation. + inlineData: omitMediaData( + part.inlineData, + "data", + ) as GoogleGenAIPart["inlineData"], + } + : part, + ), + } + : candidate.content, + }), + ), + }, + ); } if (nextResult.done) { @@ -809,6 +822,7 @@ function patchGoogleGenAIStreamingResult(args: { chunks, requestStartTime, firstTokenTime, + captureAttachments, ), }); } @@ -836,6 +850,7 @@ function patchGoogleGenAIStreamingResult(args: { chunks, requestStartTime, firstTokenTime, + captureAttachments, ), }); } else { @@ -911,7 +926,9 @@ function serializeGenerateContentOutput( ? { content: { ...candidate.content, - parts: candidate.content.parts.map((part) => serializePart(part)), + parts: candidate.content.parts + .map((part) => serializePart(part)) + .filter((part) => part !== undefined), }, } : {}), @@ -1292,7 +1309,7 @@ function serializeInteractionInput( ]) { const value = params[key]; if (value !== undefined) { - input[key] = serializeInteractionValue(value); + input[key] = serializeInteractionValue(value, false); } } @@ -1317,13 +1334,13 @@ function extractInteractionMetadata( ]) { const value = params[key]; if (value !== undefined) { - metadata[key] = serializeInteractionValue(value); + metadata[key] = serializeInteractionValue(value, false); } } if (Array.isArray(params.tools)) { metadata.tools = params.tools.map((tool) => - serializeInteractionValue(tool), + serializeInteractionValue(tool, false), ); } @@ -1355,7 +1372,9 @@ function serializeContentItem(item: string | GoogleGenAIContent): unknown { if (item.parts && Array.isArray(item.parts)) { return { ...item, - parts: item.parts.map((part: GoogleGenAIPart) => serializePart(part)), + parts: item.parts + .map((part: GoogleGenAIPart) => serializePart(part)) + .filter((part) => part !== undefined), }; } return item; @@ -1371,14 +1390,27 @@ function serializeContentItem(item: string | GoogleGenAIContent): unknown { /** * Serialize a part, converting inline data to Attachments. */ -function serializePart(part: GoogleGenAIPart): unknown { +function serializePart( + part: GoogleGenAIPart, + captureAttachments = _internalCaptureAttachmentsEnabled(), +): unknown { if (!part || typeof part !== "object") { return part; } + if (part.inlineData && !captureAttachments) + return omitMediaData({ + ...part, + inlineData: omitMediaData(part.inlineData, "data"), + }); if (part.inlineData && part.inlineData.data) { const { data, mimeType } = part.inlineData; - const attachment = createAttachmentFromInlineData(data, mimeType); + const attachment = createAttachmentFromInlineData( + data, + mimeType, + undefined, + captureAttachments, + ); if (attachment) { return mimeType.startsWith("image/") @@ -1571,6 +1603,7 @@ function serializeVideoInteractionOutput( function serializeInteractionValue( value: unknown, + processMedia = true, seen = new WeakSet(), ): unknown { if (value === null || value === undefined || typeof value !== "object") { @@ -1578,7 +1611,9 @@ function serializeInteractionValue( } if (Array.isArray(value)) { - return value.map((item) => serializeInteractionValue(item, seen)); + return value.map((item) => + serializeInteractionValue(item, processMedia, seen), + ); } const dict: unknown = tryToDict(value); @@ -1587,7 +1622,9 @@ function serializeInteractionValue( } if (Array.isArray(dict)) { - return dict.map((item) => serializeInteractionValue(item, seen)); + return dict.map((item) => + serializeInteractionValue(item, processMedia, seen), + ); } if (seen.has(dict)) { @@ -1597,22 +1634,48 @@ function serializeInteractionValue( seen.add(dict); try { const serialized: Record = {}; + const isMedia = + processMedia && + "type" in dict && + typeof dict.type === "string" && + ["image", "audio", "video", "document"].includes(dict.type); const mimeType = "mime_type" in dict && typeof dict.mime_type === "string" ? dict.mime_type : "mimeType" in dict && typeof dict.mimeType === "string" ? dict.mimeType : undefined; + const captureAttachments = isMedia && _internalCaptureAttachmentsEnabled(); const attachment = - mimeType && "data" in dict && dict.data !== undefined + captureAttachments && + mimeType && + "data" in dict && + dict.data !== undefined ? createAttachmentFromInlineData(dict.data, mimeType) : null; - for (const [key, entry] of Object.entries(dict)) { + for (const key of Object.keys(dict)) { + if (key === "data" && isMedia && !captureAttachments) continue; + const entry: unknown = Reflect.get(dict, key); if (key === "data" && attachment) { serialized[key] = attachment; } else { - serialized[key] = serializeInteractionValue(entry, seen); + // Only descend into provider content containers for media conversion. + // Tool arguments/results and metadata remain ordinary application JSON. + serialized[key] = serializeInteractionValue( + entry, + processMedia && + [ + "input", + "content", + "steps", + "outputs", + "output_image", + "output_audio", + "output_video", + ].includes(key), + seen, + ); } } @@ -1626,7 +1689,9 @@ function createAttachmentFromInlineData( data: unknown, mimeType?: string, filename = `file.${mimeType ? getExtensionFromMediaType(mimeType) : "bin"}`, + captureAttachments = _internalCaptureAttachmentsEnabled(), ): Attachment | null { + if (!captureAttachments) return null; if ( !( data instanceof Uint8Array || @@ -1977,6 +2042,7 @@ function aggregateGenerateContentChunks( chunks: GoogleGenAIGenerateContentResponse[], startTime: number, firstTokenTime: number | null, + captureAttachments: boolean, ): { aggregated: Record; metrics: Record; @@ -2035,7 +2101,9 @@ function aggregateGenerateContentChunks( } else if (part.executableCode) { otherParts.push({ executableCode: part.executableCode }); } else if (part.inlineData || part.fileData) { - const serializedPart = tryToDict(serializePart(part)); + const serializedPart = tryToDict( + serializePart(part, captureAttachments), + ); if (serializedPart) { otherParts.push(serializedPart); } @@ -2191,7 +2259,7 @@ function aggregateInteractionEvents( output.output_text = outputText; } if (latestUsage) { - output.usage = serializeInteractionValue(latestUsage); + output.usage = serializeInteractionValue(latestUsage, false); } const compactSteps = Array.from(steps.values()).sort( @@ -2253,7 +2321,10 @@ function compactInteractionStep(step: unknown): Record { "is_error", ]) { if (stepDict[key] !== undefined) { - compact[key] = serializeInteractionValue(stepDict[key]); + compact[key] = serializeInteractionValue( + stepDict[key], + key === "content", + ); } } @@ -2364,3 +2435,9 @@ function tryToDict(obj: unknown): Record | null { return null; } + +let consumer: GoogleGenAIInstrumentationConsumer | undefined; +export function registerGoogleGenAIInstrumentation(): void { + consumer ??= new GoogleGenAIInstrumentationConsumer(); + consumer.register(); +} diff --git a/js/src/instrumentation/plugins/google-genai-interceptors.ts b/js/src/instrumentation/providers/google-genai-interceptors.ts similarity index 100% rename from js/src/instrumentation/plugins/google-genai-interceptors.ts rename to js/src/instrumentation/providers/google-genai-interceptors.ts diff --git a/js/src/instrumentation/plugins/google-generative-ai-plugin.test.ts b/js/src/instrumentation/providers/google-generative-ai-instrumentation.test.ts similarity index 91% rename from js/src/instrumentation/plugins/google-generative-ai-plugin.test.ts rename to js/src/instrumentation/providers/google-generative-ai-instrumentation.test.ts index 277d13c7a..164a3c743 100644 --- a/js/src/instrumentation/plugins/google-generative-ai-plugin.test.ts +++ b/js/src/instrumentation/providers/google-generative-ai-instrumentation.test.ts @@ -6,8 +6,9 @@ import { expect, it, vi, -} from "vitest"; -import { Attachment, _exportsForTestingOnly, initLogger } from "../../logger"; +} from "vite-plus/test"; +import { Attachment, initLogger } from "../../logger/core"; +import { _exportsForTestingOnly } from "../../logger/testing"; import { configureNode } from "../../node/config"; import { wrapGoogleGenerativeAI } from "../../wrappers/google-generative-ai"; import { googleGenerativeAIChannels as channels } from "./google-generative-ai-interceptors"; @@ -22,6 +23,7 @@ describe("Google Generative AI instrumentation", () => { beforeEach(() => { logger = _exportsForTestingOnly.useTestBackgroundLogger(); initLogger({ + autoCaptureAttachments: true, projectName: "tmp-luca-google-generative-ai-tests", projectId: "test-project-id", }); @@ -366,23 +368,35 @@ describe("Google Generative AI instrumentation", () => { ]); }); - it("converts image inputs without mutating the provider request", async () => { - const inlineData = { data: "aGVsbG8=", mimeType: "image/png" }; - const request = [{ inlineData }]; - await channels.generateContent.invoke( - async () => ({ response: {} }), - { model: "models/test" }, - [request], - {}, - ); - const spans = await logger.drain(); - expect(spans[0]).toHaveProperty( - "input.contents.0.parts.0.inlineData.data", - expect.any(Attachment), - ); - expect(request[0].inlineData).toBe(inlineData); - expect(inlineData.data).toBe("aGVsbG8="); - }); + it.each([false, true])( + "applies capture=%s to image inputs without mutating the provider request", + async (captureAttachments) => { + initLogger({ + projectName: "tmp-luca-google-generative-ai-tests", + projectId: "test-project-id", + autoCaptureAttachments: captureAttachments, + }); + const inlineData = { data: "aGVsbG8=", mimeType: "image/png" }; + const request = [{ inlineData }]; + await channels.generateContent.invoke( + async () => ({ response: {} }), + { model: "models/test" }, + [request], + {}, + ); + const spans = await logger.drain(); + if (captureAttachments) { + expect(spans[0]).toHaveProperty( + "input.contents.0.parts.0.inlineData.data", + expect.any(Attachment), + ); + } else { + expect(spans[0]).toHaveProperty("input.contents.0.parts", []); + } + expect(request[0].inlineData).toBe(inlineData); + expect(inlineData.data).toBe("aGVsbG8="); + }, + ); it("keeps all original input when any attachment cannot be converted", async () => { const request = [ diff --git a/js/src/instrumentation/plugins/google-generative-ai-plugin.ts b/js/src/instrumentation/providers/google-generative-ai-instrumentation.ts similarity index 85% rename from js/src/instrumentation/plugins/google-generative-ai-plugin.ts rename to js/src/instrumentation/providers/google-generative-ai-instrumentation.ts index 0cec783fa..43287aecd 100644 --- a/js/src/instrumentation/plugins/google-generative-ai-plugin.ts +++ b/js/src/instrumentation/providers/google-generative-ai-instrumentation.ts @@ -1,12 +1,16 @@ +import { _internalCaptureAttachmentsEnabled } from "../../logger/core"; import { SpanTypeAttribute } from "../../../util/index"; import { debugLogger } from "../../debug-logger"; -import { startSpan, withCurrent, type Span } from "../../logger"; +import { startSpan, withCurrent, type Span } from "../../logger/core"; import { INSTRUMENTATION_NAMES, withSpanInstrumentationName, } from "../../span-origin"; import { getCurrentUnixTimestamp, isObject } from "../../util"; -import { processInputAttachments } from "../../wrappers/attachment-utils"; +import { + omitMediaData, + processInputAttachments, +} from "../../wrappers/attachment-utils"; import type { GenerativeAIChat, GenerativeAIConfig, @@ -24,8 +28,6 @@ import { isAutoInstrumentationSuppressed, runWithAutoInstrumentationSuppressed, } from "../auto-instrumentation-suppression"; -import { BasePlugin } from "../core"; -import { unsubscribeAll } from "../core/tracing"; import { patchStreamIfNeeded } from "../core/stream-patcher"; import { googleGenerativeAIChannels } from "./google-generative-ai-interceptors"; @@ -56,63 +58,55 @@ type Request = | { requests: GenerativeAIEmbedRequest[] }; type Operation = keyof typeof googleGenerativeAIChannels; -export class GoogleGenerativeAIPlugin extends BasePlugin { - protected onEnable(): void { - this.unsubscribers.push( - googleGenerativeAIChannels.generateContent.intercept( - (target, receiver, args, additional) => - traceGenerativeAICall( - () => Reflect.apply(target, receiver, args), - { arguments: args, self: receiver, additional }, - "generateContent", - ), +export function registerGoogleGenerativeAIInstrumentation(): void { + googleGenerativeAIChannels.generateContent.intercept( + (target, receiver, args, additional) => + traceGenerativeAICall( + () => Reflect.apply(target, receiver, args), + { arguments: args, self: receiver, additional }, + "generateContent", ), - googleGenerativeAIChannels.generateContentStream.intercept( - (target, receiver, args, additional) => - traceGenerativeAICall( - () => Reflect.apply(target, receiver, args), - { arguments: args, self: receiver, additional }, - "generateContentStream", - ), + ); + googleGenerativeAIChannels.generateContentStream.intercept( + (target, receiver, args, additional) => + traceGenerativeAICall( + () => Reflect.apply(target, receiver, args), + { arguments: args, self: receiver, additional }, + "generateContentStream", ), - googleGenerativeAIChannels.sendMessage.intercept( - (target, receiver, args, additional) => - traceGenerativeAICall( - () => Reflect.apply(target, receiver, args), - { arguments: args, self: receiver, additional }, - "sendMessage", - ), + ); + googleGenerativeAIChannels.sendMessage.intercept( + (target, receiver, args, additional) => + traceGenerativeAICall( + () => Reflect.apply(target, receiver, args), + { arguments: args, self: receiver, additional }, + "sendMessage", ), - googleGenerativeAIChannels.sendMessageStream.intercept( - (target, receiver, args, additional) => - traceGenerativeAICall( - () => Reflect.apply(target, receiver, args), - { arguments: args, self: receiver, additional }, - "sendMessageStream", - ), + ); + googleGenerativeAIChannels.sendMessageStream.intercept( + (target, receiver, args, additional) => + traceGenerativeAICall( + () => Reflect.apply(target, receiver, args), + { arguments: args, self: receiver, additional }, + "sendMessageStream", ), - googleGenerativeAIChannels.embedContent.intercept( - (target, receiver, args, additional) => - traceGenerativeAICall( - () => Reflect.apply(target, receiver, args), - { arguments: args, self: receiver, additional }, - "embedContent", - ), + ); + googleGenerativeAIChannels.embedContent.intercept( + (target, receiver, args, additional) => + traceGenerativeAICall( + () => Reflect.apply(target, receiver, args), + { arguments: args, self: receiver, additional }, + "embedContent", ), - googleGenerativeAIChannels.batchEmbedContents.intercept( - (target, receiver, args, additional) => - traceGenerativeAICall( - () => Reflect.apply(target, receiver, args), - { arguments: args, self: receiver, additional }, - "batchEmbedContents", - ), + ); + googleGenerativeAIChannels.batchEmbedContents.intercept( + (target, receiver, args, additional) => + traceGenerativeAICall( + () => Reflect.apply(target, receiver, args), + { arguments: args, self: receiver, additional }, + "batchEmbedContents", ), - ); - } - - protected onDisable(): void { - this.unsubscribers = unsubscribeAll(this.unsubscribers); - } + ); } function traceGenerativeAICall< @@ -412,10 +406,13 @@ function extractInput( // Convert Google's inlineData to the shared attachment processor's file shape, // then restore the native payload shape. Any conversion failure keeps all input. function convertAttachments(value: T): T { + const captureAttachments = _internalCaptureAttachmentsEnabled(); const convert = (node: unknown): unknown => { - if (Array.isArray(node)) return node.map(convert); + if (Array.isArray(node)) + return node.map(convert).filter((item) => item !== undefined); if (!isObject(node)) return node; if (isObject(node.inlineData)) { + if (!captureAttachments) return omitMediaData(node, "inlineData"); const { data, mimeType } = node.inlineData; const processed = processInputAttachments({ type: "file", diff --git a/js/src/instrumentation/plugins/google-generative-ai-interceptors.ts b/js/src/instrumentation/providers/google-generative-ai-interceptors.ts similarity index 100% rename from js/src/instrumentation/plugins/google-generative-ai-interceptors.ts rename to js/src/instrumentation/providers/google-generative-ai-interceptors.ts diff --git a/js/src/instrumentation/plugins/groq-plugin.test.ts b/js/src/instrumentation/providers/groq-instrumentation.test.ts similarity index 95% rename from js/src/instrumentation/plugins/groq-plugin.test.ts rename to js/src/instrumentation/providers/groq-instrumentation.test.ts index 506e3296a..c76e1ffd8 100644 --- a/js/src/instrumentation/plugins/groq-plugin.test.ts +++ b/js/src/instrumentation/providers/groq-instrumentation.test.ts @@ -1,8 +1,8 @@ -import { describe, expect, it } from "vitest"; +import { describe, expect, it } from "vite-plus/test"; import { aggregateGroqChatCompletionChunks, parseGroqMetrics, -} from "./groq-plugin"; +} from "./groq-instrumentation"; describe("parseGroqMetrics", () => { it("merges OpenAI-compatible usage metrics with Groq cache metrics", () => { diff --git a/js/src/instrumentation/plugins/groq-plugin.ts b/js/src/instrumentation/providers/groq-instrumentation.ts similarity index 74% rename from js/src/instrumentation/plugins/groq-plugin.ts rename to js/src/instrumentation/providers/groq-instrumentation.ts index cffb1c0d5..3e0ede560 100644 --- a/js/src/instrumentation/plugins/groq-plugin.ts +++ b/js/src/instrumentation/providers/groq-instrumentation.ts @@ -3,8 +3,18 @@ import { concatUint8Arrays, isObject, } from "../../../util/index"; +import { + _internalCaptureAttachmentsEnabled, + Attachment, + withCurrent, + type Span, +} from "../../logger/core"; +import { + convertDataToBlob, + getExtensionFromMediaType, + processInputAttachments, +} from "../../wrappers/attachment-utils"; import { debugLogger } from "../../debug-logger"; -import { Attachment, withCurrent, type Span } from "../../logger"; import { INSTRUMENTATION_NAMES } from "../../span-origin"; import { getCurrentUnixTimestamp } from "../../util"; import type { @@ -15,165 +25,140 @@ import type { GroqChatCompletion, GroqChatCompletionChunk, } from "../../vendor-sdk-types/groq"; -import { - convertDataToBlob, - getExtensionFromMediaType, - processInputAttachments, -} from "../../wrappers/attachment-utils"; -import { BasePlugin } from "../core"; -import { - traceAsyncCall, - traceStreamingCall, - unsubscribeAll, -} from "../core/tracing"; + +import { traceAsyncCall, traceStreamingCall } from "../core/tracing"; import { isAsyncIterable, observeByteStream } from "../core/stream-patcher"; import { groqChannels } from "./groq-interceptors"; import { aggregateChatCompletionChunks, parseMetricsFromUsage, -} from "./openai-plugin"; - -export class GroqPlugin extends BasePlugin { - protected onEnable(): void { - this.unsubscribers.push( - groqChannels.chatCompletionsCreate.intercept( - (target, receiver, args, additional) => - traceStreamingCall( - () => Reflect.apply(target, receiver, args), - { ...additional, arguments: args, self: receiver }, - { - instrumentationName: INSTRUMENTATION_NAMES.GROQ, - name: "groq.chat.completions.create", - type: SpanTypeAttribute.LLM, - extractInput: ([params]) => { - const { messages, ...metadata } = params; - return { - input: processInputAttachments(messages), - metadata: { ...metadata, provider: "groq" }, - }; - }, - extractOutput: (result) => result?.choices, - extractMetrics: (result, startTime) => { - const metrics = parseGroqMetrics(result); - if (startTime) { - metrics.time_to_first_token = - getCurrentUnixTimestamp() - startTime; - } - return metrics; - }, - aggregateChunks: aggregateGroqChatCompletionChunks, - }, - ), +} from "./openai-instrumentation"; + +export function registerGroqInstrumentation(): void { + groqChannels.chatCompletionsCreate.intercept( + (target, receiver, args, additional) => + traceStreamingCall( + () => Reflect.apply(target, receiver, args), + { ...additional, arguments: args, self: receiver }, + { + instrumentationName: INSTRUMENTATION_NAMES.GROQ, + name: "groq.chat.completions.create", + type: SpanTypeAttribute.LLM, + extractInput: ([params]) => { + const { messages, ...metadata } = params; + return { + input: processInputAttachments(messages), + metadata: { ...metadata, provider: "groq" }, + }; + }, + extractOutput: (result) => result?.choices, + extractMetrics: (result, startTime) => { + const metrics = parseGroqMetrics(result); + if (startTime) { + metrics.time_to_first_token = + getCurrentUnixTimestamp() - startTime; + } + return metrics; + }, + aggregateChunks: aggregateGroqChatCompletionChunks, + }, ), - ); + ); - this.unsubscribers.push( - groqChannels.embeddingsCreate.intercept( - (target, receiver, args, additional) => - traceAsyncCall( - () => Reflect.apply(target, receiver, args), - { ...additional, arguments: args, self: receiver }, - { - instrumentationName: INSTRUMENTATION_NAMES.GROQ, - name: "groq.embeddings.create", - type: SpanTypeAttribute.LLM, - extractInput: ([params]) => { - const { input, ...metadata } = params; - return { - input, - metadata: { ...metadata, provider: "groq" }, - }; - }, - extractOutput: (result) => { - const embedding = result?.data?.[0]?.embedding; - return Array.isArray(embedding) - ? { embedding_length: embedding.length } - : undefined; - }, - extractMetrics: (result) => parseGroqMetrics(result), - }, - ), + groqChannels.embeddingsCreate.intercept( + (target, receiver, args, additional) => + traceAsyncCall( + () => Reflect.apply(target, receiver, args), + { ...additional, arguments: args, self: receiver }, + { + instrumentationName: INSTRUMENTATION_NAMES.GROQ, + name: "groq.embeddings.create", + type: SpanTypeAttribute.LLM, + extractInput: ([params]) => { + const { input, ...metadata } = params; + return { + input, + metadata: { ...metadata, provider: "groq" }, + }; + }, + extractOutput: (result) => { + const embedding = result?.data?.[0]?.embedding; + return Array.isArray(embedding) + ? { embedding_length: embedding.length } + : undefined; + }, + extractMetrics: (result) => parseGroqMetrics(result), + }, ), - ); + ); - this.unsubscribers.push( - groqChannels.audioSpeechCreate.intercept( - (target, receiver, args, additional) => - traceStreamingCall( - () => Reflect.apply(target, receiver, args), - { ...additional, arguments: args, self: receiver }, - { - instrumentationName: INSTRUMENTATION_NAMES.GROQ, - name: "groq.audio.speech.create", - type: SpanTypeAttribute.LLM, - extractInput: ([params]) => ({ - input: { - operation: "speech", - prompt: params.input, - parameters: { - voice: params.voice, - format: params.response_format, - speed: params.speed, - }, - }, - metadata: { model: params.model, provider: "groq" }, - }), - extractOutput: () => ({ content: [] }), - extractMetrics: () => ({}), - patchResult: ({ endEvent, result, span, startTime }) => - captureGroqSpeechResponse( - result, - endEvent.arguments![0], - span, - startTime, - ), + groqChannels.audioSpeechCreate.intercept( + (target, receiver, args, additional) => + traceStreamingCall( + () => Reflect.apply(target, receiver, args), + { ...additional, arguments: args, self: receiver }, + { + instrumentationName: INSTRUMENTATION_NAMES.GROQ, + name: "groq.audio.speech.create", + type: SpanTypeAttribute.LLM, + extractInput: ([params]) => ({ + input: { + operation: "speech", + prompt: params.input, + parameters: { + voice: params.voice, + format: params.response_format, + speed: params.speed, + }, }, - ), + metadata: { model: params.model, provider: "groq" }, + }), + extractOutput: () => ({ content: [] }), + extractMetrics: () => ({}), + patchResult: ({ endEvent, result, span, startTime }) => + captureGroqSpeechResponse( + result, + endEvent.arguments![0], + span, + startTime, + ), + }, ), - ); + ); - this.unsubscribers.push( - groqChannels.audioTranscriptionsCreate.intercept( - (target, receiver, args, additional) => - traceAsyncCall( - () => Reflect.apply(target, receiver, args), - { ...additional, arguments: args, self: receiver }, - { - instrumentationName: INSTRUMENTATION_NAMES.GROQ, - name: "groq.audio.transcriptions.create", - type: SpanTypeAttribute.LLM, - extractInput: ([params], _event, span) => - extractGroqAudioInput(params, "transcribe", span), - extractOutput: extractGroqAudioTextOutput, - extractMetrics: (result) => parseGroqMetricsObject(result), - }, - ), + groqChannels.audioTranscriptionsCreate.intercept( + (target, receiver, args, additional) => + traceAsyncCall( + () => Reflect.apply(target, receiver, args), + { ...additional, arguments: args, self: receiver }, + { + instrumentationName: INSTRUMENTATION_NAMES.GROQ, + name: "groq.audio.transcriptions.create", + type: SpanTypeAttribute.LLM, + extractInput: ([params], _event, span) => + extractGroqAudioInput(params, "transcribe", span), + extractOutput: extractGroqAudioTextOutput, + extractMetrics: (result) => parseGroqMetricsObject(result), + }, ), - ); + ); - this.unsubscribers.push( - groqChannels.audioTranslationsCreate.intercept( - (target, receiver, args, additional) => - traceAsyncCall( - () => Reflect.apply(target, receiver, args), - { ...additional, arguments: args, self: receiver }, - { - instrumentationName: INSTRUMENTATION_NAMES.GROQ, - name: "groq.audio.translations.create", - type: SpanTypeAttribute.LLM, - extractInput: ([params], _event, span) => - extractGroqAudioInput(params, "translate", span), - extractOutput: extractGroqAudioTextOutput, - extractMetrics: (result) => parseGroqMetricsObject(result), - }, - ), + groqChannels.audioTranslationsCreate.intercept( + (target, receiver, args, additional) => + traceAsyncCall( + () => Reflect.apply(target, receiver, args), + { ...additional, arguments: args, self: receiver }, + { + instrumentationName: INSTRUMENTATION_NAMES.GROQ, + name: "groq.audio.translations.create", + type: SpanTypeAttribute.LLM, + extractInput: ([params], _event, span) => + extractGroqAudioInput(params, "translate", span), + extractOutput: extractGroqAudioTextOutput, + extractMetrics: (result) => parseGroqMetricsObject(result), + }, ), - ); - } - - protected onDisable(): void { - this.unsubscribers = unsubscribeAll(this.unsubscribers); - } + ); } type GroqAudioTextParams = @@ -186,7 +171,10 @@ function extractGroqAudioInput( span: Span, ): { input: unknown; metadata: Record } { const source = params.file ?? params.url; - const filePart = groqAudioFilePart(source); + const filePart = groqAudioFilePart( + source, + _internalCaptureAttachmentsEnabled(span), + ); const input = { operation, ...(params.prompt !== undefined ? { prompt: params.prompt } : {}), @@ -209,11 +197,15 @@ function extractGroqAudioInput( }; } -function groqAudioFilePart(source: unknown): unknown | undefined { +function groqAudioFilePart( + source: unknown, + captureAttachments: boolean, +): unknown | undefined { if (source === undefined || source === null) return undefined; if (typeof source === "string") { if (source.startsWith("data:")) { + if (!captureAttachments) return undefined; const contentType = source.match(/^data:([^;,]+)/)?.[1]; const blob = contentType ? convertDataToBlob(source, contentType) : null; if (contentType && blob) { @@ -235,7 +227,7 @@ function groqAudioFilePart(source: unknown): unknown | undefined { }; } - if (!isObject(source)) return undefined; + if (!captureAttachments || !isObject(source)) return undefined; const filename = typeof source.name === "string" && source.name ? filenameFromPath(source.name) @@ -269,7 +261,7 @@ function observeGroqAudioInput( input: Record, span: Span, ): void { - if (!isObject(source)) return; + if (!_internalCaptureAttachmentsEnabled(span) || !isObject(source)) return; const body = isObject(source.body) ? source.body @@ -313,7 +305,7 @@ function observeGroqAudioInput( filename, byte_size: data.byteLength, file_data: new Attachment({ - data: data.buffer, + data: Uint8Array.from(data).buffer, filename, contentType, }), @@ -421,6 +413,7 @@ function captureGroqSpeechResponse( startTime: number, ): boolean { if (!isObject(response)) return false; + const captureAttachments = _internalCaptureAttachmentsEnabled(span); const headerContentType = responseHeader(response, "content-type")?.split( ";", @@ -459,7 +452,7 @@ function captureGroqSpeechResponse( }); firstChunk = false; } - chunks.push(new Uint8Array(chunk)); + if (captureAttachments) chunks.push(new Uint8Array(chunk)); }; const complete = () => { if (finished) return; @@ -478,7 +471,7 @@ function captureGroqSpeechResponse( filename, byte_size: data.byteLength, file_data: new Attachment({ - data: data.buffer, + data: Uint8Array.from(data).buffer, filename, contentType, }), diff --git a/js/src/instrumentation/plugins/groq-interceptors.ts b/js/src/instrumentation/providers/groq-interceptors.ts similarity index 100% rename from js/src/instrumentation/plugins/groq-interceptors.ts rename to js/src/instrumentation/providers/groq-interceptors.ts diff --git a/js/src/instrumentation/plugins/harness-agent-context.test.ts b/js/src/instrumentation/providers/harness-agent-context.test.ts similarity index 99% rename from js/src/instrumentation/plugins/harness-agent-context.test.ts rename to js/src/instrumentation/providers/harness-agent-context.test.ts index de9ff7722..7da346c92 100644 --- a/js/src/instrumentation/plugins/harness-agent-context.test.ts +++ b/js/src/instrumentation/providers/harness-agent-context.test.ts @@ -1,5 +1,5 @@ -import { describe, expect, test, vi } from "vitest"; -import { _internalGetGlobalState, type Span } from "../../logger"; +import { describe, expect, test, vi } from "vite-plus/test"; +import { _internalGetGlobalState, type Span } from "../../logger/core"; import { configureNode } from "../../node/config"; import { SpanObjectTypeV3 } from "../../util"; import { SpanComponentsV4 } from "../../../util/span_identifier_v4"; diff --git a/js/src/instrumentation/plugins/huggingface-plugin.test.ts b/js/src/instrumentation/providers/huggingface-instrumentation.test.ts similarity index 98% rename from js/src/instrumentation/plugins/huggingface-plugin.test.ts rename to js/src/instrumentation/providers/huggingface-instrumentation.test.ts index 2612e47e2..168351cc5 100644 --- a/js/src/instrumentation/plugins/huggingface-plugin.test.ts +++ b/js/src/instrumentation/providers/huggingface-instrumentation.test.ts @@ -1,8 +1,8 @@ -import { describe, expect, it } from "vitest"; +import { describe, expect, it } from "vite-plus/test"; import { aggregateChatCompletionChunks, extractResponseMetadata, -} from "./huggingface-plugin"; +} from "./huggingface-instrumentation"; describe("extractResponseMetadata", () => { it("keeps allowlisted response fields", () => { diff --git a/js/src/instrumentation/plugins/huggingface-plugin.ts b/js/src/instrumentation/providers/huggingface-instrumentation.ts similarity index 73% rename from js/src/instrumentation/plugins/huggingface-plugin.ts rename to js/src/instrumentation/providers/huggingface-instrumentation.ts index 830c12525..c9e10196c 100644 --- a/js/src/instrumentation/plugins/huggingface-plugin.ts +++ b/js/src/instrumentation/providers/huggingface-instrumentation.ts @@ -1,5 +1,6 @@ +import { isAsyncIterable, patchStreamIfNeeded } from "../core/stream-patcher"; import { SpanTypeAttribute, isObject } from "../../../util/index"; -import type { Span } from "../../logger"; +import type { Span } from "../../logger/core"; import { parseMetricsFromUsage } from "../../openai-utils"; import { INSTRUMENTATION_NAMES } from "../../span-origin"; import { getCurrentUnixTimestamp } from "../../util"; @@ -10,13 +11,9 @@ import type { HuggingFaceTextGenerationDetails, HuggingFaceTextGenerationStreamOutput, } from "../../vendor-sdk-types/huggingface"; -import { BasePlugin } from "../core"; -import { - traceAsyncCall, - traceSyncStreamCall, - unsubscribeAll, -} from "../core/tracing"; -import { isAsyncIterable, patchStreamIfNeeded } from "../core/stream-patcher"; + +import { traceAsyncCall, traceSyncStreamCall } from "../core/tracing"; + import { huggingFaceChannels } from "./huggingface-interceptors"; const REQUEST_METADATA_ALLOWLIST = new Set([ @@ -42,144 +39,136 @@ const RESPONSE_METADATA_ALLOWLIST = new Set([ "object", ]); -export class HuggingFacePlugin extends BasePlugin { - protected onEnable(): void { - this.unsubscribers.push( - huggingFaceChannels.chatCompletion.intercept( - (target, receiver, args, additional) => - traceAsyncCall( - () => Reflect.apply(target, receiver, args), - { ...additional, arguments: args, self: receiver }, - { - instrumentationName: INSTRUMENTATION_NAMES.HUGGINGFACE, - name: "huggingface.chat_completion", - type: SpanTypeAttribute.LLM, - extractInput: ([params]) => - extractChatInputWithMetadata([ - { - ...params, - ...(additional.endpointUrl && !params.endpointUrl - ? { endpointUrl: additional.endpointUrl } - : {}), - }, - ]), - extractOutput: (result) => result?.choices, - extractMetadata: (result) => extractResponseMetadata(result), - extractMetrics: (result) => parseMetricsFromUsage(result?.usage), - }, - ), +export function registerHuggingFaceInstrumentation(): void { + huggingFaceChannels.chatCompletion.intercept( + (target, receiver, args, additional) => + traceAsyncCall( + () => Reflect.apply(target, receiver, args), + { ...additional, arguments: args, self: receiver }, + { + instrumentationName: INSTRUMENTATION_NAMES.HUGGINGFACE, + name: "huggingface.chat_completion", + type: SpanTypeAttribute.LLM, + extractInput: ([params]) => + extractChatInputWithMetadata([ + { + ...params, + ...(additional.endpointUrl && !params.endpointUrl + ? { endpointUrl: additional.endpointUrl } + : {}), + }, + ]), + extractOutput: (result) => result?.choices, + extractMetadata: (result) => extractResponseMetadata(result), + extractMetrics: (result) => parseMetricsFromUsage(result?.usage), + }, ), - huggingFaceChannels.chatCompletionStream.intercept( - (target, receiver, args, additional) => - traceSyncStreamCall( - () => Reflect.apply(target, receiver, args), - { ...additional, arguments: args, self: receiver }, - { - instrumentationName: INSTRUMENTATION_NAMES.HUGGINGFACE, - name: "huggingface.chat_completion_stream", - type: SpanTypeAttribute.LLM, - extractInput: ([params]) => - extractChatInputWithMetadata([ - { - ...params, - ...(additional.endpointUrl && !params.endpointUrl - ? { endpointUrl: additional.endpointUrl } - : {}), - }, - ]), - patchResult: ({ result, span, startTime }) => - patchChatCompletionStream({ - result, - span, - startTime, - }), - }, - ), + ); + huggingFaceChannels.chatCompletionStream.intercept( + (target, receiver, args, additional) => + traceSyncStreamCall( + () => Reflect.apply(target, receiver, args), + { ...additional, arguments: args, self: receiver }, + { + instrumentationName: INSTRUMENTATION_NAMES.HUGGINGFACE, + name: "huggingface.chat_completion_stream", + type: SpanTypeAttribute.LLM, + extractInput: ([params]) => + extractChatInputWithMetadata([ + { + ...params, + ...(additional.endpointUrl && !params.endpointUrl + ? { endpointUrl: additional.endpointUrl } + : {}), + }, + ]), + patchResult: ({ result, span, startTime }) => + patchChatCompletionStream({ + result, + span, + startTime, + }), + }, ), - huggingFaceChannels.textGeneration.intercept( - (target, receiver, args, additional) => - traceAsyncCall( - () => Reflect.apply(target, receiver, args), - { ...additional, arguments: args, self: receiver }, - { - instrumentationName: INSTRUMENTATION_NAMES.HUGGINGFACE, - name: "huggingface.text_generation", - type: SpanTypeAttribute.LLM, - extractInput: ([params]) => - extractTextGenerationInputWithMetadata([ - { - ...params, - ...(additional.endpointUrl && !params.endpointUrl - ? { endpointUrl: additional.endpointUrl } - : {}), - }, - ]), - extractOutput: (result) => - isObject(result) - ? { generated_text: result.generated_text } - : result, - extractMetadata: extractTextGenerationMetadata, - extractMetrics: (result) => - extractTextGenerationMetrics(result?.details ?? null), - }, - ), + ); + huggingFaceChannels.textGeneration.intercept( + (target, receiver, args, additional) => + traceAsyncCall( + () => Reflect.apply(target, receiver, args), + { ...additional, arguments: args, self: receiver }, + { + instrumentationName: INSTRUMENTATION_NAMES.HUGGINGFACE, + name: "huggingface.text_generation", + type: SpanTypeAttribute.LLM, + extractInput: ([params]) => + extractTextGenerationInputWithMetadata([ + { + ...params, + ...(additional.endpointUrl && !params.endpointUrl + ? { endpointUrl: additional.endpointUrl } + : {}), + }, + ]), + extractOutput: (result) => + isObject(result) + ? { generated_text: result.generated_text } + : result, + extractMetadata: extractTextGenerationMetadata, + extractMetrics: (result) => + extractTextGenerationMetrics(result?.details ?? null), + }, ), - huggingFaceChannels.textGenerationStream.intercept( - (target, receiver, args, additional) => - traceSyncStreamCall( - () => Reflect.apply(target, receiver, args), - { ...additional, arguments: args, self: receiver }, - { - instrumentationName: INSTRUMENTATION_NAMES.HUGGINGFACE, - name: "huggingface.text_generation_stream", - type: SpanTypeAttribute.LLM, - extractInput: ([params]) => - extractTextGenerationInputWithMetadata([ - { - ...params, - ...(additional.endpointUrl && !params.endpointUrl - ? { endpointUrl: additional.endpointUrl } - : {}), - }, - ]), - patchResult: ({ result, span, startTime }) => - patchTextGenerationStream({ - result, - span, - startTime, - }), - }, - ), + ); + huggingFaceChannels.textGenerationStream.intercept( + (target, receiver, args, additional) => + traceSyncStreamCall( + () => Reflect.apply(target, receiver, args), + { ...additional, arguments: args, self: receiver }, + { + instrumentationName: INSTRUMENTATION_NAMES.HUGGINGFACE, + name: "huggingface.text_generation_stream", + type: SpanTypeAttribute.LLM, + extractInput: ([params]) => + extractTextGenerationInputWithMetadata([ + { + ...params, + ...(additional.endpointUrl && !params.endpointUrl + ? { endpointUrl: additional.endpointUrl } + : {}), + }, + ]), + patchResult: ({ result, span, startTime }) => + patchTextGenerationStream({ + result, + span, + startTime, + }), + }, ), - huggingFaceChannels.featureExtraction.intercept( - (target, receiver, args, additional) => - traceAsyncCall( - () => Reflect.apply(target, receiver, args), - { ...additional, arguments: args, self: receiver }, - { - instrumentationName: INSTRUMENTATION_NAMES.HUGGINGFACE, - name: "huggingface.feature_extraction", - type: SpanTypeAttribute.LLM, - extractInput: ([params]) => - extractFeatureExtractionInputWithMetadata([ - { - ...params, - ...(additional.endpointUrl && !params.endpointUrl - ? { endpointUrl: additional.endpointUrl } - : {}), - }, - ]), - extractOutput: summarizeFeatureExtractionOutput, - extractMetrics: () => ({}), - }, - ), + ); + huggingFaceChannels.featureExtraction.intercept( + (target, receiver, args, additional) => + traceAsyncCall( + () => Reflect.apply(target, receiver, args), + { ...additional, arguments: args, self: receiver }, + { + instrumentationName: INSTRUMENTATION_NAMES.HUGGINGFACE, + name: "huggingface.feature_extraction", + type: SpanTypeAttribute.LLM, + extractInput: ([params]) => + extractFeatureExtractionInputWithMetadata([ + { + ...params, + ...(additional.endpointUrl && !params.endpointUrl + ? { endpointUrl: additional.endpointUrl } + : {}), + }, + ]), + extractOutput: summarizeFeatureExtractionOutput, + extractMetrics: () => ({}), + }, ), - ); - } - - protected onDisable(): void { - this.unsubscribers = unsubscribeAll(this.unsubscribers); - } + ); } function addProviderMetadata( diff --git a/js/src/instrumentation/plugins/huggingface-interceptors.ts b/js/src/instrumentation/providers/huggingface-interceptors.ts similarity index 100% rename from js/src/instrumentation/plugins/huggingface-interceptors.ts rename to js/src/instrumentation/providers/huggingface-interceptors.ts diff --git a/js/src/instrumentation/plugins/huggingface-transformers-plugin.test.ts b/js/src/instrumentation/providers/huggingface-transformers-instrumentation.test.ts similarity index 95% rename from js/src/instrumentation/plugins/huggingface-transformers-plugin.test.ts rename to js/src/instrumentation/providers/huggingface-transformers-instrumentation.test.ts index 3adb2d910..a9cff21de 100644 --- a/js/src/instrumentation/plugins/huggingface-transformers-plugin.test.ts +++ b/js/src/instrumentation/providers/huggingface-transformers-instrumentation.test.ts @@ -1,15 +1,15 @@ -import { describe, expect, it } from "vitest"; +import { describe, expect, it } from "vite-plus/test"; import { registerHuggingFaceTransformersPipeline, type HuggingFaceTransformersEventContext, } from "./huggingface-transformers-interceptors"; -import { _exportsForTestingOnly } from "./huggingface-transformers-plugin"; +import { _exportsForTestingOnly } from "./huggingface-transformers-instrumentation"; import type { HuggingFaceTransformersPipeline } from "../../vendor-sdk-types/huggingface-transformers"; const { extractInput, extractMetadata, extractOutput, isSupportedTask } = _exportsForTestingOnly; -describe("HuggingFaceTransformersPlugin extraction", () => { +describe("registerHuggingFaceTransformersInstrumentation extraction", () => { it("normalizes generation and chat payloads", () => { expect(extractInput("text-generation", ["Hello"])).toEqual([ { role: "user", content: "Hello" }, diff --git a/js/src/instrumentation/plugins/huggingface-transformers-plugin.ts b/js/src/instrumentation/providers/huggingface-transformers-instrumentation.ts similarity index 68% rename from js/src/instrumentation/plugins/huggingface-transformers-plugin.ts rename to js/src/instrumentation/providers/huggingface-transformers-instrumentation.ts index 0eb37ef6f..d0f8f0805 100644 --- a/js/src/instrumentation/plugins/huggingface-transformers-plugin.ts +++ b/js/src/instrumentation/providers/huggingface-transformers-instrumentation.ts @@ -1,6 +1,6 @@ import { INSTRUMENTATION_NAMES } from "../../span-origin"; -import { BasePlugin } from "../core"; -import { traceAsyncCall, unsubscribeAll } from "../core/tracing"; + +import { traceAsyncCall } from "../core/tracing"; import { observeResult } from "../core/observe-result"; import { SpanTypeAttribute, isObject } from "../../../util"; @@ -21,96 +21,85 @@ const REQUEST_METADATA_KEYS = [ "stop", ] as const; -export class HuggingFaceTransformersPlugin extends BasePlugin { - protected onEnable(): void { +class HuggingFaceTransformersInstrumentationConsumer { + public register(): void { this.subscribeToPipelineFactory(); - this.unsubscribers.push( - huggingFaceTransformersChannels.pipelineCall.intercept( - (target, receiver, args, additional) => - traceAsyncCall( - () => Reflect.apply(target, receiver, args), - { ...additional, arguments: args, self: receiver }, - { - instrumentationName: INSTRUMENTATION_NAMES.HUGGINGFACE, - name: (_args, event) => { - const task = getTask( - event as HuggingFaceTransformersEventContext, - ); - const operation = task?.replaceAll("-", "_") ?? "unknown"; - return `huggingface.transformers.${operation}`; - }, - type: SpanTypeAttribute.LLM, - shouldTrace: (_args, event) => - isSupportedHuggingFaceTransformersTask( - getTask(event as HuggingFaceTransformersEventContext), - ), - extractInput: (args, event) => ({ - input: extractInput( - getTask(event as HuggingFaceTransformersEventContext), - args, - ), - metadata: extractMetadata( - event as HuggingFaceTransformersEventContext, - args, - ), - }), - extractOutput: (result, event) => - extractOutput( - getTask(event as HuggingFaceTransformersEventContext), - result, - ), - extractMetrics: () => ({}), + huggingFaceTransformersChannels.pipelineCall.intercept( + (target, receiver, args, additional) => + traceAsyncCall( + () => Reflect.apply(target, receiver, args), + { ...additional, arguments: args, self: receiver }, + { + instrumentationName: INSTRUMENTATION_NAMES.HUGGINGFACE, + name: (_args, event) => { + const task = getTask( + event as HuggingFaceTransformersEventContext, + ); + const operation = task?.replaceAll("-", "_") ?? "unknown"; + return `huggingface.transformers.${operation}`; }, - ), - ), + type: SpanTypeAttribute.LLM, + shouldTrace: (_args, event) => + isSupportedHuggingFaceTransformersTask( + getTask(event as HuggingFaceTransformersEventContext), + ), + extractInput: (args, event) => ({ + input: extractInput( + getTask(event as HuggingFaceTransformersEventContext), + args, + ), + metadata: extractMetadata( + event as HuggingFaceTransformersEventContext, + args, + ), + }), + extractOutput: (result, event) => + extractOutput( + getTask(event as HuggingFaceTransformersEventContext), + result, + ), + extractMetrics: () => ({}), + }, + ), ); } - protected onDisable(): void { - this.unsubscribers = unsubscribeAll(this.unsubscribers); - } - private subscribeToPipelineFactory(): void { const channel = huggingFaceTransformersChannels.pipeline; - const removeHandlers = channel.intercept( - (target, receiver, args, additional) => { - const event = { - ...additional, - arguments: args, - self: receiver, - } as ChannelMessage; - const resolved = ( - event: ChannelMessage< - typeof huggingFaceTransformersChannels.pipeline - >, - ) => { - if (typeof event.result !== "function") { - return; - } - registerHuggingFaceTransformersPipeline( - event.result, - event.arguments?.[0], - event.arguments?.[1], - ); - }; - let result; - try { - result = Reflect.apply(target, receiver, args); - } catch (error) { - throw error; + channel.intercept((target, receiver, args, additional) => { + const event = { + ...additional, + arguments: args, + self: receiver, + } as ChannelMessage; + const resolved = ( + event: ChannelMessage, + ) => { + if (typeof event.result !== "function") { + return; } - return observeResult( - result, - (value) => { - Object.assign(event, { result: value }); - resolved(event); - }, - (error) => {}, + registerHuggingFaceTransformersPipeline( + event.result, + event.arguments?.[0], + event.arguments?.[1], ); - }, - ); - this.unsubscribers.push(removeHandlers); + }; + let result; + try { + result = Reflect.apply(target, receiver, args); + } catch (error) { + throw error; + } + return observeResult( + result, + (value) => { + Object.assign(event, { result: value }); + resolved(event); + }, + (error) => {}, + ); + }); } } @@ -354,9 +343,24 @@ function choicesFromGenerations(result: unknown): unknown { return choices.length > 0 ? choices : undefined; } -export const _exportsForTestingOnly = { +export const _exportsForTestingOnly: { + extractInput: typeof extractInput; + extractMetadata: typeof extractMetadata; + extractOutput: typeof extractOutput; + isSupportedTask: typeof isSupportedHuggingFaceTransformersTask; +} = { extractInput, extractMetadata, extractOutput, isSupportedTask: isSupportedHuggingFaceTransformersTask, }; + +let huggingFaceTransformersInstrumentationConsumer: + | HuggingFaceTransformersInstrumentationConsumer + | undefined; + +export function registerHuggingFaceTransformersInstrumentation(): void { + huggingFaceTransformersInstrumentationConsumer ??= + new HuggingFaceTransformersInstrumentationConsumer(); + huggingFaceTransformersInstrumentationConsumer.register(); +} diff --git a/js/src/instrumentation/plugins/huggingface-transformers-interceptors.ts b/js/src/instrumentation/providers/huggingface-transformers-interceptors.ts similarity index 100% rename from js/src/instrumentation/plugins/huggingface-transformers-interceptors.ts rename to js/src/instrumentation/providers/huggingface-transformers-interceptors.ts diff --git a/js/src/instrumentation/plugins/instrumentation-names.test.ts b/js/src/instrumentation/providers/instrumentation-names.test.ts similarity index 96% rename from js/src/instrumentation/plugins/instrumentation-names.test.ts rename to js/src/instrumentation/providers/instrumentation-names.test.ts index 2cf5bca49..ad7d22d15 100644 --- a/js/src/instrumentation/plugins/instrumentation-names.test.ts +++ b/js/src/instrumentation/providers/instrumentation-names.test.ts @@ -1,4 +1,4 @@ -import { describe, expect, it } from "vitest"; +import { describe, expect, it } from "vite-plus/test"; import { INSTRUMENTATION_NAMES } from "../../span-origin"; import { aiSDKChannels, harnessAgentChannels } from "./ai-sdk-interceptors"; @@ -14,7 +14,6 @@ import { cloudflareAIChatChannels } from "./cloudflare-ai-chat-interceptors"; import { cloudflareThinkChannels } from "./cloudflare-think-interceptors"; import { cohereChannels } from "./cohere-interceptors"; import { cursorSDKChannels } from "./cursor-sdk-interceptors"; -import { flueChannels } from "./flue-interceptors"; import { genkitChannels, genkitCoreChannels } from "./genkit-interceptors"; import { gitHubCopilotChannels } from "./github-copilot-interceptors"; import { googleADKChannels } from "./google-adk-interceptors"; @@ -63,7 +62,6 @@ describe("wrapping definitions are independent of span provenance", () => { ], [cohereChannels.chat, INSTRUMENTATION_NAMES.COHERE], [cursorSDKChannels.create, INSTRUMENTATION_NAMES.CURSOR_SDK], - [flueChannels.createContext, INSTRUMENTATION_NAMES.FLUE], [genkitChannels.generate, INSTRUMENTATION_NAMES.GENKIT], [genkitCoreChannels.actionSpan, INSTRUMENTATION_NAMES.GENKIT], [gitHubCopilotChannels.createSession, INSTRUMENTATION_NAMES.GITHUB_COPILOT], diff --git a/js/src/instrumentation/plugins/langchain-plugin.test.ts b/js/src/instrumentation/providers/langchain-instrumentation.test.ts similarity index 80% rename from js/src/instrumentation/plugins/langchain-plugin.test.ts rename to js/src/instrumentation/providers/langchain-instrumentation.test.ts index 6ead73b39..5073203df 100644 --- a/js/src/instrumentation/plugins/langchain-plugin.test.ts +++ b/js/src/instrumentation/providers/langchain-instrumentation.test.ts @@ -1,6 +1,6 @@ -import { describe, expect, it } from "vitest"; +import { beforeAll, describe, expect, it } from "vite-plus/test"; +import { registerLangChainInstrumentation } from "./langchain-instrumentation"; import { langChainChannels } from "./langchain-interceptors"; -import { LangChainPlugin } from "./langchain-plugin"; function createManager(handlers: unknown[] = []) { return { @@ -44,14 +44,15 @@ function createArgumentsObject(...args: unknown[]): IArguments { })(...args); } -describe("LangChainPlugin", () => { +describe("registerLangChainInstrumentation", () => { + beforeAll(() => { + registerLangChainInstrumentation(); + }); + it("injects a Braintrust callback handler into empty CallbackManager.configure() arguments", () => { - const plugin = new LangChainPlugin(); const args: unknown[] = []; - plugin.enable(); const received = traceConfigureArguments(args); - plugin.disable(); expect(received[0]).toEqual([ expect.objectContaining({ @@ -61,12 +62,9 @@ describe("LangChainPlugin", () => { }); it("injects a Braintrust callback handler into real arguments objects", () => { - const plugin = new LangChainPlugin(); const args = createArgumentsObject(); - plugin.enable(); const received = traceConfigureArgumentsObject(args); - plugin.disable(); expect(received[0]).toEqual([ expect.objectContaining({ @@ -76,12 +74,9 @@ describe("LangChainPlugin", () => { }); it("injects a Braintrust callback handler into CallbackManager.configure() results", () => { - const plugin = new LangChainPlugin(); const manager = createManager(); - plugin.enable(); traceConfigureResult(manager); - plugin.disable(); expect(manager.handlers).toHaveLength(1); expect(manager.handlers[0]).toMatchObject({ @@ -90,37 +85,25 @@ describe("LangChainPlugin", () => { }); it("does not inject duplicate handlers into the same manager", () => { - const plugin = new LangChainPlugin(); const manager = createManager(); - plugin.enable(); traceConfigureResult(manager); traceConfigureResult(manager); - plugin.disable(); expect(manager.handlers).toHaveLength(1); }); it("does not inject when a Braintrust callback handler is already present", () => { - const plugin = new LangChainPlugin(); const existingHandler = { name: "BraintrustCallbackHandler" }; const manager = createManager([existingHandler]); - plugin.enable(); traceConfigureResult(manager); - plugin.disable(); expect(manager.handlers).toEqual([existingHandler]); }); it("gracefully ignores undefined and non-manager results", () => { - const plugin = new LangChainPlugin(); - - plugin.enable(); - expect(() => traceConfigureResult(undefined)).not.toThrow(); expect(() => traceConfigureResult({ handlers: [] })).not.toThrow(); - - plugin.disable(); }); }); diff --git a/js/src/instrumentation/plugins/langchain-plugin.ts b/js/src/instrumentation/providers/langchain-instrumentation.ts similarity index 70% rename from js/src/instrumentation/plugins/langchain-plugin.ts rename to js/src/instrumentation/providers/langchain-instrumentation.ts index 5cd352210..1f49ee643 100644 --- a/js/src/instrumentation/plugins/langchain-plugin.ts +++ b/js/src/instrumentation/providers/langchain-instrumentation.ts @@ -1,4 +1,3 @@ -import { BasePlugin } from "../core"; import { runInstrumentation } from "../core/observe-result"; import type { LangChainCallbackManager } from "../../vendor-sdk-types/langchain"; @@ -13,51 +12,40 @@ type LangChainConfigureChannel = | typeof langChainChannels.configure | typeof langChainChannels.configureSync; -export class LangChainPlugin extends BasePlugin { +class LangChainInstrumentationConsumer { private injectedManagers = new WeakSet(); - protected onEnable(): void { + public register(): void { this.subscribeToConfigure(langChainChannels.configure); this.subscribeToConfigure(langChainChannels.configureSync); } - protected onDisable(): void { - for (const unsubscribe of this.unsubscribers) { - unsubscribe(); - } - this.unsubscribers = []; - this.injectedManagers = new WeakSet(); - } - private subscribeToConfigure(channel: LangChainConfigureChannel): void { const invocationHook = channel; - const removeHandlers = invocationHook.intercept( - (target, receiver, args, additional) => { - const event = { - ...additional, - arguments: args, - self: receiver, - } as ChannelMessage; - const prepare = (event: ChannelMessage) => { - injectHandlerIntoArguments(event.arguments); - }; - const returned = (event: ChannelMessage) => { - this.injectHandler(event.result); - }; - runInstrumentation(() => prepare(event)); - let result; - try { - result = Reflect.apply(target, receiver, args); - } catch (error) { - throw error; - } - Object.assign(event, { result }); - runInstrumentation(() => returned(event)); - return result; - }, - ); - this.unsubscribers.push(removeHandlers); + invocationHook.intercept((target, receiver, args, additional) => { + const event = { + ...additional, + arguments: args, + self: receiver, + } as ChannelMessage; + const prepare = (event: ChannelMessage) => { + injectHandlerIntoArguments(event.arguments); + }; + const returned = (event: ChannelMessage) => { + this.injectHandler(event.result); + }; + runInstrumentation(() => prepare(event)); + let result; + try { + result = Reflect.apply(target, receiver, args); + } catch (error) { + throw error; + } + Object.assign(event, { result }); + runInstrumentation(() => returned(event)); + return result; + }); } private injectHandler(result: unknown): void { @@ -136,3 +124,12 @@ function isBraintrustHandler(handler: unknown): boolean { Reflect.get(handler, "name") === BRAINTRUST_LANGCHAIN_CALLBACK_HANDLER_NAME ); } + +let langChainInstrumentationConsumer: + | LangChainInstrumentationConsumer + | undefined; + +export function registerLangChainInstrumentation(): void { + langChainInstrumentationConsumer ??= new LangChainInstrumentationConsumer(); + langChainInstrumentationConsumer.register(); +} diff --git a/js/src/instrumentation/plugins/langchain-interceptors.ts b/js/src/instrumentation/providers/langchain-interceptors.ts similarity index 100% rename from js/src/instrumentation/plugins/langchain-interceptors.ts rename to js/src/instrumentation/providers/langchain-interceptors.ts diff --git a/js/src/instrumentation/plugins/langgraph-sdk-plugin.test.ts b/js/src/instrumentation/providers/langgraph-sdk-instrumentation.test.ts similarity index 98% rename from js/src/instrumentation/plugins/langgraph-sdk-plugin.test.ts rename to js/src/instrumentation/providers/langgraph-sdk-instrumentation.test.ts index 483a1e558..bd180cf2f 100644 --- a/js/src/instrumentation/plugins/langgraph-sdk-plugin.test.ts +++ b/js/src/instrumentation/providers/langgraph-sdk-instrumentation.test.ts @@ -1,10 +1,20 @@ -import { afterEach, beforeAll, beforeEach, describe, expect, it } from "vitest"; -import { _exportsForTestingOnly, initLogger } from "../../logger"; +import { registerInstrumentation } from "../registrations/langgraphSDK"; +import { + afterEach, + beforeAll, + beforeEach, + describe, + expect, + it, +} from "vite-plus/test"; +import { _exportsForTestingOnly } from "../../logger/testing"; +import { initLogger } from "../../logger/core"; import { configureNode } from "../../node/config"; import { wrapLangGraphSDK } from "../../wrappers/langgraph-sdk"; import { langGraphSDKChannels } from "./langgraph-sdk-interceptors"; configureNode(); +registerInstrumentation(); describe("LangGraph SDK instrumentation", () => { let backgroundLogger: ReturnType< diff --git a/js/src/instrumentation/plugins/langgraph-sdk-plugin.ts b/js/src/instrumentation/providers/langgraph-sdk-instrumentation.ts similarity index 95% rename from js/src/instrumentation/plugins/langgraph-sdk-plugin.ts rename to js/src/instrumentation/providers/langgraph-sdk-instrumentation.ts index 59542f25e..30577e70f 100644 --- a/js/src/instrumentation/plugins/langgraph-sdk-plugin.ts +++ b/js/src/instrumentation/providers/langgraph-sdk-instrumentation.ts @@ -1,6 +1,6 @@ import { isObject, SpanTypeAttribute } from "../../../util"; import { debugLogger } from "../../debug-logger"; -import { startSpan, withCurrent, type Span } from "../../logger"; +import { startSpan, withCurrent, type Span } from "../../logger/core"; import { INSTRUMENTATION_NAMES, withSpanInstrumentationName, @@ -14,26 +14,16 @@ import { isAutoInstrumentationSuppressed, runWithAutoInstrumentationSuppressed, } from "../auto-instrumentation-suppression"; -import { BasePlugin } from "../core"; -import { unsubscribeAll } from "../core/tracing"; import { patchStreamIfNeeded } from "../core/stream-patcher"; import { langGraphSDKChannels } from "./langgraph-sdk-interceptors"; -export class LangGraphSDKPlugin extends BasePlugin { - protected onEnable(): void { - this.unsubscribers.push( - langGraphSDKChannels.wait.intercept((target, self, args) => - instrumentRun("wait", args, () => Reflect.apply(target, self, args)), - ), - langGraphSDKChannels.stream.intercept((target, self, args) => - instrumentRun("stream", args, () => Reflect.apply(target, self, args)), - ), - ); - } - - protected onDisable(): void { - this.unsubscribers = unsubscribeAll(this.unsubscribers); - } +export function registerLangGraphSDKInstrumentation(): void { + langGraphSDKChannels.wait.intercept((target, self, args) => + instrumentRun("wait", args, () => Reflect.apply(target, self, args)), + ); + langGraphSDKChannels.stream.intercept((target, self, args) => + instrumentRun("stream", args, () => Reflect.apply(target, self, args)), + ); } // Normalize only message fields; arbitrary graph state is application data. diff --git a/js/src/instrumentation/plugins/langgraph-sdk-interceptors.ts b/js/src/instrumentation/providers/langgraph-sdk-interceptors.ts similarity index 100% rename from js/src/instrumentation/plugins/langgraph-sdk-interceptors.ts rename to js/src/instrumentation/providers/langgraph-sdk-interceptors.ts diff --git a/js/src/instrumentation/plugins/langsmith-plugin.test.ts b/js/src/instrumentation/providers/langsmith-instrumentation.test.ts similarity index 86% rename from js/src/instrumentation/plugins/langsmith-plugin.test.ts rename to js/src/instrumentation/providers/langsmith-instrumentation.test.ts index aca5c0d4f..9cb7b0cd2 100644 --- a/js/src/instrumentation/plugins/langsmith-plugin.test.ts +++ b/js/src/instrumentation/providers/langsmith-instrumentation.test.ts @@ -1,37 +1,58 @@ -import { afterEach, beforeAll, beforeEach, describe, expect, it } from "vitest"; -import { _exportsForTestingOnly, initLogger } from "../../logger"; +import { resetInstrumentationForTest } from "../test-utils/reset"; +import { LRUCache } from "../../lru-cache"; +vi.mock("../../lru-cache", async (importOriginal) => { + const actual = await importOriginal(); + return { + ...actual, + LRUCache: vi.fn(function (options) { + return new actual.LRUCache(options); + }), + }; +}); +import { configureInstrumentation } from "../registry"; +import { + afterEach, + beforeAll, + beforeEach, + describe, + expect, + it, + vi, +} from "vite-plus/test"; +import { _exportsForTestingOnly } from "../../logger/testing"; +import { initLogger } from "../../logger/core"; import { configureNode } from "../../node/config"; import { langSmithChannels } from "./langsmith-interceptors"; -import { LangSmithPlugin } from "./langsmith-plugin"; +import { registerLangSmithInstrumentation } from "./langsmith-instrumentation"; try { + configureInstrumentation({ integrations: { langsmith: false } }); configureNode(); } catch { // Best-effort initialization for test environments. } -describe("LangSmithPlugin", () => { +describe("registerLangSmithInstrumentation", () => { + let completedRunCache: LRUCache; let backgroundLogger: ReturnType< typeof _exportsForTestingOnly.useTestBackgroundLogger >; - let plugin: LangSmithPlugin; beforeAll(async () => { await _exportsForTestingOnly.simulateLoginForTests(); + registerLangSmithInstrumentation(); + completedRunCache = vi.mocked(LRUCache).mock.results.at(-1)!.value; }); beforeEach(() => { backgroundLogger = _exportsForTestingOnly.useTestBackgroundLogger(); initLogger({ - projectName: "langsmith-plugin.test.ts", + projectName: "langsmith-instrumentation.test.ts", projectId: "test-project-id", }); - plugin = new LangSmithPlugin(); - plugin.enable(); }); afterEach(() => { - plugin.disable(); _exportsForTestingOnly.clearTestBackgroundLogger(); }); @@ -286,7 +307,7 @@ describe("LangSmithPlugin", () => { expect(await backgroundLogger.drain()).toEqual([]); }); - it("suppresses LangChain-serialized runs only when configured", async () => { + it("suppresses LangChain-serialized runs by default", async () => { const run = { id: "44444444-4444-4444-8444-444444444444", trace_id: "44444444-4444-4444-8444-444444444444", @@ -303,9 +324,10 @@ describe("LangSmithPlugin", () => { ); expect(await backgroundLogger.drain()).toEqual([]); - plugin.disable(); - plugin = new LangSmithPlugin({ skipLangChainRuns: false }); - plugin.enable(); + resetInstrumentationForTest(); + vi.resetModules(); + const otherBundle = await import("./langsmith-instrumentation"); + otherBundle.registerLangSmithInstrumentation({ skipLangChainRuns: false }); await langSmithChannels.updateRun.invoke( async () => undefined, undefined, @@ -316,14 +338,7 @@ describe("LangSmithPlugin", () => { }); it("bounds the completed-run deduplication cache", () => { - const completedRuns = ( - plugin as unknown as { - completedRuns: { - get(key: string): true | undefined; - set(key: string, value: true): void; - }; - } - ).completedRuns; + const completedRuns = completedRunCache; for (let index = 0; index <= 10_000; index++) { completedRuns.set(`run-${index}`, true); diff --git a/js/src/instrumentation/plugins/langsmith-plugin.ts b/js/src/instrumentation/providers/langsmith-instrumentation.ts similarity index 85% rename from js/src/instrumentation/plugins/langsmith-plugin.ts rename to js/src/instrumentation/providers/langsmith-instrumentation.ts index c619b4994..b8968f17b 100644 --- a/js/src/instrumentation/plugins/langsmith-plugin.ts +++ b/js/src/instrumentation/providers/langsmith-instrumentation.ts @@ -1,7 +1,7 @@ import { SpanTypeAttribute } from "../../../util/index"; import { debugLogger } from "../../debug-logger"; -import type { Span } from "../../logger"; -import { startSpan as startBaseSpan } from "../../logger"; +import type { Span } from "../../logger/core"; +import { startSpan as startBaseSpan } from "../../logger/core"; import { LRUCache } from "../../lru-cache"; import { INSTRUMENTATION_NAMES, @@ -11,8 +11,7 @@ import type { LangSmithBatchIngestRuns, LangSmithRun, } from "../../vendor-sdk-types/langsmith"; -import { BasePlugin } from "../core"; -import { unsubscribeAll } from "../core/tracing"; + import { runInstrumentation } from "../core/observe-result"; import type { ChannelMessage } from "../core/tracing-types"; @@ -23,8 +22,8 @@ type ActiveRun = { span: Span; }; -type LangSmithPluginOptions = { - skipLangChainRuns?: boolean; +type LangSmithInstrumentationOptions = { + skipLangChainRuns?: boolean | (() => boolean); }; const MAX_COMPLETED_RUNS = 10_000; @@ -45,86 +44,63 @@ const LLM_SETTING_KEYS = [ "response_format", ] as const; -export class LangSmithPlugin extends BasePlugin { +class LangSmithInstrumentationConsumer { private readonly activeRuns = new Map(); private readonly completedRuns = new LRUCache({ max: MAX_COMPLETED_RUNS, }); - private readonly skipLangChainRuns: boolean; + private readonly skipLangChainRuns: boolean | (() => boolean); - constructor(options: LangSmithPluginOptions = {}) { - super(); + constructor(options: LangSmithInstrumentationOptions = {}) { this.skipLangChainRuns = options.skipLangChainRuns ?? true; } - protected onEnable(): void { + public register(): void { const createChannel = langSmithChannels.createRun; - const removecreateHandlers = createChannel.intercept( - (target, receiver, args, additional) => { - runInstrumentation(() => - (( - event: ChannelMessage, - ): void => { - this.containLifecycleFailure("createRun", () => { - this.processCreate(additional.runTree ?? event.arguments[0]); - }); - })({ arguments: args }), - ); - return Reflect.apply(target, receiver, args); - }, - ); - this.unsubscribers.push(removecreateHandlers); + createChannel.intercept((target, receiver, args, additional) => { + runInstrumentation(() => + ((event: ChannelMessage): void => { + this.containLifecycleFailure("createRun", () => { + this.processCreate(additional.runTree ?? event.arguments[0]); + }); + })({ arguments: args }), + ); + return Reflect.apply(target, receiver, args); + }); const updateChannel = langSmithChannels.updateRun; - const removeupdateHandlers = updateChannel.intercept( - (target, receiver, args, additional) => { - runInstrumentation(() => - (( - event: ChannelMessage, - ): void => { - this.containLifecycleFailure("updateRun", () => { - this.processUpdate( - additional.runTree - ? ((additional.runTree as LangSmithRun).id as string) - : event.arguments[0], - additional.runTree ?? event.arguments[1], - ); - }); - })({ arguments: args }), - ); - return Reflect.apply(target, receiver, args); - }, - ); - this.unsubscribers.push(removeupdateHandlers); + updateChannel.intercept((target, receiver, args, additional) => { + runInstrumentation(() => + ((event: ChannelMessage): void => { + this.containLifecycleFailure("updateRun", () => { + this.processUpdate( + additional.runTree + ? ((additional.runTree as LangSmithRun).id as string) + : event.arguments[0], + additional.runTree ?? event.arguments[1], + ); + }); + })({ arguments: args }), + ); + return Reflect.apply(target, receiver, args); + }); const batchChannel = langSmithChannels.batchIngestRuns; - const removebatchHandlers = batchChannel.intercept( - (target, receiver, args, additional) => { - runInstrumentation(() => - (( - event: ChannelMessage, - ): void => { - this.containLifecycleFailure("batchIngestRuns", () => { - this.processBatch(event.arguments[0]); - }); - })({ arguments: args }), - ); - return Reflect.apply(target, receiver, args); - }, - ); - this.unsubscribers.push(removebatchHandlers); - } - - protected onDisable(): void { - this.unsubscribers = unsubscribeAll(this.unsubscribers); - for (const { span } of this.activeRuns.values()) { - span.end(); - } - this.activeRuns.clear(); - this.completedRuns.clear(); + batchChannel.intercept((target, receiver, args, additional) => { + runInstrumentation(() => + (( + event: ChannelMessage, + ): void => { + this.containLifecycleFailure("batchIngestRuns", () => { + this.processBatch(event.arguments[0]); + }); + })({ arguments: args }), + ); + return Reflect.apply(target, receiver, args); + }); } private processBatch(batch: LangSmithBatchIngestRuns): void { @@ -298,7 +274,11 @@ export class LangSmithPlugin extends BasePlugin { } private shouldSkipLangChainRun(run: LangSmithRun): boolean { - if (!this.skipLangChainRuns) { + const skip = + typeof this.skipLangChainRuns === "function" + ? this.skipLangChainRuns() + : this.skipLangChainRuns; + if (!skip) { return false; } const serialized = ownValue(run, "serialized"); @@ -359,6 +339,19 @@ function mergeRuns( return Object.fromEntries(entries); } +let langSmithInstrumentationConsumer: + | LangSmithInstrumentationConsumer + | undefined; + +export function registerLangSmithInstrumentation( + options: LangSmithInstrumentationOptions = {}, +): void { + langSmithInstrumentationConsumer ??= new LangSmithInstrumentationConsumer( + options, + ); + langSmithInstrumentationConsumer.register(); +} + function isRecord(value: unknown): value is Record { return typeof value === "object" && value !== null && !Array.isArray(value); } diff --git a/js/src/instrumentation/plugins/langsmith-interceptors.ts b/js/src/instrumentation/providers/langsmith-interceptors.ts similarity index 100% rename from js/src/instrumentation/plugins/langsmith-interceptors.ts rename to js/src/instrumentation/providers/langsmith-interceptors.ts diff --git a/js/src/instrumentation/plugins/livekit-interceptors.ts b/js/src/instrumentation/providers/livekit-interceptors.ts similarity index 100% rename from js/src/instrumentation/plugins/livekit-interceptors.ts rename to js/src/instrumentation/providers/livekit-interceptors.ts diff --git a/js/src/instrumentation/plugins/livekit-plugin.test.ts b/js/src/instrumentation/providers/livekit-plugin.test.ts similarity index 99% rename from js/src/instrumentation/plugins/livekit-plugin.test.ts rename to js/src/instrumentation/providers/livekit-plugin.test.ts index 68cb3b8f5..6fd3f1da5 100644 --- a/js/src/instrumentation/plugins/livekit-plugin.test.ts +++ b/js/src/instrumentation/providers/livekit-plugin.test.ts @@ -7,15 +7,12 @@ import { expect, test, vi, -} from "vitest"; +} from "vite-plus/test"; import { EventEmitter } from "node:events"; import { z } from "zod/v3"; import { configureNode } from "../../node/config"; -import { - Attachment, - _exportsForTestingOnly as logging, - initLogger, -} from "../../logger"; +import { Attachment, initLogger } from "../../logger/core"; +import { _exportsForTestingOnly as logging } from "../../logger/testing"; import type { LiveKitSpan, SpanTime } from "../../vendor-sdk-types/livekit"; import type { AudioMiddleware } from "../../audio-middleware"; import { DEFAULT_STREAMING_ATTACHMENT_BUFFER_BYTES } from "../config"; @@ -38,7 +35,7 @@ beforeEach(() => { projectName: "tmp-luca-livekit-unit", projectId: "test-project-id", }); - vi.stubEnv("BRAINTRUST_CAPTURE_ATTACHMENTS", ""); + vi.stubEnv("BRAINTRUST_AUTO_CAPTURE_ATTACHMENTS", ""); now = 1_000_000; vi.spyOn(Date, "now").mockImplementation(() => now); uploads = []; @@ -905,16 +902,18 @@ describe("recording", () => { const selections = (name: string) => result .one(name) - .metadata?.[ - "audio.selections" - ]?.map((s: any) => [s.recording_id, s.start_offset_ms, s.end_offset_ms]); + .metadata?.["audio.selections"]?.map((s: any) => [ + s.recording_id, + s.start_offset_ms, + s.end_offset_ms, + ]); return { ...result, recordings, selections }; }, }; } test("the shared attachment flag alone records no voice audio", async () => { - vi.stubEnv("BRAINTRUST_CAPTURE_ATTACHMENTS", "true"); + vi.stubEnv("BRAINTRUST_AUTO_CAPTURE_ATTACHMENTS", "true"); vi.stubEnv("BRAINTRUST_CAPTURE_USER_AUDIO_ATTACHMENTS", ""); vi.stubEnv("BRAINTRUST_CAPTURE_AGENT_AUDIO_ATTACHMENTS", ""); const stream = new ReadableStream(); diff --git a/js/src/instrumentation/plugins/livekit-plugin.ts b/js/src/instrumentation/providers/livekit-plugin.ts similarity index 93% rename from js/src/instrumentation/plugins/livekit-plugin.ts rename to js/src/instrumentation/providers/livekit-plugin.ts index b0369b93e..040f13926 100644 --- a/js/src/instrumentation/plugins/livekit-plugin.ts +++ b/js/src/instrumentation/providers/livekit-plugin.ts @@ -16,13 +16,18 @@ import type { SpeechEvent, TraceOptions, } from "../../vendor-sdk-types/livekit"; -import iso from "../../isomorph"; -import { BasePlugin } from "../core"; +import iso from "#braintrust-platform"; + import { runInstrumentation } from "../core/observe-result"; import { livekitChannels } from "./livekit-interceptors"; import { v4 as uuidv4 } from "uuid"; import { runWithAutoInstrumentationSuppressed } from "../auto-instrumentation-suppression"; -import { Attachment, startSpan, withCurrent, type Span } from "../../logger"; +import { + Attachment, + startSpan, + withCurrent, + type Span, +} from "../../logger/core"; import { withSpanInstrumentationName, INSTRUMENTATION_NAMES, @@ -56,7 +61,7 @@ const SAMPLE_RATE = 24000; const OTEL_SPAN_KEY = Symbol.for("OpenTelemetry Context Key SPAN"); const FINAL_TRANSCRIPT = 2; // SpeechEventType.FINAL_TRANSCRIPT // Voice audio is personal data, so each direction has its own explicit flag rather -// than the shared BRAINTRUST_CAPTURE_ATTACHMENTS, which also enables other media. +// than the shared BRAINTRUST_AUTO_CAPTURE_ATTACHMENTS, which also enables other media. const CAPTURE_USER_AUDIO = "BRAINTRUST_CAPTURE_USER_AUDIO_ATTACHMENTS"; const CAPTURE_AGENT_AUDIO = "BRAINTRUST_CAPTURE_AGENT_AUDIO_ATTACHMENTS"; /** Speech compresses well as Opus, and Ogg is its standard file format. */ @@ -86,111 +91,103 @@ const METRICS = { time_to_first_token: "contrib.livekit.response.ttft", } as const; -export class LiveKitPlugin extends BasePlugin { - private tracing?: LiveKitTracing; - constructor( - private streamingAttachmentBufferBytes = DEFAULT_STREAMING_ATTACHMENT_BUFFER_BYTES, - private audio?: AudioEncodingConfig, - ) { - super(); - } - - protected onEnable(): void { - const tracing = (this.tracing = new LiveKitTracing( - this.streamingAttachmentBufferBytes, - () => audioEncodingStack(this.audio, AUDIO_DEFAULTS), - )); +class LiveKitInstrumentationConsumer { + public register( + streamingAttachmentBufferBytes: number, + audio?: AudioEncodingConfig, + ): void { + const tracing = new LiveKitTracing(streamingAttachmentBufferBytes, () => + audioEncodingStack(audio, AUDIO_DEFAULTS), + ); const observeActive = (fn: (span: LiveKitSpan) => T, options: TraceOptions) => (span: LiveKitSpan) => { runInstrumentation(() => tracing.observeSpan(span, options)); return tracing.scope(span, () => fn(span)); }; - this.unsubscribers.push( - livekitChannels.startSpan.intercept((target, self, args) => { - const span = Reflect.apply(target, self, args); - runInstrumentation(() => tracing.observeSpan(span, args[0])); - return span; - }), - livekitChannels.startActiveSpan.intercept((target, self, [fn, options]) => + livekitChannels.startSpan.intercept((target, self, args) => { + const span = Reflect.apply(target, self, args); + runInstrumentation(() => tracing.observeSpan(span, args[0])); + return span; + }); + livekitChannels.startActiveSpan.intercept((target, self, [fn, options]) => + Reflect.apply(target, self, [observeActive(fn, options), options]), + ); + livekitChannels.startActiveSpanSync.intercept( + (target, self, [fn, options]) => Reflect.apply(target, self, [observeActive(fn, options), options]), + ); + livekitChannels.inference.intercept((target, self, args) => + tracing.inference(() => Reflect.apply(target, self, args)), + ); + livekitChannels.start.intercept((target, self, args) => + tracing.scope((self as AgentSession).sessionSpan, () => + Reflect.apply(target, self, args), ), - livekitChannels.startActiveSpanSync.intercept( - (target, self, [fn, options]) => - Reflect.apply(target, self, [observeActive(fn, options), options]), - ), - livekitChannels.inference.intercept((target, self, args) => - tracing.inference(() => Reflect.apply(target, self, args)), - ), - livekitChannels.start.intercept((target, self, args) => - tracing.scope((self as AgentSession).sessionSpan, () => - Reflect.apply(target, self, args), - ), - ), - livekitChannels.activity.intercept((target, self, args) => { - const session = (self as AgentActivity).agentSession; - runInstrumentation(() => tracing.output(session)); - return tracing.scope(session.sessionSpan, () => - Reflect.apply(target, self, args), - ); - }), - // Each reply re-checks the session's sink, so replaced outputs are observed. - livekitChannels.pipeline.intercept((target, self, args) => { - runInstrumentation(() => - tracing.output((self as AgentActivity).agentSession), - ); - return tracing.scope( - args[0].span, - () => Reflect.apply(target, self, args), - runInstrumentation(() => requestMetadata(args[0])) ?? {}, - ); - }), - livekitChannels.conversation.intercept((target, self, args) => { - runInstrumentation(() => - tracing.conversation(self as AgentSession, args[0]), - ); - return Reflect.apply(target, self, args); - }), - livekitChannels.input.intercept((target, self, args) => { - runInstrumentation(() => tracing.input(self as AgentActivity, args[0])); - return Reflect.apply(target, self, args); - }), - livekitChannels.userTurn.intercept((target, self, args) => { - runInstrumentation(() => - tracing.userTurn(self as AgentActivity, args[0]), - ); - return Reflect.apply(target, self, args); - }), - livekitChannels.speechStart.intercept((target, self, args) => - tracing.scope( - (self as AgentActivity).audioRecognition?.userTurnSpan, - () => Reflect.apply(target, self, args), - ), + ); + livekitChannels.activity.intercept((target, self, args) => { + const session = (self as AgentActivity).agentSession; + runInstrumentation(() => tracing.output(session)); + return tracing.scope(session.sessionSpan, () => + Reflect.apply(target, self, args), + ); + }); + livekitChannels.pipeline.intercept((target, self, args) => { + runInstrumentation(() => + tracing.output((self as AgentActivity).agentSession), + ); + return tracing.scope( + args[0].span, + () => Reflect.apply(target, self, args), + runInstrumentation(() => requestMetadata(args[0])) ?? {}, + ); + }); + livekitChannels.conversation.intercept((target, self, args) => { + runInstrumentation(() => + tracing.conversation(self as AgentSession, args[0]), + ); + return Reflect.apply(target, self, args); + }); + livekitChannels.input.intercept((target, self, args) => { + runInstrumentation(() => tracing.input(self as AgentActivity, args[0])); + return Reflect.apply(target, self, args); + }); + livekitChannels.userTurn.intercept((target, self, args) => { + runInstrumentation(() => + tracing.userTurn(self as AgentActivity, args[0]), + ); + return Reflect.apply(target, self, args); + }); + livekitChannels.speechStart.intercept((target, self, args) => + tracing.scope( + (self as AgentActivity).audioRecognition?.userTurnSpan, + () => Reflect.apply(target, self, args), ), - livekitChannels.stt.intercept((target, self, args) => { - runInstrumentation(() => - tracing.stt(self as AudioRecognition, args[0]), - ); - return Reflect.apply(target, self, args); - }), - livekitChannels.userCompleted.intercept((target, self, args) => - tracing.scope(args[0].userTurnSpan, () => - Reflect.apply(target, self, args), - ), + ); + livekitChannels.stt.intercept((target, self, args) => { + runInstrumentation(() => tracing.stt(self as AudioRecognition, args[0])); + return Reflect.apply(target, self, args); + }); + livekitChannels.userCompleted.intercept((target, self, args) => + tracing.scope(args[0].userTurnSpan, () => + Reflect.apply(target, self, args), ), - livekitChannels.close.intercept((target, self, args) => - tracing.close(self as AgentSession, () => - Reflect.apply(target, self, args), - ), + ); + livekitChannels.close.intercept((target, self, args) => + tracing.close(self as AgentSession, () => + Reflect.apply(target, self, args), ), ); } - - protected onDisable(): void { - for (const unsubscribe of this.unsubscribers.splice(0)) unsubscribe(); - void this.tracing?.shutdown(); - this.tracing = undefined; - } +} +export function registerLiveKitInstrumentation( + streamingAttachmentBufferBytes = DEFAULT_STREAMING_ATTACHMENT_BUFFER_BYTES, + audio?: AudioEncodingConfig, +): void { + new LiveKitInstrumentationConsumer().register( + streamingAttachmentBufferBytes, + audio, + ); } type Row = { @@ -1403,7 +1400,7 @@ class Recording { }; this.publishFiles(); const attachment = new Attachment({ - data: segment, + data: segment.slice().buffer, filename: `${file.id}.${fileExtension(meta.mimeType)}`, contentType: meta.mimeType, }); diff --git a/js/src/instrumentation/plugins/mcp-interceptors.ts b/js/src/instrumentation/providers/mcp-interceptors.ts similarity index 100% rename from js/src/instrumentation/plugins/mcp-interceptors.ts rename to js/src/instrumentation/providers/mcp-interceptors.ts diff --git a/js/src/instrumentation/plugins/mcp-plugin.test.ts b/js/src/instrumentation/providers/mcp-plugin.test.ts similarity index 97% rename from js/src/instrumentation/plugins/mcp-plugin.test.ts rename to js/src/instrumentation/providers/mcp-plugin.test.ts index f46d760a0..7d43fc421 100644 --- a/js/src/instrumentation/plugins/mcp-plugin.test.ts +++ b/js/src/instrumentation/providers/mcp-plugin.test.ts @@ -1,3 +1,4 @@ +import { _exportsForTestingOnly } from "../../logger/testing"; import { afterEach, beforeAll, @@ -6,15 +7,16 @@ import { expect, it, vi, -} from "vitest"; +} from "vite-plus/test"; import { Attachment, - _exportsForTestingOnly, initLogger, traced, - withParent, -} from "../../logger"; -import { registry } from "../registry"; + _internalWithParent as withParent, +} from "../../logger/core"; +import { registerMCPInstrumentation } from "./mcp-plugin"; +import type { MCPIntegrationConfig } from "../config"; +const config: MCPIntegrationConfig = {}; import { configureNode } from "../../node/config"; import type { MCPProtocol, @@ -84,15 +86,15 @@ describe("MCPPlugin", () => { >; beforeAll(async () => { + registerMCPInstrumentation(config); await _exportsForTestingOnly.simulateLoginForTests(); }); beforeEach(() => { - registry.disable(); - registry.configure({ integrations: { mcp: { captureContent: true } } }); - registry.enable(); + config.captureContent = true; backgroundLogger = _exportsForTestingOnly.useTestBackgroundLogger(); initLogger({ + autoCaptureAttachments: false, projectName: "mcp-plugin.test.ts", projectId: "test-project-id", }); @@ -307,7 +309,11 @@ describe("MCPPlugin", () => { const request = { method: "tools/call", params: { name: "render" } }; await handleRequest(async () => result, request); - vi.stubEnv("BRAINTRUST_CAPTURE_ATTACHMENTS", "1"); + initLogger({ + projectName: "mcp-plugin.test.ts", + projectId: "test-project-id", + autoCaptureAttachments: true, + }); await handleRequest(async () => result, request); const [disabled, enabled] = await drainSpans(); @@ -753,7 +759,11 @@ describe("MCPPlugin", () => { }); it("retains sanitized results if attachment conversion fails", async () => { - vi.stubEnv("BRAINTRUST_CAPTURE_ATTACHMENTS", "true"); + initLogger({ + projectName: "mcp-plugin.test.ts", + projectId: "test-project-id", + autoCaptureAttachments: true, + }); await handleRequest( async () => ({ _meta: { secret: true }, @@ -890,9 +900,7 @@ describe("MCPPlugin", () => { }); it("captures arguments, results, and tool error text by default", async () => { - registry.disable(); - registry.configure({ integrations: { mcp: true } }); - registry.enable(); + delete config.captureContent; await handleRequest( async () => ({ content: [{ type: "text", text: "City not found" }], @@ -913,10 +921,12 @@ describe("MCPPlugin", () => { }); it("does not capture arguments, results, or tool error text when captureContent is false", async () => { - registry.disable(); - registry.configure({ integrations: { mcp: { captureContent: false } } }); - registry.enable(); - vi.stubEnv("BRAINTRUST_CAPTURE_ATTACHMENTS", "true"); + config.captureContent = false; + initLogger({ + projectName: "mcp-plugin.test.ts", + projectId: "test-project-id", + autoCaptureAttachments: true, + }); const result = { content: [{ type: "text", text: "private error text" }], isError: true, diff --git a/js/src/instrumentation/providers/mcp-plugin.ts b/js/src/instrumentation/providers/mcp-plugin.ts new file mode 100644 index 000000000..2c49572b6 --- /dev/null +++ b/js/src/instrumentation/providers/mcp-plugin.ts @@ -0,0 +1,116 @@ +import type { + MCPProtocol, + MCPRequestHandler, +} from "../../vendor-sdk-types/mcp"; +import type { MCPIntegrationConfig } from "../config"; +import { runInstrumentation } from "../core/observe-result"; +import { + mcpChannels, + mcpSDKChannels, + mcpServerChannels, + wrapMCPProtocol, + wrapMCPTransport, + wrapMCPRequestHandler, +} from "./mcp-interceptors"; +import { + traceMCPMessage, + traceMCPSend, + traceMCPClose, + traceMCPHandler, + traceMCPProtocolError, + traceMCPNotificationHandler, + traceMCPHttpRequest, +} from "./mcp-tracing"; + +export function registerMCPInstrumentation( + config: MCPIntegrationConfig = {}, +): void { + void [ + ...[ + mcpChannels.connect, + mcpSDKChannels.connect, + mcpServerChannels.connect, + ].map((channel) => + channel.intercept((target, receiver, args) => { + const protocol = receiver as MCPProtocol; + // Protocol is shared with clients. Only servers have a server identity; + // the manual wrapper's channel is an explicit server opt-in. + if ( + channel === mcpChannels.connect || + protocol._serverInfo !== undefined + ) { + runInstrumentation(() => { + wrapMCPProtocol(protocol); + wrapMCPTransport(args[0], protocol); + }); + } + return Reflect.apply(target, receiver, args); + }), + ), + ...[ + mcpSDKChannels.setRequestHandler, + mcpServerChannels.setRequestHandler, + ].map((channel) => + channel.intercept((target, receiver, args) => { + const wrappedArgs = [...args]; + const index = wrappedArgs.length - 1; + if (typeof wrappedArgs[index] === "function") { + wrappedArgs[index] = wrapMCPRequestHandler( + wrappedArgs[index] as MCPRequestHandler, + receiver as MCPProtocol, + ); + } + return Reflect.apply(target, receiver, wrappedArgs); + }), + ), + mcpChannels.message.intercept((target, receiver, args, { transport }) => + traceMCPMessage( + () => Reflect.apply(target, receiver, args), + transport, + args[0], + config, + ), + ), + mcpChannels.send.intercept((target, receiver, args, { transport }) => + traceMCPSend( + () => Reflect.apply(target, receiver, args), + transport, + args[0], + args[1], + config, + ), + ), + mcpChannels.close.intercept((target, receiver, args, { transport }) => + traceMCPClose(() => Reflect.apply(target, receiver, args), transport), + ), + mcpChannels.requestHandler.intercept( + (target, receiver, args, { protocol }) => + traceMCPHandler( + () => Reflect.apply(target, receiver, args), + protocol, + args[0], + args[1], + ), + ), + mcpChannels.protocolError.intercept((target, receiver, args) => + traceMCPProtocolError( + () => Reflect.apply(target, receiver, args), + args[0], + ), + ), + mcpChannels.notificationHandler.intercept( + (target, receiver, args, { protocol }) => + traceMCPNotificationHandler( + () => Reflect.apply(target, receiver, args), + protocol, + ), + ), + mcpChannels.httpRequest.intercept((target, receiver, args, { transport }) => + traceMCPHttpRequest( + () => Reflect.apply(target, receiver, args), + transport, + args[0], + ), + ), + ]; +} diff --git a/js/src/instrumentation/plugins/mcp-tracing.ts b/js/src/instrumentation/providers/mcp-tracing.ts similarity index 99% rename from js/src/instrumentation/plugins/mcp-tracing.ts rename to js/src/instrumentation/providers/mcp-tracing.ts index ff9527976..9001d0e3a 100644 --- a/js/src/instrumentation/plugins/mcp-tracing.ts +++ b/js/src/instrumentation/providers/mcp-tracing.ts @@ -1,14 +1,14 @@ import { automaticAttachmentOriginals } from "../../automatic-attachments"; import { SpanTypeAttribute, isObject } from "../../../util/index"; -import iso, { type IsoAsyncLocalStorage } from "../../isomorph"; +import iso, { type IsoAsyncLocalStorage } from "#braintrust-platform"; import { Attachment, NOOP_SPAN, extractTraceContextFromHeaders, - startSpan, + _internalStartSpan as startSpan, withCurrent, type Span, -} from "../../logger"; +} from "../../logger/core"; import { INSTRUMENTATION_NAMES, withSpanInstrumentationName, @@ -570,7 +570,7 @@ export function traceMCPSend( }, ...(state.captureContent && ["tools/call", "prompts/get", "resources/read"].includes(state.method) - ? { output: sanitizeResult(result) } + ? { output: withCurrent(state.span, () => sanitizeResult(result)) } : {}), }); if ( diff --git a/js/src/instrumentation/plugins/mistral-plugin.test.ts b/js/src/instrumentation/providers/mistral-instrumentation.test.ts similarity index 99% rename from js/src/instrumentation/plugins/mistral-plugin.test.ts rename to js/src/instrumentation/providers/mistral-instrumentation.test.ts index 96d410440..804d2db3d 100644 --- a/js/src/instrumentation/plugins/mistral-plugin.test.ts +++ b/js/src/instrumentation/providers/mistral-instrumentation.test.ts @@ -1,10 +1,10 @@ -import { describe, expect, it } from "vitest"; +import { describe, expect, it } from "vite-plus/test"; import { aggregateMistralStreamChunks, extractMistralRequestMetadata, extractMistralResponseMetadata, parseMistralMetricsFromUsage, -} from "./mistral-plugin"; +} from "./mistral-instrumentation"; describe("extractMistralRequestMetadata", () => { it("keeps only allowlisted request metadata", () => { diff --git a/js/src/instrumentation/plugins/mistral-plugin.ts b/js/src/instrumentation/providers/mistral-instrumentation.ts similarity index 65% rename from js/src/instrumentation/plugins/mistral-plugin.ts rename to js/src/instrumentation/providers/mistral-instrumentation.ts index cb3746702..bd72ad12d 100644 --- a/js/src/instrumentation/plugins/mistral-plugin.ts +++ b/js/src/instrumentation/providers/mistral-instrumentation.ts @@ -12,267 +12,223 @@ import type { MistralToolCallDelta, } from "../../vendor-sdk-types/mistral"; import { processInputAttachments } from "../../wrappers/attachment-utils"; -import { BasePlugin } from "../core"; -import { - traceAsyncCall, - traceStreamingCall, - unsubscribeAll, -} from "../core/tracing"; + +import { traceAsyncCall, traceStreamingCall } from "../core/tracing"; import { mistralChannels } from "./mistral-interceptors"; -export class MistralPlugin extends BasePlugin { - protected onEnable(): void { +class MistralInstrumentationConsumer { + public register(): void { this.subscribeToMistralChannels(); } - - protected onDisable(): void { - this.unsubscribers = unsubscribeAll(this.unsubscribers); - } - private subscribeToMistralChannels(): void { - this.unsubscribers.push( - mistralChannels.chatComplete.intercept( - (target, receiver, args, additional) => - traceStreamingCall( - () => Reflect.apply(target, receiver, args), - { ...additional, arguments: args, self: receiver }, - { - instrumentationName: INSTRUMENTATION_NAMES.MISTRAL, - name: "mistral.chat.complete", - type: SpanTypeAttribute.LLM, - extractInput: extractMessagesInputWithMetadata, - extractOutput: (result) => { - return result?.choices; - }, - extractMetadata: (result) => - extractMistralResponseMetadata(result), - extractMetrics: (result, startTime) => - extractMistralMetrics(result?.usage, startTime), + mistralChannels.chatComplete.intercept( + (target, receiver, args, additional) => + traceStreamingCall( + () => Reflect.apply(target, receiver, args), + { ...additional, arguments: args, self: receiver }, + { + instrumentationName: INSTRUMENTATION_NAMES.MISTRAL, + name: "mistral.chat.complete", + type: SpanTypeAttribute.LLM, + extractInput: extractMessagesInputWithMetadata, + extractOutput: (result) => { + return result?.choices; }, - ), - ), + extractMetadata: (result) => extractMistralResponseMetadata(result), + extractMetrics: (result, startTime) => + extractMistralMetrics(result?.usage, startTime), + }, + ), ); - this.unsubscribers.push( - mistralChannels.chatStream.intercept( - (target, receiver, args, additional) => - traceStreamingCall( - () => Reflect.apply(target, receiver, args), - { ...additional, arguments: args, self: receiver }, - { - instrumentationName: INSTRUMENTATION_NAMES.MISTRAL, - name: "mistral.chat.stream", - type: SpanTypeAttribute.LLM, - extractInput: extractMessagesInputWithMetadata, - extractOutput: extractMistralStreamOutput, - extractMetadata: (result) => - extractMistralResponseMetadata(result), - extractMetrics: (result, startTime) => - extractMistralStreamingMetrics(result, startTime), - aggregateChunks: aggregateMistralStreamChunks, - }, - ), + mistralChannels.chatStream.intercept((target, receiver, args, additional) => + traceStreamingCall( + () => Reflect.apply(target, receiver, args), + { ...additional, arguments: args, self: receiver }, + { + instrumentationName: INSTRUMENTATION_NAMES.MISTRAL, + name: "mistral.chat.stream", + type: SpanTypeAttribute.LLM, + extractInput: extractMessagesInputWithMetadata, + extractOutput: extractMistralStreamOutput, + extractMetadata: (result) => extractMistralResponseMetadata(result), + extractMetrics: (result, startTime) => + extractMistralStreamingMetrics(result, startTime), + aggregateChunks: aggregateMistralStreamChunks, + }, ), ); - this.unsubscribers.push( - mistralChannels.embeddingsCreate.intercept( - (target, receiver, args, additional) => - traceAsyncCall( - () => Reflect.apply(target, receiver, args), - { ...additional, arguments: args, self: receiver }, - { - instrumentationName: INSTRUMENTATION_NAMES.MISTRAL, - name: "mistral.embeddings.create", - type: SpanTypeAttribute.LLM, - extractInput: extractEmbeddingInputWithMetadata, - extractOutput: (result) => { - const embedding = result?.data?.[0]?.embedding; - return Array.isArray(embedding) - ? { embedding_length: embedding.length } - : undefined; - }, - extractMetadata: (result) => - extractMistralResponseMetadata(result), - extractMetrics: (result) => - parseMistralMetricsFromUsage(result?.usage), + mistralChannels.embeddingsCreate.intercept( + (target, receiver, args, additional) => + traceAsyncCall( + () => Reflect.apply(target, receiver, args), + { ...additional, arguments: args, self: receiver }, + { + instrumentationName: INSTRUMENTATION_NAMES.MISTRAL, + name: "mistral.embeddings.create", + type: SpanTypeAttribute.LLM, + extractInput: extractEmbeddingInputWithMetadata, + extractOutput: (result) => { + const embedding = result?.data?.[0]?.embedding; + return Array.isArray(embedding) + ? { embedding_length: embedding.length } + : undefined; }, - ), - ), + extractMetadata: (result) => extractMistralResponseMetadata(result), + extractMetrics: (result) => + parseMistralMetricsFromUsage(result?.usage), + }, + ), ); - this.unsubscribers.push( - mistralChannels.classifiersModerate.intercept( - (target, receiver, args, additional) => - traceAsyncCall( - () => Reflect.apply(target, receiver, args), - { ...additional, arguments: args, self: receiver }, - { - instrumentationName: INSTRUMENTATION_NAMES.MISTRAL, - name: "mistral.classifiers.moderate", - type: SpanTypeAttribute.LLM, - extractInput: extractClassifierInputWithMetadata, - extractOutput: extractClassifierOutput, - extractMetadata: (result) => - extractMistralResponseMetadata(result), - extractMetrics: (result) => - parseMistralMetricsFromUsage(result?.usage), - }, - ), - ), + mistralChannels.classifiersModerate.intercept( + (target, receiver, args, additional) => + traceAsyncCall( + () => Reflect.apply(target, receiver, args), + { ...additional, arguments: args, self: receiver }, + { + instrumentationName: INSTRUMENTATION_NAMES.MISTRAL, + name: "mistral.classifiers.moderate", + type: SpanTypeAttribute.LLM, + extractInput: extractClassifierInputWithMetadata, + extractOutput: extractClassifierOutput, + extractMetadata: (result) => extractMistralResponseMetadata(result), + extractMetrics: (result) => + parseMistralMetricsFromUsage(result?.usage), + }, + ), ); - this.unsubscribers.push( - mistralChannels.classifiersModerateChat.intercept( - (target, receiver, args, additional) => - traceAsyncCall( - () => Reflect.apply(target, receiver, args), - { ...additional, arguments: args, self: receiver }, - { - instrumentationName: INSTRUMENTATION_NAMES.MISTRAL, - name: "mistral.classifiers.moderateChat", - type: SpanTypeAttribute.LLM, - extractInput: extractClassifierInputWithMetadata, - extractOutput: extractClassifierOutput, - extractMetadata: (result) => - extractMistralResponseMetadata(result), - extractMetrics: (result) => - parseMistralMetricsFromUsage(result?.usage), - }, - ), - ), + mistralChannels.classifiersModerateChat.intercept( + (target, receiver, args, additional) => + traceAsyncCall( + () => Reflect.apply(target, receiver, args), + { ...additional, arguments: args, self: receiver }, + { + instrumentationName: INSTRUMENTATION_NAMES.MISTRAL, + name: "mistral.classifiers.moderateChat", + type: SpanTypeAttribute.LLM, + extractInput: extractClassifierInputWithMetadata, + extractOutput: extractClassifierOutput, + extractMetadata: (result) => extractMistralResponseMetadata(result), + extractMetrics: (result) => + parseMistralMetricsFromUsage(result?.usage), + }, + ), ); - this.unsubscribers.push( - mistralChannels.classifiersClassify.intercept( - (target, receiver, args, additional) => - traceAsyncCall( - () => Reflect.apply(target, receiver, args), - { ...additional, arguments: args, self: receiver }, - { - instrumentationName: INSTRUMENTATION_NAMES.MISTRAL, - name: "mistral.classifiers.classify", - type: SpanTypeAttribute.LLM, - extractInput: extractClassifierInputWithMetadata, - extractOutput: extractClassifierOutput, - extractMetadata: (result) => - extractMistralResponseMetadata(result), - extractMetrics: (result) => - parseMistralMetricsFromUsage(result?.usage), - }, - ), - ), + mistralChannels.classifiersClassify.intercept( + (target, receiver, args, additional) => + traceAsyncCall( + () => Reflect.apply(target, receiver, args), + { ...additional, arguments: args, self: receiver }, + { + instrumentationName: INSTRUMENTATION_NAMES.MISTRAL, + name: "mistral.classifiers.classify", + type: SpanTypeAttribute.LLM, + extractInput: extractClassifierInputWithMetadata, + extractOutput: extractClassifierOutput, + extractMetadata: (result) => extractMistralResponseMetadata(result), + extractMetrics: (result) => + parseMistralMetricsFromUsage(result?.usage), + }, + ), ); - this.unsubscribers.push( - mistralChannels.classifiersClassifyChat.intercept( - (target, receiver, args, additional) => - traceAsyncCall( - () => Reflect.apply(target, receiver, args), - { ...additional, arguments: args, self: receiver }, - { - instrumentationName: INSTRUMENTATION_NAMES.MISTRAL, - name: "mistral.classifiers.classifyChat", - type: SpanTypeAttribute.LLM, - extractInput: extractClassifierInputWithMetadata, - extractOutput: extractClassifierOutput, - extractMetadata: (result) => - extractMistralResponseMetadata(result), - extractMetrics: (result) => - parseMistralMetricsFromUsage(result?.usage), - }, - ), - ), + mistralChannels.classifiersClassifyChat.intercept( + (target, receiver, args, additional) => + traceAsyncCall( + () => Reflect.apply(target, receiver, args), + { ...additional, arguments: args, self: receiver }, + { + instrumentationName: INSTRUMENTATION_NAMES.MISTRAL, + name: "mistral.classifiers.classifyChat", + type: SpanTypeAttribute.LLM, + extractInput: extractClassifierInputWithMetadata, + extractOutput: extractClassifierOutput, + extractMetadata: (result) => extractMistralResponseMetadata(result), + extractMetrics: (result) => + parseMistralMetricsFromUsage(result?.usage), + }, + ), ); - this.unsubscribers.push( - mistralChannels.fimComplete.intercept( - (target, receiver, args, additional) => - traceStreamingCall( - () => Reflect.apply(target, receiver, args), - { ...additional, arguments: args, self: receiver }, - { - instrumentationName: INSTRUMENTATION_NAMES.MISTRAL, - name: "mistral.fim.complete", - type: SpanTypeAttribute.LLM, - extractInput: extractPromptInputWithMetadata, - extractOutput: (result) => { - return result?.choices; - }, - extractMetadata: (result) => - extractMistralResponseMetadata(result), - extractMetrics: (result, startTime) => - extractMistralMetrics(result?.usage, startTime), + mistralChannels.fimComplete.intercept( + (target, receiver, args, additional) => + traceStreamingCall( + () => Reflect.apply(target, receiver, args), + { ...additional, arguments: args, self: receiver }, + { + instrumentationName: INSTRUMENTATION_NAMES.MISTRAL, + name: "mistral.fim.complete", + type: SpanTypeAttribute.LLM, + extractInput: extractPromptInputWithMetadata, + extractOutput: (result) => { + return result?.choices; }, - ), - ), + extractMetadata: (result) => extractMistralResponseMetadata(result), + extractMetrics: (result, startTime) => + extractMistralMetrics(result?.usage, startTime), + }, + ), ); - this.unsubscribers.push( - mistralChannels.fimStream.intercept( - (target, receiver, args, additional) => - traceStreamingCall( - () => Reflect.apply(target, receiver, args), - { ...additional, arguments: args, self: receiver }, - { - instrumentationName: INSTRUMENTATION_NAMES.MISTRAL, - name: "mistral.fim.stream", - type: SpanTypeAttribute.LLM, - extractInput: extractPromptInputWithMetadata, - extractOutput: extractMistralStreamOutput, - extractMetadata: (result) => - extractMistralResponseMetadata(result), - extractMetrics: (result, startTime) => - extractMistralStreamingMetrics(result, startTime), - aggregateChunks: aggregateMistralStreamChunks, - }, - ), + mistralChannels.fimStream.intercept((target, receiver, args, additional) => + traceStreamingCall( + () => Reflect.apply(target, receiver, args), + { ...additional, arguments: args, self: receiver }, + { + instrumentationName: INSTRUMENTATION_NAMES.MISTRAL, + name: "mistral.fim.stream", + type: SpanTypeAttribute.LLM, + extractInput: extractPromptInputWithMetadata, + extractOutput: extractMistralStreamOutput, + extractMetadata: (result) => extractMistralResponseMetadata(result), + extractMetrics: (result, startTime) => + extractMistralStreamingMetrics(result, startTime), + aggregateChunks: aggregateMistralStreamChunks, + }, ), ); - this.unsubscribers.push( - mistralChannels.agentsComplete.intercept( - (target, receiver, args, additional) => - traceStreamingCall( - () => Reflect.apply(target, receiver, args), - { ...additional, arguments: args, self: receiver }, - { - instrumentationName: INSTRUMENTATION_NAMES.MISTRAL, - name: "mistral.agents.complete", - type: SpanTypeAttribute.LLM, - extractInput: extractMessagesInputWithMetadata, - extractOutput: (result) => { - return result?.choices; - }, - extractMetadata: (result) => - extractMistralResponseMetadata(result), - extractMetrics: (result, startTime) => - extractMistralMetrics(result?.usage, startTime), + mistralChannels.agentsComplete.intercept( + (target, receiver, args, additional) => + traceStreamingCall( + () => Reflect.apply(target, receiver, args), + { ...additional, arguments: args, self: receiver }, + { + instrumentationName: INSTRUMENTATION_NAMES.MISTRAL, + name: "mistral.agents.complete", + type: SpanTypeAttribute.LLM, + extractInput: extractMessagesInputWithMetadata, + extractOutput: (result) => { + return result?.choices; }, - ), - ), + extractMetadata: (result) => extractMistralResponseMetadata(result), + extractMetrics: (result, startTime) => + extractMistralMetrics(result?.usage, startTime), + }, + ), ); - this.unsubscribers.push( - mistralChannels.agentsStream.intercept( - (target, receiver, args, additional) => - traceStreamingCall( - () => Reflect.apply(target, receiver, args), - { ...additional, arguments: args, self: receiver }, - { - instrumentationName: INSTRUMENTATION_NAMES.MISTRAL, - name: "mistral.agents.stream", - type: SpanTypeAttribute.LLM, - extractInput: extractMessagesInputWithMetadata, - extractOutput: extractMistralStreamOutput, - extractMetadata: (result) => - extractMistralResponseMetadata(result), - extractMetrics: (result, startTime) => - extractMistralStreamingMetrics(result, startTime), - aggregateChunks: aggregateMistralStreamChunks, - }, - ), - ), + mistralChannels.agentsStream.intercept( + (target, receiver, args, additional) => + traceStreamingCall( + () => Reflect.apply(target, receiver, args), + { ...additional, arguments: args, self: receiver }, + { + instrumentationName: INSTRUMENTATION_NAMES.MISTRAL, + name: "mistral.agents.stream", + type: SpanTypeAttribute.LLM, + extractInput: extractMessagesInputWithMetadata, + extractOutput: extractMistralStreamOutput, + extractMetadata: (result) => extractMistralResponseMetadata(result), + extractMetrics: (result, startTime) => + extractMistralStreamingMetrics(result, startTime), + aggregateChunks: aggregateMistralStreamChunks, + }, + ), ); } } @@ -1014,3 +970,10 @@ export function aggregateMistralStreamChunks( ...(metadata ? { metadata } : {}), }; } + +let mistralInstrumentationConsumer: MistralInstrumentationConsumer | undefined; + +export function registerMistralInstrumentation(): void { + mistralInstrumentationConsumer ??= new MistralInstrumentationConsumer(); + mistralInstrumentationConsumer.register(); +} diff --git a/js/src/instrumentation/plugins/mistral-interceptors.ts b/js/src/instrumentation/providers/mistral-interceptors.ts similarity index 100% rename from js/src/instrumentation/plugins/mistral-interceptors.ts rename to js/src/instrumentation/providers/mistral-interceptors.ts diff --git a/js/src/instrumentation/plugins/ollama-plugin.test.ts b/js/src/instrumentation/providers/ollama-instrumentation.test.ts similarity index 97% rename from js/src/instrumentation/plugins/ollama-plugin.test.ts rename to js/src/instrumentation/providers/ollama-instrumentation.test.ts index 0006efea8..209b070da 100644 --- a/js/src/instrumentation/plugins/ollama-plugin.test.ts +++ b/js/src/instrumentation/providers/ollama-instrumentation.test.ts @@ -1,5 +1,12 @@ -import { describe, expect, it, vi } from "vitest"; -import iso from "../../isomorph"; +import { + afterEach, + beforeEach, + describe, + expect, + it, + vi, +} from "vite-plus/test"; +import iso from "#braintrust-platform"; import { configureNode } from "../../node/config"; import { aggregateOllamaChatChunks, @@ -9,9 +16,11 @@ import { extractOllamaEmbedOutput, extractOllamaGenerateInput, extractOllamaMetrics, -} from "./ollama-plugin"; +} from "./ollama-instrumentation"; configureNode(); +beforeEach(() => vi.stubEnv("BRAINTRUST_AUTO_CAPTURE_ATTACHMENTS", "true")); +afterEach(() => vi.unstubAllEnvs()); describe("Ollama instrumentation extraction", () => { it("normalizes chat inputs, tools, and supported request metadata", () => { diff --git a/js/src/instrumentation/plugins/ollama-plugin.ts b/js/src/instrumentation/providers/ollama-instrumentation.ts similarity index 85% rename from js/src/instrumentation/plugins/ollama-plugin.ts rename to js/src/instrumentation/providers/ollama-instrumentation.ts index eef70c49e..6cb751654 100644 --- a/js/src/instrumentation/plugins/ollama-plugin.ts +++ b/js/src/instrumentation/providers/ollama-instrumentation.ts @@ -1,6 +1,10 @@ import { SpanTypeAttribute, isObject } from "../../../util/index"; -import iso from "../../isomorph"; -import { Attachment } from "../../logger"; +import iso from "#braintrust-platform"; +import { + _internalCaptureAttachmentsEnabled, + Attachment, +} from "../../logger/core"; +import { processInputAttachments } from "../../wrappers/attachment-utils"; import { INSTRUMENTATION_NAMES } from "../../span-origin"; import type { OllamaChatRequest, @@ -14,77 +18,63 @@ import type { OllamaToolCall, OllamaUsageResponse, } from "../../vendor-sdk-types/ollama"; -import { processInputAttachments } from "../../wrappers/attachment-utils"; -import { BasePlugin } from "../core"; -import { - traceAsyncCall, - traceStreamingCall, - unsubscribeAll, -} from "../core/tracing"; +import { traceAsyncCall, traceStreamingCall } from "../core/tracing"; import type { AsyncEndOf } from "../core/tracing-types"; import { ollamaChannels } from "./ollama-interceptors"; -export class OllamaPlugin extends BasePlugin { - protected onEnable(): void { - this.unsubscribers.push( - ollamaChannels.chat.intercept((target, receiver, args, additional) => - traceStreamingCall( - () => Reflect.apply(target, receiver, args), - { ...additional, arguments: args, self: receiver }, - { - instrumentationName: INSTRUMENTATION_NAMES.OLLAMA, - name: "ollama.chat", - type: SpanTypeAttribute.LLM, - extractInput: extractOllamaChatInput, - extractOutput: (result, event) => - extractOllamaChatOutput( - result, - countOllamaToolCalls(event?.arguments?.[0]?.messages), - ), - extractMetadata: extractOllamaResponseMetadata, - extractMetrics: extractOllamaMetrics, - aggregateChunks: aggregateOllamaChatChunks, - }, - ), - ), - ollamaChannels.generate.intercept((target, receiver, args, additional) => - traceStreamingCall( - () => Reflect.apply(target, receiver, args), - { ...additional, arguments: args, self: receiver }, - { - instrumentationName: INSTRUMENTATION_NAMES.OLLAMA, - name: "ollama.generate", - type: SpanTypeAttribute.LLM, - extractInput: extractOllamaGenerateInput, - extractOutput: extractOllamaGenerateOutput, - extractMetadata: extractOllamaResponseMetadata, - extractMetrics: extractOllamaMetrics, - aggregateChunks: aggregateOllamaGenerateChunks, - }, - ), - ), - ollamaChannels.embed.intercept((target, receiver, args, additional) => - traceAsyncCall( - () => Reflect.apply(target, receiver, args), - { ...additional, arguments: args, self: receiver }, - { - instrumentationName: INSTRUMENTATION_NAMES.OLLAMA, - name: "ollama.embed", - type: SpanTypeAttribute.LLM, - extractInput: extractOllamaEmbedInput, - extractOutput: extractOllamaEmbedOutput, - extractMetadata: extractOllamaResponseMetadata, - extractMetrics: extractOllamaMetrics, - }, - ), - ), - ); - } - - protected onDisable(): void { - this.unsubscribers = unsubscribeAll(this.unsubscribers); - } +export function registerOllamaInstrumentation(): void { + ollamaChannels.chat.intercept((target, receiver, args, additional) => + traceStreamingCall( + () => Reflect.apply(target, receiver, args), + { ...additional, arguments: args, self: receiver }, + { + instrumentationName: INSTRUMENTATION_NAMES.OLLAMA, + name: "ollama.chat", + type: SpanTypeAttribute.LLM, + extractInput: extractOllamaChatInput, + extractOutput: (result, event) => + extractOllamaChatOutput( + result, + countOllamaToolCalls(event?.arguments?.[0]?.messages), + ), + extractMetadata: extractOllamaResponseMetadata, + extractMetrics: extractOllamaMetrics, + aggregateChunks: aggregateOllamaChatChunks, + }, + ), + ); + ollamaChannels.generate.intercept((target, receiver, args, additional) => + traceStreamingCall( + () => Reflect.apply(target, receiver, args), + { ...additional, arguments: args, self: receiver }, + { + instrumentationName: INSTRUMENTATION_NAMES.OLLAMA, + name: "ollama.generate", + type: SpanTypeAttribute.LLM, + extractInput: extractOllamaGenerateInput, + extractOutput: extractOllamaGenerateOutput, + extractMetadata: extractOllamaResponseMetadata, + extractMetrics: extractOllamaMetrics, + aggregateChunks: aggregateOllamaGenerateChunks, + }, + ), + ); + ollamaChannels.embed.intercept((target, receiver, args, additional) => + traceAsyncCall( + () => Reflect.apply(target, receiver, args), + { ...additional, arguments: args, self: receiver }, + { + instrumentationName: INSTRUMENTATION_NAMES.OLLAMA, + name: "ollama.embed", + type: SpanTypeAttribute.LLM, + extractInput: extractOllamaEmbedInput, + extractOutput: extractOllamaEmbedOutput, + extractMetadata: extractOllamaResponseMetadata, + extractMetrics: extractOllamaMetrics, + }, + ), + ); } function isNonNegativeNumber(value: unknown): value is number { @@ -232,7 +222,17 @@ function normalizeTextAndImages( const imageParts: Record[] = []; const unrecognizedImages: unknown[] = []; + const captureAttachments = _internalCaptureAttachmentsEnabled(); for (const image of images) { + if (!captureAttachments) { + if ( + image instanceof URL || + (typeof image === "string" && /^https?:\/\//i.test(image)) + ) { + imageParts.push({ type: "image_url", image_url: { url: image } }); + } + continue; + } let localImagePath: string | undefined; let localPathMediaType: string | undefined; if ( diff --git a/js/src/instrumentation/plugins/ollama-interceptors.ts b/js/src/instrumentation/providers/ollama-interceptors.ts similarity index 100% rename from js/src/instrumentation/plugins/ollama-interceptors.ts rename to js/src/instrumentation/providers/ollama-interceptors.ts diff --git a/js/src/instrumentation/plugins/openai-agents-api-instrumentation.test.ts b/js/src/instrumentation/providers/openai-agents-api-instrumentation.test.ts similarity index 97% rename from js/src/instrumentation/plugins/openai-agents-api-instrumentation.test.ts rename to js/src/instrumentation/providers/openai-agents-api-instrumentation.test.ts index f7ba8940e..21ef99b21 100644 --- a/js/src/instrumentation/plugins/openai-agents-api-instrumentation.test.ts +++ b/js/src/instrumentation/providers/openai-agents-api-instrumentation.test.ts @@ -1,17 +1,19 @@ -import { afterEach, beforeAll, beforeEach, describe, expect, it } from "vitest"; import { - _exportsForTestingOnly, - initLogger, - startSpan, - withCurrent, -} from "../../logger"; + afterEach, + beforeAll, + beforeEach, + describe, + expect, + it, +} from "vite-plus/test"; +import { _exportsForTestingOnly } from "../../logger/testing"; +import { initLogger, startSpan, withCurrent } from "../../logger/core"; import { configureNode } from "../../node/config"; import { failOpenAIAgentsTrace, startOpenAIAgentsTrace, updateOpenAIAgentsTrace, } from "../../openai-agents-api"; -import { OpenAIPlugin } from "./openai-plugin"; try { configureNode(); @@ -23,7 +25,6 @@ describe("OpenAI Agents API instrumentation", () => { let backgroundLogger: ReturnType< typeof _exportsForTestingOnly.useTestBackgroundLogger >; - let plugin: OpenAIPlugin; beforeAll(async () => { await _exportsForTestingOnly.simulateLoginForTests(); @@ -35,12 +36,9 @@ describe("OpenAI Agents API instrumentation", () => { projectName: "openai-agents-api-instrumentation.test.ts", projectId: "test-project-id", }); - plugin = new OpenAIPlugin(); - plugin.enable(); }); afterEach(() => { - plugin.disable(); _exportsForTestingOnly.clearTestBackgroundLogger(); }); diff --git a/js/src/instrumentation/plugins/openai-agents-api-instrumentation.ts b/js/src/instrumentation/providers/openai-agents-api-instrumentation.ts similarity index 99% rename from js/src/instrumentation/plugins/openai-agents-api-instrumentation.ts rename to js/src/instrumentation/providers/openai-agents-api-instrumentation.ts index 963ad3658..765c989ae 100644 --- a/js/src/instrumentation/plugins/openai-agents-api-instrumentation.ts +++ b/js/src/instrumentation/providers/openai-agents-api-instrumentation.ts @@ -5,7 +5,7 @@ import { NOOP_SPAN, type Span, withCurrent, -} from "../../logger"; +} from "../../logger/core"; import type { OpenAIAgentsOpenTool, OpenAIAgentsTraceState, diff --git a/js/src/instrumentation/plugins/openai-agents-plugin.test.ts b/js/src/instrumentation/providers/openai-agents-instrumentation.test.ts similarity index 82% rename from js/src/instrumentation/plugins/openai-agents-plugin.test.ts rename to js/src/instrumentation/providers/openai-agents-instrumentation.test.ts index 8e8a37ed7..bc15e183d 100644 --- a/js/src/instrumentation/plugins/openai-agents-plugin.test.ts +++ b/js/src/instrumentation/providers/openai-agents-instrumentation.test.ts @@ -1,37 +1,44 @@ -import { afterEach, beforeAll, beforeEach, describe, expect, it } from "vitest"; -import { _exportsForTestingOnly, initLogger } from "../../logger"; +import { configureInstrumentation } from "../registry"; +import { + afterEach, + beforeAll, + beforeEach, + describe, + expect, + it, +} from "vite-plus/test"; +import { _exportsForTestingOnly } from "../../logger/testing"; +import { initLogger } from "../../logger/core"; import { configureNode } from "../../node/config"; import { openAIAgentsCoreChannels } from "./openai-agents-interceptors"; -import { OpenAIAgentsPlugin } from "./openai-agents-plugin"; +import { registerOpenAIAgentsInstrumentation } from "./openai-agents-instrumentation"; try { + configureInstrumentation({ integrations: { openAIAgents: false } }); configureNode(); } catch { // Best-effort initialization for test environments. } -describe("OpenAIAgentsPlugin", () => { +describe("registerOpenAIAgentsInstrumentation", () => { let backgroundLogger: ReturnType< typeof _exportsForTestingOnly.useTestBackgroundLogger >; - let plugin: OpenAIAgentsPlugin; beforeAll(async () => { await _exportsForTestingOnly.simulateLoginForTests(); + registerOpenAIAgentsInstrumentation(); }); beforeEach(() => { backgroundLogger = _exportsForTestingOnly.useTestBackgroundLogger(); initLogger({ - projectName: "openai-agents-plugin.test.ts", + projectName: "openai-agents-instrumentation.test.ts", projectId: "test-project-id", }); - plugin = new OpenAIAgentsPlugin(); - plugin.enable(); }); afterEach(() => { - plugin.disable(); _exportsForTestingOnly.clearTestBackgroundLogger(); }); diff --git a/js/src/instrumentation/providers/openai-agents-instrumentation.ts b/js/src/instrumentation/providers/openai-agents-instrumentation.ts new file mode 100644 index 000000000..0466b546c --- /dev/null +++ b/js/src/instrumentation/providers/openai-agents-instrumentation.ts @@ -0,0 +1,118 @@ +import { isObject } from "../../../util/index"; +import type { + OpenAIAgentsSpan, + OpenAIAgentsTrace, +} from "../../vendor-sdk-types/openai-agents"; + +import { runInstrumentation } from "../core/observe-result"; +import { openAIAgentsCoreChannels } from "./openai-agents-interceptors"; +import { OpenAIAgentsTraceProcessor } from "./openai-agents-trace-processor"; + +function firstArgument(args: unknown): unknown { + if (Array.isArray(args)) { + return args[0]; + } + if ( + isObject(args) && + "length" in args && + typeof args.length === "number" && + Number.isInteger(args.length) && + args.length >= 0 + ) { + return Array.from(args as unknown as ArrayLike)[0]; + } + return undefined; +} + +function isOpenAIAgentsTrace(value: unknown): value is OpenAIAgentsTrace { + return ( + isObject(value) && + value.type === "trace" && + typeof value.traceId === "string" + ); +} + +function isOpenAIAgentsSpan(value: unknown): value is OpenAIAgentsSpan { + return ( + isObject(value) && + value.type === "trace.span" && + typeof value.traceId === "string" && + typeof value.spanId === "string" + ); +} + +class OpenAIAgentsInstrumentationConsumer { + private processor = new OpenAIAgentsTraceProcessor(); + + public register(): void { + this.subscribeToTraceLifecycle(); + } + + private subscribeToTraceLifecycle(): void { + const traceStartChannel = openAIAgentsCoreChannels.onTraceStart; + + traceStartChannel.intercept((target, receiver, args) => { + runInstrumentation(() => + ((event: { arguments: unknown }) => { + const trace = firstArgument(event.arguments); + if (isOpenAIAgentsTrace(trace)) { + void this.processor.onTraceStart(trace); + } + })({ arguments: args }), + ); + return Reflect.apply(target, receiver, args); + }); + + const traceEndChannel = openAIAgentsCoreChannels.onTraceEnd; + + traceEndChannel.intercept((target, receiver, args) => { + runInstrumentation(() => + ((event: { arguments: unknown }) => { + const trace = firstArgument(event.arguments); + if (isOpenAIAgentsTrace(trace)) { + void this.processor.onTraceEnd(trace); + } + })({ arguments: args }), + ); + return Reflect.apply(target, receiver, args); + }); + + const spanStartChannel = openAIAgentsCoreChannels.onSpanStart; + + spanStartChannel.intercept((target, receiver, args) => { + runInstrumentation(() => + ((event: { arguments: unknown }) => { + const span = firstArgument(event.arguments); + if (isOpenAIAgentsSpan(span)) { + void this.processor.onSpanStart(span); + } + })({ arguments: args }), + ); + return Reflect.apply(target, receiver, args); + }); + + const spanEndChannel = openAIAgentsCoreChannels.onSpanEnd; + + spanEndChannel.intercept((target, receiver, args) => { + runInstrumentation(() => + ((event: { arguments: unknown }) => { + const span = firstArgument(event.arguments); + if (isOpenAIAgentsSpan(span)) { + void this.processor.onSpanEnd(span); + } + })({ arguments: args }), + ); + return Reflect.apply(target, receiver, args); + }); + } +} + +let openAIAgentsInstrumentationConsumer: + | OpenAIAgentsInstrumentationConsumer + | undefined; + +export function registerOpenAIAgentsInstrumentation(): void { + openAIAgentsInstrumentationConsumer ??= + new OpenAIAgentsInstrumentationConsumer(); + openAIAgentsInstrumentationConsumer.register(); +} diff --git a/js/src/instrumentation/plugins/openai-agents-interceptors.ts b/js/src/instrumentation/providers/openai-agents-interceptors.ts similarity index 100% rename from js/src/instrumentation/plugins/openai-agents-interceptors.ts rename to js/src/instrumentation/providers/openai-agents-interceptors.ts diff --git a/js/src/instrumentation/plugins/openai-agents-trace-processor.ts b/js/src/instrumentation/providers/openai-agents-trace-processor.ts similarity index 93% rename from js/src/instrumentation/plugins/openai-agents-trace-processor.ts rename to js/src/instrumentation/providers/openai-agents-trace-processor.ts index e3116ed58..57b1f14a7 100644 --- a/js/src/instrumentation/plugins/openai-agents-trace-processor.ts +++ b/js/src/instrumentation/providers/openai-agents-trace-processor.ts @@ -6,7 +6,10 @@ import { NOOP_SPAN, currentSpan, startSpan as startBaseSpan, -} from "../../logger"; + withCurrent, +} from "../../logger/core"; +import { processInputAttachments } from "../../wrappers/attachment-utils"; +import { processImagesInOutput } from "./openai-span-data"; import { INSTRUMENTATION_NAMES, withSpanInstrumentationName, @@ -44,7 +47,7 @@ type TraceMetadata = { }; export interface OpenAIAgentsTraceProcessorOptions { - logger?: Logger; + logger?: Logger; maxTraces?: number; } @@ -180,7 +183,7 @@ function parseUsageMetrics(usage: unknown): Record { export class OpenAIAgentsTraceProcessor { private static readonly DEFAULT_MAX_TRACES = 10000; - private logger?: Logger; + private logger?: Logger; private maxTraces: number; private traceSpans = new Map< string, @@ -192,7 +195,14 @@ export class OpenAIAgentsTraceProcessor { >(); private traceOrder: string[] = []; - public readonly _traceSpans = this.traceSpans; + public readonly _traceSpans: Map< + string, + { + rootSpan: BraintrustSpan; + childSpans: Map; + metadata: TraceMetadata; + } + > = this.traceSpans; constructor(options: OpenAIAgentsTraceProcessorOptions = {}) { this.logger = options.logger; @@ -325,7 +335,9 @@ export class OpenAIAgentsTraceProcessor { return Promise.resolve(); } - const logData = this.extractLogData(span); + const logData = withCurrent(braintrustSpan, () => + this.extractLogData(span), + ); braintrustSpan.log({ error: span.error, ...logData, @@ -415,8 +427,8 @@ export class OpenAIAgentsTraceProcessor { }; return { - input: spanData._input, - output, + input: processInputAttachments(spanData._input), + output: processInputAttachments(processImagesInOutput(output)), metadata: isObject(response) ? this.omitKeys(response, ["output", "usage"]) : {}, @@ -459,8 +471,8 @@ export class OpenAIAgentsTraceProcessor { span: OpenAIAgentsSpan, ): Record { return { - input: spanData.input, - output: spanData.output, + input: processInputAttachments(spanData.input), + output: processInputAttachments(processImagesInOutput(spanData.output)), metadata: { model: spanData.model, model_config: spanData.model_config, @@ -493,7 +505,10 @@ export class OpenAIAgentsTraceProcessor { spanData: OpenAIAgentsTranscriptionSpanData, ): Record { return { - input: spanData.input, + input: processInputAttachments({ + type: "input_audio", + input_audio: spanData.input, + })?.input_audio, output: spanData.output, metadata: { model: spanData.model, @@ -507,7 +522,10 @@ export class OpenAIAgentsTraceProcessor { ): Record { return { input: spanData.input, - output: spanData.output, + output: processInputAttachments({ + type: "input_audio", + input_audio: spanData.output, + })?.input_audio, metadata: { model: spanData.model, model_config: spanData.model_config, diff --git a/js/src/instrumentation/plugins/openai-batch-instrumentation.test.ts b/js/src/instrumentation/providers/openai-batch-instrumentation.test.ts similarity index 98% rename from js/src/instrumentation/plugins/openai-batch-instrumentation.test.ts rename to js/src/instrumentation/providers/openai-batch-instrumentation.test.ts index 72429cd76..7901dce41 100644 --- a/js/src/instrumentation/plugins/openai-batch-instrumentation.test.ts +++ b/js/src/instrumentation/providers/openai-batch-instrumentation.test.ts @@ -6,13 +6,9 @@ import { describe, expect, it, -} from "vitest"; -import { - _exportsForTestingOnly, - initLogger, - startSpan, - withCurrent, -} from "../../logger"; +} from "vite-plus/test"; +import { _exportsForTestingOnly } from "../../logger/testing"; +import { initLogger, startSpan, withCurrent } from "../../logger/core"; import { configureNode } from "../../node/config"; import { completeOpenAIBatchTrace, @@ -25,7 +21,7 @@ import type { OpenAIEnhancedResponse, } from "../../openai-batch-types"; import { SpanComponentsV4 } from "../../../util/span_identifier_v4"; -import { OpenAIPlugin } from "./openai-plugin"; +import { registerOpenAIInstrumentation } from "./openai-instrumentation"; try { configureNode(); @@ -101,10 +97,10 @@ describe("OpenAI Batch instrumentation", () => { let backgroundLogger: ReturnType< typeof _exportsForTestingOnly.useTestBackgroundLogger >; - let plugin: OpenAIPlugin; beforeAll(async () => { await _exportsForTestingOnly.simulateLoginForTests(); + registerOpenAIInstrumentation(); }); beforeEach(() => { @@ -113,12 +109,9 @@ describe("OpenAI Batch instrumentation", () => { projectName: "openai-batch-instrumentation.test.ts", projectId: "test-project-id", }); - plugin = new OpenAIPlugin(); - plugin.enable(); }); afterEach(() => { - plugin.disable(); _exportsForTestingOnly.clearTestBackgroundLogger(); }); diff --git a/js/src/instrumentation/plugins/openai-batch-instrumentation.ts b/js/src/instrumentation/providers/openai-batch-instrumentation.ts similarity index 99% rename from js/src/instrumentation/plugins/openai-batch-instrumentation.ts rename to js/src/instrumentation/providers/openai-batch-instrumentation.ts index 95767ead4..862cf2774 100644 --- a/js/src/instrumentation/plugins/openai-batch-instrumentation.ts +++ b/js/src/instrumentation/providers/openai-batch-instrumentation.ts @@ -6,7 +6,7 @@ import { NOOP_SPAN, type Span, withCurrent, -} from "../../logger"; +} from "../../logger/core"; import { parseMetricsFromUsage } from "../../openai-utils"; import { INSTRUMENTATION_NAMES, diff --git a/js/src/instrumentation/plugins/openai-codex-plugin.test.ts b/js/src/instrumentation/providers/openai-codex-instrumentation.test.ts similarity index 85% rename from js/src/instrumentation/plugins/openai-codex-plugin.test.ts rename to js/src/instrumentation/providers/openai-codex-instrumentation.test.ts index e25e99f62..f5957617a 100644 --- a/js/src/instrumentation/plugins/openai-codex-plugin.test.ts +++ b/js/src/instrumentation/providers/openai-codex-instrumentation.test.ts @@ -1,4 +1,11 @@ -import { afterEach, beforeEach, describe, expect, it, vi } from "vitest"; +import { + afterEach, + beforeEach, + describe, + expect, + it, + vi, +} from "vite-plus/test"; import { newGlobalInvocationHook } from "../../global-instrumentation-hooks"; import { invocationController } from "../test-utils/invocation"; vi.mock("../../global-instrumentation-hooks", async (importOriginal) => ({ @@ -12,21 +19,22 @@ const { mockStartSpan } = vi.hoisted(() => ({ mockStartSpan: vi.fn(), })); -vi.mock("../../isomorph", () => ({ +vi.mock("#braintrust-platform", () => ({ default: {}, })); -vi.mock("../../logger", () => ({ +vi.mock("../../logger/core", () => ({ startSpan: (...args: unknown[]) => mockStartSpan(...args), + _internalStartSpan: (...args: unknown[]) => mockStartSpan(...args), })); -import { OpenAICodexPlugin } from "./openai-codex-plugin"; +import { registerOpenAICodexInstrumentation } from "./openai-codex-instrumentation"; const mockNewInvocationHook = newGlobalInvocationHook as ReturnType< typeof vi.fn >; -describe("OpenAICodexPlugin", () => { +describe("registerOpenAICodexInstrumentation", () => { let handlersByName: Map; let spans: Array<{ end: ReturnType; @@ -64,8 +72,7 @@ describe("OpenAICodexPlugin", () => { }); it("uses explicit token totals by precedence instead of summing breakdowns", async () => { - const plugin = new OpenAICodexPlugin(); - plugin.enable(); + registerOpenAICodexInstrumentation(); const runHandlers = handlersByName.get( "orchestrion:@openai/codex-sdk:Thread.run", diff --git a/js/src/instrumentation/plugins/openai-codex-plugin.ts b/js/src/instrumentation/providers/openai-codex-instrumentation.ts similarity index 85% rename from js/src/instrumentation/plugins/openai-codex-plugin.ts rename to js/src/instrumentation/providers/openai-codex-instrumentation.ts index ab6ae8e76..cabb40e42 100644 --- a/js/src/instrumentation/plugins/openai-codex-plugin.ts +++ b/js/src/instrumentation/providers/openai-codex-instrumentation.ts @@ -1,10 +1,10 @@ -import { BasePlugin, toLoggedError } from "../core"; +import { toLoggedError } from "../core"; import { observeResult, runInstrumentation } from "../core/observe-result"; import { SpanTypeAttribute } from "../../../util/index"; import { debugLogger } from "../../debug-logger"; -import type { Span, StartSpanArgs } from "../../logger"; -import { startSpan as startBaseSpan } from "../../logger"; +import type { Span } from "../../logger/core"; +import { _internalStartSpan as startBaseSpan } from "../../logger/core"; import { INSTRUMENTATION_NAMES, withSpanInstrumentationName, @@ -28,6 +28,8 @@ import type { import type { ChannelMessage } from "../core/tracing-types"; import { openAICodexChannels } from "./openai-codex-interceptors"; +type InternalStartSpanArgs = NonNullable[0]>; + type CodexRunState = { activeLlmSpan?: CodexLlmSpanState; activeItemSpans: Map; @@ -56,139 +58,126 @@ const PATCHED_STREAMED_TURN = Symbol.for( "braintrust.openai-codex.patched-streamed-turn", ); -export class OpenAICodexPlugin extends BasePlugin { - protected onEnable(): void { +class OpenAICodexInstrumentationConsumer { + public register(): void { this.subscribeToRun(); this.subscribeToRunStreamed(); } - protected onDisable(): void { - for (const unsubscribe of this.unsubscribers) { - unsubscribe(); - } - this.unsubscribers = []; - } - private subscribeToRun(): void { const channel = openAICodexChannels.run; const states = new WeakMap(); - const removeHandlers = channel.intercept( - (target, receiver, args, additional) => { - const event = { - ...additional, - arguments: args, - self: receiver, - } as ChannelMessage; - const prepare = ( - event: ChannelMessage, - ) => { - states.set(event, startCodexRun(event, "Thread.run")); - }; - const resolved = async ( - event: ChannelMessage, - ) => { - const state = states.get(event); - if (!state) { - return; - } - states.delete(event); - await finalizeCompletedRun(state, event.result); - }; - const failed = async ( - event: ChannelMessage, - ) => { - const state = states.get(event); - if (!state) { - return; - } - states.delete(event); - await finalizeCodexRun(state, { error: event.error }); - }; - runInstrumentation(() => prepare(event)); - let result; - try { - result = Reflect.apply(target, receiver, args); - } catch (error) { - Object.assign(event, { error }); - runInstrumentation(() => failed(event)); - throw error; + channel.intercept((target, receiver, args, additional) => { + const event = { + ...additional, + arguments: args, + self: receiver, + } as ChannelMessage; + const prepare = ( + event: ChannelMessage, + ) => { + states.set(event, startCodexRun(event, "Thread.run")); + }; + const resolved = async ( + event: ChannelMessage, + ) => { + const state = states.get(event); + if (!state) { + return; } - return observeResult( - result, - (value) => { - Object.assign(event, { result: value }); - return resolved(event); - }, - (error) => { - Object.assign(event, { error }); - return failed(event); - }, - ); - }, - ); - this.unsubscribers.push(removeHandlers); + states.delete(event); + await finalizeCompletedRun(state, event.result); + }; + const failed = async ( + event: ChannelMessage, + ) => { + const state = states.get(event); + if (!state) { + return; + } + states.delete(event); + await finalizeCodexRun(state, { error: event.error }); + }; + runInstrumentation(() => prepare(event)); + let result; + try { + result = Reflect.apply(target, receiver, args); + } catch (error) { + Object.assign(event, { error }); + runInstrumentation(() => failed(event)); + throw error; + } + return observeResult( + result, + (value) => { + Object.assign(event, { result: value }); + return resolved(event); + }, + (error) => { + Object.assign(event, { error }); + return failed(event); + }, + ); + }); } private subscribeToRunStreamed(): void { const channel = openAICodexChannels.runStreamed; const states = new WeakMap(); - const removeHandlers = channel.intercept( - (target, receiver, args, additional) => { - const event = { - ...additional, - arguments: args, - self: receiver, - } as ChannelMessage; - const prepare = ( - event: ChannelMessage, - ) => { - states.set(event, startCodexRun(event, "Thread.runStreamed")); - }; - const resolved = async ( - event: ChannelMessage, - ) => { - const state = states.get(event); - if (!state) { - return; - } - states.delete(event); - await patchStreamedTurn(event.result, state); - }; - const failed = async ( - event: ChannelMessage, - ) => { - const state = states.get(event); - if (!state) { - return; - } - states.delete(event); - await finalizeCodexRun(state, { error: event.error }); - }; - runInstrumentation(() => prepare(event)); - let result; - try { - result = Reflect.apply(target, receiver, args); - } catch (error) { - Object.assign(event, { error }); - runInstrumentation(() => failed(event)); - throw error; + channel.intercept((target, receiver, args, additional) => { + const event = { + ...additional, + arguments: args, + self: receiver, + } as ChannelMessage; + const prepare = ( + event: ChannelMessage, + ) => { + states.set(event, startCodexRun(event, "Thread.runStreamed")); + }; + const resolved = async ( + event: ChannelMessage, + ) => { + const state = states.get(event); + if (!state) { + return; } - return observeResult( - result, - (value) => { - Object.assign(event, { result: value }); - return resolved(event); - }, - (error) => { - Object.assign(event, { error }); - return failed(event); - }, - ); - }, - ); - this.unsubscribers.push(removeHandlers); + states.delete(event); + await patchStreamedTurn(event.result, state); + }; + const failed = async ( + event: ChannelMessage, + ) => { + const state = states.get(event); + if (!state) { + return; + } + states.delete(event); + await finalizeCodexRun(state, { error: event.error }); + }; + runInstrumentation(() => prepare(event)); + let result; + try { + result = Reflect.apply(target, receiver, args); + } catch (error) { + Object.assign(event, { error }); + runInstrumentation(() => failed(event)); + throw error; + } + return observeResult( + result, + (value) => { + Object.assign(event, { result: value }); + return resolved(event); + }, + (error) => { + Object.assign(event, { error }); + return failed(event); + }, + ); + }); } } @@ -633,7 +622,7 @@ async function itemSpanArgs( item: OpenAICodexThreadItem, ): Promise< | { - start: StartSpanArgs; + start: InternalStartSpanArgs; end: Parameters[0]; } | undefined @@ -996,3 +985,13 @@ function safeLog(span: Span, event: Parameters[0]): void { function logInstrumentationError(context: string, error: unknown): void { debugLogger.error(`Error processing ${context}:`, error); } + +let openAICodexInstrumentationConsumer: + | OpenAICodexInstrumentationConsumer + | undefined; + +export function registerOpenAICodexInstrumentation(): void { + openAICodexInstrumentationConsumer ??= + new OpenAICodexInstrumentationConsumer(); + openAICodexInstrumentationConsumer.register(); +} diff --git a/js/src/instrumentation/plugins/openai-codex-interceptors.ts b/js/src/instrumentation/providers/openai-codex-interceptors.ts similarity index 100% rename from js/src/instrumentation/plugins/openai-codex-interceptors.ts rename to js/src/instrumentation/providers/openai-codex-interceptors.ts diff --git a/js/src/instrumentation/plugins/openai-decisions.test.ts b/js/src/instrumentation/providers/openai-decisions.test.ts similarity index 96% rename from js/src/instrumentation/plugins/openai-decisions.test.ts rename to js/src/instrumentation/providers/openai-decisions.test.ts index f47dee20c..b36ad7fd3 100644 --- a/js/src/instrumentation/plugins/openai-decisions.test.ts +++ b/js/src/instrumentation/providers/openai-decisions.test.ts @@ -1,17 +1,24 @@ /* eslint-disable @typescript-eslint/no-explicit-any */ /* eslint-disable @typescript-eslint/consistent-type-assertions */ -import { afterEach, beforeEach, describe, expect, test, vi } from "vitest"; +import { + afterEach, + beforeEach, + describe, + expect, + test, + vi, +} from "vite-plus/test"; import OpenAI from "openai"; +import { _exportsForTestingOnly } from "../../logger/testing"; import { - _exportsForTestingOnly, initLogger, Attachment, traced, type TestBackgroundLogger, -} from "../../logger"; +} from "../../logger/core"; import { configureNode } from "../../node/config"; import { wrapOpenAI } from "../../wrappers/oai"; -import { registry } from "../registry"; +import { registerInstrumentation } from "../registrations/openai"; import { openAIChannels } from "./openai-interceptors"; import type { OpenAIDecision, @@ -270,7 +277,7 @@ describe("OpenAI decisions", () => { }); test("omits inline images by default without modifying caller input", async () => { - vi.stubEnv("BRAINTRUST_CAPTURE_ATTACHMENTS", "false"); + vi.stubEnv("BRAINTRUST_AUTO_CAPTURE_ATTACHMENTS", "false"); const input: OpenAIDecisionCreateParams["input"] = [ { role: "user", @@ -295,7 +302,7 @@ describe("OpenAI decisions", () => { test.each(["wrapped", "channel"])( "captures mixed image inputs without changing the provider call (%s)", async (mode) => { - vi.stubEnv("BRAINTRUST_CAPTURE_ATTACHMENTS", "true"); + vi.stubEnv("BRAINTRUST_AUTO_CAPTURE_ATTACHMENTS", "true"); const image = "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVR42mP8z8DwHwAFBQIAX8jx0gAAAABJRU5ErkJggg=="; const request: OpenAIDecisionCreateParams = { @@ -466,19 +473,12 @@ describe("OpenAI decisions", () => { expect(span.metrics.end).toEqual(expect.any(Number)); }); - test("supports older clients and idempotent enable/disable", async () => { + test("supports older clients and idempotent registration", async () => { const older = { ...client(async () => response), decisions: undefined }; expect(wrapOpenAI(older).decisions).toBeUndefined(); const wrapped = wrapOpenAI(client(async () => response)); - registry.disable(); - registry.disable(); - try { - await wrapped.decisions.create(params); - expect(await logger.drain()).toHaveLength(0); - } finally { - registry.enable(); - registry.enable(); - } + registerInstrumentation(); + registerInstrumentation(); await wrapped.decisions.create(params); expect(await logger.drain()).toHaveLength(1); }); diff --git a/js/src/instrumentation/plugins/openai-decisions.ts b/js/src/instrumentation/providers/openai-decisions.ts similarity index 99% rename from js/src/instrumentation/plugins/openai-decisions.ts rename to js/src/instrumentation/providers/openai-decisions.ts index abf26df66..e308db10d 100644 --- a/js/src/instrumentation/plugins/openai-decisions.ts +++ b/js/src/instrumentation/providers/openai-decisions.ts @@ -1,5 +1,5 @@ import { debugLogger } from "../../debug-logger"; -import { startSpan, withCurrent, type Span } from "../../logger"; +import { startSpan, withCurrent, type Span } from "../../logger/core"; import { parseMetricsFromUsage } from "../../openai-utils"; import { INSTRUMENTATION_NAMES, diff --git a/js/src/instrumentation/plugins/openai-plugin.test.ts b/js/src/instrumentation/providers/openai-instrumentation.test.ts similarity index 96% rename from js/src/instrumentation/plugins/openai-plugin.test.ts rename to js/src/instrumentation/providers/openai-instrumentation.test.ts index 86d31d15e..4dbfa3dbe 100644 --- a/js/src/instrumentation/plugins/openai-plugin.test.ts +++ b/js/src/instrumentation/providers/openai-instrumentation.test.ts @@ -1,11 +1,11 @@ -import { describe, it, expect } from "vitest"; +import { describe, it, expect } from "vite-plus/test"; import { parseMetricsFromUsage, aggregateChatCompletionChunks, aggregateResponseStreamEvents, -} from "./openai-plugin"; +} from "./openai-instrumentation"; import { processImagesInOutput as processImagesInOutputWithFlag } from "./openai-span-data"; -import { Attachment } from "../../logger"; +import { Attachment } from "../../logger/core"; const processImagesInOutput = (output: unknown, captureAttachments = true) => processImagesInOutputWithFlag(output, captureAttachments); @@ -1424,9 +1424,21 @@ describe("processImagesInOutput", () => { }; expect(processImagesInOutput(output, false)).toEqual({ - ...output, - result: "", + type: "image_generation_call", + output_format: "png", + revised_prompt: "A red pixel", }); + expect( + processImagesInOutput( + [ + { type: "image_generation_call", result: "AQID" }, + { type: "message", content: [{ type: "output_text", text: "done" }] }, + ], + false, + ), + ).toEqual([ + { type: "message", content: [{ type: "output_text", text: "done" }] }, + ]); }); describe("image_generation_call conversion", () => { @@ -1442,7 +1454,7 @@ describe("processImagesInOutput", () => { revised_prompt: "A red pixel", }; - const result = processImagesInOutput(output); + const result = processImagesInOutput(output, true); expect(result.type).toBe("image_generation_call"); expect(result.output_format).toBe("png"); @@ -1464,7 +1476,7 @@ describe("processImagesInOutput", () => { output_format: "jpg", }; - const result = processImagesInOutput(output); + const result = processImagesInOutput(output, true); const attachment = result.result as Attachment; expect(attachment.reference.filename).toBe("generated_image.jpg"); @@ -1481,7 +1493,7 @@ describe("processImagesInOutput", () => { output_format: "webp", }; - const result = processImagesInOutput(output); + const result = processImagesInOutput(output, true); const attachment = result.result as Attachment; expect(attachment.reference.filename).toBe("generated_image.webp"); @@ -1497,7 +1509,7 @@ describe("processImagesInOutput", () => { result: base64Image, }; - const result = processImagesInOutput(output); + const result = processImagesInOutput(output, true); const attachment = result.result as Attachment; expect(attachment.reference.filename).toBe("generated_image.png"); @@ -1514,7 +1526,7 @@ describe("processImagesInOutput", () => { revised_prompt: "A beautiful sunset", }; - const result = processImagesInOutput(output); + const result = processImagesInOutput(output, true); const attachment = result.result as Attachment; expect(attachment.reference.filename).toContain("A_beautiful_sunset"); @@ -1533,7 +1545,7 @@ describe("processImagesInOutput", () => { revised_prompt: longPrompt, }; - const result = processImagesInOutput(output); + const result = processImagesInOutput(output, true); const attachment = result.result as Attachment; // Should be truncated to 50 chars + sanitized + extension @@ -1550,7 +1562,7 @@ describe("processImagesInOutput", () => { revised_prompt: "Hello@World#Test!", }; - const result = processImagesInOutput(output); + const result = processImagesInOutput(output, true); const attachment = result.result as Attachment; expect(attachment.reference.filename).toBe("Hello_World_Test_.png"); @@ -1566,7 +1578,7 @@ describe("processImagesInOutput", () => { output_format: "jpg", }; - const result = processImagesInOutput(output); + const result = processImagesInOutput(output, true); const attachment = result.result as Attachment; expect(attachment.reference.filename).toBe("generated_image.jpg"); @@ -1585,7 +1597,7 @@ describe("processImagesInOutput", () => { another_field: 123, }; - const result = processImagesInOutput(output); + const result = processImagesInOutput(output, true); expect(result.type).toBe("image_generation_call"); expect(result.output_format).toBe("png"); @@ -1609,7 +1621,7 @@ describe("processImagesInOutput", () => { { type: "text", content: "World" }, ]; - const result = processImagesInOutput(output); + const result = processImagesInOutput(output, true); expect(Array.isArray(result)).toBe(true); expect(result[0]).toEqual({ type: "text", content: "Hello" }); @@ -1630,7 +1642,7 @@ describe("processImagesInOutput", () => { ], ]; - const result = processImagesInOutput(output); + const result = processImagesInOutput(output, true); expect(Array.isArray(result)).toBe(true); expect(Array.isArray(result[0])).toBe(true); @@ -1654,7 +1666,7 @@ describe("processImagesInOutput", () => { }, ]; - const result = processImagesInOutput(output); + const result = processImagesInOutput(output, true); expect(result[0].result).toBeInstanceOf(Attachment); expect(result[1].result).toBeInstanceOf(Attachment); @@ -1676,7 +1688,7 @@ describe("processImagesInOutput", () => { content: "Hello world", }; - const result = processImagesInOutput(output); + const result = processImagesInOutput(output, true); expect(result).toEqual(output); }); @@ -1686,7 +1698,7 @@ describe("processImagesInOutput", () => { data: "some data", }; - const result = processImagesInOutput(output); + const result = processImagesInOutput(output, true); expect(result).toEqual(output); }); @@ -1696,7 +1708,7 @@ describe("processImagesInOutput", () => { content: "Generated text", }; - const result = processImagesInOutput(output); + const result = processImagesInOutput(output, true); expect(result).toEqual(output); }); @@ -1729,7 +1741,7 @@ describe("processImagesInOutput", () => { result: null, }; - const result = processImagesInOutput(output); + const result = processImagesInOutput(output, true); expect(result).toEqual(output); }); @@ -1739,7 +1751,7 @@ describe("processImagesInOutput", () => { result: undefined, }; - const result = processImagesInOutput(output); + const result = processImagesInOutput(output, true); expect(result).toEqual(output); }); }); @@ -1751,7 +1763,7 @@ describe("processImagesInOutput", () => { result: 12345, // Not a string }; - const result = processImagesInOutput(output); + const result = processImagesInOutput(output, true); expect(result).toEqual(output); expect(result.result).toBe(12345); }); @@ -1762,7 +1774,7 @@ describe("processImagesInOutput", () => { result: { data: "some data" }, }; - const result = processImagesInOutput(output); + const result = processImagesInOutput(output, true); expect(result).toEqual(output); }); @@ -1772,7 +1784,7 @@ describe("processImagesInOutput", () => { result: ["data"], }; - const result = processImagesInOutput(output); + const result = processImagesInOutput(output, true); expect(result).toEqual(output); }); @@ -1782,7 +1794,7 @@ describe("processImagesInOutput", () => { result: true, }; - const result = processImagesInOutput(output); + const result = processImagesInOutput(output, true); expect(result).toEqual(output); }); @@ -1796,7 +1808,7 @@ describe("processImagesInOutput", () => { revised_prompt: 12345, // Not a string }; - const result = processImagesInOutput(output); + const result = processImagesInOutput(output, true); const attachment = result.result as Attachment; expect(attachment.reference.filename).toBe("generated_image.png"); diff --git a/js/src/instrumentation/providers/openai-instrumentation.ts b/js/src/instrumentation/providers/openai-instrumentation.ts new file mode 100644 index 000000000..3bf9c3917 --- /dev/null +++ b/js/src/instrumentation/providers/openai-instrumentation.ts @@ -0,0 +1,866 @@ +import { _internalCaptureAttachmentsEnabled } from "../../logger/core"; +import { processInputAttachments } from "../../wrappers/attachment-utils"; +import { SpanTypeAttribute, isObject } from "../../../util/index"; +import { + BRAINTRUST_CACHED_STREAM_METRIC, + getCachedMetricFromHeaders, + parseMetricsFromUsage, +} from "../../openai-utils"; +import { INSTRUMENTATION_NAMES } from "../../span-origin"; +import { getCurrentUnixTimestamp } from "../../util"; +import type { + OpenAIChatChoice, + OpenAIChatCompletionChunk, + OpenAIChatLogprobs, + OpenAIResponseStreamEvent, +} from "../../vendor-sdk-types/openai"; + +import { + traceAsyncCall, + traceStreamingCall, + traceSyncStreamCall, +} from "../core/tracing"; +import { + interceptOpenAIAgentsTraceCapture, + interceptOpenAIAgentsTraceFail, + interceptOpenAIAgentsTraceStart, +} from "./openai-agents-api-instrumentation"; +import { + interceptOpenAIBatchTraceComplete, + interceptOpenAIBatchesRetrieveTraced, + interceptOpenAIFilesCreateTraced, +} from "./openai-batch-instrumentation"; +import { openAIChannels } from "./openai-interceptors"; +import { traceOpenAIDecisions } from "./openai-decisions"; +import { traceOpenAIMedia } from "./openai-media"; +import { + extractOpenAIChatInput, + extractOpenAIResponsesInput, + extractOpenAIResponsesMetadata, + processImagesInOutput, +} from "./openai-span-data"; + +/** + * Registers OpenAI SDK instrumentation. + * + * Handles instrumentation for: + * - Chat completions (streaming and non-streaming) + * - Embeddings + * - Moderations + * - Beta API (parse, stream) + * - Responses API (create, stream, parse, compact) + */ +export function registerOpenAIInstrumentation(): void { + openAIChannels.decisionsCreate.intercept( + (target, receiver, args, additional) => + traceOpenAIDecisions(() => Reflect.apply(target, receiver, args), { + arguments: args, + self: receiver, + additional, + }), + ); + openAIChannels.agentsTraceStart.intercept(interceptOpenAIAgentsTraceStart); + openAIChannels.agentsTraceCapture.intercept( + interceptOpenAIAgentsTraceCapture, + ); + openAIChannels.agentsTraceFail.intercept(interceptOpenAIAgentsTraceFail); + openAIChannels.filesCreateTraced.intercept(interceptOpenAIFilesCreateTraced); + openAIChannels.batchesRetrieveTraced.intercept( + interceptOpenAIBatchesRetrieveTraced, + ); + openAIChannels.batchesCompleteTrace.intercept( + interceptOpenAIBatchTraceComplete, + ); + + openAIChannels.imagesGenerate.intercept( + (target, receiver, args, additional) => + traceOpenAIMedia( + () => Reflect.apply(target, receiver, args), + { arguments: args, self: receiver, additional }, + openAIChannels.imagesGenerate.channelName, + "generate", + ), + ); + openAIChannels.imagesEdit.intercept((target, receiver, args, additional) => + traceOpenAIMedia( + () => Reflect.apply(target, receiver, args), + { arguments: args, self: receiver, additional }, + openAIChannels.imagesEdit.channelName, + "edit", + ), + ); + openAIChannels.imagesCreateVariation.intercept( + (target, receiver, args, additional) => + traceOpenAIMedia( + () => Reflect.apply(target, receiver, args), + { arguments: args, self: receiver, additional }, + openAIChannels.imagesCreateVariation.channelName, + "variation", + ), + ); + openAIChannels.audioSpeechCreate.intercept( + (target, receiver, args, additional) => + traceOpenAIMedia( + () => Reflect.apply(target, receiver, args), + { arguments: args, self: receiver, additional }, + openAIChannels.audioSpeechCreate.channelName, + "speech", + ), + ); + openAIChannels.audioTranscriptionsCreate.intercept( + (target, receiver, args, additional) => + traceOpenAIMedia( + () => Reflect.apply(target, receiver, args), + { arguments: args, self: receiver, additional }, + openAIChannels.audioTranscriptionsCreate.channelName, + "transcribe", + ), + ); + openAIChannels.audioTranslationsCreate.intercept( + (target, receiver, args, additional) => + traceOpenAIMedia( + () => Reflect.apply(target, receiver, args), + { arguments: args, self: receiver, additional }, + openAIChannels.audioTranslationsCreate.channelName, + "translate", + ), + ); + + // Chat Completions - supports streaming + openAIChannels.chatCompletionsCreate.intercept( + (target, receiver, args, additional) => + traceStreamingCall( + () => Reflect.apply(target, receiver, args), + { + ...additional, + arguments: args, + self: receiver, + get response() { + return additional.responseInfo?.response ?? additional.response; + }, + }, + { + instrumentationName: INSTRUMENTATION_NAMES.OPENAI, + name: "Chat Completion", + type: SpanTypeAttribute.LLM, + extractInput: ([params]) => extractOpenAIChatInput(params), + extractOutput: (result, event) => + processChatCompletionOutput( + result?.choices, + event?.arguments?.[0].audio?.format, + ), + extractMetrics: (result, startTime, endEvent) => { + const metrics = withCachedMetric( + parseMetricsFromUsage(result?.usage), + result, + endEvent, + ); + if (startTime) { + metrics.time_to_first_token = + getCurrentUnixTimestamp() - startTime; + } + return metrics; + }, + aggregateChunks: aggregateChatCompletionChunks, + }, + ), + ); + + // Embeddings + openAIChannels.embeddingsCreate.intercept( + (target, receiver, args, additional) => + traceAsyncCall( + () => Reflect.apply(target, receiver, args), + { + ...additional, + arguments: args, + self: receiver, + get response() { + return additional.responseInfo?.response ?? additional.response; + }, + }, + { + instrumentationName: INSTRUMENTATION_NAMES.OPENAI, + name: "Embedding", + type: SpanTypeAttribute.LLM, + extractInput: ([params]) => { + const { input, ...metadata } = params; + return { + input, + metadata: { ...metadata, provider: "openai" }, + }; + }, + extractOutput: (result) => { + const embedding = result?.data?.[0]?.embedding; + return Array.isArray(embedding) + ? { embedding_length: embedding.length } + : undefined; + }, + extractMetrics: (result, _startTime, endEvent) => { + return withCachedMetric( + parseMetricsFromUsage(result?.usage), + result, + endEvent, + ); + }, + }, + ), + ); + + // Beta Chat Completions Parse + openAIChannels.betaChatCompletionsParse.intercept( + (target, receiver, args, additional) => + traceStreamingCall( + () => Reflect.apply(target, receiver, args), + { + ...additional, + arguments: args, + self: receiver, + get response() { + return additional.responseInfo?.response ?? additional.response; + }, + }, + { + instrumentationName: INSTRUMENTATION_NAMES.OPENAI, + name: "Chat Completion", + type: SpanTypeAttribute.LLM, + extractInput: ([params]) => extractOpenAIChatInput(params), + extractOutput: (result, event) => + processChatCompletionOutput( + result?.choices, + event?.arguments?.[0].audio?.format, + ), + extractMetrics: (result, startTime, endEvent) => { + const metrics = withCachedMetric( + parseMetricsFromUsage(result?.usage), + result, + endEvent, + ); + if (startTime) { + metrics.time_to_first_token = + getCurrentUnixTimestamp() - startTime; + } + return metrics; + }, + aggregateChunks: aggregateChatCompletionChunks, + }, + ), + ); + + // Beta Chat Completions Stream (sync method returning event-based stream) + openAIChannels.betaChatCompletionsStream.intercept( + (target, receiver, args, additional) => + traceSyncStreamCall( + () => Reflect.apply(target, receiver, args), + { + ...additional, + arguments: args, + self: receiver, + get response() { + return additional.responseInfo?.response ?? additional.response; + }, + }, + { + instrumentationName: INSTRUMENTATION_NAMES.OPENAI, + name: "Chat Completion", + type: SpanTypeAttribute.LLM, + extractInput: ([params]) => extractOpenAIChatInput(params), + }, + ), + ); + + // Moderations + openAIChannels.moderationsCreate.intercept( + (target, receiver, args, additional) => + traceAsyncCall( + () => Reflect.apply(target, receiver, args), + { + ...additional, + arguments: args, + self: receiver, + get response() { + return additional.responseInfo?.response ?? additional.response; + }, + }, + { + instrumentationName: INSTRUMENTATION_NAMES.OPENAI, + name: "Moderation", + type: SpanTypeAttribute.LLM, + extractInput: ([params]) => { + const { input, ...metadata } = params; + return { + input, + metadata: { ...metadata, provider: "openai" }, + }; + }, + extractOutput: (result) => { + return result?.results; + }, + extractMetrics: (result, _startTime, endEvent) => { + return withCachedMetric( + parseMetricsFromUsage(result?.usage), + result, + endEvent, + ); + }, + }, + ), + ); + + // Responses API - create (supports streaming via stream=true param) + openAIChannels.responsesCreate.intercept( + (target, receiver, args, additional) => + traceStreamingCall( + () => Reflect.apply(target, receiver, args), + { + ...additional, + arguments: args, + self: receiver, + get response() { + return additional.responseInfo?.response ?? additional.response; + }, + }, + { + instrumentationName: INSTRUMENTATION_NAMES.OPENAI, + name: "openai.responses.create", + type: SpanTypeAttribute.LLM, + extractInput: ([params]) => extractOpenAIResponsesInput(params), + extractOutput: (result) => { + return processImagesInOutput(result?.output); + }, + extractMetadata: (result) => extractOpenAIResponsesMetadata(result), + extractMetrics: (result, startTime, endEvent) => { + const metrics = withCachedMetric( + parseMetricsFromUsage(result?.usage), + result, + endEvent, + ); + if (startTime) { + metrics.time_to_first_token = + getCurrentUnixTimestamp() - startTime; + } + return metrics; + }, + aggregateChunks: aggregateResponseStreamEvents, + }, + ), + ); + + // Responses API - stream (sync method returning event-based stream) + openAIChannels.responsesStream.intercept( + (target, receiver, args, additional) => + traceSyncStreamCall( + () => Reflect.apply(target, receiver, args), + { + ...additional, + arguments: args, + self: receiver, + get response() { + return additional.responseInfo?.response ?? additional.response; + }, + }, + { + instrumentationName: INSTRUMENTATION_NAMES.OPENAI, + name: "openai.responses.create", + type: SpanTypeAttribute.LLM, + extractInput: ([params]) => extractOpenAIResponsesInput(params), + extractFromEvent: (event) => + TERMINAL_RESPONSE_EVENT_TYPES.has(event.type) + ? aggregateResponseStreamEvents([event]) + : {}, + }, + ), + ); + + // Responses API - parse + openAIChannels.responsesParse.intercept( + (target, receiver, args, additional) => + traceStreamingCall( + () => Reflect.apply(target, receiver, args), + { + ...additional, + arguments: args, + self: receiver, + get response() { + return additional.responseInfo?.response ?? additional.response; + }, + }, + { + instrumentationName: INSTRUMENTATION_NAMES.OPENAI, + name: "openai.responses.parse", + type: SpanTypeAttribute.LLM, + extractInput: ([params]) => extractOpenAIResponsesInput(params), + extractOutput: (result) => { + return processImagesInOutput(result?.output); + }, + extractMetadata: (result) => extractOpenAIResponsesMetadata(result), + extractMetrics: (result, startTime, endEvent) => { + const metrics = withCachedMetric( + parseMetricsFromUsage(result?.usage), + result, + endEvent, + ); + if (startTime) { + metrics.time_to_first_token = + getCurrentUnixTimestamp() - startTime; + } + return metrics; + }, + aggregateChunks: aggregateResponseStreamEvents, + }, + ), + ); + + // Responses API - compact + openAIChannels.responsesCompact.intercept( + (target, receiver, args, additional) => + traceAsyncCall( + () => Reflect.apply(target, receiver, args), + { + ...additional, + arguments: args, + self: receiver, + get response() { + return additional.responseInfo?.response ?? additional.response; + }, + }, + { + instrumentationName: INSTRUMENTATION_NAMES.OPENAI, + name: "openai.responses.compact", + type: SpanTypeAttribute.LLM, + extractInput: ([params]) => extractOpenAIResponsesInput(params), + extractOutput: (result) => { + return processImagesInOutput(result?.output); + }, + extractMetadata: (result) => extractOpenAIResponsesMetadata(result), + extractMetrics: (result, startTime, endEvent) => { + const metrics = withCachedMetric( + parseMetricsFromUsage(result?.usage), + result, + endEvent, + ); + if (startTime) { + metrics.time_to_first_token = + getCurrentUnixTimestamp() - startTime; + } + return metrics; + }, + }, + ), + ); +} + +function processChatCompletionOutput( + choices: OpenAIChatChoice[] | undefined, + audioFormat: string | undefined, +): unknown { + const captureAttachments = _internalCaptureAttachmentsEnabled(); + return choices?.map((choice) => ({ + ...choice, + // Parsed outputs and tool arguments are application JSON, not media parts. + message: { + ...choice.message, + ...(Array.isArray(choice.message.content) + ? { + content: processInputAttachments( + choice.message.content, + captureAttachments, + ), + } + : {}), + ...(choice.message.audio + ? { + audio: processInputAttachments( + { role: "assistant", audio: choice.message.audio }, + captureAttachments, + audioFormat, + ).audio, + } + : {}), + }, + })); +} + +function getCachedMetricFromEndEvent(endEvent: unknown): number | undefined { + if (!isObject(endEvent)) { + return undefined; + } + + const response = (endEvent as Record).response; + if (!isObject(response)) { + return undefined; + } + + const headers = (response as { headers?: unknown }).headers; + if (!headers || typeof (headers as Headers).get !== "function") { + return undefined; + } + + return getCachedMetricFromHeaders(headers as Headers); +} + +function withCachedMetric( + metrics: Record, + result: unknown, + endEvent?: unknown, +): Record { + if (metrics.cached !== undefined) { + return metrics; + } + + const cachedFromEvent = getCachedMetricFromEndEvent(endEvent); + if (cachedFromEvent !== undefined) { + return { + ...metrics, + cached: cachedFromEvent, + }; + } + + if (!isObject(result)) { + return metrics; + } + + const cached = (result as Record)[ + BRAINTRUST_CACHED_STREAM_METRIC + ]; + + if (typeof cached !== "number") { + return metrics; + } + + return { + ...metrics, + cached, + }; +} + +function mergeLogprobTokens( + existing: OpenAIChatLogprobs["content"] | OpenAIChatLogprobs["refusal"], + incoming: OpenAIChatLogprobs["content"] | OpenAIChatLogprobs["refusal"], +): OpenAIChatLogprobs["content"] | OpenAIChatLogprobs["refusal"] { + if (incoming === undefined) { + return existing; + } + + if (incoming === null) { + return existing ?? null; + } + + if (Array.isArray(existing)) { + return [...existing, ...incoming]; + } + + return [...incoming]; +} + +function aggregateChatLogprobs( + existing: OpenAIChatLogprobs | null | undefined, + incoming: OpenAIChatLogprobs | null | undefined, +): OpenAIChatLogprobs | null | undefined { + if (incoming === undefined) { + return existing; + } + + if (incoming === null) { + return existing ?? null; + } + + const aggregated: OpenAIChatLogprobs = + existing && existing !== null + ? { ...existing, ...incoming } + : { ...incoming }; + + const content = mergeLogprobTokens(existing?.content, incoming.content); + if (content !== undefined) { + aggregated.content = content; + } + + const refusal = mergeLogprobTokens(existing?.refusal, incoming.refusal); + if (refusal !== undefined) { + aggregated.refusal = refusal; + } + + return aggregated; +} + +type AggregatedChatChoice = { + index: number; + role: string | undefined; + content: string | undefined; + reasoning_content: string | null | undefined; + refusal: string | undefined; + audio: NonNullable | undefined; + toolCallsByIndex: Map< + number, + NonNullable[number] + >; + logprobs: OpenAIChatLogprobs | null | undefined; + finish_reason: string | null | undefined; +}; + +function createAggregatedChatChoice(index: number): AggregatedChatChoice { + return { + index, + role: undefined, + content: undefined, + reasoning_content: undefined, + refusal: undefined, + audio: undefined, + toolCallsByIndex: new Map(), + logprobs: undefined, + finish_reason: undefined, + }; +} + +function toChatChoice(choice: AggregatedChatChoice): OpenAIChatChoice { + const toolCalls = Array.from(choice.toolCallsByIndex.entries()) + .sort(([left], [right]) => left - right) + .map(([, toolCall]) => toolCall); + + return { + index: choice.index, + message: { + role: choice.role, + content: choice.content, + ...(choice.reasoning_content !== undefined + ? { reasoning_content: choice.reasoning_content } + : {}), + ...(choice.refusal !== undefined ? { refusal: choice.refusal } : {}), + ...(choice.audio !== undefined ? { audio: choice.audio } : {}), + tool_calls: toolCalls.length > 0 ? toolCalls : undefined, + }, + logprobs: choice.logprobs ?? null, + finish_reason: choice.finish_reason, + }; +} + +// Some OpenAI-compatible providers omit `index` on streamed tool call deltas. +// Match existing ids before starting a new call; a delta without an id +// continues the latest one. +function resolveToolCallIndex( + toolCallsByIndex: AggregatedChatChoice["toolCallsByIndex"], + toolDelta: { index?: number | null; id?: string }, +): number { + if (typeof toolDelta.index === "number") { + return toolDelta.index; + } + if (toolDelta.id) { + for (const [index, toolCall] of toolCallsByIndex) { + if (toolCall.id === toolDelta.id) { + return index; + } + } + } + if (toolCallsByIndex.size === 0) { + return 0; + } + const lastIndex = Math.max(...toolCallsByIndex.keys()); + const lastId = toolCallsByIndex.get(lastIndex)?.id; + return toolDelta.id && lastId && lastId !== toolDelta.id + ? lastIndex + 1 + : lastIndex; +} + +/** + * Aggregate chat completion chunks into a single response. + * Combines role (first), content, reasoning_content and audio transcripts (concatenated), + * tool_calls (by index), finish_reason (last), and usage (last chunk). + */ +export function aggregateChatCompletionChunks( + chunks: OpenAIChatCompletionChunk[], + streamResult?: unknown, + endEvent?: unknown, +): { + output: OpenAIChatChoice[]; + metrics: Record; +} { + const choicesByIndex = new Map(); + let metrics: Record = {}; + + for (const chunk of chunks) { + if (chunk.usage) { + metrics = { + ...metrics, + ...parseMetricsFromUsage(chunk.usage), + }; + } + + const choices = chunk.choices; + if (!choices?.length) { + continue; + } + + for (const choice of choices) { + const choiceIndex = choice.index; + let aggregatedChoice = choicesByIndex.get(choiceIndex); + if (!aggregatedChoice) { + aggregatedChoice = createAggregatedChatChoice(choiceIndex); + choicesByIndex.set(choiceIndex, aggregatedChoice); + } + + if (choice.finish_reason) { + aggregatedChoice.finish_reason = choice.finish_reason; + } + + aggregatedChoice.logprobs = aggregateChatLogprobs( + aggregatedChoice.logprobs, + choice.logprobs, + ); + + const delta = choice.delta; + if (!delta) { + continue; + } + + if (delta.finish_reason) { + aggregatedChoice.finish_reason = delta.finish_reason; + } + + if (!aggregatedChoice.role && delta.role) { + aggregatedChoice.role = delta.role; + } + + if (delta.content) { + aggregatedChoice.content = + (aggregatedChoice.content || "") + delta.content; + } + + if (typeof delta.reasoning_content === "string") { + aggregatedChoice.reasoning_content = + (aggregatedChoice.reasoning_content ?? "") + delta.reasoning_content; + } else if (delta.reasoning_content === null) { + // Retain explicit nulls, but never discard text from earlier chunks. + aggregatedChoice.reasoning_content ??= null; + } + + if (delta.refusal) { + aggregatedChoice.refusal = + (aggregatedChoice.refusal || "") + delta.refusal; + } + + if (delta.audio) { + const { id, expires_at: expiresAt, transcript } = delta.audio; + if ( + id !== undefined || + expiresAt !== undefined || + transcript !== undefined + ) { + aggregatedChoice.audio ??= {}; + if (id !== undefined) { + aggregatedChoice.audio.id = id; + } + if (expiresAt !== undefined) { + aggregatedChoice.audio.expires_at = expiresAt; + } + if (transcript !== undefined) { + aggregatedChoice.audio.transcript = + (aggregatedChoice.audio.transcript || "") + transcript; + } + } + } + + if (delta.tool_calls) { + for (const toolDelta of delta.tool_calls) { + const toolIndex = resolveToolCallIndex( + aggregatedChoice.toolCallsByIndex, + toolDelta, + ); + let aggregatedToolCall = + aggregatedChoice.toolCallsByIndex.get(toolIndex); + if (!aggregatedToolCall) { + aggregatedToolCall = { + function: { arguments: "" }, + }; + aggregatedChoice.toolCallsByIndex.set( + toolIndex, + aggregatedToolCall, + ); + } + + if (toolDelta.id !== undefined) { + aggregatedToolCall.id = toolDelta.id; + } + if (toolDelta.type !== undefined) { + aggregatedToolCall.type = toolDelta.type; + } + if (toolDelta.function?.name !== undefined) { + aggregatedToolCall.function.name = toolDelta.function.name; + } + if (toolDelta.function?.arguments !== undefined) { + aggregatedToolCall.function.arguments += + toolDelta.function.arguments; + } + } + } + } + } + + metrics = withCachedMetric(metrics, streamResult, endEvent); + const output = Array.from(choicesByIndex.values()) + .sort((left, right) => left.index - right.index) + .map(toChatChoice); + + return { + metrics, + output: + output.length > 0 + ? output + : [toChatChoice(createAggregatedChatChoice(0))], + }; +} + +// A Responses API stream ends with one of these events. Streams that hit +// `max_output_tokens` or a content filter end with `response.incomplete`, and +// streams that error server-side end with `response.failed`. All of them carry +// the final `response` object with output and usage. +const TERMINAL_RESPONSE_EVENT_TYPES = new Set([ + "response.completed", + "response.incomplete", + "response.failed", +]); + +export function aggregateResponseStreamEvents( + chunks: OpenAIResponseStreamEvent[], + _streamResult?: unknown, + endEvent?: unknown, +): { + output: any; + metrics: Record; + metadata?: Record; + error?: string; +} { + let output: any = undefined; + let metrics: Record = {}; + let metadata: Record | undefined = undefined; + let error: string | undefined = undefined; + + for (const chunk of chunks) { + if (!chunk?.response || !TERMINAL_RESPONSE_EVENT_TYPES.has(chunk.type)) { + continue; + } + + const response = chunk.response; + if (response.output !== undefined) { + output = processImagesInOutput(response.output); + } + + const { usage: _usage, output: _output, ...rest } = response; + if (Object.keys(rest).length > 0) { + metadata = rest; + } + + metrics = parseMetricsFromUsage(response.usage); + if (chunk.type === "response.failed" && response.error) { + error = `${response.error.code}: ${response.error.message}`; + } + } + + return { + output, + metrics: withCachedMetric(metrics, undefined, endEvent), + ...(metadata !== undefined ? { metadata } : {}), + ...(error !== undefined ? { error } : {}), + }; +} + +export { parseMetricsFromUsage }; diff --git a/js/src/instrumentation/plugins/openai-interceptors.ts b/js/src/instrumentation/providers/openai-interceptors.ts similarity index 98% rename from js/src/instrumentation/plugins/openai-interceptors.ts rename to js/src/instrumentation/providers/openai-interceptors.ts index 14c21bdd1..7773524e2 100644 --- a/js/src/instrumentation/plugins/openai-interceptors.ts +++ b/js/src/instrumentation/providers/openai-interceptors.ts @@ -2,7 +2,7 @@ import type { OpenAIDecision, OpenAIDecisionCreateParams, } from "../../vendor-sdk-types/openai-decisions"; -import type { CompiledPrompt } from "../../logger"; +import type { CompiledPrompt } from "../../logger/prompts"; import type { OpenAIMediaParams, OpenAIMediaResponse, diff --git a/js/src/instrumentation/plugins/openai-manual-instrumentation-utils.ts b/js/src/instrumentation/providers/openai-manual-instrumentation-utils.ts similarity index 100% rename from js/src/instrumentation/plugins/openai-manual-instrumentation-utils.ts rename to js/src/instrumentation/providers/openai-manual-instrumentation-utils.ts diff --git a/js/src/instrumentation/plugins/openai-media.test.ts b/js/src/instrumentation/providers/openai-media.test.ts similarity index 93% rename from js/src/instrumentation/plugins/openai-media.test.ts rename to js/src/instrumentation/providers/openai-media.test.ts index 42e7cc5f1..119215ea3 100644 --- a/js/src/instrumentation/plugins/openai-media.test.ts +++ b/js/src/instrumentation/providers/openai-media.test.ts @@ -1,11 +1,14 @@ +import { registerInstrumentation } from "../registrations/openai"; import { Readable } from "node:stream"; -import { afterEach, beforeAll, beforeEach, expect, it } from "vitest"; -import { Attachment, _exportsForTestingOnly, initLogger } from "../../logger"; +import { afterEach, beforeAll, beforeEach, expect, it } from "vite-plus/test"; +import { Attachment, initLogger } from "../../logger/core"; +import { _exportsForTestingOnly } from "../../logger/testing"; import { configureNode } from "../../node/config"; import { openAIChannels } from "./openai-interceptors"; configureNode(); +registerInstrumentation(); const originalAutoCaptureAttachments = - process.env.BRAINTRUST_CAPTURE_ATTACHMENTS; + process.env.BRAINTRUST_AUTO_CAPTURE_ATTACHMENTS; let background: ReturnType< typeof _exportsForTestingOnly.useTestBackgroundLogger >; @@ -13,7 +16,7 @@ beforeAll(async () => { await _exportsForTestingOnly.simulateLoginForTests(); }); beforeEach(() => { - process.env.BRAINTRUST_CAPTURE_ATTACHMENTS = "true"; + process.env.BRAINTRUST_AUTO_CAPTURE_ATTACHMENTS = "true"; background = _exportsForTestingOnly.useTestBackgroundLogger(); initLogger({ projectName: "tmp-luca-openai-media-tests", @@ -22,9 +25,10 @@ beforeEach(() => { }); afterEach(() => { if (originalAutoCaptureAttachments === undefined) { - delete process.env.BRAINTRUST_CAPTURE_ATTACHMENTS; + delete process.env.BRAINTRUST_AUTO_CAPTURE_ATTACHMENTS; } else { - process.env.BRAINTRUST_CAPTURE_ATTACHMENTS = originalAutoCaptureAttachments; + process.env.BRAINTRUST_AUTO_CAPTURE_ATTACHMENTS = + originalAutoCaptureAttachments; } _exportsForTestingOnly.clearTestBackgroundLogger(); }); @@ -127,7 +131,7 @@ it("does not upload incomplete speech and preserves response identity", async () }); it("does not capture speech response bytes without attachment opt-in", async () => { - delete process.env.BRAINTRUST_CAPTURE_ATTACHMENTS; + delete process.env.BRAINTRUST_AUTO_CAPTURE_ATTACHMENTS; const response = new Response(new Uint8Array([1, 2, 3]), { headers: { "content-type": "audio/mpeg" }, }); @@ -150,7 +154,7 @@ it("does not capture speech response bytes without attachment opt-in", async () }); it("does not read media upload values without attachment opt-in", async () => { - delete process.env.BRAINTRUST_CAPTURE_ATTACHMENTS; + delete process.env.BRAINTRUST_AUTO_CAPTURE_ATTACHMENTS; let reads = 0; const image = { name: "input.png", diff --git a/js/src/instrumentation/plugins/openai-media.ts b/js/src/instrumentation/providers/openai-media.ts similarity index 97% rename from js/src/instrumentation/plugins/openai-media.ts rename to js/src/instrumentation/providers/openai-media.ts index ec81a4aca..7a658d816 100644 --- a/js/src/instrumentation/plugins/openai-media.ts +++ b/js/src/instrumentation/providers/openai-media.ts @@ -2,7 +2,13 @@ import type { ArgsOf, InvocationAdditionalOf, } from "../core/interceptor-definitions"; -import { Attachment, startSpan, withCurrent, type Span } from "../../logger"; +import { + _internalCaptureAttachmentsEnabled, + Attachment, + startSpan, + withCurrent, + type Span, +} from "../../logger/core"; import { debugLogger } from "../../debug-logger"; import { getCurrentUnixTimestamp } from "../../util"; import { @@ -12,7 +18,6 @@ import { import { convertDataToBlob, getExtensionFromMediaType, - isAutoCaptureAttachmentsEnabled, } from "../../wrappers/attachment-utils"; import { isObject } from "../../../util/index"; import { isAsyncIterable, patchStreamIfNeeded } from "../core/stream-patcher"; @@ -519,15 +524,7 @@ function observeSpeech( else { observeBytes(result.value); const value = result.value; - chunks.push( - new Blob([ - new Uint8Array( - value.buffer, - value.byteOffset, - value.byteLength, - ), - ]), - ); + chunks.push(new Blob([Uint8Array.from(value)])); } } catch (error) { debugLogger.debug("OpenAI speech capture failed", error); @@ -567,7 +564,7 @@ function observeSpeech( transform(chunk, controller) { try { observeBytes(chunk); - chunks.push(new Blob([chunk])); + chunks.push(new Blob([Uint8Array.from(chunk)])); } catch (error) { debugLogger.debug("OpenAI speech capture failed", error); } @@ -613,7 +610,7 @@ function observeSpeech( transform(chunk, controller) { try { observeBytes(chunk); - chunks.push(new Blob([chunk])); + chunks.push(new Blob([Uint8Array.from(chunk)])); } catch (error) { debugLogger.debug("OpenAI speech capture failed", error); } @@ -634,7 +631,7 @@ function observeSpeech( patchStreamIfNeeded(body, { onChunk: (chunk) => { observeBytes(chunk); - chunks.push(new Blob([chunk])); + chunks.push(new Blob([Uint8Array.from(chunk)])); }, onComplete: () => finish(new Blob(chunks, { type: contentType })), onCancel: () => { @@ -669,7 +666,7 @@ export function traceOpenAIMedia( const additional = context.additional; if (isAutoInstrumentationSuppressed()) return call(); const params = args[0]; - const captureAttachments = isAutoCaptureAttachmentsEnabled(); + const captureAttachments = _internalCaptureAttachmentsEnabled(); let span: Span; try { const { name, spanAttributes, spanInfoMetadata } = buildStartSpanArgs( diff --git a/js/src/instrumentation/plugins/openai-span-data.ts b/js/src/instrumentation/providers/openai-span-data.ts similarity index 85% rename from js/src/instrumentation/plugins/openai-span-data.ts rename to js/src/instrumentation/providers/openai-span-data.ts index a1414ca84..77a70aee8 100644 --- a/js/src/instrumentation/plugins/openai-span-data.ts +++ b/js/src/instrumentation/providers/openai-span-data.ts @@ -1,7 +1,10 @@ /* eslint-disable @typescript-eslint/no-explicit-any */ -import { Attachment } from "../../logger"; import { - isAutoCaptureAttachmentsEnabled, + _internalCaptureAttachmentsEnabled, + Attachment, +} from "../../logger/core"; +import { + omitMediaData, processInputAttachments, } from "../../wrappers/attachment-utils"; import { isObject } from "../../../util/index"; @@ -58,7 +61,7 @@ export function extractOpenAIBatchInput( const input = endpoint === "/v1/chat/completions" ? params.messages : params.input; return { - input: processInputAttachments(input, isAutoCaptureAttachmentsEnabled()), + input: processInputAttachments(input, _internalCaptureAttachmentsEnabled()), metadata: batchMetadata(params), }; } @@ -69,7 +72,10 @@ export function extractOpenAIChatInput(params: Record): { } { const { messages, ...metadata } = params; return { - input: processInputAttachments(messages, isAutoCaptureAttachmentsEnabled()), + input: processInputAttachments( + messages, + _internalCaptureAttachmentsEnabled(), + ), metadata: { ...metadata, provider: "openai" }, }; } @@ -80,7 +86,7 @@ export function extractOpenAIResponsesInput(params: Record): { } { const { input, ...metadata } = params; return { - input: processInputAttachments(input, isAutoCaptureAttachmentsEnabled()), + input: processInputAttachments(input, _internalCaptureAttachmentsEnabled()), metadata: { ...metadata, provider: "openai" }, }; } @@ -98,12 +104,12 @@ export function extractOpenAIResponsesMetadata( /** Convert Responses API base64 image outputs to Braintrust attachments. */ export function processImagesInOutput( output: any, - captureAttachments = isAutoCaptureAttachmentsEnabled(), + captureAttachments: boolean = _internalCaptureAttachmentsEnabled(), ): any { if (Array.isArray(output)) { - return output.map((item) => - processImagesInOutput(item, captureAttachments), - ); + return output + .map((item) => processImagesInOutput(item, captureAttachments)) + .filter((item) => item !== undefined); } if ( @@ -113,7 +119,7 @@ export function processImagesInOutput( output.result ) { if (!captureAttachments) { - return { ...output, result: "" }; + return omitMediaData(output, "result"); } const fileExtension = output.output_format || "png"; const contentType = `image/${fileExtension}`; diff --git a/js/src/instrumentation/plugins/openrouter-agent-plugin.test.ts b/js/src/instrumentation/providers/openrouter-agent-instrumentation.test.ts similarity index 97% rename from js/src/instrumentation/plugins/openrouter-agent-plugin.test.ts rename to js/src/instrumentation/providers/openrouter-agent-instrumentation.test.ts index 2502d3683..4e342a770 100644 --- a/js/src/instrumentation/plugins/openrouter-agent-plugin.test.ts +++ b/js/src/instrumentation/providers/openrouter-agent-instrumentation.test.ts @@ -1,3 +1,4 @@ +import { registerInstrumentation } from "../registrations/openrouterAgent"; import { afterEach, beforeAll, @@ -6,26 +7,28 @@ import { expect, it, vi, -} from "vitest"; -import { _exportsForTestingOnly, initLogger } from "../../logger"; +} from "vite-plus/test"; +import { _exportsForTestingOnly } from "../../logger/testing"; +import { initLogger } from "../../logger/core"; import { configureNode } from "../../node/config"; import { openRouterAgentChannels } from "./openrouter-agent-interceptors"; import { aggregateOpenRouterChatChunks, aggregateOpenRouterResponseStreamEvents, parseOpenRouterMetricsFromUsage, -} from "./openrouter-agent-plugin"; +} from "./openrouter-agent-instrumentation"; const TEST_PROVIDER = "openai"; const TEST_MODEL = "gpt-4.1-mini"; try { configureNode(); + registerInstrumentation(); } catch { // Best-effort initialization for test environments. } -describe("OpenRouter Agent Plugin", () => { +describe("OpenRouter Agent instrumentation", () => { let backgroundLogger: ReturnType< typeof _exportsForTestingOnly.useTestBackgroundLogger >; @@ -37,7 +40,7 @@ describe("OpenRouter Agent Plugin", () => { beforeEach(() => { backgroundLogger = _exportsForTestingOnly.useTestBackgroundLogger(); initLogger({ - projectName: "openrouter-agent-plugin.test.ts", + projectName: "openrouter-agent-instrumentation.test.ts", projectId: "test-project-id", }); }); diff --git a/js/src/instrumentation/plugins/openrouter-agent-plugin.ts b/js/src/instrumentation/providers/openrouter-agent-instrumentation.ts similarity index 86% rename from js/src/instrumentation/plugins/openrouter-agent-plugin.ts rename to js/src/instrumentation/providers/openrouter-agent-instrumentation.ts index 534f2f33b..54bf54c79 100644 --- a/js/src/instrumentation/plugins/openrouter-agent-plugin.ts +++ b/js/src/instrumentation/providers/openrouter-agent-instrumentation.ts @@ -1,16 +1,15 @@ import { INSTRUMENTATION_NAMES } from "../../span-origin"; -import { BasePlugin, toLoggedError } from "../core"; +import { toLoggedError } from "../core"; import { traceAsyncCall, traceStreamingCall, traceSyncStreamCall, - unsubscribeAll, } from "../core/tracing"; import { runInstrumentation } from "../core/observe-result"; import { SpanTypeAttribute, isObject } from "../../../util/index"; -import type { Span } from "../../logger"; -import { withCurrent } from "../../logger"; +import type { Span } from "../../logger/core"; +import { withCurrent } from "../../logger/core"; import type { OpenRouterAgentCallModelRequest, OpenRouterAgentChatChoice, @@ -25,148 +24,132 @@ import { zodToJsonSchema } from "../../zod/utils"; import type { ChannelMessage } from "../core/tracing-types"; import { openRouterAgentChannels } from "./openrouter-agent-interceptors"; -export class OpenRouterAgentPlugin extends BasePlugin { - protected onEnable(): void { +class OpenRouterAgentInstrumentationConsumer { + public register(): void { this.subscribeToOpenRouterAgentChannels(); } - - protected onDisable(): void { - this.unsubscribers = unsubscribeAll(this.unsubscribers); - } - private subscribeToOpenRouterAgentChannels(): void { - this.unsubscribers.push( - openRouterAgentChannels.callModel.intercept( - (target, receiver, args, additional) => - traceSyncStreamCall( - () => Reflect.apply(target, receiver, args), - { ...additional, arguments: args, self: receiver }, - { - instrumentationName: INSTRUMENTATION_NAMES.OPENROUTER_AGENT, - name: "openrouter.callModel", - type: SpanTypeAttribute.TASK, - extractInput: (args) => { - const request = getOpenRouterCallModelRequestArg(args); - return { - input: request - ? extractOpenRouterCallModelInput(request) - : undefined, - metadata: request - ? extractOpenRouterCallModelMetadata(request) - : { provider: "openrouter" }, - }; - }, - patchResult: ({ endEvent, result, span }) => { - return patchOpenRouterCallModelResult({ - request: getOpenRouterCallModelRequestArg(endEvent.arguments), - result, - span, - }); - }, + openRouterAgentChannels.callModel.intercept( + (target, receiver, args, additional) => + traceSyncStreamCall( + () => Reflect.apply(target, receiver, args), + { ...additional, arguments: args, self: receiver }, + { + instrumentationName: INSTRUMENTATION_NAMES.OPENROUTER_AGENT, + name: "openrouter.callModel", + type: SpanTypeAttribute.TASK, + extractInput: (args) => { + const request = getOpenRouterCallModelRequestArg(args); + return { + input: request + ? extractOpenRouterCallModelInput(request) + : undefined, + metadata: request + ? extractOpenRouterCallModelMetadata(request) + : { provider: "openrouter" }, + }; + }, + patchResult: ({ endEvent, result, span }) => { + return patchOpenRouterCallModelResult({ + request: getOpenRouterCallModelRequestArg(endEvent.arguments), + result, + span, + }); }, - ), - ), + }, + ), ); - this.unsubscribers.push( - openRouterAgentChannels.callModelTurn.intercept( - (target, receiver, args, additional) => - traceAsyncCall( - () => Reflect.apply(target, receiver, args), - { ...additional, arguments: args, self: receiver }, - { - instrumentationName: INSTRUMENTATION_NAMES.OPENROUTER_AGENT, - name: "openrouter.beta.responses.send", - type: SpanTypeAttribute.LLM, - extractInput: (args, event) => { - const request = getOpenRouterCallModelRequestArg(args); - const metadata = request - ? extractOpenRouterCallModelMetadata(request) - : { provider: "openrouter" }; - - if (isObject(metadata) && "tools" in metadata) { - delete (metadata as Record).tools; - } + openRouterAgentChannels.callModelTurn.intercept( + (target, receiver, args, additional) => + traceAsyncCall( + () => Reflect.apply(target, receiver, args), + { ...additional, arguments: args, self: receiver }, + { + instrumentationName: INSTRUMENTATION_NAMES.OPENROUTER_AGENT, + name: "openrouter.beta.responses.send", + type: SpanTypeAttribute.LLM, + extractInput: (args, event) => { + const request = getOpenRouterCallModelRequestArg(args); + const metadata = request + ? extractOpenRouterCallModelMetadata(request) + : { provider: "openrouter" }; + + if (isObject(metadata) && "tools" in metadata) { + delete (metadata as Record).tools; + } + return { + input: request + ? extractOpenRouterCallModelInput(request) + : undefined, + metadata: { + ...metadata, + step: event.step, + step_type: event.stepType, + }, + }; + }, + extractOutput: (result) => + extractOpenRouterResponseOutput( + result as Record, + ), + extractMetadata: (result, event) => { + if (!isObject(result)) { return { - input: request - ? extractOpenRouterCallModelInput(request) - : undefined, - metadata: { - ...metadata, - step: event.step, - step_type: event.stepType, - }, + step: event?.step, + step_type: event?.stepType, }; - }, - extractOutput: (result) => - extractOpenRouterResponseOutput( - result as Record, - ), - extractMetadata: (result, event) => { - if (!isObject(result)) { - return { - step: event?.step, - step_type: event?.stepType, - }; - } + } - return { - ...(extractOpenRouterResponseMetadata(result) || {}), - ...(event?.step !== undefined ? { step: event.step } : {}), - ...(event?.stepType ? { step_type: event.stepType } : {}), - }; - }, - extractMetrics: (result) => - isObject(result) - ? parseOpenRouterMetricsFromUsage(result.usage) - : {}, + return { + ...(extractOpenRouterResponseMetadata(result) || {}), + ...(event?.step !== undefined ? { step: event.step } : {}), + ...(event?.stepType ? { step_type: event.stepType } : {}), + }; }, - ), - ), + extractMetrics: (result) => + isObject(result) + ? parseOpenRouterMetricsFromUsage(result.usage) + : {}, + }, + ), ); - this.unsubscribers.push( - openRouterAgentChannels.toolExecute.intercept( - (target, receiver, args, additional) => - traceStreamingCall( - () => Reflect.apply(target, receiver, args), - { ...additional, arguments: args, self: receiver }, - { - instrumentationName: INSTRUMENTATION_NAMES.OPENROUTER_AGENT, - name: "openrouter.tool", - type: SpanTypeAttribute.TOOL, - extractInput: (args, event) => ({ - input: args[0], - metadata: { - provider: "openrouter", - tool_name: event.toolName, - ...(event.toolCallId - ? { tool_call_id: event.toolCallId } - : {}), - }, - }), - extractOutput: (result) => result, - extractMetrics: () => ({}), - aggregateChunks: (chunks) => ({ - output: - chunks.length > 0 ? chunks[chunks.length - 1] : undefined, - metrics: {}, - }), - }, - ), - ), + openRouterAgentChannels.toolExecute.intercept( + (target, receiver, args, additional) => + traceStreamingCall( + () => Reflect.apply(target, receiver, args), + { ...additional, arguments: args, self: receiver }, + { + instrumentationName: INSTRUMENTATION_NAMES.OPENROUTER_AGENT, + name: "openrouter.tool", + type: SpanTypeAttribute.TOOL, + extractInput: (args, event) => ({ + input: args[0], + metadata: { + provider: "openrouter", + tool_name: event.toolName, + ...(event.toolCallId ? { tool_call_id: event.toolCallId } : {}), + }, + }), + extractOutput: (result) => result, + extractMetrics: () => ({}), + aggregateChunks: (chunks) => ({ + output: chunks.length > 0 ? chunks[chunks.length - 1] : undefined, + metrics: {}, + }), + }, + ), ); - this.unsubscribers.push( - openRouterAgentChannels.callModel.intercept((target, receiver, args) => { - runInstrumentation(() => { - const request = getOpenRouterCallModelRequestArg(args); - if (request) patchOpenRouterCallModelRequestTools(request); - }); - return Reflect.apply(target, receiver, args); - }), - ); + openRouterAgentChannels.callModel.intercept((target, receiver, args) => { + runInstrumentation(() => { + const request = getOpenRouterCallModelRequestArg(args); + if (request) patchOpenRouterCallModelRequestTools(request); + }); + return Reflect.apply(target, receiver, args); + }); } } @@ -1318,3 +1301,13 @@ function isAsyncIterable(value: unknown): value is AsyncIterable { } export { parseOpenRouterMetricsFromUsage }; + +let openRouterAgentInstrumentationConsumer: + | OpenRouterAgentInstrumentationConsumer + | undefined; + +export function registerOpenRouterAgentInstrumentation(): void { + openRouterAgentInstrumentationConsumer ??= + new OpenRouterAgentInstrumentationConsumer(); + openRouterAgentInstrumentationConsumer.register(); +} diff --git a/js/src/instrumentation/plugins/openrouter-agent-interceptors.ts b/js/src/instrumentation/providers/openrouter-agent-interceptors.ts similarity index 100% rename from js/src/instrumentation/plugins/openrouter-agent-interceptors.ts rename to js/src/instrumentation/providers/openrouter-agent-interceptors.ts diff --git a/js/src/instrumentation/plugins/openrouter-plugin.test.ts b/js/src/instrumentation/providers/openrouter-instrumentation.test.ts similarity index 98% rename from js/src/instrumentation/plugins/openrouter-plugin.test.ts rename to js/src/instrumentation/providers/openrouter-instrumentation.test.ts index 6cd52838b..71a2bf47a 100644 --- a/js/src/instrumentation/plugins/openrouter-plugin.test.ts +++ b/js/src/instrumentation/providers/openrouter-instrumentation.test.ts @@ -1,3 +1,4 @@ +import { registerInstrumentation } from "../registrations/openrouter"; import { afterEach, beforeAll, @@ -6,15 +7,16 @@ import { expect, it, vi, -} from "vitest"; -import { _exportsForTestingOnly, initLogger } from "../../logger"; +} from "vite-plus/test"; +import { _exportsForTestingOnly } from "../../logger/testing"; +import { initLogger } from "../../logger/core"; import { configureNode } from "../../node/config"; import { openRouterChannels } from "./openrouter-interceptors"; import { aggregateOpenRouterChatChunks, aggregateOpenRouterResponseStreamEvents, parseOpenRouterMetricsFromUsage, -} from "./openrouter-plugin"; +} from "./openrouter-instrumentation"; const TEST_PROVIDER = "openai"; const TEST_MODEL = "gpt-4.1-mini"; @@ -23,11 +25,12 @@ const TEST_RERANK_MODEL = "rerank-v3.5"; try { configureNode(); + registerInstrumentation(); } catch { // Best-effort initialization for test environments. } -describe("OpenRouter Plugin", () => { +describe("OpenRouter instrumentation", () => { let backgroundLogger: ReturnType< typeof _exportsForTestingOnly.useTestBackgroundLogger >; @@ -39,7 +42,7 @@ describe("OpenRouter Plugin", () => { beforeEach(() => { backgroundLogger = _exportsForTestingOnly.useTestBackgroundLogger(); initLogger({ - projectName: "openrouter-plugin.test.ts", + projectName: "openrouter-instrumentation.test.ts", projectId: "test-project-id", }); }); diff --git a/js/src/instrumentation/plugins/openrouter-plugin.ts b/js/src/instrumentation/providers/openrouter-instrumentation.ts similarity index 73% rename from js/src/instrumentation/plugins/openrouter-plugin.ts rename to js/src/instrumentation/providers/openrouter-instrumentation.ts index bf85143a6..8c32426b9 100644 --- a/js/src/instrumentation/plugins/openrouter-plugin.ts +++ b/js/src/instrumentation/providers/openrouter-instrumentation.ts @@ -1,16 +1,15 @@ import { INSTRUMENTATION_NAMES } from "../../span-origin"; -import { BasePlugin, toLoggedError } from "../core"; +import { toLoggedError } from "../core"; import { traceAsyncCall, traceStreamingCall, traceSyncStreamCall, - unsubscribeAll, } from "../core/tracing"; import { runInstrumentation } from "../core/observe-result"; import { SpanTypeAttribute, isObject } from "../../../util/index"; -import type { Span } from "../../logger"; -import { withCurrent } from "../../logger"; +import type { Span } from "../../logger/core"; +import { withCurrent } from "../../logger/core"; import { getCurrentUnixTimestamp } from "../../util"; import type { OpenRouterCallModelRequest, @@ -27,340 +26,316 @@ import { zodToJsonSchema } from "../../zod/utils"; import type { ChannelMessage } from "../core/tracing-types"; import { openRouterChannels } from "./openrouter-interceptors"; -export class OpenRouterPlugin extends BasePlugin { - protected onEnable(): void { +class OpenRouterInstrumentationConsumer { + public register(): void { this.subscribeToOpenRouterChannels(); } - - protected onDisable(): void { - this.unsubscribers = unsubscribeAll(this.unsubscribers); - } - private subscribeToOpenRouterChannels(): void { - this.unsubscribers.push( - openRouterChannels.chatSend.intercept( - (target, receiver, args, additional) => - traceStreamingCall( - () => Reflect.apply(target, receiver, args), - { ...additional, arguments: args, self: receiver }, - { - instrumentationName: INSTRUMENTATION_NAMES.OPENROUTER, - name: "openrouter.chat.send", - type: SpanTypeAttribute.LLM, - extractInput: (args) => { - const request = getOpenRouterRequestArg(args); - const chatGenerationParams = isObject( - request?.chatGenerationParams, - ) - ? request.chatGenerationParams - : {}; - const httpReferer = request?.httpReferer; - const xTitle = request?.xTitle; - const { messages, ...metadata } = chatGenerationParams; - return { - input: messages, - metadata: buildOpenRouterMetadata( - metadata, - httpReferer, - xTitle, - ), - }; - }, - extractOutput: (result) => { - return isObject(result) ? result.choices : undefined; - }, - extractMetrics: (result, startTime) => { - const metrics = parseOpenRouterMetricsFromUsage(result?.usage); - if (startTime) { - metrics.time_to_first_token = - getCurrentUnixTimestamp() - startTime; - } - return metrics; - }, - aggregateChunks: aggregateOpenRouterChatChunks, + openRouterChannels.chatSend.intercept( + (target, receiver, args, additional) => + traceStreamingCall( + () => Reflect.apply(target, receiver, args), + { ...additional, arguments: args, self: receiver }, + { + instrumentationName: INSTRUMENTATION_NAMES.OPENROUTER, + name: "openrouter.chat.send", + type: SpanTypeAttribute.LLM, + extractInput: (args) => { + const request = getOpenRouterRequestArg(args); + const chatGenerationParams = isObject( + request?.chatGenerationParams, + ) + ? request.chatGenerationParams + : {}; + const httpReferer = request?.httpReferer; + const xTitle = request?.xTitle; + const { messages, ...metadata } = chatGenerationParams; + return { + input: messages, + metadata: buildOpenRouterMetadata( + metadata, + httpReferer, + xTitle, + ), + }; + }, + extractOutput: (result) => { + return isObject(result) ? result.choices : undefined; }, - ), - ), + extractMetrics: (result, startTime) => { + const metrics = parseOpenRouterMetricsFromUsage(result?.usage); + if (startTime) { + metrics.time_to_first_token = + getCurrentUnixTimestamp() - startTime; + } + return metrics; + }, + aggregateChunks: aggregateOpenRouterChatChunks, + }, + ), ); - this.unsubscribers.push( - openRouterChannels.embeddingsGenerate.intercept( - (target, receiver, args, additional) => - traceAsyncCall( - () => Reflect.apply(target, receiver, args), - { ...additional, arguments: args, self: receiver }, - { - instrumentationName: INSTRUMENTATION_NAMES.OPENROUTER, - name: "openrouter.embeddings.generate", - type: SpanTypeAttribute.LLM, - extractInput: (args) => { - const request = getOpenRouterRequestArg(args); - const requestBody = isObject(request?.requestBody) - ? request.requestBody - : {}; - const httpReferer = request?.httpReferer; - const xTitle = request?.xTitle; - const { input, ...metadata } = requestBody; - return { - input, - metadata: buildOpenRouterEmbeddingMetadata( - metadata, - httpReferer, - xTitle, - ), - }; - }, - extractOutput: (result) => { - if (!isObject(result)) { - return undefined; - } - - const embedding = result.data?.[0]?.embedding; - return Array.isArray(embedding) - ? { embedding_length: embedding.length } - : undefined; - }, - extractMetadata: (result) => { - if (!isObject(result)) { - return undefined; - } + openRouterChannels.embeddingsGenerate.intercept( + (target, receiver, args, additional) => + traceAsyncCall( + () => Reflect.apply(target, receiver, args), + { ...additional, arguments: args, self: receiver }, + { + instrumentationName: INSTRUMENTATION_NAMES.OPENROUTER, + name: "openrouter.embeddings.generate", + type: SpanTypeAttribute.LLM, + extractInput: (args) => { + const request = getOpenRouterRequestArg(args); + const requestBody = isObject(request?.requestBody) + ? request.requestBody + : {}; + const httpReferer = request?.httpReferer; + const xTitle = request?.xTitle; + const { input, ...metadata } = requestBody; + return { + input, + metadata: buildOpenRouterEmbeddingMetadata( + metadata, + httpReferer, + xTitle, + ), + }; + }, + extractOutput: (result) => { + if (!isObject(result)) { + return undefined; + } - return extractOpenRouterResponseMetadata(result); - }, - extractMetrics: (result) => { - return isObject(result) - ? parseOpenRouterMetricsFromUsage(result.usage) - : {}; - }, + const embedding = result.data?.[0]?.embedding; + return Array.isArray(embedding) + ? { embedding_length: embedding.length } + : undefined; }, - ), - ), - ); + extractMetadata: (result) => { + if (!isObject(result)) { + return undefined; + } - this.unsubscribers.push( - openRouterChannels.rerankRerank.intercept( - (target, receiver, args, additional) => - traceAsyncCall( - () => Reflect.apply(target, receiver, args), - { ...additional, arguments: args, self: receiver }, - { - instrumentationName: INSTRUMENTATION_NAMES.OPENROUTER, - name: "openrouter.rerank.rerank", - type: SpanTypeAttribute.LLM, - extractInput: (args) => { - const request = getOpenRouterRequestArg(args); - const requestBody = isObject(request?.requestBody) - ? request.requestBody - : {}; - const httpReferer = request?.httpReferer; - const xTitle = request?.xTitle ?? request?.appTitle; - const { documents, query, ...metadata } = requestBody; - return { - input: { - documents, - query, - }, - metadata: buildOpenRouterRerankMetadata( - metadata, - documents, - httpReferer, - xTitle, - ), - }; - }, - extractOutput: (result) => extractOpenRouterRerankOutput(result), - extractMetadata: (result) => - extractOpenRouterResponseMetadata(result), - extractMetrics: (result) => - isObject(result) - ? parseOpenRouterMetricsFromUsage(result.usage) - : {}, + return extractOpenRouterResponseMetadata(result); + }, + extractMetrics: (result) => { + return isObject(result) + ? parseOpenRouterMetricsFromUsage(result.usage) + : {}; }, - ), - ), + }, + ), ); - this.unsubscribers.push( - openRouterChannels.betaResponsesSend.intercept( - (target, receiver, args, additional) => - traceStreamingCall( - () => Reflect.apply(target, receiver, args), - { ...additional, arguments: args, self: receiver }, - { - instrumentationName: INSTRUMENTATION_NAMES.OPENROUTER, - name: "openrouter.beta.responses.send", - type: SpanTypeAttribute.LLM, - extractInput: (args) => { - const request = getOpenRouterRequestArg(args); - const openResponsesRequest = isObject( - request?.openResponsesRequest, - ) - ? request.openResponsesRequest - : {}; - const httpReferer = request?.httpReferer; - const xTitle = request?.xTitle; - const { input, ...metadata } = openResponsesRequest; - return { - input, - metadata: buildOpenRouterMetadata( - metadata, - httpReferer, - xTitle, - ), - }; - }, - extractOutput: (result) => - extractOpenRouterResponseOutput( - result as Record, + openRouterChannels.rerankRerank.intercept( + (target, receiver, args, additional) => + traceAsyncCall( + () => Reflect.apply(target, receiver, args), + { ...additional, arguments: args, self: receiver }, + { + instrumentationName: INSTRUMENTATION_NAMES.OPENROUTER, + name: "openrouter.rerank.rerank", + type: SpanTypeAttribute.LLM, + extractInput: (args) => { + const request = getOpenRouterRequestArg(args); + const requestBody = isObject(request?.requestBody) + ? request.requestBody + : {}; + const httpReferer = request?.httpReferer; + const xTitle = request?.xTitle ?? request?.appTitle; + const { documents, query, ...metadata } = requestBody; + return { + input: { + documents, + query, + }, + metadata: buildOpenRouterRerankMetadata( + metadata, + documents, + httpReferer, + xTitle, ), - extractMetadata: (result) => - extractOpenRouterResponseMetadata(result), - extractMetrics: (result, startTime) => { - const metrics = parseOpenRouterMetricsFromUsage(result?.usage); - if (startTime) { - metrics.time_to_first_token = - getCurrentUnixTimestamp() - startTime; - } - return metrics; - }, - aggregateChunks: aggregateOpenRouterResponseStreamEvents, + }; }, - ), - ), + extractOutput: (result) => extractOpenRouterRerankOutput(result), + extractMetadata: (result) => + extractOpenRouterResponseMetadata(result), + extractMetrics: (result) => + isObject(result) + ? parseOpenRouterMetricsFromUsage(result.usage) + : {}, + }, + ), ); - this.unsubscribers.push( - openRouterChannels.callModel.intercept( - (target, receiver, args, additional) => - traceSyncStreamCall( - () => Reflect.apply(target, receiver, args), - { ...additional, arguments: args, self: receiver }, - { - instrumentationName: INSTRUMENTATION_NAMES.OPENROUTER, - name: "openrouter.callModel", - type: SpanTypeAttribute.TASK, - extractInput: (args) => { - const request = getOpenRouterCallModelRequestArg(args); - return { - input: request - ? extractOpenRouterCallModelInput(request) - : undefined, - metadata: request - ? extractOpenRouterCallModelMetadata(request) - : { provider: "openrouter" }, - }; - }, - patchResult: ({ endEvent, result, span }) => { - return patchOpenRouterCallModelResult({ - request: getOpenRouterCallModelRequestArg(endEvent.arguments), - result, - span, - }); - }, + openRouterChannels.betaResponsesSend.intercept( + (target, receiver, args, additional) => + traceStreamingCall( + () => Reflect.apply(target, receiver, args), + { ...additional, arguments: args, self: receiver }, + { + instrumentationName: INSTRUMENTATION_NAMES.OPENROUTER, + name: "openrouter.beta.responses.send", + type: SpanTypeAttribute.LLM, + extractInput: (args) => { + const request = getOpenRouterRequestArg(args); + const openResponsesRequest = isObject( + request?.openResponsesRequest, + ) + ? request.openResponsesRequest + : {}; + const httpReferer = request?.httpReferer; + const xTitle = request?.xTitle; + const { input, ...metadata } = openResponsesRequest; + return { + input, + metadata: buildOpenRouterMetadata( + metadata, + httpReferer, + xTitle, + ), + }; }, - ), - ), + extractOutput: (result) => + extractOpenRouterResponseOutput( + result as Record, + ), + extractMetadata: (result) => + extractOpenRouterResponseMetadata(result), + extractMetrics: (result, startTime) => { + const metrics = parseOpenRouterMetricsFromUsage(result?.usage); + if (startTime) { + metrics.time_to_first_token = + getCurrentUnixTimestamp() - startTime; + } + return metrics; + }, + aggregateChunks: aggregateOpenRouterResponseStreamEvents, + }, + ), ); - this.unsubscribers.push( - openRouterChannels.callModelTurn.intercept( - (target, receiver, args, additional) => - traceAsyncCall( - () => Reflect.apply(target, receiver, args), - { ...additional, arguments: args, self: receiver }, - { - instrumentationName: INSTRUMENTATION_NAMES.OPENROUTER, - name: "openrouter.beta.responses.send", - type: SpanTypeAttribute.LLM, - extractInput: (args, event) => { - const request = getOpenRouterCallModelRequestArg(args); - const metadata = request + openRouterChannels.callModel.intercept( + (target, receiver, args, additional) => + traceSyncStreamCall( + () => Reflect.apply(target, receiver, args), + { ...additional, arguments: args, self: receiver }, + { + instrumentationName: INSTRUMENTATION_NAMES.OPENROUTER, + name: "openrouter.callModel", + type: SpanTypeAttribute.TASK, + extractInput: (args) => { + const request = getOpenRouterCallModelRequestArg(args); + return { + input: request + ? extractOpenRouterCallModelInput(request) + : undefined, + metadata: request ? extractOpenRouterCallModelMetadata(request) - : { provider: "openrouter" }; + : { provider: "openrouter" }, + }; + }, + patchResult: ({ endEvent, result, span }) => { + return patchOpenRouterCallModelResult({ + request: getOpenRouterCallModelRequestArg(endEvent.arguments), + result, + span, + }); + }, + }, + ), + ); - if (isObject(metadata) && "tools" in metadata) { - delete (metadata as Record).tools; - } + openRouterChannels.callModelTurn.intercept( + (target, receiver, args, additional) => + traceAsyncCall( + () => Reflect.apply(target, receiver, args), + { ...additional, arguments: args, self: receiver }, + { + instrumentationName: INSTRUMENTATION_NAMES.OPENROUTER, + name: "openrouter.beta.responses.send", + type: SpanTypeAttribute.LLM, + extractInput: (args, event) => { + const request = getOpenRouterCallModelRequestArg(args); + const metadata = request + ? extractOpenRouterCallModelMetadata(request) + : { provider: "openrouter" }; + + if (isObject(metadata) && "tools" in metadata) { + delete (metadata as Record).tools; + } + return { + input: request + ? extractOpenRouterCallModelInput(request) + : undefined, + metadata: { + ...metadata, + step: event.step, + step_type: event.stepType, + }, + }; + }, + extractOutput: (result) => + extractOpenRouterResponseOutput( + result as Record, + ), + extractMetadata: (result, event) => { + if (!isObject(result)) { return { - input: request - ? extractOpenRouterCallModelInput(request) - : undefined, - metadata: { - ...metadata, - step: event.step, - step_type: event.stepType, - }, + step: event?.step, + step_type: event?.stepType, }; - }, - extractOutput: (result) => - extractOpenRouterResponseOutput( - result as Record, - ), - extractMetadata: (result, event) => { - if (!isObject(result)) { - return { - step: event?.step, - step_type: event?.stepType, - }; - } + } - return { - ...(extractOpenRouterResponseMetadata(result) || {}), - ...(event?.step !== undefined ? { step: event.step } : {}), - ...(event?.stepType ? { step_type: event.stepType } : {}), - }; - }, - extractMetrics: (result) => - isObject(result) - ? parseOpenRouterMetricsFromUsage(result.usage) - : {}, + return { + ...(extractOpenRouterResponseMetadata(result) || {}), + ...(event?.step !== undefined ? { step: event.step } : {}), + ...(event?.stepType ? { step_type: event.stepType } : {}), + }; }, - ), - ), + extractMetrics: (result) => + isObject(result) + ? parseOpenRouterMetricsFromUsage(result.usage) + : {}, + }, + ), ); - this.unsubscribers.push( - openRouterChannels.toolExecute.intercept( - (target, receiver, args, additional) => - traceStreamingCall( - () => Reflect.apply(target, receiver, args), - { ...additional, arguments: args, self: receiver }, - { - instrumentationName: INSTRUMENTATION_NAMES.OPENROUTER, - name: "openrouter.tool", - type: SpanTypeAttribute.TOOL, - extractInput: (args, event) => ({ - input: args[0], - metadata: { - provider: "openrouter", - tool_name: event.toolName, - ...(event.toolCallId - ? { tool_call_id: event.toolCallId } - : {}), - }, - }), - extractOutput: (result) => result, - extractMetrics: () => ({}), - aggregateChunks: (chunks) => ({ - output: - chunks.length > 0 ? chunks[chunks.length - 1] : undefined, - metrics: {}, - }), - }, - ), - ), + openRouterChannels.toolExecute.intercept( + (target, receiver, args, additional) => + traceStreamingCall( + () => Reflect.apply(target, receiver, args), + { ...additional, arguments: args, self: receiver }, + { + instrumentationName: INSTRUMENTATION_NAMES.OPENROUTER, + name: "openrouter.tool", + type: SpanTypeAttribute.TOOL, + extractInput: (args, event) => ({ + input: args[0], + metadata: { + provider: "openrouter", + tool_name: event.toolName, + ...(event.toolCallId ? { tool_call_id: event.toolCallId } : {}), + }, + }), + extractOutput: (result) => result, + extractMetrics: () => ({}), + aggregateChunks: (chunks) => ({ + output: chunks.length > 0 ? chunks[chunks.length - 1] : undefined, + metrics: {}, + }), + }, + ), ); - this.unsubscribers.push( - openRouterChannels.callModel.intercept((target, receiver, args) => { - runInstrumentation(() => { - const request = getOpenRouterCallModelRequestArg(args); - if (request) patchOpenRouterCallModelRequestTools(request); - }); - return Reflect.apply(target, receiver, args); - }), - ); + openRouterChannels.callModel.intercept((target, receiver, args) => { + runInstrumentation(() => { + const request = getOpenRouterCallModelRequestArg(args); + if (request) patchOpenRouterCallModelRequestTools(request); + }); + return Reflect.apply(target, receiver, args); + }); } } @@ -1572,3 +1547,12 @@ function isAsyncIterable(value: unknown): value is AsyncIterable { } export { parseOpenRouterMetricsFromUsage }; + +let openRouterInstrumentationConsumer: + | OpenRouterInstrumentationConsumer + | undefined; + +export function registerOpenRouterInstrumentation(): void { + openRouterInstrumentationConsumer ??= new OpenRouterInstrumentationConsumer(); + openRouterInstrumentationConsumer.register(); +} diff --git a/js/src/instrumentation/plugins/openrouter-interceptors.ts b/js/src/instrumentation/providers/openrouter-interceptors.ts similarity index 100% rename from js/src/instrumentation/plugins/openrouter-interceptors.ts rename to js/src/instrumentation/providers/openrouter-interceptors.ts diff --git a/js/src/instrumentation/plugins/pi-coding-agent-context.test.ts b/js/src/instrumentation/providers/pi-coding-agent-context.test.ts similarity index 97% rename from js/src/instrumentation/plugins/pi-coding-agent-context.test.ts rename to js/src/instrumentation/providers/pi-coding-agent-context.test.ts index e50d43794..b291ab038 100644 --- a/js/src/instrumentation/plugins/pi-coding-agent-context.test.ts +++ b/js/src/instrumentation/providers/pi-coding-agent-context.test.ts @@ -1,10 +1,7 @@ -import { afterEach, beforeEach, describe, expect, it } from "vitest"; -import { - _exportsForTestingOnly, - currentSpan, - startSpan, - type Span, -} from "../../logger"; +import { registerInstrumentation } from "../registrations/piCodingAgent"; +import { afterEach, beforeEach, describe, expect, it } from "vite-plus/test"; +import { _exportsForTestingOnly } from "../../logger/testing"; +import { currentSpan, startSpan, type Span } from "../../logger/core"; import { configureNode } from "../../node/config"; import type { PiAgentEvent, @@ -13,16 +10,13 @@ import type { PiTool, } from "../../vendor-sdk-types/pi-coding-agent"; import { isAutoInstrumentationSuppressed } from "../auto-instrumentation-suppression"; -import { registry } from "../registry"; import { piCodingAgentChannels } from "./pi-coding-agent-interceptors"; -import { PiCodingAgentPlugin } from "./pi-coding-agent-plugin"; // Use real Braintrust spans and async context, capturing all log writes in memory. configureNode(); -registry.disable(); +registerInstrumentation(); describe("Pi tool execution context", () => { - let plugin: PiCodingAgentPlugin; let background: ReturnType< typeof _exportsForTestingOnly.useTestBackgroundLogger >; @@ -32,12 +26,9 @@ describe("Pi tool execution context", () => { await _exportsForTestingOnly.simulateLoginForTests(); background = _exportsForTestingOnly.useTestBackgroundLogger(); experiment = _exportsForTestingOnly.initTestExperiment("pi-tool-context"); - plugin = new PiCodingAgentPlugin(); - plugin.enable(); }); afterEach(() => { - plugin.disable(); _exportsForTestingOnly.clearTestBackgroundLogger(); _exportsForTestingOnly.simulateLogoutForTests(); }); @@ -330,7 +321,6 @@ describe("Pi tool execution context", () => { "non-string ID", "ended tool", "outside prompt", - "finalized prompt", ] as const)("preserves the caller context with %s", async (mode) => { let expectedSpan: Span; const tool: PiTool = { @@ -355,7 +345,6 @@ describe("Pi tool execution context", () => { await fixture.prompt(async () => { await fixture.start("call"); if (mode === "ended tool") await fixture.end("call"); - if (mode === "finalized prompt") plugin.disable(); const id = mode === "missing ID" ? undefined diff --git a/js/src/instrumentation/plugins/pi-coding-agent-plugin.test.ts b/js/src/instrumentation/providers/pi-coding-agent-instrumentation.test.ts similarity index 90% rename from js/src/instrumentation/plugins/pi-coding-agent-plugin.test.ts rename to js/src/instrumentation/providers/pi-coding-agent-instrumentation.test.ts index eabfed2a8..97795ea34 100644 --- a/js/src/instrumentation/plugins/pi-coding-agent-plugin.test.ts +++ b/js/src/instrumentation/providers/pi-coding-agent-instrumentation.test.ts @@ -1,4 +1,11 @@ -import { afterEach, beforeEach, describe, expect, it, vi } from "vitest"; +import { + afterEach, + beforeEach, + describe, + expect, + it, + vi, +} from "vite-plus/test"; const { interceptorsByName, mockNewTracingChannel, mockStartSpan } = vi.hoisted( () => ({ @@ -8,8 +15,8 @@ const { interceptorsByName, mockNewTracingChannel, mockStartSpan } = vi.hoisted( }), ); -vi.mock("../../isomorph", async (importOriginal) => { - const actual = await importOriginal(); +vi.mock("#braintrust-platform", async (importOriginal) => { + const actual = await importOriginal(); const { AsyncLocalStorage } = await import("node:async_hooks"); return { ...actual, @@ -28,10 +35,14 @@ vi.mock("../../global-instrumentation-hooks", async (importOriginal) => ({ newGlobalInvocationHook: mockNewTracingChannel, })); -vi.mock("../../logger", async (importOriginal) => ({ - Attachment: (await importOriginal()) +vi.mock("../../logger/core", async (importOriginal) => ({ + Attachment: (await importOriginal()) .Attachment, - startSpan: (...args: unknown[]) => mockStartSpan(...args), + _internalStartSpan: (...args: unknown[]) => mockStartSpan(...args), + BaseAttachment: (await importOriginal()) + .BaseAttachment, + _internalCaptureAttachmentsEnabled: () => + process.env.BRAINTRUST_AUTO_CAPTURE_ATTACHMENTS === "true", withCurrent: (_span: unknown, callback: () => unknown) => callback(), })); @@ -41,7 +52,7 @@ import type { PiModel, PiSimpleStreamOptions, } from "../../vendor-sdk-types/pi-coding-agent"; -import { PiCodingAgentPlugin } from "./pi-coding-agent-plugin"; +import { registerPiCodingAgentInstrumentation } from "./pi-coding-agent-instrumentation"; const PROMPT_CHANNEL = "orchestrion:@earendil-works/pi-coding-agent:AgentSession.prompt"; @@ -54,12 +65,10 @@ type TestSpan = { name?: string; }; -describe("PiCodingAgentPlugin", () => { - let plugins: PiCodingAgentPlugin[]; +describe("registerPiCodingAgentInstrumentation", () => { let spans: TestSpan[]; beforeEach(() => { - plugins = []; spans = []; interceptorsByName.clear(); mockNewTracingChannel.mockImplementation((name: string) => ({ @@ -98,9 +107,6 @@ describe("PiCodingAgentPlugin", () => { }); afterEach(() => { - for (const plugin of plugins) { - plugin.disable(); - } vi.clearAllMocks(); vi.useRealTimers(); vi.unstubAllEnvs(); @@ -203,7 +209,7 @@ describe("PiCodingAgentPlugin", () => { }); async function captureContext(context: PiContext) { - const interceptor = promptInterceptor(enablePlugin(plugins)); + const interceptor = promptInterceptor(); const originalStream = vi.fn(async (_model: PiModel, _context: PiContext) => makeStream(makeAssistantMessage("done")), ); @@ -222,7 +228,7 @@ describe("PiCodingAgentPlugin", () => { } it("captures per-call usage and cost without double-counting parent rollups", async () => { - const interceptor = promptInterceptor(enablePlugin(plugins)); + const interceptor = promptInterceptor(); // Pi input excludes cache usage; reasoning is already included in output. const usages: NonNullable[] = [ { @@ -302,7 +308,7 @@ describe("PiCodingAgentPlugin", () => { options?: PiSimpleStreamOptions, model: PiModel = anthropicModel(), ) { - const interceptor = promptInterceptor(enablePlugin(plugins)); + const interceptor = promptInterceptor(); const agent = makeAgent(vi.fn(async () => makeStream(message))); await interceptor( async () => { @@ -471,7 +477,7 @@ describe("PiCodingAgentPlugin", () => { it.each(["success", "failure", "blocked"] as const)( "records tool approval only when the executor ran: %s", async (outcome) => { - const interceptor = promptInterceptor(enablePlugin(plugins)); + const interceptor = promptInterceptor(); const agent = makeAgent( vi.fn(() => makeStream(makeAssistantMessage("done"))), ); @@ -559,7 +565,7 @@ describe("PiCodingAgentPlugin", () => { ]; const { stream } = makeIteratorBackedStream(events); const agent = makeAgent(vi.fn(async () => stream)); - const interceptor = promptInterceptor(enablePlugin(plugins)); + const interceptor = promptInterceptor(); await interceptor( async () => { const observed = await agent.streamFn(anthropicModel(), { @@ -585,7 +591,7 @@ describe("PiCodingAgentPlugin", () => { it.each(["steer", "followUp"] as const)( "does not trace queued %s prompts or correlate their later events", async (streamingBehavior) => { - const interceptor = promptInterceptor(enablePlugin(plugins)); + const interceptor = promptInterceptor(); const agent = makeAgent( vi.fn(() => makeStream(makeAssistantMessage("done"))), ); @@ -620,7 +626,7 @@ describe("PiCodingAgentPlugin", () => { ); it("finishes a direct idle prompt even when it specifies followUp behavior", async () => { - const interceptor = promptInterceptor(enablePlugin(plugins)); + const interceptor = promptInterceptor(); const agent = makeAgent( vi.fn(() => makeStream(makeAssistantMessage("done"))), ); @@ -643,7 +649,7 @@ describe("PiCodingAgentPlugin", () => { it.each([false, true])( "preserves stream and promise identities with async=%s", async (asyncStream) => { - const interceptor = promptInterceptor(enablePlugin(plugins)); + const interceptor = promptInterceptor(); const stream = makeStream(makeAssistantMessage("done")); const completion = stream.result(); stream.result = vi.fn(() => completion); @@ -682,7 +688,7 @@ describe("PiCodingAgentPlugin", () => { ); it("does not let input extraction failures replace or retry the provider call", async () => { - const interceptor = promptInterceptor(enablePlugin(plugins)); + const interceptor = promptInterceptor(); const stream = makeStream(makeAssistantMessage("done")); const target = vi.fn(() => stream); const agent = makeAgent(target); @@ -705,7 +711,7 @@ describe("PiCodingAgentPlugin", () => { it.each(["false", "true"])( "applies attachment consent %s to prompt, conversation, and tool output", async (consent) => { - vi.stubEnv("BRAINTRUST_CAPTURE_ATTACHMENTS", consent); + vi.stubEnv("BRAINTRUST_AUTO_CAPTURE_ATTACHMENTS", consent); const enabled = consent === "true" || consent === "1"; const png = "iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAQAAAC1HAwCAAAAC0lEQVR42mP8/x8AAwMCAO+aWQAAAABJRU5ErkJggg=="; @@ -726,7 +732,7 @@ describe("PiCodingAgentPlugin", () => { ], }; const before = structuredClone(context); - const interceptor = promptInterceptor(enablePlugin(plugins)); + const interceptor = promptInterceptor(); const message = makeAssistantMessage("done"); const agent = makeAgent(vi.fn(() => makeStream(message))); const result = { @@ -782,7 +788,7 @@ describe("PiCodingAgentPlugin", () => { ); it("does not read inline bytes when attachment capture is disabled", async () => { - vi.stubEnv("BRAINTRUST_CAPTURE_ATTACHMENTS", "false"); + vi.stubEnv("BRAINTRUST_AUTO_CAPTURE_ATTACHMENTS", "false"); const data = vi.fn(() => { throw new Error("must not read bytes"); }); @@ -793,7 +799,7 @@ describe("PiCodingAgentPlugin", () => { return data(); }, }; - const interceptor = promptInterceptor(enablePlugin(plugins)); + const interceptor = promptInterceptor(); const agent = makeAgent( vi.fn(() => makeStream(makeAssistantMessage("done"))), ); @@ -831,7 +837,7 @@ describe("PiCodingAgentPlugin", () => { }, ], }; - const interceptor = promptInterceptor(enablePlugin(plugins)); + const interceptor = promptInterceptor(); const agent = makeAgent(vi.fn(() => makeStream(message))); await interceptor( async () => { @@ -880,7 +886,7 @@ describe("PiCodingAgentPlugin", () => { }); it("preserves full-iterator stream behavior", async () => { - const interceptor = promptInterceptor(enablePlugin(plugins)); + const interceptor = promptInterceptor(); const message = makeAssistantMessage("done"); const { result, stream } = makeIteratorBackedStream([ { partial: message, type: "start" }, @@ -918,7 +924,7 @@ describe("PiCodingAgentPlugin", () => { }); it("forwards iterator failures and closes the underlying iterator", async () => { - const interceptor = promptInterceptor(enablePlugin(plugins)); + const interceptor = promptInterceptor(); const { iterator, stream } = makeIteratorBackedStream([]); const error = new Error("stream next failed"); iterator.next.mockRejectedValueOnce(error); @@ -952,7 +958,7 @@ describe("PiCodingAgentPlugin", () => { }); it("closes active prompt and LLM spans when the target rejects", async () => { - const interceptor = promptInterceptor(enablePlugin(plugins)); + const interceptor = promptInterceptor(); const agent = makeAgent( vi.fn(async () => makeStream(makeAssistantMessage("unused"))), ); @@ -985,62 +991,27 @@ describe("PiCodingAgentPlugin", () => { expect(taskSpan?.end).toHaveBeenCalledTimes(1); }); - it("ends in-flight prompts on disable while leaving the safe patch installed", async () => { - const plugin = enablePlugin(plugins); - const interceptor = promptInterceptor(plugin); - const originalStreamFn = vi.fn(async () => - makeStream(makeAssistantMessage("done")), - ); - const agent = makeAgent(originalStreamFn); - const session = makeSession(agent); - const targetGate = deferred(); - - const result = interceptor( - async () => { - await targetGate.promise; - }, - session, - ["disable", undefined], - {}, - ); - const patchedStreamFn = agent.streamFn; - - plugin.disable(); - - const taskSpan = findSpan(spans, "AgentSession.prompt"); - expect(agent.streamFn).toBe(patchedStreamFn); - expect(agent.streamFn).not.toBe(originalStreamFn); - expect(taskSpan?.end).toHaveBeenCalledTimes(1); - - targetGate.resolve(); - await result; - expect(taskSpan?.end).toHaveBeenCalledTimes(1); - }); - it("keeps installed tool wrappers working across SDK copies", async () => { - const first = enablePlugin(plugins); + registerPiCodingAgentInstrumentation(); const tool = { name: "lookup", execute: vi.fn((_id: string) => "result") }; const agent = makeAgent( vi.fn(() => makeStream(makeAssistantMessage("done"))), ); agent.state.tools = [tool]; const session = makeSession(agent); - await promptInterceptor(first)( + await promptInterceptor()( async () => {}, session, ["first", undefined], {}, ); const installedWrapper = tool.execute; - first.disable(); vi.resetModules(); - const { PiCodingAgentPlugin: OtherCopy } = - await import("./pi-coding-agent-plugin"); - const second = new OtherCopy(); - plugins.push(second); - second.enable(); - await promptInterceptor(second)( + const { registerPiCodingAgentInstrumentation: registerOtherCopy } = + await import("./pi-coding-agent-instrumentation"); + registerOtherCopy(); + await interceptorsByName.get(PROMPT_CHANNEL)( async () => { await agent.emit({ type: "tool_execution_start", @@ -1069,14 +1040,8 @@ describe("PiCodingAgentPlugin", () => { }); }); -function enablePlugin(plugins: PiCodingAgentPlugin[]): PiCodingAgentPlugin { - const plugin = new PiCodingAgentPlugin(); - plugins.push(plugin); - plugin.enable(); - return plugin; -} - -function promptInterceptor(_plugin: PiCodingAgentPlugin): any { +function promptInterceptor(): any { + registerPiCodingAgentInstrumentation(); const interceptor = interceptorsByName.get(PROMPT_CHANNEL); expect(interceptor).toEqual(expect.any(Function)); return interceptor; @@ -1178,11 +1143,3 @@ function makeSession(agent: ReturnType) { sessionId: "session-1", }; } - -function deferred() { - let resolve!: (value: T | PromiseLike) => void; - const promise = new Promise((nextResolve) => { - resolve = nextResolve; - }); - return { promise, resolve }; -} diff --git a/js/src/instrumentation/plugins/pi-coding-agent-plugin.ts b/js/src/instrumentation/providers/pi-coding-agent-instrumentation.ts similarity index 93% rename from js/src/instrumentation/plugins/pi-coding-agent-plugin.ts rename to js/src/instrumentation/providers/pi-coding-agent-instrumentation.ts index 7e3e2ff1c..af95a7cd7 100644 --- a/js/src/instrumentation/plugins/pi-coding-agent-plugin.ts +++ b/js/src/instrumentation/providers/pi-coding-agent-instrumentation.ts @@ -1,10 +1,14 @@ -import { BasePlugin, toLoggedError } from "../core"; +import { toLoggedError } from "../core"; import { SpanTypeAttribute, isObject } from "../../../util/index"; import { debugLogger } from "../../debug-logger"; -import iso, { type IsoAsyncLocalStorage } from "../../isomorph"; -import type { Span } from "../../logger"; -import { startSpan as startBaseSpan, withCurrent } from "../../logger"; +import iso, { type IsoAsyncLocalStorage } from "#braintrust-platform"; +import type { Span } from "../../logger/core"; +import { + _internalStartSpan as startBaseSpan, + _internalCaptureAttachmentsEnabled, + withCurrent, +} from "../../logger/core"; import { INSTRUMENTATION_NAMES, withSpanInstrumentationName, @@ -47,7 +51,6 @@ type PiPromptState = { activeToolSpans: Map; agent: PiAgent; finalized: boolean; - onFinalize?: (state: PiPromptState) => void; metadata: Record; output?: unknown; error?: string; @@ -81,88 +84,68 @@ const PI_TOOL_EXECUTE_WRAPPED = Symbol.for( ); let piPromptContextStore: IsoAsyncLocalStorage | undefined; -export class PiCodingAgentPlugin extends BasePlugin { - private readonly activePromptStates = new Set(); - - protected onEnable(): void { +class PiCodingAgentInstrumentationConsumer { + public register(): void { this.interceptPrompt(); - this.unsubscribers.push( - piCodingAgentChannels.toolExecute.intercept((target, receiver, args) => - tracePiToolExecution( - () => Reflect.apply(target, receiver, args), - args[0], - ), + void piCodingAgentChannels.toolExecute.intercept((target, receiver, args) => + tracePiToolExecution( + () => Reflect.apply(target, receiver, args), + args[0], ), ); } - protected onDisable(): void { - for (const unsubscribe of this.unsubscribers) { - unsubscribe(); - } - this.unsubscribers = []; - - for (const state of [...this.activePromptStates]) { - finishPiPromptRun(state); - } - } - private interceptPrompt(): void { - this.unsubscribers.push( - piCodingAgentChannels.prompt.intercept( - (target, thisArg, args, additional) => { - const invokeTarget = () => Reflect.apply(target, thisArg, args); - let state: PiPromptState | undefined; - try { - state = startPiPromptRun( - { - ...additional, - arguments: args, - self: thisArg, - }, - (finalizedState) => { - this.activePromptStates.delete(finalizedState); - }, - ); - } catch (error) { - logInstrumentationError("Pi Coding Agent prompt start", error); - } + void piCodingAgentChannels.prompt.intercept( + (target, thisArg, args, additional) => { + const invokeTarget = () => Reflect.apply(target, thisArg, args); + let state: PiPromptState | undefined; + try { + state = startPiPromptRun({ + ...additional, + arguments: args, + self: thisArg, + }); + } catch (error) { + logInstrumentationError("Pi Coding Agent prompt start", error); + } - if (!state) { - return promptContextStore().run(undefined, () => - runWithAutoInstrumentationSuppressed(invokeTarget), - ); - } - this.activePromptStates.add(state); + if (!state) { + return promptContextStore().run(undefined, () => + runWithAutoInstrumentationSuppressed(invokeTarget), + ); + } - return promptContextStore().run(state, () => - runWithAutoInstrumentationSuppressed(() => { - let result: PromiseLike; - try { - result = invokeTarget(); - } catch (error) { - finishPiPromptRun(state, error); - throw error; - } + return promptContextStore().run(state, () => + runWithAutoInstrumentationSuppressed(() => { + let result: PromiseLike; + try { + result = invokeTarget(); + } catch (error) { + finishPiPromptRun(state, error); + throw error; + } - return observeResult( - result, - () => finishPiPromptRun(state), - (error) => { - finishPiPromptRun(state, error); - }, - ); - }), - ); - }, - ), + return observeResult( + result, + () => finishPiPromptRun(state), + (error) => { + finishPiPromptRun(state, error); + }, + ); + }), + ); + }, ); } } +export function registerPiCodingAgentInstrumentation(): void { + new PiCodingAgentInstrumentationConsumer().register(); +} + function startPiPromptRun( event: ChannelMessage, - onFinalize?: (state: PiPromptState) => void, ): PiPromptState | undefined { const session = extractSession(event); const agent = session?.agent; @@ -210,7 +193,6 @@ function startPiPromptRun( agent, finalized: false, metadata, - onFinalize, span, startTime: getCurrentUnixTimestamp(), }; @@ -621,7 +603,12 @@ function recordPiPromptOutput( message: PiAssistantMessage, ): void { state.error = piMessageError(message); - state.output = state.error ? undefined : extractAssistantOutput(message); + state.output = state.error + ? undefined + : extractAssistantOutput( + message, + _internalCaptureAttachmentsEnabled(state.span), + ); } function startPiToolSpan( @@ -680,7 +667,7 @@ function finishPiToolSpan( }; try { const output = runInstrumentation(() => - normalizePiToolOutput(event.result), + withCurrent(toolState.span, () => normalizePiToolOutput(event.result)), ); toolState.span.log({ ...(event.isError @@ -704,7 +691,6 @@ function finishPiPromptRun(state: PiPromptState, error?: unknown): void { return; } state.finalized = true; - state.onFinalize?.(state); finishOpenLlmSpans(state, error); finishOpenToolSpans(state, error); @@ -760,7 +746,14 @@ function finishPiLlmSpan( ...cleanMetrics(llmState.metrics), ...buildTimingMetrics(llmState.startTime), }, - ...(message ? { output: extractAssistantOutput(message) } : {}), + ...(message + ? { + output: extractAssistantOutput( + message, + _internalCaptureAttachmentsEnabled(llmState.span), + ), + } + : {}), }; }); if (data) llmState.span.log(data); @@ -937,7 +930,10 @@ function normalizeToolCall(toolCall: PiToolCall): unknown { }; } -function extractAssistantOutput(message: PiAssistantMessage): unknown { +function extractAssistantOutput( + message: PiAssistantMessage, + captureAttachments: boolean, +): unknown { return processInputAttachments( [ { @@ -946,7 +942,7 @@ function extractAssistantOutput(message: PiAssistantMessage): unknown { message: normalizeAssistantMessage(message), }, ], - isAutoCaptureAttachmentsEnabled(), + captureAttachments, ); } diff --git a/js/src/instrumentation/plugins/pi-coding-agent-interceptors.ts b/js/src/instrumentation/providers/pi-coding-agent-interceptors.ts similarity index 100% rename from js/src/instrumentation/plugins/pi-coding-agent-interceptors.ts rename to js/src/instrumentation/providers/pi-coding-agent-interceptors.ts diff --git a/js/src/instrumentation/plugins/strands-agent-sdk-plugin.test.ts b/js/src/instrumentation/providers/strands-agent-sdk-instrumentation.test.ts similarity index 91% rename from js/src/instrumentation/plugins/strands-agent-sdk-plugin.test.ts rename to js/src/instrumentation/providers/strands-agent-sdk-instrumentation.test.ts index 7397d107f..c26efe670 100644 --- a/js/src/instrumentation/plugins/strands-agent-sdk-plugin.test.ts +++ b/js/src/instrumentation/providers/strands-agent-sdk-instrumentation.test.ts @@ -1,4 +1,12 @@ -import { afterEach, beforeEach, describe, expect, it, vi } from "vitest"; +import iso from "#braintrust-platform"; +import { + afterEach, + beforeEach, + describe, + expect, + it, + vi, +} from "vite-plus/test"; import { newGlobalInvocationHook } from "../../global-instrumentation-hooks"; vi.mock("../../global-instrumentation-hooks", async (importOriginal) => ({ ...(await importOriginal< @@ -29,15 +37,17 @@ const { mockWithCurrent, mockNewAsyncLocalStorage, mockStartSpan } = vi.hoisted( }), ); -vi.mock("../../isomorph", () => ({ +vi.mock("#braintrust-platform", () => ({ default: { - getEnv: vi.fn(), + getEnv: vi.fn((name: string) => + name === "BRAINTRUST_AUTO_CAPTURE_ATTACHMENTS" ? "true" : undefined, + ), newAsyncLocalStorage: mockNewAsyncLocalStorage, }, })); -vi.mock("../../logger", async (importOriginal) => { - const actual = await importOriginal(); +vi.mock("../../logger/core", async (importOriginal) => { + const actual = await importOriginal(); return { ...actual, startSpan: (...args: unknown[]) => mockStartSpan(...args), @@ -45,15 +55,15 @@ vi.mock("../../logger", async (importOriginal) => { }; }); -import { Attachment } from "../../logger"; +import { Attachment } from "../../logger/core"; import { isAutoInstrumentationSuppressed } from "../auto-instrumentation-suppression"; -import { StrandsAgentSDKPlugin } from "./strands-agent-sdk-plugin"; +import { registerStrandsAgentSDKInstrumentation } from "./strands-agent-sdk-instrumentation"; const mockNewInvocationHook = newGlobalInvocationHook as ReturnType< typeof vi.fn >; -describe("StrandsAgentSDKPlugin", () => { +describe("registerStrandsAgentSDKInstrumentation", () => { let handlersByName: Map; let spans: Array<{ args: any; @@ -156,8 +166,7 @@ describe("StrandsAgentSDKPlugin", () => { }); it("intercepts Strands stream channels", () => { - const plugin = new StrandsAgentSDKPlugin(); - plugin.enable(); + registerStrandsAgentSDKInstrumentation(); expect( handlersByName.has("orchestrion:@strands-agents/sdk:Agent.stream"), @@ -168,12 +177,10 @@ describe("StrandsAgentSDKPlugin", () => { expect( handlersByName.has("orchestrion:@strands-agents/sdk:Swarm.stream"), ).toBe(true); - plugin.disable(); }); it("records agent model and tool spans from stream events", async () => { - const plugin = new StrandsAgentSDKPlugin(); - plugin.enable(); + registerStrandsAgentSDKInstrumentation(); const handlers = handlersByName.get( "orchestrion:@strands-agents/sdk:Agent.stream", @@ -328,13 +335,19 @@ describe("StrandsAgentSDKPlugin", () => { }); it.each([ - ["binary objects", new Uint8Array([1, 2, 3])], - ["base64 strings", "AQID"], + ["binary objects", new Uint8Array([1, 2, 3]), true], + ["omitted binary objects", new Uint8Array([1, 2, 3]), false], + ["base64 strings", "AQID", true], + ["omitted base64 strings", "AQID", false], ])( "converts media from %s to one attachment shared by agent and model spans", - async (_description, bytes) => { - const plugin = new StrandsAgentSDKPlugin(); - plugin.enable(); + async (_description, bytes, captureAttachments) => { + vi.mocked(iso.getEnv).mockImplementation((name) => + name === "BRAINTRUST_AUTO_CAPTURE_ATTACHMENTS" + ? String(captureAttachments) + : undefined, + ); + registerStrandsAgentSDKInstrumentation(); const handlers = handlersByName.get( "orchestrion:@strands-agents/sdk:Agent.stream", @@ -379,10 +392,15 @@ describe("StrandsAgentSDKPlugin", () => { (span) => span.args.name === "Strands model: gpt-4o-mini", ); const rootAttachment = - rootSpan?.args.event.input[0].document.source.bytes; + rootSpan?.args.event.input[0].document.source?.bytes; const modelAttachment = - modelSpan?.args.event.input[0].content[0].document.source.bytes; + modelSpan?.args.event.input[0].content[0].document.source?.bytes; + if (!captureAttachments) { + expect(rootAttachment).toBeUndefined(); + expect(modelAttachment).toBeUndefined(); + return; + } expect(rootAttachment).toBeInstanceOf(Attachment); expect(rootAttachment.reference).toMatchObject({ content_type: "application/pdf", @@ -394,8 +412,7 @@ describe("StrandsAgentSDKPlugin", () => { ); it("parents nested agent spans under active graph nodes", async () => { - const plugin = new StrandsAgentSDKPlugin(); - plugin.enable(); + registerStrandsAgentSDKInstrumentation(); const graphHandlers = handlersByName.get( "orchestrion:@strands-agents/sdk:Graph.stream", @@ -482,8 +499,7 @@ describe("StrandsAgentSDKPlugin", () => { }); it("parents nested graph spans under active graph nodes", async () => { - const plugin = new StrandsAgentSDKPlugin(); - plugin.enable(); + registerStrandsAgentSDKInstrumentation(); const graphHandlers = handlersByName.get( "orchestrion:@strands-agents/sdk:Graph.stream", @@ -571,8 +587,7 @@ describe("StrandsAgentSDKPlugin", () => { }); it("ends open child spans when a stream errors", async () => { - const plugin = new StrandsAgentSDKPlugin(); - plugin.enable(); + registerStrandsAgentSDKInstrumentation(); const handlers = handlersByName.get( "orchestrion:@strands-agents/sdk:Agent.stream", diff --git a/js/src/instrumentation/plugins/strands-agent-sdk-plugin.ts b/js/src/instrumentation/providers/strands-agent-sdk-instrumentation.ts similarity index 90% rename from js/src/instrumentation/plugins/strands-agent-sdk-plugin.ts rename to js/src/instrumentation/providers/strands-agent-sdk-instrumentation.ts index c68017e99..7459e284c 100644 --- a/js/src/instrumentation/plugins/strands-agent-sdk-plugin.ts +++ b/js/src/instrumentation/providers/strands-agent-sdk-instrumentation.ts @@ -1,13 +1,14 @@ -import { BasePlugin, toLoggedError } from "../core"; +import { toLoggedError } from "../core"; import { isAsyncIterable, patchStreamIfNeeded } from "../core/stream-patcher"; import { debugLogger } from "../../debug-logger"; import { + _internalCaptureAttachmentsEnabled, Attachment, BaseAttachment, startSpan as startBaseSpan, withCurrent, -} from "../../logger"; -import type { Span } from "../../logger"; +} from "../../logger/core"; +import type { Span } from "../../logger/core"; import { INSTRUMENTATION_NAMES, withSpanInstrumentationName, @@ -15,7 +16,10 @@ import { import { LRUCache } from "../../lru-cache"; import { getCurrentUnixTimestamp } from "../../util"; import { SpanTypeAttribute, isObject } from "../../../util/index"; -import { convertDataToBlob } from "../../wrappers/attachment-utils"; +import { + convertDataToBlob, + omitMediaData, +} from "../../wrappers/attachment-utils"; import { runWithAutoInstrumentationSuppressed } from "../auto-instrumentation-suppression"; import { strandsAgentSDKChannels } from "./strands-agent-sdk-interceptors"; import type { @@ -91,15 +95,18 @@ type ActiveChildParents = WeakMap>; const MAX_STRANDS_STRING_ATTACHMENT_CACHE_ENTRIES = 32; -type StrandsAttachmentCache = { - objects: WeakMap>; - strings: LRUCache>; -}; +type StrandsAttachmentCache = + | { captureAttachments: false } + | { + captureAttachments: true; + objects: WeakMap>; + strings: LRUCache>; + }; -export class StrandsAgentSDKPlugin extends BasePlugin { +class StrandsAgentSDKInstrumentationConsumer { private readonly activeChildParents: ActiveChildParents = new WeakMap(); - protected onEnable(): void { + public register(): void { this.interceptAgentStream(); this.interceptMultiAgentStream( strandsAgentSDKChannels.graphStream, @@ -111,68 +118,57 @@ export class StrandsAgentSDKPlugin extends BasePlugin { ); } - protected onDisable(): void { - for (const unsubscribe of this.unsubscribers) { - unsubscribe(); - } - this.unsubscribers = []; - } - private interceptAgentStream(): void { - this.unsubscribers.push( - strandsAgentSDKChannels.agentStream.intercept( - (target, thisArg, args, additional) => - instrumentStrandsStreamInvocation< - AgentStreamState, - StrandsAgentStreamEvent, - ReturnType - >({ - finalize: finalizeAgentStream, - handleChunk: handleAgentStreamEvent, - invoke: () => Reflect.apply(target, thisArg, args), - name: "Strands Agent SDK", - start: () => - startAgentStream( - args[0], - extractAgent(additional.agent, thisArg), - this.activeChildParents, - ), - }), - ), - ); - } - - private interceptMultiAgentStream( - channel: MultiAgentStreamChannel, - operation: MultiAgentStreamState["operation"], - ): void { - this.unsubscribers.push( - channel.intercept((target, thisArg, args, additional) => + strandsAgentSDKChannels.agentStream.intercept( + (target, thisArg, args, additional) => instrumentStrandsStreamInvocation< - MultiAgentStreamState, - StrandsMultiAgentStreamEvent, + AgentStreamState, + StrandsAgentStreamEvent, ReturnType >({ - finalize: (state, error, output) => - finalizeMultiAgentStream( - state, - this.activeChildParents, - error, - output, - ), - handleChunk: (state, chunk) => - handleMultiAgentStreamEvent(state, chunk, this.activeChildParents), + finalize: finalizeAgentStream, + handleChunk: handleAgentStreamEvent, invoke: () => Reflect.apply(target, thisArg, args), - name: "Strands multi-agent", + name: "Strands Agent SDK", start: () => - startMultiAgentStream( + startAgentStream( args[0], - extractOrchestrator(additional.orchestrator, thisArg), - operation, + extractAgent(additional.agent, thisArg), this.activeChildParents, ), }), - ), + ); + } + + private interceptMultiAgentStream( + channel: MultiAgentStreamChannel, + operation: MultiAgentStreamState["operation"], + ): void { + channel.intercept((target, thisArg, args, additional) => + instrumentStrandsStreamInvocation< + MultiAgentStreamState, + StrandsMultiAgentStreamEvent, + ReturnType + >({ + finalize: (state, error, output) => + finalizeMultiAgentStream( + state, + this.activeChildParents, + error, + output, + ), + handleChunk: (state, chunk) => + handleMultiAgentStreamEvent(state, chunk, this.activeChildParents), + invoke: () => Reflect.apply(target, thisArg, args), + name: "Strands multi-agent", + start: () => + startMultiAgentStream( + args[0], + extractOrchestrator(additional.orchestrator, thisArg), + operation, + this.activeChildParents, + ), + }), ); } } @@ -246,7 +242,7 @@ function startAgentStream( const parentSpan = agent ? getOnlyChildParent(activeChildParents, agent) : undefined; - const attachmentCache = createStrandsAttachmentCache(); + const attachmentCache = createStrandsAttachmentCache(parentSpan); const processedInput = processStrandsInputAttachments(input, attachmentCache); const span = parentSpan ? withCurrent(parentSpan, () => @@ -788,7 +784,8 @@ function finalizeAgentStream( ...(output !== undefined ? { output } : {}), }); state.span.end(); - state.attachmentCache.strings.clear(); + if (state.attachmentCache.captureAttachments) + state.attachmentCache.strings.clear(); } function finalizeMultiAgentStream( @@ -1013,8 +1010,11 @@ const STRANDS_MEDIA_TYPES: Record = { xml: "application/xml", }; -function createStrandsAttachmentCache(): StrandsAttachmentCache { +function createStrandsAttachmentCache(parent?: Span): StrandsAttachmentCache { + if (!_internalCaptureAttachmentsEnabled(parent)) + return { captureAttachments: false }; return { + captureAttachments: true, objects: new WeakMap(), strings: new LRUCache({ max: MAX_STRANDS_STRING_ATTACHMENT_CACHE_ENTRIES, @@ -1141,35 +1141,44 @@ function createStrandsMediaAttachment( return undefined; } - const contentType = STRANDS_MEDIA_TYPES[format.toLowerCase()]; - if (!contentType) { - return undefined; - } - const filename = - mediaKey === "document" && - typeof media.name === "string" && - media.name.length > 0 - ? media.name - : `${mediaKey}.${format.toLowerCase()}`; - const attachment = getOrCreateStrandsAttachment( - source.bytes, - filename, - contentType, - cache, - ); - if (!attachment) { - return undefined; + let attachment: Attachment | undefined; + if (cache.captureAttachments) { + const contentType = STRANDS_MEDIA_TYPES[format.toLowerCase()]; + if (!contentType) { + return undefined; + } + const filename = + mediaKey === "document" && + typeof media.name === "string" && + media.name.length > 0 + ? media.name + : `${mediaKey}.${format.toLowerCase()}`; + attachment = getOrCreateStrandsAttachment( + source.bytes, + filename, + contentType, + cache, + ); + if (!attachment) { + return undefined; + } } - const { type: _type, ...serializedMedia } = media; - const { type: _sourceType, ...serializedSource } = source; + const { type: _type, source: _source, ...serializedMedia } = media; + const serializedSource = cache.captureAttachments + ? omitMediaData(source, "type") + : omitMediaData(source, "type", "bytes"); return { [mediaKey]: { ...serializedMedia, - source: { - ...serializedSource, - bytes: attachment, - }, + ...(serializedSource || attachment + ? { + source: { + ...serializedSource, + ...(attachment ? { bytes: attachment } : {}), + }, + } + : {}), }, }; } @@ -1178,7 +1187,7 @@ function getOrCreateStrandsAttachment( data: unknown, filename: string, contentType: string, - cache: StrandsAttachmentCache, + cache: Extract, ): Attachment | undefined { const key = `${contentType}\0${filename}`; const attachments = @@ -1363,3 +1372,13 @@ function safeLog(span: Span, event: Parameters[0]): void { function logInstrumentationError(context: string, error: unknown): void { debugLogger.debug(`${context}:`, error); } + +let strandsAgentSDKInstrumentationConsumer: + | StrandsAgentSDKInstrumentationConsumer + | undefined; + +export function registerStrandsAgentSDKInstrumentation(): void { + strandsAgentSDKInstrumentationConsumer ??= + new StrandsAgentSDKInstrumentationConsumer(); + strandsAgentSDKInstrumentationConsumer.register(); +} diff --git a/js/src/instrumentation/plugins/strands-agent-sdk-interceptors.ts b/js/src/instrumentation/providers/strands-agent-sdk-interceptors.ts similarity index 100% rename from js/src/instrumentation/plugins/strands-agent-sdk-interceptors.ts rename to js/src/instrumentation/providers/strands-agent-sdk-interceptors.ts diff --git a/js/src/instrumentation/plugins/typesafe-plugin.ts b/js/src/instrumentation/providers/typesafe-instrumentation.ts similarity index 96% rename from js/src/instrumentation/plugins/typesafe-plugin.ts rename to js/src/instrumentation/providers/typesafe-instrumentation.ts index 894cd46fd..7d4b7571b 100644 --- a/js/src/instrumentation/plugins/typesafe-plugin.ts +++ b/js/src/instrumentation/providers/typesafe-instrumentation.ts @@ -2,8 +2,8 @@ import { isObject } from "../../../util/index"; import { uint8ArrayToBase64 } from "../../../util/bytes"; import { prepareTypedAttachments } from "../../typed-attachments"; import { debugLogger } from "../../debug-logger"; -import { startSpan, withCurrent } from "../../logger"; -import type { Span } from "../../logger"; +import { startSpan, withCurrent } from "../../logger/core"; +import type { Span } from "../../logger/core"; import { INSTRUMENTATION_NAMES, withSpanInstrumentationName, @@ -20,22 +20,13 @@ import { runWithAutoInstrumentationSuppressed, } from "../auto-instrumentation-suppression"; import { isAutoCaptureAttachmentsEnabled } from "../../wrappers/attachment-utils"; -import { BasePlugin } from "../core"; -import { unsubscribeAll } from "../core/tracing"; -import { typeSafeChannels } from "./typesafe-interceptors"; -export class TypeSafePlugin extends BasePlugin { - protected onEnable(): void { - this.unsubscribers.push( - typeSafeChannels.systemOne.intercept((target, thisArg, args) => - interceptSystemOne(target, thisArg, args), - ), - ); - } +import { typeSafeChannels } from "./typesafe-interceptors"; - protected onDisable(): void { - this.unsubscribers = unsubscribeAll(this.unsubscribers); - } +export function registerTypeSafeInstrumentation(): void { + typeSafeChannels.systemOne.intercept((target, thisArg, args) => + interceptSystemOne(target, thisArg, args), + ); } function interceptSystemOne( diff --git a/js/src/instrumentation/plugins/typesafe-interceptors.ts b/js/src/instrumentation/providers/typesafe-interceptors.ts similarity index 100% rename from js/src/instrumentation/plugins/typesafe-interceptors.ts rename to js/src/instrumentation/providers/typesafe-interceptors.ts diff --git a/js/src/instrumentation/plugins/voyageai-plugin.test.ts b/js/src/instrumentation/providers/voyageai-instrumentation.test.ts similarity index 95% rename from js/src/instrumentation/plugins/voyageai-plugin.test.ts rename to js/src/instrumentation/providers/voyageai-instrumentation.test.ts index 248dd39db..274d7e078 100644 --- a/js/src/instrumentation/plugins/voyageai-plugin.test.ts +++ b/js/src/instrumentation/providers/voyageai-instrumentation.test.ts @@ -1,15 +1,25 @@ -import { afterEach, beforeAll, beforeEach, describe, expect, it } from "vitest"; -import { Attachment, _exportsForTestingOnly, initLogger } from "../../logger"; +import { registerInstrumentation } from "../registrations/voyageai"; +import { + afterEach, + beforeAll, + beforeEach, + describe, + expect, + it, +} from "vite-plus/test"; +import { Attachment, initLogger } from "../../logger/core"; +import { _exportsForTestingOnly } from "../../logger/testing"; import { configureNode } from "../../node/config"; import { voyageAIChannels } from "./voyageai-interceptors"; try { configureNode(); + registerInstrumentation(); } catch { // Best-effort initialization for test environments. } -describe("VoyageAIPlugin", () => { +describe("registerVoyageAIInstrumentation", () => { let backgroundLogger: ReturnType< typeof _exportsForTestingOnly.useTestBackgroundLogger >; @@ -21,7 +31,8 @@ describe("VoyageAIPlugin", () => { beforeEach(() => { backgroundLogger = _exportsForTestingOnly.useTestBackgroundLogger(); initLogger({ - projectName: "voyageai-plugin.test.ts", + autoCaptureAttachments: true, + projectName: "voyageai-instrumentation.test.ts", projectId: "test-project-id", }); }); diff --git a/js/src/instrumentation/plugins/voyageai-plugin.ts b/js/src/instrumentation/providers/voyageai-instrumentation.ts similarity index 83% rename from js/src/instrumentation/plugins/voyageai-plugin.ts rename to js/src/instrumentation/providers/voyageai-instrumentation.ts index a3cc692de..19ed1e92a 100644 --- a/js/src/instrumentation/plugins/voyageai-plugin.ts +++ b/js/src/instrumentation/providers/voyageai-instrumentation.ts @@ -1,7 +1,7 @@ import { SpanTypeAttribute, isObject } from "../../../util/index"; import { debugLogger } from "../../debug-logger"; -import { startSpan, withCurrent } from "../../logger"; -import type { Span } from "../../logger"; +import { startSpan, withCurrent } from "../../logger/core"; +import type { Span } from "../../logger/core"; import { INSTRUMENTATION_NAMES, withSpanInstrumentationName, @@ -17,8 +17,6 @@ import { isAutoInstrumentationSuppressed, runWithAutoInstrumentationSuppressed, } from "../auto-instrumentation-suppression"; -import { BasePlugin } from "../core"; -import { unsubscribeAll } from "../core/tracing"; import { voyageAIChannels } from "./voyageai-interceptors"; const RERANK_METADATA_ALLOWLIST = new Set([ @@ -28,56 +26,48 @@ const RERANK_METADATA_ALLOWLIST = new Set([ "truncation", ]); -export class VoyageAIPlugin extends BasePlugin { - protected onEnable(): void { - this.unsubscribers.push( - voyageAIChannels.embed.intercept((target, receiver, args, additional) => - traceVoyageAICall( - () => Reflect.apply(target, receiver, args), - { arguments: args, self: receiver, additional }, - "voyageai.embed", - extractTextEmbeddingInput, - summarizeEmbeddingOutput, - extractEmbeddingUsageMetrics, - ), - ), - voyageAIChannels.multimodalEmbed.intercept( - (target, receiver, args, additional) => - traceVoyageAICall( - () => Reflect.apply(target, receiver, args), - { arguments: args, self: receiver, additional }, - "voyageai.multimodalEmbed", - extractMultimodalEmbeddingInput, - summarizeEmbeddingOutput, - extractEmbeddingUsageMetrics, - ), - ), - voyageAIChannels.rerank.intercept((target, receiver, args, additional) => - traceVoyageAICall( - () => Reflect.apply(target, receiver, args), - { arguments: args, self: receiver, additional }, - "voyageai.rerank", - extractRerankInput, - summarizeRerankOutput, - ), +export function registerVoyageAIInstrumentation(): void { + voyageAIChannels.embed.intercept((target, receiver, args, additional) => + traceVoyageAICall( + () => Reflect.apply(target, receiver, args), + { arguments: args, self: receiver, additional }, + "voyageai.embed", + extractTextEmbeddingInput, + summarizeEmbeddingOutput, + extractEmbeddingUsageMetrics, + ), + ); + voyageAIChannels.multimodalEmbed.intercept( + (target, receiver, args, additional) => + traceVoyageAICall( + () => Reflect.apply(target, receiver, args), + { arguments: args, self: receiver, additional }, + "voyageai.multimodalEmbed", + extractMultimodalEmbeddingInput, + summarizeEmbeddingOutput, + extractEmbeddingUsageMetrics, ), - voyageAIChannels.contextualizedEmbed.intercept( - (target, receiver, args, additional) => - traceVoyageAICall( - () => Reflect.apply(target, receiver, args), - { arguments: args, self: receiver, additional }, - "voyageai.contextualizedEmbed", - extractContextualizedEmbeddingInput, - summarizeContextualizedEmbeddingOutput, - extractEmbeddingUsageMetrics, - ), + ); + voyageAIChannels.rerank.intercept((target, receiver, args, additional) => + traceVoyageAICall( + () => Reflect.apply(target, receiver, args), + { arguments: args, self: receiver, additional }, + "voyageai.rerank", + extractRerankInput, + summarizeRerankOutput, + ), + ); + voyageAIChannels.contextualizedEmbed.intercept( + (target, receiver, args, additional) => + traceVoyageAICall( + () => Reflect.apply(target, receiver, args), + { arguments: args, self: receiver, additional }, + "voyageai.contextualizedEmbed", + extractContextualizedEmbeddingInput, + summarizeContextualizedEmbeddingOutput, + extractEmbeddingUsageMetrics, ), - ); - } - - protected onDisable(): void { - this.unsubscribers = unsubscribeAll(this.unsubscribers); - } + ); } type VoyageAIResult = @@ -456,13 +446,10 @@ function extractUsageMetrics(result: VoyageAIResult): Record { const rawResponse = isObject(result.rawResponse) ? result.rawResponse : undefined; - const usage = ( - isObject(result.usage) - ? result.usage - : isObject(rawResponse?.usage) - ? rawResponse.usage - : undefined - ) as VoyageAIUsage | undefined; + const usageValue = isObject(result.usage) ? result.usage : rawResponse?.usage; + const usage = isObject(usageValue) + ? (usageValue as VoyageAIUsage) + : undefined; const tokens = typeof result.totalTokens === "number" ? result.totalTokens diff --git a/js/src/instrumentation/plugins/voyageai-interceptors.ts b/js/src/instrumentation/providers/voyageai-interceptors.ts similarity index 100% rename from js/src/instrumentation/plugins/voyageai-interceptors.ts rename to js/src/instrumentation/providers/voyageai-interceptors.ts diff --git a/js/src/instrumentation/plugins/workers-ai-interceptors.ts b/js/src/instrumentation/providers/workers-ai-interceptors.ts similarity index 100% rename from js/src/instrumentation/plugins/workers-ai-interceptors.ts rename to js/src/instrumentation/providers/workers-ai-interceptors.ts diff --git a/js/src/instrumentation/plugins/workers-ai-plugin.ts b/js/src/instrumentation/providers/workers-ai-plugin.ts similarity index 71% rename from js/src/instrumentation/plugins/workers-ai-plugin.ts rename to js/src/instrumentation/providers/workers-ai-plugin.ts index c06686337..639c73773 100644 --- a/js/src/instrumentation/plugins/workers-ai-plugin.ts +++ b/js/src/instrumentation/providers/workers-ai-plugin.ts @@ -5,8 +5,8 @@ import type { WorkersAIInput, WorkersAIResult, } from "../../vendor-sdk-types/workers-ai"; -import { BasePlugin } from "../core"; -import { traceAsyncCall, unsubscribeAll } from "../core/tracing"; + +import { traceAsyncCall } from "../core/tracing"; import { workersAIChannels } from "./workers-ai-interceptors"; const DECISION_MODELS = new Set([ @@ -94,57 +94,47 @@ function extractWorkersAIExtraction(result: WorkersAIResult): unknown { }; } -export class WorkersAIPlugin extends BasePlugin { - protected onEnable(): void { - this.unsubscribers.push( - workersAIChannels.run.intercept((target, receiver, args, additional) => { - const extraction = args[0] === "@cf/cloudflare/clairic"; - return traceAsyncCall( - () => Reflect.apply(target, receiver, args), - { ...additional, arguments: args, self: receiver }, - { - name: "workers_ai.run", - type: extraction ? "extraction" : "decision", - instrumentationName: INSTRUMENTATION_NAMES.WORKERS_AI, - // These models return JSON directly. Do not consume raw bodies or - // claim to trace an asynchronous submission or unsupported stream. - shouldTrace: () => - (extraction || DECISION_MODELS.has(args[0])) && - !args[1]?.stream && - !args[2]?.returnRawResponse && - !args[2]?.websocket && - !args[2]?.queueRequest, - extractInput: ([model, input]) => ({ - input: prepareWorkersAIInput(input, extraction), - metadata: { - model, - provider: "cloudflare", - operation: extraction ? "extraction" : "decision", - }, - }), - extractOutput: (result) => - extraction - ? extractWorkersAIExtraction(result) - : { - answers: Object.entries(result.answers ?? {}).map( - ([id, answer]) => ({ ...answer, id }), - ), - }, - extractMetadata: (result) => ({ - ...(typeof result.model === "string" - ? { model: result.model } - : {}), - }), - extractMetrics: extractWorkersAIUsage, +export function registerWorkersAIInstrumentation(): void { + workersAIChannels.run.intercept((target, receiver, args, additional) => { + const extraction = args[0] === "@cf/cloudflare/clairic"; + return traceAsyncCall( + () => Reflect.apply(target, receiver, args), + { ...additional, arguments: args, self: receiver }, + { + name: "workers_ai.run", + type: extraction ? "extraction" : "decision", + instrumentationName: INSTRUMENTATION_NAMES.WORKERS_AI, + // These models return JSON directly. Do not consume raw bodies or + // claim to trace an asynchronous submission or unsupported stream. + shouldTrace: () => + (extraction || DECISION_MODELS.has(args[0])) && + !args[1]?.stream && + !args[2]?.returnRawResponse && + !args[2]?.websocket && + !args[2]?.queueRequest, + extractInput: ([model, input]) => ({ + input: prepareWorkersAIInput(input, extraction), + metadata: { + model, + provider: "cloudflare", + operation: extraction ? "extraction" : "decision", }, - ); - }), + }), + extractOutput: (result) => + extraction + ? extractWorkersAIExtraction(result) + : { + answers: Object.entries(result.answers ?? {}).map( + ([id, answer]) => ({ ...answer, id }), + ), + }, + extractMetadata: (result) => ({ + ...(typeof result.model === "string" ? { model: result.model } : {}), + }), + extractMetrics: extractWorkersAIUsage, + }, ); - } - - protected onDisable(): void { - this.unsubscribers = unsubscribeAll(this.unsubscribers); - } + }); } function prepareWorkersAIInput( diff --git a/js/src/instrumentation/registrations/aisdk.ts b/js/src/instrumentation/registrations/aisdk.ts new file mode 100644 index 000000000..410929c81 --- /dev/null +++ b/js/src/instrumentation/registrations/aisdk.ts @@ -0,0 +1,9 @@ +import { registerAISDKInstrumentation } from "../providers/ai-sdk-instrumentation"; +import { registry } from "../registry"; + +/** Enable this provider once across compatible SDK copies. */ +export function registerInstrumentation(): void { + registry.register("aisdk", () => { + registerAISDKInstrumentation(); + }); +} diff --git a/js/src/instrumentation/registrations/anthropic.ts b/js/src/instrumentation/registrations/anthropic.ts new file mode 100644 index 000000000..ca1165dad --- /dev/null +++ b/js/src/instrumentation/registrations/anthropic.ts @@ -0,0 +1,9 @@ +import { registerAnthropicInstrumentation } from "../providers/anthropic-instrumentation"; +import { registry } from "../registry"; + +/** Enable this provider once across compatible SDK copies. */ +export function registerInstrumentation(): void { + registry.register("anthropic", () => { + registerAnthropicInstrumentation(); + }); +} diff --git a/js/src/instrumentation/registrations/awsBedrockRuntime.ts b/js/src/instrumentation/registrations/awsBedrockRuntime.ts new file mode 100644 index 000000000..d79a7b144 --- /dev/null +++ b/js/src/instrumentation/registrations/awsBedrockRuntime.ts @@ -0,0 +1,9 @@ +import { registerBedrockRuntimeInstrumentation } from "../providers/bedrock-runtime-instrumentation"; +import { registry } from "../registry"; + +/** Enable this provider once across compatible SDK copies. */ +export function registerInstrumentation(): void { + registry.register("awsBedrockRuntime", () => { + registerBedrockRuntimeInstrumentation(); + }); +} diff --git a/js/src/instrumentation/registrations/claudeAgentSDK.ts b/js/src/instrumentation/registrations/claudeAgentSDK.ts new file mode 100644 index 000000000..36eacf248 --- /dev/null +++ b/js/src/instrumentation/registrations/claudeAgentSDK.ts @@ -0,0 +1,9 @@ +import { registerClaudeAgentSDKInstrumentation } from "../providers/claude-agent-sdk-instrumentation"; +import { registry } from "../registry"; + +/** Enable this provider once across compatible SDK copies. */ +export function registerInstrumentation(): void { + registry.register("claudeAgentSDK", () => { + registerClaudeAgentSDKInstrumentation(); + }); +} diff --git a/js/src/instrumentation/registrations/cloudflareAIChat.ts b/js/src/instrumentation/registrations/cloudflareAIChat.ts new file mode 100644 index 000000000..81eae531f --- /dev/null +++ b/js/src/instrumentation/registrations/cloudflareAIChat.ts @@ -0,0 +1,9 @@ +import { registerCloudflareAIChatInstrumentation } from "../providers/cloudflare-ai-chat-consumer"; +import { registry } from "../registry"; + +/** Enable this provider once across compatible SDK copies. */ +export function registerInstrumentation(): void { + registry.register("cloudflareAIChat", () => { + registerCloudflareAIChatInstrumentation(); + }); +} diff --git a/js/src/instrumentation/registrations/cloudflareAgents.ts b/js/src/instrumentation/registrations/cloudflareAgents.ts new file mode 100644 index 000000000..91125edaf --- /dev/null +++ b/js/src/instrumentation/registrations/cloudflareAgents.ts @@ -0,0 +1,9 @@ +import { registerCloudflareAgentsInstrumentation } from "../providers/cloudflare-agents-instrumentation"; +import { registry } from "../registry"; + +/** Enable this provider once across compatible SDK copies. */ +export function registerInstrumentation(): void { + registry.register("cloudflareAgents", () => { + registerCloudflareAgentsInstrumentation(); + }); +} diff --git a/js/src/instrumentation/registrations/cloudflareThink.ts b/js/src/instrumentation/registrations/cloudflareThink.ts new file mode 100644 index 000000000..0239af4b8 --- /dev/null +++ b/js/src/instrumentation/registrations/cloudflareThink.ts @@ -0,0 +1,9 @@ +import { registerCloudflareThinkInstrumentation } from "../providers/cloudflare-think-instrumentation"; +import { registry } from "../registry"; + +/** Enable this provider once across compatible SDK copies. */ +export function registerInstrumentation(): void { + registry.register("cloudflareThink", () => { + registerCloudflareThinkInstrumentation(); + }); +} diff --git a/js/src/instrumentation/registrations/cohere.ts b/js/src/instrumentation/registrations/cohere.ts new file mode 100644 index 000000000..68624c09e --- /dev/null +++ b/js/src/instrumentation/registrations/cohere.ts @@ -0,0 +1,9 @@ +import { registerCohereInstrumentation } from "../providers/cohere-instrumentation"; +import { registry } from "../registry"; + +/** Enable this provider once across compatible SDK copies. */ +export function registerInstrumentation(): void { + registry.register("cohere", () => { + registerCohereInstrumentation(); + }); +} diff --git a/js/src/instrumentation/registrations/cursorSDK.ts b/js/src/instrumentation/registrations/cursorSDK.ts new file mode 100644 index 000000000..116b94a7e --- /dev/null +++ b/js/src/instrumentation/registrations/cursorSDK.ts @@ -0,0 +1,9 @@ +import { registerCursorSDKInstrumentation } from "../providers/cursor-sdk-instrumentation"; +import { registry } from "../registry"; + +/** Enable this provider once across compatible SDK copies. */ +export function registerInstrumentation(): void { + registry.register("cursorSDK", () => { + registerCursorSDKInstrumentation(); + }); +} diff --git a/js/src/instrumentation/registrations/elevenlabs.ts b/js/src/instrumentation/registrations/elevenlabs.ts new file mode 100644 index 000000000..f6180fb0f --- /dev/null +++ b/js/src/instrumentation/registrations/elevenlabs.ts @@ -0,0 +1,9 @@ +import { registerElevenLabsInstrumentation } from "../providers/elevenlabs-instrumentation"; +import { registry } from "../registry"; + +/** Enable this provider once across compatible SDK copies. */ +export function registerInstrumentation(): void { + registry.register("elevenlabs", () => { + registerElevenLabsInstrumentation(); + }); +} diff --git a/js/src/instrumentation/registrations/genkit.ts b/js/src/instrumentation/registrations/genkit.ts new file mode 100644 index 000000000..673253430 --- /dev/null +++ b/js/src/instrumentation/registrations/genkit.ts @@ -0,0 +1,9 @@ +import { registerGenkitInstrumentation } from "../providers/genkit-instrumentation"; +import { registry } from "../registry"; + +/** Enable this provider once across compatible SDK copies. */ +export function registerInstrumentation(): void { + registry.register("genkit", () => { + registerGenkitInstrumentation(); + }); +} diff --git a/js/src/instrumentation/registrations/gitHubCopilot.ts b/js/src/instrumentation/registrations/gitHubCopilot.ts new file mode 100644 index 000000000..9e247c50d --- /dev/null +++ b/js/src/instrumentation/registrations/gitHubCopilot.ts @@ -0,0 +1,9 @@ +import { registerGitHubCopilotInstrumentation } from "../providers/github-copilot-instrumentation"; +import { registry } from "../registry"; + +/** Enable this provider once across compatible SDK copies. */ +export function registerInstrumentation(): void { + registry.register("gitHubCopilot", () => { + registerGitHubCopilotInstrumentation(); + }); +} diff --git a/js/src/instrumentation/registrations/googleADK.ts b/js/src/instrumentation/registrations/googleADK.ts new file mode 100644 index 000000000..6704bd84b --- /dev/null +++ b/js/src/instrumentation/registrations/googleADK.ts @@ -0,0 +1,9 @@ +import { registerGoogleADKInstrumentation } from "../providers/google-adk-instrumentation"; +import { registry } from "../registry"; + +/** Enable this provider once across compatible SDK copies. */ +export function registerInstrumentation(): void { + registry.register("googleADK", () => { + registerGoogleADKInstrumentation(); + }); +} diff --git a/js/src/instrumentation/registrations/googleGenAI.ts b/js/src/instrumentation/registrations/googleGenAI.ts new file mode 100644 index 000000000..441da2c35 --- /dev/null +++ b/js/src/instrumentation/registrations/googleGenAI.ts @@ -0,0 +1,9 @@ +import { registerGoogleGenAIInstrumentation } from "../providers/google-genai-instrumentation"; +import { registry } from "../registry"; + +/** Enable this provider once across compatible SDK copies. */ +export function registerInstrumentation(): void { + registry.register("googleGenAI", () => { + registerGoogleGenAIInstrumentation(); + }); +} diff --git a/js/src/instrumentation/registrations/googleGenerativeAI.ts b/js/src/instrumentation/registrations/googleGenerativeAI.ts new file mode 100644 index 000000000..ffceccbfd --- /dev/null +++ b/js/src/instrumentation/registrations/googleGenerativeAI.ts @@ -0,0 +1,9 @@ +import { registerGoogleGenerativeAIInstrumentation } from "../providers/google-generative-ai-instrumentation"; +import { registry } from "../registry"; + +/** Enable this provider once across compatible SDK copies. */ +export function registerInstrumentation(): void { + registry.register("googleGenerativeAI", () => { + registerGoogleGenerativeAIInstrumentation(); + }); +} diff --git a/js/src/instrumentation/registrations/groq.ts b/js/src/instrumentation/registrations/groq.ts new file mode 100644 index 000000000..6acdab20d --- /dev/null +++ b/js/src/instrumentation/registrations/groq.ts @@ -0,0 +1,9 @@ +import { registerGroqInstrumentation } from "../providers/groq-instrumentation"; +import { registry } from "../registry"; + +/** Enable this provider once across compatible SDK copies. */ +export function registerInstrumentation(): void { + registry.register("groq", () => { + registerGroqInstrumentation(); + }); +} diff --git a/js/src/instrumentation/registrations/huggingface.ts b/js/src/instrumentation/registrations/huggingface.ts new file mode 100644 index 000000000..74eee8a31 --- /dev/null +++ b/js/src/instrumentation/registrations/huggingface.ts @@ -0,0 +1,11 @@ +import { registerHuggingFaceInstrumentation } from "../providers/huggingface-instrumentation"; +import { registerHuggingFaceTransformersInstrumentation } from "../providers/huggingface-transformers-instrumentation"; +import { registry } from "../registry"; + +/** Enable this provider once across compatible SDK copies. */ +export function registerInstrumentation(): void { + registry.register("huggingface", () => { + registerHuggingFaceInstrumentation(); + registerHuggingFaceTransformersInstrumentation(); + }); +} diff --git a/js/src/instrumentation/registrations/langchain.ts b/js/src/instrumentation/registrations/langchain.ts new file mode 100644 index 000000000..3a9b6d4df --- /dev/null +++ b/js/src/instrumentation/registrations/langchain.ts @@ -0,0 +1,9 @@ +import { registerLangChainInstrumentation } from "../providers/langchain-instrumentation"; +import { registry } from "../registry"; + +/** Enable this provider once across compatible SDK copies. */ +export function registerInstrumentation(): void { + registry.register("langchain", () => { + registerLangChainInstrumentation(); + }); +} diff --git a/js/src/instrumentation/registrations/langgraphSDK.ts b/js/src/instrumentation/registrations/langgraphSDK.ts new file mode 100644 index 000000000..38a2190c2 --- /dev/null +++ b/js/src/instrumentation/registrations/langgraphSDK.ts @@ -0,0 +1,9 @@ +import { registerLangGraphSDKInstrumentation } from "../providers/langgraph-sdk-instrumentation"; +import { registry } from "../registry"; + +/** Enable this provider once across compatible SDK copies. */ +export function registerInstrumentation(): void { + registry.register("langgraphSDK", () => { + registerLangGraphSDKInstrumentation(); + }); +} diff --git a/js/src/instrumentation/registrations/langsmith.ts b/js/src/instrumentation/registrations/langsmith.ts new file mode 100644 index 000000000..7de06fbd8 --- /dev/null +++ b/js/src/instrumentation/registrations/langsmith.ts @@ -0,0 +1,11 @@ +import { registerLangSmithInstrumentation } from "../providers/langsmith-instrumentation"; +import { registry } from "../registry"; + +/** Enable this provider once across compatible SDK copies. */ +export function registerInstrumentation(): void { + registry.register("langsmith", () => { + registerLangSmithInstrumentation({ + skipLangChainRuns: () => registry.isRegistered("langchain"), + }); + }); +} diff --git a/js/src/instrumentation/registrations/livekit.ts b/js/src/instrumentation/registrations/livekit.ts new file mode 100644 index 000000000..0cccd7804 --- /dev/null +++ b/js/src/instrumentation/registrations/livekit.ts @@ -0,0 +1,14 @@ +import { registerLiveKitInstrumentation } from "../providers/livekit-plugin"; +import { registry } from "../registry"; + +/** Enable this provider once across compatible SDK copies. */ +export function registerInstrumentation(): void { + registry.register("livekit", (integrations, config) => { + registerLiveKitInstrumentation( + config.streamingAttachmentBufferBytes, + typeof integrations.livekit === "object" + ? integrations.livekit.audio + : undefined, + ); + }); +} diff --git a/js/src/instrumentation/registrations/mastra.ts b/js/src/instrumentation/registrations/mastra.ts new file mode 100644 index 000000000..e909f167f --- /dev/null +++ b/js/src/instrumentation/registrations/mastra.ts @@ -0,0 +1,9 @@ +import { registry } from "../registry"; +import { installMastraExporterFactory } from "../../auto-instrumentations/loader/mastra-observability-patch"; +import { BraintrustObservabilityExporter } from "../../wrappers/mastra"; + +export function registerInstrumentation(): void { + registry.register("mastra", () => { + installMastraExporterFactory(() => new BraintrustObservabilityExporter()); + }); +} diff --git a/js/src/instrumentation/registrations/mcp.ts b/js/src/instrumentation/registrations/mcp.ts new file mode 100644 index 000000000..1527e5291 --- /dev/null +++ b/js/src/instrumentation/registrations/mcp.ts @@ -0,0 +1,11 @@ +import { registerMCPInstrumentation } from "../providers/mcp-plugin"; +import { registry } from "../registry"; + +/** Enable this provider once across compatible SDK copies. */ +export function registerInstrumentation(): void { + registry.register("mcp", (integrations) => { + registerMCPInstrumentation( + typeof integrations.mcp === "object" ? integrations.mcp : {}, + ); + }); +} diff --git a/js/src/instrumentation/registrations/mistral.ts b/js/src/instrumentation/registrations/mistral.ts new file mode 100644 index 000000000..b1419a1d2 --- /dev/null +++ b/js/src/instrumentation/registrations/mistral.ts @@ -0,0 +1,9 @@ +import { registerMistralInstrumentation } from "../providers/mistral-instrumentation"; +import { registry } from "../registry"; + +/** Enable this provider once across compatible SDK copies. */ +export function registerInstrumentation(): void { + registry.register("mistral", () => { + registerMistralInstrumentation(); + }); +} diff --git a/js/src/instrumentation/registrations/ollama.ts b/js/src/instrumentation/registrations/ollama.ts new file mode 100644 index 000000000..ff558f6e5 --- /dev/null +++ b/js/src/instrumentation/registrations/ollama.ts @@ -0,0 +1,9 @@ +import { registerOllamaInstrumentation } from "../providers/ollama-instrumentation"; +import { registry } from "../registry"; + +/** Enable this provider once across compatible SDK copies. */ +export function registerInstrumentation(): void { + registry.register("ollama", () => { + registerOllamaInstrumentation(); + }); +} diff --git a/js/src/instrumentation/registrations/openAIAgents.ts b/js/src/instrumentation/registrations/openAIAgents.ts new file mode 100644 index 000000000..601ff2061 --- /dev/null +++ b/js/src/instrumentation/registrations/openAIAgents.ts @@ -0,0 +1,9 @@ +import { registerOpenAIAgentsInstrumentation } from "../providers/openai-agents-instrumentation"; +import { registry } from "../registry"; + +/** Enable this provider once across compatible SDK copies. */ +export function registerInstrumentation(): void { + registry.register("openAIAgents", () => { + registerOpenAIAgentsInstrumentation(); + }); +} diff --git a/js/src/instrumentation/registrations/openai.ts b/js/src/instrumentation/registrations/openai.ts new file mode 100644 index 000000000..fcd7f2549 --- /dev/null +++ b/js/src/instrumentation/registrations/openai.ts @@ -0,0 +1,9 @@ +import { registerOpenAIInstrumentation } from "../providers/openai-instrumentation"; +import { registry } from "../registry"; + +/** Enable this provider once across compatible SDK copies. */ +export function registerInstrumentation(): void { + registry.register("openai", () => { + registerOpenAIInstrumentation(); + }); +} diff --git a/js/src/instrumentation/registrations/openaiCodexSDK.ts b/js/src/instrumentation/registrations/openaiCodexSDK.ts new file mode 100644 index 000000000..965ea72de --- /dev/null +++ b/js/src/instrumentation/registrations/openaiCodexSDK.ts @@ -0,0 +1,9 @@ +import { registerOpenAICodexInstrumentation } from "../providers/openai-codex-instrumentation"; +import { registry } from "../registry"; + +/** Enable this provider once across compatible SDK copies. */ +export function registerInstrumentation(): void { + registry.register("openaiCodexSDK", () => { + registerOpenAICodexInstrumentation(); + }); +} diff --git a/js/src/instrumentation/registrations/openrouter.ts b/js/src/instrumentation/registrations/openrouter.ts new file mode 100644 index 000000000..55cc46c86 --- /dev/null +++ b/js/src/instrumentation/registrations/openrouter.ts @@ -0,0 +1,9 @@ +import { registerOpenRouterInstrumentation } from "../providers/openrouter-instrumentation"; +import { registry } from "../registry"; + +/** Enable this provider once across compatible SDK copies. */ +export function registerInstrumentation(): void { + registry.register("openrouter", () => { + registerOpenRouterInstrumentation(); + }); +} diff --git a/js/src/instrumentation/registrations/openrouterAgent.ts b/js/src/instrumentation/registrations/openrouterAgent.ts new file mode 100644 index 000000000..cafc44873 --- /dev/null +++ b/js/src/instrumentation/registrations/openrouterAgent.ts @@ -0,0 +1,9 @@ +import { registerOpenRouterAgentInstrumentation } from "../providers/openrouter-agent-instrumentation"; +import { registry } from "../registry"; + +/** Enable this provider once across compatible SDK copies. */ +export function registerInstrumentation(): void { + registry.register("openrouterAgent", () => { + registerOpenRouterAgentInstrumentation(); + }); +} diff --git a/js/src/instrumentation/registrations/piCodingAgent.ts b/js/src/instrumentation/registrations/piCodingAgent.ts new file mode 100644 index 000000000..d3f15a3b8 --- /dev/null +++ b/js/src/instrumentation/registrations/piCodingAgent.ts @@ -0,0 +1,9 @@ +import { registerPiCodingAgentInstrumentation } from "../providers/pi-coding-agent-instrumentation"; +import { registry } from "../registry"; + +/** Enable this provider once across compatible SDK copies. */ +export function registerInstrumentation(): void { + registry.register("piCodingAgent", () => { + registerPiCodingAgentInstrumentation(); + }); +} diff --git a/js/src/instrumentation/registrations/strandsAgentSDK.ts b/js/src/instrumentation/registrations/strandsAgentSDK.ts new file mode 100644 index 000000000..f149be10d --- /dev/null +++ b/js/src/instrumentation/registrations/strandsAgentSDK.ts @@ -0,0 +1,9 @@ +import { registerStrandsAgentSDKInstrumentation } from "../providers/strands-agent-sdk-instrumentation"; +import { registry } from "../registry"; + +/** Enable this provider once across compatible SDK copies. */ +export function registerInstrumentation(): void { + registry.register("strandsAgentSDK", () => { + registerStrandsAgentSDKInstrumentation(); + }); +} diff --git a/js/src/instrumentation/registrations/typesafe.ts b/js/src/instrumentation/registrations/typesafe.ts new file mode 100644 index 000000000..20752f2d8 --- /dev/null +++ b/js/src/instrumentation/registrations/typesafe.ts @@ -0,0 +1,9 @@ +import { registerTypeSafeInstrumentation } from "../providers/typesafe-instrumentation"; +import { registry } from "../registry"; + +/** Enable this provider once across compatible SDK copies. */ +export function registerInstrumentation(): void { + registry.register("typesafe", () => { + registerTypeSafeInstrumentation(); + }); +} diff --git a/js/src/instrumentation/registrations/voyageai.ts b/js/src/instrumentation/registrations/voyageai.ts new file mode 100644 index 000000000..565bbd9cf --- /dev/null +++ b/js/src/instrumentation/registrations/voyageai.ts @@ -0,0 +1,9 @@ +import { registerVoyageAIInstrumentation } from "../providers/voyageai-instrumentation"; +import { registry } from "../registry"; + +/** Enable this provider once across compatible SDK copies. */ +export function registerInstrumentation(): void { + registry.register("voyageai", () => { + registerVoyageAIInstrumentation(); + }); +} diff --git a/js/src/instrumentation/registrations/workersAI.ts b/js/src/instrumentation/registrations/workersAI.ts new file mode 100644 index 000000000..69fd035af --- /dev/null +++ b/js/src/instrumentation/registrations/workersAI.ts @@ -0,0 +1,9 @@ +import { registerWorkersAIInstrumentation } from "../providers/workers-ai-plugin"; +import { registry } from "../registry"; + +/** Enable this provider once across compatible SDK copies. */ +export function registerInstrumentation(): void { + registry.register("workersAI", () => { + registerWorkersAIInstrumentation(); + }); +} diff --git a/js/src/instrumentation/registry.test.ts b/js/src/instrumentation/registry.test.ts index 7ac402482..e6caf37e2 100644 --- a/js/src/instrumentation/registry.test.ts +++ b/js/src/instrumentation/registry.test.ts @@ -1,405 +1,89 @@ -import { afterEach, beforeEach, describe, expect, it, vi } from "vitest"; -import { newGlobalInvocationHook } from "../global-instrumentation-hooks"; -import { debugLogger } from "../debug-logger"; -vi.mock("../global-instrumentation-hooks", async (importOriginal) => ({ - ...(await importOriginal()), - newGlobalInvocationHook: vi.fn(), -})); - -// Mock platform context independently of invocation hooks. -vi.mock("../isomorph", () => ({ - default: { - newAsyncLocalStorage: vi.fn(() => ({ - getStore: vi.fn(() => undefined), - run: vi.fn((_store: unknown, callback: () => unknown) => callback()), - })), - getEnv: vi.fn(() => undefined), - buildType: "node", - }, -})); - +import { + afterEach, + beforeEach, + describe, + expect, + it, + vi, +} from "vite-plus/test"; +import iso from "#braintrust-platform"; +import { GLOBAL_INSTRUMENTATION_HOOKS_PROTOCOL_VERSION } from "../global-instrumentation-hooks"; import { configureInstrumentation, registry } from "./registry"; -import { LiveKitPlugin } from "./plugins/livekit-plugin"; +const registryKey = Symbol.for( + `braintrust.registry.global-hooks.v${GLOBAL_INSTRUMENTATION_HOOKS_PROTOCOL_VERSION}`, +); + +beforeEach(() => { + delete (globalThis as Record)[registryKey]; +}); afterEach(() => { - delete (globalThis as Record)[ - Symbol.for("braintrust.instrumentation.config") - ]; + vi.restoreAllMocks(); + delete (globalThis as Record)[registryKey]; }); -const mockNewInvocationHook = newGlobalInvocationHook as ReturnType< - typeof vi.fn ->; - -describe("Plugin Registry", () => { - beforeEach(() => { - // Setup mock channel - const mockChannel = { - intercept: vi.fn(() => vi.fn()), - unintercept: vi.fn(() => vi.fn()), - hasSubscribers: false, - }; - mockNewInvocationHook.mockReturnValue(mockChannel); - }); - - // Clean up after each test - afterEach(() => { - registry.disable(); - }); - - it("should not be enabled by default", () => { - const testRegistry = new (registry.constructor as any)(); - expect(testRegistry.isEnabled()).toBe(false); - }); - - it("should enable plugins when enable() is called", () => { - const testRegistry = new (registry.constructor as any)(); - testRegistry.enable(); - expect(testRegistry.isEnabled()).toBe(true); - testRegistry.disable(); - }); - - it("should be idempotent (calling enable() multiple times)", () => { - const testRegistry = new (registry.constructor as any)(); - testRegistry.enable(); - testRegistry.enable(); // Should not throw - expect(testRegistry.isEnabled()).toBe(true); - testRegistry.disable(); - }); - - it("should block a second instance from subscribing when another is already enabled", () => { - // Regression test for BT-5139: when the SDK is loaded from two different - // module paths in the same process, each gets its own PluginRegistry - // instance. Without cross-instance deduplication, both would subscribe to - // the same global hook, causing every OpenAI call to produce two - // LLM spans. - // - // The dedup mechanism checks globalThis[Symbol.for("braintrust-state")], - // which is the shared state object that all SDK instances reuse (see - // _internalSetInitialState in logger.ts). We simulate that here. - const sharedState = {}; - const stateKey = Symbol.for("braintrust-state"); - (globalThis as any)[stateKey] = sharedState; - - const instanceA = new (registry.constructor as any)(); - const instanceB = new (registry.constructor as any)(); - - try { - instanceA.enable(); - expect(instanceA.isEnabled()).toBe(true); - - // instanceB should be blocked — the hook is already subscribed - instanceB.enable(); - expect(instanceB.isEnabled()).toBe(false); - } finally { - instanceA.disable(); - instanceB.disable(); - delete (globalThis as any)[stateKey]; - } - }); - - it("should ignore deduplication markers from older transports", () => { - const stateKey = Symbol.for("braintrust-state"); - (globalThis as any)[stateKey] = { - [Symbol.for("braintrust.registry")]: {}, - }; - const testRegistry = new (registry.constructor as any)(); - - try { - testRegistry.enable(); - expect(testRegistry.isEnabled()).toBe(true); - } finally { - testRegistry.disable(); - delete (globalThis as any)[stateKey]; - } - }); - - it("should warn if configureInstrumentation is called after enable", () => { - const testRegistry = new (registry.constructor as any)(); - const warnSpy = vi - .spyOn(debugLogger.notice, "warn") - .mockImplementation(() => true); - - try { - testRegistry.enable(); - testRegistry.configure({ integrations: { openai: false } }); - - expect(warnSpy).toHaveBeenCalledWith( - expect.stringContaining("Cannot configure instrumentation"), +describe("explicit instrumentation registration", () => { + it("does not register until requested", () => { + expect(registry.isEnabled()).toBe(false); + }); + + it("deduplicates each provider independently across registry copies", () => { + const second = new (registry.constructor as new () => typeof registry)(); + const openai = vi.fn(); + const anthropic = vi.fn(); + registry.register("openai", openai); + second.register("openai", openai); + second.register("anthropic", anthropic); + registry.register("anthropic", anthropic); + expect(openai).toHaveBeenCalledOnce(); + expect(anthropic).toHaveBeenCalledOnce(); + }); + + it("handles reentrant registration", () => { + const subscribe = vi.fn<() => void>(); + subscribe.mockImplementation(() => registry.register("openai", subscribe)); + registry.register("openai", subscribe); + expect(subscribe).toHaveBeenCalledOnce(); + }); + + it("shares configuration before registration and merges provider settings", () => { + configureInstrumentation({ integrations: { openai: false } }); + configureInstrumentation({ integrations: { anthropic: false } }); + const subscribe = vi.fn(); + registry.register("openai", subscribe); + registry.register("anthropic", subscribe); + expect(subscribe).not.toHaveBeenCalled(); + registry.register("groq", subscribe); + expect(subscribe).toHaveBeenCalledOnce(); + }); + + it("warns when configuration arrives after registration", () => { + const warn = vi.spyOn(console, "warn").mockImplementation(() => {}); + registry.register("openai", () => {}); + configureInstrumentation({ integrations: { openai: false } }); + expect(warn).toHaveBeenCalledWith( + "[braintrust]", + expect.stringContaining("Cannot configure"), + ); + }); + + it.each(["openai", " OPENAI , anthropic ", "OpenAI,ai-sdk"])( + "environment opt-outs override explicit enablement: %s", + (disabled) => { + vi.spyOn(iso, "getEnv").mockImplementation((name) => + name === "BRAINTRUST_DISABLE_INSTRUMENTATION" ? disabled : undefined, ); - } finally { - warnSpy.mockRestore(); - testRegistry.disable(); - } - }); - - it("shares startup integration settings with a separately bundled registry", () => { - const bootstrap = new (registry.constructor as any)(); - const sdk = new (registry.constructor as any)(); - bootstrap.configure({ integrations: { openai: false, livekit: false } }); - mockNewInvocationHook.mockClear(); - try { - sdk.enable(); - const names = mockNewInvocationHook.mock.calls.map(([name]) => name); - expect(names.some((name) => name.startsWith("orchestrion:openai:"))).toBe( - false, - ); - expect( - names.some((name) => name.startsWith("orchestrion:@livekit/agents:")), - ).toBe(false); - expect( - names.some((name) => name.startsWith("orchestrion:@anthropic-ai/sdk:")), - ).toBe(true); - } finally { - sdk.disable(); - } - }); - - it.each([undefined, 8192])( - "passes the %s-byte threshold from a separately bundled registry to LiveKit", - (streamingAttachmentBufferBytes) => { - const bootstrap = new (registry.constructor as any)(); - const sdk = new (registry.constructor as any)(); - const thresholds: number[] = []; - const enable = vi - .spyOn(LiveKitPlugin.prototype, "enable") - .mockImplementation(function (this: LiveKitPlugin) { - thresholds.push(Reflect.get(this, "streamingAttachmentBufferBytes")); - }); - try { - bootstrap.configure({ streamingAttachmentBufferBytes }); - // A later integration-only update must preserve the shared threshold. - sdk.configure({ integrations: { openai: false } }); - sdk.enable(); - expect(thresholds).toEqual([ - streamingAttachmentBufferBytes ?? 4 * 1024 * 1024, - ]); - } finally { - sdk.disable(); - enable.mockRestore(); - } + configureInstrumentation({ integrations: { openai: true } }); + const subscribe = vi.fn(); + registry.register("openai", subscribe); + expect(subscribe).not.toHaveBeenCalled(); }, ); - it.each([0, 1.5])( - "rejects an invalid threshold (%s) without changing startup settings", - (streamingAttachmentBufferBytes) => { - const testRegistry = new (registry.constructor as any)(); - testRegistry.configure({ streamingAttachmentBufferBytes: 1024 }); - expect(() => - testRegistry.configure({ streamingAttachmentBufferBytes }), - ).toThrow( - "streamingAttachmentBufferBytes must be a positive safe integer", - ); - expect( - Reflect.get( - globalThis, - Symbol.for("braintrust.instrumentation.config"), - ), - ).toMatchObject({ - streamingAttachmentBufferBytes: 1024, - }); - }, - ); - - it("LiveKit options keep it enabled and pass audio settings across bundles", () => { - const bootstrap = new (registry.constructor as any)(); - const sdk = new (registry.constructor as any)(); - const audio = { codec: { name: "pcm" } }; - const received: unknown[] = []; - const enable = vi - .spyOn(LiveKitPlugin.prototype, "enable") - .mockImplementation(function (this: LiveKitPlugin) { - received.push(Reflect.get(this, "audio")); - }); - try { - bootstrap.configure({ integrations: { livekit: { audio } } }); - sdk.enable(); - expect(received).toEqual([audio]); - } finally { - sdk.disable(); - enable.mockRestore(); - } - }); - - it("should allow configuration before enable", () => { - const testRegistry = new (registry.constructor as any)(); - const warnSpy = vi - .spyOn(debugLogger.notice, "warn") - .mockImplementation(() => true); - - try { - testRegistry.configure({ integrations: { openai: false } }); - testRegistry.enable(); - - expect(warnSpy).not.toHaveBeenCalled(); - } finally { - warnSpy.mockRestore(); - testRegistry.disable(); - } - }); - - it("should disable plugins when disable() is called", () => { - const testRegistry = new (registry.constructor as any)(); - testRegistry.enable(); - expect(testRegistry.isEnabled()).toBe(true); - - testRegistry.disable(); - expect(testRegistry.isEnabled()).toBe(false); - }); - - it("should be idempotent (calling disable() multiple times)", () => { - const testRegistry = new (registry.constructor as any)(); - testRegistry.enable(); - testRegistry.disable(); - testRegistry.disable(); // Should not throw - expect(testRegistry.isEnabled()).toBe(false); - }); -}); - -describe("configureInstrumentation API", () => { - afterEach(() => { - registry.disable(); - }); - - it("should export configureInstrumentation function", () => { - expect(typeof configureInstrumentation).toBe("function"); - }); - - it("should accept integration configuration", () => { - // Should not throw - configureInstrumentation({ - integrations: { - openai: false, - openaiCodexSDK: false, - anthropic: true, - huggingface: true, - openrouter: false, - mistral: false, - cohere: false, - mastra: false, - strandsAgentSDK: false, - cloudflareAIChat: false, - cloudflareAgents: false, - }, - }); - }); -}); - -describe("Environment Variable Configuration", () => { - let originalGetEnv: typeof import("../isomorph").default.getEnv; - - beforeEach(async () => { - const iso = (await import("../isomorph")).default; - originalGetEnv = iso.getEnv; - }); - - afterEach(async () => { - const iso = (await import("../isomorph")).default; - iso.getEnv = originalGetEnv; - registry.disable(); - }); - - it("should parse BRAINTRUST_DISABLE_INSTRUMENTATION with single SDK", async () => { - const iso = (await import("../isomorph")).default; - iso.getEnv = (name: string) => { - if (name === "BRAINTRUST_DISABLE_INSTRUMENTATION") { - return "openai"; - } - return originalGetEnv(name); - }; - - const testRegistry = new (registry.constructor as any)(); - testRegistry.enable(); - - // OpenAI should be disabled, others enabled by default - expect(testRegistry.isEnabled()).toBe(true); - testRegistry.disable(); - }); - - it("BRAINTRUST_DISABLE_INSTRUMENTATION disables LiveKit configured with options", async () => { - const iso = (await import("../isomorph")).default; - iso.getEnv = (name: string) => - name === "BRAINTRUST_DISABLE_INSTRUMENTATION" - ? "livekit" - : originalGetEnv(name); - const enable = vi.spyOn(LiveKitPlugin.prototype, "enable"); - const testRegistry = new (registry.constructor as any)(); - try { - testRegistry.configure({ - integrations: { livekit: { audio: { codec: { name: "pcm" } } } }, - }); - testRegistry.enable(); - expect(enable).not.toHaveBeenCalled(); - } finally { - testRegistry.disable(); - enable.mockRestore(); - } - }); - - it("should parse BRAINTRUST_DISABLE_INSTRUMENTATION with multiple SDKs", async () => { - const iso = (await import("../isomorph")).default; - iso.getEnv = (name: string) => { - if (name === "BRAINTRUST_DISABLE_INSTRUMENTATION") { - return "openai,anthropic"; - } - return originalGetEnv(name); - }; - - const testRegistry = new (registry.constructor as any)(); - testRegistry.enable(); - - // Both should be disabled - expect(testRegistry.isEnabled()).toBe(true); - testRegistry.disable(); - }); - - it("should handle whitespace in BRAINTRUST_DISABLE_INSTRUMENTATION", async () => { - const iso = (await import("../isomorph")).default; - iso.getEnv = (name: string) => { - if (name === "BRAINTRUST_DISABLE_INSTRUMENTATION") { - return " openai , anthropic , vercel "; - } - return originalGetEnv(name); - }; - - const testRegistry = new (registry.constructor as any)(); - testRegistry.enable(); - - expect(testRegistry.isEnabled()).toBe(true); - testRegistry.disable(); - }); - - it("should handle empty BRAINTRUST_DISABLE_INSTRUMENTATION", async () => { - const iso = (await import("../isomorph")).default; - iso.getEnv = (name: string) => { - if (name === "BRAINTRUST_DISABLE_INSTRUMENTATION") { - return ""; - } - return originalGetEnv(name); - }; - - const testRegistry = new (registry.constructor as any)(); - testRegistry.enable(); - - // All should be enabled (nothing disabled) - expect(testRegistry.isEnabled()).toBe(true); - testRegistry.disable(); - }); - - it("should be case-insensitive for SDK names", async () => { - const iso = (await import("../isomorph")).default; - iso.getEnv = (name: string) => { - if (name === "BRAINTRUST_DISABLE_INSTRUMENTATION") { - return "OpenAI,ANTHROPIC"; - } - return originalGetEnv(name); - }; - - const testRegistry = new (registry.constructor as any)(); - testRegistry.enable(); - - expect(testRegistry.isEnabled()).toBe(true); - testRegistry.disable(); + it("keeps the combined LangChain/LangGraph opt-out", () => { + configureInstrumentation({ integrations: { langgraph: false } }); + const subscribe = vi.fn(); + registry.register("langchain", subscribe); + expect(subscribe).not.toHaveBeenCalled(); }); }); diff --git a/js/src/instrumentation/registry.ts b/js/src/instrumentation/registry.ts index f9ab5a498..0450a1901 100644 --- a/js/src/instrumentation/registry.ts +++ b/js/src/instrumentation/registry.ts @@ -1,75 +1,43 @@ -/** - * Plugin registry and configuration for auto-instrumentation. - * - * Plugins are automatically enabled when the Braintrust library is loaded. - * Users can disable specific integrations programmatically or via environment variables. - */ - import { debugLogger } from "../debug-logger"; - -import { BraintrustPlugin } from "./braintrust-plugin"; -import iso from "../isomorph"; +import iso from "#braintrust-platform"; import { - getDefaultInstrumentationIntegrations, readDisabledInstrumentationEnvConfig, type InstrumentationConfig, + type InstrumentationIntegrationsConfig, } from "./config"; import { GLOBAL_INSTRUMENTATION_HOOKS_PROTOCOL_VERSION } from "../global-instrumentation-hooks"; +import { installGlobalHookErrorReporter } from "../debug-logger"; import { setSpanCustomizers } from "../span-customizer"; export type { InstrumentationConfig } from "./config"; -// Key used to stamp the active PluginRegistry instance onto the shared -// braintrust state object (globalThis[Symbol.for("braintrust-state")]). -// -// The braintrust state is already shared across all SDK instances loaded in -// the same process (see _internalSetInitialState in logger.ts), so using it -// as the carrier gives us cross-instance deduplication for free: -// -// - BT-5139 scenario: two SDK instances share the same state object → the -// second instance sees the marker left by the first and skips subscription, -// preventing duplicate global hook listeners. -// -// - vi.resetModules() test scenario: the test deletes the state from -// globalThis between runs, so the next import creates a fresh state with no -// marker and can subscribe normally. -const REGISTRY_STATE_KEY = Symbol.for( +const REGISTRY_KEY = Symbol.for( `braintrust.registry.global-hooks.v${GLOBAL_INSTRUMENTATION_HOOKS_PROTOCOL_VERSION}`, ); -function getSharedState(): Record | undefined { - const state = (globalThis as Record)[ - Symbol.for("braintrust-state") - ]; - return state && typeof state === "object" - ? (state as Record) - : undefined; +type Provider = keyof InstrumentationIntegrationsConfig; +interface RegistryState { + config: InstrumentationConfig; + registered: Set; } -// The standalone instrumentation entrypoint is bundled separately from the SDK. -// Share startup settings just as we share the active registry and span customizers. -const CONFIG_KEY = Symbol.for("braintrust.instrumentation.config"); -const sharedConfig: typeof globalThis & { - [CONFIG_KEY]?: InstrumentationConfig; -} = globalThis; - -class PluginRegistry { - private braintrustPlugin: BraintrustPlugin | null = null; - private enabled = false; - - private get config(): InstrumentationConfig { - return sharedConfig[CONFIG_KEY] ?? {}; - } +// Allocate only on explicit configuration or registration, independently of +// logger state. Multiple SDK copies must agree before either creates a logger. +function getRegistryState(): RegistryState { + const root = globalThis as typeof globalThis & { + [REGISTRY_KEY]?: RegistryState; + }; + return (root[REGISTRY_KEY] ??= { config: {}, registered: new Set() }); +} - /** - * Configure which integrations should be enabled. - * This must be called before any SDK imports to take effect. - */ +class InstrumentationRegistry { configure(config: InstrumentationConfig): void { - if (this.enabled) { + const state = getRegistryState(); + if (state.registered.size > 0) { + // eslint-disable-next-line no-restricted-properties -- intentional configuration diagnostic. debugLogger.notice.warn( - "Cannot configure instrumentation after it has been enabled. " + - "Call configureInstrumentation() before importing any AI SDKs.", + "Braintrust: Cannot configure instrumentation after it has been enabled. " + + "Call configureInstrumentation() before registering instrumentation or wrapping an AI SDK.", ); return; } @@ -85,126 +53,53 @@ class PluginRegistry { if ("spanCustomizers" in config) { setSpanCustomizers(config.spanCustomizers); } - sharedConfig[CONFIG_KEY] = { ...this.config, ...config }; - } - - /** - * Enable all configured plugins. - * Called automatically when the library is loaded. - */ - enable(): void { - if (this.enabled) { - return; - } - - // If another SDK instance in the same process already registered plugins, - // skip to avoid duplicate global hook subscriptions. - const sharedState = getSharedState(); - if (sharedState) { - if (sharedState[REGISTRY_STATE_KEY] !== undefined) { - return; - } - sharedState[REGISTRY_STATE_KEY] = this; - } - - this.enabled = true; - - // Read config from environment variables - const envConfig = this.readEnvConfig(); - const finalConfig = { - streamingAttachmentBufferBytes: - this.config.streamingAttachmentBufferBytes, - integrations: { - ...this.getDefaultConfig(), - ...this.config.integrations, - ...envConfig.integrations, - }, + state.config = { + ...state.config, + ...config, + integrations: { ...state.config.integrations, ...config.integrations }, }; - - // Enable BraintrustPlugin with configuration - this.braintrustPlugin = new BraintrustPlugin(finalConfig); - this.braintrustPlugin.enable(); } - /** - * Disable all plugins. - * Primarily used for testing. - */ - disable(): void { - if (!this.enabled) { + register( + provider: Provider, + subscribe: ( + integrations: InstrumentationIntegrationsConfig, + config: InstrumentationConfig, + ) => void, + ): void { + const state = getRegistryState(); + if (state.registered.has(provider)) return; + const integrations = { + ...state.config.integrations, + ...readDisabledInstrumentationEnvConfig( + iso.getEnv("BRAINTRUST_DISABLE_INSTRUMENTATION"), + ).integrations, + }; + if ( + integrations[provider] === false || + (provider === "langchain" && integrations.langgraph === false) + ) return; - } - - this.enabled = false; - const sharedState = getSharedState(); - if (sharedState && sharedState[REGISTRY_STATE_KEY] === this) { - delete sharedState[REGISTRY_STATE_KEY]; - } - - if (this.braintrustPlugin) { - this.braintrustPlugin.disable(); - this.braintrustPlugin = null; - } - } - - /** - * Check if instrumentation is enabled. - */ - isEnabled(): boolean { - return this.enabled; + installGlobalHookErrorReporter(); + // Stamp before subscribing, so reentrant setup cannot install twice. + state.registered.add(provider); + subscribe(integrations, state.config); } - /** - * Get default configuration (all integrations enabled). - */ - private getDefaultConfig(): Record { - return getDefaultInstrumentationIntegrations(); + isRegistered(provider: Provider): boolean { + return getRegistryState().registered.has(provider); } - /** - * Read configuration from environment variables. - * Supports: BRAINTRUST_DISABLE_INSTRUMENTATION=openai,anthropic,... - */ - private readEnvConfig(): InstrumentationConfig { - return readDisabledInstrumentationEnvConfig( - iso.getEnv("BRAINTRUST_DISABLE_INSTRUMENTATION"), - ); + isEnabled(): boolean { + return getRegistryState().registered.size > 0; } } -/** - * Global plugin registry instance. - */ -export const registry = new PluginRegistry(); +export const registry: InstrumentationRegistry = + /* @__PURE__ */ new InstrumentationRegistry(); -/** - * Configure auto-instrumentation. - * - * This must be called before importing any AI SDKs to take effect. - * - * @example - * ```typescript - * import { configureInstrumentation } from 'braintrust'; - * - * // Disable OpenAI instrumentation - * configureInstrumentation({ - * integrations: { openai: false } - * }); - * - * // Now import SDKs - * import OpenAI from 'openai'; - * ``` - * - * Environment variables can also be used: - * ```bash - * # Disable single SDK - * BRAINTRUST_DISABLE_INSTRUMENTATION=openai node app.js - * - * # Disable multiple SDKs - * BRAINTRUST_DISABLE_INSTRUMENTATION=openai,anthropic node app.js - * ``` - */ +/** Configure consumers before importing braintrust/auto or calling a wrapper. */ export function configureInstrumentation(config: InstrumentationConfig): void { registry.configure(config); } diff --git a/js/src/instrumentation/test-utils/reset.ts b/js/src/instrumentation/test-utils/reset.ts new file mode 100644 index 000000000..a6fe0000c --- /dev/null +++ b/js/src/instrumentation/test-utils/reset.ts @@ -0,0 +1,17 @@ +import { + GLOBAL_INSTRUMENTATION_HOOKS_KEY, + GLOBAL_INSTRUMENTATION_HOOKS_PROTOCOL_VERSION, +} from "../../global-instrumentation-hooks"; + +/** Simulate a fresh process for tests of configuration before registration. */ +export function resetInstrumentationForTest(): void { + const root = globalThis as unknown as Record; + delete root[ + Symbol.for( + `braintrust.registry.global-hooks.v${GLOBAL_INSTRUMENTATION_HOOKS_PROTOCOL_VERSION}`, + ) + ]; + ( + root[GLOBAL_INSTRUMENTATION_HOOKS_KEY] as Map | undefined + )?.clear(); +} diff --git a/js/src/isomorph-base.ts b/js/src/isomorph-base.ts new file mode 100644 index 000000000..182773e1c --- /dev/null +++ b/js/src/isomorph-base.ts @@ -0,0 +1,111 @@ +import type { GitMetadataSettings, RepoInfo } from "./sdk-types"; +import { sdkConsole } from "./logs/console"; + +export interface CallerLocation { + caller_functionname: string; + caller_filename: string; + caller_lineno: number; +} + +export interface IsoAsyncLocalStorage { + run(store: T | undefined, callback: () => R): R; + getStore(): T | undefined; +} + +class DefaultAsyncLocalStorage implements IsoAsyncLocalStorage { + constructor() {} + + run(_: T | undefined, callback: () => R): R { + return callback(); + } + getStore(): T | undefined { + return undefined; + } +} + +export interface Common { + buildType: + | "browser" // deprecated, use /workerd or /edge-light entrypoints for edge environments + | "browser-js" // @braintrust/browser package + | "node" + | "edge-light" + | "workerd" + | "unknown"; + + getRepoInfo: ( + settings?: GitMetadataSettings, + ) => Promise; + getPastNAncestors: (n?: number, remote?: string) => Promise; + getEnv: (name: string) => string | undefined; + getBraintrustApiKey: () => Promise; + getCallerLocation: () => CallerLocation | undefined; + newAsyncLocalStorage: () => IsoAsyncLocalStorage; + // eslint-disable-next-line @typescript-eslint/no-explicit-any + formatLogMessage?: (...args: unknown[]) => string; + // eslint-disable-next-line @typescript-eslint/no-explicit-any + processOn: (event: string, handler: (code: any) => void) => void; + + // Loads an installed optional package; throws if it fails to load. + loadOptionalModule?: (name: string) => unknown; + + // hash a string. not guaranteed to be crypto safe. + hash?: (data: string) => string; + + // HMAC-SHA256 for authenticating opaque instrumentation state. + hmacSha256?: (key: string, data: string) => string; + timingSafeEqual?: (left: string, right: string) => boolean; + + // Cross-platform utilities. + basename: (filepath: string) => string; + writeln: (text: string) => void; + + // Filesystem operations (async). + pathJoin?: (...args: string[]) => string; + pathDirname?: (path: string) => string; + mkdir?: ( + path: string, + opts?: { recursive?: boolean }, + ) => Promise; + writeFile?: (filename: string, data: string | Uint8Array) => Promise; + readFile?: (filename: string) => Promise; + readdir?: (path: string) => Promise; + utimes?: (path: string, atime: Date, mtime: Date) => Promise; + unlink?: (path: string) => Promise; + // eslint-disable-next-line @typescript-eslint/no-explicit-any + stat?: (path: string) => Promise; // type-erased + // eslint-disable-next-line @typescript-eslint/no-explicit-any + statSync?: (path: string) => any; // type-erased + homedir?: () => string; + tmpdir?: () => string; + + // Filesystem operations (sync) - for span cache. + writeFileSync?: (filename: string, data: string) => void; + appendFileSync?: (filename: string, data: string) => void; + readFileSync?: (filename: string, encoding: string) => string; + unlinkSync?: (path: string) => void; + // eslint-disable-next-line @typescript-eslint/no-explicit-any + openFile?: (path: string, flags: string) => Promise; // fs.promises.FileHandle, type-erased + + // zlib (promisified and type-erased). + // eslint-disable-next-line @typescript-eslint/no-explicit-any + gunzip?: (data: any) => Promise; + // eslint-disable-next-line @typescript-eslint/no-explicit-any + gzip?: (data: any) => Promise; +} + +export function createIsomorph(): Common { + return { + buildType: "unknown", // Will be set by configureBrowser() or configureNode() + getRepoInfo: async (_settings) => undefined, + getPastNAncestors: async () => [], + getEnv: (_name) => undefined, + getBraintrustApiKey: async () => undefined, + getCallerLocation: () => undefined, + newAsyncLocalStorage: () => new DefaultAsyncLocalStorage(), + // eslint-disable-next-line @typescript-eslint/no-explicit-any + processOn: (_0, _1) => {}, + basename: (filepath: string) => filepath.split(/[\\/]/).pop() || filepath, + // eslint-disable-next-line no-restricted-properties -- preserving intentional console usage. + writeln: (text: string) => sdkConsole.log(text), + }; +} diff --git a/js/src/isomorph.ts b/js/src/isomorph.ts index b4eb89ff4..b194f9829 100644 --- a/js/src/isomorph.ts +++ b/js/src/isomorph.ts @@ -1,114 +1,5 @@ -import { sdkConsole } from "./logs/console"; - -import { - type GitMetadataSettingsType as GitMetadataSettings, - type RepoInfoType as RepoInfo, -} from "./generated_types"; - -export interface CallerLocation { - caller_functionname: string; - caller_filename: string; - caller_lineno: number; -} - -export interface IsoAsyncLocalStorage { - run(store: T | undefined, callback: () => R): R; - getStore(): T | undefined; -} - -class DefaultAsyncLocalStorage implements IsoAsyncLocalStorage { - constructor() {} - - run(_: T | undefined, callback: () => R): R { - return callback(); - } - getStore(): T | undefined { - return undefined; - } -} - -interface Common { - buildType: - | "browser" // deprecated, use /workerd or /edge-light entrypoints for edge environments - | "browser-js" // @braintrust/browser package - | "node" - | "edge-light" - | "workerd" - | "unknown"; - - getRepoInfo: ( - settings?: GitMetadataSettings, - ) => Promise; - getPastNAncestors: (n?: number, remote?: string) => Promise; - getEnv: (name: string) => string | undefined; - getBraintrustApiKey: () => Promise; - getCallerLocation: () => CallerLocation | undefined; - formatLogMessage?: (...args: unknown[]) => string; - newAsyncLocalStorage: () => IsoAsyncLocalStorage; - // eslint-disable-next-line @typescript-eslint/no-explicit-any - processOn: (event: string, handler: (code: any) => void) => void; - - // Loads an installed optional package, or returns undefined if it is not - // installed. Throws if the package is installed but fails to load. - loadOptionalModule?: (name: string) => unknown; - - // hash a string. not guaranteed to be crypto safe. - hash?: (data: string) => string; - - // HMAC-SHA256 for authenticating opaque instrumentation state. - hmacSha256?: (key: string, data: string) => string; - timingSafeEqual?: (left: string, right: string) => boolean; - - // Cross-platform utilities. - basename: (filepath: string) => string; - writeln: (text: string) => void; - - // Filesystem operations (async). - pathJoin?: (...args: string[]) => string; - pathDirname?: (path: string) => string; - mkdir?: ( - path: string, - opts?: { recursive?: boolean }, - ) => Promise; - writeFile?: (filename: string, data: string | Uint8Array) => Promise; - readFile?: (filename: string) => Promise; - readdir?: (path: string) => Promise; - utimes?: (path: string, atime: Date, mtime: Date) => Promise; - unlink?: (path: string) => Promise; - // eslint-disable-next-line @typescript-eslint/no-explicit-any - stat?: (path: string) => Promise; // type-erased - // eslint-disable-next-line @typescript-eslint/no-explicit-any - statSync?: (path: string) => any; // type-erased - homedir?: () => string; - tmpdir?: () => string; - - // Filesystem operations (sync) - for span cache. - writeFileSync?: (filename: string, data: string) => void; - appendFileSync?: (filename: string, data: string) => void; - readFileSync?: (filename: string, encoding: string) => string; - unlinkSync?: (path: string) => void; - // eslint-disable-next-line @typescript-eslint/no-explicit-any - openFile?: (path: string, flags: string) => Promise; // fs.promises.FileHandle, type-erased - - // zlib (promisified and type-erased). - // eslint-disable-next-line @typescript-eslint/no-explicit-any - gunzip?: (data: any) => Promise; - // eslint-disable-next-line @typescript-eslint/no-explicit-any - gzip?: (data: any) => Promise; -} - -const iso: Common = { - buildType: "unknown", // Will be set by configureBrowser() or configureNode() - getRepoInfo: async (_settings) => undefined, - getPastNAncestors: async () => [], - getEnv: (_name) => undefined, - getBraintrustApiKey: async () => undefined, - getCallerLocation: () => undefined, - newAsyncLocalStorage: () => new DefaultAsyncLocalStorage(), - // eslint-disable-next-line @typescript-eslint/no-explicit-any - - processOn: (_0, _1) => {}, - basename: (filepath: string) => filepath.split(/[\\/]/).pop() || filepath, - writeln: (text: string) => sdkConsole.log(text), -}; +import type { Common } from "./isomorph-base"; +export type { CallerLocation, IsoAsyncLocalStorage } from "./isomorph-base"; +import { createNodeIsomorph } from "./node/platform"; +const iso: Common = /* @__PURE__ */ createNodeIsomorph(); export default iso; diff --git a/js/src/logger-caching.test.ts b/js/src/logger-caching.test.ts index 31cab579a..4377584f1 100644 --- a/js/src/logger-caching.test.ts +++ b/js/src/logger-caching.test.ts @@ -1,4 +1,4 @@ -import { afterEach, describe, expect, test, vi } from "vitest"; +import { afterEach, describe, expect, test, vi } from "vite-plus/test"; import { BraintrustState, initLogger, @@ -6,9 +6,9 @@ import { startSpan, updateSpan, _internalResumeSpan, -} from "./logger"; +} from "./logger/core"; import { configureNode } from "./node/config"; -import iso from "./isomorph"; +import iso from "#braintrust-platform"; import { SpanComponentsV4 } from "../util/span_identifier_v4"; configureNode(); @@ -198,7 +198,7 @@ describe("project metadata caching", () => { "shares lookups across SDK copies and invalidates on %s", async (action) => { vi.resetModules(); - const otherSdk = await import("./logger"); + const otherSdk = await import("./logger/core"); const otherNode = await import("./node/config"); otherNode.configureNode(); @@ -322,7 +322,7 @@ describe("project metadata caching", () => { setCurrent: false, }).startSpan(); const exported = await root.export(); - startSpan({ state, parent: exported }).end(); + startSpan({ state, parent: root.inject() }).end(); updateSpan({ state, exported, output: "updated" }); _internalResumeSpan({ state, exported }).end(); root.end(); diff --git a/js/src/logger-json-attachment.test.ts b/js/src/logger-json-attachment.test.ts index 798f1f74c..67f03db96 100644 --- a/js/src/logger-json-attachment.test.ts +++ b/js/src/logger-json-attachment.test.ts @@ -2,10 +2,10 @@ import { JSONAttachment, Attachment, BraintrustState, - _exportsForTestingOnly, BaseAttachment, -} from "./logger"; -import { afterEach, describe, expect, it, vi } from "vitest"; +} from "./logger/core"; +import { _exportsForTestingOnly } from "./logger/testing"; +import { afterEach, describe, expect, it, vi } from "vite-plus/test"; import { automaticAttachmentOriginals } from "./automatic-attachments"; import { configureNode } from "./node/config"; diff --git a/js/src/logger-misc.test.ts b/js/src/logger-misc.test.ts index bd7051544..ce5ff9ce2 100644 --- a/js/src/logger-misc.test.ts +++ b/js/src/logger-misc.test.ts @@ -4,18 +4,25 @@ */ /* eslint-disable @typescript-eslint/consistent-type-assertions */ -import { vi, expect, test, describe, beforeEach, afterEach } from "vitest"; import { - _exportsForTestingOnly, + vi, + expect, + test, + describe, + beforeEach, + afterEach, +} from "vite-plus/test"; +import { _exportsForTestingOnly } from "./logger/testing"; +import { BaseAttachment, Attachment, ExternalAttachment, - initExperiment, initLogger, NOOP_SPAN, permalink, BraintrustState, -} from "./logger"; +} from "./logger/core"; +import { init } from "./logger/experiments"; import { SpanObjectTypeV3 } from "../util/index"; import { LazyValue } from "./util"; import { BackgroundLogEvent, IS_MERGE_FIELD } from "../util/index"; @@ -368,7 +375,7 @@ describe("span.link", () => { expect(state.appUrl).toBeDefined(); // Create a test experiment - const experiment = initExperiment("test-experiment"); + const experiment = init({ project: "test-experiment" }); // Get a span within the experiment context const span = experiment.startSpan({ @@ -417,7 +424,7 @@ describe("span.link", () => { test("span.link handles missing experiment id", async () => { await _exportsForTestingOnly.simulateLoginForTests(); - const experiment = initExperiment("test-experiment"); + const experiment = init({ project: "test-experiment" }); const span = experiment.startSpan({ name: "test-span" }); span.end(); // Force parentObjectId to be undefined diff --git a/js/src/logger.test.ts b/js/src/logger.test.ts index 94d0b73d0..ec50c0468 100644 --- a/js/src/logger.test.ts +++ b/js/src/logger.test.ts @@ -1,28 +1,43 @@ +import { getLoaderCaches } from "./logger/prompts"; /* eslint-disable @typescript-eslint/consistent-type-assertions */ -import { vi, expect, test, describe, beforeEach, afterEach } from "vitest"; import { - _exportsForTestingOnly, - init, - initDataset, + vi, + expect, + expectTypeOf, + test, + describe, + beforeEach, + afterEach, +} from "vite-plus/test"; +import { _exportsForTestingOnly } from "./logger/testing"; +import { + _internalStartSpan, initLogger, - Prompt, - RemoteEvalParameters, + extractTraceContextFromHeaders, BraintrustState, FailedHTTPResponse, - loadPrompt, - loadParameters, loginToState, wrapTraced, + traced, currentSpan, withParent, startSpan, updateSpan, Attachment, deepCopyEvent, - ReadonlyExperiment, + flush, + type InitLoggerOptions, +} from "./logger/core"; +import { init, ReadonlyExperiment } from "./logger/experiments"; +import { initDataset } from "./logger/datasets"; +import { + Prompt, + RemoteEvalParameters, + loadPrompt, + loadParameters, renderMessageImpl, -} from "./logger"; +} from "./logger/prompts"; import { configureNode } from "./node/config"; import { type GitMetadataSettingsType as GitMetadataSettings } from "./generated_types"; @@ -45,6 +60,12 @@ import { LRUCache } from "./lru-cache"; configureNode(); +type InitLoggerHasAsyncFlush = "asyncFlush" extends keyof InitLoggerOptions + ? true + : false; + +expectTypeOf().toEqualTypeOf(); + test("renderMessage renders templates in structured content parts", () => { const message = { role: "user" as const, @@ -456,6 +477,57 @@ test("verify MemoryBackgroundLogger intercepts logs", async () => { _exportsForTestingOnly.clearTestBackgroundLogger(); // can go back to normal }); +test("logger and tracing APIs preserve return shapes without implicit flushing", async () => { + await _exportsForTestingOnly.simulateLoginForTests(); + const backgroundLogger = _exportsForTestingOnly.useTestBackgroundLogger(); + const flushSpy = vi.spyOn(backgroundLogger, "flush"); + + try { + const logger = initLogger({ + projectName: "test", + projectId: "test-project-id", + }); + + const id = logger.log({ input: "input" }); + expectTypeOf(id).toEqualTypeOf(); + + const loggerSyncResult = logger.traced(() => 42 as const); + expectTypeOf(loggerSyncResult).toEqualTypeOf<42>(); + expect(loggerSyncResult).toBe(42); + + const loggerAsyncResult = logger.traced(async () => 43); + expectTypeOf(loggerAsyncResult).toEqualTypeOf>(); + await expect(loggerAsyncResult).resolves.toBe(43); + + const tracedSyncResult = traced(() => "sync" as const); + expectTypeOf(tracedSyncResult).toEqualTypeOf<"sync">(); + expect(tracedSyncResult).toBe("sync"); + + const syncFunction = (value: number) => value + 1; + const wrappedSyncFunction = wrapTraced(syncFunction); + expectTypeOf(wrappedSyncFunction).toEqualTypeOf(); + expect(wrappedSyncFunction(1)).toBe(2); + + const asyncFunction = async (value: number) => value + 1; + const wrappedAsyncFunction = wrapTraced(asyncFunction); + expectTypeOf(wrappedAsyncFunction).toEqualTypeOf(); + await expect(wrappedAsyncFunction(2)).resolves.toBe(3); + + expect(flushSpy).not.toHaveBeenCalled(); + + await logger.flush(); + const span = logger.startSpan({ name: "explicit-flush" }); + span.end(); + await span.flush(); + await flush(); + + expect(flushSpy).toHaveBeenCalledTimes(3); + } finally { + _exportsForTestingOnly.clearTestBackgroundLogger(); + _exportsForTestingOnly.simulateLogoutForTests(); + } +}); + test("init validation", () => { expect(() => init({})).toThrow( "Must specify at least one of project or projectId", @@ -1034,7 +1106,8 @@ test("legacy initDataset applies bt eval internal BTQL runtime value", async () }, }); - const dataset = initDataset("test-project", { + const dataset = initDataset({ + project: "test-project", dataset: "test-dataset", state, }); @@ -2310,8 +2383,14 @@ describe("prompt and parameters loaders", () => { ) .mockResolvedValueOnce(jsonResponse({ objects: [promptRow] })) .mockResolvedValueOnce(serverErrorResponse()); - const globalCacheGet = vi.spyOn(state.promptCache, "get"); - const globalCacheSet = vi.spyOn(state.promptCache, "set"); + const globalCacheGet = vi.spyOn( + getLoaderCaches(state).promptCache, + "get", + ); + const globalCacheSet = vi.spyOn( + getLoaderCaches(state).promptCache, + "set", + ); const options = { projectName: "test-project", @@ -2714,7 +2793,7 @@ describe("prompt and parameters loaders", () => { logWarnings: false, }); const diskCacheSet = vi.spyOn(onlineDiskCache, "set"); - onlineState.promptCache = new PromptCache({ + getLoaderCaches(onlineState).promptCache = new PromptCache({ diskCache: onlineDiskCache, }); await onlineState.login({}); @@ -2743,7 +2822,7 @@ describe("prompt and parameters loaders", () => { fetch: offlineFetch as unknown as typeof globalThis.fetch, noExitFlush: true, }); - offlineState.promptCache = new PromptCache({ + getLoaderCaches(offlineState).promptCache = new PromptCache({ diskCache: new DiskCache({ cacheDir, logWarnings: false }), }); @@ -2768,7 +2847,7 @@ describe("prompt and parameters loaders", () => { fetch: rejectedFetch as unknown as typeof globalThis.fetch, noExitFlush: true, }); - rejectedState.promptCache = new PromptCache({ + getLoaderCaches(rejectedState).promptCache = new PromptCache({ diskCache: new DiskCache({ cacheDir, logWarnings: false }), }); @@ -2809,7 +2888,7 @@ describe("prompt and parameters loaders", () => { fetch: fetchMock as unknown as typeof globalThis.fetch, noExitFlush: true, }); - switchingState.promptCache = new PromptCache({ + getLoaderCaches(switchingState).promptCache = new PromptCache({ memoryCache: new LRUCache({ max: 10 }), }); @@ -2834,10 +2913,10 @@ describe("prompt and parameters loaders", () => { }); test("loadPrompt does not read legacy unnamespaced cache entries", async () => { - state.promptCache = new PromptCache({ + getLoaderCaches(state).promptCache = new PromptCache({ memoryCache: new LRUCache({ max: 10 }), }); - await state.promptCache.set( + await getLoaderCaches(state).promptCache.set( { projectName: "test-project", slug: "saved-prompt", @@ -2926,7 +3005,7 @@ describe("prompt and parameters loaders", () => { }); test("loadParameters uses request-local credentials and isolated caches", async () => { - state.parametersCache = new ParametersCache({ + getLoaderCaches(state).parametersCache = new ParametersCache({ memoryCache: new LRUCache({ max: 10, }), @@ -3681,7 +3760,7 @@ describe("wrapTraced noTraceIO", () => { }); return response; }, - { noTraceIO: true, asyncFlush: true }, + { noTraceIO: true }, ); expect(callModel({ input: "manual input" })).toEqual({ @@ -4131,7 +4210,7 @@ describe("parent precedence", () => { test("withParent + wrapTraced: child spans attach to current span (not directly to withParent)", async () => { const logger = initLogger({ projectName: "test", projectId: "pid" }); const outer = logger.startSpan({ name: "outer" }); - const parentStr = await outer.export(); + const parentContext = extractTraceContextFromHeaders(outer.inject())!; outer.end(); const inner = wrapTraced( @@ -4141,7 +4220,7 @@ describe("parent precedence", () => { { name: "inner" }, ); - await withParent(parentStr, () => inner()); + await withParent(parentContext, () => inner()); await memory.flush(); const events = await memory.drain(); @@ -4185,7 +4264,7 @@ describe("parent precedence", () => { const inner = wrapTraced( async function inner() { - startSpan({ name: "forced", parent: parentStr }).end(); + _internalStartSpan({ name: "forced", parent: parentStr }).end(); }, { name: "inner" }, ); @@ -4217,7 +4296,7 @@ describe("parent precedence", () => { const child = secondaryLogger.startSpan({ name: "child", - parent: parentStr, + parent: parentStr as never, }); child.end(); @@ -4248,7 +4327,7 @@ describe("parent precedence", () => { const child = secondaryExperiment.startSpan({ name: "child", - parent: parentStr, + parent: parentStr as never, }); child.end(); diff --git a/js/src/logger.ts b/js/src/logger/core.ts similarity index 59% rename from js/src/logger.ts rename to js/src/logger/core.ts index c71af1444..ba85d5a5e 100644 --- a/js/src/logger.ts +++ b/js/src/logger/core.ts @@ -1,28 +1,32 @@ /// - -import { TypedAttachmentProcessor } from "./typed-attachments"; +import { TypedAttachmentProcessor } from "../typed-attachments"; import { formatLogTemplate, renderLogBody, type ApplicationLogLevel, type ApplicationLogOptions, type ApplicationLogTemplate, -} from "./logs/templates"; - +} from "../logs/templates"; +import { + automaticAttachmentOriginals, + restoreAutomaticAttachments, +} from "../automatic-attachments"; import { v4 as uuidv4 } from "uuid"; - -import { Queue, DEFAULT_QUEUE_SIZE } from "./queue"; +import { Queue, DEFAULT_QUEUE_SIZE } from "../queue"; import { debugLogger, type DebugLogLevel, type DebugLogLevelOption, getEnvDebugLogLevel, normalizeDebugLogLevelOption, - resetDebugLoggerForTests, setDebugLogStateResolver, setGlobalDebugLogLevel, -} from "./debug-logger"; -import { IDGenerator, getIdGenerator, resolveUseLegacyUuidIds } from "./id-gen"; +} from "../debug-logger"; +import { + IDGenerator, + getIdGenerator, + resolveUseLegacyUuidIds, +} from "../id-gen"; import { BAGGAGE_HEADER, BRAINTRUST_PARENT_KEY, @@ -36,18 +40,14 @@ import { mergeBaggage, parseBaggage, parseTraceparent, -} from "./propagation"; +} from "../propagation"; import { _urljoin, - AnyDatasetRecord, AUDIT_METADATA_FIELD, AUDIT_SOURCE_FIELD, BackgroundLogEvent, batchItems, constructJsonArray, - DatasetRecord, - DEFAULT_IS_LEGACY_DATASET, - ensureDatasetRecord, ExperimentEvent, ExperimentLogFullArgs, ExperimentLogPartialArgs, @@ -55,7 +55,6 @@ import { IS_MERGE_FIELD, LogFeedbackFullArgs, mergeDicts, - mergeGitMetadataSettings, OBJECT_DELETE_FIELD, OBJECT_ID_KEYS, SanitizedExperimentLogPartialArgs, @@ -66,135 +65,31 @@ import { spanObjectTypeV3ToString, SpanType, SpanTypeAttribute, - TRANSACTION_ID_FIELD, TransactionId, VALID_SOURCES, - isArray, - isObject, - getObjValueByPath, -} from "./util"; +} from "../util"; import { - type AnyModelParamsType as AnyModelParam, AttachmentReference as attachmentReferenceSchema, - type AttachmentReferenceType as AttachmentReference, - BraintrustAttachmentReference as BraintrustAttachmentReferenceSchema, - type BraintrustAttachmentReferenceType as BraintrustAttachmentReference, - BraintrustModelParams as braintrustModelParamsSchema, - ChatCompletionTool as chatCompletionToolSchema, - type ChatCompletionToolType as ChatCompletionTool, - ExternalAttachmentReference as ExternalAttachmentReferenceSchema, - type ExternalAttachmentReferenceType as ExternalAttachmentReference, - type ModelParamsType as ModelParams, - ResponseFormatJsonSchema as responseFormatJsonSchemaSchema, AttachmentStatus as attachmentStatusSchema, - type AttachmentStatusType as AttachmentStatus, GitMetadataSettings as gitMetadataSettingsSchema, - type GitMetadataSettingsType as GitMetadataSettings, - type ChatCompletionMessageParamType as Message, - type ChatCompletionOpenAIMessageParamType as OpenAIMessage, - DatasetSnapshot as datasetSnapshotSchema, - type DatasetSnapshotType as DatasetSnapshot, - PromptData as promptDataSchema, - type PromptDataType as PromptData, - Prompt as promptSchema, - type PromptType as PromptRow, - type PromptSessionEventType as PromptSessionEvent, - type RepoInfoType as RepoInfo, - type ObjectReferenceType as ObjectReference, - type PromptBlockDataType as PromptBlockData, - type ResponseFormatJsonSchemaType as ResponseFormatJsonSchema, - type ObjectReferenceType, -} from "./generated_types"; - -const BRAINTRUST_ATTACHMENT = - BraintrustAttachmentReferenceSchema.shape.type.value; -const EXTERNAL_ATTACHMENT = ExternalAttachmentReferenceSchema.shape.type.value; -export const LOGS3_OVERFLOW_REFERENCE_TYPE = "logs3_overflow"; -const BRAINTRUST_PARAMS = Object.keys(braintrustModelParamsSchema.shape); -const RESET_CONTEXT_MANAGER_STATE = Symbol.for( - "braintrust.resetContextManagerState", -); -// 6 MB for the AWS lambda gateway (from our own testing). -export const DEFAULT_MAX_REQUEST_SIZE = 6 * 1024 * 1024; - -export type { DatasetSnapshot }; - -const datasetSnapshotRegisterResponseSchema = z.object({ - dataset_snapshot: datasetSnapshotSchema, - found_existing: z.boolean().optional(), -}); - -const datasetObjectInfoSchema = z.object({ - object_id: z.string(), - object_name: z.string(), - parent_cols: z.object({ - project: z.object({ - id: z.string(), - name: z.string(), - }), - }), -}); - -const datasetRestorePreviewResultSchema = z.object({ - rows_to_restore: z.number(), - rows_to_delete: z.number(), -}); -export type DatasetRestorePreviewResult = z.infer< - typeof datasetRestorePreviewResultSchema ->; - -const datasetRestoreResultSchema = z.object({ - xact_id: z.string().nullable(), - rows_restored: z.number(), - rows_deleted: z.number(), -}); -export type DatasetRestoreResult = z.infer; - -const parametersRowSchema = z.object({ - id: z.string().uuid(), - _xact_id: z.string(), - project_id: z.string().uuid(), - name: z.string(), - slug: z.string(), - description: z.union([z.string(), z.null()]).optional(), - function_type: z.literal("parameters"), - function_data: z.object({ - type: z.literal("parameters"), - data: z.record(z.unknown()).optional(), - __schema: z.record(z.unknown()), - }), - metadata: z - .union([z.object({}).partial().passthrough(), z.null()]) - .optional(), -}); -type ParametersRow = z.infer; - +} from "../sdk-schemas"; +import type { + AttachmentReference, + BraintrustAttachmentReference, + ExternalAttachmentReference, + AttachmentStatus, + GitMetadataSettings, + ObjectReference, +} from "../sdk-types"; import { waitUntil } from "@vercel/functions"; -import { - parseTemplateFormat, - renderTemplateContent, -} from "./template/renderer"; -import type { TemplateFormat } from "./template/registry"; - import { z, ZodError } from "zod/v3"; import { BraintrustStream, createFinalValuePassThroughStream, devNullWritableStream, -} from "./functions/stream"; -import iso, { IsoAsyncLocalStorage } from "./isomorph"; -import { createCacheLayers } from "./prompt-cache/cache-config"; -import { - PromptCache, - type PromptDiskCacheEntry, - type PromptMemoryCacheEntry, -} from "./prompt-cache/prompt-cache"; -import { - ParametersCache, - type ParametersDiskCacheEntry, - type ParametersMemoryCacheEntry, -} from "./prompt-cache/parameters-cache"; -import { LRUCache } from "./lru-cache"; +} from "../functions/stream"; +import iso, { IsoAsyncLocalStorage } from "#braintrust-platform"; +import { LRUCache } from "../lru-cache"; import { addAzureBlobHeaders, getCurrentUnixTimestamp, @@ -203,31 +98,48 @@ import { LazyValue, SyncLazyValue, runCatchFinally, -} from "./util"; -import { lintTemplate as _lintMustacheTemplate } from "./template/mustache-utils"; -import { prettifyXact } from "../util/index"; -import { SpanCache, CachedSpan } from "./span-cache"; -import type { EvalParameters, InferParameters } from "./eval-parameters"; +} from "../util"; +import { SpanCache, CachedSpan } from "../span-cache"; import { detectSpanOriginEnvironment, getSpanInstrumentationName, INSTRUMENTATION_NAMES, mergeSpanOriginContext, type SpanOriginEnvironment, -} from "./span-origin"; +} from "../span-origin"; import { createMaskingCustomizer, customizeSpanExport, mergeCustomizedSpanRecords, -} from "./span-customizer"; -import type { SpanCustomizer, SpanExportData } from "./instrumentation/config"; -import { - automaticAttachmentOriginals, - restoreAutomaticAttachments, -} from "./automatic-attachments"; +} from "../span-customizer"; +import type { SpanCustomizer, SpanExportData } from "../instrumentation/config"; +import type { Experiment, ExperimentSummary } from "./experiments"; +const NODE_INSPECT_CUSTOM: unique symbol = Symbol.for( + "nodejs.util.inspect.custom", +); + +const TEST_API_KEY = "___TEST_API_KEY__THIS_IS_NOT_REAL___"; + +const BRAINTRUST_ATTACHMENT = "braintrust_attachment"; + +const EXTERNAL_ATTACHMENT = "external_attachment"; + +export const LOGS3_OVERFLOW_REFERENCE_TYPE = "logs3_overflow"; + +export const RESET_CONTEXT_MANAGER_STATE: unique symbol = Symbol.for( + "braintrust.resetContextManagerState", +); + +// 6 MB for the AWS lambda gateway (from our own testing). +export const DEFAULT_MAX_REQUEST_SIZE: number = 6 * 1024 * 1024; // Manual type definition for inline attachments (not in generated_types) -const InlineAttachmentReferenceSchema = z.object({ +export const InlineAttachmentReferenceSchema: z.ZodType<{ + type: "inline_attachment"; + src: string; + content_type?: string; + filename?: string; +}> = z.object({ type: z.literal("inline_attachment"), src: z.string().min(1), content_type: z.string().optional(), @@ -248,15 +160,19 @@ export class LoginInvalidOrgError extends Error { export type SetCurrentArg = { setCurrent?: boolean }; -type StartSpanEventArgs = ExperimentLogPartialArgs & Partial; +export type StartSpanEventArgs = ExperimentLogPartialArgs & Partial; -const INITIAL_SPAN_WRITE_AS_MERGE = Symbol( +const INITIAL_SPAN_WRITE_AS_MERGE: unique symbol = Symbol( "braintrust.initial-span-write-as-merge", ); -const RESUME_SPAN_WITHOUT_INITIAL_WRITE = Symbol( + +const RESUME_SPAN_WITHOUT_INITIAL_WRITE: unique symbol = Symbol( "braintrust.resume-span-without-initial-write", ); -const INTERNAL_SPAN_CONTEXT = Symbol("braintrust.internal-span-context"); + +const INTERNAL_SPAN_CONTEXT: unique symbol = Symbol( + "braintrust.internal-span-context", +); type InitialSpanWriteAsMergeArg = { readonly [INITIAL_SPAN_WRITE_AS_MERGE]?: true; @@ -274,17 +190,20 @@ export type StartSpanArgs = { spanAttributes?: Record; startTime?: number; /** - * The parent to start this span under. May be an exported span slug string - * (from `span.export()`) or an opaque W3C trace-context (from - * {@link extractTraceContextFromHeaders}). + * An opaque W3C trace context returned by + * {@link extractTraceContextFromHeaders}. */ - parent?: string | PropagationContext; + parent?: PropagationContext; event?: StartSpanEventArgs; propagatedEvent?: StartSpanEventArgs; spanId?: string; parentSpanIds?: ParentSpanIds | MultiParentSpanIds; }; +type InternalStartSpanArgs = Omit & { + parent?: string | PropagationContext; +}; + export type EndSpanArgs = { endTime?: number; }; @@ -392,8 +311,8 @@ export interface Span extends Exportable { * parameters of {@link Span.startSpan} for usage details. * * Callers should treat the return value as opaque. The serialization format - * may change from time to time. If parsing is needed, use - * `SpanComponentsV3.fromStr`. + * may change from time to time. For cross-service propagation, prefer + * {@link Span.inject} and {@link extractTraceContextFromHeaders}. * * @returns Serialized representation of this span's identifiers. */ @@ -450,11 +369,6 @@ export interface Span extends Exportable { */ flush(): Promise; - /** - * Alias for `end`. - */ - close(args?: EndSpanArgs): number; - /** * Set the span's name, type, or other attributes after it's created. */ @@ -479,12 +393,8 @@ export interface Span extends Exportable { kind: "span"; } -export const BRAINTRUST_CURRENT_SPAN_STORE = Symbol.for( - "braintrust.currentSpanStore", -); - /** - * The type of AsyncLocalStorage exposed via {@link BRAINTRUST_CURRENT_SPAN_STORE}. + * The AsyncLocalStorage contract used by context-manager integrations. * * The stored value is intentionally opaque (`unknown`) because the concrete type * depends on the active context manager: @@ -501,6 +411,14 @@ export abstract class ContextManager { abstract runInContext(span: Span, callback: () => R): R; abstract getCurrentSpan(): Span | undefined; + /** + * Return the store used by instrumentation hooks to propagate the current + * span, when the context manager supports direct store binding. + */ + getCurrentSpanStore(): CurrentSpanStore | undefined { + return undefined; + } + /** * Returns the value to store in the ALS bound to a global hook's start event. * In default mode this is the Span itself; in OTEL mode it is the OTEL Context @@ -514,12 +432,14 @@ export abstract class ContextManager { class BraintrustContextManager extends ContextManager { private _currentSpan: IsoAsyncLocalStorage; - [BRAINTRUST_CURRENT_SPAN_STORE]: IsoAsyncLocalStorage; constructor() { super(); this._currentSpan = iso.newAsyncLocalStorage(); - this[BRAINTRUST_CURRENT_SPAN_STORE] = this._currentSpan; + } + + getCurrentSpanStore(): CurrentSpanStore { + return this._currentSpan; } getParentSpanIds(): ContextParentSpanIds | undefined { @@ -543,11 +463,8 @@ class BraintrustContextManager extends ContextManager { } } -// make sure to update @braintrust/otel package declare global { var BRAINTRUST_CONTEXT_MANAGER: (new () => ContextManager) | undefined; - var BRAINTRUST_ID_GENERATOR: (new () => IDGenerator) | undefined; - var BRAINTRUST_SPAN_COMPONENT: SpanComponent | undefined; } type SpanComponent = typeof SpanComponentsV3 | typeof SpanComponentsV4; @@ -558,11 +475,7 @@ type SpanComponent = typeof SpanComponentsV3 | typeof SpanComponentsV4; // serialize as V4, legacy UUID IDs serialize as V3. These must move together -- // serializing hex IDs via V3 would lose the compact encoding and risk // corrupting hex values that happen to parse as UUIDs. An explicit -// `globalThis.BRAINTRUST_SPAN_COMPONENT` (e.g. from `@braintrust/otel`) wins. -function getSpanComponentsClass(): SpanComponent { - if (globalThis.BRAINTRUST_SPAN_COMPONENT) { - return globalThis.BRAINTRUST_SPAN_COMPONENT; - } +export function getSpanComponentsClass(): SpanComponent { return resolveUseLegacyUuidIds() ? SpanComponentsV3 : SpanComponentsV4; } @@ -572,6 +485,20 @@ export function getContextManager(): ContextManager { : new BraintrustContextManager(); } +/** + * Configure the context manager used by Braintrust. + * + * This extension point is intended for runtime integrations such as + * `@braintrust/otel` and `@braintrust/browser`. Passing `undefined` restores + * Braintrust's default context manager. + */ +export function configureContextManager( + contextManager: (new () => ContextManager) | undefined, +): void { + globalThis.BRAINTRUST_CONTEXT_MANAGER = contextManager; + _globalState?.[RESET_CONTEXT_MANAGER_STATE](); +} + /** * A fake implementation of the Span API which does nothing. This can be used as the default span. */ @@ -590,9 +517,9 @@ export class NoopSpan implements Span { this.spanParents = []; } - public log(_: ExperimentLogPartialArgs) {} + public log(_: ExperimentLogPartialArgs): void {} - public logFeedback(_event: Omit) {} + public logFeedback(_event: Omit): void {} public traced( callback: (span: Span) => R, @@ -605,7 +532,7 @@ export class NoopSpan implements Span { return undefined; } - public startSpan(_1?: StartSpanArgs) { + public startSpan(_1?: StartSpanArgs): this { return this; } @@ -635,11 +562,7 @@ export class NoopSpan implements Span { public async flush(): Promise {} - public close(args?: EndSpanArgs): number { - return this.end(args); - } - - public setAttributes(_args: Omit) {} + public setAttributes(_args: Omit): void {} public startSpanWithParents( _spanId: string, @@ -649,19 +572,25 @@ export class NoopSpan implements Span { return this; } - public state() { + public state(): BraintrustState { return _internalGetGlobalState(); } // Custom inspect for Node.js console.log - [Symbol.for("nodejs.util.inspect.custom")](): string { - return `NoopSpan { + static { + Object.defineProperty(this.prototype, NODE_INSPECT_CUSTOM, { + configurable: true, + writable: true, + value(this: NoopSpan): string { + return `NoopSpan { kind: '${this.kind}', id: '${this.id}', spanId: '${this.spanId}', rootSpanId: '${this.rootSpanId}', spanParents: ${JSON.stringify(this.spanParents)} }`; + }, + }); } // Custom toString @@ -670,7 +599,8 @@ export class NoopSpan implements Span { } } -export const NOOP_SPAN = new NoopSpan(); +export const NOOP_SPAN: NoopSpan = new NoopSpan(); + export const NOOP_SPAN_PERMALINK = "https://braintrust.dev/noop-span"; // In certain situations (e.g. the cli), we want separately-compiled modules to @@ -695,17 +625,32 @@ const loginSchema = z.strictObject({ debugLogLevelDisabled: z.boolean().optional(), }); -export type SerializedBraintrustState = z.infer; +export type SerializedBraintrustState = { + appUrl: string; + appPublicUrl: string; + orgName: string; + apiUrl: string; + proxyUrl: string; + loginToken: string; + orgId?: string | null; + gitMetadataSettings?: GitMetadataSettings | null; + debugLogLevel?: "error" | "warn" | "info" | "debug"; + debugLogLevelDisabled?: boolean; +}; let stateNonce = 0; const V1_PROXY_SUFFIX = "/v1/proxy"; + const LOADER_LOGIN_CACHE_MAX = 16; + const LOGIN_TIMEOUT_MS = 30_000; + const PROJECT_LOOKUP_TIMEOUT_MS = 30_000; + const PROJECT_CACHE_TTL_MS = 15 * 60 * 1000; -type ResolvedLoaderLoginOptions = Required< +export type ResolvedLoaderLoginOptions = Required< Pick > & Pick & { @@ -714,12 +659,10 @@ type ResolvedLoaderLoginOptions = Required< existingState?: BraintrustState; }; -type LoaderCacheViews = { - promptCache: PromptCache; - parametersCache: ParametersCache; -}; - -type LoaderRequestState = Pick; +export type LoaderRequestState = Pick< + BraintrustState, + "appUrl" | "orgId" | "apiConn" +>; // proxyUrl may point at the universal proxy (`{apiUrl}/v1/proxy`) for // EU/self-hosted orgs, but proxyConn only targets Braintrust API endpoints @@ -731,12 +674,35 @@ function normalizeProxyConnUrl(proxyUrl: string): string { : proxyUrl; } +/** @internal */ +export const CAPTURE_ATTACHMENTS: unique symbol = Symbol.for( + "braintrust.captureAttachments", +); + +function autoCaptureAttachmentsFromEnv(): boolean { + return ["1", "true"].includes( + iso.getEnv("BRAINTRUST_AUTO_CAPTURE_ATTACHMENTS")?.trim().toLowerCase() ?? + "", + ); +} + +/** + * @internal Resolve automatic attachment capture for a logger or span. Only plain + * fields are read, so parents and global state from other SDK bundles also work. + */ +export function _internalCaptureAttachmentsEnabled(parent?: object): boolean { + // Global state is unset until a platform entrypoint configures the SDK. + const owner = parent ?? (_globalState ? getSpanParentObject() : undefined); + const policy = owner && Reflect.get(owner, CAPTURE_ATTACHMENTS); + return typeof policy === "boolean" + ? policy + : (_globalState?.autoCaptureAttachments ?? autoCaptureAttachmentsFromEnv()); +} + export class BraintrustState { public id: string; public currentExperiment: Experiment | undefined; - // Note: the value of IsAsyncFlush doesn't really matter here, since we - // (safely) dynamically cast it whenever retrieving the logger. - public currentLogger: Logger | undefined; + public currentLogger: Logger | undefined; public currentParent: IsoAsyncLocalStorage; public currentSpan: IsoAsyncLocalStorage; // Any time we re-log in, we directly update the apiConn inside the logger. @@ -755,6 +721,8 @@ export class BraintrustState { public proxyUrl: string | null = null; public loggedIn: boolean = false; public gitMetadataSettings?: GitMetadataSettings; + /** @internal Default for instrumentation without a logger-specific policy. */ + public autoCaptureAttachments?: boolean; public debugLogLevel?: DebugLogLevel; private debugLogLevelConfigured = false; @@ -762,12 +730,22 @@ export class BraintrustState { private _appConn: HTTPConnection | null = null; private _apiConn: HTTPConnection | null = null; private _proxyConn: HTTPConnection | null = null; + private _spanCache: SpanCache | undefined; - public promptCache: PromptCache; - public parametersCache: ParametersCache; - public spanCache: SpanCache; + public get spanCache(): SpanCache { + return (this._spanCache ??= new SpanCache({ + disabled: this.loginParams.disableSpanCache, + })); + } + + public set spanCache(cache: SpanCache) { + this._spanCache = cache; + } /** @internal */ - public readonly projectMetadataCache = new LRUCache< + public readonly projectMetadataCache: LRUCache< + string, + { promise: Promise; expiresAt: number } + > = new LRUCache< string, { promise: Promise; expiresAt: number } >({ max: 1000 }); @@ -828,36 +806,6 @@ export class BraintrustState { this.resetLoginInfo(); - const { memoryCache, diskCache } = createCacheLayers< - PromptMemoryCacheEntry, - PromptDiskCacheEntry - >({ - memoryMaxEnvVar: "BRAINTRUST_PROMPT_CACHE_MEMORY_MAX", - diskCacheDirEnvVar: "BRAINTRUST_PROMPT_CACHE_DIR", - diskMaxEnvVar: "BRAINTRUST_PROMPT_CACHE_DISK_MAX", - getDefaultDiskCacheDir: () => - `${iso.getEnv("HOME") ?? iso.homedir!()}/.braintrust/prompt_cache`, - }); - this.promptCache = new PromptCache({ memoryCache, diskCache }); - - const { - memoryCache: parametersMemoryCache, - diskCache: parametersDiskCache, - } = createCacheLayers( - { - memoryMaxEnvVar: "BRAINTRUST_PARAMETERS_CACHE_MEMORY_MAX", - diskCacheDirEnvVar: "BRAINTRUST_PARAMETERS_CACHE_DIR", - diskMaxEnvVar: "BRAINTRUST_PARAMETERS_CACHE_DISK_MAX", - getDefaultDiskCacheDir: () => - `${iso.getEnv("HOME") ?? iso.homedir!()}/.braintrust/parameters_cache`, - }, - ); - this.parametersCache = new ParametersCache({ - memoryCache: parametersMemoryCache, - diskCache: parametersDiskCache, - }); - - this.spanCache = new SpanCache({ disabled: loginParams.disableSpanCache }); this.spanOriginEnvironment = detectSpanOriginEnvironment(); this._internalSetTraceContextSigningSecret(loginParams.apiKey); } @@ -881,7 +829,7 @@ export class BraintrustState { )?.trim(); } - public resetLoginInfo() { + public resetLoginInfo(): void { this.loginGeneration++; this.loginQueueTail = undefined; this.pendingLogins = new WeakMap(); @@ -914,13 +862,13 @@ export class BraintrustState { }): Promise { const resolvedAppUrl = appUrl ?? - (this.loggedIn ? (this.appUrl ?? undefined) : undefined) ?? + (this.loggedIn ? this.appUrl : undefined) ?? this.loginParams.appUrl ?? iso.getEnv("BRAINTRUST_APP_URL") ?? "https://www.braintrust.dev"; const resolvedApiKey = apiKey ?? - (this.loggedIn ? (this.loginToken ?? undefined) : undefined) ?? + (this.loggedIn ? this.loginToken : undefined) ?? this.loginParams.apiKey ?? (await iso.getBraintrustApiKey()); if (!resolvedApiKey) { @@ -938,7 +886,7 @@ export class BraintrustState { iso.getEnv("BRAINTRUST_ORG_NAME"); const resolvedOrgName = orgName ?? - (usesActiveCredential ? (this.orgName ?? undefined) : undefined) ?? + (usesActiveCredential ? this.orgName : undefined) ?? requestedOrgName; const resolvedFetch = fetch ?? @@ -970,32 +918,6 @@ export class BraintrustState { }; } - /** @internal */ - public _internalGetLoaderCacheViews( - loginOptions: ResolvedLoaderLoginOptions, - requestState?: LoaderRequestState, - ): LoaderCacheViews { - const expectedResolvedOrgIdentity = - requestState?.orgId && requestState.appUrl - ? JSON.stringify([ - "loader-org", - requestState.appUrl, - requestState.orgId, - ]) - : undefined; - - return { - promptCache: this.promptCache.withNamespace( - loginOptions.credentialCacheNamespace, - expectedResolvedOrgIdentity, - ), - parametersCache: this.parametersCache.withNamespace( - loginOptions.credentialCacheNamespace, - expectedResolvedOrgIdentity, - ), - }; - } - /** @internal */ public async _internalGetLoaderState({ apiKey, @@ -1004,7 +926,7 @@ export class BraintrustState { fetch, forceLogin, existingState, - }: ResolvedLoaderLoginOptions) { + }: ResolvedLoaderLoginOptions): Promise { if (existingState) { return existingState; } @@ -1041,13 +963,19 @@ export class BraintrustState { } } - public resetIdGenState() { + public resetIdGenState(): void { // Reset the ID generator so it gets recreated with current environment variables this._idGenerator = null; } - public [RESET_CONTEXT_MANAGER_STATE]() { - this._contextManager = null; + static { + Object.defineProperty(this.prototype, RESET_CONTEXT_MANAGER_STATE, { + configurable: true, + writable: true, + value(this: BraintrustState): void { + this._contextManager = null; + }, + }); } public get idGenerator(): IDGenerator { @@ -1082,7 +1010,7 @@ export class BraintrustState { } } - public copyLoginInfo(other: BraintrustState) { + public copyLoginInfo(other: BraintrustState): void { this.projectMetadataCache.clear(); this.appUrl = other.appUrl; this.appPublicUrl = other.appPublicUrl; @@ -1188,7 +1116,7 @@ export class BraintrustState { return state; } - public setFetch(fetch: typeof globalThis.fetch) { + public setFetch(fetch: typeof globalThis.fetch): void { if (fetch !== this.fetch) { this.projectMetadataCache.clear(); } @@ -1221,7 +1149,9 @@ export class BraintrustState { return this.debugLogLevelConfigured; } - public async login(loginParams: LoginOptions & { forceLogin?: boolean }) { + public async login( + loginParams: LoginOptions & { forceLogin?: boolean }, + ): Promise { this._internalSetTraceContextSigningSecret(loginParams.apiKey); this.setDebugLogLevel(loginParams.debugLogLevel); if (this.apiUrl && !loginParams.forceLogin) { @@ -1387,20 +1317,20 @@ export class BraintrustState { return this._bgLogger.get(); } - public setOverrideBgLogger(logger: BackgroundLogger | null) { + public setOverrideBgLogger(logger: BackgroundLogger | null): void { this._overrideBgLogger = logger; } // Should only be called by the login function. - public loginReplaceApiConn(apiConn: HTTPConnection) { + public loginReplaceApiConn(apiConn: HTTPConnection): void { this._bgLogger.get().internalReplaceApiConn(apiConn); } - public disable() { + public disable(): void { this._bgLogger.get().disable(); } - public enforceQueueSizeLimit(enforce: boolean) { + public enforceQueueSizeLimit(enforce: boolean): void { this._bgLogger.get().enforceQueueSizeLimit(enforce); } @@ -1420,8 +1350,12 @@ export class BraintrustState { } // Custom inspect for Node.js console.log - [Symbol.for("nodejs.util.inspect.custom")](): string { - return `BraintrustState { + static { + Object.defineProperty(this.prototype, NODE_INSPECT_CUSTOM, { + configurable: true, + writable: true, + value(this: BraintrustState): string { + return `BraintrustState { id: '${this.id}', orgId: ${this.orgId ? `'${this.orgId}'` : "null"}, orgName: ${this.orgName ? `'${this.orgName}'` : "null"}, @@ -1431,6 +1365,8 @@ export class BraintrustState { loggedIn: ${this.loggedIn}, loginToken: '[REDACTED]' }`; + }, + }); } // Custom toString @@ -1441,58 +1377,16 @@ export class BraintrustState { let _globalState: BraintrustState; -// Return a TestBackgroundLogger that will intercept logs before they are sent to the server. -// Used for testing only. -function useTestBackgroundLogger(): TestBackgroundLogger { - const state = _internalGetGlobalState(); - if (!state) { - throw new Error("global state not set yet"); - } - - const logger = new TestBackgroundLogger(); - state.setOverrideBgLogger(logger); - return logger; -} - -function clearTestBackgroundLogger() { - _internalGetGlobalState()?.setOverrideBgLogger(null); -} - -// Initialize a test Experiment without making network calls by injecting fake metadata. -// Useful for unit tests that need an Experiment instance. -function initTestExperiment( - experimentName: string, - projectName?: string, - experimentFullInfo: Record = {}, -): Experiment { - setInitialTestState(); - const state = _internalGetGlobalState(); - const project = projectName ?? experimentName; - - const lazyMetadata: LazyValue = new LazyValue( - async () => ({ - project: { id: project, name: project, fullInfo: {} }, - experiment: { - id: experimentName, - name: experimentName, - fullInfo: experimentFullInfo, - }, - }), - ); - - return new Experiment(state, lazyMetadata); -} - /** * Initialize the global Braintrust state lazily, it only creates the BraintrustState instance on the first invocation. * * The state is stored in a global symbol to ensure cross-bundle compatibility. * - * This is invoked by platform-specific initialization functions (configureNode/configureBrowser) + * This is invoked on first use; importing the SDK leaves global state untouched. * * @internal */ -export function _internalSetInitialState() { +export function _internalSetInitialState(): void { if (_globalState) { return; } @@ -1504,12 +1398,16 @@ export function _internalSetInitialState() { existing = state; } _globalState = existing; + setDebugLogStateResolver(() => _globalState); } + /** * @internal */ -export const _internalGetGlobalState = () => _globalState; -setDebugLogStateResolver(() => _internalGetGlobalState()); +export function _internalGetGlobalState(): BraintrustState { + _internalSetInitialState(); + return _globalState; +} /** * Register a callback to flush OTEL spans. This is called by @braintrust/otel @@ -1522,9 +1420,9 @@ setDebugLogStateResolver(() => _internalGetGlobalState()); * and we need BTQL to see the complete span tree (both native + OTEL spans). */ export function registerOtelFlush(callback: () => Promise): void { - _globalState?.registerOtelFlush(callback); + _internalGetGlobalState().registerOtelFlush(callback); // Disable span cache since OTEL spans aren't in the local cache - _globalState?.spanCache?.disable(); + _internalGetGlobalState().spanCache.disable(); } export class FailedHTTPResponse extends Error { @@ -1574,7 +1472,7 @@ function rethrowHTTPTransportError( throw error; } -function isLoaderCacheFallbackError(error: unknown): boolean { +export function isLoaderCacheFallbackError(error: unknown): boolean { if (error instanceof FailedHTTPResponse) { return error.status === 408 || error.status === 429 || error.status >= 500; } @@ -1622,7 +1520,7 @@ async function checkResponse(resp: Response) { throw new FailedHTTPResponse(resp.status, resp.statusText, data); } -class HTTPConnection { +export class HTTPConnection { base_url: string; token: string | null; headers: Record; @@ -1641,11 +1539,11 @@ class HTTPConnection { this.fetch = fetch; } - public setFetch(fetch: typeof globalThis.fetch) { + public setFetch(fetch: typeof globalThis.fetch): void { this.fetch = fetch; } - async ping() { + async ping(): Promise { try { const resp = await this.get("ping"); return resp.status === 200; @@ -1654,23 +1552,23 @@ class HTTPConnection { } } - make_long_lived() { + make_long_lived(): void { // Following a suggestion in https://stackoverflow.com/questions/23013220/max-retries-exceeded-with-url-in-requests this._reset(); } - static sanitize_token(token: string) { + static sanitize_token(token: string): string { return token.trim(); } - set_token(token: string) { + set_token(token: string): void { token = HTTPConnection.sanitize_token(token); this.token = token; this._reset(); } // As far as I can tell, you cannot set the retry/backoff factor here - _reset() { + _reset(): void { this.headers = {}; if (this.token) { this.headers["Authorization"] = `Bearer ${this.token}`; @@ -1683,7 +1581,7 @@ class HTTPConnection { | Record | undefined = undefined, config?: RequestInit, - ) { + ): Promise { const { headers, ...rest } = config || {}; const url = new URL(_urljoin(this.base_url, path)); url.search = new URLSearchParams( @@ -1730,7 +1628,7 @@ class HTTPConnection { params?: Record | string, config?: RequestInit, retries: number = 0, - ) { + ): Promise { const { headers, ...rest } = config || {}; // On platforms like Cloudflare, we lose "this" when we make an async call, // so we need to bind it again. @@ -1796,7 +1694,7 @@ class HTTPConnection { args: Record | undefined = undefined, retries: number = 0, signal?: AbortSignal, - ) { + ): Promise { const tries = retries + 1; for (let i = 0; i < tries; i++) { try { @@ -1828,7 +1726,7 @@ class HTTPConnection { object_type: string, args: Record | string | undefined = undefined, signal?: AbortSignal, - ) { + ): Promise { const resp = await this.post(`${object_type}`, args, { headers: { "Content-Type": "application/json" }, signal, @@ -1837,11 +1735,17 @@ class HTTPConnection { } // Custom inspect for Node.js console.log - [Symbol.for("nodejs.util.inspect.custom")](): string { - return `HTTPConnection { + static { + Object.defineProperty(this.prototype, NODE_INSPECT_CUSTOM, { + configurable: true, + writable: true, + value(this: HTTPConnection): string { + return `HTTPConnection { base_url: '${this.base_url}', token: '[REDACTED]' }`; + }, + }); } // Custom toString @@ -1856,12 +1760,12 @@ export interface ObjectMetadata { fullInfo: Record; } -interface ProjectExperimentMetadata { +export interface ProjectExperimentMetadata { project: ObjectMetadata; experiment: ObjectMetadata; } -interface ProjectDatasetMetadata { +export interface ProjectDatasetMetadata { project: ObjectMetadata; dataset: ObjectMetadata; } @@ -1871,20 +1775,16 @@ interface OrgProjectMetadata { project: ObjectMetadata; } -export interface LinkArgs { +interface LinkArgs { org_name?: string; app_url?: string; - project_name?: string; - project_id?: string; -} - -export interface LogOptions { - asyncFlush?: IsAsyncFlush; - computeMetadataArgs?: Record; - linkArgs?: LinkArgs; } -export type PromiseUnless = B extends true ? R : Promise>; +type ProjectMetadataArgs = { + project_group_name?: string; + project_name?: string; + project_id?: string; +}; export interface AttachmentParams { data: string | Blob | ArrayBuffer; @@ -1979,14 +1879,14 @@ export class Attachment extends BaseAttachment { * * @returns The attachment status. */ - async upload() { + async upload(): Promise { return await this.uploader.get(); } /** * The attachment contents. This is a lazy value that will read the attachment contents from disk or memory on first access. */ - async data() { + async data(): Promise { return this._data.get(); } @@ -2089,7 +1989,7 @@ export class Attachment extends BaseAttachment { const errorWrapper = async () => { const status: AttachmentStatus = { upload_status: "done" }; - const state = this.state ?? _globalState; + const state = this.state ?? _internalGetGlobalState(); await withRetry(() => state.login({})); const conn = state.apiConn(); @@ -2135,7 +2035,9 @@ with a Blob/ArrayBuffer, or run the program on Node.js.`, ); } // This could stream the file in the future. - return new LazyValue(async () => new Blob([await readFile(data)])); + return new LazyValue( + async () => new Blob([Uint8Array.from(await readFile(data))]), + ); } else { return new LazyValue(async () => new Blob([data])); } @@ -2208,14 +2110,14 @@ export class ExternalAttachment extends BaseAttachment { * * @returns The attachment status, which will always indicate success. */ - async upload() { + async upload(): Promise<{ upload_status: "done" }> { return { upload_status: "done" as const }; } /** * The attachment contents. This is a lazy value that will read the attachment contents from the external object store on first access. */ - async data() { + async data(): Promise { return this._data.get(); } @@ -2246,7 +2148,10 @@ const attachmentMetadataSchema = z.object({ status: attachmentStatusSchema, }); -type AttachmentMetadata = z.infer; +type AttachmentMetadata = { + downloadUrl: string; + status: AttachmentStatus; +}; /** * A readonly alternative to `Attachment`, which can be used for fetching @@ -2279,7 +2184,7 @@ export class ReadonlyAttachment { * The attachment contents. This is a lazy value that will read the attachment * contents from the object store on first access. */ - async data() { + async data(): Promise { return this._data.get(); } @@ -2300,7 +2205,7 @@ export class ReadonlyAttachment { * This will re-fetch the status each time in case it changes over time. */ async metadata(): Promise { - const state = this.state ?? _globalState; + const state = this.state ?? _internalGetGlobalState(); await state.login({}); const params: Record = { @@ -2428,7 +2333,7 @@ export class JSONAttachment extends Attachment { } } -function logFeedbackImpl( +export function logFeedbackImpl( state: BraintrustState, parentObjectType: SpanObjectTypeV3, parentObjectId: LazyValue, @@ -2441,7 +2346,7 @@ function logFeedbackImpl( comment, source: inputSource, }: LogFeedbackFullArgs, -) { +): void { const source = inputSource ?? "external"; if (!VALID_SOURCES.includes(source)) { @@ -2513,7 +2418,7 @@ function logFeedbackImpl( } } -function updateSpanImpl({ +export function updateSpanImpl({ state, parentObjectType, parentObjectId, @@ -2579,7 +2484,7 @@ export function updateSpan({ "id" | "root_span_id" | "span_id" > & OptionalStateArg): void { - const resolvedState = state ?? _globalState; + const resolvedState = state ?? _internalGetGlobalState(); const components = SpanComponentsV4.fromStr(exported); if (!components.data.row_id) { @@ -2607,7 +2512,7 @@ export function _internalResumeSpan({ exported: string; state?: BraintrustState; }): Span { - const resolvedState = state ?? _globalState; + const resolvedState = state ?? _internalGetGlobalState(); const components = SpanComponentsV4.fromStr(exported); const { row_id, root_span_id, span_id } = components.data; if (!row_id || !root_span_id || !span_id) { @@ -2640,12 +2545,12 @@ export function _internalResumeSpan({ */ export type PropagationContext = Record; -interface ParentSpanIds { +export interface ParentSpanIds { spanId: string; rootSpanId: string; } -interface MultiParentSpanIds { +export interface MultiParentSpanIds { parentSpanIds: string[]; rootSpanId: string; } @@ -2696,7 +2601,7 @@ export async function spanComponentsToObjectId({ state?: BraintrustState; }): Promise { return await spanComponentsToObjectIdLambda( - state ?? _globalState, + state ?? _internalGetGlobalState(), components, )(); } @@ -2766,7 +2671,7 @@ export async function permalink( return NOOP_SPAN_PERMALINK; } - const state = opts?.state ?? _globalState; + const state = opts?.state ?? _internalGetGlobalState(); const getOrgName = async () => { if (opts?.orgName) { return opts.orgName; @@ -2814,7 +2719,7 @@ export async function permalink( // main argument set, so if using this in a spread, like `SpanImpl({ ...args, // ...startSpanParentArgs(...)})`, make sure to put startSpanParentArgs after // the original argument set. -function startSpanParentArgs(args: { +export function startSpanParentArgs(args: { state: BraintrustState; // `parent` may be an exported slug string or an opaque W3C trace-context // (from `extractTraceContextFromHeaders`). @@ -2898,16 +2803,16 @@ function startSpanParentArgs(args: { }; } -export class Logger implements Exportable { +export class Logger implements Exportable { private state: BraintrustState; private lazyMetadata: LazyValue; - private _asyncFlush: IsAsyncFlush | undefined; - private computeMetadataArgs: Record | undefined; - private _linkArgs: LinkArgs | undefined; + private computeMetadataArgs: ProjectMetadataArgs | undefined; + private linkArgs: LinkArgs | undefined; private lastStartTime: number; private lazyId: LazyValue; private calledStartSpan: boolean; private baselineTraceId?: string; + private readonly autoCaptureAttachments: boolean | undefined; // For type identification. public kind = "logger" as const; @@ -2915,15 +2820,19 @@ export class Logger implements Exportable { constructor( state: BraintrustState, lazyMetadata: LazyValue, - logOptions: LogOptions = {}, + options: { + computeMetadataArgs?: ProjectMetadataArgs; + linkArgs?: LinkArgs; + autoCaptureAttachments?: boolean; + } = {}, ) { this.lazyMetadata = lazyMetadata; - this._asyncFlush = logOptions.asyncFlush; - this.computeMetadataArgs = logOptions.computeMetadataArgs; - this._linkArgs = logOptions.linkArgs; + this.computeMetadataArgs = options.computeMetadataArgs; + this.linkArgs = options.linkArgs; this.lastStartTime = getCurrentUnixTimestamp(); - this.lazyId = new LazyValue(async () => await this.id); + this.lazyId = new LazyValue(() => this.id); this.calledStartSpan = false; + this.autoCaptureAttachments = options.autoCaptureAttachments; this.state = state; } @@ -2947,12 +2856,21 @@ export class Logger implements Exportable { return this.state; } + static { + Object.defineProperty(this.prototype, CAPTURE_ATTACHMENTS, { + configurable: true, + get(this: Logger): boolean { + return this.autoCaptureAttachments ?? autoCaptureAttachmentsFromEnv(); + }, + }); + } + private parentObjectType() { return SpanObjectTypeV3.PROJECT_LOGS; } /** - * Log a single event. The event will be batched and uploaded behind the scenes if `logOptions.asyncFlush` is true. + * Log a single event. The event will be batched and uploaded behind the scenes. Call and await {@link Logger.flush} to ensure the event has been uploaded. * * @param event The event to log. * @param event.input: (Optional) the arguments that uniquely define a user input (an arbitrary, JSON serializable object). @@ -2970,7 +2888,7 @@ export class Logger implements Exportable { public log( event: Readonly, options?: { allowConcurrentWithSpans?: boolean }, - ): PromiseUnless { + ): string { if (this.calledStartSpan && !options?.allowConcurrentWithSpans) { throw new Error( "Cannot run toplevel `log` method while using spans. To log to the span, call `logger.traced` and then log with `span.log`", @@ -2979,16 +2897,7 @@ export class Logger implements Exportable { const span = this.startSpanImpl({ startTime: this.lastStartTime, event }); this.lastStartTime = span.end(); - const ret = span.id; - type Ret = PromiseUnless; - if (this.asyncFlush === true) { - return ret as Ret; - } else { - return (async () => { - await this.flush(); - return ret; - })() as Ret; - } + return span.id; } /** @@ -3000,7 +2909,7 @@ export class Logger implements Exportable { body: unknown, level: ApplicationLogLevel, options?: ApplicationLogOptions, - ): PromiseUnless { + ): string { const rendered = renderLogBody(body, options); const timestamp = getCurrentUnixTimestamp(); const current = this.state.contextManager.getParentSpanIds(); @@ -3029,9 +2938,7 @@ export class Logger implements Exportable { metrics: { end: timestamp }, }, }); - type Ret = PromiseUnless; - if (this.asyncFlush === true) return span.id as Ret; - return this.flush().then(() => span.id) as Ret; + return span.id; } /** @@ -3052,18 +2959,12 @@ export class Logger implements Exportable { } /** Capture a trace-level application log. Does not start a trace. */ - public trace( - body: unknown, - options?: ApplicationLogOptions, - ): PromiseUnless { + public trace(body: unknown, options?: ApplicationLogOptions): string { return this.emitLog(body, "trace", options); } /** Capture a debug-level application log. Supports {@link Logger.fmt} templates. */ - public debug( - body: unknown, - options?: ApplicationLogOptions, - ): PromiseUnless { + public debug(body: unknown, options?: ApplicationLogOptions): string { return this.emitLog(body, "debug", options); } @@ -3072,41 +2973,29 @@ export class Logger implements Exportable { * Logs share the active span's IDs, or a baseline trace ID for this logger. * Use {@link Logger.fmt} to preserve a native JavaScript template and its values * in `braintrust.template` metadata fields. - * Returns the row ID, or a promise resolving after flush when asyncFlush is false. + * Returns the row ID. * * @example * logger.info(logger.fmt`User ${userId} paid ${amount.toFixed(2)}`, { * metadata: { source: "checkout" }, * }); */ - public info( - body: unknown, - options?: ApplicationLogOptions, - ): PromiseUnless { + public info(body: unknown, options?: ApplicationLogOptions): string { return this.emitLog(body, "info", options); } /** Capture a warn-level application log. Supports {@link Logger.fmt} templates. */ - public warn( - body: unknown, - options?: ApplicationLogOptions, - ): PromiseUnless { + public warn(body: unknown, options?: ApplicationLogOptions): string { return this.emitLog(body, "warn", options); } /** Capture an error-level application log. Supports {@link Logger.fmt} templates. */ - public error( - body: unknown, - options?: ApplicationLogOptions, - ): PromiseUnless { + public error(body: unknown, options?: ApplicationLogOptions): string { return this.emitLog(body, "error", options); } /** Capture a fatal-level application log. Supports {@link Logger.fmt} templates. */ - public fatal( - body: unknown, - options?: ApplicationLogOptions, - ): PromiseUnless { + public fatal(body: unknown, options?: ApplicationLogOptions): string { return this.emitLog(body, "fatal", options); } @@ -3118,11 +3007,11 @@ export class Logger implements Exportable { public traced( callback: (span: Span) => R, args?: StartSpanArgs & SetCurrentArg, - ): PromiseUnless { + ): R { const { setCurrent, ...argsRest } = args ?? {}; const span = this.startSpan(argsRest); - const ret = runCatchFinally( + return runCatchFinally( () => { if (setCurrent ?? true) { return withCurrent(span, callback); @@ -3130,23 +3019,12 @@ export class Logger implements Exportable { return callback(span); } }, - (e) => { - logError(span, e); - throw e; + (error) => { + logError(span, error); + throw error; }, () => span.end(), ); - type Ret = PromiseUnless; - - if (this.asyncFlush) { - return ret as Ret; - } else { - return (async () => { - const awaitedRet = await ret; - await this.flush(); - return awaitedRet; - })() as Ret; - } } /** @@ -3163,6 +3041,7 @@ export class Logger implements Exportable { private startSpanImpl(args?: StartSpanArgs): Span { return new SpanImpl({ + [CAPTURE_ATTACHMENTS]: this[CAPTURE_ATTACHMENTS], ...args, // Sometimes `args` gets passed directly into this function, and it contains an undefined value for `state`. // To ensure that we always use this logger's state, we override the `state` argument no matter what. @@ -3245,17 +3124,13 @@ export class Logger implements Exportable { return await this.state.bgLogger().flush(); } - get asyncFlush(): IsAsyncFlush | undefined { - return this._asyncFlush; - } - /** * Return the base URL for links (e.g. https://braintrust.dev/app/my-org-name) * if we have the info, otherwise return null. * Resolution order: state -> linkArgs -> env var */ public _getLinkBaseUrl(): string | null { - return _getLinkBaseUrl(this.state, this._linkArgs); + return _getLinkBaseUrl(this.state, this.linkArgs); } /** @@ -3277,27 +3152,22 @@ export class Logger implements Exportable { } } -function castLogger( - logger: Logger | undefined, - asyncFlush?: ToB, -): Logger | undefined { - if (logger === undefined) return undefined; - if (asyncFlush !== undefined && !!asyncFlush !== !!logger.asyncFlush) { - throw new Error( - `Asserted asyncFlush setting ${asyncFlush} does not match stored logger's setting ${logger.asyncFlush}`, - ); - } - return logger as unknown as Logger; -} +export type Logs3OverflowUpload = { + method: "PUT" | "POST"; + signedUrl: string; + headers?: Record; + fields?: Record; + key: string; +}; -export const logs3OverflowUploadSchema = z.object({ - method: z.enum(["PUT", "POST"]), - signedUrl: z.string().url(), - headers: z.record(z.string()).optional(), - fields: z.record(z.string()).optional(), - key: z.string().min(1), -}); -export type Logs3OverflowUpload = z.infer; +export const logs3OverflowUploadSchema: z.ZodType = + z.object({ + method: z.enum(["PUT", "POST"]), + signedUrl: z.string().url(), + headers: z.record(z.string()).optional(), + fields: z.record(z.string()).optional(), + key: z.string().min(1), + }); export type Logs3OverflowInputRow = { object_ids: Record; @@ -3316,7 +3186,10 @@ function constructLogs3Data(items: LogItemWithMeta[]) { return `{"rows": ${constructJsonArray(items.map((i) => i.str))}, "api_version": 2}`; } -export function constructLogs3OverflowRequest(key: string) { +export function constructLogs3OverflowRequest(key: string): { + rows: { type: string; key: string }; + api_version: number; +} { return { rows: { type: LOGS3_OVERFLOW_REFERENCE_TYPE, @@ -3413,7 +3286,7 @@ function stringifyWithOverflowMeta(item: object): LogItemWithMeta { }; } -export function utf8ByteLength(value: string) { +export function utf8ByteLength(value: string): number { if (typeof TextEncoder !== "undefined") { return new TextEncoder().encode(value).length; } @@ -3429,7 +3302,8 @@ export interface BackgroundLoggerOpts { onFlushError?: (error: unknown) => void; } -const DEFAULT_FLUSH_BACKPRESSURE_BYTES = 10 * 1024 * 1024; // 10 MB +const DEFAULT_FLUSH_BACKPRESSURE_BYTES = 10 * 1024 * 1024; +// 10 MB const DEFAULT_MAX_CONCURRENT_LOG_REQUESTS = 8; // Read a numeric environment variable. Unset, empty, whitespace-only and @@ -3539,9 +3413,13 @@ export class TestBackgroundLogger implements BackgroundLogger { } const BACKGROUND_LOGGER_BASE_SLEEP_TIME_S = 1.0; + const HTTP_RETRY_BASE_SLEEP_TIME_S = 1.0; + const HTTP_RETRY_JITTER_MS = 200; -const BTQL_HTTP_RETRIES = 3; + +export const BTQL_HTTP_RETRIES = 3; + const RETRYABLE_HTTP_STATUS_CODES = new Set([500, 502, 503, 504]); function isRetryableHTTPError(error: unknown): boolean { @@ -3792,7 +3670,7 @@ class HTTPBackgroundLogger implements BackgroundLogger { return this._flushBackpressureBytes; } - log(items: LazyValue[]) { + log(items: LazyValue[]): void { if (this._disabled) { return; } @@ -3908,7 +3786,10 @@ class HTTPBackgroundLogger implements BackgroundLogger { for (const group of allItems) { for (let i = 0; i < group.length; i++) { if ("span_id" in group[i]) { - group[i] = await this.typedAttachments.process(group[i]); + group[i] = await this.typedAttachments.process( + group[i], + Reflect.get(group[i], CAPTURE_ATTACHMENTS), + ); } } } @@ -4041,13 +3922,17 @@ class HTTPBackgroundLogger implements BackgroundLogger { if (this.exportCustomizers.length) { mergedItems = mergedItems.map((group) => - group.map( - (item) => - customizeSpanExport( - item as SpanExportData, - this.exportCustomizers, - ) as BackgroundLogEvent, - ), + group.map((item) => { + const customized = customizeSpanExport( + item as SpanExportData, + this.exportCustomizers, + ) as BackgroundLogEvent; + Object.defineProperty(customized, CAPTURE_ATTACHMENTS, { + value: Reflect.get(item, CAPTURE_ATTACHMENTS), + configurable: true, + }); + return customized; + }), ); // Customizers may strip or replace attachment references; don't upload // content that is no longer referenced by any exported record. @@ -4404,6356 +4289,2784 @@ class HTTPBackgroundLogger implements BackgroundLogger { } // Should only be called by BraintrustState. - public internalReplaceApiConn(apiConn: HTTPConnection) { + public internalReplaceApiConn(apiConn: HTTPConnection): void { this.apiConn = new LazyValue(async () => apiConn); } - public disable() { + public disable(): void { this._disabled = true; this.triggerActiveFlush(); } - public enforceQueueSizeLimit(enforce: boolean) { + public enforceQueueSizeLimit(enforce: boolean): void { this.queue.enforceQueueSizeLimit(enforce); } } -type InitOpenOption = { - open?: IsOpen; -}; - -/** - * Reference to a dataset by ID and optional explicit selector. - */ -type DatasetSelection = { - version?: string; - environment?: string; - snapshotName?: string; -}; - -type DatasetSnapshotNameLookup = { - snapshotName: string; - xactId?: never; -}; - -type DatasetSnapshotXactLookup = { - snapshotName?: never; - xactId: string; -}; - -export type DatasetSnapshotLookup = - | DatasetSnapshotNameLookup - | DatasetSnapshotXactLookup; - -function isDatasetSnapshotNameLookup( - lookup: DatasetSnapshotLookup, -): lookup is DatasetSnapshotNameLookup { - return "snapshotName" in lookup; +declare global { + // Set by the bt eval runner when CLI-controlled BTQL should be pushed down + // into dataset-backed evals. + // eslint-disable-next-line no-var + var __bt_eval_internal_btql: Record | undefined; } -function assertDatasetSnapshotLookup(lookup: { - snapshotName?: string; - xactId?: string; -}): asserts lookup is DatasetSnapshotLookup { - const hasSnapshotName = lookup.snapshotName !== undefined; - const hasXactId = lookup.xactId !== undefined; - if (hasSnapshotName === hasXactId) { - throw new Error("Exactly one of snapshotName or xactId must be provided"); +// Note: the argument names *must* serialize the same way as the argument names +// for the corresponding python function, because this function may be invoked +// from arguments serialized elsewhere. +async function computeLoggerMetadata( + state: BraintrustState, + { project_name, project_id, project_group_name }: ProjectMetadataArgs, +) { + await state.login({}); + const org_id = state.orgId!; + if (!isEmpty(project_id) && !isEmpty(project_name)) { + return { + org_id, + project: { id: project_id, name: project_name, fullInfo: {} }, + }; } -} - -type DatasetPinState = { - lazyPinnedVersion?: LazyValue; - pinnedEnvironment?: string; - pinnedSnapshotName?: string; -}; - -export type DatasetRef = { - id: string; -} & DatasetSelection; -export interface ParametersRef { - id: string; - version?: string; -} - -export type InitOptions = FullLoginOptions & { - experiment?: string; - description?: string; - dataset?: AnyDataset | DatasetRef; - _internal_btql?: Record; - parameters?: ParametersRef | RemoteEvalParameters; - update?: boolean; - baseExperiment?: string; - isPublic?: boolean; - metadata?: Record; - tags?: string[]; - gitMetadataSettings?: GitMetadataSettings; - projectId?: string; - baseExperimentId?: string; - repoInfo?: RepoInfo; - setCurrent?: boolean; - state?: BraintrustState; -} & InitOpenOption; + const groupName = project_group_name || undefined; + const cache = state.projectMetadataCache; + const key = JSON.stringify([ + state.appUrl, + org_id, + state.loginToken, + isEmpty(project_id) + ? ["name", project_name || GLOBAL_PROJECT, groupName] + : ["id", project_id], + ]); + const cached = cache.get(key); + if (cached && Date.now() < cached.expiresAt) { + return structuredClone(await cached.promise); + } -export type FullInitOptions = { - project?: string; -} & InitOptions; - -function getExperimentDatasetFilter({ - dataset, - _internal_btql, -}: { - dataset?: AnyDataset | DatasetRef; - _internal_btql?: Record; -}): Record | undefined { - if (_internal_btql !== undefined) { - return _internal_btql; - } - - if (!(dataset instanceof Dataset)) { - return undefined; + const conn = state.appConn(); + const controller = new AbortController(); + let timeout: ReturnType | undefined; + const entry = { + // The lookup timeout bounds pending requests; the cache TTL starts on success. + expiresAt: Infinity, + promise: Promise.race([ + (async (): Promise => { + if (isEmpty(project_id)) { + const response = await conn.post_json( + "api/project/register", + { + project_name: project_name || GLOBAL_PROJECT, + org_id, + project_group_name: groupName, + }, + controller.signal, + ); + return { + org_id, + project: { + id: response.project.id, + name: response.project.name, + fullInfo: response.project, + }, + }; + } + const response = await conn.get_json( + "api/project", + { id: project_id }, + 0, + controller.signal, + ); + return { + org_id, + project: { + id: project_id, + name: response.name, + fullInfo: response.project, + }, + }; + })(), + new Promise((_, reject) => { + timeout = setTimeout(() => { + const error = new Error( + "Braintrust project lookup timed out after 30 seconds.", + ); + reject(error); + controller.abort(error); + }, PROJECT_LOOKUP_TIMEOUT_MS); + }), + ]), + }; + cache.set(key, entry); + try { + const metadata = await entry.promise; + entry.expiresAt = Date.now() + PROJECT_CACHE_TTL_MS; + return structuredClone(metadata); + } catch (error) { + if (cache.get(key) === entry) { + cache.delete(key); + } + throw error; + } finally { + clearTimeout(timeout); } - - const datasetFilter = Reflect.get(dataset, "_internal_btql"); - return isObject(datasetFilter) ? datasetFilter : undefined; -} - -function getInternalBtqlLimit( - internalBtql?: Record, -): number | undefined { - const limit = internalBtql?.["limit"]; - return typeof limit === "number" ? limit : undefined; } -type InitializedExperiment = - IsOpen extends true ? ReadonlyExperiment : Experiment; +export type InitLoggerOptions = FullLoginOptions & { + /** + * Automatically capture inline media as attachments in instrumentation. Explicit booleans override + * BRAINTRUST_AUTO_CAPTURE_ATTACHMENTS (1 or true); capture is otherwise disabled. + * With setCurrent: false, this logger and its child spans have an independent policy + * and an omitted option uses the environment, not the global logger's setting. + * Current loggers also update the state's default when this option is supplied; + * omitting it preserves that default. Explicitly logged attachments are unaffected. + */ + autoCaptureAttachments?: boolean; + projectName?: string; + projectId?: string; + _createInProjectGroup?: string; + environment?: SpanOriginEnvironment; + setCurrent?: boolean; + state?: BraintrustState; +}; /** - * Log in, and then initialize a new experiment in a specified project. If the project does not exist, it will be created. + * Create a new logger in a specified project. If the project does not exist, it will be created. * - * @param options Options for configuring init(). - * @param options.project The name of the project to create the experiment in. Must specify at least one of `project` or `projectId`. - * @param options.experiment The name of the experiment to create. If not specified, a name will be generated automatically. - * @param options.description An optional description of the experiment. - * @param options.dataset (Optional) A dataset to associate with the experiment. You can pass in the name of the dataset (in the same project) or a dataset object (from any project). - * @param options.update If the experiment already exists, continue logging to it. If it does not exist, creates the experiment with the specified arguments. - * @param options.baseExperiment An optional experiment name to use as a base. If specified, the new experiment will be summarized and compared to this experiment. Otherwise, it will pick an experiment by finding the closest ancestor on the default (e.g. main) branch. - * @param options.isPublic An optional parameter to control whether the experiment is publicly visible to anybody with the link or privately visible to only members of the organization. Defaults to private. + * @param options Additional options for configuring init(). + * @param options.projectName The name of the project to log into. If unspecified, will default to the Global project. + * @param options.projectId The id of the project to log into. This takes precedence over projectName if specified. + * @param options._createInProjectGroup Experimental: the name of an existing project group to create the project in. Existing projects must already belong to the group, otherwise registration fails with a 409. Membership is re-checked on each fresh project lookup, so moving the project out of the group makes new loggers fail with a 409. Ignored when projectId is provided. * @param options.appUrl The URL of the Braintrust App. Defaults to https://www.braintrust.dev. - * @param options.apiKey The API key to use. If the parameter is not specified, will try to use the `BRAINTRUST_API_KEY` environment variable. In Node.js, if that is unset, will try the nearest `.env.braintrust` file in the current working directory or parent directories. If no API key is specified, will prompt the user to login. + * @param options.apiKey The API key to use. If the parameter is not specified, will try to use the `BRAINTRUST_API_KEY` environment variable. In Node.js, + * if that is unset, will try the nearest `.env.braintrust` file in the current working directory or parent directories. If no API key is specified, will prompt the user to login. * @param options.orgName (Optional) The name of a specific organization to connect to. This is useful if you belong to multiple. - * @param options.metadata (Optional) A dictionary with additional data about the test example, model outputs, or just about anything else that's relevant, that you can use to help find and analyze examples later. For example, you could log the `prompt`, example's `id`, or anything else that would be useful to slice/dice later. The values in `metadata` can be any JSON-serializable type, but its keys must be strings. - * @param options.gitMetadataSettings (Optional) Settings for collecting git metadata. By default, Braintrust collects the git metadata fields allowed by your organization's git metadata settings. If those settings are absent, git metadata is not collected unless this option is set. + * @param options.forceLogin Login again, even if you have already logged in (by default, the logger will not login if you are already logged in) + * @param options.debugLogLevel Enables internal Braintrust SDK troubleshooting output. Use `"error"`, `"warn"`, `"info"`, or `"debug"` to choose an explicit level, or `false` to explicitly disable it. If omitted, the SDK stays silent unless `BRAINTRUST_DEBUG_LOG_LEVEL` is set. * @param setCurrent If true (the default), set the global current-experiment to the newly-created one. - * @param options.open If the experiment already exists, open it in read-only mode. Throws an error if the experiment does not already exist. - * @param options.projectId The id of the project to create the experiment in. This takes precedence over `project` if specified. - * @param options.baseExperimentId An optional experiment id to use as a base. If specified, the new experiment will be summarized and compared to this. This takes precedence over `baseExperiment` if specified. - * @param options.repoInfo (Optional) Explicitly specify the git metadata for this experiment. This takes precedence over `gitMetadataSettings` if specified. - * @param options.tags (Optional) A list of tags to attach to the experiment. - * @returns The newly created Experiment. - */ -export function init( - options: Readonly>, -): InitializedExperiment; - -/** - * Legacy form of `init` which accepts the project name as the first parameter, - * separately from the remaining options. See `init(options)` for full details. - */ -export function init( - project: string, - options?: Readonly>, -): InitializedExperiment; - -/** - * Combined overload implementation of `init`. Do not call this directly. - * Instead, call `init(options)` or `init(project, options)`. + * @returns The newly created Logger. */ -export function init( - projectOrOptions: string | Readonly>, - optionalOptions?: Readonly>, -): InitializedExperiment { - const options = ((): Readonly> => { - if (typeof projectOrOptions === "string") { - return { ...optionalOptions, project: projectOrOptions }; - } else { - if (optionalOptions !== undefined) { - throw new Error( - "Cannot specify options struct as both parameters. Must call either init(project, options) or init(options).", - ); - } - return projectOrOptions; - } - })(); - +export function initLogger(options: Readonly = {}): Logger { const { - project, - experiment, - description, - dataset, - _internal_btql, - parameters, - baseExperiment, - isPublic, - open, - update, + projectName, + projectId, + _createInProjectGroup, appUrl, apiKey, orgName, forceLogin, + debugLogLevel, + environment, fetch, - metadata, - tags, - gitMetadataSettings, - projectId, - baseExperimentId, - repoInfo, state: stateArg, - } = options; + } = options || {}; - if (!project && !projectId) { - throw new Error("Must specify at least one of project or projectId"); - } + const computeMetadataArgs = { + project_name: projectName, + project_id: projectId, + project_group_name: _createInProjectGroup, + }; - if (open && update) { - throw new Error("Cannot open and update an experiment at the same time"); - } + const linkArgs = { + org_name: orgName, + app_url: appUrl, + }; - const state = stateArg ?? _globalState; + const state = stateArg ?? _internalGetGlobalState(); state._internalSetTraceContextSigningSecret(apiKey); + state.setDebugLogLevel(debugLogLevel); + state.spanOriginEnvironment = detectSpanOriginEnvironment(environment); - // Ensure unlimited queue for init() calls (experiments) - // Experiments should never drop data - state.enforceQueueSizeLimit(false); - - if (open) { - if (isEmpty(experiment)) { - throw new Error(`Cannot open an experiment without specifying its name`); - } - - const lazyMetadata: LazyValue = new LazyValue( - async () => { - await state.login({ apiKey, appUrl, orgName, fetch, forceLogin }); - const args: Record = { - project_name: project, - project_id: projectId, - org_name: state.orgName, - experiment_name: experiment, - }; - - const response = await state - .appConn() - .post_json("api/experiment/get", args); - - if (response.length === 0) { - throw new Error( - `Experiment ${experiment} not found in project ${ - projectId ?? project - }.`, - ); - } - - const info = response[0]; - return { - project: { - id: info.project_id, - name: project ?? "UNKNOWN_PROJECT", - fullInfo: {}, - }, - experiment: { - id: info.id, - name: info.name, - fullInfo: info, - }, - }; - }, - ); - - return new ReadonlyExperiment( - stateArg ?? _globalState, - lazyMetadata, - ) as InitializedExperiment; - } - - const lazyMetadata: LazyValue = new LazyValue( + // Enable queue size limit enforcement for initLogger() calls + // This ensures production observability doesn't OOM customer processes + state.enforceQueueSizeLimit(true); + const lazyMetadata: LazyValue = new LazyValue( async () => { - await state.login({ apiKey, appUrl, orgName }); - const args: Record = { - project_name: project, - project_id: projectId, - org_id: state.orgId, - update, - }; - - if (experiment) { - args["experiment_name"] = experiment; - } - - if (description) { - args["description"] = description; - } - - const repoInfoArg = await (async (): Promise => { - if (repoInfo) { - return repoInfo; - } - const mergedGitMetadataSettings = - state.gitMetadataSettings == null - ? (gitMetadataSettings ?? { collect: "none" as const }) - : mergeGitMetadataSettings( - state.gitMetadataSettings, - gitMetadataSettings ?? { collect: "all" as const }, - ); - return await iso.getRepoInfo(mergedGitMetadataSettings); - })(); - - if (repoInfoArg) { - args["repo_info"] = repoInfoArg; - } - - if (baseExperimentId) { - args["base_exp_id"] = baseExperimentId; - } else if (baseExperiment) { - args["base_experiment"] = baseExperiment; - } else { - args["ancestor_commits"] = await iso.getPastNAncestors(); - } - - if (dataset !== undefined) { - const datasetSelection = await serializeDatasetForExperiment({ - dataset, - state, - }); - args["dataset_id"] = datasetSelection.datasetId; - if (datasetSelection.datasetVersion !== undefined) { - args["dataset_version"] = datasetSelection.datasetVersion; - } - } - - const datasetFilter = getExperimentDatasetFilter({ - dataset, - _internal_btql, + // Otherwise actually log in. + await state.login({ + orgName, + apiKey, + appUrl, + forceLogin, + fetch, }); - if (datasetFilter !== undefined) { - args["internal_metadata"] = { - dataset_filter: datasetFilter, - }; - } - - if (parameters !== undefined) { - if (RemoteEvalParameters.isParameters(parameters)) { - args["parameters_id"] = parameters.id; - args["parameters_version"] = parameters.version; - } else { - args["parameters_id"] = parameters.id; - if (parameters.version !== undefined) { - args["parameters_version"] = parameters.version; - } - } - } - - if (isPublic !== undefined) { - args["public"] = isPublic; - } - - if (metadata) { - args["metadata"] = metadata; - } - - if (tags) { - validateTags(tags); - args["tags"] = tags; - } - - let response = null; - while (true) { - try { - response = await state - .appConn() - .post_json("api/experiment/register", args); - break; - } catch (e: any) { - if ( - args["base_experiment"] && - `${"data" in e && e.data}`.includes("base experiment") - ) { - debugLogger - .forState(state) - .warn(`Base experiment ${args["base_experiment"]} not found.`); - delete args["base_experiment"]; - } else { - throw e; - } - } - } - - return { - project: { - id: response.project.id, - name: response.project.name, - fullInfo: response.project, - }, - experiment: { - id: response.experiment.id, - name: response.experiment.name, - created: response.experiment.created, - fullInfo: response.experiment, - }, - }; + return computeLoggerMetadata(state, computeMetadataArgs); }, ); - const ret = new Experiment( - state, - lazyMetadata, - dataset !== undefined ? (dataset as AnyDataset) : undefined, - ); - if (options.setCurrent ?? true) { - state.currentExperiment = ret; + const setCurrent = options.setCurrent ?? true; + if (setCurrent && options.autoCaptureAttachments !== undefined) { + state.autoCaptureAttachments = options.autoCaptureAttachments; + } + const ret = new Logger(state, lazyMetadata, { + autoCaptureAttachments: + options.autoCaptureAttachments ?? + (setCurrent ? state.autoCaptureAttachments : undefined), + computeMetadataArgs, + linkArgs, + }); + if (setCurrent) { + state.currentLogger = ret; } - return ret as InitializedExperiment; + return ret; } /** - * Alias for init(options). + * Options for logging in to Braintrust. */ -export function initExperiment( - options: Readonly>, -): InitializedExperiment; +export interface LoginOptions { + /** + * The URL of the Braintrust App. Defaults to https://www.braintrust.dev. You should not need + * to change this unless you are doing the "Full" deployment. + */ + appUrl?: string; + /** + * The API key to use. If the parameter is not specified, will try to use the `BRAINTRUST_API_KEY` environment variable. In Node.js, if that is unset, will try the nearest `.env.braintrust` file in the current working directory or parent directories. + */ + apiKey?: string; + /** + * The name of a specific organization to connect to. Since API keys are scoped to organizations, this parameter is usually + * unnecessary unless you are logging in with a JWT. + */ + orgName?: string; + /** + * A custom fetch implementation to use. + */ + fetch?: typeof globalThis.fetch; + /** + * By default, the SDK installs an event handler that flushes pending writes on the `beforeExit` event. + * If true, this event handler will _not_ be installed. + */ + noExitFlush?: boolean; + /** + * Calls this function if there's an error in the background flusher. + */ + onFlushError?: (error: unknown) => void; + /** + * If true, disables the local span cache used to optimize scorer access + * to trace data. When disabled, scorers will always fetch spans from the + * server. Defaults to false. + */ + disableSpanCache?: boolean; + /** + * Controls internal Braintrust SDK troubleshooting output. + * + * Use `"error"`, `"warn"`, `"info"`, or `"debug"` to control how much + * internal SDK troubleshooting output is emitted. Use `false` to explicitly + * disable this output. + * + * When omitted, the SDK remains silent unless + * `BRAINTRUST_DEBUG_LOG_LEVEL` is set to `"error"`, `"warn"`, `"info"`, or + * `"debug"`. This option only affects local console output; it does not + * change what data is logged to Braintrust. + */ + debugLogLevel?: DebugLogLevel | false; +} -/** - * Alias for init(project, options). - */ -export function initExperiment( - project: string, - options?: Readonly>, -): InitializedExperiment; - -/** - * Combined overload implementation of `initExperiment`, which is an alias for - * `init`. Do not call this directly. Instead, call `initExperiment(options)` or - * `initExperiment(project, options)`. - */ -export function initExperiment( - projectOrOptions: string | Readonly>, - optionalOptions?: Readonly>, -): InitializedExperiment { - const options = ((): Readonly> => { - if (typeof projectOrOptions === "string") { - return { ...optionalOptions, project: projectOrOptions }; - } else { - if (optionalOptions !== undefined) { - throw new Error( - "Cannot specify options struct as both parameters. Must call either init(project, options) or init(options).", - ); - } - return projectOrOptions; - } - })(); - return init(options); -} +export type FullLoginOptions = LoginOptions & { + forceLogin?: boolean; +}; /** - * @deprecated Use {@link init} instead. + * Set a global masking function that will be applied to all logged data before sending to Braintrust. + * The masking function will be applied after records are merged but before they are sent to the backend. + * Internally, masking is a state-local export customizer that runs after any + * span customizers and also covers non-span records such as dataset rows and feedback. + * + * @param maskingFunction A function that takes a JSON-serializable object and returns a masked version. + * Set to null to disable masking. */ -export function withExperiment( - project: string, - callback: (experiment: Experiment) => R, - options: Readonly & SetCurrentArg> = {}, -): R { - debugLogger - .forState(options.state) - .warn( - "withExperiment is deprecated and will be removed in a future version of braintrust. Simply create the experiment with `init`.", - ); - const experiment = init(project, options); - return callback(experiment); +export function setMaskingFunction( + maskingFunction: ((value: unknown) => unknown) | null, +): void { + _internalGetGlobalState().setMaskingFunction(maskingFunction); } /** - * @deprecated Use {@link initLogger} instead. + * Log into Braintrust. This will prompt you for your API token, which you can find at + * https://www.braintrust.dev/app/token. This method is called automatically by `init()`. + * + * @param options Options for configuring login(). + * @param options.appUrl The URL of the Braintrust App. Defaults to https://www.braintrust.dev. + * @param options.apiKey The API key to use. If the parameter is not specified, will try to use the `BRAINTRUST_API_KEY` environment variable. In Node.js, + * if that is unset, will try the nearest `.env.braintrust` file in the current working directory or parent directories. If no API key is specified, will prompt the user to login. + * @param options.orgName (Optional) The name of a specific organization to connect to. This is useful if you belong to multiple. + * @param options.forceLogin Login again, even if you have already logged in (by default, this function will exit quickly if you have already logged in) */ -export function withLogger( - callback: (logger: Logger) => R, - options: Readonly & SetCurrentArg> = {}, -): R { - debugLogger - .forState(options.state) - .warn( - "withLogger is deprecated and will be removed in a future version of braintrust. Simply create the logger with `initLogger`.", - ); - const logger = initLogger(options); - return callback(logger); -} - -type UseOutputOption = { - useOutput?: IsLegacyDataset; -}; - -declare global { - // Set by the bt eval runner when CLI-controlled BTQL should be pushed down - // into dataset-backed evals. - // eslint-disable-next-line no-var - var __bt_eval_internal_btql: Record | undefined; -} - -export type InitDatasetOptions = - FullLoginOptions & { - dataset?: string; - datasetId?: string; - description?: string; - version?: string; - environment?: string; - snapshotName?: string; - projectId?: string; - metadata?: Record; - state?: BraintrustState; - _internal_btql?: Record; - } & UseOutputOption; +export async function login( + options: LoginOptions & { forceLogin?: boolean } = {}, +): Promise { + const { forceLogin = false } = options || {}; -export type FullInitDatasetOptions = { - project?: string; -} & InitDatasetOptions; + if (!_internalGetGlobalState()) { + _internalSetInitialState(); + } -async function getDatasetSnapshots( - params: - | { - state: BraintrustState; - datasetId: string; + const state = _internalGetGlobalState(); + state.setDebugLogLevel(options.debugLogLevel); + if (state.loggedIn && !forceLogin) { + // We have already logged in. If any provided login inputs disagree with our + // existing settings, raise an Exception warning the user to try again with + // `forceLogin: true`. + function checkUpdatedParam( + varname: string, + arg: string | undefined, + orig: string | null, + ) { + if (!isEmpty(arg) && !isEmpty(orig) && arg !== orig) { + throw new Error( + `Re-logging in with different ${varname} (${arg}) than original (${orig}). To force re-login, pass \`forceLogin: true\``, + ); } - | ({ - state: BraintrustState; - datasetId: string; - } & DatasetSnapshotLookup), -): Promise { - const { state, datasetId } = params; - return datasetSnapshotSchema.array().parse( - await state.appConn().post_json("api/dataset_snapshot/get", { - dataset_id: datasetId, - ...("snapshotName" in params ? { name: params.snapshotName } : {}), - ...("xactId" in params ? { xact_id: params.xactId } : {}), - }), - ); -} - -async function getDatasetSnapshot( - params: { - state: BraintrustState; - datasetId: string; - } & DatasetSnapshotLookup, -): Promise { - assertDatasetSnapshotLookup(params); - const snapshots = await getDatasetSnapshots(params); - if (snapshots.length > 1) { - throw new Error( - isDatasetSnapshotNameLookup(params) - ? `Expected a unique dataset snapshot named "${params.snapshotName}" for ${params.datasetId}` - : `Expected a unique dataset snapshot for xact_id "${params.xactId}" in ${params.datasetId}`, + } + checkUpdatedParam("appUrl", options.appUrl, state.appUrl); + checkUpdatedParam( + "apiKey", + options.apiKey + ? HTTPConnection.sanitize_token(options.apiKey) + : undefined, + state.loginToken, ); - } - return snapshots[0]; -} - -function normalizeDatasetSelection({ - version, - environment, - snapshotName, -}: DatasetSelection): DatasetSelection { - if (version !== undefined) { - return { version }; + checkUpdatedParam("orgName", options.orgName, state.orgName); + return state; } - if (snapshotName !== undefined) { - return { snapshotName }; - } + await state.login(options); + return state; +} - if (environment !== undefined) { - return { environment }; - } +async function loginToLoaderRequestState({ + appUrl, + apiKey, + orgName, + fetch, +}: Required> & + Pick): Promise { + let orgId: string; + let apiUrl: string; - return {}; -} + if (apiKey === TEST_API_KEY) { + orgId = "test-org-id"; + apiUrl = "https://braintrust.dev/fake-api-url"; + } else { + let loginResponse: Response; + try { + loginResponse = await fetch(_urljoin(appUrl, `/api/apikey/login`), { + method: "POST", + headers: { + "Content-Type": "application/json", + Authorization: `Bearer ${apiKey}`, + }, + }); + } catch (error) { + throw new HTTPTransportError(error); + } -async function resolveDatasetSnapshotName({ - state, - datasetId, - snapshotName, -}: { - state: BraintrustState; - datasetId: string; - snapshotName: string; -}): Promise { - const match = await getDatasetSnapshot({ - state, - datasetId, - snapshotName, - }); - if (match === undefined) { - throw new Error( - `Dataset snapshot "${snapshotName}" not found for ${datasetId}`, + const info = await readJSONResponse( + await checkResponse(loginResponse), + true, ); + const org = selectLoginOrg(info.org_info, orgName); + orgId = org.id; + apiUrl = iso.getEnv("BRAINTRUST_API_URL") || org.api_url; + if (!apiUrl) { + throw new Error( + orgName + ? `Unable to log into organization '${orgName}'. Are you sure this credential is scoped to the organization?` + : "Unable to log into any organization with the provided credential.", + ); + } } - return match.xact_id; -} -async function resolveDatasetSnapshotNameForMetadata({ - state, - lazyMetadata, - snapshotName, -}: { - state: BraintrustState; - lazyMetadata: LazyValue; - snapshotName: string; -}): Promise { - const metadata = await lazyMetadata.get(); - return await resolveDatasetSnapshotName({ - state, - datasetId: metadata.dataset.id, - snapshotName, - }); + const apiConnection = new HTTPConnection(apiUrl, fetch, true); + apiConnection.set_token(apiKey); + apiConnection.make_long_lived(); + return { + appUrl, + orgId, + apiConn: () => apiConnection, + }; } -async function resolveDatasetEnvironment({ - state, - datasetId, - environment, -}: { - state: BraintrustState; - datasetId: string; - environment: string; -}): Promise { - const environmentObjectPath = `environment-object/dataset/${datasetId}/${encodeURIComponent(environment)}`; - const response = - state.orgName == null - ? await state.apiConn().get_json(environmentObjectPath) - : await state.apiConn().get_json(environmentObjectPath, { - org_name: state.orgName, - }); - return z.object({ object_version: z.string() }).parse(response) - .object_version; -} +export async function loginToState( + options: LoginOptions = {}, +): Promise { + const { + appUrl = iso.getEnv("BRAINTRUST_APP_URL") || "https://www.braintrust.dev", + apiKey: apiKeyArg, + orgName = iso.getEnv("BRAINTRUST_ORG_NAME"), + fetch = globalThis.fetch, + } = options || {}; -async function resolveDatasetEnvironmentForMetadata({ - state, - lazyMetadata, - environment, -}: { - state: BraintrustState; - lazyMetadata: LazyValue; - environment: string; -}): Promise { - const metadata = await lazyMetadata.get(); - return await resolveDatasetEnvironment({ - state, - datasetId: metadata.dataset.id, - environment, - }); -} + const apiKey = + apiKeyArg !== undefined ? apiKeyArg : await iso.getBraintrustApiKey(); + const appPublicUrl = iso.getEnv("BRAINTRUST_APP_PUBLIC_URL") || appUrl; -async function serializeDatasetForExperiment({ - dataset, - state, -}: { - dataset: AnyDataset | DatasetRef; - state: BraintrustState; -}): Promise<{ datasetId: string; datasetVersion?: string }> { - if (!Dataset.isDataset(dataset)) { - const selection = normalizeDatasetSelection(dataset); + const state = new BraintrustState(options); + state.resetLoginInfo(); - if (selection.version !== undefined) { - return { - datasetId: dataset.id, - datasetVersion: selection.version, - }; - } + state.appUrl = appUrl; + state.appPublicUrl = appPublicUrl; - if (selection.snapshotName !== undefined) { - return { - datasetId: dataset.id, - datasetVersion: await resolveDatasetSnapshotName({ - state, - datasetId: dataset.id, - snapshotName: selection.snapshotName, + let conn = null; + + if (!apiKey) { + throw new Error( + "Please specify an api key (e.g. by setting BRAINTRUST_API_KEY).", + ); + } else if (apiKey === TEST_API_KEY) { + // This is a weird hook that lets us skip logging in and mocking out the org info. + const testOrgInfo = [ + { + id: "test-org-id", + name: "test-org-name", + api_url: "https://braintrust.dev/fake-api-url", + }, + ]; + state.loggedIn = true; + state.loginToken = TEST_API_KEY; + _saveOrgInfo(state, testOrgInfo, testOrgInfo[0].name); + return state; + } else { + const controller = new AbortController(); + let timeout: ReturnType | undefined; + let info; + try { + // Bound both headers and body reads, including custom fetches that ignore + // cancellation, so a stalled request cannot block subsequent logins. + info = await Promise.race([ + (async () => { + const response = await fetch(_urljoin(appUrl, `/api/apikey/login`), { + method: "POST", + headers: { + "Content-Type": "application/json", + Authorization: `Bearer ${apiKey}`, + }, + signal: controller.signal, + }); + return readJSONResponse(await checkResponse(response)); + })(), + new Promise((_, reject) => { + timeout = setTimeout(() => { + const error = new Error( + "Braintrust login timed out after 30 seconds.", + ); + reject(error); + controller.abort(error); + }, LOGIN_TIMEOUT_MS); }), - }; + ]); + } finally { + clearTimeout(timeout); } - if (selection.environment !== undefined) { - return { - datasetId: dataset.id, - datasetVersion: await resolveDatasetEnvironment({ - state, - datasetId: dataset.id, - environment: selection.environment, - }), - }; + _saveOrgInfo(state, info.org_info, orgName); + if (!state.apiUrl) { + if (orgName) { + throw new Error( + `Unable to log into organization '${orgName}'. Are you sure this credential is scoped to the organization?`, + ); + } else { + throw new Error( + "Unable to log into any organization with the provided credential.", + ); + } } - return { - datasetId: dataset.id, - }; - } + conn = state.apiConn(); + conn.set_token(apiKey); - const evalData = await dataset.toEvalData(); - const selection = normalizeDatasetSelection({ - version: evalData.dataset_version, - environment: evalData.dataset_environment, - snapshotName: evalData.dataset_snapshot_name, - }); + if (!conn) { + throw new Error("Conn should be set at this point (a bug)"); + } - if (selection.version !== undefined) { - return { - datasetId: evalData.dataset_id, - datasetVersion: selection.version, - }; - } + conn.make_long_lived(); - const datasetVersion = await dataset.version(); - if (datasetVersion !== undefined) { - return { - datasetId: evalData.dataset_id, - datasetVersion, - }; + // Set the same token in the API + state.appConn().set_token(apiKey); + if (state.proxyUrl) { + state.proxyConn().set_token(apiKey); + } + state.loginToken = conn.token; + state.loggedIn = true; + + // Replace the global logger's apiConn with this one. + state.loginReplaceApiConn(conn); } - return { - datasetId: evalData.dataset_id, - }; + return state; } +// XXX We should remove these global functions now /** - * Initialize a dataset by name or ID. When initializing by name, the dataset and its project will be created if they do not exist. + * Log a single event to the current experiment. The event will be batched and uploaded behind the scenes. * - * @param options Options for configuring initDataset(). - * @param options.project The name of the project to create the dataset in. Must specify at least one of `project` or `projectId` unless `datasetId` is provided. - * @param options.dataset The name of the dataset to create. If not specified, a name will be generated automatically. Ignored if `datasetId` is provided. - * @param options.datasetId The ID of an existing dataset. Takes precedence over `dataset` and does not require `project` or `projectId`. - * @param options.description An optional description when initializing a dataset by name. Cannot be used with `datasetId`. - * @param options.version Pin the dataset to a specific version xact_id. If `snapshotName` or `environment` are also provided, `version` takes precedence. - * @param options.snapshotName Pin the dataset to the version captured by this named snapshot. If `environment` is also provided, `snapshotName` takes precedence. - * @param options.environment Pin the dataset to the version tagged with this environment slug. - * @param options.appUrl The URL of the Braintrust App. Defaults to https://www.braintrust.dev. - * @param options.apiKey The API key to use. If the parameter is not specified, will try to use the `BRAINTRUST_API_KEY` environment variable. In Node.js, if that is unset, will try the nearest `.env.braintrust` file in the current working directory or parent directories. If no API key is specified, will prompt the user to login. - * @param options.orgName (Optional) The name of a specific organization to connect to. This is useful if you belong to multiple. - * @param options.projectId The id of the project to create the dataset in. This takes precedence over `project` if specified and is not required when `datasetId` is provided. - * @param options.metadata A dictionary with additional data when initializing a dataset by name. Cannot be used with `datasetId`. The values in `metadata` can be any JSON-serializable type, but its keys must be strings. - * @param options.useOutput (Deprecated) If true, records will be fetched from this dataset in the legacy format, with the "expected" field renamed to "output". This option will be removed in a future version of Braintrust. - * @returns The initialized Dataset. + * @param event The event to log. See {@link Experiment.log} for full details. + * @returns The `id` of the logged event. */ -export function initDataset< - IsLegacyDataset extends boolean = typeof DEFAULT_IS_LEGACY_DATASET, ->( - options: Readonly>, -): Dataset; +export function log(event: ExperimentLogFullArgs): string { + debugLogger.warn( + "braintrust.log is deprecated and will be removed in a future version of braintrust. Use `experiment.log` instead.", + ); + const e = currentExperiment(); + if (!e) { + throw new Error("Not initialized. Please call init() first"); + } + return e.log(event); +} /** - * Legacy form of `initDataset` which accepts the project name as the first - * parameter, separately from the remaining options. + * Summarize the current experiment, including the scores (compared to the closest reference experiment) and metadata. * - * See `initDataset(options)` for full details. - */ -export function initDataset< - IsLegacyDataset extends boolean = typeof DEFAULT_IS_LEGACY_DATASET, ->( - project: string, - options?: Readonly>, -): Dataset; - -/** - * Combined overload implementation of `initDataset`. Do not call this - * directly. Instead, call `initDataset(options)` or `initDataset(project, - * options)`. + * @param options Options for summarizing the experiment. + * @param options.summarizeScores Whether to summarize the scores. If False, only the metadata will be returned. + * @param options.comparisonExperimentId The experiment to compare against. If None, the most recent experiment on the origin's main branch will be used. + * @returns A summary of the experiment, including the scores (compared to the closest reference experiment) and metadata. */ -export function initDataset< - IsLegacyDataset extends boolean = typeof DEFAULT_IS_LEGACY_DATASET, ->( - projectOrOptions: string | Readonly>, - optionalOptions?: Readonly>, -): Dataset { - const options = ((): Readonly> => { - if (typeof projectOrOptions === "string") { - return { ...optionalOptions, project: projectOrOptions }; - } else { - if (optionalOptions !== undefined) { - throw new Error( - "Cannot specify options struct as both parameters. Must call either initDataset(project, options) or initDataset(options).", - ); - } - return projectOrOptions; - } - })(); - - const { - project, - dataset, - datasetId, - description, - version, - snapshotName, - environment, - appUrl, - apiKey, - orgName, - fetch, - forceLogin, - projectId, - metadata, - useOutput: legacy, - state: stateArg, - _internal_btql, - } = options; - if ( - datasetId !== undefined && - (description !== undefined || metadata !== undefined) - ) { - throw new Error( - "Cannot specify description or metadata when datasetId is provided", - ); +export async function summarize( + options: { + readonly summarizeScores?: boolean; + readonly comparisonExperimentId?: string; + } = {}, +): Promise { + debugLogger.warn( + "braintrust.summarize is deprecated and will be removed in a future version of braintrust. Use `experiment.summarize` instead.", + ); + const e = currentExperiment(); + if (!e) { + throw new Error("Not initialized. Please call init() first"); } + return await e.summarize(options); +} - const selection = normalizeDatasetSelection({ - version, - environment, - snapshotName, - }); - const normalizedVersion = selection.version; - const normalizedEnvironment = selection.environment; - const normalizedSnapshotName = selection.snapshotName; - - const cliInternalBtql = globalThis.__bt_eval_internal_btql; - const internalBtql = - cliInternalBtql === undefined - ? _internal_btql - : _internal_btql === undefined - ? cliInternalBtql - : isObject(_internal_btql) - ? { ...cliInternalBtql, ..._internal_btql } - : _internal_btql; - - const state = stateArg ?? _globalState; - - const lazyMetadata: LazyValue = new LazyValue( - async () => { - await state.login({ - orgName, - apiKey, - appUrl, - fetch, - forceLogin, - }); +type OptionalStateArg = { + state?: BraintrustState; +}; - if (datasetId !== undefined) { - const objectInfo = datasetObjectInfoSchema.array().parse( - await state.appConn().post_json("api/self/get_object_info", { - object_type: "dataset", - object_ids: [datasetId], - }), - ); - if (objectInfo.length === 0) { - throw new Error(`Dataset with ID ${datasetId} not found`); - } - if (objectInfo.length !== 1) { - throw new Error( - `Expected exactly one dataset with ID ${datasetId}, but found ${objectInfo.length}`, - ); - } +/** + * Returns the currently-active experiment (set by {@link init}). Returns undefined if no current experiment has been set. + */ +export function currentExperiment( + options?: OptionalStateArg, +): Experiment | undefined { + const state = options?.state ?? _internalGetGlobalState(); + return state.currentExperiment; +} - const datasetInfo = objectInfo[0]; - return { - project: { - id: datasetInfo.parent_cols.project.id, - name: datasetInfo.parent_cols.project.name, - fullInfo: datasetInfo.parent_cols.project, - }, - dataset: { - id: datasetInfo.object_id, - name: datasetInfo.object_name, - fullInfo: datasetInfo, - }, - }; - } +/** + * Returns the currently-active logger (set by {@link initLogger}). Returns undefined if no current logger has been set. + */ +export function currentLogger(options?: OptionalStateArg): Logger | undefined { + const state = options?.state ?? _internalGetGlobalState(); + return state.currentLogger; +} - const args: Record = { - org_id: state.orgId, - project_name: project, - project_id: projectId, - dataset_name: dataset, - description, - metadata, - }; - const response = await state - .appConn() - .post_json("api/dataset/register", args); +/** + * Return the currently-active span for logging (set by one of the `traced` methods). If there is no active span, returns a no-op span object, which supports the same interface as spans but does no logging. + * + * See {@link Span} for full details. + */ +export function currentSpan(options?: OptionalStateArg): Span { + const state = options?.state ?? _internalGetGlobalState(); + return state.contextManager.getCurrentSpan() ?? NOOP_SPAN; +} - return { - project: { - id: response.project.id, - name: response.project.name, - fullInfo: response.project, - }, - dataset: { - id: response.dataset.id, - name: response.dataset.name, - fullInfo: response.dataset, - }, - }; - }, - ); +/** + * Resolve the parent object and any forwarded W3C state in a single pass. + * + * Same precedence as {@link getSpanParentObject}, but also returns the W3C state + * (tracestate + raw traceparent flags) recovered while normalizing the `parent` + * argument, so callers don't have to re-run `normalizeParent` (which would + * re-parse baggage and re-resolve the active logger/experiment, potentially + * disagreeing if state changed between calls). The state is only meaningful when + * a parent slug was resolved; otherwise it is undefined. + */ +function getSpanParentObjectAndPropagatedState( + options?: OptionalStateArg & { parent?: string | PropagationContext }, +): { + parentObject: + | SpanComponentsV3 + | SpanComponentsV4 + | Span + | Experiment + | Logger; + propagatedState: PropagatedState | undefined; +} { + const state = options?.state ?? _internalGetGlobalState(); - const resolvedVersion = - normalizedVersion !== undefined - ? normalizedVersion - : normalizedSnapshotName !== undefined - ? new LazyValue(async () => { - return await resolveDatasetSnapshotNameForMetadata({ - state, - lazyMetadata, - snapshotName: normalizedSnapshotName, - }); - }) - : normalizedEnvironment !== undefined - ? new LazyValue(async () => { - return await resolveDatasetEnvironmentForMetadata({ - state, - lazyMetadata, - environment: normalizedEnvironment, - }); - }) - : undefined; + const parentSpan = currentSpan({ state }); + if (!Object.is(parentSpan, NOOP_SPAN)) { + return { parentObject: parentSpan, propagatedState: undefined }; + } - const datasetObject = new Dataset( - stateArg ?? _globalState, - lazyMetadata, - typeof resolvedVersion === "string" ? resolvedVersion : undefined, - legacy, - internalBtql, - resolvedVersion instanceof LazyValue || - normalizedEnvironment !== undefined || - normalizedSnapshotName !== undefined - ? { - ...(resolvedVersion instanceof LazyValue - ? { - lazyPinnedVersion: resolvedVersion, - } - : {}), - ...(normalizedEnvironment !== undefined - ? { - pinnedEnvironment: normalizedEnvironment, - } - : {}), - ...(normalizedSnapshotName !== undefined - ? { - pinnedSnapshotName: normalizedSnapshotName, - } - : {}), - } - : undefined, - ); + const parent = options?.parent ?? state.currentParent.getStore(); + const { parentSlug, propagatedState } = normalizeParent(parent, state); + if (parentSlug) { + return { + parentObject: SpanComponentsV4.fromStr(parentSlug), + propagatedState, + }; + } - return datasetObject; + const experiment = state.currentExperiment; + if (experiment) { + return { parentObject: experiment, propagatedState: undefined }; + } + const logger = state.currentLogger; + if (logger) { + return { parentObject: logger, propagatedState: undefined }; + } + return { parentObject: NOOP_SPAN, propagatedState: undefined }; } /** - * @deprecated Use {@link initDataset} instead. + * Mainly for internal use. Return the parent object for starting a span in a global context. + * Applies precedence: current span > propagated parent > experiment > logger. + * + * `parent` may be an exported slug string or an opaque W3C trace-context (from + * {@link extractTraceContextFromHeaders}). */ -export function withDataset< - R, - IsLegacyDataset extends boolean = typeof DEFAULT_IS_LEGACY_DATASET, ->( - project: string, - callback: (dataset: Dataset) => R, - options: Readonly> = {}, -): R { - debugLogger - .forState(options.state) - .warn( - "withDataset is deprecated and will be removed in a future version of braintrust. Simply create the dataset with `initDataset`.", - ); - const dataset = initDataset(project, options); - return callback(dataset); +export function getSpanParentObject( + options?: OptionalStateArg & { parent?: string | PropagationContext }, +): SpanComponentsV3 | SpanComponentsV4 | Span | Experiment | Logger { + return getSpanParentObjectAndPropagatedState(options).parentObject; } -// Note: the argument names *must* serialize the same way as the argument names -// for the corresponding python function, because this function may be invoked -// from arguments serialized elsewhere. -async function computeLoggerMetadata( - state: BraintrustState, - { - project_name, - project_id, - project_group_name, - }: { - project_name?: string; - project_id?: string; - project_group_name?: string; - }, -) { - await state.login({}); - const org_id = state.orgId!; - if (!isEmpty(project_id) && !isEmpty(project_name)) { - return { - org_id, - project: { id: project_id, name: project_name, fullInfo: {} }, - }; +/** @internal */ +export function _internalExportParentSynchronously( + parent: ReturnType, +): string | undefined { + if ("toStr" in parent) { + return parent.toStr(); } - const groupName = project_group_name || undefined; - const cache = state.projectMetadataCache; - const key = JSON.stringify([ - state.appUrl, - org_id, - state.loginToken, - isEmpty(project_id) - ? ["name", project_name || GLOBAL_PROJECT, groupName] - : ["id", project_id], - ]); - const cached = cache.get(key); - if (cached && Date.now() < cached.expiresAt) { - return structuredClone(await cached.promise); + if ("getParentInfo" in parent) { + const parentInfo = parent.getParentInfo(); + if (!parentInfo) { + return undefined; + } + const objectId = parentInfo.objectId.getSync().value; + if (!objectId && !parentInfo.computeObjectMetadataArgs) { + return undefined; + } + return new SpanComponentsV4({ + object_type: parentInfo.objectType, + ...(objectId + ? { object_id: objectId } + : { + compute_object_metadata_args: + parentInfo.computeObjectMetadataArgs ?? {}, + }), + row_id: parent.id, + root_span_id: parent.rootSpanId, + span_id: parent.spanId, + }).toStr(); } - const conn = state.appConn(); - const controller = new AbortController(); - let timeout: ReturnType | undefined; - const entry = { - // The lookup timeout bounds pending requests; the cache TTL starts on success. - expiresAt: Infinity, - promise: Promise.race([ - (async (): Promise => { - if (isEmpty(project_id)) { - const response = await conn.post_json( - "api/project/register", - { - project_name: project_name || GLOBAL_PROJECT, - org_id, - project_group_name: groupName, - }, - controller.signal, - ); - return { - org_id, - project: { - id: response.project.id, - name: response.project.name, - fullInfo: response.project, - }, - }; - } - const response = await conn.get_json( - "api/project", - { id: project_id }, - 0, - controller.signal, - ); - return { - org_id, - project: { - id: project_id, - name: response.name, - fullInfo: response.project, - }, - }; - })(), - new Promise((_, reject) => { - timeout = setTimeout(() => { - const error = new Error( - "Braintrust project lookup timed out after 30 seconds.", - ); - reject(error); - controller.abort(error); - }, PROJECT_LOOKUP_TIMEOUT_MS); - }), - ]), - }; - cache.set(key, entry); - try { - const metadata = await entry.promise; - entry.expiresAt = Date.now() + PROJECT_CACHE_TTL_MS; - return structuredClone(metadata); - } catch (error) { - if (cache.get(key) === entry) { - cache.delete(key); - } - throw error; - } finally { - clearTimeout(timeout); - } -} - -type AsyncFlushArg = { - asyncFlush?: IsAsyncFlush; -}; - -export type InitLoggerOptions = FullLoginOptions & { - projectName?: string; - projectId?: string; - _createInProjectGroup?: string; - environment?: SpanOriginEnvironment; - setCurrent?: boolean; - state?: BraintrustState; - orgProjectMetadata?: OrgProjectMetadata; -} & AsyncFlushArg; - -/** - * Create a new logger in a specified project. If the project does not exist, it will be created. - * - * @param options Additional options for configuring init(). - * @param options.projectName The name of the project to log into. If unspecified, will default to the Global project. - * @param options.projectId The id of the project to log into. This takes precedence over projectName if specified. - * @param options._createInProjectGroup Experimental: the name of an existing project group to create the project in. Existing projects must already belong to the group, otherwise registration fails with a 409. Membership is re-checked on each fresh project lookup, so moving the project out of the group makes new loggers fail with a 409. Ignored when projectId is provided. - * @param options.asyncFlush If true, will log asynchronously in the background. Otherwise, will log synchronously. (true by default) - * @param options.appUrl The URL of the Braintrust App. Defaults to https://www.braintrust.dev. - * @param options.apiKey The API key to use. If the parameter is not specified, will try to use the `BRAINTRUST_API_KEY` environment variable. In Node.js, - * if that is unset, will try the nearest `.env.braintrust` file in the current working directory or parent directories. If no API key is specified, will prompt the user to login. - * @param options.orgName (Optional) The name of a specific organization to connect to. This is useful if you belong to multiple. - * @param options.forceLogin Login again, even if you have already logged in (by default, the logger will not login if you are already logged in) - * @param options.debugLogLevel Enables internal Braintrust SDK troubleshooting output. Use `"error"`, `"warn"`, `"info"`, or `"debug"` to choose an explicit level, or `false` to explicitly disable it. If omitted, the SDK stays silent unless `BRAINTRUST_DEBUG_LOG_LEVEL` is set. - * @param setCurrent If true (the default), set the global current-experiment to the newly-created one. - * @returns The newly created Logger. - */ -export function initLogger( - options: Readonly> = {}, -) { - const { - projectName, - projectId, - _createInProjectGroup, - asyncFlush: asyncFlushArg, - appUrl, - apiKey, - orgName, - forceLogin, - debugLogLevel, - environment, - fetch, - state: stateArg, - } = options || {}; - - const asyncFlush = - asyncFlushArg === undefined ? (true as IsAsyncFlush) : asyncFlushArg; - - const computeMetadataArgs = { - project_name: projectName, - project_id: projectId, - project_group_name: _createInProjectGroup, - }; - - const linkArgs = { - org_name: orgName, - app_url: appUrl, - project_name: projectName, - project_id: projectId, - }; - - const state = stateArg ?? _globalState; - state._internalSetTraceContextSigningSecret(apiKey); - state.setDebugLogLevel(debugLogLevel); - state.spanOriginEnvironment = detectSpanOriginEnvironment(environment); - - // Enable queue size limit enforcement for initLogger() calls - // This ensures production observability doesn't OOM customer processes - state.enforceQueueSizeLimit(true); - const lazyMetadata: LazyValue = new LazyValue( - async () => { - // Otherwise actually log in. - await state.login({ - orgName, - apiKey, - appUrl, - forceLogin, - fetch, - }); - return computeLoggerMetadata(state, computeMetadataArgs); - }, - ); - - const ret = new Logger(state, lazyMetadata, { - asyncFlush, - computeMetadataArgs, - linkArgs, - }); - if (options.setCurrent ?? true) { - state.currentLogger = ret as Logger; - } - return ret; -} - -export type LoadPromptOptions = FullLoginOptions & { - projectName?: string; - projectId?: string; - slug?: string; - version?: string; - id?: string; - defaults?: DefaultPromptArgs; - noTrace?: boolean; - environment?: string; - state?: BraintrustState; -}; - -type LoadParametersBaseOptions = FullLoginOptions & { - state?: BraintrustState; -}; - -export type LoadParametersByIdOptions = LoadParametersBaseOptions & { - id: string; - version?: string; -}; - -export type LoadParametersByIdWithEnvOptions = LoadParametersBaseOptions & { - id: string; - environment: string; - version?: string; -}; - -export type LoadParametersByProjectNameOptions = LoadParametersBaseOptions & { - projectName: string; - slug: string; - version?: string; -}; - -export type LoadParametersByProjectNameWithEnvOptions = - LoadParametersBaseOptions & { - projectName: string; - slug: string; - environment: string; - version?: string; - }; - -export type LoadParametersByProjectIdOptions = LoadParametersBaseOptions & { - projectId: string; - slug: string; - version?: string; -}; - -export type LoadParametersByProjectIdWithEnvOptions = - LoadParametersBaseOptions & { - projectId: string; - slug: string; - environment: string; - version?: string; - }; - -export type LoadParametersOptions = - | LoadParametersByIdOptions - | LoadParametersByIdWithEnvOptions - | LoadParametersByProjectNameOptions - | LoadParametersByProjectNameWithEnvOptions - | LoadParametersByProjectIdOptions - | LoadParametersByProjectIdWithEnvOptions; - -type LoadParametersImplementationOptions = LoadParametersBaseOptions & { - projectName?: string; - projectId?: string; - slug?: string; - version?: string; - id?: string; - environment?: string; -}; - -type LoaderRequestResult = - | ({ ok: true; response: T } & LoaderCacheViews) - | ({ ok: false; error: unknown } & LoaderCacheViews); - -async function runCredentialScopedLoaderRequest( - state: BraintrustState, - loginOptions: Pick< - FullLoginOptions, - "apiKey" | "appUrl" | "orgName" | "fetch" | "forceLogin" - >, - request: (requestState: LoaderRequestState) => Promise, -): Promise> { - const resolvedLoginOptions = - await state._internalResolveLoaderLoginOptions(loginOptions); - let cacheViews = state._internalGetLoaderCacheViews(resolvedLoginOptions); - - try { - const requestState = - await state._internalGetLoaderState(resolvedLoginOptions); - cacheViews = state._internalGetLoaderCacheViews( - resolvedLoginOptions, - requestState, - ); - return { - ok: true, - response: await request(requestState), - ...cacheViews, - }; - } catch (error) { - return { ok: false, error, ...cacheViews }; - } -} - -/** - * Load a prompt from the specified project. - * - * @param options Options for configuring loadPrompt(). - * @param options.projectName The name of the project to load the prompt from. Must specify at least one of `projectName` or `projectId`. - * @param options.projectId The id of the project to load the prompt from. This takes precedence over `projectName` if specified. - * @param options.slug The slug of the prompt to load. - * @param options.version An optional version of the prompt (to read). If not specified, the latest version will be used. - * @param options.environment Fetch the version of the prompt assigned to the specified environment (e.g. "production", "staging"). If both `version` and `environment` are provided, `version` takes precedence. - * @param options.id The id of a specific prompt to load. If specified, this takes precedence over all other parameters (project and slug). - * @param options.defaults (Optional) A dictionary of default values to use when rendering the prompt. Prompt values will override these defaults. - * @param options.noTrace If true, do not include logging metadata for this prompt when build() is called. - * @param options.appUrl The URL of the Braintrust App. Defaults to https://www.braintrust.dev. - * @param options.apiKey The API key to use for this request, independently of any existing global login. If the parameter is not specified, will use an existing login or try the `BRAINTRUST_API_KEY` environment variable. In Node.js, - * if that is unset, will try the nearest `.env.braintrust` file in the current working directory or parent directories. If no API key is specified, will prompt the user to login. - * @param options.orgName (Optional) The name of a specific organization to connect to. This is useful if you belong to multiple. - * @returns The prompt object. - * @throws If the prompt is not found. - * @throws If multiple prompts are found with the same slug in the same project (this should never happen). - * - * @example - * ```javascript - * const prompt = await loadPrompt({ - * projectName: "My Project", - * slug: "my-prompt", - * }); - * ``` - */ -export async function loadPrompt({ - projectName, - projectId, - slug, - version, - environment, - id, - defaults, - noTrace = false, - appUrl, - apiKey, - orgName, - fetch, - forceLogin, - state: stateArg, -}: LoadPromptOptions) { - const versionOrEnvironment = version - ? { version } - : environment - ? { environment } - : {}; - if (id) { - // When loading by ID, we don't need project or slug - } else if (isEmpty(projectName) && isEmpty(projectId)) { - throw new Error("Must specify either projectName or projectId"); - } else if (isEmpty(slug)) { - throw new Error("Must specify slug"); - } - - const state = stateArg ?? _globalState; - const result = await runCredentialScopedLoaderRequest( - state, - { orgName, apiKey, appUrl, fetch, forceLogin }, - async (requestState) => { - let response; - if (id) { - // Load prompt by ID using the /v1/prompt/{id} endpoint - response = await requestState - .apiConn() - .get_json(`v1/prompt/${id}`, versionOrEnvironment); - // Wrap single prompt response in objects array to match list API format - if (response) { - response = { objects: [response] }; - } - } else { - response = await requestState.apiConn().get_json("v1/prompt", { - project_name: projectName, - project_id: projectId, - slug, - ...versionOrEnvironment, - }); - } - return response; - }, - ); - const { promptCache } = result; - if (!result.ok) { - const e = result.error; - if (!isLoaderCacheFallbackError(e)) { - throw e; - } - // If environment or version was specified, don't fall back to cache - if (version || environment) { - throw new Error(`Prompt not found with specified parameters: ${e}`); - } - - debugLogger - .forState(state) - .warn("Failed to load prompt, attempting to fall back to cache:", e); - let prompt; - if (id) { - prompt = await promptCache.get({ id }); - if (!prompt) { - throw new Error( - `Prompt with id ${id} not found (not found on server or in local cache): ${e}`, - ); - } - } else { - prompt = await promptCache.get({ - slug, - projectId, - projectName, - version: version ?? "latest", - }); - if (!prompt) { - throw new Error( - `Prompt ${slug} (version ${version ?? "latest"}) not found in ${[ - projectName ?? projectId, - ]} (not found on server or in local cache): ${e}`, - ); - } - } - return prompt; - } - const { response } = result; - - if (!("objects" in response) || response.objects.length === 0) { - if (id) { - throw new Error(`Prompt with id ${id} not found.`); - } else { - throw new Error( - `Prompt ${slug} not found in ${[projectName ?? projectId]}`, - ); - } - } else if (response.objects.length > 1) { - if (id) { - throw new Error( - `Multiple prompts found with id ${id}. This should never happen.`, - ); - } else { - throw new Error( - `Multiple prompts found with slug ${slug} in project ${ - projectName ?? projectId - }. This should never happen.`, - ); - } - } - - const metadata = promptSchema.parse(response["objects"][0]); - const prompt = new Prompt(metadata, defaults || {}, noTrace); - try { - if (id) { - await promptCache.set({ id }, prompt); - } else if (slug) { - await promptCache.set( - { slug, projectId, projectName, version: version ?? "latest" }, - prompt, - ); - } - } catch (e) { - debugLogger.forState(state).warn("Failed to set prompt in cache:", e); - } - return prompt; -} - -/** - * Load parameters from the specified project. - * - * @param options Options for configuring loadParameters(). - * @param options.projectName The name of the project to load the parameters from. Must specify at least one of `projectName` or `projectId`. - * @param options.projectId The id of the project to load the parameters from. This takes precedence over `projectName` if specified. - * @param options.slug The slug of the parameters to load. - * @param options.version An optional version of the parameters (to read). If not specified, the latest version will be used. - * @param options.environment Fetch the version of the parameters assigned to the specified environment (e.g. "production", "staging"). If both `version` and `environment` are provided, `version` takes precedence. - * @param options.id The id of specific parameters to load. If specified, this takes precedence over all other parameters (project and slug). - * @param options.appUrl The URL of the Braintrust App. Defaults to https://www.braintrust.dev. - * @param options.apiKey The API key to use for this request, independently of any existing global login. If the parameter is not specified, will use an existing login or try the `BRAINTRUST_API_KEY` environment variable. In Node.js, if that is unset, will try the nearest `.env.braintrust` file in the current working directory or parent directories. - * @param options.orgName (Optional) The name of a specific organization to connect to. This is useful if you belong to multiple. - * @returns The parameters object. - * @throws If the parameters are not found. - * @throws If multiple parameters are found with the same slug in the same project (this should never happen). - * - * @example - * ```typescript - * const params = await loadParameters({ - * projectName: "My Project", - * slug: "my-parameters", - * }); - * ``` - */ -export function loadParameters( - options: LoadParametersByProjectNameOptions, -): Promise>>; -export function loadParameters( - options: LoadParametersByProjectNameWithEnvOptions, -): Promise>>; -export function loadParameters( - options: LoadParametersByProjectIdOptions, -): Promise>>; -export function loadParameters( - options: LoadParametersByProjectIdWithEnvOptions, -): Promise>>; -export function loadParameters( - options: LoadParametersByIdOptions, -): Promise>>; -export function loadParameters( - options: LoadParametersByIdWithEnvOptions, -): Promise>>; -export async function loadParameters< - S extends EvalParameters = EvalParameters, ->({ - projectName, - projectId, - slug, - version, - environment, - id, - appUrl, - apiKey, - orgName, - fetch, - forceLogin, - state: stateArg, -}: LoadParametersImplementationOptions): Promise< - RemoteEvalParameters> -> { - const versionOrEnvironment = version - ? { version } - : environment - ? { environment } - : {}; - if (id) { - // When loading by ID, we don't need project or slug - } else if (isEmpty(projectName) && isEmpty(projectId)) { - throw new Error("Must specify either projectName or projectId"); - } else if (isEmpty(slug)) { - throw new Error("Must specify slug"); - } - - const state = stateArg ?? _globalState; - const result = await runCredentialScopedLoaderRequest( - state, - { orgName, apiKey, appUrl, fetch, forceLogin }, - async (requestState) => { - let response; - if (id) { - response = await requestState.apiConn().get_json(`v1/function/${id}`, { - ...versionOrEnvironment, - }); - if (response) { - response = { objects: [response] }; - } - } else { - response = await requestState.apiConn().get_json("v1/function", { - project_name: projectName, - project_id: projectId, - slug, - function_type: "parameters", - ...versionOrEnvironment, - }); - } - return response; - }, - ); - const { parametersCache } = result; - if (!result.ok) { - const e = result.error; - if (!isLoaderCacheFallbackError(e)) { - throw e; - } - if (version || environment) { - throw new Error(`Parameters not found with specified parameters: ${e}`); - } - - debugLogger - .forState(state) - .warn("Failed to load parameters, attempting to fall back to cache:", e); - let parameters; - if (id) { - parameters = await parametersCache.get({ id }); - if (!parameters) { - throw new Error( - `Parameters with id ${id} not found (not found on server or in local cache): ${e}`, - ); - } - } else { - parameters = await parametersCache.get({ - slug, - projectId, - projectName, - version: version ?? "latest", - }); - if (!parameters) { - throw new Error( - `Parameters ${slug} (version ${version ?? "latest"}) not found in ${[ - projectName ?? projectId, - ]} (not found on server or in local cache): ${e}`, - ); - } - } - // eslint-disable-next-line @typescript-eslint/consistent-type-assertions - return parameters as RemoteEvalParameters>; - } - const { response } = result; - - if (!("objects" in response) || response.objects.length === 0) { - if (id) { - throw new Error(`Parameters with id ${id} not found.`); - } else { - throw new Error( - `Parameters ${slug} not found in ${[projectName ?? projectId]}`, - ); - } - } else if (response.objects.length > 1) { - if (id) { - throw new Error( - `Multiple parameters found with id ${id}. This should never happen.`, - ); - } else { - throw new Error( - `Multiple parameters found with slug ${slug} in project ${ - projectName ?? projectId - }. This should never happen.`, - ); - } - } - - const metadata = parametersRowSchema.parse(response["objects"][0]); - const parameters = new RemoteEvalParameters(metadata); - try { - if (id) { - await parametersCache.set({ id }, parameters); - } else if (slug) { - await parametersCache.set( - { slug, projectId, projectName, version: version ?? "latest" }, - parameters, - ); - } - } catch (e) { - debugLogger.forState(state).warn("Failed to set parameters in cache:", e); - } - // eslint-disable-next-line @typescript-eslint/consistent-type-assertions - return parameters as RemoteEvalParameters>; -} - -/** - * Options for logging in to Braintrust. - */ -export interface LoginOptions { - /** - * The URL of the Braintrust App. Defaults to https://www.braintrust.dev. You should not need - * to change this unless you are doing the "Full" deployment. - */ - appUrl?: string; - /** - * The API key to use. If the parameter is not specified, will try to use the `BRAINTRUST_API_KEY` environment variable. In Node.js, if that is unset, will try the nearest `.env.braintrust` file in the current working directory or parent directories. - */ - apiKey?: string; - /** - * The name of a specific organization to connect to. Since API keys are scoped to organizations, this parameter is usually - * unnecessary unless you are logging in with a JWT. - */ - orgName?: string; - /** - * A custom fetch implementation to use. - */ - fetch?: typeof globalThis.fetch; - /** - * By default, the SDK installs an event handler that flushes pending writes on the `beforeExit` event. - * If true, this event handler will _not_ be installed. - */ - noExitFlush?: boolean; - /** - * Calls this function if there's an error in the background flusher. - */ - onFlushError?: (error: unknown) => void; - /** - * If true, disables the local span cache used to optimize scorer access - * to trace data. When disabled, scorers will always fetch spans from the - * server. Defaults to false. - */ - disableSpanCache?: boolean; - /** - * Controls internal Braintrust SDK troubleshooting output. - * - * Use `"error"`, `"warn"`, `"info"`, or `"debug"` to control how much - * internal SDK troubleshooting output is emitted. Use `false` to explicitly - * disable this output. - * - * When omitted, the SDK remains silent unless - * `BRAINTRUST_DEBUG_LOG_LEVEL` is set to `"error"`, `"warn"`, `"info"`, or - * `"debug"`. This option only affects local console output; it does not - * change what data is logged to Braintrust. - */ - debugLogLevel?: DebugLogLevel | false; -} - -export type FullLoginOptions = LoginOptions & { - forceLogin?: boolean; -}; - -/** - * Set a global masking function that will be applied to all logged data before sending to Braintrust. - * The masking function will be applied after records are merged but before they are sent to the backend. - * Internally, masking is a state-local export customizer that runs after any - * span customizers and also covers non-span records such as dataset rows and feedback. - * - * @param maskingFunction A function that takes a JSON-serializable object and returns a masked version. - * Set to null to disable masking. - */ -export function setMaskingFunction( - maskingFunction: ((value: unknown) => unknown) | null, -): void { - _internalGetGlobalState().setMaskingFunction(maskingFunction); -} - -/** - * Log into Braintrust. This will prompt you for your API token, which you can find at - * https://www.braintrust.dev/app/token. This method is called automatically by `init()`. - * - * @param options Options for configuring login(). - * @param options.appUrl The URL of the Braintrust App. Defaults to https://www.braintrust.dev. - * @param options.apiKey The API key to use. If the parameter is not specified, will try to use the `BRAINTRUST_API_KEY` environment variable. In Node.js, - * if that is unset, will try the nearest `.env.braintrust` file in the current working directory or parent directories. If no API key is specified, will prompt the user to login. - * @param options.orgName (Optional) The name of a specific organization to connect to. This is useful if you belong to multiple. - * @param options.forceLogin Login again, even if you have already logged in (by default, this function will exit quickly if you have already logged in) - */ -export async function login( - options: LoginOptions & { forceLogin?: boolean } = {}, -): Promise { - const { forceLogin = false } = options || {}; - - if (!_internalGetGlobalState()) { - _internalSetInitialState(); - } - - const state = _internalGetGlobalState(); - state.setDebugLogLevel(options.debugLogLevel); - if (state.loggedIn && !forceLogin) { - // We have already logged in. If any provided login inputs disagree with our - // existing settings, raise an Exception warning the user to try again with - // `forceLogin: true`. - function checkUpdatedParam( - varname: string, - arg: string | undefined, - orig: string | null, - ) { - if (!isEmpty(arg) && !isEmpty(orig) && arg !== orig) { - throw new Error( - `Re-logging in with different ${varname} (${arg}) than original (${orig}). To force re-login, pass \`forceLogin: true\``, - ); - } - } - checkUpdatedParam("appUrl", options.appUrl, state.appUrl); - checkUpdatedParam( - "apiKey", - options.apiKey - ? HTTPConnection.sanitize_token(options.apiKey) - : undefined, - state.loginToken, - ); - checkUpdatedParam("orgName", options.orgName, state.orgName); - return state; - } - - await state.login(options); - return state; -} - -async function loginToLoaderRequestState({ - appUrl, - apiKey, - orgName, - fetch, -}: Required> & - Pick): Promise { - let orgId: string; - let apiUrl: string; - - if (apiKey === TEST_API_KEY) { - orgId = "test-org-id"; - apiUrl = "https://braintrust.dev/fake-api-url"; - } else { - let loginResponse: Response; - try { - loginResponse = await fetch(_urljoin(appUrl, `/api/apikey/login`), { - method: "POST", - headers: { - "Content-Type": "application/json", - Authorization: `Bearer ${apiKey}`, - }, - }); - } catch (error) { - throw new HTTPTransportError(error); - } - - const info = await readJSONResponse( - await checkResponse(loginResponse), - true, - ); - const org = selectLoginOrg(info.org_info, orgName); - orgId = org.id; - apiUrl = iso.getEnv("BRAINTRUST_API_URL") || org.api_url; - if (!apiUrl) { - throw new Error( - orgName - ? `Unable to log into organization '${orgName}'. Are you sure this credential is scoped to the organization?` - : "Unable to log into any organization with the provided credential.", - ); - } - } - - const apiConnection = new HTTPConnection(apiUrl, fetch, true); - apiConnection.set_token(apiKey); - apiConnection.make_long_lived(); - return { - appUrl, - orgId, - apiConn: () => apiConnection, - }; -} - -export async function loginToState(options: LoginOptions = {}) { - const { - appUrl = iso.getEnv("BRAINTRUST_APP_URL") || "https://www.braintrust.dev", - apiKey: apiKeyArg, - orgName = iso.getEnv("BRAINTRUST_ORG_NAME"), - fetch = globalThis.fetch, - } = options || {}; - - const apiKey = - apiKeyArg !== undefined ? apiKeyArg : await iso.getBraintrustApiKey(); - const appPublicUrl = iso.getEnv("BRAINTRUST_APP_PUBLIC_URL") || appUrl; - - const state = new BraintrustState(options); - state.resetLoginInfo(); - - state.appUrl = appUrl; - state.appPublicUrl = appPublicUrl; - - let conn = null; - - if (!apiKey) { - throw new Error( - "Please specify an api key (e.g. by setting BRAINTRUST_API_KEY).", - ); - } else if (apiKey === TEST_API_KEY) { - // This is a weird hook that lets us skip logging in and mocking out the org info. - const testOrgInfo = [ - { - id: "test-org-id", - name: "test-org-name", - api_url: "https://braintrust.dev/fake-api-url", - }, - ]; - state.loggedIn = true; - state.loginToken = TEST_API_KEY; - _saveOrgInfo(state, testOrgInfo, testOrgInfo[0].name); - return state; - } else { - const controller = new AbortController(); - let timeout: ReturnType | undefined; - let info; - try { - // Bound both headers and body reads, including custom fetches that ignore - // cancellation, so a stalled request cannot block subsequent logins. - info = await Promise.race([ - (async () => { - const response = await fetch(_urljoin(appUrl, `/api/apikey/login`), { - method: "POST", - headers: { - "Content-Type": "application/json", - Authorization: `Bearer ${apiKey}`, - }, - signal: controller.signal, - }); - return readJSONResponse(await checkResponse(response)); - })(), - new Promise((_, reject) => { - timeout = setTimeout(() => { - const error = new Error( - "Braintrust login timed out after 30 seconds.", - ); - reject(error); - controller.abort(error); - }, LOGIN_TIMEOUT_MS); - }), - ]); - } finally { - clearTimeout(timeout); - } - - _saveOrgInfo(state, info.org_info, orgName); - if (!state.apiUrl) { - if (orgName) { - throw new Error( - `Unable to log into organization '${orgName}'. Are you sure this credential is scoped to the organization?`, - ); - } else { - throw new Error( - "Unable to log into any organization with the provided credential.", - ); - } - } - - conn = state.apiConn(); - conn.set_token(apiKey); - - if (!conn) { - throw new Error("Conn should be set at this point (a bug)"); - } - - conn.make_long_lived(); - - // Set the same token in the API - state.appConn().set_token(apiKey); - if (state.proxyUrl) { - state.proxyConn().set_token(apiKey); - } - state.loginToken = conn.token; - state.loggedIn = true; - - // Replace the global logger's apiConn with this one. - state.loginReplaceApiConn(conn); - } - - return state; -} - -// XXX We should remove these global functions now -/** - * Log a single event to the current experiment. The event will be batched and uploaded behind the scenes. - * - * @param event The event to log. See {@link Experiment.log} for full details. - * @returns The `id` of the logged event. - */ -export function log(event: ExperimentLogFullArgs): string { - debugLogger.warn( - "braintrust.log is deprecated and will be removed in a future version of braintrust. Use `experiment.log` instead.", - ); - const e = currentExperiment(); - if (!e) { - throw new Error("Not initialized. Please call init() first"); - } - return e.log(event); -} - -/** - * Summarize the current experiment, including the scores (compared to the closest reference experiment) and metadata. - * - * @param options Options for summarizing the experiment. - * @param options.summarizeScores Whether to summarize the scores. If False, only the metadata will be returned. - * @param options.comparisonExperimentId The experiment to compare against. If None, the most recent experiment on the origin's main branch will be used. - * @returns A summary of the experiment, including the scores (compared to the closest reference experiment) and metadata. - */ -export async function summarize( - options: { - readonly summarizeScores?: boolean; - readonly comparisonExperimentId?: string; - } = {}, -): Promise { - debugLogger.warn( - "braintrust.summarize is deprecated and will be removed in a future version of braintrust. Use `experiment.summarize` instead.", - ); - const e = currentExperiment(); - if (!e) { - throw new Error("Not initialized. Please call init() first"); - } - return await e.summarize(options); -} - -type OptionalStateArg = { - state?: BraintrustState; -}; - -/** - * Returns the currently-active experiment (set by {@link init}). Returns undefined if no current experiment has been set. - */ -export function currentExperiment( - options?: OptionalStateArg, -): Experiment | undefined { - const state = options?.state ?? _globalState; - return state.currentExperiment; -} - -/** - * Returns the currently-active logger (set by {@link initLogger}). Returns undefined if no current logger has been set. - */ -export function currentLogger( - options?: AsyncFlushArg & OptionalStateArg, -): Logger | undefined { - const state = options?.state ?? _globalState; - return castLogger(state.currentLogger, options?.asyncFlush); -} - -/** - * Return the currently-active span for logging (set by one of the `traced` methods). If there is no active span, returns a no-op span object, which supports the same interface as spans but does no logging. - * - * See {@link Span} for full details. - */ -export function currentSpan(options?: OptionalStateArg): Span { - const state = options?.state ?? _globalState; - return state.contextManager.getCurrentSpan() ?? NOOP_SPAN; -} - -/** - * Resolve the parent object and any forwarded W3C state in a single pass. - * - * Same precedence as {@link getSpanParentObject}, but also returns the W3C state - * (tracestate + raw traceparent flags) recovered while normalizing the `parent` - * argument, so callers don't have to re-run `normalizeParent` (which would - * re-parse baggage and re-resolve the active logger/experiment, potentially - * disagreeing if state changed between calls). The state is only meaningful when - * a parent slug was resolved; otherwise it is undefined. - */ -function getSpanParentObjectAndPropagatedState( - options?: AsyncFlushArg & - OptionalStateArg & { parent?: string | PropagationContext }, -): { - parentObject: - | SpanComponentsV3 - | SpanComponentsV4 - | Span - | Experiment - | Logger; - propagatedState: PropagatedState | undefined; -} { - const state = options?.state ?? _globalState; - - const parentSpan = currentSpan({ state }); - if (!Object.is(parentSpan, NOOP_SPAN)) { - return { parentObject: parentSpan, propagatedState: undefined }; - } - - const parent = options?.parent ?? state.currentParent.getStore(); - const { parentSlug, propagatedState } = normalizeParent(parent, state); - if (parentSlug) { - return { - parentObject: SpanComponentsV4.fromStr(parentSlug), - propagatedState, - }; - } - - const experiment = currentExperiment(); - if (experiment) { - return { parentObject: experiment, propagatedState: undefined }; - } - const logger = currentLogger(options); - if (logger) { - return { parentObject: logger, propagatedState: undefined }; - } - return { parentObject: NOOP_SPAN, propagatedState: undefined }; -} - -/** - * Mainly for internal use. Return the parent object for starting a span in a global context. - * Applies precedence: current span > propagated parent > experiment > logger. - * - * `parent` may be an exported slug string or an opaque W3C trace-context (from - * {@link extractTraceContextFromHeaders}). - */ -export function getSpanParentObject( - options?: AsyncFlushArg & - OptionalStateArg & { parent?: string | PropagationContext }, -): - | SpanComponentsV3 - | SpanComponentsV4 - | Span - | Experiment - | Logger { - return getSpanParentObjectAndPropagatedState(options).parentObject; -} - -/** @internal */ -export function _internalExportParentSynchronously( - parent: ReturnType, -): string | undefined { - if ("toStr" in parent) { - return parent.toStr(); - } - - if ("getParentInfo" in parent) { - const parentInfo = parent.getParentInfo(); - if (!parentInfo) { - return undefined; - } - const objectId = parentInfo.objectId.getSync().value; - if (!objectId && !parentInfo.computeObjectMetadataArgs) { - return undefined; - } - return new SpanComponentsV4({ - object_type: parentInfo.objectType, - ...(objectId - ? { object_id: objectId } - : { - compute_object_metadata_args: - parentInfo.computeObjectMetadataArgs ?? {}, - }), - row_id: parent.id, - root_span_id: parent.rootSpanId, - span_id: parent.spanId, - }).toStr(); - } - - const components = braintrustParentToComponents(parent._getOtelParent()); - if (!components) { - return undefined; - } - return new SpanComponentsV4({ - object_type: components.objectType, - ...(components.objectId - ? { object_id: components.objectId } - : { compute_object_metadata_args: components.computeArgs ?? {} }), - }).toStr(); -} - -/** - * Return the Braintrust parent string for the current logger/experiment, if any. - * - * Used as the fallback Braintrust parent on receive, when an inbound request - * carries trace identity (`traceparent`) but no `braintrust.parent` baggage. - */ -function currentBraintrustParent(state?: BraintrustState): string | undefined { - const resolvedState = state ?? _globalState; - - const experiment = currentExperiment({ state: resolvedState }); - if (experiment) { - try { - return experiment._getOtelParent() ?? undefined; - } catch { - return undefined; - } - } - - const logger = currentLogger({ state: resolvedState }); - if (logger) { - try { - return logger._getOtelParent() ?? undefined; - } catch { - return undefined; - } - } - - return undefined; -} - -interface BraintrustParentComponents { - objectType: SpanObjectTypeV3; - objectId: string | undefined; - computeArgs: Record | undefined; -} - -/** - * Parse a `braintrust.parent` string into object type / id / compute args. - * - * Accepts `project_id:`, `project_name:`, or `experiment_id:`. - * Returns undefined if the value is empty or malformed. - */ -function braintrustParentToComponents( - braintrustParent: string | undefined | null, -): BraintrustParentComponents | undefined { - if (!braintrustParent) { - return undefined; - } - if (braintrustParent.startsWith("project_id:")) { - const objectId = braintrustParent.slice("project_id:".length); - return objectId - ? { - objectType: SpanObjectTypeV3.PROJECT_LOGS, - objectId, - computeArgs: undefined, - } - : undefined; - } - if (braintrustParent.startsWith("project_name:")) { - const name = braintrustParent.slice("project_name:".length); - return name - ? { - objectType: SpanObjectTypeV3.PROJECT_LOGS, - objectId: undefined, - computeArgs: { project_name: name }, - } - : undefined; - } - if (braintrustParent.startsWith("experiment_id:")) { - const objectId = braintrustParent.slice("experiment_id:".length); - return objectId - ? { - objectType: SpanObjectTypeV3.EXPERIMENT, - objectId, - computeArgs: undefined, - } - : undefined; - } - return undefined; -} - -/** - * Set a W3C trace-context header on a carrier, sending the lowercase name. - * - * Per the W3C Trace Context spec (§3.2.1 / §3.3.1), vendors SHOULD send these - * header names in lowercase. `name` is always the canonical lowercase key. A - * plain object carrier is case-sensitive, so any pre-existing case-variant (e.g. - * `Baggage` from a framework that title-cases headers) must be removed first, - * otherwise the carrier would end up with two conflicting headers. - */ -function isMutableHeaderTupleArray( - carrier: TraceContextCarrier, -): carrier is [string, string][] { - return ( - Array.isArray(carrier) && - carrier.every((item) => Array.isArray(item) && typeof item[0] === "string") - ); -} - -function setHeader( - carrier: TraceContextCarrier, - name: string, - value: string, -): void { - if (isMutableHeaderTupleArray(carrier)) { - const lowered = name.toLowerCase(); - for (let i = carrier.length - 1; i >= 0; i--) { - if (carrier[i][0].toLowerCase() === lowered) { - carrier.splice(i, 1); - } - } - carrier.push([name, value]); - return; - } - - const setter = (carrier as { set?: unknown }).set; - if (typeof setter === "function") { - const deleter = (carrier as { delete?: unknown }).delete; - if (typeof deleter === "function") { - try { - deleter.call(carrier, name); - } catch { - // Setting below may still succeed for custom header-like objects. - } - } - setter.call(carrier, name, value); - return; - } - - const nodeSetter = (carrier as { setHeader?: unknown }).setHeader; - if (typeof nodeSetter === "function") { - const remover = (carrier as { removeHeader?: unknown }).removeHeader; - if (typeof remover === "function") { - try { - remover.call(carrier, name); - } catch { - // Setting below may still succeed for custom header-like objects. - } - } - nodeSetter.call(carrier, name, value); - return; - } - - const headerSetter = (carrier as { header?: unknown }).header; - if (typeof headerSetter === "function") { - const deleter = (carrier as { delete?: unknown }).delete; - if (typeof deleter === "function") { - try { - deleter.call(carrier, name); - } catch { - // Setting below may still succeed for custom header-like objects. - } - } - const remover = (carrier as { removeHeader?: unknown }).removeHeader; - if (typeof remover === "function") { - try { - remover.call(carrier, name); - } catch { - // Setting below may still succeed for custom header-like objects. - } - } - headerSetter.call(carrier, name, value); - return; - } - - const headerBag = carrier as { [name: string]: string }; - const lowered = name.toLowerCase(); - for (const key of Object.keys(headerBag)) { - if (key !== name && key.toLowerCase() === lowered) { - delete headerBag[key]; - } - } - headerBag[name] = value; -} - -function deleteHeader(carrier: TraceContextCarrier, name: string): void { - const lowered = name.toLowerCase(); - if (isMutableHeaderTupleArray(carrier)) { - for (let i = carrier.length - 1; i >= 0; i--) { - if (carrier[i][0].toLowerCase() === lowered) { - carrier.splice(i, 1); - } - } - return; - } - - const deleter = (carrier as { delete?: unknown }).delete; - if (typeof deleter === "function") { - try { - deleter.call(carrier, name); - return; - } catch { - // Fall through to other header carrier styles. - } - } - - const remover = (carrier as { removeHeader?: unknown }).removeHeader; - if (typeof remover === "function") { - try { - remover.call(carrier, name); - return; - } catch { - // Fall through to plain object cleanup. - } - } - - const headerBag = carrier as { [name: string]: string }; - for (const key of Object.keys(headerBag)) { - if (key.toLowerCase() === lowered) { - delete headerBag[key]; - } - } -} - -/** - * Inject W3C trace-context headers into a carrier (in place). - * - * Emits `traceparent` from the hex trace/span ids, merges `braintrust.parent` - * into existing `baggage` when known, and forwards any inbound W3C state - * (`tracestate` plus the original `traceparent` trace-flags) carried in - * `propagatedState`. Pre-existing, non-Braintrust baggage entries are preserved. - */ -export function _injectIntoCarrier( - carrier: TraceContextCarrier, - args: { - traceId: string | undefined; - spanId: string | undefined; - braintrustParent: string | undefined | null; - propagatedState?: PropagatedState | undefined; - }, -): void { - // Re-emit the inbound trace-flags verbatim so the upstream sampling decision - // (and any future flag bits) is preserved; defaults to sampled when we - // originated the trace (no inbound flags). - const traceFlags = args.propagatedState?.traceFlags; - const traceparent = traceFlags - ? formatTraceparent(args.traceId, args.spanId, traceFlags) - : formatTraceparent(args.traceId, args.spanId); - if (traceparent === undefined) { - // Ids aren't W3C-shaped (e.g. legacy UUID mode); nothing to propagate. - return; - } - setHeader(carrier, TRACEPARENT_HEADER, traceparent); - - // Forward upstream tracestate (per W3C, only alongside a valid traceparent). - const tracestate = args.propagatedState?.tracestate; - if (tracestate) { - setHeader(carrier, TRACESTATE_HEADER, tracestate); - } - - // Merge braintrust.parent into any existing baggage. Other vendors' members - // are forwarded byte-for-byte (see mergeBaggage) so we never rewrite their - // percent-encoding. - const existing = getHeader(carrier, BAGGAGE_HEADER); - const baggageValue = mergeBaggage(existing, args.braintrustParent); - if (baggageValue !== undefined) { - setHeader(carrier, BAGGAGE_HEADER, baggageValue); - } else if (existing !== undefined) { - // mergeBaggage returns undefined when the existing header only had a stale - // braintrust.parent (or otherwise no members worth forwarding), so remove - // the old header instead of letting a reused carrier propagate bad routing. - // If left in place, a downstream service could attach this trace to the - // previous project/experiment, or leak that parent to an unrelated request. - deleteHeader(carrier, BAGGAGE_HEADER); - } -} - -/** - * Inject W3C trace-context headers for the current (or given) span into a - * carrier. - * - * This is the free-function form of {@link Span.inject}, and the send-side - * counterpart of {@link extractTraceContextFromHeaders}. If no span is - * provided, the currently-active span is used. Propagation is best-effort and - * never throws. - * - * @param carrier Optional carrier (e.g. outbound HTTP headers) to mutate. - * Supports plain objects, Web `Headers`, Node-style `setHeader` carriers, - * framework `header` carriers, and mutable `HeadersInit` tuple arrays. - * @param options.span Optional span to inject. Defaults to the current span. - * @returns The carrier with propagation headers injected. - */ -export function injectTraceContext( - carrier?: undefined, - options?: OptionalStateArg & { span?: Span }, -): Record; -export function injectTraceContext( - carrier: T, - options?: OptionalStateArg & { span?: Span }, -): T; -export function injectTraceContext( - carrier?: T, - options?: OptionalStateArg & { span?: Span }, -): T | Record { - const resolvedCarrier = carrier ?? {}; - const span = options?.span ?? currentSpan({ state: options?.state }); - try { - return span.inject(resolvedCarrier); - } catch (e) { - debugLogger.warn(`Error injecting trace context: ${e}`); - return resolvedCarrier; - } -} - -/** - * Extract an opaque W3C trace-context from inbound request headers. - * - * This is the receive-side counterpart of {@link Span.inject} / - * {@link injectTraceContext}. The return value is an opaque propagation context - * that can be passed as `parent` to `startSpan`: - * - * ```ts - * const ctx = extractTraceContextFromHeaders(request.headers); - * traced((span) => { ... }, { name: "handler", parent: ctx }); - * ``` - * - * Only the W3C Trace Context headers are interpreted (`traceparent`, `baggage`, - * `tracestate`); header lookups are case-insensitive. If no valid `traceparent` - * is present, returns undefined (the caller starts a fresh root span). The - * Braintrust container the trace is routed under is resolved when the span is - * created: from the `braintrust.parent` baggage entry, or else the - * currently-active logger/experiment. - * - * Callers should treat the return value as opaque. - * - * @param headers Inbound request headers (e.g. an HTTP framework's headers). - * @returns An opaque context for `startSpan({ parent })`, or undefined. - */ -export function extractTraceContextFromHeaders( - headers: TraceContextHeaders | null | undefined, -): PropagationContext | undefined { - if (!headers) { - return undefined; - } - - const traceparent = getHeader(headers, TRACEPARENT_HEADER); - if (!traceparent || parseTraceparent(traceparent) === undefined) { - return undefined; - } - - const context: PropagationContext = { [TRACEPARENT_HEADER]: traceparent }; - const baggageValue = getHeader(headers, BAGGAGE_HEADER); - if (baggageValue) { - context[BAGGAGE_HEADER] = baggageValue; - } - const tracestate = getHeader(headers, TRACESTATE_HEADER); - if (tracestate) { - // `tracestate` is opaque vendor state that we forward but never author or - // interpret. It is passed along unchanged; receivers that want to use it - // validate it themselves, per the W3C Trace Context spec. - context[TRACESTATE_HEADER] = tracestate; - } - return context; -} - -/** - * Resolve a W3C trace-context into `{ parentSlug, propagatedState }`. - * - * Reads `traceparent` for trace identity and `braintrust.parent` from `baggage` - * (falling back to the currently-active logger/experiment) for routing, and - * builds an internal Braintrust parent slug. Captures the `tracestate` and raw - * `traceparent` flags to forward onward. Returns `{ undefined, undefined }` if - * the context cannot be resolved into a usable parent (so the caller falls back - * to local precedence / a fresh root). - */ -function resolveW3cParent( - context: PropagationContext, - state?: BraintrustState, -): { - parentSlug: string | undefined; - propagatedState: PropagatedState | undefined; -} { - const traceparent = getHeader(context, TRACEPARENT_HEADER); - const parsed = traceparent ? parseTraceparent(traceparent) : undefined; - if (parsed === undefined) { - return { parentSlug: undefined, propagatedState: undefined }; - } - const { traceId, spanId, traceFlags } = parsed; - - // Determine the Braintrust container: baggage -> current logger/experiment. - let braintrustParent: string | undefined = undefined; - const baggageValue = getHeader(context, BAGGAGE_HEADER); - if (baggageValue) { - braintrustParent = parseBaggage(baggageValue)[BRAINTRUST_PARENT_KEY]; - } - if (!braintrustParent) { - braintrustParent = currentBraintrustParent(state); - } - if (!braintrustParent) { - debugLogger.warn( - "Received traceparent without a braintrust.parent and no active logger/experiment; " + - "cannot route the trace. Starting a fresh local span instead.", - ); - return { parentSlug: undefined, propagatedState: undefined }; - } - - const parsedParent = braintrustParentToComponents(braintrustParent); - if (parsedParent === undefined) { - debugLogger.warn( - `Invalid braintrust.parent: ${JSON.stringify(braintrustParent)}`, - ); - return { parentSlug: undefined, propagatedState: undefined }; - } - const { objectType, objectId, computeArgs } = parsedParent; - - // `tracestate` is opaque vendor state, forwarded unchanged and never - // authored or interpreted by Braintrust. - const tracestate = getHeader(context, TRACESTATE_HEADER); - - const slug = new SpanComponentsV4({ - object_type: objectType, - ...(computeArgs - ? { compute_object_metadata_args: computeArgs } - : { object_id: objectId }), - row_id: "bt-propagation", // non-empty to enable span_id/root_span_id - span_id: spanId, - root_span_id: traceId, - // eslint-disable-next-line @typescript-eslint/consistent-type-assertions - } as SpanComponentsV4Data).toStr(); - return { - parentSlug: slug, - propagatedState: { tracestate, traceFlags, braintrustParent }, - }; -} - -/** - * Normalize a `parent` argument into `{ parentSlug, propagatedState }`. - * - * - object -> interpreted as a W3C trace-context (from - * `extractTraceContextFromHeaders`) - * - string -> an exported span slug (passed through unchanged) - * - undefined -> no parent - * - * `propagatedState` carries any inbound W3C state (tracestate + raw flags) to - * forward on the next `inject`; it is undefined when there is no inbound W3C - * context. - */ -function normalizeParent( - parent: string | PropagationContext | undefined | null, - state?: BraintrustState, -): { - parentSlug: string | undefined; - propagatedState: PropagatedState | undefined; -} { - if (parent && typeof parent === "object") { - return resolveW3cParent(parent, state); - } - return { - parentSlug: parent ?? undefined, - propagatedState: undefined, - }; -} - -/** - * Return true if a parent slug carries span ids the child can link to. - * - * A child links to any parent slug that carries both a span id and a root span - * id, regardless of whether those ids are hex (the default) or legacy UUID. This - * keeps the deprecated `startSpan({ parent: })` path backwards - * compatible: a slug exported by an older (UUID) sender still links into a trace - * on a newer (hex) receiver, and vice versa. The child's own freshly generated - * span id stays in the active format; the backend supports traces whose nodes - * mix id formats across a propagation boundary. Empty ids (no row) are handled - * by the caller. - */ -function parentSpanIdsUsable( - spanId: string | undefined | null, - rootSpanId: string | undefined | null, -): boolean { - return Boolean(spanId) && Boolean(rootSpanId); -} - -export function logError(span: Span, error: unknown) { - let errorMessage = ""; - let stackTrace = ""; - if (error instanceof Error) { - errorMessage = error.message; - stackTrace = error.stack || ""; - } else { - errorMessage = String(error); - } - span.log({ error: `${errorMessage}\n\n${stackTrace}` }); -} - -/** - * Toplevel function for starting a span. It checks the following (in precedence order): - * * Currently-active span - * * Currently-active experiment - * * Currently-active logger - * - * and creates a span under the first one that is active. Alternatively, if `parent` is specified, it creates a span under the specified parent row. If none of these are active, it returns a no-op span object. - * - * See {@link Span.traced} for full details. - */ -export function traced( - callback: (span: Span) => R, - args?: StartSpanArgs & - SetCurrentArg & - AsyncFlushArg & - OptionalStateArg, -): PromiseUnless { - const { span, isSyncFlushLogger } = startSpanAndIsLogger(args); - - const ret = runCatchFinally( - () => { - if (args?.setCurrent ?? true) { - return withCurrent(span, callback); - } else { - return callback(span); - } - }, - (e) => { - logError(span, e); - throw e; - }, - () => span.end(), - ); - - type Ret = PromiseUnless; - - if (args?.asyncFlush === undefined || args?.asyncFlush) { - return ret as Ret; - } else { - return (async () => { - const awaitedRet = await ret; - if (isSyncFlushLogger) { - await span.flush(); - } - return awaitedRet; - })() as Ret; - } -} - -/** - * Check if a function is a sync generator function. - * - * Note: This uses Object.prototype.toString which is sufficient for environments that - * support generator functions (ES6+). While our code is compiled to ES2022, consumers - * may run it in various environments. However, if generators aren't supported in their - * environment, the generator functions themselves won't work anyway, making detection moot. - * - * @param fn The function to check. - * @returns True if the function is a sync generator function. - */ -function isGeneratorFunction(fn: any): boolean { - return Object.prototype.toString.call(fn) === "[object GeneratorFunction]"; -} - -/** - * Check if a function is an async generator function. - * - * Note: see isGeneratorFunction disclaimer - * @param fn The function to check. - * @returns True if the function is an async generator function. - */ -function isAsyncGeneratorFunction(fn: any): boolean { - return ( - Object.prototype.toString.call(fn) === "[object AsyncGeneratorFunction]" - ); -} - -/** - * Wrap a sync generator function with tracing. - */ -function wrapTracedSyncGenerator any>( - fn: F, - spanArgs: any, - noTraceIO: boolean, -): F { - const wrapper = function* (this: any, ...fnArgs: Parameters) { - const span = startSpan(spanArgs); - try { - if (!noTraceIO) { - span.log({ input: fnArgs }); - } - - const maxItems = getNumericEnv("BRAINTRUST_MAX_GENERATOR_ITEMS") ?? 1000; - - if (!noTraceIO && maxItems !== 0) { - let collected: any[] = []; - let truncated = false; - - const gen = generatorWithCurrent(span, fn.apply(this, fnArgs)); - try { - for (const value of gen) { - if ( - maxItems === -1 || - (!truncated && collected.length < maxItems) - ) { - collected.push(value); - } else { - truncated = true; - collected = []; - debugLogger.warn( - `Generator output exceeded limit of ${maxItems} items, output not logged. ` + - `Increase BRAINTRUST_MAX_GENERATOR_ITEMS or set to -1 to disable limit.`, - ); - } - yield value; - } - - if (!truncated) { - span.log({ output: collected }); - } - } catch (error) { - logError(span, error); - if (!truncated && collected.length > 0) { - span.log({ output: collected }); - } - throw error; - } - } else { - // No output collection - const gen = generatorWithCurrent(span, fn.apply(this, fnArgs)); - for (const value of gen) { - yield value; - } - } - } finally { - span.end(); - } - }; - // Preserve the original function name - Object.defineProperty(wrapper, "name", { value: fn.name }); - return wrapper as F; -} - -/** - * Wrap an async generator function with tracing. - */ -function wrapTracedAsyncGenerator any>( - fn: F, - spanArgs: any, - noTraceIO: boolean, -): F { - const wrapper = async function* (this: any, ...fnArgs: Parameters) { - const span = startSpan(spanArgs); - try { - if (!noTraceIO) { - span.log({ input: fnArgs }); - } - - const maxItems = getNumericEnv("BRAINTRUST_MAX_GENERATOR_ITEMS") ?? 1000; - - if (!noTraceIO && maxItems !== 0) { - let collected: any[] = []; - let truncated = false; - - const gen = asyncGeneratorWithCurrent(span, fn.apply(this, fnArgs)); - try { - for await (const value of gen) { - if ( - maxItems === -1 || - (!truncated && collected.length < maxItems) - ) { - collected.push(value); - } else { - truncated = true; - collected = []; - debugLogger.warn( - `Generator output exceeded limit of ${maxItems} items, output not logged. ` + - `Increase BRAINTRUST_MAX_GENERATOR_ITEMS or set to -1 to disable limit.`, - ); - } - yield value; - } - - if (!truncated) { - span.log({ output: collected }); - } - } catch (error) { - logError(span, error); - if (!truncated && collected.length > 0) { - span.log({ output: collected }); - } - throw error; - } - } else { - // No output collection - const gen = asyncGeneratorWithCurrent(span, fn.apply(this, fnArgs)); - for await (const value of gen) { - yield value; - } - } - } finally { - span.end(); - } - }; - // Preserve the original function name - Object.defineProperty(wrapper, "name", { value: fn.name }); - return wrapper as F; -} - -type WrapTracedArgs = { - noTraceIO?: boolean; -}; - -/** - * Wrap a function with `traced`, using the arguments as `input` and return value as `output`. - * Any functions wrapped this way will automatically be traced, similar to the `@traced` decorator - * in Python. If you want to correctly propagate the function's name and define it in one go, then - * you can do so like this: - * - * ```ts - * const myFunc = wrapTraced(async function myFunc(input) { - * const result = await client.chat.completions.create({ - * model: "gpt-3.5-turbo", - * messages: [{ role: "user", content: input }], - * }); - * return result.choices[0].message.content ?? "unknown"; - * }, - * // Optional: if you're using a framework like NextJS that minifies your code, specify the function name and it will be used for the span name - * { name: "myFunc" }, - * ); - * ``` - * Now, any calls to `myFunc` will be traced, and the input and output will be logged automatically. - * If tracing is inactive, i.e. there is no active logger or experiment, it's just a no-op. - * - * @param fn The function to wrap. - * @param args Span-level arguments (e.g. a custom name or type) to pass to `traced`. - * @returns The wrapped function. - */ -export function wrapTraced< - F extends (...args: any[]) => any, - IsAsyncFlush extends boolean = true, ->( - fn: F, - args?: StartSpanArgs & - SetCurrentArg & - AsyncFlushArg & - WrapTracedArgs, -): IsAsyncFlush extends false - ? (...args: Parameters) => Promise>> - : F { - const spanArgs: typeof args = { - name: fn.name, - type: "function", - ...args, - }; - const hasExplicitInput = - args && - args.event && - "input" in args.event && - args.event.input !== undefined; - const hasExplicitOutput = - args && args.event && args.event.output !== undefined; - - const noTraceIO = args?.noTraceIO || hasExplicitInput || hasExplicitOutput; - // Check if the function is a generator - if (isGeneratorFunction(fn)) { - return wrapTracedSyncGenerator(fn, spanArgs, !!noTraceIO); - } - - if (isAsyncGeneratorFunction(fn)) { - return wrapTracedAsyncGenerator(fn, spanArgs, !!noTraceIO); - } - - if (args?.asyncFlush) { - return ((...fnArgs: Parameters) => - traced((span) => { - if (!args?.noTraceIO && !hasExplicitInput) { - span.log({ input: fnArgs }); - } - - const output = fn(...fnArgs); - - if (!args?.noTraceIO && !hasExplicitOutput) { - if (output instanceof Promise) { - return (async () => { - const result = await output; - span.log({ output: result }); - return result; - })(); - } else { - span.log({ output: output }); - } - } - - return output; - }, spanArgs)) as IsAsyncFlush extends false ? never : F; - } else { - return ((...fnArgs: Parameters) => - traced(async (span) => { - if (!args?.noTraceIO && !hasExplicitInput) { - span.log({ input: fnArgs }); - } - - const outputResult = fn(...fnArgs); - - const output = await outputResult; - - if (!args?.noTraceIO && !hasExplicitOutput) { - span.log({ output }); - } - - return output; - }, spanArgs)) as IsAsyncFlush extends false - ? (...args: Parameters) => Promise>> - : never; - } -} - -/** - * A synonym for `wrapTraced`. If you're porting from systems that use `traceable`, you can use this to - * make your codebase more consistent. - */ -export const traceable = wrapTraced; - -/** - * Lower-level alternative to `traced`. This allows you to start a span yourself, and can be useful in situations - * where you cannot use callbacks. However, spans started with `startSpan` will not be marked as the "current span", - * so `currentSpan()` and `traced()` will be no-ops. If you want to mark a span as current, use `traced` instead. - * - * See {@link traced} for full details. - */ -export function startSpan( - args?: StartSpanArgs & AsyncFlushArg & OptionalStateArg, -): Span { - return startSpanAndIsLogger(args).span; -} - -/** @internal Start a span whose initial row is merged with concurrent writes. */ -export function _internalStartSpanWithInitialMerge< - IsAsyncFlush extends boolean = true, ->(args?: StartSpanArgs & AsyncFlushArg & OptionalStateArg): Span { - return startSpanAndIsLogger({ - ...args, - [INITIAL_SPAN_WRITE_AS_MERGE]: true, - } as StartSpanArgs & - AsyncFlushArg & - OptionalStateArg & - InitialSpanWriteAsMergeArg).span; -} - -/** @internal Start a deterministic span under an exported object-only parent. */ -export function _internalStartSpanWithInitialMergeAndParentSpanIds< - IsAsyncFlush extends boolean = true, ->(args: StartSpanArgs & AsyncFlushArg & OptionalStateArg): Span { - return startSpanAndIsLogger( - { - ...args, - [INITIAL_SPAN_WRITE_AS_MERGE]: true, - } as StartSpanArgs & - AsyncFlushArg & - OptionalStateArg & - InitialSpanWriteAsMergeArg, - { useParentSpanIdsForObjectParent: true }, - ).span; -} - -/** @internal Start a span with SDK-controlled context fields. */ -export function _internalStartSpanWithContext< - IsAsyncFlush extends boolean = true, ->( - args: StartSpanArgs & AsyncFlushArg & OptionalStateArg, - context: Record, -): Span { - return startSpanAndIsLogger({ - ...args, - [INTERNAL_SPAN_CONTEXT]: context, - }).span; -} - -/** - * Flush any pending rows to the server. - */ -export async function flush(options?: OptionalStateArg): Promise { - const state = options?.state ?? _globalState; - return await state.bgLogger().flush(); -} - -/** - * Set the fetch implementation to use for requests. You can specify it here, - * or when you call `login`. - * - * @param fetch The fetch implementation to use. - */ -export function setFetch(fetch: typeof globalThis.fetch): void { - _internalGetGlobalState().setFetch(fetch); -} - -function startSpanAndIsLogger( - args?: StartSpanArgs & - AsyncFlushArg & - OptionalStateArg & - InternalSpanContextArg, - internalOptions?: { useParentSpanIdsForObjectParent?: boolean }, -): { span: Span; isSyncFlushLogger: boolean } { - const state = args?.state ?? _globalState; - - // Resolve the parent object and any forwarded W3C state in one pass, so we - // don't re-normalize `parent` (which could disagree if the active - // logger/experiment changed between calls). - const { parentObject, propagatedState } = - getSpanParentObjectAndPropagatedState({ - asyncFlush: args?.asyncFlush, - parent: args?.parent, - state, - }); - - if ( - parentObject instanceof SpanComponentsV3 || - parentObject instanceof SpanComponentsV4 - ) { - const parentSpanIds: ParentSpanIds | MultiParentSpanIds | undefined = - parentObject.data.row_id && - parentSpanIdsUsable( - parentObject.data.span_id, - parentObject.data.root_span_id, - ) - ? { - spanId: parentObject.data.span_id, - rootSpanId: parentObject.data.root_span_id, - } - : internalOptions?.useParentSpanIdsForObjectParent - ? args?.parentSpanIds - : undefined; - // The parent object/state are already resolved from `parent` above; drop - // the raw `parent` so it isn't re-normalized. - const { parent: _ignoredParent, ...spanArgs } = args ?? {}; - const span = new SpanImpl({ - state, - ...spanArgs, - parentObjectType: parentObject.data.object_type, - parentObjectId: new LazyValue( - spanComponentsToObjectIdLambda(state, parentObject), - ), - parentComputeObjectMetadataArgs: - parentObject.data.compute_object_metadata_args ?? undefined, - parentSpanIds, - propagatedEvent: - args?.propagatedEvent ?? - // eslint-disable-next-line @typescript-eslint/consistent-type-assertions - ((parentObject.data.propagated_event ?? undefined) as - | StartSpanEventArgs - | undefined), - propagatedState, - }); - return { - span, - isSyncFlushLogger: - parentObject.data.object_type === SpanObjectTypeV3.PROJECT_LOGS && - // Since there's no parent logger here, we're free to choose the async flush - // behavior, and therefore propagate along whatever we get from the arguments - args?.asyncFlush === false, - }; - } else { - const span = parentObject.startSpan(args); - return { - span, - isSyncFlushLogger: - parentObject.kind === "logger" && parentObject.asyncFlush === false, - }; - } -} - -/** - * Runs the provided callback with the span as the current span. - */ -export function withCurrent( - span: Span, - callback: (span: Span) => R, - state: BraintrustState | undefined = undefined, -): R { - const currentState = state ?? _globalState; - return currentState.contextManager.runInContext(span, () => callback(span)); -} - -/** - * Wraps a sync generator to maintain the current span context across yields. - */ -function* generatorWithCurrent( - span: Span, - gen: Generator, - state: BraintrustState | undefined = undefined, -): Generator { - let nextValue: any; - while (true) { - const result = withCurrent( - span, - () => { - try { - return gen.next(nextValue); - } catch (e) { - // Handle generator.throw() - return { value: undefined, done: true, error: e }; - } - }, - state, - ); - - if ("error" in result) { - throw result.error; - } - - if (result.done) { - return result.value; - } - - nextValue = yield result.value; - } -} - -/** - * Wraps an async generator to maintain the current span context across yields. - */ -async function* asyncGeneratorWithCurrent( - span: Span, - gen: AsyncGenerator, - state: BraintrustState | undefined = undefined, -): AsyncGenerator { - let nextValue: any; - while (true) { - const result = await withCurrent( - span, - async () => { - try { - return await gen.next(nextValue); - } catch (e) { - // Handle generator.throw() - return { value: undefined, done: true, error: e }; - } - }, - state, - ); - - if ("error" in result) { - throw result.error; - } - - if (result.done) { - return result.value; - } - - nextValue = yield result.value; - } -} - -export function withParent( - parent: string | PropagationContext, - callback: () => R, - state: BraintrustState | undefined = undefined, -): R { - return (state ?? _globalState).currentParent.run(parent, () => callback()); -} - -function _saveOrgInfo( - state: BraintrustState, - orgInfo: any, - orgName: string | undefined, -) { - const org = selectLoginOrg(orgInfo, orgName); - state.orgId = org.id; - state.orgName = org.name; - state.apiUrl = iso.getEnv("BRAINTRUST_API_URL") || org.api_url; - state.proxyUrl = iso.getEnv("BRAINTRUST_PROXY_URL") || org.proxy_url; - state.gitMetadataSettings = org.git_metadata || undefined; -} - -function selectLoginOrg(orgInfo: any, orgName: string | undefined) { - if (orgInfo.length === 0) { - throw new LoginInvalidOrgError( - "This user is not part of any organizations.", - ); - } - - for (const org of orgInfo) { - if (orgName === undefined || org.name === orgName) { - return org; - } - } - - throw new LoginInvalidOrgError( - `Organization ${orgName} not found. Must be one of ${orgInfo - .map((org: any) => org.name) - .join(", ")}`, - ); -} - -function validateTags(tags: readonly string[]) { - const seen = new Set(); - for (const tag of tags) { - if (typeof tag !== "string") { - throw new Error("tags must be strings"); - } - - if (seen.has(tag)) { - throw new Error(`duplicate tag: ${tag}`); - } - seen.add(tag); - } -} - -function validateAndSanitizeExperimentLogPartialArgs( - event: ExperimentLogPartialArgs, -): SanitizedExperimentLogPartialArgs { - if (event.scores) { - if (Array.isArray(event.scores)) { - throw new Error("scores must be an object, not an array"); - } - for (const [name, rawScore] of Object.entries(event.scores)) { - let score = rawScore; - if (typeof name !== "string") { - throw new Error("score names must be strings"); - } - - if (score === null || score === undefined) { - continue; - } - - if (typeof score === "boolean") { - score = score ? 1 : 0; - event.scores[name] = score; - } - - if (typeof score !== "number") { - throw new Error("score values must be numbers"); - } - if (score < 0 || score > 1) { - throw new Error("score values must be between 0 and 1"); - } - } - } - - if (event.metadata) { - for (const key of Object.keys(event.metadata)) { - if (typeof key !== "string") { - throw new Error("metadata keys must be strings"); - } - } - } - - if (event.metrics) { - for (const [key, value] of Object.entries(event.metrics)) { - if (typeof key !== "string") { - throw new Error("metric keys must be strings"); - } - - if (value !== undefined && typeof value !== "number") { - throw new Error("metric values must be numbers"); - } - } - } - - if ("input" in event && event.input && "inputs" in event && event.inputs) { - throw new Error( - "Only one of input or inputs (deprecated) can be specified. Prefer input.", - ); - } - - if ("tags" in event && event.tags) { - validateTags(event.tags); - } - - if ("inputs" in event) { - const { inputs, ...rest } = event; - return { input: inputs, ...rest }; - } else { - return { ...event }; - } -} - -/** - * Creates a deep copy of the given event. Replaces references to user objects - * with placeholder strings to ensure serializability, except for - * {@link Attachment} and {@link ExternalAttachment} objects, which are preserved - * and not deep-copied. - */ -export function deepCopyEvent>( - event: T, -): T { - const attachments: BaseAttachment[] = []; - const ATTACHMENT_INDEX_KEY = "_bt_internal_saved_attachment_idx"; - const ATTACHMENT_MARKER_KEY = "_bt_internal_saved_attachment_marker"; - const attachmentMarker = ++deepCopyEventMarkerCounter; - - // We both check for serializability and round-trip `event` through JSON in - // order to create a "deep copy". This has the benefit of cutting out any - // reference to user objects when the object is logged asynchronously, so that - // in case the objects are modified, the logging is unaffected. In the future, - // this could be changed to a "real" deep copy so that immutable types (long - // strings) do not have to be copied. - const serialized = JSON.stringify(event, (_k, v) => { - if (v instanceof Error) { - return v.message; - } else if (v instanceof SpanImpl || v instanceof NoopSpan) { - return ``; - } else if (v instanceof Experiment) { - return ``; - } else if (v instanceof Dataset) { - return ``; - } else if (v instanceof Logger) { - return ``; - } else if (v instanceof BaseAttachment) { - const idx = attachments.push(v); - return { - [ATTACHMENT_INDEX_KEY]: idx - 1, - [ATTACHMENT_MARKER_KEY]: attachmentMarker, - }; - } else if (v instanceof ReadonlyAttachment) { - return v.reference; - } - return v; - }); - const x = JSON.parse(serialized, (_k, v) => { - if (isDeepCopyAttachmentPlaceholder(v, attachmentMarker)) { - return attachments[v[ATTACHMENT_INDEX_KEY]]; - } - return v; - }); - return x; -} - -let deepCopyEventMarkerCounter = 0; - -function isDeepCopyAttachmentPlaceholder( - value: unknown, - attachmentMarker: number, -): value is Record< - "_bt_internal_saved_attachment_idx" | "_bt_internal_saved_attachment_marker", - number -> { - if (!value || typeof value !== "object" || Array.isArray(value)) { - return false; - } - - const record = value as Record; - if ( - !Object.hasOwn(record, "_bt_internal_saved_attachment_idx") || - !Object.hasOwn(record, "_bt_internal_saved_attachment_marker") - ) { - return false; - } - - if (Object.keys(record).length !== 2) { - return false; + const components = braintrustParentToComponents(parent._getOtelParent()); + if (!components) { + return undefined; } - - const attachmentIndex = record._bt_internal_saved_attachment_idx as number; - const marker = record._bt_internal_saved_attachment_marker; - - return ( - Number.isInteger(attachmentIndex) && - attachmentIndex >= 0 && - marker === attachmentMarker - ); + return new SpanComponentsV4({ + object_type: components.objectType, + ...(components.objectId + ? { object_id: components.objectId } + : { compute_object_metadata_args: components.computeArgs ?? {} }), + }).toStr(); } /** - * Helper function for uploading attachments. Recursively extracts `Attachment` - * and `ExternalAttachment` objects and replaces them with their associated - * `AttachmentReference` objects. + * Return the Braintrust parent string for the current logger/experiment, if any. * - * @param event The event to filter. Will be modified in-place. - * @param attachments Flat array of extracted attachments (output parameter). + * Used as the fallback Braintrust parent on receive, when an inbound request + * carries trace identity (`traceparent`) but no `braintrust.parent` baggage. */ -function extractAttachments( - event: Record, - attachments: BaseAttachment[], -): void { - const pending: { - event: Record; - key: string; - attachment: BaseAttachment; - }[] = []; - let automatic = false; - const walk = (event: Record, depth = 0): void => { - if (depth >= 128) return; - for (const [key, value] of Object.entries(event)) { - if (!value) { - continue; - } - - // Base case: Attachment or ExternalAttachment. - if (value instanceof BaseAttachment) { - automatic ||= automaticAttachmentOriginals.has(value); - pending.push({ event, key, attachment: value }); - continue; // Attachment cannot be nested. - } - - // Skip if this is already just a reference (no uploader field) - if ( - [ - BRAINTRUST_ATTACHMENT, - EXTERNAL_ATTACHMENT, - "inline_attachment", - ].includes(value?.type) - ) { - // This is already just a reference, skip it - continue; - } - - // Somewhere in our pipeline we're serializing the attachment and then deserializing it. - // This loses the instanceof identity of the attachment, so we need to recreate it because - // the above instance check doesn't catch it. - if (value?.reference?.type === BRAINTRUST_ATTACHMENT && value?.uploader) { - // This looks like a serialized Attachment object, recreate it properly - const attachment = new Attachment({ - data: value.dataDebugString, - filename: value.reference.filename, - contentType: value.reference.content_type, - }); - pending.push({ event, key, attachment }); - continue; - } - - // Base case: non-object. - if (!(value instanceof Object)) { - continue; // Nothing to explore recursively. - } +function currentBraintrustParent(state?: BraintrustState): string | undefined { + const resolvedState = state ?? _internalGetGlobalState(); - // Recursive case: object or array. - walk(value, depth + 1); + const experiment = currentExperiment({ state: resolvedState }); + if (experiment) { + try { + return experiment._getOtelParent() ?? undefined; + } catch { + return undefined; } - }; - const queueLength = attachments.length; - try { - walk(event); - // Read all references and enqueue before committing any replacement. - const references = pending.map(({ attachment }) => attachment.reference); - for (const { attachment } of pending) attachments.push(attachment); - pending.forEach(({ event, key }, index) => { - event[key] = references[index]; - }); - } catch (error) { - attachments.length = queueLength; - if (!automatic) throw error; - Object.assign(event, restoreAutomaticAttachments(event)); - } -} - -function collectAttachmentReferenceKeys(value: unknown, keys: Set) { - if (!value || typeof value !== "object") return; - if (Array.isArray(value)) { - value.forEach((item) => collectAttachmentReferenceKeys(item, keys)); - return; - } - const record = value as Record; - if (record.type === BRAINTRUST_ATTACHMENT && typeof record.key === "string") { - keys.add(record.key); - return; } - Object.values(record).forEach((item) => - collectAttachmentReferenceKeys(item, keys), - ); -} - -/** - * Recursively hydrates any `AttachmentReference` into `Attachment` by modifying - * the input in-place. - * - * @returns The same event instance as the input. - */ -// eslint-disable-next-line @typescript-eslint/no-explicit-any -function enrichAttachments>( - event: T, - state: BraintrustState | undefined, -): T { - for (const [key, value] of Object.entries(event)) { - // Base case: AttachmentReference. - const parsedValue = attachmentReferenceSchema.safeParse(value); - if (parsedValue.success) { - // eslint-disable-next-line @typescript-eslint/consistent-type-assertions, @typescript-eslint/no-explicit-any - (event as any)[key] = new ReadonlyAttachment(parsedValue.data, state); - continue; - } - // Base case: non-object. - if (!(value instanceof Object)) { - continue; + const logger = currentLogger({ state: resolvedState }); + if (logger) { + try { + return logger._getOtelParent() ?? undefined; + } catch { + return undefined; } - - // Recursive case: object or array: - enrichAttachments(value, state); } - return event; + return undefined; +} + +interface BraintrustParentComponents { + objectType: SpanObjectTypeV3; + objectId: string | undefined; + computeArgs: Record | undefined; } /** - * Recursively hydrates any `AttachmentReference` into `Attachment` by modifying - * the input in-place. + * Parse a `braintrust.parent` string into object type / id / compute args. * - * @returns The same event instance as the input. + * Accepts `project_id:`, `project_name:`, or `experiment_id:`. + * Returns undefined if the value is empty or malformed. */ -// eslint-disable-next-line @typescript-eslint/no-explicit-any -async function resolveAttachmentsToBase64>( - event: T, - state: BraintrustState | undefined, -): Promise { - for (const [key, value] of Object.entries(event)) { - if (value instanceof ReadonlyAttachment) { - // eslint-disable-next-line @typescript-eslint/consistent-type-assertions, @typescript-eslint/no-explicit-any - (event as any)[key] = await value.asBase64Url(); - continue; - } - - // Base case: non-object. - if (!(value instanceof Object)) { - continue; - } - - // Recursive case: object or array: - await resolveAttachmentsToBase64(value, state); - } - - return event; -} - -// Note that this only checks properties that are expected of a complete event. -// validateAndSanitizeExperimentLogPartialArgs should still be invoked (after -// handling special fields like 'id'). -function validateAndSanitizeExperimentLogFullArgs( - event: ExperimentLogFullArgs, - hasDataset: boolean, -): ExperimentLogFullArgs { - if ( - ("input" in event && - !isEmpty(event.input) && - "inputs" in event && - !isEmpty(event.inputs)) || - (!("input" in event) && !("inputs" in event)) - ) { - throw new Error( - "Exactly one of input or inputs (deprecated) must be specified. Prefer input.", - ); +function braintrustParentToComponents( + braintrustParent: string | undefined | null, +): BraintrustParentComponents | undefined { + if (!braintrustParent) { + return undefined; } - - if (isEmpty(event.output)) { - throw new Error("output must be specified"); + if (braintrustParent.startsWith("project_id:")) { + const objectId = braintrustParent.slice("project_id:".length); + return objectId + ? { + objectType: SpanObjectTypeV3.PROJECT_LOGS, + objectId, + computeArgs: undefined, + } + : undefined; } - if (isEmpty(event.scores)) { - throw new Error("scores must be specified"); + if (braintrustParent.startsWith("project_name:")) { + const name = braintrustParent.slice("project_name:".length); + return name + ? { + objectType: SpanObjectTypeV3.PROJECT_LOGS, + objectId: undefined, + computeArgs: { project_name: name }, + } + : undefined; } - - if (hasDataset && event.datasetRecordId === undefined) { - throw new Error("datasetRecordId must be specified when using a dataset"); - } else if (!hasDataset && event.datasetRecordId !== undefined) { - throw new Error( - "datasetRecordId cannot be specified when not using a dataset", - ); + if (braintrustParent.startsWith("experiment_id:")) { + const objectId = braintrustParent.slice("experiment_id:".length); + return objectId + ? { + objectType: SpanObjectTypeV3.EXPERIMENT, + objectId, + computeArgs: undefined, + } + : undefined; } - - return event; + return undefined; } -export type WithTransactionId = R & { - [TRANSACTION_ID_FIELD]: TransactionId; -}; - -export const DEFAULT_FETCH_BATCH_SIZE = 1000; -export const MAX_BTQL_ITERATIONS = 10000; - -export class ObjectFetcher implements AsyncIterable< - WithTransactionId -> { - private _fetchedData: WithTransactionId[] | undefined = undefined; - - constructor( - private objectType: - | "dataset" - | "experiment" - | "project_logs" - | "playground_logs", - private pinnedVersion: string | undefined, - // eslint-disable-next-line @typescript-eslint/no-explicit-any - private mutateRecord?: (r: any) => WithTransactionId, - private _internal_btql?: Record, - private _internalBrainstoreRealtime = true, - ) {} +/** + * Set a W3C trace-context header on a carrier, sending the lowercase name. + * + * Per the W3C Trace Context spec (§3.2.1 / §3.3.1), vendors SHOULD send these + * header names in lowercase. `name` is always the canonical lowercase key. A + * plain object carrier is case-sensitive, so any pre-existing case-variant (e.g. + * `Baggage` from a framework that title-cases headers) must be removed first, + * otherwise the carrier would end up with two conflicting headers. + */ +function isMutableHeaderTupleArray( + carrier: TraceContextCarrier, +): carrier is [string, string][] { + return ( + Array.isArray(carrier) && + carrier.every((item) => Array.isArray(item) && typeof item[0] === "string") + ); +} - public get id(): Promise { - throw new Error("ObjectFetcher subclasses must have an 'id' attribute"); +function setHeader( + carrier: TraceContextCarrier, + name: string, + value: string, +): void { + if (isMutableHeaderTupleArray(carrier)) { + const lowered = name.toLowerCase(); + for (let i = carrier.length - 1; i >= 0; i--) { + if (carrier[i][0].toLowerCase() === lowered) { + carrier.splice(i, 1); + } + } + carrier.push([name, value]); + return; } - protected async getState(): Promise { - throw new Error("ObjectFetcher subclasses must have a 'getState' method"); + const setter = (carrier as { set?: unknown }).set; + if (typeof setter === "function") { + const deleter = (carrier as { delete?: unknown }).delete; + if (typeof deleter === "function") { + try { + deleter.call(carrier, name); + } catch { + // Setting below may still succeed for custom header-like objects. + } + } + setter.call(carrier, name, value); + return; } - protected getPinnedVersion(): string | undefined { - return this.pinnedVersion; + const nodeSetter = (carrier as { setHeader?: unknown }).setHeader; + if (typeof nodeSetter === "function") { + const remover = (carrier as { removeHeader?: unknown }).removeHeader; + if (typeof remover === "function") { + try { + remover.call(carrier, name); + } catch { + // Setting below may still succeed for custom header-like objects. + } + } + nodeSetter.call(carrier, name, value); + return; } - protected setPinnedVersion(pinnedVersion: string | undefined): void { - this.pinnedVersion = pinnedVersion; + const headerSetter = (carrier as { header?: unknown }).header; + if (typeof headerSetter === "function") { + const deleter = (carrier as { delete?: unknown }).delete; + if (typeof deleter === "function") { + try { + deleter.call(carrier, name); + } catch { + // Setting below may still succeed for custom header-like objects. + } + } + const remover = (carrier as { removeHeader?: unknown }).removeHeader; + if (typeof remover === "function") { + try { + remover.call(carrier, name); + } catch { + // Setting below may still succeed for custom header-like objects. + } + } + headerSetter.call(carrier, name, value); + return; } - protected getInternalBtql(): Record | undefined { - return this._internal_btql; + const headerBag = carrier as { [name: string]: string }; + const lowered = name.toLowerCase(); + for (const key of Object.keys(headerBag)) { + if (key !== name && key.toLowerCase() === lowered) { + delete headerBag[key]; + } } + headerBag[name] = value; +} - private async *fetchRecordsFromApi( - batchSize: number | undefined, - ): AsyncGenerator> { - const state = await this.getState(); - const objectId = await this.id; - const batchLimit = batchSize ?? DEFAULT_FETCH_BATCH_SIZE; - const internalLimit = getInternalBtqlLimit(this._internal_btql); - let remainingLimit = internalLimit; - const internalBtqlWithoutReservedQueryKeys = Object.fromEntries( - Object.entries(this._internal_btql ?? {}).filter( - ([key]) => - key !== "cursor" && - key !== "limit" && - key !== "select" && - key !== "from", - ), - ); - let cursor = undefined; - let iterations = 0; - while (true) { - if (remainingLimit !== undefined && remainingLimit <= 0) { - return; - } - const limit = - remainingLimit === undefined - ? batchLimit - : Math.min(batchLimit, remainingLimit); - const resp = await state.apiConn().post( - `btql`, - { - query: { - select: [ - { - op: "star", - }, - ], - from: { - op: "function", - name: { - op: "ident", - name: [this.objectType], - }, - args: [ - { - op: "literal", - value: objectId, - }, - ], - }, - cursor, - limit, - ...internalBtqlWithoutReservedQueryKeys, - }, - use_columnstore: false, - brainstore_realtime: this._internalBrainstoreRealtime, - query_source: `js_sdk_object_fetcher_${this.objectType}`, - ...(this.pinnedVersion !== undefined - ? { - version: this.pinnedVersion, - } - : {}), - }, - { headers: { "Accept-Encoding": "gzip" } }, - BTQL_HTTP_RETRIES, - ); - const respJson = await resp.json(); - const mutate = this.mutateRecord; - for (const record of respJson.data ?? []) { - if (remainingLimit !== undefined && remainingLimit <= 0) { - return; - } - const mutatedRecord = mutate - ? mutate(record) - : (record as WithTransactionId); - if (remainingLimit !== undefined) { - remainingLimit--; - } - yield mutatedRecord; - } - if (!respJson.cursor) { - break; - } - cursor = respJson.cursor; - iterations++; - if (iterations > MAX_BTQL_ITERATIONS) { - throw new Error("Too many BTQL iterations"); +function deleteHeader(carrier: TraceContextCarrier, name: string): void { + const lowered = name.toLowerCase(); + if (isMutableHeaderTupleArray(carrier)) { + for (let i = carrier.length - 1; i >= 0; i--) { + if (carrier[i][0].toLowerCase() === lowered) { + carrier.splice(i, 1); } } + return; } - /** - * Fetch all records from the object. - * - * @param options Optional parameters for fetching. - * @param options.batchSize The number of records to fetch per request. Defaults to 1000. - * @returns An async generator of records. - */ - async *fetch(options?: { - batchSize?: number; - }): AsyncGenerator> { - if (this._fetchedData !== undefined) { - for (const record of this._fetchedData) { - yield record; - } + const deleter = (carrier as { delete?: unknown }).delete; + if (typeof deleter === "function") { + try { + deleter.call(carrier, name); return; + } catch { + // Fall through to other header carrier styles. } + } - for await (const record of this.fetchRecordsFromApi(options?.batchSize)) { - yield record; + const remover = (carrier as { removeHeader?: unknown }).removeHeader; + if (typeof remover === "function") { + try { + remover.call(carrier, name); + return; + } catch { + // Fall through to plain object cleanup. } } - [Symbol.asyncIterator](): AsyncIterator> { - return this.fetch(); + const headerBag = carrier as { [name: string]: string }; + for (const key of Object.keys(headerBag)) { + if (key.toLowerCase() === lowered) { + delete headerBag[key]; + } } +} - async fetchedData(options?: { batchSize?: number }) { - if (this._fetchedData === undefined) { - const data: WithTransactionId[] = []; - for await (const record of this.fetchRecordsFromApi(options?.batchSize)) { - data.push(record); - } - this._fetchedData = data; - } - return this._fetchedData || []; +/** + * Inject W3C trace-context headers into a carrier (in place). + * + * Emits `traceparent` from the hex trace/span ids, merges `braintrust.parent` + * into existing `baggage` when known, and forwards any inbound W3C state + * (`tracestate` plus the original `traceparent` trace-flags) carried in + * `propagatedState`. Pre-existing, non-Braintrust baggage entries are preserved. + */ +export function _injectIntoCarrier( + carrier: TraceContextCarrier, + args: { + traceId: string | undefined; + spanId: string | undefined; + braintrustParent: string | undefined | null; + propagatedState?: PropagatedState | undefined; + }, +): void { + // Re-emit the inbound trace-flags verbatim so the upstream sampling decision + // (and any future flag bits) is preserved; defaults to sampled when we + // originated the trace (no inbound flags). + const traceFlags = args.propagatedState?.traceFlags; + const traceparent = traceFlags + ? formatTraceparent(args.traceId, args.spanId, traceFlags) + : formatTraceparent(args.traceId, args.spanId); + if (traceparent === undefined) { + // Ids aren't W3C-shaped (e.g. legacy UUID mode); nothing to propagate. + return; } + setHeader(carrier, TRACEPARENT_HEADER, traceparent); - clearCache() { - this._fetchedData = undefined; + // Forward upstream tracestate (per W3C, only alongside a valid traceparent). + const tracestate = args.propagatedState?.tracestate; + if (tracestate) { + setHeader(carrier, TRACESTATE_HEADER, tracestate); } - public async version(options?: { batchSize?: number }) { - if (this.pinnedVersion !== undefined) { - return this.pinnedVersion; - } else { - let maxVersion: string | undefined = undefined; - for await (const record of this.fetch(options)) { - const xactId = String(record[TRANSACTION_ID_FIELD] ?? "0"); - if (maxVersion === undefined || xactId > maxVersion) { - maxVersion = xactId; - } - } - return maxVersion; - } + // Merge braintrust.parent into any existing baggage. Other vendors' members + // are forwarded byte-for-byte (see mergeBaggage) so we never rewrite their + // percent-encoding. + const existing = getHeader(carrier, BAGGAGE_HEADER); + const baggageValue = mergeBaggage(existing, args.braintrustParent); + if (baggageValue !== undefined) { + setHeader(carrier, BAGGAGE_HEADER, baggageValue); + } else if (existing !== undefined) { + // mergeBaggage returns undefined when the existing header only had a stale + // braintrust.parent (or otherwise no members worth forwarding), so remove + // the old header instead of letting a reused carrier propagate bad routing. + // If left in place, a downstream service could attach this trace to the + // previous project/experiment, or leak that parent to an unrelated request. + deleteHeader(carrier, BAGGAGE_HEADER); } } -export type BaseMetadata = Record | void; -export type DefaultMetadataType = void; -export type EvalCaseOrigin = ObjectReference; -export type EvalCase = { - input: Input; - tags?: string[]; - // These fields are only set if the EvalCase is part of a Dataset. - id?: string; - _xact_id?: TransactionId; - created?: string | null; - origin?: EvalCaseOrigin; - // This field is used to help re-run a particular experiment row. - upsert_id?: string; - // The number of times to run the evaluator for this specific input. - trialCount?: number; -} & (Expected extends void ? object : { expected: Expected }) & - (Metadata extends void ? object : { metadata: Metadata }); +/** + * Inject W3C trace-context headers for the current (or given) span into a + * carrier. + * + * This is the free-function form of {@link Span.inject}, and the send-side + * counterpart of {@link extractTraceContextFromHeaders}. If no span is + * provided, the currently-active span is used. Propagation is best-effort and + * never throws. + * + * @param carrier Optional carrier (e.g. outbound HTTP headers) to mutate. + * Supports plain objects, Web `Headers`, Node-style `setHeader` carriers, + * framework `header` carriers, and mutable `HeadersInit` tuple arrays. + * @param options.span Optional span to inject. Defaults to the current span. + * @returns The carrier with propagation headers injected. + */ +export function injectTraceContext( + carrier?: undefined, + options?: OptionalStateArg & { span?: Span }, +): Record; + +export function injectTraceContext( + carrier: T, + options?: OptionalStateArg & { span?: Span }, +): T; + +export function injectTraceContext( + carrier?: T, + options?: OptionalStateArg & { span?: Span }, +): T | Record { + const resolvedCarrier = carrier ?? {}; + const span = options?.span ?? currentSpan({ state: options?.state }); + try { + return span.inject(resolvedCarrier); + } catch (e) { + debugLogger.warn(`Error injecting trace context: ${e}`); + return resolvedCarrier; + } +} /** - * An experiment is a collection of logged events, such as model inputs and outputs, which represent - * a snapshot of your application at a particular point in time. An experiment is meant to capture more - * than just the model you use, and includes the data you use to test, pre- and post- processing code, - * comparison metrics (scores), and any other metadata you want to include. + * Extract an opaque W3C trace-context from inbound request headers. + * + * This is the receive-side counterpart of {@link Span.inject} / + * {@link injectTraceContext}. The return value is an opaque propagation context + * that can be passed as `parent` to `startSpan`: * - * Experiments are associated with a project, and two experiments are meant to be easily comparable via - * their `inputs`. You can change the attributes of the experiments in a project (e.g. scoring functions) - * over time, simply by changing what you log. + * ```ts + * const ctx = extractTraceContextFromHeaders(request.headers); + * traced((span) => { ... }, { name: "handler", parent: ctx }); + * ``` + * + * Only the W3C Trace Context headers are interpreted (`traceparent`, `baggage`, + * `tracestate`); header lookups are case-insensitive. If no valid `traceparent` + * is present, returns undefined (the caller starts a fresh root span). The + * Braintrust container the trace is routed under is resolved when the span is + * created: from the `braintrust.parent` baggage entry, or else the + * currently-active logger/experiment. + * + * Callers should treat the return value as opaque. * - * You should not create `Experiment` objects directly. Instead, use the `braintrust.init()` method. + * @param headers Inbound request headers (e.g. an HTTP framework's headers). + * @returns An opaque context for `startSpan({ parent })`, or undefined. */ -export class Experiment - extends ObjectFetcher - implements Exportable -{ - private readonly lazyMetadata: LazyValue; - public readonly dataset?: AnyDataset; - private lastStartTime: number; - private lazyId: LazyValue; - private calledStartSpan: boolean; - private state: BraintrustState; - - // For type identification. - public kind = "experiment" as const; - - constructor( - state: BraintrustState, - lazyMetadata: LazyValue, - dataset?: AnyDataset, - ) { - super("experiment", undefined, (r) => enrichAttachments(r, state)); - this.lazyMetadata = lazyMetadata; - this.dataset = dataset; - this.lastStartTime = getCurrentUnixTimestamp(); - this.lazyId = new LazyValue(async () => await this.id); - this.calledStartSpan = false; - this.state = state; - } - - public get id(): Promise { - return (async () => { - return (await this.lazyMetadata.get()).experiment.id; - })(); - } - - public get loggingState(): BraintrustState { - return this.state; +export function extractTraceContextFromHeaders( + headers: TraceContextHeaders | null | undefined, +): PropagationContext | undefined { + if (!headers) { + return undefined; } - /** - * Wait for the experiment ID to be resolved. This is useful for ensuring the ID - * is available synchronously in child spans (for OTEL parent attributes). - * @internal - */ - public async _waitForId(): Promise { - await this.lazyId.get().catch(() => { - // Ignore errors - }); + const traceparent = getHeader(headers, TRACEPARENT_HEADER); + if (!traceparent || parseTraceparent(traceparent) === undefined) { + return undefined; } - public get name(): Promise { - return (async () => { - return (await this.lazyMetadata.get()).experiment.name; - })(); + const context: PropagationContext = { [TRACEPARENT_HEADER]: traceparent }; + const baggageValue = getHeader(headers, BAGGAGE_HEADER); + if (baggageValue) { + context[BAGGAGE_HEADER] = baggageValue; } - - public get project(): Promise { - return (async () => { - return (await this.lazyMetadata.get()).project; - })(); + const tracestate = getHeader(headers, TRACESTATE_HEADER); + if (tracestate) { + // `tracestate` is opaque vendor state that we forward but never author or + // interpret. It is passed along unchanged; receivers that want to use it + // validate it themselves, per the W3C Trace Context spec. + context[TRACESTATE_HEADER] = tracestate; } + return context; +} - public async _getBaseExperimentId(): Promise { - const baseExperimentId = (await this.lazyMetadata.get()).experiment - .fullInfo["base_exp_id"]; - return typeof baseExperimentId === "string" && baseExperimentId - ? baseExperimentId - : undefined; +/** + * Resolve a W3C trace-context into `{ parentSlug, propagatedState }`. + * + * Reads `traceparent` for trace identity and `braintrust.parent` from `baggage` + * (falling back to the currently-active logger/experiment) for routing, and + * builds an internal Braintrust parent slug. Captures the `tracestate` and raw + * `traceparent` flags to forward onward. Returns `{ undefined, undefined }` if + * the context cannot be resolved into a usable parent (so the caller falls back + * to local precedence / a fresh root). + */ +function resolveW3cParent( + context: PropagationContext, + state?: BraintrustState, +): { + parentSlug: string | undefined; + propagatedState: PropagatedState | undefined; +} { + const traceparent = getHeader(context, TRACEPARENT_HEADER); + const parsed = traceparent ? parseTraceparent(traceparent) : undefined; + if (parsed === undefined) { + return { parentSlug: undefined, propagatedState: undefined }; } + const { traceId, spanId, traceFlags } = parsed; - private parentObjectType() { - return SpanObjectTypeV3.EXPERIMENT; + // Determine the Braintrust container: baggage -> current logger/experiment. + let braintrustParent: string | undefined = undefined; + const baggageValue = getHeader(context, BAGGAGE_HEADER); + if (baggageValue) { + braintrustParent = parseBaggage(baggageValue)[BRAINTRUST_PARENT_KEY]; } - - protected async getState(): Promise { - // Ensure the login state is populated by awaiting lazyMetadata. - await this.lazyMetadata.get(); - return this.state; + if (!braintrustParent) { + braintrustParent = currentBraintrustParent(state); } - - /** - * Log a single event to the experiment. The event will be batched and uploaded behind the scenes. - * - * @param event The event to log. - * @param event.input: The arguments that uniquely define a test case (an arbitrary, JSON serializable object). Later on, Braintrust will use the `input` to know whether two test cases are the same between experiments, so they should not contain experiment-specific state. A simple rule of thumb is that if you run the same experiment twice, the `input` should be identical. - * @param event.output: The output of your application, including post-processing (an arbitrary, JSON serializable object), that allows you to determine whether the result is correct or not. For example, in an app that generates SQL queries, the `output` should be the _result_ of the SQL query generated by the model, not the query itself, because there may be multiple valid queries that answer a single question. - * @param event.expected: (Optional) The ground truth value (an arbitrary, JSON serializable object) that you'd compare to `output` to determine if your `output` value is correct or not. Braintrust currently does not compare `output` to `expected` for you, since there are so many different ways to do that correctly. Instead, these values are just used to help you navigate your experiments while digging into analyses. However, we may later use these values to re-score outputs or fine-tune your models. - * @param event.error: (Optional) The error that occurred, if any. If you use tracing to run an experiment, errors are automatically logged when your code throws an exception. - * @param event.scores: A dictionary of numeric values (between 0 and 1) to log. The scores should give you a variety of signals that help you determine how accurate the outputs are compared to what you expect and diagnose failures. For example, a summarization app might have one score that tells you how accurate the summary is, and another that measures the word similarity between the generated and grouth truth summary. The word similarity score could help you determine whether the summarization was covering similar concepts or not. You can use these scores to help you sort, filter, and compare experiments. - * @param event.metadata: (Optional) a dictionary with additional data about the test example, model outputs, or just about anything else that's relevant, that you can use to help find and analyze examples later. For example, you could log the `prompt`, example's `id`, or anything else that would be useful to slice/dice later. The values in `metadata` can be any JSON-serializable type, but its keys must be strings. - * @param event.metrics: (Optional) a dictionary of metrics to log. The following keys are populated automatically: "start", "end". - * @param event.id: (Optional) a unique identifier for the event. If you don't provide one, BrainTrust will generate one for you. - * @param event.dataset_record_id: (Optional) the id of the dataset record that this event is associated with. This field is required if and only if the experiment is associated with a dataset. This field is unused and will be removed in a future version. - * @param options Additional logging options - * @param options.allowConcurrentWithSpans in rare cases where you need to log at the top level separately from spans on the experiment elsewhere, set this to true. - * @returns The `id` of the logged event. - */ - public log( - event: Readonly, - options?: { allowConcurrentWithSpans?: boolean }, - ): string { - if (this.calledStartSpan && !options?.allowConcurrentWithSpans) { - throw new Error( - "Cannot run toplevel `log` method while using spans. To log to the span, call `experiment.traced` and then log with `span.log`", - ); - } - - event = validateAndSanitizeExperimentLogFullArgs(event, !!this.dataset); - const span = this.startSpanImpl({ startTime: this.lastStartTime, event }); - this.lastStartTime = span.end(); - return span.id; + if (!braintrustParent) { + debugLogger.warn( + "Received traceparent without a braintrust.parent and no active logger/experiment; " + + "cannot route the trace. Starting a fresh local span instead.", + ); + return { parentSlug: undefined, propagatedState: undefined }; } - /** - * Create a new toplevel span underneath the experiment. The name defaults to "root". - * - * See {@link Span.traced} for full details. - */ - public traced( - callback: (span: Span) => R, - args?: StartSpanArgs & SetCurrentArg, - ): R { - const { setCurrent, ...argsRest } = args ?? {}; - const span = this.startSpan(argsRest); - - const ret = runCatchFinally( - () => { - if (setCurrent ?? true) { - return withCurrent(span, callback); - } else { - return callback(span); - } - }, - (e) => { - logError(span, e); - throw e; - }, - () => span.end(), + const parsedParent = braintrustParentToComponents(braintrustParent); + if (parsedParent === undefined) { + debugLogger.warn( + `Invalid braintrust.parent: ${JSON.stringify(braintrustParent)}`, ); - - return ret as R; + return { parentSlug: undefined, propagatedState: undefined }; } + const { objectType, objectId, computeArgs } = parsedParent; - /** - * Lower-level alternative to `traced`. This allows you to start a span yourself, and can be useful in situations - * where you cannot use callbacks. However, spans started with `startSpan` will not be marked as the "current span", - * so `currentSpan()` and `traced()` will be no-ops. If you want to mark a span as current, use `traced` instead. - * - * See {@link traced} for full details. - */ - public startSpan(args?: StartSpanArgs): Span { - this.calledStartSpan = true; - return this.startSpanImpl(args); - } + // `tracestate` is opaque vendor state, forwarded unchanged and never + // authored or interpreted by Braintrust. + const tracestate = getHeader(context, TRACESTATE_HEADER); - private startSpanImpl(args?: StartSpanArgs): Span { - return new SpanImpl({ - ...args, - // Sometimes `args` gets passed directly into this function, and it contains an undefined value for `state`. - // To ensure that we always use this experiment's state, we override the `state` argument no matter what. - state: this.state, - ...startSpanParentArgs({ - state: this.state, - parent: args?.parent, - parentObjectType: this.parentObjectType(), - parentObjectId: this.lazyId, - parentComputeObjectMetadataArgs: undefined, - parentSpanIds: undefined, - propagatedEvent: args?.propagatedEvent, - }), - defaultRootType: SpanTypeAttribute.EVAL, - }); - } + const slug = new SpanComponentsV4({ + object_type: objectType, + ...(computeArgs + ? { compute_object_metadata_args: computeArgs } + : { object_id: objectId }), + row_id: "bt-propagation", // non-empty to enable span_id/root_span_id + span_id: spanId, + root_span_id: traceId, + // eslint-disable-next-line @typescript-eslint/consistent-type-assertions + } as SpanComponentsV4Data).toStr(); + return { + parentSlug: slug, + propagatedState: { tracestate, traceFlags, braintrustParent }, + }; +} - public async fetchBaseExperiment() { - const state = await this.getState(); - const conn = state.appConn(); +/** + * Normalize a `parent` argument into `{ parentSlug, propagatedState }`. + * + * - object -> interpreted as a W3C trace-context (from + * `extractTraceContextFromHeaders`) + * - string -> an exported span slug (passed through unchanged) + * - undefined -> no parent + * + * `propagatedState` carries any inbound W3C state (tracestate + raw flags) to + * forward on the next `inject`; it is undefined when there is no inbound W3C + * context. + */ +function normalizeParent( + parent: string | PropagationContext | undefined | null, + state?: BraintrustState, +): { + parentSlug: string | undefined; + propagatedState: PropagatedState | undefined; +} { + if (parent && typeof parent === "object") { + return resolveW3cParent(parent, state); + } + return { + parentSlug: parent ?? undefined, + propagatedState: undefined, + }; +} - try { - const resp = await conn.post("/api/base_experiment/get_id", { - id: await this.id, - }); +/** + * Return true if a parent slug carries span ids the child can link to. + * + * A child links to any parent slug that carries both a span id and a root span + * id, regardless of whether those ids are hex (the default) or legacy UUID. This + * keeps the deprecated `startSpan({ parent: })` path backwards + * compatible: a slug exported by an older (UUID) sender still links into a trace + * on a newer (hex) receiver, and vice versa. The child's own freshly generated + * span id stays in the active format; the backend supports traces whose nodes + * mix id formats across a propagation boundary. Empty ids (no row) are handled + * by the caller. + */ +function parentSpanIdsUsable( + spanId: string | undefined | null, + rootSpanId: string | undefined | null, +): boolean { + return Boolean(spanId) && Boolean(rootSpanId); +} - const base = await resp.json(); - return { - id: base["base_exp_id"], - name: base["base_exp_name"], - }; - } catch (e) { - if (e instanceof FailedHTTPResponse && e.status === 400) { - // No base experiment - return null; - } else { - throw e; - } - } +export function logError(span: Span, error: unknown): void { + let errorMessage = ""; + let stackTrace = ""; + if (error instanceof Error) { + errorMessage = error.message; + stackTrace = error.stack || ""; + } else { + errorMessage = String(error); } + span.log({ error: `${errorMessage}\n\n${stackTrace}` }); +} - /** - * Summarize the experiment, including the scores (compared to the closest reference experiment) and metadata. - * - * @param options Options for summarizing the experiment. - * @param options.summarizeScores Whether to summarize the scores. If False, only the metadata will be returned. - * @param options.comparisonExperimentId The experiment to compare against. If None, the most recent experiment on the origin's main branch will be used. - * @returns A summary of the experiment, including the scores (compared to the closest reference experiment) and metadata. - */ - public async summarize( - options: { - readonly summarizeScores?: boolean; - readonly comparisonExperimentId?: string; - } = {}, - ): Promise { - const { - summarizeScores = true, - comparisonExperimentId: comparisonExperimentIdOpt, - } = options || {}; - let comparisonExperimentId = comparisonExperimentIdOpt; - - const state = await this.getState(); - const projectUrl = `${state.appPublicUrl}/app/${encodeURIComponent( - state.orgName!, - )}/p/${encodeURIComponent((await this.project).name)}`; - const experimentUrl = `${projectUrl}/experiments/${encodeURIComponent( - await this.name, - )}`; - - let scores: Record | undefined = undefined; - let metrics: Record | undefined = undefined; - let comparisonExperimentName = undefined; - if (summarizeScores) { - await this.flush(); - if (comparisonExperimentId === undefined) { - const baseExperiment = await this.fetchBaseExperiment(); - if (baseExperiment !== null) { - comparisonExperimentId = baseExperiment.id; - comparisonExperimentName = baseExperiment.name; - } +/** + * Toplevel function for starting a span. It checks the following (in precedence order): + * * Currently-active span + * * Currently-active experiment + * * Currently-active logger + * + * and creates a span under the first one that is active. Alternatively, if `parent` is specified, it creates a span under the specified parent row. If none of these are active, it returns a no-op span object. + * + * See {@link Span.traced} for full details. + */ +export function traced( + callback: (span: Span) => R, + args?: StartSpanArgs & SetCurrentArg & OptionalStateArg, +): R { + const { setCurrent, ...spanArgs } = args ?? {}; + const span = startSpanImpl(spanArgs); + + return runCatchFinally( + () => { + if (setCurrent ?? true) { + return withCurrent(span, callback); } else { - try { - const comparisonExperiment = await state - .apiConn() - .get_json(`v1/experiment/${comparisonExperimentId}`); - if (typeof comparisonExperiment["name"] === "string") { - comparisonExperimentName = comparisonExperiment["name"]; - } - } catch { - // If the explicit comparison name lookup fails, still summarize by ID. - } + return callback(span); } + }, + (error) => { + logError(span, error); + throw error; + }, + () => span.end(), + ); +} - try { - const results = await state.apiConn().get_json( - "/experiment-comparison2", - { - experiment_id: await this.id, - base_experiment_id: comparisonExperimentId, - }, - 3, - ); - - scores = results["scores"]; - metrics = results["metrics"]; - } catch (e) { - debugLogger - .forState(state) - .warn( - `Failed to fetch experiment scores and metrics: ${e}\n\nView complete results in Braintrust or run experiment.summarize() again.`, - ); - scores = {}; - metrics = {}; - } - } +type WrapTracedArgs = { + noTraceIO?: boolean; +}; - return { - projectName: (await this.project).name, - experimentName: await this.name, - projectId: (await this.project).id, - experimentId: await this.id, - projectUrl: projectUrl, - experimentUrl: experimentUrl, - comparisonExperimentName: comparisonExperimentName, - scores: scores ?? {}, - metrics: metrics ?? {}, - }; - } +/** + * Check if a function is a sync generator function. + * + * Note: This uses Object.prototype.toString which is sufficient for environments that + * support generator functions (ES6+). While our code is compiled to ES2022, consumers + * may run it in various environments. However, if generators aren't supported in their + * environment, the generator functions themselves won't work anyway, making detection moot. + * + * @param fn The function to check. + * @returns True if the function is a sync generator function. + */ +export function isGeneratorFunction(fn: unknown): boolean { + return Object.prototype.toString.call(fn) === "[object GeneratorFunction]"; +} - /** - * Log feedback to an event in the experiment. Feedback is used to save feedback scores, set an expected value, or add a comment. - * - * @param event - * @param event.id The id of the event to log feedback for. This is the `id` returned by `log` or accessible as the `id` field of a span. - * @param event.scores (Optional) a dictionary of numeric values (between 0 and 1) to log. These scores will be merged into the existing scores for the event. - * @param event.expected (Optional) the ground truth value (an arbitrary, JSON serializable object) that you'd compare to `output` to determine if your `output` value is correct or not. - * @param event.comment (Optional) an optional comment string to log about the event. - * @param event.metadata (Optional) a dictionary with additional data about the feedback. If you have a `user_id`, you can log it here and access it in the Braintrust UI. Note, this metadata does not correspond to the main event itself, but rather the audit log attached to the event. - * @param event.source (Optional) the source of the feedback. Must be one of "external" (default), "app", or "api". - */ - public logFeedback(event: LogFeedbackFullArgs): void { - logFeedbackImpl(this.state, this.parentObjectType(), this.lazyId, event); - } +/** + * Check if a function is an async generator function. + * + * Note: see isGeneratorFunction disclaimer + * @param fn The function to check. + * @returns True if the function is an async generator function. + */ +export function isAsyncGeneratorFunction(fn: unknown): boolean { + return ( + Object.prototype.toString.call(fn) === "[object AsyncGeneratorFunction]" + ); +} - /** - * Update a span in the experiment using its id. It is important that you only update a span once the original span has been fully written and flushed, - * since otherwise updates to the span may conflict with the original span. - * - * @param event The event data to update the span with. Must include `id`. See {@link Experiment.log} for a full list of valid fields. - */ - public updateSpan( - event: Omit, "id"> & - Required>, - ): void { - const { id, root_span_id, span_id, ...eventRest } = event; - if (!id) { - throw new Error("Span id is required to update a span"); - } - updateSpanImpl({ - state: this.state, - parentObjectType: this.parentObjectType(), - parentObjectId: this.lazyId, - id, - root_span_id, - span_id, - event: eventRest, - }); - } +/** + * Wrap a sync generator function with tracing. + */ +function wrapTracedSyncGenerator any>( + fn: F, + spanArgs: StartSpanArgs & SetCurrentArg, + noTraceIO: boolean, +): F { + const wrapper = function* ( + this: ThisParameterType, + ...fnArgs: Parameters + ) { + const span = startSpan(spanArgs); + try { + if (!noTraceIO) { + span.log({ input: fnArgs }); + } - /** - * Return a serialized representation of the experiment that can be used to start subspans in other places. - * - * See {@link Span.startSpan} for more details. - */ - public async export(): Promise { - return new (getSpanComponentsClass())({ - object_type: this.parentObjectType(), - object_id: await this.id, - }).toStr(); - } + const maxItems = getNumericEnv("BRAINTRUST_MAX_GENERATOR_ITEMS") ?? 1000; - /** - * Return this experiment's Braintrust parent string (`experiment_id:`) for - * the `braintrust.parent` baggage entry, or undefined when it cannot be - * determined synchronously. - */ - public _getOtelParent(): string | undefined { - const id = this.lazyId.getSync().value; - return id ? `experiment_id:${id}` : undefined; - } + if (!noTraceIO && maxItems !== 0) { + let collected: unknown[] = []; + let truncated = false; - /** - * Flush any pending rows to the server. - */ - async flush(): Promise { - return await this.state.bgLogger().flush(); - } + const gen = generatorWithCurrent(span, fn.apply(this, fnArgs)); + try { + for (const value of gen) { + if ( + maxItems === -1 || + (!truncated && collected.length < maxItems) + ) { + collected.push(value); + } else { + truncated = true; + collected = []; + debugLogger.warn( + `Generator output exceeded limit of ${maxItems} items, output not logged. ` + + `Increase BRAINTRUST_MAX_GENERATOR_ITEMS or set to -1 to disable limit.`, + ); + } + yield value; + } - /** - * @deprecated This function is deprecated. You can simply remove it from your code. - */ - public async close(): Promise { - debugLogger - .forState(this.state) - .warn( - "close is deprecated and will be removed in a future version of braintrust. It is now a no-op and can be removed", - ); - return this.id; - } + if (!truncated) { + span.log({ output: collected }); + } + } catch (error) { + logError(span, error); + if (!truncated && collected.length > 0) { + span.log({ output: collected }); + } + throw error; + } + } else { + // No output collection + const gen = generatorWithCurrent(span, fn.apply(this, fnArgs)); + for (const value of gen) { + yield value; + } + } + } finally { + span.end(); + } + }; + // Preserve the original function name + Object.defineProperty(wrapper, "name", { value: fn.name }); + return wrapper as F; } /** - * A read-only view of an experiment, initialized by passing `open: true` to `init()`. + * Wrap an async generator function with tracing. */ -export class ReadonlyExperiment extends ObjectFetcher { - constructor( - private state: BraintrustState, - private readonly lazyMetadata: LazyValue, +function wrapTracedAsyncGenerator any>( + fn: F, + spanArgs: StartSpanArgs & SetCurrentArg, + noTraceIO: boolean, +): F { + const wrapper = async function* ( + this: ThisParameterType, + ...fnArgs: Parameters ) { - super("experiment", undefined, (r) => enrichAttachments(r, state)); - } - - public get id(): Promise { - return (async () => { - return (await this.lazyMetadata.get()).experiment.id; - })(); - } - - public get name(): Promise { - return (async () => { - return (await this.lazyMetadata.get()).experiment.name; - })(); - } + const span = startSpan(spanArgs); + try { + if (!noTraceIO) { + span.log({ input: fnArgs }); + } - public get loggingState(): BraintrustState { - return this.state; - } + const maxItems = getNumericEnv("BRAINTRUST_MAX_GENERATOR_ITEMS") ?? 1000; - protected async getState(): Promise { - // Ensure the login state is populated by awaiting lazyMetadata. - await this.lazyMetadata.get(); - return this.state; - } + if (!noTraceIO && maxItems !== 0) { + let collected: unknown[] = []; + let truncated = false; - public async *asDataset< - Input, - Expected, - Metadata = DefaultMetadataType, - >(options?: { - batchSize?: number; - }): AsyncGenerator> { - const records = this.fetch(options); + const gen = asyncGeneratorWithCurrent(span, fn.apply(this, fnArgs)); + try { + for await (const value of gen) { + if ( + maxItems === -1 || + (!truncated && collected.length < maxItems) + ) { + collected.push(value); + } else { + truncated = true; + collected = []; + debugLogger.warn( + `Generator output exceeded limit of ${maxItems} items, output not logged. ` + + `Increase BRAINTRUST_MAX_GENERATOR_ITEMS or set to -1 to disable limit.`, + ); + } + yield value; + } - for await (const record of records) { - const isRoot = record.is_root ?? record.root_span_id === record.span_id; - if (!isRoot) { - continue; + if (!truncated) { + span.log({ output: collected }); + } + } catch (error) { + logError(span, error); + if (!truncated && collected.length > 0) { + span.log({ output: collected }); + } + throw error; + } + } else { + // No output collection + const gen = asyncGeneratorWithCurrent(span, fn.apply(this, fnArgs)); + for await (const value of gen) { + yield value; + } } - - const { output, expected: expectedRecord, metadata } = record; - const expected = (expectedRecord ?? output) as Expected; - - // Note: We always include expected and metadata fields to maintain type signature alignment. - // This ensures that when the type signature includes `| null | undefined`, the fields - // are still present in the runtime object. While this may incorrectly include fields - // when Metadata/Expected = void, it's preferable to incorrectly excluding them when - // the type signature expects them to be present. - yield { - input: record.input as Input, - tags: record.tags, - expected: expected as Expected, - metadata: metadata as Metadata, - } as unknown as EvalCase; + } finally { + span.end(); } - } -} - -let executionCounter = 0; - -export function newId() { - return uuidv4(); -} - -interface ResolvedSpanIds { - spanId: string; - rootSpanId: string; - spanParents: string[] | undefined; + }; + // Preserve the original function name + Object.defineProperty(wrapper, "name", { value: fn.name }); + return wrapper as F; } /** - * Resolve all span IDs (span_id, root_span_id, span_parents) from explicit values, parent info, or context. + * Wrap a function with `traced`, using the arguments as `input` and return value as `output`. + * Any functions wrapped this way will automatically be traced, similar to the `@traced` decorator + * in Python. If you want to correctly propagate the function's name and define it in one go, then + * you can do so like this: * - * @param spanId - Optional explicit span_id (from public API) - * @param parentSpanIds - Optional explicit parent span IDs (from parent string or parent span) - * @param lookupSpanParent - Whether to look up parent from context manager if no explicit parent. - * - True (default): start_span() inherits parent/root ids from the active span (if it exists) - * - False: don't look up parent in context (e.g. logger.log() doesn't inherit context) - * @param idGenerator - ID generator for creating new span/trace IDs - * @param contextManager - Context manager for looking up parent spans - * @returns ResolvedSpanIds with resolved span_id, root_span_id, and span_parents + * ```ts + * const myFunc = wrapTraced(async function myFunc(input) { + * const result = await client.chat.completions.create({ + * model: "gpt-3.5-turbo", + * messages: [{ role: "user", content: input }], + * }); + * return result.choices[0].message.content ?? "unknown"; + * }, + * // Optional: if you're using a framework like NextJS that minifies your code, specify the function name and it will be used for the span name + * { name: "myFunc" }, + * ); + * ``` + * Now, any calls to `myFunc` will be traced, and the input and output will be logged automatically. + * If tracing is inactive, i.e. there is no active logger or experiment, it's just a no-op. + * + * @param fn The function to wrap. + * @param args Span-level arguments (e.g. a custom name or type) to pass to `traced`. + * @returns The wrapped function. */ -function _resolveSpanIds( - spanId: string | undefined, - parentSpanIds: ParentSpanIds | MultiParentSpanIds | undefined, - lookupSpanParent: boolean, - idGenerator: IDGenerator, - contextManager: ContextManager, -): ResolvedSpanIds { - // Generate span_id if not provided - const resolvedSpanId = spanId ?? idGenerator.getSpanId(); +export function wrapTraced any>( + fn: F, + args?: StartSpanArgs & SetCurrentArg & WrapTracedArgs, +): F { + const { noTraceIO, ...argsRest } = args ?? {}; + const spanArgs: StartSpanArgs & SetCurrentArg = { + name: fn.name, + type: "function", + ...argsRest, + }; + const hasExplicitInput = spanArgs.event?.input !== undefined; + const hasExplicitOutput = spanArgs.event?.output !== undefined; - // If we have explicit parent span ids, use them. - if (parentSpanIds) { - return { - spanId: resolvedSpanId, - rootSpanId: parentSpanIds.rootSpanId, - spanParents: - "parentSpanIds" in parentSpanIds - ? parentSpanIds.parentSpanIds - : [parentSpanIds.spanId], - }; + const disableGeneratorTraceIO = + !!noTraceIO || hasExplicitInput || hasExplicitOutput; + // Check if the function is a generator + if (isGeneratorFunction(fn)) { + return wrapTracedSyncGenerator(fn, spanArgs, disableGeneratorTraceIO); } - // If we're using the context manager, see if there's an active parent span. - if (lookupSpanParent) { - const parentInfo = contextManager.getParentSpanIds(); - if (parentInfo) { - return { - spanId: resolvedSpanId, - rootSpanId: parentInfo.rootSpanId, - spanParents: parentInfo.spanParents, - }; - } + if (isAsyncGeneratorFunction(fn)) { + return wrapTracedAsyncGenerator(fn, spanArgs, disableGeneratorTraceIO); } - // No parent - create new root span - // Root span ID behavior differs between UUID and OTEL generators: - // - UUID (legacy): root_span_id === span_id for backwards compatibility - // - OTEL: root_span_id is a separate trace ID, following OpenTelemetry convention - // where trace_id (root_span_id) represents the entire trace, distinct from - // the individual span's ID - let resolvedRootSpanId: string; - if (idGenerator.shareRootSpanId()) { - resolvedRootSpanId = resolvedSpanId; // Backwards compat for UUID mode - } else { - resolvedRootSpanId = idGenerator.getTraceId(); - } + return ((...fnArgs: Parameters) => + traced((span) => { + if (!noTraceIO && !hasExplicitInput) { + span.log({ input: fnArgs }); + } - return { - spanId: resolvedSpanId, - rootSpanId: resolvedRootSpanId, - spanParents: undefined, - }; + const output = fn(...fnArgs); + + if (!noTraceIO && !hasExplicitOutput) { + if (output instanceof Promise) { + return (async () => { + const result = await output; + span.log({ output: result }); + return result; + })(); + } else { + span.log({ output }); + } + } + + return output; + }, spanArgs)) as F; } /** - * Primary implementation of the `Span` interface. See {@link Span} for full details on each method. + * Lower-level alternative to `traced`. This allows you to start a span yourself, and can be useful in situations + * where you cannot use callbacks. However, spans started with `startSpan` will not be marked as the "current span", + * so `currentSpan()` and `traced()` will be no-ops. If you want to mark a span as current, use `traced` instead. * - * We suggest using one of the various `traced` methods, instead of creating Spans directly. See {@link Span.startSpan} for full details. + * See {@link traced} for full details. */ -export class SpanImpl implements Span { - private _state: BraintrustState; - - private isMerge: boolean; - private loggedEndTime: number | undefined; - private propagatedEvent: StartSpanEventArgs | undefined; - - // For internal use only. - private parentObjectType: SpanObjectTypeV3; - private parentObjectId: LazyValue; - private parentComputeObjectMetadataArgs: Record | undefined; - - private _id: string; - private _spanId: string; - private _rootSpanId: string; - private _spanParents: string[] | undefined; - - // Inbound W3C trace-context state (tracestate + raw traceparent flags) to - // forward on outbound propagation. Captured at the span that received it (via - // extractTraceContextFromHeaders) and inherited by all subspans, so that any - // inject() within the trace re-emits the upstream state unchanged, per the W3C - // Trace Context spec. Not interpreted. - private _propagatedState: PropagatedState | undefined; - - public kind = "span" as const; - - constructor( - args: { - state: BraintrustState; - parentObjectType: SpanObjectTypeV3; - parentObjectId: LazyValue; - parentComputeObjectMetadataArgs: Record | undefined; - parentSpanIds: ParentSpanIds | MultiParentSpanIds | undefined; - defaultRootType?: SpanType; - spanId?: string; - propagatedState?: PropagatedState | undefined; - } & Omit & - InitialSpanWriteAsMergeArg & - InternalSpanContextArg, - ) { - this._state = args.state; - this._propagatedState = args.propagatedState; - const instrumentationName = - getSpanInstrumentationName(args) ?? - INSTRUMENTATION_NAMES.BRAINTRUST_JS_LOGGER; - - const spanAttributes = args.spanAttributes ?? {}; - const rawEvent = args.event ?? {}; - const type = - args.type ?? (args.parentSpanIds ? undefined : args.defaultRootType); - - this.loggedEndTime = undefined; - this.parentObjectType = args.parentObjectType; - this.parentObjectId = args.parentObjectId; - this.parentComputeObjectMetadataArgs = args.parentComputeObjectMetadataArgs; - - // Merge propagatedEvent into event. The propagatedEvent data will get - // propagated-and-merged into every subspan. - this.propagatedEvent = args.propagatedEvent; - if (this.propagatedEvent) { - mergeDicts(rawEvent, this.propagatedEvent); - } - const { id: eventId, ...event } = rawEvent; - - const callerLocation = iso.getCallerLocation(); - const name = (() => { - if (args.name) return args.name; - if (!args.parentSpanIds) return "root"; - if (callerLocation) { - const pathComponents = callerLocation.caller_filename.split("/"); - const filename = pathComponents[pathComponents.length - 1]; - return [callerLocation.caller_functionname] - .concat( - filename ? [`${filename}:${callerLocation.caller_lineno}`] : [], - ) - .join(":"); - } - return "subspan"; - })(); +export function startSpan(args?: StartSpanArgs & OptionalStateArg): Span { + return startSpanImpl(args); +} - const internalData = { - metrics: { - start: args.startTime ?? getCurrentUnixTimestamp(), - }, - context: mergeSpanOriginContext( - { ...callerLocation, ...args[INTERNAL_SPAN_CONTEXT] }, - instrumentationName, - this._state.spanOriginEnvironment, - ), - span_attributes: { - name, - type, - ...spanAttributes, - exec_counter: executionCounter++, - }, - created: new Date().toISOString(), - }; +export function _internalStartSpan( + args?: InternalStartSpanArgs & OptionalStateArg, +): Span { + return startSpanImpl(args); +} - this._id = eventId ?? this._state.idGenerator.getSpanId(); +/** @internal Start a span whose initial row is merged with concurrent writes. */ +export function _internalStartSpanWithInitialMerge( + args?: InternalStartSpanArgs & OptionalStateArg, +): Span { + return startSpanImpl({ + ...args, + [INITIAL_SPAN_WRITE_AS_MERGE]: true, + } as InternalStartSpanArgs & OptionalStateArg & InitialSpanWriteAsMergeArg); +} - // Resolve span IDs using the helper function that matches Python's _resolve_span_ids - const resolvedIds = _resolveSpanIds( - args.spanId, - args.parentSpanIds, - true, // Always lookup span parent from context manager unless explicit parent provided - this._state.idGenerator, - this._state.contextManager, - ); +/** @internal Start a deterministic span under an exported object-only parent. */ +export function _internalStartSpanWithInitialMergeAndParentSpanIds( + args: InternalStartSpanArgs & OptionalStateArg, +): Span { + return startSpanImpl( + { + ...args, + [INITIAL_SPAN_WRITE_AS_MERGE]: true, + } as InternalStartSpanArgs & OptionalStateArg & InitialSpanWriteAsMergeArg, + { useParentSpanIdsForObjectParent: true }, + ); +} - this._spanId = resolvedIds.spanId; - this._rootSpanId = resolvedIds.rootSpanId; - this._spanParents = resolvedIds.spanParents; +/** @internal Start a span with SDK-controlled context fields. */ +export function _internalStartSpanWithContext( + args: InternalStartSpanArgs & OptionalStateArg, + context: Record, +): Span { + return startSpanImpl({ + ...args, + [INTERNAL_SPAN_CONTEXT]: context, + }); +} - // Deterministic spans can be initialized concurrently by separate - // workflow executions, so their first write must not replace later merges. - this.isMerge = args[INITIAL_SPAN_WRITE_AS_MERGE] === true; - if (!args[RESUME_SPAN_WITHOUT_INITIAL_WRITE]) { - this.logInternal({ event, internalData }); - } - this.isMerge = true; - } +/** + * Flush any pending rows to the server. + */ +export async function flush(options?: OptionalStateArg): Promise { + const state = + options?.state ?? + _globalState ?? + ( + globalThis as typeof globalThis & { + [key: symbol]: BraintrustState | undefined; + } + )[Symbol.for("braintrust-state")]; + await state?.bgLogger().flush(); +} - public getParentInfo() { - return { - objectType: this.parentObjectType, - objectId: this.parentObjectId, - computeObjectMetadataArgs: this.parentComputeObjectMetadataArgs && { - ...this.parentComputeObjectMetadataArgs, - }, - }; - } +/** + * Set the fetch implementation to use for requests. You can specify it here, + * or when you call `login`. + * + * @param fetch The fetch implementation to use. + */ +export function setFetch(fetch: typeof globalThis.fetch): void { + _internalGetGlobalState().setFetch(fetch); +} - public get id(): string { - return this._id; - } +function startSpanImpl( + args?: InternalStartSpanArgs & OptionalStateArg & InternalSpanContextArg, + internalOptions?: { useParentSpanIdsForObjectParent?: boolean }, +): Span { + const state = args?.state ?? _internalGetGlobalState(); - public get spanId(): string { - return this._spanId; - } + // Resolve the parent object and any forwarded W3C state in one pass, so we + // don't re-normalize `parent` (which could disagree if the active + // logger/experiment changed between calls). + const { parentObject, propagatedState } = + getSpanParentObjectAndPropagatedState(args); - public get rootSpanId(): string { - return this._rootSpanId; + if ( + parentObject instanceof SpanComponentsV3 || + parentObject instanceof SpanComponentsV4 + ) { + const parentSpanIds: ParentSpanIds | MultiParentSpanIds | undefined = + parentObject.data.row_id && + parentSpanIdsUsable( + parentObject.data.span_id, + parentObject.data.root_span_id, + ) + ? { + spanId: parentObject.data.span_id, + rootSpanId: parentObject.data.root_span_id, + } + : internalOptions?.useParentSpanIdsForObjectParent + ? args?.parentSpanIds + : undefined; + // The parent object/state are already resolved from `parent` above; drop + // the raw `parent` so it isn't re-normalized. + const { parent: _ignoredParent, ...spanArgs } = args ?? {}; + return new SpanImpl({ + state, + ...spanArgs, + parentObjectType: parentObject.data.object_type, + parentObjectId: new LazyValue( + spanComponentsToObjectIdLambda(state, parentObject), + ), + parentComputeObjectMetadataArgs: + parentObject.data.compute_object_metadata_args ?? undefined, + parentSpanIds, + propagatedEvent: + args?.propagatedEvent ?? + // eslint-disable-next-line @typescript-eslint/consistent-type-assertions + ((parentObject.data.propagated_event ?? undefined) as + | StartSpanEventArgs + | undefined), + propagatedState, + }); + } else { + // The internal entrypoint still accepts exported Braintrust parent slugs. + // Span implementations support that runtime form even though it is no + // longer part of their public method signature. + return parentObject.startSpan(args as StartSpanArgs); } +} - public get spanParents(): string[] { - return this._spanParents ?? []; - } +/** + * Runs the provided callback with the span as the current span. + */ +export function withCurrent( + span: Span, + callback: (span: Span) => R, + state: BraintrustState | undefined = undefined, +): R { + const currentState = state ?? _internalGetGlobalState(); + return currentState.contextManager.runInContext(span, () => callback(span)); +} - public setAttributes(args: Omit): void { - this.logInternal({ internalData: { span_attributes: args } }); - } +/** + * Wraps a sync generator to maintain the current span context across yields. + */ +function* generatorWithCurrent( + span: Span, + gen: Generator, + state: BraintrustState | undefined = undefined, +): Generator { + let nextValue: any; + while (true) { + const result = withCurrent( + span, + () => { + try { + return gen.next(nextValue); + } catch (e) { + // Handle generator.throw() + return { value: undefined, done: true, error: e }; + } + }, + state, + ); - public setSpanParents(parents: string[]): void { - this.logInternal({ internalData: { span_parents: parents } }); - } + if ("error" in result) { + throw result.error; + } - public log(event: ExperimentLogPartialArgs): void { - this.logInternal({ event }); - } + if (result.done) { + return result.value; + } - private logInternal({ - event, - internalData, - }: { - event?: ExperimentLogPartialArgs; - // `internalData` contains fields that are not part of the "user-sanitized" - // set of fields which we want to log in just one of the span rows. - internalData?: Partial; - }): void { - const [serializableInternalData, lazyInternalData] = splitLoggingData({ - event, - internalData, - }); + nextValue = yield result.value; + } +} - // Deep copy mutable user data. - const partialRecord = deepCopyEvent({ - id: this.id, - span_id: this._spanId, - root_span_id: this._rootSpanId, - span_parents: this._spanParents, - ...serializableInternalData, - [IS_MERGE_FIELD]: this.isMerge, - }); +/** + * Wraps an async generator to maintain the current span context across yields. + */ +async function* asyncGeneratorWithCurrent( + span: Span, + gen: AsyncGenerator, + state: BraintrustState | undefined = undefined, +): AsyncGenerator { + let nextValue: any; + while (true) { + const result = await withCurrent( + span, + async () => { + try { + return await gen.next(nextValue); + } catch (e) { + // Handle generator.throw() + return { value: undefined, done: true, error: e }; + } + }, + state, + ); - if (typeof partialRecord.metrics?.end === "number") { - this.loggedEndTime = partialRecord.metrics.end; + if ("error" in result) { + throw result.error; } - // Write to local span cache for scorer access - // Only cache experiment spans - regular logs don't need caching - if (this.parentObjectType === SpanObjectTypeV3.EXPERIMENT) { - const cachedSpan: CachedSpan = { - input: partialRecord.input, - output: partialRecord.output, - expected: partialRecord.expected, - error: partialRecord.error, - scores: partialRecord.scores, - metrics: partialRecord.metrics, - metadata: partialRecord.metadata, - tags: partialRecord.tags, - span_id: this._spanId, - span_parents: this._spanParents, - is_root: !this._spanParents || this._spanParents.length === 0, - span_attributes: partialRecord.span_attributes, - }; - this._state.spanCache.queueWrite( - this._rootSpanId, - this._spanId, - cachedSpan, - ); + if (result.done) { + return result.value; } - const computeRecord = async () => { - const record = { - ...partialRecord, - ...Object.fromEntries( - await Promise.all( - Object.entries(lazyInternalData).map(async ([key, value]) => [ - key, - await value.get(), - ]), - ), - ), - ...new SpanComponentsV3({ - object_type: this.parentObjectType, - object_id: await this.parentObjectId.get(), - }).objectIdFields(), - }; - // Customize inside the memoized lazy value, before attachment processing, - // merging, and masking. Retries reuse the customized or stripped record. - return customizeSpanExport(record) as BackgroundLogEvent; - }; - this._state.bgLogger().log([new LazyValue(computeRecord)]); + nextValue = yield result.value; } +} + +export function withParent( + parent: PropagationContext, + callback: () => R, + state: BraintrustState | undefined = undefined, +): R { + return (state ?? _internalGetGlobalState()).currentParent.run(parent, () => + callback(), + ); +} + +export function _internalWithParent( + parent: string | PropagationContext, + callback: () => R, + state: BraintrustState | undefined = undefined, +): R { + return (state ?? _internalGetGlobalState()).currentParent.run(parent, () => + callback(), + ); +} - public logFeedback(event: Omit): void { - logFeedbackImpl(this._state, this.parentObjectType, this.parentObjectId, { - ...event, - id: this.id, - }); - } +function _saveOrgInfo( + state: BraintrustState, + orgInfo: any, + orgName: string | undefined, +) { + const org = selectLoginOrg(orgInfo, orgName); + state.orgId = org.id; + state.orgName = org.name; + state.apiUrl = iso.getEnv("BRAINTRUST_API_URL") || org.api_url; + state.proxyUrl = iso.getEnv("BRAINTRUST_PROXY_URL") || org.proxy_url; + state.gitMetadataSettings = org.git_metadata || undefined; +} - public traced( - callback: (span: Span) => R, - args?: StartSpanArgs & SetCurrentArg, - ): R { - const { setCurrent, ...argsRest } = args ?? {}; - const span = this.startSpan(argsRest); - return runCatchFinally( - () => { - if (setCurrent ?? true) { - return withCurrent(span, callback); - } else { - return callback(span); - } - }, - (e) => { - logError(span, e); - throw e; - }, - () => span.end(), +function selectLoginOrg(orgInfo: any, orgName: string | undefined) { + if (orgInfo.length === 0) { + throw new LoginInvalidOrgError( + "This user is not part of any organizations.", ); } - public startSpan(args?: StartSpanArgs): Span { - const parentSpanIds: ParentSpanIds | undefined = args?.parent - ? undefined - : { spanId: this._spanId, rootSpanId: this._rootSpanId }; - return new SpanImpl({ - state: this._state, - ...args, - ...startSpanParentArgs({ - state: this._state, - parent: args?.parent, - parentObjectType: this.parentObjectType, - parentObjectId: this.parentObjectId, - parentComputeObjectMetadataArgs: this.parentComputeObjectMetadataArgs, - parentSpanIds, - propagatedEvent: args?.propagatedEvent ?? this.propagatedEvent, - propagatedState: this._propagatedState, - }), - }); + for (const org of orgInfo) { + if (orgName === undefined || org.name === orgName) { + return org; + } } - public startSpanWithParents( - spanId: string, - spanParents: string[], - args?: StartSpanArgs, - ): Span { - const parentSpanIds: MultiParentSpanIds = { - parentSpanIds: spanParents, - rootSpanId: this._rootSpanId, - }; - return new SpanImpl({ - state: this._state, - ...args, - ...startSpanParentArgs({ - state: this._state, - parent: args?.parent, - parentObjectType: this.parentObjectType, - parentObjectId: this.parentObjectId, - parentComputeObjectMetadataArgs: this.parentComputeObjectMetadataArgs, - parentSpanIds, - propagatedEvent: args?.propagatedEvent ?? this.propagatedEvent, - propagatedState: this._propagatedState, - }), - spanId, - }); - } + throw new LoginInvalidOrgError( + `Organization ${orgName} not found. Must be one of ${orgInfo + .map((org: any) => org.name) + .join(", ")}`, + ); +} - public end(args?: EndSpanArgs): number { - let endTime: number; - let internalData: Partial = {}; - if (!this.loggedEndTime) { - endTime = args?.endTime ?? getCurrentUnixTimestamp(); - internalData = { metrics: { end: endTime } }; - } else { - endTime = this.loggedEndTime; +export function validateTags(tags: readonly string[]): void { + const seen = new Set(); + for (const tag of tags) { + if (typeof tag !== "string") { + throw new Error("tags must be strings"); } - this.logInternal({ internalData }); - return endTime; + + if (seen.has(tag)) { + throw new Error(`duplicate tag: ${tag}`); + } + seen.add(tag); } +} - public async export(): Promise { - // Disable span cache since remote function spans won't be in the local cache - this._state.spanCache.disable(); +function validateAndSanitizeExperimentLogPartialArgs( + event: ExperimentLogPartialArgs, +): SanitizedExperimentLogPartialArgs { + if (event.scores) { + if (Array.isArray(event.scores)) { + throw new Error("scores must be an object, not an array"); + } + for (const [name, rawScore] of Object.entries(event.scores)) { + let score = rawScore; + if (typeof name !== "string") { + throw new Error("score names must be strings"); + } - return new (getSpanComponentsClass())({ - object_type: this.parentObjectType, + if (score === null || score === undefined) { + continue; + } - ...(this.parentComputeObjectMetadataArgs && - !this.parentObjectId.hasSucceeded - ? { compute_object_metadata_args: this.parentComputeObjectMetadataArgs } - : { object_id: await this.parentObjectId.get() }), - row_id: this.id, - span_id: this._spanId, - root_span_id: this._rootSpanId, - propagated_event: this.propagatedEvent, - }).toStr(); + if (typeof score === "boolean") { + score = score ? 1 : 0; + event.scores[name] = score; + } + + if (typeof score !== "number") { + throw new Error("score values must be numbers"); + } + if (score < 0 || score > 1) { + throw new Error("score values must be between 0 and 1"); + } + } } - /** - * Return this span's Braintrust parent string (`project_id:`, - * `project_name:`, or `experiment_id:`) for the `braintrust.parent` - * baggage entry, or undefined when it cannot be determined synchronously. - */ - public _getOtelParent(): string | undefined { - if (this.parentObjectType === SpanObjectTypeV3.PROJECT_LOGS) { - const id = - this.parentComputeObjectMetadataArgs?.project_id || - this.parentObjectId.getSync().value; - const name = this.parentComputeObjectMetadataArgs?.project_name; - if (id) { - return `project_id:${id}`; - } else if (name) { - return `project_name:${name}`; + if (event.metadata) { + for (const key of Object.keys(event.metadata)) { + if (typeof key !== "string") { + throw new Error("metadata keys must be strings"); } - } else if (this.parentObjectType === SpanObjectTypeV3.EXPERIMENT) { - const id = - this.parentComputeObjectMetadataArgs?.experiment_id || - this.parentObjectId.getSync().value; - if (id) { - return `experiment_id:${id}`; + } + } + + if (event.metrics) { + for (const [key, value] of Object.entries(event.metrics)) { + if (typeof key !== "string") { + throw new Error("metric keys must be strings"); + } + + if (value !== undefined && typeof value !== "number") { + throw new Error("metric values must be numbers"); } } - return undefined; } - public inject(): Record; - public inject(carrier: T): T; - public inject( - carrier?: T, - ): T | Record { - const resolvedCarrier = carrier ?? {}; - try { - const braintrustParent = - this._getOtelParent() ?? this._propagatedState?.braintrustParent; - if (!braintrustParent) { - // Symmetric with the receive-side warning in resolveW3cParent: - // surface this in the process that caused it, rather than leaving the - // consumer to debug a trace that arrived with no destination. - debugLogger - .forState(this._state) - .warn( - "Injecting trace context without braintrust.parent because the span's " + - "destination is not available yet. The receiver will start a new " + - "local trace instead of continuing this one.", - ); - } - _injectIntoCarrier(resolvedCarrier, { - traceId: this._rootSpanId, - spanId: this._spanId, - braintrustParent, - propagatedState: this._propagatedState, - }); - } catch (e) { - // best-effort: never break the caller - debugLogger.warn(`Error injecting trace context: ${e}`); - } - return resolvedCarrier; + if ("tags" in event && event.tags) { + validateTags(event.tags); + } + + return { ...event }; +} + +/** + * Creates a deep copy of the given event. Replaces references to user objects + * with placeholder strings to ensure serializability, except for + * {@link Attachment} and {@link ExternalAttachment} objects, which are preserved + * and not deep-copied. + */ +export function deepCopyEvent>( + event: T, +): T { + const attachments: BaseAttachment[] = []; + const ATTACHMENT_INDEX_KEY = "_bt_internal_saved_attachment_idx"; + const ATTACHMENT_MARKER_KEY = "_bt_internal_saved_attachment_marker"; + const attachmentMarker = ++deepCopyEventMarkerCounter; + + // We both check for serializability and round-trip `event` through JSON in + // order to create a "deep copy". This has the benefit of cutting out any + // reference to user objects when the object is logged asynchronously, so that + // in case the objects are modified, the logging is unaffected. In the future, + // this could be changed to a "real" deep copy so that immutable types (long + // strings) do not have to be copied. + const serialized = JSON.stringify(event, (_k, v) => { + if (v instanceof Error) { + return v.message; + } else if (v instanceof SpanImpl || v instanceof NoopSpan) { + return ``; + } else if ( + v && + typeof v === "object" && + v.__braintrust_experiment_marker === true + ) { + return ``; + } else if ( + v && + typeof v === "object" && + v.__braintrust_dataset_marker === true + ) { + return ``; + } else if (v instanceof Logger) { + return ``; + } else if (v instanceof BaseAttachment) { + const idx = attachments.push(v); + return { + [ATTACHMENT_INDEX_KEY]: idx - 1, + [ATTACHMENT_MARKER_KEY]: attachmentMarker, + }; + } else if (v instanceof ReadonlyAttachment) { + return v.reference; + } + return v; + }); + const x = JSON.parse(serialized, (_k, v) => { + if (isDeepCopyAttachmentPlaceholder(v, attachmentMarker)) { + return attachments[v[ATTACHMENT_INDEX_KEY]]; + } + return v; + }); + return x; +} + +let deepCopyEventMarkerCounter = 0; + +function isDeepCopyAttachmentPlaceholder( + value: unknown, + attachmentMarker: number, +): value is Record< + "_bt_internal_saved_attachment_idx" | "_bt_internal_saved_attachment_marker", + number +> { + if (!value || typeof value !== "object" || Array.isArray(value)) { + return false; + } + + const record = value as Record; + if ( + !Object.hasOwn(record, "_bt_internal_saved_attachment_idx") || + !Object.hasOwn(record, "_bt_internal_saved_attachment_marker") + ) { + return false; } - public async permalink(): Promise { - return await permalink(await this.export(), { - state: this._state, - }); + if (Object.keys(record).length !== 2) { + return false; } - public link(): string { - if (!this.id) { - return NOOP_SPAN_PERMALINK; - } + const attachmentIndex = record._bt_internal_saved_attachment_idx as number; + const marker = record._bt_internal_saved_attachment_marker; - try { - let baseUrl: string | null = null; + return ( + Number.isInteger(attachmentIndex) && + attachmentIndex >= 0 && + marker === attachmentMarker + ); +} - if (this.parentObjectType === SpanObjectTypeV3.PROJECT_LOGS) { - // For PROJECT_LOGS, use Logger's _getLinkBaseUrl which checks - // state -> linkArgs -> env var (matches Python) - const curLogger = this._state.currentLogger; - if (curLogger) { - baseUrl = curLogger._getLinkBaseUrl(); - } +/** + * Helper function for uploading attachments. Recursively extracts `Attachment` + * and `ExternalAttachment` objects and replaces them with their associated + * `AttachmentReference` objects. + * + * @param event The event to filter. Will be modified in-place. + * @param attachments Flat array of extracted attachments (output parameter). + */ +export function extractAttachments( + event: Record, + attachments: BaseAttachment[], +): void { + const pending: { + event: Record; + key: string; + attachment: BaseAttachment; + }[] = []; + let automatic = false; + const walk = (event: Record, depth = 0): void => { + if (depth >= 128) return; + for (const [key, value] of Object.entries(event)) { + if (!value) { + continue; } - // For EXPERIMENT or if Logger not available, fall back to state -> env var - if (!baseUrl) { - baseUrl = _getLinkBaseUrl(this._state); - if (!baseUrl) { - throw new Error("log-in-or-provide-org-name"); - } + // Base case: Attachment or ExternalAttachment. + if (value instanceof BaseAttachment) { + automatic ||= automaticAttachmentOriginals.has(value); + pending.push({ event, key, attachment: value }); + continue; // Attachment cannot be nested. } - return this._link(baseUrl); - } catch (e) { - return getErrPermlink(e instanceof Error ? e.message : String(e)); - } - } - - _link(baseUrl: string): string { - // NOTE[matt]: I believe lazy values should not exist in the span or the logger. - // Nothing in this module should have the possibility of blocking with the lone exception of - // flush() which should be a clear exception. We shouldn't build on it and - // plan to remove it in the future. - const args = this.parentComputeObjectMetadataArgs; - - switch (this.parentObjectType) { - case SpanObjectTypeV3.PROJECT_LOGS: { - // Links to spans require a project id or name. We might not either, so use whatever - // we can to make a link without making a roundtrip to the server. - const projectID = - args?.project_id || this.parentObjectId.getSync().value; - const projectName = args?.project_name; - if (projectID) { - return `${baseUrl}/object?object_type=project_logs&object_id=${projectID}&id=${this._id}`; - } else if (projectName) { - return `${baseUrl}/p/${projectName}/logs?oid=${this._id}`; - } else { - return getErrPermlink("provide-project-name-or-id"); - } - } - case SpanObjectTypeV3.EXPERIMENT: { - // Experiment links require an id, so the sync version will only work after the experiment is - // resolved. - const expID = - args?.experiment_id || this.parentObjectId?.getSync()?.value; - if (!expID) { - return getErrPermlink("provide-experiment-id"); - } else { - return `${baseUrl}/object?object_type=experiment&object_id=${expID}&id=${this._id}`; - } - } - case SpanObjectTypeV3.PLAYGROUND_LOGS: { - // FIXME[matt] I dont believe these are used in the SDK. - return NOOP_SPAN_PERMALINK; - } - default: { - // trigger a compile-time error if we add a new object type - const _exhaustive: never = this.parentObjectType; - // eslint-disable-next-line no-unused-expressions - _exhaustive; - return NOOP_SPAN_PERMALINK; + // Skip if this is already just a reference (no uploader field) + if ( + [ + BRAINTRUST_ATTACHMENT, + EXTERNAL_ATTACHMENT, + "inline_attachment", + ].includes(value?.type) + ) { + // This is already just a reference, skip it + continue; } - } - } - async flush(): Promise { - return await this._state.bgLogger().flush(); - } + // Somewhere in our pipeline we're serializing the attachment and then deserializing it. + // This loses the instanceof identity of the attachment, so we need to recreate it because + // the above instance check doesn't catch it. + if (value?.reference?.type === BRAINTRUST_ATTACHMENT && value?.uploader) { + // This looks like a serialized Attachment object, recreate it properly + const attachment = new Attachment({ + data: value.dataDebugString, + filename: value.reference.filename, + contentType: value.reference.content_type, + }); + pending.push({ event, key, attachment }); + continue; + } - public close(args?: EndSpanArgs): number { - return this.end(args); - } + // Base case: non-object. + if (!(value instanceof Object)) { + continue; // Nothing to explore recursively. + } - public state(): BraintrustState { - return this._state; + // Recursive case: object or array. + walk(value, depth + 1); + } + }; + const queueLength = attachments.length; + try { + walk(event); + // Read all references and enqueue before committing any replacement. + const references = pending.map(({ attachment }) => attachment.reference); + for (const { attachment } of pending) attachments.push(attachment); + pending.forEach(({ event, key }, index) => { + event[key] = references[index]; + }); + } catch (error) { + attachments.length = queueLength; + if (!automatic) throw error; + Object.assign(event, restoreAutomaticAttachments(event)); } +} - // Custom inspect for Node.js console.log - [Symbol.for("nodejs.util.inspect.custom")](): string { - return `SpanImpl { - kind: '${this.kind}', - id: '${this.id}', - spanId: '${this.spanId}', - rootSpanId: '${this.rootSpanId}', - spanParents: ${JSON.stringify(this.spanParents)} -}`; +function collectAttachmentReferenceKeys(value: unknown, keys: Set) { + if (!value || typeof value !== "object") return; + if (Array.isArray(value)) { + value.forEach((item) => collectAttachmentReferenceKeys(item, keys)); + return; } - - // Custom toString - toString(): string { - return `SpanImpl(id=${this.id}, spanId=${this.spanId})`; + const record = value as Record; + if (record.type === BRAINTRUST_ATTACHMENT && typeof record.key === "string") { + keys.add(record.key); + return; } + Object.values(record).forEach((item) => + collectAttachmentReferenceKeys(item, keys), + ); } -function splitLoggingData({ - event, - internalData, -}: { - event?: ExperimentLogPartialArgs; - // `internalData` contains fields that are not part of the "user-sanitized" - // set of fields which we want to log in just one of the span rows. - internalData?: Partial; -}): [Partial, Record>] { - // There should be no overlap between the dictionaries being merged, - // except for `sanitized` and `internalData`, where the former overrides - // the latter. - const sanitized = validateAndSanitizeExperimentLogPartialArgs(event ?? {}); - - const sanitizedAndInternalData: Partial & - Partial = {}; - mergeDicts(sanitizedAndInternalData, internalData || {}); - mergeDicts(sanitizedAndInternalData, sanitized); - - const serializableInternalData: typeof sanitizedAndInternalData = {}; - const lazyInternalData: Record> = {}; +/** + * Recursively hydrates any `AttachmentReference` into `Attachment` by modifying + * the input in-place. + * + * @returns The same event instance as the input. + */ +// eslint-disable-next-line @typescript-eslint/no-explicit-any +export function enrichAttachments>( + event: T, + state: BraintrustState | undefined, +): T { + for (const [key, value] of Object.entries(event)) { + // Base case: AttachmentReference. + const parsedValue = attachmentReferenceSchema.safeParse(value); + if (parsedValue.success) { + // eslint-disable-next-line @typescript-eslint/consistent-type-assertions, @typescript-eslint/no-explicit-any + (event as any)[key] = new ReadonlyAttachment(parsedValue.data, state); + continue; + } - for (const [key, value] of Object.entries(sanitizedAndInternalData) as [ - keyof typeof sanitizedAndInternalData, - any, - ][]) { - if (value instanceof BraintrustStream) { - const streamCopy = value.copy(); - lazyInternalData[key] = new LazyValue(async () => { - return await new Promise((resolve, reject) => { - streamCopy - .toReadableStream() - .pipeThrough(createFinalValuePassThroughStream(resolve, reject)) - .pipeTo(devNullWritableStream()); - }); - }); - } else if (value instanceof ReadableStream) { - lazyInternalData[key] = new LazyValue(async () => { - return await new Promise((resolve, reject) => { - value - .pipeThrough(createFinalValuePassThroughStream(resolve, reject)) - .pipeTo(devNullWritableStream()); - }); - }); - } else { - serializableInternalData[key] = value; + // Base case: non-object. + if (!(value instanceof Object)) { + continue; } + + // Recursive case: object or array: + enrichAttachments(value, state); } - return [serializableInternalData, lazyInternalData]; + return event; } /** - * A dataset is a collection of records, such as model inputs and expected outputs, which represent - * data you can use to evaluate and fine-tune models. You can log production data to datasets, - * curate them with interesting examples, edit/delete records, and run evaluations against them. + * Recursively hydrates any `AttachmentReference` into `Attachment` by modifying + * the input in-place. * - * You should not create `Dataset` objects directly. Instead, use the `braintrust.initDataset()` method. + * @returns The same event instance as the input. */ -export class Dataset< - IsLegacyDataset extends boolean = typeof DEFAULT_IS_LEGACY_DATASET, -> extends ObjectFetcher> { - private readonly lazyMetadata: LazyValue; - private readonly __braintrust_dataset_marker = true; - private newRecords = 0; - private lazyPinnedVersion?: LazyValue; - private pinnedEnvironment?: string; - private pinnedSnapshotName?: string; +// eslint-disable-next-line @typescript-eslint/no-explicit-any +export async function resolveAttachmentsToBase64>( + event: T, + state: BraintrustState | undefined, +): Promise { + for (const [key, value] of Object.entries(event)) { + if (value instanceof ReadonlyAttachment) { + // eslint-disable-next-line @typescript-eslint/consistent-type-assertions, @typescript-eslint/no-explicit-any + (event as any)[key] = await value.asBase64Url(); + continue; + } - constructor( - private state: BraintrustState, - lazyMetadata: LazyValue, - pinnedVersion?: string, - legacy?: IsLegacyDataset, - _internal_btql?: Record, - pinState?: DatasetPinState, - ) { - // eslint-disable-next-line @typescript-eslint/consistent-type-assertions - const isLegacyDataset = (legacy ?? - DEFAULT_IS_LEGACY_DATASET) as IsLegacyDataset; - if (isLegacyDataset) { - debugLogger - .forState(state) - .warn( - `Records will be fetched from this dataset in the legacy format, with the "expected" field renamed to "output". Please update your code to use "expected", and use \`braintrust.initDataset()\` with \`{ useOutput: false }\`, which will become the default in a future version of Braintrust.`, - ); + // Base case: non-object. + if (!(value instanceof Object)) { + continue; } - super( - "dataset", - pinnedVersion, - (r: AnyDatasetRecord) => - // eslint-disable-next-line @typescript-eslint/consistent-type-assertions - ensureDatasetRecord( - enrichAttachments(r, this.state), - isLegacyDataset, - ) as WithTransactionId>, - _internal_btql, - ); - void this.__braintrust_dataset_marker; - this.lazyMetadata = lazyMetadata; - this.lazyPinnedVersion = pinState?.lazyPinnedVersion; - this.pinnedEnvironment = pinState?.pinnedEnvironment; - this.pinnedSnapshotName = pinState?.pinnedSnapshotName; - } - public get id(): Promise { - return (async () => { - return (await this.lazyMetadata.get()).dataset.id; - })(); + // Recursive case: object or array: + await resolveAttachmentsToBase64(value, state); } - public get name(): Promise { - return (async () => { - return (await this.lazyMetadata.get()).dataset.name; - })(); - } + return event; +} - public get project(): Promise { - return (async () => { - return (await this.lazyMetadata.get()).project; - })(); +// Note that this only checks properties that are expected of a complete event. +// validateAndSanitizeExperimentLogPartialArgs should still be invoked (after +// handling special fields like 'id'). +export function validateAndSanitizeExperimentLogFullArgs( + event: ExperimentLogFullArgs, +): ExperimentLogFullArgs { + if (!("input" in event)) { + throw new Error("input must be specified"); } - public get loggingState(): BraintrustState { - return this.state; + if (isEmpty(event.output)) { + throw new Error("output must be specified"); + } + if (isEmpty(event.scores)) { + throw new Error("scores must be specified"); } - public async toEvalData(): Promise<{ - dataset_id: string; - dataset_version?: string; - dataset_environment?: string; - dataset_snapshot_name?: string; - _internal_btql?: Record; - }> { - await this.getState(); - const metadata = await this.lazyMetadata.get(); - const pinnedVersion = this.getPinnedVersion(); - const internalBtql = this.getInternalBtql(); + return event; +} - return { - dataset_id: metadata.dataset.id, - ...(this.pinnedEnvironment !== undefined - ? { - dataset_environment: this.pinnedEnvironment, - } - : {}), - ...(this.pinnedEnvironment === undefined && - this.pinnedSnapshotName !== undefined - ? { - dataset_snapshot_name: this.pinnedSnapshotName, - } - : {}), - ...(this.pinnedEnvironment === undefined && - this.pinnedSnapshotName === undefined && - pinnedVersion !== undefined - ? { - dataset_version: pinnedVersion, - } - : {}), - ...(internalBtql !== undefined ? { _internal_btql: internalBtql } : {}), - }; - } +export type BaseMetadata = Record | void; - protected async getState(): Promise { - // Ensure the login state is populated by awaiting lazyMetadata. - await this.lazyMetadata.get(); - if ( - this.lazyPinnedVersion !== undefined && - this.getPinnedVersion() === undefined - ) { - this.setPinnedVersion(await this.lazyPinnedVersion.get()); - } - return this.state; - } +export type DefaultMetadataType = void; - public override async version(options?: { batchSize?: number }) { - const pinnedVersion = this.getPinnedVersion(); - if (pinnedVersion !== undefined) { - return pinnedVersion; - } - await this.getState(); - return await super.version(options); - } +export type EvalCaseOrigin = ObjectReference; - private validateEvent({ - metadata, - expected, - output, - tags, - }: { - metadata?: Record; - expected?: unknown; - output?: unknown; - tags?: string[]; - }) { - if (metadata !== undefined) { - for (const key of Object.keys(metadata)) { - if (typeof key !== "string") { - throw new Error("metadata keys must be strings"); - } - } - } +export type EvalCase = { + input: Input; + tags?: string[]; + // These fields are only set if the EvalCase is part of a Dataset. + id?: string; + _xact_id?: TransactionId; + created?: string | null; + origin?: EvalCaseOrigin; + // This field is used to help re-run a particular experiment row. + upsert_id?: string; + // The number of times to run the evaluator for this specific input. + trialCount?: number; +} & (Expected extends void ? object : { expected: Expected }) & + (Metadata extends void ? object : { metadata: Metadata }); - if (expected !== undefined && output !== undefined) { - throw new Error( - "Only one of expected or output (deprecated) can be specified. Prefer expected.", - ); - } +let executionCounter = 0; - if (tags) { - validateTags(tags); - } - } +export function newId(): string { + return uuidv4(); +} - private createArgs({ - id, - input, - expected, - metadata, - tags, - output, - origin, - isMerge, - }: { - id: string; - input?: unknown; - expected?: unknown; - metadata?: Record; - tags?: string[]; - output?: unknown; - origin?: ObjectReferenceType; - isMerge?: boolean; - }): LazyValue { - return new LazyValue(async () => { - const dataset_id = await this.id; - const expectedValue = expected === undefined ? output : expected; +interface ResolvedSpanIds { + spanId: string; + rootSpanId: string; + spanParents: string[] | undefined; +} - const args: BackgroundLogEvent = { - id, - input, - expected: expectedValue, - tags, - dataset_id, - created: !isMerge ? new Date().toISOString() : undefined, //if we're merging/updating an event we will not add this ts - metadata, - origin, - ...(!!isMerge - ? { - [IS_MERGE_FIELD]: true, - } - : {}), - }; +/** + * Resolve all span IDs (span_id, root_span_id, span_parents) from explicit values, parent info, or context. + * + * @param spanId - Optional explicit span_id (from public API) + * @param parentSpanIds - Optional explicit parent span IDs (from parent string or parent span) + * @param lookupSpanParent - Whether to look up parent from context manager if no explicit parent. + * - True (default): start_span() inherits parent/root ids from the active span (if it exists) + * - False: don't look up parent in context (e.g. logger.log() doesn't inherit context) + * @param idGenerator - ID generator for creating new span/trace IDs + * @param contextManager - Context manager for looking up parent spans + * @returns ResolvedSpanIds with resolved span_id, root_span_id, and span_parents + */ +function _resolveSpanIds( + spanId: string | undefined, + parentSpanIds: ParentSpanIds | MultiParentSpanIds | undefined, + lookupSpanParent: boolean, + idGenerator: IDGenerator, + contextManager: ContextManager, +): ResolvedSpanIds { + // Generate span_id if not provided + const resolvedSpanId = spanId ?? idGenerator.getSpanId(); - return args; - }); + // If we have explicit parent span ids, use them. + if (parentSpanIds) { + return { + spanId: resolvedSpanId, + rootSpanId: parentSpanIds.rootSpanId, + spanParents: + "parentSpanIds" in parentSpanIds + ? parentSpanIds.parentSpanIds + : [parentSpanIds.spanId], + }; } - /** - * Insert a single record to the dataset. The record will be batched and uploaded behind the scenes. If you pass in an `id`, - * and a record with that `id` already exists, it will be overwritten (upsert). - * - * @param event The event to log. - * @param event.input The argument that uniquely define an input case (an arbitrary, JSON serializable object). - * @param event.expected The output of your application, including post-processing (an arbitrary, JSON serializable object). - * @param event.tags (Optional) a list of strings that you can use to filter and group records later. - * @param event.metadata (Optional) a dictionary with additional data about the test example, model outputs, or just - * about anything else that's relevant, that you can use to help find and analyze examples later. For example, you could log the - * `prompt`, example's `id`, or anything else that would be useful to slice/dice later. The values in `metadata` can be any - * JSON-serializable type, but its keys must be strings. - * @param event.origin (Optional) a reference to the source object this dataset record was derived from. - * @param event.id (Optional) a unique identifier for the event. If you don't provide one, Braintrust will generate one for you. - * @param event.output: (Deprecated) The output of your application. Use `expected` instead. - * @returns The `id` of the logged record. - */ - public insert({ - input, - expected, - metadata, - tags, - id, - output, - origin, - }: { - readonly input?: unknown; - readonly expected?: unknown; - readonly tags?: string[]; - readonly metadata?: Record; - readonly id?: string; - readonly output?: unknown; - readonly origin?: ObjectReferenceType; - }): string { - this.validateEvent({ metadata, expected, output, tags }); - - const rowId = id || uuidv4(); - const args = this.createArgs( - deepCopyEvent({ - id: rowId, - input, - expected, - metadata, - tags, - output, - origin, - isMerge: false, - }), - ); + // If we're using the context manager, see if there's an active parent span. + if (lookupSpanParent) { + const parentInfo = contextManager.getParentSpanIds(); + if (parentInfo) { + return { + spanId: resolvedSpanId, + rootSpanId: parentInfo.rootSpanId, + spanParents: parentInfo.spanParents, + }; + } + } - this.state.bgLogger().log([args]); - this.newRecords++; - return rowId; + // No parent - create new root span + // Root span ID behavior differs between UUID and OTEL generators: + // - UUID (legacy): root_span_id === span_id for backwards compatibility + // - OTEL: root_span_id is a separate trace ID, following OpenTelemetry convention + // where trace_id (root_span_id) represents the entire trace, distinct from + // the individual span's ID + let resolvedRootSpanId: string; + if (idGenerator.shareRootSpanId()) { + resolvedRootSpanId = resolvedSpanId; // Backwards compat for UUID mode + } else { + resolvedRootSpanId = idGenerator.getTraceId(); } - /** - * Update fields of a single record in the dataset. The updated fields will be batched and uploaded behind the scenes. - * You must pass in an `id` of the record to update. Only the fields provided will be updated; other fields will remain unchanged. - * - * @param event The fields to update in the record. - * @param event.id The unique identifier of the record to update. - * @param event.input (Optional) The new input value for the record (an arbitrary, JSON serializable object). - * @param event.expected (Optional) The new expected output value for the record (an arbitrary, JSON serializable object). - * @param event.tags (Optional) A list of strings to update the tags of the record. - * @param event.metadata (Optional) A dictionary to update the metadata of the record. The values in `metadata` can be any - * JSON-serializable type, but its keys must be strings. - * @returns The `id` of the updated record. - */ - public update({ - input, - expected, - metadata, - tags, - id, - }: { - readonly id: string; - readonly input?: unknown; - readonly expected?: unknown; - readonly tags?: string[]; - readonly metadata?: Record; - }): string { - this.validateEvent({ metadata, expected, tags }); - - const args = this.createArgs( - deepCopyEvent({ - id, - input, - expected, - metadata, - tags, - isMerge: true, - }), - ); + return { + spanId: resolvedSpanId, + rootSpanId: resolvedRootSpanId, + spanParents: undefined, + }; +} + +/** + * Primary implementation of the `Span` interface. See {@link Span} for full details on each method. + * + * We suggest using one of the various `traced` methods, instead of creating Spans directly. See {@link Span.startSpan} for full details. + */ +export class SpanImpl implements Span { + private _state: BraintrustState; + + private isMerge: boolean; + private loggedEndTime: number | undefined; + private propagatedEvent: StartSpanEventArgs | undefined; - this.state.bgLogger().log([args]); - return id; - } + // For internal use only. + private parentObjectType: SpanObjectTypeV3; + private parentObjectId: LazyValue; + private parentComputeObjectMetadataArgs: Record | undefined; - public delete(id: string): string { - const args = new LazyValue(async () => ({ - id, - dataset_id: await this.id, - created: new Date().toISOString(), - _object_delete: true, - })); + private _id: string; + private _spanId: string; + private _rootSpanId: string; + private _spanParents: string[] | undefined; - this.state.bgLogger().log([args]); - return id; - } + // Inbound W3C trace-context state (tracestate + raw traceparent flags) to + // forward on outbound propagation. Captured at the span that received it (via + // extractTraceContextFromHeaders) and inherited by all subspans, so that any + // inject() within the trace re-emits the upstream state unchanged, per the W3C + // Trace Context spec. Not interpreted. + private _propagatedState: PropagatedState | undefined; - public async createSnapshot({ - name, - description, - update, - }: { - readonly name: string; - readonly description?: string; - readonly update?: boolean; - }): Promise { - await this.flush(); - const state = await this.getState(); - const datasetId = await this.id; - // Live-row versions omit deletions. After flushing, get a server transaction - // boundary that includes every completed write, even if no live rows remain. - // getState() above also resolves versions pinned by snapshot or environment. - const currentVersion = - this.getPinnedVersion() ?? - (await (await state.apiConn().get("xact-id")).text()); - const response = await state - .appConn() - .post_json("api/dataset_snapshot/register", { - dataset_id: datasetId, - dataset_snapshot_name: name, - description, - xact_id: currentVersion, - update, - }); - return datasetSnapshotRegisterResponseSchema.parse(response) - .dataset_snapshot; - } + public kind = "span" as const; - public async listSnapshots(): Promise { - const state = await this.getState(); - return await getDatasetSnapshots({ - state, - datasetId: await this.id, + constructor( + args: { + state: BraintrustState; + parentObjectType: SpanObjectTypeV3; + parentObjectId: LazyValue; + parentComputeObjectMetadataArgs: Record | undefined; + parentSpanIds: ParentSpanIds | MultiParentSpanIds | undefined; + defaultRootType?: SpanType; + spanId?: string; + propagatedState?: PropagatedState | undefined; + [CAPTURE_ATTACHMENTS]?: boolean; + } & Omit & + InitialSpanWriteAsMergeArg & + InternalSpanContextArg, + ) { + this._state = args.state; + Object.defineProperty(this, CAPTURE_ATTACHMENTS, { + value: + args[CAPTURE_ATTACHMENTS] ?? + args.state.autoCaptureAttachments ?? + autoCaptureAttachmentsFromEnv(), + enumerable: true, + configurable: true, + writable: true, }); - } + this._propagatedState = args.propagatedState; + const instrumentationName = + getSpanInstrumentationName(args) ?? + INSTRUMENTATION_NAMES.BRAINTRUST_JS_LOGGER; - public async getSnapshot( - lookup: DatasetSnapshotLookup, - ): Promise { - const state = await this.getState(); - const datasetId = await this.id; - return await getDatasetSnapshot({ - state, - datasetId, - ...lookup, - }); - } + const spanAttributes = args.spanAttributes ?? {}; + const rawEvent = args.event ?? {}; + const type = + args.type ?? (args.parentSpanIds ? undefined : args.defaultRootType); - public async updateSnapshot( - snapshotId: string, - { - name, - description, - }: { - readonly name?: string; - readonly description?: string | null; - }, - ): Promise { - const state = await this.getState(); - return datasetSnapshotSchema.parse( - await state.appConn().post_json("api/dataset_snapshot/patch_id", { - id: snapshotId, + this.loggedEndTime = undefined; + this.parentObjectType = args.parentObjectType; + this.parentObjectId = args.parentObjectId; + this.parentComputeObjectMetadataArgs = args.parentComputeObjectMetadataArgs; + + // Merge propagatedEvent into event. The propagatedEvent data will get + // propagated-and-merged into every subspan. + this.propagatedEvent = args.propagatedEvent; + if (this.propagatedEvent) { + mergeDicts(rawEvent, this.propagatedEvent); + } + const { id: eventId, ...event } = rawEvent; + + const callerLocation = iso.getCallerLocation(); + const name = (() => { + if (args.name) return args.name; + if (!args.parentSpanIds) return "root"; + if (callerLocation) { + const pathComponents = callerLocation.caller_filename.split("/"); + const filename = pathComponents[pathComponents.length - 1]; + return [callerLocation.caller_functionname] + .concat( + filename ? [`${filename}:${callerLocation.caller_lineno}`] : [], + ) + .join(":"); + } + return "subspan"; + })(); + + const internalData = { + metrics: { + start: args.startTime ?? getCurrentUnixTimestamp(), + }, + context: mergeSpanOriginContext( + { ...callerLocation, ...args[INTERNAL_SPAN_CONTEXT] }, + instrumentationName, + this._state.spanOriginEnvironment, + ), + span_attributes: { name, - description, - }), - ); - } + type, + ...spanAttributes, + exec_counter: executionCounter++, + }, + created: new Date().toISOString(), + }; - public async deleteSnapshot(snapshotId: string): Promise { - const state = await this.getState(); - return datasetSnapshotSchema.parse( - await state.appConn().post_json("api/dataset_snapshot/delete_id", { - id: snapshotId, - }), - ); - } + this._id = eventId ?? this._state.idGenerator.getSpanId(); - public async restorePreview({ - version, - }: { - readonly version: string; - }): Promise { - await this.flush(); - const state = await this.getState(); - const datasetId = await this.id; - return datasetRestorePreviewResultSchema.parse( - await state - .apiConn() - .post_json(`v1/dataset/${datasetId}/restore/preview`, { - version, - }), + // Resolve span IDs using the helper function that matches Python's _resolve_span_ids + const resolvedIds = _resolveSpanIds( + args.spanId, + args.parentSpanIds, + true, // Always lookup span parent from context manager unless explicit parent provided + this._state.idGenerator, + this._state.contextManager, ); - } - public async restore({ - version, - }: { - readonly version: string; - }): Promise { - await this.flush(); - const state = await this.getState(); - const datasetId = await this.id; - return datasetRestoreResultSchema.parse( - await state.apiConn().post_json(`v1/dataset/${datasetId}/restore`, { - version, - }), - ); - } + this._spanId = resolvedIds.spanId; + this._rootSpanId = resolvedIds.rootSpanId; + this._spanParents = resolvedIds.spanParents; - /** - * Summarize the dataset, including high level metrics about its size and other metadata. - * @param summarizeData Whether to summarize the data. If false, only the metadata will be returned. - * @returns `DatasetSummary` - * @returns A summary of the dataset. - */ - public async summarize( - options: { readonly summarizeData?: boolean } = {}, - ): Promise { - const { summarizeData = true } = options || {}; - - await this.flush(); - const state = await this.getState(); - const projectUrl = `${state.appPublicUrl}/app/${encodeURIComponent( - state.orgName!, - )}/p/${encodeURIComponent((await this.project).name)}`; - const datasetUrl = `${projectUrl}/datasets/${encodeURIComponent( - await this.name, - )}`; - - let dataSummary: DataSummary | undefined; - if (summarizeData) { - const rawDataSummary = z - .object({ - total_records: z.number(), - }) - .parse( - await state.apiConn().get_json( - "dataset-summary", - { - dataset_id: await this.id, - }, - 3, - ), - ); - dataSummary = { - newRecords: this.newRecords, - totalRecords: rawDataSummary.total_records, - }; + // Deterministic spans can be initialized concurrently by separate + // workflow executions, so their first write must not replace later merges. + this.isMerge = args[INITIAL_SPAN_WRITE_AS_MERGE] === true; + if (!args[RESUME_SPAN_WITHOUT_INITIAL_WRITE]) { + this.logInternal({ event, internalData }); } + this.isMerge = true; + } + public getParentInfo(): { + objectType: SpanObjectTypeV3; + objectId: LazyValue; + computeObjectMetadataArgs: { [x: string]: any } | undefined; + } { return { - projectName: (await this.project).name, - datasetName: await this.name, - projectUrl, - datasetUrl, - dataSummary, + objectType: this.parentObjectType, + objectId: this.parentObjectId, + computeObjectMetadataArgs: this.parentComputeObjectMetadataArgs && { + ...this.parentComputeObjectMetadataArgs, + }, }; } - /** - * Flush any pending rows to the server. - */ - async flush(): Promise { - return await this.state.bgLogger().flush(); + public get id(): string { + return this._id; } - /** - * @deprecated This function is deprecated. You can simply remove it from your code. - */ - public async close(): Promise { - debugLogger - .forState(this.state) - .warn( - "close is deprecated and will be removed in a future version of braintrust. It is now a no-op and can be removed", - ); - return this.id; + public get spanId(): string { + return this._spanId; } - public static isDataset(data: unknown): data is Dataset { - return ( - typeof data === "object" && - data !== null && - "__braintrust_dataset_marker" in data - ); + public get rootSpanId(): string { + return this._rootSpanId; } -} - -type CompiledPromptResponseFormat = - Exclude extends infer ResponseFormat - ? ResponseFormat extends { - type: "json_schema"; - json_schema: infer JsonSchema; - } - ? Omit & { - json_schema: Omit< - Extract, - "schema" - > & { - schema?: Record; - }; - } - : ResponseFormat - : never; - -// "none" is not assignable to older openai clients so we gotta exclude it -type CompiledPromptReasoningEffort = Exclude< - AnyModelParam["reasoning_effort"], - "none" ->; - -export type CompiledPromptParams = Omit< - NonNullable["params"], - "use_cache" | "response_format" | "reasoning_effort" -> & - Omit & { - reasoning_effort?: CompiledPromptReasoningEffort; - response_format?: CompiledPromptResponseFormat; - model: NonNullable["model"]>; - }; - -export type ChatPrompt = { - messages: OpenAIMessage[]; - tools?: ChatCompletionTool[]; -}; -export type CompletionPrompt = { - prompt: string; -}; - -export type CompiledPrompt = - CompiledPromptParams & { - span_info?: { - name?: string; - spanAttributes?: Record; - metadata: { - prompt: { - variables: Record; - id: string; - project_id: string; - version: string; - }; - }; - }; - } & (Flavor extends "chat" - ? ChatPrompt - : Flavor extends "completion" - ? CompletionPrompt - : // eslint-disable-next-line @typescript-eslint/no-empty-object-type - {}); - -export type DefaultPromptArgs = Partial< - CompiledPromptParams & AnyModelParam & ChatPrompt & CompletionPrompt ->; - -function isAttachmentObject(value: unknown): boolean { - return ( - BraintrustAttachmentReferenceSchema.safeParse(value).success || - InlineAttachmentReferenceSchema.safeParse(value).success || - ExternalAttachmentReferenceSchema.safeParse(value).success - ); -} -// Simple URL validation all braintrust attachment urls or generic links -function isURL(url: string): boolean { - try { - const parsedUrl = new URL(url.trim()); - return parsedUrl.protocol === "http:" || parsedUrl.protocol === "https:"; - } catch { - return false; + public get spanParents(): string[] { + return this._spanParents ?? []; } -} -/** - * Detect and expand attachment array variables before template rendering. - * Supports both simple variables ({{images}}) and nested paths ({{data.images}}). - * @param content The message content (should be a template variable like "{{images}}" or "{{data.images}}") - * @param variables The variables object with array values - * @returns Array of image_url parts if expansion occurred, null otherwise - */ -function expandAttachmentArrayPreTemplate( - content: unknown, - variables: Record, -): unknown[] | null { - if (typeof content !== "string") return null; - - // Detect {{varName}} or {{nested.path}} pattern (exact match only - no mixed content) - // Supports: {{images}}, {{data.images}}, {{user.profile.images}}, etc. - const match = content.match(/^\{\{\s*([\w.]+)\s*\}\}$/); - if (!match) return null; - - const varPath = match[1]; - - const value = varPath.includes(".") - ? getObjValueByPath(variables, varPath.split(".")) - : variables[varPath]; - - if (!Array.isArray(value)) return null; - - // Check if array contains attachments or image URLs - const allValid = value.every( - (v) => isAttachmentObject(v) || (typeof v === "string" && isURL(v)), - ); + public setAttributes(args: Omit): void { + this.logInternal({ internalData: { span_attributes: args } }); + } - if (!allValid) return null; + public setSpanParents(parents: string[]): void { + this.logInternal({ internalData: { span_parents: parents } }); + } - // Expand directly to image_url parts - return value.map((item) => ({ - type: "image_url" as const, - image_url: { url: item }, - })); -} + public log(event: ExperimentLogPartialArgs): void { + this.logInternal({ event }); + } -export function renderMessage( - render: (template: string) => string, - message: T, -): T; + private logInternal({ + event, + internalData, + }: { + event?: ExperimentLogPartialArgs; + // `internalData` contains fields that are not part of the "user-sanitized" + // set of fields which we want to log in just one of the span rows. + internalData?: Partial; + }): void { + const [serializableInternalData, lazyInternalData] = splitLoggingData({ + event, + internalData, + }); -export function renderMessage( - render: (template: string) => string, - message: T, -): T { - return renderMessageImpl(render, message, {}); -} + // Deep copy mutable user data. + const partialRecord = deepCopyEvent({ + id: this.id, + span_id: this._spanId, + root_span_id: this._rootSpanId, + span_parents: this._spanParents, + ...serializableInternalData, + [IS_MERGE_FIELD]: this.isMerge, + }); -export function renderMessageImpl( - render: (template: string) => string, - message: T, - variables?: Record, -): T { - return { - ...message, - ...("content" in message - ? { - content: isEmpty(message.content) - ? undefined - : typeof message.content === "string" - ? render(message.content) - : message.content.flatMap((c) => { - switch (c.type) { - case "text": - return [{ ...c, text: render(c.text) }]; - case "image_url": - if (isObject(c.image_url.url)) { - throw new Error( - "Attachments must be replaced with URLs before calling `build()`", - ); - } - - if (variables) { - const expanded = expandAttachmentArrayPreTemplate( - c.image_url.url, - variables, - ); - if (expanded) { - return expanded; - } - } - - // Otherwise render the URL template normally - return [ - { - ...c, - image_url: { - ...c.image_url, - url: render(c.image_url.url), - }, - }, - ]; - case "input_audio": - return [ - { - ...c, - input_audio: { - ...c.input_audio, - data: render(c.input_audio.data), - }, - }, - ]; - case "file": - return [ - { - ...c, - file: { - ...(c.file.file_data && { - file_data: render(c.file.file_data), - }), - ...(c.file.file_id && { - file_id: render(c.file.file_id), - }), - ...(c.file.filename && { - filename: render(c.file.filename), - }), - }, - }, - ]; - default: - const _exhaustiveCheck: never = c; - return _exhaustiveCheck; - } - }), - } - : {}), - ...("tool_calls" in message - ? { - tool_calls: isEmpty(message.tool_calls) - ? undefined - : message.tool_calls.map((t) => { - return { - type: t.type, - id: render(t.id), - function: { - name: render(t.function.name), - arguments: render(t.function.arguments), - }, - }; - }), - } - : {}), - ...("tool_call_id" in message - ? { - tool_call_id: render(message.tool_call_id), - } - : {}), - }; -} + if (typeof partialRecord.metrics?.end === "number") { + this.loggedEndTime = partialRecord.metrics.end; + } -export type PromptRowWithId< - HasId extends boolean = true, - HasVersion extends boolean = true, -> = Omit & - Partial> & - (HasId extends true - ? Pick - : Partial>) & - (HasVersion extends true - ? Pick - : Partial>); + // Write to local span cache for scorer access + // Only cache experiment spans - regular logs don't need caching + if (this.parentObjectType === SpanObjectTypeV3.EXPERIMENT) { + const cachedSpan: CachedSpan = { + input: partialRecord.input, + output: partialRecord.output, + expected: partialRecord.expected, + error: partialRecord.error, + scores: partialRecord.scores, + metrics: partialRecord.metrics, + metadata: partialRecord.metadata, + tags: partialRecord.tags, + span_id: this._spanId, + span_parents: this._spanParents, + is_root: !this._spanParents || this._spanParents.length === 0, + span_attributes: partialRecord.span_attributes, + }; + this._state.spanCache.queueWrite( + this._rootSpanId, + this._spanId, + cachedSpan, + ); + } -export function deserializePlainStringAsJSON(s: string) { - if (s.trim() === "") { - return { value: null, error: undefined }; + const computeRecord = async () => { + const record = { + ...partialRecord, + ...Object.fromEntries( + await Promise.all( + Object.entries(lazyInternalData).map(async ([key, value]) => [ + key, + await value.get(), + ]), + ), + ), + ...new SpanComponentsV3({ + object_type: this.parentObjectType, + object_id: await this.parentObjectId.get(), + }).objectIdFields(), + }; + // Customize inside the memoized lazy value, before attachment processing, + // merging, and masking. Retries reuse the customized or stripped record. + const customized = customizeSpanExport(record) as BackgroundLogEvent; + // Background flushes run outside this span's context. Keep its policy on + // the record without exposing internal metadata to customizers or JSON. + Object.defineProperty(customized, CAPTURE_ATTACHMENTS, { + value: this[CAPTURE_ATTACHMENTS], + configurable: true, + }); + return customized; + }; + this._state.bgLogger().log([new LazyValue(computeRecord)]); } - try { - return { value: JSON.parse(s), error: undefined }; - } catch (e) { - return { value: s, error: e }; + public logFeedback(event: Omit): void { + logFeedbackImpl(this._state, this.parentObjectType, this.parentObjectId, { + ...event, + id: this.id, + }); } -} -function renderTemplatedObject( - obj: unknown, - args: Record, - options: { strict?: boolean; templateFormat: TemplateFormat }, -): unknown { - if (typeof obj === "string") { - return renderTemplateContent( - obj, - args, - (value) => (typeof value === "string" ? value : JSON.stringify(value)), - { - strict: options.strict, - templateFormat: options.templateFormat, + public traced( + callback: (span: Span) => R, + args?: StartSpanArgs & SetCurrentArg, + ): R { + const { setCurrent, ...argsRest } = args ?? {}; + const span = this.startSpan(argsRest); + return runCatchFinally( + () => { + if (setCurrent ?? true) { + return withCurrent(span, callback); + } else { + return callback(span); + } }, - ); - } else if (isArray(obj)) { - return obj.map((item) => renderTemplatedObject(item, args, options)); - } else if (isObject(obj)) { - return Object.fromEntries( - Object.entries(obj).map(([key, value]) => [ - key, - renderTemplatedObject(value, args, options), - ]), + (error) => { + logError(span, error); + throw error; + }, + () => span.end(), ); } - return obj; -} - -export function renderPromptParams( - params: ModelParams | undefined, - args: Record, - options: { strict?: boolean; templateFormat?: TemplateFormat } = {}, -): ModelParams | undefined { - const templateFormat = parseTemplateFormat(options.templateFormat); - const strict = !!options.strict; - - const schemaParsed = z - .object({ - response_format: z.object({ - type: z.literal("json_schema"), - json_schema: responseFormatJsonSchemaSchema - .omit({ schema: true }) - .extend({ - schema: z.unknown(), - }), + + public startSpan(args?: StartSpanArgs): Span { + const parentSpanIds: ParentSpanIds | undefined = args?.parent + ? undefined + : { spanId: this._spanId, rootSpanId: this._rootSpanId }; + return new SpanImpl({ + state: this._state, + [CAPTURE_ATTACHMENTS]: this[CAPTURE_ATTACHMENTS], + ...args, + ...startSpanParentArgs({ + state: this._state, + parent: args?.parent, + parentObjectType: this.parentObjectType, + parentObjectId: this.parentObjectId, + parentComputeObjectMetadataArgs: this.parentComputeObjectMetadataArgs, + parentSpanIds, + propagatedEvent: args?.propagatedEvent ?? this.propagatedEvent, + propagatedState: this._propagatedState, }), - }) - .safeParse(params); - if (schemaParsed.success) { - const rawSchema = schemaParsed.data.response_format.json_schema.schema; - const templatedSchema = renderTemplatedObject(rawSchema, args, { - strict, - templateFormat, }); - const parsedSchema = - typeof templatedSchema === "string" - ? deserializePlainStringAsJSON(templatedSchema).value - : templatedSchema; - - return { - ...params, - response_format: { - ...schemaParsed.data.response_format, - json_schema: { - ...schemaParsed.data.response_format.json_schema, - schema: parsedSchema, - }, - }, - }; - } - return params; -} - -export class Prompt< - HasId extends boolean = true, - HasVersion extends boolean = true, -> { - private parsedPromptData: PromptData | undefined; - private hasParsedPromptData = false; - private readonly __braintrust_prompt_marker = true; - - constructor( - private metadata: PromptRowWithId | PromptSessionEvent, - private defaults: DefaultPromptArgs, - private noTrace: boolean, - ) { - void this.__braintrust_prompt_marker; - } - - public get id(): HasId extends true ? string : string | undefined { - // eslint-disable-next-line @typescript-eslint/consistent-type-assertions - return this.metadata.id as HasId extends true ? string : string | undefined; - } - - public get projectId(): string | undefined { - return this.metadata.project_id; - } - - public get name(): string { - return "name" in this.metadata - ? this.metadata.name - : `Playground function ${this.metadata.id}`; - } - - public get slug(): string { - return "slug" in this.metadata ? this.metadata.slug : this.metadata.id; - } - - public get prompt(): PromptData["prompt"] { - return this.getParsedPromptData()?.prompt; } - public get version(): HasId extends true - ? TransactionId - : TransactionId | undefined { - // eslint-disable-next-line @typescript-eslint/consistent-type-assertions - return this.metadata[TRANSACTION_ID_FIELD] as HasId extends true - ? TransactionId - : TransactionId | undefined; + public startSpanWithParents( + spanId: string, + spanParents: string[], + args?: StartSpanArgs, + ): Span { + const parentSpanIds: MultiParentSpanIds = { + parentSpanIds: spanParents, + rootSpanId: this._rootSpanId, + }; + return new SpanImpl({ + state: this._state, + [CAPTURE_ATTACHMENTS]: this[CAPTURE_ATTACHMENTS], + ...args, + ...startSpanParentArgs({ + state: this._state, + parent: args?.parent, + parentObjectType: this.parentObjectType, + parentObjectId: this.parentObjectId, + parentComputeObjectMetadataArgs: this.parentComputeObjectMetadataArgs, + parentSpanIds, + propagatedEvent: args?.propagatedEvent ?? this.propagatedEvent, + propagatedState: this._propagatedState, + }), + spanId, + }); } - public get options(): NonNullable { - return this.getParsedPromptData()?.options || {}; + public end(args?: EndSpanArgs): number { + let endTime: number; + let internalData: Partial = {}; + if (!this.loggedEndTime) { + endTime = args?.endTime ?? getCurrentUnixTimestamp(); + internalData = { metrics: { end: endTime } }; + } else { + endTime = this.loggedEndTime; + } + this.logInternal({ internalData }); + return endTime; } - public get templateFormat(): string | null | undefined { - return this.getParsedPromptData()?.template_format; - } + public async export(): Promise { + // Disable span cache since remote function spans won't be in the local cache + this._state.spanCache.disable(); - public get promptData(): PromptData { - return this.getParsedPromptData()!; - } + return new (getSpanComponentsClass())({ + object_type: this.parentObjectType, - /** - * Build the prompt with the given formatting options. The args you pass in will - * be forwarded to the mustache template that defines the prompt and rendered with - * the `mustache-js` library. - * - * @param buildArgs Args to forward along to the prompt template. - */ - public build( - buildArgs: unknown, - options: { - flavor?: Flavor; - messages?: Message[]; - strict?: boolean; - templateFormat?: TemplateFormat; - } = {}, - ): CompiledPrompt { - // eslint-disable-next-line @typescript-eslint/consistent-type-assertions - return this.runBuild(buildArgs, { - flavor: options.flavor ?? "chat", - messages: options.messages, - strict: options.strict, - templateFormat: options.templateFormat, - }) as CompiledPrompt; + ...(this.parentComputeObjectMetadataArgs && + !this.parentObjectId.hasSucceeded + ? { compute_object_metadata_args: this.parentComputeObjectMetadataArgs } + : { object_id: await this.parentObjectId.get() }), + row_id: this.id, + span_id: this._spanId, + root_span_id: this._rootSpanId, + propagated_event: this.propagatedEvent, + }).toStr(); } /** - * This is a special build method that first resolves attachment references, and then - * calls the regular build method. You should use this if you are building prompts from - * dataset rows that contain attachments. - * - * @param buildArgs Args to forward along to the prompt template. + * Return this span's Braintrust parent string (`project_id:`, + * `project_name:`, or `experiment_id:`) for the `braintrust.parent` + * baggage entry, or undefined when it cannot be determined synchronously. */ - public async buildWithAttachments< - Flavor extends "chat" | "completion" = "chat", - >( - buildArgs: unknown, - options: { - flavor?: Flavor; - messages?: Message[]; - strict?: boolean; - state?: BraintrustState; - templateFormat?: TemplateFormat; - } = {}, - ): Promise> { - const hydrated = - buildArgs instanceof Object - ? await resolveAttachmentsToBase64(buildArgs, options.state) - : buildArgs; - // eslint-disable-next-line @typescript-eslint/consistent-type-assertions - return this.runBuild(hydrated, { - flavor: options.flavor ?? "chat", - messages: options.messages, - strict: options.strict, - templateFormat: options.templateFormat, - }) as CompiledPrompt; - } - - private runBuild( - buildArgs: unknown, - options: { - flavor: Flavor; - messages?: Message[]; - strict?: boolean; - templateFormat?: TemplateFormat; - }, - ): CompiledPrompt { - const { flavor } = options; - - const params = Object.fromEntries( - Object.entries({ - ...this.defaults, - ...Object.fromEntries( - Object.entries(this.options.params || {}).filter( - ([k, _v]) => !BRAINTRUST_PARAMS.includes(k), - ), - ), - ...(!isEmpty(this.options.model) - ? { - model: this.options.model, - } - : {}), - }).filter(([key, value]) => key !== "response_format" || value !== null), - ); - - if (!("model" in params) || isEmpty(params.model)) { - throw new Error( - "No model specified. Either specify it in the prompt or as a default", - ); - } - - const spanInfo = this.noTrace - ? {} - : { - span_info: { - metadata: { - prompt: this.id - ? { - variables: buildArgs, - id: this.id, - project_id: this.projectId, - version: this.version, - ...("prompt_session_id" in this.metadata - ? { prompt_session_id: this.metadata.prompt_session_id } - : {}), - } - : undefined, - }, - }, - }; - - const prompt = this.prompt; - - if (!prompt) { - throw new Error("Empty prompt"); - } - - const dictArgParsed = z.record(z.unknown()).safeParse(buildArgs); - const variables: Record = { - input: buildArgs, - ...(dictArgParsed.success ? dictArgParsed.data : {}), - }; - - // Use template_format from prompt data if available, otherwise fall back to the option or default to mustache - const promptDataTemplateFormat = this.templateFormat; - const resolvedTemplateFormat = parseTemplateFormat( - options.templateFormat ?? promptDataTemplateFormat, - ); - - const renderedPrompt = Prompt.renderPrompt({ - prompt, - buildArgs, - options: { ...options, templateFormat: resolvedTemplateFormat }, - }); - - if (flavor === "chat") { - if (renderedPrompt.type !== "chat") { - throw new Error( - "Prompt is a completion prompt. Use buildCompletion() instead", - ); + public _getOtelParent(): string | undefined { + if (this.parentObjectType === SpanObjectTypeV3.PROJECT_LOGS) { + const id = + this.parentComputeObjectMetadataArgs?.project_id || + this.parentObjectId.getSync().value; + const name = this.parentComputeObjectMetadataArgs?.project_name; + if (id) { + return `project_id:${id}`; + } else if (name) { + return `project_name:${name}`; } - - // eslint-disable-next-line @typescript-eslint/consistent-type-assertions - return { - ...renderPromptParams(params, variables, { - strict: options.strict, - templateFormat: resolvedTemplateFormat, - }), - ...spanInfo, - messages: renderedPrompt.messages, - ...(renderedPrompt.tools - ? { - tools: chatCompletionToolSchema - .array() - .parse(JSON.parse(renderedPrompt.tools)), - } - : undefined), - } as CompiledPrompt; - } else if (flavor === "completion") { - if (renderedPrompt.type !== "completion") { - throw new Error(`Prompt is a chat prompt. Use flavor: 'chat' instead`); + } else if (this.parentObjectType === SpanObjectTypeV3.EXPERIMENT) { + const id = + this.parentComputeObjectMetadataArgs?.experiment_id || + this.parentObjectId.getSync().value; + if (id) { + return `experiment_id:${id}`; } - - // eslint-disable-next-line @typescript-eslint/consistent-type-assertions - return { - ...renderPromptParams(params, variables, { - strict: options.strict, - templateFormat: resolvedTemplateFormat, - }), - ...spanInfo, - prompt: renderedPrompt.content, - } as CompiledPrompt; - } else { - throw new Error("never!"); } + return undefined; } - static renderPrompt({ - prompt, - buildArgs, - options, - }: { - prompt: PromptBlockData; - buildArgs: unknown; - options: { - strict?: boolean; - messages?: Message[]; - templateFormat?: TemplateFormat; - }; - }): PromptBlockData { - const escape = (v: unknown) => { - if (v === undefined) { - throw new Error("Missing!"); - } else if (typeof v === "string") { - return v; - } else if (v instanceof ReadonlyAttachment) { - throw new Error( - "Use buildWithAttachments() to build prompts with attachments", - ); - } else { - return JSON.stringify(v); - } - }; - - const dictArgParsed = z.record(z.unknown()).safeParse(buildArgs); - const variables: Record = { - input: buildArgs, - ...(dictArgParsed.success ? dictArgParsed.data : {}), - }; - - const templateFormat = parseTemplateFormat(options.templateFormat); - - if (prompt.type === "chat") { - const render = (template: string) => - renderTemplateContent(template, variables, escape, { - strict: options.strict, - templateFormat: templateFormat, - }); - - const baseMessages = (prompt.messages || []).map((m) => - renderMessageImpl(render, m, variables), - ); - const hasSystemPrompt = baseMessages.some((m) => m.role === "system"); - - const messages: Message[] = [ - ...baseMessages, - ...(options.messages ?? []).filter( - (m) => !(hasSystemPrompt && m.role === "system"), - ), - ]; - - return { - type: "chat", - messages: messages, - ...(prompt.tools?.trim() - ? { - tools: render(prompt.tools), - } - : undefined), - }; - } else if (prompt.type === "completion") { - if (options.messages) { - throw new Error( - "extra messages are not supported for completion prompts", - ); + public inject(): Record; + public inject(carrier: T): T; + public inject( + carrier?: T, + ): T | Record { + const resolvedCarrier = carrier ?? {}; + try { + const braintrustParent = + this._getOtelParent() ?? this._propagatedState?.braintrustParent; + if (!braintrustParent) { + // Symmetric with the receive-side warning in resolveW3cParent: + // surface this in the process that caused it, rather than leaving the + // consumer to debug a trace that arrived with no destination. + debugLogger + .forState(this._state) + .warn( + "Injecting trace context without braintrust.parent because the span's " + + "destination is not available yet. The receiver will start a new " + + "local trace instead of continuing this one.", + ); } - - const content = renderTemplateContent(prompt.content, variables, escape, { - strict: options.strict, - templateFormat: templateFormat, + _injectIntoCarrier(resolvedCarrier, { + traceId: this._rootSpanId, + spanId: this._spanId, + braintrustParent, + propagatedState: this._propagatedState, }); - return { - type: "completion", - content, - }; - } else { - const _: never = prompt; - throw new Error(`Invalid prompt type: ${_}`); + } catch (e) { + // best-effort: never break the caller + debugLogger.warn(`Error injecting trace context: ${e}`); } + return resolvedCarrier; } - private getParsedPromptData(): PromptData | undefined { - if (!this.hasParsedPromptData) { - this.parsedPromptData = promptDataSchema.parse(this.metadata.prompt_data); - this.hasParsedPromptData = true; - } - return this.parsedPromptData!; + public async permalink(): Promise { + return await permalink(await this.export(), { + state: this._state, + }); } - public static isPrompt(data: unknown): data is Prompt { - return ( - typeof data === "object" && - data !== null && - "__braintrust_prompt_marker" in data - ); - } + public link(): string { + if (!this.id) { + return NOOP_SPAN_PERMALINK; + } - /** @internal */ - public _internalSerializeForCache() { - return { - metadata: this.metadata, - defaults: this.defaults, - noTrace: this.noTrace, - }; - } + try { + let baseUrl: string | null = null; - public static fromPromptData( - name: string, - promptData: PromptData, - ): Prompt { - return new Prompt( - { - name: name, - slug: name, - prompt_data: promptData, - }, - {}, - false, - ); - } -} + if (this.parentObjectType === SpanObjectTypeV3.PROJECT_LOGS) { + // For PROJECT_LOGS, use Logger's _getLinkBaseUrl which checks + // state -> linkArgs -> env var (matches Python) + const curLogger = this._state.currentLogger; + if (curLogger) { + baseUrl = curLogger._getLinkBaseUrl(); + } + } -export class RemoteEvalParameters< - HasId extends boolean = true, - HasVersion extends boolean = true, - T extends Record = Record, -> { - private readonly __braintrust_parameters_marker = true; + // For EXPERIMENT or if Logger not available, fall back to state -> env var + if (!baseUrl) { + baseUrl = _getLinkBaseUrl(this._state); + if (!baseUrl) { + throw new Error("log-in-or-provide-org-name"); + } + } - constructor(private metadata: ParametersRow) { - void this.__braintrust_parameters_marker; + return this._link(baseUrl); + } catch (e) { + return getErrPermlink(e instanceof Error ? e.message : String(e)); + } } - public get id(): HasId extends true ? string : string | undefined { - // eslint-disable-next-line @typescript-eslint/consistent-type-assertions - return this.metadata.id as HasId extends true ? string : string | undefined; - } + _link(baseUrl: string): string { + // NOTE[matt]: I believe lazy values should not exist in the span or the logger. + // Nothing in this module should have the possibility of blocking with the lone exception of + // flush() which should be a clear exception. We shouldn't build on it and + // plan to remove it in the future. + const args = this.parentComputeObjectMetadataArgs; - public get projectId(): string | undefined { - return this.metadata.project_id; + switch (this.parentObjectType) { + case SpanObjectTypeV3.PROJECT_LOGS: { + // Links to spans require a project id or name. We might not either, so use whatever + // we can to make a link without making a roundtrip to the server. + const projectID = + args?.project_id || this.parentObjectId.getSync().value; + const projectName = args?.project_name; + if (projectID) { + return `${baseUrl}/object?object_type=project_logs&object_id=${projectID}&id=${this._id}`; + } else if (projectName) { + return `${baseUrl}/p/${projectName}/logs?oid=${this._id}`; + } else { + return getErrPermlink("provide-project-name-or-id"); + } + } + case SpanObjectTypeV3.EXPERIMENT: { + // Experiment links require an id, so the sync version will only work after the experiment is + // resolved. + const expID = + args?.experiment_id || this.parentObjectId?.getSync()?.value; + if (!expID) { + return getErrPermlink("provide-experiment-id"); + } else { + return `${baseUrl}/object?object_type=experiment&object_id=${expID}&id=${this._id}`; + } + } + case SpanObjectTypeV3.PLAYGROUND_LOGS: { + // FIXME[matt] I dont believe these are used in the SDK. + return NOOP_SPAN_PERMALINK; + } + default: { + // trigger a compile-time error if we add a new object type + const _exhaustive: never = this.parentObjectType; + // eslint-disable-next-line no-unused-expressions + _exhaustive; + return NOOP_SPAN_PERMALINK; + } + } } - public get name(): string { - return this.metadata.name; + async flush(): Promise { + return await this._state.bgLogger().flush(); } - public get slug(): string { - return this.metadata.slug; + public state(): BraintrustState { + return this._state; } - public get version(): HasVersion extends true - ? TransactionId - : TransactionId | undefined { - // eslint-disable-next-line @typescript-eslint/consistent-type-assertions - return this.metadata[TRANSACTION_ID_FIELD] as HasVersion extends true - ? TransactionId - : TransactionId | undefined; + // Custom inspect for Node.js console.log + static { + Object.defineProperty(this.prototype, NODE_INSPECT_CUSTOM, { + configurable: true, + writable: true, + value(this: SpanImpl): string { + return `SpanImpl { + kind: '${this.kind}', + id: '${this.id}', + spanId: '${this.spanId}', + rootSpanId: '${this.rootSpanId}', + spanParents: ${JSON.stringify(this.spanParents)} +}`; + }, + }); } - public get schema(): Record { - return this.metadata.function_data.__schema; + // Custom toString + toString(): string { + return `SpanImpl(id=${this.id}, spanId=${this.spanId})`; } +} - public get data(): T { - // eslint-disable-next-line @typescript-eslint/consistent-type-assertions - return (this.metadata.function_data.data ?? {}) as T; - } +function splitLoggingData({ + event, + internalData, +}: { + event?: ExperimentLogPartialArgs; + // `internalData` contains fields that are not part of the "user-sanitized" + // set of fields which we want to log in just one of the span rows. + internalData?: Partial; +}): [Partial, Record>] { + // There should be no overlap between the dictionaries being merged, + // except for `sanitized` and `internalData`, where the former overrides + // the latter. + const sanitized = validateAndSanitizeExperimentLogPartialArgs(event ?? {}); - /** @internal */ - public _internalSerializeForCache() { - return { metadata: this.metadata }; - } + const sanitizedAndInternalData: Partial & + Partial = {}; + mergeDicts(sanitizedAndInternalData, internalData || {}); + mergeDicts(sanitizedAndInternalData, sanitized); - public validate(data: unknown): boolean { - if (typeof data !== "object" || data === null) { - return false; - } - const schemaProps = this.schema.properties; - if (typeof schemaProps !== "object" || schemaProps === null) { - return true; - } - for (const key of Object.keys(schemaProps)) { - if (!(key in data)) { - const required = Array.isArray(this.schema.required) - ? this.schema.required - : []; - if (required.includes(key)) { - return false; - } - } - } - return true; - } + const serializableInternalData: typeof sanitizedAndInternalData = {}; + const lazyInternalData: Record> = {}; - public static isParameters( - x: unknown, - ): x is RemoteEvalParameters> { - return ( - typeof x === "object" && - x !== null && - "__braintrust_parameters_marker" in x && - x.__braintrust_parameters_marker === true - ); + for (const [key, value] of Object.entries(sanitizedAndInternalData) as [ + keyof typeof sanitizedAndInternalData, + any, + ][]) { + if (value instanceof BraintrustStream) { + const streamCopy = value.copy(); + lazyInternalData[key] = new LazyValue(async () => { + return await new Promise((resolve, reject) => { + streamCopy + .toReadableStream() + .pipeThrough(createFinalValuePassThroughStream(resolve, reject)) + .pipeTo(devNullWritableStream()); + }); + }); + } else if (value instanceof ReadableStream) { + lazyInternalData[key] = new LazyValue(async () => { + return await new Promise((resolve, reject) => { + value + .pipeThrough(createFinalValuePassThroughStream(resolve, reject)) + .pipeTo(devNullWritableStream()); + }); + }); + } else { + serializableInternalData[key] = value; + } } -} - -export type AnyDataset = Dataset; - -/** - * Summary of a score's performance. - * @property name Name of the score. - * @property score Average score across all examples. - * @property diff Difference in score between the current and reference experiment. - * @property improvements Number of improvements in the score. - * @property regressions Number of regressions in the score. - */ -export interface ScoreSummary { - name: string; - score: number; - diff?: number; - improvements: number; - regressions: number; -} - -/** - * Summary of a metric's performance. - * @property name Name of the metric. - * @property metric Average metric across all examples. - * @property unit Unit label for the metric. - * @property diff Difference in metric between the current and reference experiment. - * @property improvements Number of improvements in the metric. - * @property regressions Number of regressions in the metric. - */ -export interface MetricSummary { - name: string; - metric: number; - unit: string; - diff?: number; - improvements: number; - regressions: number; -} -/** - * Summary of an experiment's scores and metadata. - * @property projectName Name of the project that the experiment belongs to. - * @property experimentName Name of the experiment. - * @property experimentId ID of the experiment. May be `undefined` if the eval was run locally. - * @property projectUrl URL to the project's page in the Braintrust app. - * @property experimentUrl URL to the experiment's page in the Braintrust app. - * @property comparisonExperimentName The experiment scores are baselined against. - * @property scores Summary of the experiment's scores. - */ -export interface ExperimentSummary { - projectName: string; - experimentName: string; - projectId?: string; - experimentId?: string; - projectUrl?: string; - experimentUrl?: string; - comparisonExperimentName?: string; - scores: Record; - metrics?: Record; + return [serializableInternalData, lazyInternalData]; } -/** - * Summary of a dataset's data. - * - * @property totalRecords Total records in the dataset. - */ -export interface DataSummary { - /** - * New or updated records added in this session. - */ - newRecords: number; - /** - * Total records in the dataset. - */ - totalRecords: number; +// Interface declarations preserve symbol methods with Oxc isolated emit. +export interface NoopSpan { + [NODE_INSPECT_CUSTOM](): string; } -/** - * Summary of a dataset's scores and metadata. - * - * @property projectName Name of the project that the dataset belongs to. - * @property datasetName Name of the dataset. - * @property projectUrl URL to the project's page in the Braintrust app. - * @property datasetUrl URL to the experiment's page in the Braintrust app. - * @property dataSummary Summary of the dataset's data. - */ -export interface DatasetSummary { - projectName: string; - datasetName: string; - projectUrl: string; - datasetUrl: string; - dataSummary: DataSummary | undefined; +export interface BraintrustState { + [RESET_CONTEXT_MANAGER_STATE](): void; + [NODE_INSPECT_CUSTOM](): string; } -/** - * Allows accessing helper functions for testing. - * @internal - */ - -// it's used to circumvent login for testing, but won't actually work -// on the server side. -const TEST_API_KEY = "___TEST_API_KEY__THIS_IS_NOT_REAL___"; - -function setInitialTestState() { - // a way of setting initial state for tests without any log messages - if (!_internalGetGlobalState()) { - _internalSetInitialState(); - } -} -// This is a helper function to simulate a login for testing. -async function simulateLoginForTests() { - return await login({ - apiKey: TEST_API_KEY, - appUrl: "https://braintrust.dev", - }); +export interface HTTPConnection { + [NODE_INSPECT_CUSTOM](): string; } -// This is a helper function to simulate a logout for testing. -function simulateLogoutForTests() { - const state = _internalGetGlobalState(); - state.resetLoginInfo(); - state.resetIdGenState(); - state[RESET_CONTEXT_MANAGER_STATE](); - state.appUrl = "https://www.braintrust.dev"; - return state; +export interface SpanImpl { + [NODE_INSPECT_CUSTOM](): string; + /** @internal Snapshot inherited by descendants and delayed instrumentation. */ + readonly [CAPTURE_ATTACHMENTS]: boolean; } -/** - * Get the versions for a prompt. - * - * @param projectId The ID of the project to query - * @param promptId The ID of the prompt to get versions for - * @returns Promise containing the version data - */ -export async function getPromptVersions( - projectId: string, - promptId: string, -): Promise { - const state = _internalGetGlobalState(); - if (!state) { - throw new Error("Must log in first"); - } - - await state.login({}); - - const query = { - from: { - op: "function", - name: { - op: "ident", - name: ["project_prompts"], - }, - args: [ - { - op: "literal", - value: projectId, - }, - ], - }, - select: [ - { - op: "star", - }, - ], - filter: { - op: "eq", - left: { op: "ident", name: ["id"] }, - right: { op: "literal", value: promptId }, - }, - }; - - const response = await state.apiConn().post( - "btql", - { - query, - audit_log: true, - use_columnstore: false, - brainstore_realtime: true, - }, - { headers: { "Accept-Encoding": "gzip" } }, - BTQL_HTTP_RETRIES, - ); - - if (!response.ok) { - throw new Error( - `API request failed: ${response.status} ${response.statusText}`, - ); - } - - const result = await response.json(); - - // Filter for entries where audit_data.action is "upsert" or "merge" and return only _xact_id fields - return ( - result.data - ?.filter((entry: any) => - ["upsert", "merge"].includes(entry.audit_data?.action), - ) - .map((entry: any) => prettifyXact(entry._xact_id)) || [] - ); +export interface Logger { + /** @internal */ + readonly [CAPTURE_ATTACHMENTS]: boolean; } - -// Helper function to reset ID generator state for testing -function resetIdGenStateForTests() { - const state = _internalGetGlobalState(); - if (state) { - state.resetIdGenState(); - } -} - -export const _exportsForTestingOnly = { - extractAttachments, - deepCopyEvent, - useTestBackgroundLogger, - clearTestBackgroundLogger, - resetDebugLoggerForTests, - simulateLoginForTests, - simulateLogoutForTests, - setInitialTestState, - initTestExperiment, - isGeneratorFunction, - isAsyncGeneratorFunction, - resetIdGenStateForTests, - validateTags, - // eslint-disable-next-line @typescript-eslint/no-explicit-any - isomorph: iso as any, // Expose isomorph for build type detection -}; diff --git a/js/src/logger/datasets.ts b/js/src/logger/datasets.ts new file mode 100644 index 000000000..8a97c077b --- /dev/null +++ b/js/src/logger/datasets.ts @@ -0,0 +1,1022 @@ +/// +import { v4 as uuidv4 } from "uuid"; +import { + AnyDatasetRecord, + BackgroundLogEvent, + DatasetRecord, + ensureNewDatasetRecord, + IS_MERGE_FIELD, + isObject, +} from "../util"; +import { DatasetSnapshot as datasetSnapshotSchema } from "../sdk-schemas"; +import type { DatasetSnapshot, ObjectReference } from "../sdk-types"; +import { z } from "zod/v3"; +import { LazyValue } from "../util"; +import { + FullLoginOptions, + BraintrustState, + ProjectDatasetMetadata, + _internalGetGlobalState, + enrichAttachments, + ObjectMetadata, + validateTags, + deepCopyEvent, +} from "./core"; +import { ObjectFetcher, WithTransactionId } from "./objects"; + +const datasetSnapshotRegisterResponseSchema = z.object({ + dataset_snapshot: datasetSnapshotSchema, + found_existing: z.boolean().optional(), +}); + +const datasetObjectInfoSchema = z.object({ + object_id: z.string(), + object_name: z.string(), + parent_cols: z.object({ + project: z.object({ + id: z.string(), + name: z.string(), + }), + }), +}); + +const datasetRestorePreviewResultSchema = z.object({ + rows_to_restore: z.number(), + rows_to_delete: z.number(), +}); + +export type DatasetRestorePreviewResult = { + rows_to_restore: number; + rows_to_delete: number; +}; + +const datasetRestoreResultSchema = z.object({ + xact_id: z.string().nullable(), + rows_restored: z.number(), + rows_deleted: z.number(), +}); + +export type DatasetRestoreResult = { + xact_id: string | null; + rows_restored: number; + rows_deleted: number; +}; + +/** + * Reference to a dataset by ID and optional explicit selector. + */ +export type DatasetSelection = { + version?: string; + environment?: string; + snapshotName?: string; +}; + +type DatasetSnapshotNameLookup = { + snapshotName: string; + xactId?: never; +}; + +type DatasetSnapshotXactLookup = { + snapshotName?: never; + xactId: string; +}; + +export type DatasetSnapshotLookup = + | DatasetSnapshotNameLookup + | DatasetSnapshotXactLookup; + +function isDatasetSnapshotNameLookup( + lookup: DatasetSnapshotLookup, +): lookup is DatasetSnapshotNameLookup { + return "snapshotName" in lookup; +} + +function assertDatasetSnapshotLookup(lookup: { + snapshotName?: string; + xactId?: string; +}): asserts lookup is DatasetSnapshotLookup { + const hasSnapshotName = lookup.snapshotName !== undefined; + const hasXactId = lookup.xactId !== undefined; + if (hasSnapshotName === hasXactId) { + throw new Error("Exactly one of snapshotName or xactId must be provided"); + } +} + +type DatasetPinState = { + lazyPinnedVersion?: LazyValue; + pinnedEnvironment?: string; + pinnedSnapshotName?: string; +}; + +export type DatasetRef = { + id: string; +} & DatasetSelection; + +export type InitDatasetOptions = FullLoginOptions & { + dataset?: string; + datasetId?: string; + description?: string; + version?: string; + environment?: string; + snapshotName?: string; + projectId?: string; + metadata?: Record; + state?: BraintrustState; + _internal_btql?: Record; +}; + +export type FullInitDatasetOptions = { + project?: string; +} & InitDatasetOptions; + +async function getDatasetSnapshots( + params: + | { + state: BraintrustState; + datasetId: string; + } + | ({ + state: BraintrustState; + datasetId: string; + } & DatasetSnapshotLookup), +): Promise { + const { state, datasetId } = params; + return datasetSnapshotSchema.array().parse( + await state.appConn().post_json("api/dataset_snapshot/get", { + dataset_id: datasetId, + ...("snapshotName" in params ? { name: params.snapshotName } : {}), + ...("xactId" in params ? { xact_id: params.xactId } : {}), + }), + ); +} + +async function getDatasetSnapshot( + params: { + state: BraintrustState; + datasetId: string; + } & DatasetSnapshotLookup, +): Promise { + assertDatasetSnapshotLookup(params); + const snapshots = await getDatasetSnapshots(params); + if (snapshots.length > 1) { + throw new Error( + isDatasetSnapshotNameLookup(params) + ? `Expected a unique dataset snapshot named "${params.snapshotName}" for ${params.datasetId}` + : `Expected a unique dataset snapshot for xact_id "${params.xactId}" in ${params.datasetId}`, + ); + } + return snapshots[0]; +} + +function normalizeDatasetSelection({ + version, + environment, + snapshotName, +}: DatasetSelection): DatasetSelection { + if (version !== undefined) { + return { version }; + } + + if (snapshotName !== undefined) { + return { snapshotName }; + } + + if (environment !== undefined) { + return { environment }; + } + + return {}; +} + +async function resolveDatasetSnapshotName({ + state, + datasetId, + snapshotName, +}: { + state: BraintrustState; + datasetId: string; + snapshotName: string; +}): Promise { + const match = await getDatasetSnapshot({ + state, + datasetId, + snapshotName, + }); + if (match === undefined) { + throw new Error( + `Dataset snapshot "${snapshotName}" not found for ${datasetId}`, + ); + } + return match.xact_id; +} + +async function resolveDatasetSnapshotNameForMetadata({ + state, + lazyMetadata, + snapshotName, +}: { + state: BraintrustState; + lazyMetadata: LazyValue; + snapshotName: string; +}): Promise { + const metadata = await lazyMetadata.get(); + return await resolveDatasetSnapshotName({ + state, + datasetId: metadata.dataset.id, + snapshotName, + }); +} + +async function resolveDatasetEnvironment({ + state, + datasetId, + environment, +}: { + state: BraintrustState; + datasetId: string; + environment: string; +}): Promise { + const environmentObjectPath = `environment-object/dataset/${datasetId}/${encodeURIComponent(environment)}`; + const response = + state.orgName == null + ? await state.apiConn().get_json(environmentObjectPath) + : await state.apiConn().get_json(environmentObjectPath, { + org_name: state.orgName, + }); + return z.object({ object_version: z.string() }).parse(response) + .object_version; +} + +async function resolveDatasetEnvironmentForMetadata({ + state, + lazyMetadata, + environment, +}: { + state: BraintrustState; + lazyMetadata: LazyValue; + environment: string; +}): Promise { + const metadata = await lazyMetadata.get(); + return await resolveDatasetEnvironment({ + state, + datasetId: metadata.dataset.id, + environment, + }); +} + +export async function serializeDatasetForExperiment({ + dataset, + state, +}: { + dataset: AnyDataset | DatasetRef; + state: BraintrustState; +}): Promise<{ datasetId: string; datasetVersion?: string }> { + if (!Dataset.isDataset(dataset)) { + const selection = normalizeDatasetSelection(dataset); + + if (selection.version !== undefined) { + return { + datasetId: dataset.id, + datasetVersion: selection.version, + }; + } + + if (selection.snapshotName !== undefined) { + return { + datasetId: dataset.id, + datasetVersion: await resolveDatasetSnapshotName({ + state, + datasetId: dataset.id, + snapshotName: selection.snapshotName, + }), + }; + } + + if (selection.environment !== undefined) { + return { + datasetId: dataset.id, + datasetVersion: await resolveDatasetEnvironment({ + state, + datasetId: dataset.id, + environment: selection.environment, + }), + }; + } + + return { + datasetId: dataset.id, + }; + } + + const evalData = await dataset.toEvalData(); + const selection = normalizeDatasetSelection({ + version: evalData.dataset_version, + environment: evalData.dataset_environment, + snapshotName: evalData.dataset_snapshot_name, + }); + + if (selection.version !== undefined) { + return { + datasetId: evalData.dataset_id, + datasetVersion: selection.version, + }; + } + + const datasetVersion = await dataset.version(); + if (datasetVersion !== undefined) { + return { + datasetId: evalData.dataset_id, + datasetVersion, + }; + } + + return { + datasetId: evalData.dataset_id, + }; +} + +/** + * Initialize a dataset by name or ID. When initializing by name, the dataset and its project will be created if they do not exist. + * + * @param options Options for configuring initDataset(). + * @param options.project The name of the project to create the dataset in. Must specify at least one of `project` or `projectId` unless `datasetId` is provided. + * @param options.dataset The name of the dataset to create. If not specified, a name will be generated automatically. Ignored if `datasetId` is provided. + * @param options.datasetId The ID of an existing dataset. Takes precedence over `dataset` and does not require `project` or `projectId`. + * @param options.description An optional description when initializing a dataset by name. Cannot be used with `datasetId`. + * @param options.version Pin the dataset to a specific version xact_id. If `snapshotName` or `environment` are also provided, `version` takes precedence. + * @param options.snapshotName Pin the dataset to the version captured by this named snapshot. If `environment` is also provided, `snapshotName` takes precedence. + * @param options.environment Pin the dataset to the version tagged with this environment slug. + * @param options.appUrl The URL of the Braintrust App. Defaults to https://www.braintrust.dev. + * @param options.apiKey The API key to use. If the parameter is not specified, will try to use the `BRAINTRUST_API_KEY` environment variable. In Node.js, if that is unset, will try the nearest `.env.braintrust` file in the current working directory or parent directories. If no API key is specified, will prompt the user to login. + * @param options.orgName (Optional) The name of a specific organization to connect to. This is useful if you belong to multiple. + * @param options.projectId The id of the project to create the dataset in. This takes precedence over `project` if specified and is not required when `datasetId` is provided. + * @param options.metadata A dictionary with additional data when initializing a dataset by name. Cannot be used with `datasetId`. The values in `metadata` can be any JSON-serializable type, but its keys must be strings. + * @returns The initialized Dataset. + */ +export function initDataset( + options: Readonly, +): Dataset { + const { + project, + dataset, + datasetId, + description, + version, + snapshotName, + environment, + appUrl, + apiKey, + orgName, + fetch, + forceLogin, + projectId, + metadata, + state: stateArg, + _internal_btql, + } = options; + if ( + datasetId !== undefined && + (description !== undefined || metadata !== undefined) + ) { + throw new Error( + "Cannot specify description or metadata when datasetId is provided", + ); + } + + const selection = normalizeDatasetSelection({ + version, + environment, + snapshotName, + }); + const normalizedVersion = selection.version; + const normalizedEnvironment = selection.environment; + const normalizedSnapshotName = selection.snapshotName; + + const cliInternalBtql = globalThis.__bt_eval_internal_btql; + const internalBtql = + cliInternalBtql === undefined + ? _internal_btql + : _internal_btql === undefined + ? cliInternalBtql + : isObject(_internal_btql) + ? { ...cliInternalBtql, ..._internal_btql } + : _internal_btql; + + const state = stateArg ?? _internalGetGlobalState(); + + const lazyMetadata: LazyValue = new LazyValue( + async () => { + await state.login({ + orgName, + apiKey, + appUrl, + fetch, + forceLogin, + }); + + if (datasetId !== undefined) { + const objectInfo = datasetObjectInfoSchema.array().parse( + await state.appConn().post_json("api/self/get_object_info", { + object_type: "dataset", + object_ids: [datasetId], + }), + ); + if (objectInfo.length === 0) { + throw new Error(`Dataset with ID ${datasetId} not found`); + } + if (objectInfo.length !== 1) { + throw new Error( + `Expected exactly one dataset with ID ${datasetId}, but found ${objectInfo.length}`, + ); + } + + const datasetInfo = objectInfo[0]; + return { + project: { + id: datasetInfo.parent_cols.project.id, + name: datasetInfo.parent_cols.project.name, + fullInfo: datasetInfo.parent_cols.project, + }, + dataset: { + id: datasetInfo.object_id, + name: datasetInfo.object_name, + fullInfo: datasetInfo, + }, + }; + } + + const args: Record = { + org_id: state.orgId, + project_name: project, + project_id: projectId, + dataset_name: dataset, + description, + metadata, + }; + const response = await state + .appConn() + .post_json("api/dataset/register", args); + + return { + project: { + id: response.project.id, + name: response.project.name, + fullInfo: response.project, + }, + dataset: { + id: response.dataset.id, + name: response.dataset.name, + fullInfo: response.dataset, + }, + }; + }, + ); + + const resolvedVersion = + normalizedVersion !== undefined + ? normalizedVersion + : normalizedSnapshotName !== undefined + ? new LazyValue(async () => { + return await resolveDatasetSnapshotNameForMetadata({ + state, + lazyMetadata, + snapshotName: normalizedSnapshotName, + }); + }) + : normalizedEnvironment !== undefined + ? new LazyValue(async () => { + return await resolveDatasetEnvironmentForMetadata({ + state, + lazyMetadata, + environment: normalizedEnvironment, + }); + }) + : undefined; + + const datasetObject = new Dataset( + stateArg ?? _internalGetGlobalState(), + lazyMetadata, + typeof resolvedVersion === "string" ? resolvedVersion : undefined, + internalBtql, + resolvedVersion instanceof LazyValue || + normalizedEnvironment !== undefined || + normalizedSnapshotName !== undefined + ? { + ...(resolvedVersion instanceof LazyValue + ? { + lazyPinnedVersion: resolvedVersion, + } + : {}), + ...(normalizedEnvironment !== undefined + ? { + pinnedEnvironment: normalizedEnvironment, + } + : {}), + ...(normalizedSnapshotName !== undefined + ? { + pinnedSnapshotName: normalizedSnapshotName, + } + : {}), + } + : undefined, + ); + + return datasetObject; +} + +/** + * A dataset is a collection of records, such as model inputs and expected outputs, which represent + * data you can use to evaluate and fine-tune models. You can log production data to datasets, + * curate them with interesting examples, edit/delete records, and run evaluations against them. + * + * You should not create `Dataset` objects directly. Instead, use the `braintrust.initDataset()` method. + */ +export class Dataset extends ObjectFetcher { + private readonly lazyMetadata: LazyValue; + private readonly __braintrust_dataset_marker = true; + private newRecords = 0; + private lazyPinnedVersion?: LazyValue; + private pinnedEnvironment?: string; + private pinnedSnapshotName?: string; + + constructor( + private state: BraintrustState, + lazyMetadata: LazyValue, + pinnedVersion?: string, + _internal_btql?: Record, + pinState?: DatasetPinState, + ) { + super( + "dataset", + pinnedVersion, + (r: AnyDatasetRecord) => + ensureNewDatasetRecord( + enrichAttachments(r, this.state), + ) as WithTransactionId, + _internal_btql, + ); + void this.__braintrust_dataset_marker; + this.lazyMetadata = lazyMetadata; + this.lazyPinnedVersion = pinState?.lazyPinnedVersion; + this.pinnedEnvironment = pinState?.pinnedEnvironment; + this.pinnedSnapshotName = pinState?.pinnedSnapshotName; + } + + public get id(): Promise { + return (async () => { + return (await this.lazyMetadata.get()).dataset.id; + })(); + } + + public get name(): Promise { + return (async () => { + return (await this.lazyMetadata.get()).dataset.name; + })(); + } + + public get project(): Promise { + return (async () => { + return (await this.lazyMetadata.get()).project; + })(); + } + + public get loggingState(): BraintrustState { + return this.state; + } + + public async toEvalData(): Promise<{ + dataset_id: string; + dataset_version?: string; + dataset_environment?: string; + dataset_snapshot_name?: string; + _internal_btql?: Record; + }> { + await this.getState(); + const metadata = await this.lazyMetadata.get(); + const pinnedVersion = this.getPinnedVersion(); + const internalBtql = this.getInternalBtql(); + + return { + dataset_id: metadata.dataset.id, + ...(this.pinnedEnvironment !== undefined + ? { + dataset_environment: this.pinnedEnvironment, + } + : {}), + ...(this.pinnedEnvironment === undefined && + this.pinnedSnapshotName !== undefined + ? { + dataset_snapshot_name: this.pinnedSnapshotName, + } + : {}), + ...(this.pinnedEnvironment === undefined && + this.pinnedSnapshotName === undefined && + pinnedVersion !== undefined + ? { + dataset_version: pinnedVersion, + } + : {}), + ...(internalBtql !== undefined ? { _internal_btql: internalBtql } : {}), + }; + } + + protected async getState(): Promise { + // Ensure the login state is populated by awaiting lazyMetadata. + await this.lazyMetadata.get(); + if ( + this.lazyPinnedVersion !== undefined && + this.getPinnedVersion() === undefined + ) { + this.setPinnedVersion(await this.lazyPinnedVersion.get()); + } + return this.state; + } + + public override async version(options?: { + batchSize?: number; + }): Promise { + const pinnedVersion = this.getPinnedVersion(); + if (pinnedVersion !== undefined) { + return pinnedVersion; + } + await this.getState(); + return await super.version(options); + } + + private validateEvent({ + metadata, + tags, + }: { + metadata?: Record; + tags?: string[]; + }) { + if (metadata !== undefined) { + for (const key of Object.keys(metadata)) { + if (typeof key !== "string") { + throw new Error("metadata keys must be strings"); + } + } + } + + if (tags) { + validateTags(tags); + } + } + + private createArgs({ + id, + input, + expected, + metadata, + tags, + origin, + isMerge, + }: { + id: string; + input?: unknown; + expected?: unknown; + metadata?: Record; + tags?: string[]; + origin?: ObjectReference; + isMerge?: boolean; + }): LazyValue { + return new LazyValue(async () => { + const dataset_id = await this.id; + const args: BackgroundLogEvent = { + id, + input, + expected, + tags, + dataset_id, + created: !isMerge ? new Date().toISOString() : undefined, //if we're merging/updating an event we will not add this ts + metadata, + origin, + ...(!!isMerge + ? { + [IS_MERGE_FIELD]: true, + } + : {}), + }; + + return args; + }); + } + + /** + * Insert a single record to the dataset. The record will be batched and uploaded behind the scenes. If you pass in an `id`, + * and a record with that `id` already exists, it will be overwritten (upsert). + * + * @param event The event to log. + * @param event.input The argument that uniquely define an input case (an arbitrary, JSON serializable object). + * @param event.expected The output of your application, including post-processing (an arbitrary, JSON serializable object). + * @param event.tags (Optional) a list of strings that you can use to filter and group records later. + * @param event.metadata (Optional) a dictionary with additional data about the test example, model outputs, or just + * about anything else that's relevant, that you can use to help find and analyze examples later. For example, you could log the + * `prompt`, example's `id`, or anything else that would be useful to slice/dice later. The values in `metadata` can be any + * JSON-serializable type, but its keys must be strings. + * @param event.origin (Optional) a reference to the source object this dataset record was derived from. + * @param event.id (Optional) a unique identifier for the event. If you don't provide one, Braintrust will generate one for you. + * @returns The `id` of the logged record. + */ + public insert({ + input, + expected, + metadata, + tags, + id, + origin, + }: { + readonly input?: unknown; + readonly expected?: unknown; + readonly tags?: string[]; + readonly metadata?: Record; + readonly id?: string; + readonly origin?: ObjectReference; + }): string { + this.validateEvent({ metadata, tags }); + + const rowId = id || uuidv4(); + const args = this.createArgs( + deepCopyEvent({ + id: rowId, + input, + expected, + metadata, + tags, + origin, + isMerge: false, + }), + ); + + this.state.bgLogger().log([args]); + this.newRecords++; + return rowId; + } + + /** + * Update fields of a single record in the dataset. The updated fields will be batched and uploaded behind the scenes. + * You must pass in an `id` of the record to update. Only the fields provided will be updated; other fields will remain unchanged. + * + * @param event The fields to update in the record. + * @param event.id The unique identifier of the record to update. + * @param event.input (Optional) The new input value for the record (an arbitrary, JSON serializable object). + * @param event.expected (Optional) The new expected output value for the record (an arbitrary, JSON serializable object). + * @param event.tags (Optional) A list of strings to update the tags of the record. + * @param event.metadata (Optional) A dictionary to update the metadata of the record. The values in `metadata` can be any + * JSON-serializable type, but its keys must be strings. + * @returns The `id` of the updated record. + */ + public update({ + input, + expected, + metadata, + tags, + id, + }: { + readonly id: string; + readonly input?: unknown; + readonly expected?: unknown; + readonly tags?: string[]; + readonly metadata?: Record; + }): string { + this.validateEvent({ metadata, tags }); + + const args = this.createArgs( + deepCopyEvent({ + id, + input, + expected, + metadata, + tags, + isMerge: true, + }), + ); + + this.state.bgLogger().log([args]); + return id; + } + + public delete(id: string): string { + const args = new LazyValue(async () => ({ + id, + dataset_id: await this.id, + created: new Date().toISOString(), + _object_delete: true, + })); + + this.state.bgLogger().log([args]); + return id; + } + + public async createSnapshot({ + name, + description, + update, + }: { + readonly name: string; + readonly description?: string; + readonly update?: boolean; + }): Promise { + await this.flush(); + const state = await this.getState(); + const datasetId = await this.id; + // Live-row versions omit deletions. After flushing, get a server transaction + // boundary that includes every completed write, even if no live rows remain. + // getState() above also resolves versions pinned by snapshot or environment. + const currentVersion = + this.getPinnedVersion() ?? + (await (await state.apiConn().get("xact-id")).text()); + const response = await state + .appConn() + .post_json("api/dataset_snapshot/register", { + dataset_id: datasetId, + dataset_snapshot_name: name, + description, + xact_id: currentVersion, + update, + }); + return datasetSnapshotRegisterResponseSchema.parse(response) + .dataset_snapshot; + } + + public async listSnapshots(): Promise { + const state = await this.getState(); + return await getDatasetSnapshots({ + state, + datasetId: await this.id, + }); + } + + public async getSnapshot( + lookup: DatasetSnapshotLookup, + ): Promise { + const state = await this.getState(); + const datasetId = await this.id; + return await getDatasetSnapshot({ + state, + datasetId, + ...lookup, + }); + } + + public async updateSnapshot( + snapshotId: string, + { + name, + description, + }: { + readonly name?: string; + readonly description?: string | null; + }, + ): Promise { + const state = await this.getState(); + return datasetSnapshotSchema.parse( + await state.appConn().post_json("api/dataset_snapshot/patch_id", { + id: snapshotId, + name, + description, + }), + ); + } + + public async deleteSnapshot(snapshotId: string): Promise { + const state = await this.getState(); + return datasetSnapshotSchema.parse( + await state.appConn().post_json("api/dataset_snapshot/delete_id", { + id: snapshotId, + }), + ); + } + + public async restorePreview({ + version, + }: { + readonly version: string; + }): Promise { + await this.flush(); + const state = await this.getState(); + const datasetId = await this.id; + return datasetRestorePreviewResultSchema.parse( + await state + .apiConn() + .post_json(`v1/dataset/${datasetId}/restore/preview`, { + version, + }), + ); + } + + public async restore({ + version, + }: { + readonly version: string; + }): Promise { + await this.flush(); + const state = await this.getState(); + const datasetId = await this.id; + return datasetRestoreResultSchema.parse( + await state.apiConn().post_json(`v1/dataset/${datasetId}/restore`, { + version, + }), + ); + } + + /** + * Summarize the dataset, including high level metrics about its size and other metadata. + * @param summarizeData Whether to summarize the data. If false, only the metadata will be returned. + * @returns `DatasetSummary` + * @returns A summary of the dataset. + */ + public async summarize( + options: { readonly summarizeData?: boolean } = {}, + ): Promise { + const { summarizeData = true } = options || {}; + + await this.flush(); + const state = await this.getState(); + const projectUrl = `${state.appPublicUrl}/app/${encodeURIComponent( + state.orgName!, + )}/p/${encodeURIComponent((await this.project).name)}`; + const datasetUrl = `${projectUrl}/datasets/${encodeURIComponent( + await this.name, + )}`; + + let dataSummary: DataSummary | undefined; + if (summarizeData) { + const rawDataSummary = z + .object({ + total_records: z.number(), + }) + .parse( + await state.apiConn().get_json( + "dataset-summary", + { + dataset_id: await this.id, + }, + 3, + ), + ); + dataSummary = { + newRecords: this.newRecords, + totalRecords: rawDataSummary.total_records, + }; + } + + return { + projectName: (await this.project).name, + datasetName: await this.name, + projectUrl, + datasetUrl, + dataSummary, + }; + } + + /** + * Flush any pending rows to the server. + */ + async flush(): Promise { + return await this.state.bgLogger().flush(); + } + + public static isDataset(data: unknown): data is Dataset { + return ( + typeof data === "object" && + data !== null && + "__braintrust_dataset_marker" in data + ); + } +} + +export type AnyDataset = Dataset; + +/** + * Summary of a dataset's data. + * + * @property totalRecords Total records in the dataset. + */ +export interface DataSummary { + /** + * New or updated records added in this session. + */ + newRecords: number; + /** + * Total records in the dataset. + */ + totalRecords: number; +} + +/** + * Summary of a dataset's scores and metadata. + * + * @property projectName Name of the project that the dataset belongs to. + * @property datasetName Name of the dataset. + * @property projectUrl URL to the project's page in the Braintrust app. + * @property datasetUrl URL to the experiment's page in the Braintrust app. + * @property dataSummary Summary of the dataset's data. + */ +export interface DatasetSummary { + projectName: string; + datasetName: string; + projectUrl: string; + datasetUrl: string; + dataSummary: DataSummary | undefined; +} diff --git a/js/src/logger/experiments.ts b/js/src/logger/experiments.ts new file mode 100644 index 000000000..14dd4c083 --- /dev/null +++ b/js/src/logger/experiments.ts @@ -0,0 +1,849 @@ +/// +import { debugLogger } from "../debug-logger"; +import { + ExperimentEvent, + ExperimentLogFullArgs, + LogFeedbackFullArgs, + mergeGitMetadataSettings, + SpanObjectTypeV3, + SpanTypeAttribute, + isObject, +} from "../util"; +import type { GitMetadataSettings, RepoInfo } from "../sdk-types"; +import iso from "#braintrust-platform"; +import { + getCurrentUnixTimestamp, + isEmpty, + LazyValue, + runCatchFinally, +} from "../util"; +import { + FullLoginOptions, + BraintrustState, + _internalGetGlobalState, + ProjectExperimentMetadata, + validateTags, + Exportable, + enrichAttachments, + ObjectMetadata, + validateAndSanitizeExperimentLogFullArgs, + Span, + StartSpanArgs, + SetCurrentArg, + withCurrent, + logError, + SpanImpl, + startSpanParentArgs, + FailedHTTPResponse, + logFeedbackImpl, + updateSpanImpl, + getSpanComponentsClass, + DefaultMetadataType, + EvalCase, +} from "./core"; +import { + AnyDataset, + DatasetRef, + Dataset, + serializeDatasetForExperiment, +} from "./datasets"; +import { RemoteEvalParameters } from "./prompts"; +import { ObjectFetcher } from "./objects"; + +type InitOpenOption = { + open?: IsOpen; +}; + +export interface ParametersRef { + id: string; + version?: string; +} + +export type InitOptions = FullLoginOptions & { + experiment?: string; + description?: string; + dataset?: AnyDataset | DatasetRef; + _internal_btql?: Record; + parameters?: ParametersRef | RemoteEvalParameters; + update?: boolean; + baseExperiment?: string; + isPublic?: boolean; + metadata?: Record; + tags?: string[]; + gitMetadataSettings?: GitMetadataSettings; + projectId?: string; + baseExperimentId?: string; + repoInfo?: RepoInfo; + setCurrent?: boolean; + state?: BraintrustState; +} & InitOpenOption; + +export type FullInitOptions = { + project?: string; +} & InitOptions; + +function getExperimentDatasetFilter({ + dataset, + _internal_btql, +}: { + dataset?: AnyDataset | DatasetRef; + _internal_btql?: Record; +}): Record | undefined { + if (_internal_btql !== undefined) { + return _internal_btql; + } + + if (!(dataset instanceof Dataset)) { + return undefined; + } + + const datasetFilter = Reflect.get(dataset, "_internal_btql"); + return isObject(datasetFilter) ? datasetFilter : undefined; +} + +type InitializedExperiment = + IsOpen extends true ? ReadonlyExperiment : Experiment; + +/** + * Log in, and then initialize a new experiment in a specified project. If the project does not exist, it will be created. + * + * @param options Options for configuring init(). + * @param options.project The name of the project to create the experiment in. Must specify at least one of `project` or `projectId`. + * @param options.experiment The name of the experiment to create. If not specified, a name will be generated automatically. + * @param options.description An optional description of the experiment. + * @param options.dataset (Optional) A dataset to associate with the experiment. You can pass in the name of the dataset (in the same project) or a dataset object (from any project). + * @param options.update If the experiment already exists, continue logging to it. If it does not exist, creates the experiment with the specified arguments. + * @param options.baseExperiment An optional experiment name to use as a base. If specified, the new experiment will be summarized and compared to this experiment. Otherwise, it will pick an experiment by finding the closest ancestor on the default (e.g. main) branch. + * @param options.isPublic An optional parameter to control whether the experiment is publicly visible to anybody with the link or privately visible to only members of the organization. Defaults to private. + * @param options.appUrl The URL of the Braintrust App. Defaults to https://www.braintrust.dev. + * @param options.apiKey The API key to use. If the parameter is not specified, will try to use the `BRAINTRUST_API_KEY` environment variable. In Node.js, if that is unset, will try the nearest `.env.braintrust` file in the current working directory or parent directories. If no API key is specified, will prompt the user to login. + * @param options.orgName (Optional) The name of a specific organization to connect to. This is useful if you belong to multiple. + * @param options.metadata (Optional) A dictionary with additional data about the test example, model outputs, or just about anything else that's relevant, that you can use to help find and analyze examples later. For example, you could log the `prompt`, example's `id`, or anything else that would be useful to slice/dice later. The values in `metadata` can be any JSON-serializable type, but its keys must be strings. + * @param options.gitMetadataSettings (Optional) Settings for collecting git metadata. By default, Braintrust collects the git metadata fields allowed by your organization's git metadata settings. If those settings are absent, git metadata is not collected unless this option is set. + * @param setCurrent If true (the default), set the global current-experiment to the newly-created one. + * @param options.open If the experiment already exists, open it in read-only mode. Throws an error if the experiment does not already exist. + * @param options.projectId The id of the project to create the experiment in. This takes precedence over `project` if specified. + * @param options.baseExperimentId An optional experiment id to use as a base. If specified, the new experiment will be summarized and compared to this. This takes precedence over `baseExperiment` if specified. + * @param options.repoInfo (Optional) Explicitly specify the git metadata for this experiment. This takes precedence over `gitMetadataSettings` if specified. + * @param options.tags (Optional) A list of tags to attach to the experiment. + * @returns The newly created Experiment. + */ +export function init( + options: Readonly>, +): InitializedExperiment { + const { + project, + experiment, + description, + dataset, + _internal_btql, + parameters, + baseExperiment, + isPublic, + open, + update, + appUrl, + apiKey, + orgName, + forceLogin, + fetch, + metadata, + tags, + gitMetadataSettings, + projectId, + baseExperimentId, + repoInfo, + state: stateArg, + } = options; + + if (!project && !projectId) { + throw new Error("Must specify at least one of project or projectId"); + } + + if (open && update) { + throw new Error("Cannot open and update an experiment at the same time"); + } + + const state = stateArg ?? _internalGetGlobalState(); + state._internalSetTraceContextSigningSecret(apiKey); + + // Ensure unlimited queue for init() calls (experiments) + // Experiments should never drop data + state.enforceQueueSizeLimit(false); + + if (open) { + if (isEmpty(experiment)) { + throw new Error(`Cannot open an experiment without specifying its name`); + } + + const lazyMetadata: LazyValue = new LazyValue( + async () => { + await state.login({ apiKey, appUrl, orgName, fetch, forceLogin }); + const args: Record = { + project_name: project, + project_id: projectId, + org_name: state.orgName, + experiment_name: experiment, + }; + + const response = await state + .appConn() + .post_json("api/experiment/get", args); + + if (response.length === 0) { + throw new Error( + `Experiment ${experiment} not found in project ${ + projectId ?? project + }.`, + ); + } + + const info = response[0]; + return { + project: { + id: info.project_id, + name: project ?? "UNKNOWN_PROJECT", + fullInfo: {}, + }, + experiment: { + id: info.id, + name: info.name, + fullInfo: info, + }, + }; + }, + ); + + return new ReadonlyExperiment( + stateArg ?? _internalGetGlobalState(), + lazyMetadata, + ) as InitializedExperiment; + } + + const lazyMetadata: LazyValue = new LazyValue( + async () => { + await state.login({ apiKey, appUrl, orgName }); + const args: Record = { + project_name: project, + project_id: projectId, + org_id: state.orgId, + update, + }; + + if (experiment) { + args["experiment_name"] = experiment; + } + + if (description) { + args["description"] = description; + } + + const repoInfoArg = await (async (): Promise => { + if (repoInfo) { + return repoInfo; + } + const mergedGitMetadataSettings = + state.gitMetadataSettings == null + ? (gitMetadataSettings ?? { collect: "none" as const }) + : mergeGitMetadataSettings( + state.gitMetadataSettings, + gitMetadataSettings ?? { collect: "all" as const }, + ); + return await iso.getRepoInfo(mergedGitMetadataSettings); + })(); + + if (repoInfoArg) { + args["repo_info"] = repoInfoArg; + } + + if (baseExperimentId) { + args["base_exp_id"] = baseExperimentId; + } else if (baseExperiment) { + args["base_experiment"] = baseExperiment; + } else { + args["ancestor_commits"] = await iso.getPastNAncestors(); + } + + if (dataset !== undefined) { + const datasetSelection = await serializeDatasetForExperiment({ + dataset, + state, + }); + args["dataset_id"] = datasetSelection.datasetId; + if (datasetSelection.datasetVersion !== undefined) { + args["dataset_version"] = datasetSelection.datasetVersion; + } + } + + const datasetFilter = getExperimentDatasetFilter({ + dataset, + _internal_btql, + }); + if (datasetFilter !== undefined) { + args["internal_metadata"] = { + dataset_filter: datasetFilter, + }; + } + + if (parameters !== undefined) { + if (RemoteEvalParameters.isParameters(parameters)) { + args["parameters_id"] = parameters.id; + args["parameters_version"] = parameters.version; + } else { + args["parameters_id"] = parameters.id; + if (parameters.version !== undefined) { + args["parameters_version"] = parameters.version; + } + } + } + + if (isPublic !== undefined) { + args["public"] = isPublic; + } + + if (metadata) { + args["metadata"] = metadata; + } + + if (tags) { + validateTags(tags); + args["tags"] = tags; + } + + let response = null; + while (true) { + try { + response = await state + .appConn() + .post_json("api/experiment/register", args); + break; + } catch (e: any) { + if ( + args["base_experiment"] && + `${"data" in e && e.data}`.includes("base experiment") + ) { + debugLogger + .forState(state) + .warn(`Base experiment ${args["base_experiment"]} not found.`); + delete args["base_experiment"]; + } else { + throw e; + } + } + } + + return { + project: { + id: response.project.id, + name: response.project.name, + fullInfo: response.project, + }, + experiment: { + id: response.experiment.id, + name: response.experiment.name, + created: response.experiment.created, + fullInfo: response.experiment, + }, + }; + }, + ); + + const ret = new Experiment( + state, + lazyMetadata, + dataset !== undefined ? (dataset as AnyDataset) : undefined, + ); + if (options.setCurrent ?? true) { + state.currentExperiment = ret; + } + return ret as InitializedExperiment; +} + +/** + * An experiment is a collection of logged events, such as model inputs and outputs, which represent + * a snapshot of your application at a particular point in time. An experiment is meant to capture more + * than just the model you use, and includes the data you use to test, pre- and post- processing code, + * comparison metrics (scores), and any other metadata you want to include. + * + * Experiments are associated with a project, and two experiments are meant to be easily comparable via + * their `inputs`. You can change the attributes of the experiments in a project (e.g. scoring functions) + * over time, simply by changing what you log. + * + * You should not create `Experiment` objects directly. Instead, use the `braintrust.init()` method. + */ +export class Experiment + extends ObjectFetcher + implements Exportable +{ + private readonly lazyMetadata: LazyValue; + public readonly dataset?: AnyDataset; + private lastStartTime: number; + private lazyId: LazyValue; + private calledStartSpan: boolean; + private state: BraintrustState; + + // For type identification. + public kind = "experiment" as const; + /** @internal Cross-bundle identification without importing experiments into tracing. */ + public readonly __braintrust_experiment_marker = true; + + constructor( + state: BraintrustState, + lazyMetadata: LazyValue, + dataset?: AnyDataset, + ) { + super("experiment", undefined, (r) => enrichAttachments(r, state)); + this.lazyMetadata = lazyMetadata; + this.dataset = dataset; + this.lastStartTime = getCurrentUnixTimestamp(); + this.lazyId = new LazyValue(() => this.id); + this.calledStartSpan = false; + this.state = state; + } + + public get id(): Promise { + return (async () => { + return (await this.lazyMetadata.get()).experiment.id; + })(); + } + + public get loggingState(): BraintrustState { + return this.state; + } + + /** + * Wait for the experiment ID to be resolved. This is useful for ensuring the ID + * is available synchronously in child spans (for OTEL parent attributes). + * @internal + */ + public async _waitForId(): Promise { + await this.lazyId.get().catch(() => { + // Ignore errors + }); + } + + public get name(): Promise { + return (async () => { + return (await this.lazyMetadata.get()).experiment.name; + })(); + } + + public get project(): Promise { + return (async () => { + return (await this.lazyMetadata.get()).project; + })(); + } + + public async _getBaseExperimentId(): Promise { + const baseExperimentId = (await this.lazyMetadata.get()).experiment + .fullInfo["base_exp_id"]; + return typeof baseExperimentId === "string" && baseExperimentId + ? baseExperimentId + : undefined; + } + + private parentObjectType() { + return SpanObjectTypeV3.EXPERIMENT; + } + + protected async getState(): Promise { + // Ensure the login state is populated by awaiting lazyMetadata. + await this.lazyMetadata.get(); + return this.state; + } + + /** + * Log a single event to the experiment. The event will be batched and uploaded behind the scenes. + * + * @param event The event to log. + * @param event.input: The arguments that uniquely define a test case (an arbitrary, JSON serializable object). Later on, Braintrust will use the `input` to know whether two test cases are the same between experiments, so they should not contain experiment-specific state. A simple rule of thumb is that if you run the same experiment twice, the `input` should be identical. + * @param event.output: The output of your application, including post-processing (an arbitrary, JSON serializable object), that allows you to determine whether the result is correct or not. For example, in an app that generates SQL queries, the `output` should be the _result_ of the SQL query generated by the model, not the query itself, because there may be multiple valid queries that answer a single question. + * @param event.expected: (Optional) The ground truth value (an arbitrary, JSON serializable object) that you'd compare to `output` to determine if your `output` value is correct or not. Braintrust currently does not compare `output` to `expected` for you, since there are so many different ways to do that correctly. Instead, these values are just used to help you navigate your experiments while digging into analyses. However, we may later use these values to re-score outputs or fine-tune your models. + * @param event.error: (Optional) The error that occurred, if any. If you use tracing to run an experiment, errors are automatically logged when your code throws an exception. + * @param event.scores: A dictionary of numeric values (between 0 and 1) to log. The scores should give you a variety of signals that help you determine how accurate the outputs are compared to what you expect and diagnose failures. For example, a summarization app might have one score that tells you how accurate the summary is, and another that measures the word similarity between the generated and grouth truth summary. The word similarity score could help you determine whether the summarization was covering similar concepts or not. You can use these scores to help you sort, filter, and compare experiments. + * @param event.metadata: (Optional) a dictionary with additional data about the test example, model outputs, or just about anything else that's relevant, that you can use to help find and analyze examples later. For example, you could log the `prompt`, example's `id`, or anything else that would be useful to slice/dice later. The values in `metadata` can be any JSON-serializable type, but its keys must be strings. + * @param event.metrics: (Optional) a dictionary of metrics to log. The following keys are populated automatically: "start", "end". + * @param event.id: (Optional) a unique identifier for the event. If you don't provide one, BrainTrust will generate one for you. + * @param event.dataset_record_id: (Optional) the id of the dataset record that this event is associated with. This field is required if and only if the experiment is associated with a dataset. This field is unused and will be removed in a future version. + * @param options Additional logging options + * @param options.allowConcurrentWithSpans in rare cases where you need to log at the top level separately from spans on the experiment elsewhere, set this to true. + * @returns The `id` of the logged event. + */ + public log( + event: Readonly, + options?: { allowConcurrentWithSpans?: boolean }, + ): string { + if (this.calledStartSpan && !options?.allowConcurrentWithSpans) { + throw new Error( + "Cannot run toplevel `log` method while using spans. To log to the span, call `experiment.traced` and then log with `span.log`", + ); + } + + event = validateAndSanitizeExperimentLogFullArgs(event); + const span = this.startSpanImpl({ startTime: this.lastStartTime, event }); + this.lastStartTime = span.end(); + return span.id; + } + + /** + * Create a new toplevel span underneath the experiment. The name defaults to "root". + * + * See {@link Span.traced} for full details. + */ + public traced( + callback: (span: Span) => R, + args?: StartSpanArgs & SetCurrentArg, + ): R { + const { setCurrent, ...argsRest } = args ?? {}; + const span = this.startSpan(argsRest); + + return runCatchFinally( + () => { + if (setCurrent ?? true) { + return withCurrent(span, callback); + } else { + return callback(span); + } + }, + (error) => { + logError(span, error); + throw error; + }, + () => span.end(), + ); + } + + /** + * Lower-level alternative to `traced`. This allows you to start a span yourself, and can be useful in situations + * where you cannot use callbacks. However, spans started with `startSpan` will not be marked as the "current span", + * so `currentSpan()` and `traced()` will be no-ops. If you want to mark a span as current, use `traced` instead. + * + * See {@link traced} for full details. + */ + public startSpan(args?: StartSpanArgs): Span { + this.calledStartSpan = true; + return this.startSpanImpl(args); + } + + private startSpanImpl(args?: StartSpanArgs): Span { + return new SpanImpl({ + ...args, + // Sometimes `args` gets passed directly into this function, and it contains an undefined value for `state`. + // To ensure that we always use this experiment's state, we override the `state` argument no matter what. + state: this.state, + ...startSpanParentArgs({ + state: this.state, + parent: args?.parent, + parentObjectType: this.parentObjectType(), + parentObjectId: this.lazyId, + parentComputeObjectMetadataArgs: undefined, + parentSpanIds: undefined, + propagatedEvent: args?.propagatedEvent, + }), + defaultRootType: SpanTypeAttribute.EVAL, + }); + } + + public async fetchBaseExperiment(): Promise<{ id: any; name: any } | null> { + const state = await this.getState(); + const conn = state.appConn(); + + try { + const resp = await conn.post("/api/base_experiment/get_id", { + id: await this.id, + }); + + const base = await resp.json(); + return { + id: base["base_exp_id"], + name: base["base_exp_name"], + }; + } catch (e) { + if (e instanceof FailedHTTPResponse && e.status === 400) { + // No base experiment + return null; + } else { + throw e; + } + } + } + + /** + * Summarize the experiment, including the scores (compared to the closest reference experiment) and metadata. + * + * @param options Options for summarizing the experiment. + * @param options.summarizeScores Whether to summarize the scores. If False, only the metadata will be returned. + * @param options.comparisonExperimentId The experiment to compare against. If None, the most recent experiment on the origin's main branch will be used. + * @returns A summary of the experiment, including the scores (compared to the closest reference experiment) and metadata. + */ + public async summarize( + options: { + readonly summarizeScores?: boolean; + readonly comparisonExperimentId?: string; + } = {}, + ): Promise { + const { + summarizeScores = true, + comparisonExperimentId: comparisonExperimentIdOpt, + } = options || {}; + let comparisonExperimentId = comparisonExperimentIdOpt; + + const state = await this.getState(); + const projectUrl = `${state.appPublicUrl}/app/${encodeURIComponent( + state.orgName!, + )}/p/${encodeURIComponent((await this.project).name)}`; + const experimentUrl = `${projectUrl}/experiments/${encodeURIComponent( + await this.name, + )}`; + + let scores: Record | undefined = undefined; + let metrics: Record | undefined = undefined; + let comparisonExperimentName = undefined; + if (summarizeScores) { + await this.flush(); + if (comparisonExperimentId === undefined) { + const baseExperiment = await this.fetchBaseExperiment(); + if (baseExperiment !== null) { + comparisonExperimentId = baseExperiment.id; + comparisonExperimentName = baseExperiment.name; + } + } else { + try { + const comparisonExperiment = await state + .apiConn() + .get_json(`v1/experiment/${comparisonExperimentId}`); + if (typeof comparisonExperiment["name"] === "string") { + comparisonExperimentName = comparisonExperiment["name"]; + } + } catch { + // If the explicit comparison name lookup fails, still summarize by ID. + } + } + + try { + const results = await state.apiConn().get_json( + "/experiment-comparison2", + { + experiment_id: await this.id, + base_experiment_id: comparisonExperimentId, + }, + 3, + ); + + scores = results["scores"]; + metrics = results["metrics"]; + } catch (e) { + debugLogger + .forState(state) + .warn( + `Failed to fetch experiment scores and metrics: ${e}\n\nView complete results in Braintrust or run experiment.summarize() again.`, + ); + scores = {}; + metrics = {}; + } + } + + return { + projectName: (await this.project).name, + experimentName: await this.name, + projectId: (await this.project).id, + experimentId: await this.id, + projectUrl: projectUrl, + experimentUrl: experimentUrl, + comparisonExperimentName: comparisonExperimentName, + scores: scores ?? {}, + metrics: metrics ?? {}, + }; + } + + /** + * Log feedback to an event in the experiment. Feedback is used to save feedback scores, set an expected value, or add a comment. + * + * @param event + * @param event.id The id of the event to log feedback for. This is the `id` returned by `log` or accessible as the `id` field of a span. + * @param event.scores (Optional) a dictionary of numeric values (between 0 and 1) to log. These scores will be merged into the existing scores for the event. + * @param event.expected (Optional) the ground truth value (an arbitrary, JSON serializable object) that you'd compare to `output` to determine if your `output` value is correct or not. + * @param event.comment (Optional) an optional comment string to log about the event. + * @param event.metadata (Optional) a dictionary with additional data about the feedback. If you have a `user_id`, you can log it here and access it in the Braintrust UI. Note, this metadata does not correspond to the main event itself, but rather the audit log attached to the event. + * @param event.source (Optional) the source of the feedback. Must be one of "external" (default), "app", or "api". + */ + public logFeedback(event: LogFeedbackFullArgs): void { + logFeedbackImpl(this.state, this.parentObjectType(), this.lazyId, event); + } + + /** + * Update a span in the experiment using its id. It is important that you only update a span once the original span has been fully written and flushed, + * since otherwise updates to the span may conflict with the original span. + * + * @param event The event data to update the span with. Must include `id`. See {@link Experiment.log} for a full list of valid fields. + */ + public updateSpan( + event: Omit, "id"> & + Required>, + ): void { + const { id, root_span_id, span_id, ...eventRest } = event; + if (!id) { + throw new Error("Span id is required to update a span"); + } + updateSpanImpl({ + state: this.state, + parentObjectType: this.parentObjectType(), + parentObjectId: this.lazyId, + id, + root_span_id, + span_id, + event: eventRest, + }); + } + + /** + * Return a serialized representation of the experiment that can be used to start subspans in other places. + * + * See {@link Span.startSpan} for more details. + */ + public async export(): Promise { + return new (getSpanComponentsClass())({ + object_type: this.parentObjectType(), + object_id: await this.id, + }).toStr(); + } + + /** + * Return this experiment's Braintrust parent string (`experiment_id:`) for + * the `braintrust.parent` baggage entry, or undefined when it cannot be + * determined synchronously. + */ + public _getOtelParent(): string | undefined { + const id = this.lazyId.getSync().value; + return id ? `experiment_id:${id}` : undefined; + } + + /** + * Flush any pending rows to the server. + */ + async flush(): Promise { + return await this.state.bgLogger().flush(); + } +} + +/** + * A read-only view of an experiment, initialized by passing `open: true` to `init()`. + */ +export class ReadonlyExperiment extends ObjectFetcher { + constructor( + private state: BraintrustState, + private readonly lazyMetadata: LazyValue, + ) { + super("experiment", undefined, (r) => enrichAttachments(r, state)); + } + + public get id(): Promise { + return (async () => { + return (await this.lazyMetadata.get()).experiment.id; + })(); + } + + public get name(): Promise { + return (async () => { + return (await this.lazyMetadata.get()).experiment.name; + })(); + } + + public get loggingState(): BraintrustState { + return this.state; + } + + protected async getState(): Promise { + // Ensure the login state is populated by awaiting lazyMetadata. + await this.lazyMetadata.get(); + return this.state; + } + + public async *asDataset< + Input, + Expected, + Metadata = DefaultMetadataType, + >(options?: { + batchSize?: number; + }): AsyncGenerator> { + const records = this.fetch(options); + + for await (const record of records) { + const isRoot = record.is_root ?? record.root_span_id === record.span_id; + if (!isRoot) { + continue; + } + + const { output, expected: expectedRecord, metadata } = record; + const expected = (expectedRecord ?? output) as Expected; + + // Note: We always include expected and metadata fields to maintain type signature alignment. + // This ensures that when the type signature includes `| null | undefined`, the fields + // are still present in the runtime object. While this may incorrectly include fields + // when Metadata/Expected = void, it's preferable to incorrectly excluding them when + // the type signature expects them to be present. + yield { + input: record.input as Input, + tags: record.tags, + expected: expected as Expected, + metadata: metadata as Metadata, + } as unknown as EvalCase; + } + } +} + +/** + * Summary of a score's performance. + * @property name Name of the score. + * @property score Average score across all examples. + * @property diff Difference in score between the current and reference experiment. + * @property improvements Number of improvements in the score. + * @property regressions Number of regressions in the score. + */ +export interface ScoreSummary { + name: string; + score: number; + diff?: number; + improvements: number; + regressions: number; +} + +/** + * Summary of a metric's performance. + * @property name Name of the metric. + * @property metric Average metric across all examples. + * @property unit Unit label for the metric. + * @property diff Difference in metric between the current and reference experiment. + * @property improvements Number of improvements in the metric. + * @property regressions Number of regressions in the metric. + */ +export interface MetricSummary { + name: string; + metric: number; + unit: string; + diff?: number; + improvements: number; + regressions: number; +} + +/** + * Summary of an experiment's scores and metadata. + * @property projectName Name of the project that the experiment belongs to. + * @property experimentName Name of the experiment. + * @property experimentId ID of the experiment. May be `undefined` if the eval was run locally. + * @property projectUrl URL to the project's page in the Braintrust app. + * @property experimentUrl URL to the experiment's page in the Braintrust app. + * @property comparisonExperimentName The experiment scores are baselined against. + * @property scores Summary of the experiment's scores. + */ +export interface ExperimentSummary { + projectName: string; + experimentName: string; + projectId?: string; + experimentId?: string; + projectUrl?: string; + experimentUrl?: string; + comparisonExperimentName?: string; + scores: Record; + metrics?: Record; +} diff --git a/js/src/logger/lifecycle.test.ts b/js/src/logger/lifecycle.test.ts new file mode 100644 index 000000000..1dd0613a8 --- /dev/null +++ b/js/src/logger/lifecycle.test.ts @@ -0,0 +1,30 @@ +import { expect, test, vi } from "vite-plus/test"; + +test("flush does not initialize an unused SDK", async () => { + const key = Symbol.for("braintrust-state"); + const globals = globalThis as typeof globalThis & { [key: symbol]: unknown }; + const previous = globals[key]; + delete globals[key]; + vi.resetModules(); + try { + const sdk = await import("./core"); + await sdk.flush(); + expect(globals[key]).toBeUndefined(); + } finally { + if (previous !== undefined) globals[key] = previous; + } +}); + +test("flush finds state initialized by another SDK copy", async () => { + const first = await import("./core"); + const logger = first._internalGetGlobalState().bgLogger(); + const flush = vi.spyOn(logger, "flush").mockResolvedValue(undefined); + vi.resetModules(); + try { + const second = await import("./core"); + await second.flush(); + expect(flush).toHaveBeenCalledOnce(); + } finally { + flush.mockRestore(); + } +}); diff --git a/js/src/logger/objects.ts b/js/src/logger/objects.ts new file mode 100644 index 000000000..76f7d4b94 --- /dev/null +++ b/js/src/logger/objects.ts @@ -0,0 +1,207 @@ +/// +import { TRANSACTION_ID_FIELD, TransactionId } from "../util"; +import { BraintrustState, BTQL_HTTP_RETRIES } from "./core"; + +export type WithTransactionId = R & { + [TRANSACTION_ID_FIELD]: TransactionId; +}; + +export const DEFAULT_FETCH_BATCH_SIZE = 1000; + +const MAX_BTQL_ITERATIONS = 10000; + +export class ObjectFetcher implements AsyncIterable< + WithTransactionId +> { + private _fetchedData: WithTransactionId[] | undefined = undefined; + + constructor( + private objectType: + | "dataset" + | "experiment" + | "project_logs" + | "playground_logs", + private pinnedVersion: string | undefined, + // eslint-disable-next-line @typescript-eslint/no-explicit-any + private mutateRecord?: (r: any) => WithTransactionId, + private _internal_btql?: Record, + private _internalBrainstoreRealtime = true, + ) {} + + public get id(): Promise { + throw new Error("ObjectFetcher subclasses must have an 'id' attribute"); + } + + protected async getState(): Promise { + throw new Error("ObjectFetcher subclasses must have a 'getState' method"); + } + + protected getPinnedVersion(): string | undefined { + return this.pinnedVersion; + } + + protected setPinnedVersion(pinnedVersion: string | undefined): void { + this.pinnedVersion = pinnedVersion; + } + + protected getInternalBtql(): Record | undefined { + return this._internal_btql; + } + + private async *fetchRecordsFromApi( + batchSize: number | undefined, + ): AsyncGenerator> { + const state = await this.getState(); + const objectId = await this.id; + const batchLimit = batchSize ?? DEFAULT_FETCH_BATCH_SIZE; + const internalLimit = getInternalBtqlLimit(this._internal_btql); + let remainingLimit = internalLimit; + const internalBtqlWithoutReservedQueryKeys = Object.fromEntries( + Object.entries(this._internal_btql ?? {}).filter( + ([key]) => + key !== "cursor" && + key !== "limit" && + key !== "select" && + key !== "from", + ), + ); + let cursor = undefined; + let iterations = 0; + while (true) { + if (remainingLimit !== undefined && remainingLimit <= 0) { + return; + } + const limit = + remainingLimit === undefined + ? batchLimit + : Math.min(batchLimit, remainingLimit); + const resp = await state.apiConn().post( + `btql`, + { + query: { + select: [ + { + op: "star", + }, + ], + from: { + op: "function", + name: { + op: "ident", + name: [this.objectType], + }, + args: [ + { + op: "literal", + value: objectId, + }, + ], + }, + cursor, + limit, + ...internalBtqlWithoutReservedQueryKeys, + }, + use_columnstore: false, + brainstore_realtime: this._internalBrainstoreRealtime, + query_source: `js_sdk_object_fetcher_${this.objectType}`, + ...(this.pinnedVersion !== undefined + ? { + version: this.pinnedVersion, + } + : {}), + }, + { headers: { "Accept-Encoding": "gzip" } }, + BTQL_HTTP_RETRIES, + ); + const respJson = await resp.json(); + const mutate = this.mutateRecord; + for (const record of respJson.data ?? []) { + if (remainingLimit !== undefined && remainingLimit <= 0) { + return; + } + const mutatedRecord = mutate + ? mutate(record) + : (record as WithTransactionId); + if (remainingLimit !== undefined) { + remainingLimit--; + } + yield mutatedRecord; + } + if (!respJson.cursor) { + break; + } + cursor = respJson.cursor; + iterations++; + if (iterations > MAX_BTQL_ITERATIONS) { + throw new Error("Too many BTQL iterations"); + } + } + } + + /** + * Fetch all records from the object. + * + * @param options Optional parameters for fetching. + * @param options.batchSize The number of records to fetch per request. Defaults to 1000. + * @returns An async generator of records. + */ + async *fetch(options?: { + batchSize?: number; + }): AsyncGenerator> { + if (this._fetchedData !== undefined) { + for (const record of this._fetchedData) { + yield record; + } + return; + } + + for await (const record of this.fetchRecordsFromApi(options?.batchSize)) { + yield record; + } + } + + [Symbol.asyncIterator](): AsyncIterator> { + return this.fetch(); + } + + async fetchedData(options?: { + batchSize?: number; + }): Promise[]> { + if (this._fetchedData === undefined) { + const data: WithTransactionId[] = []; + for await (const record of this.fetchRecordsFromApi(options?.batchSize)) { + data.push(record); + } + this._fetchedData = data; + } + return this._fetchedData || []; + } + + clearCache(): void { + this._fetchedData = undefined; + } + + public async version(options?: { + batchSize?: number; + }): Promise { + if (this.pinnedVersion !== undefined) { + return this.pinnedVersion; + } else { + let maxVersion: string | undefined = undefined; + for await (const record of this.fetch(options)) { + const xactId = String(record[TRANSACTION_ID_FIELD] ?? "0"); + if (maxVersion === undefined || xactId > maxVersion) { + maxVersion = xactId; + } + } + return maxVersion; + } + } +} + +function getInternalBtqlLimit( + internalBtql?: Record, +): number | undefined { + const limit = internalBtql?.["limit"]; + return typeof limit === "number" ? limit : undefined; +} diff --git a/js/src/logger/prompts.ts b/js/src/logger/prompts.ts new file mode 100644 index 000000000..3a1a77d3c --- /dev/null +++ b/js/src/logger/prompts.ts @@ -0,0 +1,1506 @@ +import { createCacheLayers } from "../prompt-cache/cache-config"; +import { + PromptCache, + type PromptDiskCacheEntry, + type PromptMemoryCacheEntry, +} from "../prompt-cache/prompt-cache"; +import { + ParametersCache, + type ParametersDiskCacheEntry, + type ParametersMemoryCacheEntry, +} from "../prompt-cache/parameters-cache"; +import iso from "#braintrust-platform"; +import type { ResolvedLoaderLoginOptions } from "./core"; +/// +import { debugLogger } from "../debug-logger"; +import { + TRANSACTION_ID_FIELD, + TransactionId, + isArray, + isObject, + getObjValueByPath, +} from "../util"; +import { + BraintrustAttachmentReference as BraintrustAttachmentReferenceSchema, + ChatCompletionTool as chatCompletionToolSchema, + ExternalAttachmentReference as ExternalAttachmentReferenceSchema, + PromptData as promptDataSchema, + Prompt as promptSchema, +} from "../sdk-schemas"; +import type { + ChatCompletionTool, + ModelParams, + ChatCompletionMessageParam as Message, + ChatCompletionOpenAIMessageParam as OpenAIMessage, + PromptData, + FunctionTypeEnum, + PromptSessionEvent, + PromptBlockData, +} from "../sdk-types"; +import { + parseTemplateFormat, + renderTemplateContent, +} from "../template/renderer"; +import type { TemplateFormat } from "../template/registry"; +import { z } from "zod/v3"; +import { isEmpty } from "../util"; +import { prettifyXact } from "../../util/index"; +import type { EvalParameters, InferParameters } from "../eval-parameters"; +import { + FullLoginOptions, + BraintrustState, + LoaderRequestState, + _internalGetGlobalState, + isLoaderCacheFallbackError, + InlineAttachmentReferenceSchema, + resolveAttachmentsToBase64, + ReadonlyAttachment, + BTQL_HTTP_RETRIES, +} from "./core"; + +const BRAINTRUST_PARAMS = [ + "use_cache", + "reasoning_enabled", + "reasoning_budget", +]; + +const parametersRowSchema = z.object({ + id: z.string().uuid(), + _xact_id: z.string(), + project_id: z.string().uuid(), + name: z.string(), + slug: z.string(), + description: z.union([z.string(), z.null()]).optional(), + function_type: z.literal("parameters"), + function_data: z.object({ + type: z.literal("parameters"), + data: z.record(z.unknown()).optional(), + __schema: z.record(z.unknown()), + }), + metadata: z + .union([z.object({}).partial().passthrough(), z.null()]) + .optional(), +}); + +export type ParametersRow = { + id: string; + _xact_id: string; + project_id: string; + name: string; + slug: string; + description?: string | null; + function_type: "parameters"; + function_data: { + type: "parameters"; + data?: Record; + __schema: Record; + }; + metadata?: Record | null; +}; + +type LoadPromptOptions = FullLoginOptions & { + projectName?: string; + projectId?: string; + slug?: string; + version?: string; + id?: string; + defaults?: DefaultPromptArgs; + noTrace?: boolean; + environment?: string; + state?: BraintrustState; +}; + +type LoadParametersBaseOptions = FullLoginOptions & { + state?: BraintrustState; +}; + +type LoadParametersByIdOptions = LoadParametersBaseOptions & { + id: string; + version?: string; +}; + +type LoadParametersByIdWithEnvOptions = LoadParametersBaseOptions & { + id: string; + environment: string; + version?: string; +}; + +type LoadParametersByProjectNameOptions = LoadParametersBaseOptions & { + projectName: string; + slug: string; + version?: string; +}; + +type LoadParametersByProjectNameWithEnvOptions = LoadParametersBaseOptions & { + projectName: string; + slug: string; + environment: string; + version?: string; +}; + +type LoadParametersByProjectIdOptions = LoadParametersBaseOptions & { + projectId: string; + slug: string; + version?: string; +}; + +type LoadParametersByProjectIdWithEnvOptions = LoadParametersBaseOptions & { + projectId: string; + slug: string; + environment: string; + version?: string; +}; + +type LoadParametersImplementationOptions = LoadParametersBaseOptions & { + projectName?: string; + projectId?: string; + slug?: string; + version?: string; + id?: string; + environment?: string; +}; + +type LoaderRequestResult = + | ({ ok: true; response: T } & LoaderCacheViews) + | ({ ok: false; error: unknown } & LoaderCacheViews); + +async function runCredentialScopedLoaderRequest( + state: BraintrustState, + loginOptions: Pick< + FullLoginOptions, + "apiKey" | "appUrl" | "orgName" | "fetch" | "forceLogin" + >, + request: (requestState: LoaderRequestState) => Promise, +): Promise> { + const resolvedLoginOptions = + await state._internalResolveLoaderLoginOptions(loginOptions); + let cacheViews = getLoaderCacheViews(state, resolvedLoginOptions); + + try { + const requestState = + await state._internalGetLoaderState(resolvedLoginOptions); + cacheViews = getLoaderCacheViews(state, resolvedLoginOptions, requestState); + return { + ok: true, + response: await request(requestState), + ...cacheViews, + }; + } catch (error) { + return { ok: false, error, ...cacheViews }; + } +} + +/** + * Load a prompt from the specified project. + * + * @param options Options for configuring loadPrompt(). + * @param options.projectName The name of the project to load the prompt from. Must specify at least one of `projectName` or `projectId`. + * @param options.projectId The id of the project to load the prompt from. This takes precedence over `projectName` if specified. + * @param options.slug The slug of the prompt to load. + * @param options.version An optional version of the prompt (to read). If not specified, the latest version will be used. + * @param options.environment Fetch the version of the prompt assigned to the specified environment (e.g. "production", "staging"). If both `version` and `environment` are provided, `version` takes precedence. + * @param options.id The id of a specific prompt to load. If specified, this takes precedence over all other parameters (project and slug). + * @param options.defaults (Optional) A dictionary of default values to use when rendering the prompt. Prompt values will override these defaults. + * @param options.noTrace If true, do not include logging metadata for this prompt when build() is called. + * @param options.appUrl The URL of the Braintrust App. Defaults to https://www.braintrust.dev. + * @param options.apiKey The API key to use for this request, independently of any existing global login. If the parameter is not specified, will use an existing login or try the `BRAINTRUST_API_KEY` environment variable. In Node.js, + * if that is unset, will try the nearest `.env.braintrust` file in the current working directory or parent directories. If no API key is specified, will prompt the user to login. + * @param options.orgName (Optional) The name of a specific organization to connect to. This is useful if you belong to multiple. + * @returns The prompt object. + * @throws If the prompt is not found. + * @throws If multiple prompts are found with the same slug in the same project (this should never happen). + * + * @example + * ```javascript + * const prompt = await loadPrompt({ + * projectName: "My Project", + * slug: "my-prompt", + * }); + * ``` + */ +export async function loadPrompt({ + projectName, + projectId, + slug, + version, + environment, + id, + defaults, + noTrace = false, + appUrl, + apiKey, + orgName, + fetch, + forceLogin, + state: stateArg, +}: LoadPromptOptions): Promise> { + const versionOrEnvironment = version + ? { version } + : environment + ? { environment } + : {}; + if (id) { + // When loading by ID, we don't need project or slug + } else if (isEmpty(projectName) && isEmpty(projectId)) { + throw new Error("Must specify either projectName or projectId"); + } else if (isEmpty(slug)) { + throw new Error("Must specify slug"); + } + + const state = stateArg ?? _internalGetGlobalState(); + const result = await runCredentialScopedLoaderRequest( + state, + { orgName, apiKey, appUrl, fetch, forceLogin }, + async (requestState) => { + let response; + if (id) { + // Load prompt by ID using the /v1/prompt/{id} endpoint + response = await requestState + .apiConn() + .get_json(`v1/prompt/${id}`, versionOrEnvironment); + // Wrap single prompt response in objects array to match list API format + if (response) { + response = { objects: [response] }; + } + } else { + response = await requestState.apiConn().get_json("v1/prompt", { + project_name: projectName, + project_id: projectId, + slug, + ...versionOrEnvironment, + }); + } + return response; + }, + ); + const { promptCache } = result; + if (!result.ok) { + const e = result.error; + if (!isLoaderCacheFallbackError(e)) { + throw e; + } + // If environment or version was specified, don't fall back to cache + if (version || environment) { + throw new Error(`Prompt not found with specified parameters: ${e}`); + } + + debugLogger + .forState(state) + .warn("Failed to load prompt, attempting to fall back to cache:", e); + let prompt; + if (id) { + prompt = await promptCache.get({ id }); + if (!prompt) { + throw new Error( + `Prompt with id ${id} not found (not found on server or in local cache): ${e}`, + ); + } + } else { + prompt = await promptCache.get({ + slug, + projectId, + projectName, + version: version ?? "latest", + }); + if (!prompt) { + throw new Error( + `Prompt ${slug} (version ${version ?? "latest"}) not found in ${[ + projectName ?? projectId, + ]} (not found on server or in local cache): ${e}`, + ); + } + } + return prompt; + } + const { response } = result; + + if (!("objects" in response) || response.objects.length === 0) { + if (id) { + throw new Error(`Prompt with id ${id} not found.`); + } else { + throw new Error( + `Prompt ${slug} not found in ${[projectName ?? projectId]}`, + ); + } + } else if (response.objects.length > 1) { + if (id) { + throw new Error( + `Multiple prompts found with id ${id}. This should never happen.`, + ); + } else { + throw new Error( + `Multiple prompts found with slug ${slug} in project ${ + projectName ?? projectId + }. This should never happen.`, + ); + } + } + + const metadata = promptSchema.parse(response["objects"][0]); + const prompt = new Prompt(metadata, defaults || {}, noTrace); + try { + if (id) { + await promptCache.set({ id }, prompt); + } else if (slug) { + await promptCache.set( + { slug, projectId, projectName, version: version ?? "latest" }, + prompt, + ); + } + } catch (e) { + debugLogger.forState(state).warn("Failed to set prompt in cache:", e); + } + return prompt; +} + +/** + * Load parameters from the specified project. + * + * @param options Options for configuring loadParameters(). + * @param options.projectName The name of the project to load the parameters from. Must specify at least one of `projectName` or `projectId`. + * @param options.projectId The id of the project to load the parameters from. This takes precedence over `projectName` if specified. + * @param options.slug The slug of the parameters to load. + * @param options.version An optional version of the parameters (to read). If not specified, the latest version will be used. + * @param options.environment Fetch the version of the parameters assigned to the specified environment (e.g. "production", "staging"). If both `version` and `environment` are provided, `version` takes precedence. + * @param options.id The id of specific parameters to load. If specified, this takes precedence over all other parameters (project and slug). + * @param options.appUrl The URL of the Braintrust App. Defaults to https://www.braintrust.dev. + * @param options.apiKey The API key to use for this request, independently of any existing global login. If the parameter is not specified, will use an existing login or try the `BRAINTRUST_API_KEY` environment variable. In Node.js, if that is unset, will try the nearest `.env.braintrust` file in the current working directory or parent directories. + * @param options.orgName (Optional) The name of a specific organization to connect to. This is useful if you belong to multiple. + * @returns The parameters object. + * @throws If the parameters are not found. + * @throws If multiple parameters are found with the same slug in the same project (this should never happen). + * + * @example + * ```typescript + * const params = await loadParameters({ + * projectName: "My Project", + * slug: "my-parameters", + * }); + * ``` + */ +export function loadParameters( + options: LoadParametersByProjectNameOptions, +): Promise>>; + +export function loadParameters( + options: LoadParametersByProjectNameWithEnvOptions, +): Promise>>; + +export function loadParameters( + options: LoadParametersByProjectIdOptions, +): Promise>>; + +export function loadParameters( + options: LoadParametersByProjectIdWithEnvOptions, +): Promise>>; + +export function loadParameters( + options: LoadParametersByIdOptions, +): Promise>>; + +export function loadParameters( + options: LoadParametersByIdWithEnvOptions, +): Promise>>; + +export async function loadParameters< + S extends EvalParameters = EvalParameters, +>({ + projectName, + projectId, + slug, + version, + environment, + id, + appUrl, + apiKey, + orgName, + fetch, + forceLogin, + state: stateArg, +}: LoadParametersImplementationOptions): Promise< + RemoteEvalParameters> +> { + const versionOrEnvironment = version + ? { version } + : environment + ? { environment } + : {}; + if (id) { + // When loading by ID, we don't need project or slug + } else if (isEmpty(projectName) && isEmpty(projectId)) { + throw new Error("Must specify either projectName or projectId"); + } else if (isEmpty(slug)) { + throw new Error("Must specify slug"); + } + + const state = stateArg ?? _internalGetGlobalState(); + const result = await runCredentialScopedLoaderRequest( + state, + { orgName, apiKey, appUrl, fetch, forceLogin }, + async (requestState) => { + let response; + if (id) { + response = await requestState.apiConn().get_json(`v1/function/${id}`, { + ...versionOrEnvironment, + }); + if (response) { + response = { objects: [response] }; + } + } else { + response = await requestState.apiConn().get_json("v1/function", { + project_name: projectName, + project_id: projectId, + slug, + function_type: "parameters", + ...versionOrEnvironment, + }); + } + return response; + }, + ); + const { parametersCache } = result; + if (!result.ok) { + const e = result.error; + if (!isLoaderCacheFallbackError(e)) { + throw e; + } + if (version || environment) { + throw new Error(`Parameters not found with specified parameters: ${e}`); + } + + debugLogger + .forState(state) + .warn("Failed to load parameters, attempting to fall back to cache:", e); + let parameters; + if (id) { + parameters = await parametersCache.get({ id }); + if (!parameters) { + throw new Error( + `Parameters with id ${id} not found (not found on server or in local cache): ${e}`, + ); + } + } else { + parameters = await parametersCache.get({ + slug, + projectId, + projectName, + version: version ?? "latest", + }); + if (!parameters) { + throw new Error( + `Parameters ${slug} (version ${version ?? "latest"}) not found in ${[ + projectName ?? projectId, + ]} (not found on server or in local cache): ${e}`, + ); + } + } + // eslint-disable-next-line @typescript-eslint/consistent-type-assertions + return parameters as RemoteEvalParameters>; + } + const { response } = result; + + if (!("objects" in response) || response.objects.length === 0) { + if (id) { + throw new Error(`Parameters with id ${id} not found.`); + } else { + throw new Error( + `Parameters ${slug} not found in ${[projectName ?? projectId]}`, + ); + } + } else if (response.objects.length > 1) { + if (id) { + throw new Error( + `Multiple parameters found with id ${id}. This should never happen.`, + ); + } else { + throw new Error( + `Multiple parameters found with slug ${slug} in project ${ + projectName ?? projectId + }. This should never happen.`, + ); + } + } + + const metadata = parametersRowSchema.parse(response["objects"][0]); + const parameters = new RemoteEvalParameters(metadata); + try { + if (id) { + await parametersCache.set({ id }, parameters); + } else if (slug) { + await parametersCache.set( + { slug, projectId, projectName, version: version ?? "latest" }, + parameters, + ); + } + } catch (e) { + debugLogger.forState(state).warn("Failed to set parameters in cache:", e); + } + // eslint-disable-next-line @typescript-eslint/consistent-type-assertions + return parameters as RemoteEvalParameters>; +} + +export type CompiledPromptResponseFormat = + | { type: "json_object" } + | { type: "text" } + | { + type: "json_schema"; + json_schema: { + name: string; + description?: string; + schema?: Record; + strict?: boolean | null; + }; + }; + +// Keep the compiled API independent of the backend's provider parameter union. +// "none" is excluded from reasoning_effort for compatibility with older OpenAI clients. +export interface CompiledPromptParams { + chat_template_kwargs?: { enable_thinking?: boolean; [key: string]: unknown }; + model: string; + max_tokens: number; + temperature?: number; + top_p?: number; + max_completion_tokens?: number; + frequency_penalty?: number; + presence_penalty?: number; + response_format?: CompiledPromptResponseFormat; + tool_choice?: + | "auto" + | "none" + | "required" + | { + type: "function"; + function: { name: string }; + }; + function_call?: "auto" | "none" | { name: string }; + n?: number; + stop?: string[]; + reasoning_effort?: "minimal" | "low" | "medium" | "high"; + verbosity?: "low" | "medium" | "high"; + top_k?: number; + stop_sequences?: string[]; + reasoning_enabled?: boolean; + reasoning_budget?: number; + /** Legacy parameter; prefer max_tokens. */ + max_tokens_to_sample?: number; + maxOutputTokens?: number; + topP?: number; + topK?: number; +} + +export type ChatPrompt = { + messages: OpenAIMessage[]; + tools?: ChatCompletionTool[]; +}; + +export type CompletionPrompt = { + prompt: string; +}; + +export type CompiledPrompt = + CompiledPromptParams & { + span_info?: { + name?: string; + spanAttributes?: Record; + metadata: { + prompt: { + variables: Record; + id: string; + project_id: string; + version: string; + }; + }; + }; + } & (Flavor extends "chat" + ? ChatPrompt + : Flavor extends "completion" + ? CompletionPrompt + : // eslint-disable-next-line @typescript-eslint/no-empty-object-type + {}); + +export interface DefaultPromptArgs + extends + Partial, + Partial, + Partial { + use_cache?: boolean; +} + +function isAttachmentObject(value: unknown): boolean { + return ( + BraintrustAttachmentReferenceSchema.safeParse(value).success || + InlineAttachmentReferenceSchema.safeParse(value).success || + ExternalAttachmentReferenceSchema.safeParse(value).success + ); +} + +// Simple URL validation all braintrust attachment urls or generic links +function isURL(url: string): boolean { + try { + const parsedUrl = new URL(url.trim()); + return parsedUrl.protocol === "http:" || parsedUrl.protocol === "https:"; + } catch { + return false; + } +} + +/** + * Detect and expand attachment array variables before template rendering. + * Supports both simple variables ({{images}}) and nested paths ({{data.images}}). + * @param content The message content (should be a template variable like "{{images}}" or "{{data.images}}") + * @param variables The variables object with array values + * @returns Array of image_url parts if expansion occurred, null otherwise + */ +function expandAttachmentArrayPreTemplate( + content: unknown, + variables: Record, +): unknown[] | null { + if (typeof content !== "string") return null; + + // Detect {{varName}} or {{nested.path}} pattern (exact match only - no mixed content) + // Supports: {{images}}, {{data.images}}, {{user.profile.images}}, etc. + const match = content.match(/^\{\{\s*([\w.]+)\s*\}\}$/); + if (!match) return null; + + const varPath = match[1]; + + const value = varPath.includes(".") + ? getObjValueByPath(variables, varPath.split(".")) + : variables[varPath]; + + if (!Array.isArray(value)) return null; + + // Check if array contains attachments or image URLs + const allValid = value.every( + (v) => isAttachmentObject(v) || (typeof v === "string" && isURL(v)), + ); + + if (!allValid) return null; + + // Expand directly to image_url parts + return value.map((item) => ({ + type: "image_url" as const, + image_url: { url: item }, + })); +} + +export function renderMessageImpl( + render: (template: string) => string, + message: T, + variables?: Record, +): T { + return { + ...message, + ...("content" in message + ? { + content: isEmpty(message.content) + ? undefined + : typeof message.content === "string" + ? render(message.content) + : message.content.flatMap((c) => { + switch (c.type) { + case "text": + return [{ ...c, text: render(c.text) }]; + case "image_url": + if (isObject(c.image_url.url)) { + throw new Error( + "Attachments must be replaced with URLs before calling `build()`", + ); + } + + if (variables) { + const expanded = expandAttachmentArrayPreTemplate( + c.image_url.url, + variables, + ); + if (expanded) { + return expanded; + } + } + + // Otherwise render the URL template normally + return [ + { + ...c, + image_url: { + ...c.image_url, + url: render(c.image_url.url), + }, + }, + ]; + case "input_audio": + return [ + { + ...c, + input_audio: { + ...c.input_audio, + data: render(c.input_audio.data), + }, + }, + ]; + case "file": + return [ + { + ...c, + file: { + ...(c.file.file_data && { + file_data: render(c.file.file_data), + }), + ...(c.file.file_id && { + file_id: render(c.file.file_id), + }), + ...(c.file.filename && { + filename: render(c.file.filename), + }), + }, + }, + ]; + default: + const _exhaustiveCheck: never = c; + return _exhaustiveCheck; + } + }), + } + : {}), + ...("tool_calls" in message + ? { + tool_calls: isEmpty(message.tool_calls) + ? undefined + : message.tool_calls.map((t) => { + return { + type: t.type, + id: render(t.id), + function: { + name: render(t.function.name), + arguments: render(t.function.arguments), + }, + }; + }), + } + : {}), + ...("tool_call_id" in message + ? { + tool_call_id: render(message.tool_call_id), + } + : {}), + }; +} + +export interface PromptMetadata { + name: string; + slug: string; + project_id?: string; + description?: string | null; + created?: string | null; + prompt_data?: PromptData | null; + tags?: string[] | null; + // Preserve the generated prompt row's permissive metadata contract. + // eslint-disable-next-line @typescript-eslint/no-empty-object-type + metadata?: {} | null; + function_type?: FunctionTypeEnum | null; +} + +export type PromptRowWithId< + HasId extends boolean = true, + HasVersion extends boolean = true, +> = PromptMetadata & + (HasId extends true ? { id: string } : { id?: string }) & + (HasVersion extends true ? { _xact_id: string } : { _xact_id?: string }); + +function deserializePlainStringAsJSON( + s: string, +): { value: any; error: undefined } | { value: string; error: unknown } { + if (s.trim() === "") { + return { value: null, error: undefined }; + } + + try { + return { value: JSON.parse(s), error: undefined }; + } catch (e) { + return { value: s, error: e }; + } +} + +function renderTemplatedObject( + obj: unknown, + args: Record, + options: { strict?: boolean; templateFormat: TemplateFormat }, +): unknown { + if (typeof obj === "string") { + return renderTemplateContent( + obj, + args, + (value) => (typeof value === "string" ? value : JSON.stringify(value)), + { + strict: options.strict, + templateFormat: options.templateFormat, + }, + ); + } else if (isArray(obj)) { + return obj.map((item) => renderTemplatedObject(item, args, options)); + } else if (isObject(obj)) { + return Object.fromEntries( + Object.entries(obj).map(([key, value]) => [ + key, + renderTemplatedObject(value, args, options), + ]), + ); + } + return obj; +} + +function renderPromptParams( + params: ModelParams | undefined, + args: Record, + options: { strict?: boolean; templateFormat?: TemplateFormat } = {}, +): ModelParams | undefined { + const templateFormat = parseTemplateFormat(options.templateFormat); + const strict = !!options.strict; + + const schemaParsed = z + .object({ + response_format: z.object({ + type: z.literal("json_schema"), + json_schema: z.object({ + name: z.string(), + description: z.string().optional(), + strict: z.boolean().nullable().optional(), + schema: z.unknown(), + }), + }), + }) + .safeParse(params); + if (schemaParsed.success) { + const rawSchema = schemaParsed.data.response_format.json_schema.schema; + const templatedSchema = renderTemplatedObject(rawSchema, args, { + strict, + templateFormat, + }); + const parsedSchema = + typeof templatedSchema === "string" + ? deserializePlainStringAsJSON(templatedSchema).value + : templatedSchema; + + return { + ...params, + response_format: { + ...schemaParsed.data.response_format, + json_schema: { + ...schemaParsed.data.response_format.json_schema, + schema: parsedSchema, + }, + }, + }; + } + return params; +} + +export interface SerializedPrompt { + metadata: PromptRowWithId | PromptSessionEvent; + defaults: DefaultPromptArgs; + noTrace: boolean; +} + +// Initialized by the class so cache serialization can access private fields +// without exposing a serialization method on the public Prompt API. +export let serializePromptForCache: (prompt: Prompt) => SerializedPrompt; + +export class Prompt< + HasId extends boolean = true, + HasVersion extends boolean = true, +> { + private parsedPromptData: PromptData | undefined; + private hasParsedPromptData = false; + private readonly __braintrust_prompt_marker = true; + + constructor( + private metadata: PromptRowWithId | PromptSessionEvent, + private defaults: DefaultPromptArgs, + private noTrace: boolean, + ) { + void this.__braintrust_prompt_marker; + } + + public get id(): HasId extends true ? string : string | undefined { + // eslint-disable-next-line @typescript-eslint/consistent-type-assertions + return this.metadata.id as HasId extends true ? string : string | undefined; + } + + public get projectId(): string | undefined { + return this.metadata.project_id; + } + + public get name(): string { + return "name" in this.metadata + ? this.metadata.name + : `Playground function ${this.metadata.id}`; + } + + public get slug(): string { + return "slug" in this.metadata ? this.metadata.slug : this.metadata.id; + } + + public get prompt(): PromptData["prompt"] { + return this.getParsedPromptData()?.prompt; + } + + public get version(): HasId extends true + ? TransactionId + : TransactionId | undefined { + // eslint-disable-next-line @typescript-eslint/consistent-type-assertions + return this.metadata[TRANSACTION_ID_FIELD] as HasId extends true + ? TransactionId + : TransactionId | undefined; + } + + public get options(): NonNullable { + return this.getParsedPromptData()?.options || {}; + } + + public get templateFormat(): string | null | undefined { + return this.getParsedPromptData()?.template_format; + } + + public get promptData(): PromptData { + return this.getParsedPromptData()!; + } + + /** + * Build the prompt with the given formatting options. The args you pass in will + * be forwarded to the mustache template that defines the prompt and rendered with + * the `mustache-js` library. + * + * @param buildArgs Args to forward along to the prompt template. + */ + public build( + buildArgs: unknown, + options: { + flavor?: Flavor; + messages?: Message[]; + strict?: boolean; + templateFormat?: TemplateFormat; + } = {}, + ): CompiledPrompt { + // eslint-disable-next-line @typescript-eslint/consistent-type-assertions + return this.runBuild(buildArgs, { + flavor: options.flavor ?? "chat", + messages: options.messages, + strict: options.strict, + templateFormat: options.templateFormat, + }) as CompiledPrompt; + } + + /** + * This is a special build method that first resolves attachment references, and then + * calls the regular build method. You should use this if you are building prompts from + * dataset rows that contain attachments. + * + * @param buildArgs Args to forward along to the prompt template. + */ + public async buildWithAttachments< + Flavor extends "chat" | "completion" = "chat", + >( + buildArgs: unknown, + options: { + flavor?: Flavor; + messages?: Message[]; + strict?: boolean; + state?: BraintrustState; + templateFormat?: TemplateFormat; + } = {}, + ): Promise> { + const hydrated = + buildArgs instanceof Object + ? await resolveAttachmentsToBase64(buildArgs, options.state) + : buildArgs; + // eslint-disable-next-line @typescript-eslint/consistent-type-assertions + return this.runBuild(hydrated, { + flavor: options.flavor ?? "chat", + messages: options.messages, + strict: options.strict, + templateFormat: options.templateFormat, + }) as CompiledPrompt; + } + + private runBuild( + buildArgs: unknown, + options: { + flavor: Flavor; + messages?: Message[]; + strict?: boolean; + templateFormat?: TemplateFormat; + }, + ): CompiledPrompt { + const { flavor } = options; + + const params = Object.fromEntries( + Object.entries({ + ...this.defaults, + ...Object.fromEntries( + Object.entries(this.options.params || {}).filter( + ([k, _v]) => !BRAINTRUST_PARAMS.includes(k), + ), + ), + ...(!isEmpty(this.options.model) + ? { + model: this.options.model, + } + : {}), + }).filter(([key, value]) => key !== "response_format" || value !== null), + ); + + if (!("model" in params) || isEmpty(params.model)) { + throw new Error( + "No model specified. Either specify it in the prompt or as a default", + ); + } + + const spanInfo = this.noTrace + ? {} + : { + span_info: { + metadata: { + prompt: this.id + ? { + variables: buildArgs, + id: this.id, + project_id: this.projectId, + version: this.version, + ...("prompt_session_id" in this.metadata + ? { prompt_session_id: this.metadata.prompt_session_id } + : {}), + } + : undefined, + }, + }, + }; + + const prompt = this.prompt; + + if (!prompt) { + throw new Error("Empty prompt"); + } + + const dictArgParsed = z.record(z.unknown()).safeParse(buildArgs); + const variables: Record = { + input: buildArgs, + ...(dictArgParsed.success ? dictArgParsed.data : {}), + }; + + // Use template_format from prompt data if available, otherwise fall back to the option or default to mustache + const promptDataTemplateFormat = this.templateFormat; + const resolvedTemplateFormat = parseTemplateFormat( + options.templateFormat ?? promptDataTemplateFormat, + ); + + const renderedPrompt = Prompt.renderPrompt({ + prompt, + buildArgs, + options: { ...options, templateFormat: resolvedTemplateFormat }, + }); + + if (flavor === "chat") { + if (renderedPrompt.type !== "chat") { + throw new Error( + "Prompt is a completion prompt. Use buildCompletion() instead", + ); + } + + // eslint-disable-next-line @typescript-eslint/consistent-type-assertions + return { + ...renderPromptParams(params, variables, { + strict: options.strict, + templateFormat: resolvedTemplateFormat, + }), + ...spanInfo, + messages: renderedPrompt.messages, + ...(renderedPrompt.tools + ? { + tools: chatCompletionToolSchema + .array() + .parse(JSON.parse(renderedPrompt.tools)), + } + : undefined), + } as CompiledPrompt; + } else if (flavor === "completion") { + if (renderedPrompt.type !== "completion") { + throw new Error(`Prompt is a chat prompt. Use flavor: 'chat' instead`); + } + + // eslint-disable-next-line @typescript-eslint/consistent-type-assertions + return { + ...renderPromptParams(params, variables, { + strict: options.strict, + templateFormat: resolvedTemplateFormat, + }), + ...spanInfo, + prompt: renderedPrompt.content, + } as CompiledPrompt; + } else { + throw new Error("never!"); + } + } + + static renderPrompt({ + prompt, + buildArgs, + options, + }: { + prompt: PromptBlockData; + buildArgs: unknown; + options: { + strict?: boolean; + messages?: Message[]; + templateFormat?: TemplateFormat; + }; + }): PromptBlockData { + const escape = (v: unknown) => { + if (v === undefined) { + throw new Error("Missing!"); + } else if (typeof v === "string") { + return v; + } else if (v instanceof ReadonlyAttachment) { + throw new Error( + "Use buildWithAttachments() to build prompts with attachments", + ); + } else { + return JSON.stringify(v); + } + }; + + const dictArgParsed = z.record(z.unknown()).safeParse(buildArgs); + const variables: Record = { + input: buildArgs, + ...(dictArgParsed.success ? dictArgParsed.data : {}), + }; + + const templateFormat = parseTemplateFormat(options.templateFormat); + + if (prompt.type === "chat") { + const render = (template: string) => + renderTemplateContent(template, variables, escape, { + strict: options.strict, + templateFormat: templateFormat, + }); + + const baseMessages = (prompt.messages || []).map((m) => + renderMessageImpl(render, m, variables), + ); + const hasSystemPrompt = baseMessages.some((m) => m.role === "system"); + + const messages: Message[] = [ + ...baseMessages, + ...(options.messages ?? []).filter( + (m) => !(hasSystemPrompt && m.role === "system"), + ), + ]; + + return { + type: "chat", + messages: messages, + ...(prompt.tools?.trim() + ? { + tools: render(prompt.tools), + } + : undefined), + }; + } else if (prompt.type === "completion") { + if (options.messages) { + throw new Error( + "extra messages are not supported for completion prompts", + ); + } + + const content = renderTemplateContent(prompt.content, variables, escape, { + strict: options.strict, + templateFormat: templateFormat, + }); + return { + type: "completion", + content, + }; + } else { + const _: never = prompt; + throw new Error(`Invalid prompt type: ${_}`); + } + } + + private getParsedPromptData(): PromptData | undefined { + if (!this.hasParsedPromptData) { + this.parsedPromptData = promptDataSchema.parse(this.metadata.prompt_data); + this.hasParsedPromptData = true; + } + return this.parsedPromptData!; + } + + public static isPrompt(data: unknown): data is Prompt { + return ( + typeof data === "object" && + data !== null && + "__braintrust_prompt_marker" in data + ); + } + + static { + serializePromptForCache = (prompt) => ({ + metadata: prompt.metadata, + defaults: prompt.defaults, + noTrace: prompt.noTrace, + }); + } + + public static fromPromptData( + name: string, + promptData: PromptData, + ): Prompt { + return new Prompt( + { + name: name, + slug: name, + prompt_data: promptData, + }, + {}, + false, + ); + } +} + +export interface SerializedParameters { + metadata: ParametersRow; +} + +export let serializeParametersForCache: ( + parameters: RemoteEvalParameters, +) => SerializedParameters; + +export class RemoteEvalParameters< + HasId extends boolean = true, + HasVersion extends boolean = true, + T extends Record = Record, +> { + private readonly __braintrust_parameters_marker = true; + + constructor(private metadata: ParametersRow) { + void this.__braintrust_parameters_marker; + } + + public get id(): HasId extends true ? string : string | undefined { + // eslint-disable-next-line @typescript-eslint/consistent-type-assertions + return this.metadata.id as HasId extends true ? string : string | undefined; + } + + public get projectId(): string | undefined { + return this.metadata.project_id; + } + + public get name(): string { + return this.metadata.name; + } + + public get slug(): string { + return this.metadata.slug; + } + + public get version(): HasVersion extends true + ? TransactionId + : TransactionId | undefined { + // eslint-disable-next-line @typescript-eslint/consistent-type-assertions + return this.metadata[TRANSACTION_ID_FIELD] as HasVersion extends true + ? TransactionId + : TransactionId | undefined; + } + + public get schema(): Record { + return this.metadata.function_data.__schema; + } + + public get data(): T { + // eslint-disable-next-line @typescript-eslint/consistent-type-assertions + return (this.metadata.function_data.data ?? {}) as T; + } + + static { + serializeParametersForCache = (parameters) => ({ + metadata: parameters.metadata, + }); + } + + public validate(data: unknown): boolean { + if (typeof data !== "object" || data === null) { + return false; + } + const schemaProps = this.schema.properties; + if (typeof schemaProps !== "object" || schemaProps === null) { + return true; + } + for (const key of Object.keys(schemaProps)) { + if (!(key in data)) { + const required = Array.isArray(this.schema.required) + ? this.schema.required + : []; + if (required.includes(key)) { + return false; + } + } + } + return true; + } + + public static isParameters( + x: unknown, + ): x is RemoteEvalParameters> { + return ( + typeof x === "object" && + x !== null && + "__braintrust_parameters_marker" in x && + x.__braintrust_parameters_marker === true + ); + } +} + +/** + * Get the versions for a prompt. + * + * @param projectId The ID of the project to query + * @param promptId The ID of the prompt to get versions for + * @returns Promise containing the version data + */ +export async function getPromptVersions( + projectId: string, + promptId: string, +): Promise { + const state = _internalGetGlobalState(); + if (!state) { + throw new Error("Must log in first"); + } + + await state.login({}); + + const query = { + from: { + op: "function", + name: { + op: "ident", + name: ["project_prompts"], + }, + args: [ + { + op: "literal", + value: projectId, + }, + ], + }, + select: [ + { + op: "star", + }, + ], + filter: { + op: "eq", + left: { op: "ident", name: ["id"] }, + right: { op: "literal", value: promptId }, + }, + }; + + const response = await state.apiConn().post( + "btql", + { + query, + audit_log: true, + use_columnstore: false, + brainstore_realtime: true, + }, + { headers: { "Accept-Encoding": "gzip" } }, + BTQL_HTTP_RETRIES, + ); + + if (!response.ok) { + throw new Error( + `API request failed: ${response.status} ${response.statusText}`, + ); + } + + const result = await response.json(); + + // Filter for entries where audit_data.action is "upsert" or "merge" and return only _xact_id fields + return ( + result.data + ?.filter((entry: any) => + ["upsert", "merge"].includes(entry.audit_data?.action), + ) + .map((entry: any) => prettifyXact(entry._xact_id)) || [] + ); +} + +interface LoaderCacheViews { + promptCache: PromptCache; + parametersCache: ParametersCache; +} + +const loaderCaches = /* @__PURE__ */ new WeakMap< + BraintrustState, + LoaderCacheViews +>(); + +/** @internal Caches belong to prompt/parameter loading, not tracing state. */ +export function getLoaderCaches(state: BraintrustState): LoaderCacheViews { + let caches = loaderCaches.get(state); + if (!caches) { + const { memoryCache, diskCache } = createCacheLayers< + PromptMemoryCacheEntry, + PromptDiskCacheEntry + >({ + memoryMaxEnvVar: "BRAINTRUST_PROMPT_CACHE_MEMORY_MAX", + diskCacheDirEnvVar: "BRAINTRUST_PROMPT_CACHE_DIR", + diskMaxEnvVar: "BRAINTRUST_PROMPT_CACHE_DISK_MAX", + getDefaultDiskCacheDir: () => + `${iso.getEnv("HOME") ?? iso.homedir!()}/.braintrust/prompt_cache`, + }); + const promptCache = new PromptCache({ memoryCache, diskCache }); + + const { + memoryCache: parametersMemoryCache, + diskCache: parametersDiskCache, + } = createCacheLayers( + { + memoryMaxEnvVar: "BRAINTRUST_PARAMETERS_CACHE_MEMORY_MAX", + diskCacheDirEnvVar: "BRAINTRUST_PARAMETERS_CACHE_DIR", + diskMaxEnvVar: "BRAINTRUST_PARAMETERS_CACHE_DISK_MAX", + getDefaultDiskCacheDir: () => + `${iso.getEnv("HOME") ?? iso.homedir!()}/.braintrust/parameters_cache`, + }, + ); + const parametersCache = new ParametersCache({ + memoryCache: parametersMemoryCache, + diskCache: parametersDiskCache, + }); + + caches = { promptCache, parametersCache }; + loaderCaches.set(state, caches); + } + return caches; +} + +function getLoaderCacheViews( + state: BraintrustState, + loginOptions: ResolvedLoaderLoginOptions, + requestState?: LoaderRequestState, +): LoaderCacheViews { + const caches = getLoaderCaches(state); + + const expectedResolvedOrgIdentity = + requestState?.orgId && requestState.appUrl + ? JSON.stringify(["loader-org", requestState.appUrl, requestState.orgId]) + : undefined; + + return { + promptCache: caches.promptCache.withNamespace( + loginOptions.credentialCacheNamespace, + expectedResolvedOrgIdentity, + ), + parametersCache: caches.parametersCache.withNamespace( + loginOptions.credentialCacheNamespace, + expectedResolvedOrgIdentity, + ), + }; +} diff --git a/js/src/logger/testing.ts b/js/src/logger/testing.ts new file mode 100644 index 000000000..416f0e6b4 --- /dev/null +++ b/js/src/logger/testing.ts @@ -0,0 +1,138 @@ +import { + extractAttachments, + deepCopyEvent, + isGeneratorFunction, + isAsyncGeneratorFunction, + validateTags, +} from "./core"; +import { resetDebugLoggerForTests } from "../debug-logger"; +/// +import iso from "#braintrust-platform"; +import { LazyValue } from "../util"; +import { + TestBackgroundLogger, + type BraintrustState, + _internalGetGlobalState, + ProjectExperimentMetadata, + _internalSetInitialState, + login, + RESET_CONTEXT_MANAGER_STATE, +} from "./core"; +import { Experiment } from "./experiments"; + +// Return a TestBackgroundLogger that will intercept logs before they are sent to the server. +// Used for testing only. +function useTestBackgroundLogger(): TestBackgroundLogger { + const state = _internalGetGlobalState(); + if (!state) { + throw new Error("global state not set yet"); + } + + const logger = new TestBackgroundLogger(); + state.setOverrideBgLogger(logger); + return logger; +} + +function clearTestBackgroundLogger(): void { + _internalGetGlobalState()?.setOverrideBgLogger(null); +} + +// Initialize a test Experiment without making network calls by injecting fake metadata. +// Useful for unit tests that need an Experiment instance. +function initTestExperiment( + experimentName: string, + projectName?: string, + experimentFullInfo: Record = {}, +): Experiment { + setInitialTestState(); + const state = _internalGetGlobalState(); + const project = projectName ?? experimentName; + + const lazyMetadata: LazyValue = new LazyValue( + async () => ({ + project: { id: project, name: project, fullInfo: {} }, + experiment: { + id: experimentName, + name: experimentName, + fullInfo: experimentFullInfo, + }, + }), + ); + + return new Experiment(state, lazyMetadata); +} + +/** + * Allows accessing helper functions for testing. + * @internal + */ + +// it's used to circumvent login for testing, but won't actually work +// on the server side. +const TEST_API_KEY = "___TEST_API_KEY__THIS_IS_NOT_REAL___"; + +function setInitialTestState(): void { + // a way of setting initial state for tests without any log messages + if (!_internalGetGlobalState()) { + _internalSetInitialState(); + } +} + +// This is a helper function to simulate a login for testing. +async function simulateLoginForTests(): Promise { + return await login({ + apiKey: TEST_API_KEY, + appUrl: "https://braintrust.dev", + }); +} + +// This is a helper function to simulate a logout for testing. +function simulateLogoutForTests(): BraintrustState { + const state = _internalGetGlobalState(); + state.resetLoginInfo(); + state.resetIdGenState(); + state[RESET_CONTEXT_MANAGER_STATE](); + state.appUrl = "https://www.braintrust.dev"; + return state; +} + +// Helper function to reset ID generator state for testing +function resetIdGenStateForTests(): void { + const state = _internalGetGlobalState(); + if (state) { + state.resetIdGenState(); + } +} + +export const _exportsForTestingOnly: { + extractAttachments: typeof extractAttachments; + deepCopyEvent: typeof deepCopyEvent; + useTestBackgroundLogger: typeof useTestBackgroundLogger; + clearTestBackgroundLogger: typeof clearTestBackgroundLogger; + resetDebugLoggerForTests: typeof resetDebugLoggerForTests; + simulateLoginForTests: typeof simulateLoginForTests; + simulateLogoutForTests: typeof simulateLogoutForTests; + setInitialTestState: typeof setInitialTestState; + initTestExperiment: typeof initTestExperiment; + isGeneratorFunction: typeof isGeneratorFunction; + isAsyncGeneratorFunction: typeof isAsyncGeneratorFunction; + resetIdGenStateForTests: typeof resetIdGenStateForTests; + validateTags: typeof validateTags; + isomorph: any; +} = { + extractAttachments, + deepCopyEvent, + useTestBackgroundLogger, + clearTestBackgroundLogger, + resetDebugLoggerForTests, + simulateLoginForTests, + simulateLogoutForTests, + setInitialTestState, + initTestExperiment, + isGeneratorFunction, + isAsyncGeneratorFunction, + resetIdGenStateForTests, + validateTags, + // eslint-disable-next-line @typescript-eslint/no-explicit-any + isomorph: iso as any, // Expose isomorph for build type detection +}; diff --git a/js/src/logs/index.ts b/js/src/logs/index.ts index fb7d1f9c9..162c24530 100644 --- a/js/src/logs/index.ts +++ b/js/src/logs/index.ts @@ -1,6 +1,5 @@ -import { currentLogger, type Logger } from "../logger"; -import iso from "../isomorph"; -import { debugLogger } from "../debug-logger"; +import { currentLogger, type Logger } from "../logger/core"; +import iso from "#braintrust-platform"; import { consoleHooks, wrapConsoleMethods } from "./console-interceptor"; import { runConsoleCall, type ConsoleMethod, type LogConsole } from "./console"; import type { ApplicationLogLevel } from "./templates"; @@ -32,7 +31,7 @@ function formatConsole(args: unknown[]): string { /** Forward one console call, keeping message extraction independent of hook registration. */ function forwardConsoleCall( call: () => void, - logger: Logger | undefined, + logger: Logger | undefined, method: ConsoleMethod, args: unknown[], ): void { @@ -55,19 +54,14 @@ function forwardConsoleCall( }); } const level: ApplicationLogLevel = method === "log" ? "info" : method; - const result = logger[level](body, { metadata }); - if (result instanceof Promise) { - void result.catch((error) => - debugLogger.error("Application log flush failed:", error), - ); - } + logger[level](body, { metadata }); } return call(); } type WrapConsoleOptions = { /** Destination for console logs. Defaults to the current project logger at call time. */ - logger?: Logger; + logger?: Logger; }; const consoleRegistrations = new WeakMap(); diff --git a/js/src/logs/logs.test.ts b/js/src/logs/logs.test.ts index ee61847fd..473a174d4 100644 --- a/js/src/logs/logs.test.ts +++ b/js/src/logs/logs.test.ts @@ -1,4 +1,4 @@ -import { afterEach, describe, expect, test, vi } from "vitest"; +import { afterEach, describe, expect, test, vi } from "vite-plus/test"; import { inspect } from "node:util"; import { Console } from "node:console"; import { Writable } from "node:stream"; @@ -7,15 +7,14 @@ import { Logger, TestBackgroundLogger, _internalGetGlobalState, -} from "../logger"; +} from "../logger/core"; import { LazyValue } from "../util"; import { wrapConsole } from "./index"; import { consoleMethods } from "./console"; import { configureNode } from "../node/config"; -import iso from "../isomorph"; +import iso from "#braintrust-platform"; import type { ExperimentEvent } from "../util"; import { debugLogger } from "../debug-logger"; -import { OTELIDGenerator } from "../id-gen"; import { logError } from "../framework"; import { SimpleProgressReporter } from "../reporters/progress"; @@ -27,7 +26,7 @@ async function drain( return (await background.drain()) as ExperimentEvent[]; } -function setup(asyncFlush: Async) { +function setup() { const state = new BraintrustState({}); const background = new TestBackgroundLogger(); vi.spyOn(state, "bgLogger").mockReturnValue(background); @@ -38,7 +37,7 @@ function setup(asyncFlush: Async) { org_id: "org", project: { id: "project", name: "application-logs", fullInfo: {} }, })), - { asyncFlush }, + {}, ); return { state, background, logger, enqueue }; } @@ -84,7 +83,7 @@ describe("application logs", () => { org_id: "org", project: { id: "project", name: "test", fullInfo: {} }, })), - { asyncFlush: true }, + {}, ); const target = fakeConsole(); const originals = { ...target }; @@ -132,7 +131,7 @@ describe("application logs", () => { }); test("severity methods each enqueue one complete initial row", async () => { - const { logger, background, enqueue } = setup(true); + const { logger, background, enqueue } = setup(); const timestamp = new Date("2026-01-02T03:04:05.000Z"); const levels = [ "trace", @@ -176,7 +175,7 @@ describe("application logs", () => { }); test("fmt captures stable templates and values without emitting or mutating metadata", async () => { - const { logger, background, enqueue } = setup(true); + const { logger, background, enqueue } = setup(); const metadata = Object.freeze({ source: "checkout", "braintrust.template": "old", @@ -207,7 +206,7 @@ describe("application logs", () => { }); test("fmt preserves cooked text, escaped placeholders, and interpolation boundaries", async () => { - const { logger, background } = setup(true); + const { logger, background } = setup(); const count = 3; logger.info(logger.fmt`line\n\t${count * 2}\\end`); logger.info(logger.fmt`literal \${value} and {braces} ${count}`); @@ -234,7 +233,7 @@ describe("application logs", () => { }); test("fmt retains JSON parameter values and encodes undefined and bigint as text", async () => { - const { logger, background } = setup(true); + const { logger, background } = setup(); const values = [Object.freeze({ id: 42 }), false, null, 0, undefined, 123n]; for (const value of values) logger.info(logger.fmt`${value}`); const rows = await drain(background); @@ -252,7 +251,7 @@ describe("application logs", () => { }); test("groups standalone messages per logger with independent row and span IDs", async () => { - const { logger, background, state } = setup(true); + const { logger, background, state } = setup(); logger.info("one"); logger.warn({ event: "two" }); const other = new Logger( @@ -261,7 +260,7 @@ describe("application logs", () => { org_id: "org", project: { id: "project", name: "test", fullInfo: {} }, })), - { asyncFlush: true }, + {}, ); other.info("three"); const [one, two, three] = await drain(background); @@ -276,16 +275,15 @@ describe("application logs", () => { test("uses the current ID generator for the first standalone log after late OpenTelemetry setup", async () => { vi.stubEnv("BRAINTRUST_LEGACY_IDS", "true"); vi.stubEnv("BRAINTRUST_OTEL_COMPAT", undefined); - vi.stubGlobal("BRAINTRUST_ID_GENERATOR", undefined); try { - const { logger, background, state } = setup(true); + const { logger, background, state } = setup(); const root = logger.startSpan({ name: "before-otel" }); state.contextManager.runInContext(root, () => logger.info("inside span")); root.end(); expect(root.rootSpanId).toContain("-"); - // Match setupOtelCompat(): install the hex generator and reset the cache. - vi.stubGlobal("BRAINTRUST_ID_GENERATOR", OTELIDGenerator); + // Change ID configuration after logger creation and reset the cache. + vi.stubEnv("BRAINTRUST_OTEL_COMPAT", "true"); state.resetIdGenState(); logger.info("first standalone log"); logger.warn("second standalone log"); @@ -306,7 +304,7 @@ describe("application logs", () => { }); test("correlates SDK and console logs across awaits without modifying the active span", async () => { - const { logger, background, state } = setup(true); + const { logger, background, state } = setup(); const target = fakeConsole(); const uninstrument = wrapConsole(target, { logger }); const root = logger.startSpan({ name: "request" }); @@ -336,7 +334,7 @@ describe("application logs", () => { }); test("uses context-manager IDs even without a native Braintrust span", async () => { - const { logger, background, state } = setup(true); + const { logger, background, state } = setup(); vi.spyOn(state.contextManager, "getParentSpanIds").mockReturnValue({ rootSpanId: "otel-trace", spanParents: ["otel-span"], @@ -348,15 +346,17 @@ describe("application logs", () => { }); }); - test("waits for flush when asyncFlush is false", async () => { - const { logger, background, enqueue } = setup(false); + test("returns a row ID immediately and waits for explicit flush", async () => { + const { logger, background, enqueue } = setup(); let finishFlush!: () => void; const flush = vi.spyOn(background, "flush").mockReturnValue( new Promise((resolve) => { finishFlush = resolve; }), ); - const pending = logger.info("synchronous"); + const id = logger.info("synchronous"); + expect(typeof id).toBe("string"); + const pending = logger.flush(); let settled = false; void pending.then(() => { settled = true; @@ -364,7 +364,7 @@ describe("application logs", () => { await Promise.resolve(); expect(settled).toBe(false); finishFlush(); - const id = await pending; + await pending; expect(flush).toHaveBeenCalledTimes(1); expect(enqueue).toHaveBeenCalledTimes(1); expect((await drain(background))[0].id).toBe(id); @@ -374,8 +374,8 @@ describe("application logs", () => { describe("wrapConsole", () => { test("wrapConsole() follows logger initialization and replacement until cleanup", async () => { const target = mockGlobalConsole(); - const first = setup(false); - const second = setup(false); + const first = setup(); + const second = setup(); const state = _internalGetGlobalState(); const previousLogger = state.currentLogger; const original = target.info; @@ -407,8 +407,8 @@ describe("wrapConsole", () => { }); test("rewrapping and cleanup preserve the current destination without reactivating older wraps", async () => { - const first = setup(true); - const second = setup(true); + const first = setup(); + const second = setup(); const target = fakeConsole(); const original = target.info; const stopFirst = wrapConsole(target, { logger: first.logger }); @@ -439,8 +439,8 @@ describe("wrapConsole", () => { }); test("cleanup affects only its console; direct logging and unrelated methods stay enabled", async () => { - const first = setup(true); - const second = setup(true); + const first = setup(); + const second = setup(); const time = vi.fn(); const firstConsole = { ...fakeConsole(), time }; const secondConsole = fakeConsole(); @@ -461,8 +461,8 @@ describe("wrapConsole", () => { }); test("nested wrapping captures once and cleanup preserves third-party patches without reviving older capture", () => { - const first = setup(true); - const second = setup(true); + const first = setup(); + const second = setup(); const target = fakeConsole(); const original = target.info; const stopFirst = wrapConsole(target, { logger: first.logger }); @@ -487,7 +487,7 @@ describe("wrapConsole", () => { }); test("nested console calls capture once and native exceptions do not leave the guard active", async () => { - const { logger, background, enqueue } = setup(true); + const { logger, background, enqueue } = setup(); const target = fakeConsole(); target.trace.mockImplementation((...args) => target.error(...args)); const error = new Error("console failure"); @@ -509,7 +509,7 @@ describe("wrapConsole", () => { }); test("object formatting hooks run only in the native console, not during capture", async () => { - const { logger, background } = setup(true); + const { logger, background } = setup(); const output: string[] = []; const stdout = new Writable({ write(chunk, _encoding, callback) { @@ -564,7 +564,7 @@ describe("wrapConsole", () => { }); test("preserves primitive formatting, parameters, errors, and circular objects without calling toJSON", async () => { - const { logger, background } = setup(true); + const { logger, background } = setup(); const target = fakeConsole(); wrapConsole(target, { logger }); target.info("Payment %s attempt %d", "pay_123", 2); @@ -594,7 +594,7 @@ describe("wrapConsole", () => { }); test("fallback formatting serializes parameters once and preserves BigInt arguments", async () => { - const { logger, background } = setup(true); + const { logger, background } = setup(); const target = fakeConsole(); wrapConsole(target, { logger }); let serializations = 0; @@ -628,7 +628,7 @@ describe("wrapConsole", () => { test("SDK output uses saved console methods across awaits, rewrapping, and cleanup", async () => { const target = mockGlobalConsole(); - const { logger, enqueue } = setup(true); + const { logger, enqueue } = setup(); const sdkState = _internalGetGlobalState(); vi.spyOn(sdkState, "getDebugLogLevel").mockReturnValue("warn"); vi.spyOn(sdkState, "hasDebugLogLevelOverride").mockReturnValue(true); diff --git a/js/src/logs/templates.ts b/js/src/logs/templates.ts index 3e53fc5be..b06adf599 100644 --- a/js/src/logs/templates.ts +++ b/js/src/logs/templates.ts @@ -2,7 +2,7 @@ export type ApplicationLogOptions = { metadata?: Record; }; -const LOG_TEMPLATE = Symbol.for("braintrust.logTemplate"); +const LOG_TEMPLATE: unique symbol = Symbol.for("braintrust.logTemplate"); export type ApplicationLogTemplate = { readonly [LOG_TEMPLATE]: true; @@ -42,7 +42,10 @@ function isLogTemplate(body: unknown): body is ApplicationLogTemplate { ); } -export function renderLogBody(body: unknown, options?: ApplicationLogOptions) { +export function renderLogBody( + body: unknown, + options?: ApplicationLogOptions, +): { body: unknown; metadata?: Record } { if (!isLogTemplate(body)) return { body, metadata: options?.metadata }; return { body: body.body, diff --git a/js/src/lru-cache.test.ts b/js/src/lru-cache.test.ts index 06235373d..e8a907b05 100644 --- a/js/src/lru-cache.test.ts +++ b/js/src/lru-cache.test.ts @@ -1,4 +1,4 @@ -import { describe, expect, it } from "vitest"; +import { describe, expect, it } from "vite-plus/test"; import { LRUCache } from "./lru-cache"; describe("LRUCache", () => { diff --git a/js/src/lru-cache.ts b/js/src/lru-cache.ts index a71a7a2e5..f22e9982e 100644 --- a/js/src/lru-cache.ts +++ b/js/src/lru-cache.ts @@ -72,7 +72,7 @@ export class LRUCache { this.cache.delete(key); } else if (this.maxSize && this.cache.size >= this.maxSize) { // Map.keys() iterates in insertion order. - const first = this.cache.keys().next().value; + const first = this.cache.keys().next().value!; this.cache.delete(first); } this.cache.set(key, { value }); diff --git a/js/src/masking.test.ts b/js/src/masking.test.ts index abed47e0b..4e710cecc 100644 --- a/js/src/masking.test.ts +++ b/js/src/masking.test.ts @@ -1,13 +1,20 @@ -import { expect, test, describe, beforeEach, afterEach, vi } from "vitest"; import { - _exportsForTestingOnly, + expect, + test, + describe, + beforeEach, + afterEach, + vi, +} from "vite-plus/test"; +import { _exportsForTestingOnly } from "./logger/testing"; +import { initLogger, wrapTraced, currentSpan, setMaskingFunction, -} from "./logger"; +} from "./logger/core"; import { configureNode } from "./node/config"; -import { configureInstrumentation, registry } from "./instrumentation/registry"; +import { setSpanCustomizers } from "./span-customizer"; import { INSTRUMENTATION_NAMES, withSpanInstrumentationName, @@ -19,13 +26,12 @@ describe("masking functionality", () => { let memoryLogger: any; beforeEach(async () => { - registry.disable(); await _exportsForTestingOnly.simulateLoginForTests(); memoryLogger = _exportsForTestingOnly.useTestBackgroundLogger(); }); afterEach(() => { - configureInstrumentation({ spanCustomizers: [] }); + setSpanCustomizers([]); setMaskingFunction(null); // Clear masking function _exportsForTestingOnly.clearTestBackgroundLogger(); vi.restoreAllMocks(); @@ -549,16 +555,14 @@ describe("masking functionality", () => { test("stripped customizer records bypass masking and remain independent of successful records", async () => { vi.spyOn(console, "error").mockImplementation(() => {}); - configureInstrumentation({ - spanCustomizers: [ - { - onSpanExport(data) { - if (data.output === "private") throw new Error("private failure"); - return data; - }, + setSpanCustomizers([ + { + onSpanExport(data) { + if (data.output === "private") throw new Error("private failure"); + return data; }, - ], - }); + }, + ]); const mask = vi.fn((data: unknown) => { if (data && typeof data === "object" && "customizer_error" in data) { return { injected: "must not replace SDK diagnostics" }; @@ -607,16 +611,14 @@ describe("masking functionality", () => { test.each(["manual", "instrumented"])( "masking runs after %s span customizers and can be replaced or disabled independently", async (origin) => { - configureInstrumentation({ - spanCustomizers: [ - { - onSpanExport(data) { - if (typeof data.output === "string") data.output += ":customized"; - return data; - }, + setSpanCustomizers([ + { + onSpanExport(data) { + if (typeof data.output === "string") data.output += ":customized"; + return data; }, - ], - }); + }, + ]); const logger = initLogger({ projectName: "test", projectId: "test-project-id", diff --git a/js/src/node/apply-auto-instrumentation-entry.ts b/js/src/node/apply-auto-instrumentation-entry.ts index cd199bba4..4ee600fa9 100644 --- a/js/src/node/apply-auto-instrumentation-entry.ts +++ b/js/src/node/apply-auto-instrumentation-entry.ts @@ -1,3 +1,4 @@ +import "../auto"; import { applyAutoInstrumentation } from "./apply-auto-instrumentation"; applyAutoInstrumentation(); diff --git a/js/src/node/apply-auto-instrumentation.ts b/js/src/node/apply-auto-instrumentation.ts index f6af91ca2..66e655705 100644 --- a/js/src/node/apply-auto-instrumentation.ts +++ b/js/src/node/apply-auto-instrumentation.ts @@ -7,7 +7,6 @@ import { syncTransformedSources, } from "../auto-instrumentations/loader/module-hooks.mjs"; import { debugLogger } from "../debug-logger"; -import { getCurrentModuleUrl } from "./module-url"; import { GLOBAL_INSTRUMENTATION_HOOKS_PROTOCOL_VERSION } from "../global-instrumentation-hooks"; interface ApplyAutoInstrumentationState { @@ -55,10 +54,7 @@ export function applyAutoInstrumentation(loaderUrl?: URL): void { } else { nodeModule.register( loaderUrl ?? - new URL( - "./auto-instrumentations/loader/module-hooks.mjs", - getCurrentModuleUrl(), - ), + new URL("../../hooks/loader/module-hooks.mjs", import.meta.url), { data: { instrumentations: allConfigs } }, ); } diff --git a/js/src/node/config.test.ts b/js/src/node/config.test.ts index 157605e72..8f3463a98 100644 --- a/js/src/node/config.test.ts +++ b/js/src/node/config.test.ts @@ -1,9 +1,9 @@ -import { afterEach, beforeEach, describe, expect, test } from "vitest"; +import { afterEach, beforeEach, describe, expect, test } from "vite-plus/test"; import { mkdir, mkdtemp, rm, writeFile } from "node:fs/promises"; import { tmpdir } from "node:os"; import path from "node:path"; -import iso from "../isomorph"; +import iso from "#braintrust-platform"; import { configureNode } from "./config"; describe("configureNode .env.braintrust API key lookup", () => { diff --git a/js/src/node/config.ts b/js/src/node/config.ts index 8f340c5ed..9ef579f53 100644 --- a/js/src/node/config.ts +++ b/js/src/node/config.ts @@ -1,225 +1,7 @@ -import { AsyncLocalStorage } from "node:async_hooks"; -import { createRequire } from "node:module"; -import { fileURLToPath } from "node:url"; -import * as path from "node:path"; -import * as fs from "node:fs/promises"; -import * as os from "node:os"; -import * as fsSync from "node:fs"; -import * as crypto from "node:crypto"; -import { formatWithOptions, inspect, promisify } from "node:util"; -import * as zlib from "node:zlib"; -import * as dotenv from "dotenv"; +import iso from "#braintrust-platform"; +import { createNodeIsomorph } from "./platform"; -import iso from "../isomorph"; -import { getRepoInfo, getPastNAncestors } from "../gitutil"; -import { getCallerLocation } from "../stackutil"; -import { _internalSetInitialState } from "../logger"; -import { registry } from "../instrumentation/registry"; -import { - isInstrumentationIntegrationDisabled, - readDisabledInstrumentationEnvConfig, -} from "../instrumentation/config"; -import { installMastraExporterFactory } from "../auto-instrumentations/loader/mastra-observability-patch"; -import { BraintrustObservabilityExporter } from "../wrappers/mastra"; -import { getCurrentModuleUrl } from "./module-url"; - -const BRAINTRUST_ENV_SEARCH_PARENT_LIMIT = 64; - -export function configureNode() { - iso.buildType = "node"; - - iso.getRepoInfo = getRepoInfo; - iso.getPastNAncestors = getPastNAncestors; - iso.getEnv = (name) => { - const value = process.env[name]; - if (name === "BRAINTRUST_API_KEY") { - return value?.trim() ? value : undefined; - } - if ( - (name === "BRAINTRUST_ENVIRONMENT_TYPE" || - name === "BRAINTRUST_ENVIRONMENT_NAME") && - !value?.trim() - ) { - return getNearestBraintrustEnvValue(name); - } - return value; - }; - iso.getBraintrustApiKey = async () => { - const value = process.env.BRAINTRUST_API_KEY; - if (value?.trim()) { - return value; - } - - // Kick off the cwd/parent reads together, then drain them nearest-first so - // a slower local file still beats a faster parent. - const envPaths = []; - for ( - let dir = process.cwd(), depth = 0; - depth <= BRAINTRUST_ENV_SEARCH_PARENT_LIMIT; - dir = path.dirname(dir), depth++ - ) { - envPaths.push(path.join(dir, ".env.braintrust")); - if (path.dirname(dir) === dir) { - break; - } - } - - type ReadResult = - | { envPath: string; index: number; contents: string } - | { envPath: string; index: number; error: unknown }; - - const pending = new Map>(); - envPaths.forEach((envPath, index) => { - pending.set( - index, - fs.readFile(envPath, "utf8").then( - (contents) => ({ contents, envPath, index }), - (error) => ({ error, envPath, index }), - ), - ); - }); - - const results: Array = []; - let nearestUnresolvedIndex = 0; - while (pending.size > 0) { - const result = await Promise.race(pending.values()); - pending.delete(result.index); - results[result.index] = result; - - while (results[nearestUnresolvedIndex]) { - const nearestResult = results[nearestUnresolvedIndex]!; - if ("contents" in nearestResult) { - const parsed = dotenv.parse(nearestResult.contents); - const apiKey = parsed.BRAINTRUST_API_KEY; - return apiKey?.trim() ? apiKey : undefined; - } - - const e = nearestResult.error; - if ( - typeof e === "object" && - e !== null && - "code" in e && - e.code === "ENOENT" - ) { - nearestUnresolvedIndex++; - continue; - } - return undefined; - } - } - - return undefined; - }; - iso.getCallerLocation = getCallerLocation; - // Inspect object parameters before substitution: customInspect alone does not - // prevent %s/%d/%j from invoking toString/valueOf/toJSON. The first argument - // stays intact because Node only treats a leading string as a format string. - // Native console output still receives the original arguments and hooks. - iso.formatLogMessage = (...args) => - formatWithOptions( - { customInspect: false }, - ...args.map((value, index) => - index > 0 && - value !== null && - (typeof value === "object" || typeof value === "function") - ? inspect(value, { customInspect: false }) - : value, - ), - ); - iso.newAsyncLocalStorage = () => new AsyncLocalStorage(); - iso.processOn = (event: string, handler: (code: unknown) => void) => { - process.on(event, handler); - }; - iso.basename = path.basename; - iso.writeln = (text: string) => process.stdout.write(text + "\n"); - iso.pathJoin = path.join; - iso.pathDirname = path.dirname; - iso.mkdir = fs.mkdir; - iso.writeFile = fs.writeFile; - iso.readFile = fs.readFile; - iso.readdir = fs.readdir; - iso.stat = fs.stat; - iso.statSync = fsSync.statSync; - iso.utimes = fs.utimes; - iso.unlink = fs.unlink; - iso.homedir = os.homedir; - iso.tmpdir = os.tmpdir; - iso.writeFileSync = fsSync.writeFileSync; - iso.appendFileSync = fsSync.appendFileSync; - iso.readFileSync = (filename: string, encoding: string) => - fsSync.readFileSync(filename, encoding as BufferEncoding); - iso.unlinkSync = fsSync.unlinkSync; - iso.openFile = fs.open; - iso.gzip = promisify(zlib.gzip); - iso.gunzip = promisify(zlib.gunzip); - iso.loadOptionalModule = (name) => { - const url = getCurrentModuleUrl(); - const load = createRequire(url); - let resolved: string; - try { - // Look beside the SDK, then in the app, for linked or global SDK installs. - resolved = load.resolve(name, { - paths: [path.dirname(fileURLToPath(url)), process.cwd()], - }); - } catch { - return undefined; - } - return load(resolved); - }; - iso.hash = (data) => crypto.createHash("sha256").update(data).digest("hex"); - iso.hmacSha256 = (key, data) => - crypto.createHmac("sha256", key).update(data).digest("hex"); - iso.timingSafeEqual = (left, right) => { - const leftBuffer = Buffer.from(left); - const rightBuffer = Buffer.from(right); - return ( - leftBuffer.length === rightBuffer.length && - crypto.timingSafeEqual(leftBuffer, rightBuffer) - ); - }; - _internalSetInitialState(); - - // The Mastra patches rewritten by the bundler plugin and the loader hook - // both look up an exporter factory on `globalThis` at runtime. Register it - // here too so a bundled app (where neither hook.mjs nor the loader runs - // against `@mastra/core`) still gets the exporter installed when its code - // imports `braintrust`. The loader path (hook.mjs) also calls this; the - // `??=` makes double-registration a no-op. - const disabled = readDisabledInstrumentationEnvConfig( - iso.getEnv("BRAINTRUST_DISABLE_INSTRUMENTATION"), - ).integrations; - if (!isInstrumentationIntegrationDisabled(disabled, "mastra")) { - installMastraExporterFactory(() => new BraintrustObservabilityExporter()); - } - - // Enable auto-instrumentation - registry.enable(); -} - -function getNearestBraintrustEnvValue(name: string): string | undefined { - for ( - let dir = process.cwd(), depth = 0; - depth <= BRAINTRUST_ENV_SEARCH_PARENT_LIMIT; - dir = path.dirname(dir), depth++ - ) { - const envPath = path.join(dir, ".env.braintrust"); - try { - const parsed = dotenv.parse(fsSync.readFileSync(envPath, "utf8")); - const value = parsed[name]; - return value?.trim() ? value : undefined; - } catch (e) { - if ( - typeof e !== "object" || - e === null || - !("code" in e) || - e.code !== "ENOENT" - ) { - return undefined; - } - } - if (path.dirname(dir) === dir) { - break; - } - } - return undefined; +/** @internal Configure source-level tests and compatibility integrations. */ +export function configureNode(): void { + Object.assign(iso, createNodeIsomorph()); } diff --git a/js/src/node/index.ts b/js/src/node/index.ts index ba75fa586..d4017cad6 100644 --- a/js/src/node/index.ts +++ b/js/src/node/index.ts @@ -42,18 +42,14 @@ * }); * ``` * - * Finally, run the script with `npx braintrust eval hello.eval.ts`. + * Install the `bt` CLI separately using the standalone installation instructions in the + * [CLI quickstart](https://www.braintrust.dev/docs/reference/cli/quickstart), then run the script: * * ```bash - * BRAINTRUST_API_KEY= npx braintrust eval hello.eval.ts + * BRAINTRUST_API_KEY= bt eval hello.eval.ts * ``` * * @module braintrust */ -import { configureNode } from "./config"; - -configureNode(); - export * from "../exports"; -export * as default from "../exports"; diff --git a/js/src/node/isomorph.ts b/js/src/node/isomorph.ts new file mode 100644 index 000000000..597a83abf --- /dev/null +++ b/js/src/node/isomorph.ts @@ -0,0 +1,5 @@ +import type { Common } from "../isomorph-base"; +export type { CallerLocation, IsoAsyncLocalStorage } from "../isomorph-base"; +import { createNodeIsomorph } from "./platform"; +const iso: Common = /* @__PURE__ */ createNodeIsomorph(); +export default iso; diff --git a/js/src/node/platform.ts b/js/src/node/platform.ts new file mode 100644 index 000000000..2da291bac --- /dev/null +++ b/js/src/node/platform.ts @@ -0,0 +1,197 @@ +import { createRequire } from "node:module"; +import { fileURLToPath } from "node:url"; +import { getCurrentModuleUrl } from "./module-url"; +import { AsyncLocalStorage } from "node:async_hooks"; +import * as path from "node:path"; +import * as fs from "node:fs/promises"; +import * as os from "node:os"; +import * as fsSync from "node:fs"; +import * as crypto from "node:crypto"; +import { formatWithOptions, inspect, promisify } from "node:util"; +import * as zlib from "node:zlib"; +import * as dotenv from "dotenv"; + +import { createIsomorph, type Common } from "../isomorph-base"; +import { getRepoInfo, getPastNAncestors } from "../gitutil"; +import { getCallerLocation } from "../stackutil"; +const BRAINTRUST_ENV_SEARCH_PARENT_LIMIT = 64; + +export function createNodeIsomorph(): Common { + const iso = createIsomorph(); + iso.buildType = "node"; + + iso.getRepoInfo = getRepoInfo; + iso.getPastNAncestors = getPastNAncestors; + iso.getEnv = (name) => { + const value = process.env[name]; + if (name === "BRAINTRUST_API_KEY") { + return value?.trim() ? value : undefined; + } + if ( + (name === "BRAINTRUST_ENVIRONMENT_TYPE" || + name === "BRAINTRUST_ENVIRONMENT_NAME") && + !value?.trim() + ) { + return getNearestBraintrustEnvValue(name); + } + return value; + }; + iso.getBraintrustApiKey = async () => { + const value = process.env.BRAINTRUST_API_KEY; + if (value?.trim()) { + return value; + } + + // Kick off the cwd/parent reads together, then drain them nearest-first so + // a slower local file still beats a faster parent. + const envPaths = []; + for ( + let dir = process.cwd(), depth = 0; + depth <= BRAINTRUST_ENV_SEARCH_PARENT_LIMIT; + dir = path.dirname(dir), depth++ + ) { + envPaths.push(path.join(dir, ".env.braintrust")); + if (path.dirname(dir) === dir) { + break; + } + } + + type ReadResult = + | { envPath: string; index: number; contents: string } + | { envPath: string; index: number; error: unknown }; + + const pending = new Map>(); + envPaths.forEach((envPath, index) => { + pending.set( + index, + fs.readFile(envPath, "utf8").then( + (contents) => ({ contents, envPath, index }), + (error) => ({ error, envPath, index }), + ), + ); + }); + + const results: Array = []; + let nearestUnresolvedIndex = 0; + while (pending.size > 0) { + const result = await Promise.race(pending.values()); + pending.delete(result.index); + results[result.index] = result; + + while (results[nearestUnresolvedIndex]) { + const nearestResult = results[nearestUnresolvedIndex]!; + if ("contents" in nearestResult) { + const parsed = dotenv.parse(nearestResult.contents); + const apiKey = parsed.BRAINTRUST_API_KEY; + return apiKey?.trim() ? apiKey : undefined; + } + + const e = nearestResult.error; + if ( + typeof e === "object" && + e !== null && + "code" in e && + e.code === "ENOENT" + ) { + nearestUnresolvedIndex++; + continue; + } + return undefined; + } + } + + return undefined; + }; + iso.getCallerLocation = getCallerLocation; + iso.formatLogMessage = (...args) => + formatWithOptions( + { customInspect: false }, + ...args.map((value, index) => + index > 0 && + value !== null && + (typeof value === "object" || typeof value === "function") + ? inspect(value, { customInspect: false }) + : value, + ), + ); + iso.newAsyncLocalStorage = () => new AsyncLocalStorage(); + iso.processOn = (event: string, handler: (code: unknown) => void) => { + process.on(event, handler); + }; + iso.basename = path.basename; + iso.writeln = (text: string) => process.stdout.write(text + "\n"); + iso.pathJoin = path.join; + iso.pathDirname = path.dirname; + iso.mkdir = fs.mkdir; + iso.writeFile = fs.writeFile; + iso.readFile = fs.readFile; + iso.readdir = fs.readdir; + iso.stat = fs.stat; + iso.statSync = fsSync.statSync; + iso.utimes = fs.utimes; + iso.unlink = fs.unlink; + iso.homedir = os.homedir; + iso.tmpdir = os.tmpdir; + iso.writeFileSync = fsSync.writeFileSync; + iso.appendFileSync = fsSync.appendFileSync; + iso.readFileSync = (filename: string, encoding: string) => + fsSync.readFileSync(filename, encoding as BufferEncoding); + iso.unlinkSync = fsSync.unlinkSync; + iso.openFile = fs.open; + iso.gzip = promisify(zlib.gzip); + iso.gunzip = promisify(zlib.gunzip); + iso.loadOptionalModule = (name) => { + const url = getCurrentModuleUrl(); + const load = createRequire(url); + let resolved: string; + try { + // Look beside the SDK, then in the app, for linked or global SDK installs. + resolved = load.resolve(name, { + paths: [path.dirname(fileURLToPath(url)), process.cwd()], + }); + } catch { + return undefined; + } + return load(resolved); + }; + iso.hash = (data) => crypto.createHash("sha256").update(data).digest("hex"); + iso.hmacSha256 = (key, data) => + crypto.createHmac("sha256", key).update(data).digest("hex"); + iso.timingSafeEqual = (left, right) => { + const leftBuffer = Buffer.from(left); + const rightBuffer = Buffer.from(right); + return ( + leftBuffer.length === rightBuffer.length && + crypto.timingSafeEqual(leftBuffer, rightBuffer) + ); + }; + return iso; +} + +function getNearestBraintrustEnvValue(name: string): string | undefined { + for ( + let dir = process.cwd(), depth = 0; + depth <= BRAINTRUST_ENV_SEARCH_PARENT_LIMIT; + dir = path.dirname(dir), depth++ + ) { + const envPath = path.join(dir, ".env.braintrust"); + try { + const parsed = dotenv.parse(fsSync.readFileSync(envPath, "utf8")); + const value = parsed[name]; + return value?.trim() ? value : undefined; + } catch (e) { + if ( + typeof e !== "object" || + e === null || + !("code" in e) || + e.code !== "ENOENT" + ) { + return undefined; + } + } + if (path.dirname(dir) === dir) { + break; + } + } + return undefined; +} diff --git a/js/src/object-fetcher.test.ts b/js/src/object-fetcher.test.ts index 5c6936cdd..be1efbef6 100644 --- a/js/src/object-fetcher.test.ts +++ b/js/src/object-fetcher.test.ts @@ -1,9 +1,6 @@ -import { describe, expect, test, vi } from "vitest"; -import { - DEFAULT_FETCH_BATCH_SIZE, - ObjectFetcher, - type BraintrustState, -} from "./logger"; +import { describe, expect, test, vi } from "vite-plus/test"; +import { DEFAULT_FETCH_BATCH_SIZE, ObjectFetcher } from "./logger/objects"; +import { type BraintrustState } from "./logger/core"; import { configureNode } from "./node/config"; configureNode(); diff --git a/js/src/openai-agents-api.ts b/js/src/openai-agents-api.ts index ba50b2993..3dc2e0ba2 100644 --- a/js/src/openai-agents-api.ts +++ b/js/src/openai-agents-api.ts @@ -1,11 +1,12 @@ +import { registerInstrumentation } from "./instrumentation/registrations/openai"; import { debugLogger } from "./debug-logger"; -import { openAIChannels } from "./instrumentation/plugins/openai-interceptors"; +import { openAIChannels } from "./instrumentation/providers/openai-interceptors"; import { resolveUseLegacyUuidIds } from "./id-gen"; import { _internalExportParentSynchronously, getSpanParentObject, newId, -} from "./logger"; +} from "./logger/core"; import { getCurrentUnixTimestamp } from "./util"; import { SpanComponentsV4 } from "../util/span_identifier_v4"; import type { @@ -127,6 +128,7 @@ export function startOpenAIAgentsTrace( // eslint-disable-next-line @typescript-eslint/no-explicit-any -- Provider types vary between OpenAI SDK versions; validate the narrow surface at runtime. args: any, ): OpenAIAgentsTraceToken { + registerInstrumentation(); const validatedArgs = validateStartArgs(args); const state = createTraceState(); if (!state) { @@ -167,6 +169,7 @@ export async function updateOpenAIAgentsTrace( // eslint-disable-next-line @typescript-eslint/no-explicit-any -- Provider event types vary between OpenAI SDK versions; validate the narrow surface at runtime. event: any, ): Promise { + registerInstrumentation(); const state = token; const validatedEvent = validateEvent(event); try { @@ -195,6 +198,7 @@ export async function failOpenAIAgentsTrace( token: OpenAIAgentsTraceToken, error: unknown, ): Promise { + registerInstrumentation(); const state = token; try { return await openAIChannels.agentsTraceFail.invoke( diff --git a/js/src/openai-batch.ts b/js/src/openai-batch.ts index 0f5692215..cc306714b 100644 --- a/js/src/openai-batch.ts +++ b/js/src/openai-batch.ts @@ -1,5 +1,6 @@ -import { openAIChannels } from "./instrumentation/plugins/openai-interceptors"; -import { getSpanParentObject } from "./logger"; +import { registerInstrumentation } from "./instrumentation/registrations/openai"; +import { openAIChannels } from "./instrumentation/providers/openai-interceptors"; +import { getSpanParentObject } from "./logger/core"; import { createLazyAPIPromise, type EnhancedResponse, @@ -126,6 +127,7 @@ export function openaiFilesCreateTraced< >( files: OpenAIFilesResource, ): (params: TParams, options?: TOptions) => OpenAIAPIPromise { + registerInstrumentation(); return (params, options) => { const parent = getSpanParentObject(); const purpose = read(params, "purpose"); @@ -197,6 +199,7 @@ export function openaiBatchesRetrieveTraced< >( batches: OpenAIBatchesResource, ): (batchId: string, options?: TOptions) => OpenAIAPIPromise { + registerInstrumentation(); return (batchId, options) => { const apiPromise = batches.retrieve(batchId, options); let enhancedResponse: EnhancedResponse | undefined; @@ -255,6 +258,7 @@ export function openaiBatchesRetrieveTraced< export async function completeOpenAIBatchTrace( args: CompleteOpenAIBatchTraceArgs, ): Promise { + registerInstrumentation(); const inputFileContent = Promise.resolve(args.inputFileContent); const outputFileContent = args.outputFileContent === undefined diff --git a/js/src/openai-promise-utils.test.ts b/js/src/openai-promise-utils.test.ts index b1a7c8e4e..bfff79800 100644 --- a/js/src/openai-promise-utils.test.ts +++ b/js/src/openai-promise-utils.test.ts @@ -1,5 +1,5 @@ -import { describe, expect, it } from "vitest"; -import { openAIChannels } from "./instrumentation/plugins/openai-interceptors"; +import { describe, expect, it } from "vite-plus/test"; +import { openAIChannels } from "./instrumentation/providers/openai-interceptors"; import { wrapOpenAI } from "./wrappers/oai"; import { type APIPromise, diff --git a/js/src/openai-utils.ts b/js/src/openai-utils.ts index 5fa850915..9bc0cfa9c 100644 --- a/js/src/openai-utils.ts +++ b/js/src/openai-utils.ts @@ -5,8 +5,8 @@ import { isObject } from "../util/index"; // when OpenAI usage data does not include it. export const BRAINTRUST_CACHED_STREAM_METRIC = "__braintrust_cached_metric"; -export const LEGACY_CACHED_HEADER = "x-cached"; -export const X_CACHED_HEADER = "x-bt-cached"; +const LEGACY_CACHED_HEADER = "x-cached"; +const X_CACHED_HEADER = "x-bt-cached"; /** * Token name mappings for OpenAI metrics. @@ -63,7 +63,7 @@ export function parseMetricsFromUsage(usage: unknown): Record { return metrics; } -export function parseCachedHeader( +function parseCachedHeader( value: string | null | undefined, ): number | undefined { if (!value) { diff --git a/js/src/package-exports.test.ts b/js/src/package-exports.test.ts new file mode 100644 index 000000000..8e6e6622e --- /dev/null +++ b/js/src/package-exports.test.ts @@ -0,0 +1,41 @@ +import { readFileSync } from "node:fs"; +import { describe, expect, test } from "vite-plus/test"; + +const packageJson = JSON.parse( + readFileSync(new URL("../package.json", import.meta.url), "utf8"), +) as { exports: Record }; + +describe("package exports", () => { + test("publishes the curated entrypoints", () => { + const keys = Object.keys(packageJson.exports); + expect(keys.sort()).toEqual( + [ + ".", + "./auto", + "./apply-auto-instrumentation", + "./custom-views", + "./preprocessors", + "./edge-light", + "./esbuild", + "./hook.mjs", + "./instrumentation", + "./next", + "./node", + "./package.json", + "./rollup", + "./vite", + "./vitest-evals-reporter", + "./webpack", + "./workerd", + ].sort(), + ); + }); + + test("does not publish legacy or implementation entrypoints", () => { + expect(packageJson.exports).not.toHaveProperty("./browser"); + expect(packageJson.exports).not.toHaveProperty("./util"); + expect(packageJson.exports).not.toHaveProperty("./webpack-loader"); + expect(packageJson.exports).not.toHaveProperty("./internal"); + expect(packageJson.exports).not.toHaveProperty("./internal/webpack-loader"); + }); +}); diff --git a/js/src/parameters.test.ts b/js/src/parameters.test.ts index f46754929..e72761ad2 100644 --- a/js/src/parameters.test.ts +++ b/js/src/parameters.test.ts @@ -1,11 +1,7 @@ -import { expect, expectTypeOf, test, beforeAll } from "vitest"; -import { - evalParametersSchema, - type InferParameters, - validateParameters, -} from "./eval-parameters"; +import { expect, expectTypeOf, test, beforeAll } from "vite-plus/test"; +import { type InferParameters, validateParameters } from "./eval-parameters"; import { Eval, runEvaluator } from "./framework"; -import { RemoteEvalParameters } from "./logger"; +import { RemoteEvalParameters } from "./logger/prompts"; import { z } from "zod/v3"; import * as z4 from "zod/v4"; import { type ProgressReporter } from "./reporters/types"; @@ -38,8 +34,6 @@ test("mixed Zod v3 and v4 parameters validate and infer parsed outputs", async ( optional: boolean | undefined; model: string; }>(); - expect(evalParametersSchema.parse(schema)).toEqual(schema); - expect(evalParametersSchema.safeParse({ invalid: {} }).success).toBe(false); await expect( validateParameters({ v3Length: "abc", v4Length: "abcd" }, schema), ).resolves.toEqual({ @@ -101,7 +95,6 @@ test("parameters are passed to task", async () => { }, }, new NoopProgressReporter(), - [], undefined, { prefix: "start:", suffix: ":end" }, true, @@ -147,7 +140,6 @@ test("prompt parameter is passed correctly", async () => { }, }, new NoopProgressReporter(), - [], undefined, undefined, true, @@ -229,7 +221,6 @@ test("custom parameter values override defaults", async () => { }, }, new NoopProgressReporter(), - [], undefined, { prefix: "custom:", @@ -261,7 +252,6 @@ test("array parameter is handled correctly", async () => { }, }, new NoopProgressReporter(), - [], undefined, undefined, true, @@ -300,7 +290,6 @@ test("object parameter is handled correctly", async () => { }, }, new NoopProgressReporter(), - [], undefined, undefined, true, @@ -331,7 +320,6 @@ test("model parameter defaults to configured value", async () => { }, }, new NoopProgressReporter(), - [], undefined, undefined, true, @@ -359,7 +347,6 @@ test("model parameter is required when default is missing", async () => { }, }, new NoopProgressReporter(), - [], undefined, undefined, true, diff --git a/js/src/pcm-buffer.test.ts b/js/src/pcm-buffer.test.ts index d2eb4797c..746f0b5ea 100644 --- a/js/src/pcm-buffer.test.ts +++ b/js/src/pcm-buffer.test.ts @@ -1,4 +1,4 @@ -import { expect, test } from "vitest"; +import { expect, test } from "vite-plus/test"; import { PcmBuffer } from "./pcm-buffer"; const MAX = Number.MAX_SAFE_INTEGER; diff --git a/js/src/preprocessors/registry.test.ts b/js/src/preprocessors/registry.test.ts index ae15c7267..322e84a42 100644 --- a/js/src/preprocessors/registry.test.ts +++ b/js/src/preprocessors/registry.test.ts @@ -1,4 +1,4 @@ -import { expect, expectTypeOf, test, vi } from "vitest"; +import { expect, expectTypeOf, test, vi } from "vite-plus/test"; import { customPreprocessor, type CustomPreprocessor, diff --git a/js/src/prompt-cache/cache-config.ts b/js/src/prompt-cache/cache-config.ts index 66dcc5e87..b06fc748c 100644 --- a/js/src/prompt-cache/cache-config.ts +++ b/js/src/prompt-cache/cache-config.ts @@ -1,5 +1,5 @@ import { debugLogger } from "../debug-logger"; -import iso from "../isomorph"; +import iso from "#braintrust-platform"; import { canUseDiskCache, DiskCache } from "./disk-cache"; import { LRUCache } from "../lru-cache"; diff --git a/js/src/prompt-cache/cache-mode.test.ts b/js/src/prompt-cache/cache-mode.test.ts index 0a585b0c2..f7549f66a 100644 --- a/js/src/prompt-cache/cache-mode.test.ts +++ b/js/src/prompt-cache/cache-mode.test.ts @@ -1,10 +1,19 @@ +import { getLoaderCaches } from "../logger/prompts"; import * as fs from "fs/promises"; import * as path from "path"; import { tmpdir } from "os"; -import { afterEach, beforeEach, describe, expect, it, vi } from "vitest"; -import iso from "../isomorph"; +import { + afterEach, + beforeEach, + describe, + expect, + it, + vi, +} from "vite-plus/test"; +import iso from "#braintrust-platform"; import { configureNode } from "../node/config"; -import { BraintrustState, Prompt, RemoteEvalParameters } from "../logger"; +import { BraintrustState } from "../logger/core"; +import { Prompt, RemoteEvalParameters } from "../logger/prompts"; configureNode(); @@ -117,19 +126,27 @@ describe("prompt and parameters cache modes", () => { await configureCacheDirs(); const state = new BraintrustState({}); - await state.promptCache.set(promptKey, testPrompt); - await state.promptCache.set({ ...promptKey, slug: "other" }, testPrompt); - await state.parametersCache.set(parametersKey, testParameters); - await state.parametersCache.set( + await getLoaderCaches(state).promptCache.set(promptKey, testPrompt); + await getLoaderCaches(state).promptCache.set( + { ...promptKey, slug: "other" }, + testPrompt, + ); + await getLoaderCaches(state).parametersCache.set( + parametersKey, + testParameters, + ); + await getLoaderCaches(state).parametersCache.set( { ...parametersKey, slug: "other" }, testParameters, ); const nextState = new BraintrustState({}); - expect(await nextState.promptCache.get(promptKey)).toEqual(testPrompt); - expect(await nextState.parametersCache.get(parametersKey)).toEqual( - testParameters, + expect(await getLoaderCaches(nextState).promptCache.get(promptKey)).toEqual( + testPrompt, ); + expect( + await getLoaderCaches(nextState).parametersCache.get(parametersKey), + ).toEqual(testParameters); }); it("memory mode does not write to disk", async () => { @@ -137,19 +154,28 @@ describe("prompt and parameters cache modes", () => { const { promptDir, parametersDir } = await configureCacheDirs(); const state = new BraintrustState({}); - await state.promptCache.set(promptKey, testPrompt); - await state.parametersCache.set(parametersKey, testParameters); - - expect(await state.promptCache.get(promptKey)).toEqual(testPrompt); - expect(await state.parametersCache.get(parametersKey)).toEqual( + await getLoaderCaches(state).promptCache.set(promptKey, testPrompt); + await getLoaderCaches(state).parametersCache.set( + parametersKey, testParameters, ); + + expect(await getLoaderCaches(state).promptCache.get(promptKey)).toEqual( + testPrompt, + ); + expect( + await getLoaderCaches(state).parametersCache.get(parametersKey), + ).toEqual(testParameters); expect(await fs.readdir(promptDir)).toEqual([]); expect(await fs.readdir(parametersDir)).toEqual([]); const nextState = new BraintrustState({}); - expect(await nextState.promptCache.get(promptKey)).toBeUndefined(); - expect(await nextState.parametersCache.get(parametersKey)).toBeUndefined(); + expect( + await getLoaderCaches(nextState).promptCache.get(promptKey), + ).toBeUndefined(); + expect( + await getLoaderCaches(nextState).parametersCache.get(parametersKey), + ).toBeUndefined(); }); it("disk mode does not warm memory", async () => { @@ -157,20 +183,29 @@ describe("prompt and parameters cache modes", () => { const { promptDir, parametersDir } = await configureCacheDirs(); const state = new BraintrustState({}); - await state.promptCache.set(promptKey, testPrompt); - await state.parametersCache.set(parametersKey, testParameters); + await getLoaderCaches(state).promptCache.set(promptKey, testPrompt); + await getLoaderCaches(state).parametersCache.set( + parametersKey, + testParameters, + ); const nextState = new BraintrustState({}); - expect(await nextState.promptCache.get(promptKey)).toEqual(testPrompt); - expect(await nextState.parametersCache.get(parametersKey)).toEqual( - testParameters, + expect(await getLoaderCaches(nextState).promptCache.get(promptKey)).toEqual( + testPrompt, ); + expect( + await getLoaderCaches(nextState).parametersCache.get(parametersKey), + ).toEqual(testParameters); await fs.rm(promptDir, { recursive: true, force: true }); await fs.rm(parametersDir, { recursive: true, force: true }); - expect(await nextState.promptCache.get(promptKey)).toBeUndefined(); - expect(await nextState.parametersCache.get(parametersKey)).toBeUndefined(); + expect( + await getLoaderCaches(nextState).promptCache.get(promptKey), + ).toBeUndefined(); + expect( + await getLoaderCaches(nextState).parametersCache.get(parametersKey), + ).toBeUndefined(); }); it("none mode disables all cache reads and writes", async () => { @@ -178,11 +213,18 @@ describe("prompt and parameters cache modes", () => { const { promptDir, parametersDir } = await configureCacheDirs(); const state = new BraintrustState({}); - await state.promptCache.set(promptKey, testPrompt); - await state.parametersCache.set(parametersKey, testParameters); + await getLoaderCaches(state).promptCache.set(promptKey, testPrompt); + await getLoaderCaches(state).parametersCache.set( + parametersKey, + testParameters, + ); - expect(await state.promptCache.get(promptKey)).toBeUndefined(); - expect(await state.parametersCache.get(parametersKey)).toBeUndefined(); + expect( + await getLoaderCaches(state).promptCache.get(promptKey), + ).toBeUndefined(); + expect( + await getLoaderCaches(state).parametersCache.get(parametersKey), + ).toBeUndefined(); expect(await fs.readdir(promptDir)).toEqual([]); expect(await fs.readdir(parametersDir)).toEqual([]); }); @@ -194,14 +236,19 @@ describe("prompt and parameters cache modes", () => { await configureCacheDirs(); const state = new BraintrustState({}); - await state.promptCache.set(promptKey, testPrompt); - await state.parametersCache.set(parametersKey, testParameters); + await getLoaderCaches(state).promptCache.set(promptKey, testPrompt); + await getLoaderCaches(state).parametersCache.set( + parametersKey, + testParameters, + ); const nextState = new BraintrustState({}); - expect(await nextState.promptCache.get(promptKey)).toEqual(testPrompt); - expect(await nextState.parametersCache.get(parametersKey)).toEqual( - testParameters, + expect(await getLoaderCaches(nextState).promptCache.get(promptKey)).toEqual( + testPrompt, ); + expect( + await getLoaderCaches(nextState).parametersCache.get(parametersKey), + ).toEqual(testParameters); expect(warnSpy).toHaveBeenCalledTimes(1); expect(warnSpy).toHaveBeenCalledWith( @@ -216,30 +263,36 @@ describe("prompt and parameters cache modes", () => { const state = new BraintrustState({}); for (let i = 0; i < 1025; i++) { - await state.promptCache.set( + await getLoaderCaches(state).promptCache.set( { ...promptKey, slug: `prompt-${i}` }, testPrompt, ); - await state.parametersCache.set( + await getLoaderCaches(state).parametersCache.set( { ...parametersKey, slug: `parameters-${i}` }, testParameters, ); } expect( - await state.promptCache.get({ ...promptKey, slug: "prompt-0" }), + await getLoaderCaches(state).promptCache.get({ + ...promptKey, + slug: "prompt-0", + }), ).toBeUndefined(); expect( - await state.parametersCache.get({ + await getLoaderCaches(state).parametersCache.get({ ...parametersKey, slug: "parameters-0", }), ).toBeUndefined(); expect( - await state.promptCache.get({ ...promptKey, slug: "prompt-1024" }), + await getLoaderCaches(state).promptCache.get({ + ...promptKey, + slug: "prompt-1024", + }), ).toEqual(testPrompt); expect( - await state.parametersCache.get({ + await getLoaderCaches(state).parametersCache.get({ ...parametersKey, slug: "parameters-1024", }), @@ -253,10 +306,17 @@ describe("prompt and parameters cache modes", () => { iso.gzip = undefined; const state = new BraintrustState({}); - await state.promptCache.set(promptKey, testPrompt); - expect(await state.promptCache.get(promptKey)).toBeUndefined(); - await state.parametersCache.set(parametersKey, testParameters); - expect(await state.parametersCache.get(parametersKey)).toBeUndefined(); + await getLoaderCaches(state).promptCache.set(promptKey, testPrompt); + expect( + await getLoaderCaches(state).promptCache.get(promptKey), + ).toBeUndefined(); + await getLoaderCaches(state).parametersCache.set( + parametersKey, + testParameters, + ); + expect( + await getLoaderCaches(state).parametersCache.get(parametersKey), + ).toBeUndefined(); new BraintrustState({}); expect(warnSpy).toHaveBeenCalledTimes(1); diff --git a/js/src/prompt-cache/disk-cache.test.ts b/js/src/prompt-cache/disk-cache.test.ts index ed0664346..eb5bf92b9 100644 --- a/js/src/prompt-cache/disk-cache.test.ts +++ b/js/src/prompt-cache/disk-cache.test.ts @@ -2,7 +2,7 @@ import * as fs from "fs/promises"; import * as path from "path"; import { DiskCache } from "./disk-cache"; import { tmpdir } from "os"; -import { beforeEach, describe, it, afterEach, expect } from "vitest"; +import { beforeEach, describe, it, afterEach, expect } from "vite-plus/test"; import { configureNode } from "../node/config"; describe("DiskCache", () => { diff --git a/js/src/prompt-cache/disk-cache.ts b/js/src/prompt-cache/disk-cache.ts index 79ccd385f..d7aeeaaf3 100644 --- a/js/src/prompt-cache/disk-cache.ts +++ b/js/src/prompt-cache/disk-cache.ts @@ -1,5 +1,5 @@ import { debugLogger } from "../debug-logger"; -import iso from "../isomorph"; +import iso from "#braintrust-platform"; export function canUseDiskCache(): boolean { return !!( diff --git a/js/src/prompt-cache/parameters-cache.test.ts b/js/src/prompt-cache/parameters-cache.test.ts index 7fe320871..96596065c 100644 --- a/js/src/prompt-cache/parameters-cache.test.ts +++ b/js/src/prompt-cache/parameters-cache.test.ts @@ -1,8 +1,8 @@ import * as fs from "fs/promises"; import * as path from "path"; import { tmpdir } from "os"; -import { afterEach, beforeEach, describe, expect, it } from "vitest"; -import { RemoteEvalParameters } from "../logger"; +import { afterEach, beforeEach, describe, expect, it } from "vite-plus/test"; +import { RemoteEvalParameters } from "../logger/prompts"; import { configureNode } from "../node/config"; import { DiskCache } from "./disk-cache"; import { diff --git a/js/src/prompt-cache/parameters-cache.ts b/js/src/prompt-cache/parameters-cache.ts index bb81a7a9c..a32ad58fd 100644 --- a/js/src/prompt-cache/parameters-cache.ts +++ b/js/src/prompt-cache/parameters-cache.ts @@ -1,4 +1,8 @@ -import { RemoteEvalParameters } from "../logger"; +import { + RemoteEvalParameters, + serializeParametersForCache, + type SerializedParameters, +} from "../logger/prompts"; import { LRUCache } from "../lru-cache"; import { DiskCache } from "./disk-cache"; @@ -35,7 +39,7 @@ export type ParametersMemoryCacheEntry = { }; export type ParametersDiskCacheEntry = { - value: ReturnType; + value: SerializedParameters; resolvedOrgIdentity?: string; }; @@ -111,7 +115,7 @@ export class ParametersCache { this.memoryCache?.set(cacheKey, memoryEntry); if (this.diskCache) { await this.diskCache.set(cacheKey, { - value: value._internalSerializeForCache(), + value: serializeParametersForCache(value), resolvedOrgIdentity: this.expectedResolvedOrgIdentity, }); } diff --git a/js/src/prompt-cache/prompt-cache.test.ts b/js/src/prompt-cache/prompt-cache.test.ts index 5c7dbe596..ef392e363 100644 --- a/js/src/prompt-cache/prompt-cache.test.ts +++ b/js/src/prompt-cache/prompt-cache.test.ts @@ -5,9 +5,9 @@ import { type PromptDiskCacheEntry, type PromptMemoryCacheEntry, } from "./prompt-cache"; -import { Prompt } from "../logger"; +import { Prompt } from "../logger/prompts"; import { tmpdir } from "os"; -import { beforeEach, describe, it, afterEach, expect } from "vitest"; +import { beforeEach, describe, it, afterEach, expect } from "vite-plus/test"; import type { PromptKey } from "./prompt-cache"; import { DiskCache } from "./disk-cache"; import { configureNode } from "../node/config"; diff --git a/js/src/prompt-cache/prompt-cache.ts b/js/src/prompt-cache/prompt-cache.ts index eb58ca0c2..14d7083fa 100644 --- a/js/src/prompt-cache/prompt-cache.ts +++ b/js/src/prompt-cache/prompt-cache.ts @@ -1,4 +1,8 @@ -import { Prompt } from "../logger"; +import { + Prompt, + serializePromptForCache, + type SerializedPrompt, +} from "../logger/prompts"; import { LRUCache } from "../lru-cache"; import { DiskCache } from "./disk-cache"; @@ -40,7 +44,7 @@ export type PromptMemoryCacheEntry = { }; export type PromptDiskCacheEntry = { - value: ReturnType; + value: SerializedPrompt; resolvedOrgIdentity?: string; }; @@ -168,7 +172,7 @@ export class PromptCache { this.memoryCache?.set(cacheKey, memoryEntry); if (this.diskCache) { await this.diskCache.set(cacheKey, { - value: value._internalSerializeForCache(), + value: serializePromptForCache(value), resolvedOrgIdentity: this.expectedResolvedOrgIdentity, }); } diff --git a/js/src/prompt-schemas.ts b/js/src/prompt-schemas.ts index c4203f90e..94a433212 100644 --- a/js/src/prompt-schemas.ts +++ b/js/src/prompt-schemas.ts @@ -1,45 +1,26 @@ -import { z } from "zod/v3"; -import { - ToolFunctionDefinition as toolFunctionDefinitionSchema, - type ToolFunctionDefinitionType as ToolFunctionDefinition, - ChatCompletionMessageParam as chatCompletionMessageParamSchema, - ModelParams as modelParamsSchema, - type PromptBlockDataType as PromptBlockData, - type PromptDataType as PromptData, -} from "./generated_types"; +import type { + ToolFunctionDefinition, + ChatCompletionMessageParam, + ModelParams, + PromptBlockData, + PromptData, +} from "./sdk-types"; // This roughly maps to promptBlockDataSchema, but is more ergonomic for the user. -export const promptContentsSchema = z.union([ - z.object({ - prompt: z.string(), - }), - z.object({ - messages: z.array(chatCompletionMessageParamSchema), - }), -]); - -export type PromptContents = z.infer; - -export const promptDefinitionSchema = promptContentsSchema.and( - z.object({ - model: z.string(), - params: modelParamsSchema.optional(), - templateFormat: z.enum(["mustache", "nunjucks", "none"]).optional(), - environments: z.array(z.string()).optional(), - }), -); - -export type PromptDefinition = z.infer; - -export const promptDefinitionWithToolsSchema = promptDefinitionSchema.and( - z.object({ - tools: z.array(toolFunctionDefinitionSchema).optional(), - }), -); - -export type PromptDefinitionWithTools = z.infer< - typeof promptDefinitionWithToolsSchema ->; +export type PromptContents = + | { prompt: string } + | { messages: ChatCompletionMessageParam[] }; + +export type PromptDefinition = PromptContents & { + model: string; + params?: ModelParams; + templateFormat?: "mustache" | "nunjucks" | "none"; + environments?: string[]; +}; + +export type PromptDefinitionWithTools = PromptDefinition & { + tools?: ToolFunctionDefinition[]; +}; export function promptDefinitionToPromptData( promptDefinition: PromptDefinition, diff --git a/js/src/prompt-types.test.ts b/js/src/prompt-types.test.ts new file mode 100644 index 000000000..3730206bc --- /dev/null +++ b/js/src/prompt-types.test.ts @@ -0,0 +1,135 @@ +import { expectTypeOf, test } from "vite-plus/test"; +import type { ChatCompletionCreateParamsNonStreaming } from "openai/resources/chat/completions"; +import type { CompletionCreateParamsNonStreaming } from "openai/resources/completions"; +import { + Prompt, + type CompiledPrompt, + type CompiledPromptParams, + type DefaultPromptArgs, + type PromptRowWithId, +} from "./logger/prompts"; +import type { CodeParameters, CodePrompt } from "./framework2"; +import type { + AnyModelParamsType, + FunctionDataType, + PromptDataType, + PromptType, +} from "./generated_plain_types"; + +test("compiled prompts preserve provider parameter types and compatibility", () => { + // The generated backend types are a compatibility reference, not the public API. + type BackendParams = Omit< + AnyModelParamsType, + "use_cache" | "response_format" | "reasoning_effort" + > & { model: string }; + type Params = Omit< + CompiledPromptParams, + "response_format" | "reasoning_effort" + >; + expectTypeOf().toMatchTypeOf(); + expectTypeOf().toMatchTypeOf(); + expectTypeOf().toEqualTypeOf(); + expectTypeOf< + CompiledPrompt<"chat"> + >().toMatchTypeOf(); + expectTypeOf< + CompiledPrompt<"completion"> + >().toMatchTypeOf(); + expectTypeOf().toEqualTypeOf< + "minimal" | "low" | "medium" | "high" | undefined + >(); + expectTypeOf().toEqualTypeOf< + boolean | undefined + >(); + + if (false) { + const prompt = null as unknown as Prompt; + // @ts-expect-error Cache serialization is internal, not a Prompt method. + prompt._internalSerializeForCache(); + expectTypeOf(prompt.build({})).toEqualTypeOf>(); + expectTypeOf(prompt.build({}, { flavor: "completion" })).toEqualTypeOf< + CompiledPrompt<"completion"> + >(); + expectTypeOf(prompt.buildWithAttachments({})).toEqualTypeOf< + Promise> + >(); + const defaults: DefaultPromptArgs = { + response_format: { + type: "json_schema", + json_schema: { + name: "result", + schema: { type: "object" }, + strict: null, + }, + }, + use_cache: true, + }; + new Prompt( + { id: "id", _xact_id: "version", name: "name", slug: "slug" }, + defaults, + false, + ); + defaults.response_format = { + type: "json_schema", + json_schema: { + name: "result", + // @ts-expect-error Compiled schemas are objects, not templates. + schema: "{{schema}}", + }, + }; + // @ts-expect-error Preserve compatibility with older OpenAI clients. + defaults.reasoning_effort = "none"; + } +}); + +test("prompt metadata preserves required and optional identifiers", () => { + type Metadata = Omit< + PromptType, + "log_id" | "org_id" | "project_id" | "id" | "_xact_id" + > & { + project_id?: string; + }; + type RequiredRow = Metadata & { id: string; _xact_id: string }; + type OptionalRow = Metadata & { id?: string; _xact_id?: string }; + expectTypeOf>().toMatchTypeOf(); + expectTypeOf().toMatchTypeOf>(); + expectTypeOf().toEqualTypeOf(); + expectTypeOf>().toMatchTypeOf(); + expectTypeOf().toMatchTypeOf>(); + expectTypeOf["id"]>().toEqualTypeOf(); + expectTypeOf["_xact_id"]>().toEqualTypeOf< + string | undefined + >(); + expectTypeOf["id"]>().toEqualTypeOf< + string | undefined + >(); + expectTypeOf< + PromptRowWithId["_xact_id"] + >().toEqualTypeOf(); + expectTypeOf["id"]>().toEqualTypeOf(); + expectTypeOf["id"]>().toEqualTypeOf< + string | undefined + >(); +}); + +test("function definitions expose only the variant each builder produces", () => { + type PromptEvent = Awaited>; + type ParametersEvent = Awaited< + ReturnType + >; + expectTypeOf().toEqualTypeOf<{ + type: "prompt"; + }>(); + expectTypeOf< + PromptEvent["prompt_data"] + >().branded.toEqualTypeOf(); + expectTypeOf< + ParametersEvent["function_data"]["type"] + >().toEqualTypeOf<"parameters">(); + expectTypeOf< + ParametersEvent["function_type"] + >().toEqualTypeOf<"parameters">(); + expectTypeOf().toMatchTypeOf< + Extract + >(); +}); diff --git a/js/src/prompt.test.ts b/js/src/prompt.test.ts index 3e18470ff..e121c042f 100644 --- a/js/src/prompt.test.ts +++ b/js/src/prompt.test.ts @@ -1,6 +1,6 @@ -import { beforeAll, describe, test, expect, vi } from "vitest"; +import { beforeAll, describe, test, expect, vi } from "vite-plus/test"; import { configureNode } from "./node/config"; -import { Prompt } from "./logger"; +import { Prompt } from "./logger/prompts"; import { type PromptDataType as PromptData } from "./generated_types"; describe("prompt strict mode", () => { @@ -270,7 +270,7 @@ describe("prompt template_format", () => { describe("prompt template_format (unconfigured/browser-like)", () => { test("throws unsupported error for nunjucks template_format when not configured", async () => { vi.resetModules(); - const { Prompt: UnconfiguredPrompt } = await import("./logger"); + const { Prompt: UnconfiguredPrompt } = await import("./logger/prompts"); const prompt = new UnconfiguredPrompt( { @@ -308,7 +308,8 @@ describe("prompt template_format (unconfigured/browser-like)", () => { test("throws unsupported error after configureBrowser()", async () => { vi.resetModules(); const { configureBrowser } = await import("./browser/config"); - const { Prompt: BrowserConfiguredPrompt } = await import("./logger"); + const { Prompt: BrowserConfiguredPrompt } = + await import("./logger/prompts"); configureBrowser(); diff --git a/js/src/propagation.test.ts b/js/src/propagation.test.ts index 062cb16d2..98b4b01fb 100644 --- a/js/src/propagation.test.ts +++ b/js/src/propagation.test.ts @@ -7,15 +7,23 @@ * propagation path (no `@opentelemetry/api` dependency). */ -import { afterEach, beforeEach, describe, expect, test, vi } from "vitest"; import { - _exportsForTestingOnly, + afterEach, + beforeEach, + describe, + expect, + test, + vi, +} from "vite-plus/test"; +import { _exportsForTestingOnly } from "./logger/testing"; +import { + _internalStartSpan, _injectIntoCarrier, extractTraceContextFromHeaders, initLogger, injectTraceContext, startSpan, -} from "./logger"; +} from "./logger/core"; import { BAGGAGE_HEADER, BRAINTRUST_PARENT_KEY, @@ -1072,14 +1080,14 @@ describe("inject / extract / round-trip", () => { root_span_id: pRoot, }).toStr(); - const child = logger.startSpan({ name: "child", parent: slug }); + const child = _internalStartSpan({ name: "child", parent: slug }); expect(child.rootSpanId).toBe(pRoot); expect(child.spanParents).toEqual([pSpan]); child.end(); }); test("legacy parent slug (UUID) linked in hex mode", () => { - const logger = makeLogger(); + makeLogger(); const pSpan = uuidv4(); const pRoot = uuidv4(); const legacySlug = new SpanComponentsV3({ @@ -1090,7 +1098,7 @@ describe("inject / extract / round-trip", () => { root_span_id: pRoot, }).toStr(); - const child = logger.startSpan({ name: "child", parent: legacySlug }); + const child = _internalStartSpan({ name: "child", parent: legacySlug }); // Links to the slug's UUID ids; the child's own span id stays hex. expect(child.rootSpanId).toBe(pRoot); expect(child.spanParents).toEqual([pSpan]); @@ -1110,7 +1118,7 @@ describe("inject / extract / round-trip", () => { root_span_id: pRoot, }).toStr(); - const child = startSpan({ name: "child", parent: legacySlug }); + const child = _internalStartSpan({ name: "child", parent: legacySlug }); expect(child.rootSpanId).toBe(pRoot); expect(child.spanParents).toEqual([pSpan]); expect(child.spanId.length).toBe(16); @@ -1267,7 +1275,7 @@ describe("legacy UUID mode", () => { }); test("legacy parent slug (UUID) linked in legacy mode", () => { - const logger = initLogger({ projectName: "legacy-proj" }); + initLogger({ projectName: "legacy-proj" }); const pSpan = uuidv4(); const pRoot = uuidv4(); const legacySlug = new SpanComponentsV3({ @@ -1278,14 +1286,14 @@ describe("legacy UUID mode", () => { root_span_id: pRoot, }).toStr(); - const child = logger.startSpan({ name: "child", parent: legacySlug }); + const child = _internalStartSpan({ name: "child", parent: legacySlug }); expect(child.rootSpanId).toBe(pRoot); expect(child.spanParents).toEqual([pSpan]); child.end(); }); test("hex parent slug linked in legacy mode", () => { - const logger = initLogger({ projectName: "legacy-proj" }); + initLogger({ projectName: "legacy-proj" }); const pSpan = "00f067aa0ba902b7"; // 8-byte hex const pRoot = "4bf92f3577b34da6a3ce929d0e0e4736"; // 16-byte hex const hexSlug = new SpanComponentsV4({ @@ -1296,7 +1304,7 @@ describe("legacy UUID mode", () => { root_span_id: pRoot, }).toStr(); - const child = logger.startSpan({ name: "child", parent: hexSlug }); + const child = _internalStartSpan({ name: "child", parent: hexSlug }); // Links to the slug's hex ids; the child's own span id stays UUID. expect(child.rootSpanId).toBe(pRoot); expect(child.spanParents).toEqual([pSpan]); diff --git a/js/src/propagation.ts b/js/src/propagation.ts index a8246cce1..1733547aa 100644 --- a/js/src/propagation.ts +++ b/js/src/propagation.ts @@ -26,7 +26,7 @@ const DEFAULT_TRACE_FLAGS = "01"; * not sampled), kept raw so any future flag bits survive a parse -> format * round trip without per-bit handling. */ -export interface ParsedTraceparent { +interface ParsedTraceparent { traceId: string; spanId: string; traceFlags: string; diff --git a/js/src/public-types.test.ts b/js/src/public-types.test.ts new file mode 100644 index 000000000..19989155c --- /dev/null +++ b/js/src/public-types.test.ts @@ -0,0 +1,95 @@ +import { describe, expect, expectTypeOf, test } from "vite-plus/test"; + +import * as publicExports from "./exports"; +import type { + ContextManager, + ContextParentSpanIds, + CurrentSpanStore, + Dataset, + WorkflowEvalStore, + EvalScorer, + EvalTask, + LangChainCallbackHandlerOptions, + PropagationContext, + Span, + StartSpanArgs, + TemplateRendererPlugin, + Trace, +} from "./exports"; +import { + BraintrustLangChainCallbackHandler, + init, + initDataset, + withParent, + wrapVitest, +} from "./exports"; + +test("exports only intentional extension types", () => { + expectTypeOf().toBeObject(); + expectTypeOf().toBeObject(); + expectTypeOf().toBeObject(); + expectTypeOf().toBeObject(); + expectTypeOf().toBeObject(); + expectTypeOf().toBeObject(); + expectTypeOf(publicExports.configureContextManager).toBeFunction(); + expectTypeOf().toBeObject(); + expectTypeOf().toBeObject(); + expectTypeOf().toBeObject(); + expectTypeOf().toBeObject(); + expectTypeOf(BraintrustLangChainCallbackHandler).toBeConstructibleWith(); + expectTypeOf< + EvalTask + >().toBeFunction(); + expectTypeOf>().toBeFunction(); +}); + +test("does not expose runtime schemas or implementation helpers", () => { + for (const name of [ + "AttachmentReference", + "braintrustStreamChunkSchema", + "logs3OverflowUploadSchema", + "promptContentsSchema", + "promptDefinitionSchema", + "promptDefinitionWithToolsSchema", + "SpanImpl", + "IDGenerator", + "_exportsForTestingOnly", + "serializePromptForCache", + "serializeParametersForCache", + "default", + ]) { + expect(publicExports).not.toHaveProperty(name); + } +}); + +test("accepts only canonical v4 call shapes", () => { + if (false) { + init({ project: "project" }); + initDataset({ project: "project", dataset: "dataset" }); + + // @ts-expect-error The string-first overload was removed in v4. + init("project"); + // @ts-expect-error The string-first overload was removed in v4. + initDataset("project", { dataset: "dataset" }); + // @ts-expect-error Exported span slugs are internal integration plumbing. + withParent("exported-span", () => undefined); + + const dataset = null as unknown as Dataset; + // @ts-expect-error Dataset records use `expected`, not the legacy `output` alias. + dataset.insert({ input: "input", output: "expected" }); + + const span = null as unknown as Span; + // @ts-expect-error Spans end with `end()`; the `close()` alias was removed. + span.close(); + } + + expect(true).toBe(true); +}); + +test("preserves the current Vitest expect API", () => { + if (false) { + const wrapped = wrapVitest({ test, describe, expect }); + expectTypeOf(wrapped.expect).toEqualTypeOf(); + wrapped.expect(1, "named assertion").toBe(1); + } +}); diff --git a/js/src/queue.test.ts b/js/src/queue.test.ts index 32cb4b593..2a7496048 100644 --- a/js/src/queue.test.ts +++ b/js/src/queue.test.ts @@ -1,4 +1,4 @@ -import { expect, test } from "vitest"; +import { expect, test } from "vite-plus/test"; import { Queue } from "./queue"; test("Queue basic operations", () => { diff --git a/js/src/queue.ts b/js/src/queue.ts index 848a70bce..b43c73ec7 100644 --- a/js/src/queue.ts +++ b/js/src/queue.ts @@ -24,7 +24,7 @@ export class Queue { * Set queue size limit enforcement. When enabled, the queue will drop new items * when it reaches maxSize. When disabled (default), the queue can grow unlimited. */ - enforceQueueSizeLimit(enforce: boolean) { + enforceQueueSizeLimit(enforce: boolean): void { this.enforceSizeLimit = enforce; } diff --git a/js/src/reporters/progress.ts b/js/src/reporters/progress.ts index 54746a8dd..0fe8f9aa4 100644 --- a/js/src/reporters/progress.ts +++ b/js/src/reporters/progress.ts @@ -2,10 +2,10 @@ import { sdkConsole } from "../logs/console"; import type { ProgressReporter } from "./types"; export class SimpleProgressReporter implements ProgressReporter { - public start(name: string, _total: number) { + public start(name: string, _total: number): void { sdkConsole.log(`Running evaluator ${name}`); } - public stop() {} - public increment(_name: string) {} - public setTotal(_name: string, _total: number) {} + public stop(): void {} + public increment(_name: string): void {} + public setTotal(_name: string, _total: number): void {} } diff --git a/js/src/reporters/types.ts b/js/src/reporters/types.ts index f68724bf6..a85be9bdb 100644 --- a/js/src/reporters/types.ts +++ b/js/src/reporters/types.ts @@ -32,7 +32,7 @@ export interface ReporterBody { /** * A function that takes all evaluator results and returns a boolean indicating - * whether the run was successful. If you return false, the `braintrust eval` + * whether the run was successful. If you return false, the `bt eval` * command will exit with a non-zero status code. * * @param reports diff --git a/js/src/sandbox.ts b/js/src/sandbox.ts index 8517c801d..678169235 100644 --- a/js/src/sandbox.ts +++ b/js/src/sandbox.ts @@ -1,13 +1,13 @@ import { z } from "zod/v3"; import { slugify } from "../util/string_util"; -import { type IfExistsType } from "./generated_types"; -import { type BraintrustState, _internalGetGlobalState } from "./logger"; +import type { IfExists } from "./sdk-types"; +import { type BraintrustState, _internalGetGlobalState } from "./logger/core"; /** * Configuration for a sandbox runtime. * @internal */ -export interface SandboxConfig { +interface SandboxConfig { /** The sandbox provider. Currently only "modal" is supported. */ provider: "modal"; /** Reference to the sandbox snapshot. */ @@ -18,7 +18,7 @@ export interface SandboxConfig { * Options for registering a sandbox function. * @internal */ -export interface RegisterSandboxOptions { +interface RegisterSandboxOptions { /** Group name for the sandbox functions. */ name: string; /** Name of the project to register the sandbox in. */ @@ -32,7 +32,7 @@ export interface RegisterSandboxOptions { /** Optional metadata. */ metadata?: Record; /** What to do if function already exists. Defaults to "replace". */ - ifExists?: IfExistsType; + ifExists?: IfExists; /** Braintrust API key. Uses BRAINTRUST_API_KEY env var if not provided. */ apiKey?: string; /** Braintrust app URL. Uses default if not provided. */ @@ -47,7 +47,7 @@ export interface RegisterSandboxOptions { * Result of registering a sandbox. * @internal */ -export interface RegisterSandboxResult { +interface RegisterSandboxResult { /** Project ID the sandbox is registered in. */ projectId: string; /** Registered eval functions discovered from this sandbox. */ diff --git a/js/src/sdk-contracts.test.ts b/js/src/sdk-contracts.test.ts new file mode 100644 index 000000000..4f88150ed --- /dev/null +++ b/js/src/sdk-contracts.test.ts @@ -0,0 +1,252 @@ +import { expectTypeOf, test } from "vite-plus/test"; +import type { z } from "zod/v3"; +import type * as Backend from "./generated_plain_types"; +import type * as backendSchemas from "./generated_types"; +import type * as SDK from "./sdk-types"; +import type * as schemas from "./sdk-schemas"; + +// Backend definitions are compatibility fixtures, never production dependencies. +test("SDK-owned contracts remain compatible with the backend", () => { + expectTypeOf().toEqualTypeOf(); + expectTypeOf().toEqualTypeOf(); + expectTypeOf().toEqualTypeOf(); + expectTypeOf().toEqualTypeOf(); + expectTypeOf().toEqualTypeOf(); + expectTypeOf().toEqualTypeOf(); + expectTypeOf< + Exclude + >().toEqualTypeOf(); + expectTypeOf().toEqualTypeOf(); + expectTypeOf().toEqualTypeOf(); + expectTypeOf().toEqualTypeOf(); + expectTypeOf().toEqualTypeOf(); + expectTypeOf().toEqualTypeOf(); + expectTypeOf().toEqualTypeOf(); + expectTypeOf().toEqualTypeOf(); + expectTypeOf().toEqualTypeOf(); + expectTypeOf().toEqualTypeOf(); + expectTypeOf().toEqualTypeOf(); + expectTypeOf().toEqualTypeOf(); + expectTypeOf().toEqualTypeOf(); + expectTypeOf().toEqualTypeOf(); + expectTypeOf().toEqualTypeOf(); + expectTypeOf().branded.toEqualTypeOf(); + expectTypeOf< + Exclude + >().toEqualTypeOf(); + expectTypeOf().branded.toEqualTypeOf(); + // Structural equality accommodates explicit fields replacing mapped intersections. + expectTypeOf().branded.toEqualTypeOf(); + expectTypeOf().toExtend(); + expectTypeOf().toExtend(); + expectTypeOf().toEqualTypeOf(); + expectTypeOf().toEqualTypeOf(); + expectTypeOf().toEqualTypeOf(); + expectTypeOf().toEqualTypeOf(); + expectTypeOf().toEqualTypeOf(); + expectTypeOf().toEqualTypeOf(); + expectTypeOf().toEqualTypeOf(); + expectTypeOf().toEqualTypeOf(); + expectTypeOf().toEqualTypeOf(); + expectTypeOf().toEqualTypeOf(); + expectTypeOf().toEqualTypeOf(); + expectTypeOf().toEqualTypeOf(); + expectTypeOf().toEqualTypeOf(); + expectTypeOf().toEqualTypeOf(); + expectTypeOf().branded.toEqualTypeOf(); + expectTypeOf().toEqualTypeOf(); + expectTypeOf().toEqualTypeOf(); + expectTypeOf().branded.toEqualTypeOf(); + expectTypeOf().branded.toEqualTypeOf(); + expectTypeOf().toEqualTypeOf(); + expectTypeOf().toEqualTypeOf(); + expectTypeOf().toEqualTypeOf(); + expectTypeOf().toEqualTypeOf(); + expectTypeOf().toEqualTypeOf(); + expectTypeOf().toEqualTypeOf(); +}); + +test("runtime validators accept and return compatible types", () => { + expectTypeOf>().toExtend< + z.input + >(); + expectTypeOf>().toExtend< + z.input + >(); + expectTypeOf>().toExtend< + z.output + >(); + expectTypeOf>().toExtend< + z.output + >(); + expectTypeOf>().toExtend< + z.input + >(); + expectTypeOf>().toExtend< + z.input + >(); + expectTypeOf>().toExtend< + z.output + >(); + expectTypeOf>().toExtend< + z.output + >(); + expectTypeOf>().toExtend< + z.input + >(); + expectTypeOf>().toExtend< + z.input + >(); + expectTypeOf>().toExtend< + z.output + >(); + expectTypeOf>().toExtend< + z.output + >(); + expectTypeOf>().toExtend< + z.input + >(); + expectTypeOf>().toExtend< + z.input + >(); + expectTypeOf>().toExtend< + z.output + >(); + expectTypeOf>().toExtend< + z.output + >(); + expectTypeOf< + z.input + >().toExtend>(); + expectTypeOf< + z.input + >().toExtend>(); + expectTypeOf< + z.output + >().toExtend>(); + expectTypeOf< + z.output + >().toExtend>(); + + expectTypeOf>().toExtend< + z.input + >(); + expectTypeOf>().toExtend< + z.input + >(); + expectTypeOf>().toExtend< + z.output + >(); + expectTypeOf>().toExtend< + z.output + >(); + expectTypeOf>().toExtend< + z.input + >(); + expectTypeOf< + z.input + >().toExtend>(); + expectTypeOf>().toExtend< + z.output + >(); + expectTypeOf< + z.output + >().toExtend>(); + + expectTypeOf>().toExtend< + z.input + >(); + expectTypeOf>().toExtend< + z.input + >(); + expectTypeOf>().toExtend< + z.output + >(); + expectTypeOf>().toExtend< + z.output + >(); + expectTypeOf>().toExtend< + z.input + >(); + expectTypeOf>().toExtend< + z.input + >(); + expectTypeOf>().toExtend< + z.output + >(); + expectTypeOf>().toExtend< + z.output + >(); + expectTypeOf>().toExtend< + z.input + >(); + expectTypeOf>().toExtend< + z.input + >(); + expectTypeOf>().toExtend< + z.output + >(); + expectTypeOf>().toExtend< + z.output + >(); + expectTypeOf>().toExtend< + z.input + >(); + expectTypeOf>().toExtend< + z.input + >(); + expectTypeOf>().toExtend< + z.output + >(); + expectTypeOf>().toExtend< + z.output + >(); + expectTypeOf>().toExtend< + z.input + >(); + expectTypeOf>().toExtend< + z.input + >(); + expectTypeOf>().toExtend< + z.output + >(); + expectTypeOf>().toExtend< + z.output + >(); + expectTypeOf>().toExtend< + z.input + >(); + expectTypeOf>().toExtend< + z.input + >(); + expectTypeOf>().toExtend< + z.output + >(); + expectTypeOf>().toExtend< + z.output + >(); + expectTypeOf>().toExtend< + z.input + >(); + expectTypeOf>().toExtend< + z.input + >(); + expectTypeOf>().toExtend< + z.output + >(); + expectTypeOf>().toExtend< + z.output + >(); + expectTypeOf>().toExtend< + z.input + >(); + expectTypeOf>().toExtend< + z.input + >(); + expectTypeOf>().toExtend< + z.output + >(); + expectTypeOf>().toExtend< + z.output + >(); +}); diff --git a/js/src/sdk-schemas.ts b/js/src/sdk-schemas.ts new file mode 100644 index 000000000..8186bf138 --- /dev/null +++ b/js/src/sdk-schemas.ts @@ -0,0 +1,660 @@ +/** + * Runtime validators for the SDK-owned subset of the Braintrust API. + * Input/output type compatibility is checked in sdk-contracts.test.ts. + */ +import { z } from "zod/v3"; +import type * as SDK from "./sdk-types"; + +const ResponseFormatJsonSchema: z.ZodType = + /* @__PURE__ */ (() => + z.object({ + name: z.string(), + description: z.string().optional(), + schema: z + .union([z.object({}).partial().passthrough(), z.string()]) + .optional(), + strict: z.union([z.boolean(), z.null()]).optional(), + }))(); + +const ResponseFormatNullish = /* @__PURE__ */ (() => + z.union([ + z.object({ type: z.literal("json_object") }), + z.object({ + type: z.literal("json_schema"), + json_schema: ResponseFormatJsonSchema, + }), + z.object({ type: z.literal("text") }), + z.null(), + ]))(); + +const BraintrustAttachmentReferenceObject = /* @__PURE__ */ (() => + z.object({ + type: z.literal("braintrust_attachment"), + filename: z.string().min(1), + content_type: z.string().min(1), + key: z.string().min(1), + }))(); + +const ExternalAttachmentReferenceObject = /* @__PURE__ */ (() => + z.object({ + type: z.literal("external_attachment"), + filename: z.string().min(1), + content_type: z.string().min(1), + url: z.string().min(1), + }))(); + +export const AttachmentReference: z.ZodType = + /* @__PURE__ */ (() => + z.discriminatedUnion("type", [ + BraintrustAttachmentReferenceObject, + ExternalAttachmentReferenceObject, + ]))(); + +const UploadStatus = /* @__PURE__ */ (() => + z.enum(["uploading", "done", "error"]))(); + +export const AttachmentStatus: z.ZodType = + /* @__PURE__ */ (() => + z.object({ + upload_status: UploadStatus, + error_message: z.string().optional(), + }))(); + +const FunctionTypeEnum = /* @__PURE__ */ (() => + z.enum([ + "llm", + "scorer", + "task", + "tool", + "custom_view", + "preprocessor", + "facet", + "classifier", + "tag", + "parameters", + "sandbox", + ]))(); + +const SavedFunctionId = /* @__PURE__ */ (() => + z.union([ + z.object({ + type: z.literal("function"), + id: z.string(), + version: z.string().optional(), + }), + z.object({ + type: z.literal("global"), + name: z.string(), + function_type: FunctionTypeEnum.optional().default("scorer"), + }), + ]))(); + +const BraintrustModelParamsObject = /* @__PURE__ */ (() => + z + .object({ + use_cache: z.boolean(), + reasoning_enabled: z.boolean(), + reasoning_budget: z.number(), + }) + .partial())(); + +export const CallEvent: z.ZodType = /* @__PURE__ */ (() => + z.union([ + z.object({ + id: z.string().optional(), + data: z.string(), + event: z.literal("text_delta"), + }), + z.object({ + id: z.string().optional(), + data: z.string(), + event: z.literal("reasoning_delta"), + }), + z.object({ + id: z.string().optional(), + data: z.string(), + event: z.literal("json_delta"), + }), + z.object({ + id: z.string().optional(), + data: z.string(), + event: z.literal("progress"), + }), + z.object({ + id: z.string().optional(), + data: z.string(), + event: z.literal("error"), + }), + z.object({ + id: z.string().optional(), + data: z.string(), + event: z.literal("console"), + }), + z.object({ + id: z.string().optional(), + event: z.literal("start"), + data: z.literal(""), + }), + z.object({ + id: z.string().optional(), + event: z.literal("done"), + data: z.literal(""), + }), + ]))(); + +const CacheControl = /* @__PURE__ */ (() => + z.object({ + type: z.literal("ephemeral"), + ttl: z.enum(["5m", "1h"]).optional(), + }))(); + +const ChatCompletionContentPartText = /* @__PURE__ */ (() => + z.object({ + text: z.string().default(""), + type: z.literal("text"), + cache_control: CacheControl.optional(), + }))(); + +const ChatCompletionContentPartImageWithTitle = /* @__PURE__ */ (() => + z.object({ + image_url: z.object({ + url: z.string(), + detail: z + .union([z.literal("auto"), z.literal("low"), z.literal("high")]) + .optional(), + }), + type: z.literal("image_url"), + cache_control: CacheControl.optional(), + }))(); + +const ChatCompletionContentPartFileFile = /* @__PURE__ */ (() => + z + .object({ + file_data: z.string(), + filename: z.string(), + file_id: z.string(), + }) + .partial())(); + +const ChatCompletionContentPartFileWithTitle = /* @__PURE__ */ (() => + z.object({ + file: ChatCompletionContentPartFileFile, + type: z.literal("file"), + cache_control: CacheControl.optional(), + }))(); + +const ChatCompletionContentPartInputAudioWithTitle = /* @__PURE__ */ (() => + z.object({ + input_audio: z.object({ data: z.string(), format: z.enum(["wav", "mp3"]) }), + type: z.literal("input_audio"), + cache_control: CacheControl.optional(), + }))(); +const ChatCompletionContentPart = /* @__PURE__ */ (() => + z.union([ + ChatCompletionContentPartText, + ChatCompletionContentPartImageWithTitle, + ChatCompletionContentPartInputAudioWithTitle, + ChatCompletionContentPartFileWithTitle, + ]))(); + +const ChatCompletionMessageToolCall = /* @__PURE__ */ (() => + z.object({ + id: z.string(), + function: z.object({ arguments: z.string(), name: z.string() }), + type: z.literal("function"), + }))(); + +const ChatCompletionMessageReasoning = /* @__PURE__ */ (() => + z.object({ id: z.string(), content: z.string() }).partial())(); + +const ChatCompletionMessageParam = /* @__PURE__ */ (() => + z.union([ + z.object({ + content: z.union([z.string(), z.array(ChatCompletionContentPartText)]), + role: z.literal("system"), + name: z.string().optional(), + }), + z.object({ + content: z.union([z.string(), z.array(ChatCompletionContentPart)]), + role: z.literal("user"), + name: z.string().optional(), + }), + z.object({ + role: z.literal("assistant"), + content: z + .union([z.string(), z.array(ChatCompletionContentPartText), z.null()]) + .optional(), + function_call: z + .object({ arguments: z.string(), name: z.string() }) + .optional(), + name: z.string().optional(), + tool_calls: z.array(ChatCompletionMessageToolCall).optional(), + reasoning: z.array(ChatCompletionMessageReasoning).optional(), + reasoning_signature: z.string().optional(), + }), + z.object({ + content: z.union([z.string(), z.array(ChatCompletionContentPartText)]), + role: z.literal("tool"), + tool_call_id: z.string().default(""), + }), + z.object({ + content: z.union([z.string(), z.null()]), + name: z.string(), + role: z.literal("function"), + }), + z.object({ + content: z.union([z.string(), z.array(ChatCompletionContentPartText)]), + role: z.literal("developer"), + name: z.string().optional(), + }), + z.object({ + role: z.literal("model"), + content: z.union([z.string(), z.null()]).optional(), + }), + ]))(); + +export const ChatCompletionTool: z.ZodType = + /* @__PURE__ */ (() => + z.object({ + function: z.object({ + name: z.string(), + description: z.string().optional(), + parameters: z.object({}).partial().passthrough().optional(), + }), + type: z.literal("function"), + }))(); + +export const ObjectReference: z.ZodType = + /* @__PURE__ */ (() => + z.object({ + object_type: z.enum([ + "project_logs", + "experiment", + "dataset", + "prompt", + "function", + "prompt_session", + ]), + object_id: z.string().uuid(), + id: z.string(), + _xact_id: z.union([z.string(), z.null()]).optional(), + created: z.union([z.string(), z.null()]).optional(), + }))(); + +export const DatasetSnapshot: z.ZodType = + /* @__PURE__ */ (() => + z.object({ + id: z.string().uuid(), + dataset_id: z.string().uuid(), + name: z.string(), + description: z.union([z.string(), z.null()]), + xact_id: z.string(), + created: z.union([z.string(), z.null()]), + }))(); + +const PromptBlockDataNullish = /* @__PURE__ */ (() => + z.union([ + z.object({ + type: z.literal("chat"), + messages: z.array(ChatCompletionMessageParam), + tools: z.string().optional(), + }), + z.object({ type: z.literal("completion"), content: z.string() }), + z.null(), + ]))(); + +const ModelParams = /* @__PURE__ */ (() => + z.union([ + BraintrustModelParamsObject.extend({ + temperature: z.number(), + top_p: z.number(), + max_tokens: z.number(), + max_completion_tokens: z.number(), + frequency_penalty: z.number(), + presence_penalty: z.number(), + response_format: ResponseFormatNullish, + tool_choice: z.union([ + z.literal("auto"), + z.literal("none"), + z.literal("required"), + z.object({ + type: z.literal("function"), + function: z.object({ name: z.string() }), + }), + ]), + function_call: z.union([ + z.literal("auto"), + z.literal("none"), + z.object({ name: z.string() }), + ]), + n: z.number(), + stop: z.array(z.string()), + chat_template_kwargs: z + .object({ enable_thinking: z.boolean() }) + .partial() + .passthrough(), + reasoning_effort: z.enum(["none", "minimal", "low", "medium", "high"]), + verbosity: z.enum(["low", "medium", "high"]), + }) + .partial() + .passthrough(), + BraintrustModelParamsObject.extend({ + max_tokens: z.number(), + temperature: z.number(), + top_p: z.number().optional(), + top_k: z.number().optional(), + stop_sequences: z.array(z.string()).optional(), + max_tokens_to_sample: z.number().optional(), + }).passthrough(), + BraintrustModelParamsObject.extend({ + temperature: z.number(), + maxOutputTokens: z.number(), + topP: z.number(), + topK: z.number(), + }) + .partial() + .passthrough(), + BraintrustModelParamsObject.extend({ + temperature: z.number(), + topK: z.number(), + }) + .partial() + .passthrough(), + BraintrustModelParamsObject.passthrough(), + ]))(); + +const PromptOptionsNullish = /* @__PURE__ */ (() => + z.union([ + z + .object({ + model: z.string(), + params: ModelParams, + position: z.string(), + endpoint_name: z.union([z.string(), z.null()]), + }) + .partial(), + z.null(), + ]))(); + +const PromptParserNullish = /* @__PURE__ */ (() => + z.union([ + z.object({ + type: z.literal("llm_classifier"), + use_cot: z.boolean(), + choice_scores: z.record(z.number().gte(0).lte(1)).optional(), + choice: z.array(z.string()).optional(), + allow_no_match: z.boolean().optional(), + allow_skip: z.boolean().optional(), + }), + z.null(), + ]))(); + +const PreprocessorId = /* @__PURE__ */ (() => + z.union([ + z.object({ + type: z.literal("function"), + id: z.string(), + version: z.string().optional(), + }), + z.object({ + type: z.literal("global"), + name: z.string(), + function_type: z + .literal("preprocessor") + .optional() + .default("preprocessor"), + }), + z.object({ type: z.literal("inline"), code: z.string().min(1) }), + z.null(), + ]))(); + +const FunctionFormat = /* @__PURE__ */ (() => + z.enum(["llm", "code", "global", "graph", "topic_map"]))(); + +export const PromptData: z.ZodType< + SDK.PromptData, + z.ZodTypeDef, + SDK.SchemaInput +> = /* @__PURE__ */ (() => + z + .object({ + prompt: PromptBlockDataNullish, + options: PromptOptionsNullish, + parser: PromptParserNullish, + preprocessor: PreprocessorId, + tool_functions: z.union([z.array(SavedFunctionId), z.null()]), + template_format: z.union([ + z.enum(["mustache", "nunjucks", "none"]), + z.null(), + ]), + mcp: z.union([ + z.record( + z.union([ + z.object({ + type: z.literal("id"), + id: z.string().uuid(), + is_disabled: z.boolean().optional(), + enabled_tools: z + .union([z.array(z.string()), z.null()]) + .optional(), + }), + z.object({ + type: z.literal("url"), + url: z.string(), + is_disabled: z.boolean().optional(), + enabled_tools: z + .union([z.array(z.string()), z.null()]) + .optional(), + }), + ]), + ), + z.null(), + ]), + origin: z.union([ + z + .object({ + prompt_id: z.string(), + project_id: z.string(), + prompt_version: z.string(), + }) + .partial(), + z.null(), + ]), + }) + .partial())(); + +export const FunctionId: z.ZodType< + SDK.FunctionId, + z.ZodTypeDef, + SDK.SchemaInput +> = /* @__PURE__ */ (() => + z.union([ + z.object({ function_id: z.string(), version: z.string().optional() }), + z.object({ + project_name: z.string(), + slug: z.string(), + version: z.string().optional(), + }), + z.object({ + global_function: z.string(), + function_type: FunctionTypeEnum.optional().default("scorer"), + }), + z.object({ + prompt_session_id: z.string(), + prompt_session_function_id: z.string(), + version: z.string().optional(), + }), + z.object({ + inline_context: z.object({ + runtime: z.enum(["node", "python", "browser", "quickjs"]), + version: z.string(), + }), + code: z.string(), + function_type: FunctionTypeEnum.and(z.unknown()).optional(), + name: z.union([z.string(), z.null()]).optional(), + }), + z.object({ + inline_prompt: PromptData.optional(), + inline_function: z.object({}).partial().passthrough(), + function_type: FunctionTypeEnum.optional().default("scorer"), + name: z.union([z.string(), z.null()]).optional(), + }), + z.object({ + inline_prompt: PromptData, + function_type: FunctionTypeEnum.optional().default("scorer"), + name: z.union([z.string(), z.null()]).optional(), + }), + ]))(); + +const FunctionObjectType = /* @__PURE__ */ (() => + z.enum([ + "prompt", + "tool", + "scorer", + "task", + "workflow", + "custom_view", + "preprocessor", + "facet", + "classifier", + "parameters", + "sandbox", + ]))(); + +const FunctionOutputType = /* @__PURE__ */ (() => + z.enum(["completion", "score", "facet", "classification", "any"]))(); + +export const GitMetadataSettings: z.ZodType = + /* @__PURE__ */ (() => + z.object({ + collect: z.enum(["all", "none", "some"]), + fields: z + .array( + z.enum([ + "commit", + "branch", + "tag", + "dirty", + "author_name", + "author_email", + "commit_message", + "commit_time", + "git_diff", + ]), + ) + .optional(), + }))(); + +const ProjectSettings = /* @__PURE__ */ (() => + z.union([ + z + .object({ + comparison_key: z.union([z.string(), z.null()]), + baseline_experiment_id: z.union([z.string(), z.null()]), + spanFieldOrder: z.union([ + z.array( + z.object({ + object_type: z.string(), + column_id: z.string(), + position: z.string(), + layout: z + .union([z.literal("full"), z.literal("two_column"), z.null()]) + .optional(), + }), + ), + z.null(), + ]), + remote_eval_sources: z.union([ + z.array( + z.object({ + url: z.string(), + name: z.union([z.string(), z.null()]).optional(), + description: z.union([z.string(), z.null()]).optional(), + }), + ), + z.null(), + ]), + disable_realtime_queries: z.union([z.boolean(), z.null()]), + monitor_charts_use_metrics_start: z.union([z.boolean(), z.null()]), + coding_agent_insights_dashboard: z.union([z.boolean(), z.null()]), + require_all_human_review_scores: z.union([z.boolean(), z.null()]), + blind_reviews: z.union([z.boolean(), z.null()]), + default_preprocessor: z.union([...SavedFunctionId.options, z.null()]), + }) + .partial(), + z.null(), + ]))(); + +export const Project: z.ZodType< + SDK.ProjectRecord, + z.ZodTypeDef, + SDK.SchemaInput +> = /* @__PURE__ */ (() => + z.object({ + id: z.string().uuid(), + org_id: z.string().uuid(), + name: z.string(), + description: z.union([z.string(), z.null()]).optional(), + created: z.union([z.string(), z.null()]).optional(), + deleted_at: z.union([z.string(), z.null()]).optional(), + user_id: z.union([z.string(), z.null()]).optional(), + settings: ProjectSettings.optional(), + }))(); + +export const Prompt: z.ZodType< + SDK.PromptRecord, + z.ZodTypeDef, + SDK.SchemaInput +> = /* @__PURE__ */ (() => + z.object({ + id: z.string().uuid(), + _xact_id: z.string(), + project_id: z.string().uuid(), + log_id: z.literal("p"), + org_id: z.string().uuid(), + name: z.string(), + slug: z.string(), + description: z.union([z.string(), z.null()]).optional(), + created: z.union([z.string(), z.null()]).optional(), + prompt_data: z.union([PromptData, z.null()]).optional(), + tags: z.union([z.array(z.string()), z.null()]).optional(), + metadata: z + .union([z.object({}).partial().passthrough(), z.null()]) + .optional(), + function_type: z.union([FunctionTypeEnum, z.null()]).optional(), + }))(); + +export const SSEConsoleEventData: z.ZodType = + /* @__PURE__ */ (() => + z.object({ + stream: z.enum(["stderr", "stdout"]), + message: z.string(), + }))(); + +export const SSEProgressEventData: z.ZodType = + /* @__PURE__ */ (() => + z.object({ + id: z.string(), + object_type: FunctionObjectType, + origin: z.union([ObjectReference, z.null()]).and(z.unknown()).optional(), + format: FunctionFormat, + output_type: FunctionOutputType, + name: z.string(), + event: z.enum([ + "reasoning_delta", + "text_delta", + "json_delta", + "error", + "console", + "start", + "done", + "progress", + ]), + data: z.string(), + }))(); + +export const BraintrustAttachmentReference: z.ZodType = + BraintrustAttachmentReferenceObject; + +export const ExternalAttachmentReference: z.ZodType = + ExternalAttachmentReferenceObject; diff --git a/js/src/sdk-types.ts b/js/src/sdk-types.ts new file mode 100644 index 000000000..58797088a --- /dev/null +++ b/js/src/sdk-types.ts @@ -0,0 +1,652 @@ +/* eslint-disable @typescript-eslint/no-empty-object-type -- Preserve the existing wire contracts, including permissive JSON fields. */ +/* eslint-disable @typescript-eslint/no-explicit-any -- Provider parameter maps intentionally accept arbitrary values. */ +/** + * SDK-owned wire contracts. Keep backend-generated definitions in compatibility + * tests only; changes here should describe the fields the SDK actually uses. + */ +export interface ResponseFormatJsonSchema { + name: string; + description?: string | undefined; + schema?: ({} | string) | undefined; + strict?: (boolean | null) | undefined; +} +export type ResponseFormatNullish = + | { + type: "json_object"; + } + | { + type: "json_schema"; + json_schema: ResponseFormatJsonSchema; + } + | { + type: "text"; + } + | null; +export type AsyncScoringState = + | { + status: "enabled"; + token: string; + function_ids: Array; + skip_logging?: (boolean | null) | undefined; + triggered_functions?: ({} | null) | undefined; + last_triggered_xact_id?: (string | number | null) | undefined; + } + | { + status: "disabled"; + } + | null; +export type AsyncScoringControl = + | { + kind: "score_update"; + token?: string | undefined; + } + | { + kind: "state_override"; + state: AsyncScoringState; + } + | { + kind: "state_force_reselect"; + } + | { + kind: "state_enabled_force_rescore"; + } + | { + kind: "trigger_functions"; + triggered_functions: Array<{ + function_id?: unknown | undefined; + scope: + | { + type: "span"; + } + | { + type: "trace"; + }; + idempotency_key?: string | undefined; + triggered_xact_id?: string | number | undefined; + }>; + } + | { + kind: "complete_triggered_functions"; + function_ids: Array; + triggered_xact_id: string; + } + | { + kind: "mark_attempt_failed"; + function_ids: Array; + }; +export interface BraintrustAttachmentReference { + type: "braintrust_attachment"; + filename: string; + content_type: string; + key: string; +} +export interface ExternalAttachmentReference { + type: "external_attachment"; + filename: string; + content_type: string; + url: string; +} +export type AttachmentReference = + | BraintrustAttachmentReference + | ExternalAttachmentReference; +export type UploadStatus = "uploading" | "done" | "error"; +export interface AttachmentStatus { + upload_status: UploadStatus; + error_message?: string | undefined; +} +export type FunctionTypeEnum = + | "llm" + | "scorer" + | "task" + | "tool" + | "custom_view" + | "preprocessor" + | "facet" + | "classifier" + | "tag" + | "parameters" + | "sandbox"; +export type SavedFunctionId = + | { + type: "function"; + id: string; + version?: string | undefined; + } + | { + type: "global"; + name: string; + function_type: FunctionTypeEnum; + }; +export type CallEvent = + | { + id?: string | undefined; + data: string; + event: "text_delta"; + } + | { + id?: string | undefined; + data: string; + event: "reasoning_delta"; + } + | { + id?: string | undefined; + data: string; + event: "json_delta"; + } + | { + id?: string | undefined; + data: string; + event: "progress"; + } + | { + id?: string | undefined; + data: string; + event: "error"; + } + | { + id?: string | undefined; + data: string; + event: "console"; + } + | { + id?: string | undefined; + event: "start"; + data: ""; + } + | { + id?: string | undefined; + event: "done"; + data: ""; + }; +interface CacheControl { + type: "ephemeral"; + ttl?: "5m" | "1h" | undefined; +} + +export interface ChatCompletionContentPartImageWithTitle { + image_url: { + url: string; + detail?: ("auto" | "low" | "high") | undefined; + }; + type: "image_url"; + cache_control?: CacheControl | undefined; +} +export interface ChatCompletionContentPartFileFile { + file_data?: string; + filename?: string; + file_id?: string; +} +export interface ChatCompletionContentPartFileWithTitle { + file: ChatCompletionContentPartFileFile; + type: "file"; + cache_control?: CacheControl | undefined; +} +export interface ChatCompletionContentPartInputAudioWithTitle { + input_audio: { data: string; format: "wav" | "mp3" }; + type: "input_audio"; + cache_control?: + | { type: "ephemeral"; ttl?: "5m" | "1h" | undefined } + | undefined; +} +export type ChatCompletionContentPart = + | ChatCompletionContentPartText + | ChatCompletionContentPartImageWithTitle + | ChatCompletionContentPartInputAudioWithTitle + | ChatCompletionContentPartFileWithTitle; +export interface ChatCompletionContentPartText { + text: string; + type: "text"; + cache_control?: CacheControl | undefined; +} +export interface ChatCompletionMessageToolCall { + id: string; + function: { + arguments: string; + name: string; + }; + type: "function"; +} +export interface ChatCompletionMessageReasoning { + id?: string; + content?: string; +} +export type ChatCompletionMessageParam = + | ChatCompletionOpenAIMessageParam + | { + role: "model"; + content?: string | null | undefined; + }; +export type ChatCompletionOpenAIMessageParam = + | { + content: string | Array; + role: "system"; + name?: string | undefined; + } + | { + content: string | Array; + role: "user"; + name?: string | undefined; + } + | { + role: "assistant"; + content?: + | (string | Array | null) + | undefined; + function_call?: + | { + arguments: string; + name: string; + } + | undefined; + name?: string | undefined; + tool_calls?: Array | undefined; + reasoning?: Array | undefined; + reasoning_signature?: string | undefined; + } + | { + content: string | Array; + role: "tool"; + tool_call_id: string; + } + | { + content: string | null; + name: string; + role: "function"; + } + | { + content: string | Array; + role: "developer"; + name?: string | undefined; + }; +export interface ChatCompletionTool { + function: { + name: string; + description?: string | undefined; + parameters?: {} | undefined; + }; + type: "function"; +} +export interface DatasetSnapshot { + id: string; + dataset_id: string; + name: string; + description: string | null; + xact_id: string; + created: string | null; +} +export type RepoInfo = { + commit?: string | null; + branch?: string | null; + tag?: string | null; + dirty?: boolean | null; + author_name?: string | null; + author_email?: string | null; + commit_message?: string | null; + commit_time?: string | null; + git_diff?: string | null; +} | null; +export type ExtendedSavedFunctionId = + | SavedFunctionId + | { + type: "slug"; + project_id: string; + slug: string; + }; +// Keep provider fields visible to editor completion while accepting custom options. +export type ModelParams = + | { + use_cache?: boolean; + reasoning_enabled?: boolean; + reasoning_budget?: number; + temperature?: number; + top_p?: number; + max_tokens?: number; + max_completion_tokens?: number; + frequency_penalty?: number; + presence_penalty?: number; + response_format?: ResponseFormatNullish; + tool_choice?: + | "auto" + | "none" + | "required" + | { + type: "function"; + function: { + name: string; + }; + }; + function_call?: + | "auto" + | "none" + | { + name: string; + }; + n?: number; + stop?: Array; + chat_template_kwargs?: { enable_thinking?: boolean; [key: string]: any }; + reasoning_effort?: "none" | "minimal" | "low" | "medium" | "high"; + verbosity?: "low" | "medium" | "high"; + [key: string]: any; + } + | { + use_cache?: boolean | undefined; + reasoning_enabled?: boolean | undefined; + reasoning_budget?: number | undefined; + max_tokens: number; + temperature: number; + top_p?: number | undefined; + top_k?: number | undefined; + stop_sequences?: Array | undefined; + max_tokens_to_sample?: number | undefined; + [key: string]: any; + } + | { + use_cache?: boolean; + reasoning_enabled?: boolean; + reasoning_budget?: number; + temperature?: number; + maxOutputTokens?: number; + topP?: number; + topK?: number; + [key: string]: any; + } + | { + use_cache?: boolean; + reasoning_enabled?: boolean; + reasoning_budget?: number; + temperature?: number; + topK?: number; + [key: string]: any; + } + | { + use_cache?: boolean; + reasoning_enabled?: boolean; + reasoning_budget?: number; + [key: string]: any; + }; +export type PromptOptionsNullish = { + model?: string; + params?: ModelParams; + position?: string; + endpoint_name?: string | null; +} | null; +export type PromptParserNullish = { + type: "llm_classifier"; + use_cot: boolean; + choice_scores?: {} | undefined; + choice?: Array | undefined; + allow_no_match?: boolean | undefined; + allow_skip?: boolean | undefined; +} | null; +export type PreprocessorId = + | { + type: "function"; + id: string; + version?: string | undefined; + } + | { + type: "global"; + name: string; + function_type: "preprocessor"; + } + | { + type: "inline"; + code: string; + } + | null; +export type PromptBlockData = + | { + type: "chat"; + messages: Array; + tools?: string | undefined; + } + | { + type: "completion"; + content: string; + }; +export type FunctionFormat = "llm" | "code" | "global" | "graph" | "topic_map"; +export interface PromptData { + prompt?: PromptBlockData | null; + options?: PromptOptionsNullish; + parser?: PromptParserNullish; + preprocessor?: PreprocessorId; + tool_functions?: Array | null; + template_format?: ("mustache" | "nunjucks" | "none") | null; + mcp?: {} | null; + origin?: { + prompt_id?: string; + project_id?: string; + prompt_version?: string; + } | null; +} +export type FunctionId = + | { + function_id: string; + version?: string | undefined; + } + | { + project_name: string; + slug: string; + version?: string | undefined; + } + | { + global_function: string; + function_type: FunctionTypeEnum; + } + | { + prompt_session_id: string; + prompt_session_function_id: string; + version?: string | undefined; + } + | { + inline_context: { + runtime: "node" | "python" | "browser" | "quickjs"; + version: string; + }; + code: string; + function_type?: FunctionTypeEnum | undefined; + name?: (string | null) | undefined; + } + | { + inline_prompt?: PromptData | undefined; + inline_function: {}; + function_type: FunctionTypeEnum; + name?: (string | null) | undefined; + } + | { + inline_prompt: PromptData; + function_type: FunctionTypeEnum; + name?: (string | null) | undefined; + }; +export type FunctionObjectType = + | "prompt" + | "tool" + | "scorer" + | "task" + | "workflow" + | "custom_view" + | "preprocessor" + | "facet" + | "classifier" + | "parameters" + | "sandbox"; +export type FunctionOutputType = + | "completion" + | "score" + | "facet" + | "classification" + | "any"; +export interface GitMetadataSettings { + collect: "all" | "none" | "some"; + fields?: + | Array< + | "commit" + | "branch" + | "tag" + | "dirty" + | "author_name" + | "author_email" + | "commit_message" + | "commit_time" + | "git_diff" + > + | undefined; +} +export type IfExists = "error" | "ignore" | "replace"; +export type InvokeParent = + | { + object_type: "project_logs" | "experiment" | "playground_logs"; + object_id: string; + row_ids?: + | ({ + id: string; + span_id: string; + root_span_id: string; + } | null) + | undefined; + propagated_event?: ({} | null) | undefined; + } + | string; +export type StreamingMode = ("auto" | "parallel" | "json" | "text") | null; +export type InvokeFunction = FunctionId & { + input?: unknown; + expected?: unknown; + metadata?: {} | null; + tags?: Array | null; + messages?: Array; + parent?: InvokeParent; + stream?: boolean | null; + mode?: StreamingMode; + strict?: boolean | null; + mcp_auth?: {}; + overrides?: {} | null; + endpoint_name?: string | null; +}; +export interface ObjectReference { + object_type: + | "project_logs" + | "experiment" + | "dataset" + | "prompt" + | "function" + | "prompt_session"; + object_id: string; + id: string; + _xact_id?: (string | null) | undefined; + created?: (string | null) | undefined; +} +export interface PromptSessionEvent { + id: string; + _xact_id: string; + created: string; + _pagination_key?: (string | null) | undefined; + project_id: string; + prompt_session_id: string; + prompt_session_data?: unknown | undefined; + prompt_data?: unknown | undefined; + function_data?: unknown | undefined; + function_type?: FunctionTypeEnum | null | undefined; + object_data?: unknown | undefined; + completion?: unknown | undefined; + tags?: (Array | null) | undefined; +} +export interface SSEConsoleEventData { + stream: "stderr" | "stdout"; + message: string; +} +export interface SSEProgressEventData { + id: string; + object_type: FunctionObjectType; + origin?: (ObjectReference | null) | undefined; + format: FunctionFormat; + output_type: FunctionOutputType; + name: string; + event: + | "reasoning_delta" + | "text_delta" + | "json_delta" + | "error" + | "console" + | "start" + | "done" + | "progress"; + data: string; +} +export interface ToolFunctionDefinition { + type: "function"; + function: { + name: string; + description?: string | undefined; + parameters?: {} | undefined; + strict?: (boolean | null) | undefined; + }; +} + +export interface ProjectRecord { + id: string; + org_id: string; + name: string; + description?: string | null; + created?: string | null; + deleted_at?: string | null; + user_id?: string | null; + settings?: { + comparison_key?: string | null; + baseline_experiment_id?: string | null; + spanFieldOrder?: + | { + object_type: string; + column_id: string; + position: string; + layout?: "full" | "two_column" | null; + }[] + | null; + remote_eval_sources?: + | { url: string; name?: string | null; description?: string | null }[] + | null; + disable_realtime_queries?: boolean | null; + monitor_charts_use_metrics_start?: boolean | null; + coding_agent_insights_dashboard?: boolean | null; + require_all_human_review_scores?: boolean | null; + blind_reviews?: boolean | null; + default_preprocessor?: SavedFunctionId | null; + } | null; +} + +export interface PromptRecord { + id: string; + _xact_id: string; + project_id: string; + log_id: "p"; + org_id: string; + name: string; + slug: string; + description?: string | null; + created?: string | null; + prompt_data?: PromptData | null; + tags?: string[] | null; + metadata?: Record | null; + function_type?: FunctionTypeEnum | null; +} + +// JSON inputs may omit values filled by schema defaults. Keep this type plain +// so declarations do not expose Zod's implementation graph. +export type SchemaInput = T extends { type: "text"; text: string } + ? { [K in keyof Omit]: SchemaInput[K]> } & { + text?: string; + } + : T extends { role: "tool"; tool_call_id: string } + ? { + [K in keyof Omit]: SchemaInput< + Omit[K] + >; + } & { tool_call_id?: string } + : T extends { function_type: FunctionTypeEnum } + ? { + [K in keyof Omit]: SchemaInput< + Omit[K] + >; + } & { function_type?: T["function_type"] } + : T extends object + ? { [K in keyof T]: SchemaInput } + : T; diff --git a/js/src/span-cache.test.ts b/js/src/span-cache.test.ts index 37760b00f..30017032a 100644 --- a/js/src/span-cache.test.ts +++ b/js/src/span-cache.test.ts @@ -6,8 +6,8 @@ import { afterEach, beforeAll, vi, -} from "vitest"; -import iso from "./isomorph"; +} from "vite-plus/test"; +import iso from "#braintrust-platform"; import { SpanCache } from "./span-cache"; import { configureNode } from "./node/config"; diff --git a/js/src/span-cache.ts b/js/src/span-cache.ts index c66a3d1d4..7b197d40e 100644 --- a/js/src/span-cache.ts +++ b/js/src/span-cache.ts @@ -9,7 +9,7 @@ * the cache becomes a no-op (all lookups return undefined). */ -import iso from "./isomorph"; +import iso from "#braintrust-platform"; import { mergeDicts } from "../util/object_util"; // Global registry of active span caches for process exit cleanup diff --git a/js/src/span-customizer.test.ts b/js/src/span-customizer.test.ts index 43b9a935f..a0f7095b4 100644 --- a/js/src/span-customizer.test.ts +++ b/js/src/span-customizer.test.ts @@ -6,26 +6,26 @@ import { expectTypeOf, test, vi, -} from "vitest"; +} from "vite-plus/test"; +import { _exportsForTestingOnly } from "./logger/testing"; import { - _exportsForTestingOnly, _internalGetGlobalState, Attachment, BraintrustState, - Dataset, initLogger, SpanImpl, updateSpan, type TestBackgroundLogger, -} from "./logger"; -import { configureInstrumentation, registry } from "./instrumentation/registry"; +} from "./logger/core"; +import { Dataset } from "./logger/datasets"; +import { setSpanCustomizers } from "./span-customizer"; import { configureNode } from "./node/config"; import { INSTRUMENTATION_NAMES, withSpanInstrumentationName, } from "./span-origin"; import vm from "node:vm"; -import type { SpanCustomizer, SpanExportData } from "./exports"; +import type { SpanCustomizer, SpanExportData } from "./instrumentation"; import { customizeSpanExport, mergeCustomizedSpanRecords, @@ -49,13 +49,12 @@ describe("onSpanExport", () => { beforeEach(async () => { vi.stubEnv("BRAINTRUST_DEBUG_LOG_LEVEL", "error"); - registry.disable(); await _exportsForTestingOnly.simulateLoginForTests(); memoryLogger = _exportsForTestingOnly.useTestBackgroundLogger(); }); afterEach(() => { - configureInstrumentation({ spanCustomizers: [] }); + setSpanCustomizers([]); _exportsForTestingOnly.clearTestBackgroundLogger(); resetSpanCustomizerFailureLoggingForTests(); vi.unstubAllEnvs(); @@ -76,16 +75,14 @@ describe("onSpanExport", () => { } test("adds a field to outgoing span records", async () => { - configureInstrumentation({ - spanCustomizers: [ - { - onSpanExport(data) { - data.custom_field = "added"; - return data; - }, + setSpanCustomizers([ + { + onSpanExport(data) { + data.custom_field = "added"; + return data; }, - ], - }); + }, + ]); const span = startInstrumentedSpan(); span.log({ output: "result" }); @@ -103,16 +100,14 @@ describe("onSpanExport", () => { }); test("alters an existing field in outgoing span records", async () => { - configureInstrumentation({ - spanCustomizers: [ - { - onSpanExport(data) { - if ("output" in data) data.output = "[redacted]"; - return data; - }, + setSpanCustomizers([ + { + onSpanExport(data) { + if ("output" in data) data.output = "[redacted]"; + return data; }, - ], - }); + }, + ]); const span = startInstrumentedSpan(); span.log({ output: "sensitive response" }); @@ -124,16 +119,14 @@ describe("onSpanExport", () => { }); test("deletes a field from outgoing span records", async () => { - configureInstrumentation({ - spanCustomizers: [ - { - onSpanExport(data) { - delete data.error; - return data; - }, + setSpanCustomizers([ + { + onSpanExport(data) { + delete data.error; + return data; }, - ], - }); + }, + ]); const span = startInstrumentedSpan(); span.log({ error: "sensitive error", output: "safe response" }); @@ -147,23 +140,21 @@ describe("onSpanExport", () => { }); test("passes replacement records through later customizers", async () => { - configureInstrumentation({ - spanCustomizers: [ - { - onSpanExport(data) { - return "output" in data ? { ...data, output: "replacement" } : data; - }, + setSpanCustomizers([ + { + onSpanExport(data) { + return "output" in data ? { ...data, output: "replacement" } : data; }, - { - onSpanExport(data) { - if (typeof data.output === "string") { - data.output = data.output.toUpperCase(); - } - return data; - }, + }, + { + onSpanExport(data) { + if (typeof data.output === "string") { + data.output = data.output.toUpperCase(); + } + return data; }, - ], - }); + }, + ]); const span = startInstrumentedSpan(); span.log({ output: "original" }); @@ -179,17 +170,15 @@ describe("onSpanExport", () => { }); test("customizes manual roots and children as well as instrumented spans", async () => { - configureInstrumentation({ - spanCustomizers: [ - { - onSpanExport(data) { - data.tags = ["customized"]; - if ("output" in data) data.output = "[redacted]"; - return data; - }, + setSpanCustomizers([ + { + onSpanExport(data) { + data.tags = ["customized"]; + if ("output" in data) data.output = "[redacted]"; + return data; }, - ], - }); + }, + ]); const root = initLogger({ projectName: "customizer-project", @@ -247,7 +236,7 @@ describe("onSpanExport", () => { data.tags = ["customized"]; return data; }); - configureInstrumentation({ spanCustomizers: [{ onSpanExport }] }); + setSpanCustomizers([{ onSpanExport }]); const logger = initLogger({ projectName: "customizer-project", projectId: "customizer-project", @@ -296,7 +285,6 @@ describe("onSpanExport", () => { }, })), undefined, - false, ); const datasetId = dataset.insert({ input: "dataset input", @@ -390,7 +378,7 @@ describe("onSpanExport", () => { _is_merge: false, }; }); - configureInstrumentation({ spanCustomizers: [{ onSpanExport }] }); + setSpanCustomizers([{ onSpanExport }]); const errorLog = vi.spyOn(console, "error").mockImplementation(() => {}); update({ output: "sensitive", metadata: { secret: "sensitive" } }); @@ -492,18 +480,16 @@ describe("onSpanExport", () => { async (_name, invalidResult) => { const errorLog = vi.spyOn(console, "error").mockImplementation(() => {}); const laterHook = vi.fn((data: SpanExportData) => data); - configureInstrumentation({ - spanCustomizers: [ - { - // @ts-expect-error Exercise invalid callback results from JavaScript. - onSpanExport(data) { - if (data.output === "private") return invalidResult(); - return data; - }, + setSpanCustomizers([ + { + // @ts-expect-error Exercise invalid callback results from JavaScript. + onSpanExport(data) { + if (data.output === "private") return invalidResult(); + return data; }, - { onSpanExport: laterHook }, - ], - }); + }, + { onSpanExport: laterHook }, + ]); const span = startInstrumentedSpan(); span.log({ output: "private" }); const manual = span.startSpan({ name: "manual" }); @@ -625,36 +611,33 @@ describe("onSpanExport", () => { ); test("restores mutable protocol fields between successful callbacks", () => { - configureInstrumentation({ - spanCustomizers: [ - { - onSpanExport(data) { - if (Array.isArray(data.span_parents)) - data.span_parents.push("wrong"); - if (Array.isArray(data._merge_paths)) - data._merge_paths[0].push("wrong"); - delete data.id; - delete data.project_id; - data._is_merge = false; - data.dataset_id = "wrong"; - data._object_delete = true; - return data; - }, + setSpanCustomizers([ + { + onSpanExport(data) { + if (Array.isArray(data.span_parents)) data.span_parents.push("wrong"); + if (Array.isArray(data._merge_paths)) + data._merge_paths[0].push("wrong"); + delete data.id; + delete data.project_id; + data._is_merge = false; + data.dataset_id = "wrong"; + data._object_delete = true; + return data; }, - { - onSpanExport(data) { - // These fields feed the next callback, not just the final exporter. - data.output = { - id: data.id, - parents: data.span_parents, - paths: data._merge_paths, - merge: data._is_merge, - }; - return Object.freeze(data); - }, + }, + { + onSpanExport(data) { + // These fields feed the next callback, not just the final exporter. + data.output = { + id: data.id, + parents: data.span_parents, + paths: data._merge_paths, + merge: data._is_merge, + }; + return Object.freeze(data); }, - ], - }); + }, + ]); const result = customizeSpanExport({ id: "original", span_id: "span", @@ -689,9 +672,7 @@ describe("onSpanExport", () => { throw new Error("private failure"); }); const laterHook = vi.fn((data: SpanExportData) => data); - configureInstrumentation({ - spanCustomizers: [{ onSpanExport }, { onSpanExport: laterHook }], - }); + setSpanCustomizers([{ onSpanExport }, { onSpanExport: laterHook }]); const enqueue = vi.spyOn(memoryLogger, "log"); const span = startInstrumentedSpan(); const records = enqueue.mock.calls.flatMap(([items]) => items); @@ -765,22 +746,20 @@ describe("onSpanExport", () => { contentType: "text/plain", }); const seenAttachments: unknown[] = []; - configureInstrumentation({ - spanCustomizers: [ - { - onSpanExport(data) { - const metadata = data.metadata as Record; - const input = data.input as unknown[]; - if (!metadata || !input) return data; - seenAttachments.push(input[1]); - delete metadata.secret; - input.push("injected"); - if (metadata.fail) throw new Error("fail after mutation"); - return data; - }, + setSpanCustomizers([ + { + onSpanExport(data) { + const metadata = data.metadata as Record; + const input = data.input as unknown[]; + if (!metadata || !input) return data; + seenAttachments.push(input[1]); + delete metadata.secret; + input.push("injected"); + if (metadata.fail) throw new Error("fail after mutation"); + return data; }, - ], - }); + }, + ]); const experiment = _exportsForTestingOnly.initTestExperiment( "customizer-experiment", "customizer-project", @@ -837,15 +816,13 @@ describe("onSpanExport", () => { test("logs the failure once logging is enabled after a silenced failure", () => { const errorLog = vi.spyOn(console, "error").mockImplementation(() => {}); const state = _internalGetGlobalState(); - configureInstrumentation({ - spanCustomizers: [ - { - onSpanExport(): SpanExportData { - throw new Error("private exception detail"); - }, + setSpanCustomizers([ + { + onSpanExport(): SpanExportData { + throw new Error("private exception detail"); }, - ], - }); + }, + ]); state.setDebugLogLevel(false); customizeSpanExport({ id: "silenced" }); @@ -873,7 +850,7 @@ describe("onSpanExport", () => { }, ]; for (let i = 0; i < 3; i++) { - configureInstrumentation({ spanCustomizers: customizers }); + setSpanCustomizers(customizers); expect(customizeSpanExport({ id: "row", output: "private" })).toEqual({ id: "row", context: { customizer_error: true }, @@ -904,7 +881,7 @@ describe("onSpanExport", () => { const errorLog = vi.spyOn(console, "error").mockImplementation(() => {}); const onSpanExport = vi.fn((data: SpanExportData) => data); if (withCustomizers) { - configureInstrumentation({ spanCustomizers: [{ onSpanExport }] }); + setSpanCustomizers([{ onSpanExport }]); } const span = new SpanImpl({ state: _internalGetGlobalState(), @@ -951,26 +928,22 @@ describe("onSpanExport", () => { return Object.freeze(payload); }); let hookCalls = 0; - configureInstrumentation({ - spanCustomizers: [ - { - // @ts-expect-error Async customizers are unsupported, but must be contained. - onSpanExport(data) { - hookCalls++; - if (data.output === "private") { - delete data.id; - data.metrics = { start: 999, end: 999, tokens: 999 }; - data.context = { secret: "private mutated context" }; - return Promise.reject( - new Error("private async exception detail"), - ); - } - return data; - }, + setSpanCustomizers([ + { + // @ts-expect-error Async customizers are unsupported, but must be contained. + onSpanExport(data) { + hookCalls++; + if (data.output === "private") { + delete data.id; + data.metrics = { start: 999, end: 999, tokens: 999 }; + data.context = { secret: "private mutated context" }; + return Promise.reject(new Error("private async exception detail")); + } + return data; }, - { onSpanExport: laterHook }, - ], - }); + }, + { onSpanExport: laterHook }, + ]); // Keep both spans in the same flush chunk, rather than auto-flushing the // manual span's initial row before the instrumented child is created. @@ -986,7 +959,6 @@ describe("onSpanExport", () => { appUrl: "https://customizer.test", apiKey: "test-key", orgName: "test-org", - asyncFlush: false, fetch: async (url, options) => { const pathname = new URL(String(url)).pathname; if (pathname === "/api/apikey/login") { @@ -1141,19 +1113,22 @@ describe("onSpanExport", () => { expect(groups[1][0]).toBe(a2); }); - function initHttpLogger(onLogs: (rows: SpanExportData[]) => Response) { + function initHttpLogger( + onLogs: (rows: SpanExportData[]) => Response, + autoCaptureAttachments?: boolean, + ) { // Keep all records in one flush chunk. vi.stubEnv("BRAINTRUST_SYNC_FLUSH", "1"); vi.stubEnv("BRAINTRUST_NUM_RETRIES", "0"); const state = new BraintrustState({ noExitFlush: true }); const logger = initLogger({ state, + autoCaptureAttachments, projectName: "customizer-project", projectId: "customizer-project", appUrl: "https://customizer.test", apiKey: "test-key", orgName: "test-org", - asyncFlush: false, fetch: async (url, options) => { const pathname = new URL(String(url)).pathname; if (pathname === "/api/apikey/login") { @@ -1180,16 +1155,14 @@ describe("onSpanExport", () => { test("stops sending later ordered groups after a group fails", async () => { vi.spyOn(console, "error").mockImplementation(() => {}); vi.spyOn(console, "warn").mockImplementation(() => {}); - configureInstrumentation({ - spanCustomizers: [ - { - onSpanExport(data) { - if (data.output === "private") throw new Error("private failure"); - return data; - }, + setSpanCustomizers([ + { + onSpanExport(data) { + if (data.output === "private") throw new Error("private failure"); + return data; }, - ], - }); + }, + ]); const requests: SpanExportData[][] = []; const { logger } = initHttpLogger((rows) => { requests.push(rows); @@ -1212,7 +1185,7 @@ describe("onSpanExport", () => { }); test("decision customizer removal uploads only surviving media", async () => { - vi.stubEnv("BRAINTRUST_CAPTURE_ATTACHMENTS", "true"); + vi.stubEnv("BRAINTRUST_AUTO_CAPTURE_ATTACHMENTS", "true"); const upload = vi .spyOn(Attachment.prototype, "upload") .mockResolvedValue({ upload_status: "done" }); @@ -1221,23 +1194,21 @@ describe("onSpanExport", () => { rows.push(...batch); return Response.json({}); }); - configureInstrumentation({ - spanCustomizers: [ - { - onSpanExport(data) { - if ( - data.input && - typeof data.input === "object" && - "state" in data.input && - Array.isArray(data.input.state) - ) { - data.input.state.splice(0, 1); - } - return data; - }, + setSpanCustomizers([ + { + onSpanExport(data) { + if ( + data.input && + typeof data.input === "object" && + "state" in data.input && + Array.isArray(data.input.state) + ) { + data.input.state.splice(0, 1); + } + return data; }, - ], - }); + }, + ]); const input = { state: [ { @@ -1284,7 +1255,7 @@ describe("onSpanExport", () => { test.each(["customizer", "masking", "upload failure"])( `${type} media survives export with %s and never uploads redacted media`, async (mode) => { - vi.stubEnv("BRAINTRUST_CAPTURE_ATTACHMENTS", "true"); + vi.stubEnv("BRAINTRUST_AUTO_CAPTURE_ATTACHMENTS", "true"); const media = "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACAAAAAgCAIAAAD8GO2jAAAAKElEQVR4nO3NsQ0AAAzCMP5/un0CNkuZ41wybXsHAAAAAAAAAAAAxR4yw/wuPL6QkAAAAABJRU5ErkJggg=="; const upload = vi @@ -1298,16 +1269,14 @@ describe("onSpanExport", () => { const redact = (value: unknown) => JSON.parse(JSON.stringify(value).replaceAll(media, "[redacted]")); if (mode === "customizer") { - configureInstrumentation({ - spanCustomizers: [ - { - onSpanExport(data) { - if (data.input) data.input = redact(data.input); - return data; - }, + setSpanCustomizers([ + { + onSpanExport(data) { + if (data.input) data.input = redact(data.input); + return data; }, - ], - }); + }, + ]); } else if (mode === "masking") { state.setMaskingFunction(redact); } @@ -1382,19 +1351,17 @@ describe("onSpanExport", () => { }; }), ); - configureInstrumentation({ - spanCustomizers: [ - { - onSpanExport(data) { - if (data.input) { - const value = data.input as typeof input; - value.state[0].content[1].image_url = "[redacted]"; - } - return data; - }, + setSpanCustomizers([ + { + onSpanExport(data) { + if (data.input) { + const value = data.input as typeof input; + value.state[0].content[1].image_url = "[redacted]"; + } + return data; }, - ], - }); + }, + ]); const rows: SpanExportData[] = []; const { logger } = initHttpLogger((batch) => { rows.push(...batch); @@ -1465,6 +1432,48 @@ describe("onSpanExport", () => { }, ); + test.each([true, false])( + "keeps scoped typed attachment capture=%s through background export and masking", + async (capture) => { + vi.stubEnv("BRAINTRUST_AUTO_CAPTURE_ATTACHMENTS", String(!capture)); + const upload = vi + .spyOn(Attachment.prototype, "upload") + .mockResolvedValue({ upload_status: "done" }); + setSpanCustomizers([{ onSpanExport: (data) => ({ ...data }) }]); + const rows: SpanExportData[] = []; + const { state, logger } = initHttpLogger((batch) => { + rows.push(...batch); + return Response.json({}); + }, capture); + state.setMaskingFunction((value) => structuredClone(value)); + const input = { + state: [{ type: "file", data: "data:text/plain;base64,aGVsbG8=" }], + questions: [{ id: "visible", type: "boolean" }], + }; + const span = logger.startSpan({ name: "typed.call" }); + span.log({ input }); + span.end(); + await logger.flush(); + const exported = rows.find((row) => row.id === span.id)!; + if (capture) { + expect(exported.input).toMatchObject({ + state: [ + { + data: { + type: "braintrust_attachment", + content_type: "text/plain", + }, + }, + ], + }); + expect(upload).toHaveBeenCalledOnce(); + } else { + expect(exported.input).toEqual(input); + expect(upload).not.toHaveBeenCalled(); + } + }, + ); + test("does not upload attachments whose references were masked away", async () => { const masked = new Attachment({ data: new ArrayBuffer(1), @@ -1510,12 +1519,10 @@ describe("OpenTelemetry compat mode", () => { onSpanExport: (data: SpanExportData) => ({ ...data, output: "redacted" }), }; - beforeEach(() => registry.disable()); - afterEach(() => { globalThis.BRAINTRUST_CONTEXT_MANAGER = undefined; vi.unstubAllEnvs(); - configureInstrumentation({ spanCustomizers: [] }); + setSpanCustomizers([]); vi.restoreAllMocks(); }); @@ -1530,23 +1537,21 @@ describe("OpenTelemetry compat mode", () => { ])("registers and clears customizers under %s", (_name, enableCompat) => { const errorLog = vi.spyOn(console, "error").mockImplementation(() => {}); enableCompat(); - configureInstrumentation({ spanCustomizers: [customizer] }); + setSpanCustomizers([customizer]); expect(customizeSpanExport({ id: "row", output: "private" })).toEqual({ id: "row", output: "redacted", }); - configureInstrumentation({ - spanCustomizers: [ - { onSpanExport: (data) => ({ ...data, output: "replacement" }) }, - ], - }); + setSpanCustomizers([ + { onSpanExport: (data) => ({ ...data, output: "replacement" }) }, + ]); expect(customizeSpanExport({ output: "private" })).toEqual({ output: "replacement", }); - configureInstrumentation({ spanCustomizers: [] }); + setSpanCustomizers([]); const record = { output: "original" }; expect(customizeSpanExport(record)).toBe(record); - configureInstrumentation({ spanCustomizers: undefined }); + setSpanCustomizers(undefined); expect(errorLog).not.toHaveBeenCalled(); }); }); diff --git a/js/src/span-origin.test.ts b/js/src/span-origin.test.ts index e78cc6c4c..16a8eb377 100644 --- a/js/src/span-origin.test.ts +++ b/js/src/span-origin.test.ts @@ -1,6 +1,6 @@ -import { afterEach, describe, expect, it, vi } from "vitest"; +import { afterEach, describe, expect, it, vi } from "vite-plus/test"; -import iso from "./isomorph"; +import iso from "#braintrust-platform"; import { detectSpanOriginEnvironment, getSpanInstrumentationName, diff --git a/js/src/span-origin.ts b/js/src/span-origin.ts index 19998e780..a5abc2e56 100644 --- a/js/src/span-origin.ts +++ b/js/src/span-origin.ts @@ -1,4 +1,4 @@ -import iso from "./isomorph"; +import iso from "#braintrust-platform"; declare const __BRAINTRUST_SDK_VERSION__: string; @@ -60,7 +60,7 @@ type SpanOrigin = { environment?: SpanOriginEnvironment; }; -export const INTERNAL_SPAN_INSTRUMENTATION_NAME = Symbol.for( +export const INTERNAL_SPAN_INSTRUMENTATION_NAME: unique symbol = Symbol.for( "braintrust.spanInstrumentationName", ); diff --git a/js/src/stackutil.ts b/js/src/stackutil.ts index 13933f8bf..e8d54d6ad 100644 --- a/js/src/stackutil.ts +++ b/js/src/stackutil.ts @@ -1,4 +1,4 @@ -import iso, { CallerLocation } from "./isomorph"; +import iso, { CallerLocation } from "#braintrust-platform"; interface StackTraceEntry { functionName: string; @@ -31,8 +31,7 @@ function getStackTrace(): StackTraceEntry[] { return out; } -// Fetches the first StackTraceEntry not contained inside the same directory as -// this file and converts it into a CallerLocation. +// Skip SDK frames, including feature modules nested below this directory. export function getCallerLocation(): CallerLocation | undefined { let thisDir: string | undefined = undefined; const entries = getStackTrace(); @@ -40,7 +39,11 @@ export function getCallerLocation(): CallerLocation | undefined { if (thisDir === undefined) { thisDir = iso.pathDirname?.(frame.fileName); } - if (iso.pathDirname?.(frame.fileName) !== thisDir) { + if ( + thisDir !== undefined && + !frame.fileName.startsWith(`${thisDir}/`) && + !frame.fileName.startsWith(`${thisDir}\\`) + ) { return { caller_functionname: frame.functionName, caller_filename: frame.fileName, diff --git a/js/src/template/mustache-utils.ts b/js/src/template/mustache-utils.ts index 1cfc24551..69f9d2e70 100644 --- a/js/src/template/mustache-utils.ts +++ b/js/src/template/mustache-utils.ts @@ -4,7 +4,7 @@ import Mustache from "mustache"; export function lintTemplate( template: string, context: Record, -) { +): void { const variables = getMustacheVars(template); for (const variable of variables) { const arrPathsReplaced = variable[1].replaceAll(/\.\d+/g, ".0"); diff --git a/js/src/template/registry.ts b/js/src/template/registry.ts index d9744e8a4..bd722876a 100644 --- a/js/src/template/registry.ts +++ b/js/src/template/registry.ts @@ -97,7 +97,15 @@ class TemplatePluginRegistry { } } -export const templateRegistry = new TemplatePluginRegistry(); +let templateRegistry: TemplatePluginRegistry | undefined; + +function getTemplateRegistry(): TemplatePluginRegistry { + if (!templateRegistry) { + templateRegistry = new TemplatePluginRegistry(); + templateRegistry.register(mustachePlugin); + } + return templateRegistry; +} /** * Register a template plugin and optionally activate it @@ -106,8 +114,9 @@ export const templateRegistry = new TemplatePluginRegistry(); * If `options` is omitted but the plugin defines `defaultOptions`, the * registry will activate the renderer using those defaults. */ -export const registerTemplatePlugin = - templateRegistry.register.bind(templateRegistry); +export function registerTemplatePlugin(plugin: TemplateRendererPlugin): void { + getTemplateRegistry().register(plugin); +} /** * Gets an active template renderer by name. @@ -127,6 +136,8 @@ export const registerTemplatePlugin = * } * ``` */ -export const getTemplateRenderer = templateRegistry.get.bind(templateRegistry); - -registerTemplatePlugin(mustachePlugin); +export function getTemplateRenderer( + name: string, +): TemplateRenderer | undefined { + return getTemplateRegistry().get(name); +} diff --git a/js/src/template/renderer.test.ts b/js/src/template/renderer.test.ts index 3e7a8ed64..e63e07359 100644 --- a/js/src/template/renderer.test.ts +++ b/js/src/template/renderer.test.ts @@ -1,4 +1,4 @@ -import { expect, test, describe } from "vitest"; +import { expect, test, describe } from "vite-plus/test"; import { parseTemplateFormat, isTemplateFormat, diff --git a/js/src/trace.test.ts b/js/src/trace.test.ts index 3c3876b1b..41dbb4773 100644 --- a/js/src/trace.test.ts +++ b/js/src/trace.test.ts @@ -6,7 +6,7 @@ import { beforeAll, beforeEach, afterEach, -} from "vitest"; +} from "vite-plus/test"; import { CachedSpanFetcher, LocalTrace, @@ -14,11 +14,8 @@ import { SpanFetchFn, SpanFilters, } from "./trace"; -import { - _exportsForTestingOnly, - _internalGetGlobalState, - type BraintrustState, -} from "./logger"; +import { _exportsForTestingOnly } from "./logger/testing"; +import { _internalGetGlobalState, type BraintrustState } from "./logger/core"; import { configureNode } from "./node/config"; // Mock the invoke function diff --git a/js/src/trace.ts b/js/src/trace.ts index fb41ad7ba..c85d8b9bc 100644 --- a/js/src/trace.ts +++ b/js/src/trace.ts @@ -1,4 +1,5 @@ -import { BraintrustState, ObjectFetcher, WithTransactionId } from "./logger"; +import { BraintrustState } from "./logger/core"; +import { ObjectFetcher, WithTransactionId } from "./logger/objects"; import { invoke } from "./functions/invoke"; interface TraceOptions { @@ -406,7 +407,7 @@ function spanFilterClauses( * Fetcher for spans by root_span_id, using the ObjectFetcher pattern. * Handles pagination automatically via cursor-based iteration. */ -export class SpanFetcher extends ObjectFetcher { +class SpanFetcher extends ObjectFetcher { constructor( objectType: "experiment" | "project_logs" | "playground_logs", private readonly _objectId: string, @@ -732,7 +733,11 @@ export class LocalTrace implements Trace { ); } - getConfiguration() { + getConfiguration(): { + object_type: "project_logs" | "experiment" | "playground_logs"; + object_id: string; + root_span_id: string; + } { return { object_type: this.objectType, object_id: this.objectId, @@ -744,7 +749,13 @@ export class LocalTrace implements Trace { * Custom JSON serialization - returns trace_ref format so LocalTrace * can be safely passed through JSON.stringify() (e.g., in invoke()). */ - toJSON() { + toJSON(): { + trace_ref: { + object_type: "project_logs" | "experiment" | "playground_logs"; + object_id: string; + root_span_id: string; + }; + } { return { trace_ref: { object_type: this.objectType, diff --git a/js/src/typed-attachments.test.ts b/js/src/typed-attachments.test.ts index cbbbec544..e482e9bb9 100644 --- a/js/src/typed-attachments.test.ts +++ b/js/src/typed-attachments.test.ts @@ -1,5 +1,12 @@ -import { afterEach, beforeEach, describe, expect, test, vi } from "vitest"; -import { Attachment } from "./logger"; +import { + afterEach, + beforeEach, + describe, + expect, + test, + vi, +} from "vite-plus/test"; +import { Attachment } from "./logger/core"; import { configureNode } from "./node/config"; import { TypedAttachmentProcessor, @@ -19,7 +26,7 @@ const file = { data: png, }; -beforeEach(() => vi.stubEnv("BRAINTRUST_CAPTURE_ATTACHMENTS", "true")); +beforeEach(() => vi.stubEnv("BRAINTRUST_AUTO_CAPTURE_ATTACHMENTS", "true")); afterEach(() => { vi.unstubAllEnvs(); vi.restoreAllMocks(); @@ -200,7 +207,7 @@ describe("decision attachment export", () => { test.each([127, 128, 129, 140])( "capture disabled does not leak media at depth %s", (depth) => { - vi.stubEnv("BRAINTRUST_CAPTURE_ATTACHMENTS", "false"); + vi.stubEnv("BRAINTRUST_AUTO_CAPTURE_ATTACHMENTS", "false"); let nested: unknown = { ...file, data: { type: "data", data: png } }; for (let i = 0; i < depth; i++) nested = i % 2 ? { nested } : [nested]; const input = { @@ -289,7 +296,7 @@ describe("decision attachment export", () => { ); test("capture disabled omits bytes recursively but preserves explicit references and URLs", async () => { - vi.stubEnv("BRAINTRUST_CAPTURE_ATTACHMENTS", "false"); + vi.stubEnv("BRAINTRUST_AUTO_CAPTURE_ATTACHMENTS", "false"); const upload = vi.spyOn(Attachment.prototype, "upload"); const reference = { type: "inline_attachment", @@ -356,7 +363,7 @@ describe("decision attachment export", () => { expect(await new TypedAttachmentProcessor().process(record)).toEqual( record, ); - vi.stubEnv("BRAINTRUST_CAPTURE_ATTACHMENTS", "false"); + vi.stubEnv("BRAINTRUST_AUTO_CAPTURE_ATTACHMENTS", "false"); expect(prepareTypedAttachments(record.input)).toEqual(record.input); expect(prepareTypedAttachments(record.output)).toEqual(record.output); expect(upload).not.toHaveBeenCalled(); @@ -373,7 +380,7 @@ describe("decision attachment export", () => { expect(await new TypedAttachmentProcessor().process(record)).toEqual( record, ); - vi.stubEnv("BRAINTRUST_CAPTURE_ATTACHMENTS", "false"); + vi.stubEnv("BRAINTRUST_AUTO_CAPTURE_ATTACHMENTS", "false"); expect(prepareTypedAttachments(record.input)).toEqual(record.input); expect(upload).not.toHaveBeenCalled(); }); diff --git a/js/src/typed-attachments.ts b/js/src/typed-attachments.ts index 8d77295bd..425815a7a 100644 --- a/js/src/typed-attachments.ts +++ b/js/src/typed-attachments.ts @@ -1,5 +1,5 @@ import { debugLogger } from "./debug-logger"; -import { Attachment } from "./logger"; +import { Attachment } from "./logger/core"; import { uint8ArrayToBase64 } from "../util/bytes"; import { isObject } from "../util/type_util"; import { @@ -156,8 +156,11 @@ export function prepareTypedAttachments(value: unknown): unknown { export class TypedAttachmentProcessor { private uploadFailed = false; - async process(record: T): Promise { - if (this.uploadFailed || !isAutoCaptureAttachmentsEnabled()) return record; + async process( + record: T, + captureAttachments = isAutoCaptureAttachmentsEnabled(), + ): Promise { + if (this.uploadFailed || !captureAttachments) return record; const payload = record as { input?: unknown; output?: unknown }; const input = isObject(payload.input) && diff --git a/js/src/util.test.ts b/js/src/util.test.ts index a6e98b579..3965d5049 100644 --- a/js/src/util.test.ts +++ b/js/src/util.test.ts @@ -1,4 +1,4 @@ -import { expect, test } from "vitest"; +import { expect, test } from "vite-plus/test"; import { LazyValue } from "./util"; test("LazyValue evaluates exactly once", async () => { diff --git a/js/src/util.ts b/js/src/util.ts index 97b2b0dc9..44e289299 100644 --- a/js/src/util.ts +++ b/js/src/util.ts @@ -125,7 +125,7 @@ export class SyncLazyValue { export function addAzureBlobHeaders( headers: Record, url: string, -) { +): void { // According to https://stackoverflow.com/questions/37824136/put-on-sas-blob-url-without-specifying-x-ms-blob-type-header, // there is no way to avoid including this. if (url.includes("blob.core.windows.net")) { @@ -142,7 +142,10 @@ export class InternalAbortError extends Error { } // Return a copy of record with the given keys removed. -export function filterFrom(record: Record, keys: string[]) { +export function filterFrom( + record: Record, + keys: string[], +): Record { const out: Record = {}; for (const k of Object.keys(record)) { if (!keys.includes(k)) { diff --git a/js/src/vendor-sdk-types/ai-sdk-common.ts b/js/src/vendor-sdk-types/ai-sdk-common.ts index 7945b173c..cb462f2ac 100644 --- a/js/src/vendor-sdk-types/ai-sdk-common.ts +++ b/js/src/vendor-sdk-types/ai-sdk-common.ts @@ -398,6 +398,9 @@ export interface AISDKProviderResolver { } export interface AISDKNamespaceBase { + Agent?: AISDKAgentClass; + Experimental_Agent?: AISDKAgentClass; + ToolLoopAgent?: AISDKAgentClass; experimental_evaluate?: AISDKEvaluateFunction; experimental_decide?: AISDKEvaluateFunction; generateText: AISDKGenerateFunction; diff --git a/js/src/vendor-sdk-types/ai-sdk-v3.ts b/js/src/vendor-sdk-types/ai-sdk-v3.ts deleted file mode 100644 index 266324d14..000000000 --- a/js/src/vendor-sdk-types/ai-sdk-v3.ts +++ /dev/null @@ -1,12 +0,0 @@ -import type { - AISDKGenerateFunction, - AISDKNamespaceBase, - AISDKStreamFunction, -} from "./ai-sdk-common"; - -export interface AISDKV3 extends AISDKNamespaceBase { - experimental_generateText?: AISDKGenerateFunction; - experimental_streamText?: AISDKStreamFunction; - experimental_generateObject?: AISDKGenerateFunction; - experimental_streamObject?: AISDKStreamFunction; -} diff --git a/js/src/vendor-sdk-types/ai-sdk-v4.ts b/js/src/vendor-sdk-types/ai-sdk-v4.ts deleted file mode 100644 index 5aa750e74..000000000 --- a/js/src/vendor-sdk-types/ai-sdk-v4.ts +++ /dev/null @@ -1,3 +0,0 @@ -import type { AISDKNamespaceBase } from "./ai-sdk-common"; - -export type AISDKV4 = AISDKNamespaceBase; diff --git a/js/src/vendor-sdk-types/ai-sdk-v5.ts b/js/src/vendor-sdk-types/ai-sdk-v5.ts deleted file mode 100644 index ce1bb3d97..000000000 --- a/js/src/vendor-sdk-types/ai-sdk-v5.ts +++ /dev/null @@ -1,6 +0,0 @@ -import type { AISDKAgentClass, AISDKNamespaceBase } from "./ai-sdk-common"; - -export interface AISDKV5 extends AISDKNamespaceBase { - Agent?: AISDKAgentClass; - Experimental_Agent?: AISDKAgentClass; -} diff --git a/js/src/vendor-sdk-types/ai-sdk-v6.ts b/js/src/vendor-sdk-types/ai-sdk-v6.ts deleted file mode 100644 index 339cc07b4..000000000 --- a/js/src/vendor-sdk-types/ai-sdk-v6.ts +++ /dev/null @@ -1,7 +0,0 @@ -import type { AISDKAgentClass, AISDKNamespaceBase } from "./ai-sdk-common"; - -export interface AISDKV6 extends AISDKNamespaceBase { - Agent?: AISDKAgentClass; - Experimental_Agent?: AISDKAgentClass; - ToolLoopAgent?: AISDKAgentClass; -} diff --git a/js/src/vendor-sdk-types/ai-sdk.ts b/js/src/vendor-sdk-types/ai-sdk.ts index b2c783b5d..22628d4b2 100644 --- a/js/src/vendor-sdk-types/ai-sdk.ts +++ b/js/src/vendor-sdk-types/ai-sdk.ts @@ -24,6 +24,7 @@ import type { AISDKModelStreamChunk, AISDKOutputObject, AISDKOutputResponseFormat, + AISDKNamespaceBase, AISDKRerankFunction, AISDKRerankParams, AISDKRerankResult, @@ -34,18 +35,7 @@ import type { AISDKUsage, AISDKWorkflowAgentClass, } from "./ai-sdk-common"; -import type { AISDKV3 } from "./ai-sdk-v3"; -import type { AISDKV4 } from "./ai-sdk-v4"; -import type { AISDKV5 } from "./ai-sdk-v5"; -import type { AISDKV6 } from "./ai-sdk-v6"; - -export type AISDKVersion = - | { majorVersion: 3; sdk: AISDKV3 } - | { majorVersion: 4; sdk: AISDKV4 } - | { majorVersion: 5; sdk: AISDKV5 } - | { majorVersion: 6; sdk: AISDKV6 }; - -export type AISDK = AISDKVersion["sdk"]; +export type AISDK = AISDKNamespaceBase; export type { AISDKEvaluateParams, diff --git a/js/src/vendor-sdk-types/eve.ts b/js/src/vendor-sdk-types/eve.ts index f2112defd..202184607 100644 --- a/js/src/vendor-sdk-types/eve.ts +++ b/js/src/vendor-sdk-types/eve.ts @@ -1,8 +1,8 @@ /** - * Vendored types for eve's authored hook APIs. + * Vendored types for eve's instrumentation provider API, introduced in 0.34.0. * * Keep this surface intentionally narrow. These types are not exported to SDK - * users and should only cover fields we read, correlate, or log. + * users and should only cover fields the Braintrust provider reads or returns. */ export type EveJsonValue = @@ -10,506 +10,28 @@ export type EveJsonValue = | boolean | number | string - | EveJsonValue[] + | readonly EveJsonValue[] | { readonly [key: string]: EveJsonValue }; -export type EveJsonObject = { readonly [key: string]: EveJsonValue }; - -export interface EveHookContext { - readonly session: { - readonly id: string; - readonly parent?: { - readonly callId?: string; - readonly sessionId?: string; - readonly turn?: { - readonly id?: string; - }; - }; - }; -} - -export type EveAssistantStepFinishReason = - | "content-filter" - | "error" - | "length" - | "other" - | "stop" - | "tool-calls"; - -export interface EveStreamEventMeta { - readonly at: string; -} - -export interface EveRuntimeToolCallActionRequest { - readonly callId: string; - readonly input: EveJsonObject; - readonly kind: "tool-call"; - readonly toolName: string; +export interface EveInstrumentationState { + get(): EveJsonValue | undefined; + set(value: EveJsonValue | undefined): void; } -export interface EveRuntimeToolResultActionResult { - readonly callId: string; - readonly isError?: boolean; - readonly kind: "tool-result"; - readonly output: EveJsonValue; - readonly toolName: string; +export interface EveInstrumentationHandlerContext { + readonly state: EveInstrumentationState; } -export type EveRuntimeActionRequest = - | EveRuntimeToolCallActionRequest - | { - readonly callId: string; - readonly input?: EveJsonObject; - readonly kind: "load-skill" | "remote-agent-call"; - readonly name?: string; - } - | { - readonly callId: string; - readonly input: EveJsonObject; - readonly kind: "subagent-call"; - readonly name?: string; - readonly subagentName?: string; - }; - -export type EveRuntimeActionResult = - | EveRuntimeToolResultActionResult - | { - readonly callId: string; - readonly isError?: boolean; - readonly kind: "load-skill-result"; - readonly output?: EveJsonValue; - readonly name?: string; - } - | { - readonly callId: string; - readonly isError?: boolean; - readonly kind: "subagent-result"; - readonly output?: EveJsonValue; - readonly subagentName?: string; - }; - -export type EveActionResultStatus = "completed" | "failed" | "rejected"; - -export interface EveActionResultError { - readonly code: string; - readonly message: string; -} - -export type EveHandleMessageStreamEvent = - | { - readonly data: { - readonly invocation?: unknown; - readonly runtime?: { - readonly agentId: string; - readonly agentName?: string; - readonly eveVersion: string; - readonly modelId: string; - }; - }; - readonly meta?: EveStreamEventMeta; - readonly type: "session.started"; - } - | { - readonly data: { - readonly sequence: number; - readonly turnId: string; - }; - readonly meta?: EveStreamEventMeta; - readonly type: "turn.started"; - } - | { - readonly data: { - readonly sequence: number; - readonly turnId: string; - }; - readonly meta?: EveStreamEventMeta; - readonly type: "turn.completed"; - } - | { - readonly data: { - readonly message: string; - readonly sequence: number; - readonly turnId: string; - }; - readonly meta?: EveStreamEventMeta; - readonly type: "message.received"; - } - | { - readonly data: { - readonly finishReason: EveAssistantStepFinishReason; - readonly message: string | null; - readonly sequence: number; - readonly stepIndex: number; - readonly turnId: string; - }; - readonly meta?: EveStreamEventMeta; - readonly type: "message.completed"; - } - | { - readonly data: { - readonly reasoning: string; - readonly sequence: number; - readonly stepIndex: number; - readonly turnId: string; - }; - readonly meta?: EveStreamEventMeta; - readonly type: "reasoning.completed"; - } - | { - readonly data: { - readonly result: EveJsonValue; - readonly sequence: number; - readonly stepIndex: number; - readonly turnId: string; - }; - readonly meta?: EveStreamEventMeta; - readonly type: "result.completed"; - } - | { - readonly data: { - readonly sequence: number; - readonly stepIndex: number; - readonly turnId: string; - }; - readonly meta?: EveStreamEventMeta; - readonly type: "step.started"; - } - | { - readonly data: { - readonly finishReason: EveAssistantStepFinishReason; - readonly providerMetadata?: { - readonly gateway?: { - readonly generationId?: string; - }; - }; - readonly sequence: number; - readonly stepIndex: number; - readonly turnId: string; - readonly usage?: { - readonly cacheReadTokens?: number; - readonly cacheWriteTokens?: number; - readonly costUsd?: number; - readonly inputTokens?: number; - readonly outputTokens?: number; - }; - }; - readonly meta?: EveStreamEventMeta; - readonly type: "step.completed"; - } - | { - readonly data: { - readonly code: string; - readonly details?: EveJsonObject; - readonly message: string; - readonly sequence: number; - readonly stepIndex: number; - readonly turnId: string; - }; - readonly meta?: EveStreamEventMeta; - readonly type: "step.failed"; - } - | { - readonly data: { - readonly actions: readonly EveRuntimeActionRequest[]; - readonly sequence: number; - readonly stepIndex: number; - readonly turnId: string; - }; - readonly meta?: EveStreamEventMeta; - readonly type: "actions.requested"; - } - | { - readonly data: { - readonly error?: EveActionResultError; - readonly result: EveRuntimeActionResult; - readonly sequence: number; - readonly stepIndex: number; - readonly status: EveActionResultStatus; - readonly turnId: string; - }; - readonly meta?: EveStreamEventMeta; - readonly type: "action.result"; - } - | { - readonly data: { - readonly callId: string; - readonly childSessionId: string; - readonly name: string; - readonly remote?: { - readonly url?: string; - }; - readonly sequence: number; - readonly toolName?: string; - readonly turnId: string; - }; - readonly meta?: EveStreamEventMeta; - readonly type: "subagent.called"; - } - | { - readonly data: { - readonly callId: string; - readonly error?: EveActionResultError; - readonly output?: EveJsonValue; - readonly sequence: number; - readonly status?: EveActionResultStatus; - readonly subagentName: string; - readonly turnId: string; - }; - readonly meta?: EveStreamEventMeta; - readonly type: "subagent.completed"; - } - | { - readonly data: { - readonly code: string; - readonly details?: EveJsonObject; - readonly message: string; - readonly sequence: number; - readonly turnId: string; - }; - readonly meta?: EveStreamEventMeta; - readonly type: "turn.failed"; - } - | { - readonly data: { - readonly code: string; - readonly details?: EveJsonObject; - readonly message: string; - readonly sessionId: string; - }; - readonly meta?: EveStreamEventMeta; - readonly type: "session.failed"; - } - | { - readonly data: { - readonly wait: "next-user-message"; - }; - readonly meta?: EveStreamEventMeta; - readonly type: "session.waiting"; - } - | { - readonly meta?: EveStreamEventMeta; - readonly type: "session.completed"; - }; - export interface EveInstrumentationSetupContext { readonly agentName: string; -} - -type EveTextPart = { - readonly text: string; - readonly type: "text"; -}; - -type EveImagePart = { - readonly image: unknown; - readonly mediaType?: string; - readonly type: "image"; -}; - -type EveFilePart = { - readonly data: unknown; - readonly filename?: string; - readonly mediaType: string; - readonly type: "file"; -}; - -type EveReasoningPart = { - readonly text: string; - readonly type: "reasoning"; -}; - -type EveReasoningFilePart = { - readonly data: unknown; - readonly mediaType: string; - readonly type: "reasoning-file"; -}; - -type EveCustomPart = { - readonly kind: `${string}.${string}`; - readonly type: "custom"; -}; - -type EveToolCallPart = { - readonly input: unknown; - readonly providerExecuted?: boolean; - readonly toolCallId: string; - readonly toolName: string; - readonly type: "tool-call"; -}; - -type EveToolResultContentPart = - | EveTextPart - | EveFilePart - | { - readonly data: string; - readonly filename?: string; - readonly mediaType: string; - readonly type: "file-data"; - } - | { - readonly mediaType?: string; - readonly type: "file-url"; - readonly url: string; - } - | { - readonly fileId: string | Readonly>; - readonly type: "file-id" | "image-file-id"; - } - | { - readonly providerReference: Readonly>; - readonly type: "file-reference" | "image-file-reference"; - } - | { - readonly data: string; - readonly mediaType: string; - readonly type: "image-data"; - } - | { - readonly type: "image-url"; - readonly url: string; - } - | { readonly type: "custom" }; - -type EveToolResultOutput = - | { - readonly type: "text" | "error-text"; - readonly value: string; - } - | { - readonly type: "json" | "error-json"; - readonly value: EveJsonValue; - } - | { - readonly reason?: string; - readonly type: "execution-denied"; - } - | { - readonly type: "content"; - readonly value: readonly EveToolResultContentPart[]; - }; - -type EveToolResultPart = { - readonly output: EveToolResultOutput; - readonly toolCallId: string; - readonly toolName: string; - readonly type: "tool-result"; -}; - -type EveToolApprovalRequest = { - readonly approvalId: string; - readonly isAutomatic?: boolean; - readonly signature?: string; - readonly toolCallId: string; - readonly type: "tool-approval-request"; -}; - -type EveToolApprovalResponse = { - readonly approvalId: string; - readonly approved: boolean; - readonly providerExecuted?: boolean; - readonly reason?: string; - readonly type: "tool-approval-response"; -}; - -export type EveSystemModelMessage = { - readonly content: string; - readonly role: "system"; -}; - -export type EveModelMessage = - | EveSystemModelMessage - | { - readonly content: - | string - | readonly (EveTextPart | EveImagePart | EveFilePart)[]; - readonly role: "user"; - } - | { - readonly content: - | string - | readonly ( - | EveTextPart - | EveCustomPart - | EveFilePart - | EveReasoningPart - | EveReasoningFilePart - | EveToolCallPart - | EveToolResultPart - | EveToolApprovalRequest - )[]; - readonly role: "assistant"; - } - | { - readonly content: readonly ( - | EveToolResultPart - | EveToolApprovalResponse - )[]; - readonly role: "tool"; - }; - -export interface EveInstrumentationModelInput { - readonly instructions?: - | string - | EveSystemModelMessage - | readonly EveSystemModelMessage[]; - readonly messages: readonly EveModelMessage[]; -} - -export interface EveInstrumentationStepStartedEventInput { - readonly modelInput: EveInstrumentationModelInput; - readonly session: { - readonly id: string; - }; - readonly step: { - readonly index: number; - }; - readonly turn: { - readonly id: string; - readonly sequence: number; - }; -} - -export interface EveInstrumentationDefinition { - readonly events?: { - readonly "step.started"?: ( - input: EveInstrumentationStepStartedEventInput, - ) => void | { readonly runtimeContext?: EveJsonObject }; - readonly [eventType: string]: - | (( - input: EveInstrumentationStepStartedEventInput, - ) => void | { readonly runtimeContext?: EveJsonObject }) - | undefined; - }; - readonly recordInputs?: boolean; - readonly recordOutputs?: boolean; - readonly setup?: (context: EveInstrumentationSetupContext) => void; -} - -/** - * Vendored types for Eve's instrumentation-provider API, introduced in - * eve@0.34.0. Keep this limited to lifecycle events consumed by Braintrust. - */ - -export interface EveProviderSetupContext { - readonly agentName: string; readonly environment?: "development" | "preview" | "production"; readonly evaluation?: { readonly runId: string }; readonly frameworkVersion?: string; } -export interface EveProviderState { - get(): EveJsonValue | undefined; - set(value: EveJsonValue | undefined): void; -} - -export interface EveProviderContext { - readonly state: EveProviderState; -} - export interface EveInstrumentationAttemptScope { readonly attemptId: string; readonly attemptIndex: number; - readonly functionId?: string; readonly rootSessionId?: string; readonly sessionId: string; readonly stepIndex: number; @@ -519,18 +41,52 @@ export interface EveInstrumentationAttemptScope { export interface EveInstrumentationParentLineage { readonly callId: string; readonly sessionId: string; - readonly subagentName?: string; readonly turnId: string; } export interface EveInstrumentationTraceContext { - readonly isRemote?: boolean; readonly spanId: string; readonly traceFlags: number; readonly traceId: string; } -export interface EveProviderTurnStartedEvent { +export interface EveInstrumentationUsage { + readonly inputTokenDetails?: { + readonly cacheReadTokens?: number; + readonly cacheWriteTokens?: number; + }; + readonly inputTokens?: number; + readonly outputTokens?: number; +} + +export interface EveInstrumentationModelInput { + readonly instructions?: unknown; + readonly messages: readonly unknown[]; +} + +export type EveInstrumentationContentPart = + | { + readonly text: string; + readonly type: "text"; + } + | { + readonly text: string; + readonly type: "reasoning"; + } + | { + readonly callId: string; + readonly input: unknown; + readonly toolName: string; + readonly type: "tool-call"; + } + | { + readonly type: "tool-result"; + } + | { + readonly type: "tool-error"; + }; + +export interface EveInstrumentationTurnStartedEvent { readonly idempotencyKey: string; readonly parentLineage?: EveInstrumentationParentLineage; readonly parentTraceContext?: EveInstrumentationTraceContext; @@ -541,22 +97,22 @@ export interface EveProviderTurnStartedEvent { readonly type: "turn.started"; } -export type EveProviderTurnTerminalEvent = - | { - readonly idempotencyKey: string; - readonly sessionId: string; - readonly turnId: string; - readonly type: "turn.cancelled" | "turn.completed"; - } - | { - readonly error?: unknown; - readonly idempotencyKey: string; - readonly sessionId: string; - readonly turnId: string; - readonly type: "turn.failed"; - }; +export interface EveInstrumentationTurnSettledEvent { + readonly idempotencyKey: string; + readonly sessionId: string; + readonly turnId: string; + readonly type: "turn.cancelled" | "turn.completed"; +} -export interface EveProviderModelCallStartedEvent { +export interface EveInstrumentationTurnFailedEvent { + readonly error?: unknown; + readonly idempotencyKey: string; + readonly sessionId: string; + readonly turnId: string; + readonly type: "turn.failed"; +} + +export interface EveInstrumentationModelCallStartedEvent { readonly idempotencyKey: string; readonly input?: EveInstrumentationModelInput; readonly model: { @@ -567,99 +123,74 @@ export interface EveProviderModelCallStartedEvent { readonly type: "model.call.started"; } -export type EveProviderContentPart = - | { readonly text: string; readonly type: "text" } - | { readonly text: string; readonly type: "reasoning" } - | { - readonly callId: string; - readonly input: unknown; - readonly toolName: string; - readonly type: "tool-call"; - } - | { - readonly callId: string; - readonly input: unknown; - readonly output: unknown; - readonly toolName: string; - readonly type: "tool-result"; - } - | { - readonly callId: string; - readonly error: unknown; - readonly input: unknown; - readonly toolName: string; - readonly type: "tool-error"; - }; +export interface EveInstrumentationModelCallCompletedEvent { + readonly content?: readonly EveInstrumentationContentPart[]; + readonly finishReason: string; + readonly idempotencyKey: string; + readonly scope: EveInstrumentationAttemptScope; + readonly type: "model.call.completed"; + readonly usage: EveInstrumentationUsage; +} -export interface EveProviderUsage { - readonly inputTokenDetails?: { - readonly cacheReadTokens?: number; - readonly cacheWriteTokens?: number; - }; - readonly inputTokens?: number; - readonly outputTokens?: number; +export interface EveInstrumentationModelCallFailedEvent { + readonly error?: unknown; + readonly idempotencyKey: string; + readonly scope: EveInstrumentationAttemptScope; + readonly type: "model.call.failed"; } -export type EveProviderModelCallTerminalEvent = - | { - readonly content?: readonly EveProviderContentPart[]; - readonly finishReason: string; - readonly idempotencyKey: string; - readonly scope: EveInstrumentationAttemptScope; - readonly type: "model.call.completed"; - readonly usage: EveProviderUsage; - } - | { - readonly error?: unknown; - readonly idempotencyKey: string; - readonly scope: EveInstrumentationAttemptScope; - readonly type: "model.call.failed"; - }; +export interface EveInstrumentationStepAttemptCompletedEvent { + readonly idempotencyKey: string; + readonly scope: EveInstrumentationAttemptScope; + readonly type: "step.attempt.completed"; +} -export type EveProviderActionKind = - | "load-skill" - | "remote-agent-call" - | "subagent-call" - | "tool-call"; +export interface EveInstrumentationStepAttemptFailedEvent { + readonly error?: unknown; + readonly idempotencyKey: string; + readonly scope: EveInstrumentationAttemptScope; + readonly type: "step.attempt.failed"; +} -export interface EveProviderActionStartedEvent { +export interface EveInstrumentationActionStartedEvent { readonly callId: string; readonly idempotencyKey: string; readonly input?: unknown; - readonly kind: EveProviderActionKind; readonly name: string; readonly scope: EveInstrumentationAttemptScope; readonly type: "action.started"; } -export type EveProviderActionTerminalEvent = - | { - readonly acceptedAtMs?: number; - readonly idempotencyKey: string; - readonly outcome: "completed"; - readonly output: - | { readonly output?: unknown; readonly type: "result" } - | { readonly error?: unknown; readonly type: "error" }; - readonly scope: EveInstrumentationAttemptScope; - readonly type: "action.completed"; - readonly usage?: EveProviderUsage; - } - | { - readonly acceptedAtMs?: number; - readonly error?: unknown; - readonly errorCode?: string; - readonly idempotencyKey: string; - readonly outcome: "abandoned" | "cancelled" | "failed" | "rejected"; - readonly scope: EveInstrumentationAttemptScope; - readonly type: "action.failed"; - }; +export interface EveInstrumentationActionCompletedEvent { + readonly acceptedAtMs?: number; + readonly idempotencyKey: string; + readonly output: + | { readonly output?: unknown; readonly type: "result" } + | { readonly error?: unknown; readonly type: "error" }; + readonly scope: EveInstrumentationAttemptScope; + readonly type: "action.completed"; +} + +export interface EveInstrumentationActionFailedEvent { + readonly acceptedAtMs?: number; + readonly error?: unknown; + readonly errorCode?: string; + readonly idempotencyKey: string; + readonly outcome: "abandoned" | "cancelled" | "failed" | "rejected"; + readonly scope: EveInstrumentationAttemptScope; + readonly type: "action.failed"; +} /** Eve 0.72+ replaces action events with tool.call events. */ -export interface EveProviderToolCallStartedEvent { +export interface EveInstrumentationToolCallStartedEvent { readonly callId: string; readonly idempotencyKey: string; readonly input?: unknown; - readonly kind?: EveProviderActionKind; + readonly kind?: + | "load-skill" + | "remote-agent-call" + | "subagent-call" + | "tool-call"; readonly parentCallId?: string; readonly startedAtMs?: number; readonly scope: EveInstrumentationAttemptScope; @@ -667,7 +198,7 @@ export interface EveProviderToolCallStartedEvent { readonly type: "tool.call.started"; } -export type EveProviderToolCallTerminalEvent = +export type EveInstrumentationToolCallTerminalEvent = | { readonly acceptedAtMs?: number; readonly completedAtMs?: number; @@ -695,60 +226,54 @@ export type EveProviderToolCallTerminalEvent = readonly type: "tool.call.failed"; }; -export type EveProviderStepAttemptTerminalEvent = { - readonly error?: unknown; - readonly idempotencyKey: string; - readonly scope: EveInstrumentationAttemptScope; - readonly type: "step.attempt.completed" | "step.attempt.failed"; -}; - -type EveProviderHandler = ( +type EveInstrumentationHandler = ( event: TEvent, - context: EveProviderContext, + context: EveInstrumentationHandlerContext, ) => void | PromiseLike; -type EveChannelAudience = "private" | "public" | "unknown"; - -interface EveTraceCaptureContext { - readonly agentName?: string; - readonly audience: EveChannelAudience; - readonly channelType?: string; -} - -type EveTracePolicyDecision = - | { readonly emit: false } - | { - readonly emit: true; - readonly recordInputs: boolean; - readonly recordOutputs: boolean; - }; - -type EveTraceCapturePolicy = ( - trace: EveTraceCaptureContext, -) => EveTracePolicyDecision | boolean; - -export interface EveProviderDefinition { - readonly tracePolicy?: EveTraceCapturePolicy; - readonly events?: { - readonly "action.completed"?: EveProviderHandler; - readonly "action.failed"?: EveProviderHandler; - readonly "action.started"?: EveProviderHandler; - readonly "model.call.completed"?: EveProviderHandler; - readonly "model.call.failed"?: EveProviderHandler; - readonly "model.call.started"?: EveProviderHandler; - readonly "step.attempt.completed"?: EveProviderHandler; - readonly "step.attempt.failed"?: EveProviderHandler; - readonly "tool.call.started"?: EveProviderHandler; - readonly "tool.call.completed"?: EveProviderHandler; - readonly "tool.call.failed"?: EveProviderHandler; - readonly "turn.cancelled"?: EveProviderHandler; - readonly "turn.completed"?: EveProviderHandler; - readonly "turn.failed"?: EveProviderHandler; - readonly "turn.started"?: EveProviderHandler; +export interface EveInstrumentationDefinition { + readonly capture: "content"; + readonly tracePolicy?: (trace: { + readonly agentName?: string; + readonly audience: "private" | "public" | "unknown"; + readonly channelType?: string; + }) => + | boolean + | { readonly emit: false } + | { + readonly emit: true; + readonly recordInputs: boolean; + readonly recordOutputs: boolean; + }; + readonly events: { + readonly "tool.call.started": EveInstrumentationHandler; + readonly "tool.call.completed": EveInstrumentationHandler< + Extract< + EveInstrumentationToolCallTerminalEvent, + { type: "tool.call.completed" } + > + >; + readonly "tool.call.failed": EveInstrumentationHandler< + Extract< + EveInstrumentationToolCallTerminalEvent, + { type: "tool.call.failed" } + > + >; + readonly "action.completed": EveInstrumentationHandler; + readonly "action.failed": EveInstrumentationHandler; + readonly "action.started": EveInstrumentationHandler; + readonly "model.call.completed": EveInstrumentationHandler; + readonly "model.call.failed": EveInstrumentationHandler; + readonly "model.call.started": EveInstrumentationHandler; + readonly "step.attempt.completed": EveInstrumentationHandler; + readonly "step.attempt.failed": EveInstrumentationHandler; + readonly "turn.cancelled": EveInstrumentationHandler; + readonly "turn.completed": EveInstrumentationHandler; + readonly "turn.failed": EveInstrumentationHandler; + readonly "turn.started": EveInstrumentationHandler; }; - readonly flush?: () => void | PromiseLike; + readonly flush: () => Promise; readonly setup?: ( - context: EveProviderSetupContext, + context: EveInstrumentationSetupContext, ) => void | PromiseLike; - readonly shutdown?: () => void | PromiseLike; } diff --git a/js/src/vendor-sdk-types/flue.ts b/js/src/vendor-sdk-types/flue.ts index 367046fe2..2f7c75222 100644 --- a/js/src/vendor-sdk-types/flue.ts +++ b/js/src/vendor-sdk-types/flue.ts @@ -1,5 +1,5 @@ /** - * Vendored types for @flue/runtime observe/instrument-based instrumentation. + * Vendored types for @flue/runtime instrumentation. * * Keep this surface intentionally narrow. These types are not exported to SDK * users and should only cover fields we read, correlate, or log. @@ -16,17 +16,11 @@ export interface FlueUsage { cacheWrite?: number; totalTokens?: number; cost?: { - input?: number; - output?: number; - cacheRead?: number; - cacheWrite?: number; total?: number; }; } export interface FlueBaseEvent { - type?: string; - v?: number; runId?: string; instanceId?: string; submissionId?: string; @@ -41,7 +35,6 @@ export interface FlueBaseEvent { harness?: string; operationId?: string; turnId?: string; - [key: string]: unknown; } export interface FlueRunStartEvent extends FlueBaseEvent { @@ -49,13 +42,7 @@ export interface FlueRunStartEvent extends FlueBaseEvent { runId: string; startedAt?: string; workflowName?: string; - owner?: { - kind?: string; - workflowName?: string; - instanceId?: string; - }; input?: unknown; - payload?: unknown; } export interface FlueRunResumeEvent extends FlueBaseEvent { @@ -91,10 +78,9 @@ export interface FlueOperationEvent extends FlueBaseEvent { result?: unknown; agentInput?: { text?: string; - images?: Array<{ mimeType?: string }>; }; agentOutput?: - | { type?: "text"; text?: string; finishReason?: string } + | { type?: "text"; text?: string } | { type?: "data"; data?: unknown }; usage?: FlueUsage; } @@ -103,14 +89,6 @@ export interface FlueTurnRequestEvent extends FlueBaseEvent { type: "turn_request"; turnId: string; purpose: FlueTurnPurpose; - model?: string; - provider?: string; - api?: string; - input?: { - systemPrompt?: string; - messages?: unknown[]; - tools?: unknown[]; - }; request?: { api?: string; input?: { @@ -118,14 +96,11 @@ export interface FlueTurnRequestEvent extends FlueBaseEvent { messages?: unknown[]; tools?: unknown[]; }; - model?: string; requestedModel?: string; providerId?: string; providerName?: string; - reasoning?: string; reasoningLevel?: string; }; - reasoning?: string; } export interface FlueTurnEvent extends FlueBaseEvent { @@ -133,12 +108,8 @@ export interface FlueTurnEvent extends FlueBaseEvent { turnId: string; purpose?: FlueTurnPurpose; durationMs?: number; - model?: string; - provider?: string; - api?: string; request?: { api?: string; - model?: string; requestedModel?: string; providerId?: string; providerName?: string; @@ -146,17 +117,11 @@ export interface FlueTurnEvent extends FlueBaseEvent { response?: { output?: unknown; usage?: FlueUsage; - stopReason?: string; finishReason?: string; responseModel?: string; error?: unknown; - errorInfo?: { type?: string; message?: string }; }; - output?: unknown; - usage?: FlueUsage; - stopReason?: string; isError?: boolean; - error?: unknown; } export interface FlueToolStartEvent extends FlueBaseEvent { @@ -164,21 +129,15 @@ export interface FlueToolStartEvent extends FlueBaseEvent { toolName?: string; toolCallId: string; args?: unknown; - arguments?: unknown; - input?: unknown; - origin?: "model" | "caller" | "framework" | "adapter"; - description?: string; } export interface FlueToolCallEvent extends FlueBaseEvent { - type: "tool_call" | "tool"; + type: "tool"; toolName?: string; toolCallId: string; isError?: boolean; result?: unknown; effectiveResult?: unknown; - output?: unknown; - error?: unknown; errorInfo?: { type?: string; message?: string }; durationMs?: number; } @@ -197,9 +156,6 @@ export interface FlueTaskEvent extends FlueBaseEvent { agent?: string; isError?: boolean; result?: unknown; - agentOutput?: - | { type?: "text"; text?: string; finishReason?: string } - | { type?: "data"; data?: unknown }; errorInfo?: { type?: string; message?: string }; durationMs?: number; } @@ -222,12 +178,7 @@ export interface FlueCompactionEvent extends FlueBaseEvent { } export interface FlueContext { - readonly id?: string; - readonly runId?: string; -} - -export interface FlueObservableContext extends FlueContext { - subscribeEvent(callback: (event: unknown) => unknown): () => void; + readonly id: string; } export type FlueExecutionOperation = @@ -244,25 +195,12 @@ export type FlueExecutionOperation = | { type: "task"; taskId: string } | { type: "coordinator"; phase: "reconcile" }; -export interface FlueTraceCarrier { - traceparent: string; - tracestate?: string; -} - export interface FlueExecutionContext { eventContext?: FlueContext; runId?: string; - instanceId?: string; - submissionId?: string; - dispatchId?: string; - agentName?: string; - conversationId?: string; - harness?: string; - session?: string; operationId?: string; turnId?: string; taskId?: string; - traceCarrier?: FlueTraceCarrier; } export type FlueExecutionInterceptor = ( diff --git a/js/src/vendor-sdk-types/langchain.ts b/js/src/vendor-sdk-types/langchain.ts index a4ffdc909..e3b0c8191 100644 --- a/js/src/vendor-sdk-types/langchain.ts +++ b/js/src/vendor-sdk-types/langchain.ts @@ -1,4 +1,4 @@ -import type { Logger, Span, StartSpanArgs } from "../logger"; +import type { Logger, Span, StartSpanArgs } from "../logger/core"; import type { ExperimentLogPartialArgs } from "../util"; export type LangChainSerialized = { @@ -27,10 +27,10 @@ export type LangChainCallbackManagerConfigureArgs = [ options?: unknown, ]; -export type LangChainCallbackHandlerOptions = { +export type LangChainCallbackHandlerOptions = { debug: boolean; excludeMetadataProps: RegExp; - logger?: Logger | Span; + logger?: Logger | Span; parent?: Span | (() => Span); }; diff --git a/js/src/vendor-sdk-types/openai-common.ts b/js/src/vendor-sdk-types/openai-common.ts index a6ec770de..177f0f025 100644 --- a/js/src/vendor-sdk-types/openai-common.ts +++ b/js/src/vendor-sdk-types/openai-common.ts @@ -22,6 +22,7 @@ export interface OpenAIWithResponse { export interface OpenAIChatCreateParams { messages: unknown; stream?: boolean | null; + audio?: { format: string; [key: string]: unknown } | null; [key: string]: unknown; } diff --git a/js/src/workerd/config.ts b/js/src/workerd/config.ts index bdd813520..f4983fa8e 100644 --- a/js/src/workerd/config.ts +++ b/js/src/workerd/config.ts @@ -1,49 +1,7 @@ -import iso from "../isomorph"; -import { _internalSetInitialState } from "../logger"; -import { resolveRuntimeAsyncLocalStorage } from "../runtime-async-local-storage"; -import { registry } from "../instrumentation/registry"; +import iso from "#braintrust-platform"; +import { createWorkerdIsomorph } from "./platform"; -let workerdConfigured = false; - -/** - * Configure the isomorph for Cloudflare Workers (workerd) runtime. - */ +/** @internal Configure source-level tests and compatibility integrations. */ export function configureWorkerd(): void { - if (workerdConfigured) { - return; - } - - iso.buildType = "workerd"; - - const runtimeAsyncLocalStorage = resolveRuntimeAsyncLocalStorage(); - - if (runtimeAsyncLocalStorage) { - iso.newAsyncLocalStorage = () => new runtimeAsyncLocalStorage(); - } - - iso.getEnv = (name: string) => { - if (typeof process === "undefined" || typeof process.env === "undefined") { - return undefined; - } - return process.env[name]; - }; - iso.getBraintrustApiKey = async () => { - const value = iso.getEnv("BRAINTRUST_API_KEY"); - return value?.trim() ? value : undefined; - }; - - iso.hash = (data: string): string => { - let hash = 0; - for (let i = 0; i < data.length; i++) { - const char = data.charCodeAt(i); - hash = (hash << 5) - hash + char; - hash = hash & hash; - } - const hashHex = (hash >>> 0).toString(16).padStart(8, "0"); - return hashHex.repeat(8).substring(0, 64); - }; - - _internalSetInitialState(); - registry.enable(); - workerdConfigured = true; + Object.assign(iso, createWorkerdIsomorph()); } diff --git a/js/src/workerd/index.ts b/js/src/workerd/index.ts index 7f1f92300..233c0b831 100644 --- a/js/src/workerd/index.ts +++ b/js/src/workerd/index.ts @@ -5,9 +5,4 @@ * runtime environments. */ -import { configureWorkerd } from "./config"; - -configureWorkerd(); - export * from "../exports"; -export * as default from "../exports"; diff --git a/js/src/workerd/isomorph.ts b/js/src/workerd/isomorph.ts new file mode 100644 index 000000000..06f710c28 --- /dev/null +++ b/js/src/workerd/isomorph.ts @@ -0,0 +1,5 @@ +import type { Common } from "../isomorph-base"; +export type { CallerLocation, IsoAsyncLocalStorage } from "../isomorph-base"; +import { createWorkerdIsomorph } from "./platform"; +const iso: Common = /* @__PURE__ */ createWorkerdIsomorph(); +export default iso; diff --git a/js/src/workerd/platform.ts b/js/src/workerd/platform.ts new file mode 100644 index 000000000..e611d8c49 --- /dev/null +++ b/js/src/workerd/platform.ts @@ -0,0 +1,43 @@ +import { createIsomorph, type Common } from "../isomorph-base"; +import { resolveRuntimeAsyncLocalStorage } from "../runtime-async-local-storage"; + +/** + * Configure the isomorph for Cloudflare Workers (workerd) runtime. + */ +export function createWorkerdIsomorph(): Common { + const iso = createIsomorph(); + + iso.buildType = "workerd"; + + const fallbackAsyncLocalStorage = iso.newAsyncLocalStorage; + iso.newAsyncLocalStorage = () => { + const RuntimeAsyncLocalStorage = resolveRuntimeAsyncLocalStorage(); + return RuntimeAsyncLocalStorage + ? new RuntimeAsyncLocalStorage() + : fallbackAsyncLocalStorage(); + }; + + iso.getEnv = (name: string) => { + if (typeof process === "undefined" || typeof process.env === "undefined") { + return undefined; + } + return process.env[name]; + }; + iso.getBraintrustApiKey = async () => { + const value = iso.getEnv("BRAINTRUST_API_KEY"); + return value?.trim() ? value : undefined; + }; + + iso.hash = (data: string): string => { + let hash = 0; + for (let i = 0; i < data.length; i++) { + const char = data.charCodeAt(i); + hash = (hash << 5) - hash + char; + hash = hash & hash; + } + const hashHex = (hash >>> 0).toString(16).padStart(8, "0"); + return hashHex.repeat(8).substring(0, 64); + }; + + return iso; +} diff --git a/js/src/workflow-eval.test.ts b/js/src/workflow-eval.test.ts index 52e4cf691..ae4bf3106 100644 --- a/js/src/workflow-eval.test.ts +++ b/js/src/workflow-eval.test.ts @@ -1,4 +1,4 @@ -import { describe, expect, expectTypeOf, test, vi } from "vitest"; +import { describe, expect, expectTypeOf, test, vi } from "vite-plus/test"; import { configureNode } from "./node/config"; import { WorkflowBatchScorer, diff --git a/js/src/workflow-eval.ts b/js/src/workflow-eval.ts index 0576aa475..ddce7959e 100644 --- a/js/src/workflow-eval.ts +++ b/js/src/workflow-eval.ts @@ -29,20 +29,19 @@ import { type OneOrMoreScores, runEvaluator, } from "./framework"; -import iso from "./isomorph"; +import iso from "#braintrust-platform"; import { type BaseMetadata, type DefaultMetadataType, type EvalCase, - type Experiment, - type ExperimentSummary, NOOP_SPAN, type Span, _internalResumeSpan, _internalStartSpanWithInitialMerge, logError as logSpanError, newId, -} from "./logger"; +} from "./logger/core"; +import { type Experiment, type ExperimentSummary } from "./logger/experiments"; const encoder = new TextEncoder(); const decoder = new TextDecoder(); @@ -94,20 +93,23 @@ export class WorkflowEvalMemoryStore implements WorkflowEvalStore { this.values.set(key, value.slice()); } - async getOrSet(key: string, value: Uint8Array) { + async getOrSet( + key: string, + value: Uint8Array, + ): Promise<{ value: Uint8Array; created: boolean }> { const existing = this.values.get(key); if (existing) return { value: existing.slice(), created: false }; this.values.set(key, value.slice()); return { value: value.slice(), created: true }; } - async addToSet(key: string, member: string) { + async addToSet(key: string, member: string): Promise { let members = this.sets.get(key); if (!members) this.sets.set(key, (members = new Set())); members.add(member); } - async getSetSize(key: string) { + async getSetSize(key: string): Promise { return this.sets.get(key)?.size ?? 0; } } @@ -181,7 +183,10 @@ export class WorkflowEvalRedisStore implements WorkflowEvalStore { } } - async getOrSet(key: string, value: Uint8Array) { + async getOrSet( + key: string, + value: Uint8Array, + ): Promise<{ value: Uint8Array; created: boolean }> { const redisKey = `${this.keyPrefix}${key}`; const encoded = uint8ArrayToBase64(value); const client = this.client as typeof this.client & { @@ -214,7 +219,7 @@ export class WorkflowEvalRedisStore implements WorkflowEvalStore { return { value: base64ToUint8Array(existing), created: false }; } - async addToSet(key: string, member: string) { + async addToSet(key: string, member: string): Promise { await this.evalSet( "redis.call('SADD', KEYS[1], ARGV[1]); redis.call('PEXPIRE', KEYS[1], ARGV[2]); return 1", key, @@ -222,7 +227,7 @@ export class WorkflowEvalRedisStore implements WorkflowEvalStore { ); } - async getSetSize(key: string) { + async getSetSize(key: string): Promise { return this.evalSet("return redis.call('SCARD', KEYS[1])", key, []); } @@ -1029,7 +1034,6 @@ async function startWorkflowEval< stop: () => undefined, increment: () => undefined, }, - [], undefined, parameters, true, diff --git a/js/src/wrappers/ai-sdk-evaluate.test.ts b/js/src/wrappers/ai-sdk-evaluate.test.ts index ea09a2c47..e98be9dda 100644 --- a/js/src/wrappers/ai-sdk-evaluate.test.ts +++ b/js/src/wrappers/ai-sdk-evaluate.test.ts @@ -1,15 +1,18 @@ /* eslint-disable @typescript-eslint/no-explicit-any */ /* eslint-disable @typescript-eslint/consistent-type-assertions */ -import { afterEach, beforeEach, describe, expect, test, vi } from "vitest"; import { - _exportsForTestingOnly, - initLogger, - traced, - type TestBackgroundLogger, -} from "../logger"; + afterEach, + beforeEach, + describe, + expect, + test, + vi, +} from "vite-plus/test"; +import { _exportsForTestingOnly } from "../logger/testing"; +import { initLogger, traced, type TestBackgroundLogger } from "../logger/core"; import { configureNode } from "../node/config"; -import { aiSDKChannels } from "../instrumentation/plugins/ai-sdk-interceptors"; -import { registry } from "../instrumentation/registry"; +import { aiSDKChannels } from "../instrumentation/providers/ai-sdk-interceptors"; +import { registerInstrumentation } from "../instrumentation/registrations/aisdk"; import { runWithAutoInstrumentationSuppressed } from "../instrumentation/auto-instrumentation-suppression"; import type { AISDKEvaluateParams, @@ -92,7 +95,7 @@ describe.each(["experimental_evaluate", "experimental_decide"] as const)( }); test("keeps media request, receiver, promise and result identity", async () => { - vi.stubEnv("BRAINTRUST_CAPTURE_ATTACHMENTS", "true"); + vi.stubEnv("BRAINTRUST_AUTO_CAPTURE_ATTACHMENTS", "true"); const bytes = new Uint8Array([1, 2]); const request = { ...params, @@ -252,21 +255,13 @@ describe.each(["experimental_evaluate", "experimental_decide"] as const)( expect(await backgroundLogger.drain()).toHaveLength(1); }); - test("preserves calls while disabled and resumes without duplicate subscriptions", async () => { + test("registers repeatedly without duplicate subscriptions", async () => { const original = Promise.resolve(result); const wrapped = wrapAISDK({ [method]: (_params: AISDKEvaluateParams) => original, }); - registry.disable(); - registry.disable(); - try { - expect(wrapped[method](params)).toBe(original); - await original; - expect(await backgroundLogger.drain()).toHaveLength(0); - } finally { - registry.enable(); - registry.enable(); - } + registerInstrumentation(); + registerInstrumentation(); await wrapped[method](params); expect(await backgroundLogger.drain()).toHaveLength(1); }); diff --git a/js/src/wrappers/ai-sdk/ai-sdk-generate-image.test.ts b/js/src/wrappers/ai-sdk/ai-sdk-generate-image.test.ts index ccba4814e..cb0334a67 100644 --- a/js/src/wrappers/ai-sdk/ai-sdk-generate-image.test.ts +++ b/js/src/wrappers/ai-sdk/ai-sdk-generate-image.test.ts @@ -1,11 +1,15 @@ /* eslint-disable @typescript-eslint/no-explicit-any */ /* eslint-disable @typescript-eslint/consistent-type-assertions */ -import { afterEach, beforeEach, describe, expect, test, vi } from "vitest"; import { - _exportsForTestingOnly, - initLogger, - TestBackgroundLogger, -} from "../../logger"; + afterEach, + beforeEach, + describe, + expect, + test, + vi, +} from "vite-plus/test"; +import { _exportsForTestingOnly } from "../../logger/testing"; +import { initLogger, TestBackgroundLogger } from "../../logger/core"; import { configureNode } from "../../node/config"; import { wrapAISDK } from "./ai-sdk"; diff --git a/js/src/wrappers/ai-sdk/ai-sdk.test.ts b/js/src/wrappers/ai-sdk/ai-sdk.test.ts index cc07edbdd..78c6a118b 100644 --- a/js/src/wrappers/ai-sdk/ai-sdk.test.ts +++ b/js/src/wrappers/ai-sdk/ai-sdk.test.ts @@ -10,15 +10,12 @@ import { expectTypeOf, expect, vi, -} from "vitest"; +} from "vite-plus/test"; import { configureNode } from "../../node/config"; import * as ai from "ai"; import { openai } from "@ai-sdk/openai"; -import { - _exportsForTestingOnly, - initLogger, - TestBackgroundLogger, -} from "../../logger"; +import { _exportsForTestingOnly } from "../../logger/testing"; +import { initLogger, TestBackgroundLogger } from "../../logger/core"; import { wrapAISDK, wrapAgentClass } from "./ai-sdk"; import { getCurrentUnixTimestamp } from "../../util"; import { readFileSync } from "fs"; diff --git a/js/src/wrappers/ai-sdk/ai-sdk.ts b/js/src/wrappers/ai-sdk/ai-sdk.ts index 86a5d874d..dc1c8acf9 100644 --- a/js/src/wrappers/ai-sdk/ai-sdk.ts +++ b/js/src/wrappers/ai-sdk/ai-sdk.ts @@ -1,3 +1,4 @@ +import { registerInstrumentation } from "../../instrumentation/registrations/aisdk"; /* eslint-disable @typescript-eslint/no-explicit-any */ import { SpanTypeAttribute } from "../../../util"; @@ -6,7 +7,7 @@ import { codeModeChannels, BRAINTRUST_WRAPPED_AI_SDK_MODEL, harnessAgentChannels, -} from "../../instrumentation/plugins/ai-sdk-interceptors"; +} from "../../instrumentation/providers/ai-sdk-interceptors"; import type { AISDK, AISDKAgentClass, @@ -121,6 +122,7 @@ function isModuleNamespace(obj: unknown): obj is AISDKNamespaceObject { * ``` */ export function wrapAISDK(aiSDK: T, options: WrapAISDKOptions = {}): T { + registerInstrumentation(); // Handle null/undefined early - can't create Proxy with non-objects if (!aiSDK || typeof aiSDK !== "object") { return aiSDK; @@ -306,6 +308,7 @@ export const wrapAgentClass = ( AgentClass: any, options: WrapAISDKOptions = {}, ): any => { + registerInstrumentation(); const typedAgentClass = AgentClass as | AISDKAgentClass | AISDKWorkflowAgentClass; diff --git a/js/src/wrappers/ai-sdk/deprecated/BraintrustMiddleware.ts b/js/src/wrappers/ai-sdk/deprecated/BraintrustMiddleware.ts deleted file mode 100644 index 30232cc3e..000000000 --- a/js/src/wrappers/ai-sdk/deprecated/BraintrustMiddleware.ts +++ /dev/null @@ -1,408 +0,0 @@ -/* eslint-disable @typescript-eslint/no-explicit-any */ -/* eslint-disable @typescript-eslint/consistent-type-assertions */ -import { SpanTypeAttribute } from "../../../../util/index"; -import { startSpan, type CompiledPrompt } from "../../../logger"; -import { - INSTRUMENTATION_NAMES, - withSpanInstrumentationName, -} from "../../../span-origin"; -import { processInputAttachments } from "../../attachment-utils"; -import { extractTokenMetrics } from "../../../instrumentation/plugins/ai-sdk-metrics"; -import type { - AISDKProviderMetadata, - AISDKUsage, -} from "../../../vendor-sdk-types/ai-sdk-common"; - -function detectProviderFromResult(result: { - providerMetadata?: Record; -}): string | undefined { - if (!result?.providerMetadata) { - return undefined; - } - - const keys = Object.keys(result.providerMetadata); - return keys?.at(0); -} - -function extractModelFromResult(result: { - response?: { modelId?: string }; - request?: { body?: { model?: string } }; -}): string | undefined { - if (result?.response?.modelId) { - return result.response.modelId; - } - - if (result?.request?.body?.model) { - return result.request.body.model; - } - - return undefined; -} - -function camelToSnake(str: string): string { - return str.replace(/[A-Z]/g, (letter) => `_${letter.toLowerCase()}`); -} - -function extractModelParameters( - params: Record, - excludeKeys: Set, -): Record { - const modelParams: Record = {}; - - for (const [key, value] of Object.entries(params)) { - if (value !== undefined && !excludeKeys.has(key)) { - const snakeKey = camelToSnake(key); - modelParams[snakeKey] = value; - } - } - - return modelParams; -} - -function normalizeFinishReason(reason: any): string | undefined { - if (typeof reason !== "string") return undefined; - return reason.replace(/-/g, "_"); -} - -function buildAssistantOutputWithToolCalls(result: any, toolCalls: any[]) { - return [ - { - index: 0, - logprobs: null, - finish_reason: - normalizeFinishReason(result?.finishReason) ?? - (toolCalls.length ? "tool_calls" : undefined), - message: { - role: "assistant", - tool_calls: toolCalls.length > 0 ? toolCalls : undefined, - }, - }, - ]; -} - -function extractToolCallsFromSteps(steps: any[] | undefined) { - const toolCalls: any[] = []; - if (!Array.isArray(steps)) return toolCalls; - let idx = 0; - for (const step of steps) { - const blocks: any[] | undefined = (step as any)?.content; - if (!Array.isArray(blocks)) continue; - for (const block of blocks) { - if ( - block && - typeof block === "object" && - (block as any).type === "tool-call" - ) { - toolCalls.push({ - id: (block as any).toolCallId, - type: "function", - index: idx++, - function: { - name: (block as any).toolName, - arguments: - typeof (block as any).input === "string" - ? (block as any).input - : JSON.stringify((block as any).input ?? {}), - }, - }); - } - } - } - return toolCalls; -} - -function extractToolCallsFromBlocks(blocks: any[] | undefined) { - if (!Array.isArray(blocks)) return []; - return extractToolCallsFromSteps([{ content: blocks }] as any); -} - -function extractInput(params: any) { - return params?.prompt ?? params?.messages ?? params?.system; -} - -// Generic middleware interface that works with any AI SDK types -interface LanguageModelV2Middleware { - wrapGenerate?: (options: { - doGenerate: () => any; - doStream: () => any; - params: TCallOptions; - model: TModel; - }) => Promise; - wrapStream?: (options: { - doGenerate: () => any; - doStream: () => any; - params: TCallOptions; - model: TModel; - }) => Promise; -} - -/** - * Configuration options for the AI SDK middleware - */ -interface MiddlewareConfig { - /** Enable debug logging */ - debug?: boolean; - /** Name identifier for the middleware instance */ - name?: string; - /** Span info from loadPrompt for prompt version tracking */ - spanInfo?: CompiledPrompt<"chat">["span_info"]; -} - -// V2-specific exclude keys for extractModelParameters -const V2_EXCLUDE_KEYS = new Set([ - "prompt", // Already captured as input - "system", // Already captured as input - "messages", // Already captured as input - "model", // Already captured in metadata.model - "providerOptions", // Internal AI SDK configuration -]); - -/** - * Creates a Braintrust middleware for AI SDK v2 that automatically traces - * generateText and streamText calls with comprehensive metadata and metrics. - * - * @param config - Configuration options for the middleware - * @returns A middleware object compatible with AI SDK v2's wrapLanguageModel - * - * @deprecated The new `wrapAISDK` replaces this middleware. - * - * @example - * ```typescript - * import { wrapLanguageModel } from "ai"; - * import { openai } from "@ai-sdk/openai"; - * import { BraintrustMiddleware } from "braintrust"; - * - * const model = wrapLanguageModel({ - * model: openai("gpt-4"), - * middleware: BraintrustMiddleware({ debug: true, name: "MyMiddleware" }) - * }); - * ``` - * - */ -export function BraintrustMiddleware( - config: MiddlewareConfig = {}, -): LanguageModelV2Middleware { - return { - wrapGenerate: async ({ - doGenerate, - params, - model: modelFromWrapGenerate, - }) => { - // Extract and process input attachments - const rawInput = extractInput(params); - const processedInput = processInputAttachments(rawInput); - - const spanArgs = { - name: config.spanInfo?.name || "ai-sdk.doGenerate", - spanAttributes: { - type: SpanTypeAttribute.LLM, - ...(config.spanInfo?.spanAttributes || {}), - }, - event: { - input: processedInput, - metadata: { - ...extractModelParameters(params, V2_EXCLUDE_KEYS), - ...(config.spanInfo?.metadata || {}), - }, - }, - }; - - const span = startSpan( - withSpanInstrumentationName(spanArgs, INSTRUMENTATION_NAMES.AI_SDK), - ); - - try { - const result = await doGenerate(); - - const metadata: Record = {}; - - const provider = detectProviderFromResult(result); - if (provider !== undefined) { - metadata.provider = provider; - } - - if (result.finishReason !== undefined) { - metadata.finish_reason = result.finishReason; - } - - const model = extractModelFromResult(result); - if (model !== undefined) { - metadata.model = model; - } else if (modelFromWrapGenerate) { - const modelId = modelFromWrapGenerate.modelId; - if (modelId) { - metadata.model = modelId; - } - } - - let toolCalls = extractToolCallsFromSteps((result as any)?.steps); - if (!toolCalls || toolCalls.length === 0) { - toolCalls = extractToolCallsFromBlocks((result as any)?.content); - } - - span.log({ - output: - toolCalls.length > 0 - ? buildAssistantOutputWithToolCalls(result, toolCalls) - : (result as any)?.content, - metadata, - metrics: extractTokenMetrics({ - usage: result.usage, - providerMetadata: result.providerMetadata, - }), - }); - - return result; - } catch (error) { - span.log({ - error: error instanceof Error ? error.message : String(error), - }); - throw error; - } finally { - span.end(); - } - }, - wrapStream: async ({ doStream, params }) => { - // Extract and process input attachments - const rawInput = extractInput(params); - const processedInput = processInputAttachments(rawInput); - - const spanArgs = { - name: config.spanInfo?.name || "ai-sdk.doStream", - spanAttributes: { - type: SpanTypeAttribute.LLM, - ...(config.spanInfo?.spanAttributes || {}), - }, - event: { - input: processedInput, - metadata: { - ...extractModelParameters(params, V2_EXCLUDE_KEYS), - ...(config.spanInfo?.metadata || {}), - }, - }, - }; - - const span = startSpan( - withSpanInstrumentationName(spanArgs, INSTRUMENTATION_NAMES.AI_SDK), - ); - - try { - const { stream, ...rest } = await doStream(); - - const textChunks: string[] = []; - const toolBlocks: any[] = []; - let finalUsage: AISDKUsage = {}; - let finalFinishReason: unknown = undefined; - let providerMetadata: AISDKProviderMetadata = {}; - - const transformStream = new TransformStream({ - transform(chunk: any, controller: any) { - try { - // Collect text deltas - if (chunk.type === "text-delta" && chunk.delta) { - textChunks.push(chunk.delta); - } - - // Collect tool call/result blocks for formatting later - if (chunk.type === "tool-call" || chunk.type === "tool-result") { - toolBlocks.push(chunk); - } - - // Capture final metadata - if (chunk.type === "finish") { - finalFinishReason = chunk.finishReason; - finalUsage = chunk.usage || {}; - providerMetadata = chunk.providerMetadata || {}; - } - - controller.enqueue(chunk); - } catch (error) { - // Log stream processing error - span.log({ - error: error instanceof Error ? error.message : String(error), - }); - span.end(); - controller.error(error); - } - }, - - flush() { - try { - // Log the final aggregated result when stream completes - const generatedText = textChunks.join(""); - let output: unknown = generatedText - ? [{ type: "text", text: generatedText }] - : []; - - // Create a result object for provider detection - const resultForDetection = { - providerMetadata, - response: rest.response, - ...rest, - finishReason: finalFinishReason, - }; - - const metadata: Record = {}; - - const provider = detectProviderFromResult(resultForDetection); - if (provider !== undefined) { - metadata.provider = provider; - } - - if (finalFinishReason !== undefined) { - metadata.finish_reason = finalFinishReason; - } - - const model = extractModelFromResult(resultForDetection); - if (model !== undefined) { - metadata.model = model; - } - - // If tool calls streamed, prefer assistant tool_calls output - if (toolBlocks.length > 0) { - const toolCalls = extractToolCallsFromSteps([ - { content: toolBlocks }, - ] as any); - if (toolCalls.length > 0) { - output = buildAssistantOutputWithToolCalls( - resultForDetection, - toolCalls, - ); - } - } - - span.log({ - output, - metadata, - metrics: extractTokenMetrics({ - usage: finalUsage, - providerMetadata, - }), - }); - - span.end(); - } catch (error) { - // Log flush error - span.log({ - error: error instanceof Error ? error.message : String(error), - }); - span.end(); - throw error; - } - }, - }); - - return { - stream: stream.pipeThrough(transformStream), - ...rest, - }; - } catch (error) { - span.log({ - error: error instanceof Error ? error.message : String(error), - }); - span.end(); - throw error; - } - }, - }; -} diff --git a/js/src/wrappers/ai-sdk/deprecated/wrapAISDKModel.ts b/js/src/wrappers/ai-sdk/deprecated/wrapAISDKModel.ts deleted file mode 100644 index f28764813..000000000 --- a/js/src/wrappers/ai-sdk/deprecated/wrapAISDKModel.ts +++ /dev/null @@ -1,362 +0,0 @@ -import { debugLogger } from "../../../debug-logger"; -/* eslint-disable @typescript-eslint/consistent-type-assertions */ -/* eslint-disable @typescript-eslint/no-explicit-any */ -import { startSpan } from "../../../logger"; -import { - INSTRUMENTATION_NAMES, - withSpanInstrumentationName, -} from "../../../span-origin"; -import { getCurrentUnixTimestamp, isEmpty } from "../../../util"; -import { - LEGACY_CACHED_HEADER, - parseCachedHeader, - X_CACHED_HEADER, -} from "../../oai"; - -/** - * Wrap an ai-sdk model (created with `.chat()`, `.completion()`, etc.) to add tracing. If Braintrust is - * not configured, this is a no-op - * - * @param model - * @deprecated You should use the new `wrapAISDK`. - * @returns The wrapped object. - */ -export function wrapAISDKModel(model: T): T { - const m = model as any; - if ( - m?.specificationVersion === "v1" && - typeof m?.provider === "string" && - typeof m?.modelId === "string" - ) { - return new BraintrustLanguageModelWrapper(m) as any as T; - } else { - debugLogger.notice.warn("Unsupported AI SDK model. Not wrapping."); - return model; - } -} - -class BraintrustLanguageModelWrapper { - supportsUrl?: (url: URL) => boolean; - - constructor(private model: any) { - // Only define supportsUrl if the model has this method - if (typeof this.model.supportsUrl === "function") { - this.supportsUrl = (url: URL) => this.model.supportsUrl!(url); - } - } - - get specificationVersion() { - return this.model.specificationVersion; - } - - get provider(): string { - return this.model.provider; - } - - get modelId(): string { - return this.model.modelId; - } - - get defaultObjectGenerationMode() { - return this.model.defaultObjectGenerationMode; - } - - get supportsImageUrls(): boolean | undefined { - return this.model.supportsImageUrls; - } - - get supportsStructuredOutputs(): boolean | undefined { - return this.model.supportsStructuredOutputs; - } - - // For the first cut, do not support custom span_info arguments. We can - // propagate those via async local storage - async doGenerate(options: any) { - const span = startSpan( - withSpanInstrumentationName( - { - name: "Chat Completion", - spanAttributes: { - type: "llm", - }, - }, - INSTRUMENTATION_NAMES.AI_SDK, - ), - ); - const { prompt, mode, ...rest } = options; - const startTime = getCurrentUnixTimestamp(); - - try { - const ret = await this.model.doGenerate(options); - span.log({ - input: postProcessPrompt(prompt), - metadata: { - model: this.modelId, - ...rest, - ...("tools" in mode && mode.tools - ? { tools: convertTools(mode.tools) } - : "tool" in mode && mode.tool - ? { tools: convertTools([mode.tool]) } - : {}), - }, - output: postProcessOutput(ret.text, ret.toolCalls, ret.finishReason), - metrics: { - time_to_first_token: getCurrentUnixTimestamp() - startTime, - tokens: !isEmpty(ret.usage) - ? ret.usage.promptTokens + ret.usage.completionTokens - : undefined, - prompt_tokens: ret.usage?.promptTokens, - completion_tokens: ret.usage?.completionTokens, - cached: parseCachedHeader( - ret.rawResponse?.headers?.[X_CACHED_HEADER] ?? - ret.rawResponse?.headers?.[LEGACY_CACHED_HEADER], - ), - }, - }); - return ret; - } finally { - span.end(); - } - } - - async doStream(options: any) { - const { prompt, mode, ...rest } = options; - const startTime = getCurrentUnixTimestamp(); - - const span = startSpan( - withSpanInstrumentationName( - { - name: "Chat Completion", - spanAttributes: { - type: "llm", - }, - }, - INSTRUMENTATION_NAMES.AI_SDK, - ), - ); - - span.log({ - input: postProcessPrompt(prompt), - metadata: { - model: this.modelId, - ...rest, - ...("tools" in mode && mode.tools - ? { tools: convertTools(mode.tools) } - : "tool" in mode && mode.tool - ? { tools: convertTools([mode.tool]) } - : {}), - }, - }); - - let ended = false; - const end = () => { - if (!ended) { - span.end(); - ended = true; - } - }; - - try { - const ret = await this.model.doStream(options); - - let time_to_first_token: number | undefined = undefined; - let usage: - | { - promptTokens: number; - completionTokens: number; - } - | undefined = undefined; - let fullText: string | undefined = undefined; - const toolCalls: Record = {}; - let finishReason: any | undefined = undefined; - return { - ...ret, - stream: ret.stream.pipeThrough( - new TransformStream({ - transform(chunk, controller) { - if (time_to_first_token === undefined) { - time_to_first_token = getCurrentUnixTimestamp() - startTime; - span.log({ metrics: { time_to_first_token } }); - } - switch (chunk.type) { - case "text-delta": - if (fullText === undefined) { - fullText = ""; - } - fullText += chunk.textDelta; - break; - case "tool-call": - toolCalls[chunk.toolCallId] = { - toolCallType: chunk.toolCallType, - toolCallId: chunk.toolCallId, - toolName: chunk.toolName, - args: chunk.args, - }; - break; - case "tool-call-delta": - if (toolCalls[chunk.toolCallId] === undefined) { - toolCalls[chunk.toolCallId] = { - toolCallType: chunk.toolCallType, - toolCallId: chunk.toolCallId, - toolName: chunk.toolName, - args: "", - }; - } - toolCalls[chunk.toolCallId].args += chunk.argsTextDelta; - break; - case "finish": - usage = chunk.usage; - finishReason = chunk.finishReason; - break; - } - - controller.enqueue(chunk); - }, - async flush(controller) { - span.log({ - output: postProcessOutput( - fullText, - Object.keys(toolCalls).length > 0 - ? Object.values(toolCalls) - : undefined, - finishReason!, - ), - metrics: { - time_to_first_token, - tokens: !isEmpty(usage) - ? usage.promptTokens + usage.completionTokens - : undefined, - prompt_tokens: usage?.promptTokens, - completion_tokens: usage?.completionTokens, - cached: parseCachedHeader( - ret.rawResponse?.headers?.[X_CACHED_HEADER] ?? - ret.rawResponse?.headers?.[LEGACY_CACHED_HEADER], - ), - }, - }); - end(); - controller.terminate(); - }, - }), - ), - }; - } finally { - end(); - } - } -} - -function convertTools(tools: Array): any[] { - return tools.map((tool) => { - const { type: _, ...rest } = tool; - return { - type: tool.type, - function: rest, - }; - }) as any[]; -} - -function postProcessPrompt(prompt: any): any[] { - return prompt.flatMap((message: any) => { - switch (message.role) { - case "system": - return [ - { - role: "system", - content: message.content, - }, - ]; - case "assistant": - const textPart = message.content.find( - (part: any) => part.type === "text", - ) as any | undefined; - const toolCallParts = message.content.filter( - (part: any) => part.type === "tool-call", - ) as any[]; - return [ - { - role: "assistant", - content: textPart?.text, - ...(toolCallParts.length > 0 - ? { - tool_calls: toolCallParts.map((part) => ({ - id: part.toolCallId, - function: { - name: part.toolName, - arguments: JSON.stringify(part.args), - }, - type: "function" as const, - })), - } - : {}), - }, - ]; - case "user": - return [ - { - role: "user", - content: message.content.map((part: any): any => { - switch (part.type) { - case "text": - return { - type: "text", - text: part.text, - ...(part.providerMetadata - ? { providerMetadata: part.providerMetadata } - : {}), - }; - case "image": - return { - type: "image_url", - image_url: { - url: part.image.toString(), - ...(part.providerMetadata - ? { providerMetadata: part.providerMetadata } - : {}), - }, - }; - default: - // We don't support files directly but also don't want to block them from being logged - return part as any; - } - }), - }, - ]; - case "tool": - return message.content.map((part: any) => ({ - role: "tool", - tool_call_id: part.toolCallId, - content: JSON.stringify(part.result), - })); - } - }); -} - -function postProcessOutput( - text: string | undefined, - toolCalls: any[] | undefined, - finishReason: any, -) { - return [ - { - index: 0, - message: { - role: "assistant", - content: text ?? "", - ...(toolCalls && toolCalls.length > 0 - ? { - tool_calls: toolCalls.map((toolCall) => ({ - id: toolCall.toolCallId, - function: { - name: toolCall.toolName, - arguments: toolCall.args, - }, - type: "function" as const, - })), - } - : {}), - }, - finish_reason: finishReason, - }, - ]; -} diff --git a/js/src/wrappers/ai-sdk/harness-agent-context.ts b/js/src/wrappers/ai-sdk/harness-agent-context.ts index 2768f9ab7..746596918 100644 --- a/js/src/wrappers/ai-sdk/harness-agent-context.ts +++ b/js/src/wrappers/ai-sdk/harness-agent-context.ts @@ -1,15 +1,15 @@ +import { _internalStartSpan } from "../../logger/core"; import { SpanComponentsV4 } from "../../../util/span_identifier_v4"; -import type { IsoAsyncLocalStorage } from "../../isomorph"; -import iso from "../../isomorph"; +import type { IsoAsyncLocalStorage } from "#braintrust-platform"; +import iso from "#braintrust-platform"; import { _internalExportParentSynchronously, _internalGetGlobalState, currentSpan, - startSpan, updateSpan, type Span, type StartSpanArgs, -} from "../../logger"; +} from "../../logger/core"; import { INSTRUMENTATION_NAMES, withSpanInstrumentationName, @@ -332,9 +332,10 @@ export function startHarnessTurnChildSpan( args, INSTRUMENTATION_NAMES.AI_SDK, ); + const { parent: _ignoredParent, ...publicSpanArgs } = spanArgs; return typeof parent === "string" - ? startSpan({ ...spanArgs, parent }) - : parent.startSpan(spanArgs); + ? _internalStartSpan({ ...spanArgs, parent }) + : parent.startSpan(publicSpanArgs); } export function updateHarnessTurn( diff --git a/js/src/wrappers/ai-sdk/index.ts b/js/src/wrappers/ai-sdk/index.ts index b2058cd16..f9e1d86f3 100644 --- a/js/src/wrappers/ai-sdk/index.ts +++ b/js/src/wrappers/ai-sdk/index.ts @@ -1,6 +1,2 @@ export { wrapAISDK, wrapAgentClass } from "./ai-sdk"; export { braintrustAISDKTelemetry } from "./telemetry"; - -// TODO: remove in the next major release -export { wrapAISDKModel } from "./deprecated/wrapAISDKModel"; -export { BraintrustMiddleware } from "./deprecated/BraintrustMiddleware"; diff --git a/js/src/wrappers/ai-sdk/normalize-logged-output.ts b/js/src/wrappers/ai-sdk/normalize-logged-output.ts index 40773f126..291ff80df 100644 --- a/js/src/wrappers/ai-sdk/normalize-logged-output.ts +++ b/js/src/wrappers/ai-sdk/normalize-logged-output.ts @@ -1,4 +1,5 @@ import type { AISDKResult } from "../../vendor-sdk-types/ai-sdk"; +import { BaseAttachment } from "../../logger/core"; const REMOVE_NORMALIZED_VALUE = Symbol("braintrust.ai-sdk.remove-normalized"); @@ -21,7 +22,12 @@ function normalizeAISDKLoggedValue( .filter((entry) => entry !== REMOVE_NORMALIZED_VALUE); } - if (!value || typeof value !== "object") { + if ( + !value || + typeof value !== "object" || + value instanceof BaseAttachment || + value instanceof URL + ) { return value; } diff --git a/js/src/wrappers/ai-sdk/telemetry.ts b/js/src/wrappers/ai-sdk/telemetry.ts index cec1832a0..6e9eb3197 100644 --- a/js/src/wrappers/ai-sdk/telemetry.ts +++ b/js/src/wrappers/ai-sdk/telemetry.ts @@ -3,7 +3,7 @@ import { AI_SDK_TOOL_CALL_ID, AI_SDK_TOOL_CAPTURE, type AISDKToolSpan, -} from "../../instrumentation/plugins/ai-sdk-tool-tracing"; +} from "../../instrumentation/providers/ai-sdk-tool-tracing"; import { isAutoInstrumentationSuppressed } from "../../instrumentation/auto-instrumentation-suppression"; import { SpanTypeAttribute, @@ -11,7 +11,7 @@ import { isObject, isObjectOrArray, } from "../../util"; -import { logError, startSpan, withCurrent, type Span } from "../../logger"; +import { logError, startSpan, withCurrent, type Span } from "../../logger/core"; import { INSTRUMENTATION_NAMES, withSpanInstrumentationName, @@ -36,7 +36,7 @@ import { processAISDKWorkflowAgentCallInput, processAISDKWorkflowAgentModelCallInput, serializeModelWithProvider, -} from "../../instrumentation/plugins/ai-sdk-plugin"; +} from "../../instrumentation/providers/ai-sdk-instrumentation"; import type { AISDKCallParams, AISDKEvaluationResult, @@ -61,7 +61,7 @@ import { currentWorkflowAgentWrapperSpan } from "./workflow-agent-context"; import { currentCloudflareThinkRun, type CloudflareThinkRun, -} from "../../instrumentation/plugins/cloudflare-think-context"; +} from "../../instrumentation/providers/cloudflare-think-context"; import { currentHarnessTurnParent, startHarnessTurnChildSpan, @@ -393,7 +393,9 @@ export function braintrustAISDKTelemetry(): any { span.log({ ...(shouldRecordOutputs(event) ? { - output: processAISDKOutput(result, DEFAULT_DENY_OUTPUT_PATHS), + output: withCurrent(span, () => + processAISDKOutput(result, DEFAULT_DENY_OUTPUT_PATHS), + ), } : {}), metrics: extractTokenMetrics(result), @@ -522,9 +524,11 @@ export function braintrustAISDKTelemetry(): any { state.span.log({ ...(shouldRecordOutputs(event) ? { - output: applyAISDKToolModelOutputs( - finishOutput(result, state.operationName), - aiSDKModelOutputToolNames(state.tools), + output: withCurrent(state.span, () => + applyAISDKToolModelOutputs( + finishOutput(result, state.operationName), + aiSDKModelOutputToolNames(state.tools), + ), ), } : {}), @@ -671,9 +675,11 @@ export function braintrustAISDKTelemetry(): any { if (shouldRecordInputs(event)) { const callInput = workflowAgentCallInput ?? operationInput(event, operationName); - const { input, outputPromise } = workflowAgent - ? processAISDKWorkflowAgentCallInput(callInput) - : processAISDKCallInput(callInput); + const { input, outputPromise } = withCurrent(span, () => + workflowAgent + ? processAISDKWorkflowAgentCallInput(callInput) + : processAISDKCallInput(callInput), + ); logPayload.input = input; const state = operations.get(operationKey); if (state) { @@ -714,12 +720,15 @@ export function braintrustAISDKTelemetry(): any { const operationName = state?.operationName ?? "generateText"; const callInput = operationInput(event, operationName); const workflowAgent = operationName === "WorkflowAgent.stream"; - const processedInput = + const processInput = () => shouldRecordInputs(event) && workflowAgent ? processAISDKWorkflowAgentModelCallInput(callInput).input : shouldRecordInputs(event) ? processAISDKCallInput(callInput).input : undefined; + const processedInput = state + ? withCurrent(state.span, processInput) + : processInput(); if ( workflowAgent && state?.ownsSpan && @@ -862,7 +871,9 @@ export function braintrustAISDKTelemetry(): any { span.log({ ...(shouldRecordOutputs(event) ? { - output: processAISDKOutput(result, DEFAULT_DENY_OUTPUT_PATHS), + output: withCurrent(span, () => + processAISDKOutput(result, DEFAULT_DENY_OUTPUT_PATHS), + ), } : {}), metrics: extractTokenMetrics(result), diff --git a/js/src/wrappers/ai-sdk/workflow-agent-context.ts b/js/src/wrappers/ai-sdk/workflow-agent-context.ts index cb602bb62..da0e1c6e1 100644 --- a/js/src/wrappers/ai-sdk/workflow-agent-context.ts +++ b/js/src/wrappers/ai-sdk/workflow-agent-context.ts @@ -1,4 +1,4 @@ -import { _internalGetGlobalState, type Span } from "../../logger"; +import { _internalGetGlobalState, type Span } from "../../logger/core"; const workflowAgentWrapperSpans = new Map(); diff --git a/js/src/wrappers/anthropic-no-install.test.ts b/js/src/wrappers/anthropic-no-install.test.ts index b303cc542..47e64a7dd 100644 --- a/js/src/wrappers/anthropic-no-install.test.ts +++ b/js/src/wrappers/anthropic-no-install.test.ts @@ -1,4 +1,4 @@ -import { test, expect, vi } from "vitest"; +import { test, expect, vi } from "vite-plus/test"; import { collectAnthropicSession } from "./anthropic-session-collector"; import { wrapAnthropic } from "./anthropic"; diff --git a/js/src/wrappers/anthropic-session-collector.ts b/js/src/wrappers/anthropic-session-collector.ts index 849b0e8d4..b8f38f010 100644 --- a/js/src/wrappers/anthropic-session-collector.ts +++ b/js/src/wrappers/anthropic-session-collector.ts @@ -1,3 +1,4 @@ +import { registerInstrumentation } from "../instrumentation/registrations/anthropic"; import { debugLogger } from "../debug-logger"; const sessionStreamCollectors = new WeakMap void>(); @@ -21,6 +22,7 @@ export function registerAnthropicSessionStreamCollector( * @returns The same stream object. */ export function collectAnthropicSession(stream: T): T { + registerInstrumentation(); const collector = sessionStreamCollectors.get(stream); if (!collector) { return stream; diff --git a/js/src/wrappers/anthropic-tokens-util.test.ts b/js/src/wrappers/anthropic-tokens-util.test.ts index b7f1fcc55..9b71f7ed2 100644 --- a/js/src/wrappers/anthropic-tokens-util.test.ts +++ b/js/src/wrappers/anthropic-tokens-util.test.ts @@ -1,4 +1,4 @@ -import { describe, expect, it } from "vitest"; +import { describe, expect, it } from "vite-plus/test"; import { finalizeAnthropicTokens, toNumericMetrics, diff --git a/js/src/wrappers/anthropic.ts b/js/src/wrappers/anthropic.ts index e98f9b12f..85c2c7a46 100644 --- a/js/src/wrappers/anthropic.ts +++ b/js/src/wrappers/anthropic.ts @@ -1,5 +1,6 @@ +import { registerInstrumentation } from "../instrumentation/registrations/anthropic"; import { debugLogger } from "../debug-logger"; -import { anthropicChannels } from "../instrumentation/plugins/anthropic-interceptors"; +import { anthropicChannels } from "../instrumentation/providers/anthropic-interceptors"; import { TypedApplyProxy } from "../typed-instrumentation-helpers"; import type { AnthropicBeta, @@ -27,6 +28,7 @@ import type { * @returns The wrapped `Anthropic` object. */ export function wrapAnthropic(anthropic: T): T { + registerInstrumentation(); const au: unknown = anthropic; if ( au && diff --git a/js/src/wrappers/attachment-utils.test.ts b/js/src/wrappers/attachment-utils.test.ts index 9b88d8a4c..6f8fa82d5 100644 --- a/js/src/wrappers/attachment-utils.test.ts +++ b/js/src/wrappers/attachment-utils.test.ts @@ -1,10 +1,9 @@ -import { afterEach, describe, expect, it, vi } from "vitest"; -import iso from "../isomorph"; -import { Attachment, BaseAttachment, _exportsForTestingOnly } from "../logger"; -import { - isAutoCaptureAttachmentsEnabled, - processInputAttachments, -} from "./attachment-utils"; +import { afterEach, describe, expect, it, vi } from "vite-plus/test"; +import iso from "#braintrust-platform"; +import { Attachment, BaseAttachment } from "../logger/core"; +import { _exportsForTestingOnly } from "../logger/testing"; +import { processInputAttachments } from "./attachment-utils"; +import { _internalCaptureAttachmentsEnabled } from "../logger/core"; const originalGetEnv = iso.getEnv; @@ -13,11 +12,11 @@ afterEach(() => { vi.unstubAllGlobals(); }); -describe("isAutoCaptureAttachmentsEnabled", () => { +describe("_internalCaptureAttachmentsEnabled", () => { it.each(["1", "true", " TRUE "])('accepts "%s"', (value) => { iso.getEnv = () => value; - expect(isAutoCaptureAttachmentsEnabled()).toBe(true); + expect(_internalCaptureAttachmentsEnabled()).toBe(true); }); it.each([undefined, "0", "false", "yes", "on", "unexpected"])( @@ -25,7 +24,7 @@ describe("isAutoCaptureAttachmentsEnabled", () => { (value) => { iso.getEnv = () => value; - expect(isAutoCaptureAttachmentsEnabled()).toBe(false); + expect(_internalCaptureAttachmentsEnabled()).toBe(false); }, ); }); @@ -52,7 +51,7 @@ it("omits inline attachment data while preserving remote references", () => { expect(processInputAttachments(input, false)).toEqual([ { type: "image_url", - image_url: { detail: "low", url: "" }, + image_url: { detail: "low" }, }, { type: "image_url", @@ -60,11 +59,88 @@ it("omits inline attachment data while preserving remote references", () => { }, { type: "file", - file: { filename: "document.pdf", file_data: "" }, + file: { filename: "document.pdf" }, }, ]); }); +it("skips data URL parsing for disabled media across supported input formats", () => { + const data = "data:application/octet-stream;base64,AQID"; + const input = [ + { type: "image_url", image_url: { url: data } }, + { type: "image_base64", imageBase64: data }, + { type: "video_base64", video_base64: data }, + { type: "file", file: { file_data: data } }, + { type: "image", image: data }, + { type: "file", data, mediaType: "application/pdf" }, + ]; + const parse = vi.spyOn(String.prototype, "match"); + let output; + let parseCalls; + try { + output = processInputAttachments(input, false); + parseCalls = parse.mock.calls.length; + } finally { + parse.mockRestore(); + } + expect(parseCalls).toBe(0); + expect(output).toEqual([{ type: "file", mediaType: "application/pdf" }]); + expect(JSON.stringify(output)).not.toContain("AQID"); + expect(input[0].image_url?.url).toBe(data); +}); + +it.each([false, true])( + "applies capture=%s to data URL objects while preserving remote URLs", + (captureAttachments) => { + const data = new URL("data:image/png;base64,AQID"); + const remote = new URL("https://example.com/image.png"); + const input = [ + data, + { type: "image", image: data }, + { type: "file", data, mediaType: "image/png" }, + { type: "image_url", image_url: { url: data, detail: "low" } }, + { type: "file", file: { file_data: data, filename: "image.png" } }, + remote, + { type: "image", image: remote }, + { type: "file", data: remote, mediaType: "image/png" }, + ]; + const decode = vi.spyOn(globalThis, "atob"); + try { + const output = processInputAttachments(input, captureAttachments); + if (captureAttachments) { + expect(output.slice(0, 5)).toEqual([ + expect.any(Attachment), + { type: "image", image: expect.any(Attachment) }, + { + type: "file", + data: expect.any(Attachment), + mediaType: "image/png", + }, + { + type: "image_url", + image_url: { url: expect.any(Attachment), detail: "low" }, + }, + { + type: "file", + file: { file_data: expect.any(Attachment), filename: "image.png" }, + }, + ]); + } else { + expect(decode).not.toHaveBeenCalled(); + expect(output.slice(0, -3)).toEqual([ + { type: "file", mediaType: "image/png" }, + { type: "image_url", image_url: { detail: "low" } }, + { type: "file", file: { filename: "image.png" } }, + ]); + } + expect(output.slice(-3)).toEqual(input.slice(-3)); + expect(JSON.stringify(output)).not.toContain("AQID"); + expect(data.href).toBe("data:image/png;base64,AQID"); + } finally { + decode.mockRestore(); + } + }, +); it("captures OpenAI input images only when enabled and preserves their detail", () => { const input = [ { @@ -81,7 +157,6 @@ it("captures OpenAI input images only when enabled and preserves their detail", ]; expect(processInputAttachments(input, false)[0].content[0]).toEqual({ type: "input_image", - image_url: "", detail: "low", }); expect(processInputAttachments(input, true)[0].content[0]).toEqual({ diff --git a/js/src/wrappers/attachment-utils.ts b/js/src/wrappers/attachment-utils.ts index 4282736ab..7d3421938 100644 --- a/js/src/wrappers/attachment-utils.ts +++ b/js/src/wrappers/attachment-utils.ts @@ -1,11 +1,35 @@ -import { Attachment } from "../logger"; -import iso from "../isomorph"; -import { automaticAttachmentOriginals } from "../automatic-attachments"; +import { + Attachment, + BaseAttachment, + _internalCaptureAttachmentsEnabled, +} from "../logger/core"; -const CAPTURE_ATTACHMENTS_ENV_VAR = "BRAINTRUST_CAPTURE_ATTACHMENTS"; +/** Remove media fields without reading their values, dropping type-only blocks. */ +export function omitMediaData( + value: object, + ...fields: string[] +): Record | undefined { + const result: Record = {}; + let hasContent = false; + for (const key of Object.keys(value)) { + if (fields.includes(key)) continue; + const item: unknown = Reflect.get(value, key); + if (item === undefined) continue; + Object.defineProperty(result, key, { + value: item, + enumerable: true, + configurable: true, + writable: true, + }); + if (key !== "type") hasContent = true; + } + return hasContent ? result : undefined; +} +import iso from "#braintrust-platform"; +import { automaticAttachmentOriginals } from "../automatic-attachments"; export function isAutoCaptureAttachmentsEnabled(): boolean { - return isEnvFlagEnabled(CAPTURE_ATTACHMENTS_ENV_VAR); + return _internalCaptureAttachmentsEnabled(); } /** Whether a capture environment variable is set to `1` or `true`. */ @@ -33,6 +57,8 @@ export function getExtensionFromMediaType(mediaType: string): string { "audio/wav": "wav", "audio/webm": "webm", "audio/ogg": "ogg", + "audio/aac": "aac", + "audio/pcm": "pcm", "video/mp4": "mp4", "video/webm": "webm", "application/pdf": "pdf", @@ -50,6 +76,10 @@ export function getExtensionFromMediaType(mediaType: string): string { */ export function convertDataToBlob(data: any, mediaType: string): Blob | null { try { + if (data instanceof URL) { + if (data.protocol !== "data:") return null; + data = data.href; + } if (typeof data === "string") { // Could be base64, data URL, or regular URL if (data.startsWith("data:")) { @@ -92,11 +122,12 @@ export function convertDataToBlob(data: any, mediaType: string): Blob | null { /** * Process input to extract and convert image/file content parts to Attachments - * Similar to processImagesInOutput in oai_responses.ts - replaces data in-place + * Only pass provider content containers, never arbitrary tool arguments or JSON results. */ export function processInputAttachments( input: any, - captureAttachments = true, + captureAttachments = _internalCaptureAttachmentsEnabled(), + audioOutputFormat?: string, ): any { if (!input) { return input; @@ -132,7 +163,9 @@ export function processInputAttachments( const processNode = (node: any, depth = 0): any => { if (depth >= 128) return captureAttachments ? node : ""; if (Array.isArray(node)) { - return node.map((item) => processNode(item, depth + 1)); + return node + .map((item) => processNode(item, depth + 1)) + .filter((item) => item !== undefined); } if (!node || typeof node !== "object") { @@ -148,32 +181,31 @@ export function processInputAttachments( ) return node; - // OpenAI chat image_url content format - if ( - node.type === "image_url" && - node.image_url && - typeof node.image_url === "object" && - typeof node.image_url.url === "string" && - node.image_url.url.startsWith("data:") - ) { + if (node instanceof BaseAttachment || node instanceof Date) return node; + // Tool arguments and JSON results are application data, not content parts. + if (node.type === "tool-call") return node; + if (node.type === "tool-result") { + return node.output?.type === "content" && Array.isArray(node.output.value) + ? { + ...node, + output: { ...node.output, value: processNode(node.output.value) }, + } + : node; + } + if (node instanceof URL) { + if (node.protocol !== "data:") return node; + if (!captureAttachments) return undefined; const mediaType = inferMediaTypeFromDataUrl( - node.image_url.url, - "image/png", + node.href, + "application/octet-stream", + ); + return ( + toAttachment( + node, + mediaType, + `file.${getExtensionFromMediaType(mediaType)}`, + ) ?? node ); - const filename = `image.${getExtensionFromMediaType(mediaType)}`; - const attachment = captureAttachments - ? toAttachment(node.image_url.url, mediaType, filename) - : ""; - - if (attachment) { - return { - ...node, - image_url: { - ...node.image_url, - url: attachment, - }, - }; - } } // OpenAI Responses and Decisions image content format. @@ -182,7 +214,7 @@ export function processInputAttachments( typeof node.image_url === "string" && node.image_url.startsWith("data:") ) { - if (!captureAttachments) return { ...node, image_url: "" }; + if (!captureAttachments) return omitMediaData(node, "image_url"); // Decisions accepts inline images only. Do not guess a MIME type or // reinterpret an unsupported format as PNG. const mediaType = node.image_url @@ -206,6 +238,135 @@ export function processInputAttachments( return { ...node, image_url: attachment }; } + // Responses API and Agents SDK image/file content parts. + if (node.type === "input_image" || node.type === "input_file") { + const field = + node.type === "input_file" + ? "file_data" in node + ? "file_data" + : "file" + : "image_url" in node + ? "image_url" + : "image"; + const data = node[field]; + if (data instanceof BaseAttachment) return node; + if (typeof data === "string" || data instanceof URL) { + if ( + (data instanceof URL && data.protocol !== "data:") || + /^https?:/i.test(String(data)) + ) + return node; + if (!captureAttachments) return omitMediaData(node, field); + const mediaType = inferMediaTypeFromDataUrl( + String(data), + node.type === "input_image" + ? "image/png" + : "application/octet-stream", + ); + const filename = + node.filename || `file.${getExtensionFromMediaType(mediaType)}`; + const attachment = toAttachment(data, mediaType, filename); + if (attachment) return { ...node, [field]: attachment }; + } + } + + if (node.type === "audio" && typeof node.audio === "string") { + if (!captureAttachments) return omitMediaData(node, "audio"); + const format = node.format || "wav"; + const attachment = toAttachment( + node.audio, + format === "mp3" ? "audio/mpeg" : `audio/${format}`, + `audio.${format}`, + ); + if (attachment) return { ...node, audio: attachment }; + } + + const audioField = node.type === "input_audio" ? "input_audio" : "audio"; + if ( + (node.type === "input_audio" || node.role === "assistant") && + node[audioField] && + typeof node[audioField] === "object" + ) { + const audio = node[audioField]; + if (audio.data instanceof BaseAttachment) return node; + if (!captureAttachments) + return omitMediaData({ + ...node, + [audioField]: omitMediaData(audio, "data"), + }); + const format = + audio.format || + (node.role === "assistant" ? audioOutputFormat : undefined) || + "wav"; + const mediaType = + format === "mp3" + ? "audio/mpeg" + : format === "opus" + ? "audio/ogg" + : format === "pcm16" + ? "audio/pcm" + : `audio/${format}`; + const attachment = toAttachment( + audio.data, + mediaType, + `audio.${getExtensionFromMediaType(mediaType)}`, + ); + if (attachment) + return { ...node, [audioField]: { ...audio, data: attachment } }; + } + + // AI SDK generateText/streamText file parts wrap a GeneratedFile instance. + if ( + node.type === "file" && + node.file && + typeof node.file === "object" && + ("base64" in node.file || "uint8Array" in node.file) + ) { + if (!captureAttachments) return omitMediaData(node, "file"); + const mediaType = node.file.mediaType || "application/octet-stream"; + const attachment = toAttachment( + node.file.base64 ?? node.file.uint8Array, + mediaType, + `file.${getExtensionFromMediaType(mediaType)}`, + ); + if (attachment) return { ...node, file: attachment }; + } + + // OpenAI chat image_url content format + if ( + node.type === "image_url" && + node.image_url && + typeof node.image_url === "object" && + ((typeof node.image_url.url === "string" && + node.image_url.url.startsWith("data:")) || + (node.image_url.url instanceof URL && + node.image_url.url.protocol === "data:")) + ) { + if (!captureAttachments) + return omitMediaData({ + ...node, + image_url: omitMediaData(node.image_url, "url"), + }); + const mediaType = inferMediaTypeFromDataUrl( + String(node.image_url.url), + "image/png", + ); + const filename = `image.${getExtensionFromMediaType(mediaType)}`; + const attachment = captureAttachments + ? toAttachment(node.image_url.url, mediaType, filename) + : ""; + + if (attachment) { + return { + ...node, + image_url: { + ...node.image_url, + url: attachment, + }, + }; + } + } + // Voyage AI multimodal image_base64/video_base64 content format const voyageBase64Key = node.type === "image_base64" @@ -225,6 +386,7 @@ export function processInputAttachments( typeof voyageBase64Value === "string" && voyageBase64Value.startsWith("data:") ) { + if (!captureAttachments) return omitMediaData(node, voyageBase64Key); const mediaType = inferMediaTypeFromDataUrl( voyageBase64Value, node.type === "video_base64" ? "video/mp4" : "image/png", @@ -247,11 +409,18 @@ export function processInputAttachments( node.type === "file" && node.file && typeof node.file === "object" && - typeof node.file.file_data === "string" && - node.file.file_data.startsWith("data:") + ((typeof node.file.file_data === "string" && + !/^https?:/i.test(node.file.file_data)) || + (node.file.file_data instanceof URL && + node.file.file_data.protocol === "data:")) ) { + if (!captureAttachments) + return omitMediaData({ + ...node, + file: omitMediaData(node.file, "file_data"), + }); const mediaType = inferMediaTypeFromDataUrl( - node.file.file_data, + String(node.file.file_data), "application/octet-stream", ); const filename = @@ -275,9 +444,20 @@ export function processInputAttachments( // AI SDK image content format if (node.type === "image" && node.image) { + if (node.image instanceof BaseAttachment) { + attachmentIndex++; + return node; + } + if (!captureAttachments) { + return (node.image instanceof URL && node.image.protocol !== "data:") || + (typeof node.image === "string" && /^https?:/i.test(node.image)) + ? node + : omitMediaData(node, "image"); + } let mediaType = "image/png"; - if (typeof node.image === "string" && node.image.startsWith("data:")) { - mediaType = inferMediaTypeFromDataUrl(node.image, mediaType); + const image = node.image instanceof URL ? node.image.href : node.image; + if (typeof image === "string" && image.startsWith("data:")) { + mediaType = inferMediaTypeFromDataUrl(image, mediaType); } else if (node.mediaType) { mediaType = node.mediaType; } @@ -299,8 +479,21 @@ export function processInputAttachments( } } - // AI SDK file content format - if (node.type === "file" && node.data) { + // AI SDK file parts and inline media in tool-result content. + if ( + ["file", "image-data", "file-data", "media"].includes(node.type) && + node.data + ) { + if (node.data instanceof BaseAttachment) { + attachmentIndex++; + return node; + } + if (!captureAttachments) { + return (node.data instanceof URL && node.data.protocol !== "data:") || + (typeof node.data === "string" && /^https?:/i.test(node.data)) + ? node + : omitMediaData(node, "data"); + } const mediaType = node.mediaType || "application/octet-stream"; const filename = node.filename || @@ -321,12 +514,12 @@ export function processInputAttachments( } } - return Object.fromEntries( - Object.entries(node).map(([key, value]) => [ - key, - processNode(value, depth + 1), - ]), - ); + const processed: Record = {}; + for (const [key, value] of Object.entries(node)) { + const result = processNode(value, depth + 1); + if (result !== undefined) processed[key] = result; + } + return processed; }; try { diff --git a/js/src/wrappers/bedrock-runtime.test.ts b/js/src/wrappers/bedrock-runtime.test.ts index 4da0d27c5..3774be7a0 100644 --- a/js/src/wrappers/bedrock-runtime.test.ts +++ b/js/src/wrappers/bedrock-runtime.test.ts @@ -7,9 +7,10 @@ import { expect, test, vi, -} from "vitest"; +} from "vite-plus/test"; import { configureNode } from "../node/config"; -import { _exportsForTestingOnly, initLogger } from "../logger"; +import { _exportsForTestingOnly } from "../logger/testing"; +import { initLogger } from "../logger/core"; import { wrapBedrockRuntime } from "./bedrock-runtime"; try { diff --git a/js/src/wrappers/bedrock-runtime.ts b/js/src/wrappers/bedrock-runtime.ts index 3919553cc..c8accc2b7 100644 --- a/js/src/wrappers/bedrock-runtime.ts +++ b/js/src/wrappers/bedrock-runtime.ts @@ -1,10 +1,11 @@ +import { registerInstrumentation } from "../instrumentation/registrations/awsBedrockRuntime"; import { debugLogger } from "../debug-logger"; import { runWithAutoInstrumentationSuppressed } from "../instrumentation/auto-instrumentation-suppression"; -import { bedrockRuntimeChannels } from "../instrumentation/plugins/bedrock-runtime-interceptors"; +import { bedrockRuntimeChannels } from "../instrumentation/providers/bedrock-runtime-interceptors"; import { buildBedrockRuntimeSpanInfo, getBedrockRuntimeOperation, -} from "../instrumentation/plugins/bedrock-runtime-common"; +} from "../instrumentation/providers/bedrock-runtime-common"; import type { BedrockRuntimeClient, BedrockRuntimeCommandLike, @@ -14,6 +15,7 @@ import type { * Wrap an AWS Bedrock Runtime client with Braintrust tracing. */ export function wrapBedrockRuntime(client: T): T { + registerInstrumentation(); if (isSupportedBedrockRuntimeClient(client)) { return bedrockRuntimeProxy(client) as T; } diff --git a/js/src/wrappers/claude-agent-sdk/claude-agent-sdk.ts b/js/src/wrappers/claude-agent-sdk/claude-agent-sdk.ts index 4ed523dd3..44d1d2e66 100644 --- a/js/src/wrappers/claude-agent-sdk/claude-agent-sdk.ts +++ b/js/src/wrappers/claude-agent-sdk/claude-agent-sdk.ts @@ -1,7 +1,8 @@ +import { registerInstrumentation } from "../../instrumentation/registrations/claudeAgentSDK"; import { debugLogger } from "../../debug-logger"; -import { claudeAgentSDKChannels } from "../../instrumentation/plugins/claude-agent-sdk-interceptors"; -import { CLAUDE_AGENT_SDK_SKIP_LOCAL_TOOL_HOOKS_OPTION } from "../../instrumentation/plugins/claude-agent-sdk-instrumentation-constants"; -import { wrapLocalClaudeToolHandler } from "../../instrumentation/plugins/claude-agent-sdk-local-tool-spans"; +import { claudeAgentSDKChannels } from "../../instrumentation/providers/claude-agent-sdk-interceptors"; +import { CLAUDE_AGENT_SDK_SKIP_LOCAL_TOOL_HOOKS_OPTION } from "../../instrumentation/providers/claude-agent-sdk-instrumentation-constants"; +import { wrapLocalClaudeToolHandler } from "../../instrumentation/providers/claude-agent-sdk-local-tool-spans"; import type { ClaudeAgentSDKModule, ClaudeAgentSDKQueryParams, @@ -21,6 +22,7 @@ type LocalToolMetadata = { * @returns Object with wrapped query, tool, and createSdkMcpServer functions */ export function wrapClaudeAgentSDK(sdk: T): T { + registerInstrumentation(); const s: unknown = sdk; if ( s && diff --git a/js/src/wrappers/cloudflare-agent.test.ts b/js/src/wrappers/cloudflare-agent.test.ts index 62458211d..550067cdc 100644 --- a/js/src/wrappers/cloudflare-agent.test.ts +++ b/js/src/wrappers/cloudflare-agent.test.ts @@ -1,4 +1,4 @@ -import { afterEach, describe, expect, it, vi } from "vitest"; +import { afterEach, describe, expect, it, vi } from "vite-plus/test"; const { invoke } = vi.hoisted(() => ({ invoke: vi.fn( diff --git a/js/src/wrappers/cloudflare-agent.ts b/js/src/wrappers/cloudflare-agent.ts index 4930a5185..eff5d53f2 100644 --- a/js/src/wrappers/cloudflare-agent.ts +++ b/js/src/wrappers/cloudflare-agent.ts @@ -1,5 +1,6 @@ +import { registerInstrumentation } from "../instrumentation/registrations/cloudflareAgents"; import { debugLogger } from "../debug-logger"; -import { cloudflareAgentsChannels } from "../instrumentation/plugins/cloudflare-agents-interceptors"; +import { cloudflareAgentsChannels } from "../instrumentation/providers/cloudflare-agents-interceptors"; import type { CloudflareAgent } from "../vendor-sdk-types/cloudflare-agents"; const WRAPPED_RUN_AGENT_TOOL = Symbol.for( @@ -13,6 +14,7 @@ const WRAPPED_RUN_AGENT_TOOL = Symbol.for( * Object exports and inheritance continue to work unchanged. */ export function wrapCloudflareAgent(Agent: T): T { + registerInstrumentation(); if (typeof Agent !== "function") { warnUnsupportedAgent(); return Agent; diff --git a/js/src/wrappers/cloudflare-ai-chat.test.ts b/js/src/wrappers/cloudflare-ai-chat.test.ts index bab75e0cc..30f7c1719 100644 --- a/js/src/wrappers/cloudflare-ai-chat.test.ts +++ b/js/src/wrappers/cloudflare-ai-chat.test.ts @@ -1,4 +1,4 @@ -import { afterEach, describe, expect, it, vi } from "vitest"; +import { afterEach, describe, expect, it, vi } from "vite-plus/test"; const { invoke } = vi.hoisted(() => ({ invoke: vi.fn( diff --git a/js/src/wrappers/cloudflare-ai-chat.ts b/js/src/wrappers/cloudflare-ai-chat.ts index 4023bbb49..b3b722c76 100644 --- a/js/src/wrappers/cloudflare-ai-chat.ts +++ b/js/src/wrappers/cloudflare-ai-chat.ts @@ -1,5 +1,6 @@ +import { registerInstrumentation } from "../instrumentation/registrations/cloudflareAIChat"; import { debugLogger } from "../debug-logger"; -import { instrumentCloudflareAIChatAgent } from "../instrumentation/plugins/cloudflare-ai-chat-instrumentation"; +import { instrumentCloudflareAIChatAgent } from "../instrumentation/providers/cloudflare-ai-chat-instrumentation"; import type { CloudflareAIChatAgent, CloudflareAIChatAgentConstructor, @@ -16,6 +17,7 @@ const wrappedClasses = new WeakMap(); * diagnostic-channel events as automatic instrumentation. */ export function wrapCloudflareAIChat(module: T): T { + registerInstrumentation(); if (!module || typeof module !== "object") { return module; } diff --git a/js/src/wrappers/cloudflare-think.test.ts b/js/src/wrappers/cloudflare-think.test.ts index 8bbfca976..c4de07c9c 100644 --- a/js/src/wrappers/cloudflare-think.test.ts +++ b/js/src/wrappers/cloudflare-think.test.ts @@ -1,4 +1,4 @@ -import { afterEach, describe, expect, it, vi } from "vitest"; +import { afterEach, describe, expect, it, vi } from "vite-plus/test"; const { invoke } = vi.hoisted(() => ({ invoke: vi.fn( diff --git a/js/src/wrappers/cloudflare-think.ts b/js/src/wrappers/cloudflare-think.ts index bda6dd5f7..618439536 100644 --- a/js/src/wrappers/cloudflare-think.ts +++ b/js/src/wrappers/cloudflare-think.ts @@ -1,4 +1,5 @@ -import { cloudflareThinkChannels } from "../instrumentation/plugins/cloudflare-think-interceptors"; +import { registerInstrumentation } from "../instrumentation/registrations/cloudflareThink"; +import { cloudflareThinkChannels } from "../instrumentation/providers/cloudflare-think-interceptors"; import type { CloudflareThinkConstructor, CloudflareThinkInstance, @@ -20,6 +21,7 @@ const WRAPPED_THINK = Symbol.for("braintrust.cloudflare-think.wrapped"); * ``` */ export function wrapCloudflareThink(sdk: T): T { + registerInstrumentation(); if (!sdk || typeof sdk !== "object") { return sdk; } diff --git a/js/src/wrappers/cohere.test.ts b/js/src/wrappers/cohere.test.ts index 9f580d166..0c20d0186 100644 --- a/js/src/wrappers/cohere.test.ts +++ b/js/src/wrappers/cohere.test.ts @@ -7,9 +7,10 @@ import { expect, test, vi, -} from "vitest"; +} from "vite-plus/test"; import { configureNode } from "../node/config"; -import { _exportsForTestingOnly, initLogger } from "../logger"; +import { _exportsForTestingOnly } from "../logger/testing"; +import { initLogger } from "../logger/core"; import { wrapCohere } from "./cohere"; try { diff --git a/js/src/wrappers/cohere.ts b/js/src/wrappers/cohere.ts index a91a8b094..a0120c315 100644 --- a/js/src/wrappers/cohere.ts +++ b/js/src/wrappers/cohere.ts @@ -1,5 +1,6 @@ +import { registerInstrumentation } from "../instrumentation/registrations/cohere"; import { debugLogger } from "../debug-logger"; -import { cohereChannels } from "../instrumentation/plugins/cohere-interceptors"; +import { cohereChannels } from "../instrumentation/providers/cohere-interceptors"; import type { CohereChatRequest, CohereChatResponse, @@ -16,6 +17,7 @@ import type { * Braintrust plugins can consume. */ export function wrapCohere(cohere: T): T { + registerInstrumentation(); if (isSupportedCohereClient(cohere)) { return cohereProxy(cohere) as T; } diff --git a/js/src/wrappers/cursor-sdk.test.ts b/js/src/wrappers/cursor-sdk.test.ts index b260e4a1d..528cae0bc 100644 --- a/js/src/wrappers/cursor-sdk.test.ts +++ b/js/src/wrappers/cursor-sdk.test.ts @@ -1,5 +1,5 @@ import { debugLogger } from "../debug-logger"; -import { afterEach, describe, expect, it, vi } from "vitest"; +import { afterEach, describe, expect, it, vi } from "vite-plus/test"; const { invoke } = vi.hoisted(() => ({ invoke: vi.fn( diff --git a/js/src/wrappers/cursor-sdk.ts b/js/src/wrappers/cursor-sdk.ts index a2ec37d60..3b2c7e232 100644 --- a/js/src/wrappers/cursor-sdk.ts +++ b/js/src/wrappers/cursor-sdk.ts @@ -1,5 +1,6 @@ +import { registerInstrumentation } from "../instrumentation/registrations/cursorSDK"; import { debugLogger } from "../debug-logger"; -import { cursorSDKChannels } from "../instrumentation/plugins/cursor-sdk-interceptors"; +import { cursorSDKChannels } from "../instrumentation/providers/cursor-sdk-interceptors"; import type { CursorSDKAgent, CursorSDKAgentClass, @@ -16,6 +17,7 @@ const WRAPPED_AGENT = Symbol.for("braintrust.cursor-sdk.wrapped-agent"); * invocation hooks; the Cursor SDK plugin owns span lifecycle. */ export function wrapCursorSDK(sdk: T): T { + registerInstrumentation(); if (!sdk || typeof sdk !== "object") { return sdk; } diff --git a/js/src/wrappers/elevenlabs.ts b/js/src/wrappers/elevenlabs.ts index b106e3422..042f0454c 100644 --- a/js/src/wrappers/elevenlabs.ts +++ b/js/src/wrappers/elevenlabs.ts @@ -1,6 +1,7 @@ +import { registerInstrumentation } from "../instrumentation/registrations/elevenlabs"; import { isObject } from "../../util"; import { debugLogger } from "../debug-logger"; -import { elevenLabsChannels } from "../instrumentation/plugins/elevenlabs-interceptors"; +import { elevenLabsChannels } from "../instrumentation/providers/elevenlabs-interceptors"; import type { ElevenLabsClient, ElevenLabsSpeechArgs, @@ -25,6 +26,7 @@ const clients = new WeakMap(); * */ export function wrapElevenLabs(client: T): T { + registerInstrumentation(); if ( !isObject(client) || !isObject(client.textToSpeech) || diff --git a/js/src/wrappers/exports.test.ts b/js/src/wrappers/exports.test.ts deleted file mode 100644 index 66850a3b3..000000000 --- a/js/src/wrappers/exports.test.ts +++ /dev/null @@ -1,38 +0,0 @@ -import { describe, it, expect } from "vitest"; -import { BraintrustMiddleware } from "./ai-sdk"; - -describe("ai-sdk exports", () => { - it("should always export BraintrustMiddleware as a function", () => { - expect(typeof BraintrustMiddleware).toBe("function"); - }); - - it("BraintrustMiddleware should return an object with wrapGenerate and wrapStream", () => { - const result = BraintrustMiddleware({}); - expect(result).toHaveProperty("wrapGenerate"); - expect(result).toHaveProperty("wrapStream"); - expect(typeof result.wrapGenerate).toBe("function"); - expect(typeof result.wrapStream).toBe("function"); - }); - - it("should handle conditional imports gracefully", () => { - // Test that imports don't throw errors regardless of AI SDK version - expect(() => { - const middleware = BraintrustMiddleware({ debug: true }); - - // Should be able to call the functions without errors - const { wrapGenerate, wrapStream } = middleware; - - expect(wrapGenerate).toBeDefined(); - expect(wrapStream).toBeDefined(); - }).not.toThrow(); - }); - - it("should export middleware functions that can be instantiated", () => { - const middleware = BraintrustMiddleware({}); - const { wrapGenerate, wrapStream } = middleware; - - // Should be functions that can be called (we don't test actual execution due to logger dependencies) - expect(typeof wrapGenerate).toBe("function"); - expect(typeof wrapStream).toBe("function"); - }); -}); diff --git a/js/src/wrappers/genkit.test.ts b/js/src/wrappers/genkit.test.ts index e471d3d93..afa3746aa 100644 --- a/js/src/wrappers/genkit.test.ts +++ b/js/src/wrappers/genkit.test.ts @@ -1,7 +1,7 @@ -import { afterEach, describe, expect, it, vi } from "vitest"; +import { afterEach, describe, expect, it, vi } from "vite-plus/test"; import type { ChannelMessage } from "../instrumentation/core/tracing-types"; -import { genkitChannels } from "../instrumentation/plugins/genkit-interceptors"; +import { genkitChannels } from "../instrumentation/providers/genkit-interceptors"; import { configureNode } from "../node/config"; import type { GenkitAction, diff --git a/js/src/wrappers/genkit.ts b/js/src/wrappers/genkit.ts index 47cbcd384..f471820da 100644 --- a/js/src/wrappers/genkit.ts +++ b/js/src/wrappers/genkit.ts @@ -1,5 +1,6 @@ +import { registerInstrumentation } from "../instrumentation/registrations/genkit"; import { debugLogger } from "../debug-logger"; -import { genkitChannels } from "../instrumentation/plugins/genkit-interceptors"; +import { genkitChannels } from "../instrumentation/providers/genkit-interceptors"; import type { GenkitAction, GenkitEmbedManyParams, @@ -29,6 +30,7 @@ const wrappedGenkitActions = new WeakMap(); * - wrapping the module namespace and then calling its `genkit` factory */ export function wrapGenkit(genkit: T): T { + registerInstrumentation(); if (isGenkitInstance(genkit)) { return wrapGenkitInstance(genkit) as T; } diff --git a/js/src/wrappers/github-copilot.ts b/js/src/wrappers/github-copilot.ts index 4faf0fd43..7db6c7b7d 100644 --- a/js/src/wrappers/github-copilot.ts +++ b/js/src/wrappers/github-copilot.ts @@ -1,5 +1,6 @@ +import { registerInstrumentation } from "../instrumentation/registrations/gitHubCopilot"; import { debugLogger } from "../debug-logger"; -import { gitHubCopilotChannels } from "../instrumentation/plugins/github-copilot-interceptors"; +import { gitHubCopilotChannels } from "../instrumentation/providers/github-copilot-interceptors"; import type { GitHubCopilotClient, GitHubCopilotResumeSessionConfig, @@ -37,6 +38,7 @@ function isGitHubCopilotClient(value: unknown): value is GitHubCopilotClient { * ``` */ export function wrapCopilotClient(client: T): T { + registerInstrumentation(); if (!isGitHubCopilotClient(client)) { debugLogger.notice.warn( "wrapCopilotClient: argument does not look like a CopilotClient. Not wrapping.", diff --git a/js/src/wrappers/google-adk.test.ts b/js/src/wrappers/google-adk.test.ts index 67de8a16b..28b982d63 100644 --- a/js/src/wrappers/google-adk.test.ts +++ b/js/src/wrappers/google-adk.test.ts @@ -1,5 +1,5 @@ import { debugLogger } from "../debug-logger"; -import { describe, it, expect, vi, afterEach } from "vitest"; +import { describe, it, expect, vi, afterEach } from "vite-plus/test"; const { invoke } = vi.hoisted(() => ({ invoke: vi.fn( diff --git a/js/src/wrappers/google-adk.ts b/js/src/wrappers/google-adk.ts index 875ce4ca4..98dcca984 100644 --- a/js/src/wrappers/google-adk.ts +++ b/js/src/wrappers/google-adk.ts @@ -1,5 +1,6 @@ +import { registerInstrumentation } from "../instrumentation/registrations/googleADK"; import { debugLogger } from "../debug-logger"; -import { googleADKChannels } from "../instrumentation/plugins/google-adk-interceptors"; +import { googleADKChannels } from "../instrumentation/providers/google-adk-interceptors"; import type { GoogleADKBaseAgent, GoogleADKBaseTool, @@ -41,6 +42,7 @@ import type { * ``` */ export function wrapGoogleADK>(adkModule: T): T { + registerInstrumentation(); if (!adkModule || typeof adkModule !== "object") { debugLogger.notice.warn("Invalid Google ADK module. Not wrapping."); return adkModule; diff --git a/js/src/wrappers/google-genai.test.ts b/js/src/wrappers/google-genai.test.ts index 64964bef7..c039d530f 100644 --- a/js/src/wrappers/google-genai.test.ts +++ b/js/src/wrappers/google-genai.test.ts @@ -1,4 +1,4 @@ -import { afterEach, describe, expect, it, vi } from "vitest"; +import { afterEach, describe, expect, it, vi } from "vite-plus/test"; const { invoke } = vi.hoisted(() => ({ invoke: vi.fn( diff --git a/js/src/wrappers/google-genai.ts b/js/src/wrappers/google-genai.ts index f9ed4917c..3406359e7 100644 --- a/js/src/wrappers/google-genai.ts +++ b/js/src/wrappers/google-genai.ts @@ -1,5 +1,6 @@ +import { registerInstrumentation } from "../instrumentation/registrations/googleGenAI"; import { debugLogger } from "../debug-logger"; -import { googleGenAIChannels } from "../instrumentation/plugins/google-genai-interceptors"; +import { googleGenAIChannels } from "../instrumentation/providers/google-genai-interceptors"; import { isObject } from "../util"; import type { GoogleGenAIClient, @@ -37,6 +38,7 @@ const patchedHttpResponses = new WeakSet(); export function wrapGoogleGenAI>( googleGenAI: T, ): T { + registerInstrumentation(); if (!googleGenAI || typeof googleGenAI !== "object") { debugLogger.notice.warn("Invalid Google GenAI module. Not wrapping."); return googleGenAI; diff --git a/js/src/wrappers/google-generative-ai.ts b/js/src/wrappers/google-generative-ai.ts index a478d2d51..8ad2ad47d 100644 --- a/js/src/wrappers/google-generative-ai.ts +++ b/js/src/wrappers/google-generative-ai.ts @@ -1,4 +1,5 @@ -import { googleGenerativeAIChannels } from "../instrumentation/plugins/google-generative-ai-interceptors"; +import { registerInstrumentation } from "../instrumentation/registrations/googleGenerativeAI"; +import { googleGenerativeAIChannels } from "../instrumentation/providers/google-generative-ai-interceptors"; import { isObject } from "../util"; import type { GenerativeAIClient, @@ -8,6 +9,7 @@ import type { /** Trace @google/generative-ai model and chat calls through Braintrust. */ export function wrapGoogleGenerativeAI(client: T): T { + registerInstrumentation(); if (!isObject(client) || typeof client.getGenerativeModel !== "function") return client; return wrapClient(client as unknown as GenerativeAIClient) as T; diff --git a/js/src/wrappers/groq.test.ts b/js/src/wrappers/groq.test.ts index 88a6c0f95..cc157938f 100644 --- a/js/src/wrappers/groq.test.ts +++ b/js/src/wrappers/groq.test.ts @@ -7,10 +7,11 @@ import { expect, test, vi, -} from "vitest"; +} from "vite-plus/test"; import { Readable } from "node:stream"; import { configureNode } from "../node/config"; -import { Attachment, _exportsForTestingOnly, initLogger } from "../logger"; +import { Attachment, initLogger } from "../logger/core"; +import { _exportsForTestingOnly } from "../logger/testing"; import { wrapGroq } from "./groq"; try { @@ -31,6 +32,7 @@ describe("groq wrapper", () => { beforeEach(() => { backgroundLogger = _exportsForTestingOnly.useTestBackgroundLogger(); initLogger({ + autoCaptureAttachments: true, projectId: "test-project-id", projectName: "groq.test.ts", }); @@ -429,39 +431,54 @@ describe("groq wrapper", () => { }); }); - test("captures speech consumed through a cloned response", async () => { - const wrapped = wrapGroq({ - audio: { - speech: { - create: vi.fn(async (_request: Record) => - Promise.resolve( - new Response(new Uint8Array([1, 2, 3]), { - headers: { "content-type": "audio/wav" }, - }), + test.each([false, true])( + "applies capture=%s to speech consumed through a cloned response", + async (captureAttachments) => { + initLogger({ + projectName: "groq.test.ts", + projectId: "test-project-id", + autoCaptureAttachments: captureAttachments, + }); + const wrapped = wrapGroq({ + audio: { + speech: { + create: vi.fn(async (_request: Record) => + Promise.resolve( + new Response(new Uint8Array([1, 2, 3]), { + headers: { "content-type": "audio/wav" }, + }), + ), ), - ), + }, }, - }, - }); + }); - const response = await wrapped.audio.speech.create({ - input: "Hello from Braintrust.", - model: "playai-tts", - voice: "Fritz-PlayAI", - }); - expect(new Uint8Array(await response.clone().arrayBuffer())).toEqual( - new Uint8Array([1, 2, 3]), - ); - expect(response.bodyUsed).toBe(false); - - const spans = (await backgroundLogger.drain()) as Record[]; - const speechSpan = spans.find( - (span) => span.span_attributes?.name === "groq.audio.speech.create", - )!; - const attachment = speechSpan.output.content[0].file - .file_data as Attachment; - expect( - new Uint8Array(await (await attachment.data()).arrayBuffer()), - ).toEqual(new Uint8Array([1, 2, 3])); - }); + const response = await wrapped.audio.speech.create({ + input: "Hello from Braintrust.", + model: "playai-tts", + voice: "Fritz-PlayAI", + }); + expect(new Uint8Array(await response.clone().arrayBuffer())).toEqual( + new Uint8Array([1, 2, 3]), + ); + expect(response.bodyUsed).toBe(false); + + const spans = (await backgroundLogger.drain()) as Record[]; + const speechSpan = spans.find( + (span) => span.span_attributes?.name === "groq.audio.speech.create", + )!; + if (!captureAttachments) { + expect(speechSpan.output.content).toEqual([]); + expect(speechSpan.metrics.time_to_first_token).toEqual( + expect.any(Number), + ); + return; + } + const attachment = speechSpan.output.content[0].file + .file_data as Attachment; + expect( + new Uint8Array(await (await attachment.data()).arrayBuffer()), + ).toEqual(new Uint8Array([1, 2, 3])); + }, + ); }); diff --git a/js/src/wrappers/groq.ts b/js/src/wrappers/groq.ts index 488efdd6d..c8b16a4e6 100644 --- a/js/src/wrappers/groq.ts +++ b/js/src/wrappers/groq.ts @@ -1,5 +1,6 @@ +import { registerInstrumentation } from "../instrumentation/registrations/groq"; import { debugLogger } from "../debug-logger"; -import { groqChannels } from "../instrumentation/plugins/groq-interceptors"; +import { groqChannels } from "../instrumentation/providers/groq-interceptors"; import type { GroqAudio, GroqAudioSpeech, @@ -19,6 +20,7 @@ import type { * Wrap a Groq client (created with `new Groq(...)`) with Braintrust tracing. */ export function wrapGroq(groq: T): T { + registerInstrumentation(); if (isSupportedGroqClient(groq)) { return groqProxy(groq) as T; } diff --git a/js/src/wrappers/huggingface-transformers.test.ts b/js/src/wrappers/huggingface-transformers.test.ts index 31e2a1180..cafa90852 100644 --- a/js/src/wrappers/huggingface-transformers.test.ts +++ b/js/src/wrappers/huggingface-transformers.test.ts @@ -5,12 +5,9 @@ import { describe, expect, test, -} from "vitest"; -import { - _exportsForTestingOnly, - initLogger, - type TestBackgroundLogger, -} from "../logger"; +} from "vite-plus/test"; +import { _exportsForTestingOnly } from "../logger/testing"; +import { initLogger, type TestBackgroundLogger } from "../logger/core"; import { configureNode } from "../node/config"; import { wrapHuggingFaceTransformers } from "./huggingface-transformers"; import type { diff --git a/js/src/wrappers/huggingface-transformers.ts b/js/src/wrappers/huggingface-transformers.ts index d5d15e51b..aec57da3a 100644 --- a/js/src/wrappers/huggingface-transformers.ts +++ b/js/src/wrappers/huggingface-transformers.ts @@ -1,7 +1,8 @@ +import { registerInstrumentation } from "../instrumentation/registrations/huggingface"; import { huggingFaceTransformersChannels, isSupportedHuggingFaceTransformersTask, -} from "../instrumentation/plugins/huggingface-transformers-interceptors"; +} from "../instrumentation/providers/huggingface-transformers-interceptors"; import type { HuggingFaceTransformersModule, HuggingFaceTransformersPipeline, @@ -25,6 +26,7 @@ export function wrapHuggingFaceTransformers( ): HuggingFaceTransformersPipeline; export function wrapHuggingFaceTransformers(transformers: T): T; export function wrapHuggingFaceTransformers(transformers: unknown): unknown { + registerInstrumentation(); const pipelineTask = (transformers as { task?: unknown } | null)?.task; if ( typeof transformers === "function" && diff --git a/js/src/wrappers/huggingface.test.ts b/js/src/wrappers/huggingface.test.ts index 7adc5a99b..6c14adf60 100644 --- a/js/src/wrappers/huggingface.test.ts +++ b/js/src/wrappers/huggingface.test.ts @@ -5,13 +5,10 @@ import { describe, expect, test, -} from "vitest"; +} from "vite-plus/test"; import { configureNode } from "../node/config"; -import { - _exportsForTestingOnly, - initLogger, - TestBackgroundLogger, -} from "../logger"; +import { _exportsForTestingOnly } from "../logger/testing"; +import { initLogger, TestBackgroundLogger } from "../logger/core"; import { wrapHuggingFace } from "./huggingface"; try { diff --git a/js/src/wrappers/huggingface.ts b/js/src/wrappers/huggingface.ts index c01e0756c..0798ddb2e 100644 --- a/js/src/wrappers/huggingface.ts +++ b/js/src/wrappers/huggingface.ts @@ -1,6 +1,8 @@ +import { registerInstrumentation } from "../instrumentation/registrations/huggingface"; +import { huggingFaceChannels } from "../instrumentation/providers/huggingface-interceptors"; import { debugLogger } from "../debug-logger"; import { isObject } from "../../util"; -import { huggingFaceChannels } from "../instrumentation/plugins/huggingface-interceptors"; + import type { HuggingFaceChatCompletion, HuggingFaceChatCompletionChunk, @@ -44,6 +46,7 @@ export function wrapHuggingFace( ): HuggingFaceClient; export function wrapHuggingFace(huggingFace: T): T; export function wrapHuggingFace(huggingFace: unknown): unknown { + registerInstrumentation(); if (isSupportedHuggingFaceModule(huggingFace)) { return moduleProxy(huggingFace); } diff --git a/js/src/wrappers/langchain/callback-handler.test.ts b/js/src/wrappers/langchain/callback-handler.test.ts index 53f61cbc2..f588ddc57 100644 --- a/js/src/wrappers/langchain/callback-handler.test.ts +++ b/js/src/wrappers/langchain/callback-handler.test.ts @@ -1,4 +1,4 @@ -import { describe, expect, it, vi } from "vitest"; +import { describe, expect, it, vi } from "vite-plus/test"; import { BraintrustLangChainCallbackHandler } from "./callback-handler"; function createHarness() { diff --git a/js/src/wrappers/langchain/callback-handler.ts b/js/src/wrappers/langchain/callback-handler.ts index 039e1f234..31dfb7695 100644 --- a/js/src/wrappers/langchain/callback-handler.ts +++ b/js/src/wrappers/langchain/callback-handler.ts @@ -1,5 +1,10 @@ -import { currentSpan, initLogger, NOOP_SPAN, startSpan } from "../../logger"; -import type { Span } from "../../logger"; +import { + currentSpan, + initLogger, + NOOP_SPAN, + startSpan, +} from "../../logger/core"; +import type { Span } from "../../logger/core"; import { INSTRUMENTATION_NAMES, withSpanInstrumentationName, @@ -15,22 +20,18 @@ import type { export const BRAINTRUST_LANGCHAIN_CALLBACK_HANDLER_NAME = "BraintrustCallbackHandler"; -export class BraintrustLangChainCallbackHandler< - IsAsyncFlush extends boolean = true, -> { - name = BRAINTRUST_LANGCHAIN_CALLBACK_HANDLER_NAME; +export class BraintrustLangChainCallbackHandler { + name: string = BRAINTRUST_LANGCHAIN_CALLBACK_HANDLER_NAME; private spans = new Map(); private skippedRuns = new Set(); private parent?: Span | (() => Span); private rootRunId?: string; - private options: LangChainCallbackHandlerOptions; + private options: LangChainCallbackHandlerOptions; private startTimes = new Map(); private firstTokenTimes = new Map(); private ttftMs = new Map(); - constructor( - options?: Partial>, - ) { + constructor(options?: Partial) { this.parent = options?.parent; this.options = { debug: options?.debug ?? false, @@ -93,13 +94,14 @@ export class BraintrustLangChainCallbackHandler< INSTRUMENTATION_NAMES.LANGCHAIN, ); - let span = parentSpan.startSpan(spanArgs); + const { parent: _ignoredParent, ...publicSpanArgs } = spanArgs; + let span = parentSpan.startSpan(publicSpanArgs); if ( !Object.is(this.options.logger, NOOP_SPAN) && Object.is(span, NOOP_SPAN) ) { - span = initLogger().startSpan(spanArgs); + span = initLogger().startSpan(publicSpanArgs); } this.spans.set(runId, span); diff --git a/js/src/wrappers/langgraph-sdk.ts b/js/src/wrappers/langgraph-sdk.ts index 6c33b6487..b099efc73 100644 --- a/js/src/wrappers/langgraph-sdk.ts +++ b/js/src/wrappers/langgraph-sdk.ts @@ -1,12 +1,14 @@ +import { registerInstrumentation } from "../instrumentation/registrations/langgraphSDK"; import { isObject } from "../../util"; import { debugLogger } from "../debug-logger"; -import { langGraphSDKChannels } from "../instrumentation/plugins/langgraph-sdk-interceptors"; +import { langGraphSDKChannels } from "../instrumentation/providers/langgraph-sdk-interceptors"; import type { LangGraphSDKClient } from "../vendor-sdk-types/langgraph-sdk"; const clients = new WeakMap(); /** Trace LangGraph Platform runs that the caller waits for or streams. */ export function wrapLangGraphSDK(client: T): T { + registerInstrumentation(); const runsClient = isObject(client) ? client.runs : undefined; if ( !isObject(client) || diff --git a/js/src/wrappers/langsmith.test.ts b/js/src/wrappers/langsmith.test.ts index 9d0be8c57..eeb29e46f 100644 --- a/js/src/wrappers/langsmith.test.ts +++ b/js/src/wrappers/langsmith.test.ts @@ -1,4 +1,4 @@ -import { afterEach, describe, expect, it, vi } from "vitest"; +import { afterEach, describe, expect, it, vi } from "vite-plus/test"; const { invoke } = vi.hoisted(() => ({ invoke: vi.fn( diff --git a/js/src/wrappers/langsmith.ts b/js/src/wrappers/langsmith.ts index 5213d5366..fdd69a2af 100644 --- a/js/src/wrappers/langsmith.ts +++ b/js/src/wrappers/langsmith.ts @@ -1,5 +1,6 @@ +import { registerInstrumentation } from "../instrumentation/registrations/langsmith"; import { debugLogger } from "../debug-logger"; -import { langSmithChannels } from "../instrumentation/plugins/langsmith-interceptors"; +import { langSmithChannels } from "../instrumentation/providers/langsmith-interceptors"; import type { LangSmithClient, LangSmithClientConstructor, @@ -33,6 +34,7 @@ const WRAPPED_TRACEABLE_NAMESPACE = Symbol.for( ); export function wrapLangSmithTraceable(namespace: T): T { + registerInstrumentation(); return wrapNamespaceExport( namespace, "traceable", @@ -42,6 +44,7 @@ export function wrapLangSmithTraceable(namespace: T): T { } export function wrapLangSmithRunTrees(namespace: T): T { + registerInstrumentation(); return wrapNamespaceExport( namespace, "RunTree", @@ -51,6 +54,7 @@ export function wrapLangSmithRunTrees(namespace: T): T { } export function wrapLangSmithClient(namespace: T): T { + registerInstrumentation(); return wrapNamespaceExport( namespace, "Client", diff --git a/js/src/wrappers/livekit.test.ts b/js/src/wrappers/livekit.test.ts index 0e8c486bf..c0c2102fa 100644 --- a/js/src/wrappers/livekit.test.ts +++ b/js/src/wrappers/livekit.test.ts @@ -1,6 +1,6 @@ -import { expect, test, vi } from "vitest"; +import { expect, test, vi } from "vite-plus/test"; import { wrapLiveKit } from "./livekit"; -import { livekitChannels } from "../instrumentation/plugins/livekit-interceptors"; +import { livekitChannels } from "../instrumentation/providers/livekit-interceptors"; test("wraps ESM namespaces and preserves return identity when wrapped twice", () => { const result = Promise.resolve(); diff --git a/js/src/wrappers/livekit.ts b/js/src/wrappers/livekit.ts index 4104fbc4c..8ff40d641 100644 --- a/js/src/wrappers/livekit.ts +++ b/js/src/wrappers/livekit.ts @@ -1,6 +1,7 @@ +import { registerInstrumentation } from "../instrumentation/registrations/livekit"; /* eslint-disable @typescript-eslint/consistent-type-assertions -- vendored LiveKit boundaries. */ import { debugLogger } from "../debug-logger"; -import { livekitChannels } from "../instrumentation/plugins/livekit-interceptors"; +import { livekitChannels } from "../instrumentation/providers/livekit-interceptors"; import type { AgentActivity, AgentSession, @@ -89,6 +90,7 @@ function wrapSession(session: AgentSession): AgentSession { * ``` */ export function wrapLiveKit(module: T): T { + registerInstrumentation(); const livekit = module as unknown as Partial | undefined; if ( !livekit || diff --git a/js/src/wrappers/mastra.test.ts b/js/src/wrappers/mastra.test.ts index 5671bf261..f62eddf8a 100644 --- a/js/src/wrappers/mastra.test.ts +++ b/js/src/wrappers/mastra.test.ts @@ -5,9 +5,10 @@ import { describe, expect, test, -} from "vitest"; +} from "vite-plus/test"; import { configureNode } from "../node/config"; -import { _exportsForTestingOnly, initLogger } from "../logger"; +import { _exportsForTestingOnly } from "../logger/testing"; +import { initLogger } from "../logger/core"; import { BraintrustObservabilityExporter } from "./mastra"; try { diff --git a/js/src/wrappers/mastra.ts b/js/src/wrappers/mastra.ts index b3ff3b419..b77283d2d 100644 --- a/js/src/wrappers/mastra.ts +++ b/js/src/wrappers/mastra.ts @@ -28,7 +28,7 @@ import { startSpan, type StartSpanArgs, type Span, -} from "../logger"; +} from "../logger/core"; import { INSTRUMENTATION_NAMES, withSpanInstrumentationName, @@ -75,7 +75,7 @@ interface MastraTracingEvent { } /** Subset of the `ObservabilityExporter` contract from `@mastra/core`. */ -export interface MastraObservabilityExporter { +interface MastraObservabilityExporter { name: string; init?(options: unknown): void; __setLogger?(logger: unknown): void; @@ -240,7 +240,7 @@ type SpanRecord = { * each instance maintains its own span map keyed by Mastra `spanId`. */ export class BraintrustObservabilityExporter implements MastraObservabilityExporter { - public readonly name = MASTRA_BRAINTRUST_EXPORTER_NAME; + public readonly name: "braintrust" = MASTRA_BRAINTRUST_EXPORTER_NAME; private readonly spans = new Map(); // Captured at the first SPAN_STARTED event. Mastra's observability bus may @@ -328,11 +328,12 @@ export class BraintrustObservabilityExporter implements MastraObservabilityExpor } } + const { parent: _ignoredParent, ...publicArgs } = args; const span = parentRecord - ? parentRecord.span.startSpan(args) + ? parentRecord.span.startSpan(publicArgs) : this.capturedParent - ? this.capturedParent.startSpan(args) - : startSpan(args); + ? this.capturedParent.startSpan(publicArgs) + : startSpan(publicArgs); const record: SpanRecord = { span, hasLoggedInput: false }; this.logPayload(record, exported); @@ -408,40 +409,3 @@ export class BraintrustObservabilityExporter implements MastraObservabilityExpor function logExporterError(err: unknown): void { debugLogger.warn("Mastra exporter failure:", err); } - -/** - * @deprecated Mastra is now instrumented through its own `ObservabilityExporter` - * contract instead of by wrapping the agent. This function does nothing and - * will be removed in the next major release. - * - * To capture Mastra spans in Braintrust, do one of: - * - * - **Auto-instrumentation**: run your app with - * `node --import braintrust/hook.mjs`. The loader installs - * `BraintrustObservabilityExporter` into every `new Mastra(...)` - * automatically. - * - **Manual wiring**: pass the exporter yourself: - * - * ```ts - * import { Mastra } from "@mastra/core"; - * import { Observability } from "@mastra/observability"; - * import { BraintrustObservabilityExporter } from "braintrust"; - * - * const mastra = new Mastra({ - * observability: new Observability({ - * configs: { - * default: { - * serviceName: "my-service", - * exporters: [new BraintrustObservabilityExporter()], - * }, - * }, - * }), - * }); - * ``` - */ -export function wrapMastraAgent( - agent: T, - _options?: { name?: string; span_name?: string }, -): T { - return agent; -} diff --git a/js/src/wrappers/mcp.test.ts b/js/src/wrappers/mcp.test.ts index b95be4942..54e70c4b0 100644 --- a/js/src/wrappers/mcp.test.ts +++ b/js/src/wrappers/mcp.test.ts @@ -1,3 +1,4 @@ +import { _exportsForTestingOnly } from "../logger/testing"; import { debugLogger } from "../debug-logger"; import { afterEach, @@ -7,10 +8,10 @@ import { expect, it, vi, -} from "vitest"; -import { _exportsForTestingOnly, initLogger } from "../logger"; +} from "vite-plus/test"; +import { initLogger } from "../logger/core"; import { configureNode } from "../node/config"; -import { mcpSDKChannels } from "../instrumentation/plugins/mcp-interceptors"; +import { mcpSDKChannels } from "../instrumentation/providers/mcp-interceptors"; import type { MCPProtocol, MCPRequestHandler, diff --git a/js/src/wrappers/mcp.ts b/js/src/wrappers/mcp.ts index 6fb82490c..c718a2b8f 100644 --- a/js/src/wrappers/mcp.ts +++ b/js/src/wrappers/mcp.ts @@ -1,6 +1,7 @@ +import { registerInstrumentation } from "../instrumentation/registrations/mcp"; import { debugLogger } from "../debug-logger"; import { isObject } from "../../util/index"; -import { wrapMCPProtocol } from "../instrumentation/plugins/mcp-interceptors"; +import { wrapMCPProtocol } from "../instrumentation/providers/mcp-interceptors"; import type { MCPProtocol } from "../vendor-sdk-types/mcp"; /** @@ -19,6 +20,7 @@ export function wrapMCPServer(server: T): T { debugLogger.notice.warn("Unsupported MCP server. Not wrapping."); return server; } + registerInstrumentation(); wrapMCPProtocol(protocol as MCPProtocol); return server; } diff --git a/js/src/wrappers/mistral.ts b/js/src/wrappers/mistral.ts index af5003074..e780e80bd 100644 --- a/js/src/wrappers/mistral.ts +++ b/js/src/wrappers/mistral.ts @@ -1,5 +1,6 @@ +import { registerInstrumentation } from "../instrumentation/registrations/mistral"; import { debugLogger } from "../debug-logger"; -import { mistralChannels } from "../instrumentation/plugins/mistral-interceptors"; +import { mistralChannels } from "../instrumentation/providers/mistral-interceptors"; import type { MistralAgents, MistralAgentsCompletionResponse, @@ -28,6 +29,7 @@ import type { * Wrap a Mistral client (created with `new Mistral(...)`) with Braintrust tracing. */ export function wrapMistral(mistral: T): T { + registerInstrumentation(); if (isSupportedMistralClient(mistral)) { return mistralProxy(mistral) as T; } diff --git a/js/src/wrappers/node-test/node-test-example.test.ts b/js/src/wrappers/node-test/node-test-example.test.ts index f9b45e5dd..e28d8150f 100644 --- a/js/src/wrappers/node-test/node-test-example.test.ts +++ b/js/src/wrappers/node-test/node-test-example.test.ts @@ -1,5 +1,5 @@ -import { test, describe, afterAll, beforeAll } from "vitest"; -import { currentSpan } from "../../logger"; +import { test, describe, afterAll, beforeAll } from "vite-plus/test"; +import { currentSpan } from "../../logger/core"; import { initNodeTestSuite } from "./suite"; import { setupNodeTestEnv, diff --git a/js/src/wrappers/node-test/node-test-span.test.ts b/js/src/wrappers/node-test/node-test-span.test.ts index 6bcf8da5c..5f2f2c983 100644 --- a/js/src/wrappers/node-test/node-test-span.test.ts +++ b/js/src/wrappers/node-test/node-test-span.test.ts @@ -1,5 +1,5 @@ -import { test, expect, describe, afterAll, beforeAll } from "vitest"; -import { currentSpan } from "../../logger"; +import { test, expect, describe, afterAll, beforeAll } from "vite-plus/test"; +import { currentSpan } from "../../logger/core"; import { initNodeTestSuite } from "./suite"; import { setupNodeTestEnv, diff --git a/js/src/wrappers/node-test/node-test.test.ts b/js/src/wrappers/node-test/node-test.test.ts index dea01d74d..dad63afbe 100644 --- a/js/src/wrappers/node-test/node-test.test.ts +++ b/js/src/wrappers/node-test/node-test.test.ts @@ -1,4 +1,11 @@ -import { test, expect, describe, afterAll, vi, beforeAll } from "vitest"; +import { + test, + expect, + describe, + afterAll, + vi, + beforeAll, +} from "vite-plus/test"; import { initNodeTestSuite } from "./suite"; import { setupNodeTestEnv, diff --git a/js/src/wrappers/node-test/suite.ts b/js/src/wrappers/node-test/suite.ts index c2ee794df..c68d2b0ac 100644 --- a/js/src/wrappers/node-test/suite.ts +++ b/js/src/wrappers/node-test/suite.ts @@ -1,4 +1,4 @@ -import { initExperiment, type Experiment } from "../../logger"; +import { init, type Experiment } from "../../logger/experiments"; import { runTracedEval } from "../shared/traced-eval"; import { summarizeAndFlush } from "../shared/flush"; import type { @@ -40,7 +40,8 @@ export function initNodeTestSuite(config: NodeTestSuiteConfig): NodeTestSuite { const experimentName = config.experimentName || `${config.projectName}-${new Date().toISOString()}`; - experiment = initExperiment(config.projectName, { + experiment = init({ + project: config.projectName, experiment: experimentName, }); return experiment; diff --git a/js/src/wrappers/node-test/test-helpers.ts b/js/src/wrappers/node-test/test-helpers.ts index 0047bf5ac..35bef9444 100644 --- a/js/src/wrappers/node-test/test-helpers.ts +++ b/js/src/wrappers/node-test/test-helpers.ts @@ -1,10 +1,8 @@ -import { vi } from "vitest"; +import { vi } from "vite-plus/test"; import { configureNode } from "../../node/config"; -import { - _exportsForTestingOnly, - type TestBackgroundLogger, -} from "../../logger"; -import * as logger from "../../logger"; +import { _exportsForTestingOnly } from "../../logger/testing"; +import { type TestBackgroundLogger } from "../../logger/core"; +import * as logger from "../../logger/experiments"; import type { MinimalTestContext } from "./types"; /** @@ -19,14 +17,12 @@ export async function setupNodeTestEnv(): Promise { await _exportsForTestingOnly.simulateLoginForTests(); const bgLogger = _exportsForTestingOnly.useTestBackgroundLogger(); - vi.spyOn(logger, "initExperiment").mockImplementation( - (projectName: string, options?: any) => { - return _exportsForTestingOnly.initTestExperiment( - options?.experiment || "test-experiment", - projectName, - ); - }, - ); + vi.spyOn(logger, "init").mockImplementation((options) => { + return _exportsForTestingOnly.initTestExperiment( + options.experiment || "test-experiment", + options.project ?? options.projectId ?? "test-project", + ); + }); return bgLogger; } diff --git a/js/src/wrappers/oai.ts b/js/src/wrappers/oai.ts index d49800de9..5e19e5240 100644 --- a/js/src/wrappers/oai.ts +++ b/js/src/wrappers/oai.ts @@ -1,3 +1,4 @@ +import { registerInstrumentation } from "../instrumentation/registrations/openai"; import { debugLogger } from "../debug-logger"; import type { OpenAIMediaClient, @@ -7,14 +8,11 @@ import type { /* eslint-disable @typescript-eslint/no-explicit-any */ import type { ArgsOf } from "../instrumentation/core/interceptor-definitions"; import type { ResultOf } from "../instrumentation/core/tracing-types"; -import { openAIChannels } from "../instrumentation/plugins/openai-interceptors"; -import type { CompiledPrompt } from "../logger"; +import { openAIChannels } from "../instrumentation/providers/openai-interceptors"; +import type { CompiledPrompt } from "../logger/prompts"; +import { responsesProxy } from "./oai_responses"; + import type { OpenAIDecisionCreateParams } from "../vendor-sdk-types/openai-decisions"; -import { - LEGACY_CACHED_HEADER, - parseCachedHeader, - X_CACHED_HEADER, -} from "../openai-utils"; import type { OpenAIChatCompletion, OpenAIChatCreateParams, @@ -26,7 +24,7 @@ import type { OpenAIModerationResponse, } from "../vendor-sdk-types/openai"; import { OpenAIV4Client } from "../vendor-sdk-types/openai-v4"; -import { responsesProxy } from "./oai_responses"; + import { APIPromise, createChannelContext, @@ -52,6 +50,7 @@ declare global { * @returns The wrapped `OpenAI` object. */ export function wrapOpenAI(openai: T): T { + registerInstrumentation(); const oai: unknown = openai; if ( oai && @@ -75,11 +74,10 @@ export function wrapOpenAI(openai: T): T { return openai; } } -globalThis.__inherited_braintrust_wrap_openai = wrapOpenAI; type OpenAILike = OpenAIV4Client; -export function wrapOpenAIv4(openai: T): T { +function wrapOpenAIv4(openai: T): T { // eslint-disable-next-line @typescript-eslint/consistent-type-assertions const typedOpenai = openai as OpenAIV4Client; // Recover `this` for fallback methods so private fields and internal slots @@ -336,8 +334,6 @@ function wrapBetaChatCompletionStream

( }; } -export { LEGACY_CACHED_HEADER, parseCachedHeader, X_CACHED_HEADER }; - function wrapChatCompletion< P extends OpenAIChatCreateParams, C extends OpenAIChatCompletion | OpenAIChatStream, diff --git a/js/src/wrappers/oai_responses.ts b/js/src/wrappers/oai_responses.ts index 1ca53560f..698c3a538 100644 --- a/js/src/wrappers/oai_responses.ts +++ b/js/src/wrappers/oai_responses.ts @@ -1,7 +1,7 @@ import type { ArgsOf } from "../instrumentation/core/interceptor-definitions"; import type { ResultOf } from "../instrumentation/core/tracing-types"; import type { ChannelSpanInfo } from "../instrumentation/core/types"; -import { openAIChannels } from "../instrumentation/plugins/openai-interceptors"; +import { openAIChannels } from "../instrumentation/providers/openai-interceptors"; import { parseMetricsFromUsage } from "../openai-utils"; import { APIPromise, @@ -17,7 +17,7 @@ type SpanInfo = { span_info?: ChannelSpanInfo; }; -export function responsesProxy(openai: any) { +export function responsesProxy(openai: any): any { // This was added in v4.87.0 of the openai-node library if (!openai.responses) { return openai; diff --git a/js/src/wrappers/ollama.test.ts b/js/src/wrappers/ollama.test.ts index 2db63e5eb..c1b39ff99 100644 --- a/js/src/wrappers/ollama.test.ts +++ b/js/src/wrappers/ollama.test.ts @@ -1,6 +1,6 @@ -import { describe, expect, it, vi } from "vitest"; +import { describe, expect, it, vi } from "vite-plus/test"; import { debugLogger } from "../debug-logger"; -import { ollamaChannels } from "../instrumentation/plugins/ollama-interceptors"; +import { ollamaChannels } from "../instrumentation/providers/ollama-interceptors"; import type { OllamaClient } from "../vendor-sdk-types/ollama"; import { wrapOllama } from "./ollama"; diff --git a/js/src/wrappers/ollama.ts b/js/src/wrappers/ollama.ts index c5b9a5faa..fb92f8109 100644 --- a/js/src/wrappers/ollama.ts +++ b/js/src/wrappers/ollama.ts @@ -1,6 +1,7 @@ +import { registerInstrumentation } from "../instrumentation/registrations/ollama"; import { isObject } from "../../util"; import { debugLogger } from "../debug-logger"; -import { ollamaChannels } from "../instrumentation/plugins/ollama-interceptors"; +import { ollamaChannels } from "../instrumentation/providers/ollama-interceptors"; import type { OllamaChatRequest, OllamaChatResult, @@ -16,6 +17,7 @@ import type { * diagnostics-channel events. */ export function wrapOllama(ollama: T): T { + registerInstrumentation(); if (isSupportedOllamaClient(ollama)) { return ollamaProxy(ollama) as T; } diff --git a/js/src/wrappers/openai-codex.ts b/js/src/wrappers/openai-codex.ts index ced9c7708..015f1bf0b 100644 --- a/js/src/wrappers/openai-codex.ts +++ b/js/src/wrappers/openai-codex.ts @@ -1,5 +1,6 @@ +import { registerInstrumentation } from "../instrumentation/registrations/openaiCodexSDK"; import { debugLogger } from "../debug-logger"; -import { openAICodexChannels } from "../instrumentation/plugins/openai-codex-interceptors"; +import { openAICodexChannels } from "../instrumentation/providers/openai-codex-interceptors"; import type { OpenAICodexClass, OpenAICodexClient, @@ -19,6 +20,7 @@ const WRAPPED_THREAD = Symbol.for("braintrust.openai-codex.wrapped-thread"); * emits diagnostics-channel events; the OpenAI Codex plugin owns span lifecycle. */ export function wrapOpenAICodexSDK(sdk: T): T { + registerInstrumentation(); if (!sdk || typeof sdk !== "object") { return sdk; } diff --git a/js/src/wrappers/openai-promise-utils.ts b/js/src/wrappers/openai-promise-utils.ts index 3a0d18e19..6526413a7 100644 --- a/js/src/wrappers/openai-promise-utils.ts +++ b/js/src/wrappers/openai-promise-utils.ts @@ -6,7 +6,7 @@ import type { ResultOf } from "../instrumentation/core/tracing-types"; import type { OpenAIAsyncChannel, OpenAIChannel, -} from "../instrumentation/plugins/openai-interceptors"; +} from "../instrumentation/providers/openai-interceptors"; export type EnhancedResponse = { response: Response; diff --git a/js/src/wrappers/openrouter-agent.test.ts b/js/src/wrappers/openrouter-agent.test.ts index c84a48333..221400552 100644 --- a/js/src/wrappers/openrouter-agent.test.ts +++ b/js/src/wrappers/openrouter-agent.test.ts @@ -1,7 +1,7 @@ import { debugLogger } from "../debug-logger"; -import { afterEach, describe, expect, it, vi } from "vitest"; +import { afterEach, describe, expect, it, vi } from "vite-plus/test"; import { wrapOpenRouterAgent } from "./openrouter-agent"; -import { openRouterAgentChannels } from "../instrumentation/plugins/openrouter-agent-interceptors"; +import { openRouterAgentChannels } from "../instrumentation/providers/openrouter-agent-interceptors"; describe("wrapOpenRouterAgent", () => { afterEach(() => { diff --git a/js/src/wrappers/openrouter-agent.ts b/js/src/wrappers/openrouter-agent.ts index dd077e704..ac3f234ea 100644 --- a/js/src/wrappers/openrouter-agent.ts +++ b/js/src/wrappers/openrouter-agent.ts @@ -1,5 +1,6 @@ +import { registerInstrumentation } from "../instrumentation/registrations/openrouterAgent"; import { debugLogger } from "../debug-logger"; -import { openRouterAgentChannels } from "../instrumentation/plugins/openrouter-agent-interceptors"; +import { openRouterAgentChannels } from "../instrumentation/providers/openrouter-agent-interceptors"; import type { OpenRouterAgentCallModelRequest, OpenRouterAgentClient, @@ -10,6 +11,7 @@ import type { * diagnostics-channel events consumed by the OpenRouter Agent plugin. */ export function wrapOpenRouterAgent(agent: T): T { + registerInstrumentation(); const candidate: unknown = agent; if ( candidate && diff --git a/js/src/wrappers/openrouter.test.ts b/js/src/wrappers/openrouter.test.ts index 50b0bdbb3..54e01263f 100644 --- a/js/src/wrappers/openrouter.test.ts +++ b/js/src/wrappers/openrouter.test.ts @@ -7,9 +7,10 @@ import { expect, test, vi, -} from "vitest"; +} from "vite-plus/test"; import { configureNode } from "../node/config"; -import { _exportsForTestingOnly, initLogger } from "../logger"; +import { _exportsForTestingOnly } from "../logger/testing"; +import { initLogger } from "../logger/core"; import { wrapOpenRouter } from "./openrouter"; const TEST_MODEL = "openai/gpt-4.1-mini"; diff --git a/js/src/wrappers/openrouter.ts b/js/src/wrappers/openrouter.ts index c3f6278be..d27c0b8e6 100644 --- a/js/src/wrappers/openrouter.ts +++ b/js/src/wrappers/openrouter.ts @@ -1,5 +1,6 @@ +import { registerInstrumentation } from "../instrumentation/registrations/openrouter"; import { debugLogger } from "../debug-logger"; -import { openRouterChannels } from "../instrumentation/plugins/openrouter-interceptors"; +import { openRouterChannels } from "../instrumentation/providers/openrouter-interceptors"; import type { OpenRouterBeta, OpenRouterCallModelRequest, @@ -23,6 +24,7 @@ import type { * diagnostics-channel events that Braintrust plugins can consume. */ export function wrapOpenRouter(openrouter: T): T { + registerInstrumentation(); const or: unknown = openrouter; if ( or && diff --git a/js/src/wrappers/pi-coding-agent.test.ts b/js/src/wrappers/pi-coding-agent.test.ts index 391bfb00c..43e7821f5 100644 --- a/js/src/wrappers/pi-coding-agent.test.ts +++ b/js/src/wrappers/pi-coding-agent.test.ts @@ -1,4 +1,4 @@ -import { afterEach, describe, expect, it, vi } from "vitest"; +import { afterEach, describe, expect, it, vi } from "vite-plus/test"; const { invoke } = vi.hoisted(() => ({ invoke: vi.fn( @@ -12,7 +12,10 @@ const { invoke } = vi.hoisted(() => ({ })); vi.mock("../global-instrumentation-hooks", async (importOriginal) => ({ ...(await importOriginal()), - newGlobalInvocationHook: vi.fn(() => ({ invoke })), + newGlobalInvocationHook: vi.fn(() => ({ + invoke, + intercept: vi.fn(() => () => {}), + })), })); import { wrapPiCodingAgentSDK } from "./pi-coding-agent"; diff --git a/js/src/wrappers/pi-coding-agent.ts b/js/src/wrappers/pi-coding-agent.ts index c7051b1b2..32dbe30bb 100644 --- a/js/src/wrappers/pi-coding-agent.ts +++ b/js/src/wrappers/pi-coding-agent.ts @@ -1,5 +1,6 @@ +import { registerInstrumentation } from "../instrumentation/registrations/piCodingAgent"; import { debugLogger } from "../debug-logger"; -import { piCodingAgentChannels } from "../instrumentation/plugins/pi-coding-agent-interceptors"; +import { piCodingAgentChannels } from "../instrumentation/providers/pi-coding-agent-interceptors"; import type { PiAgentSession, PiAgentSessionClass, @@ -14,6 +15,7 @@ const WRAPPED_PROMPT = Symbol.for("braintrust.pi-coding-agent.wrapped-prompt"); * instrumentation hook calls; the Pi Coding Agent plugin owns span lifecycle. */ export function wrapPiCodingAgentSDK(sdk: T): T { + registerInstrumentation(); if (!sdk || typeof sdk !== "object") { return sdk; } diff --git a/js/src/wrappers/shared/flush.ts b/js/src/wrappers/shared/flush.ts index 53aea44be..a9817afe2 100644 --- a/js/src/wrappers/shared/flush.ts +++ b/js/src/wrappers/shared/flush.ts @@ -1,5 +1,5 @@ import { sdkConsole } from "../../logs/console"; -import type { Experiment } from "../../logger"; +import type { Experiment } from "../../logger/experiments"; import { formatExperimentSummary } from "./format"; /** diff --git a/js/src/wrappers/shared/format.ts b/js/src/wrappers/shared/format.ts index 69687bae0..a99c07864 100644 --- a/js/src/wrappers/shared/format.ts +++ b/js/src/wrappers/shared/format.ts @@ -1,4 +1,4 @@ -import type { ExperimentSummary } from "../../logger"; +import type { ExperimentSummary } from "../../logger/experiments"; export function formatExperimentSummary(summary: ExperimentSummary): string { const lines: string[] = []; diff --git a/js/src/wrappers/shared/logging.ts b/js/src/wrappers/shared/logging.ts index 9d9003fa1..157f83d6f 100644 --- a/js/src/wrappers/shared/logging.ts +++ b/js/src/wrappers/shared/logging.ts @@ -1,4 +1,4 @@ -import { currentSpan } from "../../logger"; +import { currentSpan, type Span } from "../../logger/core"; export function logOutputs(outputs: Record): void { currentSpan().log({ output: outputs }); @@ -15,6 +15,6 @@ export function logFeedback(feedback: { }); } -export function getCurrentSpan() { +export function getCurrentSpan(): Span { return currentSpan(); } diff --git a/js/src/wrappers/shared/scorers.test.ts b/js/src/wrappers/shared/scorers.test.ts index d38cfd405..5d8023b9d 100644 --- a/js/src/wrappers/shared/scorers.test.ts +++ b/js/src/wrappers/shared/scorers.test.ts @@ -1,5 +1,5 @@ -import { afterEach, expect, test, vi } from "vitest"; -import { NOOP_SPAN } from "../../logger"; +import { afterEach, expect, test, vi } from "vite-plus/test"; +import { NOOP_SPAN } from "../../logger/core"; import { runScorers } from "./scorers"; import type { ScorerFunction } from "./types"; diff --git a/js/src/wrappers/shared/scorers.ts b/js/src/wrappers/shared/scorers.ts index d627557e6..dfd526f03 100644 --- a/js/src/wrappers/shared/scorers.ts +++ b/js/src/wrappers/shared/scorers.ts @@ -1,5 +1,5 @@ import { debugLogger } from "../../debug-logger"; -import type { Span } from "../../logger"; +import type { Span } from "../../logger/core"; import type { Score } from "../../../util/score"; import type { ScorerFunction } from "./types"; import type { OneOrMoreScores } from "../../framework"; diff --git a/js/src/wrappers/shared/traced-eval.ts b/js/src/wrappers/shared/traced-eval.ts index c0e878559..9719b3913 100644 --- a/js/src/wrappers/shared/traced-eval.ts +++ b/js/src/wrappers/shared/traced-eval.ts @@ -1,4 +1,5 @@ -import { logError, type Experiment, type Span } from "../../logger"; +import { logError, type Span } from "../../logger/core"; +import { type Experiment } from "../../logger/experiments"; import { SpanTypeAttribute } from "../../../util/index"; import { runScorers } from "./scorers"; import type { ScorerFunction } from "./types"; diff --git a/js/src/wrappers/strands-agent-sdk.test.ts b/js/src/wrappers/strands-agent-sdk.test.ts index 82ab652ed..2f1db702a 100644 --- a/js/src/wrappers/strands-agent-sdk.test.ts +++ b/js/src/wrappers/strands-agent-sdk.test.ts @@ -1,5 +1,5 @@ import { debugLogger } from "../debug-logger"; -import { afterEach, describe, expect, it, vi } from "vitest"; +import { afterEach, describe, expect, it, vi } from "vite-plus/test"; const { invoke } = vi.hoisted(() => ({ invoke: vi.fn( diff --git a/js/src/wrappers/strands-agent-sdk.ts b/js/src/wrappers/strands-agent-sdk.ts index 90b6c28c4..879ce3de9 100644 --- a/js/src/wrappers/strands-agent-sdk.ts +++ b/js/src/wrappers/strands-agent-sdk.ts @@ -1,5 +1,6 @@ +import { registerInstrumentation } from "../instrumentation/registrations/strandsAgentSDK"; import { debugLogger } from "../debug-logger"; -import { strandsAgentSDKChannels } from "../instrumentation/plugins/strands-agent-sdk-interceptors"; +import { strandsAgentSDKChannels } from "../instrumentation/providers/strands-agent-sdk-interceptors"; import type { StrandsAgent, StrandsAgentConstructor, @@ -26,6 +27,7 @@ const WRAPPED_INSTANCE = Symbol.for( * typed instrumentation channels; the Strands plugin owns span lifecycle. */ export function wrapStrandsAgentSDK(sdk: T): T { + registerInstrumentation(); if (!sdk || typeof sdk !== "object") { return sdk; } diff --git a/js/src/wrappers/test_oai_attachments.test.ts b/js/src/wrappers/test_oai_attachments.test.ts index 6fcc0ec72..42c23f191 100644 --- a/js/src/wrappers/test_oai_attachments.test.ts +++ b/js/src/wrappers/test_oai_attachments.test.ts @@ -1,7 +1,7 @@ -import { describe, it, expect, beforeEach } from "vitest"; +import { describe, it, expect, beforeEach } from "vite-plus/test"; import OpenAI from "openai"; import { wrapOpenAI } from "./oai"; -import { Attachment, initLogger } from "../logger"; +import { Attachment, initLogger } from "../logger/core"; const PROJECT_NAME = "test-project-openai-attachment-processing"; const TEST_MODEL = "gpt-4o-mini"; @@ -11,7 +11,6 @@ describe("OpenAI attachment processing", () => { initLogger({ projectName: PROJECT_NAME, apiKey: "test-key", - asyncFlush: false, }); }); diff --git a/js/src/wrappers/typesafe.test.ts b/js/src/wrappers/typesafe.test.ts index 8c8f7c428..1670eb0ed 100644 --- a/js/src/wrappers/typesafe.test.ts +++ b/js/src/wrappers/typesafe.test.ts @@ -7,13 +7,11 @@ import { expect, it, vi, -} from "vitest"; -import { Attachment, _exportsForTestingOnly, initLogger } from "../logger"; +} from "vite-plus/test"; +import { Attachment, initLogger } from "../logger/core"; +import { _exportsForTestingOnly } from "../logger/testing"; import { TypedAttachmentProcessor } from "../typed-attachments"; -import { - configureInstrumentation, - registry, -} from "../instrumentation/registry"; +import { setSpanCustomizers } from "../span-customizer"; import { configureNode } from "../node/config"; import type { TypeSafeAPIPromise, @@ -105,9 +103,7 @@ describe("TypeSafe wrapper", () => { afterEach(() => { _exportsForTestingOnly.clearTestBackgroundLogger(); - registry.disable(); - configureInstrumentation({ spanCustomizers: [] }); - registry.enable(); + setSpanCustomizers([]); vi.restoreAllMocks(); vi.unstubAllEnvs(); }); @@ -216,7 +212,7 @@ describe("TypeSafe wrapper", () => { }); it("logs image references and box answers without reading bytes when capture is disabled", async () => { - vi.stubEnv("BRAINTRUST_CAPTURE_ATTACHMENTS", "false"); + vi.stubEnv("BRAINTRUST_AUTO_CAPTURE_ATTACHMENTS", "false"); const readData = vi.fn(() => { throw new Error("Image bytes must not be observed"); }); @@ -290,7 +286,7 @@ describe("TypeSafe wrapper", () => { it.each(["bytes", "blob"])( "captures %s at every input location and snapshots caller-owned bytes", async (kind) => { - vi.stubEnv("BRAINTRUST_CAPTURE_ATTACHMENTS", "true"); + vi.stubEnv("BRAINTRUST_AUTO_CAPTURE_ATTACHMENTS", "true"); const bytes = new Uint8Array([0x89, 0x50, 0x4e, 0x47, 13, 10, 26, 10]); const originalBytes = bytes.slice(); const image = { @@ -325,7 +321,7 @@ describe("TypeSafe wrapper", () => { it.each(["state", "instructions"])( "uses escaped input-rooted image paths in %s and preserves reordered mixed answers", async (location) => { - vi.stubEnv("BRAINTRUST_CAPTURE_ATTACHMENTS", "true"); + vi.stubEnv("BRAINTRUST_AUTO_CAPTURE_ATTACHMENTS", "true"); const image = { [Symbol.for("@typesafe-ai/sdk/image")]: true, type: "image", @@ -409,7 +405,7 @@ describe("TypeSafe wrapper", () => { const client = wrapTypeSafe({ systemOne: (_request: unknown) => responsePromise({ answers }), }); - vi.stubEnv("BRAINTRUST_CAPTURE_ATTACHMENTS", "false"); + vi.stubEnv("BRAINTRUST_AUTO_CAPTURE_ATTACHMENTS", "false"); await client.systemOne({ state: [ image, @@ -431,7 +427,7 @@ describe("TypeSafe wrapper", () => { it.each(["success", "upload failure", "move", "redact"])( "keeps image paths consistent through %s without restoring customized media", async (mode) => { - vi.stubEnv("BRAINTRUST_CAPTURE_ATTACHMENTS", "true"); + vi.stubEnv("BRAINTRUST_AUTO_CAPTURE_ATTACHMENTS", "true"); const upload = vi .spyOn(Attachment.prototype, "upload") .mockResolvedValue( @@ -440,29 +436,25 @@ describe("TypeSafe wrapper", () => { : { upload_status: "done" }, ); if (mode === "move" || mode === "redact") { - registry.disable(); - configureInstrumentation({ - spanCustomizers: [ - { - onSpanExport(data) { - if (data.input && data.output) { - const input = data.input as any; - const answer = (data.output as any).answers[0]; - if (mode === "move") { - input.state = { moved: input.state.photo }; - answer.image_path = "/state/moved"; - } else { - input.state = "[redacted]"; - delete answer.image_path; - } + setSpanCustomizers([ + { + onSpanExport(data) { + if (data.input && data.output) { + const input = data.input as any; + const answer = (data.output as any).answers[0]; + if (mode === "move") { + input.state = { moved: input.state.photo }; + answer.image_path = "/state/moved"; + } else { + input.state = "[redacted]"; + delete answer.image_path; } - return data; - }, + } + return data; }, - ], - }); + }, + ]); } - registry.enable(); const image = { [Symbol.for("@typesafe-ai/sdk/image")]: true, type: "image", @@ -511,7 +503,7 @@ describe("TypeSafe wrapper", () => { ); it("keeps the span and box answer when image capture fails", async () => { - vi.stubEnv("BRAINTRUST_CAPTURE_ATTACHMENTS", "true"); + vi.stubEnv("BRAINTRUST_AUTO_CAPTURE_ATTACHMENTS", "true"); const image = { [Symbol.for("@typesafe-ai/sdk/image")]: true, get data() { diff --git a/js/src/wrappers/typesafe.ts b/js/src/wrappers/typesafe.ts index 3b639dd32..dcb7c6bc0 100644 --- a/js/src/wrappers/typesafe.ts +++ b/js/src/wrappers/typesafe.ts @@ -1,5 +1,6 @@ +import { registerInstrumentation } from "../instrumentation/registrations/typesafe"; import { debugLogger } from "../debug-logger"; -import { typeSafeChannels } from "../instrumentation/plugins/typesafe-interceptors"; +import { typeSafeChannels } from "../instrumentation/providers/typesafe-interceptors"; import type { TypeSafeClient, TypeSafeSystemOneRequest, @@ -8,6 +9,7 @@ import type { /** Wrap a TypeSafe client so systemOne calls pass through Braintrust tracing. */ export function wrapTypeSafe(client: T): T; export function wrapTypeSafe(client: unknown): unknown { + registerInstrumentation(); if (!isSupportedTypeSafeClient(client)) { debugLogger.notice.warn("Unsupported TypeSafe AI library. Not wrapping."); return client; diff --git a/js/src/wrappers/vitest-evals/reporter.test.ts b/js/src/wrappers/vitest-evals/reporter.test.ts index 7b7385306..b85990ba7 100644 --- a/js/src/wrappers/vitest-evals/reporter.test.ts +++ b/js/src/wrappers/vitest-evals/reporter.test.ts @@ -1,10 +1,15 @@ -import { beforeAll, beforeEach, describe, expect, test, vi } from "vitest"; -import BraintrustVitestEvalsReporter from "./reporter"; import { - _exportsForTestingOnly, - type TestBackgroundLogger, -} from "../../logger"; -import * as logger from "../../logger"; + beforeAll, + beforeEach, + describe, + expect, + test, + vi, +} from "vite-plus/test"; +import BraintrustVitestEvalsReporter from "./reporter"; +import { _exportsForTestingOnly } from "../../logger/testing"; +import { type TestBackgroundLogger } from "../../logger/core"; +import * as logger from "../../logger/experiments"; import { configureNode } from "../../node/config"; configureNode(); @@ -16,7 +21,7 @@ beforeAll(async () => { await _exportsForTestingOnly.simulateLoginForTests(); backgroundLogger = _exportsForTestingOnly.useTestBackgroundLogger(); - vi.spyOn(logger, "initExperiment").mockImplementation( + vi.spyOn(logger, "init").mockImplementation( (projectOrOptions: string | any, options?: any) => { const experimentOptions = typeof projectOrOptions === "string" ? options : projectOrOptions; diff --git a/js/src/wrappers/vitest-evals/reporter.ts b/js/src/wrappers/vitest-evals/reporter.ts index 71ba251e3..1aa7189d0 100644 --- a/js/src/wrappers/vitest-evals/reporter.ts +++ b/js/src/wrappers/vitest-evals/reporter.ts @@ -1,11 +1,7 @@ import type { Reporter, TestCase, TestModule, Vitest } from "vitest/node"; import { SpanTypeAttribute, isObject } from "../../../util"; -import { - initExperiment, - logError, - type Experiment, - type Span, -} from "../../logger"; +import { init, type Experiment } from "../../logger/experiments"; +import { logError, type Span } from "../../logger/core"; import { configureNode } from "../../node/config"; import { summarizeAndFlush } from "../shared/flush"; @@ -202,7 +198,7 @@ export default class BraintrustVitestEvalsReporter implements Reporter { const experimentName = this.options.experimentName ?? `vitest-evals-${new Date().toISOString()}`; - this.experiment = initExperiment({ + this.experiment = init({ ...(projectId ? { projectId } : { project: projectName }), experiment: experimentName, metadata: this.options.metadata, diff --git a/js/src/wrappers/vitest/context-manager.ts b/js/src/wrappers/vitest/context-manager.ts index da25ee17c..aa45c295f 100644 --- a/js/src/wrappers/vitest/context-manager.ts +++ b/js/src/wrappers/vitest/context-manager.ts @@ -1,5 +1,6 @@ -import iso, { type IsoAsyncLocalStorage } from "../../isomorph"; -import type { Dataset, Experiment } from "../../logger"; +import iso, { type IsoAsyncLocalStorage } from "#braintrust-platform"; +import type { Dataset } from "../../logger/datasets"; +import type { Experiment } from "../../logger/experiments"; /** * VitestExperimentContext - State for a single describe block @@ -12,7 +13,7 @@ import type { Dataset, Experiment } from "../../logger"; * - flushPromise/flushResolved: Coordination for experiment flushing */ export interface VitestExperimentContext { - dataset: Dataset | undefined; + dataset: Dataset | undefined; experiment: Experiment; datasetExamples: Map; parent?: VitestExperimentContext; // Link to parent describe @@ -59,7 +60,7 @@ class VitestContextManager { } createChildContext( - dataset: Dataset | undefined, + dataset: Dataset | undefined, experiment: Experiment, ): VitestExperimentContext { const parent = this.getCurrentContext(); diff --git a/js/src/wrappers/vitest/expect-wrapper.ts b/js/src/wrappers/vitest/expect-wrapper.ts index c3207e6dc..e99765b36 100644 --- a/js/src/wrappers/vitest/expect-wrapper.ts +++ b/js/src/wrappers/vitest/expect-wrapper.ts @@ -1,5 +1,5 @@ -import { currentSpan } from "../../logger"; -import type { Span } from "../../logger"; +import { currentSpan } from "../../logger/core"; +import type { Span } from "../../logger/core"; function proxyAssertion( assertion: object, @@ -53,9 +53,9 @@ function proxyAssertion( }); } -export function wrapExpect unknown>( - originalExpect: ExpectType, -): ExpectType { +export function wrapExpect< + ExpectType extends (value: unknown, message?: string) => unknown, +>(originalExpect: ExpectType): ExpectType { const wrapped = function (value: unknown, message?: string) { // pass through unnamed expect if (message === undefined) { diff --git a/js/src/wrappers/vitest/index.ts b/js/src/wrappers/vitest/index.ts index da2fdae12..f0a882e80 100644 --- a/js/src/wrappers/vitest/index.ts +++ b/js/src/wrappers/vitest/index.ts @@ -18,7 +18,7 @@ import type { VitestMethods, BraintrustVitest, WrapperConfig } from "./types"; * * @example Basic Usage * ```typescript - * import * as vitest from "vitest"; + * import * as vitest from "vite-plus/test"; * import { wrapVitest } from 'braintrust'; * * const {test, expect, describe } = wrapVitest( @@ -54,8 +54,9 @@ import type { VitestMethods, BraintrustVitest, WrapperConfig } from "./types"; */ export function wrapVitest< VitestContext = unknown, - ExpectType extends (...args: unknown[]) => unknown = ( - ...args: unknown[] + ExpectType extends (value: unknown, message?: string) => unknown = ( + value: unknown, + message?: string, ) => unknown, >( vitestMethods: VitestMethods, diff --git a/js/src/wrappers/vitest/package.json b/js/src/wrappers/vitest/package.json index e88dd0242..73dfde123 100644 --- a/js/src/wrappers/vitest/package.json +++ b/js/src/wrappers/vitest/package.json @@ -4,11 +4,12 @@ "description": "Test infrastructure for Vitest wrapper", "scripts": { "check:typings": "tsc --noEmit -p tsconfig.json", - "test": "vitest run" + "test": "vp test run" }, "devDependencies": { "@types/node": "^20.10.5", "typescript": "5.4.4", - "vitest": "4.1.11" + "vitest": "catalog:", + "vite-plus": "catalog:" } } diff --git a/js/src/wrappers/vitest/types.ts b/js/src/wrappers/vitest/types.ts index a587417e5..511908cda 100644 --- a/js/src/wrappers/vitest/types.ts +++ b/js/src/wrappers/vitest/types.ts @@ -1,4 +1,4 @@ -import type { Span } from "../../logger"; +import type { Span } from "../../logger/core"; import type { ScorerFunction, ProgressEvent } from "../shared/types"; @@ -89,8 +89,9 @@ export type WrappedDescribe = WithModifiers; export interface VitestMethods< VitestContext = unknown, - ExpectType extends (...args: unknown[]) => unknown = ( - ...args: unknown[] + ExpectType extends (value: unknown, message?: string) => unknown = ( + value: unknown, + message?: string, ) => unknown, > { test: TestFunction; @@ -105,8 +106,9 @@ export interface VitestMethods< export interface BraintrustVitest< VitestContext = unknown, - ExpectType extends (...args: unknown[]) => unknown = ( - ...args: unknown[] + ExpectType extends (value: unknown, message?: string) => unknown = ( + value: unknown, + message?: string, ) => unknown, > { test: WrappedTest; diff --git a/js/src/wrappers/vitest/vite.config.ts b/js/src/wrappers/vitest/vite.config.ts new file mode 100644 index 000000000..3a6974d0d --- /dev/null +++ b/js/src/wrappers/vitest/vite.config.ts @@ -0,0 +1,16 @@ +import { fileURLToPath } from "node:url"; +import { defineConfig } from "vite-plus"; + +export default defineConfig({ + resolve: { + alias: { + "#braintrust-platform": fileURLToPath( + new URL("../../isomorph.ts", import.meta.url), + ), + }, + }, + test: { + hookTimeout: 60_000, + reporters: ["default"], + }, +}); diff --git a/js/src/wrappers/vitest/vitest-wrapper-example.test.ts b/js/src/wrappers/vitest/vitest-wrapper-example.test.ts index c646c0fdf..538d343b7 100644 --- a/js/src/wrappers/vitest/vitest-wrapper-example.test.ts +++ b/js/src/wrappers/vitest/vitest-wrapper-example.test.ts @@ -1,8 +1,8 @@ -import * as vitest from "vitest"; +import * as vitest from "vite-plus/test"; import { configureNode } from "../../node/config"; import { wrapVitest } from "./index"; -import { _exportsForTestingOnly } from "../../logger"; -import * as logger from "../../logger"; +import { _exportsForTestingOnly } from "../../logger/testing"; +import * as experiments from "../../logger/experiments"; configureNode(); @@ -11,7 +11,7 @@ await _exportsForTestingOnly.simulateLoginForTests(); const moduleBackgroundLogger = _exportsForTestingOnly.useTestBackgroundLogger(); vitest.vi - .spyOn(logger, "initExperiment") + .spyOn(experiments, "init") .mockImplementation((projectName: string, options?: any) => { return _exportsForTestingOnly.initTestExperiment( options?.experiment || "test-experiment", diff --git a/js/src/wrappers/vitest/vitest-wrapper-span.test.ts b/js/src/wrappers/vitest/vitest-wrapper-span.test.ts index 47cbcbdbe..b062cfd5d 100644 --- a/js/src/wrappers/vitest/vitest-wrapper-span.test.ts +++ b/js/src/wrappers/vitest/vitest-wrapper-span.test.ts @@ -1,8 +1,8 @@ -import * as vitest from "vitest"; +import * as vitest from "vite-plus/test"; import { configureNode } from "../../node/config"; import { wrapVitest } from "./index"; -import { _exportsForTestingOnly } from "../../logger"; -import * as logger from "../../logger"; +import { _exportsForTestingOnly } from "../../logger/testing"; +import * as experiments from "../../logger/experiments"; configureNode(); @@ -11,7 +11,7 @@ await _exportsForTestingOnly.simulateLoginForTests(); const moduleBackgroundLogger = _exportsForTestingOnly.useTestBackgroundLogger(); vitest.vi - .spyOn(logger, "initExperiment") + .spyOn(experiments, "init") .mockImplementation((projectName: string, options?: any) => { return _exportsForTestingOnly.initTestExperiment( options?.experiment || "test-experiment", diff --git a/js/src/wrappers/vitest/vitest-wrapper.test.ts b/js/src/wrappers/vitest/vitest-wrapper.test.ts index 6154b21b0..36b78a2d0 100644 --- a/js/src/wrappers/vitest/vitest-wrapper.test.ts +++ b/js/src/wrappers/vitest/vitest-wrapper.test.ts @@ -8,16 +8,16 @@ import { afterEach, vi, expectTypeOf, -} from "vitest"; +} from "vite-plus/test"; import { configureNode } from "../../node/config"; import { wrapVitest } from "./index"; import { wrapExpect } from "./expect-wrapper"; -import { - _exportsForTestingOnly, - type TestBackgroundLogger, -} from "../../logger"; +import { _exportsForTestingOnly } from "../../logger/testing"; +import { type TestBackgroundLogger } from "../../logger/core"; import type { BraintrustVitest } from "./types"; -import * as logger from "../../logger"; +import * as experiments from "../../logger/experiments"; +import * as logger from "../../logger/core"; +import * as datasets from "../../logger/datasets"; import { getExperimentContext } from "./wrapper"; import { getVitestContextManager, @@ -32,13 +32,13 @@ _exportsForTestingOnly.setInitialTestState(); await _exportsForTestingOnly.simulateLoginForTests(); _exportsForTestingOnly.useTestBackgroundLogger(); -// ✅ STEP 2: Mock initDataset and initExperiment to avoid network calls -vi.spyOn(logger, "initDataset").mockReturnValue({ +// ✅ STEP 2: Mock initDataset and init to avoid network calls +vi.spyOn(datasets, "initDataset").mockReturnValue({ insert: vi.fn(() => "test-example-id"), } as any); -const initExperimentSpy = vi - .spyOn(logger, "initExperiment") +const initSpy = vi + .spyOn(experiments, "init") .mockImplementation((projectOrOptions: string | any, options?: any) => { const experimentOptions = typeof projectOrOptions === "string" ? options : projectOrOptions; @@ -451,11 +451,11 @@ describe("Project selection", () => { } beforeEach(() => { - initExperimentSpy.mockClear(); + initSpy.mockClear(); _resetContextManager(); }); - test("wrapVitest passes projectId to initExperiment", async () => { + test("wrapVitest passes projectId to init", async () => { const bt = wrapVitest(makeFakeVitestMethods(), { projectId: "project-id-123", displaySummary: false, @@ -465,7 +465,7 @@ describe("Project selection", () => { bt.test("uses project id", async () => undefined); }); - expect(initExperimentSpy).toHaveBeenCalledWith( + expect(initSpy).toHaveBeenCalledWith( expect.objectContaining({ projectId: "project-id-123", experiment: expect.stringMatching(/^project id suite-/), @@ -484,15 +484,12 @@ describe("Project selection", () => { bt.test("uses project id", async () => undefined); }); - expect(initExperimentSpy).toHaveBeenCalledWith( + expect(initSpy).toHaveBeenCalledWith( expect.objectContaining({ projectId: "project-id-123", }), ); - expect(initExperimentSpy).not.toHaveBeenCalledWith( - "project-name", - expect.anything(), - ); + expect(initSpy).not.toHaveBeenCalledWith("project-name", expect.anything()); }); test("wrapVitest preserves projectName behavior when projectId is absent", async () => { @@ -505,9 +502,9 @@ describe("Project selection", () => { bt.test("uses project name", async () => undefined); }); - expect(initExperimentSpy).toHaveBeenCalledWith( - "project-name", + expect(initSpy).toHaveBeenCalledWith( expect.objectContaining({ + project: "project-name", experiment: expect.stringMatching(/^project name suite-/), }), ); @@ -522,9 +519,9 @@ describe("Project selection", () => { bt.test("uses suite name", async () => undefined); }); - expect(initExperimentSpy).toHaveBeenCalledWith( - "fallback suite", + expect(initSpy).toHaveBeenCalledWith( expect.objectContaining({ + project: "fallback suite", experiment: expect.stringMatching(/^fallback suite-/), }), ); diff --git a/js/src/wrappers/vitest/vitest.config.js b/js/src/wrappers/vitest/vitest.config.js deleted file mode 100644 index c6b85f3bf..000000000 --- a/js/src/wrappers/vitest/vitest.config.js +++ /dev/null @@ -1,8 +0,0 @@ -import { defineConfig } from "vitest/config"; - -export default defineConfig({ - test: { - hookTimeout: 60_000, - reporters: ["default"], - }, -}); diff --git a/js/src/wrappers/vitest/wrapper.ts b/js/src/wrappers/vitest/wrapper.ts index b3c7344fa..7ec5e5bf0 100644 --- a/js/src/wrappers/vitest/wrapper.ts +++ b/js/src/wrappers/vitest/wrapper.ts @@ -1,4 +1,4 @@ -import { initExperiment } from "../../logger"; +import { init } from "../../logger/experiments"; import type { TestConfig, TestContext, @@ -215,11 +215,12 @@ export function wrapDescribe( const experimentName = `${suiteName}-${new Date().toISOString()}`; const experiment = config.projectId - ? initExperiment({ + ? init({ projectId: config.projectId, experiment: experimentName, }) - : initExperiment(config.projectName || suiteName, { + : init({ + project: config.projectName || suiteName, experiment: experimentName, }); diff --git a/js/src/wrappers/voyageai.test.ts b/js/src/wrappers/voyageai.test.ts index 323a0bc2f..181ea6e25 100644 --- a/js/src/wrappers/voyageai.test.ts +++ b/js/src/wrappers/voyageai.test.ts @@ -7,8 +7,9 @@ import { expect, it, vi, -} from "vitest"; -import { _exportsForTestingOnly, initLogger } from "../logger"; +} from "vite-plus/test"; +import { _exportsForTestingOnly } from "../logger/testing"; +import { initLogger } from "../logger/core"; import { configureNode } from "../node/config"; import { wrapVoyageAI } from "./voyageai"; diff --git a/js/src/wrappers/voyageai.ts b/js/src/wrappers/voyageai.ts index b36e17a2b..8d111f63e 100644 --- a/js/src/wrappers/voyageai.ts +++ b/js/src/wrappers/voyageai.ts @@ -1,5 +1,6 @@ +import { registerInstrumentation } from "../instrumentation/registrations/voyageai"; import { debugLogger } from "../debug-logger"; -import { voyageAIChannels } from "../instrumentation/plugins/voyageai-interceptors"; +import { voyageAIChannels } from "../instrumentation/providers/voyageai-interceptors"; import type { VoyageAIClient, VoyageAIContextualizedEmbedRequest, @@ -13,6 +14,7 @@ import type { * instrumentation hooks. */ export function wrapVoyageAI(client: T): T { + registerInstrumentation(); if (!isSupportedVoyageAIClient(client)) { debugLogger.notice.warn("Unsupported Voyage AI library. Not wrapping."); return client; diff --git a/js/src/wrappers/workers-ai.test.ts b/js/src/wrappers/workers-ai.test.ts index fd3cbebc7..bc3b1c7cb 100644 --- a/js/src/wrappers/workers-ai.test.ts +++ b/js/src/wrappers/workers-ai.test.ts @@ -1,3 +1,4 @@ +import { resetInstrumentationForTest } from "../instrumentation/test-utils/reset"; import { afterEach, beforeAll, @@ -6,21 +7,14 @@ import { expect, it, vi, -} from "vitest"; -import { - _exportsForTestingOnly, - currentSpan, - initLogger, - traced, -} from "../logger"; +} from "vite-plus/test"; +import { _exportsForTestingOnly } from "../logger/testing"; +import { currentSpan, initLogger, traced } from "../logger/core"; import { configureNode } from "../node/config"; -import { - configureInstrumentation, - registry, -} from "../instrumentation/registry"; +import { configureInstrumentation } from "../instrumentation/registry"; import { runWithAutoInstrumentationSuppressed } from "../instrumentation/auto-instrumentation-suppression"; import { TypedAttachmentProcessor } from "../typed-attachments"; -import { Attachment } from "../logger"; +import { Attachment } from "../logger/core"; import { wrapWorkersAI } from "./workers-ai"; import type { LoggingEvent } from "../../util/index"; @@ -77,9 +71,8 @@ describe("Workers AI binding", () => { }); afterEach(() => { _exportsForTestingOnly.clearTestBackgroundLogger(); - registry.disable(); + resetInstrumentationForTest(); configureInstrumentation({ integrations: { workersAI: true } }); - registry.enable(); vi.restoreAllMocks(); vi.unstubAllEnvs(); }); @@ -393,7 +386,7 @@ describe("Workers AI binding", () => { it.each([false, true])( "defers extraction image conversion and preserves the complete input on upload failure (%s)", async (fail) => { - vi.stubEnv("BRAINTRUST_CAPTURE_ATTACHMENTS", "true"); + vi.stubEnv("BRAINTRUST_AUTO_CAPTURE_ATTACHMENTS", "true"); const upload = vi .spyOn(Attachment.prototype, "upload") .mockResolvedValueOnce({ upload_status: "done" }); @@ -458,9 +451,8 @@ describe("Workers AI binding", () => { await runWithAutoInstrumentationSuppressed(() => ai.run("@cf/cloudflare/clef", input), ); - registry.disable(); + resetInstrumentationForTest(); configureInstrumentation({ integrations: { workersAI: false } }); - registry.enable(); await ai.run("@cf/cloudflare/clef", input); expect(await logger.drain()).toHaveLength(0); }); @@ -526,7 +518,7 @@ describe("Workers AI binding", () => { it.each(["@cf/cloudflare/clef-omni", "@cf/cloudflare/clairic"])( "omits inline media by default for %s", async (model) => { - vi.stubEnv("BRAINTRUST_CAPTURE_ATTACHMENTS", "false"); + vi.stubEnv("BRAINTRUST_AUTO_CAPTURE_ATTACHMENTS", "false"); const params = { ...input, fields: { id: { type: "text" } }, @@ -546,7 +538,7 @@ describe("Workers AI binding", () => { ); it("defers decision media uploads until export and captures all modalities when opted in", async () => { - vi.stubEnv("BRAINTRUST_CAPTURE_ATTACHMENTS", "true"); + vi.stubEnv("BRAINTRUST_AUTO_CAPTURE_ATTACHMENTS", "true"); const upload = vi .spyOn(Attachment.prototype, "upload") .mockResolvedValue({ upload_status: "done" }); diff --git a/js/src/wrappers/workers-ai.ts b/js/src/wrappers/workers-ai.ts index 1b4583f45..08fcb6dd2 100644 --- a/js/src/wrappers/workers-ai.ts +++ b/js/src/wrappers/workers-ai.ts @@ -1,4 +1,5 @@ -import { workersAIChannels } from "../instrumentation/plugins/workers-ai-interceptors"; +import { registerInstrumentation } from "../instrumentation/registrations/workersAI"; +import { workersAIChannels } from "../instrumentation/providers/workers-ai-interceptors"; import type { WorkersAI } from "../vendor-sdk-types/workers-ai"; const wrappedBindings = new WeakMap(); @@ -18,6 +19,7 @@ const wrappedBindings = new WeakMap(); * ``` */ export function wrapWorkersAI(binding: T): T { + registerInstrumentation(); if ( !binding || typeof binding !== "object" || diff --git a/js/src/zod/utils.ts b/js/src/zod/utils.ts index 2a36ef498..c67c1eee8 100644 --- a/js/src/zod/utils.ts +++ b/js/src/zod/utils.ts @@ -15,7 +15,9 @@ function isZodV4(zodObject: ZodSchema): zodObject is z4.ZodType { ); } -export function zodToJsonSchema(schema: ZodSchema) { +export function zodToJsonSchema( + schema: ZodSchema, +): ReturnType | ReturnType { if (isZodV4(schema)) { return z4.toJSONSchema(schema, { target: "draft-7", diff --git a/js/src/zod/zod-serialization-test-shared.ts b/js/src/zod/zod-serialization-test-shared.ts deleted file mode 100644 index a78f4b708..000000000 --- a/js/src/zod/zod-serialization-test-shared.ts +++ /dev/null @@ -1,70 +0,0 @@ -/** - * Shared test expectations for zod serialization tests - * - * This module contains expected JSON Schema outputs used by both v3 and v4 - * serialization tests to ensure consistent behavior across versions. - */ - -/** - * Expected JSON schema for string parameter with description and default - */ - -export const EXPECTED_STRING_SCHEMA = { - type: "string", - description: "The instructions for the agent", - default: "You are a helpful assistant.", - $schema: "http://json-schema.org/draft-07/schema#", -}; - -/** - * Expected JSON schema for number parameter with constraints - */ - -export const EXPECTED_NUMBER_SCHEMA = { - type: "number", - minimum: 0, - maximum: 2, - description: "Temperature for LLM", - default: 0.7, - $schema: "http://json-schema.org/draft-07/schema#", -}; - -/** - * Expected JSON schema for object parameter - */ - -export const EXPECTED_OBJECT_SCHEMA = { - type: "object", - properties: { - model: { type: "string" }, - maxTokens: { type: "number" }, - }, - additionalProperties: false, - required: ["model"], - description: "Configuration object", - $schema: "http://json-schema.org/draft-07/schema#", -}; - -/** - * Expected JSON schema for enum parameter - */ - -export const EXPECTED_ENUM_SCHEMA = { - type: "string", - enum: ["fast", "accurate", "balanced"], - description: "Processing mode", - default: "balanced", - $schema: "http://json-schema.org/draft-07/schema#", -}; - -/** - * Expected JSON schema for array parameter - */ - -export const EXPECTED_ARRAY_SCHEMA = { - type: "array", - items: { type: "string" }, - description: "Tags for filtering", - default: ["default"], - $schema: "http://json-schema.org/draft-07/schema#", -}; diff --git a/js/src/zod/zod-v3-serialization.test.ts b/js/src/zod/zod-v3-serialization.test.ts deleted file mode 100644 index c4d1097d2..000000000 --- a/js/src/zod/zod-v3-serialization.test.ts +++ /dev/null @@ -1,234 +0,0 @@ -/** - * Tests for zod v3 compatibility with serializeEvalParametersToStaticParametersSchema - * - * This file tests serializeEvalParametersToStaticParametersSchema specifically with zod v3. - * See zod-v4-serialization.test.ts for zod v4 specific tests. - */ - -import { test, describe, beforeEach, expect } from "vitest"; -import * as zodModule from "zod"; -import { z } from "zod"; -import { - EXPECTED_STRING_SCHEMA, - EXPECTED_NUMBER_SCHEMA, - EXPECTED_OBJECT_SCHEMA, - EXPECTED_ENUM_SCHEMA, - EXPECTED_ARRAY_SCHEMA, -} from "./zod-serialization-test-shared"; -import { - serializeEvalParametersToStaticParametersSchema, - serializeRemoteEvalParametersContainer, -} from "../framework2"; -import { - serializedParametersContainerSchema, - evaluatorDefinitionSchema, -} from "../../dev/types"; -// Detect which zod version is installed by checking for v4-specific properties -function getInstalledZodVersion(): 3 | 4 { - const testSchema = zodModule.z.string(); - // eslint-disable-next-line @typescript-eslint/no-explicit-any - return "_zod" in (testSchema as any) ? 4 : 3; -} - -describe("serializeEvalParametersToStaticParametersSchema with Zod v3", () => { - beforeEach(() => { - const version = getInstalledZodVersion(); - expect(version).toBe(3); - }); - - test("string schema serializes correctly", () => { - const parameters = { - instructions: z - .string() - .describe("The instructions for the agent") - .default("You are a helpful assistant."), - }; - - const result = serializeEvalParametersToStaticParametersSchema(parameters); - console.log(result); - expect(result.instructions).toBeDefined(); - expect(result.instructions.type).toBe("data"); - expect( - result.instructions.type === "data" && result.instructions.schema, - ).toStrictEqual(EXPECTED_STRING_SCHEMA); - expect(result.instructions.description).toBe( - "The instructions for the agent", - ); - expect(result.instructions.default).toBe("You are a helpful assistant."); - }); - - test("number schema serializes correctly", () => { - const parameters = { - temperature: z - .number() - .min(0) - .max(2) - .describe("Temperature for LLM") - .default(0.7), - }; - - const result = serializeEvalParametersToStaticParametersSchema(parameters); - - result.temperature.type; - - expect(result.temperature.type).toBe("data"); - expect( - result.temperature.type === "data" && result.temperature.schema, - ).toStrictEqual(EXPECTED_NUMBER_SCHEMA); - expect(result.temperature.description).toBe("Temperature for LLM"); - expect(result.temperature.default).toBe(0.7); - }); - - test("object schema serializes correctly", () => { - const parameters = { - config: z - .object({ - model: z.string(), - maxTokens: z.number().optional(), - }) - .describe("Configuration object"), - }; - - const result = serializeEvalParametersToStaticParametersSchema(parameters); - - expect(result.config.type).toBe("data"); - expect(result.config.type === "data" && result.config.schema).toStrictEqual( - EXPECTED_OBJECT_SCHEMA, - ); - expect(result.config.description).toBe("Configuration object"); - }); - - test("enum schema serializes correctly", () => { - const parameters = { - mode: z - .enum(["fast", "accurate", "balanced"]) - .describe("Processing mode") - .default("balanced"), - }; - - const result = serializeEvalParametersToStaticParametersSchema(parameters); - - expect(result.mode.type).toBe("data"); - expect(result.mode.type === "data" && result.mode.schema).toStrictEqual( - EXPECTED_ENUM_SCHEMA, - ); - expect(result.mode.description).toBe("Processing mode"); - expect(result.mode.default).toBe("balanced"); - }); - - test("array schema serializes correctly", () => { - const parameters = { - tags: z - .array(z.string()) - .describe("Tags for filtering") - .default(["default"]), - }; - - const result = serializeEvalParametersToStaticParametersSchema(parameters); - - expect(result.tags.type).toBe("data"); - expect(result.tags.type === "data" && result.tags.schema).toStrictEqual( - EXPECTED_ARRAY_SCHEMA, - ); - expect(result.tags.description).toBe("Tags for filtering"); - expect(result.tags.default).toEqual(["default"]); - }); - - test("model parameter serializes correctly", () => { - const parameters = { - model: { - type: "model" as const, - description: "Model to use", - default: "gpt-5-mini", - }, - }; - - const result = serializeEvalParametersToStaticParametersSchema(parameters); - - expect(result.model.type).toBe("model"); - expect(result.model.description).toBe("Model to use"); - expect(result.model.default).toBe("gpt-5-mini"); - }); -}); - -describe("serializeRemoteEvalParametersContainer with Zod v3", () => { - beforeEach(() => { - const version = getInstalledZodVersion(); - expect(version).toBe(3); - }); - - test("local parameters are serialized with legacyParameters type", () => { - const parameters = { - instructions: z - .string() - .describe("The instructions for the agent") - .default("You are a helpful assistant."), - }; - - const result = serializeRemoteEvalParametersContainer(parameters); - - expect(result.type).toBe("braintrust.staticParameters"); - expect(result.source).toBeNull(); - if (result.type === "braintrust.staticParameters") { - expect(result.schema.instructions).toBeDefined(); - expect(result.schema.instructions.type).toBe("data"); - } - }); -}); - -describe("serializedParametersContainerSchema parsing", () => { - const schema = { - model: { - type: "model" as const, - description: "Model to use", - default: "gpt-5-mini", - }, - }; - - test("parses braintrust.staticParameters with source: null", () => { - const result = serializedParametersContainerSchema.safeParse({ - type: "braintrust.staticParameters", - schema, - source: null, - }); - expect(result.success).toBe(true); - }); - - test("parses braintrust.staticParameters with source omitted", () => { - const result = serializedParametersContainerSchema.safeParse({ - type: "braintrust.staticParameters", - schema, - }); - expect(result.success).toBe(true); - }); -}); - -describe("evaluatorDefinitionSchema parameters nullish", () => { - test("allows parameters: null", () => { - const result = evaluatorDefinitionSchema.safeParse({ - parameters: null, - }); - expect(result.success).toBe(true); - if (result.success) { - expect(result.data.parameters).toBeNull(); - } - }); - - test("allows parameters: undefined", () => { - const result = evaluatorDefinitionSchema.safeParse({ - parameters: undefined, - }); - expect(result.success).toBe(true); - if (result.success) { - expect(result.data.parameters).toBeUndefined(); - } - }); - - test("allows parameters omitted entirely", () => { - const result = evaluatorDefinitionSchema.safeParse({}); - expect(result.success).toBe(true); - if (result.success) { - expect(result.data.parameters).toBeUndefined(); - } - }); -}); diff --git a/js/src/zod/zod-v4-serialization.test.ts b/js/src/zod/zod-v4-serialization.test.ts deleted file mode 100644 index 9ba907e01..000000000 --- a/js/src/zod/zod-v4-serialization.test.ts +++ /dev/null @@ -1,141 +0,0 @@ -/** - * Tests for zod v4 compatibility with serializeEvalParametersToStaticParametersSchema - * - * This file tests makeEvalParametersSchema specifically with zod v4. - * See zod-v3-serialization.test.ts for zod v3 specific tests. - */ - -import { test, describe, beforeEach, expect } from "vitest"; -import * as zodModule from "zod"; -import { z } from "zod"; -import { - EXPECTED_STRING_SCHEMA, - EXPECTED_NUMBER_SCHEMA, - EXPECTED_OBJECT_SCHEMA, - EXPECTED_ENUM_SCHEMA, - EXPECTED_ARRAY_SCHEMA, -} from "./zod-serialization-test-shared"; -import { serializeEvalParametersToStaticParametersSchema } from "../framework2"; -// Detect which zod version is installed by checking for v4-specific properties -function getInstalledZodVersion(): 3 | 4 { - const testSchema = zodModule.z.string(); - // eslint-disable-next-line @typescript-eslint/no-explicit-any - return "_zod" in (testSchema as any) ? 4 : 3; -} -describe("makeEvalParametersSchema with Zod v4", () => { - beforeEach(() => { - const version = getInstalledZodVersion(); - expect(version).toBe(4); - }); - - test("string schema serializes correctly", () => { - const parameters = { - instructions: z - .string() - .describe("The instructions for the agent") - .default("You are a helpful assistant."), - }; - - const result = serializeEvalParametersToStaticParametersSchema(parameters); - expect(result.instructions).toBeDefined(); - expect(result.instructions.type).toBe("data"); - expect( - result.instructions.type === "data" && result.instructions.schema, - ).toStrictEqual(EXPECTED_STRING_SCHEMA); - expect(result.instructions.description).toBe( - "The instructions for the agent", - ); - expect(result.instructions.default).toBe("You are a helpful assistant."); - }); - - test("number schema serializes correctly", () => { - const parameters = { - temperature: z - .number() - .min(0) - .max(2) - .describe("Temperature for LLM") - .default(0.7), - }; - - const result = serializeEvalParametersToStaticParametersSchema(parameters); - - expect(result.temperature.type).toBe("data"); - expect( - result.temperature.type === "data" && result.temperature.schema, - ).toStrictEqual(EXPECTED_NUMBER_SCHEMA); - expect(result.temperature.description).toBe("Temperature for LLM"); - expect(result.temperature.default).toBe(0.7); - }); - - test("object schema serializes correctly", () => { - const parameters = { - config: z - .object({ - model: z.string(), - maxTokens: z.number().optional(), - }) - .describe("Configuration object"), - }; - - const result = serializeEvalParametersToStaticParametersSchema(parameters); - - expect(result.config.type).toBe("data"); - expect(result.config.type === "data" && result.config.schema).toStrictEqual( - EXPECTED_OBJECT_SCHEMA, - ); - expect(result.config.description).toBe("Configuration object"); - }); - - test("enum schema serializes correctly", () => { - const parameters = { - mode: z - .enum(["fast", "accurate", "balanced"]) - .describe("Processing mode") - .default("balanced"), - }; - - const result = serializeEvalParametersToStaticParametersSchema(parameters); - - expect(result.mode.type).toBe("data"); - expect(result.mode.type === "data" && result.mode.schema).toStrictEqual( - EXPECTED_ENUM_SCHEMA, - ); - expect(result.mode.description).toBe("Processing mode"); - expect(result.mode.default).toBe("balanced"); - }); - - test("array schema serializes correctly", () => { - const parameters = { - tags: z - .array(z.string()) - .describe("Tags for filtering") - .default(["default"]), - }; - - const result = serializeEvalParametersToStaticParametersSchema(parameters); - - expect(result.tags.type).toBe("data"); - expect(result.tags.type === "data" && result.tags.schema).toStrictEqual( - EXPECTED_ARRAY_SCHEMA, - ); - expect(result.tags.description).toBe("Tags for filtering"); - expect(result.tags.default).toEqual(["default"]); - }); - - test("model parameter serializes correctly", () => { - const parameters = { - model: { - type: "model" as const, - description: "Model to use", - default: "gpt-5-mini", - }, - }; - - const result = serializeEvalParametersToStaticParametersSchema(parameters); - - expect(result.model.type).toBe("model"); - expect(result.model.description).toBe("Model to use"); - expect(result.model.default).toBe("gpt-5-mini"); - }); -}); diff --git a/js/tests/api-compatibility/api-compatibility.test.ts b/js/tests/api-compatibility/api-compatibility.test.ts index d3cf7eb47..6aed1f5ae 100644 --- a/js/tests/api-compatibility/api-compatibility.test.ts +++ b/js/tests/api-compatibility/api-compatibility.test.ts @@ -1,11 +1,10 @@ -import { describe, test, expect, beforeAll, afterAll } from "vitest"; +import { describe, test, expect, beforeAll, afterAll } from "vite-plus/test"; import { execSync } from "child_process"; import * as fs from "fs"; import * as path from "path"; import * as os from "os"; import * as ts from "typescript"; import * as tar from "tar"; -import type { Options } from "tsup"; /** * API Compatibility Test @@ -20,7 +19,7 @@ import type { Options } from "tsup"; * ## How It Works * * 1. Downloads the latest published version from npm - * 2. Extracts the .d.ts files for each entrypoint (main, browser, dev, util) + * 2. Extracts the .d.ts files for each built entrypoint * 3. Parses both published and current .d.ts files using TypeScript Compiler API * 4. Compares exported symbols (functions, classes, interfaces, types, etc.) * 5. Fails if breaking changes are detected in non-major version bumps @@ -47,110 +46,46 @@ import type { Options } from "tsup"; * and handles complex TypeScript syntax correctly. */ -/** - * Extracts entrypoints from tsup.config.ts - * Reads the config and identifies all builds that generate .d.ts files - */ -function getEntrypointsFromTsupConfig( - config: Options | Options[], -): Array<{ name: string; typesPath: string }> { - const configs = Array.isArray(config) ? config : [config]; - const entrypoints: Array<{ name: string; typesPath: string }> = []; - - for (const cfg of configs) { - // Skip configs without dts enabled - if (!cfg.dts) continue; - - const entry = cfg.entry; - const outDir = cfg.outDir || "dist"; - - if (Array.isArray(entry)) { - for (const entryFile of entry) { - const name = getEntrypointName(entryFile, outDir); - const typesPath = getTypesPath(entryFile, outDir); - entrypoints.push({ name, typesPath }); - } - } else if (typeof entry === "object") { - // entry is a record like { main: 'src/index.ts' } - for (const [key, entryFile] of Object.entries(entry)) { - const name = key; - const typesPath = getTypesPath(String(entryFile), outDir); - entrypoints.push({ name, typesPath }); +interface Entrypoint { + name: string; + typesPath: string; + runtimePath: string; +} + +/** Read paths from each package's own manifest, since layouts change between releases. */ +function getEntrypointsFromPackageExports(packageRoot: string): Entrypoint[] { + const pkg = JSON.parse( + fs.readFileSync(path.join(packageRoot, "package.json"), "utf8"), + ); + function target( + value: unknown, + condition: "types" | "runtime", + ): string | undefined { + if (typeof value === "string") return value; + if (!value || typeof value !== "object") return undefined; + const conditions = value as Record; + for (const key of condition === "types" + ? ["types", "node", "import", "default"] + : ["node", "import", "default"]) { + if (conditions[key] !== undefined) { + const resolved = target(conditions[key], condition); + if (resolved) return resolved; } } + return undefined; } - - // Separate CJS and ESM builds can emit the same declaration entrypoint. - return entrypoints.filter( - (entrypoint, index) => - entrypoints.findIndex( - (other) => - other.name === entrypoint.name && - other.typesPath === entrypoint.typesPath, - ) === index, - ); -} - -describe("getEntrypointsFromTsupConfig", () => { - test("deduplicates formats while preserving distinct entrypoints", () => { - expect( - getEntrypointsFromTsupConfig([ - { - entry: { browser: "src/browser/index.ts" }, - format: ["cjs"], - dts: true, - }, - { - entry: { browser: "src/browser/index.ts" }, - format: ["esm"], - dts: true, - }, - { - entry: { browser: "src/browser/index.ts" }, - outDir: "other/dist", - dts: true, - }, - { - entry: { workerd: "src/workerd/index.ts" }, - dts: true, - }, - ]), - ).toEqual([ - { name: "browser", typesPath: "dist/index.d.ts" }, - { name: "browser", typesPath: "other/dist/index.d.ts" }, - { name: "workerd", typesPath: "dist/index.d.ts" }, - ]); + return Object.entries(pkg.exports).flatMap(([name, entry]) => { + const typesPath = target(entry, "types"); + const runtimePath = target(entry, "runtime"); + if (!typesPath || !/\.d\.[cm]?ts$/.test(typesPath) || !runtimePath) + return []; + return [ + { name: name === "." ? "main" : name.slice(2), typesPath, runtimePath }, + ]; }); -}); - -/** - * Determines the entrypoint name from the entry file path - */ -function getEntrypointName(entryFile: string, outDir: string): string { - const basename = path.basename(entryFile, path.extname(entryFile)); - - // Map common patterns to friendly names - if (entryFile.includes("src/node/index.ts")) return "main"; - if (entryFile.includes("src/browser/index.ts")) return "browser"; - if (entryFile.includes("src/edge-light/index.ts")) return "edge-light"; - if (entryFile.includes("src/workerd/index.ts")) return "workerd"; - if (entryFile.includes("dev/index.ts")) return "dev"; - if (entryFile.includes("util/index.ts")) return "util"; - - // Default to basename - return basename; } -/** - * Constructs the .d.ts path from entry file and output directory - */ -function getTypesPath(entryFile: string, outDir: string): string { - const basename = path.basename(entryFile, path.extname(entryFile)); - return path.join(outDir, `${basename}.d.ts`); -} - -// Will be populated in beforeAll -let ENTRYPOINTS: Array<{ name: string; typesPath: string }> = []; +let ENTRYPOINTS: Entrypoint[] = []; interface VersionInfo { major: number; @@ -186,150 +121,80 @@ function getVersionBumpType( return "none"; } -/** - * Extracts exports and their signatures from a .d.ts file. - * For bulk exports (export { A, B, C }), looks up the actual declaration - * of each symbol to get its true signature, rather than using the export statement. - */ -function extractExportsFromDts(filePath: string): Map { - const exports = new Map(); - - if (!fs.existsSync(filePath)) { - return exports; +function getChangesetBumpType( + packageName: string, +): "major" | "minor" | "patch" | null { + const changesetDir = path.join(__dirname, "..", "..", "..", ".changeset"); + if (!fs.existsSync(changesetDir)) { + return null; } - const sourceText = fs.readFileSync(filePath, "utf-8"); - const sourceFile = ts.createSourceFile( - filePath, - sourceText, - ts.ScriptTarget.Latest, - true, + const bumpPriority = { patch: 1, minor: 2, major: 3 } as const; + let highestBump: "major" | "minor" | "patch" | null = null; + const escapedPackageName = packageName.replace(/[.*+?^${}()|[\]\\]/g, "\\$&"); + const packageBumpPattern = new RegExp( + `^\\s*["']?${escapedPackageName}["']?\\s*:\\s*(major|minor|patch)\\s*$`, + "m", ); - // First pass: collect all declarations by name - const declarations = new Map(); - - function collectDeclarations(node: ts.Node) { - let name: string | undefined; - - if (ts.isFunctionDeclaration(node) && node.name) { - name = node.name.text; - } else if (ts.isClassDeclaration(node) && node.name) { - name = node.name.text; - } else if (ts.isInterfaceDeclaration(node)) { - name = node.name.text; - } else if (ts.isTypeAliasDeclaration(node)) { - name = node.name.text; - } else if (ts.isEnumDeclaration(node)) { - name = node.name.text; - } else if (ts.isVariableStatement(node)) { - node.declarationList.declarations.forEach((decl) => { - if (ts.isIdentifier(decl.name)) { - declarations.set(decl.name.text, node); - } - }); - } else if (ts.isModuleDeclaration(node) && node.name) { - name = node.name.text; - } - - if (name) { - declarations.set(name, node); - } - - ts.forEachChild(node, collectDeclarations); - } - - collectDeclarations(sourceFile); - - // Helper to check if a node is inside a namespace - function isInsideNamespace(node: ts.Node): boolean { - let parent = node.parent; - while (parent) { - if (ts.isModuleDeclaration(parent)) { - return true; - } - parent = parent.parent; - } - return false; - } - - // Second pass: process exports - function visit(node: ts.Node) { - // Skip exports inside namespaces - they're not top-level exports - if (isInsideNamespace(node)) { - ts.forEachChild(node, visit); - return; + for (const file of fs.readdirSync(changesetDir)) { + if (!file.endsWith(".md")) { + continue; } - // Handle: export { foo, bar } or export { foo, bar } from './module' - if (ts.isExportDeclaration(node)) { - if (node.exportClause && ts.isNamedExports(node.exportClause)) { - node.exportClause.elements.forEach((element) => { - const name = element.name.text; - const declaration = declarations.get(name); - - exports.set(name, { - name, - kind: declaration ? getNodeKind(declaration) : "re-export", - // Use the actual declaration's signature if found, otherwise just the name - signature: declaration - ? declaration.getText(sourceFile) - : `export { ${name} }`, - }); - }); - } - } else if ( - ts.canHaveModifiers(node) && - ts.getModifiers(node)?.some((m) => m.kind === ts.SyntaxKind.ExportKeyword) + const contents = fs.readFileSync(path.join(changesetDir, file), "utf8"); + const frontmatter = /^---\r?\n([\s\S]*?)\r?\n---/.exec(contents)?.[1]; + const bump = frontmatter?.match(packageBumpPattern)?.[1] as + | "major" + | "minor" + | "patch" + | undefined; + if ( + bump && + (!highestBump || bumpPriority[bump] > bumpPriority[highestBump]) ) { - // Handle direct exports with 'export' keyword - let name: string | undefined; - let kind: string = "unknown"; - const signature: string = node.getText(sourceFile); - - if (ts.isFunctionDeclaration(node) && node.name) { - name = node.name.text; - kind = "function"; - } else if (ts.isClassDeclaration(node) && node.name) { - name = node.name.text; - kind = "class"; - } else if (ts.isInterfaceDeclaration(node)) { - name = node.name.text; - kind = "interface"; - } else if (ts.isTypeAliasDeclaration(node)) { - name = node.name.text; - kind = "type"; - } else if (ts.isEnumDeclaration(node)) { - name = node.name.text; - kind = "enum"; - } else if (ts.isVariableStatement(node)) { - // Handle: export const foo = ... - node.declarationList.declarations.forEach((decl) => { - if (ts.isIdentifier(decl.name)) { - const varName = decl.name.text; - exports.set(varName, { - name: varName, - kind: "variable", - signature: node.getText(sourceFile), - }); - } - }); - return; // Don't add to exports again below - } else if (ts.isModuleDeclaration(node) && node.name) { - name = node.name.text; - kind = "namespace"; - } - - if (name) { - exports.set(name, { name, kind, signature }); - } + highestBump = bump; } - - ts.forEachChild(node, visit); } - visit(sourceFile); - return exports; + return highestBump; +} + +/** + * Extracts exports and their signatures from a .d.ts file. + * For bulk exports (export { A, B, C }), looks up the actual declaration + * of each symbol to get its true signature, rather than using the export statement. + */ +function extractExportsFromDts(filePath: string): Map { + const program = ts.createProgram([filePath], { + target: ts.ScriptTarget.ES2022, + module: ts.ModuleKind.ESNext, + moduleResolution: ts.ModuleResolutionKind.Bundler, + skipLibCheck: true, + }); + const checker = program.getTypeChecker(); + const source = program.getSourceFile(filePath); + if (!source) throw new Error(`Missing declarations: ${filePath}`); + const module = checker.getSymbolAtLocation(source); + if (!module) throw new Error(`No module exports in ${filePath}`); + return new Map( + checker.getExportsOfModule(module).map((exported) => { + const symbol = + exported.flags & ts.SymbolFlags.Alias + ? checker.getAliasedSymbol(exported) + : exported; + const declarations = symbol.declarations ?? []; + const declaration = declarations[0]; + return [ + exported.name, + { + name: exported.name, + kind: declaration ? getNodeKind(declaration) : "re-export", + signature: declarations.map((node) => node.getText()).join("\n"), + }, + ]; + }), + ); } function getNodeKind(node: ts.Node): string { @@ -338,7 +203,8 @@ function getNodeKind(node: ts.Node): string { if (ts.isInterfaceDeclaration(node)) return "interface"; if (ts.isTypeAliasDeclaration(node)) return "type"; if (ts.isEnumDeclaration(node)) return "enum"; - if (ts.isVariableStatement(node)) return "variable"; + if (ts.isVariableStatement(node) || ts.isVariableDeclaration(node)) + return "variable"; if (ts.isModuleDeclaration(node)) return "namespace"; return "unknown"; } @@ -346,26 +212,22 @@ function getNodeKind(node: ts.Node): string { /** * Extracts actual runtime exports from a JavaScript module file */ -async function extractRuntimeExports(mjsPath: string): Promise> { - const exports = new Set(); - - if (!fs.existsSync(mjsPath)) { - return exports; - } - - try { - // Dynamically import the module - const module = await import(mjsPath); - - // Get all exported names - for (const key of Object.keys(module)) { - exports.add(key); - } - } catch (error) { - console.warn(`Failed to load runtime exports from ${mjsPath}:`, error); - } - - return exports; +async function extractRuntimeExports(modulePath: string): Promise> { + // Inspect ESM exports without executing setup entrypoints or test-runner integrations. + const program = ts.createProgram([modulePath], { + allowJs: true, + module: ts.ModuleKind.ESNext, + moduleResolution: ts.ModuleResolutionKind.Bundler, + target: ts.ScriptTarget.ES2022, + }); + const source = program.getSourceFile(modulePath); + if (!source) throw new Error(`Missing runtime entrypoint: ${modulePath}`); + const checker = program.getTypeChecker(); + const module = checker.getSymbolAtLocation(source); + if (!module) throw new Error(`No runtime module in ${modulePath}`); + return new Set( + checker.getExportsOfModule(module).map((symbol) => symbol.name), + ); } /** @@ -2290,8 +2152,8 @@ describe("isUnionTypeWidening", () => { describe("areInterfaceSignaturesCompatible", () => { test("should allow adding optional fields to interface", () => { - const oldInterface = `export interface LogOptions { asyncFlush?: IsAsyncFlush; computeMetadataArgs?: Record; }`; - const newInterface = `export interface LogOptions { asyncFlush?: IsAsyncFlush; computeMetadataArgs?: Record; linkArgs?: LinkArgs; }`; + const oldInterface = `export interface FeatureOptions { enabled?: Enabled; metadata?: Record; }`; + const newInterface = `export interface FeatureOptions { enabled?: Enabled; metadata?: Record; link?: string; }`; const result = areInterfaceSignaturesCompatible(oldInterface, newInterface); expect(result).toBe(true); @@ -2306,40 +2168,40 @@ describe("areInterfaceSignaturesCompatible", () => { }); test("should reject removing fields from interface", () => { - const oldInterface = `export interface LogOptions { asyncFlush?: IsAsyncFlush; computeMetadataArgs?: Record; }`; - const newInterface = `export interface LogOptions { asyncFlush?: IsAsyncFlush; }`; + const oldInterface = `export interface FeatureOptions { enabled?: Enabled; metadata?: Record; }`; + const newInterface = `export interface FeatureOptions { enabled?: Enabled; }`; const result = areInterfaceSignaturesCompatible(oldInterface, newInterface); expect(result).toBe(false); }); test("should reject adding required fields to interface", () => { - const oldInterface = `export interface LogOptions { asyncFlush?: IsAsyncFlush; }`; - const newInterface = `export interface LogOptions { asyncFlush?: IsAsyncFlush; requiredField: string; }`; + const oldInterface = `export interface FeatureOptions { enabled?: Enabled; }`; + const newInterface = `export interface FeatureOptions { enabled?: Enabled; requiredField: string; }`; const result = areInterfaceSignaturesCompatible(oldInterface, newInterface); expect(result).toBe(false); }); test("should reject changing field types in interface", () => { - const oldInterface = `export interface LogOptions { asyncFlush?: IsAsyncFlush; computeMetadataArgs?: Record; }`; - const newInterface = `export interface LogOptions { asyncFlush?: IsAsyncFlush; computeMetadataArgs?: string; }`; + const oldInterface = `export interface FeatureOptions { enabled?: Enabled; metadata?: Record; }`; + const newInterface = `export interface FeatureOptions { enabled?: Enabled; metadata?: string; }`; const result = areInterfaceSignaturesCompatible(oldInterface, newInterface); expect(result).toBe(false); }); test("should allow making required field optional", () => { - const oldInterface = `export interface LogOptions { asyncFlush: IsAsyncFlush; }`; - const newInterface = `export interface LogOptions { asyncFlush?: IsAsyncFlush; }`; + const oldInterface = `export interface FeatureOptions { enabled: Enabled; }`; + const newInterface = `export interface FeatureOptions { enabled?: Enabled; }`; const result = areInterfaceSignaturesCompatible(oldInterface, newInterface); expect(result).toBe(true); }); test("should reject making optional field required", () => { - const oldInterface = `export interface LogOptions { asyncFlush?: IsAsyncFlush; }`; - const newInterface = `export interface LogOptions { asyncFlush: IsAsyncFlush; }`; + const oldInterface = `export interface FeatureOptions { enabled?: Enabled; }`; + const newInterface = `export interface FeatureOptions { enabled: Enabled; }`; const result = areInterfaceSignaturesCompatible(oldInterface, newInterface); expect(result).toBe(false); @@ -2644,8 +2506,8 @@ describe("areFunctionSignaturesCompatible", () => { test("should allow widening a field inside an options-object parameter", () => { // Real-world case: getSpanParentObject's options param widens its nested // `parent?` field from `string` to `string | PropagationContext`. - const oldFn = `export function getSpanParentObject(options?: AsyncFlushArg & OptionalStateArg & { parent?: string; }): Span`; - const newFn = `export function getSpanParentObject(options?: AsyncFlushArg & OptionalStateArg & { parent?: string | PropagationContext; }): Span`; + const oldFn = `export function getSpanParentObject(options?: ModeArg & OptionalStateArg & { parent?: string; }): Span`; + const newFn = `export function getSpanParentObject(options?: ModeArg & OptionalStateArg & { parent?: string | PropagationContext; }): Span`; expect(areFunctionSignaturesCompatible(oldFn, newFn)).toBe(true); }); @@ -2811,44 +2673,20 @@ describe("areConstArraySignaturesCompatible", () => { * Since dist/ files aren't in git, we need to build main first in CI. * This checks if a pre-built baseline exists from CI, otherwise returns null. */ -async function getMainBranchExports(entrypoint: { - name: string; - typesPath: string; -}): Promise | null> { - try { - // Check if we're in CI and have a baseline directory - const baselineDir = - process.env.BASELINE_DIR || path.join(os.tmpdir(), "braintrust-baseline"); - const baselinePath = path.join(baselineDir, "js", entrypoint.typesPath); - - if (fs.existsSync(baselinePath)) { - // Baseline exists (built by CI) - console.log(`${entrypoint.name}: Using baseline from ${baselinePath}`); - const exports = extractExportsFromDts(baselinePath); - return exports; - } - - // Fallback: try to read from git (won't work for .d.ts but good for testing) - const filePath = `js/${entrypoint.typesPath}`; - const content = execSync(`git show origin/main:${filePath}`, { - encoding: "utf-8", - stdio: ["pipe", "pipe", "ignore"], // Suppress stderr - }); - - // Parse the content as if it were a file - const tempFile = path.join( - os.tmpdir(), - `main-${entrypoint.name}-${Date.now()}.d.ts`, - ); - fs.writeFileSync(tempFile, content); - const exports = extractExportsFromDts(tempFile); - fs.unlinkSync(tempFile); - - return exports; - } catch (error) { - // Baseline not available - will fall back to direct comparison - return null; - } +async function getMainBranchExports( + entrypoint: Entrypoint, +): Promise | null> { + const baselineRoot = path.join( + process.env.BASELINE_DIR || path.join(os.tmpdir(), "braintrust-baseline"), + "js", + ); + if (!fs.existsSync(path.join(baselineRoot, "package.json"))) return null; + const baseline = getEntrypointsFromPackageExports(baselineRoot).find( + ({ name }) => name === entrypoint.name, + ); + return baseline + ? extractExportsFromDts(path.join(baselineRoot, baseline.typesPath)) + : null; } interface BreakingChanges { @@ -2934,12 +2772,12 @@ describe("API Compatibility", () => { let versionBumpType: "major" | "minor" | "patch" | "none"; beforeAll(async () => { - // Load entrypoints from tsup config - const configPath = path.join(__dirname, "..", "..", "tsup.config.ts"); - const configModule = await import(configPath); - ENTRYPOINTS = getEntrypointsFromTsupConfig(configModule.default); + // Load entrypoints from the package contract + ENTRYPOINTS = getEntrypointsFromPackageExports( + path.join(__dirname, "../.."), + ); console.log( - `Loaded ${ENTRYPOINTS.length} entrypoints from tsup.config.ts:`, + `Loaded ${ENTRYPOINTS.length} entrypoints from package.json:`, ENTRYPOINTS.map((e) => e.name).join(", "), ); @@ -2964,13 +2802,21 @@ describe("API Compatibility", () => { // Determine version bump type const publishedVersionInfo = parseVersion(publishedVersion); const currentVersionInfo = parseVersion(currentVersion); - versionBumpType = getVersionBumpType( + const packageVersionBumpType = getVersionBumpType( publishedVersionInfo, currentVersionInfo, ); + const changesetBumpType = getChangesetBumpType("braintrust"); + const bumpPriority = { none: 0, patch: 1, minor: 2, major: 3 } as const; + versionBumpType = + changesetBumpType && + bumpPriority[changesetBumpType] > bumpPriority[packageVersionBumpType] + ? changesetBumpType + : packageVersionBumpType; console.log(`Published version: ${publishedVersion}`); console.log(`Current version: ${currentVersion}`); + console.log(`Changeset bump type: ${changesetBumpType ?? "none"}`); console.log(`Version bump type: ${versionBumpType}`); // Create temp directory for downloaded package @@ -3009,6 +2855,59 @@ describe("API Compatibility", () => { expect(fs.existsSync(path.join(tempDir, "package"))).toBe(true); }); + test("keeps public declarations free of expanded Zod schema graphs", () => { + const declarationRoot = path.join(__dirname, "..", ".."); + const publicDeclarationPaths = [ + ...new Set(ENTRYPOINTS.map(({ typesPath }) => typesPath)), + ]; + + for (const declarationTypesPath of publicDeclarationPaths) { + const declarationPath = path.join(declarationRoot, declarationTypesPath); + if (!fs.existsSync(declarationPath)) { + continue; + } + const declaration = fs.readFileSync(declarationPath, "utf8"); + + expect(declaration).not.toMatch(/z\.infer { + const dist = path.join(__dirname, "..", "..", "dist"); + const declarations = fs + .readdirSync(dist, { recursive: true }) + .filter((file) => /\.d\.(?:ts|mts)$/.test(String(file))) + .map((file) => fs.readFileSync(path.join(dist, String(file)), "utf8")) + .join("\n"); + + expect(declarations).toContain("interface CompiledPromptParams"); + expect(declarations).not.toContain("_internalSerializeForCache"); + expect(declarations).not.toMatch(/generated_(?:plain_)?types/); + expect(declarations).not.toMatch( + /\b(?:FunctionDataType|AnyModelParamsType|PromptType)\b/, + ); + }); + test("should not regress public API surface for all entrypoints", async () => { if (!publishedVersion) { console.log("Skipping test: No published version available"); @@ -3019,11 +2918,12 @@ describe("API Compatibility", () => { const failures: string[] = []; for (const entrypoint of ENTRYPOINTS) { - const publishedTypesPath = path.join( - tempDir, - "package", - entrypoint.typesPath, + const publishedRoot = path.join(tempDir, "package"); + const published = getEntrypointsFromPackageExports(publishedRoot).find( + ({ name }) => name === entrypoint.name, ); + if (!published) continue; // A new subpath has no previous contract to compare. + const publishedTypesPath = path.join(publishedRoot, published.typesPath); const currentTypesPath = path.join( __dirname, "..", @@ -3033,10 +2933,9 @@ describe("API Compatibility", () => { // Check if both files exist if (!fs.existsSync(publishedTypesPath)) { - console.warn( - `Published types not found: ${publishedTypesPath}. Skipping ${entrypoint.name}.`, + throw new Error( + `Published declarations are missing: ${publishedTypesPath}`, ); - continue; } if (!fs.existsSync(currentTypesPath)) { @@ -3054,9 +2953,11 @@ describe("API Compatibility", () => { ); // Verify that current runtime exports match .d.ts declarations - const currentMjsPath = path - .join(__dirname, "..", entrypoint.typesPath) - .replace(/\.d\.ts$/, ".mjs"); + const currentMjsPath = path.join( + __dirname, + "../..", + entrypoint.runtimePath, + ); if (fs.existsSync(currentMjsPath)) { const runtimeExports = await extractRuntimeExports(currentMjsPath); const runtimeErrors = verifyRuntimeMatchesTypes( diff --git a/js/tests/auto-instrumentations/bundled-runtime.test.ts b/js/tests/auto-instrumentations/bundled-runtime.test.ts index 453e2abb5..d767fd7f8 100644 --- a/js/tests/auto-instrumentations/bundled-runtime.test.ts +++ b/js/tests/auto-instrumentations/bundled-runtime.test.ts @@ -11,7 +11,14 @@ * by transformation.test.ts. */ -import { describe, it, expect, beforeAll, afterAll, afterEach } from "vitest"; +import { + describe, + it, + expect, + beforeAll, + afterAll, + afterEach, +} from "vite-plus/test"; import { EventEmitter } from "node:events"; import * as fs from "node:fs"; import * as path from "node:path"; diff --git a/js/tests/auto-instrumentations/fixtures/node_modules/openai/index.mjs b/js/tests/auto-instrumentations/fixtures/node_modules/openai/index.mjs index 53355b0f0..c263edefd 100644 --- a/js/tests/auto-instrumentations/fixtures/node_modules/openai/index.mjs +++ b/js/tests/auto-instrumentations/fixtures/node_modules/openai/index.mjs @@ -1,5 +1,5 @@ -import { Completions } from './resources/chat/completions.mjs'; -import { Embeddings } from './resources/embeddings.mjs'; +import {Completions} from './resources/chat/completions.mjs'; +import {Embeddings} from './resources/embeddings.mjs'; export default class OpenAI { constructor(config = {}) { diff --git a/js/tests/auto-instrumentations/import-require-in-the-middle.test.ts b/js/tests/auto-instrumentations/import-require-in-the-middle.test.ts index 55167309f..8c6170faa 100644 --- a/js/tests/auto-instrumentations/import-require-in-the-middle.test.ts +++ b/js/tests/auto-instrumentations/import-require-in-the-middle.test.ts @@ -1,4 +1,4 @@ -import { describe, expect, it } from "vitest"; +import { describe, expect, it } from "vite-plus/test"; import { spawn } from "node:child_process"; import * as path from "node:path"; import { fileURLToPath, pathToFileURL } from "node:url"; diff --git a/js/tests/auto-instrumentations/loader-hook.test.ts b/js/tests/auto-instrumentations/loader-hook.test.ts index 90808a7c7..09706df21 100644 --- a/js/tests/auto-instrumentations/loader-hook.test.ts +++ b/js/tests/auto-instrumentations/loader-hook.test.ts @@ -1,4 +1,4 @@ -import { describe, it, expect, beforeAll, afterAll } from "vitest"; +import { describe, it, expect, beforeAll, afterAll } from "vite-plus/test"; import { Worker } from "node:worker_threads"; import * as fs from "node:fs"; import { createRequire } from "node:module"; @@ -12,10 +12,7 @@ const __dirname = path.dirname(fileURLToPath(import.meta.url)); const fixturesDir = path.join(__dirname, "fixtures"); // Path to unified loader hook (built dist file) -const hookPath = path.join( - __dirname, - "../../dist/auto-instrumentations/hook.mjs", -); +const hookPath = path.join(__dirname, "../../dist/hooks/hook.mjs"); // Paths to fixtures const listenerPath = path.join(fixturesDir, "listener-esm.mjs"); diff --git a/js/tests/auto-instrumentations/next-config.test.ts b/js/tests/auto-instrumentations/next-config.test.ts index bdb7bf7f2..c88f01086 100644 --- a/js/tests/auto-instrumentations/next-config.test.ts +++ b/js/tests/auto-instrumentations/next-config.test.ts @@ -1,4 +1,11 @@ -import { afterEach, beforeEach, describe, expect, it, vi } from "vitest"; +import { + afterEach, + beforeEach, + describe, + expect, + it, + vi, +} from "vite-plus/test"; const { requireFromProject } = vi.hoisted(() => ({ requireFromProject: Object.assign(vi.fn(), { @@ -12,14 +19,14 @@ vi.mock("node:module", async (importOriginal) => ({ })); vi.mock("../../src/auto-instrumentations/bundler/webpack.js", () => ({ - webpackPlugin: vi.fn((options: unknown) => ({ + braintrustWebpackPlugin: vi.fn((options: unknown) => ({ apply: () => {}, name: "braintrust-test-webpack-plugin", options, })), })); -import { webpackPlugin } from "../../src/auto-instrumentations/bundler/webpack.js"; +import { braintrustWebpackPlugin } from "../../src/auto-instrumentations/bundler/webpack.js"; import { wrapNextjsConfigWithBraintrust } from "../../src/auto-instrumentations/bundler/next.js"; const originalArgv = [...process.argv]; @@ -59,7 +66,7 @@ describe("wrapNextjsConfigWithBraintrust", () => { const result = config.webpack({ plugins: [] }, { isServer: false }); expect(userWebpack).toHaveBeenCalledOnce(); - expect(webpackPlugin).toHaveBeenCalledWith({ browser: true }); + expect(braintrustWebpackPlugin).toHaveBeenCalledWith({ browser: true }); expect(result.plugins).toHaveLength(2); expect(result.plugins[0]).toEqual({ name: "user-plugin" }); expect(result.plugins[1].options).toEqual({ browser: true }); diff --git a/js/tests/auto-instrumentations/orchestrion-js-upstream.test.ts b/js/tests/auto-instrumentations/orchestrion-js-upstream.test.ts index db9cf72a8..60e3a88e5 100644 --- a/js/tests/auto-instrumentations/orchestrion-js-upstream.test.ts +++ b/js/tests/auto-instrumentations/orchestrion-js-upstream.test.ts @@ -3,7 +3,7 @@ * licensed under Apache-2.0. Modified by Braintrust. */ -import { afterAll, beforeAll, describe, expect, it } from "vitest"; +import { afterAll, beforeAll, describe, expect, it } from "vite-plus/test"; import { spawnSync } from "node:child_process"; import * as fs from "node:fs"; import * as os from "node:os"; diff --git a/js/tests/auto-instrumentations/test-bundle.ts b/js/tests/auto-instrumentations/test-bundle.ts index 14e70a26b..2320e8d86 100644 --- a/js/tests/auto-instrumentations/test-bundle.ts +++ b/js/tests/auto-instrumentations/test-bundle.ts @@ -13,6 +13,8 @@ export type Bundler = "esbuild" | "vite" | "rollup"; * Bundles a fixture entry (which imports packages from fixtures/node_modules) * with the Braintrust plugin for the given bundler and writes an ESM bundle to * `outfile`. + * Keep the runtime external so these tests isolate the generated hooks; e2e + * scenarios verify bundled consumer initialization and span creation. */ export async function bundleFixture( bundler: Bundler, @@ -34,6 +36,7 @@ export async function bundleFixture( absWorkingDir: fixturesDir, preserveSymlinks: true, // Don't dereference symlinks platform: "node", + external: ["braintrust/auto"], }); } else if (bundler === "vite") { const { braintrustVitePlugin } = @@ -44,11 +47,12 @@ export async function bundleFixture( lib: { entry: entryPoint, formats: ["es"], - fileName: path.basename(outfile, ".mjs"), + fileName: () => path.basename(outfile), }, outDir: path.dirname(outfile), emptyOutDir: false, minify: false, + rolldownOptions: { external: ["braintrust/auto"] }, }, plugins: [braintrustVitePlugin(options)], logLevel: "error", @@ -59,6 +63,7 @@ export async function bundleFixture( await import("../../src/auto-instrumentations/bundler/rollup.js"); const bundle = await rollup({ input: entryPoint, + external: ["braintrust/auto"], plugins: [ { name: "resolve-fixture-packages", diff --git a/js/tests/auto-instrumentations/transformation.test.ts b/js/tests/auto-instrumentations/transformation.test.ts index cf2dd7972..fccef39ee 100644 --- a/js/tests/auto-instrumentations/transformation.test.ts +++ b/js/tests/auto-instrumentations/transformation.test.ts @@ -13,7 +13,9 @@ import * as path from "node:path"; import { createRequire } from "node:module"; import { fileURLToPath } from "node:url"; import { runInNewContext } from "node:vm"; -import { afterAll, beforeAll, describe, expect, it, vi } from "vitest"; +import { SourceMapConsumer } from "source-map"; +import { Transformer } from "../../src/auto-instrumentations/orchestrion-js/transformer"; +import { afterAll, beforeAll, describe, expect, it, vi } from "vite-plus/test"; import { InstrumentationMatcher, type InstrumentationConfig, @@ -75,6 +77,7 @@ function transformTestCode( } function expectGlobalHookTransform(output: string): void { + expect(output).toContain("braintrust/auto"); expect(output).toContain("__braintrust_invocation_hooks_v2"); expect(output).toContain("braintrust.global-instrumentation-hooks.registry"); expect(output).toContain( @@ -93,6 +96,36 @@ function expectGlobalHookTransform(output: string): void { } describe("Orchestrion Transformation Tests", () => { + it("initializes consumers before CommonJS calls without changing strict mode or source maps", async () => { + const source = + '"use strict";\nfunction request() { return this; }\nmodule.exports = request();'; + const transformer = new Transformer("test-sdk", "1.0.0", "index.cjs", [ + testConfig({ functionName: "request" }), + ]); + const result = transformer.transform(source, "cjs", "braintrust/auto"); + const loadedModule = { exports: "not called" }; + const require = vi.fn((id: string) => { + expect(id).toBe("braintrust/auto"); + expect(loadedModule.exports).toBe("not called"); + }); + + Function("require", "module", result.code)(require, loadedModule); + expect(require).toHaveBeenCalledOnce(); + expect(loadedModule.exports).toBeUndefined(); + + const lines = result.code.split("\n"); + const generatedLine = + lines.findIndex((line) => line.startsWith("module.exports")) + 1; + await SourceMapConsumer.with(result.map!, null, (consumer) => { + expect( + consumer.originalPositionFor({ line: generatedLine, column: 0 }), + ).toMatchObject({ + source: "test-sdk/index.cjs", + line: 3, + column: 0, + }); + }); + }); it.each(["7.0.108", "7.0.138"])( "leaves batch lifecycle export aliases uninstrumented in %s", async (version) => { @@ -615,7 +648,7 @@ describe("Orchestrion Transformation Tests", () => { describe("esbuild", () => { it.each([ ["browser", "browser", { browser: true }], - ["legacy-browser", "browser", { useDiagnosticChannelCompatShim: true }], + ["legacy-browser", "browser", { browser: true }], ["edge", "neutral", { browser: true }], ] as const)( "should keep Mastra %s bundles free of Node-only patches", @@ -719,6 +752,7 @@ describe("Orchestrion Transformation Tests", () => { library: { type: "module" }, }, experiments: { outputModule: true }, + externals: { "braintrust/auto": "module braintrust/auto" }, mode: "development", resolve: { modules: [nodeModulesDir, "node_modules"] }, plugins: [braintrustWebpackPlugin({ browser })], @@ -733,7 +767,7 @@ describe("Orchestrion Transformation Tests", () => { describe("turbopack / webpack loader", () => { const webpackLoaderPath = path.resolve( __dirname, - "../../dist/auto-instrumentations/bundler/webpack-loader.cjs", + "../../dist/loaders/webpack-loader.cjs", ); it.each([ "", @@ -825,6 +859,7 @@ describe("Orchestrion Transformation Tests", () => { library: { type: "module" }, }, experiments: { outputModule: true }, + externals: { "braintrust/auto": "module braintrust/auto" }, mode: "development", resolve: { modules: [nodeModulesDir, "node_modules"] }, // No plugins — only the loader, mirroring turbopack's constraint @@ -853,6 +888,7 @@ describe("Orchestrion Transformation Tests", () => { library: { type: "module" }, }, experiments: { outputModule: true }, + externals: { "braintrust/auto": "module braintrust/auto" }, mode: "development", resolve: { modules: [nodeModulesDir, "node_modules"] }, module: { @@ -880,13 +916,7 @@ describe("Orchestrion Transformation Tests", () => { ); it.each([ - [ - "skip", - "legacy browser", - "web", - { useDiagnosticChannelCompatShim: true }, - false, - ], + ["skip", "legacy browser", "web", { browser: true }, false], ["skip", "browser", "web", { browser: true }, false], ["apply", "node", "node", { browser: false }, true], ] as const)( @@ -902,6 +932,7 @@ describe("Orchestrion Transformation Tests", () => { experiments: { outputModule: true }, mode: "development", target, + externals: { "braintrust/auto": "module braintrust/auto" }, module: { rules: [ { @@ -918,9 +949,11 @@ describe("Orchestrion Transformation Tests", () => { expect(errors).toHaveLength(0); if (shouldPatch) { + expect(output).toContain("braintrust/auto"); expect(output).toContain("__braintrustObservabilityClass"); expect(output).toContain("@mastra/observability"); } else { + expect(output).not.toContain("braintrust/auto"); expect(output).not.toContain("__braintrustObservabilityClass"); expect(output).not.toContain("node:module"); } @@ -938,6 +971,7 @@ describe("Orchestrion Transformation Tests", () => { experiments: { outputModule: true }, mode: "development", target: "web", + externals: { "braintrust/auto": "module braintrust/auto" }, module: { rules: [ { @@ -954,6 +988,7 @@ describe("Orchestrion Transformation Tests", () => { expect(errors).toHaveLength(0); expect(output).toContain("__btPatchAPIPromise"); + expect(output).toContain("braintrust/auto"); expect(output).toContain("__btParsePatched"); }); }); diff --git a/js/tests/provider-wrappers.test.ts b/js/tests/provider-wrappers.test.ts index 37161415c..bb3e0e6de 100644 --- a/js/tests/provider-wrappers.test.ts +++ b/js/tests/provider-wrappers.test.ts @@ -7,10 +7,11 @@ import { expect, test, vi, -} from "vitest"; +} from "vite-plus/test"; import OpenAI from "openai"; import { configureNode } from "../src/node/config"; -import { _exportsForTestingOnly, Attachment, initLogger } from "../src/logger"; +import { _exportsForTestingOnly } from "../src/logger/testing"; +import { Attachment, initLogger } from "../src/logger/core"; import { wrapClaudeAgentSDK } from "../src/wrappers/claude-agent-sdk/claude-agent-sdk"; import { wrapGoogleGenAI } from "../src/wrappers/google-genai"; import { wrapOpenAI } from "../src/wrappers/oai"; @@ -74,10 +75,10 @@ describe("provider wrapper", () => { _exportsForTestingOnly.clearTestBackgroundLogger(); }); - test("OpenAI responses image outputs are converted to attachments when enabled", async () => { + test("OpenAI responses image outputs are converted to attachments", async () => { const originalAutoCaptureAttachments = - process.env.BRAINTRUST_CAPTURE_ATTACHMENTS; - process.env.BRAINTRUST_CAPTURE_ATTACHMENTS = "true"; + process.env.BRAINTRUST_AUTO_CAPTURE_ATTACHMENTS; + process.env.BRAINTRUST_AUTO_CAPTURE_ATTACHMENTS = "true"; expect(await backgroundLogger.drain()).toHaveLength(0); const rawClient = new OpenAI({ apiKey: "sk-test" }); @@ -122,9 +123,9 @@ describe("provider wrapper", () => { } finally { client.responses.create = originalCreate; if (originalAutoCaptureAttachments === undefined) { - delete process.env.BRAINTRUST_CAPTURE_ATTACHMENTS; + delete process.env.BRAINTRUST_AUTO_CAPTURE_ATTACHMENTS; } else { - process.env.BRAINTRUST_CAPTURE_ATTACHMENTS = + process.env.BRAINTRUST_AUTO_CAPTURE_ATTACHMENTS = originalAutoCaptureAttachments; } } @@ -132,8 +133,8 @@ describe("provider wrapper", () => { test("OpenAI responses image data is omitted by default", async () => { const originalAutoCaptureAttachments = - process.env.BRAINTRUST_CAPTURE_ATTACHMENTS; - delete process.env.BRAINTRUST_CAPTURE_ATTACHMENTS; + process.env.BRAINTRUST_AUTO_CAPTURE_ATTACHMENTS; + delete process.env.BRAINTRUST_AUTO_CAPTURE_ATTACHMENTS; const rawClient = new OpenAI({ apiKey: "sk-test" }); const client = wrapOpenAI(rawClient); const mockData = { @@ -157,13 +158,15 @@ describe("provider wrapper", () => { ).toEqual(mockData); const spans = await backgroundLogger.drain(); expect(spans).toHaveLength(1); - expect(spans[0].output[0].result).toBe(""); + expect(spans[0].output).toEqual([ + { type: "image_generation_call", output_format: "png" }, + ]); } finally { client.responses.create = originalCreate; if (originalAutoCaptureAttachments === undefined) { - delete process.env.BRAINTRUST_CAPTURE_ATTACHMENTS; + delete process.env.BRAINTRUST_AUTO_CAPTURE_ATTACHMENTS; } else { - process.env.BRAINTRUST_CAPTURE_ATTACHMENTS = + process.env.BRAINTRUST_AUTO_CAPTURE_ATTACHMENTS = originalAutoCaptureAttachments; } } diff --git a/js/tsconfig.json b/js/tsconfig.json index 02be878d9..71e75c358 100644 --- a/js/tsconfig.json +++ b/js/tsconfig.json @@ -2,10 +2,15 @@ "compilerOptions": { "declaration": true, "lib": ["es2022"], - "module": "commonjs", + "module": "ESNext", "target": "es2022", - "moduleResolution": "node", - "baseUrl": ".", + "moduleResolution": "Bundler", + "paths": { + "@apm-js-collab/code-transformer": [ + "./src/auto-instrumentations/orchestrion-js/index.ts" + ], + "#braintrust-platform": ["./src/isomorph.ts"] + }, "strict": true, "noUnusedLocals": true, "esModuleInterop": true, @@ -20,13 +25,12 @@ "**/smoke/**", "scripts/**", "vendor/**", - "eslint.config.ts", - "tsup.config.ts", "src/auto-instrumentations/**/*.mts", "src/auto-instrumentations/bundler/**", "src/auto-instrumentations/configs/**", "src/auto-instrumentations/loader/**", "tests/auto-instrumentations/**", - "./playground.ts" + "./playground.ts", + "vite.config.ts" ] } diff --git a/js/tsconfig.test.json b/js/tsconfig.test.json index b6e00546a..50e053f02 100644 --- a/js/tsconfig.test.json +++ b/js/tsconfig.test.json @@ -5,11 +5,15 @@ "module": "es2022", "moduleResolution": "bundler", "noEmit": true, - "types": ["node", "vitest/globals"], + "types": ["node", "vite-plus/test/globals"], "paths": { - "@ai-sdk/openai": ["tests/type-shims/ai-sdk-openai.d.ts"], - "ai": ["tests/type-shims/ai.d.ts"], - "braintrust": ["src/node/index.ts"] + "@apm-js-collab/code-transformer": [ + "./src/auto-instrumentations/orchestrion-js/index.ts" + ], + "@ai-sdk/openai": ["./tests/type-shims/ai-sdk-openai.d.ts"], + "ai": ["./tests/type-shims/ai.d.ts"], + "braintrust": ["./src/node/index.ts"], + "#braintrust-platform": ["./src/isomorph.ts"] } }, "include": [ diff --git a/js/tsup.config.ts b/js/tsup.config.ts deleted file mode 100644 index c2b7d2f78..000000000 --- a/js/tsup.config.ts +++ /dev/null @@ -1,194 +0,0 @@ -import { defineConfig } from "tsup"; -import { readFileSync } from "node:fs"; -import { resolve } from "node:path"; - -const packageJson = JSON.parse( - readFileSync(new URL("./package.json", import.meta.url), "utf8"), -) as { version: string }; - -const define = { - __BRAINTRUST_SDK_VERSION__: JSON.stringify(packageJson.version), -}; - -export default defineConfig([ - // Node.js entrypoint - { - entry: { - index: "src/node/index.ts", - "custom-views": "src/custom-views/exports.ts", - preprocessors: "src/preprocessors/exports.ts", - "apply-auto-instrumentation": - "src/node/apply-auto-instrumentation-entry.ts", - "vitest-evals-reporter": "src/wrappers/vitest-evals/reporter.ts", - }, - format: ["cjs", "esm"], - outDir: "dist", - external: ["zod"], - removeNodeProtocol: false, - dts: { - // Split DTS generation to reduce memory usage - compilerOptions: { - skipLibCheck: true, - }, - }, - splitting: true, - define, - clean: true, - }, - { - entry: { cli: "src/cli/index.ts" }, - format: ["cjs"], - removeNodeProtocol: false, - outDir: "dist", - external: ["esbuild", "prettier", "typescript", "zod"], - // CLI doesn't need DTS - dts: false, - define, - clean: false, - }, - { - entry: ["dev/index.ts"], - format: ["cjs", "esm"], - outDir: "dev/dist", - removeNodeProtocol: false, - external: ["esbuild", "prettier", "typescript", "zod"], - dts: { - // Split DTS generation to reduce memory usage - compilerOptions: { - skipLibCheck: true, - }, - }, - splitting: true, - define, - clean: true, - }, - { - entry: ["util/index.ts"], - format: ["cjs", "esm"], - outDir: "util/dist", - external: ["esbuild", "prettier", "typescript", "zod"], - removeNodeProtocol: false, - dts: { - // Split DTS generation to reduce memory usage - compilerOptions: { - skipLibCheck: true, - }, - }, - splitting: true, - define, - clean: true, - }, - // Browser/edge entrypoints - ...(["cjs", "esm"] as const).map((format) => ({ - entry: { - browser: "src/browser/index.ts", - "edge-light": "src/edge-light/index.ts", - workerd: "src/workerd/index.ts", - "apply-auto-instrumentation.browser": - "src/non-node/apply-auto-instrumentation-entry.ts", - ...(format === "esm" ? { "isomorphic-exports": "src/exports.ts" } : {}), - }, - format: [format], - outDir: "dist", - external: ["zod"], - removeNodeProtocol: false, - platform: "browser" as const, - splitting: format === "esm", - esbuildPlugins: - format === "esm" - ? [ - { - name: "preserve-isomorphic-namespace", - setup(build: import("esbuild").PluginBuild) { - // Keep the default namespace as an ESM re-export. Bundling it - // lowers it to an impure __export() call which retains every - // API even when consumers only use a named export. - // Splitting shares logger/isomorph state with the API module. - const entrypoints = new Set( - ["browser", "edge-light", "workerd"].map((runtime) => - resolve(`src/${runtime}/index.ts`), - ), - ); - build.onResolve({ filter: /^\.\.\/exports$/ }, (args) => { - if (entrypoints.has(args.importer)) { - return { - path: "./isomorphic-exports.mjs", - external: true, - }; - } - }); - }, - }, - ] - : [], - dts: true, - define, - clean: false, - })), - { - entry: ["src/instrumentation/index.ts"], - format: ["cjs", "esm"], - outDir: "dist/instrumentation", - external: ["@braintrust/instrumentation-core", "zod"], - dts: { - compilerOptions: { - skipLibCheck: true, - }, - }, - splitting: false, - define, - clean: true, - }, - { - entry: { - "bundler/vite": "src/auto-instrumentations/bundler/vite.ts", - "bundler/webpack": "src/auto-instrumentations/bundler/webpack.ts", - "bundler/next": "src/auto-instrumentations/bundler/next.ts", - "bundler/esbuild": "src/auto-instrumentations/bundler/esbuild.ts", - "bundler/rollup": "src/auto-instrumentations/bundler/rollup.ts", - }, - format: ["cjs", "esm"], - outDir: "dist/auto-instrumentations", - dts: true, - external: ["zod"], - define, - outExtension({ format }) { - if (format === "esm") { - return { js: ".mjs" }; - } - return { js: ".cjs" }; - }, - clean: true, - }, - { - entry: { - "bundler/webpack-loader": - "src/auto-instrumentations/bundler/webpack-loader.ts", - }, - format: ["cjs"], - outDir: "dist/auto-instrumentations", - dts: true, - external: ["zod"], - define, - outExtension() { - return { js: ".cjs" }; - }, - clean: false, - }, - { - entry: [ - "src/auto-instrumentations/hook.mts", - "src/auto-instrumentations/loader/module-hooks.mts", - ], - format: ["esm"], - outDir: "dist/auto-instrumentations", - dts: false, - platform: "node", - external: ["@anthropic-ai/sdk", "zod"], - define, - outExtension({ format }) { - return { js: ".mjs" }; - }, - clean: false, - }, -]); diff --git a/js/util/generated_types.ts b/js/util/generated_types.ts index ea2bc045b..75f0a42ce 100644 --- a/js/util/generated_types.ts +++ b/js/util/generated_types.ts @@ -1,6 +1,6 @@ // Auto-generated file (content hash 387034529ea006a9) -- do not modify -import { z } from "zod/v3"; +import {z} from "zod/v3"; export const AclObjectType = z.union([ z.enum([ diff --git a/js/util/git_fields.ts b/js/util/git_fields.ts index 8eb06d625..a33b07ffe 100644 --- a/js/util/git_fields.ts +++ b/js/util/git_fields.ts @@ -1,4 +1,4 @@ -import { GitMetadataSettingsType as GitMetadataSettings } from "./generated_types"; +import type { GitMetadataSettings } from "../src/sdk-types"; export function mergeGitMetadataSettings( s1: GitMetadataSettings, diff --git a/js/util/json_util.test.ts b/js/util/json_util.test.ts index e85470f88..48cf18e8e 100644 --- a/js/util/json_util.test.ts +++ b/js/util/json_util.test.ts @@ -1,4 +1,4 @@ -import { expect, test } from "vitest"; +import { expect, test } from "vite-plus/test"; import { deterministicReplacer } from "./json_util"; test("deterministicReplacer basic", () => { diff --git a/js/util/json_util.ts b/js/util/json_util.ts index dd81d6e73..1f4c0f20c 100644 --- a/js/util/json_util.ts +++ b/js/util/json_util.ts @@ -2,7 +2,7 @@ // serialized in a deterministic key order. // // https://gist.github.com/davidfurlong/463a83a33b70a3b6618e97ec9679e490 -export function deterministicReplacer(_key: string, value: any) { +export function deterministicReplacer(_key: string, value: any): any { return value instanceof Object && !(value instanceof Array) ? Object.keys(value) .sort() diff --git a/js/util/merge_row_batch.test.ts b/js/util/merge_row_batch.test.ts index 9465bf712..1baaa629e 100644 --- a/js/util/merge_row_batch.test.ts +++ b/js/util/merge_row_batch.test.ts @@ -1,4 +1,4 @@ -import { describe, test, expect } from "vitest"; +import { describe, test, expect } from "vite-plus/test"; import { mergeRowBatch, batchItems } from "./merge_row_batch"; import { IS_MERGE_FIELD } from "./db_fields"; diff --git a/js/util/object.ts b/js/util/object.ts index 7ea593005..e34e014da 100644 --- a/js/util/object.ts +++ b/js/util/object.ts @@ -1,7 +1,4 @@ -import { - AsyncScoringControlType as AsyncScoringControl, - type ObjectReferenceType, -} from "./generated_types"; +import type { AsyncScoringControl, ObjectReference } from "../src/sdk-types"; import { Source, ASYNC_SCORING_CONTROL_FIELD, @@ -23,8 +20,7 @@ export type OtherExperimentLogFields = { classifications?: Record; metadata: Record; metrics: Record; - datasetRecordId: string; - origin: ObjectReferenceType; + origin: ObjectReference; span_attributes: Record; [ASYNC_SCORING_CONTROL_FIELD]: AsyncScoringControl; [MERGE_PATHS_FIELD]: string[][]; @@ -43,7 +39,7 @@ export type ExperimentLogFullArgs = Partial< export type LogFeedbackFullArgs = IdField & Partial< - Omit & { + Omit & { comment: string; source: Source; } @@ -102,7 +98,7 @@ export type DatasetEvent = { tags?: string[]; metadata?: unknown; created?: string; - origin?: ObjectReferenceType; + origin?: ObjectReference; id: string; dataset_id: string; } & ({ expected?: unknown } | { output?: unknown }); diff --git a/js/util/object_util.test.ts b/js/util/object_util.test.ts index 794f601d6..f55875771 100644 --- a/js/util/object_util.test.ts +++ b/js/util/object_util.test.ts @@ -1,4 +1,4 @@ -import { expect, test, describe, afterEach } from "vitest"; +import { expect, test, describe, afterEach } from "vite-plus/test"; import { mapAt, forEachMissingKey, diff --git a/js/util/object_util.ts b/js/util/object_util.ts index 607bacbb8..ad9ea72d9 100644 --- a/js/util/object_util.ts +++ b/js/util/object_util.ts @@ -22,7 +22,7 @@ export function mergeDictsWithPaths({ mergeInto: Record; mergeFrom: Record; mergePaths: string[][]; -}) { +}): Record { const mergePathsSerialized = new Set( mergePaths.map((p) => JSON.stringify(p)), ); @@ -95,7 +95,7 @@ function mergeDictsWithPathsHelper({ export function mergeDicts( mergeInto: Record, mergeFrom: Record, -) { +): Record { return mergeDictsWithPaths({ mergeInto, mergeFrom, mergePaths: [] }); } @@ -114,7 +114,7 @@ export function forEachMissingKey({ v: unknown; path: string[]; }) => void; -}) { +}): void { function helper(lhs: unknown, rhs: unknown, path: string[]) { if (lhs instanceof Object) { if (!(rhs instanceof Object)) { diff --git a/js/util/score.ts b/js/util/score.ts index 86dc03b7e..ddcadc878 100644 --- a/js/util/score.ts +++ b/js/util/score.ts @@ -40,11 +40,6 @@ export interface Score { name: string; score: number | null; metadata?: Record; - // DEPRECATION_NOTICE: this field is deprecated, as errors are propagated up to the caller. - /** - * @deprecated - */ - error?: unknown; } // A single result can use the scorer's name; array entries must stay named. diff --git a/js/util/span_identifier_v1.ts b/js/util/span_identifier_v1.ts index 50d6289e8..8c313a859 100644 --- a/js/util/span_identifier_v1.ts +++ b/js/util/span_identifier_v1.ts @@ -47,7 +47,7 @@ export class SpanRowIdsV1 { } } - public toObject() { + public toObject(): { rowId: string; spanId: string; rootSpanId: string } { return { rowId: this.rowId, spanId: this.spanId, @@ -160,7 +160,11 @@ export class SpanComponentsV1 { } } - public toObject() { + public toObject(): { + objectType: SpanObjectTypeV1; + objectId: string; + rowIds: { rowId: string; spanId: string; rootSpanId: string } | undefined; + } { return { objectType: this.objectType, objectId: this.objectId, diff --git a/js/util/span_identifier_v2.ts b/js/util/span_identifier_v2.ts index 2e37eb053..69c38b3fc 100644 --- a/js/util/span_identifier_v2.ts +++ b/js/util/span_identifier_v2.ts @@ -50,7 +50,7 @@ export class SpanRowIdsV2 { } } - public toObject() { + public toObject(): { rowId: string; spanId: string; rootSpanId: string } { return { rowId: this.rowId, spanId: this.spanId, @@ -244,7 +244,12 @@ export class SpanComponentsV2 { } } - public toObject() { + public toObject(): { + objectType: SpanObjectTypeV2; + objectId: string | undefined; + computeObjectMetadataArgs: Record | undefined; + rowIds: { rowId: string; spanId: string; rootSpanId: string } | undefined; + } { return { objectType: this.objectType, objectId: this.objectId, diff --git a/js/util/span_identifier_v3.ts b/js/util/span_identifier_v3.ts index f057f798b..78a753d53 100644 --- a/js/util/span_identifier_v3.ts +++ b/js/util/span_identifier_v3.ts @@ -38,7 +38,8 @@ export enum SpanObjectTypeV3 { PLAYGROUND_LOGS = 3, } -export const spanObjectTypeV3EnumSchema = z.nativeEnum(SpanObjectTypeV3); +export const spanObjectTypeV3EnumSchema: z.ZodType = + z.nativeEnum(SpanObjectTypeV3); export function spanObjectTypeV3ToTypedString( objectType: SpanObjectTypeV3, @@ -81,7 +82,35 @@ const _INTERNAL_SPAN_COMPONENT_UUID_FIELDS_ID_TO_NAME: Record< [InternalSpanComponentUUIDFields.ROOT_SPAN_ID]: "root_span_id", }; -export const spanComponentsV3Schema = z +type SpanObjectMetadata = + | { + object_id?: string | null; + compute_object_metadata_args?: null; + } + | { + object_id?: null; + compute_object_metadata_args: Record; + }; + +type SpanRowIds = + | { + row_id: string; + span_id: string; + root_span_id: string; + } + | { + row_id?: null; + span_id?: null; + root_span_id?: null; + }; + +export type SpanComponentsV3Data = { + object_type: SpanObjectTypeV3; + propagated_event?: Record | null; +} & SpanObjectMetadata & + SpanRowIds; + +export const spanComponentsV3Schema: z.ZodType = z .object({ object_type: spanObjectTypeV3EnumSchema, // TODO(manu): We should have a more elaborate zod schema for @@ -118,8 +147,6 @@ export const spanComponentsV3Schema = z ]), ); -export type SpanComponentsV3Data = z.infer; - export class SpanComponentsV3 { constructor(public data: SpanComponentsV3Data) {} diff --git a/js/util/span_identifier_v4.test.ts b/js/util/span_identifier_v4.test.ts index 6084607d8..3c4fa4620 100644 --- a/js/util/span_identifier_v4.test.ts +++ b/js/util/span_identifier_v4.test.ts @@ -1,4 +1,4 @@ -import { expect, test, describe } from "vitest"; +import { expect, test, describe } from "vite-plus/test"; import { SpanComponentsV4, SpanComponentsV4Data, diff --git a/js/util/span_identifier_v4.ts b/js/util/span_identifier_v4.ts index e92e3eddf..844730365 100644 --- a/js/util/span_identifier_v4.ts +++ b/js/util/span_identifier_v4.ts @@ -5,6 +5,7 @@ import { SpanComponentsV3, SpanObjectTypeV3, spanObjectTypeV3EnumSchema, + type SpanComponentsV3Data, } from "./span_identifier_v3"; import { ParentExperimentIds, @@ -19,7 +20,7 @@ import { uint8ArrayToString, } from "./bytes"; import { z } from "zod/v3"; -import { InvokeFunctionType as InvokeFunctionRequest } from "./generated_types"; +import type { InvokeFunction as InvokeFunctionRequest } from "../src/sdk-types"; import { mergeDicts } from "./object_util"; const ENCODING_VERSION_NUMBER_V4 = 4; @@ -88,7 +89,9 @@ const FIELDS_ID_TO_NAME: Record = { [Fields.ROOT_SPAN_ID]: "root_span_id", }; -export const spanComponentsV4Schema = z +export type SpanComponentsV4Data = SpanComponentsV3Data; + +export const spanComponentsV4Schema: z.ZodType = z .object({ object_type: spanObjectTypeV3EnumSchema, propagated_event: z.record(z.unknown()).nullish(), @@ -122,8 +125,6 @@ export const spanComponentsV4Schema = z ]), ); -export type SpanComponentsV4Data = z.infer; - export class SpanComponentsV4 { constructor(public data: SpanComponentsV4Data) {} diff --git a/js/util/string_util.test.ts b/js/util/string_util.test.ts index f92af00eb..d31bf2b76 100644 --- a/js/util/string_util.test.ts +++ b/js/util/string_util.test.ts @@ -1,4 +1,4 @@ -import { expect, test } from "vitest"; +import { expect, test } from "vite-plus/test"; import { camelToSnakeCase, _urljoin } from "./string_util"; test("_urljoin", () => { diff --git a/js/util/string_util.ts b/js/util/string_util.ts index 079f629ce..b2256b8f3 100644 --- a/js/util/string_util.ts +++ b/js/util/string_util.ts @@ -7,32 +7,32 @@ export function _urljoin(...parts: string[]): string { .join("/"); } -export function capitalize(s: string, sep?: string) { +export function capitalize(s: string, sep?: string): string { const items = sep ? s.split(sep) : [s]; return items .map((s) => (s ? s.charAt(0).toUpperCase() + s.slice(1) : s)) .join(sep || ""); } -export function lowercase(s: string, sep?: string) { +export function lowercase(s: string, sep?: string): string { const items = sep ? s.split(sep) : [s]; return items .map((s) => (s ? s.charAt(0).toLowerCase() + s.slice(1) : s)) .join(sep || ""); } -export function snakeToCamelCase(s: string) { +export function snakeToCamelCase(s: string): string { return s .split("_") .map((s) => capitalize(s)) .join(""); } -export function snakeToTitleCase(s: string) { +export function snakeToTitleCase(s: string): string { return capitalize(s, "_").replace("_", " "); } -export function camelToSnakeCase(s: string) { +export function camelToSnakeCase(s: string): string { return s.replace(/([A-Z])/g, (m) => "_" + m.toLowerCase()).replace(/^_/, ""); } diff --git a/js/util/zod_util.test.ts b/js/util/zod_util.test.ts index e9c41c7e5..38df59975 100644 --- a/js/util/zod_util.test.ts +++ b/js/util/zod_util.test.ts @@ -1,4 +1,4 @@ -import { expect, test } from "vitest"; +import { expect, test } from "vite-plus/test"; import { objectNullish, parseNoStrip } from "./zod_util"; import { z } from "zod/v3"; diff --git a/js/util/zod_util.ts b/js/util/zod_util.ts index 843223ac5..c88206b51 100644 --- a/js/util/zod_util.ts +++ b/js/util/zod_util.ts @@ -34,7 +34,10 @@ export class ExtraFieldsError extends Error { // Note: this check is not exactly equivalent to a deep version of `z.strict()`. // For instance, schemas which intentionally strip keys from objects using // something like `z.transform` can fail this check. -export function parseNoStrip(schema: T, input: unknown) { +export function parseNoStrip( + schema: T, + input: unknown, +): z.TypeOf { const output = schema.parse(input) as z.infer; forEachMissingKey({ lhs: output, diff --git a/js/vite.config.ts b/js/vite.config.ts new file mode 100644 index 000000000..a44caa583 --- /dev/null +++ b/js/vite.config.ts @@ -0,0 +1,203 @@ +import path from "node:path"; +import { fileURLToPath } from "node:url"; +import { defineConfig } from "vite-plus"; +import type { PackUserConfig } from "vite-plus/pack"; +import { readFileSync } from "node:fs"; + +const __dirname = import.meta.dirname; + +const pkg = JSON.parse( + readFileSync(new URL("./package.json", import.meta.url), "utf8"), +); +const define = { __BRAINTRUST_SDK_VERSION__: JSON.stringify(pkg.version) }; +const common = { + sourcemap: true, + // Clean the whole package output, including obsolete runtime directories. + clean: ["dist"], + fixedExtension: false, + define, + dts: { generator: "oxc" as const }, + deps: { + onlyBundle: ["async"], + }, +}; +const entries = { + index: "src/exports.ts", + instrumentation: "src/instrumentation/index.ts", + "custom-views": "src/custom-views/exports.ts", + preprocessors: "src/preprocessors/exports.ts", + auto: "src/auto.ts", +}; + +export default defineConfig({ + // Prefer TypeScript over JavaScript for extension-less imports in sdk/js tests + resolve: { + tsconfigPaths: true, + extensions: [".ts", ".tsx", ".mts", ".js", ".mjs", ".jsx", ".json"], + alias: { + "#braintrust-platform": path.resolve(__dirname, "src/isomorph.ts"), + "@apm-js-collab/code-transformer": path.resolve( + __dirname, + "src/auto-instrumentations/orchestrion-js/index.ts", + ), + }, + }, + server: { + watch: { + ignored: [ + "**/node_modules/**", + "**/dist/**", + "**/vendor/**", + "**/.{idea,git,cache,output,temp}/**", + ], + }, + fs: { + // Deny access to vendor folder + deny: [path.resolve(__dirname, "vendor")], + }, + }, + optimizeDeps: { + exclude: ["vendor/**"], + }, + test: { + reporters: ["default"], + exclude: [ + // Default vitest exclusions + "**/node_modules/**", + "**/dist/**", + "**/cypress/**", + "**/.{idea,git,cache,output,temp}/**", + "**/{karma,rollup,webpack,vite,vitest,jest,ava,babel,nyc,cypress,tsup,build,eslint,prettier}.config.*", + // Exclude vendor folder and all its contents + "**/vendor/**", + "vendor/**", + "./vendor/**", + // Exclude subdirectories with their own test configs + "src/wrappers/ai-sdk/**", + "src/wrappers/claude-agent-sdk/**", + "src/wrappers/vitest/**", + "smoke/**", + // Exclude example tests (require API keys and make real API calls) + "examples/vitest/**", + "examples/node-test/**", + ], + testTimeout: 15_000, + }, + run: { + tasks: { + compile: { + command: "vp pack", + dependsOn: [ + { task: "compile", from: ["dependencies", "devDependencies"] }, + ], + cache: { + input: [{ auto: true }, "!dist/**"], + untrackedEnv: ["SHELL", "BASH_ENV"], + output: ["dist/**"], + }, + }, + }, + }, + pack: [ + ...(["node", "browser", "workerd", "edge-light"] as const).map( + (runtime): PackUserConfig => ({ + ...common, + name: runtime, + // Preserve internal module boundaries so consumers can discard unused + // features through the root export without public feature subpaths. + unbundle: true, + outputOptions: { + // Keep bundled dependencies inside our ESM package scope. A nested + // node_modules directory makes loaders treat them as another package. + sanitizeFileName: (name) => + name + .replaceAll("\0", "_") + .replaceAll("node_modules", "_vendor") + .replaceAll(".pnpm", "_pnpm"), + }, + dts: { + generator: "oxc", + compilerOptions: { + paths: { "#braintrust-platform": [`./src/${runtime}/isomorph.ts`] }, + }, + }, + ...(runtime === "node" + ? {} + : { platform: "browser" as const, target: "es2022" }), + outDir: `dist/${runtime}`, + entry: { + ...entries, + "apply-auto-instrumentation": + runtime === "node" + ? "src/node/apply-auto-instrumentation-entry.ts" + : "src/non-node/apply-auto-instrumentation-entry.ts", + ...(runtime === "node" + ? { + "vitest-evals-reporter": + "src/wrappers/vitest-evals/reporter.ts", + } + : {}), + }, + // Resolve before tsconfig paths: Pack's ordinary alias option loses to + // the source-test fallback and would put Node adapters in browser builds. + plugins: [ + { + name: "braintrust-platform", + resolveId: { + order: "pre", + handler(id) { + if (id === "#braintrust-platform") + return fileURLToPath( + new URL(`./src/${runtime}/isomorph.ts`, import.meta.url), + ); + }, + }, + }, + ], + }), + ), + { + ...common, + name: "tooling", + outDir: "dist/tooling", + entry: Object.fromEntries( + [ + "bundler/vite", + "bundler/webpack", + "bundler/next", + "bundler/esbuild", + "bundler/rollup", + ].map((name) => [name, `src/auto-instrumentations/${name}.ts`]), + ), + }, + { + ...common, + name: "loader", + format: "cjs", + dts: false, + outDir: "dist/loaders", + entry: { + "webpack-loader": "src/auto-instrumentations/bundler/webpack-loader.ts", + }, + }, + { + ...common, + name: "hooks", + dts: false, + outDir: "dist/hooks", + entry: { + hook: "src/auto-instrumentations/hook.mts", + "loader/module-hooks": + "src/auto-instrumentations/loader/module-hooks.mts", + }, + fixedExtension: true, + deps: { neverBundle: ["../auto.js"] }, + outputOptions: { + paths: { + [fileURLToPath(new URL("./src/auto.js", import.meta.url))]: + "../node/auto.js", + }, + }, + }, + ], +}); diff --git a/js/vitest.config.js b/js/vitest.config.js deleted file mode 100644 index d9c34decb..000000000 --- a/js/vitest.config.js +++ /dev/null @@ -1,61 +0,0 @@ -import tsconfigPaths from "vite-tsconfig-paths"; -import path from "path"; - -const config = { - plugins: [ - tsconfigPaths({ - // Explicitly specify the root tsconfig to prevent scanning vendor folders - root: ".", - projects: ["./tsconfig.json"], - }), - ], - // Prefer TypeScript over JavaScript for extension-less imports in sdk/js tests - resolve: { - extensions: [".ts", ".tsx", ".mts", ".js", ".mjs", ".jsx", ".json"], - alias: { - // Prevent resolution into vendor directories - vendor: false, - }, - }, - server: { - fs: { - // Deny access to vendor folder - deny: [path.resolve(__dirname, "vendor")], - }, - }, - optimizeDeps: { - exclude: ["vendor/**"], - }, - test: { - reporters: ["default"], - exclude: [ - // Default vitest exclusions - "**/node_modules/**", - "**/dist/**", - "**/cypress/**", - "**/.{idea,git,cache,output,temp}/**", - "**/{karma,rollup,webpack,vite,vitest,jest,ava,babel,nyc,cypress,tsup,build,eslint,prettier}.config.*", - // Exclude vendor folder and all its contents - "**/vendor/**", - "vendor/**", - "./vendor/**", - // Exclude subdirectories with their own test configs - "src/wrappers/ai-sdk/**", - "src/wrappers/claude-agent-sdk/**", - "src/wrappers/vitest/**", - "smoke/**", - // Exclude example tests (require API keys and make real API calls) - "examples/vitest/**", - "examples/node-test/**", - ], - // Additional test environment configuration - watchExclude: [ - "**/node_modules/**", - "**/dist/**", - "**/vendor/**", - "**/.{idea,git,cache,output,temp}/**", - ], - testTimeout: 15_000, - }, -}; -export default config; diff --git a/knip.jsonc b/knip.jsonc index 40f840f2d..a608e88cb 100644 --- a/knip.jsonc +++ b/knip.jsonc @@ -19,7 +19,7 @@ "e2e": { "entry": [ "package.json", - "vitest.config.mts", + "vite.config.ts", "helpers/**/*.ts", "scenarios/**/*.test.ts", "scenarios/**/package.json", @@ -46,25 +46,29 @@ "js": { "entry": [ "src/node/index.ts", + "src/exports.ts", + "src/auto.ts", + "src/auto-instrumentations/{index.ts,hook.mts,loader/module-hooks.mts}", + "src/{node,non-node}/apply-auto-instrumentation-entry.ts", + "src/{node,browser,workerd,edge-light}/{isomorph,config}.ts", + "src/{custom-views,preprocessors}/exports.ts", "src/browser/index.ts", "src/edge-light/index.ts", "src/workerd/index.ts", "src/instrumentation/index.ts", "src/auto-instrumentations/bundler/*.ts", "tests/**/*.{ts,tsx,mts,cts,cjs,mjs}", - "scripts/**/*.ts", + "scripts/**/*.{ts,mjs}", "examples/**/*.{ts,tsx,js,jsx,mjs,cjs}", "smoke/**/*.{ts,tsx,js,jsx,mjs,cjs}", - "dev/**/*.ts", "util/**/*.ts", ], "project": [ "src/**/*.{ts,tsx,mts,cts}", "tests/**/*.{ts,tsx,mts,cts,cjs,mjs}", - "scripts/**/*.ts", + "scripts/**/*.{ts,mjs}", "examples/**/*.{ts,tsx,js,jsx,mjs,cjs}", "smoke/**/*.{ts,tsx,js,jsx,mjs,cjs,d.ts}", - "dev/**/*.ts", "util/**/*.ts", ], }, @@ -76,8 +80,13 @@ "entry": ["*.test.ts"], "project": ["*.ts"], }, + "integrations/deepseek-harness": { + "entry": ["src/index.ts", "src/**/*.test.ts"], + "project": ["src/**/*.ts"], + }, "integrations/otel-js": { "entry": [ + "src/index.ts", "src/**/*.test.ts", "tests/**/*.ts", "examples/**/*.{ts,js,mjs,cjs}", @@ -91,11 +100,15 @@ ], }, "integrations/temporal-js": { - "entry": ["examples/**/*.{ts,js,mjs,cjs}"], + "entry": [ + "src/index.ts", + "src/workflow-interceptors.ts", + "examples/**/*.{ts,js,mjs,cjs}", + ], "project": ["src/**/*.ts", "examples/**/*.{ts,js,mjs,cjs}"], }, "integrations/templates-nunjucks": { - "entry": ["smoke/**/*.{ts,tsx,js,jsx,mjs,cjs}"], + "entry": ["src/index.ts", "smoke/**/*.{ts,tsx,js,jsx,mjs,cjs}"], "project": ["src/**/*.ts", "smoke/**/*.{ts,tsx,js,jsx,mjs,cjs,d.ts}"], }, }, diff --git a/package.json b/package.json index 54b4a3f8c..86ca6bba9 100644 --- a/package.json +++ b/package.json @@ -6,29 +6,33 @@ "private": true, "scripts": { "preinstall": "node -e \"const userAgent = process.env.npm_config_user_agent || ''; if (process.env.INIT_CWD === process.cwd() && !userAgent.includes('pnpm/')) { console.error('Use pnpm in this repo.'); process.exit(1); }\"", - "build": "turbo run build", - "check:typings": "turbo run check:typings", - "dev": "turbo run dev", - "watch": "turbo run watch", - "start": "turbo run start", - "clean": "turbo run clean", + "build": "vp run -r build", + "check:typings": "vp run -r check:typings", + "dev": "vp run -r dev", + "watch": "vp run -r watch", + "start": "vp run -r start", + "clean": "vp run -r clean", "knip": "knip --config knip.jsonc --no-config-hints", - "test": "turbo run test --filter=\"!@braintrust/otel\"", - "test:e2e": "turbo run test:e2e", - "test:e2e:update": "turbo run test:e2e:update", + "test": "vp run --filter='*' --filter='@braintrust/*' --filter='!braintrust-sdk-js' --filter='!@braintrust/otel' test", + "test:e2e": "vp run -r test:e2e", + "test:e2e:update": "vp run -r test:e2e:update", "test:e2e:record": "node e2e/scripts/run-record-tests.mjs --braintrust-build-deps", "test:e2e:bump": "node e2e/scripts/run-e2e-bump-docker.mjs", "dotagents:install": "dotagents install", "dotagents:sync": "dotagents sync", - "playground": "turbo run playground --filter=\"braintrust\"", - "playground:cli:push": "turbo run playground:cli:push --filter=\"braintrust\"", - "playground:cli:eval": "turbo run playground:cli:eval --filter=\"braintrust\"", - "formatting": "prettier --check .", - "lint": "turbo run lint", - "fix:formatting": "prettier --write .", - "fix:lint": "turbo run fix:lint", - "yalc:publish": "turbo run yalc:publish", - "test:e2e:cassettes": "turbo run test:e2e -- --check-cassettes" + "changeset": "changeset", + "playground": "vp run --filter=braintrust playground", + "formatting": "vp fmt --check .", + "lint": "vp lint .", + "fix:formatting": "vp fmt --write .", + "fix:lint": "vp lint --fix .", + "yalc:publish": "vp run -r yalc:publish", + "pretest:e2e": "pnpm run build", + "pretest:e2e:update": "pnpm run build", + "pretest": "pnpm run build", + "precheck:typings": "pnpm run build", + "check": "vp check", + "test:e2e:cassettes": "pnpm run test:e2e -- --check-cassettes" }, "engines": { "pnpm": ">=10.27.0", @@ -43,55 +47,12 @@ "husky": "^9.1.7", "knip": "^5.85.0", "lint-staged": "^16.2.7", - "prettier": "^3.8.1", - "turbo": "^2.9.14", + "vite": "catalog:", + "vite-plus": "catalog:", "yalc": "1.0.0-pre.53" }, "lint-staged": { - "**/*.{js,jsx,ts,tsx,mjs,cjs,css,md,mdx,html,yaml,yml,json}": "prettier --write" + "**/*.{js,jsx,ts,tsx,mjs,cjs,css,md,mdx,html,yaml,yml,json}": "vp fmt --write" }, - "packageManager": "pnpm@10.33.0", - "pnpm": { - "overrides": { - "handlebars": "^4.7.10", - "protobufjs": "^7.6.5", - "body-parser@>=2.0.0 <2.3.0": "^2.3.0", - "brace-expansion@>=3.0.0 <5.0.12": "^5.0.12", - "esbuild": "0.28.1", - "uuid": "11.1.1", - "router@2.2.0>path-to-regexp": "8.4.0", - "express@5.2.1>qs": "6.16.0", - "qs@>=6.7.0 <6.16.0": "^6.16.0", - "tar@>=7.0.0 <7.5.21": "^7.5.21", - "tar@>=6.0.0 <7.0.0": "^6.2.1", - "flatted@<3.4.2": "^3.4.2", - "markdown-it@>=13.0.0 <14.1.1": "^14.1.1", - "@mikro-orm/knex@>=6.0.0 <6.6.14": "^6.6.14", - "glob@>=10.2.0 <10.5.0": "^10.5.0", - "fast-uri@<3.1.8": "^3.1.8", - "linkify-it@<5.0.2": "^5.0.2", - "@hono/node-server@<1.19.15": "^1.19.15", - "nanoid@<3.3.18": "^3.3.18", - "postcss@<8.5.23": "^8.5.23", - "js-yaml@3": "^3.15.1", - "js-yaml@4": "^4.3.1", - "@opentelemetry/core@2": "^2.8.0", - "@ai-sdk/provider-utils": "^4.0.33", - "brace-expansion@<1.1.21": "^1.1.21", - "brace-expansion@>=2.0.0 <2.1.7": "^2.1.7", - "ip-address@<10.7.1": "^10.7.1", - "hono@<4.13.7": "^4.13.7", - "@grpc/grpc-js@>=1.14.0 <1.14.5": "^1.14.5", - "@modelcontextprotocol/sdk@>=1.12.0 <1.31.0": "^1.31.0", - "smol-toml@<1.9.0": "^1.9.0", - "source-map-js@<1.2.2": "^1.2.2", - "proxy-addr@<2.0.8": "^2.0.8" - }, - "ignoredBuiltDependencies": [ - "@swc/core", - "esbuild", - "msw", - "protobufjs" - ] - } + "packageManager": "pnpm@10.33.0" } diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 8140e03fb..cf52f8767 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -4,6 +4,18 @@ settings: autoInstallPeers: true excludeLinksFromLockfile: false +catalogs: + default: + vite: + specifier: npm:@voidzero-dev/vite-plus-core@1.1.0 + version: 1.1.0 + vite-plus: + specifier: 1.1.0 + version: 1.1.0 + vitest: + specifier: 5.0.3 + version: 5.0.3 + overrides: handlebars: ^4.7.10 protobufjs: ^7.6.5 @@ -38,6 +50,8 @@ overrides: smol-toml@<1.9.0: ^1.9.0 source-map-js@<1.2.2: ^1.2.2 proxy-addr@<2.0.8: ^2.0.8 + vite@*: npm:@voidzero-dev/vite-plus-core@1.1.0 + vitest@*: 5.0.3 importers: @@ -64,12 +78,12 @@ importers: lint-staged: specifier: ^16.2.7 version: 16.2.7 - prettier: - specifier: ^3.8.1 - version: 3.8.1 - turbo: - specifier: ^2.9.14 - version: 2.9.14 + vite: + specifier: 'catalog:' + version: '@voidzero-dev/vite-plus-core@1.1.0(@arethetypeswrong/core@0.18.5)(@types/node@24.12.4)(esbuild@0.28.1)(jiti@2.6.1)(publint@0.3.25)(terser@5.44.1)(tsx@4.21.0)(typescript@5.9.3)(yaml@2.9.0)' + vite-plus: + specifier: 'catalog:' + version: 1.1.0(@arethetypeswrong/core@0.18.5)(@opentelemetry/api@1.9.0)(@types/node@24.12.4)(@vitest/ui@5.0.3(vitest@5.0.3))(esbuild@0.28.1)(jiti@2.6.1)(msw@2.13.6(@types/node@24.12.4)(typescript@5.9.3))(publint@0.3.25)(terser@5.44.1)(tsx@4.21.0)(typescript@5.9.3)(yaml@2.9.0) yalc: specifier: 1.0.0-pre.53 version: 1.0.0-pre.53 @@ -83,24 +97,24 @@ importers: '@types/node': specifier: ^22.10.0 version: 22.19.1 - tsup: - specifier: ^8.5.1 - version: 8.5.1(@swc/core@1.15.8)(jiti@2.6.1)(postcss@8.5.26)(tsx@4.21.0)(typescript@5.9.3)(yaml@2.9.0) typescript: specifier: ^5.7.2 version: 5.9.3 + vite-plus: + specifier: 'catalog:' + version: 1.1.0(@arethetypeswrong/core@0.18.5)(@opentelemetry/api@1.9.0)(@types/node@22.19.1)(@vitest/ui@5.0.3(vitest@5.0.3))(esbuild@0.28.1)(jiti@2.6.1)(msw@2.13.6(@types/node@22.19.1)(typescript@5.9.3))(publint@0.3.25)(terser@5.44.1)(tsx@4.21.0)(typescript@5.9.3)(yaml@2.9.0) e2e: devDependencies: '@braintrust/audio': specifier: workspace:^ version: link:../integrations/audio + '@braintrust/browser': + specifier: workspace:^ + version: link:../integrations/browser-js '@braintrust/deepseek-harness': specifier: workspace:^ version: link:../integrations/deepseek-harness - '@braintrust/langchain-js': - specifier: workspace:^ - version: link:../integrations/langchain-js '@braintrust/otel': specifier: workspace:^ version: link:../integrations/otel-js @@ -128,9 +142,12 @@ importers: typescript: specifier: 5.4.4 version: 5.4.4 + vite-plus: + specifier: 'catalog:' + version: 1.1.0(@arethetypeswrong/core@0.18.5)(@opentelemetry/api@1.9.0)(@types/node@20.19.40)(@vitest/ui@5.0.3(vitest@5.0.3))(esbuild@0.28.1)(jiti@2.6.1)(msw@2.13.6(@types/node@20.19.40)(typescript@5.4.4))(publint@0.3.25)(terser@5.44.1)(tsx@4.21.0)(typescript@5.4.4)(yaml@2.9.0) vitest: - specifier: 4.1.11 - version: 4.1.11(@opentelemetry/api@1.9.0)(@types/node@20.19.40)(msw@2.13.6(@types/node@20.19.40)(typescript@5.4.4))(vite@8.1.5(@types/node@20.19.40)(esbuild@0.28.1)(jiti@2.6.1)(terser@5.44.1)(tsx@4.21.0)(yaml@2.9.0)) + specifier: 'catalog:' + version: 5.0.3(@opentelemetry/api@1.9.0)(@types/node@20.19.40)(@vitest/browser-preview@5.0.3)(@vitest/ui@5.0.3(vitest@5.0.3))(@voidzero-dev/vite-plus-core@1.1.0(@arethetypeswrong/core@0.18.5)(@types/node@20.19.40)(esbuild@0.28.1)(jiti@2.6.1)(publint@0.3.25)(terser@5.44.1)(tsx@4.21.0)(typescript@5.4.4)(yaml@2.9.0))(msw@2.13.6(@types/node@20.19.40)(typescript@5.4.4)) zod: specifier: 4.3.6 version: 4.3.6 @@ -152,13 +169,13 @@ importers: version: 1.7.5 tsup: specifier: ^8.5.1 - version: 8.5.1(@swc/core@1.15.8)(jiti@2.6.1)(postcss@8.5.26)(tsx@4.21.0)(typescript@5.9.3)(yaml@2.9.0) + version: 8.5.1(@swc/core@1.15.8)(jiti@2.6.1)(postcss@8.5.29)(tsx@4.21.0)(typescript@5.9.3)(yaml@2.9.0) typescript: specifier: ^5.3.3 version: 5.9.3 - vitest: - specifier: 4.1.11 - version: 4.1.11(@opentelemetry/api@1.9.0)(@types/node@20.19.40)(msw@2.13.6(@types/node@20.19.40)(typescript@5.9.3))(vite@8.1.5(@types/node@20.19.40)(esbuild@0.28.1)(jiti@2.6.1)(terser@5.44.1)(tsx@4.21.0)(yaml@2.9.0)) + vite-plus: + specifier: 'catalog:' + version: 1.1.0(@arethetypeswrong/core@0.18.5)(@opentelemetry/api@1.9.0)(@types/node@20.19.40)(@vitest/ui@5.0.3(vitest@5.0.3))(esbuild@0.28.1)(jiti@2.6.1)(msw@2.13.6(@types/node@20.19.40)(typescript@5.9.3))(publint@0.3.25)(terser@5.44.1)(tsx@4.21.0)(typescript@5.9.3)(yaml@2.9.0) integrations/browser-js: dependencies: @@ -175,12 +192,12 @@ importers: '@types/node': specifier: ^20.10.5 version: 20.19.40 - tsup: - specifier: ^8.5.1 - version: 8.5.1(@swc/core@1.15.8)(jiti@2.6.1)(postcss@8.5.26)(tsx@4.21.0)(typescript@5.9.3)(yaml@2.9.0) typescript: specifier: ^5.3.3 version: 5.9.3 + vite-plus: + specifier: 'catalog:' + version: 1.1.0(@arethetypeswrong/core@0.18.5)(@opentelemetry/api@1.9.0)(@types/node@20.19.40)(@vitest/ui@5.0.3(vitest@5.0.3))(esbuild@0.28.1)(jiti@2.6.1)(msw@2.13.6(@types/node@20.19.40)(typescript@5.9.3))(publint@0.3.25)(terser@5.44.1)(tsx@4.21.0)(typescript@5.9.3)(yaml@2.9.0) integrations/deepseek-harness: dependencies: @@ -209,78 +226,12 @@ importers: '@types/node': specifier: ^22.15.21 version: 22.19.1 - tsup: - specifier: ^8.5.1 - version: 8.5.1(@swc/core@1.15.8)(jiti@2.6.1)(postcss@8.5.26)(tsx@4.21.0)(typescript@5.9.3)(yaml@2.9.0) typescript: specifier: ^5.5.4 version: 5.9.3 - vitest: - specifier: 4.1.11 - version: 4.1.11(@opentelemetry/api@1.9.0)(@types/node@22.19.1)(msw@2.13.6(@types/node@22.19.1)(typescript@5.9.3))(vite@8.1.5(@types/node@22.19.1)(esbuild@0.28.1)(jiti@2.6.1)(terser@5.44.1)(tsx@4.21.0)(yaml@2.9.0)) - - integrations/langchain-js: - devDependencies: - '@langchain/anthropic': - specifier: ^1.3.1 - version: 1.3.5(@langchain/core@1.1.10(@opentelemetry/api@1.9.0)(@opentelemetry/sdk-trace-base@2.6.1(@opentelemetry/api@1.9.0))(openai@6.32.0(ws@8.21.3)(zod@3.25.76))(ws@8.21.3)) - '@langchain/core': - specifier: ^1.1.6 - version: 1.1.10(@opentelemetry/api@1.9.0)(@opentelemetry/sdk-trace-base@2.6.1(@opentelemetry/api@1.9.0))(openai@6.32.0(ws@8.21.3)(zod@3.25.76))(ws@8.21.3) - '@langchain/langgraph': - specifier: ^1.0.7 - version: 1.0.7(@langchain/core@1.1.10(@opentelemetry/api@1.9.0)(@opentelemetry/sdk-trace-base@2.6.1(@opentelemetry/api@1.9.0))(openai@6.32.0(ws@8.21.3)(zod@3.25.76))(ws@8.21.3))(react-dom@19.2.4(react@19.2.4))(react@19.2.4)(zod-to-json-schema@3.25.1(zod@3.25.76))(zod@3.25.76) - '@langchain/openai': - specifier: ^1.2.0 - version: 1.2.1(@langchain/core@1.1.10(@opentelemetry/api@1.9.0)(@opentelemetry/sdk-trace-base@2.6.1(@opentelemetry/api@1.9.0))(openai@6.32.0(ws@8.21.3)(zod@3.25.76))(ws@8.21.3))(ws@8.21.3) - '@types/node': - specifier: ^20.10.5 - version: 20.19.40 - braintrust: - specifier: workspace:* - version: link:../../js - msw: - specifier: ^2.6.6 - version: 2.13.6(@types/node@20.19.40)(typescript@5.9.3) - tsup: - specifier: ^8.5.1 - version: 8.5.1(@swc/core@1.15.8)(jiti@2.6.1)(postcss@8.5.26)(tsx@4.21.0)(typescript@5.9.3)(yaml@2.9.0) - typescript: - specifier: ^5.3.3 - version: 5.9.3 - vitest: - specifier: 4.1.11 - version: 4.1.11(@opentelemetry/api@1.9.0)(@types/node@20.19.40)(msw@2.13.6(@types/node@20.19.40)(typescript@5.9.3))(vite@8.1.5(@types/node@20.19.40)(esbuild@0.28.1)(jiti@2.6.1)(terser@5.44.1)(tsx@4.21.0)(yaml@2.9.0)) - zod: - specifier: ^3.25.34 - version: 3.25.76 - zod-to-json-schema: - specifier: ^3.22.5 - version: 3.25.1(zod@3.25.76) - - integrations/openai-agents-js: - devDependencies: - '@openai/agents': - specifier: ^0.0.15 - version: 0.0.15(ws@8.21.3)(zod@3.25.76) - '@types/node': - specifier: ^20.10.5 - version: 20.19.40 - braintrust: - specifier: workspace:^ - version: link:../../js - tsup: - specifier: ^8.5.1 - version: 8.5.1(@swc/core@1.15.8)(jiti@2.6.1)(postcss@8.5.26)(tsx@4.21.0)(typescript@5.9.3)(yaml@2.9.0) - typescript: - specifier: ^5.3.3 - version: 5.9.3 - vitest: - specifier: 4.1.11 - version: 4.1.11(@opentelemetry/api@1.9.0)(@types/node@20.19.40)(msw@2.13.6(@types/node@20.19.40)(typescript@5.9.3))(vite@8.1.5(@types/node@20.19.40)(esbuild@0.28.1)(jiti@2.6.1)(terser@5.44.1)(tsx@4.21.0)(yaml@2.9.0)) - zod: - specifier: ^3.25.34 - version: 3.25.76 + vite-plus: + specifier: 'catalog:' + version: 1.1.0(@arethetypeswrong/core@0.18.5)(@opentelemetry/api@1.9.0)(@types/node@22.19.1)(@vitest/ui@5.0.3(vitest@5.0.3))(esbuild@0.28.1)(jiti@2.6.1)(msw@2.13.6(@types/node@22.19.1)(typescript@5.9.3))(publint@0.3.25)(terser@5.44.1)(tsx@4.21.0)(typescript@5.9.3)(yaml@2.9.0) integrations/otel-js: dependencies: @@ -306,15 +257,12 @@ importers: braintrust: specifier: workspace:* version: link:../../js - tsup: - specifier: ^8.5.0 - version: 8.5.1(@swc/core@1.15.8)(jiti@2.6.1)(postcss@8.5.26)(tsx@4.21.0)(typescript@5.5.4)(yaml@2.9.0) typescript: specifier: 5.5.4 version: 5.5.4 - vitest: - specifier: 4.1.11 - version: 4.1.11(@opentelemetry/api@1.9.0)(@types/node@22.19.1)(msw@2.13.6(@types/node@22.19.1)(typescript@5.5.4))(vite@8.1.5(@types/node@22.19.1)(esbuild@0.28.1)(jiti@2.6.1)(terser@5.44.1)(tsx@4.21.0)(yaml@2.9.0)) + vite-plus: + specifier: 'catalog:' + version: 1.1.0(@arethetypeswrong/core@0.18.5)(@opentelemetry/api@1.9.0)(@types/node@22.19.1)(@vitest/ui@5.0.3(vitest@5.0.3))(esbuild@0.28.1)(jiti@2.6.1)(msw@2.13.6(@types/node@22.19.1)(typescript@5.5.4))(publint@0.3.25)(terser@5.44.1)(tsx@4.21.0)(typescript@5.5.4)(yaml@2.9.0) integrations/templates-nunjucks: dependencies: @@ -331,15 +279,15 @@ importers: braintrust: specifier: workspace:* version: link:../../js - tsup: - specifier: ^8.5.1 - version: 8.5.1(@swc/core@1.15.8)(jiti@2.6.1)(postcss@8.5.26)(tsx@4.21.0)(typescript@5.5.4)(yaml@2.9.0) typescript: specifier: 5.5.4 version: 5.5.4 + vite-plus: + specifier: 'catalog:' + version: 1.1.0(@arethetypeswrong/core@0.18.5)(@opentelemetry/api@1.9.0)(@types/node@20.19.40)(@vitest/ui@5.0.3(vitest@5.0.3))(esbuild@0.28.1)(jiti@2.6.1)(msw@2.13.6(@types/node@20.19.40)(typescript@5.5.4))(publint@0.3.25)(terser@5.44.1)(tsx@4.21.0)(typescript@5.5.4)(yaml@2.9.0) vitest: - specifier: 4.1.11 - version: 4.1.11(@opentelemetry/api@1.9.0)(@types/node@20.19.40)(msw@2.13.6(@types/node@20.19.40)(typescript@5.5.4))(vite@8.1.5(@types/node@20.19.40)(esbuild@0.28.1)(jiti@2.6.1)(terser@5.44.1)(tsx@4.21.0)(yaml@2.9.0)) + specifier: 'catalog:' + version: 5.0.3(@opentelemetry/api@1.9.0)(@types/node@20.19.40)(@vitest/browser-preview@5.0.3)(@vitest/ui@5.0.3)(@voidzero-dev/vite-plus-core@1.1.0(@arethetypeswrong/core@0.18.5)(@types/node@20.19.40)(esbuild@0.28.1)(jiti@2.6.1)(publint@0.3.25)(terser@5.44.1)(tsx@4.21.0)(typescript@5.5.4)(yaml@2.9.0))(msw@2.13.6(@types/node@20.19.40)(typescript@5.5.4)) integrations/temporal-js: devDependencies: @@ -354,7 +302,7 @@ importers: version: 1.14.1 '@temporalio/worker': specifier: ^1.14.1 - version: 1.14.1(esbuild@0.28.1)(postcss@8.5.26) + version: 1.14.1(esbuild@0.28.1) '@temporalio/workflow': specifier: ^1.14.1 version: 1.14.1 @@ -364,39 +312,18 @@ importers: braintrust: specifier: workspace:* version: link:../../js - tsup: - specifier: ^8.5.0 - version: 8.5.1(@swc/core@1.15.8)(jiti@2.6.1)(postcss@8.5.26)(tsx@4.21.0)(typescript@5.5.4)(yaml@2.9.0) typescript: specifier: 5.5.4 version: 5.5.4 + vite-plus: + specifier: 'catalog:' + version: 1.1.0(@arethetypeswrong/core@0.18.5)(@opentelemetry/api@1.9.0)(@types/node@22.19.1)(@vitest/ui@5.0.3(vitest@5.0.3))(esbuild@0.28.1)(jiti@2.6.1)(msw@2.13.6(@types/node@22.19.1)(typescript@5.5.4))(publint@0.3.25)(terser@5.44.1)(tsx@4.21.0)(typescript@5.5.4)(yaml@2.9.0) vitest: - specifier: 4.1.11 - version: 4.1.11(@opentelemetry/api@1.9.0)(@types/node@22.19.1)(msw@2.13.6(@types/node@22.19.1)(typescript@5.5.4))(vite@8.1.5(@types/node@22.19.1)(esbuild@0.28.1)(jiti@2.6.1)(terser@5.44.1)(tsx@4.21.0)(yaml@2.9.0)) - - integrations/vercel-ai-sdk: - devDependencies: - '@types/node': - specifier: ^20.10.5 - version: 20.19.40 - braintrust: - specifier: workspace:* - version: link:../../js - tsup: - specifier: ^8.5.1 - version: 8.5.1(@swc/core@1.15.8)(jiti@2.6.1)(postcss@8.5.26)(tsx@4.21.0)(typescript@5.9.3)(yaml@2.9.0) - typescript: - specifier: ^5.3.3 - version: 5.9.3 - vitest: - specifier: 4.1.11 - version: 4.1.11(@opentelemetry/api@1.9.0)(@types/node@20.19.40)(msw@2.13.6(@types/node@20.19.40)(typescript@5.9.3))(vite@8.1.5(@types/node@20.19.40)(esbuild@0.28.1)(jiti@2.6.1)(terser@5.44.1)(tsx@4.21.0)(yaml@2.9.0)) + specifier: 'catalog:' + version: 5.0.3(@opentelemetry/api@1.9.0)(@types/node@22.19.1)(@vitest/browser-preview@5.0.3)(@vitest/ui@5.0.3)(@voidzero-dev/vite-plus-core@1.1.0(@arethetypeswrong/core@0.18.5)(@types/node@22.19.1)(esbuild@0.28.1)(jiti@2.6.1)(publint@0.3.25)(terser@5.44.1)(tsx@4.21.0)(typescript@5.5.4)(yaml@2.9.0))(msw@2.13.6(@types/node@22.19.1)(typescript@5.5.4)) js: dependencies: - '@next/env': - specifier: ^14.2.3 - version: 14.2.3 '@vercel/functions': specifier: ^1.0.2 version: 1.0.2 @@ -409,66 +336,36 @@ importers: ajv: specifier: ^8.20.0 version: 8.20.0 - argparse: - specifier: ^2.0.1 - version: 2.0.1 astring: specifier: ^1.9.0 version: 1.9.0 cjs-module-lexer: specifier: ^2.2.0 version: 2.2.0 - cli-progress: - specifier: ^3.12.0 - version: 3.12.0 - cli-table3: - specifier: ^0.6.5 - version: 0.6.5 - cors: - specifier: ^2.8.5 - version: 2.8.5 dotenv: specifier: ^16.4.5 version: 16.4.5 - esbuild: - specifier: 0.28.1 - version: 0.28.1 esquery: specifier: ^1.7.0 version: 1.7.0 eventsource-parser: specifier: ^1.1.2 version: 1.1.2 - express: - specifier: ^5.2.1 - version: 5.2.1 - http-errors: - specifier: ^2.0.0 - version: 2.0.1 meriyah: specifier: ^6.1.4 version: 6.1.4 - minimatch: - specifier: ^10.2.5 - version: 10.2.5 module-details-from-path: specifier: ^1.0.4 version: 1.0.4 mustache: specifier: ^4.2.0 version: 4.2.0 - pluralize: - specifier: ^8.0.0 - version: 8.0.0 semifies: specifier: ^1.0.0 version: 1.0.0 source-map: specifier: ^0.7.4 version: 0.7.6 - termi-link: - specifier: ^1.0.1 - version: 1.1.0 unplugin: specifier: ^2.3.5 version: 2.3.11 @@ -482,48 +379,21 @@ importers: '@anthropic-ai/sdk': specifier: ^0.60.0 version: 0.60.0 - '@nodelib/fs.walk': - specifier: ^1.2.8 - version: 1.2.8 - '@types/argparse': - specifier: ^2.0.14 - version: 2.0.14 '@types/async': specifier: ^3.2.24 version: 3.2.24 - '@types/cli-progress': - specifier: ^3.11.5 - version: 3.11.5 - '@types/cors': - specifier: ^2.8.17 - version: 2.8.17 '@types/esquery': specifier: ^1.5.4 version: 1.5.4 - '@types/express': - specifier: ^5.0.0 - version: 5.0.1 - '@types/http-errors': - specifier: ^2.0.4 - version: 2.0.4 '@types/mustache': specifier: ^4.2.5 version: 4.2.5 '@types/node': specifier: ^20.10.5 version: 20.19.40 - '@types/pluralize': - specifier: ^0.0.30 - version: 0.0.30 '@types/react': specifier: ^18.3.1 version: 18.3.31 - '@typescript-eslint/eslint-plugin': - specifier: ^8.49.0 - version: 8.50.0(@typescript-eslint/parser@8.50.0(eslint@9.39.4(jiti@2.6.1))(typescript@5.4.4))(eslint@9.39.4(jiti@2.6.1))(typescript@5.4.4) - '@typescript-eslint/parser': - specifier: ^8.49.0 - version: 8.50.0(eslint@9.39.4(jiti@2.6.1))(typescript@5.4.4) ai: specifier: ^6.0.0 version: 6.0.37(zod@3.25.76) @@ -533,18 +403,12 @@ importers: cross-env: specifier: ^7.0.3 version: 7.0.3 - eslint: - specifier: ^9.39.2 - version: 9.39.4(jiti@2.6.1) - eslint-plugin-es-x: - specifier: 9.7.0 - version: 9.7.0(eslint@9.39.4(jiti@2.6.1)) + esbuild: + specifier: 0.28.1 + version: 0.28.1 eslint-plugin-n: specifier: ^18.2.1 version: 18.2.1(eslint@9.39.4(jiti@2.6.1))(ts-declaration-location@1.0.7(typescript@5.4.4))(typescript@5.4.4) - eslint-plugin-node-import: - specifier: ^1.0.5 - version: 1.0.5(eslint@9.39.4(jiti@2.6.1)) openai: specifier: 6.25.0 version: 6.25.0(ws@8.21.3)(zod@3.25.76) @@ -560,9 +424,6 @@ importers: ts-declaration-location: specifier: ^1.0.7 version: 1.0.7(typescript@5.4.4) - tsup: - specifier: ^8.5.1 - version: 8.5.1(@swc/core@1.15.8)(jiti@2.6.1)(postcss@8.5.26)(tsx@4.21.0)(typescript@5.4.4)(yaml@2.9.0) tsx: specifier: ^4.21.0 version: 4.21.0 @@ -576,42 +437,20 @@ importers: specifier: 5.4.4 version: 5.4.4 vite: - specifier: ^8.1.5 - version: 8.1.5(@types/node@20.19.40)(esbuild@0.28.1)(jiti@2.6.1)(terser@5.44.1)(tsx@4.21.0)(yaml@2.9.0) - vite-tsconfig-paths: - specifier: ^4.3.2 - version: 4.3.2(typescript@5.4.4)(vite@8.1.5(@types/node@20.19.40)(esbuild@0.28.1)(jiti@2.6.1)(terser@5.44.1)(tsx@4.21.0)(yaml@2.9.0)) + specifier: 'catalog:' + version: '@voidzero-dev/vite-plus-core@1.1.0(@arethetypeswrong/core@0.18.5)(@types/node@20.19.40)(esbuild@0.28.1)(jiti@2.6.1)(publint@0.3.25)(terser@5.44.1)(tsx@4.21.0)(typescript@5.4.4)(yaml@2.9.0)' + vite-plus: + specifier: 'catalog:' + version: 1.1.0(@arethetypeswrong/core@0.18.5)(@opentelemetry/api@1.9.0)(@types/node@20.19.40)(@vitest/ui@5.0.3(vitest@5.0.3))(esbuild@0.28.1)(jiti@2.6.1)(msw@2.13.6(@types/node@20.19.40)(typescript@5.4.4))(publint@0.3.25)(terser@5.44.1)(tsx@4.21.0)(typescript@5.4.4)(yaml@2.9.0) vitest: - specifier: 4.1.11 - version: 4.1.11(@opentelemetry/api@1.9.0)(@types/node@20.19.40)(msw@2.13.6(@types/node@20.19.40)(typescript@5.4.4))(vite@8.1.5(@types/node@20.19.40)(esbuild@0.28.1)(jiti@2.6.1)(terser@5.44.1)(tsx@4.21.0)(yaml@2.9.0)) + specifier: 'catalog:' + version: 5.0.3(@opentelemetry/api@1.9.0)(@types/node@20.19.40)(@vitest/browser-preview@5.0.3)(@vitest/ui@5.0.3(vitest@5.0.3))(@voidzero-dev/vite-plus-core@1.1.0(@arethetypeswrong/core@0.18.5)(@types/node@20.19.40)(esbuild@0.28.1)(jiti@2.6.1)(publint@0.3.25)(terser@5.44.1)(tsx@4.21.0)(typescript@5.4.4)(yaml@2.9.0))(msw@2.13.6(@types/node@20.19.40)(typescript@5.4.4)) webpack: specifier: ^5.106.2 - version: 5.106.2(@swc/core@1.15.8)(esbuild@0.28.1)(postcss@8.5.26) + version: 5.106.2(esbuild@0.28.1) zod: specifier: ^3.25.34 version: 3.25.76 - optionalDependencies: - '@braintrust/bt-darwin-arm64': - specifier: 0.12.0 - version: 0.12.0 - '@braintrust/bt-darwin-x64': - specifier: 0.12.0 - version: 0.12.0 - '@braintrust/bt-linux-arm64': - specifier: 0.12.0 - version: 0.12.0 - '@braintrust/bt-linux-x64': - specifier: 0.12.0 - version: 0.12.0 - '@braintrust/bt-linux-x64-musl': - specifier: 0.12.0 - version: 0.12.0 - '@braintrust/bt-win32-arm64': - specifier: 0.12.0 - version: 0.12.0 - '@braintrust/bt-win32-x64': - specifier: 0.12.0 - version: 0.12.0 js/src/wrappers/vitest: devDependencies: @@ -621,9 +460,12 @@ importers: typescript: specifier: 5.4.4 version: 5.4.4 + vite-plus: + specifier: 'catalog:' + version: 1.1.0(@arethetypeswrong/core@0.18.5)(@opentelemetry/api@1.9.0)(@types/node@20.19.40)(@vitest/ui@5.0.3(vitest@5.0.3))(esbuild@0.28.1)(jiti@2.6.1)(msw@2.13.6(@types/node@20.19.40)(typescript@5.4.4))(publint@0.3.25)(terser@5.44.1)(tsx@4.21.0)(typescript@5.4.4)(yaml@2.9.0) vitest: - specifier: 4.1.11 - version: 4.1.11(@opentelemetry/api@1.9.0)(@types/node@20.19.40)(msw@2.13.6(@types/node@20.19.40)(typescript@5.4.4))(vite@8.1.5(@types/node@20.19.40)(esbuild@0.28.1)(jiti@2.6.1)(terser@5.44.1)(tsx@4.21.0)(yaml@2.9.0)) + specifier: 'catalog:' + version: 5.0.3(@opentelemetry/api@1.9.0)(@types/node@20.19.40)(@vitest/browser-preview@5.0.3)(@vitest/ui@5.0.3(vitest@5.0.3))(@voidzero-dev/vite-plus-core@1.1.0(@arethetypeswrong/core@0.18.5)(@types/node@20.19.40)(esbuild@0.28.1)(jiti@2.6.1)(publint@0.3.25)(terser@5.44.1)(tsx@4.21.0)(typescript@5.4.4)(yaml@2.9.0))(msw@2.13.6(@types/node@20.19.40)(typescript@5.4.4)) packages: @@ -647,67 +489,38 @@ packages: resolution: {integrity: sha512-qGPYdoAuECaUXPrrz0BPX1SacZQuJ6zky0aakxpW89QW1hrY0eF4gcFm/3L9Pk8C5Fwe+RvBf2z7ZjDhaPjnlg==} engines: {node: '>=18'} + '@andrewbranch/untar.js@1.0.4': + resolution: {integrity: sha512-pVXSwPsLuw8IGLo2Di0EaOfsk+ntVvpkk942J/sHYIkwvtKUakEcPh7HBgZ6tuimgzKSEHgCvO4XgQ05DEbwDw==} + '@anthropic-ai/sdk@0.60.0': resolution: {integrity: sha512-9zu/TXaUy8BZhXedDtt1wT3H4LOlpKDO1/ftiFpeR3N1PCr3KJFKkxxlQWWt1NNp08xSwUNJ3JNY8yhl8av6eQ==} hasBin: true - '@anthropic-ai/sdk@0.71.2': - resolution: {integrity: sha512-TGNDEUuEstk/DKu0/TflXAEt+p+p/WhTlFzEnoosvbaDU2LTjm42igSdlL0VijrKpWejtOKxX0b8A7uc+XiSAQ==} - hasBin: true - peerDependencies: - zod: ^3.25.0 || ^4.0.0 - peerDependenciesMeta: - zod: - optional: true + '@arethetypeswrong/core@0.18.5': + resolution: {integrity: sha512-9ytjzGwxjm9Uz7I9avfbt5vlQt6uk9uRRESzJjqrznl6WKvI6dwYTo+vJ3U02Wrq/mR3iql/PzhvHhKdJIAjDQ==} + engines: {node: '>=20'} '@audio/encode-opus@1.3.1': resolution: {integrity: sha512-FFNQgim/doIajnOyDidaIx541HI51VoMKWQk0NXFf2XP4bLgduR7kSmwoPXzF5TPCt7axC+Wu6IuTOGyuVWQPg==} engines: {node: '>=18'} - '@babel/runtime@7.28.4': - resolution: {integrity: sha512-Q/N6JNWvIvPnLDvjlE1OUBLPQHH6l3CltCEsHIujp45zQUSSh8K+gHnaEX45yAT1nyngnINhvWtzN+Nb9D8RAQ==} + '@babel/code-frame@7.29.7': + resolution: {integrity: sha512-Aup7aUOfpbAUg2ROOJN6Iw5f9DMBlzu0mIkm/malLQFN/YQgO48wCj0Kxa3sEHJvPVFg7siR+qRInwXd2qhQKw==} engines: {node: '>=6.9.0'} - '@braintrust/bt-darwin-arm64@0.12.0': - resolution: {integrity: sha512-mY6VW/3VwcOQOGN8sYHS6F0xzHTFwgZcNlj7zlQttI6OXOCGt/bhonGIqd03QBhxmj0M31ymSS7TqSeCK/RYIQ==} - cpu: [arm64] - os: [darwin] - - '@braintrust/bt-darwin-x64@0.12.0': - resolution: {integrity: sha512-woyRyDv2DfCF8+von+3X9f1cddAbFnKLSfCbEkrBQVc0ZsAM60as8nehYv7DkafJF/67yKFx/sUraV65sukesQ==} - cpu: [x64] - os: [darwin] - - '@braintrust/bt-linux-arm64@0.12.0': - resolution: {integrity: sha512-J9/7f3EIMKmFmSSQHQnAQLpUgB8YJENrOlkABjlTprBgsIYVgz7tSH7yg2P3ur+2Jem7PpGnrDxHGh/UjRfjsg==} - cpu: [arm64] - os: [linux] - libc: [glibc] - - '@braintrust/bt-linux-x64-musl@0.12.0': - resolution: {integrity: sha512-KnLgENOoztBXcH+mLFJe4bYzi67kSOuELOQrm4Hler35GjgaBMI1fFLIUjKRPAmyT9VIhBMhy1ICfGEFLueMEQ==} - cpu: [x64] - os: [linux] - libc: [musl] - - '@braintrust/bt-linux-x64@0.12.0': - resolution: {integrity: sha512-HJFbUl3HYYY1Ivw8OYBeIMcIsU9r7+fC9BzLWB5FdH7881ToKee2wbbLZssMCxFbMVeUxzEo+SzGD/1Z9Gk9CA==} - cpu: [x64] - os: [linux] - libc: [glibc] + '@babel/helper-validator-identifier@7.29.7': + resolution: {integrity: sha512-qehxGkRj55h/ff8EMaJ+cYhyaKlHIxqYDn682wQD7RNp9UujOQsHog2uS0r2vzr4pW+sXf90NeeayjcNaX3fFg==} + engines: {node: '>=6.9.0'} - '@braintrust/bt-win32-arm64@0.12.0': - resolution: {integrity: sha512-+7PkdBAmiqEJsWteGHP/Zs62F75PkHPA8m/ej4TOz/mHGKulUU0bZibw91KRqIB7J7jGi5ItvCiqkiGaLKLnyw==} - cpu: [arm64] - os: [win32] + '@babel/runtime@7.28.4': + resolution: {integrity: sha512-Q/N6JNWvIvPnLDvjlE1OUBLPQHH6l3CltCEsHIujp45zQUSSh8K+gHnaEX45yAT1nyngnINhvWtzN+Nb9D8RAQ==} + engines: {node: '>=6.9.0'} - '@braintrust/bt-win32-x64@0.12.0': - resolution: {integrity: sha512-Q0c+pVUGm82n/7N8QJ5s6j/G/Q0GFzqOaTipHjkmElLbAOOEcfRH/uljdtIPNBiEQcrzqirS0GmOgiFkeQ3Txg==} - cpu: [x64] - os: [win32] + '@blazediff/core@1.10.0': + resolution: {integrity: sha512-AOQff0zgR7cGsZL+4E7hVkmujoPUpm0J9xzWGWZj5wCjd3gmxESXAPfKyuzs93VdpQNFhHlBhfOjrcZ+XTERtQ==} - '@cfworker/json-schema@4.0.3': - resolution: {integrity: sha512-ZykIcDTVv5UNmKWSTLAs3VukO6NDJkkSKxrgUTDPBkAlORVT3H9n5DbRjRl8xIotklscHdbLIa0b9+y3mQq73g==} + '@braidai/lang@1.1.2': + resolution: {integrity: sha512-qBcknbBufNHlui137Hft8xauQMTZDKdophmLFv05r2eNmdIv/MlPuP4TdUknHG68UdWLgVZwgxVe735HzJNIwA==} '@changesets/apply-release-plan@7.1.0': resolution: {integrity: sha512-yq8ML3YS7koKQ/9bk1PqO0HMzApIFNwjlwCnwFEXMzNe8NpzeeYYKCmnhWJGkN8g7E51MnWaSbqRcTcdIxUgnQ==} @@ -775,10 +588,6 @@ packages: resolution: {integrity: sha512-EYlRokl8szrP9Z25qT5aepMdBjzBvHF9ZEhzIiUBc9guz/T31EqRgvD0QSgZcpE93xiwrr+OkB4nz0BZyF6fSA==} engines: {node: '>= 20.12.0'} - '@colors/colors@1.5.0': - resolution: {integrity: sha512-ooWCrlZP11i8GImSjTHYHLkvFDP48nS4+204nGb1RiX/WXYHmJA2III9/e2DWVabCESdW7hBAEzHRqUn9OUVvQ==} - engines: {node: '>=0.1.90'} - '@deepseek-ai/cordis@4.0.1': resolution: {integrity: sha512-YBdskTU2Po1kru3GgcUWUbkTsPMA9LkSQDAY8rBkFJeajdgcQad3QPJZE26JyK99Xb6HaASvoXg2DSUTeN/0Nw==} hasBin: true @@ -1117,12 +926,6 @@ packages: engines: {node: '>=6'} hasBin: true - '@hono/node-server@1.19.17': - resolution: {integrity: sha512-dSneS5qhiauZWGDCeK4o695Xd9nUNjviSZCMQrj10eetr8Uln1ucn6bbphOM6UynAMMtNIzZNSpL9vnASJwrPQ==} - engines: {node: '>=18.14.1'} - peerDependencies: - hono: ^4.13.7 - '@humanfs/core@0.19.2': resolution: {integrity: sha512-UhXNm+CFMWcbChXywFwkmhqjs3PRCmcSa/hfBgLIb7oQ5HNb1wS0icWsGtSAUNgefHeI+eBrA8I1fxmbHsGdvA==} engines: {node: '>=18.18.0'} @@ -1187,10 +990,6 @@ packages: '@types/node': optional: true - '@isaacs/cliui@8.0.2': - resolution: {integrity: sha512-O8jcjabXaleOG9DQ0+ARXWZBTfnP4WNAqzuiJK7ll44AmxGKv/J2M4TPjxjY3znBCfvBXFzucm1twdyFybFqEA==} - engines: {node: '>=12'} - '@isaacs/fs-minipass@4.0.1': resolution: {integrity: sha512-wgm9Ehl2jpeqP3zw/7mo3kRHFp5MEDhqAdwy1fTGkHAwnkGOVsgpvQhL8B5n1qlb01jV3n/bI0ZfZp5lWA1k4w==} engines: {node: '>=18.0.0'} @@ -1253,52 +1052,8 @@ packages: peerDependencies: tslib: '2' - '@langchain/anthropic@1.3.5': - resolution: {integrity: sha512-oB+sfcBujxW/E3cRhGfR5K23+TwZKPPZPjatEadQOZllyPoen0xefIuNoi0oLWfobz3/LPwoNixGU4si3oyGBQ==} - engines: {node: '>=20'} - peerDependencies: - '@langchain/core': 1.1.10 - - '@langchain/core@1.1.10': - resolution: {integrity: sha512-/G5Uo8EvoZ1AuNjCToMEtDMexbfvjqQ60AaF8nxN2klTgCUlPo83U0M9JP0yRQW6QzkPF5T3VwEik76WcbZ1vg==} - engines: {node: '>=20'} - - '@langchain/langgraph-checkpoint@1.0.0': - resolution: {integrity: sha512-xrclBGvNCXDmi0Nz28t3vjpxSH6UYx6w5XAXSiiB1WEdc2xD2iY/a913I3x3a31XpInUW/GGfXXfePfaghV54A==} - engines: {node: '>=18'} - peerDependencies: - '@langchain/core': ^1.0.1 - - '@langchain/langgraph-sdk@1.3.1': - resolution: {integrity: sha512-zTi7DZHwqtMEzapvm3I1FL4Q7OZsxtq9tTXy6s2gcCxyIU3sphqRboqytqVN7dNHLdTCLb8nXy49QKurs2MIBg==} - peerDependencies: - '@langchain/core': ^1.0.1 - react: ^18 || ^19 - react-dom: ^18 || ^19 - peerDependenciesMeta: - '@langchain/core': - optional: true - react: - optional: true - react-dom: - optional: true - - '@langchain/langgraph@1.0.7': - resolution: {integrity: sha512-EBGqNOWoRiEoLUaeuiXRpUM8/DE6QcwiirNyd97XhezStebBoTTilWH8CUt6S94JRGl5zwfBBRHfzotDnZS/eA==} - engines: {node: '>=18'} - peerDependencies: - '@langchain/core': ^1.0.1 - zod: ^3.25.32 || ^4.1.0 - zod-to-json-schema: ^3.x - peerDependenciesMeta: - zod-to-json-schema: - optional: true - - '@langchain/openai@1.2.1': - resolution: {integrity: sha512-eZYPhvXIwz0/8iCjj2LWqeaznQ7DZ6tBdvF+Ebv4sQW2UqJWZqRC8QIdKZgTbs8ffMWPHkSSOidYqu4XfWCNYg==} - engines: {node: '>=20'} - peerDependencies: - '@langchain/core': ^1.0.0 + '@loaderkit/resolve@1.0.6': + resolution: {integrity: sha512-G8FdIoF5CypfwmD9rl8BXod5HDn8JqB0CCNBXDTaRZ+yRYhARrrSToX1zg1zy9jX3zLqigsELwhT4gNtkdQAUg==} '@manypkg/find-root@1.1.0': resolution: {integrity: sha512-mki5uBvhHzO8kYYix/WRy2WX8S3B5wdVSc9D6KcU5lQNglP2yt58/VfLuAK49glRXChosY8ap2oJ1qgma3GUVA==} @@ -1306,16 +1061,6 @@ packages: '@manypkg/get-packages@1.1.3': resolution: {integrity: sha512-fo+QhuU3qE/2TQMQmbVMqaQ6EWbMhi4ABWP+O4AM1NqPBuy0OrApV5LO6BrrgnhtAHS2NH6RrVk9OL181tTi8A==} - '@modelcontextprotocol/sdk@1.32.1': - resolution: {integrity: sha512-2DdE+SJDtzLEEWzY1ZjY7Q+VcPhcV1KisD3zI4u0XZyktsjHum1mwbMI+JaulUBi2OZk+KJAi2uPXzxichPkdw==} - engines: {node: '>=18'} - peerDependencies: - '@cfworker/json-schema': ^4.1.1 - zod: ^3.25 || ^4.0 - peerDependenciesMeta: - '@cfworker/json-schema': - optional: true - '@mswjs/interceptors@0.41.6': resolution: {integrity: sha512-qmDvJIjcNsZ6tXWy2G9yuCgMPTTn35GMA3dPpSLm7QJVpbQzYdw0ALy1bKoivXnEM3U93/OrK+/M719b+fg84Q==} engines: {node: '>=18'} @@ -1326,9 +1071,6 @@ packages: '@emnapi/core': ^1.7.1 '@emnapi/runtime': ^1.7.1 - '@next/env@14.2.3': - resolution: {integrity: sha512-W7fd7IbkfmeeY2gXrzJYDx8D2lWKbVoTIj1o1ScPHNzvp30s1AuoEFSdr39bC5sjxJaxTtq3OTCZboNp0lNWHA==} - '@nodelib/fs.scandir@2.1.5': resolution: {integrity: sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==} engines: {node: '>= 8'} @@ -1353,23 +1095,6 @@ packages: '@open-draft/until@2.1.0': resolution: {integrity: sha512-U69T3ItWHvLwGg5eJ0n3I62nWuE6ilHlmz7zM0npLBRvPRd7e6NYmg54vvRtP5mZG7kZqZCFVdsTWo7BPtBujg==} - '@openai/agents-core@0.0.15': - resolution: {integrity: sha512-ODTqttjW0s0ejBe5PKnYRlFbJSZH2IO6OtUlRhIKmWiWrX6pGRxvpKjTSOXy8DEtpRHBj6Nhky0UoSlO6eOkDQ==} - peerDependencies: - zod: 3.25.40 - 3.25.67 - peerDependenciesMeta: - zod: - optional: true - - '@openai/agents-openai@0.0.15': - resolution: {integrity: sha512-YIX3n98HdmmWKkb/71OB+DCQUYyGEpqfzPjejzdtNLUvAEs3jvXf7nkC8oTISsuCwrirgBz0rQEefeo0oUlyFQ==} - - '@openai/agents-realtime@0.0.15': - resolution: {integrity: sha512-kSZzMyij9Xt3BpMb/9snuVnu7a5qKZLyhtN/kWMA+wmfETvWz23BBz6tbO5xOmurAt9//OktkB+94e0T0RBtlA==} - - '@openai/agents@0.0.15': - resolution: {integrity: sha512-B8y+WyWOeHowflPx09pyCfcqikC4OYWK27HTyNGt1oraXv93CzuamSr76iAaU1nWQ1MPbUwl6LHPX4BPUikVkQ==} - '@opentelemetry/api-logs@0.208.0': resolution: {integrity: sha512-CjruKY9V6NMssL/T1kAFgzosF1v9o6oeN+aX5JB/C/xPNtmgIJqcXHG7fA82Ou1zCpWGl4lROQUKwUNE1pMCyg==} engines: {node: '>=8.0.0'} @@ -1448,8 +1173,12 @@ packages: resolution: {integrity: sha512-/UhIkaZgPutTFmQ7RnIJGgDXZmtEJ7Dvi86xNTFWcnRxVRNk/aotsqDJYeEvDP+FSMB2SdW+pQzNMcWP0rwuNA==} engines: {node: '>=14'} - '@oxc-project/types@0.139.0': - resolution: {integrity: sha512-r9gHphtCs+1M7J0pw6Sn/hh/Wpa/iQrOOkrNAlVLF/gHq+/CJmHIWKKUUhdWjcD6CIa8idarspCsASiXCXvFUw==} + '@oxc-project/runtime@0.153.0': + resolution: {integrity: sha512-rXJvtlsVG4xJNq64hiaUTIatE7jbBgpa6l4QgmEa2jLgT3ULH9scUBkpsC9N1DfmqixLEyT6DuK6OSXozziIlw==} + engines: {node: ^20.19.0 || >=22.12.0} + + '@oxc-project/types@0.153.0': + resolution: {integrity: sha512-jSNrRIyvPVLL5E1z/tS5zLAprosGz/FuI1PWwEOz59j3E4TTTzSpqKb/RDGqJhhW0QHYISGXxq582wGx6T11Tw==} '@oxc-resolver/binding-android-arm-eabi@11.19.1': resolution: {integrity: sha512-aUs47y+xyXHUKlbhqHUjBABjvycq6YSD7bpxSW7vplUmdzAlJ93yXY6ZR0c1o1x5A/QKbENCvs3+NlY8IpIVzg==} @@ -1559,184 +1288,367 @@ packages: cpu: [x64] os: [win32] - '@pkgjs/parseargs@0.11.0': - resolution: {integrity: sha512-+1VkjdD0QBLPodGrJUeqarH8VAIvQODIbwh9XpP5Syisf7YoQgsJKPNFoqqLQlu+VQ/tVSshMR6loPMn8U+dPg==} - engines: {node: '>=14'} - - '@protobufjs/aspromise@1.1.2': - resolution: {integrity: sha512-j+gKExEuLmKwvz3OgROXtrJ2UG2x8Ch2YZUxahh+s1F2HZ+wAceUNLkvy6zKCPVRkU++ZWQrdxsUeQXmcg4uoQ==} - - '@protobufjs/base64@1.1.2': - resolution: {integrity: sha512-AZkcAA5vnN/v4PDqKyMR5lx7hZttPDgClv83E//FMNhR2TMcLUhfRUBHCmSl0oi9zMgDDqRUJkSxO3wm85+XLg==} - - '@protobufjs/codegen@2.0.5': - resolution: {integrity: sha512-zgXFLzW3Ap33e6d0Wlj4MGIm6Ce8O89n/apUaGNB/jx+hw+ruWEp7EwGUshdLKVRCxZW12fp9r40E1mQrf/34g==} - - '@protobufjs/eventemitter@1.1.1': - resolution: {integrity: sha512-vW1GmwMZNnL+gMRaovlh9yZX74kc+TTU3FObkkurpMaRtBfLP3ldjS9KQWlwZgraRE0+dheEEoAxdzcJQ8eXZg==} - - '@protobufjs/fetch@1.1.1': - resolution: {integrity: sha512-GpptLrs57adMSuHi3VNj0mAF8dwh36LMaYF6XyJ6JMWlVsc+t42tm1HSEDmOs3A8fC9yyeisgLhsTVQokOZ0zw==} - - '@protobufjs/float@1.0.2': - resolution: {integrity: sha512-Ddb+kVXlXst9d+R9PfTIxh1EdNkgoRe5tOX6t01f1lYWOvJnSPDBlG241QLzcyPdoNTsblLUdujGSE4RzrTZGQ==} - - '@protobufjs/path@1.1.2': - resolution: {integrity: sha512-6JOcJ5Tm08dOHAbdR3GrvP+yUUfkjG5ePsHYczMFLq3ZmMkAD98cDgcT2iA1lJ9NVwFd4tH/iSSoe44YWkltEA==} - - '@protobufjs/pool@1.1.0': - resolution: {integrity: sha512-0kELaGSIDBKvcgS4zkjz1PeddatrjYcmMWOlAuAPwAeccUrPHdUqo/J6LiymHHEiJT5NrF1UVwxY14f+fy4WQw==} - - '@protobufjs/utf8@1.1.1': - resolution: {integrity: sha512-oOAWABowe8EAbMyWKM0tYDKi8Yaox52D+HWZhAIJqQXbqe0xI/GV7FhLWqlEKreMkfDjshR5FKgi3mnle0h6Eg==} + '@oxfmt/binding-android-arm-eabi@0.72.0': + resolution: {integrity: sha512-u4U1uaDOVBuorGNC/07NLm48nECHOiJJka002YGML16Md2DRvf38RGBt01lH60EOGvNlFVmbLoksQedNBoZHKw==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [arm] + os: [android] - '@rolldown/binding-android-arm64@1.1.5': - resolution: {integrity: sha512-lZg8fqIv2v7FF237bwMgzGZEJvGL79/s5knJ/i6FmsGF4XXlzccZ4jb+TrFIxtSSxFtIpdsgrPZeMk1I9AFcyQ==} + '@oxfmt/binding-android-arm64@0.72.0': + resolution: {integrity: sha512-GCnM+Ae2mWOKC+92y3FOb6eZJeVnBAVz82e3lITUUg+kxukYKzBNAvJ/hhHIfa2A0md55SYYhdWPddSSy3KPVA==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [arm64] os: [android] - '@rolldown/binding-darwin-arm64@1.1.5': - resolution: {integrity: sha512-51Bnx9pNiMRKSUNtBfySkNJ9vMU9Hh3I1ozDd6gyPPYzaXCfnptUcEZxXGYFn+ul2dtcMUiqGR1Yai2K10uoTw==} + '@oxfmt/binding-darwin-arm64@0.72.0': + resolution: {integrity: sha512-LjQ8tlevdtwraFlWC6AnQ1Forzde0clRmnsP3QmK+ydSElDt/J+5Jc+m+//Ly1G97aP7WCNUw110QMIUw2K7xA==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [arm64] os: [darwin] - '@rolldown/binding-darwin-x64@1.1.5': - resolution: {integrity: sha512-Tm+gbfC0aHu1tBA/JvKQh32S0K6YgCHkiAF4/W6xX0K0RmNuc94VeK419dJoE65R5aRxmo+noZQSWrAMF6yb6g==} + '@oxfmt/binding-darwin-x64@0.72.0': + resolution: {integrity: sha512-+ZbcxhB1C34RQR5IldSyY6Z/yalJIxHXhP36PGE4sEq0f/lgL5jCLmOKoDeVm8dqd9gFGDsd1bNWHSGCAwdSkw==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [x64] os: [darwin] - '@rolldown/binding-freebsd-x64@1.1.5': - resolution: {integrity: sha512-JMzDKCCXq93YccG5gz3hvOs1oXRKAf0XYpfOS88e+wZrC8Iugj6j68867vrYZkvpDDpKn/KoKORThmchMpF6TA==} + '@oxfmt/binding-freebsd-x64@0.72.0': + resolution: {integrity: sha512-yGPhA8sePEBFV9iGcprhyOk3Bz3fZshswMH3DpYIPZFLT1DG5LjiZ0j6AJQpSueaL6bUK0av1K+5+fe+ldy5EQ==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [x64] os: [freebsd] - '@rolldown/binding-linux-arm-gnueabihf@1.1.5': - resolution: {integrity: sha512-uML21j2K5TfPGutKxub+M+nLjZIrWjXQ5Grx4lCe/nimTj9B4L63zHpjXLl4y0L3mcm2htEQIb06oCG/szerNw==} + '@oxfmt/binding-linux-arm-gnueabihf@0.72.0': + resolution: {integrity: sha512-/g5axuqFMcAtoQrFLR5EJEMDjR5Ea4GuzpdDmMpPWAgrF1DmL4kD1NPNVlpXEOxpFJnD6lPvLKYj1lzdlrfifA==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [arm] + os: [linux] + + '@oxfmt/binding-linux-arm-musleabihf@0.72.0': + resolution: {integrity: sha512-InVitdHbS09PM+w+WinIiyX37rC9R3L7lUP9mKy9uKTEDSUFaLjWsLoreRIxKivnv7Ndqc6Trvi+K+b6ZzhiJg==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [arm] os: [linux] - '@rolldown/binding-linux-arm64-gnu@1.1.5': - resolution: {integrity: sha512-navSiuTMogvnQoZoM/v+l3ZWo50/NTwSHSzheABx/RCnmUPaKwq9qSo4Br2OYRs21+Fz8uFqITZM3H4opOB0/Q==} + '@oxfmt/binding-linux-arm64-gnu@0.72.0': + resolution: {integrity: sha512-WKjlyRAxpPjXoqeuwFBim5LscgvY+RNEiZ971C9JlRQZONhJTsXDki/oHFYQE+fzzRpzXh3owZvOdT3kNL2g+g==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [arm64] os: [linux] libc: [glibc] - '@rolldown/binding-linux-arm64-musl@1.1.5': - resolution: {integrity: sha512-lAryqH7IteztmCXQXk0etKj4wBQ7Gx5S6LjKhsgp9zb8I5bsuvU/2llH1hDQcjsFeqIsovMVN339/8pUDDBXxA==} + '@oxfmt/binding-linux-arm64-musl@0.72.0': + resolution: {integrity: sha512-poafnbsPpJhR+5Mbwpf8CscugTyiRAzjTOxRVn8ra3X2jnURs2xL5Du2xl5g2iW85lICMglpmLf8jDjZEbNgzQ==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [arm64] os: [linux] libc: [musl] - '@rolldown/binding-linux-ppc64-gnu@1.1.5': - resolution: {integrity: sha512-fsK/sNBnxzBlL4O1JNrZakVQxPspqpED5dLtNsZS9oOKmtSpdNIzxH2kkol5HYTWJN47sE20ztMJPxfZ89qGOg==} + '@oxfmt/binding-linux-ppc64-gnu@0.72.0': + resolution: {integrity: sha512-zNUi0gKRsO7S3IfPzsTbnm88OedPuX32hmZFWZRWHvWJlAoZRwVbeO6Nb3hng4vDgL7qUKp4TGqzPz1mhsycYg==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [ppc64] os: [linux] libc: [glibc] - '@rolldown/binding-linux-s390x-gnu@1.1.5': - resolution: {integrity: sha512-gLYb4BIadlfTOYT5gO503n8zQjXflgzpD0FcyKh0Mzx3rqCZKnHoJWV9xe1KXUJ5lx2JfcSHr/mhzS0PC/McAA==} + '@oxfmt/binding-linux-riscv64-gnu@0.72.0': + resolution: {integrity: sha512-qTY7z/iyF/rWI2HWwWMevXqZW5PZUOofUy2afK7v6rseXnpld3yjgyIk6pCux3DqWQ0lB+f5atvLJNBwzLvFsw==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [riscv64] + os: [linux] + libc: [glibc] + + '@oxfmt/binding-linux-riscv64-musl@0.72.0': + resolution: {integrity: sha512-w18qfo8vEMD6EKEHtzTvjc6jH8yCyXIT1Bn7PFWJd0PpZyzH/VQDx4xvjCh0LGz6GRzqiezbisTdG9AoXtpkZQ==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [riscv64] + os: [linux] + libc: [musl] + + '@oxfmt/binding-linux-s390x-gnu@0.72.0': + resolution: {integrity: sha512-grW8HwEir+vK7fCcq1ZgL3NHQCfZ1PidiERE9Hbf+24xGys+DoDaoqTht4ccfn0PcBwbViLkwkda/KNpJu2KRQ==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [s390x] os: [linux] libc: [glibc] - '@rolldown/binding-linux-x64-gnu@1.1.5': - resolution: {integrity: sha512-FjcpEKUyJygHgs1o50VYNvkt5+7Le/VEdYt0AkRpkL33MnyQfwr8l5mXwMmfmTbyMPr5vJLC+8/Gd9gXnwU1QQ==} + '@oxfmt/binding-linux-x64-gnu@0.72.0': + resolution: {integrity: sha512-wQJRQfWBRIn88Pkr449EUViwRayrYUScg7ty+Ihiem21mQ9BUA6S9e/4RAgyNk9lMag0KBouyKskjq61rOC0WQ==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [x64] os: [linux] libc: [glibc] - '@rolldown/binding-linux-x64-musl@1.1.5': - resolution: {integrity: sha512-Me+PfPI2TMeOQk0gYWfLQZtTktrmzbr8cDboqX83XKc7UrgAi55gF+2dUkWdxd19n55Essp2yeca+O9N5rBxHg==} + '@oxfmt/binding-linux-x64-musl@0.72.0': + resolution: {integrity: sha512-T1B0uQzafxuGPjjaPrZJ497nxoi1BiI65JfsYc56nNpmev3msfb/iaTmH2kQuNFqa8PKxpMlf3GhWrWBvBPORQ==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [x64] os: [linux] libc: [musl] - '@rolldown/binding-openharmony-arm64@1.1.5': - resolution: {integrity: sha512-yc5WrLzXks6zCQfn9Oxr8pORKyl/pF+QjHmW/Qx3qu0oyrrNC+y2JLTU1E2rcWYAmzlnqngWXHQjy51VzW70Vw==} + '@oxfmt/binding-openharmony-arm64@0.72.0': + resolution: {integrity: sha512-OKxxgTurl7+hypVplO8QRAbbQlwEs0j1Y6FZv+ZN2qNbdxQk2muMlzuJNBGTCWwxQ0rkkOD1uK/DW1fvzoUSJA==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [arm64] os: [openharmony] - '@rolldown/binding-wasm32-wasi@1.1.5': - resolution: {integrity: sha512-VbQGPX2b4r48TAMIM2cjgluIM1HYutm4pcTEJsle7iEP7sB1dFqtPLBVbdLAZCxy1txCcPxf4QFf4v8uvltPqA==} + '@oxfmt/binding-win32-arm64-msvc@0.72.0': + resolution: {integrity: sha512-L78TKzURqvjuZOCo88ixMbPXf/RP7u31Y0cmjJ/KEmrHmfYhmhEvsDaTYPiyTRne64jUgCbADfY0YeoQk4/Xyw==} engines: {node: ^20.19.0 || >=22.12.0} - cpu: [wasm32] + cpu: [arm64] + os: [win32] - '@rolldown/binding-win32-arm64-msvc@1.1.5': - resolution: {integrity: sha512-gHv82k63z4qpV5+Q1y/12KrK0ltWBukVDI8nZcbT7Tt/ZlOIVwppazneq0F93oDxTo3IgAMEDIoQh3E2n6mVsw==} + '@oxfmt/binding-win32-ia32-msvc@0.72.0': + resolution: {integrity: sha512-gQtFW+Ii8BWgtJTAD17TY/WsdNS5gUfAPov8iI1jeq4r9lRRYImmxPL08UU00/RLaCfBJwh2UgINnDY0iOiZ+A==} engines: {node: ^20.19.0 || >=22.12.0} - cpu: [arm64] + cpu: [ia32] os: [win32] - '@rolldown/binding-win32-x64-msvc@1.1.5': - resolution: {integrity: sha512-tTZuDBPw85tEN5PQi1pnEBzDy0Z49HtScLAbD5t6hyeU92A95pRWaSMw1GZZi/RwgSgUIl0xrSlXIT/9QzvYSA==} + '@oxfmt/binding-win32-x64-msvc@0.72.0': + resolution: {integrity: sha512-uhooIN+DCzWX5uH7mQkSVCsODqsGluGJ4n3ch4+mITtWKpESMDjUJUSDBAVFuYQDwqY2McPDKdKJbM0oZVW/Qw==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [x64] os: [win32] - '@rolldown/pluginutils@1.0.1': - resolution: {integrity: sha512-2j9bGt5Jh8hj+vPtgzPtl72j0yRxHAyumoo6TNfAjsLB04UtpSvPbPcDcBMxz7n+9CYB0c1GxQFxYRg2jimqGw==} + '@oxlint-tsgolint/darwin-arm64@7.0.2003': + resolution: {integrity: sha512-TgV33rXr6ueXBwvc+0nssUkTBSXHJxv77I8p4RCjDjCnvexHtmoPiudVRDfj3S3puMDdeQdHW6jdUgUPy3nr/w==} + cpu: [arm64] + os: [darwin] + + '@oxlint-tsgolint/darwin-x64@7.0.2003': + resolution: {integrity: sha512-hY3FMAjIaPDdK3FNxyRXRfHPOFeoBn6dmnLyKZgQ2IbTTD1adXhl6PfCIqHhCPvurigv7nf7mYuWZZ19MmJzmg==} + cpu: [x64] + os: [darwin] - '@rollup/rollup-android-arm-eabi@4.60.3': - resolution: {integrity: sha512-x35CNW/ANXG3hE/EZpRU8MXX1JDN86hBb2wMGAtltkz7pc6cxgjpy1OMMfDosOQ+2hWqIkag/fGok1Yady9nGw==} + '@oxlint-tsgolint/linux-arm64@7.0.2003': + resolution: {integrity: sha512-eAET4JpyfBbg8SO0K74o4R55tEjVC292pIyxGOw2XGf8x/HrPNyxwQ478jMYOM54FIVOHc8sJ6VRHxluy6lucw==} + cpu: [arm64] + os: [linux] + + '@oxlint-tsgolint/linux-x64@7.0.2003': + resolution: {integrity: sha512-GXdyO/XyqDJ3s/llR/oOktLsYNjZtWSQBy0JMc+/0gsNPvTcseKPhn9c6KcFyWmnr6H4vljYALbujonqSzzEGw==} + cpu: [x64] + os: [linux] + + '@oxlint-tsgolint/win32-arm64@7.0.2003': + resolution: {integrity: sha512-TWauXnPfet0VgpmrstoAK58eJ4gbuwPUuUTQmYQAzE/RG1CpnxXtrKUJULf6lyerPE4KN3gM+DflIA1hOH+38Q==} + cpu: [arm64] + os: [win32] + + '@oxlint-tsgolint/win32-x64@7.0.2003': + resolution: {integrity: sha512-DoRmfe7j8VqNlukp8liRVW45GQDhzRccNenjD/pdzelgtffW47pCMd1xbJLkPaPbKnTwID3onn3VZlL7JbebEA==} + cpu: [x64] + os: [win32] + + '@oxlint/binding-android-arm-eabi@1.87.0': + resolution: {integrity: sha512-22tVIHlB9898OYsBKmYB6He1MCSqdTmK1BVWuSNUTVrpS0KWNege+U9uEYqGJ+KatQag0r/oU44i3OOs46z7HQ==} + engines: {node: ^20.19.0 || >=22.12.0} cpu: [arm] os: [android] - '@rollup/rollup-android-arm64@4.60.3': - resolution: {integrity: sha512-xw3xtkDApIOGayehp2+Rz4zimfkaX65r4t47iy+ymQB2G4iJCBBfj0ogVg5jpvjpn8UWn/+q9tprxleYeNp3Hw==} + '@oxlint/binding-android-arm64@1.87.0': + resolution: {integrity: sha512-JfZQ3MWf74n8ov0DojZpkOduIubYJvZL5tbTMMhDutsal6u1xH1QuNpN+lK+EQwF8AeAfgwTR2fgh5U2br27XA==} + engines: {node: ^20.19.0 || >=22.12.0} cpu: [arm64] os: [android] - '@rollup/rollup-darwin-arm64@4.60.3': - resolution: {integrity: sha512-vo6Y5Qfpx7/5EaamIwi0WqW2+zfiusVihKatLvtN1VFVy3D13uERk/6gZLU1UiHRL6fDXqj/ELIeVRGnvcTE1g==} + '@oxlint/binding-darwin-arm64@1.87.0': + resolution: {integrity: sha512-rkx6Xvvw93z2GImVOyvbZwAZcoY0Z6MJW177XNsSB+i/CBsH7ANyelTp01VZyIkM3iSXtqKVb4+ptBgPHap5pQ==} + engines: {node: ^20.19.0 || >=22.12.0} cpu: [arm64] os: [darwin] - '@rollup/rollup-darwin-x64@4.60.3': - resolution: {integrity: sha512-D+0QGcZhBzTN82weOnsSlY7V7+RMmPuF1CkbxyMAGE8+ZHeUjyb76ZiWmBlCu//AQQONvxcqRbwZTajZKqjuOw==} + '@oxlint/binding-darwin-x64@1.87.0': + resolution: {integrity: sha512-cw5nYw7L/pQ2SgGBgOcg/Qo89llQre3vIc/kuBLhSICzu1r5s8L76H3ug6AIFsD98WpROj8/z5JVzkf4daxHaQ==} + engines: {node: ^20.19.0 || >=22.12.0} cpu: [x64] os: [darwin] - '@rollup/rollup-freebsd-arm64@4.60.3': - resolution: {integrity: sha512-6HnvHCT7fDyj6R0Ph7A6x8dQS/S38MClRWeDLqc0MdfWkxjiu1HSDYrdPhqSILzjTIC/pnXbbJbo+ft+gy/9hQ==} - cpu: [arm64] - os: [freebsd] - - '@rollup/rollup-freebsd-x64@4.60.3': - resolution: {integrity: sha512-KHLgC3WKlUYW3ShFKnnosZDOJ0xjg9zp7au3sIm2bs/tGBeC2ipmvRh/N7JKi0t9Ue20C0dpEshi8WUubg+cnA==} + '@oxlint/binding-freebsd-x64@1.87.0': + resolution: {integrity: sha512-x/SuznE6loRZK9gz9AS4ZmzNzSu+5KdeoCS+sVHhnrE8HM6AD8UyTjwldQTTqlXGvkwHASgB0NiTNOc3o3PdAg==} + engines: {node: ^20.19.0 || >=22.12.0} cpu: [x64] os: [freebsd] - '@rollup/rollup-linux-arm-gnueabihf@4.60.3': - resolution: {integrity: sha512-DV6fJoxEYWJOvaZIsok7KrYl0tPvga5OZ2yvKHNNYyk/2roMLqQAbGhr78EQ5YhHpnhLKJD3S1WFusAkmUuV5g==} + '@oxlint/binding-linux-arm-gnueabihf@1.87.0': + resolution: {integrity: sha512-+JEDmCAXiiFmKhAjZhgAOwaHWY6WENOQ5QA+lFuY59tgsUCDOTRi46B+lENK6CWvtxsXpwkZXNOgpjAQcpIpnA==} + engines: {node: ^20.19.0 || >=22.12.0} cpu: [arm] os: [linux] - libc: [glibc] - '@rollup/rollup-linux-arm-musleabihf@4.60.3': - resolution: {integrity: sha512-mQKoJAzvuOs6F+TZybQO4GOTSMUu7v0WdxEk24krQ/uUxXoPTtHjuaUuPmFhtBcM4K0ons8nrE3JyhTuCFtT/w==} + '@oxlint/binding-linux-arm-musleabihf@1.87.0': + resolution: {integrity: sha512-/uC9XqXLAC9AHFLNycwmSQEvKqGrOCgU+uXALI7gNW0DG81TJ/3T2eHnKdRvcb5R1gFEfjxDwI4e4zIa29rICQ==} + engines: {node: ^20.19.0 || >=22.12.0} cpu: [arm] os: [linux] - libc: [musl] - '@rollup/rollup-linux-arm64-gnu@4.60.3': - resolution: {integrity: sha512-Whjj2qoiJ6+OOJMGptTYazaJvjOJm+iKHpXQM1P3LzGjt7Ff++Tp7nH4N8J/BUA7R9IHfDyx4DJIflifwnbmIA==} + '@oxlint/binding-linux-arm64-gnu@1.87.0': + resolution: {integrity: sha512-FTRYPsBy/M728vnUv7l+KAwSAa3Qi4BQbhVvy7NYuMTZ0sUa/OWsbMx79HVFw6u/5KWcaYayDsoOiv7xbevzDg==} + engines: {node: ^20.19.0 || >=22.12.0} cpu: [arm64] os: [linux] libc: [glibc] - '@rollup/rollup-linux-arm64-musl@4.60.3': + '@oxlint/binding-linux-arm64-musl@1.87.0': + resolution: {integrity: sha512-6sC+N7IY0h1egd8SrYUGNN7CUyKJZ4bIZakKa7V7hfinf4jfi8Kq4+klJvFp5DqXcwMn44I5MeBwmJluO1odFg==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [arm64] + os: [linux] + libc: [musl] + + '@oxlint/binding-linux-ppc64-gnu@1.87.0': + resolution: {integrity: sha512-d5Mnn+ICbhoJPi37Ccjy+86T5qfH2+mSTrTMBXNPfB+wqSWOayBBLLIrCXdUy1YssluCVdGkl+DO9w5r5ODmUA==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [ppc64] + os: [linux] + libc: [glibc] + + '@oxlint/binding-linux-riscv64-gnu@1.87.0': + resolution: {integrity: sha512-sf5YWuUo1Dx9loqWCgF8P278MycfcD4JHjDC/LV5j11L3Lqa4PFiwCjD6B8kP4AjsgZZkLn3xbpJEtZBaccs4Q==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [riscv64] + os: [linux] + libc: [glibc] + + '@oxlint/binding-linux-riscv64-musl@1.87.0': + resolution: {integrity: sha512-+aCraYbmK3uU3P0rFojjeaK9pGY6JvBDXh/0CM+gPyWKmNmXmnFe0qj4qj02U4ibFpbd9Pa9CmDvIdjJUd29rA==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [riscv64] + os: [linux] + libc: [musl] + + '@oxlint/binding-linux-s390x-gnu@1.87.0': + resolution: {integrity: sha512-7rzbZQIljo6VLWVPqUyLa2P/WV3VFsJB4M52R4NCUJn1A50yjtUsHxiMHFL9opEKGFIiTbZ3VQzmtcJOPUq4NQ==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [s390x] + os: [linux] + libc: [glibc] + + '@oxlint/binding-linux-x64-gnu@1.87.0': + resolution: {integrity: sha512-Dry46Fa+AXE/xt9J/YriujU1pU9mzfNWHGQwtOYibAFH8HLrKWCi93AhIGnuUMiwn2JbuBkRyz51f50Ly4r4ow==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [x64] + os: [linux] + libc: [glibc] + + '@oxlint/binding-linux-x64-musl@1.87.0': + resolution: {integrity: sha512-f/HMZez74gJvnRpGz+b5sdGyWrEgRLmFTXg9sNoqjPWogGxYHUhXKTKyMiYsu9rUGR2ciYqL3fk+Ua4JhMJbRA==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [x64] + os: [linux] + libc: [musl] + + '@oxlint/binding-openharmony-arm64@1.87.0': + resolution: {integrity: sha512-lEC6ZfPZm/07JlB7FsOWX9o1RiHlXiBW594LSUGuXmekC8LZmvUPP9XzE2WZcNxJi5cHA/Fw56o02c3JxgMA5g==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [arm64] + os: [openharmony] + + '@oxlint/binding-win32-arm64-msvc@1.87.0': + resolution: {integrity: sha512-afBWKOD36dUiA9AenaiWpCg8xfyUVPYznyAAVXu413brK4RA5UI/Xoc4yfeGIT8G1a44MV+JR7hioryerBDzGQ==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [arm64] + os: [win32] + + '@oxlint/binding-win32-ia32-msvc@1.87.0': + resolution: {integrity: sha512-daptNsEOuru4/nJTxV4DBHNJ5GepzmaBVQf+jjgKQGi9NRlZKik+RFq4hUL7ewsF1pwjKM+SkYaMbF82UwFGnw==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [ia32] + os: [win32] + + '@oxlint/binding-win32-x64-msvc@1.87.0': + resolution: {integrity: sha512-k+m/EShyXE29J/r1fjk0HlAzhN5JhIvj044oSJkXTEkCLrWZQgXb/3r4q5LTw/eaKW/4GuMXG2xmiJs3s5rPNw==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [x64] + os: [win32] + + '@oxlint/plugins@1.79.0': + resolution: {integrity: sha512-S0uyoxakDINJ4DPgqxGlEEvrdSMeQb7Z2lKVjxoY2gwsbZbfg2Xr8Klfeo5ZeraHmmdBCELFUHkSe6KEmBpMvg==} + engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} + + '@polka/url@1.0.0-next.29': + resolution: {integrity: sha512-wwQAWhWSuHaag8c4q/KN/vCoeOJYshAIvMQwD4GpSb3OiZklFfvAgmj0VCBBImRpuF/aFgIRzllXlVX93Jevww==} + + '@protobufjs/aspromise@1.1.2': + resolution: {integrity: sha512-j+gKExEuLmKwvz3OgROXtrJ2UG2x8Ch2YZUxahh+s1F2HZ+wAceUNLkvy6zKCPVRkU++ZWQrdxsUeQXmcg4uoQ==} + + '@protobufjs/base64@1.1.2': + resolution: {integrity: sha512-AZkcAA5vnN/v4PDqKyMR5lx7hZttPDgClv83E//FMNhR2TMcLUhfRUBHCmSl0oi9zMgDDqRUJkSxO3wm85+XLg==} + + '@protobufjs/codegen@2.0.5': + resolution: {integrity: sha512-zgXFLzW3Ap33e6d0Wlj4MGIm6Ce8O89n/apUaGNB/jx+hw+ruWEp7EwGUshdLKVRCxZW12fp9r40E1mQrf/34g==} + + '@protobufjs/eventemitter@1.1.1': + resolution: {integrity: sha512-vW1GmwMZNnL+gMRaovlh9yZX74kc+TTU3FObkkurpMaRtBfLP3ldjS9KQWlwZgraRE0+dheEEoAxdzcJQ8eXZg==} + + '@protobufjs/fetch@1.1.1': + resolution: {integrity: sha512-GpptLrs57adMSuHi3VNj0mAF8dwh36LMaYF6XyJ6JMWlVsc+t42tm1HSEDmOs3A8fC9yyeisgLhsTVQokOZ0zw==} + + '@protobufjs/float@1.0.2': + resolution: {integrity: sha512-Ddb+kVXlXst9d+R9PfTIxh1EdNkgoRe5tOX6t01f1lYWOvJnSPDBlG241QLzcyPdoNTsblLUdujGSE4RzrTZGQ==} + + '@protobufjs/path@1.1.2': + resolution: {integrity: sha512-6JOcJ5Tm08dOHAbdR3GrvP+yUUfkjG5ePsHYczMFLq3ZmMkAD98cDgcT2iA1lJ9NVwFd4tH/iSSoe44YWkltEA==} + + '@protobufjs/pool@1.1.0': + resolution: {integrity: sha512-0kELaGSIDBKvcgS4zkjz1PeddatrjYcmMWOlAuAPwAeccUrPHdUqo/J6LiymHHEiJT5NrF1UVwxY14f+fy4WQw==} + + '@protobufjs/utf8@1.1.1': + resolution: {integrity: sha512-oOAWABowe8EAbMyWKM0tYDKi8Yaox52D+HWZhAIJqQXbqe0xI/GV7FhLWqlEKreMkfDjshR5FKgi3mnle0h6Eg==} + + '@publint/pack@0.1.7': + resolution: {integrity: sha512-4EDEmvxWtgsCnnVeBvtFIFZtUhPPt1+bA9JrSwU4Sa//6oKtzCSlGGXYJr44OD9aGISymbieJ4mCKHUygUDU+g==} + engines: {node: '>=18'} + + '@rollup/rollup-android-arm-eabi@4.60.3': + resolution: {integrity: sha512-x35CNW/ANXG3hE/EZpRU8MXX1JDN86hBb2wMGAtltkz7pc6cxgjpy1OMMfDosOQ+2hWqIkag/fGok1Yady9nGw==} + cpu: [arm] + os: [android] + + '@rollup/rollup-android-arm64@4.60.3': + resolution: {integrity: sha512-xw3xtkDApIOGayehp2+Rz4zimfkaX65r4t47iy+ymQB2G4iJCBBfj0ogVg5jpvjpn8UWn/+q9tprxleYeNp3Hw==} + cpu: [arm64] + os: [android] + + '@rollup/rollup-darwin-arm64@4.60.3': + resolution: {integrity: sha512-vo6Y5Qfpx7/5EaamIwi0WqW2+zfiusVihKatLvtN1VFVy3D13uERk/6gZLU1UiHRL6fDXqj/ELIeVRGnvcTE1g==} + cpu: [arm64] + os: [darwin] + + '@rollup/rollup-darwin-x64@4.60.3': + resolution: {integrity: sha512-D+0QGcZhBzTN82weOnsSlY7V7+RMmPuF1CkbxyMAGE8+ZHeUjyb76ZiWmBlCu//AQQONvxcqRbwZTajZKqjuOw==} + cpu: [x64] + os: [darwin] + + '@rollup/rollup-freebsd-arm64@4.60.3': + resolution: {integrity: sha512-6HnvHCT7fDyj6R0Ph7A6x8dQS/S38MClRWeDLqc0MdfWkxjiu1HSDYrdPhqSILzjTIC/pnXbbJbo+ft+gy/9hQ==} + cpu: [arm64] + os: [freebsd] + + '@rollup/rollup-freebsd-x64@4.60.3': + resolution: {integrity: sha512-KHLgC3WKlUYW3ShFKnnosZDOJ0xjg9zp7au3sIm2bs/tGBeC2ipmvRh/N7JKi0t9Ue20C0dpEshi8WUubg+cnA==} + cpu: [x64] + os: [freebsd] + + '@rollup/rollup-linux-arm-gnueabihf@4.60.3': + resolution: {integrity: sha512-DV6fJoxEYWJOvaZIsok7KrYl0tPvga5OZ2yvKHNNYyk/2roMLqQAbGhr78EQ5YhHpnhLKJD3S1WFusAkmUuV5g==} + cpu: [arm] + os: [linux] + libc: [glibc] + + '@rollup/rollup-linux-arm-musleabihf@4.60.3': + resolution: {integrity: sha512-mQKoJAzvuOs6F+TZybQO4GOTSMUu7v0WdxEk24krQ/uUxXoPTtHjuaUuPmFhtBcM4K0ons8nrE3JyhTuCFtT/w==} + cpu: [arm] + os: [linux] + libc: [musl] + + '@rollup/rollup-linux-arm64-gnu@4.60.3': + resolution: {integrity: sha512-Whjj2qoiJ6+OOJMGptTYazaJvjOJm+iKHpXQM1P3LzGjt7Ff++Tp7nH4N8J/BUA7R9IHfDyx4DJIflifwnbmIA==} + cpu: [arm64] + os: [linux] + libc: [glibc] + + '@rollup/rollup-linux-arm64-musl@4.60.3': resolution: {integrity: sha512-4YTNHKqGng5+yiZt3mg77nmyuCfmNfX4fPmyUapBcIk+BdwSwmCWGXOUxhXbBEkFHtoN5boLj/5NON+u5QC9tg==} cpu: [arm64] os: [linux] @@ -1949,60 +1861,28 @@ packages: resolution: {integrity: sha512-MzshcoRo8zjQYa9WHrv3XC8LVvpRNSVaW3kOSTmHuTYDh/7be48WODOgs5yUpbnkpsw6rjVCDCgtB/K02cQwDg==} engines: {node: '>= 18.0.0'} - '@turbo/darwin-64@2.9.14': - resolution: {integrity: sha512-t7QiPflaEyBE4oayeZtSmu4mEfjgIrcNlNNl1z1dmIVPqEdtA7+CfTf8d7KXsOGPh6aNgWjKxyvQg9uGfDQF+A==} - cpu: [x64] - os: [darwin] - - '@turbo/darwin-arm64@2.9.14': - resolution: {integrity: sha512-d23147mC9BsCPA9mJ0h/ubcpbRgcJBXbcG3+Vq7YLhjz3IXuvQsJ1UXH8f4MD76ZjJ4m/E4aRdJV+MW88CDfbw==} - cpu: [arm64] - os: [darwin] - - '@turbo/linux-64@2.9.14': - resolution: {integrity: sha512-P3ZKB5tuUDdDQWuAsACGUR1qv9W7BNWxdxqVJ0kZNuNNPRaVYTPPikLcp79+GiEcW3npsR+KyP38lnQiBc5aSA==} - cpu: [x64] - os: [linux] - - '@turbo/linux-arm64@2.9.14': - resolution: {integrity: sha512-ZRTlzcUMrrPv9ZuDzRF9n60Ym13bKeG9jDB8WjxyLhWNzV+AJQN+zdpIk3NJYf2zQsGUm1mNar2P0elRzLw25g==} - cpu: [arm64] - os: [linux] - - '@turbo/windows-64@2.9.14': - resolution: {integrity: sha512-exanwN6sIduZwykYeiTQj8kCmOhazP5WOz3bvXMcYtjhL6Z3iRWLewKrXCBq0bqwSP3iBMb/AerRCnHI4lx46A==} - cpu: [x64] - os: [win32] + '@testing-library/dom@10.4.2': + resolution: {integrity: sha512-yzr2S9HyAIdhz2/6qHgbs665Q7PKVcDF05vsOlHPxG1mo36gKVesdYVeDLnXgfjJ03CrKRk08knc6+E/9m8v2Q==} + engines: {node: '>=18'} - '@turbo/windows-arm64@2.9.14': - resolution: {integrity: sha512-fVdCsnmYoKICsycbWuuGp6Jvi51/3G/UluFWuAUCvR8PIW5IJkAk5BM9UF8PSm0Q2IphWHFZjYEgjHsh3B9y/g==} - cpu: [arm64] - os: [win32] + '@testing-library/user-event@14.6.7': + resolution: {integrity: sha512-MPCpX8bxe8zS+JmmTwLp8jd0dy1rAm60Te/SL8JrQM3qvQJcBOs1d7IefJMyZzqM3EWBrDn/LWDt1BCGu4ASfg==} + engines: {node: '>=12', npm: '>=6'} + peerDependencies: + '@testing-library/dom': '>=7.21.4' '@tybys/wasm-util@0.10.3': resolution: {integrity: sha512-F3fo1MYrRJYL3zER0OUOmkutjr1Vp23m7OsSgp7nq4SP6OqX6C/56XFIPAl5bt3zaBRjmW7SGz3u/6LwFpYcOg==} - '@types/argparse@2.0.14': - resolution: {integrity: sha512-jJ6NMs9rXQ0rsqNt3TL4Elcwhd6wygo3lJOVoiHzURD34vsCcAlw443uGu4PXTtEmMF7sYKoadTCLXNmuJuQGw==} + '@types/aria-query@5.0.4': + resolution: {integrity: sha512-rfT93uj5s0PRL7EzccGMs3brplhcrghnDoV26NqKhCAS1hVo+WdNsPvE/yb6ilfr5hi2MEk6d5EWJTKdxg8jVw==} '@types/async@3.2.24': resolution: {integrity: sha512-8iHVLHsCCOBKjCF2KwFe0p9Z3rfM9mL+sSP8btyR5vTjJRAqpBYD28/ZLgXPf0pjG1VxOvtCV/BgXkQbpSe8Hw==} - '@types/body-parser@1.19.5': - resolution: {integrity: sha512-fB3Zu92ucau0iQ0JMCFQE7b/dv8Ot07NI3KaZIkIUNXq82k4eBAqUaneXfleGY9JWskeS9y+u0nXMyspcuQrCg==} - '@types/chai@5.2.3': resolution: {integrity: sha512-Mw558oeA9fFbv65/y4mHtXDs9bPnFMZAL/jxdPFUpOHHIXX91mcgEHbS5Lahr+pwZFR8A7GQleRWeI6cGFC2UA==} - '@types/cli-progress@3.11.5': - resolution: {integrity: sha512-D4PbNRbviKyppS5ivBGyFO29POlySLmA2HyUFE4p5QGazAMM3CwkKWcvTl8gvElSuxRh6FPKL8XmidX873ou4g==} - - '@types/connect@3.4.38': - resolution: {integrity: sha512-K6uROf1LD88uDQqJCktA4yzL1YYAK6NgfsI0v/mTgyPKWsX1CnJ0XPSDhViejru1GcRkLWb8RlzFYJRqGUbaug==} - - '@types/cors@2.8.17': - resolution: {integrity: sha512-8CGDvrBj1zgo2qE+oS3pOCyYNqCPryMWY2bGfwA0dcfopWGgxs+78df0Rs3rc9THP4JkOhLsAa+15VdpAqkcUA==} - '@types/deep-eql@4.0.2': resolution: {integrity: sha512-c9h9dVVMigMPc4bwTvC5dxqtqJZwQPePsWjPlpSOnojbor6pGqdk541lfA7AqFQr5pB1BRdq0juY9db81BwyFw==} @@ -2021,21 +1901,9 @@ packages: '@types/estree@1.0.9': resolution: {integrity: sha512-GhdPgy1el4/ImP05X05Uw4cw2/M93BCUmnEvWZNStlCzEKME4Fkk+YpoA5OiHNQmoS7Cafb8Xa3Pya8m1Qrzeg==} - '@types/express-serve-static-core@5.0.6': - resolution: {integrity: sha512-3xhRnjJPkULekpSzgtoNYYcTWgEZkp4myc+Saevii5JPnHNvHMRlBSHDbs7Bh1iPPoVTERHEZXyhyLbMEsExsA==} - - '@types/express@5.0.1': - resolution: {integrity: sha512-UZUw8vjpWFXuDnjFTh7/5c2TWDlQqeXHi6hcN7F2XSVT5P+WmUnnbFS3KA6Jnc6IsEqI2qCVu2bK0R0J4A8ZQQ==} - - '@types/http-errors@2.0.4': - resolution: {integrity: sha512-D0CFMMtydbJAegzOyHjtiKPLlvnm3iTZyZRSZoLq2mRhDdmLfIWOCYPfQJ4cu2erKghU++QvjcUjp/5h7hESpA==} - '@types/json-schema@7.0.15': resolution: {integrity: sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA==} - '@types/mime@1.3.5': - resolution: {integrity: sha512-/pyBZWSLD2n0dcHE3hq8s8ZvcETHtEuF+3E7XVt0Ig2nvsVQXdghHVcEkIWjy9A0wKfTn97a/PSDYohKIlnP/w==} - '@types/mustache@4.2.5': resolution: {integrity: sha512-PLwiVvTBg59tGFL/8VpcGvqOu3L4OuveNvPi0EYbWchRdEVP++yRUXJPFl+CApKEq13017/4Nf7aQ5lTtHUNsA==} @@ -2054,134 +1922,184 @@ packages: '@types/nunjucks@3.2.6': resolution: {integrity: sha512-pHiGtf83na1nCzliuAdq8GowYiXvH5l931xZ0YEHaLMNFgynpEqx+IPStlu7UaDkehfvl01e4x/9Tpwhy7Ue3w==} - '@types/pluralize@0.0.30': - resolution: {integrity: sha512-kVww6xZrW/db5BR9OqiT71J9huRdQ+z/r+LbDuT7/EK50mCmj5FoaIARnVv0rvjUS/YpDox0cDU9lpQT011VBA==} - '@types/prop-types@15.7.15': resolution: {integrity: sha512-F6bEyamV9jKGAFBEmlQnesRPGOQqS2+Uwi0Em15xenOxHaf2hv6L8YCVn3rPdPJOiJfPiCnLIRyvwVaqMY3MIw==} - '@types/qs@6.9.18': - resolution: {integrity: sha512-kK7dgTYDyGqS+e2Q4aK9X3D7q234CIZ1Bv0q/7Z5IwRDoADNU81xXJK/YVyLbLTZCoIwUoDoffFeF+p/eIklAA==} - - '@types/range-parser@1.2.7': - resolution: {integrity: sha512-hKormJbkJqzQGhziax5PItDUTMAM9uE2XXQmM37dyd4hVM+5aVl7oVxMVUiVQn2oCQFN/LKCZdvSM0pFRqbSmQ==} - '@types/react@18.3.31': resolution: {integrity: sha512-vfEqpXTvwT91yhmwdfouStN2hSKwTvyRs8qpLfADyrq/kxDw0hZM7Wk9Ug1FELj8hIby+S/+kQCSRFF32nv2Qw==} - '@types/retry@0.12.0': - resolution: {integrity: sha512-wWKOClTTiizcZhXnPY4wikVAwmdYHp8q6DmC+EJUzAMsycb7HB32Kh9RN4+0gExjmPmZSAQjgURXIGATPegAvA==} - - '@types/send@0.17.4': - resolution: {integrity: sha512-x2EM6TJOybec7c52BX0ZspPodMsQUd5L6PRwOunVyVUhXiBSKf3AezDL8Dgvgt5o0UfKNfuA0eMLr2wLT4AiBA==} - - '@types/serve-static@1.15.7': - resolution: {integrity: sha512-W8Ym+h8nhuRwaKPaDw34QUkwsGi6Rc4yYqvKFo5rm2FUEhCFbzVWrxXUxuKK8TASjWsysJY0nsmNCGhCOIsrOw==} - '@types/set-cookie-parser@2.4.10': resolution: {integrity: sha512-GGmQVGpQWUe5qglJozEjZV/5dyxbOOZ0LHe/lqyWssB88Y4svNfst0uqBVscdDeIKl5Jy5+aPSvy7mI9tYRguw==} '@types/statuses@2.0.6': resolution: {integrity: sha512-xMAgYwceFhRA2zY+XbEA7mxYbA093wdiW8Vu6gZPGWy9cmOyU9XesH1tNcEWsKFd5Vzrqx5T3D38PWx1FIIXkA==} - '@types/ws@8.18.1': - resolution: {integrity: sha512-ThVF6DCVhA8kUGy+aazFQ4kXQ7E1Ty7A3ypFOe0IcJV8O/M511G99AW24irKrW56Wt44yG9+ij8FaqoBGkuBXg==} + '@vercel/functions@1.0.2': + resolution: {integrity: sha512-j3udyHOv/05Y8o3WQ/ANMWa1aYagsY5B3ouImiwgYsz5z4CBUHTY5dk74oQAXYr+bgoVDpdDlmxkpnxGzKEdLQ==} + engines: {node: '>= 16'} - '@typescript-eslint/eslint-plugin@8.50.0': - resolution: {integrity: sha512-O7QnmOXYKVtPrfYzMolrCTfkezCJS9+ljLdKW/+DCvRsc3UAz+sbH6Xcsv7p30+0OwUbeWfUDAQE0vpabZ3QLg==} - engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} - peerDependencies: - '@typescript-eslint/parser': ^8.50.0 - eslint: ^8.57.0 || ^9.0.0 - typescript: '>=4.8.4 <6.0.0' + '@vercel/oidc@3.1.0': + resolution: {integrity: sha512-Fw28YZpRnA3cAHHDlkt7xQHiJ0fcL+NRcIqsocZQUSmbzeIKRpwttJjik5ZGanXP+vlA4SbTg+AbA3bP363l+w==} + engines: {node: '>= 20'} - '@typescript-eslint/parser@8.50.0': - resolution: {integrity: sha512-6/cmF2piao+f6wSxUsJLZjck7OQsYyRtcOZS02k7XINSNlz93v6emM8WutDQSXnroG2xwYlEVHJI+cPA7CPM3Q==} - engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + '@vitest/browser-preview@5.0.3': + resolution: {integrity: sha512-jfnEBv5yDRPR1MeiTlTW5bcZoPsiI7MBhpZR8IJiSxVhdK6Pa4gPk3tLHTRxVHy24kkWxY2TnUbnszU3J1bQJw==} peerDependencies: - eslint: ^8.57.0 || ^9.0.0 - typescript: '>=4.8.4 <6.0.0' + vitest: 5.0.3 - '@typescript-eslint/project-service@8.50.0': - resolution: {integrity: sha512-Cg/nQcL1BcoTijEWyx4mkVC56r8dj44bFDvBdygifuS20f3OZCHmFbjF34DPSi07kwlFvqfv/xOLnJ5DquxSGQ==} - engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + '@vitest/browser@5.0.3': + resolution: {integrity: sha512-7KHIkVA/gTRxQV9qPa9qLK+g5INZG6iRrlwXSFG5EKsOomW3UzzW7wsdt1Yp5PufzQ43lniuONlb4lUVCC2qSg==} peerDependencies: - typescript: '>=4.8.4 <6.0.0' + vitest: 5.0.3 - '@typescript-eslint/scope-manager@8.50.0': - resolution: {integrity: sha512-xCwfuCZjhIqy7+HKxBLrDVT5q/iq7XBVBXLn57RTIIpelLtEIZHXAF/Upa3+gaCpeV1NNS5Z9A+ID6jn50VD4A==} - engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} - - '@typescript-eslint/tsconfig-utils@8.50.0': - resolution: {integrity: sha512-vxd3G/ybKTSlm31MOA96gqvrRGv9RJ7LGtZCn2Vrc5htA0zCDvcMqUkifcjrWNNKXHUU3WCkYOzzVSFBd0wa2w==} - engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + '@vitest/mocker@5.0.3': + resolution: {integrity: sha512-T8sWAIbkSyAjkwTcaEc3Iu0o9A27X1/kdXrizhZkGuSKScRQtRzclfAMpOTcGdXCsqxeWlpGy3XjqaW8CpLORg==} peerDependencies: - typescript: '>=4.8.4 <6.0.0' + msw: ^2.4.9 + vite: ^6.0.0 || ^7.0.0 || ^8.0.0 + peerDependenciesMeta: + msw: + optional: true + vite: + optional: true - '@typescript-eslint/type-utils@8.50.0': - resolution: {integrity: sha512-7OciHT2lKCewR0mFoBrvZJ4AXTMe/sYOe87289WAViOocEmDjjv8MvIOT2XESuKj9jp8u3SZYUSh89QA4S1kQw==} - engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} - peerDependencies: - eslint: ^8.57.0 || ^9.0.0 - typescript: '>=4.8.4 <6.0.0' + '@vitest/pretty-format@5.0.3': + resolution: {integrity: sha512-3hr5eV5z2ff0l2Q1vaKQbB4j3M3tElw8stDokEOTqXJB3Es5VrT8gBRIMY59gGuFp8Y61sWlWFK1aNUH5dHDYw==} - '@typescript-eslint/types@8.50.0': - resolution: {integrity: sha512-iX1mgmGrXdANhhITbpp2QQM2fGehBse9LbTf0sidWK6yg/NE+uhV5dfU1g6EYPlcReYmkE9QLPq/2irKAmtS9w==} - engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + '@vitest/snapshot@5.0.3': + resolution: {integrity: sha512-SK4YTg9jL+jwakHIZxfciuvyOQsbY15AueR3USGtsXzmy+3FbJgqjAbC+ac20mRLwfEDtO5sfTqK6bUCDTRVPA==} - '@typescript-eslint/typescript-estree@8.50.0': - resolution: {integrity: sha512-W7SVAGBR/IX7zm1t70Yujpbk+zdPq/u4soeFSknWFdXIFuWsBGBOUu/Tn/I6KHSKvSh91OiMuaSnYp3mtPt5IQ==} - engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} - peerDependencies: - typescript: '>=4.8.4 <6.0.0' + '@vitest/spy@5.0.3': + resolution: {integrity: sha512-XhFysQTB8AZ+P4gMi+Lpo99vg2AZi0qKpaB9yXQl37+CaMEAPO3iH/wGVnSyL5MPERiLezpqTVtrR6UZH5GCXg==} - '@typescript-eslint/utils@8.50.0': - resolution: {integrity: sha512-87KgUXET09CRjGCi2Ejxy3PULXna63/bMYv72tCAlDJC3Yqwln0HiFJ3VJMst2+mEtNtZu5oFvX4qJGjKsnAgg==} - engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + '@vitest/ui@5.0.3': + resolution: {integrity: sha512-9dfvqDBvSbHM6y9YVXxAypkl2z7mrWozK/JFo237qI0cj/3M/kzDKiVZqzi3TE54Ler7Jditud7OMBZCyUvRHA==} peerDependencies: - eslint: ^8.57.0 || ^9.0.0 - typescript: '>=4.8.4 <6.0.0' + vitest: 5.0.3 - '@typescript-eslint/visitor-keys@8.50.0': - resolution: {integrity: sha512-Xzmnb58+Db78gT/CCj/PVCvK+zxbnsw6F+O1oheYszJbBSdEjVhQi3C/Xttzxgi/GLmpvOggRs1RFpiJ8+c34Q==} - engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + '@vitest/utils@5.0.3': + resolution: {integrity: sha512-77fDTZv8eu1CzlIfgPU2yrl5MXSUaUAOvgdZEeVm3X3WIxUubIX2SforqS6176lT32HCF6uBVMYVqfHxw7o6eg==} - '@vercel/functions@1.0.2': - resolution: {integrity: sha512-j3udyHOv/05Y8o3WQ/ANMWa1aYagsY5B3ouImiwgYsz5z4CBUHTY5dk74oQAXYr+bgoVDpdDlmxkpnxGzKEdLQ==} - engines: {node: '>= 16'} - - '@vercel/oidc@3.1.0': - resolution: {integrity: sha512-Fw28YZpRnA3cAHHDlkt7xQHiJ0fcL+NRcIqsocZQUSmbzeIKRpwttJjik5ZGanXP+vlA4SbTg+AbA3bP363l+w==} - engines: {node: '>= 20'} - - '@vitest/expect@4.1.11': - resolution: {integrity: sha512-VX2x5vNJXET47KAFzwERI+KRMtTTCSWTfSMKsW7JsUsXV4psq++e3DvZpuTDOpHcxytiDs6p2nhVb2tVDiiUYw==} - - '@vitest/mocker@4.1.11': - resolution: {integrity: sha512-2XJVD55d1o5AZous5CCGKS74g/riOj9odEt2bQpCVZeblHyHdnMeFl4jl0XjU21stf4mbjUkew2eXQZt65g5CQ==} + '@voidzero-dev/vite-plus-core@1.1.0': + resolution: {integrity: sha512-Ai6HQP1zG0H3ZrmStEaMVKtDzW0xU3VgMzm3LFaXDQMEp8nAqvp0VGoU8HmpB9xv9vDSl+UxiSNOpuY9T714OQ==} + engines: {node: ^20.19.0 || ^22.18.0 || >=24.11.0} peerDependencies: - msw: ^2.4.9 - vite: ^6.0.0 || ^7.0.0 || ^8.0.0 + '@arethetypeswrong/core': ^0.18.1 + '@types/node': ^20.19.0 || >=22.12.0 + '@typescript/native-preview': '*' + '@vitejs/devtools': ^0.7.1 + '@volar/typescript': ~2.4.0 + '@vue/language-core': ~3.2.0 || ~3.3.0 + esbuild: 0.28.1 + jiti: '>=1.21.0' + less: ^4.0.0 + publint: ^0.3.8 + sass: ^1.70.0 + sass-embedded: ^1.70.0 + stylus: '>=0.54.8' + sugarss: ^5.0.0 + terser: ^5.16.0 + tsx: ^4.8.1 + typescript: ^5.0.0 || ^6.0.0 || ^7.0.0 + unplugin-unused: '>=0.5.0' + unrun: '*' + vue-tsc: ~3.2.0 || ~3.3.0 + yaml: ^2.4.2 peerDependenciesMeta: - msw: + '@arethetypeswrong/core': optional: true - vite: + '@types/node': + optional: true + '@typescript/native-preview': + optional: true + '@vitejs/devtools': + optional: true + '@volar/typescript': + optional: true + '@vue/language-core': + optional: true + esbuild: + optional: true + jiti: + optional: true + less: + optional: true + publint: + optional: true + sass: + optional: true + sass-embedded: + optional: true + stylus: + optional: true + sugarss: + optional: true + terser: + optional: true + tsx: + optional: true + typescript: + optional: true + unplugin-unused: + optional: true + unrun: + optional: true + vue-tsc: + optional: true + yaml: optional: true - '@vitest/pretty-format@4.1.11': - resolution: {integrity: sha512-yiZzPbGTS9Sr/JpFl8zHrcIkAofNbFV6k21vIgQN/cY/oxZeXhJv5sc/MBJ5jFKWmWs+oJHw0UXLZjmf931+Vw==} + '@voidzero-dev/vite-plus-darwin-arm64@1.1.0': + resolution: {integrity: sha512-iWJdQZBRZWB6X1Aadk0Rrs/NJeP32Qb0OGeWnu1AYL0g6ZCj1yy3l9Oa+HasDyG97s+4UYo523LdBAAv3dlFwg==} + engines: {node: '>=20.0.0'} + cpu: [arm64] + os: [darwin] - '@vitest/runner@4.1.11': - resolution: {integrity: sha512-LztvUgdwMNJMIkj3hQnnxiC2Xy1zNxq928W/xhjCLaNCzqTZOudjwbQf6v9IntZGPw132i2Lq2rgTRZHD3JHNw==} + '@voidzero-dev/vite-plus-darwin-x64@1.1.0': + resolution: {integrity: sha512-IYSQ2KdigRMuVkKCC+e5uNliCTSHa8j3ajHsFG2cfXoiGHS1f4ausbMEzNYtn9Tcn0Xsyo7l0LN8lvB7MapJhA==} + engines: {node: '>=20.0.0'} + cpu: [x64] + os: [darwin] - '@vitest/snapshot@4.1.11': - resolution: {integrity: sha512-pN7ikn1ON7h8ee4gIAp4AzyK+zBtJPzVbqOgu5LCEh4VaJVbPQcgYQYJIMGQPXVeJJq1fnfazis7a5pFNPahog==} + '@voidzero-dev/vite-plus-linux-arm64-gnu@1.1.0': + resolution: {integrity: sha512-PmrJG8Md5KCWi/akWJz5GRjei2QfRf48R4rPB9CfualzAAlpMOmaMGeV3eloWVAFye85uhdtzCmIAqN5OUVBJA==} + engines: {node: '>=20.0.0'} + cpu: [arm64] + os: [linux] + libc: [glibc] + + '@voidzero-dev/vite-plus-linux-arm64-musl@1.1.0': + resolution: {integrity: sha512-zFr57++m5U474XEMcFGV025TOv9V2I5d87yylcuI/SvxQwDE9P7T5iRl55vGzQOzzInSnQuru6ByNIfmGa+84A==} + engines: {node: '>=20.0.0'} + cpu: [arm64] + os: [linux] + libc: [musl] - '@vitest/spy@4.1.11': - resolution: {integrity: sha512-apNa/prQy2qCeywhnixOHPRCgGNhvg7T4Dapfl1GahLp/R+uhBm5cPyFoNVyqsNd2h1nJxL6BqqdIjiABL60YA==} + '@voidzero-dev/vite-plus-linux-x64-gnu@1.1.0': + resolution: {integrity: sha512-DXeRuTMylEbIVCQX+pSBM27lSPQwAKO6PDnGBJ6nFGhu37AmNeVl7cIChtcniXOuykbFduhSt3eIr/6CZHoEag==} + engines: {node: '>=20.0.0'} + cpu: [x64] + os: [linux] + libc: [glibc] - '@vitest/utils@4.1.11': - resolution: {integrity: sha512-zTCVGpyFsGWBhllOyKlTw/vnr6D9qxsfSDyfbyZmTyjHw5N/VuvzHpHoQjm2ZJzn4RJgx5w4r7V0er69CmLgPQ==} + '@voidzero-dev/vite-plus-linux-x64-musl@1.1.0': + resolution: {integrity: sha512-J7/Mb/kxtENUsL5Gh+8SSr/p054fSqU4oj0GEthr/2VEFSwZouOUigk6CzE7ANKFeTNJm40n8QCnjm7Oiy09pQ==} + engines: {node: '>=20.0.0'} + cpu: [x64] + os: [linux] + libc: [musl] + + '@voidzero-dev/vite-plus-win32-arm64-msvc@1.1.0': + resolution: {integrity: sha512-EXWz8Tt7skmI0fLMSq5zYcuHo6SCF0reG55qDB/NMvWVIAca6Dborq7D1HyKA8v6p39zOl8r07gR7yXuYv7eeQ==} + engines: {node: '>=20.0.0'} + cpu: [arm64] + os: [win32] + + '@voidzero-dev/vite-plus-win32-x64-msvc@1.1.0': + resolution: {integrity: sha512-K9k77Mj/TszFHKsZePKsyfsIPo8HbjKeJUxaSJ6YZKAmSh9QvDsMFe4i4/HALwyii/PR1EQ8N0iN1nf9cNimGQ==} + engines: {node: '>=20.0.0'} + cpu: [x64] + os: [win32] '@wasm-audio-decoders/common@9.0.7': resolution: {integrity: sha512-WRaUuWSKV7pkttBygml/a6dIEpatq2nnZGFIoPTc5yPLkxL6Wk4YaslPM98OPQvWacvNZ+Py9xROGDtrFBDzag==} @@ -2216,29 +2134,188 @@ packages: '@webassemblyjs/leb128@1.13.2': resolution: {integrity: sha512-Lde1oNoIdzVzdkNEAWZ1dZ5orIbff80YPdHx20mrHwHrVNNTjNr8E3xz9BdpcGqRQbAEa+fkrCb+fRFTl/6sQw==} - '@webassemblyjs/utf8@1.13.2': - resolution: {integrity: sha512-3NQWGjKTASY1xV5m7Hr0iPeXD9+RDobLll3T9d2AO+g3my8xy5peVyjSag4I50mR1bBSN/Ct12lo+R9tJk0NZQ==} + '@webassemblyjs/utf8@1.13.2': + resolution: {integrity: sha512-3NQWGjKTASY1xV5m7Hr0iPeXD9+RDobLll3T9d2AO+g3my8xy5peVyjSag4I50mR1bBSN/Ct12lo+R9tJk0NZQ==} + + '@webassemblyjs/wasm-edit@1.14.1': + resolution: {integrity: sha512-RNJUIQH/J8iA/1NzlE4N7KtyZNHi3w7at7hDjvRNm5rcUXa00z1vRz3glZoULfJ5mpvYhLybmVcwcjGrC1pRrQ==} + + '@webassemblyjs/wasm-gen@1.14.1': + resolution: {integrity: sha512-AmomSIjP8ZbfGQhumkNvgC33AY7qtMCXnN6bL2u2Js4gVCg8fp735aEiMSBbDR7UQIj90n4wKAFUSEd0QN2Ukg==} + + '@webassemblyjs/wasm-opt@1.14.1': + resolution: {integrity: sha512-PTcKLUNvBqnY2U6E5bdOQcSM+oVP/PmrDY9NzowJjislEjwP/C4an2303MCVS2Mg9d3AJpIGdUFIQQWbPds0Sw==} + + '@webassemblyjs/wasm-parser@1.14.1': + resolution: {integrity: sha512-JLBl+KZ0R5qB7mCnud/yyX08jWFw5MsoalJ1pQ4EdFlgj9VdXKGuENGsiCIjegI1W7p91rUlcB/LB5yRJKNTcQ==} + + '@webassemblyjs/wast-printer@1.14.1': + resolution: {integrity: sha512-kPSSXE6De1XOR820C90RIo2ogvZG+c3KiHzqUoO/F34Y2shGzesfqv7o57xrxovZJH/MetF5UjroJ/R/3isoiw==} + + '@xtuc/ieee754@1.2.0': + resolution: {integrity: sha512-DX8nKgqcGwsc0eJSqYt5lwP4DH5FlHnmuWWBRy7X0NcaGR0ZtuyeESgMwTYVEtxmsNGY+qit4QYT/MIYTOTPeA==} + + '@xtuc/long@4.2.2': + resolution: {integrity: sha512-NuHqBY1PB/D8xU6s/thBgOAiAP7HOYDQ32+BFZILJ8ivkUkAHQnWfn6WhL79Owj1qmUnoN/YPhktdIoucipkAQ==} + + '@yuku-codegen/binding-android-arm64@0.9.5': + resolution: {integrity: sha512-jrOY5WM+AaAqkv51fHP1x28ifto4WgcZVzodLUyMU1jMWn5Sq+VciRdk/n8E0Ey5w4p4cWWE+m5GfXWOYh7Kzw==} + cpu: [arm64] + os: [android] + deprecated: yuku-codegen is pure JavaScript since 0.14 + + '@yuku-codegen/binding-darwin-arm64@0.9.5': + resolution: {integrity: sha512-4O4lkCQIzPZGjiNB1GORSI6hBECbiiSBS/APZXPvNKYH+nhY4uuqv03LNXA+SET3hoBjvr95P5rIhY8KQaQUBA==} + cpu: [arm64] + os: [darwin] + deprecated: yuku-codegen is pure JavaScript since 0.14 + + '@yuku-codegen/binding-darwin-x64@0.9.5': + resolution: {integrity: sha512-9EvoUO0SEhD6/d8VHdWuPerepPMSR1y84+UEgz8Un1Ope14Oe7xMvePpEQLTLovoIFZ8Zg3iZ8z8E11pZMqC3g==} + cpu: [x64] + os: [darwin] + deprecated: yuku-codegen is pure JavaScript since 0.14 + + '@yuku-codegen/binding-freebsd-x64@0.9.5': + resolution: {integrity: sha512-HqT78WwgHTmp8lwujoUa9CrIortX4DdpuiVC18ZPSGvuuJf4ylpIEI6QrQEM78Zwz3muhAWAOXZ5irdiYY+AyA==} + cpu: [x64] + os: [freebsd] + deprecated: yuku-codegen is pure JavaScript since 0.14 + + '@yuku-codegen/binding-linux-arm-gnu@0.9.5': + resolution: {integrity: sha512-QJXwIW6Ms3QIawbcBIedmrmXnfdpuAOjZJ/eAABq5XTzWvSxZ7lutu9W5yIHahlaTaFnBo7ikrVMD1szLTpPUw==} + cpu: [arm] + os: [linux] + libc: [glibc] + deprecated: yuku-codegen is pure JavaScript since 0.14 + + '@yuku-codegen/binding-linux-arm-musl@0.9.5': + resolution: {integrity: sha512-eHbFy3IHGb+IYarrYwAo0yWSEQV3eAmEn6VrsKA6I1Wy1YPJxWqSJlV69JhqsHtJuJlljUIF3ex0y46yaKIu9w==} + cpu: [arm] + os: [linux] + libc: [musl] + deprecated: yuku-codegen is pure JavaScript since 0.14 + + '@yuku-codegen/binding-linux-arm64-gnu@0.9.5': + resolution: {integrity: sha512-ByoJMbySTaDhjAXSu8q6Lh7HKg3YoesXpcT72aYk0Aiw4PCznmY4ybpLTq0RCvp0RIPhFm/6yECFiGBwyCC1nw==} + cpu: [arm64] + os: [linux] + libc: [glibc] + deprecated: yuku-codegen is pure JavaScript since 0.14 + + '@yuku-codegen/binding-linux-arm64-musl@0.9.5': + resolution: {integrity: sha512-gD9vfXIoBw1toSxhO9rgmSu/FEfy3PMznJAxVjIuH8DrWEiDKXmJO0pJKfj1Ltbe/TmtWVZR+TjISqeSIGQzMg==} + cpu: [arm64] + os: [linux] + libc: [musl] + deprecated: yuku-codegen is pure JavaScript since 0.14 + + '@yuku-codegen/binding-linux-x64-gnu@0.9.5': + resolution: {integrity: sha512-BARvdnvqMGjOr5Iel2JH+9H5vAIE0R6H0Z2fsT02xrvmI/1ZXNB8lkuX+ZGevPhZb3zJ9WeC/R8JZstrsUOK5g==} + cpu: [x64] + os: [linux] + libc: [glibc] + deprecated: yuku-codegen is pure JavaScript since 0.14 + + '@yuku-codegen/binding-linux-x64-musl@0.9.5': + resolution: {integrity: sha512-x8flcevS1fbb7ESrmpOY/pON4eInSaE/7Ktjqx2udOE2W33BNSZmJuwpyUgo54AoHNqrcaM7szqydyJn5fNvew==} + cpu: [x64] + os: [linux] + libc: [musl] + deprecated: yuku-codegen is pure JavaScript since 0.14 + + '@yuku-codegen/binding-win32-arm64@0.9.5': + resolution: {integrity: sha512-KOL/rBatWqH4ZpCNoF8ZtSNdOJbAxBJLmk/VeRciepGROPTbPum1A9t67GpFgOU7qrkUWlPJdJ+KHKbvbmOt+w==} + cpu: [arm64] + os: [win32] + deprecated: yuku-codegen is pure JavaScript since 0.14 + + '@yuku-codegen/binding-win32-x64@0.9.5': + resolution: {integrity: sha512-FxENahEjWSan59Syh/us/Kf4wNq8qrJLFJ3R2N4Oiwtb6yNdz/rWLNTIoNSssnsp7IoWJdUP6o/Z8ppg7lXMcg==} + cpu: [x64] + os: [win32] + deprecated: yuku-codegen is pure JavaScript since 0.14 + + '@yuku-parser/binding-android-arm64@0.9.5': + resolution: {integrity: sha512-A2JCFCSHfnficqYEw4Iujpx7XrkMM3UfFcgJFmYpZSo9zM4nyhmdIIE0FogSduuU60lhM0/UcfuUBXIVNBMlGQ==} + cpu: [arm64] + os: [android] + deprecated: yuku-parser runs on yuku-core since 0.14 + + '@yuku-parser/binding-darwin-arm64@0.9.5': + resolution: {integrity: sha512-3PiyU+Eare4YuKaQ22N98/yAiROPY5o/NQJHraICzDvk4pgS+m+bgLKOvkGBRn33OnV95Vdv1Mn38b+MQHpULQ==} + cpu: [arm64] + os: [darwin] + deprecated: yuku-parser runs on yuku-core since 0.14 + + '@yuku-parser/binding-darwin-x64@0.9.5': + resolution: {integrity: sha512-blFMAFI7AInI83XaiOF8cIeiRM46Nz9EfpZtZPRLbKxSA9aAr5v8aHYpmfN6NoyXxAv/10pwS4gsAuc1W6fy5Q==} + cpu: [x64] + os: [darwin] + deprecated: yuku-parser runs on yuku-core since 0.14 + + '@yuku-parser/binding-freebsd-x64@0.9.5': + resolution: {integrity: sha512-TsNuL4qsZdO0tHp5GW43Y5fHeLPpPHA675wdcPNTcdq2ZO5AvsQWFFoiDg8CH4oBktfsQ9tdvKhjLnLYwKvp4g==} + cpu: [x64] + os: [freebsd] + deprecated: yuku-parser runs on yuku-core since 0.14 + + '@yuku-parser/binding-linux-arm-gnu@0.9.5': + resolution: {integrity: sha512-b0afYK5gHeV8RdmOcqAlgM8ONsye4cax4DMnIBaoJyPMd1UTGvTbpkqQcPVdhmHlcfcGVGMV1laeVovlr/dscw==} + cpu: [arm] + os: [linux] + libc: [glibc] + deprecated: yuku-parser runs on yuku-core since 0.14 + + '@yuku-parser/binding-linux-arm-musl@0.9.5': + resolution: {integrity: sha512-fD3lKzl+r6j6n8DwiMY53qnh5DLqD8KJjCp+NbVud54Nnh3Q9Wprqss3YzyMHP3NSJk663Wlg1E1X5qOuFVFig==} + cpu: [arm] + os: [linux] + libc: [musl] + deprecated: yuku-parser runs on yuku-core since 0.14 - '@webassemblyjs/wasm-edit@1.14.1': - resolution: {integrity: sha512-RNJUIQH/J8iA/1NzlE4N7KtyZNHi3w7at7hDjvRNm5rcUXa00z1vRz3glZoULfJ5mpvYhLybmVcwcjGrC1pRrQ==} + '@yuku-parser/binding-linux-arm64-gnu@0.9.5': + resolution: {integrity: sha512-aRU/aCphV1MWCl/lvI6NX8LgucHQ68Fx+vz7NBb/5MEr2EuzCxiPOQqeFcMdWh+2AED/p0AvAREch0c+cSnlhA==} + cpu: [arm64] + os: [linux] + libc: [glibc] + deprecated: yuku-parser runs on yuku-core since 0.14 - '@webassemblyjs/wasm-gen@1.14.1': - resolution: {integrity: sha512-AmomSIjP8ZbfGQhumkNvgC33AY7qtMCXnN6bL2u2Js4gVCg8fp735aEiMSBbDR7UQIj90n4wKAFUSEd0QN2Ukg==} + '@yuku-parser/binding-linux-arm64-musl@0.9.5': + resolution: {integrity: sha512-5+Guro0l8H473YXlEjVNBRLN/IbPbJdnQh1zo0OLt49xxnON+XMJRRaEyTOTfkn9QSRg0+BhEKGR4W9Gu2uZJQ==} + cpu: [arm64] + os: [linux] + libc: [musl] + deprecated: yuku-parser runs on yuku-core since 0.14 - '@webassemblyjs/wasm-opt@1.14.1': - resolution: {integrity: sha512-PTcKLUNvBqnY2U6E5bdOQcSM+oVP/PmrDY9NzowJjislEjwP/C4an2303MCVS2Mg9d3AJpIGdUFIQQWbPds0Sw==} + '@yuku-parser/binding-linux-x64-gnu@0.9.5': + resolution: {integrity: sha512-pwwSyV9q+GlvzSXlsMZBMlgk22L5bud714/vqZCdeUTivzeUVltQzUaf3IQXOGXdpc59JYTeuMCtbGquaAUoCA==} + cpu: [x64] + os: [linux] + libc: [glibc] + deprecated: yuku-parser runs on yuku-core since 0.14 - '@webassemblyjs/wasm-parser@1.14.1': - resolution: {integrity: sha512-JLBl+KZ0R5qB7mCnud/yyX08jWFw5MsoalJ1pQ4EdFlgj9VdXKGuENGsiCIjegI1W7p91rUlcB/LB5yRJKNTcQ==} + '@yuku-parser/binding-linux-x64-musl@0.9.5': + resolution: {integrity: sha512-z18j6JN3lBHH8vzN7gG1M8fI0nlgItSfnC9PfbmUbt97iHViKfw2iA2G9fGwRMe5LyPRZBrejIlapbygPbpdaw==} + cpu: [x64] + os: [linux] + libc: [musl] + deprecated: yuku-parser runs on yuku-core since 0.14 - '@webassemblyjs/wast-printer@1.14.1': - resolution: {integrity: sha512-kPSSXE6De1XOR820C90RIo2ogvZG+c3KiHzqUoO/F34Y2shGzesfqv7o57xrxovZJH/MetF5UjroJ/R/3isoiw==} + '@yuku-parser/binding-win32-arm64@0.9.5': + resolution: {integrity: sha512-s6Gwttb1dQvtPX6Bgkw+UPC9IO3UBDXc6Zezog8MMgvu3UfJBBB9TQqKBX/2quu0Eyh+lLSAyNlIyyecaagUPg==} + cpu: [arm64] + os: [win32] + deprecated: yuku-parser runs on yuku-core since 0.14 - '@xtuc/ieee754@1.2.0': - resolution: {integrity: sha512-DX8nKgqcGwsc0eJSqYt5lwP4DH5FlHnmuWWBRy7X0NcaGR0ZtuyeESgMwTYVEtxmsNGY+qit4QYT/MIYTOTPeA==} + '@yuku-parser/binding-win32-x64@0.9.5': + resolution: {integrity: sha512-FrERt9YWatY3bJfSUdi4YWY+6iQcyo3MzCC821BxlWM5TFZEHijnpz/bknR79VHlXY/9CvXz8ZlaAGPsTtN3nw==} + cpu: [x64] + os: [win32] + deprecated: yuku-parser runs on yuku-core since 0.14 - '@xtuc/long@4.2.2': - resolution: {integrity: sha512-NuHqBY1PB/D8xU6s/thBgOAiAP7HOYDQ32+BFZILJ8ivkUkAHQnWfn6WhL79Owj1qmUnoN/YPhktdIoucipkAQ==} + '@yuku-toolchain/types@0.9.5': + resolution: {integrity: sha512-KiuLNNgX9uNealaWAR+G3/cMXnRk9x4TY2EkYe/KIag+UPdwiA0RRf1hr1WAxzTP8KGzCTkqUdLPvqh32sEO3w==} a-sync-waterfall@1.0.1: resolution: {integrity: sha512-RYTOHHdWipFUliRFMCS4X2Yn2X8M87V/OpSqWzKKOGhzqyUxzyVmhHDH9sAvG+ZuQf/TAOFsLCpMw09I1ufUnA==} @@ -2247,10 +2324,6 @@ packages: resolution: {integrity: sha512-h8lQ8tacZYnR3vNQTgibj+tODHI5/+l06Au2Pcriv/Gmet0eaj4TwWH41sO9wnHDiQsEj19q0drzdWdeAHtweg==} engines: {node: '>=6.5'} - accepts@2.0.0: - resolution: {integrity: sha512-5cvg6CtKwfgdmVqY1WIiXKc3Q1bkRqGLi+2W/6ao+6Y7gu/RCwRuAhGEzh5B4KlszSuTLgZYuqFqo5bImjNKng==} - engines: {node: '>= 0.6'} - acorn-import-attributes@1.9.5: resolution: {integrity: sha512-n02Vykv5uA3eHGM/Z2dQrcD56kL8TyDb2p1+0P83PClMnC/nc+anbQRhIOWnSq4Ke/KvDPrY3C9hDtC/A3eHnQ==} peerDependencies: @@ -2286,14 +2359,6 @@ packages: ajv: optional: true - ajv-formats@3.0.1: - resolution: {integrity: sha512-8iUql50EUR+uUcdRQ3HDqa6EVyo3docL8g5WJ3FNcWmu62IbkGUue/pEyLBW8VGKKucTPgqeks4fIU1DA4yowQ==} - peerDependencies: - ajv: ^8.0.0 - peerDependenciesMeta: - ajv: - optional: true - ajv-keywords@5.1.0: resolution: {integrity: sha512-YCS/JNFAUyr5vAuhk1DWm1CBxRHW9LbJ2ozWeemrIqpbsqKjHVxYPyi5GC0rjZIT5JxJ3virVTS8wk4i/Z+krw==} peerDependencies: @@ -2348,6 +2413,9 @@ packages: argparse@2.0.1: resolution: {integrity: sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==} + aria-query@5.3.0: + resolution: {integrity: sha512-b0P0sZPKtyu8HkeRAfCq0IfURZK+SuwMjY1UXGBU27wpAiTwQAIlq56IbIO+ytk/JjS1fMR14ee5WBBfKi5J6A==} + array-union@2.1.0: resolution: {integrity: sha512-HGyxoOTYUyCM6stUe6EJgnd4EoewAI7zMdfqO+kGjnlZmBDz/cR5pf8r/cR4Wq60sL/p0IkcjUEEPwS3GFrIyw==} engines: {node: '>=8'} @@ -2369,13 +2437,6 @@ packages: balanced-match@1.0.2: resolution: {integrity: sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==} - balanced-match@4.0.4: - resolution: {integrity: sha512-BLrgEcRTwX2o6gGxGOCNyMvGSp35YofuYzw9h1IMTRmKqttAZZVU67bdb9Pr2vUHA8+j3i2tJfjO6C6+4myGTA==} - engines: {node: 18 || 20 || >=22} - - base64-js@1.5.1: - resolution: {integrity: sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==} - baseline-browser-mapping@2.11.21: resolution: {integrity: sha512-uh8vpY/1/YyFkunIDFH/12p7/7VdPKA1hejMVEbdkEaWnUz0Hesvx5EbiU6XxjyHZIOju+ZMbQJkRh+es3/spQ==} engines: {node: '>=6.0.0'} @@ -2385,20 +2446,12 @@ packages: resolution: {integrity: sha512-pbnl5XzGBdrFU/wT4jqmJVPn2B6UHPBOhzMQkY/SPUPB6QtUXtmBHBIwCbXJol93mOpGMnQyP/+BB19q04xj7g==} engines: {node: '>=4'} - body-parser@2.3.0: - resolution: {integrity: sha512-2cGmJupaNgg+QUwVLAucDuWuoMZ6EX9iHDRswZ5lsNYEmwPaRknMPCLZz07yTzVq/83p4o/wzbDZbBrTvGGTIw==} - engines: {node: '>=18'} - brace-expansion@1.1.21: resolution: {integrity: sha512-9zeA+KLZNNzglF2TPKRQEDyx6Yby7daAkuy8MiPzpXPsYDWi/DRM8jmwUDxokQjYqBpv5DgPiwD4h4ZZSy1Ujw==} brace-expansion@2.1.7: resolution: {integrity: sha512-uZbew1NqdmPDTMJ8ah1y+b+9QEJrfkXFk3RcTQw3X0jW/xRUvFKsg1CfQdSYGdTbXZWExtU3J3ccxtnfw1Fi0g==} - brace-expansion@5.0.12: - resolution: {integrity: sha512-YovQ3rzhaLMIrDjNDMkNS01tea93qhEhG5xy8f6+R0l+dw3Ki+5sCoIoI942iuLZTHWogWktgwVDhU09iNEimQ==} - engines: {node: 20 || >=22} - braces@3.0.3: resolution: {integrity: sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==} engines: {node: '>=8'} @@ -2417,30 +2470,14 @@ packages: peerDependencies: esbuild: 0.28.1 - bytes@3.1.2: - resolution: {integrity: sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg==} - engines: {node: '>= 0.8'} - cac@6.7.14: resolution: {integrity: sha512-b6Ilus+c3RrdDk+JhLKUAQfzzgLEPy6wcXqS7f/xe1EETvsDP6GORG7SFuOs6cID5YkqchW/LXZbX5bc8j7ZcQ==} engines: {node: '>=8'} - call-bind-apply-helpers@1.0.2: - resolution: {integrity: sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ==} - engines: {node: '>= 0.4'} - - call-bound@1.0.4: - resolution: {integrity: sha512-+ys997U96po4Kx/ABpBCqhA9EuxJaQWDQg7295H4hBphv3IZg0boBKuwYpt4YXp6MZ5AmZQnU/tyMTlRpaSejg==} - engines: {node: '>= 0.4'} - callsites@3.1.0: resolution: {integrity: sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==} engines: {node: '>=6'} - camelcase@6.3.0: - resolution: {integrity: sha512-Gmy6FhYlCY7uOElZUSbxo2UCDH8owEk996gkbrpsgGtrJLM3J7jGxl9Ic7Qwwj4ivOE5AWZWRMecDdF7hqGjFA==} - engines: {node: '>=10'} - caniuse-lite@1.0.30001810: resolution: {integrity: sha512-TITQPUkaz+aVk5GL6NhOdwk1aEaNTSDPsGFWrTuhKGtjTF70jL/Oht2W4c6rXUe5fu7Ie19VIahAXHIIiWWNeg==} @@ -2471,6 +2508,9 @@ packages: resolution: {integrity: sha512-rNjApaLzuwaOTjCiT8lSDdGN1APCiqkChLMJxJPWLunPAt5fy8xgU9/jNOchV84wfIxrA0lRQB7oCT8jrn/wrQ==} engines: {node: '>=6.0'} + cjs-module-lexer@1.4.3: + resolution: {integrity: sha512-9z8TZaGM1pfswYeXrUpzPrkx8UnWYdhJclsiYMm6x/w5+nN+8Tf/LnAgfLGQCm59qAOxU8WwHEq2vNwF6i4j+Q==} + cjs-module-lexer@2.2.0: resolution: {integrity: sha512-4bHTS2YuzUvtoLjdy+98ykbNB5jS0+07EvFNXerqZQJ89F7DI6ET7OQo/HJuW6K0aVsKA9hj9/RVb2kQVOrPDQ==} @@ -2478,14 +2518,6 @@ packages: resolution: {integrity: sha512-aCj4O5wKyszjMmDT4tZj93kxyydN/K5zPWSCe6/0AV/AA1pqe5ZBIw0a2ZfPQV7lL5/yb5HsUreJ6UFAF1tEQw==} engines: {node: '>=18'} - cli-progress@3.12.0: - resolution: {integrity: sha512-tRkV3HJ1ASwm19THiiLIXLO7Im7wlTuKnvkYaTkyoAPefqjNg7W7DHKUlGRxy9vxDvbyCYQkQozvptuMkGCg8A==} - engines: {node: '>=4'} - - cli-table3@0.6.5: - resolution: {integrity: sha512-+W/5efTR7y5HRD7gACw9yQjqMVvEMLBHmboM/kPWam+H+Hmyrgjh6YncVKK122YZkXrLudzTuAukUw9FnMf7IQ==} - engines: {node: 10.* || >= 12.*} - cli-truncate@5.1.1: resolution: {integrity: sha512-SroPvNHxUnk+vIW/dOSfNqdy1sPEFkrTk6TUtqLCnBlo3N7TNYYkzzN7uSD6+jVjrdO4+p8nH7JzH6cIvUem6A==} engines: {node: '>=20'} @@ -2535,41 +2567,17 @@ packages: confbox@0.1.8: resolution: {integrity: sha512-RMtmw0iFkeR4YV+fUOSucriAQNb9g8zFR52MWCtl+cCZOFRNL6zeB395vPzFhEjjn4fMxXudmELnl/KF/WrK6w==} - consola@3.4.0: - resolution: {integrity: sha512-EiPU8G6dQG0GFHNR8ljnZFki/8a+cQwEQ+7wpxdChl02Q8HXlwEZWD5lqAF8vC2sEC3Tehr8hy7vErz88LHyUA==} + consola@3.4.2: + resolution: {integrity: sha512-5IKcdX0nnYavi6G7TtOhwkYzyjfJlatbjMjuLSfE2kYT5pMDOilZ4OvMhi637CcDICTmz3wARPoyhqyX1Y+XvA==} engines: {node: ^14.18.0 || >=16.10.0} - content-disposition@1.0.0: - resolution: {integrity: sha512-Au9nRL8VNUut/XSzbQA38+M78dzP4D+eqg3gfJHMIHHYa3bg067xj1KxMUWj+VULbiZMowKngFFbKczUrNJ1mg==} - engines: {node: '>= 0.6'} - - content-type@1.0.5: - resolution: {integrity: sha512-nTjqfcBFEipKdXCv4YDQWCfmcLZKm81ldF0pAopTvyrFGVbcR6P/VAAd5G7N+0tTr8QqiU0tFadD6FK4NtJwOA==} - engines: {node: '>= 0.6'} - - content-type@2.0.0: - resolution: {integrity: sha512-j/O/d7GcZCyNl7/hwZAb606rzqkyvaDctLmckbxLzHvFBzTJHuGEdodATcP3yIRoDrLHkIATJuvzbFlp/ki2cQ==} - engines: {node: '>=18'} - convert-source-map@2.0.0: resolution: {integrity: sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==} - cookie-signature@1.2.2: - resolution: {integrity: sha512-D76uU73ulSXrD1UXF4KE2TMxVVwhsnCgfAyTg9k8P6KGZjlXKrOLe4dJQKI3Bxi5wjesZoFXJWElNWBjPZMbhg==} - engines: {node: '>=6.6.0'} - - cookie@0.7.2: - resolution: {integrity: sha512-yki5XnKuf750l50uGTllt6kKILY4nQ1eNIQatoXEByZ5dWgnKqbnqmTrBE5B4N7lrMJKQ2ytWMiTO2o0v6Ew/w==} - engines: {node: '>= 0.6'} - cookie@1.1.1: resolution: {integrity: sha512-ei8Aos7ja0weRpFzJnEA9UHJ/7XQmqglbRwnf2ATjcB9Wq874VKH9kfjjirM6UhU2/E5fFYadylyhFldcqSidQ==} engines: {node: '>=18'} - cors@2.8.5: - resolution: {integrity: sha512-KIHbLJqu73RGr/hnbrO9uBeixNGuvSQjul/jdFvS/KFSIH1hWVd1ng7zOHx+YrEfInLG7q4n6GHQ9cDtxv/P6g==} - engines: {node: '>= 0.10'} - cross-env@7.0.3: resolution: {integrity: sha512-+/HKd6EgcQCJGh2PSjZuUitQBQynKor4wrFbRg4DtAgS1aWO+gU52xpH7M9ScGgXSYmAVS9bIJ8EzuaGw0oNAw==} engines: {node: '>=10.14', npm: '>=6', yarn: '>=1'} @@ -2594,16 +2602,12 @@ packages: supports-color: optional: true - decamelize@1.2.0: - resolution: {integrity: sha512-z2S+W9X73hAUUki+N+9Za2lBlun89zigOyGrsax+KUQ6wKW4ZoWpEYBkGhQjwAjjDCkWxhY0VKEhk8wzY7F5cA==} - engines: {node: '>=0.10.0'} - deep-is@0.1.4: resolution: {integrity: sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==} - depd@2.0.0: - resolution: {integrity: sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw==} - engines: {node: '>= 0.8'} + dequal@2.0.3: + resolution: {integrity: sha512-0je+qPKHEMohvfRTCEo3CrPG6cAzAYgmzKyxRiYSSDkS6eGJdyVJm7WaYA5ECaAD9wLB2T4EEeymA5aFVcYXCA==} + engines: {node: '>=6'} detect-indent@6.1.0: resolution: {integrity: sha512-reYkTUJAZb9gUuZ2RvVCNhVHdg62RHnJ7WJl8ftMi4diZ6NWlciOzQN88pUhSELEwflJht4oQDv0F0BMlwaYtA==} @@ -2617,6 +2621,9 @@ packages: resolution: {integrity: sha512-WkrWp9GR4KXfKGYzOLmTuGVi1UWFfws377n9cc55/tb6DuqyF6pcQ5AbiHEshaDpY9v6oaSr2XCDidGmMwdzIA==} engines: {node: '>=8'} + dom-accessibility-api@0.5.16: + resolution: {integrity: sha512-X7BJ2yElsnOJ30pZF4uIIDfBEVgF4XEBxL9Bxhy6dnrm5hkzqmsWHGTiHqRiITNhMyFLyAiWndIJP7Z1NTteDg==} + dotenv@16.4.5: resolution: {integrity: sha512-ZmdL2rui+eB2YwhsWzjInR8LldtZHGDoQ1ugH85ppHKwpUHL7j7rN0Ti9NCnGiQbhaZ11FpR+7ao1dNsmduNUg==} engines: {node: '>=12'} @@ -2625,16 +2632,6 @@ packages: resolution: {integrity: sha512-IO8C/dzEb6O3F9/twg6ZLXz164a2fhTnEWb95H23Dm4OuN+92NmEAlTrupP9VW6Jm3sO26tQlqyvyi4CsnY9GA==} engines: {node: '>=12'} - dunder-proto@1.0.1: - resolution: {integrity: sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==} - engines: {node: '>= 0.4'} - - eastasianwidth@0.2.0: - resolution: {integrity: sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA==} - - ee-first@1.1.1: - resolution: {integrity: sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow==} - electron-to-chromium@1.5.422: resolution: {integrity: sha512-UvA/32XqrLDdZSn7Jllo1AYNcWji/G0d5M0GTViE7KoGBiMunw3a34Sb2KO4ZZyrSEhqsxFoVhWWJshdyfKqJA==} @@ -2644,13 +2641,6 @@ packages: emoji-regex@8.0.0: resolution: {integrity: sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==} - emoji-regex@9.2.2: - resolution: {integrity: sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg==} - - encodeurl@2.0.0: - resolution: {integrity: sha512-Q0n9HRi4m6JuGIV1eFlmvJB7ZEVxu93IrMyiMsGC0lrMJMWzRgx6WGquyfQgZVb31vhGgXnfmPNNXmxnOkRBrg==} - engines: {node: '>= 0.8'} - encoding@0.1.13: resolution: {integrity: sha512-ETBauow1T35Y/WZMkio9jiM0Z5xjHHmJ4XmjZOq1l/dXz3lr2sRn87nJy20RupqSh1F2m3HHPSp8ShIPQJrJ3A==} @@ -2666,21 +2656,9 @@ packages: resolution: {integrity: sha512-xUtoPkMggbz0MPyPiIWr1Kp4aeWJjDZ6SMvURhimjdZgsRuDplF5/s9hcgGhyXMhs+6vpnuoiZ2kFiu3FMnS8Q==} engines: {node: '>=18'} - es-define-property@1.0.1: - resolution: {integrity: sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g==} - engines: {node: '>= 0.4'} - - es-errors@1.3.0: - resolution: {integrity: sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==} - engines: {node: '>= 0.4'} - es-module-lexer@2.3.2: resolution: {integrity: sha512-poHGpORABojJJucnV9KbOavETW8lBVnphkW77ER5/BQ5Fz7oXSoCNek7IH3vR5nRjdsEz926ibFYX8KtLQmdyw==} - es-object-atoms@1.1.1: - resolution: {integrity: sha512-FGgH2h8zKNim9ljj7dankFPcICIK9Cp5bm+c2gQSYePhpaG5+esrLODihIorn+Pe6FGJzWhXQotPv73jTaldXA==} - engines: {node: '>= 0.4'} - esbuild@0.28.1: resolution: {integrity: sha512-HrJrvZv5ayxBzPfwphOoNzkzOIIlifzk0KJrGK2c8R4+LKpMtpYLQeUdjnwjWv/LZlkH2laZk+4w78pi99D4Vw==} engines: {node: '>=18'} @@ -2690,9 +2668,6 @@ packages: resolution: {integrity: sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==} engines: {node: '>=6'} - escape-html@1.0.3: - resolution: {integrity: sha512-NiSupZ4OeuGwr68lGIeym/ksIZMJodUGOSCZ/FSnTxcrekbvqrgdUxlJOMpijaKZVjAJrWrGs/6Jy8OMuyj9ow==} - escape-string-regexp@4.0.0: resolution: {integrity: sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==} engines: {node: '>=10'} @@ -2709,12 +2684,6 @@ packages: peerDependencies: eslint: '>=8' - eslint-plugin-es-x@9.7.0: - resolution: {integrity: sha512-BT0pUw0toxtrHulYycGiUTL++qC11opyPYMuGUR7AgmuQ/N1YN1xLupF8rGxlaGqEqkqeedJuacCxxAcYgGkvg==} - engines: {node: ^20.19.0 || >=22.12.0} - peerDependencies: - eslint: '>=9.29.0' - eslint-plugin-n@18.2.1: resolution: {integrity: sha512-aO3C9//yq8JIvYOi/T+jPvcZ9hZzpwzbR8esrYpFtgE9vpbyM8kn42AQOtIqYspVmpaSWr8X+nrlQuAJYxXAaw==} engines: {node: ^20.19.0 || ^22.13.0 || >=24} @@ -2728,12 +2697,6 @@ packages: typescript: optional: true - eslint-plugin-node-import@1.0.5: - resolution: {integrity: sha512-razzgbr3EcB5+bm8/gqTqzTJ7Bpiu8PIChiAMRfZCNigr9GZBtnVSI+wPw+RGbWYCCIzWAsK/A7ihoAeSz5j7A==} - engines: {node: ^14.18.0 || ^16.0.0 || >= 18.0.0} - peerDependencies: - eslint: '>=7' - eslint-scope@5.1.1: resolution: {integrity: sha512-2NxwbF/hZ0KpepYN0cNbo+FN6XoK7GaHlQhgx/hIZl6Va0bF45RQOOwhLIy8lQDbuCiadSLCBnH2CFYquit5bw==} engines: {node: '>=8.0.0'} @@ -2742,12 +2705,6 @@ packages: resolution: {integrity: sha512-sNXOfKCn74rt8RICKMvJS7XKV/Xk9kA7DyJr8mJik3S7Cwgy3qlkkmyS2uQB3jiJg6VNdZd/pDBJu0nvG2NlTg==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} - eslint-type-tracer@0.5.2: - resolution: {integrity: sha512-YiJmgk6OD1suPB4UC3M/PB+oxahf145jQQ5gFXFk9F9Y7puWwnqGYC2BtTB+FoHqdOohImeTt0rNk0lhhECNaQ==} - engines: {node: ^18 || >=20} - peerDependencies: - eslint: '>=8' - eslint-visitor-keys@3.4.3: resolution: {integrity: sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag==} engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} @@ -2799,17 +2756,10 @@ packages: resolution: {integrity: sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==} engines: {node: '>=0.10.0'} - etag@1.8.1: - resolution: {integrity: sha512-aIL5Fx7mawVa300al2BnEE4iNvo1qETxLrPI/o05L7z6go7fCw1J6EQmbK4FmJ2AS7kgVF/KEZWufBfdClMcPg==} - engines: {node: '>= 0.6'} - event-target-shim@5.0.1: resolution: {integrity: sha512-i/2XbnSz/uxRCU6+NdVJgKWDTM427+MqYbkQzD321DuCQJUqOuJKIA0IM2+W2xtYHdKOmZ4dR6fExsd4SXL+WQ==} engines: {node: '>=6'} - eventemitter3@4.0.7: - resolution: {integrity: sha512-8guHBZCwKnFhYdHr2ysuRWErTwhoN2X8XELRlrRwpmfeY2jjuUN4taQMsULKUVo1K4DvZl+0pgfyoysHxvmvEw==} - eventemitter3@5.0.4: resolution: {integrity: sha512-mlsTRyGaPBjPedk6Bvw+aqbsXDtoAyAzm5MO7JgU+yVRyMQ5O8bD4Kcci7BS85f93veegeCPkL8R4GLClnjLFw==} @@ -2825,24 +2775,10 @@ packages: resolution: {integrity: sha512-EKN1vKAMcZ8MlYMpaNuxN6R9yakzH6uajHcHVTqWJzvu5pWw9DyhbP35HH8MVBQ+dZjAfDxk+A8NiR9KWaXiyQ==} engines: {node: '>=18.0.0'} - eventsource@3.0.7: - resolution: {integrity: sha512-CRT1WTyuQoD771GW56XEZFQ/ZoSfWid1alKGDYMmkt2yl8UXrVR4pspqWNEcqKvVIzg6PAltWjxcSSPrboA4iA==} - engines: {node: '>=18.0.0'} - expect-type@1.4.0: resolution: {integrity: sha512-KfYbmpRm0VbLjEvVa9yGwCi9GI34xvi7A/HXYWQO65CSD2u3MczUJSuwXKFIxlGsgBQizV9q5J9NHj4VG0n+pA==} engines: {node: '>=12.0.0'} - express-rate-limit@8.3.2: - resolution: {integrity: sha512-77VmFeJkO0/rvimEDuUC5H30oqUC4EyOhyGccfqoLebB0oiEYfM7nwPrsDsBL1gsTpwfzX8SFy2MT3TDyRq+bg==} - engines: {node: '>= 16'} - peerDependencies: - express: '>= 4.11' - - express@5.2.1: - resolution: {integrity: sha512-hIS4idWWai69NezIdRt2xFVofaF4j+6INOpJlVOLDO8zXGpUVEVzIYk12UUi2JzjEzWL3IOAxcTubgz9Po0yXw==} - engines: {node: '>= 18'} - extendable-error@0.1.7: resolution: {integrity: sha512-UOiS2in6/Q0FK0R0q6UY9vYpQ21mr/Qn1KOnte7vsACuNJf514WvCCUHSRCPcgjPT2bAhNIJdlE6bVap1GKmeg==} @@ -2886,6 +2822,9 @@ packages: picomatch: optional: true + fflate@0.8.3: + resolution: {integrity: sha512-tbZNuJrLwGUp3zshBtdy4W+ORxZuIh8a5ilyIEQDC5rY1f3U20JMry0Ll3WBzU58EZKsEuJFXhb5gwv8CsPvgA==} + file-entry-cache@8.0.0: resolution: {integrity: sha512-XXTUwCvisa5oacNGRP9SfNtYBNAMi+RPwBFmblZEF7N7swHYQS6/Zfk7SRwx4D5j3CH211YNRco1DEMNVfZCnQ==} engines: {node: '>=16.0.0'} @@ -2894,10 +2833,6 @@ packages: resolution: {integrity: sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==} engines: {node: '>=8'} - finalhandler@2.1.0: - resolution: {integrity: sha512-/t88Ty3d5JWQbWYgaOGCCYfXRwV1+be02WqYYlL6h0lEiUAMPM8o8qKGO01YIkOHzka2up08wvgYD0mDiI+q3Q==} - engines: {node: '>= 0.8'} - find-up@4.1.0: resolution: {integrity: sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw==} engines: {node: '>=8'} @@ -2913,26 +2848,14 @@ packages: resolution: {integrity: sha512-f7ccFPK3SXFHpx15UIGyRJ/FJQctuKZ0zVuN3frBo4HnK3cay9VEW0R6yPYFHC0AgqhukPzKjq22t5DmAyqGyw==} engines: {node: '>=16'} - flatted@3.4.2: - resolution: {integrity: sha512-PjDse7RzhcPkIJwy5t7KPWQSZ9cAbzQXcafsetQoD7sOJRQlGikNbx7yZp2OotDnJyrDcbyRq3Ttb18iYOqkxA==} - - foreground-child@3.3.1: - resolution: {integrity: sha512-gIXjKqtFuWEgzFRJA9WCQeSJLZDjgJUOMCMzxtvFq/37KojM1BFGufqsCy0r4qSQmYLsZYMeyRqzIWOMup03sw==} - engines: {node: '>=14'} + flatted@3.4.4: + resolution: {integrity: sha512-5+ybhBZANEJxaH3X5evAFatUxLfEHSr7n6kYJ+1Qd0mUqr4eu9gIf6GDbWHf8RJijHrjjO8G+la14SlL2SeS1Q==} formatly@0.3.0: resolution: {integrity: sha512-9XNj/o4wrRFyhSMJOvsuyMwy8aUfBaZ1VrqHVfohyXf0Sw0e+yfKG+xZaY3arGCOMdwFsqObtzVOc1gU9KiT9w==} engines: {node: '>=18.3.0'} hasBin: true - forwarded@0.2.0: - resolution: {integrity: sha512-buRG0fpBtRHSTCOASe6hD258tEubFoRLb4ZNA6NxMVHNw2gOcwHo9wyablzMzOA5z9xA9L1KNjk/Nt6MT9aYow==} - engines: {node: '>= 0.6'} - - fresh@2.0.0: - resolution: {integrity: sha512-Rx/WycZ60HOaqLKAi6cHRKKI7zxWbJ31MhntmtwMoaTeF7XFH9hhBp8vITaMidfljRQ6eYWCKkaTK+ykVJHP2A==} - engines: {node: '>= 0.8'} - fs-extra@7.0.1: resolution: {integrity: sha512-YJDaCJZEnBmcbw13fvdAM9AwNOJwOzrE4pqMqBq5nFiEqXUqHwlK4B+3pUw6JNvfSPtX05xFHtYy/1ni01eGCw==} engines: {node: '>=6 <7 || >=8'} @@ -2952,9 +2875,6 @@ packages: engines: {node: ^8.16.0 || ^10.6.0 || >=11.0.0} os: [darwin] - function-bind@1.1.2: - resolution: {integrity: sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==} - get-caller-file@2.0.5: resolution: {integrity: sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==} engines: {node: 6.* || 8.* || >= 10.*} @@ -2963,14 +2883,6 @@ packages: resolution: {integrity: sha512-CQ+bEO+Tva/qlmw24dCejulK5pMzVnUOFOijVogd3KQs07HnRIgp8TGipvCCRT06xeYEbpbgwaCxglFyiuIcmA==} engines: {node: '>=18'} - get-intrinsic@1.3.0: - resolution: {integrity: sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ==} - engines: {node: '>= 0.4'} - - get-proto@1.0.1: - resolution: {integrity: sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g==} - engines: {node: '>= 0.4'} - get-tsconfig@4.13.0: resolution: {integrity: sha512-1VKTZJCwBrvbd+Wn3AOgQP/2Av+TfTCOlE4AcRJE72W1ksZXbAx8PPBR9RzgTeSPzlPMHrbANMH3LbltH73wxQ==} @@ -2991,11 +2903,6 @@ packages: glob-to-regexp@0.4.1: resolution: {integrity: sha512-lkX1HJXwyMcprw/5YUZc2s7DrpAiHB21/V+E1rHUrVNokkvB6bqMzT0VfV6/86ZNabt1k14YOIaT7nDvOX3Iiw==} - glob@10.5.0: - resolution: {integrity: sha512-DfXN8DfhJ7NH3Oe7cFmu3NCu1wKbkReJ8TorzSAFbSKrlNaQSKfIzqYqVY8zlbs2NLBbWpRiU52GX2PbaBVNkg==} - deprecated: Old versions of glob are not supported, and contain widely publicized security vulnerabilities, which have been fixed in the current version. Please update. Support for old versions may be purchased (at exorbitant rates) by contacting i@izs.me - hasBin: true - glob@7.2.3: resolution: {integrity: sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==} deprecated: Old versions of glob are not supported, and contain widely publicized security vulnerabilities, which have been fixed in the current version. Please update. Support for old versions may be purchased (at exorbitant rates) by contacting i@izs.me @@ -3015,10 +2922,6 @@ packages: globrex@0.1.2: resolution: {integrity: sha512-uHJgbwAMwNFf5mLst7IWLNg14x1CkeqglJb/K3doi4dw6q2IvAAmM/Y81kevy83wP+Sst+nutFTYOGg3d1lsxg==} - gopd@1.2.0: - resolution: {integrity: sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg==} - engines: {node: '>= 0.4'} - graceful-fs@4.2.11: resolution: {integrity: sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==} @@ -3035,14 +2938,6 @@ packages: resolution: {integrity: sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==} engines: {node: '>=8'} - has-symbols@1.1.0: - resolution: {integrity: sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ==} - engines: {node: '>= 0.4'} - - hasown@2.0.3: - resolution: {integrity: sha512-ej4AhfhfL2Q2zpMmLo7U1Uv9+PyhIZpgQLGT1F9miIGmiCJIoCgSmczFdrc97mWT4kVY72KA+WnnhJ5pghSvSg==} - engines: {node: '>= 0.4'} - headers-polyfill@5.0.1: resolution: {integrity: sha512-1TJ6Fih/b8h5TIcv+1+Hw0PDQWJTKDKzFZzcKOiW1wJza3XoAQlkCuXLbymPYB8+ZQyw8mHvdw560e8zVFIWyA==} @@ -3050,14 +2945,6 @@ packages: resolution: {integrity: sha512-EQfezRg0NCZGNlhlDR3Evrw1FVL2G3LhU7EgPoxufQKruNBSYA8MiRPHeWbU+36o+Fhel0wMwM+sLEiBAlNLJA==} engines: {node: '>=10.0.0'} - hono@4.13.13: - resolution: {integrity: sha512-CQ46U0ZkAGmbT/4UxdzzGJpacP2IeKgY4a5/tOI9AABbpOMfK739wfDXmv1usCk+3RkKj1hQy4/fjhiwa2xlrA==} - engines: {node: '>=16.9.0'} - - http-errors@2.0.1: - resolution: {integrity: sha512-4FbRdAX+bSdmo4AUFuS0WNiPz8NgFt+r8ThgNWmlrjQjt1Q7ZR9+zTlce2859x4KSXrwIsaeTqDoKQmtP8pLmQ==} - engines: {node: '>= 0.8'} - human-id@4.1.3: resolution: {integrity: sha512-tsYlhAYpjCKa//8rXZ9DqKEawhPoSytweBC2eNvcaDK+57RZLHGqNs3PZTQO6yekLFSuvA6AlnAfrw1uBvtb+Q==} hasBin: true @@ -3086,10 +2973,6 @@ packages: resolution: {integrity: sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g==} engines: {node: '>= 4'} - ignore@7.0.5: - resolution: {integrity: sha512-Hs59xBNfUIunMFgWAbGX5cq6893IbWg4KnrjbYwX3tx0ztorVgTDA6B2sxf8ejHJ4wz8BqGUMYlnzNBer5NvGg==} - engines: {node: '>= 4'} - import-fresh@3.3.1: resolution: {integrity: sha512-TR3KfrTZTYLPB6jUjfx6MF9WcWrHL9su5TObK4ZkYgBdWKPOFoSoQIdEuTuR82pmtxH2spWG9h6etwfr1pLBqQ==} engines: {node: '>=6'} @@ -3109,14 +2992,6 @@ packages: resolution: {integrity: sha512-7PnF4oN3CvZF23ADhA5wRaYEQpJ8qygSkbtTXWBeXWXmEVRXK+1ITciHWwHhsjv1TmW0MgacIv6hEi5pX5NQdA==} engines: {node: '>=10'} - ip-address@10.7.3: - resolution: {integrity: sha512-A1kdq/tSb5QjvKvAMgIoEvDBIgL7qaqVP/jkvSwYYRZ9iEzvPpopxp2wQfu3SuZRHtpHNxMn8Fs0bS+gf5Xmwg==} - engines: {node: '>= 12'} - - ipaddr.js@1.9.1: - resolution: {integrity: sha512-0KI/607xoxSToH7GjN1FfSbLoU0+btTicjsQSWQlh/hZykN8KpmMf7uYwPW3R+akZ6R/w18ZlXSHBYXiYUPO3g==} - engines: {node: '>= 0.10'} - is-extglob@2.1.1: resolution: {integrity: sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==} engines: {node: '>=0.10.0'} @@ -3140,9 +3015,6 @@ packages: resolution: {integrity: sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==} engines: {node: '>=0.12.0'} - is-promise@4.0.0: - resolution: {integrity: sha512-hvpoI6korhJMnej285dSg6nu1+e6uxs7zG3BYAm5byqDsgJNWwxzM6z6iZiAgQR4TJ30JmBTOwqZUw3WlyH3AQ==} - is-subdir@1.2.0: resolution: {integrity: sha512-2AT6j+gXe/1ueqbW6fLZJiIw3F8iXGJtt0yDrZaBhAZEG1raiTxKWU+IPqMCzQAXOUCKdA4UDMgacKH25XG2Cw==} engines: {node: '>=4'} @@ -3154,9 +3026,6 @@ packages: isexe@2.0.0: resolution: {integrity: sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==} - jackspeak@3.4.3: - resolution: {integrity: sha512-OGlZQpz2yfahA/Rd1Y8Cd9SIEsqvXkLVoSw/cgwhnhFMDbsQFeZYoJJ7bIZBS9BcamUW96asq/npPWugM+RQBw==} - jest-worker@27.5.1: resolution: {integrity: sha512-7vuh85V5cdDofPyxn58nrPjBktZo0u9x1g8WtjQol+jZDaE+fhN+cIvTj11GndBnMnyfrUOG1sZQxCdjKh+DKg==} engines: {node: '>= 10.13.0'} @@ -3165,15 +3034,12 @@ packages: resolution: {integrity: sha512-ekilCSN1jwRvIbgeg/57YFh8qQDNbwDb9xT/qu2DAHbFFZUicIl4ygVaAvzveMhMVr3LnpSKTNnwt8PoOfmKhQ==} hasBin: true - jose@6.2.2: - resolution: {integrity: sha512-d7kPDd34KO/YnzaDOlikGpOurfF0ByC2sEV4cANCtdqLlTfBlw2p14O/5d/zv40gJPbIQxfES3nSx1/oYNyuZQ==} - joycon@3.1.1: resolution: {integrity: sha512-34wB/Y7MW7bzjKRjUKTa46I2Z7eV62Rkhva+KkopW7Qvv/OSWBqvkSY7vusOPrNuZcUG3tApvdVgNB8POj3SPw==} engines: {node: '>=10'} - js-tiktoken@1.0.21: - resolution: {integrity: sha512-biOj/6M5qdgx5TKjDnFT1ymSpM5tbd3ylwDtrQvFQSu0Z7bBYko2dF+W/aUkXUPuk6IVpRxk/3Q2sHOzGlS36g==} + js-tokens@4.0.0: + resolution: {integrity: sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==} js-yaml@3.15.2: resolution: {integrity: sha512-6EuL879VkRA+1Cz578mKMiKvjPNEuk6+r1JaFzoSWejZmtf7xWbIyw1e3KkxlkzTIt9Taw6JBhEppG7utc1P+w==} @@ -3186,19 +3052,12 @@ packages: json-buffer@3.0.1: resolution: {integrity: sha512-4bV5BfR2mqfQTJm+V5tPPdf+ZpuhiIvTuAB5g8kcrXOZpTT/QwwVRWBywX1ozr6lEuPdbHxwaJlm9G6mI2sfSQ==} - json-schema-to-ts@3.1.1: - resolution: {integrity: sha512-+DWg8jCJG2TEnpy7kOm/7/AxaYoaRbjVB4LFZLySZlWn8exGs3A4OLJR966cVvU26N7X9TWxl+Jsw7dzAqKT6g==} - engines: {node: '>=16'} - json-schema-traverse@0.4.1: resolution: {integrity: sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==} json-schema-traverse@1.0.0: resolution: {integrity: sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==} - json-schema-typed@8.0.2: - resolution: {integrity: sha512-fQhoXdcvc3V28x7C7BMs4P5+kNlgUURe2jmUT1T//oBRMDrqy1QPelJimwZGo7Hg9VPV3EQV5Bnq4hbFy2vetA==} - json-schema@0.4.0: resolution: {integrity: sha512-es94M3nTIfsEPisRafak+HDLfHXnKBhV3vU5eqPcS3flIWqcxJWgXHXiey3YrpaNsanY5ei1VoYEbOzijuq9BA==} @@ -3222,106 +3081,86 @@ packages: '@types/node': '>=18' typescript: '>=5.0.4 <7' - langsmith@0.7.1: - resolution: {integrity: sha512-Wjk90UjNoY5cBHMlNAC/eZx5clI8jnjBOBW8uJu8+MWBtx0QesNjsUiLtjI+I3UnrpxFFpDqGXcnhBjH654Mqg==} - peerDependencies: - '@opentelemetry/api': '*' - '@opentelemetry/exporter-trace-otlp-proto': '*' - '@opentelemetry/sdk-trace-base': '*' - openai: '*' - ws: '>=7' - peerDependenciesMeta: - '@opentelemetry/api': - optional: true - '@opentelemetry/exporter-trace-otlp-proto': - optional: true - '@opentelemetry/sdk-trace-base': - optional: true - openai: - optional: true - ws: - optional: true - levn@0.4.1: resolution: {integrity: sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ==} engines: {node: '>= 0.8.0'} - lightningcss-android-arm64@1.32.0: - resolution: {integrity: sha512-YK7/ClTt4kAK0vo6w3X+Pnm0D2cf2vPHbhOXdoNti1Ga0al1P4TBZhwjATvjNwLEBCnKvjJc2jQgHXH0NEwlAg==} + lightningcss-android-arm64@1.33.0: + resolution: {integrity: sha512-gEpRTalKdosp4Bb8qWtc2iOgE5SeIHlpS1up9bFq2wAyYhl1UdTObYiHe98zEM9SQvSoqQZ1IQD0JNpg3Ml5pg==} engines: {node: '>= 12.0.0'} cpu: [arm64] os: [android] - lightningcss-darwin-arm64@1.32.0: - resolution: {integrity: sha512-RzeG9Ju5bag2Bv1/lwlVJvBE3q6TtXskdZLLCyfg5pt+HLz9BqlICO7LZM7VHNTTn/5PRhHFBSjk5lc4cmscPQ==} + lightningcss-darwin-arm64@1.33.0: + resolution: {integrity: sha512-Sciaz8eenNTKn9b3t7+xr0ipTp9YxKQY4npwQ3mrRuL0BAVHBLyZxofhaKBAVtzmtRZ/zTyo0/to4B1uWG/Djg==} engines: {node: '>= 12.0.0'} cpu: [arm64] os: [darwin] - lightningcss-darwin-x64@1.32.0: - resolution: {integrity: sha512-U+QsBp2m/s2wqpUYT/6wnlagdZbtZdndSmut/NJqlCcMLTWp5muCrID+K5UJ6jqD2BFshejCYXniPDbNh73V8w==} + lightningcss-darwin-x64@1.33.0: + resolution: {integrity: sha512-Z5UPAxzrjlWNNyGy6i65cJzzvgJ5D3T6wMvs+gWpY9d7qRhANrxqAp6LhxIgZhWEw18RfJTGcRxjuLIBr+m8XQ==} engines: {node: '>= 12.0.0'} cpu: [x64] os: [darwin] - lightningcss-freebsd-x64@1.32.0: - resolution: {integrity: sha512-JCTigedEksZk3tHTTthnMdVfGf61Fky8Ji2E4YjUTEQX14xiy/lTzXnu1vwiZe3bYe0q+SpsSH/CTeDXK6WHig==} + lightningcss-freebsd-x64@1.33.0: + resolution: {integrity: sha512-QQM/Ti/hQajJwCY+RiWuCZ9sdtI/XQk7nDK5vC8kkdwixezOlDgvDx7+RT+QjK6FcFT4MpsuoBnHIo/O3StRRg==} engines: {node: '>= 12.0.0'} cpu: [x64] os: [freebsd] - lightningcss-linux-arm-gnueabihf@1.32.0: - resolution: {integrity: sha512-x6rnnpRa2GL0zQOkt6rts3YDPzduLpWvwAF6EMhXFVZXD4tPrBkEFqzGowzCsIWsPjqSK+tyNEODUBXeeVHSkw==} + lightningcss-linux-arm-gnueabihf@1.33.0: + resolution: {integrity: sha512-N7FVBe6iS24MlM6R/4RBTxGhQheZGs7tiQ9U32UtF75NzP5Q7xWPRqLBCKxlRQRk3rY1jCIPLzx7WzOhuUIRLQ==} engines: {node: '>= 12.0.0'} cpu: [arm] os: [linux] - lightningcss-linux-arm64-gnu@1.32.0: - resolution: {integrity: sha512-0nnMyoyOLRJXfbMOilaSRcLH3Jw5z9HDNGfT/gwCPgaDjnx0i8w7vBzFLFR1f6CMLKF8gVbebmkUN3fa/kQJpQ==} + lightningcss-linux-arm64-gnu@1.33.0: + resolution: {integrity: sha512-j2v/itmy4HlNxlc6voKXYgBqNi0Ng2LShg4z7GufpEgs05P+2suBVyi9I6YHq5uoVFx9ETin3eCEhLVyXGQnKg==} engines: {node: '>= 12.0.0'} cpu: [arm64] os: [linux] libc: [glibc] - lightningcss-linux-arm64-musl@1.32.0: - resolution: {integrity: sha512-UpQkoenr4UJEzgVIYpI80lDFvRmPVg6oqboNHfoH4CQIfNA+HOrZ7Mo7KZP02dC6LjghPQJeBsvXhJod/wnIBg==} + lightningcss-linux-arm64-musl@1.33.0: + resolution: {integrity: sha512-yiO5ROMuYQgXbC60yjZU5CYSFZGKXL0HFATXt9mHJn1+zW55oCtMI9NfcVhYLMFDL7gV7oBPon/EmMMGg2OvtQ==} engines: {node: '>= 12.0.0'} cpu: [arm64] os: [linux] libc: [musl] - lightningcss-linux-x64-gnu@1.32.0: - resolution: {integrity: sha512-V7Qr52IhZmdKPVr+Vtw8o+WLsQJYCTd8loIfpDaMRWGUZfBOYEJeyJIkqGIDMZPwPx24pUMfwSxxI8phr/MbOA==} + lightningcss-linux-x64-gnu@1.33.0: + resolution: {integrity: sha512-ar+Ju7LmcN0Jo4FpL4hpFybwNG9/3A/Br5KW2n2jyODg3MEZXaDYADdemoNS+BDNfMgKvylJLj4S5tyRActuAg==} engines: {node: '>= 12.0.0'} cpu: [x64] os: [linux] libc: [glibc] - lightningcss-linux-x64-musl@1.32.0: - resolution: {integrity: sha512-bYcLp+Vb0awsiXg/80uCRezCYHNg1/l3mt0gzHnWV9XP1W5sKa5/TCdGWaR/zBM2PeF/HbsQv/j2URNOiVuxWg==} + lightningcss-linux-x64-musl@1.33.0: + resolution: {integrity: sha512-RYiYbkokw0trfKqqzfF55lginwEPrD3OJDfTuJzFs1MK6iFnDenaz1fqLLtX4ITG3OktJQXOeTaw1awrBAlZPw==} engines: {node: '>= 12.0.0'} cpu: [x64] os: [linux] libc: [musl] - lightningcss-win32-arm64-msvc@1.32.0: - resolution: {integrity: sha512-8SbC8BR40pS6baCM8sbtYDSwEVQd4JlFTOlaD3gWGHfThTcABnNDBda6eTZeqbofalIJhFx0qKzgHJmcPTnGdw==} + lightningcss-win32-arm64-msvc@1.33.0: + resolution: {integrity: sha512-1K+MPfLSFVpphzpdbfkhlWk6wBrTObBzS2T6db10PNOZgR9GoVsAWzwNyuhUYYbTp23j+4RrncfujZ4uAzXvwA==} engines: {node: '>= 12.0.0'} cpu: [arm64] os: [win32] - lightningcss-win32-x64-msvc@1.32.0: - resolution: {integrity: sha512-Amq9B/SoZYdDi1kFrojnoqPLxYhQ4Wo5XiL8EVJrVsB8ARoC1PWW6VGtT0WKCemjy8aC+louJnjS7U18x3b06Q==} + lightningcss-win32-x64-msvc@1.33.0: + resolution: {integrity: sha512-OlEICDx/Xl0FqSp4bry8zFnCvGpig3Gl4gCquvYwHuqJKEC1+n9NgDniFvqHGmMv1ZkqDJrDqKKSykTDX+ehuA==} engines: {node: '>= 12.0.0'} cpu: [x64] os: [win32] - lightningcss@1.32.0: - resolution: {integrity: sha512-NXYBzinNrblfraPGyrbPoD19C1h9lfI/1mzgWYvXUTe414Gz/X1FD2XBZSZM7rRTrMA8JL3OtAaGifrIKhQ5yQ==} + lightningcss@1.33.0: + resolution: {integrity: sha512-WkUDrojuJs0xkgGf2udWxa3yGBRxPtxUkB79i6aCZLRgc7PM8fZe9TosfPDcvEpQZbuFASnHYmRLBLUbmLOIIA==} engines: {node: '>= 12.0.0'} - lilconfig@3.1.3: - resolution: {integrity: sha512-/vlFKAoH5Cgt3Ie+JLhRbwOsCQePABiU3tJ1egGvyQ+33R/vcwM2Zl2QR/LzjsBeItPt3oSVXapn+m4nQDvpzw==} + lilconfig@3.1.4: + resolution: {integrity: sha512-zJZyBmQhArDRefuWTypZxwDpFDIYmrj8ypSBvwGD5P13Y95s6DjzKgu4DVdGbeQa5PDEsAOCAPNk4X+QOR006w==} engines: {node: '>=14'} lines-and-columns@1.2.4: @@ -3368,35 +3207,31 @@ packages: long@5.3.2: resolution: {integrity: sha512-mNAgZ1GmyNhD7AuqnTG3/VQ26o760+ZYBPKjPvugO8+nLbYfX6TVpJPseBvopbdY+qpZ/lKUnmEc1LeZYS3QAA==} - lru-cache@10.4.3: - resolution: {integrity: sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ==} + lru-cache@11.5.3: + resolution: {integrity: sha512-U4N8FgzmWxc8k1VH8Kr6lQg18U7Fjvby6wXHVRX/ZZ7IwWbRMgrRbP0Wrb5q5NVinryp4SQampHKdvtecItxUg==} + engines: {node: 20 || >=22} lunr@2.3.9: resolution: {integrity: sha512-zTU3DaZaF3Rt9rhN3uBMGQD3dD2/vFQqnvZCDv4dl5iOzq2IZQqTxu90r4E5J+nP70J3ilqVCrbho2eWaeW8Ow==} + lz-string@1.5.0: + resolution: {integrity: sha512-h5bgJWpxJNswbU7qCrV0tIKQCaS3blPDrqKWx+QxzuzL1zGUzij9XCWLrSLsJPu5t+eWA/ycetzYAO5IOMcWAQ==} + hasBin: true + magic-string@0.30.21: resolution: {integrity: sha512-vd2F4YUyEXKGcLHoq+TEyCjxueSeHnFxyyjNp80yg0XV4vUhnDer/lvvlqM/arB5bXQN5K2/3oinyCRyx8T2CQ==} + magic-string@1.4.3: + resolution: {integrity: sha512-z12OxmaPGE0F4xlpGdjuAUckipLpQlTAuAmyGhNoD7ODpYUzvDfvtxUbjM/jwznyP178oju/KDdyi220wNJ0RQ==} + marked@4.3.0: resolution: {integrity: sha512-PRsaiG84bK+AMvxziE/lCFss8juXjNaWzVbN5tXAm4XjeaS9NAHhop+PjQxz2A9h8Q4M/xGmzP8vqNwy6JeK0A==} engines: {node: '>= 12'} hasBin: true - math-intrinsics@1.1.0: - resolution: {integrity: sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==} - engines: {node: '>= 0.4'} - - media-typer@1.1.0: - resolution: {integrity: sha512-aisnrDP4GNe06UcKFnV5bfMNPBUw4jsLGaWwWfnH3v02GnBuXX2MCVn5RbrWo0j3pczUilYblq7fQ7Nw2t5XKw==} - engines: {node: '>= 0.8'} - memfs@4.51.1: resolution: {integrity: sha512-Eyt3XrufitN2ZL9c/uIRMyDwXanLI88h/L3MoWqNY747ha3dMR9dWqp8cRT5ntjZ0U1TNuq4U91ZXK0sMBjYOQ==} - merge-descriptors@2.0.0: - resolution: {integrity: sha512-Snk314V5ayFLhp3fkUREub6WtjBfPdCPY1Ln8/8munuLuiYhsABgBVWsozAG+MWMbVEvcdcpbi9R7ww22l9Q3g==} - engines: {node: '>=18'} - merge-stream@2.0.0: resolution: {integrity: sha512-abv/qOcuPfk3URPfDzmZU1LKmuw8kT+0nIHvKrKgFrwifol/doWcdA4ZqsWQ8ENrFKkd67Mfpo/LovbIUsbt3w==} @@ -3416,18 +3251,10 @@ packages: resolution: {integrity: sha512-aU5EJuIN2WDemCcAp2vFBfp/m4EAhWJnUNSSw0ixs7/kXbd6Pg64EmwJkNdFhB8aWt1sH2CTXrLxo/iAGV3oPQ==} engines: {node: '>= 0.6'} - mime-types@3.0.1: - resolution: {integrity: sha512-xRc4oEhT6eaBpU1XF7AjpOFD+xQmXNB5OVKwp4tqCuBpHLS/ZbBDrc07mYTDqVMg6PfxUjjNp85O6Cd2Z/5HWA==} - engines: {node: '>= 0.6'} - mimic-function@5.0.1: resolution: {integrity: sha512-VP79XUPxV2CigYP3jWwAUFSku2aKqBH7uTAapFWCBqutsbmDo96KY5o8uh6U+/YSIn5OxJnXp73beVkpqMIGhA==} engines: {node: '>=18'} - minimatch@10.2.5: - resolution: {integrity: sha512-MULkVLfKGYDFYejP07QOurDLLQpcjk7Fw+7jXS2R2czRQzR56yHRveU5NDJEOviH+hETZKSkIk5c+T23GjFUMg==} - engines: {node: 18 || 20 || >=22} - minimatch@3.1.5: resolution: {integrity: sha512-VgjWUsnnT6n+NUk6eZq77zeFdpW2LWDzP6zFGrCbHXiYNul5Dzqk2HHQ5uFH2DNW5Xbp8+jVzaeNt94ssEEl4w==} @@ -3446,8 +3273,8 @@ packages: resolution: {integrity: sha512-KZxYo1BUkWD2TVFLr0MQoM8vUUigWD3LlD83a/75BqC+4qE0Hb1Vo5v1FgcfaNXvfXzr+5EhQ6ing/CaBijTlw==} engines: {node: '>= 18'} - mlly@1.8.0: - resolution: {integrity: sha512-l8D9ODSRWLe2KHJSifWGwBqpTZXIXTeo8mlKjY+E2HAakaTeNpqAyBZ8GSqLzHgw4XmHmC8whvpjJNMbFZN7/g==} + mlly@1.8.2: + resolution: {integrity: sha512-d+ObxMQFmbt10sretNDytwt85VrbkhhUA/JBGm1MPaWJ65Cl4wOgLaB1NYvJSZ0Ef03MMEU/0xpPMXUIQ29UfA==} module-details-from-path@1.0.4: resolution: {integrity: sha512-EGWKgxALGMgzvxYF1UyGTy0HXX/2vHLkw6+NvDKW2jypWbHpjQuj4UMcqQWXHERJhVGKikolT06G3bcKe4fi7w==} @@ -3456,6 +3283,10 @@ packages: resolution: {integrity: sha512-tzzskb3bG8LvYGFF/mDTpq3jpI6Q9wc3LEmBaghu+DdCssd1FakN7Bc0hVNmEyGq1bq3RgfkCb3cmQLpNPOroA==} engines: {node: '>=4'} + mrmime@2.0.1: + resolution: {integrity: sha512-Y3wQdFg2Va6etvQ5I82yUhGdsKrcYox6p7FfL1LbK2J4V01F9TGlepTIhnK24t7koZibmg82KGglhA1XK5IsLQ==} + engines: {node: '>=10'} + ms@2.1.3: resolution: {integrity: sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==} @@ -3488,18 +3319,14 @@ packages: resolution: {integrity: sha512-tacvGzUY5o2D8CBh2rrwxyNojUsZNU2zjNTzKQrkgGJQTbGAfArVWXSKMBokBeeg6C7OLRGUEyoFlYbfeWQIqw==} engines: {node: '>=20.17'} - nanoid@3.3.18: - resolution: {integrity: sha512-DTg4MJbGMWkfi6VZFdNt2/caMbQy4Ou+Op/hJQvGEWcnVfoA1QA+xzRKAzw9jD6+GVOOeYr/mIcuDSdug6F6+w==} + nanoid@3.3.20: + resolution: {integrity: sha512-uKdg2G3GNCKQn9byYOpxbGqrT2fGO5KRt5J/8b3pok8rT6qxGWF6hxMyJiEYtAf+FVyYuD9hRaDqX5uPFYJ4ZQ==} engines: {node: ^10 || ^12 || ^13.7 || ^14 || >=15.0.1} hasBin: true natural-compare@1.4.0: resolution: {integrity: sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==} - negotiator@1.0.0: - resolution: {integrity: sha512-8Ofs/AUQh8MaEcrlq5xOX0CQ9ypTF5dl78mjlMNfOK08fzpgTHQRQPBxcPlEtIw0yRpws+Zo/3r+5WRby7u3Gg==} - engines: {node: '>= 0.6'} - neo-async@2.6.2: resolution: {integrity: sha512-Yd3UES5mWCSqR+qNT93S3UoYUkqAZ9lLg8a7g9rimsWmYGK8cVToA4/sF3RrshdyV3sAGMXVUmpMYOw+dLpOuw==} @@ -3545,10 +3372,6 @@ packages: resolution: {integrity: sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==} engines: {node: '>=0.10.0'} - object-inspect@1.13.4: - resolution: {integrity: sha512-W67iLl4J2EXEGTbfeHCffrjDfitvLANg0UlX3wFUUSTx92KXRFegMHUVgSqE+wvhAbi4WqjGg9czysTV2Epbew==} - engines: {node: '>= 0.4'} - obug@2.2.1: resolution: {integrity: sha512-XrsrhT5sybtKI6wakr2SPOlGZWWYbUXZ7a0jT8/QOeAPau+1X/bSegNe5YR75oJmEZQbKningirmGOEJCIk61Q==} engines: {node: '>=12.20.0'} @@ -3556,10 +3379,6 @@ packages: ogg-opus-decoder@1.7.5: resolution: {integrity: sha512-08WfRbuBBew1H63PqQ3zj2B/J0Eh9ISfNOTi+Dk6vuXmkBGSx8BiPJGOJ470EYeKezTa7/GhOAwHe3XlXwOZMw==} - on-finished@2.4.1: - resolution: {integrity: sha512-oVlzkg3ENAhCk2zdv7IJwd/QUD4z2RxRwpkcGY8psCVcCYZNq4wYnVWALHM+brtuJjePWiYF/ClmuDr8Ch5+kg==} - engines: {node: '>= 0.8'} - once@1.4.0: resolution: {integrity: sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==} @@ -3567,18 +3386,6 @@ packages: resolution: {integrity: sha512-VXJjc87FScF88uafS3JllDgvAm+c/Slfz06lorj2uAY34rlUu0Nt+v8wreiImcrgAjjIHp1rXpTDlLOGw29WwQ==} engines: {node: '>=18'} - openai@5.11.0: - resolution: {integrity: sha512-+AuTc5pVjlnTuA9zvn8rA/k+1RluPIx9AD4eDcnutv6JNwHHZxIhkFy+tmMKCvmMFDQzfA/r1ujvPWB19DQkYg==} - hasBin: true - peerDependencies: - ws: ^8.18.0 - zod: ^3.23.8 - peerDependenciesMeta: - ws: - optional: true - zod: - optional: true - openai@6.25.0: resolution: {integrity: sha512-mEh6VZ2ds2AGGokWARo18aPISI1OhlgdEIC1ewhkZr8pSIT31dec0ecr9Nhxx0JlybyOgoAT1sWeKtwPZzJyww==} hasBin: true @@ -3591,18 +3398,6 @@ packages: zod: optional: true - openai@6.32.0: - resolution: {integrity: sha512-j3k+BjydAf8yQlcOI7WUQMQTbbF5GEIMAE2iZYCOzwwB3S2pCheaWYp+XZRNAch4jWVc52PMDGRRjutao3lLCg==} - hasBin: true - peerDependencies: - ws: ^8.18.0 - zod: ^3.25 || ^4.0 - peerDependenciesMeta: - ws: - optional: true - zod: - optional: true - optionator@0.9.4: resolution: {integrity: sha512-6IpQ7mKUxRcZNLIObR0hz7lxsapSSIYNZJwXPGeF0mTVqGKFIXj1DQcMoT22S3ROcLyY/rz0PWaWZ9ayWmad9g==} engines: {node: '>= 0.8.0'} @@ -3619,14 +3414,40 @@ packages: oxc-resolver@11.19.1: resolution: {integrity: sha512-qE/CIg/spwrTBFt5aKmwe3ifeDdLfA2NESN30E42X/lII5ClF8V7Wt6WIJhcGZjp0/Q+nQ+9vgxGk//xZNX2hg==} + oxfmt@0.72.0: + resolution: {integrity: sha512-16OQeL0uhPZbfmT9S9WWM0Nv9+BmVi8Ra1uTYaSKj/qSCs6FB30KOhz9JoNYw6thT+K5bVxsk/tSEOu497i34g==} + engines: {node: ^20.19.0 || >=22.12.0} + hasBin: true + peerDependencies: + svelte: ^5.0.0 + vite-plus: '*' + peerDependenciesMeta: + svelte: + optional: true + vite-plus: + optional: true + + oxlint-tsgolint@7.0.2003: + resolution: {integrity: sha512-VnK4zlqgmgq/7ZcjzCk/WpN8kKFsYGcB85Io9qT3wL4K8Un3RKmJkp793crNETieMusPMKOA4a+Mw2wbteI6TQ==} + hasBin: true + + oxlint@1.87.0: + resolution: {integrity: sha512-drxVlaW7Oqq7wSy1CN25znRu00aRq8n57Q2wniJRxCmtYVz07ufCm7EVDPQFZURrTgQIjzKrVEHAD/HcvJXObg==} + engines: {node: ^20.19.0 || >=22.12.0} + hasBin: true + peerDependencies: + oxlint-tsgolint: '>=7.0.2003' + vite-plus: '*' + peerDependenciesMeta: + oxlint-tsgolint: + optional: true + vite-plus: + optional: true + p-filter@2.1.0: resolution: {integrity: sha512-ZBxxZ5sL2HghephhpGAQdoskxplTwr7ICaehZwLIlfL6acuVgZPm8yBNuRAFBGEqtD/hmUeq9eqLg2ys9Xr/yw==} engines: {node: '>=8'} - p-finally@1.0.0: - resolution: {integrity: sha512-LICb2p9CB7FS+0eR1oqWnHhp0FljGLZCWBE9aix0Uye9W8LTQPwMTYVGWQWIw9RdQiDg4+epXQODwIYJtSJaow==} - engines: {node: '>=4'} - p-limit@2.3.0: resolution: {integrity: sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==} engines: {node: '>=6'} @@ -3647,36 +3468,20 @@ packages: resolution: {integrity: sha512-y3b8Kpd8OAN444hxfBbFfj1FY/RjtTd8tzYwhUqNYXx0fXx2iX4maP4Qr6qhIKbQXI02wTLAda4fYUbDagTUFw==} engines: {node: '>=6'} - p-queue@6.6.2: - resolution: {integrity: sha512-RwFpb72c/BhQLEXIZ5K2e+AhgNVmIejGlTgiB9MzZ0e93GRvqZ7uSi0dvRF7/XIXDeNkra2fNHBxTyPDGySpjQ==} - engines: {node: '>=8'} - - p-retry@4.6.2: - resolution: {integrity: sha512-312Id396EbJdvRONlngUx0NydfrIQ5lsYu0znKVUzVvArzEIt08V1qhtyESbGVd1FGX7UKtiFp5uwKZdM8wIuQ==} - engines: {node: '>=8'} - - p-timeout@3.2.0: - resolution: {integrity: sha512-rhIwUycgwwKcP9yTOOFK/AKsAopjjCakVqLHePO3CC6Mir1Z99xT+R63jZxAT5lFZLa2inS5h+ZS2GvR99/FBg==} - engines: {node: '>=8'} - p-try@2.2.0: resolution: {integrity: sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ==} engines: {node: '>=6'} - package-json-from-dist@1.0.1: - resolution: {integrity: sha512-UEZIS3/by4OC8vL3P2dTXRETpebLI2NiI5vIrjaD/5UtrkFX/tNbwjTSRAGC/+7CAo2pIcBaRgWmcBBHcsaCIw==} - package-manager-detector@0.2.11: resolution: {integrity: sha512-BEnLolu+yuz22S56CU1SUKq3XC3PkwD5wv4ikR4MfGvnRVcmzXR9DwSlW2fEamyTPyXHomBJRzgapeuBvRNzJQ==} + package-manager-detector@1.9.0: + resolution: {integrity: sha512-zAMfbta9nPA8AKkA4WdmNAT8R8s6Dk0/xwPsqNnRidMFcLo1YkLuPDTgs2f64XdzS8vbLNnoJ+uUIkgbz7oRHA==} + parent-module@1.0.1: resolution: {integrity: sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==} engines: {node: '>=6'} - parseurl@1.3.3: - resolution: {integrity: sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ==} - engines: {node: '>= 0.8'} - path-exists@4.0.0: resolution: {integrity: sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==} engines: {node: '>=8'} @@ -3689,16 +3494,9 @@ packages: resolution: {integrity: sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==} engines: {node: '>=8'} - path-scurry@1.11.1: - resolution: {integrity: sha512-Xa4Nw17FS9ApQFJ9umLiJS4orGjm7ZzwUrwamcGQuHSzDyth9boKDaycYdDcZDuqYATXw4HFXgaqWTctW/v1HA==} - engines: {node: '>=16 || 14 >=14.18'} - path-to-regexp@6.3.0: resolution: {integrity: sha512-Yhpw4T9C6hPpgPeA28us07OJeqZ5EzQTkbfwuhsUg0c237RomFoETJgmp2sa3F/41gfLE6G5cqcYwznmeEeOlQ==} - path-to-regexp@8.4.0: - resolution: {integrity: sha512-PuseHIvAnz3bjrM2rGJtSgo1zjgxapTLZ7x2pjhzWwlp4SJQgK3f3iZIQwkpEnBaKz6seKBADpM4B4ySkuYypg==} - path-type@4.0.0: resolution: {integrity: sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw==} engines: {node: '>=8'} @@ -3726,20 +3524,16 @@ packages: resolution: {integrity: sha512-uB80kBFb/tfd68bVleG9T5GGsGPjJrLAUpR5PZIrhBnIaRTQRjqdJSsIKkOP6OAIFbj7GOrcudc5pNjZ+geV2g==} engines: {node: '>=6'} - pirates@4.0.6: - resolution: {integrity: sha512-saLsH7WeYYPiD25LDuLRRY/i+6HaPYr6G1OUlN39otzkSTxKnubR9RTxS3/Kk50s1g2JTgFwWQDQyplC5/SHZg==} + pirates@4.0.7: + resolution: {integrity: sha512-TfySrs/5nm8fQJDcBDuUng3VOUKsd7S+zqvbOTiGXHfxX4wK31ard+hoNuvkicM/2YFzlpDgABOevKSsB4G/FA==} engines: {node: '>= 6'} - pkce-challenge@5.0.0: - resolution: {integrity: sha512-ueGLflrrnvwB3xuo/uGob5pd5FN7l0MsLf0Z87o/UQmRtwjvfylfc9MurIxRAWywCYTgrvpXBcqjV4OfCYGCIQ==} - engines: {node: '>=16.20.0'} - pkg-types@1.3.1: resolution: {integrity: sha512-/Jm5M4RvtBFVkKWRu2BLUTNP8/M2a+UwuAX+ae4770q1qVGtfjG+WTCupoZixokjmHiry8uI+dlY8KXYV5HVVQ==} - pluralize@8.0.0: - resolution: {integrity: sha512-Nc3IT5yHzflTfbjgqWcCPpo7DaKy4FnpB0l/zCAW0Tc7jxAiuqSxHasntB3D7887LSrA93kDJ9IXovxJYxyLCA==} - engines: {node: '>=4'} + pngjs@7.0.0: + resolution: {integrity: sha512-LKWqWJRhstyYo9pGvgor/ivk2w94eSjE3RGVuzLGlr3NmD8bf7RcYGze1mNdEHRP6TRP6rMuDHk5t44hnTRyow==} + engines: {node: '>=14.19.0'} postcss-load-config@6.0.1: resolution: {integrity: sha512-oPtTM4oerL+UXmx+93ytZVN82RrlY/wPUV8IeDxFrzIjXOLF1pN+EmKPLbubvKHT2HC20xXsCAH2Z+CKV6Oz/g==} @@ -3759,8 +3553,8 @@ packages: yaml: optional: true - postcss@8.5.26: - resolution: {integrity: sha512-u82N74LFzG8ca+dD8puPnplTXoGH4fTPpVGuIbt36G3qvNlkvfD0lEAZSxaly3KX8TS/L1A1gsCEmvKmBcVbkQ==} + postcss@8.5.29: + resolution: {integrity: sha512-49cGhUbXj8Qenv0iTMxA1cFBzxXoctpC9Ujd77t1WcbJIr6nF/eI7g/8MgxrYldFRuAXvja7xQRwavoW7kgrxQ==} engines: {node: ^10 || ^12 || >=14} prelude-ls@1.2.1: @@ -3772,10 +3566,9 @@ packages: engines: {node: '>=10.13.0'} hasBin: true - prettier@3.8.1: - resolution: {integrity: sha512-UOnG6LftzbdaHZcKoPFtOcCKztrQ57WkHDeRD9t/PTQtmT0NHSeWWepj6pS0z/N7+08BHFDQVUrfmfMRcZwbMg==} - engines: {node: '>=14'} - hasBin: true + pretty-format@27.5.1: + resolution: {integrity: sha512-Qb1gy5OrP5+zDf2Bvnzdl3jsTf1qXVMazbvCoKhtKqVs4/YK4ozX4gKQJJVyNe+cajNPn0KoC0MC3FUmaHWEmQ==} + engines: {node: ^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0} proto3-json-serializer@2.0.2: resolution: {integrity: sha512-SAzp/O4Yh02jGdRc+uIrGoe87dkN/XtwxfZ4ZyafJHymd79ozp5VG5nyZ7ygqPM5+cpLDjjGnYFUkngonyDPOQ==} @@ -3785,40 +3578,23 @@ packages: resolution: {integrity: sha512-/FPD0nUc9jH6rfFjji9IBqOz4pcSE3CsT1m7Ep6Mdb0LxSUMj8hgl6GomOvZzpNpAqqGaXA0P3VSrZLFzIhQrw==} engines: {node: '>=12.0.0'} - proxy-addr@2.0.8: - resolution: {integrity: sha512-5nnx0yGyVUcY6t9RnWcARWtwT9F1D8O9rt08htPvnd49W1IgZtmLkhu9WfMzQj1cFxjHIO6connUNVW5k7AVyQ==} - engines: {node: '>= 0.10'} + publint@0.3.25: + resolution: {integrity: sha512-WMMbn/d7JqRlfXQL/JUI4n5skC8cJ5WVr8/wT5mmiYWkUdENkZr1I5rgs2+dQPVRgoJd+JRYtB46MTsuDylw9g==} + engines: {node: '>=18'} + hasBin: true punycode@2.3.1: resolution: {integrity: sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==} engines: {node: '>=6'} - qs@6.16.0: - resolution: {integrity: sha512-h6fhOIaRrID2CbEY2fqs+7t+UXZo+MLAnU5gRIq85uFtdiUPCdsApMlHhXogKVM4HM2DVbIjGNTTYH2OcmP1vA==} - engines: {node: '>=0.6'} - quansync@0.2.11: resolution: {integrity: sha512-AifT7QEbW9Nri4tAwR5M/uzpBuqfZf+zwaEM/QkzEjj7NBuFD2rBuy0K3dE+8wltbezDV7JMA0WfnCPYRSYbXA==} queue-microtask@1.2.3: resolution: {integrity: sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==} - range-parser@1.2.1: - resolution: {integrity: sha512-Hrgsx+orqoygnmhFbKaHE6c296J+HTAQXoxEF6gNupROmmGJRoyzfG3ccAveqCBrwr/2yxQ5BVd/GTl5agOwSg==} - engines: {node: '>= 0.6'} - - raw-body@3.0.2: - resolution: {integrity: sha512-K5zQjDllxWkf7Z5xJdV0/B0WTNqx6vxG70zJE4N0kBs4LovmEYWJzQGxC9bS9RAKu3bgM40lrd5zoLJ12MQ5BA==} - engines: {node: '>= 0.10'} - - react-dom@19.2.4: - resolution: {integrity: sha512-AXJdLo8kgMbimY95O2aKQqsz2iWi9jMgKJhRBAxECE4IFxfcazB2LmzloIoibJI3C12IlY20+KFaLv+71bUJeQ==} - peerDependencies: - react: ^19.2.4 - - react@19.2.4: - resolution: {integrity: sha512-9nfp2hYpCwOjAN+8TZFGhtWEwgvWHXqESH8qT89AT/lWklpLON22Lc8pEtnpsZz7VmawabSU0gCjnj8aC0euHQ==} - engines: {node: '>=0.10.0'} + react-is@17.0.2: + resolution: {integrity: sha512-w2GsyukL62IJnlaff/nRegPQR94C/XXamvMWmSHRJ4y7Ts/4ocGRmTHvOs8PSE6pB3dWOrD/nueuU5sduBsQ4w==} read-yaml-file@1.1.0: resolution: {integrity: sha512-VIMnQi/Z4HT2Fxuwg5KrY174U1VdUIASQVWXXyqtNRtxSr9IYkn1rsI6Tb6HsrHCmB7gVpNwX6JxPTHcH6IoTA==} @@ -3851,10 +3627,6 @@ packages: resolution: {integrity: sha512-oMA2dcrw6u0YfxJQXm342bFKX/E4sG9rbTzO9ptUcR/e8A33cHuvStiYOwH7fszkZlZ1z/ta9AAoPk2F4qIOHA==} engines: {node: '>=18'} - retry@0.13.1: - resolution: {integrity: sha512-XQBQ3I8W1Cge0Seh+6gjj03LbmRFWuoszgK9ooCpwYIrhhoO80pfq4cUkU5DkknwfOfFteRwlZ56PYOGYyFWdg==} - engines: {node: '>= 4'} - rettime@0.11.8: resolution: {integrity: sha512-0fERGXktJTyJ+h8fBEiPxHPEFOu0h15JY7JtwrOVqR5K+vb99ho6IyOo7ekLS3h4sJCzIDy4VWKIbZUfe9njmg==} @@ -3865,35 +3637,24 @@ packages: rfdc@1.4.1: resolution: {integrity: sha512-q1b3N5QkRUWUl7iyylaaj3kOpIT0N2i9MqIEQXP73GVsN9cw3fdx8X63cEmWhJGi2PPCF23Ijp7ktmd39rawIA==} - rolldown@1.1.5: - resolution: {integrity: sha512-t9z29cJjXf/vxQ8dyhCSpt6H6aSwHTk8cT5I3iy6SMXuFpk5mB6PL6XfC8PCwrPTx93udwKUm9HRteAlTGBLiA==} - engines: {node: ^20.19.0 || >=22.12.0} - hasBin: true - rollup@4.60.3: resolution: {integrity: sha512-pAQK9HalE84QSm4Po3EmWIZPd3FnjkShVkiMlz1iligWYkWQ7wHYd1PF/T7QZ5TVSD6uSTon5gBVMSM4JfBV+A==} engines: {node: '>=18.0.0', npm: '>=8.0.0'} hasBin: true - router@2.2.0: - resolution: {integrity: sha512-nLTrUKm2UyiL7rlhapu/Zl45FwNgkZGaCpZbIHajDYgwlJCOzLSk+cIPAnsEqV955GjILJnKbdQC1nVPz+gAYQ==} - engines: {node: '>= 18'} - run-parallel@1.2.0: resolution: {integrity: sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA==} rxjs@7.8.2: resolution: {integrity: sha512-dhKf903U/PQZY6boNNtAGdWbG85WAbjT/1xYoZIC7FAY0yWapOBQVsVrDl58W86//e1VpMNBtRV4MaXfdMySFA==} - safe-buffer@5.2.1: - resolution: {integrity: sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==} + sade@1.8.1: + resolution: {integrity: sha512-xal3CZX1Xlo/k4ApwCFrHVACi9fBqJ7V+mwhBsuf/1IOKbBy098Fex+Wa/5QMubw09pSZ/u8EY8PWgevJsXp1A==} + engines: {node: '>=6'} safer-buffer@2.1.2: resolution: {integrity: sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==} - scheduler@0.27.0: - resolution: {integrity: sha512-eNv+WrVbKu1f3vbYJT/xtiF5syA5HPIMtf9IgY/nKg0sWqzAUEvqY/xm7OcZc/qafLx/iO9FgOmeSAp4v5ti/Q==} - schema-utils@4.3.3: resolution: {integrity: sha512-eflK8wEtyOE6+hsaRVPxvUKYCpRgzLqDTb8krvAsRIwOGlHoSgYLgBXoubGgLd2fT41/OUYdb48v4k4WWHQurA==} engines: {node: '>= 10.13.0'} @@ -3906,20 +3667,9 @@ packages: engines: {node: '>=10'} hasBin: true - send@1.2.0: - resolution: {integrity: sha512-uaW0WwXKpL9blXE2o0bRhoL2EGXIrZxQ2ZQ4mgcfoBxdFmQold+qWsD2jLrfZ0trjKL6vOw0j//eAwcALFjKSw==} - engines: {node: '>= 18'} - - serve-static@2.2.0: - resolution: {integrity: sha512-61g9pCh0Vnh7IutZjtLGGpTA355+OPn2TyDv/6ivP2h/AdAVX9azsoxmg2/M6nZeQZNYBEwIcsne1mJd9oQItQ==} - engines: {node: '>= 18'} - set-cookie-parser@3.1.0: resolution: {integrity: sha512-kjnC1DXBHcxaOaOXBHBeRtltsDG2nUiUni+jP92M9gYdW12rsmx92UsfpH7o5tDRs7I1ZZPSQJQGv3UaRfCiuw==} - setprototypeof@1.2.0: - resolution: {integrity: sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw==} - shebang-command@2.0.0: resolution: {integrity: sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==} engines: {node: '>=8'} @@ -3931,25 +3681,6 @@ packages: shiki@0.14.7: resolution: {integrity: sha512-dNPAPrxSc87ua2sKJ3H5dQ/6ZaY8RNnaAqK+t0eG7p0Soi2ydiqbGOTaZCqaYvA/uZYfS1LJnemt3Q+mSfcPCg==} - side-channel-list@1.0.1: - resolution: {integrity: sha512-mjn/0bi/oUURjc5Xl7IaWi/OJJJumuoJFQJfDDyO46+hBWsfaVM65TBHq2eoZBhzl9EchxOijpkbRC8SVBQU0w==} - engines: {node: '>= 0.4'} - - side-channel-map@1.0.1: - resolution: {integrity: sha512-VCjCNfgMsby3tTdo02nbjtM/ewra6jPHmpThenkTYh8pG9ucZ/1P8So4u4FGBek/BjpOVsDCMoLA/iuBKIFXRA==} - engines: {node: '>= 0.4'} - - side-channel-weakmap@1.0.2: - resolution: {integrity: sha512-WPS/HvHQTYnHisLo9McqBHOJk2FkHO/tlpvldyrnem4aeQp4hai3gythswg6p01oSoTl58rcpiFAjF2br2Ak2A==} - engines: {node: '>= 0.4'} - - side-channel@1.1.1: - resolution: {integrity: sha512-6x6dK6zJdpTzF4sQeNYxwtvBzf6Eg4GtlesS94HOvTudUeyK2WXAaIfmDgsyslYrRBeFIlsi54AYsFGUuhmvrQ==} - engines: {node: '>= 0.4'} - - siginfo@2.0.0: - resolution: {integrity: sha512-ybx0WO1/8bSBLEWXZvEd7gMW3Sn3JFlW3TvX1nREbDLRNQNaeNN8WK0meBwPdAaOI7TtRRRJn/Es1zhrrCHu7g==} - signal-exit@4.1.0: resolution: {integrity: sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw==} engines: {node: '>=14'} @@ -3957,6 +3688,10 @@ packages: simple-yenc@1.0.4: resolution: {integrity: sha512-5gvxpSd79e9a3V4QDYUqnqxeD4HGlhCakVpb6gMnDD7lexJggSBJRBO5h52y/iJrdXRilX9UCuDaIJhSWm5OWw==} + sirv@3.0.2: + resolution: {integrity: sha512-2wcC/oGxHis/BoHkkPwldgiPSYcpZK3JU28WoMVv55yHJgcZ8rlXvuG9iZggz+sU1d4bRgIGASwyWqjxu3FM0g==} + engines: {node: '>=18'} + sisteransi@1.0.5: resolution: {integrity: sha512-bLGGlR1QxBcynn2d5YmDX4MGjlZvy2MRBDRNHLJ8VI6l6+9FUiyTFNJ0IveOSP0bcXgVDPRcfGqA0pjaqUpfVg==} @@ -3999,9 +3734,6 @@ packages: sprintf-js@1.0.3: resolution: {integrity: sha512-D9cPgkvLlV3t3IzL0D0YLvGA9Ahk4PcvVwUbN0dSGr1aP0Nrt4AEnTUbuGvquEC0mA64Gqt1fzirlRs5ibXx8g==} - stackback@0.0.2: - resolution: {integrity: sha512-1XMJE5fQo1jGH6Y/7ebnwPOBEkIEnT4QF32d5R1+VXdXveM0IBMJt8zfaxX1P3QhVwrYe+576+jkANtSS2mBbw==} - statuses@2.0.2: resolution: {integrity: sha512-DvEy55V3DB7uknRo+4iOGT5fP1slR8wQohVdknigZPMpMstaKJQWhwiYBACJE3Ul2pTnATihhBYnRhZQHGBiRw==} engines: {node: '>= 0.8'} @@ -4020,10 +3752,6 @@ packages: resolution: {integrity: sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==} engines: {node: '>=8'} - string-width@5.1.2: - resolution: {integrity: sha512-HnLOCR3vjcY8beoNLtcjZ5/nxn2afmME6lhrDrebokqMap+XbeW8n9TXpPDOqdGK5qcI3oT0GKTW6wC7EMiVqA==} - engines: {node: '>=12'} - string-width@7.2.0: resolution: {integrity: sha512-tsaTIkKW9b4N+AEj+SVA+WhJzV7/zMhcSu78mLKWSk7cXMOSHsBKFWUs0fWwq8QyK3MgJBQRX6Gbi4kYbdvGkQ==} engines: {node: '>=18'} @@ -4052,8 +3780,8 @@ packages: resolution: {integrity: sha512-1tB5mhVo7U+ETBKNf92xT4hrQa3pm0MZ0PQvuDnWgAAGHDsfp4lPSpiS6psrSiet87wyGPh9ft6wmhOMQ0hDiw==} engines: {node: '>=14.16'} - sucrase@3.35.0: - resolution: {integrity: sha512-8EbVDiu9iN/nESwxeSxDKe0dunta1GOlHufmSSXxMD2z2/tMZpDMpvXQGsc+ajGo8y2uYUmixaSRUc/QPoQ0GA==} + sucrase@3.35.1: + resolution: {integrity: sha512-DhuTmvZWux4H1UOnWMB3sk0sbaCVOoQZjv8u1rDoTV0HTdGem9hkAZtl4JZy8P2z4Bg0nT+YMeOFyVr4zcG5Tw==} engines: {node: '>=16 || 14 >=14.17'} hasBin: true @@ -4085,11 +3813,7 @@ packages: term-size@2.2.1: resolution: {integrity: sha512-wK0Ri4fOGjv/XPy8SBHZChl8CM7uMc5VML7SqiQ0zG7+J5Vr+RMQDoHa2CNT6KHUnTGIXH34UDMkPzAUyapBZg==} - engines: {node: '>=8'} - - termi-link@1.1.0: - resolution: {integrity: sha512-2qSN6TnomHgVLtk+htSWbaYs4Rd2MH/RU7VpHTy6MBstyNyWbM4yKd1DCYpE3fDg8dmGWojXCngNi/MHCzGuAA==} - engines: {node: '>=12'} + engines: {node: '>=8'} terser-webpack-plugin@5.6.0: resolution: {integrity: sha512-Eum+5ajkaOhf5KbM26osvv21kLD7BaGqQ1UA4Ami4arYwylmGUQTgHFpHDdmJod1q4QXa66p0to/FBKID+J1vA==} @@ -4152,13 +3876,14 @@ packages: peerDependencies: tslib: ^2 - tinybench@2.9.0: - resolution: {integrity: sha512-0+DUvqWMValLmha6lr4kD8iAMK1HzV0/aKnCtWb9v9641TnP/MFb7Pc2bxoxQjTXAErryXVgUOfv2YqNllqGeg==} - tinybench@4.0.1: resolution: {integrity: sha512-Nb1srn7dvzkVx0J5h1vq8f48e3TIcbrS7e/UfAI/cDSef/n8yLh4zsAEsFkfpw6auTY+ZaspEvam/xs8nMnotQ==} engines: {node: '>=18.0.0'} + tinybench@6.2.2: + resolution: {integrity: sha512-nIZ2N2dFpjBsg2hgitKu7mA+FvsIJuGJOwysRngmVRRmWgR/UyPjwJj8c/fT/dhm8EkHnF5EOogRf+0Z2qrimA==} + engines: {node: '>=20.0.0'} + tinyexec@0.3.2: resolution: {integrity: sha512-KQQR9yN7R5+OSwaK0XQoj22pwHoTlgYqmUscPYoknOoWCWfj/5/ABTMRi69FrKU5ffPVh5QcFikpWJI/P1ocHA==} @@ -4170,6 +3895,10 @@ packages: resolution: {integrity: sha512-wXR/dYpcqKmfWpEdZjiKJOwCNFndD0DMnrW/cYjVGttEkBfVgcLFHoNrlj47mjOVic9yyNu65alsgF4NQyTa2g==} engines: {node: '>=12.0.0'} + tinypool@2.2.0: + resolution: {integrity: sha512-jBrmx4lYmaC9k/mgPbylxs7kBUxHtD8256up+HjLaDFfXScKJQyil+SWXSvhAtT7XHo+yTVlpB81PGmHP8oLSQ==} + engines: {node: ^20.0.0 || >=22.0.0} + tinyrainbow@3.1.1: resolution: {integrity: sha512-yau8yJdTt989Mm0Bd/236QnzEiPf2xLLTqUZRUJOo/3CB078LSwzei343DgtJVmfJKJE3TMINY1u42SQsP6mXw==} engines: {node: '>=14.0.0'} @@ -4185,9 +3914,9 @@ packages: resolution: {integrity: sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==} engines: {node: '>=8.0'} - toidentifier@1.0.1: - resolution: {integrity: sha512-o5sSPKEkg/DIQNmH43V0/uerLrpzVedkUh8tGNvaeXpfpuwjKenlSox/2O/BTlZUtEe+JG7s5YhEz608PlAHRA==} - engines: {node: '>=0.6'} + totalist@3.0.1: + resolution: {integrity: sha512-sf4i37nQ2LBx4m3wB74y+ubopq6W/dIzXg0FDGjsYnZHVa1Da8FH853wlL2gtUhg+xJXjfk3kUZS3BRoQeoQBQ==} + engines: {node: '>=6'} tough-cookie@6.0.1: resolution: {integrity: sha512-LktZQb3IeoUWB9lqR5EWTHgW/VTITCXg4D21M+lvybRVdylLrRMnqaIONLVb5mav8vM19m44HIcGq4qASeu2Qw==} @@ -4206,15 +3935,6 @@ packages: resolution: {integrity: sha512-L0Orpi8qGpRG//Nd+H90vFB+3iHnue1zSSGmNOOCh1GLJ7rUKVwV2HvijphGQS2UmhUZewS9VgvxYIdgr+fG1A==} hasBin: true - ts-algebra@2.0.0: - resolution: {integrity: sha512-FPAhNPFMrkwz76P7cdjdmiShwMynZYN6SgOujD1urY4oNm80Ou9oMdmbR45LotcKOXoy7wSmHkRFE6Mxbrhefw==} - - ts-api-utils@2.1.0: - resolution: {integrity: sha512-CUgTZL1irw8u29bzrOD/nH85jqyc74D6SshFgujOIA7osm2Rz7dYH77agkx7H4FBNxDq7Cjf+IjaX/8zwFW+ZQ==} - engines: {node: '>=18.12'} - peerDependencies: - typescript: '>=4.8.4' - ts-declaration-location@1.0.7: resolution: {integrity: sha512-EDyGAwH1gO0Ausm9gV6T2nUvBgXT5kGoCMJPllOaooZ+4VvJiKBdZE7wK18N1deEowhcUptS+5GXZK8U/fvpwA==} peerDependencies: @@ -4223,17 +3943,6 @@ packages: ts-interface-checker@0.1.13: resolution: {integrity: sha512-Y/arvbn+rrz3JCKl9C4kVNfTfSm2/mEp5FSz5EsZSANGPSlQrpRI5M4PKF+mJnE52jOO90PnPSc3Ur3bTQw0gA==} - tsconfck@3.1.1: - resolution: {integrity: sha512-00eoI6WY57SvZEVjm13stEVE90VkEdJAFGgpFLTsZbJyW/LwFQ7uQxJHWpZ2hzSWgCPKc9AnBnNP+0X7o3hAmQ==} - engines: {node: ^18 || >=20} - deprecated: unmaintained - hasBin: true - peerDependencies: - typescript: ^5.0.0 - peerDependenciesMeta: - typescript: - optional: true - tslib@2.8.1: resolution: {integrity: sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==} @@ -4261,10 +3970,6 @@ packages: engines: {node: '>=18.0.0'} hasBin: true - turbo@2.9.14: - resolution: {integrity: sha512-BQqXRr4UoWI3UPFrtznCLykYHxwxWh53iCB57x092jPMjIlW1wnm3N895g5irpiXmnxUhREBB0n6+y8BHhs4nw==} - hasBin: true - type-check@0.4.0: resolution: {integrity: sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew==} engines: {node: '>= 0.8.0'} @@ -4273,10 +3978,6 @@ packages: resolution: {integrity: sha512-8ZiHFm91orbSAe2PSAiSVBVko18pbhbiB3U9GglSzF/zCGkR+rxpHx6sEMCUm4kxY4LjDIUGgCfUMtwfZfjfUA==} engines: {node: '>=20'} - type-is@2.1.0: - resolution: {integrity: sha512-faYHw0anBbc/kWF3zFTEnxSFOAGUX9GFbOBthvDdLsIlEoWOFOtS0zgCiQYwIskL9iGXZL3kAXD8OoZ4GmMATA==} - engines: {node: '>= 18'} - typedoc-plugin-markdown@3.17.1: resolution: {integrity: sha512-QzdU3fj0Kzw2XSdoL15ExLASt2WPqD7FbLeaqwT70+XjKyTshBnUlQA5nNREO1C2P8Uen0CDjsBLMsCQ+zd0lw==} peerDependencies: @@ -4299,13 +4000,18 @@ packages: engines: {node: '>=14.17'} hasBin: true + typescript@5.6.1-rc: + resolution: {integrity: sha512-E3b2+1zEFu84jB0YQi9BORDjz9+jGbwwy1Zi3G0LUNw7a7cePUrHMRNy8aPh53nXpkFGVHSxIZo5vKTfYaFiBQ==} + engines: {node: '>=14.17'} + hasBin: true + typescript@5.9.3: resolution: {integrity: sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==} engines: {node: '>=14.17'} hasBin: true - ufo@1.6.1: - resolution: {integrity: sha512-9a4/uxlTWJ4+a5i0ooc1rU7C7YOw3wT+UGqdeNNHWnOF9qcMBgLRS+4IYUqbczewFx4mLEig6gawh7X6mFlEkA==} + ufo@1.6.4: + resolution: {integrity: sha512-JFNbkD1Svwe0KvGi8GOeLcP4kAWQ609twvCdcHxq1oSL8svv39ZuSvajcD8B+5D0eL4+s1Is2D/O6KN3qcTeRA==} uglify-js@3.19.3: resolution: {integrity: sha512-v3Xu+yuwBXisp6QYTcH4UbH+xYJXqnq2m/LtQVWKWzYc1iehYnLixoQDN9FH6/j9/oybfd6W9Ghwkl8+UMKTKQ==} @@ -4329,10 +4035,6 @@ packages: resolution: {integrity: sha512-rBJeI5CXAlmy1pV+617WB9J63U6XcazHHF2f2dbJix4XzpUF0RS3Zbj0FGIOCAva5P/d/GBOYaACQ1w+0azUkg==} engines: {node: '>= 4.0.0'} - unpipe@1.0.0: - resolution: {integrity: sha512-pjy2bYhSsufwWlKwPc+l3cN7+wuJlK6uz0YdJEOlQDbl6jo/YlPi4mb8agUkVC8BF7V8NuzeyPNqRksA3hztKQ==} - engines: {node: '>= 0.8'} - unplugin@2.3.11: resolution: {integrity: sha512-5uKD0nqiYVzlmCRs01Fhs2BdkEgBS3SAVP6ndrBsuK42iC2+JHyxM05Rm9G8+5mkmRtzMZGY8Ct5+mliZxU/Ww==} engines: {node: '>=18.12.0'} @@ -4353,78 +4055,43 @@ packages: resolution: {integrity: sha512-vIYxrBCC/N/K+Js3qSN88go7kIfNPssr/hHCesKCQNAjmgvYS2oqr69kIufEG+O4+PfezOH4EbIeHCfFov8ZgQ==} hasBin: true - vary@1.1.2: - resolution: {integrity: sha512-BNGbWLfd0eUPabhkXUVm0j8uuvREyTh5ovRa/dyow/BqAbZJyC+5fU+IzQOzmAKzYqYRAISoRhdQr3eIZ/PXqg==} - engines: {node: '>= 0.8'} - - vite-tsconfig-paths@4.3.2: - resolution: {integrity: sha512-0Vd/a6po6Q+86rPlntHye7F31zA2URZMbH8M3saAZ/xR9QoGN/L21bxEGfXdWmFdNkqPpRdxFT7nmNe12e9/uA==} - peerDependencies: - vite: '*' - peerDependenciesMeta: - vite: - optional: true + validate-npm-package-name@5.0.1: + resolution: {integrity: sha512-OljLrQ9SQdOUqTaQxqL5dEfZWrXExyyWsozYlAWFawPVNuD83igl7uJD2RTkNMbniIYgt8l81eCJGIdQF7avLQ==} + engines: {node: ^14.17.0 || ^16.13.0 || >=18.0.0} - vite@8.1.5: - resolution: {integrity: sha512-7ULLwsCdYx/nRyrpiEwvqb5TFHrMVZyBt+rg/OAXT7rgj/z+DtTDyKFeLAdDkubDVDKD8jOsndmy7m55XcfUsw==} - engines: {node: ^20.19.0 || >=22.12.0} + vite-plus@1.1.0: + resolution: {integrity: sha512-khkRlY70qqbBV0QmF9QKXSrhxIRYftb1H05AJMowMoq7iouchmAoXYeReZ8pKKMOadqrWVxFa3S9BA2m7w59iA==} + engines: {node: ^22.18.0 || ^24.11.0 || >=26.0.0} hasBin: true peerDependencies: - '@types/node': ^20.19.0 || >=22.12.0 - '@vitejs/devtools': ^0.3.0 - esbuild: 0.28.1 - jiti: '>=1.21.0' - less: ^4.0.0 - sass: ^1.70.0 - sass-embedded: ^1.70.0 - stylus: '>=0.54.8' - sugarss: ^5.0.0 - terser: ^5.16.0 - tsx: ^4.8.1 - yaml: ^2.4.2 + '@vitest/browser-playwright': 5.0.3 + '@vitest/browser-webdriverio': ^5.0.0-beta.5 || >=5.0.0 + playwright: '*' peerDependenciesMeta: - '@types/node': - optional: true - '@vitejs/devtools': - optional: true - esbuild: - optional: true - jiti: - optional: true - less: - optional: true - sass: - optional: true - sass-embedded: - optional: true - stylus: - optional: true - sugarss: - optional: true - terser: + '@vitest/browser-playwright': optional: true - tsx: + '@vitest/browser-webdriverio': optional: true - yaml: + playwright: optional: true - vitest@4.1.11: - resolution: {integrity: sha512-fhACrNXUidIbGSBr5FlbuBkO7VWC1ZyLl0DO4CU2DrQoAPxX84Ysxs+HeGQpii5lZWV1Q4gBZTTu49mF+A6Edw==} - engines: {node: ^20.0.0 || ^22.0.0 || >=24.0.0} + vitest@5.0.3: + resolution: {integrity: sha512-xMw97S3rjdtj5dkVat7jCsqWBpvchs3RlpQctUqwJD0KkERk40vz2fJ77lDwW/Vzh/pk18eItYAzkodhSes3jQ==} + engines: {node: ^22.12.0 || ^24.0.0 || >=26.0.0} hasBin: true peerDependencies: '@edge-runtime/vm': '*' '@opentelemetry/api': ^1.9.0 - '@types/node': ^20.0.0 || ^22.0.0 || >=24.0.0 - '@vitest/browser-playwright': 4.1.11 - '@vitest/browser-preview': 4.1.11 - '@vitest/browser-webdriverio': 4.1.11 - '@vitest/coverage-istanbul': 4.1.11 - '@vitest/coverage-v8': 4.1.11 - '@vitest/ui': 4.1.11 + '@types/node': ^22.0.0 || >=24.0.0 + '@vitest/browser-playwright': 5.0.3 + '@vitest/browser-preview': 5.0.3 + '@vitest/browser-webdriverio': ^5.0.0-beta.5 || >=5.0.0 + '@vitest/coverage-istanbul': 5.0.3 + '@vitest/coverage-v8': 5.0.3 + '@vitest/ui': 5.0.3 happy-dom: '*' jsdom: '*' - vite: ^6.0.0 || ^7.0.0 || ^8.0.0 + vite: ^6.4.0 || ^7.0.0 || ^8.0.0 peerDependenciesMeta: '@edge-runtime/vm': optional: true @@ -4491,9 +4158,9 @@ packages: engines: {node: '>= 8'} hasBin: true - why-is-node-running@2.3.0: - resolution: {integrity: sha512-hUrmaWBdVDcxvYqnyh09zunKzROWjbZTiNy8dBEjkS7ehEDQibXJ7XvlmtbwuTclUiIyN+CyXQD4Vmko8fNm8w==} - engines: {node: '>=8'} + why-is-node-running@3.2.1: + resolution: {integrity: sha512-Tb2FUhB4vUsGQlfSquQLYkApkuPAFQXGFzxWKHHumVz2dK+X1RUm/HnID4+TfIGYJ1kTcwOaCk/buYCEJr6YjQ==} + engines: {node: '>=20.11'} hasBin: true word-wrap@1.2.5: @@ -4507,10 +4174,6 @@ packages: resolution: {integrity: sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==} engines: {node: '>=10'} - wrap-ansi@8.1.0: - resolution: {integrity: sha512-si7QWI6zUMq56bESFvagtmzMdGOtoxfR+Sez11Mobfc7tm+VkUckk9bW2UeffTGVUbOksxmSw0AA2gs8g71NCQ==} - engines: {node: '>=12'} - wrap-ansi@9.0.2: resolution: {integrity: sha512-42AtmgqjV+X1VpdOfyTGOYRi0/zsoLqtXQckTmqTeybT+BDIbM/Guxo7x3pE2vtpr1ok6xRqM9OpBe+Jyoqyww==} engines: {node: '>=18'} @@ -4567,14 +4230,20 @@ packages: resolution: {integrity: sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==} engines: {node: '>=10'} + yuku-ast@0.9.5: + resolution: {integrity: sha512-Q8qW8WwQnN5Cm0ZZivdRIfv0sRLTjUq0YumXJkw8CYN1aCdICH9rk4C47/4n6kA5EqDtlj+F608twoTSV2MwdQ==} + + yuku-codegen@0.9.5: + resolution: {integrity: sha512-zGUVyDpSK4b6c9B0yyxi2P0wujn1XZTbG9dCb7d6gyAoP63EMgyLzUwPUvwxPG5jgYqhlI7w+S3oCyapqGr4PA==} + + yuku-parser@0.9.5: + resolution: {integrity: sha512-IBnAdNVMswWbJcM7woPluOudectJzFjJ1N8jVRxP9Uq4CIv5hZdBIcdmtRbFDYF/Ph2j2gVup4YJ4N9mh0jYFA==} + zod-to-json-schema@3.25.1: resolution: {integrity: sha512-pM/SU9d3YAggzi6MtR4h7ruuQlqKtad8e9S0fmxcMi+ueAK5Korys/aWcV9LIIHTVbj01NdzxcnXSN+O74ZIVA==} peerDependencies: zod: ^3.25 || ^4 - zod@3.25.67: - resolution: {integrity: sha512-idA2YXwpCdqUSKRCACDE6ItZD9TZzy3OZMtpfLoh6oPR47lipysRrJfjzMqFxQ3uJuUPyUeWe1r9vLH33xO/Qw==} - zod@3.25.76: resolution: {integrity: sha512-gzUt/qt81nXsFGKIFcC3YnfEAx5NkunCfnDlvuBSSFS02bcXu4Lmea0AFIUwbLWxWPx3d9p8S5QoaujKcNQxcQ==} @@ -4606,41 +4275,40 @@ snapshots: dependencies: json-schema: 0.4.0 + '@andrewbranch/untar.js@1.0.4': + optional: true + '@anthropic-ai/sdk@0.60.0': {} - '@anthropic-ai/sdk@0.71.2(zod@3.25.76)': + '@arethetypeswrong/core@0.18.5': dependencies: - json-schema-to-ts: 3.1.1 - optionalDependencies: - zod: 3.25.76 - - '@audio/encode-opus@1.3.1': {} - - '@babel/runtime@7.28.4': {} - - '@braintrust/bt-darwin-arm64@0.12.0': + '@andrewbranch/untar.js': 1.0.4 + '@loaderkit/resolve': 1.0.6 + cjs-module-lexer: 1.4.3 + fflate: 0.8.3 + lru-cache: 11.5.3 + semver: 7.8.0 + typescript: 5.6.1-rc + validate-npm-package-name: 5.0.1 optional: true - '@braintrust/bt-darwin-x64@0.12.0': - optional: true + '@audio/encode-opus@1.3.1': {} - '@braintrust/bt-linux-arm64@0.12.0': - optional: true + '@babel/code-frame@7.29.7': + dependencies: + '@babel/helper-validator-identifier': 7.29.7 + js-tokens: 4.0.0 + picocolors: 1.1.1 - '@braintrust/bt-linux-x64-musl@0.12.0': - optional: true + '@babel/helper-validator-identifier@7.29.7': {} - '@braintrust/bt-linux-x64@0.12.0': - optional: true + '@babel/runtime@7.28.4': {} - '@braintrust/bt-win32-arm64@0.12.0': - optional: true + '@blazediff/core@1.10.0': {} - '@braintrust/bt-win32-x64@0.12.0': + '@braidai/lang@1.1.2': optional: true - '@cfworker/json-schema@4.0.3': {} - '@changesets/apply-release-plan@7.1.0': dependencies: '@changesets/config': 3.1.3 @@ -4803,9 +4471,6 @@ snapshots: fast-wrap-ansi: 0.2.0 sisteransi: 1.0.5 - '@colors/colors@1.5.0': - optional: true - '@deepseek-ai/cordis@4.0.1': dependencies: '@deepseek-ai/cosmokit': 1.8.3 @@ -5069,11 +4734,6 @@ snapshots: protobufjs: 7.6.5 yargs: 17.7.2 - '@hono/node-server@1.19.17(hono@4.13.13)': - dependencies: - hono: 4.13.13 - optional: true - '@humanfs/core@0.19.2': dependencies: '@humanfs/types': 0.15.0 @@ -5090,7 +4750,8 @@ snapshots: '@humanwhocodes/retry@0.4.3': {} - '@inquirer/ansi@2.0.5': {} + '@inquirer/ansi@2.0.5': + optional: true '@inquirer/confirm@6.0.12(@types/node@20.19.40)': dependencies: @@ -5098,6 +4759,7 @@ snapshots: '@inquirer/type': 4.0.5(@types/node@20.19.40) optionalDependencies: '@types/node': 20.19.40 + optional: true '@inquirer/confirm@6.0.12(@types/node@22.19.1)': dependencies: @@ -5107,6 +4769,14 @@ snapshots: '@types/node': 22.19.1 optional: true + '@inquirer/confirm@6.0.12(@types/node@24.12.4)': + dependencies: + '@inquirer/core': 11.1.9(@types/node@24.12.4) + '@inquirer/type': 4.0.5(@types/node@24.12.4) + optionalDependencies: + '@types/node': 24.12.4 + optional: true + '@inquirer/core@11.1.9(@types/node@20.19.40)': dependencies: '@inquirer/ansi': 2.0.5 @@ -5118,6 +4788,7 @@ snapshots: signal-exit: 4.1.0 optionalDependencies: '@types/node': 20.19.40 + optional: true '@inquirer/core@11.1.9(@types/node@22.19.1)': dependencies: @@ -5132,6 +4803,19 @@ snapshots: '@types/node': 22.19.1 optional: true + '@inquirer/core@11.1.9(@types/node@24.12.4)': + dependencies: + '@inquirer/ansi': 2.0.5 + '@inquirer/figures': 2.0.5 + '@inquirer/type': 4.0.5(@types/node@24.12.4) + cli-width: 4.1.0 + fast-wrap-ansi: 0.2.0 + mute-stream: 3.0.0 + signal-exit: 4.1.0 + optionalDependencies: + '@types/node': 24.12.4 + optional: true + '@inquirer/external-editor@1.0.3(@types/node@24.12.4)': dependencies: chardet: 2.1.1 @@ -5139,25 +4823,23 @@ snapshots: optionalDependencies: '@types/node': 24.12.4 - '@inquirer/figures@2.0.5': {} + '@inquirer/figures@2.0.5': + optional: true '@inquirer/type@4.0.5(@types/node@20.19.40)': optionalDependencies: '@types/node': 20.19.40 + optional: true '@inquirer/type@4.0.5(@types/node@22.19.1)': optionalDependencies: '@types/node': 22.19.1 optional: true - '@isaacs/cliui@8.0.2': - dependencies: - string-width: 5.1.2 - string-width-cjs: string-width@4.2.3 - strip-ansi: 7.1.2 - strip-ansi-cjs: strip-ansi@6.0.1 - wrap-ansi: 8.1.0 - wrap-ansi-cjs: wrap-ansi@7.0.0 + '@inquirer/type@4.0.5(@types/node@24.12.4)': + optionalDependencies: + '@types/node': 24.12.4 + optional: true '@isaacs/fs-minipass@4.0.1': dependencies: @@ -5225,67 +4907,10 @@ snapshots: '@jsonjoy.com/codegen': 1.0.0(tslib@2.8.1) tslib: 2.8.1 - '@langchain/anthropic@1.3.5(@langchain/core@1.1.10(@opentelemetry/api@1.9.0)(@opentelemetry/sdk-trace-base@2.6.1(@opentelemetry/api@1.9.0))(openai@6.32.0(ws@8.21.3)(zod@3.25.76))(ws@8.21.3))': - dependencies: - '@anthropic-ai/sdk': 0.71.2(zod@3.25.76) - '@langchain/core': 1.1.10(@opentelemetry/api@1.9.0)(@opentelemetry/sdk-trace-base@2.6.1(@opentelemetry/api@1.9.0))(openai@6.32.0(ws@8.21.3)(zod@3.25.76))(ws@8.21.3) - zod: 3.25.76 - - '@langchain/core@1.1.10(@opentelemetry/api@1.9.0)(@opentelemetry/sdk-trace-base@2.6.1(@opentelemetry/api@1.9.0))(openai@6.32.0(ws@8.21.3)(zod@3.25.76))(ws@8.21.3)': - dependencies: - '@cfworker/json-schema': 4.0.3 - ansi-styles: 5.2.0 - camelcase: 6.3.0 - decamelize: 1.2.0 - js-tiktoken: 1.0.21 - langsmith: 0.7.1(@opentelemetry/api@1.9.0)(@opentelemetry/sdk-trace-base@2.6.1(@opentelemetry/api@1.9.0))(openai@6.32.0(ws@8.21.3)(zod@3.25.76))(ws@8.21.3) - mustache: 4.2.0 - p-queue: 6.6.2 - uuid: 11.1.1 - zod: 3.25.76 - transitivePeerDependencies: - - '@opentelemetry/api' - - '@opentelemetry/exporter-trace-otlp-proto' - - '@opentelemetry/sdk-trace-base' - - openai - - ws - - '@langchain/langgraph-checkpoint@1.0.0(@langchain/core@1.1.10(@opentelemetry/api@1.9.0)(@opentelemetry/sdk-trace-base@2.6.1(@opentelemetry/api@1.9.0))(openai@6.32.0(ws@8.21.3)(zod@3.25.76))(ws@8.21.3))': - dependencies: - '@langchain/core': 1.1.10(@opentelemetry/api@1.9.0)(@opentelemetry/sdk-trace-base@2.6.1(@opentelemetry/api@1.9.0))(openai@6.32.0(ws@8.21.3)(zod@3.25.76))(ws@8.21.3) - uuid: 11.1.1 - - '@langchain/langgraph-sdk@1.3.1(@langchain/core@1.1.10(@opentelemetry/api@1.9.0)(@opentelemetry/sdk-trace-base@2.6.1(@opentelemetry/api@1.9.0))(openai@6.32.0(ws@8.21.3)(zod@3.25.76))(ws@8.21.3))(react-dom@19.2.4(react@19.2.4))(react@19.2.4)': - dependencies: - p-queue: 6.6.2 - p-retry: 4.6.2 - uuid: 11.1.1 - optionalDependencies: - '@langchain/core': 1.1.10(@opentelemetry/api@1.9.0)(@opentelemetry/sdk-trace-base@2.6.1(@opentelemetry/api@1.9.0))(openai@6.32.0(ws@8.21.3)(zod@3.25.76))(ws@8.21.3) - react: 19.2.4 - react-dom: 19.2.4(react@19.2.4) - - '@langchain/langgraph@1.0.7(@langchain/core@1.1.10(@opentelemetry/api@1.9.0)(@opentelemetry/sdk-trace-base@2.6.1(@opentelemetry/api@1.9.0))(openai@6.32.0(ws@8.21.3)(zod@3.25.76))(ws@8.21.3))(react-dom@19.2.4(react@19.2.4))(react@19.2.4)(zod-to-json-schema@3.25.1(zod@3.25.76))(zod@3.25.76)': - dependencies: - '@langchain/core': 1.1.10(@opentelemetry/api@1.9.0)(@opentelemetry/sdk-trace-base@2.6.1(@opentelemetry/api@1.9.0))(openai@6.32.0(ws@8.21.3)(zod@3.25.76))(ws@8.21.3) - '@langchain/langgraph-checkpoint': 1.0.0(@langchain/core@1.1.10(@opentelemetry/api@1.9.0)(@opentelemetry/sdk-trace-base@2.6.1(@opentelemetry/api@1.9.0))(openai@6.32.0(ws@8.21.3)(zod@3.25.76))(ws@8.21.3)) - '@langchain/langgraph-sdk': 1.3.1(@langchain/core@1.1.10(@opentelemetry/api@1.9.0)(@opentelemetry/sdk-trace-base@2.6.1(@opentelemetry/api@1.9.0))(openai@6.32.0(ws@8.21.3)(zod@3.25.76))(ws@8.21.3))(react-dom@19.2.4(react@19.2.4))(react@19.2.4) - uuid: 11.1.1 - zod: 3.25.76 - optionalDependencies: - zod-to-json-schema: 3.25.1(zod@3.25.76) - transitivePeerDependencies: - - react - - react-dom - - '@langchain/openai@1.2.1(@langchain/core@1.1.10(@opentelemetry/api@1.9.0)(@opentelemetry/sdk-trace-base@2.6.1(@opentelemetry/api@1.9.0))(openai@6.32.0(ws@8.21.3)(zod@3.25.76))(ws@8.21.3))(ws@8.21.3)': + '@loaderkit/resolve@1.0.6': dependencies: - '@langchain/core': 1.1.10(@opentelemetry/api@1.9.0)(@opentelemetry/sdk-trace-base@2.6.1(@opentelemetry/api@1.9.0))(openai@6.32.0(ws@8.21.3)(zod@3.25.76))(ws@8.21.3) - js-tiktoken: 1.0.21 - openai: 6.25.0(ws@8.21.3)(zod@3.25.76) - zod: 3.25.76 - transitivePeerDependencies: - - ws + '@braidai/lang': 1.1.2 + optional: true '@manypkg/find-root@1.1.0': dependencies: @@ -5303,29 +4928,6 @@ snapshots: globby: 11.1.0 read-yaml-file: 1.1.0 - '@modelcontextprotocol/sdk@1.32.1(zod@3.25.76)': - dependencies: - '@hono/node-server': 1.19.17(hono@4.13.13) - ajv: 8.20.0 - ajv-formats: 3.0.1(ajv@8.20.0) - content-type: 1.0.5 - cors: 2.8.5 - cross-spawn: 7.0.6 - eventsource: 3.0.7 - eventsource-parser: 3.1.1 - express: 5.2.1 - express-rate-limit: 8.3.2(express@5.2.1) - hono: 4.13.13 - jose: 6.2.2 - json-schema-typed: 8.0.2 - pkce-challenge: 5.0.0 - raw-body: 3.0.2 - zod: 3.25.76 - zod-to-json-schema: 3.25.1(zod@3.25.76) - transitivePeerDependencies: - - supports-color - optional: true - '@mswjs/interceptors@0.41.6': dependencies: '@open-draft/deferred-promise': 2.2.0 @@ -5334,6 +4936,7 @@ snapshots: is-node-process: 1.2.0 outvariant: 1.4.3 strict-event-emitter: 0.5.1 + optional: true '@napi-rs/wasm-runtime@1.1.6(@emnapi/core@1.11.1)(@emnapi/runtime@1.11.1)': dependencies: @@ -5342,8 +4945,6 @@ snapshots: '@tybys/wasm-util': 0.10.3 optional: true - '@next/env@14.2.3': {} - '@nodelib/fs.scandir@2.1.5': dependencies: '@nodelib/fs.stat': 2.0.5 @@ -5356,70 +4957,20 @@ snapshots: '@nodelib/fs.scandir': 2.1.5 fastq: 1.16.0 - '@open-draft/deferred-promise@2.2.0': {} + '@open-draft/deferred-promise@2.2.0': + optional: true - '@open-draft/deferred-promise@3.0.0': {} + '@open-draft/deferred-promise@3.0.0': + optional: true '@open-draft/logger@0.3.0': dependencies: is-node-process: 1.2.0 outvariant: 1.4.3 + optional: true - '@open-draft/until@2.1.0': {} - - '@openai/agents-core@0.0.15(ws@8.21.3)(zod@3.25.76)': - dependencies: - '@openai/zod': zod@3.25.67 - debug: 4.4.3 - openai: 5.11.0(ws@8.21.3)(zod@3.25.76) - optionalDependencies: - '@modelcontextprotocol/sdk': 1.32.1(zod@3.25.76) - zod: 3.25.76 - transitivePeerDependencies: - - '@cfworker/json-schema' - - supports-color - - ws - - '@openai/agents-openai@0.0.15(ws@8.21.3)(zod@3.25.76)': - dependencies: - '@openai/agents-core': 0.0.15(ws@8.21.3)(zod@3.25.76) - '@openai/zod': zod@3.25.67 - debug: 4.4.3 - openai: 5.11.0(ws@8.21.3)(zod@3.25.76) - transitivePeerDependencies: - - '@cfworker/json-schema' - - supports-color - - ws - - zod - - '@openai/agents-realtime@0.0.15(zod@3.25.76)': - dependencies: - '@openai/agents-core': 0.0.15(ws@8.21.3)(zod@3.25.76) - '@openai/zod': zod@3.25.67 - '@types/ws': 8.18.1 - debug: 4.4.3 - ws: 8.21.3 - transitivePeerDependencies: - - '@cfworker/json-schema' - - bufferutil - - supports-color - - utf-8-validate - - zod - - '@openai/agents@0.0.15(ws@8.21.3)(zod@3.25.76)': - dependencies: - '@openai/agents-core': 0.0.15(ws@8.21.3)(zod@3.25.76) - '@openai/agents-openai': 0.0.15(ws@8.21.3)(zod@3.25.76) - '@openai/agents-realtime': 0.0.15(zod@3.25.76) - debug: 4.4.3 - openai: 5.11.0(ws@8.21.3)(zod@3.25.76) - transitivePeerDependencies: - - '@cfworker/json-schema' - - bufferutil - - supports-color - - utf-8-validate - - ws - - zod + '@open-draft/until@2.1.0': + optional: true '@opentelemetry/api-logs@0.208.0': dependencies: @@ -5501,148 +5052,237 @@ snapshots: '@opentelemetry/resources': 2.6.1(@opentelemetry/api@1.9.0) '@opentelemetry/semantic-conventions': 1.41.1 - '@opentelemetry/semantic-conventions@1.41.1': {} + '@opentelemetry/semantic-conventions@1.41.1': {} + + '@oxc-project/runtime@0.153.0': {} + + '@oxc-project/types@0.153.0': {} + + '@oxc-resolver/binding-android-arm-eabi@11.19.1': + optional: true + + '@oxc-resolver/binding-android-arm64@11.19.1': + optional: true + + '@oxc-resolver/binding-darwin-arm64@11.19.1': + optional: true + + '@oxc-resolver/binding-darwin-x64@11.19.1': + optional: true + + '@oxc-resolver/binding-freebsd-x64@11.19.1': + optional: true + + '@oxc-resolver/binding-linux-arm-gnueabihf@11.19.1': + optional: true + + '@oxc-resolver/binding-linux-arm-musleabihf@11.19.1': + optional: true + + '@oxc-resolver/binding-linux-arm64-gnu@11.19.1': + optional: true + + '@oxc-resolver/binding-linux-arm64-musl@11.19.1': + optional: true + + '@oxc-resolver/binding-linux-ppc64-gnu@11.19.1': + optional: true + + '@oxc-resolver/binding-linux-riscv64-gnu@11.19.1': + optional: true + + '@oxc-resolver/binding-linux-riscv64-musl@11.19.1': + optional: true + + '@oxc-resolver/binding-linux-s390x-gnu@11.19.1': + optional: true + + '@oxc-resolver/binding-linux-x64-gnu@11.19.1': + optional: true + + '@oxc-resolver/binding-linux-x64-musl@11.19.1': + optional: true + + '@oxc-resolver/binding-openharmony-arm64@11.19.1': + optional: true + + '@oxc-resolver/binding-wasm32-wasi@11.19.1(@emnapi/core@1.11.1)(@emnapi/runtime@1.11.1)': + dependencies: + '@napi-rs/wasm-runtime': 1.1.6(@emnapi/core@1.11.1)(@emnapi/runtime@1.11.1) + transitivePeerDependencies: + - '@emnapi/core' + - '@emnapi/runtime' + optional: true + + '@oxc-resolver/binding-win32-arm64-msvc@11.19.1': + optional: true - '@oxc-project/types@0.139.0': {} + '@oxc-resolver/binding-win32-ia32-msvc@11.19.1': + optional: true - '@oxc-resolver/binding-android-arm-eabi@11.19.1': + '@oxc-resolver/binding-win32-x64-msvc@11.19.1': optional: true - '@oxc-resolver/binding-android-arm64@11.19.1': + '@oxfmt/binding-android-arm-eabi@0.72.0': optional: true - '@oxc-resolver/binding-darwin-arm64@11.19.1': + '@oxfmt/binding-android-arm64@0.72.0': optional: true - '@oxc-resolver/binding-darwin-x64@11.19.1': + '@oxfmt/binding-darwin-arm64@0.72.0': optional: true - '@oxc-resolver/binding-freebsd-x64@11.19.1': + '@oxfmt/binding-darwin-x64@0.72.0': optional: true - '@oxc-resolver/binding-linux-arm-gnueabihf@11.19.1': + '@oxfmt/binding-freebsd-x64@0.72.0': optional: true - '@oxc-resolver/binding-linux-arm-musleabihf@11.19.1': + '@oxfmt/binding-linux-arm-gnueabihf@0.72.0': optional: true - '@oxc-resolver/binding-linux-arm64-gnu@11.19.1': + '@oxfmt/binding-linux-arm-musleabihf@0.72.0': optional: true - '@oxc-resolver/binding-linux-arm64-musl@11.19.1': + '@oxfmt/binding-linux-arm64-gnu@0.72.0': optional: true - '@oxc-resolver/binding-linux-ppc64-gnu@11.19.1': + '@oxfmt/binding-linux-arm64-musl@0.72.0': optional: true - '@oxc-resolver/binding-linux-riscv64-gnu@11.19.1': + '@oxfmt/binding-linux-ppc64-gnu@0.72.0': optional: true - '@oxc-resolver/binding-linux-riscv64-musl@11.19.1': + '@oxfmt/binding-linux-riscv64-gnu@0.72.0': optional: true - '@oxc-resolver/binding-linux-s390x-gnu@11.19.1': + '@oxfmt/binding-linux-riscv64-musl@0.72.0': optional: true - '@oxc-resolver/binding-linux-x64-gnu@11.19.1': + '@oxfmt/binding-linux-s390x-gnu@0.72.0': optional: true - '@oxc-resolver/binding-linux-x64-musl@11.19.1': + '@oxfmt/binding-linux-x64-gnu@0.72.0': optional: true - '@oxc-resolver/binding-openharmony-arm64@11.19.1': + '@oxfmt/binding-linux-x64-musl@0.72.0': optional: true - '@oxc-resolver/binding-wasm32-wasi@11.19.1(@emnapi/core@1.11.1)(@emnapi/runtime@1.11.1)': - dependencies: - '@napi-rs/wasm-runtime': 1.1.6(@emnapi/core@1.11.1)(@emnapi/runtime@1.11.1) - transitivePeerDependencies: - - '@emnapi/core' - - '@emnapi/runtime' + '@oxfmt/binding-openharmony-arm64@0.72.0': optional: true - '@oxc-resolver/binding-win32-arm64-msvc@11.19.1': + '@oxfmt/binding-win32-arm64-msvc@0.72.0': optional: true - '@oxc-resolver/binding-win32-ia32-msvc@11.19.1': + '@oxfmt/binding-win32-ia32-msvc@0.72.0': optional: true - '@oxc-resolver/binding-win32-x64-msvc@11.19.1': + '@oxfmt/binding-win32-x64-msvc@0.72.0': optional: true - '@pkgjs/parseargs@0.11.0': + '@oxlint-tsgolint/darwin-arm64@7.0.2003': optional: true - '@protobufjs/aspromise@1.1.2': {} + '@oxlint-tsgolint/darwin-x64@7.0.2003': + optional: true - '@protobufjs/base64@1.1.2': {} + '@oxlint-tsgolint/linux-arm64@7.0.2003': + optional: true - '@protobufjs/codegen@2.0.5': {} + '@oxlint-tsgolint/linux-x64@7.0.2003': + optional: true - '@protobufjs/eventemitter@1.1.1': {} + '@oxlint-tsgolint/win32-arm64@7.0.2003': + optional: true - '@protobufjs/fetch@1.1.1': - dependencies: - '@protobufjs/aspromise': 1.1.2 + '@oxlint-tsgolint/win32-x64@7.0.2003': + optional: true - '@protobufjs/float@1.0.2': {} + '@oxlint/binding-android-arm-eabi@1.87.0': + optional: true - '@protobufjs/path@1.1.2': {} + '@oxlint/binding-android-arm64@1.87.0': + optional: true - '@protobufjs/pool@1.1.0': {} + '@oxlint/binding-darwin-arm64@1.87.0': + optional: true - '@protobufjs/utf8@1.1.1': {} + '@oxlint/binding-darwin-x64@1.87.0': + optional: true - '@rolldown/binding-android-arm64@1.1.5': + '@oxlint/binding-freebsd-x64@1.87.0': optional: true - '@rolldown/binding-darwin-arm64@1.1.5': + '@oxlint/binding-linux-arm-gnueabihf@1.87.0': optional: true - '@rolldown/binding-darwin-x64@1.1.5': + '@oxlint/binding-linux-arm-musleabihf@1.87.0': optional: true - '@rolldown/binding-freebsd-x64@1.1.5': + '@oxlint/binding-linux-arm64-gnu@1.87.0': optional: true - '@rolldown/binding-linux-arm-gnueabihf@1.1.5': + '@oxlint/binding-linux-arm64-musl@1.87.0': optional: true - '@rolldown/binding-linux-arm64-gnu@1.1.5': + '@oxlint/binding-linux-ppc64-gnu@1.87.0': optional: true - '@rolldown/binding-linux-arm64-musl@1.1.5': + '@oxlint/binding-linux-riscv64-gnu@1.87.0': optional: true - '@rolldown/binding-linux-ppc64-gnu@1.1.5': + '@oxlint/binding-linux-riscv64-musl@1.87.0': optional: true - '@rolldown/binding-linux-s390x-gnu@1.1.5': + '@oxlint/binding-linux-s390x-gnu@1.87.0': optional: true - '@rolldown/binding-linux-x64-gnu@1.1.5': + '@oxlint/binding-linux-x64-gnu@1.87.0': optional: true - '@rolldown/binding-linux-x64-musl@1.1.5': + '@oxlint/binding-linux-x64-musl@1.87.0': optional: true - '@rolldown/binding-openharmony-arm64@1.1.5': + '@oxlint/binding-openharmony-arm64@1.87.0': optional: true - '@rolldown/binding-wasm32-wasi@1.1.5': - dependencies: - '@emnapi/core': 1.11.1 - '@emnapi/runtime': 1.11.1 - '@napi-rs/wasm-runtime': 1.1.6(@emnapi/core@1.11.1)(@emnapi/runtime@1.11.1) + '@oxlint/binding-win32-arm64-msvc@1.87.0': optional: true - '@rolldown/binding-win32-arm64-msvc@1.1.5': + '@oxlint/binding-win32-ia32-msvc@1.87.0': optional: true - '@rolldown/binding-win32-x64-msvc@1.1.5': + '@oxlint/binding-win32-x64-msvc@1.87.0': optional: true - '@rolldown/pluginutils@1.0.1': {} + '@oxlint/plugins@1.79.0': {} + + '@polka/url@1.0.0-next.29': {} + + '@protobufjs/aspromise@1.1.2': {} + + '@protobufjs/base64@1.1.2': {} + + '@protobufjs/codegen@2.0.5': {} + + '@protobufjs/eventemitter@1.1.1': {} + + '@protobufjs/fetch@1.1.1': + dependencies: + '@protobufjs/aspromise': 1.1.2 + + '@protobufjs/float@1.0.2': {} + + '@protobufjs/path@1.1.2': {} + + '@protobufjs/pool@1.1.0': {} + + '@protobufjs/utf8@1.1.1': {} + + '@publint/pack@0.1.7': + dependencies: + tinyexec: 1.3.1 + optional: true '@rollup/rollup-android-arm-eabi@4.60.3': optional: true @@ -5826,7 +5466,7 @@ snapshots: long: 5.3.2 protobufjs: 7.6.5 - '@temporalio/worker@1.14.1(esbuild@0.28.1)(postcss@8.5.26)': + '@temporalio/worker@1.14.1(esbuild@0.28.1)': dependencies: '@grpc/grpc-js': 1.14.6 '@swc/core': 1.15.8 @@ -5845,11 +5485,11 @@ snapshots: protobufjs: 7.6.5 rxjs: 7.8.2 source-map: 0.7.6 - source-map-loader: 4.0.2(webpack@5.106.2(@swc/core@1.15.8)(esbuild@0.28.1)(postcss@8.5.26)) + source-map-loader: 4.0.2(webpack@5.106.2(@swc/core@1.15.8)(esbuild@0.28.1)) supports-color: 8.1.1 - swc-loader: 0.2.6(@swc/core@1.15.8)(webpack@5.106.2(@swc/core@1.15.8)(esbuild@0.28.1)(postcss@8.5.26)) + swc-loader: 0.2.6(@swc/core@1.15.8)(webpack@5.106.2(@swc/core@1.15.8)(esbuild@0.28.1)) unionfs: 4.6.0 - webpack: 5.106.2(@swc/core@1.15.8)(esbuild@0.28.1)(postcss@8.5.26) + webpack: 5.106.2(@swc/core@1.15.8)(esbuild@0.28.1) transitivePeerDependencies: - '@minify-html/node' - '@swc/css' @@ -5871,55 +5511,35 @@ snapshots: '@temporalio/proto': 1.14.1 nexus-rpc: 0.0.1 - '@turbo/darwin-64@2.9.14': - optional: true - - '@turbo/darwin-arm64@2.9.14': - optional: true - - '@turbo/linux-64@2.9.14': - optional: true - - '@turbo/linux-arm64@2.9.14': - optional: true - - '@turbo/windows-64@2.9.14': - optional: true + '@testing-library/dom@10.4.2': + dependencies: + '@babel/code-frame': 7.29.7 + '@babel/runtime': 7.28.4 + '@types/aria-query': 5.0.4 + aria-query: 5.3.0 + dom-accessibility-api: 0.5.16 + lz-string: 1.5.0 + picocolors: 1.1.1 + pretty-format: 27.5.1 - '@turbo/windows-arm64@2.9.14': - optional: true + '@testing-library/user-event@14.6.7(@testing-library/dom@10.4.2)': + dependencies: + '@testing-library/dom': 10.4.2 '@tybys/wasm-util@0.10.3': dependencies: tslib: 2.8.1 optional: true - '@types/argparse@2.0.14': {} + '@types/aria-query@5.0.4': {} '@types/async@3.2.24': {} - '@types/body-parser@1.19.5': - dependencies: - '@types/connect': 3.4.38 - '@types/node': 22.19.1 - '@types/chai@5.2.3': dependencies: '@types/deep-eql': 4.0.2 assertion-error: 2.0.1 - '@types/cli-progress@3.11.5': - dependencies: - '@types/node': 22.19.1 - - '@types/connect@3.4.38': - dependencies: - '@types/node': 22.19.1 - - '@types/cors@2.8.17': - dependencies: - '@types/node': 22.19.1 - '@types/deep-eql@4.0.2': {} '@types/eslint-scope@3.7.7': @@ -5940,25 +5560,8 @@ snapshots: '@types/estree@1.0.9': {} - '@types/express-serve-static-core@5.0.6': - dependencies: - '@types/node': 22.19.1 - '@types/qs': 6.9.18 - '@types/range-parser': 1.2.7 - '@types/send': 0.17.4 - - '@types/express@5.0.1': - dependencies: - '@types/body-parser': 1.19.5 - '@types/express-serve-static-core': 5.0.6 - '@types/serve-static': 1.15.7 - - '@types/http-errors@2.0.4': {} - '@types/json-schema@7.0.15': {} - '@types/mime@1.3.5': {} - '@types/mustache@4.2.5': {} '@types/node@12.20.55': {} @@ -5977,214 +5580,485 @@ snapshots: '@types/nunjucks@3.2.6': {} - '@types/pluralize@0.0.30': {} - '@types/prop-types@15.7.15': {} - '@types/qs@6.9.18': {} - - '@types/range-parser@1.2.7': {} - '@types/react@18.3.31': dependencies: '@types/prop-types': 15.7.15 csstype: 3.2.3 - '@types/retry@0.12.0': {} - - '@types/send@0.17.4': + '@types/set-cookie-parser@2.4.10': dependencies: - '@types/mime': 1.3.5 '@types/node': 22.19.1 + optional: true - '@types/serve-static@1.15.7': - dependencies: - '@types/http-errors': 2.0.4 - '@types/node': 22.19.1 - '@types/send': 0.17.4 + '@types/statuses@2.0.6': + optional: true - '@types/set-cookie-parser@2.4.10': + '@vercel/functions@1.0.2': {} + + '@vercel/oidc@3.1.0': {} + + '@vitest/browser-preview@5.0.3(@voidzero-dev/vite-plus-core@1.1.0(@arethetypeswrong/core@0.18.5)(@types/node@20.19.40)(esbuild@0.28.1)(jiti@2.6.1)(publint@0.3.25)(terser@5.44.1)(tsx@4.21.0)(typescript@5.4.4)(yaml@2.9.0))(msw@2.13.6(@types/node@20.19.40)(typescript@5.4.4))(vitest@5.0.3)': dependencies: - '@types/node': 22.19.1 + '@testing-library/dom': 10.4.2 + '@testing-library/user-event': 14.6.7(@testing-library/dom@10.4.2) + '@vitest/browser': 5.0.3(@voidzero-dev/vite-plus-core@1.1.0(@arethetypeswrong/core@0.18.5)(@types/node@20.19.40)(esbuild@0.28.1)(jiti@2.6.1)(publint@0.3.25)(terser@5.44.1)(tsx@4.21.0)(typescript@5.4.4)(yaml@2.9.0))(msw@2.13.6(@types/node@20.19.40)(typescript@5.4.4))(vitest@5.0.3) + vitest: 5.0.3(@opentelemetry/api@1.9.0)(@types/node@20.19.40)(@vitest/browser-preview@5.0.3)(@vitest/ui@5.0.3)(@voidzero-dev/vite-plus-core@1.1.0(@arethetypeswrong/core@0.18.5)(@types/node@20.19.40)(esbuild@0.28.1)(jiti@2.6.1)(publint@0.3.25)(terser@5.44.1)(tsx@4.21.0)(typescript@5.4.4)(yaml@2.9.0))(msw@2.13.6(@types/node@20.19.40)(typescript@5.4.4)) + transitivePeerDependencies: + - bufferutil + - msw + - utf-8-validate + - vite - '@types/statuses@2.0.6': {} + '@vitest/browser-preview@5.0.3(@voidzero-dev/vite-plus-core@1.1.0(@arethetypeswrong/core@0.18.5)(@types/node@20.19.40)(esbuild@0.28.1)(jiti@2.6.1)(publint@0.3.25)(terser@5.44.1)(tsx@4.21.0)(typescript@5.5.4)(yaml@2.9.0))(msw@2.13.6(@types/node@20.19.40)(typescript@5.5.4))(vitest@5.0.3)': + dependencies: + '@testing-library/dom': 10.4.2 + '@testing-library/user-event': 14.6.7(@testing-library/dom@10.4.2) + '@vitest/browser': 5.0.3(@voidzero-dev/vite-plus-core@1.1.0(@arethetypeswrong/core@0.18.5)(@types/node@20.19.40)(esbuild@0.28.1)(jiti@2.6.1)(publint@0.3.25)(terser@5.44.1)(tsx@4.21.0)(typescript@5.5.4)(yaml@2.9.0))(msw@2.13.6(@types/node@20.19.40)(typescript@5.5.4))(vitest@5.0.3) + vitest: 5.0.3(@opentelemetry/api@1.9.0)(@types/node@20.19.40)(@vitest/browser-preview@5.0.3)(@vitest/ui@5.0.3)(@voidzero-dev/vite-plus-core@1.1.0(@arethetypeswrong/core@0.18.5)(@types/node@20.19.40)(esbuild@0.28.1)(jiti@2.6.1)(publint@0.3.25)(terser@5.44.1)(tsx@4.21.0)(typescript@5.5.4)(yaml@2.9.0))(msw@2.13.6(@types/node@20.19.40)(typescript@5.5.4)) + transitivePeerDependencies: + - bufferutil + - msw + - utf-8-validate + - vite - '@types/ws@8.18.1': + '@vitest/browser-preview@5.0.3(@voidzero-dev/vite-plus-core@1.1.0(@arethetypeswrong/core@0.18.5)(@types/node@20.19.40)(esbuild@0.28.1)(jiti@2.6.1)(publint@0.3.25)(terser@5.44.1)(tsx@4.21.0)(typescript@5.9.3)(yaml@2.9.0))(msw@2.13.6(@types/node@20.19.40)(typescript@5.9.3))(vitest@5.0.3)': dependencies: - '@types/node': 22.19.1 + '@testing-library/dom': 10.4.2 + '@testing-library/user-event': 14.6.7(@testing-library/dom@10.4.2) + '@vitest/browser': 5.0.3(@voidzero-dev/vite-plus-core@1.1.0(@arethetypeswrong/core@0.18.5)(@types/node@20.19.40)(esbuild@0.28.1)(jiti@2.6.1)(publint@0.3.25)(terser@5.44.1)(tsx@4.21.0)(typescript@5.9.3)(yaml@2.9.0))(msw@2.13.6(@types/node@20.19.40)(typescript@5.9.3))(vitest@5.0.3) + vitest: 5.0.3(@opentelemetry/api@1.9.0)(@types/node@20.19.40)(@vitest/browser-preview@5.0.3)(@vitest/ui@5.0.3(vitest@5.0.3))(@voidzero-dev/vite-plus-core@1.1.0(@arethetypeswrong/core@0.18.5)(@types/node@20.19.40)(esbuild@0.28.1)(jiti@2.6.1)(publint@0.3.25)(terser@5.44.1)(tsx@4.21.0)(typescript@5.9.3)(yaml@2.9.0))(msw@2.13.6(@types/node@20.19.40)(typescript@5.9.3)) + transitivePeerDependencies: + - bufferutil + - msw + - utf-8-validate + - vite - '@typescript-eslint/eslint-plugin@8.50.0(@typescript-eslint/parser@8.50.0(eslint@9.39.4(jiti@2.6.1))(typescript@5.4.4))(eslint@9.39.4(jiti@2.6.1))(typescript@5.4.4)': + '@vitest/browser-preview@5.0.3(@voidzero-dev/vite-plus-core@1.1.0(@arethetypeswrong/core@0.18.5)(@types/node@22.19.1)(esbuild@0.28.1)(jiti@2.6.1)(publint@0.3.25)(terser@5.44.1)(tsx@4.21.0)(typescript@5.5.4)(yaml@2.9.0))(msw@2.13.6(@types/node@22.19.1)(typescript@5.5.4))(vitest@5.0.3)': dependencies: - '@eslint-community/regexpp': 4.12.2 - '@typescript-eslint/parser': 8.50.0(eslint@9.39.4(jiti@2.6.1))(typescript@5.4.4) - '@typescript-eslint/scope-manager': 8.50.0 - '@typescript-eslint/type-utils': 8.50.0(eslint@9.39.4(jiti@2.6.1))(typescript@5.4.4) - '@typescript-eslint/utils': 8.50.0(eslint@9.39.4(jiti@2.6.1))(typescript@5.4.4) - '@typescript-eslint/visitor-keys': 8.50.0 - eslint: 9.39.4(jiti@2.6.1) - ignore: 7.0.5 - natural-compare: 1.4.0 - ts-api-utils: 2.1.0(typescript@5.4.4) - typescript: 5.4.4 + '@testing-library/dom': 10.4.2 + '@testing-library/user-event': 14.6.7(@testing-library/dom@10.4.2) + '@vitest/browser': 5.0.3(@voidzero-dev/vite-plus-core@1.1.0(@arethetypeswrong/core@0.18.5)(@types/node@22.19.1)(esbuild@0.28.1)(jiti@2.6.1)(publint@0.3.25)(terser@5.44.1)(tsx@4.21.0)(typescript@5.5.4)(yaml@2.9.0))(msw@2.13.6(@types/node@22.19.1)(typescript@5.5.4))(vitest@5.0.3) + vitest: 5.0.3(@opentelemetry/api@1.9.0)(@types/node@22.19.1)(@vitest/browser-preview@5.0.3)(@vitest/ui@5.0.3(vitest@5.0.3))(@voidzero-dev/vite-plus-core@1.1.0(@arethetypeswrong/core@0.18.5)(@types/node@22.19.1)(esbuild@0.28.1)(jiti@2.6.1)(publint@0.3.25)(terser@5.44.1)(tsx@4.21.0)(typescript@5.5.4)(yaml@2.9.0))(msw@2.13.6(@types/node@22.19.1)(typescript@5.5.4)) transitivePeerDependencies: - - supports-color + - bufferutil + - msw + - utf-8-validate + - vite - '@typescript-eslint/parser@8.50.0(eslint@9.39.4(jiti@2.6.1))(typescript@5.4.4)': + '@vitest/browser-preview@5.0.3(@voidzero-dev/vite-plus-core@1.1.0(@arethetypeswrong/core@0.18.5)(@types/node@22.19.1)(esbuild@0.28.1)(jiti@2.6.1)(publint@0.3.25)(terser@5.44.1)(tsx@4.21.0)(typescript@5.9.3)(yaml@2.9.0))(msw@2.13.6(@types/node@22.19.1)(typescript@5.9.3))(vitest@5.0.3)': dependencies: - '@typescript-eslint/scope-manager': 8.50.0 - '@typescript-eslint/types': 8.50.0 - '@typescript-eslint/typescript-estree': 8.50.0(typescript@5.4.4) - '@typescript-eslint/visitor-keys': 8.50.0 - debug: 4.4.3 - eslint: 9.39.4(jiti@2.6.1) - typescript: 5.4.4 + '@testing-library/dom': 10.4.2 + '@testing-library/user-event': 14.6.7(@testing-library/dom@10.4.2) + '@vitest/browser': 5.0.3(@voidzero-dev/vite-plus-core@1.1.0(@arethetypeswrong/core@0.18.5)(@types/node@22.19.1)(esbuild@0.28.1)(jiti@2.6.1)(publint@0.3.25)(terser@5.44.1)(tsx@4.21.0)(typescript@5.9.3)(yaml@2.9.0))(msw@2.13.6(@types/node@22.19.1)(typescript@5.9.3))(vitest@5.0.3) + vitest: 5.0.3(@opentelemetry/api@1.9.0)(@types/node@22.19.1)(@vitest/browser-preview@5.0.3)(@vitest/ui@5.0.3(vitest@5.0.3))(@voidzero-dev/vite-plus-core@1.1.0(@arethetypeswrong/core@0.18.5)(@types/node@22.19.1)(esbuild@0.28.1)(jiti@2.6.1)(publint@0.3.25)(terser@5.44.1)(tsx@4.21.0)(typescript@5.9.3)(yaml@2.9.0))(msw@2.13.6(@types/node@22.19.1)(typescript@5.9.3)) transitivePeerDependencies: - - supports-color + - bufferutil + - msw + - utf-8-validate + - vite - '@typescript-eslint/project-service@8.50.0(typescript@5.4.4)': + '@vitest/browser-preview@5.0.3(@voidzero-dev/vite-plus-core@1.1.0(@arethetypeswrong/core@0.18.5)(@types/node@24.12.4)(esbuild@0.28.1)(jiti@2.6.1)(publint@0.3.25)(terser@5.44.1)(tsx@4.21.0)(typescript@5.9.3)(yaml@2.9.0))(msw@2.13.6(@types/node@24.12.4)(typescript@5.9.3))(vitest@5.0.3)': dependencies: - '@typescript-eslint/tsconfig-utils': 8.50.0(typescript@5.4.4) - '@typescript-eslint/types': 8.50.0 - debug: 4.4.3 - typescript: 5.4.4 + '@testing-library/dom': 10.4.2 + '@testing-library/user-event': 14.6.7(@testing-library/dom@10.4.2) + '@vitest/browser': 5.0.3(@voidzero-dev/vite-plus-core@1.1.0(@arethetypeswrong/core@0.18.5)(@types/node@24.12.4)(esbuild@0.28.1)(jiti@2.6.1)(publint@0.3.25)(terser@5.44.1)(tsx@4.21.0)(typescript@5.9.3)(yaml@2.9.0))(msw@2.13.6(@types/node@24.12.4)(typescript@5.9.3))(vitest@5.0.3) + vitest: 5.0.3(@opentelemetry/api@1.9.0)(@types/node@24.12.4)(@vitest/browser-preview@5.0.3)(@vitest/ui@5.0.3(vitest@5.0.3))(@voidzero-dev/vite-plus-core@1.1.0(@arethetypeswrong/core@0.18.5)(@types/node@24.12.4)(esbuild@0.28.1)(jiti@2.6.1)(publint@0.3.25)(terser@5.44.1)(tsx@4.21.0)(typescript@5.9.3)(yaml@2.9.0))(msw@2.13.6(@types/node@24.12.4)(typescript@5.9.3)) transitivePeerDependencies: - - supports-color + - bufferutil + - msw + - utf-8-validate + - vite - '@typescript-eslint/scope-manager@8.50.0': + '@vitest/browser@5.0.3(@voidzero-dev/vite-plus-core@1.1.0(@arethetypeswrong/core@0.18.5)(@types/node@20.19.40)(esbuild@0.28.1)(jiti@2.6.1)(publint@0.3.25)(terser@5.44.1)(tsx@4.21.0)(typescript@5.4.4)(yaml@2.9.0))(msw@2.13.6(@types/node@20.19.40)(typescript@5.4.4))(vitest@5.0.3)': dependencies: - '@typescript-eslint/types': 8.50.0 - '@typescript-eslint/visitor-keys': 8.50.0 + '@blazediff/core': 1.10.0 + '@vitest/mocker': 5.0.3(@voidzero-dev/vite-plus-core@1.1.0(@arethetypeswrong/core@0.18.5)(@types/node@20.19.40)(esbuild@0.28.1)(jiti@2.6.1)(publint@0.3.25)(terser@5.44.1)(tsx@4.21.0)(typescript@5.4.4)(yaml@2.9.0))(msw@2.13.6(@types/node@20.19.40)(typescript@5.4.4)) + '@vitest/ui': 5.0.3(vitest@5.0.3) + '@vitest/utils': 5.0.3 + magic-string: 1.4.3 + pngjs: 7.0.0 + sirv: 3.0.2 + tinyrainbow: 3.1.1 + vitest: 5.0.3(@opentelemetry/api@1.9.0)(@types/node@20.19.40)(@vitest/browser-preview@5.0.3)(@vitest/ui@5.0.3)(@voidzero-dev/vite-plus-core@1.1.0(@arethetypeswrong/core@0.18.5)(@types/node@20.19.40)(esbuild@0.28.1)(jiti@2.6.1)(publint@0.3.25)(terser@5.44.1)(tsx@4.21.0)(typescript@5.4.4)(yaml@2.9.0))(msw@2.13.6(@types/node@20.19.40)(typescript@5.4.4)) + ws: 8.21.3 + transitivePeerDependencies: + - bufferutil + - msw + - utf-8-validate + - vite - '@typescript-eslint/tsconfig-utils@8.50.0(typescript@5.4.4)': + '@vitest/browser@5.0.3(@voidzero-dev/vite-plus-core@1.1.0(@arethetypeswrong/core@0.18.5)(@types/node@20.19.40)(esbuild@0.28.1)(jiti@2.6.1)(publint@0.3.25)(terser@5.44.1)(tsx@4.21.0)(typescript@5.5.4)(yaml@2.9.0))(msw@2.13.6(@types/node@20.19.40)(typescript@5.5.4))(vitest@5.0.3)': dependencies: - typescript: 5.4.4 + '@blazediff/core': 1.10.0 + '@vitest/mocker': 5.0.3(@voidzero-dev/vite-plus-core@1.1.0(@arethetypeswrong/core@0.18.5)(@types/node@20.19.40)(esbuild@0.28.1)(jiti@2.6.1)(publint@0.3.25)(terser@5.44.1)(tsx@4.21.0)(typescript@5.5.4)(yaml@2.9.0))(msw@2.13.6(@types/node@20.19.40)(typescript@5.5.4)) + '@vitest/ui': 5.0.3(vitest@5.0.3) + '@vitest/utils': 5.0.3 + magic-string: 1.4.3 + pngjs: 7.0.0 + sirv: 3.0.2 + tinyrainbow: 3.1.1 + vitest: 5.0.3(@opentelemetry/api@1.9.0)(@types/node@20.19.40)(@vitest/browser-preview@5.0.3)(@vitest/ui@5.0.3)(@voidzero-dev/vite-plus-core@1.1.0(@arethetypeswrong/core@0.18.5)(@types/node@20.19.40)(esbuild@0.28.1)(jiti@2.6.1)(publint@0.3.25)(terser@5.44.1)(tsx@4.21.0)(typescript@5.5.4)(yaml@2.9.0))(msw@2.13.6(@types/node@20.19.40)(typescript@5.5.4)) + ws: 8.21.3 + transitivePeerDependencies: + - bufferutil + - msw + - utf-8-validate + - vite - '@typescript-eslint/type-utils@8.50.0(eslint@9.39.4(jiti@2.6.1))(typescript@5.4.4)': + '@vitest/browser@5.0.3(@voidzero-dev/vite-plus-core@1.1.0(@arethetypeswrong/core@0.18.5)(@types/node@20.19.40)(esbuild@0.28.1)(jiti@2.6.1)(publint@0.3.25)(terser@5.44.1)(tsx@4.21.0)(typescript@5.9.3)(yaml@2.9.0))(msw@2.13.6(@types/node@20.19.40)(typescript@5.9.3))(vitest@5.0.3)': dependencies: - '@typescript-eslint/types': 8.50.0 - '@typescript-eslint/typescript-estree': 8.50.0(typescript@5.4.4) - '@typescript-eslint/utils': 8.50.0(eslint@9.39.4(jiti@2.6.1))(typescript@5.4.4) - debug: 4.4.3 - eslint: 9.39.4(jiti@2.6.1) - ts-api-utils: 2.1.0(typescript@5.4.4) - typescript: 5.4.4 + '@blazediff/core': 1.10.0 + '@vitest/mocker': 5.0.3(@voidzero-dev/vite-plus-core@1.1.0(@arethetypeswrong/core@0.18.5)(@types/node@20.19.40)(esbuild@0.28.1)(jiti@2.6.1)(publint@0.3.25)(terser@5.44.1)(tsx@4.21.0)(typescript@5.9.3)(yaml@2.9.0))(msw@2.13.6(@types/node@20.19.40)(typescript@5.9.3)) + '@vitest/ui': 5.0.3(vitest@5.0.3) + '@vitest/utils': 5.0.3 + magic-string: 1.4.3 + pngjs: 7.0.0 + sirv: 3.0.2 + tinyrainbow: 3.1.1 + vitest: 5.0.3(@opentelemetry/api@1.9.0)(@types/node@20.19.40)(@vitest/browser-preview@5.0.3)(@vitest/ui@5.0.3(vitest@5.0.3))(@voidzero-dev/vite-plus-core@1.1.0(@arethetypeswrong/core@0.18.5)(@types/node@20.19.40)(esbuild@0.28.1)(jiti@2.6.1)(publint@0.3.25)(terser@5.44.1)(tsx@4.21.0)(typescript@5.9.3)(yaml@2.9.0))(msw@2.13.6(@types/node@20.19.40)(typescript@5.9.3)) + ws: 8.21.3 transitivePeerDependencies: - - supports-color + - bufferutil + - msw + - utf-8-validate + - vite - '@typescript-eslint/types@8.50.0': {} + '@vitest/browser@5.0.3(@voidzero-dev/vite-plus-core@1.1.0(@arethetypeswrong/core@0.18.5)(@types/node@22.19.1)(esbuild@0.28.1)(jiti@2.6.1)(publint@0.3.25)(terser@5.44.1)(tsx@4.21.0)(typescript@5.5.4)(yaml@2.9.0))(msw@2.13.6(@types/node@22.19.1)(typescript@5.5.4))(vitest@5.0.3)': + dependencies: + '@blazediff/core': 1.10.0 + '@vitest/mocker': 5.0.3(@voidzero-dev/vite-plus-core@1.1.0(@arethetypeswrong/core@0.18.5)(@types/node@22.19.1)(esbuild@0.28.1)(jiti@2.6.1)(publint@0.3.25)(terser@5.44.1)(tsx@4.21.0)(typescript@5.5.4)(yaml@2.9.0))(msw@2.13.6(@types/node@22.19.1)(typescript@5.5.4)) + '@vitest/ui': 5.0.3(vitest@5.0.3) + '@vitest/utils': 5.0.3 + magic-string: 1.4.3 + pngjs: 7.0.0 + sirv: 3.0.2 + tinyrainbow: 3.1.1 + vitest: 5.0.3(@opentelemetry/api@1.9.0)(@types/node@22.19.1)(@vitest/browser-preview@5.0.3)(@vitest/ui@5.0.3(vitest@5.0.3))(@voidzero-dev/vite-plus-core@1.1.0(@arethetypeswrong/core@0.18.5)(@types/node@22.19.1)(esbuild@0.28.1)(jiti@2.6.1)(publint@0.3.25)(terser@5.44.1)(tsx@4.21.0)(typescript@5.5.4)(yaml@2.9.0))(msw@2.13.6(@types/node@22.19.1)(typescript@5.5.4)) + ws: 8.21.3 + transitivePeerDependencies: + - bufferutil + - msw + - utf-8-validate + - vite - '@typescript-eslint/typescript-estree@8.50.0(typescript@5.4.4)': + '@vitest/browser@5.0.3(@voidzero-dev/vite-plus-core@1.1.0(@arethetypeswrong/core@0.18.5)(@types/node@22.19.1)(esbuild@0.28.1)(jiti@2.6.1)(publint@0.3.25)(terser@5.44.1)(tsx@4.21.0)(typescript@5.9.3)(yaml@2.9.0))(msw@2.13.6(@types/node@22.19.1)(typescript@5.9.3))(vitest@5.0.3)': dependencies: - '@typescript-eslint/project-service': 8.50.0(typescript@5.4.4) - '@typescript-eslint/tsconfig-utils': 8.50.0(typescript@5.4.4) - '@typescript-eslint/types': 8.50.0 - '@typescript-eslint/visitor-keys': 8.50.0 - debug: 4.4.3 - minimatch: 9.0.9 - semver: 7.8.0 - tinyglobby: 0.2.17 - ts-api-utils: 2.1.0(typescript@5.4.4) - typescript: 5.4.4 + '@blazediff/core': 1.10.0 + '@vitest/mocker': 5.0.3(@voidzero-dev/vite-plus-core@1.1.0(@arethetypeswrong/core@0.18.5)(@types/node@22.19.1)(esbuild@0.28.1)(jiti@2.6.1)(publint@0.3.25)(terser@5.44.1)(tsx@4.21.0)(typescript@5.9.3)(yaml@2.9.0))(msw@2.13.6(@types/node@22.19.1)(typescript@5.9.3)) + '@vitest/ui': 5.0.3(vitest@5.0.3) + '@vitest/utils': 5.0.3 + magic-string: 1.4.3 + pngjs: 7.0.0 + sirv: 3.0.2 + tinyrainbow: 3.1.1 + vitest: 5.0.3(@opentelemetry/api@1.9.0)(@types/node@22.19.1)(@vitest/browser-preview@5.0.3)(@vitest/ui@5.0.3(vitest@5.0.3))(@voidzero-dev/vite-plus-core@1.1.0(@arethetypeswrong/core@0.18.5)(@types/node@22.19.1)(esbuild@0.28.1)(jiti@2.6.1)(publint@0.3.25)(terser@5.44.1)(tsx@4.21.0)(typescript@5.9.3)(yaml@2.9.0))(msw@2.13.6(@types/node@22.19.1)(typescript@5.9.3)) + ws: 8.21.3 transitivePeerDependencies: - - supports-color + - bufferutil + - msw + - utf-8-validate + - vite - '@typescript-eslint/utils@8.50.0(eslint@9.39.4(jiti@2.6.1))(typescript@5.4.4)': + '@vitest/browser@5.0.3(@voidzero-dev/vite-plus-core@1.1.0(@arethetypeswrong/core@0.18.5)(@types/node@24.12.4)(esbuild@0.28.1)(jiti@2.6.1)(publint@0.3.25)(terser@5.44.1)(tsx@4.21.0)(typescript@5.9.3)(yaml@2.9.0))(msw@2.13.6(@types/node@24.12.4)(typescript@5.9.3))(vitest@5.0.3)': dependencies: - '@eslint-community/eslint-utils': 4.9.1(eslint@9.39.4(jiti@2.6.1)) - '@typescript-eslint/scope-manager': 8.50.0 - '@typescript-eslint/types': 8.50.0 - '@typescript-eslint/typescript-estree': 8.50.0(typescript@5.4.4) - eslint: 9.39.4(jiti@2.6.1) - typescript: 5.4.4 + '@blazediff/core': 1.10.0 + '@vitest/mocker': 5.0.3(@voidzero-dev/vite-plus-core@1.1.0(@arethetypeswrong/core@0.18.5)(@types/node@24.12.4)(esbuild@0.28.1)(jiti@2.6.1)(publint@0.3.25)(terser@5.44.1)(tsx@4.21.0)(typescript@5.9.3)(yaml@2.9.0))(msw@2.13.6(@types/node@24.12.4)(typescript@5.9.3)) + '@vitest/ui': 5.0.3(vitest@5.0.3) + '@vitest/utils': 5.0.3 + magic-string: 1.4.3 + pngjs: 7.0.0 + sirv: 3.0.2 + tinyrainbow: 3.1.1 + vitest: 5.0.3(@opentelemetry/api@1.9.0)(@types/node@24.12.4)(@vitest/browser-preview@5.0.3)(@vitest/ui@5.0.3(vitest@5.0.3))(@voidzero-dev/vite-plus-core@1.1.0(@arethetypeswrong/core@0.18.5)(@types/node@24.12.4)(esbuild@0.28.1)(jiti@2.6.1)(publint@0.3.25)(terser@5.44.1)(tsx@4.21.0)(typescript@5.9.3)(yaml@2.9.0))(msw@2.13.6(@types/node@24.12.4)(typescript@5.9.3)) + ws: 8.21.3 transitivePeerDependencies: - - supports-color + - bufferutil + - msw + - utf-8-validate + - vite - '@typescript-eslint/visitor-keys@8.50.0': + '@vitest/mocker@5.0.3(@voidzero-dev/vite-plus-core@1.1.0(@arethetypeswrong/core@0.18.5)(@types/node@20.19.40)(esbuild@0.28.1)(jiti@2.6.1)(publint@0.3.25)(terser@5.44.1)(tsx@4.21.0)(typescript@5.4.4)(yaml@2.9.0))(msw@2.13.6(@types/node@20.19.40)(typescript@5.4.4))': dependencies: - '@typescript-eslint/types': 8.50.0 - eslint-visitor-keys: 4.2.1 + '@jridgewell/trace-mapping': 0.3.31 + '@vitest/spy': 5.0.3 + estree-walker: 3.0.3 + magic-string: 1.4.3 + optionalDependencies: + msw: 2.13.6(@types/node@20.19.40)(typescript@5.4.4) + vite: '@voidzero-dev/vite-plus-core@1.1.0(@arethetypeswrong/core@0.18.5)(@types/node@20.19.40)(esbuild@0.28.1)(jiti@2.6.1)(publint@0.3.25)(terser@5.44.1)(tsx@4.21.0)(typescript@5.4.4)(yaml@2.9.0)' - '@vercel/functions@1.0.2': {} + '@vitest/mocker@5.0.3(@voidzero-dev/vite-plus-core@1.1.0(@arethetypeswrong/core@0.18.5)(@types/node@20.19.40)(esbuild@0.28.1)(jiti@2.6.1)(publint@0.3.25)(terser@5.44.1)(tsx@4.21.0)(typescript@5.5.4)(yaml@2.9.0))(msw@2.13.6(@types/node@20.19.40)(typescript@5.5.4))': + dependencies: + '@jridgewell/trace-mapping': 0.3.31 + '@vitest/spy': 5.0.3 + estree-walker: 3.0.3 + magic-string: 1.4.3 + optionalDependencies: + msw: 2.13.6(@types/node@20.19.40)(typescript@5.5.4) + vite: '@voidzero-dev/vite-plus-core@1.1.0(@arethetypeswrong/core@0.18.5)(@types/node@20.19.40)(esbuild@0.28.1)(jiti@2.6.1)(publint@0.3.25)(terser@5.44.1)(tsx@4.21.0)(typescript@5.5.4)(yaml@2.9.0)' - '@vercel/oidc@3.1.0': {} + '@vitest/mocker@5.0.3(@voidzero-dev/vite-plus-core@1.1.0(@arethetypeswrong/core@0.18.5)(@types/node@20.19.40)(esbuild@0.28.1)(jiti@2.6.1)(publint@0.3.25)(terser@5.44.1)(tsx@4.21.0)(typescript@5.9.3)(yaml@2.9.0))(msw@2.13.6(@types/node@20.19.40)(typescript@5.9.3))': + dependencies: + '@jridgewell/trace-mapping': 0.3.31 + '@vitest/spy': 5.0.3 + estree-walker: 3.0.3 + magic-string: 1.4.3 + optionalDependencies: + msw: 2.13.6(@types/node@20.19.40)(typescript@5.9.3) + vite: '@voidzero-dev/vite-plus-core@1.1.0(@arethetypeswrong/core@0.18.5)(@types/node@20.19.40)(esbuild@0.28.1)(jiti@2.6.1)(publint@0.3.25)(terser@5.44.1)(tsx@4.21.0)(typescript@5.9.3)(yaml@2.9.0)' + + '@vitest/mocker@5.0.3(@voidzero-dev/vite-plus-core@1.1.0(@arethetypeswrong/core@0.18.5)(@types/node@22.19.1)(esbuild@0.28.1)(jiti@2.6.1)(publint@0.3.25)(terser@5.44.1)(tsx@4.21.0)(typescript@5.5.4)(yaml@2.9.0))(msw@2.13.6(@types/node@22.19.1)(typescript@5.5.4))': + dependencies: + '@jridgewell/trace-mapping': 0.3.31 + '@vitest/spy': 5.0.3 + estree-walker: 3.0.3 + magic-string: 1.4.3 + optionalDependencies: + msw: 2.13.6(@types/node@22.19.1)(typescript@5.5.4) + vite: '@voidzero-dev/vite-plus-core@1.1.0(@arethetypeswrong/core@0.18.5)(@types/node@22.19.1)(esbuild@0.28.1)(jiti@2.6.1)(publint@0.3.25)(terser@5.44.1)(tsx@4.21.0)(typescript@5.5.4)(yaml@2.9.0)' + + '@vitest/mocker@5.0.3(@voidzero-dev/vite-plus-core@1.1.0(@arethetypeswrong/core@0.18.5)(@types/node@22.19.1)(esbuild@0.28.1)(jiti@2.6.1)(publint@0.3.25)(terser@5.44.1)(tsx@4.21.0)(typescript@5.9.3)(yaml@2.9.0))(msw@2.13.6(@types/node@22.19.1)(typescript@5.9.3))': + dependencies: + '@jridgewell/trace-mapping': 0.3.31 + '@vitest/spy': 5.0.3 + estree-walker: 3.0.3 + magic-string: 1.4.3 + optionalDependencies: + msw: 2.13.6(@types/node@22.19.1)(typescript@5.9.3) + vite: '@voidzero-dev/vite-plus-core@1.1.0(@arethetypeswrong/core@0.18.5)(@types/node@22.19.1)(esbuild@0.28.1)(jiti@2.6.1)(publint@0.3.25)(terser@5.44.1)(tsx@4.21.0)(typescript@5.9.3)(yaml@2.9.0)' + + '@vitest/mocker@5.0.3(@voidzero-dev/vite-plus-core@1.1.0(@arethetypeswrong/core@0.18.5)(@types/node@24.12.4)(esbuild@0.28.1)(jiti@2.6.1)(publint@0.3.25)(terser@5.44.1)(tsx@4.21.0)(typescript@5.9.3)(yaml@2.9.0))(msw@2.13.6(@types/node@24.12.4)(typescript@5.9.3))': + dependencies: + '@jridgewell/trace-mapping': 0.3.31 + '@vitest/spy': 5.0.3 + estree-walker: 3.0.3 + magic-string: 1.4.3 + optionalDependencies: + msw: 2.13.6(@types/node@24.12.4)(typescript@5.9.3) + vite: '@voidzero-dev/vite-plus-core@1.1.0(@arethetypeswrong/core@0.18.5)(@types/node@24.12.4)(esbuild@0.28.1)(jiti@2.6.1)(publint@0.3.25)(terser@5.44.1)(tsx@4.21.0)(typescript@5.9.3)(yaml@2.9.0)' + + '@vitest/pretty-format@5.0.3': + dependencies: + tinyrainbow: 3.1.1 + + '@vitest/snapshot@5.0.3': + dependencies: + '@vitest/pretty-format': 5.0.3 + '@vitest/utils': 5.0.3 + magic-string: 1.4.3 + pathe: 2.0.3 + + '@vitest/spy@5.0.3': {} + + '@vitest/ui@5.0.3(vitest@5.0.3)': + dependencies: + '@vitest/utils': 5.0.3 + fflate: 0.8.3 + flatted: 3.4.4 + pathe: 2.0.3 + sirv: 3.0.2 + tinyrainbow: 3.1.1 + vitest: 5.0.3(@opentelemetry/api@1.9.0)(@types/node@22.19.1)(@vitest/browser-preview@5.0.3)(@vitest/ui@5.0.3(vitest@5.0.3))(@voidzero-dev/vite-plus-core@1.1.0(@arethetypeswrong/core@0.18.5)(@types/node@22.19.1)(esbuild@0.28.1)(jiti@2.6.1)(publint@0.3.25)(terser@5.44.1)(tsx@4.21.0)(typescript@5.9.3)(yaml@2.9.0))(msw@2.13.6(@types/node@22.19.1)(typescript@5.9.3)) - '@vitest/expect@4.1.11': + '@vitest/utils@5.0.3': dependencies: - '@standard-schema/spec': 1.1.0 - '@types/chai': 5.2.3 - '@vitest/spy': 4.1.11 - '@vitest/utils': 4.1.11 - chai: 6.2.2 + '@vitest/pretty-format': 5.0.3 + convert-source-map: 2.0.0 tinyrainbow: 3.1.1 - '@vitest/mocker@4.1.11(msw@2.13.6(@types/node@20.19.40)(typescript@5.4.4))(vite@8.1.5(@types/node@20.19.40)(esbuild@0.28.1)(jiti@2.6.1)(terser@5.44.1)(tsx@4.21.0)(yaml@2.9.0))': + '@voidzero-dev/vite-plus-core@1.1.0(@arethetypeswrong/core@0.18.5)(@types/node@20.19.40)(esbuild@0.28.1)(jiti@2.6.1)(publint@0.3.25)(terser@5.44.1)(tsx@4.21.0)(typescript@5.4.4)(yaml@2.9.0)': dependencies: - '@vitest/spy': 4.1.11 - estree-walker: 3.0.3 - magic-string: 0.30.21 + '@oxc-project/runtime': 0.153.0 + '@oxc-project/types': 0.153.0 + lightningcss: 1.33.0 + postcss: 8.5.29 + yuku-codegen: 0.9.5 + yuku-parser: 0.9.5 optionalDependencies: - msw: 2.13.6(@types/node@20.19.40)(typescript@5.4.4) - vite: 8.1.5(@types/node@20.19.40)(esbuild@0.28.1)(jiti@2.6.1)(terser@5.44.1)(tsx@4.21.0)(yaml@2.9.0) + '@arethetypeswrong/core': 0.18.5 + '@types/node': 20.19.40 + '@voidzero-dev/vite-plus-darwin-arm64': 1.1.0 + '@voidzero-dev/vite-plus-darwin-x64': 1.1.0 + '@voidzero-dev/vite-plus-linux-arm64-gnu': 1.1.0 + '@voidzero-dev/vite-plus-linux-arm64-musl': 1.1.0 + '@voidzero-dev/vite-plus-linux-x64-gnu': 1.1.0 + '@voidzero-dev/vite-plus-linux-x64-musl': 1.1.0 + '@voidzero-dev/vite-plus-win32-arm64-msvc': 1.1.0 + '@voidzero-dev/vite-plus-win32-x64-msvc': 1.1.0 + esbuild: 0.28.1 + fsevents: 2.3.3 + jiti: 2.6.1 + publint: 0.3.25 + terser: 5.44.1 + tsx: 4.21.0 + typescript: 5.4.4 + yaml: 2.9.0 - '@vitest/mocker@4.1.11(msw@2.13.6(@types/node@20.19.40)(typescript@5.5.4))(vite@8.1.5(@types/node@20.19.40)(esbuild@0.28.1)(jiti@2.6.1)(terser@5.44.1)(tsx@4.21.0)(yaml@2.9.0))': + '@voidzero-dev/vite-plus-core@1.1.0(@arethetypeswrong/core@0.18.5)(@types/node@20.19.40)(esbuild@0.28.1)(jiti@2.6.1)(publint@0.3.25)(terser@5.44.1)(tsx@4.21.0)(typescript@5.5.4)(yaml@2.9.0)': dependencies: - '@vitest/spy': 4.1.11 - estree-walker: 3.0.3 - magic-string: 0.30.21 + '@oxc-project/runtime': 0.153.0 + '@oxc-project/types': 0.153.0 + lightningcss: 1.33.0 + postcss: 8.5.29 + yuku-codegen: 0.9.5 + yuku-parser: 0.9.5 optionalDependencies: - msw: 2.13.6(@types/node@20.19.40)(typescript@5.5.4) - vite: 8.1.5(@types/node@20.19.40)(esbuild@0.28.1)(jiti@2.6.1)(terser@5.44.1)(tsx@4.21.0)(yaml@2.9.0) + '@arethetypeswrong/core': 0.18.5 + '@types/node': 20.19.40 + '@voidzero-dev/vite-plus-darwin-arm64': 1.1.0 + '@voidzero-dev/vite-plus-darwin-x64': 1.1.0 + '@voidzero-dev/vite-plus-linux-arm64-gnu': 1.1.0 + '@voidzero-dev/vite-plus-linux-arm64-musl': 1.1.0 + '@voidzero-dev/vite-plus-linux-x64-gnu': 1.1.0 + '@voidzero-dev/vite-plus-linux-x64-musl': 1.1.0 + '@voidzero-dev/vite-plus-win32-arm64-msvc': 1.1.0 + '@voidzero-dev/vite-plus-win32-x64-msvc': 1.1.0 + esbuild: 0.28.1 + fsevents: 2.3.3 + jiti: 2.6.1 + publint: 0.3.25 + terser: 5.44.1 + tsx: 4.21.0 + typescript: 5.5.4 + yaml: 2.9.0 - '@vitest/mocker@4.1.11(msw@2.13.6(@types/node@20.19.40)(typescript@5.9.3))(vite@8.1.5(@types/node@20.19.40)(esbuild@0.28.1)(jiti@2.6.1)(terser@5.44.1)(tsx@4.21.0)(yaml@2.9.0))': + '@voidzero-dev/vite-plus-core@1.1.0(@arethetypeswrong/core@0.18.5)(@types/node@20.19.40)(esbuild@0.28.1)(jiti@2.6.1)(publint@0.3.25)(terser@5.44.1)(tsx@4.21.0)(typescript@5.9.3)(yaml@2.9.0)': dependencies: - '@vitest/spy': 4.1.11 - estree-walker: 3.0.3 - magic-string: 0.30.21 + '@oxc-project/runtime': 0.153.0 + '@oxc-project/types': 0.153.0 + lightningcss: 1.33.0 + postcss: 8.5.29 + yuku-codegen: 0.9.5 + yuku-parser: 0.9.5 optionalDependencies: - msw: 2.13.6(@types/node@20.19.40)(typescript@5.9.3) - vite: 8.1.5(@types/node@20.19.40)(esbuild@0.28.1)(jiti@2.6.1)(terser@5.44.1)(tsx@4.21.0)(yaml@2.9.0) + '@arethetypeswrong/core': 0.18.5 + '@types/node': 20.19.40 + '@voidzero-dev/vite-plus-darwin-arm64': 1.1.0 + '@voidzero-dev/vite-plus-darwin-x64': 1.1.0 + '@voidzero-dev/vite-plus-linux-arm64-gnu': 1.1.0 + '@voidzero-dev/vite-plus-linux-arm64-musl': 1.1.0 + '@voidzero-dev/vite-plus-linux-x64-gnu': 1.1.0 + '@voidzero-dev/vite-plus-linux-x64-musl': 1.1.0 + '@voidzero-dev/vite-plus-win32-arm64-msvc': 1.1.0 + '@voidzero-dev/vite-plus-win32-x64-msvc': 1.1.0 + esbuild: 0.28.1 + fsevents: 2.3.3 + jiti: 2.6.1 + publint: 0.3.25 + terser: 5.44.1 + tsx: 4.21.0 + typescript: 5.9.3 + yaml: 2.9.0 - '@vitest/mocker@4.1.11(msw@2.13.6(@types/node@22.19.1)(typescript@5.5.4))(vite@8.1.5(@types/node@22.19.1)(esbuild@0.28.1)(jiti@2.6.1)(terser@5.44.1)(tsx@4.21.0)(yaml@2.9.0))': + '@voidzero-dev/vite-plus-core@1.1.0(@arethetypeswrong/core@0.18.5)(@types/node@22.19.1)(esbuild@0.28.1)(jiti@2.6.1)(publint@0.3.25)(terser@5.44.1)(tsx@4.21.0)(typescript@5.5.4)(yaml@2.9.0)': dependencies: - '@vitest/spy': 4.1.11 - estree-walker: 3.0.3 - magic-string: 0.30.21 + '@oxc-project/runtime': 0.153.0 + '@oxc-project/types': 0.153.0 + lightningcss: 1.33.0 + postcss: 8.5.29 + yuku-codegen: 0.9.5 + yuku-parser: 0.9.5 optionalDependencies: - msw: 2.13.6(@types/node@22.19.1)(typescript@5.5.4) - vite: 8.1.5(@types/node@22.19.1)(esbuild@0.28.1)(jiti@2.6.1)(terser@5.44.1)(tsx@4.21.0)(yaml@2.9.0) + '@arethetypeswrong/core': 0.18.5 + '@types/node': 22.19.1 + '@voidzero-dev/vite-plus-darwin-arm64': 1.1.0 + '@voidzero-dev/vite-plus-darwin-x64': 1.1.0 + '@voidzero-dev/vite-plus-linux-arm64-gnu': 1.1.0 + '@voidzero-dev/vite-plus-linux-arm64-musl': 1.1.0 + '@voidzero-dev/vite-plus-linux-x64-gnu': 1.1.0 + '@voidzero-dev/vite-plus-linux-x64-musl': 1.1.0 + '@voidzero-dev/vite-plus-win32-arm64-msvc': 1.1.0 + '@voidzero-dev/vite-plus-win32-x64-msvc': 1.1.0 + esbuild: 0.28.1 + fsevents: 2.3.3 + jiti: 2.6.1 + publint: 0.3.25 + terser: 5.44.1 + tsx: 4.21.0 + typescript: 5.5.4 + yaml: 2.9.0 - '@vitest/mocker@4.1.11(msw@2.13.6(@types/node@22.19.1)(typescript@5.9.3))(vite@8.1.5(@types/node@22.19.1)(esbuild@0.28.1)(jiti@2.6.1)(terser@5.44.1)(tsx@4.21.0)(yaml@2.9.0))': + '@voidzero-dev/vite-plus-core@1.1.0(@arethetypeswrong/core@0.18.5)(@types/node@22.19.1)(esbuild@0.28.1)(jiti@2.6.1)(publint@0.3.25)(terser@5.44.1)(tsx@4.21.0)(typescript@5.9.3)(yaml@2.9.0)': dependencies: - '@vitest/spy': 4.1.11 - estree-walker: 3.0.3 - magic-string: 0.30.21 + '@oxc-project/runtime': 0.153.0 + '@oxc-project/types': 0.153.0 + lightningcss: 1.33.0 + postcss: 8.5.29 + yuku-codegen: 0.9.5 + yuku-parser: 0.9.5 optionalDependencies: - msw: 2.13.6(@types/node@22.19.1)(typescript@5.9.3) - vite: 8.1.5(@types/node@22.19.1)(esbuild@0.28.1)(jiti@2.6.1)(terser@5.44.1)(tsx@4.21.0)(yaml@2.9.0) + '@arethetypeswrong/core': 0.18.5 + '@types/node': 22.19.1 + '@voidzero-dev/vite-plus-darwin-arm64': 1.1.0 + '@voidzero-dev/vite-plus-darwin-x64': 1.1.0 + '@voidzero-dev/vite-plus-linux-arm64-gnu': 1.1.0 + '@voidzero-dev/vite-plus-linux-arm64-musl': 1.1.0 + '@voidzero-dev/vite-plus-linux-x64-gnu': 1.1.0 + '@voidzero-dev/vite-plus-linux-x64-musl': 1.1.0 + '@voidzero-dev/vite-plus-win32-arm64-msvc': 1.1.0 + '@voidzero-dev/vite-plus-win32-x64-msvc': 1.1.0 + esbuild: 0.28.1 + fsevents: 2.3.3 + jiti: 2.6.1 + publint: 0.3.25 + terser: 5.44.1 + tsx: 4.21.0 + typescript: 5.9.3 + yaml: 2.9.0 - '@vitest/pretty-format@4.1.11': + '@voidzero-dev/vite-plus-core@1.1.0(@arethetypeswrong/core@0.18.5)(@types/node@24.12.4)(esbuild@0.28.1)(jiti@2.6.1)(publint@0.3.25)(terser@5.44.1)(tsx@4.21.0)(typescript@5.9.3)(yaml@2.9.0)': dependencies: - tinyrainbow: 3.1.1 + '@oxc-project/runtime': 0.153.0 + '@oxc-project/types': 0.153.0 + lightningcss: 1.33.0 + postcss: 8.5.29 + yuku-codegen: 0.9.5 + yuku-parser: 0.9.5 + optionalDependencies: + '@arethetypeswrong/core': 0.18.5 + '@types/node': 24.12.4 + '@voidzero-dev/vite-plus-darwin-arm64': 1.1.0 + '@voidzero-dev/vite-plus-darwin-x64': 1.1.0 + '@voidzero-dev/vite-plus-linux-arm64-gnu': 1.1.0 + '@voidzero-dev/vite-plus-linux-arm64-musl': 1.1.0 + '@voidzero-dev/vite-plus-linux-x64-gnu': 1.1.0 + '@voidzero-dev/vite-plus-linux-x64-musl': 1.1.0 + '@voidzero-dev/vite-plus-win32-arm64-msvc': 1.1.0 + '@voidzero-dev/vite-plus-win32-x64-msvc': 1.1.0 + esbuild: 0.28.1 + fsevents: 2.3.3 + jiti: 2.6.1 + publint: 0.3.25 + terser: 5.44.1 + tsx: 4.21.0 + typescript: 5.9.3 + yaml: 2.9.0 - '@vitest/runner@4.1.11': - dependencies: - '@vitest/utils': 4.1.11 - pathe: 2.0.3 + '@voidzero-dev/vite-plus-darwin-arm64@1.1.0': + optional: true - '@vitest/snapshot@4.1.11': - dependencies: - '@vitest/pretty-format': 4.1.11 - '@vitest/utils': 4.1.11 - magic-string: 0.30.21 - pathe: 2.0.3 + '@voidzero-dev/vite-plus-darwin-x64@1.1.0': + optional: true + + '@voidzero-dev/vite-plus-linux-arm64-gnu@1.1.0': + optional: true - '@vitest/spy@4.1.11': {} + '@voidzero-dev/vite-plus-linux-arm64-musl@1.1.0': + optional: true - '@vitest/utils@4.1.11': - dependencies: - '@vitest/pretty-format': 4.1.11 - convert-source-map: 2.0.0 - tinyrainbow: 3.1.1 + '@voidzero-dev/vite-plus-linux-x64-gnu@1.1.0': + optional: true + + '@voidzero-dev/vite-plus-linux-x64-musl@1.1.0': + optional: true + + '@voidzero-dev/vite-plus-win32-arm64-msvc@1.1.0': + optional: true + + '@voidzero-dev/vite-plus-win32-x64-msvc@1.1.0': + optional: true '@wasm-audio-decoders/common@9.0.7': dependencies: @@ -6275,17 +6149,86 @@ snapshots: '@xtuc/long@4.2.2': {} + '@yuku-codegen/binding-android-arm64@0.9.5': + optional: true + + '@yuku-codegen/binding-darwin-arm64@0.9.5': + optional: true + + '@yuku-codegen/binding-darwin-x64@0.9.5': + optional: true + + '@yuku-codegen/binding-freebsd-x64@0.9.5': + optional: true + + '@yuku-codegen/binding-linux-arm-gnu@0.9.5': + optional: true + + '@yuku-codegen/binding-linux-arm-musl@0.9.5': + optional: true + + '@yuku-codegen/binding-linux-arm64-gnu@0.9.5': + optional: true + + '@yuku-codegen/binding-linux-arm64-musl@0.9.5': + optional: true + + '@yuku-codegen/binding-linux-x64-gnu@0.9.5': + optional: true + + '@yuku-codegen/binding-linux-x64-musl@0.9.5': + optional: true + + '@yuku-codegen/binding-win32-arm64@0.9.5': + optional: true + + '@yuku-codegen/binding-win32-x64@0.9.5': + optional: true + + '@yuku-parser/binding-android-arm64@0.9.5': + optional: true + + '@yuku-parser/binding-darwin-arm64@0.9.5': + optional: true + + '@yuku-parser/binding-darwin-x64@0.9.5': + optional: true + + '@yuku-parser/binding-freebsd-x64@0.9.5': + optional: true + + '@yuku-parser/binding-linux-arm-gnu@0.9.5': + optional: true + + '@yuku-parser/binding-linux-arm-musl@0.9.5': + optional: true + + '@yuku-parser/binding-linux-arm64-gnu@0.9.5': + optional: true + + '@yuku-parser/binding-linux-arm64-musl@0.9.5': + optional: true + + '@yuku-parser/binding-linux-x64-gnu@0.9.5': + optional: true + + '@yuku-parser/binding-linux-x64-musl@0.9.5': + optional: true + + '@yuku-parser/binding-win32-arm64@0.9.5': + optional: true + + '@yuku-parser/binding-win32-x64@0.9.5': + optional: true + + '@yuku-toolchain/types@0.9.5': {} + a-sync-waterfall@1.0.1: {} abort-controller@3.0.0: dependencies: event-target-shim: 5.0.1 - accepts@2.0.0: - dependencies: - mime-types: 3.0.1 - negotiator: 1.0.0 - acorn-import-attributes@1.9.5(acorn@8.16.0): dependencies: acorn: 8.16.0 @@ -6312,11 +6255,6 @@ snapshots: optionalDependencies: ajv: 8.20.0 - ajv-formats@3.0.1(ajv@8.20.0): - optionalDependencies: - ajv: 8.20.0 - optional: true - ajv-keywords@5.1.0(ajv@8.20.0): dependencies: ajv: 8.20.0 @@ -6366,6 +6304,10 @@ snapshots: argparse@2.0.1: {} + aria-query@5.3.0: + dependencies: + dequal: 2.0.3 + array-union@2.1.0: {} asap@2.0.6: {} @@ -6378,30 +6320,12 @@ snapshots: balanced-match@1.0.2: {} - balanced-match@4.0.4: {} - - base64-js@1.5.1: {} - baseline-browser-mapping@2.11.21: {} better-path-resolve@1.0.0: dependencies: is-windows: 1.0.2 - body-parser@2.3.0: - dependencies: - bytes: 3.1.2 - content-type: 2.0.0 - debug: 4.4.3 - http-errors: 2.0.1 - iconv-lite: 0.7.2 - on-finished: 2.4.1 - qs: 6.16.0 - raw-body: 3.0.2 - type-is: 2.1.0 - transitivePeerDependencies: - - supports-color - brace-expansion@1.1.21: dependencies: balanced-match: 1.0.2 @@ -6411,10 +6335,6 @@ snapshots: dependencies: balanced-match: 1.0.2 - brace-expansion@5.0.12: - dependencies: - balanced-match: 4.0.4 - braces@3.0.3: dependencies: fill-range: 7.1.1 @@ -6434,24 +6354,10 @@ snapshots: esbuild: 0.28.1 load-tsconfig: 0.2.5 - bytes@3.1.2: {} - cac@6.7.14: {} - call-bind-apply-helpers@1.0.2: - dependencies: - es-errors: 1.3.0 - function-bind: 1.1.2 - - call-bound@1.0.4: - dependencies: - call-bind-apply-helpers: 1.0.2 - get-intrinsic: 1.3.0 - callsites@3.1.0: {} - camelcase@6.3.0: {} - caniuse-lite@1.0.30001810: {} chai@6.2.2: {} @@ -6473,28 +6379,22 @@ snapshots: chrome-trace-event@1.0.4: {} + cjs-module-lexer@1.4.3: + optional: true + cjs-module-lexer@2.2.0: {} cli-cursor@5.0.0: dependencies: restore-cursor: 5.1.0 - cli-progress@3.12.0: - dependencies: - string-width: 4.2.3 - - cli-table3@0.6.5: - dependencies: - string-width: 4.2.3 - optionalDependencies: - '@colors/colors': 1.5.0 - cli-truncate@5.1.1: dependencies: slice-ansi: 7.1.2 string-width: 8.2.0 - cli-width@4.1.0: {} + cli-width@4.1.0: + optional: true cliui@7.0.4: dependencies: @@ -6530,28 +6430,12 @@ snapshots: confbox@0.1.8: {} - consola@3.4.0: {} - - content-disposition@1.0.0: - dependencies: - safe-buffer: 5.2.1 - - content-type@1.0.5: {} - - content-type@2.0.0: {} + consola@3.4.2: {} convert-source-map@2.0.0: {} - cookie-signature@1.2.2: {} - - cookie@0.7.2: {} - - cookie@1.1.1: {} - - cors@2.8.5: - dependencies: - object-assign: 4.1.1 - vary: 1.1.2 + cookie@1.1.1: + optional: true cross-env@7.0.3: dependencies: @@ -6571,11 +6455,9 @@ snapshots: dependencies: ms: 2.1.3 - decamelize@1.2.0: {} - deep-is@0.1.4: {} - depd@2.0.0: {} + dequal@2.0.3: {} detect-indent@6.1.0: {} @@ -6585,30 +6467,18 @@ snapshots: dependencies: path-type: 4.0.0 + dom-accessibility-api@0.5.16: {} + dotenv@16.4.5: {} dotenv@17.3.1: {} - dunder-proto@1.0.1: - dependencies: - call-bind-apply-helpers: 1.0.2 - es-errors: 1.3.0 - gopd: 1.2.0 - - eastasianwidth@0.2.0: {} - - ee-first@1.1.1: {} - electron-to-chromium@1.5.422: {} emoji-regex@10.6.0: {} emoji-regex@8.0.0: {} - emoji-regex@9.2.2: {} - - encodeurl@2.0.0: {} - encoding@0.1.13: dependencies: iconv-lite: 0.6.3 @@ -6626,16 +6496,8 @@ snapshots: environment@1.1.0: {} - es-define-property@1.0.1: {} - - es-errors@1.3.0: {} - es-module-lexer@2.3.2: {} - es-object-atoms@1.1.1: - dependencies: - es-errors: 1.3.0 - esbuild@0.28.1: optionalDependencies: '@esbuild/aix-ppc64': 0.28.1 @@ -6667,8 +6529,6 @@ snapshots: escalade@3.2.0: {} - escape-html@1.0.3: {} - escape-string-regexp@4.0.0: {} eslint-compat-utils@0.5.1(eslint@9.39.4(jiti@2.6.1)): @@ -6683,13 +6543,6 @@ snapshots: eslint: 9.39.4(jiti@2.6.1) eslint-compat-utils: 0.5.1(eslint@9.39.4(jiti@2.6.1)) - eslint-plugin-es-x@9.7.0(eslint@9.39.4(jiti@2.6.1)): - dependencies: - '@eslint-community/eslint-utils': 4.9.1(eslint@9.39.4(jiti@2.6.1)) - '@eslint-community/regexpp': 4.12.2 - eslint: 9.39.4(jiti@2.6.1) - eslint-type-tracer: 0.5.2(eslint@9.39.4(jiti@2.6.1)) - eslint-plugin-n@18.2.1(eslint@9.39.4(jiti@2.6.1))(ts-declaration-location@1.0.7(typescript@5.4.4))(typescript@5.4.4): dependencies: '@eslint-community/eslint-utils': 4.9.1(eslint@9.39.4(jiti@2.6.1)) @@ -6705,10 +6558,6 @@ snapshots: ts-declaration-location: 1.0.7(typescript@5.4.4) typescript: 5.4.4 - eslint-plugin-node-import@1.0.5(eslint@9.39.4(jiti@2.6.1)): - dependencies: - eslint: 9.39.4(jiti@2.6.1) - eslint-scope@5.1.1: dependencies: esrecurse: 4.3.0 @@ -6719,11 +6568,6 @@ snapshots: esrecurse: 4.3.0 estraverse: 5.3.0 - eslint-type-tracer@0.5.2(eslint@9.39.4(jiti@2.6.1)): - dependencies: - '@eslint-community/eslint-utils': 4.9.1(eslint@9.39.4(jiti@2.6.1)) - eslint: 9.39.4(jiti@2.6.1) - eslint-visitor-keys@3.4.3: {} eslint-visitor-keys@4.2.1: {} @@ -6795,12 +6639,8 @@ snapshots: esutils@2.0.3: {} - etag@1.8.1: {} - event-target-shim@5.0.1: {} - eventemitter3@4.0.7: {} - eventemitter3@5.0.4: {} events@3.3.0: {} @@ -6809,52 +6649,8 @@ snapshots: eventsource-parser@3.1.1: {} - eventsource@3.0.7: - dependencies: - eventsource-parser: 3.1.1 - optional: true - expect-type@1.4.0: {} - express-rate-limit@8.3.2(express@5.2.1): - dependencies: - express: 5.2.1 - ip-address: 10.7.3 - optional: true - - express@5.2.1: - dependencies: - accepts: 2.0.0 - body-parser: 2.3.0 - content-disposition: 1.0.0 - content-type: 1.0.5 - cookie: 0.7.2 - cookie-signature: 1.2.2 - debug: 4.4.3 - depd: 2.0.0 - encodeurl: 2.0.0 - escape-html: 1.0.3 - etag: 1.8.1 - finalhandler: 2.1.0 - fresh: 2.0.0 - http-errors: 2.0.1 - merge-descriptors: 2.0.0 - mime-types: 3.0.1 - on-finished: 2.4.1 - once: 1.4.0 - parseurl: 1.3.3 - proxy-addr: 2.0.8 - qs: 6.16.0 - range-parser: 1.2.1 - router: 2.2.0 - send: 1.2.0 - serve-static: 2.2.0 - statuses: 2.0.2 - type-is: 2.1.0 - vary: 1.1.2 - transitivePeerDependencies: - - supports-color - extendable-error@0.1.7: {} fast-deep-equal@3.1.3: {} @@ -6895,6 +6691,8 @@ snapshots: optionalDependencies: picomatch: 4.0.7 + fflate@0.8.3: {} + file-entry-cache@8.0.0: dependencies: flat-cache: 4.0.1 @@ -6903,17 +6701,6 @@ snapshots: dependencies: to-regex-range: 5.0.1 - finalhandler@2.1.0: - dependencies: - debug: 4.4.3 - encodeurl: 2.0.0 - escape-html: 1.0.3 - on-finished: 2.4.1 - parseurl: 1.3.3 - statuses: 2.0.2 - transitivePeerDependencies: - - supports-color - find-up@4.1.0: dependencies: locate-path: 5.0.0 @@ -6927,29 +6714,20 @@ snapshots: fix-dts-default-cjs-exports@1.0.1: dependencies: magic-string: 0.30.21 - mlly: 1.8.0 + mlly: 1.8.2 rollup: 4.60.3 flat-cache@4.0.1: dependencies: - flatted: 3.4.2 + flatted: 3.4.4 keyv: 4.5.4 - flatted@3.4.2: {} - - foreground-child@3.3.1: - dependencies: - cross-spawn: 7.0.6 - signal-exit: 4.1.0 + flatted@3.4.4: {} formatly@0.3.0: dependencies: fd-package-json: 2.0.0 - forwarded@0.2.0: {} - - fresh@2.0.0: {} - fs-extra@7.0.1: dependencies: graceful-fs: 4.2.11 @@ -6969,30 +6747,10 @@ snapshots: fsevents@2.3.3: optional: true - function-bind@1.1.2: {} - get-caller-file@2.0.5: {} get-east-asian-width@1.5.0: {} - get-intrinsic@1.3.0: - dependencies: - call-bind-apply-helpers: 1.0.2 - es-define-property: 1.0.1 - es-errors: 1.3.0 - es-object-atoms: 1.1.1 - function-bind: 1.1.2 - get-proto: 1.0.1 - gopd: 1.2.0 - has-symbols: 1.1.0 - hasown: 2.0.3 - math-intrinsics: 1.1.0 - - get-proto@1.0.1: - dependencies: - dunder-proto: 1.0.1 - es-object-atoms: 1.1.1 - get-tsconfig@4.13.0: dependencies: resolve-pkg-maps: 1.0.0 @@ -7011,15 +6769,6 @@ snapshots: glob-to-regexp@0.4.1: {} - glob@10.5.0: - dependencies: - foreground-child: 3.3.1 - jackspeak: 3.4.3 - minimatch: 9.0.9 - minipass: 7.1.3 - package-json-from-dist: 1.0.1 - path-scurry: 1.11.1 - glob@7.2.3: dependencies: fs.realpath: 1.0.0 @@ -7044,11 +6793,10 @@ snapshots: globrex@0.1.2: {} - gopd@1.2.0: {} - graceful-fs@4.2.11: {} - graphql@16.13.2: {} + graphql@16.13.2: + optional: true handlebars@4.7.10: dependencies: @@ -7059,31 +6807,15 @@ snapshots: optionalDependencies: uglify-js: 3.19.3 - has-flag@4.0.0: {} - - has-symbols@1.1.0: {} - - hasown@2.0.3: - dependencies: - function-bind: 1.1.2 + has-flag@4.0.0: {} headers-polyfill@5.0.1: dependencies: '@types/set-cookie-parser': 2.4.10 set-cookie-parser: 3.1.0 - - heap-js@2.7.1: {} - - hono@4.13.13: optional: true - http-errors@2.0.1: - dependencies: - depd: 2.0.0 - inherits: 2.0.4 - setprototypeof: 1.2.0 - statuses: 2.0.2 - toidentifier: 1.0.1 + heap-js@2.7.1: {} human-id@4.1.3: {} @@ -7105,8 +6837,6 @@ snapshots: ignore@5.3.2: {} - ignore@7.0.5: {} - import-fresh@3.3.1: dependencies: parent-module: 1.0.1 @@ -7123,11 +6853,6 @@ snapshots: ini@2.0.0: {} - ip-address@10.7.3: - optional: true - - ipaddr.js@1.9.1: {} - is-extglob@2.1.1: {} is-fullwidth-code-point@3.0.0: {} @@ -7140,12 +6865,11 @@ snapshots: dependencies: is-extglob: 2.1.1 - is-node-process@1.2.0: {} + is-node-process@1.2.0: + optional: true is-number@7.0.0: {} - is-promise@4.0.0: {} - is-subdir@1.2.0: dependencies: better-path-resolve: 1.0.0 @@ -7154,12 +6878,6 @@ snapshots: isexe@2.0.0: {} - jackspeak@3.4.3: - dependencies: - '@isaacs/cliui': 8.0.2 - optionalDependencies: - '@pkgjs/parseargs': 0.11.0 - jest-worker@27.5.1: dependencies: '@types/node': 22.19.1 @@ -7168,14 +6886,9 @@ snapshots: jiti@2.6.1: {} - jose@6.2.2: - optional: true - joycon@3.1.1: {} - js-tiktoken@1.0.21: - dependencies: - base64-js: 1.5.1 + js-tokens@4.0.0: {} js-yaml@3.15.2: dependencies: @@ -7188,18 +6901,10 @@ snapshots: json-buffer@3.0.1: {} - json-schema-to-ts@3.1.1: - dependencies: - '@babel/runtime': 7.28.4 - ts-algebra: 2.0.0 - json-schema-traverse@0.4.1: {} json-schema-traverse@1.0.0: {} - json-schema-typed@8.0.2: - optional: true - json-schema@0.4.0: {} json-stable-stringify-without-jsonify@1.0.1: {} @@ -7234,70 +6939,61 @@ snapshots: - '@emnapi/core' - '@emnapi/runtime' - langsmith@0.7.1(@opentelemetry/api@1.9.0)(@opentelemetry/sdk-trace-base@2.6.1(@opentelemetry/api@1.9.0))(openai@6.32.0(ws@8.21.3)(zod@3.25.76))(ws@8.21.3): - dependencies: - p-queue: 6.6.2 - optionalDependencies: - '@opentelemetry/api': 1.9.0 - '@opentelemetry/sdk-trace-base': 2.6.1(@opentelemetry/api@1.9.0) - openai: 6.32.0(ws@8.21.3)(zod@3.25.76) - ws: 8.21.3 - levn@0.4.1: dependencies: prelude-ls: 1.2.1 type-check: 0.4.0 - lightningcss-android-arm64@1.32.0: + lightningcss-android-arm64@1.33.0: optional: true - lightningcss-darwin-arm64@1.32.0: + lightningcss-darwin-arm64@1.33.0: optional: true - lightningcss-darwin-x64@1.32.0: + lightningcss-darwin-x64@1.33.0: optional: true - lightningcss-freebsd-x64@1.32.0: + lightningcss-freebsd-x64@1.33.0: optional: true - lightningcss-linux-arm-gnueabihf@1.32.0: + lightningcss-linux-arm-gnueabihf@1.33.0: optional: true - lightningcss-linux-arm64-gnu@1.32.0: + lightningcss-linux-arm64-gnu@1.33.0: optional: true - lightningcss-linux-arm64-musl@1.32.0: + lightningcss-linux-arm64-musl@1.33.0: optional: true - lightningcss-linux-x64-gnu@1.32.0: + lightningcss-linux-x64-gnu@1.33.0: optional: true - lightningcss-linux-x64-musl@1.32.0: + lightningcss-linux-x64-musl@1.33.0: optional: true - lightningcss-win32-arm64-msvc@1.32.0: + lightningcss-win32-arm64-msvc@1.33.0: optional: true - lightningcss-win32-x64-msvc@1.32.0: + lightningcss-win32-x64-msvc@1.33.0: optional: true - lightningcss@1.32.0: + lightningcss@1.33.0: dependencies: detect-libc: 2.1.2 optionalDependencies: - lightningcss-android-arm64: 1.32.0 - lightningcss-darwin-arm64: 1.32.0 - lightningcss-darwin-x64: 1.32.0 - lightningcss-freebsd-x64: 1.32.0 - lightningcss-linux-arm-gnueabihf: 1.32.0 - lightningcss-linux-arm64-gnu: 1.32.0 - lightningcss-linux-arm64-musl: 1.32.0 - lightningcss-linux-x64-gnu: 1.32.0 - lightningcss-linux-x64-musl: 1.32.0 - lightningcss-win32-arm64-msvc: 1.32.0 - lightningcss-win32-x64-msvc: 1.32.0 - - lilconfig@3.1.3: {} + lightningcss-android-arm64: 1.33.0 + lightningcss-darwin-arm64: 1.33.0 + lightningcss-darwin-x64: 1.33.0 + lightningcss-freebsd-x64: 1.33.0 + lightningcss-linux-arm-gnueabihf: 1.33.0 + lightningcss-linux-arm64-gnu: 1.33.0 + lightningcss-linux-arm64-musl: 1.33.0 + lightningcss-linux-x64-gnu: 1.33.0 + lightningcss-linux-x64-musl: 1.33.0 + lightningcss-win32-arm64-msvc: 1.33.0 + lightningcss-win32-x64-msvc: 1.33.0 + + lilconfig@3.1.4: {} lines-and-columns@1.2.4: {} @@ -7348,19 +7044,22 @@ snapshots: long@5.3.2: {} - lru-cache@10.4.3: {} + lru-cache@11.5.3: + optional: true lunr@2.3.9: {} + lz-string@1.5.0: {} + magic-string@0.30.21: dependencies: '@jridgewell/sourcemap-codec': 1.6.0 - marked@4.3.0: {} - - math-intrinsics@1.1.0: {} + magic-string@1.4.3: + dependencies: + '@jridgewell/sourcemap-codec': 1.6.0 - media-typer@1.1.0: {} + marked@4.3.0: {} memfs@4.51.1: dependencies: @@ -7371,8 +7070,6 @@ snapshots: tree-dump: 1.1.0(tslib@2.8.1) tslib: 2.8.1 - merge-descriptors@2.0.0: {} - merge-stream@2.0.0: {} merge2@1.4.1: {} @@ -7386,16 +7083,8 @@ snapshots: mime-db@1.54.0: {} - mime-types@3.0.1: - dependencies: - mime-db: 1.54.0 - mimic-function@5.0.1: {} - minimatch@10.2.5: - dependencies: - brace-expansion: 5.0.12 - minimatch@3.1.5: dependencies: brace-expansion: 1.1.21 @@ -7412,17 +7101,19 @@ snapshots: dependencies: minipass: 7.1.3 - mlly@1.8.0: + mlly@1.8.2: dependencies: acorn: 8.16.0 pathe: 2.0.3 pkg-types: 1.3.1 - ufo: 1.6.1 + ufo: 1.6.4 module-details-from-path@1.0.4: {} mri@1.2.0: {} + mrmime@2.0.1: {} + ms@2.1.3: {} ms@3.0.0-canary.1: {} @@ -7503,6 +7194,7 @@ snapshots: typescript: 5.9.3 transitivePeerDependencies: - '@types/node' + optional: true msw@2.13.6(@types/node@22.19.1)(typescript@5.5.4): dependencies: @@ -7556,9 +7248,36 @@ snapshots: - '@types/node' optional: true + msw@2.13.6(@types/node@24.12.4)(typescript@5.9.3): + dependencies: + '@inquirer/confirm': 6.0.12(@types/node@24.12.4) + '@mswjs/interceptors': 0.41.6 + '@open-draft/deferred-promise': 3.0.0 + '@types/statuses': 2.0.6 + cookie: 1.1.1 + graphql: 16.13.2 + headers-polyfill: 5.0.1 + is-node-process: 1.2.0 + outvariant: 1.4.3 + path-to-regexp: 6.3.0 + picocolors: 1.1.1 + rettime: 0.11.8 + statuses: 2.0.2 + strict-event-emitter: 0.5.1 + tough-cookie: 6.0.1 + type-fest: 5.6.0 + until-async: 3.0.2 + yargs: 17.7.2 + optionalDependencies: + typescript: 5.9.3 + transitivePeerDependencies: + - '@types/node' + optional: true + mustache@4.2.0: {} - mute-stream@3.0.0: {} + mute-stream@3.0.0: + optional: true mz@2.7.0: dependencies: @@ -7568,12 +7287,10 @@ snapshots: nano-spawn@2.0.0: {} - nanoid@3.3.18: {} + nanoid@3.3.20: {} natural-compare@1.4.0: {} - negotiator@1.0.0: {} - neo-async@2.6.2: {} nexus-rpc@0.0.1: {} @@ -7607,8 +7324,6 @@ snapshots: object-assign@4.1.1: {} - object-inspect@1.13.4: {} - obug@2.2.1: {} ogg-opus-decoder@1.7.5: @@ -7618,10 +7333,6 @@ snapshots: codec-parser: 2.5.0 opus-decoder: 0.7.12 - on-finished@2.4.1: - dependencies: - ee-first: 1.1.1 - once@1.4.0: dependencies: wrappy: 1.0.2 @@ -7630,22 +7341,11 @@ snapshots: dependencies: mimic-function: 5.0.1 - openai@5.11.0(ws@8.21.3)(zod@3.25.76): - optionalDependencies: - ws: 8.21.3 - zod: 3.25.76 - openai@6.25.0(ws@8.21.3)(zod@3.25.76): optionalDependencies: ws: 8.21.3 zod: 3.25.76 - openai@6.32.0(ws@8.21.3)(zod@3.25.76): - optionalDependencies: - ws: 8.21.3 - zod: 3.25.76 - optional: true - optionator@0.9.4: dependencies: deep-is: 0.1.4 @@ -7661,7 +7361,8 @@ snapshots: outdent@0.5.0: {} - outvariant@1.4.3: {} + outvariant@1.4.3: + optional: true oxc-resolver@11.19.1(@emnapi/core@1.11.1)(@emnapi/runtime@1.11.1): optionalDependencies: @@ -7689,12 +7390,313 @@ snapshots: - '@emnapi/core' - '@emnapi/runtime' + oxfmt@0.72.0(vite-plus@1.1.0(@arethetypeswrong/core@0.18.5)(@opentelemetry/api@1.9.0)(@types/node@20.19.40)(@vitest/ui@5.0.3(vitest@5.0.3))(esbuild@0.28.1)(jiti@2.6.1)(msw@2.13.6(@types/node@20.19.40)(typescript@5.4.4))(publint@0.3.25)(terser@5.44.1)(tsx@4.21.0)(typescript@5.4.4)(yaml@2.9.0)): + dependencies: + tinypool: 2.2.0 + optionalDependencies: + '@oxfmt/binding-android-arm-eabi': 0.72.0 + '@oxfmt/binding-android-arm64': 0.72.0 + '@oxfmt/binding-darwin-arm64': 0.72.0 + '@oxfmt/binding-darwin-x64': 0.72.0 + '@oxfmt/binding-freebsd-x64': 0.72.0 + '@oxfmt/binding-linux-arm-gnueabihf': 0.72.0 + '@oxfmt/binding-linux-arm-musleabihf': 0.72.0 + '@oxfmt/binding-linux-arm64-gnu': 0.72.0 + '@oxfmt/binding-linux-arm64-musl': 0.72.0 + '@oxfmt/binding-linux-ppc64-gnu': 0.72.0 + '@oxfmt/binding-linux-riscv64-gnu': 0.72.0 + '@oxfmt/binding-linux-riscv64-musl': 0.72.0 + '@oxfmt/binding-linux-s390x-gnu': 0.72.0 + '@oxfmt/binding-linux-x64-gnu': 0.72.0 + '@oxfmt/binding-linux-x64-musl': 0.72.0 + '@oxfmt/binding-openharmony-arm64': 0.72.0 + '@oxfmt/binding-win32-arm64-msvc': 0.72.0 + '@oxfmt/binding-win32-ia32-msvc': 0.72.0 + '@oxfmt/binding-win32-x64-msvc': 0.72.0 + vite-plus: 1.1.0(@arethetypeswrong/core@0.18.5)(@opentelemetry/api@1.9.0)(@types/node@20.19.40)(@vitest/ui@5.0.3(vitest@5.0.3))(esbuild@0.28.1)(jiti@2.6.1)(msw@2.13.6(@types/node@20.19.40)(typescript@5.4.4))(publint@0.3.25)(terser@5.44.1)(tsx@4.21.0)(typescript@5.4.4)(yaml@2.9.0) + + oxfmt@0.72.0(vite-plus@1.1.0(@arethetypeswrong/core@0.18.5)(@opentelemetry/api@1.9.0)(@types/node@20.19.40)(@vitest/ui@5.0.3(vitest@5.0.3))(esbuild@0.28.1)(jiti@2.6.1)(msw@2.13.6(@types/node@20.19.40)(typescript@5.5.4))(publint@0.3.25)(terser@5.44.1)(tsx@4.21.0)(typescript@5.5.4)(yaml@2.9.0)): + dependencies: + tinypool: 2.2.0 + optionalDependencies: + '@oxfmt/binding-android-arm-eabi': 0.72.0 + '@oxfmt/binding-android-arm64': 0.72.0 + '@oxfmt/binding-darwin-arm64': 0.72.0 + '@oxfmt/binding-darwin-x64': 0.72.0 + '@oxfmt/binding-freebsd-x64': 0.72.0 + '@oxfmt/binding-linux-arm-gnueabihf': 0.72.0 + '@oxfmt/binding-linux-arm-musleabihf': 0.72.0 + '@oxfmt/binding-linux-arm64-gnu': 0.72.0 + '@oxfmt/binding-linux-arm64-musl': 0.72.0 + '@oxfmt/binding-linux-ppc64-gnu': 0.72.0 + '@oxfmt/binding-linux-riscv64-gnu': 0.72.0 + '@oxfmt/binding-linux-riscv64-musl': 0.72.0 + '@oxfmt/binding-linux-s390x-gnu': 0.72.0 + '@oxfmt/binding-linux-x64-gnu': 0.72.0 + '@oxfmt/binding-linux-x64-musl': 0.72.0 + '@oxfmt/binding-openharmony-arm64': 0.72.0 + '@oxfmt/binding-win32-arm64-msvc': 0.72.0 + '@oxfmt/binding-win32-ia32-msvc': 0.72.0 + '@oxfmt/binding-win32-x64-msvc': 0.72.0 + vite-plus: 1.1.0(@arethetypeswrong/core@0.18.5)(@opentelemetry/api@1.9.0)(@types/node@20.19.40)(@vitest/ui@5.0.3(vitest@5.0.3))(esbuild@0.28.1)(jiti@2.6.1)(msw@2.13.6(@types/node@20.19.40)(typescript@5.5.4))(publint@0.3.25)(terser@5.44.1)(tsx@4.21.0)(typescript@5.5.4)(yaml@2.9.0) + + oxfmt@0.72.0(vite-plus@1.1.0(@arethetypeswrong/core@0.18.5)(@opentelemetry/api@1.9.0)(@types/node@20.19.40)(@vitest/ui@5.0.3(vitest@5.0.3))(esbuild@0.28.1)(jiti@2.6.1)(msw@2.13.6(@types/node@20.19.40)(typescript@5.9.3))(publint@0.3.25)(terser@5.44.1)(tsx@4.21.0)(typescript@5.9.3)(yaml@2.9.0)): + dependencies: + tinypool: 2.2.0 + optionalDependencies: + '@oxfmt/binding-android-arm-eabi': 0.72.0 + '@oxfmt/binding-android-arm64': 0.72.0 + '@oxfmt/binding-darwin-arm64': 0.72.0 + '@oxfmt/binding-darwin-x64': 0.72.0 + '@oxfmt/binding-freebsd-x64': 0.72.0 + '@oxfmt/binding-linux-arm-gnueabihf': 0.72.0 + '@oxfmt/binding-linux-arm-musleabihf': 0.72.0 + '@oxfmt/binding-linux-arm64-gnu': 0.72.0 + '@oxfmt/binding-linux-arm64-musl': 0.72.0 + '@oxfmt/binding-linux-ppc64-gnu': 0.72.0 + '@oxfmt/binding-linux-riscv64-gnu': 0.72.0 + '@oxfmt/binding-linux-riscv64-musl': 0.72.0 + '@oxfmt/binding-linux-s390x-gnu': 0.72.0 + '@oxfmt/binding-linux-x64-gnu': 0.72.0 + '@oxfmt/binding-linux-x64-musl': 0.72.0 + '@oxfmt/binding-openharmony-arm64': 0.72.0 + '@oxfmt/binding-win32-arm64-msvc': 0.72.0 + '@oxfmt/binding-win32-ia32-msvc': 0.72.0 + '@oxfmt/binding-win32-x64-msvc': 0.72.0 + vite-plus: 1.1.0(@arethetypeswrong/core@0.18.5)(@opentelemetry/api@1.9.0)(@types/node@20.19.40)(@vitest/ui@5.0.3(vitest@5.0.3))(esbuild@0.28.1)(jiti@2.6.1)(msw@2.13.6(@types/node@20.19.40)(typescript@5.9.3))(publint@0.3.25)(terser@5.44.1)(tsx@4.21.0)(typescript@5.9.3)(yaml@2.9.0) + + oxfmt@0.72.0(vite-plus@1.1.0(@arethetypeswrong/core@0.18.5)(@opentelemetry/api@1.9.0)(@types/node@22.19.1)(@vitest/ui@5.0.3(vitest@5.0.3))(esbuild@0.28.1)(jiti@2.6.1)(msw@2.13.6(@types/node@22.19.1)(typescript@5.5.4))(publint@0.3.25)(terser@5.44.1)(tsx@4.21.0)(typescript@5.5.4)(yaml@2.9.0)): + dependencies: + tinypool: 2.2.0 + optionalDependencies: + '@oxfmt/binding-android-arm-eabi': 0.72.0 + '@oxfmt/binding-android-arm64': 0.72.0 + '@oxfmt/binding-darwin-arm64': 0.72.0 + '@oxfmt/binding-darwin-x64': 0.72.0 + '@oxfmt/binding-freebsd-x64': 0.72.0 + '@oxfmt/binding-linux-arm-gnueabihf': 0.72.0 + '@oxfmt/binding-linux-arm-musleabihf': 0.72.0 + '@oxfmt/binding-linux-arm64-gnu': 0.72.0 + '@oxfmt/binding-linux-arm64-musl': 0.72.0 + '@oxfmt/binding-linux-ppc64-gnu': 0.72.0 + '@oxfmt/binding-linux-riscv64-gnu': 0.72.0 + '@oxfmt/binding-linux-riscv64-musl': 0.72.0 + '@oxfmt/binding-linux-s390x-gnu': 0.72.0 + '@oxfmt/binding-linux-x64-gnu': 0.72.0 + '@oxfmt/binding-linux-x64-musl': 0.72.0 + '@oxfmt/binding-openharmony-arm64': 0.72.0 + '@oxfmt/binding-win32-arm64-msvc': 0.72.0 + '@oxfmt/binding-win32-ia32-msvc': 0.72.0 + '@oxfmt/binding-win32-x64-msvc': 0.72.0 + vite-plus: 1.1.0(@arethetypeswrong/core@0.18.5)(@opentelemetry/api@1.9.0)(@types/node@22.19.1)(@vitest/ui@5.0.3(vitest@5.0.3))(esbuild@0.28.1)(jiti@2.6.1)(msw@2.13.6(@types/node@22.19.1)(typescript@5.5.4))(publint@0.3.25)(terser@5.44.1)(tsx@4.21.0)(typescript@5.5.4)(yaml@2.9.0) + + oxfmt@0.72.0(vite-plus@1.1.0(@arethetypeswrong/core@0.18.5)(@opentelemetry/api@1.9.0)(@types/node@22.19.1)(@vitest/ui@5.0.3(vitest@5.0.3))(esbuild@0.28.1)(jiti@2.6.1)(msw@2.13.6(@types/node@22.19.1)(typescript@5.9.3))(publint@0.3.25)(terser@5.44.1)(tsx@4.21.0)(typescript@5.9.3)(yaml@2.9.0)): + dependencies: + tinypool: 2.2.0 + optionalDependencies: + '@oxfmt/binding-android-arm-eabi': 0.72.0 + '@oxfmt/binding-android-arm64': 0.72.0 + '@oxfmt/binding-darwin-arm64': 0.72.0 + '@oxfmt/binding-darwin-x64': 0.72.0 + '@oxfmt/binding-freebsd-x64': 0.72.0 + '@oxfmt/binding-linux-arm-gnueabihf': 0.72.0 + '@oxfmt/binding-linux-arm-musleabihf': 0.72.0 + '@oxfmt/binding-linux-arm64-gnu': 0.72.0 + '@oxfmt/binding-linux-arm64-musl': 0.72.0 + '@oxfmt/binding-linux-ppc64-gnu': 0.72.0 + '@oxfmt/binding-linux-riscv64-gnu': 0.72.0 + '@oxfmt/binding-linux-riscv64-musl': 0.72.0 + '@oxfmt/binding-linux-s390x-gnu': 0.72.0 + '@oxfmt/binding-linux-x64-gnu': 0.72.0 + '@oxfmt/binding-linux-x64-musl': 0.72.0 + '@oxfmt/binding-openharmony-arm64': 0.72.0 + '@oxfmt/binding-win32-arm64-msvc': 0.72.0 + '@oxfmt/binding-win32-ia32-msvc': 0.72.0 + '@oxfmt/binding-win32-x64-msvc': 0.72.0 + vite-plus: 1.1.0(@arethetypeswrong/core@0.18.5)(@opentelemetry/api@1.9.0)(@types/node@22.19.1)(@vitest/ui@5.0.3(vitest@5.0.3))(esbuild@0.28.1)(jiti@2.6.1)(msw@2.13.6(@types/node@22.19.1)(typescript@5.9.3))(publint@0.3.25)(terser@5.44.1)(tsx@4.21.0)(typescript@5.9.3)(yaml@2.9.0) + + oxfmt@0.72.0(vite-plus@1.1.0(@arethetypeswrong/core@0.18.5)(@opentelemetry/api@1.9.0)(@types/node@24.12.4)(@vitest/ui@5.0.3(vitest@5.0.3))(esbuild@0.28.1)(jiti@2.6.1)(msw@2.13.6(@types/node@24.12.4)(typescript@5.9.3))(publint@0.3.25)(terser@5.44.1)(tsx@4.21.0)(typescript@5.9.3)(yaml@2.9.0)): + dependencies: + tinypool: 2.2.0 + optionalDependencies: + '@oxfmt/binding-android-arm-eabi': 0.72.0 + '@oxfmt/binding-android-arm64': 0.72.0 + '@oxfmt/binding-darwin-arm64': 0.72.0 + '@oxfmt/binding-darwin-x64': 0.72.0 + '@oxfmt/binding-freebsd-x64': 0.72.0 + '@oxfmt/binding-linux-arm-gnueabihf': 0.72.0 + '@oxfmt/binding-linux-arm-musleabihf': 0.72.0 + '@oxfmt/binding-linux-arm64-gnu': 0.72.0 + '@oxfmt/binding-linux-arm64-musl': 0.72.0 + '@oxfmt/binding-linux-ppc64-gnu': 0.72.0 + '@oxfmt/binding-linux-riscv64-gnu': 0.72.0 + '@oxfmt/binding-linux-riscv64-musl': 0.72.0 + '@oxfmt/binding-linux-s390x-gnu': 0.72.0 + '@oxfmt/binding-linux-x64-gnu': 0.72.0 + '@oxfmt/binding-linux-x64-musl': 0.72.0 + '@oxfmt/binding-openharmony-arm64': 0.72.0 + '@oxfmt/binding-win32-arm64-msvc': 0.72.0 + '@oxfmt/binding-win32-ia32-msvc': 0.72.0 + '@oxfmt/binding-win32-x64-msvc': 0.72.0 + vite-plus: 1.1.0(@arethetypeswrong/core@0.18.5)(@opentelemetry/api@1.9.0)(@types/node@24.12.4)(@vitest/ui@5.0.3(vitest@5.0.3))(esbuild@0.28.1)(jiti@2.6.1)(msw@2.13.6(@types/node@24.12.4)(typescript@5.9.3))(publint@0.3.25)(terser@5.44.1)(tsx@4.21.0)(typescript@5.9.3)(yaml@2.9.0) + + oxlint-tsgolint@7.0.2003: + optionalDependencies: + '@oxlint-tsgolint/darwin-arm64': 7.0.2003 + '@oxlint-tsgolint/darwin-x64': 7.0.2003 + '@oxlint-tsgolint/linux-arm64': 7.0.2003 + '@oxlint-tsgolint/linux-x64': 7.0.2003 + '@oxlint-tsgolint/win32-arm64': 7.0.2003 + '@oxlint-tsgolint/win32-x64': 7.0.2003 + + oxlint@1.87.0(oxlint-tsgolint@7.0.2003)(vite-plus@1.1.0(@arethetypeswrong/core@0.18.5)(@opentelemetry/api@1.9.0)(@types/node@20.19.40)(@vitest/ui@5.0.3(vitest@5.0.3))(esbuild@0.28.1)(jiti@2.6.1)(msw@2.13.6(@types/node@20.19.40)(typescript@5.4.4))(publint@0.3.25)(terser@5.44.1)(tsx@4.21.0)(typescript@5.4.4)(yaml@2.9.0)): + optionalDependencies: + '@oxlint/binding-android-arm-eabi': 1.87.0 + '@oxlint/binding-android-arm64': 1.87.0 + '@oxlint/binding-darwin-arm64': 1.87.0 + '@oxlint/binding-darwin-x64': 1.87.0 + '@oxlint/binding-freebsd-x64': 1.87.0 + '@oxlint/binding-linux-arm-gnueabihf': 1.87.0 + '@oxlint/binding-linux-arm-musleabihf': 1.87.0 + '@oxlint/binding-linux-arm64-gnu': 1.87.0 + '@oxlint/binding-linux-arm64-musl': 1.87.0 + '@oxlint/binding-linux-ppc64-gnu': 1.87.0 + '@oxlint/binding-linux-riscv64-gnu': 1.87.0 + '@oxlint/binding-linux-riscv64-musl': 1.87.0 + '@oxlint/binding-linux-s390x-gnu': 1.87.0 + '@oxlint/binding-linux-x64-gnu': 1.87.0 + '@oxlint/binding-linux-x64-musl': 1.87.0 + '@oxlint/binding-openharmony-arm64': 1.87.0 + '@oxlint/binding-win32-arm64-msvc': 1.87.0 + '@oxlint/binding-win32-ia32-msvc': 1.87.0 + '@oxlint/binding-win32-x64-msvc': 1.87.0 + oxlint-tsgolint: 7.0.2003 + vite-plus: 1.1.0(@arethetypeswrong/core@0.18.5)(@opentelemetry/api@1.9.0)(@types/node@20.19.40)(@vitest/ui@5.0.3(vitest@5.0.3))(esbuild@0.28.1)(jiti@2.6.1)(msw@2.13.6(@types/node@20.19.40)(typescript@5.4.4))(publint@0.3.25)(terser@5.44.1)(tsx@4.21.0)(typescript@5.4.4)(yaml@2.9.0) + + oxlint@1.87.0(oxlint-tsgolint@7.0.2003)(vite-plus@1.1.0(@arethetypeswrong/core@0.18.5)(@opentelemetry/api@1.9.0)(@types/node@20.19.40)(@vitest/ui@5.0.3(vitest@5.0.3))(esbuild@0.28.1)(jiti@2.6.1)(msw@2.13.6(@types/node@20.19.40)(typescript@5.5.4))(publint@0.3.25)(terser@5.44.1)(tsx@4.21.0)(typescript@5.5.4)(yaml@2.9.0)): + optionalDependencies: + '@oxlint/binding-android-arm-eabi': 1.87.0 + '@oxlint/binding-android-arm64': 1.87.0 + '@oxlint/binding-darwin-arm64': 1.87.0 + '@oxlint/binding-darwin-x64': 1.87.0 + '@oxlint/binding-freebsd-x64': 1.87.0 + '@oxlint/binding-linux-arm-gnueabihf': 1.87.0 + '@oxlint/binding-linux-arm-musleabihf': 1.87.0 + '@oxlint/binding-linux-arm64-gnu': 1.87.0 + '@oxlint/binding-linux-arm64-musl': 1.87.0 + '@oxlint/binding-linux-ppc64-gnu': 1.87.0 + '@oxlint/binding-linux-riscv64-gnu': 1.87.0 + '@oxlint/binding-linux-riscv64-musl': 1.87.0 + '@oxlint/binding-linux-s390x-gnu': 1.87.0 + '@oxlint/binding-linux-x64-gnu': 1.87.0 + '@oxlint/binding-linux-x64-musl': 1.87.0 + '@oxlint/binding-openharmony-arm64': 1.87.0 + '@oxlint/binding-win32-arm64-msvc': 1.87.0 + '@oxlint/binding-win32-ia32-msvc': 1.87.0 + '@oxlint/binding-win32-x64-msvc': 1.87.0 + oxlint-tsgolint: 7.0.2003 + vite-plus: 1.1.0(@arethetypeswrong/core@0.18.5)(@opentelemetry/api@1.9.0)(@types/node@20.19.40)(@vitest/ui@5.0.3(vitest@5.0.3))(esbuild@0.28.1)(jiti@2.6.1)(msw@2.13.6(@types/node@20.19.40)(typescript@5.5.4))(publint@0.3.25)(terser@5.44.1)(tsx@4.21.0)(typescript@5.5.4)(yaml@2.9.0) + + oxlint@1.87.0(oxlint-tsgolint@7.0.2003)(vite-plus@1.1.0(@arethetypeswrong/core@0.18.5)(@opentelemetry/api@1.9.0)(@types/node@20.19.40)(@vitest/ui@5.0.3(vitest@5.0.3))(esbuild@0.28.1)(jiti@2.6.1)(msw@2.13.6(@types/node@20.19.40)(typescript@5.9.3))(publint@0.3.25)(terser@5.44.1)(tsx@4.21.0)(typescript@5.9.3)(yaml@2.9.0)): + optionalDependencies: + '@oxlint/binding-android-arm-eabi': 1.87.0 + '@oxlint/binding-android-arm64': 1.87.0 + '@oxlint/binding-darwin-arm64': 1.87.0 + '@oxlint/binding-darwin-x64': 1.87.0 + '@oxlint/binding-freebsd-x64': 1.87.0 + '@oxlint/binding-linux-arm-gnueabihf': 1.87.0 + '@oxlint/binding-linux-arm-musleabihf': 1.87.0 + '@oxlint/binding-linux-arm64-gnu': 1.87.0 + '@oxlint/binding-linux-arm64-musl': 1.87.0 + '@oxlint/binding-linux-ppc64-gnu': 1.87.0 + '@oxlint/binding-linux-riscv64-gnu': 1.87.0 + '@oxlint/binding-linux-riscv64-musl': 1.87.0 + '@oxlint/binding-linux-s390x-gnu': 1.87.0 + '@oxlint/binding-linux-x64-gnu': 1.87.0 + '@oxlint/binding-linux-x64-musl': 1.87.0 + '@oxlint/binding-openharmony-arm64': 1.87.0 + '@oxlint/binding-win32-arm64-msvc': 1.87.0 + '@oxlint/binding-win32-ia32-msvc': 1.87.0 + '@oxlint/binding-win32-x64-msvc': 1.87.0 + oxlint-tsgolint: 7.0.2003 + vite-plus: 1.1.0(@arethetypeswrong/core@0.18.5)(@opentelemetry/api@1.9.0)(@types/node@20.19.40)(@vitest/ui@5.0.3(vitest@5.0.3))(esbuild@0.28.1)(jiti@2.6.1)(msw@2.13.6(@types/node@20.19.40)(typescript@5.9.3))(publint@0.3.25)(terser@5.44.1)(tsx@4.21.0)(typescript@5.9.3)(yaml@2.9.0) + + oxlint@1.87.0(oxlint-tsgolint@7.0.2003)(vite-plus@1.1.0(@arethetypeswrong/core@0.18.5)(@opentelemetry/api@1.9.0)(@types/node@22.19.1)(@vitest/ui@5.0.3(vitest@5.0.3))(esbuild@0.28.1)(jiti@2.6.1)(msw@2.13.6(@types/node@22.19.1)(typescript@5.5.4))(publint@0.3.25)(terser@5.44.1)(tsx@4.21.0)(typescript@5.5.4)(yaml@2.9.0)): + optionalDependencies: + '@oxlint/binding-android-arm-eabi': 1.87.0 + '@oxlint/binding-android-arm64': 1.87.0 + '@oxlint/binding-darwin-arm64': 1.87.0 + '@oxlint/binding-darwin-x64': 1.87.0 + '@oxlint/binding-freebsd-x64': 1.87.0 + '@oxlint/binding-linux-arm-gnueabihf': 1.87.0 + '@oxlint/binding-linux-arm-musleabihf': 1.87.0 + '@oxlint/binding-linux-arm64-gnu': 1.87.0 + '@oxlint/binding-linux-arm64-musl': 1.87.0 + '@oxlint/binding-linux-ppc64-gnu': 1.87.0 + '@oxlint/binding-linux-riscv64-gnu': 1.87.0 + '@oxlint/binding-linux-riscv64-musl': 1.87.0 + '@oxlint/binding-linux-s390x-gnu': 1.87.0 + '@oxlint/binding-linux-x64-gnu': 1.87.0 + '@oxlint/binding-linux-x64-musl': 1.87.0 + '@oxlint/binding-openharmony-arm64': 1.87.0 + '@oxlint/binding-win32-arm64-msvc': 1.87.0 + '@oxlint/binding-win32-ia32-msvc': 1.87.0 + '@oxlint/binding-win32-x64-msvc': 1.87.0 + oxlint-tsgolint: 7.0.2003 + vite-plus: 1.1.0(@arethetypeswrong/core@0.18.5)(@opentelemetry/api@1.9.0)(@types/node@22.19.1)(@vitest/ui@5.0.3(vitest@5.0.3))(esbuild@0.28.1)(jiti@2.6.1)(msw@2.13.6(@types/node@22.19.1)(typescript@5.5.4))(publint@0.3.25)(terser@5.44.1)(tsx@4.21.0)(typescript@5.5.4)(yaml@2.9.0) + + oxlint@1.87.0(oxlint-tsgolint@7.0.2003)(vite-plus@1.1.0(@arethetypeswrong/core@0.18.5)(@opentelemetry/api@1.9.0)(@types/node@22.19.1)(@vitest/ui@5.0.3(vitest@5.0.3))(esbuild@0.28.1)(jiti@2.6.1)(msw@2.13.6(@types/node@22.19.1)(typescript@5.9.3))(publint@0.3.25)(terser@5.44.1)(tsx@4.21.0)(typescript@5.9.3)(yaml@2.9.0)): + optionalDependencies: + '@oxlint/binding-android-arm-eabi': 1.87.0 + '@oxlint/binding-android-arm64': 1.87.0 + '@oxlint/binding-darwin-arm64': 1.87.0 + '@oxlint/binding-darwin-x64': 1.87.0 + '@oxlint/binding-freebsd-x64': 1.87.0 + '@oxlint/binding-linux-arm-gnueabihf': 1.87.0 + '@oxlint/binding-linux-arm-musleabihf': 1.87.0 + '@oxlint/binding-linux-arm64-gnu': 1.87.0 + '@oxlint/binding-linux-arm64-musl': 1.87.0 + '@oxlint/binding-linux-ppc64-gnu': 1.87.0 + '@oxlint/binding-linux-riscv64-gnu': 1.87.0 + '@oxlint/binding-linux-riscv64-musl': 1.87.0 + '@oxlint/binding-linux-s390x-gnu': 1.87.0 + '@oxlint/binding-linux-x64-gnu': 1.87.0 + '@oxlint/binding-linux-x64-musl': 1.87.0 + '@oxlint/binding-openharmony-arm64': 1.87.0 + '@oxlint/binding-win32-arm64-msvc': 1.87.0 + '@oxlint/binding-win32-ia32-msvc': 1.87.0 + '@oxlint/binding-win32-x64-msvc': 1.87.0 + oxlint-tsgolint: 7.0.2003 + vite-plus: 1.1.0(@arethetypeswrong/core@0.18.5)(@opentelemetry/api@1.9.0)(@types/node@22.19.1)(@vitest/ui@5.0.3(vitest@5.0.3))(esbuild@0.28.1)(jiti@2.6.1)(msw@2.13.6(@types/node@22.19.1)(typescript@5.9.3))(publint@0.3.25)(terser@5.44.1)(tsx@4.21.0)(typescript@5.9.3)(yaml@2.9.0) + + oxlint@1.87.0(oxlint-tsgolint@7.0.2003)(vite-plus@1.1.0(@arethetypeswrong/core@0.18.5)(@opentelemetry/api@1.9.0)(@types/node@24.12.4)(@vitest/ui@5.0.3(vitest@5.0.3))(esbuild@0.28.1)(jiti@2.6.1)(msw@2.13.6(@types/node@24.12.4)(typescript@5.9.3))(publint@0.3.25)(terser@5.44.1)(tsx@4.21.0)(typescript@5.9.3)(yaml@2.9.0)): + optionalDependencies: + '@oxlint/binding-android-arm-eabi': 1.87.0 + '@oxlint/binding-android-arm64': 1.87.0 + '@oxlint/binding-darwin-arm64': 1.87.0 + '@oxlint/binding-darwin-x64': 1.87.0 + '@oxlint/binding-freebsd-x64': 1.87.0 + '@oxlint/binding-linux-arm-gnueabihf': 1.87.0 + '@oxlint/binding-linux-arm-musleabihf': 1.87.0 + '@oxlint/binding-linux-arm64-gnu': 1.87.0 + '@oxlint/binding-linux-arm64-musl': 1.87.0 + '@oxlint/binding-linux-ppc64-gnu': 1.87.0 + '@oxlint/binding-linux-riscv64-gnu': 1.87.0 + '@oxlint/binding-linux-riscv64-musl': 1.87.0 + '@oxlint/binding-linux-s390x-gnu': 1.87.0 + '@oxlint/binding-linux-x64-gnu': 1.87.0 + '@oxlint/binding-linux-x64-musl': 1.87.0 + '@oxlint/binding-openharmony-arm64': 1.87.0 + '@oxlint/binding-win32-arm64-msvc': 1.87.0 + '@oxlint/binding-win32-ia32-msvc': 1.87.0 + '@oxlint/binding-win32-x64-msvc': 1.87.0 + oxlint-tsgolint: 7.0.2003 + vite-plus: 1.1.0(@arethetypeswrong/core@0.18.5)(@opentelemetry/api@1.9.0)(@types/node@24.12.4)(@vitest/ui@5.0.3(vitest@5.0.3))(esbuild@0.28.1)(jiti@2.6.1)(msw@2.13.6(@types/node@24.12.4)(typescript@5.9.3))(publint@0.3.25)(terser@5.44.1)(tsx@4.21.0)(typescript@5.9.3)(yaml@2.9.0) + p-filter@2.1.0: dependencies: p-map: 2.1.0 - p-finally@1.0.0: {} - p-limit@2.3.0: dependencies: p-try: 2.2.0 @@ -7713,48 +7715,27 @@ snapshots: p-map@2.1.0: {} - p-queue@6.6.2: - dependencies: - eventemitter3: 4.0.7 - p-timeout: 3.2.0 - - p-retry@4.6.2: - dependencies: - '@types/retry': 0.12.0 - retry: 0.13.1 - - p-timeout@3.2.0: - dependencies: - p-finally: 1.0.0 - p-try@2.2.0: {} - package-json-from-dist@1.0.1: {} - package-manager-detector@0.2.11: dependencies: quansync: 0.2.11 + package-manager-detector@1.9.0: + optional: true + parent-module@1.0.1: dependencies: callsites: 3.1.0 - parseurl@1.3.3: {} - path-exists@4.0.0: {} path-is-absolute@1.0.1: {} path-key@3.1.1: {} - path-scurry@1.11.1: - dependencies: - lru-cache: 10.4.3 - minipass: 7.1.3 - - path-to-regexp@6.3.0: {} - - path-to-regexp@8.4.0: {} + path-to-regexp@6.3.0: + optional: true path-type@4.0.0: {} @@ -7770,31 +7751,28 @@ snapshots: pify@4.0.1: {} - pirates@4.0.6: {} - - pkce-challenge@5.0.0: - optional: true + pirates@4.0.7: {} pkg-types@1.3.1: dependencies: confbox: 0.1.8 - mlly: 1.8.0 + mlly: 1.8.2 pathe: 2.0.3 - pluralize@8.0.0: {} + pngjs@7.0.0: {} - postcss-load-config@6.0.1(jiti@2.6.1)(postcss@8.5.26)(tsx@4.21.0)(yaml@2.9.0): + postcss-load-config@6.0.1(jiti@2.6.1)(postcss@8.5.29)(tsx@4.21.0)(yaml@2.9.0): dependencies: - lilconfig: 3.1.3 + lilconfig: 3.1.4 optionalDependencies: jiti: 2.6.1 - postcss: 8.5.26 + postcss: 8.5.29 tsx: 4.21.0 yaml: 2.9.0 - postcss@8.5.26: + postcss@8.5.29: dependencies: - nanoid: 3.3.18 + nanoid: 3.3.20 picocolors: 1.1.1 source-map-js: 1.2.2 @@ -7802,7 +7780,11 @@ snapshots: prettier@2.8.8: {} - prettier@3.8.1: {} + pretty-format@27.5.1: + dependencies: + ansi-regex: 5.0.1 + ansi-styles: 5.2.0 + react-is: 17.0.2 proto3-json-serializer@2.0.2: dependencies: @@ -7822,39 +7804,21 @@ snapshots: '@types/node': 22.19.1 long: 5.3.2 - proxy-addr@2.0.8: + publint@0.3.25: dependencies: - forwarded: 0.2.0 - ipaddr.js: 1.9.1 + '@publint/pack': 0.1.7 + package-manager-detector: 1.9.0 + picocolors: 1.1.1 + sade: 1.8.1 + optional: true punycode@2.3.1: {} - qs@6.16.0: - dependencies: - es-define-property: 1.0.1 - side-channel: 1.1.1 - quansync@0.2.11: {} queue-microtask@1.2.3: {} - range-parser@1.2.1: {} - - raw-body@3.0.2: - dependencies: - bytes: 3.1.2 - http-errors: 2.0.1 - iconv-lite: 0.7.2 - unpipe: 1.0.0 - - react-dom@19.2.4(react@19.2.4): - dependencies: - react: 19.2.4 - scheduler: 0.27.0 - optional: true - - react@19.2.4: - optional: true + react-is@17.0.2: {} read-yaml-file@1.1.0: dependencies: @@ -7880,35 +7844,13 @@ snapshots: onetime: 7.0.0 signal-exit: 4.1.0 - retry@0.13.1: {} - - rettime@0.11.8: {} + rettime@0.11.8: + optional: true reusify@1.0.4: {} rfdc@1.4.1: {} - rolldown@1.1.5: - dependencies: - '@oxc-project/types': 0.139.0 - '@rolldown/pluginutils': 1.0.1 - optionalDependencies: - '@rolldown/binding-android-arm64': 1.1.5 - '@rolldown/binding-darwin-arm64': 1.1.5 - '@rolldown/binding-darwin-x64': 1.1.5 - '@rolldown/binding-freebsd-x64': 1.1.5 - '@rolldown/binding-linux-arm-gnueabihf': 1.1.5 - '@rolldown/binding-linux-arm64-gnu': 1.1.5 - '@rolldown/binding-linux-arm64-musl': 1.1.5 - '@rolldown/binding-linux-ppc64-gnu': 1.1.5 - '@rolldown/binding-linux-s390x-gnu': 1.1.5 - '@rolldown/binding-linux-x64-gnu': 1.1.5 - '@rolldown/binding-linux-x64-musl': 1.1.5 - '@rolldown/binding-openharmony-arm64': 1.1.5 - '@rolldown/binding-wasm32-wasi': 1.1.5 - '@rolldown/binding-win32-arm64-msvc': 1.1.5 - '@rolldown/binding-win32-x64-msvc': 1.1.5 - rollup@4.60.3: dependencies: '@types/estree': 1.0.8 @@ -7940,16 +7882,6 @@ snapshots: '@rollup/rollup-win32-x64-msvc': 4.60.3 fsevents: 2.3.3 - router@2.2.0: - dependencies: - debug: 4.4.3 - depd: 2.0.0 - is-promise: 4.0.0 - parseurl: 1.3.3 - path-to-regexp: 8.4.0 - transitivePeerDependencies: - - supports-color - run-parallel@1.2.0: dependencies: queue-microtask: 1.2.3 @@ -7958,13 +7890,13 @@ snapshots: dependencies: tslib: 2.8.1 - safe-buffer@5.2.1: {} + sade@1.8.1: + dependencies: + mri: 1.2.0 + optional: true safer-buffer@2.1.2: {} - scheduler@0.27.0: - optional: true - schema-utils@4.3.3: dependencies: '@types/json-schema': 7.0.15 @@ -7976,34 +7908,8 @@ snapshots: semver@7.8.0: {} - send@1.2.0: - dependencies: - debug: 4.4.3 - encodeurl: 2.0.0 - escape-html: 1.0.3 - etag: 1.8.1 - fresh: 2.0.0 - http-errors: 2.0.1 - mime-types: 3.0.1 - ms: 2.1.3 - on-finished: 2.4.1 - range-parser: 1.2.1 - statuses: 2.0.2 - transitivePeerDependencies: - - supports-color - - serve-static@2.2.0: - dependencies: - encodeurl: 2.0.0 - escape-html: 1.0.3 - parseurl: 1.3.3 - send: 1.2.0 - transitivePeerDependencies: - - supports-color - - set-cookie-parser@3.1.0: {} - - setprototypeof@1.2.0: {} + set-cookie-parser@3.1.0: + optional: true shebang-command@2.0.0: dependencies: @@ -8018,40 +7924,16 @@ snapshots: vscode-oniguruma: 1.7.0 vscode-textmate: 8.0.0 - side-channel-list@1.0.1: - dependencies: - es-errors: 1.3.0 - object-inspect: 1.13.4 - - side-channel-map@1.0.1: - dependencies: - call-bound: 1.0.4 - es-errors: 1.3.0 - get-intrinsic: 1.3.0 - object-inspect: 1.13.4 - - side-channel-weakmap@1.0.2: - dependencies: - call-bound: 1.0.4 - es-errors: 1.3.0 - get-intrinsic: 1.3.0 - object-inspect: 1.13.4 - side-channel-map: 1.0.1 - - side-channel@1.1.1: - dependencies: - es-errors: 1.3.0 - object-inspect: 1.13.4 - side-channel-list: 1.0.1 - side-channel-map: 1.0.1 - side-channel-weakmap: 1.0.2 - - siginfo@2.0.0: {} - signal-exit@4.1.0: {} simple-yenc@1.0.4: {} + sirv@3.0.2: + dependencies: + '@polka/url': 1.0.0-next.29 + mrmime: 2.0.1 + totalist: 3.0.1 + sisteransi@1.0.5: {} slash@3.0.0: {} @@ -8065,11 +7947,11 @@ snapshots: source-map-js@1.2.2: {} - source-map-loader@4.0.2(webpack@5.106.2(@swc/core@1.15.8)(esbuild@0.28.1)(postcss@8.5.26)): + source-map-loader@4.0.2(webpack@5.106.2(@swc/core@1.15.8)(esbuild@0.28.1)): dependencies: iconv-lite: 0.6.3 source-map-js: 1.2.2 - webpack: 5.106.2(@swc/core@1.15.8)(esbuild@0.28.1)(postcss@8.5.26) + webpack: 5.106.2(@swc/core@1.15.8)(esbuild@0.28.1) source-map-support@0.5.21: dependencies: @@ -8087,13 +7969,13 @@ snapshots: sprintf-js@1.0.3: {} - stackback@0.0.2: {} - - statuses@2.0.2: {} + statuses@2.0.2: + optional: true std-env@4.2.0: {} - strict-event-emitter@0.5.1: {} + strict-event-emitter@0.5.1: + optional: true string-argv@0.3.2: {} @@ -8103,12 +7985,6 @@ snapshots: is-fullwidth-code-point: 3.0.0 strip-ansi: 6.0.1 - string-width@5.1.2: - dependencies: - eastasianwidth: 0.2.0 - emoji-regex: 9.2.2 - strip-ansi: 7.1.2 - string-width@7.2.0: dependencies: emoji-regex: 10.6.0 @@ -8134,14 +8010,14 @@ snapshots: strip-json-comments@5.0.3: {} - sucrase@3.35.0: + sucrase@3.35.1: dependencies: '@jridgewell/gen-mapping': 0.3.13 commander: 4.1.1 - glob: 10.5.0 lines-and-columns: 1.2.4 mz: 2.7.0 - pirates: 4.0.6 + pirates: 4.0.7 + tinyglobby: 0.2.17 ts-interface-checker: 0.1.13 supports-color@7.2.0: @@ -8152,13 +8028,14 @@ snapshots: dependencies: has-flag: 4.0.0 - swc-loader@0.2.6(@swc/core@1.15.8)(webpack@5.106.2(@swc/core@1.15.8)(esbuild@0.28.1)(postcss@8.5.26)): + swc-loader@0.2.6(@swc/core@1.15.8)(webpack@5.106.2(@swc/core@1.15.8)(esbuild@0.28.1)): dependencies: '@swc/core': 1.15.8 '@swc/counter': 0.1.3 - webpack: 5.106.2(@swc/core@1.15.8)(esbuild@0.28.1)(postcss@8.5.26) + webpack: 5.106.2(@swc/core@1.15.8)(esbuild@0.28.1) - tagged-tag@1.0.0: {} + tagged-tag@1.0.0: + optional: true tapable@2.3.3: {} @@ -8172,19 +8049,26 @@ snapshots: term-size@2.2.1: {} - termi-link@1.1.0: {} - - terser-webpack-plugin@5.6.0(@swc/core@1.15.8)(esbuild@0.28.1)(postcss@8.5.26)(webpack@5.106.2(@swc/core@1.15.8)(esbuild@0.28.1)(postcss@8.5.26)): + terser-webpack-plugin@5.6.0(@swc/core@1.15.8)(esbuild@0.28.1)(webpack@5.106.2(@swc/core@1.15.8)(esbuild@0.28.1)): dependencies: '@jridgewell/trace-mapping': 0.3.31 jest-worker: 27.5.1 schema-utils: 4.3.3 terser: 5.44.1 - webpack: 5.106.2(@swc/core@1.15.8)(esbuild@0.28.1)(postcss@8.5.26) + webpack: 5.106.2(@swc/core@1.15.8)(esbuild@0.28.1) optionalDependencies: '@swc/core': 1.15.8 esbuild: 0.28.1 - postcss: 8.5.26 + + terser-webpack-plugin@5.6.0(esbuild@0.28.1)(webpack@5.106.2(esbuild@0.28.1)): + dependencies: + '@jridgewell/trace-mapping': 0.3.31 + jest-worker: 27.5.1 + schema-utils: 4.3.3 + terser: 5.44.1 + webpack: 5.106.2(esbuild@0.28.1) + optionalDependencies: + esbuild: 0.28.1 terser@5.44.1: dependencies: @@ -8205,10 +8089,10 @@ snapshots: dependencies: tslib: 2.8.1 - tinybench@2.9.0: {} - tinybench@4.0.1: {} + tinybench@6.2.2: {} + tinyexec@0.3.2: {} tinyexec@1.3.1: {} @@ -8218,131 +8102,68 @@ snapshots: fdir: 6.5.0(picomatch@4.0.7) picomatch: 4.0.7 + tinypool@2.2.0: {} + tinyrainbow@3.1.1: {} - tldts-core@7.0.29: {} + tldts-core@7.0.29: + optional: true tldts@7.0.29: dependencies: tldts-core: 7.0.29 + optional: true to-regex-range@5.0.1: dependencies: is-number: 7.0.0 - toidentifier@1.0.1: {} - - tough-cookie@6.0.1: - dependencies: - tldts: 7.0.29 - - tr46@0.0.3: {} - - tree-dump@1.1.0(tslib@2.8.1): - dependencies: - tslib: 2.8.1 - - tree-kill@1.2.2: {} - - ts-algebra@2.0.0: {} - - ts-api-utils@2.1.0(typescript@5.4.4): - dependencies: - typescript: 5.4.4 - - ts-declaration-location@1.0.7(typescript@5.4.4): - dependencies: - picomatch: 4.0.7 - typescript: 5.4.4 - - ts-interface-checker@0.1.13: {} - - tsconfck@3.1.1(typescript@5.4.4): - optionalDependencies: - typescript: 5.4.4 - - tslib@2.8.1: {} - - tsup@8.5.1(@swc/core@1.15.8)(jiti@2.6.1)(postcss@8.5.26)(tsx@4.21.0)(typescript@5.4.4)(yaml@2.9.0): - dependencies: - bundle-require: 5.1.0(esbuild@0.28.1) - cac: 6.7.14 - chokidar: 4.0.3 - consola: 3.4.0 - debug: 4.4.3 - esbuild: 0.28.1 - fix-dts-default-cjs-exports: 1.0.1 - joycon: 3.1.1 - picocolors: 1.1.1 - postcss-load-config: 6.0.1(jiti@2.6.1)(postcss@8.5.26)(tsx@4.21.0)(yaml@2.9.0) - resolve-from: 5.0.0 - rollup: 4.60.3 - source-map: 0.7.6 - sucrase: 3.35.0 - tinyexec: 0.3.2 - tinyglobby: 0.2.17 - tree-kill: 1.2.2 - optionalDependencies: - '@swc/core': 1.15.8 - postcss: 8.5.26 - typescript: 5.4.4 - transitivePeerDependencies: - - jiti - - supports-color - - tsx - - yaml + totalist@3.0.1: {} - tsup@8.5.1(@swc/core@1.15.8)(jiti@2.6.1)(postcss@8.5.26)(tsx@4.21.0)(typescript@5.5.4)(yaml@2.9.0): + tough-cookie@6.0.1: dependencies: - bundle-require: 5.1.0(esbuild@0.28.1) - cac: 6.7.14 - chokidar: 4.0.3 - consola: 3.4.0 - debug: 4.4.3 - esbuild: 0.28.1 - fix-dts-default-cjs-exports: 1.0.1 - joycon: 3.1.1 - picocolors: 1.1.1 - postcss-load-config: 6.0.1(jiti@2.6.1)(postcss@8.5.26)(tsx@4.21.0)(yaml@2.9.0) - resolve-from: 5.0.0 - rollup: 4.60.3 - source-map: 0.7.6 - sucrase: 3.35.0 - tinyexec: 0.3.2 - tinyglobby: 0.2.17 - tree-kill: 1.2.2 - optionalDependencies: - '@swc/core': 1.15.8 - postcss: 8.5.26 - typescript: 5.5.4 - transitivePeerDependencies: - - jiti - - supports-color - - tsx - - yaml + tldts: 7.0.29 + optional: true + + tr46@0.0.3: {} + + tree-dump@1.1.0(tslib@2.8.1): + dependencies: + tslib: 2.8.1 + + tree-kill@1.2.2: {} + + ts-declaration-location@1.0.7(typescript@5.4.4): + dependencies: + picomatch: 4.0.7 + typescript: 5.4.4 + + ts-interface-checker@0.1.13: {} - tsup@8.5.1(@swc/core@1.15.8)(jiti@2.6.1)(postcss@8.5.26)(tsx@4.21.0)(typescript@5.9.3)(yaml@2.9.0): + tslib@2.8.1: {} + + tsup@8.5.1(@swc/core@1.15.8)(jiti@2.6.1)(postcss@8.5.29)(tsx@4.21.0)(typescript@5.9.3)(yaml@2.9.0): dependencies: bundle-require: 5.1.0(esbuild@0.28.1) cac: 6.7.14 chokidar: 4.0.3 - consola: 3.4.0 + consola: 3.4.2 debug: 4.4.3 esbuild: 0.28.1 fix-dts-default-cjs-exports: 1.0.1 joycon: 3.1.1 picocolors: 1.1.1 - postcss-load-config: 6.0.1(jiti@2.6.1)(postcss@8.5.26)(tsx@4.21.0)(yaml@2.9.0) + postcss-load-config: 6.0.1(jiti@2.6.1)(postcss@8.5.29)(tsx@4.21.0)(yaml@2.9.0) resolve-from: 5.0.0 rollup: 4.60.3 source-map: 0.7.6 - sucrase: 3.35.0 + sucrase: 3.35.1 tinyexec: 0.3.2 tinyglobby: 0.2.17 tree-kill: 1.2.2 optionalDependencies: '@swc/core': 1.15.8 - postcss: 8.5.26 + postcss: 8.5.29 typescript: 5.9.3 transitivePeerDependencies: - jiti @@ -8357,15 +8178,6 @@ snapshots: optionalDependencies: fsevents: 2.3.3 - turbo@2.9.14: - optionalDependencies: - '@turbo/darwin-64': 2.9.14 - '@turbo/darwin-arm64': 2.9.14 - '@turbo/linux-64': 2.9.14 - '@turbo/linux-arm64': 2.9.14 - '@turbo/windows-64': 2.9.14 - '@turbo/windows-arm64': 2.9.14 - type-check@0.4.0: dependencies: prelude-ls: 1.2.1 @@ -8373,12 +8185,7 @@ snapshots: type-fest@5.6.0: dependencies: tagged-tag: 1.0.0 - - type-is@2.1.0: - dependencies: - content-type: 2.0.0 - media-typer: 1.1.0 - mime-types: 3.0.1 + optional: true typedoc-plugin-markdown@3.17.1(typedoc@0.25.13(typescript@5.4.4)): dependencies: @@ -8397,9 +8204,12 @@ snapshots: typescript@5.5.4: {} + typescript@5.6.1-rc: + optional: true + typescript@5.9.3: {} - ufo@1.6.1: {} + ufo@1.6.4: {} uglify-js@3.19.3: optional: true @@ -8416,8 +8226,6 @@ snapshots: universalify@0.1.2: {} - unpipe@1.0.0: {} - unplugin@2.3.11: dependencies: '@jridgewell/remapping': 2.3.5 @@ -8425,7 +8233,8 @@ snapshots: picomatch: 4.0.7 webpack-virtual-modules: 0.6.2 - until-async@3.0.2: {} + until-async@3.0.2: + optional: true update-browserslist-db@1.3.2(browserslist@4.28.8): dependencies: @@ -8439,188 +8248,552 @@ snapshots: uuid@11.1.1: {} - vary@1.1.2: {} + validate-npm-package-name@5.0.1: + optional: true + + vite-plus@1.1.0(@arethetypeswrong/core@0.18.5)(@opentelemetry/api@1.9.0)(@types/node@20.19.40)(@vitest/ui@5.0.3(vitest@5.0.3))(esbuild@0.28.1)(jiti@2.6.1)(msw@2.13.6(@types/node@20.19.40)(typescript@5.4.4))(publint@0.3.25)(terser@5.44.1)(tsx@4.21.0)(typescript@5.4.4)(yaml@2.9.0): + dependencies: + '@oxc-project/types': 0.153.0 + '@oxlint/plugins': 1.79.0 + '@vitest/browser': 5.0.3(@voidzero-dev/vite-plus-core@1.1.0(@arethetypeswrong/core@0.18.5)(@types/node@20.19.40)(esbuild@0.28.1)(jiti@2.6.1)(publint@0.3.25)(terser@5.44.1)(tsx@4.21.0)(typescript@5.4.4)(yaml@2.9.0))(msw@2.13.6(@types/node@20.19.40)(typescript@5.4.4))(vitest@5.0.3) + '@vitest/browser-preview': 5.0.3(@voidzero-dev/vite-plus-core@1.1.0(@arethetypeswrong/core@0.18.5)(@types/node@20.19.40)(esbuild@0.28.1)(jiti@2.6.1)(publint@0.3.25)(terser@5.44.1)(tsx@4.21.0)(typescript@5.4.4)(yaml@2.9.0))(msw@2.13.6(@types/node@20.19.40)(typescript@5.4.4))(vitest@5.0.3) + '@vitest/mocker': 5.0.3(@voidzero-dev/vite-plus-core@1.1.0(@arethetypeswrong/core@0.18.5)(@types/node@20.19.40)(esbuild@0.28.1)(jiti@2.6.1)(publint@0.3.25)(terser@5.44.1)(tsx@4.21.0)(typescript@5.4.4)(yaml@2.9.0))(msw@2.13.6(@types/node@20.19.40)(typescript@5.4.4)) + '@vitest/pretty-format': 5.0.3 + '@vitest/snapshot': 5.0.3 + '@vitest/spy': 5.0.3 + '@vitest/utils': 5.0.3 + oxfmt: 0.72.0(vite-plus@1.1.0(@arethetypeswrong/core@0.18.5)(@opentelemetry/api@1.9.0)(@types/node@20.19.40)(@vitest/ui@5.0.3(vitest@5.0.3))(esbuild@0.28.1)(jiti@2.6.1)(msw@2.13.6(@types/node@20.19.40)(typescript@5.4.4))(publint@0.3.25)(terser@5.44.1)(tsx@4.21.0)(typescript@5.4.4)(yaml@2.9.0)) + oxlint: 1.87.0(oxlint-tsgolint@7.0.2003)(vite-plus@1.1.0(@arethetypeswrong/core@0.18.5)(@opentelemetry/api@1.9.0)(@types/node@20.19.40)(@vitest/ui@5.0.3(vitest@5.0.3))(esbuild@0.28.1)(jiti@2.6.1)(msw@2.13.6(@types/node@20.19.40)(typescript@5.4.4))(publint@0.3.25)(terser@5.44.1)(tsx@4.21.0)(typescript@5.4.4)(yaml@2.9.0)) + oxlint-tsgolint: 7.0.2003 + vite: '@voidzero-dev/vite-plus-core@1.1.0(@arethetypeswrong/core@0.18.5)(@types/node@20.19.40)(esbuild@0.28.1)(jiti@2.6.1)(publint@0.3.25)(terser@5.44.1)(tsx@4.21.0)(typescript@5.4.4)(yaml@2.9.0)' + vitest: 5.0.3(@opentelemetry/api@1.9.0)(@types/node@20.19.40)(@vitest/browser-preview@5.0.3)(@vitest/ui@5.0.3)(@voidzero-dev/vite-plus-core@1.1.0(@arethetypeswrong/core@0.18.5)(@types/node@20.19.40)(esbuild@0.28.1)(jiti@2.6.1)(publint@0.3.25)(terser@5.44.1)(tsx@4.21.0)(typescript@5.4.4)(yaml@2.9.0))(msw@2.13.6(@types/node@20.19.40)(typescript@5.4.4)) + optionalDependencies: + '@voidzero-dev/vite-plus-darwin-arm64': 1.1.0 + '@voidzero-dev/vite-plus-darwin-x64': 1.1.0 + '@voidzero-dev/vite-plus-linux-arm64-gnu': 1.1.0 + '@voidzero-dev/vite-plus-linux-arm64-musl': 1.1.0 + '@voidzero-dev/vite-plus-linux-x64-gnu': 1.1.0 + '@voidzero-dev/vite-plus-linux-x64-musl': 1.1.0 + '@voidzero-dev/vite-plus-win32-arm64-msvc': 1.1.0 + '@voidzero-dev/vite-plus-win32-x64-msvc': 1.1.0 + transitivePeerDependencies: + - '@arethetypeswrong/core' + - '@edge-runtime/vm' + - '@opentelemetry/api' + - '@types/node' + - '@typescript/native-preview' + - '@vitejs/devtools' + - '@vitest/coverage-istanbul' + - '@vitest/coverage-v8' + - '@vitest/ui' + - '@volar/typescript' + - '@vue/language-core' + - bufferutil + - esbuild + - happy-dom + - jiti + - jsdom + - less + - msw + - publint + - sass + - sass-embedded + - stylus + - sugarss + - svelte + - terser + - tsx + - typescript + - unplugin-unused + - unrun + - utf-8-validate + - vue-tsc + - yaml + + vite-plus@1.1.0(@arethetypeswrong/core@0.18.5)(@opentelemetry/api@1.9.0)(@types/node@20.19.40)(@vitest/ui@5.0.3(vitest@5.0.3))(esbuild@0.28.1)(jiti@2.6.1)(msw@2.13.6(@types/node@20.19.40)(typescript@5.5.4))(publint@0.3.25)(terser@5.44.1)(tsx@4.21.0)(typescript@5.5.4)(yaml@2.9.0): + dependencies: + '@oxc-project/types': 0.153.0 + '@oxlint/plugins': 1.79.0 + '@vitest/browser': 5.0.3(@voidzero-dev/vite-plus-core@1.1.0(@arethetypeswrong/core@0.18.5)(@types/node@20.19.40)(esbuild@0.28.1)(jiti@2.6.1)(publint@0.3.25)(terser@5.44.1)(tsx@4.21.0)(typescript@5.5.4)(yaml@2.9.0))(msw@2.13.6(@types/node@20.19.40)(typescript@5.5.4))(vitest@5.0.3) + '@vitest/browser-preview': 5.0.3(@voidzero-dev/vite-plus-core@1.1.0(@arethetypeswrong/core@0.18.5)(@types/node@20.19.40)(esbuild@0.28.1)(jiti@2.6.1)(publint@0.3.25)(terser@5.44.1)(tsx@4.21.0)(typescript@5.5.4)(yaml@2.9.0))(msw@2.13.6(@types/node@20.19.40)(typescript@5.5.4))(vitest@5.0.3) + '@vitest/mocker': 5.0.3(@voidzero-dev/vite-plus-core@1.1.0(@arethetypeswrong/core@0.18.5)(@types/node@20.19.40)(esbuild@0.28.1)(jiti@2.6.1)(publint@0.3.25)(terser@5.44.1)(tsx@4.21.0)(typescript@5.5.4)(yaml@2.9.0))(msw@2.13.6(@types/node@20.19.40)(typescript@5.5.4)) + '@vitest/pretty-format': 5.0.3 + '@vitest/snapshot': 5.0.3 + '@vitest/spy': 5.0.3 + '@vitest/utils': 5.0.3 + oxfmt: 0.72.0(vite-plus@1.1.0(@arethetypeswrong/core@0.18.5)(@opentelemetry/api@1.9.0)(@types/node@20.19.40)(@vitest/ui@5.0.3(vitest@5.0.3))(esbuild@0.28.1)(jiti@2.6.1)(msw@2.13.6(@types/node@20.19.40)(typescript@5.5.4))(publint@0.3.25)(terser@5.44.1)(tsx@4.21.0)(typescript@5.5.4)(yaml@2.9.0)) + oxlint: 1.87.0(oxlint-tsgolint@7.0.2003)(vite-plus@1.1.0(@arethetypeswrong/core@0.18.5)(@opentelemetry/api@1.9.0)(@types/node@20.19.40)(@vitest/ui@5.0.3(vitest@5.0.3))(esbuild@0.28.1)(jiti@2.6.1)(msw@2.13.6(@types/node@20.19.40)(typescript@5.5.4))(publint@0.3.25)(terser@5.44.1)(tsx@4.21.0)(typescript@5.5.4)(yaml@2.9.0)) + oxlint-tsgolint: 7.0.2003 + vite: '@voidzero-dev/vite-plus-core@1.1.0(@arethetypeswrong/core@0.18.5)(@types/node@20.19.40)(esbuild@0.28.1)(jiti@2.6.1)(publint@0.3.25)(terser@5.44.1)(tsx@4.21.0)(typescript@5.5.4)(yaml@2.9.0)' + vitest: 5.0.3(@opentelemetry/api@1.9.0)(@types/node@20.19.40)(@vitest/browser-preview@5.0.3)(@vitest/ui@5.0.3)(@voidzero-dev/vite-plus-core@1.1.0(@arethetypeswrong/core@0.18.5)(@types/node@20.19.40)(esbuild@0.28.1)(jiti@2.6.1)(publint@0.3.25)(terser@5.44.1)(tsx@4.21.0)(typescript@5.5.4)(yaml@2.9.0))(msw@2.13.6(@types/node@20.19.40)(typescript@5.5.4)) + optionalDependencies: + '@voidzero-dev/vite-plus-darwin-arm64': 1.1.0 + '@voidzero-dev/vite-plus-darwin-x64': 1.1.0 + '@voidzero-dev/vite-plus-linux-arm64-gnu': 1.1.0 + '@voidzero-dev/vite-plus-linux-arm64-musl': 1.1.0 + '@voidzero-dev/vite-plus-linux-x64-gnu': 1.1.0 + '@voidzero-dev/vite-plus-linux-x64-musl': 1.1.0 + '@voidzero-dev/vite-plus-win32-arm64-msvc': 1.1.0 + '@voidzero-dev/vite-plus-win32-x64-msvc': 1.1.0 + transitivePeerDependencies: + - '@arethetypeswrong/core' + - '@edge-runtime/vm' + - '@opentelemetry/api' + - '@types/node' + - '@typescript/native-preview' + - '@vitejs/devtools' + - '@vitest/coverage-istanbul' + - '@vitest/coverage-v8' + - '@vitest/ui' + - '@volar/typescript' + - '@vue/language-core' + - bufferutil + - esbuild + - happy-dom + - jiti + - jsdom + - less + - msw + - publint + - sass + - sass-embedded + - stylus + - sugarss + - svelte + - terser + - tsx + - typescript + - unplugin-unused + - unrun + - utf-8-validate + - vue-tsc + - yaml - vite-tsconfig-paths@4.3.2(typescript@5.4.4)(vite@8.1.5(@types/node@20.19.40)(esbuild@0.28.1)(jiti@2.6.1)(terser@5.44.1)(tsx@4.21.0)(yaml@2.9.0)): - dependencies: - debug: 4.4.3 - globrex: 0.1.2 - tsconfck: 3.1.1(typescript@5.4.4) + vite-plus@1.1.0(@arethetypeswrong/core@0.18.5)(@opentelemetry/api@1.9.0)(@types/node@20.19.40)(@vitest/ui@5.0.3(vitest@5.0.3))(esbuild@0.28.1)(jiti@2.6.1)(msw@2.13.6(@types/node@20.19.40)(typescript@5.9.3))(publint@0.3.25)(terser@5.44.1)(tsx@4.21.0)(typescript@5.9.3)(yaml@2.9.0): + dependencies: + '@oxc-project/types': 0.153.0 + '@oxlint/plugins': 1.79.0 + '@vitest/browser': 5.0.3(@voidzero-dev/vite-plus-core@1.1.0(@arethetypeswrong/core@0.18.5)(@types/node@20.19.40)(esbuild@0.28.1)(jiti@2.6.1)(publint@0.3.25)(terser@5.44.1)(tsx@4.21.0)(typescript@5.9.3)(yaml@2.9.0))(msw@2.13.6(@types/node@20.19.40)(typescript@5.9.3))(vitest@5.0.3) + '@vitest/browser-preview': 5.0.3(@voidzero-dev/vite-plus-core@1.1.0(@arethetypeswrong/core@0.18.5)(@types/node@20.19.40)(esbuild@0.28.1)(jiti@2.6.1)(publint@0.3.25)(terser@5.44.1)(tsx@4.21.0)(typescript@5.9.3)(yaml@2.9.0))(msw@2.13.6(@types/node@20.19.40)(typescript@5.9.3))(vitest@5.0.3) + '@vitest/mocker': 5.0.3(@voidzero-dev/vite-plus-core@1.1.0(@arethetypeswrong/core@0.18.5)(@types/node@20.19.40)(esbuild@0.28.1)(jiti@2.6.1)(publint@0.3.25)(terser@5.44.1)(tsx@4.21.0)(typescript@5.9.3)(yaml@2.9.0))(msw@2.13.6(@types/node@20.19.40)(typescript@5.9.3)) + '@vitest/pretty-format': 5.0.3 + '@vitest/snapshot': 5.0.3 + '@vitest/spy': 5.0.3 + '@vitest/utils': 5.0.3 + oxfmt: 0.72.0(vite-plus@1.1.0(@arethetypeswrong/core@0.18.5)(@opentelemetry/api@1.9.0)(@types/node@20.19.40)(@vitest/ui@5.0.3(vitest@5.0.3))(esbuild@0.28.1)(jiti@2.6.1)(msw@2.13.6(@types/node@20.19.40)(typescript@5.9.3))(publint@0.3.25)(terser@5.44.1)(tsx@4.21.0)(typescript@5.9.3)(yaml@2.9.0)) + oxlint: 1.87.0(oxlint-tsgolint@7.0.2003)(vite-plus@1.1.0(@arethetypeswrong/core@0.18.5)(@opentelemetry/api@1.9.0)(@types/node@20.19.40)(@vitest/ui@5.0.3(vitest@5.0.3))(esbuild@0.28.1)(jiti@2.6.1)(msw@2.13.6(@types/node@20.19.40)(typescript@5.9.3))(publint@0.3.25)(terser@5.44.1)(tsx@4.21.0)(typescript@5.9.3)(yaml@2.9.0)) + oxlint-tsgolint: 7.0.2003 + vite: '@voidzero-dev/vite-plus-core@1.1.0(@arethetypeswrong/core@0.18.5)(@types/node@20.19.40)(esbuild@0.28.1)(jiti@2.6.1)(publint@0.3.25)(terser@5.44.1)(tsx@4.21.0)(typescript@5.9.3)(yaml@2.9.0)' + vitest: 5.0.3(@opentelemetry/api@1.9.0)(@types/node@20.19.40)(@vitest/browser-preview@5.0.3)(@vitest/ui@5.0.3(vitest@5.0.3))(@voidzero-dev/vite-plus-core@1.1.0(@arethetypeswrong/core@0.18.5)(@types/node@20.19.40)(esbuild@0.28.1)(jiti@2.6.1)(publint@0.3.25)(terser@5.44.1)(tsx@4.21.0)(typescript@5.9.3)(yaml@2.9.0))(msw@2.13.6(@types/node@20.19.40)(typescript@5.9.3)) optionalDependencies: - vite: 8.1.5(@types/node@20.19.40)(esbuild@0.28.1)(jiti@2.6.1)(terser@5.44.1)(tsx@4.21.0)(yaml@2.9.0) + '@voidzero-dev/vite-plus-darwin-arm64': 1.1.0 + '@voidzero-dev/vite-plus-darwin-x64': 1.1.0 + '@voidzero-dev/vite-plus-linux-arm64-gnu': 1.1.0 + '@voidzero-dev/vite-plus-linux-arm64-musl': 1.1.0 + '@voidzero-dev/vite-plus-linux-x64-gnu': 1.1.0 + '@voidzero-dev/vite-plus-linux-x64-musl': 1.1.0 + '@voidzero-dev/vite-plus-win32-arm64-msvc': 1.1.0 + '@voidzero-dev/vite-plus-win32-x64-msvc': 1.1.0 transitivePeerDependencies: - - supports-color + - '@arethetypeswrong/core' + - '@edge-runtime/vm' + - '@opentelemetry/api' + - '@types/node' + - '@typescript/native-preview' + - '@vitejs/devtools' + - '@vitest/coverage-istanbul' + - '@vitest/coverage-v8' + - '@vitest/ui' + - '@volar/typescript' + - '@vue/language-core' + - bufferutil + - esbuild + - happy-dom + - jiti + - jsdom + - less + - msw + - publint + - sass + - sass-embedded + - stylus + - sugarss + - svelte + - terser + - tsx + - typescript + - unplugin-unused + - unrun + - utf-8-validate + - vue-tsc + - yaml + + vite-plus@1.1.0(@arethetypeswrong/core@0.18.5)(@opentelemetry/api@1.9.0)(@types/node@22.19.1)(@vitest/ui@5.0.3(vitest@5.0.3))(esbuild@0.28.1)(jiti@2.6.1)(msw@2.13.6(@types/node@22.19.1)(typescript@5.5.4))(publint@0.3.25)(terser@5.44.1)(tsx@4.21.0)(typescript@5.5.4)(yaml@2.9.0): + dependencies: + '@oxc-project/types': 0.153.0 + '@oxlint/plugins': 1.79.0 + '@vitest/browser': 5.0.3(@voidzero-dev/vite-plus-core@1.1.0(@arethetypeswrong/core@0.18.5)(@types/node@22.19.1)(esbuild@0.28.1)(jiti@2.6.1)(publint@0.3.25)(terser@5.44.1)(tsx@4.21.0)(typescript@5.5.4)(yaml@2.9.0))(msw@2.13.6(@types/node@22.19.1)(typescript@5.5.4))(vitest@5.0.3) + '@vitest/browser-preview': 5.0.3(@voidzero-dev/vite-plus-core@1.1.0(@arethetypeswrong/core@0.18.5)(@types/node@22.19.1)(esbuild@0.28.1)(jiti@2.6.1)(publint@0.3.25)(terser@5.44.1)(tsx@4.21.0)(typescript@5.5.4)(yaml@2.9.0))(msw@2.13.6(@types/node@22.19.1)(typescript@5.5.4))(vitest@5.0.3) + '@vitest/mocker': 5.0.3(@voidzero-dev/vite-plus-core@1.1.0(@arethetypeswrong/core@0.18.5)(@types/node@22.19.1)(esbuild@0.28.1)(jiti@2.6.1)(publint@0.3.25)(terser@5.44.1)(tsx@4.21.0)(typescript@5.5.4)(yaml@2.9.0))(msw@2.13.6(@types/node@22.19.1)(typescript@5.5.4)) + '@vitest/pretty-format': 5.0.3 + '@vitest/snapshot': 5.0.3 + '@vitest/spy': 5.0.3 + '@vitest/utils': 5.0.3 + oxfmt: 0.72.0(vite-plus@1.1.0(@arethetypeswrong/core@0.18.5)(@opentelemetry/api@1.9.0)(@types/node@22.19.1)(@vitest/ui@5.0.3(vitest@5.0.3))(esbuild@0.28.1)(jiti@2.6.1)(msw@2.13.6(@types/node@22.19.1)(typescript@5.5.4))(publint@0.3.25)(terser@5.44.1)(tsx@4.21.0)(typescript@5.5.4)(yaml@2.9.0)) + oxlint: 1.87.0(oxlint-tsgolint@7.0.2003)(vite-plus@1.1.0(@arethetypeswrong/core@0.18.5)(@opentelemetry/api@1.9.0)(@types/node@22.19.1)(@vitest/ui@5.0.3(vitest@5.0.3))(esbuild@0.28.1)(jiti@2.6.1)(msw@2.13.6(@types/node@22.19.1)(typescript@5.5.4))(publint@0.3.25)(terser@5.44.1)(tsx@4.21.0)(typescript@5.5.4)(yaml@2.9.0)) + oxlint-tsgolint: 7.0.2003 + vite: '@voidzero-dev/vite-plus-core@1.1.0(@arethetypeswrong/core@0.18.5)(@types/node@22.19.1)(esbuild@0.28.1)(jiti@2.6.1)(publint@0.3.25)(terser@5.44.1)(tsx@4.21.0)(typescript@5.5.4)(yaml@2.9.0)' + vitest: 5.0.3(@opentelemetry/api@1.9.0)(@types/node@22.19.1)(@vitest/browser-preview@5.0.3)(@vitest/ui@5.0.3(vitest@5.0.3))(@voidzero-dev/vite-plus-core@1.1.0(@arethetypeswrong/core@0.18.5)(@types/node@22.19.1)(esbuild@0.28.1)(jiti@2.6.1)(publint@0.3.25)(terser@5.44.1)(tsx@4.21.0)(typescript@5.5.4)(yaml@2.9.0))(msw@2.13.6(@types/node@22.19.1)(typescript@5.5.4)) + optionalDependencies: + '@voidzero-dev/vite-plus-darwin-arm64': 1.1.0 + '@voidzero-dev/vite-plus-darwin-x64': 1.1.0 + '@voidzero-dev/vite-plus-linux-arm64-gnu': 1.1.0 + '@voidzero-dev/vite-plus-linux-arm64-musl': 1.1.0 + '@voidzero-dev/vite-plus-linux-x64-gnu': 1.1.0 + '@voidzero-dev/vite-plus-linux-x64-musl': 1.1.0 + '@voidzero-dev/vite-plus-win32-arm64-msvc': 1.1.0 + '@voidzero-dev/vite-plus-win32-x64-msvc': 1.1.0 + transitivePeerDependencies: + - '@arethetypeswrong/core' + - '@edge-runtime/vm' + - '@opentelemetry/api' + - '@types/node' + - '@typescript/native-preview' + - '@vitejs/devtools' + - '@vitest/coverage-istanbul' + - '@vitest/coverage-v8' + - '@vitest/ui' + - '@volar/typescript' + - '@vue/language-core' + - bufferutil + - esbuild + - happy-dom + - jiti + - jsdom + - less + - msw + - publint + - sass + - sass-embedded + - stylus + - sugarss + - svelte + - terser + - tsx + - typescript + - unplugin-unused + - unrun + - utf-8-validate + - vue-tsc + - yaml + + vite-plus@1.1.0(@arethetypeswrong/core@0.18.5)(@opentelemetry/api@1.9.0)(@types/node@22.19.1)(@vitest/ui@5.0.3(vitest@5.0.3))(esbuild@0.28.1)(jiti@2.6.1)(msw@2.13.6(@types/node@22.19.1)(typescript@5.9.3))(publint@0.3.25)(terser@5.44.1)(tsx@4.21.0)(typescript@5.9.3)(yaml@2.9.0): + dependencies: + '@oxc-project/types': 0.153.0 + '@oxlint/plugins': 1.79.0 + '@vitest/browser': 5.0.3(@voidzero-dev/vite-plus-core@1.1.0(@arethetypeswrong/core@0.18.5)(@types/node@22.19.1)(esbuild@0.28.1)(jiti@2.6.1)(publint@0.3.25)(terser@5.44.1)(tsx@4.21.0)(typescript@5.9.3)(yaml@2.9.0))(msw@2.13.6(@types/node@22.19.1)(typescript@5.9.3))(vitest@5.0.3) + '@vitest/browser-preview': 5.0.3(@voidzero-dev/vite-plus-core@1.1.0(@arethetypeswrong/core@0.18.5)(@types/node@22.19.1)(esbuild@0.28.1)(jiti@2.6.1)(publint@0.3.25)(terser@5.44.1)(tsx@4.21.0)(typescript@5.9.3)(yaml@2.9.0))(msw@2.13.6(@types/node@22.19.1)(typescript@5.9.3))(vitest@5.0.3) + '@vitest/mocker': 5.0.3(@voidzero-dev/vite-plus-core@1.1.0(@arethetypeswrong/core@0.18.5)(@types/node@22.19.1)(esbuild@0.28.1)(jiti@2.6.1)(publint@0.3.25)(terser@5.44.1)(tsx@4.21.0)(typescript@5.9.3)(yaml@2.9.0))(msw@2.13.6(@types/node@22.19.1)(typescript@5.9.3)) + '@vitest/pretty-format': 5.0.3 + '@vitest/snapshot': 5.0.3 + '@vitest/spy': 5.0.3 + '@vitest/utils': 5.0.3 + oxfmt: 0.72.0(vite-plus@1.1.0(@arethetypeswrong/core@0.18.5)(@opentelemetry/api@1.9.0)(@types/node@22.19.1)(@vitest/ui@5.0.3(vitest@5.0.3))(esbuild@0.28.1)(jiti@2.6.1)(msw@2.13.6(@types/node@22.19.1)(typescript@5.9.3))(publint@0.3.25)(terser@5.44.1)(tsx@4.21.0)(typescript@5.9.3)(yaml@2.9.0)) + oxlint: 1.87.0(oxlint-tsgolint@7.0.2003)(vite-plus@1.1.0(@arethetypeswrong/core@0.18.5)(@opentelemetry/api@1.9.0)(@types/node@22.19.1)(@vitest/ui@5.0.3(vitest@5.0.3))(esbuild@0.28.1)(jiti@2.6.1)(msw@2.13.6(@types/node@22.19.1)(typescript@5.9.3))(publint@0.3.25)(terser@5.44.1)(tsx@4.21.0)(typescript@5.9.3)(yaml@2.9.0)) + oxlint-tsgolint: 7.0.2003 + vite: '@voidzero-dev/vite-plus-core@1.1.0(@arethetypeswrong/core@0.18.5)(@types/node@22.19.1)(esbuild@0.28.1)(jiti@2.6.1)(publint@0.3.25)(terser@5.44.1)(tsx@4.21.0)(typescript@5.9.3)(yaml@2.9.0)' + vitest: 5.0.3(@opentelemetry/api@1.9.0)(@types/node@22.19.1)(@vitest/browser-preview@5.0.3)(@vitest/ui@5.0.3(vitest@5.0.3))(@voidzero-dev/vite-plus-core@1.1.0(@arethetypeswrong/core@0.18.5)(@types/node@22.19.1)(esbuild@0.28.1)(jiti@2.6.1)(publint@0.3.25)(terser@5.44.1)(tsx@4.21.0)(typescript@5.9.3)(yaml@2.9.0))(msw@2.13.6(@types/node@22.19.1)(typescript@5.9.3)) + optionalDependencies: + '@voidzero-dev/vite-plus-darwin-arm64': 1.1.0 + '@voidzero-dev/vite-plus-darwin-x64': 1.1.0 + '@voidzero-dev/vite-plus-linux-arm64-gnu': 1.1.0 + '@voidzero-dev/vite-plus-linux-arm64-musl': 1.1.0 + '@voidzero-dev/vite-plus-linux-x64-gnu': 1.1.0 + '@voidzero-dev/vite-plus-linux-x64-musl': 1.1.0 + '@voidzero-dev/vite-plus-win32-arm64-msvc': 1.1.0 + '@voidzero-dev/vite-plus-win32-x64-msvc': 1.1.0 + transitivePeerDependencies: + - '@arethetypeswrong/core' + - '@edge-runtime/vm' + - '@opentelemetry/api' + - '@types/node' + - '@typescript/native-preview' + - '@vitejs/devtools' + - '@vitest/coverage-istanbul' + - '@vitest/coverage-v8' + - '@vitest/ui' + - '@volar/typescript' + - '@vue/language-core' + - bufferutil + - esbuild + - happy-dom + - jiti + - jsdom + - less + - msw + - publint + - sass + - sass-embedded + - stylus + - sugarss + - svelte + - terser + - tsx + - typescript + - unplugin-unused + - unrun + - utf-8-validate + - vue-tsc + - yaml + + vite-plus@1.1.0(@arethetypeswrong/core@0.18.5)(@opentelemetry/api@1.9.0)(@types/node@24.12.4)(@vitest/ui@5.0.3(vitest@5.0.3))(esbuild@0.28.1)(jiti@2.6.1)(msw@2.13.6(@types/node@24.12.4)(typescript@5.9.3))(publint@0.3.25)(terser@5.44.1)(tsx@4.21.0)(typescript@5.9.3)(yaml@2.9.0): + dependencies: + '@oxc-project/types': 0.153.0 + '@oxlint/plugins': 1.79.0 + '@vitest/browser': 5.0.3(@voidzero-dev/vite-plus-core@1.1.0(@arethetypeswrong/core@0.18.5)(@types/node@24.12.4)(esbuild@0.28.1)(jiti@2.6.1)(publint@0.3.25)(terser@5.44.1)(tsx@4.21.0)(typescript@5.9.3)(yaml@2.9.0))(msw@2.13.6(@types/node@24.12.4)(typescript@5.9.3))(vitest@5.0.3) + '@vitest/browser-preview': 5.0.3(@voidzero-dev/vite-plus-core@1.1.0(@arethetypeswrong/core@0.18.5)(@types/node@24.12.4)(esbuild@0.28.1)(jiti@2.6.1)(publint@0.3.25)(terser@5.44.1)(tsx@4.21.0)(typescript@5.9.3)(yaml@2.9.0))(msw@2.13.6(@types/node@24.12.4)(typescript@5.9.3))(vitest@5.0.3) + '@vitest/mocker': 5.0.3(@voidzero-dev/vite-plus-core@1.1.0(@arethetypeswrong/core@0.18.5)(@types/node@24.12.4)(esbuild@0.28.1)(jiti@2.6.1)(publint@0.3.25)(terser@5.44.1)(tsx@4.21.0)(typescript@5.9.3)(yaml@2.9.0))(msw@2.13.6(@types/node@24.12.4)(typescript@5.9.3)) + '@vitest/pretty-format': 5.0.3 + '@vitest/snapshot': 5.0.3 + '@vitest/spy': 5.0.3 + '@vitest/utils': 5.0.3 + oxfmt: 0.72.0(vite-plus@1.1.0(@arethetypeswrong/core@0.18.5)(@opentelemetry/api@1.9.0)(@types/node@24.12.4)(@vitest/ui@5.0.3(vitest@5.0.3))(esbuild@0.28.1)(jiti@2.6.1)(msw@2.13.6(@types/node@24.12.4)(typescript@5.9.3))(publint@0.3.25)(terser@5.44.1)(tsx@4.21.0)(typescript@5.9.3)(yaml@2.9.0)) + oxlint: 1.87.0(oxlint-tsgolint@7.0.2003)(vite-plus@1.1.0(@arethetypeswrong/core@0.18.5)(@opentelemetry/api@1.9.0)(@types/node@24.12.4)(@vitest/ui@5.0.3(vitest@5.0.3))(esbuild@0.28.1)(jiti@2.6.1)(msw@2.13.6(@types/node@24.12.4)(typescript@5.9.3))(publint@0.3.25)(terser@5.44.1)(tsx@4.21.0)(typescript@5.9.3)(yaml@2.9.0)) + oxlint-tsgolint: 7.0.2003 + vite: '@voidzero-dev/vite-plus-core@1.1.0(@arethetypeswrong/core@0.18.5)(@types/node@24.12.4)(esbuild@0.28.1)(jiti@2.6.1)(publint@0.3.25)(terser@5.44.1)(tsx@4.21.0)(typescript@5.9.3)(yaml@2.9.0)' + vitest: 5.0.3(@opentelemetry/api@1.9.0)(@types/node@24.12.4)(@vitest/browser-preview@5.0.3)(@vitest/ui@5.0.3(vitest@5.0.3))(@voidzero-dev/vite-plus-core@1.1.0(@arethetypeswrong/core@0.18.5)(@types/node@24.12.4)(esbuild@0.28.1)(jiti@2.6.1)(publint@0.3.25)(terser@5.44.1)(tsx@4.21.0)(typescript@5.9.3)(yaml@2.9.0))(msw@2.13.6(@types/node@24.12.4)(typescript@5.9.3)) + optionalDependencies: + '@voidzero-dev/vite-plus-darwin-arm64': 1.1.0 + '@voidzero-dev/vite-plus-darwin-x64': 1.1.0 + '@voidzero-dev/vite-plus-linux-arm64-gnu': 1.1.0 + '@voidzero-dev/vite-plus-linux-arm64-musl': 1.1.0 + '@voidzero-dev/vite-plus-linux-x64-gnu': 1.1.0 + '@voidzero-dev/vite-plus-linux-x64-musl': 1.1.0 + '@voidzero-dev/vite-plus-win32-arm64-msvc': 1.1.0 + '@voidzero-dev/vite-plus-win32-x64-msvc': 1.1.0 + transitivePeerDependencies: + - '@arethetypeswrong/core' + - '@edge-runtime/vm' + - '@opentelemetry/api' + - '@types/node' + - '@typescript/native-preview' + - '@vitejs/devtools' + - '@vitest/coverage-istanbul' + - '@vitest/coverage-v8' + - '@vitest/ui' + - '@volar/typescript' + - '@vue/language-core' + - bufferutil + - esbuild + - happy-dom + - jiti + - jsdom + - less + - msw + - publint + - sass + - sass-embedded + - stylus + - sugarss + - svelte + - terser + - tsx - typescript + - unplugin-unused + - unrun + - utf-8-validate + - vue-tsc + - yaml - vite@8.1.5(@types/node@20.19.40)(esbuild@0.28.1)(jiti@2.6.1)(terser@5.44.1)(tsx@4.21.0)(yaml@2.9.0): + vitest@5.0.3(@opentelemetry/api@1.9.0)(@types/node@20.19.40)(@vitest/browser-preview@5.0.3)(@vitest/ui@5.0.3(vitest@5.0.3))(@voidzero-dev/vite-plus-core@1.1.0(@arethetypeswrong/core@0.18.5)(@types/node@20.19.40)(esbuild@0.28.1)(jiti@2.6.1)(publint@0.3.25)(terser@5.44.1)(tsx@4.21.0)(typescript@5.4.4)(yaml@2.9.0))(msw@2.13.6(@types/node@20.19.40)(typescript@5.4.4)): dependencies: - lightningcss: 1.32.0 + '@types/chai': 5.2.3 + '@vitest/mocker': 5.0.3(@voidzero-dev/vite-plus-core@1.1.0(@arethetypeswrong/core@0.18.5)(@types/node@20.19.40)(esbuild@0.28.1)(jiti@2.6.1)(publint@0.3.25)(terser@5.44.1)(tsx@4.21.0)(typescript@5.4.4)(yaml@2.9.0))(msw@2.13.6(@types/node@20.19.40)(typescript@5.4.4)) + chai: 6.2.2 + es-module-lexer: 2.3.2 + expect-type: 1.4.0 + magic-string: 1.4.3 + obug: 2.2.1 picomatch: 4.0.7 - postcss: 8.5.26 - rolldown: 1.1.5 + std-env: 4.2.0 + tinybench: 6.2.2 + tinyexec: 1.3.1 tinyglobby: 0.2.17 + vite: '@voidzero-dev/vite-plus-core@1.1.0(@arethetypeswrong/core@0.18.5)(@types/node@20.19.40)(esbuild@0.28.1)(jiti@2.6.1)(publint@0.3.25)(terser@5.44.1)(tsx@4.21.0)(typescript@5.4.4)(yaml@2.9.0)' + why-is-node-running: 3.2.1 optionalDependencies: + '@opentelemetry/api': 1.9.0 '@types/node': 20.19.40 - esbuild: 0.28.1 - fsevents: 2.3.3 - jiti: 2.6.1 - terser: 5.44.1 - tsx: 4.21.0 - yaml: 2.9.0 + '@vitest/browser-preview': 5.0.3(@voidzero-dev/vite-plus-core@1.1.0(@arethetypeswrong/core@0.18.5)(@types/node@20.19.40)(esbuild@0.28.1)(jiti@2.6.1)(publint@0.3.25)(terser@5.44.1)(tsx@4.21.0)(typescript@5.4.4)(yaml@2.9.0))(msw@2.13.6(@types/node@20.19.40)(typescript@5.4.4))(vitest@5.0.3) + '@vitest/ui': 5.0.3(vitest@5.0.3) + transitivePeerDependencies: + - msw - vite@8.1.5(@types/node@22.19.1)(esbuild@0.28.1)(jiti@2.6.1)(terser@5.44.1)(tsx@4.21.0)(yaml@2.9.0): + vitest@5.0.3(@opentelemetry/api@1.9.0)(@types/node@20.19.40)(@vitest/browser-preview@5.0.3)(@vitest/ui@5.0.3(vitest@5.0.3))(@voidzero-dev/vite-plus-core@1.1.0(@arethetypeswrong/core@0.18.5)(@types/node@20.19.40)(esbuild@0.28.1)(jiti@2.6.1)(publint@0.3.25)(terser@5.44.1)(tsx@4.21.0)(typescript@5.9.3)(yaml@2.9.0))(msw@2.13.6(@types/node@20.19.40)(typescript@5.9.3)): dependencies: - lightningcss: 1.32.0 + '@types/chai': 5.2.3 + '@vitest/mocker': 5.0.3(@voidzero-dev/vite-plus-core@1.1.0(@arethetypeswrong/core@0.18.5)(@types/node@20.19.40)(esbuild@0.28.1)(jiti@2.6.1)(publint@0.3.25)(terser@5.44.1)(tsx@4.21.0)(typescript@5.9.3)(yaml@2.9.0))(msw@2.13.6(@types/node@20.19.40)(typescript@5.9.3)) + chai: 6.2.2 + es-module-lexer: 2.3.2 + expect-type: 1.4.0 + magic-string: 1.4.3 + obug: 2.2.1 picomatch: 4.0.7 - postcss: 8.5.26 - rolldown: 1.1.5 + std-env: 4.2.0 + tinybench: 6.2.2 + tinyexec: 1.3.1 tinyglobby: 0.2.17 + vite: '@voidzero-dev/vite-plus-core@1.1.0(@arethetypeswrong/core@0.18.5)(@types/node@20.19.40)(esbuild@0.28.1)(jiti@2.6.1)(publint@0.3.25)(terser@5.44.1)(tsx@4.21.0)(typescript@5.9.3)(yaml@2.9.0)' + why-is-node-running: 3.2.1 optionalDependencies: - '@types/node': 22.19.1 - esbuild: 0.28.1 - fsevents: 2.3.3 - jiti: 2.6.1 - terser: 5.44.1 - tsx: 4.21.0 - yaml: 2.9.0 + '@opentelemetry/api': 1.9.0 + '@types/node': 20.19.40 + '@vitest/browser-preview': 5.0.3(@voidzero-dev/vite-plus-core@1.1.0(@arethetypeswrong/core@0.18.5)(@types/node@20.19.40)(esbuild@0.28.1)(jiti@2.6.1)(publint@0.3.25)(terser@5.44.1)(tsx@4.21.0)(typescript@5.9.3)(yaml@2.9.0))(msw@2.13.6(@types/node@20.19.40)(typescript@5.9.3))(vitest@5.0.3) + '@vitest/ui': 5.0.3(vitest@5.0.3) + transitivePeerDependencies: + - msw - vitest@4.1.11(@opentelemetry/api@1.9.0)(@types/node@20.19.40)(msw@2.13.6(@types/node@20.19.40)(typescript@5.4.4))(vite@8.1.5(@types/node@20.19.40)(esbuild@0.28.1)(jiti@2.6.1)(terser@5.44.1)(tsx@4.21.0)(yaml@2.9.0)): + vitest@5.0.3(@opentelemetry/api@1.9.0)(@types/node@20.19.40)(@vitest/browser-preview@5.0.3)(@vitest/ui@5.0.3)(@voidzero-dev/vite-plus-core@1.1.0(@arethetypeswrong/core@0.18.5)(@types/node@20.19.40)(esbuild@0.28.1)(jiti@2.6.1)(publint@0.3.25)(terser@5.44.1)(tsx@4.21.0)(typescript@5.4.4)(yaml@2.9.0))(msw@2.13.6(@types/node@20.19.40)(typescript@5.4.4)): dependencies: - '@vitest/expect': 4.1.11 - '@vitest/mocker': 4.1.11(msw@2.13.6(@types/node@20.19.40)(typescript@5.4.4))(vite@8.1.5(@types/node@20.19.40)(esbuild@0.28.1)(jiti@2.6.1)(terser@5.44.1)(tsx@4.21.0)(yaml@2.9.0)) - '@vitest/pretty-format': 4.1.11 - '@vitest/runner': 4.1.11 - '@vitest/snapshot': 4.1.11 - '@vitest/spy': 4.1.11 - '@vitest/utils': 4.1.11 + '@types/chai': 5.2.3 + '@vitest/mocker': 5.0.3(@voidzero-dev/vite-plus-core@1.1.0(@arethetypeswrong/core@0.18.5)(@types/node@20.19.40)(esbuild@0.28.1)(jiti@2.6.1)(publint@0.3.25)(terser@5.44.1)(tsx@4.21.0)(typescript@5.4.4)(yaml@2.9.0))(msw@2.13.6(@types/node@20.19.40)(typescript@5.4.4)) + chai: 6.2.2 es-module-lexer: 2.3.2 expect-type: 1.4.0 - magic-string: 0.30.21 + magic-string: 1.4.3 obug: 2.2.1 - pathe: 2.0.3 picomatch: 4.0.7 std-env: 4.2.0 - tinybench: 2.9.0 + tinybench: 6.2.2 tinyexec: 1.3.1 tinyglobby: 0.2.17 - tinyrainbow: 3.1.1 - vite: 8.1.5(@types/node@20.19.40)(esbuild@0.28.1)(jiti@2.6.1)(terser@5.44.1)(tsx@4.21.0)(yaml@2.9.0) - why-is-node-running: 2.3.0 + vite: '@voidzero-dev/vite-plus-core@1.1.0(@arethetypeswrong/core@0.18.5)(@types/node@20.19.40)(esbuild@0.28.1)(jiti@2.6.1)(publint@0.3.25)(terser@5.44.1)(tsx@4.21.0)(typescript@5.4.4)(yaml@2.9.0)' + why-is-node-running: 3.2.1 optionalDependencies: '@opentelemetry/api': 1.9.0 '@types/node': 20.19.40 + '@vitest/browser-preview': 5.0.3(@voidzero-dev/vite-plus-core@1.1.0(@arethetypeswrong/core@0.18.5)(@types/node@20.19.40)(esbuild@0.28.1)(jiti@2.6.1)(publint@0.3.25)(terser@5.44.1)(tsx@4.21.0)(typescript@5.4.4)(yaml@2.9.0))(msw@2.13.6(@types/node@20.19.40)(typescript@5.4.4))(vitest@5.0.3) + '@vitest/ui': 5.0.3(vitest@5.0.3) transitivePeerDependencies: - msw - vitest@4.1.11(@opentelemetry/api@1.9.0)(@types/node@20.19.40)(msw@2.13.6(@types/node@20.19.40)(typescript@5.5.4))(vite@8.1.5(@types/node@20.19.40)(esbuild@0.28.1)(jiti@2.6.1)(terser@5.44.1)(tsx@4.21.0)(yaml@2.9.0)): + vitest@5.0.3(@opentelemetry/api@1.9.0)(@types/node@20.19.40)(@vitest/browser-preview@5.0.3)(@vitest/ui@5.0.3)(@voidzero-dev/vite-plus-core@1.1.0(@arethetypeswrong/core@0.18.5)(@types/node@20.19.40)(esbuild@0.28.1)(jiti@2.6.1)(publint@0.3.25)(terser@5.44.1)(tsx@4.21.0)(typescript@5.5.4)(yaml@2.9.0))(msw@2.13.6(@types/node@20.19.40)(typescript@5.5.4)): dependencies: - '@vitest/expect': 4.1.11 - '@vitest/mocker': 4.1.11(msw@2.13.6(@types/node@20.19.40)(typescript@5.5.4))(vite@8.1.5(@types/node@20.19.40)(esbuild@0.28.1)(jiti@2.6.1)(terser@5.44.1)(tsx@4.21.0)(yaml@2.9.0)) - '@vitest/pretty-format': 4.1.11 - '@vitest/runner': 4.1.11 - '@vitest/snapshot': 4.1.11 - '@vitest/spy': 4.1.11 - '@vitest/utils': 4.1.11 + '@types/chai': 5.2.3 + '@vitest/mocker': 5.0.3(@voidzero-dev/vite-plus-core@1.1.0(@arethetypeswrong/core@0.18.5)(@types/node@20.19.40)(esbuild@0.28.1)(jiti@2.6.1)(publint@0.3.25)(terser@5.44.1)(tsx@4.21.0)(typescript@5.5.4)(yaml@2.9.0))(msw@2.13.6(@types/node@20.19.40)(typescript@5.5.4)) + chai: 6.2.2 es-module-lexer: 2.3.2 expect-type: 1.4.0 - magic-string: 0.30.21 + magic-string: 1.4.3 obug: 2.2.1 - pathe: 2.0.3 picomatch: 4.0.7 std-env: 4.2.0 - tinybench: 2.9.0 + tinybench: 6.2.2 tinyexec: 1.3.1 tinyglobby: 0.2.17 - tinyrainbow: 3.1.1 - vite: 8.1.5(@types/node@20.19.40)(esbuild@0.28.1)(jiti@2.6.1)(terser@5.44.1)(tsx@4.21.0)(yaml@2.9.0) - why-is-node-running: 2.3.0 + vite: '@voidzero-dev/vite-plus-core@1.1.0(@arethetypeswrong/core@0.18.5)(@types/node@20.19.40)(esbuild@0.28.1)(jiti@2.6.1)(publint@0.3.25)(terser@5.44.1)(tsx@4.21.0)(typescript@5.5.4)(yaml@2.9.0)' + why-is-node-running: 3.2.1 optionalDependencies: '@opentelemetry/api': 1.9.0 '@types/node': 20.19.40 + '@vitest/browser-preview': 5.0.3(@voidzero-dev/vite-plus-core@1.1.0(@arethetypeswrong/core@0.18.5)(@types/node@20.19.40)(esbuild@0.28.1)(jiti@2.6.1)(publint@0.3.25)(terser@5.44.1)(tsx@4.21.0)(typescript@5.5.4)(yaml@2.9.0))(msw@2.13.6(@types/node@20.19.40)(typescript@5.5.4))(vitest@5.0.3) + '@vitest/ui': 5.0.3(vitest@5.0.3) transitivePeerDependencies: - msw - vitest@4.1.11(@opentelemetry/api@1.9.0)(@types/node@20.19.40)(msw@2.13.6(@types/node@20.19.40)(typescript@5.9.3))(vite@8.1.5(@types/node@20.19.40)(esbuild@0.28.1)(jiti@2.6.1)(terser@5.44.1)(tsx@4.21.0)(yaml@2.9.0)): + vitest@5.0.3(@opentelemetry/api@1.9.0)(@types/node@22.19.1)(@vitest/browser-preview@5.0.3)(@vitest/ui@5.0.3(vitest@5.0.3))(@voidzero-dev/vite-plus-core@1.1.0(@arethetypeswrong/core@0.18.5)(@types/node@22.19.1)(esbuild@0.28.1)(jiti@2.6.1)(publint@0.3.25)(terser@5.44.1)(tsx@4.21.0)(typescript@5.5.4)(yaml@2.9.0))(msw@2.13.6(@types/node@22.19.1)(typescript@5.5.4)): dependencies: - '@vitest/expect': 4.1.11 - '@vitest/mocker': 4.1.11(msw@2.13.6(@types/node@20.19.40)(typescript@5.9.3))(vite@8.1.5(@types/node@20.19.40)(esbuild@0.28.1)(jiti@2.6.1)(terser@5.44.1)(tsx@4.21.0)(yaml@2.9.0)) - '@vitest/pretty-format': 4.1.11 - '@vitest/runner': 4.1.11 - '@vitest/snapshot': 4.1.11 - '@vitest/spy': 4.1.11 - '@vitest/utils': 4.1.11 + '@types/chai': 5.2.3 + '@vitest/mocker': 5.0.3(@voidzero-dev/vite-plus-core@1.1.0(@arethetypeswrong/core@0.18.5)(@types/node@22.19.1)(esbuild@0.28.1)(jiti@2.6.1)(publint@0.3.25)(terser@5.44.1)(tsx@4.21.0)(typescript@5.5.4)(yaml@2.9.0))(msw@2.13.6(@types/node@22.19.1)(typescript@5.5.4)) + chai: 6.2.2 es-module-lexer: 2.3.2 expect-type: 1.4.0 - magic-string: 0.30.21 + magic-string: 1.4.3 obug: 2.2.1 - pathe: 2.0.3 picomatch: 4.0.7 std-env: 4.2.0 - tinybench: 2.9.0 + tinybench: 6.2.2 tinyexec: 1.3.1 tinyglobby: 0.2.17 - tinyrainbow: 3.1.1 - vite: 8.1.5(@types/node@20.19.40)(esbuild@0.28.1)(jiti@2.6.1)(terser@5.44.1)(tsx@4.21.0)(yaml@2.9.0) - why-is-node-running: 2.3.0 + vite: '@voidzero-dev/vite-plus-core@1.1.0(@arethetypeswrong/core@0.18.5)(@types/node@22.19.1)(esbuild@0.28.1)(jiti@2.6.1)(publint@0.3.25)(terser@5.44.1)(tsx@4.21.0)(typescript@5.5.4)(yaml@2.9.0)' + why-is-node-running: 3.2.1 optionalDependencies: '@opentelemetry/api': 1.9.0 - '@types/node': 20.19.40 + '@types/node': 22.19.1 + '@vitest/browser-preview': 5.0.3(@voidzero-dev/vite-plus-core@1.1.0(@arethetypeswrong/core@0.18.5)(@types/node@22.19.1)(esbuild@0.28.1)(jiti@2.6.1)(publint@0.3.25)(terser@5.44.1)(tsx@4.21.0)(typescript@5.5.4)(yaml@2.9.0))(msw@2.13.6(@types/node@22.19.1)(typescript@5.5.4))(vitest@5.0.3) + '@vitest/ui': 5.0.3(vitest@5.0.3) transitivePeerDependencies: - msw - vitest@4.1.11(@opentelemetry/api@1.9.0)(@types/node@22.19.1)(msw@2.13.6(@types/node@22.19.1)(typescript@5.5.4))(vite@8.1.5(@types/node@22.19.1)(esbuild@0.28.1)(jiti@2.6.1)(terser@5.44.1)(tsx@4.21.0)(yaml@2.9.0)): + vitest@5.0.3(@opentelemetry/api@1.9.0)(@types/node@22.19.1)(@vitest/browser-preview@5.0.3)(@vitest/ui@5.0.3(vitest@5.0.3))(@voidzero-dev/vite-plus-core@1.1.0(@arethetypeswrong/core@0.18.5)(@types/node@22.19.1)(esbuild@0.28.1)(jiti@2.6.1)(publint@0.3.25)(terser@5.44.1)(tsx@4.21.0)(typescript@5.9.3)(yaml@2.9.0))(msw@2.13.6(@types/node@22.19.1)(typescript@5.9.3)): dependencies: - '@vitest/expect': 4.1.11 - '@vitest/mocker': 4.1.11(msw@2.13.6(@types/node@22.19.1)(typescript@5.5.4))(vite@8.1.5(@types/node@22.19.1)(esbuild@0.28.1)(jiti@2.6.1)(terser@5.44.1)(tsx@4.21.0)(yaml@2.9.0)) - '@vitest/pretty-format': 4.1.11 - '@vitest/runner': 4.1.11 - '@vitest/snapshot': 4.1.11 - '@vitest/spy': 4.1.11 - '@vitest/utils': 4.1.11 + '@types/chai': 5.2.3 + '@vitest/mocker': 5.0.3(@voidzero-dev/vite-plus-core@1.1.0(@arethetypeswrong/core@0.18.5)(@types/node@22.19.1)(esbuild@0.28.1)(jiti@2.6.1)(publint@0.3.25)(terser@5.44.1)(tsx@4.21.0)(typescript@5.9.3)(yaml@2.9.0))(msw@2.13.6(@types/node@22.19.1)(typescript@5.9.3)) + chai: 6.2.2 es-module-lexer: 2.3.2 expect-type: 1.4.0 - magic-string: 0.30.21 + magic-string: 1.4.3 obug: 2.2.1 - pathe: 2.0.3 picomatch: 4.0.7 std-env: 4.2.0 - tinybench: 2.9.0 + tinybench: 6.2.2 tinyexec: 1.3.1 tinyglobby: 0.2.17 - tinyrainbow: 3.1.1 - vite: 8.1.5(@types/node@22.19.1)(esbuild@0.28.1)(jiti@2.6.1)(terser@5.44.1)(tsx@4.21.0)(yaml@2.9.0) - why-is-node-running: 2.3.0 + vite: '@voidzero-dev/vite-plus-core@1.1.0(@arethetypeswrong/core@0.18.5)(@types/node@22.19.1)(esbuild@0.28.1)(jiti@2.6.1)(publint@0.3.25)(terser@5.44.1)(tsx@4.21.0)(typescript@5.9.3)(yaml@2.9.0)' + why-is-node-running: 3.2.1 optionalDependencies: '@opentelemetry/api': 1.9.0 '@types/node': 22.19.1 + '@vitest/browser-preview': 5.0.3(@voidzero-dev/vite-plus-core@1.1.0(@arethetypeswrong/core@0.18.5)(@types/node@22.19.1)(esbuild@0.28.1)(jiti@2.6.1)(publint@0.3.25)(terser@5.44.1)(tsx@4.21.0)(typescript@5.9.3)(yaml@2.9.0))(msw@2.13.6(@types/node@22.19.1)(typescript@5.9.3))(vitest@5.0.3) + '@vitest/ui': 5.0.3(vitest@5.0.3) transitivePeerDependencies: - msw - vitest@4.1.11(@opentelemetry/api@1.9.0)(@types/node@22.19.1)(msw@2.13.6(@types/node@22.19.1)(typescript@5.9.3))(vite@8.1.5(@types/node@22.19.1)(esbuild@0.28.1)(jiti@2.6.1)(terser@5.44.1)(tsx@4.21.0)(yaml@2.9.0)): + vitest@5.0.3(@opentelemetry/api@1.9.0)(@types/node@22.19.1)(@vitest/browser-preview@5.0.3)(@vitest/ui@5.0.3)(@voidzero-dev/vite-plus-core@1.1.0(@arethetypeswrong/core@0.18.5)(@types/node@22.19.1)(esbuild@0.28.1)(jiti@2.6.1)(publint@0.3.25)(terser@5.44.1)(tsx@4.21.0)(typescript@5.5.4)(yaml@2.9.0))(msw@2.13.6(@types/node@22.19.1)(typescript@5.5.4)): dependencies: - '@vitest/expect': 4.1.11 - '@vitest/mocker': 4.1.11(msw@2.13.6(@types/node@22.19.1)(typescript@5.9.3))(vite@8.1.5(@types/node@22.19.1)(esbuild@0.28.1)(jiti@2.6.1)(terser@5.44.1)(tsx@4.21.0)(yaml@2.9.0)) - '@vitest/pretty-format': 4.1.11 - '@vitest/runner': 4.1.11 - '@vitest/snapshot': 4.1.11 - '@vitest/spy': 4.1.11 - '@vitest/utils': 4.1.11 + '@types/chai': 5.2.3 + '@vitest/mocker': 5.0.3(@voidzero-dev/vite-plus-core@1.1.0(@arethetypeswrong/core@0.18.5)(@types/node@22.19.1)(esbuild@0.28.1)(jiti@2.6.1)(publint@0.3.25)(terser@5.44.1)(tsx@4.21.0)(typescript@5.5.4)(yaml@2.9.0))(msw@2.13.6(@types/node@22.19.1)(typescript@5.5.4)) + chai: 6.2.2 es-module-lexer: 2.3.2 expect-type: 1.4.0 - magic-string: 0.30.21 + magic-string: 1.4.3 obug: 2.2.1 - pathe: 2.0.3 picomatch: 4.0.7 std-env: 4.2.0 - tinybench: 2.9.0 + tinybench: 6.2.2 tinyexec: 1.3.1 tinyglobby: 0.2.17 - tinyrainbow: 3.1.1 - vite: 8.1.5(@types/node@22.19.1)(esbuild@0.28.1)(jiti@2.6.1)(terser@5.44.1)(tsx@4.21.0)(yaml@2.9.0) - why-is-node-running: 2.3.0 + vite: '@voidzero-dev/vite-plus-core@1.1.0(@arethetypeswrong/core@0.18.5)(@types/node@22.19.1)(esbuild@0.28.1)(jiti@2.6.1)(publint@0.3.25)(terser@5.44.1)(tsx@4.21.0)(typescript@5.5.4)(yaml@2.9.0)' + why-is-node-running: 3.2.1 optionalDependencies: '@opentelemetry/api': 1.9.0 '@types/node': 22.19.1 + '@vitest/browser-preview': 5.0.3(@voidzero-dev/vite-plus-core@1.1.0(@arethetypeswrong/core@0.18.5)(@types/node@22.19.1)(esbuild@0.28.1)(jiti@2.6.1)(publint@0.3.25)(terser@5.44.1)(tsx@4.21.0)(typescript@5.5.4)(yaml@2.9.0))(msw@2.13.6(@types/node@22.19.1)(typescript@5.5.4))(vitest@5.0.3) + '@vitest/ui': 5.0.3(vitest@5.0.3) + transitivePeerDependencies: + - msw + + vitest@5.0.3(@opentelemetry/api@1.9.0)(@types/node@24.12.4)(@vitest/browser-preview@5.0.3)(@vitest/ui@5.0.3(vitest@5.0.3))(@voidzero-dev/vite-plus-core@1.1.0(@arethetypeswrong/core@0.18.5)(@types/node@24.12.4)(esbuild@0.28.1)(jiti@2.6.1)(publint@0.3.25)(terser@5.44.1)(tsx@4.21.0)(typescript@5.9.3)(yaml@2.9.0))(msw@2.13.6(@types/node@24.12.4)(typescript@5.9.3)): + dependencies: + '@types/chai': 5.2.3 + '@vitest/mocker': 5.0.3(@voidzero-dev/vite-plus-core@1.1.0(@arethetypeswrong/core@0.18.5)(@types/node@24.12.4)(esbuild@0.28.1)(jiti@2.6.1)(publint@0.3.25)(terser@5.44.1)(tsx@4.21.0)(typescript@5.9.3)(yaml@2.9.0))(msw@2.13.6(@types/node@24.12.4)(typescript@5.9.3)) + chai: 6.2.2 + es-module-lexer: 2.3.2 + expect-type: 1.4.0 + magic-string: 1.4.3 + obug: 2.2.1 + picomatch: 4.0.7 + std-env: 4.2.0 + tinybench: 6.2.2 + tinyexec: 1.3.1 + tinyglobby: 0.2.17 + vite: '@voidzero-dev/vite-plus-core@1.1.0(@arethetypeswrong/core@0.18.5)(@types/node@24.12.4)(esbuild@0.28.1)(jiti@2.6.1)(publint@0.3.25)(terser@5.44.1)(tsx@4.21.0)(typescript@5.9.3)(yaml@2.9.0)' + why-is-node-running: 3.2.1 + optionalDependencies: + '@opentelemetry/api': 1.9.0 + '@types/node': 24.12.4 + '@vitest/browser-preview': 5.0.3(@voidzero-dev/vite-plus-core@1.1.0(@arethetypeswrong/core@0.18.5)(@types/node@24.12.4)(esbuild@0.28.1)(jiti@2.6.1)(publint@0.3.25)(terser@5.44.1)(tsx@4.21.0)(typescript@5.9.3)(yaml@2.9.0))(msw@2.13.6(@types/node@24.12.4)(typescript@5.9.3))(vitest@5.0.3) + '@vitest/ui': 5.0.3(vitest@5.0.3) transitivePeerDependencies: - msw @@ -8641,7 +8814,47 @@ snapshots: webpack-virtual-modules@0.6.2: {} - webpack@5.106.2(@swc/core@1.15.8)(esbuild@0.28.1)(postcss@8.5.26): + webpack@5.106.2(@swc/core@1.15.8)(esbuild@0.28.1): + dependencies: + '@types/eslint-scope': 3.7.7 + '@types/estree': 1.0.9 + '@types/json-schema': 7.0.15 + '@webassemblyjs/ast': 1.14.1 + '@webassemblyjs/wasm-edit': 1.14.1 + '@webassemblyjs/wasm-parser': 1.14.1 + acorn: 8.16.0 + acorn-import-phases: 1.0.4(acorn@8.16.0) + browserslist: 4.28.8 + chrome-trace-event: 1.0.4 + enhanced-resolve: 5.21.2 + es-module-lexer: 2.3.2 + eslint-scope: 5.1.1 + events: 3.3.0 + glob-to-regexp: 0.4.1 + graceful-fs: 4.2.11 + loader-runner: 4.3.1 + mime-db: 1.54.0 + neo-async: 2.6.2 + schema-utils: 4.3.3 + tapable: 2.3.3 + terser-webpack-plugin: 5.6.0(@swc/core@1.15.8)(esbuild@0.28.1)(webpack@5.106.2(@swc/core@1.15.8)(esbuild@0.28.1)) + watchpack: 2.5.1 + webpack-sources: 3.4.1 + transitivePeerDependencies: + - '@minify-html/node' + - '@swc/core' + - '@swc/css' + - '@swc/html' + - clean-css + - cssnano + - csso + - esbuild + - html-minifier-terser + - lightningcss + - postcss + - uglify-js + + webpack@5.106.2(esbuild@0.28.1): dependencies: '@types/eslint-scope': 3.7.7 '@types/estree': 1.0.9 @@ -8664,7 +8877,7 @@ snapshots: neo-async: 2.6.2 schema-utils: 4.3.3 tapable: 2.3.3 - terser-webpack-plugin: 5.6.0(@swc/core@1.15.8)(esbuild@0.28.1)(postcss@8.5.26)(webpack@5.106.2(@swc/core@1.15.8)(esbuild@0.28.1)(postcss@8.5.26)) + terser-webpack-plugin: 5.6.0(esbuild@0.28.1)(webpack@5.106.2(esbuild@0.28.1)) watchpack: 2.5.1 webpack-sources: 3.4.1 transitivePeerDependencies: @@ -8690,10 +8903,7 @@ snapshots: dependencies: isexe: 2.0.0 - why-is-node-running@2.3.0: - dependencies: - siginfo: 2.0.0 - stackback: 0.0.2 + why-is-node-running@3.2.1: {} word-wrap@1.2.5: {} @@ -8705,12 +8915,6 @@ snapshots: string-width: 4.2.3 strip-ansi: 6.0.1 - wrap-ansi@8.1.0: - dependencies: - ansi-styles: 6.2.1 - string-width: 5.1.2 - strip-ansi: 7.1.2 - wrap-ansi@9.0.2: dependencies: ansi-styles: 6.2.1 @@ -8764,12 +8968,49 @@ snapshots: yocto-queue@0.1.0: {} + yuku-ast@0.9.5: + dependencies: + '@yuku-toolchain/types': 0.9.5 + + yuku-codegen@0.9.5: + dependencies: + '@yuku-toolchain/types': 0.9.5 + optionalDependencies: + '@yuku-codegen/binding-android-arm64': 0.9.5 + '@yuku-codegen/binding-darwin-arm64': 0.9.5 + '@yuku-codegen/binding-darwin-x64': 0.9.5 + '@yuku-codegen/binding-freebsd-x64': 0.9.5 + '@yuku-codegen/binding-linux-arm-gnu': 0.9.5 + '@yuku-codegen/binding-linux-arm-musl': 0.9.5 + '@yuku-codegen/binding-linux-arm64-gnu': 0.9.5 + '@yuku-codegen/binding-linux-arm64-musl': 0.9.5 + '@yuku-codegen/binding-linux-x64-gnu': 0.9.5 + '@yuku-codegen/binding-linux-x64-musl': 0.9.5 + '@yuku-codegen/binding-win32-arm64': 0.9.5 + '@yuku-codegen/binding-win32-x64': 0.9.5 + + yuku-parser@0.9.5: + dependencies: + '@yuku-toolchain/types': 0.9.5 + yuku-ast: 0.9.5 + optionalDependencies: + '@yuku-parser/binding-android-arm64': 0.9.5 + '@yuku-parser/binding-darwin-arm64': 0.9.5 + '@yuku-parser/binding-darwin-x64': 0.9.5 + '@yuku-parser/binding-freebsd-x64': 0.9.5 + '@yuku-parser/binding-linux-arm-gnu': 0.9.5 + '@yuku-parser/binding-linux-arm-musl': 0.9.5 + '@yuku-parser/binding-linux-arm64-gnu': 0.9.5 + '@yuku-parser/binding-linux-arm64-musl': 0.9.5 + '@yuku-parser/binding-linux-x64-gnu': 0.9.5 + '@yuku-parser/binding-linux-x64-musl': 0.9.5 + '@yuku-parser/binding-win32-arm64': 0.9.5 + '@yuku-parser/binding-win32-x64': 0.9.5 + zod-to-json-schema@3.25.1(zod@3.25.76): dependencies: zod: 3.25.76 - zod@3.25.67: {} - zod@3.25.76: {} zod@4.3.6: {} diff --git a/pnpm-workspace.yaml b/pnpm-workspace.yaml index dd63d0ea2..8254fe806 100644 --- a/pnpm-workspace.yaml +++ b/pnpm-workspace.yaml @@ -8,9 +8,59 @@ packages: blockExoticSubdeps: true minimumReleaseAge: 4320 # 3 days (in minutes) -minimumReleaseAgeExclude: - - "@braintrust/bt-*" # bt binary packages, published in lockstep with braintrust trustPolicy: no-downgrade # Ignore the check for packages published more than 30 days ago (pnpm 10.27+) # Useful for older packages that pre-date provenance support trustPolicyIgnoreAfter: 43200 # minutes (30 days) +ignoredBuiltDependencies: + - "@swc/core" + - esbuild + - msw + - protobufjs +overrides: + "handlebars": "^4.7.10" + "protobufjs": "^7.6.5" + "body-parser@>=2.0.0 <2.3.0": "^2.3.0" + "brace-expansion@>=3.0.0 <5.0.12": "^5.0.12" + "esbuild": "0.28.1" + "uuid": "11.1.1" + "router@2.2.0>path-to-regexp": "8.4.0" + "express@5.2.1>qs": "6.16.0" + "qs@>=6.7.0 <6.16.0": "^6.16.0" + "tar@>=7.0.0 <7.5.21": "^7.5.21" + "tar@>=6.0.0 <7.0.0": "^6.2.1" + "flatted@<3.4.2": "^3.4.2" + "markdown-it@>=13.0.0 <14.1.1": "^14.1.1" + "@mikro-orm/knex@>=6.0.0 <6.6.14": "^6.6.14" + "glob@>=10.2.0 <10.5.0": "^10.5.0" + "fast-uri@<3.1.8": "^3.1.8" + "linkify-it@<5.0.2": "^5.0.2" + "@hono/node-server@<1.19.15": "^1.19.15" + "nanoid@<3.3.18": "^3.3.18" + "postcss@<8.5.23": "^8.5.23" + "js-yaml@3": "^3.15.1" + "js-yaml@4": "^4.3.1" + "@opentelemetry/core@2": "^2.8.0" + "@ai-sdk/provider-utils": "^4.0.33" + "brace-expansion@<1.1.21": "^1.1.21" + "brace-expansion@>=2.0.0 <2.1.7": "^2.1.7" + "ip-address@<10.7.1": "^10.7.1" + "hono@<4.13.7": "^4.13.7" + "@grpc/grpc-js@>=1.14.0 <1.14.5": "^1.14.5" + "@modelcontextprotocol/sdk@>=1.12.0 <1.31.0": "^1.31.0" + "smol-toml@<1.9.0": "^1.9.0" + "source-map-js@<1.2.2": "^1.2.2" + "proxy-addr@<2.0.8": "^2.0.8" + vite@*: "catalog:" + vitest@*: "catalog:" +catalog: + vite: npm:@voidzero-dev/vite-plus-core@1.1.0 + vitest: 5.0.3 + vite-plus: 1.1.0 +peerDependencyRules: + allowAny: + - vite + - vitest + allowedVersions: + vite: "*" + vitest: "*" diff --git a/scripts/release/_shared.mjs b/scripts/release/_shared.mjs index 0bb25507a..637af7168 100644 --- a/scripts/release/_shared.mjs +++ b/scripts/release/_shared.mjs @@ -20,18 +20,12 @@ export const PUBLISHABLE_PACKAGES = [ dir: "integrations/deepseek-harness", name: "@braintrust/deepseek-harness", }, - { dir: "integrations/langchain-js", name: "@braintrust/langchain-js" }, - { dir: "integrations/openai-agents-js", name: "@braintrust/openai-agents" }, { dir: "integrations/otel-js", name: "@braintrust/otel" }, { dir: "integrations/templates-nunjucks", name: "@braintrust/templates-nunjucks-js", }, { dir: "integrations/temporal-js", name: "@braintrust/temporal" }, - { - dir: "integrations/vercel-ai-sdk", - name: "@braintrust/vercel-ai-sdk", - }, ]; export const PRIVATE_WORKSPACE_PACKAGES = [ diff --git a/turbo.json b/turbo.json deleted file mode 100644 index 6e5da8d56..000000000 --- a/turbo.json +++ /dev/null @@ -1,135 +0,0 @@ -{ - "$schema": "https://turbo.build/schema.json", - "tasks": { - "build": { - "dependsOn": ["^build"], - "outputs": [".next/**", "!.next/cache/**", "dist/**"], - "env": [] - }, - "yalc:publish": { - "cache": false, - "dependsOn": ["build"], - "outputs": [] - }, - "check:typings": { - "dependsOn": ["^build"], - "outputs": [] - }, - "test": { - "env": [ - "ANTHROPIC_API_KEY", - "BRAINTRUST_API_KEY", - "GEMINI_API_KEY", - "COHERE_API_KEY", - "CURSOR_API_KEY", - "GROQ_API_KEY", - "OPENAI_API_KEY", - "OPENAI_BASE_URL", - "OPENROUTER_API_KEY", - "MISTRAL_API_KEY", - "HUGGINGFACE_API_KEY" - ], - "dependsOn": ["^build", "build"], - "outputs": [] - }, - "test:e2e": { - "cache": false, - "env": [ - "BRAINTRUST_API_KEY", - "BRAINTRUST_E2E_PROJECT_NAME", - "BRAINTRUST_E2E_RUN_CONTEXT_DIR" - ], - "dependsOn": ["^build"], - "outputs": [] - }, - "test:e2e:update": { - "cache": false, - "dependsOn": ["^build"], - "outputs": [] - }, - "test:e2e:record": { - "cache": false, - "env": [ - "ANTHROPIC_API_KEY", - "BRAINTRUST_API_KEY", - "BRAINTRUST_E2E_CASSETTE_MODE", - "BRAINTRUST_E2E_PROJECT_NAME", - "BRAINTRUST_E2E_RUN_CONTEXT_DIR", - "GEMINI_API_KEY", - "COHERE_API_KEY", - "GROQ_API_KEY", - "OPENAI_API_KEY", - "OPENAI_BASE_URL", - "OPENROUTER_API_KEY", - "MISTRAL_API_KEY", - "HUGGINGFACE_API_KEY" - ], - "dependsOn": ["^build"], - "outputs": [] - }, - "lint": { - "outputs": [] - }, - "fix:lint": { - "outputs": [] - }, - "dev": { - "cache": false - }, - "start": { - "cache": false - }, - "clean": { - "cache": false - }, - "watch": {}, - "playground": { - "cache": false, - "passThroughEnv": [ - "ANTHROPIC_API_KEY", - "BRAINTRUST_API_KEY", - "GEMINI_API_KEY", - "COHERE_API_KEY", - "CURSOR_API_KEY", - "GROQ_API_KEY", - "OPENAI_API_KEY", - "OPENROUTER_API_KEY", - "MISTRAL_API_KEY", - "HUGGINGFACE_API_KEY" - ], - "dependsOn": ["^build", "build"] - }, - "playground:cli:push": { - "cache": false, - "passThroughEnv": [ - "ANTHROPIC_API_KEY", - "BRAINTRUST_API_KEY", - "GEMINI_API_KEY", - "COHERE_API_KEY", - "CURSOR_API_KEY", - "GROQ_API_KEY", - "OPENAI_API_KEY", - "OPENROUTER_API_KEY", - "MISTRAL_API_KEY", - "HUGGINGFACE_API_KEY" - ], - "dependsOn": ["^build", "build"] - }, - "playground:cli:eval": { - "cache": false, - "passThroughEnv": [ - "ANTHROPIC_API_KEY", - "BRAINTRUST_API_KEY", - "GEMINI_API_KEY", - "COHERE_API_KEY", - "CURSOR_API_KEY", - "GROQ_API_KEY", - "OPENAI_API_KEY", - "OPENROUTER_API_KEY", - "MISTRAL_API_KEY", - "HUGGINGFACE_API_KEY" - ], - "dependsOn": ["^build", "build"] - } - } -} diff --git a/vite.config.ts b/vite.config.ts new file mode 100644 index 000000000..11f91acad --- /dev/null +++ b/vite.config.ts @@ -0,0 +1,167 @@ +import { defineConfig } from "vite-plus"; + +export default defineConfig({ + fmt: { + printWidth: 80, + sortImports: false, + sortPackageJson: false, + ignorePatterns: [ + "**/dist/**", + "**/node_modules/**", + ".context/**", + "js/tests/auto-instrumentations/output-*/**", + "patches/**/*.patch", + "e2e/**/__snapshots__/**", + "**/pnpm-lock.yaml", + "**/deno.lock", + "**/generated*_types.ts", + "js/scripts/openapi_zod_client_output_template.hbs", + ".agents/skills/dotagents/**", + ], + }, + lint: { + options: { typeAware: true, typeCheck: true }, + plugins: ["typescript", "unicorn", "node"], + categories: { correctness: "off" }, + ignorePatterns: [ + "**/dist/**", + "**/node_modules/**", + ".context/**", + "**/vendor/**", + "**/generated*_types.ts", + "**/*.test.ts", + "**/*.test.tsx", + "js/src/auto-instrumentations/**", + "js/examples/**", + "js/smoke/**", + "js/tests/**", + "js/scripts/**", + "e2e/**", + "integrations/**", + "dev-packages/**", + "scripts/**", + ], + jsPlugins: ["./js/node_modules/eslint-plugin-n/lib/index.js"], + rules: { + "no-unused-vars": [ + "error", + { + args: "none", + caughtErrors: "none", + varsIgnorePattern: "^_", + ignoreRestSiblings: false, + }, + ], + "prefer-const": "error", + "typescript/no-explicit-any": "warn", + "typescript/no-empty-object-type": "error", + "typescript/no-unsafe-function-type": "error", + "typescript/prefer-as-const": "error", + "typescript/no-array-constructor": "error", + "typescript/no-duplicate-enum-values": "error", + "typescript/no-extra-non-null-assertion": "error", + "typescript/no-misused-new": "error", + "typescript/no-namespace": "error", + "typescript/no-non-null-asserted-optional-chain": "error", + "typescript/no-this-alias": "error", + "typescript/no-unnecessary-type-constraint": "error", + "typescript/no-wrapper-object-types": "error", + "typescript/prefer-namespace-keyword": "error", + "typescript/triple-slash-reference": "error", + "typescript/consistent-type-assertions": [ + "warn", + { assertionStyle: "never" }, + ], + "no-unused-expressions": ["error", { allowShortCircuit: true }], + "n/no-unsupported-features/node-builtins": [ + "error", + { version: ">=22.18.0", allowExperimental: true }, + ], + "n/no-unsupported-features/es-builtins": [ + "error", + { version: ">=22.18.0" }, + ], + "n/no-unsupported-features/es-syntax": [ + "error", + { version: ">=22.18.0" }, + ], + }, + overrides: [ + { + files: ["js/src/**/*.ts", "js/util/**/*.ts"], + rules: { + "no-restricted-imports": [ + "error", + { + patterns: [ + { + group: [ + "**/generated_types", + "**/generated_plain_types", + "**/generated_types.ts", + "**/generated_plain_types.ts", + ], + message: + "Use SDK-owned types and validators; generated backend types are compatibility fixtures.", + }, + ], + }, + ], + "no-restricted-properties": [ + "error", + ...["log", "warn", "error", "debug", "info", "trace"].map( + (property) => ({ + object: "console", + property, + message: "Use debugLogger instead of console for SDK logging.", + }), + ), + { + property: "enterWith", + message: + "Use AsyncLocalStorage.run(); enterWith is unsupported in Workers.", + }, + ], + "unicorn/prefer-node-protocol": "error", + }, + }, + { + files: ["js/src/**/*.ts"], + excludeFiles: ["js/src/{node,browser,workerd,edge-light}/index.ts"], + rules: { + "no-restricted-imports": [ + "error", + { + patterns: [ + { + group: [ + "**/generated_types", + "**/generated_plain_types", + "**/generated_types.ts", + "**/generated_plain_types.ts", + ], + message: + "Use SDK-owned types and validators; generated backend types are compatibility fixtures.", + }, + { + group: [ + "./exports", + "./exports.ts", + "../exports", + "../exports.ts", + ], + message: + "Import from the specific implementation module instead of the public barrel.", + }, + ], + }, + ], + }, + }, + { + files: ["js/src/queue.bench.ts"], + rules: { "no-restricted-properties": "off" }, + }, + ], + }, +});