@@ -62,6 +62,17 @@ export function isNumber(value: unknown): value is number {
6262 return typeof value === 'number' ;
6363}
6464
65+ function isTokenUsage ( value : unknown ) : value is { promptTokens : number ; completionTokens : number } {
66+ return (
67+ typeof value === 'object' &&
68+ value !== null &&
69+ 'promptTokens' in value &&
70+ typeof value . promptTokens === 'number' &&
71+ 'completionTokens' in value &&
72+ typeof value . completionTokens === 'number'
73+ ) ;
74+ }
75+
6576function resolveParameterValue ( value : unknown ) : string | undefined {
6677 if ( typeof value === 'string' ) {
6778 return value ;
@@ -74,6 +85,32 @@ function resolveParameterValue(value: unknown): string | undefined {
7485 return undefined ;
7586}
7687
88+ function extractNodeTokenUsage (
89+ nodeRunData : ITaskData [ ] ,
90+ ) : { input : number ; output : number } | undefined {
91+ let input = 0 ;
92+ let output = 0 ;
93+ for ( const task of nodeRunData ) {
94+ const lmOutputs = task . data ?. [ NodeConnectionTypes . AiLanguageModel ] ;
95+ if ( lmOutputs ) {
96+ // LM sub-nodes (connected to Agent/Chain) — token data from N8nLlmTracing
97+ for ( const branch of lmOutputs ) {
98+ for ( const item of branch ?? [ ] ) {
99+ const usage = item ?. json ?. tokenUsage ?? item ?. json ?. tokenUsageEstimate ;
100+ if ( ! isTokenUsage ( usage ) ) continue ;
101+ input += usage . promptTokens ;
102+ output += usage . completionTokens ;
103+ }
104+ }
105+ } else if ( task . metadata ?. tokenUsage ) {
106+ // Standalone vendor nodes — token data captured via setMetadata before simplify
107+ input += task . metadata . tokenUsage . inputTokens ?? 0 ;
108+ output += task . metadata . tokenUsage . outputTokens ?? 0 ;
109+ }
110+ }
111+ return input > 0 || output > 0 ? { input, output } : undefined ;
112+ }
113+
77114const countPlaceholders = ( text : string ) => {
78115 const placeholder = / ( \{ [ a - z A - Z 0 - 9 _ ] + \} ) / g;
79116 let returnData = 0 ;
@@ -613,6 +650,14 @@ export function generateNodesGraph(
613650 }
614651 }
615652
653+ if ( nodeItem . ai_model && runData ?. [ node . name ] ) {
654+ const tokenUsage = extractNodeTokenUsage ( runData [ node . name ] ) ;
655+ if ( tokenUsage ) {
656+ nodeItem . ai_input_tokens = tokenUsage . input ;
657+ nodeItem . ai_output_tokens = tokenUsage . output ;
658+ }
659+ }
660+
616661 if ( options ?. isCloudDeployment === true ) {
617662 if ( node . type === OPENAI_LANGCHAIN_NODE_TYPE ) {
618663 nodeItem . prompts =
0 commit comments