import type { NodeParameterValueType, INodeParameterResourceLocator, IRunData, INodeExecutionData, } from 'n8n-workflow'; import { NodeConnectionTypes } from 'n8n-workflow'; type TokenUsageValues = { completionTokens: number; promptTokens: number; totalTokens: number; }; type TokenUsageInfo = Record<`${string}__${number}` | 'total', TokenUsageValues>; function isRlcValue(value: NodeParameterValueType): value is INodeParameterResourceLocator { return Boolean( typeof value === 'object' && value && 'value' in value && '__rl' in value && value.__rl, ); } export function checkNodeParameterNotEmpty(value: NodeParameterValueType) { if (value === undefined || value === null || value === '') { return false; } if (isRlcValue(value)) { return checkNodeParameterNotEmpty(value.value); } return true; } export function extractTokenUsage(executionRunData: IRunData) { const result: TokenUsageInfo = { total: { completionTokens: 0, promptTokens: 0, totalTokens: 0, }, }; const extractFromNode = (nodeName: string, nodeData: INodeExecutionData, index: number) => { function isValidTokenInfo(data: unknown): data is TokenUsageValues { return ( typeof data === 'object' && data !== null && 'completionTokens' in data && 'promptTokens' in data && 'totalTokens' in data && typeof data.completionTokens === 'number' && typeof data.promptTokens === 'number' && typeof data.totalTokens === 'number' ); } const tokenInfo = nodeData.json?.tokenUsage ?? nodeData.json?.tokenUsageEstimate; if (tokenInfo && isValidTokenInfo(tokenInfo)) { result[`${nodeName}__${index}`] = { completionTokens: tokenInfo.completionTokens, promptTokens: tokenInfo.promptTokens, totalTokens: tokenInfo.totalTokens, }; result.total.completionTokens += tokenInfo.completionTokens; result.total.promptTokens += tokenInfo.promptTokens; result.total.totalTokens += tokenInfo.totalTokens; } }; for (const [nodeName, nodeData] of Object.entries(executionRunData)) { if (nodeData[0]?.data?.[NodeConnectionTypes.AiLanguageModel]) { for (const [index, node] of nodeData.entries()) { const modelNodeExecutionData = node.data?.[NodeConnectionTypes.AiLanguageModel]?.[0]?.[0]; if (modelNodeExecutionData) { extractFromNode(nodeName, modelNodeExecutionData, index); } } } } return result; }