fix(openai): correctly map tool_call indexes in Responses API stream

- The OpenAI Responses API uses 'output_index' to identify items in the response.
- If a response starts with text (output_index 0) followed by a tool call (output_index 1), the standard Chat Completions streaming format requires the first tool call to have index 0.
- Previously, the proxy was passing output_index (1) as the tool_call index, causing client-side SDKs to fail parsing the stream and silently drop the tool calls.
- Implemented a local mapping within the stream to ensure tool_call indexes are always dense and start at 0.
This commit is contained in:
2026-03-18 18:26:27 +00:00
parent 66e8b114b9
commit 4de457cc5e

View File

@@ -731,6 +731,8 @@ impl super::Provider for OpenAIProvider {
let mut es = es;
let mut content_buffer = String::new();
let mut has_tool_calls = false;
let mut tool_index_map = std::collections::HashMap::<u32, u32>::new();
let mut next_tool_index = 0u32;
while let Some(event) = es.next().await {
match event {
@@ -765,8 +767,15 @@ impl super::Provider for OpenAIProvider {
let call_id = item.get("call_id").and_then(|v| v.as_str());
let name = item.get("name").and_then(|v| v.as_str());
let out_idx = chunk.get("output_index").and_then(|v| v.as_u64()).unwrap_or(0) as u32;
let tc_idx = *tool_index_map.entry(out_idx).or_insert_with(|| {
let i = next_tool_index;
next_tool_index += 1;
i
});
tool_calls = Some(vec![crate::models::ToolCallDelta {
index: chunk.get("output_index").and_then(|v| v.as_u64()).unwrap_or(0) as u32,
index: tc_idx,
id: call_id.map(|s| s.to_string()),
call_type: Some("function".to_string()),
function: Some(crate::models::FunctionCallDelta {
@@ -780,8 +789,16 @@ impl super::Provider for OpenAIProvider {
"response.function_call_arguments.delta" => {
if let Some(delta) = chunk.get("delta").and_then(|v| v.as_str()) {
has_tool_calls = true;
let out_idx = chunk.get("output_index").and_then(|v| v.as_u64()).unwrap_or(0) as u32;
let tc_idx = *tool_index_map.entry(out_idx).or_insert_with(|| {
let i = next_tool_index;
next_tool_index += 1;
i
});
tool_calls = Some(vec![crate::models::ToolCallDelta {
index: chunk.get("output_index").and_then(|v| v.as_u64()).unwrap_or(0) as u32,
index: tc_idx,
id: None,
call_type: None,
function: Some(crate::models::FunctionCallDelta {