llmist 0.6.2 → 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/cli.cjs CHANGED
@@ -31,6 +31,20 @@ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__ge
31
31
  ));
32
32
  var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
33
33
 
34
+ // src/core/constants.ts
35
+ var GADGET_START_PREFIX, GADGET_END_PREFIX, DEFAULT_GADGET_OUTPUT_LIMIT, DEFAULT_GADGET_OUTPUT_LIMIT_PERCENT, CHARS_PER_TOKEN, FALLBACK_CONTEXT_WINDOW;
36
+ var init_constants = __esm({
37
+ "src/core/constants.ts"() {
38
+ "use strict";
39
+ GADGET_START_PREFIX = "!!!GADGET_START:";
40
+ GADGET_END_PREFIX = "!!!GADGET_END";
41
+ DEFAULT_GADGET_OUTPUT_LIMIT = true;
42
+ DEFAULT_GADGET_OUTPUT_LIMIT_PERCENT = 15;
43
+ CHARS_PER_TOKEN = 4;
44
+ FALLBACK_CONTEXT_WINDOW = 128e3;
45
+ }
46
+ });
47
+
34
48
  // src/core/model-shortcuts.ts
35
49
  function isKnownModelPattern(model) {
36
50
  const normalized = model.toLowerCase();
@@ -328,20 +342,6 @@ var init_registry = __esm({
328
342
  }
329
343
  });
330
344
 
331
- // src/core/constants.ts
332
- var GADGET_START_PREFIX, GADGET_END_PREFIX, DEFAULT_GADGET_OUTPUT_LIMIT, DEFAULT_GADGET_OUTPUT_LIMIT_PERCENT, CHARS_PER_TOKEN, FALLBACK_CONTEXT_WINDOW;
333
- var init_constants = __esm({
334
- "src/core/constants.ts"() {
335
- "use strict";
336
- GADGET_START_PREFIX = "!!!GADGET_START:";
337
- GADGET_END_PREFIX = "!!!GADGET_END";
338
- DEFAULT_GADGET_OUTPUT_LIMIT = true;
339
- DEFAULT_GADGET_OUTPUT_LIMIT_PERCENT = 15;
340
- CHARS_PER_TOKEN = 4;
341
- FALLBACK_CONTEXT_WINDOW = 128e3;
342
- }
343
- });
344
-
345
345
  // src/core/prompt-config.ts
346
346
  function resolvePromptTemplate(template, defaultValue, context) {
347
347
  const resolved = template ?? defaultValue;
@@ -865,7 +865,7 @@ function findSafeDelimiter(content) {
865
865
  }
866
866
  let counter = 1;
867
867
  while (counter < 1e3) {
868
- const delimiter = `HEREDOC_${counter}`;
868
+ const delimiter = `__GADGET_PARAM_${counter}__`;
869
869
  const regex = new RegExp(`^${delimiter}\\s*$`);
870
870
  const isUsed = lines.some((line) => regex.test(line));
871
871
  if (!isUsed) {
@@ -923,6 +923,10 @@ function formatParamsAsYaml(params) {
923
923
  }
924
924
  return lines.join("\n");
925
925
  }
926
+ function formatTomlInlineTable(obj) {
927
+ const entries = Object.entries(obj).map(([k, v]) => `${k} = ${formatTomlValue(v)}`);
928
+ return `{ ${entries.join(", ")} }`;
929
+ }
926
930
  function formatTomlValue(value) {
927
931
  if (typeof value === "string") {
928
932
  if (value.includes("\n")) {
@@ -940,10 +944,17 @@ ${delimiter}`;
940
944
  return '""';
941
945
  }
942
946
  if (Array.isArray(value)) {
943
- return JSON.stringify(value);
947
+ if (value.length === 0) return "[]";
948
+ const items = value.map((item) => {
949
+ if (typeof item === "object" && item !== null && !Array.isArray(item)) {
950
+ return formatTomlInlineTable(item);
951
+ }
952
+ return formatTomlValue(item);
953
+ });
954
+ return `[${items.join(", ")}]`;
944
955
  }
945
956
  if (typeof value === "object") {
946
- return JSON.stringify(value);
957
+ return formatTomlInlineTable(value);
947
958
  }
948
959
  return JSON.stringify(value);
949
960
  }
@@ -961,7 +972,16 @@ var init_gadget = __esm({
961
972
  yaml = __toESM(require("js-yaml"), 1);
962
973
  init_schema_to_json();
963
974
  init_schema_validator();
964
- HEREDOC_DELIMITERS = ["EOF", "END", "DOC", "CONTENT", "TEXT", "HEREDOC", "DATA", "BLOCK"];
975
+ HEREDOC_DELIMITERS = [
976
+ "__GADGET_PARAM_EOF__",
977
+ "__GADGET_PARAM_END__",
978
+ "__GADGET_PARAM_DOC__",
979
+ "__GADGET_PARAM_CONTENT__",
980
+ "__GADGET_PARAM_TEXT__",
981
+ "__GADGET_PARAM_HEREDOC__",
982
+ "__GADGET_PARAM_DATA__",
983
+ "__GADGET_PARAM_BLOCK__"
984
+ ];
965
985
  BaseGadget = class {
966
986
  /**
967
987
  * The name of the gadget. Used for identification when LLM calls it.
@@ -1959,6 +1979,14 @@ function preprocessTomlHeredoc(tomlStr) {
1959
1979
  }
1960
1980
  return result.join("\n");
1961
1981
  }
1982
+ function stripMarkdownFences(content) {
1983
+ let cleaned = content.trim();
1984
+ const openingFence = /^```(?:toml|yaml|json)?\s*\n/i;
1985
+ const closingFence = /\n?```\s*$/;
1986
+ cleaned = cleaned.replace(openingFence, "");
1987
+ cleaned = cleaned.replace(closingFence, "");
1988
+ return cleaned.trim();
1989
+ }
1962
1990
  var yaml2, import_js_toml, globalInvocationCounter, StreamParser;
1963
1991
  var init_parser = __esm({
1964
1992
  "src/gadgets/parser.ts"() {
@@ -2014,35 +2042,36 @@ var init_parser = __esm({
2014
2042
  * Parse parameter string according to configured format
2015
2043
  */
2016
2044
  parseParameters(raw) {
2045
+ const cleaned = stripMarkdownFences(raw);
2017
2046
  if (this.parameterFormat === "json") {
2018
2047
  try {
2019
- return { parameters: JSON.parse(raw) };
2048
+ return { parameters: JSON.parse(cleaned) };
2020
2049
  } catch (error) {
2021
2050
  return { parseError: this.truncateParseError(error, "JSON") };
2022
2051
  }
2023
2052
  }
2024
2053
  if (this.parameterFormat === "yaml") {
2025
2054
  try {
2026
- return { parameters: yaml2.load(preprocessYaml(raw)) };
2055
+ return { parameters: yaml2.load(preprocessYaml(cleaned)) };
2027
2056
  } catch (error) {
2028
2057
  return { parseError: this.truncateParseError(error, "YAML") };
2029
2058
  }
2030
2059
  }
2031
2060
  if (this.parameterFormat === "toml") {
2032
2061
  try {
2033
- return { parameters: (0, import_js_toml.load)(preprocessTomlHeredoc(raw)) };
2062
+ return { parameters: (0, import_js_toml.load)(preprocessTomlHeredoc(cleaned)) };
2034
2063
  } catch (error) {
2035
2064
  return { parseError: this.truncateParseError(error, "TOML") };
2036
2065
  }
2037
2066
  }
2038
2067
  try {
2039
- return { parameters: JSON.parse(raw) };
2068
+ return { parameters: JSON.parse(cleaned) };
2040
2069
  } catch {
2041
2070
  try {
2042
- return { parameters: (0, import_js_toml.load)(preprocessTomlHeredoc(raw)) };
2071
+ return { parameters: (0, import_js_toml.load)(preprocessTomlHeredoc(cleaned)) };
2043
2072
  } catch {
2044
2073
  try {
2045
- return { parameters: yaml2.load(preprocessYaml(raw)) };
2074
+ return { parameters: yaml2.load(preprocessYaml(cleaned)) };
2046
2075
  } catch (error) {
2047
2076
  return { parseError: this.truncateParseError(error, "auto") };
2048
2077
  }
@@ -2588,6 +2617,7 @@ var init_agent = __esm({
2588
2617
  gadgetEndPrefix;
2589
2618
  onHumanInputRequired;
2590
2619
  textOnlyHandler;
2620
+ textWithGadgetsHandler;
2591
2621
  stopOnGadgetError;
2592
2622
  shouldContinueAfterError;
2593
2623
  defaultGadgetTimeoutMs;
@@ -2618,6 +2648,7 @@ var init_agent = __esm({
2618
2648
  this.gadgetEndPrefix = options.gadgetEndPrefix;
2619
2649
  this.onHumanInputRequired = options.onHumanInputRequired;
2620
2650
  this.textOnlyHandler = options.textOnlyHandler ?? "terminate";
2651
+ this.textWithGadgetsHandler = options.textWithGadgetsHandler;
2621
2652
  this.stopOnGadgetError = options.stopOnGadgetError ?? true;
2622
2653
  this.shouldContinueAfterError = options.shouldContinueAfterError;
2623
2654
  this.defaultGadgetTimeoutMs = options.defaultGadgetTimeoutMs;
@@ -2805,6 +2836,17 @@ var init_agent = __esm({
2805
2836
  }
2806
2837
  }
2807
2838
  if (result.didExecuteGadgets) {
2839
+ if (this.textWithGadgetsHandler) {
2840
+ const textContent = result.outputs.filter((output) => output.type === "text").map((output) => output.content).join("");
2841
+ if (textContent.trim()) {
2842
+ const { gadgetName, parameterMapping, resultMapping } = this.textWithGadgetsHandler;
2843
+ this.conversation.addGadgetCall(
2844
+ gadgetName,
2845
+ parameterMapping(textContent),
2846
+ resultMapping ? resultMapping(textContent) : textContent
2847
+ );
2848
+ }
2849
+ }
2808
2850
  for (const output of result.outputs) {
2809
2851
  if (output.type === "gadget_result") {
2810
2852
  const gadgetResult = output.result;
@@ -2816,7 +2858,13 @@ var init_agent = __esm({
2816
2858
  }
2817
2859
  }
2818
2860
  } else {
2819
- this.conversation.addAssistantMessage(finalMessage);
2861
+ if (finalMessage.trim()) {
2862
+ this.conversation.addGadgetCall(
2863
+ "TellUser",
2864
+ { message: finalMessage, done: false, type: "info" },
2865
+ `\u2139\uFE0F ${finalMessage}`
2866
+ );
2867
+ }
2820
2868
  const shouldBreak = await this.handleTextOnlyResponse(finalMessage);
2821
2869
  if (shouldBreak) {
2822
2870
  break;
@@ -3011,7 +3059,8 @@ var init_anthropic_models = __esm({
3011
3059
  pricing: {
3012
3060
  input: 3,
3013
3061
  output: 15,
3014
- cachedInput: 0.3
3062
+ cachedInput: 0.3,
3063
+ cacheWriteInput: 3.75
3015
3064
  },
3016
3065
  knowledgeCutoff: "2025-01",
3017
3066
  features: {
@@ -3035,7 +3084,8 @@ var init_anthropic_models = __esm({
3035
3084
  pricing: {
3036
3085
  input: 1,
3037
3086
  output: 5,
3038
- cachedInput: 0.1
3087
+ cachedInput: 0.1,
3088
+ cacheWriteInput: 1.25
3039
3089
  },
3040
3090
  knowledgeCutoff: "2025-02",
3041
3091
  features: {
@@ -3059,7 +3109,8 @@ var init_anthropic_models = __esm({
3059
3109
  pricing: {
3060
3110
  input: 3,
3061
3111
  output: 15,
3062
- cachedInput: 0.3
3112
+ cachedInput: 0.3,
3113
+ cacheWriteInput: 3.75
3063
3114
  },
3064
3115
  knowledgeCutoff: "2025-03",
3065
3116
  features: {
@@ -3083,7 +3134,8 @@ var init_anthropic_models = __esm({
3083
3134
  pricing: {
3084
3135
  input: 3,
3085
3136
  output: 15,
3086
- cachedInput: 0.3
3137
+ cachedInput: 0.3,
3138
+ cacheWriteInput: 3.75
3087
3139
  },
3088
3140
  knowledgeCutoff: "2024-11",
3089
3141
  features: {
@@ -3107,7 +3159,8 @@ var init_anthropic_models = __esm({
3107
3159
  pricing: {
3108
3160
  input: 15,
3109
3161
  output: 75,
3110
- cachedInput: 1.5
3162
+ cachedInput: 1.5,
3163
+ cacheWriteInput: 18.75
3111
3164
  },
3112
3165
  knowledgeCutoff: "2025-01",
3113
3166
  features: {
@@ -3131,7 +3184,8 @@ var init_anthropic_models = __esm({
3131
3184
  pricing: {
3132
3185
  input: 15,
3133
3186
  output: 75,
3134
- cachedInput: 1.5
3187
+ cachedInput: 1.5,
3188
+ cacheWriteInput: 18.75
3135
3189
  },
3136
3190
  knowledgeCutoff: "2025-03",
3137
3191
  features: {
@@ -3154,7 +3208,8 @@ var init_anthropic_models = __esm({
3154
3208
  pricing: {
3155
3209
  input: 0.8,
3156
3210
  output: 4,
3157
- cachedInput: 0.08
3211
+ cachedInput: 0.08,
3212
+ cacheWriteInput: 1
3158
3213
  },
3159
3214
  knowledgeCutoff: "2024-07",
3160
3215
  features: {
@@ -3177,7 +3232,8 @@ var init_anthropic_models = __esm({
3177
3232
  pricing: {
3178
3233
  input: 0.25,
3179
3234
  output: 1.25,
3180
- cachedInput: 0.025
3235
+ cachedInput: 0.025,
3236
+ cacheWriteInput: 0.3125
3181
3237
  },
3182
3238
  knowledgeCutoff: "2023-08",
3183
3239
  features: {
@@ -3201,7 +3257,8 @@ var init_anthropic_models = __esm({
3201
3257
  pricing: {
3202
3258
  input: 1,
3203
3259
  output: 5,
3204
- cachedInput: 0.1
3260
+ cachedInput: 0.1,
3261
+ cacheWriteInput: 1.25
3205
3262
  },
3206
3263
  knowledgeCutoff: "2025-02",
3207
3264
  features: {
@@ -3225,7 +3282,8 @@ var init_anthropic_models = __esm({
3225
3282
  pricing: {
3226
3283
  input: 3,
3227
3284
  output: 15,
3228
- cachedInput: 0.3
3285
+ cachedInput: 0.3,
3286
+ cacheWriteInput: 3.75
3229
3287
  },
3230
3288
  knowledgeCutoff: "2025-01",
3231
3289
  features: {
@@ -3249,7 +3307,8 @@ var init_anthropic_models = __esm({
3249
3307
  pricing: {
3250
3308
  input: 5,
3251
3309
  output: 25,
3252
- cachedInput: 0.5
3310
+ cachedInput: 0.5,
3311
+ cacheWriteInput: 6.25
3253
3312
  },
3254
3313
  knowledgeCutoff: "2025-03",
3255
3314
  features: {
@@ -3364,15 +3423,27 @@ var init_anthropic = __esm({
3364
3423
  }
3365
3424
  buildRequestPayload(options, descriptor, spec, messages) {
3366
3425
  const systemMessages = messages.filter((message) => message.role === "system");
3367
- const system = systemMessages.length > 0 ? systemMessages.map((m) => m.content).join("\n\n") : void 0;
3368
- const conversation = messages.filter(
3426
+ const system = systemMessages.length > 0 ? systemMessages.map((m, index) => ({
3427
+ type: "text",
3428
+ text: m.content,
3429
+ // Add cache_control to the LAST system message block
3430
+ ...index === systemMessages.length - 1 ? { cache_control: { type: "ephemeral" } } : {}
3431
+ })) : void 0;
3432
+ const nonSystemMessages = messages.filter(
3369
3433
  (message) => message.role !== "system"
3370
- ).map((message) => ({
3434
+ );
3435
+ const lastUserIndex = nonSystemMessages.reduce(
3436
+ (lastIdx, msg, idx) => msg.role === "user" ? idx : lastIdx,
3437
+ -1
3438
+ );
3439
+ const conversation = nonSystemMessages.map((message, index) => ({
3371
3440
  role: message.role,
3372
3441
  content: [
3373
3442
  {
3374
3443
  type: "text",
3375
- text: message.content
3444
+ text: message.content,
3445
+ // Add cache_control to the LAST user message
3446
+ ...message.role === "user" && index === lastUserIndex ? { cache_control: { type: "ephemeral" } } : {}
3376
3447
  }
3377
3448
  ]
3378
3449
  }));
@@ -3398,15 +3469,22 @@ var init_anthropic = __esm({
3398
3469
  async *wrapStream(iterable) {
3399
3470
  const stream2 = iterable;
3400
3471
  let inputTokens = 0;
3472
+ let cachedInputTokens = 0;
3473
+ let cacheCreationInputTokens = 0;
3401
3474
  for await (const event of stream2) {
3402
3475
  if (event.type === "message_start") {
3403
- inputTokens = event.message.usage.input_tokens;
3476
+ const usage = event.message.usage;
3477
+ cachedInputTokens = usage.cache_read_input_tokens ?? 0;
3478
+ cacheCreationInputTokens = usage.cache_creation_input_tokens ?? 0;
3479
+ inputTokens = usage.input_tokens + cachedInputTokens + cacheCreationInputTokens;
3404
3480
  yield {
3405
3481
  text: "",
3406
3482
  usage: {
3407
3483
  inputTokens,
3408
3484
  outputTokens: 0,
3409
- totalTokens: inputTokens
3485
+ totalTokens: inputTokens,
3486
+ cachedInputTokens,
3487
+ cacheCreationInputTokens
3410
3488
  },
3411
3489
  rawEvent: event
3412
3490
  };
@@ -3420,7 +3498,9 @@ var init_anthropic = __esm({
3420
3498
  const usage = event.usage ? {
3421
3499
  inputTokens,
3422
3500
  outputTokens: event.usage.output_tokens,
3423
- totalTokens: inputTokens + event.usage.output_tokens
3501
+ totalTokens: inputTokens + event.usage.output_tokens,
3502
+ cachedInputTokens,
3503
+ cacheCreationInputTokens
3424
3504
  } : void 0;
3425
3505
  if (event.delta.stop_reason || usage) {
3426
3506
  yield {
@@ -3501,6 +3581,7 @@ var init_gemini_models = __esm({
3501
3581
  "src/providers/gemini-models.ts"() {
3502
3582
  "use strict";
3503
3583
  GEMINI_MODELS = [
3584
+ // Gemini 3 Pro (Preview)
3504
3585
  {
3505
3586
  provider: "gemini",
3506
3587
  modelId: "gemini-3-pro-preview",
@@ -3509,8 +3590,11 @@ var init_gemini_models = __esm({
3509
3590
  maxOutputTokens: 65536,
3510
3591
  pricing: {
3511
3592
  input: 2,
3593
+ // $2.00 for prompts <= 200k, $4.00 for > 200k (using lower tier)
3512
3594
  output: 12,
3595
+ // $12.00 for prompts <= 200k, $18.00 for > 200k
3513
3596
  cachedInput: 0.2
3597
+ // $0.20 for prompts <= 200k
3514
3598
  },
3515
3599
  knowledgeCutoff: "2025-01",
3516
3600
  features: {
@@ -3523,9 +3607,10 @@ var init_gemini_models = __esm({
3523
3607
  metadata: {
3524
3608
  family: "Gemini 3",
3525
3609
  releaseDate: "2025-11-18",
3526
- notes: "Most advanced model. 1501 Elo LMArena, 91.9% GPQA Diamond, 76.2% SWE-bench. Deep Think mode available."
3610
+ notes: "Best model for multimodal understanding, agentic and vibe-coding. Deep Think mode available."
3527
3611
  }
3528
3612
  },
3613
+ // Gemini 2.5 Pro
3529
3614
  {
3530
3615
  provider: "gemini",
3531
3616
  modelId: "gemini-2.5-pro",
@@ -3534,8 +3619,11 @@ var init_gemini_models = __esm({
3534
3619
  maxOutputTokens: 65536,
3535
3620
  pricing: {
3536
3621
  input: 1.25,
3622
+ // $1.25 for prompts <= 200k, $2.50 for > 200k
3537
3623
  output: 10,
3624
+ // $10.00 for prompts <= 200k, $15.00 for > 200k
3538
3625
  cachedInput: 0.125
3626
+ // $0.125 for prompts <= 200k
3539
3627
  },
3540
3628
  knowledgeCutoff: "2025-01",
3541
3629
  features: {
@@ -3548,9 +3636,10 @@ var init_gemini_models = __esm({
3548
3636
  metadata: {
3549
3637
  family: "Gemini 2.5",
3550
3638
  releaseDate: "2025-06",
3551
- notes: "Balanced multimodal model with 1M context. Best for complex agents and reasoning."
3639
+ notes: "State-of-the-art multipurpose model. Excels at coding and complex reasoning."
3552
3640
  }
3553
3641
  },
3642
+ // Gemini 2.5 Flash
3554
3643
  {
3555
3644
  provider: "gemini",
3556
3645
  modelId: "gemini-2.5-flash",
@@ -3559,8 +3648,10 @@ var init_gemini_models = __esm({
3559
3648
  maxOutputTokens: 65536,
3560
3649
  pricing: {
3561
3650
  input: 0.3,
3651
+ // $0.30 for text/image/video, $1.00 for audio
3562
3652
  output: 2.5,
3563
3653
  cachedInput: 0.03
3654
+ // $0.03 for text/image/video
3564
3655
  },
3565
3656
  knowledgeCutoff: "2025-01",
3566
3657
  features: {
@@ -3573,9 +3664,10 @@ var init_gemini_models = __esm({
3573
3664
  metadata: {
3574
3665
  family: "Gemini 2.5",
3575
3666
  releaseDate: "2025-06",
3576
- notes: "Best price-performance ratio with thinking enabled by default"
3667
+ notes: "First hybrid reasoning model with 1M context and thinking budgets."
3577
3668
  }
3578
3669
  },
3670
+ // Gemini 2.5 Flash-Lite
3579
3671
  {
3580
3672
  provider: "gemini",
3581
3673
  modelId: "gemini-2.5-flash-lite",
@@ -3584,8 +3676,10 @@ var init_gemini_models = __esm({
3584
3676
  maxOutputTokens: 65536,
3585
3677
  pricing: {
3586
3678
  input: 0.1,
3679
+ // $0.10 for text/image/video, $0.30 for audio
3587
3680
  output: 0.4,
3588
3681
  cachedInput: 0.01
3682
+ // $0.01 for text/image/video
3589
3683
  },
3590
3684
  knowledgeCutoff: "2025-01",
3591
3685
  features: {
@@ -3597,9 +3691,10 @@ var init_gemini_models = __esm({
3597
3691
  metadata: {
3598
3692
  family: "Gemini 2.5",
3599
3693
  releaseDate: "2025-06",
3600
- notes: "Fastest and most cost-efficient model for high-volume, low-latency tasks"
3694
+ notes: "Smallest and most cost effective model, built for at scale usage."
3601
3695
  }
3602
3696
  },
3697
+ // Gemini 2.0 Flash
3603
3698
  {
3604
3699
  provider: "gemini",
3605
3700
  modelId: "gemini-2.0-flash",
@@ -3608,8 +3703,10 @@ var init_gemini_models = __esm({
3608
3703
  maxOutputTokens: 8192,
3609
3704
  pricing: {
3610
3705
  input: 0.1,
3706
+ // $0.10 for text/image/video, $0.70 for audio
3611
3707
  output: 0.4,
3612
- cachedInput: 0.01
3708
+ cachedInput: 0.025
3709
+ // $0.025 for text/image/video
3613
3710
  },
3614
3711
  knowledgeCutoff: "2024-08",
3615
3712
  features: {
@@ -3620,9 +3717,10 @@ var init_gemini_models = __esm({
3620
3717
  },
3621
3718
  metadata: {
3622
3719
  family: "Gemini 2.0",
3623
- notes: "Previous generation with 1M context and multimodal capabilities"
3720
+ notes: "Balanced multimodal model with 1M context, built for the era of Agents."
3624
3721
  }
3625
3722
  },
3723
+ // Gemini 2.0 Flash-Lite
3626
3724
  {
3627
3725
  provider: "gemini",
3628
3726
  modelId: "gemini-2.0-flash-lite",
@@ -3631,8 +3729,8 @@ var init_gemini_models = __esm({
3631
3729
  maxOutputTokens: 8192,
3632
3730
  pricing: {
3633
3731
  input: 0.075,
3634
- output: 0.3,
3635
- cachedInput: 75e-4
3732
+ output: 0.3
3733
+ // No context caching available for 2.0-flash-lite
3636
3734
  },
3637
3735
  knowledgeCutoff: "2024-08",
3638
3736
  features: {
@@ -3643,7 +3741,7 @@ var init_gemini_models = __esm({
3643
3741
  },
3644
3742
  metadata: {
3645
3743
  family: "Gemini 2.0",
3646
- notes: "Lightweight previous generation model for cost-sensitive applications"
3744
+ notes: "Smallest and most cost effective 2.0 model for at scale usage."
3647
3745
  }
3648
3746
  }
3649
3747
  ];
@@ -3813,7 +3911,9 @@ var init_gemini = __esm({
3813
3911
  return {
3814
3912
  inputTokens: usageMetadata.promptTokenCount ?? 0,
3815
3913
  outputTokens: usageMetadata.candidatesTokenCount ?? 0,
3816
- totalTokens: usageMetadata.totalTokenCount ?? 0
3914
+ totalTokens: usageMetadata.totalTokenCount ?? 0,
3915
+ // Gemini returns cached token count in cachedContentTokenCount
3916
+ cachedInputTokens: usageMetadata.cachedContentTokenCount ?? 0
3817
3917
  };
3818
3918
  }
3819
3919
  /**
@@ -3869,10 +3969,11 @@ var init_openai_models = __esm({
3869
3969
  "src/providers/openai-models.ts"() {
3870
3970
  "use strict";
3871
3971
  OPENAI_MODELS = [
3972
+ // GPT-5 Family
3872
3973
  {
3873
3974
  provider: "openai",
3874
3975
  modelId: "gpt-5.1",
3875
- displayName: "GPT-5.1 Instant",
3976
+ displayName: "GPT-5.1",
3876
3977
  contextWindow: 128e3,
3877
3978
  maxOutputTokens: 32768,
3878
3979
  pricing: {
@@ -3892,34 +3993,7 @@ var init_openai_models = __esm({
3892
3993
  metadata: {
3893
3994
  family: "GPT-5",
3894
3995
  releaseDate: "2025-11-12",
3895
- notes: "Warmer, more intelligent, better instruction following. 2-3x faster than GPT-5.",
3896
- supportsTemperature: false
3897
- }
3898
- },
3899
- {
3900
- provider: "openai",
3901
- modelId: "gpt-5.1-thinking",
3902
- displayName: "GPT-5.1 Thinking",
3903
- contextWindow: 196e3,
3904
- maxOutputTokens: 32768,
3905
- pricing: {
3906
- input: 1.25,
3907
- output: 10,
3908
- cachedInput: 0.125
3909
- },
3910
- knowledgeCutoff: "2024-09-30",
3911
- features: {
3912
- streaming: true,
3913
- functionCalling: true,
3914
- vision: true,
3915
- reasoning: true,
3916
- structuredOutputs: true,
3917
- fineTuning: true
3918
- },
3919
- metadata: {
3920
- family: "GPT-5",
3921
- releaseDate: "2025-11-12",
3922
- notes: "Advanced reasoning with thinking levels: Light, Standard, Extended, Heavy. Best for complex tasks.",
3996
+ notes: "Latest GPT-5 with improved instruction following. 2-3x faster than GPT-5.",
3923
3997
  supportsTemperature: false
3924
3998
  }
3925
3999
  },
@@ -3999,6 +4073,255 @@ var init_openai_models = __esm({
3999
4073
  notes: "Fastest, most cost-efficient version for well-defined tasks",
4000
4074
  supportsTemperature: false
4001
4075
  }
4076
+ },
4077
+ {
4078
+ provider: "openai",
4079
+ modelId: "gpt-5-pro",
4080
+ displayName: "GPT-5 Pro",
4081
+ contextWindow: 272e3,
4082
+ maxOutputTokens: 128e3,
4083
+ pricing: {
4084
+ input: 15,
4085
+ output: 120
4086
+ // No cached input pricing for gpt-5-pro
4087
+ },
4088
+ knowledgeCutoff: "2024-09-30",
4089
+ features: {
4090
+ streaming: true,
4091
+ functionCalling: true,
4092
+ vision: true,
4093
+ reasoning: true,
4094
+ structuredOutputs: true
4095
+ },
4096
+ metadata: {
4097
+ family: "GPT-5",
4098
+ notes: "Premium tier with enhanced capabilities. Does not support prompt caching.",
4099
+ supportsTemperature: false
4100
+ }
4101
+ },
4102
+ // GPT-4.1 Family
4103
+ {
4104
+ provider: "openai",
4105
+ modelId: "gpt-4.1",
4106
+ displayName: "GPT-4.1",
4107
+ contextWindow: 128e3,
4108
+ maxOutputTokens: 32768,
4109
+ pricing: {
4110
+ input: 2,
4111
+ output: 8,
4112
+ cachedInput: 0.5
4113
+ },
4114
+ knowledgeCutoff: "2024-04-01",
4115
+ features: {
4116
+ streaming: true,
4117
+ functionCalling: true,
4118
+ vision: true,
4119
+ structuredOutputs: true,
4120
+ fineTuning: true
4121
+ },
4122
+ metadata: {
4123
+ family: "GPT-4.1",
4124
+ notes: "Improved GPT-4 with better instruction following"
4125
+ }
4126
+ },
4127
+ {
4128
+ provider: "openai",
4129
+ modelId: "gpt-4.1-mini",
4130
+ displayName: "GPT-4.1 Mini",
4131
+ contextWindow: 128e3,
4132
+ maxOutputTokens: 32768,
4133
+ pricing: {
4134
+ input: 0.4,
4135
+ output: 1.6,
4136
+ cachedInput: 0.1
4137
+ },
4138
+ knowledgeCutoff: "2024-04-01",
4139
+ features: {
4140
+ streaming: true,
4141
+ functionCalling: true,
4142
+ vision: true,
4143
+ structuredOutputs: true,
4144
+ fineTuning: true
4145
+ },
4146
+ metadata: {
4147
+ family: "GPT-4.1",
4148
+ notes: "Cost-efficient GPT-4.1 variant"
4149
+ }
4150
+ },
4151
+ {
4152
+ provider: "openai",
4153
+ modelId: "gpt-4.1-nano",
4154
+ displayName: "GPT-4.1 Nano",
4155
+ contextWindow: 128e3,
4156
+ maxOutputTokens: 32768,
4157
+ pricing: {
4158
+ input: 0.1,
4159
+ output: 0.4,
4160
+ cachedInput: 0.025
4161
+ },
4162
+ knowledgeCutoff: "2024-04-01",
4163
+ features: {
4164
+ streaming: true,
4165
+ functionCalling: true,
4166
+ vision: true,
4167
+ structuredOutputs: true,
4168
+ fineTuning: true
4169
+ },
4170
+ metadata: {
4171
+ family: "GPT-4.1",
4172
+ notes: "Fastest GPT-4.1 variant for simple tasks"
4173
+ }
4174
+ },
4175
+ // GPT-4o Family
4176
+ {
4177
+ provider: "openai",
4178
+ modelId: "gpt-4o",
4179
+ displayName: "GPT-4o",
4180
+ contextWindow: 128e3,
4181
+ maxOutputTokens: 16384,
4182
+ pricing: {
4183
+ input: 2.5,
4184
+ output: 10,
4185
+ cachedInput: 1.25
4186
+ },
4187
+ knowledgeCutoff: "2024-04-01",
4188
+ features: {
4189
+ streaming: true,
4190
+ functionCalling: true,
4191
+ vision: true,
4192
+ structuredOutputs: true,
4193
+ fineTuning: true
4194
+ },
4195
+ metadata: {
4196
+ family: "GPT-4o",
4197
+ notes: "Multimodal model optimized for speed"
4198
+ }
4199
+ },
4200
+ {
4201
+ provider: "openai",
4202
+ modelId: "gpt-4o-mini",
4203
+ displayName: "GPT-4o Mini",
4204
+ contextWindow: 128e3,
4205
+ maxOutputTokens: 16384,
4206
+ pricing: {
4207
+ input: 0.15,
4208
+ output: 0.6,
4209
+ cachedInput: 0.075
4210
+ },
4211
+ knowledgeCutoff: "2024-04-01",
4212
+ features: {
4213
+ streaming: true,
4214
+ functionCalling: true,
4215
+ vision: true,
4216
+ structuredOutputs: true,
4217
+ fineTuning: true
4218
+ },
4219
+ metadata: {
4220
+ family: "GPT-4o",
4221
+ notes: "Fast and affordable multimodal model"
4222
+ }
4223
+ },
4224
+ // o-series (Reasoning models)
4225
+ {
4226
+ provider: "openai",
4227
+ modelId: "o1",
4228
+ displayName: "o1",
4229
+ contextWindow: 2e5,
4230
+ maxOutputTokens: 1e5,
4231
+ pricing: {
4232
+ input: 15,
4233
+ output: 60,
4234
+ cachedInput: 7.5
4235
+ },
4236
+ knowledgeCutoff: "2024-12-01",
4237
+ features: {
4238
+ streaming: true,
4239
+ functionCalling: true,
4240
+ vision: true,
4241
+ reasoning: true,
4242
+ structuredOutputs: true
4243
+ },
4244
+ metadata: {
4245
+ family: "o-series",
4246
+ notes: "Advanced reasoning model with chain-of-thought",
4247
+ supportsTemperature: false
4248
+ }
4249
+ },
4250
+ {
4251
+ provider: "openai",
4252
+ modelId: "o3",
4253
+ displayName: "o3",
4254
+ contextWindow: 2e5,
4255
+ maxOutputTokens: 1e5,
4256
+ pricing: {
4257
+ input: 2,
4258
+ output: 8,
4259
+ cachedInput: 0.5
4260
+ },
4261
+ knowledgeCutoff: "2025-01-01",
4262
+ features: {
4263
+ streaming: true,
4264
+ functionCalling: true,
4265
+ vision: true,
4266
+ reasoning: true,
4267
+ structuredOutputs: true
4268
+ },
4269
+ metadata: {
4270
+ family: "o-series",
4271
+ notes: "Next-gen reasoning model, more efficient than o1",
4272
+ supportsTemperature: false
4273
+ }
4274
+ },
4275
+ {
4276
+ provider: "openai",
4277
+ modelId: "o4-mini",
4278
+ displayName: "o4 Mini",
4279
+ contextWindow: 2e5,
4280
+ maxOutputTokens: 1e5,
4281
+ pricing: {
4282
+ input: 1.1,
4283
+ output: 4.4,
4284
+ cachedInput: 0.275
4285
+ },
4286
+ knowledgeCutoff: "2025-04-01",
4287
+ features: {
4288
+ streaming: true,
4289
+ functionCalling: true,
4290
+ vision: true,
4291
+ reasoning: true,
4292
+ structuredOutputs: true,
4293
+ fineTuning: true
4294
+ },
4295
+ metadata: {
4296
+ family: "o-series",
4297
+ notes: "Cost-efficient reasoning model",
4298
+ supportsTemperature: false
4299
+ }
4300
+ },
4301
+ {
4302
+ provider: "openai",
4303
+ modelId: "o3-mini",
4304
+ displayName: "o3 Mini",
4305
+ contextWindow: 2e5,
4306
+ maxOutputTokens: 1e5,
4307
+ pricing: {
4308
+ input: 1.1,
4309
+ output: 4.4,
4310
+ cachedInput: 0.55
4311
+ },
4312
+ knowledgeCutoff: "2025-01-01",
4313
+ features: {
4314
+ streaming: true,
4315
+ functionCalling: true,
4316
+ vision: true,
4317
+ reasoning: true,
4318
+ structuredOutputs: true
4319
+ },
4320
+ metadata: {
4321
+ family: "o-series",
4322
+ notes: "Compact reasoning model for cost-sensitive applications",
4323
+ supportsTemperature: false
4324
+ }
4002
4325
  }
4003
4326
  ];
4004
4327
  }
@@ -4079,7 +4402,8 @@ var init_openai = __esm({
4079
4402
  const usage = chunk.usage ? {
4080
4403
  inputTokens: chunk.usage.prompt_tokens,
4081
4404
  outputTokens: chunk.usage.completion_tokens,
4082
- totalTokens: chunk.usage.total_tokens
4405
+ totalTokens: chunk.usage.total_tokens,
4406
+ cachedInputTokens: chunk.usage.prompt_tokens_details?.cached_tokens ?? 0
4083
4407
  } : void 0;
4084
4408
  if (finishReason || usage) {
4085
4409
  yield { text: "", finishReason, usage, rawEvent: chunk };
@@ -4296,20 +4620,28 @@ var init_model_registry = __esm({
4296
4620
  /**
4297
4621
  * Estimate API cost for a given model and token usage
4298
4622
  * @param modelId - Full model identifier
4299
- * @param inputTokens - Number of input tokens
4623
+ * @param inputTokens - Number of input tokens (total, including cached and cache creation)
4300
4624
  * @param outputTokens - Number of output tokens
4301
- * @param useCachedInput - Whether to use cached input pricing (if supported by provider)
4625
+ * @param cachedInputTokens - Number of cached input tokens (subset of inputTokens)
4626
+ * @param cacheCreationInputTokens - Number of cache creation tokens (subset of inputTokens, Anthropic only)
4302
4627
  * @returns CostEstimate if model found, undefined otherwise
4303
4628
  */
4304
- estimateCost(modelId, inputTokens, outputTokens, useCachedInput = false) {
4629
+ estimateCost(modelId, inputTokens, outputTokens, cachedInputTokens = 0, cacheCreationInputTokens = 0) {
4305
4630
  const spec = this.getModelSpec(modelId);
4306
4631
  if (!spec) return void 0;
4307
- const inputRate = useCachedInput && spec.pricing.cachedInput !== void 0 ? spec.pricing.cachedInput : spec.pricing.input;
4308
- const inputCost = inputTokens / 1e6 * inputRate;
4632
+ const cachedRate = spec.pricing.cachedInput ?? spec.pricing.input;
4633
+ const cacheWriteRate = spec.pricing.cacheWriteInput ?? spec.pricing.input;
4634
+ const uncachedInputTokens = inputTokens - cachedInputTokens - cacheCreationInputTokens;
4635
+ const uncachedInputCost = uncachedInputTokens / 1e6 * spec.pricing.input;
4636
+ const cachedInputCost = cachedInputTokens / 1e6 * cachedRate;
4637
+ const cacheCreationCost = cacheCreationInputTokens / 1e6 * cacheWriteRate;
4638
+ const inputCost = uncachedInputCost + cachedInputCost + cacheCreationCost;
4309
4639
  const outputCost = outputTokens / 1e6 * spec.pricing.output;
4310
4640
  const totalCost = inputCost + outputCost;
4311
4641
  return {
4312
4642
  inputCost,
4643
+ cachedInputCost,
4644
+ cacheCreationCost,
4313
4645
  outputCost,
4314
4646
  totalCost,
4315
4647
  currency: "USD"
@@ -4690,6 +5022,7 @@ var AgentBuilder;
4690
5022
  var init_builder = __esm({
4691
5023
  "src/agent/builder.ts"() {
4692
5024
  "use strict";
5025
+ init_constants();
4693
5026
  init_model_shortcuts();
4694
5027
  init_registry();
4695
5028
  init_agent();
@@ -4711,6 +5044,7 @@ var init_builder = __esm({
4711
5044
  gadgetStartPrefix;
4712
5045
  gadgetEndPrefix;
4713
5046
  textOnlyHandler;
5047
+ textWithGadgetsHandler;
4714
5048
  stopOnGadgetError;
4715
5049
  shouldContinueAfterError;
4716
5050
  defaultGadgetTimeoutMs;
@@ -4973,6 +5307,30 @@ var init_builder = __esm({
4973
5307
  this.textOnlyHandler = handler;
4974
5308
  return this;
4975
5309
  }
5310
+ /**
5311
+ * Set the handler for text content that appears alongside gadget calls.
5312
+ *
5313
+ * When set, text accompanying gadget responses will be wrapped as a
5314
+ * synthetic gadget call before the actual gadget results in the
5315
+ * conversation history.
5316
+ *
5317
+ * @param handler - Configuration for wrapping text
5318
+ * @returns This builder for chaining
5319
+ *
5320
+ * @example
5321
+ * ```typescript
5322
+ * // Wrap text as TellUser gadget
5323
+ * .withTextWithGadgetsHandler({
5324
+ * gadgetName: "TellUser",
5325
+ * parameterMapping: (text) => ({ message: text, done: false, type: "info" }),
5326
+ * resultMapping: (text) => `ℹ️ ${text}`,
5327
+ * })
5328
+ * ```
5329
+ */
5330
+ withTextWithGadgetsHandler(handler) {
5331
+ this.textWithGadgetsHandler = handler;
5332
+ return this;
5333
+ }
4976
5334
  /**
4977
5335
  * Set whether to stop gadget execution on first error.
4978
5336
  *
@@ -5087,6 +5445,69 @@ var init_builder = __esm({
5087
5445
  this.gadgetOutputLimitPercent = percent;
5088
5446
  return this;
5089
5447
  }
5448
+ /**
5449
+ * Add a synthetic gadget call to the conversation history.
5450
+ *
5451
+ * This is useful for in-context learning - showing the LLM what "past self"
5452
+ * did correctly so it mimics the pattern. The call is formatted with proper
5453
+ * markers and parameter format.
5454
+ *
5455
+ * @param gadgetName - Name of the gadget
5456
+ * @param parameters - Parameters passed to the gadget
5457
+ * @param result - Result returned by the gadget
5458
+ * @returns This builder for chaining
5459
+ *
5460
+ * @example
5461
+ * ```typescript
5462
+ * .withSyntheticGadgetCall(
5463
+ * 'TellUser',
5464
+ * {
5465
+ * message: '👋 Hello!\n\nHere\'s what I can do:\n- Analyze code\n- Run commands',
5466
+ * done: false,
5467
+ * type: 'info'
5468
+ * },
5469
+ * 'ℹ️ 👋 Hello!\n\nHere\'s what I can do:\n- Analyze code\n- Run commands'
5470
+ * )
5471
+ * ```
5472
+ */
5473
+ withSyntheticGadgetCall(gadgetName, parameters, result) {
5474
+ const startPrefix = this.gadgetStartPrefix ?? GADGET_START_PREFIX;
5475
+ const endPrefix = this.gadgetEndPrefix ?? GADGET_END_PREFIX;
5476
+ const format = this.parameterFormat ?? "yaml";
5477
+ const paramStr = this.formatSyntheticParameters(parameters, format);
5478
+ this.initialMessages.push({
5479
+ role: "assistant",
5480
+ content: `${startPrefix}${gadgetName}
5481
+ ${paramStr}
5482
+ ${endPrefix}`
5483
+ });
5484
+ this.initialMessages.push({
5485
+ role: "user",
5486
+ content: `Result: ${result}`
5487
+ });
5488
+ return this;
5489
+ }
5490
+ /**
5491
+ * Format parameters for synthetic gadget calls.
5492
+ * Uses heredoc for multiline string values.
5493
+ */
5494
+ formatSyntheticParameters(parameters, format) {
5495
+ if (format === "json" || format === "auto") {
5496
+ return JSON.stringify(parameters);
5497
+ }
5498
+ return Object.entries(parameters).map(([key, value]) => {
5499
+ if (typeof value === "string" && value.includes("\n")) {
5500
+ const separator = format === "yaml" ? ":" : " =";
5501
+ return `${key}${separator} <<<EOF
5502
+ ${value}
5503
+ EOF`;
5504
+ }
5505
+ if (format === "yaml") {
5506
+ return typeof value === "string" ? `${key}: ${value}` : `${key}: ${JSON.stringify(value)}`;
5507
+ }
5508
+ return `${key} = ${JSON.stringify(value)}`;
5509
+ }).join("\n");
5510
+ }
5090
5511
  /**
5091
5512
  * Build and create the agent with the given user prompt.
5092
5513
  * Returns the Agent instance ready to run.
@@ -5129,6 +5550,7 @@ var init_builder = __esm({
5129
5550
  gadgetStartPrefix: this.gadgetStartPrefix,
5130
5551
  gadgetEndPrefix: this.gadgetEndPrefix,
5131
5552
  textOnlyHandler: this.textOnlyHandler,
5553
+ textWithGadgetsHandler: this.textWithGadgetsHandler,
5132
5554
  stopOnGadgetError: this.stopOnGadgetError,
5133
5555
  shouldContinueAfterError: this.shouldContinueAfterError,
5134
5556
  defaultGadgetTimeoutMs: this.defaultGadgetTimeoutMs,
@@ -5230,6 +5652,7 @@ var init_builder = __esm({
5230
5652
  gadgetStartPrefix: this.gadgetStartPrefix,
5231
5653
  gadgetEndPrefix: this.gadgetEndPrefix,
5232
5654
  textOnlyHandler: this.textOnlyHandler,
5655
+ textWithGadgetsHandler: this.textWithGadgetsHandler,
5233
5656
  stopOnGadgetError: this.stopOnGadgetError,
5234
5657
  shouldContinueAfterError: this.shouldContinueAfterError,
5235
5658
  defaultGadgetTimeoutMs: this.defaultGadgetTimeoutMs,
@@ -5265,7 +5688,8 @@ var OPTION_FLAGS = {
5265
5688
  logFile: "--log-file <path>",
5266
5689
  logReset: "--log-reset",
5267
5690
  noBuiltins: "--no-builtins",
5268
- noBuiltinInteraction: "--no-builtin-interaction"
5691
+ noBuiltinInteraction: "--no-builtin-interaction",
5692
+ quiet: "-q, --quiet"
5269
5693
  };
5270
5694
  var OPTION_DESCRIPTIONS = {
5271
5695
  model: "Model identifier, e.g. openai:gpt-5-nano or anthropic:claude-sonnet-4-5.",
@@ -5279,7 +5703,8 @@ var OPTION_DESCRIPTIONS = {
5279
5703
  logFile: "Path to log file. When set, logs are written to file instead of stderr.",
5280
5704
  logReset: "Reset (truncate) the log file at session start instead of appending.",
5281
5705
  noBuiltins: "Disable built-in gadgets (AskUser, TellUser).",
5282
- noBuiltinInteraction: "Disable interactive gadgets (AskUser) while keeping TellUser."
5706
+ noBuiltinInteraction: "Disable interactive gadgets (AskUser) while keeping TellUser.",
5707
+ quiet: "Suppress all output except content (text and TellUser messages)."
5283
5708
  };
5284
5709
  var SUMMARY_PREFIX = "[llmist]";
5285
5710
 
@@ -5289,7 +5714,7 @@ var import_commander3 = require("commander");
5289
5714
  // package.json
5290
5715
  var package_default = {
5291
5716
  name: "llmist",
5292
- version: "0.6.1",
5717
+ version: "0.7.0",
5293
5718
  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.",
5294
5719
  type: "module",
5295
5720
  main: "dist/index.cjs",
@@ -5373,6 +5798,7 @@ var package_default = {
5373
5798
  "@google/genai": "^1.27.0",
5374
5799
  chalk: "^5.6.2",
5375
5800
  commander: "^12.1.0",
5801
+ eta: "^4.4.1",
5376
5802
  "js-toml": "^1.0.2",
5377
5803
  "js-yaml": "^4.1.0",
5378
5804
  marked: "^15.0.12",
@@ -5437,7 +5863,7 @@ var tellUser = createGadget({
5437
5863
  name: "TellUser",
5438
5864
  description: "Tell the user something important. Set done=true when your work is complete and you want to end the conversation.",
5439
5865
  schema: import_zod2.z.object({
5440
- message: import_zod2.z.string().describe("The message to display to the user in Markdown"),
5866
+ message: import_zod2.z.string().optional().describe("The message to display to the user in Markdown"),
5441
5867
  done: import_zod2.z.boolean().default(false).describe("Set to true to end the conversation, false to continue"),
5442
5868
  type: import_zod2.z.enum(["info", "success", "warning", "error"]).default("info").describe("Message type: info, success, warning, or error")
5443
5869
  }),
@@ -5457,9 +5883,20 @@ var tellUser = createGadget({
5457
5883
  done: false,
5458
5884
  type: "warning"
5459
5885
  }
5886
+ },
5887
+ {
5888
+ comment: "Share detailed analysis with bullet points (use heredoc for multiline)",
5889
+ params: {
5890
+ message: "Here's what I found in the codebase:\n\n1. **Main entry point**: `src/index.ts` exports all public APIs\n2. **Core logic**: Located in `src/core/` with 5 modules\n3. **Tests**: Good coverage in `src/__tests__/`\n\nI'll continue exploring the core modules.",
5891
+ done: false,
5892
+ type: "info"
5893
+ }
5460
5894
  }
5461
5895
  ],
5462
5896
  execute: ({ message, done, type }) => {
5897
+ if (!message || message.trim() === "") {
5898
+ return "\u26A0\uFE0F TellUser was called without a message. Please provide content in the 'message' field.";
5899
+ }
5463
5900
  const prefixes = {
5464
5901
  info: "\u2139\uFE0F ",
5465
5902
  success: "\u2705 ",
@@ -5481,12 +5918,19 @@ var import_node_path2 = __toESM(require("path"), 1);
5481
5918
  var import_node_url = require("url");
5482
5919
  init_gadget();
5483
5920
  var PATH_PREFIXES = [".", "/", "~"];
5921
+ function isGadgetLike(value) {
5922
+ if (typeof value !== "object" || value === null) {
5923
+ return false;
5924
+ }
5925
+ const obj = value;
5926
+ return typeof obj.execute === "function" && typeof obj.description === "string" && ("parameterSchema" in obj || "schema" in obj);
5927
+ }
5484
5928
  function isGadgetConstructor(value) {
5485
5929
  if (typeof value !== "function") {
5486
5930
  return false;
5487
5931
  }
5488
5932
  const prototype = value.prototype;
5489
- return Boolean(prototype) && prototype instanceof BaseGadget;
5933
+ return Boolean(prototype) && (prototype instanceof BaseGadget || isGadgetLike(prototype));
5490
5934
  }
5491
5935
  function expandHomePath(input) {
5492
5936
  if (!input.startsWith("~")) {
@@ -5523,7 +5967,7 @@ function extractGadgetsFromModule(moduleExports) {
5523
5967
  return;
5524
5968
  }
5525
5969
  visited.add(value);
5526
- if (value instanceof BaseGadget) {
5970
+ if (value instanceof BaseGadget || isGadgetLike(value)) {
5527
5971
  results.push(value);
5528
5972
  return;
5529
5973
  }
@@ -5648,8 +6092,14 @@ function renderSummary(metadata) {
5648
6092
  parts.push(import_chalk.default.magenta(metadata.model));
5649
6093
  }
5650
6094
  if (metadata.usage) {
5651
- const { inputTokens, outputTokens } = metadata.usage;
6095
+ const { inputTokens, outputTokens, cachedInputTokens, cacheCreationInputTokens } = metadata.usage;
5652
6096
  parts.push(import_chalk.default.dim("\u2191") + import_chalk.default.yellow(` ${formatTokens(inputTokens)}`));
6097
+ if (cachedInputTokens && cachedInputTokens > 0) {
6098
+ parts.push(import_chalk.default.dim("\u27F3") + import_chalk.default.blue(` ${formatTokens(cachedInputTokens)}`));
6099
+ }
6100
+ if (cacheCreationInputTokens && cacheCreationInputTokens > 0) {
6101
+ parts.push(import_chalk.default.dim("\u270E") + import_chalk.default.magenta(` ${formatTokens(cacheCreationInputTokens)}`));
6102
+ }
5653
6103
  parts.push(import_chalk.default.dim("\u2193") + import_chalk.default.green(` ${formatTokens(outputTokens)}`));
5654
6104
  }
5655
6105
  if (metadata.elapsedSeconds !== void 0 && metadata.elapsedSeconds > 0) {
@@ -5741,53 +6191,6 @@ ${rendered}`;
5741
6191
  }
5742
6192
 
5743
6193
  // src/cli/utils.ts
5744
- var RARE_EMOJI = [
5745
- "\u{1F531}",
5746
- "\u2697\uFE0F",
5747
- "\u{1F9FF}",
5748
- "\u{1F530}",
5749
- "\u269B\uFE0F",
5750
- "\u{1F3FA}",
5751
- "\u{1F9EB}",
5752
- "\u{1F52C}",
5753
- "\u2695\uFE0F",
5754
- "\u{1F5DD}\uFE0F",
5755
- "\u2696\uFE0F",
5756
- "\u{1F52E}",
5757
- "\u{1FAAC}",
5758
- "\u{1F9EC}",
5759
- "\u2699\uFE0F",
5760
- "\u{1F529}",
5761
- "\u{1FA9B}",
5762
- "\u26CF\uFE0F",
5763
- "\u{1FA83}",
5764
- "\u{1F3F9}",
5765
- "\u{1F6E1}\uFE0F",
5766
- "\u2694\uFE0F",
5767
- "\u{1F5E1}\uFE0F",
5768
- "\u{1FA93}",
5769
- "\u{1F5C3}\uFE0F",
5770
- "\u{1F4DC}",
5771
- "\u{1F4EF}",
5772
- "\u{1F3B4}",
5773
- "\u{1F004}",
5774
- "\u{1F3B2}"
5775
- ];
5776
- function generateMarkers() {
5777
- const pick = (count) => {
5778
- const result = [];
5779
- const pool = [...RARE_EMOJI];
5780
- for (let i = 0; i < count && pool.length > 0; i++) {
5781
- const idx = Math.floor(Math.random() * pool.length);
5782
- result.push(pool.splice(idx, 1)[0]);
5783
- }
5784
- return result.join("");
5785
- };
5786
- return {
5787
- startPrefix: pick(5),
5788
- endPrefix: pick(5)
5789
- };
5790
- }
5791
6194
  function createNumericParser({
5792
6195
  label,
5793
6196
  integer = false,
@@ -5865,6 +6268,9 @@ var StreamProgress = class {
5865
6268
  callOutputTokensEstimated = true;
5866
6269
  callOutputChars = 0;
5867
6270
  isStreaming = false;
6271
+ // Cache token tracking for live cost estimation during streaming
6272
+ callCachedInputTokens = 0;
6273
+ callCacheCreationInputTokens = 0;
5868
6274
  // Cumulative stats (cumulative mode)
5869
6275
  totalStartTime = Date.now();
5870
6276
  totalTokens = 0;
@@ -5890,11 +6296,13 @@ var StreamProgress = class {
5890
6296
  this.callOutputTokensEstimated = true;
5891
6297
  this.callOutputChars = 0;
5892
6298
  this.isStreaming = false;
6299
+ this.callCachedInputTokens = 0;
6300
+ this.callCacheCreationInputTokens = 0;
5893
6301
  this.start();
5894
6302
  }
5895
6303
  /**
5896
6304
  * Ends the current LLM call. Updates cumulative stats and switches to cumulative mode.
5897
- * @param usage - Final token usage from the call
6305
+ * @param usage - Final token usage from the call (including cached tokens if available)
5898
6306
  */
5899
6307
  endCall(usage) {
5900
6308
  this.iterations++;
@@ -5906,7 +6314,9 @@ var StreamProgress = class {
5906
6314
  const cost = this.modelRegistry.estimateCost(
5907
6315
  modelName,
5908
6316
  usage.inputTokens,
5909
- usage.outputTokens
6317
+ usage.outputTokens,
6318
+ usage.cachedInputTokens ?? 0,
6319
+ usage.cacheCreationInputTokens ?? 0
5910
6320
  );
5911
6321
  if (cost) {
5912
6322
  this.totalCost += cost.totalCost;
@@ -5946,6 +6356,16 @@ var StreamProgress = class {
5946
6356
  this.callOutputTokens = tokens;
5947
6357
  this.callOutputTokensEstimated = estimated;
5948
6358
  }
6359
+ /**
6360
+ * Sets cached token counts for the current call (from stream metadata).
6361
+ * Used for live cost estimation during streaming.
6362
+ * @param cachedInputTokens - Number of tokens read from cache (cheaper)
6363
+ * @param cacheCreationInputTokens - Number of tokens written to cache (more expensive)
6364
+ */
6365
+ setCachedTokens(cachedInputTokens, cacheCreationInputTokens) {
6366
+ this.callCachedInputTokens = cachedInputTokens;
6367
+ this.callCacheCreationInputTokens = cacheCreationInputTokens;
6368
+ }
5949
6369
  /**
5950
6370
  * Get total elapsed time in seconds since the first call started.
5951
6371
  * @returns Elapsed time in seconds with 1 decimal place
@@ -6010,11 +6430,32 @@ var StreamProgress = class {
6010
6430
  parts.push(import_chalk2.default.dim("\u2193") + import_chalk2.default.green(` ${prefix}${formatTokens(outTokens)}`));
6011
6431
  }
6012
6432
  parts.push(import_chalk2.default.dim(`${elapsed}s`));
6013
- if (this.totalCost > 0) {
6014
- parts.push(import_chalk2.default.cyan(`$${formatCost(this.totalCost)}`));
6433
+ const callCost = this.calculateCurrentCallCost(outTokens);
6434
+ if (callCost > 0) {
6435
+ parts.push(import_chalk2.default.cyan(`$${formatCost(callCost)}`));
6015
6436
  }
6016
6437
  this.target.write(`\r${parts.join(import_chalk2.default.dim(" | "))} ${import_chalk2.default.cyan(spinner)}`);
6017
6438
  }
6439
+ /**
6440
+ * Calculates live cost estimate for the current streaming call.
6441
+ * Uses current input/output tokens and cached token counts.
6442
+ */
6443
+ calculateCurrentCallCost(outputTokens) {
6444
+ if (!this.modelRegistry || !this.model) return 0;
6445
+ try {
6446
+ const modelName = this.model.includes(":") ? this.model.split(":")[1] : this.model;
6447
+ const cost = this.modelRegistry.estimateCost(
6448
+ modelName,
6449
+ this.callInputTokens,
6450
+ outputTokens,
6451
+ this.callCachedInputTokens,
6452
+ this.callCacheCreationInputTokens
6453
+ );
6454
+ return cost?.totalCost ?? 0;
6455
+ } catch {
6456
+ return 0;
6457
+ }
6458
+ }
6018
6459
  renderCumulativeMode(spinner) {
6019
6460
  const elapsed = ((Date.now() - this.totalStartTime) / 1e3).toFixed(1);
6020
6461
  const parts = [];
@@ -6163,7 +6604,7 @@ function addCompleteOptions(cmd, defaults) {
6163
6604
  OPTION_DESCRIPTIONS.maxTokens,
6164
6605
  createNumericParser({ label: "Max tokens", integer: true, min: 1 }),
6165
6606
  defaults?.["max-tokens"]
6166
- );
6607
+ ).option(OPTION_FLAGS.quiet, OPTION_DESCRIPTIONS.quiet, defaults?.quiet);
6167
6608
  }
6168
6609
  function addAgentOptions(cmd, defaults) {
6169
6610
  const gadgetAccumulator = (value, previous = []) => [
@@ -6192,7 +6633,7 @@ function addAgentOptions(cmd, defaults) {
6192
6633
  OPTION_FLAGS.noBuiltinInteraction,
6193
6634
  OPTION_DESCRIPTIONS.noBuiltinInteraction,
6194
6635
  defaults?.["builtin-interaction"] !== false
6195
- );
6636
+ ).option(OPTION_FLAGS.quiet, OPTION_DESCRIPTIONS.quiet, defaults?.quiet);
6196
6637
  }
6197
6638
  function configToCompleteOptions(config) {
6198
6639
  const result = {};
@@ -6200,6 +6641,7 @@ function configToCompleteOptions(config) {
6200
6641
  if (config.system !== void 0) result.system = config.system;
6201
6642
  if (config.temperature !== void 0) result.temperature = config.temperature;
6202
6643
  if (config["max-tokens"] !== void 0) result.maxTokens = config["max-tokens"];
6644
+ if (config.quiet !== void 0) result.quiet = config.quiet;
6203
6645
  return result;
6204
6646
  }
6205
6647
  function configToAgentOptions(config) {
@@ -6213,6 +6655,11 @@ function configToAgentOptions(config) {
6213
6655
  if (config.builtins !== void 0) result.builtins = config.builtins;
6214
6656
  if (config["builtin-interaction"] !== void 0)
6215
6657
  result.builtinInteraction = config["builtin-interaction"];
6658
+ if (config["gadget-start-prefix"] !== void 0)
6659
+ result.gadgetStartPrefix = config["gadget-start-prefix"];
6660
+ if (config["gadget-end-prefix"] !== void 0)
6661
+ result.gadgetEndPrefix = config["gadget-end-prefix"];
6662
+ if (config.quiet !== void 0) result.quiet = config.quiet;
6216
6663
  return result;
6217
6664
  }
6218
6665
 
@@ -6258,9 +6705,10 @@ async function executeAgent(promptArg, options, env) {
6258
6705
  const prompt = await resolvePrompt(promptArg, env);
6259
6706
  const client = env.createClient();
6260
6707
  const registry = new GadgetRegistry();
6708
+ const stdinIsInteractive = isInteractive(env.stdin);
6261
6709
  if (options.builtins !== false) {
6262
6710
  for (const gadget of builtinGadgets) {
6263
- if (options.builtinInteraction === false && gadget.name === "AskUser") {
6711
+ if (gadget.name === "AskUser" && (options.builtinInteraction === false || !stdinIsInteractive)) {
6264
6712
  continue;
6265
6713
  }
6266
6714
  registry.registerByClass(gadget);
@@ -6318,6 +6766,10 @@ async function executeAgent(promptArg, options, env) {
6318
6766
  if (context.usage.outputTokens) {
6319
6767
  progress.setOutputTokens(context.usage.outputTokens, false);
6320
6768
  }
6769
+ progress.setCachedTokens(
6770
+ context.usage.cachedInputTokens ?? 0,
6771
+ context.usage.cacheCreationInputTokens ?? 0
6772
+ );
6321
6773
  }
6322
6774
  },
6323
6775
  // onLLMCallComplete: Finalize metrics after each LLM call
@@ -6336,11 +6788,13 @@ async function executeAgent(promptArg, options, env) {
6336
6788
  let callCost;
6337
6789
  if (context.usage && client.modelRegistry) {
6338
6790
  try {
6339
- const modelName = options.model.includes(":") ? options.model.split(":")[1] : options.model;
6791
+ const modelName = context.options.model.includes(":") ? context.options.model.split(":")[1] : context.options.model;
6340
6792
  const costResult = client.modelRegistry.estimateCost(
6341
6793
  modelName,
6342
6794
  context.usage.inputTokens,
6343
- context.usage.outputTokens
6795
+ context.usage.outputTokens,
6796
+ context.usage.cachedInputTokens ?? 0,
6797
+ context.usage.cacheCreationInputTokens ?? 0
6344
6798
  );
6345
6799
  if (costResult) callCost = costResult.totalCost;
6346
6800
  } catch {
@@ -6348,7 +6802,7 @@ async function executeAgent(promptArg, options, env) {
6348
6802
  }
6349
6803
  const callElapsed = progress.getCallElapsedSeconds();
6350
6804
  progress.endCall(context.usage);
6351
- if (stderrTTY) {
6805
+ if (!options.quiet) {
6352
6806
  const summary = renderSummary({
6353
6807
  iterations: context.iteration + 1,
6354
6808
  model: options.model,
@@ -6427,9 +6881,27 @@ Command rejected by user with message: "${response}"`
6427
6881
  builder.withGadgets(...gadgets);
6428
6882
  }
6429
6883
  builder.withParameterFormat(options.parameterFormat);
6430
- const markers = generateMarkers();
6431
- builder.withGadgetStartPrefix(markers.startPrefix);
6432
- builder.withGadgetEndPrefix(markers.endPrefix);
6884
+ if (options.gadgetStartPrefix) {
6885
+ builder.withGadgetStartPrefix(options.gadgetStartPrefix);
6886
+ }
6887
+ if (options.gadgetEndPrefix) {
6888
+ builder.withGadgetEndPrefix(options.gadgetEndPrefix);
6889
+ }
6890
+ builder.withSyntheticGadgetCall(
6891
+ "TellUser",
6892
+ {
6893
+ message: "\u{1F44B} Hello! I'm ready to help.\n\nHere's what I can do:\n- Analyze your codebase\n- Execute commands\n- Answer questions\n\nWhat would you like me to work on?",
6894
+ done: false,
6895
+ type: "info"
6896
+ },
6897
+ "\u2139\uFE0F \u{1F44B} Hello! I'm ready to help.\n\nHere's what I can do:\n- Analyze your codebase\n- Execute commands\n- Answer questions\n\nWhat would you like me to work on?"
6898
+ );
6899
+ builder.withTextOnlyHandler("acknowledge");
6900
+ builder.withTextWithGadgetsHandler({
6901
+ gadgetName: "TellUser",
6902
+ parameterMapping: (text) => ({ message: text, done: false, type: "info" }),
6903
+ resultMapping: (text) => `\u2139\uFE0F ${text}`
6904
+ });
6433
6905
  const agent = builder.ask(prompt);
6434
6906
  for await (const event of agent.run()) {
6435
6907
  if (event.type === "text") {
@@ -6437,7 +6909,14 @@ Command rejected by user with message: "${response}"`
6437
6909
  printer.write(event.content);
6438
6910
  } else if (event.type === "gadget_result") {
6439
6911
  progress.pause();
6440
- if (stderrTTY) {
6912
+ if (options.quiet) {
6913
+ if (event.result.gadgetName === "TellUser" && event.result.parameters?.message) {
6914
+ const message = String(event.result.parameters.message);
6915
+ const rendered = renderMarkdown(message);
6916
+ env.stdout.write(`${rendered}
6917
+ `);
6918
+ }
6919
+ } else {
6441
6920
  const tokenCount = await countGadgetOutputTokens(event.result.result);
6442
6921
  env.stderr.write(`${formatGadgetSummary({ ...event.result, tokenCount })}
6443
6922
  `);
@@ -6446,7 +6925,7 @@ Command rejected by user with message: "${response}"`
6446
6925
  }
6447
6926
  progress.complete();
6448
6927
  printer.ensureNewline();
6449
- if (stderrTTY && iterations > 1) {
6928
+ if (!options.quiet && iterations > 1) {
6450
6929
  env.stderr.write(`${import_chalk3.default.dim("\u2500".repeat(40))}
6451
6930
  `);
6452
6931
  const summary = renderOverallSummary({
@@ -6519,7 +6998,7 @@ async function executeComplete(promptArg, options, env) {
6519
6998
  progress.endCall(usage);
6520
6999
  progress.complete();
6521
7000
  printer.ensureNewline();
6522
- if (stderrTTY) {
7001
+ if (stderrTTY && !options.quiet) {
6523
7002
  const summary = renderSummary({ finishReason, usage, cost: progress.getTotalCost() });
6524
7003
  if (summary) {
6525
7004
  env.stderr.write(`${summary}
@@ -6540,9 +7019,102 @@ var import_node_fs3 = require("fs");
6540
7019
  var import_node_os = require("os");
6541
7020
  var import_node_path3 = require("path");
6542
7021
  var import_js_toml2 = require("js-toml");
7022
+
7023
+ // src/cli/templates.ts
7024
+ var import_eta = require("eta");
7025
+ var TemplateError = class extends Error {
7026
+ constructor(message, promptName, configPath) {
7027
+ super(promptName ? `[prompts.${promptName}]: ${message}` : message);
7028
+ this.promptName = promptName;
7029
+ this.configPath = configPath;
7030
+ this.name = "TemplateError";
7031
+ }
7032
+ };
7033
+ function createTemplateEngine(prompts, configPath) {
7034
+ const eta = new import_eta.Eta({
7035
+ views: "/",
7036
+ // Required but we use named templates
7037
+ autoEscape: false,
7038
+ // Don't escape - these are prompts, not HTML
7039
+ autoTrim: false
7040
+ // Preserve whitespace in prompts
7041
+ });
7042
+ for (const [name, template] of Object.entries(prompts)) {
7043
+ try {
7044
+ eta.loadTemplate(`@${name}`, template);
7045
+ } catch (error) {
7046
+ throw new TemplateError(
7047
+ error instanceof Error ? error.message : String(error),
7048
+ name,
7049
+ configPath
7050
+ );
7051
+ }
7052
+ }
7053
+ return eta;
7054
+ }
7055
+ function resolveTemplate(eta, template, context = {}, configPath) {
7056
+ try {
7057
+ const fullContext = {
7058
+ ...context,
7059
+ env: process.env
7060
+ };
7061
+ return eta.renderString(template, fullContext);
7062
+ } catch (error) {
7063
+ throw new TemplateError(
7064
+ error instanceof Error ? error.message : String(error),
7065
+ void 0,
7066
+ configPath
7067
+ );
7068
+ }
7069
+ }
7070
+ function validatePrompts(prompts, configPath) {
7071
+ const eta = createTemplateEngine(prompts, configPath);
7072
+ for (const [name, template] of Object.entries(prompts)) {
7073
+ try {
7074
+ eta.renderString(template, { env: {} });
7075
+ } catch (error) {
7076
+ throw new TemplateError(
7077
+ error instanceof Error ? error.message : String(error),
7078
+ name,
7079
+ configPath
7080
+ );
7081
+ }
7082
+ }
7083
+ }
7084
+ function validateEnvVars(template, promptName, configPath) {
7085
+ const envVarPattern = /<%=\s*it\.env\.(\w+)\s*%>/g;
7086
+ const matches = template.matchAll(envVarPattern);
7087
+ for (const match of matches) {
7088
+ const varName = match[1];
7089
+ if (process.env[varName] === void 0) {
7090
+ throw new TemplateError(
7091
+ `Environment variable '${varName}' is not set`,
7092
+ promptName,
7093
+ configPath
7094
+ );
7095
+ }
7096
+ }
7097
+ }
7098
+ function hasTemplateSyntax(str) {
7099
+ return str.includes("<%");
7100
+ }
7101
+
7102
+ // src/cli/config.ts
6543
7103
  var GLOBAL_CONFIG_KEYS = /* @__PURE__ */ new Set(["log-level", "log-file", "log-reset"]);
6544
7104
  var VALID_LOG_LEVELS = ["silly", "trace", "debug", "info", "warn", "error", "fatal"];
6545
- var COMPLETE_CONFIG_KEYS = /* @__PURE__ */ new Set(["model", "system", "temperature", "max-tokens"]);
7105
+ var COMPLETE_CONFIG_KEYS = /* @__PURE__ */ new Set([
7106
+ "model",
7107
+ "system",
7108
+ "temperature",
7109
+ "max-tokens",
7110
+ "quiet",
7111
+ "inherits",
7112
+ "log-level",
7113
+ "log-file",
7114
+ "log-reset",
7115
+ "type"
7116
+ // Allowed for inheritance compatibility, ignored for built-in commands
7117
+ ]);
6546
7118
  var AGENT_CONFIG_KEYS = /* @__PURE__ */ new Set([
6547
7119
  "model",
6548
7120
  "system",
@@ -6551,16 +7123,22 @@ var AGENT_CONFIG_KEYS = /* @__PURE__ */ new Set([
6551
7123
  "gadget",
6552
7124
  "parameter-format",
6553
7125
  "builtins",
6554
- "builtin-interaction"
7126
+ "builtin-interaction",
7127
+ "gadget-start-prefix",
7128
+ "gadget-end-prefix",
7129
+ "quiet",
7130
+ "inherits",
7131
+ "log-level",
7132
+ "log-file",
7133
+ "log-reset",
7134
+ "type"
7135
+ // Allowed for inheritance compatibility, ignored for built-in commands
6555
7136
  ]);
6556
7137
  var CUSTOM_CONFIG_KEYS = /* @__PURE__ */ new Set([
6557
7138
  ...COMPLETE_CONFIG_KEYS,
6558
7139
  ...AGENT_CONFIG_KEYS,
6559
7140
  "type",
6560
- "description",
6561
- "log-level",
6562
- "log-file",
6563
- "log-reset"
7141
+ "description"
6564
7142
  ]);
6565
7143
  var VALID_PARAMETER_FORMATS = ["json", "yaml", "toml", "auto"];
6566
7144
  function getConfigPath() {
@@ -6611,6 +7189,39 @@ function validateStringArray(value, key, section) {
6611
7189
  }
6612
7190
  return value;
6613
7191
  }
7192
+ function validateInherits(value, section) {
7193
+ if (typeof value === "string") {
7194
+ return value;
7195
+ }
7196
+ if (Array.isArray(value)) {
7197
+ for (let i = 0; i < value.length; i++) {
7198
+ if (typeof value[i] !== "string") {
7199
+ throw new ConfigError(`[${section}].inherits[${i}] must be a string`);
7200
+ }
7201
+ }
7202
+ return value;
7203
+ }
7204
+ throw new ConfigError(`[${section}].inherits must be a string or array of strings`);
7205
+ }
7206
+ function validateLoggingConfig(raw, section) {
7207
+ const result = {};
7208
+ if ("log-level" in raw) {
7209
+ const level = validateString(raw["log-level"], "log-level", section);
7210
+ if (!VALID_LOG_LEVELS.includes(level)) {
7211
+ throw new ConfigError(
7212
+ `[${section}].log-level must be one of: ${VALID_LOG_LEVELS.join(", ")}`
7213
+ );
7214
+ }
7215
+ result["log-level"] = level;
7216
+ }
7217
+ if ("log-file" in raw) {
7218
+ result["log-file"] = validateString(raw["log-file"], "log-file", section);
7219
+ }
7220
+ if ("log-reset" in raw) {
7221
+ result["log-reset"] = validateBoolean(raw["log-reset"], "log-reset", section);
7222
+ }
7223
+ return result;
7224
+ }
6614
7225
  function validateBaseConfig(raw, section) {
6615
7226
  const result = {};
6616
7227
  if ("model" in raw) {
@@ -6625,6 +7236,9 @@ function validateBaseConfig(raw, section) {
6625
7236
  max: 2
6626
7237
  });
6627
7238
  }
7239
+ if ("inherits" in raw) {
7240
+ result.inherits = validateInherits(raw.inherits, section);
7241
+ }
6628
7242
  return result;
6629
7243
  }
6630
7244
  function validateGlobalConfig(raw, section) {
@@ -6637,23 +7251,7 @@ function validateGlobalConfig(raw, section) {
6637
7251
  throw new ConfigError(`[${section}].${key} is not a valid option`);
6638
7252
  }
6639
7253
  }
6640
- const result = {};
6641
- if ("log-level" in rawObj) {
6642
- const level = validateString(rawObj["log-level"], "log-level", section);
6643
- if (!VALID_LOG_LEVELS.includes(level)) {
6644
- throw new ConfigError(
6645
- `[${section}].log-level must be one of: ${VALID_LOG_LEVELS.join(", ")}`
6646
- );
6647
- }
6648
- result["log-level"] = level;
6649
- }
6650
- if ("log-file" in rawObj) {
6651
- result["log-file"] = validateString(rawObj["log-file"], "log-file", section);
6652
- }
6653
- if ("log-reset" in rawObj) {
6654
- result["log-reset"] = validateBoolean(rawObj["log-reset"], "log-reset", section);
6655
- }
6656
- return result;
7254
+ return validateLoggingConfig(rawObj, section);
6657
7255
  }
6658
7256
  function validateCompleteConfig(raw, section) {
6659
7257
  if (typeof raw !== "object" || raw === null) {
@@ -6665,13 +7263,19 @@ function validateCompleteConfig(raw, section) {
6665
7263
  throw new ConfigError(`[${section}].${key} is not a valid option`);
6666
7264
  }
6667
7265
  }
6668
- const result = { ...validateBaseConfig(rawObj, section) };
7266
+ const result = {
7267
+ ...validateBaseConfig(rawObj, section),
7268
+ ...validateLoggingConfig(rawObj, section)
7269
+ };
6669
7270
  if ("max-tokens" in rawObj) {
6670
7271
  result["max-tokens"] = validateNumber(rawObj["max-tokens"], "max-tokens", section, {
6671
7272
  integer: true,
6672
7273
  min: 1
6673
7274
  });
6674
7275
  }
7276
+ if ("quiet" in rawObj) {
7277
+ result.quiet = validateBoolean(rawObj.quiet, "quiet", section);
7278
+ }
6675
7279
  return result;
6676
7280
  }
6677
7281
  function validateAgentConfig(raw, section) {
@@ -6684,7 +7288,10 @@ function validateAgentConfig(raw, section) {
6684
7288
  throw new ConfigError(`[${section}].${key} is not a valid option`);
6685
7289
  }
6686
7290
  }
6687
- const result = { ...validateBaseConfig(rawObj, section) };
7291
+ const result = {
7292
+ ...validateBaseConfig(rawObj, section),
7293
+ ...validateLoggingConfig(rawObj, section)
7294
+ };
6688
7295
  if ("max-iterations" in rawObj) {
6689
7296
  result["max-iterations"] = validateNumber(rawObj["max-iterations"], "max-iterations", section, {
6690
7297
  integer: true,
@@ -6713,6 +7320,23 @@ function validateAgentConfig(raw, section) {
6713
7320
  section
6714
7321
  );
6715
7322
  }
7323
+ if ("gadget-start-prefix" in rawObj) {
7324
+ result["gadget-start-prefix"] = validateString(
7325
+ rawObj["gadget-start-prefix"],
7326
+ "gadget-start-prefix",
7327
+ section
7328
+ );
7329
+ }
7330
+ if ("gadget-end-prefix" in rawObj) {
7331
+ result["gadget-end-prefix"] = validateString(
7332
+ rawObj["gadget-end-prefix"],
7333
+ "gadget-end-prefix",
7334
+ section
7335
+ );
7336
+ }
7337
+ if ("quiet" in rawObj) {
7338
+ result.quiet = validateBoolean(rawObj.quiet, "quiet", section);
7339
+ }
6716
7340
  return result;
6717
7341
  }
6718
7342
  function validateCustomConfig(raw, section) {
@@ -6768,26 +7392,42 @@ function validateCustomConfig(raw, section) {
6768
7392
  section
6769
7393
  );
6770
7394
  }
7395
+ if ("gadget-start-prefix" in rawObj) {
7396
+ result["gadget-start-prefix"] = validateString(
7397
+ rawObj["gadget-start-prefix"],
7398
+ "gadget-start-prefix",
7399
+ section
7400
+ );
7401
+ }
7402
+ if ("gadget-end-prefix" in rawObj) {
7403
+ result["gadget-end-prefix"] = validateString(
7404
+ rawObj["gadget-end-prefix"],
7405
+ "gadget-end-prefix",
7406
+ section
7407
+ );
7408
+ }
6771
7409
  if ("max-tokens" in rawObj) {
6772
7410
  result["max-tokens"] = validateNumber(rawObj["max-tokens"], "max-tokens", section, {
6773
7411
  integer: true,
6774
7412
  min: 1
6775
7413
  });
6776
7414
  }
6777
- if ("log-level" in rawObj) {
6778
- const level = validateString(rawObj["log-level"], "log-level", section);
6779
- if (!VALID_LOG_LEVELS.includes(level)) {
6780
- throw new ConfigError(
6781
- `[${section}].log-level must be one of: ${VALID_LOG_LEVELS.join(", ")}`
6782
- );
6783
- }
6784
- result["log-level"] = level;
7415
+ if ("quiet" in rawObj) {
7416
+ result.quiet = validateBoolean(rawObj.quiet, "quiet", section);
6785
7417
  }
6786
- if ("log-file" in rawObj) {
6787
- result["log-file"] = validateString(rawObj["log-file"], "log-file", section);
7418
+ Object.assign(result, validateLoggingConfig(rawObj, section));
7419
+ return result;
7420
+ }
7421
+ function validatePromptsConfig(raw, section) {
7422
+ if (typeof raw !== "object" || raw === null) {
7423
+ throw new ConfigError(`[${section}] must be a table`);
6788
7424
  }
6789
- if ("log-reset" in rawObj) {
6790
- result["log-reset"] = validateBoolean(rawObj["log-reset"], "log-reset", section);
7425
+ const result = {};
7426
+ for (const [key, value] of Object.entries(raw)) {
7427
+ if (typeof value !== "string") {
7428
+ throw new ConfigError(`[${section}].${key} must be a string`);
7429
+ }
7430
+ result[key] = value;
6791
7431
  }
6792
7432
  return result;
6793
7433
  }
@@ -6805,6 +7445,8 @@ function validateConfig(raw, configPath) {
6805
7445
  result.complete = validateCompleteConfig(value, key);
6806
7446
  } else if (key === "agent") {
6807
7447
  result.agent = validateAgentConfig(value, key);
7448
+ } else if (key === "prompts") {
7449
+ result.prompts = validatePromptsConfig(value, key);
6808
7450
  } else {
6809
7451
  result[key] = validateCustomConfig(value, key);
6810
7452
  }
@@ -6840,12 +7482,119 @@ function loadConfig() {
6840
7482
  configPath
6841
7483
  );
6842
7484
  }
6843
- return validateConfig(raw, configPath);
7485
+ const validated = validateConfig(raw, configPath);
7486
+ const inherited = resolveInheritance(validated, configPath);
7487
+ return resolveTemplatesInConfig(inherited, configPath);
6844
7488
  }
6845
7489
  function getCustomCommandNames(config) {
6846
- const reserved = /* @__PURE__ */ new Set(["global", "complete", "agent"]);
7490
+ const reserved = /* @__PURE__ */ new Set(["global", "complete", "agent", "prompts"]);
6847
7491
  return Object.keys(config).filter((key) => !reserved.has(key));
6848
7492
  }
7493
+ function resolveTemplatesInConfig(config, configPath) {
7494
+ const prompts = config.prompts ?? {};
7495
+ const hasPrompts = Object.keys(prompts).length > 0;
7496
+ let hasTemplates = false;
7497
+ for (const [sectionName, section] of Object.entries(config)) {
7498
+ if (sectionName === "global" || sectionName === "prompts") continue;
7499
+ if (!section || typeof section !== "object") continue;
7500
+ const sectionObj = section;
7501
+ if (typeof sectionObj.system === "string" && hasTemplateSyntax(sectionObj.system)) {
7502
+ hasTemplates = true;
7503
+ break;
7504
+ }
7505
+ }
7506
+ for (const template of Object.values(prompts)) {
7507
+ if (hasTemplateSyntax(template)) {
7508
+ hasTemplates = true;
7509
+ break;
7510
+ }
7511
+ }
7512
+ if (!hasPrompts && !hasTemplates) {
7513
+ return config;
7514
+ }
7515
+ try {
7516
+ validatePrompts(prompts, configPath);
7517
+ } catch (error) {
7518
+ if (error instanceof TemplateError) {
7519
+ throw new ConfigError(error.message, configPath);
7520
+ }
7521
+ throw error;
7522
+ }
7523
+ for (const [name, template] of Object.entries(prompts)) {
7524
+ try {
7525
+ validateEnvVars(template, name, configPath);
7526
+ } catch (error) {
7527
+ if (error instanceof TemplateError) {
7528
+ throw new ConfigError(error.message, configPath);
7529
+ }
7530
+ throw error;
7531
+ }
7532
+ }
7533
+ const eta = createTemplateEngine(prompts, configPath);
7534
+ const result = { ...config };
7535
+ for (const [sectionName, section] of Object.entries(config)) {
7536
+ if (sectionName === "global" || sectionName === "prompts") continue;
7537
+ if (!section || typeof section !== "object") continue;
7538
+ const sectionObj = section;
7539
+ if (typeof sectionObj.system === "string" && hasTemplateSyntax(sectionObj.system)) {
7540
+ try {
7541
+ validateEnvVars(sectionObj.system, void 0, configPath);
7542
+ } catch (error) {
7543
+ if (error instanceof TemplateError) {
7544
+ throw new ConfigError(`[${sectionName}].system: ${error.message}`, configPath);
7545
+ }
7546
+ throw error;
7547
+ }
7548
+ try {
7549
+ const resolved = resolveTemplate(eta, sectionObj.system, {}, configPath);
7550
+ result[sectionName] = {
7551
+ ...sectionObj,
7552
+ system: resolved
7553
+ };
7554
+ } catch (error) {
7555
+ if (error instanceof TemplateError) {
7556
+ throw new ConfigError(`[${sectionName}].system: ${error.message}`, configPath);
7557
+ }
7558
+ throw error;
7559
+ }
7560
+ }
7561
+ }
7562
+ return result;
7563
+ }
7564
+ function resolveInheritance(config, configPath) {
7565
+ const resolved = {};
7566
+ const resolving = /* @__PURE__ */ new Set();
7567
+ function resolveSection(name) {
7568
+ if (name in resolved) {
7569
+ return resolved[name];
7570
+ }
7571
+ if (resolving.has(name)) {
7572
+ throw new ConfigError(`Circular inheritance detected: ${name}`, configPath);
7573
+ }
7574
+ const section = config[name];
7575
+ if (section === void 0 || typeof section !== "object") {
7576
+ throw new ConfigError(`Cannot inherit from unknown section: ${name}`, configPath);
7577
+ }
7578
+ resolving.add(name);
7579
+ const sectionObj = section;
7580
+ const inheritsRaw = sectionObj.inherits;
7581
+ const inheritsList = inheritsRaw ? Array.isArray(inheritsRaw) ? inheritsRaw : [inheritsRaw] : [];
7582
+ let merged = {};
7583
+ for (const parent of inheritsList) {
7584
+ const parentResolved = resolveSection(parent);
7585
+ merged = { ...merged, ...parentResolved };
7586
+ }
7587
+ const { inherits: _inherits, ...ownValues } = sectionObj;
7588
+ merged = { ...merged, ...ownValues };
7589
+ resolving.delete(name);
7590
+ resolved[name] = merged;
7591
+ return merged;
7592
+ }
7593
+ for (const name of Object.keys(config)) {
7594
+ resolveSection(name);
7595
+ }
7596
+ return resolved;
7597
+ }
6849
7598
 
6850
7599
  // src/cli/models-command.ts
6851
7600
  var import_chalk4 = __toESM(require("chalk"), 1);