@openape/apes 1.25.1 → 1.26.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/dist/chunk-VHZEVW2N.js +998 -0
- package/dist/chunk-VHZEVW2N.js.map +1 -0
- package/dist/cli.js +7 -7
- package/dist/index.js +1 -1
- package/dist/{server-LT5BFMTP.js → server-64H5O3CG.js} +2 -2
- package/package.json +2 -2
- package/dist/chunk-L2V3CW5B.js +0 -605
- package/dist/chunk-L2V3CW5B.js.map +0 -1
- /package/dist/{server-LT5BFMTP.js.map → server-64H5O3CG.js.map} +0 -0
package/dist/chunk-L2V3CW5B.js
DELETED
|
@@ -1,605 +0,0 @@
|
|
|
1
|
-
#!/usr/bin/env node
|
|
2
|
-
|
|
3
|
-
// src/errors.ts
|
|
4
|
-
var CliError = class extends Error {
|
|
5
|
-
constructor(message, exitCode = 1) {
|
|
6
|
-
super(message);
|
|
7
|
-
this.exitCode = exitCode;
|
|
8
|
-
this.name = "CliError";
|
|
9
|
-
}
|
|
10
|
-
exitCode;
|
|
11
|
-
};
|
|
12
|
-
var CliExit = class extends Error {
|
|
13
|
-
constructor(exitCode = 0) {
|
|
14
|
-
super("");
|
|
15
|
-
this.exitCode = exitCode;
|
|
16
|
-
this.name = "CliExit";
|
|
17
|
-
}
|
|
18
|
-
exitCode;
|
|
19
|
-
};
|
|
20
|
-
|
|
21
|
-
// src/duration.ts
|
|
22
|
-
function parseDuration(value) {
|
|
23
|
-
const match = value.match(/^(\d+)\s*([smhd])$/);
|
|
24
|
-
if (!match) {
|
|
25
|
-
throw new Error(`Invalid duration format: "${value}". Use e.g. 30m, 1h, 7d`);
|
|
26
|
-
}
|
|
27
|
-
const amount = Number.parseInt(match[1], 10);
|
|
28
|
-
switch (match[2]) {
|
|
29
|
-
case "s":
|
|
30
|
-
return amount;
|
|
31
|
-
case "m":
|
|
32
|
-
return amount * 60;
|
|
33
|
-
case "h":
|
|
34
|
-
return amount * 3600;
|
|
35
|
-
case "d":
|
|
36
|
-
return amount * 86400;
|
|
37
|
-
default:
|
|
38
|
-
throw new Error(`Unknown duration unit: ${match[2]}`);
|
|
39
|
-
}
|
|
40
|
-
}
|
|
41
|
-
|
|
42
|
-
// src/lib/agent-tools/bash.ts
|
|
43
|
-
import { spawn } from "child_process";
|
|
44
|
-
var DEFAULT_TIMEOUT_MS = 5 * 60 * 1e3;
|
|
45
|
-
var MAX_STDIO_BYTES = 64 * 1024;
|
|
46
|
-
var BIN = "ape-shell";
|
|
47
|
-
function capStdio(s) {
|
|
48
|
-
const buf = Buffer.from(s, "utf8");
|
|
49
|
-
if (buf.byteLength <= MAX_STDIO_BYTES) return s;
|
|
50
|
-
return `${buf.subarray(0, MAX_STDIO_BYTES).toString("utf8")}
|
|
51
|
-
[truncated to ${MAX_STDIO_BYTES} bytes]`;
|
|
52
|
-
}
|
|
53
|
-
var bashTools = [
|
|
54
|
-
{
|
|
55
|
-
name: "bash",
|
|
56
|
-
description: "Run a shell command on the agent host. Every invocation goes through the OpenApe DDISA grant cycle \u2014 auto-approved if the owner has a matching YOLO scope, otherwise the owner gets a push notification to approve. Runs as the agent's macOS user, so file/network access is limited to what that user can see. Returns stdout, stderr, and exit code. For repeated command patterns ask the owner to set up a YOLO scope so approvals don't pile up.",
|
|
57
|
-
parameters: {
|
|
58
|
-
type: "object",
|
|
59
|
-
properties: {
|
|
60
|
-
cmd: {
|
|
61
|
-
type: "string",
|
|
62
|
-
description: "Shell command to run, e.g. `ls -la ~/Documents`, `git status`, `curl -fsSL https://example.com`. The whole string is passed to `bash -c`; quote internally as needed."
|
|
63
|
-
},
|
|
64
|
-
timeout_ms: {
|
|
65
|
-
type: "number",
|
|
66
|
-
description: "Wall-clock cap for the whole approval-and-run cycle in milliseconds. Default 300000 (5 min). Approval waits count against this budget."
|
|
67
|
-
}
|
|
68
|
-
},
|
|
69
|
-
required: ["cmd"]
|
|
70
|
-
},
|
|
71
|
-
execute: async (args) => {
|
|
72
|
-
const a = args;
|
|
73
|
-
if (typeof a.cmd !== "string" || a.cmd.trim() === "") {
|
|
74
|
-
throw new Error("cmd must be a non-empty string");
|
|
75
|
-
}
|
|
76
|
-
const timeout = typeof a.timeout_ms === "number" && a.timeout_ms > 0 ? a.timeout_ms : DEFAULT_TIMEOUT_MS;
|
|
77
|
-
return await new Promise((resolveResult) => {
|
|
78
|
-
const child = spawn(BIN, ["-c", a.cmd], {
|
|
79
|
-
env: { ...process.env, APE_WAIT: "1" },
|
|
80
|
-
stdio: ["ignore", "pipe", "pipe"]
|
|
81
|
-
});
|
|
82
|
-
let stdout = "";
|
|
83
|
-
let stderr = "";
|
|
84
|
-
let timedOut = false;
|
|
85
|
-
let spawnError = null;
|
|
86
|
-
child.stdout.on("data", (chunk) => {
|
|
87
|
-
stdout += chunk.toString("utf8");
|
|
88
|
-
});
|
|
89
|
-
child.stderr.on("data", (chunk) => {
|
|
90
|
-
stderr += chunk.toString("utf8");
|
|
91
|
-
});
|
|
92
|
-
child.on("error", (err) => {
|
|
93
|
-
spawnError = err;
|
|
94
|
-
});
|
|
95
|
-
const timer = setTimeout(() => {
|
|
96
|
-
timedOut = true;
|
|
97
|
-
child.kill("SIGTERM");
|
|
98
|
-
setTimeout(() => {
|
|
99
|
-
try {
|
|
100
|
-
child.kill("SIGKILL");
|
|
101
|
-
} catch {
|
|
102
|
-
}
|
|
103
|
-
}, 5e3);
|
|
104
|
-
}, timeout);
|
|
105
|
-
child.on("close", (code) => {
|
|
106
|
-
clearTimeout(timer);
|
|
107
|
-
if (spawnError) {
|
|
108
|
-
resolveResult({
|
|
109
|
-
error: spawnError.message,
|
|
110
|
-
hint: `Could not exec '${BIN}'. The agent host needs @openape/apes installed globally so ape-shell is on PATH.`
|
|
111
|
-
});
|
|
112
|
-
return;
|
|
113
|
-
}
|
|
114
|
-
resolveResult({
|
|
115
|
-
stdout: capStdio(stdout),
|
|
116
|
-
stderr: capStdio(stderr),
|
|
117
|
-
exit_code: code ?? -1,
|
|
118
|
-
...timedOut ? { timed_out: true } : {}
|
|
119
|
-
});
|
|
120
|
-
});
|
|
121
|
-
});
|
|
122
|
-
}
|
|
123
|
-
}
|
|
124
|
-
];
|
|
125
|
-
|
|
126
|
-
// src/lib/agent-tools/file.ts
|
|
127
|
-
import { mkdirSync, readFileSync, writeFileSync } from "fs";
|
|
128
|
-
import { homedir } from "os";
|
|
129
|
-
import { dirname, normalize, resolve } from "path";
|
|
130
|
-
var MAX_BYTES = 1024 * 1024;
|
|
131
|
-
function jailPath(input) {
|
|
132
|
-
if (typeof input !== "string" || input === "") {
|
|
133
|
-
throw new Error("path must be a non-empty string");
|
|
134
|
-
}
|
|
135
|
-
const home = homedir();
|
|
136
|
-
const candidate = input.startsWith("~/") ? resolve(home, input.slice(2)) : input.startsWith("/") ? normalize(input) : resolve(home, input);
|
|
137
|
-
if (candidate !== home && !candidate.startsWith(`${home}/`)) {
|
|
138
|
-
throw new Error(`path "${input}" resolves outside the agent's home`);
|
|
139
|
-
}
|
|
140
|
-
return candidate;
|
|
141
|
-
}
|
|
142
|
-
var fileTools = [
|
|
143
|
-
{
|
|
144
|
-
name: "file.read",
|
|
145
|
-
description: "Read a UTF-8 file from the agent's home directory ($HOME). Capped at 1MB. Path traversal blocked.",
|
|
146
|
-
parameters: {
|
|
147
|
-
type: "object",
|
|
148
|
-
properties: {
|
|
149
|
-
path: { type: "string", description: "Path relative to $HOME (or absolute under $HOME). `..` segments are rejected." }
|
|
150
|
-
},
|
|
151
|
-
required: ["path"]
|
|
152
|
-
},
|
|
153
|
-
execute: async (args) => {
|
|
154
|
-
const a = args;
|
|
155
|
-
const p = jailPath(a.path);
|
|
156
|
-
const content = readFileSync(p, "utf8");
|
|
157
|
-
if (Buffer.byteLength(content, "utf8") > MAX_BYTES) {
|
|
158
|
-
return { path: p, truncated: true, content: content.slice(0, MAX_BYTES) };
|
|
159
|
-
}
|
|
160
|
-
return { path: p, truncated: false, content };
|
|
161
|
-
}
|
|
162
|
-
},
|
|
163
|
-
{
|
|
164
|
-
name: "file.write",
|
|
165
|
-
description: "Write a UTF-8 file under the agent's home directory. Creates parent dirs as needed. 1MB max.",
|
|
166
|
-
parameters: {
|
|
167
|
-
type: "object",
|
|
168
|
-
properties: {
|
|
169
|
-
path: { type: "string", description: "Path relative to $HOME (or absolute under $HOME)." },
|
|
170
|
-
content: { type: "string", description: "File body. Existing files are overwritten." }
|
|
171
|
-
},
|
|
172
|
-
required: ["path", "content"]
|
|
173
|
-
},
|
|
174
|
-
execute: async (args) => {
|
|
175
|
-
const a = args;
|
|
176
|
-
if (typeof a.content !== "string") throw new Error("content must be a string");
|
|
177
|
-
if (Buffer.byteLength(a.content, "utf8") > MAX_BYTES) {
|
|
178
|
-
throw new Error(`content exceeds ${MAX_BYTES} byte cap`);
|
|
179
|
-
}
|
|
180
|
-
const p = jailPath(a.path);
|
|
181
|
-
mkdirSync(dirname(p), { recursive: true });
|
|
182
|
-
writeFileSync(p, a.content, { encoding: "utf8" });
|
|
183
|
-
return { path: p, bytes: Buffer.byteLength(a.content, "utf8") };
|
|
184
|
-
}
|
|
185
|
-
}
|
|
186
|
-
];
|
|
187
|
-
|
|
188
|
-
// src/lib/agent-tools/http.ts
|
|
189
|
-
var MAX_BYTES2 = 1024 * 1024;
|
|
190
|
-
var FORBIDDEN_HEADERS = /* @__PURE__ */ new Set([
|
|
191
|
-
"host",
|
|
192
|
-
"authorization",
|
|
193
|
-
"cookie",
|
|
194
|
-
"connection",
|
|
195
|
-
"transfer-encoding",
|
|
196
|
-
"upgrade",
|
|
197
|
-
"proxy-authorization"
|
|
198
|
-
]);
|
|
199
|
-
function sanitizeHeaders(input) {
|
|
200
|
-
if (!input || typeof input !== "object") return {};
|
|
201
|
-
const out = {};
|
|
202
|
-
for (const [k, v] of Object.entries(input)) {
|
|
203
|
-
if (typeof v !== "string") continue;
|
|
204
|
-
if (FORBIDDEN_HEADERS.has(k.toLowerCase())) continue;
|
|
205
|
-
out[k] = v;
|
|
206
|
-
}
|
|
207
|
-
return out;
|
|
208
|
-
}
|
|
209
|
-
async function readCappedBody(res) {
|
|
210
|
-
const buf = new Uint8Array(MAX_BYTES2 + 1);
|
|
211
|
-
let written = 0;
|
|
212
|
-
const reader = res.body?.getReader();
|
|
213
|
-
if (!reader) return await res.text();
|
|
214
|
-
while (true) {
|
|
215
|
-
const { value, done } = await reader.read();
|
|
216
|
-
if (done) break;
|
|
217
|
-
if (written + value.byteLength > MAX_BYTES2) {
|
|
218
|
-
buf.set(value.subarray(0, MAX_BYTES2 - written), written);
|
|
219
|
-
written = MAX_BYTES2;
|
|
220
|
-
try {
|
|
221
|
-
await reader.cancel();
|
|
222
|
-
} catch {
|
|
223
|
-
}
|
|
224
|
-
break;
|
|
225
|
-
}
|
|
226
|
-
buf.set(value, written);
|
|
227
|
-
written += value.byteLength;
|
|
228
|
-
}
|
|
229
|
-
return new TextDecoder().decode(buf.subarray(0, written));
|
|
230
|
-
}
|
|
231
|
-
var httpTools = [
|
|
232
|
-
{
|
|
233
|
-
name: "http.get",
|
|
234
|
-
description: "GET an HTTPS URL and return the response body (capped at 1MB). Useful for reading public APIs, RSS feeds, web pages.",
|
|
235
|
-
parameters: {
|
|
236
|
-
type: "object",
|
|
237
|
-
properties: {
|
|
238
|
-
url: { type: "string", description: "Absolute HTTPS URL." },
|
|
239
|
-
headers: { type: "object", description: "Optional headers (Host, Authorization, Cookie are stripped)." }
|
|
240
|
-
},
|
|
241
|
-
required: ["url"]
|
|
242
|
-
},
|
|
243
|
-
execute: async (args) => {
|
|
244
|
-
const a = args;
|
|
245
|
-
if (typeof a.url !== "string" || !a.url.startsWith("http")) {
|
|
246
|
-
throw new Error("url must be an http(s) URL");
|
|
247
|
-
}
|
|
248
|
-
const res = await fetch(a.url, { method: "GET", headers: sanitizeHeaders(a.headers) });
|
|
249
|
-
const body = await readCappedBody(res);
|
|
250
|
-
return { status: res.status, headers: Object.fromEntries(res.headers), body };
|
|
251
|
-
}
|
|
252
|
-
},
|
|
253
|
-
{
|
|
254
|
-
name: "http.post",
|
|
255
|
-
description: "POST JSON to an HTTPS URL and return the response body (capped at 1MB).",
|
|
256
|
-
parameters: {
|
|
257
|
-
type: "object",
|
|
258
|
-
properties: {
|
|
259
|
-
url: { type: "string", description: "Absolute HTTPS URL." },
|
|
260
|
-
body: { description: "JSON-serialisable payload." },
|
|
261
|
-
headers: { type: "object", description: "Optional headers (Host, Authorization, Cookie are stripped)." }
|
|
262
|
-
},
|
|
263
|
-
required: ["url", "body"]
|
|
264
|
-
},
|
|
265
|
-
execute: async (args) => {
|
|
266
|
-
const a = args;
|
|
267
|
-
if (typeof a.url !== "string" || !a.url.startsWith("http")) {
|
|
268
|
-
throw new Error("url must be an http(s) URL");
|
|
269
|
-
}
|
|
270
|
-
const res = await fetch(a.url, {
|
|
271
|
-
method: "POST",
|
|
272
|
-
headers: { "content-type": "application/json", ...sanitizeHeaders(a.headers) },
|
|
273
|
-
body: JSON.stringify(a.body)
|
|
274
|
-
});
|
|
275
|
-
const body = await readCappedBody(res);
|
|
276
|
-
return { status: res.status, headers: Object.fromEntries(res.headers), body };
|
|
277
|
-
}
|
|
278
|
-
}
|
|
279
|
-
];
|
|
280
|
-
|
|
281
|
-
// src/lib/agent-tools/mail.ts
|
|
282
|
-
import { execFileSync } from "child_process";
|
|
283
|
-
function o365(args) {
|
|
284
|
-
try {
|
|
285
|
-
return execFileSync("o365-cli", args, { encoding: "utf8", stdio: ["ignore", "pipe", "pipe"] });
|
|
286
|
-
} catch (err) {
|
|
287
|
-
const e = err;
|
|
288
|
-
if (e.code === "ENOENT") {
|
|
289
|
-
throw new Error("o365-cli is not installed on this agent host");
|
|
290
|
-
}
|
|
291
|
-
const stderr = typeof e.stderr === "string" ? e.stderr : e.stderr?.toString("utf8");
|
|
292
|
-
throw new Error(`o365-cli failed: ${stderr ?? e.message ?? err}`);
|
|
293
|
-
}
|
|
294
|
-
}
|
|
295
|
-
var mailTools = [
|
|
296
|
-
{
|
|
297
|
-
name: "mail.list",
|
|
298
|
-
description: "List recent inbox messages via o365-cli. Optional `unread_only` and `limit`.",
|
|
299
|
-
parameters: {
|
|
300
|
-
type: "object",
|
|
301
|
-
properties: {
|
|
302
|
-
limit: { type: "integer", minimum: 1, maximum: 100, default: 20 },
|
|
303
|
-
unread_only: { type: "boolean", default: false }
|
|
304
|
-
},
|
|
305
|
-
required: []
|
|
306
|
-
},
|
|
307
|
-
execute: async (args) => {
|
|
308
|
-
const a = args ?? {};
|
|
309
|
-
const argv = ["mail", "list", "--json", "--limit", String(a.limit ?? 20)];
|
|
310
|
-
if (a.unread_only) argv.push("--unread");
|
|
311
|
-
const out = o365(argv);
|
|
312
|
-
try {
|
|
313
|
-
return JSON.parse(out);
|
|
314
|
-
} catch {
|
|
315
|
-
return { raw: out };
|
|
316
|
-
}
|
|
317
|
-
}
|
|
318
|
-
},
|
|
319
|
-
{
|
|
320
|
-
name: "mail.search",
|
|
321
|
-
description: "Search the inbox via o365-cli using a free-form query string.",
|
|
322
|
-
parameters: {
|
|
323
|
-
type: "object",
|
|
324
|
-
properties: {
|
|
325
|
-
q: { type: "string" },
|
|
326
|
-
limit: { type: "integer", minimum: 1, maximum: 100, default: 20 }
|
|
327
|
-
},
|
|
328
|
-
required: ["q"]
|
|
329
|
-
},
|
|
330
|
-
execute: async (args) => {
|
|
331
|
-
const a = args;
|
|
332
|
-
if (typeof a.q !== "string" || a.q.length === 0) throw new Error("q is required");
|
|
333
|
-
const argv = ["mail", "search", a.q, "--json", "--limit", String(a.limit ?? 20)];
|
|
334
|
-
const out = o365(argv);
|
|
335
|
-
try {
|
|
336
|
-
return JSON.parse(out);
|
|
337
|
-
} catch {
|
|
338
|
-
return { raw: out };
|
|
339
|
-
}
|
|
340
|
-
}
|
|
341
|
-
}
|
|
342
|
-
];
|
|
343
|
-
|
|
344
|
-
// src/lib/agent-tools/tasks.ts
|
|
345
|
-
import { execFileSync as execFileSync2 } from "child_process";
|
|
346
|
-
function ape(args) {
|
|
347
|
-
try {
|
|
348
|
-
return execFileSync2("ape-tasks", args, { encoding: "utf8", stdio: ["ignore", "pipe", "pipe"] });
|
|
349
|
-
} catch (err) {
|
|
350
|
-
const e = err;
|
|
351
|
-
const stderr = typeof e.stderr === "string" ? e.stderr : e.stderr?.toString("utf8");
|
|
352
|
-
throw new Error(`ape-tasks failed: ${stderr ?? e.message ?? err}`);
|
|
353
|
-
}
|
|
354
|
-
}
|
|
355
|
-
var tasksTools = [
|
|
356
|
-
{
|
|
357
|
-
name: "tasks.list",
|
|
358
|
-
description: "List the owner's open ape-tasks (the user's personal task list at tasks.openape.ai).",
|
|
359
|
-
parameters: {
|
|
360
|
-
type: "object",
|
|
361
|
-
properties: {
|
|
362
|
-
status: { type: "string", enum: ["open", "doing", "done", "archived"] },
|
|
363
|
-
team_id: { type: "string" }
|
|
364
|
-
},
|
|
365
|
-
required: []
|
|
366
|
-
},
|
|
367
|
-
execute: async (args) => {
|
|
368
|
-
const a = args ?? {};
|
|
369
|
-
const argv = ["list", "--json"];
|
|
370
|
-
if (a.status) argv.push("--status", a.status);
|
|
371
|
-
if (a.team_id) argv.push("--team", a.team_id);
|
|
372
|
-
const out = ape(argv);
|
|
373
|
-
try {
|
|
374
|
-
return JSON.parse(out);
|
|
375
|
-
} catch {
|
|
376
|
-
return { raw: out };
|
|
377
|
-
}
|
|
378
|
-
}
|
|
379
|
-
},
|
|
380
|
-
{
|
|
381
|
-
name: "tasks.create",
|
|
382
|
-
description: "Create a new ape-task on the owner's task list at tasks.openape.ai.",
|
|
383
|
-
parameters: {
|
|
384
|
-
type: "object",
|
|
385
|
-
properties: {
|
|
386
|
-
title: { type: "string" },
|
|
387
|
-
notes: { type: "string" },
|
|
388
|
-
priority: { type: "string", enum: ["low", "med", "high"] },
|
|
389
|
-
due_at: { type: "string", description: "ISO date or +Nh/+Nd shorthand." }
|
|
390
|
-
},
|
|
391
|
-
required: ["title"]
|
|
392
|
-
},
|
|
393
|
-
execute: async (args) => {
|
|
394
|
-
const a = args;
|
|
395
|
-
const argv = ["new", "--title", a.title, "--json"];
|
|
396
|
-
if (a.notes) argv.push("--notes", a.notes);
|
|
397
|
-
if (a.priority) argv.push("--priority", a.priority);
|
|
398
|
-
if (a.due_at) argv.push("--due", a.due_at);
|
|
399
|
-
const out = ape(argv);
|
|
400
|
-
try {
|
|
401
|
-
return JSON.parse(out);
|
|
402
|
-
} catch {
|
|
403
|
-
return { raw: out };
|
|
404
|
-
}
|
|
405
|
-
}
|
|
406
|
-
}
|
|
407
|
-
];
|
|
408
|
-
|
|
409
|
-
// src/lib/agent-tools/time.ts
|
|
410
|
-
var timeTools = [
|
|
411
|
-
{
|
|
412
|
-
name: "time.now",
|
|
413
|
-
description: "Returns the current UTC date and time as ISO 8601 plus epoch seconds. No inputs.",
|
|
414
|
-
parameters: { type: "object", properties: {}, required: [] },
|
|
415
|
-
execute: async () => {
|
|
416
|
-
const now = /* @__PURE__ */ new Date();
|
|
417
|
-
return {
|
|
418
|
-
iso: now.toISOString(),
|
|
419
|
-
epoch_seconds: Math.floor(now.getTime() / 1e3),
|
|
420
|
-
timezone_offset_minutes: -now.getTimezoneOffset()
|
|
421
|
-
};
|
|
422
|
-
}
|
|
423
|
-
}
|
|
424
|
-
];
|
|
425
|
-
|
|
426
|
-
// src/lib/agent-tools/index.ts
|
|
427
|
-
var ALL_TOOLS = [
|
|
428
|
-
...timeTools,
|
|
429
|
-
...httpTools,
|
|
430
|
-
...fileTools,
|
|
431
|
-
...tasksTools,
|
|
432
|
-
...mailTools,
|
|
433
|
-
...bashTools
|
|
434
|
-
];
|
|
435
|
-
var TOOLS = Object.fromEntries(
|
|
436
|
-
ALL_TOOLS.map((t) => [t.name, t])
|
|
437
|
-
);
|
|
438
|
-
function taskTools(names) {
|
|
439
|
-
const out = [];
|
|
440
|
-
const missing = [];
|
|
441
|
-
for (const name of names) {
|
|
442
|
-
const tool = TOOLS[name];
|
|
443
|
-
if (!tool) missing.push(name);
|
|
444
|
-
else out.push(tool);
|
|
445
|
-
}
|
|
446
|
-
if (missing.length > 0) {
|
|
447
|
-
throw new Error(`unknown tool(s): ${missing.join(", ")}`);
|
|
448
|
-
}
|
|
449
|
-
return out;
|
|
450
|
-
}
|
|
451
|
-
function asOpenAiTools(tools) {
|
|
452
|
-
return tools.map((t) => ({
|
|
453
|
-
type: "function",
|
|
454
|
-
function: { name: wireToolName(t.name), description: t.description, parameters: t.parameters }
|
|
455
|
-
}));
|
|
456
|
-
}
|
|
457
|
-
function wireToolName(local) {
|
|
458
|
-
return local.replace(/\./g, "_");
|
|
459
|
-
}
|
|
460
|
-
function localToolName(wire) {
|
|
461
|
-
for (const t of Object.values(TOOLS)) {
|
|
462
|
-
if (wireToolName(t.name) === wire) return t.name;
|
|
463
|
-
}
|
|
464
|
-
return wire;
|
|
465
|
-
}
|
|
466
|
-
|
|
467
|
-
// src/lib/agent-runtime.ts
|
|
468
|
-
function previewJson(value, max = 500) {
|
|
469
|
-
let s;
|
|
470
|
-
try {
|
|
471
|
-
s = JSON.stringify(value);
|
|
472
|
-
} catch {
|
|
473
|
-
s = String(value);
|
|
474
|
-
}
|
|
475
|
-
return s.length > max ? `${s.slice(0, max)}\u2026` : s;
|
|
476
|
-
}
|
|
477
|
-
async function runLoop(opts) {
|
|
478
|
-
const fetchFn = opts.fetchImpl ?? fetch;
|
|
479
|
-
const trace = [];
|
|
480
|
-
const messages = [
|
|
481
|
-
{ role: "system", content: opts.systemPrompt },
|
|
482
|
-
...opts.history ?? [],
|
|
483
|
-
{ role: "user", content: opts.userMessage }
|
|
484
|
-
];
|
|
485
|
-
const tools = asOpenAiTools(opts.tools);
|
|
486
|
-
for (let step = 1; step <= opts.maxSteps; step++) {
|
|
487
|
-
const res = await fetchFn(`${opts.config.apiBase}/chat/completions`, {
|
|
488
|
-
method: "POST",
|
|
489
|
-
headers: {
|
|
490
|
-
"authorization": `Bearer ${opts.config.apiKey}`,
|
|
491
|
-
"content-type": "application/json"
|
|
492
|
-
},
|
|
493
|
-
body: JSON.stringify({
|
|
494
|
-
model: opts.config.model,
|
|
495
|
-
messages,
|
|
496
|
-
...tools.length > 0 ? { tools, tool_choice: "auto" } : {}
|
|
497
|
-
})
|
|
498
|
-
});
|
|
499
|
-
if (!res.ok) {
|
|
500
|
-
const text = await res.text().catch(() => "");
|
|
501
|
-
throw new Error(`LiteLLM ${res.status}: ${text.slice(0, 500)}`);
|
|
502
|
-
}
|
|
503
|
-
const data = await res.json();
|
|
504
|
-
const choice = data.choices?.[0];
|
|
505
|
-
if (!choice) throw new Error("LiteLLM response had no choices");
|
|
506
|
-
const assistant = choice.message;
|
|
507
|
-
messages.push(assistant);
|
|
508
|
-
if (assistant.content) opts.handlers?.onTextDelta?.(assistant.content);
|
|
509
|
-
trace.push({
|
|
510
|
-
step,
|
|
511
|
-
type: "assistant",
|
|
512
|
-
preview: previewJson({ content: assistant.content, tool_calls: assistant.tool_calls?.length ?? 0 })
|
|
513
|
-
});
|
|
514
|
-
if (!assistant.tool_calls || assistant.tool_calls.length === 0) {
|
|
515
|
-
const result2 = {
|
|
516
|
-
status: "ok",
|
|
517
|
-
finalMessage: assistant.content,
|
|
518
|
-
stepCount: step,
|
|
519
|
-
trace
|
|
520
|
-
};
|
|
521
|
-
opts.handlers?.onDone?.(result2);
|
|
522
|
-
return result2;
|
|
523
|
-
}
|
|
524
|
-
for (const call of assistant.tool_calls) {
|
|
525
|
-
const wireName = call.function.name;
|
|
526
|
-
const localName = localToolName(wireName);
|
|
527
|
-
const tool = opts.tools.find((t) => t.name === localName);
|
|
528
|
-
let parsedArgs;
|
|
529
|
-
try {
|
|
530
|
-
parsedArgs = JSON.parse(call.function.arguments);
|
|
531
|
-
} catch {
|
|
532
|
-
parsedArgs = {};
|
|
533
|
-
}
|
|
534
|
-
opts.handlers?.onToolCall?.({ name: localName, args: parsedArgs });
|
|
535
|
-
trace.push({ step, type: "tool_call", tool: localName, preview: previewJson(parsedArgs) });
|
|
536
|
-
let result2;
|
|
537
|
-
let isError = false;
|
|
538
|
-
if (!tool) {
|
|
539
|
-
result2 = `unknown tool: ${localName}`;
|
|
540
|
-
isError = true;
|
|
541
|
-
} else {
|
|
542
|
-
try {
|
|
543
|
-
result2 = await tool.execute(parsedArgs);
|
|
544
|
-
} catch (err) {
|
|
545
|
-
result2 = err?.message ?? String(err);
|
|
546
|
-
isError = true;
|
|
547
|
-
}
|
|
548
|
-
}
|
|
549
|
-
if (isError) {
|
|
550
|
-
opts.handlers?.onToolError?.({ name: localName, error: String(result2) });
|
|
551
|
-
trace.push({ step, type: "tool_error", tool: localName, preview: previewJson(result2) });
|
|
552
|
-
} else {
|
|
553
|
-
opts.handlers?.onToolResult?.({ name: localName, result: result2 });
|
|
554
|
-
trace.push({ step, type: "tool_result", tool: localName, preview: previewJson(result2) });
|
|
555
|
-
}
|
|
556
|
-
messages.push({
|
|
557
|
-
role: "tool",
|
|
558
|
-
tool_call_id: call.id,
|
|
559
|
-
name: wireToolName(localName),
|
|
560
|
-
content: typeof result2 === "string" ? result2 : JSON.stringify(result2)
|
|
561
|
-
});
|
|
562
|
-
}
|
|
563
|
-
}
|
|
564
|
-
const result = {
|
|
565
|
-
status: "error",
|
|
566
|
-
finalMessage: `max_steps (${opts.maxSteps}) reached without completion`,
|
|
567
|
-
stepCount: opts.maxSteps,
|
|
568
|
-
trace
|
|
569
|
-
};
|
|
570
|
-
opts.handlers?.onDone?.(result);
|
|
571
|
-
return result;
|
|
572
|
-
}
|
|
573
|
-
var RPC_SESSION_TTL_MS = 60 * 60 * 1e3;
|
|
574
|
-
var RpcSessionMap = class {
|
|
575
|
-
sessions = /* @__PURE__ */ new Map();
|
|
576
|
-
get(id) {
|
|
577
|
-
const s = this.sessions.get(id);
|
|
578
|
-
if (s) s.lastTouched = Date.now();
|
|
579
|
-
return s;
|
|
580
|
-
}
|
|
581
|
-
put(id, s) {
|
|
582
|
-
s.lastTouched = Date.now();
|
|
583
|
-
this.sessions.set(id, s);
|
|
584
|
-
}
|
|
585
|
-
evictStale() {
|
|
586
|
-
const cutoff = Date.now() - RPC_SESSION_TTL_MS;
|
|
587
|
-
for (const [k, v] of this.sessions) {
|
|
588
|
-
if (v.lastTouched < cutoff) this.sessions.delete(k);
|
|
589
|
-
}
|
|
590
|
-
}
|
|
591
|
-
size() {
|
|
592
|
-
return this.sessions.size;
|
|
593
|
-
}
|
|
594
|
-
};
|
|
595
|
-
|
|
596
|
-
export {
|
|
597
|
-
CliError,
|
|
598
|
-
CliExit,
|
|
599
|
-
parseDuration,
|
|
600
|
-
TOOLS,
|
|
601
|
-
taskTools,
|
|
602
|
-
runLoop,
|
|
603
|
-
RpcSessionMap
|
|
604
|
-
};
|
|
605
|
-
//# sourceMappingURL=chunk-L2V3CW5B.js.map
|