llmist 0.7.0 → 0.8.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-4IMGADVY.js} +2 -2
- package/dist/{chunk-ZFHFBEQ5.js → chunk-62M4TDAK.js} +359 -66
- package/dist/chunk-62M4TDAK.js.map +1 -0
- package/dist/cli.cjs +726 -123
- package/dist/cli.cjs.map +1 -1
- package/dist/cli.js +369 -59
- package/dist/cli.js.map +1 -1
- package/dist/index.cjs +358 -65
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +6 -9
- package/dist/index.d.ts +6 -9
- package/dist/index.js +2 -2
- package/dist/{mock-stream-B2qwECvd.d.cts → mock-stream-CjmvWDc3.d.cts} +21 -20
- package/dist/{mock-stream-B2qwECvd.d.ts → mock-stream-CjmvWDc3.d.ts} +21 -20
- package/dist/testing/index.cjs +358 -65
- 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-ZFHFBEQ5.js.map +0 -1
- /package/dist/{chunk-CTC2WJZA.js.map → chunk-4IMGADVY.js.map} +0 -0
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-62M4TDAK.js";
|
|
26
26
|
|
|
27
27
|
// src/cli/constants.ts
|
|
28
28
|
var CLI_NAME = "llmist";
|
|
@@ -47,7 +47,8 @@ var OPTION_FLAGS = {
|
|
|
47
47
|
logFile: "--log-file <path>",
|
|
48
48
|
logReset: "--log-reset",
|
|
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.",
|
|
@@ -61,7 +62,8 @@ var OPTION_DESCRIPTIONS = {
|
|
|
61
62
|
logFile: "Path to log file. When set, logs are written to file instead of stderr.",
|
|
62
63
|
logReset: "Reset (truncate) the log file at session start instead of appending.",
|
|
63
64
|
noBuiltins: "Disable built-in gadgets (AskUser, TellUser).",
|
|
64
|
-
noBuiltinInteraction: "Disable interactive gadgets (AskUser) while keeping TellUser."
|
|
65
|
+
noBuiltinInteraction: "Disable interactive gadgets (AskUser) while keeping TellUser.",
|
|
66
|
+
quiet: "Suppress all output except content (text and TellUser messages)."
|
|
65
67
|
};
|
|
66
68
|
var SUMMARY_PREFIX = "[llmist]";
|
|
67
69
|
|
|
@@ -71,7 +73,7 @@ import { Command, InvalidArgumentError as InvalidArgumentError3 } from "commande
|
|
|
71
73
|
// package.json
|
|
72
74
|
var package_default = {
|
|
73
75
|
name: "llmist",
|
|
74
|
-
version: "0.
|
|
76
|
+
version: "0.7.0",
|
|
75
77
|
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
78
|
type: "module",
|
|
77
79
|
main: "dist/index.cjs",
|
|
@@ -155,6 +157,7 @@ var package_default = {
|
|
|
155
157
|
"@google/genai": "^1.27.0",
|
|
156
158
|
chalk: "^5.6.2",
|
|
157
159
|
commander: "^12.1.0",
|
|
160
|
+
eta: "^4.4.1",
|
|
158
161
|
"js-toml": "^1.0.2",
|
|
159
162
|
"js-yaml": "^4.1.0",
|
|
160
163
|
marked: "^15.0.12",
|
|
@@ -274,12 +277,19 @@ import fs from "node:fs";
|
|
|
274
277
|
import path from "node:path";
|
|
275
278
|
import { pathToFileURL } from "node:url";
|
|
276
279
|
var PATH_PREFIXES = [".", "/", "~"];
|
|
280
|
+
function isGadgetLike(value) {
|
|
281
|
+
if (typeof value !== "object" || value === null) {
|
|
282
|
+
return false;
|
|
283
|
+
}
|
|
284
|
+
const obj = value;
|
|
285
|
+
return typeof obj.execute === "function" && typeof obj.description === "string" && ("parameterSchema" in obj || "schema" in obj);
|
|
286
|
+
}
|
|
277
287
|
function isGadgetConstructor(value) {
|
|
278
288
|
if (typeof value !== "function") {
|
|
279
289
|
return false;
|
|
280
290
|
}
|
|
281
291
|
const prototype = value.prototype;
|
|
282
|
-
return Boolean(prototype) && prototype instanceof BaseGadget;
|
|
292
|
+
return Boolean(prototype) && (prototype instanceof BaseGadget || isGadgetLike(prototype));
|
|
283
293
|
}
|
|
284
294
|
function expandHomePath(input) {
|
|
285
295
|
if (!input.startsWith("~")) {
|
|
@@ -316,7 +326,7 @@ function extractGadgetsFromModule(moduleExports) {
|
|
|
316
326
|
return;
|
|
317
327
|
}
|
|
318
328
|
visited.add(value);
|
|
319
|
-
if (value instanceof BaseGadget) {
|
|
329
|
+
if (value instanceof BaseGadget || isGadgetLike(value)) {
|
|
320
330
|
results.push(value);
|
|
321
331
|
return;
|
|
322
332
|
}
|
|
@@ -441,8 +451,14 @@ function renderSummary(metadata) {
|
|
|
441
451
|
parts.push(chalk.magenta(metadata.model));
|
|
442
452
|
}
|
|
443
453
|
if (metadata.usage) {
|
|
444
|
-
const { inputTokens, outputTokens } = metadata.usage;
|
|
454
|
+
const { inputTokens, outputTokens, cachedInputTokens, cacheCreationInputTokens } = metadata.usage;
|
|
445
455
|
parts.push(chalk.dim("\u2191") + chalk.yellow(` ${formatTokens(inputTokens)}`));
|
|
456
|
+
if (cachedInputTokens && cachedInputTokens > 0) {
|
|
457
|
+
parts.push(chalk.dim("\u27F3") + chalk.blue(` ${formatTokens(cachedInputTokens)}`));
|
|
458
|
+
}
|
|
459
|
+
if (cacheCreationInputTokens && cacheCreationInputTokens > 0) {
|
|
460
|
+
parts.push(chalk.dim("\u270E") + chalk.magenta(` ${formatTokens(cacheCreationInputTokens)}`));
|
|
461
|
+
}
|
|
446
462
|
parts.push(chalk.dim("\u2193") + chalk.green(` ${formatTokens(outputTokens)}`));
|
|
447
463
|
}
|
|
448
464
|
if (metadata.elapsedSeconds !== void 0 && metadata.elapsedSeconds > 0) {
|
|
@@ -611,6 +627,9 @@ var StreamProgress = class {
|
|
|
611
627
|
callOutputTokensEstimated = true;
|
|
612
628
|
callOutputChars = 0;
|
|
613
629
|
isStreaming = false;
|
|
630
|
+
// Cache token tracking for live cost estimation during streaming
|
|
631
|
+
callCachedInputTokens = 0;
|
|
632
|
+
callCacheCreationInputTokens = 0;
|
|
614
633
|
// Cumulative stats (cumulative mode)
|
|
615
634
|
totalStartTime = Date.now();
|
|
616
635
|
totalTokens = 0;
|
|
@@ -636,11 +655,13 @@ var StreamProgress = class {
|
|
|
636
655
|
this.callOutputTokensEstimated = true;
|
|
637
656
|
this.callOutputChars = 0;
|
|
638
657
|
this.isStreaming = false;
|
|
658
|
+
this.callCachedInputTokens = 0;
|
|
659
|
+
this.callCacheCreationInputTokens = 0;
|
|
639
660
|
this.start();
|
|
640
661
|
}
|
|
641
662
|
/**
|
|
642
663
|
* Ends the current LLM call. Updates cumulative stats and switches to cumulative mode.
|
|
643
|
-
* @param usage - Final token usage from the call
|
|
664
|
+
* @param usage - Final token usage from the call (including cached tokens if available)
|
|
644
665
|
*/
|
|
645
666
|
endCall(usage) {
|
|
646
667
|
this.iterations++;
|
|
@@ -652,7 +673,9 @@ var StreamProgress = class {
|
|
|
652
673
|
const cost = this.modelRegistry.estimateCost(
|
|
653
674
|
modelName,
|
|
654
675
|
usage.inputTokens,
|
|
655
|
-
usage.outputTokens
|
|
676
|
+
usage.outputTokens,
|
|
677
|
+
usage.cachedInputTokens ?? 0,
|
|
678
|
+
usage.cacheCreationInputTokens ?? 0
|
|
656
679
|
);
|
|
657
680
|
if (cost) {
|
|
658
681
|
this.totalCost += cost.totalCost;
|
|
@@ -692,6 +715,16 @@ var StreamProgress = class {
|
|
|
692
715
|
this.callOutputTokens = tokens;
|
|
693
716
|
this.callOutputTokensEstimated = estimated;
|
|
694
717
|
}
|
|
718
|
+
/**
|
|
719
|
+
* Sets cached token counts for the current call (from stream metadata).
|
|
720
|
+
* Used for live cost estimation during streaming.
|
|
721
|
+
* @param cachedInputTokens - Number of tokens read from cache (cheaper)
|
|
722
|
+
* @param cacheCreationInputTokens - Number of tokens written to cache (more expensive)
|
|
723
|
+
*/
|
|
724
|
+
setCachedTokens(cachedInputTokens, cacheCreationInputTokens) {
|
|
725
|
+
this.callCachedInputTokens = cachedInputTokens;
|
|
726
|
+
this.callCacheCreationInputTokens = cacheCreationInputTokens;
|
|
727
|
+
}
|
|
695
728
|
/**
|
|
696
729
|
* Get total elapsed time in seconds since the first call started.
|
|
697
730
|
* @returns Elapsed time in seconds with 1 decimal place
|
|
@@ -756,11 +789,32 @@ var StreamProgress = class {
|
|
|
756
789
|
parts.push(chalk2.dim("\u2193") + chalk2.green(` ${prefix}${formatTokens(outTokens)}`));
|
|
757
790
|
}
|
|
758
791
|
parts.push(chalk2.dim(`${elapsed}s`));
|
|
759
|
-
|
|
760
|
-
|
|
792
|
+
const callCost = this.calculateCurrentCallCost(outTokens);
|
|
793
|
+
if (callCost > 0) {
|
|
794
|
+
parts.push(chalk2.cyan(`$${formatCost(callCost)}`));
|
|
761
795
|
}
|
|
762
796
|
this.target.write(`\r${parts.join(chalk2.dim(" | "))} ${chalk2.cyan(spinner)}`);
|
|
763
797
|
}
|
|
798
|
+
/**
|
|
799
|
+
* Calculates live cost estimate for the current streaming call.
|
|
800
|
+
* Uses current input/output tokens and cached token counts.
|
|
801
|
+
*/
|
|
802
|
+
calculateCurrentCallCost(outputTokens) {
|
|
803
|
+
if (!this.modelRegistry || !this.model) return 0;
|
|
804
|
+
try {
|
|
805
|
+
const modelName = this.model.includes(":") ? this.model.split(":")[1] : this.model;
|
|
806
|
+
const cost = this.modelRegistry.estimateCost(
|
|
807
|
+
modelName,
|
|
808
|
+
this.callInputTokens,
|
|
809
|
+
outputTokens,
|
|
810
|
+
this.callCachedInputTokens,
|
|
811
|
+
this.callCacheCreationInputTokens
|
|
812
|
+
);
|
|
813
|
+
return cost?.totalCost ?? 0;
|
|
814
|
+
} catch {
|
|
815
|
+
return 0;
|
|
816
|
+
}
|
|
817
|
+
}
|
|
764
818
|
renderCumulativeMode(spinner) {
|
|
765
819
|
const elapsed = ((Date.now() - this.totalStartTime) / 1e3).toFixed(1);
|
|
766
820
|
const parts = [];
|
|
@@ -909,7 +963,7 @@ function addCompleteOptions(cmd, defaults) {
|
|
|
909
963
|
OPTION_DESCRIPTIONS.maxTokens,
|
|
910
964
|
createNumericParser({ label: "Max tokens", integer: true, min: 1 }),
|
|
911
965
|
defaults?.["max-tokens"]
|
|
912
|
-
);
|
|
966
|
+
).option(OPTION_FLAGS.quiet, OPTION_DESCRIPTIONS.quiet, defaults?.quiet);
|
|
913
967
|
}
|
|
914
968
|
function addAgentOptions(cmd, defaults) {
|
|
915
969
|
const gadgetAccumulator = (value, previous = []) => [
|
|
@@ -938,7 +992,7 @@ function addAgentOptions(cmd, defaults) {
|
|
|
938
992
|
OPTION_FLAGS.noBuiltinInteraction,
|
|
939
993
|
OPTION_DESCRIPTIONS.noBuiltinInteraction,
|
|
940
994
|
defaults?.["builtin-interaction"] !== false
|
|
941
|
-
);
|
|
995
|
+
).option(OPTION_FLAGS.quiet, OPTION_DESCRIPTIONS.quiet, defaults?.quiet);
|
|
942
996
|
}
|
|
943
997
|
function configToCompleteOptions(config) {
|
|
944
998
|
const result = {};
|
|
@@ -946,6 +1000,7 @@ function configToCompleteOptions(config) {
|
|
|
946
1000
|
if (config.system !== void 0) result.system = config.system;
|
|
947
1001
|
if (config.temperature !== void 0) result.temperature = config.temperature;
|
|
948
1002
|
if (config["max-tokens"] !== void 0) result.maxTokens = config["max-tokens"];
|
|
1003
|
+
if (config.quiet !== void 0) result.quiet = config.quiet;
|
|
949
1004
|
return result;
|
|
950
1005
|
}
|
|
951
1006
|
function configToAgentOptions(config) {
|
|
@@ -963,6 +1018,7 @@ function configToAgentOptions(config) {
|
|
|
963
1018
|
result.gadgetStartPrefix = config["gadget-start-prefix"];
|
|
964
1019
|
if (config["gadget-end-prefix"] !== void 0)
|
|
965
1020
|
result.gadgetEndPrefix = config["gadget-end-prefix"];
|
|
1021
|
+
if (config.quiet !== void 0) result.quiet = config.quiet;
|
|
966
1022
|
return result;
|
|
967
1023
|
}
|
|
968
1024
|
|
|
@@ -1008,9 +1064,10 @@ async function executeAgent(promptArg, options, env) {
|
|
|
1008
1064
|
const prompt = await resolvePrompt(promptArg, env);
|
|
1009
1065
|
const client = env.createClient();
|
|
1010
1066
|
const registry = new GadgetRegistry();
|
|
1067
|
+
const stdinIsInteractive = isInteractive(env.stdin);
|
|
1011
1068
|
if (options.builtins !== false) {
|
|
1012
1069
|
for (const gadget of builtinGadgets) {
|
|
1013
|
-
if (
|
|
1070
|
+
if (gadget.name === "AskUser" && (options.builtinInteraction === false || !stdinIsInteractive)) {
|
|
1014
1071
|
continue;
|
|
1015
1072
|
}
|
|
1016
1073
|
registry.registerByClass(gadget);
|
|
@@ -1068,6 +1125,10 @@ async function executeAgent(promptArg, options, env) {
|
|
|
1068
1125
|
if (context.usage.outputTokens) {
|
|
1069
1126
|
progress.setOutputTokens(context.usage.outputTokens, false);
|
|
1070
1127
|
}
|
|
1128
|
+
progress.setCachedTokens(
|
|
1129
|
+
context.usage.cachedInputTokens ?? 0,
|
|
1130
|
+
context.usage.cacheCreationInputTokens ?? 0
|
|
1131
|
+
);
|
|
1071
1132
|
}
|
|
1072
1133
|
},
|
|
1073
1134
|
// onLLMCallComplete: Finalize metrics after each LLM call
|
|
@@ -1086,11 +1147,13 @@ async function executeAgent(promptArg, options, env) {
|
|
|
1086
1147
|
let callCost;
|
|
1087
1148
|
if (context.usage && client.modelRegistry) {
|
|
1088
1149
|
try {
|
|
1089
|
-
const modelName = options.model.includes(":") ? options.model.split(":")[1] : options.model;
|
|
1150
|
+
const modelName = context.options.model.includes(":") ? context.options.model.split(":")[1] : context.options.model;
|
|
1090
1151
|
const costResult = client.modelRegistry.estimateCost(
|
|
1091
1152
|
modelName,
|
|
1092
1153
|
context.usage.inputTokens,
|
|
1093
|
-
context.usage.outputTokens
|
|
1154
|
+
context.usage.outputTokens,
|
|
1155
|
+
context.usage.cachedInputTokens ?? 0,
|
|
1156
|
+
context.usage.cacheCreationInputTokens ?? 0
|
|
1094
1157
|
);
|
|
1095
1158
|
if (costResult) callCost = costResult.totalCost;
|
|
1096
1159
|
} catch {
|
|
@@ -1098,7 +1161,7 @@ async function executeAgent(promptArg, options, env) {
|
|
|
1098
1161
|
}
|
|
1099
1162
|
const callElapsed = progress.getCallElapsedSeconds();
|
|
1100
1163
|
progress.endCall(context.usage);
|
|
1101
|
-
if (
|
|
1164
|
+
if (!options.quiet) {
|
|
1102
1165
|
const summary = renderSummary({
|
|
1103
1166
|
iterations: context.iteration + 1,
|
|
1104
1167
|
model: options.model,
|
|
@@ -1205,7 +1268,14 @@ Command rejected by user with message: "${response}"`
|
|
|
1205
1268
|
printer.write(event.content);
|
|
1206
1269
|
} else if (event.type === "gadget_result") {
|
|
1207
1270
|
progress.pause();
|
|
1208
|
-
if (
|
|
1271
|
+
if (options.quiet) {
|
|
1272
|
+
if (event.result.gadgetName === "TellUser" && event.result.parameters?.message) {
|
|
1273
|
+
const message = String(event.result.parameters.message);
|
|
1274
|
+
const rendered = renderMarkdown(message);
|
|
1275
|
+
env.stdout.write(`${rendered}
|
|
1276
|
+
`);
|
|
1277
|
+
}
|
|
1278
|
+
} else {
|
|
1209
1279
|
const tokenCount = await countGadgetOutputTokens(event.result.result);
|
|
1210
1280
|
env.stderr.write(`${formatGadgetSummary({ ...event.result, tokenCount })}
|
|
1211
1281
|
`);
|
|
@@ -1214,7 +1284,7 @@ Command rejected by user with message: "${response}"`
|
|
|
1214
1284
|
}
|
|
1215
1285
|
progress.complete();
|
|
1216
1286
|
printer.ensureNewline();
|
|
1217
|
-
if (
|
|
1287
|
+
if (!options.quiet && iterations > 1) {
|
|
1218
1288
|
env.stderr.write(`${chalk3.dim("\u2500".repeat(40))}
|
|
1219
1289
|
`);
|
|
1220
1290
|
const summary = renderOverallSummary({
|
|
@@ -1287,7 +1357,7 @@ async function executeComplete(promptArg, options, env) {
|
|
|
1287
1357
|
progress.endCall(usage);
|
|
1288
1358
|
progress.complete();
|
|
1289
1359
|
printer.ensureNewline();
|
|
1290
|
-
if (stderrTTY) {
|
|
1360
|
+
if (stderrTTY && !options.quiet) {
|
|
1291
1361
|
const summary = renderSummary({ finishReason, usage, cost: progress.getTotalCost() });
|
|
1292
1362
|
if (summary) {
|
|
1293
1363
|
env.stderr.write(`${summary}
|
|
@@ -1308,9 +1378,102 @@ import { existsSync, readFileSync } from "node:fs";
|
|
|
1308
1378
|
import { homedir } from "node:os";
|
|
1309
1379
|
import { join } from "node:path";
|
|
1310
1380
|
import { load as parseToml } from "js-toml";
|
|
1381
|
+
|
|
1382
|
+
// src/cli/templates.ts
|
|
1383
|
+
import { Eta } from "eta";
|
|
1384
|
+
var TemplateError = class extends Error {
|
|
1385
|
+
constructor(message, promptName, configPath) {
|
|
1386
|
+
super(promptName ? `[prompts.${promptName}]: ${message}` : message);
|
|
1387
|
+
this.promptName = promptName;
|
|
1388
|
+
this.configPath = configPath;
|
|
1389
|
+
this.name = "TemplateError";
|
|
1390
|
+
}
|
|
1391
|
+
};
|
|
1392
|
+
function createTemplateEngine(prompts, configPath) {
|
|
1393
|
+
const eta = new Eta({
|
|
1394
|
+
views: "/",
|
|
1395
|
+
// Required but we use named templates
|
|
1396
|
+
autoEscape: false,
|
|
1397
|
+
// Don't escape - these are prompts, not HTML
|
|
1398
|
+
autoTrim: false
|
|
1399
|
+
// Preserve whitespace in prompts
|
|
1400
|
+
});
|
|
1401
|
+
for (const [name, template] of Object.entries(prompts)) {
|
|
1402
|
+
try {
|
|
1403
|
+
eta.loadTemplate(`@${name}`, template);
|
|
1404
|
+
} catch (error) {
|
|
1405
|
+
throw new TemplateError(
|
|
1406
|
+
error instanceof Error ? error.message : String(error),
|
|
1407
|
+
name,
|
|
1408
|
+
configPath
|
|
1409
|
+
);
|
|
1410
|
+
}
|
|
1411
|
+
}
|
|
1412
|
+
return eta;
|
|
1413
|
+
}
|
|
1414
|
+
function resolveTemplate(eta, template, context = {}, configPath) {
|
|
1415
|
+
try {
|
|
1416
|
+
const fullContext = {
|
|
1417
|
+
...context,
|
|
1418
|
+
env: process.env
|
|
1419
|
+
};
|
|
1420
|
+
return eta.renderString(template, fullContext);
|
|
1421
|
+
} catch (error) {
|
|
1422
|
+
throw new TemplateError(
|
|
1423
|
+
error instanceof Error ? error.message : String(error),
|
|
1424
|
+
void 0,
|
|
1425
|
+
configPath
|
|
1426
|
+
);
|
|
1427
|
+
}
|
|
1428
|
+
}
|
|
1429
|
+
function validatePrompts(prompts, configPath) {
|
|
1430
|
+
const eta = createTemplateEngine(prompts, configPath);
|
|
1431
|
+
for (const [name, template] of Object.entries(prompts)) {
|
|
1432
|
+
try {
|
|
1433
|
+
eta.renderString(template, { env: {} });
|
|
1434
|
+
} catch (error) {
|
|
1435
|
+
throw new TemplateError(
|
|
1436
|
+
error instanceof Error ? error.message : String(error),
|
|
1437
|
+
name,
|
|
1438
|
+
configPath
|
|
1439
|
+
);
|
|
1440
|
+
}
|
|
1441
|
+
}
|
|
1442
|
+
}
|
|
1443
|
+
function validateEnvVars(template, promptName, configPath) {
|
|
1444
|
+
const envVarPattern = /<%=\s*it\.env\.(\w+)\s*%>/g;
|
|
1445
|
+
const matches = template.matchAll(envVarPattern);
|
|
1446
|
+
for (const match of matches) {
|
|
1447
|
+
const varName = match[1];
|
|
1448
|
+
if (process.env[varName] === void 0) {
|
|
1449
|
+
throw new TemplateError(
|
|
1450
|
+
`Environment variable '${varName}' is not set`,
|
|
1451
|
+
promptName,
|
|
1452
|
+
configPath
|
|
1453
|
+
);
|
|
1454
|
+
}
|
|
1455
|
+
}
|
|
1456
|
+
}
|
|
1457
|
+
function hasTemplateSyntax(str) {
|
|
1458
|
+
return str.includes("<%");
|
|
1459
|
+
}
|
|
1460
|
+
|
|
1461
|
+
// src/cli/config.ts
|
|
1311
1462
|
var GLOBAL_CONFIG_KEYS = /* @__PURE__ */ new Set(["log-level", "log-file", "log-reset"]);
|
|
1312
1463
|
var VALID_LOG_LEVELS = ["silly", "trace", "debug", "info", "warn", "error", "fatal"];
|
|
1313
|
-
var COMPLETE_CONFIG_KEYS = /* @__PURE__ */ new Set([
|
|
1464
|
+
var COMPLETE_CONFIG_KEYS = /* @__PURE__ */ new Set([
|
|
1465
|
+
"model",
|
|
1466
|
+
"system",
|
|
1467
|
+
"temperature",
|
|
1468
|
+
"max-tokens",
|
|
1469
|
+
"quiet",
|
|
1470
|
+
"inherits",
|
|
1471
|
+
"log-level",
|
|
1472
|
+
"log-file",
|
|
1473
|
+
"log-reset",
|
|
1474
|
+
"type"
|
|
1475
|
+
// Allowed for inheritance compatibility, ignored for built-in commands
|
|
1476
|
+
]);
|
|
1314
1477
|
var AGENT_CONFIG_KEYS = /* @__PURE__ */ new Set([
|
|
1315
1478
|
"model",
|
|
1316
1479
|
"system",
|
|
@@ -1321,16 +1484,20 @@ var AGENT_CONFIG_KEYS = /* @__PURE__ */ new Set([
|
|
|
1321
1484
|
"builtins",
|
|
1322
1485
|
"builtin-interaction",
|
|
1323
1486
|
"gadget-start-prefix",
|
|
1324
|
-
"gadget-end-prefix"
|
|
1487
|
+
"gadget-end-prefix",
|
|
1488
|
+
"quiet",
|
|
1489
|
+
"inherits",
|
|
1490
|
+
"log-level",
|
|
1491
|
+
"log-file",
|
|
1492
|
+
"log-reset",
|
|
1493
|
+
"type"
|
|
1494
|
+
// Allowed for inheritance compatibility, ignored for built-in commands
|
|
1325
1495
|
]);
|
|
1326
1496
|
var CUSTOM_CONFIG_KEYS = /* @__PURE__ */ new Set([
|
|
1327
1497
|
...COMPLETE_CONFIG_KEYS,
|
|
1328
1498
|
...AGENT_CONFIG_KEYS,
|
|
1329
1499
|
"type",
|
|
1330
|
-
"description"
|
|
1331
|
-
"log-level",
|
|
1332
|
-
"log-file",
|
|
1333
|
-
"log-reset"
|
|
1500
|
+
"description"
|
|
1334
1501
|
]);
|
|
1335
1502
|
var VALID_PARAMETER_FORMATS = ["json", "yaml", "toml", "auto"];
|
|
1336
1503
|
function getConfigPath() {
|
|
@@ -1381,6 +1548,39 @@ function validateStringArray(value, key, section) {
|
|
|
1381
1548
|
}
|
|
1382
1549
|
return value;
|
|
1383
1550
|
}
|
|
1551
|
+
function validateInherits(value, section) {
|
|
1552
|
+
if (typeof value === "string") {
|
|
1553
|
+
return value;
|
|
1554
|
+
}
|
|
1555
|
+
if (Array.isArray(value)) {
|
|
1556
|
+
for (let i = 0; i < value.length; i++) {
|
|
1557
|
+
if (typeof value[i] !== "string") {
|
|
1558
|
+
throw new ConfigError(`[${section}].inherits[${i}] must be a string`);
|
|
1559
|
+
}
|
|
1560
|
+
}
|
|
1561
|
+
return value;
|
|
1562
|
+
}
|
|
1563
|
+
throw new ConfigError(`[${section}].inherits must be a string or array of strings`);
|
|
1564
|
+
}
|
|
1565
|
+
function validateLoggingConfig(raw, section) {
|
|
1566
|
+
const result = {};
|
|
1567
|
+
if ("log-level" in raw) {
|
|
1568
|
+
const level = validateString(raw["log-level"], "log-level", section);
|
|
1569
|
+
if (!VALID_LOG_LEVELS.includes(level)) {
|
|
1570
|
+
throw new ConfigError(
|
|
1571
|
+
`[${section}].log-level must be one of: ${VALID_LOG_LEVELS.join(", ")}`
|
|
1572
|
+
);
|
|
1573
|
+
}
|
|
1574
|
+
result["log-level"] = level;
|
|
1575
|
+
}
|
|
1576
|
+
if ("log-file" in raw) {
|
|
1577
|
+
result["log-file"] = validateString(raw["log-file"], "log-file", section);
|
|
1578
|
+
}
|
|
1579
|
+
if ("log-reset" in raw) {
|
|
1580
|
+
result["log-reset"] = validateBoolean(raw["log-reset"], "log-reset", section);
|
|
1581
|
+
}
|
|
1582
|
+
return result;
|
|
1583
|
+
}
|
|
1384
1584
|
function validateBaseConfig(raw, section) {
|
|
1385
1585
|
const result = {};
|
|
1386
1586
|
if ("model" in raw) {
|
|
@@ -1395,6 +1595,9 @@ function validateBaseConfig(raw, section) {
|
|
|
1395
1595
|
max: 2
|
|
1396
1596
|
});
|
|
1397
1597
|
}
|
|
1598
|
+
if ("inherits" in raw) {
|
|
1599
|
+
result.inherits = validateInherits(raw.inherits, section);
|
|
1600
|
+
}
|
|
1398
1601
|
return result;
|
|
1399
1602
|
}
|
|
1400
1603
|
function validateGlobalConfig(raw, section) {
|
|
@@ -1407,23 +1610,7 @@ function validateGlobalConfig(raw, section) {
|
|
|
1407
1610
|
throw new ConfigError(`[${section}].${key} is not a valid option`);
|
|
1408
1611
|
}
|
|
1409
1612
|
}
|
|
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;
|
|
1613
|
+
return validateLoggingConfig(rawObj, section);
|
|
1427
1614
|
}
|
|
1428
1615
|
function validateCompleteConfig(raw, section) {
|
|
1429
1616
|
if (typeof raw !== "object" || raw === null) {
|
|
@@ -1435,13 +1622,19 @@ function validateCompleteConfig(raw, section) {
|
|
|
1435
1622
|
throw new ConfigError(`[${section}].${key} is not a valid option`);
|
|
1436
1623
|
}
|
|
1437
1624
|
}
|
|
1438
|
-
const result = {
|
|
1625
|
+
const result = {
|
|
1626
|
+
...validateBaseConfig(rawObj, section),
|
|
1627
|
+
...validateLoggingConfig(rawObj, section)
|
|
1628
|
+
};
|
|
1439
1629
|
if ("max-tokens" in rawObj) {
|
|
1440
1630
|
result["max-tokens"] = validateNumber(rawObj["max-tokens"], "max-tokens", section, {
|
|
1441
1631
|
integer: true,
|
|
1442
1632
|
min: 1
|
|
1443
1633
|
});
|
|
1444
1634
|
}
|
|
1635
|
+
if ("quiet" in rawObj) {
|
|
1636
|
+
result.quiet = validateBoolean(rawObj.quiet, "quiet", section);
|
|
1637
|
+
}
|
|
1445
1638
|
return result;
|
|
1446
1639
|
}
|
|
1447
1640
|
function validateAgentConfig(raw, section) {
|
|
@@ -1454,7 +1647,10 @@ function validateAgentConfig(raw, section) {
|
|
|
1454
1647
|
throw new ConfigError(`[${section}].${key} is not a valid option`);
|
|
1455
1648
|
}
|
|
1456
1649
|
}
|
|
1457
|
-
const result = {
|
|
1650
|
+
const result = {
|
|
1651
|
+
...validateBaseConfig(rawObj, section),
|
|
1652
|
+
...validateLoggingConfig(rawObj, section)
|
|
1653
|
+
};
|
|
1458
1654
|
if ("max-iterations" in rawObj) {
|
|
1459
1655
|
result["max-iterations"] = validateNumber(rawObj["max-iterations"], "max-iterations", section, {
|
|
1460
1656
|
integer: true,
|
|
@@ -1497,6 +1693,9 @@ function validateAgentConfig(raw, section) {
|
|
|
1497
1693
|
section
|
|
1498
1694
|
);
|
|
1499
1695
|
}
|
|
1696
|
+
if ("quiet" in rawObj) {
|
|
1697
|
+
result.quiet = validateBoolean(rawObj.quiet, "quiet", section);
|
|
1698
|
+
}
|
|
1500
1699
|
return result;
|
|
1501
1700
|
}
|
|
1502
1701
|
function validateCustomConfig(raw, section) {
|
|
@@ -1572,20 +1771,22 @@ function validateCustomConfig(raw, section) {
|
|
|
1572
1771
|
min: 1
|
|
1573
1772
|
});
|
|
1574
1773
|
}
|
|
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;
|
|
1774
|
+
if ("quiet" in rawObj) {
|
|
1775
|
+
result.quiet = validateBoolean(rawObj.quiet, "quiet", section);
|
|
1583
1776
|
}
|
|
1584
|
-
|
|
1585
|
-
|
|
1777
|
+
Object.assign(result, validateLoggingConfig(rawObj, section));
|
|
1778
|
+
return result;
|
|
1779
|
+
}
|
|
1780
|
+
function validatePromptsConfig(raw, section) {
|
|
1781
|
+
if (typeof raw !== "object" || raw === null) {
|
|
1782
|
+
throw new ConfigError(`[${section}] must be a table`);
|
|
1586
1783
|
}
|
|
1587
|
-
|
|
1588
|
-
|
|
1784
|
+
const result = {};
|
|
1785
|
+
for (const [key, value] of Object.entries(raw)) {
|
|
1786
|
+
if (typeof value !== "string") {
|
|
1787
|
+
throw new ConfigError(`[${section}].${key} must be a string`);
|
|
1788
|
+
}
|
|
1789
|
+
result[key] = value;
|
|
1589
1790
|
}
|
|
1590
1791
|
return result;
|
|
1591
1792
|
}
|
|
@@ -1603,6 +1804,8 @@ function validateConfig(raw, configPath) {
|
|
|
1603
1804
|
result.complete = validateCompleteConfig(value, key);
|
|
1604
1805
|
} else if (key === "agent") {
|
|
1605
1806
|
result.agent = validateAgentConfig(value, key);
|
|
1807
|
+
} else if (key === "prompts") {
|
|
1808
|
+
result.prompts = validatePromptsConfig(value, key);
|
|
1606
1809
|
} else {
|
|
1607
1810
|
result[key] = validateCustomConfig(value, key);
|
|
1608
1811
|
}
|
|
@@ -1638,12 +1841,119 @@ function loadConfig() {
|
|
|
1638
1841
|
configPath
|
|
1639
1842
|
);
|
|
1640
1843
|
}
|
|
1641
|
-
|
|
1844
|
+
const validated = validateConfig(raw, configPath);
|
|
1845
|
+
const inherited = resolveInheritance(validated, configPath);
|
|
1846
|
+
return resolveTemplatesInConfig(inherited, configPath);
|
|
1642
1847
|
}
|
|
1643
1848
|
function getCustomCommandNames(config) {
|
|
1644
|
-
const reserved = /* @__PURE__ */ new Set(["global", "complete", "agent"]);
|
|
1849
|
+
const reserved = /* @__PURE__ */ new Set(["global", "complete", "agent", "prompts"]);
|
|
1645
1850
|
return Object.keys(config).filter((key) => !reserved.has(key));
|
|
1646
1851
|
}
|
|
1852
|
+
function resolveTemplatesInConfig(config, configPath) {
|
|
1853
|
+
const prompts = config.prompts ?? {};
|
|
1854
|
+
const hasPrompts = Object.keys(prompts).length > 0;
|
|
1855
|
+
let hasTemplates = false;
|
|
1856
|
+
for (const [sectionName, section] of Object.entries(config)) {
|
|
1857
|
+
if (sectionName === "global" || sectionName === "prompts") continue;
|
|
1858
|
+
if (!section || typeof section !== "object") continue;
|
|
1859
|
+
const sectionObj = section;
|
|
1860
|
+
if (typeof sectionObj.system === "string" && hasTemplateSyntax(sectionObj.system)) {
|
|
1861
|
+
hasTemplates = true;
|
|
1862
|
+
break;
|
|
1863
|
+
}
|
|
1864
|
+
}
|
|
1865
|
+
for (const template of Object.values(prompts)) {
|
|
1866
|
+
if (hasTemplateSyntax(template)) {
|
|
1867
|
+
hasTemplates = true;
|
|
1868
|
+
break;
|
|
1869
|
+
}
|
|
1870
|
+
}
|
|
1871
|
+
if (!hasPrompts && !hasTemplates) {
|
|
1872
|
+
return config;
|
|
1873
|
+
}
|
|
1874
|
+
try {
|
|
1875
|
+
validatePrompts(prompts, configPath);
|
|
1876
|
+
} catch (error) {
|
|
1877
|
+
if (error instanceof TemplateError) {
|
|
1878
|
+
throw new ConfigError(error.message, configPath);
|
|
1879
|
+
}
|
|
1880
|
+
throw error;
|
|
1881
|
+
}
|
|
1882
|
+
for (const [name, template] of Object.entries(prompts)) {
|
|
1883
|
+
try {
|
|
1884
|
+
validateEnvVars(template, name, configPath);
|
|
1885
|
+
} catch (error) {
|
|
1886
|
+
if (error instanceof TemplateError) {
|
|
1887
|
+
throw new ConfigError(error.message, configPath);
|
|
1888
|
+
}
|
|
1889
|
+
throw error;
|
|
1890
|
+
}
|
|
1891
|
+
}
|
|
1892
|
+
const eta = createTemplateEngine(prompts, configPath);
|
|
1893
|
+
const result = { ...config };
|
|
1894
|
+
for (const [sectionName, section] of Object.entries(config)) {
|
|
1895
|
+
if (sectionName === "global" || sectionName === "prompts") continue;
|
|
1896
|
+
if (!section || typeof section !== "object") continue;
|
|
1897
|
+
const sectionObj = section;
|
|
1898
|
+
if (typeof sectionObj.system === "string" && hasTemplateSyntax(sectionObj.system)) {
|
|
1899
|
+
try {
|
|
1900
|
+
validateEnvVars(sectionObj.system, void 0, configPath);
|
|
1901
|
+
} catch (error) {
|
|
1902
|
+
if (error instanceof TemplateError) {
|
|
1903
|
+
throw new ConfigError(`[${sectionName}].system: ${error.message}`, configPath);
|
|
1904
|
+
}
|
|
1905
|
+
throw error;
|
|
1906
|
+
}
|
|
1907
|
+
try {
|
|
1908
|
+
const resolved = resolveTemplate(eta, sectionObj.system, {}, configPath);
|
|
1909
|
+
result[sectionName] = {
|
|
1910
|
+
...sectionObj,
|
|
1911
|
+
system: resolved
|
|
1912
|
+
};
|
|
1913
|
+
} catch (error) {
|
|
1914
|
+
if (error instanceof TemplateError) {
|
|
1915
|
+
throw new ConfigError(`[${sectionName}].system: ${error.message}`, configPath);
|
|
1916
|
+
}
|
|
1917
|
+
throw error;
|
|
1918
|
+
}
|
|
1919
|
+
}
|
|
1920
|
+
}
|
|
1921
|
+
return result;
|
|
1922
|
+
}
|
|
1923
|
+
function resolveInheritance(config, configPath) {
|
|
1924
|
+
const resolved = {};
|
|
1925
|
+
const resolving = /* @__PURE__ */ new Set();
|
|
1926
|
+
function resolveSection(name) {
|
|
1927
|
+
if (name in resolved) {
|
|
1928
|
+
return resolved[name];
|
|
1929
|
+
}
|
|
1930
|
+
if (resolving.has(name)) {
|
|
1931
|
+
throw new ConfigError(`Circular inheritance detected: ${name}`, configPath);
|
|
1932
|
+
}
|
|
1933
|
+
const section = config[name];
|
|
1934
|
+
if (section === void 0 || typeof section !== "object") {
|
|
1935
|
+
throw new ConfigError(`Cannot inherit from unknown section: ${name}`, configPath);
|
|
1936
|
+
}
|
|
1937
|
+
resolving.add(name);
|
|
1938
|
+
const sectionObj = section;
|
|
1939
|
+
const inheritsRaw = sectionObj.inherits;
|
|
1940
|
+
const inheritsList = inheritsRaw ? Array.isArray(inheritsRaw) ? inheritsRaw : [inheritsRaw] : [];
|
|
1941
|
+
let merged = {};
|
|
1942
|
+
for (const parent of inheritsList) {
|
|
1943
|
+
const parentResolved = resolveSection(parent);
|
|
1944
|
+
merged = { ...merged, ...parentResolved };
|
|
1945
|
+
}
|
|
1946
|
+
const { inherits: _inherits, ...ownValues } = sectionObj;
|
|
1947
|
+
merged = { ...merged, ...ownValues };
|
|
1948
|
+
resolving.delete(name);
|
|
1949
|
+
resolved[name] = merged;
|
|
1950
|
+
return merged;
|
|
1951
|
+
}
|
|
1952
|
+
for (const name of Object.keys(config)) {
|
|
1953
|
+
resolveSection(name);
|
|
1954
|
+
}
|
|
1955
|
+
return resolved;
|
|
1956
|
+
}
|
|
1647
1957
|
|
|
1648
1958
|
// src/cli/models-command.ts
|
|
1649
1959
|
import chalk4 from "chalk";
|