From fa2131ab1305652c0bb68b7dbbdf9a9e460a8c6e Mon Sep 17 00:00:00 2001 From: Octopus Date: Thu, 2 Apr 2026 00:48:11 -0500 Subject: [PATCH] fix: sync Ollama provider config to gateway runtime (fixes #448) (#745) --- .../providers/provider-runtime-sync.ts | 10 ++-- tests/unit/provider-runtime-sync.test.ts | 54 +++++++++++++++++++ 2 files changed, 59 insertions(+), 5 deletions(-) diff --git a/electron/services/providers/provider-runtime-sync.ts b/electron/services/providers/provider-runtime-sync.ts index 5dc1523e6..a1bf117a2 100644 --- a/electron/services/providers/provider-runtime-sync.ts +++ b/electron/services/providers/provider-runtime-sync.ts @@ -286,7 +286,7 @@ async function syncProviderSecretToRuntime( async function resolveRuntimeSyncContext(config: ProviderConfig): Promise { const runtimeProviderKey = await resolveRuntimeProviderKey(config); const meta = getProviderConfig(config.type); - const api = config.apiProtocol || (config.type === 'custom' ? 'openai-completions' : meta?.api); + const api = config.apiProtocol || ((config.type === 'custom' || config.type === 'ollama') ? 'openai-completions' : meta?.api); if (!api) { return null; } @@ -315,7 +315,7 @@ async function syncCustomProviderAgentModel( runtimeProviderKey: string, apiKey: string | undefined, ): Promise { - if (config.type !== 'custom') { + if (config.type !== 'custom' && config.type !== 'ollama') { return; } @@ -402,7 +402,7 @@ async function buildAgentModelProviderEntry( authHeader?: boolean; } | null> { const meta = getProviderConfig(config.type); - const api = config.apiProtocol || (config.type === 'custom' ? 'openai-completions' : meta?.api); + const api = config.apiProtocol || ((config.type === 'custom' || config.type === 'ollama') ? 'openai-completions' : meta?.api); const baseUrl = normalizeProviderBaseUrl(config, config.baseUrl || meta?.baseUrl, api); if (!api || !baseUrl) { return null; @@ -593,7 +593,7 @@ export async function syncDefaultProviderToRuntime( ? (provider.model.startsWith(`${ock}/`) ? provider.model : `${ock}/${provider.model}`) : undefined; - if (provider.type === 'custom') { + if (provider.type === 'custom' || provider.type === 'ollama') { await setOpenClawDefaultModelWithOverride(ock, modelOverride, { baseUrl: normalizeProviderBaseUrl(provider, provider.baseUrl, provider.apiProtocol || 'openai-completions'), api: provider.apiProtocol || 'openai-completions', @@ -689,7 +689,7 @@ export async function syncDefaultProviderToRuntime( } if ( - provider.type === 'custom' && + (provider.type === 'custom' || provider.type === 'ollama') && providerKey && provider.baseUrl ) { diff --git a/tests/unit/provider-runtime-sync.test.ts b/tests/unit/provider-runtime-sync.test.ts index 728a6ce70..48b232df0 100644 --- a/tests/unit/provider-runtime-sync.test.ts +++ b/tests/unit/provider-runtime-sync.test.ts @@ -256,4 +256,58 @@ describe('provider-runtime-sync refresh strategy', () => { }), ); }); + + it('syncs Ollama provider config to runtime without adding model prefix', async () => { + const ollamaProvider = createProvider({ + id: 'ollamafd', + type: 'ollama', + name: 'Ollama', + model: 'qwen3:30b', + baseUrl: 'http://localhost:11434/v1', + }); + + mocks.getProviderConfig.mockReturnValue(undefined); + mocks.getProviderSecret.mockResolvedValue({ type: 'local', apiKey: 'ollama-local' }); + + const gateway = createGateway('running'); + await syncSavedProviderToRuntime(ollamaProvider, undefined, gateway as GatewayManager); + + expect(mocks.syncProviderConfigToOpenClaw).toHaveBeenCalledWith( + 'ollama-ollamafd', + 'qwen3:30b', + expect.objectContaining({ + baseUrl: 'http://localhost:11434/v1', + api: 'openai-completions', + }), + ); + expect(gateway.debouncedReload).toHaveBeenCalledTimes(1); + }); + + it('syncs Ollama as default provider with correct baseUrl and api protocol', async () => { + const ollamaProvider = createProvider({ + id: 'ollamafd', + type: 'ollama', + name: 'Ollama', + model: 'qwen3:30b', + baseUrl: 'http://localhost:11434/v1', + }); + + mocks.getProvider.mockResolvedValue(ollamaProvider); + mocks.getDefaultProvider.mockResolvedValue('ollamafd'); + mocks.getProviderConfig.mockReturnValue(undefined); + mocks.getApiKey.mockResolvedValue('ollama-local'); + + const gateway = createGateway('running'); + await syncDefaultProviderToRuntime('ollamafd', gateway as GatewayManager); + + expect(mocks.setOpenClawDefaultModelWithOverride).toHaveBeenCalledWith( + 'ollama-ollamafd', + 'ollama-ollamafd/qwen3:30b', + expect.objectContaining({ + baseUrl: 'http://localhost:11434/v1', + api: 'openai-completions', + }), + expect.any(Array), + ); + }); });