From f95fa55cceacbac154ef7be9f52ef27ed65d2431 Mon Sep 17 00:00:00 2001 From: Danny Avila Date: Wed, 27 May 2026 22:00:53 -0700 Subject: [PATCH] =?UTF-8?q?=F0=9F=93=8E=20fix:=20Preserve=20Gemini=20PDF?= =?UTF-8?q?=20Media=20Blocks=20(#13357)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../api/src/files/encode/document.spec.ts | 32 +++++++++++++++++++ packages/api/src/files/encode/document.ts | 31 ++++++++++++------ 2 files changed, 53 insertions(+), 10 deletions(-) diff --git a/packages/api/src/files/encode/document.spec.ts b/packages/api/src/files/encode/document.spec.ts index f285d47d5d..846b61eb88 100644 --- a/packages/api/src/files/encode/document.spec.ts +++ b/packages/api/src/files/encode/document.spec.ts @@ -749,6 +749,38 @@ describe('encodeAndFormatDocuments - fileConfig integration', () => { file_data: `data:application/pdf;base64,${mockContent}`, }); }); + + it.each([Providers.GOOGLE, Providers.VERTEXAI] as const)( + 'should format %s PDF as media block when responses API is enabled', + async (provider) => { + const req = createMockRequest(15, provider) as ServerRequest; + const file = createMockFile(10); + + const mockContent = Buffer.from('test-pdf-content').toString('base64'); + mockedGetFileStream.mockResolvedValue({ + file, + content: mockContent, + metadata: file, + }); + + mockedValidatePdf.mockResolvedValue({ isValid: true }); + + const result = await encodeAndFormatDocuments( + req, + [file], + { provider, useResponsesApi: true }, + mockStrategyFunctions, + ); + + expect(result.documents).toHaveLength(1); + expect(result.documents[0]).toMatchObject({ + type: 'media', + mimeType: 'application/pdf', + data: mockContent, + }); + expect(result.documents[0]).not.toHaveProperty('type', 'input_file'); + }, + ); }); describe('Generic document encoding path', () => { diff --git a/packages/api/src/files/encode/document.ts b/packages/api/src/files/encode/document.ts index f04ad43ef2..9f595fee1e 100644 --- a/packages/api/src/files/encode/document.ts +++ b/packages/api/src/files/encode/document.ts @@ -55,6 +55,14 @@ function formatDocumentBlock( return document; } + if (provider === Providers.GOOGLE || provider === Providers.VERTEXAI) { + return { + type: 'media', + mimeType, + data: content, + }; + } + const resolvedFilename = filename ?? 'document'; if (useResponsesApi) { @@ -65,14 +73,6 @@ function formatDocumentBlock( }; } - if (provider === Providers.GOOGLE || provider === Providers.VERTEXAI) { - return { - type: 'media', - mimeType, - data: content, - }; - } - if (isOpenAILikeProvider(provider) && provider !== Providers.AZURE) { return { type: 'file', @@ -86,6 +86,18 @@ function formatDocumentBlock( return null; } +function getBase64DecodedByteCount(content: string): number { + let paddingChars = 0; + + if (content.endsWith('==')) { + paddingChars = 2; + } else if (content.endsWith('=')) { + paddingChars = 1; + } + + return Math.floor((content.length * 3) / 4) - paddingChars; +} + /** * Encodes and formats document files for various providers. * @@ -208,8 +220,7 @@ export async function encodeAndFormatDocuments( result.files.push(metadata); } } else if (isDocSupported && !isBedrock) { - const paddingChars = content.endsWith('==') ? 2 : content.endsWith('=') ? 1 : 0; - const decodedByteCount = Math.floor((content.length * 3) / 4) - paddingChars; + const decodedByteCount = getBase64DecodedByteCount(content); if (configuredFileSizeLimit && decodedByteCount > configuredFileSizeLimit) { throw new Error( `File size (~${(decodedByteCount / 1024 / 1024).toFixed(1)}MB) exceeds the configured limit for ${provider}`,