createGrepTool function
Builds the tool that searches file contents with ripgrep.
Implementation
Tool<JsonMap, String> createGrepTool(
Genkit ai, {
required ToolRuntime runtime,
}) {
return defineTextTool(
ai,
runtime: runtime,
name: 'grep',
description: _description,
properties: <String, Object?>{
'pattern': stringProperty('The regular expression to search for.'),
'path': stringProperty('File or directory to search. Defaults to the current working directory.'),
'glob': stringProperty('Glob to filter files, e.g. "*.dart" or "src/**/*.{ts,tsx}".'),
'type': stringProperty('ripgrep file type to search, e.g. "dart", "py", "rust".'),
'output_mode': stringProperty(
'What to return. Defaults to "files_with_matches".',
enumValues: <String>['content', 'files_with_matches', 'count'],
),
'-i': booleanProperty('Case-insensitive search.'),
'-A': integerProperty('Lines of context after each match (content mode only).'),
'-B': integerProperty('Lines of context before each match (content mode only).'),
'-C': integerProperty('Lines of context around each match (content mode only).'),
'multiline': booleanProperty('Let `.` match newlines and patterns span lines.'),
'head_limit': integerProperty('Maximum number of output lines. Defaults to $_defaultHeadLimit.'),
},
required: <String>['pattern'],
run: (input) async {
final pattern = requireString(input, 'pattern');
final pathInput = readString(input, 'path');
final glob = readString(input, 'glob')?.trim();
final type = readString(input, 'type')?.trim();
final outputMode = readString(input, 'output_mode')?.trim() ?? 'files_with_matches';
final headLimit = readInt(input, 'head_limit') ?? _defaultHeadLimit;
final searchPath = pathInput == null ? runtime.currentWorkingDirectory : runtime.resolvePath(pathInput);
final isContent = outputMode == 'content';
final args = <String>[
switch (outputMode) {
'content' => '--line-number',
'count' => '--count',
'files_with_matches' => '--files-with-matches',
_ => throw const ToolFailure('`output_mode` must be content, files_with_matches, or count.'),
},
'--no-heading',
'--color=never',
'--max-columns=500',
if (readBool(input, '-i') ?? false) '--ignore-case',
if (readBool(input, 'multiline') ?? false) ...<String>['--multiline', '--multiline-dotall'],
if (glob != null && glob.isNotEmpty) ...<String>['--glob', glob],
if (type != null && type.isNotEmpty) ...<String>['--type', type],
if (isContent) ..._contextArgs(input),
'--',
pattern,
searchPath,
];
runtime.startTool('grep', '$pattern in ${runtime.displayPath(searchPath)}');
final ProcessResult result;
try {
result = await Process.run('rg', args, workingDirectory: runtime.currentWorkingDirectory);
} on ProcessException {
throw const ToolFailure('ripgrep (`rg`) is not installed. Ask the user to install it.');
}
if (result.exitCode == 1) {
return 'No matches found.';
}
if (result.exitCode > 1) {
throw ToolFailure('rg exited with code ${result.exitCode}: ${'${result.stderr}'.trim()}');
}
final lines = '${result.stdout}'.trimRight().split('\n');
final shown = headLimit > 0 && lines.length > headLimit ? lines.take(headLimit).toList() : lines;
final output = truncateMiddle(shown.join('\n'), _maxOutputChars);
if (shown.length < lines.length) {
return '$output\n\n(Showing $headLimit of ${lines.length} lines. Narrow the search to see more.)';
}
return output;
},
);
}