use axum::Json;
use axum::extract::State;
use serde::Serialize;
use crate::state::AppState;
#[derive(Debug, Serialize)]
#[serde(rename_all = "camelCase")]
pub struct Capabilities {
pub version: &'static str,
pub llm: LlmCapabilities,
pub formats: FormatCapabilities,
pub search: SearchCapabilities,
}
#[derive(Debug, Serialize)]
#[serde(rename_all = "camelCase")]
pub struct LlmCapabilities {
pub providers: Vec<&'static str>,
pub supports_base_url: bool,
pub server_key_configured: bool,
pub max_concurrency: usize,
#[serde(skip_serializing_if = "Option::is_none")]
pub require_byok_header: Option<String>,
}
#[derive(Debug, Serialize)]
#[serde(rename_all = "camelCase")]
pub struct FormatCapabilities {
pub supported: Vec<&'static str>,
pub change_tracking_modes: Vec<&'static str>,
}
#[derive(Debug, Serialize)]
#[serde(rename_all = "camelCase")]
pub struct SearchCapabilities {
pub answer: bool,
pub summarize_results: bool,
}
pub async fn capabilities(State(state): State<AppState>) -> Json<Capabilities> {
let llm_cfg = state.config.extraction.llm.as_ref();
Json(Capabilities {
version: env!("CARGO_PKG_VERSION"),
llm: LlmCapabilities {
providers: vec![
"anthropic",
"openai",
"deepseek",
"openai-compatible",
"azure",
],
supports_base_url: true,
server_key_configured: llm_cfg.map(|c| !c.api_key.is_empty()).unwrap_or(false),
max_concurrency: llm_cfg.map(|c| c.max_concurrency).unwrap_or(0),
require_byok_header: llm_cfg.and_then(|c| c.require_byok_header.clone()),
},
formats: FormatCapabilities {
supported: vec![
"markdown",
"html",
"rawHtml",
"plainText",
"links",
"json",
"summary",
"changeTracking",
],
change_tracking_modes: vec!["gitDiff", "json"],
},
search: SearchCapabilities {
answer: true,
summarize_results: true,
},
})
}