Add code-mode output truncation protocol contracts (#57272)

## What changed

Add session options for client- or server-owned output truncation and optional `max_output_tokens` budgets for wait and terminate observations. Introduce `TerminateRequest` and update session implementations and callers to use it.

Extend the stdio and gRPC schemas with optional truncation settings and budgets, and define the `bounded-output-v1` capability. Existing sessions retain client-side truncation; providers reject server mode by default. This change defines the contracts without enabling server-side truncation.

## Testing

Extend protocol serialization coverage for `outputTruncation: "server"` while preserving existing wire shapes when optional fields are absent.

GitOrigin-RevId: 22145897ea16d385c5ba9e7549109f7a669fceb4
This commit is contained in:
Sean Huang
2026-10-26 21:36:35 +00:00
committed by copyberry
parent 7077b1edb9
commit e963cab11a
38 changed files with 329 additions and 55 deletions
@@ -98,6 +98,7 @@ async fn interrupt_yields_execute_and_wait_without_stopping_the_cell() {
WaitRequest {
cell_id: cell_id("1"),
yield_time_ms: 60_000,
max_output_tokens: None,
},
Some(wait_signal),
)
@@ -125,6 +126,7 @@ async fn interrupt_yields_execute_and_wait_without_stopping_the_cell() {
WaitRequest {
cell_id: cell_id("1"),
yield_time_ms: 60_000,
max_output_tokens: None,
},
/*preempt*/ None,
)
@@ -221,6 +223,7 @@ async fn execute_and_wait_clamp_yield_grace_without_stopping_the_cell() {
WaitRequest {
cell_id: cell_id("1"),
yield_time_ms: 10_000,
max_output_tokens: None,
},
/*preempt*/ None,
)
@@ -243,6 +246,7 @@ async fn execute_and_wait_clamp_yield_grace_without_stopping_the_cell() {
WaitRequest {
cell_id: cell_id("1"),
yield_time_ms: 10_000,
max_output_tokens: None,
},
/*preempt*/ None,
)
@@ -291,6 +295,7 @@ async fn wait_waits_for_nested_tool_during_yield_grace() {
WaitRequest {
cell_id: cell_id("1"),
yield_time_ms: 10_000,
max_output_tokens: None,
},
/*preempt*/ None,
)
@@ -366,6 +371,7 @@ async fn zero_yield_limit_is_immediate_and_scoped_to_its_session() {
WaitRequest {
cell_id: cell_id("1"),
yield_time_ms: 60_000,
max_output_tokens: None,
},
/*preempt*/ None,
)
@@ -441,7 +447,7 @@ async fn tool_start_acknowledgement_reports_termination_before_invocation() {
let (startup, terminated) = tokio::join!(
wait_until_tool_started(&delegate),
service.terminate(cell_id("1")),
service.terminate(cell_id("1").into()),
);
assert_eq!(startup, Err("cell closed before nested tool started"));
let response = RuntimeResponse::Terminated {
@@ -746,6 +752,7 @@ text("after exit");
let actual = service
.wait(
WaitRequest {
max_output_tokens: None,
cell_id: cell_id("1"),
yield_time_ms: 30_000,
},
@@ -1106,7 +1113,7 @@ async fn execute_to_pending_returns_once_the_runtime_is_quiescent() {
}
);
let termination = service.terminate(cell_id("1")).await.unwrap();
let termination = service.terminate(cell_id("1").into()).await.unwrap();
assert_eq!(
termination,
@@ -1150,7 +1157,7 @@ await Promise.all([
}
);
let termination = service.terminate(cell_id("1")).await.unwrap();
let termination = service.terminate(cell_id("1").into()).await.unwrap();
assert_eq!(
termination,
@@ -1218,7 +1225,7 @@ await Promise.all([
})
);
let termination = service.terminate(cell_id("1")).await.unwrap();
let termination = service.terminate(cell_id("1").into()).await.unwrap();
assert_eq!(
termination,
@@ -1285,7 +1292,7 @@ await new Promise(() => {});
})
);
let termination = service.terminate(cell_id("1")).await.unwrap();
let termination = service.terminate(cell_id("1").into()).await.unwrap();
assert_eq!(
termination,
@@ -2132,6 +2139,7 @@ async fn wait_reports_missing_cell_separately_from_runtime_results() {
WaitRequest {
cell_id: cell_id("missing"),
yield_time_ms: 1,
max_output_tokens: None,
},
/*preempt*/ None,
)