mirror of
https://github.com/google-gemini/gemini-cli.git
synced 2026-04-27 13:34:15 -07:00
feat(plan): refactor ToolConfirmationPayload to union type (#17980)
This commit is contained in:
@@ -13,6 +13,7 @@ import {
|
|||||||
type SerializableConfirmationDetails,
|
type SerializableConfirmationDetails,
|
||||||
type ToolCallConfirmationDetails,
|
type ToolCallConfirmationDetails,
|
||||||
type Config,
|
type Config,
|
||||||
|
type ToolConfirmationPayload,
|
||||||
ToolConfirmationOutcome,
|
ToolConfirmationOutcome,
|
||||||
hasRedirection,
|
hasRedirection,
|
||||||
debugLogger,
|
debugLogger,
|
||||||
@@ -65,10 +66,7 @@ export const ToolConfirmationMessage: React.FC<
|
|||||||
const isTrustedFolder = config.isTrustedFolder();
|
const isTrustedFolder = config.isTrustedFolder();
|
||||||
|
|
||||||
const handleConfirm = useCallback(
|
const handleConfirm = useCallback(
|
||||||
(
|
(outcome: ToolConfirmationOutcome, payload?: ToolConfirmationPayload) => {
|
||||||
outcome: ToolConfirmationOutcome,
|
|
||||||
payload?: { answers?: { [questionIndex: string]: string } },
|
|
||||||
) => {
|
|
||||||
void confirm(callId, outcome, payload).catch((error: unknown) => {
|
void confirm(callId, outcome, payload).catch((error: unknown) => {
|
||||||
debugLogger.error(
|
debugLogger.error(
|
||||||
`Failed to handle tool confirmation for ${callId}:`,
|
`Failed to handle tool confirmation for ${callId}:`,
|
||||||
|
|||||||
@@ -1847,6 +1847,83 @@ describe('CoreToolScheduler Sequential Execution', () => {
|
|||||||
modifyWithEditorSpy.mockRestore();
|
modifyWithEditorSpy.mockRestore();
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it('should handle inline modify with empty new content', async () => {
|
||||||
|
// Mock the modifiable check to return true for this test
|
||||||
|
const isModifiableSpy = vi
|
||||||
|
.spyOn(modifiableToolModule, 'isModifiableDeclarativeTool')
|
||||||
|
.mockReturnValue(true);
|
||||||
|
|
||||||
|
const mockTool = new MockModifiableTool();
|
||||||
|
const mockToolRegistry = {
|
||||||
|
getTool: () => mockTool,
|
||||||
|
getAllToolNames: () => [],
|
||||||
|
} as unknown as ToolRegistry;
|
||||||
|
|
||||||
|
const mockConfig = createMockConfig({
|
||||||
|
getToolRegistry: () => mockToolRegistry,
|
||||||
|
isInteractive: () => true,
|
||||||
|
});
|
||||||
|
mockConfig.getHookSystem = vi.fn().mockReturnValue(undefined);
|
||||||
|
|
||||||
|
const scheduler = new CoreToolScheduler({
|
||||||
|
config: mockConfig,
|
||||||
|
getPreferredEditor: () => 'vscode',
|
||||||
|
});
|
||||||
|
|
||||||
|
// Manually inject a waiting tool call
|
||||||
|
const callId = 'call-1';
|
||||||
|
const toolCall: WaitingToolCall = {
|
||||||
|
status: 'awaiting_approval',
|
||||||
|
request: {
|
||||||
|
callId,
|
||||||
|
name: 'mockModifiableTool',
|
||||||
|
args: {},
|
||||||
|
isClientInitiated: false,
|
||||||
|
prompt_id: 'p1',
|
||||||
|
},
|
||||||
|
tool: mockTool,
|
||||||
|
invocation: {} as unknown as ToolInvocation<
|
||||||
|
Record<string, unknown>,
|
||||||
|
ToolResult
|
||||||
|
>,
|
||||||
|
confirmationDetails: {
|
||||||
|
type: 'edit',
|
||||||
|
title: 'Confirm',
|
||||||
|
fileName: 'test.txt',
|
||||||
|
filePath: 'test.txt',
|
||||||
|
fileDiff: 'diff',
|
||||||
|
originalContent: 'old',
|
||||||
|
newContent: 'new',
|
||||||
|
onConfirm: async () => {},
|
||||||
|
},
|
||||||
|
startTime: Date.now(),
|
||||||
|
};
|
||||||
|
|
||||||
|
const schedulerInternals = scheduler as unknown as {
|
||||||
|
toolCalls: ToolCall[];
|
||||||
|
toolModifier: { applyInlineModify: Mock };
|
||||||
|
};
|
||||||
|
schedulerInternals.toolCalls = [toolCall];
|
||||||
|
|
||||||
|
const applyInlineModifySpy = vi
|
||||||
|
.spyOn(schedulerInternals.toolModifier, 'applyInlineModify')
|
||||||
|
.mockResolvedValue({
|
||||||
|
updatedParams: { content: '' },
|
||||||
|
updatedDiff: 'diff-empty',
|
||||||
|
});
|
||||||
|
|
||||||
|
await scheduler.handleConfirmationResponse(
|
||||||
|
callId,
|
||||||
|
async () => {},
|
||||||
|
ToolConfirmationOutcome.ProceedOnce,
|
||||||
|
new AbortController().signal,
|
||||||
|
{ newContent: '' } as ToolConfirmationPayload,
|
||||||
|
);
|
||||||
|
|
||||||
|
expect(applyInlineModifySpy).toHaveBeenCalled();
|
||||||
|
isModifiableSpy.mockRestore();
|
||||||
|
});
|
||||||
|
|
||||||
it('should pass serverName to policy engine for DiscoveredMCPTool', async () => {
|
it('should pass serverName to policy engine for DiscoveredMCPTool', async () => {
|
||||||
const mockMcpTool = {
|
const mockMcpTool = {
|
||||||
tool: async () => ({ functionDeclarations: [] }),
|
tool: async () => ({ functionDeclarations: [] }),
|
||||||
|
|||||||
@@ -789,7 +789,7 @@ export class CoreToolScheduler {
|
|||||||
} else {
|
} else {
|
||||||
// If the client provided new content, apply it and wait for
|
// If the client provided new content, apply it and wait for
|
||||||
// re-confirmation.
|
// re-confirmation.
|
||||||
if (payload?.newContent && toolCall) {
|
if (payload && 'newContent' in payload && toolCall) {
|
||||||
const result = await this.toolModifier.applyInlineModify(
|
const result = await this.toolModifier.applyInlineModify(
|
||||||
toolCall as WaitingToolCall,
|
toolCall as WaitingToolCall,
|
||||||
payload,
|
payload,
|
||||||
|
|||||||
@@ -156,7 +156,7 @@ export async function resolveConfirmation(
|
|||||||
|
|
||||||
if (outcome === ToolConfirmationOutcome.ModifyWithEditor) {
|
if (outcome === ToolConfirmationOutcome.ModifyWithEditor) {
|
||||||
await handleExternalModification(deps, toolCall, signal);
|
await handleExternalModification(deps, toolCall, signal);
|
||||||
} else if (response.payload?.newContent) {
|
} else if (response.payload && 'newContent' in response.payload) {
|
||||||
await handleInlineModification(deps, toolCall, response.payload, signal);
|
await handleInlineModification(deps, toolCall, response.payload, signal);
|
||||||
outcome = ToolConfirmationOutcome.ProceedOnce;
|
outcome = ToolConfirmationOutcome.ProceedOnce;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -193,13 +193,46 @@ describe('ToolModificationHandler', () => {
|
|||||||
|
|
||||||
const result = await handler.applyInlineModify(
|
const result = await handler.applyInlineModify(
|
||||||
mockWaitingToolCall,
|
mockWaitingToolCall,
|
||||||
{ newContent: undefined } as unknown as ToolConfirmationPayload,
|
{} as ToolConfirmationPayload, // no newContent property
|
||||||
new AbortController().signal,
|
new AbortController().signal,
|
||||||
);
|
);
|
||||||
|
|
||||||
expect(result).toBeUndefined();
|
expect(result).toBeUndefined();
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it('should process empty string as valid new content', async () => {
|
||||||
|
vi.mocked(
|
||||||
|
modifiableToolModule.isModifiableDeclarativeTool,
|
||||||
|
).mockReturnValue(true);
|
||||||
|
(Diff.createPatch as unknown as Mock).mockReturnValue('mock-diff-empty');
|
||||||
|
|
||||||
|
mockModifyContext.getCurrentContent.mockResolvedValue('old content');
|
||||||
|
mockModifyContext.getFilePath.mockReturnValue('test.txt');
|
||||||
|
mockModifyContext.createUpdatedParams.mockReturnValue({
|
||||||
|
content: '',
|
||||||
|
});
|
||||||
|
|
||||||
|
const mockWaitingToolCall = createMockWaitingToolCall({
|
||||||
|
tool: mockModifiableTool,
|
||||||
|
});
|
||||||
|
|
||||||
|
const result = await handler.applyInlineModify(
|
||||||
|
mockWaitingToolCall,
|
||||||
|
{ newContent: '' },
|
||||||
|
new AbortController().signal,
|
||||||
|
);
|
||||||
|
|
||||||
|
expect(mockModifyContext.createUpdatedParams).toHaveBeenCalledWith(
|
||||||
|
expect.any(String),
|
||||||
|
'',
|
||||||
|
expect.any(Object),
|
||||||
|
);
|
||||||
|
expect(result).toEqual({
|
||||||
|
updatedParams: { content: '' },
|
||||||
|
updatedDiff: 'mock-diff-empty',
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
it('should calculate diff and return updated params', async () => {
|
it('should calculate diff and return updated params', async () => {
|
||||||
vi.mocked(
|
vi.mocked(
|
||||||
modifiableToolModule.isModifiableDeclarativeTool,
|
modifiableToolModule.isModifiableDeclarativeTool,
|
||||||
|
|||||||
@@ -70,7 +70,7 @@ export class ToolModificationHandler {
|
|||||||
): Promise<ModificationResult | undefined> {
|
): Promise<ModificationResult | undefined> {
|
||||||
if (
|
if (
|
||||||
toolCall.confirmationDetails.type !== 'edit' ||
|
toolCall.confirmationDetails.type !== 'edit' ||
|
||||||
!payload.newContent ||
|
!('newContent' in payload) ||
|
||||||
!isModifiableDeclarativeTool(toolCall.tool)
|
!isModifiableDeclarativeTool(toolCall.tool)
|
||||||
) {
|
) {
|
||||||
return undefined;
|
return undefined;
|
||||||
|
|||||||
@@ -180,7 +180,7 @@ export class AskUserInvocation extends BaseToolInvocation<
|
|||||||
payload?: ToolConfirmationPayload,
|
payload?: ToolConfirmationPayload,
|
||||||
) => {
|
) => {
|
||||||
this.confirmationOutcome = outcome;
|
this.confirmationOutcome = outcome;
|
||||||
if (payload?.answers) {
|
if (payload && 'answers' in payload) {
|
||||||
this.userAnswers = payload.answers;
|
this.userAnswers = payload.answers;
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -693,14 +693,18 @@ export interface ToolEditConfirmationDetails {
|
|||||||
ideConfirmation?: Promise<DiffUpdateResult>;
|
ideConfirmation?: Promise<DiffUpdateResult>;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface ToolConfirmationPayload {
|
export interface ToolEditConfirmationPayload {
|
||||||
// used to override `modifiedProposedContent` for modifiable tools in the
|
newContent: string;
|
||||||
// inline modify flow
|
|
||||||
newContent?: string;
|
|
||||||
// used for askuser tool to return user's answers
|
|
||||||
answers?: { [questionIndex: string]: string };
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export interface ToolAskUserConfirmationPayload {
|
||||||
|
answers: { [questionIndex: string]: string };
|
||||||
|
}
|
||||||
|
|
||||||
|
export type ToolConfirmationPayload =
|
||||||
|
| ToolEditConfirmationPayload
|
||||||
|
| ToolAskUserConfirmationPayload;
|
||||||
|
|
||||||
export interface ToolExecuteConfirmationDetails {
|
export interface ToolExecuteConfirmationDetails {
|
||||||
type: 'exec';
|
type: 'exec';
|
||||||
title: string;
|
title: string;
|
||||||
|
|||||||
Reference in New Issue
Block a user