From 92a221904fe18f55d7846a6b3cfe0f427c06f2d9 Mon Sep 17 00:00:00 2001 From: Niko Date: Wed, 12 Aug 2026 10:44:13 +0300 Subject: [PATCH] fix(llm-providers): pass complete data URLs through image conversion MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Screenshot tools can hand the OpenAI-compatible converter a full `data:;base64,...` URL instead of raw base64. The converter unconditionally wrapped the value in a second `data:;base64,` prefix, nesting the prefix inside the payload — the resulting URL contains `:` and `,` where base64 is expected, so providers reject the request with "invalid base64-encoded value" and vision turns fail on models like ChatGPT-5.6 Luna. Detect a leading `data:` prefix and pass the value through untouched; raw base64, Uint8Array, and URL inputs behave exactly as before. 🤖 Generated with Codebuff Co-Authored-By: Codebuff --- ...to-openai-compatible-chat-messages.test.ts | 27 +++++++++++++++++++ ...vert-to-openai-compatible-chat-messages.ts | 19 ++++++++----- 2 files changed, 40 insertions(+), 6 deletions(-) diff --git a/packages/llm-providers/src/openai-compatible/chat/convert-to-openai-compatible-chat-messages.test.ts b/packages/llm-providers/src/openai-compatible/chat/convert-to-openai-compatible-chat-messages.test.ts index 5b8c7b5b35..09cf152e44 100644 --- a/packages/llm-providers/src/openai-compatible/chat/convert-to-openai-compatible-chat-messages.test.ts +++ b/packages/llm-providers/src/openai-compatible/chat/convert-to-openai-compatible-chat-messages.test.ts @@ -74,6 +74,33 @@ describe('user messages', () => { ]) }) + it('should not double-wrap an already-complete data URL', async () => { + const result = convertToOpenAICompatibleChatMessages([ + { + role: 'user', + content: [ + { + type: 'file', + data: 'data:image/png;base64,AAECAw==', + mediaType: 'image/png', + }, + ], + }, + ]) + + expect(result).toEqual([ + { + role: 'user', + content: [ + { + type: 'image_url', + image_url: { url: 'data:image/png;base64,AAECAw==' }, + }, + ], + }, + ]) + }) + it('should handle URL-based images', async () => { const result = convertToOpenAICompatibleChatMessages([ { diff --git a/packages/llm-providers/src/openai-compatible/chat/convert-to-openai-compatible-chat-messages.ts b/packages/llm-providers/src/openai-compatible/chat/convert-to-openai-compatible-chat-messages.ts index 1c984d6438..b1a5c129c9 100644 --- a/packages/llm-providers/src/openai-compatible/chat/convert-to-openai-compatible-chat-messages.ts +++ b/packages/llm-providers/src/openai-compatible/chat/convert-to-openai-compatible-chat-messages.ts @@ -41,14 +41,21 @@ export function convertToOpenAICompatibleChatMessages( const mediaType = part.mediaType === 'image/*' ? 'image/jpeg' : part.mediaType + const dataUrl = + part.data instanceof URL + ? part.data.toString() + : typeof part.data === 'string' && + part.data.startsWith('data:') + ? // Already a complete data URL (e.g. produced by a + // screenshot tool): wrapping it again would nest a + // second `data:;base64,` prefix inside the + // payload, which providers reject as invalid base64. + part.data + : `data:${mediaType};base64,${convertToBase64(part.data)}` + return { type: 'image_url', - image_url: { - url: - part.data instanceof URL - ? part.data.toString() - : `data:${mediaType};base64,${convertToBase64(part.data)}`, - }, + image_url: { url: dataUrl }, ...partMetadata, } } else {