Skip to main content

vtcode_llm/providers/openresponses/
types.rs

1//! OpenResponses specification types.
2//!
3//! This module defines types that conform to the OpenResponses specification.
4//! See <https://www.openresponses.org/specification> for details.
5
6use serde::{Deserialize, Serialize};
7use serde_json::Value;
8
9// ============================================================================
10// Item Types - Core units of context in OpenResponses
11// ============================================================================
12
13/// The type of an item in the OpenResponses API.
14#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
15#[serde(rename_all = "snake_case")]
16pub enum ItemType {
17    /// A message item (user, assistant, system, or developer).
18    Message,
19    /// A function call item.
20    FunctionCall,
21    /// A function call output item.
22    FunctionCallOutput,
23    /// A reasoning item.
24    Reasoning,
25    /// An item reference.
26    ItemReference,
27    /// Catch-all for unknown item types added by the OpenResponses spec.
28    #[serde(other)]
29    Unknown,
30}
31
32/// Role for message items.
33#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
34#[serde(rename_all = "lowercase")]
35pub enum MessageRole {
36    User,
37    Assistant,
38    System,
39    Developer,
40    /// Catch-all for unknown roles added by the OpenResponses spec.
41    #[serde(other)]
42    Unknown,
43}
44
45/// Status for items that have a lifecycle.
46#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
47#[serde(rename_all = "snake_case")]
48pub enum ItemStatus {
49    InProgress,
50    Completed,
51    Failed,
52    /// Catch-all for unknown statuses added by the OpenResponses spec.
53    #[serde(other)]
54    Unknown,
55}
56
57/// Status for function calls.
58#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
59#[serde(rename_all = "snake_case")]
60pub enum FunctionCallStatus {
61    InProgress,
62    Completed,
63    Failed,
64    /// Catch-all for unknown statuses added by the OpenResponses spec.
65    #[serde(other)]
66    Unknown,
67}
68
69// ============================================================================
70// Content Types - Building blocks for message content
71// ============================================================================
72
73/// Input text content for user/system/developer messages.
74#[derive(Debug, Clone, Serialize, Deserialize)]
75pub struct InputTextContent {
76    #[serde(rename = "type")]
77    content_type: String, // "input_text"
78    text: String,
79}
80
81impl InputTextContent {
82    fn new(text: impl Into<String>) -> Self {
83        Self {
84            content_type: "input_text".to_string(),
85            text: text.into(),
86        }
87    }
88}
89
90/// Output text content for assistant messages.
91#[derive(Debug, Clone, Serialize, Deserialize)]
92pub struct OutputTextContent {
93    #[serde(rename = "type")]
94    content_type: String, // "output_text"
95    text: String,
96    #[serde(skip_serializing_if = "Option::is_none")]
97    annotations: Option<Vec<Annotation>>,
98}
99
100impl OutputTextContent {
101    pub fn new(text: impl Into<String>) -> Self {
102        Self {
103            content_type: "output_text".to_string(),
104            text: text.into(),
105            annotations: None,
106        }
107    }
108}
109
110/// Refusal content for when the model refuses a request.
111#[derive(Debug, Clone, Serialize, Deserialize)]
112pub struct RefusalContent {
113    #[serde(rename = "type")]
114    content_type: String, // "refusal"
115    refusal: String,
116}
117
118/// Image detail level.
119#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
120#[serde(rename_all = "lowercase")]
121pub enum ImageDetail {
122    Low,
123    High,
124    Auto,
125    Original,
126    /// Catch-all for unknown detail levels.
127    #[serde(other)]
128    Unknown,
129}
130
131/// Input image content.
132#[derive(Debug, Clone, Serialize, Deserialize)]
133pub struct InputImageContent {
134    #[serde(rename = "type")]
135    content_type: String, // "input_image"
136    #[serde(skip_serializing_if = "Option::is_none")]
137    image_url: Option<String>,
138    #[serde(skip_serializing_if = "Option::is_none")]
139    detail: Option<ImageDetail>,
140}
141
142/// Input file content.
143#[derive(Debug, Clone, Serialize, Deserialize)]
144pub struct InputFileContent {
145    #[serde(rename = "type")]
146    content_type: String, // "input_file"
147    #[serde(skip_serializing_if = "Option::is_none")]
148    filename: Option<String>,
149    #[serde(skip_serializing_if = "Option::is_none")]
150    file_id: Option<String>,
151    #[serde(skip_serializing_if = "Option::is_none")]
152    file_data: Option<String>,
153    #[serde(skip_serializing_if = "Option::is_none")]
154    file_url: Option<String>,
155}
156
157/// Annotation for citations.
158#[derive(Debug, Clone, Serialize, Deserialize)]
159#[serde(tag = "type")]
160pub enum Annotation {
161    #[serde(rename = "url_citation")]
162    UrlCitation {
163        start_index: usize,
164        end_index: usize,
165        url: String,
166        #[serde(skip_serializing_if = "Option::is_none")]
167        title: Option<String>,
168    },
169    /// Catch-all for unknown annotation types added by the OpenResponses spec.
170    #[serde(other)]
171    Unknown,
172}
173
174/// Content part union for input messages.
175#[derive(Debug, Clone, Serialize, Deserialize)]
176#[serde(untagged)]
177pub enum InputContent {
178    Text(InputTextContent),
179    Image(InputImageContent),
180    File(InputFileContent),
181}
182
183/// Content part union for output messages.
184#[derive(Debug, Clone, Serialize, Deserialize)]
185#[serde(untagged)]
186pub enum OutputContent {
187    Text(OutputTextContent),
188    Refusal(RefusalContent),
189}
190
191// ============================================================================
192// Item Params - Items that can be sent as input
193// ============================================================================
194
195/// User message item parameter.
196#[derive(Debug, Clone, Serialize, Deserialize)]
197pub struct UserMessageItemParam {
198    #[serde(rename = "type")]
199    item_type: String, // "message"
200    role: String, // "user"
201    content: Vec<InputContent>,
202    #[serde(skip_serializing_if = "Option::is_none")]
203    id: Option<String>,
204    #[serde(skip_serializing_if = "Option::is_none")]
205    status: Option<String>,
206}
207
208impl UserMessageItemParam {
209    fn new(text: impl Into<String>) -> Self {
210        Self {
211            item_type: "message".to_string(),
212            role: "user".to_string(),
213            content: vec![InputContent::Text(InputTextContent::new(text))],
214            id: None,
215            status: None,
216        }
217    }
218}
219
220/// System message item parameter.
221#[derive(Debug, Clone, Serialize, Deserialize)]
222pub struct SystemMessageItemParam {
223    #[serde(rename = "type")]
224    item_type: String, // "message"
225    role: String, // "system"
226    content: Vec<InputTextContent>,
227    #[serde(skip_serializing_if = "Option::is_none")]
228    id: Option<String>,
229    #[serde(skip_serializing_if = "Option::is_none")]
230    status: Option<String>,
231}
232
233impl SystemMessageItemParam {
234    pub fn new(text: impl Into<String>) -> Self {
235        Self {
236            item_type: "message".to_string(),
237            role: "system".to_string(),
238            content: vec![InputTextContent::new(text)],
239            id: None,
240            status: None,
241        }
242    }
243}
244
245/// Developer message item parameter.
246#[derive(Debug, Clone, Serialize, Deserialize)]
247pub struct DeveloperMessageItemParam {
248    #[serde(rename = "type")]
249    item_type: String, // "message"
250    role: String, // "developer"
251    content: Vec<InputTextContent>,
252    #[serde(skip_serializing_if = "Option::is_none")]
253    id: Option<String>,
254    #[serde(skip_serializing_if = "Option::is_none")]
255    status: Option<String>,
256}
257
258/// Assistant message item parameter.
259#[derive(Debug, Clone, Serialize, Deserialize)]
260pub struct AssistantMessageItemParam {
261    #[serde(rename = "type")]
262    item_type: String, // "message"
263    role: String, // "assistant"
264    content: Vec<OutputContent>,
265    #[serde(skip_serializing_if = "Option::is_none")]
266    id: Option<String>,
267    #[serde(skip_serializing_if = "Option::is_none")]
268    status: Option<String>,
269}
270
271impl AssistantMessageItemParam {
272    pub fn new(text: impl Into<String>) -> Self {
273        Self {
274            item_type: "message".to_string(),
275            role: "assistant".to_string(),
276            content: vec![OutputContent::Text(OutputTextContent::new(text))],
277            id: None,
278            status: None,
279        }
280    }
281}
282
283/// Function call item parameter.
284#[derive(Debug, Clone, Serialize, Deserialize)]
285pub struct FunctionCallItemParam {
286    #[serde(rename = "type")]
287    item_type: String, // "function_call"
288    id: String,
289    call_id: String,
290    name: String,
291    arguments: String,
292    #[serde(skip_serializing_if = "Option::is_none")]
293    status: Option<FunctionCallStatus>,
294}
295
296/// Function call output item parameter.
297#[derive(Debug, Clone, Serialize, Deserialize)]
298pub struct FunctionCallOutputItemParam {
299    #[serde(rename = "type")]
300    item_type: String, // "function_call_output"
301    call_id: String,
302    output: String,
303    #[serde(skip_serializing_if = "Option::is_none")]
304    id: Option<String>,
305    #[serde(skip_serializing_if = "Option::is_none")]
306    status: Option<FunctionCallStatus>,
307}
308
309impl FunctionCallOutputItemParam {
310    fn new(call_id: impl Into<String>, output: impl Into<String>) -> Self {
311        Self {
312            item_type: "function_call_output".to_string(),
313            call_id: call_id.into(),
314            output: output.into(),
315            id: None,
316            status: None,
317        }
318    }
319}
320
321/// Reasoning summary content.
322#[derive(Debug, Clone, Serialize, Deserialize)]
323pub struct ReasoningSummaryContent {
324    #[serde(rename = "type")]
325    content_type: String, // "summary_text"
326    text: String,
327}
328
329/// Reasoning item parameter.
330#[derive(Debug, Clone, Serialize, Deserialize)]
331pub struct ReasoningItemParam {
332    #[serde(rename = "type")]
333    item_type: String, // "reasoning"
334    summary: Vec<ReasoningSummaryContent>,
335    #[serde(skip_serializing_if = "Option::is_none")]
336    id: Option<String>,
337    #[serde(skip_serializing_if = "Option::is_none")]
338    content: Option<Value>,
339    #[serde(skip_serializing_if = "Option::is_none")]
340    encrypted_content: Option<String>,
341    #[serde(skip_serializing_if = "Option::is_none")]
342    usage: Option<ReasoningUsage>,
343}
344
345/// Usage information for reasoning items.
346#[derive(Debug, Clone, Serialize, Deserialize)]
347pub struct ReasoningUsage {
348    #[serde(skip_serializing_if = "Option::is_none")]
349    input_tokens: Option<u32>,
350    #[serde(skip_serializing_if = "Option::is_none")]
351    output_tokens: Option<u32>,
352    #[serde(skip_serializing_if = "Option::is_none")]
353    total_tokens: Option<u32>,
354}
355
356/// Item reference parameter for referencing previous items.
357#[derive(Debug, Clone, Serialize, Deserialize)]
358pub struct ItemReferenceParam {
359    #[serde(rename = "type")]
360    item_type: String, // "item_reference"
361    id: String,
362}
363
364/// Union of all item parameters that can be sent as input.
365#[derive(Debug, Clone, Serialize, Deserialize)]
366#[serde(untagged)]
367pub enum ItemParam {
368    UserMessage(UserMessageItemParam),
369    SystemMessage(SystemMessageItemParam),
370    DeveloperMessage(DeveloperMessageItemParam),
371    AssistantMessage(AssistantMessageItemParam),
372    FunctionCall(FunctionCallItemParam),
373    FunctionCallOutput(FunctionCallOutputItemParam),
374    Reasoning(ReasoningItemParam),
375    ItemReference(ItemReferenceParam),
376}
377
378// ============================================================================
379// Tool Types
380// ============================================================================
381
382/// Function tool parameter.
383#[derive(Debug, Clone, Serialize, Deserialize)]
384pub struct FunctionToolParam {
385    #[serde(rename = "type")]
386    tool_type: String, // "function"
387    name: String,
388    #[serde(skip_serializing_if = "Option::is_none")]
389    description: Option<String>,
390    #[serde(skip_serializing_if = "Option::is_none")]
391    parameters: Option<Value>,
392    #[serde(skip_serializing_if = "Option::is_none")]
393    strict: Option<bool>,
394}
395
396impl FunctionToolParam {
397    fn new(name: impl Into<String>) -> Self {
398        Self {
399            tool_type: "function".to_string(),
400            name: name.into(),
401            description: None,
402            parameters: None,
403            strict: None,
404        }
405    }
406
407    fn with_description(mut self, description: impl Into<String>) -> Self {
408        self.description = Some(description.into());
409        self
410    }
411
412    fn with_parameters(mut self, parameters: Value) -> Self {
413        self.parameters = Some(parameters);
414        self
415    }
416
417    pub fn with_strict(mut self, strict: bool) -> Self {
418        self.strict = Some(strict);
419        self
420    }
421}
422
423/// Tool choice values.
424#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
425#[serde(rename_all = "lowercase")]
426pub enum ToolChoiceValue {
427    Auto,
428    None,
429    Required,
430    /// Catch-all for unknown tool choice values.
431    #[serde(other)]
432    Unknown,
433}
434
435/// Specific function choice.
436#[derive(Debug, Clone, Serialize, Deserialize)]
437pub struct SpecificFunctionChoice {
438    #[serde(rename = "type")]
439    choice_type: String, // "function"
440    name: String,
441}
442
443/// Tool choice parameter union.
444#[derive(Debug, Clone, Serialize, Deserialize)]
445#[serde(untagged)]
446pub enum ToolChoiceParam {
447    Value(ToolChoiceValue),
448    Specific(SpecificFunctionChoice),
449}
450
451/// Incomplete details for partial responses.
452#[derive(Debug, Clone, Serialize, Deserialize)]
453pub struct IncompleteDetails {
454    #[serde(skip_serializing_if = "Option::is_none")]
455    reason: Option<String>,
456}
457
458/// Error object.
459#[derive(Debug, Clone, Serialize, Deserialize)]
460pub struct ResponseError {
461    code: String,
462    message: String,
463}
464
465/// Response status.
466#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, Default)]
467#[serde(rename_all = "snake_case")]
468pub enum ResponseStatus {
469    Queued,
470    #[default]
471    InProgress,
472    Completed,
473    Failed,
474    Cancelled,
475    /// Catch-all for unknown statuses added by the OpenResponses spec.
476    #[serde(other)]
477    Unknown,
478}
479
480/// OpenResponses API response body.
481#[derive(Debug, Clone, Serialize, Deserialize)]
482pub struct OpenResponsesResponse {
483    id: String,
484    object: String, // "response"
485    #[serde(default)]
486    status: ResponseStatus,
487    output: Vec<Value>,
488    #[serde(skip_serializing_if = "Option::is_none")]
489    usage: Option<ResponseUsage>,
490    #[serde(skip_serializing_if = "Option::is_none")]
491    error: Option<ResponseError>,
492    #[serde(skip_serializing_if = "Option::is_none")]
493    incomplete_details: Option<IncompleteDetails>,
494    #[serde(skip_serializing_if = "Option::is_none")]
495    model: Option<String>,
496    #[serde(skip_serializing_if = "Option::is_none")]
497    created_at: Option<u64>,
498    #[serde(skip_serializing_if = "Option::is_none")]
499    metadata: Option<Value>,
500}
501
502/// Response usage statistics.
503#[derive(Debug, Clone, Serialize, Deserialize)]
504pub struct ResponseUsage {
505    input_tokens: u64,
506    output_tokens: u64,
507    #[serde(skip_serializing_if = "Option::is_none")]
508    input_tokens_details: Option<Value>,
509    #[serde(skip_serializing_if = "Option::is_none")]
510    output_tokens_details: Option<Value>,
511}
512
513#[cfg(test)]
514mod tests {
515    use super::*;
516    use serde_json::json;
517
518    #[test]
519    fn test_user_message_serialization() {
520        let msg = UserMessageItemParam::new("Hello, world!");
521        let json = serde_json::to_value(&msg).unwrap();
522        assert_eq!(json["type"], "message");
523        assert_eq!(json["role"], "user");
524        assert_eq!(json["content"][0]["type"], "input_text");
525        assert_eq!(json["content"][0]["text"], "Hello, world!");
526    }
527
528    #[test]
529    fn test_function_tool_param() {
530        let tool = FunctionToolParam::new("get_weather")
531            .with_description("Get the current weather")
532            .with_parameters(json!({
533                "type": "object",
534                "properties": {
535                    "location": {"type": "string"}
536                }
537            }));
538
539        let json = serde_json::to_value(&tool).unwrap();
540        assert_eq!(json["type"], "function");
541        assert_eq!(json["name"], "get_weather");
542        assert!(json["description"].is_string());
543    }
544
545    #[test]
546    fn test_function_call_output() {
547        let output = FunctionCallOutputItemParam::new("call_123", r#"{"temperature": 72}"#);
548        let json = serde_json::to_value(&output).unwrap();
549        assert_eq!(json["type"], "function_call_output");
550        assert_eq!(json["call_id"], "call_123");
551    }
552}