btca-server 1.0.962 → 2.0.1
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/config/remote.ts
CHANGED
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
import { promises as fs } from 'node:fs';
|
|
2
2
|
import path from 'node:path';
|
|
3
3
|
|
|
4
|
-
import {
|
|
4
|
+
import { parseJsonc } from '@btca/shared';
|
|
5
5
|
import { z } from 'zod';
|
|
6
6
|
|
|
7
7
|
import { CommonHints, type TaggedErrorOptions } from '../errors.ts';
|
|
8
|
-
import {
|
|
8
|
+
import { metricsError, metricsInfo } from '../metrics/index.ts';
|
|
9
9
|
import { GitResourceSchema, type GitResource } from '../resources/schema.ts';
|
|
10
10
|
|
|
11
11
|
// ─────────────────────────────────────────────────────────────────────────────
|
|
@@ -91,408 +91,253 @@ const expandHome = (filePath: string): string => {
|
|
|
91
91
|
return filePath;
|
|
92
92
|
};
|
|
93
93
|
|
|
94
|
-
const
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
let escaped = false;
|
|
101
|
-
|
|
102
|
-
while (i < content.length) {
|
|
103
|
-
const ch = content[i] ?? '';
|
|
104
|
-
const next = content[i + 1] ?? '';
|
|
105
|
-
|
|
106
|
-
if (inString) {
|
|
107
|
-
out += ch;
|
|
108
|
-
if (escaped) escaped = false;
|
|
109
|
-
else if (ch === '\\') escaped = true;
|
|
110
|
-
else if (quote && ch === quote) {
|
|
111
|
-
inString = false;
|
|
112
|
-
quote = null;
|
|
113
|
-
}
|
|
114
|
-
i += 1;
|
|
115
|
-
continue;
|
|
116
|
-
}
|
|
117
|
-
|
|
118
|
-
if (ch === '/' && next === '/') {
|
|
119
|
-
i += 2;
|
|
120
|
-
while (i < content.length && content[i] !== '\n') i += 1;
|
|
121
|
-
continue;
|
|
122
|
-
}
|
|
123
|
-
|
|
124
|
-
if (ch === '/' && next === '*') {
|
|
125
|
-
i += 2;
|
|
126
|
-
while (i < content.length) {
|
|
127
|
-
if (content[i] === '*' && content[i + 1] === '/') {
|
|
128
|
-
i += 2;
|
|
129
|
-
break;
|
|
130
|
-
}
|
|
131
|
-
i += 1;
|
|
132
|
-
}
|
|
133
|
-
continue;
|
|
134
|
-
}
|
|
135
|
-
|
|
136
|
-
if (ch === '"' || ch === "'") {
|
|
137
|
-
inString = true;
|
|
138
|
-
quote = ch;
|
|
139
|
-
out += ch;
|
|
140
|
-
i += 1;
|
|
141
|
-
continue;
|
|
142
|
-
}
|
|
143
|
-
|
|
144
|
-
out += ch;
|
|
145
|
-
i += 1;
|
|
94
|
+
const readJsonFile = async (filePath: string) => {
|
|
95
|
+
try {
|
|
96
|
+
const content = await Bun.file(filePath).text();
|
|
97
|
+
return JSON.parse(content);
|
|
98
|
+
} catch {
|
|
99
|
+
return null;
|
|
146
100
|
}
|
|
101
|
+
};
|
|
147
102
|
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
while (i < out.length) {
|
|
156
|
-
const ch = out[i] ?? '';
|
|
157
|
-
|
|
158
|
-
if (inString) {
|
|
159
|
-
normalized += ch;
|
|
160
|
-
if (escaped) escaped = false;
|
|
161
|
-
else if (ch === '\\') escaped = true;
|
|
162
|
-
else if (quote && ch === quote) {
|
|
163
|
-
inString = false;
|
|
164
|
-
quote = null;
|
|
165
|
-
}
|
|
166
|
-
i += 1;
|
|
167
|
-
continue;
|
|
168
|
-
}
|
|
169
|
-
|
|
170
|
-
if (ch === '"' || ch === "'") {
|
|
171
|
-
inString = true;
|
|
172
|
-
quote = ch;
|
|
173
|
-
normalized += ch;
|
|
174
|
-
i += 1;
|
|
175
|
-
continue;
|
|
176
|
-
}
|
|
177
|
-
|
|
178
|
-
if (ch === ',') {
|
|
179
|
-
let j = i + 1;
|
|
180
|
-
while (j < out.length && /\s/.test(out[j] ?? '')) j += 1;
|
|
181
|
-
const nextNonWs = out[j] ?? '';
|
|
182
|
-
if (nextNonWs === ']' || nextNonWs === '}') {
|
|
183
|
-
i += 1;
|
|
184
|
-
continue;
|
|
185
|
-
}
|
|
186
|
-
}
|
|
187
|
-
|
|
188
|
-
normalized += ch;
|
|
189
|
-
i += 1;
|
|
103
|
+
const readJsoncFile = async (filePath: string) => {
|
|
104
|
+
try {
|
|
105
|
+
const content = await Bun.file(filePath).text();
|
|
106
|
+
return parseJsonc(content);
|
|
107
|
+
} catch {
|
|
108
|
+
return null;
|
|
190
109
|
}
|
|
191
|
-
|
|
192
|
-
return normalized.trim();
|
|
193
110
|
};
|
|
194
111
|
|
|
195
|
-
|
|
112
|
+
export function getAuthPath(): string {
|
|
113
|
+
return `${expandHome(GLOBAL_CONFIG_DIR)}/${REMOTE_AUTH_FILENAME}`;
|
|
114
|
+
}
|
|
196
115
|
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
116
|
+
/**
|
|
117
|
+
* Get the path to the remote config file in the current directory
|
|
118
|
+
*/
|
|
119
|
+
export function getConfigPath(cwd: string = process.cwd()): string {
|
|
120
|
+
return `${cwd}/${REMOTE_CONFIG_FILENAME}`;
|
|
121
|
+
}
|
|
203
122
|
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
123
|
+
/**
|
|
124
|
+
* Check if the user is authenticated with remote
|
|
125
|
+
*/
|
|
126
|
+
export async function isAuthenticated(): Promise<boolean> {
|
|
127
|
+
const authPath = getAuthPath();
|
|
128
|
+
const parsed = await readJsonFile(authPath);
|
|
129
|
+
if (!parsed) return false;
|
|
130
|
+
const authResult = RemoteAuthSchema.safeParse(parsed);
|
|
131
|
+
return authResult.success && !!authResult.data.apiKey;
|
|
132
|
+
}
|
|
210
133
|
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
134
|
+
/**
|
|
135
|
+
* Load the remote auth credentials
|
|
136
|
+
*/
|
|
137
|
+
export async function loadAuth(): Promise<RemoteAuth | null> {
|
|
138
|
+
const authPath = getAuthPath();
|
|
139
|
+
const parsed = await readJsonFile(authPath);
|
|
140
|
+
if (!parsed) return null;
|
|
141
|
+
const authResult = RemoteAuthSchema.safeParse(parsed);
|
|
142
|
+
if (!authResult.success) {
|
|
143
|
+
metricsError('remote.auth.invalid', { path: authPath, error: authResult.error.message });
|
|
144
|
+
return null;
|
|
145
|
+
}
|
|
146
|
+
return authResult.data;
|
|
147
|
+
}
|
|
214
148
|
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
149
|
+
/**
|
|
150
|
+
* Save remote auth credentials
|
|
151
|
+
*/
|
|
152
|
+
export async function saveAuth(auth: RemoteAuth): Promise<void> {
|
|
153
|
+
const authPath = getAuthPath();
|
|
154
|
+
const configDir = path.dirname(authPath);
|
|
155
|
+
try {
|
|
156
|
+
await fs.mkdir(configDir, { recursive: true });
|
|
157
|
+
await Bun.write(authPath, JSON.stringify(auth, null, 2));
|
|
158
|
+
await fs.chmod(authPath, 0o600);
|
|
159
|
+
} catch (cause) {
|
|
160
|
+
throw new RemoteConfigError({
|
|
161
|
+
message: `Failed to save remote auth to: "${authPath}"`,
|
|
162
|
+
hint: 'Check that you have write permissions to the config directory.',
|
|
163
|
+
cause
|
|
164
|
+
});
|
|
221
165
|
}
|
|
166
|
+
metricsInfo('remote.auth.saved', { path: authPath });
|
|
167
|
+
}
|
|
222
168
|
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
169
|
+
/**
|
|
170
|
+
* Delete remote auth credentials (unlink)
|
|
171
|
+
*/
|
|
172
|
+
export async function deleteAuth(): Promise<void> {
|
|
173
|
+
const authPath = getAuthPath();
|
|
174
|
+
try {
|
|
175
|
+
await fs.unlink(authPath);
|
|
176
|
+
metricsInfo('remote.auth.deleted', { path: authPath });
|
|
177
|
+
} catch {
|
|
178
|
+
return;
|
|
228
179
|
}
|
|
180
|
+
}
|
|
229
181
|
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
182
|
+
/**
|
|
183
|
+
* Check if a remote config file exists in the current directory
|
|
184
|
+
*/
|
|
185
|
+
export async function configExists(cwd: string = process.cwd()): Promise<boolean> {
|
|
186
|
+
const configPath = getConfigPath(cwd);
|
|
187
|
+
return Bun.file(configPath).exists();
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
/**
|
|
191
|
+
* Load the remote config from the current directory
|
|
192
|
+
*/
|
|
193
|
+
export async function loadConfig(cwd: string = process.cwd()): Promise<RemoteConfig | null> {
|
|
194
|
+
const configPath = getConfigPath(cwd);
|
|
195
|
+
|
|
196
|
+
const parsed = await readJsoncFile(configPath);
|
|
197
|
+
if (!parsed) return null;
|
|
198
|
+
|
|
199
|
+
const parsedResult = RemoteConfigSchema.safeParse(parsed);
|
|
200
|
+
if (!parsedResult.success) {
|
|
201
|
+
const issues = parsedResult.error.issues
|
|
202
|
+
.map((i) => ` - ${i.path.join('.')}: ${i.message}`)
|
|
203
|
+
.join('\n');
|
|
204
|
+
throw new RemoteConfigError({
|
|
205
|
+
message: `Invalid remote config structure:\n${issues}`,
|
|
206
|
+
hint: `${CommonHints.CHECK_CONFIG} Required field: "project" (string).`,
|
|
207
|
+
cause: parsedResult.error
|
|
242
208
|
});
|
|
243
209
|
}
|
|
244
210
|
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
211
|
+
metricsInfo('remote.config.loaded', {
|
|
212
|
+
path: configPath,
|
|
213
|
+
project: parsedResult.data.project,
|
|
214
|
+
resourceCount: parsedResult.data.resources.length
|
|
215
|
+
});
|
|
216
|
+
|
|
217
|
+
return parsedResult.data;
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
/**
|
|
221
|
+
* Save the remote config to the current directory
|
|
222
|
+
*/
|
|
223
|
+
export async function saveConfig(config: RemoteConfig, cwd: string = process.cwd()): Promise<void> {
|
|
224
|
+
const configPath = getConfigPath(cwd);
|
|
225
|
+
|
|
226
|
+
const toSave = {
|
|
227
|
+
$schema: REMOTE_CONFIG_SCHEMA_URL,
|
|
228
|
+
...config
|
|
229
|
+
};
|
|
230
|
+
|
|
231
|
+
try {
|
|
232
|
+
await Bun.write(configPath, JSON.stringify(toSave, null, '\t'));
|
|
233
|
+
} catch (cause) {
|
|
234
|
+
throw new RemoteConfigError({
|
|
235
|
+
message: `Failed to save remote config to: "${configPath}"`,
|
|
236
|
+
hint: 'Check that you have write permissions to the directory.',
|
|
237
|
+
cause
|
|
261
238
|
});
|
|
262
239
|
}
|
|
240
|
+
metricsInfo('remote.config.saved', {
|
|
241
|
+
path: configPath,
|
|
242
|
+
project: config.project,
|
|
243
|
+
resourceCount: config.resources.length
|
|
244
|
+
});
|
|
245
|
+
}
|
|
263
246
|
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
try: () => fs.mkdir(configDir, { recursive: true }),
|
|
275
|
-
catch: (cause) =>
|
|
276
|
-
new RemoteConfigError({
|
|
277
|
-
message: `Failed to save remote auth to: "${authPath}"`,
|
|
278
|
-
hint: 'Check that you have write permissions to the config directory.',
|
|
279
|
-
cause
|
|
280
|
-
})
|
|
281
|
-
})
|
|
282
|
-
);
|
|
283
|
-
|
|
284
|
-
yield* Result.await(
|
|
285
|
-
Result.tryPromise({
|
|
286
|
-
try: () => Bun.write(authPath, JSON.stringify(auth, null, 2)),
|
|
287
|
-
catch: (cause) =>
|
|
288
|
-
new RemoteConfigError({
|
|
289
|
-
message: `Failed to save remote auth to: "${authPath}"`,
|
|
290
|
-
hint: 'Check that you have write permissions to the config directory.',
|
|
291
|
-
cause
|
|
292
|
-
})
|
|
293
|
-
})
|
|
294
|
-
);
|
|
295
|
-
|
|
296
|
-
yield* Result.await(
|
|
297
|
-
Result.tryPromise({
|
|
298
|
-
try: () => fs.chmod(authPath, 0o600),
|
|
299
|
-
catch: (cause) =>
|
|
300
|
-
new RemoteConfigError({
|
|
301
|
-
message: `Failed to save remote auth to: "${authPath}"`,
|
|
302
|
-
hint: 'Check that you have write permissions to the config directory.',
|
|
303
|
-
cause
|
|
304
|
-
})
|
|
305
|
-
})
|
|
306
|
-
);
|
|
307
|
-
|
|
308
|
-
return Result.ok(undefined);
|
|
309
|
-
});
|
|
247
|
+
/**
|
|
248
|
+
* Create a new remote config with defaults
|
|
249
|
+
*/
|
|
250
|
+
export function createDefaultConfig(projectName: string): RemoteConfig {
|
|
251
|
+
return {
|
|
252
|
+
project: projectName,
|
|
253
|
+
model: 'claude-haiku',
|
|
254
|
+
resources: []
|
|
255
|
+
};
|
|
256
|
+
}
|
|
310
257
|
|
|
311
|
-
|
|
312
|
-
|
|
258
|
+
/**
|
|
259
|
+
* Add a resource to the remote config
|
|
260
|
+
*/
|
|
261
|
+
export async function addResource(
|
|
262
|
+
resource: GitResource,
|
|
263
|
+
cwd: string = process.cwd()
|
|
264
|
+
): Promise<RemoteConfig> {
|
|
265
|
+
let config = await loadConfig(cwd);
|
|
266
|
+
|
|
267
|
+
if (!config) {
|
|
268
|
+
throw new RemoteConfigError({
|
|
269
|
+
message: 'No remote config found in current directory',
|
|
270
|
+
hint: `Create a remote config first with "btca remote init" or create a ${REMOTE_CONFIG_FILENAME} file.`
|
|
271
|
+
});
|
|
313
272
|
}
|
|
314
273
|
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
const result = await Result.tryPromise(() => fs.unlink(authPath));
|
|
321
|
-
result.match({
|
|
322
|
-
ok: () => Metrics.info('remote.auth.deleted', { path: authPath }),
|
|
323
|
-
err: () => undefined
|
|
274
|
+
// Check for duplicate
|
|
275
|
+
if (config.resources.some((r) => r.name === resource.name)) {
|
|
276
|
+
throw new RemoteConfigError({
|
|
277
|
+
message: `Resource "${resource.name}" already exists in remote config`,
|
|
278
|
+
hint: `Remove the existing resource first or use a different name.`
|
|
324
279
|
});
|
|
325
280
|
}
|
|
326
281
|
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
const configPath = getConfigPath(cwd);
|
|
332
|
-
return Bun.file(configPath).exists();
|
|
333
|
-
}
|
|
282
|
+
config = {
|
|
283
|
+
...config,
|
|
284
|
+
resources: [...config.resources, resource]
|
|
285
|
+
};
|
|
334
286
|
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
export async function loadConfig(cwd: string = process.cwd()): Promise<RemoteConfig | null> {
|
|
339
|
-
const configPath = getConfigPath(cwd);
|
|
287
|
+
await saveConfig(config, cwd);
|
|
288
|
+
return config;
|
|
289
|
+
}
|
|
340
290
|
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
message: `Invalid remote config structure:\n${issues}`,
|
|
355
|
-
hint: `${CommonHints.CHECK_CONFIG} Required field: "project" (string).`,
|
|
356
|
-
cause: parsedResult.error
|
|
357
|
-
});
|
|
358
|
-
}
|
|
359
|
-
|
|
360
|
-
Metrics.info('remote.config.loaded', {
|
|
361
|
-
path: configPath,
|
|
362
|
-
project: parsedResult.data.project,
|
|
363
|
-
resourceCount: parsedResult.data.resources.length
|
|
291
|
+
/**
|
|
292
|
+
* Remove a resource from the remote config
|
|
293
|
+
*/
|
|
294
|
+
export async function removeResource(
|
|
295
|
+
name: string,
|
|
296
|
+
cwd: string = process.cwd()
|
|
297
|
+
): Promise<RemoteConfig> {
|
|
298
|
+
let config = await loadConfig(cwd);
|
|
299
|
+
|
|
300
|
+
if (!config) {
|
|
301
|
+
throw new RemoteConfigError({
|
|
302
|
+
message: 'No remote config found in current directory',
|
|
303
|
+
hint: `Create a remote config first with "btca remote init" or create a ${REMOTE_CONFIG_FILENAME} file.`
|
|
364
304
|
});
|
|
365
|
-
|
|
366
|
-
return parsedResult.data;
|
|
367
305
|
}
|
|
368
306
|
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
cwd: string = process.cwd()
|
|
375
|
-
): Promise<void> {
|
|
376
|
-
const configPath = getConfigPath(cwd);
|
|
377
|
-
|
|
378
|
-
const toSave = {
|
|
379
|
-
$schema: REMOTE_CONFIG_SCHEMA_URL,
|
|
380
|
-
...config
|
|
381
|
-
};
|
|
382
|
-
|
|
383
|
-
const result = await Result.tryPromise({
|
|
384
|
-
try: () => Bun.write(configPath, JSON.stringify(toSave, null, '\t')),
|
|
385
|
-
catch: (cause) =>
|
|
386
|
-
new RemoteConfigError({
|
|
387
|
-
message: `Failed to save remote config to: "${configPath}"`,
|
|
388
|
-
hint: 'Check that you have write permissions to the directory.',
|
|
389
|
-
cause
|
|
390
|
-
})
|
|
391
|
-
});
|
|
392
|
-
|
|
393
|
-
if (!Result.isOk(result)) throw result.error;
|
|
394
|
-
Metrics.info('remote.config.saved', {
|
|
395
|
-
path: configPath,
|
|
396
|
-
project: config.project,
|
|
397
|
-
resourceCount: config.resources.length
|
|
307
|
+
const existingIndex = config.resources.findIndex((r) => r.name === name);
|
|
308
|
+
if (existingIndex === -1) {
|
|
309
|
+
throw new RemoteConfigError({
|
|
310
|
+
message: `Resource "${name}" not found in remote config`,
|
|
311
|
+
hint: `Available resources: ${config.resources.map((r) => r.name).join(', ') || 'none'}`
|
|
398
312
|
});
|
|
399
313
|
}
|
|
400
314
|
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
return {
|
|
406
|
-
project: projectName,
|
|
407
|
-
model: 'claude-haiku',
|
|
408
|
-
resources: []
|
|
409
|
-
};
|
|
410
|
-
}
|
|
315
|
+
config = {
|
|
316
|
+
...config,
|
|
317
|
+
resources: config.resources.filter((r) => r.name !== name)
|
|
318
|
+
};
|
|
411
319
|
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
export async function addResource(
|
|
416
|
-
resource: GitResource,
|
|
417
|
-
cwd: string = process.cwd()
|
|
418
|
-
): Promise<RemoteConfig> {
|
|
419
|
-
let config = await loadConfig(cwd);
|
|
420
|
-
|
|
421
|
-
if (!config) {
|
|
422
|
-
throw new RemoteConfigError({
|
|
423
|
-
message: 'No remote config found in current directory',
|
|
424
|
-
hint: `Create a remote config first with "btca remote init" or create a ${REMOTE_CONFIG_FILENAME} file.`
|
|
425
|
-
});
|
|
426
|
-
}
|
|
427
|
-
|
|
428
|
-
// Check for duplicate
|
|
429
|
-
if (config.resources.some((r) => r.name === resource.name)) {
|
|
430
|
-
throw new RemoteConfigError({
|
|
431
|
-
message: `Resource "${resource.name}" already exists in remote config`,
|
|
432
|
-
hint: `Remove the existing resource first or use a different name.`
|
|
433
|
-
});
|
|
434
|
-
}
|
|
435
|
-
|
|
436
|
-
config = {
|
|
437
|
-
...config,
|
|
438
|
-
resources: [...config.resources, resource]
|
|
439
|
-
};
|
|
440
|
-
|
|
441
|
-
await saveConfig(config, cwd);
|
|
442
|
-
return config;
|
|
443
|
-
}
|
|
320
|
+
await saveConfig(config, cwd);
|
|
321
|
+
return config;
|
|
322
|
+
}
|
|
444
323
|
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
}
|
|
460
|
-
|
|
461
|
-
const existingIndex = config.resources.findIndex((r) => r.name === name);
|
|
462
|
-
if (existingIndex === -1) {
|
|
463
|
-
throw new RemoteConfigError({
|
|
464
|
-
message: `Resource "${name}" not found in remote config`,
|
|
465
|
-
hint: `Available resources: ${config.resources.map((r) => r.name).join(', ') || 'none'}`
|
|
466
|
-
});
|
|
467
|
-
}
|
|
468
|
-
|
|
469
|
-
config = {
|
|
470
|
-
...config,
|
|
471
|
-
resources: config.resources.filter((r) => r.name !== name)
|
|
472
|
-
};
|
|
473
|
-
|
|
474
|
-
await saveConfig(config, cwd);
|
|
475
|
-
return config;
|
|
324
|
+
/**
|
|
325
|
+
* Update the model in the remote config
|
|
326
|
+
*/
|
|
327
|
+
export async function updateModel(
|
|
328
|
+
model: RemoteModelId,
|
|
329
|
+
cwd: string = process.cwd()
|
|
330
|
+
): Promise<RemoteConfig> {
|
|
331
|
+
let config = await loadConfig(cwd);
|
|
332
|
+
|
|
333
|
+
if (!config) {
|
|
334
|
+
throw new RemoteConfigError({
|
|
335
|
+
message: 'No remote config found in current directory',
|
|
336
|
+
hint: `Create a remote config first with "btca remote init" or create a ${REMOTE_CONFIG_FILENAME} file.`
|
|
337
|
+
});
|
|
476
338
|
}
|
|
477
339
|
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
export async function updateModel(
|
|
482
|
-
model: RemoteModelId,
|
|
483
|
-
cwd: string = process.cwd()
|
|
484
|
-
): Promise<RemoteConfig> {
|
|
485
|
-
let config = await loadConfig(cwd);
|
|
486
|
-
|
|
487
|
-
if (!config) {
|
|
488
|
-
throw new RemoteConfigError({
|
|
489
|
-
message: 'No remote config found in current directory',
|
|
490
|
-
hint: `Create a remote config first with "btca remote init" or create a ${REMOTE_CONFIG_FILENAME} file.`
|
|
491
|
-
});
|
|
492
|
-
}
|
|
493
|
-
|
|
494
|
-
config = { ...config, model };
|
|
495
|
-
await saveConfig(config, cwd);
|
|
496
|
-
return config;
|
|
497
|
-
}
|
|
340
|
+
config = { ...config, model };
|
|
341
|
+
await saveConfig(config, cwd);
|
|
342
|
+
return config;
|
|
498
343
|
}
|
package/src/context/index.ts
CHANGED
|
@@ -7,18 +7,12 @@ export type ContextStore = {
|
|
|
7
7
|
|
|
8
8
|
const storage = new AsyncLocalStorage<ContextStore>();
|
|
9
9
|
|
|
10
|
-
export
|
|
11
|
-
|
|
12
|
-
return Promise.resolve(storage.run(store, fn));
|
|
13
|
-
};
|
|
10
|
+
export const runContext = <T>(store: ContextStore, fn: () => Promise<T> | T): Promise<T> =>
|
|
11
|
+
Promise.resolve(storage.run(store, fn));
|
|
14
12
|
|
|
15
|
-
|
|
13
|
+
export const getContext = (): ContextStore | undefined => storage.getStore();
|
|
16
14
|
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
if (!store) throw new Error('Missing AsyncLocalStorage context');
|
|
20
|
-
return store;
|
|
21
|
-
};
|
|
15
|
+
export const requireContext = (): ContextStore =>
|
|
16
|
+
storage.getStore() ?? { requestId: 'unknown', txDepth: 0 };
|
|
22
17
|
|
|
23
|
-
|
|
24
|
-
}
|
|
18
|
+
export const requestId = (): string => storage.getStore()?.requestId ?? 'unknown';
|