use std::sync::Arc;
use async_trait::async_trait;
use thiserror::Error;
use crate::{Context, Request, Response};
pub type BoxError = Box<dyn std::error::Error + Send + Sync + 'static>;
#[non_exhaustive]
#[derive(Debug, Error)]
pub enum LlmClientError {
#[error("invalid request: {message}")]
InvalidRequest {
message: String,
},
#[error("request translation failed: {0}")]
RequestTranslation(String),
#[error("outbound request encoding failed: {0}")]
RequestEncoding(String),
#[error("response translation failed: {0}")]
ResponseTranslation(String),
#[error("client configuration error: {message}")]
Configuration {
message: String,
},
#[error("upstream transport error: {source}")]
Transport {
#[source]
source: BoxError,
},
#[error("upstream request timed out: {source}")]
Timeout {
#[source]
source: BoxError,
},
#[error("context window exceeded for model {model}: {message}")]
ContextWindowExceeded {
model: String,
message: String,
},
#[error("upstream returned HTTP {status}: {body}")]
UpstreamHttp {
status: u16,
body: String,
},
#[error("invalid upstream response: {source}")]
InvalidResponse {
#[source]
source: BoxError,
},
#[error("foreign function interface error: {source}")]
Ffi {
#[source]
source: BoxError,
},
#[error("{0}")]
General(String),
}
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub enum RoutingFallbackReason {
ContextWindow,
Unavailable,
}
impl RoutingFallbackReason {
pub const fn as_str(self) -> &'static str {
match self {
Self::ContextWindow => "context_window",
Self::Unavailable => "unavailable",
}
}
}
pub trait Decision: Send + Sync {
fn selected_model(&self) -> &str;
fn routing_tier(&self) -> Option<&str> {
None
}
fn is_routed_call(&self) -> bool {
true
}
fn fallback_reason(&self) -> Option<RoutingFallbackReason> {
None
}
fn reasoning(&self) -> Option<&str>;
fn as_any(&self) -> &dyn std::any::Any;
}
pub struct SimpleDecision {
pub selected_model: String,
pub reasoning: Option<String>,
}
impl Decision for SimpleDecision {
fn selected_model(&self) -> &str {
&self.selected_model
}
fn reasoning(&self) -> Option<&str> {
self.reasoning.as_deref()
}
fn as_any(&self) -> &dyn std::any::Any {
self
}
}
#[async_trait]
pub trait RoutedLlmClient: Send + Sync {
async fn call(
&self,
ctx: Context,
request: Request,
decision: Arc<dyn Decision>,
) -> Result<Response, LlmClientError>;
}