UNPKG

n8n

Version:

n8n Workflow Automation Tool

383 lines • 13.5 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.parseStoredMessages = parseStoredMessages; exports.collectConfirmationRequestIds = collectConfirmationRequestIds; exports.markExpiredConfirmations = markExpiredConfirmations; const api_types_1 = require("@n8n/api-types"); const zod_1 = require("zod"); const internal_messages_1 = require("./internal-messages"); const toolCallContentPartSchema = zod_1.z.object({ type: zod_1.z.literal('tool-call'), toolCallId: zod_1.z.string(), toolName: zod_1.z.string(), input: zod_1.z.unknown().optional(), state: zod_1.z.enum(['pending', 'resolved', 'rejected']).optional(), output: zod_1.z.unknown().optional(), error: zod_1.z.string().optional(), }); const textContentPartSchema = zod_1.z.object({ type: zod_1.z.literal('text'), text: zod_1.z.string() }); const reasoningContentPartSchema = zod_1.z.object({ type: zod_1.z.literal('reasoning'), text: zod_1.z.string() }); const opaqueContentPartSchema = zod_1.z .object({ type: zod_1.z.enum(['invalid-tool-call', 'file', 'citation', 'provider']) }) .passthrough(); const contentPartSchema = zod_1.z.union([ textContentPartSchema, reasoningContentPartSchema, toolCallContentPartSchema, opaqueContentPartSchema, ]); function extractTextFromContent(content) { if (typeof content === 'string') return content; if (Array.isArray(content)) return extractTextFromParts(content); return ''; } function extractReasoningFromContent(content) { if (typeof content === 'string') return ''; if (Array.isArray(content)) return extractReasoningFromParts(content); return ''; } function extractTextFromParts(parts) { return parts .flatMap((p) => { const parsed = textContentPartSchema.safeParse(p); return parsed.success ? [parsed.data.text] : []; }) .join(''); } function extractReasoningFromParts(parts) { return parts .flatMap((p) => { const parsed = reasoningContentPartSchema.safeParse(p); return parsed.success ? [parsed.data.text] : []; }) .join(''); } function extractParts(content) { if (Array.isArray(content)) return content.filter(isStoredContentPart); return undefined; } function isStoredContentPart(value) { return contentPartSchema.safeParse(value).success; } function toRecord(value) { return typeof value === 'object' && value !== null && !Array.isArray(value) ? value : {}; } function nativeToolPartToInvocation(part) { if (part.type !== 'tool-call') return undefined; const parsed = toolCallContentPartSchema.safeParse(part); if (!parsed.success) return undefined; const toolCall = parsed.data; const args = toRecord(toolCall.input); if (toolCall.state === 'resolved') { return { state: 'result', toolCallId: toolCall.toolCallId, toolName: toolCall.toolName, args, result: toolCall.output, }; } if (toolCall.state === 'rejected') { return { state: 'result', toolCallId: toolCall.toolCallId, toolName: toolCall.toolName, args, error: toolCall.error, }; } return { state: 'call', toolCallId: toolCall.toolCallId, toolName: toolCall.toolName, args, }; } function extractToolInvocations(content) { if (typeof content === 'string') return []; if (Array.isArray(content)) return content.filter(isStoredContentPart).flatMap((part) => { const invocation = nativeToolPartToInvocation(part); return invocation ? [invocation] : []; }); return []; } function buildToolCallState(invocation) { const isCompleted = invocation.state === 'result'; return { toolCallId: invocation.toolCallId, toolName: invocation.toolName, args: invocation.args, result: isCompleted ? invocation.result : undefined, error: isCompleted ? invocation.error : undefined, isLoading: !isCompleted, renderHint: (0, api_types_1.getRenderHint)(invocation.toolName), }; } function buildTimeline(textContent, toolCalls, parts) { if (parts?.length) { const timeline = []; for (const part of parts) { if (part.type === 'text' && part.text) { timeline.push({ type: 'text', content: part.text }); } else if (part.type === 'tool-call' && part.toolCallId) { timeline.push({ type: 'tool-call', toolCallId: part.toolCallId }); } } return timeline; } const timeline = []; for (const tc of toolCalls) { timeline.push({ type: 'tool-call', toolCallId: tc.toolCallId }); } if (textContent) { timeline.push({ type: 'text', content: textContent }); } return timeline; } function buildFlatAgentTree(textContent, reasoning, toolCalls, parts) { return { agentId: 'agent-001', role: 'orchestrator', status: 'completed', textContent, reasoning, toolCalls, children: [], timeline: buildTimeline(textContent, toolCalls, parts), }; } function snapshotTimestamp(snapshot) { return (snapshot.updatedAt ?? snapshot.createdAt ?? new Date(0)).toISOString(); } function snapshotCreatedAtMs(snapshot) { return snapshot.createdAt?.getTime(); } function messageCreatedAtMs(message) { return message.createdAt.getTime(); } function getNextConversationMessageTimestamp(messages, currentIndex) { for (let i = currentIndex + 1; i < messages.length; i++) { const role = messages[i].role; if (role === 'user' || role === 'assistant') return messageCreatedAtMs(messages[i]); } return undefined; } function buildSnapshotMessage(snapshot) { const groupId = snapshot.messageGroupId ?? snapshot.runId; return { id: groupId, runId: snapshot.runId, messageGroupId: snapshot.messageGroupId, runIds: snapshot.runIds, role: 'assistant', createdAt: snapshotTimestamp(snapshot), content: snapshot.tree.textContent, reasoning: snapshot.tree.reasoning, isStreaming: false, agentTree: snapshot.tree, }; } function parseStoredMessages(storedMessages, snapshots) { const messages = []; const snapshotList = snapshots ?? []; const conversationMessages = storedMessages.filter((message) => 'role' in message); let nextSnapshotIdx = 0; const consumedSnapshots = new Set(); const messagesWithSnapshotTree = new Set(); let lastUserMessageId; function pushSnapshotMessage(snapshot) { const built = buildSnapshotMessage(snapshot); messagesWithSnapshotTree.add(built); messages.push(built); } function appendChronologicalOrphansBefore(message) { const messageTimestamp = messageCreatedAtMs(message); while (nextSnapshotIdx < snapshotList.length) { const snapshot = snapshotList[nextSnapshotIdx]; const snapshotTimestamp = snapshotCreatedAtMs(snapshot); if (snapshotTimestamp === undefined || snapshotTimestamp >= messageTimestamp) return; consumedSnapshots.add(snapshot); pushSnapshotMessage(snapshot); nextSnapshotIdx++; } } function takeSnapshotForAssistant(message, messageIndex) { appendChronologicalOrphansBefore(message); const snapshot = snapshotList[nextSnapshotIdx]; if (!snapshot) return undefined; const nextMessageTimestamp = getNextConversationMessageTimestamp(conversationMessages, messageIndex); const snapshotTimestamp = snapshotCreatedAtMs(snapshot); if (snapshotTimestamp === undefined || (nextMessageTimestamp !== undefined && snapshotTimestamp > nextMessageTimestamp)) { return undefined; } consumedSnapshots.add(snapshot); nextSnapshotIdx++; return snapshot; } for (const [messageIndex, msg] of conversationMessages.entries()) { appendChronologicalOrphansBefore(msg); const text = extractTextFromContent(msg.content); if (msg.role === 'user') { lastUserMessageId = msg.id; const content = (0, internal_messages_1.cleanStoredUserMessage)(text); if (content === null) continue; messages.push({ id: msg.id, role: 'user', createdAt: msg.createdAt.toISOString(), content, reasoning: '', isStreaming: false, }); continue; } if (msg.role === 'assistant') { const reasoning = extractReasoningFromContent(msg.content); const invocations = extractToolInvocations(msg.content); const toolCalls = invocations.map(buildToolCallState); const parts = extractParts(msg.content); const snapshot = takeSnapshotForAssistant(msg, messageIndex); const runId = snapshot?.runId ?? lastUserMessageId ?? msg.id; const agentTree = snapshot?.tree ?? (toolCalls.length > 0 || text ? buildFlatAgentTree(text, reasoning, toolCalls, parts) : undefined); const assistantMessage = { id: msg.id, runId, messageGroupId: snapshot?.messageGroupId, runIds: snapshot?.runIds, role: 'assistant', createdAt: msg.createdAt.toISOString(), content: text, reasoning, isStreaming: false, agentTree, }; if (snapshot) messagesWithSnapshotTree.add(assistantMessage); messages.push(assistantMessage); continue; } } for (const snapshot of snapshots ?? []) { if (consumedSnapshots.has(snapshot)) continue; pushSnapshotMessage(snapshot); } propagateMessageGroupIdAcrossTurns(messages); const keptIndexByGid = new Map(); const toRemove = new Set(); for (let i = messages.length - 1; i >= 0; i--) { const gid = messages[i].messageGroupId; if (!gid) continue; const keptIdx = keptIndexByGid.get(gid); if (keptIdx === undefined) { keptIndexByGid.set(gid, i); continue; } const kept = messages[keptIdx]; const candidate = messages[i]; if (!messagesWithSnapshotTree.has(kept) && messagesWithSnapshotTree.has(candidate)) { kept.agentTree = candidate.agentTree; kept.runIds = candidate.runIds; messagesWithSnapshotTree.add(kept); } toRemove.add(i); } for (let i = messages.length - 1; i >= 0; i--) { if (toRemove.has(i)) messages.splice(i, 1); } return messages; } function propagateMessageGroupIdAcrossTurns(messages) { let turnStart = 0; for (let i = 0; i <= messages.length; i++) { const atBoundary = i === messages.length || messages[i].role === 'user'; if (!atBoundary) continue; propagateMessageGroupIdWithinRange(messages, turnStart, i); turnStart = i + 1; } } function propagateMessageGroupIdWithinRange(messages, start, end) { let turnGroupId; for (let i = end - 1; i >= start; i--) { const gid = messages[i].messageGroupId; if (gid) { turnGroupId = gid; break; } } if (!turnGroupId) return; for (let i = start; i < end; i++) { const msg = messages[i]; if (msg.role === 'assistant' && !msg.messageGroupId) { msg.messageGroupId = turnGroupId; } } } function isActionableConfirmation(tc) { return (tc.confirmation !== undefined && tc.isLoading && tc.confirmationStatus !== 'approved' && tc.confirmationStatus !== 'denied'); } function collectConfirmationRequestIds(messages) { const requestIds = []; for (const message of messages) { if (!message.agentTree) continue; walkAgentNodes(message.agentTree, (node) => { for (const tc of node.toolCalls) { const { confirmation } = tc; if (!confirmation || !isActionableConfirmation(tc)) continue; requestIds.push(confirmation.requestId); } }); } return requestIds; } function markExpiredConfirmations(messages, liveRequestIds) { for (const message of messages) { if (!message.agentTree) continue; walkAgentNodes(message.agentTree, (node) => { for (const tc of node.toolCalls) { const { confirmation } = tc; if (!confirmation || !isActionableConfirmation(tc)) continue; if (!liveRequestIds.has(confirmation.requestId)) { confirmation.expired = true; } } }); } } function walkAgentNodes(node, visit) { visit(node); for (const child of node.children) walkAgentNodes(child, visit); } //# sourceMappingURL=message-parser.js.map