braintrust 3.29.0 → 3.31.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 (51) hide show
  1. package/dev/dist/index.d.mts +4 -1
  2. package/dev/dist/index.d.ts +4 -1
  3. package/dev/dist/index.js +1492 -887
  4. package/dev/dist/index.mjs +836 -231
  5. package/dist/apply-auto-instrumentation.js +217 -217
  6. package/dist/apply-auto-instrumentation.mjs +2 -2
  7. package/dist/auto-instrumentations/bundler/esbuild.cjs +13 -1
  8. package/dist/auto-instrumentations/bundler/esbuild.mjs +2 -2
  9. package/dist/auto-instrumentations/bundler/next.cjs +22 -4
  10. package/dist/auto-instrumentations/bundler/next.mjs +12 -6
  11. package/dist/auto-instrumentations/bundler/rollup.cjs +13 -1
  12. package/dist/auto-instrumentations/bundler/rollup.mjs +2 -2
  13. package/dist/auto-instrumentations/bundler/vite.cjs +13 -1
  14. package/dist/auto-instrumentations/bundler/vite.mjs +2 -2
  15. package/dist/auto-instrumentations/bundler/webpack-loader.cjs +13 -1
  16. package/dist/auto-instrumentations/bundler/webpack.cjs +13 -1
  17. package/dist/auto-instrumentations/bundler/webpack.mjs +3 -3
  18. package/dist/auto-instrumentations/{chunk-DKTGDNA7.mjs → chunk-LW4HDHXT.mjs} +1 -1
  19. package/dist/auto-instrumentations/{chunk-OMCZ3MV2.mjs → chunk-OXINGIEZ.mjs} +1 -1
  20. package/dist/auto-instrumentations/{chunk-AOIYCVEL.mjs → chunk-U64OYU4Q.mjs} +13 -1
  21. package/dist/auto-instrumentations/hook.mjs +18 -4
  22. package/dist/auto-instrumentations/index.cjs +13 -1
  23. package/dist/auto-instrumentations/index.mjs +1 -1
  24. package/dist/browser.d.mts +614 -480
  25. package/dist/browser.d.ts +614 -480
  26. package/dist/browser.js +1172 -305
  27. package/dist/browser.mjs +1172 -305
  28. package/dist/{chunk-6Z5S7VOU.js → chunk-4AQGZS6X.js} +13 -1
  29. package/dist/{chunk-M6XPNJC4.mjs → chunk-AJT4FR25.mjs} +703 -120
  30. package/dist/{chunk-7FA6VP2S.mjs → chunk-CE3BLB2L.mjs} +13 -1
  31. package/dist/{chunk-XLLGRXGR.js → chunk-V32LW47Z.js} +1819 -1236
  32. package/dist/cli.js +846 -241
  33. package/dist/edge-light.d.mts +1 -1
  34. package/dist/edge-light.d.ts +1 -1
  35. package/dist/edge-light.js +1172 -305
  36. package/dist/edge-light.mjs +1172 -305
  37. package/dist/index.d.mts +614 -480
  38. package/dist/index.d.ts +614 -480
  39. package/dist/index.js +915 -643
  40. package/dist/index.mjs +349 -77
  41. package/dist/instrumentation/index.d.mts +15 -0
  42. package/dist/instrumentation/index.d.ts +15 -0
  43. package/dist/instrumentation/index.js +836 -230
  44. package/dist/instrumentation/index.mjs +836 -230
  45. package/dist/vitest-evals-reporter.js +16 -16
  46. package/dist/vitest-evals-reporter.mjs +2 -2
  47. package/dist/workerd.d.mts +1 -1
  48. package/dist/workerd.d.ts +1 -1
  49. package/dist/workerd.js +1172 -305
  50. package/dist/workerd.mjs +1172 -305
  51. package/package.json +1 -1
@@ -4118,6 +4118,7 @@ var ProjectSettings = z6.union([
4118
4118
  ]),
4119
4119
  disable_realtime_queries: z6.union([z6.boolean(), z6.null()]),
4120
4120
  monitor_charts_use_metrics_start: z6.union([z6.boolean(), z6.null()]),
4121
+ blind_reviews: z6.union([z6.boolean(), z6.null()]),
4121
4122
  default_preprocessor: NullableSavedFunctionId
4122
4123
  }).partial(),
4123
4124
  z6.null()
@@ -4179,6 +4180,7 @@ var WindowedAutomationConfig = z6.object({
4179
4180
  auto_approve_tools: z6.array(z6.string().min(1)).optional().default([]),
4180
4181
  harness: z6.enum(["native", "codex", "claude-code"]).optional(),
4181
4182
  model: z6.string().min(1).optional(),
4183
+ endpoint_name: z6.string().min(1).optional(),
4182
4184
  reasoning_effort: z6.enum(["none", "minimal", "low", "medium", "high", "xhigh", "max"]).optional()
4183
4185
  }).optional(),
4184
4186
  actions: z6.array(
@@ -5972,7 +5974,7 @@ var INSTRUMENTATION_NAMES = {
5972
5974
  var INTERNAL_SPAN_INSTRUMENTATION_NAME = /* @__PURE__ */ Symbol.for(
5973
5975
  "braintrust.spanInstrumentationName"
5974
5976
  );
5975
- var SDK_VERSION = true ? "3.29.0" : "0.0.0";
5977
+ var SDK_VERSION = true ? "3.31.0" : "0.0.0";
5976
5978
  function withSpanInstrumentationName(args, instrumentationName) {
5977
5979
  return {
5978
5980
  ...args,
@@ -9487,6 +9489,15 @@ function _internalStartSpanWithInitialMerge(args) {
9487
9489
  [INITIAL_SPAN_WRITE_AS_MERGE]: true
9488
9490
  }).span;
9489
9491
  }
9492
+ function _internalStartSpanWithInitialMergeAndParentSpanIds(args) {
9493
+ return startSpanAndIsLogger(
9494
+ {
9495
+ ...args,
9496
+ [INITIAL_SPAN_WRITE_AS_MERGE]: true
9497
+ },
9498
+ { useParentSpanIdsForObjectParent: true }
9499
+ ).span;
9500
+ }
9490
9501
  function _internalStartSpanWithContext(args, context) {
9491
9502
  return startSpanAndIsLogger({
9492
9503
  ...args,
@@ -9497,7 +9508,7 @@ async function flush(options) {
9497
9508
  const state = options?.state ?? _globalState;
9498
9509
  return await state.bgLogger().flush();
9499
9510
  }
9500
- function startSpanAndIsLogger(args) {
9511
+ function startSpanAndIsLogger(args, internalOptions) {
9501
9512
  const state = args?.state ?? _globalState;
9502
9513
  const { parentObject, propagatedState } = getSpanParentObjectAndPropagatedState({
9503
9514
  asyncFlush: args?.asyncFlush,
@@ -9511,7 +9522,7 @@ function startSpanAndIsLogger(args) {
9511
9522
  ) ? {
9512
9523
  spanId: parentObject.data.span_id,
9513
9524
  rootSpanId: parentObject.data.root_span_id
9514
- } : void 0;
9525
+ } : internalOptions?.useParentSpanIdsForObjectParent ? args?.parentSpanIds : void 0;
9515
9526
  const { parent: _ignoredParent, ...spanArgs } = args ?? {};
9516
9527
  const span = new SpanImpl({
9517
9528
  state,
@@ -12811,6 +12822,131 @@ function unsubscribeAll(unsubscribers) {
12811
12822
  return [];
12812
12823
  }
12813
12824
 
12825
+ // src/instrumentation/core/channel-definitions.ts
12826
+ function channel(spec) {
12827
+ return spec;
12828
+ }
12829
+ function defineChannels(pkg, channels, options) {
12830
+ const { instrumentationName } = options;
12831
+ return Object.fromEntries(
12832
+ Object.entries(channels).map(([key, spec]) => {
12833
+ const fullChannelName = `orchestrion:${pkg}:${spec.channelName}`;
12834
+ if (spec.kind === "async") {
12835
+ const asyncSpec = spec;
12836
+ const tracingChannel2 = () => isomorph_default.newTracingChannel(
12837
+ fullChannelName
12838
+ );
12839
+ const intercept2 = (interceptor) => {
12840
+ const hook = tracingChannel2();
12841
+ return typeof hook.intercept === "function" ? hook.intercept(interceptor) : () => {
12842
+ };
12843
+ };
12844
+ return [
12845
+ key,
12846
+ {
12847
+ ...asyncSpec,
12848
+ instrumentationName,
12849
+ intercept: intercept2,
12850
+ invoke: (target, thisArg, args, additional) => {
12851
+ const hook = tracingChannel2();
12852
+ return typeof hook.invoke === "function" ? hook.invoke(target, thisArg, args, additional) : Reflect.apply(target, thisArg, args);
12853
+ },
12854
+ tracingChannel: tracingChannel2,
12855
+ tracePromise: (fn, context) => tracingChannel2().tracePromise(
12856
+ fn,
12857
+ // eslint-disable-next-line @typescript-eslint/consistent-type-assertions
12858
+ context
12859
+ )
12860
+ }
12861
+ ];
12862
+ }
12863
+ const syncSpec = spec;
12864
+ const tracingChannel = () => isomorph_default.newTracingChannel(
12865
+ fullChannelName
12866
+ );
12867
+ const intercept = (interceptor) => {
12868
+ const hook = tracingChannel();
12869
+ return typeof hook.intercept === "function" ? hook.intercept(interceptor) : () => {
12870
+ };
12871
+ };
12872
+ return [
12873
+ key,
12874
+ {
12875
+ ...syncSpec,
12876
+ instrumentationName,
12877
+ intercept,
12878
+ invoke: (target, thisArg, args, additional) => {
12879
+ const hook = tracingChannel();
12880
+ return typeof hook.invoke === "function" ? hook.invoke(target, thisArg, args, additional) : Reflect.apply(target, thisArg, args);
12881
+ },
12882
+ tracingChannel,
12883
+ traceSync: (fn, context) => tracingChannel().traceSync(
12884
+ fn,
12885
+ // eslint-disable-next-line @typescript-eslint/consistent-type-assertions
12886
+ context
12887
+ )
12888
+ }
12889
+ ];
12890
+ })
12891
+ );
12892
+ }
12893
+
12894
+ // src/instrumentation/plugins/openai-channels.ts
12895
+ var openAIChannels = defineChannels(
12896
+ "openai",
12897
+ {
12898
+ filesCreateTraced: channel({
12899
+ channelName: "files.create-traced",
12900
+ kind: "async"
12901
+ }),
12902
+ batchesRetrieveTraced: channel({
12903
+ channelName: "batches.retrieve-traced",
12904
+ kind: "async"
12905
+ }),
12906
+ batchesCompleteTrace: channel({
12907
+ channelName: "batches.complete-trace",
12908
+ kind: "async"
12909
+ }),
12910
+ chatCompletionsCreate: channel({
12911
+ channelName: "chat.completions.create",
12912
+ kind: "async"
12913
+ }),
12914
+ embeddingsCreate: channel({
12915
+ channelName: "embeddings.create",
12916
+ kind: "async"
12917
+ }),
12918
+ betaChatCompletionsParse: channel({
12919
+ channelName: "beta.chat.completions.parse",
12920
+ kind: "async"
12921
+ }),
12922
+ betaChatCompletionsStream: channel({
12923
+ channelName: "beta.chat.completions.stream",
12924
+ kind: "sync-stream"
12925
+ }),
12926
+ moderationsCreate: channel({
12927
+ channelName: "moderations.create",
12928
+ kind: "async"
12929
+ }),
12930
+ responsesCreate: channel({
12931
+ channelName: "responses.create",
12932
+ kind: "async"
12933
+ }),
12934
+ responsesStream: channel({
12935
+ channelName: "responses.stream",
12936
+ kind: "sync-stream"
12937
+ }),
12938
+ responsesParse: channel({
12939
+ channelName: "responses.parse",
12940
+ kind: "async"
12941
+ }),
12942
+ responsesCompact: channel({
12943
+ channelName: "responses.compact",
12944
+ kind: "async"
12945
+ })
12946
+ },
12947
+ { instrumentationName: INSTRUMENTATION_NAMES.OPENAI }
12948
+ );
12949
+
12814
12950
  // src/wrappers/attachment-utils.ts
12815
12951
  function getExtensionFromMediaType(mediaType) {
12816
12952
  const extensionMap = {
@@ -12986,118 +13122,91 @@ function processInputAttachments(input) {
12986
13122
  return processNode(input);
12987
13123
  }
12988
13124
 
12989
- // src/instrumentation/core/channel-definitions.ts
12990
- function channel(spec) {
12991
- return spec;
12992
- }
12993
- function defineChannels(pkg, channels, options) {
12994
- const { instrumentationName } = options;
12995
- return Object.fromEntries(
12996
- Object.entries(channels).map(([key, spec]) => {
12997
- const fullChannelName = `orchestrion:${pkg}:${spec.channelName}`;
12998
- if (spec.kind === "async") {
12999
- const asyncSpec = spec;
13000
- const tracingChannel2 = () => isomorph_default.newTracingChannel(
13001
- fullChannelName
13002
- );
13003
- const intercept2 = (interceptor) => {
13004
- const hook = tracingChannel2();
13005
- return typeof hook.intercept === "function" ? hook.intercept(interceptor) : () => {
13006
- };
13007
- };
13008
- return [
13009
- key,
13010
- {
13011
- ...asyncSpec,
13012
- instrumentationName,
13013
- intercept: intercept2,
13014
- invoke: (target, thisArg, args, additional) => {
13015
- const hook = tracingChannel2();
13016
- return typeof hook.invoke === "function" ? hook.invoke(target, thisArg, args, additional) : Reflect.apply(target, thisArg, args);
13017
- },
13018
- tracingChannel: tracingChannel2,
13019
- tracePromise: (fn, context) => tracingChannel2().tracePromise(
13020
- fn,
13021
- // eslint-disable-next-line @typescript-eslint/consistent-type-assertions
13022
- context
13023
- )
13024
- }
13025
- ];
13125
+ // src/instrumentation/plugins/openai-span-data.ts
13126
+ var OPENAI_METADATA_KEYS = [
13127
+ "model",
13128
+ "temperature",
13129
+ "top_p",
13130
+ "max_tokens",
13131
+ "frequency_penalty",
13132
+ "presence_penalty",
13133
+ "stop",
13134
+ "response_format",
13135
+ "tools",
13136
+ "tool_choice",
13137
+ "parallel_tool_calls",
13138
+ "max_tool_calls"
13139
+ ];
13140
+ function batchMetadata(params) {
13141
+ const metadata = { provider: "openai" };
13142
+ for (const key of OPENAI_METADATA_KEYS) {
13143
+ try {
13144
+ const value = Reflect.get(params, key);
13145
+ if (value !== void 0) {
13146
+ metadata[key] = value;
13026
13147
  }
13027
- const syncSpec = spec;
13028
- const tracingChannel = () => isomorph_default.newTracingChannel(
13029
- fullChannelName
13030
- );
13031
- const intercept = (interceptor) => {
13032
- const hook = tracingChannel();
13033
- return typeof hook.intercept === "function" ? hook.intercept(interceptor) : () => {
13034
- };
13035
- };
13036
- return [
13037
- key,
13038
- {
13039
- ...syncSpec,
13040
- instrumentationName,
13041
- intercept,
13042
- invoke: (target, thisArg, args, additional) => {
13043
- const hook = tracingChannel();
13044
- return typeof hook.invoke === "function" ? hook.invoke(target, thisArg, args, additional) : Reflect.apply(target, thisArg, args);
13045
- },
13046
- tracingChannel,
13047
- traceSync: (fn, context) => tracingChannel().traceSync(
13048
- fn,
13049
- // eslint-disable-next-line @typescript-eslint/consistent-type-assertions
13050
- context
13051
- )
13052
- }
13053
- ];
13054
- })
13055
- );
13148
+ } catch {
13149
+ }
13150
+ }
13151
+ return metadata;
13152
+ }
13153
+ function extractOpenAIBatchInput(endpoint, params) {
13154
+ const input = endpoint === "/v1/chat/completions" ? params.messages : params.input;
13155
+ return {
13156
+ input: processInputAttachments(input),
13157
+ metadata: batchMetadata(params)
13158
+ };
13159
+ }
13160
+ function extractOpenAIChatInput(params) {
13161
+ const { messages, ...metadata } = params;
13162
+ return {
13163
+ input: processInputAttachments(messages),
13164
+ metadata: { ...metadata, provider: "openai" }
13165
+ };
13166
+ }
13167
+ function extractOpenAIResponsesInput(params) {
13168
+ const { input, ...metadata } = params;
13169
+ return {
13170
+ input: processInputAttachments(input),
13171
+ metadata: { ...metadata, provider: "openai" }
13172
+ };
13173
+ }
13174
+ function extractOpenAIResponsesMetadata(result) {
13175
+ if (!result) {
13176
+ return void 0;
13177
+ }
13178
+ const { output: _output, usage: _usage, ...metadata } = result;
13179
+ return Object.keys(metadata).length > 0 ? metadata : void 0;
13180
+ }
13181
+ function processImagesInOutput(output) {
13182
+ if (Array.isArray(output)) {
13183
+ return output.map(processImagesInOutput);
13184
+ }
13185
+ if (isObject(output) && output.type === "image_generation_call" && typeof output.result === "string" && output.result) {
13186
+ const fileExtension = output.output_format || "png";
13187
+ const contentType = `image/${fileExtension}`;
13188
+ const baseFilename = typeof output.revised_prompt === "string" ? output.revised_prompt.slice(0, 50).replace(/[^a-zA-Z0-9]/g, "_") : "generated_image";
13189
+ let binaryString;
13190
+ try {
13191
+ binaryString = atob(output.result);
13192
+ } catch {
13193
+ return output;
13194
+ }
13195
+ const bytes = new Uint8Array(binaryString.length);
13196
+ for (let i = 0; i < binaryString.length; i++) {
13197
+ bytes[i] = binaryString.charCodeAt(i);
13198
+ }
13199
+ return {
13200
+ ...output,
13201
+ result: new Attachment({
13202
+ data: new Blob([bytes], { type: contentType }),
13203
+ filename: `${baseFilename}.${fileExtension}`,
13204
+ contentType
13205
+ })
13206
+ };
13207
+ }
13208
+ return output;
13056
13209
  }
13057
-
13058
- // src/instrumentation/plugins/openai-channels.ts
13059
- var openAIChannels = defineChannels(
13060
- "openai",
13061
- {
13062
- chatCompletionsCreate: channel({
13063
- channelName: "chat.completions.create",
13064
- kind: "async"
13065
- }),
13066
- embeddingsCreate: channel({
13067
- channelName: "embeddings.create",
13068
- kind: "async"
13069
- }),
13070
- betaChatCompletionsParse: channel({
13071
- channelName: "beta.chat.completions.parse",
13072
- kind: "async"
13073
- }),
13074
- betaChatCompletionsStream: channel({
13075
- channelName: "beta.chat.completions.stream",
13076
- kind: "sync-stream"
13077
- }),
13078
- moderationsCreate: channel({
13079
- channelName: "moderations.create",
13080
- kind: "async"
13081
- }),
13082
- responsesCreate: channel({
13083
- channelName: "responses.create",
13084
- kind: "async"
13085
- }),
13086
- responsesStream: channel({
13087
- channelName: "responses.stream",
13088
- kind: "sync-stream"
13089
- }),
13090
- responsesParse: channel({
13091
- channelName: "responses.parse",
13092
- kind: "async"
13093
- }),
13094
- responsesCompact: channel({
13095
- channelName: "responses.compact",
13096
- kind: "async"
13097
- })
13098
- },
13099
- { instrumentationName: INSTRUMENTATION_NAMES.OPENAI }
13100
- );
13101
13210
 
13102
13211
  // src/openai-utils.ts
13103
13212
  var BRAINTRUST_CACHED_STREAM_METRIC = "__braintrust_cached_metric";
@@ -13154,23 +13263,573 @@ function getCachedMetricFromHeaders(headers) {
13154
13263
  return parseCachedHeader(headers.get(LEGACY_CACHED_HEADER));
13155
13264
  }
13156
13265
 
13266
+ // src/instrumentation/plugins/openai-batch-instrumentation.ts
13267
+ var SUPPORTED_ENDPOINTS = /* @__PURE__ */ new Set(["/v1/chat/completions", "/v1/responses"]);
13268
+ var TERMINAL_STATUSES = /* @__PURE__ */ new Set([
13269
+ "completed",
13270
+ "failed",
13271
+ "expired",
13272
+ "cancelled"
13273
+ ]);
13274
+ var pendingBatchTraces = /* @__PURE__ */ new Map();
13275
+ function read(value, key) {
13276
+ if (!isObject(value)) {
13277
+ return void 0;
13278
+ }
13279
+ try {
13280
+ return Reflect.get(value, key);
13281
+ } catch {
13282
+ return void 0;
13283
+ }
13284
+ }
13285
+ function isBatchRecordIterable(value) {
13286
+ if (value === null || typeof value !== "object" && typeof value !== "function") {
13287
+ return false;
13288
+ }
13289
+ return typeof read(value, Symbol.iterator) === "function" || typeof read(value, Symbol.asyncIterator) === "function";
13290
+ }
13291
+ function validCustomId(value) {
13292
+ return typeof value === "string" && value.length > 0 && value.length <= 64;
13293
+ }
13294
+ function logBatchInstrumentationError(context, error) {
13295
+ debugLogger.debug(`OpenAI Batch instrumentation ${context}:`, error);
13296
+ }
13297
+ async function exportParent(parent) {
13298
+ if ("toStr" in parent && typeof parent.toStr === "function") {
13299
+ return parent.toStr();
13300
+ }
13301
+ if ("export" in parent && typeof parent.export === "function") {
13302
+ return await parent.export();
13303
+ }
13304
+ return void 0;
13305
+ }
13306
+ async function deterministicDigest(namespace, ...parts) {
13307
+ const encoded = new TextEncoder().encode(
13308
+ [namespace, ...parts].map((part) => `${part.length}:${part}`).join("\0")
13309
+ );
13310
+ return new Uint8Array(
13311
+ await globalThis.crypto.subtle.digest("SHA-256", encoded)
13312
+ );
13313
+ }
13314
+ function digestHex(bytes, length) {
13315
+ return Array.from(bytes.slice(0, length)).map((byte) => byte.toString(16).padStart(2, "0")).join("");
13316
+ }
13317
+ function digestUuid(bytes) {
13318
+ const hex = digestHex(bytes, 16);
13319
+ return `${hex.slice(0, 8)}-${hex.slice(8, 12)}-${hex.slice(12, 16)}-${hex.slice(16, 20)}-${hex.slice(20)}`;
13320
+ }
13321
+ async function batchSpanIds(inputFileId) {
13322
+ const [row, span, root] = await Promise.all([
13323
+ deterministicDigest("openai:batch:row", inputFileId),
13324
+ deterministicDigest("openai:batch:span", inputFileId),
13325
+ deterministicDigest("openai:batch:root", inputFileId)
13326
+ ]);
13327
+ return {
13328
+ rowId: digestUuid(row),
13329
+ spanId: digestHex(span, 8),
13330
+ rootSpanId: digestHex(root, 16)
13331
+ };
13332
+ }
13333
+ async function childSpanIds(inputFileId, customId) {
13334
+ const [row, span] = await Promise.all([
13335
+ deterministicDigest("openai:batch:child:row", inputFileId, customId),
13336
+ deterministicDigest("openai:batch:child:span", inputFileId, customId)
13337
+ ]);
13338
+ return { rowId: digestUuid(row), spanId: digestHex(span, 8) };
13339
+ }
13340
+ async function startBatchSpan(context) {
13341
+ const ids = await batchSpanIds(context.inputFileId);
13342
+ const parent = SpanComponentsV4.fromStr(context.parent);
13343
+ const hasParentSpan = Boolean(
13344
+ parent.data.row_id && parent.data.span_id && parent.data.root_span_id
13345
+ );
13346
+ return withCurrent(
13347
+ NOOP_SPAN,
13348
+ () => _internalStartSpanWithInitialMergeAndParentSpanIds(
13349
+ withSpanInstrumentationName(
13350
+ {
13351
+ name: "openai.batch",
13352
+ type: "task" /* TASK */,
13353
+ parent: context.parent,
13354
+ ...!hasParentSpan ? {
13355
+ parentSpanIds: {
13356
+ parentSpanIds: [],
13357
+ rootSpanId: ids.rootSpanId
13358
+ }
13359
+ } : {},
13360
+ spanId: ids.spanId,
13361
+ startTime: context.taskStartTime,
13362
+ event: {
13363
+ id: ids.rowId,
13364
+ metadata: {
13365
+ endpoint: context.endpoint,
13366
+ input_file_id: context.inputFileId,
13367
+ provider: "openai"
13368
+ }
13369
+ }
13370
+ },
13371
+ INSTRUMENTATION_NAMES.OPENAI
13372
+ )
13373
+ )
13374
+ );
13375
+ }
13376
+ async function startBatchChild(context, taskParent, input) {
13377
+ const ids = await childSpanIds(context.inputFileId, input.customId);
13378
+ return withCurrent(
13379
+ NOOP_SPAN,
13380
+ () => _internalStartSpanWithInitialMerge(
13381
+ withSpanInstrumentationName(
13382
+ {
13383
+ name: context.endpoint === "/v1/chat/completions" ? "Chat Completion" : "openai.responses.create",
13384
+ type: "llm" /* LLM */,
13385
+ parent: taskParent,
13386
+ spanId: ids.spanId,
13387
+ startTime: context.childStartTime,
13388
+ event: {
13389
+ id: ids.rowId,
13390
+ ...input.spanData?.input !== void 0 ? { input: input.spanData.input } : {},
13391
+ metadata: {
13392
+ ...input.spanData?.metadata,
13393
+ custom_id: input.customId,
13394
+ provider: "openai"
13395
+ }
13396
+ }
13397
+ },
13398
+ INSTRUMENTATION_NAMES.OPENAI
13399
+ )
13400
+ )
13401
+ );
13402
+ }
13403
+ async function* jsonlRecords(file, onIssue = () => {
13404
+ }) {
13405
+ const resolvedFile = await file;
13406
+ if (typeof resolvedFile === "string") {
13407
+ for (const line of resolvedFile.split("\n")) {
13408
+ if (!line.trim()) {
13409
+ continue;
13410
+ }
13411
+ try {
13412
+ yield JSON.parse(line.replace(/\r$/, ""));
13413
+ } catch (error) {
13414
+ logBatchInstrumentationError("skipped malformed JSONL", error);
13415
+ onIssue(new Error("OpenAI Batch file contains malformed JSONL"));
13416
+ }
13417
+ }
13418
+ return;
13419
+ }
13420
+ const body = read(resolvedFile, "body");
13421
+ const getReader = read(body, "getReader");
13422
+ if (isObject(body) && typeof getReader === "function") {
13423
+ let reader;
13424
+ try {
13425
+ reader = Reflect.apply(getReader, body, []);
13426
+ const decoder = new TextDecoder();
13427
+ let pending = "";
13428
+ while (true) {
13429
+ const readChunk = read(reader, "read");
13430
+ if (typeof readChunk !== "function") {
13431
+ throw new Error("Response body stream has no read method");
13432
+ }
13433
+ const chunk = await Reflect.apply(readChunk, reader, []);
13434
+ if (!isObject(chunk)) {
13435
+ throw new Error("Response body stream returned an invalid chunk");
13436
+ }
13437
+ if (chunk.done === true) {
13438
+ pending += decoder.decode();
13439
+ break;
13440
+ }
13441
+ if (!(chunk.value instanceof Uint8Array)) {
13442
+ throw new Error("Response body stream returned a non-byte chunk");
13443
+ }
13444
+ pending += decoder.decode(chunk.value, { stream: true });
13445
+ let newline = pending.indexOf("\n");
13446
+ while (newline !== -1) {
13447
+ const line = pending.slice(0, newline).replace(/\r$/, "");
13448
+ pending = pending.slice(newline + 1);
13449
+ if (line.trim()) {
13450
+ try {
13451
+ yield JSON.parse(line);
13452
+ } catch (error) {
13453
+ logBatchInstrumentationError("skipped malformed JSONL", error);
13454
+ onIssue(new Error("OpenAI Batch file contains malformed JSONL"));
13455
+ }
13456
+ }
13457
+ newline = pending.indexOf("\n");
13458
+ }
13459
+ }
13460
+ if (pending.trim()) {
13461
+ try {
13462
+ yield JSON.parse(pending.replace(/\r$/, ""));
13463
+ } catch (error) {
13464
+ logBatchInstrumentationError("skipped malformed JSONL", error);
13465
+ onIssue(new Error("OpenAI Batch file contains malformed JSONL"));
13466
+ }
13467
+ }
13468
+ } catch (error) {
13469
+ logBatchInstrumentationError("could not read JSONL response body", error);
13470
+ onIssue(new Error("OpenAI Batch response body could not be read"));
13471
+ } finally {
13472
+ const releaseLock = read(reader, "releaseLock");
13473
+ if (typeof releaseLock === "function") {
13474
+ try {
13475
+ Reflect.apply(releaseLock, reader, []);
13476
+ } catch (error) {
13477
+ logBatchInstrumentationError(
13478
+ "could not release stream reader",
13479
+ error
13480
+ );
13481
+ }
13482
+ }
13483
+ }
13484
+ return;
13485
+ }
13486
+ if (isBatchRecordIterable(resolvedFile)) {
13487
+ for await (const record of resolvedFile) {
13488
+ yield record;
13489
+ }
13490
+ return;
13491
+ }
13492
+ logBatchInstrumentationError("skipped invalid JSONL source", resolvedFile);
13493
+ onIssue(new Error("OpenAI Batch file source is invalid"));
13494
+ }
13495
+ async function readBatchInputs(file) {
13496
+ const inputs = /* @__PURE__ */ new Map();
13497
+ const issues = [];
13498
+ let endpoint;
13499
+ try {
13500
+ for await (const value of jsonlRecords(
13501
+ file,
13502
+ (issue) => issues.push(issue)
13503
+ )) {
13504
+ const customId = read(value, "custom_id");
13505
+ const url = read(value, "url");
13506
+ const body = read(value, "body");
13507
+ if (!validCustomId(customId) || inputs.has(customId) || read(value, "method") !== "POST" || typeof url !== "string" || !SUPPORTED_ENDPOINTS.has(url) || endpoint !== void 0 && endpoint !== url || !isObject(body)) {
13508
+ issues.push(new Error("OpenAI Batch input contains an invalid record"));
13509
+ continue;
13510
+ }
13511
+ endpoint = url;
13512
+ let spanData;
13513
+ try {
13514
+ spanData = extractOpenAIBatchInput(url, body);
13515
+ } catch (error) {
13516
+ logBatchInstrumentationError("could not extract batch input", error);
13517
+ }
13518
+ inputs.set(customId, { customId, spanData });
13519
+ }
13520
+ } catch (error) {
13521
+ logBatchInstrumentationError("could not process input file", error);
13522
+ issues.push(new Error("OpenAI Batch input file could not be processed"));
13523
+ }
13524
+ if (!endpoint || inputs.size === 0) {
13525
+ issues.push(new Error("OpenAI Batch input contains no supported records"));
13526
+ }
13527
+ return { endpoint, inputs, issues };
13528
+ }
13529
+ async function writePendingSpans(trace, endTime) {
13530
+ const task = await startBatchSpan(trace.context);
13531
+ const taskParent = await task.export();
13532
+ for (const input of trace.inputs.values()) {
13533
+ try {
13534
+ const child = await startBatchChild(trace.context, taskParent, input);
13535
+ if (endTime !== void 0) {
13536
+ child.end({ endTime });
13537
+ }
13538
+ } catch (error) {
13539
+ logBatchInstrumentationError("could not write batch request span", error);
13540
+ }
13541
+ }
13542
+ if (endTime !== void 0) {
13543
+ if (trace.status && trace.status !== "completed") {
13544
+ task.log({ error: new Error(`OpenAI Batch ${trace.status}`) });
13545
+ }
13546
+ task.end({ endTime });
13547
+ }
13548
+ }
13549
+ var interceptOpenAIFilesCreateTraced = async (target, thisArg, args) => {
13550
+ const startTime = getCurrentUnixTimestamp();
13551
+ const inputPromise = readBatchInputs(args[0].inputFileContent);
13552
+ const parentPromise = exportParent(args[0].parent);
13553
+ const file = await Reflect.apply(target, thisArg, args);
13554
+ try {
13555
+ const inputFileId = read(file, "id");
13556
+ const [inputData, exportedParent2] = await Promise.all([
13557
+ inputPromise,
13558
+ parentPromise
13559
+ ]);
13560
+ if (typeof inputFileId !== "string" || !exportedParent2 || !inputData.endpoint || inputData.issues.length > 0) {
13561
+ if (inputData.issues[0]) {
13562
+ logBatchInstrumentationError(
13563
+ "skipped invalid input file",
13564
+ inputData.issues[0]
13565
+ );
13566
+ }
13567
+ return file;
13568
+ }
13569
+ const trace = {
13570
+ context: {
13571
+ inputFileId,
13572
+ endpoint: inputData.endpoint,
13573
+ parent: exportedParent2,
13574
+ taskStartTime: startTime,
13575
+ childStartTime: startTime
13576
+ },
13577
+ inputs: inputData.inputs
13578
+ };
13579
+ pendingBatchTraces.set(inputFileId, trace);
13580
+ await writePendingSpans(trace);
13581
+ } catch (error) {
13582
+ logBatchInstrumentationError("could not start batch spans", error);
13583
+ }
13584
+ return file;
13585
+ };
13586
+ function validTimestamp(value) {
13587
+ return typeof value === "number" && Number.isFinite(value) && value >= 0;
13588
+ }
13589
+ function terminalEndTime(batch, startTime) {
13590
+ let timestamp;
13591
+ switch (batch.status) {
13592
+ case "completed":
13593
+ timestamp = batch.completed_at;
13594
+ break;
13595
+ case "failed":
13596
+ timestamp = batch.failed_at;
13597
+ break;
13598
+ case "expired":
13599
+ timestamp = batch.expired_at;
13600
+ break;
13601
+ default:
13602
+ timestamp = batch.cancelled_at;
13603
+ }
13604
+ return validTimestamp(timestamp) ? Math.max(timestamp, startTime) : Math.max(getCurrentUnixTimestamp(), startTime);
13605
+ }
13606
+ async function updateBatchTimestamps(batch) {
13607
+ const trace = pendingBatchTraces.get(batch.input_file_id);
13608
+ if (!trace || trace.context.endpoint !== batch.endpoint) {
13609
+ return;
13610
+ }
13611
+ if (validTimestamp(batch.created_at)) {
13612
+ trace.context.taskStartTime = batch.created_at;
13613
+ }
13614
+ trace.context.childStartTime = validTimestamp(batch.in_progress_at) ? Math.max(batch.in_progress_at, trace.context.taskStartTime) : trace.context.taskStartTime;
13615
+ trace.status = batch.status;
13616
+ if (TERMINAL_STATUSES.has(batch.status)) {
13617
+ trace.endTime = terminalEndTime(batch, trace.context.childStartTime);
13618
+ }
13619
+ await writePendingSpans(trace, trace.endTime);
13620
+ }
13621
+ var interceptOpenAIBatchesRetrieveTraced = (target, thisArg, args) => Promise.resolve(Reflect.apply(target, thisArg, args)).then(async (batch) => {
13622
+ try {
13623
+ await updateBatchTimestamps(batch);
13624
+ } catch (error) {
13625
+ logBatchInstrumentationError("could not update batch timestamps", error);
13626
+ }
13627
+ return batch;
13628
+ });
13629
+ function errorFromResult(result) {
13630
+ const error = read(result.value, "error");
13631
+ if (isObject(error)) {
13632
+ const message = read(error, "message");
13633
+ return new Error(
13634
+ typeof message === "string" ? message : "OpenAI Batch request failed"
13635
+ );
13636
+ }
13637
+ const response = read(result.value, "response");
13638
+ const statusCode = read(response, "status_code");
13639
+ if (result.source === "error" || typeof statusCode === "number" && (statusCode < 200 || statusCode >= 300)) {
13640
+ const message = read(read(read(response, "body"), "error"), "message");
13641
+ return new Error(
13642
+ typeof message === "string" ? message : "OpenAI Batch request failed"
13643
+ );
13644
+ }
13645
+ return void 0;
13646
+ }
13647
+ async function completeBatchResult(context, taskParent, endTime, input, result) {
13648
+ const child = await startBatchChild(context, taskParent, input);
13649
+ try {
13650
+ const resultError = errorFromResult(result);
13651
+ const responseBody = read(read(result.value, "response"), "body");
13652
+ if (resultError) {
13653
+ child.log({ error: resultError });
13654
+ } else if (isObject(responseBody)) {
13655
+ const model = read(responseBody, "model");
13656
+ child.log({
13657
+ output: context.endpoint === "/v1/chat/completions" ? read(responseBody, "choices") : processImagesInOutput(read(responseBody, "output")),
13658
+ ...typeof model === "string" ? { metadata: { model } } : {},
13659
+ metrics: parseMetricsFromUsage(read(responseBody, "usage"))
13660
+ });
13661
+ } else {
13662
+ child.log({ error: new Error("OpenAI Batch response body is missing") });
13663
+ }
13664
+ } catch (error) {
13665
+ child.log({ error });
13666
+ } finally {
13667
+ child.end({ endTime });
13668
+ }
13669
+ }
13670
+ async function completeResultFile({
13671
+ context,
13672
+ endTime,
13673
+ file,
13674
+ inputs,
13675
+ issues,
13676
+ seen,
13677
+ source,
13678
+ taskParent
13679
+ }) {
13680
+ if (file === void 0) {
13681
+ return;
13682
+ }
13683
+ for await (const value of jsonlRecords(file, (issue) => issues.push(issue))) {
13684
+ const customId = read(value, "custom_id");
13685
+ if (!validCustomId(customId) || !isObject(value)) {
13686
+ issues.push(
13687
+ new Error("OpenAI Batch result is missing a valid custom_id")
13688
+ );
13689
+ continue;
13690
+ }
13691
+ if (seen.has(customId)) {
13692
+ issues.push(new Error("OpenAI Batch result contains a duplicate"));
13693
+ continue;
13694
+ }
13695
+ const input = inputs.get(customId);
13696
+ if (!input) {
13697
+ issues.push(
13698
+ new Error("OpenAI Batch result does not match an input record")
13699
+ );
13700
+ continue;
13701
+ }
13702
+ seen.add(customId);
13703
+ await completeBatchResult(context, taskParent, endTime, input, {
13704
+ value,
13705
+ source
13706
+ });
13707
+ }
13708
+ }
13709
+ function sameInputs(first, second) {
13710
+ return first.size === second.size && [...first.keys()].every((customId) => second.has(customId));
13711
+ }
13712
+ async function contextForCompletion(inputFileId, inputData) {
13713
+ if (!inputData.endpoint || inputData.issues.length > 0) {
13714
+ return void 0;
13715
+ }
13716
+ const existing = pendingBatchTraces.get(inputFileId);
13717
+ if (existing) {
13718
+ if (existing.context.endpoint !== inputData.endpoint || !sameInputs(existing.inputs, inputData.inputs)) {
13719
+ return void 0;
13720
+ }
13721
+ return { ...existing, inputs: inputData.inputs };
13722
+ }
13723
+ const parent = await exportParent(getSpanParentObject());
13724
+ if (!parent) {
13725
+ return void 0;
13726
+ }
13727
+ const startTime = getCurrentUnixTimestamp();
13728
+ return {
13729
+ context: {
13730
+ inputFileId,
13731
+ endpoint: inputData.endpoint,
13732
+ parent,
13733
+ taskStartTime: startTime,
13734
+ childStartTime: startTime
13735
+ },
13736
+ inputs: inputData.inputs
13737
+ };
13738
+ }
13739
+ async function completeBatch(args) {
13740
+ const inputData = await readBatchInputs(args.inputFileContent);
13741
+ const trace = await contextForCompletion(args.inputFileId, inputData);
13742
+ if (!trace) {
13743
+ logBatchInstrumentationError(
13744
+ "left batch spans pending",
13745
+ inputData.issues[0] ?? new Error("OpenAI Batch input does not match")
13746
+ );
13747
+ return;
13748
+ }
13749
+ const endTime = trace.endTime ?? getCurrentUnixTimestamp();
13750
+ const task = await startBatchSpan(trace.context);
13751
+ const taskParent = await task.export();
13752
+ const issues = [];
13753
+ const seen = /* @__PURE__ */ new Set();
13754
+ await Promise.all([
13755
+ completeResultFile({
13756
+ context: trace.context,
13757
+ endTime,
13758
+ file: args.outputFileContent,
13759
+ inputs: trace.inputs,
13760
+ issues,
13761
+ seen,
13762
+ source: "output",
13763
+ taskParent
13764
+ }),
13765
+ completeResultFile({
13766
+ context: trace.context,
13767
+ endTime,
13768
+ file: args.errorFileContent,
13769
+ inputs: trace.inputs,
13770
+ issues,
13771
+ seen,
13772
+ source: "error",
13773
+ taskParent
13774
+ })
13775
+ ]);
13776
+ if (issues.length > 0) {
13777
+ logBatchInstrumentationError("left batch spans pending", issues[0]);
13778
+ return;
13779
+ }
13780
+ const missing = [...trace.inputs.values()].filter(
13781
+ ({ customId }) => !seen.has(customId)
13782
+ );
13783
+ if (!trace.status || trace.status === "completed") {
13784
+ if (missing.length > 0) {
13785
+ logBatchInstrumentationError(
13786
+ "left batch spans pending",
13787
+ new Error("OpenAI Batch result files are incomplete")
13788
+ );
13789
+ return;
13790
+ }
13791
+ } else {
13792
+ for (const input of missing) {
13793
+ const child = await startBatchChild(trace.context, taskParent, input);
13794
+ child.log({ error: new Error(`OpenAI Batch ${trace.status}`) });
13795
+ child.end({ endTime });
13796
+ }
13797
+ task.log({ error: new Error(`OpenAI Batch ${trace.status}`) });
13798
+ }
13799
+ task.end({ endTime });
13800
+ pendingBatchTraces.delete(args.inputFileId);
13801
+ }
13802
+ var interceptOpenAIBatchTraceComplete = (target, thisArg, args) => Promise.resolve(Reflect.apply(target, thisArg, args)).then(async (result) => {
13803
+ try {
13804
+ await completeBatch(args[0]);
13805
+ } catch (error) {
13806
+ logBatchInstrumentationError("could not complete batch", error);
13807
+ }
13808
+ return result;
13809
+ });
13810
+
13157
13811
  // src/instrumentation/plugins/openai-plugin.ts
13158
13812
  var OpenAIPlugin = class extends BasePlugin {
13159
13813
  constructor() {
13160
13814
  super();
13161
13815
  }
13162
13816
  onEnable() {
13817
+ this.unsubscribers.push(
13818
+ openAIChannels.filesCreateTraced.intercept(
13819
+ interceptOpenAIFilesCreateTraced
13820
+ ),
13821
+ openAIChannels.batchesRetrieveTraced.intercept(
13822
+ interceptOpenAIBatchesRetrieveTraced
13823
+ ),
13824
+ openAIChannels.batchesCompleteTrace.intercept(
13825
+ interceptOpenAIBatchTraceComplete
13826
+ )
13827
+ );
13163
13828
  this.unsubscribers.push(
13164
13829
  traceStreamingChannel(openAIChannels.chatCompletionsCreate, {
13165
13830
  name: "Chat Completion",
13166
13831
  type: "llm" /* LLM */,
13167
- extractInput: ([params]) => {
13168
- const { messages, ...metadata } = params;
13169
- return {
13170
- input: processInputAttachments(messages),
13171
- metadata: { ...metadata, provider: "openai" }
13172
- };
13173
- },
13832
+ extractInput: ([params]) => extractOpenAIChatInput(params),
13174
13833
  extractOutput: (result) => {
13175
13834
  return result?.choices;
13176
13835
  },
@@ -13216,13 +13875,7 @@ var OpenAIPlugin = class extends BasePlugin {
13216
13875
  traceStreamingChannel(openAIChannels.betaChatCompletionsParse, {
13217
13876
  name: "Chat Completion",
13218
13877
  type: "llm" /* LLM */,
13219
- extractInput: ([params]) => {
13220
- const { messages, ...metadata } = params;
13221
- return {
13222
- input: processInputAttachments(messages),
13223
- metadata: { ...metadata, provider: "openai" }
13224
- };
13225
- },
13878
+ extractInput: ([params]) => extractOpenAIChatInput(params),
13226
13879
  extractOutput: (result) => {
13227
13880
  return result?.choices;
13228
13881
  },
@@ -13244,13 +13897,7 @@ var OpenAIPlugin = class extends BasePlugin {
13244
13897
  traceSyncStreamChannel(openAIChannels.betaChatCompletionsStream, {
13245
13898
  name: "Chat Completion",
13246
13899
  type: "llm" /* LLM */,
13247
- extractInput: ([params]) => {
13248
- const { messages, ...metadata } = params;
13249
- return {
13250
- input: processInputAttachments(messages),
13251
- metadata: { ...metadata, provider: "openai" }
13252
- };
13253
- }
13900
+ extractInput: ([params]) => extractOpenAIChatInput(params)
13254
13901
  })
13255
13902
  );
13256
13903
  this.unsubscribers.push(
@@ -13280,23 +13927,11 @@ var OpenAIPlugin = class extends BasePlugin {
13280
13927
  traceStreamingChannel(openAIChannels.responsesCreate, {
13281
13928
  name: "openai.responses.create",
13282
13929
  type: "llm" /* LLM */,
13283
- extractInput: ([params]) => {
13284
- const { input, ...metadata } = params;
13285
- return {
13286
- input: processInputAttachments(input),
13287
- metadata: { ...metadata, provider: "openai" }
13288
- };
13289
- },
13930
+ extractInput: ([params]) => extractOpenAIResponsesInput(params),
13290
13931
  extractOutput: (result) => {
13291
13932
  return processImagesInOutput(result?.output);
13292
13933
  },
13293
- extractMetadata: (result) => {
13294
- if (!result) {
13295
- return void 0;
13296
- }
13297
- const { output: _output, usage: _usage, ...metadata } = result;
13298
- return Object.keys(metadata).length > 0 ? metadata : void 0;
13299
- },
13934
+ extractMetadata: (result) => extractOpenAIResponsesMetadata(result),
13300
13935
  extractMetrics: (result, startTime, endEvent) => {
13301
13936
  const metrics = withCachedMetric(
13302
13937
  parseMetricsFromUsage(result?.usage),
@@ -13315,13 +13950,7 @@ var OpenAIPlugin = class extends BasePlugin {
13315
13950
  traceSyncStreamChannel(openAIChannels.responsesStream, {
13316
13951
  name: "openai.responses.create",
13317
13952
  type: "llm" /* LLM */,
13318
- extractInput: ([params]) => {
13319
- const { input, ...metadata } = params;
13320
- return {
13321
- input: processInputAttachments(input),
13322
- metadata: { ...metadata, provider: "openai" }
13323
- };
13324
- },
13953
+ extractInput: ([params]) => extractOpenAIResponsesInput(params),
13325
13954
  extractFromEvent: (event) => {
13326
13955
  if (event.type !== "response.completed" || !event.response) {
13327
13956
  return {};
@@ -13344,23 +13973,11 @@ var OpenAIPlugin = class extends BasePlugin {
13344
13973
  traceStreamingChannel(openAIChannels.responsesParse, {
13345
13974
  name: "openai.responses.parse",
13346
13975
  type: "llm" /* LLM */,
13347
- extractInput: ([params]) => {
13348
- const { input, ...metadata } = params;
13349
- return {
13350
- input: processInputAttachments(input),
13351
- metadata: { ...metadata, provider: "openai" }
13352
- };
13353
- },
13976
+ extractInput: ([params]) => extractOpenAIResponsesInput(params),
13354
13977
  extractOutput: (result) => {
13355
13978
  return processImagesInOutput(result?.output);
13356
13979
  },
13357
- extractMetadata: (result) => {
13358
- if (!result) {
13359
- return void 0;
13360
- }
13361
- const { output: _output, usage: _usage, ...metadata } = result;
13362
- return Object.keys(metadata).length > 0 ? metadata : void 0;
13363
- },
13980
+ extractMetadata: (result) => extractOpenAIResponsesMetadata(result),
13364
13981
  extractMetrics: (result, startTime, endEvent) => {
13365
13982
  const metrics = withCachedMetric(
13366
13983
  parseMetricsFromUsage(result?.usage),
@@ -13379,23 +13996,11 @@ var OpenAIPlugin = class extends BasePlugin {
13379
13996
  traceAsyncChannel(openAIChannels.responsesCompact, {
13380
13997
  name: "openai.responses.compact",
13381
13998
  type: "llm" /* LLM */,
13382
- extractInput: ([params]) => {
13383
- const { input, ...metadata } = params;
13384
- return {
13385
- input: processInputAttachments(input),
13386
- metadata: { ...metadata, provider: "openai" }
13387
- };
13388
- },
13999
+ extractInput: ([params]) => extractOpenAIResponsesInput(params),
13389
14000
  extractOutput: (result) => {
13390
14001
  return processImagesInOutput(result?.output);
13391
14002
  },
13392
- extractMetadata: (result) => {
13393
- if (!result) {
13394
- return void 0;
13395
- }
13396
- const { output: _output, usage: _usage, ...metadata } = result;
13397
- return Object.keys(metadata).length > 0 ? metadata : void 0;
13398
- },
14003
+ extractMetadata: (result) => extractOpenAIResponsesMetadata(result),
13399
14004
  extractMetrics: (result, startTime, endEvent) => {
13400
14005
  const metrics = withCachedMetric(
13401
14006
  parseMetricsFromUsage(result?.usage),
@@ -13451,35 +14056,6 @@ function withCachedMetric(metrics, result, endEvent) {
13451
14056
  cached
13452
14057
  };
13453
14058
  }
13454
- function processImagesInOutput(output) {
13455
- if (Array.isArray(output)) {
13456
- return output.map(processImagesInOutput);
13457
- }
13458
- if (isObject(output)) {
13459
- if (output.type === "image_generation_call" && output.result && typeof output.result === "string") {
13460
- const fileExtension = output.output_format || "png";
13461
- const contentType = `image/${fileExtension}`;
13462
- const baseFilename = output.revised_prompt && typeof output.revised_prompt === "string" ? output.revised_prompt.slice(0, 50).replace(/[^a-zA-Z0-9]/g, "_") : "generated_image";
13463
- const filename = `${baseFilename}.${fileExtension}`;
13464
- const binaryString = atob(output.result);
13465
- const bytes = new Uint8Array(binaryString.length);
13466
- for (let i = 0; i < binaryString.length; i++) {
13467
- bytes[i] = binaryString.charCodeAt(i);
13468
- }
13469
- const blob = new Blob([bytes], { type: contentType });
13470
- const attachment = new Attachment({
13471
- data: blob,
13472
- filename,
13473
- contentType
13474
- });
13475
- return {
13476
- ...output,
13477
- result: attachment
13478
- };
13479
- }
13480
- }
13481
- return output;
13482
- }
13483
14059
  function mergeLogprobTokens(existing, incoming) {
13484
14060
  if (incoming === void 0) {
13485
14061
  return existing;
@@ -18623,6 +19199,12 @@ function prepareAISDKChildTracing(params, self, parentSpan, denyOutputPaths, aiS
18623
19199
  if (!activeEntry) {
18624
19200
  return result;
18625
19201
  }
19202
+ const executionOptions = args[1];
19203
+ const toolCallId = isObject(executionOptions) ? executionOptions.toolCallId : void 0;
19204
+ const toolInput = {
19205
+ input: serializeToolExecutionInput(args),
19206
+ ...typeof toolCallId === "string" ? { metadata: { toolCallId } } : {}
19207
+ };
18626
19208
  if (isAsyncGenerator(result)) {
18627
19209
  return (async function* () {
18628
19210
  const span = activeEntry.parentSpan.startSpan(
@@ -18636,7 +19218,7 @@ function prepareAISDKChildTracing(params, self, parentSpan, denyOutputPaths, aiS
18636
19218
  INSTRUMENTATION_NAMES.AI_SDK
18637
19219
  )
18638
19220
  );
18639
- span.log({ input: serializeToolExecutionInput(args) });
19221
+ span.log(toolInput);
18640
19222
  try {
18641
19223
  let lastValue;
18642
19224
  for await (const value of result) {
@@ -18654,7 +19236,7 @@ function prepareAISDKChildTracing(params, self, parentSpan, denyOutputPaths, aiS
18654
19236
  }
18655
19237
  return activeEntry.parentSpan.traced(
18656
19238
  async (span) => {
18657
- span.log({ input: serializeToolExecutionInput(args) });
19239
+ span.log(toolInput);
18658
19240
  const awaitedResult = await result;
18659
19241
  span.log({ output: awaitedResult });
18660
19242
  return awaitedResult;
@@ -32776,14 +33358,17 @@ function getMetricsFromResponse(response) {
32776
33358
  if (!isRecord(usageMetadata)) {
32777
33359
  continue;
32778
33360
  }
32779
- const inputTokenDetails = usageMetadata.input_token_details;
33361
+ const inputTokenDetails = isRecord(usageMetadata.input_token_details) ? usageMetadata.input_token_details : {};
32780
33362
  const outputTokenDetails = usageMetadata.output_token_details;
32781
33363
  return normalizeTokenMetrics({
32782
33364
  total_tokens: usageMetadata.total_tokens,
32783
33365
  prompt_tokens: usageMetadata.input_tokens,
32784
33366
  completion_tokens: usageMetadata.output_tokens,
32785
- prompt_cache_creation_tokens: isRecord(inputTokenDetails) ? inputTokenDetails.cache_creation : void 0,
32786
- prompt_cached_tokens: isRecord(inputTokenDetails) ? inputTokenDetails.cache_read : void 0,
33367
+ // Prefer TTL-specific cache writes over the aggregate when available.
33368
+ prompt_cache_creation_tokens: inputTokenDetails.ephemeral_5m_input_tokens == null && inputTokenDetails.ephemeral_1h_input_tokens == null ? inputTokenDetails.cache_creation : void 0,
33369
+ prompt_cache_creation_5m_tokens: inputTokenDetails.ephemeral_5m_input_tokens,
33370
+ prompt_cache_creation_1h_tokens: inputTokenDetails.ephemeral_1h_input_tokens,
33371
+ prompt_cached_tokens: inputTokenDetails.cache_read,
32787
33372
  completion_reasoning_tokens: isRecord(outputTokenDetails) ? outputTokenDetails.reasoning : void 0
32788
33373
  });
32789
33374
  }
@@ -33499,7 +34084,7 @@ function extractSession(event) {
33499
34084
  return isObject(candidate) && typeof candidate.prompt === "function" ? candidate : void 0;
33500
34085
  }
33501
34086
  function isPiAgent(value) {
33502
- return isObject(value) && typeof value.streamFn === "function" && typeof value.subscribe === "function";
34087
+ return isObject(value) && (typeof value.streamFunction === "function" || typeof value.streamFn === "function") && typeof value.subscribe === "function";
33503
34088
  }
33504
34089
  function promptContextStore() {
33505
34090
  piPromptContextStore ??= isomorph_default.newAsyncLocalStorage();
@@ -33509,17 +34094,21 @@ function currentPiPromptState() {
33509
34094
  return promptContextStore().getStore();
33510
34095
  }
33511
34096
  function installPiAgentInstrumentation(agent) {
34097
+ const property = typeof agent.streamFunction === "function" ? "streamFunction" : "streamFn";
34098
+ const streamFunction = agent[property];
33512
34099
  const existing = piAgentPatchStates.get(agent);
33513
- if (!existing || agent.streamFn !== existing.wrappedStreamFn) {
34100
+ if (streamFunction && (!existing || existing.property !== property || streamFunction !== existing.wrappedStreamFunction)) {
33514
34101
  const patchState = {
33515
- originalStreamFn: agent.streamFn,
33516
- wrappedStreamFn: agent.streamFn
34102
+ originalStreamFunction: streamFunction,
34103
+ property,
34104
+ wrappedStreamFunction: streamFunction
33517
34105
  };
33518
- patchState.wrappedStreamFn = makeInstrumentedStreamFn(
34106
+ patchState.wrappedStreamFunction = makeInstrumentedStreamFunction(
33519
34107
  agent,
33520
- patchState.originalStreamFn
34108
+ patchState.originalStreamFunction,
34109
+ property
33521
34110
  );
33522
- agent.streamFn = patchState.wrappedStreamFn;
34111
+ agent[property] = patchState.wrappedStreamFunction;
33523
34112
  piAgentPatchStates.set(agent, patchState);
33524
34113
  }
33525
34114
  if (piAgentEventSubscriptions.has(agent)) {
@@ -33544,15 +34133,21 @@ function installPiAgentInstrumentation(agent) {
33544
34133
  logInstrumentationError4("Pi Coding Agent event subscription", error);
33545
34134
  }
33546
34135
  }
33547
- function makeInstrumentedStreamFn(agent, originalStreamFn) {
33548
- return async function instrumentedPiStreamFn(model, context, options) {
33549
- const invokeOriginal = () => Reflect.apply(originalStreamFn, this, [model, context, options]);
34136
+ function makeInstrumentedStreamFunction(agent, originalStreamFunction, property) {
34137
+ return async function instrumentedPiStreamFunction(model, context, options) {
34138
+ const invokeOriginal = () => Reflect.apply(originalStreamFunction, this, [model, context, options]);
33550
34139
  const state = currentPiPromptState();
33551
34140
  if (!state || state.agent !== agent || state.finalized) {
33552
34141
  return invokeOriginal();
33553
34142
  }
33554
34143
  wrapPiToolExecutors(context.tools);
33555
- const llmState = await startPiLlmSpan(state, model, context, options);
34144
+ const llmState = await startPiLlmSpan(
34145
+ state,
34146
+ model,
34147
+ context,
34148
+ property,
34149
+ options
34150
+ );
33556
34151
  try {
33557
34152
  const stream = await runWithAutoInstrumentationSuppressed(invokeOriginal);
33558
34153
  return patchAssistantMessageStream(stream, state, llmState);
@@ -33589,12 +34184,12 @@ function wrapPiToolExecutors(tools) {
33589
34184
  }
33590
34185
  }
33591
34186
  }
33592
- async function startPiLlmSpan(state, model, context, options) {
34187
+ async function startPiLlmSpan(state, model, context, property, options) {
33593
34188
  const metadata = {
33594
34189
  ...extractModelMetadata2(model),
33595
34190
  ...extractStreamOptionsMetadata(options),
33596
34191
  ...extractToolMetadata(context.tools),
33597
- "pi_coding_agent.operation": "agent.streamFn"
34192
+ "pi_coding_agent.operation": `agent.${property}`
33598
34193
  };
33599
34194
  const span = startSpan(
33600
34195
  withSpanInstrumentationName(
@@ -38787,19 +39382,29 @@ function buildSpanScores(results) {
38787
39382
  function _internalPrepareEvaluatorScore(scoreValue, name) {
38788
39383
  if (scoreValue === null) return { results: null };
38789
39384
  if (Array.isArray(scoreValue)) {
39385
+ const names = /* @__PURE__ */ new Set();
38790
39386
  for (const score of scoreValue) {
38791
39387
  if (!(typeof score === "object" && !isEmpty2(score))) {
38792
39388
  throw new Error(
38793
39389
  `When returning an array of scores, each score must be a non-empty object. Got: ${JSON.stringify(score)}`
38794
39390
  );
38795
39391
  }
39392
+ if (typeof score.name !== "string") {
39393
+ throw new Error(
39394
+ `When returning an array of scores, each score must have a name. Got: ${JSON.stringify(score)}`
39395
+ );
39396
+ }
39397
+ if (names.has(score.name)) {
39398
+ throw new Error(`Duplicate score name '${score.name}' in score array`);
39399
+ }
39400
+ names.add(score.name);
38796
39401
  }
38797
39402
  }
38798
39403
  let results;
38799
39404
  if (Array.isArray(scoreValue)) {
38800
39405
  results = scoreValue;
38801
39406
  } else if (typeof scoreValue === "object" && !isEmpty2(scoreValue)) {
38802
- results = [scoreValue];
39407
+ results = [{ ...scoreValue, name: scoreValue.name ?? name }];
38803
39408
  } else {
38804
39409
  results = [{ name, score: scoreValue }];
38805
39410
  }