mirror of
https://github.com/wu736139669/hapi.git
synced 2026-08-06 06:41:56 +00:00
358 lines
12 KiB
TypeScript
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?')
|
|
})
|
|
})
|