feat(backend): SessionBridge widget-descriptor <-> RPC + pending widget

Decodes native extension_ui_request (method:input, title=JSON) into
ui_request ClientEvent; encodes respond() as extension_ui_response with
value payload; tracks pending widget; handles notify→info and steer.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-06-27 20:56:26 +02:00
co-authored by Claude Sonnet 4.6
parent df5d2c36ac
commit 2d680c958c
2 changed files with 85 additions and 0 deletions
+43
View File
@@ -0,0 +1,43 @@
import type { RpcClient } from "../rpc/rpc-client.js";
export type ClientEvent =
| { type: "ui_request"; ui_request: any }
| { type: "text_delta"; text: string }
| { type: "info"; [k: string]: any }
| { type: "system_event"; [k: string]: any };
export class SessionBridge {
private pending: any = null;
private cbs = new Set<(e: ClientEvent) => void>();
constructor(private rpc: RpcClient) {
rpc.on("event", (m) => {
if (m.type === "extension_ui_request" && m.method === "input") {
let descriptor: any;
try { descriptor = JSON.parse(m.title); } catch { return; }
this.pending = descriptor;
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 === "text_delta") {
this.fan({ type: "text_delta", text: m.text ?? "" });
} else if (m.type === "system_event") {
this.fan(m as ClientEvent);
}
// altri method nativi (setStatus/setWidget) e altri eventi Pi ignorati in MVP
});
}
private fan(e: ClientEvent) { for (const cb of this.cbs) cb(e); }
onClientEvent(cb: (e: ClientEvent) => void): void { this.cbs.add(cb); }
respond(uiResponse: object & { id: string }): void {
this.rpc.send({ type: "extension_ui_response", id: uiResponse.id, value: JSON.stringify(uiResponse) });
if (this.pending && uiResponse.id === this.pending.id) this.pending = null;
}
steer(text: string): void { this.rpc.send({ type: "steer", message: text }); }
pendingWidget(): object | null { return this.pending; }
}