fix(ai-formats): omit request tool call ids in OpenAI Responses input

This commit is contained in:
zhefox
2026-06-17 09:15:38 +08:00
parent 8ad97761e8
commit 5b305c64e1
3 changed files with 76 additions and 4 deletions
@@ -9,7 +9,7 @@ use crate::{
},
protocol::canonical::{
canonical_response_format_to_openai_responses, canonical_tool_is_openai_custom,
canonical_tool_use_to_openai_responses_item, is_claude_messages_request,
canonical_tool_use_to_openai_responses_input_item, is_claude_messages_request,
is_claude_system_instruction, is_claude_thinking_block, is_claude_tool_result,
is_openai_responses_input_message, is_openai_thinking_block, media_data_or_url,
namespace_extension_object, openai_content_text, openai_extensions,
@@ -331,7 +331,7 @@ fn canonical_messages_to_responses_input(canonical: &CanonicalRequest) -> Option
let call_id = responses_tool_call_id(id, &mut next_generated_tool_call_index);
let tool_name = responses_tool_name(name);
pending_tool_call_ids.push_back(call_id.clone());
input.push(canonical_tool_use_to_openai_responses_item(
input.push(canonical_tool_use_to_openai_responses_input_item(
&call_id, &tool_name, arguments, extensions,
));
}
@@ -1430,7 +1430,7 @@ mod tests {
assert_eq!(body["input"].as_array().expect("input").len(), 2);
assert_eq!(body["input"][0]["type"], "function_call");
assert_eq!(body["input"][0]["id"], "call_auto_0");
assert!(body["input"][0].get("id").is_none());
assert_eq!(body["input"][0]["call_id"], "call_auto_0");
assert_eq!(body["input"][0]["name"], "unknown");
assert_eq!(body["input"][0]["arguments"], "{\"q\":\"rust\"}");
@@ -2609,7 +2609,7 @@ mod tests {
.value;
assert_eq!(converted["input"][0]["type"], "function_call");
assert_eq!(converted["input"][0]["id"], "call_lookup_1");
assert!(converted["input"][0].get("id").is_none());
assert_eq!(converted["input"][0]["call_id"], "call_lookup_1");
assert_eq!(converted["input"][1]["type"], "function_call_output");
assert_eq!(converted["input"][1]["call_id"], "call_lookup_1");
@@ -808,6 +808,66 @@ pub(crate) fn canonical_tool_use_to_openai_responses_item(
})
}
pub(crate) fn canonical_tool_use_to_openai_responses_input_item(
id: &str,
name: &str,
input: &Value,
extensions: &BTreeMap<String, Value>,
) -> Value {
if let Some(item_type) = openai_responses_hosted_tool_call_item_type(extensions) {
let mut item = Map::new();
item.insert("type".to_string(), Value::String(item_type.to_string()));
item.insert("id".to_string(), Value::String(id.to_string()));
item.insert("call_id".to_string(), Value::String(id.to_string()));
item.insert("status".to_string(), Value::String("completed".to_string()));
if let Some(input_object) = input.as_object() {
for field in openai_responses_hosted_tool_input_fields(item_type) {
if let Some(value) = input_object.get(*field) {
item.insert((*field).to_string(), value.clone());
}
}
} else if item_type == "apply_patch_call" {
item.insert("operation".to_string(), input.clone());
} else if !name.trim().is_empty() {
item.insert("name".to_string(), Value::String(name.to_string()));
}
return Value::Object(item);
}
if is_openai_custom_tool_call(extensions) {
let mut item = Map::new();
item.insert(
"type".to_string(),
Value::String("custom_tool_call".to_string()),
);
if let Some(item_id) = openai_responses_request_tool_call_item_id(extensions, "ctc") {
item.insert("id".to_string(), Value::String(item_id));
}
item.insert("call_id".to_string(), Value::String(id.to_string()));
item.insert("status".to_string(), Value::String("completed".to_string()));
item.insert("name".to_string(), Value::String(name.to_string()));
item.insert(
"input".to_string(),
Value::String(openai_custom_tool_input_text(input)),
);
return Value::Object(item);
}
let mut item = Map::new();
item.insert(
"type".to_string(),
Value::String("function_call".to_string()),
);
if let Some(item_id) = openai_responses_request_tool_call_item_id(extensions, "fc") {
item.insert("id".to_string(), Value::String(item_id));
}
item.insert("call_id".to_string(), Value::String(id.to_string()));
item.insert("name".to_string(), Value::String(name.to_string()));
item.insert(
"arguments".to_string(),
Value::String(canonicalize_tool_arguments(input)),
);
Value::Object(item)
}
fn openai_responses_tool_call_item_id(
call_id: &str,
extensions: &BTreeMap<String, Value>,
@@ -828,6 +888,18 @@ fn openai_responses_tool_call_item_id(
}
}
fn openai_responses_request_tool_call_item_id(
extensions: &BTreeMap<String, Value>,
prefix: &str,
) -> Option<String> {
openai_responses_extension(extensions)
.and_then(|value| value.get("item_id").or_else(|| value.get("id")))
.and_then(Value::as_str)
.map(str::trim)
.filter(|value| value.starts_with(prefix))
.map(ToString::to_string)
}
fn openai_responses_hosted_tool_call_item_type(
extensions: &BTreeMap<String, Value>,
) -> Option<&str> {