btca-server 1.0.63 → 1.0.71
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/README.md +4 -1
- package/package.json +4 -2
- package/src/agent/agent.test.ts +114 -16
- package/src/agent/loop.ts +14 -11
- package/src/agent/service.ts +117 -86
- package/src/collections/index.ts +0 -0
- package/src/collections/service.ts +187 -57
- package/src/collections/types.ts +1 -0
- package/src/collections/virtual-metadata.ts +32 -0
- package/src/config/config.test.ts +0 -0
- package/src/config/index.ts +195 -127
- package/src/config/remote.ts +132 -79
- package/src/context/index.ts +0 -0
- package/src/context/transaction.ts +20 -15
- package/src/errors.ts +0 -0
- package/src/index.ts +29 -15
- package/src/metrics/index.ts +18 -13
- package/src/providers/auth.ts +38 -11
- package/src/providers/model.ts +3 -1
- package/src/providers/openrouter.ts +39 -0
- package/src/providers/registry.ts +2 -0
- package/src/resources/helpers.ts +0 -0
- package/src/resources/impls/git.test.ts +0 -0
- package/src/resources/impls/git.ts +160 -117
- package/src/resources/index.ts +0 -0
- package/src/resources/schema.ts +24 -27
- package/src/resources/service.ts +0 -0
- package/src/resources/types.ts +0 -0
- package/src/stream/index.ts +0 -0
- package/src/stream/service.ts +23 -14
- package/src/tools/context.ts +4 -0
- package/src/tools/glob.ts +72 -45
- package/src/tools/grep.ts +136 -57
- package/src/tools/index.ts +0 -2
- package/src/tools/list.ts +34 -53
- package/src/tools/read.ts +46 -32
- package/src/tools/virtual-sandbox.ts +103 -0
- package/src/validation/index.ts +12 -12
- package/src/vfs/virtual-fs.test.ts +107 -0
- package/src/vfs/virtual-fs.ts +273 -0
- package/src/tools/ripgrep.ts +0 -348
- package/src/tools/sandbox.ts +0 -164
|
@@ -1,13 +1,21 @@
|
|
|
1
|
-
import { promises as fs } from 'node:fs';
|
|
2
1
|
import path from 'node:path';
|
|
3
2
|
|
|
3
|
+
import { Result } from 'better-result';
|
|
4
|
+
|
|
4
5
|
import { Config } from '../config/index.ts';
|
|
5
6
|
import { Transaction } from '../context/transaction.ts';
|
|
6
7
|
import { CommonHints, getErrorHint, getErrorMessage } from '../errors.ts';
|
|
7
8
|
import { Metrics } from '../metrics/index.ts';
|
|
8
9
|
import { Resources } from '../resources/service.ts';
|
|
10
|
+
import { isGitResource } from '../resources/schema.ts';
|
|
9
11
|
import { FS_RESOURCE_SYSTEM_NOTE, type BtcaFsResource } from '../resources/types.ts';
|
|
10
12
|
import { CollectionError, getCollectionKey, type CollectionResult } from './types.ts';
|
|
13
|
+
import { VirtualFs } from '../vfs/virtual-fs.ts';
|
|
14
|
+
import {
|
|
15
|
+
clearVirtualCollectionMetadata,
|
|
16
|
+
setVirtualCollectionMetadata,
|
|
17
|
+
type VirtualResourceMetadata
|
|
18
|
+
} from './virtual-metadata.ts';
|
|
11
19
|
|
|
12
20
|
export namespace Collections {
|
|
13
21
|
export type Service = {
|
|
@@ -32,6 +40,126 @@ export namespace Collections {
|
|
|
32
40
|
return lines.join('\n');
|
|
33
41
|
};
|
|
34
42
|
|
|
43
|
+
const ignoreErrors = async (action: () => Promise<unknown>) => {
|
|
44
|
+
const result = await Result.tryPromise(action);
|
|
45
|
+
result.match({
|
|
46
|
+
ok: () => undefined,
|
|
47
|
+
err: () => undefined
|
|
48
|
+
});
|
|
49
|
+
};
|
|
50
|
+
|
|
51
|
+
const initVirtualRoot = (collectionPath: string, vfsId: string) =>
|
|
52
|
+
Result.tryPromise({
|
|
53
|
+
try: () => VirtualFs.mkdir(collectionPath, { recursive: true }, vfsId),
|
|
54
|
+
catch: (cause) =>
|
|
55
|
+
new CollectionError({
|
|
56
|
+
message: `Failed to initialize virtual collection root: "${collectionPath}"`,
|
|
57
|
+
hint: 'Check that the virtual filesystem is available.',
|
|
58
|
+
cause
|
|
59
|
+
})
|
|
60
|
+
});
|
|
61
|
+
|
|
62
|
+
const loadResource = (resources: Resources.Service, name: string, quiet: boolean) =>
|
|
63
|
+
Result.tryPromise({
|
|
64
|
+
try: () => resources.load(name, { quiet }),
|
|
65
|
+
catch: (cause) => {
|
|
66
|
+
const underlyingHint = getErrorHint(cause);
|
|
67
|
+
const underlyingMessage = getErrorMessage(cause);
|
|
68
|
+
return new CollectionError({
|
|
69
|
+
message: `Failed to load resource "${name}": ${underlyingMessage}`,
|
|
70
|
+
hint:
|
|
71
|
+
underlyingHint ??
|
|
72
|
+
`${CommonHints.CLEAR_CACHE} Check that the resource "${name}" is correctly configured.`,
|
|
73
|
+
cause
|
|
74
|
+
});
|
|
75
|
+
}
|
|
76
|
+
});
|
|
77
|
+
|
|
78
|
+
const resolveResourcePath = (resource: BtcaFsResource) =>
|
|
79
|
+
Result.tryPromise({
|
|
80
|
+
try: () => resource.getAbsoluteDirectoryPath(),
|
|
81
|
+
catch: (cause) =>
|
|
82
|
+
new CollectionError({
|
|
83
|
+
message: `Failed to get path for resource "${resource.name}"`,
|
|
84
|
+
hint: CommonHints.CLEAR_CACHE,
|
|
85
|
+
cause
|
|
86
|
+
})
|
|
87
|
+
});
|
|
88
|
+
|
|
89
|
+
const virtualizeResource = (args: {
|
|
90
|
+
resource: BtcaFsResource;
|
|
91
|
+
resourcePath: string;
|
|
92
|
+
virtualResourcePath: string;
|
|
93
|
+
vfsId: string;
|
|
94
|
+
}) =>
|
|
95
|
+
Result.tryPromise({
|
|
96
|
+
try: () =>
|
|
97
|
+
VirtualFs.importDirectoryFromDisk({
|
|
98
|
+
sourcePath: args.resourcePath,
|
|
99
|
+
destinationPath: args.virtualResourcePath,
|
|
100
|
+
vfsId: args.vfsId,
|
|
101
|
+
ignore: (relativePath) => {
|
|
102
|
+
const normalized = relativePath.split(path.sep).join('/');
|
|
103
|
+
return (
|
|
104
|
+
normalized === '.git' ||
|
|
105
|
+
normalized.startsWith('.git/') ||
|
|
106
|
+
normalized.includes('/.git/')
|
|
107
|
+
);
|
|
108
|
+
}
|
|
109
|
+
}),
|
|
110
|
+
catch: (cause) =>
|
|
111
|
+
new CollectionError({
|
|
112
|
+
message: `Failed to virtualize resource "${args.resource.name}"`,
|
|
113
|
+
hint: CommonHints.CLEAR_CACHE,
|
|
114
|
+
cause
|
|
115
|
+
})
|
|
116
|
+
});
|
|
117
|
+
|
|
118
|
+
const getGitHeadHash = async (resourcePath: string) => {
|
|
119
|
+
const result = await Result.tryPromise(async () => {
|
|
120
|
+
const proc = Bun.spawn(['git', 'rev-parse', 'HEAD'], {
|
|
121
|
+
cwd: resourcePath,
|
|
122
|
+
stdout: 'pipe',
|
|
123
|
+
stderr: 'pipe'
|
|
124
|
+
});
|
|
125
|
+
const stdout = await new Response(proc.stdout).text();
|
|
126
|
+
const exitCode = await proc.exited;
|
|
127
|
+
if (exitCode !== 0) return undefined;
|
|
128
|
+
const trimmed = stdout.trim();
|
|
129
|
+
return trimmed.length > 0 ? trimmed : undefined;
|
|
130
|
+
});
|
|
131
|
+
|
|
132
|
+
return result.match({
|
|
133
|
+
ok: (value) => value,
|
|
134
|
+
err: () => undefined
|
|
135
|
+
});
|
|
136
|
+
};
|
|
137
|
+
|
|
138
|
+
const buildVirtualMetadata = async (args: {
|
|
139
|
+
resource: BtcaFsResource;
|
|
140
|
+
resourcePath: string;
|
|
141
|
+
loadedAt: string;
|
|
142
|
+
definition?: ReturnType<Config.Service['getResource']>;
|
|
143
|
+
}) => {
|
|
144
|
+
if (!args.definition) return null;
|
|
145
|
+
const base = {
|
|
146
|
+
name: args.resource.name,
|
|
147
|
+
fsName: args.resource.fsName,
|
|
148
|
+
type: args.resource.type,
|
|
149
|
+
path: args.resourcePath,
|
|
150
|
+
repoSubPaths: args.resource.repoSubPaths,
|
|
151
|
+
loadedAt: args.loadedAt
|
|
152
|
+
};
|
|
153
|
+
if (!isGitResource(args.definition)) return base;
|
|
154
|
+
const commit = await getGitHeadHash(args.resourcePath);
|
|
155
|
+
return {
|
|
156
|
+
...base,
|
|
157
|
+
url: args.definition.url,
|
|
158
|
+
branch: args.definition.branch,
|
|
159
|
+
commit
|
|
160
|
+
};
|
|
161
|
+
};
|
|
162
|
+
|
|
35
163
|
export const create = (args: {
|
|
36
164
|
config: Config.Service;
|
|
37
165
|
resources: Resources.Service;
|
|
@@ -50,72 +178,74 @@ export namespace Collections {
|
|
|
50
178
|
|
|
51
179
|
const sortedNames = [...uniqueNames].sort((a, b) => a.localeCompare(b));
|
|
52
180
|
const key = getCollectionKey(sortedNames);
|
|
53
|
-
const collectionPath =
|
|
181
|
+
const collectionPath = '/';
|
|
182
|
+
const vfsId = VirtualFs.create();
|
|
183
|
+
const cleanupVirtual = () => {
|
|
184
|
+
VirtualFs.dispose(vfsId);
|
|
185
|
+
clearVirtualCollectionMetadata(vfsId);
|
|
186
|
+
};
|
|
54
187
|
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
} catch (cause) {
|
|
58
|
-
throw new CollectionError({
|
|
59
|
-
message: `Failed to create collection directory: "${collectionPath}"`,
|
|
60
|
-
hint: 'Check that you have write permissions to the btca data directory.',
|
|
61
|
-
cause
|
|
62
|
-
});
|
|
63
|
-
}
|
|
188
|
+
const result = await Result.gen(async function* () {
|
|
189
|
+
yield* Result.await(initVirtualRoot(collectionPath, vfsId));
|
|
64
190
|
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
loadedResources.push(
|
|
69
|
-
} catch (cause) {
|
|
70
|
-
// Preserve the hint from the underlying error if available
|
|
71
|
-
const underlyingHint = getErrorHint(cause);
|
|
72
|
-
const underlyingMessage = getErrorMessage(cause);
|
|
73
|
-
throw new CollectionError({
|
|
74
|
-
message: `Failed to load resource "${name}": ${underlyingMessage}`,
|
|
75
|
-
hint:
|
|
76
|
-
underlyingHint ??
|
|
77
|
-
`${CommonHints.CLEAR_CACHE} Check that the resource "${name}" is correctly configured.`,
|
|
78
|
-
cause
|
|
79
|
-
});
|
|
191
|
+
const loadedResources: BtcaFsResource[] = [];
|
|
192
|
+
for (const name of sortedNames) {
|
|
193
|
+
const resource = yield* Result.await(loadResource(args.resources, name, quiet));
|
|
194
|
+
loadedResources.push(resource);
|
|
80
195
|
}
|
|
81
|
-
}
|
|
82
196
|
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
resourcePath =
|
|
87
|
-
|
|
88
|
-
throw new CollectionError({
|
|
89
|
-
message: `Failed to get path for resource "${resource.name}"`,
|
|
90
|
-
hint: CommonHints.CLEAR_CACHE,
|
|
91
|
-
cause
|
|
92
|
-
});
|
|
93
|
-
}
|
|
197
|
+
const metadataResources: VirtualResourceMetadata[] = [];
|
|
198
|
+
const loadedAt = new Date().toISOString();
|
|
199
|
+
for (const resource of loadedResources) {
|
|
200
|
+
const resourcePath = yield* Result.await(resolveResourcePath(resource));
|
|
201
|
+
const virtualResourcePath = path.posix.join('/', resource.fsName);
|
|
94
202
|
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
203
|
+
await ignoreErrors(() =>
|
|
204
|
+
VirtualFs.rm(virtualResourcePath, { recursive: true, force: true }, vfsId)
|
|
205
|
+
);
|
|
206
|
+
|
|
207
|
+
yield* Result.await(
|
|
208
|
+
virtualizeResource({
|
|
209
|
+
resource,
|
|
210
|
+
resourcePath,
|
|
211
|
+
virtualResourcePath,
|
|
212
|
+
vfsId
|
|
213
|
+
})
|
|
214
|
+
);
|
|
101
215
|
|
|
102
|
-
|
|
103
|
-
await
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
cause
|
|
216
|
+
const definition = args.config.getResource(resource.name);
|
|
217
|
+
const metadata = await buildVirtualMetadata({
|
|
218
|
+
resource,
|
|
219
|
+
resourcePath,
|
|
220
|
+
loadedAt,
|
|
221
|
+
definition
|
|
109
222
|
});
|
|
223
|
+
if (metadata) metadataResources.push(metadata);
|
|
110
224
|
}
|
|
111
|
-
}
|
|
112
225
|
|
|
113
|
-
|
|
226
|
+
setVirtualCollectionMetadata({
|
|
227
|
+
vfsId,
|
|
228
|
+
collectionKey: key,
|
|
229
|
+
createdAt: loadedAt,
|
|
230
|
+
resources: metadataResources
|
|
231
|
+
});
|
|
114
232
|
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
233
|
+
const instructionBlocks = loadedResources.map(createCollectionInstructionBlock);
|
|
234
|
+
|
|
235
|
+
return Result.ok({
|
|
236
|
+
path: collectionPath,
|
|
237
|
+
agentInstructions: instructionBlocks.join('\n\n'),
|
|
238
|
+
vfsId
|
|
239
|
+
});
|
|
240
|
+
});
|
|
241
|
+
|
|
242
|
+
return result.match({
|
|
243
|
+
ok: (value) => value,
|
|
244
|
+
err: (error) => {
|
|
245
|
+
cleanupVirtual();
|
|
246
|
+
throw error;
|
|
247
|
+
}
|
|
248
|
+
});
|
|
119
249
|
})
|
|
120
250
|
};
|
|
121
251
|
};
|
package/src/collections/types.ts
CHANGED
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
export type VirtualResourceMetadata = {
|
|
2
|
+
name: string;
|
|
3
|
+
fsName: string;
|
|
4
|
+
type: 'git' | 'local';
|
|
5
|
+
path: string;
|
|
6
|
+
repoSubPaths: readonly string[];
|
|
7
|
+
url?: string;
|
|
8
|
+
branch?: string;
|
|
9
|
+
commit?: string;
|
|
10
|
+
loadedAt: string;
|
|
11
|
+
};
|
|
12
|
+
|
|
13
|
+
export type VirtualCollectionMetadata = {
|
|
14
|
+
vfsId: string;
|
|
15
|
+
collectionKey: string;
|
|
16
|
+
createdAt: string;
|
|
17
|
+
resources: VirtualResourceMetadata[];
|
|
18
|
+
};
|
|
19
|
+
|
|
20
|
+
const metadataByVfsId = new Map<string, VirtualCollectionMetadata>();
|
|
21
|
+
|
|
22
|
+
export const setVirtualCollectionMetadata = (metadata: VirtualCollectionMetadata) => {
|
|
23
|
+
metadataByVfsId.set(metadata.vfsId, metadata);
|
|
24
|
+
};
|
|
25
|
+
|
|
26
|
+
export const getVirtualCollectionMetadata = (vfsId: string) => metadataByVfsId.get(vfsId);
|
|
27
|
+
|
|
28
|
+
export const clearVirtualCollectionMetadata = (vfsId: string) => metadataByVfsId.delete(vfsId);
|
|
29
|
+
|
|
30
|
+
export const clearAllVirtualCollectionMetadata = () => {
|
|
31
|
+
metadataByVfsId.clear();
|
|
32
|
+
};
|
|
File without changes
|