Skip to main content

ai_lib_core/protocol/
manifest.rs

1//! Protocol manifest structure and implementation
2//!
3//! This module contains the main ProtocolManifest structure that represents
4//! a provider's protocol configuration.
5
6use serde::{Deserialize, Serialize};
7use std::collections::HashMap;
8
9use super::config::*;
10use super::error::ProtocolError;
11use super::request::UnifiedRequest;
12
13/// Protocol manifest structure (parsed from YAML)
14///
15/// Required fields per schema: id, protocol_version, endpoint, availability, capabilities
16#[derive(Debug, Clone, Serialize, Deserialize)]
17pub struct ProtocolManifest {
18    #[serde(rename = "$schema", skip_serializing_if = "Option::is_none")]
19    pub schema: Option<String>,
20
21    // Required fields
22    pub id: String,
23    pub protocol_version: String,
24    pub endpoint: EndpointDefinition,
25    #[serde(default, skip_serializing_if = "Option::is_none")]
26    pub availability: Option<AvailabilityConfig>,
27    pub capabilities: Capabilities,
28
29    // Provider metadata (required in manifests)
30    #[serde(skip_serializing_if = "Option::is_none")]
31    pub name: Option<String>,
32    #[serde(skip_serializing_if = "Option::is_none")]
33    pub provider_id: Option<String>,
34    #[serde(skip_serializing_if = "Option::is_none")]
35    pub version: Option<String>,
36    pub status: String,   // stable/beta/deprecated
37    pub category: String, // ai_provider / model_provider / third_party_aggregator
38    pub official_url: String,
39    pub support_contact: String,
40
41    // Auth and configuration
42    #[serde(default, skip_serializing_if = "Option::is_none")]
43    pub auth: Option<AuthConfig>,
44    #[serde(skip_serializing_if = "Option::is_none")]
45    pub payload_format: Option<String>,
46    #[serde(default)]
47    pub parameter_mappings: HashMap<String, String>,
48    #[serde(skip_serializing_if = "Option::is_none")]
49    pub response_format: Option<String>,
50    #[serde(skip_serializing_if = "Option::is_none")]
51    pub response_paths: Option<HashMap<String, String>>,
52
53    // Streaming and features
54    #[serde(skip_serializing_if = "Option::is_none")]
55    pub streaming: Option<StreamingConfig>,
56    #[serde(skip_serializing_if = "Option::is_none")]
57    pub features: Option<FeaturesConfig>,
58
59    // Endpoints and services
60    #[serde(skip_serializing_if = "Option::is_none")]
61    pub endpoints: Option<HashMap<String, EndpointConfig>>,
62    #[serde(skip_serializing_if = "Option::is_none")]
63    pub services: Option<HashMap<String, ServiceConfig>>,
64
65    // API families
66    #[serde(skip_serializing_if = "Option::is_none")]
67    pub api_families: Option<Vec<String>>,
68    #[serde(skip_serializing_if = "Option::is_none")]
69    pub default_api_family: Option<String>,
70
71    // Tooling and termination
72    #[serde(skip_serializing_if = "Option::is_none")]
73    pub termination: Option<TerminationConfig>,
74    #[serde(skip_serializing_if = "Option::is_none")]
75    pub tooling: Option<ToolingConfig>,
76
77    // Error handling and resilience
78    #[serde(skip_serializing_if = "Option::is_none")]
79    pub retry_policy: Option<RetryPolicy>,
80    #[serde(skip_serializing_if = "Option::is_none")]
81    pub error_classification: Option<ErrorClassification>,
82    #[serde(skip_serializing_if = "Option::is_none")]
83    pub rate_limit_headers: Option<RateLimitHeaders>,
84
85    // Experimental features
86    #[serde(skip_serializing_if = "Option::is_none")]
87    pub experimental_features: Option<Vec<String>>,
88    #[serde(skip_serializing_if = "Option::is_none")]
89    pub capability_profile: Option<serde_json::Value>,
90}
91
92impl ProtocolManifest {
93    /// Check if protocol supports a specific capability
94    pub fn supports_capability(&self, capability: &str) -> bool {
95        match capability {
96            "streaming" => self.capabilities.streaming,
97            "tools" => self.capabilities.tools,
98            "vision" => self.capabilities.vision,
99            "agentic" => self.capabilities.agentic,
100            "parallel_tools" => self.capabilities.parallel_tools,
101            "reasoning" => self.capabilities.reasoning,
102            "multimodal" => {
103                self.capabilities.multimodal || self.capabilities.vision || self.capabilities.audio
104            }
105            "audio" => self.capabilities.audio,
106            "structured_output" => self.capabilities.structured_output,
107            "mcp_client" => self.capabilities.mcp_client,
108            _ => false,
109        }
110    }
111
112    /// Get base URL from endpoint definition
113    pub fn get_base_url(&self) -> &str {
114        &self.endpoint.base_url
115    }
116
117    /// Compile unified request to provider-specific format
118    pub fn compile_request(
119        &self,
120        request: &UnifiedRequest,
121    ) -> Result<serde_json::Value, ProtocolError> {
122        use crate::utils::PathMapper;
123
124        let mut provider_request = serde_json::json!({});
125
126        // Model is required for most OpenAI-compatible APIs
127        let model_path = self
128            .parameter_mappings
129            .get("model")
130            .map(|s| s.as_str())
131            .unwrap_or("model");
132        PathMapper::set_path(
133            &mut provider_request,
134            model_path,
135            serde_json::Value::String(request.model.clone()),
136        )
137        .map_err(|e| ProtocolError::ValidationError(format!("Failed to set model: {}", e)))?;
138
139        // Map standard parameters to provider-specific names using PathMapper
140        if let Some(temp) = request.temperature {
141            if let Some(mapped) = self.parameter_mappings.get("temperature") {
142                PathMapper::set_path(
143                    &mut provider_request,
144                    mapped,
145                    serde_json::Value::Number(serde_json::Number::from_f64(temp).ok_or_else(
146                        || ProtocolError::ValidationError("Invalid temperature".to_string()),
147                    )?),
148                )
149                .map_err(|e| {
150                    ProtocolError::ValidationError(format!("Failed to set temperature: {}", e))
151                })?;
152            }
153        }
154
155        if let Some(max) = request.max_tokens {
156            if let Some(mapped) = self.parameter_mappings.get("max_tokens") {
157                PathMapper::set_path(
158                    &mut provider_request,
159                    mapped,
160                    serde_json::Value::Number(max.into()),
161                )
162                .map_err(|e| {
163                    ProtocolError::ValidationError(format!("Failed to set max_tokens: {}", e))
164                })?;
165            }
166        }
167
168        if let Some(mapped) = self.parameter_mappings.get("stream") {
169            PathMapper::set_path(
170                &mut provider_request,
171                mapped,
172                serde_json::Value::Bool(request.stream),
173            )
174            .map_err(|e| ProtocolError::ValidationError(format!("Failed to set stream: {}", e)))?;
175        }
176
177        // Map messages (format depends on payload_format)
178        let messages_path = self
179            .parameter_mappings
180            .get("messages")
181            .map(|s| s.as_str())
182            .unwrap_or("messages");
183        let messages: Vec<serde_json::Value> = request
184            .messages
185            .iter()
186            .map(|m| serde_json::to_value(m).unwrap())
187            .collect();
188        PathMapper::set_path(
189            &mut provider_request,
190            messages_path,
191            serde_json::Value::Array(messages),
192        )
193        .map_err(|e| ProtocolError::ValidationError(format!("Failed to set messages: {}", e)))?;
194
195        // Map tools if present
196        if let Some(tools) = &request.tools {
197            if let Some(mapped) = self.parameter_mappings.get("tools") {
198                let tools_value: Vec<serde_json::Value> = tools
199                    .iter()
200                    .map(|t| serde_json::to_value(t).unwrap())
201                    .collect();
202                PathMapper::set_path(
203                    &mut provider_request,
204                    mapped,
205                    serde_json::Value::Array(tools_value),
206                )
207                .map_err(|e| {
208                    ProtocolError::ValidationError(format!("Failed to set tools: {}", e))
209                })?;
210            }
211        }
212
213        // Map tool_choice if present
214        if let Some(tool_choice) = &request.tool_choice {
215            if let Some(mapped) = self.parameter_mappings.get("tool_choice") {
216                PathMapper::set_path(&mut provider_request, mapped, tool_choice.clone()).map_err(
217                    |e| ProtocolError::ValidationError(format!("Failed to set tool_choice: {}", e)),
218                )?;
219            }
220        }
221
222        if let Some(fmt) = &request.response_format {
223            let patch = fmt.to_openai_format();
224            if let serde_json::Value::Object(extra) = patch {
225                for (k, v) in extra {
226                    provider_request[k] = v;
227                }
228            }
229        }
230
231        Ok(provider_request)
232    }
233}