fix(model): update gemini apikey modify function (#119)

This commit is contained in:
Haze
2026-02-20 20:17:30 +08:00
committed by GitHub
Unverified
parent 1808990f5b
commit f821949829
4 changed files with 57 additions and 18 deletions

View File

@@ -65,7 +65,7 @@ export function registerIpcHandlers(
registerOpenClawHandlers(); registerOpenClawHandlers();
// Provider handlers // Provider handlers
registerProviderHandlers(); registerProviderHandlers(gatewayManager);
// Shell handlers // Shell handlers
registerShellHandlers(); registerShellHandlers();
@@ -780,7 +780,7 @@ function registerWhatsAppHandlers(mainWindow: BrowserWindow): void {
/** /**
* Provider-related IPC handlers * Provider-related IPC handlers
*/ */
function registerProviderHandlers(): void { function registerProviderHandlers(gatewayManager: GatewayManager): void {
// Get all providers with key info // Get all providers with key info
ipcMain.handle('provider:list', async () => { ipcMain.handle('provider:list', async () => {
return await getAllProvidersWithKeyInfo(); return await getAllProvidersWithKeyInfo();
@@ -976,6 +976,16 @@ function registerProviderHandlers(): void {
if (providerKey) { if (providerKey) {
saveProviderKeyToOpenClaw(provider.type, 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) { } catch (err) {
console.warn('Failed to set OpenClaw default model:', err); console.warn('Failed to set OpenClaw default model:', err);
} }
@@ -1254,12 +1264,8 @@ async function validateGoogleQueryKey(
apiKey: string, apiKey: string,
baseUrl?: string baseUrl?: string
): Promise<{ valid: boolean; error?: string }> { ): Promise<{ valid: boolean; error?: string }> {
const trimmedBaseUrl = baseUrl?.trim(); // Default to the official Google Gemini API base URL if none is provided
if (!trimmedBaseUrl) { const base = normalizeBaseUrl(baseUrl || 'https://generativelanguage.googleapis.com/v1beta');
return { valid: false, error: `Base URL is required for provider "${providerType}" validation` };
}
const base = normalizeBaseUrl(trimmedBaseUrl);
const url = `${base}/models?pageSize=1&key=${encodeURIComponent(apiKey)}`; const url = `${base}/models?pageSize=1&key=${encodeURIComponent(apiKey)}`;
return await performProviderValidationRequest(providerType, url, {}); return await performProviderValidationRequest(providerType, url, {});
} }

View File

@@ -212,8 +212,8 @@ export function setOpenClawDefaultModel(provider: string, modelOverride?: string
config.agents = agents; config.agents = agents;
// Configure models.providers for providers that need explicit registration. // Configure models.providers for providers that need explicit registration.
// For built-in providers this comes from registry; for custom/ollama-like // Built-in providers (anthropic, google) are part of OpenClaw's pi-ai catalog
// providers callers can supply runtime overrides. // and must NOT have a models.providers entry — it would override the built-in.
const providerCfg = getProviderConfig(provider); const providerCfg = getProviderConfig(provider);
if (providerCfg) { if (providerCfg) {
const models = (config.models || {}) as Record<string, unknown>; const models = (config.models || {}) as Record<string, unknown>;
@@ -229,7 +229,6 @@ export function setOpenClawDefaultModel(provider: string, modelOverride?: string
: []; : [];
const registryModels = (providerCfg.models ?? []).map((m) => ({ ...m })) as Array<Record<string, unknown>>; const registryModels = (providerCfg.models ?? []).map((m) => ({ ...m })) as Array<Record<string, unknown>>;
// Merge model entries by id and ensure the selected/default model id exists.
const mergedModels = [...registryModels]; const mergedModels = [...registryModels];
for (const item of existingModels) { for (const item of existingModels) {
const id = typeof item?.id === 'string' ? item.id : ''; const id = typeof item?.id === 'string' ? item.id : '';
@@ -245,13 +244,25 @@ export function setOpenClawDefaultModel(provider: string, modelOverride?: string
...existingProvider, ...existingProvider,
baseUrl: providerCfg.baseUrl, baseUrl: providerCfg.baseUrl,
api: providerCfg.api, api: providerCfg.api,
apiKey: providerCfg.apiKeyEnv, apiKey: `\${${providerCfg.apiKeyEnv}}`,
models: mergedModels, models: mergedModels,
}; };
console.log(`Configured models.providers.${provider} with baseUrl=${providerCfg.baseUrl}, model=${modelId}`); console.log(`Configured models.providers.${provider} with baseUrl=${providerCfg.baseUrl}, model=${modelId}`);
models.providers = providers; models.providers = providers;
config.models = models; 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<string, unknown>;
const providers = (models.providers || {}) as Record<string, unknown>;
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 // Ensure gateway mode is set
@@ -338,7 +349,7 @@ export function setOpenClawDefaultModelWithOverride(
models: mergedModels, models: mergedModels,
}; };
if (override.apiKeyEnv) { if (override.apiKeyEnv) {
nextProvider.apiKey = override.apiKeyEnv; nextProvider.apiKey = `\${${override.apiKeyEnv}}`;
} }
providers[provider] = nextProvider; providers[provider] = nextProvider;

View File

@@ -53,11 +53,8 @@ const REGISTRY: Record<string, ProviderBackendMeta> = {
google: { google: {
envVar: 'GEMINI_API_KEY', envVar: 'GEMINI_API_KEY',
defaultModel: 'google/gemini-3-pro-preview', defaultModel: 'google/gemini-3-pro-preview',
providerConfig: { // google is built-in to OpenClaw's pi-ai catalog, no providerConfig needed.
baseUrl: 'https://generativelanguage.googleapis.com/v1beta', // Adding models.providers.google overrides the built-in and can break Gemini.
api: 'google',
apiKeyEnv: 'GEMINI_API_KEY',
},
}, },
openrouter: { openrouter: {
envVar: 'OPENROUTER_API_KEY', envVar: 'OPENROUTER_API_KEY',

View File

@@ -1150,6 +1150,31 @@ export const useChatStore = create<ChatState>((set, get) => ({
} else { } else {
// No runId from gateway; keep sending state and wait for events. // 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) { } catch (err) {
set({ error: String(err), sending: false }); set({ error: String(err), sending: false });
} }