import type { StreamOperation, StreamWireEvent, N8nStreamEvent, } from "../../types"; import { chatUnavailableMessage } from "../../constants"; import type { StreamProtocolAdapter } from "../protocol"; export function isN8nEvent(event: StreamWireEvent): event is N8nStreamEvent { switch (event.type) { case "begin": case "item": case "end": return true; case "error": return "content" in event || "metadata" in event; default: return false; } } export function makeN8nId( prefix: string, nodeId: string, runIndex?: number, ): string { return `n8n:${prefix}:${nodeId}:${runIndex ?? 0}`; } export class N8nStreamAdapter implements StreamProtocolAdapter { private hasStarted = false; private hasErrored = false; private messageId: string | null = null; canHandle(event: StreamWireEvent): boolean { return isN8nEvent(event); } adapt(raw: StreamWireEvent): StreamOperation[] { const event = raw as { type: string; content?: string; item?: { text?: string }; metadata?: { nodeId?: string; runIndex?: number; }; }; const meta = event.metadata ?? {}; const nodeId = meta.nodeId ?? "unknown"; switch (event.type) { case "begin": { if (!this.hasStarted) { this.hasStarted = true; this.messageId = makeN8nId("msg", nodeId, meta.runIndex); return [ { type: "messageStart", id: this.messageId }, { type: "stepStart", id: makeN8nId("step", nodeId, meta.runIndex), }, { type: "textStart", id: makeN8nId("text", nodeId, meta.runIndex), }, ]; } return [ { type: "stepStart", id: makeN8nId("step", nodeId, meta.runIndex), }, { type: "textStart", id: makeN8nId("text", nodeId, meta.runIndex), }, ]; } case "item": { if (!this.hasStarted) { this.hasStarted = true; this.messageId = makeN8nId("msg", nodeId, meta.runIndex); const textId = makeN8nId("text", nodeId, meta.runIndex); const delta = event.item?.text ?? event.content ?? ""; return [ { type: "messageStart", id: this.messageId }, { type: "stepStart", id: makeN8nId("step", nodeId, meta.runIndex) }, { type: "textStart", id: textId }, { type: "textDelta", id: textId, delta }, ]; } const delta = event.item?.text ?? event.content ?? ""; return [ { type: "textDelta", id: makeN8nId("text", nodeId, meta.runIndex), delta, }, ]; } case "end": { return [ { type: "textEnd", id: makeN8nId("text", nodeId, meta.runIndex), }, { type: "stepEnd", id: makeN8nId("step", nodeId, meta.runIndex), status: "completed" as const, }, ]; } case "error": { this.hasErrored = true; return [{ type: "messageError", message: chatUnavailableMessage }]; } default: return []; } } finish(): StreamOperation[] { if (!this.messageId || this.hasErrored) return []; return [{ type: "messageFinish", id: this.messageId }]; } }