perf: optimize chat recording and add universal tool output truncation

This commit is contained in:
mkorwel
2026-02-09 12:41:08 -06:00
parent 81ccd80c6d
commit fca27bd4a4
2 changed files with 23 additions and 12 deletions
+1 -2
View File
@@ -17,7 +17,6 @@ import {
logToolOutputTruncated, logToolOutputTruncated,
runInDevTraceSpan, runInDevTraceSpan,
} from '../index.js'; } from '../index.js';
import { SHELL_TOOL_NAME } from '../tools/tool-names.js';
import { ShellToolInvocation } from '../tools/shell.js'; import { ShellToolInvocation } from '../tools/shell.js';
import { executeToolWithHooks } from '../core/coreToolHookTriggers.js'; import { executeToolWithHooks } from '../core/coreToolHookTriggers.js';
import { import {
@@ -204,7 +203,7 @@ export class ToolExecutor {
const toolName = call.request.name; const toolName = call.request.name;
const callId = call.request.callId; const callId = call.request.callId;
if (typeof content === 'string' && toolName === SHELL_TOOL_NAME) { if (typeof content === 'string') {
const threshold = this.config.getTruncateToolOutputThreshold(); const threshold = this.config.getTruncateToolOutputThreshold();
if (threshold > 0 && content.length > threshold) { if (threshold > 0 && content.length > threshold) {
@@ -125,6 +125,7 @@ export interface ResumedSessionData {
*/ */
export class ChatRecordingService { export class ChatRecordingService {
private conversationFile: string | null = null; private conversationFile: string | null = null;
private conversation: ConversationRecord | null = null;
private cachedLastConvData: string | null = null; private cachedLastConvData: string | null = null;
private sessionId: string; private sessionId: string;
private projectHash: string; private projectHash: string;
@@ -148,6 +149,7 @@ export class ChatRecordingService {
// Resume from existing session // Resume from existing session
this.conversationFile = resumedSessionData.filePath; this.conversationFile = resumedSessionData.filePath;
this.sessionId = resumedSessionData.conversation.sessionId; this.sessionId = resumedSessionData.conversation.sessionId;
this.conversation = resumedSessionData.conversation;
// Update the session ID in the existing file // Update the session ID in the existing file
this.updateConversation((conversation) => { this.updateConversation((conversation) => {
@@ -174,13 +176,15 @@ export class ChatRecordingService {
)}.json`; )}.json`;
this.conversationFile = path.join(chatsDir, filename); this.conversationFile = path.join(chatsDir, filename);
this.writeConversation({ const initialConversation: ConversationRecord = {
sessionId: this.sessionId, sessionId: this.sessionId,
projectHash: this.projectHash, projectHash: this.projectHash,
startTime: new Date().toISOString(), startTime: new Date().toISOString(),
lastUpdated: new Date().toISOString(), lastUpdated: new Date().toISOString(),
messages: [], messages: [],
}); };
this.conversation = initialConversation;
this.writeConversation(initialConversation, { allowEmpty: true });
} }
// Clear any queued data since this is a fresh start // Clear any queued data since this is a fresh start
@@ -416,9 +420,12 @@ export class ChatRecordingService {
* Loads up the conversation record from disk. * Loads up the conversation record from disk.
*/ */
private readConversation(): ConversationRecord { private readConversation(): ConversationRecord {
if (this.conversation) return this.conversation;
try { try {
this.cachedLastConvData = fs.readFileSync(this.conversationFile!, 'utf8'); this.cachedLastConvData = fs.readFileSync(this.conversationFile!, 'utf8');
return JSON.parse(this.cachedLastConvData); this.conversation = JSON.parse(this.cachedLastConvData);
return this.conversation!;
} catch (error) { } catch (error) {
if ((error as NodeJS.ErrnoException).code !== 'ENOENT') { if ((error as NodeJS.ErrnoException).code !== 'ENOENT') {
debugLogger.error('Error reading conversation file.', error); debugLogger.error('Error reading conversation file.', error);
@@ -426,13 +433,14 @@ export class ChatRecordingService {
} }
// Placeholder empty conversation if file doesn't exist. // Placeholder empty conversation if file doesn't exist.
return { this.conversation = {
sessionId: this.sessionId, sessionId: this.sessionId,
projectHash: this.projectHash, projectHash: this.projectHash,
startTime: new Date().toISOString(), startTime: new Date().toISOString(),
lastUpdated: new Date().toISOString(), lastUpdated: new Date().toISOString(),
messages: [], messages: [],
}; };
return this.conversation;
} }
} }
@@ -446,14 +454,18 @@ export class ChatRecordingService {
try { try {
if (!this.conversationFile) return; if (!this.conversationFile) return;
// Don't write the file yet until there's at least one message. // Don't write the file yet until there's at least one message.
if (conversation.messages.length === 0 && !allowEmpty) return; if ((conversation.messages?.length ?? 0) === 0 && !allowEmpty) return;
// Only write the file if this change would change the file. // Avoid redundant stringification by checking if the content changed first.
if (this.cachedLastConvData !== JSON.stringify(conversation, null, 2)) { // We use the cached string for comparison to avoid a new stringification
// when nothing has changed.
const currentContent = JSON.stringify(conversation, null, 2);
if (this.cachedLastConvData !== currentContent) {
// Only update the timestamp and re-stringify if something actually changed.
conversation.lastUpdated = new Date().toISOString(); conversation.lastUpdated = new Date().toISOString();
const newContent = JSON.stringify(conversation, null, 2); const finalContent = JSON.stringify(conversation, null, 2);
this.cachedLastConvData = newContent; this.cachedLastConvData = finalContent;
fs.writeFileSync(this.conversationFile, newContent); fs.writeFileSync(this.conversationFile, finalContent);
} }
} catch (error) { } catch (error) {
// Handle disk full (ENOSPC) gracefully - disable recording but allow conversation to continue // Handle disk full (ENOSPC) gracefully - disable recording but allow conversation to continue