fix: clear error messages for missing API keys on provider switch
Previously, switching to zhipuai/openrouter/xai via /model would throw a confusing 'OPENAI_API_KEY missing' error from the OpenAI SDK. Now createClientFromConfig validates API keys before constructing the client, throwing errors that name the correct env var (e.g. ZHIPUAI_API_KEY). Also fixes the misleading 'as anthropic' type cast in the /model handler to validate against MODEL_PROVIDERS and use the ModelProvider type.
This commit is contained in:
+18
-3
@@ -3,6 +3,21 @@ import { AnthropicClient, OpenAIClient, OllamaClient, LlamaCppClient, GeminiClie
|
|||||||
import type { ModelClient, RetryConfig, ModelTier } from '../models/index.js';
|
import type { ModelClient, RetryConfig, ModelTier } from '../models/index.js';
|
||||||
import { logger } from '../logger.js';
|
import { logger } from '../logger.js';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Resolve an API key from config or environment variable.
|
||||||
|
* Throws a clear error naming the expected env var if neither source provides a key.
|
||||||
|
*/
|
||||||
|
function requireApiKey(cfg: ModelConfig, envVar: string): string {
|
||||||
|
const key = cfg.api_key ?? process.env[envVar];
|
||||||
|
if (!key) {
|
||||||
|
throw new Error(
|
||||||
|
`API key required for ${cfg.provider}. ` +
|
||||||
|
`Set ${envVar} environment variable or provide api_key in config.`
|
||||||
|
);
|
||||||
|
}
|
||||||
|
return key;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Create a ModelClient from a provider config entry.
|
* Create a ModelClient from a provider config entry.
|
||||||
* Dispatches on the `provider` field so all tiers and fallback entries
|
* Dispatches on the `provider` field so all tiers and fallback entries
|
||||||
@@ -41,19 +56,19 @@ export function createClientFromConfig(cfg: ModelConfig): ModelClient {
|
|||||||
case 'openrouter':
|
case 'openrouter':
|
||||||
return new OpenAIClient({
|
return new OpenAIClient({
|
||||||
model: cfg.model,
|
model: cfg.model,
|
||||||
apiKey: cfg.api_key ?? process.env.OPENROUTER_API_KEY,
|
apiKey: requireApiKey(cfg, 'OPENROUTER_API_KEY'),
|
||||||
baseURL: cfg.endpoint ?? 'https://openrouter.ai/api/v1',
|
baseURL: cfg.endpoint ?? 'https://openrouter.ai/api/v1',
|
||||||
});
|
});
|
||||||
case 'zhipuai':
|
case 'zhipuai':
|
||||||
return new OpenAIClient({
|
return new OpenAIClient({
|
||||||
model: cfg.model,
|
model: cfg.model,
|
||||||
apiKey: cfg.api_key ?? process.env.ZHIPUAI_API_KEY,
|
apiKey: requireApiKey(cfg, 'ZHIPUAI_API_KEY'),
|
||||||
baseURL: cfg.endpoint ?? 'https://api.z.ai/api/paas/v4',
|
baseURL: cfg.endpoint ?? 'https://api.z.ai/api/paas/v4',
|
||||||
});
|
});
|
||||||
case 'xai':
|
case 'xai':
|
||||||
return new OpenAIClient({
|
return new OpenAIClient({
|
||||||
model: cfg.model,
|
model: cfg.model,
|
||||||
apiKey: cfg.api_key ?? process.env.XAI_API_KEY,
|
apiKey: requireApiKey(cfg, 'XAI_API_KEY'),
|
||||||
baseURL: cfg.endpoint ?? 'https://api.x.ai/v1',
|
baseURL: cfg.endpoint ?? 'https://api.x.ai/v1',
|
||||||
});
|
});
|
||||||
case 'bedrock':
|
case 'bedrock':
|
||||||
|
|||||||
@@ -5,7 +5,8 @@ import type { ModelRouter, ModelTier } from '../../models/router.js';
|
|||||||
import type { NativeAgent } from '../../backends/native/agent.js';
|
import type { NativeAgent } from '../../backends/native/agent.js';
|
||||||
import { parseCommand, getHelpText, resolveModelAlias, getCommandCompletions, getCommandTooltip, type Command } from './commands.js';
|
import { parseCommand, getHelpText, resolveModelAlias, getCommandCompletions, getCommandTooltip, type Command } from './commands.js';
|
||||||
import { renderMarkdown } from './markdown.js';
|
import { renderMarkdown } from './markdown.js';
|
||||||
import type { ModelConfig } from '../../config/schema.js';
|
import type { ModelConfig, ModelProvider } from '../../config/schema.js';
|
||||||
|
import { MODEL_PROVIDERS } from '../../config/schema.js';
|
||||||
import { OllamaClient, LlamaCppClient } from '../../models/index.js';
|
import { OllamaClient, LlamaCppClient } from '../../models/index.js';
|
||||||
import { createClientFromConfig } from '../../daemon/index.js';
|
import { createClientFromConfig } from '../../daemon/index.js';
|
||||||
import { loginGitHub } from '../../auth/index.js';
|
import { loginGitHub } from '../../auth/index.js';
|
||||||
@@ -232,8 +233,13 @@ export class MinimalTui {
|
|||||||
const provider = providerModel.slice(0, slashIdx);
|
const provider = providerModel.slice(0, slashIdx);
|
||||||
const model = providerModel.slice(slashIdx + 1);
|
const model = providerModel.slice(slashIdx + 1);
|
||||||
|
|
||||||
|
if (!MODEL_PROVIDERS.includes(provider as ModelProvider)) {
|
||||||
|
console.log(`${colors.gray}Unknown provider "${provider}". Known providers: ${MODEL_PROVIDERS.join(', ')}${colors.reset}\n`);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const client = createClientFromConfig({ provider: provider as 'anthropic', model });
|
const client = createClientFromConfig({ provider: provider as ModelProvider, model });
|
||||||
router.setClient(tier, client, providerModel);
|
router.setClient(tier, client, providerModel);
|
||||||
console.log(`${colors.gray}Set ${tier} to:${colors.reset} ${providerModel}\n`);
|
console.log(`${colors.gray}Set ${tier} to:${colors.reset} ${providerModel}\n`);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
|
|||||||
Reference in New Issue
Block a user