feat(runner): validate provider credentials on startup

- Add isOAuthProvider() and isProviderAvailable() helpers
- Show login instructions for OAuth providers without credentials
- Show configuration example for API Key providers without keys
- Fail early with helpful error message instead of cryptic API errors
This commit is contained in:
Jiang Bohan 2026-02-02 17:10:10 +08:00
parent 051e56dbf6
commit 35b13f976d
2 changed files with 52 additions and 4 deletions

View file

@ -269,3 +269,24 @@ export function getLoginInstructions(providerId: string): string {
return "No login instructions available.";
}
/**
* Check if a provider uses OAuth authentication
*/
export function isOAuthProvider(providerId: string): boolean {
const info = PROVIDER_INFO[providerId];
return info?.authMethod === "oauth";
}
/**
* Check if provider is available (has valid credentials)
*/
export function isProviderAvailable(providerId: string): boolean {
const info = PROVIDER_INFO[providerId];
if (!info) return false;
if (info.authMethod === "oauth") {
return isOAuthAvailable(providerId);
}
return isApiKeyConfigured(providerId);
}

View file

@ -7,7 +7,7 @@ import { SessionManager } from "./session/session-manager.js";
import { ProfileManager } from "./profile/index.js";
import { SkillManager } from "./skills/index.js";
import { credentialManager, getCredentialsPath } from "./credentials.js";
import { resolveProviderConfig } from "./oauth/providers.js";
import { resolveProviderConfig, isOAuthProvider, getLoginInstructions } from "./oauth/providers.js";
import {
checkContextWindow,
DEFAULT_CONTEXT_TOKENS,
@ -79,10 +79,37 @@ export class Agent {
const resolvedModel = resolveModelId(resolvedProvider, options.model);
const apiKey = resolveApiKey(resolvedProvider, options.apiKey);
// Validate credentials before proceeding
if (!apiKey) {
if (isOAuthProvider(resolvedProvider)) {
// OAuth provider without valid credentials - show login instructions
const instructions = getLoginInstructions(resolvedProvider);
throw new Error(
`Provider "${resolvedProvider}" requires authentication.\n\n` +
`${instructions}\n\n` +
`After logging in, run: multica --provider ${resolvedProvider}`,
);
}
// API Key provider without key - show configuration instructions
throw new Error(
`Provider "${resolvedProvider}" requires an API key.\n\n` +
`Add your API key to: ${getCredentialsPath()}\n\n` +
`Example:\n` +
`{\n` +
` "llm": {\n` +
` "provider": "${resolvedProvider}",\n` +
` "providers": {\n` +
` "${resolvedProvider}": {\n` +
` "apiKey": "your-api-key-here"\n` +
` }\n` +
` }\n` +
` }\n` +
`}`,
);
}
this.agent = new PiAgentCore(
apiKey
? { getApiKey: (_provider: string) => apiKey }
: {},
{ getApiKey: (_provider: string) => apiKey },
);
// Load Agent Profile (if profileId is specified)