Files
hapi/web/src/components/ToolCard/knownTools.test.tsx
T

358 lines
12 KiB
TypeScript

import { describe, expect, it } from 'vitest'
import { getToolPresentation } from '@/components/ToolCard/knownTools'
describe('getToolPresentation — unknown tool semantic title + subtitle dedup', () => {
it('promotes semantic title "Run shell" when toolName equals input.command (Gemini ACP case)', () => {
const presentation = getToolPresentation({
toolName: 'cat /tmp/hello.txt',
input: { command: 'cat /tmp/hello.txt' },
result: null,
childrenCount: 0,
description: null,
metadata: null,
})
expect(presentation.title).toBe('Run shell')
expect(presentation.subtitle).toBe('cat /tmp/hello.txt')
})
it('promotes semantic title "Read file" when toolName equals input.file_path', () => {
const presentation = getToolPresentation({
toolName: 'README.md',
input: { file_path: 'README.md' },
result: null,
childrenCount: 0,
description: null,
metadata: null,
})
expect(presentation.title).toBe('Read file')
expect(presentation.subtitle).toBe('README.md')
})
it('promotes semantic title "Search" when toolName equals input.pattern', () => {
const presentation = getToolPresentation({
toolName: '*.ts',
input: { pattern: '*.ts' },
result: null,
childrenCount: 0,
description: null,
metadata: null,
})
expect(presentation.title).toBe('Search')
expect(presentation.subtitle).toBe('*.ts')
})
it('keeps the original toolName when subtitle differs (no promotion needed)', () => {
const presentation = getToolPresentation({
toolName: 'run_shell_command',
input: { command: 'ls -la /tmp' },
result: null,
childrenCount: 0,
description: null,
metadata: null,
})
expect(presentation.title).toBe('run_shell_command')
expect(presentation.subtitle).toBe('ls -la /tmp')
})
it('uses input.name as a fallback subtitle for unknown tool cards', () => {
const presentation = getToolPresentation({
toolName: 'Tool',
input: { name: 'Tool 1' },
result: null,
childrenCount: 0,
description: null,
metadata: null,
})
expect(presentation.title).toBe('Tool')
expect(presentation.subtitle).toBe('Tool 1')
})
it('returns null subtitle when no recognized input field is present', () => {
const presentation = getToolPresentation({
toolName: 'mystery_tool',
input: { foo: 'bar' },
result: null,
childrenCount: 0,
description: null,
metadata: null,
})
expect(presentation.title).toBe('mystery_tool')
expect(presentation.subtitle).toBeNull()
})
})
describe('getToolPresentation — Codex agent tools', () => {
it('titles CodexAgent cards from work summary instead of agent id', () => {
const presentation = getToolPresentation({
toolName: 'CodexAgent',
input: {
agentId: 'agent-1234567890',
summary: '检查 Hub Web README',
activity: 'Reading file: README.md',
reasoning_effort: 'medium'
},
result: null,
childrenCount: 0,
description: null,
metadata: null,
})
expect(presentation.title).toBe('Agent: 检查 Hub Web README')
expect(presentation.title).not.toContain('agent-1234567890')
expect(presentation.subtitle).toBe('reasoning medium · Reading file: README.md')
expect(presentation.minimal).toBe(true)
})
it('shows Codex auto-selected effort on CodexAgent cards even before activity is available', () => {
const presentation = getToolPresentation({
toolName: 'CodexAgent',
input: {
summary: 'Inspect package metadata',
reasoning_effort: 'low'
},
result: null,
childrenCount: 0,
description: null,
metadata: null,
})
expect(presentation.title).toBe('Agent: Inspect package metadata')
expect(presentation.subtitle).toBe('reasoning low')
})
it('does not present sub-operation completion as final agent completion while still running', () => {
const presentation = getToolPresentation({
toolName: 'CodexAgent',
input: {
summary: 'Inspect package metadata',
agentStatus: 'running',
activity: 'Command completed: bun test',
reasoning_effort: 'low'
},
result: null,
childrenCount: 0,
description: null,
metadata: null,
})
expect(presentation.subtitle).toBe('reasoning low · Command finished: bun test')
})
it('falls back to prompt-derived CodexAgent titles without exposing agent id', () => {
const presentation = getToolPresentation({
toolName: 'CodexAgent',
input: {
agentId: 'agent-1234567890',
message: 'Fix the reducer for live agent cards.\nDo not revert other changes.'
},
result: null,
childrenCount: 0,
description: null,
metadata: null,
})
expect(presentation.title).toBe('Agent: Fix the reducer for live agent cards.')
expect(presentation.title).not.toContain('agent-1234567890')
})
it('summarizes spawn_agent with the spawned agent id', () => {
const presentation = getToolPresentation({
toolName: 'spawn_agent',
input: {
agent_type: 'worker',
message: 'Implement the parser'
},
result: '{"agent_id":"agent-123","nickname":"Raman"}',
childrenCount: 0,
description: null,
metadata: null,
})
expect(presentation.title).toBe('Spawn worker agent')
expect(presentation.subtitle).toBe('Launched Raman (agent-123)')
expect(presentation.minimal).toBe(true)
})
it('summarizes wait_agent status counts', () => {
const presentation = getToolPresentation({
toolName: 'wait_agent',
input: {
targets: ['a', 'b'],
timeout_ms: 30000
},
result: '{"status":{"a":{"completed":"done"},"b":{"failed":"boom"}},"timed_out":false}',
childrenCount: 0,
description: null,
metadata: null,
})
expect(presentation.title).toBe('Wait for 2 agents')
expect(presentation.subtitle).toBe('1 completed, 1 non-completed')
expect(presentation.minimal).toBe(true)
})
it('does not expose close_agent previous output in the collapsed subtitle', () => {
const presentation = getToolPresentation({
toolName: 'close_agent',
input: {
target: 'agent-123'
},
result: '{"previous_status":{"completed":"hidden child output"}}',
childrenCount: 0,
description: null,
metadata: null,
})
expect(presentation.title).toBe('Close agent')
expect(presentation.subtitle).toBe('Closed (completed)')
expect(presentation.subtitle).not.toContain('hidden child output')
expect(presentation.minimal).toBe(true)
})
it('presents MultiAgent V2 messaging tools by intent', () => {
const message = getToolPresentation({
toolName: 'send_message',
input: { target: '/root/review', message: 'Status?' },
result: '',
childrenCount: 0,
description: null,
metadata: null,
})
const followup = getToolPresentation({
toolName: 'followup_task',
input: { target: '/root/review', message: 'Run tests' },
result: '',
childrenCount: 0,
description: null,
metadata: null,
})
expect(message).toMatchObject({ title: 'Message agent', subtitle: '/root/review', minimal: true })
expect(followup).toMatchObject({ title: 'Follow up agent', subtitle: '/root/review', minimal: true })
})
it('summarizes list_agents and interrupt_agent results', () => {
const list = getToolPresentation({
toolName: 'list_agents',
input: {},
result: JSON.stringify({
agents: [
{ agent_name: '/root/a', agent_status: 'running' },
{ agent_name: '/root/b', agent_status: { completed: 'done' } }
]
}),
childrenCount: 0,
description: null,
metadata: null,
})
const interrupt = getToolPresentation({
toolName: 'interrupt_agent',
input: { target: '/root/a' },
result: '{"previous_status":"running"}',
childrenCount: 0,
description: null,
metadata: null,
})
expect(list).toMatchObject({ title: 'List agents', subtitle: '2 live, 1 running' })
expect(interrupt).toMatchObject({ title: 'Interrupt agent', subtitle: 'Interrupted (running)' })
})
it('uses MultiAgent V2 result fields and status variants', () => {
const spawn = getToolPresentation({
toolName: 'spawn_agent',
input: { task_name: 'review' },
result: JSON.stringify({ task_name: '/root/review', nickname: 'Reviewer' }),
childrenCount: 0,
description: null,
metadata: null,
})
const wait = getToolPresentation({
toolName: 'wait_agent',
input: { timeout_ms: 1000 },
result: JSON.stringify({ message: 'Wait completed.', timed_out: false }),
childrenCount: 0,
description: null,
metadata: null,
})
const list = getToolPresentation({
toolName: 'list_agents',
input: {},
result: JSON.stringify({
agents: [{ agent_name: '/root/review', agent_status: { errored: 'test failed' } }]
}),
childrenCount: 0,
description: null,
metadata: null,
})
expect(spawn.subtitle).toBe('Launched Reviewer (/root/review)')
expect(wait.subtitle).toBe('Wait completed.')
expect(list.subtitle).toBe('1 live agent')
})
})
describe('getToolPresentation — native titles', () => {
it('uses a preserved native title for unknown lowercase tools', () => {
const presentation = getToolPresentation({
toolName: 'bash',
input: { command: 'bun test' },
result: null,
childrenCount: 0,
description: 'Run project tests',
metadata: null,
})
expect(presentation.title).toBe('Run project tests')
expect(presentation.subtitle).toBe('bun test')
})
})
describe('getToolPresentation — request_user_input', () => {
it('uses the question header instead of exposing its protocol id', () => {
const presentation = getToolPresentation({
toolName: 'request_user_input',
input: {
questions: [{
id: '__mcp_url_confirmation',
header: 'Sign in',
question: 'Sign in to continue'
}]
},
result: null,
childrenCount: 0,
description: null,
metadata: null,
})
expect(presentation.title).toBe('Sign in')
expect(presentation.title).not.toContain('__mcp_url_confirmation')
expect(presentation.subtitle).toBe('Sign in to continue')
})
it('falls back to Question rather than exposing an id when no header is present', () => {
const presentation = getToolPresentation({
toolName: 'request_user_input',
input: {
questions: [{
id: '__mcp_form_confirmation',
question: 'Continue?'
}]
},
result: null,
childrenCount: 0,
description: null,
metadata: null,
})
expect(presentation.title).toBe('Question')
expect(presentation.subtitle).toBe('Continue?')
})
})