Skip to main content

openai_protocol/
realtime_conversation.rs

1// OpenAI Realtime Conversation API types
2// https://platform.openai.com/docs/api-reference/realtime
3//
4// Session configuration and audio types live in `realtime_session`.
5// Event type constants live in `event_types`.
6// This module covers conversation items, content parts.
7
8use serde::{Deserialize, Serialize};
9
10use crate::common::Redacted;
11
12// ============================================================================
13// Conversation Item
14// ============================================================================
15/// A conversation item in the Realtime API.
16///
17/// Discriminated by the `type` field:
18/// - `message`               — a text/audio/image message (system/user/assistant)
19/// - `function_call`         — a function call issued by the model
20/// - `function_call_output`  — the result supplied by the client
21/// - `mcp_call`              — an MCP tool call issued by the model
22/// - `mcp_list_tools`        — MCP list-tools result
23/// - `mcp_approval_request`  — server asks client to approve an MCP call
24/// - `mcp_approval_response` — client approves/denies an MCP call
25///
26/// # Content-part / role constraints
27///
28/// The OpenAI spec restricts which content-part types are valid per role:
29/// - `system`    → `input_text` only
30/// - `user`      → `input_text`, `input_audio`, `input_image`
31/// - `assistant` → `output_text`, `output_audio`
32///
33/// Serde does not enforce this. Call [`RealtimeConversationItem::validate()`]
34/// after deserialization to check these invariants.
35#[serde_with::skip_serializing_none]
36#[derive(Debug, Clone, Serialize, Deserialize)]
37#[serde(tag = "type", rename_all = "snake_case")]
38pub enum RealtimeConversationItem {
39    Message {
40        content: Vec<RealtimeContentPart>,
41        role: ConversationItemRole,
42        id: Option<String>,
43        object: Option<ConversationItemObject>,
44        status: Option<ConversationItemStatus>,
45    },
46    FunctionCall {
47        arguments: String,
48        name: String,
49        id: Option<String>,
50        call_id: Option<String>,
51        object: Option<ConversationItemObject>,
52        status: Option<ConversationItemStatus>,
53    },
54    FunctionCallOutput {
55        call_id: String,
56        output: String,
57        id: Option<String>,
58        object: Option<ConversationItemObject>,
59        status: Option<ConversationItemStatus>,
60    },
61    McpApprovalResponse {
62        id: String,
63        approval_request_id: String,
64        approve: bool,
65        reason: Option<String>,
66    },
67    McpListTools {
68        server_label: String,
69        tools: Vec<McpListToolEntry>,
70        id: Option<String>,
71    },
72    McpCall {
73        id: String,
74        arguments: String,
75        name: String,
76        server_label: String,
77        approval_request_id: Option<String>,
78        error: Option<McpCallError>,
79        output: Option<String>,
80    },
81    McpApprovalRequest {
82        id: String,
83        arguments: String,
84        name: String,
85        server_label: String,
86    },
87}
88
89/// Object type for conversation items. Always `"realtime.item"`.
90#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
91pub enum ConversationItemObject {
92    #[serde(rename = "realtime.item")]
93    RealtimeItem,
94}
95
96/// Status of a conversation item.
97#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
98#[serde(rename_all = "snake_case")]
99pub enum ConversationItemStatus {
100    Completed,
101    Incomplete,
102    InProgress,
103}
104
105/// Role for a conversation item.
106#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
107#[serde(rename_all = "snake_case")]
108pub enum ConversationItemRole {
109    User,
110    Assistant,
111    System,
112}
113
114// ============================================================================
115// Content Parts (Realtime-specific)
116// ============================================================================
117
118/// Content part inside a `RealtimeConversationItem::Message`.
119///
120#[serde_with::skip_serializing_none]
121#[derive(Debug, Clone, Serialize, Deserialize)]
122#[serde(tag = "type", rename_all = "snake_case")]
123pub enum RealtimeContentPart {
124    InputText {
125        text: Option<String>,
126    },
127    InputAudio {
128        audio: Option<Redacted>,
129        transcript: Option<String>,
130    },
131    InputImage {
132        detail: Option<ImageDetail>,
133        image_url: Option<Redacted>,
134    },
135    OutputText {
136        text: Option<String>,
137    },
138    OutputAudio {
139        audio: Option<Redacted>,
140        transcript: Option<String>,
141    },
142}
143
144impl RealtimeContentPart {
145    /// Returns the wire-format type name (e.g. `"input_text"`).
146    pub const fn type_name(&self) -> &'static str {
147        match self {
148            Self::InputText { .. } => "input_text",
149            Self::InputAudio { .. } => "input_audio",
150            Self::InputImage { .. } => "input_image",
151            Self::OutputText { .. } => "output_text",
152            Self::OutputAudio { .. } => "output_audio",
153        }
154    }
155}
156
157/// Detail level for image processing. `auto` will default to `high`.
158#[derive(Debug, Clone, Serialize, Deserialize, Default)]
159#[serde(rename_all = "snake_case")]
160pub enum ImageDetail {
161    #[default]
162    Auto,
163    Low,
164    High,
165}
166
167// ============================================================================
168// MCP Types
169// ============================================================================
170
171/// A tool entry returned in `mcp_list_tools` conversation items.
172#[serde_with::skip_serializing_none]
173#[derive(Debug, Clone, Serialize, Deserialize)]
174pub struct McpListToolEntry {
175    pub input_schema: serde_json::Value,
176    pub name: String,
177    pub annotations: Option<serde_json::Value>,
178    pub description: Option<String>,
179}
180
181/// Error from an MCP tool call.
182///
183/// One of: protocol error, tool execution error, or HTTP error.
184#[expect(clippy::enum_variant_names)]
185#[serde_with::skip_serializing_none]
186#[derive(Debug, Clone, Serialize, Deserialize)]
187#[serde(tag = "type", rename_all = "snake_case")]
188pub enum McpCallError {
189    /// MCP protocol-level error.
190    ProtocolError { code: i64, message: String },
191    /// Error during tool execution on the MCP server.
192    ToolExecutionError { message: String },
193    /// HTTP-level error communicating with the MCP server.
194    HttpError { code: i64, message: String },
195}