From 0e03284c95bcc582397a3ec39ef06cbe33095a2f Mon Sep 17 00:00:00 2001 From: Carlos Escalante Date: Sat, 4 Jul 2026 16:10:12 -0600 Subject: [PATCH] Assistant: restore ReconcileSnapshotMiddleware Retiring it in the middleware audit was premature: MAF 1.10 reuses the streamed id for plain-text snapshots but deliberately mints a fresh UUID for post-tool-call assistant text (their #3619), so tool+text turns still rendered a duplicate assistant bubble (re-verified live). The user-message duplication that motivated the retirement only fired on a2ui retry runs, which no longer exist now that a2ui is disabled. Co-Authored-By: Claude Fable 5 --- frontend/server.ts | 67 +++++++++++++++++++++++++++++++++++++++++++--- 1 file changed, 63 insertions(+), 4 deletions(-) diff --git a/frontend/server.ts b/frontend/server.ts index 4f4084e..e854757 100644 --- a/frontend/server.ts +++ b/frontend/server.ts @@ -191,6 +191,68 @@ class StripModelArtifactsMiddleware extends (Middleware as any) { } } +// ── ReconcileSnapshotMiddleware ────────────────────────────────────────────── +// MAF 1.10 reuses the streamed id for PLAIN-text snapshots, but still mints a +// fresh UUID for post-tool-call assistant text — deliberately, per their +// issue #3619 ("separate message"). ag-ui's snapshot merge appends unknown +// ids, so tool+text turns still produce a duplicate assistant bubble without +// this reconciliation (re-verified live on MAF 1.10 / CK 1.62, 2026-07-04). +// Drops only the orphan text-only assistant message with no streamed +// counterpart. Caveat: interfered with a2ui retry runs (duplicated the user +// message) — a2ui is disabled while that pipeline matures; revisit both +// together. + +// eslint-disable-next-line @typescript-eslint/no-explicit-any +class ReconcileSnapshotMiddleware extends (Middleware as any) { + // eslint-disable-next-line @typescript-eslint/no-explicit-any + run(input: any, next: any): Observable { + const streamedTextIds = new Set(); + + // eslint-disable-next-line @typescript-eslint/no-explicit-any + return new Observable((observer) => { + // eslint-disable-next-line @typescript-eslint/no-explicit-any + const sub = (this as any).runNextWithState(input, next).subscribe({ + // eslint-disable-next-line @typescript-eslint/no-explicit-any + next: (eventWithState: any) => { + const event = eventWithState.event; + const type: string = event?.type ?? ""; + + if (type === EventType.TEXT_MESSAGE_START && event?.messageId) { + streamedTextIds.add(String(event.messageId)); + } + + if (type === EventType.MESSAGES_SNAPSHOT) { + // eslint-disable-next-line @typescript-eslint/no-explicit-any + const msgs: any[] = Array.isArray(event?.messages) ? event.messages : []; + const filtered = msgs.filter((m) => { + if (m?.role !== "assistant") return true; + const id = String(m?.id ?? ""); + const hasText = typeof m?.content === "string" && m.content.length > 0; + const hasToolCalls = + (Array.isArray(m?.toolCalls) && m.toolCalls.length > 0) || + (Array.isArray(m?.tool_calls) && m.tool_calls.length > 0); + if (hasText && !hasToolCalls && !streamedTextIds.has(id)) { + return false; // drop orphan text-only assistant duplicate + } + return true; + }); + if (filtered.length !== msgs.length) { + observer.next({ ...event, messages: filtered }); + return; + } + } + + observer.next(event); + }, + // eslint-disable-next-line @typescript-eslint/no-explicit-any + error: (err: any) => observer.error(err), + complete: () => observer.complete(), + }); + return () => sub.unsubscribe(); + }); + } +} + // ── SuppressRenderToolTextMiddleware ────────────────────────────────────────── // When the LLM emits text content in the same response as a render tool call // (render_a2ui or render_spending_summary), the text appears as a duplicate @@ -298,10 +360,7 @@ app.all("/api/copilotkit/*", async (c) => { if (process.env.CK_MIDDLEWARES !== "off") { agent.use(new SuppressRenderToolTextMiddleware() as any); agent.use(new StripModelArtifactsMiddleware() as any); - // ReconcileSnapshotMiddleware retired 2026-07-04: MAF 1.9+ reuses the - // streamed message id in MESSAGES_SNAPSHOT (verified in _agent_run.py at - // python-1.10.0 and in live event logs), and keeping the surgery caused - // duplicated user messages on a2ui follow-up runs. + agent.use(new ReconcileSnapshotMiddleware() as any); agent.use(new DeduplicateToolCallMiddleware() as any); } if (process.env.CK_DEBUG_EVENTS === "1" || process.env.CK_MIDDLEWARES === "off") {