mirror of
https://github.com/google-gemini/gemini-cli.git
synced 2026-03-10 14:10:37 -07:00
fix(core): handle AbortError when ESC cancels tool execution (#20863)
This commit is contained in:
@@ -211,6 +211,87 @@ describe('ToolExecutor', () => {
|
||||
});
|
||||
});
|
||||
|
||||
it('should return cancelled result when executeToolWithHooks rejects with AbortError', async () => {
|
||||
const mockTool = new MockTool({
|
||||
name: 'webSearchTool',
|
||||
description: 'Mock web search',
|
||||
});
|
||||
const invocation = mockTool.build({});
|
||||
|
||||
const abortErr = new Error('The user aborted a request.');
|
||||
abortErr.name = 'AbortError';
|
||||
vi.mocked(coreToolHookTriggers.executeToolWithHooks).mockRejectedValue(
|
||||
abortErr,
|
||||
);
|
||||
|
||||
const scheduledCall: ScheduledToolCall = {
|
||||
status: CoreToolCallStatus.Scheduled,
|
||||
request: {
|
||||
callId: 'call-abort',
|
||||
name: 'webSearchTool',
|
||||
args: {},
|
||||
isClientInitiated: false,
|
||||
prompt_id: 'prompt-abort',
|
||||
},
|
||||
tool: mockTool,
|
||||
invocation: invocation as unknown as AnyToolInvocation,
|
||||
startTime: Date.now(),
|
||||
};
|
||||
|
||||
const result = await executor.execute({
|
||||
call: scheduledCall,
|
||||
signal: new AbortController().signal,
|
||||
onUpdateToolCall: vi.fn(),
|
||||
});
|
||||
|
||||
expect(result.status).toBe(CoreToolCallStatus.Cancelled);
|
||||
if (result.status === CoreToolCallStatus.Cancelled) {
|
||||
const response = result.response.responseParts[0]?.functionResponse
|
||||
?.response as Record<string, unknown>;
|
||||
expect(response['error']).toContain('Operation cancelled.');
|
||||
}
|
||||
});
|
||||
|
||||
it('should return cancelled result when executeToolWithHooks rejects with "Operation cancelled by user" message', async () => {
|
||||
const mockTool = new MockTool({
|
||||
name: 'someTool',
|
||||
description: 'Mock',
|
||||
});
|
||||
const invocation = mockTool.build({});
|
||||
|
||||
const cancelErr = new Error('Operation cancelled by user');
|
||||
vi.mocked(coreToolHookTriggers.executeToolWithHooks).mockRejectedValue(
|
||||
cancelErr,
|
||||
);
|
||||
|
||||
const scheduledCall: ScheduledToolCall = {
|
||||
status: CoreToolCallStatus.Scheduled,
|
||||
request: {
|
||||
callId: 'call-cancel-msg',
|
||||
name: 'someTool',
|
||||
args: {},
|
||||
isClientInitiated: false,
|
||||
prompt_id: 'prompt-cancel-msg',
|
||||
},
|
||||
tool: mockTool,
|
||||
invocation: invocation as unknown as AnyToolInvocation,
|
||||
startTime: Date.now(),
|
||||
};
|
||||
|
||||
const result = await executor.execute({
|
||||
call: scheduledCall,
|
||||
signal: new AbortController().signal,
|
||||
onUpdateToolCall: vi.fn(),
|
||||
});
|
||||
|
||||
expect(result.status).toBe(CoreToolCallStatus.Cancelled);
|
||||
if (result.status === CoreToolCallStatus.Cancelled) {
|
||||
const response = result.response.responseParts[0]?.functionResponse
|
||||
?.response as Record<string, unknown>;
|
||||
expect(response['error']).toContain('User cancelled tool execution.');
|
||||
}
|
||||
});
|
||||
|
||||
it('should return cancelled result when signal is aborted', async () => {
|
||||
const mockTool = new MockTool({
|
||||
name: 'slowTool',
|
||||
|
||||
@@ -16,6 +16,7 @@ import {
|
||||
type AgentLoopContext,
|
||||
type ToolLiveOutput,
|
||||
} from '../index.js';
|
||||
import { isAbortError } from '../utils/errors.js';
|
||||
import { SHELL_TOOL_NAME } from '../tools/tool-names.js';
|
||||
import { ShellToolInvocation } from '../tools/shell.js';
|
||||
import { DiscoveredMCPTool } from '../tools/mcp-tool.js';
|
||||
@@ -159,15 +160,17 @@ export class ToolExecutor {
|
||||
}
|
||||
} catch (executionError: unknown) {
|
||||
spanMetadata.error = executionError;
|
||||
const isAbortError =
|
||||
executionError instanceof Error &&
|
||||
(executionError.name === 'AbortError' ||
|
||||
const abortedByError =
|
||||
isAbortError(executionError) ||
|
||||
(executionError instanceof Error &&
|
||||
executionError.message.includes('Operation cancelled by user'));
|
||||
|
||||
if (signal.aborted || isAbortError) {
|
||||
if (signal.aborted || abortedByError) {
|
||||
completedToolCall = await this.createCancelledResult(
|
||||
call,
|
||||
'User cancelled tool execution.',
|
||||
isAbortError(executionError)
|
||||
? 'Operation cancelled.'
|
||||
: 'User cancelled tool execution.',
|
||||
);
|
||||
} else {
|
||||
const error =
|
||||
|
||||
@@ -16,7 +16,7 @@ import {
|
||||
} from './tools.js';
|
||||
import { ToolErrorType } from './tool-error.js';
|
||||
|
||||
import { getErrorMessage } from '../utils/errors.js';
|
||||
import { getErrorMessage, isAbortError } from '../utils/errors.js';
|
||||
import { type Config } from '../config/config.js';
|
||||
import { getResponseText } from '../utils/partUtils.js';
|
||||
import { debugLogger } from '../utils/debugLogger.js';
|
||||
@@ -175,6 +175,12 @@ class WebSearchToolInvocation extends BaseToolInvocation<
|
||||
sources,
|
||||
};
|
||||
} catch (error: unknown) {
|
||||
if (isAbortError(error)) {
|
||||
return {
|
||||
llmContent: 'Web search was cancelled.',
|
||||
returnDisplay: 'Search cancelled.',
|
||||
};
|
||||
}
|
||||
const errorMessage = `Error during web search for query "${
|
||||
this.params.query
|
||||
}": ${getErrorMessage(error)}`;
|
||||
|
||||
Reference in New Issue
Block a user