From 7c58c212c51dd71f0359600742ded2b3614ae36b Mon Sep 17 00:00:00 2001 From: Kevin On <40454531+kevin-on@users.noreply.github.com> Date: Sun, 25 Jan 2026 09:03:10 +0900 Subject: [PATCH 1/2] fix: support custom base URL for Gemini provider Fixes #512 The Gemini provider was throwing an error when a custom base URL was set, causing the chat interface to crash. The @google/genai SDK actually supports custom base URLs via the httpOptions.baseUrl configuration. This change removes the error and passes the custom base URL to the SDK, enabling users to use custom Gemini-compatible endpoints (like proxies or self-hosted alternatives). --- src/core/llm/gemini.ts | 58 ++++++++++++++++++++++-------------------- 1 file changed, 30 insertions(+), 28 deletions(-) diff --git a/src/core/llm/gemini.ts b/src/core/llm/gemini.ts index 26c54335..9a1a9b9b 100644 --- a/src/core/llm/gemini.ts +++ b/src/core/llm/gemini.ts @@ -49,11 +49,13 @@ export class GeminiProvider extends BaseLLMProvider< constructor(provider: Extract) { super(provider) - if (provider.baseUrl) { - throw new Error('Gemini does not support custom base URL') - } - this.client = new GoogleGenAI({ apiKey: provider.apiKey ?? '' }) + this.client = new GoogleGenAI({ + apiKey: provider.apiKey ?? '', + httpOptions: provider.baseUrl + ? { baseUrl: provider.baseUrl.replace(/\/+$/, '') } + : undefined, + }) this.apiKey = provider.apiKey ?? '' } @@ -199,24 +201,24 @@ export class GeminiProvider extends BaseLLMProvider< case 'user': { const contentParts: Part[] = Array.isArray(message.content) ? message.content.map((part) => { - switch (part.type) { - case 'text': - return { text: part.text } - case 'image_url': { - const { mimeType, base64Data } = parseImageDataUrl( - part.image_url.url, - ) - GeminiProvider.validateImageType(mimeType) - - return { - inlineData: { - data: base64Data, - mimeType, - }, - } + switch (part.type) { + case 'text': + return { text: part.text } + case 'image_url': { + const { mimeType, base64Data } = parseImageDataUrl( + part.image_url.url, + ) + GeminiProvider.validateImageType(mimeType) + + return { + inlineData: { + data: base64Data, + mimeType, + }, } } - }) + } + }) : [{ text: message.content }] return { @@ -324,10 +326,10 @@ export class GeminiProvider extends BaseLLMProvider< object: 'chat.completion', usage: response.usageMetadata ? { - prompt_tokens: response.usageMetadata.promptTokenCount ?? 0, - completion_tokens: response.usageMetadata.candidatesTokenCount ?? 0, - total_tokens: response.usageMetadata.totalTokenCount ?? 0, - } + prompt_tokens: response.usageMetadata.promptTokenCount ?? 0, + completion_tokens: response.usageMetadata.candidatesTokenCount ?? 0, + total_tokens: response.usageMetadata.totalTokenCount ?? 0, + } : undefined, } } @@ -363,10 +365,10 @@ export class GeminiProvider extends BaseLLMProvider< object: 'chat.completion.chunk', usage: chunk.usageMetadata ? { - prompt_tokens: chunk.usageMetadata.promptTokenCount ?? 0, - completion_tokens: chunk.usageMetadata.candidatesTokenCount ?? 0, - total_tokens: chunk.usageMetadata.totalTokenCount ?? 0, - } + prompt_tokens: chunk.usageMetadata.promptTokenCount ?? 0, + completion_tokens: chunk.usageMetadata.candidatesTokenCount ?? 0, + total_tokens: chunk.usageMetadata.totalTokenCount ?? 0, + } : undefined, } } From a681e3b2c64434ab753885478b7fd9f6c71b35a9 Mon Sep 17 00:00:00 2001 From: Kevin On <40454531+kevin-on@users.noreply.github.com> Date: Sun, 25 Jan 2026 17:37:03 +0900 Subject: [PATCH 2/2] fix: Fix lint --- src/core/llm/gemini.ts | 48 +++++++++++++++++++++--------------------- 1 file changed, 24 insertions(+), 24 deletions(-) diff --git a/src/core/llm/gemini.ts b/src/core/llm/gemini.ts index 9a1a9b9b..b0ba9dc8 100644 --- a/src/core/llm/gemini.ts +++ b/src/core/llm/gemini.ts @@ -201,24 +201,24 @@ export class GeminiProvider extends BaseLLMProvider< case 'user': { const contentParts: Part[] = Array.isArray(message.content) ? message.content.map((part) => { - switch (part.type) { - case 'text': - return { text: part.text } - case 'image_url': { - const { mimeType, base64Data } = parseImageDataUrl( - part.image_url.url, - ) - GeminiProvider.validateImageType(mimeType) - - return { - inlineData: { - data: base64Data, - mimeType, - }, + switch (part.type) { + case 'text': + return { text: part.text } + case 'image_url': { + const { mimeType, base64Data } = parseImageDataUrl( + part.image_url.url, + ) + GeminiProvider.validateImageType(mimeType) + + return { + inlineData: { + data: base64Data, + mimeType, + }, + } } } - } - }) + }) : [{ text: message.content }] return { @@ -326,10 +326,10 @@ export class GeminiProvider extends BaseLLMProvider< object: 'chat.completion', usage: response.usageMetadata ? { - prompt_tokens: response.usageMetadata.promptTokenCount ?? 0, - completion_tokens: response.usageMetadata.candidatesTokenCount ?? 0, - total_tokens: response.usageMetadata.totalTokenCount ?? 0, - } + prompt_tokens: response.usageMetadata.promptTokenCount ?? 0, + completion_tokens: response.usageMetadata.candidatesTokenCount ?? 0, + total_tokens: response.usageMetadata.totalTokenCount ?? 0, + } : undefined, } } @@ -365,10 +365,10 @@ export class GeminiProvider extends BaseLLMProvider< object: 'chat.completion.chunk', usage: chunk.usageMetadata ? { - prompt_tokens: chunk.usageMetadata.promptTokenCount ?? 0, - completion_tokens: chunk.usageMetadata.candidatesTokenCount ?? 0, - total_tokens: chunk.usageMetadata.totalTokenCount ?? 0, - } + prompt_tokens: chunk.usageMetadata.promptTokenCount ?? 0, + completion_tokens: chunk.usageMetadata.candidatesTokenCount ?? 0, + total_tokens: chunk.usageMetadata.totalTokenCount ?? 0, + } : undefined, } }