diff --git a/app/src/lib/copilot/repair-history.ts b/app/src/lib/copilot/repair-history.ts index 04ac615..ad8f3e6 100644 --- a/app/src/lib/copilot/repair-history.ts +++ b/app/src/lib/copilot/repair-history.ts @@ -22,7 +22,12 @@ function isToolResult(message: Message): message is Message & ToolResult { */ export function repairUnansweredToolCalls( messages: ReadonlyArray, - newId: () => string = () => newId(), + /* + * Named so it cannot shadow the import it defaults to. A parameter called `newId` defaulting to + * `newId()` resolves to itself and recurses until the stack goes, and it does so only on the + * repair branch, which every test avoided by passing its own. Biome said so, as an unused import. + */ + mintId: () => string = newId, ): ReadonlyArray { const answered = new Set(); for (const message of messages) { @@ -47,7 +52,7 @@ export function repairUnansweredToolCalls( // OpenAI requires the results to follow their calls, and some providers require the order to // match the `tool_calls` array as well. repaired.push({ - id: newId(), + id: mintId(), role: "tool", toolCallId: call.id, content: UNANSWERED, diff --git a/app/tests/repair-history.test.ts b/app/tests/repair-history.test.ts index c697efe..3ee972c 100644 --- a/app/tests/repair-history.test.ts +++ b/app/tests/repair-history.test.ts @@ -123,3 +123,39 @@ describe("repairing a history before it is sent", () => { expect(repairUnansweredToolCalls(messages, ids)).toBe(messages); }); }); + +/** + * The default id source, which every test above replaces with its own. + * + * That is the reason a self-recursive default survived review: the parameter exists so a test can + * make ids predictable, so no test ever ran the default, and the default only runs on the repair + * branch. This one calls it the way the only real caller does, with the argument omitted. + */ +describe("repairUnansweredToolCalls without an id source", () => { + test("repairs using its own ids rather than recursing", () => { + const messages: Message[] = [ + { id: "a", role: "user", content: "go" }, + { + id: "b", + role: "assistant", + content: "", + toolCalls: [ + { + id: "call-1", + type: "function", + function: { name: "act", arguments: "{}" }, + }, + ], + }, + ]; + + const repaired = repairUnansweredToolCalls(messages); + + expect(repaired).toHaveLength(3); + const result = repaired[2] as Message & { toolCallId: string }; + expect(result.role).toBe("tool"); + expect(result.toolCallId).toBe("call-1"); + // A real id, not an empty string and not the call's own id. + expect(result.id).toMatch(/^[0-9a-f-]{36}$/); + }); +});