Skip to main content

claude_codex/providers/codex/translate/
live_stream.rs

1use std::collections::HashMap;
2
3use crate::anthropic::sse::encode_sse_event;
4use crate::traffic::TrafficCapture;
5
6use super::read_rewrite::sanitize_read_args;
7use super::reducer::{
8    CodexUsage, STOP_END_TURN, STOP_MAX_TOKENS, STOP_TOOL_USE, map_codex_usage_to_anthropic,
9};
10
11const BUFFERED_READ_REPAIR_TRAILING_WHITESPACE_BYTES: usize = 1_024;
12const BUFFERED_TOOL_MAX_ARGS_BYTES: usize = 5_000_000;
13
14enum LiveBlock {
15    Text {
16        index: usize,
17        text: String,
18        deferred: bool,
19    },
20    Tool {
21        index: usize,
22        call_id: String,
23        name: String,
24        args_accum: String,
25        had_delta: bool,
26        buffer_until_done: bool,
27        emitted_args: bool,
28    },
29}
30
31struct LiveWebSearch {
32    index: usize,
33    result_index: usize,
34    id: String,
35    query: String,
36}
37
38#[derive(Clone)]
39struct LiveWebSearchResult {
40    title: String,
41    url: String,
42}
43
44pub struct LiveStreamTranslator {
45    message_id: String,
46    model: String,
47    message_started: bool,
48    blocks_by_output_index: HashMap<usize, LiveBlock>,
49    item_id_to_output_index: HashMap<String, usize>,
50    anthropic_index: usize,
51    thinking_index: Option<usize>,
52    saw_tool_use: bool,
53    web_search_requests: usize,
54    web_searches: Vec<LiveWebSearch>,
55    web_search_results: Vec<LiveWebSearchResult>,
56    deferred_text: Vec<(usize, String)>,
57    finished: bool,
58}
59
60impl LiveStreamTranslator {
61    pub fn new(message_id: impl Into<String>, model: impl Into<String>) -> Self {
62        Self {
63            message_id: message_id.into(),
64            model: model.into(),
65            message_started: false,
66            blocks_by_output_index: HashMap::new(),
67            item_id_to_output_index: HashMap::new(),
68            anthropic_index: 0,
69            thinking_index: None,
70            saw_tool_use: false,
71            web_search_requests: 0,
72            web_searches: Vec::new(),
73            web_search_results: Vec::new(),
74            deferred_text: Vec::new(),
75            finished: false,
76        }
77    }
78
79    pub fn accept(
80        &mut self,
81        payload: &serde_json::Value,
82        traffic: Option<&TrafficCapture>,
83    ) -> Result<Vec<u8>, String> {
84        if self.finished {
85            return Ok(Vec::new());
86        }
87
88        let kind = payload.get("type").and_then(|v| v.as_str()).unwrap_or("");
89        let mut out = Vec::new();
90
91        match kind {
92            "codex.rate_limits" => {
93                if payload
94                    .get("rate_limits")
95                    .and_then(|r| r.get("limit_reached"))
96                    .and_then(|v| v.as_bool())
97                    == Some(true)
98                {
99                    return Err("rate limit reached".to_string());
100                }
101            }
102            "keepalive" => {}
103            "response.failed" | "response.error" | "error" => {
104                return Err(error_message(payload));
105            }
106            "response.web_search_call.in_progress"
107            | "response.web_search_call.searching"
108            | "response.web_search_call.completed" => {}
109            "response.output_item.added" => {
110                self.output_item_added(payload, traffic, &mut out);
111            }
112            "response.reasoning_summary_part.added" => {
113                if let Some(index) = self.thinking_index {
114                    self.emit(
115                        traffic,
116                        &mut out,
117                        "content_block_delta",
118                        &serde_json::json!({
119                            "type": "content_block_delta",
120                            "index": index,
121                            "delta": {"type": "thinking_delta", "thinking": "\n\n"}
122                        }),
123                    );
124                }
125            }
126            "response.reasoning_summary_text.delta" => {
127                self.reasoning_delta(payload, traffic, &mut out);
128            }
129            "response.output_text.delta" => {
130                self.text_delta(payload, traffic, &mut out);
131            }
132            "response.output_text.annotation.added" => {
133                self.web_search_annotation(payload);
134            }
135            "response.function_call_arguments.delta" => {
136                self.tool_delta(payload, traffic, &mut out)?;
137            }
138            "response.function_call_arguments.done" => {
139                self.tool_arguments_done(payload);
140            }
141            "response.output_item.done" => {
142                self.output_item_done(payload, traffic, &mut out);
143            }
144            "response.completed" | "response.incomplete" | "response.done" => {
145                self.finish(payload, traffic, &mut out);
146            }
147            _ => {}
148        }
149
150        Ok(out)
151    }
152
153    pub fn is_finished(&self) -> bool {
154        self.finished
155    }
156
157    pub fn finish_after_closed_completed_tool_call(
158        &mut self,
159        traffic: Option<&TrafficCapture>,
160    ) -> Vec<u8> {
161        let mut out = Vec::new();
162        if self.finished || !self.saw_tool_use || !self.blocks_by_output_index.is_empty() {
163            return out;
164        }
165        self.close_thinking(traffic, &mut out);
166        self.ensure_message_start(traffic, &mut out);
167        self.emit_finish(STOP_TOOL_USE, None, traffic, &mut out);
168        out
169    }
170
171    pub fn error_chunk(
172        &mut self,
173        message: &str,
174        error_type: &str,
175        traffic: Option<&TrafficCapture>,
176    ) -> Vec<u8> {
177        let mut out = Vec::new();
178        if self.finished {
179            return out;
180        }
181        self.close_open_blocks(traffic, &mut out);
182        self.ensure_message_start(traffic, &mut out);
183        self.emit(
184            traffic,
185            &mut out,
186            "error",
187            &serde_json::json!({
188                "type": "error",
189                "error": {
190                    "type": error_type,
191                    "message": message,
192                }
193            }),
194        );
195        self.finished = true;
196        out
197    }
198
199    fn ensure_message_start(&mut self, traffic: Option<&TrafficCapture>, out: &mut Vec<u8>) {
200        if self.message_started {
201            return;
202        }
203        self.message_started = true;
204        self.emit(
205            traffic,
206            out,
207            "message_start",
208            &serde_json::json!({
209                "type": "message_start",
210                "message": {
211                    "id": self.message_id,
212                    "type": "message",
213                    "role": "assistant",
214                    "model": self.model,
215                    "content": [],
216                    "stop_reason": null,
217                    "stop_sequence": null,
218                    "usage": {
219                        "input_tokens": 0,
220                        "output_tokens": 0
221                    }
222                }
223            }),
224        );
225    }
226
227    fn emit(
228        &self,
229        traffic: Option<&TrafficCapture>,
230        out: &mut Vec<u8>,
231        event: &str,
232        data: &serde_json::Value,
233    ) {
234        if let Some(traffic) = traffic {
235            traffic.write_json_event(
236                "050-downstream-event",
237                &serde_json::json!({
238                    "event": event,
239                    "data": data,
240                }),
241            );
242        }
243        out.extend_from_slice(&encode_sse_event(Some(event), &data.to_string()));
244    }
245
246    fn output_item_added(
247        &mut self,
248        payload: &serde_json::Value,
249        traffic: Option<&TrafficCapture>,
250        out: &mut Vec<u8>,
251    ) {
252        let Some(item) = payload.get("item") else {
253            return;
254        };
255        let output_index = output_index(payload);
256        let item_type = item.get("type").and_then(|v| v.as_str()).unwrap_or("");
257
258        match item_type {
259            "message" => {
260                self.close_thinking(traffic, out);
261                let index = self.anthropic_index;
262                self.anthropic_index += 1;
263                if let Some(id) = item.get("id").and_then(|v| v.as_str()) {
264                    self.item_id_to_output_index
265                        .insert(id.to_string(), output_index);
266                }
267                let deferred = !self.web_searches.is_empty();
268                self.blocks_by_output_index.insert(
269                    output_index,
270                    LiveBlock::Text {
271                        index,
272                        text: String::new(),
273                        deferred,
274                    },
275                );
276                if !deferred {
277                    self.ensure_message_start(traffic, out);
278                    self.emit(
279                        traffic,
280                        out,
281                        "content_block_start",
282                        &serde_json::json!({
283                            "type": "content_block_start",
284                            "index": index,
285                            "content_block": {"type": "text", "text": ""}
286                        }),
287                    );
288                }
289            }
290            "function_call" => {
291                self.close_thinking(traffic, out);
292                self.saw_tool_use = true;
293                let index = self.anthropic_index;
294                self.anthropic_index += 1;
295                let call_id = item
296                    .get("call_id")
297                    .and_then(|v| v.as_str())
298                    .unwrap_or("")
299                    .to_string();
300                let name = item
301                    .get("name")
302                    .and_then(|v| v.as_str())
303                    .unwrap_or("")
304                    .to_string();
305                self.blocks_by_output_index.insert(
306                    output_index,
307                    LiveBlock::Tool {
308                        index,
309                        call_id: call_id.clone(),
310                        name: name.clone(),
311                        args_accum: String::new(),
312                        had_delta: false,
313                        buffer_until_done: name == "Read",
314                        emitted_args: false,
315                    },
316                );
317                self.ensure_message_start(traffic, out);
318                self.emit(
319                    traffic,
320                    out,
321                    "content_block_start",
322                    &serde_json::json!({
323                        "type": "content_block_start",
324                        "index": index,
325                        "content_block": {
326                            "type": "tool_use",
327                            "id": call_id,
328                            "name": name,
329                            "input": {}
330                        }
331                    }),
332                );
333            }
334            "web_search_call" => {
335                self.web_search_requests += 1;
336            }
337            _ => {}
338        }
339    }
340
341    fn reasoning_delta(
342        &mut self,
343        payload: &serde_json::Value,
344        traffic: Option<&TrafficCapture>,
345        out: &mut Vec<u8>,
346    ) {
347        let delta = payload.get("delta").and_then(|v| v.as_str()).unwrap_or("");
348        if delta.is_empty() {
349            return;
350        }
351        if self.thinking_index.is_none() {
352            let index = self.anthropic_index;
353            self.anthropic_index += 1;
354            self.thinking_index = Some(index);
355            self.ensure_message_start(traffic, out);
356            self.emit(
357                traffic,
358                out,
359                "content_block_start",
360                &serde_json::json!({
361                    "type": "content_block_start",
362                    "index": index,
363                    "content_block": {"type": "thinking", "thinking": "", "signature": ""}
364                }),
365            );
366        }
367        let index = self.thinking_index.unwrap();
368        self.emit(
369            traffic,
370            out,
371            "content_block_delta",
372            &serde_json::json!({
373                "type": "content_block_delta",
374                "index": index,
375                "delta": {"type": "thinking_delta", "thinking": delta}
376            }),
377        );
378    }
379
380    fn text_delta(
381        &mut self,
382        payload: &serde_json::Value,
383        traffic: Option<&TrafficCapture>,
384        out: &mut Vec<u8>,
385    ) {
386        self.close_thinking(traffic, out);
387        let delta = payload.get("delta").and_then(|v| v.as_str()).unwrap_or("");
388        if delta.is_empty() {
389            return;
390        }
391
392        let output_index = payload
393            .get("output_index")
394            .and_then(|v| v.as_u64())
395            .map(|v| v as usize)
396            .or_else(|| {
397                payload
398                    .get("item_id")
399                    .and_then(|v| v.as_str())
400                    .and_then(|id| self.item_id_to_output_index.get(id).copied())
401            })
402            .unwrap_or(0);
403
404        if !self.blocks_by_output_index.contains_key(&output_index) {
405            let index = self.anthropic_index;
406            self.anthropic_index += 1;
407            let deferred = !self.web_searches.is_empty();
408            self.blocks_by_output_index.insert(
409                output_index,
410                LiveBlock::Text {
411                    index,
412                    text: String::new(),
413                    deferred,
414                },
415            );
416            if !deferred {
417                self.ensure_message_start(traffic, out);
418                self.emit(
419                    traffic,
420                    out,
421                    "content_block_start",
422                    &serde_json::json!({
423                        "type": "content_block_start",
424                        "index": index,
425                        "content_block": {"type": "text", "text": ""}
426                    }),
427                );
428            }
429        }
430
431        let Some(LiveBlock::Text {
432            index,
433            text,
434            deferred,
435        }) = self.blocks_by_output_index.get_mut(&output_index)
436        else {
437            return;
438        };
439        text.push_str(delta);
440        if *deferred {
441            return;
442        }
443        let index = *index;
444        self.emit(
445            traffic,
446            out,
447            "content_block_delta",
448            &serde_json::json!({
449                "type": "content_block_delta",
450                "index": index,
451                "delta": {"type": "text_delta", "text": delta}
452            }),
453        );
454    }
455
456    fn tool_delta(
457        &mut self,
458        payload: &serde_json::Value,
459        traffic: Option<&TrafficCapture>,
460        out: &mut Vec<u8>,
461    ) -> Result<(), String> {
462        let Some(output_index) = payload
463            .get("output_index")
464            .and_then(|v| v.as_u64())
465            .map(|v| v as usize)
466        else {
467            return Ok(());
468        };
469        let delta = payload.get("delta").and_then(|v| v.as_str()).unwrap_or("");
470        if delta.is_empty() {
471            return Ok(());
472        }
473        let mut repaired_read: Option<(usize, String)> = None;
474        let Some(LiveBlock::Tool {
475            index,
476            call_id,
477            name,
478            args_accum,
479            had_delta,
480            buffer_until_done,
481            emitted_args,
482            ..
483        }) = self.blocks_by_output_index.get_mut(&output_index)
484        else {
485            return Ok(());
486        };
487        args_accum.push_str(delta);
488        *had_delta = true;
489        if *buffer_until_done {
490            if args_accum.len() > BUFFERED_TOOL_MAX_ARGS_BYTES {
491                return Err(format!(
492                    "Buffered {name} tool arguments exceeded safe limits"
493                ));
494            }
495            if let Some(repaired) =
496                repair_whitespace_stalled_read_args(name, args_accum, Some(call_id.as_str()))
497            {
498                *args_accum = repaired.clone();
499                *emitted_args = true;
500                repaired_read = Some((*index, repaired));
501            }
502        } else {
503            *emitted_args = true;
504            let index = *index;
505            self.emit(
506                traffic,
507                out,
508                "content_block_delta",
509                &serde_json::json!({
510                    "type": "content_block_delta",
511                    "index": index,
512                    "delta": {
513                        "type": "input_json_delta",
514                        "partial_json": delta
515                    }
516                }),
517            );
518            return Ok(());
519        }
520        if let Some((index, repaired)) = repaired_read {
521            self.blocks_by_output_index.remove(&output_index);
522            self.emit(
523                traffic,
524                out,
525                "content_block_delta",
526                &serde_json::json!({
527                    "type": "content_block_delta",
528                    "index": index,
529                    "delta": {
530                        "type": "input_json_delta",
531                        "partial_json": repaired
532                    }
533                }),
534            );
535            self.emit(
536                traffic,
537                out,
538                "content_block_stop",
539                &serde_json::json!({
540                    "type": "content_block_stop",
541                    "index": index,
542                }),
543            );
544            self.ensure_message_start(traffic, out);
545            self.emit_finish(STOP_TOOL_USE, None, traffic, out);
546        }
547        Ok(())
548    }
549
550    fn tool_arguments_done(&mut self, payload: &serde_json::Value) {
551        let Some(output_index) = payload
552            .get("output_index")
553            .and_then(|v| v.as_u64())
554            .map(|v| v as usize)
555        else {
556            return;
557        };
558        let Some(args) = payload.get("arguments").and_then(|v| v.as_str()) else {
559            return;
560        };
561        let Some(LiveBlock::Tool { args_accum, .. }) =
562            self.blocks_by_output_index.get_mut(&output_index)
563        else {
564            return;
565        };
566        if args_accum.is_empty() {
567            *args_accum = args.to_string();
568        }
569    }
570
571    fn output_item_done(
572        &mut self,
573        payload: &serde_json::Value,
574        traffic: Option<&TrafficCapture>,
575        out: &mut Vec<u8>,
576    ) {
577        let output_index = output_index(payload);
578        if payload
579            .get("item")
580            .and_then(|item| item.get("type"))
581            .and_then(|v| v.as_str())
582            == Some("reasoning")
583        {
584            self.close_thinking(traffic, out);
585            return;
586        }
587
588        if payload
589            .get("item")
590            .and_then(|item| item.get("type"))
591            .and_then(|v| v.as_str())
592            == Some("web_search_call")
593        {
594            self.close_thinking(traffic, out);
595            let item = &payload["item"];
596            let index = self.anthropic_index;
597            self.anthropic_index += 1;
598            let result_index = self.anthropic_index;
599            self.anthropic_index += 1;
600            let raw_id = item.get("id").and_then(|v| v.as_str()).unwrap_or("");
601            self.web_searches.push(LiveWebSearch {
602                index,
603                result_index,
604                id: super::web_search_compat::server_tool_use_id_from_codex_web_search_id(raw_id),
605                query: web_search_query(item),
606            });
607            return;
608        }
609
610        let Some(mut state) = self.blocks_by_output_index.remove(&output_index) else {
611            return;
612        };
613
614        match &mut state {
615            LiveBlock::Text {
616                index,
617                text,
618                deferred,
619            } => {
620                if *deferred {
621                    self.deferred_text.push((*index, std::mem::take(text)));
622                } else {
623                    self.emit(
624                        traffic,
625                        out,
626                        "content_block_stop",
627                        &serde_json::json!({
628                            "type": "content_block_stop",
629                            "index": index,
630                        }),
631                    );
632                }
633            }
634            LiveBlock::Tool {
635                index,
636                name,
637                call_id,
638                args_accum,
639                had_delta,
640                buffer_until_done,
641                emitted_args,
642                ..
643            } => {
644                if let Some(final_args) = payload
645                    .get("item")
646                    .and_then(|item| item.get("arguments"))
647                    .and_then(|v| v.as_str())
648                    .filter(|s| !s.is_empty())
649                    && (args_accum.is_empty() || (!*had_delta && !*emitted_args))
650                {
651                    *args_accum = final_args.to_string();
652                }
653                if !args_accum.is_empty() {
654                    *args_accum = sanitize_read_args(name, args_accum, Some(call_id.as_str()));
655                    if *buffer_until_done || !*emitted_args {
656                        *emitted_args = true;
657                        self.emit(
658                            traffic,
659                            out,
660                            "content_block_delta",
661                            &serde_json::json!({
662                                "type": "content_block_delta",
663                                "index": index,
664                                "delta": {
665                                    "type": "input_json_delta",
666                                    "partial_json": args_accum
667                                }
668                            }),
669                        );
670                    }
671                }
672                self.emit(
673                    traffic,
674                    out,
675                    "content_block_stop",
676                    &serde_json::json!({
677                        "type": "content_block_stop",
678                        "index": index,
679                    }),
680                );
681            }
682        }
683    }
684
685    fn web_search_annotation(&mut self, payload: &serde_json::Value) {
686        let Some(annotation) = payload.get("annotation") else {
687            return;
688        };
689        if annotation.get("type").and_then(|v| v.as_str()) != Some("url_citation") {
690            return;
691        }
692        let Some(url) = annotation.get("url").and_then(|v| v.as_str()) else {
693            return;
694        };
695        if self
696            .web_search_results
697            .iter()
698            .any(|result| result.url == url)
699        {
700            return;
701        }
702        let title = annotation
703            .get("title")
704            .and_then(|v| v.as_str())
705            .filter(|title| !title.is_empty())
706            .unwrap_or(url);
707        self.web_search_results.push(LiveWebSearchResult {
708            title: title.to_string(),
709            url: url.to_string(),
710        });
711    }
712
713    fn emit_web_searches(&mut self, traffic: Option<&TrafficCapture>, out: &mut Vec<u8>) {
714        let searches = std::mem::take(&mut self.web_searches);
715        for search in searches {
716            self.ensure_message_start(traffic, out);
717            self.emit(
718                traffic,
719                out,
720                "content_block_start",
721                &serde_json::json!({
722                    "type": "content_block_start",
723                    "index": search.index,
724                    "content_block": {
725                        "type": "server_tool_use",
726                        "id": search.id,
727                        "name": "web_search",
728                        "input": {}
729                    }
730                }),
731            );
732            self.emit(
733                traffic,
734                out,
735                "content_block_delta",
736                &serde_json::json!({
737                    "type": "content_block_delta",
738                    "index": search.index,
739                    "delta": {
740                        "type": "input_json_delta",
741                        "partial_json": serde_json::to_string(&serde_json::json!({"query": search.query})).unwrap_or_default()
742                    }
743                }),
744            );
745            self.emit(
746                traffic,
747                out,
748                "content_block_stop",
749                &serde_json::json!({"type": "content_block_stop", "index": search.index}),
750            );
751            let results: Vec<_> = self
752                .web_search_results
753                .iter()
754                .map(|result| {
755                    serde_json::json!({
756                        "type": "web_search_result",
757                        "title": result.title,
758                        "url": result.url,
759                    })
760                })
761                .collect();
762            self.emit(
763                traffic,
764                out,
765                "content_block_start",
766                &serde_json::json!({
767                    "type": "content_block_start",
768                    "index": search.result_index,
769                    "content_block": {
770                        "type": "web_search_tool_result",
771                        "tool_use_id": search.id,
772                        "content": results
773                    }
774                }),
775            );
776            self.emit(
777                traffic,
778                out,
779                "content_block_stop",
780                &serde_json::json!({"type": "content_block_stop", "index": search.result_index}),
781            );
782        }
783
784        for (index, text) in std::mem::take(&mut self.deferred_text) {
785            self.emit(
786                traffic,
787                out,
788                "content_block_start",
789                &serde_json::json!({
790                    "type": "content_block_start",
791                    "index": index,
792                    "content_block": {"type": "text", "text": ""}
793                }),
794            );
795            if !text.is_empty() {
796                self.emit(
797                    traffic,
798                    out,
799                    "content_block_delta",
800                    &serde_json::json!({
801                        "type": "content_block_delta",
802                        "index": index,
803                        "delta": {"type": "text_delta", "text": text}
804                    }),
805                );
806            }
807            self.emit(
808                traffic,
809                out,
810                "content_block_stop",
811                &serde_json::json!({"type": "content_block_stop", "index": index}),
812            );
813        }
814    }
815
816    fn finish(
817        &mut self,
818        payload: &serde_json::Value,
819        traffic: Option<&TrafficCapture>,
820        out: &mut Vec<u8>,
821    ) {
822        self.close_thinking(traffic, out);
823        self.close_open_blocks(traffic, out);
824        self.emit_web_searches(traffic, out);
825        self.ensure_message_start(traffic, out);
826        let usage = payload.get("response").map(parse_codex_usage);
827        let incomplete = response_is_incomplete(payload);
828        let stop_reason = if incomplete {
829            STOP_MAX_TOKENS
830        } else if self.saw_tool_use {
831            STOP_TOOL_USE
832        } else {
833            STOP_END_TURN
834        };
835        self.emit_finish(stop_reason, usage, traffic, out);
836    }
837
838    fn emit_finish(
839        &mut self,
840        stop_reason: &str,
841        usage: Option<CodexUsage>,
842        traffic: Option<&TrafficCapture>,
843        out: &mut Vec<u8>,
844    ) {
845        let mapped = map_codex_usage_to_anthropic(&usage, Some(self.web_search_requests));
846        self.emit(
847            traffic,
848            out,
849            "message_delta",
850            &serde_json::json!({
851                "type": "message_delta",
852                "delta": {
853                    "stop_reason": stop_reason,
854                    "stop_sequence": null
855                },
856                "usage": mapped,
857            }),
858        );
859        self.emit(
860            traffic,
861            out,
862            "message_stop",
863            &serde_json::json!({"type": "message_stop"}),
864        );
865        self.finished = true;
866    }
867
868    fn close_open_blocks(&mut self, traffic: Option<&TrafficCapture>, out: &mut Vec<u8>) {
869        self.close_thinking(traffic, out);
870        let open: Vec<usize> = self.blocks_by_output_index.keys().copied().collect();
871        for output_index in open {
872            let Some(state) = self.blocks_by_output_index.remove(&output_index) else {
873                continue;
874            };
875            let index = match state {
876                LiveBlock::Text {
877                    index,
878                    text,
879                    deferred: true,
880                } => {
881                    self.deferred_text.push((index, text));
882                    continue;
883                }
884                LiveBlock::Text { index, .. } => index,
885                LiveBlock::Tool { index, .. } => index,
886            };
887            self.emit(
888                traffic,
889                out,
890                "content_block_stop",
891                &serde_json::json!({
892                    "type": "content_block_stop",
893                    "index": index,
894                }),
895            );
896        }
897    }
898
899    fn close_thinking(&mut self, traffic: Option<&TrafficCapture>, out: &mut Vec<u8>) {
900        let Some(index) = self.thinking_index.take() else {
901            return;
902        };
903        self.emit(
904            traffic,
905            out,
906            "content_block_stop",
907            &serde_json::json!({
908                "type": "content_block_stop",
909                "index": index,
910            }),
911        );
912    }
913}
914
915fn web_search_query(item: &serde_json::Value) -> String {
916    let Some(action) = item.get("action") else {
917        return String::new();
918    };
919    action
920        .get("query")
921        .and_then(|v| v.as_str())
922        .or_else(|| {
923            action
924                .get("queries")
925                .and_then(|v| v.as_array())
926                .and_then(|queries| queries.iter().find_map(|query| query.as_str()))
927        })
928        .unwrap_or("")
929        .to_string()
930}
931
932fn output_index(payload: &serde_json::Value) -> usize {
933    payload
934        .get("output_index")
935        .and_then(|v| v.as_u64())
936        .unwrap_or(0) as usize
937}
938
939fn parse_codex_usage(response: &serde_json::Value) -> CodexUsage {
940    let usage = match response.get("usage") {
941        Some(u) => u,
942        None => return CodexUsage::default(),
943    };
944    CodexUsage {
945        input_tokens: usage.get("input_tokens").and_then(|v| v.as_u64()),
946        output_tokens: usage.get("output_tokens").and_then(|v| v.as_u64()),
947        input_tokens_details_cached: usage
948            .get("input_tokens_details")
949            .and_then(|d| d.get("cached_tokens"))
950            .and_then(|v| v.as_u64()),
951        output_tokens_details_reasoning: usage
952            .get("output_tokens_details")
953            .and_then(|d| d.get("reasoning_tokens"))
954            .and_then(|v| v.as_u64()),
955    }
956}
957
958fn response_is_incomplete(payload: &serde_json::Value) -> bool {
959    payload.get("type").and_then(|v| v.as_str()) == Some("response.incomplete")
960        || payload
961            .get("response")
962            .and_then(|r| r.get("status"))
963            .and_then(|v| v.as_str())
964            == Some("incomplete")
965        || payload
966            .get("response")
967            .and_then(|r| r.get("incomplete_details"))
968            .and_then(|d| d.get("reason"))
969            .and_then(|v| v.as_str())
970            .is_some()
971}
972
973fn repair_whitespace_stalled_read_args(
974    name: &str,
975    args: &str,
976    call_id: Option<&str>,
977) -> Option<String> {
978    if name != "Read" {
979        return None;
980    }
981    let trimmed = args.trim_end();
982    let trailing_whitespace = args.len().saturating_sub(trimmed.len());
983    if trailing_whitespace < BUFFERED_READ_REPAIR_TRAILING_WHITESPACE_BYTES {
984        return None;
985    }
986    parse_read_args_candidate(trimmed, call_id).or_else(|| {
987        let with_brace = format!("{trimmed}}}");
988        parse_read_args_candidate(&with_brace, call_id)
989    })
990}
991
992fn parse_read_args_candidate(args: &str, call_id: Option<&str>) -> Option<String> {
993    let parsed: serde_json::Value = serde_json::from_str(args).ok()?;
994    if !is_valid_read_args(&parsed) {
995        return None;
996    }
997    Some(sanitize_read_args(
998        "Read",
999        &serde_json::to_string(&parsed).ok()?,
1000        call_id,
1001    ))
1002}
1003
1004fn is_valid_read_args(value: &serde_json::Value) -> bool {
1005    let Some(obj) = value.as_object() else {
1006        return false;
1007    };
1008    for key in obj.keys() {
1009        if !matches!(key.as_str(), "file_path" | "offset" | "limit" | "pages") {
1010            return false;
1011        }
1012    }
1013    let Some(file_path) = obj.get("file_path").and_then(|v| v.as_str()) else {
1014        return false;
1015    };
1016    if file_path.is_empty() {
1017        return false;
1018    }
1019    if let Some(offset) = obj.get("offset").and_then(|v| v.as_i64())
1020        && offset < 0
1021    {
1022        return false;
1023    }
1024    if let Some(limit) = obj.get("limit").and_then(|v| v.as_i64())
1025        && limit <= 0
1026    {
1027        return false;
1028    }
1029    if obj.get("offset").is_some_and(|v| !v.is_i64()) {
1030        return false;
1031    }
1032    if obj.get("limit").is_some_and(|v| !v.is_i64()) {
1033        return false;
1034    }
1035    if obj.get("pages").is_some_and(|v| !v.is_string()) {
1036        return false;
1037    }
1038    true
1039}
1040
1041fn error_message(payload: &serde_json::Value) -> String {
1042    payload
1043        .get("response")
1044        .and_then(|r| r.get("error"))
1045        .and_then(|e| e.get("message"))
1046        .and_then(|v| v.as_str())
1047        .or_else(|| {
1048            payload
1049                .get("error")
1050                .and_then(|e| e.get("message"))
1051                .and_then(|v| v.as_str())
1052        })
1053        .unwrap_or("Upstream error")
1054        .to_string()
1055}
1056
1057#[cfg(test)]
1058mod tests {
1059    use super::*;
1060    use serde_json::json;
1061
1062    fn render(events: Vec<serde_json::Value>) -> String {
1063        let mut translator = LiveStreamTranslator::new("msg_1", "gpt-5.5");
1064        let mut out = Vec::new();
1065        for event in events {
1066            out.extend(translator.accept(&event, None).unwrap());
1067        }
1068        String::from_utf8(out).unwrap()
1069    }
1070
1071    #[test]
1072    fn emits_text_delta_before_terminal_event() {
1073        let mut translator = LiveStreamTranslator::new("msg_1", "gpt-5.5");
1074        let out = translator
1075            .accept(
1076                &json!({
1077                    "type": "response.output_text.delta",
1078                    "output_index": 0,
1079                    "delta": "hello"
1080                }),
1081                None,
1082            )
1083            .unwrap();
1084        let out = String::from_utf8(out).unwrap();
1085        assert!(out.contains("message_start"));
1086        assert!(out.contains("content_block_start"));
1087        assert!(out.contains("text_delta"));
1088        assert!(out.contains("hello"));
1089        assert!(!out.contains("message_stop"));
1090    }
1091
1092    #[test]
1093    fn finishes_text_stream() {
1094        let out = render(vec![
1095            json!({
1096                "type": "response.output_item.added",
1097                "output_index": 0,
1098                "item": {"type": "message", "id": "msg_up"}
1099            }),
1100            json!({
1101                "type": "response.output_text.delta",
1102                "output_index": 0,
1103                "delta": "hello"
1104            }),
1105            json!({
1106                "type": "response.output_item.done",
1107                "output_index": 0,
1108                "item": {"type": "message"}
1109            }),
1110            json!({
1111                "type": "response.completed",
1112                "response": {"id": "resp_1", "status": "completed", "incomplete_details": null, "usage": {"input_tokens": 2, "output_tokens": 1}}
1113            }),
1114        ]);
1115        assert!(out.contains("content_block_stop"));
1116        assert!(out.contains("message_delta"));
1117        assert!(out.contains(r#""stop_reason":"end_turn""#));
1118        assert!(out.contains("message_stop"));
1119    }
1120
1121    #[test]
1122    fn completed_response_with_null_incomplete_details_is_end_turn() {
1123        let out = render(vec![json!({
1124            "type": "response.completed",
1125            "response": {"id": "resp_1", "status": "completed", "incomplete_details": null, "usage": {}}
1126        })]);
1127        assert!(out.contains(r#""stop_reason":"end_turn""#));
1128        assert!(!out.contains(r#""stop_reason":"max_tokens""#));
1129    }
1130
1131    #[test]
1132    fn buffers_read_tool_args_until_done() {
1133        let out = render(vec![
1134            json!({
1135                "type": "response.output_item.added",
1136                "output_index": 0,
1137                "item": {"type": "function_call", "call_id": "call_1", "name": "Read"}
1138            }),
1139            json!({
1140                "type": "response.function_call_arguments.delta",
1141                "output_index": 0,
1142                "delta": "{\"file_path\":\"/tmp/a\",\"pages\":\"\"}"
1143            }),
1144            json!({
1145                "type": "response.output_item.done",
1146                "output_index": 0,
1147                "item": {
1148                    "type": "function_call",
1149                    "call_id": "call_1",
1150                    "name": "Read",
1151                    "arguments": "{\"file_path\":\"/tmp/a\",\"pages\":\"\"}"
1152                }
1153            }),
1154            json!({
1155                "type": "response.completed",
1156                "response": {"id": "resp_1", "usage": {}}
1157            }),
1158        ]);
1159        assert!(out.contains("tool_use"));
1160        assert!(out.contains("input_json_delta"));
1161        assert!(out.contains("/tmp/a"));
1162        assert!(!out.contains("pages"));
1163    }
1164
1165    #[test]
1166    fn repairs_whitespace_stalled_read_args_as_tool_use_finish() {
1167        let mut translator = LiveStreamTranslator::new("msg_1", "gpt-5.5");
1168        let mut out = Vec::new();
1169        out.extend(
1170            translator
1171                .accept(
1172                    &json!({
1173                        "type": "response.output_item.added",
1174                        "output_index": 0,
1175                        "item": {"type":"function_call","call_id":"call_1","name":"Read"}
1176                    }),
1177                    None,
1178                )
1179                .unwrap(),
1180        );
1181        out.extend(
1182            translator
1183                .accept(
1184                    &json!({
1185                        "type": "response.function_call_arguments.delta",
1186                        "output_index": 0,
1187                        "delta": format!("{{\"file_path\":\"/tmp/a\",\"pages\":\"\"{}", " ".repeat(1024))
1188                    }),
1189                    None,
1190                )
1191                .unwrap(),
1192        );
1193        let rendered = String::from_utf8(out).unwrap();
1194        assert!(rendered.contains(r#""partial_json":"{\"file_path\":\"/tmp/a\"}""#));
1195        assert!(rendered.contains(r#""stop_reason":"tool_use""#));
1196        assert!(rendered.contains("message_stop"));
1197        assert!(translator.is_finished());
1198    }
1199
1200    #[test]
1201    fn finishes_after_closed_completed_tool_call() {
1202        let mut translator = LiveStreamTranslator::new("msg_1", "gpt-5.5");
1203        let mut out = Vec::new();
1204        for event in [
1205            json!({
1206                "type": "response.output_item.added",
1207                "output_index": 0,
1208                "item": {"type":"function_call","call_id":"call_1","name":"WebSearch"}
1209            }),
1210            json!({
1211                "type": "response.function_call_arguments.done",
1212                "output_index": 0,
1213                "arguments": "{\"query\":\"claude-code-proxy github\"}"
1214            }),
1215            json!({
1216                "type": "response.output_item.done",
1217                "output_index": 0,
1218                "item": {
1219                    "type":"function_call",
1220                    "call_id":"call_1",
1221                    "name":"WebSearch",
1222                    "arguments":"{\"query\":\"claude-code-proxy github\"}"
1223                }
1224            }),
1225        ] {
1226            out.extend(translator.accept(&event, None).unwrap());
1227        }
1228        out.extend(translator.finish_after_closed_completed_tool_call(None));
1229        let rendered = String::from_utf8(out).unwrap();
1230        assert!(rendered.contains("content_block_start"));
1231        assert!(rendered.contains("input_json_delta"));
1232        assert!(rendered.contains(r#""stop_reason":"tool_use""#));
1233        assert!(rendered.contains("message_stop"));
1234        assert!(!rendered.contains("event: error"));
1235    }
1236
1237    #[test]
1238    fn emits_web_search_results_from_citations_before_deferred_text() {
1239        let out = render(vec![
1240            json!({
1241                "type": "response.output_item.added",
1242                "output_index": 0,
1243                "item": {"type": "web_search_call", "id": "ws_1"}
1244            }),
1245            json!({
1246                "type": "response.output_item.done",
1247                "output_index": 0,
1248                "item": {
1249                    "type": "web_search_call",
1250                    "id": "ws_1",
1251                    "action": {"query": "grok reasoning effort"}
1252                }
1253            }),
1254            json!({
1255                "type": "response.output_item.added",
1256                "output_index": 1,
1257                "item": {"type": "message", "id": "msg_up"}
1258            }),
1259            json!({
1260                "type": "response.output_text.delta",
1261                "output_index": 1,
1262                "delta": "See the official docs."
1263            }),
1264            json!({
1265                "type": "response.output_text.annotation.added",
1266                "annotation": {
1267                    "type": "url_citation",
1268                    "title": "Reasoning",
1269                    "url": "https://docs.x.ai/docs/guides/reasoning"
1270                }
1271            }),
1272            json!({
1273                "type": "response.output_item.done",
1274                "output_index": 1,
1275                "item": {"type": "message"}
1276            }),
1277            json!({
1278                "type": "response.completed",
1279                "response": {"status": "completed", "usage": {}}
1280            }),
1281        ]);
1282
1283        let tool = out.find("server_tool_use").unwrap();
1284        let result = out.find("web_search_tool_result").unwrap();
1285        let text = out.find("See the official docs.").unwrap();
1286        assert!(tool < result && result < text);
1287        assert!(out.contains("https://docs.x.ai/docs/guides/reasoning"));
1288        assert!(out.contains(r#""web_search_requests":1"#));
1289    }
1290
1291    #[test]
1292    fn rate_limit_event_returns_error() {
1293        let mut translator = LiveStreamTranslator::new("msg_1", "gpt-5.5");
1294        let err = translator
1295            .accept(
1296                &json!({
1297                    "type": "codex.rate_limits",
1298                    "rate_limits": {"limit_reached": true}
1299                }),
1300                None,
1301            )
1302            .unwrap_err();
1303        assert_eq!(err, "rate limit reached");
1304    }
1305}