spec_ai_api/api/
models.rs

1/// API request and response models
2use serde::{Deserialize, Serialize};
3
4/// Request to query the agent
5#[derive(Debug, Clone, Serialize, Deserialize)]
6pub struct QueryRequest {
7    /// The user's message/query
8    pub message: String,
9    /// Optional session ID for conversation continuity
10    pub session_id: Option<String>,
11    /// Optional agent profile to use
12    pub agent: Option<String>,
13    /// Whether to stream the response
14    #[serde(default)]
15    pub stream: bool,
16    /// Optional temperature override
17    pub temperature: Option<f32>,
18    /// Optional max tokens
19    pub max_tokens: Option<usize>,
20}
21
22/// Response from the agent
23#[derive(Debug, Clone, Serialize, Deserialize)]
24pub struct QueryResponse {
25    /// The agent's response message
26    pub response: String,
27    /// Session ID for this conversation
28    pub session_id: String,
29    /// Agent profile used
30    pub agent: String,
31    /// Tool calls made (if any)
32    #[serde(skip_serializing_if = "Vec::is_empty", default)]
33    pub tool_calls: Vec<ToolCallInfo>,
34    /// Processing metadata
35    pub metadata: ResponseMetadata,
36}
37
38/// Information about a tool call
39#[derive(Debug, Clone, Serialize, Deserialize)]
40pub struct ToolCallInfo {
41    /// Tool name
42    pub name: String,
43    /// Tool arguments
44    pub arguments: serde_json::Value,
45    /// Execution status
46    pub success: bool,
47    /// Tool output (if any)
48    #[serde(skip_serializing_if = "Option::is_none")]
49    pub output: Option<String>,
50    /// Error message if the tool failed
51    #[serde(skip_serializing_if = "Option::is_none")]
52    pub error: Option<String>,
53}
54
55/// Response metadata
56#[derive(Debug, Clone, Serialize, Deserialize)]
57pub struct ResponseMetadata {
58    /// Timestamp of response
59    pub timestamp: String,
60    /// Model used
61    pub model: String,
62    /// Processing time in milliseconds
63    pub processing_time_ms: u64,
64    /// Unique identifier for correlating with telemetry
65    pub run_id: String,
66}
67
68/// Streaming response chunk
69#[derive(Debug, Clone, Serialize, Deserialize)]
70#[serde(tag = "type")]
71pub enum StreamChunk {
72    /// Initial metadata
73    #[serde(rename = "start")]
74    Start { session_id: String, agent: String },
75    /// Content chunk
76    #[serde(rename = "chunk")]
77    Content { text: String },
78    /// Tool call notification
79    #[serde(rename = "tool_call")]
80    ToolCall {
81        name: String,
82        arguments: serde_json::Value,
83    },
84    /// Tool result
85    #[serde(rename = "tool_result")]
86    ToolResult {
87        name: String,
88        result: serde_json::Value,
89    },
90    /// End of stream
91    #[serde(rename = "end")]
92    End { metadata: ResponseMetadata },
93    /// Error occurred
94    #[serde(rename = "error")]
95    Error { message: String },
96}
97
98/// Error response
99#[derive(Debug, Clone, Serialize, Deserialize)]
100pub struct ErrorResponse {
101    /// Error message
102    pub error: String,
103    /// Error code
104    pub code: String,
105    /// Additional details
106    #[serde(skip_serializing_if = "Option::is_none")]
107    pub details: Option<serde_json::Value>,
108}
109
110impl ErrorResponse {
111    pub fn new(code: impl Into<String>, error: impl Into<String>) -> Self {
112        Self {
113            error: error.into(),
114            code: code.into(),
115            details: None,
116        }
117    }
118
119    pub fn with_details(mut self, details: serde_json::Value) -> Self {
120        self.details = Some(details);
121        self
122    }
123}
124
125/// Health check response
126#[derive(Debug, Clone, Serialize, Deserialize)]
127pub struct HealthResponse {
128    /// Service status
129    pub status: String,
130    /// Server version
131    pub version: String,
132    /// Uptime in seconds
133    pub uptime_seconds: u64,
134    /// Active sessions count
135    pub active_sessions: usize,
136}
137
138/// Agent list response
139#[derive(Debug, Clone, Serialize, Deserialize)]
140pub struct AgentListResponse {
141    /// Available agents
142    pub agents: Vec<AgentInfo>,
143}
144
145/// Agent information
146#[derive(Debug, Clone, Serialize, Deserialize)]
147pub struct AgentInfo {
148    /// Agent ID
149    pub id: String,
150    /// Agent description/prompt
151    pub description: String,
152    /// Allowed tools
153    #[serde(skip_serializing_if = "Vec::is_empty", default)]
154    pub allowed_tools: Vec<String>,
155    /// Denied tools
156    #[serde(skip_serializing_if = "Vec::is_empty", default)]
157    pub denied_tools: Vec<String>,
158}
159
160#[cfg(test)]
161mod tests {
162    use super::*;
163
164    #[test]
165    fn test_query_request_serialization() {
166        let req = QueryRequest {
167            message: "Hello".to_string(),
168            session_id: Some("sess123".to_string()),
169            agent: Some("coder".to_string()),
170            stream: false,
171            temperature: Some(0.7),
172            max_tokens: Some(1000),
173        };
174
175        let json = serde_json::to_string(&req).unwrap();
176        let deserialized: QueryRequest = serde_json::from_str(&json).unwrap();
177
178        assert_eq!(deserialized.message, "Hello");
179        assert_eq!(deserialized.session_id, Some("sess123".to_string()));
180    }
181
182    #[test]
183    fn test_query_response_serialization() {
184        let resp = QueryResponse {
185            response: "Hi there".to_string(),
186            session_id: "sess123".to_string(),
187            agent: "coder".to_string(),
188            tool_calls: vec![],
189            metadata: ResponseMetadata {
190                timestamp: "2024-01-01T00:00:00Z".to_string(),
191                model: "mock".to_string(),
192                processing_time_ms: 100,
193                run_id: "run-1".to_string(),
194            },
195        };
196
197        let json = serde_json::to_string(&resp).unwrap();
198        let deserialized: QueryResponse = serde_json::from_str(&json).unwrap();
199
200        assert_eq!(deserialized.response, "Hi there");
201        assert_eq!(deserialized.session_id, "sess123");
202    }
203
204    #[test]
205    fn test_stream_chunk_variants() {
206        let chunks = vec![
207            StreamChunk::Start {
208                session_id: "sess1".to_string(),
209                agent: "coder".to_string(),
210            },
211            StreamChunk::Content {
212                text: "Hello".to_string(),
213            },
214            StreamChunk::End {
215                metadata: ResponseMetadata {
216                    timestamp: "2024-01-01T00:00:00Z".to_string(),
217                    model: "mock".to_string(),
218                    processing_time_ms: 100,
219                    run_id: "run-1".to_string(),
220                },
221            },
222        ];
223
224        for chunk in chunks {
225            let json = serde_json::to_string(&chunk).unwrap();
226            let _deserialized: StreamChunk = serde_json::from_str(&json).unwrap();
227        }
228    }
229
230    #[test]
231    fn test_error_response() {
232        let err = ErrorResponse::new("invalid_request", "Invalid API key")
233            .with_details(serde_json::json!({"hint": "Check your configuration"}));
234
235        assert_eq!(err.error, "Invalid API key");
236        assert_eq!(err.code, "invalid_request");
237        assert!(err.details.is_some());
238    }
239
240    #[test]
241    fn test_health_response() {
242        let health = HealthResponse {
243            status: "healthy".to_string(),
244            version: "0.1.0".to_string(),
245            uptime_seconds: 3600,
246            active_sessions: 5,
247        };
248
249        let json = serde_json::to_string(&health).unwrap();
250        let deserialized: HealthResponse = serde_json::from_str(&json).unwrap();
251
252        assert_eq!(deserialized.status, "healthy");
253        assert_eq!(deserialized.uptime_seconds, 3600);
254    }
255}