fix(openai): split embedded tool_calls into standard chunk format
Some checks failed
CI / Check (push) Has been cancelled
CI / Clippy (push) Has been cancelled
CI / Formatting (push) Has been cancelled
CI / Test (push) Has been cancelled
CI / Release Build (push) Has been cancelled

- Standard OpenAI clients expect tool_calls to be streamed as two parts:
  1. Initialization chunk containing 'id', 'type', and 'name', with empty 'arguments'.
  2. Payload chunk(s) containing 'arguments', with 'id', 'type', and 'name' omitted.
- Previously, the proxy was yielding all fields in a single chunk when parsing the custom 'tool_uses' JSON from gpt-5.4, causing strict clients like opencode to fail silently when delegating parallel tasks.
- The proxy now splits the extracted JSON into the correct two-chunk sequence, restoring subagent compatibility.
This commit is contained in:
2026-03-18 18:05:37 +00:00
parent 1cac45502a
commit 66e8b114b9

View File

@@ -819,15 +819,37 @@ impl super::Provider for OpenAIProvider {
};
}
// Yield the tool calls
// ... (rest of tool call yielding unchanged)
let deltas: Vec<crate::models::ToolCallDelta> = embedded_calls.into_iter().enumerate().map(|(idx, tc)| {
// Yield the tool calls in two chunks to mimic standard streaming behavior
// Chunk 1: Initialization (id, name)
let init_deltas: Vec<crate::models::ToolCallDelta> = embedded_calls.iter().enumerate().map(|(idx, tc)| {
crate::models::ToolCallDelta {
index: idx as u32,
id: Some(tc.id),
id: Some(tc.id.clone()),
call_type: Some("function".to_string()),
function: Some(crate::models::FunctionCallDelta {
name: Some(tc.function.name),
name: Some(tc.function.name.clone()),
arguments: Some("".to_string()),
}),
}
}).collect();
yield ProviderStreamChunk {
content: String::new(),
reasoning_content: None,
finish_reason: None,
tool_calls: Some(init_deltas),
model: model.clone(),
usage: None,
};
// Chunk 2: Payload (arguments)
let arg_deltas: Vec<crate::models::ToolCallDelta> = embedded_calls.into_iter().enumerate().map(|(idx, tc)| {
crate::models::ToolCallDelta {
index: idx as u32,
id: None,
call_type: None,
function: Some(crate::models::FunctionCallDelta {
name: None,
arguments: Some(tc.function.arguments),
}),
}
@@ -837,7 +859,7 @@ impl super::Provider for OpenAIProvider {
content: String::new(),
reasoning_content: None,
finish_reason: None,
tool_calls: Some(deltas),
tool_calls: Some(arg_deltas),
model: model.clone(),
usage: None,
};