[defer] fix(grok): align responses tool-call shape for grok models (#6937)

* fix(grok): align responses tool-call shape for grok models

* test(grok): cover responses tool call indexes

---------

Co-authored-by: minisforum <no@mail.com>
This commit is contained in:
CitrusIce
2026-07-20 07:52:28 +08:00
committed by GitHub
parent 73327dfc81
commit 747bce2f10
2 changed files with 57 additions and 3 deletions

View File

@@ -412,6 +412,9 @@ function closeMessage(state, emit, idx) {
function emitToolCall(state, emit, tc) {
const tcIdx = tc.index ?? 0;
const outputIndex = state.reasoningId
? normalizeOutputIndex(state.reasoningIndex) + 1 + normalizeOutputIndex(tcIdx)
: normalizeOutputIndex(tcIdx);
const newCallId = tc.id;
const funcName = tc.function?.name;
@@ -440,7 +443,7 @@ function emitToolCall(state, emit, tc) {
emit("response.output_item.added", {
type: "response.output_item.added",
output_index: tcIdx,
output_index: outputIndex,
item: isCustomTool
? {
id: `fc_${newCallId}`,
@@ -448,6 +451,7 @@ function emitToolCall(state, emit, tc) {
input: "",
call_id: newCallId,
name: state.funcNames[tcIdx] || "",
status: "in_progress",
}
: {
id: `fc_${newCallId}`,
@@ -455,6 +459,7 @@ function emitToolCall(state, emit, tc) {
arguments: "",
call_id: newCallId,
name: state.funcNames[tcIdx] || "",
status: "in_progress",
},
});
}
@@ -476,7 +481,7 @@ function emitToolCall(state, emit, tc) {
emit(deltaEvent, {
type: deltaEvent,
item_id: `fc_${refCallId}`,
output_index: tcIdx,
output_index: outputIndex,
delta: emittedDelta,
});
}
@@ -486,7 +491,9 @@ function emitToolCall(state, emit, tc) {
function closeToolCall(state, emit, idx, recordAsCompleted = true) {
const callId = state.funcCallIds[idx];
if (callId && !state.funcItemDone[idx]) {
const normalizedIndex = normalizeOutputIndex(idx);
const normalizedIndex = state.reasoningId
? normalizeOutputIndex(state.reasoningIndex) + 1 + normalizeOutputIndex(idx)
: normalizeOutputIndex(idx);
const args = state.funcArgsBuf[idx] || "{}";
const isCustomTool = (state.funcNames[idx] || "") === "apply_patch";
@@ -515,6 +522,7 @@ function closeToolCall(state, emit, idx, recordAsCompleted = true) {
input: rawInput,
call_id: callId,
name: state.funcNames[idx] || "",
status: "completed",
};
emit("response.output_item.done", {
@@ -536,6 +544,7 @@ function closeToolCall(state, emit, idx, recordAsCompleted = true) {
arguments: args,
call_id: callId,
name: state.funcNames[idx] || "",
status: "completed",
};
emit("response.output_item.done", {

View File

@@ -673,3 +673,48 @@ test("Responses→Chat streaming: flush finalizes stop when no tool call was emi
assert.ok(result, "flush should emit a final chunk");
assert.equal(result.choices[0].finish_reason, "stop");
});
test("Chat→Responses streaming: reasoning and a following tool call use distinct output indexes", () => {
const state = initState(FORMATS.OPENAI_RESPONSES);
const reasoningEvents = openaiToOpenAIResponsesResponse(
{
id: "chatcmpl-grok",
choices: [{ index: 0, delta: { reasoning_content: "I should call the tool." } }],
},
state
);
const toolEvents = openaiToOpenAIResponsesResponse(
{
choices: [
{
index: 0,
delta: {
tool_calls: [
{
index: 0,
id: "call_grok",
type: "function",
function: { name: "lookup", arguments: '{"query":"status"}' },
},
],
},
},
],
},
state
);
const reasoningItem = reasoningEvents.find(
(event) => event.event === "response.output_item.added" && event.data.item.type === "reasoning"
);
const toolItem = toolEvents.find(
(event) =>
event.event === "response.output_item.added" && event.data.item.type === "function_call"
);
assert.ok(reasoningItem, "should announce the reasoning item");
assert.ok(toolItem, "should announce the function call item");
assert.equal(reasoningItem.data.output_index, 0);
assert.equal(toolItem.data.output_index, 1);
});