@llmist/cli 12.3.6 → 13.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/README.md +82 -0
- package/dist/cli.js +797 -137
- package/dist/cli.js.map +1 -1
- package/dist/index.js.map +1 -1
- package/package.json +3 -3
package/dist/cli.js
CHANGED
|
@@ -40,7 +40,18 @@ var OPTION_FLAGS = {
|
|
|
40
40
|
voice: "--voice <name>",
|
|
41
41
|
speechFormat: "--format <format>",
|
|
42
42
|
speechSpeed: "--speed <value>",
|
|
43
|
-
speechOutput: "-o, --output <path>"
|
|
43
|
+
speechOutput: "-o, --output <path>",
|
|
44
|
+
// Rate limiting options
|
|
45
|
+
rateLimitRpm: "--rate-limit-rpm <count>",
|
|
46
|
+
rateLimitTpm: "--rate-limit-tpm <count>",
|
|
47
|
+
rateLimitDaily: "--rate-limit-daily <count>",
|
|
48
|
+
rateLimitSafetyMargin: "--rate-limit-safety-margin <value>",
|
|
49
|
+
noRateLimit: "--no-rate-limit",
|
|
50
|
+
// Retry options
|
|
51
|
+
maxRetries: "--max-retries <count>",
|
|
52
|
+
retryMinTimeout: "--retry-min-timeout <ms>",
|
|
53
|
+
retryMaxTimeout: "--retry-max-timeout <ms>",
|
|
54
|
+
noRetry: "--no-retry"
|
|
44
55
|
};
|
|
45
56
|
var OPTION_DESCRIPTIONS = {
|
|
46
57
|
model: "Model identifier, e.g. openai:gpt-5-nano or anthropic:claude-sonnet-4-5.",
|
|
@@ -66,7 +77,18 @@ var OPTION_DESCRIPTIONS = {
|
|
|
66
77
|
voice: "Voice name for speech generation, e.g. 'nova', 'alloy', 'Zephyr'.",
|
|
67
78
|
speechFormat: "Audio format: 'mp3', 'opus', 'aac', 'flac', 'wav', 'pcm'.",
|
|
68
79
|
speechSpeed: "Speech speed multiplier (0.25 to 4.0, default 1.0).",
|
|
69
|
-
speechOutput: "Output path for audio file. Defaults to stdout if not specified."
|
|
80
|
+
speechOutput: "Output path for audio file. Defaults to stdout if not specified.",
|
|
81
|
+
// Rate limiting descriptions
|
|
82
|
+
rateLimitRpm: "Maximum requests per minute (RPM). Overrides config and defaults.",
|
|
83
|
+
rateLimitTpm: "Maximum tokens per minute (TPM). Overrides config and defaults.",
|
|
84
|
+
rateLimitDaily: "Maximum tokens per day. Useful for Gemini free tier.",
|
|
85
|
+
rateLimitSafetyMargin: "Safety margin (0-1). Start throttling at this percentage of limit.",
|
|
86
|
+
noRateLimit: "Disable rate limiting entirely.",
|
|
87
|
+
// Retry descriptions
|
|
88
|
+
maxRetries: "Maximum retry attempts for failed API calls.",
|
|
89
|
+
retryMinTimeout: "Initial retry delay in milliseconds.",
|
|
90
|
+
retryMaxTimeout: "Maximum retry delay in milliseconds.",
|
|
91
|
+
noRetry: "Disable retry logic for API calls."
|
|
70
92
|
};
|
|
71
93
|
var SUMMARY_PREFIX = "[llmist]";
|
|
72
94
|
|
|
@@ -76,7 +98,7 @@ import { Command, InvalidArgumentError as InvalidArgumentError2 } from "commande
|
|
|
76
98
|
// package.json
|
|
77
99
|
var package_default = {
|
|
78
100
|
name: "@llmist/cli",
|
|
79
|
-
version: "
|
|
101
|
+
version: "13.0.0",
|
|
80
102
|
description: "CLI for llmist - run LLM agents from the command line",
|
|
81
103
|
type: "module",
|
|
82
104
|
main: "dist/cli.js",
|
|
@@ -132,7 +154,7 @@ var package_default = {
|
|
|
132
154
|
node: ">=22.0.0"
|
|
133
155
|
},
|
|
134
156
|
dependencies: {
|
|
135
|
-
llmist: "^
|
|
157
|
+
llmist: "^13.0.0",
|
|
136
158
|
"@unblessed/node": "^1.0.0-alpha.23",
|
|
137
159
|
chalk: "^5.6.2",
|
|
138
160
|
commander: "^12.1.0",
|
|
@@ -146,7 +168,7 @@ var package_default = {
|
|
|
146
168
|
zod: "^4.1.12"
|
|
147
169
|
},
|
|
148
170
|
devDependencies: {
|
|
149
|
-
"@llmist/testing": "^
|
|
171
|
+
"@llmist/testing": "^13.0.0",
|
|
150
172
|
"@types/diff": "^8.0.0",
|
|
151
173
|
"@types/js-yaml": "^4.0.9",
|
|
152
174
|
"@types/marked-terminal": "^6.1.1",
|
|
@@ -399,6 +421,8 @@ var COMPLETE_CONFIG_KEYS = /* @__PURE__ */ new Set([
|
|
|
399
421
|
"inherits",
|
|
400
422
|
"log-level",
|
|
401
423
|
"log-llm-requests",
|
|
424
|
+
"rate-limits",
|
|
425
|
+
"retry",
|
|
402
426
|
"type"
|
|
403
427
|
// Allowed for inheritance compatibility, ignored for built-in commands
|
|
404
428
|
]);
|
|
@@ -429,6 +453,8 @@ var AGENT_CONFIG_KEYS = /* @__PURE__ */ new Set([
|
|
|
429
453
|
"inherits",
|
|
430
454
|
"log-level",
|
|
431
455
|
"log-llm-requests",
|
|
456
|
+
"rate-limits",
|
|
457
|
+
"retry",
|
|
432
458
|
"type"
|
|
433
459
|
// Allowed for inheritance compatibility, ignored for built-in commands
|
|
434
460
|
]);
|
|
@@ -554,7 +580,110 @@ function validateSubagentConfigMap(value, section) {
|
|
|
554
580
|
}
|
|
555
581
|
const result = {};
|
|
556
582
|
for (const [subagentName, config] of Object.entries(value)) {
|
|
557
|
-
result[subagentName] = validateSingleSubagentConfig(
|
|
583
|
+
result[subagentName] = validateSingleSubagentConfig(
|
|
584
|
+
config,
|
|
585
|
+
subagentName,
|
|
586
|
+
`${section}.subagents`
|
|
587
|
+
);
|
|
588
|
+
}
|
|
589
|
+
return result;
|
|
590
|
+
}
|
|
591
|
+
var RATE_LIMITS_CONFIG_KEYS = /* @__PURE__ */ new Set([
|
|
592
|
+
"requests-per-minute",
|
|
593
|
+
"tokens-per-minute",
|
|
594
|
+
"tokens-per-day",
|
|
595
|
+
"safety-margin",
|
|
596
|
+
"enabled"
|
|
597
|
+
]);
|
|
598
|
+
function validateRateLimitsConfig(value, section) {
|
|
599
|
+
if (typeof value !== "object" || value === null || Array.isArray(value)) {
|
|
600
|
+
throw new ConfigError(`[${section}] must be a table`);
|
|
601
|
+
}
|
|
602
|
+
const raw = value;
|
|
603
|
+
const result = {};
|
|
604
|
+
for (const [key, val] of Object.entries(raw)) {
|
|
605
|
+
if (!RATE_LIMITS_CONFIG_KEYS.has(key)) {
|
|
606
|
+
throw new ConfigError(`[${section}] has unknown key: ${key}`);
|
|
607
|
+
}
|
|
608
|
+
switch (key) {
|
|
609
|
+
case "requests-per-minute":
|
|
610
|
+
result["requests-per-minute"] = validateNumber(val, key, section, {
|
|
611
|
+
integer: true,
|
|
612
|
+
min: 1
|
|
613
|
+
});
|
|
614
|
+
break;
|
|
615
|
+
case "tokens-per-minute":
|
|
616
|
+
result["tokens-per-minute"] = validateNumber(val, key, section, { integer: true, min: 1 });
|
|
617
|
+
break;
|
|
618
|
+
case "tokens-per-day":
|
|
619
|
+
result["tokens-per-day"] = validateNumber(val, key, section, { integer: true, min: 1 });
|
|
620
|
+
break;
|
|
621
|
+
case "safety-margin":
|
|
622
|
+
result["safety-margin"] = validateNumber(val, key, section, { min: 0, max: 1 });
|
|
623
|
+
break;
|
|
624
|
+
case "enabled":
|
|
625
|
+
result.enabled = validateBoolean(val, key, section);
|
|
626
|
+
break;
|
|
627
|
+
}
|
|
628
|
+
}
|
|
629
|
+
if (result["requests-per-minute"] && result["requests-per-minute"] > 1e4) {
|
|
630
|
+
console.warn(
|
|
631
|
+
`\u26A0\uFE0F Warning: [${section}].requests-per-minute is very high (${result["requests-per-minute"]}). Make sure your API tier supports this rate.`
|
|
632
|
+
);
|
|
633
|
+
}
|
|
634
|
+
if (result["tokens-per-minute"] && result["tokens-per-minute"] > 5e6) {
|
|
635
|
+
console.warn(
|
|
636
|
+
`\u26A0\uFE0F Warning: [${section}].tokens-per-minute is very high (${result["tokens-per-minute"]}). Make sure your API tier supports this rate.`
|
|
637
|
+
);
|
|
638
|
+
}
|
|
639
|
+
return result;
|
|
640
|
+
}
|
|
641
|
+
var RETRY_CONFIG_KEYS = /* @__PURE__ */ new Set([
|
|
642
|
+
"enabled",
|
|
643
|
+
"retries",
|
|
644
|
+
"min-timeout",
|
|
645
|
+
"max-timeout",
|
|
646
|
+
"factor",
|
|
647
|
+
"randomize",
|
|
648
|
+
"respect-retry-after",
|
|
649
|
+
"max-retry-after-ms"
|
|
650
|
+
]);
|
|
651
|
+
function validateRetryConfig(value, section) {
|
|
652
|
+
if (typeof value !== "object" || value === null || Array.isArray(value)) {
|
|
653
|
+
throw new ConfigError(`[${section}] must be a table`);
|
|
654
|
+
}
|
|
655
|
+
const raw = value;
|
|
656
|
+
const result = {};
|
|
657
|
+
for (const [key, val] of Object.entries(raw)) {
|
|
658
|
+
if (!RETRY_CONFIG_KEYS.has(key)) {
|
|
659
|
+
throw new ConfigError(`[${section}] has unknown key: ${key}`);
|
|
660
|
+
}
|
|
661
|
+
switch (key) {
|
|
662
|
+
case "enabled":
|
|
663
|
+
result.enabled = validateBoolean(val, key, section);
|
|
664
|
+
break;
|
|
665
|
+
case "retries":
|
|
666
|
+
result.retries = validateNumber(val, key, section, { integer: true, min: 0 });
|
|
667
|
+
break;
|
|
668
|
+
case "min-timeout":
|
|
669
|
+
result["min-timeout"] = validateNumber(val, key, section, { integer: true, min: 0 });
|
|
670
|
+
break;
|
|
671
|
+
case "max-timeout":
|
|
672
|
+
result["max-timeout"] = validateNumber(val, key, section, { integer: true, min: 0 });
|
|
673
|
+
break;
|
|
674
|
+
case "factor":
|
|
675
|
+
result.factor = validateNumber(val, key, section, { min: 1 });
|
|
676
|
+
break;
|
|
677
|
+
case "randomize":
|
|
678
|
+
result.randomize = validateBoolean(val, key, section);
|
|
679
|
+
break;
|
|
680
|
+
case "respect-retry-after":
|
|
681
|
+
result["respect-retry-after"] = validateBoolean(val, key, section);
|
|
682
|
+
break;
|
|
683
|
+
case "max-retry-after-ms":
|
|
684
|
+
result["max-retry-after-ms"] = validateNumber(val, key, section, { integer: true, min: 0 });
|
|
685
|
+
break;
|
|
686
|
+
}
|
|
558
687
|
}
|
|
559
688
|
return result;
|
|
560
689
|
}
|
|
@@ -610,7 +739,9 @@ function validateInitialGadgets(value, section) {
|
|
|
610
739
|
}
|
|
611
740
|
const entryObj = entry;
|
|
612
741
|
if (!("gadget" in entryObj)) {
|
|
613
|
-
throw new ConfigError(
|
|
742
|
+
throw new ConfigError(
|
|
743
|
+
`[${section}].initial-gadgets[${i}] is missing required field 'gadget'`
|
|
744
|
+
);
|
|
614
745
|
}
|
|
615
746
|
if (typeof entryObj.gadget !== "string") {
|
|
616
747
|
throw new ConfigError(`[${section}].initial-gadgets[${i}].gadget must be a string`);
|
|
@@ -624,7 +755,9 @@ function validateInitialGadgets(value, section) {
|
|
|
624
755
|
throw new ConfigError(`[${section}].initial-gadgets[${i}].parameters must be a table`);
|
|
625
756
|
}
|
|
626
757
|
if (!("result" in entryObj)) {
|
|
627
|
-
throw new ConfigError(
|
|
758
|
+
throw new ConfigError(
|
|
759
|
+
`[${section}].initial-gadgets[${i}] is missing required field 'result'`
|
|
760
|
+
);
|
|
628
761
|
}
|
|
629
762
|
if (typeof entryObj.result !== "string") {
|
|
630
763
|
throw new ConfigError(`[${section}].initial-gadgets[${i}].result must be a string`);
|
|
@@ -705,7 +838,20 @@ function validateCompleteConfig(raw, section) {
|
|
|
705
838
|
result.quiet = validateBoolean(rawObj.quiet, "quiet", section);
|
|
706
839
|
}
|
|
707
840
|
if ("log-llm-requests" in rawObj) {
|
|
708
|
-
result["log-llm-requests"] = validateBoolean(
|
|
841
|
+
result["log-llm-requests"] = validateBoolean(
|
|
842
|
+
rawObj["log-llm-requests"],
|
|
843
|
+
"log-llm-requests",
|
|
844
|
+
section
|
|
845
|
+
);
|
|
846
|
+
}
|
|
847
|
+
if ("rate-limits" in rawObj) {
|
|
848
|
+
result["rate-limits"] = validateRateLimitsConfig(
|
|
849
|
+
rawObj["rate-limits"],
|
|
850
|
+
`${section}.rate-limits`
|
|
851
|
+
);
|
|
852
|
+
}
|
|
853
|
+
if ("retry" in rawObj) {
|
|
854
|
+
result.retry = validateRetryConfig(rawObj.retry, `${section}.retry`);
|
|
709
855
|
}
|
|
710
856
|
return result;
|
|
711
857
|
}
|
|
@@ -789,7 +935,20 @@ function validateAgentConfig(raw, section) {
|
|
|
789
935
|
result.quiet = validateBoolean(rawObj.quiet, "quiet", section);
|
|
790
936
|
}
|
|
791
937
|
if ("log-llm-requests" in rawObj) {
|
|
792
|
-
result["log-llm-requests"] = validateBoolean(
|
|
938
|
+
result["log-llm-requests"] = validateBoolean(
|
|
939
|
+
rawObj["log-llm-requests"],
|
|
940
|
+
"log-llm-requests",
|
|
941
|
+
section
|
|
942
|
+
);
|
|
943
|
+
}
|
|
944
|
+
if ("rate-limits" in rawObj) {
|
|
945
|
+
result["rate-limits"] = validateRateLimitsConfig(
|
|
946
|
+
rawObj["rate-limits"],
|
|
947
|
+
`${section}.rate-limits`
|
|
948
|
+
);
|
|
949
|
+
}
|
|
950
|
+
if ("retry" in rawObj) {
|
|
951
|
+
result.retry = validateRetryConfig(rawObj.retry, `${section}.retry`);
|
|
793
952
|
}
|
|
794
953
|
return result;
|
|
795
954
|
}
|
|
@@ -959,7 +1118,11 @@ function validateCustomConfig(raw, section) {
|
|
|
959
1118
|
result.quiet = validateBoolean(rawObj.quiet, "quiet", section);
|
|
960
1119
|
}
|
|
961
1120
|
if ("log-llm-requests" in rawObj) {
|
|
962
|
-
result["log-llm-requests"] = validateBoolean(
|
|
1121
|
+
result["log-llm-requests"] = validateBoolean(
|
|
1122
|
+
rawObj["log-llm-requests"],
|
|
1123
|
+
"log-llm-requests",
|
|
1124
|
+
section
|
|
1125
|
+
);
|
|
963
1126
|
}
|
|
964
1127
|
Object.assign(result, validateLoggingConfig(rawObj, section));
|
|
965
1128
|
return result;
|
|
@@ -999,6 +1162,10 @@ function validateConfig(raw, configPath) {
|
|
|
999
1162
|
result.prompts = validatePromptsConfig(value, key);
|
|
1000
1163
|
} else if (key === "subagents") {
|
|
1001
1164
|
result.subagents = validateGlobalSubagentConfig(value, key);
|
|
1165
|
+
} else if (key === "rate-limits") {
|
|
1166
|
+
result["rate-limits"] = validateRateLimitsConfig(value, key);
|
|
1167
|
+
} else if (key === "retry") {
|
|
1168
|
+
result.retry = validateRetryConfig(value, key);
|
|
1002
1169
|
} else {
|
|
1003
1170
|
result[key] = validateCustomConfig(value, key);
|
|
1004
1171
|
}
|
|
@@ -1046,7 +1213,9 @@ function getCustomCommandNames(config) {
|
|
|
1046
1213
|
"image",
|
|
1047
1214
|
"speech",
|
|
1048
1215
|
"prompts",
|
|
1049
|
-
"subagents"
|
|
1216
|
+
"subagents",
|
|
1217
|
+
"rate-limits",
|
|
1218
|
+
"retry"
|
|
1050
1219
|
]);
|
|
1051
1220
|
return Object.keys(config).filter((key) => !reserved.has(key));
|
|
1052
1221
|
}
|
|
@@ -1191,7 +1360,7 @@ function resolveInheritance(config, configPath) {
|
|
|
1191
1360
|
if (resolvedGadgets.length > 0) {
|
|
1192
1361
|
merged.gadgets = resolvedGadgets;
|
|
1193
1362
|
}
|
|
1194
|
-
delete merged
|
|
1363
|
+
delete merged.gadget;
|
|
1195
1364
|
delete merged["gadget-add"];
|
|
1196
1365
|
delete merged["gadget-remove"];
|
|
1197
1366
|
resolving.delete(name);
|
|
@@ -3483,7 +3652,42 @@ function addCompleteOptions(cmd, defaults) {
|
|
|
3483
3652
|
OPTION_FLAGS.logLlmRequests,
|
|
3484
3653
|
OPTION_DESCRIPTIONS.logLlmRequests,
|
|
3485
3654
|
defaults?.["log-llm-requests"]
|
|
3486
|
-
).option(OPTION_FLAGS.inputImage, OPTION_DESCRIPTIONS.inputImage).option(OPTION_FLAGS.inputAudio, OPTION_DESCRIPTIONS.inputAudio)
|
|
3655
|
+
).option(OPTION_FLAGS.inputImage, OPTION_DESCRIPTIONS.inputImage).option(OPTION_FLAGS.inputAudio, OPTION_DESCRIPTIONS.inputAudio).option(
|
|
3656
|
+
OPTION_FLAGS.rateLimitRpm,
|
|
3657
|
+
OPTION_DESCRIPTIONS.rateLimitRpm,
|
|
3658
|
+
createNumericParser({ label: "RPM", integer: true, min: 1 }),
|
|
3659
|
+
defaults?.["rate-limits"]?.["requests-per-minute"]
|
|
3660
|
+
).option(
|
|
3661
|
+
OPTION_FLAGS.rateLimitTpm,
|
|
3662
|
+
OPTION_DESCRIPTIONS.rateLimitTpm,
|
|
3663
|
+
createNumericParser({ label: "TPM", integer: true, min: 1 }),
|
|
3664
|
+
defaults?.["rate-limits"]?.["tokens-per-minute"]
|
|
3665
|
+
).option(
|
|
3666
|
+
OPTION_FLAGS.rateLimitDaily,
|
|
3667
|
+
OPTION_DESCRIPTIONS.rateLimitDaily,
|
|
3668
|
+
createNumericParser({ label: "Daily tokens", integer: true, min: 1 }),
|
|
3669
|
+
defaults?.["rate-limits"]?.["tokens-per-day"]
|
|
3670
|
+
).option(
|
|
3671
|
+
OPTION_FLAGS.rateLimitSafetyMargin,
|
|
3672
|
+
OPTION_DESCRIPTIONS.rateLimitSafetyMargin,
|
|
3673
|
+
createNumericParser({ label: "Safety margin", min: 0, max: 1 }),
|
|
3674
|
+
defaults?.["rate-limits"]?.["safety-margin"]
|
|
3675
|
+
).option(OPTION_FLAGS.noRateLimit, OPTION_DESCRIPTIONS.noRateLimit).option(
|
|
3676
|
+
OPTION_FLAGS.maxRetries,
|
|
3677
|
+
OPTION_DESCRIPTIONS.maxRetries,
|
|
3678
|
+
createNumericParser({ label: "Max retries", integer: true, min: 0 }),
|
|
3679
|
+
defaults?.retry?.retries
|
|
3680
|
+
).option(
|
|
3681
|
+
OPTION_FLAGS.retryMinTimeout,
|
|
3682
|
+
OPTION_DESCRIPTIONS.retryMinTimeout,
|
|
3683
|
+
createNumericParser({ label: "Min timeout", integer: true, min: 0 }),
|
|
3684
|
+
defaults?.retry?.["min-timeout"]
|
|
3685
|
+
).option(
|
|
3686
|
+
OPTION_FLAGS.retryMaxTimeout,
|
|
3687
|
+
OPTION_DESCRIPTIONS.retryMaxTimeout,
|
|
3688
|
+
createNumericParser({ label: "Max timeout", integer: true, min: 0 }),
|
|
3689
|
+
defaults?.retry?.["max-timeout"]
|
|
3690
|
+
).option(OPTION_FLAGS.noRetry, OPTION_DESCRIPTIONS.noRetry);
|
|
3487
3691
|
}
|
|
3488
3692
|
function addAgentOptions(cmd, defaults) {
|
|
3489
3693
|
const gadgetAccumulator = (value, previous = []) => [
|
|
@@ -3511,7 +3715,42 @@ function addAgentOptions(cmd, defaults) {
|
|
|
3511
3715
|
OPTION_FLAGS.logLlmRequests,
|
|
3512
3716
|
OPTION_DESCRIPTIONS.logLlmRequests,
|
|
3513
3717
|
defaults?.["log-llm-requests"]
|
|
3514
|
-
).option(OPTION_FLAGS.inputImage, OPTION_DESCRIPTIONS.inputImage).option(OPTION_FLAGS.inputAudio, OPTION_DESCRIPTIONS.inputAudio)
|
|
3718
|
+
).option(OPTION_FLAGS.inputImage, OPTION_DESCRIPTIONS.inputImage).option(OPTION_FLAGS.inputAudio, OPTION_DESCRIPTIONS.inputAudio).option(
|
|
3719
|
+
OPTION_FLAGS.rateLimitRpm,
|
|
3720
|
+
OPTION_DESCRIPTIONS.rateLimitRpm,
|
|
3721
|
+
createNumericParser({ label: "RPM", integer: true, min: 1 }),
|
|
3722
|
+
defaults?.["rate-limits"]?.["requests-per-minute"]
|
|
3723
|
+
).option(
|
|
3724
|
+
OPTION_FLAGS.rateLimitTpm,
|
|
3725
|
+
OPTION_DESCRIPTIONS.rateLimitTpm,
|
|
3726
|
+
createNumericParser({ label: "TPM", integer: true, min: 1 }),
|
|
3727
|
+
defaults?.["rate-limits"]?.["tokens-per-minute"]
|
|
3728
|
+
).option(
|
|
3729
|
+
OPTION_FLAGS.rateLimitDaily,
|
|
3730
|
+
OPTION_DESCRIPTIONS.rateLimitDaily,
|
|
3731
|
+
createNumericParser({ label: "Daily tokens", integer: true, min: 1 }),
|
|
3732
|
+
defaults?.["rate-limits"]?.["tokens-per-day"]
|
|
3733
|
+
).option(
|
|
3734
|
+
OPTION_FLAGS.rateLimitSafetyMargin,
|
|
3735
|
+
OPTION_DESCRIPTIONS.rateLimitSafetyMargin,
|
|
3736
|
+
createNumericParser({ label: "Safety margin", min: 0, max: 1 }),
|
|
3737
|
+
defaults?.["rate-limits"]?.["safety-margin"]
|
|
3738
|
+
).option(OPTION_FLAGS.noRateLimit, OPTION_DESCRIPTIONS.noRateLimit).option(
|
|
3739
|
+
OPTION_FLAGS.maxRetries,
|
|
3740
|
+
OPTION_DESCRIPTIONS.maxRetries,
|
|
3741
|
+
createNumericParser({ label: "Max retries", integer: true, min: 0 }),
|
|
3742
|
+
defaults?.retry?.retries
|
|
3743
|
+
).option(
|
|
3744
|
+
OPTION_FLAGS.retryMinTimeout,
|
|
3745
|
+
OPTION_DESCRIPTIONS.retryMinTimeout,
|
|
3746
|
+
createNumericParser({ label: "Min timeout", integer: true, min: 0 }),
|
|
3747
|
+
defaults?.retry?.["min-timeout"]
|
|
3748
|
+
).option(
|
|
3749
|
+
OPTION_FLAGS.retryMaxTimeout,
|
|
3750
|
+
OPTION_DESCRIPTIONS.retryMaxTimeout,
|
|
3751
|
+
createNumericParser({ label: "Max timeout", integer: true, min: 0 }),
|
|
3752
|
+
defaults?.retry?.["max-timeout"]
|
|
3753
|
+
).option(OPTION_FLAGS.noRetry, OPTION_DESCRIPTIONS.noRetry);
|
|
3515
3754
|
}
|
|
3516
3755
|
function configToCompleteOptions(config) {
|
|
3517
3756
|
const result = {};
|
|
@@ -3521,6 +3760,21 @@ function configToCompleteOptions(config) {
|
|
|
3521
3760
|
if (config["max-tokens"] !== void 0) result.maxTokens = config["max-tokens"];
|
|
3522
3761
|
if (config.quiet !== void 0) result.quiet = config.quiet;
|
|
3523
3762
|
if (config["log-llm-requests"] !== void 0) result.logLlmRequests = config["log-llm-requests"];
|
|
3763
|
+
if (config["rate-limits"]) {
|
|
3764
|
+
const rl = config["rate-limits"];
|
|
3765
|
+
if (rl["requests-per-minute"] !== void 0) result.rateLimitRpm = rl["requests-per-minute"];
|
|
3766
|
+
if (rl["tokens-per-minute"] !== void 0) result.rateLimitTpm = rl["tokens-per-minute"];
|
|
3767
|
+
if (rl["tokens-per-day"] !== void 0) result.rateLimitDaily = rl["tokens-per-day"];
|
|
3768
|
+
if (rl["safety-margin"] !== void 0) result.rateLimitSafetyMargin = rl["safety-margin"];
|
|
3769
|
+
if (rl.enabled === false) result.noRateLimit = true;
|
|
3770
|
+
}
|
|
3771
|
+
if (config.retry) {
|
|
3772
|
+
const r = config.retry;
|
|
3773
|
+
if (r.retries !== void 0) result.maxRetries = r.retries;
|
|
3774
|
+
if (r["min-timeout"] !== void 0) result.retryMinTimeout = r["min-timeout"];
|
|
3775
|
+
if (r["max-timeout"] !== void 0) result.retryMaxTimeout = r["max-timeout"];
|
|
3776
|
+
if (r.enabled === false) result.noRetry = true;
|
|
3777
|
+
}
|
|
3524
3778
|
return result;
|
|
3525
3779
|
}
|
|
3526
3780
|
function configToAgentOptions(config) {
|
|
@@ -3545,9 +3799,190 @@ function configToAgentOptions(config) {
|
|
|
3545
3799
|
if (config["log-llm-requests"] !== void 0) result.logLlmRequests = config["log-llm-requests"];
|
|
3546
3800
|
if (config.subagents !== void 0) result.subagents = config.subagents;
|
|
3547
3801
|
if (config["initial-gadgets"] !== void 0) result.initialGadgets = config["initial-gadgets"];
|
|
3802
|
+
if (config["rate-limits"]) {
|
|
3803
|
+
const rl = config["rate-limits"];
|
|
3804
|
+
if (rl["requests-per-minute"] !== void 0) result.rateLimitRpm = rl["requests-per-minute"];
|
|
3805
|
+
if (rl["tokens-per-minute"] !== void 0) result.rateLimitTpm = rl["tokens-per-minute"];
|
|
3806
|
+
if (rl["tokens-per-day"] !== void 0) result.rateLimitDaily = rl["tokens-per-day"];
|
|
3807
|
+
if (rl["safety-margin"] !== void 0) result.rateLimitSafetyMargin = rl["safety-margin"];
|
|
3808
|
+
if (rl.enabled === false) result.noRateLimit = true;
|
|
3809
|
+
}
|
|
3810
|
+
if (config.retry) {
|
|
3811
|
+
const r = config.retry;
|
|
3812
|
+
if (r.retries !== void 0) result.maxRetries = r.retries;
|
|
3813
|
+
if (r["min-timeout"] !== void 0) result.retryMinTimeout = r["min-timeout"];
|
|
3814
|
+
if (r["max-timeout"] !== void 0) result.retryMaxTimeout = r["max-timeout"];
|
|
3815
|
+
if (r.enabled === false) result.noRetry = true;
|
|
3816
|
+
}
|
|
3548
3817
|
return result;
|
|
3549
3818
|
}
|
|
3550
3819
|
|
|
3820
|
+
// src/rate-limit-resolver.ts
|
|
3821
|
+
import { getProvider, resolveModel } from "llmist";
|
|
3822
|
+
var PROVIDER_DEFAULTS = {
|
|
3823
|
+
anthropic: {
|
|
3824
|
+
requestsPerMinute: 50,
|
|
3825
|
+
// Tier 1 safe
|
|
3826
|
+
tokensPerMinute: 4e4,
|
|
3827
|
+
// Tier 1 for claude-3-5-sonnet
|
|
3828
|
+
safetyMargin: 0.8
|
|
3829
|
+
},
|
|
3830
|
+
openai: {
|
|
3831
|
+
requestsPerMinute: 3,
|
|
3832
|
+
// Free tier minimum (very conservative)
|
|
3833
|
+
tokensPerMinute: 4e4,
|
|
3834
|
+
safetyMargin: 0.8
|
|
3835
|
+
},
|
|
3836
|
+
gemini: {
|
|
3837
|
+
requestsPerMinute: 15,
|
|
3838
|
+
// Free tier documented
|
|
3839
|
+
tokensPerMinute: 1e6,
|
|
3840
|
+
// Free tier documented
|
|
3841
|
+
tokensPerDay: 15e5,
|
|
3842
|
+
// Free tier daily limit
|
|
3843
|
+
safetyMargin: 0.8
|
|
3844
|
+
}
|
|
3845
|
+
};
|
|
3846
|
+
function detectProvider(model) {
|
|
3847
|
+
try {
|
|
3848
|
+
const resolved = resolveModel(model, { silent: true });
|
|
3849
|
+
return getProvider(resolved) ?? null;
|
|
3850
|
+
} catch {
|
|
3851
|
+
return null;
|
|
3852
|
+
}
|
|
3853
|
+
}
|
|
3854
|
+
function resolveRateLimitConfig(options, globalConfig, profileConfig, model) {
|
|
3855
|
+
if (options.noRateLimit === true) {
|
|
3856
|
+
return { enabled: false, safetyMargin: 0.8 };
|
|
3857
|
+
}
|
|
3858
|
+
let resolved;
|
|
3859
|
+
if (model) {
|
|
3860
|
+
const provider = detectProvider(model);
|
|
3861
|
+
if (provider && PROVIDER_DEFAULTS[provider]) {
|
|
3862
|
+
resolved = { ...PROVIDER_DEFAULTS[provider] };
|
|
3863
|
+
}
|
|
3864
|
+
}
|
|
3865
|
+
if (!resolved) {
|
|
3866
|
+
resolved = {};
|
|
3867
|
+
}
|
|
3868
|
+
if (globalConfig) {
|
|
3869
|
+
if (globalConfig["requests-per-minute"] !== void 0) {
|
|
3870
|
+
resolved.requestsPerMinute = globalConfig["requests-per-minute"];
|
|
3871
|
+
}
|
|
3872
|
+
if (globalConfig["tokens-per-minute"] !== void 0) {
|
|
3873
|
+
resolved.tokensPerMinute = globalConfig["tokens-per-minute"];
|
|
3874
|
+
}
|
|
3875
|
+
if (globalConfig["tokens-per-day"] !== void 0) {
|
|
3876
|
+
resolved.tokensPerDay = globalConfig["tokens-per-day"];
|
|
3877
|
+
}
|
|
3878
|
+
if (globalConfig["safety-margin"] !== void 0) {
|
|
3879
|
+
resolved.safetyMargin = globalConfig["safety-margin"];
|
|
3880
|
+
}
|
|
3881
|
+
if (globalConfig.enabled !== void 0) {
|
|
3882
|
+
resolved.enabled = globalConfig.enabled;
|
|
3883
|
+
}
|
|
3884
|
+
}
|
|
3885
|
+
if (profileConfig) {
|
|
3886
|
+
if (profileConfig["requests-per-minute"] !== void 0) {
|
|
3887
|
+
resolved.requestsPerMinute = profileConfig["requests-per-minute"];
|
|
3888
|
+
}
|
|
3889
|
+
if (profileConfig["tokens-per-minute"] !== void 0) {
|
|
3890
|
+
resolved.tokensPerMinute = profileConfig["tokens-per-minute"];
|
|
3891
|
+
}
|
|
3892
|
+
if (profileConfig["tokens-per-day"] !== void 0) {
|
|
3893
|
+
resolved.tokensPerDay = profileConfig["tokens-per-day"];
|
|
3894
|
+
}
|
|
3895
|
+
if (profileConfig["safety-margin"] !== void 0) {
|
|
3896
|
+
resolved.safetyMargin = profileConfig["safety-margin"];
|
|
3897
|
+
}
|
|
3898
|
+
if (profileConfig.enabled !== void 0) {
|
|
3899
|
+
resolved.enabled = profileConfig.enabled;
|
|
3900
|
+
}
|
|
3901
|
+
}
|
|
3902
|
+
if (options.rateLimitRpm !== void 0) {
|
|
3903
|
+
resolved.requestsPerMinute = options.rateLimitRpm;
|
|
3904
|
+
}
|
|
3905
|
+
if (options.rateLimitTpm !== void 0) {
|
|
3906
|
+
resolved.tokensPerMinute = options.rateLimitTpm;
|
|
3907
|
+
}
|
|
3908
|
+
if (options.rateLimitDaily !== void 0) {
|
|
3909
|
+
resolved.tokensPerDay = options.rateLimitDaily;
|
|
3910
|
+
}
|
|
3911
|
+
if (options.rateLimitSafetyMargin !== void 0) {
|
|
3912
|
+
resolved.safetyMargin = options.rateLimitSafetyMargin;
|
|
3913
|
+
}
|
|
3914
|
+
const hasLimits = resolved.requestsPerMinute !== void 0 || resolved.tokensPerMinute !== void 0 || resolved.tokensPerDay !== void 0;
|
|
3915
|
+
if (resolved.enabled === false) {
|
|
3916
|
+
return { enabled: false, safetyMargin: resolved.safetyMargin ?? 0.8 };
|
|
3917
|
+
}
|
|
3918
|
+
if (!hasLimits) {
|
|
3919
|
+
return void 0;
|
|
3920
|
+
}
|
|
3921
|
+
return {
|
|
3922
|
+
...resolved,
|
|
3923
|
+
enabled: true,
|
|
3924
|
+
safetyMargin: resolved.safetyMargin ?? 0.8
|
|
3925
|
+
};
|
|
3926
|
+
}
|
|
3927
|
+
var DEFAULT_RETRY_CONFIG = {
|
|
3928
|
+
enabled: true,
|
|
3929
|
+
retries: 3,
|
|
3930
|
+
minTimeout: 1e3,
|
|
3931
|
+
maxTimeout: 3e4,
|
|
3932
|
+
factor: 2,
|
|
3933
|
+
randomize: true,
|
|
3934
|
+
respectRetryAfter: true,
|
|
3935
|
+
maxRetryAfterMs: 12e4
|
|
3936
|
+
};
|
|
3937
|
+
function resolveRetryConfig(options, globalConfig, profileConfig) {
|
|
3938
|
+
const resolved = { ...DEFAULT_RETRY_CONFIG };
|
|
3939
|
+
if (globalConfig) {
|
|
3940
|
+
if (globalConfig.enabled !== void 0) resolved.enabled = globalConfig.enabled;
|
|
3941
|
+
if (globalConfig.retries !== void 0) resolved.retries = globalConfig.retries;
|
|
3942
|
+
if (globalConfig["min-timeout"] !== void 0)
|
|
3943
|
+
resolved.minTimeout = globalConfig["min-timeout"];
|
|
3944
|
+
if (globalConfig["max-timeout"] !== void 0)
|
|
3945
|
+
resolved.maxTimeout = globalConfig["max-timeout"];
|
|
3946
|
+
if (globalConfig.factor !== void 0) resolved.factor = globalConfig.factor;
|
|
3947
|
+
if (globalConfig.randomize !== void 0) resolved.randomize = globalConfig.randomize;
|
|
3948
|
+
if (globalConfig["respect-retry-after"] !== void 0) {
|
|
3949
|
+
resolved.respectRetryAfter = globalConfig["respect-retry-after"];
|
|
3950
|
+
}
|
|
3951
|
+
if (globalConfig["max-retry-after-ms"] !== void 0) {
|
|
3952
|
+
resolved.maxRetryAfterMs = globalConfig["max-retry-after-ms"];
|
|
3953
|
+
}
|
|
3954
|
+
}
|
|
3955
|
+
if (profileConfig) {
|
|
3956
|
+
if (profileConfig.enabled !== void 0) resolved.enabled = profileConfig.enabled;
|
|
3957
|
+
if (profileConfig.retries !== void 0) resolved.retries = profileConfig.retries;
|
|
3958
|
+
if (profileConfig["min-timeout"] !== void 0)
|
|
3959
|
+
resolved.minTimeout = profileConfig["min-timeout"];
|
|
3960
|
+
if (profileConfig["max-timeout"] !== void 0)
|
|
3961
|
+
resolved.maxTimeout = profileConfig["max-timeout"];
|
|
3962
|
+
if (profileConfig.factor !== void 0) resolved.factor = profileConfig.factor;
|
|
3963
|
+
if (profileConfig.randomize !== void 0) resolved.randomize = profileConfig.randomize;
|
|
3964
|
+
if (profileConfig["respect-retry-after"] !== void 0) {
|
|
3965
|
+
resolved.respectRetryAfter = profileConfig["respect-retry-after"];
|
|
3966
|
+
}
|
|
3967
|
+
if (profileConfig["max-retry-after-ms"] !== void 0) {
|
|
3968
|
+
resolved.maxRetryAfterMs = profileConfig["max-retry-after-ms"];
|
|
3969
|
+
}
|
|
3970
|
+
}
|
|
3971
|
+
if (options.maxRetries !== void 0) {
|
|
3972
|
+
resolved.retries = options.maxRetries;
|
|
3973
|
+
}
|
|
3974
|
+
if (options.retryMinTimeout !== void 0) {
|
|
3975
|
+
resolved.minTimeout = options.retryMinTimeout;
|
|
3976
|
+
}
|
|
3977
|
+
if (options.retryMaxTimeout !== void 0) {
|
|
3978
|
+
resolved.maxTimeout = options.retryMaxTimeout;
|
|
3979
|
+
}
|
|
3980
|
+
if (options.noRetry === true) {
|
|
3981
|
+
resolved.enabled = false;
|
|
3982
|
+
}
|
|
3983
|
+
return resolved;
|
|
3984
|
+
}
|
|
3985
|
+
|
|
3551
3986
|
// src/subagent-config.ts
|
|
3552
3987
|
var INHERIT_MODEL = "inherit";
|
|
3553
3988
|
function resolveSubagentConfig(subagentName, parentModel, profileConfig, globalConfig) {
|
|
@@ -4142,6 +4577,33 @@ var BlockRenderer = class _BlockRenderer {
|
|
|
4142
4577
|
this.rebuildBlocks();
|
|
4143
4578
|
return id;
|
|
4144
4579
|
}
|
|
4580
|
+
/**
|
|
4581
|
+
* Add a system message block (for rate limiting, retry notifications, etc.).
|
|
4582
|
+
*
|
|
4583
|
+
* Displays immediately with an icon and color based on category.
|
|
4584
|
+
* Non-selectable like text blocks.
|
|
4585
|
+
*
|
|
4586
|
+
* @param message - The system message text
|
|
4587
|
+
* @param category - Message category for styling
|
|
4588
|
+
* @returns The block ID
|
|
4589
|
+
*/
|
|
4590
|
+
addSystemMessage(message, category) {
|
|
4591
|
+
const id = this.generateId("system");
|
|
4592
|
+
const node = {
|
|
4593
|
+
id,
|
|
4594
|
+
type: "system_message",
|
|
4595
|
+
depth: 0,
|
|
4596
|
+
parentId: null,
|
|
4597
|
+
sessionId: this.currentSessionId,
|
|
4598
|
+
message,
|
|
4599
|
+
category,
|
|
4600
|
+
children: []
|
|
4601
|
+
};
|
|
4602
|
+
this.nodes.set(id, node);
|
|
4603
|
+
this.rootIds.push(id);
|
|
4604
|
+
this.rebuildBlocks();
|
|
4605
|
+
return id;
|
|
4606
|
+
}
|
|
4145
4607
|
/**
|
|
4146
4608
|
* Add a user message block (for REPL mid-session input).
|
|
4147
4609
|
*
|
|
@@ -4524,6 +4986,50 @@ ${fullContent}
|
|
|
4524
4986
|
}
|
|
4525
4987
|
return this.abbreviateToLines(fullContent, 2, selected);
|
|
4526
4988
|
}
|
|
4989
|
+
case "system_message": {
|
|
4990
|
+
const icon = this.getSystemMessageIcon(node.category);
|
|
4991
|
+
const color = this.getSystemMessageColor(node.category);
|
|
4992
|
+
const RESET2 = "\x1B[0m";
|
|
4993
|
+
return `${indent}${color}${icon} ${node.message}${RESET2}`;
|
|
4994
|
+
}
|
|
4995
|
+
}
|
|
4996
|
+
}
|
|
4997
|
+
/**
|
|
4998
|
+
* Get icon for system message category.
|
|
4999
|
+
*/
|
|
5000
|
+
getSystemMessageIcon(category) {
|
|
5001
|
+
switch (category) {
|
|
5002
|
+
case "throttle":
|
|
5003
|
+
return "\u23F8";
|
|
5004
|
+
case "retry":
|
|
5005
|
+
return "\u{1F504}";
|
|
5006
|
+
case "info":
|
|
5007
|
+
return "\u2139\uFE0F";
|
|
5008
|
+
case "warning":
|
|
5009
|
+
return "\u26A0\uFE0F";
|
|
5010
|
+
case "error":
|
|
5011
|
+
return "\u274C";
|
|
5012
|
+
}
|
|
5013
|
+
}
|
|
5014
|
+
/**
|
|
5015
|
+
* Get ANSI color code for system message category.
|
|
5016
|
+
*/
|
|
5017
|
+
getSystemMessageColor(category) {
|
|
5018
|
+
const YELLOW2 = "\x1B[33m";
|
|
5019
|
+
const BLUE = "\x1B[34m";
|
|
5020
|
+
const GRAY = "\x1B[90m";
|
|
5021
|
+
const RED2 = "\x1B[31m";
|
|
5022
|
+
switch (category) {
|
|
5023
|
+
case "throttle":
|
|
5024
|
+
return YELLOW2;
|
|
5025
|
+
case "retry":
|
|
5026
|
+
return BLUE;
|
|
5027
|
+
case "info":
|
|
5028
|
+
return GRAY;
|
|
5029
|
+
case "warning":
|
|
5030
|
+
return YELLOW2;
|
|
5031
|
+
case "error":
|
|
5032
|
+
return RED2;
|
|
4527
5033
|
}
|
|
4528
5034
|
}
|
|
4529
5035
|
/**
|
|
@@ -6268,6 +6774,10 @@ var StatusBar = class {
|
|
|
6268
6774
|
nodeIdToGadgetName = /* @__PURE__ */ new Map();
|
|
6269
6775
|
/** Tree subscription unsubscribe function */
|
|
6270
6776
|
treeUnsubscribe = null;
|
|
6777
|
+
/** Rate limiting state */
|
|
6778
|
+
rateLimitState = null;
|
|
6779
|
+
/** Retry state */
|
|
6780
|
+
retryState = null;
|
|
6271
6781
|
constructor(statusBox, model, renderCallback, renderNowCallback) {
|
|
6272
6782
|
this.statusBox = statusBox;
|
|
6273
6783
|
this.renderCallback = renderCallback;
|
|
@@ -6407,6 +6917,37 @@ var StatusBar = class {
|
|
|
6407
6917
|
this.stopSpinner();
|
|
6408
6918
|
this.render();
|
|
6409
6919
|
}
|
|
6920
|
+
/**
|
|
6921
|
+
* Show rate limiting throttle indicator.
|
|
6922
|
+
* @param delayMs - Delay in milliseconds before next request
|
|
6923
|
+
*/
|
|
6924
|
+
showThrottling(delayMs) {
|
|
6925
|
+
this.rateLimitState = { isThrottling: true, delayMs };
|
|
6926
|
+
this.render(true);
|
|
6927
|
+
}
|
|
6928
|
+
/**
|
|
6929
|
+
* Clear rate limiting throttle indicator.
|
|
6930
|
+
*/
|
|
6931
|
+
clearThrottling() {
|
|
6932
|
+
this.rateLimitState = null;
|
|
6933
|
+
this.render(true);
|
|
6934
|
+
}
|
|
6935
|
+
/**
|
|
6936
|
+
* Show retry attempt indicator.
|
|
6937
|
+
* @param attemptNumber - Current attempt number (1-based)
|
|
6938
|
+
* @param retriesLeft - Number of retries remaining after this attempt
|
|
6939
|
+
*/
|
|
6940
|
+
showRetry(attemptNumber, retriesLeft) {
|
|
6941
|
+
this.retryState = { attemptNumber, retriesLeft };
|
|
6942
|
+
this.render(true);
|
|
6943
|
+
}
|
|
6944
|
+
/**
|
|
6945
|
+
* Clear retry attempt indicator.
|
|
6946
|
+
*/
|
|
6947
|
+
clearRetry() {
|
|
6948
|
+
this.retryState = null;
|
|
6949
|
+
this.render(true);
|
|
6950
|
+
}
|
|
6410
6951
|
// ─────────────────────────────────────────────────────────────────────────────
|
|
6411
6952
|
// Tree Subscription (for tree-only block creation)
|
|
6412
6953
|
// ─────────────────────────────────────────────────────────────────────────────
|
|
@@ -6634,6 +7175,15 @@ var StatusBar = class {
|
|
|
6634
7175
|
const typeStr = debug.nodeType ? ` [${debug.nodeType}]` : "";
|
|
6635
7176
|
parts.push(`${GRAY}${debugStr}${typeStr}${RESET2}`);
|
|
6636
7177
|
}
|
|
7178
|
+
if (this.rateLimitState?.isThrottling) {
|
|
7179
|
+
const seconds = Math.ceil(this.rateLimitState.delayMs / 1e3);
|
|
7180
|
+
parts.push(`${YELLOW2}\u23F8 Throttled ${seconds}s${RESET2}`);
|
|
7181
|
+
}
|
|
7182
|
+
if (this.retryState) {
|
|
7183
|
+
const { attemptNumber, retriesLeft } = this.retryState;
|
|
7184
|
+
const totalAttempts = attemptNumber + retriesLeft;
|
|
7185
|
+
parts.push(`${BLUE}\u{1F504} Retry ${attemptNumber}/${totalAttempts}${RESET2}`);
|
|
7186
|
+
}
|
|
6637
7187
|
if (this.activeLLMCalls.size > 0 || this.activeGadgets.size > 0) {
|
|
6638
7188
|
const spinner = SPINNER_FRAMES2[this.spinnerFrame];
|
|
6639
7189
|
if (this.activeLLMCalls.size > 0) {
|
|
@@ -7018,6 +7568,42 @@ var TUIApp = class _TUIApp {
|
|
|
7018
7568
|
flushText() {
|
|
7019
7569
|
this.statusBar.clearActivity();
|
|
7020
7570
|
}
|
|
7571
|
+
/**
|
|
7572
|
+
* Show rate limiting throttle indicator in status bar.
|
|
7573
|
+
* @param delayMs - Delay in milliseconds before next request
|
|
7574
|
+
*/
|
|
7575
|
+
showThrottling(delayMs) {
|
|
7576
|
+
this.statusBar.showThrottling(delayMs);
|
|
7577
|
+
}
|
|
7578
|
+
/**
|
|
7579
|
+
* Clear rate limiting throttle indicator from status bar.
|
|
7580
|
+
*/
|
|
7581
|
+
clearThrottling() {
|
|
7582
|
+
this.statusBar.clearThrottling();
|
|
7583
|
+
}
|
|
7584
|
+
/**
|
|
7585
|
+
* Show retry attempt indicator in status bar.
|
|
7586
|
+
* @param attemptNumber - Current attempt number (1-based)
|
|
7587
|
+
* @param retriesLeft - Number of retries remaining after this attempt
|
|
7588
|
+
*/
|
|
7589
|
+
showRetry(attemptNumber, retriesLeft) {
|
|
7590
|
+
this.statusBar.showRetry(attemptNumber, retriesLeft);
|
|
7591
|
+
}
|
|
7592
|
+
/**
|
|
7593
|
+
* Clear retry attempt indicator from status bar.
|
|
7594
|
+
*/
|
|
7595
|
+
clearRetry() {
|
|
7596
|
+
this.statusBar.clearRetry();
|
|
7597
|
+
}
|
|
7598
|
+
/**
|
|
7599
|
+
* Add a system message to the conversation (for rate limiting, retry notifications, etc.).
|
|
7600
|
+
* @param message - The system message text
|
|
7601
|
+
* @param category - Message category for styling
|
|
7602
|
+
* @returns The block ID
|
|
7603
|
+
*/
|
|
7604
|
+
addSystemMessage(message, category) {
|
|
7605
|
+
return this.blockRenderer.addSystemMessage(message, category);
|
|
7606
|
+
}
|
|
7021
7607
|
// ─────────────────────────────────────────────────────────────────────────────
|
|
7022
7608
|
// Profile Management
|
|
7023
7609
|
// ─────────────────────────────────────────────────────────────────────────────
|
|
@@ -7223,7 +7809,7 @@ async function executeAgent(promptArg, options, env, commandName) {
|
|
|
7223
7809
|
}
|
|
7224
7810
|
}
|
|
7225
7811
|
const abortController = new AbortController();
|
|
7226
|
-
let
|
|
7812
|
+
let _wasCancelled = false;
|
|
7227
7813
|
const handleQuit = () => {
|
|
7228
7814
|
if (tui) {
|
|
7229
7815
|
tui.destroy();
|
|
@@ -7233,7 +7819,7 @@ async function executeAgent(promptArg, options, env, commandName) {
|
|
|
7233
7819
|
if (tui) {
|
|
7234
7820
|
tui.onQuit(handleQuit);
|
|
7235
7821
|
tui.onCancel(() => {
|
|
7236
|
-
|
|
7822
|
+
_wasCancelled = true;
|
|
7237
7823
|
abortController.abort();
|
|
7238
7824
|
});
|
|
7239
7825
|
}
|
|
@@ -7258,12 +7844,12 @@ async function executeAgent(promptArg, options, env, commandName) {
|
|
|
7258
7844
|
gadgetApprovals,
|
|
7259
7845
|
defaultMode: "allowed"
|
|
7260
7846
|
};
|
|
7261
|
-
let
|
|
7847
|
+
let _usage;
|
|
7262
7848
|
let iterations = 0;
|
|
7263
7849
|
const llmLogsEnabled = options.logLlmRequests === true;
|
|
7264
7850
|
const llmLogDir = llmLogsEnabled ? env.session?.logDir : void 0;
|
|
7265
7851
|
let llmCallCounter = 0;
|
|
7266
|
-
const
|
|
7852
|
+
const _countGadgetOutputTokens = async (output) => {
|
|
7267
7853
|
if (!output) return void 0;
|
|
7268
7854
|
try {
|
|
7269
7855
|
const messages = [{ role: "assistant", content: output }];
|
|
@@ -7306,12 +7892,46 @@ async function executeAgent(promptArg, options, env, commandName) {
|
|
|
7306
7892
|
// onLLMCallComplete: Capture metadata for final summary and file logging
|
|
7307
7893
|
onLLMCallComplete: async (context) => {
|
|
7308
7894
|
if (context.subagentContext) return;
|
|
7309
|
-
|
|
7895
|
+
_usage = context.usage;
|
|
7310
7896
|
iterations = Math.max(iterations, context.iteration + 1);
|
|
7311
7897
|
if (llmLogDir) {
|
|
7312
7898
|
const filename = `${formatCallNumber(llmCallCounter)}.response`;
|
|
7313
7899
|
await writeLogFile(llmLogDir, filename, context.rawResponse);
|
|
7314
7900
|
}
|
|
7901
|
+
if (tui) {
|
|
7902
|
+
tui.clearRetry();
|
|
7903
|
+
}
|
|
7904
|
+
},
|
|
7905
|
+
// onRateLimitThrottle: Show throttling delay in status bar and conversation
|
|
7906
|
+
onRateLimitThrottle: async (context) => {
|
|
7907
|
+
if (context.subagentContext) return;
|
|
7908
|
+
if (tui) {
|
|
7909
|
+
const seconds = Math.ceil(context.delayMs / 1e3);
|
|
7910
|
+
tui.showThrottling(context.delayMs);
|
|
7911
|
+
const statsMsg = [];
|
|
7912
|
+
if (context.stats.rpm > 0) statsMsg.push(`${context.stats.rpm} RPM`);
|
|
7913
|
+
if (context.stats.tpm > 0)
|
|
7914
|
+
statsMsg.push(`${Math.round(context.stats.tpm / 1e3)}K TPM`);
|
|
7915
|
+
const statsStr = statsMsg.length > 0 ? ` (${statsMsg.join(", ")})` : "";
|
|
7916
|
+
tui.addSystemMessage(
|
|
7917
|
+
`Rate limit approaching${statsStr}, waiting ${seconds}s...`,
|
|
7918
|
+
"throttle"
|
|
7919
|
+
);
|
|
7920
|
+
setTimeout(() => tui.clearThrottling(), context.delayMs);
|
|
7921
|
+
}
|
|
7922
|
+
},
|
|
7923
|
+
// onRetryAttempt: Show retry attempt in status bar and conversation
|
|
7924
|
+
onRetryAttempt: async (context) => {
|
|
7925
|
+
if (context.subagentContext) return;
|
|
7926
|
+
if (tui) {
|
|
7927
|
+
const totalAttempts = context.attemptNumber + context.retriesLeft;
|
|
7928
|
+
tui.showRetry(context.attemptNumber, context.retriesLeft);
|
|
7929
|
+
const retryAfterInfo = context.retryAfterMs ? ` (server requested ${Math.ceil(context.retryAfterMs / 1e3)}s wait)` : "";
|
|
7930
|
+
tui.addSystemMessage(
|
|
7931
|
+
`Request failed (attempt ${context.attemptNumber}/${totalAttempts}), retrying...${retryAfterInfo}`,
|
|
7932
|
+
"retry"
|
|
7933
|
+
);
|
|
7934
|
+
}
|
|
7315
7935
|
}
|
|
7316
7936
|
},
|
|
7317
7937
|
// SHOWCASE: Controller-based approval gating for gadgets
|
|
@@ -7380,6 +8000,19 @@ ${ctx.gadgetName} requires interactive approval. Run in a terminal to approve.`
|
|
|
7380
8000
|
}
|
|
7381
8001
|
}
|
|
7382
8002
|
});
|
|
8003
|
+
const rateLimitConfig = resolveRateLimitConfig(
|
|
8004
|
+
options,
|
|
8005
|
+
options.globalRateLimits,
|
|
8006
|
+
options.profileRateLimits,
|
|
8007
|
+
options.model
|
|
8008
|
+
);
|
|
8009
|
+
const retryConfig = resolveRetryConfig(options, options.globalRetry, options.profileRetry);
|
|
8010
|
+
if (rateLimitConfig) {
|
|
8011
|
+
builder.withRateLimits(rateLimitConfig);
|
|
8012
|
+
}
|
|
8013
|
+
if (retryConfig) {
|
|
8014
|
+
builder.withRetry(retryConfig);
|
|
8015
|
+
}
|
|
7383
8016
|
if (options.system) {
|
|
7384
8017
|
builder.withSystem(options.system);
|
|
7385
8018
|
}
|
|
@@ -7530,7 +8163,7 @@ ${ctx.gadgetName} requires interactive approval. Run in a terminal to approve.`
|
|
|
7530
8163
|
}
|
|
7531
8164
|
}
|
|
7532
8165
|
}
|
|
7533
|
-
function registerAgentCommand(program, env, config, globalSubagents) {
|
|
8166
|
+
function registerAgentCommand(program, env, config, globalSubagents, globalRateLimits, globalRetry) {
|
|
7534
8167
|
const cmd = program.command(COMMANDS.agent).description("Run the llmist agent loop with optional gadgets.").argument("[prompt]", "Prompt for the agent loop. Falls back to stdin when available.");
|
|
7535
8168
|
addAgentOptions(cmd, config);
|
|
7536
8169
|
cmd.action(
|
|
@@ -7540,7 +8173,11 @@ function registerAgentCommand(program, env, config, globalSubagents) {
|
|
|
7540
8173
|
gadgetApproval: config?.["gadget-approval"],
|
|
7541
8174
|
subagents: config?.subagents,
|
|
7542
8175
|
globalSubagents,
|
|
7543
|
-
initialGadgets: config?.["initial-gadgets"]
|
|
8176
|
+
initialGadgets: config?.["initial-gadgets"],
|
|
8177
|
+
globalRateLimits,
|
|
8178
|
+
globalRetry,
|
|
8179
|
+
profileRateLimits: config?.["rate-limits"],
|
|
8180
|
+
profileRetry: config?.retry
|
|
7544
8181
|
};
|
|
7545
8182
|
return executeAgent(prompt, mergedOptions, env, "agent");
|
|
7546
8183
|
}, env)
|
|
@@ -7548,14 +8185,11 @@ function registerAgentCommand(program, env, config, globalSubagents) {
|
|
|
7548
8185
|
}
|
|
7549
8186
|
|
|
7550
8187
|
// src/complete-command.ts
|
|
7551
|
-
import { text as text2 } from "llmist";
|
|
7552
|
-
import { LLMMessageBuilder } from "llmist";
|
|
7553
|
-
import { resolveModel } from "llmist";
|
|
7554
|
-
import { FALLBACK_CHARS_PER_TOKEN as FALLBACK_CHARS_PER_TOKEN2 } from "llmist";
|
|
8188
|
+
import { FALLBACK_CHARS_PER_TOKEN as FALLBACK_CHARS_PER_TOKEN2, LLMMessageBuilder, resolveModel as resolveModel2, text as text2 } from "llmist";
|
|
7555
8189
|
async function executeComplete(promptArg, options, env) {
|
|
7556
8190
|
const prompt = await resolvePrompt(promptArg, env);
|
|
7557
8191
|
const client = env.createClient();
|
|
7558
|
-
const model =
|
|
8192
|
+
const model = resolveModel2(options.model);
|
|
7559
8193
|
const builder = new LLMMessageBuilder();
|
|
7560
8194
|
if (options.system) {
|
|
7561
8195
|
builder.addSystem(options.system);
|
|
@@ -7629,11 +8263,18 @@ async function executeComplete(promptArg, options, env) {
|
|
|
7629
8263
|
}
|
|
7630
8264
|
}
|
|
7631
8265
|
}
|
|
7632
|
-
function registerCompleteCommand(program, env, config) {
|
|
8266
|
+
function registerCompleteCommand(program, env, config, globalRateLimits, globalRetry) {
|
|
7633
8267
|
const cmd = program.command(COMMANDS.complete).description("Stream a single completion from a specified model.").argument("[prompt]", "Prompt to send to the LLM. If omitted, stdin is used when available.");
|
|
7634
8268
|
addCompleteOptions(cmd, config);
|
|
7635
8269
|
cmd.action(
|
|
7636
|
-
(prompt, options) => executeAction(() =>
|
|
8270
|
+
(prompt, options) => executeAction(() => {
|
|
8271
|
+
const mergedOptions = {
|
|
8272
|
+
...options,
|
|
8273
|
+
globalRateLimits,
|
|
8274
|
+
globalRetry
|
|
8275
|
+
};
|
|
8276
|
+
return executeComplete(prompt, mergedOptions, env);
|
|
8277
|
+
}, env)
|
|
7637
8278
|
);
|
|
7638
8279
|
}
|
|
7639
8280
|
|
|
@@ -7760,104 +8401,6 @@ System Prompt (${chars.toLocaleString()} chars, ${lines} lines):
|
|
|
7760
8401
|
}
|
|
7761
8402
|
}
|
|
7762
8403
|
|
|
7763
|
-
// src/init-command.ts
|
|
7764
|
-
import { existsSync as existsSync3, mkdirSync, writeFileSync } from "fs";
|
|
7765
|
-
import { dirname as dirname2 } from "path";
|
|
7766
|
-
var STARTER_CONFIG = `# ~/.llmist/cli.toml
|
|
7767
|
-
# llmist CLI configuration file
|
|
7768
|
-
#
|
|
7769
|
-
# This is a minimal starter config. For a comprehensive example with all options:
|
|
7770
|
-
# https://github.com/zbigniewsobiecki/llmist/blob/main/examples/cli.example.toml
|
|
7771
|
-
#
|
|
7772
|
-
# Key concepts:
|
|
7773
|
-
# - Any section can inherit from others using: inherits = "section-name"
|
|
7774
|
-
# - Prompts can use templates with Eta syntax: <%~ include("@prompt-name") %>
|
|
7775
|
-
# - Custom sections become CLI commands: [my-command] -> llmist my-command
|
|
7776
|
-
|
|
7777
|
-
#\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500
|
|
7778
|
-
# GLOBAL OPTIONS
|
|
7779
|
-
# These apply to all commands. CLI flags override these settings.
|
|
7780
|
-
#\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500
|
|
7781
|
-
[global]
|
|
7782
|
-
# log-level = "info" # silly, trace, debug, info, warn, error, fatal
|
|
7783
|
-
# log-file = "/tmp/llmist.log" # Enable file logging (JSON format)
|
|
7784
|
-
|
|
7785
|
-
#\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500
|
|
7786
|
-
# COMPLETE COMMAND DEFAULTS
|
|
7787
|
-
# For single LLM responses: llmist complete "prompt"
|
|
7788
|
-
# Model format: provider:model (e.g., openai:gpt-4o, anthropic:claude-sonnet-4-5)
|
|
7789
|
-
#\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500
|
|
7790
|
-
[complete]
|
|
7791
|
-
# model = "openai:gpt-4o"
|
|
7792
|
-
# temperature = 0.7 # 0-2, higher = more creative
|
|
7793
|
-
# max-tokens = 4096 # Maximum response length
|
|
7794
|
-
|
|
7795
|
-
#\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500
|
|
7796
|
-
# AGENT COMMAND DEFAULTS
|
|
7797
|
-
# For tool-using agents: llmist agent "prompt"
|
|
7798
|
-
#\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500
|
|
7799
|
-
[agent]
|
|
7800
|
-
# model = "anthropic:claude-sonnet-4-5"
|
|
7801
|
-
# max-iterations = 15 # Max tool-use loops before stopping
|
|
7802
|
-
# gadgets = [ # Tools the agent can use
|
|
7803
|
-
# "ListDirectory",
|
|
7804
|
-
# "ReadFile",
|
|
7805
|
-
# "WriteFile",
|
|
7806
|
-
# ]
|
|
7807
|
-
|
|
7808
|
-
#\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500
|
|
7809
|
-
# CUSTOM COMMANDS
|
|
7810
|
-
# Any other section becomes a new CLI command!
|
|
7811
|
-
# Uncomment below to create: llmist summarize "your text"
|
|
7812
|
-
#\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500
|
|
7813
|
-
# [summarize]
|
|
7814
|
-
# type = "complete" # "complete" or "agent"
|
|
7815
|
-
# description = "Summarize text concisely."
|
|
7816
|
-
# system = "Summarize the following text in 2-3 bullet points."
|
|
7817
|
-
# temperature = 0.3
|
|
7818
|
-
`;
|
|
7819
|
-
async function executeInit(_options, env) {
|
|
7820
|
-
const configPath = getConfigPath();
|
|
7821
|
-
const configDir = dirname2(configPath);
|
|
7822
|
-
if (existsSync3(configPath)) {
|
|
7823
|
-
env.stderr.write(`Configuration already exists at ${configPath}
|
|
7824
|
-
`);
|
|
7825
|
-
env.stderr.write("\n");
|
|
7826
|
-
env.stderr.write(`To view it: cat ${configPath}
|
|
7827
|
-
`);
|
|
7828
|
-
env.stderr.write(`To reset: rm ${configPath} && llmist init
|
|
7829
|
-
`);
|
|
7830
|
-
return;
|
|
7831
|
-
}
|
|
7832
|
-
if (!existsSync3(configDir)) {
|
|
7833
|
-
mkdirSync(configDir, { recursive: true });
|
|
7834
|
-
}
|
|
7835
|
-
writeFileSync(configPath, STARTER_CONFIG, "utf-8");
|
|
7836
|
-
env.stderr.write(`Created ${configPath}
|
|
7837
|
-
`);
|
|
7838
|
-
env.stderr.write("\n");
|
|
7839
|
-
env.stderr.write("Next steps:\n");
|
|
7840
|
-
env.stderr.write(" 1. Set your API key:\n");
|
|
7841
|
-
env.stderr.write(" export OPENAI_API_KEY=sk-...\n");
|
|
7842
|
-
env.stderr.write(" export ANTHROPIC_API_KEY=sk-...\n");
|
|
7843
|
-
env.stderr.write(" export GEMINI_API_KEY=...\n");
|
|
7844
|
-
env.stderr.write("\n");
|
|
7845
|
-
env.stderr.write(` 2. Customize your config:
|
|
7846
|
-
`);
|
|
7847
|
-
env.stderr.write(` $EDITOR ${configPath}
|
|
7848
|
-
`);
|
|
7849
|
-
env.stderr.write("\n");
|
|
7850
|
-
env.stderr.write(" 3. See all options:\n");
|
|
7851
|
-
env.stderr.write(
|
|
7852
|
-
" https://github.com/zbigniewsobiecki/llmist/blob/main/examples/cli.example.toml\n"
|
|
7853
|
-
);
|
|
7854
|
-
env.stderr.write("\n");
|
|
7855
|
-
env.stderr.write('Try it: llmist complete "Hello, world!"\n');
|
|
7856
|
-
}
|
|
7857
|
-
function registerInitCommand(program, env) {
|
|
7858
|
-
program.command(COMMANDS.init).description("Initialize llmist configuration at ~/.llmist/cli.toml").action((options) => executeAction(() => executeInit(options, env), env));
|
|
7859
|
-
}
|
|
7860
|
-
|
|
7861
8404
|
// src/environment.ts
|
|
7862
8405
|
import { join as join3 } from "path";
|
|
7863
8406
|
import readline from "readline";
|
|
@@ -7956,7 +8499,7 @@ function createCommandEnvironment(baseEnv, config) {
|
|
|
7956
8499
|
createLogger: createLoggerFactory(loggerConfig, baseEnv.session?.logDir)
|
|
7957
8500
|
};
|
|
7958
8501
|
}
|
|
7959
|
-
function registerCustomCommand(program, name, config, env, globalSubagents) {
|
|
8502
|
+
function registerCustomCommand(program, name, config, env, globalSubagents, globalRateLimits, globalRetry) {
|
|
7960
8503
|
const type = config.type ?? "agent";
|
|
7961
8504
|
const description = config.description ?? `Custom ${type} command`;
|
|
7962
8505
|
const cmd = program.command(name).description(description).argument("[prompt]", "Prompt for the command. Falls back to stdin when available.");
|
|
@@ -7968,7 +8511,9 @@ function registerCustomCommand(program, name, config, env, globalSubagents) {
|
|
|
7968
8511
|
const configDefaults = configToCompleteOptions(config);
|
|
7969
8512
|
const options = {
|
|
7970
8513
|
...configDefaults,
|
|
7971
|
-
...cliOptions
|
|
8514
|
+
...cliOptions,
|
|
8515
|
+
globalRateLimits,
|
|
8516
|
+
globalRetry
|
|
7972
8517
|
};
|
|
7973
8518
|
await executeComplete(prompt, options, cmdEnv);
|
|
7974
8519
|
}, cmdEnv);
|
|
@@ -7982,7 +8527,9 @@ function registerCustomCommand(program, name, config, env, globalSubagents) {
|
|
|
7982
8527
|
const options = {
|
|
7983
8528
|
...configDefaults,
|
|
7984
8529
|
...cliOptions,
|
|
7985
|
-
globalSubagents
|
|
8530
|
+
globalSubagents,
|
|
8531
|
+
globalRateLimits,
|
|
8532
|
+
globalRetry
|
|
7986
8533
|
};
|
|
7987
8534
|
await executeAgent(prompt, options, cmdEnv, name);
|
|
7988
8535
|
}, cmdEnv);
|
|
@@ -8425,7 +8972,7 @@ function registerGadgetCommand(program, env) {
|
|
|
8425
8972
|
}
|
|
8426
8973
|
|
|
8427
8974
|
// src/image-command.ts
|
|
8428
|
-
import { writeFileSync
|
|
8975
|
+
import { writeFileSync } from "fs";
|
|
8429
8976
|
var DEFAULT_IMAGE_MODEL = "dall-e-3";
|
|
8430
8977
|
async function executeImage(promptArg, options, env) {
|
|
8431
8978
|
const prompt = await resolvePrompt(promptArg, env);
|
|
@@ -8449,7 +8996,7 @@ async function executeImage(promptArg, options, env) {
|
|
|
8449
8996
|
const imageData = result.images[0];
|
|
8450
8997
|
if (imageData.b64Json) {
|
|
8451
8998
|
const buffer = Buffer.from(imageData.b64Json, "base64");
|
|
8452
|
-
|
|
8999
|
+
writeFileSync(options.output, buffer);
|
|
8453
9000
|
if (!options.quiet) {
|
|
8454
9001
|
env.stderr.write(`${SUMMARY_PREFIX} Image saved to ${options.output}
|
|
8455
9002
|
`);
|
|
@@ -8487,6 +9034,104 @@ function registerImageCommand(program, env, config) {
|
|
|
8487
9034
|
);
|
|
8488
9035
|
}
|
|
8489
9036
|
|
|
9037
|
+
// src/init-command.ts
|
|
9038
|
+
import { existsSync as existsSync3, mkdirSync, writeFileSync as writeFileSync2 } from "fs";
|
|
9039
|
+
import { dirname as dirname2 } from "path";
|
|
9040
|
+
var STARTER_CONFIG = `# ~/.llmist/cli.toml
|
|
9041
|
+
# llmist CLI configuration file
|
|
9042
|
+
#
|
|
9043
|
+
# This is a minimal starter config. For a comprehensive example with all options:
|
|
9044
|
+
# https://github.com/zbigniewsobiecki/llmist/blob/main/examples/cli.example.toml
|
|
9045
|
+
#
|
|
9046
|
+
# Key concepts:
|
|
9047
|
+
# - Any section can inherit from others using: inherits = "section-name"
|
|
9048
|
+
# - Prompts can use templates with Eta syntax: <%~ include("@prompt-name") %>
|
|
9049
|
+
# - Custom sections become CLI commands: [my-command] -> llmist my-command
|
|
9050
|
+
|
|
9051
|
+
#\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500
|
|
9052
|
+
# GLOBAL OPTIONS
|
|
9053
|
+
# These apply to all commands. CLI flags override these settings.
|
|
9054
|
+
#\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500
|
|
9055
|
+
[global]
|
|
9056
|
+
# log-level = "info" # silly, trace, debug, info, warn, error, fatal
|
|
9057
|
+
# log-file = "/tmp/llmist.log" # Enable file logging (JSON format)
|
|
9058
|
+
|
|
9059
|
+
#\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500
|
|
9060
|
+
# COMPLETE COMMAND DEFAULTS
|
|
9061
|
+
# For single LLM responses: llmist complete "prompt"
|
|
9062
|
+
# Model format: provider:model (e.g., openai:gpt-4o, anthropic:claude-sonnet-4-5)
|
|
9063
|
+
#\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500
|
|
9064
|
+
[complete]
|
|
9065
|
+
# model = "openai:gpt-4o"
|
|
9066
|
+
# temperature = 0.7 # 0-2, higher = more creative
|
|
9067
|
+
# max-tokens = 4096 # Maximum response length
|
|
9068
|
+
|
|
9069
|
+
#\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500
|
|
9070
|
+
# AGENT COMMAND DEFAULTS
|
|
9071
|
+
# For tool-using agents: llmist agent "prompt"
|
|
9072
|
+
#\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500
|
|
9073
|
+
[agent]
|
|
9074
|
+
# model = "anthropic:claude-sonnet-4-5"
|
|
9075
|
+
# max-iterations = 15 # Max tool-use loops before stopping
|
|
9076
|
+
# gadgets = [ # Tools the agent can use
|
|
9077
|
+
# "ListDirectory",
|
|
9078
|
+
# "ReadFile",
|
|
9079
|
+
# "WriteFile",
|
|
9080
|
+
# ]
|
|
9081
|
+
|
|
9082
|
+
#\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500
|
|
9083
|
+
# CUSTOM COMMANDS
|
|
9084
|
+
# Any other section becomes a new CLI command!
|
|
9085
|
+
# Uncomment below to create: llmist summarize "your text"
|
|
9086
|
+
#\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500
|
|
9087
|
+
# [summarize]
|
|
9088
|
+
# type = "complete" # "complete" or "agent"
|
|
9089
|
+
# description = "Summarize text concisely."
|
|
9090
|
+
# system = "Summarize the following text in 2-3 bullet points."
|
|
9091
|
+
# temperature = 0.3
|
|
9092
|
+
`;
|
|
9093
|
+
async function executeInit(_options, env) {
|
|
9094
|
+
const configPath = getConfigPath();
|
|
9095
|
+
const configDir = dirname2(configPath);
|
|
9096
|
+
if (existsSync3(configPath)) {
|
|
9097
|
+
env.stderr.write(`Configuration already exists at ${configPath}
|
|
9098
|
+
`);
|
|
9099
|
+
env.stderr.write("\n");
|
|
9100
|
+
env.stderr.write(`To view it: cat ${configPath}
|
|
9101
|
+
`);
|
|
9102
|
+
env.stderr.write(`To reset: rm ${configPath} && llmist init
|
|
9103
|
+
`);
|
|
9104
|
+
return;
|
|
9105
|
+
}
|
|
9106
|
+
if (!existsSync3(configDir)) {
|
|
9107
|
+
mkdirSync(configDir, { recursive: true });
|
|
9108
|
+
}
|
|
9109
|
+
writeFileSync2(configPath, STARTER_CONFIG, "utf-8");
|
|
9110
|
+
env.stderr.write(`Created ${configPath}
|
|
9111
|
+
`);
|
|
9112
|
+
env.stderr.write("\n");
|
|
9113
|
+
env.stderr.write("Next steps:\n");
|
|
9114
|
+
env.stderr.write(" 1. Set your API key:\n");
|
|
9115
|
+
env.stderr.write(" export OPENAI_API_KEY=sk-...\n");
|
|
9116
|
+
env.stderr.write(" export ANTHROPIC_API_KEY=sk-...\n");
|
|
9117
|
+
env.stderr.write(" export GEMINI_API_KEY=...\n");
|
|
9118
|
+
env.stderr.write("\n");
|
|
9119
|
+
env.stderr.write(` 2. Customize your config:
|
|
9120
|
+
`);
|
|
9121
|
+
env.stderr.write(` $EDITOR ${configPath}
|
|
9122
|
+
`);
|
|
9123
|
+
env.stderr.write("\n");
|
|
9124
|
+
env.stderr.write(" 3. See all options:\n");
|
|
9125
|
+
env.stderr.write(
|
|
9126
|
+
" https://github.com/zbigniewsobiecki/llmist/blob/main/examples/cli.example.toml\n"
|
|
9127
|
+
);
|
|
9128
|
+
env.stderr.write("\n");
|
|
9129
|
+
env.stderr.write('Try it: llmist complete "Hello, world!"\n');
|
|
9130
|
+
}
|
|
9131
|
+
function registerInitCommand(program, env) {
|
|
9132
|
+
program.command(COMMANDS.init).description("Initialize llmist configuration at ~/.llmist/cli.toml").action((options) => executeAction(() => executeInit(options, env), env));
|
|
9133
|
+
}
|
|
9134
|
+
|
|
8490
9135
|
// src/models-command.ts
|
|
8491
9136
|
import chalk7 from "chalk";
|
|
8492
9137
|
import { MODEL_ALIASES } from "llmist";
|
|
@@ -9105,10 +9750,10 @@ function registerSpeechCommand(program, env, config) {
|
|
|
9105
9750
|
}
|
|
9106
9751
|
|
|
9107
9752
|
// src/vision-command.ts
|
|
9108
|
-
import { resolveModel as
|
|
9753
|
+
import { resolveModel as resolveModel3 } from "llmist";
|
|
9109
9754
|
async function executeVision(imagePath, options, env) {
|
|
9110
9755
|
const client = env.createClient();
|
|
9111
|
-
const model =
|
|
9756
|
+
const model = resolveModel3(options.model);
|
|
9112
9757
|
const imageBuffer = await readFileBuffer(imagePath);
|
|
9113
9758
|
const prompt = options.prompt ?? "Describe this image in detail.";
|
|
9114
9759
|
const stderrTTY = env.stderr.isTTY === true;
|
|
@@ -9154,8 +9799,15 @@ function createProgram(env, config) {
|
|
|
9154
9799
|
writeOut: (str) => env.stdout.write(str),
|
|
9155
9800
|
writeErr: (str) => env.stderr.write(str)
|
|
9156
9801
|
});
|
|
9157
|
-
registerCompleteCommand(program, env, config?.complete);
|
|
9158
|
-
registerAgentCommand(
|
|
9802
|
+
registerCompleteCommand(program, env, config?.complete, config?.["rate-limits"], config?.retry);
|
|
9803
|
+
registerAgentCommand(
|
|
9804
|
+
program,
|
|
9805
|
+
env,
|
|
9806
|
+
config?.agent,
|
|
9807
|
+
config?.subagents,
|
|
9808
|
+
config?.["rate-limits"],
|
|
9809
|
+
config?.retry
|
|
9810
|
+
);
|
|
9159
9811
|
registerImageCommand(program, env, config?.image);
|
|
9160
9812
|
registerSpeechCommand(program, env, config?.speech);
|
|
9161
9813
|
registerVisionCommand(program, env);
|
|
@@ -9167,7 +9819,15 @@ function createProgram(env, config) {
|
|
|
9167
9819
|
const customNames = getCustomCommandNames(config);
|
|
9168
9820
|
for (const name of customNames) {
|
|
9169
9821
|
const cmdConfig = config[name];
|
|
9170
|
-
registerCustomCommand(
|
|
9822
|
+
registerCustomCommand(
|
|
9823
|
+
program,
|
|
9824
|
+
name,
|
|
9825
|
+
cmdConfig,
|
|
9826
|
+
env,
|
|
9827
|
+
config.subagents,
|
|
9828
|
+
config["rate-limits"],
|
|
9829
|
+
config.retry
|
|
9830
|
+
);
|
|
9171
9831
|
}
|
|
9172
9832
|
}
|
|
9173
9833
|
return program;
|