btca-server 1.0.961 → 2.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/package.json +3 -3
- package/src/agent/agent.test.ts +31 -24
- package/src/agent/index.ts +8 -2
- package/src/agent/loop.ts +303 -346
- package/src/agent/service.ts +252 -233
- package/src/agent/types.ts +2 -2
- package/src/collections/index.ts +2 -1
- package/src/collections/service.ts +352 -345
- package/src/config/config.test.ts +3 -1
- package/src/config/index.ts +615 -727
- package/src/config/remote.ts +214 -369
- package/src/context/index.ts +6 -12
- package/src/context/transaction.ts +23 -30
- package/src/effect/errors.ts +45 -0
- package/src/effect/layers.ts +26 -0
- package/src/effect/runtime.ts +19 -0
- package/src/effect/services.ts +154 -0
- package/src/index.ts +291 -369
- package/src/metrics/index.ts +46 -46
- package/src/pricing/models-dev.ts +104 -106
- package/src/providers/auth.ts +159 -200
- package/src/providers/index.ts +19 -2
- package/src/providers/model.ts +115 -135
- package/src/providers/openai.ts +3 -3
- package/src/resources/impls/git.ts +123 -146
- package/src/resources/impls/npm.test.ts +16 -5
- package/src/resources/impls/npm.ts +66 -75
- package/src/resources/index.ts +6 -1
- package/src/resources/schema.ts +7 -6
- package/src/resources/service.test.ts +13 -12
- package/src/resources/service.ts +153 -112
- package/src/stream/index.ts +1 -1
- package/src/stream/service.test.ts +5 -5
- package/src/stream/service.ts +282 -293
- package/src/tools/glob.ts +126 -141
- package/src/tools/grep.ts +205 -210
- package/src/tools/index.ts +8 -4
- package/src/tools/list.ts +118 -140
- package/src/tools/read.ts +209 -235
- package/src/tools/virtual-sandbox.ts +91 -83
- package/src/validation/index.ts +18 -22
- package/src/vfs/virtual-fs.test.ts +37 -25
- package/src/vfs/virtual-fs.ts +218 -216
package/src/tools/grep.ts
CHANGED
|
@@ -4,241 +4,236 @@
|
|
|
4
4
|
*/
|
|
5
5
|
import * as path from 'node:path';
|
|
6
6
|
import { z } from 'zod';
|
|
7
|
-
import { Result } from 'better-result';
|
|
8
7
|
|
|
9
8
|
import type { ToolContext } from './context.ts';
|
|
10
|
-
import {
|
|
11
|
-
import {
|
|
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
|
-
const safeStat = async (filePath: string, vfsId?: string) => {
|
|
44
|
-
const result = await Result.tryPromise(() => VirtualFs.stat(filePath, vfsId));
|
|
45
|
-
return result.match({
|
|
46
|
-
ok: (value) => value,
|
|
47
|
-
err: () => null
|
|
48
|
-
});
|
|
49
|
-
};
|
|
50
|
-
|
|
51
|
-
const safeReadBuffer = async (filePath: string, vfsId?: string) => {
|
|
52
|
-
const result = await Result.tryPromise(() => VirtualFs.readFileBuffer(filePath, vfsId));
|
|
53
|
-
return result.match({
|
|
54
|
-
ok: (value) => value,
|
|
55
|
-
err: () => null
|
|
56
|
-
});
|
|
9
|
+
import { resolveSandboxPath } from './virtual-sandbox.ts';
|
|
10
|
+
import {
|
|
11
|
+
listVirtualFsFilesRecursive,
|
|
12
|
+
readVirtualFsFile,
|
|
13
|
+
readVirtualFsFileBuffer,
|
|
14
|
+
statVirtualFs
|
|
15
|
+
} from '../vfs/virtual-fs.ts';
|
|
16
|
+
|
|
17
|
+
const MAX_RESULTS = 100;
|
|
18
|
+
|
|
19
|
+
export const GrepToolParameters = z.object({
|
|
20
|
+
pattern: z.string().describe('The regex pattern to search for in file contents'),
|
|
21
|
+
path: z
|
|
22
|
+
.string()
|
|
23
|
+
.optional()
|
|
24
|
+
.describe('The directory to search in. Defaults to the collection root.'),
|
|
25
|
+
include: z
|
|
26
|
+
.string()
|
|
27
|
+
.optional()
|
|
28
|
+
.describe('File pattern to include in the search (e.g. "*.js", "*.{ts,tsx}")')
|
|
29
|
+
});
|
|
30
|
+
|
|
31
|
+
export type GrepToolParametersType = z.infer<typeof GrepToolParameters>;
|
|
32
|
+
|
|
33
|
+
export type GrepToolResult = {
|
|
34
|
+
title: string;
|
|
35
|
+
output: string;
|
|
36
|
+
metadata: {
|
|
37
|
+
matchCount: number;
|
|
38
|
+
fileCount: number;
|
|
39
|
+
truncated: boolean;
|
|
57
40
|
};
|
|
41
|
+
};
|
|
58
42
|
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
* Execute the grep tool
|
|
67
|
-
*/
|
|
68
|
-
export async function execute(params: ParametersType, context: ToolContext): Promise<Result> {
|
|
69
|
-
const { basePath, vfsId } = context;
|
|
70
|
-
|
|
71
|
-
// Resolve search path within sandbox
|
|
72
|
-
const searchPath = params.path ? VirtualSandbox.resolvePath(basePath, params.path) : basePath;
|
|
73
|
-
|
|
74
|
-
// Validate the search path exists and is a directory
|
|
75
|
-
const stats = await safeStat(searchPath, vfsId);
|
|
76
|
-
if (!stats) {
|
|
77
|
-
return {
|
|
78
|
-
title: params.pattern,
|
|
79
|
-
output: `Directory not found: ${params.path || '.'}`,
|
|
80
|
-
metadata: {
|
|
81
|
-
matchCount: 0,
|
|
82
|
-
fileCount: 0,
|
|
83
|
-
truncated: false
|
|
84
|
-
}
|
|
85
|
-
};
|
|
86
|
-
}
|
|
87
|
-
if (!stats.isDirectory) {
|
|
88
|
-
return {
|
|
89
|
-
title: params.pattern,
|
|
90
|
-
output: `Path is not a directory: ${params.path || '.'}`,
|
|
91
|
-
metadata: {
|
|
92
|
-
matchCount: 0,
|
|
93
|
-
fileCount: 0,
|
|
94
|
-
truncated: false
|
|
95
|
-
}
|
|
96
|
-
};
|
|
97
|
-
}
|
|
98
|
-
|
|
99
|
-
const regex = compileRegex(params.pattern);
|
|
100
|
-
if (!regex) {
|
|
101
|
-
return {
|
|
102
|
-
title: params.pattern,
|
|
103
|
-
output: 'Invalid regex pattern.',
|
|
104
|
-
metadata: {
|
|
105
|
-
matchCount: 0,
|
|
106
|
-
fileCount: 0,
|
|
107
|
-
truncated: false
|
|
108
|
-
}
|
|
109
|
-
};
|
|
110
|
-
}
|
|
43
|
+
const safeStat = async (filePath: string, vfsId?: string) => {
|
|
44
|
+
try {
|
|
45
|
+
return await statVirtualFs(filePath, vfsId);
|
|
46
|
+
} catch {
|
|
47
|
+
return null;
|
|
48
|
+
}
|
|
49
|
+
};
|
|
111
50
|
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
51
|
+
const safeReadBuffer = async (filePath: string, vfsId?: string) => {
|
|
52
|
+
try {
|
|
53
|
+
return await readVirtualFsFileBuffer(filePath, vfsId);
|
|
54
|
+
} catch {
|
|
55
|
+
return null;
|
|
56
|
+
}
|
|
57
|
+
};
|
|
116
58
|
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
59
|
+
const compileRegex = (pattern: string) => {
|
|
60
|
+
try {
|
|
61
|
+
return new RegExp(pattern);
|
|
62
|
+
} catch {
|
|
63
|
+
return null;
|
|
64
|
+
}
|
|
65
|
+
};
|
|
66
|
+
|
|
67
|
+
export const executeGrepTool = async (
|
|
68
|
+
params: GrepToolParametersType,
|
|
69
|
+
context: ToolContext
|
|
70
|
+
): Promise<GrepToolResult> => {
|
|
71
|
+
const { basePath, vfsId } = context;
|
|
72
|
+
const searchPath = params.path ? resolveSandboxPath(basePath, params.path) : basePath;
|
|
73
|
+
const stats = await safeStat(searchPath, vfsId);
|
|
74
|
+
if (!stats) {
|
|
75
|
+
return {
|
|
76
|
+
title: params.pattern,
|
|
77
|
+
output: `Directory not found: ${params.path || '.'}`,
|
|
78
|
+
metadata: {
|
|
79
|
+
matchCount: 0,
|
|
80
|
+
fileCount: 0,
|
|
81
|
+
truncated: false
|
|
138
82
|
}
|
|
139
|
-
}
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
}
|
|
150
|
-
};
|
|
151
|
-
}
|
|
152
|
-
|
|
153
|
-
const truncated = results.length > MAX_RESULTS;
|
|
154
|
-
const displayResults = truncated ? results.slice(0, MAX_RESULTS) : results;
|
|
155
|
-
displayResults.sort((a, b) => b.mtime - a.mtime);
|
|
156
|
-
|
|
157
|
-
const fileGroups = new Map<string, Array<{ lineNumber: number; lineText: string }>>();
|
|
158
|
-
for (const result of displayResults) {
|
|
159
|
-
const relativePath = path.posix.relative(basePath, result.path);
|
|
160
|
-
if (!fileGroups.has(relativePath)) {
|
|
161
|
-
fileGroups.set(relativePath, []);
|
|
83
|
+
};
|
|
84
|
+
}
|
|
85
|
+
if (!stats.isDirectory) {
|
|
86
|
+
return {
|
|
87
|
+
title: params.pattern,
|
|
88
|
+
output: `Path is not a directory: ${params.path || '.'}`,
|
|
89
|
+
metadata: {
|
|
90
|
+
matchCount: 0,
|
|
91
|
+
fileCount: 0,
|
|
92
|
+
truncated: false
|
|
162
93
|
}
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
lineText: result.lineText
|
|
166
|
-
});
|
|
167
|
-
}
|
|
94
|
+
};
|
|
95
|
+
}
|
|
168
96
|
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
97
|
+
const regex = compileRegex(params.pattern);
|
|
98
|
+
if (!regex) {
|
|
99
|
+
return {
|
|
100
|
+
title: params.pattern,
|
|
101
|
+
output: 'Invalid regex pattern.',
|
|
102
|
+
metadata: {
|
|
103
|
+
matchCount: 0,
|
|
104
|
+
fileCount: 0,
|
|
105
|
+
truncated: false
|
|
176
106
|
}
|
|
177
|
-
|
|
178
|
-
|
|
107
|
+
};
|
|
108
|
+
}
|
|
179
109
|
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
110
|
+
const includeMatcher = params.include ? buildIncludeMatcher(params.include) : null;
|
|
111
|
+
const allFiles = await listVirtualFsFilesRecursive(searchPath, vfsId);
|
|
112
|
+
const results: Array<{ path: string; lineNumber: number; lineText: string; mtime: number }> = [];
|
|
113
|
+
|
|
114
|
+
for (const filePath of allFiles) {
|
|
115
|
+
if (results.length > MAX_RESULTS) break;
|
|
116
|
+
const relative = path.posix.relative(searchPath, filePath);
|
|
117
|
+
if (includeMatcher && !includeMatcher(relative)) continue;
|
|
118
|
+
const buffer = await safeReadBuffer(filePath, vfsId);
|
|
119
|
+
if (!buffer || isBinaryBuffer(buffer)) continue;
|
|
120
|
+
const text = await readVirtualFsFile(filePath, vfsId);
|
|
121
|
+
const lines = text.split('\n');
|
|
122
|
+
const fileStats = await safeStat(filePath, vfsId);
|
|
123
|
+
const mtime = fileStats?.mtimeMs ?? 0;
|
|
124
|
+
for (let i = 0; i < lines.length; i++) {
|
|
125
|
+
const lineText = lines[i] ?? '';
|
|
126
|
+
if (!regex.test(lineText)) continue;
|
|
127
|
+
results.push({
|
|
128
|
+
path: filePath,
|
|
129
|
+
lineNumber: i + 1,
|
|
130
|
+
lineText,
|
|
131
|
+
mtime
|
|
132
|
+
});
|
|
133
|
+
if (results.length > MAX_RESULTS) break;
|
|
184
134
|
}
|
|
135
|
+
}
|
|
185
136
|
|
|
137
|
+
if (results.length === 0) {
|
|
186
138
|
return {
|
|
187
139
|
title: params.pattern,
|
|
188
|
-
output:
|
|
140
|
+
output: 'No matches found.',
|
|
189
141
|
metadata: {
|
|
190
|
-
matchCount:
|
|
191
|
-
fileCount:
|
|
192
|
-
truncated
|
|
142
|
+
matchCount: 0,
|
|
143
|
+
fileCount: 0,
|
|
144
|
+
truncated: false
|
|
193
145
|
}
|
|
194
146
|
};
|
|
195
147
|
}
|
|
196
148
|
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
149
|
+
const truncated = results.length > MAX_RESULTS;
|
|
150
|
+
const displayResults = truncated ? results.slice(0, MAX_RESULTS) : results;
|
|
151
|
+
displayResults.sort((a, b) => b.mtime - a.mtime);
|
|
152
|
+
|
|
153
|
+
const fileGroups = new Map<string, Array<{ lineNumber: number; lineText: string }>>();
|
|
154
|
+
for (const result of displayResults) {
|
|
155
|
+
const relativePath = path.posix.relative(basePath, result.path);
|
|
156
|
+
if (!fileGroups.has(relativePath)) {
|
|
157
|
+
fileGroups.set(relativePath, []);
|
|
200
158
|
}
|
|
201
|
-
|
|
159
|
+
fileGroups.get(relativePath)!.push({
|
|
160
|
+
lineNumber: result.lineNumber,
|
|
161
|
+
lineText: result.lineText
|
|
162
|
+
});
|
|
202
163
|
}
|
|
203
164
|
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
const
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
regex += '.*';
|
|
212
|
-
i += 2;
|
|
213
|
-
continue;
|
|
214
|
-
}
|
|
215
|
-
if (char === '*') {
|
|
216
|
-
regex += '[^/]*';
|
|
217
|
-
i += 1;
|
|
218
|
-
continue;
|
|
219
|
-
}
|
|
220
|
-
if (char === '?') {
|
|
221
|
-
regex += '[^/]';
|
|
222
|
-
i += 1;
|
|
223
|
-
continue;
|
|
224
|
-
}
|
|
225
|
-
if ('\\.^$+{}()|[]'.includes(char)) {
|
|
226
|
-
regex += '\\' + char;
|
|
227
|
-
} else {
|
|
228
|
-
regex += char;
|
|
229
|
-
}
|
|
230
|
-
i += 1;
|
|
165
|
+
const outputLines: string[] = [];
|
|
166
|
+
for (const [filePath, matches] of fileGroups) {
|
|
167
|
+
outputLines.push(`${filePath}:`);
|
|
168
|
+
for (const match of matches) {
|
|
169
|
+
const lineText =
|
|
170
|
+
match.lineText.length > 200 ? match.lineText.substring(0, 200) + '...' : match.lineText;
|
|
171
|
+
outputLines.push(` ${match.lineNumber}: ${lineText}`);
|
|
231
172
|
}
|
|
232
|
-
|
|
233
|
-
|
|
173
|
+
outputLines.push('');
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
if (truncated) {
|
|
177
|
+
outputLines.push(
|
|
178
|
+
`[Truncated: Results limited to ${MAX_RESULTS} matches. Narrow your search pattern for more specific results.]`
|
|
179
|
+
);
|
|
234
180
|
}
|
|
235
181
|
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
182
|
+
return {
|
|
183
|
+
title: params.pattern,
|
|
184
|
+
output: outputLines.join('\n').trim(),
|
|
185
|
+
metadata: {
|
|
186
|
+
matchCount: displayResults.length,
|
|
187
|
+
fileCount: fileGroups.size,
|
|
188
|
+
truncated
|
|
189
|
+
}
|
|
190
|
+
};
|
|
191
|
+
};
|
|
192
|
+
|
|
193
|
+
const isBinaryBuffer = (bytes: Uint8Array): boolean => {
|
|
194
|
+
for (const byte of bytes) {
|
|
195
|
+
if (byte === 0) return true;
|
|
196
|
+
}
|
|
197
|
+
return false;
|
|
198
|
+
};
|
|
199
|
+
|
|
200
|
+
const globToRegExp = (pattern: string): RegExp => {
|
|
201
|
+
let regex = '^';
|
|
202
|
+
let i = 0;
|
|
203
|
+
while (i < pattern.length) {
|
|
204
|
+
const char = pattern[i] ?? '';
|
|
205
|
+
const next = pattern[i + 1] ?? '';
|
|
206
|
+
if (char === '*' && next === '*') {
|
|
207
|
+
regex += '.*';
|
|
208
|
+
i += 2;
|
|
209
|
+
continue;
|
|
210
|
+
}
|
|
211
|
+
if (char === '*') {
|
|
212
|
+
regex += '[^/]*';
|
|
213
|
+
i += 1;
|
|
214
|
+
continue;
|
|
241
215
|
}
|
|
242
|
-
|
|
216
|
+
if (char === '?') {
|
|
217
|
+
regex += '[^/]';
|
|
218
|
+
i += 1;
|
|
219
|
+
continue;
|
|
220
|
+
}
|
|
221
|
+
if ('\\.^$+{}()|[]'.includes(char)) {
|
|
222
|
+
regex += '\\' + char;
|
|
223
|
+
} else {
|
|
224
|
+
regex += char;
|
|
225
|
+
}
|
|
226
|
+
i += 1;
|
|
227
|
+
}
|
|
228
|
+
regex += '$';
|
|
229
|
+
return new RegExp(regex);
|
|
230
|
+
};
|
|
231
|
+
|
|
232
|
+
const buildIncludeMatcher = (pattern: string): ((relativePath: string) => boolean) => {
|
|
233
|
+
const regex = globToRegExp(pattern);
|
|
234
|
+
if (!pattern.includes('/')) {
|
|
235
|
+
return (relativePath) =>
|
|
236
|
+
regex.test(path.posix.basename(relativePath)) || regex.test(relativePath);
|
|
243
237
|
}
|
|
244
|
-
|
|
238
|
+
return (relativePath) => regex.test(relativePath);
|
|
239
|
+
};
|
package/src/tools/index.ts
CHANGED
|
@@ -2,7 +2,11 @@
|
|
|
2
2
|
* Tools Module
|
|
3
3
|
* Exports all agent tools and utilities
|
|
4
4
|
*/
|
|
5
|
-
export {
|
|
6
|
-
export {
|
|
7
|
-
export {
|
|
8
|
-
export {
|
|
5
|
+
export { ReadToolParameters, executeReadTool } from './read.ts';
|
|
6
|
+
export { GrepToolParameters, executeGrepTool } from './grep.ts';
|
|
7
|
+
export { GlobToolParameters, executeGlobTool } from './glob.ts';
|
|
8
|
+
export { ListToolParameters, executeListTool } from './list.ts';
|
|
9
|
+
export type { ReadToolParametersType, ReadToolResult } from './read.ts';
|
|
10
|
+
export type { GrepToolParametersType, GrepToolResult } from './grep.ts';
|
|
11
|
+
export type { GlobToolParametersType, GlobToolResult } from './glob.ts';
|
|
12
|
+
export type { ListToolParametersType, ListToolEntry, ListToolResult } from './list.ts';
|