@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
  }
@@ -504,7 +609,7 @@ function formatMessagesForXAI(messages) {
504
609
  return {
505
610
  role: "tool",
506
611
  tool_call_id: msg.toolCallId,
507
- content: msg.content
612
+ content: textToolOutput(msg.modelOutput, msg.content)
508
613
  };
509
614
  default:
510
615
  return msg;
@@ -512,6 +617,64 @@ function formatMessagesForXAI(messages) {
512
617
  });
513
618
  }
514
619
 
620
+ // src/core/openai-responses.ts
621
+ function resolveOpenAITransport(defaults, overrides) {
622
+ return {
623
+ api: overrides?.api ?? defaults.api ?? "auto",
624
+ responses: defaults.responses !== void 0 || overrides?.responses !== void 0 ? { ...defaults.responses, ...overrides?.responses } : void 0
625
+ };
626
+ }
627
+ function useOpenAIResponses(model, baseURL, parameters, transport, hasTools, hasReplay, nativeToolSearch = false) {
628
+ const api = transport.api ?? "auto";
629
+ if (!["auto", "chat", "responses"].includes(api)) {
630
+ throw new Error(`Unknown OpenAI API selection: ${api}`);
631
+ }
632
+ if (api === "chat") {
633
+ if (transport.responses !== void 0 || nativeToolSearch) {
634
+ throw new Error(
635
+ "Responses options and native tool search require the Responses API"
636
+ );
637
+ }
638
+ return false;
639
+ }
640
+ let officialEndpoint = !baseURL;
641
+ try {
642
+ officialEndpoint = !baseURL || new URL(baseURL).hostname === "api.openai.com";
643
+ } catch {
644
+ }
645
+ return api === "responses" || transport.responses !== void 0 || hasReplay && officialEndpoint || nativeToolSearch || hasTools && requiresOpenAIResponses(model, baseURL, parameters);
646
+ }
647
+ function responseMetadata(response) {
648
+ const output = Array.isArray(response?.output) ? response.output : [];
649
+ return {
650
+ openai: {
651
+ response: {
652
+ id: response?.id,
653
+ model: response?.model,
654
+ status: response?.status,
655
+ output,
656
+ usage: response?.usage,
657
+ store: response?.store,
658
+ refusals: output.flatMap(
659
+ (item) => item?.type === "message" && Array.isArray(item.content) ? item.content.filter(
660
+ (part) => part?.type === "refusal" && typeof part.refusal === "string"
661
+ ).map((part) => part.refusal) : []
662
+ )
663
+ }
664
+ }
665
+ };
666
+ }
667
+ function getResponseReplay(metadata) {
668
+ const response = metadata?.openai?.response;
669
+ if (response === void 0) return void 0;
670
+ if (!Array.isArray(response.output) || response.output.some((item) => !item || typeof item.type !== "string")) {
671
+ throw new Error(
672
+ "Invalid OpenAI Responses replay metadata: output must contain typed items"
673
+ );
674
+ }
675
+ return response.output;
676
+ }
677
+
515
678
  // src/core/provider-error.ts
516
679
  function providerErrorEvent(error, code) {
517
680
  const details = error ?? {};
@@ -588,13 +751,16 @@ var OpenAIAdapter = class _OpenAIAdapter {
588
751
  }));
589
752
  }
590
753
  shouldUseResponsesApi(request) {
591
- if (this.toolDefinitions(request).length === 0) return false;
592
- if (request.providerToolOptions?.openai?.nativeToolSearch?.enabled === true && request.providerToolOptions.openai.nativeToolSearch.useResponsesApi !== false)
593
- return true;
594
- return requiresOpenAIResponses(
754
+ return useOpenAIResponses(
595
755
  request.config?.model || this.model,
596
756
  this.config.baseUrl,
597
- this.parameterOptions(request)
757
+ this.parameterOptions(request),
758
+ resolveOpenAITransport(this.config, request.config?.openai),
759
+ this.toolDefinitions(request).length > 0,
760
+ !!(request.rawMessages ?? request.messages)?.some(
761
+ (message) => message.providerMetadata?.openai?.response
762
+ ),
763
+ request.providerToolOptions?.openai?.nativeToolSearch?.enabled === true && request.providerToolOptions.openai.nativeToolSearch.useResponsesApi !== false
598
764
  );
599
765
  }
600
766
  /**
@@ -627,11 +793,11 @@ var OpenAIAdapter = class _OpenAIAdapter {
627
793
  });
628
794
  }
629
795
  buildResponsesInput(request) {
630
- const sourceMessages = request.rawMessages && request.rawMessages.length > 0 ? this.formatRawMessages(request.rawMessages) : formatMessagesForOpenAI(request.messages, void 0);
796
+ const sourceMessages = request.rawMessages && request.rawMessages.length > 0 ? this.formatRawMessages(request.rawMessages, true) : formatMessagesForOpenAI(request.messages, void 0, true);
631
797
  const input = [];
632
798
  for (const message of sourceMessages) {
633
799
  if (message.role === "system") {
634
- if (!request.systemPrompt && message.content) {
800
+ if (message.content && message.content !== request.systemPrompt) {
635
801
  input.push({
636
802
  type: "message",
637
803
  role: "developer",
@@ -641,6 +807,11 @@ var OpenAIAdapter = class _OpenAIAdapter {
641
807
  continue;
642
808
  }
643
809
  if (message.role === "assistant") {
810
+ const replay = getResponseReplay(message.providerMetadata);
811
+ if (replay) {
812
+ input.push(...replay);
813
+ continue;
814
+ }
644
815
  const content = this.toResponsesContent(message.content, "assistant");
645
816
  if (typeof content === "string" ? content : Array.isArray(content) && content.length > 0) {
646
817
  input.push({
@@ -664,7 +835,7 @@ var OpenAIAdapter = class _OpenAIAdapter {
664
835
  input.push({
665
836
  type: "function_call_output",
666
837
  call_id: message.tool_call_id,
667
- output: typeof message.content === "string" ? message.content : JSON.stringify(message.content ?? null)
838
+ output: message.modelOutput ? toolOutputForProvider(message.modelOutput, "responses") : typeof message.content === "string" ? message.content : JSON.stringify(message.content ?? null)
668
839
  });
669
840
  continue;
670
841
  }
@@ -678,10 +849,12 @@ var OpenAIAdapter = class _OpenAIAdapter {
678
849
  return input;
679
850
  }
680
851
  /** Preserve Copilot screenshot attachments on every OpenAI transport. */
681
- formatRawMessages(messages) {
852
+ formatRawMessages(messages, preserveProviderMetadata = false) {
682
853
  return messages.map((message) => {
683
- const { attachments, ...rest } = message;
684
- let content = message.content;
854
+ const { attachments, providerMetadata, modelOutput, ...rest } = message;
855
+ let content = message.role === "tool" && !preserveProviderMetadata ? textToolOutput(modelOutput, String(message.content ?? "")) : message.content;
856
+ if (preserveProviderMetadata && modelOutput)
857
+ rest.modelOutput = modelOutput;
685
858
  if (message.role === "assistant" && Array.isArray(message.tool_calls) && message.tool_calls.length > 0 && content === "") {
686
859
  content = null;
687
860
  }
@@ -694,7 +867,11 @@ var OpenAIAdapter = class _OpenAIAdapter {
694
867
  content = [...parts, ...images];
695
868
  }
696
869
  }
697
- return { ...rest, content };
870
+ return {
871
+ ...rest,
872
+ content,
873
+ ...preserveProviderMetadata && providerMetadata ? { providerMetadata } : {}
874
+ };
698
875
  });
699
876
  }
700
877
  /**
@@ -775,14 +952,25 @@ var OpenAIAdapter = class _OpenAIAdapter {
775
952
  ).map((part) => part.text) : []
776
953
  ).join("") : "";
777
954
  const content = textFromOutput || (typeof response?.output_text === "string" ? response.output_text : "");
778
- const toolCalls = Array.isArray(response?.output) ? response.output.filter((item) => item?.type === "function_call").map((item) => ({
779
- id: item.call_id ?? item.id ?? generateToolCallId(),
780
- name: item.name,
781
- args: JSON.parse(item.arguments ?? "{}")
782
- })) : [];
955
+ const seenCallIds = /* @__PURE__ */ new Set();
956
+ const toolCalls = Array.isArray(response?.output) ? response.output.filter((item) => item?.type === "function_call").map((item) => {
957
+ if (typeof item.call_id !== "string" || !item.call_id || typeof item.name !== "string" || !item.name || typeof item.arguments !== "string" || item.status && item.status !== "completed") {
958
+ throw new Error("Responses contains an incomplete function call");
959
+ }
960
+ if (seenCallIds.has(item.call_id))
961
+ throw new Error("Responses contains duplicate function call IDs");
962
+ seenCallIds.add(item.call_id);
963
+ const args = JSON.parse(item.arguments);
964
+ if (!args || typeof args !== "object" || Array.isArray(args))
965
+ throw new Error(
966
+ "Responses function arguments must be a JSON object"
967
+ );
968
+ return { id: item.call_id, name: item.name, args };
969
+ }) : [];
783
970
  return {
784
971
  content,
785
972
  toolCalls,
973
+ providerMetadata: responseMetadata(response),
786
974
  usage: response?.usage ? {
787
975
  promptTokens: response.usage.input_tokens ?? 0,
788
976
  completionTokens: response.usage.output_tokens ?? 0,
@@ -793,6 +981,17 @@ var OpenAIAdapter = class _OpenAIAdapter {
793
981
  }
794
982
  buildResponsesPayload(request, stream) {
795
983
  const openaiToolOptions = request.providerToolOptions?.openai;
984
+ const options = resolveOpenAITransport(
985
+ this.config,
986
+ request.config?.openai
987
+ ).responses;
988
+ const parameters = buildOpenAIParameters(
989
+ request.config?.model || this.model,
990
+ request.config?.maxTokens ?? this.config.maxTokens,
991
+ request.config?.temperature ?? this.config.temperature,
992
+ this.parameterOptions(request),
993
+ "responses"
994
+ );
796
995
  const responsesTextFormat = toOpenAIResponsesTextFormat(
797
996
  request.config?.responseFormat
798
997
  );
@@ -808,13 +1007,26 @@ var OpenAIAdapter = class _OpenAIAdapter {
808
1007
  ),
809
1008
  tool_choice: openaiToolOptions?.toolChoice,
810
1009
  parallel_tool_calls: openaiToolOptions?.parallelToolCalls,
811
- ...buildOpenAIParameters(
812
- modelIdForPayload,
813
- request.config?.maxTokens ?? this.config.maxTokens,
814
- request.config?.temperature ?? this.config.temperature,
815
- this.parameterOptions(request),
816
- "responses"
817
- ),
1010
+ ...parameters,
1011
+ ...options?.reasoningSummary ? {
1012
+ reasoning: {
1013
+ ...parameters.reasoning,
1014
+ summary: options.reasoningSummary
1015
+ }
1016
+ } : {},
1017
+ ...options?.store !== void 0 ? { store: options.store } : {},
1018
+ ...options?.previousResponseId !== void 0 ? { previous_response_id: options.previousResponseId } : {},
1019
+ ...options?.promptCacheKey !== void 0 ? { prompt_cache_key: options.promptCacheKey } : {},
1020
+ ...options?.promptCacheOptions !== void 0 ? { prompt_cache_options: options.promptCacheOptions } : {},
1021
+ ...options?.promptCacheRetention !== void 0 ? { prompt_cache_retention: options.promptCacheRetention } : {},
1022
+ ...options?.store === false || options?.include ? {
1023
+ include: [
1024
+ .../* @__PURE__ */ new Set([
1025
+ ...options?.include ?? [],
1026
+ ...options?.store === false ? ["reasoning.encrypted_content"] : []
1027
+ ])
1028
+ ]
1029
+ } : {},
818
1030
  ...responsesTextFormat ? { text: { format: responsesTextFormat } } : {},
819
1031
  stream
820
1032
  };
@@ -873,6 +1085,10 @@ var OpenAIAdapter = class _OpenAIAdapter {
873
1085
  let usage;
874
1086
  const citations = [];
875
1087
  let completed = false;
1088
+ let terminalResponse;
1089
+ let responseIdentity = {};
1090
+ const outputItems = /* @__PURE__ */ new Map();
1091
+ let streamedText = "";
876
1092
  function* flushToolCalls() {
877
1093
  for (const call of toolCalls.values()) {
878
1094
  if (!call.name)
@@ -889,17 +1105,36 @@ var OpenAIAdapter = class _OpenAIAdapter {
889
1105
  for await (const event of stream) {
890
1106
  throwIfAborted(request.signal);
891
1107
  logProviderPayload("openai", "stream chunk", event, request.debug);
1108
+ if (completed)
1109
+ throw new Error("Responses stream emitted an event after completion");
892
1110
  const index = event?.output_index ?? 0;
893
1111
  switch (event?.type) {
1112
+ case "response.created":
1113
+ responseIdentity = {
1114
+ id: event.response?.id,
1115
+ model: event.response?.model
1116
+ };
1117
+ break;
1118
+ case "response.output_item.done":
1119
+ if (event.item) outputItems.set(index, event.item);
1120
+ if (event.item?.type === "function_call") {
1121
+ toolCalls.set(index, {
1122
+ id: event.item.call_id,
1123
+ name: event.item.name,
1124
+ arguments: event.item.arguments
1125
+ });
1126
+ }
1127
+ break;
894
1128
  case "response.output_text.delta":
895
1129
  if (event.delta) {
1130
+ streamedText += event.delta;
896
1131
  yield { type: "message:delta", content: event.delta };
897
1132
  }
898
1133
  break;
899
1134
  case "response.output_item.added":
900
1135
  if (event.item?.type === "function_call") {
901
1136
  toolCalls.set(index, {
902
- id: event.item.call_id ?? event.item.id ?? generateToolCallId(),
1137
+ id: event.item.call_id ?? "",
903
1138
  name: event.item.name ?? "",
904
1139
  arguments: event.item.arguments ?? ""
905
1140
  });
@@ -918,7 +1153,10 @@ var OpenAIAdapter = class _OpenAIAdapter {
918
1153
  break;
919
1154
  }
920
1155
  case "response.completed": {
1156
+ if (event.response?.status && event.response.status !== "completed")
1157
+ throw new Error(`Responses stream ${event.response.status}`);
921
1158
  completed = true;
1159
+ terminalResponse = { ...responseIdentity, ...event.response };
922
1160
  const found = this.collectResponsesCitations(
923
1161
  event.response?.output,
924
1162
  citations.length
@@ -955,12 +1193,64 @@ var OpenAIAdapter = class _OpenAIAdapter {
955
1193
  throwIfAborted(request.signal);
956
1194
  if (!completed)
957
1195
  throw new Error("Responses stream ended without a completed response");
1196
+ const output = terminalResponse.output?.length ? terminalResponse.output : [...outputItems.entries()].sort(([a], [b]) => a - b).map(([, item]) => item);
1197
+ if (output.some((item) => item.type === "function_call"))
1198
+ toolCalls.clear();
1199
+ for (const [index, item] of output.entries()) {
1200
+ if (item.type === "function_call") {
1201
+ toolCalls.set(index, {
1202
+ id: item.call_id,
1203
+ name: item.name,
1204
+ arguments: item.arguments
1205
+ });
1206
+ }
1207
+ }
1208
+ const uniqueCalls = /* @__PURE__ */ new Map();
1209
+ for (const call of toolCalls.values()) {
1210
+ if (!call.id || !call.name)
1211
+ throw new Error("Responses stream contains an incomplete tool call");
1212
+ uniqueCalls.set(call.id, call);
1213
+ }
1214
+ toolCalls.clear();
1215
+ [...uniqueCalls.values()].forEach(
1216
+ (call, index) => toolCalls.set(index, call)
1217
+ );
1218
+ if (!output.length && toolCalls.size) {
1219
+ for (const call of toolCalls.values())
1220
+ output.push({
1221
+ type: "function_call",
1222
+ call_id: call.id,
1223
+ name: call.name,
1224
+ arguments: call.arguments
1225
+ });
1226
+ }
1227
+ if (!output.length && streamedText)
1228
+ output.push({
1229
+ type: "message",
1230
+ role: "assistant",
1231
+ content: [{ type: "output_text", text: streamedText, annotations: [] }]
1232
+ });
1233
+ terminalResponse.output = output;
1234
+ const finalText = this.parseResponsesResult(terminalResponse).content;
1235
+ if (finalText.startsWith(streamedText) && finalText.length > streamedText.length) {
1236
+ yield {
1237
+ type: "message:delta",
1238
+ content: finalText.slice(streamedText.length)
1239
+ };
1240
+ }
958
1241
  yield* flushToolCalls();
959
1242
  if (citations.length > 0) {
960
1243
  yield { type: "citation", citations: deduplicateCitations(citations) };
961
1244
  }
962
- yield { type: "message:end" };
963
- yield { type: "done", usage };
1245
+ yield {
1246
+ type: "message:end",
1247
+ providerMetadata: responseMetadata(terminalResponse)
1248
+ };
1249
+ yield {
1250
+ type: "done",
1251
+ usage,
1252
+ providerMetadata: responseMetadata(terminalResponse)
1253
+ };
964
1254
  }
965
1255
  async *stream(request) {
966
1256
  throwIfAborted(request.signal);