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.
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
@@ -10,221 +10,180 @@
10
10
  import * as path from 'node:path';
11
11
  import * as os from 'node:os';
12
12
  import { z } from 'zod';
13
- import { Result } from 'better-result';
14
-
15
- export namespace Auth {
16
- export type AuthType = 'api' | 'oauth' | 'wellknown';
17
-
18
- export type AuthStatus =
19
- | { status: 'ok'; authType: AuthType; apiKey?: string; accountId?: string }
20
- | { status: 'missing' }
21
- | { status: 'invalid'; authType: AuthType };
22
-
23
- const PROVIDER_AUTH_TYPES: Record<string, readonly AuthType[]> = {
24
- opencode: ['api'],
25
- 'github-copilot': ['oauth'],
26
- openrouter: ['api'],
27
- openai: ['oauth'],
28
- 'openai-compat': ['api'],
29
- anthropic: ['api'],
30
- google: ['api', 'oauth'],
31
- minimax: ['api']
32
- };
33
-
34
- const readEnv = (key: string) => {
35
- const value = process.env[key];
36
- return value && value.trim().length > 0 ? value.trim() : undefined;
37
- };
38
-
39
- const getEnvApiKey = (providerId: string) => {
40
- if (providerId === 'openrouter') return readEnv('OPENROUTER_API_KEY');
41
- if (providerId === 'opencode') return readEnv('OPENCODE_API_KEY');
42
- if (providerId === 'minimax') return readEnv('MINIMAX_API_KEY');
43
- return undefined;
44
- };
45
-
46
- // Auth schema matching OpenCode's format
47
- const ApiKeyAuthSchema = z.object({
48
- type: z.literal('api'),
49
- key: z.string()
50
- });
51
-
52
- const OAuthAuthSchema = z.object({
53
- type: z.literal('oauth'),
54
- access: z.string(),
55
- refresh: z.string(),
56
- expires: z.number(),
57
- accountId: z.string().optional()
58
- });
59
-
60
- const WellKnownAuthSchema = z.object({
61
- type: z.literal('wellknown')
62
- });
63
-
64
- const AuthInfoSchema = z.union([ApiKeyAuthSchema, OAuthAuthSchema, WellKnownAuthSchema]);
65
- const AuthFileSchema = z.record(z.string(), AuthInfoSchema);
66
-
67
- export type ApiKeyAuth = z.infer<typeof ApiKeyAuthSchema>;
68
- export type OAuthAuth = z.infer<typeof OAuthAuthSchema>;
69
- export type WellKnownAuth = z.infer<typeof WellKnownAuthSchema>;
70
- export type AuthInfo = z.infer<typeof AuthInfoSchema>;
71
-
72
- /**
73
- * Get the path to OpenCode's data directory
74
- */
75
- function getDataPath(): string {
76
- const platform = os.platform();
77
-
78
- if (platform === 'win32') {
79
- const appdata = process.env.APPDATA || path.join(os.homedir(), 'AppData', 'Roaming');
80
- return path.join(appdata, 'opencode');
81
- }
82
-
83
- // Linux and macOS use XDG_DATA_HOME or ~/.local/share
84
- const xdgData = process.env.XDG_DATA_HOME || path.join(os.homedir(), '.local', 'share');
85
- return path.join(xdgData, 'opencode');
86
- }
87
13
 
88
- /**
89
- * Get the path to the auth.json file
90
- */
91
- function getAuthFilePath(): string {
92
- return path.join(getDataPath(), 'auth.json');
14
+ export type AuthType = 'api' | 'oauth' | 'wellknown';
15
+
16
+ export type AuthStatus =
17
+ | { status: 'ok'; authType: AuthType; apiKey?: string; accountId?: string }
18
+ | { status: 'missing' }
19
+ | { status: 'invalid'; authType: AuthType };
20
+
21
+ const PROVIDER_AUTH_TYPES: Record<string, readonly AuthType[]> = {
22
+ opencode: ['api'],
23
+ 'github-copilot': ['oauth'],
24
+ openrouter: ['api'],
25
+ openai: ['oauth'],
26
+ 'openai-compat': ['api'],
27
+ anthropic: ['api'],
28
+ google: ['api', 'oauth'],
29
+ minimax: ['api']
30
+ };
31
+
32
+ const readEnv = (key: string) => {
33
+ const value = process.env[key];
34
+ return value && value.trim().length > 0 ? value.trim() : undefined;
35
+ };
36
+
37
+ const getEnvApiKey = (providerId: string) => {
38
+ if (providerId === 'openrouter') return readEnv('OPENROUTER_API_KEY');
39
+ if (providerId === 'opencode') return readEnv('OPENCODE_API_KEY');
40
+ if (providerId === 'minimax') return readEnv('MINIMAX_API_KEY');
41
+ return undefined;
42
+ };
43
+
44
+ const ApiKeyAuthSchema = z.object({
45
+ type: z.literal('api'),
46
+ key: z.string()
47
+ });
48
+
49
+ const OAuthAuthSchema = z.object({
50
+ type: z.literal('oauth'),
51
+ access: z.string(),
52
+ refresh: z.string(),
53
+ expires: z.number(),
54
+ accountId: z.string().optional()
55
+ });
56
+
57
+ const WellKnownAuthSchema = z.object({
58
+ type: z.literal('wellknown')
59
+ });
60
+
61
+ const AuthInfoSchema = z.union([ApiKeyAuthSchema, OAuthAuthSchema, WellKnownAuthSchema]);
62
+ const AuthFileSchema = z.record(z.string(), AuthInfoSchema);
63
+
64
+ export type ApiKeyAuth = z.infer<typeof ApiKeyAuthSchema>;
65
+ export type OAuthAuth = z.infer<typeof OAuthAuthSchema>;
66
+ export type WellKnownAuth = z.infer<typeof WellKnownAuthSchema>;
67
+ export type AuthInfo = z.infer<typeof AuthInfoSchema>;
68
+
69
+ const getDataPath = (): string => {
70
+ const platform = os.platform();
71
+
72
+ if (platform === 'win32') {
73
+ const appdata = process.env.APPDATA || path.join(os.homedir(), 'AppData', 'Roaming');
74
+ return path.join(appdata, 'opencode');
93
75
  }
94
76
 
95
- /**
96
- * Read and parse the auth file
97
- */
98
- async function readAuthFile(): Promise<Record<string, AuthInfo>> {
99
- const filepath = getAuthFilePath();
100
- const file = Bun.file(filepath);
77
+ const xdgData = process.env.XDG_DATA_HOME || path.join(os.homedir(), '.local', 'share');
78
+ return path.join(xdgData, 'opencode');
79
+ };
101
80
 
102
- if (!(await file.exists())) {
103
- return {};
104
- }
81
+ const getAuthFilePath = (): string => path.join(getDataPath(), 'auth.json');
105
82
 
106
- const result = await Result.tryPromise(() => file.json());
107
- return result.match({
108
- ok: (content) => {
109
- const parsed = AuthFileSchema.safeParse(content);
110
- if (!parsed.success) {
111
- console.warn('Invalid auth.json format:', parsed.error);
112
- return {};
113
- }
114
- return parsed.data;
115
- },
116
- err: (error) => {
117
- console.warn('Failed to read auth.json:', error);
118
- return {};
119
- }
120
- });
121
- }
83
+ const readAuthFile = async (): Promise<Record<string, AuthInfo>> => {
84
+ const filepath = getAuthFilePath();
85
+ const file = Bun.file(filepath);
122
86
 
123
- /**
124
- * Get stored credentials for a provider
125
- * Returns undefined if no credentials are stored
126
- */
127
- export async function getCredentials(providerId: string): Promise<AuthInfo | undefined> {
128
- const authData = await readAuthFile();
129
- if (providerId === 'openrouter') {
130
- return authData.openrouter ?? authData['openrouter.ai'] ?? authData['openrouter-ai'];
131
- }
132
- return authData[providerId];
87
+ if (!(await file.exists())) {
88
+ return {};
133
89
  }
134
90
 
135
- export async function getAuthStatus(providerId: string): Promise<AuthStatus> {
136
- const allowedTypes = PROVIDER_AUTH_TYPES[providerId];
137
- if (!allowedTypes) return { status: 'missing' };
138
-
139
- const envKey = getEnvApiKey(providerId);
140
- if (envKey) {
141
- return allowedTypes.includes('api')
142
- ? { status: 'ok', authType: 'api', apiKey: envKey }
143
- : { status: 'invalid', authType: 'api' };
144
- }
145
-
146
- const auth = await getCredentials(providerId);
147
- if (!auth) return { status: 'missing' };
148
-
149
- if (!allowedTypes.includes(auth.type)) {
150
- return { status: 'invalid', authType: auth.type };
91
+ try {
92
+ const content = await file.json();
93
+ const parsed = AuthFileSchema.safeParse(content);
94
+ if (!parsed.success) {
95
+ console.warn('Invalid auth.json format:', parsed.error);
96
+ return {};
151
97
  }
152
-
153
- const oauthKey =
154
- auth.type === 'oauth'
155
- ? providerId === 'github-copilot'
156
- ? auth.refresh
157
- : auth.access
158
- : undefined;
159
- const apiKey = auth.type === 'api' ? auth.key : auth.type === 'oauth' ? oauthKey : undefined;
160
- const accountId = auth.type === 'oauth' ? auth.accountId : undefined;
161
- return { status: 'ok', authType: auth.type, apiKey, accountId };
98
+ return parsed.data;
99
+ } catch (error) {
100
+ console.warn('Failed to read auth.json:', error);
101
+ return {};
162
102
  }
103
+ };
163
104
 
164
- export const getProviderAuthHint = (providerId: string) => {
165
- switch (providerId) {
166
- case 'github-copilot':
167
- return 'Run "btca connect -p github-copilot" and complete device flow OAuth.';
168
- case 'openai':
169
- return 'Run "opencode auth --provider openai" and complete OAuth.';
170
- case 'openai-compat':
171
- return 'Set baseURL + name via "btca connect" and optionally add an API key.';
172
- case 'anthropic':
173
- return 'Run "opencode auth --provider anthropic" and enter an API key.';
174
- case 'google':
175
- return 'Run "opencode auth --provider google" and enter an API key or OAuth.';
176
- case 'openrouter':
177
- return 'Set OPENROUTER_API_KEY or run "opencode auth --provider openrouter".';
178
- case 'opencode':
179
- return 'Set OPENCODE_API_KEY or run "opencode auth --provider opencode".';
180
- case 'minimax':
181
- return 'Run "btca connect -p minimax" and enter your API key. Get your API key at https://platform.minimax.io/user-center/basic-information.';
182
- default:
183
- return 'Run "btca connect" and configure credentials for this provider.';
184
- }
185
- };
186
-
187
- /**
188
- * Check if a provider is authenticated
189
- */
190
- export async function isAuthenticated(providerId: string): Promise<boolean> {
191
- const status = await getAuthStatus(providerId);
192
- return status.status === 'ok';
105
+ export const getCredentials = async (providerId: string): Promise<AuthInfo | undefined> => {
106
+ const authData = await readAuthFile();
107
+ if (providerId === 'openrouter') {
108
+ return authData.openrouter ?? authData['openrouter.ai'] ?? authData['openrouter-ai'];
193
109
  }
194
-
195
- /**
196
- * Get the API key or access token for a provider
197
- * Returns undefined if not authenticated or no key available
198
- */
199
- export async function getApiKey(providerId: string): Promise<string | undefined> {
200
- const status = await getAuthStatus(providerId);
201
- if (status.status !== 'ok') return undefined;
202
- return status.apiKey;
110
+ return authData[providerId];
111
+ };
112
+
113
+ export const getAuthStatus = async (providerId: string): Promise<AuthStatus> => {
114
+ const allowedTypes = PROVIDER_AUTH_TYPES[providerId];
115
+ if (!allowedTypes) return { status: 'missing' };
116
+
117
+ const envKey = getEnvApiKey(providerId);
118
+ if (envKey) {
119
+ return allowedTypes.includes('api')
120
+ ? { status: 'ok', authType: 'api', apiKey: envKey }
121
+ : { status: 'invalid', authType: 'api' };
203
122
  }
204
123
 
205
- /**
206
- * Get all stored credentials
207
- */
208
- export async function getAllCredentials(): Promise<Record<string, AuthInfo>> {
209
- return readAuthFile();
210
- }
124
+ const auth = await getCredentials(providerId);
125
+ if (!auth) return { status: 'missing' };
211
126
 
212
- /**
213
- * Update stored credentials for a provider
214
- */
215
- export async function setCredentials(providerId: string, info: AuthInfo): Promise<void> {
216
- const filepath = getAuthFilePath();
217
- const existing = await readAuthFile();
218
- const next = { ...existing, [providerId]: info };
219
- await Bun.write(filepath, JSON.stringify(next, null, 2), { mode: 0o600 });
127
+ if (!allowedTypes.includes(auth.type)) {
128
+ return { status: 'invalid', authType: auth.type };
220
129
  }
221
130
 
222
- /**
223
- * Get the list of all authenticated provider IDs
224
- */
225
- export async function getAuthenticatedProviders(): Promise<string[]> {
226
- const providers = Object.keys(PROVIDER_AUTH_TYPES);
227
- const statuses = await Promise.all(providers.map((provider) => getAuthStatus(provider)));
228
- return providers.filter((_, index) => statuses[index]?.status === 'ok');
131
+ const oauthKey =
132
+ auth.type === 'oauth'
133
+ ? providerId === 'github-copilot'
134
+ ? auth.refresh
135
+ : auth.access
136
+ : undefined;
137
+ const apiKey = auth.type === 'api' ? auth.key : auth.type === 'oauth' ? oauthKey : undefined;
138
+ const accountId = auth.type === 'oauth' ? auth.accountId : undefined;
139
+ return { status: 'ok', authType: auth.type, apiKey, accountId };
140
+ };
141
+
142
+ export const getProviderAuthHint = (providerId: string) => {
143
+ switch (providerId) {
144
+ case 'github-copilot':
145
+ return 'Run "btca connect -p github-copilot" and complete device flow OAuth.';
146
+ case 'openai':
147
+ return 'Run "opencode auth --provider openai" and complete OAuth.';
148
+ case 'openai-compat':
149
+ return 'Set baseURL + name via "btca connect" and optionally add an API key.';
150
+ case 'anthropic':
151
+ return 'Run "opencode auth --provider anthropic" and enter an API key.';
152
+ case 'google':
153
+ return 'Run "opencode auth --provider google" and enter an API key or OAuth.';
154
+ case 'openrouter':
155
+ return 'Set OPENROUTER_API_KEY or run "opencode auth --provider openrouter".';
156
+ case 'opencode':
157
+ return 'Set OPENCODE_API_KEY or run "opencode auth --provider opencode".';
158
+ case 'minimax':
159
+ return 'Run "btca connect -p minimax" and enter your API key. Get your API key at https://platform.minimax.io/user-center/basic-information.';
160
+ default:
161
+ return 'Run "btca connect" and configure credentials for this provider.';
229
162
  }
230
- }
163
+ };
164
+
165
+ export const isAuthenticated = async (providerId: string): Promise<boolean> => {
166
+ const status = await getAuthStatus(providerId);
167
+ return status.status === 'ok';
168
+ };
169
+
170
+ export const getApiKey = async (providerId: string): Promise<string | undefined> => {
171
+ const status = await getAuthStatus(providerId);
172
+ if (status.status !== 'ok') return undefined;
173
+ return status.apiKey;
174
+ };
175
+
176
+ export const getAllCredentials = async (): Promise<Record<string, AuthInfo>> => readAuthFile();
177
+
178
+ export const setCredentials = async (providerId: string, info: AuthInfo): Promise<void> => {
179
+ const filepath = getAuthFilePath();
180
+ const existing = await readAuthFile();
181
+ const next = { ...existing, [providerId]: info };
182
+ await Bun.write(filepath, JSON.stringify(next, null, 2), { mode: 0o600 });
183
+ };
184
+
185
+ export const getAuthenticatedProviders = async (): Promise<string[]> => {
186
+ const providers = Object.keys(PROVIDER_AUTH_TYPES);
187
+ const statuses = await Promise.all(providers.map((provider) => getAuthStatus(provider)));
188
+ return providers.filter((_, index) => statuses[index]?.status === 'ok');
189
+ };
@@ -2,8 +2,25 @@
2
2
  * Provider Abstraction Layer
3
3
  * Exports auth, registry, and model utilities
4
4
  */
5
- export { Auth } from './auth.ts';
6
- export { Model } from './model.ts';
5
+ export {
6
+ getCredentials,
7
+ getAuthStatus,
8
+ getProviderAuthHint,
9
+ isAuthenticated,
10
+ getApiKey,
11
+ getAllCredentials,
12
+ setCredentials,
13
+ getAuthenticatedProviders
14
+ } from './auth.ts';
15
+ export {
16
+ getModel,
17
+ canUseModel,
18
+ getAvailableProviders,
19
+ ProviderNotFoundError,
20
+ ProviderNotAuthenticatedError,
21
+ ProviderAuthTypeError,
22
+ ProviderOptionsError
23
+ } from './model.ts';
7
24
  export {
8
25
  PROVIDER_REGISTRY,
9
26
  isProviderSupported,