Files
codex/codex-rs/core/src/tools/mod.rs
T
ningyi-oai 0d46c252b3 Encapsulate executed tool call metadata recording (#44002)
## What changed

Introduce `ExecutedToolCalls` to own optional shared recorder state and centralize feature checks, call recording, and accepted result source lookup. Update session, tool runtime, MCP, and Code Mode callers to use this interface.

Move prompt attachment, retry handling, and metadata budgeting into the `request_metadata` module, preserving existing recording and request behavior.

## Testing

Add coverage for all session and turn feature combinations, lazy result source lookup, and unchanged prompts when recording is disabled. Retain the existing metadata budget and cell completeness tests in the new module.

GitOrigin-RevId: bc19f34317232b6599c7ec71c15db4a80123ff6f
2026-09-09 02:33:17 +00:00

143 lines
4.9 KiB
Rust

mod approvals;
pub(crate) mod code_mode;
pub(crate) mod context;
mod control_tool_analytics;
pub(crate) mod events;
mod executed_tool_calls;
pub(crate) mod handlers;
pub(crate) mod hook_names;
pub(crate) mod hosted_spec;
pub(crate) mod lifecycle;
pub(crate) mod network_approval;
pub(crate) mod orchestrator;
pub(crate) mod parallel;
pub(crate) mod registry;
pub(crate) mod router;
pub(crate) mod runtimes;
pub(crate) mod sandboxing;
pub(crate) mod spec_plan;
pub(crate) mod tool_dispatch_trace;
mod tool_namespaces_info;
use std::borrow::Cow;
use crate::session::turn_context::TurnContext;
pub(crate) use approvals::ApprovalContext;
use codex_features::Feature;
use codex_protocol::exec_output::ExecToolCallOutput;
use codex_protocol::openai_models::ModelInfo;
use codex_protocol::openai_models::ToolMode;
use codex_tools::ToolName;
use codex_utils_output_truncation::TruncationPolicy;
use codex_utils_output_truncation::formatted_truncate_text;
use codex_utils_output_truncation::truncate_text;
pub(crate) use executed_tool_calls::ExecutedToolCalls;
pub use router::ToolRouter;
/// Legacy boundaries such as hook payloads, telemetry tags, and Responses tool
/// names still require a single flattened string. Keep comparisons and sorting
/// on `ToolName` itself; use this only when crossing those boundaries.
pub(crate) fn flat_tool_name(tool_name: &ToolName) -> Cow<'_, str> {
if tool_name.is_default_namespace() {
return Cow::Borrowed(tool_name.name.as_str());
}
match tool_name.namespace.as_deref() {
Some(namespace) => {
let mut name = String::with_capacity(namespace.len() + tool_name.name.len());
name.push_str(namespace);
name.push_str(&tool_name.name);
Cow::Owned(name)
}
None => Cow::Borrowed(tool_name.name.as_str()),
}
}
pub(crate) fn tool_user_shell_type(
user_shell: &crate::shell::Shell,
) -> codex_tools::ToolUserShellType {
match user_shell.shell_type {
crate::shell::ShellType::Zsh => codex_tools::ToolUserShellType::Zsh,
crate::shell::ShellType::Bash => codex_tools::ToolUserShellType::Bash,
crate::shell::ShellType::PowerShell => codex_tools::ToolUserShellType::PowerShell,
crate::shell::ShellType::Sh => codex_tools::ToolUserShellType::Sh,
crate::shell::ShellType::Cmd => codex_tools::ToolUserShellType::Cmd,
}
}
pub(crate) fn requested_tool_mode(turn_context: &TurnContext, model_info: &ModelInfo) -> ToolMode {
model_info.tool_mode.unwrap_or_else(|| {
if turn_context.config.features.enabled(Feature::CodeModeOnly) {
ToolMode::CodeModeOnly
} else if turn_context.config.features.enabled(Feature::CodeMode) {
ToolMode::CodeMode
} else {
ToolMode::Direct
}
})
}
pub(crate) fn effective_tool_mode(turn_context: &TurnContext, model_info: &ModelInfo) -> ToolMode {
let requested_tool_mode = requested_tool_mode(turn_context, model_info);
if !turn_context.code_mode_available
&& requested_tool_mode == ToolMode::CodeMode
&& !turn_context.config.code_mode.disable_in_process_fallback
{
ToolMode::Direct
} else {
requested_tool_mode
}
}
/// Format the combined exec output for sending back to the model.
/// Includes exit code and duration metadata; truncates large bodies safely.
pub fn format_exec_output_for_model(
exec_output: &ExecToolCallOutput,
truncation_policy: TruncationPolicy,
) -> String {
// round to 1 decimal place
let duration_seconds = ((exec_output.duration.as_secs_f32()) * 10.0).round() / 10.0;
let content = build_content_with_timeout(exec_output);
let total_lines = content.lines().count();
let formatted_output = truncate_text(&content, truncation_policy);
let mut sections = Vec::new();
sections.push(format!("Exit code: {}", exec_output.exit_code));
sections.push(format!("Wall time: {duration_seconds} seconds"));
if total_lines != formatted_output.lines().count() {
sections.push(format!("Total output lines: {total_lines}"));
}
sections.push("Output:".to_string());
sections.push(formatted_output);
sections.join("\n")
}
pub fn format_exec_output_str(
exec_output: &ExecToolCallOutput,
truncation_policy: TruncationPolicy,
) -> String {
let content = build_content_with_timeout(exec_output);
// Truncate for model consumption before serialization.
formatted_truncate_text(&content, truncation_policy)
}
/// Extracts exec output content and prepends a timeout message if the command timed out.
fn build_content_with_timeout(exec_output: &ExecToolCallOutput) -> String {
if exec_output.timed_out {
format!(
"command timed out after {} milliseconds\n{}",
exec_output.duration.as_millis(),
exec_output.aggregated_output.text
)
} else {
exec_output.aggregated_output.text.clone()
}
}