api_openai/components/
output.rs

1// src/components/output.rs
2//! Structures related to output items generated by the model, such as messages, tool calls, and annotations.
3
4/// Define a private namespace for all its items.
5mod private
6{
7  use crate::components::common::{ VectorStoreFileAttributes, Reasoning };
8  use crate::components::input::ListedInputContentPart;
9  use crate::components::responses::OutputMessage;
10  use crate::components::tools::
11  {
12    FileSearchToolCall,
13    FunctionToolCall,
14    WebSearchToolCall,
15    ComputerToolCall,
16  };
17
18  // Standard library imports
19  use core::fmt;
20  // Serde imports
21  use serde::{ Deserialize, Serialize };
22  use serde::
23  {
24    de ::{ self, Deserializer, MapAccess, Visitor },
25  };
26
27  // --- Annotation Structs (Restored) ---
28
29  /// Inner data for a file citation annotation.
30  ///
31  /// # Used By
32  /// - `Annotation::FileCitation`
33  /// - `MessageContentTextAnnotationsFileCitationObject` (within `assistants_shared.rs`)
34  #[ derive( Debug, Serialize, Deserialize, Clone, PartialEq ) ]
35  pub struct FileCitationAnnotation
36  {
37    /// The ID of the specific File the citation is from.
38    pub file_id : String,
39  }
40
41  /// Inner data for a URL citation annotation.
42  ///
43  /// # Used By
44  /// - `Annotation::UrlCitation`
45  /// - `ChatCompletionResponseMessage` (within `chat_shared.rs`)
46  #[ derive( Debug, Serialize, Deserialize, Clone, PartialEq ) ]
47  pub struct UrlCitationAnnotation
48  {
49    /// The URL of the web resource.
50    pub url : String,
51    /// The title of the web resource.
52    pub title : String,
53  }
54
55  /// Inner data for a file path annotation.
56  ///
57  /// # Used By
58  /// - `Annotation::FilePath`
59  /// - `MessageContentTextAnnotationsFilePathObject` (within `assistants_shared.rs`)
60  #[ derive( Debug, Serialize, Deserialize, Clone, PartialEq ) ]
61  pub struct FilePathAnnotation
62  {
63    /// The ID of the file that was generated.
64    pub file_id : String,
65  }
66
67  /// Represents annotations embedded within output text content.
68  /// These annotations provide context or references for parts of the text.
69  ///
70  /// # Used By
71  /// - `OutputContentPart::Text`
72  /// - `MessageContentTextObject` (within `assistants_shared.rs`)
73  /// - `MessageDeltaTextContent` (within `assistants_shared.rs`)
74  /// - `ResponseTextAnnotationDeltaEvent` (within `realtime_shared.rs`)
75  #[ derive( Debug, Serialize, Clone, PartialEq ) ]
76  pub enum Annotation
77  {
78    /// A citation within the message that points to a specific quote from a specific File.
79    FileCitation
80    {
81      /// The text in the message content that needs to be replaced.
82      text : String,
83      /// The details of the file citation.
84      file_citation : FileCitationAnnotation,
85      /// The start index of the citation in the text.
86      start_index : u32,
87      /// The end index of the citation in the text.
88      end_index : u32,
89    },
90    /// A citation for a web resource used to generate a model response.
91    UrlCitation
92    {
93      /// The URL of the web resource.
94      url : String,
95      /// The title of the web resource.
96      title : String,
97      /// The start index of the citation in the text.
98      start_index : u32,
99      /// The end index of the citation in the text.
100      end_index : u32,
101    },
102    /// A URL for the file that's generated when the assistant used the `code_interpreter` tool.
103    FilePath
104    {
105      /// The text in the message content that needs to be replaced.
106      text : String,
107      /// The details of the file path.
108      file_path : FilePathAnnotation,
109      /// The start index of the file path in the text.
110      start_index : u32,
111      /// The end index of the file path in the text.
112      end_index : u32,
113    },
114  }
115
116  // Manual Deserialization for Annotation enum based on the 'type' field
117  impl< 'de > Deserialize< 'de > for Annotation
118  {
119    #[ allow( clippy::too_many_lines, reason = "Manual deserialization requires many lines." ) ]
120    #[ inline ]
121    fn deserialize< D >( deserializer : D ) -> Result< Self, D::Error >
122    where
123      D : Deserializer< 'de >,
124    {
125      #[ derive( Deserialize ) ]
126      #[ serde( field_identifier, rename_all = "snake_case" ) ]
127      enum Field
128      {
129        Type, Text, FileCitation, UrlCitation, FilePath, StartIndex, EndIndex, Url, Title
130      }
131
132      struct AnnotationVisitor;
133
134      impl< 'de > Visitor< 'de > for AnnotationVisitor
135      {
136        type Value = Annotation;
137
138        fn expecting( &self, formatter : &mut fmt::Formatter< '_ > ) -> fmt::Result
139        {
140          formatter.write_str( "struct Annotation" )
141        }
142
143        fn visit_map< V >( self, mut map : V ) -> Result< Annotation, V::Error >
144        where
145          V : MapAccess< 'de >,
146        {
147          let mut type_ : Option< String > = None;
148          let mut text : Option< String > = None;
149          let mut file_citation : Option< FileCitationAnnotation > = None;
150          let mut url_citation : Option< UrlCitationAnnotation > = None;
151          let mut file_path : Option< FilePathAnnotation > = None;
152          let mut start_index : Option< u32 > = None;
153          let mut end_index : Option< u32 > = None;
154          let mut url : Option< String > = None;
155          let mut title : Option< String > = None;
156
157          while let Some( key ) = map.next_key()?
158          {
159            match key
160            {
161              Field::Type =>
162              {
163                if type_.is_some() { Err( de::Error::duplicate_field( "type" ) )? }
164                type_ = Some( map.next_value()? );
165              }
166              Field::Text =>
167              {
168                if text.is_some() { Err( de::Error::duplicate_field( "text" ) )? }
169                text = Some( map.next_value()? );
170              }
171              Field::FileCitation =>
172              {
173                if file_citation.is_some() { Err( de::Error::duplicate_field( "file_citation" ) )? }
174                file_citation = Some( map.next_value()? );
175              }
176              Field::UrlCitation =>
177              {
178                if url_citation.is_some() { Err( de::Error::duplicate_field( "url_citation" ) )? }
179                url_citation = Some( map.next_value()? );
180              }
181              Field::FilePath =>
182              {
183                if file_path.is_some() { Err( de::Error::duplicate_field( "file_path" ) )? }
184                file_path = Some( map.next_value()? );
185              }
186              Field::StartIndex =>
187              {
188                if start_index.is_some() { Err( de::Error::duplicate_field( "start_index" ) )? }
189                start_index = Some( map.next_value()? );
190              }
191              Field::EndIndex =>
192              {
193                if end_index.is_some() { Err( de::Error::duplicate_field( "end_index" ) )? }
194                end_index = Some( map.next_value()? );
195              }
196              Field::Url =>
197              {
198                if url.is_some() { Err( de::Error::duplicate_field( "url" ) )? }
199                url = Some( map.next_value()? );
200              }
201              Field::Title =>
202              {
203                if title.is_some() { Err( de::Error::duplicate_field( "title" ) )? }
204                title = Some( map.next_value()? );
205              }
206            }
207          }
208
209          let type_ = type_.ok_or_else( || de::Error::missing_field( "type" ) )?;
210          let start_index = start_index.ok_or_else( || de::Error::missing_field( "start_index" ) )?;
211          let end_index = end_index.ok_or_else( || de::Error::missing_field( "end_index" ) )?;
212
213          match type_.as_str()
214          {
215            "file_citation" =>
216            {
217              let text = text.ok_or_else( || de::Error::missing_field( "text" ) )?;
218              let file_citation = file_citation.ok_or_else( || de::Error::missing_field( "file_citation" ) )?;
219              Ok( Annotation::FileCitation { text, file_citation, start_index, end_index } )
220            }
221            "url_citation" =>
222            {
223              let url = url.ok_or_else( || de::Error::missing_field( "url" ) )?;
224              let title = title.ok_or_else( || de::Error::missing_field( "title" ) )?;
225              Ok( Annotation::UrlCitation { url, title, start_index, end_index } )
226            }
227            "file_path" =>
228            {
229              let text = text.ok_or_else( || de::Error::missing_field( "text" ) )?;
230              let file_path = file_path.ok_or_else( || de::Error::missing_field( "file_path" ) )?;
231              Ok( Annotation::FilePath { text, file_path, start_index, end_index } )
232            }
233            _ => Err( de::Error::unknown_variant( &type_, &[ "file_citation", "url_citation", "file_path" ] ) ),
234          }
235        }
236      }
237
238      const FIELDS : &[ &str ] = &[ "type", "text", "file_citation", "url_citation", "file_path", "start_index", "end_index" ];
239      deserializer.deserialize_struct( "Annotation", FIELDS, AnnotationVisitor )
240    }
241  }
242
243  // --- OutputItem Enum (Added) ---
244
245  /// Represents the different types of items that can appear in the `output` array of a `Response`.
246  /// Corresponds to the `OutputItem` schema in the `OpenAPI` spec.
247  ///
248  /// # Used By
249  /// - `Response` (within `responses.rs`)
250  #[ derive( Debug, Serialize, Deserialize, Clone, PartialEq ) ]
251  #[ serde( tag = "type" ) ] // Use the 'type' field to discriminate variants
252  pub enum OutputItem
253  {
254    /// An output message from the model.
255    #[ serde( rename = "message" ) ]
256    Message( OutputMessage ), // Use definition from responses.rs
257
258    /// A call to the file search tool.
259    #[ serde( rename = "file_search_call" ) ]
260    FileSearchCall( FileSearchToolCall ),
261
262    /// A call to a function tool.
263    #[ serde( rename = "function_call" ) ]
264    FunctionCall( FunctionToolCall ),
265
266    /// A call to the web search tool.
267    #[ serde( rename = "web_search_call" ) ]
268    WebSearchCall( WebSearchToolCall ),
269
270    /// A call to the computer use tool.
271    #[ serde( rename = "computer_call" ) ]
272    ComputerCall( ComputerToolCall ),
273
274    /// Reasoning steps taken by the model.
275    #[ serde( rename = "reasoning" ) ]
276    Reasoning( Reasoning ), // Corrected name
277  }
278
279  // --- OutputContentPart Definition ---
280  /// Represents parts of the output content within a response message.
281  /// Corresponds to the `OutputContent` schema in the `OpenAPI` spec.
282  ///
283  /// # Used By
284  /// - `OutputMessage` (within `responses.rs`)
285  #[ derive( Debug, Serialize, Deserialize, Clone, PartialEq ) ]
286  #[ serde( tag = "type" ) ]
287  pub enum OutputContentPart
288  {
289    /// Text content generated by the model.
290    #[ serde( rename = "output_text" ) ]
291    Text
292    {
293      /// The text content.
294      text : String,
295      /// Any annotations associated with the text (e.g., citations).
296      #[ serde( default ) ]
297      annotations : Vec< Annotation >,
298    },
299    /// A refusal message generated by the model.
300    #[ serde( rename = "refusal" ) ]
301    Refusal
302    {
303      /// The refusal explanation.
304      refusal : String,
305    },
306    // Note : Other output types like audio might be added here in the future
307  }
308
309  // --- FileSearchResultItem (Moved from file_search.rs for dependency reasons) ---
310  // Note : Ideally, this would stay in file_search.rs, but OutputItem needs it.
311  // Consider refactoring later if this becomes problematic.
312  /// Represents a single item found during a file search.
313  ///
314  /// # Used By
315  /// - `FileSearchToolCall` (within `tools/file_search.rs`)
316  #[ derive( Debug, Serialize, Deserialize, Clone, PartialEq ) ]
317  pub struct FileSearchResultItem
318  {
319      /// The ID of the file containing the result.
320      pub file_id : String,
321      /// The name of the file containing the result.
322      pub filename : String,
323      /// The relevance score of the result (between 0 and 1).
324      pub score : f64,
325      /// Attributes associated with the file.
326      #[ serde( default ) ]
327      pub attributes : VectorStoreFileAttributes,
328      /// Content chunks from the file (only included if requested).
329      #[ serde( default ) ]
330      pub content : Vec< ListedInputContentPart >,
331  }
332
333  // --- ComputerScreenshotImage (Moved from computer_use.rs for dependency reasons) ---
334  // Note : Ideally, this would stay in computer_use.rs.
335  /// Represents the output of a computer tool action, typically a screenshot.
336  ///
337  /// # Used By
338  /// - `ComputerToolCallOutput` (within `tools/computer_use.rs`)
339  #[ derive( Debug, Serialize, Deserialize, Clone, PartialEq ) ]
340  pub struct ComputerScreenshotImage
341  {
342    /// The URL of the screenshot image.
343    #[ serde( skip_serializing_if = "Option::is_none" ) ]
344    pub image_url : Option< String >,
345    /// The identifier of an uploaded file that contains the screenshot.
346    #[ serde( skip_serializing_if = "Option::is_none" ) ]
347    pub file_id : Option< String >,
348  }
349
350
351} // end mod private
352
353crate ::mod_interface!
354{
355  exposed use
356  {
357    FileCitationAnnotation,
358    UrlCitationAnnotation,
359    FilePathAnnotation,
360    Annotation,
361    OutputItem,
362    OutputContentPart, // Expose the newly defined enum
363    // ReasoningItem, // No longer a placeholder here
364    // Expose moved structs
365    FileSearchResultItem,
366    ComputerScreenshotImage,
367  };
368
369  // Re-export types used within the exposed items
370  own use crate::components::
371  {
372    common ::{ VectorStoreFileAttributes, Reasoning }, // Corrected ReasoningItem to Reasoning
373    input ::ListedInputContentPart,
374    tools ::
375    {
376      FileSearchToolCall,
377      FunctionToolCall,
378      WebSearchToolCall,
379      ComputerToolCall,
380    },
381    // Import OutputMessage from responses.rs
382    responses ::OutputMessage,
383  };
384}