basant307/AI_Governance_Project
045
1/**2 * @license3 * Copyright 2025 Google LLC4 * SPDX-License-Identifier: Apache-2.05 */6 7import type { EditorType } from '../utils/editor.js';8import { openDiff } from '../utils/editor.js';9import os from 'node:os';10import path from 'node:path';11import fs from 'node:fs';12import { createPatchSmart } from './diffOptions.js';13import { isNodeError } from '../utils/errors.js';14import { createDebugLogger } from '../utils/debugLogger.js';15import type {16 AnyDeclarativeTool,17 DeclarativeTool,18 ToolResult,19} from './tools.js';20 21const debugLogger = createDebugLogger('MODIFIABLE_TOOL');22 23/**24 * A declarative tool that supports a modify operation.25 */26export interface ModifiableDeclarativeTool<TParams extends object>27 extends DeclarativeTool<TParams, ToolResult> {28 getModifyContext(abortSignal: AbortSignal): ModifyContext<TParams>;29}30 31export interface ModifyContext<ToolParams> {32 getFilePath: (params: ToolParams) => string;33 34 getCurrentContent: (params: ToolParams) => Promise<string>;35 36 getProposedContent: (params: ToolParams) => Promise<string>;37 38 createUpdatedParams: (39 oldContent: string,40 modifiedProposedContent: string,41 originalParams: ToolParams,42 ) => ToolParams;43}44 45export interface ModifyResult<ToolParams> {46 updatedParams: ToolParams;47 updatedDiff: string;48}49 50/**51 * Type guard to check if a declarative tool is modifiable.52 */53export function isModifiableDeclarativeTool(54 tool: AnyDeclarativeTool,55): tool is ModifiableDeclarativeTool<object> {56 return 'getModifyContext' in tool;57}58 59function createTempFilesForModify(60 currentContent: string,61 proposedContent: string,62 file_path: string,63): { oldPath: string; newPath: string } {64 const tempDir = os.tmpdir();65 const diffDir = path.join(tempDir, 'qwen-code-tool-modify-diffs');66 67 if (!fs.existsSync(diffDir)) {68 fs.mkdirSync(diffDir, { recursive: true });69 }70 71 const ext = path.extname(file_path);72 const fileName = path.basename(file_path, ext);73 const timestamp = Date.now();74 const tempOldPath = path.join(75 diffDir,76 `qwen-code-modify-${fileName}-old-${timestamp}${ext}`,77 );78 const tempNewPath = path.join(79 diffDir,80 `qwen-code-modify-${fileName}-new-${timestamp}${ext}`,81 );82 83 fs.writeFileSync(tempOldPath, currentContent, 'utf8');84 fs.writeFileSync(tempNewPath, proposedContent, 'utf8');85 86 return { oldPath: tempOldPath, newPath: tempNewPath };87}88 89function getUpdatedParams<ToolParams>(90 tmpOldPath: string,91 tempNewPath: string,92 originalParams: ToolParams,93 modifyContext: ModifyContext<ToolParams>,94): { updatedParams: ToolParams; updatedDiff: string } {95 let oldContent = '';96 let newContent = '';97 98 try {99 oldContent = fs.readFileSync(tmpOldPath, 'utf8');100 } catch (err) {101 if (!isNodeError(err) || err.code !== 'ENOENT') throw err;102 oldContent = '';103 }104 105 try {106 newContent = fs.readFileSync(tempNewPath, 'utf8');107 } catch (err) {108 if (!isNodeError(err) || err.code !== 'ENOENT') throw err;109 newContent = '';110 }111 112 const updatedParams = modifyContext.createUpdatedParams(113 oldContent,114 newContent,115 originalParams,116 );117 const updatedDiff = createPatchSmart(118 path.basename(modifyContext.getFilePath(originalParams)),119 oldContent,120 newContent,121 'Current',122 'Proposed',123 );124 125 return { updatedParams, updatedDiff };126}127 128function deleteTempFiles(oldPath: string, newPath: string): void {129 try {130 fs.unlinkSync(oldPath);131 } catch {132 debugLogger.warn(`Error deleting temp diff file: ${oldPath}`);133 }134 135 try {136 fs.unlinkSync(newPath);137 } catch {138 debugLogger.warn(`Error deleting temp diff file: ${newPath}`);139 }140}141 142/**143 * Triggers an external editor for the user to modify the proposed content,144 * and returns the updated tool parameters and the diff after the user has modified the proposed content.145 */146export async function modifyWithEditor<ToolParams>(147 originalParams: ToolParams,148 modifyContext: ModifyContext<ToolParams>,149 editorType: EditorType,150 _abortSignal: AbortSignal,151 onEditorClose: () => void,152): Promise<ModifyResult<ToolParams>> {153 const currentContent = await modifyContext.getCurrentContent(originalParams);154 const proposedContent =155 await modifyContext.getProposedContent(originalParams);156 157 const { oldPath, newPath } = createTempFilesForModify(158 currentContent,159 proposedContent,160 modifyContext.getFilePath(originalParams),161 );162 163 try {164 await openDiff(oldPath, newPath, editorType, onEditorClose);165 const result = getUpdatedParams(166 oldPath,167 newPath,168 originalParams,169 modifyContext,170 );171 172 return result;173 } finally {174 deleteTempFiles(oldPath, newPath);175 }176}177 