diff --git a/packages/cli/src/acp-integration/session/emitters/ToolCallEmitter.test.ts b/packages/cli/src/acp-integration/session/emitters/ToolCallEmitter.test.ts index 6b0e6449e11..6acc3022213 100644 --- a/packages/cli/src/acp-integration/session/emitters/ToolCallEmitter.test.ts +++ b/packages/cli/src/acp-integration/session/emitters/ToolCallEmitter.test.ts @@ -233,6 +233,41 @@ describe('ToolCallEmitter', () => { ); }); + it('should not replay truncated session previews as full diffs', async () => { + await emitter.emitResult({ + toolName: 'edit_file', + callId: 'call-edit', + success: true, + message: [], + resultDisplay: { + fileName: '/test/file.ts', + originalContent: 'old preview', + newContent: 'new preview', + truncatedForSession: true, + fileDiffLength: 200000, + fileDiffTruncated: true, + }, + }); + + expect(sendUpdateSpy).toHaveBeenCalledWith( + expect.objectContaining({ + sessionUpdate: 'tool_call_update', + toolCallId: 'call-edit', + status: 'completed', + content: [ + { + type: 'content', + content: { + type: 'text', + text: 'Full diff omitted from saved session history for /test/file.ts. Original fileDiff length: 200000 chars.', + }, + }, + ], + _meta: { toolName: 'edit_file' }, + }), + ); + }); + it('should transform message parts to content', async () => { await emitter.emitResult({ toolName: 'test_tool', diff --git a/packages/cli/src/acp-integration/session/emitters/ToolCallEmitter.ts b/packages/cli/src/acp-integration/session/emitters/ToolCallEmitter.ts index a84e4f4e7da..92f66ee4740 100644 --- a/packages/cli/src/acp-integration/session/emitters/ToolCallEmitter.ts +++ b/packages/cli/src/acp-integration/session/emitters/ToolCallEmitter.ts @@ -20,6 +20,7 @@ import type { } from '@agentclientprotocol/sdk'; import type { Part } from '@google/genai'; import { ToolNames, Kind } from '@qwen-code/qwen-code-core'; +import { buildTruncatedDiffPreviewText } from '../../../utils/truncatedDiffPreview.js'; /** * Unified tool call event emitter. @@ -273,6 +274,16 @@ export class ToolCallEmitter extends BaseEmitter { // Check if this is a diff display (edit tool result) if ('fileName' in obj && 'newContent' in obj) { + if (obj['truncatedForSession'] === true) { + return { + type: 'content', + content: { + type: 'text', + text: buildTruncatedDiffPreviewText(obj), + }, + }; + } + return { type: 'diff', path: obj['fileName'] as string, diff --git a/packages/cli/src/ui/components/messages/ToolMessage.test.tsx b/packages/cli/src/ui/components/messages/ToolMessage.test.tsx index 568c9c12c13..274b391487b 100644 --- a/packages/cli/src/ui/components/messages/ToolMessage.test.tsx +++ b/packages/cli/src/ui/components/messages/ToolMessage.test.tsx @@ -274,6 +274,27 @@ describe('', () => { expect(lastFrame()).toMatch(/MockDiff:--- a\/file\.txt/); }); + it('renders a saved-session preview notice for truncated diff results', () => { + const diffResult = { + fileDiff: '--- file.txt\n+++ file.txt\n@@ -1 +1 @@\n-omitted\n+preview', + fileName: 'file.txt', + originalContent: 'old preview', + newContent: 'new preview', + truncatedForSession: true, + fileDiffLength: 123456, + fileDiffTruncated: true, + }; + const { lastFrame } = renderWithContext( + , + StreamingState.Idle, + ); + + expect(lastFrame()).toContain( + 'Saved session preview only; full diff omitted from JSONL (123456 chars).', + ); + expect(lastFrame()).toContain('MockDiff:--- file.txt'); + }); + it('renders emphasis correctly', () => { const { lastFrame: highEmphasisFrame } = renderWithContext( , diff --git a/packages/cli/src/ui/components/messages/ToolMessage.tsx b/packages/cli/src/ui/components/messages/ToolMessage.tsx index ebdfed81211..314ded5acdb 100644 --- a/packages/cli/src/ui/components/messages/ToolMessage.tsx +++ b/packages/cli/src/ui/components/messages/ToolMessage.tsx @@ -22,6 +22,7 @@ import type { AnsiOutputDisplay, Config, McpToolProgressData, + FileDiff, } from '@qwen-code/qwen-code-core'; import { AgentExecutionDisplay } from '../subagents/index.js'; import { ToolConfirmationMessage } from './ToolConfirmationMessage.js'; @@ -49,6 +50,14 @@ const DEFAULT_SHELL_OUTPUT_MAX_LINES = 5; // outputs that will get truncated further MaxSizedBox anyway. const MAXIMUM_RESULT_DISPLAY_CHARACTERS = 1000000; export type TextEmphasis = 'high' | 'medium' | 'low'; +type DiffResultDisplay = Pick< + FileDiff, + | 'fileDiff' + | 'fileName' + | 'truncatedForSession' + | 'fileDiffLength' + | 'fileDiffTruncated' +>; function sliceTextForMaxHeight( text: string, @@ -175,7 +184,7 @@ const useResultDisplayRenderer = ( ) { return { type: 'diff', - data: resultDisplay as { fileDiff: string; fileName: string }, + data: resultDisplay as DiffResultDisplay, }; } @@ -378,19 +387,38 @@ const StringResultRenderer: React.FC<{ * Component to render diff results */ const DiffResultRenderer: React.FC<{ - data: { fileDiff: string; fileName: string }; + data: DiffResultDisplay; availableHeight?: number; childWidth: number; settings?: LoadedSettings; -}> = ({ data, availableHeight, childWidth, settings }) => ( - -); +}> = ({ data, availableHeight, childWidth, settings }) => { + const diffHeight = + data.truncatedForSession && availableHeight !== undefined + ? Math.max(1, availableHeight - 1) + : availableHeight; + + return ( + + {data.truncatedForSession && ( + + {data.fileDiffTruncated + ? 'Saved session preview only; full diff omitted from JSONL' + : 'Saved session preview only; full file contents truncated in JSONL'} + {data.fileDiffTruncated && typeof data.fileDiffLength === 'number' + ? ` (${data.fileDiffLength} chars).` + : '.'} + + )} + + + ); +}; export interface ToolMessageProps extends IndividualToolCallDisplay { availableTerminalHeight?: number; diff --git a/packages/cli/src/ui/utils/export/collect.test.ts b/packages/cli/src/ui/utils/export/collect.test.ts new file mode 100644 index 00000000000..0789bc11f56 --- /dev/null +++ b/packages/cli/src/ui/utils/export/collect.test.ts @@ -0,0 +1,89 @@ +/** + * @license + * Copyright 2025 Qwen Team + * SPDX-License-Identifier: Apache-2.0 + */ + +import { describe, expect, it, vi } from 'vitest'; +import type { ChatRecord, Config } from '@qwen-code/qwen-code-core'; +import { collectSessionData } from './collect.js'; + +describe('collectSessionData', () => { + const config = { + getToolRegistry: vi.fn().mockReturnValue({ + getTool: vi.fn().mockReturnValue(null), + }), + } as unknown as Config; + + it('skips line-count fallback for truncated saved-session previews', async () => { + const records: ChatRecord[] = [ + { + uuid: 'assistant-1', + parentUuid: null, + sessionId: 'session-1', + timestamp: '2025-01-01T00:00:00.000Z', + type: 'assistant', + cwd: '', + version: '1.0.0', + message: { + role: 'model', + parts: [ + { + functionCall: { + id: 'call-1', + name: 'edit_file', + args: { file_path: '/test/file.ts' }, + }, + }, + ], + }, + }, + { + uuid: 'tool-1', + parentUuid: 'assistant-1', + sessionId: 'session-1', + timestamp: '2025-01-01T00:00:01.000Z', + type: 'tool_result', + cwd: '', + version: '1.0.0', + message: { + role: 'user', + parts: [ + { + functionResponse: { + id: 'call-1', + name: 'edit_file', + response: { output: 'ok' }, + }, + }, + ], + }, + toolCallResult: { + callId: 'call-1', + resultDisplay: { + fileName: 'file.ts', + fileDiff: + '--- file.ts\n+++ file.ts\n@@ -1,2 +1,2 @@\n-old\n-preview\n+new\n+preview', + originalContent: 'old\npreview', + newContent: 'new\npreview', + truncatedForSession: true, + }, + }, + }, + ]; + + const data = await collectSessionData( + { + sessionId: 'session-1', + startTime: '2025-01-01T00:00:00.000Z', + messages: records, + }, + config, + ); + + expect(data.metadata?.filesWritten).toBe(1); + expect(data.metadata?.uniqueFiles).toEqual(['/test/file.ts']); + expect(data.metadata?.linesAdded).toBe(0); + expect(data.metadata?.linesRemoved).toBe(0); + }); +}); diff --git a/packages/cli/src/ui/utils/export/collect.ts b/packages/cli/src/ui/utils/export/collect.ts index d929e0b41e9..04c374031c3 100644 --- a/packages/cli/src/ui/utils/export/collect.ts +++ b/packages/cli/src/ui/utils/export/collect.ts @@ -171,6 +171,7 @@ function calculateFileStats(records: ChatRecord[]): FileOperationStats { originalContent?: string | null; newContent?: string; diffStat?: { model_added_lines?: number; model_removed_lines?: number }; + truncatedForSession?: boolean; }; // Determine operation type based on content fields @@ -197,7 +198,7 @@ function calculateFileStats(records: ChatRecord[]): FileOperationStats { // Use diffStat if available for accurate counts stats.linesAdded += display.diffStat.model_added_lines ?? 0; stats.linesRemoved += display.diffStat.model_removed_lines ?? 0; - } else { + } else if (!display.truncatedForSession) { // Fallback: count lines in content const oldText = String(display.originalContent ?? ''); const newText = String(display.newContent ?? ''); diff --git a/packages/cli/src/ui/utils/export/normalize.test.ts b/packages/cli/src/ui/utils/export/normalize.test.ts new file mode 100644 index 00000000000..ba2c362d08e --- /dev/null +++ b/packages/cli/src/ui/utils/export/normalize.test.ts @@ -0,0 +1,72 @@ +/** + * @license + * Copyright 2025 Qwen Team + * SPDX-License-Identifier: Apache-2.0 + */ + +import { describe, expect, it, vi } from 'vitest'; +import type { ChatRecord, Config } from '@qwen-code/qwen-code-core'; +import { normalizeSessionData } from './normalize.js'; + +describe('normalizeSessionData', () => { + const config = { + getToolRegistry: vi.fn().mockReturnValue(undefined), + } as unknown as Config; + + it('does not export truncated saved-session previews as full diffs', () => { + const record: ChatRecord = { + uuid: 'tool-1', + parentUuid: null, + sessionId: 'session-1', + timestamp: '2025-01-01T00:00:00.000Z', + type: 'tool_result', + cwd: '', + version: '1.0.0', + message: { + role: 'user', + parts: [ + { + functionResponse: { + id: 'call-1', + name: 'edit_file', + response: { output: 'ok' }, + }, + }, + ], + }, + toolCallResult: { + callId: 'call-1', + resultDisplay: { + fileName: '/test/file.ts', + fileDiff: + '--- /test/file.ts\n+++ /test/file.ts\n@@ -1 +1 @@\n-omitted\n+preview', + originalContent: 'old preview', + newContent: 'new preview', + truncatedForSession: true, + fileDiffLength: 200000, + fileDiffTruncated: true, + }, + }, + }; + + const normalized = normalizeSessionData( + { + sessionId: 'session-1', + startTime: '2025-01-01T00:00:00.000Z', + messages: [], + }, + [record], + config, + ); + + expect(normalized.messages[0].toolCall?.content).toEqual([ + { + type: 'content', + content: { + type: 'text', + text: 'Full diff omitted from saved session history for /test/file.ts. Original fileDiff length: 200000 chars.', + }, + }, + ]); + }); +}); diff --git a/packages/cli/src/ui/utils/export/normalize.ts b/packages/cli/src/ui/utils/export/normalize.ts index 99ab62c329d..44a90418b88 100644 --- a/packages/cli/src/ui/utils/export/normalize.ts +++ b/packages/cli/src/ui/utils/export/normalize.ts @@ -7,6 +7,7 @@ import type { Part } from '@google/genai'; import { ToolNames } from '@qwen-code/qwen-code-core'; import type { ChatRecord, Config, Kind } from '@qwen-code/qwen-code-core'; +import { buildTruncatedDiffPreviewText } from '../../../utils/truncatedDiffPreview.js'; import type { ExportMessage, ExportSessionData } from './types.js'; /** @@ -283,6 +284,18 @@ function extractDiffContent( const display = resultDisplay as Record; if ('fileName' in display && 'newContent' in display) { + if (display['truncatedForSession'] === true) { + return [ + { + type: 'content', + content: { + type: 'text', + text: buildTruncatedDiffPreviewText(display), + }, + }, + ]; + } + return [ { type: 'diff', diff --git a/packages/cli/src/utils/truncatedDiffPreview.ts b/packages/cli/src/utils/truncatedDiffPreview.ts new file mode 100644 index 00000000000..7b2510dd341 --- /dev/null +++ b/packages/cli/src/utils/truncatedDiffPreview.ts @@ -0,0 +1,24 @@ +/** + * @license + * Copyright 2025 Qwen Team + * SPDX-License-Identifier: Apache-2.0 + */ + +export function buildTruncatedDiffPreviewText( + display: Record, +): string { + const fileName = + typeof display['fileName'] === 'string' + ? display['fileName'] + : 'the edited file'; + const fileDiffLength = + typeof display['fileDiffLength'] === 'number' + ? ` Original fileDiff length: ${display['fileDiffLength']} chars.` + : ''; + + if (display['fileDiffTruncated'] === true) { + return `Full diff omitted from saved session history for ${fileName}.${fileDiffLength}`; + } + + return `Saved session preview only for ${fileName}; full original and new file contents are unavailable.`; +} diff --git a/packages/core/src/services/chatRecordingService.test.ts b/packages/core/src/services/chatRecordingService.test.ts index 15173c32bc9..2bbc6143e23 100644 --- a/packages/core/src/services/chatRecordingService.test.ts +++ b/packages/core/src/services/chatRecordingService.test.ts @@ -17,6 +17,7 @@ import { } from './chatRecordingService.js'; import * as jsonl from '../utils/jsonl-utils.js'; import type { Part } from '@google/genai'; +import type { FileDiff } from '../tools/tools.js'; vi.mock('node:path'); vi.mock('node:child_process'); @@ -290,6 +291,170 @@ describe('ChatRecordingService', () => { expect(record.toolCallResult?.callId).toBe('call-1'); }); + it('should keep small file diff resultDisplay unchanged', async () => { + const toolResultParts: Part[] = [ + { + functionResponse: { + id: 'call-1', + name: 'edit', + response: { output: 'ok' }, + }, + }, + ]; + const resultDisplay: FileDiff = { + fileName: 'file.txt', + fileDiff: '--- file.txt\n+++ file.txt\n@@ -1 +1 @@\n-old\n+new', + originalContent: 'old', + newContent: 'new', + diffStat: { + model_added_lines: 1, + model_removed_lines: 1, + model_added_chars: 3, + model_removed_chars: 3, + user_added_lines: 0, + user_removed_lines: 0, + user_added_chars: 0, + user_removed_chars: 0, + }, + }; + const metadata = { + callId: 'call-1', + status: 'success' as const, + responseParts: toolResultParts, + resultDisplay, + error: undefined, + errorType: undefined, + }; + + chatRecordingService.recordToolResult(toolResultParts, metadata); + await chatRecordingService.flush(); + + const record = vi.mocked(jsonl.writeLine).mock.calls[0][1] as ChatRecord; + + expect(record.toolCallResult?.resultDisplay).toBe(resultDisplay); + expect( + (record.toolCallResult?.resultDisplay as FileDiff).truncatedForSession, + ).toBeUndefined(); + }); + + it('should shrink large file diff resultDisplay without mutating input', async () => { + const toolResultParts: Part[] = [ + { + functionResponse: { + id: 'call-1', + name: 'write_file', + response: { output: 'ok' }, + }, + }, + ]; + const largeDiff = 'd'.repeat(70_000); + const largeOriginal = 'a'.repeat(20_000); + const largeNew = 'b'.repeat(20_000); + const resultDisplay: FileDiff = { + fileName: 'large.txt', + fileDiff: largeDiff, + originalContent: largeOriginal, + newContent: largeNew, + diffStat: { + model_added_lines: 1, + model_removed_lines: 1, + model_added_chars: largeNew.length, + model_removed_chars: largeOriginal.length, + user_added_lines: 0, + user_removed_lines: 0, + user_added_chars: 0, + user_removed_chars: 0, + }, + }; + const metadata = { + callId: 'call-1', + status: 'success' as const, + responseParts: toolResultParts, + resultDisplay, + error: undefined, + errorType: undefined, + }; + + chatRecordingService.recordToolResult(toolResultParts, metadata); + await chatRecordingService.flush(); + + const record = vi.mocked(jsonl.writeLine).mock.calls[0][1] as ChatRecord; + const savedDisplay = record.toolCallResult?.resultDisplay as FileDiff; + + expect(savedDisplay).not.toBe(resultDisplay); + expect(savedDisplay.truncatedForSession).toBe(true); + expect(savedDisplay.fileDiffLength).toBe(largeDiff.length); + expect(savedDisplay.originalContentLength).toBe(largeOriginal.length); + expect(savedDisplay.newContentLength).toBe(largeNew.length); + expect(savedDisplay.fileDiffTruncated).toBe(true); + expect(savedDisplay.originalContentTruncated).toBe(true); + expect(savedDisplay.newContentTruncated).toBe(true); + expect(savedDisplay.fileDiff).toContain( + 'Full diff omitted from saved session history', + ); + expect(savedDisplay.fileDiff).not.toBe(largeDiff); + expect(savedDisplay.originalContent?.length).toBeLessThanOrEqual(16_000); + expect(savedDisplay.originalContent).toContain( + 'truncated for saved session preview', + ); + expect(savedDisplay.newContent.length).toBeLessThanOrEqual(16_000); + expect(savedDisplay.newContent).toContain( + 'truncated for saved session preview', + ); + expect(savedDisplay.diffStat).toEqual(resultDisplay.diffStat); + + expect(resultDisplay.fileDiff).toBe(largeDiff); + expect(resultDisplay.originalContent).toBe(largeOriginal); + expect(resultDisplay.newContent).toBe(largeNew); + expect(resultDisplay.truncatedForSession).toBeUndefined(); + }); + + it('should continue stripping nested tool calls from task execution results', async () => { + const toolResultParts: Part[] = [ + { + functionResponse: { + id: 'call-1', + name: 'task', + response: { output: 'ok' }, + }, + }, + ]; + const metadata = { + callId: 'call-1', + status: 'success' as const, + responseParts: toolResultParts, + resultDisplay: { + type: 'task_execution' as const, + subagentName: 'Task', + taskDescription: 'Run task', + taskPrompt: 'Run task', + status: 'completed' as const, + result: 'done', + toolCalls: [ + { + callId: 'nested-call', + name: 'read_file', + status: 'success' as const, + args: {}, + result: 'nested result', + }, + ], + }, + error: undefined, + errorType: undefined, + }; + + chatRecordingService.recordToolResult(toolResultParts, metadata); + await chatRecordingService.flush(); + + const record = vi.mocked(jsonl.writeLine).mock.calls[0][1] as ChatRecord; + + expect(record.toolCallResult?.resultDisplay).toMatchObject({ + type: 'task_execution', + toolCalls: [], + }); + }); + it('should chain tool result correctly with parentUuid', async () => { chatRecordingService.recordUserMessage([{ text: 'Hello' }]); chatRecordingService.recordAssistantTurn({ diff --git a/packages/core/src/services/chatRecordingService.ts b/packages/core/src/services/chatRecordingService.ts index 35be0ed5f00..2404983728f 100644 --- a/packages/core/src/services/chatRecordingService.ts +++ b/packages/core/src/services/chatRecordingService.ts @@ -25,7 +25,7 @@ import type { ToolCallResponseInfo, } from '../core/turn.js'; import type { Status } from '../core/coreToolScheduler.js'; -import type { AgentResultDisplay } from '../tools/tools.js'; +import type { AgentResultDisplay, FileDiff } from '../tools/tools.js'; import type { UiEvent } from '../telemetry/uiTelemetry.js'; const debugLogger = createDebugLogger('CHAT_RECORDING'); @@ -36,6 +36,132 @@ const debugLogger = createDebugLogger('CHAT_RECORDING'); * retrying across turns. */ const AUTO_TITLE_ATTEMPT_CAP = 3; +const SESSION_FILE_DIFF_AGGREGATE_CHAR_LIMIT = 100_000; +const SESSION_FILE_DIFF_CHAR_LIMIT = 50_000; +const SESSION_FILE_CONTENT_CHAR_LIMIT = 16_000; + +function isFileDiffDisplay(resultDisplay: unknown): resultDisplay is FileDiff { + if ( + typeof resultDisplay !== 'object' || + resultDisplay === null || + !('fileDiff' in resultDisplay) || + !('fileName' in resultDisplay) || + !('originalContent' in resultDisplay) || + !('newContent' in resultDisplay) + ) { + return false; + } + + const display = resultDisplay as Record; + const originalContent = display['originalContent']; + return ( + typeof display['fileDiff'] === 'string' && + typeof display['fileName'] === 'string' && + typeof display['newContent'] === 'string' && + (originalContent === null || typeof originalContent === 'string') + ); +} + +function stringLength(value: string | null | undefined): number { + return typeof value === 'string' ? value.length : 0; +} + +function truncateMiddleForSession(value: string, limit: number): string { + if (value.length <= limit) { + return value; + } + + const marker = `\n[... truncated for saved session preview; original length: ${value.length} characters ...]\n`; + const contentBudget = Math.max(0, limit - marker.length); + const headLength = Math.ceil(contentBudget * 0.6); + const tailLength = contentBudget - headLength; + + return ( + value.slice(0, headLength) + + marker + + (tailLength > 0 ? value.slice(value.length - tailLength) : '') + ); +} + +function buildSyntheticDiffPreview(display: FileDiff): string { + const originalLength = stringLength(display.originalContent); + return [ + `--- ${display.fileName}`, + `+++ ${display.fileName}`, + '@@ -1 +1 @@', + `-Full diff omitted from saved session history; original fileDiff length: ${display.fileDiff.length} characters.`, + `+Saved session preview only; originalContent length: ${originalLength} characters, newContent length: ${display.newContent.length} characters.`, + ].join('\n'); +} + +function sanitizeFileDiffForRecording(display: FileDiff): FileDiff { + const fileDiffLength = display.fileDiff.length; + const originalContentLength = stringLength(display.originalContent); + const newContentLength = display.newContent.length; + const aggregateLength = + fileDiffLength + originalContentLength + newContentLength; + + const fileDiffTruncated = fileDiffLength > SESSION_FILE_DIFF_CHAR_LIMIT; + const originalContentTruncated = + originalContentLength > SESSION_FILE_CONTENT_CHAR_LIMIT; + const newContentTruncated = + newContentLength > SESSION_FILE_CONTENT_CHAR_LIMIT; + + if ( + aggregateLength <= SESSION_FILE_DIFF_AGGREGATE_CHAR_LIMIT && + !fileDiffTruncated && + !originalContentTruncated && + !newContentTruncated + ) { + return display; + } + + return { + ...display, + fileDiff: fileDiffTruncated + ? buildSyntheticDiffPreview(display) + : display.fileDiff, + originalContent: + display.originalContent !== null && originalContentTruncated + ? truncateMiddleForSession( + display.originalContent, + SESSION_FILE_CONTENT_CHAR_LIMIT, + ) + : display.originalContent, + newContent: newContentTruncated + ? truncateMiddleForSession( + display.newContent, + SESSION_FILE_CONTENT_CHAR_LIMIT, + ) + : display.newContent, + truncatedForSession: true, + fileDiffLength, + originalContentLength, + newContentLength, + fileDiffTruncated, + originalContentTruncated, + newContentTruncated, + }; +} + +export function sanitizeToolCallResultForRecording< + T extends Partial, +>(toolCallResult: T): T { + const resultDisplay = toolCallResult.resultDisplay; + if (!isFileDiffDisplay(resultDisplay)) { + return toolCallResult; + } + + const sanitizedResultDisplay = sanitizeFileDiffForRecording(resultDisplay); + if (sanitizedResultDisplay === resultDisplay) { + return toolCallResult; + } + + return { + ...toolCallResult, + resultDisplay: sanitizedResultDisplay, + } as T; +} /** * Users who don't want the fast model silently generating titles can opt @@ -712,23 +838,27 @@ export class ChatRecordingService { }; if (toolCallResult) { + const recordingToolCallResult = + sanitizeToolCallResultForRecording(toolCallResult); + // special case for task executions - we don't want to record the tool calls if ( - typeof toolCallResult.resultDisplay === 'object' && - toolCallResult.resultDisplay !== null && - 'type' in toolCallResult.resultDisplay && - toolCallResult.resultDisplay.type === 'task_execution' + typeof recordingToolCallResult.resultDisplay === 'object' && + recordingToolCallResult.resultDisplay !== null && + 'type' in recordingToolCallResult.resultDisplay && + recordingToolCallResult.resultDisplay.type === 'task_execution' ) { - const taskResult = toolCallResult.resultDisplay as AgentResultDisplay; + const taskResult = + recordingToolCallResult.resultDisplay as AgentResultDisplay; record.toolCallResult = { - ...toolCallResult, + ...recordingToolCallResult, resultDisplay: { ...taskResult, toolCalls: [], }, }; } else { - record.toolCallResult = toolCallResult; + record.toolCallResult = recordingToolCallResult; } } diff --git a/packages/core/src/tools/tools.ts b/packages/core/src/tools/tools.ts index 65ec2fcef27..04f3a055cd4 100644 --- a/packages/core/src/tools/tools.ts +++ b/packages/core/src/tools/tools.ts @@ -561,6 +561,13 @@ export interface FileDiff { originalContent: string | null; newContent: string; diffStat?: DiffStat; + truncatedForSession?: boolean; + fileDiffLength?: number; + originalContentLength?: number; + newContentLength?: number; + fileDiffTruncated?: boolean; + originalContentTruncated?: boolean; + newContentTruncated?: boolean; } export interface DiffStat {