Files
codex/codex-rs/core/src/tools/call_trace.rs
T
bkotsopoulos b82d542aef Add turn, tool, and skill attribution to traces (#56524)
## What changed

- Emit `codex.turn_profile` events with finalized sampling request and retry counts when turns complete or abort, and preserve the caller's tracing span for detached extension interrupts.
- Add conversation and turn identifiers to turn and tool dispatch spans, identify tool sources, and include dispatch-to-result-ready duration on tool completion events.
- Emit `codex.skill_read.source_selected` before cache or provider access, recording the selected cloud or executor source, skill and plugin identity, resource kind, and continuation status.

## Testing

Add regression coverage for trace correlation, tool completion timing, turn profile counters on completion and interruption, detached interrupt span propagation, and skill source attribution including cached cloud reads.

GitOrigin-RevId: db78aa270bbcb8dc9e4558d202c109ad54141811
2026-10-23 16:55:32 +00:00

97 lines
2.8 KiB
Rust

//! Trace milestones for every direct or code-mode tool call handled by core.
//! These events contain call identifiers and names, never arguments or output.
//! Code-mode calls reach the broker before the dispatching turn is known.
use std::time::Duration;
use codex_protocol::DEFAULT_FUNCTION_NAMESPACE;
use codex_protocol::ThreadId;
use codex_tools::ToolName;
#[derive(Clone, Copy)]
pub(crate) enum Source {
Direct,
CodeMode,
}
impl Source {
pub(crate) fn as_str(self) -> &'static str {
match self {
Self::Direct => "direct",
Self::CodeMode => "code_mode",
}
}
}
pub(crate) enum Receipt<'a> {
ModelTurn(&'a str),
CodeModeBroker {
cell_id: Option<&'a str>,
runtime_tool_call_id: Option<&'a str>,
},
}
pub(crate) fn received(
thread_id: ThreadId,
tool_name: &ToolName,
call_id: &str,
receipt: Receipt<'_>,
) {
let (turn_id, source, cell_id, runtime_tool_call_id) = match receipt {
Receipt::ModelTurn(turn_id) => (Some(turn_id), Source::Direct, None, None),
Receipt::CodeModeBroker {
cell_id,
runtime_tool_call_id,
} => (None, Source::CodeMode, cell_id, runtime_tool_call_id),
};
tracing::event!(
name: "codex.tool_call_received",
target: "codex_otel.trace_safe",
tracing::Level::INFO,
event.name = "codex.tool_call_received",
conversation.id = %thread_id,
turn_id,
call_id,
tool_name = tool_name.name.as_str(),
tool_namespace = namespace(tool_name),
tool_source = source.as_str(),
cell.id = cell_id,
runtime_tool_call_id,
);
}
// Record the measured duration on the terminal event so a retained completion
// remains useful even when its receipt event or dispatch span was not retained.
// This includes runtime admission and handler work, but excludes broker queuing
// before dispatch and the sampling loop's later result collection.
pub(crate) fn result_ready(
thread_id: ThreadId,
turn_id: &str,
tool_name: &ToolName,
call_id: &str,
source: Source,
dispatch_to_result_ready: Duration,
) {
tracing::event!(
name: "codex.tool_result_ready",
target: "codex_otel.trace_safe",
tracing::Level::INFO,
event.name = "codex.tool_result_ready",
dispatch_to_result_ready_ms = dispatch_to_result_ready.as_secs_f64() * 1000.0,
conversation.id = %thread_id,
turn_id,
call_id,
tool_name = tool_name.name.as_str(),
tool_namespace = namespace(tool_name),
tool_source = source.as_str(),
);
}
pub(crate) fn namespace(tool_name: &ToolName) -> &str {
tool_name
.namespace
.as_deref()
.filter(|namespace| !namespace.is_empty())
.unwrap_or(DEFAULT_FUNCTION_NAMESPACE)
}