import type { ToolResultBlockParam } from '@anthropic-ai/sdk/resources/index.mjs' import * as React from 'react' import { extractTag } from 'src/utils/messages.js' import { FallbackToolUseErrorMessage } from 'src/components/FallbackToolUseErrorMessage.js' import { MessageResponse } from 'src/components/MessageResponse.js' import { Text } from '@anthropic/ink' import { FilePathLink } from 'src/components/FilePathLink.js' import { FILE_NOT_FOUND_CWD_NOTE, getDisplayPath } from 'src/utils/file.js' import { formatFileSize } from 'src/utils/format.js' import { getPlansDirectory } from 'src/utils/plans.js' import { getTaskOutputDir } from 'src/utils/task/diskOutput.js' import type { Input, Output } from './FileReadTool.js' /** * Check if a file path is an agent output file and extract the task ID. * Agent output files follow the pattern: {projectTempDir}/tasks/{taskId}.output */ function getAgentOutputTaskId(filePath: string): string | null { const prefix = `${getTaskOutputDir()}/` const suffix = '.output' if (filePath.startsWith(prefix) && filePath.endsWith(suffix)) { const taskId = filePath.slice(prefix.length, -suffix.length) // Validate it looks like a task ID (alphanumeric, reasonable length) if ( taskId.length > 0 && taskId.length <= 20 && /^[a-zA-Z0-9_-]+$/.test(taskId) ) { return taskId } } return null } export function renderToolUseMessage( { file_path, offset, limit, pages }: Partial, { verbose }: { verbose: boolean }, ): React.ReactNode { if (!file_path) { return null } // For agent output files, return empty string so no parentheses are shown // The task ID is displayed separately by AssistantToolUseMessage if (getAgentOutputTaskId(file_path)) { return '' } const displayPath = verbose ? file_path : getDisplayPath(file_path) if (pages) { return ( <> {displayPath} {` · pages ${pages}`} ) } if (verbose && (offset || limit)) { const startLine = offset ?? 1 const lineRange = limit ? `lines ${startLine}-${startLine + limit - 1}` : `from line ${startLine}` return ( <> {displayPath} {` · ${lineRange}`} ) } return {displayPath} } export function renderToolUseTag({ file_path, }: Partial): React.ReactNode { const agentTaskId = file_path ? getAgentOutputTaskId(file_path) : null // Show agent task ID for Read tool when reading agent output if (!agentTaskId) { return null } return {agentTaskId} } export function renderToolResultMessage(output: Output): React.ReactNode { // TODO: Render recursively switch (output.type) { case 'image': { const { originalSize } = output.file const formattedSize = formatFileSize(originalSize) return ( Read image ({formattedSize}) ) } case 'notebook': { const { cells } = output.file if (!cells || cells.length < 1) { return No cells found in notebook } return ( Read {cells.length} cells ) } case 'pdf': { const { originalSize } = output.file const formattedSize = formatFileSize(originalSize) return ( Read PDF ({formattedSize}) ) } case 'parts': { return ( Read {output.file.count}{' '} {output.file.count === 1 ? 'page' : 'pages'} ( {formatFileSize(output.file.originalSize)}) ) } case 'text': { const { numLines } = output.file return ( Read {numLines}{' '} {numLines === 1 ? 'line' : 'lines'} ) } case 'file_unchanged': { return ( Unchanged since last read ) } } } export function renderToolUseErrorMessage( result: ToolResultBlockParam['content'], { verbose }: { verbose: boolean }, ): React.ReactNode { if (!verbose && typeof result === 'string') { // FileReadTool throws from call() so errors lack wrapping — // check the raw string directly for the cwd note marker. if (result.includes(FILE_NOT_FOUND_CWD_NOTE)) { return ( File not found ) } if (extractTag(result, 'tool_use_error')) { return ( Error reading file ) } } return } export function userFacingName(input: Partial | undefined): string { if (input?.file_path?.startsWith(getPlansDirectory())) { return 'Reading Plan' } if (input?.file_path && getAgentOutputTaskId(input.file_path)) { return 'Read agent output' } return 'Read' } export function getToolUseSummary( input: Partial | undefined, ): string | null { if (!input?.file_path) { return null } // For agent output files, just show the task ID const agentTaskId = getAgentOutputTaskId(input.file_path) if (agentTaskId) { return agentTaskId } return getDisplayPath(input.file_path) }