From f821949829fc6862bce9732fc465b684dd8ba685 Mon Sep 17 00:00:00 2001 From: Haze <709547807@qq.com> Date: Fri, 20 Feb 2026 20:17:30 +0800 Subject: [PATCH] fix(model): update gemini apikey modify function (#119) --- electron/main/ipc-handlers.ts | 22 ++++++++++++++-------- electron/utils/openclaw-auth.ts | 21 ++++++++++++++++----- electron/utils/provider-registry.ts | 7 ++----- src/stores/chat.ts | 25 +++++++++++++++++++++++++ 4 files changed, 57 insertions(+), 18 deletions(-) diff --git a/electron/main/ipc-handlers.ts b/electron/main/ipc-handlers.ts index 3e38c1478..615ec5248 100644 --- a/electron/main/ipc-handlers.ts +++ b/electron/main/ipc-handlers.ts @@ -65,7 +65,7 @@ export function registerIpcHandlers( registerOpenClawHandlers(); // Provider handlers - registerProviderHandlers(); + registerProviderHandlers(gatewayManager); // Shell handlers registerShellHandlers(); @@ -780,7 +780,7 @@ function registerWhatsAppHandlers(mainWindow: BrowserWindow): void { /** * Provider-related IPC handlers */ -function registerProviderHandlers(): void { +function registerProviderHandlers(gatewayManager: GatewayManager): void { // Get all providers with key info ipcMain.handle('provider:list', async () => { return await getAllProvidersWithKeyInfo(); @@ -976,6 +976,16 @@ function registerProviderHandlers(): void { if (providerKey) { saveProviderKeyToOpenClaw(provider.type, providerKey); } + + // Restart Gateway so it picks up the new config and env vars. + // OpenClaw reads openclaw.json per-request, but env vars (API keys) + // are only available if they were injected at process startup. + if (gatewayManager.isConnected()) { + logger.info(`Restarting Gateway after provider switch to "${provider.type}"`); + void gatewayManager.restart().catch((err) => { + logger.warn('Gateway restart after provider switch failed:', err); + }); + } } catch (err) { console.warn('Failed to set OpenClaw default model:', err); } @@ -1254,12 +1264,8 @@ async function validateGoogleQueryKey( apiKey: string, baseUrl?: string ): Promise<{ valid: boolean; error?: string }> { - const trimmedBaseUrl = baseUrl?.trim(); - if (!trimmedBaseUrl) { - return { valid: false, error: `Base URL is required for provider "${providerType}" validation` }; - } - - const base = normalizeBaseUrl(trimmedBaseUrl); + // Default to the official Google Gemini API base URL if none is provided + const base = normalizeBaseUrl(baseUrl || 'https://generativelanguage.googleapis.com/v1beta'); const url = `${base}/models?pageSize=1&key=${encodeURIComponent(apiKey)}`; return await performProviderValidationRequest(providerType, url, {}); } diff --git a/electron/utils/openclaw-auth.ts b/electron/utils/openclaw-auth.ts index 59db6131b..840d0d0f7 100644 --- a/electron/utils/openclaw-auth.ts +++ b/electron/utils/openclaw-auth.ts @@ -212,8 +212,8 @@ export function setOpenClawDefaultModel(provider: string, modelOverride?: string config.agents = agents; // Configure models.providers for providers that need explicit registration. - // For built-in providers this comes from registry; for custom/ollama-like - // providers callers can supply runtime overrides. + // Built-in providers (anthropic, google) are part of OpenClaw's pi-ai catalog + // and must NOT have a models.providers entry — it would override the built-in. const providerCfg = getProviderConfig(provider); if (providerCfg) { const models = (config.models || {}) as Record; @@ -229,7 +229,6 @@ export function setOpenClawDefaultModel(provider: string, modelOverride?: string : []; const registryModels = (providerCfg.models ?? []).map((m) => ({ ...m })) as Array>; - // Merge model entries by id and ensure the selected/default model id exists. const mergedModels = [...registryModels]; for (const item of existingModels) { const id = typeof item?.id === 'string' ? item.id : ''; @@ -245,13 +244,25 @@ export function setOpenClawDefaultModel(provider: string, modelOverride?: string ...existingProvider, baseUrl: providerCfg.baseUrl, api: providerCfg.api, - apiKey: providerCfg.apiKeyEnv, + apiKey: `\${${providerCfg.apiKeyEnv}}`, models: mergedModels, }; console.log(`Configured models.providers.${provider} with baseUrl=${providerCfg.baseUrl}, model=${modelId}`); models.providers = providers; config.models = models; + } else { + // Built-in provider: remove any stale models.providers entry that may + // have been written by an earlier version. Leaving it in place would + // override the native pi-ai catalog and can break streaming/auth. + const models = (config.models || {}) as Record; + const providers = (models.providers || {}) as Record; + if (providers[provider]) { + delete providers[provider]; + console.log(`Removed stale models.providers.${provider} (built-in provider)`); + models.providers = providers; + config.models = models; + } } // Ensure gateway mode is set @@ -338,7 +349,7 @@ export function setOpenClawDefaultModelWithOverride( models: mergedModels, }; if (override.apiKeyEnv) { - nextProvider.apiKey = override.apiKeyEnv; + nextProvider.apiKey = `\${${override.apiKeyEnv}}`; } providers[provider] = nextProvider; diff --git a/electron/utils/provider-registry.ts b/electron/utils/provider-registry.ts index 78d7f6cc7..155d87091 100644 --- a/electron/utils/provider-registry.ts +++ b/electron/utils/provider-registry.ts @@ -53,11 +53,8 @@ const REGISTRY: Record = { google: { envVar: 'GEMINI_API_KEY', defaultModel: 'google/gemini-3-pro-preview', - providerConfig: { - baseUrl: 'https://generativelanguage.googleapis.com/v1beta', - api: 'google', - apiKeyEnv: 'GEMINI_API_KEY', - }, + // google is built-in to OpenClaw's pi-ai catalog, no providerConfig needed. + // Adding models.providers.google overrides the built-in and can break Gemini. }, openrouter: { envVar: 'OPENROUTER_API_KEY', diff --git a/src/stores/chat.ts b/src/stores/chat.ts index 0369324f7..733bccac7 100644 --- a/src/stores/chat.ts +++ b/src/stores/chat.ts @@ -1150,6 +1150,31 @@ export const useChatStore = create((set, get) => ({ } else { // No runId from gateway; keep sending state and wait for events. } + + // Safety timeout: if we're still in "sending" state after 90s without + // receiving any streaming event, the run likely failed silently (e.g. + // provider error not surfaced as a chat event). Surface the error to the + // user instead of leaving an infinite spinner. + if (result.success) { + const sentAt = Date.now(); + const SAFETY_TIMEOUT_MS = 90_000; + const checkStuck = () => { + const state = get(); + if (!state.sending) return; + if (state.streamingMessage || state.streamingText) return; + if (Date.now() - sentAt < SAFETY_TIMEOUT_MS) { + setTimeout(checkStuck, 10_000); + return; + } + set({ + error: 'No response received from the model. The provider may be unavailable or the API key may have insufficient quota. Please check your provider settings.', + sending: false, + activeRunId: null, + lastUserMessageAt: null, + }); + }; + setTimeout(checkStuck, 30_000); + } } catch (err) { set({ error: String(err), sending: false }); }