diff options
| author | Tolik Malibroda <[email protected]> | 2025-06-02 22:05:45 +0200 |
|---|---|---|
| committer | GitHub <[email protected]> | 2025-06-02 22:05:45 +0200 |
| commit | 0795e55f0e7d2f2822bcd83eaf066eb99c67f858 (patch) | |
| tree | 3fd259976c8cfc5df79bba2d37f0a17fa3f683a4 /packages/core/src | |
| parent | 42bedbc3d39265932cbd6c9b818b6a7fbcbdd022 (diff) | |
feat: Add --yolo mode that automatically accepts all tools executions (#695)
Co-authored-by: N. Taylor Mullen <[email protected]>
Diffstat (limited to 'packages/core/src')
| -rw-r--r-- | packages/core/src/config/config.ts | 21 | ||||
| -rw-r--r-- | packages/core/src/core/coreToolScheduler.ts | 52 | ||||
| -rw-r--r-- | packages/core/src/tools/edit.test.ts | 20 | ||||
| -rw-r--r-- | packages/core/src/tools/edit.ts | 6 | ||||
| -rw-r--r-- | packages/core/src/tools/tool-registry.test.ts | 4 | ||||
| -rw-r--r-- | packages/core/src/tools/write-file.test.ts | 12 | ||||
| -rw-r--r-- | packages/core/src/tools/write-file.ts | 6 |
7 files changed, 65 insertions, 56 deletions
diff --git a/packages/core/src/config/config.ts b/packages/core/src/config/config.ts index a6279e2e..71c4a7d2 100644 --- a/packages/core/src/config/config.ts +++ b/packages/core/src/config/config.ts @@ -22,6 +22,12 @@ import { ReadManyFilesTool } from '../tools/read-many-files.js'; import { MemoryTool, setGeminiMdFilename } from '../tools/memoryTool.js'; import { WebSearchTool } from '../tools/web-search.js'; +export enum ApprovalMode { + DEFAULT = 'default', + AUTO_EDIT = 'autoEdit', + YOLO = 'yolo', +} + export class MCPServerConfig { constructor( // For stdio transport @@ -53,7 +59,7 @@ export interface ConfigParameters { userAgent: string; userMemory?: string; geminiMdFileCount?: number; - alwaysSkipModificationConfirmation?: boolean; + approvalMode?: ApprovalMode; vertexai?: boolean; showMemoryUsage?: boolean; contextFileName?: string; @@ -76,7 +82,7 @@ export class Config { private readonly userAgent: string; private userMemory: string; private geminiMdFileCount: number; - private alwaysSkipModificationConfirmation: boolean; + private approvalMode: ApprovalMode; private readonly vertexai: boolean | undefined; private readonly showMemoryUsage: boolean; @@ -96,8 +102,7 @@ export class Config { this.userAgent = params.userAgent; this.userMemory = params.userMemory ?? ''; this.geminiMdFileCount = params.geminiMdFileCount ?? 0; - this.alwaysSkipModificationConfirmation = - params.alwaysSkipModificationConfirmation ?? false; + this.approvalMode = params.approvalMode ?? ApprovalMode.DEFAULT; this.vertexai = params.vertexai; this.showMemoryUsage = params.showMemoryUsage ?? false; @@ -179,12 +184,12 @@ export class Config { this.geminiMdFileCount = count; } - getAlwaysSkipModificationConfirmation(): boolean { - return this.alwaysSkipModificationConfirmation; + getApprovalMode(): ApprovalMode { + return this.approvalMode; } - setAlwaysSkipModificationConfirmation(skip: boolean): void { - this.alwaysSkipModificationConfirmation = skip; + setApprovalMode(mode: ApprovalMode): void { + this.approvalMode = mode; } getVertexAI(): boolean | undefined { diff --git a/packages/core/src/core/coreToolScheduler.ts b/packages/core/src/core/coreToolScheduler.ts index 58f821c5..3fe6562e 100644 --- a/packages/core/src/core/coreToolScheduler.ts +++ b/packages/core/src/core/coreToolScheduler.ts @@ -12,6 +12,7 @@ import { ToolCallConfirmationDetails, ToolResult, ToolRegistry, + ApprovalMode, } from '../index.js'; import { Part, PartUnion, PartListUnion } from '@google/genai'; @@ -159,6 +160,7 @@ interface CoreToolSchedulerOptions { outputUpdateHandler?: OutputUpdateHandler; onAllToolCallsComplete?: AllToolCallsCompleteHandler; onToolCallsUpdate?: ToolCallsUpdateHandler; + approvalMode?: ApprovalMode; } export class CoreToolScheduler { @@ -168,12 +170,14 @@ export class CoreToolScheduler { private outputUpdateHandler?: OutputUpdateHandler; private onAllToolCallsComplete?: AllToolCallsCompleteHandler; private onToolCallsUpdate?: ToolCallsUpdateHandler; + private approvalMode: ApprovalMode; constructor(options: CoreToolSchedulerOptions) { this.toolRegistry = options.toolRegistry; this.outputUpdateHandler = options.outputUpdateHandler; this.onAllToolCallsComplete = options.onAllToolCallsComplete; this.onToolCallsUpdate = options.onToolCallsUpdate; + this.approvalMode = options.approvalMode ?? ApprovalMode.DEFAULT; this.abortController = new AbortController(); } @@ -324,29 +328,33 @@ export class CoreToolScheduler { const { request: reqInfo, tool: toolInstance } = toolCall; try { - const confirmationDetails = await toolInstance.shouldConfirmExecute( - reqInfo.args, - this.abortController.signal, - ); - - if (confirmationDetails) { - const originalOnConfirm = confirmationDetails.onConfirm; - const wrappedConfirmationDetails: ToolCallConfirmationDetails = { - ...confirmationDetails, - onConfirm: (outcome: ToolConfirmationOutcome) => - this.handleConfirmationResponse( - reqInfo.callId, - originalOnConfirm, - outcome, - ), - }; - this.setStatusInternal( - reqInfo.callId, - 'awaiting_approval', - wrappedConfirmationDetails, - ); - } else { + if (this.approvalMode === ApprovalMode.YOLO) { this.setStatusInternal(reqInfo.callId, 'scheduled'); + } else { + const confirmationDetails = await toolInstance.shouldConfirmExecute( + reqInfo.args, + this.abortController.signal, + ); + + if (confirmationDetails) { + const originalOnConfirm = confirmationDetails.onConfirm; + const wrappedConfirmationDetails: ToolCallConfirmationDetails = { + ...confirmationDetails, + onConfirm: (outcome: ToolConfirmationOutcome) => + this.handleConfirmationResponse( + reqInfo.callId, + originalOnConfirm, + outcome, + ), + }; + this.setStatusInternal( + reqInfo.callId, + 'awaiting_approval', + wrappedConfirmationDetails, + ); + } else { + this.setStatusInternal(reqInfo.callId, 'scheduled'); + } } } catch (error) { this.setStatusInternal( diff --git a/packages/core/src/tools/edit.test.ts b/packages/core/src/tools/edit.test.ts index c6c2ba63..3b93708a 100644 --- a/packages/core/src/tools/edit.test.ts +++ b/packages/core/src/tools/edit.test.ts @@ -25,7 +25,7 @@ import { FileDiff } from './tools.js'; import path from 'path'; import fs from 'fs'; import os from 'os'; -import { Config } from '../config/config.js'; +import { ApprovalMode, Config } from '../config/config.js'; import { Content, Part, SchemaUnion } from '@google/genai'; describe('EditTool', () => { @@ -41,8 +41,8 @@ describe('EditTool', () => { mockConfig = { getTargetDir: () => rootDir, - getAlwaysSkipModificationConfirmation: vi.fn(() => false), - setAlwaysSkipModificationConfirmation: vi.fn(), + getApprovalMode: vi.fn(() => false), + setApprovalMode: vi.fn(), // getGeminiConfig: () => ({ apiKey: 'test-api-key' }), // This was not a real Config method // Add other properties/methods of Config if EditTool uses them // Minimal other methods to satisfy Config type if needed by EditTool constructor or other direct uses: @@ -65,12 +65,10 @@ describe('EditTool', () => { } as unknown as Config; // Reset mocks before each test - (mockConfig.getAlwaysSkipModificationConfirmation as Mock).mockClear(); - (mockConfig.setAlwaysSkipModificationConfirmation as Mock).mockClear(); + (mockConfig.getApprovalMode as Mock).mockClear(); + (mockConfig.getApprovalMode as Mock).mockClear(); // Default to not skipping confirmation - (mockConfig.getAlwaysSkipModificationConfirmation as Mock).mockReturnValue( - false, - ); + (mockConfig.getApprovalMode as Mock).mockReturnValue(ApprovalMode.DEFAULT); // Reset mocks and set default implementation for ensureCorrectEdit mockEnsureCorrectEdit.mockReset(); @@ -439,9 +437,9 @@ describe('EditTool', () => { new_string: fileContent, }; - ( - mockConfig.getAlwaysSkipModificationConfirmation as Mock - ).mockReturnValueOnce(true); + (mockConfig.getApprovalMode as Mock).mockReturnValueOnce( + ApprovalMode.AUTO_EDIT, + ); const result = await tool.execute(params, new AbortController().signal); expect(result.llmContent).toMatch(/Created new file/); diff --git a/packages/core/src/tools/edit.ts b/packages/core/src/tools/edit.ts index 53b12480..b2f648f8 100644 --- a/packages/core/src/tools/edit.ts +++ b/packages/core/src/tools/edit.ts @@ -20,7 +20,7 @@ import { makeRelative, shortenPath } from '../utils/paths.js'; import { isNodeError } from '../utils/errors.js'; import { ReadFileTool } from './read-file.js'; import { GeminiClient } from '../core/client.js'; -import { Config } from '../config/config.js'; +import { Config, ApprovalMode } from '../config/config.js'; import { ensureCorrectEdit } from '../utils/editCorrector.js'; import { DEFAULT_DIFF_OPTIONS } from './diffOptions.js'; @@ -281,7 +281,7 @@ Expectation for required parameters: params: EditToolParams, abortSignal: AbortSignal, ): Promise<ToolCallConfirmationDetails | false> { - if (this.config.getAlwaysSkipModificationConfirmation()) { + if (this.config.getApprovalMode() === ApprovalMode.AUTO_EDIT) { return false; } const validationError = this.validateToolParams(params); @@ -356,7 +356,7 @@ Expectation for required parameters: fileDiff, onConfirm: async (outcome: ToolConfirmationOutcome) => { if (outcome === ToolConfirmationOutcome.ProceedAlways) { - this.config.setAlwaysSkipModificationConfirmation(true); + this.config.setApprovalMode(ApprovalMode.AUTO_EDIT); } }, }; diff --git a/packages/core/src/tools/tool-registry.test.ts b/packages/core/src/tools/tool-registry.test.ts index 121e91c8..9aaa7e5a 100644 --- a/packages/core/src/tools/tool-registry.test.ts +++ b/packages/core/src/tools/tool-registry.test.ts @@ -16,7 +16,7 @@ import { } from 'vitest'; import { ToolRegistry, DiscoveredTool } from './tool-registry.js'; import { DiscoveredMCPTool } from './mcp-tool.js'; -import { Config, ConfigParameters } from '../config/config.js'; +import { ApprovalMode, Config, ConfigParameters } from '../config/config.js'; import { BaseTool, ToolResult } from './tools.js'; import { FunctionDeclaration } from '@google/genai'; import { execSync, spawn } from 'node:child_process'; // Import spawn here @@ -85,7 +85,7 @@ const baseConfigParams: ConfigParameters = { userAgent: 'TestAgent/1.0', userMemory: '', geminiMdFileCount: 0, - alwaysSkipModificationConfirmation: false, + approvalMode: ApprovalMode.DEFAULT, vertexai: false, }; diff --git a/packages/core/src/tools/write-file.test.ts b/packages/core/src/tools/write-file.test.ts index 3fd97c9e..c94edfd1 100644 --- a/packages/core/src/tools/write-file.test.ts +++ b/packages/core/src/tools/write-file.test.ts @@ -20,7 +20,7 @@ import { ToolEditConfirmationDetails, } from './tools.js'; import { type EditToolParams } from './edit.js'; -import { Config } from '../config/config.js'; +import { ApprovalMode, Config } from '../config/config.js'; import { ToolRegistry } from './tool-registry.js'; import path from 'path'; import fs from 'fs'; @@ -51,8 +51,8 @@ vi.mocked(ensureCorrectFileContent).mockImplementation( // Mock Config const mockConfigInternal = { getTargetDir: () => rootDir, - getAlwaysSkipModificationConfirmation: vi.fn(() => false), - setAlwaysSkipModificationConfirmation: vi.fn(), + getApprovalMode: vi.fn(() => ApprovalMode.DEFAULT), + setApprovalMode: vi.fn(), getApiKey: () => 'test-key', getModel: () => 'test-model', getSandbox: () => false, @@ -100,10 +100,8 @@ describe('WriteFileTool', () => { tool = new WriteFileTool(mockConfig); // Reset mocks before each test - mockConfigInternal.getAlwaysSkipModificationConfirmation.mockReturnValue( - false, - ); - mockConfigInternal.setAlwaysSkipModificationConfirmation.mockClear(); + mockConfigInternal.getApprovalMode.mockReturnValue(ApprovalMode.DEFAULT); + mockConfigInternal.setApprovalMode.mockClear(); mockEnsureCorrectEdit.mockReset(); mockEnsureCorrectFileContent.mockReset(); diff --git a/packages/core/src/tools/write-file.ts b/packages/core/src/tools/write-file.ts index 2285c819..2e04a10a 100644 --- a/packages/core/src/tools/write-file.ts +++ b/packages/core/src/tools/write-file.ts @@ -7,7 +7,7 @@ import fs from 'fs'; import path from 'path'; import * as Diff from 'diff'; -import { Config } from '../config/config.js'; +import { Config, ApprovalMode } from '../config/config.js'; import { BaseTool, ToolResult, @@ -143,7 +143,7 @@ export class WriteFileTool extends BaseTool<WriteFileToolParams, ToolResult> { params: WriteFileToolParams, abortSignal: AbortSignal, ): Promise<ToolCallConfirmationDetails | false> { - if (this.config.getAlwaysSkipModificationConfirmation()) { + if (this.config.getApprovalMode() === ApprovalMode.AUTO_EDIT) { return false; } @@ -186,7 +186,7 @@ export class WriteFileTool extends BaseTool<WriteFileToolParams, ToolResult> { fileDiff, onConfirm: async (outcome: ToolConfirmationOutcome) => { if (outcome === ToolConfirmationOutcome.ProceedAlways) { - this.config.setAlwaysSkipModificationConfirmation(true); + this.config.setApprovalMode(ApprovalMode.AUTO_EDIT); } }, }; |
