tracelet-cli 0.1.0 → 0.1.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/bin.js
CHANGED
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
|
|
3
3
|
// src/program.ts
|
|
4
|
-
import { confirm, select } from "@inquirer/prompts";
|
|
4
|
+
import { confirm as confirm2, select as select2 } from "@inquirer/prompts";
|
|
5
|
+
import { readFileSync } from "fs";
|
|
5
6
|
import { Command, Option } from "commander";
|
|
6
7
|
|
|
7
8
|
// src/agents.ts
|
|
@@ -19,16 +20,38 @@ function launchEnv(env = process.env) {
|
|
|
19
20
|
return { ...env, NO_PROXY: noProxy, no_proxy: noProxy };
|
|
20
21
|
}
|
|
21
22
|
async function hasBin(command) {
|
|
22
|
-
const paths = process.env.PATH?.split(delimiter) ?? [];
|
|
23
|
+
const paths = command.includes("/") || command.includes("\\") ? [""] : process.env.PATH?.split(delimiter) ?? [];
|
|
24
|
+
const names = process.platform === "win32" && !/\.[^\\/]+$/.test(command) ? [command, ...(process.env.PATHEXT ?? ".COM;.EXE;.BAT;.CMD").split(";").map((ext) => `${command}${ext}`)] : [command];
|
|
23
25
|
for (const path of paths) {
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
26
|
+
for (const name of names) {
|
|
27
|
+
try {
|
|
28
|
+
await access(join(path, name), constants.X_OK);
|
|
29
|
+
return true;
|
|
30
|
+
} catch {
|
|
31
|
+
}
|
|
28
32
|
}
|
|
29
33
|
}
|
|
30
34
|
return false;
|
|
31
35
|
}
|
|
36
|
+
function customAdapter(id, config) {
|
|
37
|
+
return {
|
|
38
|
+
id,
|
|
39
|
+
label: config.label,
|
|
40
|
+
command: config.command,
|
|
41
|
+
protocol: config.protocol,
|
|
42
|
+
detect: () => hasBin(config.command),
|
|
43
|
+
upstream: async () => config.upstream,
|
|
44
|
+
launch: (proxyUrl2, args) => {
|
|
45
|
+
const env = launchEnv();
|
|
46
|
+
const inject = config.inject.type === "env" ? [] : config.inject.args.map((arg) => arg.replaceAll("{baseUrl}", proxyUrl2));
|
|
47
|
+
if (config.inject.type === "env") {
|
|
48
|
+
const name = config.inject.name ?? (config.protocol === "anthropic" ? "ANTHROPIC_BASE_URL" : "OPENAI_BASE_URL");
|
|
49
|
+
env[name] = proxyUrl2;
|
|
50
|
+
}
|
|
51
|
+
return { command: config.command, args: [...inject, ...config.args, ...args], env };
|
|
52
|
+
}
|
|
53
|
+
};
|
|
54
|
+
}
|
|
32
55
|
async function usesChatGpt() {
|
|
33
56
|
try {
|
|
34
57
|
const result = await exec("codex", ["login", "status"]);
|
|
@@ -138,6 +161,13 @@ async function clearData(root) {
|
|
|
138
161
|
await mkdir(runs, { recursive: true });
|
|
139
162
|
}
|
|
140
163
|
|
|
164
|
+
// src/custom-menu.ts
|
|
165
|
+
import { confirm, input, select } from "@inquirer/prompts";
|
|
166
|
+
|
|
167
|
+
// src/settings.ts
|
|
168
|
+
import { mkdir as mkdir2, readFile as readFile2, writeFile } from "fs/promises";
|
|
169
|
+
import { dirname } from "path";
|
|
170
|
+
|
|
141
171
|
// src/paths.ts
|
|
142
172
|
import { existsSync } from "fs";
|
|
143
173
|
import { homedir as homedir3 } from "os";
|
|
@@ -155,6 +185,340 @@ function dashboardDir() {
|
|
|
155
185
|
return existsSync(bundled) ? bundled : workspace;
|
|
156
186
|
}
|
|
157
187
|
|
|
188
|
+
// src/settings.ts
|
|
189
|
+
function readAgent(value, id) {
|
|
190
|
+
if (!/^custom-[a-z0-9]+(?:-[a-z0-9]+)*$/.test(id) || !isObject(value)) {
|
|
191
|
+
throw new Error(`Invalid custom agent: ${id}`);
|
|
192
|
+
}
|
|
193
|
+
const { label, command, args, protocol, upstream, inject } = value;
|
|
194
|
+
if (typeof label !== "string" || !label.trim() || typeof command !== "string" || !command.trim() || !Array.isArray(args) || !args.every((arg) => typeof arg === "string") || protocol !== "anthropic" && protocol !== "openai" || typeof upstream !== "string" || !isObject(inject) || inject.type !== "env" && inject.type !== "args") {
|
|
195
|
+
throw new Error(`Invalid custom agent: ${id}`);
|
|
196
|
+
}
|
|
197
|
+
try {
|
|
198
|
+
if (!["http:", "https:"].includes(new URL(upstream).protocol)) {
|
|
199
|
+
throw new Error("Unsupported protocol");
|
|
200
|
+
}
|
|
201
|
+
} catch {
|
|
202
|
+
throw new Error(`Invalid upstream URL for custom agent: ${id}`);
|
|
203
|
+
}
|
|
204
|
+
if (inject.type === "env") {
|
|
205
|
+
if (inject.name !== void 0 && (typeof inject.name !== "string" || !/^[A-Za-z_][A-Za-z0-9_]*$/.test(inject.name))) {
|
|
206
|
+
throw new Error(`Invalid environment variable for custom agent: ${id}`);
|
|
207
|
+
}
|
|
208
|
+
return { label: label.trim(), command: command.trim(), args, protocol, upstream, inject: {
|
|
209
|
+
type: "env",
|
|
210
|
+
...inject.name ? { name: inject.name } : {}
|
|
211
|
+
} };
|
|
212
|
+
}
|
|
213
|
+
if (!Array.isArray(inject.args) || !inject.args.every((arg) => typeof arg === "string") || !inject.args.some((arg) => arg.includes("{baseUrl}"))) {
|
|
214
|
+
throw new Error(`Invalid Base URL argument template for custom agent: ${id}`);
|
|
215
|
+
}
|
|
216
|
+
return {
|
|
217
|
+
label: label.trim(),
|
|
218
|
+
command: command.trim(),
|
|
219
|
+
args,
|
|
220
|
+
protocol,
|
|
221
|
+
upstream,
|
|
222
|
+
inject: { type: "args", args: inject.args }
|
|
223
|
+
};
|
|
224
|
+
}
|
|
225
|
+
function isObject(value) {
|
|
226
|
+
return value !== null && typeof value === "object" && !Array.isArray(value);
|
|
227
|
+
}
|
|
228
|
+
async function readRaw(path) {
|
|
229
|
+
try {
|
|
230
|
+
const value = JSON.parse(await readFile2(path, "utf8"));
|
|
231
|
+
if (!isObject(value)) {
|
|
232
|
+
throw new Error("Settings root must be an object");
|
|
233
|
+
}
|
|
234
|
+
return value;
|
|
235
|
+
} catch (error) {
|
|
236
|
+
if (error?.code === "ENOENT") {
|
|
237
|
+
return {};
|
|
238
|
+
}
|
|
239
|
+
throw new Error(`Failed to read Tracelet settings: ${path}`, { cause: error });
|
|
240
|
+
}
|
|
241
|
+
}
|
|
242
|
+
function readProxy(value, name) {
|
|
243
|
+
if (!isObject(value) || value.mode !== "direct" && value.mode !== "system") {
|
|
244
|
+
throw new Error(`Invalid proxy mode: ${name}`);
|
|
245
|
+
}
|
|
246
|
+
return { mode: value.mode };
|
|
247
|
+
}
|
|
248
|
+
function parseSettings(value, path) {
|
|
249
|
+
if (value.customAgents !== void 0 && !isObject(value.customAgents)) {
|
|
250
|
+
throw new Error(`Invalid custom agents: ${path}`);
|
|
251
|
+
}
|
|
252
|
+
const customAgents = {};
|
|
253
|
+
if (isObject(value.customAgents)) {
|
|
254
|
+
for (const [id, config] of Object.entries(value.customAgents)) {
|
|
255
|
+
customAgents[id] = readAgent(config, id);
|
|
256
|
+
}
|
|
257
|
+
}
|
|
258
|
+
if (value.proxy !== void 0 && !isObject(value.proxy)) {
|
|
259
|
+
throw new Error(`Invalid proxy settings: ${path}`);
|
|
260
|
+
}
|
|
261
|
+
const proxy = isObject(value.proxy) ? value.proxy : {};
|
|
262
|
+
const defaultConfig = proxy.default === void 0 ? { mode: "direct" } : readProxy(proxy.default, "default");
|
|
263
|
+
if (proxy.agents !== void 0 && !isObject(proxy.agents)) {
|
|
264
|
+
throw new Error(`Invalid agent proxy settings: ${path}`);
|
|
265
|
+
}
|
|
266
|
+
const agents2 = {};
|
|
267
|
+
if (isObject(proxy.agents)) {
|
|
268
|
+
for (const [agentId, config] of Object.entries(proxy.agents)) {
|
|
269
|
+
agents2[agentId] = readProxy(config, agentId);
|
|
270
|
+
}
|
|
271
|
+
}
|
|
272
|
+
return { customAgents, proxy: { default: defaultConfig, agents: agents2 } };
|
|
273
|
+
}
|
|
274
|
+
async function writeSettings(path, value) {
|
|
275
|
+
await mkdir2(dirname(path), { recursive: true });
|
|
276
|
+
await writeFile(path, `${JSON.stringify(value, null, 2)}
|
|
277
|
+
`, { encoding: "utf8", mode: 384 });
|
|
278
|
+
}
|
|
279
|
+
async function loadSettings(path = settingsFile()) {
|
|
280
|
+
return parseSettings(await readRaw(path), path);
|
|
281
|
+
}
|
|
282
|
+
function proxyMode(settings, agentId) {
|
|
283
|
+
return settings.proxy.agents[agentId]?.mode ?? settings.proxy.default.mode;
|
|
284
|
+
}
|
|
285
|
+
async function saveProxy(target, mode, path = settingsFile()) {
|
|
286
|
+
const value = await readRaw(path);
|
|
287
|
+
const current = parseSettings(value, path);
|
|
288
|
+
const agents2 = { ...current.proxy.agents };
|
|
289
|
+
let defaultConfig = current.proxy.default;
|
|
290
|
+
if (target === "all") {
|
|
291
|
+
defaultConfig = { mode };
|
|
292
|
+
for (const agentId of Object.keys(agents2)) {
|
|
293
|
+
delete agents2[agentId];
|
|
294
|
+
}
|
|
295
|
+
} else if (mode === defaultConfig.mode) {
|
|
296
|
+
delete agents2[target];
|
|
297
|
+
} else {
|
|
298
|
+
agents2[target] = { mode };
|
|
299
|
+
}
|
|
300
|
+
const next = { ...value, proxy: { default: defaultConfig, agents: agents2 } };
|
|
301
|
+
await writeSettings(path, next);
|
|
302
|
+
}
|
|
303
|
+
async function saveAgent(id, agent, path = settingsFile()) {
|
|
304
|
+
const value = await readRaw(path);
|
|
305
|
+
const current = parseSettings(value, path);
|
|
306
|
+
const customAgents = { ...current.customAgents, [id]: readAgent(agent, id) };
|
|
307
|
+
await writeSettings(path, { ...value, customAgents });
|
|
308
|
+
}
|
|
309
|
+
async function removeAgent(id, path = settingsFile()) {
|
|
310
|
+
const value = await readRaw(path);
|
|
311
|
+
const current = parseSettings(value, path);
|
|
312
|
+
const customAgents = { ...current.customAgents };
|
|
313
|
+
const proxyAgents = { ...current.proxy.agents };
|
|
314
|
+
delete customAgents[id];
|
|
315
|
+
delete proxyAgents[id];
|
|
316
|
+
await writeSettings(path, {
|
|
317
|
+
...value,
|
|
318
|
+
customAgents,
|
|
319
|
+
proxy: { default: current.proxy.default, agents: proxyAgents }
|
|
320
|
+
});
|
|
321
|
+
}
|
|
322
|
+
|
|
323
|
+
// src/custom-menu.ts
|
|
324
|
+
function baseEnv(protocol) {
|
|
325
|
+
return protocol === "anthropic" ? "ANTHROPIC_BASE_URL" : "OPENAI_BASE_URL";
|
|
326
|
+
}
|
|
327
|
+
function required(value) {
|
|
328
|
+
return value.trim() ? true : "This field is required.";
|
|
329
|
+
}
|
|
330
|
+
function parseArgs(value) {
|
|
331
|
+
const args = JSON.parse(value);
|
|
332
|
+
if (!Array.isArray(args) || !args.every((arg) => typeof arg === "string")) {
|
|
333
|
+
throw new Error("Enter a JSON array of strings.");
|
|
334
|
+
}
|
|
335
|
+
return args;
|
|
336
|
+
}
|
|
337
|
+
async function askArgs(message, args, needsUrl = false) {
|
|
338
|
+
const value = await input({
|
|
339
|
+
message,
|
|
340
|
+
default: JSON.stringify(args),
|
|
341
|
+
validate: (text) => {
|
|
342
|
+
try {
|
|
343
|
+
const parsed = parseArgs(text);
|
|
344
|
+
if (needsUrl && !parsed.some((arg) => arg.includes("{baseUrl}"))) {
|
|
345
|
+
return "Include {baseUrl} in one argument.";
|
|
346
|
+
}
|
|
347
|
+
return true;
|
|
348
|
+
} catch {
|
|
349
|
+
return 'Enter a JSON array of strings, for example ["--profile","work"].';
|
|
350
|
+
}
|
|
351
|
+
}
|
|
352
|
+
});
|
|
353
|
+
return parseArgs(value);
|
|
354
|
+
}
|
|
355
|
+
function validUrl(value) {
|
|
356
|
+
try {
|
|
357
|
+
return ["http:", "https:"].includes(new URL(value).protocol) ? true : "Use an http:// or https:// URL.";
|
|
358
|
+
} catch {
|
|
359
|
+
return "Enter a valid upstream URL.";
|
|
360
|
+
}
|
|
361
|
+
}
|
|
362
|
+
async function askProtocol(current) {
|
|
363
|
+
return select({
|
|
364
|
+
message: "Protocol",
|
|
365
|
+
choices: [
|
|
366
|
+
{ name: "Anthropic Messages", value: "anthropic" },
|
|
367
|
+
{ name: "OpenAI Responses", value: "openai" }
|
|
368
|
+
],
|
|
369
|
+
default: current
|
|
370
|
+
});
|
|
371
|
+
}
|
|
372
|
+
async function askInject(protocol, current) {
|
|
373
|
+
const type = await select({
|
|
374
|
+
message: "How should Tracelet pass its local Base URL?",
|
|
375
|
+
choices: [
|
|
376
|
+
{ name: "Environment variable (recommended)", value: "env" },
|
|
377
|
+
{ name: "Command arguments", value: "args" }
|
|
378
|
+
],
|
|
379
|
+
default: current?.type ?? "env"
|
|
380
|
+
});
|
|
381
|
+
if (type === "env") {
|
|
382
|
+
const fallback = baseEnv(protocol);
|
|
383
|
+
const name = await input({
|
|
384
|
+
message: `Environment variable (blank uses ${fallback})`,
|
|
385
|
+
default: current?.type === "env" ? current.name ?? "" : "",
|
|
386
|
+
validate: (value) => !value || /^[A-Za-z_][A-Za-z0-9_]*$/.test(value) ? true : "Enter a valid environment variable name."
|
|
387
|
+
});
|
|
388
|
+
return { type: "env", ...name ? { name } : {} };
|
|
389
|
+
}
|
|
390
|
+
const args = await askArgs(
|
|
391
|
+
"Base URL arguments (JSON array containing {baseUrl})",
|
|
392
|
+
current?.type === "args" ? current.args : ["--base-url", "{baseUrl}"],
|
|
393
|
+
true
|
|
394
|
+
);
|
|
395
|
+
return { type: "args", args };
|
|
396
|
+
}
|
|
397
|
+
function nextId(label, used) {
|
|
398
|
+
const slug = label.toLowerCase().normalize("NFKD").replace(/[^a-z0-9]+/g, "-").replace(/^-|-$/g, "") || "agent";
|
|
399
|
+
const base = `custom-${slug}`;
|
|
400
|
+
let id = base;
|
|
401
|
+
let count = 2;
|
|
402
|
+
while (used[id]) {
|
|
403
|
+
id = `${base}-${count++}`;
|
|
404
|
+
}
|
|
405
|
+
return id;
|
|
406
|
+
}
|
|
407
|
+
function showAgent(id, agent) {
|
|
408
|
+
console.log(`
|
|
409
|
+
${agent.label} (${id})`);
|
|
410
|
+
console.log(` Command: ${JSON.stringify([agent.command, ...agent.args])}`);
|
|
411
|
+
console.log(` Protocol: ${agent.protocol}`);
|
|
412
|
+
console.log(` Upstream: ${agent.upstream}`);
|
|
413
|
+
console.log(` Base URL: ${agent.inject.type === "env" ? `env ${agent.inject.name ?? baseEnv(agent.protocol)}` : `args ${JSON.stringify(agent.inject.args)}`}
|
|
414
|
+
`);
|
|
415
|
+
}
|
|
416
|
+
async function addAgent() {
|
|
417
|
+
const settings = await loadSettings();
|
|
418
|
+
const label = await input({ message: "Agent name", validate: required });
|
|
419
|
+
const id = nextId(label, settings.customAgents);
|
|
420
|
+
const command = await input({ message: "Executable command", validate: required });
|
|
421
|
+
const args = await askArgs("Default arguments (JSON array)", []);
|
|
422
|
+
const protocol = await askProtocol();
|
|
423
|
+
const inject = await askInject(protocol);
|
|
424
|
+
const envName = inject.type === "env" ? inject.name ?? baseEnv(protocol) : baseEnv(protocol);
|
|
425
|
+
const upstream = await input({
|
|
426
|
+
message: "Original upstream URL",
|
|
427
|
+
default: process.env[envName] ?? (protocol === "anthropic" ? "https://api.anthropic.com" : "https://api.openai.com/v1"),
|
|
428
|
+
validate: validUrl
|
|
429
|
+
});
|
|
430
|
+
const agent = { label, command, args, protocol, upstream, inject };
|
|
431
|
+
showAgent(id, agent);
|
|
432
|
+
if (!await confirm({ message: "Save this agent?", default: true })) {
|
|
433
|
+
return void 0;
|
|
434
|
+
}
|
|
435
|
+
await saveAgent(id, agent);
|
|
436
|
+
console.log(`Agent saved: ${id}`);
|
|
437
|
+
return await confirm({ message: "Launch it now?", default: false }) ? id : void 0;
|
|
438
|
+
}
|
|
439
|
+
async function chooseAgent(message) {
|
|
440
|
+
const entries = Object.entries((await loadSettings()).customAgents);
|
|
441
|
+
if (entries.length === 0) {
|
|
442
|
+
console.log("No custom agents configured.");
|
|
443
|
+
return void 0;
|
|
444
|
+
}
|
|
445
|
+
return select({
|
|
446
|
+
message,
|
|
447
|
+
choices: [
|
|
448
|
+
...entries.map(([id, agent]) => ({ name: `${agent.label} (${id})`, value: id })),
|
|
449
|
+
{ name: "Back", value: "back" }
|
|
450
|
+
]
|
|
451
|
+
});
|
|
452
|
+
}
|
|
453
|
+
async function editAgent() {
|
|
454
|
+
const id = await chooseAgent("Select an agent to edit");
|
|
455
|
+
if (!id || id === "back") return;
|
|
456
|
+
const draft = structuredClone((await loadSettings()).customAgents[id]);
|
|
457
|
+
showAgent(id, draft);
|
|
458
|
+
for (; ; ) {
|
|
459
|
+
const field = await select({
|
|
460
|
+
message: "Select a field to edit",
|
|
461
|
+
// 一次展示全部字段,并在首尾停止方向键导航。
|
|
462
|
+
pageSize: 8,
|
|
463
|
+
loop: false,
|
|
464
|
+
choices: [
|
|
465
|
+
{ name: `Name: ${draft.label}`, value: "label" },
|
|
466
|
+
{ name: `Command: ${draft.command}`, value: "command" },
|
|
467
|
+
{ name: `Default arguments: ${JSON.stringify(draft.args)}`, value: "args" },
|
|
468
|
+
{ name: `Protocol: ${draft.protocol}`, value: "protocol" },
|
|
469
|
+
{ name: `Upstream: ${draft.upstream}`, value: "upstream" },
|
|
470
|
+
{ name: "Base URL injection", value: "inject" },
|
|
471
|
+
{ name: "Save changes", value: "save" },
|
|
472
|
+
{ name: "Cancel", value: "cancel" }
|
|
473
|
+
]
|
|
474
|
+
});
|
|
475
|
+
if (field === "cancel") return;
|
|
476
|
+
if (field === "save") {
|
|
477
|
+
await saveAgent(id, draft);
|
|
478
|
+
console.log(`Agent updated: ${id}`);
|
|
479
|
+
return;
|
|
480
|
+
}
|
|
481
|
+
if (field === "label") draft.label = await input({ message: "Agent name", default: draft.label, validate: required });
|
|
482
|
+
if (field === "command") draft.command = await input({ message: "Executable command", default: draft.command, validate: required });
|
|
483
|
+
if (field === "args") draft.args = await askArgs("Default arguments (JSON array)", draft.args);
|
|
484
|
+
if (field === "protocol") draft.protocol = await askProtocol(draft.protocol);
|
|
485
|
+
if (field === "upstream") draft.upstream = await input({ message: "Original upstream URL", default: draft.upstream, validate: validUrl });
|
|
486
|
+
if (field === "inject") draft.inject = await askInject(draft.protocol, draft.inject);
|
|
487
|
+
}
|
|
488
|
+
}
|
|
489
|
+
async function deleteAgent() {
|
|
490
|
+
const id = await chooseAgent("Select an agent to delete");
|
|
491
|
+
if (!id || id === "back") return;
|
|
492
|
+
const agent = (await loadSettings()).customAgents[id];
|
|
493
|
+
if (!await confirm({ message: `Delete ${agent.label} (${id})? Recorded history will be kept.`, default: false })) {
|
|
494
|
+
console.log("Delete cancelled.");
|
|
495
|
+
return;
|
|
496
|
+
}
|
|
497
|
+
await removeAgent(id);
|
|
498
|
+
console.log(`Agent deleted: ${id}. Recorded history was kept.`);
|
|
499
|
+
}
|
|
500
|
+
async function manageAgents() {
|
|
501
|
+
if (!process.stdin.isTTY) throw new Error("The agent command requires an interactive terminal.");
|
|
502
|
+
for (; ; ) {
|
|
503
|
+
const action = await select({
|
|
504
|
+
message: "Manage custom agents",
|
|
505
|
+
choices: [
|
|
506
|
+
{ name: "Add agent", value: "add" },
|
|
507
|
+
{ name: "Edit agent", value: "edit" },
|
|
508
|
+
{ name: "Delete agent", value: "delete" },
|
|
509
|
+
{ name: "Back", value: "back" }
|
|
510
|
+
]
|
|
511
|
+
});
|
|
512
|
+
if (action === "back") return void 0;
|
|
513
|
+
if (action === "add") {
|
|
514
|
+
const id = await addAgent();
|
|
515
|
+
if (id) return id;
|
|
516
|
+
}
|
|
517
|
+
if (action === "edit") await editAgent();
|
|
518
|
+
if (action === "delete") await deleteAgent();
|
|
519
|
+
}
|
|
520
|
+
}
|
|
521
|
+
|
|
158
522
|
// ../../packages/shared/src/headers.ts
|
|
159
523
|
var secrets = /* @__PURE__ */ new Set([
|
|
160
524
|
"authorization",
|
|
@@ -229,8 +593,8 @@ function responseHeaders(headers) {
|
|
|
229
593
|
}
|
|
230
594
|
|
|
231
595
|
// ../../packages/proxy/src/target.ts
|
|
232
|
-
function targetUrl(route,
|
|
233
|
-
const local = new URL(
|
|
596
|
+
function targetUrl(route, input2) {
|
|
597
|
+
const local = new URL(input2, "http://127.0.0.1");
|
|
234
598
|
const target = new URL(route.upstream);
|
|
235
599
|
const suffix = local.pathname.slice(route.prefix.length).replace(/^\//, "");
|
|
236
600
|
const basePath = target.pathname.replace(/\/$/, "");
|
|
@@ -1685,11 +2049,11 @@ function parseOutputFormat(params, content) {
|
|
|
1685
2049
|
}
|
|
1686
2050
|
|
|
1687
2051
|
// ../../node_modules/.pnpm/@anthropic-ai+sdk@0.125.0/node_modules/@anthropic-ai/sdk/_vendor/partial-json-parser/parser.mjs
|
|
1688
|
-
var tokenize = (
|
|
2052
|
+
var tokenize = (input2) => {
|
|
1689
2053
|
let current = 0;
|
|
1690
2054
|
let tokens = [];
|
|
1691
|
-
while (current <
|
|
1692
|
-
let char =
|
|
2055
|
+
while (current < input2.length) {
|
|
2056
|
+
let char = input2[current];
|
|
1693
2057
|
if (char === "\\") {
|
|
1694
2058
|
current++;
|
|
1695
2059
|
continue;
|
|
@@ -1745,26 +2109,26 @@ var tokenize = (input) => {
|
|
|
1745
2109
|
if (char === '"') {
|
|
1746
2110
|
let value = "";
|
|
1747
2111
|
let danglingQuote = false;
|
|
1748
|
-
char =
|
|
2112
|
+
char = input2[++current];
|
|
1749
2113
|
while (char !== '"') {
|
|
1750
|
-
if (current ===
|
|
2114
|
+
if (current === input2.length) {
|
|
1751
2115
|
danglingQuote = true;
|
|
1752
2116
|
break;
|
|
1753
2117
|
}
|
|
1754
2118
|
if (char === "\\") {
|
|
1755
2119
|
current++;
|
|
1756
|
-
if (current ===
|
|
2120
|
+
if (current === input2.length) {
|
|
1757
2121
|
danglingQuote = true;
|
|
1758
2122
|
break;
|
|
1759
2123
|
}
|
|
1760
|
-
value += char +
|
|
1761
|
-
char =
|
|
2124
|
+
value += char + input2[current];
|
|
2125
|
+
char = input2[++current];
|
|
1762
2126
|
} else {
|
|
1763
2127
|
value += char;
|
|
1764
|
-
char =
|
|
2128
|
+
char = input2[++current];
|
|
1765
2129
|
}
|
|
1766
2130
|
}
|
|
1767
|
-
char =
|
|
2131
|
+
char = input2[++current];
|
|
1768
2132
|
if (!danglingQuote) {
|
|
1769
2133
|
tokens.push({
|
|
1770
2134
|
type: "string",
|
|
@@ -1783,13 +2147,13 @@ var tokenize = (input) => {
|
|
|
1783
2147
|
let value = "";
|
|
1784
2148
|
if (char === "-") {
|
|
1785
2149
|
value += char;
|
|
1786
|
-
char =
|
|
2150
|
+
char = input2[++current];
|
|
1787
2151
|
}
|
|
1788
2152
|
while (char && (NUMBERS.test(char) || char === "." || // exponent marker, e.g. `1e10` or `1.5E-9`
|
|
1789
2153
|
char === "e" || char === "E" || // exponent sign, only valid immediately after the exponent marker
|
|
1790
2154
|
(char === "-" || char === "+") && (value[value.length - 1] === "e" || value[value.length - 1] === "E"))) {
|
|
1791
2155
|
value += char;
|
|
1792
|
-
char =
|
|
2156
|
+
char = input2[++current];
|
|
1793
2157
|
}
|
|
1794
2158
|
tokens.push({
|
|
1795
2159
|
type: "number",
|
|
@@ -1801,11 +2165,11 @@ var tokenize = (input) => {
|
|
|
1801
2165
|
if (char && LETTERS.test(char)) {
|
|
1802
2166
|
let value = "";
|
|
1803
2167
|
while (char && LETTERS.test(char)) {
|
|
1804
|
-
if (current ===
|
|
2168
|
+
if (current === input2.length) {
|
|
1805
2169
|
break;
|
|
1806
2170
|
}
|
|
1807
2171
|
value += char;
|
|
1808
|
-
char =
|
|
2172
|
+
char = input2[++current];
|
|
1809
2173
|
}
|
|
1810
2174
|
if (value == "true" || value == "false" || value === "null") {
|
|
1811
2175
|
tokens.push({
|
|
@@ -1904,7 +2268,7 @@ var generate = (tokens) => {
|
|
|
1904
2268
|
});
|
|
1905
2269
|
return output;
|
|
1906
2270
|
};
|
|
1907
|
-
var partialParse = (
|
|
2271
|
+
var partialParse = (input2) => JSON.parse(generate(unstrip(strip(tokenize(input2)))));
|
|
1908
2272
|
|
|
1909
2273
|
// ../../node_modules/.pnpm/@anthropic-ai+sdk@0.125.0/node_modules/@anthropic-ai/sdk/internal/message-stream-utils.mjs
|
|
1910
2274
|
var JSON_BUF_PROPERTY = "__json_buf";
|
|
@@ -1915,17 +2279,17 @@ function withLazyInput(prev, jsonBuf) {
|
|
|
1915
2279
|
next[key] = prev[key];
|
|
1916
2280
|
}
|
|
1917
2281
|
Object.defineProperty(next, JSON_BUF_PROPERTY, { value: jsonBuf, enumerable: false, writable: true });
|
|
1918
|
-
let
|
|
2282
|
+
let input2;
|
|
1919
2283
|
let parsed = false;
|
|
1920
2284
|
Object.defineProperty(next, "input", {
|
|
1921
2285
|
enumerable: true,
|
|
1922
2286
|
configurable: true,
|
|
1923
2287
|
get() {
|
|
1924
2288
|
if (!parsed) {
|
|
1925
|
-
|
|
2289
|
+
input2 = jsonBuf ? partialParse(jsonBuf) : {};
|
|
1926
2290
|
parsed = true;
|
|
1927
2291
|
}
|
|
1928
|
-
return
|
|
2292
|
+
return input2;
|
|
1929
2293
|
}
|
|
1930
2294
|
});
|
|
1931
2295
|
return next;
|
|
@@ -6716,17 +7080,17 @@ var Recorder = class {
|
|
|
6716
7080
|
this.onChange = onChange;
|
|
6717
7081
|
}
|
|
6718
7082
|
/** 开始捕获一次模型 HTTP 请求。 */
|
|
6719
|
-
async start(
|
|
7083
|
+
async start(input2) {
|
|
6720
7084
|
const meta = {
|
|
6721
7085
|
id: makeId("ex"),
|
|
6722
|
-
runId:
|
|
6723
|
-
sessionId: `run:${
|
|
7086
|
+
runId: input2.runId,
|
|
7087
|
+
sessionId: `run:${input2.runId}`,
|
|
6724
7088
|
sessionSource: "run",
|
|
6725
|
-
protocol:
|
|
6726
|
-
method:
|
|
6727
|
-
path:
|
|
7089
|
+
protocol: input2.protocol,
|
|
7090
|
+
method: input2.method,
|
|
7091
|
+
path: input2.path,
|
|
6728
7092
|
stream: false,
|
|
6729
|
-
requestHeaders: redactHeaders(
|
|
7093
|
+
requestHeaders: redactHeaders(input2.headers),
|
|
6730
7094
|
startedAt: nowIso(),
|
|
6731
7095
|
requestBytes: 0,
|
|
6732
7096
|
responseBytes: 0,
|
|
@@ -6748,12 +7112,12 @@ var Recorder = class {
|
|
|
6748
7112
|
};
|
|
6749
7113
|
|
|
6750
7114
|
// ../../packages/storage/src/reader.ts
|
|
6751
|
-
import { readFile as
|
|
7115
|
+
import { readFile as readFile4, readdir } from "fs/promises";
|
|
6752
7116
|
import { join as join4 } from "path";
|
|
6753
7117
|
|
|
6754
7118
|
// ../../packages/storage/src/files.ts
|
|
6755
|
-
import { mkdir as
|
|
6756
|
-
import { dirname, join as join3 } from "path";
|
|
7119
|
+
import { mkdir as mkdir3, readFile as readFile3, rename, writeFile as writeFile2 } from "fs/promises";
|
|
7120
|
+
import { dirname as dirname2, join as join3 } from "path";
|
|
6757
7121
|
function dateKey(iso) {
|
|
6758
7122
|
return iso.slice(0, 10);
|
|
6759
7123
|
}
|
|
@@ -6762,18 +7126,18 @@ function getRunDir(root, iso, runId) {
|
|
|
6762
7126
|
}
|
|
6763
7127
|
async function writeJson(path, value) {
|
|
6764
7128
|
const temp = `${path}.tmp`;
|
|
6765
|
-
await
|
|
6766
|
-
await
|
|
7129
|
+
await mkdir3(dirname2(path), { recursive: true });
|
|
7130
|
+
await writeFile2(temp, `${JSON.stringify(value, null, 2)}
|
|
6767
7131
|
`, "utf8");
|
|
6768
7132
|
await rename(temp, path);
|
|
6769
7133
|
}
|
|
6770
7134
|
async function readJson(path) {
|
|
6771
|
-
return JSON.parse(await
|
|
7135
|
+
return JSON.parse(await readFile3(path, "utf8"));
|
|
6772
7136
|
}
|
|
6773
7137
|
async function readJsonl(path) {
|
|
6774
7138
|
let text = "";
|
|
6775
7139
|
try {
|
|
6776
|
-
text = await
|
|
7140
|
+
text = await readFile3(path, "utf8");
|
|
6777
7141
|
} catch {
|
|
6778
7142
|
return [];
|
|
6779
7143
|
}
|
|
@@ -6835,6 +7199,28 @@ function contentText(value) {
|
|
|
6835
7199
|
const parts = array(value).map((item) => record(item)).filter((item) => item?.type === "text" || item?.type === "input_text" || item?.type === "output_text").map((item) => string(item?.text)).filter((text) => typeof text === "string" && !isContext(text));
|
|
6836
7200
|
return string(parts.join("\n\n"));
|
|
6837
7201
|
}
|
|
7202
|
+
function promptText(value) {
|
|
7203
|
+
if (typeof value === "string") {
|
|
7204
|
+
return string(value);
|
|
7205
|
+
}
|
|
7206
|
+
const parts = array(value).map((item) => record(item)).filter((item) => item?.type === "text" || item?.type === "input_text" || item?.type === "output_text").map((item) => string(item?.text)).filter((text) => Boolean(text));
|
|
7207
|
+
return string(parts.join("\n\n"));
|
|
7208
|
+
}
|
|
7209
|
+
function systemText(exchange) {
|
|
7210
|
+
const body = record(exchange?.request);
|
|
7211
|
+
const parts = [promptText(body?.system), promptText(body?.instructions)].filter((text) => Boolean(text));
|
|
7212
|
+
for (const raw of array(body?.input)) {
|
|
7213
|
+
const item = record(raw);
|
|
7214
|
+
if (item?.type !== "message" || item?.role !== "system" && item?.role !== "developer") {
|
|
7215
|
+
continue;
|
|
7216
|
+
}
|
|
7217
|
+
const text = promptText(item?.content);
|
|
7218
|
+
if (text) {
|
|
7219
|
+
parts.push(text);
|
|
7220
|
+
}
|
|
7221
|
+
}
|
|
7222
|
+
return string(parts.join("\n\n"));
|
|
7223
|
+
}
|
|
6838
7224
|
function reasoningText(value) {
|
|
6839
7225
|
const summary = array(value?.summary).map((item) => string(record(item)?.text)).filter((text) => Boolean(text));
|
|
6840
7226
|
return string(summary.join("\n\n")) ?? string(value?.thinking) ?? contentText(value?.content);
|
|
@@ -6931,13 +7317,13 @@ function codexItem(value) {
|
|
|
6931
7317
|
}
|
|
6932
7318
|
if (type.endsWith("_call")) {
|
|
6933
7319
|
const callId = string(item?.call_id) ?? string(item?.id);
|
|
6934
|
-
const
|
|
7320
|
+
const input2 = item?.arguments ?? item?.input ?? item?.action;
|
|
6935
7321
|
return [{
|
|
6936
7322
|
...callId ? { sourceId: callId, callId } : {},
|
|
6937
7323
|
kind: "tool",
|
|
6938
7324
|
role: "assistant",
|
|
6939
7325
|
name: string(item?.name) ?? type.replace(/_call$/, ""),
|
|
6940
|
-
input: json(
|
|
7326
|
+
input: json(input2),
|
|
6941
7327
|
status: "pending"
|
|
6942
7328
|
}];
|
|
6943
7329
|
}
|
|
@@ -6964,10 +7350,10 @@ function turnMeta(exchange) {
|
|
|
6964
7350
|
const metadata = record(json(encoded));
|
|
6965
7351
|
const source = string(client?.thread_source) ?? string(metadata?.thread_source);
|
|
6966
7352
|
const id = string(client?.turn_id) ?? string(metadata?.turn_id);
|
|
6967
|
-
const
|
|
7353
|
+
const claudeInternal = exchange?.meta?.protocol === "anthropic" && isInternalText(requestItems(exchange).filter((item) => item?.kind === "message").map((item) => item?.text ?? "").join("\n"));
|
|
6968
7354
|
return {
|
|
6969
7355
|
...id ? { id } : {},
|
|
6970
|
-
internal: source === "system" ||
|
|
7356
|
+
internal: source === "system" || claudeInternal
|
|
6971
7357
|
};
|
|
6972
7358
|
}
|
|
6973
7359
|
function fingerprint(item) {
|
|
@@ -7007,6 +7393,7 @@ var Builder = class {
|
|
|
7007
7393
|
ids = /* @__PURE__ */ new Set();
|
|
7008
7394
|
seen = /* @__PURE__ */ new Set();
|
|
7009
7395
|
max = /* @__PURE__ */ new Map();
|
|
7396
|
+
systemPrompt;
|
|
7010
7397
|
current;
|
|
7011
7398
|
turnSeq = 0;
|
|
7012
7399
|
itemSeq = 0;
|
|
@@ -7016,10 +7403,16 @@ var Builder = class {
|
|
|
7016
7403
|
}
|
|
7017
7404
|
/** 将一次 Exchange 合并到当前会话。 */
|
|
7018
7405
|
add(exchange) {
|
|
7406
|
+
const prompt = systemText(exchange);
|
|
7407
|
+
if (prompt && !this.systemPrompt) {
|
|
7408
|
+
this.systemPrompt = { text: prompt, exchangeIds: [exchange.meta.id] };
|
|
7409
|
+
} else if (prompt && this.systemPrompt?.text === prompt) {
|
|
7410
|
+
this.link(this.systemPrompt.exchangeIds, exchange.meta.id);
|
|
7411
|
+
}
|
|
7019
7412
|
const meta = turnMeta(exchange);
|
|
7020
|
-
const
|
|
7021
|
-
const userIndex = lastUser(
|
|
7022
|
-
|
|
7413
|
+
const input2 = this.snapshot(requestItems(exchange));
|
|
7414
|
+
const userIndex = lastUser(input2);
|
|
7415
|
+
input2.forEach((item, index) => {
|
|
7023
7416
|
const newTurn = item?.kind === "message" && item?.role === "user";
|
|
7024
7417
|
const explicit = newTurn && index === userIndex ? meta.id : void 0;
|
|
7025
7418
|
const turn = newTurn ? this.turn(explicit, meta.internal, exchange, true) : this.turn(meta.id, meta.internal, exchange, false);
|
|
@@ -7040,6 +7433,7 @@ var Builder = class {
|
|
|
7040
7433
|
sessionId: this.session.id,
|
|
7041
7434
|
protocol: this.session.protocol,
|
|
7042
7435
|
...model ? { model } : {},
|
|
7436
|
+
...this.systemPrompt ? { systemPrompt: this.systemPrompt } : {},
|
|
7043
7437
|
startedAt: this.session.startedAt,
|
|
7044
7438
|
...this.session.endedAt ? { endedAt: this.session.endedAt } : {},
|
|
7045
7439
|
exchangeIds: this.session.exchanges.map((item) => item.id),
|
|
@@ -7219,12 +7613,19 @@ async function scan(root) {
|
|
|
7219
7613
|
for (const date of await readDirs(runsRoot)) {
|
|
7220
7614
|
const dateDir = join4(runsRoot, date);
|
|
7221
7615
|
for (const run of await readDirs(dateDir)) {
|
|
7616
|
+
const runDir = join4(dateDir, run);
|
|
7617
|
+
let agentLabel;
|
|
7618
|
+
try {
|
|
7619
|
+
const info = await readJson(join4(runDir, "run.json"));
|
|
7620
|
+
agentLabel = info.agentLabel ?? (info.agent === "claude" ? "Claude Code" : info.agent === "codex" ? "Codex" : info.agent);
|
|
7621
|
+
} catch {
|
|
7622
|
+
}
|
|
7222
7623
|
const exchangesDir = join4(dateDir, run, "exchanges");
|
|
7223
7624
|
for (const exchange of await readDirs(exchangesDir)) {
|
|
7224
7625
|
const dir = join4(exchangesDir, exchange);
|
|
7225
7626
|
try {
|
|
7226
7627
|
const meta = await readJson(join4(dir, "meta.json"));
|
|
7227
|
-
result.push({ dir, meta: normalizeMeta(meta) });
|
|
7628
|
+
result.push({ dir, meta: normalizeMeta(meta), ...agentLabel ? { agentLabel } : {} });
|
|
7228
7629
|
} catch {
|
|
7229
7630
|
}
|
|
7230
7631
|
}
|
|
@@ -7266,6 +7667,7 @@ async function listSessions(root) {
|
|
|
7266
7667
|
groups.set(item.meta.sessionId, {
|
|
7267
7668
|
id: item.meta.sessionId,
|
|
7268
7669
|
protocol: item.meta.protocol,
|
|
7670
|
+
...item.agentLabel ? { agentLabel: item.agentLabel } : {},
|
|
7269
7671
|
startedAt: item.meta.startedAt,
|
|
7270
7672
|
...item.meta.completedAt ? { endedAt: item.meta.completedAt } : {},
|
|
7271
7673
|
...isInternal(item.meta) ? { internal: true } : {},
|
|
@@ -7276,8 +7678,8 @@ async function listSessions(root) {
|
|
|
7276
7678
|
}
|
|
7277
7679
|
async function readExchange(stored) {
|
|
7278
7680
|
const [requestBody, responseBody, chunkRows, events] = await Promise.all([
|
|
7279
|
-
|
|
7280
|
-
|
|
7681
|
+
readFile4(join4(stored.dir, "request.bin")),
|
|
7682
|
+
readFile4(join4(stored.dir, "response.bin")),
|
|
7281
7683
|
readJsonl(join4(stored.dir, "response-chunks.jsonl")),
|
|
7282
7684
|
readJsonl(join4(stored.dir, "sse-events.jsonl"))
|
|
7283
7685
|
]);
|
|
@@ -7343,11 +7745,11 @@ async function getConversation(root, sessionId) {
|
|
|
7343
7745
|
}
|
|
7344
7746
|
|
|
7345
7747
|
// ../../packages/storage/src/store.ts
|
|
7346
|
-
import { mkdir as
|
|
7748
|
+
import { mkdir as mkdir5 } from "fs/promises";
|
|
7347
7749
|
import { join as join6 } from "path";
|
|
7348
7750
|
|
|
7349
7751
|
// ../../packages/storage/src/writer.ts
|
|
7350
|
-
import { mkdir as
|
|
7752
|
+
import { mkdir as mkdir4, open } from "fs/promises";
|
|
7351
7753
|
import { join as join5 } from "path";
|
|
7352
7754
|
var ExchangeWriter = class {
|
|
7353
7755
|
dir;
|
|
@@ -7370,7 +7772,7 @@ var ExchangeWriter = class {
|
|
|
7370
7772
|
}
|
|
7371
7773
|
/** 创建目录并打开全部追加文件。 */
|
|
7372
7774
|
async init() {
|
|
7373
|
-
await
|
|
7775
|
+
await mkdir4(this.dir, { recursive: true });
|
|
7374
7776
|
[this.req, this.reqIndex, this.res, this.resIndex, this.eventFile] = await Promise.all([
|
|
7375
7777
|
open(join5(this.dir, "request.bin"), "a"),
|
|
7376
7778
|
open(join5(this.dir, "request-chunks.jsonl"), "a"),
|
|
@@ -7463,7 +7865,7 @@ var FileStore = class {
|
|
|
7463
7865
|
}
|
|
7464
7866
|
/** 初始化存储根目录。 */
|
|
7465
7867
|
async init() {
|
|
7466
|
-
await
|
|
7868
|
+
await mkdir5(join6(this.root, "runs"), { recursive: true });
|
|
7467
7869
|
}
|
|
7468
7870
|
/** 创建一次运行记录并返回其目录。 */
|
|
7469
7871
|
async startRun(run) {
|
|
@@ -7559,7 +7961,7 @@ data: ${JSON.stringify(data)}
|
|
|
7559
7961
|
};
|
|
7560
7962
|
|
|
7561
7963
|
// ../../packages/server/src/static.ts
|
|
7562
|
-
import { readFile as
|
|
7964
|
+
import { readFile as readFile5, stat } from "fs/promises";
|
|
7563
7965
|
import { extname, resolve as resolve3, sep } from "path";
|
|
7564
7966
|
var contentTypes = {
|
|
7565
7967
|
".css": "text/css; charset=utf-8",
|
|
@@ -7588,7 +7990,7 @@ async function serveStatic(root, pathname, res) {
|
|
|
7588
7990
|
target = resolve3(base, "index.html");
|
|
7589
7991
|
}
|
|
7590
7992
|
try {
|
|
7591
|
-
const body = await
|
|
7993
|
+
const body = await readFile5(target);
|
|
7592
7994
|
res.writeHead(200, {
|
|
7593
7995
|
"content-type": contentTypes?.[extname(target)] ?? "application/octet-stream",
|
|
7594
7996
|
"cache-control": target.endsWith("index.html") ? "no-cache" : "public, max-age=31536000, immutable"
|
|
@@ -7702,53 +8104,12 @@ var TraceletServer = class {
|
|
|
7702
8104
|
}
|
|
7703
8105
|
};
|
|
7704
8106
|
|
|
7705
|
-
// src/settings.ts
|
|
7706
|
-
import { mkdir as mkdir5, readFile as readFile5, writeFile as writeFile2 } from "fs/promises";
|
|
7707
|
-
import { dirname as dirname2 } from "path";
|
|
7708
|
-
function isObject(value) {
|
|
7709
|
-
return value !== null && typeof value === "object" && !Array.isArray(value);
|
|
7710
|
-
}
|
|
7711
|
-
async function readRaw(path) {
|
|
7712
|
-
try {
|
|
7713
|
-
const value = JSON.parse(await readFile5(path, "utf8"));
|
|
7714
|
-
if (!isObject(value)) {
|
|
7715
|
-
throw new Error("Settings root must be an object");
|
|
7716
|
-
}
|
|
7717
|
-
return value;
|
|
7718
|
-
} catch (error) {
|
|
7719
|
-
if (error?.code === "ENOENT") {
|
|
7720
|
-
return {};
|
|
7721
|
-
}
|
|
7722
|
-
throw new Error(`Failed to read Tracelet settings: ${path}`, { cause: error });
|
|
7723
|
-
}
|
|
7724
|
-
}
|
|
7725
|
-
async function loadSettings(path = settingsFile()) {
|
|
7726
|
-
const value = await readRaw(path);
|
|
7727
|
-
const proxy = value.proxy;
|
|
7728
|
-
if (proxy !== void 0 && !isObject(proxy)) {
|
|
7729
|
-
throw new Error(`Invalid proxy settings: ${path}`);
|
|
7730
|
-
}
|
|
7731
|
-
const mode = isObject(proxy) ? proxy.mode : void 0;
|
|
7732
|
-
if (mode !== void 0 && mode !== "direct" && mode !== "system") {
|
|
7733
|
-
throw new Error(`Invalid proxy mode: ${String(mode)}`);
|
|
7734
|
-
}
|
|
7735
|
-
return { proxy: { mode: mode ?? "direct" } };
|
|
7736
|
-
}
|
|
7737
|
-
async function saveProxy(mode, path = settingsFile()) {
|
|
7738
|
-
const value = await readRaw(path);
|
|
7739
|
-
const proxy = isObject(value.proxy) ? value.proxy : {};
|
|
7740
|
-
const next = { ...value, proxy: { ...proxy, mode } };
|
|
7741
|
-
await mkdir5(dirname2(path), { recursive: true });
|
|
7742
|
-
await writeFile2(path, `${JSON.stringify(next, null, 2)}
|
|
7743
|
-
`, { encoding: "utf8", mode: 384 });
|
|
7744
|
-
}
|
|
7745
|
-
|
|
7746
8107
|
// src/spawn.ts
|
|
7747
8108
|
import { spawn } from "child_process";
|
|
7748
|
-
async function spawnAgent(
|
|
8109
|
+
async function spawnAgent(input2) {
|
|
7749
8110
|
return new Promise((resolve4, reject) => {
|
|
7750
|
-
const child = spawn(
|
|
7751
|
-
env:
|
|
8111
|
+
const child = spawn(input2.command, input2.args, {
|
|
8112
|
+
env: input2.env,
|
|
7752
8113
|
stdio: "inherit"
|
|
7753
8114
|
});
|
|
7754
8115
|
function onSigint() {
|
|
@@ -7783,12 +8144,12 @@ function lineValue(output, key) {
|
|
|
7783
8144
|
return output.match(new RegExp(`^\\s*${key}\\s*:\\s*(.+?)\\s*$`, "m"))?.[1];
|
|
7784
8145
|
}
|
|
7785
8146
|
function proxyUrl(value) {
|
|
7786
|
-
const
|
|
7787
|
-
if (!
|
|
8147
|
+
const input2 = value?.trim();
|
|
8148
|
+
if (!input2) {
|
|
7788
8149
|
return void 0;
|
|
7789
8150
|
}
|
|
7790
8151
|
try {
|
|
7791
|
-
const url = new URL(/^[a-z]+:\/\//i.test(
|
|
8152
|
+
const url = new URL(/^[a-z]+:\/\//i.test(input2) ? input2 : `http://${input2}`);
|
|
7792
8153
|
return url.protocol === "http:" || url.protocol === "https:" ? url.toString().replace(/\/$/, "") : void 0;
|
|
7793
8154
|
} catch {
|
|
7794
8155
|
return void 0;
|
|
@@ -7844,13 +8205,17 @@ async function systemProxy(platform = process.platform) {
|
|
|
7844
8205
|
|
|
7845
8206
|
// src/run.ts
|
|
7846
8207
|
async function runAgent(agentId, args, options) {
|
|
7847
|
-
const
|
|
8208
|
+
const settings = await loadSettings();
|
|
8209
|
+
const adapter = agentId === "claude" || agentId === "codex" ? agents[agentId] : settings.customAgents[agentId] ? customAdapter(agentId, settings.customAgents[agentId]) : void 0;
|
|
8210
|
+
if (!adapter) {
|
|
8211
|
+
throw new Error(`Unknown agent: ${agentId}`);
|
|
8212
|
+
}
|
|
7848
8213
|
if (!await adapter.detect()) {
|
|
7849
8214
|
throw new Error(`${adapter.label} command not found: ${adapter.command}`);
|
|
7850
8215
|
}
|
|
7851
|
-
const
|
|
7852
|
-
const proxy =
|
|
7853
|
-
if (
|
|
8216
|
+
const mode = proxyMode(settings, agentId);
|
|
8217
|
+
const proxy = mode === "system" ? await systemProxy() : void 0;
|
|
8218
|
+
if (mode === "system" && !proxy) {
|
|
7854
8219
|
console.warn("System proxy not found. Using a direct connection.");
|
|
7855
8220
|
}
|
|
7856
8221
|
const server = new TraceletServer({
|
|
@@ -7861,6 +8226,7 @@ async function runAgent(agentId, args, options) {
|
|
|
7861
8226
|
const run = {
|
|
7862
8227
|
id: makeId("run"),
|
|
7863
8228
|
agent: adapter.id,
|
|
8229
|
+
agentLabel: adapter.label,
|
|
7864
8230
|
cwd: process.cwd(),
|
|
7865
8231
|
command: adapter.command,
|
|
7866
8232
|
startedAt: nowIso()
|
|
@@ -7895,6 +8261,9 @@ async function runDashboard(options) {
|
|
|
7895
8261
|
}
|
|
7896
8262
|
|
|
7897
8263
|
// src/program.ts
|
|
8264
|
+
var { version } = JSON.parse(
|
|
8265
|
+
readFileSync(new URL("../package.json", import.meta.url), "utf8")
|
|
8266
|
+
);
|
|
7898
8267
|
function parsePort(value) {
|
|
7899
8268
|
const port = Number.parseInt(value, 10);
|
|
7900
8269
|
if (!Number.isInteger(port) || port < 0 || port > 65535) {
|
|
@@ -7911,28 +8280,47 @@ function getOptions(program) {
|
|
|
7911
8280
|
}
|
|
7912
8281
|
async function interactive(program) {
|
|
7913
8282
|
if (!process.stdin.isTTY) {
|
|
7914
|
-
throw new Error("Use tracelet claude or tracelet
|
|
8283
|
+
throw new Error("Use tracelet claude, tracelet codex, or tracelet run <id> in a non-interactive environment.");
|
|
7915
8284
|
}
|
|
7916
|
-
|
|
7917
|
-
|
|
7918
|
-
|
|
7919
|
-
|
|
7920
|
-
|
|
7921
|
-
|
|
7922
|
-
|
|
7923
|
-
|
|
7924
|
-
|
|
7925
|
-
|
|
7926
|
-
|
|
7927
|
-
|
|
7928
|
-
|
|
7929
|
-
|
|
7930
|
-
|
|
7931
|
-
|
|
8285
|
+
for (; ; ) {
|
|
8286
|
+
const settings = await loadSettings();
|
|
8287
|
+
const [claudeReady, codexReady] = await Promise.all([
|
|
8288
|
+
agents.claude.detect(),
|
|
8289
|
+
agents.codex.detect()
|
|
8290
|
+
]);
|
|
8291
|
+
const custom = await Promise.all(Object.entries(settings.customAgents).map(async ([id, config]) => ({
|
|
8292
|
+
name: config.label,
|
|
8293
|
+
value: id,
|
|
8294
|
+
...!await customAdapter(id, config).detect() ? { disabled: `${config.command} command not found` } : {}
|
|
8295
|
+
})));
|
|
8296
|
+
const agent = await select2({
|
|
8297
|
+
message: "Select an agent to trace",
|
|
8298
|
+
choices: [
|
|
8299
|
+
{
|
|
8300
|
+
name: "Claude Code",
|
|
8301
|
+
value: "claude",
|
|
8302
|
+
...!claudeReady ? { disabled: "claude command not found" } : {}
|
|
8303
|
+
},
|
|
8304
|
+
{
|
|
8305
|
+
name: "Codex",
|
|
8306
|
+
value: "codex",
|
|
8307
|
+
...!codexReady ? { disabled: "codex command not found" } : {}
|
|
8308
|
+
},
|
|
8309
|
+
...custom,
|
|
8310
|
+
{ name: "Manage custom agents...", value: "manage" }
|
|
8311
|
+
]
|
|
8312
|
+
});
|
|
8313
|
+
if (agent === "manage") {
|
|
8314
|
+
const launch = await manageAgents();
|
|
8315
|
+
if (launch) {
|
|
8316
|
+
process.exitCode = await runAgent(launch, [], getOptions(program));
|
|
8317
|
+
return;
|
|
7932
8318
|
}
|
|
7933
|
-
|
|
7934
|
-
|
|
7935
|
-
|
|
8319
|
+
continue;
|
|
8320
|
+
}
|
|
8321
|
+
process.exitCode = await runAgent(agent, [], getOptions(program));
|
|
8322
|
+
return;
|
|
8323
|
+
}
|
|
7936
8324
|
}
|
|
7937
8325
|
async function clearRecords(program, yes) {
|
|
7938
8326
|
const root = dataDir(getOptions(program).dataDir);
|
|
@@ -7940,7 +8328,7 @@ async function clearRecords(program, yes) {
|
|
|
7940
8328
|
if (!process.stdin.isTTY) {
|
|
7941
8329
|
throw new Error("Use tracelet clear --yes in a non-interactive environment.");
|
|
7942
8330
|
}
|
|
7943
|
-
const accepted = await
|
|
8331
|
+
const accepted = await confirm2({
|
|
7944
8332
|
message: `This will permanently delete all Tracelet records in ${root}. Continue?`,
|
|
7945
8333
|
default: false
|
|
7946
8334
|
});
|
|
@@ -7956,8 +8344,27 @@ async function configureProxy() {
|
|
|
7956
8344
|
if (!process.stdin.isTTY) {
|
|
7957
8345
|
throw new Error("The proxy command requires an interactive terminal.");
|
|
7958
8346
|
}
|
|
7959
|
-
const
|
|
7960
|
-
const
|
|
8347
|
+
const settings = await loadSettings();
|
|
8348
|
+
const target = await select2({
|
|
8349
|
+
message: "Select an agent to configure",
|
|
8350
|
+
choices: [
|
|
8351
|
+
{
|
|
8352
|
+
name: `Claude Code (${proxyMode(settings, "claude") === "system" ? "On" : "Off"})`,
|
|
8353
|
+
value: "claude"
|
|
8354
|
+
},
|
|
8355
|
+
{
|
|
8356
|
+
name: `Codex (${proxyMode(settings, "codex") === "system" ? "On" : "Off"})`,
|
|
8357
|
+
value: "codex"
|
|
8358
|
+
},
|
|
8359
|
+
{ name: "All agents", value: "all" },
|
|
8360
|
+
...Object.entries(settings.customAgents).map(([id, agent]) => ({
|
|
8361
|
+
name: `${agent.label} (${proxyMode(settings, id) === "system" ? "On" : "Off"})`,
|
|
8362
|
+
value: id
|
|
8363
|
+
}))
|
|
8364
|
+
]
|
|
8365
|
+
});
|
|
8366
|
+
const current = target === "all" ? settings.proxy.default.mode : proxyMode(settings, target);
|
|
8367
|
+
const mode = await select2({
|
|
7961
8368
|
message: "Use the system proxy for upstream requests?",
|
|
7962
8369
|
choices: [
|
|
7963
8370
|
{ name: "On", value: "system" },
|
|
@@ -7965,17 +8372,25 @@ async function configureProxy() {
|
|
|
7965
8372
|
],
|
|
7966
8373
|
default: current
|
|
7967
8374
|
});
|
|
7968
|
-
await saveProxy(mode);
|
|
7969
|
-
|
|
8375
|
+
await saveProxy(target, mode);
|
|
8376
|
+
const label = target === "all" ? "all agents" : target === "claude" || target === "codex" ? agents[target].label : settings.customAgents[target]?.label ?? target;
|
|
8377
|
+
console.log(`System proxy ${mode === "system" ? "enabled" : "disabled"} for ${label}.`);
|
|
7970
8378
|
}
|
|
7971
8379
|
function createProgram() {
|
|
7972
8380
|
const program = new Command();
|
|
7973
|
-
program.name("tracelet").description("Record LLM requests and streaming responses from
|
|
8381
|
+
program.name("tracelet").description("Record LLM requests and streaming responses from built-in and custom agents").version(version).enablePositionalOptions().addOption(new Option("-p, --port <port>", "Local server port").default(4318).argParser(parsePort)).option("--data-dir <path>", "Local trace directory").action(() => interactive(program));
|
|
7974
8382
|
for (const agentId of ["claude", "codex"]) {
|
|
7975
8383
|
program.command(`${agentId} [args...]`).description(`Start and trace ${agents[agentId].label}`).allowUnknownOption().passThroughOptions().action(async (args) => {
|
|
7976
8384
|
process.exitCode = await runAgent(agentId, args, getOptions(program));
|
|
7977
8385
|
});
|
|
7978
8386
|
}
|
|
8387
|
+
program.command("agent").description("Add, edit, or delete custom agents").action(async () => {
|
|
8388
|
+
const launch = await manageAgents();
|
|
8389
|
+
if (launch) process.exitCode = await runAgent(launch, [], getOptions(program));
|
|
8390
|
+
});
|
|
8391
|
+
program.command("run <id> [args...]").description("Start and trace a custom agent by ID").allowUnknownOption().passThroughOptions().action(async (id, args) => {
|
|
8392
|
+
process.exitCode = await runAgent(id, args, getOptions(program));
|
|
8393
|
+
});
|
|
7979
8394
|
program.command("dashboard").description("View local Tracelet history").action(() => runDashboard(getOptions(program)));
|
|
7980
8395
|
program.command("proxy").description("Configure system proxy usage").action(configureProxy);
|
|
7981
8396
|
const clear = program.command("clear").description("Clear all local Tracelet history").option("-y, --yes", "Skip confirmation");
|