Restore Codex tool plugins over Chat Completions third-party proxy

When proxying Codex Responses requests through the Chat Completions
format (api_format=openai_chat), the request transform only forwarded
plain `function` tools and silently dropped `tool_search`, `namespace`
(MCP), and `custom` tools, so third-party APIs never saw the official
Codex plugins and could not call them.

Introduce CodexToolContext, built from the original Responses request,
which flattens all four tool kinds into Chat functions and keeps a
chat_name -> CodexToolSpec map. The response path (streaming and
non-streaming) looks names up in this map to restore the original
tool_search_call / custom_tool_call / namespaced function_call items
instead of reparsing the flattened name. Long namespaced names are
truncated with a sha256 suffix to fit the 64-char Chat tool-name limit.

Covered by new round-trip tests for all four tool kinds across both
streaming and non-streaming paths.
This commit is contained in:
Jason
2026-06-01 16:29:23 +08:00
parent b7499fc871
commit 5968336364
4 changed files with 904 additions and 61 deletions
@@ -170,6 +170,25 @@ pub(crate) fn response_function_call_item(
item
}
pub(crate) fn response_function_call_item_with_namespace(
item_id: &str,
status: &str,
call_id: &str,
name: &str,
namespace: Option<&str>,
arguments: &str,
reasoning: Option<&str>,
) -> Value {
let mut item =
response_function_call_item(item_id, status, call_id, name, arguments, reasoning);
if let Some(namespace) = namespace.filter(|value| !value.is_empty()) {
if let Some(obj) = item.as_object_mut() {
obj.insert("namespace".to_string(), json!(namespace));
}
}
item
}
pub(crate) fn response_item_call_id(item: &Value) -> Option<String> {
item.get("call_id")
.or_else(|| item.get("id"))