use async_trait::async_trait;
use thiserror::Error;
use crate::{ModelId, 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(
"Responses state tracking reached its limit of {limit} IDs; no existing records were removed"
)]
ResponseStateLimitExceeded {
limit: usize,
},
#[error("Responses state ID is already recorded for another model; its owner was not changed")]
ResponseStateConflict,
#[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: ModelId,
message: String,
},
#[error("upstream returned HTTP {status}: {body}")]
UpstreamHttp {
status: http::StatusCode,
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",
}
}
}
#[async_trait]
pub trait RoutedLlmClient: Send + Sync {
async fn call(&self, request: Request) -> Result<Response, LlmClientError>;
}