llmist 1.7.0 → 2.1.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-JGORHSHC.js → chunk-LSCCBXS7.js} +10 -3
- package/dist/chunk-LSCCBXS7.js.map +1 -0
- package/dist/{chunk-E52IO2NO.js → chunk-PDYVT3FI.js} +421 -53
- package/dist/chunk-PDYVT3FI.js.map +1 -0
- package/dist/cli.cjs +591 -154
- package/dist/cli.cjs.map +1 -1
- package/dist/cli.js +188 -110
- package/dist/cli.js.map +1 -1
- package/dist/index.cjs +415 -47
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +72 -13
- package/dist/index.d.ts +72 -13
- package/dist/index.js +4 -2
- package/dist/{mock-stream-BMuFlQI1.d.cts → mock-stream-HF7MBNhi.d.cts} +650 -319
- package/dist/{mock-stream-BMuFlQI1.d.ts → mock-stream-HF7MBNhi.d.ts} +650 -319
- package/dist/testing/index.cjs +416 -49
- package/dist/testing/index.cjs.map +1 -1
- package/dist/testing/index.d.cts +4 -2
- package/dist/testing/index.d.ts +4 -2
- package/dist/testing/index.js +1 -1
- package/package.json +1 -1
- package/dist/chunk-E52IO2NO.js.map +0 -1
- package/dist/chunk-JGORHSHC.js.map +0 -1
package/dist/cli.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
import "./chunk-
|
|
2
|
+
import "./chunk-LSCCBXS7.js";
|
|
3
3
|
import {
|
|
4
4
|
AgentBuilder,
|
|
5
5
|
BaseGadget,
|
|
@@ -27,7 +27,7 @@ import {
|
|
|
27
27
|
resolveModel,
|
|
28
28
|
schemaToJSONSchema,
|
|
29
29
|
validateGadgetSchema
|
|
30
|
-
} from "./chunk-
|
|
30
|
+
} from "./chunk-PDYVT3FI.js";
|
|
31
31
|
|
|
32
32
|
// src/cli/constants.ts
|
|
33
33
|
var CLI_NAME = "llmist";
|
|
@@ -51,7 +51,6 @@ var OPTION_FLAGS = {
|
|
|
51
51
|
logFile: "--log-file <path>",
|
|
52
52
|
logReset: "--log-reset",
|
|
53
53
|
logLlmRequests: "--log-llm-requests [dir]",
|
|
54
|
-
logLlmResponses: "--log-llm-responses [dir]",
|
|
55
54
|
noBuiltins: "--no-builtins",
|
|
56
55
|
noBuiltinInteraction: "--no-builtin-interaction",
|
|
57
56
|
quiet: "-q, --quiet",
|
|
@@ -70,8 +69,7 @@ var OPTION_DESCRIPTIONS = {
|
|
|
70
69
|
logLevel: "Log level: silly, trace, debug, info, warn, error, fatal.",
|
|
71
70
|
logFile: "Path to log file. When set, logs are written to file instead of stderr.",
|
|
72
71
|
logReset: "Reset (truncate) the log file at session start instead of appending.",
|
|
73
|
-
logLlmRequests: "Save
|
|
74
|
-
logLlmResponses: "Save raw LLM responses as plain text. Optional dir, defaults to ~/.llmist/logs/responses/",
|
|
72
|
+
logLlmRequests: "Save LLM requests/responses to session directories. Optional dir, defaults to ~/.llmist/logs/requests/",
|
|
75
73
|
noBuiltins: "Disable built-in gadgets (AskUser, TellUser).",
|
|
76
74
|
noBuiltinInteraction: "Disable interactive gadgets (AskUser) while keeping TellUser.",
|
|
77
75
|
quiet: "Suppress all output except content (text and TellUser messages).",
|
|
@@ -88,7 +86,7 @@ import { Command, InvalidArgumentError as InvalidArgumentError2 } from "commande
|
|
|
88
86
|
// package.json
|
|
89
87
|
var package_default = {
|
|
90
88
|
name: "llmist",
|
|
91
|
-
version: "
|
|
89
|
+
version: "2.0.0",
|
|
92
90
|
description: "Universal TypeScript LLM client with streaming-first agent framework. Works with any model - no structured outputs or native tool calling required. Implements its own flexible grammar for function calling.",
|
|
93
91
|
type: "module",
|
|
94
92
|
main: "dist/index.cjs",
|
|
@@ -261,6 +259,14 @@ ${addedLines}`;
|
|
|
261
259
|
}
|
|
262
260
|
|
|
263
261
|
// src/cli/approval/context-providers.ts
|
|
262
|
+
function formatGadgetSummary(gadgetName, params) {
|
|
263
|
+
const paramEntries = Object.entries(params);
|
|
264
|
+
if (paramEntries.length === 0) {
|
|
265
|
+
return `${gadgetName}()`;
|
|
266
|
+
}
|
|
267
|
+
const paramStr = paramEntries.map(([k, v]) => `${k}=${JSON.stringify(v)}`).join(", ");
|
|
268
|
+
return `${gadgetName}(${paramStr})`;
|
|
269
|
+
}
|
|
264
270
|
var WriteFileContextProvider = class {
|
|
265
271
|
gadgetName = "WriteFile";
|
|
266
272
|
async getContext(params) {
|
|
@@ -269,14 +275,14 @@ var WriteFileContextProvider = class {
|
|
|
269
275
|
const resolvedPath = resolve(process.cwd(), filePath);
|
|
270
276
|
if (!existsSync(resolvedPath)) {
|
|
271
277
|
return {
|
|
272
|
-
summary:
|
|
278
|
+
summary: formatGadgetSummary(this.gadgetName, params),
|
|
273
279
|
details: formatNewFileDiff(filePath, newContent)
|
|
274
280
|
};
|
|
275
281
|
}
|
|
276
282
|
const oldContent = readFileSync(resolvedPath, "utf-8");
|
|
277
283
|
const diff = createPatch(filePath, oldContent, newContent, "original", "modified");
|
|
278
284
|
return {
|
|
279
|
-
summary:
|
|
285
|
+
summary: formatGadgetSummary(this.gadgetName, params),
|
|
280
286
|
details: diff
|
|
281
287
|
};
|
|
282
288
|
}
|
|
@@ -290,37 +296,27 @@ var EditFileContextProvider = class {
|
|
|
290
296
|
const newContent = String(params.content);
|
|
291
297
|
if (!existsSync(resolvedPath)) {
|
|
292
298
|
return {
|
|
293
|
-
summary:
|
|
299
|
+
summary: formatGadgetSummary(this.gadgetName, params),
|
|
294
300
|
details: formatNewFileDiff(filePath, newContent)
|
|
295
301
|
};
|
|
296
302
|
}
|
|
297
303
|
const oldContent = readFileSync(resolvedPath, "utf-8");
|
|
298
304
|
const diff = createPatch(filePath, oldContent, newContent, "original", "modified");
|
|
299
305
|
return {
|
|
300
|
-
summary:
|
|
306
|
+
summary: formatGadgetSummary(this.gadgetName, params),
|
|
301
307
|
details: diff
|
|
302
308
|
};
|
|
303
309
|
}
|
|
304
310
|
if ("commands" in params) {
|
|
305
311
|
const commands = String(params.commands);
|
|
306
312
|
return {
|
|
307
|
-
summary:
|
|
313
|
+
summary: formatGadgetSummary(this.gadgetName, params),
|
|
308
314
|
details: `Commands:
|
|
309
315
|
${commands}`
|
|
310
316
|
};
|
|
311
317
|
}
|
|
312
318
|
return {
|
|
313
|
-
summary:
|
|
314
|
-
};
|
|
315
|
-
}
|
|
316
|
-
};
|
|
317
|
-
var RunCommandContextProvider = class {
|
|
318
|
-
gadgetName = "RunCommand";
|
|
319
|
-
async getContext(params) {
|
|
320
|
-
const command = String(params.command ?? "");
|
|
321
|
-
const cwd = params.cwd ? ` (in ${params.cwd})` : "";
|
|
322
|
-
return {
|
|
323
|
-
summary: `Execute: ${command}${cwd}`
|
|
319
|
+
summary: formatGadgetSummary(this.gadgetName, params)
|
|
324
320
|
};
|
|
325
321
|
}
|
|
326
322
|
};
|
|
@@ -329,27 +325,15 @@ var DefaultContextProvider = class {
|
|
|
329
325
|
this.gadgetName = gadgetName;
|
|
330
326
|
}
|
|
331
327
|
async getContext(params) {
|
|
332
|
-
const paramEntries = Object.entries(params);
|
|
333
|
-
if (paramEntries.length === 0) {
|
|
334
|
-
return {
|
|
335
|
-
summary: `${this.gadgetName}()`
|
|
336
|
-
};
|
|
337
|
-
}
|
|
338
|
-
const formatValue = (value) => {
|
|
339
|
-
const MAX_LEN = 50;
|
|
340
|
-
const str = JSON.stringify(value);
|
|
341
|
-
return str.length > MAX_LEN ? `${str.slice(0, MAX_LEN - 3)}...` : str;
|
|
342
|
-
};
|
|
343
|
-
const paramStr = paramEntries.map(([k, v]) => `${k}=${formatValue(v)}`).join(", ");
|
|
344
328
|
return {
|
|
345
|
-
summary:
|
|
329
|
+
summary: formatGadgetSummary(this.gadgetName, params)
|
|
346
330
|
};
|
|
347
331
|
}
|
|
348
332
|
};
|
|
349
333
|
var builtinContextProviders = [
|
|
350
334
|
new WriteFileContextProvider(),
|
|
351
|
-
new EditFileContextProvider()
|
|
352
|
-
|
|
335
|
+
new EditFileContextProvider()
|
|
336
|
+
// Note: RunCommand uses DefaultContextProvider - no custom details needed
|
|
353
337
|
];
|
|
354
338
|
|
|
355
339
|
// src/cli/approval/manager.ts
|
|
@@ -360,11 +344,13 @@ var ApprovalManager = class {
|
|
|
360
344
|
* @param config - Approval configuration with per-gadget modes
|
|
361
345
|
* @param env - CLI environment for I/O operations
|
|
362
346
|
* @param progress - Optional progress indicator to pause during prompts
|
|
347
|
+
* @param keyboard - Optional keyboard coordinator to disable ESC listener during prompts
|
|
363
348
|
*/
|
|
364
|
-
constructor(config, env, progress) {
|
|
349
|
+
constructor(config, env, progress, keyboard) {
|
|
365
350
|
this.config = config;
|
|
366
351
|
this.env = env;
|
|
367
352
|
this.progress = progress;
|
|
353
|
+
this.keyboard = keyboard;
|
|
368
354
|
for (const provider of builtinContextProviders) {
|
|
369
355
|
this.registerProvider(provider);
|
|
370
356
|
}
|
|
@@ -433,26 +419,34 @@ var ApprovalManager = class {
|
|
|
433
419
|
const provider = this.providers.get(gadgetName.toLowerCase()) ?? new DefaultContextProvider(gadgetName);
|
|
434
420
|
const context = await provider.getContext(params);
|
|
435
421
|
this.progress?.pause();
|
|
436
|
-
this.
|
|
422
|
+
if (this.keyboard?.cleanupEsc) {
|
|
423
|
+
this.keyboard.cleanupEsc();
|
|
424
|
+
this.keyboard.cleanupEsc = null;
|
|
425
|
+
}
|
|
426
|
+
try {
|
|
427
|
+
this.env.stderr.write(`
|
|
437
428
|
${chalk2.yellow("\u{1F512} Approval required:")} ${context.summary}
|
|
438
429
|
`);
|
|
439
|
-
|
|
440
|
-
|
|
430
|
+
if (context.details) {
|
|
431
|
+
this.env.stderr.write(`
|
|
441
432
|
${renderColoredDiff(context.details)}
|
|
442
433
|
`);
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
434
|
+
}
|
|
435
|
+
const response = await this.prompt(" \u23CE approve, or type to reject: ");
|
|
436
|
+
const isApproved = response === "" || response.toLowerCase() === "y";
|
|
437
|
+
if (isApproved) {
|
|
438
|
+
this.env.stderr.write(` ${chalk2.green("\u2713 Approved")}
|
|
448
439
|
|
|
449
440
|
`);
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
441
|
+
return { approved: true };
|
|
442
|
+
}
|
|
443
|
+
this.env.stderr.write(` ${chalk2.red("\u2717 Denied")}
|
|
453
444
|
|
|
454
445
|
`);
|
|
455
|
-
|
|
446
|
+
return { approved: false, reason: response || "Rejected by user" };
|
|
447
|
+
} finally {
|
|
448
|
+
this.keyboard?.restore();
|
|
449
|
+
}
|
|
456
450
|
}
|
|
457
451
|
/**
|
|
458
452
|
* Prompts for user input.
|
|
@@ -891,49 +885,77 @@ error: ${message}`;
|
|
|
891
885
|
import { z as z6 } from "zod";
|
|
892
886
|
var runCommand = createGadget({
|
|
893
887
|
name: "RunCommand",
|
|
894
|
-
description: "Execute a
|
|
888
|
+
description: "Execute a command with arguments and return its output. Uses argv array to bypass shell - arguments are passed directly without interpretation. Returns stdout/stderr combined with exit status.",
|
|
895
889
|
schema: z6.object({
|
|
896
|
-
|
|
890
|
+
argv: z6.array(z6.string()).describe("Command and arguments as array (e.g., ['git', 'commit', '-m', 'message'])"),
|
|
897
891
|
cwd: z6.string().optional().describe("Working directory for the command (default: current directory)"),
|
|
898
892
|
timeout: z6.number().default(3e4).describe("Timeout in milliseconds (default: 30000)")
|
|
899
893
|
}),
|
|
900
894
|
examples: [
|
|
901
895
|
{
|
|
902
|
-
params: {
|
|
896
|
+
params: { argv: ["ls", "-la"], timeout: 3e4 },
|
|
903
897
|
output: "status=0\n\ntotal 24\ndrwxr-xr-x 5 user staff 160 Nov 27 10:00 .\ndrwxr-xr-x 3 user staff 96 Nov 27 09:00 ..\n-rw-r--r-- 1 user staff 1024 Nov 27 10:00 package.json",
|
|
904
898
|
comment: "List directory contents with details"
|
|
905
899
|
},
|
|
906
900
|
{
|
|
907
|
-
params: {
|
|
901
|
+
params: { argv: ["echo", "Hello World"], timeout: 3e4 },
|
|
908
902
|
output: "status=0\n\nHello World",
|
|
909
|
-
comment: "
|
|
903
|
+
comment: "Echo without shell - argument passed directly"
|
|
910
904
|
},
|
|
911
905
|
{
|
|
912
|
-
params: {
|
|
906
|
+
params: { argv: ["cat", "nonexistent.txt"], timeout: 3e4 },
|
|
913
907
|
output: "status=1\n\ncat: nonexistent.txt: No such file or directory",
|
|
914
908
|
comment: "Command that fails returns non-zero status"
|
|
915
909
|
},
|
|
916
910
|
{
|
|
917
|
-
params: {
|
|
911
|
+
params: { argv: ["pwd"], cwd: "/tmp", timeout: 3e4 },
|
|
918
912
|
output: "status=0\n\n/tmp",
|
|
919
913
|
comment: "Execute command in a specific directory"
|
|
914
|
+
},
|
|
915
|
+
{
|
|
916
|
+
params: { argv: ["gh", "pr", "review", "123", "--comment", "--body", "Review with `backticks` and 'quotes'"], timeout: 3e4 },
|
|
917
|
+
output: "status=0\n\n(no output)",
|
|
918
|
+
comment: "Complex arguments with special characters - no escaping needed"
|
|
919
|
+
},
|
|
920
|
+
{
|
|
921
|
+
params: {
|
|
922
|
+
argv: [
|
|
923
|
+
"gh",
|
|
924
|
+
"pr",
|
|
925
|
+
"review",
|
|
926
|
+
"123",
|
|
927
|
+
"--approve",
|
|
928
|
+
"--body",
|
|
929
|
+
"## Review Summary\n\n**Looks good!**\n\n- Clean code\n- Tests pass"
|
|
930
|
+
],
|
|
931
|
+
timeout: 3e4
|
|
932
|
+
},
|
|
933
|
+
output: "status=0\n\nApproving pull request #123",
|
|
934
|
+
comment: "Multiline body: --body flag and content must be SEPARATE array elements"
|
|
920
935
|
}
|
|
921
936
|
],
|
|
922
|
-
execute: async ({
|
|
937
|
+
execute: async ({ argv, cwd, timeout }) => {
|
|
923
938
|
const workingDir = cwd ?? process.cwd();
|
|
939
|
+
if (argv.length === 0) {
|
|
940
|
+
return "status=1\n\nerror: argv array cannot be empty";
|
|
941
|
+
}
|
|
942
|
+
let timeoutId;
|
|
924
943
|
try {
|
|
925
|
-
const proc = Bun.spawn(
|
|
944
|
+
const proc = Bun.spawn(argv, {
|
|
926
945
|
cwd: workingDir,
|
|
927
946
|
stdout: "pipe",
|
|
928
947
|
stderr: "pipe"
|
|
929
948
|
});
|
|
930
949
|
const timeoutPromise = new Promise((_, reject) => {
|
|
931
|
-
setTimeout(() => {
|
|
950
|
+
timeoutId = setTimeout(() => {
|
|
932
951
|
proc.kill();
|
|
933
952
|
reject(new Error(`Command timed out after ${timeout}ms`));
|
|
934
953
|
}, timeout);
|
|
935
954
|
});
|
|
936
955
|
const exitCode = await Promise.race([proc.exited, timeoutPromise]);
|
|
956
|
+
if (timeoutId) {
|
|
957
|
+
clearTimeout(timeoutId);
|
|
958
|
+
}
|
|
937
959
|
const stdout = await new Response(proc.stdout).text();
|
|
938
960
|
const stderr = await new Response(proc.stderr).text();
|
|
939
961
|
const output = [stdout, stderr].filter(Boolean).join("\n").trim();
|
|
@@ -941,6 +963,9 @@ var runCommand = createGadget({
|
|
|
941
963
|
|
|
942
964
|
${output || "(no output)"}`;
|
|
943
965
|
} catch (error) {
|
|
966
|
+
if (timeoutId) {
|
|
967
|
+
clearTimeout(timeoutId);
|
|
968
|
+
}
|
|
944
969
|
const message = error instanceof Error ? error.message : String(error);
|
|
945
970
|
return `status=1
|
|
946
971
|
|
|
@@ -1113,6 +1138,30 @@ async function writeLogFile(dir, filename, content) {
|
|
|
1113
1138
|
await mkdir(dir, { recursive: true });
|
|
1114
1139
|
await writeFile2(join(dir, filename), content, "utf-8");
|
|
1115
1140
|
}
|
|
1141
|
+
function formatSessionTimestamp(date = /* @__PURE__ */ new Date()) {
|
|
1142
|
+
const pad = (n) => n.toString().padStart(2, "0");
|
|
1143
|
+
const year = date.getFullYear();
|
|
1144
|
+
const month = pad(date.getMonth() + 1);
|
|
1145
|
+
const day = pad(date.getDate());
|
|
1146
|
+
const hours = pad(date.getHours());
|
|
1147
|
+
const minutes = pad(date.getMinutes());
|
|
1148
|
+
const seconds = pad(date.getSeconds());
|
|
1149
|
+
return `${year}-${month}-${day}_${hours}-${minutes}-${seconds}`;
|
|
1150
|
+
}
|
|
1151
|
+
async function createSessionDir(baseDir) {
|
|
1152
|
+
const timestamp = formatSessionTimestamp();
|
|
1153
|
+
const sessionDir = join(baseDir, timestamp);
|
|
1154
|
+
try {
|
|
1155
|
+
await mkdir(sessionDir, { recursive: true });
|
|
1156
|
+
return sessionDir;
|
|
1157
|
+
} catch (error) {
|
|
1158
|
+
console.warn(`[llmist] Failed to create log session directory: ${sessionDir}`, error);
|
|
1159
|
+
return void 0;
|
|
1160
|
+
}
|
|
1161
|
+
}
|
|
1162
|
+
function formatCallNumber(n) {
|
|
1163
|
+
return n.toString().padStart(4, "0");
|
|
1164
|
+
}
|
|
1116
1165
|
|
|
1117
1166
|
// src/cli/utils.ts
|
|
1118
1167
|
init_constants();
|
|
@@ -1276,7 +1325,7 @@ function formatBytes(bytes) {
|
|
|
1276
1325
|
}
|
|
1277
1326
|
return `${(bytes / (1024 * 1024)).toFixed(1)} MB`;
|
|
1278
1327
|
}
|
|
1279
|
-
function
|
|
1328
|
+
function formatGadgetSummary2(result) {
|
|
1280
1329
|
const gadgetLabel = chalk3.magenta.bold(result.gadgetName);
|
|
1281
1330
|
const timeLabel = chalk3.dim(`${Math.round(result.executionTimeMs)}ms`);
|
|
1282
1331
|
const paramsStr = formatParametersInline(result.parameters);
|
|
@@ -1361,12 +1410,21 @@ function isInteractive(stream) {
|
|
|
1361
1410
|
}
|
|
1362
1411
|
var ESC_KEY = 27;
|
|
1363
1412
|
var ESC_TIMEOUT_MS = 50;
|
|
1364
|
-
|
|
1413
|
+
var CTRL_C = 3;
|
|
1414
|
+
function createEscKeyListener(stdin, onEsc, onCtrlC) {
|
|
1365
1415
|
if (!stdin.isTTY || typeof stdin.setRawMode !== "function") {
|
|
1366
1416
|
return null;
|
|
1367
1417
|
}
|
|
1368
1418
|
let escTimeout = null;
|
|
1369
1419
|
const handleData = (data) => {
|
|
1420
|
+
if (data[0] === CTRL_C && onCtrlC) {
|
|
1421
|
+
if (escTimeout) {
|
|
1422
|
+
clearTimeout(escTimeout);
|
|
1423
|
+
escTimeout = null;
|
|
1424
|
+
}
|
|
1425
|
+
onCtrlC();
|
|
1426
|
+
return;
|
|
1427
|
+
}
|
|
1370
1428
|
if (data[0] === ESC_KEY) {
|
|
1371
1429
|
if (data.length === 1) {
|
|
1372
1430
|
escTimeout = setTimeout(() => {
|
|
@@ -1814,7 +1872,7 @@ function addCompleteOptions(cmd, defaults) {
|
|
|
1814
1872
|
OPTION_DESCRIPTIONS.maxTokens,
|
|
1815
1873
|
createNumericParser({ label: "Max tokens", integer: true, min: 1 }),
|
|
1816
1874
|
defaults?.["max-tokens"]
|
|
1817
|
-
).option(OPTION_FLAGS.quiet, OPTION_DESCRIPTIONS.quiet, defaults?.quiet).option(OPTION_FLAGS.logLlmRequests, OPTION_DESCRIPTIONS.logLlmRequests, defaults?.["log-llm-requests"])
|
|
1875
|
+
).option(OPTION_FLAGS.quiet, OPTION_DESCRIPTIONS.quiet, defaults?.quiet).option(OPTION_FLAGS.logLlmRequests, OPTION_DESCRIPTIONS.logLlmRequests, defaults?.["log-llm-requests"]);
|
|
1818
1876
|
}
|
|
1819
1877
|
function addAgentOptions(cmd, defaults) {
|
|
1820
1878
|
const gadgetAccumulator = (value, previous = []) => [
|
|
@@ -1838,7 +1896,7 @@ function addAgentOptions(cmd, defaults) {
|
|
|
1838
1896
|
OPTION_FLAGS.noBuiltinInteraction,
|
|
1839
1897
|
OPTION_DESCRIPTIONS.noBuiltinInteraction,
|
|
1840
1898
|
defaults?.["builtin-interaction"] !== false
|
|
1841
|
-
).option(OPTION_FLAGS.quiet, OPTION_DESCRIPTIONS.quiet, defaults?.quiet).option(OPTION_FLAGS.logLlmRequests, OPTION_DESCRIPTIONS.logLlmRequests, defaults?.["log-llm-requests"]).option(OPTION_FLAGS.
|
|
1899
|
+
).option(OPTION_FLAGS.quiet, OPTION_DESCRIPTIONS.quiet, defaults?.quiet).option(OPTION_FLAGS.logLlmRequests, OPTION_DESCRIPTIONS.logLlmRequests, defaults?.["log-llm-requests"]).option(OPTION_FLAGS.docker, OPTION_DESCRIPTIONS.docker).option(OPTION_FLAGS.dockerRo, OPTION_DESCRIPTIONS.dockerRo).option(OPTION_FLAGS.noDocker, OPTION_DESCRIPTIONS.noDocker).option(OPTION_FLAGS.dockerDev, OPTION_DESCRIPTIONS.dockerDev);
|
|
1842
1900
|
}
|
|
1843
1901
|
function configToCompleteOptions(config) {
|
|
1844
1902
|
const result = {};
|
|
@@ -1848,7 +1906,6 @@ function configToCompleteOptions(config) {
|
|
|
1848
1906
|
if (config["max-tokens"] !== void 0) result.maxTokens = config["max-tokens"];
|
|
1849
1907
|
if (config.quiet !== void 0) result.quiet = config.quiet;
|
|
1850
1908
|
if (config["log-llm-requests"] !== void 0) result.logLlmRequests = config["log-llm-requests"];
|
|
1851
|
-
if (config["log-llm-responses"] !== void 0) result.logLlmResponses = config["log-llm-responses"];
|
|
1852
1909
|
return result;
|
|
1853
1910
|
}
|
|
1854
1911
|
function configToAgentOptions(config) {
|
|
@@ -1872,7 +1929,6 @@ function configToAgentOptions(config) {
|
|
|
1872
1929
|
result.gadgetApproval = config["gadget-approval"];
|
|
1873
1930
|
if (config.quiet !== void 0) result.quiet = config.quiet;
|
|
1874
1931
|
if (config["log-llm-requests"] !== void 0) result.logLlmRequests = config["log-llm-requests"];
|
|
1875
|
-
if (config["log-llm-responses"] !== void 0) result.logLlmResponses = config["log-llm-responses"];
|
|
1876
1932
|
if (config.docker !== void 0) result.docker = config.docker;
|
|
1877
1933
|
if (config["docker-cwd-permission"] !== void 0)
|
|
1878
1934
|
result.dockerCwdPermission = config["docker-cwd-permission"];
|
|
@@ -1890,7 +1946,8 @@ var DOCKER_CONFIG_KEYS = /* @__PURE__ */ new Set([
|
|
|
1890
1946
|
"env-vars",
|
|
1891
1947
|
"image-name",
|
|
1892
1948
|
"dev-mode",
|
|
1893
|
-
"dev-source"
|
|
1949
|
+
"dev-source",
|
|
1950
|
+
"docker-args"
|
|
1894
1951
|
]);
|
|
1895
1952
|
var DEFAULT_IMAGE_NAME = "llmist-sandbox";
|
|
1896
1953
|
var DEFAULT_CWD_PERMISSION = "rw";
|
|
@@ -2005,7 +2062,6 @@ var COMPLETE_CONFIG_KEYS = /* @__PURE__ */ new Set([
|
|
|
2005
2062
|
"log-file",
|
|
2006
2063
|
"log-reset",
|
|
2007
2064
|
"log-llm-requests",
|
|
2008
|
-
"log-llm-responses",
|
|
2009
2065
|
"type",
|
|
2010
2066
|
// Allowed for inheritance compatibility, ignored for built-in commands
|
|
2011
2067
|
"docker",
|
|
@@ -2038,7 +2094,6 @@ var AGENT_CONFIG_KEYS = /* @__PURE__ */ new Set([
|
|
|
2038
2094
|
"log-file",
|
|
2039
2095
|
"log-reset",
|
|
2040
2096
|
"log-llm-requests",
|
|
2041
|
-
"log-llm-responses",
|
|
2042
2097
|
"type",
|
|
2043
2098
|
// Allowed for inheritance compatibility, ignored for built-in commands
|
|
2044
2099
|
"docker",
|
|
@@ -2226,13 +2281,6 @@ function validateCompleteConfig(raw, section) {
|
|
|
2226
2281
|
section
|
|
2227
2282
|
);
|
|
2228
2283
|
}
|
|
2229
|
-
if ("log-llm-responses" in rawObj) {
|
|
2230
|
-
result["log-llm-responses"] = validateStringOrBoolean(
|
|
2231
|
-
rawObj["log-llm-responses"],
|
|
2232
|
-
"log-llm-responses",
|
|
2233
|
-
section
|
|
2234
|
-
);
|
|
2235
|
-
}
|
|
2236
2284
|
return result;
|
|
2237
2285
|
}
|
|
2238
2286
|
function validateAgentConfig(raw, section) {
|
|
@@ -2311,13 +2359,6 @@ function validateAgentConfig(raw, section) {
|
|
|
2311
2359
|
section
|
|
2312
2360
|
);
|
|
2313
2361
|
}
|
|
2314
|
-
if ("log-llm-responses" in rawObj) {
|
|
2315
|
-
result["log-llm-responses"] = validateStringOrBoolean(
|
|
2316
|
-
rawObj["log-llm-responses"],
|
|
2317
|
-
"log-llm-responses",
|
|
2318
|
-
section
|
|
2319
|
-
);
|
|
2320
|
-
}
|
|
2321
2362
|
return result;
|
|
2322
2363
|
}
|
|
2323
2364
|
function validateStringOrBoolean(value, field, section) {
|
|
@@ -2759,6 +2800,9 @@ function validateDockerConfig(raw, section) {
|
|
|
2759
2800
|
if ("dev-source" in rawObj) {
|
|
2760
2801
|
result["dev-source"] = validateString2(rawObj["dev-source"], "dev-source", section);
|
|
2761
2802
|
}
|
|
2803
|
+
if ("docker-args" in rawObj) {
|
|
2804
|
+
result["docker-args"] = validateStringArray2(rawObj["docker-args"], "docker-args", section);
|
|
2805
|
+
}
|
|
2762
2806
|
return result;
|
|
2763
2807
|
}
|
|
2764
2808
|
|
|
@@ -2770,6 +2814,8 @@ FROM oven/bun:1-debian
|
|
|
2770
2814
|
|
|
2771
2815
|
# Install essential tools
|
|
2772
2816
|
RUN apt-get update && apt-get install -y --no-install-recommends \\
|
|
2817
|
+
# ed for EditFile gadget (line-oriented editor)
|
|
2818
|
+
ed \\
|
|
2773
2819
|
# ripgrep for fast file searching
|
|
2774
2820
|
ripgrep \\
|
|
2775
2821
|
# git for version control operations
|
|
@@ -2802,6 +2848,7 @@ FROM oven/bun:1-debian
|
|
|
2802
2848
|
|
|
2803
2849
|
# Install essential tools (same as production)
|
|
2804
2850
|
RUN apt-get update && apt-get install -y --no-install-recommends \\
|
|
2851
|
+
ed \\
|
|
2805
2852
|
ripgrep \\
|
|
2806
2853
|
git \\
|
|
2807
2854
|
curl \\
|
|
@@ -3036,6 +3083,9 @@ function buildDockerRunArgs(ctx, imageName, devMode) {
|
|
|
3036
3083
|
}
|
|
3037
3084
|
}
|
|
3038
3085
|
}
|
|
3086
|
+
if (ctx.config["docker-args"]) {
|
|
3087
|
+
args.push(...ctx.config["docker-args"]);
|
|
3088
|
+
}
|
|
3039
3089
|
args.push(imageName);
|
|
3040
3090
|
args.push(...ctx.forwardArgs);
|
|
3041
3091
|
return args;
|
|
@@ -3202,6 +3252,8 @@ async function executeAgent(promptArg, options, env) {
|
|
|
3202
3252
|
env.stderr.write(chalk5.yellow(`
|
|
3203
3253
|
[Cancelled] ${progress.formatStats()}
|
|
3204
3254
|
`));
|
|
3255
|
+
} else {
|
|
3256
|
+
handleQuit();
|
|
3205
3257
|
}
|
|
3206
3258
|
};
|
|
3207
3259
|
const keyboard = {
|
|
@@ -3209,7 +3261,7 @@ async function executeAgent(promptArg, options, env) {
|
|
|
3209
3261
|
cleanupSigint: null,
|
|
3210
3262
|
restore: () => {
|
|
3211
3263
|
if (stdinIsInteractive && stdinStream.isTTY && !wasCancelled) {
|
|
3212
|
-
keyboard.cleanupEsc = createEscKeyListener(stdinStream, handleCancel);
|
|
3264
|
+
keyboard.cleanupEsc = createEscKeyListener(stdinStream, handleCancel, handleCancel);
|
|
3213
3265
|
}
|
|
3214
3266
|
}
|
|
3215
3267
|
};
|
|
@@ -3234,7 +3286,7 @@ async function executeAgent(promptArg, options, env) {
|
|
|
3234
3286
|
process.exit(130);
|
|
3235
3287
|
};
|
|
3236
3288
|
if (stdinIsInteractive && stdinStream.isTTY) {
|
|
3237
|
-
keyboard.cleanupEsc = createEscKeyListener(stdinStream, handleCancel);
|
|
3289
|
+
keyboard.cleanupEsc = createEscKeyListener(stdinStream, handleCancel, handleCancel);
|
|
3238
3290
|
}
|
|
3239
3291
|
keyboard.cleanupSigint = createSigintListener(
|
|
3240
3292
|
handleCancel,
|
|
@@ -3260,11 +3312,11 @@ async function executeAgent(promptArg, options, env) {
|
|
|
3260
3312
|
gadgetApprovals,
|
|
3261
3313
|
defaultMode: "allowed"
|
|
3262
3314
|
};
|
|
3263
|
-
const approvalManager = new ApprovalManager(approvalConfig, env, progress);
|
|
3315
|
+
const approvalManager = new ApprovalManager(approvalConfig, env, progress, keyboard);
|
|
3264
3316
|
let usage;
|
|
3265
3317
|
let iterations = 0;
|
|
3266
|
-
const
|
|
3267
|
-
|
|
3318
|
+
const llmLogsBaseDir = resolveLogDir(options.logLlmRequests, "requests");
|
|
3319
|
+
let llmSessionDir;
|
|
3268
3320
|
let llmCallCounter = 0;
|
|
3269
3321
|
const countMessagesTokens = async (model, messages) => {
|
|
3270
3322
|
try {
|
|
@@ -3296,10 +3348,19 @@ async function executeAgent(promptArg, options, env) {
|
|
|
3296
3348
|
);
|
|
3297
3349
|
progress.startCall(context.options.model, inputTokens);
|
|
3298
3350
|
progress.setInputTokens(inputTokens, false);
|
|
3299
|
-
|
|
3300
|
-
|
|
3301
|
-
|
|
3302
|
-
|
|
3351
|
+
},
|
|
3352
|
+
// onLLMCallReady: Log the exact request being sent to the LLM
|
|
3353
|
+
// This fires AFTER controller modifications (e.g., trailing messages)
|
|
3354
|
+
onLLMCallReady: async (context) => {
|
|
3355
|
+
if (llmLogsBaseDir) {
|
|
3356
|
+
if (!llmSessionDir) {
|
|
3357
|
+
llmSessionDir = await createSessionDir(llmLogsBaseDir);
|
|
3358
|
+
}
|
|
3359
|
+
if (llmSessionDir) {
|
|
3360
|
+
const filename = `${formatCallNumber(llmCallCounter)}.request`;
|
|
3361
|
+
const content = formatLlmRequest(context.options.messages);
|
|
3362
|
+
await writeLogFile(llmSessionDir, filename, content);
|
|
3363
|
+
}
|
|
3303
3364
|
}
|
|
3304
3365
|
},
|
|
3305
3366
|
// onStreamChunk: Real-time updates as LLM generates tokens
|
|
@@ -3364,9 +3425,9 @@ async function executeAgent(promptArg, options, env) {
|
|
|
3364
3425
|
`);
|
|
3365
3426
|
}
|
|
3366
3427
|
}
|
|
3367
|
-
if (
|
|
3368
|
-
const filename = `${
|
|
3369
|
-
await writeLogFile(
|
|
3428
|
+
if (llmSessionDir) {
|
|
3429
|
+
const filename = `${formatCallNumber(llmCallCounter)}.response`;
|
|
3430
|
+
await writeLogFile(llmSessionDir, filename, context.rawResponse);
|
|
3370
3431
|
}
|
|
3371
3432
|
}
|
|
3372
3433
|
},
|
|
@@ -3463,6 +3524,13 @@ Denied: ${result.reason ?? "by user"}`
|
|
|
3463
3524
|
parameterMapping: (text) => ({ message: text, done: false, type: "info" }),
|
|
3464
3525
|
resultMapping: (text) => `\u2139\uFE0F ${text}`
|
|
3465
3526
|
});
|
|
3527
|
+
builder.withTrailingMessage(
|
|
3528
|
+
(ctx) => [
|
|
3529
|
+
`[Iteration ${ctx.iteration + 1}/${ctx.maxIterations}]`,
|
|
3530
|
+
"Think carefully: what gadget invocations can you make in parallel right now?",
|
|
3531
|
+
"Maximize efficiency by batching independent operations in a single response."
|
|
3532
|
+
].join(" ")
|
|
3533
|
+
);
|
|
3466
3534
|
const agent = builder.ask(prompt);
|
|
3467
3535
|
let textBuffer = "";
|
|
3468
3536
|
const flushTextBuffer = () => {
|
|
@@ -3488,7 +3556,7 @@ Denied: ${result.reason ?? "by user"}`
|
|
|
3488
3556
|
}
|
|
3489
3557
|
} else {
|
|
3490
3558
|
const tokenCount = await countGadgetOutputTokens(event.result.result);
|
|
3491
|
-
env.stderr.write(`${
|
|
3559
|
+
env.stderr.write(`${formatGadgetSummary2({ ...event.result, tokenCount })}
|
|
3492
3560
|
`);
|
|
3493
3561
|
}
|
|
3494
3562
|
}
|
|
@@ -3500,7 +3568,10 @@ Denied: ${result.reason ?? "by user"}`
|
|
|
3500
3568
|
} finally {
|
|
3501
3569
|
isStreaming = false;
|
|
3502
3570
|
keyboard.cleanupEsc?.();
|
|
3503
|
-
keyboard.cleanupSigint
|
|
3571
|
+
if (keyboard.cleanupSigint) {
|
|
3572
|
+
keyboard.cleanupSigint();
|
|
3573
|
+
process.once("SIGINT", () => process.exit(130));
|
|
3574
|
+
}
|
|
3504
3575
|
}
|
|
3505
3576
|
flushTextBuffer();
|
|
3506
3577
|
progress.complete();
|
|
@@ -3548,13 +3619,15 @@ async function executeComplete(promptArg, options, env) {
|
|
|
3548
3619
|
}
|
|
3549
3620
|
builder.addUser(prompt);
|
|
3550
3621
|
const messages = builder.build();
|
|
3551
|
-
const
|
|
3552
|
-
|
|
3553
|
-
|
|
3554
|
-
|
|
3555
|
-
|
|
3556
|
-
|
|
3557
|
-
|
|
3622
|
+
const llmLogsBaseDir = resolveLogDir(options.logLlmRequests, "requests");
|
|
3623
|
+
let llmSessionDir;
|
|
3624
|
+
if (llmLogsBaseDir) {
|
|
3625
|
+
llmSessionDir = await createSessionDir(llmLogsBaseDir);
|
|
3626
|
+
if (llmSessionDir) {
|
|
3627
|
+
const filename = "0001.request";
|
|
3628
|
+
const content = formatLlmRequest(messages);
|
|
3629
|
+
await writeLogFile(llmSessionDir, filename, content);
|
|
3630
|
+
}
|
|
3558
3631
|
}
|
|
3559
3632
|
const stream = client.stream({
|
|
3560
3633
|
model,
|
|
@@ -3593,9 +3666,9 @@ async function executeComplete(promptArg, options, env) {
|
|
|
3593
3666
|
progress.endCall(usage);
|
|
3594
3667
|
progress.complete();
|
|
3595
3668
|
printer.ensureNewline();
|
|
3596
|
-
if (
|
|
3597
|
-
const filename =
|
|
3598
|
-
await writeLogFile(
|
|
3669
|
+
if (llmSessionDir) {
|
|
3670
|
+
const filename = "0001.response";
|
|
3671
|
+
await writeLogFile(llmSessionDir, filename, accumulatedResponse);
|
|
3599
3672
|
}
|
|
3600
3673
|
if (stderrTTY && !options.quiet) {
|
|
3601
3674
|
const summary = renderSummary({ finishReason, usage, cost: progress.getTotalCost() });
|
|
@@ -3830,9 +3903,11 @@ ${issues}`);
|
|
|
3830
3903
|
env.stderr.write(chalk7.dim("\nExecuting...\n"));
|
|
3831
3904
|
const startTime = Date.now();
|
|
3832
3905
|
let result;
|
|
3906
|
+
let cost;
|
|
3833
3907
|
try {
|
|
3908
|
+
let rawResult;
|
|
3834
3909
|
if (gadget.timeoutMs && gadget.timeoutMs > 0) {
|
|
3835
|
-
|
|
3910
|
+
rawResult = await Promise.race([
|
|
3836
3911
|
Promise.resolve(gadget.execute(params)),
|
|
3837
3912
|
new Promise(
|
|
3838
3913
|
(_, reject) => setTimeout(
|
|
@@ -3842,15 +3917,18 @@ ${issues}`);
|
|
|
3842
3917
|
)
|
|
3843
3918
|
]);
|
|
3844
3919
|
} else {
|
|
3845
|
-
|
|
3920
|
+
rawResult = await Promise.resolve(gadget.execute(params));
|
|
3846
3921
|
}
|
|
3922
|
+
result = typeof rawResult === "string" ? rawResult : rawResult.result;
|
|
3923
|
+
cost = typeof rawResult === "object" ? rawResult.cost : void 0;
|
|
3847
3924
|
} catch (error) {
|
|
3848
3925
|
const message = error instanceof Error ? error.message : String(error);
|
|
3849
3926
|
throw new Error(`Execution failed: ${message}`);
|
|
3850
3927
|
}
|
|
3851
3928
|
const elapsed = Date.now() - startTime;
|
|
3929
|
+
const costInfo = cost !== void 0 && cost > 0 ? ` (Cost: $${cost.toFixed(6)})` : "";
|
|
3852
3930
|
env.stderr.write(chalk7.green(`
|
|
3853
|
-
\u2713 Completed in ${elapsed}ms
|
|
3931
|
+
\u2713 Completed in ${elapsed}ms${costInfo}
|
|
3854
3932
|
|
|
3855
3933
|
`));
|
|
3856
3934
|
formatOutput(result, options, env.stdout);
|