Skip to content

Commit de4c1b1

Browse files
committed
more cleanup
1 parent 0aa8095 commit de4c1b1

File tree

4 files changed

+692
-567
lines changed

4 files changed

+692
-567
lines changed

src/metrics/chat-processor.ts

Lines changed: 38 additions & 241 deletions
Original file line numberDiff line numberDiff line change
@@ -3,12 +3,16 @@ import path from 'path';
33

44
import { logger } from '../utils';
55
import {
6-
ApiHistoryEntry,
7-
CONTROL_MARKER,
8-
MCP_MARKER,
9-
TaskSegment,
10-
UIMessage,
11-
} from './types';
6+
createTimestampBoundaries,
7+
extractConversationHistoryIndex,
8+
filterRelevantMessages,
9+
findTaskBoundaries,
10+
getLastValidMessageTimestamp,
11+
getTimestampFromApiEntry,
12+
identifyTestType,
13+
validateTaskBoundaries,
14+
} from './metrics-utils';
15+
import { ApiHistoryEntry, TaskSegment, UIMessage } from './types';
1216

1317
class ChatProcessor {
1418
private chatDir: string;
@@ -45,7 +49,7 @@ class ChatProcessor {
4549
}
4650

4751
// Identify test type with validation
48-
const testType = this.identifyTestType();
52+
const testType = this.getTestType();
4953
if (!testType) {
5054
logger.warn(
5155
`Could not identify test type for ${this.directoryId}, skipping processing`,
@@ -95,7 +99,7 @@ class ChatProcessor {
9599
* Identify the test type (control or mcp) from the chat data
96100
* @returns {string|undefined} Test type or undefined if not identifiable
97101
*/
98-
identifyTestType(): string | undefined {
102+
getTestType(): string | undefined {
99103
if (
100104
!this.initialized ||
101105
!this.uiMessages?.length ||
@@ -104,64 +108,7 @@ class ChatProcessor {
104108
return undefined;
105109
}
106110

107-
// Check UI messages first
108-
for (const message of this.uiMessages) {
109-
if (message.type === 'say' && message.say === 'text' && message.text) {
110-
if (
111-
message.text.includes(
112-
"'agent-instructions/control_instructions.md'",
113-
) ||
114-
message.text.includes(
115-
'"agent-instructions/control_instructions.md"',
116-
) ||
117-
message.text.includes(CONTROL_MARKER)
118-
) {
119-
return 'control';
120-
}
121-
122-
if (
123-
message.text.includes("'agent-instructions/mcp_instructions.md'") ||
124-
message.text.includes('"agent-instructions/mcp_instructions.md"') ||
125-
message.text.includes(MCP_MARKER)
126-
) {
127-
return 'mcp';
128-
}
129-
}
130-
131-
// Check for MCP usage
132-
if (message.type === 'say' && message.say === 'use_mcp_server') {
133-
return 'mcp';
134-
}
135-
}
136-
137-
// Check API history as backup
138-
for (const entry of this.apiHistory) {
139-
if (
140-
entry.role === 'user' &&
141-
entry.content &&
142-
Array.isArray(entry.content)
143-
) {
144-
const content = entry.content.map((c) => c.text ?? '').join(' ');
145-
146-
if (
147-
content.includes(CONTROL_MARKER) ||
148-
content.includes("'agent-instructions/control_instructions.md'") ||
149-
content.includes('"agent-instructions/control_instructions.md"')
150-
) {
151-
return 'control';
152-
}
153-
154-
if (
155-
content.includes(MCP_MARKER) ||
156-
content.includes("'agent-instructions/mcp_instructions.md'") ||
157-
content.includes('"agent-instructions/mcp_instructions.md"')
158-
) {
159-
return 'mcp';
160-
}
161-
}
162-
}
163-
164-
return undefined;
111+
return identifyTestType(this.uiMessages, this.apiHistory);
165112
}
166113

167114
/**
@@ -175,52 +122,12 @@ class ChatProcessor {
175122
return Promise.resolve([]);
176123
}
177124

178-
const taskBoundaries: TaskSegment[] = [];
179-
const taskNumbers = new Set<number>();
180-
181-
// Find task boundaries in UI messages
182-
for (let i = 0; i < this.uiMessages.length; i++) {
183-
const message = this.uiMessages[i];
184-
185-
if (message.type === 'say' && message.say === 'text' && message.text) {
186-
let taskNumber: number | undefined;
187-
let messageTestType = testType;
188-
189-
// Only look for task starts in specific instruction formats
190-
const taskStartMatch = message.text.match(
191-
/Complete Task (\d+) using the (?:commands|tools|functions)/i,
192-
);
193-
if (taskStartMatch?.[1]) {
194-
taskNumber = parseInt(taskStartMatch[1], 10);
195-
196-
// Verify this is actually a task instruction by checking for instruction file references
197-
if (message.text.includes('control_instructions.md')) {
198-
messageTestType = 'control';
199-
} else if (message.text.includes('mcp_instructions.md')) {
200-
messageTestType = 'mcp';
201-
} else {
202-
// Not a real task instruction
203-
taskNumber = undefined;
204-
}
205-
}
206-
207-
if (taskNumber && !taskNumbers.has(taskNumber)) {
208-
taskNumbers.add(taskNumber);
209-
210-
taskBoundaries.push({
211-
taskNumber,
212-
directoryId: this.directoryId,
213-
startIndex: i,
214-
startTime: message.ts as number,
215-
apiCalls: [],
216-
userMessages: [],
217-
endIndex: null,
218-
endTime: null,
219-
testType: messageTestType,
220-
});
221-
}
222-
}
223-
}
125+
// Find task boundaries
126+
const taskBoundaries = findTaskBoundaries(
127+
this.uiMessages,
128+
this.directoryId,
129+
testType,
130+
);
224131

225132
// If no task boundaries found, return empty array
226133
if (taskBoundaries.length === 0) {
@@ -229,48 +136,18 @@ class ChatProcessor {
229136
}
230137

231138
// Get the last valid message timestamp
232-
let lastValidMessageTs: number | undefined;
233-
for (let i = this.uiMessages.length - 1; i >= 0; i--) {
234-
const message = this.uiMessages[i];
235-
// Skip resume_completed_task messages as they can appear much later
236-
if (
237-
message?.ts &&
238-
typeof message.ts === 'number' &&
239-
!(message.type === 'ask' && message.ask === 'resume_completed_task')
240-
) {
241-
lastValidMessageTs = message.ts;
242-
break;
243-
}
244-
}
139+
const lastValidMessageTs = getLastValidMessageTimestamp(this.uiMessages);
245140

246-
// Set end boundaries with validation
247-
for (let i = 0; i < taskBoundaries.length; i++) {
248-
if (i < taskBoundaries.length - 1) {
249-
// For non-last tasks, end at the start of next task
250-
taskBoundaries[i].endIndex = taskBoundaries[i + 1].startIndex - 1;
251-
taskBoundaries[i].endTime = taskBoundaries[i + 1].startTime;
252-
} else {
253-
// For the last task, use the last valid message timestamp
254-
taskBoundaries[i].endIndex = this.uiMessages.length - 1;
255-
taskBoundaries[i].endTime = lastValidMessageTs as number;
256-
}
257-
258-
// Validate endTime is after startTime and within reasonable bounds
259-
if (
260-
!taskBoundaries[i].endTime ||
261-
taskBoundaries[i].endTime! < taskBoundaries[i].startTime ||
262-
(lastValidMessageTs && taskBoundaries[i].endTime! > lastValidMessageTs)
263-
) {
264-
logger.warn(
265-
`Invalid endTime detected for task ${taskBoundaries[i].taskNumber}. Using last valid message timestamp.`,
266-
);
267-
taskBoundaries[i].endTime = lastValidMessageTs as number;
268-
}
269-
}
141+
// Validate and set end boundaries
142+
const validatedBoundaries = validateTaskBoundaries(
143+
taskBoundaries,
144+
lastValidMessageTs,
145+
this.uiMessages.length,
146+
);
270147

271148
// Process task segments in parallel
272149
return Promise.all(
273-
taskBoundaries.map((task) => this.processTaskSegment(task)),
150+
validatedBoundaries.map((task) => this.processTaskSegment(task)),
274151
);
275152
}
276153

@@ -300,78 +177,28 @@ class ChatProcessor {
300177
`Processing ${messages.length} messages for task ${task.taskNumber}`,
301178
);
302179

303-
const TASK_SEGMENT_TEXTS = [
304-
'read_file',
305-
'codebase_search',
306-
'grep_search',
307-
'file_search',
308-
'<function_calls>',
309-
'<fnr>',
310-
];
311-
312180
// Filter messages to include only relevant ones
313-
const relevantMessages = messages.filter((msg) => {
314-
// Include API requests and MCP server requests
315-
if (
316-
msg.type === 'say' &&
317-
(msg.say === 'api_req_started' ||
318-
msg.say === 'mcp_server_request_started')
319-
) {
320-
return true;
321-
}
322-
323-
// Include tool usage messages
324-
if (msg.type === 'say' && msg.say === 'text' && msg.text) {
325-
return TASK_SEGMENT_TEXTS.some((text) => msg.text?.includes(text));
326-
}
327-
328-
// Include user messages
329-
if (
330-
msg.type === 'say' &&
331-
msg.say === 'text' &&
332-
(!msg.from || msg.from === 'user')
333-
) {
334-
return true;
335-
}
336-
337-
// Include all other 'say' messages for completeness
338-
if (msg.type === 'say') {
339-
return true;
340-
}
341-
342-
return false;
343-
});
181+
const relevantMessages = filterRelevantMessages(messages);
344182

345-
// Create a combined task timestamp for filtering API entries
346-
const startBoundary = task.startTime - 60 * 1000; // 1 minute before task start
347-
const endBoundary = (task.endTime as number) + 5 * 60 * 1000; // 5 minutes after task end
183+
// Create timestamp boundaries for filtering API entries
184+
const [startBoundary, endBoundary] = createTimestampBoundaries(
185+
task.startTime,
186+
task.endTime as number,
187+
);
348188

349189
// Filter API entries that fall within this task's time boundaries
350190
const apiEntries = this.apiHistory.filter((entry) => {
351-
const timestamp = this.getTimestampFromApiEntry(entry);
191+
const timestamp = getTimestampFromApiEntry(entry);
352192
return (
353193
timestamp && timestamp >= startBoundary && timestamp <= endBoundary
354194
);
355195
});
356196

357-
// Extract conversation history index from UI messages if available
197+
// Extract conversation history index from UI messages
358198
for (const msg of relevantMessages) {
359-
if (msg.type === 'say' && msg.text) {
360-
try {
361-
// First try to parse JSON to get index from structured data
362-
const jsonData = JSON.parse(msg.text);
363-
if (jsonData.conversationHistoryIndex !== undefined) {
364-
msg.conversationHistoryIndex = jsonData.conversationHistoryIndex;
365-
}
366-
} catch (e) {
367-
// If JSON parsing fails, try regex
368-
const indexMatch = msg.text.match(
369-
/conversationHistoryIndex["\s:]+(\d+)/i,
370-
);
371-
if (indexMatch?.[1]) {
372-
msg.conversationHistoryIndex = parseInt(indexMatch[1], 10);
373-
}
374-
}
199+
const index = extractConversationHistoryIndex(msg);
200+
if (index !== undefined) {
201+
msg.conversationHistoryIndex = index;
375202
}
376203
}
377204

@@ -385,36 +212,6 @@ class ChatProcessor {
385212
messageCount: relevantMessages.length,
386213
};
387214
}
388-
389-
/**
390-
* Extract timestamp from an API entry
391-
* @param {ApiHistoryEntry} entry The API entry
392-
* @returns {number|undefined} Timestamp in milliseconds or undefined if not found
393-
*/
394-
getTimestampFromApiEntry(entry: ApiHistoryEntry): number | undefined {
395-
if (!entry?.content || !Array.isArray(entry.content)) {
396-
return undefined;
397-
}
398-
399-
for (const content of entry.content) {
400-
if (
401-
content.type === 'text' &&
402-
content.text &&
403-
content.text.includes('environment_details') &&
404-
content.text.includes('Current Time')
405-
) {
406-
const match = content.text.match(/Current Time\s+([^<]+)/);
407-
if (match?.[1]) {
408-
const date = new Date(match[1].trim());
409-
if (!Number.isNaN(date.getTime())) {
410-
return date.getTime();
411-
}
412-
}
413-
}
414-
}
415-
416-
return undefined;
417-
}
418215
}
419216

420217
export default ChatProcessor;

0 commit comments

Comments
 (0)