llmist 0.7.0 → 1.0.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-CTC2WJZA.js → chunk-53MM55JS.js} +40 -5
- package/dist/chunk-53MM55JS.js.map +1 -0
- package/dist/{chunk-ZFHFBEQ5.js → chunk-T24KLXY4.js} +673 -590
- package/dist/chunk-T24KLXY4.js.map +1 -0
- package/dist/cli.cjs +1287 -789
- package/dist/cli.cjs.map +1 -1
- package/dist/cli.js +532 -116
- package/dist/cli.js.map +1 -1
- package/dist/index.cjs +709 -593
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +69 -14
- package/dist/index.d.ts +69 -14
- package/dist/index.js +2 -2
- package/dist/{mock-stream-B2qwECvd.d.cts → mock-stream-DKF5yatf.d.cts} +61 -121
- package/dist/{mock-stream-B2qwECvd.d.ts → mock-stream-DKF5yatf.d.ts} +61 -121
- package/dist/testing/index.cjs +709 -593
- package/dist/testing/index.cjs.map +1 -1
- package/dist/testing/index.d.cts +2 -2
- package/dist/testing/index.d.ts +2 -2
- package/dist/testing/index.js +2 -2
- package/package.json +2 -1
- package/dist/chunk-CTC2WJZA.js.map +0 -1
- package/dist/chunk-ZFHFBEQ5.js.map +0 -1
package/dist/cli.js
CHANGED
|
@@ -22,7 +22,7 @@ import {
|
|
|
22
22
|
init_model_shortcuts,
|
|
23
23
|
init_registry,
|
|
24
24
|
resolveModel
|
|
25
|
-
} from "./chunk-
|
|
25
|
+
} from "./chunk-T24KLXY4.js";
|
|
26
26
|
|
|
27
27
|
// src/cli/constants.ts
|
|
28
28
|
var CLI_NAME = "llmist";
|
|
@@ -34,7 +34,6 @@ var COMMANDS = {
|
|
|
34
34
|
};
|
|
35
35
|
var LOG_LEVELS = ["silly", "trace", "debug", "info", "warn", "error", "fatal"];
|
|
36
36
|
var DEFAULT_MODEL = "openai:gpt-5-nano";
|
|
37
|
-
var DEFAULT_PARAMETER_FORMAT = "toml";
|
|
38
37
|
var OPTION_FLAGS = {
|
|
39
38
|
model: "-m, --model <identifier>",
|
|
40
39
|
systemPrompt: "-s, --system <prompt>",
|
|
@@ -42,12 +41,14 @@ var OPTION_FLAGS = {
|
|
|
42
41
|
maxTokens: "--max-tokens <count>",
|
|
43
42
|
maxIterations: "-i, --max-iterations <count>",
|
|
44
43
|
gadgetModule: "-g, --gadget <module>",
|
|
45
|
-
parameterFormat: "--parameter-format <format>",
|
|
46
44
|
logLevel: "--log-level <level>",
|
|
47
45
|
logFile: "--log-file <path>",
|
|
48
46
|
logReset: "--log-reset",
|
|
47
|
+
logLlmRequests: "--log-llm-requests [dir]",
|
|
48
|
+
logLlmResponses: "--log-llm-responses [dir]",
|
|
49
49
|
noBuiltins: "--no-builtins",
|
|
50
|
-
noBuiltinInteraction: "--no-builtin-interaction"
|
|
50
|
+
noBuiltinInteraction: "--no-builtin-interaction",
|
|
51
|
+
quiet: "-q, --quiet"
|
|
51
52
|
};
|
|
52
53
|
var OPTION_DESCRIPTIONS = {
|
|
53
54
|
model: "Model identifier, e.g. openai:gpt-5-nano or anthropic:claude-sonnet-4-5.",
|
|
@@ -56,22 +57,24 @@ var OPTION_DESCRIPTIONS = {
|
|
|
56
57
|
maxTokens: "Maximum number of output tokens requested from the model.",
|
|
57
58
|
maxIterations: "Maximum number of agent loop iterations before exiting.",
|
|
58
59
|
gadgetModule: "Path or module specifier for a gadget export. Repeat to register multiple gadgets.",
|
|
59
|
-
parameterFormat: "Format for gadget parameter schemas: 'json', 'yaml', 'toml', or 'auto'.",
|
|
60
60
|
logLevel: "Log level: silly, trace, debug, info, warn, error, fatal.",
|
|
61
61
|
logFile: "Path to log file. When set, logs are written to file instead of stderr.",
|
|
62
62
|
logReset: "Reset (truncate) the log file at session start instead of appending.",
|
|
63
|
+
logLlmRequests: "Save raw LLM requests as plain text. Optional dir, defaults to ~/.llmist/logs/requests/",
|
|
64
|
+
logLlmResponses: "Save raw LLM responses as plain text. Optional dir, defaults to ~/.llmist/logs/responses/",
|
|
63
65
|
noBuiltins: "Disable built-in gadgets (AskUser, TellUser).",
|
|
64
|
-
noBuiltinInteraction: "Disable interactive gadgets (AskUser) while keeping TellUser."
|
|
66
|
+
noBuiltinInteraction: "Disable interactive gadgets (AskUser) while keeping TellUser.",
|
|
67
|
+
quiet: "Suppress all output except content (text and TellUser messages)."
|
|
65
68
|
};
|
|
66
69
|
var SUMMARY_PREFIX = "[llmist]";
|
|
67
70
|
|
|
68
71
|
// src/cli/program.ts
|
|
69
|
-
import { Command, InvalidArgumentError as
|
|
72
|
+
import { Command, InvalidArgumentError as InvalidArgumentError2 } from "commander";
|
|
70
73
|
|
|
71
74
|
// package.json
|
|
72
75
|
var package_default = {
|
|
73
76
|
name: "llmist",
|
|
74
|
-
version: "0.
|
|
77
|
+
version: "0.8.0",
|
|
75
78
|
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.",
|
|
76
79
|
type: "module",
|
|
77
80
|
main: "dist/index.cjs",
|
|
@@ -155,6 +158,7 @@ var package_default = {
|
|
|
155
158
|
"@google/genai": "^1.27.0",
|
|
156
159
|
chalk: "^5.6.2",
|
|
157
160
|
commander: "^12.1.0",
|
|
161
|
+
eta: "^4.4.1",
|
|
158
162
|
"js-toml": "^1.0.2",
|
|
159
163
|
"js-yaml": "^4.1.0",
|
|
160
164
|
marked: "^15.0.12",
|
|
@@ -274,12 +278,19 @@ import fs from "node:fs";
|
|
|
274
278
|
import path from "node:path";
|
|
275
279
|
import { pathToFileURL } from "node:url";
|
|
276
280
|
var PATH_PREFIXES = [".", "/", "~"];
|
|
281
|
+
function isGadgetLike(value) {
|
|
282
|
+
if (typeof value !== "object" || value === null) {
|
|
283
|
+
return false;
|
|
284
|
+
}
|
|
285
|
+
const obj = value;
|
|
286
|
+
return typeof obj.execute === "function" && typeof obj.description === "string" && ("parameterSchema" in obj || "schema" in obj);
|
|
287
|
+
}
|
|
277
288
|
function isGadgetConstructor(value) {
|
|
278
289
|
if (typeof value !== "function") {
|
|
279
290
|
return false;
|
|
280
291
|
}
|
|
281
292
|
const prototype = value.prototype;
|
|
282
|
-
return Boolean(prototype) && prototype instanceof BaseGadget;
|
|
293
|
+
return Boolean(prototype) && (prototype instanceof BaseGadget || isGadgetLike(prototype));
|
|
283
294
|
}
|
|
284
295
|
function expandHomePath(input) {
|
|
285
296
|
if (!input.startsWith("~")) {
|
|
@@ -316,7 +327,7 @@ function extractGadgetsFromModule(moduleExports) {
|
|
|
316
327
|
return;
|
|
317
328
|
}
|
|
318
329
|
visited.add(value);
|
|
319
|
-
if (value instanceof BaseGadget) {
|
|
330
|
+
if (value instanceof BaseGadget || isGadgetLike(value)) {
|
|
320
331
|
results.push(value);
|
|
321
332
|
return;
|
|
322
333
|
}
|
|
@@ -365,8 +376,33 @@ async function loadGadgets(specifiers, cwd, importer = (specifier) => import(spe
|
|
|
365
376
|
return gadgets;
|
|
366
377
|
}
|
|
367
378
|
|
|
368
|
-
// src/cli/
|
|
369
|
-
import {
|
|
379
|
+
// src/cli/llm-logging.ts
|
|
380
|
+
import { mkdir, writeFile } from "node:fs/promises";
|
|
381
|
+
import { homedir } from "node:os";
|
|
382
|
+
import { join } from "node:path";
|
|
383
|
+
var DEFAULT_LLM_LOG_DIR = join(homedir(), ".llmist", "logs");
|
|
384
|
+
function resolveLogDir(option, subdir) {
|
|
385
|
+
if (option === true) {
|
|
386
|
+
return join(DEFAULT_LLM_LOG_DIR, subdir);
|
|
387
|
+
}
|
|
388
|
+
if (typeof option === "string") {
|
|
389
|
+
return option;
|
|
390
|
+
}
|
|
391
|
+
return void 0;
|
|
392
|
+
}
|
|
393
|
+
function formatLlmRequest(messages) {
|
|
394
|
+
const lines = [];
|
|
395
|
+
for (const msg of messages) {
|
|
396
|
+
lines.push(`=== ${msg.role.toUpperCase()} ===`);
|
|
397
|
+
lines.push(msg.content ?? "");
|
|
398
|
+
lines.push("");
|
|
399
|
+
}
|
|
400
|
+
return lines.join("\n");
|
|
401
|
+
}
|
|
402
|
+
async function writeLogFile(dir, filename, content) {
|
|
403
|
+
await mkdir(dir, { recursive: true });
|
|
404
|
+
await writeFile(join(dir, filename), content, "utf-8");
|
|
405
|
+
}
|
|
370
406
|
|
|
371
407
|
// src/cli/utils.ts
|
|
372
408
|
init_constants();
|
|
@@ -410,9 +446,29 @@ function ensureMarkedConfigured() {
|
|
|
410
446
|
}
|
|
411
447
|
function renderMarkdown(text) {
|
|
412
448
|
ensureMarkedConfigured();
|
|
413
|
-
|
|
449
|
+
let rendered = marked.parse(text);
|
|
450
|
+
rendered = rendered.replace(/\*\*(.+?)\*\*/g, (_, content) => chalk.bold(content)).replace(/(?<!\*)\*(\S[^*]*)\*(?!\*)/g, (_, content) => chalk.italic(content));
|
|
414
451
|
return rendered.trimEnd();
|
|
415
452
|
}
|
|
453
|
+
function createRainbowSeparator() {
|
|
454
|
+
const colors = [chalk.red, chalk.yellow, chalk.green, chalk.cyan, chalk.blue, chalk.magenta];
|
|
455
|
+
const char = "\u2500";
|
|
456
|
+
const width = process.stdout.columns || 80;
|
|
457
|
+
let result = "";
|
|
458
|
+
for (let i = 0; i < width; i++) {
|
|
459
|
+
result += colors[i % colors.length](char);
|
|
460
|
+
}
|
|
461
|
+
return result;
|
|
462
|
+
}
|
|
463
|
+
function renderMarkdownWithSeparators(text) {
|
|
464
|
+
const rendered = renderMarkdown(text);
|
|
465
|
+
const separator = createRainbowSeparator();
|
|
466
|
+
return `
|
|
467
|
+
${separator}
|
|
468
|
+
${rendered}
|
|
469
|
+
${separator}
|
|
470
|
+
`;
|
|
471
|
+
}
|
|
416
472
|
function formatTokens(tokens) {
|
|
417
473
|
return tokens >= 1e3 ? `${(tokens / 1e3).toFixed(1)}k` : `${tokens}`;
|
|
418
474
|
}
|
|
@@ -441,8 +497,14 @@ function renderSummary(metadata) {
|
|
|
441
497
|
parts.push(chalk.magenta(metadata.model));
|
|
442
498
|
}
|
|
443
499
|
if (metadata.usage) {
|
|
444
|
-
const { inputTokens, outputTokens } = metadata.usage;
|
|
500
|
+
const { inputTokens, outputTokens, cachedInputTokens, cacheCreationInputTokens } = metadata.usage;
|
|
445
501
|
parts.push(chalk.dim("\u2191") + chalk.yellow(` ${formatTokens(inputTokens)}`));
|
|
502
|
+
if (cachedInputTokens && cachedInputTokens > 0) {
|
|
503
|
+
parts.push(chalk.dim("\u27F3") + chalk.blue(` ${formatTokens(cachedInputTokens)}`));
|
|
504
|
+
}
|
|
505
|
+
if (cacheCreationInputTokens && cacheCreationInputTokens > 0) {
|
|
506
|
+
parts.push(chalk.dim("\u270E") + chalk.magenta(` ${formatTokens(cacheCreationInputTokens)}`));
|
|
507
|
+
}
|
|
446
508
|
parts.push(chalk.dim("\u2193") + chalk.green(` ${formatTokens(outputTokens)}`));
|
|
447
509
|
}
|
|
448
510
|
if (metadata.elapsedSeconds !== void 0 && metadata.elapsedSeconds > 0) {
|
|
@@ -526,7 +588,7 @@ function formatGadgetSummary(result) {
|
|
|
526
588
|
const summaryLine = `${icon} ${gadgetLabel}${paramsLabel} ${chalk.dim("\u2192")} ${outputLabel} ${timeLabel}`;
|
|
527
589
|
if (result.gadgetName === "TellUser" && result.parameters?.message) {
|
|
528
590
|
const message = String(result.parameters.message);
|
|
529
|
-
const rendered =
|
|
591
|
+
const rendered = renderMarkdownWithSeparators(message);
|
|
530
592
|
return `${summaryLine}
|
|
531
593
|
${rendered}`;
|
|
532
594
|
}
|
|
@@ -611,6 +673,9 @@ var StreamProgress = class {
|
|
|
611
673
|
callOutputTokensEstimated = true;
|
|
612
674
|
callOutputChars = 0;
|
|
613
675
|
isStreaming = false;
|
|
676
|
+
// Cache token tracking for live cost estimation during streaming
|
|
677
|
+
callCachedInputTokens = 0;
|
|
678
|
+
callCacheCreationInputTokens = 0;
|
|
614
679
|
// Cumulative stats (cumulative mode)
|
|
615
680
|
totalStartTime = Date.now();
|
|
616
681
|
totalTokens = 0;
|
|
@@ -636,11 +701,13 @@ var StreamProgress = class {
|
|
|
636
701
|
this.callOutputTokensEstimated = true;
|
|
637
702
|
this.callOutputChars = 0;
|
|
638
703
|
this.isStreaming = false;
|
|
704
|
+
this.callCachedInputTokens = 0;
|
|
705
|
+
this.callCacheCreationInputTokens = 0;
|
|
639
706
|
this.start();
|
|
640
707
|
}
|
|
641
708
|
/**
|
|
642
709
|
* Ends the current LLM call. Updates cumulative stats and switches to cumulative mode.
|
|
643
|
-
* @param usage - Final token usage from the call
|
|
710
|
+
* @param usage - Final token usage from the call (including cached tokens if available)
|
|
644
711
|
*/
|
|
645
712
|
endCall(usage) {
|
|
646
713
|
this.iterations++;
|
|
@@ -652,7 +719,9 @@ var StreamProgress = class {
|
|
|
652
719
|
const cost = this.modelRegistry.estimateCost(
|
|
653
720
|
modelName,
|
|
654
721
|
usage.inputTokens,
|
|
655
|
-
usage.outputTokens
|
|
722
|
+
usage.outputTokens,
|
|
723
|
+
usage.cachedInputTokens ?? 0,
|
|
724
|
+
usage.cacheCreationInputTokens ?? 0
|
|
656
725
|
);
|
|
657
726
|
if (cost) {
|
|
658
727
|
this.totalCost += cost.totalCost;
|
|
@@ -692,6 +761,16 @@ var StreamProgress = class {
|
|
|
692
761
|
this.callOutputTokens = tokens;
|
|
693
762
|
this.callOutputTokensEstimated = estimated;
|
|
694
763
|
}
|
|
764
|
+
/**
|
|
765
|
+
* Sets cached token counts for the current call (from stream metadata).
|
|
766
|
+
* Used for live cost estimation during streaming.
|
|
767
|
+
* @param cachedInputTokens - Number of tokens read from cache (cheaper)
|
|
768
|
+
* @param cacheCreationInputTokens - Number of tokens written to cache (more expensive)
|
|
769
|
+
*/
|
|
770
|
+
setCachedTokens(cachedInputTokens, cacheCreationInputTokens) {
|
|
771
|
+
this.callCachedInputTokens = cachedInputTokens;
|
|
772
|
+
this.callCacheCreationInputTokens = cacheCreationInputTokens;
|
|
773
|
+
}
|
|
695
774
|
/**
|
|
696
775
|
* Get total elapsed time in seconds since the first call started.
|
|
697
776
|
* @returns Elapsed time in seconds with 1 decimal place
|
|
@@ -756,11 +835,32 @@ var StreamProgress = class {
|
|
|
756
835
|
parts.push(chalk2.dim("\u2193") + chalk2.green(` ${prefix}${formatTokens(outTokens)}`));
|
|
757
836
|
}
|
|
758
837
|
parts.push(chalk2.dim(`${elapsed}s`));
|
|
759
|
-
|
|
760
|
-
|
|
838
|
+
const callCost = this.calculateCurrentCallCost(outTokens);
|
|
839
|
+
if (callCost > 0) {
|
|
840
|
+
parts.push(chalk2.cyan(`$${formatCost(callCost)}`));
|
|
761
841
|
}
|
|
762
842
|
this.target.write(`\r${parts.join(chalk2.dim(" | "))} ${chalk2.cyan(spinner)}`);
|
|
763
843
|
}
|
|
844
|
+
/**
|
|
845
|
+
* Calculates live cost estimate for the current streaming call.
|
|
846
|
+
* Uses current input/output tokens and cached token counts.
|
|
847
|
+
*/
|
|
848
|
+
calculateCurrentCallCost(outputTokens) {
|
|
849
|
+
if (!this.modelRegistry || !this.model) return 0;
|
|
850
|
+
try {
|
|
851
|
+
const modelName = this.model.includes(":") ? this.model.split(":")[1] : this.model;
|
|
852
|
+
const cost = this.modelRegistry.estimateCost(
|
|
853
|
+
modelName,
|
|
854
|
+
this.callInputTokens,
|
|
855
|
+
outputTokens,
|
|
856
|
+
this.callCachedInputTokens,
|
|
857
|
+
this.callCacheCreationInputTokens
|
|
858
|
+
);
|
|
859
|
+
return cost?.totalCost ?? 0;
|
|
860
|
+
} catch {
|
|
861
|
+
return 0;
|
|
862
|
+
}
|
|
863
|
+
}
|
|
764
864
|
renderCumulativeMode(spinner) {
|
|
765
865
|
const elapsed = ((Date.now() - this.totalStartTime) / 1e3).toFixed(1);
|
|
766
866
|
const parts = [];
|
|
@@ -795,7 +895,7 @@ var StreamProgress = class {
|
|
|
795
895
|
}
|
|
796
896
|
this.isRunning = false;
|
|
797
897
|
if (this.hasRendered) {
|
|
798
|
-
this.target.write("\r\x1B[K");
|
|
898
|
+
this.target.write("\r\x1B[K\x1B[0G");
|
|
799
899
|
this.hasRendered = false;
|
|
800
900
|
}
|
|
801
901
|
}
|
|
@@ -888,16 +988,6 @@ async function executeAction(action, env) {
|
|
|
888
988
|
}
|
|
889
989
|
|
|
890
990
|
// src/cli/option-helpers.ts
|
|
891
|
-
var PARAMETER_FORMAT_VALUES = ["json", "yaml", "toml", "auto"];
|
|
892
|
-
function parseParameterFormat(value) {
|
|
893
|
-
const normalized = value.toLowerCase();
|
|
894
|
-
if (!PARAMETER_FORMAT_VALUES.includes(normalized)) {
|
|
895
|
-
throw new InvalidArgumentError2(
|
|
896
|
-
`Parameter format must be one of: ${PARAMETER_FORMAT_VALUES.join(", ")}`
|
|
897
|
-
);
|
|
898
|
-
}
|
|
899
|
-
return normalized;
|
|
900
|
-
}
|
|
901
991
|
function addCompleteOptions(cmd, defaults) {
|
|
902
992
|
return cmd.option(OPTION_FLAGS.model, OPTION_DESCRIPTIONS.model, defaults?.model ?? DEFAULT_MODEL).option(OPTION_FLAGS.systemPrompt, OPTION_DESCRIPTIONS.systemPrompt, defaults?.system).option(
|
|
903
993
|
OPTION_FLAGS.temperature,
|
|
@@ -909,7 +999,7 @@ function addCompleteOptions(cmd, defaults) {
|
|
|
909
999
|
OPTION_DESCRIPTIONS.maxTokens,
|
|
910
1000
|
createNumericParser({ label: "Max tokens", integer: true, min: 1 }),
|
|
911
1001
|
defaults?.["max-tokens"]
|
|
912
|
-
);
|
|
1002
|
+
).option(OPTION_FLAGS.quiet, OPTION_DESCRIPTIONS.quiet, defaults?.quiet).option(OPTION_FLAGS.logLlmRequests, OPTION_DESCRIPTIONS.logLlmRequests, defaults?.["log-llm-requests"]).option(OPTION_FLAGS.logLlmResponses, OPTION_DESCRIPTIONS.logLlmResponses, defaults?.["log-llm-responses"]);
|
|
913
1003
|
}
|
|
914
1004
|
function addAgentOptions(cmd, defaults) {
|
|
915
1005
|
const gadgetAccumulator = (value, previous = []) => [
|
|
@@ -929,16 +1019,11 @@ function addAgentOptions(cmd, defaults) {
|
|
|
929
1019
|
defaults?.["max-iterations"]
|
|
930
1020
|
).option(OPTION_FLAGS.gadgetModule, OPTION_DESCRIPTIONS.gadgetModule, gadgetAccumulator, [
|
|
931
1021
|
...defaultGadgets
|
|
932
|
-
]).option(
|
|
933
|
-
OPTION_FLAGS.parameterFormat,
|
|
934
|
-
OPTION_DESCRIPTIONS.parameterFormat,
|
|
935
|
-
parseParameterFormat,
|
|
936
|
-
defaults?.["parameter-format"] ?? DEFAULT_PARAMETER_FORMAT
|
|
937
|
-
).option(OPTION_FLAGS.noBuiltins, OPTION_DESCRIPTIONS.noBuiltins, defaults?.builtins !== false).option(
|
|
1022
|
+
]).option(OPTION_FLAGS.noBuiltins, OPTION_DESCRIPTIONS.noBuiltins, defaults?.builtins !== false).option(
|
|
938
1023
|
OPTION_FLAGS.noBuiltinInteraction,
|
|
939
1024
|
OPTION_DESCRIPTIONS.noBuiltinInteraction,
|
|
940
1025
|
defaults?.["builtin-interaction"] !== false
|
|
941
|
-
);
|
|
1026
|
+
).option(OPTION_FLAGS.quiet, OPTION_DESCRIPTIONS.quiet, defaults?.quiet).option(OPTION_FLAGS.logLlmRequests, OPTION_DESCRIPTIONS.logLlmRequests, defaults?.["log-llm-requests"]).option(OPTION_FLAGS.logLlmResponses, OPTION_DESCRIPTIONS.logLlmResponses, defaults?.["log-llm-responses"]);
|
|
942
1027
|
}
|
|
943
1028
|
function configToCompleteOptions(config) {
|
|
944
1029
|
const result = {};
|
|
@@ -946,6 +1031,9 @@ function configToCompleteOptions(config) {
|
|
|
946
1031
|
if (config.system !== void 0) result.system = config.system;
|
|
947
1032
|
if (config.temperature !== void 0) result.temperature = config.temperature;
|
|
948
1033
|
if (config["max-tokens"] !== void 0) result.maxTokens = config["max-tokens"];
|
|
1034
|
+
if (config.quiet !== void 0) result.quiet = config.quiet;
|
|
1035
|
+
if (config["log-llm-requests"] !== void 0) result.logLlmRequests = config["log-llm-requests"];
|
|
1036
|
+
if (config["log-llm-responses"] !== void 0) result.logLlmResponses = config["log-llm-responses"];
|
|
949
1037
|
return result;
|
|
950
1038
|
}
|
|
951
1039
|
function configToAgentOptions(config) {
|
|
@@ -955,7 +1043,6 @@ function configToAgentOptions(config) {
|
|
|
955
1043
|
if (config.temperature !== void 0) result.temperature = config.temperature;
|
|
956
1044
|
if (config["max-iterations"] !== void 0) result.maxIterations = config["max-iterations"];
|
|
957
1045
|
if (config.gadget !== void 0) result.gadget = config.gadget;
|
|
958
|
-
if (config["parameter-format"] !== void 0) result.parameterFormat = config["parameter-format"];
|
|
959
1046
|
if (config.builtins !== void 0) result.builtins = config.builtins;
|
|
960
1047
|
if (config["builtin-interaction"] !== void 0)
|
|
961
1048
|
result.builtinInteraction = config["builtin-interaction"];
|
|
@@ -963,6 +1050,11 @@ function configToAgentOptions(config) {
|
|
|
963
1050
|
result.gadgetStartPrefix = config["gadget-start-prefix"];
|
|
964
1051
|
if (config["gadget-end-prefix"] !== void 0)
|
|
965
1052
|
result.gadgetEndPrefix = config["gadget-end-prefix"];
|
|
1053
|
+
if (config["gadget-arg-prefix"] !== void 0)
|
|
1054
|
+
result.gadgetArgPrefix = config["gadget-arg-prefix"];
|
|
1055
|
+
if (config.quiet !== void 0) result.quiet = config.quiet;
|
|
1056
|
+
if (config["log-llm-requests"] !== void 0) result.logLlmRequests = config["log-llm-requests"];
|
|
1057
|
+
if (config["log-llm-responses"] !== void 0) result.logLlmResponses = config["log-llm-responses"];
|
|
966
1058
|
return result;
|
|
967
1059
|
}
|
|
968
1060
|
|
|
@@ -986,7 +1078,7 @@ function createHumanInputHandler(env, progress) {
|
|
|
986
1078
|
const rl = createInterface({ input: env.stdin, output: env.stdout });
|
|
987
1079
|
try {
|
|
988
1080
|
const questionLine = question.trim() ? `
|
|
989
|
-
${
|
|
1081
|
+
${renderMarkdownWithSeparators(question.trim())}` : "";
|
|
990
1082
|
let isFirst = true;
|
|
991
1083
|
while (true) {
|
|
992
1084
|
const statsPrompt = progress.formatPrompt();
|
|
@@ -1008,9 +1100,10 @@ async function executeAgent(promptArg, options, env) {
|
|
|
1008
1100
|
const prompt = await resolvePrompt(promptArg, env);
|
|
1009
1101
|
const client = env.createClient();
|
|
1010
1102
|
const registry = new GadgetRegistry();
|
|
1103
|
+
const stdinIsInteractive = isInteractive(env.stdin);
|
|
1011
1104
|
if (options.builtins !== false) {
|
|
1012
1105
|
for (const gadget of builtinGadgets) {
|
|
1013
|
-
if (
|
|
1106
|
+
if (gadget.name === "AskUser" && (options.builtinInteraction === false || !stdinIsInteractive)) {
|
|
1014
1107
|
continue;
|
|
1015
1108
|
}
|
|
1016
1109
|
registry.registerByClass(gadget);
|
|
@@ -1028,6 +1121,9 @@ async function executeAgent(promptArg, options, env) {
|
|
|
1028
1121
|
const progress = new StreamProgress(env.stderr, stderrTTY, client.modelRegistry);
|
|
1029
1122
|
let usage;
|
|
1030
1123
|
let iterations = 0;
|
|
1124
|
+
const llmRequestsDir = resolveLogDir(options.logLlmRequests, "requests");
|
|
1125
|
+
const llmResponsesDir = resolveLogDir(options.logLlmResponses, "responses");
|
|
1126
|
+
let llmCallCounter = 0;
|
|
1031
1127
|
const countMessagesTokens = async (model, messages) => {
|
|
1032
1128
|
try {
|
|
1033
1129
|
return await client.countTokens(model, messages);
|
|
@@ -1050,12 +1146,18 @@ async function executeAgent(promptArg, options, env) {
|
|
|
1050
1146
|
// onLLMCallStart: Start progress indicator for each LLM call
|
|
1051
1147
|
// This showcases how to react to agent lifecycle events
|
|
1052
1148
|
onLLMCallStart: async (context) => {
|
|
1149
|
+
llmCallCounter++;
|
|
1053
1150
|
const inputTokens = await countMessagesTokens(
|
|
1054
1151
|
context.options.model,
|
|
1055
1152
|
context.options.messages
|
|
1056
1153
|
);
|
|
1057
1154
|
progress.startCall(context.options.model, inputTokens);
|
|
1058
1155
|
progress.setInputTokens(inputTokens, false);
|
|
1156
|
+
if (llmRequestsDir) {
|
|
1157
|
+
const filename = `${Date.now()}_call_${llmCallCounter}.request.txt`;
|
|
1158
|
+
const content = formatLlmRequest(context.options.messages);
|
|
1159
|
+
await writeLogFile(llmRequestsDir, filename, content);
|
|
1160
|
+
}
|
|
1059
1161
|
},
|
|
1060
1162
|
// onStreamChunk: Real-time updates as LLM generates tokens
|
|
1061
1163
|
// This enables responsive UIs that show progress during generation
|
|
@@ -1068,6 +1170,10 @@ async function executeAgent(promptArg, options, env) {
|
|
|
1068
1170
|
if (context.usage.outputTokens) {
|
|
1069
1171
|
progress.setOutputTokens(context.usage.outputTokens, false);
|
|
1070
1172
|
}
|
|
1173
|
+
progress.setCachedTokens(
|
|
1174
|
+
context.usage.cachedInputTokens ?? 0,
|
|
1175
|
+
context.usage.cacheCreationInputTokens ?? 0
|
|
1176
|
+
);
|
|
1071
1177
|
}
|
|
1072
1178
|
},
|
|
1073
1179
|
// onLLMCallComplete: Finalize metrics after each LLM call
|
|
@@ -1086,11 +1192,13 @@ async function executeAgent(promptArg, options, env) {
|
|
|
1086
1192
|
let callCost;
|
|
1087
1193
|
if (context.usage && client.modelRegistry) {
|
|
1088
1194
|
try {
|
|
1089
|
-
const modelName = options.model.includes(":") ? options.model.split(":")[1] : options.model;
|
|
1195
|
+
const modelName = context.options.model.includes(":") ? context.options.model.split(":")[1] : context.options.model;
|
|
1090
1196
|
const costResult = client.modelRegistry.estimateCost(
|
|
1091
1197
|
modelName,
|
|
1092
1198
|
context.usage.inputTokens,
|
|
1093
|
-
context.usage.outputTokens
|
|
1199
|
+
context.usage.outputTokens,
|
|
1200
|
+
context.usage.cachedInputTokens ?? 0,
|
|
1201
|
+
context.usage.cacheCreationInputTokens ?? 0
|
|
1094
1202
|
);
|
|
1095
1203
|
if (costResult) callCost = costResult.totalCost;
|
|
1096
1204
|
} catch {
|
|
@@ -1098,7 +1206,7 @@ async function executeAgent(promptArg, options, env) {
|
|
|
1098
1206
|
}
|
|
1099
1207
|
const callElapsed = progress.getCallElapsedSeconds();
|
|
1100
1208
|
progress.endCall(context.usage);
|
|
1101
|
-
if (
|
|
1209
|
+
if (!options.quiet) {
|
|
1102
1210
|
const summary = renderSummary({
|
|
1103
1211
|
iterations: context.iteration + 1,
|
|
1104
1212
|
model: options.model,
|
|
@@ -1112,6 +1220,10 @@ async function executeAgent(promptArg, options, env) {
|
|
|
1112
1220
|
`);
|
|
1113
1221
|
}
|
|
1114
1222
|
}
|
|
1223
|
+
if (llmResponsesDir) {
|
|
1224
|
+
const filename = `${Date.now()}_call_${llmCallCounter}.response.txt`;
|
|
1225
|
+
await writeLogFile(llmResponsesDir, filename, context.rawResponse);
|
|
1226
|
+
}
|
|
1115
1227
|
}
|
|
1116
1228
|
},
|
|
1117
1229
|
// SHOWCASE: Controller-based approval gating for dangerous gadgets
|
|
@@ -1176,13 +1288,15 @@ Command rejected by user with message: "${response}"`
|
|
|
1176
1288
|
if (gadgets.length > 0) {
|
|
1177
1289
|
builder.withGadgets(...gadgets);
|
|
1178
1290
|
}
|
|
1179
|
-
builder.withParameterFormat(options.parameterFormat);
|
|
1180
1291
|
if (options.gadgetStartPrefix) {
|
|
1181
1292
|
builder.withGadgetStartPrefix(options.gadgetStartPrefix);
|
|
1182
1293
|
}
|
|
1183
1294
|
if (options.gadgetEndPrefix) {
|
|
1184
1295
|
builder.withGadgetEndPrefix(options.gadgetEndPrefix);
|
|
1185
1296
|
}
|
|
1297
|
+
if (options.gadgetArgPrefix) {
|
|
1298
|
+
builder.withGadgetArgPrefix(options.gadgetArgPrefix);
|
|
1299
|
+
}
|
|
1186
1300
|
builder.withSyntheticGadgetCall(
|
|
1187
1301
|
"TellUser",
|
|
1188
1302
|
{
|
|
@@ -1199,22 +1313,38 @@ Command rejected by user with message: "${response}"`
|
|
|
1199
1313
|
resultMapping: (text) => `\u2139\uFE0F ${text}`
|
|
1200
1314
|
});
|
|
1201
1315
|
const agent = builder.ask(prompt);
|
|
1316
|
+
let textBuffer = "";
|
|
1317
|
+
const flushTextBuffer = () => {
|
|
1318
|
+
if (textBuffer) {
|
|
1319
|
+
const output = options.quiet ? textBuffer : renderMarkdownWithSeparators(textBuffer);
|
|
1320
|
+
printer.write(output);
|
|
1321
|
+
textBuffer = "";
|
|
1322
|
+
}
|
|
1323
|
+
};
|
|
1202
1324
|
for await (const event of agent.run()) {
|
|
1203
1325
|
if (event.type === "text") {
|
|
1204
1326
|
progress.pause();
|
|
1205
|
-
|
|
1327
|
+
textBuffer += event.content;
|
|
1206
1328
|
} else if (event.type === "gadget_result") {
|
|
1329
|
+
flushTextBuffer();
|
|
1207
1330
|
progress.pause();
|
|
1208
|
-
if (
|
|
1331
|
+
if (options.quiet) {
|
|
1332
|
+
if (event.result.gadgetName === "TellUser" && event.result.parameters?.message) {
|
|
1333
|
+
const message = String(event.result.parameters.message);
|
|
1334
|
+
env.stdout.write(`${message}
|
|
1335
|
+
`);
|
|
1336
|
+
}
|
|
1337
|
+
} else {
|
|
1209
1338
|
const tokenCount = await countGadgetOutputTokens(event.result.result);
|
|
1210
1339
|
env.stderr.write(`${formatGadgetSummary({ ...event.result, tokenCount })}
|
|
1211
1340
|
`);
|
|
1212
1341
|
}
|
|
1213
1342
|
}
|
|
1214
1343
|
}
|
|
1344
|
+
flushTextBuffer();
|
|
1215
1345
|
progress.complete();
|
|
1216
1346
|
printer.ensureNewline();
|
|
1217
|
-
if (
|
|
1347
|
+
if (!options.quiet && iterations > 1) {
|
|
1218
1348
|
env.stderr.write(`${chalk3.dim("\u2500".repeat(40))}
|
|
1219
1349
|
`);
|
|
1220
1350
|
const summary = renderOverallSummary({
|
|
@@ -1250,9 +1380,18 @@ async function executeComplete(promptArg, options, env) {
|
|
|
1250
1380
|
builder.addSystem(options.system);
|
|
1251
1381
|
}
|
|
1252
1382
|
builder.addUser(prompt);
|
|
1383
|
+
const messages = builder.build();
|
|
1384
|
+
const llmRequestsDir = resolveLogDir(options.logLlmRequests, "requests");
|
|
1385
|
+
const llmResponsesDir = resolveLogDir(options.logLlmResponses, "responses");
|
|
1386
|
+
const timestamp = Date.now();
|
|
1387
|
+
if (llmRequestsDir) {
|
|
1388
|
+
const filename = `${timestamp}_complete.request.txt`;
|
|
1389
|
+
const content = formatLlmRequest(messages);
|
|
1390
|
+
await writeLogFile(llmRequestsDir, filename, content);
|
|
1391
|
+
}
|
|
1253
1392
|
const stream = client.stream({
|
|
1254
1393
|
model,
|
|
1255
|
-
messages
|
|
1394
|
+
messages,
|
|
1256
1395
|
temperature: options.temperature,
|
|
1257
1396
|
maxTokens: options.maxTokens
|
|
1258
1397
|
});
|
|
@@ -1263,7 +1402,7 @@ async function executeComplete(promptArg, options, env) {
|
|
|
1263
1402
|
progress.startCall(model, estimatedInputTokens);
|
|
1264
1403
|
let finishReason;
|
|
1265
1404
|
let usage;
|
|
1266
|
-
let
|
|
1405
|
+
let accumulatedResponse = "";
|
|
1267
1406
|
for await (const chunk of stream) {
|
|
1268
1407
|
if (chunk.usage) {
|
|
1269
1408
|
usage = chunk.usage;
|
|
@@ -1276,8 +1415,8 @@ async function executeComplete(promptArg, options, env) {
|
|
|
1276
1415
|
}
|
|
1277
1416
|
if (chunk.text) {
|
|
1278
1417
|
progress.pause();
|
|
1279
|
-
|
|
1280
|
-
progress.update(
|
|
1418
|
+
accumulatedResponse += chunk.text;
|
|
1419
|
+
progress.update(accumulatedResponse.length);
|
|
1281
1420
|
printer.write(chunk.text);
|
|
1282
1421
|
}
|
|
1283
1422
|
if (chunk.finishReason !== void 0) {
|
|
@@ -1287,7 +1426,11 @@ async function executeComplete(promptArg, options, env) {
|
|
|
1287
1426
|
progress.endCall(usage);
|
|
1288
1427
|
progress.complete();
|
|
1289
1428
|
printer.ensureNewline();
|
|
1290
|
-
if (
|
|
1429
|
+
if (llmResponsesDir) {
|
|
1430
|
+
const filename = `${timestamp}_complete.response.txt`;
|
|
1431
|
+
await writeLogFile(llmResponsesDir, filename, accumulatedResponse);
|
|
1432
|
+
}
|
|
1433
|
+
if (stderrTTY && !options.quiet) {
|
|
1291
1434
|
const summary = renderSummary({ finishReason, usage, cost: progress.getTotalCost() });
|
|
1292
1435
|
if (summary) {
|
|
1293
1436
|
env.stderr.write(`${summary}
|
|
@@ -1305,36 +1448,136 @@ function registerCompleteCommand(program, env, config) {
|
|
|
1305
1448
|
|
|
1306
1449
|
// src/cli/config.ts
|
|
1307
1450
|
import { existsSync, readFileSync } from "node:fs";
|
|
1308
|
-
import { homedir } from "node:os";
|
|
1309
|
-
import { join } from "node:path";
|
|
1451
|
+
import { homedir as homedir2 } from "node:os";
|
|
1452
|
+
import { join as join2 } from "node:path";
|
|
1310
1453
|
import { load as parseToml } from "js-toml";
|
|
1454
|
+
|
|
1455
|
+
// src/cli/templates.ts
|
|
1456
|
+
import { Eta } from "eta";
|
|
1457
|
+
var TemplateError = class extends Error {
|
|
1458
|
+
constructor(message, promptName, configPath) {
|
|
1459
|
+
super(promptName ? `[prompts.${promptName}]: ${message}` : message);
|
|
1460
|
+
this.promptName = promptName;
|
|
1461
|
+
this.configPath = configPath;
|
|
1462
|
+
this.name = "TemplateError";
|
|
1463
|
+
}
|
|
1464
|
+
};
|
|
1465
|
+
function createTemplateEngine(prompts, configPath) {
|
|
1466
|
+
const eta = new Eta({
|
|
1467
|
+
views: "/",
|
|
1468
|
+
// Required but we use named templates
|
|
1469
|
+
autoEscape: false,
|
|
1470
|
+
// Don't escape - these are prompts, not HTML
|
|
1471
|
+
autoTrim: false
|
|
1472
|
+
// Preserve whitespace in prompts
|
|
1473
|
+
});
|
|
1474
|
+
for (const [name, template] of Object.entries(prompts)) {
|
|
1475
|
+
try {
|
|
1476
|
+
eta.loadTemplate(`@${name}`, template);
|
|
1477
|
+
} catch (error) {
|
|
1478
|
+
throw new TemplateError(
|
|
1479
|
+
error instanceof Error ? error.message : String(error),
|
|
1480
|
+
name,
|
|
1481
|
+
configPath
|
|
1482
|
+
);
|
|
1483
|
+
}
|
|
1484
|
+
}
|
|
1485
|
+
return eta;
|
|
1486
|
+
}
|
|
1487
|
+
function resolveTemplate(eta, template, context = {}, configPath) {
|
|
1488
|
+
try {
|
|
1489
|
+
const fullContext = {
|
|
1490
|
+
...context,
|
|
1491
|
+
env: process.env
|
|
1492
|
+
};
|
|
1493
|
+
return eta.renderString(template, fullContext);
|
|
1494
|
+
} catch (error) {
|
|
1495
|
+
throw new TemplateError(
|
|
1496
|
+
error instanceof Error ? error.message : String(error),
|
|
1497
|
+
void 0,
|
|
1498
|
+
configPath
|
|
1499
|
+
);
|
|
1500
|
+
}
|
|
1501
|
+
}
|
|
1502
|
+
function validatePrompts(prompts, configPath) {
|
|
1503
|
+
const eta = createTemplateEngine(prompts, configPath);
|
|
1504
|
+
for (const [name, template] of Object.entries(prompts)) {
|
|
1505
|
+
try {
|
|
1506
|
+
eta.renderString(template, { env: {} });
|
|
1507
|
+
} catch (error) {
|
|
1508
|
+
throw new TemplateError(
|
|
1509
|
+
error instanceof Error ? error.message : String(error),
|
|
1510
|
+
name,
|
|
1511
|
+
configPath
|
|
1512
|
+
);
|
|
1513
|
+
}
|
|
1514
|
+
}
|
|
1515
|
+
}
|
|
1516
|
+
function validateEnvVars(template, promptName, configPath) {
|
|
1517
|
+
const envVarPattern = /<%=\s*it\.env\.(\w+)\s*%>/g;
|
|
1518
|
+
const matches = template.matchAll(envVarPattern);
|
|
1519
|
+
for (const match of matches) {
|
|
1520
|
+
const varName = match[1];
|
|
1521
|
+
if (process.env[varName] === void 0) {
|
|
1522
|
+
throw new TemplateError(
|
|
1523
|
+
`Environment variable '${varName}' is not set`,
|
|
1524
|
+
promptName,
|
|
1525
|
+
configPath
|
|
1526
|
+
);
|
|
1527
|
+
}
|
|
1528
|
+
}
|
|
1529
|
+
}
|
|
1530
|
+
function hasTemplateSyntax(str) {
|
|
1531
|
+
return str.includes("<%");
|
|
1532
|
+
}
|
|
1533
|
+
|
|
1534
|
+
// src/cli/config.ts
|
|
1311
1535
|
var GLOBAL_CONFIG_KEYS = /* @__PURE__ */ new Set(["log-level", "log-file", "log-reset"]);
|
|
1312
1536
|
var VALID_LOG_LEVELS = ["silly", "trace", "debug", "info", "warn", "error", "fatal"];
|
|
1313
|
-
var COMPLETE_CONFIG_KEYS = /* @__PURE__ */ new Set([
|
|
1537
|
+
var COMPLETE_CONFIG_KEYS = /* @__PURE__ */ new Set([
|
|
1538
|
+
"model",
|
|
1539
|
+
"system",
|
|
1540
|
+
"temperature",
|
|
1541
|
+
"max-tokens",
|
|
1542
|
+
"quiet",
|
|
1543
|
+
"inherits",
|
|
1544
|
+
"log-level",
|
|
1545
|
+
"log-file",
|
|
1546
|
+
"log-reset",
|
|
1547
|
+
"log-llm-requests",
|
|
1548
|
+
"log-llm-responses",
|
|
1549
|
+
"type"
|
|
1550
|
+
// Allowed for inheritance compatibility, ignored for built-in commands
|
|
1551
|
+
]);
|
|
1314
1552
|
var AGENT_CONFIG_KEYS = /* @__PURE__ */ new Set([
|
|
1315
1553
|
"model",
|
|
1316
1554
|
"system",
|
|
1317
1555
|
"temperature",
|
|
1318
1556
|
"max-iterations",
|
|
1319
1557
|
"gadget",
|
|
1320
|
-
"parameter-format",
|
|
1321
1558
|
"builtins",
|
|
1322
1559
|
"builtin-interaction",
|
|
1323
1560
|
"gadget-start-prefix",
|
|
1324
|
-
"gadget-end-prefix"
|
|
1561
|
+
"gadget-end-prefix",
|
|
1562
|
+
"gadget-arg-prefix",
|
|
1563
|
+
"quiet",
|
|
1564
|
+
"inherits",
|
|
1565
|
+
"log-level",
|
|
1566
|
+
"log-file",
|
|
1567
|
+
"log-reset",
|
|
1568
|
+
"log-llm-requests",
|
|
1569
|
+
"log-llm-responses",
|
|
1570
|
+
"type"
|
|
1571
|
+
// Allowed for inheritance compatibility, ignored for built-in commands
|
|
1325
1572
|
]);
|
|
1326
1573
|
var CUSTOM_CONFIG_KEYS = /* @__PURE__ */ new Set([
|
|
1327
1574
|
...COMPLETE_CONFIG_KEYS,
|
|
1328
1575
|
...AGENT_CONFIG_KEYS,
|
|
1329
1576
|
"type",
|
|
1330
|
-
"description"
|
|
1331
|
-
"log-level",
|
|
1332
|
-
"log-file",
|
|
1333
|
-
"log-reset"
|
|
1577
|
+
"description"
|
|
1334
1578
|
]);
|
|
1335
|
-
var VALID_PARAMETER_FORMATS = ["json", "yaml", "toml", "auto"];
|
|
1336
1579
|
function getConfigPath() {
|
|
1337
|
-
return
|
|
1580
|
+
return join2(homedir2(), ".llmist", "cli.toml");
|
|
1338
1581
|
}
|
|
1339
1582
|
var ConfigError = class extends Error {
|
|
1340
1583
|
constructor(message, path2) {
|
|
@@ -1381,6 +1624,39 @@ function validateStringArray(value, key, section) {
|
|
|
1381
1624
|
}
|
|
1382
1625
|
return value;
|
|
1383
1626
|
}
|
|
1627
|
+
function validateInherits(value, section) {
|
|
1628
|
+
if (typeof value === "string") {
|
|
1629
|
+
return value;
|
|
1630
|
+
}
|
|
1631
|
+
if (Array.isArray(value)) {
|
|
1632
|
+
for (let i = 0; i < value.length; i++) {
|
|
1633
|
+
if (typeof value[i] !== "string") {
|
|
1634
|
+
throw new ConfigError(`[${section}].inherits[${i}] must be a string`);
|
|
1635
|
+
}
|
|
1636
|
+
}
|
|
1637
|
+
return value;
|
|
1638
|
+
}
|
|
1639
|
+
throw new ConfigError(`[${section}].inherits must be a string or array of strings`);
|
|
1640
|
+
}
|
|
1641
|
+
function validateLoggingConfig(raw, section) {
|
|
1642
|
+
const result = {};
|
|
1643
|
+
if ("log-level" in raw) {
|
|
1644
|
+
const level = validateString(raw["log-level"], "log-level", section);
|
|
1645
|
+
if (!VALID_LOG_LEVELS.includes(level)) {
|
|
1646
|
+
throw new ConfigError(
|
|
1647
|
+
`[${section}].log-level must be one of: ${VALID_LOG_LEVELS.join(", ")}`
|
|
1648
|
+
);
|
|
1649
|
+
}
|
|
1650
|
+
result["log-level"] = level;
|
|
1651
|
+
}
|
|
1652
|
+
if ("log-file" in raw) {
|
|
1653
|
+
result["log-file"] = validateString(raw["log-file"], "log-file", section);
|
|
1654
|
+
}
|
|
1655
|
+
if ("log-reset" in raw) {
|
|
1656
|
+
result["log-reset"] = validateBoolean(raw["log-reset"], "log-reset", section);
|
|
1657
|
+
}
|
|
1658
|
+
return result;
|
|
1659
|
+
}
|
|
1384
1660
|
function validateBaseConfig(raw, section) {
|
|
1385
1661
|
const result = {};
|
|
1386
1662
|
if ("model" in raw) {
|
|
@@ -1395,6 +1671,9 @@ function validateBaseConfig(raw, section) {
|
|
|
1395
1671
|
max: 2
|
|
1396
1672
|
});
|
|
1397
1673
|
}
|
|
1674
|
+
if ("inherits" in raw) {
|
|
1675
|
+
result.inherits = validateInherits(raw.inherits, section);
|
|
1676
|
+
}
|
|
1398
1677
|
return result;
|
|
1399
1678
|
}
|
|
1400
1679
|
function validateGlobalConfig(raw, section) {
|
|
@@ -1407,23 +1686,7 @@ function validateGlobalConfig(raw, section) {
|
|
|
1407
1686
|
throw new ConfigError(`[${section}].${key} is not a valid option`);
|
|
1408
1687
|
}
|
|
1409
1688
|
}
|
|
1410
|
-
|
|
1411
|
-
if ("log-level" in rawObj) {
|
|
1412
|
-
const level = validateString(rawObj["log-level"], "log-level", section);
|
|
1413
|
-
if (!VALID_LOG_LEVELS.includes(level)) {
|
|
1414
|
-
throw new ConfigError(
|
|
1415
|
-
`[${section}].log-level must be one of: ${VALID_LOG_LEVELS.join(", ")}`
|
|
1416
|
-
);
|
|
1417
|
-
}
|
|
1418
|
-
result["log-level"] = level;
|
|
1419
|
-
}
|
|
1420
|
-
if ("log-file" in rawObj) {
|
|
1421
|
-
result["log-file"] = validateString(rawObj["log-file"], "log-file", section);
|
|
1422
|
-
}
|
|
1423
|
-
if ("log-reset" in rawObj) {
|
|
1424
|
-
result["log-reset"] = validateBoolean(rawObj["log-reset"], "log-reset", section);
|
|
1425
|
-
}
|
|
1426
|
-
return result;
|
|
1689
|
+
return validateLoggingConfig(rawObj, section);
|
|
1427
1690
|
}
|
|
1428
1691
|
function validateCompleteConfig(raw, section) {
|
|
1429
1692
|
if (typeof raw !== "object" || raw === null) {
|
|
@@ -1435,13 +1698,33 @@ function validateCompleteConfig(raw, section) {
|
|
|
1435
1698
|
throw new ConfigError(`[${section}].${key} is not a valid option`);
|
|
1436
1699
|
}
|
|
1437
1700
|
}
|
|
1438
|
-
const result = {
|
|
1701
|
+
const result = {
|
|
1702
|
+
...validateBaseConfig(rawObj, section),
|
|
1703
|
+
...validateLoggingConfig(rawObj, section)
|
|
1704
|
+
};
|
|
1439
1705
|
if ("max-tokens" in rawObj) {
|
|
1440
1706
|
result["max-tokens"] = validateNumber(rawObj["max-tokens"], "max-tokens", section, {
|
|
1441
1707
|
integer: true,
|
|
1442
1708
|
min: 1
|
|
1443
1709
|
});
|
|
1444
1710
|
}
|
|
1711
|
+
if ("quiet" in rawObj) {
|
|
1712
|
+
result.quiet = validateBoolean(rawObj.quiet, "quiet", section);
|
|
1713
|
+
}
|
|
1714
|
+
if ("log-llm-requests" in rawObj) {
|
|
1715
|
+
result["log-llm-requests"] = validateStringOrBoolean(
|
|
1716
|
+
rawObj["log-llm-requests"],
|
|
1717
|
+
"log-llm-requests",
|
|
1718
|
+
section
|
|
1719
|
+
);
|
|
1720
|
+
}
|
|
1721
|
+
if ("log-llm-responses" in rawObj) {
|
|
1722
|
+
result["log-llm-responses"] = validateStringOrBoolean(
|
|
1723
|
+
rawObj["log-llm-responses"],
|
|
1724
|
+
"log-llm-responses",
|
|
1725
|
+
section
|
|
1726
|
+
);
|
|
1727
|
+
}
|
|
1445
1728
|
return result;
|
|
1446
1729
|
}
|
|
1447
1730
|
function validateAgentConfig(raw, section) {
|
|
@@ -1454,7 +1737,10 @@ function validateAgentConfig(raw, section) {
|
|
|
1454
1737
|
throw new ConfigError(`[${section}].${key} is not a valid option`);
|
|
1455
1738
|
}
|
|
1456
1739
|
}
|
|
1457
|
-
const result = {
|
|
1740
|
+
const result = {
|
|
1741
|
+
...validateBaseConfig(rawObj, section),
|
|
1742
|
+
...validateLoggingConfig(rawObj, section)
|
|
1743
|
+
};
|
|
1458
1744
|
if ("max-iterations" in rawObj) {
|
|
1459
1745
|
result["max-iterations"] = validateNumber(rawObj["max-iterations"], "max-iterations", section, {
|
|
1460
1746
|
integer: true,
|
|
@@ -1464,15 +1750,6 @@ function validateAgentConfig(raw, section) {
|
|
|
1464
1750
|
if ("gadget" in rawObj) {
|
|
1465
1751
|
result.gadget = validateStringArray(rawObj.gadget, "gadget", section);
|
|
1466
1752
|
}
|
|
1467
|
-
if ("parameter-format" in rawObj) {
|
|
1468
|
-
const format = validateString(rawObj["parameter-format"], "parameter-format", section);
|
|
1469
|
-
if (!VALID_PARAMETER_FORMATS.includes(format)) {
|
|
1470
|
-
throw new ConfigError(
|
|
1471
|
-
`[${section}].parameter-format must be one of: ${VALID_PARAMETER_FORMATS.join(", ")}`
|
|
1472
|
-
);
|
|
1473
|
-
}
|
|
1474
|
-
result["parameter-format"] = format;
|
|
1475
|
-
}
|
|
1476
1753
|
if ("builtins" in rawObj) {
|
|
1477
1754
|
result.builtins = validateBoolean(rawObj.builtins, "builtins", section);
|
|
1478
1755
|
}
|
|
@@ -1497,8 +1774,38 @@ function validateAgentConfig(raw, section) {
|
|
|
1497
1774
|
section
|
|
1498
1775
|
);
|
|
1499
1776
|
}
|
|
1777
|
+
if ("gadget-arg-prefix" in rawObj) {
|
|
1778
|
+
result["gadget-arg-prefix"] = validateString(
|
|
1779
|
+
rawObj["gadget-arg-prefix"],
|
|
1780
|
+
"gadget-arg-prefix",
|
|
1781
|
+
section
|
|
1782
|
+
);
|
|
1783
|
+
}
|
|
1784
|
+
if ("quiet" in rawObj) {
|
|
1785
|
+
result.quiet = validateBoolean(rawObj.quiet, "quiet", section);
|
|
1786
|
+
}
|
|
1787
|
+
if ("log-llm-requests" in rawObj) {
|
|
1788
|
+
result["log-llm-requests"] = validateStringOrBoolean(
|
|
1789
|
+
rawObj["log-llm-requests"],
|
|
1790
|
+
"log-llm-requests",
|
|
1791
|
+
section
|
|
1792
|
+
);
|
|
1793
|
+
}
|
|
1794
|
+
if ("log-llm-responses" in rawObj) {
|
|
1795
|
+
result["log-llm-responses"] = validateStringOrBoolean(
|
|
1796
|
+
rawObj["log-llm-responses"],
|
|
1797
|
+
"log-llm-responses",
|
|
1798
|
+
section
|
|
1799
|
+
);
|
|
1800
|
+
}
|
|
1500
1801
|
return result;
|
|
1501
1802
|
}
|
|
1803
|
+
function validateStringOrBoolean(value, field, section) {
|
|
1804
|
+
if (typeof value === "string" || typeof value === "boolean") {
|
|
1805
|
+
return value;
|
|
1806
|
+
}
|
|
1807
|
+
throw new ConfigError(`[${section}].${field} must be a string or boolean`);
|
|
1808
|
+
}
|
|
1502
1809
|
function validateCustomConfig(raw, section) {
|
|
1503
1810
|
if (typeof raw !== "object" || raw === null) {
|
|
1504
1811
|
throw new ConfigError(`[${section}] must be a table`);
|
|
@@ -1533,15 +1840,6 @@ function validateCustomConfig(raw, section) {
|
|
|
1533
1840
|
if ("gadget" in rawObj) {
|
|
1534
1841
|
result.gadget = validateStringArray(rawObj.gadget, "gadget", section);
|
|
1535
1842
|
}
|
|
1536
|
-
if ("parameter-format" in rawObj) {
|
|
1537
|
-
const format = validateString(rawObj["parameter-format"], "parameter-format", section);
|
|
1538
|
-
if (!VALID_PARAMETER_FORMATS.includes(format)) {
|
|
1539
|
-
throw new ConfigError(
|
|
1540
|
-
`[${section}].parameter-format must be one of: ${VALID_PARAMETER_FORMATS.join(", ")}`
|
|
1541
|
-
);
|
|
1542
|
-
}
|
|
1543
|
-
result["parameter-format"] = format;
|
|
1544
|
-
}
|
|
1545
1843
|
if ("builtins" in rawObj) {
|
|
1546
1844
|
result.builtins = validateBoolean(rawObj.builtins, "builtins", section);
|
|
1547
1845
|
}
|
|
@@ -1566,26 +1864,35 @@ function validateCustomConfig(raw, section) {
|
|
|
1566
1864
|
section
|
|
1567
1865
|
);
|
|
1568
1866
|
}
|
|
1867
|
+
if ("gadget-arg-prefix" in rawObj) {
|
|
1868
|
+
result["gadget-arg-prefix"] = validateString(
|
|
1869
|
+
rawObj["gadget-arg-prefix"],
|
|
1870
|
+
"gadget-arg-prefix",
|
|
1871
|
+
section
|
|
1872
|
+
);
|
|
1873
|
+
}
|
|
1569
1874
|
if ("max-tokens" in rawObj) {
|
|
1570
1875
|
result["max-tokens"] = validateNumber(rawObj["max-tokens"], "max-tokens", section, {
|
|
1571
1876
|
integer: true,
|
|
1572
1877
|
min: 1
|
|
1573
1878
|
});
|
|
1574
1879
|
}
|
|
1575
|
-
if ("
|
|
1576
|
-
|
|
1577
|
-
if (!VALID_LOG_LEVELS.includes(level)) {
|
|
1578
|
-
throw new ConfigError(
|
|
1579
|
-
`[${section}].log-level must be one of: ${VALID_LOG_LEVELS.join(", ")}`
|
|
1580
|
-
);
|
|
1581
|
-
}
|
|
1582
|
-
result["log-level"] = level;
|
|
1880
|
+
if ("quiet" in rawObj) {
|
|
1881
|
+
result.quiet = validateBoolean(rawObj.quiet, "quiet", section);
|
|
1583
1882
|
}
|
|
1584
|
-
|
|
1585
|
-
|
|
1883
|
+
Object.assign(result, validateLoggingConfig(rawObj, section));
|
|
1884
|
+
return result;
|
|
1885
|
+
}
|
|
1886
|
+
function validatePromptsConfig(raw, section) {
|
|
1887
|
+
if (typeof raw !== "object" || raw === null) {
|
|
1888
|
+
throw new ConfigError(`[${section}] must be a table`);
|
|
1586
1889
|
}
|
|
1587
|
-
|
|
1588
|
-
|
|
1890
|
+
const result = {};
|
|
1891
|
+
for (const [key, value] of Object.entries(raw)) {
|
|
1892
|
+
if (typeof value !== "string") {
|
|
1893
|
+
throw new ConfigError(`[${section}].${key} must be a string`);
|
|
1894
|
+
}
|
|
1895
|
+
result[key] = value;
|
|
1589
1896
|
}
|
|
1590
1897
|
return result;
|
|
1591
1898
|
}
|
|
@@ -1603,6 +1910,8 @@ function validateConfig(raw, configPath) {
|
|
|
1603
1910
|
result.complete = validateCompleteConfig(value, key);
|
|
1604
1911
|
} else if (key === "agent") {
|
|
1605
1912
|
result.agent = validateAgentConfig(value, key);
|
|
1913
|
+
} else if (key === "prompts") {
|
|
1914
|
+
result.prompts = validatePromptsConfig(value, key);
|
|
1606
1915
|
} else {
|
|
1607
1916
|
result[key] = validateCustomConfig(value, key);
|
|
1608
1917
|
}
|
|
@@ -1638,12 +1947,119 @@ function loadConfig() {
|
|
|
1638
1947
|
configPath
|
|
1639
1948
|
);
|
|
1640
1949
|
}
|
|
1641
|
-
|
|
1950
|
+
const validated = validateConfig(raw, configPath);
|
|
1951
|
+
const inherited = resolveInheritance(validated, configPath);
|
|
1952
|
+
return resolveTemplatesInConfig(inherited, configPath);
|
|
1642
1953
|
}
|
|
1643
1954
|
function getCustomCommandNames(config) {
|
|
1644
|
-
const reserved = /* @__PURE__ */ new Set(["global", "complete", "agent"]);
|
|
1955
|
+
const reserved = /* @__PURE__ */ new Set(["global", "complete", "agent", "prompts"]);
|
|
1645
1956
|
return Object.keys(config).filter((key) => !reserved.has(key));
|
|
1646
1957
|
}
|
|
1958
|
+
function resolveTemplatesInConfig(config, configPath) {
|
|
1959
|
+
const prompts = config.prompts ?? {};
|
|
1960
|
+
const hasPrompts = Object.keys(prompts).length > 0;
|
|
1961
|
+
let hasTemplates = false;
|
|
1962
|
+
for (const [sectionName, section] of Object.entries(config)) {
|
|
1963
|
+
if (sectionName === "global" || sectionName === "prompts") continue;
|
|
1964
|
+
if (!section || typeof section !== "object") continue;
|
|
1965
|
+
const sectionObj = section;
|
|
1966
|
+
if (typeof sectionObj.system === "string" && hasTemplateSyntax(sectionObj.system)) {
|
|
1967
|
+
hasTemplates = true;
|
|
1968
|
+
break;
|
|
1969
|
+
}
|
|
1970
|
+
}
|
|
1971
|
+
for (const template of Object.values(prompts)) {
|
|
1972
|
+
if (hasTemplateSyntax(template)) {
|
|
1973
|
+
hasTemplates = true;
|
|
1974
|
+
break;
|
|
1975
|
+
}
|
|
1976
|
+
}
|
|
1977
|
+
if (!hasPrompts && !hasTemplates) {
|
|
1978
|
+
return config;
|
|
1979
|
+
}
|
|
1980
|
+
try {
|
|
1981
|
+
validatePrompts(prompts, configPath);
|
|
1982
|
+
} catch (error) {
|
|
1983
|
+
if (error instanceof TemplateError) {
|
|
1984
|
+
throw new ConfigError(error.message, configPath);
|
|
1985
|
+
}
|
|
1986
|
+
throw error;
|
|
1987
|
+
}
|
|
1988
|
+
for (const [name, template] of Object.entries(prompts)) {
|
|
1989
|
+
try {
|
|
1990
|
+
validateEnvVars(template, name, configPath);
|
|
1991
|
+
} catch (error) {
|
|
1992
|
+
if (error instanceof TemplateError) {
|
|
1993
|
+
throw new ConfigError(error.message, configPath);
|
|
1994
|
+
}
|
|
1995
|
+
throw error;
|
|
1996
|
+
}
|
|
1997
|
+
}
|
|
1998
|
+
const eta = createTemplateEngine(prompts, configPath);
|
|
1999
|
+
const result = { ...config };
|
|
2000
|
+
for (const [sectionName, section] of Object.entries(config)) {
|
|
2001
|
+
if (sectionName === "global" || sectionName === "prompts") continue;
|
|
2002
|
+
if (!section || typeof section !== "object") continue;
|
|
2003
|
+
const sectionObj = section;
|
|
2004
|
+
if (typeof sectionObj.system === "string" && hasTemplateSyntax(sectionObj.system)) {
|
|
2005
|
+
try {
|
|
2006
|
+
validateEnvVars(sectionObj.system, void 0, configPath);
|
|
2007
|
+
} catch (error) {
|
|
2008
|
+
if (error instanceof TemplateError) {
|
|
2009
|
+
throw new ConfigError(`[${sectionName}].system: ${error.message}`, configPath);
|
|
2010
|
+
}
|
|
2011
|
+
throw error;
|
|
2012
|
+
}
|
|
2013
|
+
try {
|
|
2014
|
+
const resolved = resolveTemplate(eta, sectionObj.system, {}, configPath);
|
|
2015
|
+
result[sectionName] = {
|
|
2016
|
+
...sectionObj,
|
|
2017
|
+
system: resolved
|
|
2018
|
+
};
|
|
2019
|
+
} catch (error) {
|
|
2020
|
+
if (error instanceof TemplateError) {
|
|
2021
|
+
throw new ConfigError(`[${sectionName}].system: ${error.message}`, configPath);
|
|
2022
|
+
}
|
|
2023
|
+
throw error;
|
|
2024
|
+
}
|
|
2025
|
+
}
|
|
2026
|
+
}
|
|
2027
|
+
return result;
|
|
2028
|
+
}
|
|
2029
|
+
function resolveInheritance(config, configPath) {
|
|
2030
|
+
const resolved = {};
|
|
2031
|
+
const resolving = /* @__PURE__ */ new Set();
|
|
2032
|
+
function resolveSection(name) {
|
|
2033
|
+
if (name in resolved) {
|
|
2034
|
+
return resolved[name];
|
|
2035
|
+
}
|
|
2036
|
+
if (resolving.has(name)) {
|
|
2037
|
+
throw new ConfigError(`Circular inheritance detected: ${name}`, configPath);
|
|
2038
|
+
}
|
|
2039
|
+
const section = config[name];
|
|
2040
|
+
if (section === void 0 || typeof section !== "object") {
|
|
2041
|
+
throw new ConfigError(`Cannot inherit from unknown section: ${name}`, configPath);
|
|
2042
|
+
}
|
|
2043
|
+
resolving.add(name);
|
|
2044
|
+
const sectionObj = section;
|
|
2045
|
+
const inheritsRaw = sectionObj.inherits;
|
|
2046
|
+
const inheritsList = inheritsRaw ? Array.isArray(inheritsRaw) ? inheritsRaw : [inheritsRaw] : [];
|
|
2047
|
+
let merged = {};
|
|
2048
|
+
for (const parent of inheritsList) {
|
|
2049
|
+
const parentResolved = resolveSection(parent);
|
|
2050
|
+
merged = { ...merged, ...parentResolved };
|
|
2051
|
+
}
|
|
2052
|
+
const { inherits: _inherits, ...ownValues } = sectionObj;
|
|
2053
|
+
merged = { ...merged, ...ownValues };
|
|
2054
|
+
resolving.delete(name);
|
|
2055
|
+
resolved[name] = merged;
|
|
2056
|
+
return merged;
|
|
2057
|
+
}
|
|
2058
|
+
for (const name of Object.keys(config)) {
|
|
2059
|
+
resolveSection(name);
|
|
2060
|
+
}
|
|
2061
|
+
return resolved;
|
|
2062
|
+
}
|
|
1647
2063
|
|
|
1648
2064
|
// src/cli/models-command.ts
|
|
1649
2065
|
import chalk4 from "chalk";
|
|
@@ -1940,7 +2356,7 @@ function registerCustomCommand(program, name, config, env) {
|
|
|
1940
2356
|
function parseLogLevel(value) {
|
|
1941
2357
|
const normalized = value.toLowerCase();
|
|
1942
2358
|
if (!LOG_LEVELS.includes(normalized)) {
|
|
1943
|
-
throw new
|
|
2359
|
+
throw new InvalidArgumentError2(`Log level must be one of: ${LOG_LEVELS.join(", ")}`);
|
|
1944
2360
|
}
|
|
1945
2361
|
return normalized;
|
|
1946
2362
|
}
|