diff --git a/.changeset/decouple-otel-sdk-versions.md b/.changeset/decouple-otel-sdk-versions.md new file mode 100644 index 000000000..ae8160ef3 --- /dev/null +++ b/.changeset/decouple-otel-sdk-versions.md @@ -0,0 +1,6 @@ +--- +"@braintrust/otel": major +"braintrust": patch +--- + +refactor(otel)!: Decouple from OpenTelemetry SDK versions diff --git a/.github/workflows/checks.yaml b/.github/workflows/checks.yaml index 91d746e97..aabb010ef 100644 --- a/.github/workflows/checks.yaml +++ b/.github/workflows/checks.yaml @@ -472,7 +472,7 @@ jobs: fail-fast: false matrix: node-version: [22.18.0, 24, 26] - test-dir: [otel-v1, otel-v2] + test-dir: [otel-v1, otel-v2, otel-v3] steps: - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 - name: Set up Docker Buildx diff --git a/e2e/config/pr-comment-scenarios.json b/e2e/config/pr-comment-scenarios.json index 0e79fe350..2dcab0311 100644 --- a/e2e/config/pr-comment-scenarios.json +++ b/e2e/config/pr-comment-scenarios.json @@ -1072,5 +1072,36 @@ "label": "v4 latest" } ] + }, + { + "scenarioDirName": "otel-sdk-compat", + "label": "OpenTelemetry SDK compatibility", + "metadataScenario": "otel-sdk-compat", + "variants": [ + { + "variantKey": "otel-v1", + "label": "v1 pinned" + }, + { + "variantKey": "otel-v1-latest", + "label": "v1 latest" + }, + { + "variantKey": "otel-v2", + "label": "v2 pinned" + }, + { + "variantKey": "otel-v2-latest", + "label": "v2 latest" + }, + { + "variantKey": "otel-v3", + "label": "v3 pinned" + }, + { + "variantKey": "otel-v3-latest", + "label": "v3 latest" + } + ] } ] diff --git a/e2e/package.json b/e2e/package.json index 164dc4065..5fc585da5 100644 --- a/e2e/package.json +++ b/e2e/package.json @@ -23,6 +23,7 @@ "typescript": "5.4.4", "vitest": "catalog:", "zod": "4.3.6", - "vite-plus": "catalog:" + "vite-plus": "catalog:", + "@opentelemetry/exporter-trace-otlp-http": "0.217.0" } } diff --git a/e2e/scenarios/ai-sdk-otel-export/scenario.impl.ts b/e2e/scenarios/ai-sdk-otel-export/scenario.impl.ts index 09a78cc99..f79073c8b 100644 --- a/e2e/scenarios/ai-sdk-otel-export/scenario.impl.ts +++ b/e2e/scenarios/ai-sdk-otel-export/scenario.impl.ts @@ -1,5 +1,6 @@ +import { OTLPTraceExporter } from "@opentelemetry/exporter-trace-otlp-http"; import { SimpleSpanProcessor } from "@opentelemetry/sdk-trace-base"; -import { BraintrustExporter } from "@braintrust/otel"; +import { BraintrustExporter, braintrustOtlpConfig } from "@braintrust/otel"; import { createTracerProvider, getTestRunId, @@ -32,9 +33,13 @@ export async function runAISDKOtelExport(options: AISDKOtelScenarioOptions) { const projectName = scopedName("e2e-ai-sdk-otel-export", testRunId); const exporter = new BraintrustExporter({ - apiKey: process.env.BRAINTRUST_API_KEY!, - apiUrl: process.env.BRAINTRUST_API_URL!, - parent: `project_name:${projectName}`, + exporter: new OTLPTraceExporter( + braintrustOtlpConfig({ + apiKey: process.env.BRAINTRUST_API_KEY!, + apiUrl: process.env.BRAINTRUST_API_URL!, + parent: `project_name:${projectName}`, + }), + ), filterAISpans: true, }); diff --git a/e2e/scenarios/nextjs-instrumentation/src/instrumentation.ts b/e2e/scenarios/nextjs-instrumentation/src/instrumentation.ts index 9beefd06f..3c300e339 100644 --- a/e2e/scenarios/nextjs-instrumentation/src/instrumentation.ts +++ b/e2e/scenarios/nextjs-instrumentation/src/instrumentation.ts @@ -1,4 +1,5 @@ -import { BraintrustExporter } from "@braintrust/otel"; +import { OTLPHttpJsonTraceExporter } from "@vercel/otel"; +import { BraintrustExporter, braintrustOtlpConfig } from "@braintrust/otel"; import { registerOTel } from "@vercel/otel"; import { initLogger } from "braintrust"; import { scopedName } from "./lib/runtime-check"; @@ -12,10 +13,14 @@ export async function register() { registerOTel({ serviceName: "nextjs-instrumentation-e2e", traceExporter: new BraintrustExporter({ - apiKey: process.env.BRAINTRUST_API_KEY!, - apiUrl: process.env.BRAINTRUST_API_URL!, - parent: `project_name:${scopedName("e2e-nextjs-instrumentation")}`, + exporter: new OTLPHttpJsonTraceExporter( + braintrustOtlpConfig({ + apiKey: process.env.BRAINTRUST_API_KEY!, + apiUrl: process.env.BRAINTRUST_API_URL!, + parent: `project_name:${scopedName("e2e-nextjs-instrumentation")}`, + }), + ), filterAISpans: false, - }) as any, + }), }); } diff --git a/e2e/scenarios/nextjs-instrumentation/src/lib/runtime-check.ts b/e2e/scenarios/nextjs-instrumentation/src/lib/runtime-check.ts index 969f96621..c90b0a7a8 100644 --- a/e2e/scenarios/nextjs-instrumentation/src/lib/runtime-check.ts +++ b/e2e/scenarios/nextjs-instrumentation/src/lib/runtime-check.ts @@ -1,5 +1,5 @@ -import { BraintrustExporter } from "@braintrust/otel"; -import { trace } from "@opentelemetry/api"; +import { OTLPHttpJsonTraceExporter } from "@vercel/otel"; +import { BraintrustExporter, braintrustOtlpConfig } from "@braintrust/otel"; import { BasicTracerProvider, SimpleSpanProcessor, @@ -33,54 +33,20 @@ export function scopedName(base: string): string { return `${base}-${suffix}`; } -function createTracerProvider(processors: unknown[]) { - if ( - typeof (new BasicTracerProvider() as { addSpanProcessor?: unknown }) - .addSpanProcessor === "function" - ) { - const provider = new BasicTracerProvider() as { - addSpanProcessor: (processor: unknown) => void; - getTracer: (name: string) => { - startActiveSpan: ( - name: string, - callback: (span: { - end: () => void; - setAttribute: (key: string, value: string) => void; - }) => Promise | void, - ) => Promise; - }; - shutdown?: () => Promise; - }; - processors.forEach((processor) => provider.addSpanProcessor(processor)); - return provider; - } - - return new BasicTracerProvider({ - spanProcessors: processors as never, - }) as { - getTracer: (name: string) => { - startActiveSpan: ( - name: string, - callback: (span: { - end: () => void; - setAttribute: (key: string, value: string) => void; - }) => Promise | void, - ) => Promise; - }; - shutdown?: () => Promise; - }; -} - async function emitOtelSpan(runtime: Runtime, testRunId: string) { const exporter = new BraintrustExporter({ - apiKey: requiredEnv("BRAINTRUST_API_KEY"), - apiUrl: requiredEnv("BRAINTRUST_API_URL"), - parent: `project_name:${scopedName("e2e-nextjs-instrumentation")}`, + exporter: new OTLPHttpJsonTraceExporter( + braintrustOtlpConfig({ + apiKey: requiredEnv("BRAINTRUST_API_KEY"), + apiUrl: requiredEnv("BRAINTRUST_API_URL"), + parent: `project_name:${scopedName("e2e-nextjs-instrumentation")}`, + }), + ), filterAISpans: false, }); - const provider = createTracerProvider([new SimpleSpanProcessor(exporter)]); - trace.setGlobalTracerProvider(provider as never); + const processor = new SimpleSpanProcessor(exporter); + const provider = new BasicTracerProvider({ spanProcessors: [processor] }); await provider .getTracer("nextjs-instrumentation-e2e") @@ -91,9 +57,8 @@ async function emitOtelSpan(runtime: Runtime, testRunId: string) { span.end(); }); - await exporter.forceFlush(); - await provider.shutdown?.(); - await new Promise((resolve) => setTimeout(resolve, 250)); + await processor.forceFlush(); + await provider.shutdown(); } export async function runRuntimeCheck(runtime: Runtime) { diff --git a/e2e/scenarios/otel-compat-mixed-tracing/scenario.ts b/e2e/scenarios/otel-compat-mixed-tracing/scenario.ts index f74828b5e..75068993f 100644 --- a/e2e/scenarios/otel-compat-mixed-tracing/scenario.ts +++ b/e2e/scenarios/otel-compat-mixed-tracing/scenario.ts @@ -1,6 +1,13 @@ +import { BatchSpanProcessor } from "@opentelemetry/sdk-trace-base"; +import { OTLPTraceExporter } from "@opentelemetry/exporter-trace-otlp-http"; import { context as otelContext, trace } from "@opentelemetry/api"; import { AsyncHooksContextManager } from "@opentelemetry/context-async-hooks"; -import { BraintrustSpanProcessor, setupOtelCompat } from "@braintrust/otel"; +import { + BraintrustSpanProcessor, + setupOtelCompat, + BraintrustExporter, + braintrustOtlpConfig, +} from "@braintrust/otel"; import { initLogger, withCurrent } from "braintrust"; import { createTracerProvider, @@ -19,9 +26,17 @@ async function main() { try { const processor = new BraintrustSpanProcessor({ - apiKey: process.env.BRAINTRUST_API_KEY!, - apiUrl: process.env.BRAINTRUST_API_URL!, - parent: `project_name:${scopedName("e2e-otel-compat-mixed-tracing", testRunId)}`, + processor: new BatchSpanProcessor( + new BraintrustExporter({ + exporter: new OTLPTraceExporter( + braintrustOtlpConfig({ + apiKey: process.env.BRAINTRUST_API_KEY!, + apiUrl: process.env.BRAINTRUST_API_URL!, + parent: `project_name:${scopedName("e2e-otel-compat-mixed-tracing", testRunId)}`, + }), + ), + }), + ), }); const provider = createTracerProvider([processor]); trace.setGlobalTracerProvider(provider); diff --git a/e2e/scenarios/otel-sdk-compat/package.json b/e2e/scenarios/otel-sdk-compat/package.json new file mode 100644 index 000000000..72a3b3528 --- /dev/null +++ b/e2e/scenarios/otel-sdk-compat/package.json @@ -0,0 +1,82 @@ +{ + "name": "@braintrust/e2e-otel-sdk-compat", + "private": true, + "braintrustScenario": { + "bump": { + "dependencies": { + "otel-v1-latest-api": { + "package": "@opentelemetry/api", + "range": "~1.9.0", + "targetDependency": "@opentelemetry/api", + "targetManifest": "versions/otel-v1-latest/package.json" + }, + "otel-v1-latest-sdk-trace-base": { + "package": "@opentelemetry/sdk-trace-base", + "range": "^1.30.1", + "targetDependency": "@opentelemetry/sdk-trace-base", + "targetManifest": "versions/otel-v1-latest/package.json" + }, + "otel-v1-latest-context-async-hooks": { + "package": "@opentelemetry/context-async-hooks", + "range": "^1.30.1", + "targetDependency": "@opentelemetry/context-async-hooks", + "targetManifest": "versions/otel-v1-latest/package.json" + }, + "otel-v1-latest-exporter-trace-otlp-http": { + "package": "@opentelemetry/exporter-trace-otlp-http", + "range": "^0.57.2", + "targetDependency": "@opentelemetry/exporter-trace-otlp-http", + "targetManifest": "versions/otel-v1-latest/package.json" + }, + "otel-v2-latest-api": { + "package": "@opentelemetry/api", + "range": "~1.9.0", + "targetDependency": "@opentelemetry/api", + "targetManifest": "versions/otel-v2-latest/package.json" + }, + "otel-v2-latest-sdk-trace-base": { + "package": "@opentelemetry/sdk-trace-base", + "range": "^2.12.0", + "targetDependency": "@opentelemetry/sdk-trace-base", + "targetManifest": "versions/otel-v2-latest/package.json" + }, + "otel-v2-latest-context-async-hooks": { + "package": "@opentelemetry/context-async-hooks", + "range": "^2.12.0", + "targetDependency": "@opentelemetry/context-async-hooks", + "targetManifest": "versions/otel-v2-latest/package.json" + }, + "otel-v2-latest-exporter-trace-otlp-http": { + "package": "@opentelemetry/exporter-trace-otlp-http", + "range": "^0.218.0", + "targetDependency": "@opentelemetry/exporter-trace-otlp-http", + "targetManifest": "versions/otel-v2-latest/package.json" + }, + "otel-v3-latest-api": { + "package": "@opentelemetry/api", + "range": "~1.10.0", + "targetDependency": "@opentelemetry/api", + "targetManifest": "versions/otel-v3-latest/package.json" + }, + "otel-v3-latest-sdk-trace": { + "package": "@opentelemetry/sdk-trace", + "range": "^3.1.0", + "targetDependency": "@opentelemetry/sdk-trace", + "targetManifest": "versions/otel-v3-latest/package.json" + }, + "otel-v3-latest-context-async-hooks": { + "package": "@opentelemetry/context-async-hooks", + "range": "^3.1.0", + "targetDependency": "@opentelemetry/context-async-hooks", + "targetManifest": "versions/otel-v3-latest/package.json" + }, + "otel-v3-latest-exporter-trace-otlp-http": { + "package": "@opentelemetry/exporter-trace-otlp-http", + "range": "^0.301.0", + "targetDependency": "@opentelemetry/exporter-trace-otlp-http", + "targetManifest": "versions/otel-v3-latest/package.json" + } + } + } + } +} diff --git a/e2e/scenarios/otel-sdk-compat/scenario.test.ts b/e2e/scenarios/otel-sdk-compat/scenario.test.ts new file mode 100644 index 000000000..b6cdacce8 --- /dev/null +++ b/e2e/scenarios/otel-sdk-compat/scenario.test.ts @@ -0,0 +1,82 @@ +import { promises as fs } from "node:fs"; +import path from "node:path"; +import { expect, test } from "vite-plus/test"; +import { + prepareScenarioDir, + resolveScenarioDir, + withScenarioHarness, +} from "../../helpers/scenario-harness"; +import { extractOtelSpans } from "../../helpers/trace-summary"; + +const originalScenarioDir = resolveScenarioDir(import.meta.url); +const variants = [ + "otel-v1", + "otel-v1-latest", + "otel-v2", + "otel-v2-latest", + "otel-v3", + "otel-v3-latest", +]; +const prepared = await Promise.all( + variants.map(async (variant) => { + // Separate install roots prevent different OTel API peer ranges from sharing a copy. + const sourceDir = path.resolve( + originalScenarioDir, + `../../.bt-tmp/generated-scenarios/otel-sdk-compat-${variant}`, + ); + await fs.rm(sourceDir, { recursive: true, force: true }); + await fs.cp(path.join(originalScenarioDir, "template"), sourceDir, { + recursive: true, + }); + await fs.cp( + path.join(originalScenarioDir, "versions", variant), + sourceDir, + { recursive: true }, + ); + return { + variant, + scenarioDir: await prepareScenarioDir({ scenarioDir: sourceDir }), + }; + }), +); + +for (const { variant, scenarioDir } of prepared) { + test(`otel-sdk-compat ${variant}: native context, filtering, customization and OTLP export`, async () => { + await withScenarioHarness( + async ({ runNodeScenarioDir, requestsAfter, testRunId }) => { + await runNodeScenarioDir({ + scenarioDir, + entry: "scenario.mjs", + env: { OTEL_MAJOR: variant.slice(6, 7) }, + runContext: { + variantKey: variant, + originalScenarioDir, + cassette: false, + }, + }); + const requests = requestsAfter( + 0, + (request) => request.path === "/otel/v1/traces", + ); + expect(requests).toHaveLength(1); + const spans = extractOtelSpans(requests[0].jsonBody); + expect(spans.map((span) => span.name).sort()).toEqual([ + "gen_ai.child", + "gen_ai.remote", + ]); + for (const span of spans) { + expect(span.attributes.testRunId).toBe(testRunId); + expect(span.attributes.reviewed).toBe(true); + expect(span.attributes.secret).toBeUndefined(); + expect(span.attributes["braintrust.parent"]).toBeDefined(); + expect(span.parentSpanId).toBeDefined(); + } + expect(spans[0].traceId).toBe(spans[1].traceId); + expect(spans[0].parentSpanId).toBe(spans[1].parentSpanId); + expect(JSON.stringify(requests[0].jsonBody)).not.toContain( + "private-payload", + ); + }, + ); + }); +} diff --git a/e2e/scenarios/otel-sdk-compat/template/scenario.mjs b/e2e/scenarios/otel-sdk-compat/template/scenario.mjs new file mode 100644 index 000000000..b33ba731d --- /dev/null +++ b/e2e/scenarios/otel-sdk-compat/template/scenario.mjs @@ -0,0 +1,137 @@ +import assert from "node:assert/strict"; +import { configureInstrumentation } from "braintrust/instrumentation"; + +let customized = 0; +configureInstrumentation({ + spanCustomizers: [ + { + onSpanExport(data) { + if ("attributes" in data) { + customized++; + delete data.attributes.secret; + data.attributes.reviewed = true; + } + return data; + }, + }, + ], +}); + +const { context, trace, createTraceState } = await import("@opentelemetry/api"); +const { AsyncLocalStorageContextManager } = + await import("@opentelemetry/context-async-hooks"); +const { OTLPTraceExporter } = + await import("@opentelemetry/exporter-trace-otlp-http"); +const { + BraintrustExporter, + BraintrustSpanProcessor, + braintrustOtlpConfig, + setupOtelCompat, + resetOtelCompat, + contextFromSpan, +} = await import("@braintrust/otel"); +const { initLogger, withCurrent, currentSpan } = await import("braintrust"); +const modern = process.env.OTEL_MAJOR === "3"; +const sdk = await import( + modern ? "@opentelemetry/sdk-trace" : "@opentelemetry/sdk-trace-base" +); +const testRunId = process.env.BRAINTRUST_E2E_RUN_ID; +const projectName = + process.env.BRAINTRUST_E2E_PROJECT_NAME || `tmp-luca-otel-${testRunId}`; +const exporter = new BraintrustExporter({ + exporter: new OTLPTraceExporter( + braintrustOtlpConfig({ parent: `project_name:${projectName}` }), + ), +}); +const batch = modern + ? new sdk.BatchSpanProcessor({ exporter }) + : new sdk.BatchSpanProcessor(exporter); +const processor = new BraintrustSpanProcessor({ + processor: batch, + filterAISpans: true, +}); +const Provider = modern ? sdk.TracerProvider : sdk.BasicTracerProvider; +const provider = new Provider( + process.env.OTEL_MAJOR === "1" ? {} : { spanProcessors: [processor] }, +); +// The pinned OTel 1 line is configured through addSpanProcessor. +if (process.env.OTEL_MAJOR === "1") provider.addSpanProcessor(processor); +const contextManager = new AsyncLocalStorageContextManager().enable(); +context.setGlobalContextManager(contextManager); +trace.setGlobalTracerProvider(provider); +setupOtelCompat(); +try { + const logger = initLogger({ projectName }); + const root = logger.startSpan({ + name: "native-root", + event: { metadata: { testRunId, scenario: "otel-sdk-compat" } }, + }); + const tracer = trace.getTracer("otel-sdk-compat", "1.0.0"); + await withCurrent(root, async () => { + await Promise.resolve(); + assert.equal(currentSpan(), root); + await tracer.startActiveSpan("gen_ai.child", async (span) => { + assert.equal(span.spanContext().traceId, root.rootSpanId); + span.setAttributes({ + testRunId, + scenario: "otel-sdk-compat", + secret: "private-payload", + }); + const child = logger.startSpan({ + name: "native-grandchild", + event: { metadata: { testRunId, scenario: "otel-sdk-compat" } }, + }); + assert.deepEqual(child.spanParents, [span.spanContext().spanId]); + child.end(); + await Promise.resolve(); + span.end(); + }); + tracer.startSpan("filtered-out").end(); + }); + const unsampled = trace.setSpanContext(context.active(), { + traceId: root.rootSpanId, + spanId: root.spanId, + traceFlags: 0, + traceState: createTraceState("vendor=value"), + isRemote: true, + }); + await context.with(unsampled, () => + logger.traced( + async (native) => { + assert.match(native.inject().traceparent, /-00$/); + assert.equal(native.inject().tracestate, "vendor=value"); + const child = tracer.startSpan("gen_ai.unsampled"); + assert.equal(child.isRecording(), false); + assert.equal(child.spanContext().traceFlags, 0); + child.end(); + const propagated = trace.getSpanContext(contextFromSpan(native)); + assert.equal(propagated.traceFlags, 0); + assert.equal(propagated.traceState.serialize(), "vendor=value"); + }, + { + name: "native-under-unsampled-parent", + event: { metadata: { testRunId, scenario: "otel-sdk-compat" } }, + }, + ), + ); + const remote = tracer.startSpan("gen_ai.remote", {}, contextFromSpan(root)); + remote.setAttributes({ + testRunId, + scenario: "otel-sdk-compat", + secret: "private-payload", + }); + remote.end(); + root.end(); + await logger.flush(); + await provider.forceFlush(); + assert.equal(customized, 2); + console.log( + JSON.stringify({ rootSpanId: root.rootSpanId, rootId: root.spanId }), + ); +} finally { + await provider.shutdown(); + resetOtelCompat(); + context.disable(); + trace.disable(); + contextManager.disable(); +} diff --git a/e2e/scenarios/otel-sdk-compat/versions/otel-v1-latest/package.json b/e2e/scenarios/otel-sdk-compat/versions/otel-v1-latest/package.json new file mode 100644 index 000000000..53756d83a --- /dev/null +++ b/e2e/scenarios/otel-sdk-compat/versions/otel-v1-latest/package.json @@ -0,0 +1,11 @@ +{ + "name": "@braintrust/e2e-otel-v1-latest", + "private": true, + "type": "module", + "dependencies": { + "@opentelemetry/api": "1.9.0", + "@opentelemetry/sdk-trace-base": "1.30.1", + "@opentelemetry/context-async-hooks": "1.30.1", + "@opentelemetry/exporter-trace-otlp-http": "0.57.2" + } +} diff --git a/e2e/scenarios/otel-sdk-compat/versions/otel-v1-latest/pnpm-lock.yaml b/e2e/scenarios/otel-sdk-compat/versions/otel-v1-latest/pnpm-lock.yaml new file mode 100644 index 000000000..84598ac4d --- /dev/null +++ b/e2e/scenarios/otel-sdk-compat/versions/otel-v1-latest/pnpm-lock.yaml @@ -0,0 +1,244 @@ +lockfileVersion: '9.0' + +settings: + autoInstallPeers: true + excludeLinksFromLockfile: false + +importers: + + .: + dependencies: + '@opentelemetry/api': + specifier: 1.9.0 + version: 1.9.0 + '@opentelemetry/context-async-hooks': + specifier: 1.30.1 + version: 1.30.1(@opentelemetry/api@1.9.0) + '@opentelemetry/exporter-trace-otlp-http': + specifier: 0.57.2 + version: 0.57.2(@opentelemetry/api@1.9.0) + '@opentelemetry/sdk-trace-base': + specifier: 1.30.1 + version: 1.30.1(@opentelemetry/api@1.9.0) + +packages: + + '@opentelemetry/api-logs@0.57.2': + resolution: {integrity: sha512-uIX52NnTM0iBh84MShlpouI7UKqkZ7MrUszTmaypHBu4r7NofznSnQRfJ+uUeDtQDj6w8eFGg5KBLDAwAPz1+A==} + engines: {node: '>=14'} + + '@opentelemetry/api@1.9.0': + resolution: {integrity: sha512-3giAOQvZiH5F9bMlMiv8+GSPMeqg0dbaeo58/0SlA9sxSqZhnUtxzX9/2FzyhS9sWQf5S0GJE0AKBrFqjpeYcg==} + engines: {node: '>=8.0.0'} + + '@opentelemetry/context-async-hooks@1.30.1': + resolution: {integrity: sha512-s5vvxXPVdjqS3kTLKMeBMvop9hbWkwzBpu+mUO2M7sZtlkyDJGwFe33wRKnbaYDo8ExRVBIIdwIGrqpxHuKttA==} + engines: {node: '>=14'} + peerDependencies: + '@opentelemetry/api': '>=1.0.0 <1.10.0' + + '@opentelemetry/core@1.30.1': + resolution: {integrity: sha512-OOCM2C/QIURhJMuKaekP3TRBxBKxG/TWWA0TL2J6nXUtDnuCtccy49LUJF8xPFXMX+0LMcxFpCo8M9cGY1W6rQ==} + engines: {node: '>=14'} + peerDependencies: + '@opentelemetry/api': '>=1.0.0 <1.10.0' + + '@opentelemetry/exporter-trace-otlp-http@0.57.2': + resolution: {integrity: sha512-sB/gkSYFu+0w2dVQ0PWY9fAMl172PKMZ/JrHkkW8dmjCL0CYkmXeE+ssqIL/yBUTPOvpLIpenX5T9RwXRBW/3g==} + engines: {node: '>=14'} + peerDependencies: + '@opentelemetry/api': ^1.3.0 + + '@opentelemetry/otlp-exporter-base@0.57.2': + resolution: {integrity: sha512-XdxEzL23Urhidyebg5E6jZoaiW5ygP/mRjxLHixogbqwDy2Faduzb5N0o/Oi+XTIJu+iyxXdVORjXax+Qgfxag==} + engines: {node: '>=14'} + peerDependencies: + '@opentelemetry/api': ^1.3.0 + + '@opentelemetry/otlp-transformer@0.57.2': + resolution: {integrity: sha512-48IIRj49gbQVK52jYsw70+Jv+JbahT8BqT2Th7C4H7RCM9d0gZ5sgNPoMpWldmfjvIsSgiGJtjfk9MeZvjhoig==} + engines: {node: '>=14'} + peerDependencies: + '@opentelemetry/api': ^1.3.0 + + '@opentelemetry/resources@1.30.1': + resolution: {integrity: sha512-5UxZqiAgLYGFjS4s9qm5mBVo433u+dSPUFWVWXmLAD4wB65oMCoXaJP1KJa9DIYYMeHu3z4BZcStG3LC593cWA==} + engines: {node: '>=14'} + peerDependencies: + '@opentelemetry/api': '>=1.0.0 <1.10.0' + + '@opentelemetry/sdk-logs@0.57.2': + resolution: {integrity: sha512-TXFHJ5c+BKggWbdEQ/inpgIzEmS2BGQowLE9UhsMd7YYlUfBQJ4uax0VF/B5NYigdM/75OoJGhAV3upEhK+3gg==} + engines: {node: '>=14'} + peerDependencies: + '@opentelemetry/api': '>=1.4.0 <1.10.0' + + '@opentelemetry/sdk-metrics@1.30.1': + resolution: {integrity: sha512-q9zcZ0Okl8jRgmy7eNW3Ku1XSgg3sDLa5evHZpCwjspw7E8Is4K/haRPDJrBcX3YSn/Y7gUvFnByNYEKQNbNog==} + engines: {node: '>=14'} + peerDependencies: + '@opentelemetry/api': '>=1.3.0 <1.10.0' + + '@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/semantic-conventions@1.28.0': + resolution: {integrity: sha512-lp4qAiMTD4sNWW4DbKLBkfiMZ4jbAboJIGOQr5DvciMRI494OapieI9qiODpOt0XBr1LjIDy1xAGAnVs5supTA==} + 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.2': + resolution: {integrity: sha512-b1UQwcEZ4yCnMCD8DAL1VlbvBJE9/IX4FTIp7BG1xYpf29SLazLSrqUkj4w7Y5y7cCVP6E5tcqqcI0xemPkHug==} + + '@types/node@26.6.6': + resolution: {integrity: sha512-MzQj+8Kx3XI40OmUAE6Qoh58ZMNstNNMwn2nfd3qWsfe7nDNSqJNzes4njhz6AHwrSHOffSLwXU6yb474Q4aog==} + + long@5.3.2: + resolution: {integrity: sha512-mNAgZ1GmyNhD7AuqnTG3/VQ26o760+ZYBPKjPvugO8+nLbYfX6TVpJPseBvopbdY+qpZ/lKUnmEc1LeZYS3QAA==} + + protobufjs@7.6.6: + resolution: {integrity: sha512-dYDWdjSl5RNb7SgPxGQcRU+GtvP7s2fpkrY0r432PcOIaZ0/rBcxEZnQN67iJhFuQiVw754JDoPruPCNdGsbjg==} + engines: {node: '>=12.0.0'} + + undici-types@8.9.0: + resolution: {integrity: sha512-KTDyRTYX8sWmKXAikPHHSyc63CRPETMctyjKFupcC6OBLXT3xsN0e9aF7m+mIXutFWpUXuedtowG7iLOzp0kQg==} + +snapshots: + + '@opentelemetry/api-logs@0.57.2': + dependencies: + '@opentelemetry/api': 1.9.0 + + '@opentelemetry/api@1.9.0': {} + + '@opentelemetry/context-async-hooks@1.30.1(@opentelemetry/api@1.9.0)': + dependencies: + '@opentelemetry/api': 1.9.0 + + '@opentelemetry/core@1.30.1(@opentelemetry/api@1.9.0)': + dependencies: + '@opentelemetry/api': 1.9.0 + '@opentelemetry/semantic-conventions': 1.28.0 + + '@opentelemetry/exporter-trace-otlp-http@0.57.2(@opentelemetry/api@1.9.0)': + dependencies: + '@opentelemetry/api': 1.9.0 + '@opentelemetry/core': 1.30.1(@opentelemetry/api@1.9.0) + '@opentelemetry/otlp-exporter-base': 0.57.2(@opentelemetry/api@1.9.0) + '@opentelemetry/otlp-transformer': 0.57.2(@opentelemetry/api@1.9.0) + '@opentelemetry/resources': 1.30.1(@opentelemetry/api@1.9.0) + '@opentelemetry/sdk-trace-base': 1.30.1(@opentelemetry/api@1.9.0) + + '@opentelemetry/otlp-exporter-base@0.57.2(@opentelemetry/api@1.9.0)': + dependencies: + '@opentelemetry/api': 1.9.0 + '@opentelemetry/core': 1.30.1(@opentelemetry/api@1.9.0) + '@opentelemetry/otlp-transformer': 0.57.2(@opentelemetry/api@1.9.0) + + '@opentelemetry/otlp-transformer@0.57.2(@opentelemetry/api@1.9.0)': + dependencies: + '@opentelemetry/api': 1.9.0 + '@opentelemetry/api-logs': 0.57.2 + '@opentelemetry/core': 1.30.1(@opentelemetry/api@1.9.0) + '@opentelemetry/resources': 1.30.1(@opentelemetry/api@1.9.0) + '@opentelemetry/sdk-logs': 0.57.2(@opentelemetry/api@1.9.0) + '@opentelemetry/sdk-metrics': 1.30.1(@opentelemetry/api@1.9.0) + '@opentelemetry/sdk-trace-base': 1.30.1(@opentelemetry/api@1.9.0) + protobufjs: 7.6.6 + + '@opentelemetry/resources@1.30.1(@opentelemetry/api@1.9.0)': + dependencies: + '@opentelemetry/api': 1.9.0 + '@opentelemetry/core': 1.30.1(@opentelemetry/api@1.9.0) + '@opentelemetry/semantic-conventions': 1.28.0 + + '@opentelemetry/sdk-logs@0.57.2(@opentelemetry/api@1.9.0)': + dependencies: + '@opentelemetry/api': 1.9.0 + '@opentelemetry/api-logs': 0.57.2 + '@opentelemetry/core': 1.30.1(@opentelemetry/api@1.9.0) + '@opentelemetry/resources': 1.30.1(@opentelemetry/api@1.9.0) + + '@opentelemetry/sdk-metrics@1.30.1(@opentelemetry/api@1.9.0)': + dependencies: + '@opentelemetry/api': 1.9.0 + '@opentelemetry/core': 1.30.1(@opentelemetry/api@1.9.0) + '@opentelemetry/resources': 1.30.1(@opentelemetry/api@1.9.0) + + '@opentelemetry/sdk-trace-base@1.30.1(@opentelemetry/api@1.9.0)': + dependencies: + '@opentelemetry/api': 1.9.0 + '@opentelemetry/core': 1.30.1(@opentelemetry/api@1.9.0) + '@opentelemetry/resources': 1.30.1(@opentelemetry/api@1.9.0) + '@opentelemetry/semantic-conventions': 1.28.0 + + '@opentelemetry/semantic-conventions@1.28.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 + + '@protobufjs/float@1.0.2': {} + + '@protobufjs/path@1.1.2': {} + + '@protobufjs/pool@1.1.0': {} + + '@protobufjs/utf8@1.1.2': {} + + '@types/node@26.6.6': + dependencies: + undici-types: 8.9.0 + + long@5.3.2: {} + + protobufjs@7.6.6: + dependencies: + '@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 + '@protobufjs/float': 1.0.2 + '@protobufjs/path': 1.1.2 + '@protobufjs/pool': 1.1.0 + '@protobufjs/utf8': 1.1.2 + '@types/node': 26.6.6 + long: 5.3.2 + + undici-types@8.9.0: {} diff --git a/e2e/scenarios/otel-sdk-compat/versions/otel-v1/package.json b/e2e/scenarios/otel-sdk-compat/versions/otel-v1/package.json new file mode 100644 index 000000000..ff2bba64d --- /dev/null +++ b/e2e/scenarios/otel-sdk-compat/versions/otel-v1/package.json @@ -0,0 +1,11 @@ +{ + "name": "@braintrust/e2e-otel-v1", + "private": true, + "type": "module", + "dependencies": { + "@opentelemetry/api": "1.9.0", + "@opentelemetry/sdk-trace-base": "1.30.0", + "@opentelemetry/context-async-hooks": "1.30.0", + "@opentelemetry/exporter-trace-otlp-http": "0.57.0" + } +} diff --git a/e2e/scenarios/otel-sdk-compat/versions/otel-v1/pnpm-lock.yaml b/e2e/scenarios/otel-sdk-compat/versions/otel-v1/pnpm-lock.yaml new file mode 100644 index 000000000..2a1fe3d81 --- /dev/null +++ b/e2e/scenarios/otel-sdk-compat/versions/otel-v1/pnpm-lock.yaml @@ -0,0 +1,244 @@ +lockfileVersion: '9.0' + +settings: + autoInstallPeers: true + excludeLinksFromLockfile: false + +importers: + + .: + dependencies: + '@opentelemetry/api': + specifier: 1.9.0 + version: 1.9.0 + '@opentelemetry/context-async-hooks': + specifier: 1.30.0 + version: 1.30.0(@opentelemetry/api@1.9.0) + '@opentelemetry/exporter-trace-otlp-http': + specifier: 0.57.0 + version: 0.57.0(@opentelemetry/api@1.9.0) + '@opentelemetry/sdk-trace-base': + specifier: 1.30.0 + version: 1.30.0(@opentelemetry/api@1.9.0) + +packages: + + '@opentelemetry/api-logs@0.57.0': + resolution: {integrity: sha512-l1aJ30CXeauVYaI+btiynHpw341LthkMTv3omi1VJDX14werY2Wmv9n1yudMsq9HuY0m8PvXEVX4d8zxEb+WRg==} + engines: {node: '>=14'} + + '@opentelemetry/api@1.9.0': + resolution: {integrity: sha512-3giAOQvZiH5F9bMlMiv8+GSPMeqg0dbaeo58/0SlA9sxSqZhnUtxzX9/2FzyhS9sWQf5S0GJE0AKBrFqjpeYcg==} + engines: {node: '>=8.0.0'} + + '@opentelemetry/context-async-hooks@1.30.0': + resolution: {integrity: sha512-roCetrG/cz0r/gugQm/jFo75UxblVvHaNSRoR0kSSRSzXFAiIBqFCZuH458BHBNRtRe+0yJdIJ21L9t94bw7+g==} + engines: {node: '>=14'} + peerDependencies: + '@opentelemetry/api': '>=1.0.0 <1.10.0' + + '@opentelemetry/core@1.30.0': + resolution: {integrity: sha512-Q/3u/K73KUjTCnFUP97ZY+pBjQ1kPEgjOfXj/bJl8zW7GbXdkw6cwuyZk6ZTXkVgCBsYRYUzx4fvYK1jxdb9MA==} + engines: {node: '>=14'} + peerDependencies: + '@opentelemetry/api': '>=1.0.0 <1.10.0' + + '@opentelemetry/exporter-trace-otlp-http@0.57.0': + resolution: {integrity: sha512-BJl35PSkwoMlGEOrzjCG1ih6zqZoAZJIR4xyqSKC2BqPtwuRjID0vWBaEdP9xrxxJTEIEQw+gEY/0pUgicX0ew==} + engines: {node: '>=14'} + peerDependencies: + '@opentelemetry/api': ^1.3.0 + + '@opentelemetry/otlp-exporter-base@0.57.0': + resolution: {integrity: sha512-QQl4Ngm3D6H8SDO0EM642ncTxjRsf/HDq7+IWIA0eaEK/NTsJeQ3iYJiZj3F4jkALnvyeM1kkwd+DHtqxTBx9Q==} + engines: {node: '>=14'} + peerDependencies: + '@opentelemetry/api': ^1.3.0 + + '@opentelemetry/otlp-transformer@0.57.0': + resolution: {integrity: sha512-yHX7sdwkdAmSa6Jbi3caSLDWy0PCHS1pKQeKz8AIWSyQqL7IojHKgdk9A+7eRd98Z1n9YTdwWSWLnObvIqhEhQ==} + engines: {node: '>=14'} + peerDependencies: + '@opentelemetry/api': ^1.3.0 + + '@opentelemetry/resources@1.30.0': + resolution: {integrity: sha512-5mGMjL0Uld/99t7/pcd7CuVtJbkARckLVuiOX84nO8RtLtIz0/J6EOHM2TGvPZ6F4K+XjUq13gMx14w80SVCQg==} + engines: {node: '>=14'} + peerDependencies: + '@opentelemetry/api': '>=1.0.0 <1.10.0' + + '@opentelemetry/sdk-logs@0.57.0': + resolution: {integrity: sha512-6Kbxdu/QE9LWH7+WSLmYo3DjAq+c55TiCLXiXu6b/2m2muy5SyOG2m0MrGqetyRpfYSSbIqHmJoqNVTN3+2a9g==} + engines: {node: '>=14'} + peerDependencies: + '@opentelemetry/api': '>=1.4.0 <1.10.0' + + '@opentelemetry/sdk-metrics@1.30.0': + resolution: {integrity: sha512-5kcj6APyRMvv6dEIP5plz2qfJAD4OMipBRT11u/pa1a68rHKI2Ln+iXVkAGKgx8o7CXbD7FdPypTUY88ZQgP4Q==} + engines: {node: '>=14'} + peerDependencies: + '@opentelemetry/api': '>=1.3.0 <1.10.0' + + '@opentelemetry/sdk-trace-base@1.30.0': + resolution: {integrity: sha512-RKQDaDIkV7PwizmHw+rE/FgfB2a6MBx+AEVVlAHXRG1YYxLiBpPX2KhmoB99R5vA4b72iJrjle68NDWnbrE9Dg==} + engines: {node: '>=14'} + deprecated: Use @opentelemetry/sdk-trace package + peerDependencies: + '@opentelemetry/api': '>=1.0.0 <1.10.0' + + '@opentelemetry/semantic-conventions@1.28.0': + resolution: {integrity: sha512-lp4qAiMTD4sNWW4DbKLBkfiMZ4jbAboJIGOQr5DvciMRI494OapieI9qiODpOt0XBr1LjIDy1xAGAnVs5supTA==} + 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.2': + resolution: {integrity: sha512-b1UQwcEZ4yCnMCD8DAL1VlbvBJE9/IX4FTIp7BG1xYpf29SLazLSrqUkj4w7Y5y7cCVP6E5tcqqcI0xemPkHug==} + + '@types/node@26.6.6': + resolution: {integrity: sha512-MzQj+8Kx3XI40OmUAE6Qoh58ZMNstNNMwn2nfd3qWsfe7nDNSqJNzes4njhz6AHwrSHOffSLwXU6yb474Q4aog==} + + long@5.3.2: + resolution: {integrity: sha512-mNAgZ1GmyNhD7AuqnTG3/VQ26o760+ZYBPKjPvugO8+nLbYfX6TVpJPseBvopbdY+qpZ/lKUnmEc1LeZYS3QAA==} + + protobufjs@7.6.6: + resolution: {integrity: sha512-dYDWdjSl5RNb7SgPxGQcRU+GtvP7s2fpkrY0r432PcOIaZ0/rBcxEZnQN67iJhFuQiVw754JDoPruPCNdGsbjg==} + engines: {node: '>=12.0.0'} + + undici-types@8.9.0: + resolution: {integrity: sha512-KTDyRTYX8sWmKXAikPHHSyc63CRPETMctyjKFupcC6OBLXT3xsN0e9aF7m+mIXutFWpUXuedtowG7iLOzp0kQg==} + +snapshots: + + '@opentelemetry/api-logs@0.57.0': + dependencies: + '@opentelemetry/api': 1.9.0 + + '@opentelemetry/api@1.9.0': {} + + '@opentelemetry/context-async-hooks@1.30.0(@opentelemetry/api@1.9.0)': + dependencies: + '@opentelemetry/api': 1.9.0 + + '@opentelemetry/core@1.30.0(@opentelemetry/api@1.9.0)': + dependencies: + '@opentelemetry/api': 1.9.0 + '@opentelemetry/semantic-conventions': 1.28.0 + + '@opentelemetry/exporter-trace-otlp-http@0.57.0(@opentelemetry/api@1.9.0)': + dependencies: + '@opentelemetry/api': 1.9.0 + '@opentelemetry/core': 1.30.0(@opentelemetry/api@1.9.0) + '@opentelemetry/otlp-exporter-base': 0.57.0(@opentelemetry/api@1.9.0) + '@opentelemetry/otlp-transformer': 0.57.0(@opentelemetry/api@1.9.0) + '@opentelemetry/resources': 1.30.0(@opentelemetry/api@1.9.0) + '@opentelemetry/sdk-trace-base': 1.30.0(@opentelemetry/api@1.9.0) + + '@opentelemetry/otlp-exporter-base@0.57.0(@opentelemetry/api@1.9.0)': + dependencies: + '@opentelemetry/api': 1.9.0 + '@opentelemetry/core': 1.30.0(@opentelemetry/api@1.9.0) + '@opentelemetry/otlp-transformer': 0.57.0(@opentelemetry/api@1.9.0) + + '@opentelemetry/otlp-transformer@0.57.0(@opentelemetry/api@1.9.0)': + dependencies: + '@opentelemetry/api': 1.9.0 + '@opentelemetry/api-logs': 0.57.0 + '@opentelemetry/core': 1.30.0(@opentelemetry/api@1.9.0) + '@opentelemetry/resources': 1.30.0(@opentelemetry/api@1.9.0) + '@opentelemetry/sdk-logs': 0.57.0(@opentelemetry/api@1.9.0) + '@opentelemetry/sdk-metrics': 1.30.0(@opentelemetry/api@1.9.0) + '@opentelemetry/sdk-trace-base': 1.30.0(@opentelemetry/api@1.9.0) + protobufjs: 7.6.6 + + '@opentelemetry/resources@1.30.0(@opentelemetry/api@1.9.0)': + dependencies: + '@opentelemetry/api': 1.9.0 + '@opentelemetry/core': 1.30.0(@opentelemetry/api@1.9.0) + '@opentelemetry/semantic-conventions': 1.28.0 + + '@opentelemetry/sdk-logs@0.57.0(@opentelemetry/api@1.9.0)': + dependencies: + '@opentelemetry/api': 1.9.0 + '@opentelemetry/api-logs': 0.57.0 + '@opentelemetry/core': 1.30.0(@opentelemetry/api@1.9.0) + '@opentelemetry/resources': 1.30.0(@opentelemetry/api@1.9.0) + + '@opentelemetry/sdk-metrics@1.30.0(@opentelemetry/api@1.9.0)': + dependencies: + '@opentelemetry/api': 1.9.0 + '@opentelemetry/core': 1.30.0(@opentelemetry/api@1.9.0) + '@opentelemetry/resources': 1.30.0(@opentelemetry/api@1.9.0) + + '@opentelemetry/sdk-trace-base@1.30.0(@opentelemetry/api@1.9.0)': + dependencies: + '@opentelemetry/api': 1.9.0 + '@opentelemetry/core': 1.30.0(@opentelemetry/api@1.9.0) + '@opentelemetry/resources': 1.30.0(@opentelemetry/api@1.9.0) + '@opentelemetry/semantic-conventions': 1.28.0 + + '@opentelemetry/semantic-conventions@1.28.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 + + '@protobufjs/float@1.0.2': {} + + '@protobufjs/path@1.1.2': {} + + '@protobufjs/pool@1.1.0': {} + + '@protobufjs/utf8@1.1.2': {} + + '@types/node@26.6.6': + dependencies: + undici-types: 8.9.0 + + long@5.3.2: {} + + protobufjs@7.6.6: + dependencies: + '@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 + '@protobufjs/float': 1.0.2 + '@protobufjs/path': 1.1.2 + '@protobufjs/pool': 1.1.0 + '@protobufjs/utf8': 1.1.2 + '@types/node': 26.6.6 + long: 5.3.2 + + undici-types@8.9.0: {} diff --git a/e2e/scenarios/otel-sdk-compat/versions/otel-v2-latest/package.json b/e2e/scenarios/otel-sdk-compat/versions/otel-v2-latest/package.json new file mode 100644 index 000000000..872fd2794 --- /dev/null +++ b/e2e/scenarios/otel-sdk-compat/versions/otel-v2-latest/package.json @@ -0,0 +1,11 @@ +{ + "name": "@braintrust/e2e-otel-v2-latest", + "private": true, + "type": "module", + "dependencies": { + "@opentelemetry/api": "1.9.0", + "@opentelemetry/sdk-trace-base": "2.12.0", + "@opentelemetry/context-async-hooks": "2.12.0", + "@opentelemetry/exporter-trace-otlp-http": "0.218.0" + } +} diff --git a/e2e/scenarios/otel-sdk-compat/versions/otel-v2-latest/pnpm-lock.yaml b/e2e/scenarios/otel-sdk-compat/versions/otel-v2-latest/pnpm-lock.yaml new file mode 100644 index 000000000..45a5f79b9 --- /dev/null +++ b/e2e/scenarios/otel-sdk-compat/versions/otel-v2-latest/pnpm-lock.yaml @@ -0,0 +1,213 @@ +lockfileVersion: '9.0' + +settings: + autoInstallPeers: true + excludeLinksFromLockfile: false + +importers: + + .: + dependencies: + '@opentelemetry/api': + specifier: 1.9.0 + version: 1.9.0 + '@opentelemetry/context-async-hooks': + specifier: 2.12.0 + version: 2.12.0(@opentelemetry/api@1.9.0) + '@opentelemetry/exporter-trace-otlp-http': + specifier: 0.218.0 + version: 0.218.0(@opentelemetry/api@1.9.0) + '@opentelemetry/sdk-trace-base': + specifier: 2.12.0 + version: 2.12.0(@opentelemetry/api@1.9.0) + +packages: + + '@opentelemetry/api-logs@0.218.0': + resolution: {integrity: sha512-fmEWp5kXlGEc3i/lR698Hz41DfGyN4Tbe4g7L1AxSc7fF8Xeh/FQ9Quqpa9dVA413Q1Ad43QOLzU4JoXgbFPWw==} + engines: {node: '>=8.0.0'} + + '@opentelemetry/api@1.9.0': + resolution: {integrity: sha512-3giAOQvZiH5F9bMlMiv8+GSPMeqg0dbaeo58/0SlA9sxSqZhnUtxzX9/2FzyhS9sWQf5S0GJE0AKBrFqjpeYcg==} + engines: {node: '>=8.0.0'} + + '@opentelemetry/context-async-hooks@2.12.0': + resolution: {integrity: sha512-iK/1nD7mmZVkuYjVjkdFBiCvzntJdFny1S3CYbUYyOq7V5enL6XX+gJ7eh965L1LnS11VA8mQ7Ej/ck04SB0MQ==} + engines: {node: ^18.19.0 || >=20.6.0} + peerDependencies: + '@opentelemetry/api': '>=1.0.0 <1.10.0' + + '@opentelemetry/core@2.12.0': + resolution: {integrity: sha512-1HsSAuvT4/my0QrXWsyXFdWjKaPeCHsismKZgEEIb8NK13LP6DrMRlsUbvD/0F9mDWK3xDom9AMv3IscxgOnOQ==} + engines: {node: ^18.19.0 || >=20.6.0} + peerDependencies: + '@opentelemetry/api': '>=1.0.0 <1.10.0' + + '@opentelemetry/core@2.7.1': + resolution: {integrity: sha512-QAqIj32AtK6+pEVNG7EOVxHdE06RP+FM5qpiEJ4RtDcFIqKUZHYhl7/7UY5efhwmwNAg7j8QbJVBLxMerc0+gw==} + engines: {node: ^18.19.0 || >=20.6.0} + peerDependencies: + '@opentelemetry/api': '>=1.0.0 <1.10.0' + + '@opentelemetry/exporter-trace-otlp-http@0.218.0': + resolution: {integrity: sha512-8dqezsmPhtKitIK/eTipZhYl9EX2/gNQ5zUMhaz3uxEURwfkNf8IPvo6yNfrzbxdtpAOybS/+h7wmIWYqFSpiw==} + engines: {node: ^18.19.0 || >=20.6.0} + peerDependencies: + '@opentelemetry/api': ^1.3.0 + + '@opentelemetry/otlp-exporter-base@0.218.0': + resolution: {integrity: sha512-ZwqpkNL5W7RyGJPDZ9g06DvKp8KFTWPJPN12anpMQYSKpTSU0z3EIZuPq9vPGpS8siFyOqDYDAuCwlNO9FqgbA==} + engines: {node: ^18.19.0 || >=20.6.0} + peerDependencies: + '@opentelemetry/api': ^1.3.0 + + '@opentelemetry/otlp-transformer@0.218.0': + resolution: {integrity: sha512-CFaKH87WAzjuJ4awowTTLzUvMfaRfiOFG5+qm5S5ncyalRtN4ecQ+YmuANJSCrVPuvZFEkUgKhBPBndxi3rHsQ==} + engines: {node: ^18.19.0 || >=20.6.0} + peerDependencies: + '@opentelemetry/api': ^1.3.0 + + '@opentelemetry/resources@2.12.0': + resolution: {integrity: sha512-+rSRoOdln6NhXTVcBdHSlZxJDNzwxadeweK2ltZO3tVtfp6aklABc06xclDk6ovRR+EiGgIcSbh/6NgjFhnb3A==} + engines: {node: ^18.19.0 || >=20.6.0} + peerDependencies: + '@opentelemetry/api': '>=1.3.0 <1.10.0' + + '@opentelemetry/resources@2.7.1': + resolution: {integrity: sha512-DeT6KKolmC4e/dRQvMQ/RwlnzhaqeiFOXY5ngoOPJ07GgVVKxZOg9EcrNZb5aTzUn+iCrJldAgOfQm1O/QfPAQ==} + engines: {node: ^18.19.0 || >=20.6.0} + peerDependencies: + '@opentelemetry/api': '>=1.3.0 <1.10.0' + + '@opentelemetry/sdk-logs@0.218.0': + resolution: {integrity: sha512-QvnNdugatFTVCJXH0Mcu7GOOJSylA9j127kIezOE4YwTI4YbowRons2K4WZTv5FMS8T4q9P0NdaRHdkSmeAIag==} + engines: {node: ^18.19.0 || >=20.6.0} + peerDependencies: + '@opentelemetry/api': '>=1.4.0 <1.10.0' + + '@opentelemetry/sdk-metrics@2.7.1': + resolution: {integrity: sha512-MpDJdkiFDs3Pm1RHO3KByuZbuBdJEXEAkiC0+yJdsZGVCdf1RpHR6n+LHDcS7ffmfrt5kVCzJSCfm4z2C7v0uQ==} + engines: {node: ^18.19.0 || >=20.6.0} + peerDependencies: + '@opentelemetry/api': '>=1.9.0 <1.10.0' + + '@opentelemetry/sdk-trace-base@2.12.0': + resolution: {integrity: sha512-JOERgZCxRM06FEJ5kEWxlSRChah6MmXDx3KwCruqEv/iEyLkTokCSemf2y1w8oIHtfrr8EVTpzebq/31vn2Cpg==} + 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-base@2.7.1': + resolution: {integrity: sha512-NAYIlsF8MPUsKqJMiDQJTMPOmlbawC1Iz/omMLygZ1C9am8fTKYjTaI+OZM+WTY3t3Glo0wnOg/6/pac6RGPPw==} + 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@2.12.0': + resolution: {integrity: sha512-ImVuRQ6faOsTi1Wna88s2avgrrf/iDgXPigIeynkUhjstMuoQUDOJXgkez2j1mlU4btjXcXBGa+q7qKFYR3GsA==} + engines: {node: ^18.19.0 || >=20.6.0} + peerDependencies: + '@opentelemetry/api': '>=1.3.0 <1.10.0' + + '@opentelemetry/semantic-conventions@1.45.0': + resolution: {integrity: sha512-aOP45mbcOKbQVvFWfQmgoOPT8R2iwbbbgl83k4CujGV9BrJhFr3qG55MFpwFs4YLabpuW3HhzUY9eQxPlDqaZw==} + engines: {node: '>=14'} + +snapshots: + + '@opentelemetry/api-logs@0.218.0': + dependencies: + '@opentelemetry/api': 1.9.0 + + '@opentelemetry/api@1.9.0': {} + + '@opentelemetry/context-async-hooks@2.12.0(@opentelemetry/api@1.9.0)': + dependencies: + '@opentelemetry/api': 1.9.0 + + '@opentelemetry/core@2.12.0(@opentelemetry/api@1.9.0)': + dependencies: + '@opentelemetry/api': 1.9.0 + '@opentelemetry/semantic-conventions': 1.45.0 + + '@opentelemetry/core@2.7.1(@opentelemetry/api@1.9.0)': + dependencies: + '@opentelemetry/api': 1.9.0 + '@opentelemetry/semantic-conventions': 1.45.0 + + '@opentelemetry/exporter-trace-otlp-http@0.218.0(@opentelemetry/api@1.9.0)': + dependencies: + '@opentelemetry/api': 1.9.0 + '@opentelemetry/core': 2.7.1(@opentelemetry/api@1.9.0) + '@opentelemetry/otlp-exporter-base': 0.218.0(@opentelemetry/api@1.9.0) + '@opentelemetry/otlp-transformer': 0.218.0(@opentelemetry/api@1.9.0) + '@opentelemetry/resources': 2.7.1(@opentelemetry/api@1.9.0) + '@opentelemetry/sdk-trace-base': 2.7.1(@opentelemetry/api@1.9.0) + + '@opentelemetry/otlp-exporter-base@0.218.0(@opentelemetry/api@1.9.0)': + dependencies: + '@opentelemetry/api': 1.9.0 + '@opentelemetry/core': 2.7.1(@opentelemetry/api@1.9.0) + '@opentelemetry/otlp-transformer': 0.218.0(@opentelemetry/api@1.9.0) + + '@opentelemetry/otlp-transformer@0.218.0(@opentelemetry/api@1.9.0)': + dependencies: + '@opentelemetry/api': 1.9.0 + '@opentelemetry/api-logs': 0.218.0 + '@opentelemetry/core': 2.7.1(@opentelemetry/api@1.9.0) + '@opentelemetry/resources': 2.7.1(@opentelemetry/api@1.9.0) + '@opentelemetry/sdk-logs': 0.218.0(@opentelemetry/api@1.9.0) + '@opentelemetry/sdk-metrics': 2.7.1(@opentelemetry/api@1.9.0) + '@opentelemetry/sdk-trace-base': 2.7.1(@opentelemetry/api@1.9.0) + + '@opentelemetry/resources@2.12.0(@opentelemetry/api@1.9.0)': + dependencies: + '@opentelemetry/api': 1.9.0 + '@opentelemetry/core': 2.12.0(@opentelemetry/api@1.9.0) + '@opentelemetry/semantic-conventions': 1.45.0 + + '@opentelemetry/resources@2.7.1(@opentelemetry/api@1.9.0)': + dependencies: + '@opentelemetry/api': 1.9.0 + '@opentelemetry/core': 2.7.1(@opentelemetry/api@1.9.0) + '@opentelemetry/semantic-conventions': 1.45.0 + + '@opentelemetry/sdk-logs@0.218.0(@opentelemetry/api@1.9.0)': + dependencies: + '@opentelemetry/api': 1.9.0 + '@opentelemetry/api-logs': 0.218.0 + '@opentelemetry/core': 2.7.1(@opentelemetry/api@1.9.0) + '@opentelemetry/resources': 2.7.1(@opentelemetry/api@1.9.0) + '@opentelemetry/semantic-conventions': 1.45.0 + + '@opentelemetry/sdk-metrics@2.7.1(@opentelemetry/api@1.9.0)': + dependencies: + '@opentelemetry/api': 1.9.0 + '@opentelemetry/core': 2.7.1(@opentelemetry/api@1.9.0) + '@opentelemetry/resources': 2.7.1(@opentelemetry/api@1.9.0) + + '@opentelemetry/sdk-trace-base@2.12.0(@opentelemetry/api@1.9.0)': + dependencies: + '@opentelemetry/api': 1.9.0 + '@opentelemetry/core': 2.12.0(@opentelemetry/api@1.9.0) + '@opentelemetry/resources': 2.12.0(@opentelemetry/api@1.9.0) + '@opentelemetry/sdk-trace': 2.12.0(@opentelemetry/api@1.9.0) + '@opentelemetry/semantic-conventions': 1.45.0 + + '@opentelemetry/sdk-trace-base@2.7.1(@opentelemetry/api@1.9.0)': + dependencies: + '@opentelemetry/api': 1.9.0 + '@opentelemetry/core': 2.7.1(@opentelemetry/api@1.9.0) + '@opentelemetry/resources': 2.7.1(@opentelemetry/api@1.9.0) + '@opentelemetry/semantic-conventions': 1.45.0 + + '@opentelemetry/sdk-trace@2.12.0(@opentelemetry/api@1.9.0)': + dependencies: + '@opentelemetry/api': 1.9.0 + '@opentelemetry/core': 2.12.0(@opentelemetry/api@1.9.0) + '@opentelemetry/resources': 2.12.0(@opentelemetry/api@1.9.0) + '@opentelemetry/semantic-conventions': 1.45.0 + + '@opentelemetry/semantic-conventions@1.45.0': {} diff --git a/e2e/scenarios/otel-sdk-compat/versions/otel-v2/package.json b/e2e/scenarios/otel-sdk-compat/versions/otel-v2/package.json new file mode 100644 index 000000000..114485722 --- /dev/null +++ b/e2e/scenarios/otel-sdk-compat/versions/otel-v2/package.json @@ -0,0 +1,11 @@ +{ + "name": "@braintrust/e2e-otel-v2", + "private": true, + "type": "module", + "dependencies": { + "@opentelemetry/api": "1.9.0", + "@opentelemetry/sdk-trace-base": "2.0.0", + "@opentelemetry/context-async-hooks": "2.0.0", + "@opentelemetry/exporter-trace-otlp-http": "0.200.0" + } +} diff --git a/e2e/scenarios/otel-sdk-compat/versions/otel-v2/pnpm-lock.yaml b/e2e/scenarios/otel-sdk-compat/versions/otel-v2/pnpm-lock.yaml new file mode 100644 index 000000000..7b861c9f8 --- /dev/null +++ b/e2e/scenarios/otel-sdk-compat/versions/otel-v2/pnpm-lock.yaml @@ -0,0 +1,244 @@ +lockfileVersion: '9.0' + +settings: + autoInstallPeers: true + excludeLinksFromLockfile: false + +importers: + + .: + dependencies: + '@opentelemetry/api': + specifier: 1.9.0 + version: 1.9.0 + '@opentelemetry/context-async-hooks': + specifier: 2.0.0 + version: 2.0.0(@opentelemetry/api@1.9.0) + '@opentelemetry/exporter-trace-otlp-http': + specifier: 0.200.0 + version: 0.200.0(@opentelemetry/api@1.9.0) + '@opentelemetry/sdk-trace-base': + specifier: 2.0.0 + version: 2.0.0(@opentelemetry/api@1.9.0) + +packages: + + '@opentelemetry/api-logs@0.200.0': + resolution: {integrity: sha512-IKJBQxh91qJ+3ssRly5hYEJ8NDHu9oY/B1PXVSCWf7zytmYO9RNLB0Ox9XQ/fJ8m6gY6Q6NtBWlmXfaXt5Uc4Q==} + engines: {node: '>=8.0.0'} + + '@opentelemetry/api@1.9.0': + resolution: {integrity: sha512-3giAOQvZiH5F9bMlMiv8+GSPMeqg0dbaeo58/0SlA9sxSqZhnUtxzX9/2FzyhS9sWQf5S0GJE0AKBrFqjpeYcg==} + engines: {node: '>=8.0.0'} + + '@opentelemetry/context-async-hooks@2.0.0': + resolution: {integrity: sha512-IEkJGzK1A9v3/EHjXh3s2IiFc6L4jfK+lNgKVgUjeUJQRRhnVFMIO3TAvKwonm9O1HebCuoOt98v8bZW7oVQHA==} + engines: {node: ^18.19.0 || >=20.6.0} + peerDependencies: + '@opentelemetry/api': '>=1.0.0 <1.10.0' + + '@opentelemetry/core@2.0.0': + resolution: {integrity: sha512-SLX36allrcnVaPYG3R78F/UZZsBsvbc7lMCLx37LyH5MJ1KAAZ2E3mW9OAD3zGz0G8q/BtoS5VUrjzDydhD6LQ==} + engines: {node: ^18.19.0 || >=20.6.0} + peerDependencies: + '@opentelemetry/api': '>=1.0.0 <1.10.0' + + '@opentelemetry/exporter-trace-otlp-http@0.200.0': + resolution: {integrity: sha512-Goi//m/7ZHeUedxTGVmEzH19NgqJY+Bzr6zXo1Rni1+hwqaksEyJ44gdlEMREu6dzX1DlAaH/qSykSVzdrdafA==} + engines: {node: ^18.19.0 || >=20.6.0} + peerDependencies: + '@opentelemetry/api': ^1.3.0 + + '@opentelemetry/otlp-exporter-base@0.200.0': + resolution: {integrity: sha512-IxJgA3FD7q4V6gGq4bnmQM5nTIyMDkoGFGrBrrDjB6onEiq1pafma55V+bHvGYLWvcqbBbRfezr1GED88lacEQ==} + engines: {node: ^18.19.0 || >=20.6.0} + peerDependencies: + '@opentelemetry/api': ^1.3.0 + + '@opentelemetry/otlp-transformer@0.200.0': + resolution: {integrity: sha512-+9YDZbYybOnv7sWzebWOeK6gKyt2XE7iarSyBFkwwnP559pEevKOUD8NyDHhRjCSp13ybh9iVXlMfcj/DwF/yw==} + engines: {node: ^18.19.0 || >=20.6.0} + peerDependencies: + '@opentelemetry/api': ^1.3.0 + + '@opentelemetry/resources@2.0.0': + resolution: {integrity: sha512-rnZr6dML2z4IARI4zPGQV4arDikF/9OXZQzrC01dLmn0CZxU5U5OLd/m1T7YkGRj5UitjeoCtg/zorlgMQcdTg==} + engines: {node: ^18.19.0 || >=20.6.0} + peerDependencies: + '@opentelemetry/api': '>=1.3.0 <1.10.0' + + '@opentelemetry/sdk-logs@0.200.0': + resolution: {integrity: sha512-VZG870063NLfObmQQNtCVcdXXLzI3vOjjrRENmU37HYiPFa0ZXpXVDsTD02Nh3AT3xYJzQaWKl2X2lQ2l7TWJA==} + engines: {node: ^18.19.0 || >=20.6.0} + peerDependencies: + '@opentelemetry/api': '>=1.4.0 <1.10.0' + + '@opentelemetry/sdk-metrics@2.0.0': + resolution: {integrity: sha512-Bvy8QDjO05umd0+j+gDeWcTaVa1/R2lDj/eOvjzpm8VQj1K1vVZJuyjThpV5/lSHyYW2JaHF2IQ7Z8twJFAhjA==} + engines: {node: ^18.19.0 || >=20.6.0} + peerDependencies: + '@opentelemetry/api': '>=1.9.0 <1.10.0' + + '@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/semantic-conventions@1.45.0': + resolution: {integrity: sha512-aOP45mbcOKbQVvFWfQmgoOPT8R2iwbbbgl83k4CujGV9BrJhFr3qG55MFpwFs4YLabpuW3HhzUY9eQxPlDqaZw==} + 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.2': + resolution: {integrity: sha512-b1UQwcEZ4yCnMCD8DAL1VlbvBJE9/IX4FTIp7BG1xYpf29SLazLSrqUkj4w7Y5y7cCVP6E5tcqqcI0xemPkHug==} + + '@types/node@26.6.6': + resolution: {integrity: sha512-MzQj+8Kx3XI40OmUAE6Qoh58ZMNstNNMwn2nfd3qWsfe7nDNSqJNzes4njhz6AHwrSHOffSLwXU6yb474Q4aog==} + + long@5.3.2: + resolution: {integrity: sha512-mNAgZ1GmyNhD7AuqnTG3/VQ26o760+ZYBPKjPvugO8+nLbYfX6TVpJPseBvopbdY+qpZ/lKUnmEc1LeZYS3QAA==} + + protobufjs@7.6.6: + resolution: {integrity: sha512-dYDWdjSl5RNb7SgPxGQcRU+GtvP7s2fpkrY0r432PcOIaZ0/rBcxEZnQN67iJhFuQiVw754JDoPruPCNdGsbjg==} + engines: {node: '>=12.0.0'} + + undici-types@8.9.0: + resolution: {integrity: sha512-KTDyRTYX8sWmKXAikPHHSyc63CRPETMctyjKFupcC6OBLXT3xsN0e9aF7m+mIXutFWpUXuedtowG7iLOzp0kQg==} + +snapshots: + + '@opentelemetry/api-logs@0.200.0': + dependencies: + '@opentelemetry/api': 1.9.0 + + '@opentelemetry/api@1.9.0': {} + + '@opentelemetry/context-async-hooks@2.0.0(@opentelemetry/api@1.9.0)': + dependencies: + '@opentelemetry/api': 1.9.0 + + '@opentelemetry/core@2.0.0(@opentelemetry/api@1.9.0)': + dependencies: + '@opentelemetry/api': 1.9.0 + '@opentelemetry/semantic-conventions': 1.45.0 + + '@opentelemetry/exporter-trace-otlp-http@0.200.0(@opentelemetry/api@1.9.0)': + dependencies: + '@opentelemetry/api': 1.9.0 + '@opentelemetry/core': 2.0.0(@opentelemetry/api@1.9.0) + '@opentelemetry/otlp-exporter-base': 0.200.0(@opentelemetry/api@1.9.0) + '@opentelemetry/otlp-transformer': 0.200.0(@opentelemetry/api@1.9.0) + '@opentelemetry/resources': 2.0.0(@opentelemetry/api@1.9.0) + '@opentelemetry/sdk-trace-base': 2.0.0(@opentelemetry/api@1.9.0) + + '@opentelemetry/otlp-exporter-base@0.200.0(@opentelemetry/api@1.9.0)': + dependencies: + '@opentelemetry/api': 1.9.0 + '@opentelemetry/core': 2.0.0(@opentelemetry/api@1.9.0) + '@opentelemetry/otlp-transformer': 0.200.0(@opentelemetry/api@1.9.0) + + '@opentelemetry/otlp-transformer@0.200.0(@opentelemetry/api@1.9.0)': + dependencies: + '@opentelemetry/api': 1.9.0 + '@opentelemetry/api-logs': 0.200.0 + '@opentelemetry/core': 2.0.0(@opentelemetry/api@1.9.0) + '@opentelemetry/resources': 2.0.0(@opentelemetry/api@1.9.0) + '@opentelemetry/sdk-logs': 0.200.0(@opentelemetry/api@1.9.0) + '@opentelemetry/sdk-metrics': 2.0.0(@opentelemetry/api@1.9.0) + '@opentelemetry/sdk-trace-base': 2.0.0(@opentelemetry/api@1.9.0) + protobufjs: 7.6.6 + + '@opentelemetry/resources@2.0.0(@opentelemetry/api@1.9.0)': + dependencies: + '@opentelemetry/api': 1.9.0 + '@opentelemetry/core': 2.0.0(@opentelemetry/api@1.9.0) + '@opentelemetry/semantic-conventions': 1.45.0 + + '@opentelemetry/sdk-logs@0.200.0(@opentelemetry/api@1.9.0)': + dependencies: + '@opentelemetry/api': 1.9.0 + '@opentelemetry/api-logs': 0.200.0 + '@opentelemetry/core': 2.0.0(@opentelemetry/api@1.9.0) + '@opentelemetry/resources': 2.0.0(@opentelemetry/api@1.9.0) + + '@opentelemetry/sdk-metrics@2.0.0(@opentelemetry/api@1.9.0)': + dependencies: + '@opentelemetry/api': 1.9.0 + '@opentelemetry/core': 2.0.0(@opentelemetry/api@1.9.0) + '@opentelemetry/resources': 2.0.0(@opentelemetry/api@1.9.0) + + '@opentelemetry/sdk-trace-base@2.0.0(@opentelemetry/api@1.9.0)': + dependencies: + '@opentelemetry/api': 1.9.0 + '@opentelemetry/core': 2.0.0(@opentelemetry/api@1.9.0) + '@opentelemetry/resources': 2.0.0(@opentelemetry/api@1.9.0) + '@opentelemetry/semantic-conventions': 1.45.0 + + '@opentelemetry/semantic-conventions@1.45.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 + + '@protobufjs/float@1.0.2': {} + + '@protobufjs/path@1.1.2': {} + + '@protobufjs/pool@1.1.0': {} + + '@protobufjs/utf8@1.1.2': {} + + '@types/node@26.6.6': + dependencies: + undici-types: 8.9.0 + + long@5.3.2: {} + + protobufjs@7.6.6: + dependencies: + '@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 + '@protobufjs/float': 1.0.2 + '@protobufjs/path': 1.1.2 + '@protobufjs/pool': 1.1.0 + '@protobufjs/utf8': 1.1.2 + '@types/node': 26.6.6 + long: 5.3.2 + + undici-types@8.9.0: {} diff --git a/e2e/scenarios/otel-sdk-compat/versions/otel-v3-latest/package.json b/e2e/scenarios/otel-sdk-compat/versions/otel-v3-latest/package.json new file mode 100644 index 000000000..ab849e61c --- /dev/null +++ b/e2e/scenarios/otel-sdk-compat/versions/otel-v3-latest/package.json @@ -0,0 +1,11 @@ +{ + "name": "@braintrust/e2e-otel-v3-latest", + "private": true, + "type": "module", + "dependencies": { + "@opentelemetry/api": "1.10.0", + "@opentelemetry/sdk-trace": "3.1.0", + "@opentelemetry/context-async-hooks": "3.1.0", + "@opentelemetry/exporter-trace-otlp-http": "0.301.0" + } +} diff --git a/e2e/scenarios/otel-sdk-compat/versions/otel-v3-latest/pnpm-lock.yaml b/e2e/scenarios/otel-sdk-compat/versions/otel-v3-latest/pnpm-lock.yaml new file mode 100644 index 000000000..6493ae4e6 --- /dev/null +++ b/e2e/scenarios/otel-sdk-compat/versions/otel-v3-latest/pnpm-lock.yaml @@ -0,0 +1,149 @@ +lockfileVersion: '9.0' + +settings: + autoInstallPeers: true + excludeLinksFromLockfile: false + +importers: + + .: + dependencies: + '@opentelemetry/api': + specifier: 1.10.0 + version: 1.10.0 + '@opentelemetry/context-async-hooks': + specifier: 3.1.0 + version: 3.1.0(@opentelemetry/api@1.10.0) + '@opentelemetry/exporter-trace-otlp-http': + specifier: 0.301.0 + version: 0.301.0(@opentelemetry/api@1.10.0) + '@opentelemetry/sdk-trace': + specifier: 3.1.0 + version: 3.1.0(@opentelemetry/api@1.10.0) + +packages: + + '@opentelemetry/api@1.10.0': + resolution: {integrity: sha512-lMkyes8iHrG+mk6WH7X70MdCLI2RS0FwNBY1PyQ8UemaijE2m5mv5A0Niy3HtbYTFrgkUEQjFMXmSAk2hQDuPQ==} + engines: {node: '>=8.0.0'} + + '@opentelemetry/context-async-hooks@3.1.0': + resolution: {integrity: sha512-IEgk4jBcLY1yLej9dFSJJWEIkUZTRsfBkJ0p/M8hpFKYBMYNzB8wwoziACFRiFW8mgpyBTRCjqjnd/IV5OoNLg==} + engines: {node: '>=22.15.0'} + peerDependencies: + '@opentelemetry/api': '>=1.0.0 <1.11.0' + + '@opentelemetry/core@3.1.0': + resolution: {integrity: sha512-TS/6jqU5gilzjvjGnsTEKVFF/0MMAtBc2psxaX39IGCBYLat83/iHxYBWn+O6WB+pnyB0SwiH46rvTEp/OeYxA==} + engines: {node: '>=22.15.0'} + peerDependencies: + '@opentelemetry/api': '>=1.10.0 <1.11.0' + + '@opentelemetry/exporter-trace-otlp-http@0.301.0': + resolution: {integrity: sha512-jngi+SsCkfg+7kBxn4TrtdK47lqzfKfh+qzxHTRaSF4GGD3q9z+mIUbvzytgrbqHwS4Q7NrEMJS6LEqeG+a1tw==} + engines: {node: '>=22.15.0'} + peerDependencies: + '@opentelemetry/api': ^1.10.0 + + '@opentelemetry/otlp-exporter-base@0.301.0': + resolution: {integrity: sha512-X5qmkSNvJMkUWnOsIZ1lg2VkcJkRTuXMvlACngE7EkxKWkfhgjq1GXd/qp8kCATokAq0RfNHtMFIZJ18FRjqKg==} + engines: {node: '>=22.15.0'} + peerDependencies: + '@opentelemetry/api': ^1.10.0 + + '@opentelemetry/otlp-transformer@0.301.0': + resolution: {integrity: sha512-lxFBIG8mhAnFouNxJNpT+6zxn7lrqwej9fPUWbrxuzldYXimesH8p5xwZYPHlapRMlGt2eMBvzMsvGnJUlJMgg==} + engines: {node: '>=22.15.0'} + peerDependencies: + '@opentelemetry/api': '>=1.10.0 <1.11.0' + + '@opentelemetry/resources@3.1.0': + resolution: {integrity: sha512-51dObapLj5PFRoQ+V4G4pZuQhzgFw/fC4m6F6Qa7rhjkrPyldroR34uOhL1vQqbpR02WdRh0JmsxyNlh2YNNvg==} + engines: {node: '>=22.15.0'} + peerDependencies: + '@opentelemetry/api': '>=1.10.0 <1.11.0' + + '@opentelemetry/sdk-logs@3.1.0': + resolution: {integrity: sha512-/WSmZkeGXQjSazW538YsD+3/aiQzZYA81pAddH2+7+VBxuTJl04W9c0xLke+m9jSTfuZaCIS4fbzeSUKKIzc4Q==} + engines: {node: '>=22.15.0'} + peerDependencies: + '@opentelemetry/api': '>=1.10.0 <1.11.0' + + '@opentelemetry/sdk-metrics@3.1.0': + resolution: {integrity: sha512-eU7MfYDxGPAJhA8bKYTkAFpmKrKMbgyKZQoBCKu+L9rJ3SKK3wfN3Vuv1/Kx90nLMwQW0931Zf04bO9LKYbcFQ==} + engines: {node: '>=22.15.0'} + peerDependencies: + '@opentelemetry/api': '>=1.10.0 <1.11.0' + + '@opentelemetry/sdk-trace@3.1.0': + resolution: {integrity: sha512-MlK9KhpKp4Gkz7o9OjwdWgSLCms1mr5SSDykqwaBq4NUsB6IhM4ihXpC6fZek3LGuCB8KIueQAPpcgU1Q3UYkg==} + engines: {node: '>=22.15.0'} + peerDependencies: + '@opentelemetry/api': '>=1.10.0 <1.11.0' + + '@opentelemetry/semantic-conventions@1.45.0': + resolution: {integrity: sha512-aOP45mbcOKbQVvFWfQmgoOPT8R2iwbbbgl83k4CujGV9BrJhFr3qG55MFpwFs4YLabpuW3HhzUY9eQxPlDqaZw==} + engines: {node: '>=14'} + +snapshots: + + '@opentelemetry/api@1.10.0': {} + + '@opentelemetry/context-async-hooks@3.1.0(@opentelemetry/api@1.10.0)': + dependencies: + '@opentelemetry/api': 1.10.0 + + '@opentelemetry/core@3.1.0(@opentelemetry/api@1.10.0)': + dependencies: + '@opentelemetry/api': 1.10.0 + '@opentelemetry/semantic-conventions': 1.45.0 + + '@opentelemetry/exporter-trace-otlp-http@0.301.0(@opentelemetry/api@1.10.0)': + dependencies: + '@opentelemetry/api': 1.10.0 + '@opentelemetry/otlp-exporter-base': 0.301.0(@opentelemetry/api@1.10.0) + '@opentelemetry/otlp-transformer': 0.301.0(@opentelemetry/api@1.10.0) + '@opentelemetry/sdk-trace': 3.1.0(@opentelemetry/api@1.10.0) + + '@opentelemetry/otlp-exporter-base@0.301.0(@opentelemetry/api@1.10.0)': + dependencies: + '@opentelemetry/api': 1.10.0 + '@opentelemetry/core': 3.1.0(@opentelemetry/api@1.10.0) + '@opentelemetry/otlp-transformer': 0.301.0(@opentelemetry/api@1.10.0) + + '@opentelemetry/otlp-transformer@0.301.0(@opentelemetry/api@1.10.0)': + dependencies: + '@opentelemetry/api': 1.10.0 + '@opentelemetry/core': 3.1.0(@opentelemetry/api@1.10.0) + '@opentelemetry/resources': 3.1.0(@opentelemetry/api@1.10.0) + '@opentelemetry/sdk-logs': 3.1.0(@opentelemetry/api@1.10.0) + '@opentelemetry/sdk-metrics': 3.1.0(@opentelemetry/api@1.10.0) + '@opentelemetry/sdk-trace': 3.1.0(@opentelemetry/api@1.10.0) + + '@opentelemetry/resources@3.1.0(@opentelemetry/api@1.10.0)': + dependencies: + '@opentelemetry/api': 1.10.0 + '@opentelemetry/core': 3.1.0(@opentelemetry/api@1.10.0) + '@opentelemetry/semantic-conventions': 1.45.0 + + '@opentelemetry/sdk-logs@3.1.0(@opentelemetry/api@1.10.0)': + dependencies: + '@opentelemetry/api': 1.10.0 + '@opentelemetry/core': 3.1.0(@opentelemetry/api@1.10.0) + '@opentelemetry/resources': 3.1.0(@opentelemetry/api@1.10.0) + '@opentelemetry/semantic-conventions': 1.45.0 + + '@opentelemetry/sdk-metrics@3.1.0(@opentelemetry/api@1.10.0)': + dependencies: + '@opentelemetry/api': 1.10.0 + '@opentelemetry/core': 3.1.0(@opentelemetry/api@1.10.0) + '@opentelemetry/resources': 3.1.0(@opentelemetry/api@1.10.0) + + '@opentelemetry/sdk-trace@3.1.0(@opentelemetry/api@1.10.0)': + dependencies: + '@opentelemetry/api': 1.10.0 + '@opentelemetry/core': 3.1.0(@opentelemetry/api@1.10.0) + '@opentelemetry/resources': 3.1.0(@opentelemetry/api@1.10.0) + '@opentelemetry/semantic-conventions': 1.45.0 + + '@opentelemetry/semantic-conventions@1.45.0': {} diff --git a/e2e/scenarios/otel-sdk-compat/versions/otel-v3/package.json b/e2e/scenarios/otel-sdk-compat/versions/otel-v3/package.json new file mode 100644 index 000000000..df0e8ddea --- /dev/null +++ b/e2e/scenarios/otel-sdk-compat/versions/otel-v3/package.json @@ -0,0 +1,11 @@ +{ + "name": "@braintrust/e2e-otel-v3", + "private": true, + "type": "module", + "dependencies": { + "@opentelemetry/api": "1.10.0", + "@opentelemetry/sdk-trace": "3.0.0", + "@opentelemetry/context-async-hooks": "3.0.0", + "@opentelemetry/exporter-trace-otlp-http": "0.300.0" + } +} diff --git a/e2e/scenarios/otel-sdk-compat/versions/otel-v3/pnpm-lock.yaml b/e2e/scenarios/otel-sdk-compat/versions/otel-v3/pnpm-lock.yaml new file mode 100644 index 000000000..318b9fa67 --- /dev/null +++ b/e2e/scenarios/otel-sdk-compat/versions/otel-v3/pnpm-lock.yaml @@ -0,0 +1,149 @@ +lockfileVersion: '9.0' + +settings: + autoInstallPeers: true + excludeLinksFromLockfile: false + +importers: + + .: + dependencies: + '@opentelemetry/api': + specifier: 1.10.0 + version: 1.10.0 + '@opentelemetry/context-async-hooks': + specifier: 3.0.0 + version: 3.0.0(@opentelemetry/api@1.10.0) + '@opentelemetry/exporter-trace-otlp-http': + specifier: 0.300.0 + version: 0.300.0(@opentelemetry/api@1.10.0) + '@opentelemetry/sdk-trace': + specifier: 3.0.0 + version: 3.0.0(@opentelemetry/api@1.10.0) + +packages: + + '@opentelemetry/api@1.10.0': + resolution: {integrity: sha512-lMkyes8iHrG+mk6WH7X70MdCLI2RS0FwNBY1PyQ8UemaijE2m5mv5A0Niy3HtbYTFrgkUEQjFMXmSAk2hQDuPQ==} + engines: {node: '>=8.0.0'} + + '@opentelemetry/context-async-hooks@3.0.0': + resolution: {integrity: sha512-qDZeKSgTT+qTaL99eRbpvWO5+oZ0MKvMH4pqvzi6xYKyhiMTDBxBeTLI6BpdeMt58T5TpxHJvFl9P52i6CDWGA==} + engines: {node: '>=22.15.0'} + peerDependencies: + '@opentelemetry/api': '>=1.0.0 <1.11.0' + + '@opentelemetry/core@3.0.0': + resolution: {integrity: sha512-whOLlww0R/RLtCWGBXhK1fdvL9t2/TL0fqdjZvcaoUUuNilRPjXecqQdzyVwV68s6XEDA5oYbm7/bZT62GXF4A==} + engines: {node: '>=22.15.0'} + peerDependencies: + '@opentelemetry/api': '>=1.10.0 <1.11.0' + + '@opentelemetry/exporter-trace-otlp-http@0.300.0': + resolution: {integrity: sha512-Dwdagba93cdhROlFGbkU4yK4hvd3l0k7WV9nvTWIoN6KwxFP+HCvy9EkgIpyfN11fVdIWxY3XqOwWOq83OPfcA==} + engines: {node: '>=22.15.0'} + peerDependencies: + '@opentelemetry/api': ^1.3.0 + + '@opentelemetry/otlp-exporter-base@0.300.0': + resolution: {integrity: sha512-D+PhzYURtv6p7Kk1rUrilLtV+ZvS218tI0jiKTV5z1agOeELw78C+cFd3o9UUshUx1j+EIpFM62yGk8BuydQvg==} + engines: {node: '>=22.15.0'} + peerDependencies: + '@opentelemetry/api': ^1.3.0 + + '@opentelemetry/otlp-transformer@0.300.0': + resolution: {integrity: sha512-T55lRmjRwvXWyTFdy/BbJ3T5RRsq3bAwQgxxA7qHFxgRd4+pZzRk02ZwOFsYK5072OXGYjYFNJcp68eQV4i0Ig==} + engines: {node: '>=22.15.0'} + peerDependencies: + '@opentelemetry/api': '>=1.10.0 <1.11.0' + + '@opentelemetry/resources@3.0.0': + resolution: {integrity: sha512-4762KVmlDroRIt+4Ra8AnwcQqdXlkAR55jDrhzna8bzhJx1mUzvHW/Lh/OemFuY9+ouM4xaDc6CbUmoJmM6BFg==} + engines: {node: '>=22.15.0'} + peerDependencies: + '@opentelemetry/api': '>=1.10.0 <1.11.0' + + '@opentelemetry/sdk-logs@3.0.0': + resolution: {integrity: sha512-IysNZWrBk2PqYw4qU9PZcC7pjrZLa+KLk7vcLmPPNBUr97MgeamWhX0DUHsmxwJv3dnjYM6mLRmP8mw69ld+dA==} + engines: {node: '>=22.15.0'} + peerDependencies: + '@opentelemetry/api': '>=1.10.0 <1.11.0' + + '@opentelemetry/sdk-metrics@3.0.0': + resolution: {integrity: sha512-YozyS2rIlizvUNLZwcWxH0x2FhX02/mcZwzM8hsl8yvQk+xeKmAc7vcSieWiO4oSwIB20B8eS/zwdopAd/J5zQ==} + engines: {node: '>=22.15.0'} + peerDependencies: + '@opentelemetry/api': '>=1.10.0 <1.11.0' + + '@opentelemetry/sdk-trace@3.0.0': + resolution: {integrity: sha512-4T0DbDGdtsjNmYK5/lNalHro4fsMZ0UZ3JE+C8fcqYP25xaJC22FHg8kdJNJ21srjJY5XoT7WNFCtj88NNTl4Q==} + engines: {node: '>=22.15.0'} + peerDependencies: + '@opentelemetry/api': '>=1.10.0 <1.11.0' + + '@opentelemetry/semantic-conventions@1.45.0': + resolution: {integrity: sha512-aOP45mbcOKbQVvFWfQmgoOPT8R2iwbbbgl83k4CujGV9BrJhFr3qG55MFpwFs4YLabpuW3HhzUY9eQxPlDqaZw==} + engines: {node: '>=14'} + +snapshots: + + '@opentelemetry/api@1.10.0': {} + + '@opentelemetry/context-async-hooks@3.0.0(@opentelemetry/api@1.10.0)': + dependencies: + '@opentelemetry/api': 1.10.0 + + '@opentelemetry/core@3.0.0(@opentelemetry/api@1.10.0)': + dependencies: + '@opentelemetry/api': 1.10.0 + '@opentelemetry/semantic-conventions': 1.45.0 + + '@opentelemetry/exporter-trace-otlp-http@0.300.0(@opentelemetry/api@1.10.0)': + dependencies: + '@opentelemetry/api': 1.10.0 + '@opentelemetry/otlp-exporter-base': 0.300.0(@opentelemetry/api@1.10.0) + '@opentelemetry/otlp-transformer': 0.300.0(@opentelemetry/api@1.10.0) + '@opentelemetry/sdk-trace': 3.0.0(@opentelemetry/api@1.10.0) + + '@opentelemetry/otlp-exporter-base@0.300.0(@opentelemetry/api@1.10.0)': + dependencies: + '@opentelemetry/api': 1.10.0 + '@opentelemetry/core': 3.0.0(@opentelemetry/api@1.10.0) + '@opentelemetry/otlp-transformer': 0.300.0(@opentelemetry/api@1.10.0) + + '@opentelemetry/otlp-transformer@0.300.0(@opentelemetry/api@1.10.0)': + dependencies: + '@opentelemetry/api': 1.10.0 + '@opentelemetry/core': 3.0.0(@opentelemetry/api@1.10.0) + '@opentelemetry/resources': 3.0.0(@opentelemetry/api@1.10.0) + '@opentelemetry/sdk-logs': 3.0.0(@opentelemetry/api@1.10.0) + '@opentelemetry/sdk-metrics': 3.0.0(@opentelemetry/api@1.10.0) + '@opentelemetry/sdk-trace': 3.0.0(@opentelemetry/api@1.10.0) + + '@opentelemetry/resources@3.0.0(@opentelemetry/api@1.10.0)': + dependencies: + '@opentelemetry/api': 1.10.0 + '@opentelemetry/core': 3.0.0(@opentelemetry/api@1.10.0) + '@opentelemetry/semantic-conventions': 1.45.0 + + '@opentelemetry/sdk-logs@3.0.0(@opentelemetry/api@1.10.0)': + dependencies: + '@opentelemetry/api': 1.10.0 + '@opentelemetry/core': 3.0.0(@opentelemetry/api@1.10.0) + '@opentelemetry/resources': 3.0.0(@opentelemetry/api@1.10.0) + '@opentelemetry/semantic-conventions': 1.45.0 + + '@opentelemetry/sdk-metrics@3.0.0(@opentelemetry/api@1.10.0)': + dependencies: + '@opentelemetry/api': 1.10.0 + '@opentelemetry/core': 3.0.0(@opentelemetry/api@1.10.0) + '@opentelemetry/resources': 3.0.0(@opentelemetry/api@1.10.0) + + '@opentelemetry/sdk-trace@3.0.0(@opentelemetry/api@1.10.0)': + dependencies: + '@opentelemetry/api': 1.10.0 + '@opentelemetry/core': 3.0.0(@opentelemetry/api@1.10.0) + '@opentelemetry/resources': 3.0.0(@opentelemetry/api@1.10.0) + '@opentelemetry/semantic-conventions': 1.45.0 + + '@opentelemetry/semantic-conventions@1.45.0': {} diff --git a/e2e/scenarios/otel-span-processor-export/scenario.ts b/e2e/scenarios/otel-span-processor-export/scenario.ts index c7d972ee7..123c7d961 100644 --- a/e2e/scenarios/otel-span-processor-export/scenario.ts +++ b/e2e/scenarios/otel-span-processor-export/scenario.ts @@ -1,5 +1,11 @@ +import { BatchSpanProcessor } from "@opentelemetry/sdk-trace-base"; +import { OTLPTraceExporter } from "@opentelemetry/exporter-trace-otlp-http"; import { context, trace } from "@opentelemetry/api"; -import { BraintrustSpanProcessor } from "@braintrust/otel"; +import { + BraintrustSpanProcessor, + BraintrustExporter, + braintrustOtlpConfig, +} from "@braintrust/otel"; import { createTracerProvider, getTestRunId, @@ -10,10 +16,18 @@ import { async function main() { const testRunId = getTestRunId(); const processor = new BraintrustSpanProcessor({ - apiKey: process.env.BRAINTRUST_API_KEY!, - apiUrl: process.env.BRAINTRUST_API_URL!, + processor: new BatchSpanProcessor( + new BraintrustExporter({ + exporter: new OTLPTraceExporter( + braintrustOtlpConfig({ + apiKey: process.env.BRAINTRUST_API_KEY!, + apiUrl: process.env.BRAINTRUST_API_URL!, + parent: `project_name:${scopedName("e2e-otel-span-processor-export", testRunId)}`, + }), + ), + }), + ), filterAISpans: true, - parent: `project_name:${scopedName("e2e-otel-span-processor-export", testRunId)}`, }); const provider = createTracerProvider([processor]); trace.setGlobalTracerProvider(provider); diff --git a/e2e/scenarios/otel-v1-span-processor-export/package.json b/e2e/scenarios/otel-v1-span-processor-export/package.json index 0905ad1e4..5cfc1a722 100644 --- a/e2e/scenarios/otel-v1-span-processor-export/package.json +++ b/e2e/scenarios/otel-v1-span-processor-export/package.json @@ -3,6 +3,7 @@ "private": true, "dependencies": { "@opentelemetry/api": "1.9.0", - "@opentelemetry/sdk-trace-base": "1.30.1" + "@opentelemetry/sdk-trace-base": "1.30.1", + "@opentelemetry/exporter-trace-otlp-http": "0.57.2" } } diff --git a/e2e/scenarios/otel-v1-span-processor-export/pnpm-lock.yaml b/e2e/scenarios/otel-v1-span-processor-export/pnpm-lock.yaml index fe9c41e96..7b82b8134 100644 --- a/e2e/scenarios/otel-v1-span-processor-export/pnpm-lock.yaml +++ b/e2e/scenarios/otel-v1-span-processor-export/pnpm-lock.yaml @@ -11,12 +11,19 @@ importers: '@opentelemetry/api': specifier: 1.9.0 version: 1.9.0 + '@opentelemetry/exporter-trace-otlp-http': + specifier: 0.57.2 + version: 0.57.2(@opentelemetry/api@1.9.0) '@opentelemetry/sdk-trace-base': specifier: 1.30.1 version: 1.30.1(@opentelemetry/api@1.9.0) packages: + '@opentelemetry/api-logs@0.57.2': + resolution: {integrity: sha512-uIX52NnTM0iBh84MShlpouI7UKqkZ7MrUszTmaypHBu4r7NofznSnQRfJ+uUeDtQDj6w8eFGg5KBLDAwAPz1+A==} + engines: {node: '>=14'} + '@opentelemetry/api@1.9.0': resolution: {integrity: sha512-3giAOQvZiH5F9bMlMiv8+GSPMeqg0dbaeo58/0SlA9sxSqZhnUtxzX9/2FzyhS9sWQf5S0GJE0AKBrFqjpeYcg==} engines: {node: '>=8.0.0'} @@ -27,12 +34,42 @@ packages: peerDependencies: '@opentelemetry/api': '>=1.0.0 <1.10.0' + '@opentelemetry/exporter-trace-otlp-http@0.57.2': + resolution: {integrity: sha512-sB/gkSYFu+0w2dVQ0PWY9fAMl172PKMZ/JrHkkW8dmjCL0CYkmXeE+ssqIL/yBUTPOvpLIpenX5T9RwXRBW/3g==} + engines: {node: '>=14'} + peerDependencies: + '@opentelemetry/api': ^1.3.0 + + '@opentelemetry/otlp-exporter-base@0.57.2': + resolution: {integrity: sha512-XdxEzL23Urhidyebg5E6jZoaiW5ygP/mRjxLHixogbqwDy2Faduzb5N0o/Oi+XTIJu+iyxXdVORjXax+Qgfxag==} + engines: {node: '>=14'} + peerDependencies: + '@opentelemetry/api': ^1.3.0 + + '@opentelemetry/otlp-transformer@0.57.2': + resolution: {integrity: sha512-48IIRj49gbQVK52jYsw70+Jv+JbahT8BqT2Th7C4H7RCM9d0gZ5sgNPoMpWldmfjvIsSgiGJtjfk9MeZvjhoig==} + engines: {node: '>=14'} + peerDependencies: + '@opentelemetry/api': ^1.3.0 + '@opentelemetry/resources@1.30.1': resolution: {integrity: sha512-5UxZqiAgLYGFjS4s9qm5mBVo433u+dSPUFWVWXmLAD4wB65oMCoXaJP1KJa9DIYYMeHu3z4BZcStG3LC593cWA==} engines: {node: '>=14'} peerDependencies: '@opentelemetry/api': '>=1.0.0 <1.10.0' + '@opentelemetry/sdk-logs@0.57.2': + resolution: {integrity: sha512-TXFHJ5c+BKggWbdEQ/inpgIzEmS2BGQowLE9UhsMd7YYlUfBQJ4uax0VF/B5NYigdM/75OoJGhAV3upEhK+3gg==} + engines: {node: '>=14'} + peerDependencies: + '@opentelemetry/api': '>=1.4.0 <1.10.0' + + '@opentelemetry/sdk-metrics@1.30.1': + resolution: {integrity: sha512-q9zcZ0Okl8jRgmy7eNW3Ku1XSgg3sDLa5evHZpCwjspw7E8Is4K/haRPDJrBcX3YSn/Y7gUvFnByNYEKQNbNog==} + engines: {node: '>=14'} + peerDependencies: + '@opentelemetry/api': '>=1.3.0 <1.10.0' + '@opentelemetry/sdk-trace-base@1.30.1': resolution: {integrity: sha512-jVPgBbH1gCy2Lb7X0AVQ8XAfgg0pJ4nvl8/IiQA6nxOsPvS+0zMJaFSs2ltXe0J6C8dqjcnpyqINDJmU30+uOg==} engines: {node: '>=14'} @@ -43,8 +80,52 @@ packages: resolution: {integrity: sha512-lp4qAiMTD4sNWW4DbKLBkfiMZ4jbAboJIGOQr5DvciMRI494OapieI9qiODpOt0XBr1LjIDy1xAGAnVs5supTA==} 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.2': + resolution: {integrity: sha512-b1UQwcEZ4yCnMCD8DAL1VlbvBJE9/IX4FTIp7BG1xYpf29SLazLSrqUkj4w7Y5y7cCVP6E5tcqqcI0xemPkHug==} + + '@types/node@26.6.6': + resolution: {integrity: sha512-MzQj+8Kx3XI40OmUAE6Qoh58ZMNstNNMwn2nfd3qWsfe7nDNSqJNzes4njhz6AHwrSHOffSLwXU6yb474Q4aog==} + + long@5.3.2: + resolution: {integrity: sha512-mNAgZ1GmyNhD7AuqnTG3/VQ26o760+ZYBPKjPvugO8+nLbYfX6TVpJPseBvopbdY+qpZ/lKUnmEc1LeZYS3QAA==} + + protobufjs@7.6.6: + resolution: {integrity: sha512-dYDWdjSl5RNb7SgPxGQcRU+GtvP7s2fpkrY0r432PcOIaZ0/rBcxEZnQN67iJhFuQiVw754JDoPruPCNdGsbjg==} + engines: {node: '>=12.0.0'} + + undici-types@8.9.0: + resolution: {integrity: sha512-KTDyRTYX8sWmKXAikPHHSyc63CRPETMctyjKFupcC6OBLXT3xsN0e9aF7m+mIXutFWpUXuedtowG7iLOzp0kQg==} + snapshots: + '@opentelemetry/api-logs@0.57.2': + dependencies: + '@opentelemetry/api': 1.9.0 + '@opentelemetry/api@1.9.0': {} '@opentelemetry/core@1.30.1(@opentelemetry/api@1.9.0)': @@ -52,12 +133,51 @@ snapshots: '@opentelemetry/api': 1.9.0 '@opentelemetry/semantic-conventions': 1.28.0 + '@opentelemetry/exporter-trace-otlp-http@0.57.2(@opentelemetry/api@1.9.0)': + dependencies: + '@opentelemetry/api': 1.9.0 + '@opentelemetry/core': 1.30.1(@opentelemetry/api@1.9.0) + '@opentelemetry/otlp-exporter-base': 0.57.2(@opentelemetry/api@1.9.0) + '@opentelemetry/otlp-transformer': 0.57.2(@opentelemetry/api@1.9.0) + '@opentelemetry/resources': 1.30.1(@opentelemetry/api@1.9.0) + '@opentelemetry/sdk-trace-base': 1.30.1(@opentelemetry/api@1.9.0) + + '@opentelemetry/otlp-exporter-base@0.57.2(@opentelemetry/api@1.9.0)': + dependencies: + '@opentelemetry/api': 1.9.0 + '@opentelemetry/core': 1.30.1(@opentelemetry/api@1.9.0) + '@opentelemetry/otlp-transformer': 0.57.2(@opentelemetry/api@1.9.0) + + '@opentelemetry/otlp-transformer@0.57.2(@opentelemetry/api@1.9.0)': + dependencies: + '@opentelemetry/api': 1.9.0 + '@opentelemetry/api-logs': 0.57.2 + '@opentelemetry/core': 1.30.1(@opentelemetry/api@1.9.0) + '@opentelemetry/resources': 1.30.1(@opentelemetry/api@1.9.0) + '@opentelemetry/sdk-logs': 0.57.2(@opentelemetry/api@1.9.0) + '@opentelemetry/sdk-metrics': 1.30.1(@opentelemetry/api@1.9.0) + '@opentelemetry/sdk-trace-base': 1.30.1(@opentelemetry/api@1.9.0) + protobufjs: 7.6.6 + '@opentelemetry/resources@1.30.1(@opentelemetry/api@1.9.0)': dependencies: '@opentelemetry/api': 1.9.0 '@opentelemetry/core': 1.30.1(@opentelemetry/api@1.9.0) '@opentelemetry/semantic-conventions': 1.28.0 + '@opentelemetry/sdk-logs@0.57.2(@opentelemetry/api@1.9.0)': + dependencies: + '@opentelemetry/api': 1.9.0 + '@opentelemetry/api-logs': 0.57.2 + '@opentelemetry/core': 1.30.1(@opentelemetry/api@1.9.0) + '@opentelemetry/resources': 1.30.1(@opentelemetry/api@1.9.0) + + '@opentelemetry/sdk-metrics@1.30.1(@opentelemetry/api@1.9.0)': + dependencies: + '@opentelemetry/api': 1.9.0 + '@opentelemetry/core': 1.30.1(@opentelemetry/api@1.9.0) + '@opentelemetry/resources': 1.30.1(@opentelemetry/api@1.9.0) + '@opentelemetry/sdk-trace-base@1.30.1(@opentelemetry/api@1.9.0)': dependencies: '@opentelemetry/api': 1.9.0 @@ -66,3 +186,45 @@ snapshots: '@opentelemetry/semantic-conventions': 1.28.0 '@opentelemetry/semantic-conventions@1.28.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 + + '@protobufjs/float@1.0.2': {} + + '@protobufjs/path@1.1.2': {} + + '@protobufjs/pool@1.1.0': {} + + '@protobufjs/utf8@1.1.2': {} + + '@types/node@26.6.6': + dependencies: + undici-types: 8.9.0 + + long@5.3.2: {} + + protobufjs@7.6.6: + dependencies: + '@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 + '@protobufjs/float': 1.0.2 + '@protobufjs/path': 1.1.2 + '@protobufjs/pool': 1.1.0 + '@protobufjs/utf8': 1.1.2 + '@types/node': 26.6.6 + long: 5.3.2 + + undici-types@8.9.0: {} 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 5993e3731..bbee2aa3e 100644 --- a/e2e/scenarios/otel-v1-span-processor-export/scenario.test.ts +++ b/e2e/scenarios/otel-v1-span-processor-export/scenario.test.ts @@ -1,5 +1,3 @@ -import { readFile } from "node:fs/promises"; -import { createRequire } from "node:module"; import { expect, test } from "vite-plus/test"; import { prepareScenarioDir, @@ -12,22 +10,14 @@ import { extractOtelSpans } from "../../helpers/trace-summary"; const scenarioDir = await prepareScenarioDir({ scenarioDir: resolveScenarioDir(import.meta.url), }); -const require = createRequire(import.meta.url); const otelSdkVersion = await readInstalledPackageVersion( scenarioDir, "@opentelemetry/sdk-trace-base", ); -const braintrustOtelEntry = require.resolve("@braintrust/otel"); -const otlpExporterManifestPath = require.resolve( - "@opentelemetry/exporter-trace-otlp-http/package.json", - { - paths: [braintrustOtelEntry], - }, +const otlpExporterVersion = await readInstalledPackageVersion( + scenarioDir, + "@opentelemetry/exporter-trace-otlp-http", ); -const otlpExporterManifest = JSON.parse( - await readFile(otlpExporterManifestPath, "utf8"), -) as { version: string }; -const otlpExporterVersion = otlpExporterManifest.version; type OtlpTraceRequest = { resourceSpans?: Array<{ @@ -45,7 +35,7 @@ type OtlpTraceRequest = { test(`otel-v1-span-processor-export exports real OTel ${otelSdkVersion} spans through OTLP exporter ${otlpExporterVersion}`, async () => { expect(otelSdkVersion).toMatch(/^1\./); - expect(Number(otlpExporterVersion.split(".")[1])).toBeGreaterThanOrEqual(200); + expect(otlpExporterVersion).toMatch(/^0\.57\./); await withScenarioHarness( async ({ requestsAfter, runScenarioDir, testRunId }) => { diff --git a/e2e/scenarios/otel-v1-span-processor-export/scenario.ts b/e2e/scenarios/otel-v1-span-processor-export/scenario.ts index 298160c97..f453e9da7 100644 --- a/e2e/scenarios/otel-v1-span-processor-export/scenario.ts +++ b/e2e/scenarios/otel-v1-span-processor-export/scenario.ts @@ -1,6 +1,12 @@ +import { BatchSpanProcessor } from "@opentelemetry/sdk-trace-base"; +import { OTLPTraceExporter } from "@opentelemetry/exporter-trace-otlp-http"; import { context, trace } from "@opentelemetry/api"; import { BasicTracerProvider } from "@opentelemetry/sdk-trace-base"; -import { BraintrustSpanProcessor } from "@braintrust/otel"; +import { + BraintrustSpanProcessor, + BraintrustExporter, + braintrustOtlpConfig, +} from "@braintrust/otel"; import { getTestRunId, runMain, @@ -10,9 +16,17 @@ import { async function main() { const testRunId = getTestRunId(); const processor = new BraintrustSpanProcessor({ - apiKey: process.env.BRAINTRUST_API_KEY!, - apiUrl: process.env.BRAINTRUST_API_URL!, - parent: `project_name:${scopedName("e2e-otel-v1-span-processor-export", testRunId)}`, + processor: new BatchSpanProcessor( + new BraintrustExporter({ + exporter: new OTLPTraceExporter( + braintrustOtlpConfig({ + apiKey: process.env.BRAINTRUST_API_KEY!, + apiUrl: process.env.BRAINTRUST_API_URL!, + parent: `project_name:${scopedName("e2e-otel-v1-span-processor-export", testRunId)}`, + }), + ), + }), + ), }); const provider = new BasicTracerProvider() as BasicTracerProvider & { addSpanProcessor: (processor: BraintrustSpanProcessor) => void; diff --git a/integrations/otel-js/CLAUDE.md b/integrations/otel-js/CLAUDE.md deleted file mode 100644 index a11b4ca9f..000000000 --- a/integrations/otel-js/CLAUDE.md +++ /dev/null @@ -1,470 +0,0 @@ -# OpenTelemetry Integration Testing Architecture - -## Overview - -This package (`@braintrust/otel`) integrates Braintrust with OpenTelemetry. To ensure compatibility across different OpenTelemetry versions, we use a unique testing architecture with isolated test environments. - -## Directory Structure - -``` -sdk/integrations/otel-js/ -├── src/ # Main source code (published) -│ ├── index.ts # Public API -│ ├── otel.ts # Core implementation -│ ├── context.ts # Context management -│ └── *.test.ts # Test files -├── tests/ -│ └── utils.ts # Cross-version compatibility helpers -├── dist/ # Built package (tsup output) -├── package.json # Main package definition -├── otel-v1/ # Test environment for OTel 1.x -│ ├── src -> ../src # SYMLINK to parent src/ -│ ├── package.json # Pins OTel 1.x versions -│ ├── 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 - ├── vite.config.ts # Aliases to local node_modules - ├── pnpm-lock.yaml # Independent lockfile - └── node_modules/ # Isolated v2.x dependencies -``` - -## How It Works - -### 1. Single Source, Multiple Test Environments - -- **Source code** lives in `src/` and is shared across both test environments via symlinks -- **Test execution** happens from `otel-v1/` and `otel-v2/` directories -- Each test environment has its own `package.json` with specific OpenTelemetry version constraints - -### 2. Symlinks - -Both `otel-v1/` and `otel-v2/` contain symlinks to the parent directories: - -```bash -cd otel-v1 && ls -la src -# lrwxr-xr-x src -> ../src - -cd otel-v2 && ls -la src -# lrwxr-xr-x src -> ../src -``` - -This means: - -- ✅ We maintain one source of truth for the code -- ✅ Tests run against the same code but with different OTel versions -- ✅ No code duplication - -### 3. Version Pinning - -#### otel-v1/package.json - -```json -{ - "devDependencies": { - "@opentelemetry/api": "1.9.0", - "@opentelemetry/core": "1.9.0", - "@opentelemetry/sdk-trace-base": "1.9.0", - "@opentelemetry/resources": "1.30.1", - "@opentelemetry/exporter-trace-otlp-http": "0.57.2" - } -} -``` - -#### otel-v2/package.json - -```json -{ - "devDependencies": { - "@opentelemetry/api": "1.9.0", - "@opentelemetry/core": "^2.0.0", - "@opentelemetry/sdk-trace-base": "2.0.0", // EXACT version, not ^2.0.0 - "@opentelemetry/resources": "2.0.0", // EXACT version, not ^2.0.0 - "@opentelemetry/exporter-trace-otlp-http": "^0.200.0" - } -} -``` - -**⚠️ Critical: Version 2.1.0 has bugs** - -- `@opentelemetry/resources@2.1.0` has a packaging bug where it references a missing `./detect-resources` module -- `@opentelemetry/sdk-trace-base@2.1.0` pulls in `@opentelemetry/resources@2.1.0` as a dependency -- **Solution**: Pin both packages to exactly `2.0.0` (without caret `^`) to prevent resolution to `2.1.0` - -### 4. Workspace Isolation - -**IMPORTANT**: These test packages MUST NOT be part of the pnpm workspace hoisting. - -#### Why Isolation is Critical - -If pnpm hoists dependencies, you get version conflicts: - -``` -sdk/node_modules/@opentelemetry/resources@2.1.0 ← Hoisted (broken) - ↑ -otel-v2/node_modules/@opentelemetry/resources@2.0.0 ← Should use this -``` - -Without isolation, Node.js may resolve to the hoisted version, breaking tests. - -#### How Isolation Works - -**Workspace Exclusions:** - -Both the root and SDK workspace configurations explicitly exclude otel-v1 and otel-v2: - -```yaml -# pnpm-workspace.yaml -packages: - - "sdk/integrations/*" - - "!sdk/integrations/otel-js/otel-v1" # Excluded - - "!sdk/integrations/otel-js/otel-v2" # Excluded -``` - -This ensures these directories maintain their own isolated `node_modules/` and aren't subject to workspace hoisting. - -**Local .npmrc Configuration:** - -Each test directory has a `.npmrc` file with: - -``` -link-workspace-packages=false -``` - -This prevents pnpm from linking workspace packages when installing dependencies. - -#### How to Install Dependencies - -**For CI and automated testing:** Dependencies are handled automatically by the dedicated workflow (see CI Testing section below). - -**For local development:** - -Due to a pnpm quirk, you may need to "nudge" pnpm to create local `node_modules`: - -```bash -cd otel-v2 -rm -rf node_modules -pnpm add -D vitest # Forces creation of local node_modules -``` - -After the initial setup, subsequent `pnpm install` commands will work correctly because the `.npmrc` and workspace exclusions are in place. - -**Alternatively, use --ignore-workspace:** - -```bash -cd otel-v2 -pnpm install --ignore-workspace # Forces isolated install -``` - -This is rarely needed but useful for troubleshooting or regenerating dependencies from scratch. - -### 5. Vitest Configuration - -Each test environment has a `vite.config.ts` that creates module aliases: - -```typescript -import { defineConfig } from "vite-plus"; -import { - detectOtelVersion, - logOtelVersions, - createOtelAliases, -} from "../tests/utils"; - -const cwd = process.cwd(); -const version = detectOtelVersion(cwd); // Detects "v1" or "v2" from path - -logOtelVersions(version); // Logs installed versions for -debugging; - -export default defineConfig({ - resolve: - version !== "parent" - ? { - alias: createOtelAliases(cwd), // Force resolution to local node_modules - } - : {}, -}); -``` - -The `createOtelAliases()` function creates absolute path aliases: - -```typescript -{ - "@opentelemetry/api": "/full/path/to/otel-v2/node_modules/@opentelemetry/api", - "@opentelemetry/core": "/full/path/to/otel-v2/node_modules/@opentelemetry/core", - "@opentelemetry/sdk-trace-base": "/full/path/to/otel-v2/node_modules/@opentelemetry/sdk-trace-base", - "@opentelemetry/resources": "/full/path/to/otel-v2/node_modules/@opentelemetry/resources", - // ... etc -} -``` - -This ensures vitest resolves OTel packages from the local test environment, not from hoisted workspace packages. - -### 6. Cross-Version Compatibility Helpers - -`tests/utils.ts` provides helpers that work across both OTel v1 and v2: - -#### Example: createTracerProvider() - -OpenTelemetry changed APIs between versions: - -**v1.x API:** - -```typescript -const provider = new BasicTracerProvider(); -provider.addSpanProcessor(processor); // Method-based -``` - -**v2.x API:** - -```typescript -const provider = new BasicTracerProvider({ - spanProcessors: [processor], // Constructor-based -}); -``` - -**Our helper:** - -```typescript -export function createTracerProvider( - ProviderClass: any, - processors: any[], - config?: any, -): any { - const testProvider = new ProviderClass(config || {}); - - if (typeof testProvider.addSpanProcessor === "function") { - // OTel 1.x path - const provider = new ProviderClass(config); - for (const processor of processors) { - provider.addSpanProcessor(processor); - } - return provider; - } else { - // OTel 2.x path - return new ProviderClass({ - ...config, - spanProcessors: processors, - }); - } -} -``` - -Tests use this helper to work seamlessly across both versions. - -## Running Tests - -### Test Both Versions - -```bash -cd sdk/integrations/otel-js -pnpm test # Runs both v1 and v2 tests -``` - -### Test Specific Version - -```bash -cd sdk/integrations/otel-js -pnpm test:v1 # Only OTel 1.x tests -pnpm test:v2 # Only OTel 2.x tests -``` - -### Test Specific File in v2 - -```bash -cd otel-v2 -pnpm test exporter.test.ts -``` - -## Troubleshooting - -### Error: "Cannot find module 'braintrust' or its corresponding type declarations" - -**Cause:** The `braintrust` peer dependency is not available during TypeScript declaration (`.d.ts`) build - -**Solution:** The `braintrust` package must be added as a dev dependency in addition to being a peer dependency. This is already configured in `package.json`: - -```json -{ - "devDependencies": { - "braintrust": "workspace:*" - }, - "peerDependencies": { - "braintrust": ">=0.5.0" - } -} -``` - -The workspace reference ensures the local `braintrust` SDK is available during builds while the peer dependency ensures consumers provide their own version. - -### Error: "Cannot find module './detect-resources'" - -**Cause:** `@opentelemetry/resources@2.1.0` is being used (it's broken) - -**Solution:** - -1. Check `otel-v2/package.json` has exact versions: - ```json - "@opentelemetry/sdk-trace-base": "2.0.0", - "@opentelemetry/resources": "2.0.0" - ``` -2. Reinstall with isolation: - ```bash - cd otel-v2 - rm -rf node_modules - pnpm install --ignore-workspace - ``` - -### Error: "vitest: command not found" - -**Cause:** Local `node_modules/` wasn't created (workspace hoisting was used) - -**Solution:** - -```bash -cd otel-v2 -pnpm install --ignore-workspace -``` - -### Wrong OTel Version in Tests - -Check the console output at test start: - -``` -=== OpenTelemetry Versions (v2) === - @opentelemetry/api: 1.9.0 - @opentelemetry/core: 2.0.0 - @opentelemetry/resources: 2.0.0 ← Should be 2.0.0, NOT 2.1.0 - @opentelemetry/sdk-trace-base: 2.0.0 -=================================== -``` - -If versions are wrong, reinstall with `--ignore-workspace`. - -## Development Workflow - -### Making Changes to Source Code - -1. Edit files in `src/` (not in `otel-v1/src` or `otel-v2/src` - those are symlinks!) -2. Run tests in both environments: - ```bash - pnpm test - ``` - -### Adding New Dependencies - -**For main package:** - -```bash -cd sdk/integrations/otel-js -pnpm add -``` - -**For test environments (rare):** - -```bash -cd otel-v1 -pnpm add -D --ignore-workspace -``` - -### Adding New Test Files - -Test files should be added to `src/` and will automatically be available in both test environments via symlinks: - -```bash -# Create test file in main src directory -touch src/my-new-feature.test.ts - -# Now accessible from both: -ls otel-v1/src/my-new-feature.test.ts # Via symlink -ls otel-v2/src/my-new-feature.test.ts # Via symlink -``` - -## Why This Architecture? - -1. **Version Compatibility**: OpenTelemetry has breaking changes between major versions -2. **Single Source of Truth**: Symlinks ensure we test the same code, not duplicates -3. **Isolation**: Separate `node_modules` prevents version conflicts -4. **CI/CD**: Tests verify compatibility with multiple OTel versions before publishing -5. **Real-world Testing**: Users may have either OTel v1.x or v2.x installed - -## CI Testing - -The otel-js package has a **dedicated CI workflow** separate from the main JavaScript SDK tests. - -### Why a Separate Workflow? - -1. **Unique isolation requirements**: otel-v1 and otel-v2 need isolated `node_modules` -2. **Special setup steps**: Requires "nudging" pnpm to create local dependencies -3. **Different failure modes**: OTel version conflicts shouldn't block other SDK tests -4. **Clearer test results**: Separate workflow makes it easier to identify OTel-specific issues - -### CI Workflow: `.github/workflows/checks.yaml` - -The consolidated `checks` workflow contains a dedicated `otel-js` job that: - -1. **Builds isolated Docker images** for `otel-v1` and `otel-v2` -2. **Runs each image across Node 20 and 22** -3. **Keeps OTel-specific failures scoped to the `otel-js` job** - -### Workflow Triggers - -The workflow runs on: - -- **Pull requests** that modify: - - `integrations/otel-js/**` - - `js/**` (braintrust SDK) - - `.github/workflows/checks.yaml` -- **Pushes to main** that modify the same paths - -### Matrix Testing - -Tests run on: - -- **Operating systems**: Ubuntu, Windows -- **Node versions**: 20, 22 - -### Checks Workflow Placement - -OTel coverage now lives inside the consolidated `checks` workflow as its own job rather than in a separate workflow file. - -### Makefile Targets - -**For CI:** - -```bash -make js-verify-checks # Main hermetic checks workflow entrypoint -make js-test-otel-docker # Local equivalent for the Docker-based otel-js job -``` - -**For local development:** - -```bash -cd integrations/otel-js -pnpm test # Runs both v1 and v2 tests -pnpm test:v1 # Only OTel 1.x tests -pnpm test:v2 # Only OTel 2.x tests -``` - -**For Docker local development:** - -```bash -# Build and run otel-v1 tests -docker build -f integrations/otel-js/Dockerfile.test --build-arg NODE_VERSION=22 --build-arg TEST_DIR=otel-v1 -t otel-js-test-v1 . && docker run --rm otel-js-test-v1 - -# Build and run otel-v2 tests -docker build -f integrations/otel-js/Dockerfile.test --build-arg NODE_VERSION=22 --build-arg TEST_DIR=otel-v2 -t otel-js-test-v2 . && docker run --rm otel-js-test-v2 -``` - -## Related Files - -- `package.json` - Main package definition and peer dependencies -- `tsup.config.ts` - Build configuration for the published package -- `tests/utils.ts` - Cross-version compatibility helpers -- `otel-v1/package.json` - OTel 1.x version constraints -- `otel-v1/.npmrc` - Isolation config for v1 -- `otel-v2/package.json` - OTel 2.x version constraints -- `otel-v2/.npmrc` - Isolation config for v2 -- `.github/workflows/checks.yaml` - Consolidated CI workflow diff --git a/integrations/otel-js/Dockerfile.test b/integrations/otel-js/Dockerfile.test index eaee521f6..b9507cb47 100644 --- a/integrations/otel-js/Dockerfile.test +++ b/integrations/otel-js/Dockerfile.test @@ -17,7 +17,7 @@ ARG TEST_DIR # Validate that required build arguments are provided RUN if [ -z "$NODE_VERSION" ] || [ "$NODE_VERSION" = "REQUIRED" ]; then echo "Error: NODE_VERSION build arg is required (e.g., --build-arg NODE_VERSION=20)" && exit 1; fi RUN if [ -z "$TEST_DIR" ]; then echo "Error: TEST_DIR build arg is required (e.g., --build-arg TEST_DIR=otel-v1)" && exit 1; fi -RUN if [ "$TEST_DIR" != "otel-v1" ] && [ "$TEST_DIR" != "otel-v2" ]; then echo "Error: TEST_DIR must be either 'otel-v1' or 'otel-v2', got: '$TEST_DIR'" && exit 1; fi +RUN if [ "$TEST_DIR" != "otel-v1" ] && [ "$TEST_DIR" != "otel-v2" ] && [ "$TEST_DIR" != "otel-v3" ]; then echo "Error: TEST_DIR must be 'otel-v1', 'otel-v2' or 'otel-v3', got: '$TEST_DIR'" && exit 1; fi # Install pnpm (Corepack is not included in Node.js 26 images) RUN npm install --global pnpm@10 diff --git a/integrations/otel-js/README.md b/integrations/otel-js/README.md index 2868f8a0a..2ecbae8a0 100644 --- a/integrations/otel-js/README.md +++ b/integrations/otel-js/README.md @@ -1,222 +1,153 @@ # @braintrust/otel -[![npm version](https://img.shields.io/npm/v/%40braintrust%2Fotel.svg)](https://www.npmjs.com/package/@braintrust/otel) +Connect an application's OpenTelemetry pipeline to Braintrust. +The application selects and configures its OpenTelemetry SDK, processors, and transport. +This package depends on the public `@opentelemetry/api` API and the matching Braintrust SDK, with no OpenTelemetry SDK peer dependencies. -SDK for integrating [Braintrust](https://braintrust.dev) with [OpenTelemetry](https://opentelemetry.io/). This package enables you to send OpenTelemetry spans to Braintrust for logging and observability, and provides seamless interoperability between Braintrust and OpenTelemetry tracing contexts. +## Export with standard OTLP -## Installation +Use your installed OTLP/HTTP exporter with Braintrust's endpoint and headers: -This package has peer dependencies that you must install alongside it: +```ts +import { NodeSDK } from "@opentelemetry/sdk-node"; +import { OTLPTraceExporter } from "@opentelemetry/exporter-trace-otlp-http"; +import { braintrustOtlpConfig } from "@braintrust/otel"; -```bash -npm install @braintrust/otel braintrust @opentelemetry/api @opentelemetry/core @opentelemetry/sdk-trace-base @opentelemetry/exporter-trace-otlp-http -# or -yarn add @braintrust/otel braintrust @opentelemetry/api @opentelemetry/core @opentelemetry/sdk-trace-base @opentelemetry/exporter-trace-otlp-http -# or -pnpm add @braintrust/otel braintrust @opentelemetry/api @opentelemetry/core @opentelemetry/sdk-trace-base @opentelemetry/exporter-trace-otlp-http +const sdk = new NodeSDK({ + traceExporter: new OTLPTraceExporter( + braintrustOtlpConfig({ parent: "project_name:my-project" }), + ), +}); +sdk.start(); ``` -If you're using additional OpenTelemetry features like context managers, you may also need: - -```bash -npm install @opentelemetry/context-async-hooks +`braintrustOtlpConfig()` returns `{ url, headers }` synchronously. +It does not create an exporter, contact Braintrust, register a provider, or create a project. +The exporter determines the wire encoding and content type. +Use exporter and SDK versions that are compatible with each other. + +Options: + +- `apiKey`: defaults to `BRAINTRUST_API_KEY`, then the nearest `.env.braintrust` file in Node.js. +- `apiUrl`: defaults to `BRAINTRUST_API_URL`, then `https://api.braintrust.dev`. +- `parent`: a Braintrust project or experiment identifier, such as `project_name:my-project` or `project_id:`; defaults to `BRAINTRUST_PARENT`. +- `headers`: additional transport headers. + The resolved API key and parent take precedence over authorization and routing headers, including differently cased names. + +Missing credentials or parent configuration throw during setup. +There is no implicit default project. + +## Braintrust processors and exporters + +Use the adapters when you need Braintrust routing, span origin metadata, AI filtering, or export customization. +For OTel 3: + +```ts +import { NodeSDK } from "@opentelemetry/sdk-node"; +import { BatchSpanProcessor } from "@opentelemetry/sdk-trace"; +import { OTLPTraceExporter } from "@opentelemetry/exporter-trace-otlp-http"; +import { + BraintrustExporter, + BraintrustSpanProcessor, + braintrustOtlpConfig, +} from "@braintrust/otel"; + +const exporter = new BraintrustExporter({ + exporter: new OTLPTraceExporter( + braintrustOtlpConfig({ parent: "project_name:my-project" }), + ), +}); +const processor = new BraintrustSpanProcessor({ + processor: new BatchSpanProcessor({ exporter }), + filterAISpans: true, +}); +const sdk = new NodeSDK({ spanProcessors: [processor] }); +sdk.start(); ``` -## Requirements - -- Node.js >= 16 -- OpenTelemetry API >= 1.9.0 -- OpenTelemetry SDK >= 1.9.0 -- Braintrust >= 0.5.0 - -This package supports both OpenTelemetry 1.x and 2.x versions. - -Note: You may run into a known [OpenTelemetry browser bug](https://github.com/open-telemetry/opentelemetry-js/issues/3545) with `@opentelemetry/exporter-trace-otlp-http` < 0.205.0. If you encounter this issue, upgrade the `@opentelemetry/exporter-trace-otlp-http` and related packages to a minimum of: - -- `@opentelemetry/exporter-trace-otlp-http` >= 0.205.0 -- `@opentelemetry/sdk-trace-base` >= 2.1.0 -- `@opentelemetry/api` >= 2.1.0 -- `@opentelemetry/core` >= 2.1.0 - -It's **important** that `@opentelemetry/core` and related packages all use the same major version (1.x or 2.x). - -## Capabilities - -This integration provides two main capabilities: - -1. **Export OpenTelemetry spans to Braintrust**: Use `BraintrustSpanProcessor` to send your OpenTelemetry traces to Braintrust for logging, monitoring, and analysis. +For OTel 1 and 2, import `BatchSpanProcessor` from `@opentelemetry/sdk-trace-base` and construct it with `new BatchSpanProcessor(exporter)`. +The Braintrust adapters use the same entry point and options for all three SDK lines. -2. **Bidirectional interoperability between Braintrust and OpenTelemetry**: Use `setupOtelCompat()` to enable Braintrust spans and OpenTelemetry spans to work together in the same unified trace. This allows: - - OpenTelemetry spans to be children of Braintrust spans - - Braintrust spans to be children of OpenTelemetry spans - - Both systems share the same trace/span IDs (using OpenTelemetry's format) - - Seamless context propagation across service boundaries, regardless of which tracing system each service uses +`BraintrustSpanProcessor` accepts a required `processor` instance. +It adds Braintrust routing from the supplied parent context, propagator baggage, parent span attributes, or active native Braintrust span. +It adds span origin metadata and optionally filters completed spans before forwarding them. +It forwards processor lifecycle hooks, including OTel 3’s optional `onEnding` hook. +It does not create a batch processor or exporter. -## Core Components +`BraintrustExporter` accepts a required `exporter` instance. +It enriches completed batches, optionally filters them, applies registered Braintrust span customizers, and forwards the resulting batch. +It does not batch spans again or force a transport flush after every export. +A processor alone does not apply export customizers; put `BraintrustExporter` around the transport when those customizers are required. -### `BraintrustSpanProcessor` +Both adapters accept: -A span processor that sends OpenTelemetry spans to Braintrust's telemetry endpoint. +- `filterAISpans`: defaults to `false`. + When enabled, only spans with names or attributes starting with `gen_ai.`, `braintrust.`, `llm.`, `ai.`, or `traceloop.` pass. + Routing and origin attributes alone do not make a span an AI span. + Root spans receive no automatic exemption. +- `customFilter`: with filtering enabled, return `true` to keep a span, `false` to drop it, or `null`/`undefined` to use the default filter. +- `environment`: optional `{ type, name }` span origin metadata, otherwise inferred from the runtime environment. -**When to use it**: Add this processor to your OpenTelemetry `TracerProvider` when you want to export OpenTelemetry spans to Braintrust. This is useful when you're already using OpenTelemetry instrumentation and want to leverage Braintrust's logging and observability features. +Configure filtering on one adapter to avoid evaluating your filter twice. +The SDK owns sampling, batching, queue limits, and export timeouts. +The transport owns serialization and retries. -**Key features**: +For Next.js Edge, use `OTLPHttpJsonTraceExporter` from `@vercel/otel` as the downstream exporter. +It uses `fetch`, which the Edge runtime supports. +The standard OTel browser exporter can require `XMLHttpRequest`. -- Automatically sends spans to Braintrust using OTLP protocol -- Optional AI span filtering to reduce noise from non-AI operations -- Supports custom filtering logic -- Configurable via options or environment variables +## Context interoperability -```typescript -import { BasicTracerProvider } from "@opentelemetry/sdk-trace-base"; -import { trace } from "@opentelemetry/api"; -import { BraintrustSpanProcessor } from "@braintrust/otel"; - -const provider = new BasicTracerProvider(); -provider.addSpanProcessor( - new BraintrustSpanProcessor({ - apiKey: "your-api-key", // or set BRAINTRUST_API_KEY env var - parent: "project_name:my_project", // or set BRAINTRUST_PARENT env var - filterAISpans: true, // optional: filter out non-AI spans - }), -); - -trace.setGlobalTracerProvider(provider); -``` - -**Configuration options**: - -- `apiKey` (string, optional): Braintrust API key. Falls back to the `BRAINTRUST_API_KEY` environment variable, then the nearest `.env.braintrust` file in Node.js. -- `apiUrl` (string, optional): Braintrust API URL. Falls back to `BRAINTRUST_API_URL` environment variable. Defaults to `https://api.braintrust.dev`. -- `parent` (string, optional): Parent project identifier in format `project_name:my_project` or `project_id:uuid`. Falls back to `BRAINTRUST_PARENT` environment variable. -- `filterAISpans` (boolean, optional): When true, filters out non-AI spans. Only spans with prefixes like `gen_ai.*`, `llm.*`, `ai.*`, `braintrust.*`, or `traceloop.*` will be sent. Defaults to false. -- `customFilter` (function, optional): Custom filter function for advanced span filtering logic. -- `headers` (object, optional): Additional HTTP headers to send with telemetry data. - -### `setupOtelCompat()` - -Configures Braintrust to use OpenTelemetry's context management and ID generation, enabling bidirectional interoperability. - -**When to use it**: Call this function **once at application startup, before creating any Braintrust loggers or OpenTelemetry spans**. This configures Braintrust to integrate with OpenTelemetry tracing contexts and enables: - -- **Braintrust spans to appear as children of OpenTelemetry spans**: When you create a Braintrust span inside an OpenTelemetry span context, it automatically becomes a child -- **OpenTelemetry spans to appear as children of Braintrust spans**: When you create an OpenTelemetry span inside a Braintrust span context, it automatically becomes a child -- **Unified trace structure**: Both systems use OpenTelemetry-compatible trace and span IDs, so spans from both systems appear in the same trace tree -- **Distributed tracing**: Proper context propagation across services that use different tracing systems - -```typescript +```ts import { setupOtelCompat } from "@braintrust/otel"; -import { initLogger } from "braintrust"; -// IMPORTANT: Call setupOtelCompat() before creating any Braintrust loggers or OpenTelemetry spans +// Register your OTel provider and context manager first. setupOtelCompat(); - -// Now Braintrust will use OpenTelemetry context management -const logger = initLogger({ projectName: "my_project" }); -``` - -**What it does**: - -- Sets Braintrust to use OpenTelemetry's context manager for parent-child span relationships -- 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). - -### `contextFromSpan(span: Span)` - -Creates an OpenTelemetry context from a Braintrust span using W3C Trace Context. - -**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 { contextFromSpan } from "@braintrust/otel"; -import { context } from "@opentelemetry/api"; - -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) => { - // ... - span.end(); - }); -}); -``` - -### `addSpanParentToBaggage(span: Span, ctx?: Context)` - -Copies the `braintrust.parent` attribute from an OpenTelemetry span to OpenTelemetry baggage. - -**When to use it**: When you need to propagate Braintrust parent information across service boundaries using OpenTelemetry's propagation mechanism (typically before calling `propagation.inject()`). - -```typescript -import { addSpanParentToBaggage } from "@braintrust/otel"; -import { propagation, context } from "@opentelemetry/api"; - -tracer.startActiveSpan("service_a", async (span) => { - // Copy braintrust.parent to baggage so it propagates - const ctx = addSpanParentToBaggage(span); - - // Export headers for downstream service - const headers = {}; - propagation.inject(ctx, headers); - - // Send headers to Service B - await fetch("https://service-b/api", { headers }); - - span.end(); -}); ``` -### `addParentToBaggage(parent: string, ctx?: Context)` - -Adds a Braintrust parent identifier directly to OpenTelemetry baggage. +Native Braintrust spans and OTel spans can then share trace IDs and parent relationships. +The bridge uses only public OTel context APIs, including for instrumentation store propagation. +It requires a registered OTel context manager for asynchronous propagation. +`resetOtelCompat()` restores Braintrust's default context manager. -**When to use it**: When you have a Braintrust parent string and want to add it to OpenTelemetry context for propagation. +For explicit propagation: -```typescript -import { addParentToBaggage } from "@braintrust/otel"; +- `contextFromSpan(span)` creates an OTel parent context from a native Braintrust span, including trace identity, trace state, and Braintrust baggage routing. +- `addParentToBaggage(parent, context?)` returns a context containing Braintrust routing baggage. +- `addSpanParentToBaggage(span, context?)` copies the span's routing attribute into baggage. +- `parentFromHeaders(headers)` extracts native Braintrust propagation context from W3C headers. -const parent = "project_name:my_project"; -const ctx = addParentToBaggage(parent); -// Now ctx contains braintrust.parent in baggage -``` +Use the returned context with `context.with(...)` or as the explicit parent argument to `tracer.startSpan(...)`. +These helpers do not replace the active context by themselves. -### `parentFromHeaders(headers: Record)` +## Flush and shutdown -Extracts Braintrust parent information from HTTP headers that contain OpenTelemetry trace context. +Flush the application's provider or processor to drain the SDK's queue. +`BraintrustExporter.forceFlush()` only waits for batches already received and flushes its downstream exporter when that method is available. +It cannot flush a queue that belongs to an upstream processor. -**When to use it**: When Service A uses OpenTelemetry and sends trace headers to Service B that uses Braintrust. This allows Service B to create Braintrust spans as children of Service A's span. +Each `BraintrustSpanProcessor` registers its downstream flush with Braintrust and removes its registration during shutdown. +Multiple processors can be registered at the same time. +The application owns provider shutdown; adapter shutdown delegates to its downstream instance once. -```typescript -import { parentFromHeaders } from "@braintrust/otel"; -import { initLogger } from "braintrust"; +For the plain OTLP configuration path, connect a provider explicitly if Braintrust scorers need to flush OTel spans before querying: -// Service B receives headers from Service A (OpenTelemetry) -const parent = parentFromHeaders(incomingHeaders); +```ts +import { registerOtelFlush } from "braintrust/instrumentation"; -// Create Braintrust span as child of Service A's span -const logger = initLogger({ projectName: "my_project" }); -await logger.traced( - async (span) => { - // This span will be a child of Service A's span - span.log({ message: "Processing in Service B" }); - }, - { name: "service_b_operation", parent }, -); +const unregister = registerOtelFlush(() => provider.forceFlush()); +// When the application shuts down: +unregister(); +await provider.shutdown(); ``` -## Environment Variables - -The following environment variables can be used to configure the integration: - -- `BRAINTRUST_API_KEY`: Your Braintrust API key (required for `BraintrustSpanProcessor`; in Node.js, this can also be set in the nearest `.env.braintrust` file) -- `BRAINTRUST_PARENT`: Parent project identifier (e.g., `project_name:my_project`) -- `BRAINTRUST_API_URL`: Braintrust API endpoint (defaults to `https://api.braintrust.dev`) +## Migration -## Documentation +Replace `new BraintrustSpanProcessor({ apiKey, parent })` with an application-owned processor and exporter, as shown above. +Move credentials, API URL, parent, and custom headers to `braintrustOtlpConfig()`. +Replace the internal `_spanProcessor` option with the required `processor` option. +Replace `new BraintrustExporter({ apiKey, parent })` with `new BraintrustExporter({ exporter: ... })`. +Keep customizers at the exporter boundary and flush the provider or processor instead of relying on the old nested batch processor. -- [Braintrust Documentation](https://docs.braintrust.dev) -- [OpenTelemetry Documentation](https://opentelemetry.io/docs/) -- [Braintrust OTEL Guide](https://docs.braintrust.dev/integrations/sdk-integrations/opentelemetry/send-traces-and-logs) +No `/v3` entry point or runtime SDK version detection is required. +Compatibility tests cover OTel 1, 2, and 3; new SDK versions still require verification of their public span contracts. diff --git a/integrations/otel-js/otel-v1/package.json b/integrations/otel-js/otel-v1/package.json index e5b6cd127..ba4375841 100644 --- a/integrations/otel-js/otel-v1/package.json +++ b/integrations/otel-js/otel-v1/package.json @@ -6,21 +6,22 @@ "scripts": { "clean": "rm -rf dist/*", "build:deps": "(cd ../../.. && pnpm exec vp run --filter=braintrust --filter=@braintrust/otel build)", - "test": "pnpm run build:deps && vp test run" + "test": "pnpm run build:deps && pnpm run check:typings && vp test run", + "check:typings": "tsc --noEmit" }, "author": "Braintrust Data Inc.", "license": "MIT", "devDependencies": { "@braintrust/otel": "file:..", "@opentelemetry/api": "1.9.0", - "@opentelemetry/context-async-hooks": "1.9.0", - "@opentelemetry/core": "1.9.0", + "@opentelemetry/context-async-hooks": "1.30.1", + "@opentelemetry/core": "1.30.1", "@opentelemetry/exporter-trace-otlp-http": "0.57.2", "@opentelemetry/otlp-exporter-base": "0.57.2", "@opentelemetry/otlp-transformer": "0.57.2", "@opentelemetry/resources": "1.30.1", - "@opentelemetry/sdk-node": "0.35.0", - "@opentelemetry/sdk-trace-base": "1.9.0", + "@opentelemetry/sdk-node": "0.57.2", + "@opentelemetry/sdk-trace-base": "1.30.1", "@types/node": "^22.15.21", "braintrust": "file:../../../js", "msw": "^2.6.6", diff --git a/integrations/otel-js/otel-v1/pnpm-lock.yaml b/integrations/otel-js/otel-v1/pnpm-lock.yaml index e5448217b..52fd28a0f 100644 --- a/integrations/otel-js/otel-v1/pnpm-lock.yaml +++ b/integrations/otel-js/otel-v1/pnpm-lock.yaml @@ -32,16 +32,16 @@ importers: devDependencies: '@braintrust/otel': specifier: file:.. - version: 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)) + version: file:..(@opentelemetry/api@1.9.0)(braintrust@file:../../../js(zod@4.4.3)) '@opentelemetry/api': specifier: 1.9.0 version: 1.9.0 '@opentelemetry/context-async-hooks': - specifier: 1.9.0 - version: 1.9.0(@opentelemetry/api@1.9.0) + specifier: 1.30.1 + version: 1.30.1(@opentelemetry/api@1.9.0) '@opentelemetry/core': - specifier: 1.9.0 - version: 1.9.0(@opentelemetry/api@1.9.0) + specifier: 1.30.1 + version: 1.30.1(@opentelemetry/api@1.9.0) '@opentelemetry/exporter-trace-otlp-http': specifier: 0.57.2 version: 0.57.2(@opentelemetry/api@1.9.0) @@ -55,11 +55,11 @@ importers: specifier: 1.30.1 version: 1.30.1(@opentelemetry/api@1.9.0) '@opentelemetry/sdk-node': - specifier: 0.35.0 - version: 0.35.0(@opentelemetry/api@1.9.0) + specifier: 0.57.2 + version: 0.57.2(@opentelemetry/api@1.9.0) '@opentelemetry/sdk-trace-base': - specifier: 1.9.0 - version: 1.9.0(@opentelemetry/api@1.9.0) + specifier: 1.30.1 + version: 1.30.1(@opentelemetry/api@1.9.0) '@types/node': specifier: ^22.15.21 version: 22.19.19 @@ -103,11 +103,8 @@ packages: resolution: {directory: .., type: directory} engines: {node: '>=22.18.0'} peerDependencies: - '@opentelemetry/api': '>=1.9.0' - '@opentelemetry/core': '>=1.9.0' - '@opentelemetry/exporter-trace-otlp-http': '>=0.35.0' - '@opentelemetry/sdk-trace-base': '>=1.9.0' - braintrust: '>=1.0.0-0' + '@opentelemetry/api': ^1.9.0 + braintrust: workspace:^ '@esbuild/aix-ppc64@0.28.1': resolution: {integrity: sha512-Svl7tq8k/08+p6CXPpRjQ1fKX+1odH/BQbb48fV6fj3CWHhsoIOoY87w1oHXm0qEpkIK3ZfVgp0hed3XBXzXMQ==} @@ -269,11 +266,6 @@ packages: resolution: {integrity: sha512-SgC/B3/VZ8lTyOZ4Cwk6f1skWhmkNAaVfiX90qd5u1rRXGKk1//8FTUEN9GvOUq7rfWIqAYkA3CoyumYVpOS6g==} engines: {node: '>=12.10.0'} - '@grpc/proto-loader@0.7.15': - resolution: {integrity: sha512-tMXdRCfYVixjuFK+Hk0Q1s38gV9zDiDJfWL3h1rv4Qc39oILCu1TRTDt7+fGUI8K4G1Fj125Hx/ru3azECWTyQ==} - engines: {node: '>=6'} - hasBin: true - '@grpc/proto-loader@0.8.1': resolution: {integrity: sha512-wtF6h+DY6M3YaDBPAmvuuA6jV8Sif9MjtOI5euKFWRgCDl5PeDpPsHR9u2l6St5ceY8AZgoNDww5+HvEsXFsGg==} engines: {node: '>=6'} @@ -357,11 +349,11 @@ packages: resolution: {integrity: sha512-3giAOQvZiH5F9bMlMiv8+GSPMeqg0dbaeo58/0SlA9sxSqZhnUtxzX9/2FzyhS9sWQf5S0GJE0AKBrFqjpeYcg==} engines: {node: '>=8.0.0'} - '@opentelemetry/context-async-hooks@1.9.0': - resolution: {integrity: sha512-Ur+TgRmJgAEvg6VQuhkqzsWsqoOtr+QSZ8r8Yf6WrvlZpAl/sdRU+yUXWjA7r8JFS9VbBq7IEp7oMStFuJT2ow==} + '@opentelemetry/context-async-hooks@1.30.1': + resolution: {integrity: sha512-s5vvxXPVdjqS3kTLKMeBMvop9hbWkwzBpu+mUO2M7sZtlkyDJGwFe33wRKnbaYDo8ExRVBIIdwIGrqpxHuKttA==} engines: {node: '>=14'} peerDependencies: - '@opentelemetry/api': '>=1.0.0 <1.5.0' + '@opentelemetry/api': '>=1.0.0 <1.10.0' '@opentelemetry/core@1.30.1': resolution: {integrity: sha512-OOCM2C/QIURhJMuKaekP3TRBxBKxG/TWWA0TL2J6nXUtDnuCtccy49LUJF8xPFXMX+0LMcxFpCo8M9cGY1W6rQ==} @@ -369,85 +361,89 @@ packages: peerDependencies: '@opentelemetry/api': '>=1.0.0 <1.10.0' - '@opentelemetry/core@1.9.0': - resolution: {integrity: sha512-Koy1ApRUp5DB5KpOqhDk0JjO9x6QeEkmcePl8qQDsXZGF4MuHUBShXibd+J2tRNckTsvgEHi1uEuUckDgN+c/A==} + '@opentelemetry/exporter-logs-otlp-grpc@0.57.2': + resolution: {integrity: sha512-eovEy10n3umjKJl2Ey6TLzikPE+W4cUQ4gCwgGP1RqzTGtgDra0WjIqdy29ohiUKfvmbiL3MndZww58xfIvyFw==} engines: {node: '>=14'} peerDependencies: - '@opentelemetry/api': '>=1.0.0 <1.5.0' + '@opentelemetry/api': ^1.3.0 - '@opentelemetry/exporter-jaeger@1.9.0': - resolution: {integrity: sha512-XLsDLuTqQyw08s0n03zeqmz2ike/iQHiep819SJliJnJJKMbGta+JvaWWgrh/YUEYlbBLc/mQP1cndXtd8lbaA==} + '@opentelemetry/exporter-logs-otlp-http@0.57.2': + resolution: {integrity: sha512-0rygmvLcehBRp56NQVLSleJ5ITTduq/QfU7obOkyWgPpFHulwpw2LYTqNIz5TczKZuy5YY+5D3SDnXZL1tXImg==} engines: {node: '>=14'} - deprecated: Package no longer supported. See https://github.com/open-telemetry/opentelemetry-js/pull/7098 peerDependencies: - '@opentelemetry/api': ^1.0.0 + '@opentelemetry/api': ^1.3.0 - '@opentelemetry/exporter-trace-otlp-grpc@0.35.0': - resolution: {integrity: sha512-T3g2u/GJ+mnRTEmKYivjnI4dEYZIqA+z85WovHzOxU2/Em8GJk1/NJ2U6ORJUYrgOlN12QZ/tCGrWsg3q9nyBA==} + '@opentelemetry/exporter-logs-otlp-proto@0.57.2': + resolution: {integrity: sha512-ta0ithCin0F8lu9eOf4lEz9YAScecezCHkMMyDkvd9S7AnZNX5ikUmC5EQOQADU+oCcgo/qkQIaKcZvQ0TYKDw==} engines: {node: '>=14'} peerDependencies: - '@opentelemetry/api': ^1.0.0 + '@opentelemetry/api': ^1.3.0 - '@opentelemetry/exporter-trace-otlp-http@0.35.0': - resolution: {integrity: sha512-wb5XqkTYq0uES65gIyjF5WZjUZooog/Dd2eP+AKOGJ9n5eewvvLjebC9qmFm0ZjHsTw7hzxnQVX/CvhD3NeVHw==} + '@opentelemetry/exporter-metrics-otlp-grpc@0.57.2': + resolution: {integrity: sha512-r70B8yKR41F0EC443b5CGB4rUaOMm99I5N75QQt6sHKxYDzSEc6gm48Diz1CI1biwa5tDPznpylTrywO/pT7qw==} engines: {node: '>=14'} peerDependencies: - '@opentelemetry/api': ^1.0.0 + '@opentelemetry/api': ^1.3.0 - '@opentelemetry/exporter-trace-otlp-http@0.57.2': - resolution: {integrity: sha512-sB/gkSYFu+0w2dVQ0PWY9fAMl172PKMZ/JrHkkW8dmjCL0CYkmXeE+ssqIL/yBUTPOvpLIpenX5T9RwXRBW/3g==} + '@opentelemetry/exporter-metrics-otlp-http@0.57.2': + resolution: {integrity: sha512-ttb9+4iKw04IMubjm3t0EZsYRNWr3kg44uUuzfo9CaccYlOh8cDooe4QObDUkvx9d5qQUrbEckhrWKfJnKhemA==} engines: {node: '>=14'} peerDependencies: '@opentelemetry/api': ^1.3.0 - '@opentelemetry/exporter-trace-otlp-proto@0.35.0': - resolution: {integrity: sha512-B2Evc3h8MopqwvljFDpTZyWe9PPVbYKxFEOpN5cls84GtYDsveHchCwiSG98liOqTQHORVNsz8lvLRNoK/J+Tw==} + '@opentelemetry/exporter-metrics-otlp-proto@0.57.2': + resolution: {integrity: sha512-HX068Q2eNs38uf7RIkNN9Hl4Ynl+3lP0++KELkXMCpsCbFO03+0XNNZ1SkwxPlP9jrhQahsMPMkzNXpq3fKsnw==} engines: {node: '>=14'} peerDependencies: - '@opentelemetry/api': ^1.0.0 + '@opentelemetry/api': ^1.3.0 - '@opentelemetry/exporter-zipkin@1.9.0': - resolution: {integrity: sha512-+AghaRdhSritrXmB/zoreOheNVuDOWOy/LK/W4odCRtkM/WprpHCOVj5eN8/zqg0LVeE9AbVMODk//eR0lu2fA==} + '@opentelemetry/exporter-prometheus@0.57.2': + resolution: {integrity: sha512-VqIqXnuxWMWE/1NatAGtB1PvsQipwxDcdG4RwA/umdBcW3/iOHp0uejvFHTRN2O78ZPged87ErJajyUBPUhlDQ==} engines: {node: '>=14'} peerDependencies: - '@opentelemetry/api': ^1.0.0 + '@opentelemetry/api': ^1.3.0 - '@opentelemetry/instrumentation@0.35.0': - resolution: {integrity: sha512-pQ5shVG53acTtq72DF7kx+4690ZKh3lATMRqf2JDMRvn0bcX/JaQ+NjPmt7oyT3h9brMA1rSFMVFS6yj8kd2OQ==} + '@opentelemetry/exporter-trace-otlp-grpc@0.57.2': + resolution: {integrity: sha512-gHU1vA3JnHbNxEXg5iysqCWxN9j83d7/epTYBZflqQnTyCC4N7yZXn/dMM+bEmyhQPGjhCkNZLx4vZuChH1PYw==} engines: {node: '>=14'} peerDependencies: '@opentelemetry/api': ^1.3.0 - '@opentelemetry/otlp-exporter-base@0.35.0': - resolution: {integrity: sha512-ybXq1Dvg38ZwiNCtqRCRmJ93rP7jMhL8nHEYVXNKknPVplUoY9fsb8tCPi24iY1suAD98wAMy3hiHk4W8VqfSg==} + '@opentelemetry/exporter-trace-otlp-http@0.57.2': + resolution: {integrity: sha512-sB/gkSYFu+0w2dVQ0PWY9fAMl172PKMZ/JrHkkW8dmjCL0CYkmXeE+ssqIL/yBUTPOvpLIpenX5T9RwXRBW/3g==} engines: {node: '>=14'} peerDependencies: - '@opentelemetry/api': ^1.0.0 + '@opentelemetry/api': ^1.3.0 - '@opentelemetry/otlp-exporter-base@0.57.2': - resolution: {integrity: sha512-XdxEzL23Urhidyebg5E6jZoaiW5ygP/mRjxLHixogbqwDy2Faduzb5N0o/Oi+XTIJu+iyxXdVORjXax+Qgfxag==} + '@opentelemetry/exporter-trace-otlp-proto@0.57.2': + resolution: {integrity: sha512-awDdNRMIwDvUtoRYxRhja5QYH6+McBLtoz1q9BeEsskhZcrGmH/V1fWpGx8n+Rc+542e8pJA6y+aullbIzQmlw==} engines: {node: '>=14'} peerDependencies: '@opentelemetry/api': ^1.3.0 - '@opentelemetry/otlp-grpc-exporter-base@0.35.0': - resolution: {integrity: sha512-Tpk3+LC4eWbFO/QOwPcJAImQaG0hAVIhkLYo76RJCgxVSxbIcj6PzUwBEsCiw/M4dNFtx7886cy8+45FKZoJ1g==} + '@opentelemetry/exporter-zipkin@1.30.1': + resolution: {integrity: sha512-6S2QIMJahIquvFaaxmcwpvQQRD/YFaMTNoIxrfPIPOeITN+a8lfEcPDxNxn8JDAaxkg+4EnXhz8upVDYenoQjA==} engines: {node: '>=14'} peerDependencies: '@opentelemetry/api': ^1.0.0 - '@opentelemetry/otlp-proto-exporter-base@0.35.0': - resolution: {integrity: sha512-yJhHeqh0tU/3vzPbFD0mVrlQe+ccrE9LShy5YlXk9RB9x/RXkSU5uNdKmOgmQU+6CSMZYporJYviUNE5knnYHQ==} + '@opentelemetry/instrumentation@0.57.2': + resolution: {integrity: sha512-BdBGhQBh8IjZ2oIIX6F2/Q3LKm/FDDKi6ccYKcBTeilh6SNdNKveDOLk73BkSJjQLJk6qe4Yh+hHw1UPhCDdrg==} engines: {node: '>=14'} - deprecated: Package no longer supported. Contact Support at https://www.npmjs.com/support for more info. peerDependencies: - '@opentelemetry/api': ^1.0.0 + '@opentelemetry/api': ^1.3.0 + + '@opentelemetry/otlp-exporter-base@0.57.2': + resolution: {integrity: sha512-XdxEzL23Urhidyebg5E6jZoaiW5ygP/mRjxLHixogbqwDy2Faduzb5N0o/Oi+XTIJu+iyxXdVORjXax+Qgfxag==} + engines: {node: '>=14'} + peerDependencies: + '@opentelemetry/api': ^1.3.0 - '@opentelemetry/otlp-transformer@0.35.0': - resolution: {integrity: sha512-XjxX6RLHYKadQNEVs7TT7YRwEhXzP8itLiu6en2P7HukSg0gTwOMhNzriBZBRC4q+HVsdnncWua1wCD1TBAfmg==} + '@opentelemetry/otlp-grpc-exporter-base@0.57.2': + resolution: {integrity: sha512-USn173KTWy0saqqRB5yU9xUZ2xdgb1Rdu5IosJnm9aV4hMTuFFRTUsQxbgc24QxpCHeoKzzCSnS/JzdV0oM2iQ==} engines: {node: '>=14'} peerDependencies: - '@opentelemetry/api': '>=1.3.0 <1.5.0' + '@opentelemetry/api': ^1.3.0 '@opentelemetry/otlp-transformer@0.57.2': resolution: {integrity: sha512-48IIRj49gbQVK52jYsw70+Jv+JbahT8BqT2Th7C4H7RCM9d0gZ5sgNPoMpWldmfjvIsSgiGJtjfk9MeZvjhoig==} @@ -455,18 +451,18 @@ packages: peerDependencies: '@opentelemetry/api': ^1.3.0 - '@opentelemetry/propagator-b3@1.9.0': - resolution: {integrity: sha512-5M/NvJj7ZHZXEU8lkAFhrSrWaHmCCkFLstNbL8p16qpTn1AOZowuSjMOYRoJJBmL5PUobkZ3W8Gjov1UgldPBg==} + '@opentelemetry/propagator-b3@1.30.1': + resolution: {integrity: sha512-oATwWWDIJzybAZ4pO76ATN5N6FFbOA1otibAVlS8v90B4S1wClnhRUk7K+2CHAwN1JKYuj4jh/lpCEG5BAqFuQ==} engines: {node: '>=14'} peerDependencies: - '@opentelemetry/api': '>=1.0.0 <1.5.0' + '@opentelemetry/api': '>=1.0.0 <1.10.0' - '@opentelemetry/propagator-jaeger@1.9.0': - resolution: {integrity: sha512-oo8RyuyzEbbXdIfeEG9iA5vmTH4Kld+dZMIZICd5G5SmeNcNes3sLrparpunIGms41wIP2mWiIlcOelDCmGceg==} + '@opentelemetry/propagator-jaeger@1.30.1': + resolution: {integrity: sha512-Pj/BfnYEKIOImirH76M4hDaBSx6HyZ2CXUqk+Kj02m6BB80c/yo4BdWkn/1gDFfU+YPY+bPR2U0DKBfdxCKwmg==} 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' + '@opentelemetry/api': '>=1.0.0 <1.10.0' '@opentelemetry/resources@1.30.1': resolution: {integrity: sha512-5UxZqiAgLYGFjS4s9qm5mBVo433u+dSPUFWVWXmLAD4wB65oMCoXaJP1KJa9DIYYMeHu3z4BZcStG3LC593cWA==} @@ -474,12 +470,6 @@ packages: peerDependencies: '@opentelemetry/api': '>=1.0.0 <1.10.0' - '@opentelemetry/resources@1.9.0': - resolution: {integrity: sha512-zCyien0p3XWarU6zv72c/JZ6QlG5QW/hc61Nh5TSR1K9ndnljzAGrH55x4nfyQdubfoh9QxLNh9FXH0fWK6vcg==} - engines: {node: '>=14'} - peerDependencies: - '@opentelemetry/api': '>=1.0.0 <1.5.0' - '@opentelemetry/sdk-logs@0.57.2': resolution: {integrity: sha512-TXFHJ5c+BKggWbdEQ/inpgIzEmS2BGQowLE9UhsMd7YYlUfBQJ4uax0VF/B5NYigdM/75OoJGhAV3upEhK+3gg==} engines: {node: '>=14'} @@ -492,17 +482,11 @@ packages: peerDependencies: '@opentelemetry/api': '>=1.3.0 <1.10.0' - '@opentelemetry/sdk-metrics@1.9.0': - resolution: {integrity: sha512-fSlJWhp86kCan1zuxdH6LTyUBYlohQwDKnxep5qHMnRTNErkYmdjgsmjZvSMdAfUFtQqfZmTXe2Lap7a5AIf9Q==} - engines: {node: '>=14'} - peerDependencies: - '@opentelemetry/api': '>=1.3.0 <1.5.0' - - '@opentelemetry/sdk-node@0.35.0': - resolution: {integrity: sha512-fascA8taT4CRCGLAjxiXlOcJmbSZxe5YN3PogmAZnm1sAxbR01ejWImyOx2YSmVjQXfyBkMKtKgsIqOENnh+KA==} + '@opentelemetry/sdk-node@0.57.2': + resolution: {integrity: sha512-8BaeqZyN5sTuPBtAoY+UtKwXBdqyuRKmekN5bFzAO40CgbGzAxfTpiL3PBerT7rhZ7p2nBdq7FaMv/tBQgHE4A==} engines: {node: '>=14'} peerDependencies: - '@opentelemetry/api': '>=1.3.0 <1.5.0' + '@opentelemetry/api': '>=1.3.0 <1.10.0' '@opentelemetry/sdk-trace-base@1.30.1': resolution: {integrity: sha512-jVPgBbH1gCy2Lb7X0AVQ8XAfgg0pJ4nvl8/IiQA6nxOsPvS+0zMJaFSs2ltXe0J6C8dqjcnpyqINDJmU30+uOg==} @@ -511,28 +495,17 @@ packages: peerDependencies: '@opentelemetry/api': '>=1.0.0 <1.10.0' - '@opentelemetry/sdk-trace-base@1.9.0': - resolution: {integrity: sha512-glNgtJjxAIrDku8DG5Xu3nBK25rT+hkyg7yuXh8RUurp/4BcsXjMyVqpyJvb2kg+lxAX73VJBhncRKGHn9t8QQ==} + '@opentelemetry/sdk-trace-node@1.30.1': + resolution: {integrity: sha512-cBjYOINt1JxXdpw1e5MlHmFRc5fgj4GW/86vsKFxJCJ8AL4PdVtYH41gWwl4qd4uQjqEL1oJVrXkSy5cnduAnQ==} 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' + '@opentelemetry/api': '>=1.0.0 <1.10.0' '@opentelemetry/semantic-conventions@1.28.0': resolution: {integrity: sha512-lp4qAiMTD4sNWW4DbKLBkfiMZ4jbAboJIGOQr5DvciMRI494OapieI9qiODpOt0XBr1LjIDy1xAGAnVs5supTA==} engines: {node: '>=14'} - '@opentelemetry/semantic-conventions@1.9.0': - resolution: {integrity: sha512-po7penSfQ/Z8352lRVDpaBrd9znwA5mHGqXR7nDEiVnxkDFkBIhVf/tKeAJDIq/erFpcRowKFeCsr5eqqcSyFQ==} - engines: {node: '>=14'} - '@oxc-project/runtime@0.153.0': resolution: {integrity: sha512-rXJvtlsVG4xJNq64hiaUTIatE7jbBgpa6l4QgmEa2jLgT3ULH9scUBkpsC9N1DfmqixLEyT6DuK6OSXozziIlw==} engines: {node: ^20.19.0 || >=22.12.0} @@ -876,6 +849,9 @@ packages: '@types/set-cookie-parser@2.4.10': resolution: {integrity: sha512-GGmQVGpQWUe5qglJozEjZV/5dyxbOOZ0LHe/lqyWssB88Y4svNfst0uqBVscdDeIKl5Jy5+aPSvy7mI9tYRguw==} + '@types/shimmer@1.2.0': + resolution: {integrity: sha512-UE7oxhQLLd9gub6JKIAhDq06T0F6FnztwMNRvYgjeQSBeMc1ZG/tA47EwfduvkuQS8apbkM/lpLpWsaCeYsXVg==} + '@types/statuses@2.0.6': resolution: {integrity: sha512-xMAgYwceFhRA2zY+XbEA7mxYbA093wdiW8Vu6gZPGWy9cmOyU9XesH1tNcEWsKFd5Vzrqx5T3D38PWx1FIIXkA==} @@ -1219,9 +1195,6 @@ packages: ajv@8.20.0: resolution: {integrity: sha512-Thbli+OlOj+iMPYFBVBfJ3OmCAnaSyNn4M1vz9T6Gka5Jt9ba/HIR56joy65tY6kx/FCF5VXNB819Y7/GUrBGA==} - ansi-color@0.2.2: - resolution: {integrity: sha512-qPx7iZZDHITYrrfzaUFXQpIcF2xYifcQHQflP1pFz8yY3lfU6GgCHb0+hJD7nimYKO7f2iaYYwBpZ+GaNcAhcA==} - ansi-regex@5.0.1: resolution: {integrity: sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==} engines: {node: '>=8'} @@ -1256,14 +1229,13 @@ packages: '@types/react': optional: true - bufrw@1.4.0: - resolution: {integrity: sha512-sWm8iPbqvL9+5SiYxXH73UOkyEbGQg7kyHQmReF89WJHQJw2eV4P/yZ0E+b71cczJ4pPobVhXxgQcmfSTgGHxQ==} - engines: {node: '>= 0.10.x'} - chai@6.2.2: resolution: {integrity: sha512-NUPRluOfOiTKBKvWPtSD4PhFvWCqOi0BGStNWs57X9js7XGTprSmFoz5F0tWhR4WPjNeR9jXqdC7/UpSJTnlRg==} engines: {node: '>=18'} + 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==} @@ -1316,9 +1288,6 @@ packages: emoji-regex@8.0.0: resolution: {integrity: sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==} - error@7.0.2: - resolution: {integrity: sha512-UtVv4l5MhijsYUxPJo4390gzfZvAnTHreNnDjnTZaKIiZ/SemXxAhBkYSKtWa5RtBXbLP8tMgn/n0RUa/H7jXw==} - es-errors@1.3.0: resolution: {integrity: sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==} engines: {node: '>= 0.4'} @@ -1407,10 +1376,8 @@ packages: headers-polyfill@5.0.1: resolution: {integrity: sha512-1TJ6Fih/b8h5TIcv+1+Hw0PDQWJTKDKzFZzcKOiW1wJza3XoAQlkCuXLbymPYB8+ZQyw8mHvdw560e8zVFIWyA==} - hexer@1.5.0: - resolution: {integrity: sha512-dyrPC8KzBzUJ19QTIo1gXNqIISRXQ0NwteW6OeQHRN4ZuZeHkdODfj0zHBdOlHbRY8GqbqK57C9oWSvQZizFsg==} - engines: {node: '>= 0.10.x'} - hasBin: true + import-in-the-middle@1.15.0: + resolution: {integrity: sha512-bpQy+CrsRmYmoPMAE/0G33iwRqwW4ouqdRg8jgbH3aKuCtOc8lxgmYXg2dMM92CRiGP660EtBcymH/eVUpCSaA==} is-core-module@2.16.2: resolution: {integrity: sha512-evOr8xfXKxE6qSR0hSXL2r3sd7ALj8+7jQEUvPYcm5sgZFdJ+AYzT6yNmJenvIYQBgIGwfwz08sL8zoL7yq2BA==} @@ -1423,10 +1390,6 @@ packages: is-node-process@1.2.0: resolution: {integrity: sha512-Vg4o6/fqPxIjtxgUH5QLJhwZ7gW5diGCVlXpuUfELC62CuxM1iHcRe51f2W1FDy04Ai4KJkagKjx3XaqyfRKXw==} - jaeger-client@3.19.0: - resolution: {integrity: sha512-M0c7cKHmdyEUtjemnJyx/y9uX16XHocL46yQvyqDlPdvAcwPDbHrIbKjQdBqtiE4apQ/9dmr+ZLJYYPGnurgpw==} - engines: {node: '>=10'} - js-tokens@4.0.0: resolution: {integrity: sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==} @@ -1510,13 +1473,6 @@ packages: lodash.camelcase@4.3.0: resolution: {integrity: sha512-TwuEnCnxbc3rAvhf/LbG7tJUDzhqXyFnv3dtzLOPgCG/hODL7WFnsbwktkD7yUV0RrreP/l1PALq/YSg6VvjlA==} - lodash.merge@4.6.2: - resolution: {integrity: sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==} - - long@2.4.0: - resolution: {integrity: sha512-ijUtjmO/n2A5PaosNG9ZGDsQ3vxJg7ZW8vsY8Kp0f2yIZWhSJvjmegV7t+9RPQKxKrvj8yKGehhS+po14hPLGQ==} - engines: {node: '>=0.6'} - long@5.3.2: resolution: {integrity: sha512-mNAgZ1GmyNhD7AuqnTG3/VQ26o760+ZYBPKjPvugO8+nLbYfX6TVpJPseBvopbdY+qpZ/lKUnmEc1LeZYS3QAA==} @@ -1531,9 +1487,6 @@ packages: resolution: {integrity: sha512-Sz8FzjzI0kN13GK/6MVEsVzMZEPvOhnmmI1lU5+/1cGOiK3QUahntrNNtdVeihrO7t9JpoH75iMNXg6R6uWflQ==} engines: {node: '>=18.0.0'} - minimist@1.2.8: - resolution: {integrity: sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA==} - module-details-from-path@1.0.4: resolution: {integrity: sha512-EGWKgxALGMgzvxYF1UyGTy0HXX/2vHLkw6+NvDKW2jypWbHpjQuj4UMcqQWXHERJhVGKikolT06G3bcKe4fi7w==} @@ -1567,17 +1520,10 @@ packages: engines: {node: ^10 || ^12 || ^13.7 || ^14 || >=15.0.1} hasBin: true - node-int64@0.4.0: - resolution: {integrity: sha512-O5lz91xSOeoXP6DulyHfllpq+Eg00MWitZIbtPfoSEvqIHdl5gfcY6hYzDWnj0qD5tz52PI08u9qUvSVeUBeHw==} - obug@2.2.1: resolution: {integrity: sha512-XrsrhT5sybtKI6wakr2SPOlGZWWYbUXZ7a0jT8/QOeAPau+1X/bSegNe5YR75oJmEZQbKningirmGOEJCIk61Q==} engines: {node: '>=12.20.0'} - opentracing@0.14.7: - resolution: {integrity: sha512-vz9iS7MJ5+Bp1URw8Khvdyw1H/hGvzHWlKQ7eRrQojSCDL1/SrWfrY9QebLw97n2deyRtzHRC3MkQfVNUCo91Q==} - engines: {node: '>=0.10'} - outvariant@1.4.3: resolution: {integrity: sha512-+Sl2UErvtsoajRDKCE5/dBz4DIvHXQQnAxtQTF04OJxY0+DyZXSo5P5Bb7XYWOh81syohlYL24hbDwxedPUJCA==} @@ -1639,10 +1585,6 @@ packages: 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'} - protobufjs@7.6.5: resolution: {integrity: sha512-/FPD0nUc9jH6rfFjji9IBqOz4pcSE3CsT1m7Ep6Mdb0LxSUMj8hgl6GomOvZzpNpAqqGaXA0P3VSrZLFzIhQrw==} engines: {node: '>=12.0.0'} @@ -1658,9 +1600,9 @@ packages: resolution: {integrity: sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw==} engines: {node: '>=0.10.0'} - require-in-the-middle@5.2.0: - resolution: {integrity: sha512-efCx3b+0Z69/LGJmm9Yvi4cqEdxnoGnxYxGxBghkkTTFeXRtTCmmhO0AnAfHz59k957uTSuy8WaHqOs8wbYUWg==} - engines: {node: '>=6'} + require-in-the-middle@7.5.2: + resolution: {integrity: sha512-gAZ+kLqBdHarXB64XpAe2VCjB7rIRv+mU8tfRWziHRJ5umKsIHN2tLLv6EtMw7WCdP19S0ERVMldNvxYCHnhSQ==} + engines: {node: '>=8.6.0'} resolve@1.22.12: resolution: {integrity: sha512-TyeJ1zif53BPfHootBGwPRYT1RUt6oGWsaQr8UyZW/eAm9bKoijtvruSDEmZHm92CwS9nj7/fWttqPCgzep8CA==} @@ -1710,9 +1652,6 @@ packages: strict-event-emitter@0.5.1: resolution: {integrity: sha512-vMgjE/GGEPEFnhFub6pa4FmJBRBVOLpIII2hvCZ8Kzb7K0hlHo7mQv6xYrBvCL2LtAIBwFUK8wvuJgTVSQ5MFQ==} - string-template@0.2.1: - resolution: {integrity: sha512-Yptehjogou2xm4UJbxJ4CxgZx12HBfeystp0y3x7s4Dj32ltVVG1Gg8YhKjHZkHicuKpZX/ffilA8505VbUbpw==} - string-width@4.2.3: resolution: {integrity: sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==} engines: {node: '>=8'} @@ -1729,11 +1668,6 @@ packages: resolution: {integrity: sha512-yEFYrVhod+hdNyx7g5Bnkkb0G6si8HJurOoOEgC8B/O0uXLHlaey/65KRv6cuWBNhBgHKAROVpc7QyYqE5gFng==} engines: {node: '>=20'} - thriftrw@3.11.4: - resolution: {integrity: sha512-UcuBd3eanB3T10nXWRRMwfwoaC6VMk7qe3/5YIWP2Jtw+EbHqJ0p1/K3x8ixiR5dozKSSfcg1W+0e33G1Di3XA==} - engines: {node: '>= 0.10.x'} - hasBin: true - tinybench@6.2.2: resolution: {integrity: sha512-nIZ2N2dFpjBsg2hgitKu7mA+FvsIJuGJOwysRngmVRRmWgR/UyPjwJj8c/fT/dhm8EkHnF5EOogRf+0Z2qrimA==} engines: {node: '>=20.0.0'} @@ -1873,13 +1807,6 @@ packages: utf-8-validate: optional: true - xorshift@1.2.0: - resolution: {integrity: sha512-iYgNnGyeeJ4t6U11NpA/QiKy+PXn5Aa3Azg5qkwIFz1tBLllQrjjsk9yzD7IAK0naNU4JxdeDgqW9ov4u/hc4g==} - - xtend@4.0.2: - resolution: {integrity: sha512-LKYU1iAXJXUgAXn9URjiu+MWhyUXHsvfp7mcuYm9dSUKK0/CjtrUwFAxD82/mCWbtLsGjFIad0wIsod4zrTAEQ==} - engines: {node: '>=0.4'} - y18n@5.0.8: resolution: {integrity: sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==} engines: {node: '>=10'} @@ -1923,12 +1850,9 @@ snapshots: '@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))': + '@braintrust/otel@file:..(@opentelemetry/api@1.9.0)(braintrust@file:../../../js(zod@4.4.3))': dependencies: '@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) '@esbuild/aix-ppc64@0.28.1': @@ -2014,13 +1938,6 @@ snapshots: '@grpc/proto-loader': 0.8.1 '@js-sdsl/ordered-map': 4.4.2 - '@grpc/proto-loader@0.7.15': - dependencies: - lodash.camelcase: 4.3.0 - long: 5.3.2 - protobufjs: 7.6.5 - yargs: 17.7.2 - '@grpc/proto-loader@0.8.1': dependencies: lodash.camelcase: 4.3.0 @@ -2102,7 +2019,7 @@ snapshots: '@opentelemetry/api@1.9.0': {} - '@opentelemetry/context-async-hooks@1.9.0(@opentelemetry/api@1.9.0)': + '@opentelemetry/context-async-hooks@1.30.1(@opentelemetry/api@1.9.0)': dependencies: '@opentelemetry/api': 1.9.0 @@ -2111,107 +2028,136 @@ snapshots: '@opentelemetry/api': 1.9.0 '@opentelemetry/semantic-conventions': 1.28.0 - '@opentelemetry/core@1.9.0(@opentelemetry/api@1.9.0)': + '@opentelemetry/exporter-logs-otlp-grpc@0.57.2(@opentelemetry/api@1.9.0)': dependencies: + '@grpc/grpc-js': 1.14.6 '@opentelemetry/api': 1.9.0 - '@opentelemetry/semantic-conventions': 1.9.0 + '@opentelemetry/core': 1.30.1(@opentelemetry/api@1.9.0) + '@opentelemetry/otlp-exporter-base': 0.57.2(@opentelemetry/api@1.9.0) + '@opentelemetry/otlp-grpc-exporter-base': 0.57.2(@opentelemetry/api@1.9.0) + '@opentelemetry/otlp-transformer': 0.57.2(@opentelemetry/api@1.9.0) + '@opentelemetry/sdk-logs': 0.57.2(@opentelemetry/api@1.9.0) - '@opentelemetry/exporter-jaeger@1.9.0(@opentelemetry/api@1.9.0)': + '@opentelemetry/exporter-logs-otlp-http@0.57.2(@opentelemetry/api@1.9.0)': dependencies: '@opentelemetry/api': 1.9.0 - '@opentelemetry/core': 1.9.0(@opentelemetry/api@1.9.0) - '@opentelemetry/sdk-trace-base': 1.9.0(@opentelemetry/api@1.9.0) - '@opentelemetry/semantic-conventions': 1.9.0 - jaeger-client: 3.19.0 + '@opentelemetry/api-logs': 0.57.2 + '@opentelemetry/core': 1.30.1(@opentelemetry/api@1.9.0) + '@opentelemetry/otlp-exporter-base': 0.57.2(@opentelemetry/api@1.9.0) + '@opentelemetry/otlp-transformer': 0.57.2(@opentelemetry/api@1.9.0) + '@opentelemetry/sdk-logs': 0.57.2(@opentelemetry/api@1.9.0) - '@opentelemetry/exporter-trace-otlp-grpc@0.35.0(@opentelemetry/api@1.9.0)': + '@opentelemetry/exporter-logs-otlp-proto@0.57.2(@opentelemetry/api@1.9.0)': dependencies: - '@grpc/grpc-js': 1.14.6 '@opentelemetry/api': 1.9.0 - '@opentelemetry/core': 1.9.0(@opentelemetry/api@1.9.0) - '@opentelemetry/otlp-grpc-exporter-base': 0.35.0(@opentelemetry/api@1.9.0) - '@opentelemetry/otlp-transformer': 0.35.0(@opentelemetry/api@1.9.0) - '@opentelemetry/resources': 1.9.0(@opentelemetry/api@1.9.0) - '@opentelemetry/sdk-trace-base': 1.9.0(@opentelemetry/api@1.9.0) + '@opentelemetry/api-logs': 0.57.2 + '@opentelemetry/core': 1.30.1(@opentelemetry/api@1.9.0) + '@opentelemetry/otlp-exporter-base': 0.57.2(@opentelemetry/api@1.9.0) + '@opentelemetry/otlp-transformer': 0.57.2(@opentelemetry/api@1.9.0) + '@opentelemetry/resources': 1.30.1(@opentelemetry/api@1.9.0) + '@opentelemetry/sdk-logs': 0.57.2(@opentelemetry/api@1.9.0) + '@opentelemetry/sdk-trace-base': 1.30.1(@opentelemetry/api@1.9.0) - '@opentelemetry/exporter-trace-otlp-http@0.35.0(@opentelemetry/api@1.9.0)': + '@opentelemetry/exporter-metrics-otlp-grpc@0.57.2(@opentelemetry/api@1.9.0)': dependencies: + '@grpc/grpc-js': 1.14.6 '@opentelemetry/api': 1.9.0 - '@opentelemetry/core': 1.9.0(@opentelemetry/api@1.9.0) - '@opentelemetry/otlp-exporter-base': 0.35.0(@opentelemetry/api@1.9.0) - '@opentelemetry/otlp-transformer': 0.35.0(@opentelemetry/api@1.9.0) - '@opentelemetry/resources': 1.9.0(@opentelemetry/api@1.9.0) - '@opentelemetry/sdk-trace-base': 1.9.0(@opentelemetry/api@1.9.0) + '@opentelemetry/core': 1.30.1(@opentelemetry/api@1.9.0) + '@opentelemetry/exporter-metrics-otlp-http': 0.57.2(@opentelemetry/api@1.9.0) + '@opentelemetry/otlp-exporter-base': 0.57.2(@opentelemetry/api@1.9.0) + '@opentelemetry/otlp-grpc-exporter-base': 0.57.2(@opentelemetry/api@1.9.0) + '@opentelemetry/otlp-transformer': 0.57.2(@opentelemetry/api@1.9.0) + '@opentelemetry/resources': 1.30.1(@opentelemetry/api@1.9.0) + '@opentelemetry/sdk-metrics': 1.30.1(@opentelemetry/api@1.9.0) - '@opentelemetry/exporter-trace-otlp-http@0.57.2(@opentelemetry/api@1.9.0)': + '@opentelemetry/exporter-metrics-otlp-http@0.57.2(@opentelemetry/api@1.9.0)': dependencies: '@opentelemetry/api': 1.9.0 '@opentelemetry/core': 1.30.1(@opentelemetry/api@1.9.0) '@opentelemetry/otlp-exporter-base': 0.57.2(@opentelemetry/api@1.9.0) '@opentelemetry/otlp-transformer': 0.57.2(@opentelemetry/api@1.9.0) '@opentelemetry/resources': 1.30.1(@opentelemetry/api@1.9.0) - '@opentelemetry/sdk-trace-base': 1.30.1(@opentelemetry/api@1.9.0) + '@opentelemetry/sdk-metrics': 1.30.1(@opentelemetry/api@1.9.0) - '@opentelemetry/exporter-trace-otlp-proto@0.35.0(@opentelemetry/api@1.9.0)': + '@opentelemetry/exporter-metrics-otlp-proto@0.57.2(@opentelemetry/api@1.9.0)': dependencies: '@opentelemetry/api': 1.9.0 - '@opentelemetry/core': 1.9.0(@opentelemetry/api@1.9.0) - '@opentelemetry/otlp-exporter-base': 0.35.0(@opentelemetry/api@1.9.0) - '@opentelemetry/otlp-proto-exporter-base': 0.35.0(@opentelemetry/api@1.9.0) - '@opentelemetry/otlp-transformer': 0.35.0(@opentelemetry/api@1.9.0) - '@opentelemetry/resources': 1.9.0(@opentelemetry/api@1.9.0) - '@opentelemetry/sdk-trace-base': 1.9.0(@opentelemetry/api@1.9.0) + '@opentelemetry/core': 1.30.1(@opentelemetry/api@1.9.0) + '@opentelemetry/exporter-metrics-otlp-http': 0.57.2(@opentelemetry/api@1.9.0) + '@opentelemetry/otlp-exporter-base': 0.57.2(@opentelemetry/api@1.9.0) + '@opentelemetry/otlp-transformer': 0.57.2(@opentelemetry/api@1.9.0) + '@opentelemetry/resources': 1.30.1(@opentelemetry/api@1.9.0) + '@opentelemetry/sdk-metrics': 1.30.1(@opentelemetry/api@1.9.0) - '@opentelemetry/exporter-zipkin@1.9.0(@opentelemetry/api@1.9.0)': + '@opentelemetry/exporter-prometheus@0.57.2(@opentelemetry/api@1.9.0)': dependencies: '@opentelemetry/api': 1.9.0 - '@opentelemetry/core': 1.9.0(@opentelemetry/api@1.9.0) - '@opentelemetry/resources': 1.9.0(@opentelemetry/api@1.9.0) - '@opentelemetry/sdk-trace-base': 1.9.0(@opentelemetry/api@1.9.0) - '@opentelemetry/semantic-conventions': 1.9.0 + '@opentelemetry/core': 1.30.1(@opentelemetry/api@1.9.0) + '@opentelemetry/resources': 1.30.1(@opentelemetry/api@1.9.0) + '@opentelemetry/sdk-metrics': 1.30.1(@opentelemetry/api@1.9.0) - '@opentelemetry/instrumentation@0.35.0(@opentelemetry/api@1.9.0)': + '@opentelemetry/exporter-trace-otlp-grpc@0.57.2(@opentelemetry/api@1.9.0)': dependencies: + '@grpc/grpc-js': 1.14.6 '@opentelemetry/api': 1.9.0 - require-in-the-middle: 5.2.0 - semver: 7.8.1 - shimmer: 1.2.1 - transitivePeerDependencies: - - supports-color + '@opentelemetry/core': 1.30.1(@opentelemetry/api@1.9.0) + '@opentelemetry/otlp-exporter-base': 0.57.2(@opentelemetry/api@1.9.0) + '@opentelemetry/otlp-grpc-exporter-base': 0.57.2(@opentelemetry/api@1.9.0) + '@opentelemetry/otlp-transformer': 0.57.2(@opentelemetry/api@1.9.0) + '@opentelemetry/resources': 1.30.1(@opentelemetry/api@1.9.0) + '@opentelemetry/sdk-trace-base': 1.30.1(@opentelemetry/api@1.9.0) - '@opentelemetry/otlp-exporter-base@0.35.0(@opentelemetry/api@1.9.0)': + '@opentelemetry/exporter-trace-otlp-http@0.57.2(@opentelemetry/api@1.9.0)': dependencies: '@opentelemetry/api': 1.9.0 - '@opentelemetry/core': 1.9.0(@opentelemetry/api@1.9.0) + '@opentelemetry/core': 1.30.1(@opentelemetry/api@1.9.0) + '@opentelemetry/otlp-exporter-base': 0.57.2(@opentelemetry/api@1.9.0) + '@opentelemetry/otlp-transformer': 0.57.2(@opentelemetry/api@1.9.0) + '@opentelemetry/resources': 1.30.1(@opentelemetry/api@1.9.0) + '@opentelemetry/sdk-trace-base': 1.30.1(@opentelemetry/api@1.9.0) - '@opentelemetry/otlp-exporter-base@0.57.2(@opentelemetry/api@1.9.0)': + '@opentelemetry/exporter-trace-otlp-proto@0.57.2(@opentelemetry/api@1.9.0)': dependencies: '@opentelemetry/api': 1.9.0 '@opentelemetry/core': 1.30.1(@opentelemetry/api@1.9.0) + '@opentelemetry/otlp-exporter-base': 0.57.2(@opentelemetry/api@1.9.0) '@opentelemetry/otlp-transformer': 0.57.2(@opentelemetry/api@1.9.0) + '@opentelemetry/resources': 1.30.1(@opentelemetry/api@1.9.0) + '@opentelemetry/sdk-trace-base': 1.30.1(@opentelemetry/api@1.9.0) - '@opentelemetry/otlp-grpc-exporter-base@0.35.0(@opentelemetry/api@1.9.0)': + '@opentelemetry/exporter-zipkin@1.30.1(@opentelemetry/api@1.9.0)': dependencies: - '@grpc/grpc-js': 1.14.6 - '@grpc/proto-loader': 0.7.15 '@opentelemetry/api': 1.9.0 - '@opentelemetry/core': 1.9.0(@opentelemetry/api@1.9.0) - '@opentelemetry/otlp-exporter-base': 0.35.0(@opentelemetry/api@1.9.0) + '@opentelemetry/core': 1.30.1(@opentelemetry/api@1.9.0) + '@opentelemetry/resources': 1.30.1(@opentelemetry/api@1.9.0) + '@opentelemetry/sdk-trace-base': 1.30.1(@opentelemetry/api@1.9.0) + '@opentelemetry/semantic-conventions': 1.28.0 - '@opentelemetry/otlp-proto-exporter-base@0.35.0(@opentelemetry/api@1.9.0)': + '@opentelemetry/instrumentation@0.57.2(@opentelemetry/api@1.9.0)': dependencies: '@opentelemetry/api': 1.9.0 - '@opentelemetry/core': 1.9.0(@opentelemetry/api@1.9.0) - '@opentelemetry/otlp-exporter-base': 0.35.0(@opentelemetry/api@1.9.0) - protobufjs: 7.6.5 + '@opentelemetry/api-logs': 0.57.2 + '@types/shimmer': 1.2.0 + import-in-the-middle: 1.15.0 + require-in-the-middle: 7.5.2 + semver: 7.8.1 + shimmer: 1.2.1 + transitivePeerDependencies: + - supports-color - '@opentelemetry/otlp-transformer@0.35.0(@opentelemetry/api@1.9.0)': + '@opentelemetry/otlp-exporter-base@0.57.2(@opentelemetry/api@1.9.0)': dependencies: '@opentelemetry/api': 1.9.0 - '@opentelemetry/core': 1.9.0(@opentelemetry/api@1.9.0) - '@opentelemetry/resources': 1.9.0(@opentelemetry/api@1.9.0) - '@opentelemetry/sdk-metrics': 1.9.0(@opentelemetry/api@1.9.0) - '@opentelemetry/sdk-trace-base': 1.9.0(@opentelemetry/api@1.9.0) + '@opentelemetry/core': 1.30.1(@opentelemetry/api@1.9.0) + '@opentelemetry/otlp-transformer': 0.57.2(@opentelemetry/api@1.9.0) + + '@opentelemetry/otlp-grpc-exporter-base@0.57.2(@opentelemetry/api@1.9.0)': + dependencies: + '@grpc/grpc-js': 1.14.6 + '@opentelemetry/api': 1.9.0 + '@opentelemetry/core': 1.30.1(@opentelemetry/api@1.9.0) + '@opentelemetry/otlp-exporter-base': 0.57.2(@opentelemetry/api@1.9.0) + '@opentelemetry/otlp-transformer': 0.57.2(@opentelemetry/api@1.9.0) '@opentelemetry/otlp-transformer@0.57.2(@opentelemetry/api@1.9.0)': dependencies: @@ -2224,15 +2170,15 @@ snapshots: '@opentelemetry/sdk-trace-base': 1.30.1(@opentelemetry/api@1.9.0) protobufjs: 7.6.5 - '@opentelemetry/propagator-b3@1.9.0(@opentelemetry/api@1.9.0)': + '@opentelemetry/propagator-b3@1.30.1(@opentelemetry/api@1.9.0)': dependencies: '@opentelemetry/api': 1.9.0 - '@opentelemetry/core': 1.9.0(@opentelemetry/api@1.9.0) + '@opentelemetry/core': 1.30.1(@opentelemetry/api@1.9.0) - '@opentelemetry/propagator-jaeger@1.9.0(@opentelemetry/api@1.9.0)': + '@opentelemetry/propagator-jaeger@1.30.1(@opentelemetry/api@1.9.0)': dependencies: '@opentelemetry/api': 1.9.0 - '@opentelemetry/core': 1.9.0(@opentelemetry/api@1.9.0) + '@opentelemetry/core': 1.30.1(@opentelemetry/api@1.9.0) '@opentelemetry/resources@1.30.1(@opentelemetry/api@1.9.0)': dependencies: @@ -2240,12 +2186,6 @@ snapshots: '@opentelemetry/core': 1.30.1(@opentelemetry/api@1.9.0) '@opentelemetry/semantic-conventions': 1.28.0 - '@opentelemetry/resources@1.9.0(@opentelemetry/api@1.9.0)': - dependencies: - '@opentelemetry/api': 1.9.0 - '@opentelemetry/core': 1.9.0(@opentelemetry/api@1.9.0) - '@opentelemetry/semantic-conventions': 1.9.0 - '@opentelemetry/sdk-logs@0.57.2(@opentelemetry/api@1.9.0)': dependencies: '@opentelemetry/api': 1.9.0 @@ -2259,28 +2199,29 @@ snapshots: '@opentelemetry/core': 1.30.1(@opentelemetry/api@1.9.0) '@opentelemetry/resources': 1.30.1(@opentelemetry/api@1.9.0) - '@opentelemetry/sdk-metrics@1.9.0(@opentelemetry/api@1.9.0)': - dependencies: - '@opentelemetry/api': 1.9.0 - '@opentelemetry/core': 1.9.0(@opentelemetry/api@1.9.0) - '@opentelemetry/resources': 1.9.0(@opentelemetry/api@1.9.0) - lodash.merge: 4.6.2 - - '@opentelemetry/sdk-node@0.35.0(@opentelemetry/api@1.9.0)': + '@opentelemetry/sdk-node@0.57.2(@opentelemetry/api@1.9.0)': dependencies: '@opentelemetry/api': 1.9.0 - '@opentelemetry/core': 1.9.0(@opentelemetry/api@1.9.0) - '@opentelemetry/exporter-jaeger': 1.9.0(@opentelemetry/api@1.9.0) - '@opentelemetry/exporter-trace-otlp-grpc': 0.35.0(@opentelemetry/api@1.9.0) - '@opentelemetry/exporter-trace-otlp-http': 0.35.0(@opentelemetry/api@1.9.0) - '@opentelemetry/exporter-trace-otlp-proto': 0.35.0(@opentelemetry/api@1.9.0) - '@opentelemetry/exporter-zipkin': 1.9.0(@opentelemetry/api@1.9.0) - '@opentelemetry/instrumentation': 0.35.0(@opentelemetry/api@1.9.0) - '@opentelemetry/resources': 1.9.0(@opentelemetry/api@1.9.0) - '@opentelemetry/sdk-metrics': 1.9.0(@opentelemetry/api@1.9.0) - '@opentelemetry/sdk-trace-base': 1.9.0(@opentelemetry/api@1.9.0) - '@opentelemetry/sdk-trace-node': 1.9.0(@opentelemetry/api@1.9.0) - '@opentelemetry/semantic-conventions': 1.9.0 + '@opentelemetry/api-logs': 0.57.2 + '@opentelemetry/core': 1.30.1(@opentelemetry/api@1.9.0) + '@opentelemetry/exporter-logs-otlp-grpc': 0.57.2(@opentelemetry/api@1.9.0) + '@opentelemetry/exporter-logs-otlp-http': 0.57.2(@opentelemetry/api@1.9.0) + '@opentelemetry/exporter-logs-otlp-proto': 0.57.2(@opentelemetry/api@1.9.0) + '@opentelemetry/exporter-metrics-otlp-grpc': 0.57.2(@opentelemetry/api@1.9.0) + '@opentelemetry/exporter-metrics-otlp-http': 0.57.2(@opentelemetry/api@1.9.0) + '@opentelemetry/exporter-metrics-otlp-proto': 0.57.2(@opentelemetry/api@1.9.0) + '@opentelemetry/exporter-prometheus': 0.57.2(@opentelemetry/api@1.9.0) + '@opentelemetry/exporter-trace-otlp-grpc': 0.57.2(@opentelemetry/api@1.9.0) + '@opentelemetry/exporter-trace-otlp-http': 0.57.2(@opentelemetry/api@1.9.0) + '@opentelemetry/exporter-trace-otlp-proto': 0.57.2(@opentelemetry/api@1.9.0) + '@opentelemetry/exporter-zipkin': 1.30.1(@opentelemetry/api@1.9.0) + '@opentelemetry/instrumentation': 0.57.2(@opentelemetry/api@1.9.0) + '@opentelemetry/resources': 1.30.1(@opentelemetry/api@1.9.0) + '@opentelemetry/sdk-logs': 0.57.2(@opentelemetry/api@1.9.0) + '@opentelemetry/sdk-metrics': 1.30.1(@opentelemetry/api@1.9.0) + '@opentelemetry/sdk-trace-base': 1.30.1(@opentelemetry/api@1.9.0) + '@opentelemetry/sdk-trace-node': 1.30.1(@opentelemetry/api@1.9.0) + '@opentelemetry/semantic-conventions': 1.28.0 transitivePeerDependencies: - supports-color @@ -2291,27 +2232,18 @@ snapshots: '@opentelemetry/resources': 1.30.1(@opentelemetry/api@1.9.0) '@opentelemetry/semantic-conventions': 1.28.0 - '@opentelemetry/sdk-trace-base@1.9.0(@opentelemetry/api@1.9.0)': - dependencies: - '@opentelemetry/api': 1.9.0 - '@opentelemetry/core': 1.9.0(@opentelemetry/api@1.9.0) - '@opentelemetry/resources': 1.9.0(@opentelemetry/api@1.9.0) - '@opentelemetry/semantic-conventions': 1.9.0 - - '@opentelemetry/sdk-trace-node@1.9.0(@opentelemetry/api@1.9.0)': + '@opentelemetry/sdk-trace-node@1.30.1(@opentelemetry/api@1.9.0)': dependencies: '@opentelemetry/api': 1.9.0 - '@opentelemetry/context-async-hooks': 1.9.0(@opentelemetry/api@1.9.0) - '@opentelemetry/core': 1.9.0(@opentelemetry/api@1.9.0) - '@opentelemetry/propagator-b3': 1.9.0(@opentelemetry/api@1.9.0) - '@opentelemetry/propagator-jaeger': 1.9.0(@opentelemetry/api@1.9.0) - '@opentelemetry/sdk-trace-base': 1.9.0(@opentelemetry/api@1.9.0) + '@opentelemetry/context-async-hooks': 1.30.1(@opentelemetry/api@1.9.0) + '@opentelemetry/core': 1.30.1(@opentelemetry/api@1.9.0) + '@opentelemetry/propagator-b3': 1.30.1(@opentelemetry/api@1.9.0) + '@opentelemetry/propagator-jaeger': 1.30.1(@opentelemetry/api@1.9.0) + '@opentelemetry/sdk-trace-base': 1.30.1(@opentelemetry/api@1.9.0) semver: 7.8.1 '@opentelemetry/semantic-conventions@1.28.0': {} - '@opentelemetry/semantic-conventions@1.9.0': {} - '@oxc-project/runtime@0.153.0': {} '@oxc-project/types@0.153.0': {} @@ -2506,6 +2438,8 @@ snapshots: dependencies: '@types/node': 22.19.19 + '@types/shimmer@1.2.0': {} + '@types/statuses@2.0.6': {} '@vercel/functions@1.6.0': {} @@ -2712,8 +2646,6 @@ snapshots: json-schema-traverse: 1.0.0 require-from-string: 2.0.2 - ansi-color@0.2.2: {} - ansi-regex@5.0.1: {} ansi-styles@4.3.0: @@ -2753,15 +2685,10 @@ snapshots: transitivePeerDependencies: - '@aws-sdk/credential-provider-web-identity' - bufrw@1.4.0: - dependencies: - ansi-color: 0.2.2 - error: 7.0.2 - hexer: 1.5.0 - xtend: 4.0.2 - chai@6.2.2: {} + cjs-module-lexer@1.4.3: {} + cjs-module-lexer@2.2.0: {} cli-width@4.1.0: {} @@ -2796,11 +2723,6 @@ snapshots: emoji-regex@8.0.0: {} - error@7.0.2: - dependencies: - string-template: 0.2.1 - xtend: 4.0.2 - es-errors@1.3.0: {} es-module-lexer@2.3.2: {} @@ -2891,12 +2813,12 @@ snapshots: '@types/set-cookie-parser': 2.4.10 set-cookie-parser: 3.1.0 - hexer@1.5.0: + import-in-the-middle@1.15.0: dependencies: - ansi-color: 0.2.2 - minimist: 1.2.8 - process: 0.10.1 - xtend: 4.0.2 + acorn: 8.16.0 + acorn-import-attributes: 1.9.5(acorn@8.16.0) + cjs-module-lexer: 1.4.3 + module-details-from-path: 1.0.4 is-core-module@2.16.2: dependencies: @@ -2906,14 +2828,6 @@ snapshots: is-node-process@1.2.0: {} - jaeger-client@3.19.0: - dependencies: - node-int64: 0.4.0 - opentracing: 0.14.7 - thriftrw: 3.11.4 - uuid: 11.1.1 - xorshift: 1.2.0 - js-tokens@4.0.0: {} json-schema-traverse@1.0.0: {} @@ -2969,10 +2883,6 @@ snapshots: lodash.camelcase@4.3.0: {} - lodash.merge@4.6.2: {} - - long@2.4.0: {} - long@5.3.2: {} lz-string@1.5.0: {} @@ -2983,8 +2893,6 @@ snapshots: meriyah@6.1.4: {} - minimist@1.2.8: {} - module-details-from-path@1.0.4: {} mrmime@2.0.1: {} @@ -3022,12 +2930,8 @@ snapshots: nanoid@3.3.19: {} - node-int64@0.4.0: {} - obug@2.2.1: {} - opentracing@0.14.7: {} - outvariant@1.4.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)): @@ -3112,8 +3016,6 @@ snapshots: ansi-styles: 5.2.0 react-is: 17.0.2 - process@0.10.1: {} - protobufjs@7.6.5: dependencies: '@protobufjs/aspromise': 1.1.2 @@ -3134,7 +3036,7 @@ snapshots: require-from-string@2.0.2: {} - require-in-the-middle@5.2.0: + require-in-the-middle@7.5.2: dependencies: debug: 4.4.3 module-details-from-path: 1.0.4 @@ -3177,8 +3079,6 @@ snapshots: strict-event-emitter@0.5.1: {} - string-template@0.2.1: {} - string-width@4.2.3: dependencies: emoji-regex: 8.0.0 @@ -3193,12 +3093,6 @@ snapshots: tagged-tag@1.0.0: {} - thriftrw@3.11.4: - dependencies: - bufrw: 1.4.0 - error: 7.0.2 - long: 2.4.0 - tinybench@6.2.2: {} tinyexec@1.3.1: {} @@ -3338,10 +3232,6 @@ snapshots: ws@8.22.0: {} - xorshift@1.2.0: {} - - xtend@4.0.2: {} - y18n@5.0.8: {} yargs-parser@21.1.1: {} 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 c31b8e320..6cb2e4b73 100644 --- a/integrations/otel-js/otel-v1/tests/otel-v1.test.ts +++ b/integrations/otel-js/otel-v1/tests/otel-v1.test.ts @@ -1,63 +1,44 @@ -/* eslint-disable @typescript-eslint/no-explicit-any */ -/* eslint-disable @typescript-eslint/consistent-type-assertions */ -import { describe, it, expect, vi } from "vite-plus/test"; -import { BasicTracerProvider } from "@opentelemetry/sdk-trace-base"; -import { BraintrustExporter } from "../src/otel"; - -describe("OTEL v1 version-specific attributes", () => { - it("exporter handles v1-specific span attributes", () => { - process.env.BRAINTRUST_API_KEY = "test-api-key"; - - const exporter = new BraintrustExporter(); - const processor = (exporter as any).processor; - const batchProcessor = (processor as any).processor; - const baseExporter = (batchProcessor as any)._exporter; - - const mockExport = vi.fn(); - baseExporter.export = mockExport; - - const v1Span = { - name: "gen_ai.completion", - spanContext: () => ({ traceId: "trace-123", spanId: "span-456" }), - parentSpanId: "parent-789", - instrumentationLibrary: { name: "openai", version: "1.0.0" }, - resource: { - attributes: { - "service.name": "test-service", - }, - }, - } as any; - - baseExporter.export([v1Span], () => {}); - expect(mockExport).toHaveBeenCalledOnce(); - const [exportedSpans] = mockExport.mock.calls[0]; - - expect(exportedSpans).toHaveLength(1); - const exportedSpan = exportedSpans[0]; - - // V1-specific: should have parentSpanId - expect(exportedSpan.parentSpanId).toBe("parent-789"); - - // V1-specific: should have instrumentationLibrary (not instrumentationScope) - expect(exportedSpan.instrumentationLibrary).toEqual({ - name: "openai", - version: "1.0.0", - }); - - // V1-specific: should NOT have v2 properties - expect(exportedSpan.parentSpanContext).toBeUndefined(); - expect(exportedSpan.instrumentationScope).toBeUndefined(); - }); - - it("confirms v1 environment by checking real span structure", () => { - const testProvider = new BasicTracerProvider(); - const testTracer = testProvider.getTracer("test"); - const testSpan = testTracer.startSpan("test") as any; - - // Verify we're in v1 environment - expect(testSpan).toHaveProperty("instrumentationLibrary"); - expect(testSpan).not.toHaveProperty("instrumentationScope"); - - testSpan.end(); - }); +import { expect, test } from "vite-plus/test"; +import { context, trace } from "@opentelemetry/api"; +import { + BasicTracerProvider, + BatchSpanProcessor, + InMemorySpanExporter, + type SpanExporter, + type SpanProcessor, +} from "@opentelemetry/sdk-trace-base"; +import { BraintrustExporter, BraintrustSpanProcessor } from "@braintrust/otel"; + +test("OTel 1 accepts the adapters and exports real parent and child spans", async () => { + const memory = new InMemorySpanExporter(); + const exporter = new BraintrustExporter({ + exporter: memory, + }) satisfies SpanExporter; + const processor = new BraintrustSpanProcessor({ + processor: new BatchSpanProcessor(exporter), + }) satisfies SpanProcessor; + const provider = new BasicTracerProvider(); + provider.addSpanProcessor(processor); + try { + const tracer = provider.getTracer("compatibility-test", "1.0.0"); + const root = tracer.startSpan("root"); + const child = tracer.startSpan( + "child", + {}, + trace.setSpan(context.active(), root), + ); + child.end(); + root.end(); + await provider.forceFlush(); + const spans = memory.getFinishedSpans(); + expect(spans.map((span) => span.name)).toEqual(["child", "root"]); + expect(spans[0].spanContext().traceId).toBe(root.spanContext().traceId); + expect(spans[0].parentSpanId).toBe(root.spanContext().spanId); + expect(spans[0].instrumentationLibrary.name).toBe("compatibility-test"); + expect( + JSON.parse(spans[0].attributes["braintrust.context_json"] as string), + ).toHaveProperty("span_origin.instrumentation.name", "braintrust-otel-js"); + } finally { + await provider.shutdown(); + } }); diff --git a/integrations/otel-js/otel-v1/tsconfig.json b/integrations/otel-js/otel-v1/tsconfig.json index 3cf966d7c..79ffe5662 100644 --- a/integrations/otel-js/otel-v1/tsconfig.json +++ b/integrations/otel-js/otel-v1/tsconfig.json @@ -1,4 +1,7 @@ { "extends": "../tsconfig.json", - "include": ["src"] + "compilerOptions": { + "skipLibCheck": false + }, + "include": ["tests"] } diff --git a/integrations/otel-js/otel-v2/package.json b/integrations/otel-js/otel-v2/package.json index 0a1fdda01..556db8e52 100644 --- a/integrations/otel-js/otel-v2/package.json +++ b/integrations/otel-js/otel-v2/package.json @@ -6,7 +6,8 @@ "scripts": { "clean": "rm -rf dist/*", "build:deps": "(cd ../../.. && pnpm exec vp run --filter=braintrust --filter=@braintrust/otel build)", - "test": "pnpm run build:deps && vp test run" + "test": "pnpm run build:deps && pnpm run check:typings && vp test run", + "check:typings": "tsc --noEmit" }, "author": "Braintrust Data Inc.", "license": "MIT", diff --git a/integrations/otel-js/otel-v2/pnpm-lock.yaml b/integrations/otel-js/otel-v2/pnpm-lock.yaml index 8a6e2cfa0..b962e3d46 100644 --- a/integrations/otel-js/otel-v2/pnpm-lock.yaml +++ b/integrations/otel-js/otel-v2/pnpm-lock.yaml @@ -32,7 +32,7 @@ importers: devDependencies: '@braintrust/otel': specifier: file:.. - version: 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)) + version: file:..(@opentelemetry/api@1.9.0)(braintrust@file:../../../js(zod@4.4.3)) '@opentelemetry/api': specifier: 1.9.0 version: 1.9.0 @@ -97,11 +97,8 @@ packages: resolution: {directory: .., type: directory} engines: {node: '>=22.18.0'} peerDependencies: - '@opentelemetry/api': '>=1.9.0' - '@opentelemetry/core': '>=1.9.0' - '@opentelemetry/exporter-trace-otlp-http': '>=0.35.0' - '@opentelemetry/sdk-trace-base': '>=1.9.0' - braintrust: '>=1.0.0-0' + '@opentelemetry/api': ^1.9.0 + braintrust: workspace:^ '@esbuild/aix-ppc64@0.28.1': resolution: {integrity: sha512-Svl7tq8k/08+p6CXPpRjQ1fKX+1odH/BQbb48fV6fj3CWHhsoIOoY87w1oHXm0qEpkIK3ZfVgp0hed3XBXzXMQ==} @@ -1855,12 +1852,9 @@ snapshots: '@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))': + '@braintrust/otel@file:..(@opentelemetry/api@1.9.0)(braintrust@file:../../../js(zod@4.4.3))': dependencies: '@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) '@esbuild/aix-ppc64@0.28.1': 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 988188dee..df6a307d5 100644 --- a/integrations/otel-js/otel-v2/tests/otel-v2.test.ts +++ b/integrations/otel-js/otel-v2/tests/otel-v2.test.ts @@ -1,69 +1,43 @@ -/* eslint-disable @typescript-eslint/no-explicit-any */ -/* eslint-disable @typescript-eslint/consistent-type-assertions */ -import { describe, it, expect, vi } from "vite-plus/test"; -import { BasicTracerProvider } from "@opentelemetry/sdk-trace-base"; -import { BraintrustExporter } from "../src/otel"; - -describe("OTEL v2 version-specific attributes", () => { - it("exporter handles v2-specific span attributes", () => { - process.env.BRAINTRUST_API_KEY = "test-api-key"; - - const exporter = new BraintrustExporter(); - const processor = (exporter as any).processor; - const batchProcessor = (processor as any).processor; - const baseExporter = (batchProcessor as any)._exporter; - - const mockExport = vi.fn(); - baseExporter.export = mockExport; - - const v2Span = { - name: "gen_ai.completion", - spanContext: () => ({ traceId: "trace-123", spanId: "span-456" }), - parentSpanContext: { - spanId: "parent-789", - traceId: "trace-123", - }, - instrumentationScope: { name: "openai", version: "1.0.0" }, - resource: { - attributes: { - "service.name": "test-service", - }, - }, - } as any; - - baseExporter.export([v2Span], () => {}); - expect(mockExport).toHaveBeenCalledOnce(); - const [exportedSpans] = mockExport.mock.calls[0]; - - expect(exportedSpans).toHaveLength(1); - const exportedSpan = exportedSpans[0]; - - // V2-specific: should have parentSpanContext - expect(exportedSpan.parentSpanContext).toEqual({ - spanId: "parent-789", - traceId: "trace-123", - }); - - // V2-specific: should have instrumentationScope (not instrumentationLibrary) - expect(exportedSpan.instrumentationScope).toEqual({ - name: "openai", - version: "1.0.0", - }); - - // V2-specific: should NOT have v1 properties - expect(exportedSpan.parentSpanId).toBeUndefined(); - expect(exportedSpan.instrumentationLibrary).toBeUndefined(); - }); - - it("confirms v2 environment by checking real span structure", () => { - const testProvider = new BasicTracerProvider(); - const testTracer = testProvider.getTracer("test"); - const testSpan = testTracer.startSpan("test") as any; - - // Verify we're in v2 environment - expect(testSpan).toHaveProperty("instrumentationScope"); - expect(testSpan).not.toHaveProperty("instrumentationLibrary"); - - testSpan.end(); - }); +import { expect, test } from "vite-plus/test"; +import { context, trace } from "@opentelemetry/api"; +import { + BasicTracerProvider, + BatchSpanProcessor, + InMemorySpanExporter, + type SpanExporter, + type SpanProcessor, +} from "@opentelemetry/sdk-trace-base"; +import { BraintrustExporter, BraintrustSpanProcessor } from "@braintrust/otel"; + +test("OTel 2 accepts the adapters and exports real parent and child spans", async () => { + const memory = new InMemorySpanExporter(); + const exporter = new BraintrustExporter({ + exporter: memory, + }) satisfies SpanExporter; + const processor = new BraintrustSpanProcessor({ + processor: new BatchSpanProcessor(exporter), + }) satisfies SpanProcessor; + const provider = new BasicTracerProvider({ spanProcessors: [processor] }); + try { + const tracer = provider.getTracer("compatibility-test", "1.0.0"); + const root = tracer.startSpan("root"); + const child = tracer.startSpan( + "child", + {}, + trace.setSpan(context.active(), root), + ); + child.end(); + root.end(); + await provider.forceFlush(); + const spans = memory.getFinishedSpans(); + expect(spans.map((span) => span.name)).toEqual(["child", "root"]); + expect(spans[0].spanContext().traceId).toBe(root.spanContext().traceId); + expect(spans[0].parentSpanContext?.spanId).toBe(root.spanContext().spanId); + expect(spans[0].instrumentationScope.name).toBe("compatibility-test"); + expect( + JSON.parse(spans[0].attributes["braintrust.context_json"] as string), + ).toHaveProperty("span_origin.instrumentation.name", "braintrust-otel-js"); + } finally { + await provider.shutdown(); + } }); diff --git a/integrations/otel-js/otel-v2/tsconfig.json b/integrations/otel-js/otel-v2/tsconfig.json index 3c43903cf..79ffe5662 100644 --- a/integrations/otel-js/otel-v2/tsconfig.json +++ b/integrations/otel-js/otel-v2/tsconfig.json @@ -1,3 +1,7 @@ { - "extends": "../tsconfig.json" + "extends": "../tsconfig.json", + "compilerOptions": { + "skipLibCheck": false + }, + "include": ["tests"] } diff --git a/integrations/otel-js/otel-v3/package.json b/integrations/otel-js/otel-v3/package.json new file mode 100644 index 000000000..ae7c91602 --- /dev/null +++ b/integrations/otel-js/otel-v3/package.json @@ -0,0 +1,55 @@ +{ + "name": "@braintrust/otel-v3-test", + "version": "0.0.1-test", + "private": true, + "description": "Test package for @braintrust/otel with OpenTelemetry 3.x", + "scripts": { + "clean": "rm -rf dist/*", + "build:deps": "(cd ../../.. && pnpm exec vp run --filter=braintrust --filter=@braintrust/otel build)", + "test": "pnpm run build:deps && pnpm install --ignore-workspace && pnpm run check:typings && vp test run", + "check:typings": "tsc --noEmit" + }, + "author": "Braintrust Data Inc.", + "license": "MIT", + "devDependencies": { + "@braintrust/otel": "file:..", + "@opentelemetry/api": "1.10.0", + "@opentelemetry/context-async-hooks": "3.1.0", + "@opentelemetry/core": "3.1.0", + "@opentelemetry/exporter-trace-otlp-http": "0.301.0", + "@opentelemetry/resources": "3.1.0", + "@opentelemetry/sdk-node": "0.301.0", + "@types/node": "^22.15.21", + "braintrust": "file:../../../js", + "msw": "^2.6.6", + "typescript": "5.5.4", + "vitest": "5.0.3", + "vite-plus": "1.1.0", + "vite": "npm:@voidzero-dev/vite-plus-core@1.1.0", + "@opentelemetry/sdk-trace": "3.1.0" + }, + "pnpm": { + "overrides": { + "protobufjs": "^7.6.5", + "body-parser@>=2.0.0 <2.3.0": "^2.3.0", + "uuid": "^11.1.1", + "qs@>=6.7.0 <6.15.2": "^6.15.2", + "path-to-regexp@<0.1.13": "^0.1.13", + "vite@>=8.0.0 <8.0.5": "^8.0.5", + "vite@>=7.0.0 <7.3.2": "^7.3.2", + "vite@>=6.0.0 <6.4.2": "^6.4.2", + "vite@>=5.0.0 <6.0.0": "^5.4.21", + "postcss@<8.5.18": "^8.5.18", + "picomatch@<2.3.2": "^2.3.2", + "picomatch@>=3.0.0 <3.0.2": "^3.0.2", + "picomatch@>=4.0.0 <4.0.4": "^4.0.4", + "fast-uri@<3.1.8": "^3.1.8", + "brace-expansion@<1.1.21": "^1.1.21", + "brace-expansion@>=2.0.0 <2.1.7": "^2.1.7", + "brace-expansion@>=3.0.0 <5.0.12": "^5.0.12", + "@grpc/grpc-js@>=1.14.0 <1.14.5": "^1.14.5", + "source-map-js@<1.2.2": "^1.2.2", + "proxy-addr@<2.0.8": "^2.0.8" + } + } +} diff --git a/integrations/otel-js/otel-v3/pnpm-lock.yaml b/integrations/otel-js/otel-v3/pnpm-lock.yaml new file mode 100644 index 000000000..59881c3e9 --- /dev/null +++ b/integrations/otel-js/otel-v3/pnpm-lock.yaml @@ -0,0 +1,2902 @@ +lockfileVersion: '9.0' + +settings: + autoInstallPeers: true + excludeLinksFromLockfile: false + +overrides: + protobufjs: ^7.6.5 + body-parser@>=2.0.0 <2.3.0: ^2.3.0 + uuid: ^11.1.1 + qs@>=6.7.0 <6.15.2: ^6.15.2 + path-to-regexp@<0.1.13: ^0.1.13 + vite@>=8.0.0 <8.0.5: ^8.0.5 + vite@>=7.0.0 <7.3.2: ^7.3.2 + vite@>=6.0.0 <6.4.2: ^6.4.2 + vite@>=5.0.0 <6.0.0: ^5.4.21 + postcss@<8.5.18: ^8.5.18 + picomatch@<2.3.2: ^2.3.2 + picomatch@>=3.0.0 <3.0.2: ^3.0.2 + picomatch@>=4.0.0 <4.0.4: ^4.0.4 + fast-uri@<3.1.8: ^3.1.8 + brace-expansion@<1.1.21: ^1.1.21 + brace-expansion@>=2.0.0 <2.1.7: ^2.1.7 + brace-expansion@>=3.0.0 <5.0.12: ^5.0.12 + '@grpc/grpc-js@>=1.14.0 <1.14.5': ^1.14.5 + source-map-js@<1.2.2: ^1.2.2 + proxy-addr@<2.0.8: ^2.0.8 + +importers: + + .: + devDependencies: + '@braintrust/otel': + specifier: file:.. + version: file:..(@opentelemetry/api@1.10.0)(braintrust@file:../../../js(zod@4.6.7)) + '@opentelemetry/api': + specifier: 1.10.0 + version: 1.10.0 + '@opentelemetry/context-async-hooks': + specifier: 3.1.0 + version: 3.1.0(@opentelemetry/api@1.10.0) + '@opentelemetry/core': + specifier: 3.1.0 + version: 3.1.0(@opentelemetry/api@1.10.0) + '@opentelemetry/exporter-trace-otlp-http': + specifier: 0.301.0 + version: 0.301.0(@opentelemetry/api@1.10.0) + '@opentelemetry/resources': + specifier: 3.1.0 + version: 3.1.0(@opentelemetry/api@1.10.0) + '@opentelemetry/sdk-node': + specifier: 0.301.0 + version: 0.301.0(@opentelemetry/api@1.10.0) + '@opentelemetry/sdk-trace': + specifier: 3.1.0 + version: 3.1.0(@opentelemetry/api@1.10.0) + '@types/node': + specifier: ^22.15.21 + version: 22.20.5 + braintrust: + specifier: file:../../../js + version: file:../../../js(zod@4.6.7) + msw: + specifier: ^2.6.6 + version: 2.15.0(@types/node@22.20.5)(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.20.5)(typescript@5.5.4)(yaml@2.9.1)' + vite-plus: + specifier: 1.1.0 + version: 1.1.0(@opentelemetry/api@1.10.0)(@types/node@22.20.5)(@vitest/ui@5.0.3(vitest@5.0.3))(msw@2.15.0(@types/node@22.20.5)(typescript@5.5.4))(typescript@5.5.4)(yaml@2.9.1) + vitest: + specifier: 5.0.3 + version: 5.0.3(@opentelemetry/api@1.10.0)(@types/node@22.20.5)(@vitest/browser-preview@5.0.3)(@vitest/ui@5.0.3)(@voidzero-dev/vite-plus-core@1.1.0(@types/node@22.20.5)(typescript@5.5.4)(yaml@2.9.1))(msw@2.15.0(@types/node@22.20.5)(typescript@5.5.4)) + +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.13': + resolution: {integrity: sha512-v+oN26xP4YIlQsbQ/CbTH1WG5m/l/DjKKZNy/Kp3f/AFvbgt7Em/D0FeMM3O2hX0YzeN2M16W8lOpyhIkfCBcQ==} + engines: {node: '>=6.9.0'} + + '@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 + braintrust: workspace:^ + + '@grpc/grpc-js@1.14.7': + resolution: {integrity: sha512-W4W/fcBMGRVtG6dW2c7cw2mnpS3hvbBd+oQXjh2iYFTZDk1JJmcNqkM5KSaPubQpamlUUurrlb7G1s9NJeERmQ==} + engines: {node: '>=12.10.0'} + + '@grpc/proto-loader@0.8.1': + resolution: {integrity: sha512-wtF6h+DY6M3YaDBPAmvuuA6jV8Sif9MjtOI5euKFWRgCDl5PeDpPsHR9u2l6St5ceY8AZgoNDww5+HvEsXFsGg==} + engines: {node: '>=6'} + hasBin: true + + '@inquirer/ansi@2.0.10': + resolution: {integrity: sha512-dwrc+1aqVZpHNkNDswXGtn1typ+Pxpo5FHZRyBw1le7onEJUkPUkYOY1FIFC1LvFMYUqqLuWBwElhAmencL7MQ==} + engines: {node: '>=23.5.0 || ^22.13.0 || ^20.17.0'} + + '@inquirer/confirm@6.3.5': + resolution: {integrity: sha512-BJ59BOyURiZiAxdr9AuRu38/k+rV+DcTIM2fUF/c/BXrab6H8zMZTTOl3LgkSJH96Xob2vOJxDiBkHiz32Xx2w==} + engines: {node: '>=23.5.0 || ^22.13.0 || ^20.17.0'} + peerDependencies: + '@types/node': '>=18' + peerDependenciesMeta: + '@types/node': + optional: true + + '@inquirer/core@12.0.6': + resolution: {integrity: sha512-5yxn2LsJ8Tu4ei2a+yNkmFRhM/cb+JMEsEs/AW6CCOnBnpMy4IjYiPMFyHtYq8weOmlTyqqCx40y0DsD7OsEPw==} + engines: {node: '>=23.5.0 || ^22.13.0 || ^20.17.0'} + peerDependencies: + '@types/node': '>=18' + peerDependenciesMeta: + '@types/node': + optional: true + + '@inquirer/figures@2.0.10': + resolution: {integrity: sha512-51elrUJV3jZpvD0ygz8+U10Kc6+Lzqln+/tYmnbhy6jo5oXpE6hMCcv+OTBC0enwJXsb3JIuT3VtEinRNCSK2A==} + engines: {node: '>=23.5.0 || ^22.13.0 || ^20.17.0'} + + '@inquirer/type@4.2.1': + resolution: {integrity: sha512-lDkPsnv5M5CcYB2T1qTc3dobNTPkVr4DU3Ic2Q6SW8/qVRHgvzdRdmVZ6YqG9Ag1aJTeMsLU0s0WPRh3CZWDQQ==} + engines: {node: '>=23.5.0 || ^22.13.0 || ^20.17.0'} + peerDependencies: + '@types/node': '>=18' + peerDependenciesMeta: + '@types/node': + optional: true + + '@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.6.0': + resolution: {integrity: sha512-T7jf+5zgsZHwNJ4lvQ7/aezbyk0nNX+zJVWpmHA7VYsEx7a7qr5Rg5IbtJFqkgze5Y2sruq1RUY8Q837Od7iFw==} + + '@jridgewell/trace-mapping@0.3.31': + resolution: {integrity: sha512-zzNR+SdQSDJzc8joaeP8QQoCQr8NuYx2dIIytl1QeBEZHJ9uW6hebsrYgbz8hJwUQao3TWCMtmfV8Nu1twOLAw==} + + '@js-sdsl/ordered-map@4.4.2': + resolution: {integrity: sha512-iUKgm52T8HOE/makSxjqoWhe95ZJA1/G1sYsGev2JDKUSS14KAgg1LHb+Ba+IPow0xflbnSkOsZcO08C7w1gYw==} + + '@mswjs/interceptors@0.41.9': + resolution: {integrity: sha512-VVPPgHyQ6ShqnrmDWuxjmUIsO9gWyOZFmuOfLd9LfBGQJwZfy0gvv9pbHSJuoFNIYC7ZDX9aoFwowjcdSC4E8w==} + engines: {node: '>=18'} + + '@open-draft/deferred-promise@2.2.0': + resolution: {integrity: sha512-CecwLWx3rhxVQF6V4bAgPS5t+So2sTbPgAzafKkVizyi7tlwpcFpdFqq+wqF2OwNBmqFuu6tOyouTuxgpMfzmA==} + + '@open-draft/deferred-promise@3.0.0': + resolution: {integrity: sha512-XW375UK8/9SqUVNVa6M0yEy8+iTi4QN5VZ7aZuRFQmy76LRwI9wy5F4YIBU6T+eTe2/DNDo8tqu8RHlwLHM6RA==} + + '@open-draft/logger@0.3.0': + resolution: {integrity: sha512-X2g45fzhxH238HKO4xbSr7+wBS8Fvw6ixhTDuvLd5mqh6bJJCFAPwU9mPDxbcrRtfxv4u5IHCEH77BmxvXmmxQ==} + + '@open-draft/until@2.1.0': + resolution: {integrity: sha512-U69T3ItWHvLwGg5eJ0n3I62nWuE6ilHlmz7zM0npLBRvPRd7e6NYmg54vvRtP5mZG7kZqZCFVdsTWo7BPtBujg==} + + '@opentelemetry/api@1.10.0': + resolution: {integrity: sha512-lMkyes8iHrG+mk6WH7X70MdCLI2RS0FwNBY1PyQ8UemaijE2m5mv5A0Niy3HtbYTFrgkUEQjFMXmSAk2hQDuPQ==} + engines: {node: '>=8.0.0'} + + '@opentelemetry/configuration@0.301.0': + resolution: {integrity: sha512-pq68mOXEwi/4ojJBc38VEGg3qADsTt5I+mDHgxCh03nAg/QcD7NR4xjY9htyhWkFTQZFKyg9PR3At0GJKaYBFg==} + engines: {node: '>=22.15.0'} + peerDependencies: + '@opentelemetry/api': ^1.10.0 + + '@opentelemetry/context-async-hooks@3.1.0': + resolution: {integrity: sha512-IEgk4jBcLY1yLej9dFSJJWEIkUZTRsfBkJ0p/M8hpFKYBMYNzB8wwoziACFRiFW8mgpyBTRCjqjnd/IV5OoNLg==} + engines: {node: '>=22.15.0'} + peerDependencies: + '@opentelemetry/api': '>=1.0.0 <1.11.0' + + '@opentelemetry/core@3.1.0': + resolution: {integrity: sha512-TS/6jqU5gilzjvjGnsTEKVFF/0MMAtBc2psxaX39IGCBYLat83/iHxYBWn+O6WB+pnyB0SwiH46rvTEp/OeYxA==} + engines: {node: '>=22.15.0'} + peerDependencies: + '@opentelemetry/api': '>=1.10.0 <1.11.0' + + '@opentelemetry/exporter-logs-otlp-grpc@0.301.0': + resolution: {integrity: sha512-kLzHx/kVjA6X8NfPkjGHltRFclnTy9/eKUfZWSenoPlfC0agNG5MmGXFDYTL9iA53wbfN3yRt039Yy7Lzo/Hbg==} + engines: {node: '>=22.15.0'} + peerDependencies: + '@opentelemetry/api': ^1.10.0 + + '@opentelemetry/exporter-logs-otlp-http@0.301.0': + resolution: {integrity: sha512-+k8wM02vB0PGKWJcWZ8GqjlPryGzP/k5HQwYruJbuj4YBLwSGqfjtNihSjYjGqhJh7aYTugY8WWP0Ux4Tvzz1w==} + engines: {node: '>=22.15.0'} + peerDependencies: + '@opentelemetry/api': ^1.10.0 + + '@opentelemetry/exporter-logs-otlp-proto@0.301.0': + resolution: {integrity: sha512-KQR2KbQcFJHPbTknEXqpdcSyv9UT2xfLaXGNwcxQnjl9QBtSlZSup23AnkOlWuEplQ6aVaaPxSp2a2hfjMA3iQ==} + engines: {node: '>=22.15.0'} + peerDependencies: + '@opentelemetry/api': ^1.10.0 + + '@opentelemetry/exporter-metrics-otlp-grpc@0.301.0': + resolution: {integrity: sha512-vz2rRZBuHm5QSAWJ0NDCK5MJlXCdiQ7tFk977T1nRq9qJ98+2VYw3cN+KfTXAYZIKwcI5P7pWMIF+fxPmpWIDg==} + engines: {node: '>=22.15.0'} + peerDependencies: + '@opentelemetry/api': ^1.10.0 + + '@opentelemetry/exporter-metrics-otlp-http@0.301.0': + resolution: {integrity: sha512-c0TdH1TWdgcwGDSJi0WZpukPspsec37bEuNlGh03AFFnTX6qlvoXUQ1raRiElcIUypksgqwx+y3Wo+YVBX6okQ==} + engines: {node: '>=22.15.0'} + peerDependencies: + '@opentelemetry/api': ^1.10.0 + + '@opentelemetry/exporter-metrics-otlp-proto@0.301.0': + resolution: {integrity: sha512-fhJ3H1bvcc7h7F4Rgdl+aN9tc5xNIhUXLO7N8krMeT/STGS51YBUHOIgaNcjXQDl3vz/m805tVeaZlna7DRbUg==} + engines: {node: '>=22.15.0'} + peerDependencies: + '@opentelemetry/api': ^1.10.0 + + '@opentelemetry/exporter-prometheus@0.301.0': + resolution: {integrity: sha512-I0n68f/Vr4Rq7dUmuwVRInkIU8eYe9TL6mxmDW2c2fAgJDhCQ/OA/AwI6r7ux8QwAIRtm4SuJ2FWmsiiMPJfVQ==} + engines: {node: '>=22.15.0'} + peerDependencies: + '@opentelemetry/api': '>=1.10.0 <1.11.0' + + '@opentelemetry/exporter-trace-otlp-grpc@0.301.0': + resolution: {integrity: sha512-DlYsmzueY7Ut2zuhrsZ5AO55L1k1bBgcY/YQxGBYobvotvkTMFXG6ur8yjS7A4ZAnKjnH8gEWfPe5wUE5V5MCA==} + engines: {node: '>=22.15.0'} + peerDependencies: + '@opentelemetry/api': ^1.10.0 + + '@opentelemetry/exporter-trace-otlp-http@0.301.0': + resolution: {integrity: sha512-jngi+SsCkfg+7kBxn4TrtdK47lqzfKfh+qzxHTRaSF4GGD3q9z+mIUbvzytgrbqHwS4Q7NrEMJS6LEqeG+a1tw==} + engines: {node: '>=22.15.0'} + peerDependencies: + '@opentelemetry/api': ^1.10.0 + + '@opentelemetry/exporter-trace-otlp-proto@0.301.0': + resolution: {integrity: sha512-GcW0pOtxbw1IIWiMxu7eBQkpxMuvW7PrcZzG84UwHILzu/tW+HeFu6+kOVQTGsADYod2YNFCgpueXkLFdQ/BSw==} + engines: {node: '>=22.15.0'} + peerDependencies: + '@opentelemetry/api': ^1.10.0 + + '@opentelemetry/exporter-zipkin@3.1.0': + resolution: {integrity: sha512-329IlPeDP1My46OM1XajIFGSbGPaDw9urwYNmKOzxxvFkOwQoA13gknK210JuA3rAJqQ6HnazVYjPEfP2j/8dQ==} + engines: {node: '>=22.15.0'} + peerDependencies: + '@opentelemetry/api': ^1.10.0 + + '@opentelemetry/instrumentation@0.301.0': + resolution: {integrity: sha512-ubspM+RvsFAzW1pAN3np0T1GxVh+/yzoZI24LDGhy+hFxcdXs9wQkSZH4qx6xnZ1035gO3EZVpCPmqR3ZZwhjg==} + engines: {node: '>=22.15.0'} + peerDependencies: + '@opentelemetry/api': '>=1.10.0 <1.11.0' + + '@opentelemetry/otlp-exporter-base@0.301.0': + resolution: {integrity: sha512-X5qmkSNvJMkUWnOsIZ1lg2VkcJkRTuXMvlACngE7EkxKWkfhgjq1GXd/qp8kCATokAq0RfNHtMFIZJ18FRjqKg==} + engines: {node: '>=22.15.0'} + peerDependencies: + '@opentelemetry/api': ^1.10.0 + + '@opentelemetry/otlp-grpc-exporter-base@0.301.0': + resolution: {integrity: sha512-JI7yMzQJaYTs2b2MxaAlOxLkOahqb85MsbRzRU1C/XezIMyLh3ONfuC5QtKrAlgZTrGBKt8GzS3FuIUWG93Bzw==} + engines: {node: '>=22.15.0'} + peerDependencies: + '@opentelemetry/api': ^1.10.0 + + '@opentelemetry/otlp-transformer@0.301.0': + resolution: {integrity: sha512-lxFBIG8mhAnFouNxJNpT+6zxn7lrqwej9fPUWbrxuzldYXimesH8p5xwZYPHlapRMlGt2eMBvzMsvGnJUlJMgg==} + engines: {node: '>=22.15.0'} + peerDependencies: + '@opentelemetry/api': '>=1.10.0 <1.11.0' + + '@opentelemetry/propagator-b3@3.1.0': + resolution: {integrity: sha512-6mz/39sr2Z7z4RfBdo/VFFzAXT88afOFU3mf/g7sZk11zheZeypFhVsoDP/lxCQbWh3c0FKuVMIkLZXjD1K2Og==} + engines: {node: '>=22.15.0'} + peerDependencies: + '@opentelemetry/api': '>=1.10.0 <1.11.0' + + '@opentelemetry/resources@3.1.0': + resolution: {integrity: sha512-51dObapLj5PFRoQ+V4G4pZuQhzgFw/fC4m6F6Qa7rhjkrPyldroR34uOhL1vQqbpR02WdRh0JmsxyNlh2YNNvg==} + engines: {node: '>=22.15.0'} + peerDependencies: + '@opentelemetry/api': '>=1.10.0 <1.11.0' + + '@opentelemetry/sdk-logs@3.1.0': + resolution: {integrity: sha512-/WSmZkeGXQjSazW538YsD+3/aiQzZYA81pAddH2+7+VBxuTJl04W9c0xLke+m9jSTfuZaCIS4fbzeSUKKIzc4Q==} + engines: {node: '>=22.15.0'} + peerDependencies: + '@opentelemetry/api': '>=1.10.0 <1.11.0' + + '@opentelemetry/sdk-metrics@3.1.0': + resolution: {integrity: sha512-eU7MfYDxGPAJhA8bKYTkAFpmKrKMbgyKZQoBCKu+L9rJ3SKK3wfN3Vuv1/Kx90nLMwQW0931Zf04bO9LKYbcFQ==} + engines: {node: '>=22.15.0'} + peerDependencies: + '@opentelemetry/api': '>=1.10.0 <1.11.0' + + '@opentelemetry/sdk-node@0.301.0': + resolution: {integrity: sha512-+TdBrdDo15mRWsSRbvGYX7Edsrby0LZo1oXrvBIiDKhPmH+VXDrsDb/MwfUoUQTcwv0Nw22L8xrqHorP5ii7uQ==} + engines: {node: '>=22.15.0'} + peerDependencies: + '@opentelemetry/api': '>=1.10.0 <1.11.0' + + '@opentelemetry/sdk-trace@3.1.0': + resolution: {integrity: sha512-MlK9KhpKp4Gkz7o9OjwdWgSLCms1mr5SSDykqwaBq4NUsB6IhM4ihXpC6fZek3LGuCB8KIueQAPpcgU1Q3UYkg==} + engines: {node: '>=22.15.0'} + peerDependencies: + '@opentelemetry/api': '>=1.10.0 <1.11.0' + + '@opentelemetry/semantic-conventions@1.45.0': + resolution: {integrity: sha512-aOP45mbcOKbQVvFWfQmgoOPT8R2iwbbbgl83k4CujGV9BrJhFr3qG55MFpwFs4YLabpuW3HhzUY9eQxPlDqaZw==} + engines: {node: '>=14'} + + '@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] + + '@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] + + '@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] + + '@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] + + '@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] + + '@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] + + '@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] + + '@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] + + '@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] + + '@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] + + '@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] + + '@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] + + '@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] + + '@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] + + '@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] + + '@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] + + '@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] + + '@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] + + '@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] + + '@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] + + '@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] + + '@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] + + '@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] + + '@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.2': + resolution: {integrity: sha512-b1UQwcEZ4yCnMCD8DAL1VlbvBJE9/IX4FTIp7BG1xYpf29SLazLSrqUkj4w7Y5y7cCVP6E5tcqqcI0xemPkHug==} + + '@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.10': + resolution: {integrity: sha512-lTyOMnOcmRBzdCv6/4zUZCrEhV0xrqQiUZBZtVcbmLHBXKLnN8q89v/wKRF/Q0HbwVsqbIG3o5HDoFl9sduM+g==} + + '@types/node@22.20.5': + resolution: {integrity: sha512-U2+DNr+wSjpsTS/wZGYHq7GcwfuSmKiKvoPvK22zwTlRhU91yOniN4qRR5KhIjvif7ysw/dz/hKmfDH0Ris4aA==} + + '@types/set-cookie-parser@2.4.10': + resolution: {integrity: sha512-GGmQVGpQWUe5qglJozEjZV/5dyxbOOZ0LHe/lqyWssB88Y4svNfst0uqBVscdDeIKl5Jy5+aPSvy7mI9tYRguw==} + + '@types/statuses@2.0.6': + resolution: {integrity: sha512-xMAgYwceFhRA2zY+XbEA7mxYbA093wdiW8Vu6gZPGWy9cmOyU9XesH1tNcEWsKFd5Vzrqx5T3D38PWx1FIIXkA==} + + '@vercel/functions@1.6.0': + resolution: {integrity: sha512-R6FKQrYT5MZs5IE1SqeCJWxMuBdHawFcCZboKKw8p7s+6/mcd55Gx6tWmyKnQTyrSEA04NH73Tc9CbqpEle8RA==} + engines: {node: '>= 16'} + peerDependencies: + '@aws-sdk/credential-provider-web-identity': '*' + peerDependenciesMeta: + '@aws-sdk/credential-provider-web-identity': + optional: true + + '@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: ^8.0.5 + 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==} + + '@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 + + '@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 + + '@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==} + + acorn-import-attributes@1.9.5: + resolution: {integrity: sha512-n02Vykv5uA3eHGM/Z2dQrcD56kL8TyDb2p1+0P83PClMnC/nc+anbQRhIOWnSq4Ke/KvDPrY3C9hDtC/A3eHnQ==} + peerDependencies: + acorn: ^8 + + acorn@8.19.0: + resolution: {integrity: sha512-oJlA3XiRm7Cyk6qFD2Jn8ak9B7jSy0qy00ADO3+8dpT0LSjFihQYv4C02LFCSYJV3Q37xYwwRy5m+IpIiUzqWw==} + engines: {node: '>=0.4.0'} + hasBin: true + + ajv@8.20.0: + resolution: {integrity: sha512-Thbli+OlOj+iMPYFBVBfJ3OmCAnaSyNn4M1vz9T6Gka5Jt9ba/HIR56joy65tY6kx/FCF5VXNB819Y7/GUrBGA==} + + ansi-regex@5.0.1: + resolution: {integrity: sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==} + engines: {node: '>=8'} + + ansi-styles@4.3.0: + resolution: {integrity: sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==} + 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'} + + astring@1.9.0: + resolution: {integrity: sha512-LElXdjswlqjWrPpJFg1Fx4wpkOCxj1TDHlSV4PlaRxHGWko024xICaa97ZkMfs6DRKlCguiAI+rbXv5GWwXIkg==} + hasBin: true + + braintrust@file:../../../js: + resolution: {directory: ../../../js, type: directory} + engines: {node: '>=22.18.0'} + peerDependencies: + '@types/react': '>=18' + zod: ^3.25.34 || ^4.0 + peerDependenciesMeta: + '@types/react': + optional: true + + chai@6.3.0: + resolution: {integrity: sha512-XWAtwJ6OHO+tj0EKCs0Y2UamnyOxseZWltU4x2U2wh8g4AigdjwvtUjvLP2tqkA/avxHEtzxNaqGq/YGNwckKg==} + engines: {node: '>=18'} + + cjs-module-lexer@2.3.0: + resolution: {integrity: sha512-lmNyBzi6iYiqyrExTctfmjd8tInx+dHu9PBomTfH5RXS53c27JMFJgzNv5TyAnm3PgGf+MLy0+Ws41/HlVxdKQ==} + + cli-width@4.1.0: + resolution: {integrity: sha512-ouuZd4/dm2Sw5Gmqy6bGyNNNe1qt9RpmxveLSO7KcgsTnU7RXfsw+/bukWGo1abgBiMAic068rclZsO4IWmmxQ==} + engines: {node: '>= 12'} + + cliui@8.0.1: + resolution: {integrity: sha512-BSeNnyus75C4//NQ9gQt1/csTXyo/8Sb+afLAkzAptFuMsod9HFokGNudZpi/oQV73hnVK+sR+5PVRMd+Dr7YQ==} + engines: {node: '>=12'} + + color-convert@2.0.1: + resolution: {integrity: sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==} + engines: {node: '>=7.0.0'} + + color-name@1.1.4: + resolution: {integrity: sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==} + + convert-source-map@2.0.0: + resolution: {integrity: sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==} + + cookie@1.1.1: + resolution: {integrity: sha512-ei8Aos7ja0weRpFzJnEA9UHJ/7XQmqglbRwnf2ATjcB9Wq874VKH9kfjjirM6UhU2/E5fFYadylyhFldcqSidQ==} + engines: {node: '>=18'} + + debug@4.4.3: + resolution: {integrity: sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==} + engines: {node: '>=6.0'} + peerDependencies: + supports-color: '*' + peerDependenciesMeta: + supports-color: + optional: true + + 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'} + + emoji-regex@8.0.0: + resolution: {integrity: sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==} + + es-module-lexer@2.3.2: + resolution: {integrity: sha512-poHGpORABojJJucnV9KbOavETW8lBVnphkW77ER5/BQ5Fz7oXSoCNek7IH3vR5nRjdsEz926ibFYX8KtLQmdyw==} + + es-module-lexer@3.0.4: + resolution: {integrity: sha512-BldNY5mwkVu+cQ5Ymn3e6hB9Sx8XHx4ZevWbiUyZbfKxJxYO27Hf0wfzkxIaD5CAJMaGl3YspKELj0ff/7nCMA==} + + escalade@3.2.0: + resolution: {integrity: sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==} + engines: {node: '>=6'} + + esquery@1.7.0: + resolution: {integrity: sha512-Ap6G0WQwcU/LHsvLwON1fAQX9Zp0A2Y6Y/cJBl9r/JbW90Zyg4/zbG6zzKa2OTALELarYHmKu0GhpM5EO+7T0g==} + engines: {node: '>=0.10'} + + estraverse@5.3.0: + resolution: {integrity: sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==} + engines: {node: '>=4.0'} + + estree-walker@3.0.3: + resolution: {integrity: sha512-7RUKfXgSMMkzt6ZuXmqapOurLGPPfgj6l9uRZ7lRGolvk0y2yocc35LdcxKC5PQZdn2DMqioAQ2NoWcrTKmm6g==} + + eventsource-parser@1.1.2: + resolution: {integrity: sha512-v0eOBUbiaFojBu2s2NPBfYUoRR9GjcDNvCXVaqEf5vVfpIAh9f8RCo4vXTP8c63QRKCFwoLpMpTdPwwhEKVgzA==} + engines: {node: '>=14.18'} + + expect-type@1.4.0: + resolution: {integrity: sha512-KfYbmpRm0VbLjEvVa9yGwCi9GI34xvi7A/HXYWQO65CSD2u3MczUJSuwXKFIxlGsgBQizV9q5J9NHj4VG0n+pA==} + engines: {node: '>=12.0.0'} + + fast-deep-equal@3.1.3: + resolution: {integrity: sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==} + + fast-string-truncated-width@3.0.3: + resolution: {integrity: sha512-0jjjIEL6+0jag3l2XWWizO64/aZVtpiGE3t0Zgqxv0DPuxiMjvB3M24fCyhZUO4KomJQPj3LTSUnDP3GpdwC0g==} + + fast-string-width@3.0.2: + resolution: {integrity: sha512-gX8LrtNEI5hq8DVUfRQMbr5lpaS4nMIWV+7XEbXk2b8kiQIizgnlr12B4dA3ZEx3308ze0O4Q1R+cHts8kyUJg==} + + fast-uri@3.1.9: + resolution: {integrity: sha512-hS/0Wt3Ya043oKXs0sIxrMpKQITXhMLIrVAKKXi4gozGC9v7YRWVnXIZzjNPTO02M9BdxLCWqK1yRXgNziVyJQ==} + + fast-wrap-ansi@0.2.2: + resolution: {integrity: sha512-7F2Fl+TjRSenLqlU3UjSH0iyqopqoZIu7eZVpEirP2g1GtWa2G/ecEmBdgz31+Mxr+ELclgg6sokpSFIQiZ02Q==} + + fdir@6.5.0: + resolution: {integrity: sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg==} + engines: {node: '>=12.0.0'} + peerDependencies: + picomatch: ^3.0.2 + 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] + + get-caller-file@2.0.5: + resolution: {integrity: sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==} + engines: {node: 6.* || 8.* || >= 10.*} + + graphql@16.15.0: + resolution: {integrity: sha512-32kz9FecohDiC/tgcViwnRXM7HFBVTPiW7pHO+DikKOI5KjhU/jNULOReXsrFqt2WAYbE9xFXsTffJw6iTO8qw==} + engines: {node: ^12.22.0 || ^14.16.0 || ^16.0.0 || >=17.0.0} + + headers-polyfill@5.0.1: + resolution: {integrity: sha512-1TJ6Fih/b8h5TIcv+1+Hw0PDQWJTKDKzFZzcKOiW1wJza3XoAQlkCuXLbymPYB8+ZQyw8mHvdw560e8zVFIWyA==} + + import-in-the-middle@3.6.0: + resolution: {integrity: sha512-xRU7hI24S2FhucXtuZXn8EpcOjfVP6iiEQyR0USayPeaOroEmSsNlqnl95866iRdRtkHrd6Q/Z4D14R60au0+A==} + engines: {node: '>=18'} + + is-fullwidth-code-point@3.0.0: + resolution: {integrity: sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==} + engines: {node: '>=8'} + + is-node-process@1.2.0: + resolution: {integrity: sha512-Vg4o6/fqPxIjtxgUH5QLJhwZ7gW5diGCVlXpuUfELC62CuxM1iHcRe51f2W1FDy04Ai4KJkagKjx3XaqyfRKXw==} + + js-tokens@4.0.0: + resolution: {integrity: sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==} + + json-schema-traverse@1.0.0: + resolution: {integrity: sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==} + + 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'} + + 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==} + + lz-string@1.5.0: + resolution: {integrity: sha512-h5bgJWpxJNswbU7qCrV0tIKQCaS3blPDrqKWx+QxzuzL1zGUzij9XCWLrSLsJPu5t+eWA/ycetzYAO5IOMcWAQ==} + hasBin: true + + magic-string@1.4.5: + resolution: {integrity: sha512-oLLC2+YvQUfAa+vxoufE/yc1icb87M9iXIRqAoR/2EhE/8cb3SF/+Gi0c2By1B+5Vz2e2vQtUvHiu1+63Znh+w==} + + meriyah@6.1.4: + resolution: {integrity: sha512-Sz8FzjzI0kN13GK/6MVEsVzMZEPvOhnmmI1lU5+/1cGOiK3QUahntrNNtdVeihrO7t9JpoH75iMNXg6R6uWflQ==} + engines: {node: '>=18.0.0'} + + 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==} + + msw@2.15.0: + resolution: {integrity: sha512-2wQAmKkQKxRuXvYJxVhPGG0wZNBQyD06oJvxqw90XqLvptdqxdlHrFUfEteKkpaNORX3Xzc+HtEl/q0nfmN2wQ==} + engines: {node: '>=18'} + hasBin: true + peerDependencies: + typescript: '>= 4.8.x' + peerDependenciesMeta: + typescript: + optional: true + + mustache@4.2.0: + resolution: {integrity: sha512-71ippSywq5Yb7/tVYyGbkBggbU8H3u5Rz56fH60jGFgr8uHwxs+aSKeqmluIVzM0m0kB7xQjKS6qPfd0b2ZoqQ==} + hasBin: true + + mute-stream@3.0.0: + resolution: {integrity: sha512-dkEJPVvun4FryqBmZ5KhDo0K9iDXAwn08tMLDinNdRBNPcYEDiWYysLcc6k3mjTMlbP9KyylvRpd4wFtwrT9rw==} + engines: {node: ^20.17.0 || >=22.9.0} + + 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'} + + outvariant@1.4.3: + resolution: {integrity: sha512-+Sl2UErvtsoajRDKCE5/dBz4DIvHXQQnAxtQTF04OJxY0+DyZXSo5P5Bb7XYWOh81syohlYL24hbDwxedPUJCA==} + + 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-to-regexp@6.3.0: + resolution: {integrity: sha512-Yhpw4T9C6hPpgPeA28us07OJeqZ5EzQTkbfwuhsUg0c237RomFoETJgmp2sa3F/41gfLE6G5cqcYwznmeEeOlQ==} + + 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.32: + resolution: {integrity: sha512-LJlKCCAUFKOuZxipUdYw1NzIITEsPcgyOG9LkvRJvuKj/bRzbuRY0ARXvL0pWGpsZvn8jku+LMep2eU0seHp8g==} + 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.6: + resolution: {integrity: sha512-dYDWdjSl5RNb7SgPxGQcRU+GtvP7s2fpkrY0r432PcOIaZ0/rBcxEZnQN67iJhFuQiVw754JDoPruPCNdGsbjg==} + engines: {node: '>=12.0.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==} + engines: {node: '>=0.10.0'} + + require-from-string@2.0.2: + resolution: {integrity: sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw==} + engines: {node: '>=0.10.0'} + + require-in-the-middle@8.0.1: + resolution: {integrity: sha512-QT7FVMXfWOYFbeRBF6nu+I6tr2Tf3u0q8RIEjNob/heKY/nh7drD/k7eeMFmSQgnTtCzLDcCu/XEnpW2wk4xCQ==} + engines: {node: '>=9.3.0 || >=8.10.0 <9.0.0'} + + rettime@0.11.12: + resolution: {integrity: sha512-5ExtR1ppiDBQkVoPw5NAqdbfH4Iq2VOmQKHMtUoaFxA9bo2krzFYBtyq6K40xxbYp+2w/YOr6D+1ToF6tTN53A==} + + semifies@1.0.0: + resolution: {integrity: sha512-xXR3KGeoxTNWPD4aBvL5NUpMTT7WMANr3EWnaS190QVkY52lqqcVRD7Q05UVbBhiWDGWMlJEUam9m7uFFGVScw==} + + set-cookie-parser@3.2.2: + resolution: {integrity: sha512-df017qMeYuXo4emL09nlyMpi2i2MtEIKS/6jtXH3vmjFgIRsOi852/5RmhJ4EkvN3ZF8jXadM/EkTPXgc8yPIw==} + + 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'} + + source-map@0.7.6: + resolution: {integrity: sha512-i5uvt8C3ikiWeNZSVZNWcfZPItFQOsYTUAOkcUPGd8DqDy1uOUikjt5dG+uRlwyvR108Fb9DOd4GvXfT0N2/uQ==} + engines: {node: '>= 12'} + + statuses@2.0.2: + resolution: {integrity: sha512-DvEy55V3DB7uknRo+4iOGT5fP1slR8wQohVdknigZPMpMstaKJQWhwiYBACJE3Ul2pTnATihhBYnRhZQHGBiRw==} + engines: {node: '>= 0.8'} + + std-env@4.3.0: + resolution: {integrity: sha512-OtU/EgQ1kIm5KwqQpBC6ZEMXrZRui11w8zgfTWp8cdO9B8OaPsbA8bTHO2P+HNo1VlUTGMVBwPhydu6poeXiag==} + + strict-event-emitter@0.5.1: + resolution: {integrity: sha512-vMgjE/GGEPEFnhFub6pa4FmJBRBVOLpIII2hvCZ8Kzb7K0hlHo7mQv6xYrBvCL2LtAIBwFUK8wvuJgTVSQ5MFQ==} + + string-width@4.2.3: + resolution: {integrity: sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==} + engines: {node: '>=8'} + + strip-ansi@6.0.1: + resolution: {integrity: sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==} + engines: {node: '>=8'} + + tagged-tag@1.0.0: + resolution: {integrity: sha512-yEFYrVhod+hdNyx7g5Bnkkb0G6si8HJurOoOEgC8B/O0uXLHlaey/65KRv6cuWBNhBgHKAROVpc7QyYqE5gFng==} + engines: {node: '>=20'} + + tinybench@6.2.3: + resolution: {integrity: sha512-Al01C2JIBnO2CXCAPbpAl6sUiOZHeThGtHYNTa51JRw9PyWOCdr+9Ong05R4qg/Sk3+H645E8ov1h2c58ityNw==} + 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'} + + tldts-core@7.4.20: + resolution: {integrity: sha512-C630TWXZCZmLq0F2bW4QqwnaaxCQ/svpAqvJwnC4JvgEM5ohhBnGsEzTLSOXcN+xjz+kEaHS5uoWIw2klnqd0A==} + + tldts@7.4.19: + resolution: {integrity: sha512-48dH6aSpQnpbzDUc4vIQZ1VjB8tkK+e8c1yx05/SZK+rkssgjEOlxDd/yU8/a4vGYQ9vRgJr9dwMq9TJ0b3X2Q==} + hasBin: true + + totalist@3.0.1: + resolution: {integrity: sha512-sf4i37nQ2LBx4m3wB74y+ubopq6W/dIzXg0FDGjsYnZHVa1Da8FH853wlL2gtUhg+xJXjfk3kUZS3BRoQeoQBQ==} + engines: {node: '>=6'} + + tough-cookie@6.1.0: + resolution: {integrity: sha512-FTxc8u8Kl2tz0Lat1u8ksvcy8bwHRyEMawwTWybo51JqaC6O7SvfuhAKg+Ld6VqyIe2QRJBzY9HA7y2WyFRT2w==} + engines: {node: '>=16'} + + type-fest@5.10.0: + resolution: {integrity: sha512-NoSdpq/WEiAg5sjmBkmV/hfxv6HJH4NqPNrqjtSO5CwRmpsDfaf4begxW34KdJykH/l1yHtwBWQkCRdoXO8mPA==} + engines: {node: '>=20'} + + typescript@5.5.4: + resolution: {integrity: sha512-Mtq29sKDAEYP7aljRgtPOpTvOfbwRWlS6dPRzwjdE+C0R4brX/GUyhHSecbHMFLNBLcJIPt9nl9yG5TZ1weH+Q==} + engines: {node: '>=14.17'} + hasBin: true + + undici-types@6.21.0: + resolution: {integrity: sha512-iwDZqg0QAGrg9Rav5H4n0M64c3mkR59cJ6wQp+7C4nI0gsmExaedaYLNO44eT4AtBBwjbTiGPMlt2Md0T9H9JQ==} + + unplugin@2.3.11: + resolution: {integrity: sha512-5uKD0nqiYVzlmCRs01Fhs2BdkEgBS3SAVP6ndrBsuK42iC2+JHyxM05Rm9G8+5mkmRtzMZGY8Ct5+mliZxU/Ww==} + engines: {node: '>=18.12.0'} + + until-async@3.0.2: + resolution: {integrity: sha512-IiSk4HlzAMqTUseHHe3VhIGyuFmN90zMTpD3Z3y8jeQbzLIq500MVM7Jq2vUAnTKAFPJrqwkzr6PoTcPhGcOiw==} + + uuid@11.1.1: + resolution: {integrity: sha512-vIYxrBCC/N/K+Js3qSN88go7kIfNPssr/hHCesKCQNAjmgvYS2oqr69kIufEG+O4+PfezOH4EbIeHCfFov8ZgQ==} + hasBin: true + + 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: ^8.0.5 + 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 + + webpack-virtual-modules@0.6.2: + resolution: {integrity: sha512-66/V2i5hQanC51vBQKPH4aI8NMAcBW59FVBs+rC7eGHupMyfn34q7rZIE+ETlJ+XTevqfUhVVBgSUNSW2flEUQ==} + + 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'} + + ws@8.22.2: + resolution: {integrity: sha512-/rEZ2ejown2N5gxVTHUu9bVVC7ttu0/Tgzf5hbTOixR3Fs7mbbXgn2DbdbcKgJOmpQMtrIUWivtYuVMnp/W/yA==} + 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==} + engines: {node: '>=10'} + + yaml@2.9.1: + resolution: {integrity: sha512-3NxN8+78OdzbT7C/WjGsyfPAtJaN3FNDsWxv7Y7mcDsT/oOmgW8BpyQQFFBnvZE3j9Y2Sdz1ULFLezL7Eb2yFw==} + engines: {node: '>= 14.6'} + hasBin: true + + yargs-parser@21.1.1: + resolution: {integrity: sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw==} + engines: {node: '>=12'} + + yargs@17.7.3: + resolution: {integrity: sha512-GZtjxm/J/4TSxuL3FNYjCmLktBTnIw/rVmKSIyKeYAZpmJB2ig9VauCC5xsa82GNKVKDAqpOn3KVzNt0zmrU0g==} + 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: + zod: ^3.25.28 || ^4 + + zod@4.6.7: + resolution: {integrity: sha512-g8W2D2kU6a8untc053fX1uVBKzM7yqAxW/3zwk83+6RyF2+SKOuV+c0wBDhUwzV1PaGapmZH+rmcUzPf+3CGwg==} + +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.13': {} + + '@blazediff/core@1.10.0': {} + + '@braintrust/otel@file:..(@opentelemetry/api@1.10.0)(braintrust@file:../../../js(zod@4.6.7))': + dependencies: + '@opentelemetry/api': 1.10.0 + braintrust: file:../../../js(zod@4.6.7) + + '@grpc/grpc-js@1.14.7': + dependencies: + '@grpc/proto-loader': 0.8.1 + '@js-sdsl/ordered-map': 4.4.2 + + '@grpc/proto-loader@0.8.1': + dependencies: + lodash.camelcase: 4.3.0 + long: 5.3.2 + protobufjs: 7.6.6 + yargs: 17.7.3 + + '@inquirer/ansi@2.0.10': {} + + '@inquirer/confirm@6.3.5(@types/node@22.20.5)': + dependencies: + '@inquirer/core': 12.0.6(@types/node@22.20.5) + '@inquirer/type': 4.2.1(@types/node@22.20.5) + optionalDependencies: + '@types/node': 22.20.5 + + '@inquirer/core@12.0.6(@types/node@22.20.5)': + dependencies: + '@inquirer/ansi': 2.0.10 + '@inquirer/figures': 2.0.10 + '@inquirer/type': 4.2.1(@types/node@22.20.5) + cli-width: 4.1.0 + fast-wrap-ansi: 0.2.2 + mute-stream: 3.0.0 + signal-exit: 4.1.0 + optionalDependencies: + '@types/node': 22.20.5 + + '@inquirer/figures@2.0.10': {} + + '@inquirer/type@4.2.1(@types/node@22.20.5)': + optionalDependencies: + '@types/node': 22.20.5 + + '@jridgewell/gen-mapping@0.3.13': + dependencies: + '@jridgewell/sourcemap-codec': 1.6.0 + '@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.6.0': {} + + '@jridgewell/trace-mapping@0.3.31': + dependencies: + '@jridgewell/resolve-uri': 3.1.2 + '@jridgewell/sourcemap-codec': 1.6.0 + + '@js-sdsl/ordered-map@4.4.2': {} + + '@mswjs/interceptors@0.41.9': + dependencies: + '@open-draft/deferred-promise': 2.2.0 + '@open-draft/logger': 0.3.0 + '@open-draft/until': 2.1.0 + is-node-process: 1.2.0 + outvariant: 1.4.3 + strict-event-emitter: 0.5.1 + + '@open-draft/deferred-promise@2.2.0': {} + + '@open-draft/deferred-promise@3.0.0': {} + + '@open-draft/logger@0.3.0': + dependencies: + is-node-process: 1.2.0 + outvariant: 1.4.3 + + '@open-draft/until@2.1.0': {} + + '@opentelemetry/api@1.10.0': {} + + '@opentelemetry/configuration@0.301.0(@opentelemetry/api@1.10.0)': + dependencies: + '@opentelemetry/api': 1.10.0 + '@opentelemetry/core': 3.1.0(@opentelemetry/api@1.10.0) + yaml: 2.9.1 + + '@opentelemetry/context-async-hooks@3.1.0(@opentelemetry/api@1.10.0)': + dependencies: + '@opentelemetry/api': 1.10.0 + + '@opentelemetry/core@3.1.0(@opentelemetry/api@1.10.0)': + dependencies: + '@opentelemetry/api': 1.10.0 + '@opentelemetry/semantic-conventions': 1.45.0 + + '@opentelemetry/exporter-logs-otlp-grpc@0.301.0(@opentelemetry/api@1.10.0)': + dependencies: + '@opentelemetry/api': 1.10.0 + '@opentelemetry/otlp-exporter-base': 0.301.0(@opentelemetry/api@1.10.0) + '@opentelemetry/otlp-grpc-exporter-base': 0.301.0(@opentelemetry/api@1.10.0) + '@opentelemetry/otlp-transformer': 0.301.0(@opentelemetry/api@1.10.0) + '@opentelemetry/sdk-logs': 3.1.0(@opentelemetry/api@1.10.0) + + '@opentelemetry/exporter-logs-otlp-http@0.301.0(@opentelemetry/api@1.10.0)': + dependencies: + '@opentelemetry/api': 1.10.0 + '@opentelemetry/otlp-exporter-base': 0.301.0(@opentelemetry/api@1.10.0) + '@opentelemetry/otlp-transformer': 0.301.0(@opentelemetry/api@1.10.0) + '@opentelemetry/sdk-logs': 3.1.0(@opentelemetry/api@1.10.0) + + '@opentelemetry/exporter-logs-otlp-proto@0.301.0(@opentelemetry/api@1.10.0)': + dependencies: + '@opentelemetry/api': 1.10.0 + '@opentelemetry/otlp-exporter-base': 0.301.0(@opentelemetry/api@1.10.0) + '@opentelemetry/otlp-transformer': 0.301.0(@opentelemetry/api@1.10.0) + '@opentelemetry/sdk-logs': 3.1.0(@opentelemetry/api@1.10.0) + + '@opentelemetry/exporter-metrics-otlp-grpc@0.301.0(@opentelemetry/api@1.10.0)': + dependencies: + '@opentelemetry/api': 1.10.0 + '@opentelemetry/exporter-metrics-otlp-http': 0.301.0(@opentelemetry/api@1.10.0) + '@opentelemetry/otlp-grpc-exporter-base': 0.301.0(@opentelemetry/api@1.10.0) + '@opentelemetry/otlp-transformer': 0.301.0(@opentelemetry/api@1.10.0) + + '@opentelemetry/exporter-metrics-otlp-http@0.301.0(@opentelemetry/api@1.10.0)': + dependencies: + '@opentelemetry/api': 1.10.0 + '@opentelemetry/core': 3.1.0(@opentelemetry/api@1.10.0) + '@opentelemetry/otlp-exporter-base': 0.301.0(@opentelemetry/api@1.10.0) + '@opentelemetry/otlp-transformer': 0.301.0(@opentelemetry/api@1.10.0) + '@opentelemetry/sdk-metrics': 3.1.0(@opentelemetry/api@1.10.0) + + '@opentelemetry/exporter-metrics-otlp-proto@0.301.0(@opentelemetry/api@1.10.0)': + dependencies: + '@opentelemetry/api': 1.10.0 + '@opentelemetry/exporter-metrics-otlp-http': 0.301.0(@opentelemetry/api@1.10.0) + '@opentelemetry/otlp-exporter-base': 0.301.0(@opentelemetry/api@1.10.0) + '@opentelemetry/otlp-transformer': 0.301.0(@opentelemetry/api@1.10.0) + + '@opentelemetry/exporter-prometheus@0.301.0(@opentelemetry/api@1.10.0)': + dependencies: + '@opentelemetry/api': 1.10.0 + '@opentelemetry/core': 3.1.0(@opentelemetry/api@1.10.0) + '@opentelemetry/resources': 3.1.0(@opentelemetry/api@1.10.0) + '@opentelemetry/sdk-metrics': 3.1.0(@opentelemetry/api@1.10.0) + '@opentelemetry/semantic-conventions': 1.45.0 + + '@opentelemetry/exporter-trace-otlp-grpc@0.301.0(@opentelemetry/api@1.10.0)': + dependencies: + '@opentelemetry/api': 1.10.0 + '@opentelemetry/otlp-exporter-base': 0.301.0(@opentelemetry/api@1.10.0) + '@opentelemetry/otlp-grpc-exporter-base': 0.301.0(@opentelemetry/api@1.10.0) + '@opentelemetry/otlp-transformer': 0.301.0(@opentelemetry/api@1.10.0) + '@opentelemetry/sdk-trace': 3.1.0(@opentelemetry/api@1.10.0) + + '@opentelemetry/exporter-trace-otlp-http@0.301.0(@opentelemetry/api@1.10.0)': + dependencies: + '@opentelemetry/api': 1.10.0 + '@opentelemetry/otlp-exporter-base': 0.301.0(@opentelemetry/api@1.10.0) + '@opentelemetry/otlp-transformer': 0.301.0(@opentelemetry/api@1.10.0) + '@opentelemetry/sdk-trace': 3.1.0(@opentelemetry/api@1.10.0) + + '@opentelemetry/exporter-trace-otlp-proto@0.301.0(@opentelemetry/api@1.10.0)': + dependencies: + '@opentelemetry/api': 1.10.0 + '@opentelemetry/otlp-exporter-base': 0.301.0(@opentelemetry/api@1.10.0) + '@opentelemetry/otlp-transformer': 0.301.0(@opentelemetry/api@1.10.0) + '@opentelemetry/sdk-trace': 3.1.0(@opentelemetry/api@1.10.0) + + '@opentelemetry/exporter-zipkin@3.1.0(@opentelemetry/api@1.10.0)': + dependencies: + '@opentelemetry/api': 1.10.0 + '@opentelemetry/core': 3.1.0(@opentelemetry/api@1.10.0) + '@opentelemetry/sdk-trace': 3.1.0(@opentelemetry/api@1.10.0) + '@opentelemetry/semantic-conventions': 1.45.0 + + '@opentelemetry/instrumentation@0.301.0(@opentelemetry/api@1.10.0)': + dependencies: + '@opentelemetry/api': 1.10.0 + import-in-the-middle: 3.6.0 + require-in-the-middle: 8.0.1 + transitivePeerDependencies: + - supports-color + + '@opentelemetry/otlp-exporter-base@0.301.0(@opentelemetry/api@1.10.0)': + dependencies: + '@opentelemetry/api': 1.10.0 + '@opentelemetry/core': 3.1.0(@opentelemetry/api@1.10.0) + '@opentelemetry/otlp-transformer': 0.301.0(@opentelemetry/api@1.10.0) + + '@opentelemetry/otlp-grpc-exporter-base@0.301.0(@opentelemetry/api@1.10.0)': + dependencies: + '@grpc/grpc-js': 1.14.7 + '@opentelemetry/api': 1.10.0 + '@opentelemetry/core': 3.1.0(@opentelemetry/api@1.10.0) + '@opentelemetry/otlp-exporter-base': 0.301.0(@opentelemetry/api@1.10.0) + '@opentelemetry/otlp-transformer': 0.301.0(@opentelemetry/api@1.10.0) + + '@opentelemetry/otlp-transformer@0.301.0(@opentelemetry/api@1.10.0)': + dependencies: + '@opentelemetry/api': 1.10.0 + '@opentelemetry/core': 3.1.0(@opentelemetry/api@1.10.0) + '@opentelemetry/resources': 3.1.0(@opentelemetry/api@1.10.0) + '@opentelemetry/sdk-logs': 3.1.0(@opentelemetry/api@1.10.0) + '@opentelemetry/sdk-metrics': 3.1.0(@opentelemetry/api@1.10.0) + '@opentelemetry/sdk-trace': 3.1.0(@opentelemetry/api@1.10.0) + + '@opentelemetry/propagator-b3@3.1.0(@opentelemetry/api@1.10.0)': + dependencies: + '@opentelemetry/api': 1.10.0 + '@opentelemetry/core': 3.1.0(@opentelemetry/api@1.10.0) + + '@opentelemetry/resources@3.1.0(@opentelemetry/api@1.10.0)': + dependencies: + '@opentelemetry/api': 1.10.0 + '@opentelemetry/core': 3.1.0(@opentelemetry/api@1.10.0) + '@opentelemetry/semantic-conventions': 1.45.0 + + '@opentelemetry/sdk-logs@3.1.0(@opentelemetry/api@1.10.0)': + dependencies: + '@opentelemetry/api': 1.10.0 + '@opentelemetry/core': 3.1.0(@opentelemetry/api@1.10.0) + '@opentelemetry/resources': 3.1.0(@opentelemetry/api@1.10.0) + '@opentelemetry/semantic-conventions': 1.45.0 + + '@opentelemetry/sdk-metrics@3.1.0(@opentelemetry/api@1.10.0)': + dependencies: + '@opentelemetry/api': 1.10.0 + '@opentelemetry/core': 3.1.0(@opentelemetry/api@1.10.0) + '@opentelemetry/resources': 3.1.0(@opentelemetry/api@1.10.0) + + '@opentelemetry/sdk-node@0.301.0(@opentelemetry/api@1.10.0)': + dependencies: + '@opentelemetry/api': 1.10.0 + '@opentelemetry/configuration': 0.301.0(@opentelemetry/api@1.10.0) + '@opentelemetry/context-async-hooks': 3.1.0(@opentelemetry/api@1.10.0) + '@opentelemetry/core': 3.1.0(@opentelemetry/api@1.10.0) + '@opentelemetry/exporter-logs-otlp-grpc': 0.301.0(@opentelemetry/api@1.10.0) + '@opentelemetry/exporter-logs-otlp-http': 0.301.0(@opentelemetry/api@1.10.0) + '@opentelemetry/exporter-logs-otlp-proto': 0.301.0(@opentelemetry/api@1.10.0) + '@opentelemetry/exporter-metrics-otlp-grpc': 0.301.0(@opentelemetry/api@1.10.0) + '@opentelemetry/exporter-metrics-otlp-http': 0.301.0(@opentelemetry/api@1.10.0) + '@opentelemetry/exporter-metrics-otlp-proto': 0.301.0(@opentelemetry/api@1.10.0) + '@opentelemetry/exporter-prometheus': 0.301.0(@opentelemetry/api@1.10.0) + '@opentelemetry/exporter-trace-otlp-grpc': 0.301.0(@opentelemetry/api@1.10.0) + '@opentelemetry/exporter-trace-otlp-http': 0.301.0(@opentelemetry/api@1.10.0) + '@opentelemetry/exporter-trace-otlp-proto': 0.301.0(@opentelemetry/api@1.10.0) + '@opentelemetry/exporter-zipkin': 3.1.0(@opentelemetry/api@1.10.0) + '@opentelemetry/instrumentation': 0.301.0(@opentelemetry/api@1.10.0) + '@opentelemetry/otlp-exporter-base': 0.301.0(@opentelemetry/api@1.10.0) + '@opentelemetry/otlp-grpc-exporter-base': 0.301.0(@opentelemetry/api@1.10.0) + '@opentelemetry/propagator-b3': 3.1.0(@opentelemetry/api@1.10.0) + '@opentelemetry/resources': 3.1.0(@opentelemetry/api@1.10.0) + '@opentelemetry/sdk-logs': 3.1.0(@opentelemetry/api@1.10.0) + '@opentelemetry/sdk-metrics': 3.1.0(@opentelemetry/api@1.10.0) + '@opentelemetry/sdk-trace': 3.1.0(@opentelemetry/api@1.10.0) + '@opentelemetry/semantic-conventions': 1.45.0 + transitivePeerDependencies: + - supports-color + + '@opentelemetry/sdk-trace@3.1.0(@opentelemetry/api@1.10.0)': + dependencies: + '@opentelemetry/api': 1.10.0 + '@opentelemetry/core': 3.1.0(@opentelemetry/api@1.10.0) + '@opentelemetry/resources': 3.1.0(@opentelemetry/api@1.10.0) + '@opentelemetry/semantic-conventions': 1.45.0 + + '@opentelemetry/semantic-conventions@1.45.0': {} + + '@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 + + '@oxfmt/binding-win32-x64-msvc@0.72.0': + optional: true + + '@oxlint-tsgolint/darwin-arm64@7.0.2003': + optional: true + + '@oxlint-tsgolint/darwin-x64@7.0.2003': + optional: true + + '@oxlint-tsgolint/linux-arm64@7.0.2003': + optional: true + + '@oxlint-tsgolint/linux-x64@7.0.2003': + optional: true + + '@oxlint-tsgolint/win32-arm64@7.0.2003': + optional: true + + '@oxlint-tsgolint/win32-x64@7.0.2003': + optional: true + + '@oxlint/binding-android-arm-eabi@1.87.0': + optional: true + + '@oxlint/binding-android-arm64@1.87.0': + optional: true + + '@oxlint/binding-darwin-arm64@1.87.0': + optional: true + + '@oxlint/binding-darwin-x64@1.87.0': + optional: true + + '@oxlint/binding-freebsd-x64@1.87.0': + optional: true + + '@oxlint/binding-linux-arm-gnueabihf@1.87.0': + optional: true + + '@oxlint/binding-linux-arm-musleabihf@1.87.0': + optional: true + + '@oxlint/binding-linux-arm64-gnu@1.87.0': + optional: true + + '@oxlint/binding-linux-arm64-musl@1.87.0': + optional: true + + '@oxlint/binding-linux-ppc64-gnu@1.87.0': + optional: true + + '@oxlint/binding-linux-riscv64-gnu@1.87.0': + optional: true + + '@oxlint/binding-linux-riscv64-musl@1.87.0': + optional: true + + '@oxlint/binding-linux-s390x-gnu@1.87.0': + optional: true + + '@oxlint/binding-linux-x64-gnu@1.87.0': + optional: true + + '@oxlint/binding-linux-x64-musl@1.87.0': + optional: true + + '@oxlint/binding-openharmony-arm64@1.87.0': + optional: true + + '@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': {} + + '@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.2': {} + + '@testing-library/dom@10.4.2': + dependencies: + '@babel/code-frame': 7.29.7 + '@babel/runtime': 7.29.13 + '@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.10': {} + + '@types/node@22.20.5': + dependencies: + undici-types: 6.21.0 + + '@types/set-cookie-parser@2.4.10': + dependencies: + '@types/node': 22.20.5 + + '@types/statuses@2.0.6': {} + + '@vercel/functions@1.6.0': {} + + '@vitest/browser-preview@5.0.3(@voidzero-dev/vite-plus-core@1.1.0(@types/node@22.20.5)(typescript@5.5.4)(yaml@2.9.1))(msw@2.15.0(@types/node@22.20.5)(typescript@5.5.4))(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@22.20.5)(typescript@5.5.4)(yaml@2.9.1))(msw@2.15.0(@types/node@22.20.5)(typescript@5.5.4))(vitest@5.0.3) + vitest: 5.0.3(@opentelemetry/api@1.10.0)(@types/node@22.20.5)(@vitest/browser-preview@5.0.3)(@vitest/ui@5.0.3)(@voidzero-dev/vite-plus-core@1.1.0(@types/node@22.20.5)(typescript@5.5.4)(yaml@2.9.1))(msw@2.15.0(@types/node@22.20.5)(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.20.5)(typescript@5.5.4)(yaml@2.9.1))(msw@2.15.0(@types/node@22.20.5)(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.20.5)(typescript@5.5.4)(yaml@2.9.1))(msw@2.15.0(@types/node@22.20.5)(typescript@5.5.4)) + '@vitest/ui': 5.0.3(vitest@5.0.3) + '@vitest/utils': 5.0.3 + magic-string: 1.4.5 + pngjs: 7.0.0 + sirv: 3.0.2 + tinyrainbow: 3.2.0 + vitest: 5.0.3(@opentelemetry/api@1.10.0)(@types/node@22.20.5)(@vitest/browser-preview@5.0.3)(@vitest/ui@5.0.3)(@voidzero-dev/vite-plus-core@1.1.0(@types/node@22.20.5)(typescript@5.5.4)(yaml@2.9.1))(msw@2.15.0(@types/node@22.20.5)(typescript@5.5.4)) + ws: 8.22.2 + transitivePeerDependencies: + - bufferutil + - msw + - utf-8-validate + - vite + + '@vitest/mocker@5.0.3(@voidzero-dev/vite-plus-core@1.1.0(@types/node@22.20.5)(typescript@5.5.4)(yaml@2.9.1))(msw@2.15.0(@types/node@22.20.5)(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.5 + optionalDependencies: + msw: 2.15.0(@types/node@22.20.5)(typescript@5.5.4) + vite: '@voidzero-dev/vite-plus-core@1.1.0(@types/node@22.20.5)(typescript@5.5.4)(yaml@2.9.1)' + + '@vitest/pretty-format@5.0.3': + dependencies: + tinyrainbow: 3.2.0 + + '@vitest/snapshot@5.0.3': + dependencies: + '@vitest/pretty-format': 5.0.3 + '@vitest/utils': 5.0.3 + magic-string: 1.4.5 + 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.2.0 + vitest: 5.0.3(@opentelemetry/api@1.10.0)(@types/node@22.20.5)(@vitest/browser-preview@5.0.3)(@vitest/ui@5.0.3)(@voidzero-dev/vite-plus-core@1.1.0(@types/node@22.20.5)(typescript@5.5.4)(yaml@2.9.1))(msw@2.15.0(@types/node@22.20.5)(typescript@5.5.4)) + + '@vitest/utils@5.0.3': + dependencies: + '@vitest/pretty-format': 5.0.3 + convert-source-map: 2.0.0 + tinyrainbow: 3.2.0 + + '@voidzero-dev/vite-plus-core@1.1.0(@types/node@22.20.5)(typescript@5.5.4)(yaml@2.9.1)': + dependencies: + '@oxc-project/runtime': 0.153.0 + '@oxc-project/types': 0.153.0 + lightningcss: 1.33.0 + postcss: 8.5.32 + yuku-codegen: 0.9.5 + yuku-parser: 0.9.5 + optionalDependencies: + '@types/node': 22.20.5 + '@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 + fsevents: 2.3.3 + typescript: 5.5.4 + yaml: 2.9.1 + + '@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': {} + + acorn-import-attributes@1.9.5(acorn@8.19.0): + dependencies: + acorn: 8.19.0 + + acorn@8.19.0: {} + + ajv@8.20.0: + dependencies: + fast-deep-equal: 3.1.3 + fast-uri: 3.1.9 + json-schema-traverse: 1.0.0 + require-from-string: 2.0.2 + + ansi-regex@5.0.1: {} + + ansi-styles@4.3.0: + dependencies: + color-convert: 2.0.1 + + ansi-styles@5.2.0: {} + + aria-query@5.3.0: + dependencies: + dequal: 2.0.3 + + assertion-error@2.0.1: {} + + astring@1.9.0: {} + + braintrust@file:../../../js(zod@4.6.7): + dependencies: + '@vercel/functions': 1.6.0 + acorn: 8.19.0 + acorn-import-attributes: 1.9.5(acorn@8.19.0) + ajv: 8.20.0 + astring: 1.9.0 + cjs-module-lexer: 2.3.0 + dotenv: 16.6.1 + esquery: 1.7.0 + eventsource-parser: 1.1.2 + meriyah: 6.1.4 + module-details-from-path: 1.0.4 + mustache: 4.2.0 + semifies: 1.0.0 + source-map: 0.7.6 + unplugin: 2.3.11 + uuid: 11.1.1 + zod: 4.6.7 + zod-to-json-schema: 3.25.2(zod@4.6.7) + transitivePeerDependencies: + - '@aws-sdk/credential-provider-web-identity' + + chai@6.3.0: {} + + cjs-module-lexer@2.3.0: {} + + cli-width@4.1.0: {} + + cliui@8.0.1: + dependencies: + string-width: 4.2.3 + strip-ansi: 6.0.1 + wrap-ansi: 7.0.0 + + color-convert@2.0.1: + dependencies: + color-name: 1.1.4 + + color-name@1.1.4: {} + + convert-source-map@2.0.0: {} + + cookie@1.1.1: {} + + debug@4.4.3: + dependencies: + ms: 2.1.3 + + dequal@2.0.3: {} + + detect-libc@2.1.2: {} + + dom-accessibility-api@0.5.16: {} + + dotenv@16.6.1: {} + + emoji-regex@8.0.0: {} + + es-module-lexer@2.3.2: {} + + es-module-lexer@3.0.4: {} + + escalade@3.2.0: {} + + esquery@1.7.0: + dependencies: + estraverse: 5.3.0 + + estraverse@5.3.0: {} + + estree-walker@3.0.3: + dependencies: + '@types/estree': 1.0.10 + + eventsource-parser@1.1.2: {} + + expect-type@1.4.0: {} + + fast-deep-equal@3.1.3: {} + + fast-string-truncated-width@3.0.3: {} + + fast-string-width@3.0.2: + dependencies: + fast-string-truncated-width: 3.0.3 + + 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.7): + optionalDependencies: + picomatch: 4.0.7 + + fflate@0.8.3: {} + + flatted@3.4.4: {} + + fsevents@2.3.3: + optional: true + + get-caller-file@2.0.5: {} + + graphql@16.15.0: {} + + headers-polyfill@5.0.1: + dependencies: + '@types/set-cookie-parser': 2.4.10 + set-cookie-parser: 3.2.2 + + import-in-the-middle@3.6.0: + dependencies: + cjs-module-lexer: 2.3.0 + es-module-lexer: 3.0.4 + module-details-from-path: 1.0.4 + + is-fullwidth-code-point@3.0.0: {} + + is-node-process@1.2.0: {} + + js-tokens@4.0.0: {} + + json-schema-traverse@1.0.0: {} + + lightningcss-android-arm64@1.33.0: + optional: true + + lightningcss-darwin-arm64@1.33.0: + optional: true + + lightningcss-darwin-x64@1.33.0: + optional: true + + lightningcss-freebsd-x64@1.33.0: + optional: true + + lightningcss-linux-arm-gnueabihf@1.33.0: + optional: true + + lightningcss-linux-arm64-gnu@1.33.0: + optional: true + + lightningcss-linux-arm64-musl@1.33.0: + optional: true + + lightningcss-linux-x64-gnu@1.33.0: + optional: true + + lightningcss-linux-x64-musl@1.33.0: + optional: true + + lightningcss-win32-arm64-msvc@1.33.0: + optional: true + + lightningcss-win32-x64-msvc@1.33.0: + optional: true + + lightningcss@1.33.0: + dependencies: + 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 + + lodash.camelcase@4.3.0: {} + + long@5.3.2: {} + + lz-string@1.5.0: {} + + magic-string@1.4.5: + dependencies: + '@jridgewell/sourcemap-codec': 1.6.0 + + meriyah@6.1.4: {} + + module-details-from-path@1.0.4: {} + + mrmime@2.0.1: {} + + ms@2.1.3: {} + + msw@2.15.0(@types/node@22.20.5)(typescript@5.5.4): + dependencies: + '@inquirer/confirm': 6.3.5(@types/node@22.20.5) + '@mswjs/interceptors': 0.41.9 + '@open-draft/deferred-promise': 3.0.0 + '@types/statuses': 2.0.6 + cookie: 1.1.1 + graphql: 16.15.0 + 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.12 + statuses: 2.0.2 + strict-event-emitter: 0.5.1 + tough-cookie: 6.1.0 + type-fest: 5.10.0 + until-async: 3.0.2 + yargs: 17.7.3 + optionalDependencies: + typescript: 5.5.4 + transitivePeerDependencies: + - '@types/node' + + mustache@4.2.0: {} + + mute-stream@3.0.0: {} + + nanoid@3.3.20: {} + + obug@2.2.1: {} + + outvariant@1.4.3: {} + + oxfmt@0.72.0(vite-plus@1.1.0(@opentelemetry/api@1.10.0)(@types/node@22.20.5)(@vitest/ui@5.0.3(vitest@5.0.3))(msw@2.15.0(@types/node@22.20.5)(typescript@5.5.4))(typescript@5.5.4)(yaml@2.9.1)): + 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.10.0)(@types/node@22.20.5)(@vitest/ui@5.0.3(vitest@5.0.3))(msw@2.15.0(@types/node@22.20.5)(typescript@5.5.4))(typescript@5.5.4)(yaml@2.9.1) + + 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.10.0)(@types/node@22.20.5)(@vitest/ui@5.0.3(vitest@5.0.3))(msw@2.15.0(@types/node@22.20.5)(typescript@5.5.4))(typescript@5.5.4)(yaml@2.9.1)): + 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.10.0)(@types/node@22.20.5)(@vitest/ui@5.0.3(vitest@5.0.3))(msw@2.15.0(@types/node@22.20.5)(typescript@5.5.4))(typescript@5.5.4)(yaml@2.9.1) + + path-to-regexp@6.3.0: {} + + pathe@2.0.3: {} + + picocolors@1.1.1: {} + + picomatch@4.0.7: {} + + pngjs@7.0.0: {} + + postcss@8.5.32: + dependencies: + nanoid: 3.3.20 + 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.6: + dependencies: + '@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 + '@protobufjs/float': 1.0.2 + '@protobufjs/path': 1.1.2 + '@protobufjs/pool': 1.1.0 + '@protobufjs/utf8': 1.1.2 + '@types/node': 22.20.5 + long: 5.3.2 + + react-is@17.0.2: {} + + require-directory@2.1.1: {} + + require-from-string@2.0.2: {} + + require-in-the-middle@8.0.1: + dependencies: + debug: 4.4.3 + module-details-from-path: 1.0.4 + transitivePeerDependencies: + - supports-color + + rettime@0.11.12: {} + + semifies@1.0.0: {} + + set-cookie-parser@3.2.2: {} + + signal-exit@4.1.0: {} + + sirv@3.0.2: + dependencies: + '@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: {} + + statuses@2.0.2: {} + + std-env@4.3.0: {} + + strict-event-emitter@0.5.1: {} + + string-width@4.2.3: + dependencies: + emoji-regex: 8.0.0 + is-fullwidth-code-point: 3.0.0 + strip-ansi: 6.0.1 + + strip-ansi@6.0.1: + dependencies: + ansi-regex: 5.0.1 + + tagged-tag@1.0.0: {} + + tinybench@6.2.3: {} + + tinyexec@1.3.1: {} + + tinyglobby@0.2.17: + dependencies: + fdir: 6.5.0(picomatch@4.0.7) + picomatch: 4.0.7 + + tinypool@2.2.0: {} + + tinyrainbow@3.2.0: {} + + tldts-core@7.4.20: {} + + tldts@7.4.19: + dependencies: + tldts-core: 7.4.20 + + totalist@3.0.1: {} + + tough-cookie@6.1.0: + dependencies: + tldts: 7.4.19 + + type-fest@5.10.0: + dependencies: + tagged-tag: 1.0.0 + + typescript@5.5.4: {} + + undici-types@6.21.0: {} + + unplugin@2.3.11: + dependencies: + '@jridgewell/remapping': 2.3.5 + acorn: 8.19.0 + picomatch: 4.0.7 + webpack-virtual-modules: 0.6.2 + + until-async@3.0.2: {} + + uuid@11.1.1: {} + + vite-plus@1.1.0(@opentelemetry/api@1.10.0)(@types/node@22.20.5)(@vitest/ui@5.0.3(vitest@5.0.3))(msw@2.15.0(@types/node@22.20.5)(typescript@5.5.4))(typescript@5.5.4)(yaml@2.9.1): + 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.20.5)(typescript@5.5.4)(yaml@2.9.1))(msw@2.15.0(@types/node@22.20.5)(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.20.5)(typescript@5.5.4)(yaml@2.9.1))(msw@2.15.0(@types/node@22.20.5)(typescript@5.5.4))(vitest@5.0.3) + '@vitest/mocker': 5.0.3(@voidzero-dev/vite-plus-core@1.1.0(@types/node@22.20.5)(typescript@5.5.4)(yaml@2.9.1))(msw@2.15.0(@types/node@22.20.5)(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.10.0)(@types/node@22.20.5)(@vitest/ui@5.0.3(vitest@5.0.3))(msw@2.15.0(@types/node@22.20.5)(typescript@5.5.4))(typescript@5.5.4)(yaml@2.9.1)) + oxlint: 1.87.0(oxlint-tsgolint@7.0.2003)(vite-plus@1.1.0(@opentelemetry/api@1.10.0)(@types/node@22.20.5)(@vitest/ui@5.0.3(vitest@5.0.3))(msw@2.15.0(@types/node@22.20.5)(typescript@5.5.4))(typescript@5.5.4)(yaml@2.9.1)) + oxlint-tsgolint: 7.0.2003 + vite: '@voidzero-dev/vite-plus-core@1.1.0(@types/node@22.20.5)(typescript@5.5.4)(yaml@2.9.1)' + vitest: 5.0.3(@opentelemetry/api@1.10.0)(@types/node@22.20.5)(@vitest/browser-preview@5.0.3)(@vitest/ui@5.0.3)(@voidzero-dev/vite-plus-core@1.1.0(@types/node@22.20.5)(typescript@5.5.4)(yaml@2.9.1))(msw@2.15.0(@types/node@22.20.5)(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 + + vitest@5.0.3(@opentelemetry/api@1.10.0)(@types/node@22.20.5)(@vitest/browser-preview@5.0.3)(@vitest/ui@5.0.3)(@voidzero-dev/vite-plus-core@1.1.0(@types/node@22.20.5)(typescript@5.5.4)(yaml@2.9.1))(msw@2.15.0(@types/node@22.20.5)(typescript@5.5.4)): + dependencies: + '@types/chai': 5.2.3 + '@vitest/mocker': 5.0.3(@voidzero-dev/vite-plus-core@1.1.0(@types/node@22.20.5)(typescript@5.5.4)(yaml@2.9.1))(msw@2.15.0(@types/node@22.20.5)(typescript@5.5.4)) + chai: 6.3.0 + es-module-lexer: 2.3.2 + expect-type: 1.4.0 + magic-string: 1.4.5 + obug: 2.2.1 + picomatch: 4.0.7 + std-env: 4.3.0 + tinybench: 6.2.3 + tinyexec: 1.3.1 + tinyglobby: 0.2.17 + vite: '@voidzero-dev/vite-plus-core@1.1.0(@types/node@22.20.5)(typescript@5.5.4)(yaml@2.9.1)' + why-is-node-running: 3.2.1 + optionalDependencies: + '@opentelemetry/api': 1.10.0 + '@types/node': 22.20.5 + '@vitest/browser-preview': 5.0.3(@voidzero-dev/vite-plus-core@1.1.0(@types/node@22.20.5)(typescript@5.5.4)(yaml@2.9.1))(msw@2.15.0(@types/node@22.20.5)(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@3.2.1: {} + + wrap-ansi@7.0.0: + dependencies: + ansi-styles: 4.3.0 + string-width: 4.2.3 + strip-ansi: 6.0.1 + + ws@8.22.2: {} + + y18n@5.0.8: {} + + yaml@2.9.1: {} + + yargs-parser@21.1.1: {} + + yargs@17.7.3: + dependencies: + cliui: 8.0.1 + escalade: 3.2.0 + get-caller-file: 2.0.5 + require-directory: 2.1.1 + string-width: 4.2.3 + 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.6.7): + dependencies: + zod: 4.6.7 + + zod@4.6.7: {} diff --git a/integrations/otel-js/otel-v3/tests/otel-v3.test.ts b/integrations/otel-js/otel-v3/tests/otel-v3.test.ts new file mode 100644 index 000000000..f99eeb4f1 --- /dev/null +++ b/integrations/otel-js/otel-v3/tests/otel-v3.test.ts @@ -0,0 +1,54 @@ +import { expect, test } from "vite-plus/test"; +import { context, trace } from "@opentelemetry/api"; +import { + TracerProvider, + BatchSpanProcessor, + InMemorySpanExporter, + type SpanExporter, + type SpanProcessor, +} from "@opentelemetry/sdk-trace"; +import { BraintrustExporter, BraintrustSpanProcessor } from "@braintrust/otel"; + +test("OTel 3 accepts the adapters and exports real parent and child spans", async () => { + const memory = new InMemorySpanExporter(); + const exporter = new BraintrustExporter({ + exporter: memory, + }) satisfies SpanExporter; + const batch = new BatchSpanProcessor({ exporter }); + const downstream: SpanProcessor = { + onStart: (span, parent) => batch.onStart(span, parent), + onEnding: (span) => span.setAttribute("ending-hook", true), + onEnd: (span) => batch.onEnd(span), + forceFlush: () => batch.forceFlush(), + shutdown: () => batch.shutdown(), + }; + const processor = new BraintrustSpanProcessor({ + processor: downstream, + }) satisfies SpanProcessor; + const provider = new TracerProvider({ spanProcessors: [processor] }); + try { + const tracer = provider.getTracer("compatibility-test", "1.0.0"); + const root = tracer.startSpan("root"); + const child = tracer.startSpan( + "child", + {}, + trace.setSpan(context.active(), root), + ); + child.end(); + root.end(); + await provider.forceFlush(); + const spans = memory.getFinishedSpans(); + expect(spans.map((span) => span.name)).toEqual(["child", "root"]); + expect(spans[0].spanContext().traceId).toBe(root.spanContext().traceId); + expect(spans[0].parentSpanContext?.spanId).toBe(root.spanContext().spanId); + expect(spans.every((span) => span.attributes["ending-hook"] === true)).toBe( + true, + ); + expect(spans[0].instrumentationScope.name).toBe("compatibility-test"); + expect( + JSON.parse(spans[0].attributes["braintrust.context_json"] as string), + ).toHaveProperty("span_origin.instrumentation.name", "braintrust-otel-js"); + } finally { + await provider.shutdown(); + } +}); diff --git a/integrations/otel-js/otel-v3/tsconfig.json b/integrations/otel-js/otel-v3/tsconfig.json new file mode 100644 index 000000000..79ffe5662 --- /dev/null +++ b/integrations/otel-js/otel-v3/tsconfig.json @@ -0,0 +1,7 @@ +{ + "extends": "../tsconfig.json", + "compilerOptions": { + "skipLibCheck": false + }, + "include": ["tests"] +} diff --git a/integrations/otel-js/otel-v3/vite.config.ts b/integrations/otel-js/otel-v3/vite.config.ts new file mode 100644 index 000000000..00aee0225 --- /dev/null +++ b/integrations/otel-js/otel-v3/vite.config.ts @@ -0,0 +1,2 @@ +import { defineConfig } from "vite-plus"; +export default defineConfig({ test: { include: ["tests/**/*.test.ts"] } }); diff --git a/integrations/otel-js/package.json b/integrations/otel-js/package.json index 26e06186b..8a231e479 100644 --- a/integrations/otel-js/package.json +++ b/integrations/otel-js/package.json @@ -17,10 +17,11 @@ "check:typings": "tsc --noEmit && tsc --noEmit -p tsconfig.test.json", "watch": "vp pack --watch", "clean": "rm -r dist/*", - "test": "pnpm test:v1 && pnpm test:v2", + "test": "pnpm test:v1 && pnpm test:v2 && pnpm test:v3", "test:v1": "cd otel-v1 && pnpm test", "test:v2": "cd otel-v2 && pnpm test", - "yalc:publish": "yalc publish" + "yalc:publish": "yalc publish", + "test:v3": "cd otel-v3 && pnpm test" }, "author": "Braintrust Data Inc.", "license": "MIT", @@ -29,31 +30,16 @@ "@types/node": "^22.15.21", "braintrust": "workspace:*", "typescript": "5.5.4", - "vite-plus": "catalog:" + "vite-plus": "catalog:", + "@opentelemetry/api": "1.9.0", + "@opentelemetry/core": "2.0.0", + "@opentelemetry/sdk-trace-base": "2.0.0", + "@opentelemetry/exporter-trace-otlp-http": "0.200.0", + "@opentelemetry/resources": "2.0.0" }, "peerDependencies": { - "@opentelemetry/api": ">=1.9.0", - "@opentelemetry/core": ">=1.9.0", - "@opentelemetry/exporter-trace-otlp-http": ">=0.35.0", - "@opentelemetry/sdk-trace-base": ">=1.9.0", - "braintrust": ">=1.0.0-0" - }, - "peerDependenciesMeta": { - "braintrust": { - "optional": false - }, - "@opentelemetry/api": { - "optional": false - }, - "@opentelemetry/exporter-trace-otlp-http": { - "optional": false - }, - "@opentelemetry/sdk-trace-base": { - "optional": false - }, - "@opentelemetry/core": { - "optional": false - } + "@opentelemetry/api": "^1.9.0", + "braintrust": "workspace:^" }, "repository": { "type": "git", @@ -70,5 +56,8 @@ "engines": { "node": ">=22.18.0" }, - "sideEffects": false + "sideEffects": false, + "files": [ + "dist" + ] } diff --git a/integrations/otel-js/run-tests.sh b/integrations/otel-js/run-tests.sh index 59b97c916..0e6dc893a 100644 --- a/integrations/otel-js/run-tests.sh +++ b/integrations/otel-js/run-tests.sh @@ -2,4 +2,5 @@ set -e pnpm install --ignore-workspace +pnpm run check:typings pnpm exec vp test run diff --git a/integrations/otel-js/smoke/scenarios/otel-v1/package.json b/integrations/otel-js/smoke/scenarios/otel-v1/package.json index 8762966ed..06a7cf6d9 100644 --- a/integrations/otel-js/smoke/scenarios/otel-v1/package.json +++ b/integrations/otel-js/smoke/scenarios/otel-v1/package.json @@ -5,11 +5,11 @@ "dependencies": { "braintrust": "file:../../../../../js/artifacts/braintrust-latest.tgz", "@braintrust/otel": "file:../../../../../js/artifacts/braintrust-otel-latest.tgz", - "@opentelemetry/api": "^1.9.0", - "@opentelemetry/core": "^1.9.0", - "@opentelemetry/exporter-trace-otlp-http": "^0.217.0", - "@opentelemetry/sdk-node": "^0.217.0", - "@opentelemetry/sdk-trace-base": "^1.9.0", + "@opentelemetry/api": "1.9.0", + "@opentelemetry/core": "1.30.1", + "@opentelemetry/exporter-trace-otlp-http": "0.57.2", + "@opentelemetry/sdk-node": "0.57.2", + "@opentelemetry/sdk-trace-base": "1.30.1", "zod": "^3.25.76" }, "devDependencies": { 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 d7a307a04..c022581a9 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 @@ -1,8 +1,13 @@ +import { BatchSpanProcessor } from "@opentelemetry/sdk-trace-base"; +import { OTLPTraceExporter } from "@opentelemetry/exporter-trace-otlp-http"; import { setTimeout as delay } from "node:timers/promises"; import { trace } from "@opentelemetry/api"; import { NodeSDK } from "@opentelemetry/sdk-node"; -import type { SpanProcessor } from "@opentelemetry/sdk-trace-base"; -import { BraintrustSpanProcessor } from "@braintrust/otel"; +import { + BraintrustSpanProcessor, + BraintrustExporter, + braintrustOtlpConfig, +} from "@braintrust/otel"; import { setupMockOtlpCollector } from "../src/test-helpers.js"; import { displayTestResults, @@ -22,8 +27,17 @@ async function main() { const sdk = new NodeSDK({ serviceName: "test-service", spanProcessor: new BraintrustSpanProcessor({ - apiKey: "smoke-test-api-key", - }) as unknown as SpanProcessor, + processor: new BatchSpanProcessor( + new BraintrustExporter({ + exporter: new OTLPTraceExporter( + braintrustOtlpConfig({ + apiKey: "smoke-test-api-key", + parent: "project_name:smoke-test", + }), + ), + }), + ), + }), }); await sdk.start(); 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 bd0362f91..72c20bda9 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 @@ -1,8 +1,13 @@ +import { BatchSpanProcessor } from "@opentelemetry/sdk-trace-base"; +import { OTLPTraceExporter } from "@opentelemetry/exporter-trace-otlp-http"; import { setTimeout as delay } from "node:timers/promises"; import { trace } from "@opentelemetry/api"; import { NodeSDK } from "@opentelemetry/sdk-node"; -import type { SpanProcessor } from "@opentelemetry/sdk-trace-base"; -import { BraintrustSpanProcessor } from "@braintrust/otel"; +import { + BraintrustSpanProcessor, + BraintrustExporter, + braintrustOtlpConfig, +} from "@braintrust/otel"; import { setupMockOtlpCollector } from "../src/test-helpers.js"; import { displayTestResults, @@ -39,9 +44,18 @@ async function main() { const sdk = new NodeSDK({ serviceName: "filtering-test", spanProcessor: new BraintrustSpanProcessor({ - apiKey: "smoke-test-api-key", + processor: new BatchSpanProcessor( + new BraintrustExporter({ + exporter: new OTLPTraceExporter( + braintrustOtlpConfig({ + apiKey: "smoke-test-api-key", + parent: "project_name:smoke-test", + }), + ), + }), + ), filterAISpans: true, - }) as unknown as SpanProcessor, + }), }); await sdk.start(); diff --git a/integrations/otel-js/src/adapters.test.ts b/integrations/otel-js/src/adapters.test.ts new file mode 100644 index 000000000..d4880fd60 --- /dev/null +++ b/integrations/otel-js/src/adapters.test.ts @@ -0,0 +1,211 @@ +import { afterEach, describe, expect, test, vi } from "vite-plus/test"; +import { context, ROOT_CONTEXT, trace } from "@opentelemetry/api"; +import { + BasicTracerProvider, + InMemorySpanExporter, + SimpleSpanProcessor, +} from "@opentelemetry/sdk-trace-base"; +import { + BraintrustExporter, + BraintrustSpanProcessor, + braintrustOtlpConfig, + addParentToBaggage, +} from "./otel"; +import { createTracerProvider } from "../tests/utils"; +import { _internalGetGlobalState } from "../../../js/src/logger/core"; +import type { ReadableSpan, ExportResult } from "./types"; + +async function realSpans() { + const memory = new InMemorySpanExporter(); + const provider = createTracerProvider(BasicTracerProvider, [ + new SimpleSpanProcessor(memory), + ]); + provider.getTracer("adapters").startSpan("gen_ai.test").end(); + await provider.forceFlush(); + const spans = [...memory.getFinishedSpans()]; + await provider.shutdown(); + return spans; +} + +afterEach(() => { + vi.unstubAllEnvs(); +}); + +describe("OTLP configuration", () => { + test("returns protocol configuration without choosing SDK classes or content type", () => { + expect( + braintrustOtlpConfig({ + apiKey: "test-key", + parent: "project_name:test", + apiUrl: "https://example.test/prefix", + headers: { + "x-custom": "value", + authorization: "ignored", + "X-BT-Parent": "ignored", + }, + }), + ).toEqual({ + url: "https://example.test/prefix/otel/v1/traces", + headers: { + Authorization: "Bearer test-key", + "x-bt-parent": "project_name:test", + "x-custom": "value", + }, + }); + }); + + test("uses environment defaults and gives explicit options precedence", () => { + vi.stubEnv("BRAINTRUST_API_KEY", "env-key"); + vi.stubEnv("BRAINTRUST_PARENT", "project_name:env"); + vi.stubEnv("BRAINTRUST_API_URL", "https://example.test/"); + expect(braintrustOtlpConfig().headers.Authorization).toBe("Bearer env-key"); + expect( + braintrustOtlpConfig({ apiKey: "explicit" }).headers.Authorization, + ).toBe("Bearer explicit"); + expect(braintrustOtlpConfig().url).toBe( + "https://example.test/otel/v1/traces", + ); + }); + + test("requires credentials and explicit routing without creating a default project", () => { + expect(() => + braintrustOtlpConfig({ apiKey: "", parent: "project_name:test" }), + ).toThrow("API key is required"); + expect(() => braintrustOtlpConfig({ apiKey: "test", parent: "" })).toThrow( + "parent is required", + ); + }); +}); + +describe("exporter lifecycle", () => { + test("forwards one complete batch and the original transport result", async () => { + const spans = await realSpans(); + const result = { code: 1, error: new Error("transport failed") }; + const downstream = { + export: vi.fn( + (_spans: ReadableSpan[], callback: (result: ExportResult) => void) => + callback(result), + ), + shutdown: vi.fn(async () => {}), + forceFlush: vi.fn(async () => {}), + }; + const exporter = new BraintrustExporter({ exporter: downstream }); + const received = await new Promise((resolve) => + exporter.export(spans, resolve), + ); + expect(received).toBe(result); + expect(downstream.export).toHaveBeenCalledTimes(1); + expect(downstream.export.mock.calls[0][0]).toHaveLength(1); + expect(downstream.forceFlush).not.toHaveBeenCalled(); + expect(spans[0].attributes["braintrust.context_json"]).toBeUndefined(); + await exporter.shutdown(); + }); + + test("reports synchronous transport errors once", async () => { + const spans = await realSpans(); + const error = new Error("transport failed"); + const callback = vi.fn(); + const exporter = new BraintrustExporter({ + exporter: { + export() { + throw error; + }, + async shutdown() {}, + }, + }); + exporter.export(spans, callback); + await exporter.forceFlush(); + expect(callback).toHaveBeenCalledExactlyOnceWith({ code: 1, error }); + await exporter.shutdown(); + }); + + test("flush and shutdown wait for in-flight exports and shutdown is idempotent", async () => { + const spans = await realSpans(); + let finish!: (result: ExportResult) => void; + const downstream = { + export: vi.fn( + (_spans: ReadableSpan[], callback: (result: ExportResult) => void) => { + finish = callback; + }, + ), + shutdown: vi.fn(async () => {}), + forceFlush: vi.fn(async () => {}), + }; + const exporter = new BraintrustExporter({ exporter: downstream }); + const callback = vi.fn(); + exporter.export(spans, callback); + await vi.waitFor(() => expect(downstream.export).toHaveBeenCalledTimes(1)); + const flushed = exporter.forceFlush(); + const shutdown = exporter.shutdown(); + expect(exporter.shutdown()).toBe(shutdown); + expect(downstream.shutdown).not.toHaveBeenCalled(); + const rejected = vi.fn(); + exporter.export(spans, rejected); + expect(rejected.mock.calls[0][0].code).toBe(1); + finish({ code: 0 }); + await Promise.all([flushed, shutdown]); + expect(callback).toHaveBeenCalledExactlyOnceWith({ code: 0 }); + expect(downstream.shutdown).toHaveBeenCalledTimes(1); + expect(downstream.forceFlush).toHaveBeenCalledTimes(1); + }); + + test("completes empty batches without calling the transport", async () => { + const downstream = { export: vi.fn(), async shutdown() {} }; + const exporter = new BraintrustExporter({ exporter: downstream }); + expect( + await new Promise((resolve) => exporter.export([], resolve)), + ).toEqual({ code: 0 }); + expect(downstream.export).not.toHaveBeenCalled(); + await exporter.shutdown(); + }); +}); + +test("all active processors flush and shutdown removes only its own registration", async () => { + const first = { + onStart() {}, + onEnd() {}, + forceFlush: vi.fn(async () => {}), + shutdown: vi.fn(async () => {}), + }; + const second = { + ...first, + forceFlush: vi.fn(async () => {}), + shutdown: vi.fn(async () => {}), + }; + const a = new BraintrustSpanProcessor({ processor: first }); + const b = new BraintrustSpanProcessor({ processor: second }); + try { + await _internalGetGlobalState().flushOtel(); + expect(first.forceFlush).toHaveBeenCalledTimes(1); + expect(second.forceFlush).toHaveBeenCalledTimes(1); + await a.shutdown(); + await a.shutdown(); + await _internalGetGlobalState().flushOtel(); + expect(first.forceFlush).toHaveBeenCalledTimes(1); + expect(second.forceFlush).toHaveBeenCalledTimes(2); + expect(first.shutdown).toHaveBeenCalledTimes(1); + } finally { + await Promise.all([a.shutdown(), b.shutdown()]); + } +}); + +test("routing follows the supplied parent context instead of the ambient context", async () => { + const memory = new InMemorySpanExporter(); + const processor = new BraintrustSpanProcessor({ + processor: new SimpleSpanProcessor(memory), + }); + const provider = createTracerProvider(BasicTracerProvider, [processor]); + try { + const tracer = provider.getTracer("routing"); + const parent = addParentToBaggage("project_name:explicit", ROOT_CONTEXT); + const span = tracer.startSpan("child", {}, parent); + span.end(); + await provider.forceFlush(); + expect(memory.getFinishedSpans()[0].attributes["braintrust.parent"]).toBe( + "project_name:explicit", + ); + expect(trace.getSpan(context.active())).toBeUndefined(); + } finally { + await provider.shutdown(); + } +}); diff --git a/integrations/otel-js/src/context.test.ts b/integrations/otel-js/src/context.test.ts new file mode 100644 index 000000000..ba1f97d01 --- /dev/null +++ b/integrations/otel-js/src/context.test.ts @@ -0,0 +1,103 @@ +import { afterEach, beforeEach, expect, test } from "vite-plus/test"; +import { + context, + trace, + type ContextManager as OtelContext, +} from "@opentelemetry/api"; +import { AsyncLocalStorageContextManager } from "@opentelemetry/context-async-hooks"; +import { NoopSpan } from "../../../js/src/logger/core"; +import { OtelContextManager } from "./context"; + +// Expose only the public contract. Private fields cannot be discovered through this wrapper. +let manager: AsyncLocalStorageContextManager; +beforeEach(() => { + manager = new AsyncLocalStorageContextManager().enable(); + const publicManager: OtelContext = { + active: () => manager.active(), + with: (ctx, fn, thisArg, ...args) => + manager.with(ctx, fn, thisArg, ...args), + bind: (ctx, target) => manager.bind(ctx, target), + enable() { + return this; + }, + disable() { + return this; + }, + }; + context.setGlobalContextManager(publicManager); +}); +afterEach(() => { + context.disable(); + manager.disable(); +}); + +const span = Object.assign(new NoopSpan(), { + rootSpanId: "1".repeat(32), + spanId: "2".repeat(16), +}); + +test("application exceptions propagate without retrying the callback", () => { + const bridge = new OtelContextManager(); + const error = new Error("application failure"); + let calls = 0; + expect(() => + bridge.runInContext(span, () => { + calls++; + throw error; + }), + ).toThrow(error); + expect(calls).toBe(1); + expect(bridge.getCurrentSpan()).toBeUndefined(); +}); + +test("public context APIs retain identity across async work and isolate concurrent requests", async () => { + const bridge = new OtelContextManager(); + const other = Object.assign(new NoopSpan(), { + rootSpanId: span.rootSpanId, + spanId: "3".repeat(16), + }); + await Promise.all( + [span, other].map((current) => + bridge.runInContext(current, async () => { + await new Promise((resolve) => setTimeout(resolve, 1)); + expect(bridge.getCurrentSpan()).toBe(current); + expect(trace.getSpanContext(context.active())?.spanId).toBe( + current.spanId, + ); + expect(bridge.getParentSpanIds()?.spanParents).toEqual([ + current.spanId, + ]); + }), + ), + ); + expect(bridge.getCurrentSpan()).toBeUndefined(); +}); + +test("the instrumentation store uses the same public context as manual spans", async () => { + const bridge = new OtelContextManager(); + const store = bridge.getCurrentSpanStore(); + await store.run(bridge.wrapSpanForStore(span), async () => { + await Promise.resolve(); + expect(bridge.getCurrentSpan()).toBe(span); + expect(store.getStore()).toBe(context.active()); + }); + expect(bridge.getCurrentSpan()).toBeUndefined(); +}); + +test("the local bridge preserves an upstream sampling decision and trace state", () => { + const bridge = new OtelContextManager(); + const unsampled = Object.assign(new NoopSpan(), { + rootSpanId: span.rootSpanId, + spanId: span.spanId, + inject: () => ({ + traceparent: `00-${span.rootSpanId}-${span.spanId}-00`, + tracestate: "vendor=value", + }), + }); + bridge.runInContext(unsampled, () => { + const active = trace.getSpanContext(context.active())!; + expect(active.traceFlags).toBe(0); + expect(active.traceState?.serialize()).toBe("vendor=value"); + expect(active.isRemote).not.toBe(true); + }); +}); diff --git a/integrations/otel-js/src/context.ts b/integrations/otel-js/src/context.ts index b61416e41..b23be29bf 100644 --- a/integrations/otel-js/src/context.ts +++ b/integrations/otel-js/src/context.ts @@ -1,186 +1,74 @@ import { ContextManager, + extractTraceContextFromHeaders, 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"; - -// eslint-disable-next-line @typescript-eslint/no-explicit-any -const BT_SPAN_KEY = "braintrust_span" as any; -// eslint-disable-next-line @typescript-eslint/no-explicit-any -const BT_PARENT_KEY = "braintrust.parent" as any; - -function isOtelSpan(span: unknown): span is { - spanContext: () => { spanId: string; traceId: string }; -} { - return ( - typeof span === "object" && - span !== null && - "spanContext" in span && - // eslint-disable-next-line @typescript-eslint/consistent-type-assertions -- Type guard ensures object has property - typeof (span as { spanContext?: unknown }).spanContext === "function" - ); -} - -function isBraintrustSpan(span: unknown): span is Span { - return ( - typeof span === "object" && - span !== null && - "spanId" in span && - "rootSpanId" in span && - typeof span.spanId === "string" - ); -} - -function isValidSpanContext(spanContext: unknown): boolean { - if ( - !spanContext || - typeof spanContext !== "object" || - !("spanId" in spanContext) || - !("traceId" in spanContext) - ) { - return false; - } - // eslint-disable-next-line @typescript-eslint/consistent-type-assertions - const ctx = spanContext as { spanId: string; traceId: string }; - return ( - ctx.spanId !== "0000000000000000" && - ctx.traceId !== "00000000000000000000000000000000" - ); -} - -/** - * Builds an OTEL Context containing a NonRecordingSpan wrapper around the - * Braintrust span's IDs, plus the BT span stored under BT_SPAN_KEY for - * retrieval by getCurrentSpan(). - */ -function buildBtOtelContext(span: Span): unknown { - const btSpan = span as { spanId: string; rootSpanId: string }; - const spanContext = { - traceId: btSpan.rootSpanId, - spanId: btSpan.spanId, - traceFlags: 1, // sampled - }; - const wrappedSpan = otelTrace.wrapSpanContext(spanContext); - const currentContext = otelContext.active(); - let newContext = otelTrace.setSpan(currentContext, wrappedSpan); - newContext = newContext.setValue(BT_SPAN_KEY, span); - - if (isBraintrustSpan(span)) { - // eslint-disable-next-line @typescript-eslint/consistent-type-assertions - const parentValue = getOtelParentFromSpan(span as never); - if (parentValue) { - newContext = newContext.setValue(BT_PARENT_KEY, parentValue); - } - } - - return newContext; +import { + context, + trace, + createContextKey, + createTraceState, + isSpanContextValid, + type Context, +} from "@opentelemetry/api"; +import { addParentToBaggage, getOtelParentFromSpan } from "./otel"; + +const BRAINTRUST_SPAN_KEY = createContextKey("braintrust.span"); + +function contextWithSpan(span: Span): Context { + const headers = extractTraceContextFromHeaders(span.inject()); + const ctx = trace + .setSpanContext(context.active(), { + traceId: span.rootSpanId, + spanId: span.spanId, + traceFlags: headers ? parseInt(headers.traceparent.split("-")[3], 16) : 1, + ...(headers?.tracestate + ? { traceState: createTraceState(headers.tracestate) } + : {}), + }) + .setValue(BRAINTRUST_SPAN_KEY, span); + const parent = getOtelParentFromSpan(span); + return parent ? addParentToBaggage(parent, ctx) : ctx; } +/** Bridge through the registered OTel context manager's public API. */ export class OtelContextManager extends ContextManager { - /** Fallback ALS used when the OTEL context manager doesn't expose _asyncLocalStorage. */ - private _ownAls: CurrentSpanStore | undefined; - - private _getOtelAls(): CurrentSpanStore | undefined { - // eslint-disable-next-line @typescript-eslint/no-explicit-any - return (otelContext as any)._getContextManager?.()._asyncLocalStorage; - } + private readonly store: CurrentSpanStore = { + getStore: () => context.active(), + // CurrentSpanStore erases the value type. wrapSpanForStore supplies Context. + run: (value, callback) => + context.with( + (value as Context | undefined) ?? context.active(), + callback, + ), + }; getCurrentSpanStore(): CurrentSpanStore { - const otelAls = this._getOtelAls(); - if (otelAls) return otelAls; - if (!this._ownAls) this._ownAls = new AsyncLocalStorage(); - return this._ownAls; + return this.store; } - wrapSpanForStore(span: Span): unknown { - // When using OTEL's ALS the stored value must be an OTEL Context, not a raw - // Span, so that OTEL's own context propagation sees a valid Context object. - // When using our own fallback ALS we store the Span directly (default mode). - if (this._getOtelAls()) return buildBtOtelContext(span); - return span; + wrapSpanForStore(span: Span): Context { + return contextWithSpan(span); } getParentSpanIds(): ContextParentSpanIds | undefined { - const currentSpan = otelTrace.getActiveSpan(); - if (!currentSpan || !isOtelSpan(currentSpan)) { - return undefined; - } - - const spanContext = currentSpan.spanContext(); - if (!isValidSpanContext(spanContext)) { - return undefined; - } - - // Check if this is a wrapped BT span - const btSpan = otelContext?.active().getValue?.(BT_SPAN_KEY); - if ( - btSpan && - currentSpan.constructor.name === "NonRecordingSpan" && - typeof btSpan === "object" && - btSpan !== null && - "rootSpanId" in btSpan && - "spanId" in btSpan - ) { - // eslint-disable-next-line @typescript-eslint/consistent-type-assertions - const typedBtSpan = btSpan as { rootSpanId: string; spanId: string }; - return { - rootSpanId: typedBtSpan.rootSpanId, - spanParents: [typedBtSpan.spanId], - }; - } - - // Otherwise use OTEL span IDs - const otelTraceId = spanContext.traceId.toString().padStart(32, "0"); - const otelSpanId = spanContext.spanId.toString().padStart(16, "0"); + const parent = trace.getSpanContext(context.active()); + if (!parent || !isSpanContextValid(parent)) return undefined; return { - rootSpanId: otelTraceId, - spanParents: [otelSpanId], + rootSpanId: parent.traceId, + spanParents: [parent.spanId], + traceFlags: parent.traceFlags, + traceState: parent.traceState?.serialize(), }; } runInContext(span: Span, callback: () => R): R { - try { - if (isBraintrustSpan(span)) { - return otelContext.with( - // eslint-disable-next-line @typescript-eslint/consistent-type-assertions - buildBtOtelContext(span) as Parameters[0], - callback, - ); - } - } catch (error) { - console.warn("Failed to run in OTEL context:", error); - } - - return callback(); + return context.with(contextWithSpan(span), callback); } getCurrentSpan(): Span | undefined { - // Check OTEL context first — this covers both runInContext and the OTEL-ALS - // TracingChannel path where runStores stores a Context under BT_SPAN_KEY. - const btSpan = otelContext.active().getValue?.(BT_SPAN_KEY); - if ( - btSpan && - typeof btSpan === "object" && - btSpan !== null && - "spanId" in btSpan && - "rootSpanId" in btSpan - ) { - // eslint-disable-next-line @typescript-eslint/consistent-type-assertions - return btSpan as Span; - } - - // If we're using the fallback ALS (non-ALS OTEL context manager), spans are - // stored directly in our own ALS by TracingChannel's runStores. - if (this._ownAls) { - const stored = this._ownAls.getStore(); - if (isBraintrustSpan(stored)) return stored; - } - - return undefined; + return context.active().getValue(BRAINTRUST_SPAN_KEY) as Span | undefined; } } diff --git a/integrations/otel-js/src/exporter.test.ts b/integrations/otel-js/src/exporter.test.ts index 0a0ab6d33..03c9ab286 100644 --- a/integrations/otel-js/src/exporter.test.ts +++ b/integrations/otel-js/src/exporter.test.ts @@ -6,39 +6,14 @@ import { afterAll, beforeEach, afterEach, - vi, } from "vite-plus/test"; import { createServer, type Server } from "node:http"; import type { AddressInfo } from "node:net"; -import { - diag, - DiagLogLevel, - TraceFlags, - type DiagLogger, -} from "@opentelemetry/api"; -import { - BasicTracerProvider, - type ReadableSpan, -} from "@opentelemetry/sdk-trace-base"; -import { BraintrustSpanProcessor } from "./otel"; +import { BasicTracerProvider } from "@opentelemetry/sdk-trace-base"; import { createTracerProvider } from "../tests/utils"; +import { createTestProcessor } from "../tests/export-fixtures"; import { _exportsForTestingOnly } from "../../../js/src/logger/testing"; -type OtlpTraceRequest = { - resourceSpans?: Array<{ - scopeSpans?: Array<{ - scope?: { - name?: string; - version?: string; - }; - spans?: Array<{ - name?: string; - parentSpanId?: string; - }>; - }>; - }>; -}; - describe("BraintrustSpanProcessor - Real HTTP Exporter", () => { const TEST_API_KEY = "test-api-key-12345"; const TEST_PARENT = "project_name:test-export-project"; @@ -104,14 +79,13 @@ describe("BraintrustSpanProcessor - Real HTTP Exporter", () => { }); afterEach(() => { - diag.disable(); _exportsForTestingOnly.clearTestBackgroundLogger(); _exportsForTestingOnly.simulateLogoutForTests(); }); it("should send spans via HTTP to correct OTLP endpoint with proper headers", async () => { // Create processor WITHOUT _spanProcessor to test real exporter path - const processor = new BraintrustSpanProcessor({ + const processor = createTestProcessor({ apiKey: TEST_API_KEY, apiUrl: testApiUrl, parent: TEST_PARENT, @@ -147,7 +121,7 @@ describe("BraintrustSpanProcessor - Real HTTP Exporter", () => { }); it("should work with filterAISpans enabled", async () => { - const processor = new BraintrustSpanProcessor({ + const processor = createTestProcessor({ apiKey: TEST_API_KEY, apiUrl: testApiUrl, parent: TEST_PARENT, @@ -171,76 +145,10 @@ describe("BraintrustSpanProcessor - Real HTTP Exporter", () => { expect(capturedRequests[0].body).toHaveProperty("resourceSpans"); }); - it("should export OTel 1.x-shaped spans through newer OTLP exporters", async () => { - const processor = new BraintrustSpanProcessor({ - apiKey: TEST_API_KEY, - apiUrl: testApiUrl, - parent: TEST_PARENT, - }); - const parentSpanId = "3333333333333333"; - const v1Span = { - name: "gen_ai.completion", - spanContext: () => ({ - traceId: "11111111111111111111111111111111", - spanId: "2222222222222222", - traceFlags: TraceFlags.SAMPLED, - }), - parentSpanId, - instrumentationLibrary: { name: "otel-v1-lib", version: "1.2.3" }, - kind: 0, - startTime: [0, 0], - endTime: [0, 1], - status: { code: 0 }, - attributes: {}, - events: [], - links: [], - droppedAttributesCount: 0, - droppedEventsCount: 0, - droppedLinksCount: 0, - resource: { - attributes: { - "service.name": "otel-v1-service", - }, - asyncAttributesPending: false, - }, - } as unknown as ReadableSpan; - - processor.onEnd(v1Span); - await processor.forceFlush(); - await processor.shutdown(); - - expect(capturedRequests.length).toBeGreaterThanOrEqual(1); - const body = capturedRequests[0].body as OtlpTraceRequest; - const scopeSpan = body.resourceSpans?.[0]?.scopeSpans?.[0]; - const exportedSpan = scopeSpan?.spans?.[0]; - - expect(scopeSpan?.scope).toEqual({ - name: "otel-v1-lib", - version: "1.2.3", - }); - expect(exportedSpan?.name).toBe("gen_ai.completion"); - expect(exportedSpan?.parentSpanId).toBe(parentSpanId); - expect("instrumentationScope" in v1Span).toBe(false); - expect("parentSpanContext" in v1Span).toBe(false); - }); - - it("should report export failures through OTel diagnostics", async () => { + it("should propagate transport failures through processor forceFlush", async () => { failExports = true; - const diagErrors: unknown[][] = []; - const testDiagLogger: DiagLogger = { - error: (...args) => diagErrors.push(args), - warn: vi.fn(), - info: vi.fn(), - debug: vi.fn(), - verbose: vi.fn(), - }; - diag.setLogger(testDiagLogger, { - logLevel: DiagLogLevel.ERROR, - suppressOverrideMessage: true, - }); - - const processor = new BraintrustSpanProcessor({ + const processor = createTestProcessor({ apiKey: TEST_API_KEY, apiUrl: testApiUrl, parent: TEST_PARENT, @@ -252,13 +160,5 @@ describe("BraintrustSpanProcessor - Real HTTP Exporter", () => { await expect(processor.forceFlush()).rejects.toThrow(); await provider.shutdown().catch(() => undefined); - - expect( - diagErrors.some( - (args) => - args[0] === "@braintrust/otel" && - args[1] === "Braintrust OTLP span export failed", - ), - ).toBe(true); }); }); diff --git a/integrations/otel-js/src/index.ts b/integrations/otel-js/src/index.ts index a96c89551..5323f1ab1 100644 --- a/integrations/otel-js/src/index.ts +++ b/integrations/otel-js/src/index.ts @@ -2,6 +2,7 @@ import { OtelContextManager } from "./context"; import { configureContextManager } from "braintrust"; export { + braintrustOtlpConfig, contextFromSpan, addSpanParentToBaggage, addParentToBaggage, diff --git a/integrations/otel-js/src/otel-compat.test.ts b/integrations/otel-js/src/otel-compat.test.ts index 70161e73c..8b8e9b145 100644 --- a/integrations/otel-js/src/otel-compat.test.ts +++ b/integrations/otel-js/src/otel-compat.test.ts @@ -9,6 +9,7 @@ import { beforeEach, afterEach, describe, expect, test } from "vite-plus/test"; import { initLogger, currentSpan, Eval } from "braintrust"; import { _exportsForTestingOnly } from "../../../js/src/logger/testing"; import { + NoopSpan, _internalStartSpan, _internalWithParent, getContextManager, @@ -18,7 +19,13 @@ import { InMemorySpanExporter, SimpleSpanProcessor, } from "@opentelemetry/sdk-trace-base"; -import { context as otelContext, trace as otelTrace } from "@opentelemetry/api"; +import { + context as otelContext, + trace as otelTrace, + type Context, + createTraceState, + ROOT_CONTEXT, +} from "@opentelemetry/api"; import { AsyncHooksContextManager, AsyncLocalStorageContextManager, @@ -32,6 +39,7 @@ import { setupOtelCompat, resetOtelCompat, BraintrustSpanProcessor, + contextFromSpan, } from "@braintrust/otel"; class NoopProgressReporter { @@ -40,15 +48,14 @@ class NoopProgressReporter { public increment() {} } -function setupOtelFixture(projectName: string = "otel-compat-test") { +function setupOtelFixture(_projectName: string = "otel-compat-test") { // For introspection in tests const exporter = new InMemorySpanExporter(); const memoryProcessor = new SimpleSpanProcessor(exporter); // For actual Braintrust integration - inject test processor to avoid hitting APIs const braintrustProcessor = new BraintrustSpanProcessor({ - parent: `project_name:${projectName}`, - _spanProcessor: memoryProcessor, + processor: memoryProcessor, }); const tp = createTracerProvider(BasicTracerProvider, [ @@ -90,6 +97,62 @@ describe("OTEL compatibility mode", () => { resetOtelCompat(); }); + test("an explicit native parent overrides routing from the ambient native span", async () => { + const { tracer, exporter, processor } = setupOtelFixture(); + const first = initLogger({ projectName: "routing-first" }); + const second = initLogger({ projectName: "routing-second" }); + const explicit = second.startSpan({ name: "explicit-parent" }); + try { + await first.traced(async () => { + tracer.startSpan("child", {}, contextFromSpan(explicit)).end(); + tracer.startSpan("unrelated-root", {}, ROOT_CONTEXT).end(); + }); + await processor.forceFlush(); + const child = exporter.getFinishedSpans()[0]; + expect(child.attributes["braintrust.parent"]).toBe( + "project_name:routing-second", + ); + expect(getParentSpanId(child)).toBe(explicit.spanId); + expect( + exporter.getFinishedSpans()[1].attributes["braintrust.parent"], + ).toBeUndefined(); + } finally { + explicit.end(); + await processor.shutdown(); + } + }); + + test("native spans preserve an unsampled OTel parent and its trace state", async () => { + const { tracer, exporter, processor } = setupOtelFixture(); + const logger = initLogger({ projectName: "sampling-state" }); + const parent = otelTrace.setSpanContext(otelContext.active(), { + traceId: "1".repeat(32), + spanId: "2".repeat(16), + traceFlags: 0, + traceState: createTraceState("vendor=value"), + isRemote: true, + }); + try { + await otelContext.with(parent, async () => { + await logger.traced(async (native) => { + expect(native.inject().traceparent).toMatch(/-00$/); + expect(native.inject().tracestate).toBe("vendor=value"); + const child = tracer.startSpan("gen_ai.unsampled"); + expect(child.isRecording()).toBe(false); + expect(child.spanContext().traceFlags).toBe(0); + expect(child.spanContext().traceState?.serialize()).toBe( + "vendor=value", + ); + child.end(); + }); + }); + await processor.forceFlush(); + expect(exporter.getFinishedSpans()).toHaveLength(0); + } finally { + await processor.shutdown(); + } + }); + test("mixed BT/OTEL tracing with BT logger first", async () => { const { tracer, exporter, processor } = setupOtelFixture( "mixed-tracing-bt-first", @@ -264,25 +327,11 @@ describe("OTEL compatibility mode", () => { }); 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. - const cm = getContextManager(); - - // If OTEL is truly available, we should get OtelContextManager - // Otherwise, fallback to BraintrustContextManager is acceptable - if (cm.constructor.name === "OtelContextManager") { - expect(cm.getParentSpanIds).toBeDefined(); - expect(cm.runInContext).toBeDefined(); - expect(cm.getCurrentSpan).toBeDefined(); - } else { - // OTEL module not actually available at runtime, which is fine - console.warn( - "OTEL context manager not available at runtime, using fallback", - ); - } + expect(cm.constructor.name).toBe("OtelContextManager"); + expect(cm.getParentSpanIds).toBeDefined(); + expect(cm.runInContext).toBeDefined(); + expect(cm.getCurrentSpan).toBeDefined(); }); test("OTEL spans inherit braintrust.parent attribute", async () => { @@ -698,8 +747,6 @@ describe("OtelContextManager TracingChannel integration", () => { await _exportsForTestingOnly.simulateLoginForTests(); _exportsForTestingOnly.useTestBackgroundLogger(); - // AsyncLocalStorageContextManager (not AsyncHooksContextManager) exposes - // _asyncLocalStorage which is required for TracingChannel bindStore. contextManager = new AsyncLocalStorageContextManager(); contextManager.enable(); otelContext.setGlobalContextManager(contextManager); @@ -715,7 +762,7 @@ describe("OtelContextManager TracingChannel integration", () => { resetOtelCompat(); }); - test("OtelContextManager exposes the OTEL ALS", () => { + test("OtelContextManager exposes a public context store", () => { const cm = getContextManager(); expect(cm.constructor.name).toBe("OtelContextManager"); @@ -727,21 +774,20 @@ describe("OtelContextManager TracingChannel integration", () => { test("wrapSpanForStore returns OTEL Context that makes currentSpan() work", () => { const cm = getContextManager(); - const mockSpan = { + const mockSpan = Object.assign(new NoopSpan(), { spanId: "abc123def456789a", rootSpanId: "00000000000000000000000000000001", spanParents: [], getParentInfo: () => undefined, - }; + }); - // eslint-disable-next-line @typescript-eslint/no-explicit-any - const ctx = cm.wrapSpanForStore(mockSpan as any); + const ctx = cm.wrapSpanForStore(mockSpan); expect(ctx).toBeDefined(); // Running in the returned context should expose the BT span via currentSpan() otelContext.with( // eslint-disable-next-line @typescript-eslint/no-explicit-any - ctx as any, + ctx as Context, () => { expect(currentSpan()).toBe(mockSpan); }, @@ -753,15 +799,14 @@ describe("OtelContextManager TracingChannel integration", () => { const store = cm.getCurrentSpanStore()!; expect(store).toBeDefined(); - const mockSpan = { + const mockSpan = Object.assign(new NoopSpan(), { spanId: "deadbeef12345678", rootSpanId: "00000000000000000000000000000002", spanParents: [], getParentInfo: () => undefined, - }; + }); - // eslint-disable-next-line @typescript-eslint/no-explicit-any - const wrappedCtx = cm.wrapSpanForStore(mockSpan as any); + const wrappedCtx = cm.wrapSpanForStore(mockSpan); // Simulate what TracingChannel's runStores does: call store.run(value, fn) store.run(wrappedCtx, () => { @@ -773,25 +818,25 @@ describe("OtelContextManager TracingChannel integration", () => { const cm = getContextManager(); const store = cm.getCurrentSpanStore()!; - const parentSpan = { + const parentSpan = Object.assign(new NoopSpan(), { spanId: "1111111111111111", rootSpanId: "00000000000000000000000000000003", spanParents: [], getParentInfo: () => undefined, - }; - const childSpan = { + }); + const childSpan = Object.assign(new NoopSpan(), { spanId: "2222222222222222", rootSpanId: "00000000000000000000000000000003", spanParents: ["1111111111111111"], getParentInfo: () => undefined, - }; + }); // eslint-disable-next-line @typescript-eslint/no-explicit-any - store.run(cm.wrapSpanForStore(parentSpan as any), () => { + store.run(cm.wrapSpanForStore(parentSpan), () => { expect(currentSpan()).toBe(parentSpan); // eslint-disable-next-line @typescript-eslint/no-explicit-any - store.run(cm.wrapSpanForStore(childSpan as any), () => { + store.run(cm.wrapSpanForStore(childSpan), () => { expect(currentSpan()).toBe(childSpan); }); @@ -805,19 +850,18 @@ describe("OtelContextManager TracingChannel integration", () => { test("wrapSpanForStore result sets OTEL active span with matching IDs", () => { const cm = getContextManager(); - const mockSpan = { + const mockSpan = Object.assign(new NoopSpan(), { spanId: "aabbccddeeff0011", rootSpanId: "aabbccddeeff00112233445566778899", spanParents: [], getParentInfo: () => undefined, - }; + }); - // eslint-disable-next-line @typescript-eslint/no-explicit-any - const ctx = cm.wrapSpanForStore(mockSpan as any); + const ctx = cm.wrapSpanForStore(mockSpan); otelContext.with( // eslint-disable-next-line @typescript-eslint/no-explicit-any - ctx as any, + ctx as Context, () => { const activeSpan = otelTrace.getActiveSpan(); expect(activeSpan).toBeDefined(); @@ -830,7 +874,7 @@ describe("OtelContextManager TracingChannel integration", () => { }); }); -describe("OtelContextManager fallback ALS (AsyncHooksContextManager)", () => { +describe("OtelContextManager with AsyncHooksContextManager", () => { let hooksContextManager: AsyncHooksContextManager; beforeEach(async () => { @@ -838,8 +882,6 @@ describe("OtelContextManager fallback ALS (AsyncHooksContextManager)", () => { await _exportsForTestingOnly.simulateLoginForTests(); _exportsForTestingOnly.useTestBackgroundLogger(); - // AsyncHooksContextManager does NOT expose _asyncLocalStorage, so - // OtelContextManager should create its own fallback ALS. hooksContextManager = new AsyncHooksContextManager(); hooksContextManager.enable(); otelContext.setGlobalContextManager(hooksContextManager); @@ -855,7 +897,7 @@ describe("OtelContextManager fallback ALS (AsyncHooksContextManager)", () => { resetOtelCompat(); }); - test("exposes fallback ALS when OTEL ALS is unavailable", () => { + test("exposes a store without access to OTel internals", () => { const cm = getContextManager(); const store = cm.getCurrentSpanStore()!; expect(store).toBeDefined(); @@ -863,33 +905,33 @@ describe("OtelContextManager fallback ALS (AsyncHooksContextManager)", () => { expect(typeof store.getStore).toBe("function"); }); - test("wrapSpanForStore returns span directly (not OTEL Context) in fallback mode", () => { + test("wrapSpanForStore uses OTel context with AsyncHooksContextManager", () => { const cm = getContextManager(); - const mockSpan = { + const mockSpan = Object.assign(new NoopSpan(), { spanId: "1234567890abcdef", rootSpanId: "00000000000000000000000000000010", spanParents: [], getParentInfo: () => undefined, - }; + }); - // eslint-disable-next-line @typescript-eslint/no-explicit-any - const result = cm.wrapSpanForStore(mockSpan as any); - // In fallback mode, the span is stored directly (not wrapped in OTEL Context) - expect(result).toBe(mockSpan); + const result = cm.wrapSpanForStore(mockSpan); + cm.getCurrentSpanStore()!.run(result, () => + expect(cm.getCurrentSpan()).toBe(mockSpan), + ); }); - test("store.run() with span in fallback mode makes getCurrentSpan() work", () => { + test("store.run() with AsyncHooksContextManager makes getCurrentSpan() work", () => { const cm = getContextManager(); const store = cm.getCurrentSpanStore()!; - const mockSpan = { + const mockSpan = Object.assign(new NoopSpan(), { spanId: "fedcba0987654321", rootSpanId: "00000000000000000000000000000011", spanParents: [], getParentInfo: () => undefined, - }; + }); - store.run(mockSpan, () => { + store.run(cm.wrapSpanForStore(mockSpan), () => { expect(cm.getCurrentSpan()).toBe(mockSpan); }); @@ -1015,19 +1057,10 @@ describe("Distributed Tracing (BT → OTEL)", () => { // Assert unified trace ID across services expect(serviceBTraceId).toBe(serviceATraceId); - // Service B span should have Service A span as parent - if ((serviceBSpan as any).parentSpanId) { - expect((serviceBSpan as any).parentSpanId).toBe(serviceASpanId); - } - - // Note: In distributed tracing, the braintrust.parent attribute is NOT - // automatically set in Service B because there's no active BT span context. - // The imported context only contains trace/span IDs for parent-child relationships. - // This is expected behavior - the key feature is trace ID unification. - expect(serviceBSpan.attributes).toBeDefined(); - if (serviceBSpan.attributes) { - // Verify the attribute is NOT set (this is expected in distributed scenarios) - expect(serviceBSpan.attributes["braintrust.parent"]).toBeUndefined(); - } + expect(getParentSpanId(serviceBSpan)).toBe(serviceASpanId); + // W3C baggage carries project routing across the service boundary. + expect(serviceBSpan.attributes["braintrust.parent"]).toBe( + "project_name:service-a-project", + ); }); }); diff --git a/integrations/otel-js/src/otel.test.ts b/integrations/otel-js/src/otel.test.ts index 08efa0586..e11acfbfa 100644 --- a/integrations/otel-js/src/otel.test.ts +++ b/integrations/otel-js/src/otel.test.ts @@ -26,9 +26,7 @@ import { CompositePropagator, } from "@opentelemetry/core"; import { - AISpanProcessor, BraintrustSpanProcessor, - BraintrustExporter, addSpanParentToBaggage, addParentToBaggage, parentFromHeaders, @@ -57,19 +55,13 @@ async function withBraintrustEnvFile( try { await writeFile(path.join(tempDir, ".env.braintrust"), content); process.chdir(tempDir); - return fn(); + return await fn(); } finally { process.chdir(originalCwd); await rm(tempDir, { force: true, recursive: true }); } } -async function withEmptyBraintrustEnvFile( - fn: () => T | Promise, -): Promise { - 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]; @@ -77,10 +69,10 @@ function restoreEnvironment(snapshot: NodeJS.ProcessEnv): void { Object.assign(process.env, snapshot); } -describe("AISpanProcessor", () => { +describe("BraintrustSpanProcessor filtering", () => { let memoryExporter: InMemorySpanExporter; let provider: BasicTracerProvider; - let filterProcessor: AISpanProcessor; + let filterProcessor: BraintrustSpanProcessor; let tracer: Tracer; let baseProcessor: SimpleSpanProcessor; @@ -89,7 +81,10 @@ describe("AISpanProcessor", () => { // Create processor with our filtering logic baseProcessor = new SimpleSpanProcessor(memoryExporter); - filterProcessor = new AISpanProcessor(baseProcessor); + filterProcessor = new BraintrustSpanProcessor({ + processor: baseProcessor, + filterAISpans: true, + }); provider = createTracerProvider(BasicTracerProvider, [filterProcessor]); @@ -241,10 +236,11 @@ describe("AISpanProcessor", () => { }; const customMemoryExporter = new InMemorySpanExporter(); - const customFilterProcessor = new AISpanProcessor( - new SimpleSpanProcessor(customMemoryExporter), + const customFilterProcessor = new BraintrustSpanProcessor({ + processor: new SimpleSpanProcessor(customMemoryExporter), + filterAISpans: true, customFilter, - ); + }); const customProvider = createTracerProvider(BasicTracerProvider, [ customFilterProcessor, ]); @@ -275,10 +271,11 @@ describe("AISpanProcessor", () => { }; const customMemoryExporter = new InMemorySpanExporter(); - const customFilterProcessor = new AISpanProcessor( - new SimpleSpanProcessor(customMemoryExporter), + const customFilterProcessor = new BraintrustSpanProcessor({ + processor: new SimpleSpanProcessor(customMemoryExporter), + filterAISpans: true, customFilter, - ); + }); const customProvider = createTracerProvider(BasicTracerProvider, [ customFilterProcessor, ]); @@ -319,10 +316,11 @@ describe("AISpanProcessor", () => { }; const customMemoryExporter = new InMemorySpanExporter(); - const customFilterProcessor = new AISpanProcessor( - new SimpleSpanProcessor(customMemoryExporter), + const customFilterProcessor = new BraintrustSpanProcessor({ + processor: new SimpleSpanProcessor(customMemoryExporter), + filterAISpans: true, customFilter, - ); + }); const customProvider = createTracerProvider(BasicTracerProvider, [ customFilterProcessor, ]); @@ -361,10 +359,11 @@ describe("AISpanProcessor", () => { }; const customMemoryExporter = new InMemorySpanExporter(); - const customFilterProcessor = new AISpanProcessor( - new SimpleSpanProcessor(customMemoryExporter), + const customFilterProcessor = new BraintrustSpanProcessor({ + processor: new SimpleSpanProcessor(customMemoryExporter), + filterAISpans: true, customFilter, - ); + }); const customProvider = createTracerProvider(BasicTracerProvider, [ customFilterProcessor, ]); @@ -407,10 +406,11 @@ describe("AISpanProcessor", () => { }; const customMemoryExporter = new InMemorySpanExporter(); - const customFilterProcessor = new AISpanProcessor( - new SimpleSpanProcessor(customMemoryExporter), + const customFilterProcessor = new BraintrustSpanProcessor({ + processor: new SimpleSpanProcessor(customMemoryExporter), + filterAISpans: true, customFilter, - ); + }); const customProvider = createTracerProvider(BasicTracerProvider, [ customFilterProcessor, ]); @@ -574,7 +574,16 @@ describe("AISpanProcessor", () => { expected, reason, }) => { - const filterProcessor = new AISpanProcessor({} as any); + const onEnd = vi.fn(); + const filterProcessor = new BraintrustSpanProcessor({ + processor: { + onEnd, + onStart() {}, + async forceFlush() {}, + async shutdown() {}, + }, + filterAISpans: true, + }); const mockSpan = { name: spanName, @@ -601,11 +610,9 @@ describe("AISpanProcessor", () => { droppedLinksCount: 0, } as unknown as ReadableSpan; - const result = (filterProcessor as any).shouldKeepFilteredSpan( - mockSpan, - ); - - expect(result).toBe(expected); + filterProcessor.onEnd(mockSpan); + expect(onEnd).toHaveBeenCalledTimes(expected ? 1 : 0); + void filterProcessor.shutdown(); }, ); }); @@ -627,190 +634,6 @@ describe("BraintrustSpanProcessor", () => { _exportsForTestingOnly.simulateLogoutForTests(); }); - it("should create BraintrustSpanProcessor with API key from environment", () => { - process.env.BRAINTRUST_API_KEY = "test-api-key"; - - expect(() => { - new BraintrustSpanProcessor(); - }).not.toThrow(); - }); - - it("should create BraintrustSpanProcessor with API key from options", () => { - expect(() => { - new BraintrustSpanProcessor({ - apiKey: "test-api-key", - }); - }).not.toThrow(); - }); - - it("should reject forceFlush when no API key is provided", async () => { - delete process.env.BRAINTRUST_API_KEY; - - await withEmptyBraintrustEnvFile(async () => { - const processor = new BraintrustSpanProcessor(); - await expect(processor.forceFlush()).rejects.toThrow( - "Braintrust API key is required", - ); - }); - }); - - it("should create BraintrustSpanProcessor with API key from .env.braintrust", async () => { - delete process.env.BRAINTRUST_API_KEY; - const originalCwd = process.cwd(); - const tempDir = await mkdtemp(path.join(tmpdir(), "braintrust-otel-env-")); - try { - await writeFile( - path.join(tempDir, ".env.braintrust"), - "BRAINTRUST_API_KEY=file-api-key\n", - ); - process.chdir(tempDir); - - const processor = new BraintrustSpanProcessor(); - await expect(processor.forceFlush()).resolves.toBeUndefined(); - } finally { - process.chdir(originalCwd); - await rm(tempDir, { force: true, recursive: true }); - } - }); - - it("should use default API URL when not provided", () => { - process.env.BRAINTRUST_API_KEY = "test-api-key"; - - const processor = new BraintrustSpanProcessor(); - expect(processor).toBeDefined(); - }); - - it("should use custom API URL when provided", () => { - const processor = new BraintrustSpanProcessor({ - apiKey: "test-api-key", - apiUrl: "https://custom.api.url", - }); - expect(processor).toBeDefined(); - }); - - it("should support custom headers", () => { - const processor = new BraintrustSpanProcessor({ - apiKey: "test-api-key", - headers: { - "X-Custom-Header": "custom-value", - }, - }); - expect(processor).toBeDefined(); - }); - - it("should support custom filter function", () => { - const customFilter = (span: ReadableSpan) => { - return span.name.includes("important"); - }; - - const processor = new BraintrustSpanProcessor({ - apiKey: "test-api-key", - customFilter, - }); - expect(processor).toBeDefined(); - }); - - it("should support parent option", () => { - const processor = new BraintrustSpanProcessor({ - apiKey: "test-api-key", - parent: "project_name:otel_examples", - }); - expect(processor).toBeDefined(); - }); - - it("should use BRAINTRUST_PARENT environment variable", () => { - process.env.BRAINTRUST_API_KEY = "test-api-key"; - process.env.BRAINTRUST_PARENT = "project_name:env_examples"; - - const processor = new BraintrustSpanProcessor(); - expect(processor).toBeDefined(); - }); - - it("should use BRAINTRUST_API_URL environment variable", () => { - process.env.BRAINTRUST_API_KEY = "test-api-key"; - process.env.BRAINTRUST_API_URL = "https://custom.env.url"; - - const processor = new BraintrustSpanProcessor(); - expect(processor).toBeDefined(); - }); - - it("should prioritize options over environment variables", () => { - process.env.BRAINTRUST_API_KEY = "env-api-key"; - process.env.BRAINTRUST_PARENT = "env-parent"; - process.env.BRAINTRUST_API_URL = "https://env.url"; - - const processor = new BraintrustSpanProcessor({ - apiKey: "option-api-key", - parent: "option-parent", - apiUrl: "https://option.url", - }); - expect(processor).toBeDefined(); - }); - - it("should disable filtering by default", () => { - const processor = new BraintrustSpanProcessor({ - apiKey: "test-api-key", - }); - expect(processor).toBeDefined(); - }); - - it("should enable filtering when filterAISpans is true", () => { - const processor = new BraintrustSpanProcessor({ - apiKey: "test-api-key", - filterAISpans: true, - }); - expect(processor).toBeDefined(); - }); - - it("should disable filtering when filterAISpans is false", () => { - const processor = new BraintrustSpanProcessor({ - apiKey: "test-api-key", - filterAISpans: false, - }); - expect(processor).toBeDefined(); - }); - - it("should implement SpanProcessor interface", () => { - process.env.BRAINTRUST_API_KEY = "test-api-key"; - - const processor = new BraintrustSpanProcessor(); - - expect(typeof processor.onStart).toBe("function"); - expect(typeof processor.onEnd).toBe("function"); - expect(typeof processor.shutdown).toBe("function"); - expect(typeof processor.forceFlush).toBe("function"); - }); - - it("should forward span lifecycle methods to AISpanProcessor", async () => { - process.env.BRAINTRUST_API_KEY = "test-api-key"; - - const processor = new BraintrustSpanProcessor(); - - // Create a mock span - const mockSpan = { - spanContext: () => ({ traceId: "test-trace", spanId: "test-span" }), - end: vi.fn(), - setAttributes: vi.fn(), - name: "test-span", - attributes: {}, - parentSpanContext: undefined, - } as any; - - // Test onStart - expect(() => { - processor.onStart(mockSpan, context.active()); - }).not.toThrow(); - - // Test onEnd - expect(() => { - processor.onEnd(mockSpan); - }).not.toThrow(); - - // Test shutdown and forceFlush - await expect(processor.shutdown()).resolves.toBeUndefined(); - await expect(processor.forceFlush()).resolves.toBeUndefined(); - }); - it("should merge span origin with context_json set after span start", () => { let endedSpan: ReadableSpan | undefined; const innerProcessor = { @@ -823,8 +646,7 @@ describe("BraintrustSpanProcessor", () => { }; const processor = new BraintrustSpanProcessor({ - apiKey: "test-api-key", - _spanProcessor: innerProcessor as any, + processor: innerProcessor as any, }); const mockSpan = { @@ -867,8 +689,7 @@ describe("BraintrustSpanProcessor", () => { }; const processor = new BraintrustSpanProcessor({ - apiKey: "test-api-key", - _spanProcessor: innerProcessor as any, + processor: innerProcessor as any, }); const mockSpan = { @@ -906,8 +727,7 @@ describe("BraintrustSpanProcessor", () => { }; const processor = new BraintrustSpanProcessor({ - apiKey: "test-api-key", - _spanProcessor: innerProcessor as any, + processor: innerProcessor as any, }); const mockSpan = { @@ -945,8 +765,7 @@ describe("BraintrustSpanProcessor", () => { }; const processor = new BraintrustSpanProcessor({ - apiKey: "test-api-key", - _spanProcessor: innerProcessor as any, + processor: innerProcessor as any, }); const mockSpan = { @@ -965,364 +784,6 @@ describe("BraintrustSpanProcessor", () => { ); expect(parsed.span_origin.environment).toEqual({ name: "preview" }); }); - - it("should use default parent when none is provided", () => { - process.env.BRAINTRUST_API_KEY = "test-api-key"; - delete process.env.BRAINTRUST_PARENT; - - const consoleSpy = vi.spyOn(console, "info"); - - const processor = new BraintrustSpanProcessor(); - - expect(processor).toBeDefined(); - expect(consoleSpy).toHaveBeenCalledWith( - "No parent specified, using default: project_name:default-otel-project. " + - "Configure with BRAINTRUST_PARENT environment variable or parent parameter.", - ); - - consoleSpy.mockRestore(); - }); - - it("should not use default parent when BRAINTRUST_PARENT is set", () => { - process.env.BRAINTRUST_API_KEY = "test-api-key"; - process.env.BRAINTRUST_PARENT = "my-project:my-experiment"; - - const consoleSpy = vi.spyOn(console, "info"); - - const processor = new BraintrustSpanProcessor(); - - expect(processor).toBeDefined(); - expect(consoleSpy).not.toHaveBeenCalled(); - - consoleSpy.mockRestore(); - }); - - it("should not use default parent when parent option is provided", () => { - process.env.BRAINTRUST_API_KEY = "test-api-key"; - delete process.env.BRAINTRUST_PARENT; - - const consoleSpy = vi.spyOn(console, "info"); - - const processor = new BraintrustSpanProcessor({ - parent: "option-project:option-experiment", - }); - - expect(processor).toBeDefined(); - expect(consoleSpy).not.toHaveBeenCalled(); - - consoleSpy.mockRestore(); - }); -}); - -describe("BraintrustExporter", () => { - let originalEnv: NodeJS.ProcessEnv; - - beforeEach(async () => { - originalEnv = { ...process.env }; - await _exportsForTestingOnly.simulateLoginForTests(); - _exportsForTestingOnly.useTestBackgroundLogger(); - }); - - afterEach(() => { - restoreEnvironment(originalEnv); - vi.restoreAllMocks(); - _exportsForTestingOnly.clearTestBackgroundLogger(); - _exportsForTestingOnly.simulateLogoutForTests(); - }); - - it("should create BraintrustExporter with API key from environment", () => { - process.env.BRAINTRUST_API_KEY = "test-api-key"; - - expect(() => { - new BraintrustExporter(); - }).not.toThrow(); - }); - - it("should create BraintrustExporter with API key from options", () => { - expect(() => { - new BraintrustExporter({ - apiKey: "test-api-key", - }); - }).not.toThrow(); - }); - - it("should reject forceFlush when no API key is provided", async () => { - delete process.env.BRAINTRUST_API_KEY; - - await withEmptyBraintrustEnvFile(async () => { - const exporter = new BraintrustExporter(); - await expect(exporter.forceFlush()).rejects.toThrow( - "Braintrust API key is required", - ); - }); - }); - - it("should use same options as BraintrustSpanProcessor", () => { - const options = { - apiKey: "test-api-key", - apiUrl: "https://custom.api.url", - parent: "project_name:test", - filterAISpans: true, - customFilter: (span: ReadableSpan) => span.name.includes("important"), - headers: { "X-Custom": "value" }, - }; - - expect(() => { - new BraintrustExporter(options); - }).not.toThrow(); - }); - - it("should implement exporter interface", () => { - process.env.BRAINTRUST_API_KEY = "test-api-key"; - - const exporter = new BraintrustExporter(); - - expect(typeof exporter.export).toBe("function"); - expect(typeof exporter.shutdown).toBe("function"); - expect(typeof exporter.forceFlush).toBe("function"); - }); - - it("should export spans successfully", async () => { - process.env.BRAINTRUST_API_KEY = "test-api-key"; - - const exporter = new BraintrustExporter(); - const mockSpans = [ - { - name: "test-span", - spanContext: () => ({ traceId: "test-trace", spanId: "test-span" }), - attributes: {}, - parentSpanContext: undefined, - }, - ] as unknown as ReadableSpan[]; - - return new Promise((resolve, reject) => { - exporter.export(mockSpans, (result) => { - try { - expect(result.code).toBe(0); // SUCCESS - resolve(); - } catch (error) { - reject(error); - } - }); - }); - }); - - it("should handle export errors gracefully", async () => { - process.env.BRAINTRUST_API_KEY = "test-api-key"; - - const exporter = new BraintrustExporter(); - - // Mock the processor to throw an error - (exporter as any).processor = { - onEnd: vi.fn().mockImplementation(() => { - throw new Error("Test error"); - }), - forceFlush: vi.fn(), - shutdown: vi.fn(), - }; - - const mockSpans = [ - { - name: "test-span", - spanContext: () => ({ traceId: "test-trace", spanId: "test-span" }), - attributes: {}, - parentSpanContext: undefined, - }, - ] as unknown as ReadableSpan[]; - - return new Promise((resolve, reject) => { - exporter.export(mockSpans, (result) => { - try { - expect(result.code).toBe(1); // FAILURE - expect(result.error).toBeDefined(); - resolve(); - } catch (error) { - reject(error); - } - }); - }); - }); - - it("should handle forceFlush errors gracefully", async () => { - process.env.BRAINTRUST_API_KEY = "test-api-key"; - - const exporter = new BraintrustExporter(); - - // Mock the processor to have forceFlush fail - (exporter as any).processor = { - onEnd: vi.fn(), - forceFlush: vi.fn().mockRejectedValue(new Error("Flush error")), - shutdown: vi.fn(), - }; - - const mockSpans = [ - { - name: "test-span", - spanContext: () => ({ traceId: "test-trace", spanId: "test-span" }), - attributes: {}, - parentSpanContext: undefined, - }, - ] as unknown as ReadableSpan[]; - - return new Promise((resolve, reject) => { - exporter.export(mockSpans, (result) => { - try { - expect(result.code).toBe(1); // FAILURE - expect(result.error).toBeDefined(); - resolve(); - } catch (error) { - reject(error); - } - }); - }); - }); - - it("should forward shutdown to processor", async () => { - process.env.BRAINTRUST_API_KEY = "test-api-key"; - - const exporter = new BraintrustExporter(); - const shutdownSpy = vi.spyOn((exporter as any).processor, "shutdown"); - - await exporter.shutdown(); - - expect(shutdownSpy).toHaveBeenCalled(); - }); - - it("should forward forceFlush to processor", async () => { - process.env.BRAINTRUST_API_KEY = "test-api-key"; - - const exporter = new BraintrustExporter(); - const flushSpy = vi.spyOn((exporter as any).processor, "forceFlush"); - - await exporter.forceFlush(); - - expect(flushSpy).toHaveBeenCalled(); - }); - - it("should process multiple spans", async () => { - process.env.BRAINTRUST_API_KEY = "test-api-key"; - - const exporter = new BraintrustExporter(); - const onEndSpy = vi.spyOn((exporter as any).processor, "onEnd"); - - const mockSpans = [ - { - name: "test-span-1", - spanContext: () => ({ traceId: "test-trace", spanId: "test-span-1" }), - attributes: {}, - parentSpanContext: undefined, - }, - { - name: "test-span-2", - spanContext: () => ({ traceId: "test-trace", spanId: "test-span-2" }), - attributes: {}, - parentSpanContext: undefined, - }, - ] as unknown as ReadableSpan[]; - - return new Promise((resolve, reject) => { - exporter.export(mockSpans, (result) => { - try { - expect(result.code).toBe(0); // SUCCESS - expect(onEndSpy).toHaveBeenCalledTimes(2); - expect(onEndSpy).toHaveBeenCalledWith(mockSpans[0]); - expect(onEndSpy).toHaveBeenCalledWith(mockSpans[1]); - resolve(); - } catch (error) { - reject(error); - } - }); - }); - }); - - it("should support filtering options", () => { - const exporter = new BraintrustExporter({ - apiKey: "test-api-key", - filterAISpans: true, - customFilter: (span: ReadableSpan) => span.name.includes("important"), - }); - - expect(exporter).toBeDefined(); - }); - - it("should support all configuration options", () => { - process.env.BRAINTRUST_API_KEY = "env-key"; - process.env.BRAINTRUST_PARENT = "env-parent"; - process.env.BRAINTRUST_API_URL = "https://env.url"; - - const exporter = new BraintrustExporter({ - apiKey: "option-key", - parent: "option-parent", - apiUrl: "https://option.url", - filterAISpans: false, - headers: { "X-Custom": "value" }, - }); - - expect(exporter).toBeDefined(); - }); - - it("should use default parent when none is provided", () => { - process.env.BRAINTRUST_API_KEY = "test-api-key"; - delete process.env.BRAINTRUST_PARENT; - - const consoleSpy = vi.spyOn(console, "info"); - - const exporter = new BraintrustExporter(); - - expect(exporter).toBeDefined(); - expect(consoleSpy).toHaveBeenCalledWith( - "No parent specified, using default: project_name:default-otel-project. " + - "Configure with BRAINTRUST_PARENT environment variable or parent parameter.", - ); - - consoleSpy.mockRestore(); - }); - - it("exporter handles spans with common v1/v2 attributes", () => { - process.env.BRAINTRUST_API_KEY = "test-api-key"; - - const exporter = new BraintrustExporter(); - const processor = (exporter as any).processor; - const batchProcessor = (processor as any).processor; - const baseExporter = (batchProcessor as any)._exporter; - - const mockExport = vi.fn(); - baseExporter.export = mockExport; - - const testSpan = { - name: "gen_ai.completion", - spanContext: () => ({ traceId: "trace-123", spanId: "span-456" }), - parentSpanId: "parent-789", - instrumentationLibrary: { name: "openai", version: "1.0.0" }, - resource: { - attributes: { - "service.name": "test-service", - }, - }, - } as any; - - baseExporter.export([testSpan], () => {}); - expect(mockExport).toHaveBeenCalledOnce(); - const [exportedSpans] = mockExport.mock.calls[0]; - - expect(exportedSpans).toHaveLength(1); - const exportedSpan = exportedSpans[0]; - - // Check attributes that exist in both v1 and v2 - expect(exportedSpan.name).toBe("gen_ai.completion"); - expect(exportedSpan.spanContext).toBeTypeOf("function"); - expect(exportedSpan.spanContext()).toEqual({ - traceId: "trace-123", - spanId: "span-456", - }); - expect(exportedSpan.instrumentationLibrary).toEqual({ - name: "openai", - version: "1.0.0", - }); - expect(exportedSpan.resource.attributes).toEqual({ - "service.name": "test-service", - }); - }); }); describe("otel namespace helpers", () => { diff --git a/integrations/otel-js/src/otel.ts b/integrations/otel-js/src/otel.ts index ce431834d..522b9fdf5 100644 --- a/integrations/otel-js/src/otel.ts +++ b/integrations/otel-js/src/otel.ts @@ -1,32 +1,20 @@ import { registerOtelFlush } from "braintrust/instrumentation"; -import { - customizeOtelSpans, - isCustomizedOtelSpan, - otelCompatFields, -} from "./span-customizer"; +import { customizeOtelSpans, isCustomizedOtelSpan } from "./span-customizer"; -import { OTLPTraceExporter } from "@opentelemetry/exporter-trace-otlp-http"; -import { - CompositePropagator, - W3CBaggagePropagator, - W3CTraceContextPropagator, -} from "@opentelemetry/core"; import { context, Context, - diag, trace, propagation, Span, - type TextMapGetter, + createTraceState, } from "@opentelemetry/api"; -import { +import type { SpanProcessor, ReadableSpan, - BatchSpanProcessor, - type Span as SDKSpan, - type SpanExporter, -} from "@opentelemetry/sdk-trace-base"; + SpanExporter, + ExportResult, +} from "./types"; import { currentSpan, extractTraceContextFromHeaders, @@ -36,11 +24,6 @@ import { declare const __BRAINTRUST_OTEL_VERSION__: string; -interface ExportResult { - code: number; - error?: Error; -} - const FILTER_PREFIXES = [ "gen_ai.", "braintrust.", @@ -54,20 +37,14 @@ const SYSTEM_ATTRIBUTE_NAMES = new Set([ "braintrust.context_json", ]); -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. * @param span - The span to evaluate * @returns true to definitely keep, false to definitely drop, null/undefined to not influence the decision */ -type CustomSpanFilter = (span: ReadableSpan) => boolean | null | undefined; +type CustomSpanFilter = ( + span: S, +) => boolean | null | undefined; /** * Type guard to check if a span has the attributes property (i.e., is a ReadableSpan). @@ -81,10 +58,7 @@ function isReadableSpan(span: Span | ReadableSpan): span is ReadableSpan { * Checks both parentSpanId (OTel 1.x) and parentSpanContext (OTel 2.x). */ export function isRootSpan(span: ReadableSpan): boolean { - const hasParent = - ("parentSpanId" in span && (span as any).parentSpanId) || - ("parentSpanContext" in span && (span as any).parentSpanContext); - return !hasParent; + return !span.parentSpanId && !span.parentSpanContext; } /** @@ -110,121 +84,57 @@ function isAISpan(span: ReadableSpan): boolean { return false; } -/** - * A span processor that filters spans to only export filtered telemetry. - * - * Only filtered spans and root spans will be forwarded to the inner processor. - * This dramatically reduces telemetry volume while preserving important observability. - * - * @example - * ```typescript - * const processor = new AISpanProcessor(new BatchSpanProcessor(new OTLPTraceExporter())); - * const provider = new TracerProvider(); - * provider.addSpanProcessor(processor); - * ``` - */ -export class AISpanProcessor { - private readonly processor: SpanProcessor; - private readonly customFilter: CustomSpanFilter; - - /** - * Initialize the filter span processor. - * - * @param processor - The wrapped span processor that will receive filtered spans - * @param customFilter - Optional function that takes a span and returns: - * true to keep, false to drop, - * null/undefined to not influence the decision - */ - constructor(processor: SpanProcessor, customFilter?: CustomSpanFilter) { - this.processor = processor; - this.customFilter = customFilter || isAISpan; - } - - /** - * Forward span start events to the inner processor. - */ - onStart(span: SDKSpan, parentContext: Context): void { - this.processor.onStart(span, parentContext); - } - - /** - * Apply filtering logic and conditionally forward span end events. - */ - onEnd(span: ReadableSpan): void { - const shouldKeep = this.shouldKeepFilteredSpan(span); - if (shouldKeep) { - this.processor.onEnd(span); - } - } - - /** - * Shutdown the inner processor. - */ - shutdown(): Promise { - return this.processor.shutdown(); - } - - /** - * Force flush the inner processor. - */ - forceFlush(): Promise { - return this.processor.forceFlush(); - } - - /** - * Determine if a span should be kept based on filtering criteria. - * - * Keep spans if: - * 1. Custom filter returns true/false (if provided) - * 2. Span name starts with 'gen_ai.', 'braintrust.', 'llm.', 'ai.', or 'traceloop.' - * 3. Any attribute name starts with those prefixes - */ - private shouldKeepFilteredSpan(span: ReadableSpan): boolean { - if (!span) { - return false; - } - const result = this.customFilter(span); - if (typeof result === "boolean") { - return result; - } - // Fallback: if customFilter returns undefined or null, use isAISpan - return isAISpan(span); - } -} - -interface BraintrustSpanProcessorOptions { - /** - * Braintrust API key. If not provided, will use BRAINTRUST_API_KEY, then - * the nearest .env.braintrust file in Node.js. - */ +interface BraintrustOtlpOptions { + /** Defaults to BRAINTRUST_API_KEY or the nearest .env.braintrust file. */ apiKey?: string; - /** - * Braintrust API URL. If not provided, will use BRAINTRUST_API_URL environment variable. Defaults to https://api.braintrust.dev - */ + /** Defaults to BRAINTRUST_API_URL or https://api.braintrust.dev. */ apiUrl?: string; - /** - * Braintrust parent project name (e.g., "project_name:otel_examples"). If not provided, will use BRAINTRUST_PARENT environment variable. - */ + /** Required project or experiment routing, or BRAINTRUST_PARENT. */ parent?: string; - /** - * Whether to enable AI span filtering. Defaults to false. - */ - filterAISpans?: boolean; - /** - * Custom filter function for span filtering - */ - customFilter?: CustomSpanFilter; - /** - * Additional headers to send with telemetry data - */ headers?: Record; +} + +interface SpanOptions { + filterAISpans?: boolean; + customFilter?: CustomSpanFilter; environment?: { type?: string; name?: string }; - /** - * @internal - * Internal option for dependency injection during testing. - * If provided, this processor will be used instead of creating an OTLP exporter. - */ - _spanProcessor?: SpanProcessor; +} + +/** Configure the application's OTLP/HTTP exporter without selecting an SDK. */ +export function braintrustOtlpConfig(options: BraintrustOtlpOptions = {}): { + url: string; + headers: Record; +} { + const apiKey = options.apiKey ?? 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.", + ); + } + const parent = options.parent ?? getEnv("BRAINTRUST_PARENT"); + if (!parent?.trim()) { + throw new Error( + "Braintrust parent is required. Set BRAINTRUST_PARENT or pass parent.", + ); + } + const apiUrl = + options.apiUrl ?? + getEnv("BRAINTRUST_API_URL") ?? + "https://api.braintrust.dev"; + return { + url: new URL("otel/v1/traces", apiUrl.endsWith("/") ? apiUrl : `${apiUrl}/`) + .href, + headers: { + ...Object.fromEntries( + Object.entries(options.headers ?? {}).filter( + ([name]) => + !["authorization", "x-bt-parent"].includes(name.toLowerCase()), + ), + ), + Authorization: `Bearer ${apiKey}`, + "x-bt-parent": parent, + }, + }; } const SDK_VERSION = __BRAINTRUST_OTEL_VERSION__; @@ -274,10 +184,11 @@ function parseContextJson(contextJson: string): Record { } } -function withSpanOriginAttributes( - span: ReadableSpan, +function withSpanOriginAttributes( + span: S, environment?: { type?: string; name?: string }, -): ReadableSpan { +): S { + if (isCustomizedOtelSpan(span)) return span; const attributes = { ...span.attributes, "braintrust.context_json": mergeSpanOriginContextJson( @@ -286,9 +197,9 @@ function withSpanOriginAttributes( ), }; return new Proxy(span, { - get(target, prop, receiver) { + get(target, prop) { if (prop === "attributes") return attributes; - const value = Reflect.get(target, prop, receiver); + const value = Reflect.get(target, prop, target); return typeof value === "function" ? value.bind(target) : value; }, }); @@ -399,341 +310,165 @@ function detectEnvironment(explicit?: { return { name: nodeEnv }; } -class LazyBraintrustOTLPTraceExporter implements SpanExporter { - private readonly diagLogger = diag.createComponentLogger({ - namespace: "@braintrust/otel", - }); - private exporter?: OTLPTraceExporter; - private exporterPromise?: Promise; +/** + * Enrich and filter spans before forwarding to an application-owned processor. + * Batching, sampling, export timeouts and transport remain owned by that SDK. + * Use BraintrustExporter around the transport to apply export customizers. + */ +export class BraintrustSpanProcessor { + private readonly processor: SpanProcessor; + private readonly environment?: { type?: string; name?: string }; + private readonly unregisterFlush: () => void; + private shutdownPromise?: Promise; constructor( - private readonly apiKey: string | undefined, - private readonly apiUrl: string, - private readonly parent: string, - private readonly headers: Record | undefined, - ) {} - - async initialize(): Promise { - await this.getExporter(); + private readonly options: SpanOptions & { processor: SpanProcessor }, + ) { + this.processor = options.processor; + this.environment = detectEnvironment(options.environment); + this.unregisterFlush = registerOtelFlush(() => this.forceFlush()); + } + + onStart(span: Span, parentContext: Context): void { + const baggageParent = propagation + .getBaggage(parentContext) + ?.getEntry("braintrust.parent")?.value; + const parentSpan = trace.getSpan(parentContext); + const inherited = + parentSpan && "attributes" in parentSpan + ? (parentSpan.attributes as ReadableSpan["attributes"])[ + "braintrust.parent" + ] + : undefined; + const parent = + baggageParent ?? + (typeof inherited === "string" ? inherited : undefined) ?? + (parentContext === context.active() + ? getOtelParentFromSpan(currentSpan()) + : undefined); + if (parent) span.setAttribute("braintrust.parent", parent); + this.processor.onStart(span, parentContext); } - export( - spans: ReadableSpan[], - resultCallback: (result: ExportResult) => void, - ): void { - void this.getExporter() - .then(async (exporter) => { - // Customize at the export boundary: upstream processors have already - // filtered, enriched and dropped unsampled spans. - const customizedSpans = await customizeOtelSpans(spans); - const compatibleSpans = customizedSpans.map((span) => { - // Customized snapshots already carry both OTel 1.x and 2.x fields. - if (isCustomizedOtelSpan(span)) return span; - const { - instrumentationScope, - instrumentationLibrary, - parentSpanContext, - parentSpanId, - } = otelCompatFields(span); - - return new Proxy(span, { - get(target, prop) { - if (prop === "instrumentationScope") { - return instrumentationScope; - } - if (prop === "instrumentationLibrary") { - return instrumentationLibrary; - } - if (prop === "parentSpanContext") { - return parentSpanContext; - } - if (prop === "parentSpanId") { - return parentSpanId; - } - - const value = Reflect.get(target, prop, target); - return typeof value === "function" ? value.bind(target) : value; - }, - has(target, prop) { - return ( - prop === "instrumentationScope" || - prop === "instrumentationLibrary" || - prop === "parentSpanContext" || - prop === "parentSpanId" || - prop in target - ); - }, - }); - }); - - exporter.export(compatibleSpans, (result) => { - if (result.code !== 0) { - this.diagLogger.error( - "Braintrust OTLP span export failed", - result.error, - ); - } - resultCallback(result); - }); - }) - .catch((error) => { - const errorObj = - error instanceof Error ? error : new Error(String(error)); - this.diagLogger.error("Braintrust OTLP span export failed", errorObj); - resultCallback({ code: 1, error: errorObj }); - }); + onEnding(span: Span): void { + this.processor.onEnding?.(span); } - shutdown(): Promise { - if (!this.exporterPromise) { - return Promise.resolve(); - } - return this.getExporter().then((exporter) => exporter.shutdown()); + onEnd(span: S): void { + const enriched = withSpanOriginAttributes(span, this.environment); + if ( + this.options.filterAISpans && + !(this.options.customFilter?.(enriched) ?? isAISpan(enriched)) + ) + return; + this.processor.onEnd(enriched); + } + + forceFlush(): Promise { + return this.processor.forceFlush(); } - private getExporter(): Promise { - if (!this.exporterPromise) { - this.exporterPromise = (async () => { - const apiKey = - this.apiKey !== undefined - ? this.apiKey - : 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.", - ); - } - - this.exporter = new OTLPTraceExporter({ - url: new URL("otel/v1/traces", this.apiUrl).href, - headers: { - Authorization: `Bearer ${apiKey}`, - "Content-Type": "application/json", - "x-bt-parent": this.parent, - ...this.headers, - }, - }); - return this.exporter; - })(); + shutdown(): Promise { + if (!this.shutdownPromise) { + this.unregisterFlush(); + this.shutdownPromise = this.processor.shutdown(); } - return this.exporterPromise; + return this.shutdownPromise; } } /** - * A span processor that sends OpenTelemetry spans to Braintrust. - * - * This processor uses a BatchSpanProcessor and an OTLP exporter configured - * to send data to Braintrust's telemetry endpoint. Span filtering is disabled - * by default but can be enabled with the filterAISpans option. - * - * Environment Variables: - * - BRAINTRUST_API_KEY: Your Braintrust API key. In Node.js, this can also be set in the nearest .env.braintrust file. - * - BRAINTRUST_PARENT: Parent identifier (e.g., "project_name:test") - * - BRAINTRUST_API_URL: Base URL for Braintrust API (defaults to https://api.braintrust.dev) - * - * @example - * ```typescript - * const processor = new BraintrustSpanProcessor({ - * apiKey: 'your-api-key', - * apiUrl: 'https://api.braintrust.dev' - * }); - * const provider = new TracerProvider(); - * provider.addSpanProcessor(processor); - * ``` - * - * @example With span filtering enabled: - * ```typescript - * const processor = new BraintrustSpanProcessor({ - * apiKey: 'your-api-key', - * filterAISpans: true - * }); - * ``` - * - * @example Using environment variables: - * ```typescript - * // Set environment variables: - * // BRAINTRUST_API_KEY=your-api-key - * // BRAINTRUST_PARENT=project_name:test - * // BRAINTRUST_API_URL=https://api.braintrust.dev - * const processor = new BraintrustSpanProcessor(); - * ``` + * Customize and forward completed batches to an application-owned exporter. + * This exporter never creates a processor, queue or timer. */ -export class BraintrustSpanProcessor implements SpanProcessor { - private readonly processor: SpanProcessor; - private readonly aiSpanProcessor: SpanProcessor; - private readonly exporter?: LazyBraintrustOTLPTraceExporter; +export class BraintrustExporter { private readonly environment?: { type?: string; name?: string }; + private readonly pending = new Set>(); + private shutdownPromise?: Promise; - constructor(options: BraintrustSpanProcessorOptions = {}) { + constructor( + private readonly options: SpanOptions & { exporter: SpanExporter }, + ) { this.environment = detectEnvironment(options.environment); - // If a processor is injected (for testing), use it directly - if (options._spanProcessor) { - this.processor = options._spanProcessor; - // Apply filtering if requested - if (options.filterAISpans === true) { - this.aiSpanProcessor = new AISpanProcessor( - this.processor, - options.customFilter, - ); - } else { - this.aiSpanProcessor = this.processor; - } - // Register forceFlush callback with main SDK - registerOtelFlush(() => this.forceFlush()); - return; - } - - // Get API key from options or environment - const apiKey = - options.apiKey !== undefined - ? options.apiKey - : getEnv("BRAINTRUST_API_KEY"); - - // Get API URL from options or environment - let apiUrl = - options.apiUrl || - process.env.BRAINTRUST_API_URL || - "https://api.braintrust.dev"; - - // Ensure apiUrl ends with / for proper joining - if (!apiUrl.endsWith("/")) { - apiUrl += "/"; - } - - // Get parent from options or environment - let parent = options.parent || process.env.BRAINTRUST_PARENT; + } - // Default parent if not provided - if (!parent) { - parent = "project_name:default-otel-project"; - console.info( - `No parent specified, using default: ${parent}. ` + - "Configure with BRAINTRUST_PARENT environment variable or parent parameter.", - ); + export(spans: S[], callback: (result: ExportResult) => void): void { + if (this.shutdownPromise) { + callback({ + code: 1, + error: new Error("BraintrustExporter is shut down"), + }); + return; } - - const exporter = new LazyBraintrustOTLPTraceExporter( - apiKey, - apiUrl, - parent, - options.headers, - ); - this.exporter = exporter; - - this.processor = new BatchSpanProcessor(exporter); - // Conditionally wrap with filtering based on filterAISpans flag - if (options.filterAISpans === true) { - // Only enable filtering if explicitly requested - this.aiSpanProcessor = new AISpanProcessor( - this.processor, - options.customFilter, + let complete!: () => void; + const pending = new Promise((resolve) => { + complete = resolve; + }); + this.pending.add(pending); + let finished = false; + const finish = (result: ExportResult) => { + if (finished) return; + finished = true; + this.pending.delete(pending); + complete(); + callback(result); + }; + void (async () => { + const filtered = this.options.filterAISpans + ? spans.filter( + (span) => this.options.customFilter?.(span) ?? isAISpan(span), + ) + : spans; + const prepared = await customizeOtelSpans( + filtered.map((span) => + withSpanOriginAttributes(span, this.environment), + ), ); - } else { - // Use the batch processor directly without filtering (default behavior) - this.aiSpanProcessor = this.processor; - } - - // Register forceFlush callback with main SDK so OTEL spans get flushed - // when scorers call trace.getSpans() and need to query BTQL - registerOtelFlush(() => this.forceFlush()); - } - - onStart(span: SDKSpan, parentContext: Context): void { - try { - let parentValue: string | undefined; - - // Priority 1: Check if braintrust.parent is in current OTEL context - if (context) { - const currentContext = context.active(); - const contextValue = currentContext.getValue?.( - // eslint-disable-next-line @typescript-eslint/consistent-type-assertions, @typescript-eslint/no-explicit-any - "braintrust.parent" as any, - ); - if (typeof contextValue === "string") { - parentValue = contextValue; - } - - // Priority 2: Check if parent_context has braintrust.parent (backup) - if (!parentValue && parentContext) { - const parentContextValue = - typeof parentContext.getValue === "function" - ? // eslint-disable-next-line @typescript-eslint/consistent-type-assertions, @typescript-eslint/no-explicit-any - parentContext.getValue("braintrust.parent" as any) - : undefined; - if (typeof parentContextValue === "string") { - parentValue = parentContextValue; - } - } - - // Priority 3: Check if parent OTEL span has braintrust.parent attribute - if (!parentValue && parentContext) { - parentValue = this._getParentOtelBraintrustParent(parentContext); - } - - // Priority 4: Check the active Braintrust span directly - if (!parentValue) { - const braintrustSpan = currentSpan(); - if (braintrustSpan.rootSpanId && braintrustSpan.spanId) { - parentValue = getOtelParentFromSpan(braintrustSpan); - } - } - - // Set the attribute if we found a parent value - if (parentValue) { - span.setAttributes?.({ "braintrust.parent": parentValue }); - } + if (prepared.length === 0) { + finish({ code: 0 }); + return; } - } catch { - // If there's an exception, just don't set braintrust.parent - } - - this.aiSpanProcessor.onStart(span, parentContext); - } - - private _getParentOtelBraintrustParent( - parentContext: Context, - ): string | undefined { - const currentSpan = trace?.getSpan(parentContext); - - if ( - currentSpan && - typeof currentSpan === "object" && - "attributes" in currentSpan && - typeof currentSpan.attributes === "object" - ) { - // eslint-disable-next-line @typescript-eslint/consistent-type-assertions - const attributes = currentSpan.attributes as Record; - const parentAttr = attributes["braintrust.parent"]; - return typeof parentAttr === "string" ? parentAttr : undefined; - } - - return undefined; + this.options.exporter.export(prepared, finish); + })().catch((error: unknown) => { + finish({ + code: 1, + error: error instanceof Error ? error : new Error(String(error)), + }); + }); } - onEnd(span: ReadableSpan): void { - this.aiSpanProcessor.onEnd( - withSpanOriginAttributes(span, this.environment), - ); + async forceFlush(): Promise { + await Promise.all(this.pending); + await this.options.exporter.forceFlush?.(); } shutdown(): Promise { - return this.aiSpanProcessor.shutdown(); - } - - async forceFlush(): Promise { - await this.exporter?.initialize(); - return this.aiSpanProcessor.forceFlush(); + this.shutdownPromise ??= (async () => { + await Promise.all(this.pending); + await this.options.exporter.shutdown(); + })(); + return this.shutdownPromise; } } /** 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, - ); + const headers = extractTraceContextFromHeaders(span.inject()); + if (!headers) return context.active(); + // The native SDK has already validated the W3C header. + const [, traceId, spanId, flags] = headers.traceparent.split("-"); + let ctx = trace.setSpanContext(context.active(), { + traceId, + spanId, + traceFlags: parseInt(flags, 16), + isRemote: true, + ...(headers.tracestate + ? { traceState: createTraceState(headers.tracestate) } + : {}), + }); + const parent = getOtelParentFromSpan(span); + if (parent) ctx = addParentToBaggage(parent, ctx); + return ctx; } /** @@ -761,106 +496,6 @@ export function getOtelParentFromSpan( return undefined; } -/** - * A trace exporter that sends OpenTelemetry spans to Braintrust. - - - * - * This exporter wraps the standard OTLP trace exporter and can be used with - * any OpenTelemetry setup, including @vercel/otel's registerOTel function, - * NodeSDK, or custom tracer providers. It can optionally filter spans to - * only send AI-related telemetry. - * - * Environment Variables: - * - BRAINTRUST_API_KEY: Your Braintrust API key. In Node.js, this can also be set in the nearest .env.braintrust file. - * - BRAINTRUST_PARENT: Parent identifier (e.g., "project_name:test") - * - BRAINTRUST_API_URL: Base URL for Braintrust API (defaults to https://api.braintrust.dev) - * - * @example With @vercel/otel: - * ```typescript - * import { registerOTel } from '@vercel/otel'; - * import { BraintrustExporter } from '@braintrust/otel'; - * - * export function register() { - * registerOTel({ - * serviceName: 'my-app', - * traceExporter: new BraintrustExporter({ - * filterAISpans: true, - * }), - * }); - * } - * ``` - * - * @example With NodeSDK: - * ```typescript - * import { NodeSDK } from '@opentelemetry/sdk-node'; - * import { BatchSpanProcessor } from '@opentelemetry/sdk-trace-base'; - * import { BraintrustExporter } from 'braintrust'; - * - * const sdk = new NodeSDK({ - * spanProcessors: [ - * new BatchSpanProcessor(new BraintrustExporter({ - * apiKey: 'your-api-key', - * parent: 'project_name:test' - * })) - * ] - * }); - * ``` - */ -export class BraintrustExporter { - private readonly processor: BraintrustSpanProcessor; - - constructor(options: BraintrustSpanProcessorOptions = {}) { - // Use BraintrustSpanProcessor under the hood - this.processor = new BraintrustSpanProcessor(options); - } - - /** - * Export spans to Braintrust by simulating span processor behavior. - */ - export( - spans: ReadableSpan[], - resultCallback: (result: ExportResult) => void, - ): void { - try { - // Process each span through the processor - spans.forEach((span) => { - this.processor.onEnd(span); - }); - - // Force flush to ensure spans are sent - this.processor - .forceFlush() - .then(() => { - resultCallback({ code: 0 }); // SUCCESS - }) - .catch((error) => { - const errorObj = - error instanceof Error ? error : new Error(String(error)); - resultCallback({ code: 1, error: errorObj }); // FAILURE - }); - } catch (error) { - const errorObj = - error instanceof Error ? error : new Error(String(error)); - resultCallback({ code: 1, error: errorObj }); // FAILURE - } - } - - /** - * Shutdown the exporter. - */ - shutdown(): Promise { - return this.processor.shutdown(); - } - - /** - * Force flush the exporter. - */ - forceFlush(): Promise { - return this.processor.forceFlush(); - } -} - /** * Add braintrust.parent to OTEL baggage. * @@ -878,33 +513,21 @@ export class BraintrustExporter { * import { propagation } from "@opentelemetry/api"; * * // Set braintrust.parent in baggage - * addParentToBaggage("project_name:my-project"); + * const ctx = addParentToBaggage("project_name:my-project"); * * // Export headers (will include braintrust.parent in baggage) * const headers = {}; - * propagation.inject(context.active(), headers); + * propagation.inject(ctx, headers); * ``` */ export function addParentToBaggage(parent: string, ctx?: Context): Context { - try { - if (!propagation) { - console.error("OTEL propagation API not available"); - // eslint-disable-next-line @typescript-eslint/consistent-type-assertions - return (ctx || context.active()) as Context; - } - - const currentCtx = ctx || context.active(); - const baggage = - propagation.getBaggage(currentCtx) || propagation.createBaggage(); - return propagation.setBaggage( - currentCtx, - baggage.setEntry("braintrust.parent", { value: parent }), - ); - } catch (error) { - console.error("Failed to add braintrust.parent to baggage:", error); - // eslint-disable-next-line @typescript-eslint/consistent-type-assertions - return (ctx || context.active()) as Context; - } + const currentCtx = ctx ?? context.active(); + const baggage = + propagation.getBaggage(currentCtx) ?? propagation.createBaggage(); + return propagation.setBaggage( + currentCtx, + baggage.setEntry("braintrust.parent", { value: parent }), + ); } /** @@ -920,16 +543,16 @@ export function addParentToBaggage(parent: string, ctx?: Context): Context { * * @example * ```typescript - * import { otel } from "braintrust"; + * import { addSpanParentToBaggage } from "@braintrust/otel"; * import { propagation } from "@opentelemetry/api"; * * tracer.startActiveSpan("service_b", (span) => { * // Copy braintrust.parent from span attribute to baggage - * otel.addSpanParentToBaggage(span); + * const ctx = addSpanParentToBaggage(span); * * // Export headers (will include braintrust.parent in baggage) * const headers = {}; - * propagation.inject(context.active(), headers); + * if (ctx) propagation.inject(ctx, headers); * span.end(); * }); * ``` diff --git a/integrations/otel-js/src/span-customizer.ts b/integrations/otel-js/src/span-customizer.ts index cd31bae70..3a2c7911a 100644 --- a/integrations/otel-js/src/span-customizer.ts +++ b/integrations/otel-js/src/span-customizer.ts @@ -1,5 +1,5 @@ import { createTraceState, type SpanContext } from "@opentelemetry/api"; -import type { ReadableSpan } from "@opentelemetry/sdk-trace-base"; +import type { ReadableSpan } from "./types"; type RecordData = Record; const CUSTOMIZED = Symbol.for("braintrust.customizedOtelSpan"); @@ -36,7 +36,7 @@ interface OtelCompatFields { } /** Normalize fields renamed between OTel 1.x and 2.x so either serializer works. */ -export function otelCompatFields(span: ReadableSpan): OtelCompatFields { +function otelCompatFields(span: ReadableSpan): OtelCompatFields { const compatible = span as ReadableSpan & OtelCompatFields; return { instrumentationScope: @@ -61,9 +61,9 @@ export function otelCompatFields(span: ReadableSpan): OtelCompatFields { * Customize one export batch. Runs at the Braintrust exporter boundary, so * filtering, enrichment and sampling have already happened upstream. */ -export async function customizeOtelSpans( - spans: ReadableSpan[], -): Promise { +export async function customizeOtelSpans( + spans: S[], +): Promise { if (!runtime()?.hasCustomizers()) return spans; // OTLP serializers wait for async resource detectors. Snapshot only once // those attributes exist, so hooks can redact them too. A failed detector @@ -237,10 +237,10 @@ function contextData(context: SpanContext | undefined): RecordData | undefined { } /** Customize an isolated, plain OTel snapshot, never a live ReadableSpan. */ -function customizeOtelSpan( - span: ReadableSpan, +function customizeOtelSpan( + span: S, resources: ResourceCache, -): ReadableSpan { +): S { const policy = runtime(); if (isCustomizedOtelSpan(span) || !policy) return span; const copy = (value: unknown) => copyData(value, policy.isPlainRecord); @@ -347,7 +347,7 @@ function customizeOtelSpan( status: span.status, resource: { attributes: span.resource.attributes, - schemaUrl: (span.resource as unknown as RecordData).schemaUrl, + schemaUrl: span.resource.schemaUrl, }, instrumentationScope: compatible.instrumentationScope ?? { name: "", @@ -399,7 +399,7 @@ function customizeOtelSpan( ended: true, }; Object.defineProperty(exported, CUSTOMIZED, { value: true }); - return exported as unknown as ReadableSpan; + return exported as unknown as S; } export function isCustomizedOtelSpan(span: ReadableSpan): boolean { diff --git a/integrations/otel-js/src/span-customizers.test.ts b/integrations/otel-js/src/span-customizers.test.ts index 0268bd38c..8331cfbb9 100644 --- a/integrations/otel-js/src/span-customizers.test.ts +++ b/integrations/otel-js/src/span-customizers.test.ts @@ -24,13 +24,12 @@ import { SimpleSpanProcessor, type ReadableSpan, } from "@opentelemetry/sdk-trace-base"; -import { - setupOtelCompat, - resetOtelCompat, - BraintrustSpanProcessor, - BraintrustExporter, -} from "./"; +import { setupOtelCompat, resetOtelCompat, BraintrustSpanProcessor } from "./"; import { createTracerProvider } from "../tests/utils"; +import { + createTestExporter, + createTestProcessor, +} from "../tests/export-fixtures"; type Data = Record; const shared = globalThis as Record; @@ -127,7 +126,7 @@ describe("OpenTelemetry span customizers at the HTTP boundary", () => { } async function send(spans: ReadableSpan[]): Promise { - const exporter = new BraintrustExporter({ + const exporter = createTestExporter({ apiKey: "test", apiUrl, parent: "project_name:default", @@ -436,7 +435,7 @@ describe("OpenTelemetry span customizers at the HTTP boundary", () => { return { name: "redacted" }; }); shared[customizersKey] = [{ onSpanExport: hook }]; - const processor = new BraintrustSpanProcessor({ + const processor = createTestProcessor({ apiKey: "test", apiUrl, parent: "project_name:test", @@ -468,13 +467,13 @@ describe("OpenTelemetry span customizers at the HTTP boundary", () => { }); vi.spyOn(console, "error").mockImplementation(() => {}); shared[customizersKey] = [{ onSpanExport: hook }]; - const exporter = new BraintrustExporter({ + const exporter = createTestExporter({ apiKey: "test", apiUrl, parent: "project_name:test", }); const processor = new BraintrustSpanProcessor({ - _spanProcessor: new SimpleSpanProcessor(exporter), + processor: new SimpleSpanProcessor(exporter), }); const spans = await fixture(); processor.onEnd(spans[0]); @@ -514,7 +513,7 @@ describe("OpenTelemetry span customizers at the HTTP boundary", () => { }); test("compatibility mode customizes native logger/experiment and OTel roots and children", async () => { - const processor = new BraintrustSpanProcessor({ + const processor = createTestProcessor({ apiKey: "test", apiUrl, parent: "project_name:test", @@ -625,10 +624,11 @@ describe("OpenTelemetry span customizers at the HTTP boundary", () => { calls++; return { name: "bootstrap-redacted" }; } }] }); - const { BraintrustExporter, setupOtelCompat } = require("@braintrust/otel"); + const { BraintrustExporter, braintrustOtlpConfig, setupOtelCompat } = require("@braintrust/otel"); + const { OTLPTraceExporter } = require("@opentelemetry/exporter-trace-otlp-http"); setupOtelCompat(); const exporter = new BraintrustExporter({ - apiKey: "test", apiUrl: ${JSON.stringify(apiUrl)}, parent: "project_name:bootstrap" + exporter: new OTLPTraceExporter(braintrustOtlpConfig({ apiKey: "test", apiUrl: ${JSON.stringify(apiUrl)}, parent: "project_name:bootstrap" })) }); const span = { name: "PRIVATE", spanContext: () => ({ traceId: "1".repeat(32), spanId: "2".repeat(16), traceFlags: 1 }), @@ -670,7 +670,7 @@ describe("OpenTelemetry span customizers at the HTTP boundary", () => { return typeof value === "function" ? value.bind(target) : value; }, }); - const processor = new BraintrustSpanProcessor({ + const processor = createTestProcessor({ apiKey: "test", apiUrl, parent: "project_name:test", @@ -697,14 +697,14 @@ describe("OpenTelemetry span customizers at the HTTP boundary", () => { return data; }); shared[customizersKey] = [{ onSpanExport: hook }]; - const exporter = new BraintrustExporter({ + const exporter = createTestExporter({ apiKey: "test", apiUrl, parent: "project_name:test", filterAISpans: true, }); const processor = new BraintrustSpanProcessor({ - _spanProcessor: new SimpleSpanProcessor(exporter), + processor: new SimpleSpanProcessor(exporter), }); const provider = createTracerProvider(BasicTracerProvider, [processor]); try { @@ -801,7 +801,7 @@ describe("OpenTelemetry span customizers at the HTTP boundary", () => { test("does not customize unsampled recorded spans", async () => { const hook = vi.fn((data: Data) => data); shared[customizersKey] = [{ onSpanExport: hook }]; - const processor = new BraintrustSpanProcessor({ + const processor = createTestProcessor({ apiKey: "test", apiUrl, parent: "project_name:test", @@ -916,7 +916,7 @@ describe("OpenTelemetry span customizers at the HTTP boundary", () => { test("customFilter sees span origin context before customization", async () => { const contexts: unknown[] = []; shared[customizersKey] = [{ onSpanExport: (data: Data) => data }]; - const processor = new BraintrustSpanProcessor({ + const processor = createTestProcessor({ apiKey: "test", apiUrl, parent: "project_name:test", diff --git a/integrations/otel-js/src/types.ts b/integrations/otel-js/src/types.ts new file mode 100644 index 000000000..be96c1648 --- /dev/null +++ b/integrations/otel-js/src/types.ts @@ -0,0 +1,69 @@ +import type { + Attributes, + Context, + HrTime, + Link, + Span, + SpanContext, + SpanKind, + SpanStatus, +} from "@opentelemetry/api"; + +// Structural contracts for the data this integration uses. They intentionally +// do not import SDK types: the application selects its SDK and exporter version. +interface InstrumentationScope { + name: string; + version?: string; + schemaUrl?: string; + attributes?: Attributes; +} + +export interface ReadableSpan { + readonly name: string; + readonly kind: SpanKind; + readonly attributes: Attributes; + readonly status: SpanStatus; + readonly startTime: HrTime; + readonly endTime: HrTime; + readonly duration: HrTime; + readonly events: readonly { + name: string; + time: HrTime; + attributes?: Attributes; + droppedAttributesCount?: number; + }[]; + readonly links: readonly Link[]; + readonly resource: { + readonly attributes: Attributes; + readonly schemaUrl?: string; + readonly asyncAttributesPending?: boolean; + waitForAsyncAttributes?(): Promise; + }; + readonly parentSpanId?: string; + readonly parentSpanContext?: SpanContext; + readonly instrumentationScope?: InstrumentationScope; + readonly instrumentationLibrary?: InstrumentationScope; + readonly droppedAttributesCount: number; + readonly droppedEventsCount: number; + readonly droppedLinksCount: number; + spanContext(): SpanContext; +} + +export interface ExportResult { + code: number; + error?: Error; +} + +export interface SpanExporter { + export(spans: S[], callback: (result: ExportResult) => void): void; + shutdown(): Promise; + forceFlush?(): Promise; +} + +export interface SpanProcessor { + onStart(span: Span, parentContext: Context): void; + onEnding?(span: Span): void; + onEnd(span: S): void; + forceFlush(): Promise; + shutdown(): Promise; +} diff --git a/integrations/otel-js/tests/export-fixtures.ts b/integrations/otel-js/tests/export-fixtures.ts new file mode 100644 index 000000000..89fca4dec --- /dev/null +++ b/integrations/otel-js/tests/export-fixtures.ts @@ -0,0 +1,37 @@ +import { BatchSpanProcessor } from "@opentelemetry/sdk-trace-base"; +import { OTLPTraceExporter } from "@opentelemetry/exporter-trace-otlp-http"; +import { + BraintrustExporter, + BraintrustSpanProcessor, + braintrustOtlpConfig, +} from "../src/otel"; + +type TestExportOptions = Parameters[0] & { + filterAISpans?: boolean; + customFilter?: ConstructorParameters< + typeof BraintrustExporter + >[0]["customFilter"]; + environment?: { type?: string; name?: string }; +}; + +export function createTestExporter(options: TestExportOptions) { + return new BraintrustExporter({ + ...options, + exporter: new OTLPTraceExporter(braintrustOtlpConfig(options)), + }); +} + +export function createTestProcessor(options: TestExportOptions) { + return new BraintrustSpanProcessor({ + ...options, + processor: new BatchSpanProcessor( + createTestExporter({ + apiKey: options.apiKey, + apiUrl: options.apiUrl, + parent: options.parent, + headers: options.headers, + environment: options.environment, + }), + ), + }); +} diff --git a/integrations/otel-js/tests/utils.ts b/integrations/otel-js/tests/utils.ts index f7eb32b4e..fe3fb0e99 100644 --- a/integrations/otel-js/tests/utils.ts +++ b/integrations/otel-js/tests/utils.ts @@ -26,10 +26,7 @@ export function createTracerProvider( processors: any[], config?: any, ): any { - // Create a test provider to detect which API version we're using - const testProvider = new ProviderClass(config || {}); - - if (typeof testProvider.addSpanProcessor === "function") { + if (typeof ProviderClass.prototype.addSpanProcessor === "function") { // OTel 1.x: Has addSpanProcessor method const provider = new ProviderClass(config); for (const processor of processors) { @@ -69,8 +66,10 @@ export function getParentSpanId(span: any): string | undefined { return undefined; } -import path from "path"; -import fs from "fs"; +import path from "node:path"; +import { createRequire } from "node:module"; +const require = createRequire(import.meta.url); +import fs from "node:fs"; /** * Detect if we're in v1 or v2 based on the calling directory. diff --git a/js/src/functions/invoke.test.ts b/js/src/functions/invoke.test.ts index 3272151e7..8caf9d2c3 100644 --- a/js/src/functions/invoke.test.ts +++ b/js/src/functions/invoke.test.ts @@ -83,3 +83,49 @@ describe("registerOtelFlush", () => { await state.flushOtel(); }); }); + +test("OTel flush registrations coexist and can be removed independently", async () => { + _exportsForTestingOnly.setInitialTestState(); + const { registerOtelFlush } = await import("../logger/core"); + const state = _internalGetGlobalState(); + const calls: string[] = []; + const removeFirst = registerOtelFlush(async () => { + calls.push("first"); + }); + const removeSecond = registerOtelFlush(async () => { + calls.push("second"); + }); + try { + await state.flushOtel(); + expect(calls).toEqual(["first", "second"]); + removeFirst(); + removeFirst(); + calls.length = 0; + await state.flushOtel(); + expect(calls).toEqual(["second"]); + } finally { + removeFirst(); + removeSecond(); + } +}); + +test("a failed OTel flush still starts every registered callback", async () => { + _exportsForTestingOnly.setInitialTestState(); + const { registerOtelFlush } = await import("../logger/core"); + const state = _internalGetGlobalState(); + const error = new Error("flush failed"); + let secondFlushed = false; + const removeFirst = registerOtelFlush(() => { + throw error; + }); + const removeSecond = registerOtelFlush(async () => { + secondFlushed = true; + }); + try { + await expect(state.flushOtel()).rejects.toBe(error); + expect(secondFlushed).toBe(true); + } finally { + removeFirst(); + removeSecond(); + } +}); diff --git a/js/src/instrumentation/README.md b/js/src/instrumentation/README.md index 9416d44f5..10f2e9e28 100644 --- a/js/src/instrumentation/README.md +++ b/js/src/instrumentation/README.md @@ -162,7 +162,7 @@ instrumentation context. ### OpenTelemetry spans The same registration applies to spans exported through `@braintrust/otel`'s -`BraintrustSpanProcessor` or `BraintrustExporter`, including compatibility mode +`BraintrustExporter`, including pipelines composed with `BraintrustSpanProcessor` and compatibility mode (`BRAINTRUST_OTEL_COMPAT` or `setupOtelCompat()`). Compatibility mode is not required to customize OTel exports. Native spans still use the native record shape above; OTel hooks receive an isolated plain snapshot, not a live diff --git a/js/src/logger/core.ts b/js/src/logger/core.ts index ba85d5a5e..299391d6d 100644 --- a/js/src/logger/core.ts +++ b/js/src/logger/core.ts @@ -150,6 +150,9 @@ export const InlineAttachmentReferenceSchema: z.ZodType<{ export interface ContextParentSpanIds { rootSpanId: string; spanParents: string[]; + /** W3C state inherited from an external tracing context. */ + traceFlags?: number; + traceState?: string; } export class LoginInvalidOrgError extends Error { @@ -751,7 +754,7 @@ export class BraintrustState { >({ max: 1000 }); private _idGenerator: IDGenerator | null = null; private _contextManager: ContextManager | null = null; - private _otelFlushCallback: (() => Promise) | null = null; + private readonly _otelFlushCallbacks = new Set<() => Promise>(); public spanOriginEnvironment: SpanOriginEnvironment | undefined; private traceContextSigningSecret: string | undefined; private loaderLoginCache = new WeakMap< @@ -994,10 +997,13 @@ export class BraintrustState { /** * Register an OTEL flush callback. This is called by @braintrust/otel - * when it initializes a BraintrustSpanProcessor/Exporter. + * when it initializes a BraintrustSpanProcessor. Returns an unregister callback. */ - public registerOtelFlush(callback: () => Promise): void { - this._otelFlushCallback = callback; + public registerOtelFlush(callback: () => Promise): () => void { + this._otelFlushCallbacks.add(callback); + return () => { + this._otelFlushCallbacks.delete(callback); + }; } /** @@ -1005,9 +1011,11 @@ export class BraintrustState { * Called during ensureSpansFlushed to ensure OTEL spans are visible in BTQL. */ public async flushOtel(): Promise { - if (this._otelFlushCallback) { - await this._otelFlushCallback(); - } + await Promise.all( + Array.from(this._otelFlushCallbacks, (callback) => + Promise.resolve().then(callback), + ), + ); } public copyLoginInfo(other: BraintrustState): void { @@ -1411,18 +1419,20 @@ export function _internalGetGlobalState(): BraintrustState { /** * Register a callback to flush OTEL spans. This is called by @braintrust/otel - * when it initializes a BraintrustSpanProcessor/Exporter. + * when it initializes a BraintrustSpanProcessor. Returns an unregister callback. * * When ensureSpansFlushed is called (e.g., before a BTQL query in scorers), - * this callback will be invoked to ensure OTEL spans are flushed to the server. + * all registered callbacks run to flush OTEL spans to the server. + * Returns an idempotent callback that removes this registration. * * Also disables the span cache, since OTEL spans aren't in the local cache * and we need BTQL to see the complete span tree (both native + OTEL spans). */ -export function registerOtelFlush(callback: () => Promise): void { - _internalGetGlobalState().registerOtelFlush(callback); +export function registerOtelFlush(callback: () => Promise): () => void { + const unregister = _internalGetGlobalState().registerOtelFlush(callback); // Disable span cache since OTEL spans aren't in the local cache _internalGetGlobalState().spanCache.disable(); + return unregister; } export class FailedHTTPResponse extends Error { @@ -6381,6 +6391,7 @@ interface ResolvedSpanIds { spanId: string; rootSpanId: string; spanParents: string[] | undefined; + propagatedState?: PropagatedState; } /** @@ -6425,6 +6436,16 @@ function _resolveSpanIds( spanId: resolvedSpanId, rootSpanId: parentInfo.rootSpanId, spanParents: parentInfo.spanParents, + propagatedState: + parentInfo.traceFlags !== undefined || + parentInfo.traceState !== undefined + ? { + traceFlags: parentInfo.traceFlags + ?.toString(16) + .padStart(2, "0"), + tracestate: parentInfo.traceState, + } + : undefined, }; } } @@ -6505,7 +6526,6 @@ export class SpanImpl implements Span { configurable: true, writable: true, }); - this._propagatedState = args.propagatedState; const instrumentationName = getSpanInstrumentationName(args) ?? INSTRUMENTATION_NAMES.BRAINTRUST_JS_LOGGER; @@ -6573,6 +6593,7 @@ export class SpanImpl implements Span { this._state.contextManager, ); + this._propagatedState = args.propagatedState ?? resolvedIds.propagatedState; this._spanId = resolvedIds.spanId; this._rootSpanId = resolvedIds.rootSpanId; this._spanParents = resolvedIds.spanParents; diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index b12b5910f..3d44621b7 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -129,6 +129,9 @@ importers: '@opentelemetry/context-async-hooks': specifier: '>=1.9.0' version: 2.6.1(@opentelemetry/api@1.9.0) + '@opentelemetry/exporter-trace-otlp-http': + specifier: 0.217.0 + version: 0.217.0(@opentelemetry/api@1.9.0) '@opentelemetry/sdk-trace-base': specifier: '>=1.9.0' version: 2.6.1(@opentelemetry/api@1.9.0) @@ -236,23 +239,25 @@ importers: 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: + devDependencies: '@opentelemetry/api': - specifier: '>=1.9.0' + specifier: 1.9.0 version: 1.9.0 + '@opentelemetry/context-async-hooks': + specifier: 2.6.1 + version: 2.6.1(@opentelemetry/api@1.9.0) '@opentelemetry/core': specifier: ^2.8.0 version: 2.10.0(@opentelemetry/api@1.9.0) '@opentelemetry/exporter-trace-otlp-http': - specifier: '>=0.35.0' - version: 0.208.0(@opentelemetry/api@1.9.0) + specifier: 0.200.0 + version: 0.200.0(@opentelemetry/api@1.9.0) + '@opentelemetry/resources': + specifier: 2.0.0 + version: 2.0.0(@opentelemetry/api@1.9.0) '@opentelemetry/sdk-trace-base': - specifier: '>=1.9.0' - version: 2.6.1(@opentelemetry/api@1.9.0) - devDependencies: - '@opentelemetry/context-async-hooks': - specifier: 2.6.1 - version: 2.6.1(@opentelemetry/api@1.9.0) + specifier: 2.0.0 + version: 2.0.0(@opentelemetry/api@1.9.0) '@types/node': specifier: ^22.15.21 version: 22.19.1 @@ -469,6 +474,7 @@ importers: 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: '@ai-sdk/gateway@3.0.15': @@ -1097,10 +1103,18 @@ packages: '@open-draft/until@2.1.0': resolution: {integrity: sha512-U69T3ItWHvLwGg5eJ0n3I62nWuE6ilHlmz7zM0npLBRvPRd7e6NYmg54vvRtP5mZG7kZqZCFVdsTWo7BPtBujg==} + '@opentelemetry/api-logs@0.200.0': + resolution: {integrity: sha512-IKJBQxh91qJ+3ssRly5hYEJ8NDHu9oY/B1PXVSCWf7zytmYO9RNLB0Ox9XQ/fJ8m6gY6Q6NtBWlmXfaXt5Uc4Q==} + engines: {node: '>=8.0.0'} + '@opentelemetry/api-logs@0.208.0': resolution: {integrity: sha512-CjruKY9V6NMssL/T1kAFgzosF1v9o6oeN+aX5JB/C/xPNtmgIJqcXHG7fA82Ou1zCpWGl4lROQUKwUNE1pMCyg==} engines: {node: '>=8.0.0'} + '@opentelemetry/api-logs@0.217.0': + resolution: {integrity: sha512-Cdq0jW2lknrNfrAm92MyEAvpe2cRsKjdnQLHUL6xRA4IVUnsWx6P65E7NcUO0Y+L4w1Aee5iV8FvjSwd+lrs9A==} + engines: {node: '>=8.0.0'} + '@opentelemetry/api@1.9.0': resolution: {integrity: sha512-3giAOQvZiH5F9bMlMiv8+GSPMeqg0dbaeo58/0SlA9sxSqZhnUtxzX9/2FzyhS9sWQf5S0GJE0AKBrFqjpeYcg==} engines: {node: '>=8.0.0'} @@ -1117,24 +1131,66 @@ packages: peerDependencies: '@opentelemetry/api': '>=1.0.0 <1.10.0' + '@opentelemetry/exporter-trace-otlp-http@0.200.0': + resolution: {integrity: sha512-Goi//m/7ZHeUedxTGVmEzH19NgqJY+Bzr6zXo1Rni1+hwqaksEyJ44gdlEMREu6dzX1DlAaH/qSykSVzdrdafA==} + engines: {node: ^18.19.0 || >=20.6.0} + peerDependencies: + '@opentelemetry/api': ^1.3.0 + '@opentelemetry/exporter-trace-otlp-http@0.208.0': resolution: {integrity: sha512-jbzDw1q+BkwKFq9yxhjAJ9rjKldbt5AgIy1gmEIJjEV/WRxQ3B6HcLVkwbjJ3RcMif86BDNKR846KJ0tY0aOJA==} engines: {node: ^18.19.0 || >=20.6.0} peerDependencies: '@opentelemetry/api': ^1.3.0 + '@opentelemetry/exporter-trace-otlp-http@0.217.0': + resolution: {integrity: sha512-38YQoqtYjglz2GV94LGUN/djLvxtvGIQO68o6qAFPVshjmwSdX1F2i0c7vn3lEl1L5B/YqjB/bgKXaVx7KO+RQ==} + engines: {node: ^18.19.0 || >=20.6.0} + peerDependencies: + '@opentelemetry/api': ^1.3.0 + + '@opentelemetry/otlp-exporter-base@0.200.0': + resolution: {integrity: sha512-IxJgA3FD7q4V6gGq4bnmQM5nTIyMDkoGFGrBrrDjB6onEiq1pafma55V+bHvGYLWvcqbBbRfezr1GED88lacEQ==} + engines: {node: ^18.19.0 || >=20.6.0} + peerDependencies: + '@opentelemetry/api': ^1.3.0 + '@opentelemetry/otlp-exporter-base@0.208.0': resolution: {integrity: sha512-gMd39gIfVb2OgxldxUtOwGJYSH8P1kVFFlJLuut32L6KgUC4gl1dMhn+YC2mGn0bDOiQYSk/uHOdSjuKp58vvA==} engines: {node: ^18.19.0 || >=20.6.0} peerDependencies: '@opentelemetry/api': ^1.3.0 + '@opentelemetry/otlp-exporter-base@0.217.0': + resolution: {integrity: sha512-eYfqnB3UhKu/5frhd1R6+FprKygbhkomuaceMXDyzxbfXB9tKgZOVmjaJ02CkLA6Tdzumxl+e2H+vo2a8jiMPQ==} + engines: {node: ^18.19.0 || >=20.6.0} + peerDependencies: + '@opentelemetry/api': ^1.3.0 + + '@opentelemetry/otlp-transformer@0.200.0': + resolution: {integrity: sha512-+9YDZbYybOnv7sWzebWOeK6gKyt2XE7iarSyBFkwwnP559pEevKOUD8NyDHhRjCSp13ybh9iVXlMfcj/DwF/yw==} + engines: {node: ^18.19.0 || >=20.6.0} + peerDependencies: + '@opentelemetry/api': ^1.3.0 + '@opentelemetry/otlp-transformer@0.208.0': resolution: {integrity: sha512-DCFPY8C6lAQHUNkzcNT9R+qYExvsk6C5Bto2pbNxgicpcSWbe2WHShLxkOxIdNcBiYPdVHv/e7vH7K6TI+C+fQ==} engines: {node: ^18.19.0 || >=20.6.0} peerDependencies: '@opentelemetry/api': ^1.3.0 + '@opentelemetry/otlp-transformer@0.217.0': + resolution: {integrity: sha512-MKK8UHKFUOGAvbZRWh90MhwHG+Fxm6OROBdjKPCF+HQobjuJ/Kuf8Chs8CR45X1aqotxrMj7OxTdsXe8sXuGVA==} + engines: {node: ^18.19.0 || >=20.6.0} + peerDependencies: + '@opentelemetry/api': ^1.3.0 + + '@opentelemetry/resources@2.0.0': + resolution: {integrity: sha512-rnZr6dML2z4IARI4zPGQV4arDikF/9OXZQzrC01dLmn0CZxU5U5OLd/m1T7YkGRj5UitjeoCtg/zorlgMQcdTg==} + engines: {node: ^18.19.0 || >=20.6.0} + peerDependencies: + '@opentelemetry/api': '>=1.3.0 <1.10.0' + '@opentelemetry/resources@2.2.0': resolution: {integrity: sha512-1pNQf/JazQTMA0BiO5NINUzH0cbLbbl7mntLa4aJNmCCXSj0q03T5ZXXL0zw4G55TjdL9Tz32cznGClf+8zr5A==} engines: {node: ^18.19.0 || >=20.6.0} @@ -1147,18 +1203,55 @@ packages: peerDependencies: '@opentelemetry/api': '>=1.3.0 <1.10.0' + '@opentelemetry/resources@2.7.1': + resolution: {integrity: sha512-DeT6KKolmC4e/dRQvMQ/RwlnzhaqeiFOXY5ngoOPJ07GgVVKxZOg9EcrNZb5aTzUn+iCrJldAgOfQm1O/QfPAQ==} + engines: {node: ^18.19.0 || >=20.6.0} + peerDependencies: + '@opentelemetry/api': '>=1.3.0 <1.10.0' + + '@opentelemetry/sdk-logs@0.200.0': + resolution: {integrity: sha512-VZG870063NLfObmQQNtCVcdXXLzI3vOjjrRENmU37HYiPFa0ZXpXVDsTD02Nh3AT3xYJzQaWKl2X2lQ2l7TWJA==} + engines: {node: ^18.19.0 || >=20.6.0} + peerDependencies: + '@opentelemetry/api': '>=1.4.0 <1.10.0' + '@opentelemetry/sdk-logs@0.208.0': resolution: {integrity: sha512-QlAyL1jRpOeaqx7/leG1vJMp84g0xKP6gJmfELBpnI4O/9xPX+Hu5m1POk9Kl+veNkyth5t19hRlN6tNY1sjbA==} engines: {node: ^18.19.0 || >=20.6.0} peerDependencies: '@opentelemetry/api': '>=1.4.0 <1.10.0' + '@opentelemetry/sdk-logs@0.217.0': + resolution: {integrity: sha512-BB+PcHItcZDL63dPMW+mJvwN9rk37wuIDjRxbVlg6pPDvDR/7GL7UJHbGsllgoggOoTimsKgENaWPoGch/oE1A==} + engines: {node: ^18.19.0 || >=20.6.0} + peerDependencies: + '@opentelemetry/api': '>=1.4.0 <1.10.0' + + '@opentelemetry/sdk-metrics@2.0.0': + resolution: {integrity: sha512-Bvy8QDjO05umd0+j+gDeWcTaVa1/R2lDj/eOvjzpm8VQj1K1vVZJuyjThpV5/lSHyYW2JaHF2IQ7Z8twJFAhjA==} + engines: {node: ^18.19.0 || >=20.6.0} + peerDependencies: + '@opentelemetry/api': '>=1.9.0 <1.10.0' + '@opentelemetry/sdk-metrics@2.2.0': resolution: {integrity: sha512-G5KYP6+VJMZzpGipQw7Giif48h6SGQ2PFKEYCybeXJsOCB4fp8azqMAAzE5lnnHK3ZVwYQrgmFbsUJO/zOnwGw==} engines: {node: ^18.19.0 || >=20.6.0} peerDependencies: '@opentelemetry/api': '>=1.9.0 <1.10.0' + '@opentelemetry/sdk-metrics@2.7.1': + resolution: {integrity: sha512-MpDJdkiFDs3Pm1RHO3KByuZbuBdJEXEAkiC0+yJdsZGVCdf1RpHR6n+LHDcS7ffmfrt5kVCzJSCfm4z2C7v0uQ==} + engines: {node: ^18.19.0 || >=20.6.0} + peerDependencies: + '@opentelemetry/api': '>=1.9.0 <1.10.0' + + '@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-base@2.2.0': resolution: {integrity: sha512-xWQgL0Bmctsalg6PaXExmzdedSp3gyKV8mQBwK/j9VGdCDu2fmXIb2gAehBKbkXCpJ4HPkgv3QfoJWRT4dHWbw==} engines: {node: ^18.19.0 || >=20.6.0} @@ -1171,6 +1264,13 @@ packages: peerDependencies: '@opentelemetry/api': '>=1.3.0 <1.10.0' + '@opentelemetry/sdk-trace-base@2.7.1': + resolution: {integrity: sha512-NAYIlsF8MPUsKqJMiDQJTMPOmlbawC1Iz/omMLygZ1C9am8fTKYjTaI+OZM+WTY3t3Glo0wnOg/6/pac6RGPPw==} + engines: {node: ^18.19.0 || >=20.6.0} + deprecated: Use @opentelemetry/sdk-trace package + peerDependencies: + '@opentelemetry/api': '>=1.3.0 <1.10.0' + '@opentelemetry/semantic-conventions@1.41.1': resolution: {integrity: sha512-/UhIkaZgPutTFmQ7RnIJGgDXZmtEJ7Dvi86xNTFWcnRxVRNk/aotsqDJYeEvDP+FSMB2SdW+pQzNMcWP0rwuNA==} engines: {node: '>=14'} @@ -4255,6 +4355,7 @@ packages: zod@4.6.7: resolution: {integrity: sha512-g8W2D2kU6a8untc053fX1uVBKzM7yqAxW/3zwk83+6RyF2+SKOuV+c0wBDhUwzV1PaGapmZH+rmcUzPf+3CGwg==} + snapshots: '@ai-sdk/gateway@3.0.15(zod@3.25.76)': @@ -4977,10 +5078,18 @@ snapshots: '@open-draft/until@2.1.0': optional: true + '@opentelemetry/api-logs@0.200.0': + dependencies: + '@opentelemetry/api': 1.9.0 + '@opentelemetry/api-logs@0.208.0': dependencies: '@opentelemetry/api': 1.9.0 + '@opentelemetry/api-logs@0.217.0': + dependencies: + '@opentelemetry/api': 1.9.0 + '@opentelemetry/api@1.9.0': {} '@opentelemetry/context-async-hooks@2.6.1(@opentelemetry/api@1.9.0)': @@ -4992,6 +5101,15 @@ snapshots: '@opentelemetry/api': 1.9.0 '@opentelemetry/semantic-conventions': 1.41.1 + '@opentelemetry/exporter-trace-otlp-http@0.200.0(@opentelemetry/api@1.9.0)': + dependencies: + '@opentelemetry/api': 1.9.0 + '@opentelemetry/core': 2.10.0(@opentelemetry/api@1.9.0) + '@opentelemetry/otlp-exporter-base': 0.200.0(@opentelemetry/api@1.9.0) + '@opentelemetry/otlp-transformer': 0.200.0(@opentelemetry/api@1.9.0) + '@opentelemetry/resources': 2.0.0(@opentelemetry/api@1.9.0) + '@opentelemetry/sdk-trace-base': 2.0.0(@opentelemetry/api@1.9.0) + '@opentelemetry/exporter-trace-otlp-http@0.208.0(@opentelemetry/api@1.9.0)': dependencies: '@opentelemetry/api': 1.9.0 @@ -5001,12 +5119,44 @@ snapshots: '@opentelemetry/resources': 2.2.0(@opentelemetry/api@1.9.0) '@opentelemetry/sdk-trace-base': 2.2.0(@opentelemetry/api@1.9.0) + '@opentelemetry/exporter-trace-otlp-http@0.217.0(@opentelemetry/api@1.9.0)': + dependencies: + '@opentelemetry/api': 1.9.0 + '@opentelemetry/core': 2.10.0(@opentelemetry/api@1.9.0) + '@opentelemetry/otlp-exporter-base': 0.217.0(@opentelemetry/api@1.9.0) + '@opentelemetry/otlp-transformer': 0.217.0(@opentelemetry/api@1.9.0) + '@opentelemetry/resources': 2.7.1(@opentelemetry/api@1.9.0) + '@opentelemetry/sdk-trace-base': 2.7.1(@opentelemetry/api@1.9.0) + + '@opentelemetry/otlp-exporter-base@0.200.0(@opentelemetry/api@1.9.0)': + dependencies: + '@opentelemetry/api': 1.9.0 + '@opentelemetry/core': 2.10.0(@opentelemetry/api@1.9.0) + '@opentelemetry/otlp-transformer': 0.200.0(@opentelemetry/api@1.9.0) + '@opentelemetry/otlp-exporter-base@0.208.0(@opentelemetry/api@1.9.0)': dependencies: '@opentelemetry/api': 1.9.0 '@opentelemetry/core': 2.10.0(@opentelemetry/api@1.9.0) '@opentelemetry/otlp-transformer': 0.208.0(@opentelemetry/api@1.9.0) + '@opentelemetry/otlp-exporter-base@0.217.0(@opentelemetry/api@1.9.0)': + dependencies: + '@opentelemetry/api': 1.9.0 + '@opentelemetry/core': 2.10.0(@opentelemetry/api@1.9.0) + '@opentelemetry/otlp-transformer': 0.217.0(@opentelemetry/api@1.9.0) + + '@opentelemetry/otlp-transformer@0.200.0(@opentelemetry/api@1.9.0)': + dependencies: + '@opentelemetry/api': 1.9.0 + '@opentelemetry/api-logs': 0.200.0 + '@opentelemetry/core': 2.10.0(@opentelemetry/api@1.9.0) + '@opentelemetry/resources': 2.0.0(@opentelemetry/api@1.9.0) + '@opentelemetry/sdk-logs': 0.200.0(@opentelemetry/api@1.9.0) + '@opentelemetry/sdk-metrics': 2.0.0(@opentelemetry/api@1.9.0) + '@opentelemetry/sdk-trace-base': 2.0.0(@opentelemetry/api@1.9.0) + protobufjs: 7.6.5 + '@opentelemetry/otlp-transformer@0.208.0(@opentelemetry/api@1.9.0)': dependencies: '@opentelemetry/api': 1.9.0 @@ -5018,6 +5168,23 @@ snapshots: '@opentelemetry/sdk-trace-base': 2.2.0(@opentelemetry/api@1.9.0) protobufjs: 7.6.5 + '@opentelemetry/otlp-transformer@0.217.0(@opentelemetry/api@1.9.0)': + dependencies: + '@opentelemetry/api': 1.9.0 + '@opentelemetry/api-logs': 0.217.0 + '@opentelemetry/core': 2.10.0(@opentelemetry/api@1.9.0) + '@opentelemetry/resources': 2.7.1(@opentelemetry/api@1.9.0) + '@opentelemetry/sdk-logs': 0.217.0(@opentelemetry/api@1.9.0) + '@opentelemetry/sdk-metrics': 2.7.1(@opentelemetry/api@1.9.0) + '@opentelemetry/sdk-trace-base': 2.7.1(@opentelemetry/api@1.9.0) + protobufjs: 7.6.5 + + '@opentelemetry/resources@2.0.0(@opentelemetry/api@1.9.0)': + dependencies: + '@opentelemetry/api': 1.9.0 + '@opentelemetry/core': 2.10.0(@opentelemetry/api@1.9.0) + '@opentelemetry/semantic-conventions': 1.41.1 + '@opentelemetry/resources@2.2.0(@opentelemetry/api@1.9.0)': dependencies: '@opentelemetry/api': 1.9.0 @@ -5030,6 +5197,19 @@ snapshots: '@opentelemetry/core': 2.10.0(@opentelemetry/api@1.9.0) '@opentelemetry/semantic-conventions': 1.41.1 + '@opentelemetry/resources@2.7.1(@opentelemetry/api@1.9.0)': + dependencies: + '@opentelemetry/api': 1.9.0 + '@opentelemetry/core': 2.10.0(@opentelemetry/api@1.9.0) + '@opentelemetry/semantic-conventions': 1.41.1 + + '@opentelemetry/sdk-logs@0.200.0(@opentelemetry/api@1.9.0)': + dependencies: + '@opentelemetry/api': 1.9.0 + '@opentelemetry/api-logs': 0.200.0 + '@opentelemetry/core': 2.10.0(@opentelemetry/api@1.9.0) + '@opentelemetry/resources': 2.0.0(@opentelemetry/api@1.9.0) + '@opentelemetry/sdk-logs@0.208.0(@opentelemetry/api@1.9.0)': dependencies: '@opentelemetry/api': 1.9.0 @@ -5037,12 +5217,39 @@ snapshots: '@opentelemetry/core': 2.10.0(@opentelemetry/api@1.9.0) '@opentelemetry/resources': 2.2.0(@opentelemetry/api@1.9.0) + '@opentelemetry/sdk-logs@0.217.0(@opentelemetry/api@1.9.0)': + dependencies: + '@opentelemetry/api': 1.9.0 + '@opentelemetry/api-logs': 0.217.0 + '@opentelemetry/core': 2.10.0(@opentelemetry/api@1.9.0) + '@opentelemetry/resources': 2.7.1(@opentelemetry/api@1.9.0) + '@opentelemetry/semantic-conventions': 1.41.1 + + '@opentelemetry/sdk-metrics@2.0.0(@opentelemetry/api@1.9.0)': + dependencies: + '@opentelemetry/api': 1.9.0 + '@opentelemetry/core': 2.10.0(@opentelemetry/api@1.9.0) + '@opentelemetry/resources': 2.0.0(@opentelemetry/api@1.9.0) + '@opentelemetry/sdk-metrics@2.2.0(@opentelemetry/api@1.9.0)': dependencies: '@opentelemetry/api': 1.9.0 '@opentelemetry/core': 2.10.0(@opentelemetry/api@1.9.0) '@opentelemetry/resources': 2.2.0(@opentelemetry/api@1.9.0) + '@opentelemetry/sdk-metrics@2.7.1(@opentelemetry/api@1.9.0)': + dependencies: + '@opentelemetry/api': 1.9.0 + '@opentelemetry/core': 2.10.0(@opentelemetry/api@1.9.0) + '@opentelemetry/resources': 2.7.1(@opentelemetry/api@1.9.0) + + '@opentelemetry/sdk-trace-base@2.0.0(@opentelemetry/api@1.9.0)': + dependencies: + '@opentelemetry/api': 1.9.0 + '@opentelemetry/core': 2.10.0(@opentelemetry/api@1.9.0) + '@opentelemetry/resources': 2.0.0(@opentelemetry/api@1.9.0) + '@opentelemetry/semantic-conventions': 1.41.1 + '@opentelemetry/sdk-trace-base@2.2.0(@opentelemetry/api@1.9.0)': dependencies: '@opentelemetry/api': 1.9.0 @@ -5057,6 +5264,13 @@ snapshots: '@opentelemetry/resources': 2.6.1(@opentelemetry/api@1.9.0) '@opentelemetry/semantic-conventions': 1.41.1 + '@opentelemetry/sdk-trace-base@2.7.1(@opentelemetry/api@1.9.0)': + dependencies: + '@opentelemetry/api': 1.9.0 + '@opentelemetry/core': 2.10.0(@opentelemetry/api@1.9.0) + '@opentelemetry/resources': 2.7.1(@opentelemetry/api@1.9.0) + '@opentelemetry/semantic-conventions': 1.41.1 + '@opentelemetry/semantic-conventions@1.41.1': {} '@oxc-project/runtime@0.153.0': {}