1use serde::Deserialize;
18
19use crate::responses::{Response, ResponseOutputItem};
20
21#[derive(Debug, Deserialize, Clone)]
27#[serde(tag = "type")]
28pub enum ResponseStreamEvent {
29 #[serde(rename = "response.created")]
31 ResponseCreated {
32 #[serde(default)]
33 sequence_number: Option<u64>,
34 response: Response,
35 },
36
37 #[serde(rename = "response.in_progress")]
39 ResponseInProgress {
40 #[serde(default)]
41 sequence_number: Option<u64>,
42 response: Response,
43 },
44
45 #[serde(rename = "response.completed")]
48 ResponseCompleted {
49 #[serde(default)]
50 sequence_number: Option<u64>,
51 response: Response,
52 },
53
54 #[serde(rename = "response.incomplete")]
57 ResponseIncomplete {
58 #[serde(default)]
59 sequence_number: Option<u64>,
60 response: Response,
61 },
62
63 #[serde(rename = "response.failed")]
66 ResponseFailed {
67 #[serde(default)]
68 sequence_number: Option<u64>,
69 response: Response,
70 },
71
72 #[serde(rename = "error")]
74 Error {
75 #[serde(default)]
76 sequence_number: Option<u64>,
77 #[serde(default)]
79 message: Option<String>,
80 #[serde(default)]
82 code: Option<String>,
83 #[serde(default)]
85 param: Option<String>,
86 },
87
88 #[serde(rename = "response.output_item.added")]
90 OutputItemAdded {
91 #[serde(default)]
92 sequence_number: Option<u64>,
93 #[serde(default)]
95 output_index: Option<u32>,
96 item: ResponseOutputItem,
98 },
99
100 #[serde(rename = "response.output_item.done")]
102 OutputItemDone {
103 #[serde(default)]
104 sequence_number: Option<u64>,
105 #[serde(default)]
106 output_index: Option<u32>,
107 item: ResponseOutputItem,
109 },
110
111 #[serde(rename = "response.content_part.added")]
113 ContentPartAdded {
114 #[serde(default)]
115 sequence_number: Option<u64>,
116 #[serde(default)]
118 item_id: Option<String>,
119 #[serde(default)]
120 output_index: Option<u32>,
121 #[serde(default)]
123 content_index: Option<u32>,
124 part: EventContentPart,
126 },
127
128 #[serde(rename = "response.content_part.done")]
130 ContentPartDone {
131 #[serde(default)]
132 sequence_number: Option<u64>,
133 #[serde(default)]
134 item_id: Option<String>,
135 #[serde(default)]
136 output_index: Option<u32>,
137 #[serde(default)]
138 content_index: Option<u32>,
139 part: EventContentPart,
141 },
142
143 #[serde(rename = "response.output_text.delta")]
145 OutputTextDelta {
146 #[serde(default)]
147 sequence_number: Option<u64>,
148 #[serde(default)]
149 item_id: Option<String>,
150 #[serde(default)]
151 output_index: Option<u32>,
152 #[serde(default)]
153 content_index: Option<u32>,
154 delta: String,
156 },
157
158 #[serde(rename = "response.output_text.done")]
160 OutputTextDone {
161 #[serde(default)]
162 sequence_number: Option<u64>,
163 #[serde(default)]
164 item_id: Option<String>,
165 #[serde(default)]
166 output_index: Option<u32>,
167 #[serde(default)]
168 content_index: Option<u32>,
169 text: String,
171 },
172
173 #[serde(rename = "response.reasoning_text.delta")]
175 ReasoningTextDelta {
176 #[serde(default)]
177 sequence_number: Option<u64>,
178 #[serde(default)]
179 item_id: Option<String>,
180 #[serde(default)]
181 output_index: Option<u32>,
182 #[serde(default)]
183 content_index: Option<u32>,
184 delta: String,
186 },
187
188 #[serde(rename = "response.reasoning_text.done")]
190 ReasoningTextDone {
191 #[serde(default)]
192 sequence_number: Option<u64>,
193 #[serde(default)]
194 item_id: Option<String>,
195 #[serde(default)]
196 output_index: Option<u32>,
197 #[serde(default)]
198 content_index: Option<u32>,
199 text: String,
201 },
202
203 #[serde(rename = "response.function_call_arguments.delta")]
205 FunctionCallArgumentsDelta {
206 #[serde(default)]
207 sequence_number: Option<u64>,
208 #[serde(default)]
209 item_id: Option<String>,
210 #[serde(default)]
211 output_index: Option<u32>,
212 delta: String,
214 },
215
216 #[serde(rename = "response.function_call_arguments.done")]
218 FunctionCallArgumentsDone {
219 #[serde(default)]
220 sequence_number: Option<u64>,
221 #[serde(default)]
222 item_id: Option<String>,
223 #[serde(default)]
224 output_index: Option<u32>,
225 arguments: String,
227 },
228
229 #[serde(rename = "response.custom_tool_call_input.delta")]
232 CustomToolCallInputDelta {
233 #[serde(default)]
234 sequence_number: Option<u64>,
235 #[serde(default)]
236 item_id: Option<String>,
237 #[serde(default)]
238 output_index: Option<u32>,
239 delta: String,
241 },
242
243 #[serde(rename = "response.custom_tool_call_input.done")]
245 CustomToolCallInputDone {
246 #[serde(default)]
247 sequence_number: Option<u64>,
248 #[serde(default)]
249 item_id: Option<String>,
250 #[serde(default)]
251 output_index: Option<u32>,
252 input: String,
254 },
255
256 #[serde(rename = "response.web_search_call.in_progress")]
258 WebSearchCallInProgress {
259 #[serde(default)]
260 sequence_number: Option<u64>,
261 #[serde(default)]
262 item_id: Option<String>,
263 #[serde(default)]
264 output_index: Option<u32>,
265 },
266
267 #[serde(rename = "response.web_search_call.searching")]
269 WebSearchCallSearching {
270 #[serde(default)]
271 sequence_number: Option<u64>,
272 #[serde(default)]
273 item_id: Option<String>,
274 #[serde(default)]
275 output_index: Option<u32>,
276 },
277
278 #[serde(rename = "response.web_search_call.completed")]
280 WebSearchCallCompleted {
281 #[serde(default)]
282 sequence_number: Option<u64>,
283 #[serde(default)]
284 item_id: Option<String>,
285 #[serde(default)]
286 output_index: Option<u32>,
287 },
288
289 #[cfg(feature = "qwen")]
291 #[serde(rename = "response.mcp_call_arguments.delta")]
292 McpCallArgumentsDelta {
293 #[serde(default)]
294 sequence_number: Option<u64>,
295 #[serde(default)]
296 item_id: Option<String>,
297 #[serde(default)]
298 output_index: Option<u32>,
299 delta: String,
301 },
302
303 #[cfg(feature = "qwen")]
305 #[serde(rename = "response.mcp_call_arguments.done")]
306 McpCallArgumentsDone {
307 #[serde(default)]
308 sequence_number: Option<u64>,
309 #[serde(default)]
310 item_id: Option<String>,
311 #[serde(default)]
312 output_index: Option<u32>,
313 arguments: String,
315 },
316
317 #[cfg(feature = "qwen")]
319 #[serde(rename = "response.mcp_call.completed")]
320 McpCallCompleted {
321 #[serde(default)]
322 sequence_number: Option<u64>,
323 #[serde(default)]
324 item_id: Option<String>,
325 #[serde(default)]
326 output_index: Option<u32>,
327 },
328
329 #[cfg(feature = "qwen")]
331 #[serde(rename = "response.file_search_call.in_progress")]
332 FileSearchCallInProgress {
333 #[serde(default)]
334 sequence_number: Option<u64>,
335 #[serde(default)]
336 item_id: Option<String>,
337 #[serde(default)]
338 output_index: Option<u32>,
339 },
340
341 #[cfg(feature = "qwen")]
343 #[serde(rename = "response.file_search_call.searching")]
344 FileSearchCallSearching {
345 #[serde(default)]
346 sequence_number: Option<u64>,
347 #[serde(default)]
348 item_id: Option<String>,
349 #[serde(default)]
350 output_index: Option<u32>,
351 },
352
353 #[cfg(feature = "qwen")]
355 #[serde(rename = "response.file_search_call.completed")]
356 FileSearchCallCompleted {
357 #[serde(default)]
358 sequence_number: Option<u64>,
359 #[serde(default)]
360 item_id: Option<String>,
361 #[serde(default)]
362 output_index: Option<u32>,
363 },
364
365 #[serde(other)]
367 Other,
368}
369
370impl ResponseStreamEvent {
371 pub fn final_response(&self) -> Option<&Response> {
375 match self {
376 Self::ResponseCompleted { response, .. }
377 | Self::ResponseIncomplete { response, .. }
378 | Self::ResponseFailed { response, .. } => Some(response),
379 _ => None,
380 }
381 }
382}
383
384#[derive(Debug, Deserialize, Clone)]
387#[serde(tag = "type", rename_all = "snake_case")]
388pub enum EventContentPart {
389 OutputText {
391 #[serde(default)]
393 text: Option<String>,
394 },
395 ReasoningText {
397 #[serde(default)]
399 text: Option<String>,
400 },
401 Refusal {
403 #[serde(default)]
405 refusal: Option<String>,
406 },
407 #[serde(other)]
409 Other,
410}
411
412crate::impl_from_str!(ResponseStreamEvent);
413
414#[cfg(test)]
415mod tests {
416 use std::str::FromStr;
420
421 use super::*;
422 use crate::responses::{ResponseObject, ResponseOutputItem};
423
424 #[test]
427 fn parses_documented_stream_events() {
428 let created = ResponseStreamEvent::from_str(
429 r#"{"type": "response.created", "sequence_number": 0, "response": {"id": "resp_1", "object": "response", "created_at": 0, "status": "in_progress", "model": "deepseek-v4-flash", "output": []}}"#,
430 ).unwrap();
431 assert!(matches!(
432 created,
433 ResponseStreamEvent::ResponseCreated { .. }
434 ));
435
436 let delta = ResponseStreamEvent::from_str(
437 r#"{"type": "response.reasoning_text.delta", "sequence_number": 4, "item_id": "rs_1", "output_index": 0, "content_index": 0, "delta": "The user"}"#,
438 ).unwrap();
439 match delta {
440 ResponseStreamEvent::ReasoningTextDelta { delta, item_id, .. } => {
441 assert_eq!(delta, "The user");
442 assert_eq!(item_id.as_deref(), Some("rs_1"));
443 }
444 other => panic!("unexpected event: {other:?}"),
445 }
446
447 let text_delta = ResponseStreamEvent::from_str(
448 r#"{"type": "response.output_text.delta", "sequence_number": 11, "item_id": "msg_1", "output_index": 1, "content_index": 0, "delta": "Hello"}"#,
449 ).unwrap();
450 assert!(matches!(
451 text_delta,
452 ResponseStreamEvent::OutputTextDelta { .. }
453 ));
454 }
455
456 #[test]
457 fn parses_completed_event_and_extracts_response() {
458 let event = ResponseStreamEvent::from_str(
459 r#"{"type": "response.completed", "sequence_number": 20, "response": {"id": "resp_1", "object": "response", "created_at": 0, "status": "completed", "model": "deepseek-v4-flash", "output": [{"type": "message", "id": "msg_1", "status": "completed", "role": "assistant", "content": [{"type": "output_text", "text": "Hi!", "annotations": []}]}], "usage": {"input_tokens": 1, "output_tokens": 2, "total_tokens": 3}}}"#,
460 ).unwrap();
461
462 let response = event.final_response().expect("terminal event");
463 assert_eq!(response.object, ResponseObject::Response);
464 assert!(response.is_completed());
465 assert_eq!(response.output_text(), "Hi!");
466 assert!(matches!(
467 response.output.first(),
468 Some(ResponseOutputItem::Message(_))
469 ));
470 }
471
472 #[test]
473 fn unknown_event_type_is_preserved() {
474 let event = ResponseStreamEvent::from_str(
475 r#"{"type": "response.some_future_event", "sequence_number": 3}"#,
476 )
477 .unwrap();
478 assert!(matches!(event, ResponseStreamEvent::Other));
479 }
480}