mirror of
https://github.com/farion1231/cc-switch.git
synced 2026-07-25 13:45:03 +08:00
Stream Codex custom tools with native input events
Custom (freeform) Codex tools are bridged through Chat Completions as
JSON `{"input": "..."}` functions, but the Chat->Responses stream still
re-emitted them via `response.function_call_arguments.*`, leaking the
JSON wrapper and using event types the Codex client does not route for
freeform tools.
Emit `response.custom_tool_call_input.delta`/`.done` (with the unwrapped
input text) for custom tool calls instead, suppressing the intermediate
function-argument deltas since a partial `{"input":` fragment cannot be
safely unwrapped mid-stream. Custom tool call items now use a `ctc_`
item-id prefix (matching the input events' item_id) consistently across
the streaming and non-streaming paths via a shared
response_tool_call_item_id_from_chat_name helper.
Covered by new streaming and non-streaming custom-tool tests.
This commit is contained in:
@@ -73,6 +73,11 @@ impl CodexToolContext {
|
||||
self.chat_name_to_spec.get(chat_name)
|
||||
}
|
||||
|
||||
pub(crate) fn is_custom_tool_chat_name(&self, chat_name: &str) -> bool {
|
||||
self.lookup_chat_name(chat_name)
|
||||
.is_some_and(|spec| matches!(&spec.kind, CodexToolKind::Custom))
|
||||
}
|
||||
|
||||
fn chat_name_for_response_function(&self, name: &str, namespace: Option<&str>) -> String {
|
||||
if let Some(namespace) = namespace.filter(|value| !value.is_empty()) {
|
||||
if let Some(chat_name) = self
|
||||
@@ -1351,7 +1356,7 @@ fn chat_tool_call_to_response_item(
|
||||
let name = function.get("name").and_then(|v| v.as_str()).unwrap_or("");
|
||||
let arguments = canonicalize_tool_arguments(function.get("arguments"));
|
||||
|
||||
let item_id = format!("fc_{call_id}");
|
||||
let item_id = response_tool_call_item_id_from_chat_name(&call_id, name, tool_context);
|
||||
response_tool_call_item_from_chat_name(
|
||||
&item_id,
|
||||
"completed",
|
||||
@@ -1379,7 +1384,7 @@ fn chat_legacy_function_call_to_response_item(
|
||||
.unwrap_or("");
|
||||
let arguments = canonicalize_tool_arguments(function_call.get("arguments"));
|
||||
|
||||
let item_id = format!("fc_{call_id}");
|
||||
let item_id = response_tool_call_item_id_from_chat_name(call_id, name, tool_context);
|
||||
response_tool_call_item_from_chat_name(
|
||||
&item_id,
|
||||
"completed",
|
||||
@@ -1391,6 +1396,18 @@ fn chat_legacy_function_call_to_response_item(
|
||||
)
|
||||
}
|
||||
|
||||
pub(crate) fn response_tool_call_item_id_from_chat_name(
|
||||
call_id: &str,
|
||||
chat_name: &str,
|
||||
tool_context: &CodexToolContext,
|
||||
) -> String {
|
||||
if tool_context.is_custom_tool_chat_name(chat_name) {
|
||||
format!("ctc_{call_id}")
|
||||
} else {
|
||||
format!("fc_{call_id}")
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn response_tool_call_item_from_chat_name(
|
||||
item_id: &str,
|
||||
status: &str,
|
||||
@@ -1448,7 +1465,7 @@ fn response_custom_tool_call_item(
|
||||
arguments: &str,
|
||||
reasoning: Option<&str>,
|
||||
) -> Value {
|
||||
let input = parse_custom_tool_input(arguments);
|
||||
let input = custom_tool_input_from_chat_arguments(arguments);
|
||||
let mut item = json!({
|
||||
"id": item_id,
|
||||
"type": "custom_tool_call",
|
||||
@@ -1471,7 +1488,7 @@ fn parse_tool_arguments_object(arguments: &str) -> Value {
|
||||
.unwrap_or_else(|| json!({ "query": arguments }))
|
||||
}
|
||||
|
||||
fn parse_custom_tool_input(arguments: &str) -> String {
|
||||
pub(crate) fn custom_tool_input_from_chat_arguments(arguments: &str) -> String {
|
||||
if arguments.trim().is_empty() {
|
||||
return String::new();
|
||||
}
|
||||
@@ -2631,6 +2648,7 @@ mod tests {
|
||||
let result = chat_completion_to_response_with_context(chat, &context).unwrap();
|
||||
|
||||
assert_eq!(result["output"][0]["type"], "custom_tool_call");
|
||||
assert_eq!(result["output"][0]["id"], "ctc_call_patch");
|
||||
assert_eq!(result["output"][0]["call_id"], "call_patch");
|
||||
assert_eq!(result["output"][0]["name"], "apply_patch");
|
||||
assert_eq!(
|
||||
|
||||
Reference in New Issue
Block a user