(taskClass: TaskClass, contextTokens: number, options: { explicitModel?: string } = {})
| 275 | return prefix.length > 0 ? prefix : substr; |
| 276 | } |
| 277 | |
| 278 | route(taskClass: TaskClass, contextTokens: number, options: { explicitModel?: string } = {}): RouteDecision { |
| 279 | if (!this.initialized) { |
| 280 | throw new ProviderError('Router not initialized', 'router'); |
| 281 | } |
| 282 | |
| 283 | // Explicit user override |
| 284 | if (options.explicitModel) { |
| 285 | const resolved = this.resolveModel(options.explicitModel); |
| 286 | if (!resolved) { |
| 287 | const candidates = this.matchModelCandidates(options.explicitModel); |
| 288 | const all = this.listAvailableModels(); |
| 289 | let msg = `Model not available: ${options.explicitModel}`; |
| 290 | if (candidates.length > 1) { |
| 291 | msg += `. ${candidates.length} models match — be more specific:\n` + |
| 292 | candidates.map(c => ` ${c.provider.name}/${c.info.id}`).join('\n'); |
| 293 | } else if (all.length > 0) { |
| 294 | msg += `. Available models:\n` + all.map(m => ` ${m.provider}/${m.model}`).join('\n'); |
| 295 | } else { |
| 296 | msg += `. No models are currently available — is a provider running? ` + |
| 297 | `(e.g. \`ollama serve\` then \`ollama pull qwen2.5-coder:32b\`, or set an API key).`; |
| 298 | } |
| 299 | throw new ProviderError(msg, 'router'); |
| 300 | } |
| 301 | return { |
| 302 | provider: resolved.provider, |
| 303 | model: resolved.resolvedId, |
| 304 | modelInfo: resolved.modelInfo, |
| 305 | reason: `Explicit user choice: ${options.explicitModel}`, |
| 306 | }; |
| 307 | } |
| 308 | |
| 309 | // Class-based routing |
| 310 | let candidateId: string; |
| 311 | switch (taskClass) { |
| 312 | case 'planning': |
| 313 | case 'tool-decision': |
| 314 | case 'reflection': |
| 315 | candidateId = this.config.routing[taskClass === 'tool-decision' ? 'toolDecision' : taskClass]; |
| 316 | break; |
| 317 | case 'code-generation': |
| 318 | case 'general': |
| 319 | candidateId = this.config.routing.codeGeneration; |
| 320 | break; |
| 321 | default: |
| 322 | candidateId = this.config.defaults.model; |
| 323 | } |
| 324 | |
| 325 | let resolved = this.resolvePreferringDefaultProvider(candidateId); |
| 326 | |
| 327 | // Fall back to default if class-specific isn't available |
| 328 | if (!resolved) { |
| 329 | resolved = this.resolvePreferringDefaultProvider(this.config.defaults.model); |
| 330 | } |
| 331 | |
| 332 | // Last resort: any model that fits context |
| 333 | if (!resolved) { |
| 334 | for (const { provider, info } of this.modelIndex.values()) { |
no test coverage detected