()
| 67 | return [...this.providers.keys()]; |
| 68 | } |
| 69 | |
| 70 | async initialize(): Promise<void> { |
| 71 | // Register providers |
| 72 | const ollamaCfg = this.config.providers.ollama; |
| 73 | this.providers.set('ollama', new OllamaProvider(ollamaCfg.baseUrl, { |
| 74 | keepAlive: ollamaCfg.keepAlive, |
| 75 | options: ollamaCfg.options, |
| 76 | draftModel: ollamaCfg.draftModel, |
| 77 | numCtxCeiling: ollamaNumCtxCeiling(this.config.hardware), |
| 78 | })); |
| 79 | // Anthropic prompt caching is ON by default — strictly cheaper for multi-iteration agent |
| 80 | // loops (the shared tools+system+history prefix is served at 0.1× instead of full price |
| 81 | // each turn). Opt out with providers.anthropic.useCaching: false. |
| 82 | const anthropicUseCaching = (this.config.providers.anthropic as any)?.useCaching !== false; |
| 83 | this.providers.set('anthropic', new AnthropicProvider( |
| 84 | process.env[this.config.providers.anthropic.apiKeyEnv], |
| 85 | { useCaching: anthropicUseCaching }, |
| 86 | )); |
| 87 | const openaiCfg = this.config.providers.openai; |
| 88 | this.providers.set('openai', new OpenAIProvider({ |
| 89 | apiKey: process.env[openaiCfg.apiKeyEnv], |
| 90 | baseURL: openaiCfg.baseUrl, |
| 91 | extraModels: openaiCfg.extraModels as any, |
| 92 | defaultHeaders: openaiCfg.defaultHeaders, |
| 93 | samplingOptions: openaiCfg.samplingOptions as any, |
| 94 | draftModel: (openaiCfg as any).draftModel, |
| 95 | })); |
| 96 | const deepseekCfg = this.config.providers.deepseek; |
| 97 | this.providers.set('deepseek', new DeepSeekProvider({ |
| 98 | apiKey: process.env[deepseekCfg.apiKeyEnv], |
| 99 | baseURL: deepseekCfg.baseUrl, |
| 100 | extraModels: deepseekCfg.extraModels as any, |
| 101 | defaultHeaders: deepseekCfg.defaultHeaders, |
| 102 | })); |
| 103 | |
| 104 | // User-defined OpenAI-compatible providers (providers.custom[] in config.yaml). |
| 105 | // Lets QodeX talk to ANY gateway that issues an API key — Groq, Gemini's compat |
| 106 | // layer, GitHub Models, Mistral, OpenRouter, a self-host — with no code change. |
| 107 | // Invalid entries are skipped with a warning rather than crashing the CLI. |
| 108 | const { providers: customDefs, errors: customErrors } = |
| 109 | validateCustomProviders((this.config.providers as any).custom); |
| 110 | for (const err of customErrors) { |
| 111 | logger.warn(`Ignoring custom provider: ${err}`); |
| 112 | } |
| 113 | for (const def of customDefs) { |
| 114 | if (this.providers.has(def.name)) { |
| 115 | logger.warn(`Custom provider "${def.name}" collides with an already-registered provider — skipping.`); |
| 116 | continue; |
| 117 | } |
| 118 | const key = process.env[def.apiKeyEnv]; |
| 119 | if (!key) { |
| 120 | logger.info(`Custom provider "${def.name}" configured but ${def.apiKeyEnv} is not set — it will stay inactive until you export it.`); |
| 121 | } |
| 122 | this.providers.set(def.name, new CustomOpenAIProvider(def, key)); |
| 123 | } |
| 124 | |
| 125 | // Discover available models |
| 126 | for (const [name, provider] of this.providers) { |
no test coverage detected