-
Notifications
You must be signed in to change notification settings - Fork 145
Expand file tree
/
Copy pathgrep.ts
More file actions
97 lines (94 loc) · 3.21 KB
/
grep.ts
File metadata and controls
97 lines (94 loc) · 3.21 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
import fs from 'fs';
import path from 'pathe';
import { z } from 'zod';
import { TOOL_NAMES } from '../constants';
import { createTool } from '../tool';
import { ripGrep } from '../utils/ripgrep';
import { safeStringify } from '../utils/safeStringify';
const DEFAULT_LIMIT = 1000;
export function createGrepTool(opts: { cwd: string }) {
return createTool({
name: TOOL_NAMES.GREP,
description: `Search for a pattern in a file or directory.`,
parameters: z.object({
pattern: z.string().describe('The pattern to search for'),
search_path: z.string().optional().describe('The path to search in'),
include: z
.string()
.optional()
.describe('The file pattern to include in the search'),
limit: z
.number()
// .positive()
.max(DEFAULT_LIMIT)
.optional()
.describe(
`Maximum number of files to return (positive number, default: ${DEFAULT_LIMIT})`,
),
}),
getDescription: ({ params }) => {
if (!params.pattern || typeof params.pattern !== 'string') {
return 'No pattern provided';
}
return String(params.pattern);
},
execute: async ({ pattern, search_path, include, limit }) => {
try {
const start = Date.now();
const args = ['-li', pattern];
if (include) {
args.push('--glob', include);
}
const absolutePath = search_path
? path.isAbsolute(search_path)
? search_path
: path.resolve(opts.cwd, search_path)
: opts.cwd;
const results = await ripGrep(args, absolutePath);
const stats = await Promise.all(results.map((_) => fs.statSync(_)));
const allMatches = results
// Sort by modification time
.map((_, i) => [_, stats[i]!] as const)
.sort((a, b) => {
if (process.env.NODE_ENV === 'test') {
// In tests, we always want to sort by filename, so that results are deterministic
return a[0].localeCompare(b[0]);
}
const timeComparison = (b[1].mtimeMs ?? 0) - (a[1].mtimeMs ?? 0);
if (timeComparison === 0) {
return a[0].localeCompare(b[0]);
}
return timeComparison;
})
.map((_) => _[0]);
const maxFiles = limit ?? DEFAULT_LIMIT;
const totalFiles = allMatches.length;
const truncated = totalFiles > maxFiles;
const matches = allMatches.slice(0, maxFiles);
const returnedFiles = matches.length;
const durationMs = Date.now() - start;
const returnDisplay = truncated
? `Found ${totalFiles} files (showing first ${returnedFiles} of ${totalFiles} total) in ${durationMs}ms.`
: `Found ${totalFiles} files in ${durationMs}ms.`;
return {
returnDisplay,
llmContent: safeStringify({
filenames: matches,
durationMs,
totalFiles,
returnedFiles,
truncated,
}),
};
} catch (e) {
return {
isError: true,
llmContent: e instanceof Error ? e.message : 'Unknown error',
};
}
},
approval: {
category: 'read',
},
});
}