1mod text;
6mod tool;
7
8use std::collections::BTreeMap;
9
10use crate::protocol::openai::{
11 Extra, KnownResponseStreamEvent as KnownEvent, ResponseItem, ResponseMessageItem,
12 ResponseObject, ResponseObjectType, ResponseOutputItem, ResponseStatus, ResponseStreamEvent,
13 TypedResponseItem,
14};
15
16use super::{SseDecoder, SseFrame, encode_responses_event};
17use text::ResponsesTextItemState;
18use tool::{ResponsesToolItemState, ResponsesToolKind};
19
20#[derive(Default)]
22pub struct ResponsesStreamNormalizer {
23 decoder: SseDecoder,
24 responses: ResponsesStreamState,
25}
26
27impl ResponsesStreamNormalizer {
28 pub fn new() -> Self {
29 Self::default()
30 }
31
32 pub fn push(&mut self, chunk: &[u8]) -> Vec<u8> {
33 let mut out = Vec::new();
34 for frame in self.decoder.push(chunk) {
35 self.normalize_into(frame, &mut out);
36 }
37 out
38 }
39
40 pub fn finish(&mut self) -> Vec<u8> {
41 let mut out = Vec::new();
42 if let Some(frame) = self.decoder.finish() {
43 self.normalize_into(frame, &mut out);
44 }
45 out
46 }
47
48 fn normalize_into(&mut self, frame: SseFrame, out: &mut Vec<u8>) {
49 if frame.data.trim() == "[DONE]" {
50 out.extend_from_slice(frame.encode().as_bytes());
51 return;
52 }
53 let Ok(event) = serde_json::from_str::<ResponseStreamEvent>(&frame.data) else {
54 out.extend_from_slice(frame.encode().as_bytes());
55 return;
56 };
57 for event in self.responses.push(event) {
58 encode_responses_event(&event, out);
59 }
60 }
61}
62
63#[derive(Default)]
64pub(super) struct ResponsesStreamState {
65 message: ResponsesTextItemState,
66 reasoning: ResponsesTextItemState,
67 tools: BTreeMap<u32, ResponsesToolItemState>,
68 completed: bool,
69}
70
71impl ResponsesStreamState {
72 pub(super) fn push(&mut self, event: ResponseStreamEvent) -> Vec<ResponseStreamEvent> {
73 let mut event = match event {
74 ResponseStreamEvent::Known(known) => known,
75 unknown => return vec![unknown],
76 };
77 let mut out = match &mut event {
78 KnownEvent::ResponseOutputTextDelta {
79 content_index,
80 delta,
81 item_id,
82 output_index,
83 ..
84 } => {
85 let mut out = self.finish_reasoning();
86 out.extend(self.message.ensure(
87 item_id,
88 *output_index,
89 *content_index,
90 text::message_item_added,
91 ));
92 self.message.text.push_str(delta);
93 out
94 }
95 KnownEvent::ResponseReasoningTextDelta {
96 content_index,
97 delta,
98 item_id,
99 output_index,
100 ..
101 } => {
102 let out = self.reasoning.ensure(
103 item_id,
104 *output_index,
105 *content_index,
106 text::reasoning_item_added,
107 );
108 self.reasoning.text.push_str(delta);
109 out
110 }
111 KnownEvent::ResponseFunctionCallArgumentsDelta {
112 delta,
113 item_id,
114 output_index,
115 ..
116 } => {
117 self.note_tool_input_delta(
118 ResponsesToolKind::Function,
119 *output_index,
120 item_id,
121 delta,
122 );
123 Vec::new()
124 }
125 KnownEvent::ResponseCustomToolCallInputDelta {
126 delta,
127 item_id,
128 output_index,
129 ..
130 } => {
131 self.note_tool_input_delta(
132 ResponsesToolKind::Custom,
133 *output_index,
134 item_id,
135 delta,
136 );
137 Vec::new()
138 }
139 KnownEvent::ResponseFunctionCallArgumentsDone {
140 arguments,
141 item_id,
142 name,
143 output_index,
144 ..
145 } => {
146 self.note_tool_input_done(
147 ResponsesToolKind::Function,
148 *output_index,
149 item_id,
150 arguments,
151 Some(name),
152 );
153 Vec::new()
154 }
155 KnownEvent::ResponseCustomToolCallInputDone {
156 input,
157 item_id,
158 output_index,
159 ..
160 } => {
161 self.note_tool_input_done(
162 ResponsesToolKind::Custom,
163 *output_index,
164 item_id,
165 input,
166 None,
167 );
168 Vec::new()
169 }
170 KnownEvent::ResponseCompleted { response, .. } => {
171 let mut out = self.finish_reasoning();
172 out.extend(self.finish_message());
173 out.extend(self.finish_tools());
174 self.patch_completed_output(response);
175 self.completed = true;
176 out
177 }
178 KnownEvent::ResponseOutputItemAdded {
179 item, output_index, ..
180 } => {
181 self.note_item_added(item, *output_index);
182 Vec::new()
183 }
184 KnownEvent::ResponseOutputItemDone {
185 item, output_index, ..
186 } => {
187 self.note_item_done(item, *output_index);
188 Vec::new()
189 }
190 KnownEvent::ResponseOutputTextDone { text, .. } => {
191 self.message.note_done_text(text);
192 Vec::new()
193 }
194 KnownEvent::ResponseReasoningTextDone { text, .. } => {
195 self.reasoning.note_done_text(text);
196 Vec::new()
197 }
198 _ => Vec::new(),
199 };
200 out.push(ResponseStreamEvent::Known(event));
201 out
202 }
203
204 pub(super) fn finish(&mut self) -> Vec<ResponseStreamEvent> {
205 if self.completed {
206 return Vec::new();
207 }
208 let mut out = self.finish_reasoning();
209 out.extend(self.finish_message());
210 if !out.is_empty() {
211 out.extend(self.finish_tools());
212 out.push(known(KnownEvent::ResponseCompleted {
213 response: Box::new(fallback_completed_response()),
214 sequence_number: None,
215 extra: Extra::new(),
216 }));
217 self.completed = true;
218 }
219 out
220 }
221
222 fn finish_message(&mut self) -> Vec<ResponseStreamEvent> {
223 self.message.finish(text::message_done_events)
224 }
225
226 fn finish_reasoning(&mut self) -> Vec<ResponseStreamEvent> {
227 self.reasoning.finish(text::reasoning_done_events)
228 }
229
230 fn note_item_added(&mut self, item: &ResponseOutputItem, output_index: u32) {
231 match &item.0 {
232 ResponseItem::Message(message) if message_has_type(message) => {
233 self.message.note_added(message_id(message), output_index);
234 }
235 ResponseItem::Typed(TypedResponseItem::Reasoning { id, .. }) => {
236 self.reasoning.note_added(id.as_deref(), output_index);
237 }
238 ResponseItem::Typed(typed @ TypedResponseItem::FunctionCall { .. }) => {
239 self.note_tool_added(typed, ResponsesToolKind::Function, output_index);
240 }
241 ResponseItem::Typed(typed @ TypedResponseItem::CustomToolCall { .. }) => {
242 self.note_tool_added(typed, ResponsesToolKind::Custom, output_index);
243 }
244 _ => {}
245 }
246 }
247
248 fn note_item_done(&mut self, item: &ResponseOutputItem, output_index: u32) {
249 match &item.0 {
250 ResponseItem::Message(message) if message_has_type(message) => {
251 self.message
252 .note_item_done(message_id(message), output_index);
253 }
254 ResponseItem::Typed(TypedResponseItem::Reasoning { id, .. }) => {
255 self.reasoning.note_item_done(id.as_deref(), output_index);
256 }
257 ResponseItem::Typed(typed @ TypedResponseItem::FunctionCall { .. }) => {
258 self.note_tool_item_done(typed, ResponsesToolKind::Function, output_index);
259 }
260 ResponseItem::Typed(typed @ TypedResponseItem::CustomToolCall { .. }) => {
261 self.note_tool_item_done(typed, ResponsesToolKind::Custom, output_index);
262 }
263 _ => {}
264 }
265 }
266
267 fn note_tool_added(&mut self, item: &TypedResponseItem, kind: ResponsesToolKind, index: u32) {
268 let state = self.tools.entry(index).or_default();
269 state.note_kind(kind, index);
270 state.note_item(item);
271 }
272
273 fn note_tool_item_done(
274 &mut self,
275 item: &TypedResponseItem,
276 kind: ResponsesToolKind,
277 index: u32,
278 ) {
279 let state = self.tools.entry(index).or_default();
280 state.note_kind(kind, index);
281 state.item_done = true;
282 state.note_item(item);
283 }
284
285 fn note_tool_input_delta(
286 &mut self,
287 kind: ResponsesToolKind,
288 index: u32,
289 item_id: &mut String,
290 delta: &str,
291 ) {
292 let state = self.tools.entry(index).or_default();
293 state.note_kind(kind, index);
294 state.note_event_item_id(item_id);
295 state.rewrite_event_item_id(item_id);
296 state.input.push_str(delta);
297 }
298
299 fn note_tool_input_done(
300 &mut self,
301 kind: ResponsesToolKind,
302 index: u32,
303 item_id: &mut String,
304 input: &str,
305 name: Option<&str>,
306 ) {
307 let state = self.tools.entry(index).or_default();
308 state.note_kind(kind, index);
309 state.note_event_item_id(item_id);
310 state.rewrite_event_item_id(item_id);
311 input.clone_into(&mut state.input);
312 if let Some(name) = name {
313 state.name.get_or_insert_with(|| name.to_owned());
314 }
315 state.input_done = true;
316 }
317
318 fn finish_tools(&mut self) -> Vec<ResponseStreamEvent> {
319 let mut out = Vec::new();
320 for state in self.tools.values_mut() {
321 if !state.can_finish() {
322 continue;
323 }
324 if !state.input_done {
325 out.push(state.input_done_event());
326 state.input_done = true;
327 }
328 if !state.item_done {
329 out.push(state.item_done_event());
330 state.item_done = true;
331 }
332 }
333 out
334 }
335
336 fn patch_completed_output(&self, response: &mut ResponseObject) {
337 if !response.output.is_empty() {
338 return;
339 }
340 let output = self.completed_output_items();
341 if !output.is_empty() {
342 response.output = output;
343 }
344 }
345
346 fn completed_output_items(&self) -> Vec<ResponseOutputItem> {
347 use crate::protocol::openai::ResponseItemLifecycleStatus::Completed;
348 let mut output = Vec::new();
349 if self.reasoning.started {
350 output.push(text::reasoning_item(&self.reasoning, Completed));
351 }
352 if self.message.started {
353 output.push(text::message_item(&self.message, Completed));
354 }
355 output.extend(
356 self.tools
357 .values()
358 .filter(|state| state.can_finish())
359 .map(ResponsesToolItemState::completed_item),
360 );
361 output
362 }
363}
364
365fn known(event: KnownEvent) -> ResponseStreamEvent {
366 ResponseStreamEvent::Known(event)
367}
368
369fn message_has_type(message: &ResponseMessageItem) -> bool {
370 match message {
371 ResponseMessageItem::Output(_) => true,
372 ResponseMessageItem::Input(input) => input.type_.is_some(),
373 ResponseMessageItem::EasyInput(easy) => easy.type_.is_some(),
374 }
375}
376
377fn message_id(message: &ResponseMessageItem) -> Option<&str> {
378 match message {
379 ResponseMessageItem::Output(output) => Some(&output.id),
380 ResponseMessageItem::Input(input) => input.id.as_deref(),
381 ResponseMessageItem::EasyInput(_) => None,
382 }
383}
384
385fn fallback_completed_response() -> ResponseObject {
387 ResponseObject {
388 id: "resp_0".to_owned(),
389 created_at: 0,
390 background: None,
391 completed_at: Some(0),
392 conversation: None,
393 error: None,
394 incomplete_details: None,
395 instructions: None,
396 max_output_tokens: None,
397 max_tool_calls: None,
398 metadata: None,
399 model: None,
400 moderation: None,
401 multi_agent: None,
402 object: ResponseObjectType::Response,
403 output: Vec::new(),
404 output_text: None,
405 parallel_tool_calls: None,
406 prompt: None,
407 prompt_cache_key: None,
408 prompt_cache_options: None,
409 prompt_cache_retention: None,
410 previous_response_id: None,
411 reasoning: None,
412 safety_identifier: None,
413 service_tier: None,
414 status: Some(ResponseStatus::Completed),
415 store: None,
416 temperature: None,
417 text: None,
418 tool_choice: None,
419 tools: None,
420 top_logprobs: None,
421 top_p: None,
422 truncation: None,
423 usage: None,
424 user: None,
425 extra: Extra::new(),
426 }
427}