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