Skip to content

Commit 265b122

Browse files
authored
fix(model): custom model choose error (#164)
1 parent 1166074 commit 265b122

4 files changed

Lines changed: 98 additions & 64 deletions

File tree

electron/gateway/manager.ts

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -498,8 +498,14 @@ export class GatewayManager extends EventEmitter {
498498
if (pids.length > 0) {
499499
if (!this.process || !pids.includes(String(this.process.pid))) {
500500
logger.info(`Found orphaned process listening on port ${port} (PIDs: ${pids.join(', ')}), attempting to kill...`);
501+
// SIGTERM first so the gateway can clean up its lock file.
501502
for (const pid of pids) {
502-
try { process.kill(parseInt(pid), 'SIGKILL'); } catch { /* ignore */ }
503+
try { process.kill(parseInt(pid), 'SIGTERM'); } catch { /* ignore */ }
504+
}
505+
await new Promise(r => setTimeout(r, 3000));
506+
// SIGKILL any survivors.
507+
for (const pid of pids) {
508+
try { process.kill(parseInt(pid), 0); process.kill(parseInt(pid), 'SIGKILL'); } catch { /* already exited */ }
503509
}
504510
await new Promise(r => setTimeout(r, 1000));
505511
return null;

electron/main/ipc-handlers.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -962,6 +962,8 @@ function registerProviderHandlers(gatewayManager: GatewayManager): void {
962962

963963
if (provider.type === 'custom' || provider.type === 'ollama') {
964964
// For runtime-configured providers, use user-entered base URL/api.
965+
// Do NOT set apiKeyEnv — the OpenClaw gateway resolves custom
966+
// provider keys via auth-profiles, not the config apiKey field.
965967
setOpenClawDefaultModelWithOverride(provider.type, modelOverride, {
966968
baseUrl: provider.baseUrl,
967969
api: 'openai-completions',

electron/utils/openclaw-auth.ts

Lines changed: 85 additions & 62 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33
* Writes API keys to ~/.openclaw/agents/main/agent/auth-profiles.json
44
* so the OpenClaw Gateway can load them for AI provider calls.
55
*/
6-
import { existsSync, mkdirSync, readFileSync, writeFileSync } from 'fs';
6+
import { existsSync, mkdirSync, readFileSync, readdirSync, writeFileSync } from 'fs';
77
import { join } from 'path';
88
import { homedir } from 'os';
99
import {
@@ -81,78 +81,106 @@ function writeAuthProfiles(store: AuthProfilesStore, agentId = 'main'): void {
8181
writeFileSync(filePath, JSON.stringify(store, null, 2), 'utf-8');
8282
}
8383

84+
/**
85+
* Discover all agent IDs that have an agent/ subdirectory.
86+
*/
87+
function discoverAgentIds(): string[] {
88+
const agentsDir = join(homedir(), '.openclaw', 'agents');
89+
try {
90+
if (!existsSync(agentsDir)) return ['main'];
91+
return readdirSync(agentsDir, { withFileTypes: true })
92+
.filter((d) => d.isDirectory() && existsSync(join(agentsDir, d.name, 'agent')))
93+
.map((d) => d.name);
94+
} catch {
95+
return ['main'];
96+
}
97+
}
98+
8499
/**
85100
* Save a provider API key to OpenClaw's auth-profiles.json
86101
* This writes the key in the format OpenClaw expects so the gateway
87102
* can use it for AI provider calls.
103+
*
104+
* Writes to ALL discovered agent directories so every agent
105+
* (including non-"main" agents like "dev") stays in sync.
88106
*
89107
* @param provider - Provider type (e.g., 'anthropic', 'openrouter', 'openai', 'google')
90108
* @param apiKey - The API key to store
91-
* @param agentId - Agent ID (defaults to 'main')
109+
* @param agentId - Optional single agent ID. When omitted, writes to every agent.
92110
*/
93111
export function saveProviderKeyToOpenClaw(
94112
provider: string,
95113
apiKey: string,
96-
agentId = 'main'
114+
agentId?: string
97115
): void {
98-
const store = readAuthProfiles(agentId);
99-
100-
// Profile ID follows OpenClaw convention: <provider>:default
101-
const profileId = `${provider}:default`;
102-
103-
// Upsert the profile entry
104-
store.profiles[profileId] = {
105-
type: 'api_key',
106-
provider,
107-
key: apiKey,
108-
};
109-
110-
// Update order to include this profile
111-
if (!store.order) {
112-
store.order = {};
113-
}
114-
if (!store.order[provider]) {
115-
store.order[provider] = [];
116-
}
117-
if (!store.order[provider].includes(profileId)) {
118-
store.order[provider].push(profileId);
119-
}
120-
121-
// Set as last good
122-
if (!store.lastGood) {
123-
store.lastGood = {};
116+
const agentIds = agentId ? [agentId] : discoverAgentIds();
117+
if (agentIds.length === 0) agentIds.push('main');
118+
119+
for (const id of agentIds) {
120+
const store = readAuthProfiles(id);
121+
122+
// Profile ID follows OpenClaw convention: <provider>:default
123+
const profileId = `${provider}:default`;
124+
125+
// Upsert the profile entry
126+
store.profiles[profileId] = {
127+
type: 'api_key',
128+
provider,
129+
key: apiKey,
130+
};
131+
132+
// Update order to include this profile
133+
if (!store.order) {
134+
store.order = {};
135+
}
136+
if (!store.order[provider]) {
137+
store.order[provider] = [];
138+
}
139+
if (!store.order[provider].includes(profileId)) {
140+
store.order[provider].push(profileId);
141+
}
142+
143+
// Set as last good
144+
if (!store.lastGood) {
145+
store.lastGood = {};
146+
}
147+
store.lastGood[provider] = profileId;
148+
149+
writeAuthProfiles(store, id);
124150
}
125-
store.lastGood[provider] = profileId;
126-
127-
writeAuthProfiles(store, agentId);
128-
console.log(`Saved API key for provider "${provider}" to OpenClaw auth-profiles (agent: ${agentId})`);
151+
console.log(`Saved API key for provider "${provider}" to OpenClaw auth-profiles (agents: ${agentIds.join(', ')})`);
129152
}
130153

131154
/**
132155
* Remove a provider API key from OpenClaw auth-profiles.json
133156
*/
134157
export function removeProviderKeyFromOpenClaw(
135158
provider: string,
136-
agentId = 'main'
159+
agentId?: string
137160
): void {
138-
const store = readAuthProfiles(agentId);
139-
const profileId = `${provider}:default`;
161+
const agentIds = agentId ? [agentId] : discoverAgentIds();
162+
if (agentIds.length === 0) agentIds.push('main');
163+
164+
for (const id of agentIds) {
165+
const store = readAuthProfiles(id);
166+
const profileId = `${provider}:default`;
140167

141-
delete store.profiles[profileId];
168+
delete store.profiles[profileId];
142169

143-
if (store.order?.[provider]) {
144-
store.order[provider] = store.order[provider].filter((id) => id !== profileId);
145-
if (store.order[provider].length === 0) {
146-
delete store.order[provider];
170+
if (store.order?.[provider]) {
171+
store.order[provider] = store.order[provider].filter((aid) => aid !== profileId);
172+
if (store.order[provider].length === 0) {
173+
delete store.order[provider];
174+
}
147175
}
148-
}
149176

150-
if (store.lastGood?.[provider] === profileId) {
151-
delete store.lastGood[provider];
152-
}
177+
if (store.lastGood?.[provider] === profileId) {
178+
delete store.lastGood[provider];
179+
}
153180

154-
writeAuthProfiles(store, agentId);
155-
console.log(`Removed API key for provider "${provider}" from OpenClaw auth-profiles (agent: ${agentId})`);
181+
writeAuthProfiles(store, id);
182+
}
183+
console.log(`Removed API key for provider "${provider}" from OpenClaw auth-profiles (agents: ${agentIds.join(', ')})`);
156184
}
157185

158186
/**
@@ -244,7 +272,7 @@ export function setOpenClawDefaultModel(provider: string, modelOverride?: string
244272
...existingProvider,
245273
baseUrl: providerCfg.baseUrl,
246274
api: providerCfg.api,
247-
apiKey: `\${${providerCfg.apiKeyEnv}}`,
275+
apiKey: providerCfg.apiKeyEnv,
248276
models: mergedModels,
249277
};
250278
console.log(`Configured models.providers.${provider} with baseUrl=${providerCfg.baseUrl}, model=${modelId}`);
@@ -329,27 +357,22 @@ export function setOpenClawDefaultModelWithOverride(
329357
const models = (config.models || {}) as Record<string, unknown>;
330358
const providers = (models.providers || {}) as Record<string, unknown>;
331359

332-
const existingProvider =
333-
providers[provider] && typeof providers[provider] === 'object'
334-
? (providers[provider] as Record<string, unknown>)
335-
: {};
336-
337-
const existingModels = Array.isArray(existingProvider.models)
338-
? (existingProvider.models as Array<Record<string, unknown>>)
339-
: [];
340-
const mergedModels = [...existingModels];
341-
if (modelId && !mergedModels.some((m) => m.id === modelId)) {
342-
mergedModels.push({ id: modelId, name: modelId });
360+
// Replace the provider entry entirely rather than merging.
361+
// Different custom/ollama provider instances have different baseUrls,
362+
// so merging models from a previous instance creates an inconsistent
363+
// config (models pointing at the wrong endpoint).
364+
const nextModels: Array<Record<string, unknown>> = [];
365+
if (modelId) {
366+
nextModels.push({ id: modelId, name: modelId });
343367
}
344368

345369
const nextProvider: Record<string, unknown> = {
346-
...existingProvider,
347370
baseUrl: override.baseUrl,
348371
api: override.api,
349-
models: mergedModels,
372+
models: nextModels,
350373
};
351374
if (override.apiKeyEnv) {
352-
nextProvider.apiKey = `\${${override.apiKeyEnv}}`;
375+
nextProvider.apiKey = override.apiKeyEnv;
353376
}
354377

355378
providers[provider] = nextProvider;

electron/utils/provider-registry.ts

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -52,7 +52,7 @@ const REGISTRY: Record<string, ProviderBackendMeta> = {
5252
},
5353
google: {
5454
envVar: 'GEMINI_API_KEY',
55-
defaultModel: 'google/gemini-3-pro-preview',
55+
defaultModel: 'google/gemini-3.1-pro-preview',
5656
// google is built-in to OpenClaw's pi-ai catalog, no providerConfig needed.
5757
// Adding models.providers.google overrides the built-in and can break Gemini.
5858
},
@@ -94,6 +94,9 @@ const REGISTRY: Record<string, ProviderBackendMeta> = {
9494
apiKeyEnv: 'SILICONFLOW_API_KEY',
9595
},
9696
},
97+
custom: {
98+
envVar: 'CUSTOM_API_KEY',
99+
},
97100
// Additional providers with env var mappings but no default model
98101
groq: { envVar: 'GROQ_API_KEY' },
99102
deepgram: { envVar: 'DEEPGRAM_API_KEY' },

0 commit comments

Comments
 (0)