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
@@ -5771,7 +5771,7 @@ var INSTRUMENTATION_NAMES = {
5771
5771
  var INTERNAL_SPAN_INSTRUMENTATION_NAME = /* @__PURE__ */ Symbol.for(
5772
5772
  "braintrust.spanInstrumentationName"
5773
5773
  );
5774
- var SDK_VERSION = true ? "3.29.0" : "0.0.0";
5774
+ var SDK_VERSION = true ? "3.30.0" : "0.0.0";
5775
5775
  function withSpanInstrumentationName(args, instrumentationName) {
5776
5776
  return {
5777
5777
  ...args,
@@ -8487,6 +8487,9 @@ function getSpanParentObjectAndPropagatedState(options) {
8487
8487
  }
8488
8488
  return { parentObject: NOOP_SPAN, propagatedState: void 0 };
8489
8489
  }
8490
+ function getSpanParentObject(options) {
8491
+ return getSpanParentObjectAndPropagatedState(options).parentObject;
8492
+ }
8490
8493
  function currentBraintrustParent(state) {
8491
8494
  const resolvedState = state ?? _globalState;
8492
8495
  const experiment = currentExperiment({ state: resolvedState });
@@ -8733,6 +8736,15 @@ function _internalStartSpanWithInitialMerge(args) {
8733
8736
  [INITIAL_SPAN_WRITE_AS_MERGE]: true
8734
8737
  }).span;
8735
8738
  }
8739
+ function _internalStartSpanWithInitialMergeAndParentSpanIds(args) {
8740
+ return startSpanAndIsLogger(
8741
+ {
8742
+ ...args,
8743
+ [INITIAL_SPAN_WRITE_AS_MERGE]: true
8744
+ },
8745
+ { useParentSpanIdsForObjectParent: true }
8746
+ ).span;
8747
+ }
8736
8748
  function _internalStartSpanWithContext(args, context) {
8737
8749
  return startSpanAndIsLogger({
8738
8750
  ...args,
@@ -8743,7 +8755,7 @@ async function flush(options) {
8743
8755
  const state = options?.state ?? _globalState;
8744
8756
  return await state.bgLogger().flush();
8745
8757
  }
8746
- function startSpanAndIsLogger(args) {
8758
+ function startSpanAndIsLogger(args, internalOptions) {
8747
8759
  const state = args?.state ?? _globalState;
8748
8760
  const { parentObject, propagatedState } = getSpanParentObjectAndPropagatedState({
8749
8761
  asyncFlush: args?.asyncFlush,
@@ -8757,7 +8769,7 @@ function startSpanAndIsLogger(args) {
8757
8769
  ) ? {
8758
8770
  spanId: parentObject.data.span_id,
8759
8771
  rootSpanId: parentObject.data.root_span_id
8760
- } : void 0;
8772
+ } : internalOptions?.useParentSpanIdsForObjectParent ? args?.parentSpanIds : void 0;
8761
8773
  const { parent: _ignoredParent, ...spanArgs } = args ?? {};
8762
8774
  const span = new SpanImpl({
8763
8775
  state,
@@ -11787,6 +11799,131 @@ function unsubscribeAll(unsubscribers) {
11787
11799
  return [];
11788
11800
  }
11789
11801
 
11802
+ // src/instrumentation/core/channel-definitions.ts
11803
+ function channel(spec) {
11804
+ return spec;
11805
+ }
11806
+ function defineChannels(pkg, channels, options) {
11807
+ const { instrumentationName } = options;
11808
+ return Object.fromEntries(
11809
+ Object.entries(channels).map(([key, spec]) => {
11810
+ const fullChannelName = `orchestrion:${pkg}:${spec.channelName}`;
11811
+ if (spec.kind === "async") {
11812
+ const asyncSpec = spec;
11813
+ const tracingChannel2 = () => isomorph_default.newTracingChannel(
11814
+ fullChannelName
11815
+ );
11816
+ const intercept2 = (interceptor) => {
11817
+ const hook = tracingChannel2();
11818
+ return typeof hook.intercept === "function" ? hook.intercept(interceptor) : () => {
11819
+ };
11820
+ };
11821
+ return [
11822
+ key,
11823
+ {
11824
+ ...asyncSpec,
11825
+ instrumentationName,
11826
+ intercept: intercept2,
11827
+ invoke: (target, thisArg, args, additional) => {
11828
+ const hook = tracingChannel2();
11829
+ return typeof hook.invoke === "function" ? hook.invoke(target, thisArg, args, additional) : Reflect.apply(target, thisArg, args);
11830
+ },
11831
+ tracingChannel: tracingChannel2,
11832
+ tracePromise: (fn, context) => tracingChannel2().tracePromise(
11833
+ fn,
11834
+ // eslint-disable-next-line @typescript-eslint/consistent-type-assertions
11835
+ context
11836
+ )
11837
+ }
11838
+ ];
11839
+ }
11840
+ const syncSpec = spec;
11841
+ const tracingChannel = () => isomorph_default.newTracingChannel(
11842
+ fullChannelName
11843
+ );
11844
+ const intercept = (interceptor) => {
11845
+ const hook = tracingChannel();
11846
+ return typeof hook.intercept === "function" ? hook.intercept(interceptor) : () => {
11847
+ };
11848
+ };
11849
+ return [
11850
+ key,
11851
+ {
11852
+ ...syncSpec,
11853
+ instrumentationName,
11854
+ intercept,
11855
+ invoke: (target, thisArg, args, additional) => {
11856
+ const hook = tracingChannel();
11857
+ return typeof hook.invoke === "function" ? hook.invoke(target, thisArg, args, additional) : Reflect.apply(target, thisArg, args);
11858
+ },
11859
+ tracingChannel,
11860
+ traceSync: (fn, context) => tracingChannel().traceSync(
11861
+ fn,
11862
+ // eslint-disable-next-line @typescript-eslint/consistent-type-assertions
11863
+ context
11864
+ )
11865
+ }
11866
+ ];
11867
+ })
11868
+ );
11869
+ }
11870
+
11871
+ // src/instrumentation/plugins/openai-channels.ts
11872
+ var openAIChannels = defineChannels(
11873
+ "openai",
11874
+ {
11875
+ filesCreateTraced: channel({
11876
+ channelName: "files.create-traced",
11877
+ kind: "async"
11878
+ }),
11879
+ batchesRetrieveTraced: channel({
11880
+ channelName: "batches.retrieve-traced",
11881
+ kind: "async"
11882
+ }),
11883
+ batchesCompleteTrace: channel({
11884
+ channelName: "batches.complete-trace",
11885
+ kind: "async"
11886
+ }),
11887
+ chatCompletionsCreate: channel({
11888
+ channelName: "chat.completions.create",
11889
+ kind: "async"
11890
+ }),
11891
+ embeddingsCreate: channel({
11892
+ channelName: "embeddings.create",
11893
+ kind: "async"
11894
+ }),
11895
+ betaChatCompletionsParse: channel({
11896
+ channelName: "beta.chat.completions.parse",
11897
+ kind: "async"
11898
+ }),
11899
+ betaChatCompletionsStream: channel({
11900
+ channelName: "beta.chat.completions.stream",
11901
+ kind: "sync-stream"
11902
+ }),
11903
+ moderationsCreate: channel({
11904
+ channelName: "moderations.create",
11905
+ kind: "async"
11906
+ }),
11907
+ responsesCreate: channel({
11908
+ channelName: "responses.create",
11909
+ kind: "async"
11910
+ }),
11911
+ responsesStream: channel({
11912
+ channelName: "responses.stream",
11913
+ kind: "sync-stream"
11914
+ }),
11915
+ responsesParse: channel({
11916
+ channelName: "responses.parse",
11917
+ kind: "async"
11918
+ }),
11919
+ responsesCompact: channel({
11920
+ channelName: "responses.compact",
11921
+ kind: "async"
11922
+ })
11923
+ },
11924
+ { instrumentationName: INSTRUMENTATION_NAMES.OPENAI }
11925
+ );
11926
+
11790
11927
  // src/wrappers/attachment-utils.ts
11791
11928
  function getExtensionFromMediaType(mediaType) {
11792
11929
  const extensionMap = {
@@ -11962,173 +12099,691 @@ function processInputAttachments(input) {
11962
12099
  return processNode(input);
11963
12100
  }
11964
12101
 
11965
- // src/instrumentation/core/channel-definitions.ts
11966
- function channel(spec) {
11967
- return spec;
12102
+ // src/instrumentation/plugins/openai-span-data.ts
12103
+ var OPENAI_METADATA_KEYS = [
12104
+ "model",
12105
+ "temperature",
12106
+ "top_p",
12107
+ "max_tokens",
12108
+ "frequency_penalty",
12109
+ "presence_penalty",
12110
+ "stop",
12111
+ "response_format",
12112
+ "tools",
12113
+ "tool_choice",
12114
+ "parallel_tool_calls",
12115
+ "max_tool_calls"
12116
+ ];
12117
+ function batchMetadata(params) {
12118
+ const metadata = { provider: "openai" };
12119
+ for (const key of OPENAI_METADATA_KEYS) {
12120
+ try {
12121
+ const value = Reflect.get(params, key);
12122
+ if (value !== void 0) {
12123
+ metadata[key] = value;
12124
+ }
12125
+ } catch {
12126
+ }
12127
+ }
12128
+ return metadata;
11968
12129
  }
11969
- function defineChannels(pkg, channels, options) {
11970
- const { instrumentationName } = options;
11971
- return Object.fromEntries(
11972
- Object.entries(channels).map(([key, spec]) => {
11973
- const fullChannelName = `orchestrion:${pkg}:${spec.channelName}`;
11974
- if (spec.kind === "async") {
11975
- const asyncSpec = spec;
11976
- const tracingChannel2 = () => isomorph_default.newTracingChannel(
11977
- fullChannelName
11978
- );
11979
- const intercept2 = (interceptor) => {
11980
- const hook = tracingChannel2();
11981
- return typeof hook.intercept === "function" ? hook.intercept(interceptor) : () => {
11982
- };
11983
- };
11984
- return [
11985
- key,
11986
- {
11987
- ...asyncSpec,
11988
- instrumentationName,
11989
- intercept: intercept2,
11990
- invoke: (target, thisArg, args, additional) => {
11991
- const hook = tracingChannel2();
11992
- return typeof hook.invoke === "function" ? hook.invoke(target, thisArg, args, additional) : Reflect.apply(target, thisArg, args);
11993
- },
11994
- tracingChannel: tracingChannel2,
11995
- tracePromise: (fn, context) => tracingChannel2().tracePromise(
11996
- fn,
11997
- // eslint-disable-next-line @typescript-eslint/consistent-type-assertions
11998
- context
11999
- )
12000
- }
12001
- ];
12130
+ function extractOpenAIBatchInput(endpoint, params) {
12131
+ const input = endpoint === "/v1/chat/completions" ? params.messages : params.input;
12132
+ return {
12133
+ input: processInputAttachments(input),
12134
+ metadata: batchMetadata(params)
12135
+ };
12136
+ }
12137
+ function extractOpenAIChatInput(params) {
12138
+ const { messages, ...metadata } = params;
12139
+ return {
12140
+ input: processInputAttachments(messages),
12141
+ metadata: { ...metadata, provider: "openai" }
12142
+ };
12143
+ }
12144
+ function extractOpenAIResponsesInput(params) {
12145
+ const { input, ...metadata } = params;
12146
+ return {
12147
+ input: processInputAttachments(input),
12148
+ metadata: { ...metadata, provider: "openai" }
12149
+ };
12150
+ }
12151
+ function extractOpenAIResponsesMetadata(result) {
12152
+ if (!result) {
12153
+ return void 0;
12154
+ }
12155
+ const { output: _output, usage: _usage, ...metadata } = result;
12156
+ return Object.keys(metadata).length > 0 ? metadata : void 0;
12157
+ }
12158
+ function processImagesInOutput(output) {
12159
+ if (Array.isArray(output)) {
12160
+ return output.map(processImagesInOutput);
12161
+ }
12162
+ if (isObject(output) && output.type === "image_generation_call" && typeof output.result === "string" && output.result) {
12163
+ const fileExtension = output.output_format || "png";
12164
+ const contentType = `image/${fileExtension}`;
12165
+ const baseFilename = typeof output.revised_prompt === "string" ? output.revised_prompt.slice(0, 50).replace(/[^a-zA-Z0-9]/g, "_") : "generated_image";
12166
+ let binaryString;
12167
+ try {
12168
+ binaryString = atob(output.result);
12169
+ } catch {
12170
+ return output;
12171
+ }
12172
+ const bytes = new Uint8Array(binaryString.length);
12173
+ for (let i = 0; i < binaryString.length; i++) {
12174
+ bytes[i] = binaryString.charCodeAt(i);
12175
+ }
12176
+ return {
12177
+ ...output,
12178
+ result: new Attachment({
12179
+ data: new Blob([bytes], { type: contentType }),
12180
+ filename: `${baseFilename}.${fileExtension}`,
12181
+ contentType
12182
+ })
12183
+ };
12184
+ }
12185
+ return output;
12186
+ }
12187
+
12188
+ // src/openai-utils.ts
12189
+ var BRAINTRUST_CACHED_STREAM_METRIC = "__braintrust_cached_metric";
12190
+ var LEGACY_CACHED_HEADER = "x-cached";
12191
+ var X_CACHED_HEADER = "x-bt-cached";
12192
+ var TOKEN_NAME_MAP = {
12193
+ input_tokens: "prompt_tokens",
12194
+ output_tokens: "completion_tokens",
12195
+ total_tokens: "tokens"
12196
+ };
12197
+ var TOKEN_PREFIX_MAP = {
12198
+ input: "prompt",
12199
+ output: "completion"
12200
+ };
12201
+ function parseMetricsFromUsage(usage) {
12202
+ if (!usage) {
12203
+ return {};
12204
+ }
12205
+ const metrics = {};
12206
+ for (const [oaiName, value] of Object.entries(usage)) {
12207
+ if (typeof value === "number") {
12208
+ const metricName = TOKEN_NAME_MAP[oaiName] || oaiName;
12209
+ metrics[metricName] = value;
12210
+ continue;
12211
+ }
12212
+ if (!oaiName.endsWith("_tokens_details") || !isObject(value)) {
12213
+ continue;
12214
+ }
12215
+ const rawPrefix = oaiName.slice(0, -"_tokens_details".length);
12216
+ const prefix = TOKEN_PREFIX_MAP[rawPrefix] || rawPrefix;
12217
+ for (const [key, nestedValue] of Object.entries(value)) {
12218
+ if (typeof nestedValue !== "number") {
12219
+ continue;
12002
12220
  }
12003
- const syncSpec = spec;
12004
- const tracingChannel = () => isomorph_default.newTracingChannel(
12005
- fullChannelName
12006
- );
12007
- const intercept = (interceptor) => {
12008
- const hook = tracingChannel();
12009
- return typeof hook.intercept === "function" ? hook.intercept(interceptor) : () => {
12010
- };
12011
- };
12012
- return [
12013
- key,
12221
+ metrics[`${prefix}_${key}`] = nestedValue;
12222
+ }
12223
+ }
12224
+ return metrics;
12225
+ }
12226
+ function parseCachedHeader(value) {
12227
+ if (!value) {
12228
+ return void 0;
12229
+ }
12230
+ return ["true", "hit"].includes(value.toLowerCase()) ? 1 : 0;
12231
+ }
12232
+ function getCachedMetricFromHeaders(headers) {
12233
+ if (!headers || typeof headers.get !== "function") {
12234
+ return void 0;
12235
+ }
12236
+ const cachedHeader = headers.get(X_CACHED_HEADER);
12237
+ if (cachedHeader) {
12238
+ return parseCachedHeader(cachedHeader);
12239
+ }
12240
+ return parseCachedHeader(headers.get(LEGACY_CACHED_HEADER));
12241
+ }
12242
+
12243
+ // src/instrumentation/plugins/openai-batch-instrumentation.ts
12244
+ var SUPPORTED_ENDPOINTS = /* @__PURE__ */ new Set(["/v1/chat/completions", "/v1/responses"]);
12245
+ var TERMINAL_STATUSES = /* @__PURE__ */ new Set([
12246
+ "completed",
12247
+ "failed",
12248
+ "expired",
12249
+ "cancelled"
12250
+ ]);
12251
+ var pendingBatchTraces = /* @__PURE__ */ new Map();
12252
+ function read(value, key) {
12253
+ if (!isObject(value)) {
12254
+ return void 0;
12255
+ }
12256
+ try {
12257
+ return Reflect.get(value, key);
12258
+ } catch {
12259
+ return void 0;
12260
+ }
12261
+ }
12262
+ function isBatchRecordIterable(value) {
12263
+ if (value === null || typeof value !== "object" && typeof value !== "function") {
12264
+ return false;
12265
+ }
12266
+ return typeof read(value, Symbol.iterator) === "function" || typeof read(value, Symbol.asyncIterator) === "function";
12267
+ }
12268
+ function validCustomId(value) {
12269
+ return typeof value === "string" && value.length > 0 && value.length <= 64;
12270
+ }
12271
+ function logBatchInstrumentationError(context, error) {
12272
+ debugLogger.debug(`OpenAI Batch instrumentation ${context}:`, error);
12273
+ }
12274
+ async function exportParent(parent) {
12275
+ if ("toStr" in parent && typeof parent.toStr === "function") {
12276
+ return parent.toStr();
12277
+ }
12278
+ if ("export" in parent && typeof parent.export === "function") {
12279
+ return await parent.export();
12280
+ }
12281
+ return void 0;
12282
+ }
12283
+ async function deterministicDigest(namespace, ...parts) {
12284
+ const encoded = new TextEncoder().encode(
12285
+ [namespace, ...parts].map((part) => `${part.length}:${part}`).join("\0")
12286
+ );
12287
+ return new Uint8Array(
12288
+ await globalThis.crypto.subtle.digest("SHA-256", encoded)
12289
+ );
12290
+ }
12291
+ function digestHex(bytes, length) {
12292
+ return Array.from(bytes.slice(0, length)).map((byte) => byte.toString(16).padStart(2, "0")).join("");
12293
+ }
12294
+ function digestUuid(bytes) {
12295
+ const hex = digestHex(bytes, 16);
12296
+ return `${hex.slice(0, 8)}-${hex.slice(8, 12)}-${hex.slice(12, 16)}-${hex.slice(16, 20)}-${hex.slice(20)}`;
12297
+ }
12298
+ async function batchSpanIds(inputFileId) {
12299
+ const [row, span, root] = await Promise.all([
12300
+ deterministicDigest("openai:batch:row", inputFileId),
12301
+ deterministicDigest("openai:batch:span", inputFileId),
12302
+ deterministicDigest("openai:batch:root", inputFileId)
12303
+ ]);
12304
+ return {
12305
+ rowId: digestUuid(row),
12306
+ spanId: digestHex(span, 8),
12307
+ rootSpanId: digestHex(root, 16)
12308
+ };
12309
+ }
12310
+ async function childSpanIds(inputFileId, customId) {
12311
+ const [row, span] = await Promise.all([
12312
+ deterministicDigest("openai:batch:child:row", inputFileId, customId),
12313
+ deterministicDigest("openai:batch:child:span", inputFileId, customId)
12314
+ ]);
12315
+ return { rowId: digestUuid(row), spanId: digestHex(span, 8) };
12316
+ }
12317
+ async function startBatchSpan(context) {
12318
+ const ids = await batchSpanIds(context.inputFileId);
12319
+ const parent = SpanComponentsV4.fromStr(context.parent);
12320
+ const hasParentSpan = Boolean(
12321
+ parent.data.row_id && parent.data.span_id && parent.data.root_span_id
12322
+ );
12323
+ return withCurrent(
12324
+ NOOP_SPAN,
12325
+ () => _internalStartSpanWithInitialMergeAndParentSpanIds(
12326
+ withSpanInstrumentationName(
12014
12327
  {
12015
- ...syncSpec,
12016
- instrumentationName,
12017
- intercept,
12018
- invoke: (target, thisArg, args, additional) => {
12019
- const hook = tracingChannel();
12020
- return typeof hook.invoke === "function" ? hook.invoke(target, thisArg, args, additional) : Reflect.apply(target, thisArg, args);
12021
- },
12022
- tracingChannel,
12023
- traceSync: (fn, context) => tracingChannel().traceSync(
12024
- fn,
12025
- // eslint-disable-next-line @typescript-eslint/consistent-type-assertions
12026
- context
12027
- )
12028
- }
12029
- ];
12030
- })
12328
+ name: "openai.batch",
12329
+ type: "task" /* TASK */,
12330
+ parent: context.parent,
12331
+ ...!hasParentSpan ? {
12332
+ parentSpanIds: {
12333
+ parentSpanIds: [],
12334
+ rootSpanId: ids.rootSpanId
12335
+ }
12336
+ } : {},
12337
+ spanId: ids.spanId,
12338
+ startTime: context.taskStartTime,
12339
+ event: {
12340
+ id: ids.rowId,
12341
+ metadata: {
12342
+ endpoint: context.endpoint,
12343
+ input_file_id: context.inputFileId,
12344
+ provider: "openai"
12345
+ }
12346
+ }
12347
+ },
12348
+ INSTRUMENTATION_NAMES.OPENAI
12349
+ )
12350
+ )
12031
12351
  );
12032
12352
  }
12033
-
12034
- // src/instrumentation/plugins/openai-channels.ts
12035
- var openAIChannels = defineChannels(
12036
- "openai",
12037
- {
12038
- chatCompletionsCreate: channel({
12039
- channelName: "chat.completions.create",
12040
- kind: "async"
12041
- }),
12042
- embeddingsCreate: channel({
12043
- channelName: "embeddings.create",
12044
- kind: "async"
12045
- }),
12046
- betaChatCompletionsParse: channel({
12047
- channelName: "beta.chat.completions.parse",
12048
- kind: "async"
12049
- }),
12050
- betaChatCompletionsStream: channel({
12051
- channelName: "beta.chat.completions.stream",
12052
- kind: "sync-stream"
12053
- }),
12054
- moderationsCreate: channel({
12055
- channelName: "moderations.create",
12056
- kind: "async"
12057
- }),
12058
- responsesCreate: channel({
12059
- channelName: "responses.create",
12060
- kind: "async"
12061
- }),
12062
- responsesStream: channel({
12063
- channelName: "responses.stream",
12064
- kind: "sync-stream"
12065
- }),
12066
- responsesParse: channel({
12067
- channelName: "responses.parse",
12068
- kind: "async"
12069
- }),
12070
- responsesCompact: channel({
12071
- channelName: "responses.compact",
12072
- kind: "async"
12073
- })
12074
- },
12075
- { instrumentationName: INSTRUMENTATION_NAMES.OPENAI }
12076
- );
12077
-
12078
- // src/openai-utils.ts
12079
- var BRAINTRUST_CACHED_STREAM_METRIC = "__braintrust_cached_metric";
12080
- var LEGACY_CACHED_HEADER = "x-cached";
12081
- var X_CACHED_HEADER = "x-bt-cached";
12082
- var TOKEN_NAME_MAP = {
12083
- input_tokens: "prompt_tokens",
12084
- output_tokens: "completion_tokens",
12085
- total_tokens: "tokens"
12086
- };
12087
- var TOKEN_PREFIX_MAP = {
12088
- input: "prompt",
12089
- output: "completion"
12353
+ async function startBatchChild(context, taskParent, input) {
12354
+ const ids = await childSpanIds(context.inputFileId, input.customId);
12355
+ return withCurrent(
12356
+ NOOP_SPAN,
12357
+ () => _internalStartSpanWithInitialMerge(
12358
+ withSpanInstrumentationName(
12359
+ {
12360
+ name: context.endpoint === "/v1/chat/completions" ? "Chat Completion" : "openai.responses.create",
12361
+ type: "llm" /* LLM */,
12362
+ parent: taskParent,
12363
+ spanId: ids.spanId,
12364
+ startTime: context.childStartTime,
12365
+ event: {
12366
+ id: ids.rowId,
12367
+ ...input.spanData?.input !== void 0 ? { input: input.spanData.input } : {},
12368
+ metadata: {
12369
+ ...input.spanData?.metadata,
12370
+ custom_id: input.customId,
12371
+ provider: "openai"
12372
+ }
12373
+ }
12374
+ },
12375
+ INSTRUMENTATION_NAMES.OPENAI
12376
+ )
12377
+ )
12378
+ );
12379
+ }
12380
+ async function* jsonlRecords(file, onIssue = () => {
12381
+ }) {
12382
+ const resolvedFile = await file;
12383
+ if (typeof resolvedFile === "string") {
12384
+ for (const line of resolvedFile.split("\n")) {
12385
+ if (!line.trim()) {
12386
+ continue;
12387
+ }
12388
+ try {
12389
+ yield JSON.parse(line.replace(/\r$/, ""));
12390
+ } catch (error) {
12391
+ logBatchInstrumentationError("skipped malformed JSONL", error);
12392
+ onIssue(new Error("OpenAI Batch file contains malformed JSONL"));
12393
+ }
12394
+ }
12395
+ return;
12396
+ }
12397
+ const body = read(resolvedFile, "body");
12398
+ const getReader = read(body, "getReader");
12399
+ if (isObject(body) && typeof getReader === "function") {
12400
+ let reader;
12401
+ try {
12402
+ reader = Reflect.apply(getReader, body, []);
12403
+ const decoder = new TextDecoder();
12404
+ let pending = "";
12405
+ while (true) {
12406
+ const readChunk = read(reader, "read");
12407
+ if (typeof readChunk !== "function") {
12408
+ throw new Error("Response body stream has no read method");
12409
+ }
12410
+ const chunk = await Reflect.apply(readChunk, reader, []);
12411
+ if (!isObject(chunk)) {
12412
+ throw new Error("Response body stream returned an invalid chunk");
12413
+ }
12414
+ if (chunk.done === true) {
12415
+ pending += decoder.decode();
12416
+ break;
12417
+ }
12418
+ if (!(chunk.value instanceof Uint8Array)) {
12419
+ throw new Error("Response body stream returned a non-byte chunk");
12420
+ }
12421
+ pending += decoder.decode(chunk.value, { stream: true });
12422
+ let newline = pending.indexOf("\n");
12423
+ while (newline !== -1) {
12424
+ const line = pending.slice(0, newline).replace(/\r$/, "");
12425
+ pending = pending.slice(newline + 1);
12426
+ if (line.trim()) {
12427
+ try {
12428
+ yield JSON.parse(line);
12429
+ } catch (error) {
12430
+ logBatchInstrumentationError("skipped malformed JSONL", error);
12431
+ onIssue(new Error("OpenAI Batch file contains malformed JSONL"));
12432
+ }
12433
+ }
12434
+ newline = pending.indexOf("\n");
12435
+ }
12436
+ }
12437
+ if (pending.trim()) {
12438
+ try {
12439
+ yield JSON.parse(pending.replace(/\r$/, ""));
12440
+ } catch (error) {
12441
+ logBatchInstrumentationError("skipped malformed JSONL", error);
12442
+ onIssue(new Error("OpenAI Batch file contains malformed JSONL"));
12443
+ }
12444
+ }
12445
+ } catch (error) {
12446
+ logBatchInstrumentationError("could not read JSONL response body", error);
12447
+ onIssue(new Error("OpenAI Batch response body could not be read"));
12448
+ } finally {
12449
+ const releaseLock = read(reader, "releaseLock");
12450
+ if (typeof releaseLock === "function") {
12451
+ try {
12452
+ Reflect.apply(releaseLock, reader, []);
12453
+ } catch (error) {
12454
+ logBatchInstrumentationError(
12455
+ "could not release stream reader",
12456
+ error
12457
+ );
12458
+ }
12459
+ }
12460
+ }
12461
+ return;
12462
+ }
12463
+ if (isBatchRecordIterable(resolvedFile)) {
12464
+ for await (const record of resolvedFile) {
12465
+ yield record;
12466
+ }
12467
+ return;
12468
+ }
12469
+ logBatchInstrumentationError("skipped invalid JSONL source", resolvedFile);
12470
+ onIssue(new Error("OpenAI Batch file source is invalid"));
12471
+ }
12472
+ async function readBatchInputs(file) {
12473
+ const inputs = /* @__PURE__ */ new Map();
12474
+ const issues = [];
12475
+ let endpoint;
12476
+ try {
12477
+ for await (const value of jsonlRecords(
12478
+ file,
12479
+ (issue) => issues.push(issue)
12480
+ )) {
12481
+ const customId = read(value, "custom_id");
12482
+ const url = read(value, "url");
12483
+ const body = read(value, "body");
12484
+ if (!validCustomId(customId) || inputs.has(customId) || read(value, "method") !== "POST" || typeof url !== "string" || !SUPPORTED_ENDPOINTS.has(url) || endpoint !== void 0 && endpoint !== url || !isObject(body)) {
12485
+ issues.push(new Error("OpenAI Batch input contains an invalid record"));
12486
+ continue;
12487
+ }
12488
+ endpoint = url;
12489
+ let spanData;
12490
+ try {
12491
+ spanData = extractOpenAIBatchInput(url, body);
12492
+ } catch (error) {
12493
+ logBatchInstrumentationError("could not extract batch input", error);
12494
+ }
12495
+ inputs.set(customId, { customId, spanData });
12496
+ }
12497
+ } catch (error) {
12498
+ logBatchInstrumentationError("could not process input file", error);
12499
+ issues.push(new Error("OpenAI Batch input file could not be processed"));
12500
+ }
12501
+ if (!endpoint || inputs.size === 0) {
12502
+ issues.push(new Error("OpenAI Batch input contains no supported records"));
12503
+ }
12504
+ return { endpoint, inputs, issues };
12505
+ }
12506
+ async function writePendingSpans(trace, endTime) {
12507
+ const task = await startBatchSpan(trace.context);
12508
+ const taskParent = await task.export();
12509
+ for (const input of trace.inputs.values()) {
12510
+ try {
12511
+ const child = await startBatchChild(trace.context, taskParent, input);
12512
+ if (endTime !== void 0) {
12513
+ child.end({ endTime });
12514
+ }
12515
+ } catch (error) {
12516
+ logBatchInstrumentationError("could not write batch request span", error);
12517
+ }
12518
+ }
12519
+ if (endTime !== void 0) {
12520
+ if (trace.status && trace.status !== "completed") {
12521
+ task.log({ error: new Error(`OpenAI Batch ${trace.status}`) });
12522
+ }
12523
+ task.end({ endTime });
12524
+ }
12525
+ }
12526
+ var interceptOpenAIFilesCreateTraced = async (target, thisArg, args) => {
12527
+ const startTime = getCurrentUnixTimestamp();
12528
+ const inputPromise = readBatchInputs(args[0].inputFileContent);
12529
+ const parentPromise = exportParent(args[0].parent);
12530
+ const file = await Reflect.apply(target, thisArg, args);
12531
+ try {
12532
+ const inputFileId = read(file, "id");
12533
+ const [inputData, exportedParent2] = await Promise.all([
12534
+ inputPromise,
12535
+ parentPromise
12536
+ ]);
12537
+ if (typeof inputFileId !== "string" || !exportedParent2 || !inputData.endpoint || inputData.issues.length > 0) {
12538
+ if (inputData.issues[0]) {
12539
+ logBatchInstrumentationError(
12540
+ "skipped invalid input file",
12541
+ inputData.issues[0]
12542
+ );
12543
+ }
12544
+ return file;
12545
+ }
12546
+ const trace = {
12547
+ context: {
12548
+ inputFileId,
12549
+ endpoint: inputData.endpoint,
12550
+ parent: exportedParent2,
12551
+ taskStartTime: startTime,
12552
+ childStartTime: startTime
12553
+ },
12554
+ inputs: inputData.inputs
12555
+ };
12556
+ pendingBatchTraces.set(inputFileId, trace);
12557
+ await writePendingSpans(trace);
12558
+ } catch (error) {
12559
+ logBatchInstrumentationError("could not start batch spans", error);
12560
+ }
12561
+ return file;
12090
12562
  };
12091
- function parseMetricsFromUsage(usage) {
12092
- if (!usage) {
12093
- return {};
12563
+ function validTimestamp(value) {
12564
+ return typeof value === "number" && Number.isFinite(value) && value >= 0;
12565
+ }
12566
+ function terminalEndTime(batch, startTime) {
12567
+ let timestamp;
12568
+ switch (batch.status) {
12569
+ case "completed":
12570
+ timestamp = batch.completed_at;
12571
+ break;
12572
+ case "failed":
12573
+ timestamp = batch.failed_at;
12574
+ break;
12575
+ case "expired":
12576
+ timestamp = batch.expired_at;
12577
+ break;
12578
+ default:
12579
+ timestamp = batch.cancelled_at;
12094
12580
  }
12095
- const metrics = {};
12096
- for (const [oaiName, value] of Object.entries(usage)) {
12097
- if (typeof value === "number") {
12098
- const metricName = TOKEN_NAME_MAP[oaiName] || oaiName;
12099
- metrics[metricName] = value;
12581
+ return validTimestamp(timestamp) ? Math.max(timestamp, startTime) : Math.max(getCurrentUnixTimestamp(), startTime);
12582
+ }
12583
+ async function updateBatchTimestamps(batch) {
12584
+ const trace = pendingBatchTraces.get(batch.input_file_id);
12585
+ if (!trace || trace.context.endpoint !== batch.endpoint) {
12586
+ return;
12587
+ }
12588
+ if (validTimestamp(batch.created_at)) {
12589
+ trace.context.taskStartTime = batch.created_at;
12590
+ }
12591
+ trace.context.childStartTime = validTimestamp(batch.in_progress_at) ? Math.max(batch.in_progress_at, trace.context.taskStartTime) : trace.context.taskStartTime;
12592
+ trace.status = batch.status;
12593
+ if (TERMINAL_STATUSES.has(batch.status)) {
12594
+ trace.endTime = terminalEndTime(batch, trace.context.childStartTime);
12595
+ }
12596
+ await writePendingSpans(trace, trace.endTime);
12597
+ }
12598
+ var interceptOpenAIBatchesRetrieveTraced = (target, thisArg, args) => Promise.resolve(Reflect.apply(target, thisArg, args)).then(async (batch) => {
12599
+ try {
12600
+ await updateBatchTimestamps(batch);
12601
+ } catch (error) {
12602
+ logBatchInstrumentationError("could not update batch timestamps", error);
12603
+ }
12604
+ return batch;
12605
+ });
12606
+ function errorFromResult(result) {
12607
+ const error = read(result.value, "error");
12608
+ if (isObject(error)) {
12609
+ const message = read(error, "message");
12610
+ return new Error(
12611
+ typeof message === "string" ? message : "OpenAI Batch request failed"
12612
+ );
12613
+ }
12614
+ const response = read(result.value, "response");
12615
+ const statusCode = read(response, "status_code");
12616
+ if (result.source === "error" || typeof statusCode === "number" && (statusCode < 200 || statusCode >= 300)) {
12617
+ const message = read(read(read(response, "body"), "error"), "message");
12618
+ return new Error(
12619
+ typeof message === "string" ? message : "OpenAI Batch request failed"
12620
+ );
12621
+ }
12622
+ return void 0;
12623
+ }
12624
+ async function completeBatchResult(context, taskParent, endTime, input, result) {
12625
+ const child = await startBatchChild(context, taskParent, input);
12626
+ try {
12627
+ const resultError = errorFromResult(result);
12628
+ const responseBody = read(read(result.value, "response"), "body");
12629
+ if (resultError) {
12630
+ child.log({ error: resultError });
12631
+ } else if (isObject(responseBody)) {
12632
+ const model = read(responseBody, "model");
12633
+ child.log({
12634
+ output: context.endpoint === "/v1/chat/completions" ? read(responseBody, "choices") : processImagesInOutput(read(responseBody, "output")),
12635
+ ...typeof model === "string" ? { metadata: { model } } : {},
12636
+ metrics: parseMetricsFromUsage(read(responseBody, "usage"))
12637
+ });
12638
+ } else {
12639
+ child.log({ error: new Error("OpenAI Batch response body is missing") });
12640
+ }
12641
+ } catch (error) {
12642
+ child.log({ error });
12643
+ } finally {
12644
+ child.end({ endTime });
12645
+ }
12646
+ }
12647
+ async function completeResultFile({
12648
+ context,
12649
+ endTime,
12650
+ file,
12651
+ inputs,
12652
+ issues,
12653
+ seen,
12654
+ source,
12655
+ taskParent
12656
+ }) {
12657
+ if (file === void 0) {
12658
+ return;
12659
+ }
12660
+ for await (const value of jsonlRecords(file, (issue) => issues.push(issue))) {
12661
+ const customId = read(value, "custom_id");
12662
+ if (!validCustomId(customId) || !isObject(value)) {
12663
+ issues.push(
12664
+ new Error("OpenAI Batch result is missing a valid custom_id")
12665
+ );
12100
12666
  continue;
12101
12667
  }
12102
- if (!oaiName.endsWith("_tokens_details") || !isObject(value)) {
12668
+ if (seen.has(customId)) {
12669
+ issues.push(new Error("OpenAI Batch result contains a duplicate"));
12103
12670
  continue;
12104
12671
  }
12105
- const rawPrefix = oaiName.slice(0, -"_tokens_details".length);
12106
- const prefix = TOKEN_PREFIX_MAP[rawPrefix] || rawPrefix;
12107
- for (const [key, nestedValue] of Object.entries(value)) {
12108
- if (typeof nestedValue !== "number") {
12109
- continue;
12110
- }
12111
- metrics[`${prefix}_${key}`] = nestedValue;
12672
+ const input = inputs.get(customId);
12673
+ if (!input) {
12674
+ issues.push(
12675
+ new Error("OpenAI Batch result does not match an input record")
12676
+ );
12677
+ continue;
12112
12678
  }
12679
+ seen.add(customId);
12680
+ await completeBatchResult(context, taskParent, endTime, input, {
12681
+ value,
12682
+ source
12683
+ });
12113
12684
  }
12114
- return metrics;
12115
12685
  }
12116
- function parseCachedHeader(value) {
12117
- if (!value) {
12686
+ function sameInputs(first, second) {
12687
+ return first.size === second.size && [...first.keys()].every((customId) => second.has(customId));
12688
+ }
12689
+ async function contextForCompletion(inputFileId, inputData) {
12690
+ if (!inputData.endpoint || inputData.issues.length > 0) {
12118
12691
  return void 0;
12119
12692
  }
12120
- return ["true", "hit"].includes(value.toLowerCase()) ? 1 : 0;
12121
- }
12122
- function getCachedMetricFromHeaders(headers) {
12123
- if (!headers || typeof headers.get !== "function") {
12693
+ const existing = pendingBatchTraces.get(inputFileId);
12694
+ if (existing) {
12695
+ if (existing.context.endpoint !== inputData.endpoint || !sameInputs(existing.inputs, inputData.inputs)) {
12696
+ return void 0;
12697
+ }
12698
+ return { ...existing, inputs: inputData.inputs };
12699
+ }
12700
+ const parent = await exportParent(getSpanParentObject());
12701
+ if (!parent) {
12124
12702
  return void 0;
12125
12703
  }
12126
- const cachedHeader = headers.get(X_CACHED_HEADER);
12127
- if (cachedHeader) {
12128
- return parseCachedHeader(cachedHeader);
12704
+ const startTime = getCurrentUnixTimestamp();
12705
+ return {
12706
+ context: {
12707
+ inputFileId,
12708
+ endpoint: inputData.endpoint,
12709
+ parent,
12710
+ taskStartTime: startTime,
12711
+ childStartTime: startTime
12712
+ },
12713
+ inputs: inputData.inputs
12714
+ };
12715
+ }
12716
+ async function completeBatch(args) {
12717
+ const inputData = await readBatchInputs(args.inputFileContent);
12718
+ const trace = await contextForCompletion(args.inputFileId, inputData);
12719
+ if (!trace) {
12720
+ logBatchInstrumentationError(
12721
+ "left batch spans pending",
12722
+ inputData.issues[0] ?? new Error("OpenAI Batch input does not match")
12723
+ );
12724
+ return;
12129
12725
  }
12130
- return parseCachedHeader(headers.get(LEGACY_CACHED_HEADER));
12726
+ const endTime = trace.endTime ?? getCurrentUnixTimestamp();
12727
+ const task = await startBatchSpan(trace.context);
12728
+ const taskParent = await task.export();
12729
+ const issues = [];
12730
+ const seen = /* @__PURE__ */ new Set();
12731
+ await Promise.all([
12732
+ completeResultFile({
12733
+ context: trace.context,
12734
+ endTime,
12735
+ file: args.outputFileContent,
12736
+ inputs: trace.inputs,
12737
+ issues,
12738
+ seen,
12739
+ source: "output",
12740
+ taskParent
12741
+ }),
12742
+ completeResultFile({
12743
+ context: trace.context,
12744
+ endTime,
12745
+ file: args.errorFileContent,
12746
+ inputs: trace.inputs,
12747
+ issues,
12748
+ seen,
12749
+ source: "error",
12750
+ taskParent
12751
+ })
12752
+ ]);
12753
+ if (issues.length > 0) {
12754
+ logBatchInstrumentationError("left batch spans pending", issues[0]);
12755
+ return;
12756
+ }
12757
+ const missing = [...trace.inputs.values()].filter(
12758
+ ({ customId }) => !seen.has(customId)
12759
+ );
12760
+ if (!trace.status || trace.status === "completed") {
12761
+ if (missing.length > 0) {
12762
+ logBatchInstrumentationError(
12763
+ "left batch spans pending",
12764
+ new Error("OpenAI Batch result files are incomplete")
12765
+ );
12766
+ return;
12767
+ }
12768
+ } else {
12769
+ for (const input of missing) {
12770
+ const child = await startBatchChild(trace.context, taskParent, input);
12771
+ child.log({ error: new Error(`OpenAI Batch ${trace.status}`) });
12772
+ child.end({ endTime });
12773
+ }
12774
+ task.log({ error: new Error(`OpenAI Batch ${trace.status}`) });
12775
+ }
12776
+ task.end({ endTime });
12777
+ pendingBatchTraces.delete(args.inputFileId);
12131
12778
  }
12779
+ var interceptOpenAIBatchTraceComplete = (target, thisArg, args) => Promise.resolve(Reflect.apply(target, thisArg, args)).then(async (result) => {
12780
+ try {
12781
+ await completeBatch(args[0]);
12782
+ } catch (error) {
12783
+ logBatchInstrumentationError("could not complete batch", error);
12784
+ }
12785
+ return result;
12786
+ });
12132
12787
 
12133
12788
  // src/instrumentation/plugins/openai-plugin.ts
12134
12789
  var OpenAIPlugin = class extends BasePlugin {
@@ -12136,17 +12791,22 @@ var OpenAIPlugin = class extends BasePlugin {
12136
12791
  super();
12137
12792
  }
12138
12793
  onEnable() {
12794
+ this.unsubscribers.push(
12795
+ openAIChannels.filesCreateTraced.intercept(
12796
+ interceptOpenAIFilesCreateTraced
12797
+ ),
12798
+ openAIChannels.batchesRetrieveTraced.intercept(
12799
+ interceptOpenAIBatchesRetrieveTraced
12800
+ ),
12801
+ openAIChannels.batchesCompleteTrace.intercept(
12802
+ interceptOpenAIBatchTraceComplete
12803
+ )
12804
+ );
12139
12805
  this.unsubscribers.push(
12140
12806
  traceStreamingChannel(openAIChannels.chatCompletionsCreate, {
12141
12807
  name: "Chat Completion",
12142
12808
  type: "llm" /* LLM */,
12143
- extractInput: ([params]) => {
12144
- const { messages, ...metadata } = params;
12145
- return {
12146
- input: processInputAttachments(messages),
12147
- metadata: { ...metadata, provider: "openai" }
12148
- };
12149
- },
12809
+ extractInput: ([params]) => extractOpenAIChatInput(params),
12150
12810
  extractOutput: (result) => {
12151
12811
  return result?.choices;
12152
12812
  },
@@ -12192,13 +12852,7 @@ var OpenAIPlugin = class extends BasePlugin {
12192
12852
  traceStreamingChannel(openAIChannels.betaChatCompletionsParse, {
12193
12853
  name: "Chat Completion",
12194
12854
  type: "llm" /* LLM */,
12195
- extractInput: ([params]) => {
12196
- const { messages, ...metadata } = params;
12197
- return {
12198
- input: processInputAttachments(messages),
12199
- metadata: { ...metadata, provider: "openai" }
12200
- };
12201
- },
12855
+ extractInput: ([params]) => extractOpenAIChatInput(params),
12202
12856
  extractOutput: (result) => {
12203
12857
  return result?.choices;
12204
12858
  },
@@ -12220,13 +12874,7 @@ var OpenAIPlugin = class extends BasePlugin {
12220
12874
  traceSyncStreamChannel(openAIChannels.betaChatCompletionsStream, {
12221
12875
  name: "Chat Completion",
12222
12876
  type: "llm" /* LLM */,
12223
- extractInput: ([params]) => {
12224
- const { messages, ...metadata } = params;
12225
- return {
12226
- input: processInputAttachments(messages),
12227
- metadata: { ...metadata, provider: "openai" }
12228
- };
12229
- }
12877
+ extractInput: ([params]) => extractOpenAIChatInput(params)
12230
12878
  })
12231
12879
  );
12232
12880
  this.unsubscribers.push(
@@ -12256,23 +12904,11 @@ var OpenAIPlugin = class extends BasePlugin {
12256
12904
  traceStreamingChannel(openAIChannels.responsesCreate, {
12257
12905
  name: "openai.responses.create",
12258
12906
  type: "llm" /* LLM */,
12259
- extractInput: ([params]) => {
12260
- const { input, ...metadata } = params;
12261
- return {
12262
- input: processInputAttachments(input),
12263
- metadata: { ...metadata, provider: "openai" }
12264
- };
12265
- },
12907
+ extractInput: ([params]) => extractOpenAIResponsesInput(params),
12266
12908
  extractOutput: (result) => {
12267
12909
  return processImagesInOutput(result?.output);
12268
12910
  },
12269
- extractMetadata: (result) => {
12270
- if (!result) {
12271
- return void 0;
12272
- }
12273
- const { output: _output, usage: _usage, ...metadata } = result;
12274
- return Object.keys(metadata).length > 0 ? metadata : void 0;
12275
- },
12911
+ extractMetadata: (result) => extractOpenAIResponsesMetadata(result),
12276
12912
  extractMetrics: (result, startTime, endEvent) => {
12277
12913
  const metrics = withCachedMetric(
12278
12914
  parseMetricsFromUsage(result?.usage),
@@ -12291,13 +12927,7 @@ var OpenAIPlugin = class extends BasePlugin {
12291
12927
  traceSyncStreamChannel(openAIChannels.responsesStream, {
12292
12928
  name: "openai.responses.create",
12293
12929
  type: "llm" /* LLM */,
12294
- extractInput: ([params]) => {
12295
- const { input, ...metadata } = params;
12296
- return {
12297
- input: processInputAttachments(input),
12298
- metadata: { ...metadata, provider: "openai" }
12299
- };
12300
- },
12930
+ extractInput: ([params]) => extractOpenAIResponsesInput(params),
12301
12931
  extractFromEvent: (event) => {
12302
12932
  if (event.type !== "response.completed" || !event.response) {
12303
12933
  return {};
@@ -12320,23 +12950,11 @@ var OpenAIPlugin = class extends BasePlugin {
12320
12950
  traceStreamingChannel(openAIChannels.responsesParse, {
12321
12951
  name: "openai.responses.parse",
12322
12952
  type: "llm" /* LLM */,
12323
- extractInput: ([params]) => {
12324
- const { input, ...metadata } = params;
12325
- return {
12326
- input: processInputAttachments(input),
12327
- metadata: { ...metadata, provider: "openai" }
12328
- };
12329
- },
12953
+ extractInput: ([params]) => extractOpenAIResponsesInput(params),
12330
12954
  extractOutput: (result) => {
12331
12955
  return processImagesInOutput(result?.output);
12332
12956
  },
12333
- extractMetadata: (result) => {
12334
- if (!result) {
12335
- return void 0;
12336
- }
12337
- const { output: _output, usage: _usage, ...metadata } = result;
12338
- return Object.keys(metadata).length > 0 ? metadata : void 0;
12339
- },
12957
+ extractMetadata: (result) => extractOpenAIResponsesMetadata(result),
12340
12958
  extractMetrics: (result, startTime, endEvent) => {
12341
12959
  const metrics = withCachedMetric(
12342
12960
  parseMetricsFromUsage(result?.usage),
@@ -12355,23 +12973,11 @@ var OpenAIPlugin = class extends BasePlugin {
12355
12973
  traceAsyncChannel(openAIChannels.responsesCompact, {
12356
12974
  name: "openai.responses.compact",
12357
12975
  type: "llm" /* LLM */,
12358
- extractInput: ([params]) => {
12359
- const { input, ...metadata } = params;
12360
- return {
12361
- input: processInputAttachments(input),
12362
- metadata: { ...metadata, provider: "openai" }
12363
- };
12364
- },
12976
+ extractInput: ([params]) => extractOpenAIResponsesInput(params),
12365
12977
  extractOutput: (result) => {
12366
12978
  return processImagesInOutput(result?.output);
12367
12979
  },
12368
- extractMetadata: (result) => {
12369
- if (!result) {
12370
- return void 0;
12371
- }
12372
- const { output: _output, usage: _usage, ...metadata } = result;
12373
- return Object.keys(metadata).length > 0 ? metadata : void 0;
12374
- },
12980
+ extractMetadata: (result) => extractOpenAIResponsesMetadata(result),
12375
12981
  extractMetrics: (result, startTime, endEvent) => {
12376
12982
  const metrics = withCachedMetric(
12377
12983
  parseMetricsFromUsage(result?.usage),
@@ -12427,35 +13033,6 @@ function withCachedMetric(metrics, result, endEvent) {
12427
13033
  cached
12428
13034
  };
12429
13035
  }
12430
- function processImagesInOutput(output) {
12431
- if (Array.isArray(output)) {
12432
- return output.map(processImagesInOutput);
12433
- }
12434
- if (isObject(output)) {
12435
- if (output.type === "image_generation_call" && output.result && typeof output.result === "string") {
12436
- const fileExtension = output.output_format || "png";
12437
- const contentType = `image/${fileExtension}`;
12438
- const baseFilename = output.revised_prompt && typeof output.revised_prompt === "string" ? output.revised_prompt.slice(0, 50).replace(/[^a-zA-Z0-9]/g, "_") : "generated_image";
12439
- const filename = `${baseFilename}.${fileExtension}`;
12440
- const binaryString = atob(output.result);
12441
- const bytes = new Uint8Array(binaryString.length);
12442
- for (let i = 0; i < binaryString.length; i++) {
12443
- bytes[i] = binaryString.charCodeAt(i);
12444
- }
12445
- const blob = new Blob([bytes], { type: contentType });
12446
- const attachment = new Attachment({
12447
- data: blob,
12448
- filename,
12449
- contentType
12450
- });
12451
- return {
12452
- ...output,
12453
- result: attachment
12454
- };
12455
- }
12456
- }
12457
- return output;
12458
- }
12459
13036
  function mergeLogprobTokens(existing, incoming) {
12460
13037
  if (incoming === void 0) {
12461
13038
  return existing;
@@ -32475,7 +33052,7 @@ function extractSession(event) {
32475
33052
  return isObject(candidate) && typeof candidate.prompt === "function" ? candidate : void 0;
32476
33053
  }
32477
33054
  function isPiAgent(value) {
32478
- return isObject(value) && typeof value.streamFn === "function" && typeof value.subscribe === "function";
33055
+ return isObject(value) && (typeof value.streamFunction === "function" || typeof value.streamFn === "function") && typeof value.subscribe === "function";
32479
33056
  }
32480
33057
  function promptContextStore() {
32481
33058
  piPromptContextStore ??= isomorph_default.newAsyncLocalStorage();
@@ -32485,17 +33062,21 @@ function currentPiPromptState() {
32485
33062
  return promptContextStore().getStore();
32486
33063
  }
32487
33064
  function installPiAgentInstrumentation(agent) {
33065
+ const property = typeof agent.streamFunction === "function" ? "streamFunction" : "streamFn";
33066
+ const streamFunction = agent[property];
32488
33067
  const existing = piAgentPatchStates.get(agent);
32489
- if (!existing || agent.streamFn !== existing.wrappedStreamFn) {
33068
+ if (streamFunction && (!existing || existing.property !== property || streamFunction !== existing.wrappedStreamFunction)) {
32490
33069
  const patchState = {
32491
- originalStreamFn: agent.streamFn,
32492
- wrappedStreamFn: agent.streamFn
33070
+ originalStreamFunction: streamFunction,
33071
+ property,
33072
+ wrappedStreamFunction: streamFunction
32493
33073
  };
32494
- patchState.wrappedStreamFn = makeInstrumentedStreamFn(
33074
+ patchState.wrappedStreamFunction = makeInstrumentedStreamFunction(
32495
33075
  agent,
32496
- patchState.originalStreamFn
33076
+ patchState.originalStreamFunction,
33077
+ property
32497
33078
  );
32498
- agent.streamFn = patchState.wrappedStreamFn;
33079
+ agent[property] = patchState.wrappedStreamFunction;
32499
33080
  piAgentPatchStates.set(agent, patchState);
32500
33081
  }
32501
33082
  if (piAgentEventSubscriptions.has(agent)) {
@@ -32520,15 +33101,21 @@ function installPiAgentInstrumentation(agent) {
32520
33101
  logInstrumentationError4("Pi Coding Agent event subscription", error);
32521
33102
  }
32522
33103
  }
32523
- function makeInstrumentedStreamFn(agent, originalStreamFn) {
32524
- return async function instrumentedPiStreamFn(model, context, options) {
32525
- const invokeOriginal = () => Reflect.apply(originalStreamFn, this, [model, context, options]);
33104
+ function makeInstrumentedStreamFunction(agent, originalStreamFunction, property) {
33105
+ return async function instrumentedPiStreamFunction(model, context, options) {
33106
+ const invokeOriginal = () => Reflect.apply(originalStreamFunction, this, [model, context, options]);
32526
33107
  const state = currentPiPromptState();
32527
33108
  if (!state || state.agent !== agent || state.finalized) {
32528
33109
  return invokeOriginal();
32529
33110
  }
32530
33111
  wrapPiToolExecutors(context.tools);
32531
- const llmState = await startPiLlmSpan(state, model, context, options);
33112
+ const llmState = await startPiLlmSpan(
33113
+ state,
33114
+ model,
33115
+ context,
33116
+ property,
33117
+ options
33118
+ );
32532
33119
  try {
32533
33120
  const stream = await runWithAutoInstrumentationSuppressed(invokeOriginal);
32534
33121
  return patchAssistantMessageStream(stream, state, llmState);
@@ -32565,12 +33152,12 @@ function wrapPiToolExecutors(tools) {
32565
33152
  }
32566
33153
  }
32567
33154
  }
32568
- async function startPiLlmSpan(state, model, context, options) {
33155
+ async function startPiLlmSpan(state, model, context, property, options) {
32569
33156
  const metadata = {
32570
33157
  ...extractModelMetadata2(model),
32571
33158
  ...extractStreamOptionsMetadata(options),
32572
33159
  ...extractToolMetadata(context.tools),
32573
- "pi_coding_agent.operation": "agent.streamFn"
33160
+ "pi_coding_agent.operation": `agent.${property}`
32574
33161
  };
32575
33162
  const span = startSpan(
32576
33163
  withSpanInstrumentationName(
@@ -36881,7 +37468,15 @@ var MAX_EVE_PROVIDER_CACHE_ENTRIES = 1e4;
36881
37468
  function createEveInstrumentationProvider(options = {}) {
36882
37469
  const bridge = new EveProviderBridge(options.metadata);
36883
37470
  return {
37471
+ // Eve versions before tracePolicy use capture to decide whether provider
37472
+ // events include content. Newer versions prefer tracePolicy when both are
37473
+ // present, so keep the deprecated field for backwards compatibility.
36884
37474
  capture: "content",
37475
+ tracePolicy: () => ({
37476
+ emit: true,
37477
+ recordInputs: true,
37478
+ recordOutputs: true
37479
+ }),
36885
37480
  events: {
36886
37481
  "action.completed": (event, context) => bridge.handleActionTerminal(event, context),
36887
37482
  "action.failed": (event, context) => bridge.handleActionTerminal(event, context),