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
+7 -2
View File
@@ -17,6 +17,7 @@ use codex_code_mode_protocol::CodeModeSessionProviderFuture;
use codex_code_mode_protocol::CodeModeSessionResultFuture;
use codex_code_mode_protocol::ExecuteRequest;
use codex_code_mode_protocol::StartedCell;
use codex_code_mode_protocol::TerminateRequest;
use codex_code_mode_protocol::WaitOutcome;
use codex_code_mode_protocol::WaitRequest;
use codex_code_mode_protocol::grpc;
@@ -119,6 +120,7 @@ impl GrpcCodeModeSessionProvider {
loop {
let mut request = tonic::Request::new(grpc::OpenSessionRequest {
cell_execution_limits,
output_truncation: None,
});
inject_span_traceparent(&mut request, &open_session_span);
let result = async {
@@ -269,8 +271,11 @@ impl CodeModeSession for GrpcCodeModeSession {
Box::pin(self.inner.wait(request, preempt))
}
fn terminate<'a>(&'a self, cell_id: CellId) -> CodeModeSessionResultFuture<'a, WaitOutcome> {
Box::pin(self.inner.terminate(cell_id))
fn terminate<'a>(
&'a self,
request: TerminateRequest,
) -> CodeModeSessionResultFuture<'a, WaitOutcome> {
Box::pin(self.inner.terminate(request.cell_id))
}
fn shutdown<'a>(&'a self) -> CodeModeSessionResultFuture<'a, ()> {
@@ -313,6 +313,7 @@ impl SessionInner {
cell_id: expected_cell_id.as_str().to_string(),
wait_id: wait_id.clone(),
yield_time_ms: request.yield_time_ms,
max_output_tokens: None,
};
let mut client = self.client();
let watcher = preempt.map(|signal| {
@@ -368,6 +369,7 @@ impl SessionInner {
client.terminate(self.request(grpc::TerminateRequest {
session_id: self.id.clone(),
cell_id: cell_id.as_str().to_string(),
max_output_tokens: None,
})),
)
.await?;
@@ -4,13 +4,13 @@ use std::sync::PoisonError;
use std::sync::atomic::AtomicU64;
use std::sync::atomic::Ordering;
use codex_code_mode_protocol::CellId;
use codex_code_mode_protocol::CodeModeSession;
use codex_code_mode_protocol::CodeModeSessionCellExecutionLimits;
use codex_code_mode_protocol::CodeModeSessionDelegate;
use codex_code_mode_protocol::CodeModeSessionResultFuture;
use codex_code_mode_protocol::ExecuteRequest;
use codex_code_mode_protocol::StartedCell;
use codex_code_mode_protocol::TerminateRequest;
use codex_code_mode_protocol::WaitOutcome;
use codex_code_mode_protocol::WaitRequest;
use tokio::sync::Semaphore;
@@ -97,6 +97,7 @@ impl CodeModeSession for ReconnectableSession {
Box::pin(async move {
let binding = self.inner.binding_for_request(generation).await?;
let request = WaitRequest {
max_output_tokens: request.max_output_tokens,
cell_id: generation::remote_cell_id(binding.generation, &request.cell_id)?,
yield_time_ms: request.yield_time_ms,
};
@@ -105,15 +106,21 @@ impl CodeModeSession for ReconnectableSession {
})
}
fn terminate<'a>(&'a self, cell_id: CellId) -> CodeModeSessionResultFuture<'a, WaitOutcome> {
fn terminate<'a>(
&'a self,
request: TerminateRequest,
) -> CodeModeSessionResultFuture<'a, WaitOutcome> {
Box::pin(async move {
// Cleanup must not replace the runtime and hide its loss from the next request.
let binding = self
.inner
.binding()
.ok_or_else(|| SHUTDOWN_ERROR.to_string())?;
let cell_id = generation::remote_cell_id(binding.generation, &cell_id)?;
let outcome = binding.session.terminate(cell_id).await?;
let cell_id = generation::remote_cell_id(binding.generation, &request.cell_id)?;
let outcome = binding
.session
.terminate(TerminateRequest { cell_id, ..request })
.await?;
Ok(generation::public_wait_outcome(binding.generation, outcome))
})
}