fix(model): custom model choose error (#164)

This commit is contained in:
Haze
2026-02-25 16:38:03 +08:00
committed by GitHub
Unverified
parent 1166074a52
commit 265b12281c
4 changed files with 98 additions and 64 deletions

View File

@@ -498,8 +498,14 @@ export class GatewayManager extends EventEmitter {
if (pids.length > 0) {
if (!this.process || !pids.includes(String(this.process.pid))) {
logger.info(`Found orphaned process listening on port ${port} (PIDs: ${pids.join(', ')}), attempting to kill...`);
// SIGTERM first so the gateway can clean up its lock file.
for (const pid of pids) {
try { process.kill(parseInt(pid), 'SIGKILL'); } catch { /* ignore */ }
try { process.kill(parseInt(pid), 'SIGTERM'); } catch { /* ignore */ }
}
await new Promise(r => setTimeout(r, 3000));
// SIGKILL any survivors.
for (const pid of pids) {
try { process.kill(parseInt(pid), 0); process.kill(parseInt(pid), 'SIGKILL'); } catch { /* already exited */ }
}
await new Promise(r => setTimeout(r, 1000));
return null;

View File

@@ -962,6 +962,8 @@ function registerProviderHandlers(gatewayManager: GatewayManager): void {
if (provider.type === 'custom' || provider.type === 'ollama') {
// For runtime-configured providers, use user-entered base URL/api.
// Do NOT set apiKeyEnv — the OpenClaw gateway resolves custom
// provider keys via auth-profiles, not the config apiKey field.
setOpenClawDefaultModelWithOverride(provider.type, modelOverride, {
baseUrl: provider.baseUrl,
api: 'openai-completions',

View File

@@ -3,7 +3,7 @@
* Writes API keys to ~/.openclaw/agents/main/agent/auth-profiles.json
* so the OpenClaw Gateway can load them for AI provider calls.
*/
import { existsSync, mkdirSync, readFileSync, writeFileSync } from 'fs';
import { existsSync, mkdirSync, readFileSync, readdirSync, writeFileSync } from 'fs';
import { join } from 'path';
import { homedir } from 'os';
import {
@@ -81,51 +81,74 @@ function writeAuthProfiles(store: AuthProfilesStore, agentId = 'main'): void {
writeFileSync(filePath, JSON.stringify(store, null, 2), 'utf-8');
}
/**
* Discover all agent IDs that have an agent/ subdirectory.
*/
function discoverAgentIds(): string[] {
const agentsDir = join(homedir(), '.openclaw', 'agents');
try {
if (!existsSync(agentsDir)) return ['main'];
return readdirSync(agentsDir, { withFileTypes: true })
.filter((d) => d.isDirectory() && existsSync(join(agentsDir, d.name, 'agent')))
.map((d) => d.name);
} catch {
return ['main'];
}
}
/**
* Save a provider API key to OpenClaw's auth-profiles.json
* This writes the key in the format OpenClaw expects so the gateway
* can use it for AI provider calls.
*
* Writes to ALL discovered agent directories so every agent
* (including non-"main" agents like "dev") stays in sync.
*
* @param provider - Provider type (e.g., 'anthropic', 'openrouter', 'openai', 'google')
* @param apiKey - The API key to store
* @param agentId - Agent ID (defaults to 'main')
* @param agentId - Optional single agent ID. When omitted, writes to every agent.
*/
export function saveProviderKeyToOpenClaw(
provider: string,
apiKey: string,
agentId = 'main'
agentId?: string
): void {
const store = readAuthProfiles(agentId);
const agentIds = agentId ? [agentId] : discoverAgentIds();
if (agentIds.length === 0) agentIds.push('main');
// Profile ID follows OpenClaw convention: <provider>:default
const profileId = `${provider}:default`;
for (const id of agentIds) {
const store = readAuthProfiles(id);
// Upsert the profile entry
store.profiles[profileId] = {
type: 'api_key',
provider,
key: apiKey,
};
// Profile ID follows OpenClaw convention: <provider>:default
const profileId = `${provider}:default`;
// Update order to include this profile
if (!store.order) {
store.order = {};
// Upsert the profile entry
store.profiles[profileId] = {
type: 'api_key',
provider,
key: apiKey,
};
// Update order to include this profile
if (!store.order) {
store.order = {};
}
if (!store.order[provider]) {
store.order[provider] = [];
}
if (!store.order[provider].includes(profileId)) {
store.order[provider].push(profileId);
}
// Set as last good
if (!store.lastGood) {
store.lastGood = {};
}
store.lastGood[provider] = profileId;
writeAuthProfiles(store, id);
}
if (!store.order[provider]) {
store.order[provider] = [];
}
if (!store.order[provider].includes(profileId)) {
store.order[provider].push(profileId);
}
// Set as last good
if (!store.lastGood) {
store.lastGood = {};
}
store.lastGood[provider] = profileId;
writeAuthProfiles(store, agentId);
console.log(`Saved API key for provider "${provider}" to OpenClaw auth-profiles (agent: ${agentId})`);
console.log(`Saved API key for provider "${provider}" to OpenClaw auth-profiles (agents: ${agentIds.join(', ')})`);
}
/**
@@ -133,26 +156,31 @@ export function saveProviderKeyToOpenClaw(
*/
export function removeProviderKeyFromOpenClaw(
provider: string,
agentId = 'main'
agentId?: string
): void {
const store = readAuthProfiles(agentId);
const profileId = `${provider}:default`;
const agentIds = agentId ? [agentId] : discoverAgentIds();
if (agentIds.length === 0) agentIds.push('main');
delete store.profiles[profileId];
for (const id of agentIds) {
const store = readAuthProfiles(id);
const profileId = `${provider}:default`;
if (store.order?.[provider]) {
store.order[provider] = store.order[provider].filter((id) => id !== profileId);
if (store.order[provider].length === 0) {
delete store.order[provider];
delete store.profiles[profileId];
if (store.order?.[provider]) {
store.order[provider] = store.order[provider].filter((aid) => aid !== profileId);
if (store.order[provider].length === 0) {
delete store.order[provider];
}
}
}
if (store.lastGood?.[provider] === profileId) {
delete store.lastGood[provider];
}
if (store.lastGood?.[provider] === profileId) {
delete store.lastGood[provider];
}
writeAuthProfiles(store, agentId);
console.log(`Removed API key for provider "${provider}" from OpenClaw auth-profiles (agent: ${agentId})`);
writeAuthProfiles(store, id);
}
console.log(`Removed API key for provider "${provider}" from OpenClaw auth-profiles (agents: ${agentIds.join(', ')})`);
}
/**
@@ -244,7 +272,7 @@ 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}`);
@@ -329,27 +357,22 @@ export function setOpenClawDefaultModelWithOverride(
const models = (config.models || {}) as Record<string, unknown>;
const providers = (models.providers || {}) as Record<string, unknown>;
const existingProvider =
providers[provider] && typeof providers[provider] === 'object'
? (providers[provider] as Record<string, unknown>)
: {};
const existingModels = Array.isArray(existingProvider.models)
? (existingProvider.models as Array<Record<string, unknown>>)
: [];
const mergedModels = [...existingModels];
if (modelId && !mergedModels.some((m) => m.id === modelId)) {
mergedModels.push({ id: modelId, name: modelId });
// Replace the provider entry entirely rather than merging.
// Different custom/ollama provider instances have different baseUrls,
// so merging models from a previous instance creates an inconsistent
// config (models pointing at the wrong endpoint).
const nextModels: Array<Record<string, unknown>> = [];
if (modelId) {
nextModels.push({ id: modelId, name: modelId });
}
const nextProvider: Record<string, unknown> = {
...existingProvider,
baseUrl: override.baseUrl,
api: override.api,
models: mergedModels,
models: nextModels,
};
if (override.apiKeyEnv) {
nextProvider.apiKey = `\${${override.apiKeyEnv}}`;
nextProvider.apiKey = override.apiKeyEnv;
}
providers[provider] = nextProvider;

View File

@@ -52,7 +52,7 @@ const REGISTRY: Record<string, ProviderBackendMeta> = {
},
google: {
envVar: 'GEMINI_API_KEY',
defaultModel: 'google/gemini-3-pro-preview',
defaultModel: 'google/gemini-3.1-pro-preview',
// 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.
},
@@ -94,6 +94,9 @@ const REGISTRY: Record<string, ProviderBackendMeta> = {
apiKeyEnv: 'SILICONFLOW_API_KEY',
},
},
custom: {
envVar: 'CUSTOM_API_KEY',
},
// Additional providers with env var mappings but no default model
groq: { envVar: 'GROQ_API_KEY' },
deepgram: { envVar: 'DEEPGRAM_API_KEY' },