From 3a8074b963fdc1d7ba41df3322446cec62df093d Mon Sep 17 00:00:00 2001 From: Brian Mahabir <56164556+bmahabirbu@users.noreply.github.com> Date: Sun, 12 Jul 2026 20:16:11 -0400 Subject: [PATCH 1/2] fix(gemini): register OpenShell connection as openai provider type OpenShell only supports protocol-family provider types (openai, anthropic, ...); it has no dedicated "gemini" type, so workspace creation failed with "unsupported provider type or profile: gemini". Google publishes an OpenAI-compatible endpoint that accepts a plain Bearer API key, so the Gemini connection is now registered with OpenShell as an "openai" provider pointed at that endpoint, mirroring the pattern used by openai-compatible. Co-Authored-By: Claude Sonnet 5 Signed-off-by: Brian Mahabir <56164556+bmahabirbu@users.noreply.github.com> --- extensions/gemini/package.json | 10 ++++++++-- extensions/gemini/src/gemini.spec.ts | 20 +++++++++++++------- extensions/gemini/src/gemini.ts | 16 +++++++++++++--- 3 files changed, 34 insertions(+), 12 deletions(-) diff --git a/extensions/gemini/package.json b/extensions/gemini/package.json index 7850d3f4f3..11b6c3de3e 100644 --- a/extensions/gemini/package.json +++ b/extensions/gemini/package.json @@ -28,15 +28,21 @@ "gemini.connection._type": { "type": "string", "scope": "InferenceProviderConnection", - "description": "Gemini provider type", + "description": "OpenShell provider type", "hidden": true }, - "gemini.connection.GEMINI_API_KEY": { + "gemini.connection.OPENAI_API_KEY": { "type": "string", "scope": "InferenceProviderConnection", "description": "API key secret reference", "format": "password", "hidden": true + }, + "gemini.connection.OPENAI_BASE_URL": { + "type": "string", + "scope": "InferenceProviderConnection", + "description": "Gemini OpenAI-compatible base URL", + "hidden": true } } } diff --git a/extensions/gemini/src/gemini.spec.ts b/extensions/gemini/src/gemini.spec.ts index 161c48cdae..c19faaf311 100644 --- a/extensions/gemini/src/gemini.spec.ts +++ b/extensions/gemini/src/gemini.spec.ts @@ -313,7 +313,8 @@ describe('factory', () => { expect(SAFE_STORAGE_MOCK.delete).toHaveBeenCalledWith('gemini:fake-uuid-1:token'); expect(CONFIG_UPDATE_MOCK).toHaveBeenCalledWith('gemini.connection._type', undefined); - expect(CONFIG_UPDATE_MOCK).toHaveBeenCalledWith('gemini.connection.GEMINI_API_KEY', undefined); + expect(CONFIG_UPDATE_MOCK).toHaveBeenCalledWith('gemini.connection.OPENAI_API_KEY', undefined); + expect(CONFIG_UPDATE_MOCK).toHaveBeenCalledWith('gemini.connection.OPENAI_BASE_URL', undefined); }); }); @@ -353,7 +354,8 @@ describe('connection delete lifecycle', () => { // Verify configuration clearing expect(CONFIG_UPDATE_MOCK).toHaveBeenCalledWith('gemini.connection._type', undefined); - expect(CONFIG_UPDATE_MOCK).toHaveBeenCalledWith('gemini.connection.GEMINI_API_KEY', undefined); + expect(CONFIG_UPDATE_MOCK).toHaveBeenCalledWith('gemini.connection.OPENAI_API_KEY', undefined); + expect(CONFIG_UPDATE_MOCK).toHaveBeenCalledWith('gemini.connection.OPENAI_BASE_URL', undefined); // Verify storage update - the tokens list is updated after per-connection secret expect(SAFE_STORAGE_MOCK.store).toHaveBeenCalledWith(TOKENS_KEY, JSON.stringify([])); @@ -415,11 +417,15 @@ describe('workspace configuration', () => { expect(configuration.getConfiguration).toHaveBeenCalledWith(undefined, connection); // Verify configuration updates - expect(CONFIG_UPDATE_MOCK).toHaveBeenCalledWith('gemini.connection._type', PROVIDER_ID); + expect(CONFIG_UPDATE_MOCK).toHaveBeenCalledWith('gemini.connection._type', 'openai'); expect(CONFIG_UPDATE_MOCK).toHaveBeenCalledWith( - 'gemini.connection.GEMINI_API_KEY', + 'gemini.connection.OPENAI_API_KEY', `${PROVIDER_ID}:fake-uuid-1:token`, ); + expect(CONFIG_UPDATE_MOCK).toHaveBeenCalledWith( + 'gemini.connection.OPENAI_BASE_URL', + 'https://generativelanguage.googleapis.com/v1beta/openai/', + ); }); test('should set workspace configuration for each restored connection', async () => { @@ -437,8 +443,8 @@ describe('workspace configuration', () => { expect(SAFE_STORAGE_MOCK.store).toHaveBeenCalledWith(`${PROVIDER_ID}:id-2:token`, 'key2'); // Verify configuration updates for both connections - expect(CONFIG_UPDATE_MOCK).toHaveBeenCalledWith('gemini.connection._type', PROVIDER_ID); - expect(CONFIG_UPDATE_MOCK).toHaveBeenCalledWith('gemini.connection.GEMINI_API_KEY', `${PROVIDER_ID}:id-1:token`); - expect(CONFIG_UPDATE_MOCK).toHaveBeenCalledWith('gemini.connection.GEMINI_API_KEY', `${PROVIDER_ID}:id-2:token`); + expect(CONFIG_UPDATE_MOCK).toHaveBeenCalledWith('gemini.connection._type', 'openai'); + expect(CONFIG_UPDATE_MOCK).toHaveBeenCalledWith('gemini.connection.OPENAI_API_KEY', `${PROVIDER_ID}:id-1:token`); + expect(CONFIG_UPDATE_MOCK).toHaveBeenCalledWith('gemini.connection.OPENAI_API_KEY', `${PROVIDER_ID}:id-2:token`); }); }); diff --git a/extensions/gemini/src/gemini.ts b/extensions/gemini/src/gemini.ts index c72b028ca8..611ad9ef75 100644 --- a/extensions/gemini/src/gemini.ts +++ b/extensions/gemini/src/gemini.ts @@ -33,6 +33,14 @@ import { configuration } from '@openkaiden/api'; export const PROVIDER_ID = 'gemini'; export const TOKENS_KEY = 'gemini:tokens'; +// OpenShell has no dedicated "gemini" provider type — it only understands +// protocol families (openai, anthropic, ...). Google publishes an +// OpenAI-compatible endpoint that accepts a plain Bearer API key, so the +// connection is registered with OpenShell as an "openai" provider pointed +// at that endpoint. +const OPENSHELL_PROVIDER_TYPE = 'openai'; +const OPENAI_COMPAT_BASE_URL = 'https://generativelanguage.googleapis.com/v1beta/openai/'; + export interface StoredConnection { id: string; token: string; @@ -126,8 +134,9 @@ export class Gemini implements Disposable { await this.secrets.store(secretName, token); const config = configuration.getConfiguration(undefined, connection); - await config.update('gemini.connection._type', PROVIDER_ID); - await config.update('gemini.connection.GEMINI_API_KEY', secretName); + await config.update('gemini.connection._type', OPENSHELL_PROVIDER_TYPE); + await config.update('gemini.connection.OPENAI_API_KEY', secretName); + await config.update('gemini.connection.OPENAI_BASE_URL', OPENAI_COMPAT_BASE_URL); } private async clearConnectionConfiguration(connection: InferenceProviderConnection): Promise { @@ -136,7 +145,8 @@ export class Gemini implements Disposable { const config = configuration.getConfiguration(undefined, connection); await config.update('gemini.connection._type', undefined); - await config.update('gemini.connection.GEMINI_API_KEY', undefined); + await config.update('gemini.connection.OPENAI_API_KEY', undefined); + await config.update('gemini.connection.OPENAI_BASE_URL', undefined); } private async registerInferenceProviderConnection({ id, token }: { id: string; token: string }): Promise { From e6fe45482a0985e72a10db51f0a06c856616f807 Mon Sep 17 00:00:00 2001 From: Brian Date: Mon, 13 Jul 2026 02:39:09 -0400 Subject: [PATCH 2/2] fix(gemini,opencode): set endpoint and apiKey for OpenAI-compat routing MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The Gemini connection was missing an endpoint, so the OpenCode agent generated a model string (gemini/...) with no provider config block — OpenCode could not reach the model. Add the Google OpenAI-compat endpoint to the connection so a custom provider entry is written. Also inject a default apiKey option ({env:OPENAI_API_KEY}) for custom providers in the OpenCode extension so @ai-sdk/openai-compatible can authenticate via the env var that OpenShell injects. Co-Authored-By: Claude Opus 4.6 (1M context) Signed-off-by: Brian --- extensions/gemini/src/gemini.spec.ts | 1 + extensions/gemini/src/gemini.ts | 5 +++-- extensions/opencode/src/extension.spec.ts | 2 +- extensions/opencode/src/extension.ts | 2 +- 4 files changed, 6 insertions(+), 4 deletions(-) diff --git a/extensions/gemini/src/gemini.spec.ts b/extensions/gemini/src/gemini.spec.ts index c19faaf311..e9779a0cae 100644 --- a/extensions/gemini/src/gemini.spec.ts +++ b/extensions/gemini/src/gemini.spec.ts @@ -290,6 +290,7 @@ describe('factory', () => { name: 'dum*****', type: 'cloud', llmMetadata: { name: 'gemini' }, + endpoint: 'https://generativelanguage.googleapis.com/v1beta/openai/', status: expect.any(Function), lifecycle: { delete: expect.any(Function), diff --git a/extensions/gemini/src/gemini.ts b/extensions/gemini/src/gemini.ts index 611ad9ef75..f1f364ea4f 100644 --- a/extensions/gemini/src/gemini.ts +++ b/extensions/gemini/src/gemini.ts @@ -74,7 +74,7 @@ export class Gemini implements Disposable { // register MCP Provider connection factory this.provider?.setInferenceProviderConnectionFactory({ connectionTypes: ['cloud'], - llmMetadata: { name: 'gemini' }, + llmMetadata: { name: PROVIDER_ID }, create: this.mcpFactory.bind(this), }); @@ -175,7 +175,8 @@ export class Gemini implements Disposable { id, name: this.maskKey(token), type: 'cloud', - llmMetadata: { name: 'gemini' }, + llmMetadata: { name: PROVIDER_ID }, + endpoint: OPENAI_COMPAT_BASE_URL, sdk: google, status(): ProviderConnectionStatus { return status; diff --git a/extensions/opencode/src/extension.spec.ts b/extensions/opencode/src/extension.spec.ts index b3eb07c91c..27c622b5a1 100644 --- a/extensions/opencode/src/extension.spec.ts +++ b/extensions/opencode/src/extension.spec.ts @@ -169,7 +169,7 @@ describe('activate', () => { ollama: { name: 'ollama', npm: '@ai-sdk/openai-compatible', - options: { baseURL: 'http://localhost:11434/v1' }, + options: { apiKey: '{env:OPENAI_API_KEY}', baseURL: 'http://localhost:11434/v1' }, models: { llama3: { _launch: true, name: 'llama3' }, }, diff --git a/extensions/opencode/src/extension.ts b/extensions/opencode/src/extension.ts index 015aa74964..135becd621 100644 --- a/extensions/opencode/src/extension.ts +++ b/extensions/opencode/src/extension.ts @@ -128,7 +128,7 @@ export async function activate(extensionContext: ExtensionContext): Promise