fix add model provider (#190)
This commit is contained in:
committed by
GitHub
Unverified
parent
d38a6b012d
commit
7929a43601
@@ -30,6 +30,7 @@ import {
|
|||||||
removeProviderFromOpenClaw,
|
removeProviderFromOpenClaw,
|
||||||
setOpenClawDefaultModel,
|
setOpenClawDefaultModel,
|
||||||
setOpenClawDefaultModelWithOverride,
|
setOpenClawDefaultModelWithOverride,
|
||||||
|
syncProviderConfigToOpenClaw,
|
||||||
updateAgentModelProvider,
|
updateAgentModelProvider,
|
||||||
} from '../utils/openclaw-auth';
|
} from '../utils/openclaw-auth';
|
||||||
import { logger } from '../utils/logger';
|
import { logger } from '../utils/logger';
|
||||||
@@ -832,16 +833,56 @@ function registerProviderHandlers(gatewayManager: GatewayManager): void {
|
|||||||
await saveProvider(config);
|
await saveProvider(config);
|
||||||
|
|
||||||
// Store the API key if provided
|
// Store the API key if provided
|
||||||
if (apiKey) {
|
if (apiKey !== undefined) {
|
||||||
await storeApiKey(config.id, apiKey);
|
const trimmedKey = apiKey.trim();
|
||||||
|
if (trimmedKey) {
|
||||||
|
await storeApiKey(config.id, trimmedKey);
|
||||||
|
|
||||||
// Also write to OpenClaw auth-profiles.json so the gateway can use it
|
// Also write to OpenClaw auth-profiles.json so the gateway can use it
|
||||||
try {
|
try {
|
||||||
saveProviderKeyToOpenClaw(config.type, apiKey);
|
saveProviderKeyToOpenClaw(config.type, trimmedKey);
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
console.warn('Failed to save key to OpenClaw auth-profiles:', err);
|
console.warn('Failed to save key to OpenClaw auth-profiles:', err);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Sync the provider configuration to openclaw.json so Gateway knows about it
|
||||||
|
try {
|
||||||
|
const meta = getProviderConfig(config.type);
|
||||||
|
const api = config.type === 'custom' || config.type === 'ollama' ? 'openai-completions' : meta?.api;
|
||||||
|
|
||||||
|
if (api) {
|
||||||
|
syncProviderConfigToOpenClaw(config.type, config.model, {
|
||||||
|
baseUrl: config.baseUrl || meta?.baseUrl,
|
||||||
|
api,
|
||||||
|
apiKeyEnv: meta?.apiKeyEnv,
|
||||||
|
});
|
||||||
|
|
||||||
|
if (config.type === 'custom' || config.type === 'ollama') {
|
||||||
|
const resolvedKey = apiKey !== undefined
|
||||||
|
? (apiKey.trim() || null)
|
||||||
|
: await getApiKey(config.id);
|
||||||
|
if (resolvedKey && config.baseUrl) {
|
||||||
|
const modelId = config.model;
|
||||||
|
updateAgentModelProvider(config.type, {
|
||||||
|
baseUrl: config.baseUrl,
|
||||||
|
api: 'openai-completions',
|
||||||
|
models: modelId ? [{ id: modelId, name: modelId }] : [],
|
||||||
|
apiKey: resolvedKey,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Restart Gateway so it picks up the new config and env vars
|
||||||
|
logger.info(`Restarting Gateway after saving provider "${config.type}" config`);
|
||||||
|
void gatewayManager.restart().catch((err) => {
|
||||||
|
logger.warn('Gateway restart after provider save failed:', err);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
} catch (err) {
|
||||||
|
console.warn('Failed to sync openclaw provider config:', err);
|
||||||
|
}
|
||||||
|
|
||||||
return { success: true };
|
return { success: true };
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
@@ -928,25 +969,21 @@ function registerProviderHandlers(gatewayManager: GatewayManager): void {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// If this provider is the current default, propagate model/baseUrl
|
// Sync the provider configuration to openclaw.json so Gateway knows about it
|
||||||
// changes to openclaw.json and models.json immediately so the gateway
|
|
||||||
// picks them up without requiring the user to re-activate the provider.
|
|
||||||
const defaultProviderId = await getDefaultProvider();
|
|
||||||
if (defaultProviderId === providerId) {
|
|
||||||
try {
|
try {
|
||||||
const modelOverride = nextConfig.model
|
const meta = getProviderConfig(nextConfig.type);
|
||||||
? `${nextConfig.type}/${nextConfig.model}`
|
const api = nextConfig.type === 'custom' || nextConfig.type === 'ollama' ? 'openai-completions' : meta?.api;
|
||||||
: undefined;
|
|
||||||
if (nextConfig.type === 'custom' || nextConfig.type === 'ollama') {
|
if (api) {
|
||||||
setOpenClawDefaultModelWithOverride(nextConfig.type, modelOverride, {
|
syncProviderConfigToOpenClaw(nextConfig.type, nextConfig.model, {
|
||||||
baseUrl: nextConfig.baseUrl,
|
baseUrl: nextConfig.baseUrl || meta?.baseUrl,
|
||||||
api: 'openai-completions',
|
api,
|
||||||
|
apiKeyEnv: meta?.apiKeyEnv,
|
||||||
});
|
});
|
||||||
// Also update per-agent models.json so the gateway sees the
|
|
||||||
// change immediately (baseUrl or model ID may have changed).
|
if (nextConfig.type === 'custom' || nextConfig.type === 'ollama') {
|
||||||
const resolvedKey =
|
const resolvedKey = apiKey !== undefined
|
||||||
apiKey !== undefined
|
? (apiKey.trim() || null)
|
||||||
? apiKey.trim() || null
|
|
||||||
: await getApiKey(providerId);
|
: await getApiKey(providerId);
|
||||||
if (resolvedKey && nextConfig.baseUrl) {
|
if (resolvedKey && nextConfig.baseUrl) {
|
||||||
const modelId = nextConfig.model;
|
const modelId = nextConfig.model;
|
||||||
@@ -957,13 +994,33 @@ function registerProviderHandlers(gatewayManager: GatewayManager): void {
|
|||||||
apiKey: resolvedKey,
|
apiKey: resolvedKey,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
} else {
|
|
||||||
setOpenClawDefaultModel(nextConfig.type, modelOverride);
|
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// If this provider is the current default, update the primary model
|
||||||
|
const defaultProviderId = await getDefaultProvider();
|
||||||
|
if (defaultProviderId === providerId) {
|
||||||
|
const modelOverride = nextConfig.model
|
||||||
|
? `${nextConfig.type}/${nextConfig.model}`
|
||||||
|
: undefined;
|
||||||
|
if (nextConfig.type !== 'custom' && nextConfig.type !== 'ollama') {
|
||||||
|
setOpenClawDefaultModel(nextConfig.type, modelOverride);
|
||||||
|
} else {
|
||||||
|
setOpenClawDefaultModelWithOverride(nextConfig.type, modelOverride, {
|
||||||
|
baseUrl: nextConfig.baseUrl,
|
||||||
|
api: 'openai-completions',
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Restart Gateway so it picks up the new config and env vars
|
||||||
|
logger.info(`Restarting Gateway after updating provider "${nextConfig.type}" config`);
|
||||||
|
void gatewayManager.restart().catch((err) => {
|
||||||
|
logger.warn('Gateway restart after provider update failed:', err);
|
||||||
|
});
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
console.warn('Failed to sync openclaw config after provider update:', err);
|
console.warn('Failed to sync openclaw config after provider update:', err);
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
return { success: true };
|
return { success: true };
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
@@ -1027,11 +1084,12 @@ function registerProviderHandlers(gatewayManager: GatewayManager): void {
|
|||||||
const provider = await getProvider(providerId);
|
const provider = await getProvider(providerId);
|
||||||
if (provider) {
|
if (provider) {
|
||||||
try {
|
try {
|
||||||
// OAuth providers (qwen-portal, minimax-portal) have their openclaw.json
|
const providerKey = await getApiKey(providerId);
|
||||||
// model config already written by `openclaw models auth login --set-default`.
|
|
||||||
// Non-OAuth providers need us to write it here.
|
// OAuth providers (qwen-portal, minimax-portal) might use OAuth OR a direct API key.
|
||||||
|
// Treat them as OAuth only if they don't have a local API key configured.
|
||||||
const OAUTH_PROVIDER_TYPES = ['qwen-portal', 'minimax-portal'];
|
const OAUTH_PROVIDER_TYPES = ['qwen-portal', 'minimax-portal'];
|
||||||
const isOAuthProvider = OAUTH_PROVIDER_TYPES.includes(provider.type);
|
const isOAuthProvider = OAUTH_PROVIDER_TYPES.includes(provider.type) && !providerKey;
|
||||||
|
|
||||||
if (!isOAuthProvider) {
|
if (!isOAuthProvider) {
|
||||||
// If the provider has a user-specified model (e.g. siliconflow),
|
// If the provider has a user-specified model (e.g. siliconflow),
|
||||||
|
|||||||
@@ -444,6 +444,66 @@ interface RuntimeProviderConfigOverride {
|
|||||||
apiKeyEnv?: string;
|
apiKeyEnv?: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Register or update a provider's configuration in openclaw.json
|
||||||
|
* without changing the current default model.
|
||||||
|
*/
|
||||||
|
export function syncProviderConfigToOpenClaw(
|
||||||
|
provider: string,
|
||||||
|
modelId: string | undefined,
|
||||||
|
override: RuntimeProviderConfigOverride
|
||||||
|
): void {
|
||||||
|
const configPath = join(homedir(), '.openclaw', 'openclaw.json');
|
||||||
|
|
||||||
|
let config: Record<string, unknown> = {};
|
||||||
|
try {
|
||||||
|
if (existsSync(configPath)) {
|
||||||
|
config = JSON.parse(readFileSync(configPath, 'utf-8'));
|
||||||
|
}
|
||||||
|
} catch (err) {
|
||||||
|
console.warn('Failed to read openclaw.json, creating fresh config:', err);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (override.baseUrl && override.api) {
|
||||||
|
const models = (config.models || {}) as Record<string, unknown>;
|
||||||
|
const providers = (models.providers || {}) as Record<string, unknown>;
|
||||||
|
|
||||||
|
const nextModels: Array<Record<string, unknown>> = [];
|
||||||
|
if (modelId) {
|
||||||
|
nextModels.push({ id: modelId, name: modelId });
|
||||||
|
}
|
||||||
|
|
||||||
|
const nextProvider: Record<string, unknown> = {
|
||||||
|
baseUrl: override.baseUrl,
|
||||||
|
api: override.api,
|
||||||
|
models: nextModels,
|
||||||
|
};
|
||||||
|
if (override.apiKeyEnv) {
|
||||||
|
nextProvider.apiKey = override.apiKeyEnv;
|
||||||
|
}
|
||||||
|
|
||||||
|
providers[provider] = nextProvider;
|
||||||
|
models.providers = providers;
|
||||||
|
config.models = models;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Ensure extension is enabled for oauth providers to prevent gateway wiping config
|
||||||
|
if (provider === 'minimax-portal' || provider === 'qwen-portal') {
|
||||||
|
const plugins = (config.plugins || {}) as Record<string, unknown>;
|
||||||
|
const entries = (plugins.entries || {}) as Record<string, unknown>;
|
||||||
|
entries[`${provider}-auth`] = { enabled: true };
|
||||||
|
plugins.entries = entries;
|
||||||
|
config.plugins = plugins;
|
||||||
|
}
|
||||||
|
|
||||||
|
const dir = join(configPath, '..');
|
||||||
|
if (!existsSync(dir)) {
|
||||||
|
mkdirSync(dir, { recursive: true });
|
||||||
|
}
|
||||||
|
|
||||||
|
writeFileSync(configPath, JSON.stringify(config, null, 2), 'utf-8');
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Update OpenClaw model + provider config using runtime config values.
|
* Update OpenClaw model + provider config using runtime config values.
|
||||||
* Useful for user-configurable providers (custom/ollama-like) where
|
* Useful for user-configurable providers (custom/ollama-like) where
|
||||||
|
|||||||
@@ -97,10 +97,22 @@ const REGISTRY: Record<string, ProviderBackendMeta> = {
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
'minimax-portal': {
|
'minimax-portal': {
|
||||||
|
envVar: 'MINIMAX_API_KEY',
|
||||||
defaultModel: 'minimax-portal/MiniMax-M2.1',
|
defaultModel: 'minimax-portal/MiniMax-M2.1',
|
||||||
|
providerConfig: {
|
||||||
|
baseUrl: 'https://api.minimax.io/anthropic',
|
||||||
|
api: 'anthropic-messages',
|
||||||
|
apiKeyEnv: 'MINIMAX_API_KEY',
|
||||||
|
},
|
||||||
},
|
},
|
||||||
'qwen-portal': {
|
'qwen-portal': {
|
||||||
|
envVar: 'QWEN_API_KEY',
|
||||||
defaultModel: 'qwen-portal/coder-model',
|
defaultModel: 'qwen-portal/coder-model',
|
||||||
|
providerConfig: {
|
||||||
|
baseUrl: 'https://portal.qwen.ai/v1',
|
||||||
|
api: 'openai-completions',
|
||||||
|
apiKeyEnv: 'QWEN_API_KEY',
|
||||||
|
},
|
||||||
},
|
},
|
||||||
custom: {
|
custom: {
|
||||||
envVar: 'CUSTOM_API_KEY',
|
envVar: 'CUSTOM_API_KEY',
|
||||||
|
|||||||
Reference in New Issue
Block a user