UNPKG

n8n

Version:

n8n Workflow Automation Tool

272 lines 9.61 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.ExecutionRecorder = void 0; const agents_1 = require("@n8n/agents"); const n8n_workflow_1 = require("n8n-workflow"); function workingMemoryContentFromInput(input) { if (input && typeof input === 'object' && !Array.isArray(input)) { const maybe = input.memory; if (typeof maybe === 'string') return maybe; } return JSON.stringify(input, null, 2); } function resolveFromAIInValue(value, llmArgs) { if (typeof value === 'string') return resolveFromAIInString(value, llmArgs); if (Array.isArray(value)) return value.map((v) => resolveFromAIInValue(v, llmArgs)); if (value !== null && typeof value === 'object') { const out = {}; for (const [k, v] of Object.entries(value)) { out[k] = resolveFromAIInValue(v, llmArgs); } return out; } return value; } function resolveFromAIInString(str, llmArgs) { if (!str.includes('$fromAI')) return str; let calls; try { calls = (0, n8n_workflow_1.extractFromAICalls)(str); } catch { return str; } if (calls.length === 0) return str; if ((0, n8n_workflow_1.isFromAIOnlyExpression)(str)) { const call = calls[0]; if (call.key in llmArgs) return llmArgs[call.key]; if (call.defaultValue !== undefined) return call.defaultValue; return str; } const pattern = /\$fromAI\s*\([^)]*\)/g; return str.replace(pattern, (match) => { try { const inner = (0, n8n_workflow_1.extractFromAICalls)(match); if (inner.length === 0) return match; const call = inner[0]; const resolved = call.key in llmArgs ? llmArgs[call.key] : call.defaultValue !== undefined ? call.defaultValue : undefined; if (resolved === undefined) return match; if (typeof resolved === 'object') return JSON.stringify(resolved); return String(resolved); } catch { return match; } }); } function isRecord(v) { return typeof v === 'object' && v !== null; } class ExecutionRecorder { constructor(registry) { this.textParts = []; this.textBuffer = []; this.model = null; this.finishReason = 'unknown'; this.usage = null; this.totalCost = null; this.toolCalls = []; this.timeline = []; this.textStartTime = null; this._suspended = false; this.error = null; this.workingMemory = null; this.startTime = Date.now(); this.registry = registry ?? new Map(); } record(chunk) { switch (chunk.type) { case 'text-delta': if (this.textStartTime === null) this.textStartTime = Date.now(); this.textParts.push(chunk.delta); this.textBuffer.push(chunk.delta); break; case 'tool-call': if (chunk.toolName === agents_1.UPDATE_WORKING_MEMORY_TOOL_NAME) { this.recordWorkingMemoryUpdate(workingMemoryContentFromInput(chunk.input)); } else { this.recordToolCall(chunk.toolCallId, chunk.toolName, chunk.input); } break; case 'tool-result': if (chunk.toolName === agents_1.UPDATE_WORKING_MEMORY_TOOL_NAME) { break; } this.recordToolResult(chunk.toolCallId, chunk.toolName, chunk.output, chunk.isError === true); break; case 'finish': this.flushTextBuffer(); this.finishReason = chunk.finishReason; if (chunk.usage) { this.usage = { promptTokens: chunk.usage.promptTokens, completionTokens: chunk.usage.completionTokens, totalTokens: chunk.usage.totalTokens, }; } this.model = chunk.model ?? null; this.totalCost = chunk.totalCost ?? chunk.usage?.cost ?? null; break; case 'tool-call-suspended': this.flushTextBuffer(); this._suspended = true; this.timeline.push({ type: 'suspension', toolName: chunk.toolName ?? '', toolCallId: chunk.toolCallId ?? '', timestamp: Date.now(), }); break; case 'error': { const errMsg = chunk.error instanceof Error ? chunk.error.message : String(chunk.error); this.error = errMsg; break; } } } get suspended() { return this._suspended; } getMessageRecord() { this.flushTextBuffer(); return { assistantResponse: this.textParts.join(''), model: this.model, finishReason: this.finishReason, usage: this.usage, totalCost: this.totalCost, toolCalls: this.toolCalls, timeline: this.timeline, startTime: this.startTime, duration: Date.now() - this.startTime, error: this.error, workingMemory: this.workingMemory, }; } flushTextBuffer() { if (this.textBuffer.length === 0) return; const content = this.textBuffer.join(''); if (content.trim()) { const now = Date.now(); this.timeline.push({ type: 'text', content, timestamp: this.textStartTime ?? now, endTime: now, }); } this.textBuffer = []; this.textStartTime = null; } recordWorkingMemoryUpdate(content) { this.flushTextBuffer(); this.workingMemory = content; this.timeline.push({ type: 'working-memory', content, timestamp: Date.now(), }); } recordToolCall(toolCallId, name, input) { this.flushTextBuffer(); this.toolCalls.push({ name, input, output: undefined }); const entry = this.registry.get(name); const llmArgs = input !== null && typeof input === 'object' ? input : {}; const resolvedNodeParameters = entry?.nodeParameters !== undefined ? resolveFromAIInValue(entry.nodeParameters, llmArgs) : undefined; this.timeline.push({ type: 'tool-call', kind: entry?.kind ?? 'tool', name, toolCallId, input, output: undefined, startTime: Date.now(), endTime: 0, success: false, workflowId: entry?.workflowId, workflowName: entry?.workflowName, triggerType: entry?.triggerType, nodeType: entry?.nodeType, nodeTypeVersion: entry?.nodeTypeVersion, nodeDisplayName: entry?.nodeDisplayName, nodeParameters: resolvedNodeParameters, }); } recordToolResult(toolCallId, name, output, isError) { const pendingFlat = [...this.toolCalls] .reverse() .find((tc) => tc.name === name && tc.output === undefined); if (pendingFlat) { pendingFlat.output = output; } else { this.toolCalls.push({ name, input: undefined, output }); } const pendingTimeline = [...this.timeline] .reverse() .find((e) => e.type === 'tool-call' && (toolCallId ? e.toolCallId === toolCallId : e.name === name) && e.endTime === 0); if (pendingTimeline) { pendingTimeline.output = output; pendingTimeline.endTime = Date.now(); pendingTimeline.success = !isError; if (pendingTimeline.kind === 'workflow' && isRecord(output)) { const execId = output.executionId; if (typeof execId === 'string') { pendingTimeline.workflowExecutionId = execId; } } return; } this.flushTextBuffer(); const entry = this.registry.get(name); const now = Date.now(); const synthesized = { type: 'tool-call', kind: entry?.kind ?? 'tool', name, toolCallId, input: undefined, output, startTime: now, endTime: now, success: !isError, workflowId: entry?.workflowId, workflowName: entry?.workflowName, triggerType: entry?.triggerType, nodeType: entry?.nodeType, nodeTypeVersion: entry?.nodeTypeVersion, nodeDisplayName: entry?.nodeDisplayName, nodeParameters: entry?.nodeParameters, }; if (synthesized.kind === 'workflow' && isRecord(output)) { const execId = output.executionId; if (typeof execId === 'string') { synthesized.workflowExecutionId = execId; } } this.timeline.push(synthesized); } } exports.ExecutionRecorder = ExecutionRecorder; //# sourceMappingURL=execution-recorder.js.map