@vesk/agentic 0.2.11
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 +53 -0
- package/dist/checkpoints.d.ts +155 -0
- package/dist/checkpoints.d.ts.map +1 -0
- package/dist/checkpoints.js +394 -0
- package/dist/config.d.ts +57 -0
- package/dist/config.d.ts.map +1 -0
- package/dist/config.js +399 -0
- package/dist/context.d.ts +21 -0
- package/dist/context.d.ts.map +1 -0
- package/dist/context.js +64 -0
- package/dist/dev-api.d.ts +85 -0
- package/dist/dev-api.d.ts.map +1 -0
- package/dist/dev-api.js +942 -0
- package/dist/index.d.ts +25 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +23 -0
- package/dist/loop.d.ts +156 -0
- package/dist/loop.d.ts.map +1 -0
- package/dist/loop.js +178 -0
- package/dist/permissions.d.ts +14 -0
- package/dist/permissions.d.ts.map +1 -0
- package/dist/permissions.js +74 -0
- package/dist/plugin.d.ts +38 -0
- package/dist/plugin.d.ts.map +1 -0
- package/dist/plugin.js +28 -0
- package/dist/providers/anthropic.d.ts +13 -0
- package/dist/providers/anthropic.d.ts.map +1 -0
- package/dist/providers/anthropic.js +100 -0
- package/dist/providers/google.d.ts +12 -0
- package/dist/providers/google.d.ts.map +1 -0
- package/dist/providers/google.js +87 -0
- package/dist/providers/ollama.d.ts +11 -0
- package/dist/providers/ollama.d.ts.map +1 -0
- package/dist/providers/ollama.js +61 -0
- package/dist/providers/openai.d.ts +12 -0
- package/dist/providers/openai.d.ts.map +1 -0
- package/dist/providers/openai.js +193 -0
- package/dist/providers/registry.d.ts +7 -0
- package/dist/providers/registry.d.ts.map +1 -0
- package/dist/providers/registry.js +33 -0
- package/dist/providers/types.d.ts +28 -0
- package/dist/providers/types.d.ts.map +1 -0
- package/dist/providers/types.js +15 -0
- package/dist/slash.d.ts +18 -0
- package/dist/slash.d.ts.map +1 -0
- package/dist/slash.js +77 -0
- package/dist/tools/browser.d.ts +3 -0
- package/dist/tools/browser.d.ts.map +1 -0
- package/dist/tools/browser.js +289 -0
- package/dist/tools/command.d.ts +14 -0
- package/dist/tools/command.d.ts.map +1 -0
- package/dist/tools/command.js +55 -0
- package/dist/tools/fs.d.ts +10 -0
- package/dist/tools/fs.d.ts.map +1 -0
- package/dist/tools/fs.js +142 -0
- package/dist/tools/vesk.d.ts +22 -0
- package/dist/tools/vesk.d.ts.map +1 -0
- package/dist/tools/vesk.js +828 -0
- package/dist/tools/web.d.ts +3 -0
- package/dist/tools/web.d.ts.map +1 -0
- package/dist/tools/web.js +111 -0
- package/package.json +47 -0
package/dist/config.js
ADDED
|
@@ -0,0 +1,399 @@
|
|
|
1
|
+
import { readFileSync, writeFileSync, existsSync, mkdirSync, chmodSync } from 'node:fs';
|
|
2
|
+
import { resolve, dirname } from 'node:path';
|
|
3
|
+
const DEFAULT_CONFIG = {
|
|
4
|
+
provider: 'openai',
|
|
5
|
+
model: 'gpt-4o-mini',
|
|
6
|
+
mode: 'explore',
|
|
7
|
+
maxSteps: 25,
|
|
8
|
+
};
|
|
9
|
+
export const SUPPORTED_PROVIDERS = [
|
|
10
|
+
'openai', 'anthropic', 'google', 'ollama',
|
|
11
|
+
'opencode', 'opencode-go', 'openrouter', 'loopers', 'custom',
|
|
12
|
+
];
|
|
13
|
+
// ---------------------------------------------------------------------------
|
|
14
|
+
// Paths
|
|
15
|
+
// ---------------------------------------------------------------------------
|
|
16
|
+
function configPath(projectDir) {
|
|
17
|
+
return resolve(projectDir, '.vesk', 'agentic', 'config.json');
|
|
18
|
+
}
|
|
19
|
+
function keysDir(projectDir) {
|
|
20
|
+
return resolve(projectDir, '.vesk', 'agentic', 'keys');
|
|
21
|
+
}
|
|
22
|
+
function sanitizeProvider(provider) {
|
|
23
|
+
// keep lower-case, replace path separators and disallowed chars
|
|
24
|
+
const p = provider.trim().toLowerCase();
|
|
25
|
+
if (!p)
|
|
26
|
+
return 'default';
|
|
27
|
+
// replace any slash/backslash or .. and non-alnum except - _ .
|
|
28
|
+
return p.replace(/[\/\\]+/g, '_').replace(/[^a-z0-9_.-]/g, '_') || 'default';
|
|
29
|
+
}
|
|
30
|
+
function normalizeProvider(provider) {
|
|
31
|
+
return provider.trim().toLowerCase();
|
|
32
|
+
}
|
|
33
|
+
function providerKeyPath(projectDir, provider) {
|
|
34
|
+
return resolve(keysDir(projectDir), `${sanitizeProvider(provider)}.key`);
|
|
35
|
+
}
|
|
36
|
+
// ── .env.local provider keys (VK_{PROVIDER}_KEY) ────────────────────────────
|
|
37
|
+
// Primary key store: project `.env.local` holds one line per provider, e.g.
|
|
38
|
+
// VK_OPENAI_KEY=sk-...
|
|
39
|
+
// VK_OPENCODE_KEY=sk-...
|
|
40
|
+
// VK_OPENCODE_GO_KEY=sk-...
|
|
41
|
+
// These are read into `process.env` at CLI startup via loadEnvFiles, so
|
|
42
|
+
// reading `process.env` reflects the file. Saving rewrites the file so the
|
|
43
|
+
// change persists for later `vesk dev`/`vesk start` runs.
|
|
44
|
+
export function providerDotenvVar(provider) {
|
|
45
|
+
const prov = sanitizeProvider(provider);
|
|
46
|
+
// opencode-go -> VK_OPENCODE_GO_KEY
|
|
47
|
+
return `VK_${prov.toUpperCase().replace(/[^A-Z0-9]/g, '_')}_KEY`;
|
|
48
|
+
}
|
|
49
|
+
export function dotenvPath(projectDir) {
|
|
50
|
+
return resolve(projectDir, '.env.local');
|
|
51
|
+
}
|
|
52
|
+
/**
|
|
53
|
+
* Read a provider key from the project `.env.local` file.
|
|
54
|
+
* Note: normally `process.env` already reflects `.env.local` (loaded at CLI
|
|
55
|
+
* startup); this reads the file directly as a defensive fallback (e.g. when
|
|
56
|
+
* the file changed after startup, or callers didn't go through loadEnvFiles).
|
|
57
|
+
*/
|
|
58
|
+
export function readDotenvValue(projectDir, key, def = null) {
|
|
59
|
+
try {
|
|
60
|
+
const p = dotenvPath(projectDir);
|
|
61
|
+
if (!existsSync(p))
|
|
62
|
+
return def;
|
|
63
|
+
const content = readFileSync(p, 'utf-8');
|
|
64
|
+
for (const rawLine of content.split('\n')) {
|
|
65
|
+
const line = rawLine.trim();
|
|
66
|
+
if (!line || line.startsWith('#'))
|
|
67
|
+
continue;
|
|
68
|
+
const eq = line.indexOf('=');
|
|
69
|
+
if (eq === -1)
|
|
70
|
+
continue;
|
|
71
|
+
if (line.slice(0, eq).trim() !== key)
|
|
72
|
+
continue;
|
|
73
|
+
let val = line.slice(eq + 1).trim();
|
|
74
|
+
if ((val.startsWith('"') && val.endsWith('"')) || (val.startsWith("'") && val.endsWith("'")))
|
|
75
|
+
val = val.slice(1, -1);
|
|
76
|
+
return val;
|
|
77
|
+
}
|
|
78
|
+
return def;
|
|
79
|
+
}
|
|
80
|
+
catch {
|
|
81
|
+
return def;
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
/**
|
|
85
|
+
* Set a `KEY=VALUE` line in the project `.env.local`, creating the file if
|
|
86
|
+
* missing, updating in place if present. Also mirrors into `process.env` so
|
|
87
|
+
* the current process sees it immediately. Returns true on success.
|
|
88
|
+
*/
|
|
89
|
+
export function writeDotenvValue(projectDir, key, value) {
|
|
90
|
+
const p = dotenvPath(projectDir);
|
|
91
|
+
const pad = value && !value.startsWith('#');
|
|
92
|
+
const newLine = `${key}=${pad && /^[A-Za-z0-9_@./:+-]+$/.test(value) ? value : JSON.stringify(value)}`;
|
|
93
|
+
try {
|
|
94
|
+
let out = '';
|
|
95
|
+
let replaced = false;
|
|
96
|
+
if (existsSync(p)) {
|
|
97
|
+
const lines = readFileSync(p, 'utf-8').split('\n');
|
|
98
|
+
for (const rawLine of lines) {
|
|
99
|
+
const trimmed = rawLine.trim();
|
|
100
|
+
const eq = trimmed.indexOf('=');
|
|
101
|
+
if (eq !== -1 && trimmed.slice(0, eq).trim() === key) {
|
|
102
|
+
out += newLine + '\n';
|
|
103
|
+
replaced = true;
|
|
104
|
+
}
|
|
105
|
+
else {
|
|
106
|
+
out += rawLine + '\n';
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
if (!replaced)
|
|
111
|
+
out += newLine + '\n';
|
|
112
|
+
writeFileSync(p, out, { mode: 0o600 });
|
|
113
|
+
try {
|
|
114
|
+
chmodSync(p, 0o600);
|
|
115
|
+
}
|
|
116
|
+
catch { }
|
|
117
|
+
process.env[key] = value;
|
|
118
|
+
return true;
|
|
119
|
+
}
|
|
120
|
+
catch {
|
|
121
|
+
return false;
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
// ---------------------------------------------------------------------------
|
|
125
|
+
// Config (non-secret)
|
|
126
|
+
// ---------------------------------------------------------------------------
|
|
127
|
+
export function loadAgenticConfig(projectDir) {
|
|
128
|
+
const p = configPath(projectDir);
|
|
129
|
+
let cfg = { ...DEFAULT_CONFIG };
|
|
130
|
+
try {
|
|
131
|
+
if (existsSync(p))
|
|
132
|
+
cfg = { ...cfg, ...JSON.parse(readFileSync(p, 'utf-8')) };
|
|
133
|
+
}
|
|
134
|
+
catch { }
|
|
135
|
+
// hasKey = true if any provider key is set (read from .env.local VK_*_KEY)
|
|
136
|
+
let hasKey = false;
|
|
137
|
+
try {
|
|
138
|
+
for (const prov of SUPPORTED_PROVIDERS) {
|
|
139
|
+
if (getProviderSpecificKey(projectDir, prov)) {
|
|
140
|
+
hasKey = true;
|
|
141
|
+
break;
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
catch { }
|
|
146
|
+
return { ...cfg, hasKey };
|
|
147
|
+
}
|
|
148
|
+
export function saveAgenticConfig(projectDir, patch) {
|
|
149
|
+
const p = configPath(projectDir);
|
|
150
|
+
const current = loadAgenticConfig(projectDir);
|
|
151
|
+
const next = { ...DEFAULT_CONFIG, ...current, ...patch };
|
|
152
|
+
// never store apiKey in config.json
|
|
153
|
+
const toWrite = { provider: next.provider, model: next.model, mode: next.mode, maxSteps: next.maxSteps };
|
|
154
|
+
if (next.baseUrl)
|
|
155
|
+
toWrite.baseUrl = next.baseUrl;
|
|
156
|
+
try {
|
|
157
|
+
mkdirSync(dirname(p), { recursive: true });
|
|
158
|
+
writeFileSync(p, JSON.stringify(toWrite, null, 2), 'utf-8');
|
|
159
|
+
}
|
|
160
|
+
catch { }
|
|
161
|
+
return next;
|
|
162
|
+
}
|
|
163
|
+
// ---------------------------------------------------------------------------
|
|
164
|
+
// Masking
|
|
165
|
+
// ---------------------------------------------------------------------------
|
|
166
|
+
export function maskApiKey(apiKey) {
|
|
167
|
+
if (!apiKey)
|
|
168
|
+
return null;
|
|
169
|
+
const trimmed = apiKey.trim();
|
|
170
|
+
if (!trimmed)
|
|
171
|
+
return null;
|
|
172
|
+
if (trimmed.length <= 8)
|
|
173
|
+
return '***';
|
|
174
|
+
return trimmed.slice(0, 7) + '***' + trimmed.slice(-4);
|
|
175
|
+
}
|
|
176
|
+
// aliases for "masked preview per provider" requirement — multiple names for compatibility
|
|
177
|
+
export const maskKey = maskApiKey;
|
|
178
|
+
export const maskedPreview = maskApiKey;
|
|
179
|
+
export function getMaskedKey(projectDir, provider) {
|
|
180
|
+
return getKeyPreview(projectDir, provider);
|
|
181
|
+
}
|
|
182
|
+
export function getApiKeyPreview(projectDir, provider) {
|
|
183
|
+
return getKeyPreview(projectDir, provider);
|
|
184
|
+
}
|
|
185
|
+
export function getMaskedPreview(projectDir, provider) {
|
|
186
|
+
return getKeyPreview(projectDir, provider);
|
|
187
|
+
}
|
|
188
|
+
export function getKeyPreview(projectDir, provider) {
|
|
189
|
+
// preview per provider — mask the result of getApiKey (which includes fallback to legacy/generic)
|
|
190
|
+
// This matches spec: getApiKey with provider checks env provider, file, legacy, generic
|
|
191
|
+
const key = getApiKey(projectDir, provider);
|
|
192
|
+
return maskApiKey(key);
|
|
193
|
+
}
|
|
194
|
+
// internal helper for per-provider direct lookup (no legacy/generic fallback)
|
|
195
|
+
function getProviderSpecificKey(projectDir, provider) {
|
|
196
|
+
if (!provider || !provider.trim())
|
|
197
|
+
return null;
|
|
198
|
+
const prov = provider.trim();
|
|
199
|
+
// 0. .env.local VK_*_KEY (primary) — process.env reflects the file at startup
|
|
200
|
+
const dotenvName = providerDotenvVar(prov);
|
|
201
|
+
const dotenvEnv = process.env[dotenvName];
|
|
202
|
+
if (dotenvEnv && dotenvEnv.trim())
|
|
203
|
+
return dotenvEnv.trim();
|
|
204
|
+
const dotenvFile = readDotenvValue(projectDir, dotenvName);
|
|
205
|
+
if (dotenvFile && dotenvFile.trim())
|
|
206
|
+
return dotenvFile.trim();
|
|
207
|
+
// 1. legacy per-provider file (compat fallback)
|
|
208
|
+
try {
|
|
209
|
+
const pp = providerKeyPath(projectDir, prov);
|
|
210
|
+
if (existsSync(pp)) {
|
|
211
|
+
const v = readFileSync(pp, 'utf-8').trim();
|
|
212
|
+
if (v)
|
|
213
|
+
return v;
|
|
214
|
+
}
|
|
215
|
+
}
|
|
216
|
+
catch { }
|
|
217
|
+
return null;
|
|
218
|
+
}
|
|
219
|
+
// ---------------------------------------------------------------------------
|
|
220
|
+
// Per-provider keys
|
|
221
|
+
// ---------------------------------------------------------------------------
|
|
222
|
+
/**
|
|
223
|
+
* Save an API key for a specific provider.
|
|
224
|
+
*
|
|
225
|
+
* Supports both new 3-arg form `saveApiKey(projectDir, provider, apiKey)`
|
|
226
|
+
* and legacy 2-arg form `saveApiKey(projectDir, apiKey)` for backward compat.
|
|
227
|
+
* Keys are stored in project `.env.local` as VK_{PROVIDER}_KEY (0600).
|
|
228
|
+
* The legacy 2-arg form targets the currently-configured provider.
|
|
229
|
+
*/
|
|
230
|
+
export function saveApiKey(projectDir, providerOrApiKey, apiKeyMaybe) {
|
|
231
|
+
let provider;
|
|
232
|
+
let apiKey;
|
|
233
|
+
if (apiKeyMaybe === undefined) {
|
|
234
|
+
// legacy 2-arg — target the currently-configured provider
|
|
235
|
+
provider = loadAgenticConfig(projectDir).provider;
|
|
236
|
+
apiKey = providerOrApiKey;
|
|
237
|
+
}
|
|
238
|
+
else {
|
|
239
|
+
provider = providerOrApiKey;
|
|
240
|
+
apiKey = apiKeyMaybe;
|
|
241
|
+
}
|
|
242
|
+
if (typeof apiKey !== 'string')
|
|
243
|
+
apiKey = String(apiKey ?? '');
|
|
244
|
+
if (provider !== null) {
|
|
245
|
+
provider = provider.trim();
|
|
246
|
+
if (!provider)
|
|
247
|
+
provider = null;
|
|
248
|
+
}
|
|
249
|
+
if (provider) {
|
|
250
|
+
// Primary: write/update the provider line in project .env.local (VK_*_KEY)
|
|
251
|
+
const dotenvName = providerDotenvVar(provider);
|
|
252
|
+
writeDotenvValue(projectDir, dotenvName, apiKey);
|
|
253
|
+
}
|
|
254
|
+
else {
|
|
255
|
+
// no provider configured — fall back to the openai line
|
|
256
|
+
writeDotenvValue(projectDir, providerDotenvVar('openai'), apiKey);
|
|
257
|
+
}
|
|
258
|
+
}
|
|
259
|
+
/**
|
|
260
|
+
* Get API key for a provider.
|
|
261
|
+
* Precedence:
|
|
262
|
+
* 1. .env.local VK_{PROVIDER}_KEY (primary store; reflected in process.env)
|
|
263
|
+
* 2. (fallback) legacy .vesk/agentic/keys/{provider}.key
|
|
264
|
+
* If `provider` is omitted, returns the key for the currently-configured
|
|
265
|
+
* provider, else null.
|
|
266
|
+
*/
|
|
267
|
+
export function getApiKey(projectDir, provider) {
|
|
268
|
+
const hasProvider = typeof provider === 'string' && provider.trim().length > 0;
|
|
269
|
+
if (hasProvider) {
|
|
270
|
+
const prov = provider.trim();
|
|
271
|
+
const dotenvName = providerDotenvVar(prov);
|
|
272
|
+
if (process.env[dotenvName] && process.env[dotenvName].trim())
|
|
273
|
+
return process.env[dotenvName].trim();
|
|
274
|
+
try {
|
|
275
|
+
const df = readDotenvValue(projectDir, dotenvName);
|
|
276
|
+
if (df && df.trim())
|
|
277
|
+
return df.trim();
|
|
278
|
+
}
|
|
279
|
+
catch { }
|
|
280
|
+
try {
|
|
281
|
+
const pp = providerKeyPath(projectDir, prov);
|
|
282
|
+
if (existsSync(pp)) {
|
|
283
|
+
const v = readFileSync(pp, 'utf-8').trim();
|
|
284
|
+
if (v)
|
|
285
|
+
return v;
|
|
286
|
+
}
|
|
287
|
+
}
|
|
288
|
+
catch { }
|
|
289
|
+
return null;
|
|
290
|
+
}
|
|
291
|
+
else {
|
|
292
|
+
const prov = normalizeProvider(loadAgenticConfig(projectDir).provider);
|
|
293
|
+
const dotenvName = providerDotenvVar(prov);
|
|
294
|
+
if (process.env[dotenvName] && process.env[dotenvName].trim())
|
|
295
|
+
return process.env[dotenvName].trim();
|
|
296
|
+
try {
|
|
297
|
+
const df = readDotenvValue(projectDir, dotenvName);
|
|
298
|
+
if (df && df.trim())
|
|
299
|
+
return df.trim();
|
|
300
|
+
}
|
|
301
|
+
catch { }
|
|
302
|
+
return null;
|
|
303
|
+
}
|
|
304
|
+
}
|
|
305
|
+
// hasKey / hasApiKey — per-provider check
|
|
306
|
+
export function hasApiKey(projectDirOrProvider, providerMaybe) {
|
|
307
|
+
let projectDir;
|
|
308
|
+
let provider;
|
|
309
|
+
if (providerMaybe !== undefined) {
|
|
310
|
+
projectDir = projectDirOrProvider;
|
|
311
|
+
provider = providerMaybe;
|
|
312
|
+
}
|
|
313
|
+
else {
|
|
314
|
+
// single arg — could be provider name or projectDir
|
|
315
|
+
const arg = projectDirOrProvider;
|
|
316
|
+
// heuristic: if arg is a known provider or doesn't look like a path, treat as provider with cwd
|
|
317
|
+
const isProviderName = SUPPORTED_PROVIDERS.includes(arg) || SUPPORTED_PROVIDERS.includes(arg.toLowerCase());
|
|
318
|
+
const looksLikePath = arg.includes('/') || arg.includes('\\') || arg.startsWith('.') || arg.includes(':');
|
|
319
|
+
if (isProviderName && !looksLikePath) {
|
|
320
|
+
projectDir = process.cwd();
|
|
321
|
+
provider = arg;
|
|
322
|
+
}
|
|
323
|
+
else if (!looksLikePath && /^[a-z0-9_.-]+$/i.test(arg) && arg.length < 30 && !existsSync(resolve(arg, '.vesk'))) {
|
|
324
|
+
// ambiguous short string without path separators — treat as provider if it's in supported list or single word
|
|
325
|
+
// but to avoid false positive for projectDir like "/tmp/foo", we check existsSync for .vesk fallback above.
|
|
326
|
+
// If it doesn't look like a path and is known provider, already handled; otherwise treat as projectDir without provider
|
|
327
|
+
// For safety, if arg matches provider pattern and is short, consider it provider.
|
|
328
|
+
// We'll default to projectDir without provider for hasKey(projectDir) calls.
|
|
329
|
+
// To cover both, check if arg is supported provider else treat as projectDir
|
|
330
|
+
if (SUPPORTED_PROVIDERS.includes(arg.toLowerCase())) {
|
|
331
|
+
projectDir = process.cwd();
|
|
332
|
+
provider = arg;
|
|
333
|
+
}
|
|
334
|
+
else {
|
|
335
|
+
projectDir = arg;
|
|
336
|
+
provider = undefined;
|
|
337
|
+
}
|
|
338
|
+
}
|
|
339
|
+
else {
|
|
340
|
+
projectDir = arg;
|
|
341
|
+
provider = undefined;
|
|
342
|
+
}
|
|
343
|
+
}
|
|
344
|
+
return !!getApiKey(projectDir, provider);
|
|
345
|
+
}
|
|
346
|
+
// primary name "hasKey" per prompt
|
|
347
|
+
export const hasKey = hasApiKey;
|
|
348
|
+
// additional alias used in some codebases
|
|
349
|
+
export const hasApiKeys = hasApiKey;
|
|
350
|
+
// listApiKeys
|
|
351
|
+
export function listApiKeys(projectDir) {
|
|
352
|
+
const out = {};
|
|
353
|
+
const seen = new Set(SUPPORTED_PROVIDERS);
|
|
354
|
+
// discover additional providers from .env.local VK_*_KEY lines
|
|
355
|
+
try {
|
|
356
|
+
const p = dotenvPath(projectDir);
|
|
357
|
+
if (existsSync(p)) {
|
|
358
|
+
for (const rawLine of readFileSync(p, 'utf-8').split('\n')) {
|
|
359
|
+
const trimmed = rawLine.trim();
|
|
360
|
+
if (!trimmed.startsWith('VK_') || !trimmed.endsWith('_KEY=') && !trimmed.includes('_KEY='))
|
|
361
|
+
continue;
|
|
362
|
+
const eq = trimmed.indexOf('=');
|
|
363
|
+
if (eq === -1)
|
|
364
|
+
continue;
|
|
365
|
+
const k = trimmed.slice(0, eq).trim();
|
|
366
|
+
if (!k.startsWith('VK_') || !k.endsWith('_KEY'))
|
|
367
|
+
continue;
|
|
368
|
+
const prov = k.slice(3, -4).toLowerCase().replace(/_/g, '-');
|
|
369
|
+
if (prov)
|
|
370
|
+
seen.add(prov);
|
|
371
|
+
}
|
|
372
|
+
}
|
|
373
|
+
}
|
|
374
|
+
catch { }
|
|
375
|
+
// also consider VK_*_KEY env vars not in seen
|
|
376
|
+
try {
|
|
377
|
+
for (const k of Object.keys(process.env)) {
|
|
378
|
+
if (k.startsWith('VK_') && k.endsWith('_KEY')) {
|
|
379
|
+
const prov = k.slice(3, -4).toLowerCase().replace(/_/g, '-');
|
|
380
|
+
if (prov)
|
|
381
|
+
seen.add(prov);
|
|
382
|
+
}
|
|
383
|
+
}
|
|
384
|
+
}
|
|
385
|
+
catch { }
|
|
386
|
+
for (const p of seen) {
|
|
387
|
+
try {
|
|
388
|
+
out[p] = !!getApiKey(projectDir, p);
|
|
389
|
+
}
|
|
390
|
+
catch {
|
|
391
|
+
out[p] = false;
|
|
392
|
+
}
|
|
393
|
+
}
|
|
394
|
+
// Ensure supported providers always present
|
|
395
|
+
for (const p of SUPPORTED_PROVIDERS)
|
|
396
|
+
if (!(p in out))
|
|
397
|
+
out[p] = false;
|
|
398
|
+
return out;
|
|
399
|
+
}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import type { AgentMode } from './permissions.js';
|
|
2
|
+
export interface LiveProjectContext {
|
|
3
|
+
files?: Array<{
|
|
4
|
+
path: string;
|
|
5
|
+
content: string;
|
|
6
|
+
}>;
|
|
7
|
+
config?: unknown;
|
|
8
|
+
diagnostics?: unknown[];
|
|
9
|
+
plugins?: unknown;
|
|
10
|
+
git?: unknown;
|
|
11
|
+
compilerState?: unknown;
|
|
12
|
+
}
|
|
13
|
+
export interface AgentContextLayers {
|
|
14
|
+
framework: string;
|
|
15
|
+
projectMd: string;
|
|
16
|
+
live: LiveProjectContext;
|
|
17
|
+
}
|
|
18
|
+
export declare function loadFrameworkKnowledge(projectDir?: string): string;
|
|
19
|
+
export declare function loadProjectKnowledge(projectDir: string): string;
|
|
20
|
+
export declare function assembleSystemPrompt(layers: AgentContextLayers, mode: AgentMode): string;
|
|
21
|
+
//# sourceMappingURL=context.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"context.d.ts","sourceRoot":"","sources":["../src/context.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,kBAAkB,CAAC;AAElD,MAAM,WAAW,kBAAkB;IACjC,KAAK,CAAC,EAAE,KAAK,CAAC;QAAE,IAAI,EAAE,MAAM,CAAC;QAAC,OAAO,EAAE,MAAM,CAAA;KAAE,CAAC,CAAC;IACjD,MAAM,CAAC,EAAE,OAAO,CAAC;IACjB,WAAW,CAAC,EAAE,OAAO,EAAE,CAAC;IACxB,OAAO,CAAC,EAAE,OAAO,CAAC;IAClB,GAAG,CAAC,EAAE,OAAO,CAAC;IACd,aAAa,CAAC,EAAE,OAAO,CAAC;CACzB;AAED,MAAM,WAAW,kBAAkB;IACjC,SAAS,EAAE,MAAM,CAAC;IAClB,SAAS,EAAE,MAAM,CAAC;IAClB,IAAI,EAAE,kBAAkB,CAAC;CAC1B;AAED,wBAAgB,sBAAsB,CAAC,UAAU,CAAC,EAAE,MAAM,GAAG,MAAM,CAgBlE;AAED,wBAAgB,oBAAoB,CAAC,UAAU,EAAE,MAAM,GAAG,MAAM,CAO/D;AAED,wBAAgB,oBAAoB,CAAC,MAAM,EAAE,kBAAkB,EAAE,IAAI,EAAE,SAAS,GAAG,MAAM,CAkBxF"}
|
package/dist/context.js
ADDED
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
import { readFileSync, existsSync } from 'node:fs';
|
|
2
|
+
import { resolve } from 'node:path';
|
|
3
|
+
export function loadFrameworkKnowledge(projectDir) {
|
|
4
|
+
// Prefer llm.txt at project root or repo root, fallback to minimal built-in knowledge.
|
|
5
|
+
const candidates = [
|
|
6
|
+
projectDir ? resolve(projectDir, 'llm.txt') : null,
|
|
7
|
+
resolve(process.cwd(), 'llm.txt'),
|
|
8
|
+
resolve(process.cwd(), 'plans', 'devtools.md'),
|
|
9
|
+
].filter(Boolean);
|
|
10
|
+
for (const p of candidates) {
|
|
11
|
+
try {
|
|
12
|
+
if (existsSync(p))
|
|
13
|
+
return readFileSync(p, 'utf-8').slice(0, 8000);
|
|
14
|
+
}
|
|
15
|
+
catch { }
|
|
16
|
+
}
|
|
17
|
+
return [
|
|
18
|
+
'Vesk is a compiler-first framework. Components use `component Name { ... }` with expression or statement mode.',
|
|
19
|
+
'Reactivity: const &[count]=track(0), effect(), derived, islands via #client.',
|
|
20
|
+
'Routing: app/ file-based, useFetch/stream, Md for markdown.',
|
|
21
|
+
'Config: vesk.config.ts, plugins via @vesk/plugin-*, dev server is capability-gated.',
|
|
22
|
+
].join('\n');
|
|
23
|
+
}
|
|
24
|
+
export function loadProjectKnowledge(projectDir) {
|
|
25
|
+
const p = resolve(projectDir, 'agents.md');
|
|
26
|
+
try {
|
|
27
|
+
if (existsSync(p))
|
|
28
|
+
return readFileSync(p, 'utf-8').slice(0, 8000);
|
|
29
|
+
}
|
|
30
|
+
catch { }
|
|
31
|
+
// Also try AGENTS.md
|
|
32
|
+
const p2 = resolve(projectDir, 'AGENTS.md');
|
|
33
|
+
try {
|
|
34
|
+
if (existsSync(p2))
|
|
35
|
+
return readFileSync(p2, 'utf-8').slice(0, 8000);
|
|
36
|
+
}
|
|
37
|
+
catch { }
|
|
38
|
+
return '';
|
|
39
|
+
}
|
|
40
|
+
export function assembleSystemPrompt(layers, mode) {
|
|
41
|
+
const parts = [];
|
|
42
|
+
parts.push('# Vesk Framework Knowledge');
|
|
43
|
+
parts.push(layers.framework || '(no framework docs)');
|
|
44
|
+
parts.push('\n# Project Knowledge (agents.md)');
|
|
45
|
+
parts.push(layers.projectMd || '(no project agents.md)');
|
|
46
|
+
if (layers.live) {
|
|
47
|
+
parts.push('\n# Live Project Context');
|
|
48
|
+
if (layers.live.config)
|
|
49
|
+
parts.push(`Config: ${JSON.stringify(layers.live.config).slice(0, 2000)}`);
|
|
50
|
+
if (layers.live.diagnostics)
|
|
51
|
+
parts.push(`Diagnostics: ${JSON.stringify(layers.live.diagnostics).slice(0, 2000)}`);
|
|
52
|
+
if (layers.live.plugins)
|
|
53
|
+
parts.push(`Plugins: ${JSON.stringify(layers.live.plugins).slice(0, 2000)}`);
|
|
54
|
+
}
|
|
55
|
+
parts.push(`\n# Mode: ${mode}`);
|
|
56
|
+
if (mode === 'explore')
|
|
57
|
+
parts.push('You are in Explore (read-only). Do NOT modify files, run commands, or install packages. Explain and analyze only.');
|
|
58
|
+
if (mode === 'debug')
|
|
59
|
+
parts.push('You are in Debug. You may read and make controlled fixes to relevant source files and run build/tests, but respect permissions.');
|
|
60
|
+
if (mode === 'agent')
|
|
61
|
+
parts.push('You are in Agent. You may act fully within granted capabilities. Always respect the capability table; blocked tools will return errors.');
|
|
62
|
+
parts.push('\nAlways use Vesk-native tools (vesk.*) when they apply; prefer them over raw filesystem writes.');
|
|
63
|
+
return parts.join('\n');
|
|
64
|
+
}
|
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Dev API router — `createAgentRouter` (B6-plug of plans/devtools.md).
|
|
3
|
+
*
|
|
4
|
+
* A self-contained, dependency-injectable router for the agentic
|
|
5
|
+
* dev-panel HTTP endpoints under `/__vesk/agent/*`. Mirrors the shape of
|
|
6
|
+
* `createDevApiRouter` from `@vesk/adapter/src/dev-api.ts`:
|
|
7
|
+
*
|
|
8
|
+
* POST /__vesk/agent/run → run the agent (prompt, mode, providerConfig)
|
|
9
|
+
* GET /__vesk/agent/history → list checkpoints (history.json)
|
|
10
|
+
* POST /__vesk/agent/checkpoint → create a checkpoint
|
|
11
|
+
* POST /__vesk/agent/rollback → rollback to a checkpoint
|
|
12
|
+
*
|
|
13
|
+
* ARCHITECTURE: the Dev Server is the ONLY path from browser → project files /
|
|
14
|
+
* build system. Every endpoint is gated by an `AgentCapability` in
|
|
15
|
+
* `AgentCapabilityTable` (server-enforced — the browser cannot bypass it).
|
|
16
|
+
* There is NO raw `child_process` reach: the agent's `command.execute` tool
|
|
17
|
+
* routes through the dev server's allowlisted `runCommand` hook, and file
|
|
18
|
+
* access is containment-checked.
|
|
19
|
+
*
|
|
20
|
+
* Pure (fake injectable inputs, no socket/listener), mirroring
|
|
21
|
+
* `createPluginStateRouter` / `createDevApiRouter`: returns
|
|
22
|
+
* `{ route(method, pathname, body, search) }`, yielding `null` for
|
|
23
|
+
* non-`/ __vesk/agent/*` paths so the dev server can fall through to the
|
|
24
|
+
* next router (e.g. the adapter's `createDevApiRouter`).
|
|
25
|
+
*
|
|
26
|
+
* Zero deps — only local `@vesk/agentic` modules; no npm dependencies
|
|
27
|
+
* beyond `@vesk/types`.
|
|
28
|
+
*/
|
|
29
|
+
import type { AgentCapabilityTable, AgentMode } from './permissions.js';
|
|
30
|
+
import type { AgentResult, AgentStreamEvent } from './loop.js';
|
|
31
|
+
import type { ProviderConfig } from './providers/types.js';
|
|
32
|
+
import type { Checkpoint } from './checkpoints.js';
|
|
33
|
+
export interface DevPanelResponse {
|
|
34
|
+
status: number;
|
|
35
|
+
headers: Record<string, string>;
|
|
36
|
+
body: string;
|
|
37
|
+
encoding?: 'utf8' | 'base64';
|
|
38
|
+
/** When set, `body`/`encoding` are ignored and this async iterable of
|
|
39
|
+
(already-framed) strings is streamed to the client instead — used for
|
|
40
|
+
SSE agent progress. */
|
|
41
|
+
stream?: AsyncIterable<string>;
|
|
42
|
+
}
|
|
43
|
+
export interface AgentRouter {
|
|
44
|
+
route: (method: string, pathname: string, body?: unknown, search?: string) => Promise<DevPanelResponse | null>;
|
|
45
|
+
}
|
|
46
|
+
export interface AgentRouterOptions {
|
|
47
|
+
/** Absolute path to the project root (where `vesk.config.ts` lives). */
|
|
48
|
+
projectDir: string;
|
|
49
|
+
/** Absolute path to the `app/` directory. */
|
|
50
|
+
appDir: string;
|
|
51
|
+
/** Absolute path to the `.vesk` state directory. */
|
|
52
|
+
veskDir: string;
|
|
53
|
+
/** Current permission snapshot — server-enforced. */
|
|
54
|
+
getPermissions: () => AgentCapabilityTable;
|
|
55
|
+
/**
|
|
56
|
+
* Run the agent for a single turn.
|
|
57
|
+
* `(prompt, mode, providerConfig) => AgentResult`
|
|
58
|
+
*/
|
|
59
|
+
runAgent: (prompt: string, mode: AgentMode, providerConfig?: ProviderConfig | unknown) => Promise<AgentResult>;
|
|
60
|
+
/**
|
|
61
|
+
* Stream the agent for a single turn. Optional — when provided, the
|
|
62
|
+
* `/__vesk/agent/run` endpoint responds with an SSE stream of
|
|
63
|
+
* `AgentStreamEvent`s for `{ stream: true }` requests.
|
|
64
|
+
*/
|
|
65
|
+
runAgentStream?: (prompt: string, mode: AgentMode, providerConfig?: ProviderConfig | unknown) => AsyncIterable<AgentStreamEvent> | Promise<AsyncIterable<AgentStreamEvent>>;
|
|
66
|
+
/**
|
|
67
|
+
* List all checkpoints, newest first. Closure form is typically
|
|
68
|
+
* `() => manager.list()` or `() => listCheckpoints(projectDir)`.
|
|
69
|
+
*/
|
|
70
|
+
listCheckpoints?: () => Checkpoint[] | Promise<Checkpoint[]>;
|
|
71
|
+
/**
|
|
72
|
+
* Rollback to a checkpoint by id. Closure form is
|
|
73
|
+
* `(id: string) => Checkpoint | null`.
|
|
74
|
+
*/
|
|
75
|
+
rollback?: (id: string) => Checkpoint | null | Promise<Checkpoint | null>;
|
|
76
|
+
/**
|
|
77
|
+
* Optional injection for checkpoint creation. Signature may be
|
|
78
|
+
* `(message, changes?, buildResult?) => Checkpoint` or
|
|
79
|
+
* `(projectDir, message, changes?, buildResult?) => Checkpoint` or
|
|
80
|
+
* `(opts: CreateCheckpointOptions) => Checkpoint`.
|
|
81
|
+
*/
|
|
82
|
+
createCheckpoint?: (...args: unknown[]) => Checkpoint | Promise<Checkpoint>;
|
|
83
|
+
}
|
|
84
|
+
export declare function createAgentRouter(opts: AgentRouterOptions): AgentRouter;
|
|
85
|
+
//# sourceMappingURL=dev-api.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"dev-api.d.ts","sourceRoot":"","sources":["../src/dev-api.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;GA2BG;AAEH,OAAO,KAAK,EAAE,oBAAoB,EAAE,SAAS,EAAE,MAAM,kBAAkB,CAAC;AACxE,OAAO,KAAK,EAAE,WAAW,EAAE,gBAAgB,EAAE,MAAM,WAAW,CAAC;AAC/D,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,sBAAsB,CAAC;AAC3D,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,kBAAkB,CAAC;AAenD,MAAM,WAAW,gBAAgB;IAC/B,MAAM,EAAE,MAAM,CAAC;IACf,OAAO,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IAChC,IAAI,EAAE,MAAM,CAAC;IACb,QAAQ,CAAC,EAAE,MAAM,GAAG,QAAQ,CAAC;IAC7B;;8BAE0B;IAC1B,MAAM,CAAC,EAAE,aAAa,CAAC,MAAM,CAAC,CAAC;CAChC;AAED,MAAM,WAAW,WAAW;IAC1B,KAAK,EAAE,CACL,MAAM,EAAE,MAAM,EACd,QAAQ,EAAE,MAAM,EAChB,IAAI,CAAC,EAAE,OAAO,EACd,MAAM,CAAC,EAAE,MAAM,KACZ,OAAO,CAAC,gBAAgB,GAAG,IAAI,CAAC,CAAC;CACvC;AAED,MAAM,WAAW,kBAAkB;IACjC,wEAAwE;IACxE,UAAU,EAAE,MAAM,CAAC;IACnB,6CAA6C;IAC7C,MAAM,EAAE,MAAM,CAAC;IACf,oDAAoD;IACpD,OAAO,EAAE,MAAM,CAAC;IAChB,qDAAqD;IACrD,cAAc,EAAE,MAAM,oBAAoB,CAAC;IAC3C;;;OAGG;IACH,QAAQ,EAAE,CACR,MAAM,EAAE,MAAM,EACd,IAAI,EAAE,SAAS,EACf,cAAc,CAAC,EAAE,cAAc,GAAG,OAAO,KACtC,OAAO,CAAC,WAAW,CAAC,CAAC;IAC1B;;;;OAIG;IACH,cAAc,CAAC,EAAE,CACf,MAAM,EAAE,MAAM,EACd,IAAI,EAAE,SAAS,EACf,cAAc,CAAC,EAAE,cAAc,GAAG,OAAO,KACtC,aAAa,CAAC,gBAAgB,CAAC,GAAG,OAAO,CAAC,aAAa,CAAC,gBAAgB,CAAC,CAAC,CAAC;IAChF;;;OAGG;IACH,eAAe,CAAC,EAAE,MAAM,UAAU,EAAE,GAAG,OAAO,CAAC,UAAU,EAAE,CAAC,CAAC;IAC7D;;;OAGG;IACH,QAAQ,CAAC,EAAE,CAAC,EAAE,EAAE,MAAM,KAAK,UAAU,GAAG,IAAI,GAAG,OAAO,CAAC,UAAU,GAAG,IAAI,CAAC,CAAC;IAC1E;;;;;OAKG;IACH,gBAAgB,CAAC,EAAE,CAAC,GAAG,IAAI,EAAE,OAAO,EAAE,KAAK,UAAU,GAAG,OAAO,CAAC,UAAU,CAAC,CAAC;CAC7E;AA4HD,wBAAgB,iBAAiB,CAAC,IAAI,EAAE,kBAAkB,GAAG,WAAW,CA6pBvE"}
|