refactor(sse): dedup fallback tool_call id helper (#4736)

Integrated into release/v3.8.36
This commit is contained in:
Diego Rodrigues de Sa e Souza
2026-06-24 12:38:25 -03:00
committed by GitHub
parent cc8557cedf
commit 6e28889aad
4 changed files with 31 additions and 3 deletions

View File

@@ -2,6 +2,12 @@
const ALPHANUM9 = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789";
// Fallback streaming tool_call id when a provider response omits one (index optional).
// `call_<ts>` when no index is given; `call_<index>_<ts>` when an index is supplied.
export function fallbackToolCallId(index?: number): string {
return index === undefined ? `call_${Date.now()}` : `call_${index}_${Date.now()}`;
}
// Generate unique tool call ID (default long form)
export function generateToolCallId() {
return `call_${Date.now().toString(36)}_${Math.random().toString(36).slice(2, 9)}`;

View File

@@ -4,6 +4,7 @@
*/
import { register } from "../registry.ts";
import { FORMATS } from "../formats.ts";
import { fallbackToolCallId } from "../helpers/toolCallHelper.ts";
/**
* Parse Kiro SSE event and convert to OpenAI format
@@ -116,7 +117,7 @@ export function convertKiroToOpenAI(chunk, state) {
// Handle tool use events
if (eventType === "toolUseEvent" || data.toolUseEvent) {
const toolUse = data.toolUseEvent || data;
const toolCallId = toolUse.toolUseId || `call_${Date.now()}`;
const toolCallId = toolUse.toolUseId || fallbackToolCallId();
const toolName = toolUse.name || "";
const toolInput = toolUse.input || {};

View File

@@ -5,6 +5,7 @@
import { register } from "../registry.ts";
import { FORMATS } from "../formats.ts";
import { appendToolCallArgumentDelta } from "../../utils/toolCallArguments.ts";
import { fallbackToolCallId } from "../helpers/toolCallHelper.ts";
function normalizeToolName(value) {
return typeof value === "string" ? value.trim() : "";
@@ -615,7 +616,7 @@ function openaiResponsesToOpenAIResponseStream(chunk, state) {
// Function call started
if (eventType === "response.output_item.added" && data.item?.type === "function_call") {
const item = data.item;
state.currentToolCallId = item.call_id || `call_${Date.now()}`;
state.currentToolCallId = item.call_id || fallbackToolCallId();
state.currentToolCallArgsBuffer = ""; // reset per-call arg buffer
state.currentToolCallDeferred = false;
@@ -694,7 +695,7 @@ function openaiResponsesToOpenAIResponseStream(chunk, state) {
const item = data.item;
const buffered = state.currentToolCallArgsBuffer || "";
const currentIndex = state.toolCallIndex; // capture before increment
const callId = item.call_id || state.currentToolCallId || `call_${Date.now()}`;
const callId = item.call_id || state.currentToolCallId || fallbackToolCallId();
const toolName = normalizeToolName(item.name);
if (state.currentToolCallDeferred) {

View File

@@ -599,6 +599,26 @@ test("fixMissingToolResponses keeps OpenAI role:tool when assistant uses OpenAI
assert.equal(fixed.messages[2].tool_call_id, "call_b");
});
test("fallbackToolCallId returns the right id shape with and without an index", () => {
const noIndex = toolCallHelper.fallbackToolCallId();
assert.match(
noIndex,
/^call_\d+$/,
"no-index form must be `call_<ts>` (matches kiro/openai-responses fallback shape)"
);
const withIndex = toolCallHelper.fallbackToolCallId(2);
assert.match(
withIndex,
/^call_2_\d+$/,
"index form must be `call_<i>_<ts>` (matches indexed fallback shape)"
);
// index 0 is falsy but defined — must still produce the indexed form, not the no-index form.
const zeroIndex = toolCallHelper.fallbackToolCallId(0);
assert.match(zeroIndex, /^call_0_\d+$/, "index 0 must use the indexed form, not the bare form");
});
test("translateRequest replays cached reasoning-only messages when interleaved field is reasoning_content", () => {
clearReasoningCacheAll();
clearModelsDevCapabilities();