11const logger = require ( '../../utils/logger' ) ;
22
3+ const DEFAULT_METRICS = {
4+ tokensIn : 0 ,
5+ tokensOut : 0 ,
6+ totalCost : 0 ,
7+ cacheWrites : 0 ,
8+ cacheReads : 0 ,
9+ conversationHistoryIndex : 0
10+ } ;
11+
312/**
413 * Calculator for processing and extracting metrics from task segments
514 */
@@ -138,19 +147,17 @@ class MetricsCalculator {
138147 return false ;
139148 }
140149
141- for ( const apiCall of this . segment . apiCalls ) {
150+ return this . segment . apiCalls . some ( apiCall => {
142151 if ( apiCall . role === 'assistant' && apiCall . content && Array . isArray ( apiCall . content ) ) {
143- for ( const content of apiCall . content ) {
144- if ( content . type === 'text' && content . text &&
145- ( content . text . includes ( 'use_mcp_tool' ) ||
146- content . text . includes ( 'use_mcp_server' ) ||
147- content . text . includes ( 'access_mcp_resource' ) ) ) {
148- return true ;
149- }
150- }
152+ return apiCall . content . some ( content =>
153+ content . type === 'text' && content . text &&
154+ ( content . text . includes ( 'use_mcp_tool' ) ||
155+ content . text . includes ( 'use_mcp_server' ) ||
156+ content . text . includes ( 'access_mcp_resource' ) )
157+ ) ;
151158 }
152- }
153- return false ;
159+ return false ;
160+ } ) ;
154161 }
155162
156163 /**
@@ -165,15 +172,13 @@ class MetricsCalculator {
165172 let apiCallCount = 0 ;
166173
167174 // Count API calls from UI messages only - only counting api_req_started events
168- for ( const msg of this . segment . userMessages ) {
175+ this . segment . userMessages . forEach ( msg => {
169176 // Count API request operations
170177 if ( msg . type === 'say' && msg . say === 'api_req_started' ) {
171178 apiCallCount ++ ;
172179 logger . debug ( `Found API request in task ${ this . segment . taskNumber } : ${ msg . text ?? '[no text]' } ` ) ;
173180 }
174-
175- // No longer counting MCP tool requests as per requirements
176- }
181+ } ) ;
177182
178183 logger . info ( `API Call Count for Task ${ this . segment . taskNumber } : ${ apiCallCount } ` ) ;
179184
@@ -201,39 +206,41 @@ class MetricsCalculator {
201206 let foundInitialTask = false ;
202207
203208 // Look in API conversation history (most reliable source)
204- for ( const entry of this . segment . apiCalls ) {
209+ this . segment . apiCalls . forEach ( entry => {
205210 // Only process user messages
206- if ( entry . role === 'user' && entry . content && Array . isArray ( entry . content ) ) {
207- // Extract the text from all content items
208- const fullText = entry . content
209- . filter ( item => item . type === 'text' )
210- . map ( item => item . text ?? '' )
211- . join ( ' ' ) ;
212-
213- // Check if this is the initial task request
214- if ( ! foundInitialTask && (
215- fullText . includes ( 'Complete Task' ) ||
216- fullText . includes ( 'agent-instructions/mcp_instructions.md' ) ||
217- fullText . includes ( 'agent-instructions/control_instructions.md' )
218- ) ) {
219- foundInitialTask = true ;
220- userInteractionCount = 1 ; // Set to exactly 1 for the initial task
221- logger . info ( `Found initial task instruction for task ${ this . segment . taskNumber } ` ) ;
222- continue ; // Skip to next message
223- }
224-
225- // Only count additional user messages if they appear to be actual human follow-ups
226- // and not system messages
227- if ( foundInitialTask &&
228- ! fullText . includes ( '<environment_details>' ) &&
229- ! fullText . startsWith ( '[' ) &&
230- fullText . trim ( ) . length > 10 ) { // Minimum length to exclude noise
231- // This appears to be a genuine follow-up question
232- userInteractionCount ++ ;
233- logger . info ( `Found follow-up user message for task ${ this . segment . taskNumber } : "${ fullText . substring ( 0 , 50 ) } ..."` ) ;
234- }
211+ if ( entry . role !== 'user' || ! entry . content || ! Array . isArray ( entry . content ) ) {
212+ return ;
235213 }
236- }
214+
215+ // Extract the text from all content items
216+ const fullText = entry . content
217+ . filter ( item => item . type === 'text' )
218+ . map ( item => item . text ?? '' )
219+ . join ( ' ' ) ;
220+
221+ // Check if this is the initial task request
222+ if ( ! foundInitialTask && (
223+ fullText . includes ( 'Complete Task' ) ||
224+ fullText . includes ( 'agent-instructions/mcp_instructions.md' ) ||
225+ fullText . includes ( 'agent-instructions/control_instructions.md' )
226+ ) ) {
227+ foundInitialTask = true ;
228+ userInteractionCount = 1 ; // Set to exactly 1 for the initial task
229+ logger . info ( `Found initial task instruction for task ${ this . segment . taskNumber } ` ) ;
230+ return ; // Skip to next message
231+ }
232+
233+ // Only count additional user messages if they appear to be actual human follow-ups
234+ // and not system messages
235+ if ( foundInitialTask &&
236+ ! fullText . includes ( '<environment_details>' ) &&
237+ ! fullText . startsWith ( '[' ) &&
238+ fullText . trim ( ) . length > 10 ) { // Minimum length to exclude noise
239+ // This appears to be a genuine follow-up question
240+ userInteractionCount ++ ;
241+ logger . info ( `Found follow-up user message for task ${ this . segment . taskNumber } : "${ fullText . substring ( 0 , 50 ) } ..."` ) ;
242+ }
243+ } ) ;
237244
238245 // If we still haven't found any interactions, default to 1
239246 if ( userInteractionCount === 0 ) {
@@ -250,17 +257,10 @@ class MetricsCalculator {
250257 */
251258 async calculateTokenMetrics ( ) {
252259 // Default values
253- const defaultMetrics = {
254- tokensIn : 0 ,
255- tokensOut : 0 ,
256- totalCost : 0 ,
257- cacheWrites : 0 ,
258- cacheReads : 0 ,
259- conversationHistoryIndex : 0
260- } ;
260+ const metrics = { ...DEFAULT_METRICS } ;
261261
262262 if ( ! this . segment ?. userMessages ?. length ) {
263- return defaultMetrics ;
263+ return metrics ;
264264 }
265265
266266 let tokensIn = 0 ;
@@ -274,7 +274,7 @@ class MetricsCalculator {
274274 logger . info ( `Starting token calculation for task ${ this . segment . taskNumber } ` ) ;
275275
276276 // First pass: Collect reported token usage from Claude
277- for ( const message of this . segment . userMessages ) {
277+ this . segment . userMessages . forEach ( message => {
278278 if ( message . type === 'say' && message . text ) {
279279 try {
280280 const data = JSON . parse ( message . text ) ;
@@ -321,20 +321,20 @@ class MetricsCalculator {
321321 highestConvHistoryIndex = indexValue ;
322322 }
323323 }
324- }
324+ } ) ;
325325
326326 // Second pass: Check API calls for any additional token usage
327327 if ( this . segment . apiCalls ?. length ) {
328328 let apiCallsWithTokens = 0 ;
329- for ( const apiCall of this . segment . apiCalls ) {
329+ this . segment . apiCalls . forEach ( apiCall => {
330330 if ( apiCall . usage ) {
331331 if ( apiCall . usage . input_tokens ) tokensIn += apiCall . usage . input_tokens ;
332332 if ( apiCall . usage . output_tokens ) tokensOut += apiCall . usage . output_tokens ;
333333 if ( apiCall . usage . cost ) totalCost += apiCall . usage . cost ;
334334 apiCallsWithTokens ++ ;
335335 logger . info ( `API Call ${ apiCallsWithTokens } tokens - In: ${ apiCall . usage . input_tokens ?? 0 } , Out: ${ apiCall . usage . output_tokens ?? 0 } ` ) ;
336336 }
337- }
337+ } ) ;
338338 }
339339
340340 // Calculate total cost if not already set
@@ -381,24 +381,27 @@ class MetricsCalculator {
381381 }
382382
383383 // Try to determine from logs
384- for ( const apiCall of this . segment . apiCalls ) {
384+ const modelFromLogs = this . segment . apiCalls . find ( apiCall => {
385385 if ( apiCall . role === 'assistant' && apiCall . content && Array . isArray ( apiCall . content ) ) {
386- for ( const content of apiCall . content ) {
386+ return apiCall . content . some ( content => {
387387 if ( content . type === 'text' && content . text ) {
388388 // Look for model in system prompt
389389 const systemPromptMatch = content . text . match ( / Y o u a r e a p o w e r f u l a g e n t i c A I c o d i n g a s s i s t a n t , p o w e r e d b y ( C l a u d e [ \d . ] + \w + ) / i) ;
390- if ( systemPromptMatch ?. [ 1 ] ) {
391- // Normalize model name
392- const detectedModel = systemPromptMatch [ 1 ] . toLowerCase ( )
393- . replace ( 'claude ' , 'claude-' )
394- . replace ( ' ' , '-' ) ;
395-
396- logger . info ( `Detected model from logs: ${ detectedModel } ` ) ;
397- return detectedModel ;
398- }
390+ return systemPromptMatch ?. [ 1 ] ;
399391 }
400- }
392+ return false ;
393+ } ) ;
401394 }
395+ return false ;
396+ } ) ;
397+
398+ if ( modelFromLogs ) {
399+ const detectedModel = modelFromLogs . toLowerCase ( )
400+ . replace ( 'claude ' , 'claude-' )
401+ . replace ( ' ' , '-' ) ;
402+
403+ logger . info ( `Detected model from logs: ${ detectedModel } ` ) ;
404+ return detectedModel ;
402405 }
403406
404407 // Default if not found in logs and no argument provided
0 commit comments