crw_server/routes/
capabilities.rs1use axum::Json;
9use axum::extract::State;
10use serde::Serialize;
11
12use crate::state::AppState;
13
14#[derive(Debug, Serialize)]
15#[serde(rename_all = "camelCase")]
16pub struct Capabilities {
17 pub version: &'static str,
18 pub llm: LlmCapabilities,
19 pub formats: FormatCapabilities,
20 pub search: SearchCapabilities,
21 pub extract: ExtractCapabilities,
22 pub documents: DocumentCapabilities,
23}
24
25#[derive(Debug, Serialize)]
26#[serde(rename_all = "camelCase")]
27pub struct ExtractCapabilities {
28 pub supported: bool,
30 pub max_urls: usize,
32 pub per_field_attribution: bool,
34}
35
36#[derive(Debug, Serialize)]
37#[serde(rename_all = "camelCase")]
38pub struct DocumentCapabilities {
39 pub parsers: Vec<&'static str>,
43 pub file_upload: FileUploadCapabilities,
45}
46
47#[derive(Debug, Serialize)]
48#[serde(rename_all = "camelCase")]
49pub struct FileUploadCapabilities {
50 pub supported: bool,
51 pub endpoint: &'static str,
52 pub max_bytes: usize,
53 pub types: Vec<&'static str>,
54 pub ocr: bool,
56}
57
58#[derive(Debug, Serialize)]
59#[serde(rename_all = "camelCase")]
60pub struct LlmCapabilities {
61 pub providers: Vec<&'static str>,
63 pub supports_base_url: bool,
64 pub server_key_configured: bool,
68 pub max_concurrency: usize,
71 #[serde(skip_serializing_if = "Option::is_none")]
74 pub require_byok_header: Option<String>,
75}
76
77#[derive(Debug, Serialize)]
78#[serde(rename_all = "camelCase")]
79pub struct FormatCapabilities {
80 pub supported: Vec<&'static str>,
81 pub change_tracking_modes: Vec<&'static str>,
85}
86
87#[derive(Debug, Serialize)]
88#[serde(rename_all = "camelCase")]
89pub struct SearchCapabilities {
90 pub answer: bool,
91 pub summarize_results: bool,
92}
93
94pub async fn capabilities(State(state): State<AppState>) -> Json<Capabilities> {
95 let llm_cfg = state.config.extraction.llm.as_ref();
96 Json(Capabilities {
97 version: env!("CARGO_PKG_VERSION"),
98 llm: LlmCapabilities {
99 providers: vec![
100 "anthropic",
101 "openai",
102 "deepseek",
103 "openai-compatible",
104 "azure",
105 ],
106 supports_base_url: true,
107 server_key_configured: llm_cfg.map(|c| !c.api_key.is_empty()).unwrap_or(false),
108 max_concurrency: llm_cfg.map(|c| c.max_concurrency).unwrap_or(0),
109 require_byok_header: llm_cfg.and_then(|c| c.require_byok_header.clone()),
110 },
111 formats: FormatCapabilities {
112 supported: vec![
113 "markdown",
114 "html",
115 "rawHtml",
116 "plainText",
117 "links",
118 "json",
119 "summary",
120 "changeTracking",
121 ],
122 change_tracking_modes: vec!["gitDiff", "json"],
123 },
124 search: SearchCapabilities {
125 answer: true,
126 summarize_results: true,
127 },
128 extract: ExtractCapabilities {
129 supported: true,
130 max_urls: state.config.crawler.max_extract_urls,
131 per_field_attribution: false,
132 },
133 documents: {
134 let pdf_on = crw_extract::pdf::PDF_SUPPORTED && state.config.document.enabled;
135 DocumentCapabilities {
136 parsers: if pdf_on { vec!["pdf"] } else { vec![] },
137 file_upload: FileUploadCapabilities {
138 supported: pdf_on,
139 endpoint: "/v2/parse",
140 max_bytes: state.config.document.max_upload_bytes,
141 types: if pdf_on {
142 vec!["application/pdf"]
143 } else {
144 vec![]
145 },
146 ocr: false,
147 },
148 }
149 },
150 })
151}