import * as React from 'react' import { MessageResponse } from '../../components/MessageResponse.js' import { Text } from '@anthropic/ink' import { truncateToWidth } from '../format.js' import type { MCPToolResult } from '../mcpValidation.js' type CuToolInput = Record & { coordinate?: [number, number] start_coordinate?: [number, number] text?: string apps?: Array<{ displayName?: string }> region?: [number, number, number, number] direction?: string amount?: number duration?: number } function fmtCoord(c: [number, number] | undefined): string { return c ? `(${c[0]}, ${c[1]})` : '' } const RESULT_SUMMARY: Readonly>> = { screenshot: 'Captured', zoom: 'Captured', request_access: 'Access updated', left_click: 'Clicked', right_click: 'Clicked', middle_click: 'Clicked', double_click: 'Clicked', triple_click: 'Clicked', type: 'Typed', key: 'Pressed', hold_key: 'Pressed', scroll: 'Scrolled', left_click_drag: 'Dragged', open_application: 'Opened', } /** * Rendering overrides for `mcp__computer-use__*` tools. Spread into the MCP * tool object in `client.ts` after the default `userFacingName`, so these win. * Mirror of `getClaudeInChromeMCPToolOverrides`. */ export function getComputerUseMCPRenderingOverrides(toolName: string): { userFacingName: () => string renderToolUseMessage: ( input: Record, options: { verbose: boolean }, ) => React.ReactNode renderToolResultMessage: ( output: MCPToolResult, progressMessages: unknown[], options: { verbose: boolean }, ) => React.ReactNode } { return { userFacingName() { return `Computer Use[${toolName}]` }, // AssistantToolUseMessage.tsx contract: null hides the ENTIRE row, '' shows // the tool name without "(args)". Every path below returns '' when there's // nothing to show — never null. renderToolUseMessage(input: CuToolInput) { switch (toolName) { case 'screenshot': case 'left_mouse_down': case 'left_mouse_up': case 'cursor_position': case 'list_granted_applications': case 'read_clipboard': return '' case 'left_click': case 'right_click': case 'middle_click': case 'double_click': case 'triple_click': case 'mouse_move': return fmtCoord(input.coordinate) case 'left_click_drag': return input.start_coordinate ? `${fmtCoord(input.start_coordinate)} → ${fmtCoord(input.coordinate)}` : `to ${fmtCoord(input.coordinate)}` case 'type': return typeof input.text === 'string' ? `"${truncateToWidth(input.text, 40)}"` : '' case 'key': case 'hold_key': return typeof input.text === 'string' ? input.text : '' case 'scroll': return [ input.direction, input.amount && `×${input.amount}`, input.coordinate && `at ${fmtCoord(input.coordinate)}`, ] .filter(Boolean) .join(' ') case 'zoom': { const r = input.region return Array.isArray(r) && r.length === 4 ? `[${r[0]}, ${r[1]}, ${r[2]}, ${r[3]}]` : '' } case 'wait': return typeof input.duration === 'number' ? `${input.duration}s` : '' case 'write_clipboard': return typeof input.text === 'string' ? `"${truncateToWidth(input.text, 40)}"` : '' case 'open_application': return typeof input.bundle_id === 'string' ? String(input.bundle_id) : '' case 'request_access': { const apps = input.apps if (!Array.isArray(apps)) return '' const names = apps .map(a => (typeof a?.displayName === 'string' ? a.displayName : '')) .filter(Boolean) return names.join(', ') } case 'computer_batch': { const actions = input.actions return Array.isArray(actions) ? `${actions.length} actions` : '' } default: return '' } }, renderToolResultMessage(output, _progress, { verbose }) { if (verbose || typeof output !== 'object' || output === null) return null // Non-verbose: one-line dim summary, like Chrome's pattern. const summary = RESULT_SUMMARY[toolName] if (!summary) return null return ( {summary} ) }, } }