UNPKG

@vfarcic/dot-ai

Version:

AI-powered development productivity platform that enhances software development workflows through intelligent automation and AI-driven assistance

219 lines (218 loc) 9.77 kB
"use strict"; /** * AI Provider Factory * * Creates AI provider instances based on configuration. * Supports environment-based provider selection and extensible provider architecture. * * Phase 1 Implementation (PRD 73): anthropic, openai, google * Architecture supports future expansion to 19+ Vercel AI SDK providers */ Object.defineProperty(exports, "__esModule", { value: true }); exports.AIProviderFactory = void 0; exports.createAIProvider = createAIProvider; const vercel_provider_1 = require("./providers/vercel-provider"); const host_provider_1 = require("./providers/host-provider"); const noop_provider_1 = require("./providers/noop-provider"); const constants_1 = require("./constants"); const model_config_1 = require("./model-config"); /** * Provider environment variable mappings * Phase 1 (PRD 73): anthropic, openai, google */ const PROVIDER_ENV_KEYS = { anthropic: 'ANTHROPIC_API_KEY', anthropic_opus: 'ANTHROPIC_API_KEY', // Uses same API key as regular Anthropic anthropic_haiku: 'ANTHROPIC_API_KEY', // Uses same API key as regular Anthropic openai: 'OPENAI_API_KEY', google: 'GOOGLE_GENERATIVE_AI_API_KEY', // Standard Vercel AI SDK env var (also checks GOOGLE_API_KEY as fallback) google_flash: 'GOOGLE_GENERATIVE_AI_API_KEY', // PRD #294: Uses same API key as regular Google kimi: 'MOONSHOT_API_KEY', // PRD #353: Moonshot AI Kimi K2.5 alibaba: 'ALIBABA_API_KEY', // PRD #382: Alibaba Qwen 3.5 Plus xai: 'XAI_API_KEY', custom: 'CUSTOM_LLM_API_KEY', // PRD #194 / Issue #474: Explicit opt-in to custom OpenAI-compatible endpoint copilot: 'GITHUB_COPILOT_TOKEN', // PRD #587: GitHub Copilot provider }; const IMPLEMENTED_PROVIDERS = Object.keys(model_config_1.CURRENT_MODELS); /** * Factory for creating AI provider instances * * Usage: * ```typescript * // Explicit provider selection * const provider = AIProviderFactory.create({ * provider: 'anthropic', * apiKey: process.env.ANTHROPIC_API_KEY * }); * * // Auto-detect from environment * const provider = AIProviderFactory.createFromEnv(); * ``` */ class AIProviderFactory { /** * Create an AI provider instance with explicit configuration * * @param config Provider configuration * @returns Configured AI provider instance * @throws Error if provider type is unsupported or configuration is invalid */ static create(config) { if (config.provider === model_config_1.CURRENT_MODELS.host) { return new host_provider_1.HostProvider(); } // Validate configuration if (!config.apiKey) { throw new Error(constants_1.AI_SERVICE_ERROR_TEMPLATES.API_KEY_REQUIRED(config.provider)); } if (!config.provider) { throw new Error('Provider type must be specified'); } // Check if provider is implemented in Phase 1 if (!IMPLEMENTED_PROVIDERS.includes(config.provider)) { throw new Error(`Provider '${config.provider}' is not yet implemented. ` + `Phase 1 providers: ${IMPLEMENTED_PROVIDERS.join(', ')}. ` + `Future phases will add support for additional Vercel AI SDK providers.`); } // All providers use VercelProvider (PRD #238: consolidated on Vercel AI SDK) return new vercel_provider_1.VercelProvider(config); } /** * Create provider from environment variables * * Detects provider from AI_PROVIDER env var (defaults to 'anthropic') * and loads corresponding API key from environment. * * If no API keys are configured, returns a NoOpAIProvider that allows * the MCP server to start but returns helpful errors when AI is needed. * * @returns Configured AI provider instance or NoOpProvider if no keys available */ static createFromEnv() { const providerType = process.env.AI_PROVIDER || 'anthropic'; // Validate provider is implemented if (!IMPLEMENTED_PROVIDERS.includes(providerType)) { // Write to stderr for logging process.stderr.write(`WARNING: Invalid AI_PROVIDER: ${providerType}. ` + `Must be one of: ${IMPLEMENTED_PROVIDERS.join(', ')}. ` + `Falling back to NoOpProvider.\n`); return new noop_provider_1.NoOpAIProvider(); } // Get API key for the provider // PRD #194: Support CUSTOM_LLM_API_KEY for custom LLM endpoints // Priority: 1. CUSTOM_LLM_API_KEY, 2. Provider-specific key (e.g., OPENAI_API_KEY) // PRD #175: Amazon Bedrock uses AWS SDK credential chain, not API keys let apiKey; // Special handling for Amazon Bedrock - AWS SDK handles credentials automatically if (providerType === 'amazon_bedrock') { // Use dummy API key for Bedrock - AWS SDK will handle actual authentication // AWS credentials checked at runtime by AWS SDK (env vars, ~/.aws/credentials, IAM roles) apiKey = 'bedrock-uses-aws-credentials'; } else if (providerType === model_config_1.CURRENT_MODELS.host) { return new host_provider_1.HostProvider(); } else { const apiKeyEnvVar = PROVIDER_ENV_KEYS[providerType]; if (!apiKeyEnvVar) { process.stderr.write(`WARNING: No API key environment variable defined for provider: ${providerType}. ` + `Falling back to NoOpProvider.\n`); return new noop_provider_1.NoOpAIProvider(); } // Check primary env var, with fallback for Google providers (GOOGLE_API_KEY for backward compatibility) let resolvedApiKey = process.env.CUSTOM_LLM_API_KEY || process.env[apiKeyEnvVar]; if (!resolvedApiKey && providerType.startsWith('google')) { resolvedApiKey = process.env.GOOGLE_API_KEY; // Fallback for backward compatibility } if (!resolvedApiKey) { process.stderr.write(`INFO: ${apiKeyEnvVar} not configured. ` + `AI features will be unavailable. ` + `Tools that don't require AI (prompts, project-setup) will still work.\n`); return new noop_provider_1.NoOpAIProvider(); } apiKey = resolvedApiKey; } // Get optional model override const model = process.env.AI_MODEL; // Get debug mode setting const debugMode = process.env.DEBUG_DOT_AI === 'true'; // PRD #194: Get custom endpoint URL for OpenAI-compatible LLM APIs // Use CUSTOM_LLM_BASE_URL for LLM endpoints (separate from OPENAI_BASE_URL used for embeddings) const baseURL = process.env.CUSTOM_LLM_BASE_URL; // PRD #443: Parse custom headers from environment variable let customHeaders; const customHeadersEnv = process.env.CUSTOM_LLM_HEADERS; if (customHeadersEnv) { try { customHeaders = JSON.parse(customHeadersEnv); } catch { process.stderr.write(`WARNING: CUSTOM_LLM_HEADERS is not valid JSON: ${customHeadersEnv}. Custom headers will be ignored.\n`); } } // Determine effective provider type based on endpoint configuration let effectiveProviderType = providerType; // PRD #194: Detect OpenRouter and override provider type // OpenRouter requires dedicated provider for proper tool calling support if (baseURL && baseURL.includes('openrouter.ai')) { effectiveProviderType = 'openrouter'; } else if (baseURL && !process.env.AI_PROVIDER) { // PRD #443: Only force 'custom' provider when AI_PROVIDER is NOT explicitly set. // If AI_PROVIDER is explicitly set (e.g., 'anthropic'), preserve it so that // native provider features (cache control, tool calling format) are retained // when using a custom base URL (e.g., corporate Anthropic proxy). effectiveProviderType = 'custom'; } return this.create({ provider: effectiveProviderType, apiKey, model, debugMode, baseURL, customHeaders, }); } /** * Check if a provider is available (has API key configured) * * @param provider Provider type to check * @returns true if provider has API key configured */ static isProviderAvailable(provider) { const apiKeyEnvVar = PROVIDER_ENV_KEYS[provider]; if (!apiKeyEnvVar) return false; // Check primary env var, with fallback for Google providers const hasKey = !!process.env[apiKeyEnvVar]; if (!hasKey && provider.startsWith('google')) { return !!process.env.GOOGLE_API_KEY; // Fallback for backward compatibility } return hasKey; } /** * Get list of available providers (implemented + have API keys configured) * * @returns Array of available provider types */ static getAvailableProviders() { return IMPLEMENTED_PROVIDERS.filter(provider => this.isProviderAvailable(provider)); } /** * Check if a provider is implemented in current phase * * @param provider Provider type to check * @returns true if provider is implemented */ static isProviderImplemented(provider) { return IMPLEMENTED_PROVIDERS.includes(provider); } } exports.AIProviderFactory = AIProviderFactory; /** * Convenience function to create AI provider from environment * Maintains backward compatibility with existing code */ function createAIProvider() { return AIProviderFactory.createFromEnv(); }