claude-code-best/src/utils/computerUse/toolRendering.tsx
2026-04-06 23:56:45 +08:00

158 lines
4.6 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

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<string, unknown> & {
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<Partial<Record<string, string>>> = {
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<string, unknown>,
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 (
<MessageResponse height={1}>
<Text dimColor>{summary}</Text>
</MessageResponse>
)
},
}
}