braintrust 3.29.0 → 3.30.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (49) hide show
  1. package/dev/dist/index.js +1465 -881
  2. package/dev/dist/index.mjs +846 -262
  3. package/dist/apply-auto-instrumentation.js +217 -217
  4. package/dist/apply-auto-instrumentation.mjs +2 -2
  5. package/dist/auto-instrumentations/bundler/esbuild.cjs +13 -1
  6. package/dist/auto-instrumentations/bundler/esbuild.mjs +2 -2
  7. package/dist/auto-instrumentations/bundler/next.cjs +13 -1
  8. package/dist/auto-instrumentations/bundler/next.mjs +3 -3
  9. package/dist/auto-instrumentations/bundler/rollup.cjs +13 -1
  10. package/dist/auto-instrumentations/bundler/rollup.mjs +2 -2
  11. package/dist/auto-instrumentations/bundler/vite.cjs +13 -1
  12. package/dist/auto-instrumentations/bundler/vite.mjs +2 -2
  13. package/dist/auto-instrumentations/bundler/webpack-loader.cjs +13 -1
  14. package/dist/auto-instrumentations/bundler/webpack.cjs +13 -1
  15. package/dist/auto-instrumentations/bundler/webpack.mjs +3 -3
  16. package/dist/auto-instrumentations/{chunk-DKTGDNA7.mjs → chunk-LW4HDHXT.mjs} +1 -1
  17. package/dist/auto-instrumentations/{chunk-OMCZ3MV2.mjs → chunk-OXINGIEZ.mjs} +1 -1
  18. package/dist/auto-instrumentations/{chunk-AOIYCVEL.mjs → chunk-U64OYU4Q.mjs} +13 -1
  19. package/dist/auto-instrumentations/hook.mjs +16 -4
  20. package/dist/auto-instrumentations/index.cjs +13 -1
  21. package/dist/auto-instrumentations/index.mjs +1 -1
  22. package/dist/browser.d.mts +133 -2
  23. package/dist/browser.d.ts +133 -2
  24. package/dist/browser.js +1143 -297
  25. package/dist/browser.mjs +1143 -297
  26. package/dist/{chunk-6Z5S7VOU.js → chunk-2XDW3UCG.js} +13 -1
  27. package/dist/{chunk-M6XPNJC4.mjs → chunk-BU6SM54N.mjs} +687 -115
  28. package/dist/{chunk-7FA6VP2S.mjs → chunk-N3JKQ3D3.mjs} +13 -1
  29. package/dist/{chunk-XLLGRXGR.js → chunk-TWCDSYJN.js} +1803 -1231
  30. package/dist/cli.js +819 -235
  31. package/dist/edge-light.d.mts +1 -1
  32. package/dist/edge-light.d.ts +1 -1
  33. package/dist/edge-light.js +1143 -297
  34. package/dist/edge-light.mjs +1143 -297
  35. package/dist/index.d.mts +133 -2
  36. package/dist/index.d.ts +133 -2
  37. package/dist/index.js +902 -640
  38. package/dist/index.mjs +336 -74
  39. package/dist/instrumentation/index.d.mts +15 -0
  40. package/dist/instrumentation/index.d.ts +15 -0
  41. package/dist/instrumentation/index.js +857 -262
  42. package/dist/instrumentation/index.mjs +857 -262
  43. package/dist/vitest-evals-reporter.js +16 -16
  44. package/dist/vitest-evals-reporter.mjs +2 -2
  45. package/dist/workerd.d.mts +1 -1
  46. package/dist/workerd.d.ts +1 -1
  47. package/dist/workerd.js +1143 -297
  48. package/dist/workerd.mjs +1143 -297
  49. package/package.json +1 -1
@@ -5815,7 +5815,7 @@ var INSTRUMENTATION_NAMES = {
5815
5815
  var INTERNAL_SPAN_INSTRUMENTATION_NAME = /* @__PURE__ */ Symbol.for(
5816
5816
  "braintrust.spanInstrumentationName"
5817
5817
  );
5818
- var SDK_VERSION = true ? "3.29.0" : "0.0.0";
5818
+ var SDK_VERSION = true ? "3.30.0" : "0.0.0";
5819
5819
  function withSpanInstrumentationName(args, instrumentationName) {
5820
5820
  return {
5821
5821
  ...args,
@@ -8531,6 +8531,9 @@ function getSpanParentObjectAndPropagatedState(options) {
8531
8531
  }
8532
8532
  return { parentObject: NOOP_SPAN, propagatedState: void 0 };
8533
8533
  }
8534
+ function getSpanParentObject(options) {
8535
+ return getSpanParentObjectAndPropagatedState(options).parentObject;
8536
+ }
8534
8537
  function currentBraintrustParent(state) {
8535
8538
  const resolvedState = state ?? _globalState;
8536
8539
  const experiment = currentExperiment({ state: resolvedState });
@@ -8777,6 +8780,15 @@ function _internalStartSpanWithInitialMerge(args) {
8777
8780
  [INITIAL_SPAN_WRITE_AS_MERGE]: true
8778
8781
  }).span;
8779
8782
  }
8783
+ function _internalStartSpanWithInitialMergeAndParentSpanIds(args) {
8784
+ return startSpanAndIsLogger(
8785
+ {
8786
+ ...args,
8787
+ [INITIAL_SPAN_WRITE_AS_MERGE]: true
8788
+ },
8789
+ { useParentSpanIdsForObjectParent: true }
8790
+ ).span;
8791
+ }
8780
8792
  function _internalStartSpanWithContext(args, context) {
8781
8793
  return startSpanAndIsLogger({
8782
8794
  ...args,
@@ -8787,7 +8799,7 @@ async function flush(options) {
8787
8799
  const state = options?.state ?? _globalState;
8788
8800
  return await state.bgLogger().flush();
8789
8801
  }
8790
- function startSpanAndIsLogger(args) {
8802
+ function startSpanAndIsLogger(args, internalOptions) {
8791
8803
  const state = args?.state ?? _globalState;
8792
8804
  const { parentObject, propagatedState } = getSpanParentObjectAndPropagatedState({
8793
8805
  asyncFlush: args?.asyncFlush,
@@ -8801,7 +8813,7 @@ function startSpanAndIsLogger(args) {
8801
8813
  ) ? {
8802
8814
  spanId: parentObject.data.span_id,
8803
8815
  rootSpanId: parentObject.data.root_span_id
8804
- } : void 0;
8816
+ } : internalOptions?.useParentSpanIdsForObjectParent ? args?.parentSpanIds : void 0;
8805
8817
  const { parent: _ignoredParent, ...spanArgs } = args ?? {};
8806
8818
  const span = new SpanImpl({
8807
8819
  state,
@@ -11831,6 +11843,131 @@ function unsubscribeAll(unsubscribers) {
11831
11843
  return [];
11832
11844
  }
11833
11845
 
11846
+ // src/instrumentation/core/channel-definitions.ts
11847
+ function channel(spec) {
11848
+ return spec;
11849
+ }
11850
+ function defineChannels(pkg, channels, options) {
11851
+ const { instrumentationName } = options;
11852
+ return Object.fromEntries(
11853
+ Object.entries(channels).map(([key, spec]) => {
11854
+ const fullChannelName = `orchestrion:${pkg}:${spec.channelName}`;
11855
+ if (spec.kind === "async") {
11856
+ const asyncSpec = spec;
11857
+ const tracingChannel2 = () => isomorph_default.newTracingChannel(
11858
+ fullChannelName
11859
+ );
11860
+ const intercept2 = (interceptor) => {
11861
+ const hook = tracingChannel2();
11862
+ return typeof hook.intercept === "function" ? hook.intercept(interceptor) : () => {
11863
+ };
11864
+ };
11865
+ return [
11866
+ key,
11867
+ {
11868
+ ...asyncSpec,
11869
+ instrumentationName,
11870
+ intercept: intercept2,
11871
+ invoke: (target, thisArg, args, additional) => {
11872
+ const hook = tracingChannel2();
11873
+ return typeof hook.invoke === "function" ? hook.invoke(target, thisArg, args, additional) : Reflect.apply(target, thisArg, args);
11874
+ },
11875
+ tracingChannel: tracingChannel2,
11876
+ tracePromise: (fn, context) => tracingChannel2().tracePromise(
11877
+ fn,
11878
+ // eslint-disable-next-line @typescript-eslint/consistent-type-assertions
11879
+ context
11880
+ )
11881
+ }
11882
+ ];
11883
+ }
11884
+ const syncSpec = spec;
11885
+ const tracingChannel = () => isomorph_default.newTracingChannel(
11886
+ fullChannelName
11887
+ );
11888
+ const intercept = (interceptor) => {
11889
+ const hook = tracingChannel();
11890
+ return typeof hook.intercept === "function" ? hook.intercept(interceptor) : () => {
11891
+ };
11892
+ };
11893
+ return [
11894
+ key,
11895
+ {
11896
+ ...syncSpec,
11897
+ instrumentationName,
11898
+ intercept,
11899
+ invoke: (target, thisArg, args, additional) => {
11900
+ const hook = tracingChannel();
11901
+ return typeof hook.invoke === "function" ? hook.invoke(target, thisArg, args, additional) : Reflect.apply(target, thisArg, args);
11902
+ },
11903
+ tracingChannel,
11904
+ traceSync: (fn, context) => tracingChannel().traceSync(
11905
+ fn,
11906
+ // eslint-disable-next-line @typescript-eslint/consistent-type-assertions
11907
+ context
11908
+ )
11909
+ }
11910
+ ];
11911
+ })
11912
+ );
11913
+ }
11914
+
11915
+ // src/instrumentation/plugins/openai-channels.ts
11916
+ var openAIChannels = defineChannels(
11917
+ "openai",
11918
+ {
11919
+ filesCreateTraced: channel({
11920
+ channelName: "files.create-traced",
11921
+ kind: "async"
11922
+ }),
11923
+ batchesRetrieveTraced: channel({
11924
+ channelName: "batches.retrieve-traced",
11925
+ kind: "async"
11926
+ }),
11927
+ batchesCompleteTrace: channel({
11928
+ channelName: "batches.complete-trace",
11929
+ kind: "async"
11930
+ }),
11931
+ chatCompletionsCreate: channel({
11932
+ channelName: "chat.completions.create",
11933
+ kind: "async"
11934
+ }),
11935
+ embeddingsCreate: channel({
11936
+ channelName: "embeddings.create",
11937
+ kind: "async"
11938
+ }),
11939
+ betaChatCompletionsParse: channel({
11940
+ channelName: "beta.chat.completions.parse",
11941
+ kind: "async"
11942
+ }),
11943
+ betaChatCompletionsStream: channel({
11944
+ channelName: "beta.chat.completions.stream",
11945
+ kind: "sync-stream"
11946
+ }),
11947
+ moderationsCreate: channel({
11948
+ channelName: "moderations.create",
11949
+ kind: "async"
11950
+ }),
11951
+ responsesCreate: channel({
11952
+ channelName: "responses.create",
11953
+ kind: "async"
11954
+ }),
11955
+ responsesStream: channel({
11956
+ channelName: "responses.stream",
11957
+ kind: "sync-stream"
11958
+ }),
11959
+ responsesParse: channel({
11960
+ channelName: "responses.parse",
11961
+ kind: "async"
11962
+ }),
11963
+ responsesCompact: channel({
11964
+ channelName: "responses.compact",
11965
+ kind: "async"
11966
+ })
11967
+ },
11968
+ { instrumentationName: INSTRUMENTATION_NAMES.OPENAI }
11969
+ );
11970
+
11834
11971
  // src/wrappers/attachment-utils.ts
11835
11972
  function getExtensionFromMediaType(mediaType) {
11836
11973
  const extensionMap = {
@@ -12006,173 +12143,691 @@ function processInputAttachments(input) {
12006
12143
  return processNode(input);
12007
12144
  }
12008
12145
 
12009
- // src/instrumentation/core/channel-definitions.ts
12010
- function channel(spec) {
12011
- return spec;
12146
+ // src/instrumentation/plugins/openai-span-data.ts
12147
+ var OPENAI_METADATA_KEYS = [
12148
+ "model",
12149
+ "temperature",
12150
+ "top_p",
12151
+ "max_tokens",
12152
+ "frequency_penalty",
12153
+ "presence_penalty",
12154
+ "stop",
12155
+ "response_format",
12156
+ "tools",
12157
+ "tool_choice",
12158
+ "parallel_tool_calls",
12159
+ "max_tool_calls"
12160
+ ];
12161
+ function batchMetadata(params) {
12162
+ const metadata = { provider: "openai" };
12163
+ for (const key of OPENAI_METADATA_KEYS) {
12164
+ try {
12165
+ const value = Reflect.get(params, key);
12166
+ if (value !== void 0) {
12167
+ metadata[key] = value;
12168
+ }
12169
+ } catch {
12170
+ }
12171
+ }
12172
+ return metadata;
12012
12173
  }
12013
- function defineChannels(pkg, channels, options) {
12014
- const { instrumentationName } = options;
12015
- return Object.fromEntries(
12016
- Object.entries(channels).map(([key, spec]) => {
12017
- const fullChannelName = `orchestrion:${pkg}:${spec.channelName}`;
12018
- if (spec.kind === "async") {
12019
- const asyncSpec = spec;
12020
- const tracingChannel2 = () => isomorph_default.newTracingChannel(
12021
- fullChannelName
12022
- );
12023
- const intercept2 = (interceptor) => {
12024
- const hook = tracingChannel2();
12025
- return typeof hook.intercept === "function" ? hook.intercept(interceptor) : () => {
12026
- };
12027
- };
12028
- return [
12029
- key,
12030
- {
12031
- ...asyncSpec,
12032
- instrumentationName,
12033
- intercept: intercept2,
12034
- invoke: (target, thisArg, args, additional) => {
12035
- const hook = tracingChannel2();
12036
- return typeof hook.invoke === "function" ? hook.invoke(target, thisArg, args, additional) : Reflect.apply(target, thisArg, args);
12037
- },
12038
- tracingChannel: tracingChannel2,
12039
- tracePromise: (fn, context) => tracingChannel2().tracePromise(
12040
- fn,
12041
- // eslint-disable-next-line @typescript-eslint/consistent-type-assertions
12042
- context
12043
- )
12044
- }
12045
- ];
12174
+ function extractOpenAIBatchInput(endpoint, params) {
12175
+ const input = endpoint === "/v1/chat/completions" ? params.messages : params.input;
12176
+ return {
12177
+ input: processInputAttachments(input),
12178
+ metadata: batchMetadata(params)
12179
+ };
12180
+ }
12181
+ function extractOpenAIChatInput(params) {
12182
+ const { messages, ...metadata } = params;
12183
+ return {
12184
+ input: processInputAttachments(messages),
12185
+ metadata: { ...metadata, provider: "openai" }
12186
+ };
12187
+ }
12188
+ function extractOpenAIResponsesInput(params) {
12189
+ const { input, ...metadata } = params;
12190
+ return {
12191
+ input: processInputAttachments(input),
12192
+ metadata: { ...metadata, provider: "openai" }
12193
+ };
12194
+ }
12195
+ function extractOpenAIResponsesMetadata(result) {
12196
+ if (!result) {
12197
+ return void 0;
12198
+ }
12199
+ const { output: _output, usage: _usage, ...metadata } = result;
12200
+ return Object.keys(metadata).length > 0 ? metadata : void 0;
12201
+ }
12202
+ function processImagesInOutput(output) {
12203
+ if (Array.isArray(output)) {
12204
+ return output.map(processImagesInOutput);
12205
+ }
12206
+ if (isObject(output) && output.type === "image_generation_call" && typeof output.result === "string" && output.result) {
12207
+ const fileExtension = output.output_format || "png";
12208
+ const contentType = `image/${fileExtension}`;
12209
+ const baseFilename = typeof output.revised_prompt === "string" ? output.revised_prompt.slice(0, 50).replace(/[^a-zA-Z0-9]/g, "_") : "generated_image";
12210
+ let binaryString;
12211
+ try {
12212
+ binaryString = atob(output.result);
12213
+ } catch {
12214
+ return output;
12215
+ }
12216
+ const bytes = new Uint8Array(binaryString.length);
12217
+ for (let i = 0; i < binaryString.length; i++) {
12218
+ bytes[i] = binaryString.charCodeAt(i);
12219
+ }
12220
+ return {
12221
+ ...output,
12222
+ result: new Attachment({
12223
+ data: new Blob([bytes], { type: contentType }),
12224
+ filename: `${baseFilename}.${fileExtension}`,
12225
+ contentType
12226
+ })
12227
+ };
12228
+ }
12229
+ return output;
12230
+ }
12231
+
12232
+ // src/openai-utils.ts
12233
+ var BRAINTRUST_CACHED_STREAM_METRIC = "__braintrust_cached_metric";
12234
+ var LEGACY_CACHED_HEADER = "x-cached";
12235
+ var X_CACHED_HEADER = "x-bt-cached";
12236
+ var TOKEN_NAME_MAP = {
12237
+ input_tokens: "prompt_tokens",
12238
+ output_tokens: "completion_tokens",
12239
+ total_tokens: "tokens"
12240
+ };
12241
+ var TOKEN_PREFIX_MAP = {
12242
+ input: "prompt",
12243
+ output: "completion"
12244
+ };
12245
+ function parseMetricsFromUsage(usage) {
12246
+ if (!usage) {
12247
+ return {};
12248
+ }
12249
+ const metrics = {};
12250
+ for (const [oaiName, value] of Object.entries(usage)) {
12251
+ if (typeof value === "number") {
12252
+ const metricName = TOKEN_NAME_MAP[oaiName] || oaiName;
12253
+ metrics[metricName] = value;
12254
+ continue;
12255
+ }
12256
+ if (!oaiName.endsWith("_tokens_details") || !isObject(value)) {
12257
+ continue;
12258
+ }
12259
+ const rawPrefix = oaiName.slice(0, -"_tokens_details".length);
12260
+ const prefix = TOKEN_PREFIX_MAP[rawPrefix] || rawPrefix;
12261
+ for (const [key, nestedValue] of Object.entries(value)) {
12262
+ if (typeof nestedValue !== "number") {
12263
+ continue;
12046
12264
  }
12047
- const syncSpec = spec;
12048
- const tracingChannel = () => isomorph_default.newTracingChannel(
12049
- fullChannelName
12050
- );
12051
- const intercept = (interceptor) => {
12052
- const hook = tracingChannel();
12053
- return typeof hook.intercept === "function" ? hook.intercept(interceptor) : () => {
12054
- };
12055
- };
12056
- return [
12057
- key,
12265
+ metrics[`${prefix}_${key}`] = nestedValue;
12266
+ }
12267
+ }
12268
+ return metrics;
12269
+ }
12270
+ function parseCachedHeader(value) {
12271
+ if (!value) {
12272
+ return void 0;
12273
+ }
12274
+ return ["true", "hit"].includes(value.toLowerCase()) ? 1 : 0;
12275
+ }
12276
+ function getCachedMetricFromHeaders(headers) {
12277
+ if (!headers || typeof headers.get !== "function") {
12278
+ return void 0;
12279
+ }
12280
+ const cachedHeader = headers.get(X_CACHED_HEADER);
12281
+ if (cachedHeader) {
12282
+ return parseCachedHeader(cachedHeader);
12283
+ }
12284
+ return parseCachedHeader(headers.get(LEGACY_CACHED_HEADER));
12285
+ }
12286
+
12287
+ // src/instrumentation/plugins/openai-batch-instrumentation.ts
12288
+ var SUPPORTED_ENDPOINTS = /* @__PURE__ */ new Set(["/v1/chat/completions", "/v1/responses"]);
12289
+ var TERMINAL_STATUSES = /* @__PURE__ */ new Set([
12290
+ "completed",
12291
+ "failed",
12292
+ "expired",
12293
+ "cancelled"
12294
+ ]);
12295
+ var pendingBatchTraces = /* @__PURE__ */ new Map();
12296
+ function read(value, key) {
12297
+ if (!isObject(value)) {
12298
+ return void 0;
12299
+ }
12300
+ try {
12301
+ return Reflect.get(value, key);
12302
+ } catch {
12303
+ return void 0;
12304
+ }
12305
+ }
12306
+ function isBatchRecordIterable(value) {
12307
+ if (value === null || typeof value !== "object" && typeof value !== "function") {
12308
+ return false;
12309
+ }
12310
+ return typeof read(value, Symbol.iterator) === "function" || typeof read(value, Symbol.asyncIterator) === "function";
12311
+ }
12312
+ function validCustomId(value) {
12313
+ return typeof value === "string" && value.length > 0 && value.length <= 64;
12314
+ }
12315
+ function logBatchInstrumentationError(context, error) {
12316
+ debugLogger.debug(`OpenAI Batch instrumentation ${context}:`, error);
12317
+ }
12318
+ async function exportParent(parent) {
12319
+ if ("toStr" in parent && typeof parent.toStr === "function") {
12320
+ return parent.toStr();
12321
+ }
12322
+ if ("export" in parent && typeof parent.export === "function") {
12323
+ return await parent.export();
12324
+ }
12325
+ return void 0;
12326
+ }
12327
+ async function deterministicDigest(namespace, ...parts) {
12328
+ const encoded = new TextEncoder().encode(
12329
+ [namespace, ...parts].map((part) => `${part.length}:${part}`).join("\0")
12330
+ );
12331
+ return new Uint8Array(
12332
+ await globalThis.crypto.subtle.digest("SHA-256", encoded)
12333
+ );
12334
+ }
12335
+ function digestHex(bytes, length) {
12336
+ return Array.from(bytes.slice(0, length)).map((byte) => byte.toString(16).padStart(2, "0")).join("");
12337
+ }
12338
+ function digestUuid(bytes) {
12339
+ const hex = digestHex(bytes, 16);
12340
+ return `${hex.slice(0, 8)}-${hex.slice(8, 12)}-${hex.slice(12, 16)}-${hex.slice(16, 20)}-${hex.slice(20)}`;
12341
+ }
12342
+ async function batchSpanIds(inputFileId) {
12343
+ const [row, span, root] = await Promise.all([
12344
+ deterministicDigest("openai:batch:row", inputFileId),
12345
+ deterministicDigest("openai:batch:span", inputFileId),
12346
+ deterministicDigest("openai:batch:root", inputFileId)
12347
+ ]);
12348
+ return {
12349
+ rowId: digestUuid(row),
12350
+ spanId: digestHex(span, 8),
12351
+ rootSpanId: digestHex(root, 16)
12352
+ };
12353
+ }
12354
+ async function childSpanIds(inputFileId, customId) {
12355
+ const [row, span] = await Promise.all([
12356
+ deterministicDigest("openai:batch:child:row", inputFileId, customId),
12357
+ deterministicDigest("openai:batch:child:span", inputFileId, customId)
12358
+ ]);
12359
+ return { rowId: digestUuid(row), spanId: digestHex(span, 8) };
12360
+ }
12361
+ async function startBatchSpan(context) {
12362
+ const ids = await batchSpanIds(context.inputFileId);
12363
+ const parent = SpanComponentsV4.fromStr(context.parent);
12364
+ const hasParentSpan = Boolean(
12365
+ parent.data.row_id && parent.data.span_id && parent.data.root_span_id
12366
+ );
12367
+ return withCurrent(
12368
+ NOOP_SPAN,
12369
+ () => _internalStartSpanWithInitialMergeAndParentSpanIds(
12370
+ withSpanInstrumentationName(
12058
12371
  {
12059
- ...syncSpec,
12060
- instrumentationName,
12061
- intercept,
12062
- invoke: (target, thisArg, args, additional) => {
12063
- const hook = tracingChannel();
12064
- return typeof hook.invoke === "function" ? hook.invoke(target, thisArg, args, additional) : Reflect.apply(target, thisArg, args);
12065
- },
12066
- tracingChannel,
12067
- traceSync: (fn, context) => tracingChannel().traceSync(
12068
- fn,
12069
- // eslint-disable-next-line @typescript-eslint/consistent-type-assertions
12070
- context
12071
- )
12072
- }
12073
- ];
12074
- })
12372
+ name: "openai.batch",
12373
+ type: "task" /* TASK */,
12374
+ parent: context.parent,
12375
+ ...!hasParentSpan ? {
12376
+ parentSpanIds: {
12377
+ parentSpanIds: [],
12378
+ rootSpanId: ids.rootSpanId
12379
+ }
12380
+ } : {},
12381
+ spanId: ids.spanId,
12382
+ startTime: context.taskStartTime,
12383
+ event: {
12384
+ id: ids.rowId,
12385
+ metadata: {
12386
+ endpoint: context.endpoint,
12387
+ input_file_id: context.inputFileId,
12388
+ provider: "openai"
12389
+ }
12390
+ }
12391
+ },
12392
+ INSTRUMENTATION_NAMES.OPENAI
12393
+ )
12394
+ )
12075
12395
  );
12076
12396
  }
12077
-
12078
- // src/instrumentation/plugins/openai-channels.ts
12079
- var openAIChannels = defineChannels(
12080
- "openai",
12081
- {
12082
- chatCompletionsCreate: channel({
12083
- channelName: "chat.completions.create",
12084
- kind: "async"
12085
- }),
12086
- embeddingsCreate: channel({
12087
- channelName: "embeddings.create",
12088
- kind: "async"
12089
- }),
12090
- betaChatCompletionsParse: channel({
12091
- channelName: "beta.chat.completions.parse",
12092
- kind: "async"
12093
- }),
12094
- betaChatCompletionsStream: channel({
12095
- channelName: "beta.chat.completions.stream",
12096
- kind: "sync-stream"
12097
- }),
12098
- moderationsCreate: channel({
12099
- channelName: "moderations.create",
12100
- kind: "async"
12101
- }),
12102
- responsesCreate: channel({
12103
- channelName: "responses.create",
12104
- kind: "async"
12105
- }),
12106
- responsesStream: channel({
12107
- channelName: "responses.stream",
12108
- kind: "sync-stream"
12109
- }),
12110
- responsesParse: channel({
12111
- channelName: "responses.parse",
12112
- kind: "async"
12113
- }),
12114
- responsesCompact: channel({
12115
- channelName: "responses.compact",
12116
- kind: "async"
12117
- })
12118
- },
12119
- { instrumentationName: INSTRUMENTATION_NAMES.OPENAI }
12120
- );
12121
-
12122
- // src/openai-utils.ts
12123
- var BRAINTRUST_CACHED_STREAM_METRIC = "__braintrust_cached_metric";
12124
- var LEGACY_CACHED_HEADER = "x-cached";
12125
- var X_CACHED_HEADER = "x-bt-cached";
12126
- var TOKEN_NAME_MAP = {
12127
- input_tokens: "prompt_tokens",
12128
- output_tokens: "completion_tokens",
12129
- total_tokens: "tokens"
12130
- };
12131
- var TOKEN_PREFIX_MAP = {
12132
- input: "prompt",
12133
- output: "completion"
12397
+ async function startBatchChild(context, taskParent, input) {
12398
+ const ids = await childSpanIds(context.inputFileId, input.customId);
12399
+ return withCurrent(
12400
+ NOOP_SPAN,
12401
+ () => _internalStartSpanWithInitialMerge(
12402
+ withSpanInstrumentationName(
12403
+ {
12404
+ name: context.endpoint === "/v1/chat/completions" ? "Chat Completion" : "openai.responses.create",
12405
+ type: "llm" /* LLM */,
12406
+ parent: taskParent,
12407
+ spanId: ids.spanId,
12408
+ startTime: context.childStartTime,
12409
+ event: {
12410
+ id: ids.rowId,
12411
+ ...input.spanData?.input !== void 0 ? { input: input.spanData.input } : {},
12412
+ metadata: {
12413
+ ...input.spanData?.metadata,
12414
+ custom_id: input.customId,
12415
+ provider: "openai"
12416
+ }
12417
+ }
12418
+ },
12419
+ INSTRUMENTATION_NAMES.OPENAI
12420
+ )
12421
+ )
12422
+ );
12423
+ }
12424
+ async function* jsonlRecords(file, onIssue = () => {
12425
+ }) {
12426
+ const resolvedFile = await file;
12427
+ if (typeof resolvedFile === "string") {
12428
+ for (const line of resolvedFile.split("\n")) {
12429
+ if (!line.trim()) {
12430
+ continue;
12431
+ }
12432
+ try {
12433
+ yield JSON.parse(line.replace(/\r$/, ""));
12434
+ } catch (error) {
12435
+ logBatchInstrumentationError("skipped malformed JSONL", error);
12436
+ onIssue(new Error("OpenAI Batch file contains malformed JSONL"));
12437
+ }
12438
+ }
12439
+ return;
12440
+ }
12441
+ const body = read(resolvedFile, "body");
12442
+ const getReader = read(body, "getReader");
12443
+ if (isObject(body) && typeof getReader === "function") {
12444
+ let reader;
12445
+ try {
12446
+ reader = Reflect.apply(getReader, body, []);
12447
+ const decoder = new TextDecoder();
12448
+ let pending = "";
12449
+ while (true) {
12450
+ const readChunk = read(reader, "read");
12451
+ if (typeof readChunk !== "function") {
12452
+ throw new Error("Response body stream has no read method");
12453
+ }
12454
+ const chunk = await Reflect.apply(readChunk, reader, []);
12455
+ if (!isObject(chunk)) {
12456
+ throw new Error("Response body stream returned an invalid chunk");
12457
+ }
12458
+ if (chunk.done === true) {
12459
+ pending += decoder.decode();
12460
+ break;
12461
+ }
12462
+ if (!(chunk.value instanceof Uint8Array)) {
12463
+ throw new Error("Response body stream returned a non-byte chunk");
12464
+ }
12465
+ pending += decoder.decode(chunk.value, { stream: true });
12466
+ let newline = pending.indexOf("\n");
12467
+ while (newline !== -1) {
12468
+ const line = pending.slice(0, newline).replace(/\r$/, "");
12469
+ pending = pending.slice(newline + 1);
12470
+ if (line.trim()) {
12471
+ try {
12472
+ yield JSON.parse(line);
12473
+ } catch (error) {
12474
+ logBatchInstrumentationError("skipped malformed JSONL", error);
12475
+ onIssue(new Error("OpenAI Batch file contains malformed JSONL"));
12476
+ }
12477
+ }
12478
+ newline = pending.indexOf("\n");
12479
+ }
12480
+ }
12481
+ if (pending.trim()) {
12482
+ try {
12483
+ yield JSON.parse(pending.replace(/\r$/, ""));
12484
+ } catch (error) {
12485
+ logBatchInstrumentationError("skipped malformed JSONL", error);
12486
+ onIssue(new Error("OpenAI Batch file contains malformed JSONL"));
12487
+ }
12488
+ }
12489
+ } catch (error) {
12490
+ logBatchInstrumentationError("could not read JSONL response body", error);
12491
+ onIssue(new Error("OpenAI Batch response body could not be read"));
12492
+ } finally {
12493
+ const releaseLock = read(reader, "releaseLock");
12494
+ if (typeof releaseLock === "function") {
12495
+ try {
12496
+ Reflect.apply(releaseLock, reader, []);
12497
+ } catch (error) {
12498
+ logBatchInstrumentationError(
12499
+ "could not release stream reader",
12500
+ error
12501
+ );
12502
+ }
12503
+ }
12504
+ }
12505
+ return;
12506
+ }
12507
+ if (isBatchRecordIterable(resolvedFile)) {
12508
+ for await (const record of resolvedFile) {
12509
+ yield record;
12510
+ }
12511
+ return;
12512
+ }
12513
+ logBatchInstrumentationError("skipped invalid JSONL source", resolvedFile);
12514
+ onIssue(new Error("OpenAI Batch file source is invalid"));
12515
+ }
12516
+ async function readBatchInputs(file) {
12517
+ const inputs = /* @__PURE__ */ new Map();
12518
+ const issues = [];
12519
+ let endpoint;
12520
+ try {
12521
+ for await (const value of jsonlRecords(
12522
+ file,
12523
+ (issue) => issues.push(issue)
12524
+ )) {
12525
+ const customId = read(value, "custom_id");
12526
+ const url = read(value, "url");
12527
+ const body = read(value, "body");
12528
+ if (!validCustomId(customId) || inputs.has(customId) || read(value, "method") !== "POST" || typeof url !== "string" || !SUPPORTED_ENDPOINTS.has(url) || endpoint !== void 0 && endpoint !== url || !isObject(body)) {
12529
+ issues.push(new Error("OpenAI Batch input contains an invalid record"));
12530
+ continue;
12531
+ }
12532
+ endpoint = url;
12533
+ let spanData;
12534
+ try {
12535
+ spanData = extractOpenAIBatchInput(url, body);
12536
+ } catch (error) {
12537
+ logBatchInstrumentationError("could not extract batch input", error);
12538
+ }
12539
+ inputs.set(customId, { customId, spanData });
12540
+ }
12541
+ } catch (error) {
12542
+ logBatchInstrumentationError("could not process input file", error);
12543
+ issues.push(new Error("OpenAI Batch input file could not be processed"));
12544
+ }
12545
+ if (!endpoint || inputs.size === 0) {
12546
+ issues.push(new Error("OpenAI Batch input contains no supported records"));
12547
+ }
12548
+ return { endpoint, inputs, issues };
12549
+ }
12550
+ async function writePendingSpans(trace, endTime) {
12551
+ const task = await startBatchSpan(trace.context);
12552
+ const taskParent = await task.export();
12553
+ for (const input of trace.inputs.values()) {
12554
+ try {
12555
+ const child = await startBatchChild(trace.context, taskParent, input);
12556
+ if (endTime !== void 0) {
12557
+ child.end({ endTime });
12558
+ }
12559
+ } catch (error) {
12560
+ logBatchInstrumentationError("could not write batch request span", error);
12561
+ }
12562
+ }
12563
+ if (endTime !== void 0) {
12564
+ if (trace.status && trace.status !== "completed") {
12565
+ task.log({ error: new Error(`OpenAI Batch ${trace.status}`) });
12566
+ }
12567
+ task.end({ endTime });
12568
+ }
12569
+ }
12570
+ var interceptOpenAIFilesCreateTraced = async (target, thisArg, args) => {
12571
+ const startTime = getCurrentUnixTimestamp();
12572
+ const inputPromise = readBatchInputs(args[0].inputFileContent);
12573
+ const parentPromise = exportParent(args[0].parent);
12574
+ const file = await Reflect.apply(target, thisArg, args);
12575
+ try {
12576
+ const inputFileId = read(file, "id");
12577
+ const [inputData, exportedParent2] = await Promise.all([
12578
+ inputPromise,
12579
+ parentPromise
12580
+ ]);
12581
+ if (typeof inputFileId !== "string" || !exportedParent2 || !inputData.endpoint || inputData.issues.length > 0) {
12582
+ if (inputData.issues[0]) {
12583
+ logBatchInstrumentationError(
12584
+ "skipped invalid input file",
12585
+ inputData.issues[0]
12586
+ );
12587
+ }
12588
+ return file;
12589
+ }
12590
+ const trace = {
12591
+ context: {
12592
+ inputFileId,
12593
+ endpoint: inputData.endpoint,
12594
+ parent: exportedParent2,
12595
+ taskStartTime: startTime,
12596
+ childStartTime: startTime
12597
+ },
12598
+ inputs: inputData.inputs
12599
+ };
12600
+ pendingBatchTraces.set(inputFileId, trace);
12601
+ await writePendingSpans(trace);
12602
+ } catch (error) {
12603
+ logBatchInstrumentationError("could not start batch spans", error);
12604
+ }
12605
+ return file;
12134
12606
  };
12135
- function parseMetricsFromUsage(usage) {
12136
- if (!usage) {
12137
- return {};
12607
+ function validTimestamp(value) {
12608
+ return typeof value === "number" && Number.isFinite(value) && value >= 0;
12609
+ }
12610
+ function terminalEndTime(batch, startTime) {
12611
+ let timestamp;
12612
+ switch (batch.status) {
12613
+ case "completed":
12614
+ timestamp = batch.completed_at;
12615
+ break;
12616
+ case "failed":
12617
+ timestamp = batch.failed_at;
12618
+ break;
12619
+ case "expired":
12620
+ timestamp = batch.expired_at;
12621
+ break;
12622
+ default:
12623
+ timestamp = batch.cancelled_at;
12138
12624
  }
12139
- const metrics = {};
12140
- for (const [oaiName, value] of Object.entries(usage)) {
12141
- if (typeof value === "number") {
12142
- const metricName = TOKEN_NAME_MAP[oaiName] || oaiName;
12143
- metrics[metricName] = value;
12625
+ return validTimestamp(timestamp) ? Math.max(timestamp, startTime) : Math.max(getCurrentUnixTimestamp(), startTime);
12626
+ }
12627
+ async function updateBatchTimestamps(batch) {
12628
+ const trace = pendingBatchTraces.get(batch.input_file_id);
12629
+ if (!trace || trace.context.endpoint !== batch.endpoint) {
12630
+ return;
12631
+ }
12632
+ if (validTimestamp(batch.created_at)) {
12633
+ trace.context.taskStartTime = batch.created_at;
12634
+ }
12635
+ trace.context.childStartTime = validTimestamp(batch.in_progress_at) ? Math.max(batch.in_progress_at, trace.context.taskStartTime) : trace.context.taskStartTime;
12636
+ trace.status = batch.status;
12637
+ if (TERMINAL_STATUSES.has(batch.status)) {
12638
+ trace.endTime = terminalEndTime(batch, trace.context.childStartTime);
12639
+ }
12640
+ await writePendingSpans(trace, trace.endTime);
12641
+ }
12642
+ var interceptOpenAIBatchesRetrieveTraced = (target, thisArg, args) => Promise.resolve(Reflect.apply(target, thisArg, args)).then(async (batch) => {
12643
+ try {
12644
+ await updateBatchTimestamps(batch);
12645
+ } catch (error) {
12646
+ logBatchInstrumentationError("could not update batch timestamps", error);
12647
+ }
12648
+ return batch;
12649
+ });
12650
+ function errorFromResult(result) {
12651
+ const error = read(result.value, "error");
12652
+ if (isObject(error)) {
12653
+ const message = read(error, "message");
12654
+ return new Error(
12655
+ typeof message === "string" ? message : "OpenAI Batch request failed"
12656
+ );
12657
+ }
12658
+ const response = read(result.value, "response");
12659
+ const statusCode = read(response, "status_code");
12660
+ if (result.source === "error" || typeof statusCode === "number" && (statusCode < 200 || statusCode >= 300)) {
12661
+ const message = read(read(read(response, "body"), "error"), "message");
12662
+ return new Error(
12663
+ typeof message === "string" ? message : "OpenAI Batch request failed"
12664
+ );
12665
+ }
12666
+ return void 0;
12667
+ }
12668
+ async function completeBatchResult(context, taskParent, endTime, input, result) {
12669
+ const child = await startBatchChild(context, taskParent, input);
12670
+ try {
12671
+ const resultError = errorFromResult(result);
12672
+ const responseBody = read(read(result.value, "response"), "body");
12673
+ if (resultError) {
12674
+ child.log({ error: resultError });
12675
+ } else if (isObject(responseBody)) {
12676
+ const model = read(responseBody, "model");
12677
+ child.log({
12678
+ output: context.endpoint === "/v1/chat/completions" ? read(responseBody, "choices") : processImagesInOutput(read(responseBody, "output")),
12679
+ ...typeof model === "string" ? { metadata: { model } } : {},
12680
+ metrics: parseMetricsFromUsage(read(responseBody, "usage"))
12681
+ });
12682
+ } else {
12683
+ child.log({ error: new Error("OpenAI Batch response body is missing") });
12684
+ }
12685
+ } catch (error) {
12686
+ child.log({ error });
12687
+ } finally {
12688
+ child.end({ endTime });
12689
+ }
12690
+ }
12691
+ async function completeResultFile({
12692
+ context,
12693
+ endTime,
12694
+ file,
12695
+ inputs,
12696
+ issues,
12697
+ seen,
12698
+ source,
12699
+ taskParent
12700
+ }) {
12701
+ if (file === void 0) {
12702
+ return;
12703
+ }
12704
+ for await (const value of jsonlRecords(file, (issue) => issues.push(issue))) {
12705
+ const customId = read(value, "custom_id");
12706
+ if (!validCustomId(customId) || !isObject(value)) {
12707
+ issues.push(
12708
+ new Error("OpenAI Batch result is missing a valid custom_id")
12709
+ );
12144
12710
  continue;
12145
12711
  }
12146
- if (!oaiName.endsWith("_tokens_details") || !isObject(value)) {
12712
+ if (seen.has(customId)) {
12713
+ issues.push(new Error("OpenAI Batch result contains a duplicate"));
12147
12714
  continue;
12148
12715
  }
12149
- const rawPrefix = oaiName.slice(0, -"_tokens_details".length);
12150
- const prefix = TOKEN_PREFIX_MAP[rawPrefix] || rawPrefix;
12151
- for (const [key, nestedValue] of Object.entries(value)) {
12152
- if (typeof nestedValue !== "number") {
12153
- continue;
12154
- }
12155
- metrics[`${prefix}_${key}`] = nestedValue;
12716
+ const input = inputs.get(customId);
12717
+ if (!input) {
12718
+ issues.push(
12719
+ new Error("OpenAI Batch result does not match an input record")
12720
+ );
12721
+ continue;
12156
12722
  }
12723
+ seen.add(customId);
12724
+ await completeBatchResult(context, taskParent, endTime, input, {
12725
+ value,
12726
+ source
12727
+ });
12157
12728
  }
12158
- return metrics;
12159
12729
  }
12160
- function parseCachedHeader(value) {
12161
- if (!value) {
12730
+ function sameInputs(first, second) {
12731
+ return first.size === second.size && [...first.keys()].every((customId) => second.has(customId));
12732
+ }
12733
+ async function contextForCompletion(inputFileId, inputData) {
12734
+ if (!inputData.endpoint || inputData.issues.length > 0) {
12162
12735
  return void 0;
12163
12736
  }
12164
- return ["true", "hit"].includes(value.toLowerCase()) ? 1 : 0;
12165
- }
12166
- function getCachedMetricFromHeaders(headers) {
12167
- if (!headers || typeof headers.get !== "function") {
12737
+ const existing = pendingBatchTraces.get(inputFileId);
12738
+ if (existing) {
12739
+ if (existing.context.endpoint !== inputData.endpoint || !sameInputs(existing.inputs, inputData.inputs)) {
12740
+ return void 0;
12741
+ }
12742
+ return { ...existing, inputs: inputData.inputs };
12743
+ }
12744
+ const parent = await exportParent(getSpanParentObject());
12745
+ if (!parent) {
12168
12746
  return void 0;
12169
12747
  }
12170
- const cachedHeader = headers.get(X_CACHED_HEADER);
12171
- if (cachedHeader) {
12172
- return parseCachedHeader(cachedHeader);
12748
+ const startTime = getCurrentUnixTimestamp();
12749
+ return {
12750
+ context: {
12751
+ inputFileId,
12752
+ endpoint: inputData.endpoint,
12753
+ parent,
12754
+ taskStartTime: startTime,
12755
+ childStartTime: startTime
12756
+ },
12757
+ inputs: inputData.inputs
12758
+ };
12759
+ }
12760
+ async function completeBatch(args) {
12761
+ const inputData = await readBatchInputs(args.inputFileContent);
12762
+ const trace = await contextForCompletion(args.inputFileId, inputData);
12763
+ if (!trace) {
12764
+ logBatchInstrumentationError(
12765
+ "left batch spans pending",
12766
+ inputData.issues[0] ?? new Error("OpenAI Batch input does not match")
12767
+ );
12768
+ return;
12173
12769
  }
12174
- return parseCachedHeader(headers.get(LEGACY_CACHED_HEADER));
12770
+ const endTime = trace.endTime ?? getCurrentUnixTimestamp();
12771
+ const task = await startBatchSpan(trace.context);
12772
+ const taskParent = await task.export();
12773
+ const issues = [];
12774
+ const seen = /* @__PURE__ */ new Set();
12775
+ await Promise.all([
12776
+ completeResultFile({
12777
+ context: trace.context,
12778
+ endTime,
12779
+ file: args.outputFileContent,
12780
+ inputs: trace.inputs,
12781
+ issues,
12782
+ seen,
12783
+ source: "output",
12784
+ taskParent
12785
+ }),
12786
+ completeResultFile({
12787
+ context: trace.context,
12788
+ endTime,
12789
+ file: args.errorFileContent,
12790
+ inputs: trace.inputs,
12791
+ issues,
12792
+ seen,
12793
+ source: "error",
12794
+ taskParent
12795
+ })
12796
+ ]);
12797
+ if (issues.length > 0) {
12798
+ logBatchInstrumentationError("left batch spans pending", issues[0]);
12799
+ return;
12800
+ }
12801
+ const missing = [...trace.inputs.values()].filter(
12802
+ ({ customId }) => !seen.has(customId)
12803
+ );
12804
+ if (!trace.status || trace.status === "completed") {
12805
+ if (missing.length > 0) {
12806
+ logBatchInstrumentationError(
12807
+ "left batch spans pending",
12808
+ new Error("OpenAI Batch result files are incomplete")
12809
+ );
12810
+ return;
12811
+ }
12812
+ } else {
12813
+ for (const input of missing) {
12814
+ const child = await startBatchChild(trace.context, taskParent, input);
12815
+ child.log({ error: new Error(`OpenAI Batch ${trace.status}`) });
12816
+ child.end({ endTime });
12817
+ }
12818
+ task.log({ error: new Error(`OpenAI Batch ${trace.status}`) });
12819
+ }
12820
+ task.end({ endTime });
12821
+ pendingBatchTraces.delete(args.inputFileId);
12175
12822
  }
12823
+ var interceptOpenAIBatchTraceComplete = (target, thisArg, args) => Promise.resolve(Reflect.apply(target, thisArg, args)).then(async (result) => {
12824
+ try {
12825
+ await completeBatch(args[0]);
12826
+ } catch (error) {
12827
+ logBatchInstrumentationError("could not complete batch", error);
12828
+ }
12829
+ return result;
12830
+ });
12176
12831
 
12177
12832
  // src/instrumentation/plugins/openai-plugin.ts
12178
12833
  var OpenAIPlugin = class extends BasePlugin {
@@ -12180,17 +12835,22 @@ var OpenAIPlugin = class extends BasePlugin {
12180
12835
  super();
12181
12836
  }
12182
12837
  onEnable() {
12838
+ this.unsubscribers.push(
12839
+ openAIChannels.filesCreateTraced.intercept(
12840
+ interceptOpenAIFilesCreateTraced
12841
+ ),
12842
+ openAIChannels.batchesRetrieveTraced.intercept(
12843
+ interceptOpenAIBatchesRetrieveTraced
12844
+ ),
12845
+ openAIChannels.batchesCompleteTrace.intercept(
12846
+ interceptOpenAIBatchTraceComplete
12847
+ )
12848
+ );
12183
12849
  this.unsubscribers.push(
12184
12850
  traceStreamingChannel(openAIChannels.chatCompletionsCreate, {
12185
12851
  name: "Chat Completion",
12186
12852
  type: "llm" /* LLM */,
12187
- extractInput: ([params]) => {
12188
- const { messages, ...metadata } = params;
12189
- return {
12190
- input: processInputAttachments(messages),
12191
- metadata: { ...metadata, provider: "openai" }
12192
- };
12193
- },
12853
+ extractInput: ([params]) => extractOpenAIChatInput(params),
12194
12854
  extractOutput: (result) => {
12195
12855
  return result?.choices;
12196
12856
  },
@@ -12236,13 +12896,7 @@ var OpenAIPlugin = class extends BasePlugin {
12236
12896
  traceStreamingChannel(openAIChannels.betaChatCompletionsParse, {
12237
12897
  name: "Chat Completion",
12238
12898
  type: "llm" /* LLM */,
12239
- extractInput: ([params]) => {
12240
- const { messages, ...metadata } = params;
12241
- return {
12242
- input: processInputAttachments(messages),
12243
- metadata: { ...metadata, provider: "openai" }
12244
- };
12245
- },
12899
+ extractInput: ([params]) => extractOpenAIChatInput(params),
12246
12900
  extractOutput: (result) => {
12247
12901
  return result?.choices;
12248
12902
  },
@@ -12264,13 +12918,7 @@ var OpenAIPlugin = class extends BasePlugin {
12264
12918
  traceSyncStreamChannel(openAIChannels.betaChatCompletionsStream, {
12265
12919
  name: "Chat Completion",
12266
12920
  type: "llm" /* LLM */,
12267
- extractInput: ([params]) => {
12268
- const { messages, ...metadata } = params;
12269
- return {
12270
- input: processInputAttachments(messages),
12271
- metadata: { ...metadata, provider: "openai" }
12272
- };
12273
- }
12921
+ extractInput: ([params]) => extractOpenAIChatInput(params)
12274
12922
  })
12275
12923
  );
12276
12924
  this.unsubscribers.push(
@@ -12300,23 +12948,11 @@ var OpenAIPlugin = class extends BasePlugin {
12300
12948
  traceStreamingChannel(openAIChannels.responsesCreate, {
12301
12949
  name: "openai.responses.create",
12302
12950
  type: "llm" /* LLM */,
12303
- extractInput: ([params]) => {
12304
- const { input, ...metadata } = params;
12305
- return {
12306
- input: processInputAttachments(input),
12307
- metadata: { ...metadata, provider: "openai" }
12308
- };
12309
- },
12951
+ extractInput: ([params]) => extractOpenAIResponsesInput(params),
12310
12952
  extractOutput: (result) => {
12311
12953
  return processImagesInOutput(result?.output);
12312
12954
  },
12313
- extractMetadata: (result) => {
12314
- if (!result) {
12315
- return void 0;
12316
- }
12317
- const { output: _output, usage: _usage, ...metadata } = result;
12318
- return Object.keys(metadata).length > 0 ? metadata : void 0;
12319
- },
12955
+ extractMetadata: (result) => extractOpenAIResponsesMetadata(result),
12320
12956
  extractMetrics: (result, startTime, endEvent) => {
12321
12957
  const metrics = withCachedMetric(
12322
12958
  parseMetricsFromUsage(result?.usage),
@@ -12335,13 +12971,7 @@ var OpenAIPlugin = class extends BasePlugin {
12335
12971
  traceSyncStreamChannel(openAIChannels.responsesStream, {
12336
12972
  name: "openai.responses.create",
12337
12973
  type: "llm" /* LLM */,
12338
- extractInput: ([params]) => {
12339
- const { input, ...metadata } = params;
12340
- return {
12341
- input: processInputAttachments(input),
12342
- metadata: { ...metadata, provider: "openai" }
12343
- };
12344
- },
12974
+ extractInput: ([params]) => extractOpenAIResponsesInput(params),
12345
12975
  extractFromEvent: (event) => {
12346
12976
  if (event.type !== "response.completed" || !event.response) {
12347
12977
  return {};
@@ -12364,23 +12994,11 @@ var OpenAIPlugin = class extends BasePlugin {
12364
12994
  traceStreamingChannel(openAIChannels.responsesParse, {
12365
12995
  name: "openai.responses.parse",
12366
12996
  type: "llm" /* LLM */,
12367
- extractInput: ([params]) => {
12368
- const { input, ...metadata } = params;
12369
- return {
12370
- input: processInputAttachments(input),
12371
- metadata: { ...metadata, provider: "openai" }
12372
- };
12373
- },
12997
+ extractInput: ([params]) => extractOpenAIResponsesInput(params),
12374
12998
  extractOutput: (result) => {
12375
12999
  return processImagesInOutput(result?.output);
12376
13000
  },
12377
- extractMetadata: (result) => {
12378
- if (!result) {
12379
- return void 0;
12380
- }
12381
- const { output: _output, usage: _usage, ...metadata } = result;
12382
- return Object.keys(metadata).length > 0 ? metadata : void 0;
12383
- },
13001
+ extractMetadata: (result) => extractOpenAIResponsesMetadata(result),
12384
13002
  extractMetrics: (result, startTime, endEvent) => {
12385
13003
  const metrics = withCachedMetric(
12386
13004
  parseMetricsFromUsage(result?.usage),
@@ -12399,23 +13017,11 @@ var OpenAIPlugin = class extends BasePlugin {
12399
13017
  traceAsyncChannel(openAIChannels.responsesCompact, {
12400
13018
  name: "openai.responses.compact",
12401
13019
  type: "llm" /* LLM */,
12402
- extractInput: ([params]) => {
12403
- const { input, ...metadata } = params;
12404
- return {
12405
- input: processInputAttachments(input),
12406
- metadata: { ...metadata, provider: "openai" }
12407
- };
12408
- },
13020
+ extractInput: ([params]) => extractOpenAIResponsesInput(params),
12409
13021
  extractOutput: (result) => {
12410
13022
  return processImagesInOutput(result?.output);
12411
13023
  },
12412
- extractMetadata: (result) => {
12413
- if (!result) {
12414
- return void 0;
12415
- }
12416
- const { output: _output, usage: _usage, ...metadata } = result;
12417
- return Object.keys(metadata).length > 0 ? metadata : void 0;
12418
- },
13024
+ extractMetadata: (result) => extractOpenAIResponsesMetadata(result),
12419
13025
  extractMetrics: (result, startTime, endEvent) => {
12420
13026
  const metrics = withCachedMetric(
12421
13027
  parseMetricsFromUsage(result?.usage),
@@ -12471,35 +13077,6 @@ function withCachedMetric(metrics, result, endEvent) {
12471
13077
  cached
12472
13078
  };
12473
13079
  }
12474
- function processImagesInOutput(output) {
12475
- if (Array.isArray(output)) {
12476
- return output.map(processImagesInOutput);
12477
- }
12478
- if (isObject(output)) {
12479
- if (output.type === "image_generation_call" && output.result && typeof output.result === "string") {
12480
- const fileExtension = output.output_format || "png";
12481
- const contentType = `image/${fileExtension}`;
12482
- const baseFilename = output.revised_prompt && typeof output.revised_prompt === "string" ? output.revised_prompt.slice(0, 50).replace(/[^a-zA-Z0-9]/g, "_") : "generated_image";
12483
- const filename = `${baseFilename}.${fileExtension}`;
12484
- const binaryString = atob(output.result);
12485
- const bytes = new Uint8Array(binaryString.length);
12486
- for (let i = 0; i < binaryString.length; i++) {
12487
- bytes[i] = binaryString.charCodeAt(i);
12488
- }
12489
- const blob = new Blob([bytes], { type: contentType });
12490
- const attachment = new Attachment({
12491
- data: blob,
12492
- filename,
12493
- contentType
12494
- });
12495
- return {
12496
- ...output,
12497
- result: attachment
12498
- };
12499
- }
12500
- }
12501
- return output;
12502
- }
12503
13080
  function mergeLogprobTokens(existing, incoming) {
12504
13081
  if (incoming === void 0) {
12505
13082
  return existing;
@@ -32519,7 +33096,7 @@ function extractSession(event) {
32519
33096
  return isObject(candidate) && typeof candidate.prompt === "function" ? candidate : void 0;
32520
33097
  }
32521
33098
  function isPiAgent(value) {
32522
- return isObject(value) && typeof value.streamFn === "function" && typeof value.subscribe === "function";
33099
+ return isObject(value) && (typeof value.streamFunction === "function" || typeof value.streamFn === "function") && typeof value.subscribe === "function";
32523
33100
  }
32524
33101
  function promptContextStore() {
32525
33102
  piPromptContextStore ??= isomorph_default.newAsyncLocalStorage();
@@ -32529,17 +33106,21 @@ function currentPiPromptState() {
32529
33106
  return promptContextStore().getStore();
32530
33107
  }
32531
33108
  function installPiAgentInstrumentation(agent) {
33109
+ const property = typeof agent.streamFunction === "function" ? "streamFunction" : "streamFn";
33110
+ const streamFunction = agent[property];
32532
33111
  const existing = piAgentPatchStates.get(agent);
32533
- if (!existing || agent.streamFn !== existing.wrappedStreamFn) {
33112
+ if (streamFunction && (!existing || existing.property !== property || streamFunction !== existing.wrappedStreamFunction)) {
32534
33113
  const patchState = {
32535
- originalStreamFn: agent.streamFn,
32536
- wrappedStreamFn: agent.streamFn
33114
+ originalStreamFunction: streamFunction,
33115
+ property,
33116
+ wrappedStreamFunction: streamFunction
32537
33117
  };
32538
- patchState.wrappedStreamFn = makeInstrumentedStreamFn(
33118
+ patchState.wrappedStreamFunction = makeInstrumentedStreamFunction(
32539
33119
  agent,
32540
- patchState.originalStreamFn
33120
+ patchState.originalStreamFunction,
33121
+ property
32541
33122
  );
32542
- agent.streamFn = patchState.wrappedStreamFn;
33123
+ agent[property] = patchState.wrappedStreamFunction;
32543
33124
  piAgentPatchStates.set(agent, patchState);
32544
33125
  }
32545
33126
  if (piAgentEventSubscriptions.has(agent)) {
@@ -32564,15 +33145,21 @@ function installPiAgentInstrumentation(agent) {
32564
33145
  logInstrumentationError4("Pi Coding Agent event subscription", error);
32565
33146
  }
32566
33147
  }
32567
- function makeInstrumentedStreamFn(agent, originalStreamFn) {
32568
- return async function instrumentedPiStreamFn(model, context, options) {
32569
- const invokeOriginal = () => Reflect.apply(originalStreamFn, this, [model, context, options]);
33148
+ function makeInstrumentedStreamFunction(agent, originalStreamFunction, property) {
33149
+ return async function instrumentedPiStreamFunction(model, context, options) {
33150
+ const invokeOriginal = () => Reflect.apply(originalStreamFunction, this, [model, context, options]);
32570
33151
  const state = currentPiPromptState();
32571
33152
  if (!state || state.agent !== agent || state.finalized) {
32572
33153
  return invokeOriginal();
32573
33154
  }
32574
33155
  wrapPiToolExecutors(context.tools);
32575
- const llmState = await startPiLlmSpan(state, model, context, options);
33156
+ const llmState = await startPiLlmSpan(
33157
+ state,
33158
+ model,
33159
+ context,
33160
+ property,
33161
+ options
33162
+ );
32576
33163
  try {
32577
33164
  const stream = await runWithAutoInstrumentationSuppressed(invokeOriginal);
32578
33165
  return patchAssistantMessageStream(stream, state, llmState);
@@ -32609,12 +33196,12 @@ function wrapPiToolExecutors(tools) {
32609
33196
  }
32610
33197
  }
32611
33198
  }
32612
- async function startPiLlmSpan(state, model, context, options) {
33199
+ async function startPiLlmSpan(state, model, context, property, options) {
32613
33200
  const metadata = {
32614
33201
  ...extractModelMetadata2(model),
32615
33202
  ...extractStreamOptionsMetadata(options),
32616
33203
  ...extractToolMetadata(context.tools),
32617
- "pi_coding_agent.operation": "agent.streamFn"
33204
+ "pi_coding_agent.operation": `agent.${property}`
32618
33205
  };
32619
33206
  const span = startSpan(
32620
33207
  withSpanInstrumentationName(
@@ -36925,7 +37512,15 @@ var MAX_EVE_PROVIDER_CACHE_ENTRIES = 1e4;
36925
37512
  function createEveInstrumentationProvider(options = {}) {
36926
37513
  const bridge = new EveProviderBridge(options.metadata);
36927
37514
  return {
37515
+ // Eve versions before tracePolicy use capture to decide whether provider
37516
+ // events include content. Newer versions prefer tracePolicy when both are
37517
+ // present, so keep the deprecated field for backwards compatibility.
36928
37518
  capture: "content",
37519
+ tracePolicy: () => ({
37520
+ emit: true,
37521
+ recordInputs: true,
37522
+ recordOutputs: true
37523
+ }),
36929
37524
  events: {
36930
37525
  "action.completed": (event, context) => bridge.handleActionTerminal(event, context),
36931
37526
  "action.failed": (event, context) => bridge.handleActionTerminal(event, context),