createEditTool function
Builds the tool that edits a file through exact string replacement.
Implementation
Tool<JsonMap, String> createEditTool(
Genkit ai, {
required ToolRuntime runtime,
}) {
return defineTextTool(
ai,
runtime: runtime,
name: 'edit',
description: _description,
properties: <String, Object?>{
'file_path': stringProperty('The absolute path of the file to modify.'),
'old_string': stringProperty('The exact text to replace.'),
'new_string': stringProperty('The text to replace it with. Must differ from old_string.'),
'replace_all': booleanProperty('Replace every occurrence of old_string. Defaults to false.'),
},
required: <String>['file_path', 'old_string', 'new_string'],
run: (input) async {
final path = runtime.resolvePath(requireString(input, 'file_path'));
final oldString = readString(input, 'old_string') ?? '';
final newString = readString(input, 'new_string') ?? '';
final replaceAll = readBool(input, 'replace_all') ?? false;
if (!runtime.isWithinWorkspace(path)) {
throw ToolFailure('$path is outside the workspace ${runtime.workspaceRoot}.');
}
if (oldString.isEmpty) {
throw const ToolFailure('`old_string` must not be empty. Use the write tool to create a file.');
}
if (oldString == newString) {
throw const ToolFailure('`old_string` and `new_string` are identical; there is nothing to change.');
}
final tracked = runtime.trackedFile(path);
if (tracked == null) {
throw const ToolFailure('The file has not been read yet. Read it with the read tool before editing.');
}
if (!await runtime.fileMatchesTrackedState(path)) {
throw const ToolFailure('The file changed since it was last read. Read it again before editing.');
}
final content = tracked.content;
final occurrences = _countOccurrences(content, oldString);
if (occurrences == 0) {
throw const ToolFailure(
'`old_string` was not found in the file. Check whitespace and indentation, and make sure '
'you did not include line-number prefixes from the read output.',
);
}
if (occurrences > 1 && !replaceAll) {
throw ToolFailure(
'`old_string` occurs $occurrences times. Add surrounding context to make it unique, '
'or set `replace_all` to true to change every occurrence.',
);
}
runtime.startTool('edit', runtime.displayPath(path));
final firstIndex = content.indexOf(oldString);
final updated = replaceAll
? content.replaceAll(oldString, newString)
: content.replaceFirst(oldString, newString);
final file = File(path);
await file.writeAsString(updated);
runtime.recordFileRead(path, updated, await file.stat());
if (replaceAll) {
return 'Replaced $occurrences occurrences in $path.';
}
return 'Edited $path. Snippet of the result:\n${_snippet(updated, firstIndex, newString)}';
},
);
}