mirror of
https://github.com/escalante29/WealthySmart.git
synced 2026-07-17 09:08:46 +02:00
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 <noreply@anthropic.com>
This commit is contained in:
@@ -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<any> {
|
||||||
|
const streamedTextIds = new Set<string>();
|
||||||
|
|
||||||
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||||
|
return new Observable<any>((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 ──────────────────────────────────────────
|
// ── SuppressRenderToolTextMiddleware ──────────────────────────────────────────
|
||||||
// When the LLM emits text content in the same response as a render tool call
|
// 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
|
// (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") {
|
if (process.env.CK_MIDDLEWARES !== "off") {
|
||||||
agent.use(new SuppressRenderToolTextMiddleware() as any);
|
agent.use(new SuppressRenderToolTextMiddleware() as any);
|
||||||
agent.use(new StripModelArtifactsMiddleware() as any);
|
agent.use(new StripModelArtifactsMiddleware() as any);
|
||||||
// ReconcileSnapshotMiddleware retired 2026-07-04: MAF 1.9+ reuses the
|
agent.use(new ReconcileSnapshotMiddleware() as any);
|
||||||
// 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 DeduplicateToolCallMiddleware() as any);
|
agent.use(new DeduplicateToolCallMiddleware() as any);
|
||||||
}
|
}
|
||||||
if (process.env.CK_DEBUG_EVENTS === "1" || process.env.CK_MIDDLEWARES === "off") {
|
if (process.env.CK_DEBUG_EVENTS === "1" || process.env.CK_MIDDLEWARES === "off") {
|
||||||
|
|||||||
Reference in New Issue
Block a user