feature(commands) - Refactor Slash Command + Vision For the Future (#3175)

This commit is contained in:
Abhi
2025-07-07 16:45:44 -04:00
committed by GitHub
parent 6eccb474c7
commit aa10ccba71
26 changed files with 2436 additions and 726 deletions

View File

@@ -19,7 +19,12 @@ import {
import { Config, EditorType, AuthType } from '@google/gemini-cli-core';
import { Part, PartListUnion } from '@google/genai';
import { UseHistoryManagerReturn } from './useHistoryManager.js';
import { HistoryItem, MessageType, StreamingState } from '../types.js';
import {
HistoryItem,
MessageType,
SlashCommandProcessorResult,
StreamingState,
} from '../types.js';
import { Dispatch, SetStateAction } from 'react';
import { LoadedSettings } from '../../config/settings.js';
@@ -360,10 +365,7 @@ describe('useGeminiStream', () => {
onDebugMessage: (message: string) => void;
handleSlashCommand: (
cmd: PartListUnion,
) => Promise<
| import('./slashCommandProcessor.js').SlashCommandActionReturn
| boolean
>;
) => Promise<SlashCommandProcessorResult | false>;
shellModeActive: boolean;
loadedSettings: LoadedSettings;
toolCalls?: TrackedToolCall[]; // Allow passing updated toolCalls
@@ -396,10 +398,7 @@ describe('useGeminiStream', () => {
onDebugMessage: mockOnDebugMessage,
handleSlashCommand: mockHandleSlashCommand as unknown as (
cmd: PartListUnion,
) => Promise<
| import('./slashCommandProcessor.js').SlashCommandActionReturn
| boolean
>,
) => Promise<SlashCommandProcessorResult | false>,
shellModeActive: false,
loadedSettings: mockLoadedSettings,
toolCalls: initialToolCalls,
@@ -966,83 +965,52 @@ describe('useGeminiStream', () => {
});
});
describe('Client-Initiated Tool Calls', () => {
it('should execute a client-initiated tool without sending a response to Gemini', async () => {
const clientToolRequest = {
shouldScheduleTool: true,
describe('Slash Command Handling', () => {
it('should schedule a tool call when the command processor returns a schedule_tool action', async () => {
const clientToolRequest: SlashCommandProcessorResult = {
type: 'schedule_tool',
toolName: 'save_memory',
toolArgs: { fact: 'test fact' },
};
mockHandleSlashCommand.mockResolvedValue(clientToolRequest);
const completedToolCall: TrackedCompletedToolCall = {
request: {
callId: 'client-call-1',
name: clientToolRequest.toolName,
args: clientToolRequest.toolArgs,
isClientInitiated: true,
},
status: 'success',
responseSubmittedToGemini: false,
response: {
callId: 'client-call-1',
responseParts: [{ text: 'Memory saved' }],
resultDisplay: 'Success: Memory saved',
error: undefined,
},
tool: {
name: clientToolRequest.toolName,
description: 'Saves memory',
getDescription: vi.fn(),
} as any,
};
const { result } = renderTestHook();
// Capture the onComplete callback
let capturedOnComplete:
| ((completedTools: TrackedToolCall[]) => Promise<void>)
| null = null;
mockUseReactToolScheduler.mockImplementation((onComplete) => {
capturedOnComplete = onComplete;
return [[], mockScheduleToolCalls, mockMarkToolsAsSubmitted];
});
const { result } = renderHook(() =>
useGeminiStream(
new MockedGeminiClientClass(mockConfig),
[],
mockAddItem,
mockSetShowHelp,
mockConfig,
mockOnDebugMessage,
mockHandleSlashCommand,
false,
() => 'vscode' as EditorType,
() => {},
() => Promise.resolve(),
),
);
// --- User runs the slash command ---
await act(async () => {
await result.current.submitQuery('/memory add "test fact"');
});
// Trigger the onComplete callback with the completed client-initiated tool
await waitFor(() => {
expect(mockScheduleToolCalls).toHaveBeenCalledWith(
[
expect.objectContaining({
name: 'save_memory',
args: { fact: 'test fact' },
isClientInitiated: true,
}),
],
expect.any(AbortSignal),
);
expect(mockSendMessageStream).not.toHaveBeenCalled();
});
});
it('should stop processing and not call Gemini when a command is handled without a tool call', async () => {
const uiOnlyCommandResult: SlashCommandProcessorResult = {
type: 'handled',
};
mockHandleSlashCommand.mockResolvedValue(uiOnlyCommandResult);
const { result } = renderTestHook();
await act(async () => {
if (capturedOnComplete) {
await capturedOnComplete([completedToolCall]);
}
await result.current.submitQuery('/help');
});
// --- Assert the outcome ---
await waitFor(() => {
// The tool should be marked as submitted locally
expect(mockMarkToolsAsSubmitted).toHaveBeenCalledWith([
'client-call-1',
]);
// Crucially, no message should be sent to the Gemini API
expect(mockSendMessageStream).not.toHaveBeenCalled();
expect(mockHandleSlashCommand).toHaveBeenCalledWith('/help');
expect(mockScheduleToolCalls).not.toHaveBeenCalled();
expect(mockSendMessageStream).not.toHaveBeenCalled(); // No LLM call made
});
});
});