Skip to main content

vtcode_core/open_responses/
content.rs

1//! Content parts for Open Responses items.
2//!
3//! Content parts represent the atomic units of content within items,
4//! such as text, images, or files. They are streamable and follow
5//! the delta event pattern for incremental updates.
6
7use serde::{Deserialize, Serialize};
8
9/// Unique identifier for a content part within an output item.
10pub type ContentPartId = String;
11
12/// Content part types supported by Open Responses.
13///
14/// Content parts are the atomic units of content within message items.
15/// Each content part has its own lifecycle and can be streamed independently.
16#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
17#[serde(tag = "type", rename_all = "snake_case")]
18pub enum ContentPart {
19    /// Text output from the model.
20    OutputText(OutputTextContent),
21
22    /// Text input provided to the model.
23    InputText(InputTextContent),
24
25    /// Image input provided to the model.
26    InputImage(InputImageContent),
27
28    /// File input provided to the model.
29    InputFile(InputFileContent),
30
31    /// URL citation for web resources used in generation.
32    UrlCitation(UrlCitationContent),
33
34    /// Refusal content when the model declines to respond.
35    Refusal(RefusalContent),
36}
37
38impl ContentPart {
39    /// Create a new output text content part.
40    pub fn output_text(text: impl Into<String>) -> Self {
41        Self::OutputText(OutputTextContent { text: text.into() })
42    }
43
44    /// Create a new input text content part.
45    pub fn input_text(text: impl Into<String>) -> Self {
46        Self::InputText(InputTextContent { text: text.into() })
47    }
48
49    /// Create a new refusal content part.
50    pub fn refusal(refusal: impl Into<String>) -> Self {
51        Self::Refusal(RefusalContent {
52            refusal: refusal.into(),
53        })
54    }
55
56    /// Returns the text content if this is a text-based content part.
57    pub fn as_text(&self) -> Option<&str> {
58        match self {
59            Self::OutputText(c) => Some(&c.text),
60            Self::InputText(c) => Some(&c.text),
61            Self::Refusal(c) => Some(&c.refusal),
62            _ => None,
63        }
64    }
65}
66
67/// Text output from the model.
68#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
69pub struct OutputTextContent {
70    /// The text content generated by the model.
71    pub text: String,
72}
73
74/// Text input provided to the model.
75#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
76pub struct InputTextContent {
77    /// The text content provided as input.
78    pub text: String,
79}
80
81/// Image input provided to the model.
82#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
83pub struct InputImageContent {
84    /// URL of the image (data URI or HTTP URL).
85    pub image_url: String,
86
87    /// Level of detail for image analysis.
88    #[serde(default, skip_serializing_if = "Option::is_none")]
89    pub detail: Option<ImageDetail>,
90}
91
92/// Level of detail for image analysis.
93#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize, Default)]
94#[serde(rename_all = "lowercase")]
95pub enum ImageDetail {
96    /// Low detail mode.
97    Low,
98    /// High detail mode.
99    High,
100    /// Let the model decide.
101    #[default]
102    Auto,
103    /// Preserve the original image detail.
104    Original,
105}
106
107/// File input provided to the model.
108#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
109pub struct InputFileContent {
110    /// Filename of the input file.
111    #[serde(skip_serializing_if = "Option::is_none")]
112    pub filename: Option<String>,
113
114    /// File ID returned by the Files API.
115    #[serde(skip_serializing_if = "Option::is_none")]
116    pub file_id: Option<String>,
117
118    /// Base64-encoded file data.
119    #[serde(skip_serializing_if = "Option::is_none")]
120    pub file_data: Option<String>,
121
122    /// URL of the file.
123    #[serde(skip_serializing_if = "Option::is_none")]
124    pub file_url: Option<String>,
125}
126
127/// URL citation for web resources.
128#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
129pub struct UrlCitationContent {
130    /// The URL of the web resource.
131    pub url: String,
132
133    /// Title of the web resource.
134    pub title: String,
135
136    /// Start index in the message text.
137    pub start_index: usize,
138
139    /// End index in the message text.
140    pub end_index: usize,
141}
142
143/// Refusal content when the model declines to respond.
144#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
145pub struct RefusalContent {
146    /// The refusal message.
147    pub refusal: String,
148}
149
150#[cfg(test)]
151mod tests {
152    use super::*;
153
154    #[test]
155    fn test_content_part_output_text() {
156        let part = ContentPart::output_text("Hello, world!");
157        assert_eq!(part.as_text(), Some("Hello, world!"));
158    }
159
160    #[test]
161    fn test_content_part_serialization() {
162        let part = ContentPart::output_text("Test");
163        let json = serde_json::to_string(&part).unwrap();
164        assert!(json.contains("\"type\":\"output_text\""));
165        assert!(json.contains("\"text\":\"Test\""));
166    }
167}