use std::sync::Arc;
use axum::extract::{Query, State};
use axum::Json;
use serde::Deserialize;
use crate::error::AppError;
use crate::server::AppState;
#[derive(Debug, Deserialize, Default)]
pub struct ModelsQuery {
pub provider: Option<String>,
pub q: Option<String>,
}
#[derive(Debug, Deserialize)]
pub struct SetModelRequest {
pub model_id: String,
}
#[derive(Debug, Deserialize)]
pub struct SetApiKeyRequest {
pub provider: String,
pub api_key: String,
}
#[derive(Debug, Deserialize)]
pub struct SetProviderOptionsRequest {
pub options: oxi_sdk::ProviderOptions,
}
#[derive(Debug, Deserialize)]
pub struct ValidateKeyRequest {
pub provider: String,
pub api_key: String,
}
pub type RoutingUpdateRequest = oxios_kernel::RoutingUpdate;
pub(crate) async fn handle_engine_providers(
state: State<Arc<AppState>>,
) -> Result<Json<serde_json::Value>, AppError> {
let providers = state.kernel.engine.providers();
Ok(Json(serde_json::json!({
"providers": providers,
})))
}
pub(crate) async fn handle_engine_models(
state: State<Arc<AppState>>,
Query(params): Query<ModelsQuery>,
) -> Result<Json<serde_json::Value>, AppError> {
let models = match (params.provider.as_deref(), params.q.as_deref()) {
(Some(provider), Some(q)) => state.kernel.engine.models(provider, Some(q)),
(Some(provider), None) => state.kernel.engine.models(provider, None),
(None, Some(q)) => state.kernel.engine.search_models(q),
(None, None) => {
let config = state.config.read();
let provider = oxios_kernel::credential::CredentialStore::provider_from_model(
&config.engine.default_model,
);
match provider {
Some(p) => state.kernel.engine.models(p, None),
None => {
state.kernel.engine.models("anthropic", None)
}
}
}
};
Ok(Json(serde_json::json!({
"models": models,
"count": models.len(),
})))
}
pub(crate) async fn handle_engine_config(
state: State<Arc<AppState>>,
) -> Result<Json<serde_json::Value>, AppError> {
let config = state.kernel.engine.config();
Ok(Json(serde_json::json!(config)))
}
pub(crate) async fn handle_engine_set_model(
state: State<Arc<AppState>>,
Json(body): Json<SetModelRequest>,
) -> Result<Json<serde_json::Value>, AppError> {
if body.model_id.is_empty() {
return Err(AppError::BadRequest("model_id is required".into()));
}
state
.kernel
.engine
.set_model(&body.model_id)
.map_err(|e| AppError::Internal(e.to_string()))?;
{
let mut cfg = state.config.write();
cfg.engine.default_model = body.model_id.clone();
}
tracing::info!(model = %body.model_id, "Engine model updated");
Ok(Json(serde_json::json!({
"ok": true,
"model": body.model_id,
})))
}
pub(crate) async fn handle_engine_set_api_key(
state: State<Arc<AppState>>,
Json(body): Json<SetApiKeyRequest>,
) -> Result<Json<serde_json::Value>, AppError> {
if body.provider.is_empty() {
return Err(AppError::BadRequest("provider is required".into()));
}
if body.api_key.is_empty() {
return Err(AppError::BadRequest("api_key is required".into()));
}
state
.kernel
.engine
.set_api_key(&body.provider, &body.api_key)
.map_err(|e| AppError::Internal(e.to_string()))?;
tracing::info!(provider = %body.provider, "API key set");
Ok(Json(serde_json::json!({
"ok": true,
"provider": body.provider,
})))
}
pub(crate) async fn handle_engine_set_provider_options(
state: State<Arc<AppState>>,
Json(body): Json<SetProviderOptionsRequest>,
) -> Result<Json<serde_json::Value>, AppError> {
state
.kernel
.engine
.set_provider_options(&body.options)
.map_err(|e| AppError::Internal(e.to_string()))?;
Ok(Json(serde_json::json!({
"ok": true,
})))
}
pub(crate) async fn handle_engine_validate_key(
state: State<Arc<AppState>>,
Json(body): Json<ValidateKeyRequest>,
) -> Result<Json<serde_json::Value>, AppError> {
if body.provider.is_empty() {
return Err(AppError::BadRequest("provider is required".into()));
}
let result = state
.kernel
.engine
.validate_key(&body.provider, &body.api_key);
Ok(Json(serde_json::json!(result)))
}
pub(crate) async fn handle_engine_set_routing(
state: State<Arc<AppState>>,
Json(body): Json<RoutingUpdateRequest>,
) -> Result<Json<serde_json::Value>, AppError> {
state
.kernel
.engine
.set_routing(body)
.map_err(|e| AppError::Internal(e.to_string()))?;
tracing::info!("Routing configuration updated via API");
Ok(Json(serde_json::json!({ "ok": true })))
}
pub(crate) async fn handle_engine_routing_stats(
state: State<Arc<AppState>>,
) -> Result<Json<serde_json::Value>, AppError> {
let stats = state.kernel.engine.routing_stats_snapshot();
Ok(Json(serde_json::json!(stats)))
}
#[derive(Debug, Deserialize, Default)]
pub struct FallbacksQuery {
pub limit: Option<usize>,
}
pub(crate) async fn handle_engine_routing_fallbacks(
state: State<Arc<AppState>>,
Query(params): Query<FallbacksQuery>,
) -> Result<Json<serde_json::Value>, AppError> {
let limit = params.limit.unwrap_or(20);
let events = state.kernel.engine.fallback_history(limit);
Ok(Json(serde_json::json!({
"events": events,
"total_count": events.len(),
})))
}