@aparte/runner-n8n 0.27.3 → 0.28.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js CHANGED
@@ -292,7 +292,7 @@ function setupN8nButtons(config = aparteGlobalConfig) {
292
292
  tone: button.type === "secondary" ? "deny" : "affirm"
293
293
  })),
294
294
  // n8n's "Block User Input": a button, or nothing — no words instead.
295
- freeText: !input2.blockUserInput,
295
+ allowOther: !input2.blockUserInput,
296
296
  signal,
297
297
  ...context?.target ? { target: context.target } : {}
298
298
  });
package/dist/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","sources":["../src/wire.ts","../src/http.ts","../src/socket.ts","../src/buttons.ts","../src/runner.ts","../src/history.ts"],"sourcesContent":["/**\n * The body of n8n's answer to a chat message, read as it arrives.\n *\n * Two shapes come back, both labelled `application/json` by n8n:\n * - **streamed** — a Chat Trigger (or Webhook node) whose Response Mode is *Streaming*\n * sends newline-delimited JSON frames, `begin` / `item` / `end` per node that streams,\n * `error` when the workflow fails, and `keepalive` every 30 seconds while nothing else\n * comes — so before the first `begin` when a slow node runs first;\n * - **whole** — any other Response Mode sends one value once the workflow ends: the last\n * node's item (`{ output }` for an AI Agent), or what a *Respond to Webhook* node sent.\n *\n * Which one it is shows on the first line: a frame has a known `type`. Read off\n * `@n8n/chat` 1.43.3, the widget n8n embeds, and recorded against n8n 2.35.7 and 2.44.6.\n */\n\n/** One thing an answer carries, in order. */\nexport type N8nReplyPart =\n /** A node starts streaming. `stream` names it (node, run, item). */\n | { kind: 'begin'; stream: string }\n /** A piece of text. `stream` is absent for a line that was not a frame. */\n | { kind: 'item'; stream?: string; text: string }\n | { kind: 'end'; stream: string }\n /** The workflow failed; its message. */\n | { kind: 'error'; message: string }\n /** A frame type this reader does not know (a newer n8n). */\n | { kind: 'unknown'; type: string }\n /** The whole answer of a workflow that does not stream. */\n | { kind: 'reply'; text: string }\n /** A whole answer that is JSON with none of the fields a chat reads; its field names. */\n | { kind: 'unreadable'; fields: string[] }\n /**\n * The workflow answers later, through response nodes over n8n's chat WebSocket: the\n * execution to listen to, and the token n8n gives with it.\n */\n | { kind: 'deferred'; executionId?: string; resumeToken?: string }\n /** An HTML page instead of the answer: a proxy gave up waiting. */\n | { kind: 'html' }\n /** The stream stopped in the middle of a frame. */\n | { kind: 'cut' };\n\n/**\n * Whether the first line opens a stream. A whole answer can be any JSON — a Respond to\n * Webhook node may send `{ type: 'error', message }` or `{ type: 'item', output }` — so a\n * frame has to show it is one: n8n stamps every node frame with `metadata.nodeId`, its\n * `error` frame carries the text in `content`, and `keepalive` is n8n's alone.\n */\nfunction opensStream(value: unknown): boolean {\n if (!isFrame(value)) return false;\n const metadata = value.metadata;\n const fromNode = typeof metadata === 'object' && metadata !== null && typeof metadata['nodeId'] === 'string';\n if (value.type === 'keepalive') return true;\n if (value.type === 'error') return fromNode || typeof value.content === 'string';\n return fromNode;\n}\n\n/**\n * A proxy's error page: text that STARTS as HTML. An answer that merely contains markup —\n * an agent asked for a snippet — is not one, which is why this is a prefix test.\n */\nfunction isHtml(text: string): boolean {\n return /^\\s*<(?:!doctype|html|head|body)[\\s>]/i.test(text);\n}\n\nfunction parse(line: string): unknown {\n try { return JSON.parse(line); } catch { return undefined; }\n}\n\nfunction isFrame(value: unknown): value is { type: string; content?: unknown; metadata?: Record<string, unknown> } {\n return typeof value === 'object' && value !== null && !Array.isArray(value) && typeof (value as { type?: unknown }).type === 'string';\n}\n\n/** Which node's stream a frame belongs to: n8n streams one per node, run and item. */\nfunction streamOf(frame: { metadata?: Record<string, unknown> }): string {\n const m = frame.metadata ?? {};\n return `${String(m['nodeId'] ?? '')}:${String(m['runIndex'] ?? 0)}:${String(m['itemIndex'] ?? 0)}`;\n}\n\nfunction frameToPart(frame: { type: string; content?: unknown; metadata?: Record<string, unknown> }): N8nReplyPart | undefined {\n switch (frame.type) {\n case 'begin': return { kind: 'begin', stream: streamOf(frame) };\n case 'item': return { kind: 'item', stream: streamOf(frame), text: typeof frame.content === 'string' ? frame.content : '' };\n case 'end': return { kind: 'end', stream: streamOf(frame) };\n case 'error': return { kind: 'error', message: typeof frame.content === 'string' ? frame.content : '' };\n // n8n's heartbeat: it says the workflow is still running, nothing to draw.\n case 'keepalive': return undefined;\n default: return { kind: 'unknown', type: frame.type };\n }\n}\n\n/**\n * The text of a whole answer, the way n8n's widget reads it: `output`, else `text`, else\n * `message`; a `{ type: 'text', text }` part as its text. `undefined` when the value has\n * none of them — where the widget printed the JSON into the bubble, the most asked-about\n * failure on n8n's forum, this lets the runner say which field is missing instead.\n */\nexport function replyText(value: unknown): string | undefined {\n if (typeof value === 'string') return value;\n if (Array.isArray(value)) {\n const texts = value.map(replyText);\n return texts.every((t) => t === undefined) && value.length > 0 ? undefined : texts.filter((t) => t !== undefined).join('\\n\\n');\n }\n if (typeof value !== 'object' || value === null) return value === null || value === undefined ? '' : String(value);\n const record = value as Record<string, unknown>;\n const key = ['output', 'text', 'message'].find((k) => record[k] !== undefined && record[k] !== null);\n if (key === undefined) return Object.keys(record).length > 0 ? undefined : '';\n const text = record[key];\n if (typeof text === 'string') return text;\n if (typeof text === 'object' && text !== null && (text as { type?: unknown }).type === 'text' && typeof (text as { text?: unknown }).text === 'string') {\n return (text as { text: string }).text;\n }\n if (typeof text === 'number' || typeof text === 'boolean') return String(text);\n // An agent with an output parser answers with a structure: shown as such, readable.\n return ['```json', JSON.stringify(text, null, 2), '```'].join('\\n');\n}\n\n/**\n * The field names a whole answer carried, to name them in the error: the first item's, for\n * a list. Called only for a value `replyText` could not read, which always holds a record.\n */\nfunction fieldsOf(value: unknown): string[] {\n let record = value;\n while (Array.isArray(record)) record = record[0];\n return Object.keys(record as object).slice(0, 6);\n}\n\nfunction wholePart(raw: string): N8nReplyPart | undefined {\n const text = raw.trim();\n if (!text) return undefined;\n // Parsed first: a JSON answer is an answer, whatever markup its text holds.\n const value = parse(text);\n if (value === undefined) return isHtml(text) ? { kind: 'html' } : { kind: 'reply', text: raw };\n if (typeof value === 'object' && value !== null && (value as { executionStarted?: unknown }).executionStarted === true) {\n const { executionId, resumeToken } = value as { executionId?: unknown; resumeToken?: unknown };\n return {\n kind: 'deferred',\n ...(typeof executionId === 'string' || typeof executionId === 'number' ? { executionId: String(executionId) } : {}),\n ...(typeof resumeToken === 'string' ? { resumeToken } : {}),\n };\n }\n const reply = replyText(value);\n return reply === undefined ? { kind: 'unreadable', fields: fieldsOf(value) } : { kind: 'reply', text: reply };\n}\n\n/** Yields what n8n's answer carries, frame by frame as it streams, or once for a whole answer. */\nexport async function* readN8nReply(body: ReadableStream<Uint8Array>): AsyncGenerator<N8nReplyPart> {\n const reader = body.getReader();\n const decoder = new TextDecoder();\n // Widened on purpose: `lines` below decides it, a closure TypeScript's narrowing does not follow.\n let mode = 'unknown' as 'unknown' | 'frames' | 'whole';\n let raw = '';\n let pending = '';\n\n function* lines(text: string, last: boolean): Generator<N8nReplyPart> {\n const all = text.split('\\n');\n for (const [i, line] of all.entries()) {\n if (!line.trim()) continue;\n const value = parse(line);\n if (mode === 'unknown') {\n if (!opensStream(value)) { mode = 'whole'; return; }\n mode = 'frames';\n }\n if (isFrame(value)) {\n const part = frameToPart(value);\n if (part) yield part;\n } else if (last && i === all.length - 1 && line.trimStart().startsWith('{')) {\n // The body ended inside a frame: what came before it stands, the fragment does not.\n yield { kind: 'cut' };\n } else if (isHtml(line)) {\n yield { kind: 'html' };\n } else {\n // A line that is not a frame is text, as the widget reads it.\n yield { kind: 'item', text: line };\n }\n }\n }\n\n try {\n for (;;) {\n const { done, value } = await reader.read();\n const chunk = done ? decoder.decode() : decoder.decode(value, { stream: true });\n raw += chunk;\n if (mode !== 'whole') {\n pending += chunk;\n const cut = done ? pending.length : pending.lastIndexOf('\\n') + 1;\n const complete = pending.slice(0, cut);\n pending = pending.slice(cut);\n yield* lines(complete, done);\n }\n if (done) break;\n }\n } finally {\n reader.releaseLock();\n }\n if (mode !== 'frames') {\n const part = wholePart(raw);\n if (part) yield part;\n }\n}\n","/**\n * An HTTP error from n8n, said so the developer knows what to change in the workflow.\n *\n * n8n answers an error as JSON `{ message, hint }` (a 404 for a workflow that is not\n * active carries the hint to activate it), and a proxy in front of it answers as an HTML\n * page — a 502 or a 504 when the workflow ran longer than the proxy waits.\n */\nimport { AparteError, AparteErrorCode } from '@aparte/core';\n\nconst PROXY_STATUS = new Set([502, 504, 524]);\n\n/** What the request was for, said in the messages that depend on it. */\nexport type N8nRequestKind = 'answer' | 'history';\n\nfunction credentialNames(body: { credentials?: unknown }): string {\n const list = Array.isArray(body.credentials) ? body.credentials : [];\n const names = list\n .map((c) => (typeof c === 'object' && c !== null ? (c as { credentialName?: unknown }).credentialName : undefined))\n .filter((name): name is string => typeof name === 'string' && name.length > 0);\n return names.length > 0 ? ` (${names.join(', ')})` : '';\n}\n\nexport async function httpError(response: Response, kind: N8nRequestKind): Promise<AparteError> {\n const status = response.status;\n const text = (await response.text().catch(() => '')).trim();\n // A 404 from a Chat Trigger is a workflow that is not published or a wrong URL: n8n is\n // set up so it cannot answer, which no retry mends.\n const code = AparteError.codeForStatus(status) ?? (status === 404 ? AparteErrorCode.PROVIDER_MISCONFIGURED : AparteErrorCode.PROVIDER_ERROR);\n const make = (message: string, errorCode: AparteErrorCode = code) => new AparteError(message, errorCode, undefined, undefined, status);\n const misconfigured = AparteErrorCode.PROVIDER_MISCONFIGURED;\n\n // A page, not an answer: never show its markup to the person chatting.\n if (text.startsWith('<') || (response.headers.get('content-type') ?? '').includes('text/html')) {\n return PROXY_STATUS.has(status)\n ? make(`A proxy in front of n8n gave up waiting (HTTP ${status}). The workflow may still be running — its execution list shows the result.`, AparteErrorCode.NET_TIMEOUT)\n // A status that says something keeps it (a 429, a 503, a refused key); an unmapped\n // one, a 404 above all, is a wrong URL: set up so it cannot answer.\n : make(`n8n answered HTTP ${status} with a web page instead of a chat answer. Check that the URL is the Chat Trigger's production URL, the one that ends in /chat.`, AparteError.codeForStatus(status) ?? misconfigured);\n }\n\n let body: { message?: unknown; hint?: unknown; status?: unknown; credentials?: unknown } = {};\n try { body = JSON.parse(text) as typeof body; } catch { /* not JSON: its text below */ }\n\n // The Chat Trigger checks the workflow's credentials before it runs it.\n if (status === 428 || body.status === 'credential_connections_required') {\n return make(`The n8n workflow cannot run until its credentials are connected${credentialNames(body)}. Connect them in n8n, then try again.`, misconfigured);\n }\n if (body.status === 'credential_readiness_check_failed') {\n return make('n8n could not check the workflow\\'s credentials. Open the workflow in n8n to see which one fails.');\n }\n // Loading a session's history from memory needs the memory on the trigger too.\n if (kind === 'history' && status === 500) {\n return make('n8n could not load the conversation\\'s history. When the Chat Trigger\\'s \"Load Previous Session\" '\n + 'is \"From Memory\", connect the memory node to the Chat Trigger as well as to the agent.', misconfigured);\n }\n\n let message = typeof body.message === 'string' ? body.message : text;\n if (typeof body.message === 'string' && typeof body.hint === 'string') message = `${body.message} ${body.hint}`;\n return make(message.slice(0, 400) || `The n8n workflow answered HTTP ${status}.`);\n}\n\n/**\n * The request never reached an answer: n8n is down, the URL is wrong, or the browser\n * refused the answer because the Chat Trigger does not allow this page's origin — which\n * the browser reports exactly like a network failure, so all three are named.\n */\nexport function unreachable(error: unknown, url: string): unknown {\n if (!(error instanceof TypeError)) return error;\n const origin = typeof location === 'undefined' ? \"this page's origin\" : location.origin;\n const offline = typeof navigator !== 'undefined' && navigator.onLine === false;\n return new AparteError(\n `Could not reach n8n at ${url}. Check that it is the Chat Trigger's production URL, that the workflow is published, `\n + `and that ${origin} is in the trigger's Allowed Origins (CORS).`,\n offline ? AparteErrorCode.NET_OFFLINE : AparteErrorCode.NET_ERROR,\n );\n}\n","/**\n * n8n's chat WebSocket — how a workflow that answers through response nodes (Chat nodes,\n * \"Using Response Nodes\" on the Chat Trigger) talks to the page.\n *\n * Recorded against n8n 2.44.7 rather than read from documentation, which has none: the\n * message that starts the execution is answered `{ executionStarted, executionId,\n * resumeToken }`, the page opens `/chat?sessionId&executionId&isPublic=true&token`, and the\n * socket then carries, in this order for a Chat node that sends and then one that asks:\n *\n * \"n8n|heartbeat\" answered \"n8n|heartbeat-ack\"\n * \"Hello! You said: hi\" a message, as plain text\n * \"n8n|continue\" the execution goes on: more is coming\n * {\"type\":\"with-buttons\", text, buttons:[{ text, link, type }], blockUserInput}\n *\n * A button's link resumes the execution when fetched (a GET), and the next messages come on\n * the same socket. A Chat node that waits for a written reply is answered on the socket\n * too, as JSON. n8n 3.0 moves the bare `n8n|…` strings to JSON (`{ type: 'heartbeat' }`,\n * `{ type: 'continue' }`, `{ type: 'message', text }`); both are read.\n */\n\n/** A button of a Chat node that waits for an approval. */\nexport interface N8nChatButton {\n text: string;\n /** The URL that resumes the waiting execution with this answer. */\n link: string;\n /** n8n's style: `primary` or `secondary`. */\n type: string;\n}\n\nexport type N8nSocketFrame =\n | { kind: 'text'; text: string }\n | { kind: 'continue' }\n | { kind: 'buttons'; text: string; buttons: N8nChatButton[]; blockUserInput: boolean }\n | { kind: 'error'; message: string }\n | { kind: 'closed' };\n\n/** The socket a deferred execution talks on, from the workflow's chat URL. */\nexport function chatSocketUrl(webhookUrl: string, sessionId: string, executionId: string, token?: string): string {\n const origin = new URL(webhookUrl).origin.replace(/^http/, 'ws');\n const params = new URLSearchParams({ sessionId, executionId, isPublic: 'true' });\n if (token) params.set('token', token);\n return `${origin}/chat?${params.toString()}`;\n}\n\n/** One frame as text, read the way n8n's own widget reads it. */\nexport function readSocketFrame(data: string): N8nSocketFrame | 'heartbeat' | 'heartbeat-json' {\n if (data === 'n8n|heartbeat') return 'heartbeat';\n if (data === 'n8n|continue') return { kind: 'continue' };\n let value: unknown;\n try { value = JSON.parse(data); } catch { return { kind: 'text', text: data }; }\n if (typeof value !== 'object' || value === null) return { kind: 'text', text: data };\n const frame = value as { type?: unknown; text?: unknown; message?: unknown; buttons?: unknown; blockUserInput?: unknown };\n switch (frame.type) {\n case 'heartbeat': return 'heartbeat-json';\n case 'continue': return { kind: 'continue' };\n case 'message': return { kind: 'text', text: typeof frame.text === 'string' ? frame.text : '' };\n case 'error': return { kind: 'error', message: typeof frame.message === 'string' ? frame.message : '' };\n case 'with-buttons': {\n const buttons = (Array.isArray(frame.buttons) ? frame.buttons : [])\n .filter((b): b is N8nChatButton => typeof b === 'object' && b !== null\n && typeof (b as N8nChatButton).text === 'string' && typeof (b as N8nChatButton).link === 'string')\n .map((b) => ({ text: b.text, link: b.link, type: typeof b.type === 'string' ? b.type : 'primary' }));\n return { kind: 'buttons', text: typeof frame.text === 'string' ? frame.text : '', buttons, blockUserInput: frame.blockUserInput === true };\n }\n default: return { kind: 'text', text: data };\n }\n}\n\n/** The constructor the runner opens sockets with: the browser's, or one a test gives. */\nexport type N8nWebSocketLike = new (url: string) => {\n readonly readyState: number;\n send(data: string): void;\n close(): void;\n onopen: ((event: unknown) => void) | null;\n onmessage: ((event: { data: unknown }) => void) | null;\n onclose: ((event: unknown) => void) | null;\n onerror: ((event: unknown) => void) | null;\n};\n\nconst OPEN = 1;\n\n/**\n * One execution's socket: frames queue until a turn reads them, so a message that arrives\n * between two turns — after a button, before the page resumed — is not lost.\n */\nexport class N8nChatSocket {\n closed = false;\n private readonly socket: InstanceType<N8nWebSocketLike>;\n private readonly queue: N8nSocketFrame[] = [];\n private waiter: ((frame: N8nSocketFrame) => void) | undefined;\n private readonly opened: Promise<void>;\n\n constructor(url: string, Socket: N8nWebSocketLike) {\n this.socket = new Socket(url);\n this.opened = new Promise((resolve) => {\n this.socket.onopen = () => resolve();\n });\n this.socket.onmessage = (event) => {\n const read = readSocketFrame(String(event.data));\n if (read === 'heartbeat') this.socket.send('n8n|heartbeat-ack');\n else if (read === 'heartbeat-json') this.socket.send(JSON.stringify({ type: 'heartbeat-ack' }));\n else this.push(read);\n };\n // An error is followed by a close, which is the one that says it is over.\n this.socket.onerror = () => undefined;\n this.socket.onclose = () => {\n this.closed = true;\n this.push({ kind: 'closed' });\n };\n }\n\n private push(frame: N8nSocketFrame): void {\n const waiter = this.waiter;\n this.waiter = undefined;\n if (waiter) waiter(frame);\n else this.queue.push(frame);\n }\n\n /** Put a frame back in front, read too early. */\n unshift(frame: N8nSocketFrame): void {\n this.queue.unshift(frame);\n }\n\n /**\n * The next frame, waited for; `'timeout'` when `timeoutMs` passes first, `undefined`\n * when the signal fires. A closed socket answers `closed` from then on.\n */\n next(signal: AbortSignal, timeoutMs?: number): Promise<N8nSocketFrame | 'timeout' | undefined> {\n const queued = this.queue.shift();\n if (queued) return Promise.resolve(queued);\n if (this.closed) return Promise.resolve({ kind: 'closed' });\n if (signal.aborted) return Promise.resolve(undefined);\n return new Promise((resolve) => {\n let timer: ReturnType<typeof setTimeout> | undefined;\n const done = (value: N8nSocketFrame | 'timeout' | undefined) => {\n if (timer !== undefined) clearTimeout(timer);\n signal.removeEventListener('abort', onAbort);\n if (this.waiter === settle) this.waiter = undefined;\n resolve(value);\n };\n const settle = (frame: N8nSocketFrame) => done(frame);\n const onAbort = () => done(undefined);\n this.waiter = settle;\n signal.addEventListener('abort', onAbort, { once: true });\n if (timeoutMs !== undefined) timer = setTimeout(() => done('timeout'), timeoutMs);\n });\n }\n\n /** Send a message on the socket, once it is open. */\n async send(message: Record<string, unknown>): Promise<void> {\n if (this.socket.readyState !== OPEN) await this.opened;\n this.socket.send(JSON.stringify(message));\n }\n\n close(): void {\n this.socket.close();\n }\n}\n","/**\n * A Chat node's buttons, answered in the page.\n *\n * The runner hands them over as a call the page answers — the shape a browser tool has\n * (`run-suspended`, reason `external`) — so the turn waits for the click the way it waits\n * for any answer only the page can give, and goes on when it comes. `setupN8nButtons`\n * registers what answers it: the buttons, asked at the composer with their own labels\n * through core's `requestUserInput`, and a transcript line that says which was chosen.\n */\nimport { aparteGlobalConfig, requestUserInput } from '@aparte/core';\nimport type { AparteConfig, AparteToolCallSegment } from '@aparte/core';\n\n/** The name the runner gives a Chat node's buttons, and `setupN8nButtons` answers. */\nexport const N8N_BUTTONS_TOOL = 'n8n_buttons';\n\n/** What the runner puts in the call: the question, and its buttons in n8n's order. */\nexport interface N8nButtonsInput {\n text: string;\n buttons: { text: string; type: string }[];\n /** n8n's \"Block User Input\": the answer has to be a button, with no words instead. */\n blockUserInput: boolean;\n}\n\n/**\n * What the person answered: a button, by its place and its label — or words of their own\n * (`written`, `index` -1), which n8n hears as a chat message: the Chat node resumes on it as\n * not approved, with the words as its input.\n */\nexport interface N8nButtonsResult {\n index: number;\n text: string;\n written?: true;\n}\n\n/**\n * Answer a Chat node's buttons in this page: they are asked at the composer, with their\n * labels, and the line in the transcript keeps the one chosen. Call it once, on the config\n * the chat renders with, when the workflow has Chat nodes that wait for an approval.\n *\n * @example\n * setupN8nButtons();\n * new AparteClient({ streamRunner: createN8nRunner({ webhookUrl }) }).start();\n */\nexport function setupN8nButtons(config: AparteConfig = aparteGlobalConfig): void {\n config.registerTool({\n name: N8N_BUTTONS_TOOL,\n description: 'The buttons of an n8n Chat node that waits for an answer. Answered by the person in the page.',\n inputSchema: { type: 'object', properties: {} },\n }, async (call, signal, context) => {\n const input = call.input as unknown as N8nButtonsInput;\n const answer = await requestUserInput({\n kind: 'approval',\n message: input.text,\n options: input.buttons.map((button, index) => ({\n value: String(index),\n label: button.text,\n tone: button.type === 'secondary' ? 'deny' : 'affirm',\n })),\n // n8n's \"Block User Input\": a button, or nothing — no words instead.\n freeText: !input.blockUserInput,\n signal,\n ...(context?.target ? { target: context.target } : {}),\n });\n const content = answer.action === 'accept' ? (answer.content as { option?: string; instruction?: string } | undefined) : undefined;\n const written = content?.instruction?.trim();\n if (written && content?.option === undefined) {\n const result: N8nButtonsResult = { index: -1, text: written, written: true };\n return { toolCallId: call.id, content: written, structuredContent: result };\n }\n const index = content?.option === undefined ? -1 : Number(content.option);\n const chosen = input.buttons[index];\n if (!chosen) throw new Error('No button was chosen.');\n const result: N8nButtonsResult = { index, text: chosen.text };\n return { toolCallId: call.id, content: chosen.text, structuredContent: result };\n });\n // The line in the transcript is the answer, quietly: no tool name, no \"Done\", nothing to open.\n config.registerToolDisplay(N8N_BUTTONS_TOOL, {\n icon: 'messageSquare',\n label: (segment: AparteToolCallSegment) => (typeof segment.result === 'string' && segment.result ? segment.result : input(segment)),\n quiet: true,\n details: false,\n });\n}\n\n/** While the buttons wait: the question they answer. */\nfunction input(segment: AparteToolCallSegment): string {\n return (segment.toolCall?.input as Partial<N8nButtonsInput> | undefined)?.text ?? '';\n}\n","/**\n * createN8nRunner — an n8n chat workflow, drawn by aparté.\n *\n * The workflow's agent runs in n8n, with its model, its tools and its memory; n8n keeps\n * the conversation per session. So each turn sends what n8n's own widget sends — the\n * session, the message, the files — and the runner turns n8n's answer into the events\n * `AparteClient`'s adapter draws, the same ones a loop run in the page emits.\n */\nimport { AparteError, AparteErrorCode, uuid } from '@aparte/core';\nimport type { AparteChatMessage, AparteStreamRunEvent, AparteStreamRunner, AparteStreamRunOptions } from '@aparte/core';\nimport { readN8nReply } from './wire.js';\nimport { httpError, unreachable } from './http.js';\nimport { N8nChatSocket, chatSocketUrl } from './socket.js';\nimport type { N8nWebSocketLike } from './socket.js';\nimport { N8N_BUTTONS_TOOL } from './buttons.js';\nimport type { N8nButtonsInput, N8nButtonsResult } from './buttons.js';\n\ntype Suspended = Extract<AparteStreamRunEvent, { type: 'run-suspended' }>;\n\nexport interface N8nRunnerOptions {\n /**\n * The workflow's chat URL: the Chat Trigger's production URL, the one that ends in\n * `/chat` — or a Webhook node's URL, for a workflow that answers through it.\n */\n webhookUrl: string;\n /**\n * The n8n session a turn runs in. n8n keeps the agent's memory per session and the\n * page sends only the new message, so this is what ties a conversation to its memory.\n * Give the id your app keeps for the conversation — the one you loaded its history\n * with ({@link loadN8nHistory}), or a conversation manager's `activeId`. A function\n * is read every turn.\n * @default a new session for each new conversation: a turn with no reply before it\n * starts one, and the turns after it continue it\n */\n sessionId?: string | ((opts: AparteStreamRunOptions) => string);\n /** Sent with every message as `metadata`, which the workflow reads from the trigger's output. */\n metadata?: Record<string, unknown> | ((opts: AparteStreamRunOptions) => Record<string, unknown>);\n /** Sent with every request; a function is called per request (a fresh token each time). */\n headers?: Record<string, string> | (() => Record<string, string> | Promise<Record<string, string>>);\n /** @default the global `fetch` */\n fetch?: typeof fetch;\n /** For a workflow on another origin that authenticates by cookie. */\n credentials?: RequestCredentials;\n /**\n * The body field that carries the session, for a workflow that reads another one.\n * @default 'sessionId', the Chat Trigger's\n */\n chatSessionKey?: string;\n /**\n * The body field that carries the message, for a workflow that reads another one.\n * @default 'chatInput', the Chat Trigger's\n */\n chatInputKey?: string;\n /**\n * Told what the runner could not read in n8n's answer — a frame type it does not\n * know, a stream cut mid-frame. For the developer, not the person chatting.\n * @default `console.warn`\n */\n onWarning?: (message: string) => void;\n /**\n * What a workflow that answers through response nodes is read with: n8n's chat\n * WebSocket. Given for a runtime without a global one, or a test.\n * @default the global `WebSocket`\n */\n WebSocket?: N8nWebSocketLike;\n}\n\n/** What a paused turn hands the page, and gets back with the button chosen. */\ninterface Ticket {\n v: 1;\n sessionId: string;\n executionId: string;\n token?: string;\n toolCallId: string;\n /** The buttons' links, in the order the page was given the buttons. */\n links: string[];\n}\n\nfunction readTicket(token: string | undefined): Ticket | undefined {\n if (!token) return undefined;\n try {\n const ticket = JSON.parse(token) as Partial<Ticket>;\n return ticket.v === 1 && typeof ticket.sessionId === 'string' && typeof ticket.executionId === 'string'\n && typeof ticket.toolCallId === 'string' && Array.isArray(ticket.links) ? ticket as Ticket : undefined;\n } catch {\n return undefined;\n }\n}\n\n/**\n * How long a message is given to be followed by `continue`. n8n sends it in the same tick\n * as the message when the execution goes on (recorded on 2.44.7: both at +3172 ms); a\n * message with nothing behind it is one the execution waits after, for the person.\n */\nconst CONTINUE_GRACE_MS = 250;\n\n/** A file as n8n's widget sends it on the socket: a data URL. */\nasync function dataUrl(file: { blob: Blob; name: string }): Promise<{ name: string; type: string; data: string }> {\n const bytes = new Uint8Array(await file.blob.arrayBuffer());\n let binary = '';\n for (let i = 0; i < bytes.length; i++) binary += String.fromCharCode(bytes[i]!);\n const type = file.blob.type || 'application/octet-stream';\n return { name: file.name, type, data: `data:${type};base64,${btoa(binary)}` };\n}\n\n/** The workflow streamed nothing: the widget's own diagnosis, said to the developer. */\nconst EMPTY_STREAM = 'The n8n workflow streamed nothing. When its Chat Trigger streams, the node that answers '\n + 'has to stream too: turn on streaming in the AI Agent\\'s options, or set the trigger\\'s Response Mode to '\n + '\"When Last Node Finishes\".';\n\nconst EMPTY_WHOLE = 'The n8n workflow finished without an answer: its last node returned no text. End the '\n + 'workflow on the node that answers — an AI Agent, or a node that returns an `output` field.';\n\nconst PROXY_TIMEOUT = 'A proxy timed out waiting for n8n\\'s answer. The workflow may still be running — '\n + 'its execution list shows the result.';\n\nconst DEFERRED = 'This n8n workflow answers through response nodes, over n8n\\'s chat WebSocket, and n8n did not say '\n + 'which execution to listen to. Check the Chat Trigger\\'s Response Mode.';\n\nconst NO_WEBSOCKET = 'This n8n workflow answers through response nodes, over n8n\\'s chat WebSocket, and this runtime has '\n + 'no WebSocket. Pass one as the runner\\'s `WebSocket` option.';\n\nconst unreadable = (fields: string[]) => `The n8n workflow answered with the fields ${fields.map((f) => `\"${f}\"`).join(', ')} `\n + 'but no \"output\", \"text\" or \"message\" field, which is where a chat reads the answer. End the workflow on an AI Agent, '\n + 'or add a Set node that puts the reply in \"output\".';\n\n/** The message the turn answers: the last one the person wrote. */\nfunction lastUserIndex(messages: AparteChatMessage[]): number {\n for (let i = messages.length - 1; i >= 0; i--) if (messages[i]?.role === 'user') return i;\n return -1;\n}\n\n/** The text of a message, its parts kept apart: a file inlined after the words starts on a line of its own. */\nfunction textOf(content: AparteChatMessage['content'] | undefined): string {\n if (content === undefined) return '';\n if (typeof content === 'string') return content;\n return content.flatMap((part) => (part.type === 'text' && part.text ? [part.text] : [])).join('\\n\\n');\n}\n\nconst EXTENSIONS: Record<string, string> = { 'image/png': 'png', 'image/jpeg': 'jpg', 'image/gif': 'gif', 'image/webp': 'webp', 'image/svg+xml': 'svg' };\n\n/**\n * An image part — a data URL — as the file the Chat Trigger receives. A `Blob` and a name\n * rather than a `File`, which Node only has from 20 and this package runs from 18.\n */\nfunction imageFile(image: string, mimeType: string | undefined, index: number): { blob: Blob; name: string } | undefined {\n const match = /^data:([^;,]+)?((?:;[^;,]*)*?);base64,(.*)$/s.exec(image);\n if (!match) return undefined;\n const type = mimeType ?? match[1] ?? 'application/octet-stream';\n const binary = atob(match[3] ?? '');\n const bytes = new Uint8Array(binary.length);\n for (let i = 0; i < binary.length; i++) bytes[i] = binary.charCodeAt(i);\n return { blob: new Blob([bytes], { type }), name: `image-${index + 1}.${EXTENSIONS[type] ?? 'bin'}` };\n}\n\n/**\n * What the Chat Trigger receives as `files`: the files the person picked, whatever their\n * type, when the client hands them over (`files` on a send) — else the images inlined in\n * the message, for a caller that only has those.\n */\nfunction filesOf(opts: AparteStreamRunOptions, message: AparteChatMessage | undefined): { blob: Blob; name: string }[] {\n if (opts.files && opts.files.length > 0) return opts.files.map((file) => ({ blob: file, name: file.name }));\n return (Array.isArray(message?.content) ? message.content : [])\n .flatMap((part) => (part.type === 'image' ? [part] : []))\n .map((part, i) => imageFile(part.image, part.mimeType, i))\n .filter((file) => file !== undefined);\n}\n\n/**\n * Put aparté on an n8n chat workflow: pass the result as `AparteClient`'s `streamRunner`.\n * n8n runs the agent, its tools and its memory; the page sends the person's message and\n * draws the answer, streamed when the Chat Trigger streams.\n */\nexport function createN8nRunner(options: N8nRunnerOptions): AparteStreamRunner {\n const warn = options.onWarning ?? ((message: string) => console.warn(`[aparte] n8n: ${message}`));\n const sessionKey = options.chatSessionKey ?? 'sessionId';\n const inputKey = options.chatInputKey ?? 'chatInput';\n let ownSession = `aparte-${uuid()}`;\n // One socket per session while its execution runs: the turns after it — a written\n // reply to a Chat node that waits for one, a button's answer — go on it.\n const sockets = new Map<string, { socket: N8nChatSocket; executionId: string; token?: string }>();\n\n const openSocket = (sessionId: string, executionId: string, token?: string) => {\n const Socket = options.WebSocket ?? (globalThis as { WebSocket?: N8nWebSocketLike }).WebSocket;\n if (!Socket) throw new AparteError(NO_WEBSOCKET, AparteErrorCode.PROVIDER_MISCONFIGURED);\n sockets.get(sessionId)?.socket.close();\n const entry = { socket: new N8nChatSocket(chatSocketUrl(options.webhookUrl, sessionId, executionId, token), Socket), executionId, ...(token ? { token } : {}) };\n sockets.set(sessionId, entry);\n return entry;\n };\n\n /**\n * Read the execution's socket for this turn: each message a block of its own, until the\n * execution stops to wait — for a written reply, or on buttons, which pause the turn for\n * the page — or ends.\n */\n const listen = async (opts: AparteStreamRunOptions, sessionId: string): Promise<void> => {\n const emit = opts.emitter;\n const entry = sockets.get(sessionId)!;\n const { socket } = entry;\n let drawn = false;\n const finish = () => {\n if (drawn) emit({ type: 'text-flush' });\n emit({ type: 'run-done' });\n };\n for (;;) {\n const frame = await socket.next(opts.signal);\n if (frame === undefined) return;\n if (frame === 'timeout' || frame.kind === 'continue') continue;\n switch (frame.kind) {\n case 'text': {\n if (drawn) emit({ type: 'text-flush' });\n if (frame.text) {\n emit({ type: 'text-delta', delta: frame.text });\n drawn = true;\n }\n const after = await socket.next(opts.signal, CONTINUE_GRACE_MS);\n if (after === undefined) return;\n if (after === 'timeout') return finish();\n if (after.kind !== 'continue') socket.unshift(after);\n break;\n }\n case 'buttons': {\n if (drawn) emit({ type: 'text-flush' });\n if (frame.text) {\n emit({ type: 'text-delta', delta: frame.text });\n emit({ type: 'text-flush' });\n }\n // As n8n's widget: only a button that leads back to this n8n is shown —\n // a link elsewhere would have the page fetch whatever a workflow wrote.\n const origin = new URL(options.webhookUrl).origin;\n const buttons = frame.buttons.filter((b) => {\n try { return new URL(b.link, options.webhookUrl).origin === origin; } catch { return false; }\n });\n if (buttons.length === 0) {\n warn('An n8n Chat node sent buttons whose links lead off this n8n instance; they were not shown.');\n emit({ type: 'run-done' });\n return;\n }\n const toolCallId = `n8n-buttons-${uuid()}`;\n const input: N8nButtonsInput = {\n text: frame.text,\n buttons: buttons.map((b) => ({ text: b.text, type: b.type })),\n blockUserInput: frame.blockUserInput,\n };\n const ticket: Ticket = {\n v: 1, sessionId, executionId: entry.executionId, ...(entry.token ? { token: entry.token } : {}),\n toolCallId, links: buttons.map((b) => b.link),\n };\n emit({ type: 'tool-start', toolCallId, name: N8N_BUTTONS_TOOL, input });\n emit({\n type: 'run-suspended',\n pending: [{ toolCallId, name: N8N_BUTTONS_TOOL, input, reason: 'external' }],\n ticket: JSON.stringify(ticket),\n } as Suspended);\n return;\n }\n case 'error':\n if (drawn) emit({ type: 'text-flush' });\n throw new AparteError(frame.message || 'The n8n workflow failed.', AparteErrorCode.PROVIDER_ERROR);\n case 'closed':\n // The execution ended: the next message starts a new one.\n sockets.delete(sessionId);\n return finish();\n }\n }\n };\n\n /** The page answered a Chat node's buttons: n8n hears it through the button's link. */\n const resume = async (opts: AparteStreamRunOptions, ticket: Ticket, doFetch: typeof fetch): Promise<void> => {\n const emit = opts.emitter;\n emit({ type: 'run-start' });\n emit({ type: 'turn-start' });\n const outcome = opts.deferral?.outcomes?.[ticket.toolCallId];\n const chosen = outcome?.kind === 'result' ? (outcome.structuredContent as N8nButtonsResult | undefined) : undefined;\n const link = chosen ? ticket.links[chosen.index] : undefined;\n // Words of the person's own instead of a button: n8n hears them as a chat message,\n // which the waiting Chat node resumes on as not approved.\n if (outcome?.kind === 'result' && chosen?.written) {\n emit({ type: 'tool-resolved', toolCallId: ticket.toolCallId, result: outcome.content, structuredResult: chosen });\n const entry = sockets.get(ticket.sessionId) ?? openSocket(ticket.sessionId, ticket.executionId, ticket.token);\n await entry.socket.send({ sessionId: ticket.sessionId, action: 'sendMessage', chatInput: chosen.text, files: [] });\n await listen(opts, ticket.sessionId);\n return;\n }\n if (!outcome || outcome.kind !== 'result' || !link) {\n // No button: the execution keeps waiting in n8n, and a written message answers it.\n emit({ type: 'tool-aborted', toolCallId: ticket.toolCallId });\n emit({ type: 'run-done' });\n return;\n }\n emit({ type: 'tool-resolved', toolCallId: ticket.toolCallId, result: outcome.content, structuredResult: chosen });\n // The socket the buttons came on, or a new one on the same execution if it closed.\n const entry = sockets.get(ticket.sessionId);\n if (!entry || entry.socket.closed) openSocket(ticket.sessionId, ticket.executionId, ticket.token);\n const response = await doFetch(link, {\n signal: opts.signal,\n ...(options.credentials ? { credentials: options.credentials } : {}),\n }).catch((error: unknown) => { throw unreachable(error, link); });\n if (!response.ok) throw await httpError(response, 'answer');\n await listen(opts, ticket.sessionId);\n };\n\n return async (opts) => {\n const emit = opts.emitter;\n const doFetch = options.fetch ?? fetch;\n try {\n const ticket = readTicket(opts.deferral?.token);\n if (ticket) {\n await resume(opts, ticket, doFetch);\n return undefined;\n }\n const messages = (opts.baseRequest as { messages: AparteChatMessage[] }).messages;\n const last = lastUserIndex(messages);\n const message = last >= 0 ? messages[last] : undefined;\n // No reply before the message: a new conversation, or one that was cleared. n8n\n // must not answer it from the memory of the one before.\n if (options.sessionId === undefined && !messages.slice(0, Math.max(last, 0)).some((m) => m.role === 'assistant')) {\n ownSession = `aparte-${uuid()}`;\n }\n const headers = typeof options.headers === 'function' ? await options.headers() : options.headers ?? {};\n const sessionId = (typeof options.sessionId === 'function' ? options.sessionId(opts) : options.sessionId) ?? ownSession;\n const metadata = typeof options.metadata === 'function' ? options.metadata(opts) : options.metadata;\n const files = filesOf(opts, message);\n\n // An execution that waits for the person's written reply hears it on its socket.\n const execution = sockets.get(sessionId);\n if (execution && !execution.socket.closed) {\n emit({ type: 'run-start' });\n emit({ type: 'turn-start' });\n await execution.socket.send({\n sessionId,\n action: 'sendMessage',\n chatInput: textOf(message?.content),\n files: await Promise.all(files.map(dataUrl)),\n });\n await listen(opts, sessionId);\n return undefined;\n }\n\n // As n8n's widget sends it: JSON, or a form when the message carries files.\n const fields: Record<string, string | Record<string, unknown>> = {\n action: 'sendMessage',\n [sessionKey]: sessionId,\n [inputKey]: textOf(message?.content),\n ...(metadata ? { metadata } : {}),\n };\n let body: string | FormData;\n // The page's headers, without a Content-Type in any case: a form's boundary is\n // the browser's to write, and JSON gets exactly one, ours.\n const requestHeaders: Record<string, string> = { Accept: 'text/plain' };\n for (const [name, value] of Object.entries(headers)) if (name.toLowerCase() !== 'content-type') requestHeaders[name] = value;\n if (files.length > 0) {\n const form = new FormData();\n for (const [key, value] of Object.entries(fields)) form.append(key, typeof value === 'string' ? value : JSON.stringify(value));\n for (const file of files) form.append('files', file.blob, file.name);\n body = form;\n } else {\n body = JSON.stringify(fields);\n requestHeaders['Content-Type'] = 'application/json';\n }\n\n const response = await doFetch(options.webhookUrl, {\n method: 'POST',\n headers: requestHeaders,\n body,\n signal: opts.signal,\n ...(options.credentials ? { credentials: options.credentials } : {}),\n }).catch((error: unknown) => { throw unreachable(error, options.webhookUrl); });\n if (!response.ok) throw await httpError(response, 'answer');\n\n emit({ type: 'run-start' });\n emit({ type: 'turn-start' });\n\n // n8n streams once per node that answers. One is drawn at a time, in the order\n // they began: another node's text waits for the drawn one to end, and starts a\n // block of its own below it.\n let drawn = false;\n let whole = false;\n let textOpen = false;\n let deferred = false;\n let active: string | undefined;\n const waiting = new Map<string, { text: string[]; ended: boolean }>();\n const unknown = new Set<string>();\n const flush = () => {\n if (textOpen) emit({ type: 'text-flush' });\n textOpen = false;\n };\n const draw = (text: string) => {\n if (!text) return;\n drawn = true;\n textOpen = true;\n emit({ type: 'text-delta', delta: text });\n };\n const open = (stream: string) => {\n active = stream;\n flush();\n };\n const drain = () => {\n active = undefined;\n for (const [stream, queued] of waiting) {\n waiting.delete(stream);\n open(stream);\n for (const text of queued.text) draw(text);\n if (!queued.ended) return;\n active = undefined;\n }\n };\n const waitFor = (stream: string) => {\n let queued = waiting.get(stream);\n if (!queued) waiting.set(stream, queued = { text: [], ended: false });\n return queued;\n };\n\n if (!response.body) throw new AparteError(EMPTY_STREAM, AparteErrorCode.PROVIDER_MISCONFIGURED);\n for await (const part of readN8nReply(response.body)) {\n switch (part.kind) {\n case 'begin':\n if (active === undefined) open(part.stream);\n else if (part.stream !== active) waitFor(part.stream);\n break;\n case 'item':\n if (part.stream === undefined || part.stream === active) draw(part.text);\n else if (active === undefined) { open(part.stream); draw(part.text); }\n else waitFor(part.stream).text.push(part.text);\n break;\n case 'end':\n if (part.stream === active) drain();\n else waitFor(part.stream).ended = true;\n break;\n case 'error':\n flush();\n throw new AparteError(part.message || 'The n8n workflow failed.', AparteErrorCode.PROVIDER_ERROR);\n case 'reply':\n whole = true;\n draw(part.text);\n break;\n case 'unreadable':\n throw new AparteError(unreadable(part.fields), AparteErrorCode.PROVIDER_MISCONFIGURED);\n case 'deferred':\n // Response nodes: the answer comes on n8n's chat socket.\n if (!part.executionId) throw new AparteError(DEFERRED, AparteErrorCode.PROVIDER_MISCONFIGURED);\n openSocket(sessionId, part.executionId, part.resumeToken);\n deferred = true;\n break;\n case 'html':\n flush();\n throw new AparteError(PROXY_TIMEOUT, AparteErrorCode.NET_TIMEOUT);\n case 'cut':\n warn('n8n\\'s stream stopped in the middle of a frame; the text before it is kept.');\n break;\n case 'unknown':\n if (!unknown.has(part.type)) warn(`n8n sent a \"${part.type}\" frame, which this runner does not read; it was skipped.`);\n unknown.add(part.type);\n break;\n }\n }\n if (deferred) {\n await listen(opts, sessionId);\n return undefined;\n }\n // A stream that was cut before its `end`: what it sent is still the answer — all\n // of it, the streams still waiting included, ended or not.\n drain();\n for (const [stream, queued] of waiting) {\n waiting.delete(stream);\n open(stream);\n for (const text of queued.text) draw(text);\n }\n if (!drawn) throw new AparteError(whole ? EMPTY_WHOLE : EMPTY_STREAM, AparteErrorCode.PROVIDER_MISCONFIGURED);\n flush();\n emit({ type: 'run-done' });\n return undefined;\n } catch (error) {\n // Stopped by the person: not a failure, nothing to report.\n if (opts.signal.aborted) return undefined;\n throw error;\n }\n };\n}\n","/**\n * loadN8nHistory — a session's earlier messages, read back from the workflow's memory.\n *\n * n8n keeps the conversation, the page keeps nothing: after a reload, the Chat Trigger\n * answers `loadPreviousSession` with the session's messages as its memory node stored\n * them (LangChain's serialised messages). This turns them into messages a chat shows.\n */\nimport { AparteError, AparteErrorCode } from '@aparte/core';\nimport type { AparteMessage } from '@aparte/core';\nimport { httpError, unreachable } from './http.js';\n\nexport interface N8nHistoryOptions {\n /** The Chat Trigger's production URL, the one the runner sends to. */\n webhookUrl: string;\n /** The session to read: the one the runner sends the conversation's turns in. */\n sessionId: string;\n /** Sent as `metadata`, as the widget sends it with this request too. */\n metadata?: Record<string, unknown>;\n /** Sent with the request; a function is called for it. */\n headers?: Record<string, string> | (() => Record<string, string> | Promise<Record<string, string>>);\n /** @default the global `fetch` */\n fetch?: typeof fetch;\n /** For a workflow on another origin that authenticates by cookie. */\n credentials?: RequestCredentials;\n /** @default 'sessionId', the Chat Trigger's */\n chatSessionKey?: string;\n /** Cancels the request. */\n signal?: AbortSignal;\n}\n\ninterface StoredMessage {\n id?: unknown;\n type?: unknown;\n kwargs?: { content?: unknown };\n}\n\n/** LangChain's message class, last segment of its `id` path (`['langchain_core','messages','AIMessage']`). */\nfunction classOf(message: StoredMessage): string {\n const path = Array.isArray(message.id) ? message.id : [];\n const last = path[path.length - 1];\n return typeof last === 'string' ? last : typeof message.type === 'string' ? message.type : '';\n}\n\n/** Text content, or the text parts of a content list; a turn that only called a tool has none. */\nfunction textOf(content: unknown): string {\n if (typeof content === 'string') return content;\n if (!Array.isArray(content)) return '';\n return content\n .map((part) => (typeof part === 'string' ? part : typeof part === 'object' && part !== null && (part as { type?: unknown }).type === 'text' ? String((part as { text?: unknown }).text ?? '') : ''))\n .filter(Boolean)\n .join('\\n\\n');\n}\n\n/**\n * The messages a chat shows, from what the memory stored: what the person wrote and what\n * the agent answered. A tool's call and its result are left out, as the live stream\n * leaves them out — where n8n's widget drew them as bubbles of raw JSON after a reload.\n */\nexport function n8nHistoryToMessages(stored: unknown[], { now = Date.now() } = {}): AparteMessage[] {\n const messages: AparteMessage[] = [];\n stored.forEach((entry, index) => {\n if (typeof entry !== 'object' || entry === null) return;\n const message = entry as StoredMessage;\n const kind = classOf(message);\n const role = /^Human/.test(kind) || kind === 'human' ? 'user' : /^AI/.test(kind) || kind === 'ai' ? 'assistant' : undefined;\n if (!role) return;\n const content = textOf(message.kwargs?.content);\n if (!content.trim()) return;\n // Ordered, in the past: the memory keeps no time, and a chat sorts nothing by it.\n messages.push({ id: `n8n-${index}`, role, content, timestamp: now - (stored.length - index), status: 'completed' });\n });\n return messages;\n}\n\n/**\n * Read a session's earlier messages from the workflow's memory — for a page that keeps\n * the session id across reloads and shows the conversation again. Needs the Chat\n * Trigger's *Load Previous Session* set to *From Memory*; when it is off, n8n answers\n * with no messages.\n */\nexport async function loadN8nHistory(options: N8nHistoryOptions): Promise<AparteMessage[]> {\n const doFetch = options.fetch ?? fetch;\n const headers = typeof options.headers === 'function' ? await options.headers() : options.headers ?? {};\n const sent: Record<string, string> = { 'Content-Type': 'application/json' };\n for (const [name, value] of Object.entries(headers)) if (name.toLowerCase() !== 'content-type') sent[name] = value;\n const response = await doFetch(options.webhookUrl, {\n method: 'POST',\n headers: sent,\n body: JSON.stringify({\n action: 'loadPreviousSession',\n [options.chatSessionKey ?? 'sessionId']: options.sessionId,\n ...(options.metadata ? { metadata: options.metadata } : {}),\n }),\n ...(options.signal ? { signal: options.signal } : {}),\n ...(options.credentials ? { credentials: options.credentials } : {}),\n }).catch((error: unknown) => { throw unreachable(error, options.webhookUrl); });\n if (!response.ok) throw await httpError(response, 'history');\n let body: unknown;\n try {\n body = await response.json();\n } catch {\n throw new AparteError('n8n answered the history request with something that is not JSON. Check that the URL is the Chat Trigger\\'s production URL.', AparteErrorCode.PROVIDER_MISCONFIGURED);\n }\n const data = typeof body === 'object' && body !== null ? (body as { data?: unknown }).data : undefined;\n return n8nHistoryToMessages(Array.isArray(data) ? data : []);\n}\n"],"names":["message","input","index","result","textOf","entry"],"mappings":"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"}
1
+ {"version":3,"file":"index.js","sources":["../src/wire.ts","../src/http.ts","../src/socket.ts","../src/buttons.ts","../src/runner.ts","../src/history.ts"],"sourcesContent":["/**\n * The body of n8n's answer to a chat message, read as it arrives.\n *\n * Two shapes come back, both labelled `application/json` by n8n:\n * - **streamed** — a Chat Trigger (or Webhook node) whose Response Mode is *Streaming*\n * sends newline-delimited JSON frames, `begin` / `item` / `end` per node that streams,\n * `error` when the workflow fails, and `keepalive` every 30 seconds while nothing else\n * comes — so before the first `begin` when a slow node runs first;\n * - **whole** — any other Response Mode sends one value once the workflow ends: the last\n * node's item (`{ output }` for an AI Agent), or what a *Respond to Webhook* node sent.\n *\n * Which one it is shows on the first line: a frame has a known `type`. Read off\n * `@n8n/chat` 1.43.3, the widget n8n embeds, and recorded against n8n 2.35.7 and 2.44.6.\n */\n\n/** One thing an answer carries, in order. */\nexport type N8nReplyPart =\n /** A node starts streaming. `stream` names it (node, run, item). */\n | { kind: 'begin'; stream: string }\n /** A piece of text. `stream` is absent for a line that was not a frame. */\n | { kind: 'item'; stream?: string; text: string }\n | { kind: 'end'; stream: string }\n /** The workflow failed; its message. */\n | { kind: 'error'; message: string }\n /** A frame type this reader does not know (a newer n8n). */\n | { kind: 'unknown'; type: string }\n /** The whole answer of a workflow that does not stream. */\n | { kind: 'reply'; text: string }\n /** A whole answer that is JSON with none of the fields a chat reads; its field names. */\n | { kind: 'unreadable'; fields: string[] }\n /**\n * The workflow answers later, through response nodes over n8n's chat WebSocket: the\n * execution to listen to, and the token n8n gives with it.\n */\n | { kind: 'deferred'; executionId?: string; resumeToken?: string }\n /** An HTML page instead of the answer: a proxy gave up waiting. */\n | { kind: 'html' }\n /** The stream stopped in the middle of a frame. */\n | { kind: 'cut' };\n\n/**\n * Whether the first line opens a stream. A whole answer can be any JSON — a Respond to\n * Webhook node may send `{ type: 'error', message }` or `{ type: 'item', output }` — so a\n * frame has to show it is one: n8n stamps every node frame with `metadata.nodeId`, its\n * `error` frame carries the text in `content`, and `keepalive` is n8n's alone.\n */\nfunction opensStream(value: unknown): boolean {\n if (!isFrame(value)) return false;\n const metadata = value.metadata;\n const fromNode = typeof metadata === 'object' && metadata !== null && typeof metadata['nodeId'] === 'string';\n if (value.type === 'keepalive') return true;\n if (value.type === 'error') return fromNode || typeof value.content === 'string';\n return fromNode;\n}\n\n/**\n * A proxy's error page: text that STARTS as HTML. An answer that merely contains markup —\n * an agent asked for a snippet — is not one, which is why this is a prefix test.\n */\nfunction isHtml(text: string): boolean {\n return /^\\s*<(?:!doctype|html|head|body)[\\s>]/i.test(text);\n}\n\nfunction parse(line: string): unknown {\n try { return JSON.parse(line); } catch { return undefined; }\n}\n\nfunction isFrame(value: unknown): value is { type: string; content?: unknown; metadata?: Record<string, unknown> } {\n return typeof value === 'object' && value !== null && !Array.isArray(value) && typeof (value as { type?: unknown }).type === 'string';\n}\n\n/** Which node's stream a frame belongs to: n8n streams one per node, run and item. */\nfunction streamOf(frame: { metadata?: Record<string, unknown> }): string {\n const m = frame.metadata ?? {};\n return `${String(m['nodeId'] ?? '')}:${String(m['runIndex'] ?? 0)}:${String(m['itemIndex'] ?? 0)}`;\n}\n\nfunction frameToPart(frame: { type: string; content?: unknown; metadata?: Record<string, unknown> }): N8nReplyPart | undefined {\n switch (frame.type) {\n case 'begin': return { kind: 'begin', stream: streamOf(frame) };\n case 'item': return { kind: 'item', stream: streamOf(frame), text: typeof frame.content === 'string' ? frame.content : '' };\n case 'end': return { kind: 'end', stream: streamOf(frame) };\n case 'error': return { kind: 'error', message: typeof frame.content === 'string' ? frame.content : '' };\n // n8n's heartbeat: it says the workflow is still running, nothing to draw.\n case 'keepalive': return undefined;\n default: return { kind: 'unknown', type: frame.type };\n }\n}\n\n/**\n * The text of a whole answer, the way n8n's widget reads it: `output`, else `text`, else\n * `message`; a `{ type: 'text', text }` part as its text. `undefined` when the value has\n * none of them — where the widget printed the JSON into the bubble, the most asked-about\n * failure on n8n's forum, this lets the runner say which field is missing instead.\n */\nexport function replyText(value: unknown): string | undefined {\n if (typeof value === 'string') return value;\n if (Array.isArray(value)) {\n const texts = value.map(replyText);\n return texts.every((t) => t === undefined) && value.length > 0 ? undefined : texts.filter((t) => t !== undefined).join('\\n\\n');\n }\n if (typeof value !== 'object' || value === null) return value === null || value === undefined ? '' : String(value);\n const record = value as Record<string, unknown>;\n const key = ['output', 'text', 'message'].find((k) => record[k] !== undefined && record[k] !== null);\n if (key === undefined) return Object.keys(record).length > 0 ? undefined : '';\n const text = record[key];\n if (typeof text === 'string') return text;\n if (typeof text === 'object' && text !== null && (text as { type?: unknown }).type === 'text' && typeof (text as { text?: unknown }).text === 'string') {\n return (text as { text: string }).text;\n }\n if (typeof text === 'number' || typeof text === 'boolean') return String(text);\n // An agent with an output parser answers with a structure: shown as such, readable.\n return ['```json', JSON.stringify(text, null, 2), '```'].join('\\n');\n}\n\n/**\n * The field names a whole answer carried, to name them in the error: the first item's, for\n * a list. Called only for a value `replyText` could not read, which always holds a record.\n */\nfunction fieldsOf(value: unknown): string[] {\n let record = value;\n while (Array.isArray(record)) record = record[0];\n return Object.keys(record as object).slice(0, 6);\n}\n\nfunction wholePart(raw: string): N8nReplyPart | undefined {\n const text = raw.trim();\n if (!text) return undefined;\n // Parsed first: a JSON answer is an answer, whatever markup its text holds.\n const value = parse(text);\n if (value === undefined) return isHtml(text) ? { kind: 'html' } : { kind: 'reply', text: raw };\n if (typeof value === 'object' && value !== null && (value as { executionStarted?: unknown }).executionStarted === true) {\n const { executionId, resumeToken } = value as { executionId?: unknown; resumeToken?: unknown };\n return {\n kind: 'deferred',\n ...(typeof executionId === 'string' || typeof executionId === 'number' ? { executionId: String(executionId) } : {}),\n ...(typeof resumeToken === 'string' ? { resumeToken } : {}),\n };\n }\n const reply = replyText(value);\n return reply === undefined ? { kind: 'unreadable', fields: fieldsOf(value) } : { kind: 'reply', text: reply };\n}\n\n/** Yields what n8n's answer carries, frame by frame as it streams, or once for a whole answer. */\nexport async function* readN8nReply(body: ReadableStream<Uint8Array>): AsyncGenerator<N8nReplyPart> {\n const reader = body.getReader();\n const decoder = new TextDecoder();\n // Widened on purpose: `lines` below decides it, a closure TypeScript's narrowing does not follow.\n let mode = 'unknown' as 'unknown' | 'frames' | 'whole';\n let raw = '';\n let pending = '';\n\n function* lines(text: string, last: boolean): Generator<N8nReplyPart> {\n const all = text.split('\\n');\n for (const [i, line] of all.entries()) {\n if (!line.trim()) continue;\n const value = parse(line);\n if (mode === 'unknown') {\n if (!opensStream(value)) { mode = 'whole'; return; }\n mode = 'frames';\n }\n if (isFrame(value)) {\n const part = frameToPart(value);\n if (part) yield part;\n } else if (last && i === all.length - 1 && line.trimStart().startsWith('{')) {\n // The body ended inside a frame: what came before it stands, the fragment does not.\n yield { kind: 'cut' };\n } else if (isHtml(line)) {\n yield { kind: 'html' };\n } else {\n // A line that is not a frame is text, as the widget reads it.\n yield { kind: 'item', text: line };\n }\n }\n }\n\n try {\n for (;;) {\n const { done, value } = await reader.read();\n const chunk = done ? decoder.decode() : decoder.decode(value, { stream: true });\n raw += chunk;\n if (mode !== 'whole') {\n pending += chunk;\n const cut = done ? pending.length : pending.lastIndexOf('\\n') + 1;\n const complete = pending.slice(0, cut);\n pending = pending.slice(cut);\n yield* lines(complete, done);\n }\n if (done) break;\n }\n } finally {\n reader.releaseLock();\n }\n if (mode !== 'frames') {\n const part = wholePart(raw);\n if (part) yield part;\n }\n}\n","/**\n * An HTTP error from n8n, said so the developer knows what to change in the workflow.\n *\n * n8n answers an error as JSON `{ message, hint }` (a 404 for a workflow that is not\n * active carries the hint to activate it), and a proxy in front of it answers as an HTML\n * page — a 502 or a 504 when the workflow ran longer than the proxy waits.\n */\nimport { AparteError, AparteErrorCode } from '@aparte/core';\n\nconst PROXY_STATUS = new Set([502, 504, 524]);\n\n/** What the request was for, said in the messages that depend on it. */\nexport type N8nRequestKind = 'answer' | 'history';\n\nfunction credentialNames(body: { credentials?: unknown }): string {\n const list = Array.isArray(body.credentials) ? body.credentials : [];\n const names = list\n .map((c) => (typeof c === 'object' && c !== null ? (c as { credentialName?: unknown }).credentialName : undefined))\n .filter((name): name is string => typeof name === 'string' && name.length > 0);\n return names.length > 0 ? ` (${names.join(', ')})` : '';\n}\n\nexport async function httpError(response: Response, kind: N8nRequestKind): Promise<AparteError> {\n const status = response.status;\n const text = (await response.text().catch(() => '')).trim();\n // A 404 from a Chat Trigger is a workflow that is not published or a wrong URL: n8n is\n // set up so it cannot answer, which no retry mends.\n const code = AparteError.codeForStatus(status) ?? (status === 404 ? AparteErrorCode.PROVIDER_MISCONFIGURED : AparteErrorCode.PROVIDER_ERROR);\n const make = (message: string, errorCode: AparteErrorCode = code) => new AparteError(message, errorCode, undefined, undefined, status);\n const misconfigured = AparteErrorCode.PROVIDER_MISCONFIGURED;\n\n // A page, not an answer: never show its markup to the person chatting.\n if (text.startsWith('<') || (response.headers.get('content-type') ?? '').includes('text/html')) {\n return PROXY_STATUS.has(status)\n ? make(`A proxy in front of n8n gave up waiting (HTTP ${status}). The workflow may still be running — its execution list shows the result.`, AparteErrorCode.NET_TIMEOUT)\n // A status that says something keeps it (a 429, a 503, a refused key); an unmapped\n // one, a 404 above all, is a wrong URL: set up so it cannot answer.\n : make(`n8n answered HTTP ${status} with a web page instead of a chat answer. Check that the URL is the Chat Trigger's production URL, the one that ends in /chat.`, AparteError.codeForStatus(status) ?? misconfigured);\n }\n\n let body: { message?: unknown; hint?: unknown; status?: unknown; credentials?: unknown } = {};\n try { body = JSON.parse(text) as typeof body; } catch { /* not JSON: its text below */ }\n\n // The Chat Trigger checks the workflow's credentials before it runs it.\n if (status === 428 || body.status === 'credential_connections_required') {\n return make(`The n8n workflow cannot run until its credentials are connected${credentialNames(body)}. Connect them in n8n, then try again.`, misconfigured);\n }\n if (body.status === 'credential_readiness_check_failed') {\n return make('n8n could not check the workflow\\'s credentials. Open the workflow in n8n to see which one fails.');\n }\n // Loading a session's history from memory needs the memory on the trigger too.\n if (kind === 'history' && status === 500) {\n return make('n8n could not load the conversation\\'s history. When the Chat Trigger\\'s \"Load Previous Session\" '\n + 'is \"From Memory\", connect the memory node to the Chat Trigger as well as to the agent.', misconfigured);\n }\n\n let message = typeof body.message === 'string' ? body.message : text;\n if (typeof body.message === 'string' && typeof body.hint === 'string') message = `${body.message} ${body.hint}`;\n return make(message.slice(0, 400) || `The n8n workflow answered HTTP ${status}.`);\n}\n\n/**\n * The request never reached an answer: n8n is down, the URL is wrong, or the browser\n * refused the answer because the Chat Trigger does not allow this page's origin — which\n * the browser reports exactly like a network failure, so all three are named.\n */\nexport function unreachable(error: unknown, url: string): unknown {\n if (!(error instanceof TypeError)) return error;\n const origin = typeof location === 'undefined' ? \"this page's origin\" : location.origin;\n const offline = typeof navigator !== 'undefined' && navigator.onLine === false;\n return new AparteError(\n `Could not reach n8n at ${url}. Check that it is the Chat Trigger's production URL, that the workflow is published, `\n + `and that ${origin} is in the trigger's Allowed Origins (CORS).`,\n offline ? AparteErrorCode.NET_OFFLINE : AparteErrorCode.NET_ERROR,\n );\n}\n","/**\n * n8n's chat WebSocket — how a workflow that answers through response nodes (Chat nodes,\n * \"Using Response Nodes\" on the Chat Trigger) talks to the page.\n *\n * Recorded against n8n 2.44.7 rather than read from documentation, which has none: the\n * message that starts the execution is answered `{ executionStarted, executionId,\n * resumeToken }`, the page opens `/chat?sessionId&executionId&isPublic=true&token`, and the\n * socket then carries, in this order for a Chat node that sends and then one that asks:\n *\n * \"n8n|heartbeat\" answered \"n8n|heartbeat-ack\"\n * \"Hello! You said: hi\" a message, as plain text\n * \"n8n|continue\" the execution goes on: more is coming\n * {\"type\":\"with-buttons\", text, buttons:[{ text, link, type }], blockUserInput}\n *\n * A button's link resumes the execution when fetched (a GET), and the next messages come on\n * the same socket. A Chat node that waits for a written reply is answered on the socket\n * too, as JSON. n8n 3.0 moves the bare `n8n|…` strings to JSON (`{ type: 'heartbeat' }`,\n * `{ type: 'continue' }`, `{ type: 'message', text }`); both are read.\n */\n\n/** A button of a Chat node that waits for an approval. */\nexport interface N8nChatButton {\n text: string;\n /** The URL that resumes the waiting execution with this answer. */\n link: string;\n /** n8n's style: `primary` or `secondary`. */\n type: string;\n}\n\nexport type N8nSocketFrame =\n | { kind: 'text'; text: string }\n | { kind: 'continue' }\n | { kind: 'buttons'; text: string; buttons: N8nChatButton[]; blockUserInput: boolean }\n | { kind: 'error'; message: string }\n | { kind: 'closed' };\n\n/** The socket a deferred execution talks on, from the workflow's chat URL. */\nexport function chatSocketUrl(webhookUrl: string, sessionId: string, executionId: string, token?: string): string {\n const origin = new URL(webhookUrl).origin.replace(/^http/, 'ws');\n const params = new URLSearchParams({ sessionId, executionId, isPublic: 'true' });\n if (token) params.set('token', token);\n return `${origin}/chat?${params.toString()}`;\n}\n\n/** One frame as text, read the way n8n's own widget reads it. */\nexport function readSocketFrame(data: string): N8nSocketFrame | 'heartbeat' | 'heartbeat-json' {\n if (data === 'n8n|heartbeat') return 'heartbeat';\n if (data === 'n8n|continue') return { kind: 'continue' };\n let value: unknown;\n try { value = JSON.parse(data); } catch { return { kind: 'text', text: data }; }\n if (typeof value !== 'object' || value === null) return { kind: 'text', text: data };\n const frame = value as { type?: unknown; text?: unknown; message?: unknown; buttons?: unknown; blockUserInput?: unknown };\n switch (frame.type) {\n case 'heartbeat': return 'heartbeat-json';\n case 'continue': return { kind: 'continue' };\n case 'message': return { kind: 'text', text: typeof frame.text === 'string' ? frame.text : '' };\n case 'error': return { kind: 'error', message: typeof frame.message === 'string' ? frame.message : '' };\n case 'with-buttons': {\n const buttons = (Array.isArray(frame.buttons) ? frame.buttons : [])\n .filter((b): b is N8nChatButton => typeof b === 'object' && b !== null\n && typeof (b as N8nChatButton).text === 'string' && typeof (b as N8nChatButton).link === 'string')\n .map((b) => ({ text: b.text, link: b.link, type: typeof b.type === 'string' ? b.type : 'primary' }));\n return { kind: 'buttons', text: typeof frame.text === 'string' ? frame.text : '', buttons, blockUserInput: frame.blockUserInput === true };\n }\n default: return { kind: 'text', text: data };\n }\n}\n\n/** The constructor the runner opens sockets with: the browser's, or one a test gives. */\nexport type N8nWebSocketLike = new (url: string) => {\n readonly readyState: number;\n send(data: string): void;\n close(): void;\n onopen: ((event: unknown) => void) | null;\n onmessage: ((event: { data: unknown }) => void) | null;\n onclose: ((event: unknown) => void) | null;\n onerror: ((event: unknown) => void) | null;\n};\n\nconst OPEN = 1;\n\n/**\n * One execution's socket: frames queue until a turn reads them, so a message that arrives\n * between two turns — after a button, before the page resumed — is not lost.\n */\nexport class N8nChatSocket {\n closed = false;\n private readonly socket: InstanceType<N8nWebSocketLike>;\n private readonly queue: N8nSocketFrame[] = [];\n private waiter: ((frame: N8nSocketFrame) => void) | undefined;\n private readonly opened: Promise<void>;\n\n constructor(url: string, Socket: N8nWebSocketLike) {\n this.socket = new Socket(url);\n this.opened = new Promise((resolve) => {\n this.socket.onopen = () => resolve();\n });\n this.socket.onmessage = (event) => {\n const read = readSocketFrame(String(event.data));\n if (read === 'heartbeat') this.socket.send('n8n|heartbeat-ack');\n else if (read === 'heartbeat-json') this.socket.send(JSON.stringify({ type: 'heartbeat-ack' }));\n else this.push(read);\n };\n // An error is followed by a close, which is the one that says it is over.\n this.socket.onerror = () => undefined;\n this.socket.onclose = () => {\n this.closed = true;\n this.push({ kind: 'closed' });\n };\n }\n\n private push(frame: N8nSocketFrame): void {\n const waiter = this.waiter;\n this.waiter = undefined;\n if (waiter) waiter(frame);\n else this.queue.push(frame);\n }\n\n /** Put a frame back in front, read too early. */\n unshift(frame: N8nSocketFrame): void {\n this.queue.unshift(frame);\n }\n\n /**\n * The next frame, waited for; `'timeout'` when `timeoutMs` passes first, `undefined`\n * when the signal fires. A closed socket answers `closed` from then on.\n */\n next(signal: AbortSignal, timeoutMs?: number): Promise<N8nSocketFrame | 'timeout' | undefined> {\n const queued = this.queue.shift();\n if (queued) return Promise.resolve(queued);\n if (this.closed) return Promise.resolve({ kind: 'closed' });\n if (signal.aborted) return Promise.resolve(undefined);\n return new Promise((resolve) => {\n let timer: ReturnType<typeof setTimeout> | undefined;\n const done = (value: N8nSocketFrame | 'timeout' | undefined) => {\n if (timer !== undefined) clearTimeout(timer);\n signal.removeEventListener('abort', onAbort);\n if (this.waiter === settle) this.waiter = undefined;\n resolve(value);\n };\n const settle = (frame: N8nSocketFrame) => done(frame);\n const onAbort = () => done(undefined);\n this.waiter = settle;\n signal.addEventListener('abort', onAbort, { once: true });\n if (timeoutMs !== undefined) timer = setTimeout(() => done('timeout'), timeoutMs);\n });\n }\n\n /** Send a message on the socket, once it is open. */\n async send(message: Record<string, unknown>): Promise<void> {\n if (this.socket.readyState !== OPEN) await this.opened;\n this.socket.send(JSON.stringify(message));\n }\n\n close(): void {\n this.socket.close();\n }\n}\n","/**\n * A Chat node's buttons, answered in the page.\n *\n * The runner hands them over as a call the page answers — the shape a browser tool has\n * (`run-suspended`, reason `external`) — so the turn waits for the click the way it waits\n * for any answer only the page can give, and goes on when it comes. `setupN8nButtons`\n * registers what answers it: the buttons, asked at the composer with their own labels\n * through core's `requestUserInput`, and a transcript line that says which was chosen.\n */\nimport { aparteGlobalConfig, requestUserInput } from '@aparte/core';\nimport type { AparteConfig, AparteToolCallSegment } from '@aparte/core';\n\n/** The name the runner gives a Chat node's buttons, and `setupN8nButtons` answers. */\nexport const N8N_BUTTONS_TOOL = 'n8n_buttons';\n\n/** What the runner puts in the call: the question, and its buttons in n8n's order. */\nexport interface N8nButtonsInput {\n text: string;\n buttons: { text: string; type: string }[];\n /** n8n's \"Block User Input\": the answer has to be a button, with no words instead. */\n blockUserInput: boolean;\n}\n\n/**\n * What the person answered: a button, by its place and its label — or words of their own\n * (`written`, `index` -1), which n8n hears as a chat message: the Chat node resumes on it as\n * not approved, with the words as its input.\n */\nexport interface N8nButtonsResult {\n index: number;\n text: string;\n written?: true;\n}\n\n/**\n * Answer a Chat node's buttons in this page: they are asked at the composer, with their\n * labels, and the line in the transcript keeps the one chosen. Call it once, on the config\n * the chat renders with, when the workflow has Chat nodes that wait for an approval.\n *\n * @example\n * setupN8nButtons();\n * new AparteClient({ streamRunner: createN8nRunner({ webhookUrl }) }).start();\n */\nexport function setupN8nButtons(config: AparteConfig = aparteGlobalConfig): void {\n config.registerTool({\n name: N8N_BUTTONS_TOOL,\n description: 'The buttons of an n8n Chat node that waits for an answer. Answered by the person in the page.',\n inputSchema: { type: 'object', properties: {} },\n }, async (call, signal, context) => {\n const input = call.input as unknown as N8nButtonsInput;\n const answer = await requestUserInput({\n kind: 'approval',\n message: input.text,\n options: input.buttons.map((button, index) => ({\n value: String(index),\n label: button.text,\n tone: button.type === 'secondary' ? 'deny' : 'affirm',\n })),\n // n8n's \"Block User Input\": a button, or nothing — no words instead.\n allowOther: !input.blockUserInput,\n signal,\n ...(context?.target ? { target: context.target } : {}),\n });\n const content = answer.action === 'accept' ? (answer.content as { option?: string; instruction?: string } | undefined) : undefined;\n const written = content?.instruction?.trim();\n if (written && content?.option === undefined) {\n const result: N8nButtonsResult = { index: -1, text: written, written: true };\n return { toolCallId: call.id, content: written, structuredContent: result };\n }\n const index = content?.option === undefined ? -1 : Number(content.option);\n const chosen = input.buttons[index];\n if (!chosen) throw new Error('No button was chosen.');\n const result: N8nButtonsResult = { index, text: chosen.text };\n return { toolCallId: call.id, content: chosen.text, structuredContent: result };\n });\n // The line in the transcript is the answer, quietly: no tool name, no \"Done\", nothing to open.\n config.registerToolDisplay(N8N_BUTTONS_TOOL, {\n icon: 'messageSquare',\n label: (segment: AparteToolCallSegment) => (typeof segment.result === 'string' && segment.result ? segment.result : input(segment)),\n quiet: true,\n details: false,\n });\n}\n\n/** While the buttons wait: the question they answer. */\nfunction input(segment: AparteToolCallSegment): string {\n return (segment.toolCall?.input as Partial<N8nButtonsInput> | undefined)?.text ?? '';\n}\n","/**\n * createN8nRunner — an n8n chat workflow, drawn by aparté.\n *\n * The workflow's agent runs in n8n, with its model, its tools and its memory; n8n keeps\n * the conversation per session. So each turn sends what n8n's own widget sends — the\n * session, the message, the files — and the runner turns n8n's answer into the events\n * `AparteClient`'s adapter draws, the same ones a loop run in the page emits.\n */\nimport { AparteError, AparteErrorCode, uuid } from '@aparte/core';\nimport type { AparteChatMessage, AparteStreamRunEvent, AparteStreamRunner, AparteStreamRunOptions } from '@aparte/core';\nimport { readN8nReply } from './wire.js';\nimport { httpError, unreachable } from './http.js';\nimport { N8nChatSocket, chatSocketUrl } from './socket.js';\nimport type { N8nWebSocketLike } from './socket.js';\nimport { N8N_BUTTONS_TOOL } from './buttons.js';\nimport type { N8nButtonsInput, N8nButtonsResult } from './buttons.js';\n\ntype Suspended = Extract<AparteStreamRunEvent, { type: 'run-suspended' }>;\n\nexport interface N8nRunnerOptions {\n /**\n * The workflow's chat URL: the Chat Trigger's production URL, the one that ends in\n * `/chat` — or a Webhook node's URL, for a workflow that answers through it.\n */\n webhookUrl: string;\n /**\n * The n8n session a turn runs in. n8n keeps the agent's memory per session and the\n * page sends only the new message, so this is what ties a conversation to its memory.\n * Give the id your app keeps for the conversation — the one you loaded its history\n * with ({@link loadN8nHistory}), or a conversation manager's `activeId`. A function\n * is read every turn.\n * @default a new session for each new conversation: a turn with no reply before it\n * starts one, and the turns after it continue it\n */\n sessionId?: string | ((opts: AparteStreamRunOptions) => string);\n /** Sent with every message as `metadata`, which the workflow reads from the trigger's output. */\n metadata?: Record<string, unknown> | ((opts: AparteStreamRunOptions) => Record<string, unknown>);\n /** Sent with every request; a function is called per request (a fresh token each time). */\n headers?: Record<string, string> | (() => Record<string, string> | Promise<Record<string, string>>);\n /** @default the global `fetch` */\n fetch?: typeof fetch;\n /** For a workflow on another origin that authenticates by cookie. */\n credentials?: RequestCredentials;\n /**\n * The body field that carries the session, for a workflow that reads another one.\n * @default 'sessionId', the Chat Trigger's\n */\n chatSessionKey?: string;\n /**\n * The body field that carries the message, for a workflow that reads another one.\n * @default 'chatInput', the Chat Trigger's\n */\n chatInputKey?: string;\n /**\n * Told what the runner could not read in n8n's answer — a frame type it does not\n * know, a stream cut mid-frame. For the developer, not the person chatting.\n * @default `console.warn`\n */\n onWarning?: (message: string) => void;\n /**\n * What a workflow that answers through response nodes is read with: n8n's chat\n * WebSocket. Given for a runtime without a global one, or a test.\n * @default the global `WebSocket`\n */\n WebSocket?: N8nWebSocketLike;\n}\n\n/** What a paused turn hands the page, and gets back with the button chosen. */\ninterface Ticket {\n v: 1;\n sessionId: string;\n executionId: string;\n token?: string;\n toolCallId: string;\n /** The buttons' links, in the order the page was given the buttons. */\n links: string[];\n}\n\nfunction readTicket(token: string | undefined): Ticket | undefined {\n if (!token) return undefined;\n try {\n const ticket = JSON.parse(token) as Partial<Ticket>;\n return ticket.v === 1 && typeof ticket.sessionId === 'string' && typeof ticket.executionId === 'string'\n && typeof ticket.toolCallId === 'string' && Array.isArray(ticket.links) ? ticket as Ticket : undefined;\n } catch {\n return undefined;\n }\n}\n\n/**\n * How long a message is given to be followed by `continue`. n8n sends it in the same tick\n * as the message when the execution goes on (recorded on 2.44.7: both at +3172 ms); a\n * message with nothing behind it is one the execution waits after, for the person.\n */\nconst CONTINUE_GRACE_MS = 250;\n\n/** A file as n8n's widget sends it on the socket: a data URL. */\nasync function dataUrl(file: { blob: Blob; name: string }): Promise<{ name: string; type: string; data: string }> {\n const bytes = new Uint8Array(await file.blob.arrayBuffer());\n let binary = '';\n for (let i = 0; i < bytes.length; i++) binary += String.fromCharCode(bytes[i]!);\n const type = file.blob.type || 'application/octet-stream';\n return { name: file.name, type, data: `data:${type};base64,${btoa(binary)}` };\n}\n\n/** The workflow streamed nothing: the widget's own diagnosis, said to the developer. */\nconst EMPTY_STREAM = 'The n8n workflow streamed nothing. When its Chat Trigger streams, the node that answers '\n + 'has to stream too: turn on streaming in the AI Agent\\'s options, or set the trigger\\'s Response Mode to '\n + '\"When Last Node Finishes\".';\n\nconst EMPTY_WHOLE = 'The n8n workflow finished without an answer: its last node returned no text. End the '\n + 'workflow on the node that answers — an AI Agent, or a node that returns an `output` field.';\n\nconst PROXY_TIMEOUT = 'A proxy timed out waiting for n8n\\'s answer. The workflow may still be running — '\n + 'its execution list shows the result.';\n\nconst DEFERRED = 'This n8n workflow answers through response nodes, over n8n\\'s chat WebSocket, and n8n did not say '\n + 'which execution to listen to. Check the Chat Trigger\\'s Response Mode.';\n\nconst NO_WEBSOCKET = 'This n8n workflow answers through response nodes, over n8n\\'s chat WebSocket, and this runtime has '\n + 'no WebSocket. Pass one as the runner\\'s `WebSocket` option.';\n\nconst unreadable = (fields: string[]) => `The n8n workflow answered with the fields ${fields.map((f) => `\"${f}\"`).join(', ')} `\n + 'but no \"output\", \"text\" or \"message\" field, which is where a chat reads the answer. End the workflow on an AI Agent, '\n + 'or add a Set node that puts the reply in \"output\".';\n\n/** The message the turn answers: the last one the person wrote. */\nfunction lastUserIndex(messages: AparteChatMessage[]): number {\n for (let i = messages.length - 1; i >= 0; i--) if (messages[i]?.role === 'user') return i;\n return -1;\n}\n\n/** The text of a message, its parts kept apart: a file inlined after the words starts on a line of its own. */\nfunction textOf(content: AparteChatMessage['content'] | undefined): string {\n if (content === undefined) return '';\n if (typeof content === 'string') return content;\n return content.flatMap((part) => (part.type === 'text' && part.text ? [part.text] : [])).join('\\n\\n');\n}\n\nconst EXTENSIONS: Record<string, string> = { 'image/png': 'png', 'image/jpeg': 'jpg', 'image/gif': 'gif', 'image/webp': 'webp', 'image/svg+xml': 'svg' };\n\n/**\n * An image part — a data URL — as the file the Chat Trigger receives. A `Blob` and a name\n * rather than a `File`, which Node only has from 20 and this package runs from 18.\n */\nfunction imageFile(image: string, mimeType: string | undefined, index: number): { blob: Blob; name: string } | undefined {\n const match = /^data:([^;,]+)?((?:;[^;,]*)*?);base64,(.*)$/s.exec(image);\n if (!match) return undefined;\n const type = mimeType ?? match[1] ?? 'application/octet-stream';\n const binary = atob(match[3] ?? '');\n const bytes = new Uint8Array(binary.length);\n for (let i = 0; i < binary.length; i++) bytes[i] = binary.charCodeAt(i);\n return { blob: new Blob([bytes], { type }), name: `image-${index + 1}.${EXTENSIONS[type] ?? 'bin'}` };\n}\n\n/**\n * What the Chat Trigger receives as `files`: the files the person picked, whatever their\n * type, when the client hands them over (`files` on a send) — else the images inlined in\n * the message, for a caller that only has those.\n */\nfunction filesOf(opts: AparteStreamRunOptions, message: AparteChatMessage | undefined): { blob: Blob; name: string }[] {\n if (opts.files && opts.files.length > 0) return opts.files.map((file) => ({ blob: file, name: file.name }));\n return (Array.isArray(message?.content) ? message.content : [])\n .flatMap((part) => (part.type === 'image' ? [part] : []))\n .map((part, i) => imageFile(part.image, part.mimeType, i))\n .filter((file) => file !== undefined);\n}\n\n/**\n * Put aparté on an n8n chat workflow: pass the result as `AparteClient`'s `streamRunner`.\n * n8n runs the agent, its tools and its memory; the page sends the person's message and\n * draws the answer, streamed when the Chat Trigger streams.\n */\nexport function createN8nRunner(options: N8nRunnerOptions): AparteStreamRunner {\n const warn = options.onWarning ?? ((message: string) => console.warn(`[aparte] n8n: ${message}`));\n const sessionKey = options.chatSessionKey ?? 'sessionId';\n const inputKey = options.chatInputKey ?? 'chatInput';\n let ownSession = `aparte-${uuid()}`;\n // One socket per session while its execution runs: the turns after it — a written\n // reply to a Chat node that waits for one, a button's answer — go on it.\n const sockets = new Map<string, { socket: N8nChatSocket; executionId: string; token?: string }>();\n\n const openSocket = (sessionId: string, executionId: string, token?: string) => {\n const Socket = options.WebSocket ?? (globalThis as { WebSocket?: N8nWebSocketLike }).WebSocket;\n if (!Socket) throw new AparteError(NO_WEBSOCKET, AparteErrorCode.PROVIDER_MISCONFIGURED);\n sockets.get(sessionId)?.socket.close();\n const entry = { socket: new N8nChatSocket(chatSocketUrl(options.webhookUrl, sessionId, executionId, token), Socket), executionId, ...(token ? { token } : {}) };\n sockets.set(sessionId, entry);\n return entry;\n };\n\n /**\n * Read the execution's socket for this turn: each message a block of its own, until the\n * execution stops to wait — for a written reply, or on buttons, which pause the turn for\n * the page — or ends.\n */\n const listen = async (opts: AparteStreamRunOptions, sessionId: string): Promise<void> => {\n const emit = opts.emitter;\n const entry = sockets.get(sessionId)!;\n const { socket } = entry;\n let drawn = false;\n const finish = () => {\n if (drawn) emit({ type: 'text-flush' });\n emit({ type: 'run-done' });\n };\n for (;;) {\n const frame = await socket.next(opts.signal);\n if (frame === undefined) return;\n if (frame === 'timeout' || frame.kind === 'continue') continue;\n switch (frame.kind) {\n case 'text': {\n if (drawn) emit({ type: 'text-flush' });\n if (frame.text) {\n emit({ type: 'text-delta', delta: frame.text });\n drawn = true;\n }\n const after = await socket.next(opts.signal, CONTINUE_GRACE_MS);\n if (after === undefined) return;\n if (after === 'timeout') return finish();\n if (after.kind !== 'continue') socket.unshift(after);\n break;\n }\n case 'buttons': {\n if (drawn) emit({ type: 'text-flush' });\n if (frame.text) {\n emit({ type: 'text-delta', delta: frame.text });\n emit({ type: 'text-flush' });\n }\n // As n8n's widget: only a button that leads back to this n8n is shown —\n // a link elsewhere would have the page fetch whatever a workflow wrote.\n const origin = new URL(options.webhookUrl).origin;\n const buttons = frame.buttons.filter((b) => {\n try { return new URL(b.link, options.webhookUrl).origin === origin; } catch { return false; }\n });\n if (buttons.length === 0) {\n warn('An n8n Chat node sent buttons whose links lead off this n8n instance; they were not shown.');\n emit({ type: 'run-done' });\n return;\n }\n const toolCallId = `n8n-buttons-${uuid()}`;\n const input: N8nButtonsInput = {\n text: frame.text,\n buttons: buttons.map((b) => ({ text: b.text, type: b.type })),\n blockUserInput: frame.blockUserInput,\n };\n const ticket: Ticket = {\n v: 1, sessionId, executionId: entry.executionId, ...(entry.token ? { token: entry.token } : {}),\n toolCallId, links: buttons.map((b) => b.link),\n };\n emit({ type: 'tool-start', toolCallId, name: N8N_BUTTONS_TOOL, input });\n emit({\n type: 'run-suspended',\n pending: [{ toolCallId, name: N8N_BUTTONS_TOOL, input, reason: 'external' }],\n ticket: JSON.stringify(ticket),\n } as Suspended);\n return;\n }\n case 'error':\n if (drawn) emit({ type: 'text-flush' });\n throw new AparteError(frame.message || 'The n8n workflow failed.', AparteErrorCode.PROVIDER_ERROR);\n case 'closed':\n // The execution ended: the next message starts a new one.\n sockets.delete(sessionId);\n return finish();\n }\n }\n };\n\n /** The page answered a Chat node's buttons: n8n hears it through the button's link. */\n const resume = async (opts: AparteStreamRunOptions, ticket: Ticket, doFetch: typeof fetch): Promise<void> => {\n const emit = opts.emitter;\n emit({ type: 'run-start' });\n emit({ type: 'turn-start' });\n const outcome = opts.deferral?.outcomes?.[ticket.toolCallId];\n const chosen = outcome?.kind === 'result' ? (outcome.structuredContent as N8nButtonsResult | undefined) : undefined;\n const link = chosen ? ticket.links[chosen.index] : undefined;\n // Words of the person's own instead of a button: n8n hears them as a chat message,\n // which the waiting Chat node resumes on as not approved.\n if (outcome?.kind === 'result' && chosen?.written) {\n emit({ type: 'tool-resolved', toolCallId: ticket.toolCallId, result: outcome.content, structuredResult: chosen });\n const entry = sockets.get(ticket.sessionId) ?? openSocket(ticket.sessionId, ticket.executionId, ticket.token);\n await entry.socket.send({ sessionId: ticket.sessionId, action: 'sendMessage', chatInput: chosen.text, files: [] });\n await listen(opts, ticket.sessionId);\n return;\n }\n if (!outcome || outcome.kind !== 'result' || !link) {\n // No button: the execution keeps waiting in n8n, and a written message answers it.\n emit({ type: 'tool-aborted', toolCallId: ticket.toolCallId });\n emit({ type: 'run-done' });\n return;\n }\n emit({ type: 'tool-resolved', toolCallId: ticket.toolCallId, result: outcome.content, structuredResult: chosen });\n // The socket the buttons came on, or a new one on the same execution if it closed.\n const entry = sockets.get(ticket.sessionId);\n if (!entry || entry.socket.closed) openSocket(ticket.sessionId, ticket.executionId, ticket.token);\n const response = await doFetch(link, {\n signal: opts.signal,\n ...(options.credentials ? { credentials: options.credentials } : {}),\n }).catch((error: unknown) => { throw unreachable(error, link); });\n if (!response.ok) throw await httpError(response, 'answer');\n await listen(opts, ticket.sessionId);\n };\n\n return async (opts) => {\n const emit = opts.emitter;\n const doFetch = options.fetch ?? fetch;\n try {\n const ticket = readTicket(opts.deferral?.token);\n if (ticket) {\n await resume(opts, ticket, doFetch);\n return undefined;\n }\n const messages = (opts.baseRequest as { messages: AparteChatMessage[] }).messages;\n const last = lastUserIndex(messages);\n const message = last >= 0 ? messages[last] : undefined;\n // No reply before the message: a new conversation, or one that was cleared. n8n\n // must not answer it from the memory of the one before.\n if (options.sessionId === undefined && !messages.slice(0, Math.max(last, 0)).some((m) => m.role === 'assistant')) {\n ownSession = `aparte-${uuid()}`;\n }\n const headers = typeof options.headers === 'function' ? await options.headers() : options.headers ?? {};\n const sessionId = (typeof options.sessionId === 'function' ? options.sessionId(opts) : options.sessionId) ?? ownSession;\n const metadata = typeof options.metadata === 'function' ? options.metadata(opts) : options.metadata;\n const files = filesOf(opts, message);\n\n // An execution that waits for the person's written reply hears it on its socket.\n const execution = sockets.get(sessionId);\n if (execution && !execution.socket.closed) {\n emit({ type: 'run-start' });\n emit({ type: 'turn-start' });\n await execution.socket.send({\n sessionId,\n action: 'sendMessage',\n chatInput: textOf(message?.content),\n files: await Promise.all(files.map(dataUrl)),\n });\n await listen(opts, sessionId);\n return undefined;\n }\n\n // As n8n's widget sends it: JSON, or a form when the message carries files.\n const fields: Record<string, string | Record<string, unknown>> = {\n action: 'sendMessage',\n [sessionKey]: sessionId,\n [inputKey]: textOf(message?.content),\n ...(metadata ? { metadata } : {}),\n };\n let body: string | FormData;\n // The page's headers, without a Content-Type in any case: a form's boundary is\n // the browser's to write, and JSON gets exactly one, ours.\n const requestHeaders: Record<string, string> = { Accept: 'text/plain' };\n for (const [name, value] of Object.entries(headers)) if (name.toLowerCase() !== 'content-type') requestHeaders[name] = value;\n if (files.length > 0) {\n const form = new FormData();\n for (const [key, value] of Object.entries(fields)) form.append(key, typeof value === 'string' ? value : JSON.stringify(value));\n for (const file of files) form.append('files', file.blob, file.name);\n body = form;\n } else {\n body = JSON.stringify(fields);\n requestHeaders['Content-Type'] = 'application/json';\n }\n\n const response = await doFetch(options.webhookUrl, {\n method: 'POST',\n headers: requestHeaders,\n body,\n signal: opts.signal,\n ...(options.credentials ? { credentials: options.credentials } : {}),\n }).catch((error: unknown) => { throw unreachable(error, options.webhookUrl); });\n if (!response.ok) throw await httpError(response, 'answer');\n\n emit({ type: 'run-start' });\n emit({ type: 'turn-start' });\n\n // n8n streams once per node that answers. One is drawn at a time, in the order\n // they began: another node's text waits for the drawn one to end, and starts a\n // block of its own below it.\n let drawn = false;\n let whole = false;\n let textOpen = false;\n let deferred = false;\n let active: string | undefined;\n const waiting = new Map<string, { text: string[]; ended: boolean }>();\n const unknown = new Set<string>();\n const flush = () => {\n if (textOpen) emit({ type: 'text-flush' });\n textOpen = false;\n };\n const draw = (text: string) => {\n if (!text) return;\n drawn = true;\n textOpen = true;\n emit({ type: 'text-delta', delta: text });\n };\n const open = (stream: string) => {\n active = stream;\n flush();\n };\n const drain = () => {\n active = undefined;\n for (const [stream, queued] of waiting) {\n waiting.delete(stream);\n open(stream);\n for (const text of queued.text) draw(text);\n if (!queued.ended) return;\n active = undefined;\n }\n };\n const waitFor = (stream: string) => {\n let queued = waiting.get(stream);\n if (!queued) waiting.set(stream, queued = { text: [], ended: false });\n return queued;\n };\n\n if (!response.body) throw new AparteError(EMPTY_STREAM, AparteErrorCode.PROVIDER_MISCONFIGURED);\n for await (const part of readN8nReply(response.body)) {\n switch (part.kind) {\n case 'begin':\n if (active === undefined) open(part.stream);\n else if (part.stream !== active) waitFor(part.stream);\n break;\n case 'item':\n if (part.stream === undefined || part.stream === active) draw(part.text);\n else if (active === undefined) { open(part.stream); draw(part.text); }\n else waitFor(part.stream).text.push(part.text);\n break;\n case 'end':\n if (part.stream === active) drain();\n else waitFor(part.stream).ended = true;\n break;\n case 'error':\n flush();\n throw new AparteError(part.message || 'The n8n workflow failed.', AparteErrorCode.PROVIDER_ERROR);\n case 'reply':\n whole = true;\n draw(part.text);\n break;\n case 'unreadable':\n throw new AparteError(unreadable(part.fields), AparteErrorCode.PROVIDER_MISCONFIGURED);\n case 'deferred':\n // Response nodes: the answer comes on n8n's chat socket.\n if (!part.executionId) throw new AparteError(DEFERRED, AparteErrorCode.PROVIDER_MISCONFIGURED);\n openSocket(sessionId, part.executionId, part.resumeToken);\n deferred = true;\n break;\n case 'html':\n flush();\n throw new AparteError(PROXY_TIMEOUT, AparteErrorCode.NET_TIMEOUT);\n case 'cut':\n warn('n8n\\'s stream stopped in the middle of a frame; the text before it is kept.');\n break;\n case 'unknown':\n if (!unknown.has(part.type)) warn(`n8n sent a \"${part.type}\" frame, which this runner does not read; it was skipped.`);\n unknown.add(part.type);\n break;\n }\n }\n if (deferred) {\n await listen(opts, sessionId);\n return undefined;\n }\n // A stream that was cut before its `end`: what it sent is still the answer — all\n // of it, the streams still waiting included, ended or not.\n drain();\n for (const [stream, queued] of waiting) {\n waiting.delete(stream);\n open(stream);\n for (const text of queued.text) draw(text);\n }\n if (!drawn) throw new AparteError(whole ? EMPTY_WHOLE : EMPTY_STREAM, AparteErrorCode.PROVIDER_MISCONFIGURED);\n flush();\n emit({ type: 'run-done' });\n return undefined;\n } catch (error) {\n // Stopped by the person: not a failure, nothing to report.\n if (opts.signal.aborted) return undefined;\n throw error;\n }\n };\n}\n","/**\n * loadN8nHistory — a session's earlier messages, read back from the workflow's memory.\n *\n * n8n keeps the conversation, the page keeps nothing: after a reload, the Chat Trigger\n * answers `loadPreviousSession` with the session's messages as its memory node stored\n * them (LangChain's serialised messages). This turns them into messages a chat shows.\n */\nimport { AparteError, AparteErrorCode } from '@aparte/core';\nimport type { AparteMessage } from '@aparte/core';\nimport { httpError, unreachable } from './http.js';\n\nexport interface N8nHistoryOptions {\n /** The Chat Trigger's production URL, the one the runner sends to. */\n webhookUrl: string;\n /** The session to read: the one the runner sends the conversation's turns in. */\n sessionId: string;\n /** Sent as `metadata`, as the widget sends it with this request too. */\n metadata?: Record<string, unknown>;\n /** Sent with the request; a function is called for it. */\n headers?: Record<string, string> | (() => Record<string, string> | Promise<Record<string, string>>);\n /** @default the global `fetch` */\n fetch?: typeof fetch;\n /** For a workflow on another origin that authenticates by cookie. */\n credentials?: RequestCredentials;\n /** @default 'sessionId', the Chat Trigger's */\n chatSessionKey?: string;\n /** Cancels the request. */\n signal?: AbortSignal;\n}\n\ninterface StoredMessage {\n id?: unknown;\n type?: unknown;\n kwargs?: { content?: unknown };\n}\n\n/** LangChain's message class, last segment of its `id` path (`['langchain_core','messages','AIMessage']`). */\nfunction classOf(message: StoredMessage): string {\n const path = Array.isArray(message.id) ? message.id : [];\n const last = path[path.length - 1];\n return typeof last === 'string' ? last : typeof message.type === 'string' ? message.type : '';\n}\n\n/** Text content, or the text parts of a content list; a turn that only called a tool has none. */\nfunction textOf(content: unknown): string {\n if (typeof content === 'string') return content;\n if (!Array.isArray(content)) return '';\n return content\n .map((part) => (typeof part === 'string' ? part : typeof part === 'object' && part !== null && (part as { type?: unknown }).type === 'text' ? String((part as { text?: unknown }).text ?? '') : ''))\n .filter(Boolean)\n .join('\\n\\n');\n}\n\n/**\n * The messages a chat shows, from what the memory stored: what the person wrote and what\n * the agent answered. A tool's call and its result are left out, as the live stream\n * leaves them out — where n8n's widget drew them as bubbles of raw JSON after a reload.\n */\nexport function n8nHistoryToMessages(stored: unknown[], { now = Date.now() } = {}): AparteMessage[] {\n const messages: AparteMessage[] = [];\n stored.forEach((entry, index) => {\n if (typeof entry !== 'object' || entry === null) return;\n const message = entry as StoredMessage;\n const kind = classOf(message);\n const role = /^Human/.test(kind) || kind === 'human' ? 'user' : /^AI/.test(kind) || kind === 'ai' ? 'assistant' : undefined;\n if (!role) return;\n const content = textOf(message.kwargs?.content);\n if (!content.trim()) return;\n // Ordered, in the past: the memory keeps no time, and a chat sorts nothing by it.\n messages.push({ id: `n8n-${index}`, role, content, timestamp: now - (stored.length - index), status: 'completed' });\n });\n return messages;\n}\n\n/**\n * Read a session's earlier messages from the workflow's memory — for a page that keeps\n * the session id across reloads and shows the conversation again. Needs the Chat\n * Trigger's *Load Previous Session* set to *From Memory*; when it is off, n8n answers\n * with no messages.\n */\nexport async function loadN8nHistory(options: N8nHistoryOptions): Promise<AparteMessage[]> {\n const doFetch = options.fetch ?? fetch;\n const headers = typeof options.headers === 'function' ? await options.headers() : options.headers ?? {};\n const sent: Record<string, string> = { 'Content-Type': 'application/json' };\n for (const [name, value] of Object.entries(headers)) if (name.toLowerCase() !== 'content-type') sent[name] = value;\n const response = await doFetch(options.webhookUrl, {\n method: 'POST',\n headers: sent,\n body: JSON.stringify({\n action: 'loadPreviousSession',\n [options.chatSessionKey ?? 'sessionId']: options.sessionId,\n ...(options.metadata ? { metadata: options.metadata } : {}),\n }),\n ...(options.signal ? { signal: options.signal } : {}),\n ...(options.credentials ? { credentials: options.credentials } : {}),\n }).catch((error: unknown) => { throw unreachable(error, options.webhookUrl); });\n if (!response.ok) throw await httpError(response, 'history');\n let body: unknown;\n try {\n body = await response.json();\n } catch {\n throw new AparteError('n8n answered the history request with something that is not JSON. Check that the URL is the Chat Trigger\\'s production URL.', AparteErrorCode.PROVIDER_MISCONFIGURED);\n }\n const data = typeof body === 'object' && body !== null ? (body as { data?: unknown }).data : undefined;\n return n8nHistoryToMessages(Array.isArray(data) ? data : []);\n}\n"],"names":["message","input","index","result","textOf","entry"],"mappings":"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"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@aparte/runner-n8n",
3
- "version": "0.27.3",
3
+ "version": "0.28.0",
4
4
  "homepage": "https://apartejs.dev/providers/agent/n8n/",
5
5
  "description": "Put aparté on an n8n chat workflow — the Chat Trigger's agent runs in n8n, with its memory and its tools; aparté draws the chat, streamed or not.",
6
6
  "type": "module",
@@ -25,13 +25,13 @@
25
25
  "node": ">=18"
26
26
  },
27
27
  "peerDependencies": {
28
- "@aparte/core": ">=0.27.3 <1.0.0"
28
+ "@aparte/core": ">=0.28.0 <1.0.0"
29
29
  },
30
30
  "devDependencies": {
31
31
  "@types/node": "^22.0.0",
32
32
  "typescript": "~5.7.0",
33
33
  "vite": "^6.0.0",
34
- "@aparte/core": "0.27.3"
34
+ "@aparte/core": "0.28.0"
35
35
  },
36
36
  "keywords": [
37
37
  "ai",