From 091a055bf01a93cd3517900f16ce44efc83cbb66 Mon Sep 17 00:00:00 2001 From: mptyl Date: Sun, 28 Jun 2026 15:03:13 +0200 Subject: [PATCH] fix(backend): bridge maps real Pi message_update->text_delta for FE streaming MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Live end-to-end against real pi --mode rpc revealed Pi streams assistant text as top-level message_update events whose nested assistantMessageEvent carries the incremental delta — not the top-level text_delta the fake-pi-rpc emits. The bridge now maps message_update(assistantMessageEvent.text_delta).delta -> FE text_delta, so the chat shows the model's output during a real session. Co-Authored-By: Claude Opus 4.8 --- backend/src/bridge/session-bridge.ts | 5 +++++ backend/test/session-bridge.test.ts | 13 +++++++++++++ 2 files changed, 18 insertions(+) diff --git a/backend/src/bridge/session-bridge.ts b/backend/src/bridge/session-bridge.ts index 80a13e70..cd7cd658 100644 --- a/backend/src/bridge/session-bridge.ts +++ b/backend/src/bridge/session-bridge.ts @@ -19,6 +19,11 @@ export class SessionBridge { this.fan({ type: "ui_request", ui_request: descriptor }); } else if (m.type === "extension_ui_request" && m.method === "notify") { this.fan({ type: "info", level: m.notifyType ?? "info", text: m.message ?? "" }); + } else if (m.type === "message_update" && m.assistantMessageEvent?.type === "text_delta") { + // Real Pi streams assistant text as message_update events whose nested + // assistantMessageEvent carries the incremental `delta`. Map it to the FE's + // text_delta contract. (Top-level text_delta below is what the fake-pi-rpc emits.) + this.fan({ type: "text_delta", text: m.assistantMessageEvent.delta ?? "" }); } else if (m.type === "text_delta") { this.fan({ type: "text_delta", text: m.text ?? "" }); } else if (m.type === "system_event") { diff --git a/backend/test/session-bridge.test.ts b/backend/test/session-bridge.test.ts index 2957c855..76324d92 100644 --- a/backend/test/session-bridge.test.ts +++ b/backend/test/session-bridge.test.ts @@ -15,6 +15,19 @@ function fakeRpc() { }; } +test("real Pi message_update (assistantMessageEvent text_delta) becomes a text_delta to the FE", () => { + const { rpc, fire } = fakeRpc(); + const b = new SessionBridge(rpc); + const seen: any[] = []; + b.onClientEvent((e) => seen.push(e)); + // Shape emitted by real `pi --mode rpc`: text streams inside message_update.assistantMessageEvent.delta + fire({ type: "message_update", assistantMessageEvent: { type: "text_delta", contentIndex: 0, delta: "Avvio" } }); + expect(seen[0]).toEqual({ type: "text_delta", text: "Avvio" }); + // Non-text message_update events (e.g. text_start) are ignored. + fire({ type: "message_update", assistantMessageEvent: { type: "text_start", contentIndex: 0 } }); + expect(seen).toHaveLength(1); +}); + test("extension_ui_request nativo (method:input, title=json) diventa ui_request ed è il pendente", () => { const { rpc, fire } = fakeRpc(); const b = new SessionBridge(rpc);