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.
Files changed (43) hide show
  1. package/package.json +3 -3
  2. package/src/agent/agent.test.ts +31 -24
  3. package/src/agent/index.ts +8 -2
  4. package/src/agent/loop.ts +303 -346
  5. package/src/agent/service.ts +252 -233
  6. package/src/agent/types.ts +2 -2
  7. package/src/collections/index.ts +2 -1
  8. package/src/collections/service.ts +352 -345
  9. package/src/config/config.test.ts +3 -1
  10. package/src/config/index.ts +615 -727
  11. package/src/config/remote.ts +214 -369
  12. package/src/context/index.ts +6 -12
  13. package/src/context/transaction.ts +23 -30
  14. package/src/effect/errors.ts +45 -0
  15. package/src/effect/layers.ts +26 -0
  16. package/src/effect/runtime.ts +19 -0
  17. package/src/effect/services.ts +154 -0
  18. package/src/index.ts +291 -369
  19. package/src/metrics/index.ts +46 -46
  20. package/src/pricing/models-dev.ts +104 -106
  21. package/src/providers/auth.ts +159 -200
  22. package/src/providers/index.ts +19 -2
  23. package/src/providers/model.ts +115 -135
  24. package/src/providers/openai.ts +3 -3
  25. package/src/resources/impls/git.ts +123 -146
  26. package/src/resources/impls/npm.test.ts +16 -5
  27. package/src/resources/impls/npm.ts +66 -75
  28. package/src/resources/index.ts +6 -1
  29. package/src/resources/schema.ts +7 -6
  30. package/src/resources/service.test.ts +13 -12
  31. package/src/resources/service.ts +153 -112
  32. package/src/stream/index.ts +1 -1
  33. package/src/stream/service.test.ts +5 -5
  34. package/src/stream/service.ts +282 -293
  35. package/src/tools/glob.ts +126 -141
  36. package/src/tools/grep.ts +205 -210
  37. package/src/tools/index.ts +8 -4
  38. package/src/tools/list.ts +118 -140
  39. package/src/tools/read.ts +209 -235
  40. package/src/tools/virtual-sandbox.ts +91 -83
  41. package/src/validation/index.ts +18 -22
  42. package/src/vfs/virtual-fs.test.ts +37 -25
  43. 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 { VirtualSandbox } from './virtual-sandbox.ts';
11
- import { VirtualFs } from '../vfs/virtual-fs.ts';
12
-
13
- export namespace GrepTool {
14
- // Configuration
15
- const MAX_RESULTS = 100;
16
-
17
- // Schema for tool parameters
18
- export const Parameters = z.object({
19
- pattern: z.string().describe('The regex pattern to search for in file contents'),
20
- path: z
21
- .string()
22
- .optional()
23
- .describe('The directory to search in. Defaults to the collection root.'),
24
- include: z
25
- .string()
26
- .optional()
27
- .describe('File pattern to include in the search (e.g. "*.js", "*.{ts,tsx}")')
28
- });
29
-
30
- export type ParametersType = z.infer<typeof Parameters>;
31
-
32
- // Result type
33
- export type Result = {
34
- title: string;
35
- output: string;
36
- metadata: {
37
- matchCount: number;
38
- fileCount: number;
39
- truncated: boolean;
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
- const compileRegex = (pattern: string) =>
60
- Result.try(() => new RegExp(pattern)).match({
61
- ok: (value) => value,
62
- err: () => null
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
- const includeMatcher = params.include ? buildIncludeMatcher(params.include) : null;
113
- const allFiles = await VirtualFs.listFilesRecursive(searchPath, vfsId);
114
- const results: Array<{ path: string; lineNumber: number; lineText: string; mtime: number }> =
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
- for (const filePath of allFiles) {
118
- if (results.length > MAX_RESULTS) break;
119
- const relative = path.posix.relative(searchPath, filePath);
120
- if (includeMatcher && !includeMatcher(relative)) continue;
121
- const buffer = await safeReadBuffer(filePath, vfsId);
122
- if (!buffer) continue;
123
- if (isBinaryBuffer(buffer)) continue;
124
- const text = await VirtualFs.readFile(filePath, vfsId);
125
- const lines = text.split('\n');
126
- const fileStats = await safeStat(filePath, vfsId);
127
- const mtime = fileStats?.mtimeMs ?? 0;
128
- for (let i = 0; i < lines.length; i++) {
129
- const lineText = lines[i] ?? '';
130
- if (!regex.test(lineText)) continue;
131
- results.push({
132
- path: filePath,
133
- lineNumber: i + 1,
134
- lineText,
135
- mtime
136
- });
137
- if (results.length > MAX_RESULTS) break;
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
- if (results.length === 0) {
142
- return {
143
- title: params.pattern,
144
- output: 'No matches found.',
145
- metadata: {
146
- matchCount: 0,
147
- fileCount: 0,
148
- truncated: false
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
- fileGroups.get(relativePath)!.push({
164
- lineNumber: result.lineNumber,
165
- lineText: result.lineText
166
- });
167
- }
94
+ };
95
+ }
168
96
 
169
- const outputLines: string[] = [];
170
- for (const [filePath, matches] of fileGroups) {
171
- outputLines.push(`${filePath}:`);
172
- for (const match of matches) {
173
- const lineText =
174
- match.lineText.length > 200 ? match.lineText.substring(0, 200) + '...' : match.lineText;
175
- outputLines.push(` ${match.lineNumber}: ${lineText}`);
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
- outputLines.push('');
178
- }
107
+ };
108
+ }
179
109
 
180
- if (truncated) {
181
- outputLines.push(
182
- `[Truncated: Results limited to ${MAX_RESULTS} matches. Narrow your search pattern for more specific results.]`
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: outputLines.join('\n').trim(),
140
+ output: 'No matches found.',
189
141
  metadata: {
190
- matchCount: displayResults.length,
191
- fileCount: fileGroups.size,
192
- truncated
142
+ matchCount: 0,
143
+ fileCount: 0,
144
+ truncated: false
193
145
  }
194
146
  };
195
147
  }
196
148
 
197
- function isBinaryBuffer(bytes: Uint8Array): boolean {
198
- for (const byte of bytes) {
199
- if (byte === 0) return true;
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
- return false;
159
+ fileGroups.get(relativePath)!.push({
160
+ lineNumber: result.lineNumber,
161
+ lineText: result.lineText
162
+ });
202
163
  }
203
164
 
204
- function globToRegExp(pattern: string): RegExp {
205
- let regex = '^';
206
- let i = 0;
207
- while (i < pattern.length) {
208
- const char = pattern[i] ?? '';
209
- const next = pattern[i + 1] ?? '';
210
- if (char === '*' && next === '*') {
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
- regex += '$';
233
- return new RegExp(regex);
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
- function buildIncludeMatcher(pattern: string): (relativePath: string) => boolean {
237
- const regex = globToRegExp(pattern);
238
- if (!pattern.includes('/')) {
239
- return (relativePath) =>
240
- regex.test(path.posix.basename(relativePath)) || regex.test(relativePath);
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
- return (relativePath) => regex.test(relativePath);
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
+ };
@@ -2,7 +2,11 @@
2
2
  * Tools Module
3
3
  * Exports all agent tools and utilities
4
4
  */
5
- export { ReadTool } from './read.ts';
6
- export { GrepTool } from './grep.ts';
7
- export { GlobTool } from './glob.ts';
8
- export { ListTool } from './list.ts';
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';