Wire bounded output collection into the code mode runtime (#57850)

## Why

Bounding collected output also requires limiting the raw event backlog between
V8 and the cell actor. Previously, runtime events and cell output accumulated
in an unbounded channel and vector.

## What changed

- Select the event transport and `OutputCollector` from the cell's
  `output_truncation` mode. Server mode uses an ordered, bounded channel with
  backpressure; client mode retains untruncated output.
- Transfer the collector with each observation and restore it on failed
  delivery, preserving retained media and omission metadata.
- Include server-mode script errors in the observation's output budget. Build
  completion output only after completion wins against termination, so a
  rejected error cannot evict output returned by termination.
- Keep the public service on client-side truncation.

## Testing

Add regression coverage for channel backpressure, event ordering, receiver
closure, failed observation delivery, termination during completion commit,
and oversized errors across UTF-8 and omission boundaries.

GitOrigin-RevId: 7604b60416e33c636ca41138be65dc0fd452774a
This commit is contained in:
Sean Huang
2026-10-28 22:33:13 +00:00
committed by copyberry
parent 6d97d6ecb4
commit 92e802fb61
21 changed files with 686 additions and 355 deletions
@@ -6,12 +6,14 @@ use std::task::Poll;
use std::task::Waker;
use std::time::Duration;
use codex_code_mode_protocol::CodeModeOutputTruncation;
use pretty_assertions::assert_eq;
use serde_json::Value as JsonValue;
use tokio_util::sync::CancellationToken;
use super::*;
use crate::cell_actor::CompletionCommit;
use crate::output::OutputCollector;
struct RecordingDelegate;
@@ -80,9 +82,9 @@ async fn reports_cell_actor_panics_to_the_owner() {
assert_eq!(
started.initial_event().await,
Ok(CellEvent::Completed {
content_items: vec![OutputItem::Text {
content_items: OutputCollector::Client(vec![OutputItem::InputText {
text: "done".to_string(),
}],
}]),
result_buffered_for: Duration::ZERO,
error_text: None,
})
@@ -104,21 +106,13 @@ async fn termination_rejects_a_waiting_store_commit_before_the_next_cell_can_loa
inner: Arc::clone(&runtime.inner),
execution_context: opentelemetry::Context::new(),
};
let completion = CellEvent::Completed {
content_items: vec![OutputItem::Text {
text: "uncommitted output".to_string(),
}],
result_buffered_for: Duration::ZERO,
error_text: None,
};
let stored_values = runtime.inner.stored_values.lock().await;
let commit = host.commit_completion(
HashMap::from([(
"candidate".to_string(),
Arc::new(JsonValue::String("lost".to_string())),
)]),
completion.clone(),
|| panic!("terminated cell must not construct completion output"),
Arc::clone(&cell_state),
);
tokio::pin!(commit);
@@ -128,9 +122,9 @@ async fn termination_rejects_a_waiting_store_commit_before_the_next_cell_can_loa
let termination = cell_state.request_termination();
drop(stored_values);
assert_eq!(commit.await, CompletionCommit::Rejected(completion));
assert_eq!(commit.await, CompletionCommit::Rejected);
let terminated = CellEvent::Terminated {
content_items: Vec::new(),
content_items: OutputCollector::Client(Vec::new()),
};
assert_eq!(
cell_state.finish_termination(terminated.clone()),
@@ -149,6 +143,7 @@ async fn termination_rejects_a_waiting_store_commit_before_the_next_cell_can_loa
let reader = runtime
.execute(
CreateCellRequest {
output_truncation: CodeModeOutputTruncation::Client,
tool_call_id: "reader".to_string(),
enabled_tools: Vec::new(),
source: r#"text(String(load("candidate")));"#.to_string(),
@@ -161,9 +156,9 @@ async fn termination_rejects_a_waiting_store_commit_before_the_next_cell_can_loa
assert_eq!(
reader.initial_event().await,
Ok(CellEvent::Completed {
content_items: vec![OutputItem::Text {
content_items: OutputCollector::Client(vec![OutputItem::InputText {
text: "undefined".to_string(),
}],
}]),
result_buffered_for: Duration::ZERO,
error_text: None,
})
@@ -173,6 +168,7 @@ async fn termination_rejects_a_waiting_store_commit_before_the_next_cell_can_loa
fn execute_request(source: &str) -> CreateCellRequest {
CreateCellRequest {
output_truncation: CodeModeOutputTruncation::Client,
tool_call_id: "call-1".to_string(),
enabled_tools: Vec::new(),
source: source.to_string(),
@@ -255,7 +251,7 @@ async fn drop_terminates_cells_when_the_registry_is_locked() {
assert_eq!(
started.initial_event().await,
Ok(CellEvent::Yielded {
content_items: Vec::new(),
content_items: OutputCollector::Client(Vec::new()),
})
);