@@ -60,6 +60,22 @@ export interface SessionPausedParams {
6060 workflowInput ?: any
6161}
6262
63+ interface AccumulatedCost {
64+ total : number
65+ input : number
66+ output : number
67+ tokens : { input : number ; output : number ; total : number }
68+ models : Record <
69+ string ,
70+ {
71+ input : number
72+ output : number
73+ total : number
74+ tokens : { input : number ; output : number ; total : number }
75+ }
76+ >
77+ }
78+
6379export class LoggingSession {
6480 private workflowId : string
6581 private executionId : string
@@ -70,6 +86,14 @@ export class LoggingSession {
7086 private workflowState ?: WorkflowState
7187 private isResume = false
7288 private completed = false
89+ private accumulatedCost : AccumulatedCost = {
90+ total : BASE_EXECUTION_CHARGE ,
91+ input : 0 ,
92+ output : 0 ,
93+ tokens : { input : 0 , output : 0 , total : 0 } ,
94+ models : { } ,
95+ }
96+ private costFlushed = false
7397
7498 constructor (
7599 workflowId : string ,
@@ -83,6 +107,102 @@ export class LoggingSession {
83107 this . requestId = requestId
84108 }
85109
110+ async onBlockComplete (
111+ blockId : string ,
112+ blockName : string ,
113+ blockType : string ,
114+ output : any
115+ ) : Promise < void > {
116+ if ( ! output ?. cost || typeof output . cost . total !== 'number' || output . cost . total <= 0 ) {
117+ return
118+ }
119+
120+ const { cost, tokens, model } = output
121+
122+ this . accumulatedCost . total += cost . total || 0
123+ this . accumulatedCost . input += cost . input || 0
124+ this . accumulatedCost . output += cost . output || 0
125+
126+ if ( tokens ) {
127+ this . accumulatedCost . tokens . input += tokens . input || 0
128+ this . accumulatedCost . tokens . output += tokens . output || 0
129+ this . accumulatedCost . tokens . total += tokens . total || 0
130+ }
131+
132+ if ( model ) {
133+ if ( ! this . accumulatedCost . models [ model ] ) {
134+ this . accumulatedCost . models [ model ] = {
135+ input : 0 ,
136+ output : 0 ,
137+ total : 0 ,
138+ tokens : { input : 0 , output : 0 , total : 0 } ,
139+ }
140+ }
141+ this . accumulatedCost . models [ model ] . input += cost . input || 0
142+ this . accumulatedCost . models [ model ] . output += cost . output || 0
143+ this . accumulatedCost . models [ model ] . total += cost . total || 0
144+ if ( tokens ) {
145+ this . accumulatedCost . models [ model ] . tokens . input += tokens . input || 0
146+ this . accumulatedCost . models [ model ] . tokens . output += tokens . output || 0
147+ this . accumulatedCost . models [ model ] . tokens . total += tokens . total || 0
148+ }
149+ }
150+
151+ await this . flushAccumulatedCost ( )
152+ }
153+
154+ private async flushAccumulatedCost ( ) : Promise < void > {
155+ try {
156+ await db
157+ . update ( workflowExecutionLogs )
158+ . set ( {
159+ cost : {
160+ total : this . accumulatedCost . total ,
161+ input : this . accumulatedCost . input ,
162+ output : this . accumulatedCost . output ,
163+ tokens : this . accumulatedCost . tokens ,
164+ models : this . accumulatedCost . models ,
165+ } ,
166+ } )
167+ . where ( eq ( workflowExecutionLogs . executionId , this . executionId ) )
168+
169+ this . costFlushed = true
170+ } catch ( error ) {
171+ logger . error ( `Failed to flush accumulated cost for execution ${ this . executionId } :` , {
172+ error : error instanceof Error ? error . message : String ( error ) ,
173+ } )
174+ }
175+ }
176+
177+ private async loadExistingCost ( ) : Promise < void > {
178+ try {
179+ const [ existing ] = await db
180+ . select ( { cost : workflowExecutionLogs . cost } )
181+ . from ( workflowExecutionLogs )
182+ . where ( eq ( workflowExecutionLogs . executionId , this . executionId ) )
183+ . limit ( 1 )
184+
185+ if ( existing ?. cost ) {
186+ const cost = existing . cost as any
187+ this . accumulatedCost = {
188+ total : cost . total || BASE_EXECUTION_CHARGE ,
189+ input : cost . input || 0 ,
190+ output : cost . output || 0 ,
191+ tokens : {
192+ input : cost . tokens ?. input || 0 ,
193+ output : cost . tokens ?. output || 0 ,
194+ total : cost . tokens ?. total || 0 ,
195+ } ,
196+ models : cost . models || { } ,
197+ }
198+ }
199+ } catch ( error ) {
200+ logger . error ( `Failed to load existing cost for execution ${ this . executionId } :` , {
201+ error : error instanceof Error ? error . message : String ( error ) ,
202+ } )
203+ }
204+ }
205+
86206 async start ( params : SessionStartParams ) : Promise < void > {
87207 const { userId, workspaceId, variables, triggerData, skipLogCreation, deploymentVersionId } =
88208 params
@@ -102,7 +222,6 @@ export class LoggingSession {
102222 ? await loadDeployedWorkflowStateForLogging ( this . workflowId )
103223 : await loadWorkflowStateForExecution ( this . workflowId )
104224
105- // Only create a new log entry if not resuming
106225 if ( ! skipLogCreation ) {
107226 await executionLogger . startWorkflowExecution ( {
108227 workflowId : this . workflowId ,
@@ -118,7 +237,8 @@ export class LoggingSession {
118237 logger . debug ( `[${ this . requestId } ] Started logging for execution ${ this . executionId } ` )
119238 }
120239 } else {
121- this . isResume = true // Mark as resume
240+ this . isResume = true
241+ await this . loadExistingCost ( )
122242 if ( this . requestId ) {
123243 logger . debug (
124244 `[${ this . requestId } ] Resuming logging for existing execution ${ this . executionId } `
0 commit comments