From 6723aa856186b9fc6ff6111e225dea8726e1b433 Mon Sep 17 00:00:00 2001 From: Jiang Bohan Date: Mon, 2 Feb 2026 17:20:02 +0800 Subject: [PATCH] refactor(providers): extract provider management to dedicated module - Create src/agent/providers/ with registry.ts and resolver.ts - registry.ts: Provider metadata, status checking, login instructions - resolver.ts: API key resolution, model resolution - oauth/providers.ts now re-exports from providers/ (deprecated) - tools.ts: Remove PROVIDER_ALIAS and DEFAULT_MODELS (moved to providers/) - Update imports in runner.ts and chat.ts This separates concerns: - oauth/ only handles OAuth credential reading - providers/ manages all provider metadata and resolution --- src/agent/cli/commands/chat.ts | 2 +- src/agent/oauth/providers.ts | 304 ++------------------------------ src/agent/providers/index.ts | 34 ++++ src/agent/providers/registry.ts | 275 +++++++++++++++++++++++++++++ src/agent/providers/resolver.ts | 166 +++++++++++++++++ src/agent/runner.ts | 49 +---- src/agent/tools.ts | 55 +----- 7 files changed, 503 insertions(+), 382 deletions(-) create mode 100644 src/agent/providers/index.ts create mode 100644 src/agent/providers/registry.ts create mode 100644 src/agent/providers/resolver.ts diff --git a/src/agent/cli/commands/chat.ts b/src/agent/cli/commands/chat.ts index 244c6e94..16d897eb 100644 --- a/src/agent/cli/commands/chat.ts +++ b/src/agent/cli/commands/chat.ts @@ -17,7 +17,7 @@ import { getCurrentProvider, getLoginInstructions, type ProviderInfo, -} from "../../oauth/providers.js"; +} from "../../providers/index.js"; type ChatOptions = { profile?: string; diff --git a/src/agent/oauth/providers.ts b/src/agent/oauth/providers.ts index 53363cb7..ddd5f8dc 100644 --- a/src/agent/oauth/providers.ts +++ b/src/agent/oauth/providers.ts @@ -1,292 +1,20 @@ /** - * Provider Management + * @deprecated This file is deprecated. Import from '../providers/index.js' instead. * - * Manage LLM providers with support for: - * - API Key authentication (traditional) - * - OAuth authentication (Claude Code, Codex) + * This file re-exports from the new providers/ module for backwards compatibility. + * Will be removed in a future version. */ -import { credentialManager } from "../credentials.js"; -import { - readClaudeCliCredentials, - readCodexCliCredentials, - hasValidClaudeCliCredentials, - hasValidCodexCliCredentials, - type ClaudeCliCredential, - type CodexCliCredential, -} from "./cli-credentials.js"; - -// ============================================================ -// Types -// ============================================================ - -export type AuthMethod = "api-key" | "oauth"; - -export interface ProviderInfo { - id: string; - name: string; - authMethod: AuthMethod; - available: boolean; - configured: boolean; - current: boolean; - models: string[]; - loginUrl?: string; - loginCommand?: string; -} - -export interface ProviderConfig { - provider: string; - model?: string | undefined; - apiKey?: string | undefined; - baseUrl?: string | undefined; - // OAuth specific - accessToken?: string | undefined; - refreshToken?: string | undefined; - expires?: number | undefined; -} - -// ============================================================ -// Provider Registry -// ============================================================ - -const PROVIDER_INFO: Record> = { - "anthropic": { - id: "anthropic", - name: "Anthropic (API Key)", - authMethod: "api-key", - models: ["claude-sonnet-4-20250514", "claude-opus-4-20250514", "claude-haiku-3-5-20241022"], - loginUrl: "https://console.anthropic.com/", - }, - "claude-code": { - id: "claude-code", - name: "Claude Code (OAuth)", - authMethod: "oauth", - models: ["claude-sonnet-4-20250514", "claude-opus-4-20250514"], - loginCommand: "claude login", - }, - "openai": { - id: "openai", - name: "OpenAI", - authMethod: "api-key", - models: ["gpt-4o", "gpt-4o-mini", "o1", "o1-mini"], - loginUrl: "https://platform.openai.com/api-keys", - }, - "openai-codex": { - id: "openai-codex", - name: "Codex (OAuth)", - authMethod: "oauth", - models: ["gpt-5.1", "gpt-5.1-codex-max"], - loginCommand: "codex login", - }, - "kimi-coding": { - id: "kimi-coding", - name: "Kimi Code", - authMethod: "api-key", - models: ["kimi-k2-thinking", "k2p5"], - loginUrl: "https://kimi.moonshot.cn/", - }, - "google": { - id: "google", - name: "Google AI", - authMethod: "api-key", - models: ["gemini-2.0-flash", "gemini-1.5-pro"], - loginUrl: "https://aistudio.google.com/apikey", - }, - "groq": { - id: "groq", - name: "Groq", - authMethod: "api-key", - models: ["llama-3.3-70b-versatile", "mixtral-8x7b-32768"], - loginUrl: "https://console.groq.com/keys", - }, - "mistral": { - id: "mistral", - name: "Mistral", - authMethod: "api-key", - models: ["mistral-large-latest", "codestral-latest"], - loginUrl: "https://console.mistral.ai/api-keys", - }, - "xai": { - id: "xai", - name: "xAI (Grok)", - authMethod: "api-key", - models: ["grok-beta", "grok-vision-beta"], - loginUrl: "https://console.x.ai/", - }, - "openrouter": { - id: "openrouter", - name: "OpenRouter", - authMethod: "api-key", - models: ["anthropic/claude-3.5-sonnet", "openai/gpt-4o"], - loginUrl: "https://openrouter.ai/keys", - }, -}; - -// ============================================================ -// Provider Status -// ============================================================ - -/** - * Check if a provider is configured with API key in credentials.json5 - */ -function isApiKeyConfigured(providerId: string): boolean { - const config = credentialManager.getLlmProviderConfig(providerId); - return !!config?.apiKey; -} - -/** - * Check if OAuth provider has valid credentials - */ -function isOAuthAvailable(providerId: string): boolean { - if (providerId === "claude-code") { - return hasValidClaudeCliCredentials(); - } - if (providerId === "openai-codex") { - return hasValidCodexCliCredentials(); - } - return false; -} - -/** - * Get current provider from credentials - */ -export function getCurrentProvider(): string { - return credentialManager.getLlmProvider() ?? "kimi-coding"; -} - -/** - * Get list of all providers with their status - */ -export function getProviderList(): ProviderInfo[] { - const currentProvider = getCurrentProvider(); - - return Object.values(PROVIDER_INFO).map((info) => { - const isOAuth = info.authMethod === "oauth"; - const available = isOAuth ? isOAuthAvailable(info.id) : isApiKeyConfigured(info.id); - const configured = isOAuth ? isOAuthAvailable(info.id) : isApiKeyConfigured(info.id); - - // Check if this is the current provider - // For claude-code, check if current is "anthropic" and OAuth is available - let isCurrent = currentProvider === info.id; - if (info.id === "claude-code" && currentProvider === "anthropic") { - // If anthropic is current and claude-code OAuth is available, mark both - isCurrent = hasValidClaudeCliCredentials(); - } - - return { - ...info, - available, - configured, - current: isCurrent, - }; - }); -} - -/** - * Get available providers only - */ -export function getAvailableProviders(): ProviderInfo[] { - return getProviderList().filter((p) => p.available); -} - -// ============================================================ -// Provider Resolution -// ============================================================ - -/** - * Get provider config for making API calls - */ -export function resolveProviderConfig(providerId: string): ProviderConfig | null { - const info = PROVIDER_INFO[providerId]; - if (!info) return null; - - if (info.authMethod === "oauth") { - if (providerId === "claude-code") { - const creds = readClaudeCliCredentials(); - if (!creds) return null; - - const accessToken = creds.type === "oauth" ? creds.access : creds.token; - return { - provider: "anthropic", // Use anthropic API - apiKey: accessToken, - accessToken, - refreshToken: creds.type === "oauth" ? creds.refresh : undefined, - expires: creds.expires, - }; - } - - if (providerId === "openai-codex") { - const creds = readCodexCliCredentials(); - if (!creds) return null; - - return { - provider: "openai-codex", - accessToken: creds.access, - refreshToken: creds.refresh, - expires: creds.expires, - }; - } - } - - // API Key based - const config = credentialManager.getLlmProviderConfig(providerId); - if (!config?.apiKey) return null; - - return { - provider: providerId, - model: config.model, - apiKey: config.apiKey, - baseUrl: config.baseUrl, - }; -} - -/** - * Format provider for display - */ -export function formatProviderStatus(provider: ProviderInfo): string { - const status = provider.available ? "✓" : "✗"; - const current = provider.current ? " (current)" : ""; - const auth = provider.authMethod === "oauth" ? " [OAuth]" : ""; - return `${status} ${provider.name}${auth}${current}`; -} - -/** - * Get login instructions for a provider - */ -export function getLoginInstructions(providerId: string): string { - const info = PROVIDER_INFO[providerId]; - if (!info) return `Unknown provider: ${providerId}`; - - if (info.authMethod === "oauth") { - if (info.loginCommand) { - return `Run: ${info.loginCommand}\nThen restart Super Multica to use the credentials.`; - } - } - - if (info.loginUrl) { - return `Get your API key at: ${info.loginUrl}\nThen add it to ~/.super-multica/credentials.json5`; - } - - 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); -} +export { + type AuthMethod, + type ProviderInfo, + type ProviderConfig, + isOAuthProvider, + isProviderAvailable, + getCurrentProvider, + getProviderList, + getAvailableProviders, + formatProviderStatus, + getLoginInstructions, + resolveProviderConfig, +} from "../providers/index.js"; diff --git a/src/agent/providers/index.ts b/src/agent/providers/index.ts new file mode 100644 index 00000000..25108916 --- /dev/null +++ b/src/agent/providers/index.ts @@ -0,0 +1,34 @@ +/** + * Provider Management + * + * Unified exports for LLM provider management: + * - Registry: Provider metadata, status checking, listing + * - Resolver: API key resolution, model resolution + */ + +// Registry exports +export { + type AuthMethod, + type ProviderInfo, + type ProviderMeta, + PROVIDER_ALIAS, + isOAuthProvider, + isProviderAvailable, + getCurrentProvider, + getProviderMeta, + getDefaultModel, + getProviderList, + getAvailableProviders, + formatProviderStatus, + getLoginInstructions, +} from "./registry.js"; + +// Resolver exports +export { + type ProviderConfig, + resolveProviderConfig, + resolveApiKey, + resolveBaseUrl, + resolveModelId, + resolveModel, +} from "./resolver.js"; diff --git a/src/agent/providers/registry.ts b/src/agent/providers/registry.ts new file mode 100644 index 00000000..acff7ee9 --- /dev/null +++ b/src/agent/providers/registry.ts @@ -0,0 +1,275 @@ +/** + * Provider Registry + * + * Central registry for all LLM providers with metadata, + * status checking, and display formatting. + */ + +import { credentialManager } from "../credentials.js"; +import { + hasValidClaudeCliCredentials, + hasValidCodexCliCredentials, +} from "../oauth/cli-credentials.js"; + +// ============================================================ +// Types +// ============================================================ + +export type AuthMethod = "api-key" | "oauth"; + +export interface ProviderInfo { + id: string; + name: string; + authMethod: AuthMethod; + available: boolean; + configured: boolean; + current: boolean; + defaultModel: string; + models: string[]; + loginUrl?: string | undefined; + loginCommand?: string | undefined; +} + +/** Static provider metadata (without runtime status) */ +export interface ProviderMeta { + id: string; + name: string; + authMethod: AuthMethod; + defaultModel: string; + models: string[]; + loginUrl?: string | undefined; + loginCommand?: string | undefined; +} + +// ============================================================ +// Provider Registry +// ============================================================ + +const PROVIDER_REGISTRY: Record = { + "claude-code": { + id: "claude-code", + name: "Claude Code (OAuth)", + authMethod: "oauth", + defaultModel: "claude-sonnet-4-20250514", + models: ["claude-sonnet-4-20250514", "claude-opus-4-20250514"], + loginCommand: "claude login", + }, + "openai-codex": { + id: "openai-codex", + name: "Codex (OAuth)", + authMethod: "oauth", + defaultModel: "gpt-5.1", + models: ["gpt-5.1", "gpt-5.1-codex-max"], + loginCommand: "codex login", + }, + "anthropic": { + id: "anthropic", + name: "Anthropic (API Key)", + authMethod: "api-key", + defaultModel: "claude-sonnet-4-20250514", + models: ["claude-sonnet-4-20250514", "claude-opus-4-20250514", "claude-haiku-3-5-20241022"], + loginUrl: "https://console.anthropic.com/", + }, + "openai": { + id: "openai", + name: "OpenAI", + authMethod: "api-key", + defaultModel: "gpt-4o", + models: ["gpt-4o", "gpt-4o-mini", "o1", "o1-mini"], + loginUrl: "https://platform.openai.com/api-keys", + }, + "kimi-coding": { + id: "kimi-coding", + name: "Kimi Code", + authMethod: "api-key", + defaultModel: "kimi-k2-thinking", + models: ["kimi-k2-thinking", "k2p5"], + loginUrl: "https://kimi.moonshot.cn/", + }, + "google": { + id: "google", + name: "Google AI", + authMethod: "api-key", + defaultModel: "gemini-2.0-flash", + models: ["gemini-2.0-flash", "gemini-1.5-pro"], + loginUrl: "https://aistudio.google.com/apikey", + }, + "groq": { + id: "groq", + name: "Groq", + authMethod: "api-key", + defaultModel: "llama-3.3-70b-versatile", + models: ["llama-3.3-70b-versatile", "mixtral-8x7b-32768"], + loginUrl: "https://console.groq.com/keys", + }, + "mistral": { + id: "mistral", + name: "Mistral", + authMethod: "api-key", + defaultModel: "mistral-large-latest", + models: ["mistral-large-latest", "codestral-latest"], + loginUrl: "https://console.mistral.ai/api-keys", + }, + "xai": { + id: "xai", + name: "xAI (Grok)", + authMethod: "api-key", + defaultModel: "grok-beta", + models: ["grok-beta", "grok-vision-beta"], + loginUrl: "https://console.x.ai/", + }, + "openrouter": { + id: "openrouter", + name: "OpenRouter", + authMethod: "api-key", + defaultModel: "anthropic/claude-3.5-sonnet", + models: ["anthropic/claude-3.5-sonnet", "openai/gpt-4o"], + loginUrl: "https://openrouter.ai/keys", + }, +}; + +/** + * Provider alias mapping for OAuth providers. + * Maps friendly names to actual pi-ai provider names. + */ +export const PROVIDER_ALIAS: Record = { + "claude-code": "anthropic", // Claude Code OAuth uses anthropic API +}; + +// ============================================================ +// Status Checking +// ============================================================ + +/** + * Check if a provider is configured with API key in credentials.json5 + */ +function isApiKeyConfigured(providerId: string): boolean { + const config = credentialManager.getLlmProviderConfig(providerId); + return !!config?.apiKey; +} + +/** + * Check if OAuth provider has valid credentials + */ +function isOAuthAvailable(providerId: string): boolean { + if (providerId === "claude-code") { + return hasValidClaudeCliCredentials(); + } + if (providerId === "openai-codex") { + return hasValidCodexCliCredentials(); + } + return false; +} + +/** + * Check if a provider uses OAuth authentication + */ +export function isOAuthProvider(providerId: string): boolean { + const info = PROVIDER_REGISTRY[providerId]; + return info?.authMethod === "oauth"; +} + +/** + * Check if provider is available (has valid credentials) + */ +export function isProviderAvailable(providerId: string): boolean { + const info = PROVIDER_REGISTRY[providerId]; + if (!info) return false; + + if (info.authMethod === "oauth") { + return isOAuthAvailable(providerId); + } + return isApiKeyConfigured(providerId); +} + +/** + * Get current provider from credentials + */ +export function getCurrentProvider(): string { + return credentialManager.getLlmProvider() ?? "kimi-coding"; +} + +// ============================================================ +// Provider Listing +// ============================================================ + +/** + * Get static provider metadata + */ +export function getProviderMeta(providerId: string): ProviderMeta | undefined { + return PROVIDER_REGISTRY[providerId]; +} + +/** + * Get default model for a provider + */ +export function getDefaultModel(providerId: string): string | undefined { + return PROVIDER_REGISTRY[providerId]?.defaultModel; +} + +/** + * Get list of all providers with their runtime status + */ +export function getProviderList(): ProviderInfo[] { + const currentProvider = getCurrentProvider(); + + return Object.values(PROVIDER_REGISTRY).map((meta) => { + const isOAuth = meta.authMethod === "oauth"; + const available = isOAuth ? isOAuthAvailable(meta.id) : isApiKeyConfigured(meta.id); + + // Check if this is the current provider + // For claude-code, check if current is "anthropic" and OAuth is available + let isCurrent = currentProvider === meta.id; + if (meta.id === "claude-code" && currentProvider === "anthropic") { + isCurrent = hasValidClaudeCliCredentials(); + } + + return { + ...meta, + available, + configured: available, + current: isCurrent, + }; + }); +} + +/** + * Get available providers only + */ +export function getAvailableProviders(): ProviderInfo[] { + return getProviderList().filter((p) => p.available); +} + +// ============================================================ +// Display Helpers +// ============================================================ + +/** + * Format provider for display + */ +export function formatProviderStatus(provider: ProviderInfo): string { + const status = provider.available ? "✓" : "✗"; + const current = provider.current ? " (current)" : ""; + const auth = provider.authMethod === "oauth" ? " [OAuth]" : ""; + return `${status} ${provider.name}${auth}${current}`; +} + +/** + * Get login instructions for a provider + */ +export function getLoginInstructions(providerId: string): string { + const info = PROVIDER_REGISTRY[providerId]; + if (!info) return `Unknown provider: ${providerId}`; + + if (info.authMethod === "oauth") { + if (info.loginCommand) { + return `Run: ${info.loginCommand}\nThen restart Super Multica to use the credentials.`; + } + } + + if (info.loginUrl) { + return `Get your API key at: ${info.loginUrl}\nThen add it to ~/.super-multica/credentials.json5`; + } + + return "No login instructions available."; +} diff --git a/src/agent/providers/resolver.ts b/src/agent/providers/resolver.ts new file mode 100644 index 00000000..a29748e8 --- /dev/null +++ b/src/agent/providers/resolver.ts @@ -0,0 +1,166 @@ +/** + * Provider Resolver + * + * Resolves provider configuration for making API calls, + * including API keys, OAuth tokens, and model selection. + */ + +import { getModel } from "@mariozechner/pi-ai"; +import { credentialManager } from "../credentials.js"; +import { + readClaudeCliCredentials, + readCodexCliCredentials, +} from "../oauth/cli-credentials.js"; +import { + PROVIDER_ALIAS, + getProviderMeta, + getDefaultModel, + isOAuthProvider, +} from "./registry.js"; +import type { AgentOptions } from "../types.js"; + +// ============================================================ +// Types +// ============================================================ + +export interface ProviderConfig { + provider: string; + model?: string | undefined; + apiKey?: string | undefined; + baseUrl?: string | undefined; + // OAuth specific + accessToken?: string | undefined; + refreshToken?: string | undefined; + expires?: number | undefined; +} + +// ============================================================ +// Provider Config Resolution +// ============================================================ + +/** + * Get provider config for making API calls. + * Handles both OAuth and API Key authentication. + */ +export function resolveProviderConfig(providerId: string): ProviderConfig | null { + const meta = getProviderMeta(providerId); + if (!meta) return null; + + if (meta.authMethod === "oauth") { + if (providerId === "claude-code") { + const creds = readClaudeCliCredentials(); + if (!creds) return null; + + const accessToken = creds.type === "oauth" ? creds.access : creds.token; + return { + provider: "anthropic", // Use anthropic API + apiKey: accessToken, + accessToken, + refreshToken: creds.type === "oauth" ? creds.refresh : undefined, + expires: creds.expires, + }; + } + + if (providerId === "openai-codex") { + const creds = readCodexCliCredentials(); + if (!creds) return null; + + return { + provider: "openai-codex", + accessToken: creds.access, + refreshToken: creds.refresh, + expires: creds.expires, + }; + } + } + + // API Key based + const config = credentialManager.getLlmProviderConfig(providerId); + if (!config?.apiKey) return null; + + return { + provider: providerId, + model: config.model, + apiKey: config.apiKey, + baseUrl: config.baseUrl, + }; +} + +// ============================================================ +// API Key Resolution +// ============================================================ + +/** + * Get API Key based on provider. + * Priority: explicit key > OAuth credentials > credentials.json5 config. + */ +export function resolveApiKey(provider: string, explicitKey?: string): string | undefined { + if (explicitKey) return explicitKey; + + // Try OAuth providers first (claude-code, openai-codex) + const providerConfig = resolveProviderConfig(provider); + if (providerConfig?.apiKey) { + return providerConfig.apiKey; + } + if (providerConfig?.accessToken) { + return providerConfig.accessToken; + } + + // Fall back to credentials.json5 + return credentialManager.getLlmProviderConfig(provider)?.apiKey; +} + +/** + * Get Base URL based on provider. + * Priority: explicit URL > credentials.json5 config. + */ +export function resolveBaseUrl(provider: string, explicitUrl?: string): string | undefined { + if (explicitUrl) return explicitUrl; + return credentialManager.getLlmProviderConfig(provider)?.baseUrl; +} + +/** + * Get Model ID based on provider. + * Priority: explicit model > credentials.json5 config > default. + */ +export function resolveModelId(provider: string, explicitModel?: string): string | undefined { + if (explicitModel) return explicitModel; + return credentialManager.getLlmProviderConfig(provider)?.model ?? getDefaultModel(provider); +} + +// ============================================================ +// Model Resolution +// ============================================================ + +/** + * Resolve model for pi-ai based on provider and options. + */ +export function resolveModel(options: AgentOptions) { + if (options.provider && options.model) { + // Map provider alias (e.g., claude-code -> anthropic) + const actualProvider = PROVIDER_ALIAS[options.provider] ?? options.provider; + + // Type assertion needed because provider/model come from dynamic user config + return (getModel as (p: string, m: string) => ReturnType)( + actualProvider, + options.model, + ); + } + + // If only provider specified, use default model for that provider + if (options.provider) { + const actualProvider = PROVIDER_ALIAS[options.provider] ?? options.provider; + const defaultModel = getDefaultModel(options.provider) ?? getDefaultModel(actualProvider); + if (defaultModel) { + return (getModel as (p: string, m: string) => ReturnType)( + actualProvider, + defaultModel, + ); + } + } + + return getModel("kimi-coding", "kimi-k2-thinking"); +} + +// Re-export for convenience +export { isOAuthProvider }; diff --git a/src/agent/runner.ts b/src/agent/runner.ts index e6478849..2234dca3 100644 --- a/src/agent/runner.ts +++ b/src/agent/runner.ts @@ -7,7 +7,13 @@ 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, isOAuthProvider, getLoginInstructions } from "./oauth/providers.js"; +import { + resolveApiKey, + resolveBaseUrl, + resolveModelId, + isOAuthProvider, + getLoginInstructions, +} from "./providers/index.js"; import { checkContextWindow, DEFAULT_CONTEXT_TOKENS, @@ -15,47 +21,6 @@ import { } from "./context-window/index.js"; import { mergeToolsConfig, type ToolsConfig } from "./tools/policy.js"; -/** - * Get API Key based on provider. - * Priority: explicit key > OAuth credentials > credentials.json5 config. - * - * Supports OAuth providers like "claude-code" and "openai-codex" by - * reading credentials from their respective CLI tools. - */ -function resolveApiKey(provider: string, explicitKey?: string): string | undefined { - if (explicitKey) return explicitKey; - - // Try OAuth providers first (claude-code, openai-codex) - const providerConfig = resolveProviderConfig(provider); - if (providerConfig?.apiKey) { - return providerConfig.apiKey; - } - if (providerConfig?.accessToken) { - return providerConfig.accessToken; - } - - // Fall back to credentials.json5 - return credentialManager.getLlmProviderConfig(provider)?.apiKey; -} - -/** - * Get Base URL based on provider. - * Priority: explicit URL > provider-specific env var > generic env var format. - */ -function resolveBaseUrl(provider: string, explicitUrl?: string): string | undefined { - if (explicitUrl) return explicitUrl; - return credentialManager.getLlmProviderConfig(provider)?.baseUrl; -} - -/** - * Get Model ID based on provider. - * Priority: explicit model > provider-specific env var > generic env var format. - */ -function resolveModelId(provider: string, explicitModel?: string): string | undefined { - if (explicitModel) return explicitModel; - return credentialManager.getLlmProviderConfig(provider)?.model; -} - export class Agent { private readonly agent: PiAgentCore; private readonly output; diff --git a/src/agent/tools.ts b/src/agent/tools.ts index 8cec06b3..a7c150bd 100644 --- a/src/agent/tools.ts +++ b/src/agent/tools.ts @@ -1,5 +1,4 @@ import type { AgentOptions } from "./types.js"; -import { getModel } from "@mariozechner/pi-ai"; import { createCodingTools } from "@mariozechner/pi-coding-agent"; import type { AgentTool } from "@mariozechner/pi-agent-core"; import { createExecTool } from "./tools/exec.js"; @@ -9,62 +8,16 @@ import { createWebFetchTool, createWebSearchTool } from "./tools/web/index.js"; import { createMemoryTools } from "./tools/memory/index.js"; import { filterTools } from "./tools/policy.js"; -/** - * Provider alias mapping for OAuth providers. - * Maps friendly names to actual pi-ai provider names. - */ -const PROVIDER_ALIAS: Record = { - "claude-code": "anthropic", // Claude Code OAuth uses anthropic API -}; - -/** - * Default models for each provider. - */ -const DEFAULT_MODELS: Record = { - "anthropic": "claude-sonnet-4-20250514", - "claude-code": "claude-sonnet-4-20250514", - "openai": "gpt-4o", - "openai-codex": "gpt-5.1", - "kimi-coding": "kimi-k2-thinking", - "google": "gemini-2.0-flash", - "groq": "llama-3.3-70b-versatile", - "mistral": "mistral-large-latest", -}; - -export function resolveModel(options: AgentOptions) { - if (options.provider && options.model) { - // Map provider alias (e.g., claude-code -> anthropic) - const actualProvider = PROVIDER_ALIAS[options.provider] ?? options.provider; - - // Type assertion needed because provider/model come from dynamic user config - return (getModel as (p: string, m: string) => ReturnType)( - actualProvider, - options.model, - ); - } - - // If only provider specified, use default model for that provider - if (options.provider) { - const actualProvider = PROVIDER_ALIAS[options.provider] ?? options.provider; - const defaultModel = DEFAULT_MODELS[options.provider] ?? DEFAULT_MODELS[actualProvider]; - if (defaultModel) { - return (getModel as (p: string, m: string) => ReturnType)( - actualProvider, - defaultModel, - ); - } - } - - return getModel("kimi-coding", "kimi-k2-thinking"); -} +// Re-export resolveModel from providers for backwards compatibility +export { resolveModel } from "./providers/index.js"; /** Options for creating tools */ export interface CreateToolsOptions { cwd: string; /** Profile ID for memory tools (optional) */ - profileId?: string; + profileId?: string | undefined; /** Base directory for profiles (optional) */ - profileBaseDir?: string; + profileBaseDir?: string | undefined; } /**