/** * @license * Copyright 2025 Google LLC * SPDX-License-Identifier: Apache-2.0 */ import { expect, describe, it } from 'vitest'; import { doesToolInvocationMatch, getToolSuggestion, shouldHideToolCall, } from './tool-utils.js'; import { ReadFileTool, ApprovalMode, CoreToolCallStatus, ASK_USER_DISPLAY_NAME, WRITE_FILE_DISPLAY_NAME, EDIT_DISPLAY_NAME, READ_FILE_DISPLAY_NAME, type AnyToolInvocation, type Config, } from '../index.js'; import { createMockMessageBus } from '../test-utils/mock-message-bus.js'; describe('shouldHideToolCall', () => { it.each([ { status: CoreToolCallStatus.Scheduled, hasResult: true, shouldHide: true, }, { status: CoreToolCallStatus.Executing, hasResult: true, shouldHide: true, }, { status: CoreToolCallStatus.AwaitingApproval, hasResult: true, shouldHide: true, }, { status: CoreToolCallStatus.Validating, hasResult: true, shouldHide: true, }, { status: CoreToolCallStatus.Success, hasResult: true, shouldHide: false, }, { status: CoreToolCallStatus.Error, hasResult: false, shouldHide: true, }, { status: CoreToolCallStatus.Error, hasResult: true, shouldHide: false, }, ])( 'AskUser: status=$status, hasResult=$hasResult -> hide=$shouldHide', ({ status, hasResult, shouldHide }) => { expect( shouldHideToolCall({ displayName: ASK_USER_DISPLAY_NAME, status, hasResultDisplay: hasResult, }), ).toBe(shouldHide); }, ); it.each([ { name: WRITE_FILE_DISPLAY_NAME, mode: ApprovalMode.PLAN, visible: false, }, { name: EDIT_DISPLAY_NAME, mode: ApprovalMode.PLAN, visible: false }, { name: WRITE_FILE_DISPLAY_NAME, mode: ApprovalMode.DEFAULT, visible: true, }, { name: READ_FILE_DISPLAY_NAME, mode: ApprovalMode.PLAN, visible: true }, ])( 'Plan Mode: tool=$name, mode=$mode -> visible=$visible', ({ name, mode, visible }) => { expect( shouldHideToolCall({ displayName: name, status: CoreToolCallStatus.Success, approvalMode: mode, hasResultDisplay: true, }), ).toBe(!visible); }, ); it('hides tool calls with a parentCallId', () => { expect( shouldHideToolCall({ displayName: 'any_tool', status: CoreToolCallStatus.Success, hasResultDisplay: true, parentCallId: 'some-parent', }), ).toBe(true); }); }); describe('getToolSuggestion', () => { it('should suggest the top N closest tool names for a typo', () => { const allToolNames = ['list_files', 'read_file', 'write_file']; // Test that the right tool is selected, with only 1 result, for typos const misspelledTool = getToolSuggestion('list_fils', allToolNames, 1); expect(misspelledTool).toBe(' Did you mean "list_files"?'); // Test that the right tool is selected, with only 1 result, for prefixes const prefixedTool = getToolSuggestion( 'github.list_files', allToolNames, 1, ); expect(prefixedTool).toBe(' Did you mean "list_files"?'); // Test that the right tool is first const suggestionMultiple = getToolSuggestion('list_fils', allToolNames); expect(suggestionMultiple).toBe( ' Did you mean one of: "list_files", "read_file", "write_file"?', ); }); }); describe('doesToolInvocationMatch', () => { it('should not match a partial command prefix', () => { const invocation = { params: { command: 'git commitsomething' }, } as AnyToolInvocation; const patterns = ['ShellTool(git commit)']; const result = doesToolInvocationMatch( 'run_shell_command', invocation, patterns, ); expect(result).toBe(false); }); it('should match an exact command', () => { const invocation = { params: { command: 'git status' }, } as AnyToolInvocation; const patterns = ['ShellTool(git status)']; const result = doesToolInvocationMatch( 'run_shell_command', invocation, patterns, ); expect(result).toBe(true); }); it('should match a command with an alias', () => { const invocation = { params: { command: 'wc -l' }, } as AnyToolInvocation; const patterns = ['ShellTool(wc)']; const result = doesToolInvocationMatch('ShellTool', invocation, patterns); expect(result).toBe(true); }); it('should match a command that is a prefix', () => { const invocation = { params: { command: 'git status -v' }, } as AnyToolInvocation; const patterns = ['ShellTool(git status)']; const result = doesToolInvocationMatch( 'run_shell_command', invocation, patterns, ); expect(result).toBe(true); }); describe('for non-shell tools', () => { const mockConfig = { getTargetDir: () => '/tmp', getFileFilteringOptions: () => ({}), } as unknown as Config; const readFileTool = new ReadFileTool(mockConfig, createMockMessageBus()); const invocation = { params: { file: 'test.txt' }, } as AnyToolInvocation; it('should match by tool name', () => { const patterns = ['read_file']; const result = doesToolInvocationMatch( readFileTool, invocation, patterns, ); expect(result).toBe(true); }); it('should match by tool class name', () => { const patterns = ['ReadFileTool']; const result = doesToolInvocationMatch( readFileTool, invocation, patterns, ); expect(result).toBe(true); }); it('should not match if neither name is in the patterns', () => { const patterns = ['some_other_tool', 'AnotherToolClass']; const result = doesToolInvocationMatch( readFileTool, invocation, patterns, ); expect(result).toBe(false); }); it('should match by tool name when passed as a string', () => { const patterns = ['read_file']; const result = doesToolInvocationMatch('read_file', invocation, patterns); expect(result).toBe(true); }); }); });