mirror of
https://github.com/google-gemini/gemini-cli.git
synced 2026-03-12 23:21:27 -07:00
feat(core): experimental in-progress steering hints (2 of 2) (#19307)
This commit is contained in:
@@ -4,10 +4,11 @@
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
import { describe, it, afterEach } from 'vitest';
|
||||
import { describe, it, afterEach, expect } from 'vitest';
|
||||
import { AppRig } from './AppRig.js';
|
||||
import path from 'node:path';
|
||||
import { fileURLToPath } from 'node:url';
|
||||
import { debugLogger } from '@google/gemini-cli-core';
|
||||
|
||||
const __dirname = path.dirname(fileURLToPath(import.meta.url));
|
||||
|
||||
@@ -18,6 +19,47 @@ describe('AppRig', () => {
|
||||
await rig?.unmount();
|
||||
});
|
||||
|
||||
it('should handle deterministic tool turns with breakpoints', async () => {
|
||||
const fakeResponsesPath = path.join(
|
||||
__dirname,
|
||||
'fixtures',
|
||||
'steering.responses',
|
||||
);
|
||||
rig = new AppRig({
|
||||
fakeResponsesPath,
|
||||
configOverrides: { modelSteering: true },
|
||||
});
|
||||
await rig.initialize();
|
||||
rig.render();
|
||||
await rig.waitForIdle();
|
||||
|
||||
// Set breakpoints on the canonical tool names
|
||||
rig.setBreakpoint('list_directory');
|
||||
rig.setBreakpoint('read_file');
|
||||
|
||||
// Start a task
|
||||
debugLogger.log('[Test] Sending message: Start long task');
|
||||
await rig.sendMessage('Start long task');
|
||||
|
||||
// Wait for the first breakpoint (list_directory)
|
||||
const pending1 = await rig.waitForPendingConfirmation('list_directory');
|
||||
expect(pending1.toolName).toBe('list_directory');
|
||||
|
||||
// Injected a hint
|
||||
await rig.addUserHint('focus on .txt');
|
||||
|
||||
// Resolve and wait for the NEXT breakpoint (read_file)
|
||||
// resolveTool will automatically remove the breakpoint policy for list_directory
|
||||
await rig.resolveTool('list_directory');
|
||||
|
||||
const pending2 = await rig.waitForPendingConfirmation('read_file');
|
||||
expect(pending2.toolName).toBe('read_file');
|
||||
|
||||
// Resolve and finish. Also removes read_file breakpoint.
|
||||
await rig.resolveTool('read_file');
|
||||
await rig.waitForOutput('Task complete.', 100000);
|
||||
});
|
||||
|
||||
it('should render the app and handle a simple message', async () => {
|
||||
const fakeResponsesPath = path.join(
|
||||
__dirname,
|
||||
|
||||
@@ -74,6 +74,20 @@ class MockExtensionManager extends ExtensionLoader {
|
||||
setRequestSetting = vi.fn();
|
||||
}
|
||||
|
||||
// Mock GeminiRespondingSpinner to disable animations (avoiding 'act()' warnings) without triggering screen reader mode.
|
||||
vi.mock('../ui/components/GeminiRespondingSpinner.js', async () => {
|
||||
const React = await import('react');
|
||||
const { Text } = await import('ink');
|
||||
return {
|
||||
GeminiSpinner: () => React.createElement(Text, null, '...'),
|
||||
GeminiRespondingSpinner: ({
|
||||
nonRespondingDisplay,
|
||||
}: {
|
||||
nonRespondingDisplay: string;
|
||||
}) => React.createElement(Text, null, nonRespondingDisplay || '...'),
|
||||
};
|
||||
});
|
||||
|
||||
export interface AppRigOptions {
|
||||
fakeResponsesPath?: string;
|
||||
terminalWidth?: number;
|
||||
@@ -449,12 +463,11 @@ export class AppRig {
|
||||
this.lastAwaitedConfirmation = undefined;
|
||||
}
|
||||
|
||||
async addUserHint(_hint: string) {
|
||||
async addUserHint(hint: string) {
|
||||
if (!this.config) throw new Error('AppRig not initialized');
|
||||
// TODO(joshualitt): Land hints.
|
||||
// await act(async () => {
|
||||
// this.config!.addUserHint(hint);
|
||||
// });
|
||||
await act(async () => {
|
||||
this.config!.userHintService.addUserHint(hint);
|
||||
});
|
||||
}
|
||||
|
||||
getConfig(): Config {
|
||||
|
||||
4
packages/cli/src/test-utils/fixtures/steering.responses
Normal file
4
packages/cli/src/test-utils/fixtures/steering.responses
Normal file
@@ -0,0 +1,4 @@
|
||||
{"method":"generateContentStream","response":[{"candidates":[{"content":{"role":"model","parts":[{"text":"Starting a long task. First, I'll list the files."},{"functionCall":{"name":"list_directory","args":{"dir_path":"."}}}]},"finishReason":"STOP"}]}]}
|
||||
{"method":"generateContent","response":{"candidates":[{"content":{"role":"model","parts":[{"text":"ACK: I will focus on .txt files now."}]},"finishReason":"STOP"}]}}
|
||||
{"method":"generateContentStream","response":[{"candidates":[{"content":{"role":"model","parts":[{"text":"I see the files. Since you want me to focus on .txt files, I will read file1.txt."},{"functionCall":{"name":"read_file","args":{"file_path":"file1.txt"}}}]},"finishReason":"STOP"}]}]}
|
||||
{"method":"generateContentStream","response":[{"candidates":[{"content":{"role":"model","parts":[{"text":"I have read file1.txt. Task complete."}]},"finishReason":"STOP"}]}]}
|
||||
@@ -166,6 +166,8 @@ const baseMockUiState = {
|
||||
proQuotaRequest: null,
|
||||
validationRequest: null,
|
||||
},
|
||||
hintMode: false,
|
||||
hintBuffer: '',
|
||||
};
|
||||
|
||||
export const mockAppState: AppState = {
|
||||
@@ -219,6 +221,10 @@ const mockUIActions: UIActions = {
|
||||
setActiveBackgroundShellPid: vi.fn(),
|
||||
setIsBackgroundShellListOpen: vi.fn(),
|
||||
setAuthContext: vi.fn(),
|
||||
onHintInput: vi.fn(),
|
||||
onHintBackspace: vi.fn(),
|
||||
onHintClear: vi.fn(),
|
||||
onHintSubmit: vi.fn(),
|
||||
handleRestart: vi.fn(),
|
||||
handleNewAgentsSelect: vi.fn(),
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user