|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
import { EditorType } from '../utils/editor.js'; |
|
|
import os from 'os'; |
|
|
import path from 'path'; |
|
|
import fs from 'fs'; |
|
|
import * as Diff from 'diff'; |
|
|
import { openDiff } from '../utils/editor.js'; |
|
|
import { DEFAULT_DIFF_OPTIONS } from './diffOptions.js'; |
|
|
import { isNodeError } from '../utils/errors.js'; |
|
|
import { Tool } from './tools.js'; |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
export interface ModifiableTool<ToolParams> extends Tool<ToolParams> { |
|
|
getModifyContext(abortSignal: AbortSignal): ModifyContext<ToolParams>; |
|
|
} |
|
|
|
|
|
export interface ModifyContext<ToolParams> { |
|
|
getFilePath: (params: ToolParams) => string; |
|
|
|
|
|
getCurrentContent: (params: ToolParams) => Promise<string>; |
|
|
|
|
|
getProposedContent: (params: ToolParams) => Promise<string>; |
|
|
|
|
|
createUpdatedParams: ( |
|
|
oldContent: string, |
|
|
modifiedProposedContent: string, |
|
|
originalParams: ToolParams, |
|
|
) => ToolParams; |
|
|
} |
|
|
|
|
|
export interface ModifyResult<ToolParams> { |
|
|
updatedParams: ToolParams; |
|
|
updatedDiff: string; |
|
|
} |
|
|
|
|
|
export function isModifiableTool<TParams>( |
|
|
tool: Tool<TParams>, |
|
|
): tool is ModifiableTool<TParams> { |
|
|
return 'getModifyContext' in tool; |
|
|
} |
|
|
|
|
|
function createTempFilesForModify( |
|
|
currentContent: string, |
|
|
proposedContent: string, |
|
|
file_path: string, |
|
|
): { oldPath: string; newPath: string } { |
|
|
const tempDir = os.tmpdir(); |
|
|
const diffDir = path.join(tempDir, 'gemini-cli-tool-modify-diffs'); |
|
|
|
|
|
if (!fs.existsSync(diffDir)) { |
|
|
fs.mkdirSync(diffDir, { recursive: true }); |
|
|
} |
|
|
|
|
|
const fileName = path.basename(file_path); |
|
|
const timestamp = Date.now(); |
|
|
const tempOldPath = path.join( |
|
|
diffDir, |
|
|
`gemini-cli-modify-${fileName}-old-${timestamp}`, |
|
|
); |
|
|
const tempNewPath = path.join( |
|
|
diffDir, |
|
|
`gemini-cli-modify-${fileName}-new-${timestamp}`, |
|
|
); |
|
|
|
|
|
fs.writeFileSync(tempOldPath, currentContent, 'utf8'); |
|
|
fs.writeFileSync(tempNewPath, proposedContent, 'utf8'); |
|
|
|
|
|
return { oldPath: tempOldPath, newPath: tempNewPath }; |
|
|
} |
|
|
|
|
|
function getUpdatedParams<ToolParams>( |
|
|
tmpOldPath: string, |
|
|
tempNewPath: string, |
|
|
originalParams: ToolParams, |
|
|
modifyContext: ModifyContext<ToolParams>, |
|
|
): { updatedParams: ToolParams; updatedDiff: string } { |
|
|
let oldContent = ''; |
|
|
let newContent = ''; |
|
|
|
|
|
try { |
|
|
oldContent = fs.readFileSync(tmpOldPath, 'utf8'); |
|
|
} catch (err) { |
|
|
if (!isNodeError(err) || err.code !== 'ENOENT') throw err; |
|
|
oldContent = ''; |
|
|
} |
|
|
|
|
|
try { |
|
|
newContent = fs.readFileSync(tempNewPath, 'utf8'); |
|
|
} catch (err) { |
|
|
if (!isNodeError(err) || err.code !== 'ENOENT') throw err; |
|
|
newContent = ''; |
|
|
} |
|
|
|
|
|
const updatedParams = modifyContext.createUpdatedParams( |
|
|
oldContent, |
|
|
newContent, |
|
|
originalParams, |
|
|
); |
|
|
const updatedDiff = Diff.createPatch( |
|
|
path.basename(modifyContext.getFilePath(originalParams)), |
|
|
oldContent, |
|
|
newContent, |
|
|
'Current', |
|
|
'Proposed', |
|
|
DEFAULT_DIFF_OPTIONS, |
|
|
); |
|
|
|
|
|
return { updatedParams, updatedDiff }; |
|
|
} |
|
|
|
|
|
function deleteTempFiles(oldPath: string, newPath: string): void { |
|
|
try { |
|
|
fs.unlinkSync(oldPath); |
|
|
} catch { |
|
|
console.error(`Error deleting temp diff file: ${oldPath}`); |
|
|
} |
|
|
|
|
|
try { |
|
|
fs.unlinkSync(newPath); |
|
|
} catch { |
|
|
console.error(`Error deleting temp diff file: ${newPath}`); |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
export async function modifyWithEditor<ToolParams>( |
|
|
originalParams: ToolParams, |
|
|
modifyContext: ModifyContext<ToolParams>, |
|
|
editorType: EditorType, |
|
|
_abortSignal: AbortSignal, |
|
|
): Promise<ModifyResult<ToolParams>> { |
|
|
const currentContent = await modifyContext.getCurrentContent(originalParams); |
|
|
const proposedContent = |
|
|
await modifyContext.getProposedContent(originalParams); |
|
|
|
|
|
const { oldPath, newPath } = createTempFilesForModify( |
|
|
currentContent, |
|
|
proposedContent, |
|
|
modifyContext.getFilePath(originalParams), |
|
|
); |
|
|
|
|
|
try { |
|
|
await openDiff(oldPath, newPath, editorType); |
|
|
const result = getUpdatedParams( |
|
|
oldPath, |
|
|
newPath, |
|
|
originalParams, |
|
|
modifyContext, |
|
|
); |
|
|
|
|
|
return result; |
|
|
} finally { |
|
|
deleteTempFiles(oldPath, newPath); |
|
|
} |
|
|
} |
|
|
|