tool-call id uniquifier: the Lacan fix (owner-approved 2026-10-04)
DeepSeek via OpenRouter returns tool-call ids that reset every response — a one-call-per-turn session names every call call_0. Everything keys on toolCallId (the fold's call↔result join, the pending gate, answer routing, the option-shuffle seed), so the Lacan session corrupted: the quiz answer overwrote the ask card, the ask question vanished, and the order scrambled. Record itself was always intact. - src/server/pi/call-ids.ts (new): ToolCallIdUniquifier at the wire boundary — colliding ids remint once (orig~2, ~3…), the same mints at settlement; tables key by message OBJECT IDENTITY (streaming messages carry no ids; the message_end object is reference-identical to the state object). History never rewrites; providers see paired ids, which is protocol-legal (pi synthesizes ids for providers that send none). - ui-stream: runPiTurn remaps events + settles through the uniquifier (spec.usedToolCallIds, seeded by chat-handler from the record). - view.ts: findToolPart scans newest-first — pre-fix duplicate-id records (the existing Lacan session) heal on next render. - tests: 9 unit pins (call-ids.test.ts) + the two-call_0 e2e (chat-flow): ask answered as ask, quiz as quiz, ids unique in the record.
This commit is contained in:
+9
-1
@@ -129,7 +129,15 @@ export function projectView(messages: readonly LhMessage[], live?: LiveTurnState
|
||||
};
|
||||
const findToolPart = (toolCallId: string): LhViewPart | undefined => {
|
||||
for (let i = out.length - 1; i >= 0; i--) {
|
||||
for (const part of out[i]!.parts) {
|
||||
// NEWEST-FIRST part scan within a group (the Lacan heal, 2026-10-04):
|
||||
// a result pairs with the most RECENT call carrying its id. Unique
|
||||
// ids (the uniquifier guarantees them for new turns) make scan order
|
||||
// irrelevant; for legacy duplicate-id records (DeepSeek's per-response
|
||||
// `call_0`) this routes each answer to its own call instead of letting
|
||||
// a later result overwrite an earlier question's card.
|
||||
const parts = out[i]!.parts;
|
||||
for (let p = parts.length - 1; p >= 0; p--) {
|
||||
const part = parts[p]!;
|
||||
if (part.type.startsWith("tool-") && part.toolCallId === toolCallId) return part;
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user