mirror of
https://github.com/QwenLM/qwen-code.git
synced 2025-12-20 08:47:44 +00:00
feature(commands) - Refactor Slash Command + Vision For the Future (#3175)
This commit is contained in:
@@ -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
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user