| 1 | //! Route resolution errors (#3384). |
| 2 | //! |
| 3 | //! `thiserror` is not a dependency of this crate, so [`std::fmt::Display`] and |
| 4 | //! [`std::error::Error`] are hand-implemented. No new dependency is added. |
| 5 | |
| 6 | use std::fmt; |
| 7 | |
| 8 | use super::ids::ProviderId; |
| 9 | |
| 10 | /// Why a [`super::resolver::RouteResolver`] could not produce a candidate. |
| 11 | #[derive(Debug, Clone)] |
| 12 | pub enum RouteError { |
| 13 | /// The requested model selector was empty. |
| 14 | EmptyModel, |
| 15 | /// The named provider could not be resolved. |
| 16 | InvalidProvider(String), |
| 17 | /// A model matched multiple providers; the caller must disambiguate. |
| 18 | AmbiguousModel(Vec<ProviderId>), |
| 19 | /// A clearly-foreign model was requested for a strict direct provider. |
| 20 | ForeignModelForDirectProvider { |
| 21 | /// The strict direct provider that rejected the model. |
| 22 | provider: ProviderId, |
| 23 | /// The foreign model selector that was rejected. |
| 24 | model: String, |
| 25 | }, |
| 26 | /// A model-aware provider did not prove a supported request protocol for |
| 27 | /// the selected model/endpoint. |
| 28 | UnsupportedModelProtocol { |
| 29 | /// Provider whose catalog row was incomplete or unsupported. |
| 30 | provider: ProviderId, |
| 31 | /// Selected provider-owned model id. |
| 32 | model: String, |
| 33 | /// Catalog endpoint key, when one was present. |
| 34 | endpoint_key: String, |
| 35 | }, |
| 36 | } |
| 37 | |
| 38 | impl fmt::Display for RouteError { |
| 39 | fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { |
| 40 | match self { |
| 41 | Self::EmptyModel => write!(f, "model selector was empty"), |
| 42 | Self::InvalidProvider(name) => write!(f, "invalid provider: {name}"), |
| 43 | Self::AmbiguousModel(providers) => { |
| 44 | let names: Vec<&str> = providers.iter().map(ProviderId::as_str).collect(); |
| 45 | write!( |
| 46 | f, |
| 47 | "model matches multiple providers ({}); specify a provider", |
| 48 | names.join(", ") |
| 49 | ) |
| 50 | } |
| 51 | Self::ForeignModelForDirectProvider { provider, model } => write!( |
| 52 | f, |
| 53 | "model {model:?} is not served by direct provider {}", |
| 54 | provider.as_str() |
| 55 | ), |
| 56 | Self::UnsupportedModelProtocol { |
| 57 | provider, |
| 58 | model, |
| 59 | endpoint_key, |
| 60 | } => { |
| 61 | write!( |
| 62 | f, |
| 63 | "model {model:?} on provider {} has unsupported or unproven endpoint {endpoint_key:?}", |
| 64 | provider.as_str() |
| 65 | )?; |
| 66 | // #6705: say which case this is, so a catalog Codewhale has not |
| 67 | // caught up with does not read as a broken install. The |
| 68 | // refresh remedy belongs to callers whose resolver actually |
| 69 | // reads the refreshed catalog, so it is not stated here. |
| 70 | match endpoint_key.as_str() { |
| 71 | "unproven" => write!( |
| 72 | f, |
| 73 | ": no catalog Codewhale has loaded proves this model's wire protocol. \ |
| 74 | Reach it through a `kind = \"openai-compatible\"` provider with an \ |
| 75 | explicit `wire`" |
| 76 | ), |
| 77 | super::OPENCODE_ZEN_DEPRECATED_ENDPOINT_KEY => write!( |
| 78 | f, |
| 79 | ": the provider's catalog marks this model deprecated; choose a current model" |
| 80 | ), |
| 81 | _ => write!( |
| 82 | f, |
| 83 | ": the provider serves this model over a protocol Codewhale does not speak" |
| 84 | ), |
| 85 | } |
| 86 | } |
| 87 | } |
| 88 | } |
| 89 | } |
| 90 | |
| 91 | impl std::error::Error for RouteError {} |
| 92 |