mirror of
https://github.com/pchuan98/codex.git
synced 2026-07-01 00:31:56 +08:00
Preserve namespaces on custom tool calls (#30302)
## Summary - Preserve the optional namespace on custom tool calls during response deserialization and app-server replay. - Use the namespaced tool identifier for streaming argument handling and tool dispatch. - Regenerate app-server protocol schemas. - Add regression tests covering namespace serialization and routing. ## Testing - Ran affected protocol and app-server test suites. - Ran the full core test suite; two load-sensitive timing tests passed when rerun individually. - Ran Clippy and formatting checks. - Verified with a local end-to-end app-server replay that the namespace is preserved through the complete request/response flow.
This commit is contained in:
committed by
GitHub
Unverified
parent
c464468493
commit
328e95110c
+14
-8
@@ -2683,6 +2683,12 @@
|
||||
"name": {
|
||||
"type": "string"
|
||||
},
|
||||
"namespace": {
|
||||
"type": [
|
||||
"string",
|
||||
"null"
|
||||
]
|
||||
},
|
||||
"status": {
|
||||
"type": [
|
||||
"string",
|
||||
@@ -3631,6 +3637,13 @@
|
||||
"ephemeral": {
|
||||
"type": "boolean"
|
||||
},
|
||||
"lastTurnId": {
|
||||
"description": "Optional last turn id to fork through, inclusive.\n\nWhen specified, turns after `last_turn_id` are omitted from the fork. The referenced turn cannot be in progress.",
|
||||
"type": [
|
||||
"string",
|
||||
"null"
|
||||
]
|
||||
},
|
||||
"model": {
|
||||
"description": "Configuration overrides for the forked thread, if any.",
|
||||
"type": [
|
||||
@@ -3673,13 +3686,6 @@
|
||||
}
|
||||
],
|
||||
"description": "Optional client-supplied analytics source classification for this forked thread."
|
||||
},
|
||||
"lastTurnId": {
|
||||
"description": "Optional last turn id to fork through, inclusive.\n\nWhen specified, turns after `last_turn_id` are omitted from the fork. The referenced turn cannot be in progress.",
|
||||
"type": [
|
||||
"string",
|
||||
"null"
|
||||
]
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
@@ -6873,4 +6879,4 @@
|
||||
}
|
||||
],
|
||||
"title": "ClientRequest"
|
||||
}
|
||||
}
|
||||
+14
-8
@@ -15510,6 +15510,12 @@
|
||||
"name": {
|
||||
"type": "string"
|
||||
},
|
||||
"namespace": {
|
||||
"type": [
|
||||
"string",
|
||||
"null"
|
||||
]
|
||||
},
|
||||
"status": {
|
||||
"type": [
|
||||
"string",
|
||||
@@ -17279,6 +17285,13 @@
|
||||
"ephemeral": {
|
||||
"type": "boolean"
|
||||
},
|
||||
"lastTurnId": {
|
||||
"description": "Optional last turn id to fork through, inclusive.\n\nWhen specified, turns after `last_turn_id` are omitted from the fork. The referenced turn cannot be in progress.",
|
||||
"type": [
|
||||
"string",
|
||||
"null"
|
||||
]
|
||||
},
|
||||
"model": {
|
||||
"description": "Configuration overrides for the forked thread, if any.",
|
||||
"type": [
|
||||
@@ -17321,13 +17334,6 @@
|
||||
}
|
||||
],
|
||||
"description": "Optional client-supplied analytics source classification for this forked thread."
|
||||
},
|
||||
"lastTurnId": {
|
||||
"description": "Optional last turn id to fork through, inclusive.\n\nWhen specified, turns after `last_turn_id` are omitted from the fork. The referenced turn cannot be in progress.",
|
||||
"type": [
|
||||
"string",
|
||||
"null"
|
||||
]
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
@@ -20874,4 +20880,4 @@
|
||||
},
|
||||
"title": "CodexAppServerProtocol",
|
||||
"type": "object"
|
||||
}
|
||||
}
|
||||
+14
-8
@@ -11914,6 +11914,12 @@
|
||||
"name": {
|
||||
"type": "string"
|
||||
},
|
||||
"namespace": {
|
||||
"type": [
|
||||
"string",
|
||||
"null"
|
||||
]
|
||||
},
|
||||
"status": {
|
||||
"type": [
|
||||
"string",
|
||||
@@ -15058,6 +15064,13 @@
|
||||
"ephemeral": {
|
||||
"type": "boolean"
|
||||
},
|
||||
"lastTurnId": {
|
||||
"description": "Optional last turn id to fork through, inclusive.\n\nWhen specified, turns after `last_turn_id` are omitted from the fork. The referenced turn cannot be in progress.",
|
||||
"type": [
|
||||
"string",
|
||||
"null"
|
||||
]
|
||||
},
|
||||
"model": {
|
||||
"description": "Configuration overrides for the forked thread, if any.",
|
||||
"type": [
|
||||
@@ -15100,13 +15113,6 @@
|
||||
}
|
||||
],
|
||||
"description": "Optional client-supplied analytics source classification for this forked thread."
|
||||
},
|
||||
"lastTurnId": {
|
||||
"description": "Optional last turn id to fork through, inclusive.\n\nWhen specified, turns after `last_turn_id` are omitted from the fork. The referenced turn cannot be in progress.",
|
||||
"type": [
|
||||
"string",
|
||||
"null"
|
||||
]
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
@@ -18652,4 +18658,4 @@
|
||||
},
|
||||
"title": "CodexAppServerProtocolV2",
|
||||
"type": "object"
|
||||
}
|
||||
}
|
||||
+6
@@ -751,6 +751,12 @@
|
||||
"name": {
|
||||
"type": "string"
|
||||
},
|
||||
"namespace": {
|
||||
"type": [
|
||||
"string",
|
||||
"null"
|
||||
]
|
||||
},
|
||||
"status": {
|
||||
"type": [
|
||||
"string",
|
||||
|
||||
@@ -126,6 +126,13 @@
|
||||
"ephemeral": {
|
||||
"type": "boolean"
|
||||
},
|
||||
"lastTurnId": {
|
||||
"description": "Optional last turn id to fork through, inclusive.\n\nWhen specified, turns after `last_turn_id` are omitted from the fork. The referenced turn cannot be in progress.",
|
||||
"type": [
|
||||
"string",
|
||||
"null"
|
||||
]
|
||||
},
|
||||
"model": {
|
||||
"description": "Configuration overrides for the forked thread, if any.",
|
||||
"type": [
|
||||
@@ -168,13 +175,6 @@
|
||||
}
|
||||
],
|
||||
"description": "Optional client-supplied analytics source classification for this forked thread."
|
||||
},
|
||||
"lastTurnId": {
|
||||
"description": "Optional last turn id to fork through, inclusive.\n\nWhen specified, turns after `last_turn_id` are omitted from the fork. The referenced turn cannot be in progress.",
|
||||
"type": [
|
||||
"string",
|
||||
"null"
|
||||
]
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
@@ -182,4 +182,4 @@
|
||||
],
|
||||
"title": "ThreadForkParams",
|
||||
"type": "object"
|
||||
}
|
||||
}
|
||||
@@ -821,6 +821,12 @@
|
||||
"name": {
|
||||
"type": "string"
|
||||
},
|
||||
"namespace": {
|
||||
"type": [
|
||||
"string",
|
||||
"null"
|
||||
]
|
||||
},
|
||||
"status": {
|
||||
"type": [
|
||||
"string",
|
||||
|
||||
@@ -20,4 +20,4 @@ id?: string,
|
||||
/**
|
||||
* Set when using the Responses API.
|
||||
*/
|
||||
call_id: string | null, status: LocalShellStatus, action: LocalShellAction, internal_chat_message_metadata_passthrough?: InternalChatMessageMetadataPassthrough, } | { "type": "function_call", id?: string, name: string, namespace?: string, arguments: string, call_id: string, internal_chat_message_metadata_passthrough?: InternalChatMessageMetadataPassthrough, } | { "type": "tool_search_call", id?: string, call_id: string | null, status?: string, execution: string, arguments: unknown, internal_chat_message_metadata_passthrough?: InternalChatMessageMetadataPassthrough, } | { "type": "function_call_output", id?: string, call_id: string, output: FunctionCallOutputBody, internal_chat_message_metadata_passthrough?: InternalChatMessageMetadataPassthrough, } | { "type": "custom_tool_call", id?: string, status?: string, call_id: string, name: string, input: string, internal_chat_message_metadata_passthrough?: InternalChatMessageMetadataPassthrough, } | { "type": "custom_tool_call_output", id?: string, call_id: string, name?: string, output: FunctionCallOutputBody, internal_chat_message_metadata_passthrough?: InternalChatMessageMetadataPassthrough, } | { "type": "tool_search_output", id?: string, call_id: string | null, status: string, execution: string, tools: unknown[], internal_chat_message_metadata_passthrough?: InternalChatMessageMetadataPassthrough, } | { "type": "web_search_call", id?: string, status?: string, action?: WebSearchAction, internal_chat_message_metadata_passthrough?: InternalChatMessageMetadataPassthrough, } | { "type": "image_generation_call", id?: string, status: string, revised_prompt?: string, result: string, internal_chat_message_metadata_passthrough?: InternalChatMessageMetadataPassthrough, } | { "type": "compaction", id?: string, encrypted_content: string, internal_chat_message_metadata_passthrough?: InternalChatMessageMetadataPassthrough, } | { "type": "compaction_trigger", } | { "type": "context_compaction", id?: string, encrypted_content?: string, internal_chat_message_metadata_passthrough?: InternalChatMessageMetadataPassthrough, } | { "type": "other" };
|
||||
call_id: string | null, status: LocalShellStatus, action: LocalShellAction, internal_chat_message_metadata_passthrough?: InternalChatMessageMetadataPassthrough, } | { "type": "function_call", id?: string, name: string, namespace?: string, arguments: string, call_id: string, internal_chat_message_metadata_passthrough?: InternalChatMessageMetadataPassthrough, } | { "type": "tool_search_call", id?: string, call_id: string | null, status?: string, execution: string, arguments: unknown, internal_chat_message_metadata_passthrough?: InternalChatMessageMetadataPassthrough, } | { "type": "function_call_output", id?: string, call_id: string, output: FunctionCallOutputBody, internal_chat_message_metadata_passthrough?: InternalChatMessageMetadataPassthrough, } | { "type": "custom_tool_call", id?: string, status?: string, call_id: string, name: string, namespace?: string, input: string, internal_chat_message_metadata_passthrough?: InternalChatMessageMetadataPassthrough, } | { "type": "custom_tool_call_output", id?: string, call_id: string, name?: string, output: FunctionCallOutputBody, internal_chat_message_metadata_passthrough?: InternalChatMessageMetadataPassthrough, } | { "type": "tool_search_output", id?: string, call_id: string | null, status: string, execution: string, tools: unknown[], internal_chat_message_metadata_passthrough?: InternalChatMessageMetadataPassthrough, } | { "type": "web_search_call", id?: string, status?: string, action?: WebSearchAction, internal_chat_message_metadata_passthrough?: InternalChatMessageMetadataPassthrough, } | { "type": "image_generation_call", id?: string, status: string, revised_prompt?: string, result: string, internal_chat_message_metadata_passthrough?: InternalChatMessageMetadataPassthrough, } | { "type": "compaction", id?: string, encrypted_content: string, internal_chat_message_metadata_passthrough?: InternalChatMessageMetadataPassthrough, } | { "type": "compaction_trigger", } | { "type": "context_compaction", id?: string, encrypted_content?: string, internal_chat_message_metadata_passthrough?: InternalChatMessageMetadataPassthrough, } | { "type": "other" };
|
||||
|
||||
@@ -80,6 +80,7 @@ async fn forward_events_cancelled_while_send_blocked_shuts_down_delegate() {
|
||||
status: None,
|
||||
call_id: "call-1".to_string(),
|
||||
name: "tool".to_string(),
|
||||
namespace: None,
|
||||
input: "{}".to_string(),
|
||||
internal_chat_message_metadata_passthrough: None,
|
||||
},
|
||||
|
||||
@@ -513,6 +513,7 @@ fn for_prompt_strips_images_when_model_does_not_support_images() {
|
||||
status: None,
|
||||
call_id: "tool-1".to_string(),
|
||||
name: "js_repl".to_string(),
|
||||
namespace: None,
|
||||
input: "view_image".to_string(),
|
||||
internal_chat_message_metadata_passthrough: None,
|
||||
},
|
||||
@@ -582,6 +583,7 @@ fn for_prompt_strips_images_when_model_does_not_support_images() {
|
||||
status: None,
|
||||
call_id: "tool-1".to_string(),
|
||||
name: "js_repl".to_string(),
|
||||
namespace: None,
|
||||
input: "view_image".to_string(),
|
||||
internal_chat_message_metadata_passthrough: None,
|
||||
},
|
||||
@@ -1090,6 +1092,7 @@ fn remove_first_item_handles_custom_tool_pair() {
|
||||
status: None,
|
||||
call_id: "tool-1".to_string(),
|
||||
name: "my_tool".to_string(),
|
||||
namespace: None,
|
||||
input: "{}".to_string(),
|
||||
internal_chat_message_metadata_passthrough: None,
|
||||
},
|
||||
@@ -1378,6 +1381,7 @@ fn normalize_adds_missing_output_for_custom_tool_call() {
|
||||
status: None,
|
||||
call_id: "tool-x".to_string(),
|
||||
name: "custom".to_string(),
|
||||
namespace: None,
|
||||
input: "{}".to_string(),
|
||||
internal_chat_message_metadata_passthrough: None,
|
||||
}];
|
||||
@@ -1393,6 +1397,7 @@ fn normalize_adds_missing_output_for_custom_tool_call() {
|
||||
status: None,
|
||||
call_id: "tool-x".to_string(),
|
||||
name: "custom".to_string(),
|
||||
namespace: None,
|
||||
input: "{}".to_string(),
|
||||
internal_chat_message_metadata_passthrough: None,
|
||||
},
|
||||
@@ -1512,6 +1517,7 @@ fn normalize_mixed_inserts_and_removals() {
|
||||
status: None,
|
||||
call_id: "t1".to_string(),
|
||||
name: "tool".to_string(),
|
||||
namespace: None,
|
||||
input: "{}".to_string(),
|
||||
internal_chat_message_metadata_passthrough: None,
|
||||
},
|
||||
@@ -1556,6 +1562,7 @@ fn normalize_mixed_inserts_and_removals() {
|
||||
status: None,
|
||||
call_id: "t1".to_string(),
|
||||
name: "tool".to_string(),
|
||||
namespace: None,
|
||||
input: "{}".to_string(),
|
||||
internal_chat_message_metadata_passthrough: None,
|
||||
},
|
||||
@@ -1668,6 +1675,7 @@ fn normalize_adds_missing_output_for_custom_tool_call_panics_in_debug() {
|
||||
status: None,
|
||||
call_id: "tool-x".to_string(),
|
||||
name: "custom".to_string(),
|
||||
namespace: None,
|
||||
input: "{}".to_string(),
|
||||
internal_chat_message_metadata_passthrough: None,
|
||||
}];
|
||||
@@ -1810,6 +1818,7 @@ fn normalize_mixed_inserts_and_removals_panics_in_debug() {
|
||||
status: None,
|
||||
call_id: "t1".to_string(),
|
||||
name: "tool".to_string(),
|
||||
namespace: None,
|
||||
input: "{}".to_string(),
|
||||
internal_chat_message_metadata_passthrough: None,
|
||||
},
|
||||
|
||||
@@ -10482,6 +10482,7 @@ async fn fatal_tool_error_stops_turn_and_reports_error() {
|
||||
status: None,
|
||||
call_id: "call-1".to_string(),
|
||||
name: "shell_command".to_string(),
|
||||
namespace: None,
|
||||
input: "{}".to_string(),
|
||||
internal_chat_message_metadata_passthrough: None,
|
||||
};
|
||||
|
||||
@@ -2080,8 +2080,14 @@ async fn try_run_sampling_request(
|
||||
}
|
||||
}
|
||||
ResponseEvent::OutputItemAdded(item) => {
|
||||
if let ResponseItem::CustomToolCall { call_id, name, .. } = &item {
|
||||
let tool_name = ToolName::plain(name.as_str());
|
||||
if let ResponseItem::CustomToolCall {
|
||||
call_id,
|
||||
name,
|
||||
namespace,
|
||||
..
|
||||
} = &item
|
||||
{
|
||||
let tool_name = ToolName::new(namespace.clone(), name.as_str());
|
||||
active_tool_argument_diff_consumer = tool_runtime
|
||||
.create_diff_consumer(&tool_name)
|
||||
.map(|consumer| (call_id.clone(), consumer));
|
||||
|
||||
@@ -116,6 +116,7 @@ fn external_context_pollution_items_exclude_local_tool_calls() {
|
||||
status: None,
|
||||
call_id: "custom-1".to_string(),
|
||||
name: "apply_patch".to_string(),
|
||||
namespace: None,
|
||||
input: "*** Begin Patch\n*** End Patch\n".to_string(),
|
||||
internal_chat_message_metadata_passthrough: None,
|
||||
},
|
||||
|
||||
@@ -25,7 +25,7 @@ use tracing::instrument;
|
||||
|
||||
pub use crate::tools::context::ToolCallSource;
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
#[derive(Clone, Debug, PartialEq)]
|
||||
pub struct ToolCall {
|
||||
pub tool_name: ToolName,
|
||||
pub call_id: String,
|
||||
@@ -147,11 +147,12 @@ impl ToolRouter {
|
||||
ResponseItem::ToolSearchCall { .. } => Ok(None),
|
||||
ResponseItem::CustomToolCall {
|
||||
name,
|
||||
namespace,
|
||||
input,
|
||||
call_id,
|
||||
..
|
||||
} => Ok(Some(ToolCall {
|
||||
tool_name: ToolName::plain(name),
|
||||
tool_name: ToolName::new(namespace, name),
|
||||
call_id,
|
||||
payload: ToolPayload::Custom { input },
|
||||
})),
|
||||
|
||||
@@ -179,6 +179,35 @@ async fn build_tool_call_uses_namespace_for_registry_name() -> anyhow::Result<()
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn build_custom_tool_call_uses_namespace_for_registry_name() -> anyhow::Result<()> {
|
||||
let tool_name = "exec".to_string();
|
||||
|
||||
let call = ToolRouter::build_tool_call(ResponseItem::CustomToolCall {
|
||||
id: None,
|
||||
status: None,
|
||||
call_id: "call-namespace".to_string(),
|
||||
name: tool_name.clone(),
|
||||
namespace: Some("mcp__python".to_string()),
|
||||
input: "print('hello')".to_string(),
|
||||
internal_chat_message_metadata_passthrough: None,
|
||||
})?
|
||||
.expect("custom_tool_call should produce a tool call");
|
||||
|
||||
assert_eq!(
|
||||
call,
|
||||
ToolCall {
|
||||
tool_name: ToolName::namespaced("mcp__python", tool_name),
|
||||
call_id: "call-namespace".to_string(),
|
||||
payload: ToolPayload::Custom {
|
||||
input: "print('hello')".to_string(),
|
||||
},
|
||||
}
|
||||
);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn mcp_parallel_support_uses_handler_data() -> anyhow::Result<()> {
|
||||
let (_, turn) = make_session_and_context().await;
|
||||
|
||||
@@ -121,6 +121,7 @@ fn response_item_records_turn_ttft_for_first_output_signals() {
|
||||
status: None,
|
||||
call_id: "call-2".to_string(),
|
||||
name: "custom".to_string(),
|
||||
namespace: None,
|
||||
input: "echo hi".to_string(),
|
||||
internal_chat_message_metadata_passthrough: None,
|
||||
}
|
||||
|
||||
@@ -895,6 +895,24 @@ pub fn ev_custom_tool_call(call_id: &str, name: &str, input: &str) -> Value {
|
||||
})
|
||||
}
|
||||
|
||||
pub fn ev_custom_tool_call_with_namespace(
|
||||
call_id: &str,
|
||||
namespace: &str,
|
||||
name: &str,
|
||||
input: &str,
|
||||
) -> Value {
|
||||
serde_json::json!({
|
||||
"type": "response.output_item.done",
|
||||
"item": {
|
||||
"type": "custom_tool_call",
|
||||
"call_id": call_id,
|
||||
"namespace": namespace,
|
||||
"name": name,
|
||||
"input": input
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
pub fn ev_local_shell_call(call_id: &str, status: &str, command: Vec<&str>) -> Value {
|
||||
serde_json::json!({
|
||||
"type": "response.output_item.done",
|
||||
|
||||
@@ -785,6 +785,7 @@ async fn resume_replays_legacy_js_repl_image_rollout_shapes() {
|
||||
status: None,
|
||||
call_id: "legacy-js-call".to_string(),
|
||||
name: "js_repl".to_string(),
|
||||
namespace: None,
|
||||
input: "console.log('legacy image flow')".to_string(),
|
||||
internal_chat_message_metadata_passthrough: None,
|
||||
};
|
||||
@@ -976,6 +977,7 @@ async fn resume_replays_image_tool_outputs_with_detail() {
|
||||
status: Some("completed".to_string()),
|
||||
call_id: custom_call_id.to_string(),
|
||||
name: "js_repl".to_string(),
|
||||
namespace: None,
|
||||
input: "console.log('image flow')".to_string(),
|
||||
internal_chat_message_metadata_passthrough: None,
|
||||
}),
|
||||
@@ -2912,6 +2914,7 @@ async fn azure_responses_request_includes_store_and_reasoning_ids() {
|
||||
status: Some("completed".into()),
|
||||
call_id: "custom-tool-call-id".into(),
|
||||
name: "custom_tool".into(),
|
||||
namespace: None,
|
||||
input: "{}".into(),
|
||||
internal_chat_message_metadata_passthrough: None,
|
||||
});
|
||||
|
||||
@@ -20,6 +20,7 @@ use core_test_support::assert_regex_match;
|
||||
use core_test_support::responses::ev_assistant_message;
|
||||
use core_test_support::responses::ev_completed;
|
||||
use core_test_support::responses::ev_custom_tool_call;
|
||||
use core_test_support::responses::ev_custom_tool_call_with_namespace;
|
||||
use core_test_support::responses::ev_function_call;
|
||||
use core_test_support::responses::ev_response_created;
|
||||
use core_test_support::responses::mount_sse_once;
|
||||
@@ -177,6 +178,80 @@ async fn custom_tool_unknown_returns_custom_output_error() -> Result<()> {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
|
||||
async fn namespaced_custom_tool_call_preserves_namespace_through_dispatch_and_replay() -> Result<()>
|
||||
{
|
||||
skip_if_no_network!(Ok(()));
|
||||
|
||||
let server = start_mock_server().await;
|
||||
let mut builder = test_codex();
|
||||
let test = builder.build(&server).await?;
|
||||
|
||||
let call_id = "custom-namespaced";
|
||||
let namespace = "test_namespace::";
|
||||
let tool_name = "unsupported_tool";
|
||||
let input = "\"payload\"";
|
||||
|
||||
mount_sse_once(
|
||||
&server,
|
||||
sse(vec![
|
||||
ev_response_created("resp-1"),
|
||||
ev_custom_tool_call_with_namespace(call_id, namespace, tool_name, input),
|
||||
ev_completed("resp-1"),
|
||||
]),
|
||||
)
|
||||
.await;
|
||||
let mock = mount_sse_once(
|
||||
&server,
|
||||
sse(vec![
|
||||
ev_assistant_message("msg-1", "done"),
|
||||
ev_completed("resp-2"),
|
||||
]),
|
||||
)
|
||||
.await;
|
||||
|
||||
test.submit_turn_with_approval_and_permission_profile(
|
||||
"invoke namespaced custom tool",
|
||||
AskForApproval::Never,
|
||||
PermissionProfile::Disabled,
|
||||
)
|
||||
.await?;
|
||||
|
||||
let request = mock.single_request();
|
||||
let custom_tool_calls = request.inputs_of_type("custom_tool_call");
|
||||
let turn_id = custom_tool_calls
|
||||
.first()
|
||||
.and_then(|item| item.pointer("/internal_chat_message_metadata_passthrough/turn_id"))
|
||||
.and_then(Value::as_str)
|
||||
.map(str::to_string)
|
||||
.expect("custom tool call should include turn metadata");
|
||||
assert_eq!(
|
||||
(custom_tool_calls, request.custom_tool_call_output(call_id),),
|
||||
(
|
||||
vec![json!({
|
||||
"type": "custom_tool_call",
|
||||
"call_id": call_id,
|
||||
"namespace": namespace,
|
||||
"name": tool_name,
|
||||
"input": input,
|
||||
"internal_chat_message_metadata_passthrough": {
|
||||
"turn_id": turn_id,
|
||||
},
|
||||
})],
|
||||
json!({
|
||||
"type": "custom_tool_call_output",
|
||||
"call_id": call_id,
|
||||
"output": format!("unsupported custom tool call: {namespace}{tool_name}"),
|
||||
"internal_chat_message_metadata_passthrough": {
|
||||
"turn_id": turn_id,
|
||||
},
|
||||
}),
|
||||
)
|
||||
);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
|
||||
async fn shell_command_escalated_permissions_rejected_then_ok() -> Result<()> {
|
||||
skip_if_no_network!(Ok(()));
|
||||
|
||||
@@ -97,6 +97,7 @@ async fn recent_image_fallback_selects_newest_images_in_chronological_order() {
|
||||
status: Some("completed".to_string()),
|
||||
call_id: "code-mode-call".to_string(),
|
||||
name: "exec".to_string(),
|
||||
namespace: None,
|
||||
input: String::new(),
|
||||
internal_chat_message_metadata_passthrough: None,
|
||||
},
|
||||
|
||||
@@ -1052,6 +1052,9 @@ pub enum ResponseItem {
|
||||
|
||||
call_id: String,
|
||||
name: String,
|
||||
#[serde(default, skip_serializing_if = "Option::is_none")]
|
||||
#[ts(optional)]
|
||||
namespace: Option<String>,
|
||||
input: String,
|
||||
#[serde(default, skip_serializing_if = "Option::is_none")]
|
||||
#[ts(optional)]
|
||||
|
||||
@@ -3,7 +3,7 @@ use std::borrow::Cow;
|
||||
use codex_protocol::models::SearchToolCallParams;
|
||||
|
||||
/// Canonical payload shapes accepted by model-visible tool runtimes.
|
||||
#[derive(Clone, Debug)]
|
||||
#[derive(Clone, Debug, PartialEq)]
|
||||
pub enum ToolPayload {
|
||||
Function { arguments: String },
|
||||
ToolSearch { arguments: SearchToolCallParams },
|
||||
|
||||
Reference in New Issue
Block a user