diff --git a/codex-rs/core/src/chat_completions.rs b/codex-rs/core/src/chat_completions.rs index ffeaadd39f..829f6bd15a 100644 --- a/codex-rs/core/src/chat_completions.rs +++ b/codex-rs/core/src/chat_completions.rs @@ -391,6 +391,9 @@ pub(crate) struct AggregatedChatStream { inner: S, cumulative: String, pending_completed: Option, + // When true, do not emit a cumulative assistant message at Completed. + streaming_mode: bool, + // When true, forward reasoning deltas instead of ignoring them. } impl Stream for AggregatedChatStream @@ -445,7 +448,7 @@ where response_id, token_usage, }))) => { - if !this.cumulative.is_empty() { + if !this.streaming_mode && !this.cumulative.is_empty() { let aggregated_item = crate::models::ResponseItem::Message { id: None, role: "assistant".to_string(), @@ -477,14 +480,21 @@ where continue; } Poll::Ready(Some(Ok(ResponseEvent::OutputTextDelta(delta)))) => { - // Accumulate incremental assistant text deltas. - this.cumulative.push_str(&delta); - continue; + if this.streaming_mode { + return Poll::Ready(Some(Ok(ResponseEvent::OutputTextDelta(delta)))); + } else { + // Accumulate incremental assistant text deltas. + this.cumulative.push_str(&delta); + continue; + } } - Poll::Ready(Some(Ok(ResponseEvent::ReasoningSummaryDelta(_)))) => { - // Reasoning deltas are intentionally ignored by this - // adapter; they are handled by the UI directly. - continue; + Poll::Ready(Some(Ok(ResponseEvent::ReasoningSummaryDelta(delta)))) => { + if this.streaming_mode { + return Poll::Ready(Some(Ok(ResponseEvent::ReasoningSummaryDelta(delta)))); + } else { + // Reasoning deltas are intentionally ignored in aggregated mode. + continue; + } } } } @@ -517,8 +527,21 @@ pub(crate) trait AggregateStreamExt: Stream> + Size inner: self, cumulative: String::new(), pending_completed: None, + streaming_mode: false, } } } impl AggregateStreamExt for T where T: Stream> + Sized {} + +impl AggregatedChatStream { + pub(crate) fn streaming_mode(inner: S) -> Self { + AggregatedChatStream { + inner, + cumulative: String::new(), + pending_completed: None, + streaming_mode: true, + } + } +} + diff --git a/codex-rs/core/src/client.rs b/codex-rs/core/src/client.rs index ba8960026c..21752ceb4c 100644 --- a/codex-rs/core/src/client.rs +++ b/codex-rs/core/src/client.rs @@ -93,7 +93,11 @@ impl ModelClient { // Wrap it with the aggregation adapter so callers see *only* // the final assistant message per turn (matching the // behaviour of the Responses API). - let mut aggregated = response_stream.aggregate(); + let mut aggregated = if self.config.show_reasoning_content { + crate::chat_completions::AggregatedChatStream::streaming_mode(response_stream) + } else { + response_stream.aggregate() + }; // Bridge the aggregated stream back into a standard // `ResponseStream` by forwarding events through a channel.