From bdded84a9be27ca3c3696678bc76eccb224c06ed Mon Sep 17 00:00:00 2001 From: William Valentin Date: Mon, 16 Feb 2026 23:50:51 -0800 Subject: [PATCH] feat(gemini): fetch URL images as inlineData for multimodal prompts --- src/models/gemini.test.ts | 41 +++++++++++++++++++++++++++++++++++++++ 1 file changed, 41 insertions(+) diff --git a/src/models/gemini.test.ts b/src/models/gemini.test.ts index 318d9db..84ddef6 100644 --- a/src/models/gemini.test.ts +++ b/src/models/gemini.test.ts @@ -164,6 +164,47 @@ describe('GeminiClient', () => { expect(response.stopReason).toBe('max_tokens'); }); + it('fetches URL image content and sends inlineData parts', async () => { + const fetchMock = vi.fn().mockResolvedValue({ + ok: true, + headers: { get: (name: string) => name.toLowerCase() === 'content-type' ? 'image/png' : null }, + arrayBuffer: async () => Uint8Array.from([1, 2, 3, 4]).buffer, + } as unknown as Response); + vi.stubGlobal('fetch', fetchMock); + + const client = new GeminiClient({ + apiKey: 'test-key', + model: 'gemini-2.0-flash', + }); + + await client.chat({ + messages: [{ + role: 'user', + content: [ + { type: 'text', text: 'describe this image' }, + { type: 'image', source: { type: 'url', url: 'https://example.com/test.png', media_type: 'image/png' } }, + ], + }], + }); + + expect(fetchMock).toHaveBeenCalledWith('https://example.com/test.png'); + expect(mockGenerateContent).toHaveBeenCalledWith({ + contents: [{ + role: 'user', + parts: [ + { text: 'describe this image' }, + { + inlineData: { + mimeType: 'image/png', + data: Buffer.from([1, 2, 3, 4]).toString('base64'), + }, + }, + ], + }], + }); + vi.unstubAllGlobals(); + }); + it('uses environment variable for API key when not provided', () => { const originalEnv = process.env.GOOGLE_API_KEY; process.env.GOOGLE_API_KEY = 'env-key';