1use serde::{Deserialize, Serialize};
2use serde_json::Value;
3
4use crate::openai::create_response::response::ResponseBody as CreateResponseBody;
5use crate::openai::create_response::types::{
6 ResponseOutputItem, ResponseOutputRefusal, ResponseOutputText, ResponseReasoningTextContent,
7 ResponseSummaryTextContent,
8};
9
10#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
11pub struct ResponseStreamErrorPayload {
12 #[serde(rename = "type")]
13 pub type_: String,
14 #[serde(default, skip_serializing_if = "Option::is_none")]
15 pub code: Option<String>,
16 pub message: String,
17 #[serde(default, skip_serializing_if = "Option::is_none")]
18 pub param: Option<String>,
19}
20
21impl ResponseStreamErrorPayload {
22 pub fn code_or_type(&self) -> &str {
23 self.code.as_deref().unwrap_or(self.type_.as_str())
24 }
25}
26
27#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
32#[serde(tag = "type")]
33pub enum ResponseStreamEvent {
34 #[serde(rename = "response.audio.delta")]
35 AudioDelta {
36 delta: String,
37 sequence_number: u64,
38 #[serde(default, skip_serializing_if = "Option::is_none")]
39 obfuscation: Option<String>,
40 },
41 #[serde(rename = "response.audio.done")]
42 AudioDone { sequence_number: u64 },
43 #[serde(rename = "response.audio.transcript.delta")]
44 AudioTranscriptDelta {
45 delta: String,
46 sequence_number: u64,
47 #[serde(default, skip_serializing_if = "Option::is_none")]
48 obfuscation: Option<String>,
49 },
50 #[serde(rename = "response.audio.transcript.done")]
51 AudioTranscriptDone { sequence_number: u64 },
52
53 #[serde(rename = "response.code_interpreter_call_code.delta")]
54 CodeInterpreterCallCodeDelta {
55 delta: String,
56 item_id: String,
57 output_index: u64,
58 sequence_number: u64,
59 #[serde(default, skip_serializing_if = "Option::is_none")]
60 obfuscation: Option<String>,
61 },
62 #[serde(rename = "response.code_interpreter_call_code.done")]
63 CodeInterpreterCallCodeDone {
64 code: String,
65 item_id: String,
66 output_index: u64,
67 sequence_number: u64,
68 },
69 #[serde(rename = "response.code_interpreter_call.completed")]
70 CodeInterpreterCallCompleted {
71 item_id: String,
72 output_index: u64,
73 sequence_number: u64,
74 },
75 #[serde(rename = "response.code_interpreter_call.in_progress")]
76 CodeInterpreterCallInProgress {
77 item_id: String,
78 output_index: u64,
79 sequence_number: u64,
80 },
81 #[serde(rename = "response.code_interpreter_call.interpreting")]
82 CodeInterpreterCallInterpreting {
83 item_id: String,
84 output_index: u64,
85 sequence_number: u64,
86 },
87
88 #[serde(rename = "response.created")]
89 Created {
90 response: CreateResponseBody,
91 sequence_number: u64,
92 },
93 #[serde(rename = "response.queued")]
94 Queued {
95 response: CreateResponseBody,
96 sequence_number: u64,
97 },
98 #[serde(rename = "response.in_progress")]
99 InProgress {
100 response: CreateResponseBody,
101 sequence_number: u64,
102 },
103 #[serde(rename = "response.failed")]
104 Failed {
105 response: CreateResponseBody,
106 sequence_number: u64,
107 },
108 #[serde(rename = "response.incomplete")]
109 Incomplete {
110 response: CreateResponseBody,
111 sequence_number: u64,
112 },
113 #[serde(rename = "response.completed")]
114 Completed {
115 response: CreateResponseBody,
116 sequence_number: u64,
117 },
118
119 #[serde(rename = "response.output_item.added")]
120 OutputItemAdded {
121 item: ResponseOutputItem,
122 output_index: u64,
123 sequence_number: u64,
124 },
125 #[serde(rename = "response.output_item.done")]
126 OutputItemDone {
127 item: ResponseOutputItem,
128 output_index: u64,
129 sequence_number: u64,
130 },
131
132 #[serde(rename = "response.content_part.added")]
133 ContentPartAdded {
134 content_index: u64,
135 item_id: String,
136 output_index: u64,
137 part: ResponseStreamContentPart,
138 sequence_number: u64,
139 },
140 #[serde(rename = "response.content_part.done")]
141 ContentPartDone {
142 content_index: u64,
143 item_id: String,
144 output_index: u64,
145 part: ResponseStreamContentPart,
146 sequence_number: u64,
147 },
148
149 #[serde(rename = "response.output_text.annotation.added")]
150 OutputTextAnnotationAdded {
151 annotation: Value,
152 annotation_index: u64,
153 content_index: u64,
154 item_id: String,
155 output_index: u64,
156 sequence_number: u64,
157 },
158
159 #[serde(rename = "response.output_text.delta")]
160 OutputTextDelta {
161 content_index: u64,
162 delta: String,
163 item_id: String,
164 #[serde(default, skip_serializing_if = "Option::is_none")]
165 logprobs: Option<Vec<ResponseStreamTokenLogprob>>,
166 output_index: u64,
167 sequence_number: u64,
168 #[serde(default, skip_serializing_if = "Option::is_none")]
169 obfuscation: Option<String>,
170 },
171 #[serde(rename = "response.output_text.done")]
172 OutputTextDone {
173 content_index: u64,
174 item_id: String,
175 #[serde(default, skip_serializing_if = "Option::is_none")]
176 logprobs: Option<Vec<ResponseStreamTokenLogprob>>,
177 output_index: u64,
178 sequence_number: u64,
179 text: String,
180 },
181
182 #[serde(rename = "response.refusal.delta")]
183 RefusalDelta {
184 content_index: u64,
185 delta: String,
186 item_id: String,
187 output_index: u64,
188 sequence_number: u64,
189 #[serde(default, skip_serializing_if = "Option::is_none")]
190 obfuscation: Option<String>,
191 },
192 #[serde(rename = "response.refusal.done")]
193 RefusalDone {
194 content_index: u64,
195 item_id: String,
196 output_index: u64,
197 refusal: String,
198 sequence_number: u64,
199 },
200
201 #[serde(rename = "response.reasoning_text.delta")]
202 ReasoningTextDelta {
203 content_index: u64,
204 delta: String,
205 item_id: String,
206 output_index: u64,
207 sequence_number: u64,
208 #[serde(default, skip_serializing_if = "Option::is_none")]
209 obfuscation: Option<String>,
210 },
211 #[serde(rename = "response.reasoning_text.done")]
212 ReasoningTextDone {
213 content_index: u64,
214 item_id: String,
215 output_index: u64,
216 sequence_number: u64,
217 text: String,
218 },
219
220 #[serde(rename = "response.reasoning_summary_part.added")]
221 ReasoningSummaryPartAdded {
222 item_id: String,
223 output_index: u64,
224 part: ResponseSummaryTextContent,
225 sequence_number: u64,
226 summary_index: u64,
227 },
228 #[serde(rename = "response.reasoning_summary_part.done")]
229 ReasoningSummaryPartDone {
230 item_id: String,
231 output_index: u64,
232 part: ResponseSummaryTextContent,
233 sequence_number: u64,
234 summary_index: u64,
235 },
236 #[serde(rename = "response.reasoning_summary_text.delta")]
237 ReasoningSummaryTextDelta {
238 delta: String,
239 item_id: String,
240 output_index: u64,
241 sequence_number: u64,
242 summary_index: u64,
243 #[serde(default, skip_serializing_if = "Option::is_none")]
244 obfuscation: Option<String>,
245 },
246 #[serde(rename = "response.reasoning_summary_text.done")]
247 ReasoningSummaryTextDone {
248 item_id: String,
249 output_index: u64,
250 sequence_number: u64,
251 summary_index: u64,
252 text: String,
253 },
254
255 #[serde(rename = "response.function_call_arguments.delta")]
256 FunctionCallArgumentsDelta {
257 delta: String,
258 item_id: String,
259 output_index: u64,
260 sequence_number: u64,
261 #[serde(default, skip_serializing_if = "Option::is_none")]
262 obfuscation: Option<String>,
263 },
264 #[serde(rename = "response.function_call_arguments.done")]
265 FunctionCallArgumentsDone {
266 arguments: String,
267 item_id: String,
268 #[serde(default, skip_serializing_if = "Option::is_none")]
269 name: Option<String>,
270 output_index: u64,
271 sequence_number: u64,
272 },
273
274 #[serde(rename = "response.file_search_call.in_progress")]
275 FileSearchCallInProgress {
276 item_id: String,
277 output_index: u64,
278 sequence_number: u64,
279 },
280 #[serde(rename = "response.file_search_call.searching")]
281 FileSearchCallSearching {
282 item_id: String,
283 output_index: u64,
284 sequence_number: u64,
285 },
286 #[serde(rename = "response.file_search_call.completed")]
287 FileSearchCallCompleted {
288 item_id: String,
289 output_index: u64,
290 sequence_number: u64,
291 },
292
293 #[serde(rename = "response.web_search_call.in_progress")]
294 WebSearchCallInProgress {
295 item_id: String,
296 output_index: u64,
297 sequence_number: u64,
298 },
299 #[serde(rename = "response.web_search_call.searching")]
300 WebSearchCallSearching {
301 item_id: String,
302 output_index: u64,
303 sequence_number: u64,
304 },
305 #[serde(rename = "response.web_search_call.completed")]
306 WebSearchCallCompleted {
307 item_id: String,
308 output_index: u64,
309 sequence_number: u64,
310 },
311
312 #[serde(rename = "response.image_generation_call.in_progress")]
313 ImageGenerationCallInProgress {
314 item_id: String,
315 output_index: u64,
316 sequence_number: u64,
317 },
318 #[serde(rename = "response.image_generation_call.generating")]
319 ImageGenerationCallGenerating {
320 item_id: String,
321 output_index: u64,
322 sequence_number: u64,
323 },
324 #[serde(rename = "response.image_generation_call.partial_image")]
325 ImageGenerationCallPartialImage {
326 item_id: String,
327 output_index: u64,
328 partial_image_b64: String,
329 partial_image_index: u64,
330 sequence_number: u64,
331 },
332 #[serde(rename = "response.image_generation_call.completed")]
333 ImageGenerationCallCompleted {
334 item_id: String,
335 output_index: u64,
336 sequence_number: u64,
337 },
338
339 #[serde(rename = "response.mcp_call_arguments.delta")]
340 McpCallArgumentsDelta {
341 delta: String,
342 item_id: String,
343 output_index: u64,
344 sequence_number: u64,
345 #[serde(default, skip_serializing_if = "Option::is_none")]
346 obfuscation: Option<String>,
347 },
348 #[serde(rename = "response.mcp_call_arguments.done")]
349 McpCallArgumentsDone {
350 arguments: String,
351 item_id: String,
352 output_index: u64,
353 sequence_number: u64,
354 },
355 #[serde(rename = "response.mcp_call.in_progress")]
356 McpCallInProgress {
357 item_id: String,
358 output_index: u64,
359 sequence_number: u64,
360 },
361 #[serde(rename = "response.mcp_call.completed")]
362 McpCallCompleted {
363 item_id: String,
364 output_index: u64,
365 sequence_number: u64,
366 },
367 #[serde(rename = "response.mcp_call.failed")]
368 McpCallFailed {
369 item_id: String,
370 output_index: u64,
371 sequence_number: u64,
372 },
373
374 #[serde(rename = "response.mcp_list_tools.in_progress")]
375 McpListToolsInProgress {
376 item_id: String,
377 output_index: u64,
378 sequence_number: u64,
379 },
380 #[serde(rename = "response.mcp_list_tools.completed")]
381 McpListToolsCompleted {
382 item_id: String,
383 output_index: u64,
384 sequence_number: u64,
385 },
386 #[serde(rename = "response.mcp_list_tools.failed")]
387 McpListToolsFailed {
388 item_id: String,
389 output_index: u64,
390 sequence_number: u64,
391 },
392
393 #[serde(rename = "response.custom_tool_call_input.delta")]
394 CustomToolCallInputDelta {
395 delta: String,
396 item_id: String,
397 output_index: u64,
398 sequence_number: u64,
399 #[serde(default, skip_serializing_if = "Option::is_none")]
400 obfuscation: Option<String>,
401 },
402 #[serde(rename = "response.custom_tool_call_input.done")]
403 CustomToolCallInputDone {
404 input: String,
405 item_id: String,
406 output_index: u64,
407 sequence_number: u64,
408 },
409
410 #[serde(rename = "error")]
411 Error {
412 error: ResponseStreamErrorPayload,
413 sequence_number: u64,
414 },
415
416 #[serde(rename = "keepalive")]
421 Keepalive { sequence_number: u64 },
422}
423
424#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
425#[serde(untagged)]
426pub enum ResponseStreamContentPart {
427 OutputText(ResponseOutputText),
428 Refusal(ResponseOutputRefusal),
429 ReasoningText(ResponseReasoningTextContent),
430}
431
432#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
433pub struct ResponseStreamTokenLogprob {
434 pub token: String,
435 pub logprob: f64,
436 #[serde(default, skip_serializing_if = "Option::is_none")]
437 pub top_logprobs: Option<Vec<ResponseStreamTopLogprob>>,
438}
439
440#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
441pub struct ResponseStreamTopLogprob {
442 #[serde(default, skip_serializing_if = "Option::is_none")]
443 pub token: Option<String>,
444 #[serde(default, skip_serializing_if = "Option::is_none")]
445 pub logprob: Option<f64>,
446}
447
448#[cfg(test)]
449mod tests {
450 use super::*;
451
452 #[test]
456 fn deserializes_codex_keepalive_frame() {
457 let event: ResponseStreamEvent =
458 serde_json::from_str(r#"{"type":"keepalive","sequence_number":5}"#)
459 .expect("keepalive must deserialize");
460 assert!(matches!(
461 event,
462 ResponseStreamEvent::Keepalive { sequence_number: 5 }
463 ));
464 }
465}