headroom-ai 0.40.0 → 0.42.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -279,8 +279,16 @@ const hints = await client.feedback.getHints('list_servers');
279
279
  // TOIN (Tool Output Intelligence Network)
280
280
  const toinStats = await client.toin.getStats();
281
281
  const patterns = await client.toin.getPatterns(20);
282
+ const pattern = patterns[0] && await client.toin.getPattern(patterns[0].hash);
282
283
  ```
283
284
 
285
+ Each `hash` is the complete scoped aggregation key, including tenant, auth mode,
286
+ model family, and tool signature. Pass it back unchanged for an exact lookup;
287
+ the client URL-encodes it. Shorter aggregation-key prefixes are accepted only
288
+ when they identify one pattern; ambiguous prefixes return HTTP 409 rather than
289
+ an arbitrary pattern.
290
+
291
+
284
292
  ## Configuration Types
285
293
 
286
294
  Full TypeScript interfaces for every Python config dataclass.
@@ -1,6 +1,6 @@
1
1
  'use strict';
2
2
 
3
- var chunkN7HYJMEN_cjs = require('../chunk-N7HYJMEN.cjs');
3
+ var chunkCINZDAE4_cjs = require('../chunk-CINZDAE4.cjs');
4
4
 
5
5
  // src/adapters/anthropic.ts
6
6
  function withHeadroom(client, options = {}) {
@@ -8,13 +8,13 @@ function withHeadroom(client, options = {}) {
8
8
  const compressedCreate = async (params) => {
9
9
  const messages = params.messages;
10
10
  const model = options.model ?? params.model ?? "claude-sonnet-4-5-20250929";
11
- const openaiMessages = chunkN7HYJMEN_cjs.anthropicToOpenAI(messages);
12
- const result = await chunkN7HYJMEN_cjs.compress(openaiMessages, {
11
+ const openaiMessages = chunkCINZDAE4_cjs.anthropicToOpenAI(messages);
12
+ const result = await chunkCINZDAE4_cjs.compress(openaiMessages, {
13
13
  stack: "adapter_ts_anthropic",
14
14
  ...options,
15
15
  model
16
16
  });
17
- const anthropicMessages = result.compressed ? chunkN7HYJMEN_cjs.openAIToAnthropic(result.messages) : messages;
17
+ const anthropicMessages = result.compressed ? chunkCINZDAE4_cjs.openAIToAnthropic(result.messages) : messages;
18
18
  return originalCreate({
19
19
  ...params,
20
20
  messages: anthropicMessages
@@ -1,4 +1,4 @@
1
- import { C as CompressOptions } from '../types-Cjdl75F4.cjs';
1
+ import { C as CompressOptions } from '../types-Bxcai7Tt.cjs';
2
2
 
3
3
  interface AnthropicLike {
4
4
  messages: {
@@ -1,4 +1,4 @@
1
- import { C as CompressOptions } from '../types-Cjdl75F4.js';
1
+ import { C as CompressOptions } from '../types-Bxcai7Tt.js';
2
2
 
3
3
  interface AnthropicLike {
4
4
  messages: {
@@ -1,4 +1,4 @@
1
- import { anthropicToOpenAI, compress, openAIToAnthropic } from '../chunk-FJQ65W3R.js';
1
+ import { anthropicToOpenAI, compress, openAIToAnthropic } from '../chunk-UVX5TGIN.js';
2
2
 
3
3
  // src/adapters/anthropic.ts
4
4
  function withHeadroom(client, options = {}) {
@@ -1,6 +1,6 @@
1
1
  'use strict';
2
2
 
3
- var chunkN7HYJMEN_cjs = require('../chunk-N7HYJMEN.cjs');
3
+ var chunkCINZDAE4_cjs = require('../chunk-CINZDAE4.cjs');
4
4
 
5
5
  // src/adapters/gemini.ts
6
6
  function withHeadroom(model, options = {}) {
@@ -9,7 +9,7 @@ function withHeadroom(model, options = {}) {
9
9
  const compressedGenerate = async (params) => {
10
10
  const contents = params.contents ?? params;
11
11
  const modelName = options.model ?? "gemini-2.0-flash";
12
- const result = await chunkN7HYJMEN_cjs.compress(
12
+ const result = await chunkCINZDAE4_cjs.compress(
13
13
  Array.isArray(contents) ? contents : [contents],
14
14
  { stack: "adapter_ts_gemini", ...options, model: modelName }
15
15
  );
@@ -19,7 +19,7 @@ function withHeadroom(model, options = {}) {
19
19
  const compressedStream = originalStream ? async (params) => {
20
20
  const contents = params.contents ?? params;
21
21
  const modelName = options.model ?? "gemini-2.0-flash";
22
- const result = await chunkN7HYJMEN_cjs.compress(
22
+ const result = await chunkCINZDAE4_cjs.compress(
23
23
  Array.isArray(contents) ? contents : [contents],
24
24
  { stack: "adapter_ts_gemini", ...options, model: modelName }
25
25
  );
@@ -1,4 +1,4 @@
1
- import { C as CompressOptions } from '../types-Cjdl75F4.cjs';
1
+ import { C as CompressOptions } from '../types-Bxcai7Tt.cjs';
2
2
 
3
3
  /**
4
4
  * Gemini adapter — wraps Google Generative AI model with auto-compression.
@@ -1,4 +1,4 @@
1
- import { C as CompressOptions } from '../types-Cjdl75F4.js';
1
+ import { C as CompressOptions } from '../types-Bxcai7Tt.js';
2
2
 
3
3
  /**
4
4
  * Gemini adapter — wraps Google Generative AI model with auto-compression.
@@ -1,4 +1,4 @@
1
- import { compress } from '../chunk-FJQ65W3R.js';
1
+ import { compress } from '../chunk-UVX5TGIN.js';
2
2
 
3
3
  // src/adapters/gemini.ts
4
4
  function withHeadroom(model, options = {}) {
@@ -1,6 +1,6 @@
1
1
  'use strict';
2
2
 
3
- var chunkN7HYJMEN_cjs = require('../chunk-N7HYJMEN.cjs');
3
+ var chunkCINZDAE4_cjs = require('../chunk-CINZDAE4.cjs');
4
4
 
5
5
  // src/adapters/openai.ts
6
6
  function withHeadroom(client, options = {}) {
@@ -10,7 +10,7 @@ function withHeadroom(client, options = {}) {
10
10
  const compressedCreate = async (params) => {
11
11
  const messages = params.messages;
12
12
  const model = options.model ?? params.model ?? "gpt-4o";
13
- const result = await chunkN7HYJMEN_cjs.compress(messages, {
13
+ const result = await chunkCINZDAE4_cjs.compress(messages, {
14
14
  stack: "adapter_ts_openai",
15
15
  ...options,
16
16
  model
@@ -1,4 +1,4 @@
1
- import { C as CompressOptions } from '../types-Cjdl75F4.cjs';
1
+ import { C as CompressOptions } from '../types-Bxcai7Tt.cjs';
2
2
 
3
3
  interface OpenAILike {
4
4
  chat: {
@@ -1,4 +1,4 @@
1
- import { C as CompressOptions } from '../types-Cjdl75F4.js';
1
+ import { C as CompressOptions } from '../types-Bxcai7Tt.js';
2
2
 
3
3
  interface OpenAILike {
4
4
  chat: {
@@ -1,4 +1,4 @@
1
- import { compress } from '../chunk-FJQ65W3R.js';
1
+ import { compress } from '../chunk-UVX5TGIN.js';
2
2
 
3
3
  // src/adapters/openai.ts
4
4
  function withHeadroom(client, options = {}) {
@@ -1,6 +1,6 @@
1
1
  'use strict';
2
2
 
3
- var chunkN7HYJMEN_cjs = require('../chunk-N7HYJMEN.cjs');
3
+ var chunkCINZDAE4_cjs = require('../chunk-CINZDAE4.cjs');
4
4
  var module$1 = require('module');
5
5
 
6
6
  var _documentCurrentScript = typeof document !== 'undefined' ? document.currentScript : null;
@@ -12,25 +12,25 @@ function headroomMiddleware(options = {}) {
12
12
  const prompt = params.prompt;
13
13
  if (!prompt || prompt.length === 0) return params;
14
14
  const model = options.model ?? params.modelId ?? "gpt-4o";
15
- const openaiMessages = chunkN7HYJMEN_cjs.vercelToOpenAI(prompt);
16
- const result = await chunkN7HYJMEN_cjs.compress(openaiMessages, {
15
+ const openaiMessages = chunkCINZDAE4_cjs.vercelToOpenAI(prompt);
16
+ const result = await chunkCINZDAE4_cjs.compress(openaiMessages, {
17
17
  stack: "adapter_ts_vercel_ai",
18
18
  ...options,
19
19
  model
20
20
  });
21
21
  if (!result.compressed) return params;
22
- const compressedPrompt = chunkN7HYJMEN_cjs.openAIToVercel(result.messages);
22
+ const compressedPrompt = chunkCINZDAE4_cjs.openAIToVercel(result.messages);
23
23
  return { ...params, prompt: compressedPrompt };
24
24
  }
25
25
  };
26
26
  }
27
27
  async function compressVercelMessages(messages, options = {}) {
28
- const openaiMessages = chunkN7HYJMEN_cjs.vercelToOpenAI(messages);
29
- const result = await chunkN7HYJMEN_cjs.compress(openaiMessages, {
28
+ const openaiMessages = chunkCINZDAE4_cjs.vercelToOpenAI(messages);
29
+ const result = await chunkCINZDAE4_cjs.compress(openaiMessages, {
30
30
  stack: "adapter_ts_vercel_ai",
31
31
  ...options
32
32
  });
33
- const vercelMessages = chunkN7HYJMEN_cjs.openAIToVercel(result.messages);
33
+ const vercelMessages = chunkCINZDAE4_cjs.openAIToVercel(result.messages);
34
34
  return {
35
35
  ...result,
36
36
  messages: vercelMessages
@@ -1,4 +1,4 @@
1
- import { C as CompressOptions, a as CompressResult } from '../types-Cjdl75F4.cjs';
1
+ import { C as CompressOptions, a as CompressResult } from '../types-Bxcai7Tt.cjs';
2
2
 
3
3
  /**
4
4
  * Minimal structural type for Vercel AI SDK language models.
@@ -1,4 +1,4 @@
1
- import { C as CompressOptions, a as CompressResult } from '../types-Cjdl75F4.js';
1
+ import { C as CompressOptions, a as CompressResult } from '../types-Bxcai7Tt.js';
2
2
 
3
3
  /**
4
4
  * Minimal structural type for Vercel AI SDK language models.
@@ -1,4 +1,4 @@
1
- import { vercelToOpenAI, compress, openAIToVercel } from '../chunk-FJQ65W3R.js';
1
+ import { vercelToOpenAI, compress, openAIToVercel } from '../chunk-UVX5TGIN.js';
2
2
  import { createRequire } from 'module';
3
3
 
4
4
  function headroomMiddleware(options = {}) {
@@ -181,6 +181,11 @@ function makeFallbackResult(messages) {
181
181
  compressed: false
182
182
  };
183
183
  }
184
+ function trimTrailingSlashes(value) {
185
+ let end = value.length;
186
+ while (end > 0 && value.charCodeAt(end - 1) === 47) end--;
187
+ return value.slice(0, end);
188
+ }
184
189
  var ChatCompletions = class {
185
190
  constructor(client) {
186
191
  this.client = client;
@@ -283,7 +288,9 @@ var HeadroomClient = class {
283
288
  /** Anthropic-style messages API. */
284
289
  messages;
285
290
  constructor(options = {}) {
286
- this.baseUrl = (options.baseUrl ?? getEnv("HEADROOM_BASE_URL") ?? DEFAULT_BASE_URL).replace(/\/+$/, "");
291
+ this.baseUrl = trimTrailingSlashes(
292
+ options.baseUrl ?? getEnv("HEADROOM_BASE_URL") ?? DEFAULT_BASE_URL
293
+ );
287
294
  this.apiKey = options.apiKey ?? getEnv("HEADROOM_API_KEY");
288
295
  this.timeout = options.timeout ?? DEFAULT_TIMEOUT;
289
296
  this.fallback = options.fallback ?? true;
@@ -607,7 +614,11 @@ function detectFormat(messages) {
607
614
  for (const part of msg.content) {
608
615
  if (part.type === "tool-call" || part.type === "tool-result") return "vercel";
609
616
  if (part.type === "tool_use" || part.type === "tool_result") return "anthropic";
610
- if (part.type === "image" && part.source?.type) return "anthropic";
617
+ if ((part.type === "image" || part.type === "document") && part.source?.type) return "anthropic";
618
+ if (part && typeof part === "object") {
619
+ if (part.type === "file" && ("data" in part || "mediaType" in part)) return "vercel";
620
+ if (part.type === "image" && "image" in part) return "vercel";
621
+ }
611
622
  }
612
623
  }
613
624
  }
@@ -626,6 +637,50 @@ function openAIImageBlock(url) {
626
637
  if (m) return { type: "image", source: { type: "base64", media_type: m[1], data: m[2] } };
627
638
  return { type: "image", source: { type: "url", url } };
628
639
  }
640
+ var ANTHROPIC_DOCUMENT_FIELDS = ["title", "context", "citations", "cache_control"];
641
+ function anthropicMediaImageUrl(block) {
642
+ if (block.type === "image") {
643
+ const url = anthropicImageUrl(block.source);
644
+ return url ? { url } : null;
645
+ }
646
+ if (block.type !== "document" || !block.source || typeof block.source !== "object") return null;
647
+ const src = block.source;
648
+ let out;
649
+ if (src.type === "base64" && src.media_type && src.data) {
650
+ out = { url: `data:${src.media_type};base64,${src.data}` };
651
+ } else if (src.type === "url" && typeof src.url === "string") {
652
+ out = { url: src.url, mime_type: "application/pdf" };
653
+ } else if (src.type === "text" && typeof src.data === "string") {
654
+ out = {
655
+ url: `data:${src.media_type || "text/plain"};base64,${bytesToBase64(new TextEncoder().encode(src.data))}`,
656
+ source_type: "text"
657
+ };
658
+ } else {
659
+ out = { url: "data:;base64,", source: src };
660
+ }
661
+ const fields = {};
662
+ for (const k of ANTHROPIC_DOCUMENT_FIELDS) if (block[k] !== void 0) fields[k] = block[k];
663
+ if (Object.keys(fields).length > 0) out.document = fields;
664
+ return out;
665
+ }
666
+ function anthropicMediaBlock(imageUrl) {
667
+ const url = imageUrl.url;
668
+ const m = /^data:([^;,]*);base64,(.*)$/s.exec(url);
669
+ const mediaType = m && m[1] || (typeof imageUrl.mime_type === "string" ? imageUrl.mime_type : void 0);
670
+ const fields = imageUrl.document && typeof imageUrl.document === "object" ? imageUrl.document : {};
671
+ if (imageUrl.source && typeof imageUrl.source === "object") {
672
+ return { type: "document", source: imageUrl.source, ...fields };
673
+ }
674
+ if (imageUrl.source_type === "text" && m) {
675
+ return { type: "document", source: { type: "text", media_type: "text/plain", data: base64ToText(m[2]) }, ...fields };
676
+ }
677
+ if (mediaType === "application/pdf") {
678
+ const source = m ? { type: "base64", media_type: "application/pdf", data: m[2] } : { type: "url", url };
679
+ return { type: "document", source, ...fields };
680
+ }
681
+ if (mediaType === void 0 || mediaType.startsWith("image/")) return openAIImageBlock(url);
682
+ return null;
683
+ }
629
684
  function anthropicToOpenAI(messages) {
630
685
  const result = [];
631
686
  for (const msg of messages) {
@@ -636,12 +691,12 @@ function anthropicToOpenAI(messages) {
636
691
  }
637
692
  if (Array.isArray(msg.content)) {
638
693
  const toolResults = msg.content.filter((b) => b.type === "tool_result");
639
- const hasImages = msg.content.some((b) => b.type === "image");
640
- if (hasImages) {
641
- const parts = msg.content.filter((b) => b.type === "text" || b.type === "image").map((b) => {
694
+ const hasMedia = msg.content.some((b) => b.type === "image" || b.type === "document");
695
+ if (hasMedia) {
696
+ const parts = msg.content.filter((b) => b.type === "text" || b.type === "image" || b.type === "document").map((b) => {
642
697
  if (b.type === "text") return { type: "text", text: b.text };
643
- const url = anthropicImageUrl(b.source);
644
- return url ? { type: "image_url", image_url: { url } } : null;
698
+ const imageUrl = anthropicMediaImageUrl(b);
699
+ return imageUrl ? { type: "image_url", image_url: imageUrl } : null;
645
700
  }).filter((p) => p !== null);
646
701
  if (parts.length > 0) {
647
702
  result.push({ role: "user", content: parts });
@@ -674,7 +729,17 @@ function anthropicToOpenAI(messages) {
674
729
  if (Array.isArray(msg.content)) {
675
730
  const textBlocks = msg.content.filter((b) => b.type === "text");
676
731
  const toolUseBlocks = msg.content.filter((b) => b.type === "tool_use");
677
- const content = textBlocks.length > 0 ? textBlocks.map((b) => b.text).join("\n") : null;
732
+ const hasMedia = msg.content.some((b) => b.type === "image" || b.type === "document");
733
+ let content;
734
+ if (hasMedia) {
735
+ content = msg.content.filter((b) => b.type === "text" || b.type === "image" || b.type === "document").map((b) => {
736
+ if (b.type === "text") return { type: "text", text: b.text };
737
+ const imageUrl = anthropicMediaImageUrl(b);
738
+ return imageUrl ? { type: "image_url", image_url: imageUrl } : null;
739
+ }).filter((p) => p !== null);
740
+ } else {
741
+ content = textBlocks.length > 0 ? textBlocks.map((b) => b.text).join("\n") : null;
742
+ }
678
743
  const openaiMsg = { role: "assistant", content };
679
744
  if (toolUseBlocks.length > 0) {
680
745
  openaiMsg.tool_calls = toolUseBlocks.map((b) => ({
@@ -708,7 +773,7 @@ function openAIToAnthropic(messages) {
708
773
  role: "user",
709
774
  content: msg.content.map((p) => {
710
775
  if (p.type === "text") return { type: "text", text: p.text };
711
- if (p.type === "image_url") return openAIImageBlock(p.image_url.url);
776
+ if (p.type === "image_url") return anthropicMediaBlock(p.image_url) ?? { type: "text", text: "" };
712
777
  return { type: "text", text: "" };
713
778
  })
714
779
  });
@@ -717,7 +782,17 @@ function openAIToAnthropic(messages) {
717
782
  }
718
783
  if (msg.role === "assistant") {
719
784
  const blocks = [];
720
- if (msg.content) blocks.push({ type: "text", text: msg.content });
785
+ if (typeof msg.content === "string") {
786
+ if (msg.content) blocks.push({ type: "text", text: msg.content });
787
+ } else if (Array.isArray(msg.content)) {
788
+ for (const g of groupAssistantParts(msg.content)) {
789
+ if ("text" in g) blocks.push({ type: "text", text: g.text });
790
+ else {
791
+ const block = anthropicMediaBlock(g.image_url);
792
+ if (block) blocks.push(block);
793
+ }
794
+ }
795
+ }
721
796
  if (msg.tool_calls) {
722
797
  for (const tc of msg.tool_calls) {
723
798
  blocks.push({
@@ -746,6 +821,121 @@ function openAIToAnthropic(messages) {
746
821
  }
747
822
  return result;
748
823
  }
824
+ function bytesToBase64(bytes) {
825
+ if (typeof Buffer !== "undefined") return Buffer.from(bytes).toString("base64");
826
+ let binary = "";
827
+ for (let i = 0; i < bytes.length; i += 32768) {
828
+ binary += String.fromCharCode(...bytes.subarray(i, i + 32768));
829
+ }
830
+ return btoa(binary);
831
+ }
832
+ function parseUrl(value) {
833
+ try {
834
+ return new URL(value);
835
+ } catch {
836
+ return null;
837
+ }
838
+ }
839
+ function base64ToText(b64) {
840
+ if (typeof Buffer !== "undefined") return Buffer.from(b64, "base64").toString("utf8");
841
+ const binary = atob(b64);
842
+ const bytes = new Uint8Array(binary.length);
843
+ for (let i = 0; i < binary.length; i++) bytes[i] = binary.charCodeAt(i);
844
+ return new TextDecoder().decode(bytes);
845
+ }
846
+ function isProviderReference(v) {
847
+ return !!v && typeof v === "object" && !Array.isArray(v) && !(v instanceof Uint8Array) && !(v instanceof ArrayBuffer) && !(v instanceof URL) && v.type === void 0 && Object.values(v).every((x) => typeof x === "string");
848
+ }
849
+ function vercelMediaImageUrl(p) {
850
+ let data = p.type === "file" ? p.data : p.image;
851
+ const mediaType = typeof p.mediaType === "string" ? p.mediaType : p.mimeType;
852
+ const out = { url: "" };
853
+ if (p.type === "file") out.part = "file";
854
+ if (typeof p.filename === "string") out.filename = p.filename;
855
+ if (data && typeof data === "object" && typeof data.type === "string") {
856
+ if (data.type === "data") {
857
+ out.data_tag = "data";
858
+ data = data.data;
859
+ } else if (data.type === "url") {
860
+ out.data_tag = "url";
861
+ data = data.url;
862
+ } else if (data.type === "text" && typeof data.text === "string") {
863
+ out.data_tag = "text";
864
+ out.url = `data:${mediaType ?? "text/plain"};base64,${bytesToBase64(new TextEncoder().encode(data.text))}`;
865
+ return out;
866
+ } else if (data.type === "reference" && data.reference && typeof data.reference === "object") {
867
+ out.data_tag = "reference";
868
+ out.reference = data.reference;
869
+ out.url = `data:${mediaType ?? ""};base64,`;
870
+ return out;
871
+ } else {
872
+ return null;
873
+ }
874
+ } else if (isProviderReference(data)) {
875
+ out.reference = data;
876
+ out.url = `data:${mediaType ?? ""};base64,`;
877
+ return out;
878
+ }
879
+ let carryType = false;
880
+ if (data instanceof Uint8Array || data instanceof ArrayBuffer) {
881
+ const bytes = data instanceof Uint8Array ? data : new Uint8Array(data);
882
+ out.url = `data:${mediaType ?? ""};base64,${bytesToBase64(bytes)}`;
883
+ } else if (data instanceof URL) {
884
+ out.url = data.toString();
885
+ carryType = true;
886
+ } else if (typeof data === "string") {
887
+ if (data.startsWith("data:")) out.url = data;
888
+ else if (parseUrl(data)) {
889
+ out.url = data;
890
+ carryType = true;
891
+ } else out.url = `data:${mediaType ?? ""};base64,${data}`;
892
+ } else {
893
+ return null;
894
+ }
895
+ if (carryType && mediaType) out.mime_type = mediaType;
896
+ return out;
897
+ }
898
+ function vercelMediaPart(imageUrl) {
899
+ const url = imageUrl.url;
900
+ const tag = typeof imageUrl.data_tag === "string" ? imageUrl.data_tag : void 0;
901
+ const m = /^data:([^;,]*);base64,(.*)$/s.exec(url);
902
+ let mediaType = typeof imageUrl.mime_type === "string" ? imageUrl.mime_type : void 0;
903
+ if (m && m[1]) mediaType = m[1];
904
+ let data;
905
+ if (imageUrl.reference && typeof imageUrl.reference === "object") {
906
+ data = tag === "reference" ? { type: "reference", reference: imageUrl.reference } : imageUrl.reference;
907
+ } else if (m) {
908
+ data = m[2];
909
+ if (tag === "data") data = { type: "data", data: m[2] };
910
+ else if (tag === "text") data = { type: "text", text: base64ToText(m[2]) };
911
+ } else {
912
+ const parsed = parseUrl(url);
913
+ data = parsed ?? url;
914
+ if (tag === "url") data = { type: "url", url: parsed ?? url };
915
+ }
916
+ const isFile = imageUrl.part === "file" || mediaType !== void 0 && !mediaType.startsWith("image/");
917
+ if (isFile) {
918
+ const part2 = { type: "file", mediaType: mediaType ?? "application/octet-stream", data };
919
+ if (typeof imageUrl.filename === "string") part2.filename = imageUrl.filename;
920
+ return part2;
921
+ }
922
+ const part = { type: "image", image: data };
923
+ if (mediaType) part.mediaType = mediaType;
924
+ return part;
925
+ }
926
+ function groupAssistantParts(parts) {
927
+ const hasMedia = parts.some((p) => p.type === "image_url");
928
+ if (!hasMedia) {
929
+ const text = parts.filter((p) => p.type === "text").map((p) => p.text).join("\n");
930
+ return text ? [{ text }] : [];
931
+ }
932
+ const out = [];
933
+ for (const p of parts) {
934
+ if (p.type === "text") out.push({ text: p.text });
935
+ else if (p.type === "image_url") out.push({ image_url: p.image_url });
936
+ }
937
+ return out;
938
+ }
749
939
  function vercelToOpenAI(messages) {
750
940
  const result = [];
751
941
  for (const msg of messages) {
@@ -760,18 +950,15 @@ function vercelToOpenAI(messages) {
760
950
  }
761
951
  const parts = Array.isArray(msg.content) ? msg.content : [];
762
952
  const textParts = parts.filter((p) => p.type === "text");
763
- const imageParts = parts.filter((p) => p.type === "image");
764
- if (imageParts.length === 0 && textParts.length > 0) {
953
+ const mediaParts = parts.filter((p) => p.type === "image" || p.type === "file");
954
+ if (mediaParts.length === 0 && textParts.length > 0) {
765
955
  result.push({ role: "user", content: textParts.map((p) => p.text).join("") });
766
956
  } else {
767
- const openaiParts = parts.filter((p) => p.type === "text" || p.type === "image").map((p) => {
957
+ const openaiParts = parts.filter((p) => p.type === "text" || p.type === "image" || p.type === "file").map((p) => {
768
958
  if (p.type === "text") return { type: "text", text: p.text };
769
- if (p.type === "image") {
770
- const url = p.image instanceof URL ? p.image.toString() : String(p.image);
771
- return { type: "image_url", image_url: { url } };
772
- }
773
- return { type: "text", text: "" };
774
- });
959
+ const imageUrl = vercelMediaImageUrl(p);
960
+ return imageUrl ? { type: "image_url", image_url: imageUrl } : null;
961
+ }).filter((p) => p !== null);
775
962
  result.push({ role: "user", content: openaiParts });
776
963
  }
777
964
  continue;
@@ -784,7 +971,17 @@ function vercelToOpenAI(messages) {
784
971
  const parts = Array.isArray(msg.content) ? msg.content : [];
785
972
  const textParts = parts.filter((p) => p.type === "text");
786
973
  const toolCallParts = parts.filter((p) => p.type === "tool-call");
787
- const content = textParts.length > 0 ? textParts.map((p) => p.text).join("") : null;
974
+ const hasFiles = parts.some((p) => p.type === "file");
975
+ let content;
976
+ if (hasFiles) {
977
+ content = parts.filter((p) => p.type === "text" || p.type === "file").map((p) => {
978
+ if (p.type === "text") return { type: "text", text: p.text };
979
+ const imageUrl = vercelMediaImageUrl(p);
980
+ return imageUrl ? { type: "image_url", image_url: imageUrl } : null;
981
+ }).filter((p) => p !== null);
982
+ } else {
983
+ content = textParts.length > 0 ? textParts.map((p) => p.text).join("") : null;
984
+ }
788
985
  const openaiMsg = { role: "assistant", content };
789
986
  if (toolCallParts.length > 0) {
790
987
  openaiMsg.tool_calls = toolCallParts.map((p) => ({
@@ -836,7 +1033,7 @@ function openAIToVercel(messages) {
836
1033
  } else if (Array.isArray(msg.content)) {
837
1034
  const parts = msg.content.map((p) => {
838
1035
  if (p.type === "text") return { type: "text", text: p.text };
839
- if (p.type === "image_url") return { type: "image", image: new URL(p.image_url.url) };
1036
+ if (p.type === "image_url") return vercelMediaPart(p.image_url);
840
1037
  return { type: "text", text: "" };
841
1038
  });
842
1039
  result.push({ role: "user", content: parts });
@@ -845,7 +1042,14 @@ function openAIToVercel(messages) {
845
1042
  }
846
1043
  if (msg.role === "assistant") {
847
1044
  const parts = [];
848
- if (msg.content) parts.push({ type: "text", text: msg.content });
1045
+ if (typeof msg.content === "string") {
1046
+ if (msg.content) parts.push({ type: "text", text: msg.content });
1047
+ } else if (Array.isArray(msg.content)) {
1048
+ for (const p of msg.content) {
1049
+ if (p.type === "text") parts.push({ type: "text", text: p.text });
1050
+ else if (p.type === "image_url") parts.push(vercelMediaPart(p.image_url));
1051
+ }
1052
+ }
849
1053
  if (msg.tool_calls) {
850
1054
  for (const tc of msg.tool_calls) {
851
1055
  toolNames.set(tc.id, tc.function.name);
@@ -950,7 +1154,17 @@ function geminiToOpenAI(messages) {
950
1154
  if (role === "assistant") {
951
1155
  const textParts = parts.filter((p) => p.text !== void 0);
952
1156
  const funcCalls = parts.filter((p) => p.functionCall);
953
- const content = textParts.length > 0 ? textParts.map((p) => p.text).join("\n") : null;
1157
+ const hasMedia = parts.some((p) => p.inlineData || p.fileData);
1158
+ let content;
1159
+ if (hasMedia) {
1160
+ content = parts.filter((p) => p.text !== void 0 || p.inlineData || p.fileData).map((p) => {
1161
+ if (p.text !== void 0) return { type: "text", text: p.text };
1162
+ const imageUrl = geminiMediaImageUrl(p);
1163
+ return imageUrl ? { type: "image_url", image_url: imageUrl } : null;
1164
+ }).filter((p) => p !== null);
1165
+ } else {
1166
+ content = textParts.length > 0 ? textParts.map((p) => p.text).join("\n") : null;
1167
+ }
954
1168
  const openaiMsg = { role: "assistant", content };
955
1169
  if (funcCalls.length > 0) {
956
1170
  openaiMsg.tool_calls = funcCalls.map((p) => ({
@@ -991,7 +1205,14 @@ function openAIToGemini(messages) {
991
1205
  }
992
1206
  if (msg.role === "assistant") {
993
1207
  const parts = [];
994
- if (msg.content) parts.push({ text: msg.content });
1208
+ if (typeof msg.content === "string") {
1209
+ if (msg.content) parts.push({ text: msg.content });
1210
+ } else if (Array.isArray(msg.content)) {
1211
+ for (const g of groupAssistantParts(msg.content)) {
1212
+ if ("text" in g) parts.push({ text: g.text });
1213
+ else parts.push(geminiMediaPart(g.image_url));
1214
+ }
1215
+ }
995
1216
  if (msg.tool_calls) {
996
1217
  for (const tc of msg.tool_calls) {
997
1218
  parts.push({
@@ -1183,5 +1404,5 @@ exports.parseSSE = parseSSE;
1183
1404
  exports.snakeToCamel = snakeToCamel;
1184
1405
  exports.toOpenAI = toOpenAI;
1185
1406
  exports.vercelToOpenAI = vercelToOpenAI;
1186
- //# sourceMappingURL=chunk-N7HYJMEN.cjs.map
1187
- //# sourceMappingURL=chunk-N7HYJMEN.cjs.map
1407
+ //# sourceMappingURL=chunk-CINZDAE4.cjs.map
1408
+ //# sourceMappingURL=chunk-CINZDAE4.cjs.map