mirror of
https://github.com/google-gemini/gemini-cli.git
synced 2026-03-14 08:01:02 -07:00
235 lines
6.1 KiB
TypeScript
235 lines
6.1 KiB
TypeScript
/**
|
|
* @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);
|
|
});
|
|
});
|
|
});
|