mirror of
https://github.com/openai/codex.git
synced 2026-09-10 20:26:47 +00:00
propagate dynamic tool failures in code mode
This commit is contained in:
@@ -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))
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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")
|
||||
|
||||
Reference in New Issue
Block a user