propagate dynamic tool failures in code mode

This commit is contained in:
Abhinav Vedmala
2026-06-22 17:21:43 -07:00
parent 97dc6abb11
commit 8653edb49d
2 changed files with 48 additions and 5 deletions

View File

@@ -2,6 +2,7 @@ use crate::function_tool::FunctionCallError;
use crate::session::session::Session;
use crate::session::turn_context::TurnContext;
use crate::tools::context::FunctionToolOutput;
use crate::tools::context::ToolCallSource;
use crate::tools::context::ToolInvocation;
use crate::tools::context::ToolPayload;
use crate::tools::context::boxed_tool_output;
@@ -121,6 +122,7 @@ impl DynamicToolHandler {
turn,
call_id,
payload,
source,
..
} = invocation;
@@ -156,10 +158,19 @@ impl DynamicToolHandler {
.into_iter()
.map(FunctionCallOutputContentItem::from)
.collect::<Vec<_>>();
Ok(boxed_tool_output(FunctionToolOutput::from_content(
body,
Some(success),
)))
let output = FunctionToolOutput::from_content(body, Some(success));
if !success && matches!(source, ToolCallSource::CodeMode { .. }) {
let message = output.into_text();
return Err(FunctionCallError::RespondToModel(
if message.trim().is_empty() {
"dynamic tool call failed".to_string()
} else {
message
},
));
}
Ok(boxed_tool_output(output))
}
}

View File

@@ -3423,7 +3423,7 @@ text(JSON.stringify(tool));
}
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
async fn code_mode_can_call_hidden_dynamic_tools() -> Result<()> {
async fn code_mode_can_call_hidden_dynamic_tools_and_catch_failures() -> Result<()> {
skip_if_no_network!(Ok(()));
let server = responses::start_mock_server().await;
@@ -3463,11 +3463,18 @@ async fn code_mode_can_call_hidden_dynamic_tools() -> Result<()> {
let code = r#"
const tool = ALL_TOOLS.find(({ name }) => name === "codex_app__hidden_dynamic_tool");
const out = await tools.codex_app__hidden_dynamic_tool({ city: "Paris" });
let error = null;
try {
await tools.codex_app__hidden_dynamic_tool({ city: "London" });
} catch (caught) {
error = caught?.message ?? String(caught);
}
text(
JSON.stringify({
name: tool?.name ?? null,
description: tool?.description ?? null,
out,
error,
})
);
"#;
@@ -3549,6 +3556,25 @@ text(
},
})
.await?;
let request = wait_for_event_match(&test.codex, |event| match event {
EventMsg::DynamicToolCallRequest(request) => Some(request.clone()),
_ => None,
})
.await;
assert_eq!(request.namespace.as_deref(), Some("codex_app"));
assert_eq!(request.tool, "hidden_dynamic_tool");
assert_eq!(request.arguments, serde_json::json!({ "city": "London" }));
test.codex
.submit(Op::DynamicToolResponse {
id: request.call_id,
response: DynamicToolResponse {
content_items: vec![DynamicToolCallOutputContentItem::InputText {
text: "automation_update received invalid arguments.".to_string(),
}],
success: false,
},
})
.await?;
wait_for_event(&test.codex, |event| match event {
EventMsg::TurnComplete(event) => event.turn_id == turn_id,
_ => false,
@@ -3575,6 +3601,12 @@ text(
parsed.get("out"),
Some(&Value::String("hidden-ok".to_string()))
);
assert_eq!(
parsed.get("error"),
Some(&Value::String(
"automation_update received invalid arguments.".to_string()
))
);
assert!(
parsed
.get("description")