mirror of
https://github.com/fabro-sh/fabro.git
synced 2026-10-10 03:30:59 +00:00
fix: render Ask Fabro session responses
Handle the flattened session SSE event envelopes emitted by the API so assistant deltas are applied to the Ask Fabro runtime instead of being ignored.
This commit is contained in:
parent
4190e13a20
commit
5b5e0d83fd
3 changed files with 59 additions and 11 deletions
|
|
@ -13,7 +13,38 @@ function event(name: string, properties: Record<string, unknown>): SessionStream
|
|||
} as unknown as SessionStreamEvent;
|
||||
}
|
||||
|
||||
function flattenedEvent(
|
||||
name: string,
|
||||
properties: Record<string, unknown>,
|
||||
): SessionStreamEvent {
|
||||
return {
|
||||
seq: 0,
|
||||
id: "evt_1",
|
||||
ts: "2026-05-22T16:25:34.940200Z",
|
||||
run_id: "run_1",
|
||||
event: name,
|
||||
properties,
|
||||
} as unknown as SessionStreamEvent;
|
||||
}
|
||||
|
||||
describe("applyTurnEvent", () => {
|
||||
test("appends assistant deltas from flattened SSE event envelopes", () => {
|
||||
const acc = {
|
||||
activeTextIndex: null,
|
||||
parts: [],
|
||||
toolCallIndex: new Map(),
|
||||
} as Parameters<typeof applyTurnEvent>[0];
|
||||
|
||||
expect(
|
||||
applyTurnEvent(
|
||||
acc,
|
||||
flattenedEvent("run.session.assistant_delta", { delta: "Hello" }),
|
||||
),
|
||||
).toBe(true);
|
||||
|
||||
expect(acc.parts).toEqual([{ type: "text", text: "Hello" }]);
|
||||
});
|
||||
|
||||
test("appends assistant deltas into a single streaming text part", () => {
|
||||
const acc = {
|
||||
activeTextIndex: null,
|
||||
|
|
|
|||
|
|
@ -100,17 +100,34 @@ interface NestedRunEvent {
|
|||
properties?: Record<string, unknown>;
|
||||
}
|
||||
|
||||
function isRecord(value: unknown): value is Record<string, unknown> {
|
||||
return typeof value === "object" && value !== null && !Array.isArray(value);
|
||||
}
|
||||
|
||||
function eventPayload(envelope: SessionStreamEvent): {
|
||||
eventName: string;
|
||||
props: Record<string, unknown>;
|
||||
} {
|
||||
const raw = envelope as unknown as Record<string, unknown>;
|
||||
if (typeof raw.event === "string") {
|
||||
return {
|
||||
eventName: raw.event,
|
||||
props: isRecord(raw.properties) ? raw.properties : {},
|
||||
};
|
||||
}
|
||||
|
||||
const nested = isRecord(raw.event) ? (raw.event as NestedRunEvent) : {};
|
||||
return {
|
||||
eventName: nested.event ?? "",
|
||||
props: isRecord(nested.properties) ? nested.properties : {},
|
||||
};
|
||||
}
|
||||
|
||||
export function applyTurnEvent(
|
||||
acc: TurnAccumulator,
|
||||
envelope: SessionStreamEvent,
|
||||
): boolean {
|
||||
// The on-wire SSE envelope is `{ seq, event: { event: "...", properties } }`,
|
||||
// but the generated OpenAPI `EventEnvelope` type flattens the inner event
|
||||
// fields. Cast through `unknown` to read the nested runtime shape that the
|
||||
// server actually emits (matches `session-stream.test.ts`).
|
||||
const nested = (envelope as unknown as { event?: NestedRunEvent }).event ?? {};
|
||||
const eventName = nested.event ?? "";
|
||||
const props: Record<string, unknown> = nested.properties ?? {};
|
||||
const { eventName, props } = eventPayload(envelope);
|
||||
|
||||
if (eventName === "run.session.assistant_delta") {
|
||||
const delta = typeof props.delta === "string" ? props.delta : "";
|
||||
|
|
|
|||
|
|
@ -41,7 +41,7 @@ describe("session stream helpers", () => {
|
|||
streamResponse(
|
||||
[
|
||||
"id: 3\nevent: run.session.turn.started\n",
|
||||
'data: {"seq":3,"event":{"event":"run.session.turn.started","properties":{"turn_id":"turn_1"}}}\n\n',
|
||||
'data: {"seq":3,"id":"evt_1","ts":"2026-05-22T16:25:34.940200Z","run_id":"run_1","event":"run.session.turn.started","properties":{"turn_id":"turn_1"}}\n\n',
|
||||
],
|
||||
200,
|
||||
{ "x-fabro-turn-id": "turn_1" },
|
||||
|
|
@ -65,7 +65,7 @@ describe("session stream helpers", () => {
|
|||
});
|
||||
expect(events).toHaveLength(1);
|
||||
expect(events[0]?.seq).toBe(3);
|
||||
expect(events[0]?.event.event).toBe("run.session.turn.started");
|
||||
expect(events[0]?.event).toBe("run.session.turn.started");
|
||||
});
|
||||
|
||||
test("attaches to session events from a run sequence", async () => {
|
||||
|
|
@ -73,7 +73,7 @@ describe("session stream helpers", () => {
|
|||
const fetchMock = mock(() =>
|
||||
Promise.resolve(
|
||||
streamResponse([
|
||||
'data: {"seq":7,"event":{"event":"run.session.assistant_message","properties":{}}}\n\n',
|
||||
'data: {"seq":7,"id":"evt_2","ts":"2026-05-22T16:25:34.940200Z","run_id":"run_1","event":"run.session.assistant_message","properties":{}}\n\n',
|
||||
]),
|
||||
),
|
||||
);
|
||||
|
|
@ -96,7 +96,7 @@ describe("session stream helpers", () => {
|
|||
const fetchMock = mock(() =>
|
||||
Promise.resolve(
|
||||
streamResponse([
|
||||
'data: {"seq":8,"event":{"event":"run.session.assistant_message","properties":{}}}\r\n\r\n',
|
||||
'data: {"seq":8,"id":"evt_3","ts":"2026-05-22T16:25:34.940200Z","run_id":"run_1","event":"run.session.assistant_message","properties":{}}\r\n\r\n',
|
||||
]),
|
||||
),
|
||||
);
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue