Skip to main content

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