mirror of
https://github.com/google-gemini/gemini-cli.git
synced 2026-03-25 05:21:03 -07:00
refactor(core): delete obsolete coreToolScheduler (#23502)
This commit is contained in:
@@ -24,14 +24,16 @@ import {
|
||||
} from '@google/genai';
|
||||
import * as codeAssist from './codeAssist.js';
|
||||
import type { CodeAssistServer } from './server.js';
|
||||
import type { CompletedToolCall } from '../core/coreToolScheduler.js';
|
||||
import type {
|
||||
CompletedToolCall,
|
||||
ToolCallResponseInfo,
|
||||
} from '../scheduler/types.js';
|
||||
import {
|
||||
ToolConfirmationOutcome,
|
||||
type AnyDeclarativeTool,
|
||||
type AnyToolInvocation,
|
||||
} from '../tools/tools.js';
|
||||
import type { Config } from '../config/config.js';
|
||||
import type { ToolCallResponseInfo } from '../scheduler/types.js';
|
||||
|
||||
function createMockResponse(
|
||||
candidates: GenerateContentResponse['candidates'] = [],
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@@ -1,313 +0,0 @@
|
||||
/**
|
||||
* @license
|
||||
* Copyright 2025 Google LLC
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
import { describe, it, expect, vi } from 'vitest';
|
||||
import { CoreToolScheduler } from './coreToolScheduler.js';
|
||||
import type { ToolCall, ErroredToolCall } from '../scheduler/types.js';
|
||||
import type { Config, ToolRegistry, AgentLoopContext } from '../index.js';
|
||||
import {
|
||||
ApprovalMode,
|
||||
DEFAULT_TRUNCATE_TOOL_OUTPUT_THRESHOLD,
|
||||
} from '../index.js';
|
||||
import { createMockMessageBus } from '../test-utils/mock-message-bus.js';
|
||||
import { MockTool } from '../test-utils/mock-tool.js';
|
||||
import { DEFAULT_GEMINI_MODEL } from '../config/models.js';
|
||||
import type { PolicyEngine } from '../policy/policy-engine.js';
|
||||
import type { HookSystem } from '../hooks/hookSystem.js';
|
||||
import { BeforeToolHookOutput } from '../hooks/types.js';
|
||||
|
||||
function createMockConfig(overrides: Partial<Config> = {}): Config {
|
||||
const defaultToolRegistry = {
|
||||
getTool: () => undefined,
|
||||
getToolByName: () => undefined,
|
||||
getFunctionDeclarations: () => [],
|
||||
tools: new Map(),
|
||||
discovery: {},
|
||||
registerTool: () => {},
|
||||
getToolByDisplayName: () => undefined,
|
||||
getTools: () => [],
|
||||
discoverTools: async () => {},
|
||||
getAllTools: () => [],
|
||||
getToolsByServer: () => [],
|
||||
getExperiments: () => {},
|
||||
} as unknown as ToolRegistry;
|
||||
|
||||
const baseConfig = {
|
||||
getSessionId: () => 'test-session-id',
|
||||
getUsageStatisticsEnabled: () => true,
|
||||
getDebugMode: () => false,
|
||||
isInteractive: () => true,
|
||||
getApprovalMode: () => ApprovalMode.DEFAULT,
|
||||
setApprovalMode: () => {},
|
||||
getAllowedTools: () => [],
|
||||
getContentGeneratorConfig: () => ({
|
||||
model: 'test-model',
|
||||
authType: 'oauth-personal',
|
||||
}),
|
||||
getShellExecutionConfig: () => ({
|
||||
terminalWidth: 90,
|
||||
terminalHeight: 30,
|
||||
sanitizationConfig: {
|
||||
enableEnvironmentVariableRedaction: true,
|
||||
allowedEnvironmentVariables: [],
|
||||
blockedEnvironmentVariables: [],
|
||||
},
|
||||
}),
|
||||
storage: {
|
||||
getProjectTempDir: () => '/tmp',
|
||||
},
|
||||
getTruncateToolOutputThreshold: () =>
|
||||
DEFAULT_TRUNCATE_TOOL_OUTPUT_THRESHOLD,
|
||||
getTruncateToolOutputLines: () => 1000,
|
||||
getToolRegistry: () => defaultToolRegistry,
|
||||
getActiveModel: () => DEFAULT_GEMINI_MODEL,
|
||||
getGeminiClient: () => null,
|
||||
getMessageBus: () => createMockMessageBus(),
|
||||
getEnableHooks: () => true, // Enabled for these tests
|
||||
getExperiments: () => {},
|
||||
getPolicyEngine: () =>
|
||||
({
|
||||
check: async () => ({ decision: 'allow' }), // Default allow for hook tests
|
||||
}) as unknown as PolicyEngine,
|
||||
} as unknown as Config;
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/no-misused-spread
|
||||
return { ...baseConfig, ...overrides } as Config;
|
||||
}
|
||||
|
||||
describe('CoreToolScheduler Hooks', () => {
|
||||
it('should stop execution if BeforeTool hook requests stop', async () => {
|
||||
const executeFn = vi.fn().mockResolvedValue({
|
||||
llmContent: 'Tool executed',
|
||||
returnDisplay: 'Tool executed',
|
||||
});
|
||||
const mockTool = new MockTool({ name: 'mockTool', execute: executeFn });
|
||||
|
||||
const toolRegistry = {
|
||||
getTool: () => mockTool,
|
||||
getToolByName: () => mockTool,
|
||||
getFunctionDeclarations: () => [],
|
||||
tools: new Map(),
|
||||
discovery: {},
|
||||
registerTool: () => {},
|
||||
getToolByDisplayName: () => mockTool,
|
||||
getTools: () => [],
|
||||
discoverTools: async () => {},
|
||||
getAllTools: () => [],
|
||||
getToolsByServer: () => [],
|
||||
} as unknown as ToolRegistry;
|
||||
|
||||
const mockMessageBus = createMockMessageBus();
|
||||
const mockHookSystem = {
|
||||
fireBeforeToolEvent: vi.fn().mockResolvedValue({
|
||||
shouldStopExecution: () => true,
|
||||
getEffectiveReason: () => 'Hook stopped execution',
|
||||
getBlockingError: () => ({ blocked: false }),
|
||||
isAskDecision: () => false,
|
||||
}),
|
||||
} as unknown as HookSystem;
|
||||
|
||||
const mockConfig = createMockConfig({
|
||||
getToolRegistry: () => toolRegistry,
|
||||
getMessageBus: () => mockMessageBus,
|
||||
getHookSystem: () => mockHookSystem,
|
||||
getApprovalMode: () => ApprovalMode.YOLO,
|
||||
});
|
||||
|
||||
const onAllToolCallsComplete = vi.fn();
|
||||
const scheduler = new CoreToolScheduler({
|
||||
context: {
|
||||
config: mockConfig,
|
||||
messageBus: mockMessageBus,
|
||||
toolRegistry,
|
||||
} as unknown as AgentLoopContext,
|
||||
onAllToolCallsComplete,
|
||||
getPreferredEditor: () => 'vscode',
|
||||
});
|
||||
|
||||
const request = {
|
||||
callId: '1',
|
||||
name: 'mockTool',
|
||||
args: {},
|
||||
isClientInitiated: false,
|
||||
prompt_id: 'prompt-1',
|
||||
};
|
||||
|
||||
await scheduler.schedule([request], new AbortController().signal);
|
||||
|
||||
await vi.waitFor(() => {
|
||||
expect(onAllToolCallsComplete).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
const completedCalls = onAllToolCallsComplete.mock
|
||||
.calls[0][0] as ToolCall[];
|
||||
expect(completedCalls[0].status).toBe('error');
|
||||
const erroredCall = completedCalls[0] as ErroredToolCall;
|
||||
|
||||
// Check error type/message
|
||||
expect(erroredCall.response.error?.message).toContain(
|
||||
'Hook stopped execution',
|
||||
);
|
||||
expect(executeFn).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('should block tool execution if BeforeTool hook requests block', async () => {
|
||||
const executeFn = vi.fn();
|
||||
const mockTool = new MockTool({ name: 'mockTool', execute: executeFn });
|
||||
|
||||
const toolRegistry = {
|
||||
getTool: () => mockTool,
|
||||
getToolByName: () => mockTool,
|
||||
getFunctionDeclarations: () => [],
|
||||
tools: new Map(),
|
||||
discovery: {},
|
||||
registerTool: () => {},
|
||||
getToolByDisplayName: () => mockTool,
|
||||
getTools: () => [],
|
||||
discoverTools: async () => {},
|
||||
getAllTools: () => [],
|
||||
getToolsByServer: () => [],
|
||||
} as unknown as ToolRegistry;
|
||||
|
||||
const mockMessageBus = createMockMessageBus();
|
||||
const mockHookSystem = {
|
||||
fireBeforeToolEvent: vi.fn().mockResolvedValue({
|
||||
shouldStopExecution: () => false,
|
||||
getBlockingError: () => ({
|
||||
blocked: true,
|
||||
reason: 'Hook blocked execution',
|
||||
}),
|
||||
isAskDecision: () => false,
|
||||
}),
|
||||
} as unknown as HookSystem;
|
||||
|
||||
const mockConfig = createMockConfig({
|
||||
getToolRegistry: () => toolRegistry,
|
||||
getMessageBus: () => mockMessageBus,
|
||||
getHookSystem: () => mockHookSystem,
|
||||
getApprovalMode: () => ApprovalMode.YOLO,
|
||||
});
|
||||
|
||||
const onAllToolCallsComplete = vi.fn();
|
||||
const scheduler = new CoreToolScheduler({
|
||||
context: {
|
||||
config: mockConfig,
|
||||
messageBus: mockMessageBus,
|
||||
toolRegistry,
|
||||
} as unknown as AgentLoopContext,
|
||||
onAllToolCallsComplete,
|
||||
getPreferredEditor: () => 'vscode',
|
||||
});
|
||||
|
||||
const request = {
|
||||
callId: '1',
|
||||
name: 'mockTool',
|
||||
args: {},
|
||||
isClientInitiated: false,
|
||||
prompt_id: 'prompt-1',
|
||||
};
|
||||
|
||||
await scheduler.schedule([request], new AbortController().signal);
|
||||
|
||||
await vi.waitFor(() => {
|
||||
expect(onAllToolCallsComplete).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
const completedCalls = onAllToolCallsComplete.mock
|
||||
.calls[0][0] as ToolCall[];
|
||||
expect(completedCalls[0].status).toBe('error');
|
||||
const erroredCall = completedCalls[0] as ErroredToolCall;
|
||||
expect(erroredCall.response.error?.message).toContain(
|
||||
'Hook blocked execution',
|
||||
);
|
||||
expect(executeFn).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('should update tool input if BeforeTool hook provides modified input', async () => {
|
||||
const executeFn = vi.fn().mockResolvedValue({
|
||||
llmContent: 'Tool executed',
|
||||
returnDisplay: 'Tool executed',
|
||||
});
|
||||
const mockTool = new MockTool({ name: 'mockTool', execute: executeFn });
|
||||
|
||||
const toolRegistry = {
|
||||
getTool: () => mockTool,
|
||||
getToolByName: () => mockTool,
|
||||
getFunctionDeclarations: () => [],
|
||||
tools: new Map(),
|
||||
discovery: {},
|
||||
registerTool: () => {},
|
||||
getToolByDisplayName: () => mockTool,
|
||||
getTools: () => [],
|
||||
discoverTools: async () => {},
|
||||
getAllTools: () => [],
|
||||
getToolsByServer: () => [],
|
||||
} as unknown as ToolRegistry;
|
||||
|
||||
const mockMessageBus = createMockMessageBus();
|
||||
const mockBeforeOutput = new BeforeToolHookOutput({
|
||||
continue: true,
|
||||
hookSpecificOutput: {
|
||||
hookEventName: 'BeforeTool',
|
||||
tool_input: { newParam: 'modifiedValue' },
|
||||
},
|
||||
});
|
||||
|
||||
const mockHookSystem = {
|
||||
fireBeforeToolEvent: vi.fn().mockResolvedValue(mockBeforeOutput),
|
||||
fireAfterToolEvent: vi.fn(),
|
||||
} as unknown as HookSystem;
|
||||
|
||||
const mockConfig = createMockConfig({
|
||||
getToolRegistry: () => toolRegistry,
|
||||
getMessageBus: () => mockMessageBus,
|
||||
getHookSystem: () => mockHookSystem,
|
||||
getApprovalMode: () => ApprovalMode.YOLO,
|
||||
});
|
||||
|
||||
const onAllToolCallsComplete = vi.fn();
|
||||
const scheduler = new CoreToolScheduler({
|
||||
context: {
|
||||
config: mockConfig,
|
||||
messageBus: mockMessageBus,
|
||||
toolRegistry,
|
||||
} as unknown as AgentLoopContext,
|
||||
onAllToolCallsComplete,
|
||||
getPreferredEditor: () => 'vscode',
|
||||
});
|
||||
|
||||
const request = {
|
||||
callId: '1',
|
||||
name: 'mockTool',
|
||||
args: { originalParam: 'originalValue' },
|
||||
isClientInitiated: false,
|
||||
prompt_id: 'prompt-1',
|
||||
};
|
||||
|
||||
await scheduler.schedule([request], new AbortController().signal);
|
||||
|
||||
await vi.waitFor(() => {
|
||||
expect(onAllToolCallsComplete).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
const completedCalls = onAllToolCallsComplete.mock
|
||||
.calls[0][0] as ToolCall[];
|
||||
expect(completedCalls[0].status).toBe('success');
|
||||
|
||||
// Verify execute was called with modified args
|
||||
expect(executeFn).toHaveBeenCalledWith(
|
||||
{ newParam: 'modifiedValue' },
|
||||
expect.anything(),
|
||||
undefined,
|
||||
expect.anything(),
|
||||
);
|
||||
|
||||
// Verify call request args were updated in the completion report
|
||||
expect(completedCalls[0].request.args).toEqual({
|
||||
newParam: 'modifiedValue',
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -34,11 +34,9 @@ import {
|
||||
ROOT_SCHEDULER_ID,
|
||||
type ValidatingToolCall,
|
||||
type ToolCallRequestInfo,
|
||||
type CompletedToolCall,
|
||||
} from './types.js';
|
||||
import type { PolicyEngine } from '../policy/policy-engine.js';
|
||||
import { DiscoveredMCPTool } from '../tools/mcp-tool.js';
|
||||
import { CoreToolScheduler } from '../core/coreToolScheduler.js';
|
||||
import { Scheduler } from './scheduler.js';
|
||||
import { ToolErrorType } from '../tools/tool-error.js';
|
||||
import type { ToolRegistry } from '../tools/tool-registry.js';
|
||||
@@ -840,61 +838,32 @@ describe('Plan Mode Denial Consistency', () => {
|
||||
vi.clearAllMocks();
|
||||
});
|
||||
|
||||
describe.each([
|
||||
{ enableEventDrivenScheduler: false, name: 'Legacy CoreToolScheduler' },
|
||||
{ enableEventDrivenScheduler: true, name: 'Event-Driven Scheduler' },
|
||||
])('$name', ({ enableEventDrivenScheduler }) => {
|
||||
it('should return the correct Plan Mode denial message when policy denies execution', async () => {
|
||||
let resultMessage: string | undefined;
|
||||
let resultErrorType: ToolErrorType | undefined;
|
||||
it('should return the correct Plan Mode denial message when policy denies execution', async () => {
|
||||
let resultMessage: string | undefined;
|
||||
let resultErrorType: ToolErrorType | undefined;
|
||||
|
||||
const signal = new AbortController().signal;
|
||||
const signal = new AbortController().signal;
|
||||
|
||||
if (enableEventDrivenScheduler) {
|
||||
const scheduler = new Scheduler({
|
||||
context: {
|
||||
config: mockConfig,
|
||||
messageBus: mockMessageBus,
|
||||
toolRegistry: mockToolRegistry,
|
||||
} as unknown as AgentLoopContext,
|
||||
getPreferredEditor: () => undefined,
|
||||
schedulerId: ROOT_SCHEDULER_ID,
|
||||
});
|
||||
|
||||
const results = await scheduler.schedule(req, signal);
|
||||
const result = results[0];
|
||||
|
||||
expect(result.status).toBe('error');
|
||||
if (result.status === 'error') {
|
||||
resultMessage = result.response.error?.message;
|
||||
resultErrorType = result.response.errorType;
|
||||
}
|
||||
} else {
|
||||
let capturedCalls: CompletedToolCall[] = [];
|
||||
const scheduler = new CoreToolScheduler({
|
||||
context: {
|
||||
config: mockConfig,
|
||||
messageBus: mockMessageBus,
|
||||
toolRegistry: mockToolRegistry,
|
||||
} as unknown as AgentLoopContext,
|
||||
getPreferredEditor: () => undefined,
|
||||
onAllToolCallsComplete: async (calls) => {
|
||||
capturedCalls = calls;
|
||||
},
|
||||
});
|
||||
|
||||
await scheduler.schedule(req, signal);
|
||||
|
||||
expect(capturedCalls.length).toBeGreaterThan(0);
|
||||
const call = capturedCalls[0];
|
||||
if (call.status === 'error') {
|
||||
resultMessage = call.response.error?.message;
|
||||
resultErrorType = call.response.errorType;
|
||||
}
|
||||
}
|
||||
|
||||
expect(resultMessage).toBe('Tool execution denied by policy.');
|
||||
expect(resultErrorType).toBe(ToolErrorType.POLICY_VIOLATION);
|
||||
const scheduler = new Scheduler({
|
||||
context: {
|
||||
config: mockConfig,
|
||||
messageBus: mockMessageBus,
|
||||
toolRegistry: mockToolRegistry,
|
||||
} as unknown as AgentLoopContext,
|
||||
getPreferredEditor: () => undefined,
|
||||
schedulerId: ROOT_SCHEDULER_ID,
|
||||
});
|
||||
|
||||
const results = await scheduler.schedule(req, signal);
|
||||
const result = results[0];
|
||||
|
||||
expect(result.status).toBe('error');
|
||||
if (result.status === 'error') {
|
||||
resultMessage = result.response.error?.message;
|
||||
resultErrorType = result.response.errorType;
|
||||
}
|
||||
|
||||
expect(resultMessage).toBe('Tool execution denied by policy.');
|
||||
expect(resultErrorType).toBe(ToolErrorType.POLICY_VIOLATION);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -25,7 +25,7 @@ import {
|
||||
AuthType,
|
||||
type ContentGeneratorConfig,
|
||||
} from '../../core/contentGenerator.js';
|
||||
import type { SuccessfulToolCall } from '../../core/coreToolScheduler.js';
|
||||
import type { SuccessfulToolCall } from '../../scheduler/types.js';
|
||||
import type { ConfigParameters } from '../../config/config.js';
|
||||
import { EventMetadataKey } from './event-metadata-key.js';
|
||||
import { makeFakeConfig } from '../../test-utils/config.js';
|
||||
|
||||
@@ -20,7 +20,7 @@ import type {
|
||||
CompletedToolCall,
|
||||
ErroredToolCall,
|
||||
SuccessfulToolCall,
|
||||
} from '../core/coreToolScheduler.js';
|
||||
} from '../scheduler/types.js';
|
||||
import { ToolErrorType } from '../tools/tool-error.js';
|
||||
import { ToolConfirmationOutcome } from '../tools/tools.js';
|
||||
import { MockTool } from '../test-utils/mock-tool.js';
|
||||
|
||||
Reference in New Issue
Block a user