@yourgpt/llm-sdk 2.6.0-beta.2 → 2.6.0-beta.3

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.
package/dist/index.mjs CHANGED
@@ -1,6 +1,56 @@
1
1
  import { Hono } from 'hono';
2
2
  import { cors } from 'hono/cors';
3
3
 
4
+ // src/core/openai-responses.ts
5
+ function responseReplayMetadata(metadata) {
6
+ const response = metadata?.openai?.response;
7
+ if (!response) return metadata;
8
+ const output = response.output.map((item) => {
9
+ const fields = ["type", "id", "status"];
10
+ switch (item.type) {
11
+ case "reasoning":
12
+ fields.push("encrypted_content");
13
+ break;
14
+ case "message":
15
+ fields.push("role", "phase");
16
+ break;
17
+ case "function_call":
18
+ fields.push("call_id", "name", "namespace", "arguments");
19
+ break;
20
+ case "tool_search_call":
21
+ fields.push("call_id", "execution", "arguments");
22
+ break;
23
+ case "tool_search_output":
24
+ fields.push("call_id", "tools");
25
+ break;
26
+ case "web_search_call":
27
+ fields.push("action");
28
+ break;
29
+ default:
30
+ return stripItemDiagnostics(item);
31
+ }
32
+ const replay = Object.fromEntries(
33
+ fields.filter((key) => item[key] !== void 0).map((key) => [key, item[key]])
34
+ );
35
+ if (item.type === "reasoning") replay.summary = [];
36
+ if (item.type === "message")
37
+ replay.content = Array.isArray(item.content) ? item.content.flatMap(
38
+ (part) => part?.type === "output_text" && typeof part.text === "string" ? [{ type: "output_text", text: part.text, annotations: [] }] : part?.type === "refusal" && typeof part.refusal === "string" ? [{ type: "refusal", refusal: part.refusal }] : []
39
+ ) : [];
40
+ return stripItemDiagnostics(replay);
41
+ });
42
+ return { openai: { response: { output, refusals: [] } } };
43
+ }
44
+ function stripItemDiagnostics(item) {
45
+ const {
46
+ usage: _usage,
47
+ logprobs: _logprobs,
48
+ metadata: _metadata,
49
+ ...state
50
+ } = item;
51
+ return state;
52
+ }
53
+
4
54
  // src/core/tool-output.ts
5
55
  var imageTypes = /* @__PURE__ */ new Set([
6
56
  "image/png",
@@ -238,10 +288,11 @@ async function generateText(params) {
238
288
  signal
239
289
  });
240
290
  throwIfAborted(signal);
291
+ const toolCalls = result.toolCalls ?? [];
241
292
  const stepToolResults = [];
242
- if (result.toolCalls && result.toolCalls.length > 0 && tools) {
243
- allToolCalls.push(...result.toolCalls);
244
- for (const call of result.toolCalls) {
293
+ if (toolCalls.length > 0 && tools) {
294
+ allToolCalls.push(...toolCalls);
295
+ for (const call of toolCalls) {
245
296
  throwIfAborted(signal);
246
297
  const toolDef = tools[call.name];
247
298
  if (!toolDef) {
@@ -286,7 +337,7 @@ async function generateText(params) {
286
337
  }
287
338
  steps.push({
288
339
  text: result.text,
289
- toolCalls: result.toolCalls,
340
+ toolCalls,
290
341
  toolResults: stepToolResults,
291
342
  finishReason: result.finishReason,
292
343
  usage: result.usage,
@@ -295,11 +346,11 @@ async function generateText(params) {
295
346
  const assistantMessage = {
296
347
  role: "assistant",
297
348
  content: result.text || null,
298
- ...result.toolCalls.length ? { toolCalls: result.toolCalls } : {},
299
- ...result.providerMetadata ? { providerMetadata: result.providerMetadata } : {}
349
+ ...toolCalls.length ? { toolCalls } : {},
350
+ ...result.providerMetadata ? { providerMetadata: responseReplayMetadata(result.providerMetadata) } : {}
300
351
  };
301
352
  messages = [...messages, assistantMessage];
302
- if (!result.toolCalls || result.toolCalls.length === 0) break;
353
+ if (toolCalls.length === 0) break;
303
354
  for (const tr of stepToolResults) {
304
355
  const toolMessage = {
305
356
  role: "tool",
@@ -450,7 +501,7 @@ async function streamText(params) {
450
501
  role: "assistant",
451
502
  content: stepText || null,
452
503
  ...toolCalls.length ? { toolCalls } : {},
453
- ...stepProviderMetadata ? { providerMetadata: stepProviderMetadata } : {}
504
+ ...stepProviderMetadata ? { providerMetadata: responseReplayMetadata(stepProviderMetadata) } : {}
454
505
  }
455
506
  ];
456
507
  if (toolCalls.length === 0 || !tools) break;
@@ -782,17 +833,32 @@ function createTextStreamHeaders() {
782
833
  };
783
834
  }
784
835
  function formatSSEData(event) {
785
- return `data: ${JSON.stringify(event)}
836
+ const safeEvent = { ...event };
837
+ if ("providerMetadata" in safeEvent)
838
+ safeEvent.providerMetadata = responseReplayMetadata(
839
+ safeEvent.providerMetadata
840
+ );
841
+ if (safeEvent.type === "done" && safeEvent.messages)
842
+ safeEvent.messages = safeEvent.messages.map((message) => ({
843
+ ...message,
844
+ ...message.providerMetadata ? { providerMetadata: responseReplayMetadata(message.providerMetadata) } : {}
845
+ }));
846
+ return `data: ${JSON.stringify(safeEvent)}
786
847
 
787
848
  `;
788
849
  }
789
- function createEventStream(generator) {
850
+ function createEventStream(generator, options) {
790
851
  const encoder = new TextEncoder();
791
852
  return new ReadableStream({
792
853
  async start(controller) {
793
854
  try {
794
855
  for await (const event of generator) {
795
- const data = formatSSEData(event);
856
+ let clientEvent = event;
857
+ if (event.type === "done" && !options?.includeUsage) {
858
+ const { usage: _usage, ...withoutUsage } = event;
859
+ clientEvent = withoutUsage;
860
+ }
861
+ const data = formatSSEData(clientEvent);
796
862
  controller.enqueue(encoder.encode(data));
797
863
  }
798
864
  } catch (error) {
@@ -933,7 +999,9 @@ var StreamResult = class {
933
999
  ...createSSEHeaders(),
934
1000
  ...options?.headers
935
1001
  };
936
- return new Response(createEventStream(this.generator), { headers });
1002
+ return new Response(createEventStream(this.generator, options), {
1003
+ headers
1004
+ });
937
1005
  }
938
1006
  /**
939
1007
  * Alias for toResponse() - returns SSE Response
@@ -2376,7 +2444,10 @@ var Runtime = class {
2376
2444
  yield event;
2377
2445
  break;
2378
2446
  case "message:end":
2379
- yield event;
2447
+ yield {
2448
+ ...event,
2449
+ providerMetadata: responseReplayMetadata(event.providerMetadata)
2450
+ };
2380
2451
  break;
2381
2452
  case "message:delta":
2382
2453
  accumulatedText += event.content;
@@ -2498,7 +2569,9 @@ var Runtime = class {
2498
2569
  return;
2499
2570
  // Exit on error
2500
2571
  case "done":
2501
- adapterProviderMetadata = event.providerMetadata;
2572
+ adapterProviderMetadata = responseReplayMetadata(
2573
+ event.providerMetadata
2574
+ );
2502
2575
  if (event.usage) {
2503
2576
  adapterUsage = event.usage;
2504
2577
  }
@@ -2769,7 +2842,7 @@ var Runtime = class {
2769
2842
  }
2770
2843
  yield {
2771
2844
  type: "message:end",
2772
- providerMetadata: result.providerMetadata
2845
+ providerMetadata: responseReplayMetadata(result.providerMetadata)
2773
2846
  };
2774
2847
  if (result.toolCalls.length > 0) {
2775
2848
  const serverToolCalls = [];
@@ -2870,7 +2943,11 @@ var Runtime = class {
2870
2943
  const assistantMessage = {
2871
2944
  role: "assistant",
2872
2945
  content: result.content || null,
2873
- ...result.providerMetadata ? { providerMetadata: result.providerMetadata } : {},
2946
+ ...result.providerMetadata ? {
2947
+ providerMetadata: responseReplayMetadata(
2948
+ result.providerMetadata
2949
+ )
2950
+ } : {},
2874
2951
  tool_calls: result.toolCalls.map((tc) => ({
2875
2952
  id: tc.id,
2876
2953
  type: "function",
@@ -2900,7 +2977,11 @@ var Runtime = class {
2900
2977
  const assistantWithToolCalls = {
2901
2978
  role: "assistant",
2902
2979
  content: result.content || null,
2903
- ...result.providerMetadata ? { providerMetadata: result.providerMetadata } : {},
2980
+ ...result.providerMetadata ? {
2981
+ providerMetadata: responseReplayMetadata(
2982
+ result.providerMetadata
2983
+ )
2984
+ } : {},
2904
2985
  tool_calls: result.toolCalls.map((tc) => ({
2905
2986
  id: tc.id,
2906
2987
  type: "function",
@@ -2932,7 +3013,11 @@ var Runtime = class {
2932
3013
  const assistantMessage = {
2933
3014
  role: "assistant",
2934
3015
  content: result.content || null,
2935
- ...result.providerMetadata ? { providerMetadata: result.providerMetadata } : {},
3016
+ ...result.providerMetadata ? {
3017
+ providerMetadata: responseReplayMetadata(
3018
+ result.providerMetadata
3019
+ )
3020
+ } : {},
2936
3021
  tool_calls: clientToolCalls.map((tc) => ({
2937
3022
  id: tc.id,
2938
3023
  type: "function",
@@ -2962,7 +3047,11 @@ var Runtime = class {
2962
3047
  newMessages.push({
2963
3048
  role: "assistant",
2964
3049
  content: result.content,
2965
- ...result.providerMetadata ? { providerMetadata: result.providerMetadata } : {}
3050
+ ...result.providerMetadata ? {
3051
+ providerMetadata: responseReplayMetadata(
3052
+ result.providerMetadata
3053
+ )
3054
+ } : {}
2966
3055
  });
2967
3056
  }
2968
3057
  if (debug) {
@@ -294,21 +294,14 @@ function messageToAnthropicContent(message) {
294
294
  return blocks;
295
295
  }
296
296
  function extractAnthropicSystemPrompt(messages, systemPrompt) {
297
- const parts = systemPrompt ? [systemPrompt] : [];
297
+ const explicit = typeof systemPrompt === "string" ? systemPrompt : "";
298
+ const parts = explicit ? [explicit] : [];
298
299
  for (const message of messages) {
299
300
  if (message.role !== "system") continue;
300
- if (typeof message.content === "string") {
301
- if (message.content) parts.push(message.content);
302
- } else if (Array.isArray(message.content)) {
303
- for (const block of message.content) {
304
- if (block?.type !== "text" || typeof block.text !== "string") {
305
- throw new TypeError("Anthropic system messages must contain text.");
306
- }
307
- if (block.text) parts.push(block.text);
308
- }
309
- } else {
310
- throw new TypeError("Anthropic system messages must contain text.");
311
- }
301
+ const text = typeof message.content === "string" ? message.content : Array.isArray(message.content) ? message.content.filter(
302
+ (block) => block?.type === "text" && typeof block.text === "string"
303
+ ).map((block) => block.text).filter(Boolean).join("\n") : "";
304
+ if (text && text !== explicit) parts.push(text);
312
305
  }
313
306
  return parts.join("\n");
314
307
  }
@@ -292,21 +292,14 @@ function messageToAnthropicContent(message) {
292
292
  return blocks;
293
293
  }
294
294
  function extractAnthropicSystemPrompt(messages, systemPrompt) {
295
- const parts = systemPrompt ? [systemPrompt] : [];
295
+ const explicit = typeof systemPrompt === "string" ? systemPrompt : "";
296
+ const parts = explicit ? [explicit] : [];
296
297
  for (const message of messages) {
297
298
  if (message.role !== "system") continue;
298
- if (typeof message.content === "string") {
299
- if (message.content) parts.push(message.content);
300
- } else if (Array.isArray(message.content)) {
301
- for (const block of message.content) {
302
- if (block?.type !== "text" || typeof block.text !== "string") {
303
- throw new TypeError("Anthropic system messages must contain text.");
304
- }
305
- if (block.text) parts.push(block.text);
306
- }
307
- } else {
308
- throw new TypeError("Anthropic system messages must contain text.");
309
- }
299
+ const text = typeof message.content === "string" ? message.content : Array.isArray(message.content) ? message.content.filter(
300
+ (block) => block?.type === "text" && typeof block.text === "string"
301
+ ).map((block) => block.text).filter(Boolean).join("\n") : "";
302
+ if (text && text !== explicit) parts.push(text);
310
303
  }
311
304
  return parts.join("\n");
312
305
  }
@@ -635,6 +635,54 @@ function responseMetadata(response) {
635
635
  }
636
636
  };
637
637
  }
638
+ function responseReplayMetadata(metadata) {
639
+ const response = metadata?.openai?.response;
640
+ if (!response) return metadata;
641
+ const output = response.output.map((item) => {
642
+ const fields = ["type", "id", "status"];
643
+ switch (item.type) {
644
+ case "reasoning":
645
+ fields.push("encrypted_content");
646
+ break;
647
+ case "message":
648
+ fields.push("role", "phase");
649
+ break;
650
+ case "function_call":
651
+ fields.push("call_id", "name", "namespace", "arguments");
652
+ break;
653
+ case "tool_search_call":
654
+ fields.push("call_id", "execution", "arguments");
655
+ break;
656
+ case "tool_search_output":
657
+ fields.push("call_id", "tools");
658
+ break;
659
+ case "web_search_call":
660
+ fields.push("action");
661
+ break;
662
+ default:
663
+ return stripItemDiagnostics(item);
664
+ }
665
+ const replay = Object.fromEntries(
666
+ fields.filter((key) => item[key] !== void 0).map((key) => [key, item[key]])
667
+ );
668
+ if (item.type === "reasoning") replay.summary = [];
669
+ if (item.type === "message")
670
+ replay.content = Array.isArray(item.content) ? item.content.flatMap(
671
+ (part) => part?.type === "output_text" && typeof part.text === "string" ? [{ type: "output_text", text: part.text, annotations: [] }] : part?.type === "refusal" && typeof part.refusal === "string" ? [{ type: "refusal", refusal: part.refusal }] : []
672
+ ) : [];
673
+ return stripItemDiagnostics(replay);
674
+ });
675
+ return { openai: { response: { output, refusals: [] } } };
676
+ }
677
+ function stripItemDiagnostics(item) {
678
+ const {
679
+ usage: _usage,
680
+ logprobs: _logprobs,
681
+ metadata: _metadata,
682
+ ...state
683
+ } = item;
684
+ return state;
685
+ }
638
686
  function getResponseReplay(metadata) {
639
687
  const response = metadata?.openai?.response;
640
688
  if (response === void 0) return void 0;
@@ -643,10 +691,17 @@ function getResponseReplay(metadata) {
643
691
  "Invalid OpenAI Responses replay metadata: output must contain typed items"
644
692
  );
645
693
  }
646
- return response.output;
694
+ return responseReplayMetadata(metadata)?.openai?.response?.output;
647
695
  }
648
696
 
649
697
  // src/core/provider-error.ts
698
+ function invalidProviderResponse(message) {
699
+ return Object.assign(new Error(message), {
700
+ name: "InvalidProviderResponseError",
701
+ status: 502,
702
+ code: "INVALID_PROVIDER_RESPONSE"
703
+ });
704
+ }
650
705
  function providerErrorEvent(error, code) {
651
706
  const details = error ?? {};
652
707
  const event = {
@@ -823,7 +878,7 @@ var OpenAIAdapter = class _OpenAIAdapter {
823
878
  formatRawMessages(messages, preserveProviderMetadata = false) {
824
879
  return messages.map((message) => {
825
880
  const { attachments, providerMetadata, modelOutput, ...rest } = message;
826
- let content = message.role === "tool" && !preserveProviderMetadata ? textToolOutput(modelOutput, String(message.content ?? "")) : message.content;
881
+ let content = message.role === "tool" && !preserveProviderMetadata && modelOutput !== void 0 ? textToolOutput(modelOutput, "") : message.content;
827
882
  if (preserveProviderMetadata && modelOutput)
828
883
  rest.modelOutput = modelOutput;
829
884
  if (message.role === "assistant" && Array.isArray(message.tool_calls) && message.tool_calls.length > 0 && content === "") {
@@ -926,17 +981,32 @@ var OpenAIAdapter = class _OpenAIAdapter {
926
981
  const seenCallIds = /* @__PURE__ */ new Set();
927
982
  const toolCalls = Array.isArray(response?.output) ? response.output.filter((item) => item?.type === "function_call").map((item) => {
928
983
  if (typeof item.call_id !== "string" || !item.call_id || typeof item.name !== "string" || !item.name || typeof item.arguments !== "string" || item.status && item.status !== "completed") {
929
- throw new Error("Responses contains an incomplete function call");
984
+ throw invalidProviderResponse(
985
+ "Responses contains an incomplete function call"
986
+ );
930
987
  }
931
988
  if (seenCallIds.has(item.call_id))
932
- throw new Error("Responses contains duplicate function call IDs");
989
+ throw invalidProviderResponse(
990
+ "Responses contains duplicate function call IDs"
991
+ );
933
992
  seenCallIds.add(item.call_id);
934
- const args = JSON.parse(item.arguments);
993
+ let args;
994
+ try {
995
+ args = JSON.parse(item.arguments);
996
+ } catch {
997
+ throw invalidProviderResponse(
998
+ "Responses function arguments must be valid JSON"
999
+ );
1000
+ }
935
1001
  if (!args || typeof args !== "object" || Array.isArray(args))
936
- throw new Error(
1002
+ throw invalidProviderResponse(
937
1003
  "Responses function arguments must be a JSON object"
938
1004
  );
939
- return { id: item.call_id, name: item.name, args };
1005
+ return {
1006
+ id: item.call_id,
1007
+ name: item.name,
1008
+ args
1009
+ };
940
1010
  }) : [];
941
1011
  return {
942
1012
  content,
@@ -1179,7 +1249,9 @@ var OpenAIAdapter = class _OpenAIAdapter {
1179
1249
  const uniqueCalls = /* @__PURE__ */ new Map();
1180
1250
  for (const call of toolCalls.values()) {
1181
1251
  if (!call.id || !call.name)
1182
- throw new Error("Responses stream contains an incomplete tool call");
1252
+ throw invalidProviderResponse(
1253
+ "Responses stream contains an incomplete tool call"
1254
+ );
1183
1255
  uniqueCalls.set(call.id, call);
1184
1256
  }
1185
1257
  toolCalls.clear();
@@ -633,6 +633,54 @@ function responseMetadata(response) {
633
633
  }
634
634
  };
635
635
  }
636
+ function responseReplayMetadata(metadata) {
637
+ const response = metadata?.openai?.response;
638
+ if (!response) return metadata;
639
+ const output = response.output.map((item) => {
640
+ const fields = ["type", "id", "status"];
641
+ switch (item.type) {
642
+ case "reasoning":
643
+ fields.push("encrypted_content");
644
+ break;
645
+ case "message":
646
+ fields.push("role", "phase");
647
+ break;
648
+ case "function_call":
649
+ fields.push("call_id", "name", "namespace", "arguments");
650
+ break;
651
+ case "tool_search_call":
652
+ fields.push("call_id", "execution", "arguments");
653
+ break;
654
+ case "tool_search_output":
655
+ fields.push("call_id", "tools");
656
+ break;
657
+ case "web_search_call":
658
+ fields.push("action");
659
+ break;
660
+ default:
661
+ return stripItemDiagnostics(item);
662
+ }
663
+ const replay = Object.fromEntries(
664
+ fields.filter((key) => item[key] !== void 0).map((key) => [key, item[key]])
665
+ );
666
+ if (item.type === "reasoning") replay.summary = [];
667
+ if (item.type === "message")
668
+ replay.content = Array.isArray(item.content) ? item.content.flatMap(
669
+ (part) => part?.type === "output_text" && typeof part.text === "string" ? [{ type: "output_text", text: part.text, annotations: [] }] : part?.type === "refusal" && typeof part.refusal === "string" ? [{ type: "refusal", refusal: part.refusal }] : []
670
+ ) : [];
671
+ return stripItemDiagnostics(replay);
672
+ });
673
+ return { openai: { response: { output, refusals: [] } } };
674
+ }
675
+ function stripItemDiagnostics(item) {
676
+ const {
677
+ usage: _usage,
678
+ logprobs: _logprobs,
679
+ metadata: _metadata,
680
+ ...state
681
+ } = item;
682
+ return state;
683
+ }
636
684
  function getResponseReplay(metadata) {
637
685
  const response = metadata?.openai?.response;
638
686
  if (response === void 0) return void 0;
@@ -641,10 +689,17 @@ function getResponseReplay(metadata) {
641
689
  "Invalid OpenAI Responses replay metadata: output must contain typed items"
642
690
  );
643
691
  }
644
- return response.output;
692
+ return responseReplayMetadata(metadata)?.openai?.response?.output;
645
693
  }
646
694
 
647
695
  // src/core/provider-error.ts
696
+ function invalidProviderResponse(message) {
697
+ return Object.assign(new Error(message), {
698
+ name: "InvalidProviderResponseError",
699
+ status: 502,
700
+ code: "INVALID_PROVIDER_RESPONSE"
701
+ });
702
+ }
648
703
  function providerErrorEvent(error, code) {
649
704
  const details = error ?? {};
650
705
  const event = {
@@ -821,7 +876,7 @@ var OpenAIAdapter = class _OpenAIAdapter {
821
876
  formatRawMessages(messages, preserveProviderMetadata = false) {
822
877
  return messages.map((message) => {
823
878
  const { attachments, providerMetadata, modelOutput, ...rest } = message;
824
- let content = message.role === "tool" && !preserveProviderMetadata ? textToolOutput(modelOutput, String(message.content ?? "")) : message.content;
879
+ let content = message.role === "tool" && !preserveProviderMetadata && modelOutput !== void 0 ? textToolOutput(modelOutput, "") : message.content;
825
880
  if (preserveProviderMetadata && modelOutput)
826
881
  rest.modelOutput = modelOutput;
827
882
  if (message.role === "assistant" && Array.isArray(message.tool_calls) && message.tool_calls.length > 0 && content === "") {
@@ -924,17 +979,32 @@ var OpenAIAdapter = class _OpenAIAdapter {
924
979
  const seenCallIds = /* @__PURE__ */ new Set();
925
980
  const toolCalls = Array.isArray(response?.output) ? response.output.filter((item) => item?.type === "function_call").map((item) => {
926
981
  if (typeof item.call_id !== "string" || !item.call_id || typeof item.name !== "string" || !item.name || typeof item.arguments !== "string" || item.status && item.status !== "completed") {
927
- throw new Error("Responses contains an incomplete function call");
982
+ throw invalidProviderResponse(
983
+ "Responses contains an incomplete function call"
984
+ );
928
985
  }
929
986
  if (seenCallIds.has(item.call_id))
930
- throw new Error("Responses contains duplicate function call IDs");
987
+ throw invalidProviderResponse(
988
+ "Responses contains duplicate function call IDs"
989
+ );
931
990
  seenCallIds.add(item.call_id);
932
- const args = JSON.parse(item.arguments);
991
+ let args;
992
+ try {
993
+ args = JSON.parse(item.arguments);
994
+ } catch {
995
+ throw invalidProviderResponse(
996
+ "Responses function arguments must be valid JSON"
997
+ );
998
+ }
933
999
  if (!args || typeof args !== "object" || Array.isArray(args))
934
- throw new Error(
1000
+ throw invalidProviderResponse(
935
1001
  "Responses function arguments must be a JSON object"
936
1002
  );
937
- return { id: item.call_id, name: item.name, args };
1003
+ return {
1004
+ id: item.call_id,
1005
+ name: item.name,
1006
+ args
1007
+ };
938
1008
  }) : [];
939
1009
  return {
940
1010
  content,
@@ -1177,7 +1247,9 @@ var OpenAIAdapter = class _OpenAIAdapter {
1177
1247
  const uniqueCalls = /* @__PURE__ */ new Map();
1178
1248
  for (const call of toolCalls.values()) {
1179
1249
  if (!call.id || !call.name)
1180
- throw new Error("Responses stream contains an incomplete tool call");
1250
+ throw invalidProviderResponse(
1251
+ "Responses stream contains an incomplete tool call"
1252
+ );
1181
1253
  uniqueCalls.set(call.id, call);
1182
1254
  }
1183
1255
  toolCalls.clear();
@@ -715,6 +715,54 @@ function responseMetadata(response) {
715
715
  }
716
716
  };
717
717
  }
718
+ function responseReplayMetadata(metadata) {
719
+ const response = metadata?.openai?.response;
720
+ if (!response) return metadata;
721
+ const output = response.output.map((item) => {
722
+ const fields = ["type", "id", "status"];
723
+ switch (item.type) {
724
+ case "reasoning":
725
+ fields.push("encrypted_content");
726
+ break;
727
+ case "message":
728
+ fields.push("role", "phase");
729
+ break;
730
+ case "function_call":
731
+ fields.push("call_id", "name", "namespace", "arguments");
732
+ break;
733
+ case "tool_search_call":
734
+ fields.push("call_id", "execution", "arguments");
735
+ break;
736
+ case "tool_search_output":
737
+ fields.push("call_id", "tools");
738
+ break;
739
+ case "web_search_call":
740
+ fields.push("action");
741
+ break;
742
+ default:
743
+ return stripItemDiagnostics(item);
744
+ }
745
+ const replay = Object.fromEntries(
746
+ fields.filter((key) => item[key] !== void 0).map((key) => [key, item[key]])
747
+ );
748
+ if (item.type === "reasoning") replay.summary = [];
749
+ if (item.type === "message")
750
+ replay.content = Array.isArray(item.content) ? item.content.flatMap(
751
+ (part) => part?.type === "output_text" && typeof part.text === "string" ? [{ type: "output_text", text: part.text, annotations: [] }] : part?.type === "refusal" && typeof part.refusal === "string" ? [{ type: "refusal", refusal: part.refusal }] : []
752
+ ) : [];
753
+ return stripItemDiagnostics(replay);
754
+ });
755
+ return { openai: { response: { output, refusals: [] } } };
756
+ }
757
+ function stripItemDiagnostics(item) {
758
+ const {
759
+ usage: _usage,
760
+ logprobs: _logprobs,
761
+ metadata: _metadata,
762
+ ...state
763
+ } = item;
764
+ return state;
765
+ }
718
766
  function getResponseReplay(metadata) {
719
767
  const response = metadata?.openai?.response;
720
768
  if (response === void 0) return void 0;
@@ -723,10 +771,17 @@ function getResponseReplay(metadata) {
723
771
  "Invalid OpenAI Responses replay metadata: output must contain typed items"
724
772
  );
725
773
  }
726
- return response.output;
774
+ return responseReplayMetadata(metadata)?.openai?.response?.output;
727
775
  }
728
776
 
729
777
  // src/core/provider-error.ts
778
+ function invalidProviderResponse(message) {
779
+ return Object.assign(new Error(message), {
780
+ name: "InvalidProviderResponseError",
781
+ status: 502,
782
+ code: "INVALID_PROVIDER_RESPONSE"
783
+ });
784
+ }
730
785
  function providerErrorEvent(error, code) {
731
786
  const details = error ?? {};
732
787
  const event = {
@@ -903,7 +958,7 @@ var OpenAIAdapter = class _OpenAIAdapter {
903
958
  formatRawMessages(messages, preserveProviderMetadata = false) {
904
959
  return messages.map((message) => {
905
960
  const { attachments, providerMetadata, modelOutput, ...rest } = message;
906
- let content = message.role === "tool" && !preserveProviderMetadata ? textToolOutput(modelOutput, String(message.content ?? "")) : message.content;
961
+ let content = message.role === "tool" && !preserveProviderMetadata && modelOutput !== void 0 ? textToolOutput(modelOutput, "") : message.content;
907
962
  if (preserveProviderMetadata && modelOutput)
908
963
  rest.modelOutput = modelOutput;
909
964
  if (message.role === "assistant" && Array.isArray(message.tool_calls) && message.tool_calls.length > 0 && content === "") {
@@ -1006,17 +1061,32 @@ var OpenAIAdapter = class _OpenAIAdapter {
1006
1061
  const seenCallIds = /* @__PURE__ */ new Set();
1007
1062
  const toolCalls = Array.isArray(response?.output) ? response.output.filter((item) => item?.type === "function_call").map((item) => {
1008
1063
  if (typeof item.call_id !== "string" || !item.call_id || typeof item.name !== "string" || !item.name || typeof item.arguments !== "string" || item.status && item.status !== "completed") {
1009
- throw new Error("Responses contains an incomplete function call");
1064
+ throw invalidProviderResponse(
1065
+ "Responses contains an incomplete function call"
1066
+ );
1010
1067
  }
1011
1068
  if (seenCallIds.has(item.call_id))
1012
- throw new Error("Responses contains duplicate function call IDs");
1069
+ throw invalidProviderResponse(
1070
+ "Responses contains duplicate function call IDs"
1071
+ );
1013
1072
  seenCallIds.add(item.call_id);
1014
- const args = JSON.parse(item.arguments);
1073
+ let args;
1074
+ try {
1075
+ args = JSON.parse(item.arguments);
1076
+ } catch {
1077
+ throw invalidProviderResponse(
1078
+ "Responses function arguments must be valid JSON"
1079
+ );
1080
+ }
1015
1081
  if (!args || typeof args !== "object" || Array.isArray(args))
1016
- throw new Error(
1082
+ throw invalidProviderResponse(
1017
1083
  "Responses function arguments must be a JSON object"
1018
1084
  );
1019
- return { id: item.call_id, name: item.name, args };
1085
+ return {
1086
+ id: item.call_id,
1087
+ name: item.name,
1088
+ args
1089
+ };
1020
1090
  }) : [];
1021
1091
  return {
1022
1092
  content,
@@ -1259,7 +1329,9 @@ var OpenAIAdapter = class _OpenAIAdapter {
1259
1329
  const uniqueCalls = /* @__PURE__ */ new Map();
1260
1330
  for (const call of toolCalls.values()) {
1261
1331
  if (!call.id || !call.name)
1262
- throw new Error("Responses stream contains an incomplete tool call");
1332
+ throw invalidProviderResponse(
1333
+ "Responses stream contains an incomplete tool call"
1334
+ );
1263
1335
  uniqueCalls.set(call.id, call);
1264
1336
  }
1265
1337
  toolCalls.clear();