mirror of
https://github.com/QwenLM/qwen-code.git
synced 2025-12-19 09:33:53 +00:00
feat: Add --yolo mode that automatically accepts all tools executions (#695)
Co-authored-by: N. Taylor Mullen <ntaylormullen@google.com>
This commit is contained in:
@@ -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 {
|
||||
|
||||
@@ -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(
|
||||
|
||||
@@ -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/);
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
},
|
||||
};
|
||||
|
||||
@@ -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,
|
||||
};
|
||||
|
||||
|
||||
@@ -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();
|
||||
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
},
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user