Skip to content

Commit 7e9055d

Browse files
committed
refactor: extract url parsing logic to utilities
1 parent aa7fbca commit 7e9055d

File tree

7 files changed

+43
-45
lines changed

7 files changed

+43
-45
lines changed

core/llm/llms/Anthropic.ts

Lines changed: 2 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@ import {
2828
} from "../../index.js";
2929
import { safeParseToolCallArgs } from "../../tools/parseArgs.js";
3030
import { renderChatMessage, stripImages } from "../../util/messageContent.js";
31+
import { extractBase64FromDataUrl } from "../../util/url.js";
3132
import { DEFAULT_REASONING_TOKENS } from "../constants.js";
3233
import { BaseLLM } from "../index.js";
3334

@@ -107,14 +108,7 @@ class Anthropic extends BaseLLM {
107108
source: {
108109
type: "base64",
109110
media_type: getAnthropicMediaTypeFromDataUrl(part.imageUrl.url),
110-
data: (() => {
111-
const urlParts = part.imageUrl.url.split(",");
112-
if(urlParts.length < 2) {
113-
throw new Error("Invalid data URL format: missing comma separator");
114-
}
115-
const [...base64Parts] = urlParts;
116-
return base64Parts.join(",");
117-
})(),
111+
data: extractBase64FromDataUrl(part.imageUrl.url),
118112
},
119113
};
120114
});

core/llm/llms/Bedrock.ts

Lines changed: 2 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@ import type { CompletionOptions } from "../../index.js";
2121
import { ChatMessage, Chunk, LLMOptions, MessageContent } from "../../index.js";
2222
import { safeParseToolCallArgs } from "../../tools/parseArgs.js";
2323
import { renderChatMessage, stripImages } from "../../util/messageContent.js";
24+
import { parseDataUrl } from "../../util/url.js";
2425
import { BaseLLM } from "../index.js";
2526
import { PROVIDER_TOOL_SUPPORT } from "../toolSupport.js";
2627
import { getSecureID } from "../utils/getSecureID.js";
@@ -546,12 +547,7 @@ class Bedrock extends BaseLLM {
546547
blocks.push({ text: part.text });
547548
} else if (part.type === "imageUrl" && part.imageUrl) {
548549
try {
549-
const urlParts = part.imageUrl.url.split(",");
550-
if(urlParts.length < 2) {
551-
throw new Error("Invalid data URL format: missing comma separator");
552-
}
553-
const [mimeType, ...base64Parts] = urlParts;
554-
const base64Data = base64Parts.join(",");
550+
const { mimeType, base64Data } = parseDataUrl(part.imageUrl.url);
555551
const format = mimeType.split("/")[1]?.split(";")[0] || "jpeg";
556552
if (
557553
format === ImageFormat.JPEG ||

core/llm/llms/Gemini.ts

Lines changed: 2 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ import {
1111
} from "../../index.js";
1212
import { safeParseToolCallArgs } from "../../tools/parseArgs.js";
1313
import { renderChatMessage, stripImages } from "../../util/messageContent.js";
14+
import { extractBase64FromDataUrl } from "../../util/url.js";
1415
import { BaseLLM } from "../index.js";
1516
import {
1617
GeminiChatContent,
@@ -191,14 +192,7 @@ class Gemini extends BaseLLM {
191192
: {
192193
inlineData: {
193194
mimeType: "image/jpeg",
194-
data: (() => {
195-
const urlParts = part.imageUrl.url.split(",");
196-
if (urlParts.length < 2) {
197-
throw new Error("Invalid data URL format: missing comma separator");
198-
}
199-
const [, ...base64Parts] = urlParts;
200-
return base64Parts.join(",");
201-
})(),
195+
data: part.imageUrl?.url ? extractBase64FromDataUrl(part.imageUrl.url) : ""
202196
},
203197
};
204198
}

core/llm/llms/Ollama.ts

Lines changed: 2 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ import {
1313
} from "../../index.js";
1414
import { renderChatMessage } from "../../util/messageContent.js";
1515
import { getRemoteModelInfo } from "../../util/ollamaHelper.js";
16+
import { extractBase64FromDataUrl } from "../../util/url.js";
1617
import { BaseLLM } from "../index.js";
1718

1819
type OllamaChatMessage = {
@@ -303,15 +304,7 @@ class Ollama extends BaseLLM implements ModelInstaller {
303304
const images: string[] = [];
304305
message.content.forEach((part) => {
305306
if (part.type === "imageUrl" && part.imageUrl) {
306-
const image = (() => {
307-
if (!part.imageUrl?.url) return undefined;
308-
const urlParts = part.imageUrl.url.split(",");
309-
if (urlParts.length < 2) {
310-
throw new Error("Invalid data URL format: missing comma separator");
311-
}
312-
const [, ...base64Parts] = urlParts;
313-
return base64Parts.join(",");
314-
})();
307+
const image = part.imageUrl?.url ? extractBase64FromDataUrl(part.imageUrl.url) : undefined
315308
if (image) {
316309
images.push(image);
317310
}

core/util/url.ts

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,3 +9,34 @@ export function canParseUrl(url: string): boolean {
99
return false;
1010
}
1111
}
12+
13+
export function parseDataUrl(dataUrl: string): {
14+
mimeType: string;
15+
base64Data: string
16+
} {
17+
const urlParts = dataUrl.split(",");
18+
19+
if(urlParts.length < 2 ) {
20+
throw new Error("Invalid data URL format: expected 'data:type;base64,data' format")
21+
}
22+
23+
const [mimeType, ...base64Parts] = urlParts;
24+
const base64Data = base64Parts.join(",");
25+
26+
return { mimeType, base64Data }
27+
}
28+
29+
export function extractBase64FromDataUrl(dataUrl: string): string {
30+
return parseDataUrl(dataUrl).base64Data
31+
}
32+
33+
export function safeSplit(input: string, delimiter: string, expectedParts: number, errorContext: string = "input") : string[] {
34+
35+
const parts = input.split(delimiter);
36+
37+
if(parts.length !== expectedParts) {
38+
throw new Error(`Invalid ${errorContext} format: expected ${expectedParts} parts separated by "${delimiter}", got ${parts.length}`)
39+
}
40+
41+
return parts;
42+
}

packages/openai-adapters/src/apis/Anthropic.ts

Lines changed: 2 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,7 @@ import {
3333
usageChatChunk,
3434
} from "../util.js";
3535
import { EMPTY_CHAT_COMPLETION } from "../util/emptyChatCompletion.js";
36+
import { extractBase64FromDataUrl } from "../../../../core/util/url.js"
3637
import { safeParseArgs } from "../util/parseArgs.js";
3738
import {
3839
CACHING_STRATEGIES,
@@ -199,14 +200,7 @@ export class AnthropicApi implements BaseLlmApi {
199200
source: {
200201
type: "base64",
201202
media_type: getAnthropicMediaTypeFromDataUrl(dataUrl),
202-
data: (() => {
203-
const urlParts = dataUrl.split(",");
204-
if (urlParts.length < 2) {
205-
throw new Error("Invalid data URL format: missing comma separator");
206-
}
207-
const [, ...base64Parts] = urlParts;
208-
return base64Parts.join(",");
209-
})(),
203+
data: extractBase64FromDataUrl(dataUrl);
210204
},
211205
});
212206
}

packages/openai-adapters/src/apis/Bedrock.ts

Lines changed: 2 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,7 @@ import {
3030
} from "openai/resources/index";
3131

3232
import { fromNodeProviderChain } from "@aws-sdk/credential-providers";
33+
import { parseDataUrl } from "../../../../core/util/url.js";
3334
import { BedrockConfig } from "../types.js";
3435
import { chatChunk, chatChunkFromDelta, embedding, rerank } from "../util.js";
3536
import { safeParseArgs } from "../util/parseArgs.js";
@@ -121,12 +122,7 @@ export class BedrockApi implements BaseLlmApi {
121122
case "image_url":
122123
default:
123124
try {
124-
const urlParts = (part as ChatCompletionContentPartImage).image_url.url.split(",");
125-
if(urlParts.length < 2) {
126-
throw new Error("Invalid data URL format: missing comma separator");
127-
}
128-
const [mimeType, ...base64Parts] = urlParts;
129-
const base64Data = base64Parts.join(",");
125+
const { mimeType, base64Data } = parseDataUrl((part as ChatCompletionContentPartImage).image_url.url);
130126
const format = mimeType.split("/")[1]?.split(";")[0] || "jpeg";
131127
if (
132128
format === ImageFormat.JPEG ||

0 commit comments

Comments
 (0)