use crate::skins::{OpenAIErrorHandler, SkinErrorHandler};
use crate::{
middleware::RequestHandler,
router::Router,
service::{OmniferenceService, ProviderManager},
};
use std::sync::Arc;
use tokio::sync::RwLock;
use tokio_util::sync::CancellationToken;
#[derive(Clone)]
pub struct SkinContext {
chat_handler: Arc<dyn RequestHandler>,
provider_manager: Arc<RwLock<ProviderManager>>,
cancel_tokens: Arc<CancellationToken>,
error_handler: Arc<dyn SkinErrorHandler + Send + Sync>,
}
impl SkinContext {
pub fn new(router: Router) -> Self {
let router = Arc::new(router);
Self {
chat_handler: router,
provider_manager: Arc::new(RwLock::new(ProviderManager::new())),
cancel_tokens: Arc::new(CancellationToken::new()),
error_handler: Arc::new(OpenAIErrorHandler),
}
}
pub fn with_provider_manager(router: Router, provider_manager: Arc<RwLock<ProviderManager>>, _catalog: Arc<crate::catalog::Catalog>) -> Self {
let router = Arc::new(router);
Self {
chat_handler: router,
provider_manager,
cancel_tokens: Arc::new(CancellationToken::new()),
error_handler: Arc::new(OpenAIErrorHandler),
}
}
pub fn with_error_handler(
router: Router,
provider_manager: Arc<RwLock<ProviderManager>>,
_catalog: Arc<crate::catalog::Catalog>,
error_handler: Arc<dyn SkinErrorHandler + Send + Sync>,
) -> Self {
let router = Arc::new(router);
Self {
chat_handler: router,
provider_manager,
cancel_tokens: Arc::new(CancellationToken::new()),
error_handler,
}
}
pub fn with_service(service: OmniferenceService) -> Self {
Self {
provider_manager: service.provider_manager().clone(),
cancel_tokens: Arc::new(CancellationToken::new()),
error_handler: Arc::new(OpenAIErrorHandler),
chat_handler: Arc::new(service),
}
}
pub async fn execute_chat(&self, request: crate::types::ChatRequestIR) -> Result<crate::middleware::ChatStream, crate::adapter::InferenceError> {
let cancel = self.cancel_tokens.child_token();
let upstream_cancel = cancel.clone();
let inner = self
.chat_handler
.handle(request, cancel)
.await
.map_err(crate::adapter::InferenceError::from_handler_error)?;
Ok(crate::middleware::cancel_on_drop(inner, upstream_cancel))
}
pub async fn list_models(&self) -> Vec<crate::types::DiscoveredModel> {
self.provider_manager.read().await.list_models().into_iter().cloned().collect()
}
pub fn handle_model_not_found(&self, model: &str) -> axum::response::Response {
self.error_handler.handle_model_not_found(model)
}
pub fn handle_inference_error(&self, error: &crate::adapter::InferenceError) -> axum::response::Response {
self.error_handler.handle_inference_error(error)
}
}
pub fn determine_skin_from_path(path: &str) -> Arc<dyn SkinErrorHandler + Send + Sync> {
if path.starts_with("/api/openai/v1/") || path.starts_with("/api/openai-compatible/v1/") {
Arc::new(OpenAIErrorHandler)
} else if path.starts_with("/api/anthropic/v1/") {
Arc::new(OpenAIErrorHandler) } else {
Arc::new(OpenAIErrorHandler)
}
}
impl SkinContext {
pub async fn resolve_model_ref(&self, model: &str) -> Option<crate::types::ModelRef> {
let mgr = self.provider_manager.read().await;
let discovered = mgr.get_model(model).cloned().or_else(|| {
if model.contains('/') {
return None;
}
let mut matches = mgr
.list_models()
.into_iter()
.filter(|candidate| candidate.id.split_once('/').is_some_and(|(_, native_model_id)| native_model_id == model));
let discovered = matches.next()?.clone();
matches.next().is_none().then_some(discovered)
})?;
let provider = mgr
.list_providers()
.into_iter()
.find(|provider| provider.name.eq_ignore_ascii_case(&discovered.provider_name))?;
let provider_prefix = format!("{}/", discovered.provider_name.to_lowercase());
let provider_model_id = discovered.id.strip_prefix(&provider_prefix).unwrap_or(&discovered.id).to_string();
Some(crate::types::ModelRef {
alias: discovered.id.clone(),
provider: provider.clone(),
model_id: provider_model_id,
input_modalities: discovered.input_modalities.clone(),
output_modalities: discovered.output_modalities.clone(),
})
}
}