UNPKG

n8n

Version:

n8n Workflow Automation Tool

212 lines • 7.21 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.initSseStream = initSseStream; exports.pumpChunks = pumpChunks; const n8n_workflow_1 = require("n8n-workflow"); const SSE_HEARTBEAT_INTERVAL_MS = 30_000; function initSseStream(res) { res.setHeader('Content-Type', 'text/event-stream; charset=UTF-8'); res.setHeader('Cache-Control', 'no-cache, no-transform'); res.setHeader('Connection', 'keep-alive'); res.setHeader('X-Accel-Buffering', 'no'); res.flushHeaders(); res.socket?.setTimeout(0); res.socket?.setNoDelay(true); res.socket?.setKeepAlive(true); res.write(':ok\n\n'); res.flush?.(); const heartbeat = setInterval(() => { if (!res.writableEnded && !res.destroyed) { res.write(':ping\n\n'); res.flush?.(); } }, SSE_HEARTBEAT_INTERVAL_MS); heartbeat.unref(); const stopHeartbeat = () => clearInterval(heartbeat); res.once('finish', stopHeartbeat); res.once('close', stopHeartbeat); const send = (event) => { res.write(`data: ${JSON.stringify(event)}\n\n`); res.flush?.(); }; return { send }; } function toAgentSseMessage(message) { if (!('content' in message) || !Array.isArray(message.content)) return undefined; const content = []; for (const part of message.content) { if (part.type === 'text' && 'text' in part) { content.push({ type: 'text', text: part.text }); } else if (part.type === 'reasoning' && 'text' in part) { content.push({ type: 'reasoning', text: part.text }); } } if (content.length === 0) return undefined; return { role: message.role, content }; } function emitTextLikeChunk(chunk, send) { switch (chunk.type) { case 'text-start': send({ type: 'text-start', id: chunk.id }); break; case 'text-delta': if (chunk.delta) send({ type: 'text-delta', id: chunk.id, delta: chunk.delta }); break; case 'text-end': send({ type: 'text-end', id: chunk.id }); break; case 'reasoning-start': send({ type: 'reasoning-start', id: chunk.id }); break; case 'reasoning-delta': if (chunk.delta) send({ type: 'reasoning-delta', id: chunk.id, delta: chunk.delta }); break; case 'reasoning-end': send({ type: 'reasoning-end', id: chunk.id }); break; } } function emitToolChunk(chunk, ctx) { const { send } = ctx; switch (chunk.type) { case 'tool-input-start': send({ type: 'tool-input-start', toolCallId: chunk.toolCallId, toolName: chunk.toolName, }); break; case 'tool-input-delta': if (chunk.delta) { send({ type: 'tool-input-delta', toolCallId: chunk.toolCallId, delta: chunk.delta }); } break; case 'tool-call': send({ type: 'tool-call', toolCallId: chunk.toolCallId, toolName: chunk.toolName, input: chunk.input, }); break; case 'tool-execution-start': send({ type: 'tool-execution-start', toolCallId: chunk.toolCallId, toolName: chunk.toolName, startTime: chunk.startTime, }); break; case 'tool-execution-end': send({ type: 'tool-execution-end', toolCallId: chunk.toolCallId, toolName: chunk.toolName, isError: chunk.isError, endTime: chunk.endTime, }); break; case 'tool-result': { const toolResultChunk = chunk; send({ type: 'tool-result', toolCallId: chunk.toolCallId, toolName: chunk.toolName, output: chunk.output, ...(chunk.isError !== undefined && { isError: chunk.isError }), ...(toolResultChunk.canceled !== undefined && { canceled: toolResultChunk.canceled }), }); break; } case 'tool-call-suspended': { const payload = { toolCallId: chunk.toolCallId, runId: chunk.runId, toolName: chunk.toolName, input: chunk.suspendPayload, }; send({ type: 'tool-call-suspended', payload }); return { suspended: true }; } } return { suspended: false }; } function emitChunkEvents(chunk, ctx) { switch (chunk.type) { case 'start-step': ctx.send({ type: 'start-step' }); return { suspended: false }; case 'finish-step': ctx.send({ type: 'finish-step' }); return { suspended: false }; case 'text-start': case 'text-delta': case 'text-end': case 'reasoning-start': case 'reasoning-delta': case 'reasoning-end': emitTextLikeChunk(chunk, ctx.send); return { suspended: false }; case 'tool-input-start': case 'tool-input-delta': case 'tool-call': case 'tool-execution-start': case 'tool-execution-end': case 'tool-result': case 'tool-call-suspended': return emitToolChunk(chunk, ctx); case 'message': { const sseMessage = toAgentSseMessage(chunk.message); if (sseMessage) ctx.send({ type: 'message', message: sseMessage }); return { suspended: false }; } case 'error': { const errMsg = stringifyError(chunk.error); ctx.send({ type: 'error', message: errMsg }); return { suspended: false }; } case 'warning': { ctx.send({ type: 'warning', message: chunk.message, ...(chunk.code !== undefined && { code: chunk.code }), ...(chunk.source !== undefined && { source: chunk.source }), ...(chunk.server !== undefined && { server: chunk.server }), }); return { suspended: false }; } default: return { suspended: false }; } } function stringifyError(error) { try { if (error instanceof Error) { return error.message; } if (typeof error === 'object') { return JSON.stringify(error, null, 2); } return `Error: ${String(error)}`; } catch (e) { n8n_workflow_1.LoggerProxy.warn('Failed to stringify agent streaming error', { error }); } return 'Unknown error'; } async function pumpChunks(chunks, send) { const ctx = { send }; let suspended = false; for await (const chunk of chunks) { const result = emitChunkEvents(chunk, ctx); suspended ||= result.suspended; } return suspended; } //# sourceMappingURL=agent-sse-stream.js.map