From 0ada2db7d78b36fb42ea2e8399cf187b86e84021 Mon Sep 17 00:00:00 2001 From: Charles Packer Date: Thu, 12 Feb 2026 11:13:42 -0800 Subject: [PATCH] fix: prevent assistant/reasoning accumulator id collisions (#929) --- src/cli/helpers/accumulator.ts | 28 ++++++++++++++++++++++-- src/tests/cli/accumulator-usage.test.ts | 29 +++++++++++++++++++++++++ 2 files changed, 55 insertions(+), 2 deletions(-) diff --git a/src/cli/helpers/accumulator.ts b/src/cli/helpers/accumulator.ts index 3e82315..ef13cfb 100644 --- a/src/cli/helpers/accumulator.ts +++ b/src/cli/helpers/accumulator.ts @@ -451,6 +451,18 @@ function extractTextPart(v: unknown): string { return ""; } +function resolveLineIdForKind( + b: Buffers, + canonicalId: string, + kind: "assistant" | "reasoning", +): string { + const existing = b.byId.get(canonicalId); + if (!existing || existing.kind === kind) return canonicalId; + + // Avoid cross-kind collisions when providers reuse the same id/otid. + return `${kind}:${canonicalId}`; +} + function resolveAssistantLineId( b: Buffers, chunk: LettaStreamingResponse & { id?: string; otid?: string }, @@ -499,7 +511,13 @@ function resolveAssistantLineId( b.assistantCanonicalByOtid.set(otid, canonical); } - return canonical; + const lineId = resolveLineIdForKind(b, canonical, "assistant"); + if (lineId !== canonical) { + if (messageId) b.assistantCanonicalByMessageId.set(messageId, lineId); + if (otid) b.assistantCanonicalByOtid.set(otid, lineId); + } + + return lineId; } function resolveReasoningLineId( @@ -549,7 +567,13 @@ function resolveReasoningLineId( b.reasoningCanonicalByOtid.set(otid, canonical); } - return canonical; + const lineId = resolveLineIdForKind(b, canonical, "reasoning"); + if (lineId !== canonical) { + if (messageId) b.reasoningCanonicalByMessageId.set(messageId, lineId); + if (otid) b.reasoningCanonicalByOtid.set(otid, lineId); + } + + return lineId; } /** diff --git a/src/tests/cli/accumulator-usage.test.ts b/src/tests/cli/accumulator-usage.test.ts index 0656fb9..9a514ca 100644 --- a/src/tests/cli/accumulator-usage.test.ts +++ b/src/tests/cli/accumulator-usage.test.ts @@ -237,4 +237,33 @@ describe("accumulator usage statistics", () => { expect(line && "text" in line ? line.text : "").toBe("Think through it"); expect(buffers.byId.get("reasoning-msg-2")).toBeUndefined(); }); + + test("separates reasoning and assistant lines when ids overlap", () => { + const buffers = createBuffers(); + + onChunk(buffers, { + message_type: "reasoning_message", + id: "shared-stream-id", + reasoning: "Thinking... ", + } as unknown as LettaStreamingResponse); + + onChunk(buffers, { + message_type: "assistant_message", + id: "shared-stream-id", + content: [{ type: "text", text: "Final answer" }], + } as unknown as LettaStreamingResponse); + + const reasoning = buffers.byId.get("shared-stream-id"); + const assistant = buffers.byId.get("assistant:shared-stream-id"); + + expect(reasoning?.kind).toBe("reasoning"); + expect(reasoning && "text" in reasoning ? reasoning.text : "").toBe( + "Thinking... ", + ); + + expect(assistant?.kind).toBe("assistant"); + expect(assistant && "text" in assistant ? assistant.text : "").toBe( + "Final answer", + ); + }); });