@agentskit/cli 0.5.3 → 0.7.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/README.md +6 -1
- package/dist/bin.cjs +1746 -441
- package/dist/bin.cjs.map +1 -1
- package/dist/bin.js +1 -1
- package/dist/{chunk-CCPJYGHP.js → chunk-72XFU2X2.js} +1605 -292
- package/dist/chunk-72XFU2X2.js.map +1 -0
- package/dist/index.cjs +1781 -441
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +480 -7
- package/dist/index.d.ts +480 -7
- package/dist/index.js +1 -1
- package/package.json +9 -8
- package/dist/chunk-CCPJYGHP.js.map +0 -1
|
@@ -1,33 +1,37 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
import { mkdir, writeFile, readFile } from 'fs/promises';
|
|
3
|
-
import
|
|
2
|
+
import { readdir, stat, mkdir, writeFile, glob, readFile } from 'fs/promises';
|
|
3
|
+
import { homedir } from 'os';
|
|
4
|
+
import path, { join, resolve, isAbsolute, basename } from 'path';
|
|
4
5
|
import { kimi, grok, deepseek, ollama, gemini, anthropic, openai } from '@agentskit/adapters';
|
|
5
|
-
import
|
|
6
|
+
import { randomBytes, createHash } from 'crypto';
|
|
7
|
+
import { writeFileSync, existsSync, readdirSync, statSync, readFileSync, mkdirSync } from 'fs';
|
|
8
|
+
import { spawn } from 'child_process';
|
|
9
|
+
import React2, { useMemo, useState, useEffect, useRef } from 'react';
|
|
6
10
|
import { Box, Text, render } from 'ink';
|
|
7
|
-
import { useChat, StatusHeader, ChatContainer, Message, ToolCallView, ThinkingIndicator, InputBar } from '@agentskit/ink';
|
|
8
|
-
import { shell, filesystem, webSearch } from '@agentskit/tools';
|
|
11
|
+
import { useChat, StatusHeader, ChatContainer, Message, ToolCallView, ToolConfirmation, ThinkingIndicator, InputBar } from '@agentskit/ink';
|
|
12
|
+
import { shell, filesystem, fetchUrl, webSearch } from '@agentskit/tools';
|
|
9
13
|
import { summarizer, critic, planner, coder, researcher, composeSkills } from '@agentskit/skills';
|
|
10
|
-
import { fileChatMemory, sqliteChatMemory } from '@agentskit/memory';
|
|
14
|
+
import { fileVectorMemory, fileChatMemory, sqliteChatMemory } from '@agentskit/memory';
|
|
11
15
|
import { jsxs, jsx } from 'react/jsx-runtime';
|
|
16
|
+
import { pathToFileURL } from 'url';
|
|
12
17
|
import { createRuntime } from '@agentskit/runtime';
|
|
13
|
-
import { existsSync } from 'fs';
|
|
14
|
-
import { spawn } from 'child_process';
|
|
15
18
|
import chokidar from 'chokidar';
|
|
16
19
|
import kleur3 from 'kleur';
|
|
20
|
+
import { createRAG } from '@agentskit/rag';
|
|
17
21
|
import { Command } from 'commander';
|
|
18
22
|
import { input, select, checkbox, confirm } from '@inquirer/prompts';
|
|
19
23
|
|
|
20
|
-
async function loadJsonConfig(
|
|
24
|
+
async function loadJsonConfig(path5) {
|
|
21
25
|
try {
|
|
22
|
-
const raw = await readFile(
|
|
26
|
+
const raw = await readFile(path5, "utf8");
|
|
23
27
|
return JSON.parse(raw);
|
|
24
28
|
} catch {
|
|
25
29
|
return void 0;
|
|
26
30
|
}
|
|
27
31
|
}
|
|
28
|
-
async function loadTsConfig(
|
|
32
|
+
async function loadTsConfig(path5) {
|
|
29
33
|
try {
|
|
30
|
-
const mod = await import(
|
|
34
|
+
const mod = await import(path5);
|
|
31
35
|
return mod.default ?? mod;
|
|
32
36
|
} catch {
|
|
33
37
|
return void 0;
|
|
@@ -45,16 +49,39 @@ async function loadPackageJsonConfig(dir) {
|
|
|
45
49
|
return void 0;
|
|
46
50
|
}
|
|
47
51
|
}
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
+
function mergeConfigs(base, override) {
|
|
53
|
+
if (!base && !override) return void 0;
|
|
54
|
+
if (!base) return override;
|
|
55
|
+
if (!override) return base;
|
|
56
|
+
return {
|
|
57
|
+
...base,
|
|
58
|
+
...override,
|
|
59
|
+
tools: { ...base.tools, ...override.tools },
|
|
60
|
+
defaults: { ...base.defaults, ...override.defaults },
|
|
61
|
+
runtime: { ...base.runtime, ...override.runtime },
|
|
62
|
+
observability: { ...base.observability, ...override.observability }
|
|
63
|
+
};
|
|
64
|
+
}
|
|
65
|
+
async function loadLocalConfig(cwd) {
|
|
66
|
+
const tsConfig = await loadTsConfig(join(cwd, ".agentskit.config.ts"));
|
|
52
67
|
if (tsConfig) return tsConfig;
|
|
53
|
-
const
|
|
54
|
-
const jsonConfig = await loadJsonConfig(jsonPath);
|
|
68
|
+
const jsonConfig = await loadJsonConfig(join(cwd, ".agentskit.config.json"));
|
|
55
69
|
if (jsonConfig) return jsonConfig;
|
|
56
70
|
return await loadPackageJsonConfig(cwd);
|
|
57
71
|
}
|
|
72
|
+
async function loadGlobalConfig(home) {
|
|
73
|
+
if (home === null) return void 0;
|
|
74
|
+
const globalDir = join(home ?? homedir(), ".agentskit");
|
|
75
|
+
const tsConfig = await loadTsConfig(join(globalDir, "config.ts"));
|
|
76
|
+
if (tsConfig) return tsConfig;
|
|
77
|
+
return await loadJsonConfig(join(globalDir, "config.json"));
|
|
78
|
+
}
|
|
79
|
+
async function loadConfig(options) {
|
|
80
|
+
const cwd = resolve(options?.cwd ?? process.cwd());
|
|
81
|
+
const global = await loadGlobalConfig(options?.home);
|
|
82
|
+
const local = await loadLocalConfig(cwd);
|
|
83
|
+
return mergeConfigs(global, local);
|
|
84
|
+
}
|
|
58
85
|
var providers = {
|
|
59
86
|
openai: {
|
|
60
87
|
label: "OpenAI",
|
|
@@ -114,7 +141,7 @@ function createDemoAdapter(provider, model) {
|
|
|
114
141
|
].join(" ");
|
|
115
142
|
for (const chunk of reply.match(/.{1,18}/g) ?? []) {
|
|
116
143
|
if (cancelled) return;
|
|
117
|
-
await new Promise((
|
|
144
|
+
await new Promise((resolve4) => setTimeout(resolve4, 35));
|
|
118
145
|
yield { type: "text", content: chunk };
|
|
119
146
|
}
|
|
120
147
|
yield { type: "done" };
|
|
@@ -165,6 +192,533 @@ function resolveChatProvider(options) {
|
|
|
165
192
|
summary: `${entry.label} live adapter`
|
|
166
193
|
};
|
|
167
194
|
}
|
|
195
|
+
var ROOT = join(homedir(), ".agentskit", "sessions");
|
|
196
|
+
var META_SUFFIX = ".meta.json";
|
|
197
|
+
function cwdHash(cwd = process.cwd()) {
|
|
198
|
+
return createHash("sha256").update(cwd).digest("hex").slice(0, 12);
|
|
199
|
+
}
|
|
200
|
+
function dirFor(cwd = process.cwd()) {
|
|
201
|
+
return join(ROOT, cwdHash(cwd));
|
|
202
|
+
}
|
|
203
|
+
function ensureDir(dir) {
|
|
204
|
+
if (!existsSync(dir)) mkdirSync(dir, { recursive: true });
|
|
205
|
+
}
|
|
206
|
+
function generateSessionId() {
|
|
207
|
+
const ts = (/* @__PURE__ */ new Date()).toISOString().replace(/[:.]/g, "-").slice(0, 19);
|
|
208
|
+
const suffix = randomBytes(3).toString("hex");
|
|
209
|
+
return `${ts}-${suffix}`;
|
|
210
|
+
}
|
|
211
|
+
function sessionFilePath(id, cwd = process.cwd()) {
|
|
212
|
+
ensureDir(dirFor(cwd));
|
|
213
|
+
return join(dirFor(cwd), `${id}.json`);
|
|
214
|
+
}
|
|
215
|
+
function metaPath(id, cwd = process.cwd()) {
|
|
216
|
+
return join(dirFor(cwd), `${id}${META_SUFFIX}`);
|
|
217
|
+
}
|
|
218
|
+
function readMeta(id, cwd = process.cwd()) {
|
|
219
|
+
const path5 = metaPath(id, cwd);
|
|
220
|
+
if (!existsSync(path5)) return null;
|
|
221
|
+
try {
|
|
222
|
+
return JSON.parse(readFileSync(path5, "utf8"));
|
|
223
|
+
} catch {
|
|
224
|
+
return null;
|
|
225
|
+
}
|
|
226
|
+
}
|
|
227
|
+
function writeSessionMeta(meta, cwd = process.cwd()) {
|
|
228
|
+
ensureDir(dirFor(cwd));
|
|
229
|
+
writeFileSync(metaPath(meta.id, cwd), JSON.stringify(meta, null, 2));
|
|
230
|
+
}
|
|
231
|
+
function derivePreview(messages) {
|
|
232
|
+
const firstUser = messages.find((m) => m.role === "user" && m.content.trim());
|
|
233
|
+
if (!firstUser) return "(empty)";
|
|
234
|
+
const single = firstUser.content.replace(/\s+/g, " ").trim();
|
|
235
|
+
return single.length > 80 ? `${single.slice(0, 80)}\u2026` : single;
|
|
236
|
+
}
|
|
237
|
+
function listSessions(cwd = process.cwd()) {
|
|
238
|
+
const dir = dirFor(cwd);
|
|
239
|
+
if (!existsSync(dir)) return [];
|
|
240
|
+
const entries = readdirSync(dir);
|
|
241
|
+
const records = [];
|
|
242
|
+
for (const entry of entries) {
|
|
243
|
+
if (!entry.endsWith(".json") || entry.endsWith(META_SUFFIX)) continue;
|
|
244
|
+
const id = entry.replace(/\.json$/, "");
|
|
245
|
+
const meta = readMeta(id, cwd);
|
|
246
|
+
const file = join(dir, entry);
|
|
247
|
+
if (meta) {
|
|
248
|
+
records.push({ metadata: meta, file });
|
|
249
|
+
} else {
|
|
250
|
+
const stats = statSync(file);
|
|
251
|
+
records.push({
|
|
252
|
+
metadata: {
|
|
253
|
+
id,
|
|
254
|
+
cwd,
|
|
255
|
+
createdAt: stats.birthtime.toISOString(),
|
|
256
|
+
updatedAt: stats.mtime.toISOString(),
|
|
257
|
+
messageCount: 0,
|
|
258
|
+
preview: "(legacy session)"
|
|
259
|
+
},
|
|
260
|
+
file
|
|
261
|
+
});
|
|
262
|
+
}
|
|
263
|
+
}
|
|
264
|
+
records.sort((a, b) => b.metadata.updatedAt.localeCompare(a.metadata.updatedAt));
|
|
265
|
+
return records;
|
|
266
|
+
}
|
|
267
|
+
function findLatestSession(cwd = process.cwd()) {
|
|
268
|
+
const all = listSessions(cwd);
|
|
269
|
+
return all[0] ?? null;
|
|
270
|
+
}
|
|
271
|
+
function findSession(id, cwd = process.cwd()) {
|
|
272
|
+
const all = listSessions(cwd);
|
|
273
|
+
const exact = all.find((s) => s.metadata.id === id || s.metadata.label === id);
|
|
274
|
+
if (exact) return exact;
|
|
275
|
+
const prefix = all.find((s) => s.metadata.id.startsWith(id));
|
|
276
|
+
return prefix ?? null;
|
|
277
|
+
}
|
|
278
|
+
function renameSession(id, label, cwd = process.cwd()) {
|
|
279
|
+
const record = findSession(id, cwd);
|
|
280
|
+
if (!record) throw new Error(`No session matching "${id}".`);
|
|
281
|
+
const next = { ...record.metadata, label, updatedAt: (/* @__PURE__ */ new Date()).toISOString() };
|
|
282
|
+
writeSessionMeta(next, cwd);
|
|
283
|
+
return next;
|
|
284
|
+
}
|
|
285
|
+
function forkSession(id, cwd = process.cwd()) {
|
|
286
|
+
const record = findSession(id, cwd);
|
|
287
|
+
if (!record) throw new Error(`No session matching "${id}".`);
|
|
288
|
+
const newId = generateSessionId();
|
|
289
|
+
const newFile = sessionFilePath(newId, cwd);
|
|
290
|
+
if (existsSync(record.file)) {
|
|
291
|
+
writeFileSync(newFile, readFileSync(record.file, "utf8"));
|
|
292
|
+
}
|
|
293
|
+
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
294
|
+
writeSessionMeta(
|
|
295
|
+
{
|
|
296
|
+
...record.metadata,
|
|
297
|
+
id: newId,
|
|
298
|
+
createdAt: now,
|
|
299
|
+
updatedAt: now,
|
|
300
|
+
forkedFrom: record.metadata.id,
|
|
301
|
+
label: void 0
|
|
302
|
+
},
|
|
303
|
+
cwd
|
|
304
|
+
);
|
|
305
|
+
return { id: newId, file: newFile, isNew: true };
|
|
306
|
+
}
|
|
307
|
+
function resolveSession(input2) {
|
|
308
|
+
const cwd = input2.cwd ?? process.cwd();
|
|
309
|
+
if (input2.explicitPath) {
|
|
310
|
+
return { id: "custom", file: input2.explicitPath, isNew: !existsSync(input2.explicitPath) };
|
|
311
|
+
}
|
|
312
|
+
if (input2.forceNew) {
|
|
313
|
+
const id2 = generateSessionId();
|
|
314
|
+
return { id: id2, file: sessionFilePath(id2, cwd), isNew: true };
|
|
315
|
+
}
|
|
316
|
+
if (input2.resumeId) {
|
|
317
|
+
const target = input2.resumeId === true ? findLatestSession(cwd) : findSession(input2.resumeId, cwd);
|
|
318
|
+
if (target) {
|
|
319
|
+
return { id: target.metadata.id, file: target.file, isNew: false };
|
|
320
|
+
}
|
|
321
|
+
process.stderr.write(
|
|
322
|
+
`No session matching "${String(input2.resumeId)}" \u2014 starting a new one.
|
|
323
|
+
`
|
|
324
|
+
);
|
|
325
|
+
}
|
|
326
|
+
const latest = findLatestSession(cwd);
|
|
327
|
+
if (latest) return { id: latest.metadata.id, file: latest.file, isNew: false };
|
|
328
|
+
const id = generateSessionId();
|
|
329
|
+
return { id, file: sessionFilePath(id, cwd), isNew: true };
|
|
330
|
+
}
|
|
331
|
+
|
|
332
|
+
// src/extensibility/telemetry/pricing.ts
|
|
333
|
+
var builtinPricing = {
|
|
334
|
+
"gpt-4o": { inputPerM: 2.5, outputPerM: 10 },
|
|
335
|
+
"gpt-4o-mini": { inputPerM: 0.15, outputPerM: 0.6 },
|
|
336
|
+
"gpt-4.1": { inputPerM: 2, outputPerM: 8 },
|
|
337
|
+
"gpt-4.1-mini": { inputPerM: 0.4, outputPerM: 1.6 },
|
|
338
|
+
"claude-opus-4": { inputPerM: 15, outputPerM: 75 },
|
|
339
|
+
"claude-sonnet-4": { inputPerM: 3, outputPerM: 15 },
|
|
340
|
+
"claude-haiku-4": { inputPerM: 0.8, outputPerM: 4 },
|
|
341
|
+
"gemini-2.5-pro": { inputPerM: 1.25, outputPerM: 10 },
|
|
342
|
+
"gemini-2.5-flash": { inputPerM: 0.3, outputPerM: 2.5 }
|
|
343
|
+
};
|
|
344
|
+
var customPricing = {};
|
|
345
|
+
function registerPricing(model, pricing) {
|
|
346
|
+
customPricing[model] = pricing;
|
|
347
|
+
}
|
|
348
|
+
function getPricing(model) {
|
|
349
|
+
if (!model) return void 0;
|
|
350
|
+
if (customPricing[model]) return customPricing[model];
|
|
351
|
+
if (builtinPricing[model]) return builtinPricing[model];
|
|
352
|
+
const short = model.includes("/") ? model.split("/").pop() : model;
|
|
353
|
+
return customPricing[short] ?? builtinPricing[short];
|
|
354
|
+
}
|
|
355
|
+
function computeCost(model, usage) {
|
|
356
|
+
if (!model) return void 0;
|
|
357
|
+
const pricing = getPricing(model);
|
|
358
|
+
if (!pricing) return void 0;
|
|
359
|
+
const inputUsd = usage.promptTokens / 1e6 * pricing.inputPerM;
|
|
360
|
+
const outputUsd = usage.completionTokens / 1e6 * pricing.outputPerM;
|
|
361
|
+
return {
|
|
362
|
+
model,
|
|
363
|
+
inputUsd,
|
|
364
|
+
outputUsd,
|
|
365
|
+
totalUsd: inputUsd + outputUsd
|
|
366
|
+
};
|
|
367
|
+
}
|
|
368
|
+
|
|
369
|
+
// src/extensibility/permissions/policy.ts
|
|
370
|
+
var defaultPolicy = {
|
|
371
|
+
mode: "default",
|
|
372
|
+
rules: []
|
|
373
|
+
};
|
|
374
|
+
function evaluatePolicy(policy, toolName) {
|
|
375
|
+
if (policy.mode === "bypassPermissions") return "allow";
|
|
376
|
+
if (policy.mode === "plan") return "ask";
|
|
377
|
+
for (const rule of policy.rules) {
|
|
378
|
+
if (matchesRule(rule, toolName)) return rule.action;
|
|
379
|
+
}
|
|
380
|
+
if (policy.mode === "acceptEdits" && /^(fs_write|edit|write_file)/.test(toolName)) {
|
|
381
|
+
return "allow";
|
|
382
|
+
}
|
|
383
|
+
return "ask";
|
|
384
|
+
}
|
|
385
|
+
function matchesRule(rule, toolName) {
|
|
386
|
+
if (rule.tool instanceof RegExp) return rule.tool.test(toolName);
|
|
387
|
+
const str = rule.tool;
|
|
388
|
+
if (str.startsWith("re:")) return new RegExp(str.slice(3)).test(toolName);
|
|
389
|
+
return str === toolName;
|
|
390
|
+
}
|
|
391
|
+
function applyPolicyToTool(policy, tool) {
|
|
392
|
+
const action = evaluatePolicy(policy, tool.name);
|
|
393
|
+
if (action === "deny") return null;
|
|
394
|
+
if (action === "allow") return { ...tool, requiresConfirmation: false };
|
|
395
|
+
return { ...tool, requiresConfirmation: true };
|
|
396
|
+
}
|
|
397
|
+
function applyPolicyToTools(policy, tools) {
|
|
398
|
+
const out = [];
|
|
399
|
+
for (const tool of tools) {
|
|
400
|
+
const gated = applyPolicyToTool(policy, tool);
|
|
401
|
+
if (gated) out.push(gated);
|
|
402
|
+
}
|
|
403
|
+
return out;
|
|
404
|
+
}
|
|
405
|
+
|
|
406
|
+
// src/extensibility/hooks/runner.ts
|
|
407
|
+
var HookDispatcher = class {
|
|
408
|
+
constructor(handlers = [], onError = (_h, err) => process.stderr.write(
|
|
409
|
+
`[agentskit] hook error: ${err instanceof Error ? err.message : String(err)}
|
|
410
|
+
`
|
|
411
|
+
)) {
|
|
412
|
+
this.onError = onError;
|
|
413
|
+
this.handlers = /* @__PURE__ */ new Map();
|
|
414
|
+
for (const handler of handlers) this.register(handler);
|
|
415
|
+
}
|
|
416
|
+
register(handler) {
|
|
417
|
+
const list = this.handlers.get(handler.event) ?? [];
|
|
418
|
+
list.push(handler);
|
|
419
|
+
this.handlers.set(handler.event, list);
|
|
420
|
+
}
|
|
421
|
+
async dispatch(event, payload) {
|
|
422
|
+
const list = this.handlers.get(event) ?? [];
|
|
423
|
+
let current = { ...payload, event };
|
|
424
|
+
for (const handler of list) {
|
|
425
|
+
if (!this.matches(handler, current)) continue;
|
|
426
|
+
let result;
|
|
427
|
+
try {
|
|
428
|
+
result = await handler.run(current);
|
|
429
|
+
} catch (err) {
|
|
430
|
+
this.onError(handler, err);
|
|
431
|
+
continue;
|
|
432
|
+
}
|
|
433
|
+
if (result.decision === "block") {
|
|
434
|
+
return { payload: current, blocked: true, reason: result.reason };
|
|
435
|
+
}
|
|
436
|
+
if (result.decision === "modify") {
|
|
437
|
+
current = { ...result.payload, event };
|
|
438
|
+
}
|
|
439
|
+
}
|
|
440
|
+
return { payload: current, blocked: false };
|
|
441
|
+
}
|
|
442
|
+
matches(handler, payload) {
|
|
443
|
+
if (!handler.matcher) return true;
|
|
444
|
+
if (typeof handler.matcher === "function") return handler.matcher(payload);
|
|
445
|
+
return handler.matcher.test(String(payload.tool ?? payload.prompt ?? ""));
|
|
446
|
+
}
|
|
447
|
+
};
|
|
448
|
+
function configHooksToHandlers(config) {
|
|
449
|
+
if (!config) return [];
|
|
450
|
+
const handlers = [];
|
|
451
|
+
for (const [event, entries] of Object.entries(config)) {
|
|
452
|
+
for (const entry of entries) {
|
|
453
|
+
handlers.push({
|
|
454
|
+
event,
|
|
455
|
+
matcher: entry.matcher ? new RegExp(entry.matcher) : void 0,
|
|
456
|
+
run: (payload) => runShellHook(entry, payload)
|
|
457
|
+
});
|
|
458
|
+
}
|
|
459
|
+
}
|
|
460
|
+
return handlers;
|
|
461
|
+
}
|
|
462
|
+
function runShellHook(entry, payload) {
|
|
463
|
+
return new Promise((resolvePromise) => {
|
|
464
|
+
const timeoutMs = entry.timeout ?? 5e3;
|
|
465
|
+
const child = spawn("sh", ["-c", entry.run], {
|
|
466
|
+
stdio: ["pipe", "pipe", "inherit"]
|
|
467
|
+
});
|
|
468
|
+
let stdout = "";
|
|
469
|
+
child.stdout.on("data", (chunk) => {
|
|
470
|
+
stdout += chunk.toString();
|
|
471
|
+
});
|
|
472
|
+
const timer = setTimeout(() => {
|
|
473
|
+
child.kill("SIGTERM");
|
|
474
|
+
}, timeoutMs);
|
|
475
|
+
child.on("close", (code) => {
|
|
476
|
+
clearTimeout(timer);
|
|
477
|
+
if (code !== 0) {
|
|
478
|
+
resolvePromise({
|
|
479
|
+
decision: "block",
|
|
480
|
+
reason: `shell hook exited with code ${code}`
|
|
481
|
+
});
|
|
482
|
+
return;
|
|
483
|
+
}
|
|
484
|
+
const trimmed = stdout.trim();
|
|
485
|
+
if (!trimmed) {
|
|
486
|
+
resolvePromise({ decision: "continue" });
|
|
487
|
+
return;
|
|
488
|
+
}
|
|
489
|
+
try {
|
|
490
|
+
const parsed = JSON.parse(trimmed);
|
|
491
|
+
resolvePromise(parsed);
|
|
492
|
+
} catch {
|
|
493
|
+
resolvePromise({ decision: "continue" });
|
|
494
|
+
}
|
|
495
|
+
});
|
|
496
|
+
child.on("error", (err) => {
|
|
497
|
+
clearTimeout(timer);
|
|
498
|
+
resolvePromise({ decision: "block", reason: err.message });
|
|
499
|
+
});
|
|
500
|
+
try {
|
|
501
|
+
child.stdin.write(JSON.stringify(payload));
|
|
502
|
+
child.stdin.end();
|
|
503
|
+
} catch {
|
|
504
|
+
}
|
|
505
|
+
});
|
|
506
|
+
}
|
|
507
|
+
|
|
508
|
+
// src/slash-commands.ts
|
|
509
|
+
function parseSlashCommand(input2) {
|
|
510
|
+
if (!input2.startsWith("/")) return null;
|
|
511
|
+
const match = input2.slice(1).match(/^(\S+)\s*([\s\S]*)$/);
|
|
512
|
+
if (!match) return null;
|
|
513
|
+
return { name: match[1], args: match[2] ?? "" };
|
|
514
|
+
}
|
|
515
|
+
function createSlashRegistry(commands) {
|
|
516
|
+
const map = /* @__PURE__ */ new Map();
|
|
517
|
+
for (const cmd of commands) {
|
|
518
|
+
map.set(cmd.name, cmd);
|
|
519
|
+
for (const alias of cmd.aliases ?? []) map.set(alias, cmd);
|
|
520
|
+
}
|
|
521
|
+
return map;
|
|
522
|
+
}
|
|
523
|
+
var builtinSlashCommands = [
|
|
524
|
+
{
|
|
525
|
+
name: "help",
|
|
526
|
+
aliases: ["?"],
|
|
527
|
+
description: "List available slash commands.",
|
|
528
|
+
run(ctx) {
|
|
529
|
+
const seen = /* @__PURE__ */ new Set();
|
|
530
|
+
const lines = [];
|
|
531
|
+
for (const cmd of ctx.commands) {
|
|
532
|
+
if (seen.has(cmd.name)) continue;
|
|
533
|
+
seen.add(cmd.name);
|
|
534
|
+
const suffix = cmd.usage ? ` (${cmd.usage})` : "";
|
|
535
|
+
lines.push(` /${cmd.name.padEnd(10)} ${cmd.description}${suffix}`);
|
|
536
|
+
}
|
|
537
|
+
ctx.feedback(`Slash commands:
|
|
538
|
+
${lines.join("\n")}`, "info");
|
|
539
|
+
}
|
|
540
|
+
},
|
|
541
|
+
{
|
|
542
|
+
name: "model",
|
|
543
|
+
description: "Switch the active model.",
|
|
544
|
+
usage: "/model <name>",
|
|
545
|
+
run(ctx, args) {
|
|
546
|
+
const value = args.trim();
|
|
547
|
+
if (!value) {
|
|
548
|
+
ctx.feedback(
|
|
549
|
+
`Current model: ${ctx.runtime.model ?? "unset"}. Usage: /model <name>`,
|
|
550
|
+
"warn"
|
|
551
|
+
);
|
|
552
|
+
return;
|
|
553
|
+
}
|
|
554
|
+
ctx.setModel(value);
|
|
555
|
+
ctx.feedback(`Model \u2192 ${value}`, "success");
|
|
556
|
+
}
|
|
557
|
+
},
|
|
558
|
+
{
|
|
559
|
+
name: "provider",
|
|
560
|
+
description: "Switch the adapter provider.",
|
|
561
|
+
usage: "/provider openai|anthropic|gemini|ollama|deepseek|grok|kimi|demo",
|
|
562
|
+
run(ctx, args) {
|
|
563
|
+
const value = args.trim();
|
|
564
|
+
if (!value) {
|
|
565
|
+
ctx.feedback(
|
|
566
|
+
`Current provider: ${ctx.runtime.provider}. Usage: /provider <name>`,
|
|
567
|
+
"warn"
|
|
568
|
+
);
|
|
569
|
+
return;
|
|
570
|
+
}
|
|
571
|
+
ctx.setProvider(value);
|
|
572
|
+
ctx.feedback(`Provider \u2192 ${value}`, "success");
|
|
573
|
+
}
|
|
574
|
+
},
|
|
575
|
+
{
|
|
576
|
+
name: "base-url",
|
|
577
|
+
aliases: ["baseurl"],
|
|
578
|
+
description: "Override provider base URL.",
|
|
579
|
+
usage: "/base-url <url|clear>",
|
|
580
|
+
run(ctx, args) {
|
|
581
|
+
const value = args.trim();
|
|
582
|
+
if (!value || value === "clear") {
|
|
583
|
+
ctx.setBaseUrl(void 0);
|
|
584
|
+
ctx.feedback("Base URL cleared.", "success");
|
|
585
|
+
return;
|
|
586
|
+
}
|
|
587
|
+
ctx.setBaseUrl(value);
|
|
588
|
+
ctx.feedback(`Base URL \u2192 ${value}`, "success");
|
|
589
|
+
}
|
|
590
|
+
},
|
|
591
|
+
{
|
|
592
|
+
name: "tools",
|
|
593
|
+
description: "Set active tools (comma-separated) or clear them.",
|
|
594
|
+
usage: "/tools web_search,fetch_url | /tools clear",
|
|
595
|
+
run(ctx, args) {
|
|
596
|
+
const value = args.trim();
|
|
597
|
+
if (!value || value === "clear") {
|
|
598
|
+
ctx.setTools(void 0);
|
|
599
|
+
ctx.feedback("Tools reset to defaults.", "success");
|
|
600
|
+
return;
|
|
601
|
+
}
|
|
602
|
+
ctx.setTools(value);
|
|
603
|
+
ctx.feedback(`Tools \u2192 ${value}`, "success");
|
|
604
|
+
}
|
|
605
|
+
},
|
|
606
|
+
{
|
|
607
|
+
name: "skill",
|
|
608
|
+
description: "Set active skill(s) (comma-separated) or clear them.",
|
|
609
|
+
usage: "/skill researcher,coder | /skill clear",
|
|
610
|
+
run(ctx, args) {
|
|
611
|
+
const value = args.trim();
|
|
612
|
+
if (!value || value === "clear") {
|
|
613
|
+
ctx.setSkill(void 0);
|
|
614
|
+
ctx.feedback("Skills cleared.", "success");
|
|
615
|
+
return;
|
|
616
|
+
}
|
|
617
|
+
ctx.setSkill(value);
|
|
618
|
+
ctx.feedback(`Skills \u2192 ${value}`, "success");
|
|
619
|
+
}
|
|
620
|
+
},
|
|
621
|
+
{
|
|
622
|
+
name: "clear",
|
|
623
|
+
aliases: ["reset"],
|
|
624
|
+
description: "Clear the conversation history in this session.",
|
|
625
|
+
async run(ctx) {
|
|
626
|
+
await ctx.chat.clear();
|
|
627
|
+
ctx.feedback("History cleared.", "success");
|
|
628
|
+
}
|
|
629
|
+
},
|
|
630
|
+
{
|
|
631
|
+
name: "usage",
|
|
632
|
+
description: "Show the cumulative token usage for this session.",
|
|
633
|
+
run(ctx) {
|
|
634
|
+
const usage = ctx.chat.usage;
|
|
635
|
+
if (!usage || usage.totalTokens === 0) {
|
|
636
|
+
ctx.feedback("No usage reported yet for this session.", "info");
|
|
637
|
+
return;
|
|
638
|
+
}
|
|
639
|
+
ctx.feedback(
|
|
640
|
+
`Tokens \u2014 prompt=${usage.promptTokens} completion=${usage.completionTokens} total=${usage.totalTokens}`,
|
|
641
|
+
"info"
|
|
642
|
+
);
|
|
643
|
+
}
|
|
644
|
+
},
|
|
645
|
+
{
|
|
646
|
+
name: "cost",
|
|
647
|
+
description: "Estimate the cost so far for the current model.",
|
|
648
|
+
run(ctx) {
|
|
649
|
+
const usage = ctx.chat.usage;
|
|
650
|
+
const model = ctx.runtime.model;
|
|
651
|
+
if (!usage || usage.totalTokens === 0) {
|
|
652
|
+
ctx.feedback("No usage reported yet for this session.", "info");
|
|
653
|
+
return;
|
|
654
|
+
}
|
|
655
|
+
const cost = computeCost(model, usage);
|
|
656
|
+
if (!cost) {
|
|
657
|
+
ctx.feedback(
|
|
658
|
+
`No pricing registered for model "${model ?? "unset"}". Register with registerPricing() or provide a known model name.`,
|
|
659
|
+
"warn"
|
|
660
|
+
);
|
|
661
|
+
return;
|
|
662
|
+
}
|
|
663
|
+
ctx.feedback(
|
|
664
|
+
`$${cost.totalUsd.toFixed(4)} total (in=$${cost.inputUsd.toFixed(4)} out=$${cost.outputUsd.toFixed(4)} model=${cost.model})`,
|
|
665
|
+
"info"
|
|
666
|
+
);
|
|
667
|
+
}
|
|
668
|
+
},
|
|
669
|
+
{
|
|
670
|
+
name: "rename",
|
|
671
|
+
description: "Attach a human-readable label to the current session.",
|
|
672
|
+
usage: "/rename <label>",
|
|
673
|
+
run(ctx, args) {
|
|
674
|
+
const label = args.trim();
|
|
675
|
+
const sessionId = ctx.runtime.sessionId;
|
|
676
|
+
if (!sessionId || sessionId === "custom") {
|
|
677
|
+
ctx.feedback("Rename is only available for managed sessions.", "warn");
|
|
678
|
+
return;
|
|
679
|
+
}
|
|
680
|
+
if (!label) {
|
|
681
|
+
ctx.feedback("Usage: /rename <label>", "warn");
|
|
682
|
+
return;
|
|
683
|
+
}
|
|
684
|
+
try {
|
|
685
|
+
renameSession(sessionId, label);
|
|
686
|
+
ctx.feedback(`Session labeled "${label}".`, "success");
|
|
687
|
+
} catch (err) {
|
|
688
|
+
ctx.feedback(`/rename failed: ${err instanceof Error ? err.message : String(err)}`, "error");
|
|
689
|
+
}
|
|
690
|
+
}
|
|
691
|
+
},
|
|
692
|
+
{
|
|
693
|
+
name: "fork",
|
|
694
|
+
description: "Branch a copy of the current session. Does not switch to it.",
|
|
695
|
+
run(ctx) {
|
|
696
|
+
const sessionId = ctx.runtime.sessionId;
|
|
697
|
+
if (!sessionId || sessionId === "custom") {
|
|
698
|
+
ctx.feedback("Fork is only available for managed sessions.", "warn");
|
|
699
|
+
return;
|
|
700
|
+
}
|
|
701
|
+
try {
|
|
702
|
+
const result = forkSession(sessionId);
|
|
703
|
+
ctx.feedback(
|
|
704
|
+
`Forked into ${result.id}. Resume with:
|
|
705
|
+
agentskit chat --resume ${result.id}`,
|
|
706
|
+
"success"
|
|
707
|
+
);
|
|
708
|
+
} catch (err) {
|
|
709
|
+
ctx.feedback(`/fork failed: ${err instanceof Error ? err.message : String(err)}`, "error");
|
|
710
|
+
}
|
|
711
|
+
}
|
|
712
|
+
},
|
|
713
|
+
{
|
|
714
|
+
name: "exit",
|
|
715
|
+
aliases: ["quit", "q"],
|
|
716
|
+
description: "Exit the chat.",
|
|
717
|
+
run() {
|
|
718
|
+
process.exit(0);
|
|
719
|
+
}
|
|
720
|
+
}
|
|
721
|
+
];
|
|
168
722
|
var skillRegistry = {
|
|
169
723
|
researcher,
|
|
170
724
|
coder,
|
|
@@ -172,19 +726,34 @@ var skillRegistry = {
|
|
|
172
726
|
critic,
|
|
173
727
|
summarizer
|
|
174
728
|
};
|
|
729
|
+
function instantiate(kind) {
|
|
730
|
+
switch (kind) {
|
|
731
|
+
case "web_search":
|
|
732
|
+
return [webSearch()];
|
|
733
|
+
case "fetch_url":
|
|
734
|
+
return [fetchUrl()];
|
|
735
|
+
case "filesystem":
|
|
736
|
+
return filesystem({ basePath: process.cwd() });
|
|
737
|
+
case "shell":
|
|
738
|
+
return [shell({ timeout: 3e4 })];
|
|
739
|
+
}
|
|
740
|
+
}
|
|
741
|
+
function gateTool(tool) {
|
|
742
|
+
if (tool.requiresConfirmation === false) return tool;
|
|
743
|
+
return { ...tool, requiresConfirmation: true };
|
|
744
|
+
}
|
|
175
745
|
function resolveTools(toolNames) {
|
|
176
|
-
if (!toolNames)
|
|
746
|
+
if (!toolNames) {
|
|
747
|
+
return [...instantiate("web_search"), ...instantiate("fetch_url")].map(gateTool);
|
|
748
|
+
}
|
|
177
749
|
const tools = [];
|
|
178
|
-
for (const name of toolNames.split(",").map((s) => s.trim())) {
|
|
750
|
+
for (const name of toolNames.split(",").map((s) => s.trim()).filter(Boolean)) {
|
|
179
751
|
switch (name) {
|
|
180
752
|
case "web_search":
|
|
181
|
-
|
|
182
|
-
break;
|
|
753
|
+
case "fetch_url":
|
|
183
754
|
case "filesystem":
|
|
184
|
-
tools.push(...filesystem({ basePath: process.cwd() }));
|
|
185
|
-
break;
|
|
186
755
|
case "shell":
|
|
187
|
-
tools.push(
|
|
756
|
+
tools.push(...instantiate(name));
|
|
188
757
|
break;
|
|
189
758
|
default:
|
|
190
759
|
process.stderr.write(`Unknown tool: ${name}
|
|
@@ -224,6 +793,108 @@ function resolveMemory(backend, memoryPath) {
|
|
|
224
793
|
return fileChatMemory(memoryPath);
|
|
225
794
|
}
|
|
226
795
|
}
|
|
796
|
+
|
|
797
|
+
// src/runtime/use-runtime.ts
|
|
798
|
+
function useRuntime(options) {
|
|
799
|
+
const [provider, setProvider] = useState(options.provider);
|
|
800
|
+
const [model, setModel] = useState(options.model);
|
|
801
|
+
const [apiKey, setApiKey] = useState(options.apiKey);
|
|
802
|
+
const [baseUrl, setBaseUrl] = useState(options.baseUrl);
|
|
803
|
+
const [toolsFlag, setToolsFlag] = useState(options.tools);
|
|
804
|
+
const [skillFlag, setSkillFlag] = useState(options.skill);
|
|
805
|
+
const runtime = useMemo(
|
|
806
|
+
() => resolveChatProvider({ provider, model, apiKey, baseUrl }),
|
|
807
|
+
[provider, model, apiKey, baseUrl]
|
|
808
|
+
);
|
|
809
|
+
const memory = useMemo(
|
|
810
|
+
() => resolveMemory(options.memoryBackend, options.memoryPath ?? ".agentskit-history.json"),
|
|
811
|
+
[options.memoryPath, options.memoryBackend]
|
|
812
|
+
);
|
|
813
|
+
const tools = useMemo(() => {
|
|
814
|
+
const resolved = resolveTools(toolsFlag);
|
|
815
|
+
if (!options.permissionPolicy) return resolved;
|
|
816
|
+
return applyPolicyToTools(options.permissionPolicy, resolved);
|
|
817
|
+
}, [toolsFlag, options.permissionPolicy]);
|
|
818
|
+
const skills = useMemo(() => {
|
|
819
|
+
if (!skillFlag) return void 0;
|
|
820
|
+
const names = skillFlag.split(",").map((s) => s.trim());
|
|
821
|
+
const resolved = names.map((n) => skillRegistry[n]).filter(Boolean);
|
|
822
|
+
if (resolved.length === 0) return void 0;
|
|
823
|
+
return resolved;
|
|
824
|
+
}, [skillFlag]);
|
|
825
|
+
return {
|
|
826
|
+
runtime,
|
|
827
|
+
memory,
|
|
828
|
+
tools,
|
|
829
|
+
skills,
|
|
830
|
+
state: { provider, model, apiKey, baseUrl, toolsFlag, skillFlag },
|
|
831
|
+
setProvider,
|
|
832
|
+
setModel,
|
|
833
|
+
setApiKey,
|
|
834
|
+
setBaseUrl,
|
|
835
|
+
setToolsFlag,
|
|
836
|
+
setSkillFlag
|
|
837
|
+
};
|
|
838
|
+
}
|
|
839
|
+
function useToolPermissions(chat) {
|
|
840
|
+
const [sessionAllowed, setSessionAllowed] = useState(/* @__PURE__ */ new Set());
|
|
841
|
+
const autoApprovedRef = useRef(/* @__PURE__ */ new Set());
|
|
842
|
+
useEffect(() => {
|
|
843
|
+
if (sessionAllowed.size === 0) return;
|
|
844
|
+
for (const message of chat.messages) {
|
|
845
|
+
for (const call of message.toolCalls ?? []) {
|
|
846
|
+
if (call.status === "requires_confirmation" && sessionAllowed.has(call.name) && !autoApprovedRef.current.has(call.id)) {
|
|
847
|
+
autoApprovedRef.current.add(call.id);
|
|
848
|
+
void chat.approve(call.id);
|
|
849
|
+
}
|
|
850
|
+
}
|
|
851
|
+
}
|
|
852
|
+
}, [chat.messages, sessionAllowed, chat.approve]);
|
|
853
|
+
const handleApproveAlways = (toolCallId, toolName) => {
|
|
854
|
+
setSessionAllowed((prev) => {
|
|
855
|
+
if (prev.has(toolName)) return prev;
|
|
856
|
+
const next = new Set(prev);
|
|
857
|
+
next.add(toolName);
|
|
858
|
+
return next;
|
|
859
|
+
});
|
|
860
|
+
autoApprovedRef.current.add(toolCallId);
|
|
861
|
+
void chat.approve(toolCallId);
|
|
862
|
+
};
|
|
863
|
+
const awaitingConfirmation = useMemo(
|
|
864
|
+
() => chat.messages.some(
|
|
865
|
+
(message) => message.toolCalls?.some(
|
|
866
|
+
(call) => call.status === "requires_confirmation" && !sessionAllowed.has(call.name)
|
|
867
|
+
)
|
|
868
|
+
),
|
|
869
|
+
[chat.messages, sessionAllowed]
|
|
870
|
+
);
|
|
871
|
+
return { sessionAllowed, handleApproveAlways, awaitingConfirmation };
|
|
872
|
+
}
|
|
873
|
+
function useSessionMeta(options) {
|
|
874
|
+
const sessionCreatedAtRef = useRef(void 0);
|
|
875
|
+
const messageCount = options.messages.length;
|
|
876
|
+
const firstUserContent = options.messages.find((m) => m.role === "user")?.content ?? "";
|
|
877
|
+
useEffect(() => {
|
|
878
|
+
const sessionId = options.sessionId;
|
|
879
|
+
if (!sessionId || sessionId === "custom") return;
|
|
880
|
+
if (!sessionCreatedAtRef.current) {
|
|
881
|
+
sessionCreatedAtRef.current = (/* @__PURE__ */ new Date()).toISOString();
|
|
882
|
+
}
|
|
883
|
+
try {
|
|
884
|
+
writeSessionMeta({
|
|
885
|
+
id: sessionId,
|
|
886
|
+
cwd: process.cwd(),
|
|
887
|
+
createdAt: sessionCreatedAtRef.current,
|
|
888
|
+
updatedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
889
|
+
messageCount,
|
|
890
|
+
preview: derivePreview(options.messages),
|
|
891
|
+
provider: options.provider,
|
|
892
|
+
model: options.model
|
|
893
|
+
});
|
|
894
|
+
} catch {
|
|
895
|
+
}
|
|
896
|
+
}, [options.sessionId, messageCount, firstUserContent, options.provider, options.model]);
|
|
897
|
+
}
|
|
227
898
|
function groupIntoTurns(messages) {
|
|
228
899
|
const turns = [];
|
|
229
900
|
let current = [];
|
|
@@ -243,33 +914,121 @@ function groupIntoTurns(messages) {
|
|
|
243
914
|
return turns;
|
|
244
915
|
}
|
|
245
916
|
function ChatApp(options) {
|
|
246
|
-
const
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
const
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
917
|
+
const {
|
|
918
|
+
runtime,
|
|
919
|
+
memory,
|
|
920
|
+
tools,
|
|
921
|
+
skills,
|
|
922
|
+
state: { baseUrl, toolsFlag, skillFlag },
|
|
923
|
+
setProvider,
|
|
924
|
+
setModel,
|
|
925
|
+
setApiKey,
|
|
926
|
+
setBaseUrl,
|
|
927
|
+
setToolsFlag,
|
|
928
|
+
setSkillFlag
|
|
929
|
+
} = useRuntime(options);
|
|
930
|
+
const mergedTools = useMemo(() => {
|
|
931
|
+
const extra = options.extraTools ?? [];
|
|
932
|
+
return [...tools, ...extra];
|
|
933
|
+
}, [tools, options.extraTools]);
|
|
934
|
+
const mergedSkills = useMemo(() => {
|
|
935
|
+
const extra = options.extraSkills ?? [];
|
|
936
|
+
if (!skills && extra.length === 0) return void 0;
|
|
937
|
+
return [...skills ?? [], ...extra];
|
|
938
|
+
}, [skills, options.extraSkills]);
|
|
264
939
|
const chat = useChat({
|
|
265
940
|
adapter: runtime.adapter,
|
|
266
941
|
memory,
|
|
267
942
|
systemPrompt: options.system,
|
|
268
|
-
tools:
|
|
269
|
-
skills
|
|
943
|
+
tools: mergedTools.length > 0 ? mergedTools : void 0,
|
|
944
|
+
skills: mergedSkills
|
|
945
|
+
});
|
|
946
|
+
const { sessionAllowed, handleApproveAlways, awaitingConfirmation } = useToolPermissions(chat);
|
|
947
|
+
useSessionMeta({
|
|
948
|
+
sessionId: options.sessionId,
|
|
949
|
+
messages: chat.messages,
|
|
950
|
+
provider: runtime.provider,
|
|
951
|
+
model: runtime.model
|
|
270
952
|
});
|
|
271
953
|
const turns = useMemo(() => groupIntoTurns(chat.messages), [chat.messages]);
|
|
272
|
-
const toolNames =
|
|
954
|
+
const toolNames = toolsFlag ? toolsFlag.split(",").map((s) => s.trim()).filter(Boolean) : [];
|
|
955
|
+
const [feedback, setFeedback] = useState(null);
|
|
956
|
+
const hookDispatcher = useMemo(
|
|
957
|
+
() => new HookDispatcher(options.hookHandlers ?? []),
|
|
958
|
+
[options.hookHandlers]
|
|
959
|
+
);
|
|
960
|
+
useEffect(() => {
|
|
961
|
+
void hookDispatcher.dispatch("SessionStart", {
|
|
962
|
+
event: "SessionStart",
|
|
963
|
+
sessionId: options.sessionId,
|
|
964
|
+
provider: runtime.provider,
|
|
965
|
+
model: runtime.model
|
|
966
|
+
});
|
|
967
|
+
return () => {
|
|
968
|
+
void hookDispatcher.dispatch("SessionEnd", {
|
|
969
|
+
event: "SessionEnd",
|
|
970
|
+
sessionId: options.sessionId
|
|
971
|
+
});
|
|
972
|
+
};
|
|
973
|
+
}, [hookDispatcher]);
|
|
974
|
+
const slashCommands = useMemo(
|
|
975
|
+
() => [...builtinSlashCommands, ...options.slashCommands ?? []],
|
|
976
|
+
[options.slashCommands]
|
|
977
|
+
);
|
|
978
|
+
const slashRegistry = useMemo(() => createSlashRegistry(slashCommands), [slashCommands]);
|
|
979
|
+
const handleSubmitInput = async (raw) => {
|
|
980
|
+
const parsed = parseSlashCommand(raw);
|
|
981
|
+
if (!parsed) {
|
|
982
|
+
const hookResult = await hookDispatcher.dispatch("UserPromptSubmit", {
|
|
983
|
+
event: "UserPromptSubmit",
|
|
984
|
+
prompt: raw
|
|
985
|
+
});
|
|
986
|
+
if (hookResult.blocked) {
|
|
987
|
+
setFeedback({
|
|
988
|
+
message: `Prompt blocked: ${hookResult.reason ?? "hook refused"}`,
|
|
989
|
+
kind: "warn"
|
|
990
|
+
});
|
|
991
|
+
return true;
|
|
992
|
+
}
|
|
993
|
+
setFeedback(null);
|
|
994
|
+
return false;
|
|
995
|
+
}
|
|
996
|
+
const cmd = slashRegistry.get(parsed.name);
|
|
997
|
+
if (!cmd) {
|
|
998
|
+
setFeedback({
|
|
999
|
+
message: `Unknown command: /${parsed.name}. Type /help for the list.`,
|
|
1000
|
+
kind: "error"
|
|
1001
|
+
});
|
|
1002
|
+
return true;
|
|
1003
|
+
}
|
|
1004
|
+
const ctx = {
|
|
1005
|
+
chat,
|
|
1006
|
+
runtime: {
|
|
1007
|
+
provider: runtime.provider,
|
|
1008
|
+
model: runtime.model,
|
|
1009
|
+
mode: runtime.mode,
|
|
1010
|
+
baseUrl,
|
|
1011
|
+
tools: toolsFlag,
|
|
1012
|
+
skill: skillFlag,
|
|
1013
|
+
sessionId: options.sessionId
|
|
1014
|
+
},
|
|
1015
|
+
setProvider,
|
|
1016
|
+
setModel,
|
|
1017
|
+
setApiKey,
|
|
1018
|
+
setBaseUrl,
|
|
1019
|
+
setTools: setToolsFlag,
|
|
1020
|
+
setSkill: setSkillFlag,
|
|
1021
|
+
feedback: (message, kind = "info") => setFeedback({ message, kind }),
|
|
1022
|
+
commands: slashCommands
|
|
1023
|
+
};
|
|
1024
|
+
try {
|
|
1025
|
+
await cmd.run(ctx, parsed.args);
|
|
1026
|
+
} catch (err) {
|
|
1027
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
1028
|
+
setFeedback({ message: `/${parsed.name} failed: ${message}`, kind: "error" });
|
|
1029
|
+
}
|
|
1030
|
+
return true;
|
|
1031
|
+
};
|
|
273
1032
|
return /* @__PURE__ */ jsxs(Box, { flexDirection: "column", gap: 1, children: [
|
|
274
1033
|
/* @__PURE__ */ jsx(
|
|
275
1034
|
StatusHeader,
|
|
@@ -278,7 +1037,8 @@ function ChatApp(options) {
|
|
|
278
1037
|
model: runtime.model,
|
|
279
1038
|
mode: runtime.mode,
|
|
280
1039
|
tools: toolNames,
|
|
281
|
-
messageCount: chat.messages.length
|
|
1040
|
+
messageCount: chat.messages.length,
|
|
1041
|
+
sessionId: options.sessionId
|
|
282
1042
|
}
|
|
283
1043
|
),
|
|
284
1044
|
/* @__PURE__ */ jsx(ChatContainer, { children: turns.map((turn, turnIdx) => {
|
|
@@ -295,7 +1055,18 @@ function ChatApp(options) {
|
|
|
295
1055
|
assistantSteps
|
|
296
1056
|
] }) : null,
|
|
297
1057
|
/* @__PURE__ */ jsx(Message, { message }),
|
|
298
|
-
message.toolCalls?.map((toolCall) => /* @__PURE__ */
|
|
1058
|
+
message.toolCalls?.map((toolCall) => /* @__PURE__ */ jsxs(Box, { flexDirection: "column", children: [
|
|
1059
|
+
/* @__PURE__ */ jsx(ToolCallView, { toolCall, expanded: true }),
|
|
1060
|
+
toolCall.status === "requires_confirmation" && !sessionAllowed.has(toolCall.name) ? /* @__PURE__ */ jsx(
|
|
1061
|
+
ToolConfirmation,
|
|
1062
|
+
{
|
|
1063
|
+
toolCall,
|
|
1064
|
+
onApprove: chat.approve,
|
|
1065
|
+
onDeny: chat.deny,
|
|
1066
|
+
onApproveAlways: handleApproveAlways
|
|
1067
|
+
}
|
|
1068
|
+
) : null
|
|
1069
|
+
] }, toolCall.id))
|
|
299
1070
|
] }, message.id);
|
|
300
1071
|
}) }, `turn-${turnIdx}`);
|
|
301
1072
|
}) }),
|
|
@@ -306,9 +1077,38 @@ function ChatApp(options) {
|
|
|
306
1077
|
label: toolNames.length > 0 ? "agent working" : "thinking"
|
|
307
1078
|
}
|
|
308
1079
|
),
|
|
309
|
-
/* @__PURE__ */
|
|
1080
|
+
chat.error ? /* @__PURE__ */ jsxs(Box, { borderStyle: "round", borderColor: "red", paddingX: 1, flexDirection: "column", children: [
|
|
1081
|
+
/* @__PURE__ */ jsxs(Text, { color: "red", bold: true, children: [
|
|
1082
|
+
"\u2717 ",
|
|
1083
|
+
chat.error.name || "Error"
|
|
1084
|
+
] }),
|
|
1085
|
+
/* @__PURE__ */ jsx(Text, { color: "red", children: chat.error.message })
|
|
1086
|
+
] }) : null,
|
|
1087
|
+
feedback ? /* @__PURE__ */ jsx(Box, { borderStyle: "round", borderColor: feedbackBorder(feedback.kind), paddingX: 1, children: /* @__PURE__ */ jsx(Text, { color: feedbackBorder(feedback.kind), children: feedback.message }) }) : null,
|
|
1088
|
+
/* @__PURE__ */ jsx(
|
|
1089
|
+
InputBar,
|
|
1090
|
+
{
|
|
1091
|
+
chat,
|
|
1092
|
+
placeholder: "Type a message or /help for commands",
|
|
1093
|
+
disabled: awaitingConfirmation,
|
|
1094
|
+
onSubmitInput: handleSubmitInput
|
|
1095
|
+
}
|
|
1096
|
+
)
|
|
310
1097
|
] });
|
|
311
1098
|
}
|
|
1099
|
+
function feedbackBorder(kind) {
|
|
1100
|
+
switch (kind) {
|
|
1101
|
+
case "error":
|
|
1102
|
+
return "red";
|
|
1103
|
+
case "warn":
|
|
1104
|
+
return "yellow";
|
|
1105
|
+
case "success":
|
|
1106
|
+
return "green";
|
|
1107
|
+
case "info":
|
|
1108
|
+
default:
|
|
1109
|
+
return "cyan";
|
|
1110
|
+
}
|
|
1111
|
+
}
|
|
312
1112
|
function renderChatHeader(options) {
|
|
313
1113
|
const runtime = resolveChatProvider(options);
|
|
314
1114
|
const parts = [`provider=${runtime.provider}`];
|
|
@@ -319,32 +1119,317 @@ function renderChatHeader(options) {
|
|
|
319
1119
|
if (options.memoryBackend) parts.push(`memory=${options.memoryBackend}`);
|
|
320
1120
|
return parts.join(" ");
|
|
321
1121
|
}
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
};
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
}
|
|
342
|
-
|
|
343
|
-
const
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
1122
|
+
async function loadPlugins(options = {}) {
|
|
1123
|
+
const {
|
|
1124
|
+
specs = [],
|
|
1125
|
+
pluginDirs = [],
|
|
1126
|
+
cwd = process.cwd(),
|
|
1127
|
+
autoDiscoverUserDir = true,
|
|
1128
|
+
onError = (spec, err) => process.stderr.write(
|
|
1129
|
+
`[agentskit] plugin "${spec}" failed to load: ${err instanceof Error ? err.message : String(err)}
|
|
1130
|
+
`
|
|
1131
|
+
),
|
|
1132
|
+
log = () => {
|
|
1133
|
+
}
|
|
1134
|
+
} = options;
|
|
1135
|
+
const resolvedSpecs = [...specs];
|
|
1136
|
+
const discoveryDirs = [...pluginDirs];
|
|
1137
|
+
if (autoDiscoverUserDir) discoveryDirs.push(join(homedir(), ".agentskit", "plugins"));
|
|
1138
|
+
for (const dir of discoveryDirs) {
|
|
1139
|
+
const discovered = await discoverPluginsInDir(dir);
|
|
1140
|
+
resolvedSpecs.push(...discovered);
|
|
1141
|
+
}
|
|
1142
|
+
const plugins = [];
|
|
1143
|
+
for (const spec of resolvedSpecs) {
|
|
1144
|
+
try {
|
|
1145
|
+
const plugin = await loadPluginFromSpec(spec, cwd, log);
|
|
1146
|
+
if (plugin) plugins.push(plugin);
|
|
1147
|
+
} catch (err) {
|
|
1148
|
+
onError(spec, err);
|
|
1149
|
+
}
|
|
1150
|
+
}
|
|
1151
|
+
return mergePluginsIntoBundle(plugins);
|
|
1152
|
+
}
|
|
1153
|
+
async function discoverPluginsInDir(dir) {
|
|
1154
|
+
try {
|
|
1155
|
+
const entries = await readdir(dir);
|
|
1156
|
+
const absolutes = entries.filter((name) => /\.(m?js|ts)$/i.test(name)).map((name) => join(dir, name));
|
|
1157
|
+
const validated = [];
|
|
1158
|
+
for (const p of absolutes) {
|
|
1159
|
+
try {
|
|
1160
|
+
const s = await stat(p);
|
|
1161
|
+
if (s.isFile()) validated.push(p);
|
|
1162
|
+
} catch {
|
|
1163
|
+
}
|
|
1164
|
+
}
|
|
1165
|
+
return validated;
|
|
1166
|
+
} catch {
|
|
1167
|
+
return [];
|
|
1168
|
+
}
|
|
1169
|
+
}
|
|
1170
|
+
async function loadPluginFromSpec(spec, cwd, log) {
|
|
1171
|
+
const isPath = spec.startsWith("./") || spec.startsWith("../") || isAbsolute(spec);
|
|
1172
|
+
const importTarget = isPath ? pathToFileURL(resolve(cwd, spec)).href : spec;
|
|
1173
|
+
const mod = await import(importTarget);
|
|
1174
|
+
const exported = mod.default ?? mod.plugin ?? mod;
|
|
1175
|
+
const sourcePath = isPath ? resolve(cwd, spec) : void 0;
|
|
1176
|
+
const ctx = {
|
|
1177
|
+
cwd,
|
|
1178
|
+
sourcePath,
|
|
1179
|
+
log: (msg) => log(`[${spec}] ${msg}`)
|
|
1180
|
+
};
|
|
1181
|
+
if (typeof exported === "function") {
|
|
1182
|
+
const factory = exported;
|
|
1183
|
+
return await factory(ctx);
|
|
1184
|
+
}
|
|
1185
|
+
if (exported && typeof exported === "object" && "name" in exported) {
|
|
1186
|
+
const plugin = exported;
|
|
1187
|
+
if (plugin.init) await plugin.init(ctx);
|
|
1188
|
+
return plugin;
|
|
1189
|
+
}
|
|
1190
|
+
throw new Error(
|
|
1191
|
+
"Module did not export a Plugin \u2014 expected default export to be a Plugin object or a PluginFactory function."
|
|
1192
|
+
);
|
|
1193
|
+
}
|
|
1194
|
+
function mergePluginsIntoBundle(plugins) {
|
|
1195
|
+
const bundle = {
|
|
1196
|
+
plugins,
|
|
1197
|
+
slashCommands: [],
|
|
1198
|
+
tools: [],
|
|
1199
|
+
skills: [],
|
|
1200
|
+
providers: {},
|
|
1201
|
+
hooks: [],
|
|
1202
|
+
mcpServers: []
|
|
1203
|
+
};
|
|
1204
|
+
for (const plugin of plugins) {
|
|
1205
|
+
if (plugin.slashCommands) bundle.slashCommands.push(...plugin.slashCommands);
|
|
1206
|
+
if (plugin.tools) bundle.tools.push(...plugin.tools);
|
|
1207
|
+
if (plugin.skills) bundle.skills.push(...plugin.skills);
|
|
1208
|
+
if (plugin.hooks) bundle.hooks.push(...plugin.hooks);
|
|
1209
|
+
if (plugin.mcpServers) bundle.mcpServers.push(...plugin.mcpServers);
|
|
1210
|
+
if (plugin.providers) {
|
|
1211
|
+
for (const [name, factory] of Object.entries(plugin.providers)) {
|
|
1212
|
+
bundle.providers[name] = factory;
|
|
1213
|
+
}
|
|
1214
|
+
}
|
|
1215
|
+
}
|
|
1216
|
+
return bundle;
|
|
1217
|
+
}
|
|
1218
|
+
var McpClient = class {
|
|
1219
|
+
constructor(spec, onError = (err) => process.stderr.write(
|
|
1220
|
+
`[agentskit] mcp[${spec.name}] error: ${err instanceof Error ? err.message : String(err)}
|
|
1221
|
+
`
|
|
1222
|
+
)) {
|
|
1223
|
+
this.spec = spec;
|
|
1224
|
+
this.onError = onError;
|
|
1225
|
+
this.buffer = "";
|
|
1226
|
+
this.nextId = 1;
|
|
1227
|
+
this.pending = /* @__PURE__ */ new Map();
|
|
1228
|
+
this.disposed = false;
|
|
1229
|
+
}
|
|
1230
|
+
async start() {
|
|
1231
|
+
if (this.child) return;
|
|
1232
|
+
const child = spawn(this.spec.command, this.spec.args ?? [], {
|
|
1233
|
+
env: { ...process.env, ...this.spec.env ?? {} },
|
|
1234
|
+
stdio: ["pipe", "pipe", "pipe"]
|
|
1235
|
+
});
|
|
1236
|
+
this.child = child;
|
|
1237
|
+
child.stdout.on("data", (chunk) => this.onStdout(chunk.toString()));
|
|
1238
|
+
child.stderr.on("data", (chunk) => {
|
|
1239
|
+
process.stderr.write(`[mcp ${this.spec.name}] ${chunk}`);
|
|
1240
|
+
});
|
|
1241
|
+
child.on("error", (err) => this.onError(err));
|
|
1242
|
+
child.on("close", () => {
|
|
1243
|
+
this.disposed = true;
|
|
1244
|
+
for (const pending of this.pending.values()) {
|
|
1245
|
+
pending({ jsonrpc: "2.0", id: 0, error: { code: -1, message: "server closed" } });
|
|
1246
|
+
}
|
|
1247
|
+
this.pending.clear();
|
|
1248
|
+
});
|
|
1249
|
+
await this.request("initialize", {
|
|
1250
|
+
protocolVersion: "2024-11-05",
|
|
1251
|
+
capabilities: {},
|
|
1252
|
+
clientInfo: { name: "agentskit", version: "0" }
|
|
1253
|
+
});
|
|
1254
|
+
}
|
|
1255
|
+
async listTools() {
|
|
1256
|
+
const res = await this.request("tools/list", {});
|
|
1257
|
+
const tools = res.tools ?? [];
|
|
1258
|
+
return tools;
|
|
1259
|
+
}
|
|
1260
|
+
async callTool(name, args) {
|
|
1261
|
+
return await this.request("tools/call", { name, arguments: args });
|
|
1262
|
+
}
|
|
1263
|
+
dispose() {
|
|
1264
|
+
if (this.disposed) return;
|
|
1265
|
+
this.disposed = true;
|
|
1266
|
+
this.child?.kill("SIGTERM");
|
|
1267
|
+
}
|
|
1268
|
+
request(method, params) {
|
|
1269
|
+
return new Promise((resolvePromise, rejectPromise) => {
|
|
1270
|
+
if (!this.child || this.disposed) {
|
|
1271
|
+
rejectPromise(new Error(`mcp server ${this.spec.name} not running`));
|
|
1272
|
+
return;
|
|
1273
|
+
}
|
|
1274
|
+
const id = this.nextId++;
|
|
1275
|
+
const timeoutMs = this.spec.timeout ?? 1e4;
|
|
1276
|
+
const timer = setTimeout(() => {
|
|
1277
|
+
this.pending.delete(id);
|
|
1278
|
+
rejectPromise(new Error(`mcp ${this.spec.name}.${method} timed out`));
|
|
1279
|
+
}, timeoutMs);
|
|
1280
|
+
this.pending.set(id, (res) => {
|
|
1281
|
+
clearTimeout(timer);
|
|
1282
|
+
if (res.error) {
|
|
1283
|
+
rejectPromise(new Error(`mcp ${method} failed: ${res.error.message}`));
|
|
1284
|
+
return;
|
|
1285
|
+
}
|
|
1286
|
+
resolvePromise(res.result);
|
|
1287
|
+
});
|
|
1288
|
+
const message = JSON.stringify({ jsonrpc: "2.0", id, method, params }) + "\n";
|
|
1289
|
+
this.child.stdin.write(message);
|
|
1290
|
+
});
|
|
1291
|
+
}
|
|
1292
|
+
onStdout(chunk) {
|
|
1293
|
+
this.buffer += chunk;
|
|
1294
|
+
let newlineIndex = this.buffer.indexOf("\n");
|
|
1295
|
+
while (newlineIndex !== -1) {
|
|
1296
|
+
const line = this.buffer.slice(0, newlineIndex).trim();
|
|
1297
|
+
this.buffer = this.buffer.slice(newlineIndex + 1);
|
|
1298
|
+
if (line) {
|
|
1299
|
+
try {
|
|
1300
|
+
const parsed = JSON.parse(line);
|
|
1301
|
+
const pending = this.pending.get(Number(parsed.id));
|
|
1302
|
+
if (pending) {
|
|
1303
|
+
this.pending.delete(Number(parsed.id));
|
|
1304
|
+
pending(parsed);
|
|
1305
|
+
}
|
|
1306
|
+
} catch (err) {
|
|
1307
|
+
this.onError(err);
|
|
1308
|
+
}
|
|
1309
|
+
}
|
|
1310
|
+
newlineIndex = this.buffer.indexOf("\n");
|
|
1311
|
+
}
|
|
1312
|
+
}
|
|
1313
|
+
};
|
|
1314
|
+
|
|
1315
|
+
// src/extensibility/mcp/bridge.ts
|
|
1316
|
+
async function bridgeMcpServers(specs) {
|
|
1317
|
+
const clients = [];
|
|
1318
|
+
const tools = [];
|
|
1319
|
+
for (const spec of specs) {
|
|
1320
|
+
const client = new McpClient(spec);
|
|
1321
|
+
try {
|
|
1322
|
+
await client.start();
|
|
1323
|
+
const mcpTools = await client.listTools();
|
|
1324
|
+
for (const mcpTool of mcpTools) {
|
|
1325
|
+
tools.push(mcpToolToDefinition(spec.name, client, mcpTool));
|
|
1326
|
+
}
|
|
1327
|
+
clients.push(client);
|
|
1328
|
+
} catch (err) {
|
|
1329
|
+
process.stderr.write(
|
|
1330
|
+
`[agentskit] mcp server "${spec.name}" failed: ${err instanceof Error ? err.message : String(err)}
|
|
1331
|
+
`
|
|
1332
|
+
);
|
|
1333
|
+
client.dispose();
|
|
1334
|
+
}
|
|
1335
|
+
}
|
|
1336
|
+
return { clients, tools };
|
|
1337
|
+
}
|
|
1338
|
+
function mcpToolToDefinition(serverName, client, tool) {
|
|
1339
|
+
return {
|
|
1340
|
+
name: `${serverName}__${tool.name}`,
|
|
1341
|
+
description: tool.description ?? `MCP tool ${tool.name} from ${serverName}`,
|
|
1342
|
+
schema: tool.inputSchema ?? { type: "object", properties: {} },
|
|
1343
|
+
execute: async (args) => {
|
|
1344
|
+
return await client.callTool(tool.name, args);
|
|
1345
|
+
}
|
|
1346
|
+
};
|
|
1347
|
+
}
|
|
1348
|
+
function disposeMcpClients(clients) {
|
|
1349
|
+
for (const client of clients) client.dispose();
|
|
1350
|
+
}
|
|
1351
|
+
function formatEvent(event) {
|
|
1352
|
+
switch (event.type) {
|
|
1353
|
+
case "agent:step":
|
|
1354
|
+
return `[step ${event.step}] ${event.action}`;
|
|
1355
|
+
case "llm:start":
|
|
1356
|
+
return `[llm] start (${event.messageCount} messages)`;
|
|
1357
|
+
case "llm:end": {
|
|
1358
|
+
const preview = event.content.length > 100 ? event.content.slice(0, 100) + "..." : event.content;
|
|
1359
|
+
return `[llm] done (${event.durationMs}ms) "${preview}"`;
|
|
1360
|
+
}
|
|
1361
|
+
case "tool:start":
|
|
1362
|
+
return `[tool] ${event.name} ${JSON.stringify(event.args)}`;
|
|
1363
|
+
case "tool:end":
|
|
1364
|
+
return `[tool] ${event.name} done (${event.durationMs}ms)`;
|
|
1365
|
+
case "error":
|
|
1366
|
+
return `[error] ${event.error.message}`;
|
|
1367
|
+
default:
|
|
1368
|
+
return `[${event.type}]`;
|
|
1369
|
+
}
|
|
1370
|
+
}
|
|
1371
|
+
async function runAgent(task, options) {
|
|
1372
|
+
if (options.skill && options.skills) {
|
|
1373
|
+
process.stderr.write("Error: --skill and --skills are mutually exclusive. Use one or the other.\n");
|
|
1374
|
+
process.exit(1);
|
|
1375
|
+
}
|
|
1376
|
+
const { adapter } = resolveChatProvider({
|
|
1377
|
+
provider: options.provider,
|
|
1378
|
+
model: options.model,
|
|
1379
|
+
apiKey: options.apiKey,
|
|
1380
|
+
baseUrl: options.baseUrl
|
|
1381
|
+
});
|
|
1382
|
+
const tools = resolveTools(options.tools);
|
|
1383
|
+
const skill = options.skills ? resolveSkills(options.skills) : resolveSkill(options.skill);
|
|
1384
|
+
const memory = options.memory ? resolveMemory(options.memoryBackend, options.memory) : void 0;
|
|
1385
|
+
const observers = [];
|
|
1386
|
+
if (options.verbose) {
|
|
1387
|
+
observers.push({
|
|
1388
|
+
name: "cli-verbose",
|
|
1389
|
+
on(event) {
|
|
1390
|
+
process.stderr.write(formatEvent(event) + "\n");
|
|
1391
|
+
}
|
|
1392
|
+
});
|
|
1393
|
+
}
|
|
1394
|
+
const runtime = createRuntime({
|
|
1395
|
+
adapter,
|
|
1396
|
+
tools,
|
|
1397
|
+
memory,
|
|
1398
|
+
systemPrompt: options.systemPrompt,
|
|
1399
|
+
maxSteps: options.maxSteps ? parseInt(options.maxSteps, 10) : void 0,
|
|
1400
|
+
observers
|
|
1401
|
+
});
|
|
1402
|
+
const result = await runtime.run(task, {
|
|
1403
|
+
skill: skill ?? void 0
|
|
1404
|
+
});
|
|
1405
|
+
process.stdout.write(result.content + "\n");
|
|
1406
|
+
}
|
|
1407
|
+
var PROVIDER_IMPORT = {
|
|
1408
|
+
openai: "openai",
|
|
1409
|
+
anthropic: "anthropic",
|
|
1410
|
+
gemini: "gemini",
|
|
1411
|
+
ollama: "ollama"
|
|
1412
|
+
};
|
|
1413
|
+
var PROVIDER_DEFAULT_MODEL = {
|
|
1414
|
+
openai: "gpt-4o-mini",
|
|
1415
|
+
anthropic: "claude-sonnet-4-6",
|
|
1416
|
+
gemini: "gemini-2.5-flash",
|
|
1417
|
+
ollama: "llama3.1",
|
|
1418
|
+
demo: "demo"
|
|
1419
|
+
};
|
|
1420
|
+
var PROVIDER_ENV_KEY = {
|
|
1421
|
+
openai: "OPENAI_API_KEY",
|
|
1422
|
+
anthropic: "ANTHROPIC_API_KEY",
|
|
1423
|
+
gemini: "GEMINI_API_KEY",
|
|
1424
|
+
ollama: null,
|
|
1425
|
+
demo: null
|
|
1426
|
+
};
|
|
1427
|
+
function adapterCall(provider, prefix = "process.env") {
|
|
1428
|
+
const model = PROVIDER_DEFAULT_MODEL[provider];
|
|
1429
|
+
if (provider === "demo") return `demoAdapter()`;
|
|
1430
|
+
if (provider === "ollama") return `ollama({ model: '${model}' })`;
|
|
1431
|
+
const envKey = PROVIDER_ENV_KEY[provider];
|
|
1432
|
+
return `${PROVIDER_IMPORT[provider]}({ apiKey: ${prefix}.${envKey} ?? '', model: '${model}' })`;
|
|
348
1433
|
}
|
|
349
1434
|
function viteAdapterCall(provider) {
|
|
350
1435
|
if (provider === "demo") return `demoAdapter()`;
|
|
@@ -792,62 +1877,6 @@ async function writeStarterProject(options) {
|
|
|
792
1877
|
})
|
|
793
1878
|
);
|
|
794
1879
|
}
|
|
795
|
-
function formatEvent(event) {
|
|
796
|
-
switch (event.type) {
|
|
797
|
-
case "agent:step":
|
|
798
|
-
return `[step ${event.step}] ${event.action}`;
|
|
799
|
-
case "llm:start":
|
|
800
|
-
return `[llm] start (${event.messageCount} messages)`;
|
|
801
|
-
case "llm:end": {
|
|
802
|
-
const preview = event.content.length > 100 ? event.content.slice(0, 100) + "..." : event.content;
|
|
803
|
-
return `[llm] done (${event.durationMs}ms) "${preview}"`;
|
|
804
|
-
}
|
|
805
|
-
case "tool:start":
|
|
806
|
-
return `[tool] ${event.name} ${JSON.stringify(event.args)}`;
|
|
807
|
-
case "tool:end":
|
|
808
|
-
return `[tool] ${event.name} done (${event.durationMs}ms)`;
|
|
809
|
-
case "error":
|
|
810
|
-
return `[error] ${event.error.message}`;
|
|
811
|
-
default:
|
|
812
|
-
return `[${event.type}]`;
|
|
813
|
-
}
|
|
814
|
-
}
|
|
815
|
-
async function runAgent(task, options) {
|
|
816
|
-
if (options.skill && options.skills) {
|
|
817
|
-
process.stderr.write("Error: --skill and --skills are mutually exclusive. Use one or the other.\n");
|
|
818
|
-
process.exit(1);
|
|
819
|
-
}
|
|
820
|
-
const { adapter } = resolveChatProvider({
|
|
821
|
-
provider: options.provider,
|
|
822
|
-
model: options.model,
|
|
823
|
-
apiKey: options.apiKey,
|
|
824
|
-
baseUrl: options.baseUrl
|
|
825
|
-
});
|
|
826
|
-
const tools = resolveTools(options.tools);
|
|
827
|
-
const skill = options.skills ? resolveSkills(options.skills) : resolveSkill(options.skill);
|
|
828
|
-
const memory = options.memory ? resolveMemory(options.memoryBackend, options.memory) : void 0;
|
|
829
|
-
const observers = [];
|
|
830
|
-
if (options.verbose) {
|
|
831
|
-
observers.push({
|
|
832
|
-
name: "cli-verbose",
|
|
833
|
-
on(event) {
|
|
834
|
-
process.stderr.write(formatEvent(event) + "\n");
|
|
835
|
-
}
|
|
836
|
-
});
|
|
837
|
-
}
|
|
838
|
-
const runtime = createRuntime({
|
|
839
|
-
adapter,
|
|
840
|
-
tools,
|
|
841
|
-
memory,
|
|
842
|
-
systemPrompt: options.systemPrompt,
|
|
843
|
-
maxSteps: options.maxSteps ? parseInt(options.maxSteps, 10) : void 0,
|
|
844
|
-
observers
|
|
845
|
-
});
|
|
846
|
-
const result = await runtime.run(task, {
|
|
847
|
-
skill: skill ?? void 0
|
|
848
|
-
});
|
|
849
|
-
process.stdout.write(result.content + "\n");
|
|
850
|
-
}
|
|
851
1880
|
var PROVIDER_ENV_KEYS = {
|
|
852
1881
|
openai: "OPENAI_API_KEY",
|
|
853
1882
|
anthropic: "ANTHROPIC_API_KEY",
|
|
@@ -907,8 +1936,8 @@ async function checkPnpm() {
|
|
|
907
1936
|
};
|
|
908
1937
|
}
|
|
909
1938
|
async function checkPackageJson() {
|
|
910
|
-
const
|
|
911
|
-
if (!existsSync(
|
|
1939
|
+
const path5 = join(process.cwd(), "package.json");
|
|
1940
|
+
if (!existsSync(path5)) {
|
|
912
1941
|
return {
|
|
913
1942
|
status: "warn",
|
|
914
1943
|
name: "package.json",
|
|
@@ -917,7 +1946,7 @@ async function checkPackageJson() {
|
|
|
917
1946
|
};
|
|
918
1947
|
}
|
|
919
1948
|
try {
|
|
920
|
-
const pkg = JSON.parse(await readFile(
|
|
1949
|
+
const pkg = JSON.parse(await readFile(path5, "utf8"));
|
|
921
1950
|
const deps = {
|
|
922
1951
|
...pkg.dependencies ?? {},
|
|
923
1952
|
...pkg.devDependencies ?? {}
|
|
@@ -1179,11 +2208,11 @@ function startDev(options) {
|
|
|
1179
2208
|
}
|
|
1180
2209
|
});
|
|
1181
2210
|
};
|
|
1182
|
-
const restart = (
|
|
2211
|
+
const restart = (path5) => {
|
|
1183
2212
|
if (restartTimer) clearTimeout(restartTimer);
|
|
1184
2213
|
restartTimer = setTimeout(() => {
|
|
1185
2214
|
restartTimer = void 0;
|
|
1186
|
-
banner(`\u21BB change detected \u2014 ${
|
|
2215
|
+
banner(`\u21BB change detected \u2014 ${path5}`, "yellow");
|
|
1187
2216
|
if (child && !child.killed && child.exitCode === null) {
|
|
1188
2217
|
child.kill("SIGTERM");
|
|
1189
2218
|
}
|
|
@@ -1284,132 +2313,205 @@ async function startTunnel(options) {
|
|
|
1284
2313
|
requests: () => requests
|
|
1285
2314
|
};
|
|
1286
2315
|
}
|
|
1287
|
-
|
|
1288
|
-
|
|
1289
|
-
|
|
1290
|
-
|
|
1291
|
-
|
|
1292
|
-
|
|
1293
|
-
const
|
|
1294
|
-
|
|
1295
|
-
|
|
1296
|
-
|
|
1297
|
-
|
|
1298
|
-
|
|
1299
|
-
|
|
1300
|
-
return true;
|
|
1301
|
-
}
|
|
1302
|
-
});
|
|
1303
|
-
const template = await select({
|
|
1304
|
-
message: "Template:",
|
|
1305
|
-
default: defaults.template ?? "react",
|
|
1306
|
-
choices: [
|
|
1307
|
-
{ name: "React chat (Vite + browser)", value: "react", description: "Streaming UI with @agentskit/react" },
|
|
1308
|
-
{ name: "Ink chat (terminal UI)", value: "ink", description: "Same chat but in your terminal" },
|
|
1309
|
-
{ name: "Runtime (headless agent, no UI)", value: "runtime", description: "Autonomous task \u2192 result" },
|
|
1310
|
-
{ name: "Multi-agent (planner + delegates)", value: "multi-agent", description: "Supervisor pattern, ready to extend" }
|
|
1311
|
-
]
|
|
1312
|
-
});
|
|
1313
|
-
const provider = await select({
|
|
1314
|
-
message: "LLM provider:",
|
|
1315
|
-
default: "demo",
|
|
1316
|
-
choices: [
|
|
1317
|
-
{ name: "Demo (no API key \u2014 deterministic stub)", value: "demo" },
|
|
1318
|
-
{ name: "OpenAI", value: "openai" },
|
|
1319
|
-
{ name: "Anthropic", value: "anthropic" },
|
|
1320
|
-
{ name: "Gemini", value: "gemini" },
|
|
1321
|
-
{ name: "Ollama (local, no key)", value: "ollama" }
|
|
1322
|
-
]
|
|
2316
|
+
|
|
2317
|
+
// src/extensibility/rag/embedders.ts
|
|
2318
|
+
function createOpenAiEmbedder(config) {
|
|
2319
|
+
const model = config.model ?? "text-embedding-3-small";
|
|
2320
|
+
const baseUrl = (config.baseUrl ?? "https://api.openai.com").replace(/\/$/, "");
|
|
2321
|
+
return async (text) => {
|
|
2322
|
+
const res = await fetch(`${baseUrl}/v1/embeddings`, {
|
|
2323
|
+
method: "POST",
|
|
2324
|
+
headers: {
|
|
2325
|
+
"content-type": "application/json",
|
|
2326
|
+
authorization: `Bearer ${config.apiKey}`
|
|
2327
|
+
},
|
|
2328
|
+
body: JSON.stringify({ model, input: text })
|
|
1323
2329
|
});
|
|
1324
|
-
|
|
1325
|
-
|
|
1326
|
-
|
|
1327
|
-
message: "Tools (space to toggle, enter to confirm):",
|
|
1328
|
-
choices: [
|
|
1329
|
-
{ name: "web_search", value: "web_search" },
|
|
1330
|
-
{ name: "filesystem", value: "filesystem" },
|
|
1331
|
-
{ name: "shell", value: "shell" }
|
|
1332
|
-
]
|
|
1333
|
-
});
|
|
2330
|
+
if (!res.ok) {
|
|
2331
|
+
const body = await res.text().catch(() => "");
|
|
2332
|
+
throw new Error(`embedder ${model} HTTP ${res.status}: ${body}`);
|
|
1334
2333
|
}
|
|
1335
|
-
const
|
|
1336
|
-
|
|
1337
|
-
|
|
1338
|
-
|
|
1339
|
-
|
|
1340
|
-
|
|
1341
|
-
|
|
1342
|
-
|
|
1343
|
-
|
|
1344
|
-
|
|
1345
|
-
|
|
1346
|
-
|
|
1347
|
-
|
|
1348
|
-
|
|
1349
|
-
|
|
1350
|
-
{ name: "yarn", value: "yarn" },
|
|
1351
|
-
{ name: "bun", value: "bun" }
|
|
1352
|
-
]
|
|
1353
|
-
});
|
|
1354
|
-
process.stdout.write("\n" + kleur3.dim(" Summary:\n"));
|
|
1355
|
-
process.stdout.write(kleur3.dim(` dir ${targetDir}
|
|
1356
|
-
`));
|
|
1357
|
-
process.stdout.write(kleur3.dim(` template ${template}
|
|
1358
|
-
`));
|
|
1359
|
-
process.stdout.write(kleur3.dim(` provider ${provider}
|
|
1360
|
-
`));
|
|
1361
|
-
if (tools.length) process.stdout.write(kleur3.dim(` tools ${tools.join(", ")}
|
|
1362
|
-
`));
|
|
1363
|
-
process.stdout.write(kleur3.dim(` memory ${memory}
|
|
1364
|
-
`));
|
|
1365
|
-
process.stdout.write(kleur3.dim(` pm ${packageManager}
|
|
1366
|
-
|
|
1367
|
-
`));
|
|
1368
|
-
const proceed = await confirm({ message: "Generate?", default: true });
|
|
1369
|
-
if (!proceed) {
|
|
1370
|
-
process.stdout.write(kleur3.yellow("Cancelled.\n"));
|
|
1371
|
-
return { cancelled: true, options: { targetDir, template, provider, tools, memory, packageManager } };
|
|
1372
|
-
}
|
|
1373
|
-
return {
|
|
1374
|
-
cancelled: false,
|
|
1375
|
-
options: {
|
|
1376
|
-
targetDir: path.resolve(process.cwd(), targetDir),
|
|
1377
|
-
template,
|
|
1378
|
-
provider,
|
|
1379
|
-
tools,
|
|
1380
|
-
memory,
|
|
1381
|
-
packageManager
|
|
1382
|
-
}
|
|
1383
|
-
};
|
|
1384
|
-
} catch (err) {
|
|
1385
|
-
if (err.name === "ExitPromptError") {
|
|
1386
|
-
process.stdout.write(kleur3.yellow("\nCancelled.\n"));
|
|
1387
|
-
return { cancelled: true, options: { targetDir: "", template: "react" } };
|
|
1388
|
-
}
|
|
1389
|
-
throw err;
|
|
2334
|
+
const json = await res.json();
|
|
2335
|
+
const first = json.data?.[0]?.embedding;
|
|
2336
|
+
if (!first) throw new Error(`embedder ${model}: response missing data[0].embedding`);
|
|
2337
|
+
return first;
|
|
2338
|
+
};
|
|
2339
|
+
}
|
|
2340
|
+
function resolveEmbedder(config) {
|
|
2341
|
+
const embedder = config.embedder;
|
|
2342
|
+
const provider = embedder?.provider ?? "openai";
|
|
2343
|
+
if (provider !== "openai") {
|
|
2344
|
+
throw new Error(`Unsupported RAG embedder provider: ${provider}. Only "openai" is built-in.`);
|
|
2345
|
+
}
|
|
2346
|
+
const apiKey = embedder?.apiKey ?? process.env.OPENAI_API_KEY ?? process.env.OPENROUTER_API_KEY;
|
|
2347
|
+
if (!apiKey) {
|
|
2348
|
+
throw new Error("RAG embedder needs an API key (config.rag.embedder.apiKey or OPENAI_API_KEY env).");
|
|
1390
2349
|
}
|
|
2350
|
+
return createOpenAiEmbedder({
|
|
2351
|
+
apiKey,
|
|
2352
|
+
model: embedder?.model,
|
|
2353
|
+
baseUrl: embedder?.baseUrl
|
|
2354
|
+
});
|
|
1391
2355
|
}
|
|
1392
|
-
function
|
|
1393
|
-
const
|
|
1394
|
-
const
|
|
1395
|
-
const
|
|
1396
|
-
const
|
|
1397
|
-
|
|
1398
|
-
|
|
1399
|
-
|
|
1400
|
-
|
|
1401
|
-
|
|
1402
|
-
|
|
1403
|
-
|
|
1404
|
-
|
|
1405
|
-
|
|
1406
|
-
|
|
1407
|
-
|
|
2356
|
+
function buildRagFromConfig(options) {
|
|
2357
|
+
const cwd = options.cwd ?? process.cwd();
|
|
2358
|
+
const dir = resolve(cwd, options.config.dir ?? "./.agentskit-rag");
|
|
2359
|
+
const store = fileVectorMemory({ path: `${dir}/store.json` });
|
|
2360
|
+
const embed = options.embedder ?? resolveEmbedder(options.config);
|
|
2361
|
+
return createRAG({
|
|
2362
|
+
embed,
|
|
2363
|
+
store,
|
|
2364
|
+
chunkSize: options.config.chunkSize,
|
|
2365
|
+
topK: options.config.topK
|
|
2366
|
+
});
|
|
2367
|
+
}
|
|
2368
|
+
async function indexSources(rag, config, cwd) {
|
|
2369
|
+
const root = cwd ?? process.cwd();
|
|
2370
|
+
const sources = config.sources ?? [];
|
|
2371
|
+
const absolutePaths = [];
|
|
2372
|
+
for (const pattern of sources) {
|
|
2373
|
+
for await (const match of glob(pattern, { cwd: root })) {
|
|
2374
|
+
absolutePaths.push(resolve(root, match));
|
|
2375
|
+
}
|
|
1408
2376
|
}
|
|
1409
|
-
|
|
2377
|
+
const documents = await Promise.all(
|
|
2378
|
+
absolutePaths.map(async (path5) => ({
|
|
2379
|
+
id: path5,
|
|
2380
|
+
content: await readFile(path5, "utf8"),
|
|
2381
|
+
source: path5
|
|
2382
|
+
}))
|
|
2383
|
+
);
|
|
2384
|
+
if (documents.length > 0) await rag.ingest(documents);
|
|
2385
|
+
return { documentCount: documents.length, sources: absolutePaths };
|
|
2386
|
+
}
|
|
1410
2387
|
|
|
2388
|
+
// src/commands/shared.ts
|
|
2389
|
+
function mergeWithConfig(options, config) {
|
|
2390
|
+
if (!config) return options;
|
|
2391
|
+
const d = config.defaults ?? {};
|
|
2392
|
+
const resolvedApiKey = options.apiKey ?? (d.apiKeyEnv ? process.env[d.apiKeyEnv] : void 0) ?? d.apiKey;
|
|
2393
|
+
return {
|
|
2394
|
+
...options,
|
|
2395
|
+
provider: options.provider !== "demo" ? options.provider : d.provider ?? options.provider,
|
|
2396
|
+
model: options.model ?? d.model,
|
|
2397
|
+
apiKey: resolvedApiKey,
|
|
2398
|
+
baseUrl: options.baseUrl ?? d.baseUrl,
|
|
2399
|
+
tools: options.tools ?? d.tools,
|
|
2400
|
+
skill: options.skill ?? d.skill,
|
|
2401
|
+
system: options.system ?? d.system,
|
|
2402
|
+
memoryBackend: options.memoryBackend ?? d.memoryBackend
|
|
2403
|
+
};
|
|
2404
|
+
}
|
|
2405
|
+
|
|
2406
|
+
// src/commands/chat.ts
|
|
2407
|
+
function registerChatCommand(program) {
|
|
2408
|
+
program.command("chat").description("Start a terminal chat session.").option("--provider <provider>", "Provider to use", "demo").option("--model <model>", "Model name").option("--api-key <key>", "API key for the selected provider").option("--base-url <url>", "Override provider base URL").option("--system <prompt>", "System prompt").option("--memory <path>", "Explicit memory file path (overrides session management)").option("--tools <tools>", "Comma-separated tools: web_search,fetch_url,filesystem,shell").option("--skill <skills>", "Comma-separated skills: researcher,coder,planner,critic,summarizer").option("--memory-backend <backend>", "Memory backend: file (default), sqlite").option("--new", "Start a fresh chat session (ignore previous conversations in this directory)").option("--resume [id]", "Resume a prior session by id; omit id to resume the latest").option("--list-sessions", "List saved sessions for this directory and exit").option("--no-config", "Skip loading .agentskit.config.json").option(
|
|
2409
|
+
"--plugin-dir <dir>",
|
|
2410
|
+
"Extra directory to auto-discover plugin modules from (repeatable)",
|
|
2411
|
+
(value, prev = []) => [...prev, value],
|
|
2412
|
+
[]
|
|
2413
|
+
).option(
|
|
2414
|
+
"--mode <mode>",
|
|
2415
|
+
"Permission mode: default | plan | acceptEdits | bypassPermissions"
|
|
2416
|
+
).action(async (options) => {
|
|
2417
|
+
if (options.listSessions) {
|
|
2418
|
+
const sessions = listSessions();
|
|
2419
|
+
if (sessions.length === 0) {
|
|
2420
|
+
process.stdout.write("No saved sessions for this directory.\n");
|
|
2421
|
+
return;
|
|
2422
|
+
}
|
|
2423
|
+
for (const s of sessions) {
|
|
2424
|
+
const { id, updatedAt, messageCount, preview, model, label, forkedFrom } = s.metadata;
|
|
2425
|
+
const display = label ? `${label} (${id})` : id;
|
|
2426
|
+
const forkNote = forkedFrom ? ` \u2190 fork ${forkedFrom}` : "";
|
|
2427
|
+
process.stdout.write(
|
|
2428
|
+
`${display} ${updatedAt} msgs=${messageCount}${model ? ` model=${model}` : ""}${forkNote}
|
|
2429
|
+
${preview}
|
|
2430
|
+
`
|
|
2431
|
+
);
|
|
2432
|
+
}
|
|
2433
|
+
return;
|
|
2434
|
+
}
|
|
2435
|
+
const config = options.config !== false ? await loadConfig() : void 0;
|
|
2436
|
+
const merged = mergeWithConfig(options, config);
|
|
2437
|
+
const session = resolveSession({
|
|
2438
|
+
explicitPath: options.memory,
|
|
2439
|
+
forceNew: Boolean(options.new),
|
|
2440
|
+
resumeId: options.resume
|
|
2441
|
+
});
|
|
2442
|
+
if (!session.isNew && !options.memory) {
|
|
2443
|
+
process.stdout.write(
|
|
2444
|
+
`Resuming session ${session.id}. Start fresh with --new or list with --list-sessions.
|
|
2445
|
+
`
|
|
2446
|
+
);
|
|
2447
|
+
}
|
|
2448
|
+
const pluginBundle = await loadPlugins({
|
|
2449
|
+
specs: config?.plugins ?? [],
|
|
2450
|
+
pluginDirs: options.pluginDir ?? []
|
|
2451
|
+
});
|
|
2452
|
+
const configHooks = configHooksToHandlers(config?.hooks);
|
|
2453
|
+
const hookHandlers = [...configHooks, ...pluginBundle.hooks];
|
|
2454
|
+
const configMcpSpecs = Object.entries(config?.mcp?.servers ?? {}).map(([name, spec]) => ({
|
|
2455
|
+
name,
|
|
2456
|
+
command: spec.command,
|
|
2457
|
+
args: spec.args,
|
|
2458
|
+
env: spec.env,
|
|
2459
|
+
timeout: spec.timeout
|
|
2460
|
+
}));
|
|
2461
|
+
const allMcpSpecs = [...configMcpSpecs, ...pluginBundle.mcpServers];
|
|
2462
|
+
const { clients: mcpClients, tools: mcpTools } = await bridgeMcpServers(allMcpSpecs);
|
|
2463
|
+
const policyMode = options.mode ?? config?.permissions?.mode ?? "default";
|
|
2464
|
+
const permissionPolicy = {
|
|
2465
|
+
mode: policyMode,
|
|
2466
|
+
rules: (config?.permissions?.rules ?? []).map((r) => ({
|
|
2467
|
+
tool: r.tool,
|
|
2468
|
+
action: r.action,
|
|
2469
|
+
scope: r.scope
|
|
2470
|
+
}))
|
|
2471
|
+
};
|
|
2472
|
+
const chatOptions = {
|
|
2473
|
+
apiKey: merged.apiKey ?? options.apiKey,
|
|
2474
|
+
baseUrl: merged.baseUrl ?? options.baseUrl,
|
|
2475
|
+
provider: merged.provider,
|
|
2476
|
+
model: merged.model,
|
|
2477
|
+
system: merged.system ?? options.system,
|
|
2478
|
+
memoryPath: session.file,
|
|
2479
|
+
sessionId: session.id,
|
|
2480
|
+
tools: merged.tools ?? options.tools,
|
|
2481
|
+
skill: merged.skill ?? options.skill,
|
|
2482
|
+
memoryBackend: merged.memoryBackend ?? options.memoryBackend,
|
|
2483
|
+
agentsKitConfig: config,
|
|
2484
|
+
slashCommands: pluginBundle.slashCommands,
|
|
2485
|
+
extraTools: [...pluginBundle.tools, ...mcpTools],
|
|
2486
|
+
extraSkills: pluginBundle.skills,
|
|
2487
|
+
hookHandlers,
|
|
2488
|
+
permissionPolicy
|
|
2489
|
+
};
|
|
2490
|
+
process.stdout.write(`${renderChatHeader(chatOptions)}
|
|
1411
2491
|
`);
|
|
1412
|
-
|
|
2492
|
+
const instance = render(React2.createElement(ChatApp, chatOptions));
|
|
2493
|
+
try {
|
|
2494
|
+
await instance.waitUntilExit();
|
|
2495
|
+
} finally {
|
|
2496
|
+
disposeMcpClients(mcpClients);
|
|
2497
|
+
}
|
|
2498
|
+
if (options.memory) {
|
|
2499
|
+
process.stdout.write(
|
|
2500
|
+
`
|
|
2501
|
+
Session saved to ${session.file}. Resume with --memory ${session.file}
|
|
2502
|
+
`
|
|
2503
|
+
);
|
|
2504
|
+
} else {
|
|
2505
|
+
process.stdout.write(
|
|
2506
|
+
`
|
|
2507
|
+
Session saved. Resume with:
|
|
2508
|
+
agentskit chat --resume ${session.id}
|
|
2509
|
+
Or start fresh with:
|
|
2510
|
+
agentskit chat --new
|
|
2511
|
+
`
|
|
2512
|
+
);
|
|
2513
|
+
}
|
|
2514
|
+
});
|
|
1413
2515
|
}
|
|
1414
2516
|
function RunApp({ task, options }) {
|
|
1415
2517
|
const [status, setStatus] = useState("running");
|
|
@@ -1510,38 +2612,8 @@ function RunApp({ task, options }) {
|
|
|
1510
2612
|
] });
|
|
1511
2613
|
}
|
|
1512
2614
|
|
|
1513
|
-
// src/commands.ts
|
|
1514
|
-
function
|
|
1515
|
-
if (!config) return options;
|
|
1516
|
-
return {
|
|
1517
|
-
...options,
|
|
1518
|
-
// Config defaults — only apply if CLI flag wasn't set
|
|
1519
|
-
provider: options.provider !== "demo" ? options.provider : config.defaults?.provider ?? options.provider,
|
|
1520
|
-
model: options.model ?? config.defaults?.model
|
|
1521
|
-
};
|
|
1522
|
-
}
|
|
1523
|
-
function createCli() {
|
|
1524
|
-
const program = new Command();
|
|
1525
|
-
program.name("agentskit").description("AgentsKit CLI for chat demos and project bootstrapping.");
|
|
1526
|
-
program.command("chat").description("Start a terminal chat session.").option("--provider <provider>", "Provider to use", "demo").option("--model <model>", "Model name").option("--api-key <key>", "API key for the selected provider").option("--base-url <url>", "Override provider base URL").option("--system <prompt>", "System prompt").option("--memory <path>", "Path for file-based memory", ".agentskit-history.json").option("--tools <tools>", "Comma-separated tools: web_search,filesystem,shell").option("--skill <skills>", "Comma-separated skills: researcher,coder,planner,critic,summarizer").option("--memory-backend <backend>", "Memory backend: file (default), sqlite").option("--no-config", "Skip loading .agentskit.config.json").action(async (options) => {
|
|
1527
|
-
const config = options.config !== false ? await loadConfig() : void 0;
|
|
1528
|
-
const merged = mergeWithConfig(options, config);
|
|
1529
|
-
const chatOptions = {
|
|
1530
|
-
apiKey: merged.apiKey ?? options.apiKey,
|
|
1531
|
-
baseUrl: merged.baseUrl ?? options.baseUrl,
|
|
1532
|
-
provider: merged.provider,
|
|
1533
|
-
model: merged.model,
|
|
1534
|
-
system: options.system,
|
|
1535
|
-
memoryPath: options.memory,
|
|
1536
|
-
tools: options.tools,
|
|
1537
|
-
skill: options.skill,
|
|
1538
|
-
memoryBackend: options.memoryBackend,
|
|
1539
|
-
agentsKitConfig: config
|
|
1540
|
-
};
|
|
1541
|
-
process.stdout.write(`${renderChatHeader(chatOptions)}
|
|
1542
|
-
`);
|
|
1543
|
-
render(React3.createElement(ChatApp, chatOptions));
|
|
1544
|
-
});
|
|
2615
|
+
// src/commands/run.ts
|
|
2616
|
+
function registerRunCommand(program) {
|
|
1545
2617
|
program.command("run [task]").description("Execute an agent task and output the result.").option("--task <task>", "Task string (alternative to positional argument)").option("--provider <provider>", "Provider to use", "demo").option("--model <model>", "Model name").option("--api-key <key>", "API key for the selected provider").option("--base-url <url>", "Override provider base URL").option("--skill <skill>", "Single skill to use").option("--skills <skills>", "Comma-separated skills (composed together)").option("--tools <tools>", "Comma-separated tools: web_search,filesystem,shell").option("--memory <path>", "Path for memory persistence").option("--memory-backend <backend>", "Memory backend: file (default), sqlite").option("--system-prompt <prompt>", "System prompt").option("--max-steps <steps>", "Maximum agent steps", "10").option("--verbose", "Stream agent steps to stderr").option("--pretty", "Use rich Ink-based output").option("--no-config", "Skip loading .agentskit.config.json").action(async (positionalTask, options) => {
|
|
1546
2618
|
const task = options.task ?? positionalTask;
|
|
1547
2619
|
if (!task) {
|
|
@@ -1551,7 +2623,7 @@ function createCli() {
|
|
|
1551
2623
|
const config = options.config !== false ? await loadConfig() : void 0;
|
|
1552
2624
|
const merged = mergeWithConfig(options, config);
|
|
1553
2625
|
if (options.pretty) {
|
|
1554
|
-
render(
|
|
2626
|
+
render(React2.createElement(RunApp, { task, options }));
|
|
1555
2627
|
} else {
|
|
1556
2628
|
try {
|
|
1557
2629
|
await runAgent(task, { ...options, provider: merged.provider, model: merged.model });
|
|
@@ -1562,6 +2634,137 @@ function createCli() {
|
|
|
1562
2634
|
}
|
|
1563
2635
|
}
|
|
1564
2636
|
});
|
|
2637
|
+
}
|
|
2638
|
+
async function runInteractiveInit(defaults = {}) {
|
|
2639
|
+
process.stdout.write(`
|
|
2640
|
+
${kleur3.bold().green("\u25B2")} ${kleur3.bold("agentskit init")}
|
|
2641
|
+
`);
|
|
2642
|
+
process.stdout.write(kleur3.dim(" Generate a starter project \u2014 answer five questions.\n\n"));
|
|
2643
|
+
try {
|
|
2644
|
+
const targetDir = await input({
|
|
2645
|
+
message: "Project directory:",
|
|
2646
|
+
default: defaults.dir ?? "agentskit-app",
|
|
2647
|
+
validate: (value) => {
|
|
2648
|
+
if (!value.trim()) return "A directory name is required.";
|
|
2649
|
+
const abs = path.resolve(process.cwd(), value);
|
|
2650
|
+
if (existsSync(abs)) return `${value} already exists. Pick a different name.`;
|
|
2651
|
+
return true;
|
|
2652
|
+
}
|
|
2653
|
+
});
|
|
2654
|
+
const template = await select({
|
|
2655
|
+
message: "Template:",
|
|
2656
|
+
default: defaults.template ?? "react",
|
|
2657
|
+
choices: [
|
|
2658
|
+
{ name: "React chat (Vite + browser)", value: "react", description: "Streaming UI with @agentskit/react" },
|
|
2659
|
+
{ name: "Ink chat (terminal UI)", value: "ink", description: "Same chat but in your terminal" },
|
|
2660
|
+
{ name: "Runtime (headless agent, no UI)", value: "runtime", description: "Autonomous task \u2192 result" },
|
|
2661
|
+
{ name: "Multi-agent (planner + delegates)", value: "multi-agent", description: "Supervisor pattern, ready to extend" }
|
|
2662
|
+
]
|
|
2663
|
+
});
|
|
2664
|
+
const provider = await select({
|
|
2665
|
+
message: "LLM provider:",
|
|
2666
|
+
default: "demo",
|
|
2667
|
+
choices: [
|
|
2668
|
+
{ name: "Demo (no API key \u2014 deterministic stub)", value: "demo" },
|
|
2669
|
+
{ name: "OpenAI", value: "openai" },
|
|
2670
|
+
{ name: "Anthropic", value: "anthropic" },
|
|
2671
|
+
{ name: "Gemini", value: "gemini" },
|
|
2672
|
+
{ name: "Ollama (local, no key)", value: "ollama" }
|
|
2673
|
+
]
|
|
2674
|
+
});
|
|
2675
|
+
let tools = [];
|
|
2676
|
+
if (template !== "react") {
|
|
2677
|
+
tools = await checkbox({
|
|
2678
|
+
message: "Tools (space to toggle, enter to confirm):",
|
|
2679
|
+
choices: [
|
|
2680
|
+
{ name: "web_search", value: "web_search" },
|
|
2681
|
+
{ name: "filesystem", value: "filesystem" },
|
|
2682
|
+
{ name: "shell", value: "shell" }
|
|
2683
|
+
]
|
|
2684
|
+
});
|
|
2685
|
+
}
|
|
2686
|
+
const memory = await select({
|
|
2687
|
+
message: "Memory backend:",
|
|
2688
|
+
default: "none",
|
|
2689
|
+
choices: [
|
|
2690
|
+
{ name: "None (stateless)", value: "none" },
|
|
2691
|
+
{ name: "File (JSON on disk)", value: "file" },
|
|
2692
|
+
{ name: "SQLite (better-sqlite3)", value: "sqlite" }
|
|
2693
|
+
]
|
|
2694
|
+
});
|
|
2695
|
+
const packageManager = await select({
|
|
2696
|
+
message: "Package manager:",
|
|
2697
|
+
default: "pnpm",
|
|
2698
|
+
choices: [
|
|
2699
|
+
{ name: "pnpm", value: "pnpm" },
|
|
2700
|
+
{ name: "npm", value: "npm" },
|
|
2701
|
+
{ name: "yarn", value: "yarn" },
|
|
2702
|
+
{ name: "bun", value: "bun" }
|
|
2703
|
+
]
|
|
2704
|
+
});
|
|
2705
|
+
process.stdout.write("\n" + kleur3.dim(" Summary:\n"));
|
|
2706
|
+
process.stdout.write(kleur3.dim(` dir ${targetDir}
|
|
2707
|
+
`));
|
|
2708
|
+
process.stdout.write(kleur3.dim(` template ${template}
|
|
2709
|
+
`));
|
|
2710
|
+
process.stdout.write(kleur3.dim(` provider ${provider}
|
|
2711
|
+
`));
|
|
2712
|
+
if (tools.length) process.stdout.write(kleur3.dim(` tools ${tools.join(", ")}
|
|
2713
|
+
`));
|
|
2714
|
+
process.stdout.write(kleur3.dim(` memory ${memory}
|
|
2715
|
+
`));
|
|
2716
|
+
process.stdout.write(kleur3.dim(` pm ${packageManager}
|
|
2717
|
+
|
|
2718
|
+
`));
|
|
2719
|
+
const proceed = await confirm({ message: "Generate?", default: true });
|
|
2720
|
+
if (!proceed) {
|
|
2721
|
+
process.stdout.write(kleur3.yellow("Cancelled.\n"));
|
|
2722
|
+
return { cancelled: true, options: { targetDir, template, provider, tools, memory, packageManager } };
|
|
2723
|
+
}
|
|
2724
|
+
return {
|
|
2725
|
+
cancelled: false,
|
|
2726
|
+
options: {
|
|
2727
|
+
targetDir: path.resolve(process.cwd(), targetDir),
|
|
2728
|
+
template,
|
|
2729
|
+
provider,
|
|
2730
|
+
tools,
|
|
2731
|
+
memory,
|
|
2732
|
+
packageManager
|
|
2733
|
+
}
|
|
2734
|
+
};
|
|
2735
|
+
} catch (err) {
|
|
2736
|
+
if (err.name === "ExitPromptError") {
|
|
2737
|
+
process.stdout.write(kleur3.yellow("\nCancelled.\n"));
|
|
2738
|
+
return { cancelled: true, options: { targetDir: "", template: "react" } };
|
|
2739
|
+
}
|
|
2740
|
+
throw err;
|
|
2741
|
+
}
|
|
2742
|
+
}
|
|
2743
|
+
function printNextSteps(options) {
|
|
2744
|
+
const dir = path.relative(process.cwd(), options.targetDir) || ".";
|
|
2745
|
+
const pm = options.packageManager ?? "pnpm";
|
|
2746
|
+
const installCmd = pm === "npm" ? "npm install" : `${pm} install`;
|
|
2747
|
+
const runCmd = pm === "npm" ? "npm run dev" : `${pm} dev`;
|
|
2748
|
+
process.stdout.write("\n" + kleur3.green("\u2713 Created starter at ") + kleur3.bold(dir) + "\n\n");
|
|
2749
|
+
process.stdout.write(kleur3.bold("Next steps:\n\n"));
|
|
2750
|
+
process.stdout.write(` ${kleur3.cyan("cd")} ${dir}
|
|
2751
|
+
`);
|
|
2752
|
+
process.stdout.write(` ${kleur3.cyan(installCmd)}
|
|
2753
|
+
`);
|
|
2754
|
+
if (options.provider && options.provider !== "demo" && options.provider !== "ollama") {
|
|
2755
|
+
process.stdout.write(
|
|
2756
|
+
` ${kleur3.cyan("cp")} .env.example .env ${kleur3.dim("# add your API key")}
|
|
2757
|
+
`
|
|
2758
|
+
);
|
|
2759
|
+
}
|
|
2760
|
+
process.stdout.write(` ${kleur3.cyan(runCmd)}
|
|
2761
|
+
|
|
2762
|
+
`);
|
|
2763
|
+
process.stdout.write(kleur3.dim(" Docs: https://www.agentskit.io/docs\n\n"));
|
|
2764
|
+
}
|
|
2765
|
+
|
|
2766
|
+
// src/commands/init.ts
|
|
2767
|
+
function registerInitCommand(program) {
|
|
1565
2768
|
program.command("init").description("Generate a starter project. Run with no flags for interactive mode.").option("--template <template>", "Starter template (react|ink|runtime|multi-agent)").option("--dir <directory>", "Target directory", "agentskit-app").option("--provider <provider>", "LLM provider (openai|anthropic|gemini|ollama|demo)").option("--tools <tools>", "Comma-separated tools (web_search,filesystem,shell)").option("--memory <backend>", "Memory backend (none|file|sqlite)").option("--pm <packageManager>", "Package manager (pnpm|npm|yarn|bun)").option("-y, --yes", "Skip interactive prompts; use flag values + defaults").action(async (rawOptions) => {
|
|
1566
2769
|
const isCi = !process.stdout.isTTY || rawOptions.yes || rawOptions.template;
|
|
1567
2770
|
let resolved;
|
|
@@ -1595,6 +2798,10 @@ function createCli() {
|
|
|
1595
2798
|
printNextSteps(resolved);
|
|
1596
2799
|
}
|
|
1597
2800
|
});
|
|
2801
|
+
}
|
|
2802
|
+
|
|
2803
|
+
// src/commands/doctor.ts
|
|
2804
|
+
function registerDoctorCommand(program) {
|
|
1598
2805
|
program.command("doctor").description("Diagnose your AgentsKit environment.").option("--no-network", "Skip provider reachability checks").option(
|
|
1599
2806
|
"--providers <providers>",
|
|
1600
2807
|
"Comma-separated providers to check (default: openai,anthropic,gemini,ollama)"
|
|
@@ -1611,6 +2818,10 @@ function createCli() {
|
|
|
1611
2818
|
}
|
|
1612
2819
|
if (report.fail > 0) process.exit(1);
|
|
1613
2820
|
});
|
|
2821
|
+
}
|
|
2822
|
+
|
|
2823
|
+
// src/commands/dev.ts
|
|
2824
|
+
function registerDevCommand(program) {
|
|
1614
2825
|
program.command("dev [entry]").description("Run an entry file with hot-reload on file changes.").option("--watch <globs>", "Comma-separated glob patterns to watch").option("--ignore <globs>", "Comma-separated glob patterns to ignore").option("--debounce <ms>", "Debounce window before restart", "200").action(async (positional, options) => {
|
|
1615
2826
|
const entry = positional ?? "src/index.ts";
|
|
1616
2827
|
const watch = options.watch ? options.watch.split(",").map((s) => s.trim()).filter(Boolean) : void 0;
|
|
@@ -1629,6 +2840,55 @@ function createCli() {
|
|
|
1629
2840
|
process.exit(1);
|
|
1630
2841
|
}
|
|
1631
2842
|
});
|
|
2843
|
+
}
|
|
2844
|
+
function registerConfigCommand(program) {
|
|
2845
|
+
program.command("config").description("Show or scaffold the AgentsKit config.").argument(
|
|
2846
|
+
"[action]",
|
|
2847
|
+
'Action: "init" to create a template, "show" to print the merged config.',
|
|
2848
|
+
"show"
|
|
2849
|
+
).option("--global", "Write/read the global config at ~/.agentskit/config.json (default)").option("--local", "Write/read a project-level .agentskit.config.json in the current directory").option("--force", "Overwrite an existing config file").action(async (action, options) => {
|
|
2850
|
+
const isLocal = Boolean(options.local);
|
|
2851
|
+
const targetPath = isLocal ? path.join(process.cwd(), ".agentskit.config.json") : path.join(homedir(), ".agentskit", "config.json");
|
|
2852
|
+
if (action === "show") {
|
|
2853
|
+
const config = await loadConfig();
|
|
2854
|
+
process.stdout.write(JSON.stringify(config ?? {}, null, 2) + "\n");
|
|
2855
|
+
return;
|
|
2856
|
+
}
|
|
2857
|
+
if (action !== "init") {
|
|
2858
|
+
process.stderr.write(`Unknown action: ${action}. Use "init" or "show".
|
|
2859
|
+
`);
|
|
2860
|
+
process.exit(2);
|
|
2861
|
+
}
|
|
2862
|
+
if (existsSync(targetPath) && !options.force) {
|
|
2863
|
+
process.stderr.write(
|
|
2864
|
+
`Config already exists at ${targetPath}. Re-run with --force to overwrite.
|
|
2865
|
+
`
|
|
2866
|
+
);
|
|
2867
|
+
process.exit(1);
|
|
2868
|
+
}
|
|
2869
|
+
const template = {
|
|
2870
|
+
defaults: {
|
|
2871
|
+
provider: "openai",
|
|
2872
|
+
baseUrl: "https://openrouter.ai/api",
|
|
2873
|
+
apiKeyEnv: "OPENROUTER_API_KEY",
|
|
2874
|
+
model: "openai/gpt-oss-120b:free",
|
|
2875
|
+
tools: "web_search,fetch_url"
|
|
2876
|
+
}
|
|
2877
|
+
};
|
|
2878
|
+
mkdirSync(path.dirname(targetPath), { recursive: true });
|
|
2879
|
+
writeFileSync(targetPath, JSON.stringify(template, null, 2) + "\n");
|
|
2880
|
+
process.stdout.write(
|
|
2881
|
+
`Wrote ${targetPath}
|
|
2882
|
+
Edit it to taste, then run:
|
|
2883
|
+
agentskit chat
|
|
2884
|
+
(flags on the CLI still win over config values.)
|
|
2885
|
+
`
|
|
2886
|
+
);
|
|
2887
|
+
});
|
|
2888
|
+
}
|
|
2889
|
+
|
|
2890
|
+
// src/commands/tunnel.ts
|
|
2891
|
+
function registerTunnelCommand(program) {
|
|
1632
2892
|
program.command("tunnel <port>").description("Open a public URL pointing to a local port (great for webhooks).").option("--subdomain <name>", "Hint for a stable subdomain (provider may decline)").option("--host <host>", "Local hostname", "localhost").action(async (port, options) => {
|
|
1633
2893
|
const portNum = Number(port);
|
|
1634
2894
|
if (Number.isNaN(portNum) || portNum < 1 || portNum > 65535) {
|
|
@@ -1649,9 +2909,62 @@ function createCli() {
|
|
|
1649
2909
|
process.exit(1);
|
|
1650
2910
|
}
|
|
1651
2911
|
});
|
|
2912
|
+
}
|
|
2913
|
+
|
|
2914
|
+
// src/commands/rag.ts
|
|
2915
|
+
function registerRagCommand(program) {
|
|
2916
|
+
const rag = program.command("rag").description("Retrieval-augmented generation utilities.");
|
|
2917
|
+
rag.command("index").description("Index files referenced by config.rag.sources into the vector store.").option(
|
|
2918
|
+
"--source <glob>",
|
|
2919
|
+
"Glob to index (overrides config.rag.sources; repeatable)",
|
|
2920
|
+
(value, prev = []) => [...prev, value],
|
|
2921
|
+
[]
|
|
2922
|
+
).action(async (options) => {
|
|
2923
|
+
const config = await loadConfig();
|
|
2924
|
+
const rawConfig = config?.rag;
|
|
2925
|
+
const overrideSources = options.source;
|
|
2926
|
+
const ragConfig = {
|
|
2927
|
+
...rawConfig ?? {},
|
|
2928
|
+
sources: overrideSources.length > 0 ? overrideSources : rawConfig?.sources ?? []
|
|
2929
|
+
};
|
|
2930
|
+
if (!ragConfig.sources || ragConfig.sources.length === 0) {
|
|
2931
|
+
process.stderr.write("No RAG sources configured. Set config.rag.sources or pass --source <glob>.\n");
|
|
2932
|
+
process.exit(1);
|
|
2933
|
+
}
|
|
2934
|
+
try {
|
|
2935
|
+
const instance = buildRagFromConfig({ config: ragConfig });
|
|
2936
|
+
const result = await indexSources(instance, ragConfig);
|
|
2937
|
+
process.stdout.write(
|
|
2938
|
+
`Indexed ${result.documentCount} document${result.documentCount === 1 ? "" : "s"}.
|
|
2939
|
+
`
|
|
2940
|
+
);
|
|
2941
|
+
for (const source of result.sources) {
|
|
2942
|
+
process.stdout.write(` \u2022 ${source}
|
|
2943
|
+
`);
|
|
2944
|
+
}
|
|
2945
|
+
} catch (err) {
|
|
2946
|
+
process.stderr.write(`Error: ${err instanceof Error ? err.message : String(err)}
|
|
2947
|
+
`);
|
|
2948
|
+
process.exit(1);
|
|
2949
|
+
}
|
|
2950
|
+
});
|
|
2951
|
+
}
|
|
2952
|
+
|
|
2953
|
+
// src/commands/index.ts
|
|
2954
|
+
function createCli() {
|
|
2955
|
+
const program = new Command();
|
|
2956
|
+
program.name("agentskit").description("AgentsKit CLI for chat demos and project bootstrapping.");
|
|
2957
|
+
registerChatCommand(program);
|
|
2958
|
+
registerRunCommand(program);
|
|
2959
|
+
registerInitCommand(program);
|
|
2960
|
+
registerDoctorCommand(program);
|
|
2961
|
+
registerDevCommand(program);
|
|
2962
|
+
registerConfigCommand(program);
|
|
2963
|
+
registerTunnelCommand(program);
|
|
2964
|
+
registerRagCommand(program);
|
|
1652
2965
|
return program;
|
|
1653
2966
|
}
|
|
1654
2967
|
|
|
1655
|
-
export { ChatApp, createCli, loadConfig, renderChatHeader, renderReport, resolveChatProvider, runAgent, runDoctor, startDev, startTunnel, writeStarterProject };
|
|
1656
|
-
//# sourceMappingURL=chunk-
|
|
1657
|
-
//# sourceMappingURL=chunk-
|
|
2968
|
+
export { ChatApp, HookDispatcher, McpClient, applyPolicyToTool, applyPolicyToTools, bridgeMcpServers, buildRagFromConfig, computeCost, configHooksToHandlers, createCli, createOpenAiEmbedder, defaultPolicy, derivePreview, disposeMcpClients, evaluatePolicy, findLatestSession, findSession, forkSession, generateSessionId, getPricing, indexSources, listSessions, loadConfig, loadPlugins, mergePluginsIntoBundle, registerPricing, renameSession, renderChatHeader, renderReport, resolveChatProvider, resolveSession, runAgent, runDoctor, sessionFilePath, startDev, startTunnel, writeSessionMeta, writeStarterProject };
|
|
2969
|
+
//# sourceMappingURL=chunk-72XFU2X2.js.map
|
|
2970
|
+
//# sourceMappingURL=chunk-72XFU2X2.js.map
|