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