@yourgpt/llm-sdk 2.6.0-beta.1 → 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.
Files changed (61) hide show
  1. package/README.md +146 -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 +437 -39
  5. package/dist/adapters/index.mjs +437 -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 +20 -36
  13. package/dist/index.d.ts +20 -36
  14. package/dist/index.js +327 -69
  15. package/dist/index.mjs +325 -70
  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 +116 -7
  19. package/dist/providers/anthropic/index.mjs +116 -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 +393 -31
  27. package/dist/providers/fireworks/index.mjs +393 -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 +393 -31
  31. package/dist/providers/google/index.mjs +393 -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 +822 -40
  39. package/dist/providers/openai/index.mjs +821 -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 +393 -31
  43. package/dist/providers/openrouter/index.mjs +393 -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 +393 -31
  47. package/dist/providers/togetherai/index.mjs +393 -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 +393 -31
  51. package/dist/providers/xai/index.mjs +393 -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,7 +1,7 @@
1
- import { L as LanguageModel } from '../../types-BW3csYo5.mjs';
2
- import { a as AnthropicProviderConfig, A as AIProvider } from '../../types-NOswlhm9.mjs';
1
+ import { L as LanguageModel } from '../../types-Dg1Bcg1k.mjs';
2
+ import { a as AnthropicProviderConfig, A as AIProvider } from '../../types-DHQEMFHA.mjs';
3
3
  import 'zod';
4
- import '../../base-CkeT7u-O.mjs';
4
+ import '../../base-BICaQaRS.mjs';
5
5
 
6
6
  /**
7
7
  * Anthropic Provider - Modern Pattern
@@ -1,7 +1,7 @@
1
- import { L as LanguageModel } from '../../types-BW3csYo5.js';
2
- import { a as AnthropicProviderConfig, A as AIProvider } from '../../types-DA54bSmX.js';
1
+ import { L as LanguageModel } from '../../types-Dg1Bcg1k.js';
2
+ import { a as AnthropicProviderConfig, A as AIProvider } from '../../types-Dikj3En4.js';
3
3
  import 'zod';
4
- import '../../base-BdTpUAWI.js';
4
+ import '../../base-CpRPB8u8.js';
5
5
 
6
6
  /**
7
7
  * Anthropic Provider - Modern Pattern
@@ -1,5 +1,92 @@
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
+ if ("fileId" in part) {
79
+ throw new Error(
80
+ "OpenAI image file references cannot be sent to another provider"
81
+ );
82
+ }
83
+ return {
84
+ type: "image",
85
+ source: "url" in part ? { type: "url", url: part.url } : { type: "base64", media_type: part.mediaType, data: part.data }
86
+ };
87
+ });
88
+ }
89
+
3
90
  // src/adapters/base.ts
4
91
  function stringifyForDebug(value) {
5
92
  return JSON.stringify(
@@ -206,7 +293,20 @@ function messageToAnthropicContent(message) {
206
293
  }
207
294
  return blocks;
208
295
  }
296
+ function extractAnthropicSystemPrompt(messages, systemPrompt) {
297
+ const explicit = typeof systemPrompt === "string" ? systemPrompt : "";
298
+ const parts = explicit ? [explicit] : [];
299
+ for (const message of messages) {
300
+ if (message.role !== "system") continue;
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);
305
+ }
306
+ return parts.join("\n");
307
+ }
209
308
  function formatMessagesForAnthropic(messages, systemPrompt) {
309
+ const system = extractAnthropicSystemPrompt(messages, systemPrompt);
210
310
  const formatted = [];
211
311
  for (let i = 0; i < messages.length; i++) {
212
312
  const msg = messages[i];
@@ -235,7 +335,7 @@ function formatMessagesForAnthropic(messages, systemPrompt) {
235
335
  {
236
336
  type: "tool_result",
237
337
  tool_use_id: msg.tool_call_id,
238
- content: msg.content ?? ""
338
+ content: msg.modelOutput ? toolOutputForProvider(msg.modelOutput) : msg.content ?? ""
239
339
  }
240
340
  ];
241
341
  while (i + 1 < messages.length && messages[i + 1].role === "tool") {
@@ -245,7 +345,7 @@ function formatMessagesForAnthropic(messages, systemPrompt) {
245
345
  toolResults.push({
246
346
  type: "tool_result",
247
347
  tool_use_id: nextTool.tool_call_id,
248
- content: nextTool.content ?? ""
348
+ content: nextTool.modelOutput ? toolOutputForProvider(nextTool.modelOutput) : nextTool.content ?? ""
249
349
  });
250
350
  }
251
351
  }
@@ -261,7 +361,7 @@ function formatMessagesForAnthropic(messages, systemPrompt) {
261
361
  }
262
362
  }
263
363
  return {
264
- system: "",
364
+ system,
265
365
  messages: formatted
266
366
  };
267
367
  }
@@ -631,7 +731,7 @@ function formatMessagesForAnthropic2(messages) {
631
731
  }
632
732
  pendingToolResults.push({
633
733
  toolCallId,
634
- content: msg.content
734
+ content: msg.modelOutput ? toolOutputForProvider(msg.modelOutput) : msg.content
635
735
  });
636
736
  }
637
737
  }
@@ -750,7 +850,7 @@ var AnthropicAdapter = class {
750
850
  }
751
851
  pendingToolResults.push({
752
852
  tool_use_id: toolCallId,
753
- content: typeof msg.content === "string" ? msg.content : JSON.stringify(msg.content)
853
+ content: msg.modelOutput ? toolOutputForProvider(msg.modelOutput) : typeof msg.content === "string" ? msg.content : JSON.stringify(msg.content)
754
854
  });
755
855
  } else if (msg.role === "user") {
756
856
  if (pendingToolResults.length > 0) {
@@ -871,14 +971,23 @@ var AnthropicAdapter = class {
871
971
  buildRequestOptions(request) {
872
972
  const responseFormat = request.config?.responseFormat;
873
973
  const jsonObjectSuffix = responseFormat?.type === "json_object" ? "\n\nRespond with a single JSON object and no other text." : "";
874
- const systemMessage = (request.systemPrompt || "") + jsonObjectSuffix;
974
+ let systemMessage;
875
975
  let messages;
876
976
  if (request.rawMessages && request.rawMessages.length > 0) {
977
+ systemMessage = extractAnthropicSystemPrompt(
978
+ request.rawMessages,
979
+ request.systemPrompt
980
+ );
877
981
  messages = this.convertToAnthropicMessages(request.rawMessages);
878
982
  } else {
879
- const formatted = formatMessagesForAnthropic(request.messages);
983
+ const formatted = formatMessagesForAnthropic(
984
+ request.messages,
985
+ request.systemPrompt
986
+ );
987
+ systemMessage = formatted.system;
880
988
  messages = formatted.messages;
881
989
  }
990
+ systemMessage += jsonObjectSuffix;
882
991
  const anthropicNativeSearch = request.providerToolOptions?.anthropic?.nativeToolSearch;
883
992
  const tools = anthropicNativeSearch?.enabled ? this.buildNativeSearchTools(
884
993
  request.toolDefinitions ?? [],
@@ -1,3 +1,90 @@
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: "text",
74
+ text: part.text
75
+ };
76
+ if ("fileId" in part) {
77
+ throw new Error(
78
+ "OpenAI image file references cannot be sent to another provider"
79
+ );
80
+ }
81
+ return {
82
+ type: "image",
83
+ source: "url" in part ? { type: "url", url: part.url } : { type: "base64", media_type: part.mediaType, data: part.data }
84
+ };
85
+ });
86
+ }
87
+
1
88
  // src/adapters/base.ts
2
89
  function stringifyForDebug(value) {
3
90
  return JSON.stringify(
@@ -204,7 +291,20 @@ function messageToAnthropicContent(message) {
204
291
  }
205
292
  return blocks;
206
293
  }
294
+ function extractAnthropicSystemPrompt(messages, systemPrompt) {
295
+ const explicit = typeof systemPrompt === "string" ? systemPrompt : "";
296
+ const parts = explicit ? [explicit] : [];
297
+ for (const message of messages) {
298
+ if (message.role !== "system") continue;
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);
303
+ }
304
+ return parts.join("\n");
305
+ }
207
306
  function formatMessagesForAnthropic(messages, systemPrompt) {
307
+ const system = extractAnthropicSystemPrompt(messages, systemPrompt);
208
308
  const formatted = [];
209
309
  for (let i = 0; i < messages.length; i++) {
210
310
  const msg = messages[i];
@@ -233,7 +333,7 @@ function formatMessagesForAnthropic(messages, systemPrompt) {
233
333
  {
234
334
  type: "tool_result",
235
335
  tool_use_id: msg.tool_call_id,
236
- content: msg.content ?? ""
336
+ content: msg.modelOutput ? toolOutputForProvider(msg.modelOutput) : msg.content ?? ""
237
337
  }
238
338
  ];
239
339
  while (i + 1 < messages.length && messages[i + 1].role === "tool") {
@@ -243,7 +343,7 @@ function formatMessagesForAnthropic(messages, systemPrompt) {
243
343
  toolResults.push({
244
344
  type: "tool_result",
245
345
  tool_use_id: nextTool.tool_call_id,
246
- content: nextTool.content ?? ""
346
+ content: nextTool.modelOutput ? toolOutputForProvider(nextTool.modelOutput) : nextTool.content ?? ""
247
347
  });
248
348
  }
249
349
  }
@@ -259,7 +359,7 @@ function formatMessagesForAnthropic(messages, systemPrompt) {
259
359
  }
260
360
  }
261
361
  return {
262
- system: "",
362
+ system,
263
363
  messages: formatted
264
364
  };
265
365
  }
@@ -629,7 +729,7 @@ function formatMessagesForAnthropic2(messages) {
629
729
  }
630
730
  pendingToolResults.push({
631
731
  toolCallId,
632
- content: msg.content
732
+ content: msg.modelOutput ? toolOutputForProvider(msg.modelOutput) : msg.content
633
733
  });
634
734
  }
635
735
  }
@@ -748,7 +848,7 @@ var AnthropicAdapter = class {
748
848
  }
749
849
  pendingToolResults.push({
750
850
  tool_use_id: toolCallId,
751
- content: typeof msg.content === "string" ? msg.content : JSON.stringify(msg.content)
851
+ content: msg.modelOutput ? toolOutputForProvider(msg.modelOutput) : typeof msg.content === "string" ? msg.content : JSON.stringify(msg.content)
752
852
  });
753
853
  } else if (msg.role === "user") {
754
854
  if (pendingToolResults.length > 0) {
@@ -869,14 +969,23 @@ var AnthropicAdapter = class {
869
969
  buildRequestOptions(request) {
870
970
  const responseFormat = request.config?.responseFormat;
871
971
  const jsonObjectSuffix = responseFormat?.type === "json_object" ? "\n\nRespond with a single JSON object and no other text." : "";
872
- const systemMessage = (request.systemPrompt || "") + jsonObjectSuffix;
972
+ let systemMessage;
873
973
  let messages;
874
974
  if (request.rawMessages && request.rawMessages.length > 0) {
975
+ systemMessage = extractAnthropicSystemPrompt(
976
+ request.rawMessages,
977
+ request.systemPrompt
978
+ );
875
979
  messages = this.convertToAnthropicMessages(request.rawMessages);
876
980
  } else {
877
- const formatted = formatMessagesForAnthropic(request.messages);
981
+ const formatted = formatMessagesForAnthropic(
982
+ request.messages,
983
+ request.systemPrompt
984
+ );
985
+ systemMessage = formatted.system;
878
986
  messages = formatted.messages;
879
987
  }
988
+ systemMessage += jsonObjectSuffix;
880
989
  const anthropicNativeSearch = request.providerToolOptions?.anthropic?.nativeToolSearch;
881
990
  const tools = anthropicNativeSearch?.enabled ? this.buildNativeSearchTools(
882
991
  request.toolDefinitions ?? [],
@@ -1,7 +1,7 @@
1
- import { b as AzureProviderConfig, A as AIProvider } from '../../types-NOswlhm9.mjs';
2
- import '../../types-BW3csYo5.mjs';
1
+ import { b as AzureProviderConfig, A as AIProvider } from '../../types-DHQEMFHA.mjs';
2
+ import '../../types-Dg1Bcg1k.mjs';
3
3
  import 'zod';
4
- import '../../base-CkeT7u-O.mjs';
4
+ import '../../base-BICaQaRS.mjs';
5
5
 
6
6
  /**
7
7
  * Azure OpenAI Provider
@@ -1,7 +1,7 @@
1
- import { b as AzureProviderConfig, A as AIProvider } from '../../types-DA54bSmX.js';
2
- import '../../types-BW3csYo5.js';
1
+ import { b as AzureProviderConfig, A as AIProvider } from '../../types-Dikj3En4.js';
2
+ import '../../types-Dg1Bcg1k.js';
3
3
  import 'zod';
4
- import '../../base-BdTpUAWI.js';
4
+ import '../../base-CpRPB8u8.js';
5
5
 
6
6
  /**
7
7
  * Azure OpenAI 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)}`;
@@ -189,7 +274,7 @@ function messageToOpenAIContent(message) {
189
274
  }
190
275
  return blocks;
191
276
  }
192
- function formatMessagesForOpenAI(messages, systemPrompt) {
277
+ function formatMessagesForOpenAI(messages, systemPrompt, preserveProviderMetadata = false) {
193
278
  const formatted = [];
194
279
  if (systemPrompt) {
195
280
  formatted.push({ role: "system", content: systemPrompt });
@@ -207,7 +292,8 @@ function formatMessagesForOpenAI(messages, systemPrompt) {
207
292
  const assistantMsg = {
208
293
  role: "assistant",
209
294
  // Gemini/xAI (OpenAI-compatible) reject content: "" on assistant messages with tool_calls
210
- content: hasToolCalls ? msg.content || null : msg.content
295
+ content: hasToolCalls ? msg.content || null : msg.content,
296
+ ...preserveProviderMetadata && msg.providerMetadata ? { providerMetadata: msg.providerMetadata } : {}
211
297
  };
212
298
  if (hasToolCalls) {
213
299
  assistantMsg.tool_calls = msg.tool_calls;
@@ -216,7 +302,8 @@ function formatMessagesForOpenAI(messages, systemPrompt) {
216
302
  } else if (msg.role === "tool" && msg.tool_call_id) {
217
303
  formatted.push({
218
304
  role: "tool",
219
- content: msg.content ?? "",
305
+ content: preserveProviderMetadata ? msg.content ?? "" : textToolOutput(msg.modelOutput, msg.content ?? ""),
306
+ ...preserveProviderMetadata && msg.modelOutput ? { modelOutput: msg.modelOutput } : {},
220
307
  tool_call_id: msg.tool_call_id
221
308
  });
222
309
  }
@@ -256,6 +343,13 @@ var AzureAdapter = class {
256
343
  let messages;
257
344
  if (request.rawMessages && request.rawMessages.length > 0) {
258
345
  const processedMessages = request.rawMessages.map((msg) => {
346
+ const { modelOutput, ...rest } = msg;
347
+ msg = {
348
+ ...rest,
349
+ ...msg.role === "tool" ? {
350
+ content: textToolOutput(modelOutput, String(msg.content ?? ""))
351
+ } : {}
352
+ };
259
353
  const hasAttachments = msg.attachments && Array.isArray(msg.attachments) && msg.attachments.length > 0;
260
354
  if (hasAttachments) {
261
355
  const content = [];
@@ -386,7 +480,10 @@ var AzureAdapter = class {
386
480
  const client = await this.getClient();
387
481
  let messages;
388
482
  if (request.rawMessages && request.rawMessages.length > 0) {
389
- messages = request.rawMessages;
483
+ messages = request.rawMessages.map(({ modelOutput, ...msg }) => ({
484
+ ...msg,
485
+ ...msg.role === "tool" ? { content: textToolOutput(modelOutput, String(msg.content ?? "")) } : {}
486
+ }));
390
487
  if (request.systemPrompt) {
391
488
  const hasSystem = messages.some((m) => m.role === "system");
392
489
  if (!hasSystem) {