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
@@ -52,7 +52,7 @@ import {
52
52
  strandsAgentSDKChannels,
53
53
  voyageAIChannels,
54
54
  withSpanInstrumentationName
55
- } from "./chunk-7FA6VP2S.mjs";
55
+ } from "./chunk-N3JKQ3D3.mjs";
56
56
 
57
57
  // src/id-gen.ts
58
58
  import { v4 as uuidv4 } from "uuid";
@@ -9030,6 +9030,15 @@ function _internalStartSpanWithInitialMerge(args) {
9030
9030
  [INITIAL_SPAN_WRITE_AS_MERGE]: true
9031
9031
  }).span;
9032
9032
  }
9033
+ function _internalStartSpanWithInitialMergeAndParentSpanIds(args) {
9034
+ return startSpanAndIsLogger(
9035
+ {
9036
+ ...args,
9037
+ [INITIAL_SPAN_WRITE_AS_MERGE]: true
9038
+ },
9039
+ { useParentSpanIdsForObjectParent: true }
9040
+ ).span;
9041
+ }
9033
9042
  function _internalStartSpanWithContext(args, context) {
9034
9043
  return startSpanAndIsLogger({
9035
9044
  ...args,
@@ -9043,7 +9052,7 @@ async function flush(options) {
9043
9052
  function setFetch(fetch2) {
9044
9053
  _internalGetGlobalState().setFetch(fetch2);
9045
9054
  }
9046
- function startSpanAndIsLogger(args) {
9055
+ function startSpanAndIsLogger(args, internalOptions) {
9047
9056
  const state = args?.state ?? _globalState;
9048
9057
  const { parentObject, propagatedState } = getSpanParentObjectAndPropagatedState({
9049
9058
  asyncFlush: args?.asyncFlush,
@@ -9057,7 +9066,7 @@ function startSpanAndIsLogger(args) {
9057
9066
  ) ? {
9058
9067
  spanId: parentObject.data.span_id,
9059
9068
  rootSpanId: parentObject.data.root_span_id
9060
- } : void 0;
9069
+ } : internalOptions?.useParentSpanIdsForObjectParent ? args?.parentSpanIds : void 0;
9061
9070
  const { parent: _ignoredParent, ...spanArgs } = args ?? {};
9062
9071
  const span = new SpanImpl({
9063
9072
  state,
@@ -18593,23 +18602,659 @@ function isRecord(value) {
18593
18602
  return typeof value === "object" && value !== null && !Array.isArray(value);
18594
18603
  }
18595
18604
 
18605
+ // src/instrumentation/plugins/openai-span-data.ts
18606
+ var OPENAI_METADATA_KEYS = [
18607
+ "model",
18608
+ "temperature",
18609
+ "top_p",
18610
+ "max_tokens",
18611
+ "frequency_penalty",
18612
+ "presence_penalty",
18613
+ "stop",
18614
+ "response_format",
18615
+ "tools",
18616
+ "tool_choice",
18617
+ "parallel_tool_calls",
18618
+ "max_tool_calls"
18619
+ ];
18620
+ function batchMetadata(params) {
18621
+ const metadata = { provider: "openai" };
18622
+ for (const key of OPENAI_METADATA_KEYS) {
18623
+ try {
18624
+ const value = Reflect.get(params, key);
18625
+ if (value !== void 0) {
18626
+ metadata[key] = value;
18627
+ }
18628
+ } catch {
18629
+ }
18630
+ }
18631
+ return metadata;
18632
+ }
18633
+ function extractOpenAIBatchInput(endpoint, params) {
18634
+ const input = endpoint === "/v1/chat/completions" ? params.messages : params.input;
18635
+ return {
18636
+ input: processInputAttachments(input),
18637
+ metadata: batchMetadata(params)
18638
+ };
18639
+ }
18640
+ function extractOpenAIChatInput(params) {
18641
+ const { messages, ...metadata } = params;
18642
+ return {
18643
+ input: processInputAttachments(messages),
18644
+ metadata: { ...metadata, provider: "openai" }
18645
+ };
18646
+ }
18647
+ function extractOpenAIResponsesInput(params) {
18648
+ const { input, ...metadata } = params;
18649
+ return {
18650
+ input: processInputAttachments(input),
18651
+ metadata: { ...metadata, provider: "openai" }
18652
+ };
18653
+ }
18654
+ function extractOpenAIResponsesMetadata(result) {
18655
+ if (!result) {
18656
+ return void 0;
18657
+ }
18658
+ const { output: _output, usage: _usage, ...metadata } = result;
18659
+ return Object.keys(metadata).length > 0 ? metadata : void 0;
18660
+ }
18661
+ function processImagesInOutput(output) {
18662
+ if (Array.isArray(output)) {
18663
+ return output.map(processImagesInOutput);
18664
+ }
18665
+ if (isObject(output) && output.type === "image_generation_call" && typeof output.result === "string" && output.result) {
18666
+ const fileExtension = output.output_format || "png";
18667
+ const contentType = `image/${fileExtension}`;
18668
+ const baseFilename = typeof output.revised_prompt === "string" ? output.revised_prompt.slice(0, 50).replace(/[^a-zA-Z0-9]/g, "_") : "generated_image";
18669
+ let binaryString;
18670
+ try {
18671
+ binaryString = atob(output.result);
18672
+ } catch {
18673
+ return output;
18674
+ }
18675
+ const bytes = new Uint8Array(binaryString.length);
18676
+ for (let i = 0; i < binaryString.length; i++) {
18677
+ bytes[i] = binaryString.charCodeAt(i);
18678
+ }
18679
+ return {
18680
+ ...output,
18681
+ result: new Attachment({
18682
+ data: new Blob([bytes], { type: contentType }),
18683
+ filename: `${baseFilename}.${fileExtension}`,
18684
+ contentType
18685
+ })
18686
+ };
18687
+ }
18688
+ return output;
18689
+ }
18690
+
18691
+ // src/instrumentation/plugins/openai-batch-instrumentation.ts
18692
+ var SUPPORTED_ENDPOINTS = /* @__PURE__ */ new Set(["/v1/chat/completions", "/v1/responses"]);
18693
+ var TERMINAL_STATUSES = /* @__PURE__ */ new Set([
18694
+ "completed",
18695
+ "failed",
18696
+ "expired",
18697
+ "cancelled"
18698
+ ]);
18699
+ var pendingBatchTraces = /* @__PURE__ */ new Map();
18700
+ function read(value, key) {
18701
+ if (!isObject(value)) {
18702
+ return void 0;
18703
+ }
18704
+ try {
18705
+ return Reflect.get(value, key);
18706
+ } catch {
18707
+ return void 0;
18708
+ }
18709
+ }
18710
+ function isBatchRecordIterable(value) {
18711
+ if (value === null || typeof value !== "object" && typeof value !== "function") {
18712
+ return false;
18713
+ }
18714
+ return typeof read(value, Symbol.iterator) === "function" || typeof read(value, Symbol.asyncIterator) === "function";
18715
+ }
18716
+ function validCustomId(value) {
18717
+ return typeof value === "string" && value.length > 0 && value.length <= 64;
18718
+ }
18719
+ function logBatchInstrumentationError(context, error) {
18720
+ debugLogger.debug(`OpenAI Batch instrumentation ${context}:`, error);
18721
+ }
18722
+ async function exportParent(parent) {
18723
+ if ("toStr" in parent && typeof parent.toStr === "function") {
18724
+ return parent.toStr();
18725
+ }
18726
+ if ("export" in parent && typeof parent.export === "function") {
18727
+ return await parent.export();
18728
+ }
18729
+ return void 0;
18730
+ }
18731
+ async function deterministicDigest(namespace, ...parts) {
18732
+ const encoded = new TextEncoder().encode(
18733
+ [namespace, ...parts].map((part) => `${part.length}:${part}`).join("\0")
18734
+ );
18735
+ return new Uint8Array(
18736
+ await globalThis.crypto.subtle.digest("SHA-256", encoded)
18737
+ );
18738
+ }
18739
+ function digestHex(bytes, length) {
18740
+ return Array.from(bytes.slice(0, length)).map((byte) => byte.toString(16).padStart(2, "0")).join("");
18741
+ }
18742
+ function digestUuid(bytes) {
18743
+ const hex = digestHex(bytes, 16);
18744
+ return `${hex.slice(0, 8)}-${hex.slice(8, 12)}-${hex.slice(12, 16)}-${hex.slice(16, 20)}-${hex.slice(20)}`;
18745
+ }
18746
+ async function batchSpanIds(inputFileId) {
18747
+ const [row, span, root] = await Promise.all([
18748
+ deterministicDigest("openai:batch:row", inputFileId),
18749
+ deterministicDigest("openai:batch:span", inputFileId),
18750
+ deterministicDigest("openai:batch:root", inputFileId)
18751
+ ]);
18752
+ return {
18753
+ rowId: digestUuid(row),
18754
+ spanId: digestHex(span, 8),
18755
+ rootSpanId: digestHex(root, 16)
18756
+ };
18757
+ }
18758
+ async function childSpanIds(inputFileId, customId) {
18759
+ const [row, span] = await Promise.all([
18760
+ deterministicDigest("openai:batch:child:row", inputFileId, customId),
18761
+ deterministicDigest("openai:batch:child:span", inputFileId, customId)
18762
+ ]);
18763
+ return { rowId: digestUuid(row), spanId: digestHex(span, 8) };
18764
+ }
18765
+ async function startBatchSpan(context) {
18766
+ const ids = await batchSpanIds(context.inputFileId);
18767
+ const parent = SpanComponentsV4.fromStr(context.parent);
18768
+ const hasParentSpan = Boolean(
18769
+ parent.data.row_id && parent.data.span_id && parent.data.root_span_id
18770
+ );
18771
+ return withCurrent(
18772
+ NOOP_SPAN,
18773
+ () => _internalStartSpanWithInitialMergeAndParentSpanIds(
18774
+ withSpanInstrumentationName(
18775
+ {
18776
+ name: "openai.batch",
18777
+ type: "task" /* TASK */,
18778
+ parent: context.parent,
18779
+ ...!hasParentSpan ? {
18780
+ parentSpanIds: {
18781
+ parentSpanIds: [],
18782
+ rootSpanId: ids.rootSpanId
18783
+ }
18784
+ } : {},
18785
+ spanId: ids.spanId,
18786
+ startTime: context.taskStartTime,
18787
+ event: {
18788
+ id: ids.rowId,
18789
+ metadata: {
18790
+ endpoint: context.endpoint,
18791
+ input_file_id: context.inputFileId,
18792
+ provider: "openai"
18793
+ }
18794
+ }
18795
+ },
18796
+ INSTRUMENTATION_NAMES.OPENAI
18797
+ )
18798
+ )
18799
+ );
18800
+ }
18801
+ async function startBatchChild(context, taskParent, input) {
18802
+ const ids = await childSpanIds(context.inputFileId, input.customId);
18803
+ return withCurrent(
18804
+ NOOP_SPAN,
18805
+ () => _internalStartSpanWithInitialMerge(
18806
+ withSpanInstrumentationName(
18807
+ {
18808
+ name: context.endpoint === "/v1/chat/completions" ? "Chat Completion" : "openai.responses.create",
18809
+ type: "llm" /* LLM */,
18810
+ parent: taskParent,
18811
+ spanId: ids.spanId,
18812
+ startTime: context.childStartTime,
18813
+ event: {
18814
+ id: ids.rowId,
18815
+ ...input.spanData?.input !== void 0 ? { input: input.spanData.input } : {},
18816
+ metadata: {
18817
+ ...input.spanData?.metadata,
18818
+ custom_id: input.customId,
18819
+ provider: "openai"
18820
+ }
18821
+ }
18822
+ },
18823
+ INSTRUMENTATION_NAMES.OPENAI
18824
+ )
18825
+ )
18826
+ );
18827
+ }
18828
+ async function* jsonlRecords(file, onIssue = () => {
18829
+ }) {
18830
+ const resolvedFile = await file;
18831
+ if (typeof resolvedFile === "string") {
18832
+ for (const line of resolvedFile.split("\n")) {
18833
+ if (!line.trim()) {
18834
+ continue;
18835
+ }
18836
+ try {
18837
+ yield JSON.parse(line.replace(/\r$/, ""));
18838
+ } catch (error) {
18839
+ logBatchInstrumentationError("skipped malformed JSONL", error);
18840
+ onIssue(new Error("OpenAI Batch file contains malformed JSONL"));
18841
+ }
18842
+ }
18843
+ return;
18844
+ }
18845
+ const body = read(resolvedFile, "body");
18846
+ const getReader = read(body, "getReader");
18847
+ if (isObject(body) && typeof getReader === "function") {
18848
+ let reader;
18849
+ try {
18850
+ reader = Reflect.apply(getReader, body, []);
18851
+ const decoder = new TextDecoder();
18852
+ let pending = "";
18853
+ while (true) {
18854
+ const readChunk = read(reader, "read");
18855
+ if (typeof readChunk !== "function") {
18856
+ throw new Error("Response body stream has no read method");
18857
+ }
18858
+ const chunk = await Reflect.apply(readChunk, reader, []);
18859
+ if (!isObject(chunk)) {
18860
+ throw new Error("Response body stream returned an invalid chunk");
18861
+ }
18862
+ if (chunk.done === true) {
18863
+ pending += decoder.decode();
18864
+ break;
18865
+ }
18866
+ if (!(chunk.value instanceof Uint8Array)) {
18867
+ throw new Error("Response body stream returned a non-byte chunk");
18868
+ }
18869
+ pending += decoder.decode(chunk.value, { stream: true });
18870
+ let newline = pending.indexOf("\n");
18871
+ while (newline !== -1) {
18872
+ const line = pending.slice(0, newline).replace(/\r$/, "");
18873
+ pending = pending.slice(newline + 1);
18874
+ if (line.trim()) {
18875
+ try {
18876
+ yield JSON.parse(line);
18877
+ } catch (error) {
18878
+ logBatchInstrumentationError("skipped malformed JSONL", error);
18879
+ onIssue(new Error("OpenAI Batch file contains malformed JSONL"));
18880
+ }
18881
+ }
18882
+ newline = pending.indexOf("\n");
18883
+ }
18884
+ }
18885
+ if (pending.trim()) {
18886
+ try {
18887
+ yield JSON.parse(pending.replace(/\r$/, ""));
18888
+ } catch (error) {
18889
+ logBatchInstrumentationError("skipped malformed JSONL", error);
18890
+ onIssue(new Error("OpenAI Batch file contains malformed JSONL"));
18891
+ }
18892
+ }
18893
+ } catch (error) {
18894
+ logBatchInstrumentationError("could not read JSONL response body", error);
18895
+ onIssue(new Error("OpenAI Batch response body could not be read"));
18896
+ } finally {
18897
+ const releaseLock = read(reader, "releaseLock");
18898
+ if (typeof releaseLock === "function") {
18899
+ try {
18900
+ Reflect.apply(releaseLock, reader, []);
18901
+ } catch (error) {
18902
+ logBatchInstrumentationError(
18903
+ "could not release stream reader",
18904
+ error
18905
+ );
18906
+ }
18907
+ }
18908
+ }
18909
+ return;
18910
+ }
18911
+ if (isBatchRecordIterable(resolvedFile)) {
18912
+ for await (const record of resolvedFile) {
18913
+ yield record;
18914
+ }
18915
+ return;
18916
+ }
18917
+ logBatchInstrumentationError("skipped invalid JSONL source", resolvedFile);
18918
+ onIssue(new Error("OpenAI Batch file source is invalid"));
18919
+ }
18920
+ async function readBatchInputs(file) {
18921
+ const inputs = /* @__PURE__ */ new Map();
18922
+ const issues = [];
18923
+ let endpoint;
18924
+ try {
18925
+ for await (const value of jsonlRecords(
18926
+ file,
18927
+ (issue) => issues.push(issue)
18928
+ )) {
18929
+ const customId = read(value, "custom_id");
18930
+ const url = read(value, "url");
18931
+ const body = read(value, "body");
18932
+ if (!validCustomId(customId) || inputs.has(customId) || read(value, "method") !== "POST" || typeof url !== "string" || !SUPPORTED_ENDPOINTS.has(url) || endpoint !== void 0 && endpoint !== url || !isObject(body)) {
18933
+ issues.push(new Error("OpenAI Batch input contains an invalid record"));
18934
+ continue;
18935
+ }
18936
+ endpoint = url;
18937
+ let spanData;
18938
+ try {
18939
+ spanData = extractOpenAIBatchInput(url, body);
18940
+ } catch (error) {
18941
+ logBatchInstrumentationError("could not extract batch input", error);
18942
+ }
18943
+ inputs.set(customId, { customId, spanData });
18944
+ }
18945
+ } catch (error) {
18946
+ logBatchInstrumentationError("could not process input file", error);
18947
+ issues.push(new Error("OpenAI Batch input file could not be processed"));
18948
+ }
18949
+ if (!endpoint || inputs.size === 0) {
18950
+ issues.push(new Error("OpenAI Batch input contains no supported records"));
18951
+ }
18952
+ return { endpoint, inputs, issues };
18953
+ }
18954
+ async function writePendingSpans(trace, endTime) {
18955
+ const task = await startBatchSpan(trace.context);
18956
+ const taskParent = await task.export();
18957
+ for (const input of trace.inputs.values()) {
18958
+ try {
18959
+ const child = await startBatchChild(trace.context, taskParent, input);
18960
+ if (endTime !== void 0) {
18961
+ child.end({ endTime });
18962
+ }
18963
+ } catch (error) {
18964
+ logBatchInstrumentationError("could not write batch request span", error);
18965
+ }
18966
+ }
18967
+ if (endTime !== void 0) {
18968
+ if (trace.status && trace.status !== "completed") {
18969
+ task.log({ error: new Error(`OpenAI Batch ${trace.status}`) });
18970
+ }
18971
+ task.end({ endTime });
18972
+ }
18973
+ }
18974
+ var interceptOpenAIFilesCreateTraced = async (target, thisArg, args) => {
18975
+ const startTime = getCurrentUnixTimestamp();
18976
+ const inputPromise = readBatchInputs(args[0].inputFileContent);
18977
+ const parentPromise = exportParent(args[0].parent);
18978
+ const file = await Reflect.apply(target, thisArg, args);
18979
+ try {
18980
+ const inputFileId = read(file, "id");
18981
+ const [inputData, exportedParent2] = await Promise.all([
18982
+ inputPromise,
18983
+ parentPromise
18984
+ ]);
18985
+ if (typeof inputFileId !== "string" || !exportedParent2 || !inputData.endpoint || inputData.issues.length > 0) {
18986
+ if (inputData.issues[0]) {
18987
+ logBatchInstrumentationError(
18988
+ "skipped invalid input file",
18989
+ inputData.issues[0]
18990
+ );
18991
+ }
18992
+ return file;
18993
+ }
18994
+ const trace = {
18995
+ context: {
18996
+ inputFileId,
18997
+ endpoint: inputData.endpoint,
18998
+ parent: exportedParent2,
18999
+ taskStartTime: startTime,
19000
+ childStartTime: startTime
19001
+ },
19002
+ inputs: inputData.inputs
19003
+ };
19004
+ pendingBatchTraces.set(inputFileId, trace);
19005
+ await writePendingSpans(trace);
19006
+ } catch (error) {
19007
+ logBatchInstrumentationError("could not start batch spans", error);
19008
+ }
19009
+ return file;
19010
+ };
19011
+ function validTimestamp(value) {
19012
+ return typeof value === "number" && Number.isFinite(value) && value >= 0;
19013
+ }
19014
+ function terminalEndTime(batch, startTime) {
19015
+ let timestamp;
19016
+ switch (batch.status) {
19017
+ case "completed":
19018
+ timestamp = batch.completed_at;
19019
+ break;
19020
+ case "failed":
19021
+ timestamp = batch.failed_at;
19022
+ break;
19023
+ case "expired":
19024
+ timestamp = batch.expired_at;
19025
+ break;
19026
+ default:
19027
+ timestamp = batch.cancelled_at;
19028
+ }
19029
+ return validTimestamp(timestamp) ? Math.max(timestamp, startTime) : Math.max(getCurrentUnixTimestamp(), startTime);
19030
+ }
19031
+ async function updateBatchTimestamps(batch) {
19032
+ const trace = pendingBatchTraces.get(batch.input_file_id);
19033
+ if (!trace || trace.context.endpoint !== batch.endpoint) {
19034
+ return;
19035
+ }
19036
+ if (validTimestamp(batch.created_at)) {
19037
+ trace.context.taskStartTime = batch.created_at;
19038
+ }
19039
+ trace.context.childStartTime = validTimestamp(batch.in_progress_at) ? Math.max(batch.in_progress_at, trace.context.taskStartTime) : trace.context.taskStartTime;
19040
+ trace.status = batch.status;
19041
+ if (TERMINAL_STATUSES.has(batch.status)) {
19042
+ trace.endTime = terminalEndTime(batch, trace.context.childStartTime);
19043
+ }
19044
+ await writePendingSpans(trace, trace.endTime);
19045
+ }
19046
+ var interceptOpenAIBatchesRetrieveTraced = (target, thisArg, args) => Promise.resolve(Reflect.apply(target, thisArg, args)).then(async (batch) => {
19047
+ try {
19048
+ await updateBatchTimestamps(batch);
19049
+ } catch (error) {
19050
+ logBatchInstrumentationError("could not update batch timestamps", error);
19051
+ }
19052
+ return batch;
19053
+ });
19054
+ function errorFromResult(result) {
19055
+ const error = read(result.value, "error");
19056
+ if (isObject(error)) {
19057
+ const message = read(error, "message");
19058
+ return new Error(
19059
+ typeof message === "string" ? message : "OpenAI Batch request failed"
19060
+ );
19061
+ }
19062
+ const response = read(result.value, "response");
19063
+ const statusCode = read(response, "status_code");
19064
+ if (result.source === "error" || typeof statusCode === "number" && (statusCode < 200 || statusCode >= 300)) {
19065
+ const message = read(read(read(response, "body"), "error"), "message");
19066
+ return new Error(
19067
+ typeof message === "string" ? message : "OpenAI Batch request failed"
19068
+ );
19069
+ }
19070
+ return void 0;
19071
+ }
19072
+ async function completeBatchResult(context, taskParent, endTime, input, result) {
19073
+ const child = await startBatchChild(context, taskParent, input);
19074
+ try {
19075
+ const resultError = errorFromResult(result);
19076
+ const responseBody = read(read(result.value, "response"), "body");
19077
+ if (resultError) {
19078
+ child.log({ error: resultError });
19079
+ } else if (isObject(responseBody)) {
19080
+ const model = read(responseBody, "model");
19081
+ child.log({
19082
+ output: context.endpoint === "/v1/chat/completions" ? read(responseBody, "choices") : processImagesInOutput(read(responseBody, "output")),
19083
+ ...typeof model === "string" ? { metadata: { model } } : {},
19084
+ metrics: parseMetricsFromUsage(read(responseBody, "usage"))
19085
+ });
19086
+ } else {
19087
+ child.log({ error: new Error("OpenAI Batch response body is missing") });
19088
+ }
19089
+ } catch (error) {
19090
+ child.log({ error });
19091
+ } finally {
19092
+ child.end({ endTime });
19093
+ }
19094
+ }
19095
+ async function completeResultFile({
19096
+ context,
19097
+ endTime,
19098
+ file,
19099
+ inputs,
19100
+ issues,
19101
+ seen,
19102
+ source,
19103
+ taskParent
19104
+ }) {
19105
+ if (file === void 0) {
19106
+ return;
19107
+ }
19108
+ for await (const value of jsonlRecords(file, (issue) => issues.push(issue))) {
19109
+ const customId = read(value, "custom_id");
19110
+ if (!validCustomId(customId) || !isObject(value)) {
19111
+ issues.push(
19112
+ new Error("OpenAI Batch result is missing a valid custom_id")
19113
+ );
19114
+ continue;
19115
+ }
19116
+ if (seen.has(customId)) {
19117
+ issues.push(new Error("OpenAI Batch result contains a duplicate"));
19118
+ continue;
19119
+ }
19120
+ const input = inputs.get(customId);
19121
+ if (!input) {
19122
+ issues.push(
19123
+ new Error("OpenAI Batch result does not match an input record")
19124
+ );
19125
+ continue;
19126
+ }
19127
+ seen.add(customId);
19128
+ await completeBatchResult(context, taskParent, endTime, input, {
19129
+ value,
19130
+ source
19131
+ });
19132
+ }
19133
+ }
19134
+ function sameInputs(first, second) {
19135
+ return first.size === second.size && [...first.keys()].every((customId) => second.has(customId));
19136
+ }
19137
+ async function contextForCompletion(inputFileId, inputData) {
19138
+ if (!inputData.endpoint || inputData.issues.length > 0) {
19139
+ return void 0;
19140
+ }
19141
+ const existing = pendingBatchTraces.get(inputFileId);
19142
+ if (existing) {
19143
+ if (existing.context.endpoint !== inputData.endpoint || !sameInputs(existing.inputs, inputData.inputs)) {
19144
+ return void 0;
19145
+ }
19146
+ return { ...existing, inputs: inputData.inputs };
19147
+ }
19148
+ const parent = await exportParent(getSpanParentObject());
19149
+ if (!parent) {
19150
+ return void 0;
19151
+ }
19152
+ const startTime = getCurrentUnixTimestamp();
19153
+ return {
19154
+ context: {
19155
+ inputFileId,
19156
+ endpoint: inputData.endpoint,
19157
+ parent,
19158
+ taskStartTime: startTime,
19159
+ childStartTime: startTime
19160
+ },
19161
+ inputs: inputData.inputs
19162
+ };
19163
+ }
19164
+ async function completeBatch(args) {
19165
+ const inputData = await readBatchInputs(args.inputFileContent);
19166
+ const trace = await contextForCompletion(args.inputFileId, inputData);
19167
+ if (!trace) {
19168
+ logBatchInstrumentationError(
19169
+ "left batch spans pending",
19170
+ inputData.issues[0] ?? new Error("OpenAI Batch input does not match")
19171
+ );
19172
+ return;
19173
+ }
19174
+ const endTime = trace.endTime ?? getCurrentUnixTimestamp();
19175
+ const task = await startBatchSpan(trace.context);
19176
+ const taskParent = await task.export();
19177
+ const issues = [];
19178
+ const seen = /* @__PURE__ */ new Set();
19179
+ await Promise.all([
19180
+ completeResultFile({
19181
+ context: trace.context,
19182
+ endTime,
19183
+ file: args.outputFileContent,
19184
+ inputs: trace.inputs,
19185
+ issues,
19186
+ seen,
19187
+ source: "output",
19188
+ taskParent
19189
+ }),
19190
+ completeResultFile({
19191
+ context: trace.context,
19192
+ endTime,
19193
+ file: args.errorFileContent,
19194
+ inputs: trace.inputs,
19195
+ issues,
19196
+ seen,
19197
+ source: "error",
19198
+ taskParent
19199
+ })
19200
+ ]);
19201
+ if (issues.length > 0) {
19202
+ logBatchInstrumentationError("left batch spans pending", issues[0]);
19203
+ return;
19204
+ }
19205
+ const missing = [...trace.inputs.values()].filter(
19206
+ ({ customId }) => !seen.has(customId)
19207
+ );
19208
+ if (!trace.status || trace.status === "completed") {
19209
+ if (missing.length > 0) {
19210
+ logBatchInstrumentationError(
19211
+ "left batch spans pending",
19212
+ new Error("OpenAI Batch result files are incomplete")
19213
+ );
19214
+ return;
19215
+ }
19216
+ } else {
19217
+ for (const input of missing) {
19218
+ const child = await startBatchChild(trace.context, taskParent, input);
19219
+ child.log({ error: new Error(`OpenAI Batch ${trace.status}`) });
19220
+ child.end({ endTime });
19221
+ }
19222
+ task.log({ error: new Error(`OpenAI Batch ${trace.status}`) });
19223
+ }
19224
+ task.end({ endTime });
19225
+ pendingBatchTraces.delete(args.inputFileId);
19226
+ }
19227
+ var interceptOpenAIBatchTraceComplete = (target, thisArg, args) => Promise.resolve(Reflect.apply(target, thisArg, args)).then(async (result) => {
19228
+ try {
19229
+ await completeBatch(args[0]);
19230
+ } catch (error) {
19231
+ logBatchInstrumentationError("could not complete batch", error);
19232
+ }
19233
+ return result;
19234
+ });
19235
+
18596
19236
  // src/instrumentation/plugins/openai-plugin.ts
18597
19237
  var OpenAIPlugin = class extends BasePlugin {
18598
19238
  constructor() {
18599
19239
  super();
18600
19240
  }
18601
19241
  onEnable() {
19242
+ this.unsubscribers.push(
19243
+ openAIChannels.filesCreateTraced.intercept(
19244
+ interceptOpenAIFilesCreateTraced
19245
+ ),
19246
+ openAIChannels.batchesRetrieveTraced.intercept(
19247
+ interceptOpenAIBatchesRetrieveTraced
19248
+ ),
19249
+ openAIChannels.batchesCompleteTrace.intercept(
19250
+ interceptOpenAIBatchTraceComplete
19251
+ )
19252
+ );
18602
19253
  this.unsubscribers.push(
18603
19254
  traceStreamingChannel(openAIChannels.chatCompletionsCreate, {
18604
19255
  name: "Chat Completion",
18605
19256
  type: "llm" /* LLM */,
18606
- extractInput: ([params]) => {
18607
- const { messages, ...metadata } = params;
18608
- return {
18609
- input: processInputAttachments(messages),
18610
- metadata: { ...metadata, provider: "openai" }
18611
- };
18612
- },
19257
+ extractInput: ([params]) => extractOpenAIChatInput(params),
18613
19258
  extractOutput: (result) => {
18614
19259
  return result?.choices;
18615
19260
  },
@@ -18655,13 +19300,7 @@ var OpenAIPlugin = class extends BasePlugin {
18655
19300
  traceStreamingChannel(openAIChannels.betaChatCompletionsParse, {
18656
19301
  name: "Chat Completion",
18657
19302
  type: "llm" /* LLM */,
18658
- extractInput: ([params]) => {
18659
- const { messages, ...metadata } = params;
18660
- return {
18661
- input: processInputAttachments(messages),
18662
- metadata: { ...metadata, provider: "openai" }
18663
- };
18664
- },
19303
+ extractInput: ([params]) => extractOpenAIChatInput(params),
18665
19304
  extractOutput: (result) => {
18666
19305
  return result?.choices;
18667
19306
  },
@@ -18683,13 +19322,7 @@ var OpenAIPlugin = class extends BasePlugin {
18683
19322
  traceSyncStreamChannel(openAIChannels.betaChatCompletionsStream, {
18684
19323
  name: "Chat Completion",
18685
19324
  type: "llm" /* LLM */,
18686
- extractInput: ([params]) => {
18687
- const { messages, ...metadata } = params;
18688
- return {
18689
- input: processInputAttachments(messages),
18690
- metadata: { ...metadata, provider: "openai" }
18691
- };
18692
- }
19325
+ extractInput: ([params]) => extractOpenAIChatInput(params)
18693
19326
  })
18694
19327
  );
18695
19328
  this.unsubscribers.push(
@@ -18719,23 +19352,11 @@ var OpenAIPlugin = class extends BasePlugin {
18719
19352
  traceStreamingChannel(openAIChannels.responsesCreate, {
18720
19353
  name: "openai.responses.create",
18721
19354
  type: "llm" /* LLM */,
18722
- extractInput: ([params]) => {
18723
- const { input, ...metadata } = params;
18724
- return {
18725
- input: processInputAttachments(input),
18726
- metadata: { ...metadata, provider: "openai" }
18727
- };
18728
- },
19355
+ extractInput: ([params]) => extractOpenAIResponsesInput(params),
18729
19356
  extractOutput: (result) => {
18730
19357
  return processImagesInOutput(result?.output);
18731
19358
  },
18732
- extractMetadata: (result) => {
18733
- if (!result) {
18734
- return void 0;
18735
- }
18736
- const { output: _output, usage: _usage, ...metadata } = result;
18737
- return Object.keys(metadata).length > 0 ? metadata : void 0;
18738
- },
19359
+ extractMetadata: (result) => extractOpenAIResponsesMetadata(result),
18739
19360
  extractMetrics: (result, startTime, endEvent) => {
18740
19361
  const metrics = withCachedMetric(
18741
19362
  parseMetricsFromUsage(result?.usage),
@@ -18754,13 +19375,7 @@ var OpenAIPlugin = class extends BasePlugin {
18754
19375
  traceSyncStreamChannel(openAIChannels.responsesStream, {
18755
19376
  name: "openai.responses.create",
18756
19377
  type: "llm" /* LLM */,
18757
- extractInput: ([params]) => {
18758
- const { input, ...metadata } = params;
18759
- return {
18760
- input: processInputAttachments(input),
18761
- metadata: { ...metadata, provider: "openai" }
18762
- };
18763
- },
19378
+ extractInput: ([params]) => extractOpenAIResponsesInput(params),
18764
19379
  extractFromEvent: (event) => {
18765
19380
  if (event.type !== "response.completed" || !event.response) {
18766
19381
  return {};
@@ -18783,23 +19398,11 @@ var OpenAIPlugin = class extends BasePlugin {
18783
19398
  traceStreamingChannel(openAIChannels.responsesParse, {
18784
19399
  name: "openai.responses.parse",
18785
19400
  type: "llm" /* LLM */,
18786
- extractInput: ([params]) => {
18787
- const { input, ...metadata } = params;
18788
- return {
18789
- input: processInputAttachments(input),
18790
- metadata: { ...metadata, provider: "openai" }
18791
- };
18792
- },
19401
+ extractInput: ([params]) => extractOpenAIResponsesInput(params),
18793
19402
  extractOutput: (result) => {
18794
19403
  return processImagesInOutput(result?.output);
18795
19404
  },
18796
- extractMetadata: (result) => {
18797
- if (!result) {
18798
- return void 0;
18799
- }
18800
- const { output: _output, usage: _usage, ...metadata } = result;
18801
- return Object.keys(metadata).length > 0 ? metadata : void 0;
18802
- },
19405
+ extractMetadata: (result) => extractOpenAIResponsesMetadata(result),
18803
19406
  extractMetrics: (result, startTime, endEvent) => {
18804
19407
  const metrics = withCachedMetric(
18805
19408
  parseMetricsFromUsage(result?.usage),
@@ -18818,23 +19421,11 @@ var OpenAIPlugin = class extends BasePlugin {
18818
19421
  traceAsyncChannel(openAIChannels.responsesCompact, {
18819
19422
  name: "openai.responses.compact",
18820
19423
  type: "llm" /* LLM */,
18821
- extractInput: ([params]) => {
18822
- const { input, ...metadata } = params;
18823
- return {
18824
- input: processInputAttachments(input),
18825
- metadata: { ...metadata, provider: "openai" }
18826
- };
18827
- },
19424
+ extractInput: ([params]) => extractOpenAIResponsesInput(params),
18828
19425
  extractOutput: (result) => {
18829
19426
  return processImagesInOutput(result?.output);
18830
19427
  },
18831
- extractMetadata: (result) => {
18832
- if (!result) {
18833
- return void 0;
18834
- }
18835
- const { output: _output, usage: _usage, ...metadata } = result;
18836
- return Object.keys(metadata).length > 0 ? metadata : void 0;
18837
- },
19428
+ extractMetadata: (result) => extractOpenAIResponsesMetadata(result),
18838
19429
  extractMetrics: (result, startTime, endEvent) => {
18839
19430
  const metrics = withCachedMetric(
18840
19431
  parseMetricsFromUsage(result?.usage),
@@ -18890,35 +19481,6 @@ function withCachedMetric(metrics, result, endEvent) {
18890
19481
  cached
18891
19482
  };
18892
19483
  }
18893
- function processImagesInOutput(output) {
18894
- if (Array.isArray(output)) {
18895
- return output.map(processImagesInOutput);
18896
- }
18897
- if (isObject(output)) {
18898
- if (output.type === "image_generation_call" && output.result && typeof output.result === "string") {
18899
- const fileExtension = output.output_format || "png";
18900
- const contentType = `image/${fileExtension}`;
18901
- const baseFilename = output.revised_prompt && typeof output.revised_prompt === "string" ? output.revised_prompt.slice(0, 50).replace(/[^a-zA-Z0-9]/g, "_") : "generated_image";
18902
- const filename = `${baseFilename}.${fileExtension}`;
18903
- const binaryString = atob(output.result);
18904
- const bytes = new Uint8Array(binaryString.length);
18905
- for (let i = 0; i < binaryString.length; i++) {
18906
- bytes[i] = binaryString.charCodeAt(i);
18907
- }
18908
- const blob = new Blob([bytes], { type: contentType });
18909
- const attachment = new Attachment({
18910
- data: blob,
18911
- filename,
18912
- contentType
18913
- });
18914
- return {
18915
- ...output,
18916
- result: attachment
18917
- };
18918
- }
18919
- }
18920
- return output;
18921
- }
18922
19484
  function mergeLogprobTokens(existing, incoming) {
18923
19485
  if (incoming === void 0) {
18924
19486
  return existing;
@@ -32442,7 +33004,7 @@ function extractSession(event) {
32442
33004
  return isObject(candidate) && typeof candidate.prompt === "function" ? candidate : void 0;
32443
33005
  }
32444
33006
  function isPiAgent(value) {
32445
- return isObject(value) && typeof value.streamFn === "function" && typeof value.subscribe === "function";
33007
+ return isObject(value) && (typeof value.streamFunction === "function" || typeof value.streamFn === "function") && typeof value.subscribe === "function";
32446
33008
  }
32447
33009
  function promptContextStore() {
32448
33010
  piPromptContextStore ??= isomorph_default.newAsyncLocalStorage();
@@ -32452,17 +33014,21 @@ function currentPiPromptState() {
32452
33014
  return promptContextStore().getStore();
32453
33015
  }
32454
33016
  function installPiAgentInstrumentation(agent) {
33017
+ const property = typeof agent.streamFunction === "function" ? "streamFunction" : "streamFn";
33018
+ const streamFunction = agent[property];
32455
33019
  const existing = piAgentPatchStates.get(agent);
32456
- if (!existing || agent.streamFn !== existing.wrappedStreamFn) {
33020
+ if (streamFunction && (!existing || existing.property !== property || streamFunction !== existing.wrappedStreamFunction)) {
32457
33021
  const patchState = {
32458
- originalStreamFn: agent.streamFn,
32459
- wrappedStreamFn: agent.streamFn
33022
+ originalStreamFunction: streamFunction,
33023
+ property,
33024
+ wrappedStreamFunction: streamFunction
32460
33025
  };
32461
- patchState.wrappedStreamFn = makeInstrumentedStreamFn(
33026
+ patchState.wrappedStreamFunction = makeInstrumentedStreamFunction(
32462
33027
  agent,
32463
- patchState.originalStreamFn
33028
+ patchState.originalStreamFunction,
33029
+ property
32464
33030
  );
32465
- agent.streamFn = patchState.wrappedStreamFn;
33031
+ agent[property] = patchState.wrappedStreamFunction;
32466
33032
  piAgentPatchStates.set(agent, patchState);
32467
33033
  }
32468
33034
  if (piAgentEventSubscriptions.has(agent)) {
@@ -32487,15 +33053,21 @@ function installPiAgentInstrumentation(agent) {
32487
33053
  logInstrumentationError4("Pi Coding Agent event subscription", error);
32488
33054
  }
32489
33055
  }
32490
- function makeInstrumentedStreamFn(agent, originalStreamFn) {
32491
- return async function instrumentedPiStreamFn(model, context, options) {
32492
- const invokeOriginal = () => Reflect.apply(originalStreamFn, this, [model, context, options]);
33056
+ function makeInstrumentedStreamFunction(agent, originalStreamFunction, property) {
33057
+ return async function instrumentedPiStreamFunction(model, context, options) {
33058
+ const invokeOriginal = () => Reflect.apply(originalStreamFunction, this, [model, context, options]);
32493
33059
  const state = currentPiPromptState();
32494
33060
  if (!state || state.agent !== agent || state.finalized) {
32495
33061
  return invokeOriginal();
32496
33062
  }
32497
33063
  wrapPiToolExecutors(context.tools);
32498
- const llmState = await startPiLlmSpan(state, model, context, options);
33064
+ const llmState = await startPiLlmSpan(
33065
+ state,
33066
+ model,
33067
+ context,
33068
+ property,
33069
+ options
33070
+ );
32499
33071
  try {
32500
33072
  const stream = await runWithAutoInstrumentationSuppressed(invokeOriginal);
32501
33073
  return patchAssistantMessageStream(stream, state, llmState);
@@ -32532,12 +33104,12 @@ function wrapPiToolExecutors(tools) {
32532
33104
  }
32533
33105
  }
32534
33106
  }
32535
- async function startPiLlmSpan(state, model, context, options) {
33107
+ async function startPiLlmSpan(state, model, context, property, options) {
32536
33108
  const metadata = {
32537
33109
  ...extractModelMetadata2(model),
32538
33110
  ...extractStreamOptionsMetadata(options),
32539
33111
  ...extractToolMetadata(context.tools),
32540
- "pi_coding_agent.operation": "agent.streamFn"
33112
+ "pi_coding_agent.operation": `agent.${property}`
32541
33113
  };
32542
33114
  const span = startSpan(
32543
33115
  withSpanInstrumentationName(