Report code mode host request durations (#41452)

## Why

Code mode wall time should measure the host operation itself, without including
client-side response delays or idle time between requests.

## What changed

- Measure each execute, wait, and terminate request in the code mode host.
- Carry the duration through the stdio and gRPC protocols and use it for
  model-visible wall time.
- Emit a structured `codex.code_mode.host_timing` event correlated with the
  conversation, turn, tool call, and cell.

## Testing

- Cover successful and failed execution timing, delayed response reads,
  repeated waits, termination, and missing cells across stdio and gRPC.
- Verify timing survives protocol serialization and is reflected in app-server
  model output and structured telemetry.

GitOrigin-RevId: d24af30c3fc5820521b4beba1f9970714dad6482
This commit is contained in:
Adam Perry @ OpenAI authored and copyberry committed 2026-08-29 02:52:58 +00:00
1 parent 75388bf321
commit 48e22a5fa0
34 files changed
+1398 -259

No files matched your search

@@ -109,6 +109,9 @@ impl CodeModeExecuteHandler {
.initial_response()
.await
.map_err(FunctionCallError::RespondToModel)?;
if let Some(code_mode_host_duration) = response.code_mode_host_duration() {
telemetry.record_code_mode_host_duration(code_mode_host_duration);
}
// Record the raw runtime boundary. The model-visible custom-tool output
// is produced by `handle_runtime_response` and later linked through
// `CodeCell.output_item_ids` in the reduced trace.
@@ -131,7 +134,10 @@ impl CodeModeExecuteHandler {
});
}
exec.session.services.elicitations.wait_until_clear().await;
handle_runtime_response(&exec, response, args.max_output_tokens, started_at)
let wall_time = response
.code_mode_host_duration()
.unwrap_or_else(|| started_at.elapsed());
handle_runtime_response(&exec, response, args.max_output_tokens, wall_time)
.await
.map_err(FunctionCallError::RespondToModel)
}
+4 -4
View File
@@ -251,7 +251,7 @@ pub(super) async fn handle_runtime_response(
exec: &ExecContext,
response: RuntimeResponse,
max_output_tokens: Option<usize>,
started_at: std::time::Instant,
wall_time: Duration,
) -> Result<FunctionToolOutput, String> {
let script_status = format_script_status(&response);
@@ -260,14 +260,14 @@ pub(super) async fn handle_runtime_response(
let mut content_items = into_function_call_output_content_items(content_items);
sanitize_runtime_image_detail(exec.turn.as_ref(), &mut content_items);
content_items = truncate_code_mode_result(content_items, max_output_tokens);
prepend_script_status(&mut content_items, &script_status, started_at.elapsed());
prepend_script_status(&mut content_items, &script_status, wall_time);
Ok(FunctionToolOutput::from_content(content_items, Some(true)))
}
RuntimeResponse::Terminated { content_items, .. } => {
let mut content_items = into_function_call_output_content_items(content_items);
sanitize_runtime_image_detail(exec.turn.as_ref(), &mut content_items);
content_items = truncate_code_mode_result(content_items, max_output_tokens);
prepend_script_status(&mut content_items, &script_status, started_at.elapsed());
prepend_script_status(&mut content_items, &script_status, wall_time);
Ok(FunctionToolOutput::from_content(content_items, Some(true)))
}
RuntimeResponse::Result {
@@ -284,7 +284,7 @@ pub(super) async fn handle_runtime_response(
});
}
content_items = truncate_code_mode_result(content_items, max_output_tokens);
prepend_script_status(&mut content_items, &script_status, started_at.elapsed());
prepend_script_status(&mut content_items, &script_status, wall_time);
Ok(FunctionToolOutput::from_content(
content_items,
Some(success),
@@ -3,6 +3,7 @@ use codex_analytics::CodeModeToolCallFact;
use codex_analytics::CodeModeToolCallStatus;
use codex_analytics::TurnAnalyticsMetadata;
use std::sync::Arc;
use std::time::Duration;
pub(super) struct CodeModeToolCallGuard {
analytics: AnalyticsEventsClient,
@@ -45,6 +46,27 @@ impl CodeModeToolCallGuard {
CodeModeToolCallStatus::Failed
};
}
pub(super) fn record_code_mode_host_duration(&self, duration: Duration) {
let Ok(code_mode_host_duration_ns) = u64::try_from(duration.as_nanos()) else {
return;
};
// Bridge joins this record to the outer tool-completion event. Emit it
// before the handler returns so consumers never need another timeout.
tracing::info!(
target: "codex_code_mode::timing",
{
event.name = "codex.code_mode.host_timing",
conversation_id = %self.thread_id,
turn_id = %self.turn_id,
call_id = %self.call_id,
cell_id = self.cell_id.as_deref(),
tool_name = self.tool_name,
code_mode_host_duration_ns,
},
"code-mode host operation completed"
);
}
}
impl Drop for CodeModeToolCallGuard {
@@ -94,6 +94,7 @@ impl CodeModeWaitHandler {
})?;
let exec = ExecContext { session, turn };
let started_at = std::time::Instant::now();
telemetry.cell_id = Some(args.cell_id.clone());
let cell_id = codex_code_mode::CellId::new(args.cell_id);
let wait_response = if args.terminate {
exec.session
@@ -152,8 +153,14 @@ impl CodeModeWaitHandler {
);
}
}
if let Some(code_mode_host_duration) = wait_response.code_mode_host_duration() {
telemetry.record_code_mode_host_duration(code_mode_host_duration);
}
exec.session.services.elicitations.wait_until_clear().await;
handle_runtime_response(&exec, wait_response.into(), args.max_tokens, started_at)
let wall_time = wait_response
.code_mode_host_duration()
.unwrap_or_else(|| started_at.elapsed());
handle_runtime_response(&exec, wait_response.into(), args.max_tokens, wall_time)
.await
.map_err(FunctionCallError::RespondToModel)
.map(boxed_tool_output)