1use serde::{Deserialize, Serialize};
6use std::collections::HashMap;
7
8#[derive(Debug, Clone, Serialize, Deserialize)]
10pub struct EndpointDefinition {
11 pub base_url: String,
12 #[serde(skip_serializing_if = "Option::is_none")]
13 pub protocol: Option<String>, #[serde(skip_serializing_if = "Option::is_none")]
15 pub timeout_ms: Option<u32>,
16}
17
18#[derive(Debug, Clone, Serialize)]
20pub struct EndpointConfig {
21 pub path: String,
22 pub method: String,
23 #[serde(skip_serializing_if = "Option::is_none")]
24 pub adapter: Option<String>,
25}
26
27impl<'de> Deserialize<'de> for EndpointConfig {
28 fn deserialize<D>(deserializer: D) -> std::result::Result<Self, D::Error>
29 where
30 D: serde::Deserializer<'de>,
31 {
32 #[derive(Deserialize)]
33 #[serde(untagged)]
34 enum Input {
35 Path(String),
37 Obj {
39 path: String,
40 #[serde(default = "default_method")]
41 method: String,
42 #[serde(default)]
43 adapter: Option<String>,
44 },
45 }
46
47 match Input::deserialize(deserializer)? {
48 Input::Path(path) => Ok(EndpointConfig {
49 path,
50 method: default_method(),
51 adapter: None,
52 }),
53 Input::Obj {
54 path,
55 method,
56 adapter,
57 } => Ok(EndpointConfig {
58 path,
59 method,
60 adapter,
61 }),
62 }
63 }
64}
65
66fn default_method() -> String {
67 "POST".to_string()
68}
69
70#[derive(Debug, Clone, Serialize, Deserialize)]
72pub struct ServiceConfig {
73 pub path: String,
74 #[serde(default = "default_method_get")]
75 pub method: String,
76 #[serde(skip_serializing_if = "Option::is_none")]
77 pub headers: Option<HashMap<String, String>>,
78 #[serde(skip_serializing_if = "Option::is_none")]
79 pub query_params: Option<HashMap<String, String>>,
80 #[serde(skip_serializing_if = "Option::is_none")]
81 pub response_binding: Option<String>,
82}
83
84fn default_method_get() -> String {
85 "GET".to_string()
86}
87
88#[derive(Debug, Clone, Serialize)]
90pub struct Capabilities {
91 pub streaming: bool,
92 pub tools: bool,
93 pub vision: bool,
94 #[serde(default, skip_serializing_if = "is_false")]
95 pub agentic: bool,
96 #[serde(default, skip_serializing_if = "is_false")]
97 pub parallel_tools: bool,
98 #[serde(default, skip_serializing_if = "is_false")]
99 pub reasoning: bool,
100 #[serde(default, skip_serializing_if = "is_false")]
101 pub multimodal: bool,
102 #[serde(default, skip_serializing_if = "is_false")]
103 pub audio: bool,
104 #[serde(default, skip_serializing_if = "is_false")]
106 pub structured_output: bool,
107 #[serde(default, skip_serializing_if = "is_false")]
109 pub mcp_client: bool,
110}
111
112impl<'de> Deserialize<'de> for Capabilities {
113 fn deserialize<D>(deserializer: D) -> std::result::Result<Self, D::Error>
114 where
115 D: serde::Deserializer<'de>,
116 {
117 #[derive(Deserialize)]
118 struct LegacyCaps {
119 streaming: bool,
120 tools: bool,
121 vision: bool,
122 #[serde(default)]
123 agentic: bool,
124 #[serde(default)]
125 parallel_tools: bool,
126 #[serde(default)]
127 reasoning: bool,
128 #[serde(default)]
129 multimodal: bool,
130 #[serde(default)]
131 audio: bool,
132 }
133
134 #[derive(Deserialize, Default)]
135 struct FeatureFlags {
136 #[serde(default)]
137 parallel_tool_calls: bool,
138 #[serde(default)]
139 extended_thinking: bool,
140 #[serde(default)]
141 structured_output: bool,
142 }
143
144 #[derive(Deserialize)]
145 struct V2Caps {
146 required: Vec<String>,
147 #[serde(default)]
148 optional: Vec<String>,
149 #[serde(default)]
150 feature_flags: Option<FeatureFlags>,
151 }
152
153 #[derive(Deserialize)]
154 #[serde(untagged)]
155 enum Input {
156 TagList(Vec<String>),
158 Legacy(LegacyCaps),
159 V2(V2Caps),
160 }
161
162 fn from_capability_tags(tags: &[String]) -> Capabilities {
163 let mut c = Capabilities {
164 streaming: false,
165 tools: false,
166 vision: false,
167 agentic: false,
168 parallel_tools: false,
169 reasoning: false,
170 multimodal: false,
171 audio: false,
172 structured_output: false,
173 mcp_client: false,
174 };
175 for t in tags {
176 match t.as_str() {
177 "chat" | "text" => {}
178 "streaming" => c.streaming = true,
179 "tools" => c.tools = true,
180 "vision" => {
181 c.vision = true;
182 c.multimodal = true;
183 }
184 "audio" => {
185 c.audio = true;
186 c.multimodal = true;
187 }
188 "video" => c.multimodal = true,
189 "agentic" => c.agentic = true,
190 "parallel_tools" => c.parallel_tools = true,
191 "reasoning" => c.reasoning = true,
192 "structured_output" => c.structured_output = true,
193 "mcp_client" => c.mcp_client = true,
194 _ => {}
195 }
196 }
197 c
198 }
199
200 match Input::deserialize(deserializer)? {
201 Input::TagList(tags) => Ok(from_capability_tags(&tags)),
202 Input::Legacy(v) => Ok(Capabilities {
203 streaming: v.streaming,
204 tools: v.tools,
205 vision: v.vision,
206 agentic: v.agentic,
207 parallel_tools: v.parallel_tools,
208 reasoning: v.reasoning,
209 multimodal: v.multimodal,
210 audio: v.audio,
211 structured_output: false,
212 mcp_client: false,
213 }),
214 Input::V2(v) => {
215 let has = |name: &str| {
216 v.required.iter().any(|c| c == name) || v.optional.iter().any(|c| c == name)
217 };
218 let flags = v.feature_flags.unwrap_or_default();
219 Ok(Capabilities {
220 streaming: has("streaming"),
221 tools: has("tools"),
222 vision: has("vision"),
223 agentic: has("agentic"),
224 parallel_tools: has("parallel_tools") || flags.parallel_tool_calls,
225 reasoning: has("reasoning") || flags.extended_thinking,
226 multimodal: has("vision") || has("audio") || has("video"),
227 audio: has("audio"),
228 structured_output: has("structured_output") || flags.structured_output,
229 mcp_client: has("mcp_client"),
230 })
231 }
232 }
233 }
234}
235
236fn is_false(b: &bool) -> bool {
237 !*b
238}
239
240#[derive(Debug, Clone, Serialize, Deserialize)]
242pub struct AuthConfig {
243 #[serde(rename = "type")]
244 pub auth_type: String,
245 #[serde(default, skip_serializing_if = "Option::is_none")]
246 pub token_env: Option<String>,
247 #[serde(default, skip_serializing_if = "Option::is_none")]
248 pub key_env: Option<String>,
249 #[serde(default, skip_serializing_if = "Option::is_none")]
250 pub param_name: Option<String>,
251 #[serde(default, skip_serializing_if = "Option::is_none")]
252 pub header_name: Option<String>,
253 #[serde(default, skip_serializing_if = "Option::is_none")]
254 pub extra_headers: Option<Vec<HeaderConfig>>,
255}
256
257#[derive(Debug, Clone, Serialize, Deserialize)]
259pub struct HeaderConfig {
260 pub name: String,
261 pub value: String,
262}
263
264#[derive(Debug, Clone, Serialize, Deserialize)]
266pub struct StreamingConfig {
267 #[serde(skip_serializing_if = "Option::is_none")]
268 pub event_format: Option<String>,
269 #[serde(skip_serializing_if = "Option::is_none")]
270 pub decoder: Option<DecoderConfig>,
271 #[serde(skip_serializing_if = "Option::is_none")]
272 pub frame_selector: Option<String>,
273 #[serde(default, skip_serializing_if = "Option::is_none")]
275 pub content_path: Option<String>,
276 #[serde(default, skip_serializing_if = "Option::is_none")]
278 pub tool_call_path: Option<String>,
279 #[serde(default, skip_serializing_if = "Option::is_none")]
281 pub usage_path: Option<String>,
282 #[serde(skip_serializing_if = "Option::is_none")]
283 pub candidate: Option<CandidateConfig>,
284 #[serde(skip_serializing_if = "Option::is_none")]
285 pub accumulator: Option<AccumulatorConfig>,
286 #[serde(default)]
287 pub event_map: Vec<EventMapRule>,
288 #[serde(skip_serializing_if = "Option::is_none")]
289 pub stop_condition: Option<String>,
290}
291
292#[derive(Debug, Clone, Serialize, Deserialize)]
294pub struct DecoderConfig {
295 pub format: String,
296 #[serde(skip_serializing_if = "Option::is_none")]
297 pub strategy: Option<String>,
298 #[serde(skip_serializing_if = "Option::is_none")]
299 pub delimiter: Option<String>,
300 #[serde(skip_serializing_if = "Option::is_none")]
301 pub prefix: Option<String>,
302 #[serde(skip_serializing_if = "Option::is_none")]
303 pub done_signal: Option<String>,
304}
305
306#[derive(Debug, Clone, Serialize, Deserialize)]
308pub struct CandidateConfig {
309 #[serde(skip_serializing_if = "Option::is_none")]
310 pub candidate_id_path: Option<String>,
311 #[serde(skip_serializing_if = "Option::is_none")]
312 pub fan_out: Option<bool>,
313}
314
315#[derive(Debug, Clone, Serialize, Deserialize)]
317pub struct AccumulatorConfig {
318 #[serde(default)]
319 pub stateful_tool_parsing: bool,
320 #[serde(skip_serializing_if = "Option::is_none")]
321 pub key_path: Option<String>,
322 #[serde(skip_serializing_if = "Option::is_none")]
323 pub flush_on: Option<String>,
324}
325
326#[derive(Debug, Clone, Serialize, Deserialize)]
328pub struct EventMapRule {
329 #[serde(rename = "match")]
330 pub match_expr: String,
331 pub emit: String,
332 #[serde(default, skip_serializing_if = "Option::is_none")]
333 pub fields: Option<HashMap<String, String>>,
334}
335
336#[derive(Debug, Clone, Serialize, Deserialize)]
338pub struct FeaturesConfig {
339 #[serde(default, skip_serializing_if = "Option::is_none")]
340 pub multi_candidate: Option<MultiCandidateConfig>,
341 #[serde(default, skip_serializing_if = "Option::is_none")]
342 pub response_mapping: Option<ResponseMappingConfig>,
343}
344
345#[derive(Debug, Clone, Serialize, Deserialize)]
347pub struct MultiCandidateConfig {
348 pub support_type: String,
349 #[serde(default, skip_serializing_if = "Option::is_none")]
350 pub param_name: Option<String>,
351 #[serde(default, skip_serializing_if = "Option::is_none")]
352 pub max_concurrent: Option<u32>,
353}
354
355#[derive(Debug, Clone, Serialize, Deserialize)]
357pub struct ResponseMappingConfig {
358 #[serde(default, skip_serializing_if = "Option::is_none")]
359 pub tool_calls: Option<ToolCallsMapping>,
360 #[serde(default, skip_serializing_if = "Option::is_none")]
361 pub error: Option<ErrorMapping>,
362}
363
364#[derive(Debug, Clone, Serialize, Deserialize)]
366pub struct ToolCallsMapping {
367 pub path: String,
368 #[serde(default, skip_serializing_if = "Option::is_none")]
369 pub filter: Option<String>,
370 pub fields: HashMap<String, String>,
371 #[serde(default, skip_serializing_if = "Option::is_none")]
372 pub array_fan_out: Option<bool>,
373}
374
375#[derive(Debug, Clone, Serialize, Deserialize)]
377pub struct ErrorMapping {
378 #[serde(default, skip_serializing_if = "Option::is_none")]
379 pub message_path: Option<String>,
380 #[serde(default, skip_serializing_if = "Option::is_none")]
381 pub code_path: Option<String>,
382 #[serde(default, skip_serializing_if = "Option::is_none")]
383 pub type_path: Option<String>,
384}
385
386#[derive(Debug, Clone, Serialize, Deserialize)]
388pub struct TerminationConfig {
389 pub source_field: String,
390 #[serde(default, skip_serializing_if = "Option::is_none")]
391 pub mapping: Option<HashMap<String, String>>,
392}
393
394#[derive(Debug, Clone, Serialize, Deserialize)]
396pub struct ToolingConfig {
397 pub source_model: String,
398 #[serde(default, skip_serializing_if = "Option::is_none")]
399 pub tool_use: Option<ToolUseMapping>,
400 #[serde(default, skip_serializing_if = "Option::is_none")]
401 pub tool_result: Option<ToolResultMapping>,
402}
403
404#[derive(Debug, Clone, Serialize, Deserialize)]
406pub struct ToolUseMapping {
407 #[serde(default, skip_serializing_if = "Option::is_none")]
408 pub id_path: Option<String>,
409 #[serde(default, skip_serializing_if = "Option::is_none")]
410 pub name_path: Option<String>,
411 #[serde(default, skip_serializing_if = "Option::is_none")]
412 pub input_path: Option<String>,
413 #[serde(default, skip_serializing_if = "Option::is_none")]
414 pub input_format: Option<String>,
415}
416
417#[derive(Debug, Clone, Serialize, Deserialize)]
419pub struct ToolResultMapping {
420 #[serde(default, skip_serializing_if = "Option::is_none")]
421 pub id_path: Option<String>,
422 #[serde(default, skip_serializing_if = "Option::is_none")]
423 pub name_path: Option<String>,
424 #[serde(default, skip_serializing_if = "Option::is_none")]
425 pub response_path: Option<String>,
426}
427
428#[derive(Debug, Clone, Serialize, Deserialize)]
430pub struct RetryPolicy {
431 pub strategy: String,
432 #[serde(default, skip_serializing_if = "Option::is_none")]
433 pub max_retries: Option<u32>,
434 #[serde(default, skip_serializing_if = "Option::is_none")]
435 pub min_delay_ms: Option<u32>,
436 #[serde(default, skip_serializing_if = "Option::is_none")]
437 pub max_delay_ms: Option<u32>,
438 #[serde(default, skip_serializing_if = "Option::is_none")]
439 pub jitter: Option<String>,
440 #[serde(default, skip_serializing_if = "Option::is_none")]
441 pub retry_on_http_status: Option<Vec<u16>>,
442 #[serde(default, skip_serializing_if = "Option::is_none")]
443 pub retry_on_error_status: Option<Vec<String>>,
444}
445
446#[derive(Debug, Clone, Serialize, Deserialize)]
448pub struct ErrorClassification {
449 #[serde(default, skip_serializing_if = "Option::is_none")]
450 pub by_http_status: Option<HashMap<String, String>>,
451 #[serde(default, skip_serializing_if = "Option::is_none")]
452 pub by_error_status: Option<HashMap<String, String>>,
453}
454
455#[derive(Debug, Clone, Serialize, Deserialize)]
458pub struct AvailabilityConfig {
459 pub required: bool,
460 pub regions: Vec<String>, pub check: HealthCheckConfig,
462 #[serde(skip_serializing_if = "Option::is_none")]
463 pub notes: Option<Vec<String>>,
464}
465
466#[derive(Debug, Clone, Serialize, Deserialize)]
469pub struct HealthCheckConfig {
470 pub method: String, pub path: String,
472 pub expected_status: Vec<u16>,
473 #[serde(skip_serializing_if = "Option::is_none")]
474 pub timeout_ms: Option<u32>,
475}
476
477#[derive(Debug, Clone, Serialize, Deserialize)]
479pub struct RateLimitHeaders {
480 #[serde(default, skip_serializing_if = "Option::is_none")]
481 pub requests_limit: Option<String>,
482 #[serde(default, skip_serializing_if = "Option::is_none")]
483 pub requests_remaining: Option<String>,
484 #[serde(default, skip_serializing_if = "Option::is_none")]
485 pub requests_reset: Option<String>,
486 #[serde(default, skip_serializing_if = "Option::is_none")]
487 pub tokens_limit: Option<String>,
488 #[serde(default, skip_serializing_if = "Option::is_none")]
489 pub tokens_remaining: Option<String>,
490 #[serde(default, skip_serializing_if = "Option::is_none")]
491 pub tokens_reset: Option<String>,
492 #[serde(default, skip_serializing_if = "Option::is_none")]
493 pub retry_after: Option<String>,
494}