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

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 (61) hide show
  1. package/README.md +112 -0
  2. package/dist/adapters/index.d.mts +5 -5
  3. package/dist/adapters/index.d.ts +5 -5
  4. package/dist/adapters/index.js +372 -39
  5. package/dist/adapters/index.mjs +372 -39
  6. package/dist/audio-BBSwnjo8.d.mts +92 -0
  7. package/dist/audio-BBSwnjo8.d.ts +92 -0
  8. package/dist/{base-CkeT7u-O.d.mts → base-BICaQaRS.d.mts} +7 -3
  9. package/dist/{base-BdTpUAWI.d.ts → base-CpRPB8u8.d.ts} +7 -3
  10. package/dist/fallback/index.d.mts +4 -4
  11. package/dist/fallback/index.d.ts +4 -4
  12. package/dist/index.d.mts +17 -35
  13. package/dist/index.d.ts +17 -35
  14. package/dist/index.js +229 -60
  15. package/dist/index.mjs +227 -61
  16. package/dist/providers/anthropic/index.d.mts +3 -3
  17. package/dist/providers/anthropic/index.d.ts +3 -3
  18. package/dist/providers/anthropic/index.js +123 -7
  19. package/dist/providers/anthropic/index.mjs +123 -7
  20. package/dist/providers/azure/index.d.mts +3 -3
  21. package/dist/providers/azure/index.d.ts +3 -3
  22. package/dist/providers/azure/index.js +101 -4
  23. package/dist/providers/azure/index.mjs +101 -4
  24. package/dist/providers/fireworks/index.d.mts +3 -3
  25. package/dist/providers/fireworks/index.d.ts +3 -3
  26. package/dist/providers/fireworks/index.js +321 -31
  27. package/dist/providers/fireworks/index.mjs +321 -31
  28. package/dist/providers/google/index.d.mts +3 -3
  29. package/dist/providers/google/index.d.ts +3 -3
  30. package/dist/providers/google/index.js +321 -31
  31. package/dist/providers/google/index.mjs +321 -31
  32. package/dist/providers/ollama/index.d.mts +4 -4
  33. package/dist/providers/ollama/index.d.ts +4 -4
  34. package/dist/providers/ollama/index.js +87 -2
  35. package/dist/providers/ollama/index.mjs +87 -2
  36. package/dist/providers/openai/index.d.mts +25 -6
  37. package/dist/providers/openai/index.d.ts +25 -6
  38. package/dist/providers/openai/index.js +750 -40
  39. package/dist/providers/openai/index.mjs +749 -41
  40. package/dist/providers/openrouter/index.d.mts +3 -3
  41. package/dist/providers/openrouter/index.d.ts +3 -3
  42. package/dist/providers/openrouter/index.js +321 -31
  43. package/dist/providers/openrouter/index.mjs +321 -31
  44. package/dist/providers/togetherai/index.d.mts +3 -3
  45. package/dist/providers/togetherai/index.d.ts +3 -3
  46. package/dist/providers/togetherai/index.js +321 -31
  47. package/dist/providers/togetherai/index.mjs +321 -31
  48. package/dist/providers/xai/index.d.mts +3 -3
  49. package/dist/providers/xai/index.d.ts +3 -3
  50. package/dist/providers/xai/index.js +321 -31
  51. package/dist/providers/xai/index.mjs +321 -31
  52. package/dist/{types-BWRYIvl_.d.mts → types-C3RgRbEC.d.mts} +1 -1
  53. package/dist/{types-CpXcT-tY.d.ts → types-DCFqkJAy.d.ts} +1 -1
  54. package/dist/{types-NOswlhm9.d.mts → types-DHQEMFHA.d.mts} +3 -3
  55. package/dist/{types-BW3csYo5.d.mts → types-Dg1Bcg1k.d.mts} +106 -3
  56. package/dist/{types-BW3csYo5.d.ts → types-Dg1Bcg1k.d.ts} +106 -3
  57. package/dist/{types-DA54bSmX.d.ts → types-Dikj3En4.d.ts} +3 -3
  58. package/dist/yourgpt/index.d.mts +1 -1
  59. package/dist/yourgpt/index.d.ts +1 -1
  60. package/docs/multimodal-and-audio.md +170 -0
  61. package/package.json +3 -2
@@ -1,5 +1,108 @@
1
1
  'use strict';
2
2
 
3
+ // src/core/tool-output.ts
4
+ var imageTypes = /* @__PURE__ */ new Set([
5
+ "image/png",
6
+ "image/jpeg",
7
+ "image/webp",
8
+ "image/gif"
9
+ ]);
10
+ function normalizeToolOutput(value) {
11
+ const output = value;
12
+ if (output?.type !== "content" || !Array.isArray(output.content) || !output.content.length) {
13
+ throw new Error("Tool model output must contain a nonempty content array");
14
+ }
15
+ return {
16
+ type: "content",
17
+ content: output.content.map((part) => {
18
+ if (part?.type === "text" && typeof part.text === "string")
19
+ return { type: "text", text: part.text };
20
+ if (part?.type !== "image")
21
+ throw new Error("Unsupported tool output content type");
22
+ const sources = ["data", "url", "fileId"].filter((key) => key in part);
23
+ if (sources.length !== 1)
24
+ throw new Error(
25
+ "Tool image requires exactly one of data, url, or fileId"
26
+ );
27
+ if ("fileId" in part) {
28
+ if (part.provider !== "openai" || typeof part.fileId !== "string" || !part.fileId.trim())
29
+ throw new Error(
30
+ "Tool image file references must name the openai provider and fileId"
31
+ );
32
+ return { type: "image", fileId: part.fileId, provider: "openai" };
33
+ }
34
+ if ("url" in part) {
35
+ let url;
36
+ try {
37
+ url = new URL(part.url);
38
+ } catch {
39
+ throw new Error("Invalid tool image URL");
40
+ }
41
+ if (!["https:", "http:"].includes(url.protocol) || url.username || url.password)
42
+ throw new Error(
43
+ "Tool image URL must use HTTP(S) without credentials"
44
+ );
45
+ return { type: "image", url: url.href };
46
+ }
47
+ if (!("data" in part) || !imageTypes.has(part.mediaType))
48
+ throw new Error("Unsupported tool image media type");
49
+ let data = part.data instanceof Uint8Array ? Buffer.from(part.data).toString("base64") : part.data;
50
+ if (typeof data !== "string")
51
+ throw new Error("Tool image data must be base64 or bytes");
52
+ if (data.startsWith("data:")) {
53
+ const match = /^data:([^;,]+);base64,(.+)$/.exec(data);
54
+ if (!match || match[1] !== part.mediaType)
55
+ throw new Error(
56
+ "Tool image data URL media type does not match mediaType"
57
+ );
58
+ data = match[2];
59
+ }
60
+ if (!data.length || data.length % 4 !== 0 || !/^[A-Za-z0-9+/]+={0,2}$/.test(data))
61
+ throw new Error("Invalid tool image base64 data");
62
+ if (Buffer.byteLength(data, "base64") > 20 * 1024 * 1024)
63
+ throw new Error("Tool image exceeds 20 MiB");
64
+ if (Buffer.from(data, "base64").toString("base64") !== data)
65
+ throw new Error("Invalid tool image base64 data");
66
+ return { type: "image", data, mediaType: part.mediaType };
67
+ })
68
+ };
69
+ }
70
+ function toolOutputForProvider(value, provider) {
71
+ const output = normalizeToolOutput(value);
72
+ return output.content.map((part) => {
73
+ if (part.type === "text")
74
+ return {
75
+ type: provider === "responses" ? "input_text" : "text",
76
+ text: part.text
77
+ };
78
+ if (provider === "unsupported")
79
+ throw new Error(
80
+ "Image tool results require OpenAI Responses or Anthropic; this endpoint is not supported"
81
+ );
82
+ if ("fileId" in part) {
83
+ if (provider !== "responses")
84
+ throw new Error(
85
+ "OpenAI image file references cannot be sent to another provider"
86
+ );
87
+ return { type: "input_image", file_id: part.fileId, detail: "auto" };
88
+ }
89
+ if (provider === "responses")
90
+ return {
91
+ type: "input_image",
92
+ image_url: "url" in part ? part.url : `data:${part.mediaType};base64,${part.data}`,
93
+ detail: "auto"
94
+ };
95
+ return {
96
+ type: "image",
97
+ source: "url" in part ? { type: "url", url: part.url } : { type: "base64", media_type: part.mediaType, data: part.data }
98
+ };
99
+ });
100
+ }
101
+ function textToolOutput(value, fallback) {
102
+ if (value === void 0) return fallback;
103
+ return toolOutputForProvider(value, "unsupported").map((part) => part.text).join("\n");
104
+ }
105
+
3
106
  // src/core/openai-parameters.ts
4
107
  function capabilities(model) {
5
108
  const gpt = /^gpt-(\d+)(?:\.(\d+))?(?:-(.+))?$/i.exec(model);
@@ -241,7 +344,7 @@ function messageToOpenAIContent(message) {
241
344
  }
242
345
  return blocks;
243
346
  }
244
- function formatMessagesForOpenAI(messages, systemPrompt) {
347
+ function formatMessagesForOpenAI(messages, systemPrompt, preserveProviderMetadata = false) {
245
348
  const formatted = [];
246
349
  if (systemPrompt) {
247
350
  formatted.push({ role: "system", content: systemPrompt });
@@ -259,7 +362,8 @@ function formatMessagesForOpenAI(messages, systemPrompt) {
259
362
  const assistantMsg = {
260
363
  role: "assistant",
261
364
  // Gemini/xAI (OpenAI-compatible) reject content: "" on assistant messages with tool_calls
262
- content: hasToolCalls ? msg.content || null : msg.content
365
+ content: hasToolCalls ? msg.content || null : msg.content,
366
+ ...preserveProviderMetadata && msg.providerMetadata ? { providerMetadata: msg.providerMetadata } : {}
263
367
  };
264
368
  if (hasToolCalls) {
265
369
  assistantMsg.tool_calls = msg.tool_calls;
@@ -268,7 +372,8 @@ function formatMessagesForOpenAI(messages, systemPrompt) {
268
372
  } else if (msg.role === "tool" && msg.tool_call_id) {
269
373
  formatted.push({
270
374
  role: "tool",
271
- content: msg.content ?? "",
375
+ content: preserveProviderMetadata ? msg.content ?? "" : textToolOutput(msg.modelOutput, msg.content ?? ""),
376
+ ...preserveProviderMetadata && msg.modelOutput ? { modelOutput: msg.modelOutput } : {},
272
377
  tool_call_id: msg.tool_call_id
273
378
  });
274
379
  }
@@ -555,7 +660,7 @@ function formatMessagesForGoogle(messages) {
555
660
  return {
556
661
  role: "tool",
557
662
  tool_call_id: msg.toolCallId,
558
- content: msg.content
663
+ content: textToolOutput(msg.modelOutput, msg.content)
559
664
  };
560
665
  default:
561
666
  return msg;
@@ -563,6 +668,64 @@ function formatMessagesForGoogle(messages) {
563
668
  });
564
669
  }
565
670
 
671
+ // src/core/openai-responses.ts
672
+ function resolveOpenAITransport(defaults, overrides) {
673
+ return {
674
+ api: overrides?.api ?? defaults.api ?? "auto",
675
+ responses: defaults.responses !== void 0 || overrides?.responses !== void 0 ? { ...defaults.responses, ...overrides?.responses } : void 0
676
+ };
677
+ }
678
+ function useOpenAIResponses(model, baseURL, parameters, transport, hasTools, hasReplay, nativeToolSearch = false) {
679
+ const api = transport.api ?? "auto";
680
+ if (!["auto", "chat", "responses"].includes(api)) {
681
+ throw new Error(`Unknown OpenAI API selection: ${api}`);
682
+ }
683
+ if (api === "chat") {
684
+ if (transport.responses !== void 0 || nativeToolSearch) {
685
+ throw new Error(
686
+ "Responses options and native tool search require the Responses API"
687
+ );
688
+ }
689
+ return false;
690
+ }
691
+ let officialEndpoint = !baseURL;
692
+ try {
693
+ officialEndpoint = !baseURL || new URL(baseURL).hostname === "api.openai.com";
694
+ } catch {
695
+ }
696
+ return api === "responses" || transport.responses !== void 0 || hasReplay && officialEndpoint || nativeToolSearch || hasTools && requiresOpenAIResponses(model, baseURL, parameters);
697
+ }
698
+ function responseMetadata(response) {
699
+ const output = Array.isArray(response?.output) ? response.output : [];
700
+ return {
701
+ openai: {
702
+ response: {
703
+ id: response?.id,
704
+ model: response?.model,
705
+ status: response?.status,
706
+ output,
707
+ usage: response?.usage,
708
+ store: response?.store,
709
+ refusals: output.flatMap(
710
+ (item) => item?.type === "message" && Array.isArray(item.content) ? item.content.filter(
711
+ (part) => part?.type === "refusal" && typeof part.refusal === "string"
712
+ ).map((part) => part.refusal) : []
713
+ )
714
+ }
715
+ }
716
+ };
717
+ }
718
+ function getResponseReplay(metadata) {
719
+ const response = metadata?.openai?.response;
720
+ if (response === void 0) return void 0;
721
+ if (!Array.isArray(response.output) || response.output.some((item) => !item || typeof item.type !== "string")) {
722
+ throw new Error(
723
+ "Invalid OpenAI Responses replay metadata: output must contain typed items"
724
+ );
725
+ }
726
+ return response.output;
727
+ }
728
+
566
729
  // src/core/provider-error.ts
567
730
  function providerErrorEvent(error, code) {
568
731
  const details = error ?? {};
@@ -639,13 +802,16 @@ var OpenAIAdapter = class _OpenAIAdapter {
639
802
  }));
640
803
  }
641
804
  shouldUseResponsesApi(request) {
642
- if (this.toolDefinitions(request).length === 0) return false;
643
- if (request.providerToolOptions?.openai?.nativeToolSearch?.enabled === true && request.providerToolOptions.openai.nativeToolSearch.useResponsesApi !== false)
644
- return true;
645
- return requiresOpenAIResponses(
805
+ return useOpenAIResponses(
646
806
  request.config?.model || this.model,
647
807
  this.config.baseUrl,
648
- this.parameterOptions(request)
808
+ this.parameterOptions(request),
809
+ resolveOpenAITransport(this.config, request.config?.openai),
810
+ this.toolDefinitions(request).length > 0,
811
+ !!(request.rawMessages ?? request.messages)?.some(
812
+ (message) => message.providerMetadata?.openai?.response
813
+ ),
814
+ request.providerToolOptions?.openai?.nativeToolSearch?.enabled === true && request.providerToolOptions.openai.nativeToolSearch.useResponsesApi !== false
649
815
  );
650
816
  }
651
817
  /**
@@ -678,11 +844,11 @@ var OpenAIAdapter = class _OpenAIAdapter {
678
844
  });
679
845
  }
680
846
  buildResponsesInput(request) {
681
- const sourceMessages = request.rawMessages && request.rawMessages.length > 0 ? this.formatRawMessages(request.rawMessages) : formatMessagesForOpenAI(request.messages, void 0);
847
+ const sourceMessages = request.rawMessages && request.rawMessages.length > 0 ? this.formatRawMessages(request.rawMessages, true) : formatMessagesForOpenAI(request.messages, void 0, true);
682
848
  const input = [];
683
849
  for (const message of sourceMessages) {
684
850
  if (message.role === "system") {
685
- if (!request.systemPrompt && message.content) {
851
+ if (message.content && message.content !== request.systemPrompt) {
686
852
  input.push({
687
853
  type: "message",
688
854
  role: "developer",
@@ -692,6 +858,11 @@ var OpenAIAdapter = class _OpenAIAdapter {
692
858
  continue;
693
859
  }
694
860
  if (message.role === "assistant") {
861
+ const replay = getResponseReplay(message.providerMetadata);
862
+ if (replay) {
863
+ input.push(...replay);
864
+ continue;
865
+ }
695
866
  const content = this.toResponsesContent(message.content, "assistant");
696
867
  if (typeof content === "string" ? content : Array.isArray(content) && content.length > 0) {
697
868
  input.push({
@@ -715,7 +886,7 @@ var OpenAIAdapter = class _OpenAIAdapter {
715
886
  input.push({
716
887
  type: "function_call_output",
717
888
  call_id: message.tool_call_id,
718
- output: typeof message.content === "string" ? message.content : JSON.stringify(message.content ?? null)
889
+ output: message.modelOutput ? toolOutputForProvider(message.modelOutput, "responses") : typeof message.content === "string" ? message.content : JSON.stringify(message.content ?? null)
719
890
  });
720
891
  continue;
721
892
  }
@@ -729,10 +900,12 @@ var OpenAIAdapter = class _OpenAIAdapter {
729
900
  return input;
730
901
  }
731
902
  /** Preserve Copilot screenshot attachments on every OpenAI transport. */
732
- formatRawMessages(messages) {
903
+ formatRawMessages(messages, preserveProviderMetadata = false) {
733
904
  return messages.map((message) => {
734
- const { attachments, ...rest } = message;
735
- let content = message.content;
905
+ const { attachments, providerMetadata, modelOutput, ...rest } = message;
906
+ let content = message.role === "tool" && !preserveProviderMetadata ? textToolOutput(modelOutput, String(message.content ?? "")) : message.content;
907
+ if (preserveProviderMetadata && modelOutput)
908
+ rest.modelOutput = modelOutput;
736
909
  if (message.role === "assistant" && Array.isArray(message.tool_calls) && message.tool_calls.length > 0 && content === "") {
737
910
  content = null;
738
911
  }
@@ -745,7 +918,11 @@ var OpenAIAdapter = class _OpenAIAdapter {
745
918
  content = [...parts, ...images];
746
919
  }
747
920
  }
748
- return { ...rest, content };
921
+ return {
922
+ ...rest,
923
+ content,
924
+ ...preserveProviderMetadata && providerMetadata ? { providerMetadata } : {}
925
+ };
749
926
  });
750
927
  }
751
928
  /**
@@ -826,14 +1003,25 @@ var OpenAIAdapter = class _OpenAIAdapter {
826
1003
  ).map((part) => part.text) : []
827
1004
  ).join("") : "";
828
1005
  const content = textFromOutput || (typeof response?.output_text === "string" ? response.output_text : "");
829
- const toolCalls = Array.isArray(response?.output) ? response.output.filter((item) => item?.type === "function_call").map((item) => ({
830
- id: item.call_id ?? item.id ?? generateToolCallId(),
831
- name: item.name,
832
- args: JSON.parse(item.arguments ?? "{}")
833
- })) : [];
1006
+ const seenCallIds = /* @__PURE__ */ new Set();
1007
+ const toolCalls = Array.isArray(response?.output) ? response.output.filter((item) => item?.type === "function_call").map((item) => {
1008
+ 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");
1010
+ }
1011
+ if (seenCallIds.has(item.call_id))
1012
+ throw new Error("Responses contains duplicate function call IDs");
1013
+ seenCallIds.add(item.call_id);
1014
+ const args = JSON.parse(item.arguments);
1015
+ if (!args || typeof args !== "object" || Array.isArray(args))
1016
+ throw new Error(
1017
+ "Responses function arguments must be a JSON object"
1018
+ );
1019
+ return { id: item.call_id, name: item.name, args };
1020
+ }) : [];
834
1021
  return {
835
1022
  content,
836
1023
  toolCalls,
1024
+ providerMetadata: responseMetadata(response),
837
1025
  usage: response?.usage ? {
838
1026
  promptTokens: response.usage.input_tokens ?? 0,
839
1027
  completionTokens: response.usage.output_tokens ?? 0,
@@ -844,6 +1032,17 @@ var OpenAIAdapter = class _OpenAIAdapter {
844
1032
  }
845
1033
  buildResponsesPayload(request, stream) {
846
1034
  const openaiToolOptions = request.providerToolOptions?.openai;
1035
+ const options = resolveOpenAITransport(
1036
+ this.config,
1037
+ request.config?.openai
1038
+ ).responses;
1039
+ const parameters = buildOpenAIParameters(
1040
+ request.config?.model || this.model,
1041
+ request.config?.maxTokens ?? this.config.maxTokens,
1042
+ request.config?.temperature ?? this.config.temperature,
1043
+ this.parameterOptions(request),
1044
+ "responses"
1045
+ );
847
1046
  const responsesTextFormat = toOpenAIResponsesTextFormat(
848
1047
  request.config?.responseFormat
849
1048
  );
@@ -859,13 +1058,26 @@ var OpenAIAdapter = class _OpenAIAdapter {
859
1058
  ),
860
1059
  tool_choice: openaiToolOptions?.toolChoice,
861
1060
  parallel_tool_calls: openaiToolOptions?.parallelToolCalls,
862
- ...buildOpenAIParameters(
863
- modelIdForPayload,
864
- request.config?.maxTokens ?? this.config.maxTokens,
865
- request.config?.temperature ?? this.config.temperature,
866
- this.parameterOptions(request),
867
- "responses"
868
- ),
1061
+ ...parameters,
1062
+ ...options?.reasoningSummary ? {
1063
+ reasoning: {
1064
+ ...parameters.reasoning,
1065
+ summary: options.reasoningSummary
1066
+ }
1067
+ } : {},
1068
+ ...options?.store !== void 0 ? { store: options.store } : {},
1069
+ ...options?.previousResponseId !== void 0 ? { previous_response_id: options.previousResponseId } : {},
1070
+ ...options?.promptCacheKey !== void 0 ? { prompt_cache_key: options.promptCacheKey } : {},
1071
+ ...options?.promptCacheOptions !== void 0 ? { prompt_cache_options: options.promptCacheOptions } : {},
1072
+ ...options?.promptCacheRetention !== void 0 ? { prompt_cache_retention: options.promptCacheRetention } : {},
1073
+ ...options?.store === false || options?.include ? {
1074
+ include: [
1075
+ .../* @__PURE__ */ new Set([
1076
+ ...options?.include ?? [],
1077
+ ...options?.store === false ? ["reasoning.encrypted_content"] : []
1078
+ ])
1079
+ ]
1080
+ } : {},
869
1081
  ...responsesTextFormat ? { text: { format: responsesTextFormat } } : {},
870
1082
  stream
871
1083
  };
@@ -924,6 +1136,10 @@ var OpenAIAdapter = class _OpenAIAdapter {
924
1136
  let usage;
925
1137
  const citations = [];
926
1138
  let completed = false;
1139
+ let terminalResponse;
1140
+ let responseIdentity = {};
1141
+ const outputItems = /* @__PURE__ */ new Map();
1142
+ let streamedText = "";
927
1143
  function* flushToolCalls() {
928
1144
  for (const call of toolCalls.values()) {
929
1145
  if (!call.name)
@@ -940,17 +1156,36 @@ var OpenAIAdapter = class _OpenAIAdapter {
940
1156
  for await (const event of stream) {
941
1157
  throwIfAborted(request.signal);
942
1158
  logProviderPayload("openai", "stream chunk", event, request.debug);
1159
+ if (completed)
1160
+ throw new Error("Responses stream emitted an event after completion");
943
1161
  const index = event?.output_index ?? 0;
944
1162
  switch (event?.type) {
1163
+ case "response.created":
1164
+ responseIdentity = {
1165
+ id: event.response?.id,
1166
+ model: event.response?.model
1167
+ };
1168
+ break;
1169
+ case "response.output_item.done":
1170
+ if (event.item) outputItems.set(index, event.item);
1171
+ if (event.item?.type === "function_call") {
1172
+ toolCalls.set(index, {
1173
+ id: event.item.call_id,
1174
+ name: event.item.name,
1175
+ arguments: event.item.arguments
1176
+ });
1177
+ }
1178
+ break;
945
1179
  case "response.output_text.delta":
946
1180
  if (event.delta) {
1181
+ streamedText += event.delta;
947
1182
  yield { type: "message:delta", content: event.delta };
948
1183
  }
949
1184
  break;
950
1185
  case "response.output_item.added":
951
1186
  if (event.item?.type === "function_call") {
952
1187
  toolCalls.set(index, {
953
- id: event.item.call_id ?? event.item.id ?? generateToolCallId(),
1188
+ id: event.item.call_id ?? "",
954
1189
  name: event.item.name ?? "",
955
1190
  arguments: event.item.arguments ?? ""
956
1191
  });
@@ -969,7 +1204,10 @@ var OpenAIAdapter = class _OpenAIAdapter {
969
1204
  break;
970
1205
  }
971
1206
  case "response.completed": {
1207
+ if (event.response?.status && event.response.status !== "completed")
1208
+ throw new Error(`Responses stream ${event.response.status}`);
972
1209
  completed = true;
1210
+ terminalResponse = { ...responseIdentity, ...event.response };
973
1211
  const found = this.collectResponsesCitations(
974
1212
  event.response?.output,
975
1213
  citations.length
@@ -1006,12 +1244,64 @@ var OpenAIAdapter = class _OpenAIAdapter {
1006
1244
  throwIfAborted(request.signal);
1007
1245
  if (!completed)
1008
1246
  throw new Error("Responses stream ended without a completed response");
1247
+ const output = terminalResponse.output?.length ? terminalResponse.output : [...outputItems.entries()].sort(([a], [b]) => a - b).map(([, item]) => item);
1248
+ if (output.some((item) => item.type === "function_call"))
1249
+ toolCalls.clear();
1250
+ for (const [index, item] of output.entries()) {
1251
+ if (item.type === "function_call") {
1252
+ toolCalls.set(index, {
1253
+ id: item.call_id,
1254
+ name: item.name,
1255
+ arguments: item.arguments
1256
+ });
1257
+ }
1258
+ }
1259
+ const uniqueCalls = /* @__PURE__ */ new Map();
1260
+ for (const call of toolCalls.values()) {
1261
+ if (!call.id || !call.name)
1262
+ throw new Error("Responses stream contains an incomplete tool call");
1263
+ uniqueCalls.set(call.id, call);
1264
+ }
1265
+ toolCalls.clear();
1266
+ [...uniqueCalls.values()].forEach(
1267
+ (call, index) => toolCalls.set(index, call)
1268
+ );
1269
+ if (!output.length && toolCalls.size) {
1270
+ for (const call of toolCalls.values())
1271
+ output.push({
1272
+ type: "function_call",
1273
+ call_id: call.id,
1274
+ name: call.name,
1275
+ arguments: call.arguments
1276
+ });
1277
+ }
1278
+ if (!output.length && streamedText)
1279
+ output.push({
1280
+ type: "message",
1281
+ role: "assistant",
1282
+ content: [{ type: "output_text", text: streamedText, annotations: [] }]
1283
+ });
1284
+ terminalResponse.output = output;
1285
+ const finalText = this.parseResponsesResult(terminalResponse).content;
1286
+ if (finalText.startsWith(streamedText) && finalText.length > streamedText.length) {
1287
+ yield {
1288
+ type: "message:delta",
1289
+ content: finalText.slice(streamedText.length)
1290
+ };
1291
+ }
1009
1292
  yield* flushToolCalls();
1010
1293
  if (citations.length > 0) {
1011
1294
  yield { type: "citation", citations: deduplicateCitations(citations) };
1012
1295
  }
1013
- yield { type: "message:end" };
1014
- yield { type: "done", usage };
1296
+ yield {
1297
+ type: "message:end",
1298
+ providerMetadata: responseMetadata(terminalResponse)
1299
+ };
1300
+ yield {
1301
+ type: "done",
1302
+ usage,
1303
+ providerMetadata: responseMetadata(terminalResponse)
1304
+ };
1015
1305
  }
1016
1306
  async *stream(request) {
1017
1307
  throwIfAborted(request.signal);