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:
@@ -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; }
|
||||
}
|
||||
@@ -0,0 +1,42 @@
|
||||
import { test, expect, vi } from "vitest";
|
||||
import { SessionBridge } from "../src/bridge/session-bridge.js";
|
||||
|
||||
function fakeRpc() {
|
||||
const sent: any[] = [];
|
||||
let evcb: any;
|
||||
return {
|
||||
rpc: {
|
||||
send: (c: any) => sent.push(c),
|
||||
on: (_: any, cb: any) => { evcb = cb; },
|
||||
request: vi.fn(),
|
||||
} as any,
|
||||
sent,
|
||||
fire: (m: any) => evcb(m),
|
||||
};
|
||||
}
|
||||
|
||||
test("extension_ui_request nativo (method:input, title=json) diventa ui_request ed è il pendente", () => {
|
||||
const { rpc, fire } = fakeRpc();
|
||||
const b = new SessionBridge(rpc);
|
||||
const seen: any[] = [];
|
||||
b.onClientEvent((e) => seen.push(e));
|
||||
const descriptor = { id: "u1", widget: "select" };
|
||||
fire({ type: "extension_ui_request", id: "u1", method: "input", title: JSON.stringify(descriptor) });
|
||||
expect(seen[0]).toEqual({ type: "ui_request", ui_request: descriptor });
|
||||
expect(b.pendingWidget()).toEqual(descriptor);
|
||||
});
|
||||
|
||||
test("respond invia extension_ui_response con payload in value e azzera il pendente", () => {
|
||||
const { rpc, sent, fire } = fakeRpc();
|
||||
const b = new SessionBridge(rpc);
|
||||
fire({ type: "extension_ui_request", id: "u1", method: "input", title: JSON.stringify({ id: "u1", widget: "select" }) });
|
||||
b.respond({ id: "u1", choices: ["a"] });
|
||||
expect(sent.at(-1)).toEqual({ type: "extension_ui_response", id: "u1", value: JSON.stringify({ id: "u1", choices: ["a"] }) });
|
||||
expect(b.pendingWidget()).toBeNull();
|
||||
});
|
||||
|
||||
test("steer invia un comando steer", () => {
|
||||
const { rpc, sent } = fakeRpc();
|
||||
new SessionBridge(rpc).steer("considera solo il 2024");
|
||||
expect(sent.at(-1)).toEqual({ type: "steer", message: "considera solo il 2024" });
|
||||
});
|
||||
Reference in New Issue
Block a user