mirror of
https://github.com/openai/codex.git
synced 2026-11-10 21:00:47 +00:00
Support negotiated server-side truncation in Code Mode clients (#58125)
## Why Host-side truncation can bound Code Mode output before it crosses the transport, including script errors that would otherwise exceed the frame limit. ## What changed - Negotiate server-side output truncation for stdio and gRPC sessions, with client-side fallback for older hosts. - Forward output budgets for execution, waiting, and termination when server-side truncation is selected. Avoid truncating host-formatted output or appending script errors a second time. - Preserve the negotiated mode across reconnects and reject hosts that cannot maintain it. ## Testing Add transport and integration coverage for output budgets, oversized script errors, reconnect compatibility, and preservation of output-limit notices with a zero-token budget. GitOrigin-RevId: 2075101e4845eca64f45491c27a3b326788645c8
This commit is contained in:
@@ -2,13 +2,16 @@ use std::io;
|
||||
use std::path::PathBuf;
|
||||
use std::sync::Arc;
|
||||
use std::sync::Mutex as StdMutex;
|
||||
use std::sync::OnceLock;
|
||||
use std::sync::atomic::AtomicBool;
|
||||
use std::sync::atomic::AtomicU64;
|
||||
use std::sync::atomic::Ordering;
|
||||
|
||||
use codex_code_mode_protocol::CodeModeOutputTruncation;
|
||||
use codex_code_mode_protocol::CodeModeSession;
|
||||
use codex_code_mode_protocol::CodeModeSessionCellExecutionLimits;
|
||||
use codex_code_mode_protocol::CodeModeSessionDelegate;
|
||||
use codex_code_mode_protocol::CodeModeSessionOptions;
|
||||
use codex_code_mode_protocol::CodeModeSessionProvider;
|
||||
use codex_code_mode_protocol::CodeModeSessionProviderFuture;
|
||||
use codex_code_mode_protocol::CodeModeSessionResultFuture;
|
||||
@@ -83,7 +86,17 @@ impl CodeModeSessionProvider for ProcessOwnedCodeModeSessionProvider {
|
||||
&'a self,
|
||||
limits: CodeModeSessionCellExecutionLimits,
|
||||
) -> CodeModeSessionProviderFuture<'a> {
|
||||
Box::pin(create_host_session(self.process_host(), limits))
|
||||
self.create_session_with_options(CodeModeSessionOptions {
|
||||
cell_execution_limits: limits,
|
||||
..CodeModeSessionOptions::default()
|
||||
})
|
||||
}
|
||||
|
||||
fn create_session_with_options<'a>(
|
||||
&'a self,
|
||||
options: CodeModeSessionOptions,
|
||||
) -> CodeModeSessionProviderFuture<'a> {
|
||||
Box::pin(create_host_session(self.process_host(), options))
|
||||
}
|
||||
}
|
||||
|
||||
@@ -106,9 +119,9 @@ impl CodeModeSessionProvider for DisabledCodeModeSessionProvider {
|
||||
|
||||
async fn create_host_session(
|
||||
host: Arc<OwnedCodeModeHost>,
|
||||
limits: CodeModeSessionCellExecutionLimits,
|
||||
options: CodeModeSessionOptions,
|
||||
) -> Result<Arc<dyn CodeModeSession>, String> {
|
||||
let session = ProcessOwnedCodeModeSession::with_host(host, limits);
|
||||
let session = ProcessOwnedCodeModeSession::with_host(host, options);
|
||||
session.connection().await?;
|
||||
Ok(Arc::new(session))
|
||||
}
|
||||
@@ -218,7 +231,9 @@ struct SessionBinding {
|
||||
|
||||
struct SessionInner {
|
||||
host: Arc<OwnedCodeModeHost>,
|
||||
limits: CodeModeSessionCellExecutionLimits,
|
||||
options: CodeModeSessionOptions,
|
||||
// The first successful open fixes the output policy for all generations of this session.
|
||||
output_truncation: OnceLock<CodeModeOutputTruncation>,
|
||||
state: StdMutex<SessionState>,
|
||||
shutdown_requested: AtomicBool,
|
||||
shutdown_result: StdMutex<Option<ShutdownResultReceiver>>,
|
||||
@@ -236,15 +251,16 @@ impl ProcessOwnedCodeModeSession {
|
||||
Arc::new(OwnedCodeModeHost::new(
|
||||
InstallContext::current().code_mode_host_program(),
|
||||
)),
|
||||
CodeModeSessionCellExecutionLimits::default(),
|
||||
CodeModeSessionOptions::default(),
|
||||
)
|
||||
}
|
||||
|
||||
fn with_host(host: Arc<OwnedCodeModeHost>, limits: CodeModeSessionCellExecutionLimits) -> Self {
|
||||
fn with_host(host: Arc<OwnedCodeModeHost>, options: CodeModeSessionOptions) -> Self {
|
||||
Self {
|
||||
inner: Arc::new(SessionInner {
|
||||
host,
|
||||
limits,
|
||||
options,
|
||||
output_truncation: OnceLock::new(),
|
||||
state: StdMutex::new(SessionState::New),
|
||||
shutdown_requested: AtomicBool::new(false),
|
||||
shutdown_result: StdMutex::new(None),
|
||||
@@ -257,9 +273,13 @@ impl ProcessOwnedCodeModeSession {
|
||||
self.inner.connection().await
|
||||
}
|
||||
|
||||
fn server_output_budget(&self, budget: Option<usize>) -> Option<usize> {
|
||||
budget.filter(|_| self.output_truncation() == CodeModeOutputTruncation::Server)
|
||||
}
|
||||
|
||||
pub async fn execute(
|
||||
&self,
|
||||
request: ExecuteRequest,
|
||||
mut request: ExecuteRequest,
|
||||
delegate: Arc<dyn CodeModeSessionDelegate>,
|
||||
preempt: Option<CancellationToken>,
|
||||
) -> Result<StartedCell, String> {
|
||||
@@ -268,6 +288,10 @@ impl ProcessOwnedCodeModeSession {
|
||||
delegate,
|
||||
trace: codex_otel::current_span_w3c_trace_context(),
|
||||
});
|
||||
// V1 encodes the exec budget as i32. Its maximum already exceeds the host collector limit.
|
||||
request.max_output_tokens = self
|
||||
.server_output_budget(request.max_output_tokens)
|
||||
.map(|tokens| tokens.min(i32::MAX as usize));
|
||||
binding
|
||||
.connection
|
||||
.execute(binding.remote, request, delegate, preempt)
|
||||
@@ -276,22 +300,21 @@ impl ProcessOwnedCodeModeSession {
|
||||
|
||||
pub async fn wait(
|
||||
&self,
|
||||
request: WaitRequest,
|
||||
mut request: WaitRequest,
|
||||
preempt: Option<CancellationToken>,
|
||||
) -> Result<WaitOutcome, String> {
|
||||
let binding = self.connection().await?;
|
||||
request.max_output_tokens = self.server_output_budget(request.max_output_tokens);
|
||||
binding
|
||||
.connection
|
||||
.wait(binding.remote, request, preempt)
|
||||
.await
|
||||
}
|
||||
|
||||
pub async fn terminate(&self, request: TerminateRequest) -> Result<WaitOutcome, String> {
|
||||
pub async fn terminate(&self, mut request: TerminateRequest) -> Result<WaitOutcome, String> {
|
||||
let binding = self.connection().await?;
|
||||
binding
|
||||
.connection
|
||||
.terminate(binding.remote, request.cell_id)
|
||||
.await
|
||||
request.max_output_tokens = self.server_output_budget(request.max_output_tokens);
|
||||
binding.connection.terminate(binding.remote, request).await
|
||||
}
|
||||
|
||||
pub async fn shutdown(&self) -> Result<(), String> {
|
||||
@@ -352,7 +375,11 @@ impl SessionInner {
|
||||
let result = match self.host.connection().await {
|
||||
Ok(connection) => {
|
||||
let cleanup = connection
|
||||
.open_session(remote.clone(), self.limits.clone())
|
||||
.open_session(
|
||||
remote.clone(),
|
||||
self.options.clone(),
|
||||
&self.output_truncation,
|
||||
)
|
||||
.await;
|
||||
cleanup.map(|cleanup| SessionBinding {
|
||||
connection,
|
||||
@@ -529,6 +556,14 @@ impl Default for ProcessOwnedCodeModeSession {
|
||||
}
|
||||
|
||||
impl CodeModeSession for ProcessOwnedCodeModeSession {
|
||||
fn output_truncation(&self) -> CodeModeOutputTruncation {
|
||||
self.inner
|
||||
.output_truncation
|
||||
.get()
|
||||
.copied()
|
||||
.unwrap_or_default()
|
||||
}
|
||||
|
||||
fn execute<'a>(
|
||||
&'a self,
|
||||
request: ExecuteRequest,
|
||||
|
||||
Reference in New Issue
Block a user