UNPKG

n8n

Version:

n8n Workflow Automation Tool

261 lines • 10.8 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.AgentChatStreamConsumer = void 0; const createResponseState = () => ({ hasVisibleResponse: 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': { const { delta } = chunk; await responseLifecycle.startStreamingResponse(); textStream.yield?.(delta); if (delta.trim()) responseState.hasVisibleResponse = true; break; } case 'reasoning-delta': { 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; } break; default: break; } } await this.postFallbackIfNeeded(responseState, responseLifecycle, thread); } finally { await responseLifecycle.finish(); } } 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': buffer += chunk.delta; break; case 'reasoning-delta': buffer += `_${chunk.delta}_`; break; case 'tool-call-suspended': { 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; } 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