mirror of
https://github.com/openai/codex.git
synced 2026-11-02 19:43:24 +00:00
add tool override marker to tool output
This commit is contained in:
@@ -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) {
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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,
|
||||
|
||||
Reference in New Issue
Block a user