mirror of
https://github.com/google-gemini/gemini-cli.git
synced 2026-05-12 21:03:05 -07:00
chore(core): delete legacy nonInteractiveToolExecutor (#17573)
This commit is contained in:
@@ -1,386 +0,0 @@
|
|||||||
/**
|
|
||||||
* @license
|
|
||||||
* Copyright 2025 Google LLC
|
|
||||||
* SPDX-License-Identifier: Apache-2.0
|
|
||||||
*/
|
|
||||||
|
|
||||||
import { describe, it, expect, vi, beforeEach } from 'vitest';
|
|
||||||
import type { Mock } from 'vitest';
|
|
||||||
import { executeToolCall } from './nonInteractiveToolExecutor.js';
|
|
||||||
import type {
|
|
||||||
ToolRegistry,
|
|
||||||
ToolCallRequestInfo,
|
|
||||||
ToolResult,
|
|
||||||
Config,
|
|
||||||
} from '../index.js';
|
|
||||||
import {
|
|
||||||
DEFAULT_TRUNCATE_TOOL_OUTPUT_LINES,
|
|
||||||
DEFAULT_TRUNCATE_TOOL_OUTPUT_THRESHOLD,
|
|
||||||
ToolErrorType,
|
|
||||||
ApprovalMode,
|
|
||||||
HookSystem,
|
|
||||||
PREVIEW_GEMINI_MODEL,
|
|
||||||
PolicyDecision,
|
|
||||||
} from '../index.js';
|
|
||||||
import type { Part } from '@google/genai';
|
|
||||||
import { MockTool } from '../test-utils/mock-tool.js';
|
|
||||||
import { createMockMessageBus } from '../test-utils/mock-message-bus.js';
|
|
||||||
|
|
||||||
describe('executeToolCall', () => {
|
|
||||||
let mockToolRegistry: ToolRegistry;
|
|
||||||
let mockTool: MockTool;
|
|
||||||
let executeFn: Mock;
|
|
||||||
let abortController: AbortController;
|
|
||||||
let mockConfig: Config;
|
|
||||||
|
|
||||||
beforeEach(() => {
|
|
||||||
executeFn = vi.fn();
|
|
||||||
mockTool = new MockTool({ name: 'testTool', execute: executeFn });
|
|
||||||
|
|
||||||
mockToolRegistry = {
|
|
||||||
getTool: vi.fn(),
|
|
||||||
getAllToolNames: vi.fn(),
|
|
||||||
} as unknown as ToolRegistry;
|
|
||||||
|
|
||||||
mockConfig = {
|
|
||||||
getToolRegistry: () => mockToolRegistry,
|
|
||||||
getApprovalMode: () => ApprovalMode.DEFAULT,
|
|
||||||
getAllowedTools: () => [],
|
|
||||||
getSessionId: () => 'test-session-id',
|
|
||||||
getUsageStatisticsEnabled: () => true,
|
|
||||||
getDebugMode: () => false,
|
|
||||||
getContentGeneratorConfig: () => ({
|
|
||||||
model: 'test-model',
|
|
||||||
authType: 'oauth-personal',
|
|
||||||
}),
|
|
||||||
getShellExecutionConfig: () => ({
|
|
||||||
terminalWidth: 90,
|
|
||||||
terminalHeight: 30,
|
|
||||||
}),
|
|
||||||
storage: {
|
|
||||||
getProjectTempDir: () => '/tmp',
|
|
||||||
},
|
|
||||||
getTruncateToolOutputThreshold: () =>
|
|
||||||
DEFAULT_TRUNCATE_TOOL_OUTPUT_THRESHOLD,
|
|
||||||
getTruncateToolOutputLines: () => DEFAULT_TRUNCATE_TOOL_OUTPUT_LINES,
|
|
||||||
getActiveModel: () => PREVIEW_GEMINI_MODEL,
|
|
||||||
getGeminiClient: () => null, // No client needed for these tests
|
|
||||||
getMessageBus: () => null,
|
|
||||||
getPolicyEngine: () => ({
|
|
||||||
check: async () => ({ decision: PolicyDecision.ALLOW }),
|
|
||||||
}),
|
|
||||||
isInteractive: () => false,
|
|
||||||
getExperiments: () => {},
|
|
||||||
getEnableHooks: () => false,
|
|
||||||
} as unknown as Config;
|
|
||||||
|
|
||||||
// Use proper MessageBus mocking for Phase 3 preparation
|
|
||||||
const mockMessageBus = createMockMessageBus();
|
|
||||||
mockConfig.getMessageBus = vi.fn().mockReturnValue(mockMessageBus);
|
|
||||||
mockConfig.getHookSystem = vi
|
|
||||||
.fn()
|
|
||||||
.mockReturnValue(new HookSystem(mockConfig));
|
|
||||||
abortController = new AbortController();
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should execute a tool successfully', async () => {
|
|
||||||
const request: ToolCallRequestInfo = {
|
|
||||||
callId: 'call1',
|
|
||||||
name: 'testTool',
|
|
||||||
args: { param1: 'value1' },
|
|
||||||
isClientInitiated: false,
|
|
||||||
prompt_id: 'prompt-id-1',
|
|
||||||
};
|
|
||||||
const toolResult: ToolResult = {
|
|
||||||
llmContent: 'Tool executed successfully',
|
|
||||||
returnDisplay: 'Success!',
|
|
||||||
};
|
|
||||||
vi.mocked(mockToolRegistry.getTool).mockReturnValue(mockTool);
|
|
||||||
executeFn.mockResolvedValue(toolResult);
|
|
||||||
|
|
||||||
const { response } = await executeToolCall(
|
|
||||||
mockConfig,
|
|
||||||
request,
|
|
||||||
abortController.signal,
|
|
||||||
);
|
|
||||||
|
|
||||||
expect(mockToolRegistry.getTool).toHaveBeenCalledWith('testTool');
|
|
||||||
expect(executeFn).toHaveBeenCalledWith(request.args);
|
|
||||||
expect(response).toStrictEqual({
|
|
||||||
callId: 'call1',
|
|
||||||
error: undefined,
|
|
||||||
errorType: undefined,
|
|
||||||
outputFile: undefined,
|
|
||||||
resultDisplay: 'Success!',
|
|
||||||
contentLength:
|
|
||||||
typeof toolResult.llmContent === 'string'
|
|
||||||
? toolResult.llmContent.length
|
|
||||||
: undefined,
|
|
||||||
responseParts: [
|
|
||||||
{
|
|
||||||
functionResponse: {
|
|
||||||
name: 'testTool',
|
|
||||||
id: 'call1',
|
|
||||||
response: { output: 'Tool executed successfully' },
|
|
||||||
},
|
|
||||||
},
|
|
||||||
],
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should return an error if tool is not found', async () => {
|
|
||||||
const request: ToolCallRequestInfo = {
|
|
||||||
callId: 'call2',
|
|
||||||
name: 'nonexistentTool',
|
|
||||||
args: {},
|
|
||||||
isClientInitiated: false,
|
|
||||||
prompt_id: 'prompt-id-2',
|
|
||||||
};
|
|
||||||
vi.mocked(mockToolRegistry.getTool).mockReturnValue(undefined);
|
|
||||||
vi.mocked(mockToolRegistry.getAllToolNames).mockReturnValue([
|
|
||||||
'testTool',
|
|
||||||
'anotherTool',
|
|
||||||
]);
|
|
||||||
|
|
||||||
const { response } = await executeToolCall(
|
|
||||||
mockConfig,
|
|
||||||
request,
|
|
||||||
abortController.signal,
|
|
||||||
);
|
|
||||||
|
|
||||||
const expectedErrorMessage =
|
|
||||||
'Tool "nonexistentTool" not found in registry. Tools must use the exact names that are registered. Did you mean one of: "testTool", "anotherTool"?';
|
|
||||||
expect(response).toStrictEqual({
|
|
||||||
callId: 'call2',
|
|
||||||
error: new Error(expectedErrorMessage),
|
|
||||||
errorType: ToolErrorType.TOOL_NOT_REGISTERED,
|
|
||||||
resultDisplay: expectedErrorMessage,
|
|
||||||
contentLength: expectedErrorMessage.length,
|
|
||||||
responseParts: [
|
|
||||||
{
|
|
||||||
functionResponse: {
|
|
||||||
name: 'nonexistentTool',
|
|
||||||
id: 'call2',
|
|
||||||
response: {
|
|
||||||
error: expectedErrorMessage,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
],
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should return an error if tool validation fails', async () => {
|
|
||||||
const request: ToolCallRequestInfo = {
|
|
||||||
callId: 'call3',
|
|
||||||
name: 'testTool',
|
|
||||||
args: { param1: 'invalid' },
|
|
||||||
isClientInitiated: false,
|
|
||||||
prompt_id: 'prompt-id-3',
|
|
||||||
};
|
|
||||||
vi.mocked(mockToolRegistry.getTool).mockReturnValue(mockTool);
|
|
||||||
vi.spyOn(mockTool, 'build').mockImplementation(() => {
|
|
||||||
throw new Error('Invalid parameters');
|
|
||||||
});
|
|
||||||
|
|
||||||
const { response } = await executeToolCall(
|
|
||||||
mockConfig,
|
|
||||||
request,
|
|
||||||
abortController.signal,
|
|
||||||
);
|
|
||||||
|
|
||||||
expect(response).toStrictEqual({
|
|
||||||
callId: 'call3',
|
|
||||||
error: new Error('Invalid parameters'),
|
|
||||||
errorType: ToolErrorType.INVALID_TOOL_PARAMS,
|
|
||||||
responseParts: [
|
|
||||||
{
|
|
||||||
functionResponse: {
|
|
||||||
id: 'call3',
|
|
||||||
name: 'testTool',
|
|
||||||
response: {
|
|
||||||
error: 'Invalid parameters',
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
],
|
|
||||||
resultDisplay: 'Invalid parameters',
|
|
||||||
contentLength: 'Invalid parameters'.length,
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should return an error if tool execution fails', async () => {
|
|
||||||
const request: ToolCallRequestInfo = {
|
|
||||||
callId: 'call4',
|
|
||||||
name: 'testTool',
|
|
||||||
args: { param1: 'value1' },
|
|
||||||
isClientInitiated: false,
|
|
||||||
prompt_id: 'prompt-id-4',
|
|
||||||
};
|
|
||||||
const executionErrorResult: ToolResult = {
|
|
||||||
llmContent: 'Error: Execution failed',
|
|
||||||
returnDisplay: 'Execution failed',
|
|
||||||
error: {
|
|
||||||
message: 'Execution failed',
|
|
||||||
type: ToolErrorType.EXECUTION_FAILED,
|
|
||||||
},
|
|
||||||
};
|
|
||||||
vi.mocked(mockToolRegistry.getTool).mockReturnValue(mockTool);
|
|
||||||
executeFn.mockResolvedValue(executionErrorResult);
|
|
||||||
|
|
||||||
const { response } = await executeToolCall(
|
|
||||||
mockConfig,
|
|
||||||
request,
|
|
||||||
abortController.signal,
|
|
||||||
);
|
|
||||||
expect(response).toStrictEqual({
|
|
||||||
callId: 'call4',
|
|
||||||
error: new Error('Execution failed'),
|
|
||||||
errorType: ToolErrorType.EXECUTION_FAILED,
|
|
||||||
responseParts: [
|
|
||||||
{
|
|
||||||
functionResponse: {
|
|
||||||
id: 'call4',
|
|
||||||
name: 'testTool',
|
|
||||||
response: {
|
|
||||||
error: 'Execution failed',
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
],
|
|
||||||
resultDisplay: 'Execution failed',
|
|
||||||
contentLength: 'Execution failed'.length,
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should return an unhandled exception error if execution throws', async () => {
|
|
||||||
const request: ToolCallRequestInfo = {
|
|
||||||
callId: 'call5',
|
|
||||||
name: 'testTool',
|
|
||||||
args: { param1: 'value1' },
|
|
||||||
isClientInitiated: false,
|
|
||||||
prompt_id: 'prompt-id-5',
|
|
||||||
};
|
|
||||||
vi.mocked(mockToolRegistry.getTool).mockReturnValue(mockTool);
|
|
||||||
executeFn.mockRejectedValue(new Error('Something went very wrong'));
|
|
||||||
|
|
||||||
const { response } = await executeToolCall(
|
|
||||||
mockConfig,
|
|
||||||
request,
|
|
||||||
abortController.signal,
|
|
||||||
);
|
|
||||||
|
|
||||||
expect(response).toStrictEqual({
|
|
||||||
callId: 'call5',
|
|
||||||
error: new Error('Something went very wrong'),
|
|
||||||
errorType: ToolErrorType.UNHANDLED_EXCEPTION,
|
|
||||||
resultDisplay: 'Something went very wrong',
|
|
||||||
contentLength: 'Something went very wrong'.length,
|
|
||||||
responseParts: [
|
|
||||||
{
|
|
||||||
functionResponse: {
|
|
||||||
name: 'testTool',
|
|
||||||
id: 'call5',
|
|
||||||
response: { error: 'Something went very wrong' },
|
|
||||||
},
|
|
||||||
},
|
|
||||||
],
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should correctly format llmContent with inlineData', async () => {
|
|
||||||
const request: ToolCallRequestInfo = {
|
|
||||||
callId: 'call6',
|
|
||||||
name: 'testTool',
|
|
||||||
args: {},
|
|
||||||
isClientInitiated: false,
|
|
||||||
prompt_id: 'prompt-id-6',
|
|
||||||
};
|
|
||||||
const imageDataPart: Part = {
|
|
||||||
inlineData: { mimeType: 'image/png', data: 'base64data' },
|
|
||||||
};
|
|
||||||
const toolResult: ToolResult = {
|
|
||||||
llmContent: [imageDataPart],
|
|
||||||
returnDisplay: 'Image processed',
|
|
||||||
};
|
|
||||||
vi.mocked(mockToolRegistry.getTool).mockReturnValue(mockTool);
|
|
||||||
executeFn.mockResolvedValue(toolResult);
|
|
||||||
|
|
||||||
const { response } = await executeToolCall(
|
|
||||||
mockConfig,
|
|
||||||
request,
|
|
||||||
abortController.signal,
|
|
||||||
);
|
|
||||||
|
|
||||||
expect(response).toStrictEqual({
|
|
||||||
callId: 'call6',
|
|
||||||
error: undefined,
|
|
||||||
errorType: undefined,
|
|
||||||
outputFile: undefined,
|
|
||||||
resultDisplay: 'Image processed',
|
|
||||||
contentLength: undefined,
|
|
||||||
responseParts: [
|
|
||||||
{
|
|
||||||
functionResponse: {
|
|
||||||
name: 'testTool',
|
|
||||||
id: 'call6',
|
|
||||||
response: { output: 'Binary content provided (1 item(s)).' },
|
|
||||||
parts: [imageDataPart],
|
|
||||||
},
|
|
||||||
},
|
|
||||||
],
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should calculate contentLength for a string llmContent', async () => {
|
|
||||||
const request: ToolCallRequestInfo = {
|
|
||||||
callId: 'call7',
|
|
||||||
name: 'testTool',
|
|
||||||
args: {},
|
|
||||||
isClientInitiated: false,
|
|
||||||
prompt_id: 'prompt-id-7',
|
|
||||||
};
|
|
||||||
const toolResult: ToolResult = {
|
|
||||||
llmContent: 'This is a test string.',
|
|
||||||
returnDisplay: 'String returned',
|
|
||||||
};
|
|
||||||
vi.mocked(mockToolRegistry.getTool).mockReturnValue(mockTool);
|
|
||||||
executeFn.mockResolvedValue(toolResult);
|
|
||||||
|
|
||||||
const { response } = await executeToolCall(
|
|
||||||
mockConfig,
|
|
||||||
request,
|
|
||||||
abortController.signal,
|
|
||||||
);
|
|
||||||
|
|
||||||
expect(response.contentLength).toBe(
|
|
||||||
typeof toolResult.llmContent === 'string'
|
|
||||||
? toolResult.llmContent.length
|
|
||||||
: undefined,
|
|
||||||
);
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should have undefined contentLength for array llmContent with no string parts', async () => {
|
|
||||||
const request: ToolCallRequestInfo = {
|
|
||||||
callId: 'call8',
|
|
||||||
name: 'testTool',
|
|
||||||
args: {},
|
|
||||||
isClientInitiated: false,
|
|
||||||
prompt_id: 'prompt-id-8',
|
|
||||||
};
|
|
||||||
const toolResult: ToolResult = {
|
|
||||||
llmContent: [{ inlineData: { mimeType: 'image/png', data: 'fakedata' } }],
|
|
||||||
returnDisplay: 'Image data returned',
|
|
||||||
};
|
|
||||||
vi.mocked(mockToolRegistry.getTool).mockReturnValue(mockTool);
|
|
||||||
executeFn.mockResolvedValue(toolResult);
|
|
||||||
|
|
||||||
const { response } = await executeToolCall(
|
|
||||||
mockConfig,
|
|
||||||
request,
|
|
||||||
abortController.signal,
|
|
||||||
);
|
|
||||||
|
|
||||||
expect(response.contentLength).toBeUndefined();
|
|
||||||
});
|
|
||||||
});
|
|
||||||
@@ -1,38 +0,0 @@
|
|||||||
/**
|
|
||||||
* @license
|
|
||||||
* Copyright 2025 Google LLC
|
|
||||||
* SPDX-License-Identifier: Apache-2.0
|
|
||||||
*/
|
|
||||||
|
|
||||||
import type { ToolCallRequestInfo, Config } from '../index.js';
|
|
||||||
import {
|
|
||||||
CoreToolScheduler,
|
|
||||||
type CompletedToolCall,
|
|
||||||
} from './coreToolScheduler.js';
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Executes a single tool call non-interactively by leveraging the CoreToolScheduler.
|
|
||||||
*/
|
|
||||||
export async function executeToolCall(
|
|
||||||
config: Config,
|
|
||||||
toolCallRequest: ToolCallRequestInfo,
|
|
||||||
abortSignal: AbortSignal,
|
|
||||||
): Promise<CompletedToolCall> {
|
|
||||||
return new Promise<CompletedToolCall>((resolve, reject) => {
|
|
||||||
const scheduler = new CoreToolScheduler({
|
|
||||||
config,
|
|
||||||
getPreferredEditor: () => undefined,
|
|
||||||
onAllToolCallsComplete: async (completedToolCalls) => {
|
|
||||||
if (completedToolCalls.length > 0) {
|
|
||||||
resolve(completedToolCalls[0]);
|
|
||||||
} else {
|
|
||||||
reject(new Error('No completed tool calls returned.'));
|
|
||||||
}
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
scheduler.schedule(toolCallRequest, abortSignal).catch((error) => {
|
|
||||||
reject(error);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
}
|
|
||||||
@@ -39,7 +39,6 @@ export * from './core/coreToolScheduler.js';
|
|||||||
export * from './scheduler/scheduler.js';
|
export * from './scheduler/scheduler.js';
|
||||||
export * from './scheduler/types.js';
|
export * from './scheduler/types.js';
|
||||||
export * from './scheduler/tool-executor.js';
|
export * from './scheduler/tool-executor.js';
|
||||||
export * from './core/nonInteractiveToolExecutor.js';
|
|
||||||
export * from './core/recordingContentGenerator.js';
|
export * from './core/recordingContentGenerator.js';
|
||||||
|
|
||||||
export * from './fallback/types.js';
|
export * from './fallback/types.js';
|
||||||
|
|||||||
Reference in New Issue
Block a user