diff --git a/src/pipeline.rs b/src/pipeline.rs index 741a0c0..e979372 100644 --- a/src/pipeline.rs +++ b/src/pipeline.rs @@ -313,6 +313,42 @@ impl Pipeline { } } } + // Capability widening: a tier chain built of tools-less local models + // must not kill an agent request. When the filtered chain is empty on + // an AUTO route, widen to any pool model that satisfies the required + // capabilities — preferring the routing table's own medium→high order + // (an agentic request is worth a capable model), then the rest of the + // pool. Coding agents (OpenCode, Cline, …) always send tools; without + // this every low-tier verdict was a hard failure. + if final_candidates.is_empty() { + let mut widened: Vec = Vec::new(); + for tier in ["medium", "high"] { + for id in live.routing.candidates(tier) { + if !widened.contains(&id) { + widened.push(id); + } + } + } + for id in live.registry.ids() { + if !widened.contains(id) { + widened.push(id.clone()); + } + } + for id in widened { + if let Some(prov) = live.registry.get(&id) { + if needs_tools && !prov.caps().tools { + continue; + } + final_candidates.push(id); + } + } + if !final_candidates.is_empty() { + tracing::info!( + chosen = %final_candidates[0], + "capability widening: tier chain had no model with the required capabilities" + ); + } + } if final_candidates.is_empty() { return Err(GatewayError::UpstreamUnavailable( "No healthy model available that satisfies required capabilities (e.g. tools)"