add tool override marker to tool output

This commit is contained in:
Qiyao Qin
2026-03-14 16:22:35 -07:00
parent 6ac7664d4f
commit 39e83fee71
8 changed files with 99 additions and 2 deletions
+4
View File
@@ -111,6 +111,7 @@ fn is_shell_tool_name(name: &str) -> bool {
struct ExecOutputJson {
output: String,
metadata: ExecOutputMetadataJson,
executed_command: Option<Vec<String>>,
}
#[derive(Deserialize)]
@@ -131,6 +132,9 @@ fn build_structured_output(parsed: &ExecOutputJson) -> String {
"Wall time: {} seconds",
parsed.metadata.duration_seconds
));
if let Some(executed_command) = parsed.executed_command.as_ref() {
sections.push(format!("Executed command: {}", executed_command.join(" ")));
}
let mut output = parsed.output.clone();
if let Some((stripped, total_lines)) = strip_total_output_header(&parsed.output) {
+38
View File
@@ -196,6 +196,44 @@ fn reserializes_shell_outputs_for_function_and_custom_tool_calls() {
);
}
#[test]
fn reserializes_shell_outputs_preserve_executed_command_when_present() {
let raw_output = r#"{"output":"override-ok","metadata":{"exit_code":0,"duration_seconds":0.5},"executed_command":["/bin/echo","override-ok"]}"#;
let expected_output = "Exit code: 0\nWall time: 0.5 seconds\nExecuted command: /bin/echo override-ok\nOutput:\noverride-ok";
let mut items = vec![
ResponseItem::FunctionCall {
id: None,
name: "shell".to_string(),
namespace: None,
arguments: "{}".to_string(),
call_id: "call-1".to_string(),
},
ResponseItem::FunctionCallOutput {
call_id: "call-1".to_string(),
output: FunctionCallOutputPayload::from_text(raw_output.to_string()),
},
];
reserialize_shell_outputs(&mut items);
assert_eq!(
items,
vec![
ResponseItem::FunctionCall {
id: None,
name: "shell".to_string(),
namespace: None,
arguments: "{}".to_string(),
call_id: "call-1".to_string(),
},
ResponseItem::FunctionCallOutput {
call_id: "call-1".to_string(),
output: FunctionCallOutputPayload::from_text(expected_output.to_string()),
},
]
);
}
#[test]
fn tool_search_output_namespace_serializes_with_deferred_child_tools() {
let namespace = tools::ToolSearchOutputTool::Namespace(tools::ResponsesApiNamespace {
+18 -1
View File
@@ -90,6 +90,7 @@ pub(crate) async fn emit_exec_command_begin(
pub(crate) enum ToolEmitter {
Shell {
command: Vec<String>,
is_command_overridden: bool,
cwd: PathBuf,
source: ExecCommandSource,
parsed_cmd: Vec<ParsedCommand>,
@@ -111,6 +112,7 @@ pub(crate) enum ToolEmitter {
impl ToolEmitter {
pub fn shell(
command: Vec<String>,
is_command_overridden: bool,
cwd: PathBuf,
source: ExecCommandSource,
freeform: bool,
@@ -118,6 +120,7 @@ impl ToolEmitter {
let parsed_cmd = parse_command(&command);
Self::Shell {
command,
is_command_overridden,
cwd,
source,
parsed_cmd,
@@ -153,6 +156,7 @@ impl ToolEmitter {
(
Self::Shell {
command,
is_command_overridden: _,
cwd,
source,
parsed_cmd,
@@ -292,7 +296,20 @@ impl ToolEmitter {
Self::Shell { freeform: true, .. } => {
super::format_exec_output_for_model_freeform(output, ctx.turn.truncation_policy)
}
_ => super::format_exec_output_for_model_structured(output, ctx.turn.truncation_policy),
Self::Shell {
command,
is_command_overridden,
..
} => super::format_exec_output_for_model_structured(
output,
ctx.turn.truncation_policy,
is_command_overridden.then_some(command.as_slice()),
),
_ => super::format_exec_output_for_model_structured(
output,
ctx.turn.truncation_policy,
None,
),
}
}
@@ -223,6 +223,7 @@ fn default_runtime_manager(codex_home: std::path::PathBuf) -> ArtifactRuntimeMan
async fn emit_exec_begin(session: &Session, turn: &TurnContext, call_id: &str) {
let emitter = ToolEmitter::shell(
vec![ARTIFACTS_TOOL_NAME.to_string()],
false,
turn.cwd.clone(),
ExecCommandSource::Agent,
true,
@@ -249,6 +250,7 @@ async fn emit_exec_end(
};
let emitter = ToolEmitter::shell(
vec![ARTIFACTS_TOOL_NAME.to_string()],
false,
turn.cwd.clone(),
ExecCommandSource::Agent,
true,
@@ -61,6 +61,7 @@ async fn emit_js_repl_exec_begin(
) {
let emitter = ToolEmitter::shell(
vec!["js_repl".to_string()],
false,
turn.cwd.clone(),
ExecCommandSource::Agent,
false,
@@ -80,6 +81,7 @@ async fn emit_js_repl_exec_end(
let exec_output = build_js_repl_exec_output(output, error, duration);
let emitter = ToolEmitter::shell(
vec!["js_repl".to_string()],
false,
turn.cwd.clone(),
ExecCommandSource::Agent,
false,
+4 -1
View File
@@ -474,8 +474,11 @@ impl ShellHandler {
)
.await
.map(|result| result.output);
let effective_command = effective_command.lock().await.clone();
let is_command_overridden = effective_command != exec_params.command;
let emitter = ToolEmitter::shell(
effective_command.lock().await.clone(),
effective_command,
is_command_overridden,
exec_params.cwd.clone(),
source,
freeform,
+30
View File
@@ -32,6 +32,7 @@ pub(crate) const TELEMETRY_PREVIEW_TRUNCATION_NOTICE: &str =
pub fn format_exec_output_for_model_structured(
exec_output: &ExecToolCallOutput,
truncation_policy: TruncationPolicy,
executed_command: Option<&[String]>,
) -> String {
let ExecToolCallOutput {
exit_code,
@@ -49,6 +50,8 @@ pub fn format_exec_output_for_model_structured(
struct ExecOutput<'a> {
output: &'a str,
metadata: ExecMetadata,
#[serde(skip_serializing_if = "Option::is_none")]
executed_command: Option<&'a [String]>,
}
// round to 1 decimal place
@@ -62,6 +65,7 @@ pub fn format_exec_output_for_model_structured(
exit_code: *exit_code,
duration_seconds,
},
executed_command,
};
#[expect(clippy::expect_used)]
@@ -117,3 +121,29 @@ fn build_content_with_timeout(exec_output: &ExecToolCallOutput) -> String {
exec_output.aggregated_output.text.clone()
}
}
#[cfg(test)]
mod tests {
use super::*;
use std::time::Duration;
#[test]
fn format_exec_output_for_model_structured_includes_executed_command_when_present() {
let exec_output = ExecToolCallOutput {
aggregated_output: crate::exec::StreamOutput::new("override-ok\n".to_string()),
duration: Duration::from_millis(100),
..ExecToolCallOutput::default()
};
let formatted = format_exec_output_for_model_structured(
&exec_output,
TruncationPolicy::Bytes(1024),
Some(&["/bin/echo".to_string(), "override-ok".to_string()]),
);
assert_eq!(
formatted,
r#"{"output":"override-ok\n","metadata":{"exit_code":0,"duration_seconds":0.1},"executed_command":["/bin/echo","override-ok"]}"#
);
}
}
@@ -228,6 +228,7 @@ impl ToolRuntime<ShellRequest, ExecToolCallOutput> for ShellRuntime {
let base_command = ctx.command_override.as_ref().unwrap_or(&req.command);
crate::tools::events::ToolEmitter::shell(
base_command.to_vec(),
false,
req.cwd.clone(),
ExecCommandSource::Agent,
false,