UNPKG

n8n

Version:

n8n Workflow Automation Tool

282 lines • 11.8 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.AgentChatStreamConsumer = void 0; const is_record_1 = require("@n8n/utils/is-record"); const agent_chat_suspension_cards_1 = require("./agent-chat-suspension-cards"); const createResponseState = () => ({ hasVisibleResponse: false, suppressText: false, fallbackSource: null, fallbackError: null, }); class AgentChatStreamConsumer { constructor(options) { this.options = options; } async consume(stream, thread, options = {}) { if (this.options.disableStreaming || options.forceBuffered) { await this.consumeBuffered(stream, thread, { statusHandle: options.statusHandle, }); return; } const textStream = { yield: null, end: null, }; let streamingPost = null; const createTextIterable = () => { const queue = []; let done = false; let waiting = null; textStream.yield = (text) => { if (waiting) { const resolve = waiting; waiting = null; resolve({ value: text, done: false }); } else { queue.push(text); } }; textStream.end = () => { done = true; if (waiting) { const resolve = waiting; waiting = null; resolve({ value: '', done: true }); } }; return { [Symbol.asyncIterator]() { return { async next() { if (queue.length > 0) { return { value: queue.shift(), done: false }; } if (done) { return { value: '', done: true }; } return await new Promise((resolve) => { waiting = resolve; }); }, }; }, }; }; const startStreamingPost = () => { const iterable = createTextIterable(); streamingPost = thread.post(iterable).catch(async (postError) => { await this.options.postErrorToThread(thread, postError); this.options.logger.error('[AgentChatBridge] Streaming post failed', { error: postError instanceof Error ? postError.message : String(postError), }); }); }; const endStreamingPost = async () => { if (textStream.end) { textStream.end(); textStream.end = null; textStream.yield = null; } if (streamingPost) { await streamingPost; streamingPost = null; } }; const ensureStreamingPost = () => { if (!streamingPost) startStreamingPost(); }; const responseLifecycle = this.createResponseLifecycle({ statusHandle: options.statusHandle, ensureStreamingPost, endStreamingPost, }); const responseState = createResponseState(); try { for await (const chunk of stream) { switch (chunk.type) { case 'text-delta': { if (responseState.suppressText) break; const { delta } = chunk; await responseLifecycle.startStreamingResponse(); textStream.yield?.(delta); if (delta.trim()) responseState.hasVisibleResponse = true; break; } case 'tool-call-suspended': { await responseLifecycle.startDiscreteResponse(); const result = await this.options.handleSuspension(chunk, thread); responseState.hasVisibleResponse ||= result === 'posted'; if (result === 'failed') { responseState.fallbackSource = 'suspension'; responseState.fallbackError = new Error('Failed to post tool approval request'); } break; } case 'message': await responseLifecycle.startDiscreteResponse(); responseState.hasVisibleResponse ||= await this.options.handleMessage(chunk, thread); break; case 'error': await responseLifecycle.startDiscreteResponse(); await this.options.postErrorToThread(thread, chunk.error); responseState.hasVisibleResponse = true; break; case 'tool-result': if (chunk.isError) { responseState.fallbackSource = 'tool-error'; responseState.fallbackError = chunk.output; } else if (responseState.fallbackSource === 'tool-error') { responseState.fallbackSource = null; } if (this.isSilentOutcome(chunk)) responseState.suppressText = true; break; default: break; } } await this.postFallbackIfNeeded(responseState, responseLifecycle, thread); } finally { await responseLifecycle.finish(); } } isSilentOutcome(chunk) { if (chunk.isError || !(this.options.isIntegrationActionTool?.(chunk.toolName) ?? false)) { return false; } if (!(0, is_record_1.isRecord)(chunk.output)) return false; if (chunk.output.silent === true) return true; return (Array.isArray(chunk.output.results) && chunk.output.results.some((entry) => (0, is_record_1.isRecord)(entry) && entry.action === 'do_not_respond' && (0, is_record_1.isRecord)(entry.result) && entry.result.ok === true && entry.result.silent === true)); } createResponseLifecycle(options) { let responseStarted = false; const clearStatusBeforeFirstResponse = async () => { if (responseStarted) return; responseStarted = true; await options.statusHandle?.clearBeforeResponse(); }; return { startStreamingResponse: async () => { await clearStatusBeforeFirstResponse(); options.ensureStreamingPost?.(); }, startDiscreteResponse: async () => { await options.endStreamingPost?.(); await clearStatusBeforeFirstResponse(); }, finish: async () => { await options.endStreamingPost?.(); await clearStatusBeforeFirstResponse(); }, }; } async postFallbackIfNeeded(state, lifecycle, thread) { if (!state.fallbackSource) return; if (state.fallbackSource === 'tool-error' && state.hasVisibleResponse) return; await lifecycle.startDiscreteResponse(); await this.options.postErrorToThread(thread, state.fallbackError); state.hasVisibleResponse = true; } async consumeBuffered(stream, thread, options = {}) { let buffer = ''; const responseState = createResponseState(); const responseLifecycle = this.createResponseLifecycle({ statusHandle: options.statusHandle, }); const flushBuffer = async () => { const text = buffer; buffer = ''; if (!text.trim()) return; try { await responseLifecycle.startDiscreteResponse(); await thread.post({ markdown: text }); } catch (postError) { await this.options.postErrorToThread(thread, postError); this.options.logger.error('[AgentChatBridge] Buffered post failed', { error: postError instanceof Error ? postError.message : String(postError), }); } responseState.hasVisibleResponse = true; }; try { for await (const chunk of stream) { switch (chunk.type) { case 'text-delta': if (!responseState.suppressText) buffer += chunk.delta; break; case 'tool-call-suspended': { if ((0, agent_chat_suspension_cards_1.isIntegrationActionSuspendPayload)(chunk.suspendPayload)) { buffer = ''; } else { await flushBuffer(); } await responseLifecycle.startDiscreteResponse(); const result = await this.options.handleSuspension(chunk, thread); responseState.hasVisibleResponse ||= result === 'posted'; if (result === 'failed') { responseState.fallbackSource = 'suspension'; responseState.fallbackError = new Error('Failed to post tool approval request'); } break; } case 'message': await flushBuffer(); await responseLifecycle.startDiscreteResponse(); responseState.hasVisibleResponse ||= await this.options.handleMessage(chunk, thread); break; case 'error': await flushBuffer(); await responseLifecycle.startDiscreteResponse(); await this.options.postErrorToThread(thread, chunk.error); responseState.hasVisibleResponse = true; break; case 'tool-result': if (chunk.isError) { responseState.fallbackSource = 'tool-error'; responseState.fallbackError = chunk.output; } else if (responseState.fallbackSource === 'tool-error') { responseState.fallbackSource = null; } if (this.isSilentOutcome(chunk)) { responseState.suppressText = true; buffer = ''; } break; default: break; } } await flushBuffer(); await this.postFallbackIfNeeded(responseState, responseLifecycle, thread); } finally { await flushBuffer(); await responseLifecycle.finish(); } } } exports.AgentChatStreamConsumer = AgentChatStreamConsumer; //# sourceMappingURL=agent-chat-stream-consumer.js.map