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
@@ -5972,7 +5972,7 @@ var INSTRUMENTATION_NAMES = {
5972
5972
  var INTERNAL_SPAN_INSTRUMENTATION_NAME = /* @__PURE__ */ Symbol.for(
5973
5973
  "braintrust.spanInstrumentationName"
5974
5974
  );
5975
- var SDK_VERSION = true ? "3.29.0" : "0.0.0";
5975
+ var SDK_VERSION = true ? "3.30.0" : "0.0.0";
5976
5976
  function withSpanInstrumentationName(args, instrumentationName) {
5977
5977
  return {
5978
5978
  ...args,
@@ -9487,6 +9487,15 @@ function _internalStartSpanWithInitialMerge(args) {
9487
9487
  [INITIAL_SPAN_WRITE_AS_MERGE]: true
9488
9488
  }).span;
9489
9489
  }
9490
+ function _internalStartSpanWithInitialMergeAndParentSpanIds(args) {
9491
+ return startSpanAndIsLogger(
9492
+ {
9493
+ ...args,
9494
+ [INITIAL_SPAN_WRITE_AS_MERGE]: true
9495
+ },
9496
+ { useParentSpanIdsForObjectParent: true }
9497
+ ).span;
9498
+ }
9490
9499
  function _internalStartSpanWithContext(args, context) {
9491
9500
  return startSpanAndIsLogger({
9492
9501
  ...args,
@@ -9497,7 +9506,7 @@ async function flush(options) {
9497
9506
  const state = options?.state ?? _globalState;
9498
9507
  return await state.bgLogger().flush();
9499
9508
  }
9500
- function startSpanAndIsLogger(args) {
9509
+ function startSpanAndIsLogger(args, internalOptions) {
9501
9510
  const state = args?.state ?? _globalState;
9502
9511
  const { parentObject, propagatedState } = getSpanParentObjectAndPropagatedState({
9503
9512
  asyncFlush: args?.asyncFlush,
@@ -9511,7 +9520,7 @@ function startSpanAndIsLogger(args) {
9511
9520
  ) ? {
9512
9521
  spanId: parentObject.data.span_id,
9513
9522
  rootSpanId: parentObject.data.root_span_id
9514
- } : void 0;
9523
+ } : internalOptions?.useParentSpanIdsForObjectParent ? args?.parentSpanIds : void 0;
9515
9524
  const { parent: _ignoredParent, ...spanArgs } = args ?? {};
9516
9525
  const span = new SpanImpl({
9517
9526
  state,
@@ -12811,6 +12820,131 @@ function unsubscribeAll(unsubscribers) {
12811
12820
  return [];
12812
12821
  }
12813
12822
 
12823
+ // src/instrumentation/core/channel-definitions.ts
12824
+ function channel(spec) {
12825
+ return spec;
12826
+ }
12827
+ function defineChannels(pkg, channels, options) {
12828
+ const { instrumentationName } = options;
12829
+ return Object.fromEntries(
12830
+ Object.entries(channels).map(([key, spec]) => {
12831
+ const fullChannelName = `orchestrion:${pkg}:${spec.channelName}`;
12832
+ if (spec.kind === "async") {
12833
+ const asyncSpec = spec;
12834
+ const tracingChannel2 = () => isomorph_default.newTracingChannel(
12835
+ fullChannelName
12836
+ );
12837
+ const intercept2 = (interceptor) => {
12838
+ const hook = tracingChannel2();
12839
+ return typeof hook.intercept === "function" ? hook.intercept(interceptor) : () => {
12840
+ };
12841
+ };
12842
+ return [
12843
+ key,
12844
+ {
12845
+ ...asyncSpec,
12846
+ instrumentationName,
12847
+ intercept: intercept2,
12848
+ invoke: (target, thisArg, args, additional) => {
12849
+ const hook = tracingChannel2();
12850
+ return typeof hook.invoke === "function" ? hook.invoke(target, thisArg, args, additional) : Reflect.apply(target, thisArg, args);
12851
+ },
12852
+ tracingChannel: tracingChannel2,
12853
+ tracePromise: (fn, context) => tracingChannel2().tracePromise(
12854
+ fn,
12855
+ // eslint-disable-next-line @typescript-eslint/consistent-type-assertions
12856
+ context
12857
+ )
12858
+ }
12859
+ ];
12860
+ }
12861
+ const syncSpec = spec;
12862
+ const tracingChannel = () => isomorph_default.newTracingChannel(
12863
+ fullChannelName
12864
+ );
12865
+ const intercept = (interceptor) => {
12866
+ const hook = tracingChannel();
12867
+ return typeof hook.intercept === "function" ? hook.intercept(interceptor) : () => {
12868
+ };
12869
+ };
12870
+ return [
12871
+ key,
12872
+ {
12873
+ ...syncSpec,
12874
+ instrumentationName,
12875
+ intercept,
12876
+ invoke: (target, thisArg, args, additional) => {
12877
+ const hook = tracingChannel();
12878
+ return typeof hook.invoke === "function" ? hook.invoke(target, thisArg, args, additional) : Reflect.apply(target, thisArg, args);
12879
+ },
12880
+ tracingChannel,
12881
+ traceSync: (fn, context) => tracingChannel().traceSync(
12882
+ fn,
12883
+ // eslint-disable-next-line @typescript-eslint/consistent-type-assertions
12884
+ context
12885
+ )
12886
+ }
12887
+ ];
12888
+ })
12889
+ );
12890
+ }
12891
+
12892
+ // src/instrumentation/plugins/openai-channels.ts
12893
+ var openAIChannels = defineChannels(
12894
+ "openai",
12895
+ {
12896
+ filesCreateTraced: channel({
12897
+ channelName: "files.create-traced",
12898
+ kind: "async"
12899
+ }),
12900
+ batchesRetrieveTraced: channel({
12901
+ channelName: "batches.retrieve-traced",
12902
+ kind: "async"
12903
+ }),
12904
+ batchesCompleteTrace: channel({
12905
+ channelName: "batches.complete-trace",
12906
+ kind: "async"
12907
+ }),
12908
+ chatCompletionsCreate: channel({
12909
+ channelName: "chat.completions.create",
12910
+ kind: "async"
12911
+ }),
12912
+ embeddingsCreate: channel({
12913
+ channelName: "embeddings.create",
12914
+ kind: "async"
12915
+ }),
12916
+ betaChatCompletionsParse: channel({
12917
+ channelName: "beta.chat.completions.parse",
12918
+ kind: "async"
12919
+ }),
12920
+ betaChatCompletionsStream: channel({
12921
+ channelName: "beta.chat.completions.stream",
12922
+ kind: "sync-stream"
12923
+ }),
12924
+ moderationsCreate: channel({
12925
+ channelName: "moderations.create",
12926
+ kind: "async"
12927
+ }),
12928
+ responsesCreate: channel({
12929
+ channelName: "responses.create",
12930
+ kind: "async"
12931
+ }),
12932
+ responsesStream: channel({
12933
+ channelName: "responses.stream",
12934
+ kind: "sync-stream"
12935
+ }),
12936
+ responsesParse: channel({
12937
+ channelName: "responses.parse",
12938
+ kind: "async"
12939
+ }),
12940
+ responsesCompact: channel({
12941
+ channelName: "responses.compact",
12942
+ kind: "async"
12943
+ })
12944
+ },
12945
+ { instrumentationName: INSTRUMENTATION_NAMES.OPENAI }
12946
+ );
12947
+
12814
12948
  // src/wrappers/attachment-utils.ts
12815
12949
  function getExtensionFromMediaType(mediaType) {
12816
12950
  const extensionMap = {
@@ -12986,173 +13120,691 @@ function processInputAttachments(input) {
12986
13120
  return processNode(input);
12987
13121
  }
12988
13122
 
12989
- // src/instrumentation/core/channel-definitions.ts
12990
- function channel(spec) {
12991
- return spec;
13123
+ // src/instrumentation/plugins/openai-span-data.ts
13124
+ var OPENAI_METADATA_KEYS = [
13125
+ "model",
13126
+ "temperature",
13127
+ "top_p",
13128
+ "max_tokens",
13129
+ "frequency_penalty",
13130
+ "presence_penalty",
13131
+ "stop",
13132
+ "response_format",
13133
+ "tools",
13134
+ "tool_choice",
13135
+ "parallel_tool_calls",
13136
+ "max_tool_calls"
13137
+ ];
13138
+ function batchMetadata(params) {
13139
+ const metadata = { provider: "openai" };
13140
+ for (const key of OPENAI_METADATA_KEYS) {
13141
+ try {
13142
+ const value = Reflect.get(params, key);
13143
+ if (value !== void 0) {
13144
+ metadata[key] = value;
13145
+ }
13146
+ } catch {
13147
+ }
13148
+ }
13149
+ return metadata;
12992
13150
  }
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
- ];
13151
+ function extractOpenAIBatchInput(endpoint, params) {
13152
+ const input = endpoint === "/v1/chat/completions" ? params.messages : params.input;
13153
+ return {
13154
+ input: processInputAttachments(input),
13155
+ metadata: batchMetadata(params)
13156
+ };
13157
+ }
13158
+ function extractOpenAIChatInput(params) {
13159
+ const { messages, ...metadata } = params;
13160
+ return {
13161
+ input: processInputAttachments(messages),
13162
+ metadata: { ...metadata, provider: "openai" }
13163
+ };
13164
+ }
13165
+ function extractOpenAIResponsesInput(params) {
13166
+ const { input, ...metadata } = params;
13167
+ return {
13168
+ input: processInputAttachments(input),
13169
+ metadata: { ...metadata, provider: "openai" }
13170
+ };
13171
+ }
13172
+ function extractOpenAIResponsesMetadata(result) {
13173
+ if (!result) {
13174
+ return void 0;
13175
+ }
13176
+ const { output: _output, usage: _usage, ...metadata } = result;
13177
+ return Object.keys(metadata).length > 0 ? metadata : void 0;
13178
+ }
13179
+ function processImagesInOutput(output) {
13180
+ if (Array.isArray(output)) {
13181
+ return output.map(processImagesInOutput);
13182
+ }
13183
+ if (isObject(output) && output.type === "image_generation_call" && typeof output.result === "string" && output.result) {
13184
+ const fileExtension = output.output_format || "png";
13185
+ const contentType = `image/${fileExtension}`;
13186
+ const baseFilename = typeof output.revised_prompt === "string" ? output.revised_prompt.slice(0, 50).replace(/[^a-zA-Z0-9]/g, "_") : "generated_image";
13187
+ let binaryString;
13188
+ try {
13189
+ binaryString = atob(output.result);
13190
+ } catch {
13191
+ return output;
13192
+ }
13193
+ const bytes = new Uint8Array(binaryString.length);
13194
+ for (let i = 0; i < binaryString.length; i++) {
13195
+ bytes[i] = binaryString.charCodeAt(i);
13196
+ }
13197
+ return {
13198
+ ...output,
13199
+ result: new Attachment({
13200
+ data: new Blob([bytes], { type: contentType }),
13201
+ filename: `${baseFilename}.${fileExtension}`,
13202
+ contentType
13203
+ })
13204
+ };
13205
+ }
13206
+ return output;
13207
+ }
13208
+
13209
+ // src/openai-utils.ts
13210
+ var BRAINTRUST_CACHED_STREAM_METRIC = "__braintrust_cached_metric";
13211
+ var LEGACY_CACHED_HEADER = "x-cached";
13212
+ var X_CACHED_HEADER = "x-bt-cached";
13213
+ var TOKEN_NAME_MAP = {
13214
+ input_tokens: "prompt_tokens",
13215
+ output_tokens: "completion_tokens",
13216
+ total_tokens: "tokens"
13217
+ };
13218
+ var TOKEN_PREFIX_MAP = {
13219
+ input: "prompt",
13220
+ output: "completion"
13221
+ };
13222
+ function parseMetricsFromUsage(usage) {
13223
+ if (!usage) {
13224
+ return {};
13225
+ }
13226
+ const metrics = {};
13227
+ for (const [oaiName, value] of Object.entries(usage)) {
13228
+ if (typeof value === "number") {
13229
+ const metricName = TOKEN_NAME_MAP[oaiName] || oaiName;
13230
+ metrics[metricName] = value;
13231
+ continue;
13232
+ }
13233
+ if (!oaiName.endsWith("_tokens_details") || !isObject(value)) {
13234
+ continue;
13235
+ }
13236
+ const rawPrefix = oaiName.slice(0, -"_tokens_details".length);
13237
+ const prefix = TOKEN_PREFIX_MAP[rawPrefix] || rawPrefix;
13238
+ for (const [key, nestedValue] of Object.entries(value)) {
13239
+ if (typeof nestedValue !== "number") {
13240
+ continue;
13026
13241
  }
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,
13242
+ metrics[`${prefix}_${key}`] = nestedValue;
13243
+ }
13244
+ }
13245
+ return metrics;
13246
+ }
13247
+ function parseCachedHeader(value) {
13248
+ if (!value) {
13249
+ return void 0;
13250
+ }
13251
+ return ["true", "hit"].includes(value.toLowerCase()) ? 1 : 0;
13252
+ }
13253
+ function getCachedMetricFromHeaders(headers) {
13254
+ if (!headers || typeof headers.get !== "function") {
13255
+ return void 0;
13256
+ }
13257
+ const cachedHeader = headers.get(X_CACHED_HEADER);
13258
+ if (cachedHeader) {
13259
+ return parseCachedHeader(cachedHeader);
13260
+ }
13261
+ return parseCachedHeader(headers.get(LEGACY_CACHED_HEADER));
13262
+ }
13263
+
13264
+ // src/instrumentation/plugins/openai-batch-instrumentation.ts
13265
+ var SUPPORTED_ENDPOINTS = /* @__PURE__ */ new Set(["/v1/chat/completions", "/v1/responses"]);
13266
+ var TERMINAL_STATUSES = /* @__PURE__ */ new Set([
13267
+ "completed",
13268
+ "failed",
13269
+ "expired",
13270
+ "cancelled"
13271
+ ]);
13272
+ var pendingBatchTraces = /* @__PURE__ */ new Map();
13273
+ function read(value, key) {
13274
+ if (!isObject(value)) {
13275
+ return void 0;
13276
+ }
13277
+ try {
13278
+ return Reflect.get(value, key);
13279
+ } catch {
13280
+ return void 0;
13281
+ }
13282
+ }
13283
+ function isBatchRecordIterable(value) {
13284
+ if (value === null || typeof value !== "object" && typeof value !== "function") {
13285
+ return false;
13286
+ }
13287
+ return typeof read(value, Symbol.iterator) === "function" || typeof read(value, Symbol.asyncIterator) === "function";
13288
+ }
13289
+ function validCustomId(value) {
13290
+ return typeof value === "string" && value.length > 0 && value.length <= 64;
13291
+ }
13292
+ function logBatchInstrumentationError(context, error) {
13293
+ debugLogger.debug(`OpenAI Batch instrumentation ${context}:`, error);
13294
+ }
13295
+ async function exportParent(parent) {
13296
+ if ("toStr" in parent && typeof parent.toStr === "function") {
13297
+ return parent.toStr();
13298
+ }
13299
+ if ("export" in parent && typeof parent.export === "function") {
13300
+ return await parent.export();
13301
+ }
13302
+ return void 0;
13303
+ }
13304
+ async function deterministicDigest(namespace, ...parts) {
13305
+ const encoded = new TextEncoder().encode(
13306
+ [namespace, ...parts].map((part) => `${part.length}:${part}`).join("\0")
13307
+ );
13308
+ return new Uint8Array(
13309
+ await globalThis.crypto.subtle.digest("SHA-256", encoded)
13310
+ );
13311
+ }
13312
+ function digestHex(bytes, length) {
13313
+ return Array.from(bytes.slice(0, length)).map((byte) => byte.toString(16).padStart(2, "0")).join("");
13314
+ }
13315
+ function digestUuid(bytes) {
13316
+ const hex = digestHex(bytes, 16);
13317
+ return `${hex.slice(0, 8)}-${hex.slice(8, 12)}-${hex.slice(12, 16)}-${hex.slice(16, 20)}-${hex.slice(20)}`;
13318
+ }
13319
+ async function batchSpanIds(inputFileId) {
13320
+ const [row, span, root] = await Promise.all([
13321
+ deterministicDigest("openai:batch:row", inputFileId),
13322
+ deterministicDigest("openai:batch:span", inputFileId),
13323
+ deterministicDigest("openai:batch:root", inputFileId)
13324
+ ]);
13325
+ return {
13326
+ rowId: digestUuid(row),
13327
+ spanId: digestHex(span, 8),
13328
+ rootSpanId: digestHex(root, 16)
13329
+ };
13330
+ }
13331
+ async function childSpanIds(inputFileId, customId) {
13332
+ const [row, span] = await Promise.all([
13333
+ deterministicDigest("openai:batch:child:row", inputFileId, customId),
13334
+ deterministicDigest("openai:batch:child:span", inputFileId, customId)
13335
+ ]);
13336
+ return { rowId: digestUuid(row), spanId: digestHex(span, 8) };
13337
+ }
13338
+ async function startBatchSpan(context) {
13339
+ const ids = await batchSpanIds(context.inputFileId);
13340
+ const parent = SpanComponentsV4.fromStr(context.parent);
13341
+ const hasParentSpan = Boolean(
13342
+ parent.data.row_id && parent.data.span_id && parent.data.root_span_id
13343
+ );
13344
+ return withCurrent(
13345
+ NOOP_SPAN,
13346
+ () => _internalStartSpanWithInitialMergeAndParentSpanIds(
13347
+ withSpanInstrumentationName(
13038
13348
  {
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
- })
13349
+ name: "openai.batch",
13350
+ type: "task" /* TASK */,
13351
+ parent: context.parent,
13352
+ ...!hasParentSpan ? {
13353
+ parentSpanIds: {
13354
+ parentSpanIds: [],
13355
+ rootSpanId: ids.rootSpanId
13356
+ }
13357
+ } : {},
13358
+ spanId: ids.spanId,
13359
+ startTime: context.taskStartTime,
13360
+ event: {
13361
+ id: ids.rowId,
13362
+ metadata: {
13363
+ endpoint: context.endpoint,
13364
+ input_file_id: context.inputFileId,
13365
+ provider: "openai"
13366
+ }
13367
+ }
13368
+ },
13369
+ INSTRUMENTATION_NAMES.OPENAI
13370
+ )
13371
+ )
13055
13372
  );
13056
13373
  }
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
-
13102
- // src/openai-utils.ts
13103
- var BRAINTRUST_CACHED_STREAM_METRIC = "__braintrust_cached_metric";
13104
- var LEGACY_CACHED_HEADER = "x-cached";
13105
- var X_CACHED_HEADER = "x-bt-cached";
13106
- var TOKEN_NAME_MAP = {
13107
- input_tokens: "prompt_tokens",
13108
- output_tokens: "completion_tokens",
13109
- total_tokens: "tokens"
13110
- };
13111
- var TOKEN_PREFIX_MAP = {
13112
- input: "prompt",
13113
- output: "completion"
13374
+ async function startBatchChild(context, taskParent, input) {
13375
+ const ids = await childSpanIds(context.inputFileId, input.customId);
13376
+ return withCurrent(
13377
+ NOOP_SPAN,
13378
+ () => _internalStartSpanWithInitialMerge(
13379
+ withSpanInstrumentationName(
13380
+ {
13381
+ name: context.endpoint === "/v1/chat/completions" ? "Chat Completion" : "openai.responses.create",
13382
+ type: "llm" /* LLM */,
13383
+ parent: taskParent,
13384
+ spanId: ids.spanId,
13385
+ startTime: context.childStartTime,
13386
+ event: {
13387
+ id: ids.rowId,
13388
+ ...input.spanData?.input !== void 0 ? { input: input.spanData.input } : {},
13389
+ metadata: {
13390
+ ...input.spanData?.metadata,
13391
+ custom_id: input.customId,
13392
+ provider: "openai"
13393
+ }
13394
+ }
13395
+ },
13396
+ INSTRUMENTATION_NAMES.OPENAI
13397
+ )
13398
+ )
13399
+ );
13400
+ }
13401
+ async function* jsonlRecords(file, onIssue = () => {
13402
+ }) {
13403
+ const resolvedFile = await file;
13404
+ if (typeof resolvedFile === "string") {
13405
+ for (const line of resolvedFile.split("\n")) {
13406
+ if (!line.trim()) {
13407
+ continue;
13408
+ }
13409
+ try {
13410
+ yield JSON.parse(line.replace(/\r$/, ""));
13411
+ } catch (error) {
13412
+ logBatchInstrumentationError("skipped malformed JSONL", error);
13413
+ onIssue(new Error("OpenAI Batch file contains malformed JSONL"));
13414
+ }
13415
+ }
13416
+ return;
13417
+ }
13418
+ const body = read(resolvedFile, "body");
13419
+ const getReader = read(body, "getReader");
13420
+ if (isObject(body) && typeof getReader === "function") {
13421
+ let reader;
13422
+ try {
13423
+ reader = Reflect.apply(getReader, body, []);
13424
+ const decoder = new TextDecoder();
13425
+ let pending = "";
13426
+ while (true) {
13427
+ const readChunk = read(reader, "read");
13428
+ if (typeof readChunk !== "function") {
13429
+ throw new Error("Response body stream has no read method");
13430
+ }
13431
+ const chunk = await Reflect.apply(readChunk, reader, []);
13432
+ if (!isObject(chunk)) {
13433
+ throw new Error("Response body stream returned an invalid chunk");
13434
+ }
13435
+ if (chunk.done === true) {
13436
+ pending += decoder.decode();
13437
+ break;
13438
+ }
13439
+ if (!(chunk.value instanceof Uint8Array)) {
13440
+ throw new Error("Response body stream returned a non-byte chunk");
13441
+ }
13442
+ pending += decoder.decode(chunk.value, { stream: true });
13443
+ let newline = pending.indexOf("\n");
13444
+ while (newline !== -1) {
13445
+ const line = pending.slice(0, newline).replace(/\r$/, "");
13446
+ pending = pending.slice(newline + 1);
13447
+ if (line.trim()) {
13448
+ try {
13449
+ yield JSON.parse(line);
13450
+ } catch (error) {
13451
+ logBatchInstrumentationError("skipped malformed JSONL", error);
13452
+ onIssue(new Error("OpenAI Batch file contains malformed JSONL"));
13453
+ }
13454
+ }
13455
+ newline = pending.indexOf("\n");
13456
+ }
13457
+ }
13458
+ if (pending.trim()) {
13459
+ try {
13460
+ yield JSON.parse(pending.replace(/\r$/, ""));
13461
+ } catch (error) {
13462
+ logBatchInstrumentationError("skipped malformed JSONL", error);
13463
+ onIssue(new Error("OpenAI Batch file contains malformed JSONL"));
13464
+ }
13465
+ }
13466
+ } catch (error) {
13467
+ logBatchInstrumentationError("could not read JSONL response body", error);
13468
+ onIssue(new Error("OpenAI Batch response body could not be read"));
13469
+ } finally {
13470
+ const releaseLock = read(reader, "releaseLock");
13471
+ if (typeof releaseLock === "function") {
13472
+ try {
13473
+ Reflect.apply(releaseLock, reader, []);
13474
+ } catch (error) {
13475
+ logBatchInstrumentationError(
13476
+ "could not release stream reader",
13477
+ error
13478
+ );
13479
+ }
13480
+ }
13481
+ }
13482
+ return;
13483
+ }
13484
+ if (isBatchRecordIterable(resolvedFile)) {
13485
+ for await (const record of resolvedFile) {
13486
+ yield record;
13487
+ }
13488
+ return;
13489
+ }
13490
+ logBatchInstrumentationError("skipped invalid JSONL source", resolvedFile);
13491
+ onIssue(new Error("OpenAI Batch file source is invalid"));
13492
+ }
13493
+ async function readBatchInputs(file) {
13494
+ const inputs = /* @__PURE__ */ new Map();
13495
+ const issues = [];
13496
+ let endpoint;
13497
+ try {
13498
+ for await (const value of jsonlRecords(
13499
+ file,
13500
+ (issue) => issues.push(issue)
13501
+ )) {
13502
+ const customId = read(value, "custom_id");
13503
+ const url = read(value, "url");
13504
+ const body = read(value, "body");
13505
+ if (!validCustomId(customId) || inputs.has(customId) || read(value, "method") !== "POST" || typeof url !== "string" || !SUPPORTED_ENDPOINTS.has(url) || endpoint !== void 0 && endpoint !== url || !isObject(body)) {
13506
+ issues.push(new Error("OpenAI Batch input contains an invalid record"));
13507
+ continue;
13508
+ }
13509
+ endpoint = url;
13510
+ let spanData;
13511
+ try {
13512
+ spanData = extractOpenAIBatchInput(url, body);
13513
+ } catch (error) {
13514
+ logBatchInstrumentationError("could not extract batch input", error);
13515
+ }
13516
+ inputs.set(customId, { customId, spanData });
13517
+ }
13518
+ } catch (error) {
13519
+ logBatchInstrumentationError("could not process input file", error);
13520
+ issues.push(new Error("OpenAI Batch input file could not be processed"));
13521
+ }
13522
+ if (!endpoint || inputs.size === 0) {
13523
+ issues.push(new Error("OpenAI Batch input contains no supported records"));
13524
+ }
13525
+ return { endpoint, inputs, issues };
13526
+ }
13527
+ async function writePendingSpans(trace, endTime) {
13528
+ const task = await startBatchSpan(trace.context);
13529
+ const taskParent = await task.export();
13530
+ for (const input of trace.inputs.values()) {
13531
+ try {
13532
+ const child = await startBatchChild(trace.context, taskParent, input);
13533
+ if (endTime !== void 0) {
13534
+ child.end({ endTime });
13535
+ }
13536
+ } catch (error) {
13537
+ logBatchInstrumentationError("could not write batch request span", error);
13538
+ }
13539
+ }
13540
+ if (endTime !== void 0) {
13541
+ if (trace.status && trace.status !== "completed") {
13542
+ task.log({ error: new Error(`OpenAI Batch ${trace.status}`) });
13543
+ }
13544
+ task.end({ endTime });
13545
+ }
13546
+ }
13547
+ var interceptOpenAIFilesCreateTraced = async (target, thisArg, args) => {
13548
+ const startTime = getCurrentUnixTimestamp();
13549
+ const inputPromise = readBatchInputs(args[0].inputFileContent);
13550
+ const parentPromise = exportParent(args[0].parent);
13551
+ const file = await Reflect.apply(target, thisArg, args);
13552
+ try {
13553
+ const inputFileId = read(file, "id");
13554
+ const [inputData, exportedParent2] = await Promise.all([
13555
+ inputPromise,
13556
+ parentPromise
13557
+ ]);
13558
+ if (typeof inputFileId !== "string" || !exportedParent2 || !inputData.endpoint || inputData.issues.length > 0) {
13559
+ if (inputData.issues[0]) {
13560
+ logBatchInstrumentationError(
13561
+ "skipped invalid input file",
13562
+ inputData.issues[0]
13563
+ );
13564
+ }
13565
+ return file;
13566
+ }
13567
+ const trace = {
13568
+ context: {
13569
+ inputFileId,
13570
+ endpoint: inputData.endpoint,
13571
+ parent: exportedParent2,
13572
+ taskStartTime: startTime,
13573
+ childStartTime: startTime
13574
+ },
13575
+ inputs: inputData.inputs
13576
+ };
13577
+ pendingBatchTraces.set(inputFileId, trace);
13578
+ await writePendingSpans(trace);
13579
+ } catch (error) {
13580
+ logBatchInstrumentationError("could not start batch spans", error);
13581
+ }
13582
+ return file;
13114
13583
  };
13115
- function parseMetricsFromUsage(usage) {
13116
- if (!usage) {
13117
- return {};
13584
+ function validTimestamp(value) {
13585
+ return typeof value === "number" && Number.isFinite(value) && value >= 0;
13586
+ }
13587
+ function terminalEndTime(batch, startTime) {
13588
+ let timestamp;
13589
+ switch (batch.status) {
13590
+ case "completed":
13591
+ timestamp = batch.completed_at;
13592
+ break;
13593
+ case "failed":
13594
+ timestamp = batch.failed_at;
13595
+ break;
13596
+ case "expired":
13597
+ timestamp = batch.expired_at;
13598
+ break;
13599
+ default:
13600
+ timestamp = batch.cancelled_at;
13118
13601
  }
13119
- const metrics = {};
13120
- for (const [oaiName, value] of Object.entries(usage)) {
13121
- if (typeof value === "number") {
13122
- const metricName = TOKEN_NAME_MAP[oaiName] || oaiName;
13123
- metrics[metricName] = value;
13602
+ return validTimestamp(timestamp) ? Math.max(timestamp, startTime) : Math.max(getCurrentUnixTimestamp(), startTime);
13603
+ }
13604
+ async function updateBatchTimestamps(batch) {
13605
+ const trace = pendingBatchTraces.get(batch.input_file_id);
13606
+ if (!trace || trace.context.endpoint !== batch.endpoint) {
13607
+ return;
13608
+ }
13609
+ if (validTimestamp(batch.created_at)) {
13610
+ trace.context.taskStartTime = batch.created_at;
13611
+ }
13612
+ trace.context.childStartTime = validTimestamp(batch.in_progress_at) ? Math.max(batch.in_progress_at, trace.context.taskStartTime) : trace.context.taskStartTime;
13613
+ trace.status = batch.status;
13614
+ if (TERMINAL_STATUSES.has(batch.status)) {
13615
+ trace.endTime = terminalEndTime(batch, trace.context.childStartTime);
13616
+ }
13617
+ await writePendingSpans(trace, trace.endTime);
13618
+ }
13619
+ var interceptOpenAIBatchesRetrieveTraced = (target, thisArg, args) => Promise.resolve(Reflect.apply(target, thisArg, args)).then(async (batch) => {
13620
+ try {
13621
+ await updateBatchTimestamps(batch);
13622
+ } catch (error) {
13623
+ logBatchInstrumentationError("could not update batch timestamps", error);
13624
+ }
13625
+ return batch;
13626
+ });
13627
+ function errorFromResult(result) {
13628
+ const error = read(result.value, "error");
13629
+ if (isObject(error)) {
13630
+ const message = read(error, "message");
13631
+ return new Error(
13632
+ typeof message === "string" ? message : "OpenAI Batch request failed"
13633
+ );
13634
+ }
13635
+ const response = read(result.value, "response");
13636
+ const statusCode = read(response, "status_code");
13637
+ if (result.source === "error" || typeof statusCode === "number" && (statusCode < 200 || statusCode >= 300)) {
13638
+ const message = read(read(read(response, "body"), "error"), "message");
13639
+ return new Error(
13640
+ typeof message === "string" ? message : "OpenAI Batch request failed"
13641
+ );
13642
+ }
13643
+ return void 0;
13644
+ }
13645
+ async function completeBatchResult(context, taskParent, endTime, input, result) {
13646
+ const child = await startBatchChild(context, taskParent, input);
13647
+ try {
13648
+ const resultError = errorFromResult(result);
13649
+ const responseBody = read(read(result.value, "response"), "body");
13650
+ if (resultError) {
13651
+ child.log({ error: resultError });
13652
+ } else if (isObject(responseBody)) {
13653
+ const model = read(responseBody, "model");
13654
+ child.log({
13655
+ output: context.endpoint === "/v1/chat/completions" ? read(responseBody, "choices") : processImagesInOutput(read(responseBody, "output")),
13656
+ ...typeof model === "string" ? { metadata: { model } } : {},
13657
+ metrics: parseMetricsFromUsage(read(responseBody, "usage"))
13658
+ });
13659
+ } else {
13660
+ child.log({ error: new Error("OpenAI Batch response body is missing") });
13661
+ }
13662
+ } catch (error) {
13663
+ child.log({ error });
13664
+ } finally {
13665
+ child.end({ endTime });
13666
+ }
13667
+ }
13668
+ async function completeResultFile({
13669
+ context,
13670
+ endTime,
13671
+ file,
13672
+ inputs,
13673
+ issues,
13674
+ seen,
13675
+ source,
13676
+ taskParent
13677
+ }) {
13678
+ if (file === void 0) {
13679
+ return;
13680
+ }
13681
+ for await (const value of jsonlRecords(file, (issue) => issues.push(issue))) {
13682
+ const customId = read(value, "custom_id");
13683
+ if (!validCustomId(customId) || !isObject(value)) {
13684
+ issues.push(
13685
+ new Error("OpenAI Batch result is missing a valid custom_id")
13686
+ );
13124
13687
  continue;
13125
13688
  }
13126
- if (!oaiName.endsWith("_tokens_details") || !isObject(value)) {
13689
+ if (seen.has(customId)) {
13690
+ issues.push(new Error("OpenAI Batch result contains a duplicate"));
13127
13691
  continue;
13128
13692
  }
13129
- const rawPrefix = oaiName.slice(0, -"_tokens_details".length);
13130
- const prefix = TOKEN_PREFIX_MAP[rawPrefix] || rawPrefix;
13131
- for (const [key, nestedValue] of Object.entries(value)) {
13132
- if (typeof nestedValue !== "number") {
13133
- continue;
13134
- }
13135
- metrics[`${prefix}_${key}`] = nestedValue;
13693
+ const input = inputs.get(customId);
13694
+ if (!input) {
13695
+ issues.push(
13696
+ new Error("OpenAI Batch result does not match an input record")
13697
+ );
13698
+ continue;
13136
13699
  }
13700
+ seen.add(customId);
13701
+ await completeBatchResult(context, taskParent, endTime, input, {
13702
+ value,
13703
+ source
13704
+ });
13137
13705
  }
13138
- return metrics;
13139
13706
  }
13140
- function parseCachedHeader(value) {
13141
- if (!value) {
13707
+ function sameInputs(first, second) {
13708
+ return first.size === second.size && [...first.keys()].every((customId) => second.has(customId));
13709
+ }
13710
+ async function contextForCompletion(inputFileId, inputData) {
13711
+ if (!inputData.endpoint || inputData.issues.length > 0) {
13142
13712
  return void 0;
13143
13713
  }
13144
- return ["true", "hit"].includes(value.toLowerCase()) ? 1 : 0;
13145
- }
13146
- function getCachedMetricFromHeaders(headers) {
13147
- if (!headers || typeof headers.get !== "function") {
13714
+ const existing = pendingBatchTraces.get(inputFileId);
13715
+ if (existing) {
13716
+ if (existing.context.endpoint !== inputData.endpoint || !sameInputs(existing.inputs, inputData.inputs)) {
13717
+ return void 0;
13718
+ }
13719
+ return { ...existing, inputs: inputData.inputs };
13720
+ }
13721
+ const parent = await exportParent(getSpanParentObject());
13722
+ if (!parent) {
13148
13723
  return void 0;
13149
13724
  }
13150
- const cachedHeader = headers.get(X_CACHED_HEADER);
13151
- if (cachedHeader) {
13152
- return parseCachedHeader(cachedHeader);
13725
+ const startTime = getCurrentUnixTimestamp();
13726
+ return {
13727
+ context: {
13728
+ inputFileId,
13729
+ endpoint: inputData.endpoint,
13730
+ parent,
13731
+ taskStartTime: startTime,
13732
+ childStartTime: startTime
13733
+ },
13734
+ inputs: inputData.inputs
13735
+ };
13736
+ }
13737
+ async function completeBatch(args) {
13738
+ const inputData = await readBatchInputs(args.inputFileContent);
13739
+ const trace = await contextForCompletion(args.inputFileId, inputData);
13740
+ if (!trace) {
13741
+ logBatchInstrumentationError(
13742
+ "left batch spans pending",
13743
+ inputData.issues[0] ?? new Error("OpenAI Batch input does not match")
13744
+ );
13745
+ return;
13153
13746
  }
13154
- return parseCachedHeader(headers.get(LEGACY_CACHED_HEADER));
13747
+ const endTime = trace.endTime ?? getCurrentUnixTimestamp();
13748
+ const task = await startBatchSpan(trace.context);
13749
+ const taskParent = await task.export();
13750
+ const issues = [];
13751
+ const seen = /* @__PURE__ */ new Set();
13752
+ await Promise.all([
13753
+ completeResultFile({
13754
+ context: trace.context,
13755
+ endTime,
13756
+ file: args.outputFileContent,
13757
+ inputs: trace.inputs,
13758
+ issues,
13759
+ seen,
13760
+ source: "output",
13761
+ taskParent
13762
+ }),
13763
+ completeResultFile({
13764
+ context: trace.context,
13765
+ endTime,
13766
+ file: args.errorFileContent,
13767
+ inputs: trace.inputs,
13768
+ issues,
13769
+ seen,
13770
+ source: "error",
13771
+ taskParent
13772
+ })
13773
+ ]);
13774
+ if (issues.length > 0) {
13775
+ logBatchInstrumentationError("left batch spans pending", issues[0]);
13776
+ return;
13777
+ }
13778
+ const missing = [...trace.inputs.values()].filter(
13779
+ ({ customId }) => !seen.has(customId)
13780
+ );
13781
+ if (!trace.status || trace.status === "completed") {
13782
+ if (missing.length > 0) {
13783
+ logBatchInstrumentationError(
13784
+ "left batch spans pending",
13785
+ new Error("OpenAI Batch result files are incomplete")
13786
+ );
13787
+ return;
13788
+ }
13789
+ } else {
13790
+ for (const input of missing) {
13791
+ const child = await startBatchChild(trace.context, taskParent, input);
13792
+ child.log({ error: new Error(`OpenAI Batch ${trace.status}`) });
13793
+ child.end({ endTime });
13794
+ }
13795
+ task.log({ error: new Error(`OpenAI Batch ${trace.status}`) });
13796
+ }
13797
+ task.end({ endTime });
13798
+ pendingBatchTraces.delete(args.inputFileId);
13155
13799
  }
13800
+ var interceptOpenAIBatchTraceComplete = (target, thisArg, args) => Promise.resolve(Reflect.apply(target, thisArg, args)).then(async (result) => {
13801
+ try {
13802
+ await completeBatch(args[0]);
13803
+ } catch (error) {
13804
+ logBatchInstrumentationError("could not complete batch", error);
13805
+ }
13806
+ return result;
13807
+ });
13156
13808
 
13157
13809
  // src/instrumentation/plugins/openai-plugin.ts
13158
13810
  var OpenAIPlugin = class extends BasePlugin {
@@ -13160,17 +13812,22 @@ var OpenAIPlugin = class extends BasePlugin {
13160
13812
  super();
13161
13813
  }
13162
13814
  onEnable() {
13815
+ this.unsubscribers.push(
13816
+ openAIChannels.filesCreateTraced.intercept(
13817
+ interceptOpenAIFilesCreateTraced
13818
+ ),
13819
+ openAIChannels.batchesRetrieveTraced.intercept(
13820
+ interceptOpenAIBatchesRetrieveTraced
13821
+ ),
13822
+ openAIChannels.batchesCompleteTrace.intercept(
13823
+ interceptOpenAIBatchTraceComplete
13824
+ )
13825
+ );
13163
13826
  this.unsubscribers.push(
13164
13827
  traceStreamingChannel(openAIChannels.chatCompletionsCreate, {
13165
13828
  name: "Chat Completion",
13166
13829
  type: "llm" /* LLM */,
13167
- extractInput: ([params]) => {
13168
- const { messages, ...metadata } = params;
13169
- return {
13170
- input: processInputAttachments(messages),
13171
- metadata: { ...metadata, provider: "openai" }
13172
- };
13173
- },
13830
+ extractInput: ([params]) => extractOpenAIChatInput(params),
13174
13831
  extractOutput: (result) => {
13175
13832
  return result?.choices;
13176
13833
  },
@@ -13216,13 +13873,7 @@ var OpenAIPlugin = class extends BasePlugin {
13216
13873
  traceStreamingChannel(openAIChannels.betaChatCompletionsParse, {
13217
13874
  name: "Chat Completion",
13218
13875
  type: "llm" /* LLM */,
13219
- extractInput: ([params]) => {
13220
- const { messages, ...metadata } = params;
13221
- return {
13222
- input: processInputAttachments(messages),
13223
- metadata: { ...metadata, provider: "openai" }
13224
- };
13225
- },
13876
+ extractInput: ([params]) => extractOpenAIChatInput(params),
13226
13877
  extractOutput: (result) => {
13227
13878
  return result?.choices;
13228
13879
  },
@@ -13244,13 +13895,7 @@ var OpenAIPlugin = class extends BasePlugin {
13244
13895
  traceSyncStreamChannel(openAIChannels.betaChatCompletionsStream, {
13245
13896
  name: "Chat Completion",
13246
13897
  type: "llm" /* LLM */,
13247
- extractInput: ([params]) => {
13248
- const { messages, ...metadata } = params;
13249
- return {
13250
- input: processInputAttachments(messages),
13251
- metadata: { ...metadata, provider: "openai" }
13252
- };
13253
- }
13898
+ extractInput: ([params]) => extractOpenAIChatInput(params)
13254
13899
  })
13255
13900
  );
13256
13901
  this.unsubscribers.push(
@@ -13280,23 +13925,11 @@ var OpenAIPlugin = class extends BasePlugin {
13280
13925
  traceStreamingChannel(openAIChannels.responsesCreate, {
13281
13926
  name: "openai.responses.create",
13282
13927
  type: "llm" /* LLM */,
13283
- extractInput: ([params]) => {
13284
- const { input, ...metadata } = params;
13285
- return {
13286
- input: processInputAttachments(input),
13287
- metadata: { ...metadata, provider: "openai" }
13288
- };
13289
- },
13928
+ extractInput: ([params]) => extractOpenAIResponsesInput(params),
13290
13929
  extractOutput: (result) => {
13291
13930
  return processImagesInOutput(result?.output);
13292
13931
  },
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
- },
13932
+ extractMetadata: (result) => extractOpenAIResponsesMetadata(result),
13300
13933
  extractMetrics: (result, startTime, endEvent) => {
13301
13934
  const metrics = withCachedMetric(
13302
13935
  parseMetricsFromUsage(result?.usage),
@@ -13315,13 +13948,7 @@ var OpenAIPlugin = class extends BasePlugin {
13315
13948
  traceSyncStreamChannel(openAIChannels.responsesStream, {
13316
13949
  name: "openai.responses.create",
13317
13950
  type: "llm" /* LLM */,
13318
- extractInput: ([params]) => {
13319
- const { input, ...metadata } = params;
13320
- return {
13321
- input: processInputAttachments(input),
13322
- metadata: { ...metadata, provider: "openai" }
13323
- };
13324
- },
13951
+ extractInput: ([params]) => extractOpenAIResponsesInput(params),
13325
13952
  extractFromEvent: (event) => {
13326
13953
  if (event.type !== "response.completed" || !event.response) {
13327
13954
  return {};
@@ -13344,23 +13971,11 @@ var OpenAIPlugin = class extends BasePlugin {
13344
13971
  traceStreamingChannel(openAIChannels.responsesParse, {
13345
13972
  name: "openai.responses.parse",
13346
13973
  type: "llm" /* LLM */,
13347
- extractInput: ([params]) => {
13348
- const { input, ...metadata } = params;
13349
- return {
13350
- input: processInputAttachments(input),
13351
- metadata: { ...metadata, provider: "openai" }
13352
- };
13353
- },
13974
+ extractInput: ([params]) => extractOpenAIResponsesInput(params),
13354
13975
  extractOutput: (result) => {
13355
13976
  return processImagesInOutput(result?.output);
13356
13977
  },
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
- },
13978
+ extractMetadata: (result) => extractOpenAIResponsesMetadata(result),
13364
13979
  extractMetrics: (result, startTime, endEvent) => {
13365
13980
  const metrics = withCachedMetric(
13366
13981
  parseMetricsFromUsage(result?.usage),
@@ -13379,23 +13994,11 @@ var OpenAIPlugin = class extends BasePlugin {
13379
13994
  traceAsyncChannel(openAIChannels.responsesCompact, {
13380
13995
  name: "openai.responses.compact",
13381
13996
  type: "llm" /* LLM */,
13382
- extractInput: ([params]) => {
13383
- const { input, ...metadata } = params;
13384
- return {
13385
- input: processInputAttachments(input),
13386
- metadata: { ...metadata, provider: "openai" }
13387
- };
13388
- },
13997
+ extractInput: ([params]) => extractOpenAIResponsesInput(params),
13389
13998
  extractOutput: (result) => {
13390
13999
  return processImagesInOutput(result?.output);
13391
14000
  },
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
- },
14001
+ extractMetadata: (result) => extractOpenAIResponsesMetadata(result),
13399
14002
  extractMetrics: (result, startTime, endEvent) => {
13400
14003
  const metrics = withCachedMetric(
13401
14004
  parseMetricsFromUsage(result?.usage),
@@ -13451,35 +14054,6 @@ function withCachedMetric(metrics, result, endEvent) {
13451
14054
  cached
13452
14055
  };
13453
14056
  }
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
14057
  function mergeLogprobTokens(existing, incoming) {
13484
14058
  if (incoming === void 0) {
13485
14059
  return existing;
@@ -33499,7 +34073,7 @@ function extractSession(event) {
33499
34073
  return isObject(candidate) && typeof candidate.prompt === "function" ? candidate : void 0;
33500
34074
  }
33501
34075
  function isPiAgent(value) {
33502
- return isObject(value) && typeof value.streamFn === "function" && typeof value.subscribe === "function";
34076
+ return isObject(value) && (typeof value.streamFunction === "function" || typeof value.streamFn === "function") && typeof value.subscribe === "function";
33503
34077
  }
33504
34078
  function promptContextStore() {
33505
34079
  piPromptContextStore ??= isomorph_default.newAsyncLocalStorage();
@@ -33509,17 +34083,21 @@ function currentPiPromptState() {
33509
34083
  return promptContextStore().getStore();
33510
34084
  }
33511
34085
  function installPiAgentInstrumentation(agent) {
34086
+ const property = typeof agent.streamFunction === "function" ? "streamFunction" : "streamFn";
34087
+ const streamFunction = agent[property];
33512
34088
  const existing = piAgentPatchStates.get(agent);
33513
- if (!existing || agent.streamFn !== existing.wrappedStreamFn) {
34089
+ if (streamFunction && (!existing || existing.property !== property || streamFunction !== existing.wrappedStreamFunction)) {
33514
34090
  const patchState = {
33515
- originalStreamFn: agent.streamFn,
33516
- wrappedStreamFn: agent.streamFn
34091
+ originalStreamFunction: streamFunction,
34092
+ property,
34093
+ wrappedStreamFunction: streamFunction
33517
34094
  };
33518
- patchState.wrappedStreamFn = makeInstrumentedStreamFn(
34095
+ patchState.wrappedStreamFunction = makeInstrumentedStreamFunction(
33519
34096
  agent,
33520
- patchState.originalStreamFn
34097
+ patchState.originalStreamFunction,
34098
+ property
33521
34099
  );
33522
- agent.streamFn = patchState.wrappedStreamFn;
34100
+ agent[property] = patchState.wrappedStreamFunction;
33523
34101
  piAgentPatchStates.set(agent, patchState);
33524
34102
  }
33525
34103
  if (piAgentEventSubscriptions.has(agent)) {
@@ -33544,15 +34122,21 @@ function installPiAgentInstrumentation(agent) {
33544
34122
  logInstrumentationError4("Pi Coding Agent event subscription", error);
33545
34123
  }
33546
34124
  }
33547
- function makeInstrumentedStreamFn(agent, originalStreamFn) {
33548
- return async function instrumentedPiStreamFn(model, context, options) {
33549
- const invokeOriginal = () => Reflect.apply(originalStreamFn, this, [model, context, options]);
34125
+ function makeInstrumentedStreamFunction(agent, originalStreamFunction, property) {
34126
+ return async function instrumentedPiStreamFunction(model, context, options) {
34127
+ const invokeOriginal = () => Reflect.apply(originalStreamFunction, this, [model, context, options]);
33550
34128
  const state = currentPiPromptState();
33551
34129
  if (!state || state.agent !== agent || state.finalized) {
33552
34130
  return invokeOriginal();
33553
34131
  }
33554
34132
  wrapPiToolExecutors(context.tools);
33555
- const llmState = await startPiLlmSpan(state, model, context, options);
34133
+ const llmState = await startPiLlmSpan(
34134
+ state,
34135
+ model,
34136
+ context,
34137
+ property,
34138
+ options
34139
+ );
33556
34140
  try {
33557
34141
  const stream = await runWithAutoInstrumentationSuppressed(invokeOriginal);
33558
34142
  return patchAssistantMessageStream(stream, state, llmState);
@@ -33589,12 +34173,12 @@ function wrapPiToolExecutors(tools) {
33589
34173
  }
33590
34174
  }
33591
34175
  }
33592
- async function startPiLlmSpan(state, model, context, options) {
34176
+ async function startPiLlmSpan(state, model, context, property, options) {
33593
34177
  const metadata = {
33594
34178
  ...extractModelMetadata2(model),
33595
34179
  ...extractStreamOptionsMetadata(options),
33596
34180
  ...extractToolMetadata(context.tools),
33597
- "pi_coding_agent.operation": "agent.streamFn"
34181
+ "pi_coding_agent.operation": `agent.${property}`
33598
34182
  };
33599
34183
  const span = startSpan(
33600
34184
  withSpanInstrumentationName(