Skip to main content

open_agent/types/
openai.rs

1/// OpenAI message content, serialized as either a string or a list of parts.
2///
3/// Images require [`OpenAIContent::Parts`]; text-only messages can use
4/// [`OpenAIContent::Text`].
5#[derive(Debug, Clone, Serialize, Deserialize)]
6#[serde(untagged)]
7pub enum OpenAIContent {
8    /// Simple text string (backward compatible)
9    Text(String),
10    /// Array of content parts (text and/or images)
11    Parts(Vec<OpenAIContentPart>),
12}
13
14/// A single content part in an OpenAI message.
15///
16/// Can be either text or an image URL. This is a tagged enum that prevents
17/// invalid states (e.g., having both text and image_url, or neither).
18#[derive(Debug, Clone, Serialize, Deserialize)]
19#[serde(tag = "type", rename_all = "snake_case")]
20pub enum OpenAIContentPart {
21    /// Text content part
22    Text {
23        /// The text content
24        text: String,
25    },
26    /// Image URL content part
27    #[serde(rename = "image_url")]
28    ImageUrl {
29        /// The image URL details
30        image_url: OpenAIImageUrl,
31    },
32}
33
34impl OpenAIContentPart {
35    /// Creates a text part.
36    pub fn text(text: impl Into<String>) -> Self {
37        Self::Text { text: text.into() }
38    }
39
40    /// Converts a validated [`ImageBlock`] while preserving its URL and detail hint.
41    pub fn from_image(image: &ImageBlock) -> Self {
42        Self::ImageUrl {
43            image_url: OpenAIImageUrl {
44                url: image.url().to_string(),
45                detail: Some(image.detail().to_string()),
46            },
47        }
48    }
49
50    /// Creates an unvalidated image part. Prefer [`from_image`](Self::from_image)
51    /// with a validated [`ImageBlock`].
52    #[deprecated(
53        since = "0.6.0",
54        note = "Use `from_image()` instead to ensure proper validation"
55    )]
56    pub fn image_url(url: impl Into<String>, detail: ImageDetail) -> Self {
57        Self::ImageUrl {
58            image_url: OpenAIImageUrl {
59                url: url.into(),
60                detail: Some(detail.to_string()),
61            },
62        }
63    }
64}
65
66/// OpenAI image URL structure.
67#[derive(Debug, Clone, Serialize, Deserialize)]
68pub struct OpenAIImageUrl {
69    /// Image URL or data URI
70    pub url: String,
71    /// Detail level: "low", "high", or "auto"
72    #[serde(skip_serializing_if = "Option::is_none")]
73    pub detail: Option<String>,
74}
75
76#[derive(Debug, Clone, Serialize, Deserialize)]
77pub struct OpenAIMessage {
78    /// Message role as a string ("system", "user", "assistant", "tool").
79    pub role: String,
80
81    /// Message content (string for text-only, array for text+images).
82    #[serde(skip_serializing_if = "Option::is_none")]
83    pub content: Option<OpenAIContent>,
84
85    /// Tool calls requested by the assistant (assistant messages only).
86    ///
87    /// When the model wants to call tools, this field contains the list
88    /// of tool invocations with their parameters. Only present in assistant
89    /// messages.
90    #[serde(skip_serializing_if = "Option::is_none")]
91    pub tool_calls: Option<Vec<OpenAIToolCall>>,
92
93    /// ID of the tool call this message is responding to (tool messages only).
94    ///
95    /// When sending tool results back to the model, this field links the
96    /// result to the original tool call request. Only present in tool
97    /// messages.
98    #[serde(skip_serializing_if = "Option::is_none")]
99    pub tool_call_id: Option<String>,
100}
101
102/// An OpenAI tool call, including the call ID used to pair its result.
103#[derive(Debug, Clone, Serialize, Deserialize)]
104pub struct OpenAIToolCall {
105    /// Unique identifier for this tool call.
106    ///
107    /// Generated by the model. Used to correlate tool results back to
108    /// this specific call.
109    pub id: String,
110
111    /// Type of the call (always "function" in current API).
112    ///
113    /// The `rename` attribute ensures this serializes as `"type"` in JSON
114    /// since `type` is a Rust keyword.
115    #[serde(rename = "type")]
116    pub call_type: String,
117
118    /// Function/tool details (name and arguments).
119    pub function: OpenAIFunction,
120}
121
122/// Function name and JSON-encoded arguments for an OpenAI tool call.
123#[derive(Debug, Clone, Serialize, Deserialize)]
124pub struct OpenAIFunction {
125    /// Name of the function/tool to call.
126    pub name: String,
127
128    /// Function arguments as a **JSON string** (OpenAI API quirk).
129    ///
130    /// Must be parsed as JSON before use. For example, this might contain
131    /// the string `"{\"location\":\"NYC\",\"units\":\"fahrenheit\"}"` which
132    /// needs to be parsed into an actual JSON value.
133    pub arguments: String,
134}
135
136/// An OpenAI chat-completions request.
137///
138/// Optional fields are omitted when unset. The client also uses this as its internal
139/// request representation before translating to Anthropic at the transport boundary.
140#[derive(Debug, Clone, Serialize)]
141pub struct OpenAIRequest {
142    /// Model identifier (e.g., "gpt-4", "qwen2.5-32b-instruct").
143    pub model: String,
144
145    /// Conversation history as a sequence of messages.
146    ///
147    /// Includes system prompt, user messages, assistant responses, and
148    /// tool results. Order matters - messages are processed sequentially.
149    pub messages: Vec<OpenAIMessage>,
150
151    /// Whether to stream the response.
152    ///
153    /// The SDK sets this to `true` to request SSE responses.
154    /// Streaming allows incremental processing of responses rather than
155    /// waiting for the entire completion.
156    pub stream: bool,
157
158    /// Maximum tokens to generate (optional).
159    ///
160    /// `None` uses the provider's default. Some providers require this
161    /// to be set explicitly.
162    #[serde(skip_serializing_if = "Option::is_none")]
163    pub max_tokens: Option<u32>,
164
165    /// Sampling temperature (optional).
166    ///
167    /// `None` uses the provider's default. Controls randomness in
168    /// generation.
169    #[serde(skip_serializing_if = "Option::is_none")]
170    pub temperature: Option<f32>,
171
172    /// Tools/functions available to the model (optional).
173    ///
174    /// When present, enables function calling. Each tool is described
175    /// with a JSON schema defining its parameters. `None` means no
176    /// tools are available.
177    #[serde(skip_serializing_if = "Option::is_none")]
178    pub tools: Option<Vec<serde_json::Value>>,
179}