btca-server 1.0.962 → 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/resources/service.ts
CHANGED
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
import { createHash } from 'node:crypto';
|
|
2
2
|
|
|
3
|
-
import {
|
|
3
|
+
import { Effect } from 'effect';
|
|
4
|
+
|
|
5
|
+
import type { ConfigService as ConfigServiceShape } from '../config/index.ts';
|
|
4
6
|
import { parseNpmReference, validateGitUrl } from '../validation/index.ts';
|
|
5
7
|
import { CommonHints } from '../errors.ts';
|
|
6
8
|
|
|
@@ -33,135 +35,174 @@ export const createAnonymousDirectoryKey = (reference: string): string => {
|
|
|
33
35
|
|
|
34
36
|
const isAnonymousResource = (name: string): boolean => name.startsWith(ANON_PREFIX);
|
|
35
37
|
|
|
36
|
-
export
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
38
|
+
export type ResourcesService = {
|
|
39
|
+
load: (
|
|
40
|
+
name: string,
|
|
41
|
+
options?: {
|
|
42
|
+
quiet?: boolean;
|
|
43
|
+
}
|
|
44
|
+
) => Promise<BtcaFsResource>;
|
|
45
|
+
loadEffect: (
|
|
46
|
+
name: string,
|
|
47
|
+
options?: {
|
|
48
|
+
quiet?: boolean;
|
|
49
|
+
}
|
|
50
|
+
) => Effect.Effect<BtcaFsResource, ResourceError>;
|
|
51
|
+
};
|
|
45
52
|
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
+
const normalizeSearchPaths = (definition: GitResource): string[] => {
|
|
54
|
+
const paths = [
|
|
55
|
+
...(definition.searchPaths ?? []),
|
|
56
|
+
...(definition.searchPath ? [definition.searchPath] : [])
|
|
57
|
+
];
|
|
58
|
+
return paths.filter((path) => path.trim().length > 0);
|
|
59
|
+
};
|
|
53
60
|
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
61
|
+
const definitionToGitArgs = (
|
|
62
|
+
definition: GitResource,
|
|
63
|
+
resourcesDirectory: string,
|
|
64
|
+
quiet: boolean
|
|
65
|
+
): BtcaGitResourceArgs => ({
|
|
66
|
+
type: 'git',
|
|
67
|
+
name: definition.name,
|
|
68
|
+
url: definition.url,
|
|
69
|
+
branch: definition.branch,
|
|
70
|
+
repoSubPaths: normalizeSearchPaths(definition),
|
|
71
|
+
resourcesDirectoryPath: resourcesDirectory,
|
|
72
|
+
specialAgentInstructions: definition.specialNotes ?? '',
|
|
73
|
+
quiet,
|
|
74
|
+
ephemeral: isAnonymousResource(definition.name),
|
|
75
|
+
localDirectoryKey: isAnonymousResource(definition.name)
|
|
76
|
+
? createAnonymousDirectoryKey(definition.url)
|
|
77
|
+
: undefined
|
|
78
|
+
});
|
|
79
|
+
|
|
80
|
+
const definitionToLocalArgs = (definition: LocalResource): BtcaLocalResourceArgs => ({
|
|
81
|
+
type: 'local',
|
|
82
|
+
name: definition.name,
|
|
83
|
+
path: definition.path,
|
|
84
|
+
specialAgentInstructions: definition.specialNotes ?? ''
|
|
85
|
+
});
|
|
86
|
+
|
|
87
|
+
const definitionToNpmArgs = (
|
|
88
|
+
definition: NpmResource,
|
|
89
|
+
resourcesDirectory: string
|
|
90
|
+
): BtcaNpmResourceArgs => {
|
|
91
|
+
const reference = `${definition.package}${definition.version ? `@${definition.version}` : ''}`;
|
|
92
|
+
return {
|
|
93
|
+
type: 'npm',
|
|
60
94
|
name: definition.name,
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
repoSubPaths: normalizeSearchPaths(definition),
|
|
95
|
+
package: definition.package,
|
|
96
|
+
...(definition.version ? { version: definition.version } : {}),
|
|
64
97
|
resourcesDirectoryPath: resourcesDirectory,
|
|
65
98
|
specialAgentInstructions: definition.specialNotes ?? '',
|
|
66
|
-
quiet,
|
|
67
99
|
ephemeral: isAnonymousResource(definition.name),
|
|
68
100
|
localDirectoryKey: isAnonymousResource(definition.name)
|
|
69
|
-
? createAnonymousDirectoryKey(
|
|
101
|
+
? createAnonymousDirectoryKey(reference)
|
|
70
102
|
: undefined
|
|
71
|
-
}
|
|
72
|
-
|
|
73
|
-
const definitionToLocalArgs = (definition: LocalResource): BtcaLocalResourceArgs => ({
|
|
74
|
-
type: 'local',
|
|
75
|
-
name: definition.name,
|
|
76
|
-
path: definition.path,
|
|
77
|
-
specialAgentInstructions: definition.specialNotes ?? ''
|
|
78
|
-
});
|
|
103
|
+
};
|
|
104
|
+
};
|
|
79
105
|
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
106
|
+
const loadLocalResource = (args: BtcaLocalResourceArgs): BtcaFsResource => ({
|
|
107
|
+
_tag: 'fs-based',
|
|
108
|
+
name: args.name,
|
|
109
|
+
fsName: resourceNameToKey(args.name),
|
|
110
|
+
type: 'local',
|
|
111
|
+
repoSubPaths: [],
|
|
112
|
+
specialAgentInstructions: args.specialAgentInstructions,
|
|
113
|
+
getAbsoluteDirectoryPath: async () => args.path
|
|
114
|
+
});
|
|
115
|
+
|
|
116
|
+
export const createAnonymousResource = (reference: string): ResourceDefinition | null => {
|
|
117
|
+
const npmReference = parseNpmReference(reference);
|
|
118
|
+
if (npmReference) {
|
|
85
119
|
return {
|
|
86
120
|
type: 'npm',
|
|
87
|
-
name:
|
|
88
|
-
package:
|
|
89
|
-
...(
|
|
90
|
-
resourcesDirectoryPath: resourcesDirectory,
|
|
91
|
-
specialAgentInstructions: definition.specialNotes ?? '',
|
|
92
|
-
ephemeral: isAnonymousResource(definition.name),
|
|
93
|
-
localDirectoryKey: isAnonymousResource(definition.name)
|
|
94
|
-
? createAnonymousDirectoryKey(reference)
|
|
95
|
-
: undefined
|
|
121
|
+
name: `${ANON_PREFIX}${npmReference.normalizedReference}`,
|
|
122
|
+
package: npmReference.packageName,
|
|
123
|
+
...(npmReference.version ? { version: npmReference.version } : {})
|
|
96
124
|
};
|
|
97
|
-
}
|
|
98
|
-
|
|
99
|
-
const loadLocalResource = (args: BtcaLocalResourceArgs): BtcaFsResource => ({
|
|
100
|
-
_tag: 'fs-based',
|
|
101
|
-
name: args.name,
|
|
102
|
-
fsName: resourceNameToKey(args.name),
|
|
103
|
-
type: 'local',
|
|
104
|
-
repoSubPaths: [],
|
|
105
|
-
specialAgentInstructions: args.specialAgentInstructions,
|
|
106
|
-
getAbsoluteDirectoryPath: async () => args.path
|
|
107
|
-
});
|
|
125
|
+
}
|
|
108
126
|
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
if (gitUrlResult.valid) {
|
|
122
|
-
const normalizedUrl = gitUrlResult.value;
|
|
123
|
-
return {
|
|
124
|
-
type: 'git',
|
|
125
|
-
name: `${ANON_PREFIX}${normalizedUrl}`,
|
|
126
|
-
url: normalizedUrl,
|
|
127
|
-
branch: DEFAULT_ANON_BRANCH
|
|
128
|
-
};
|
|
129
|
-
}
|
|
130
|
-
return null;
|
|
131
|
-
};
|
|
127
|
+
const gitUrlResult = validateGitUrl(reference);
|
|
128
|
+
if (gitUrlResult.valid) {
|
|
129
|
+
const normalizedUrl = gitUrlResult.value;
|
|
130
|
+
return {
|
|
131
|
+
type: 'git',
|
|
132
|
+
name: `${ANON_PREFIX}${normalizedUrl}`,
|
|
133
|
+
url: normalizedUrl,
|
|
134
|
+
branch: DEFAULT_ANON_BRANCH
|
|
135
|
+
};
|
|
136
|
+
}
|
|
137
|
+
return null;
|
|
138
|
+
};
|
|
132
139
|
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
140
|
+
export const resolveResourceDefinition = (
|
|
141
|
+
reference: string,
|
|
142
|
+
getResource: ConfigServiceShape['getResource']
|
|
143
|
+
): ResourceDefinition => {
|
|
144
|
+
const definition = getResource(reference);
|
|
145
|
+
if (definition) return definition;
|
|
139
146
|
|
|
140
|
-
|
|
141
|
-
|
|
147
|
+
const anonymousDefinition = createAnonymousResource(reference);
|
|
148
|
+
if (anonymousDefinition) return anonymousDefinition;
|
|
142
149
|
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
150
|
+
throw new ResourceError({
|
|
151
|
+
message: `Resource "${reference}" not found in config`,
|
|
152
|
+
hint: `${CommonHints.LIST_RESOURCES} ${CommonHints.ADD_RESOURCE}`
|
|
153
|
+
});
|
|
154
|
+
};
|
|
148
155
|
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
156
|
+
export const createResourcesService = (config: ConfigServiceShape): ResourcesService => {
|
|
157
|
+
const loadEffect: ResourcesService['loadEffect'] = (name, options) =>
|
|
158
|
+
Effect.gen(function* () {
|
|
159
|
+
const quiet = options?.quiet ?? false;
|
|
160
|
+
const definition = yield* Effect.try({
|
|
161
|
+
try: () => resolveResourceDefinition(name, config.getResource),
|
|
162
|
+
catch: (cause) =>
|
|
163
|
+
cause instanceof ResourceError
|
|
164
|
+
? cause
|
|
165
|
+
: new ResourceError({
|
|
166
|
+
message: `Failed to resolve resource "${name}"`,
|
|
167
|
+
hint: `${CommonHints.LIST_RESOURCES} ${CommonHints.ADD_RESOURCE}`,
|
|
168
|
+
cause
|
|
169
|
+
})
|
|
170
|
+
});
|
|
171
|
+
|
|
172
|
+
if (isGitResource(definition)) {
|
|
173
|
+
return yield* Effect.tryPromise({
|
|
174
|
+
try: () =>
|
|
175
|
+
loadGitResource(definitionToGitArgs(definition, config.resourcesDirectory, quiet)),
|
|
176
|
+
catch: (cause) =>
|
|
177
|
+
cause instanceof ResourceError
|
|
178
|
+
? cause
|
|
179
|
+
: new ResourceError({
|
|
180
|
+
message: `Failed to load git resource "${name}"`,
|
|
181
|
+
hint: CommonHints.CLEAR_CACHE,
|
|
182
|
+
cause
|
|
183
|
+
})
|
|
184
|
+
});
|
|
185
|
+
}
|
|
154
186
|
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
187
|
+
if (isNpmResource(definition)) {
|
|
188
|
+
return yield* Effect.tryPromise({
|
|
189
|
+
try: () => loadNpmResource(definitionToNpmArgs(definition, config.resourcesDirectory)),
|
|
190
|
+
catch: (cause) =>
|
|
191
|
+
cause instanceof ResourceError
|
|
192
|
+
? cause
|
|
193
|
+
: new ResourceError({
|
|
194
|
+
message: `Failed to load npm resource "${name}"`,
|
|
195
|
+
hint: CommonHints.CLEAR_CACHE,
|
|
196
|
+
cause
|
|
197
|
+
})
|
|
198
|
+
});
|
|
199
|
+
}
|
|
158
200
|
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
}
|
|
201
|
+
return loadLocalResource(definitionToLocalArgs(definition));
|
|
202
|
+
});
|
|
162
203
|
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
204
|
+
return {
|
|
205
|
+
load: (name, options) => Effect.runPromise(loadEffect(name, options)),
|
|
206
|
+
loadEffect
|
|
166
207
|
};
|
|
167
|
-
}
|
|
208
|
+
};
|
package/src/stream/index.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { describe, it, expect } from 'bun:test';
|
|
2
2
|
|
|
3
|
-
import {
|
|
3
|
+
import { createSseStream } from './service.ts';
|
|
4
4
|
import type { BtcaStreamEvent } from './types.ts';
|
|
5
5
|
|
|
6
6
|
const readStream = async (stream: ReadableStream<Uint8Array>) => {
|
|
@@ -22,7 +22,7 @@ const parseSseEvents = (payload: string) =>
|
|
|
22
22
|
.filter((line): line is string => Boolean(line))
|
|
23
23
|
.map((line) => JSON.parse(line.slice(6)) as BtcaStreamEvent);
|
|
24
24
|
|
|
25
|
-
describe('
|
|
25
|
+
describe('createSseStream', () => {
|
|
26
26
|
it('streams reasoning deltas and includes final reasoning in done', async () => {
|
|
27
27
|
const eventStream = (async function* () {
|
|
28
28
|
yield { type: 'reasoning-delta', text: 'First ' } as const;
|
|
@@ -31,7 +31,7 @@ describe('StreamService.createSseStream', () => {
|
|
|
31
31
|
yield { type: 'finish', finishReason: 'stop' } as const;
|
|
32
32
|
})();
|
|
33
33
|
|
|
34
|
-
const stream =
|
|
34
|
+
const stream = createSseStream({
|
|
35
35
|
meta: {
|
|
36
36
|
type: 'meta',
|
|
37
37
|
model: { provider: 'test', model: 'test-model' },
|
|
@@ -71,7 +71,7 @@ describe('StreamService.createSseStream', () => {
|
|
|
71
71
|
} as const;
|
|
72
72
|
})();
|
|
73
73
|
|
|
74
|
-
const stream =
|
|
74
|
+
const stream = createSseStream({
|
|
75
75
|
meta: {
|
|
76
76
|
type: 'meta',
|
|
77
77
|
model: { provider: 'openrouter', model: 'openai/gpt-4o-mini' },
|
|
@@ -124,7 +124,7 @@ describe('StreamService.createSseStream', () => {
|
|
|
124
124
|
yield { type: 'finish', finishReason: 'stop' } as const;
|
|
125
125
|
})();
|
|
126
126
|
|
|
127
|
-
const stream =
|
|
127
|
+
const stream = createSseStream({
|
|
128
128
|
meta: {
|
|
129
129
|
type: 'meta',
|
|
130
130
|
model: { provider: 'test', model: 'test-model' },
|