@link-assistant/hive-mind 2.3.0 → 2.4.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/CHANGELOG.md CHANGED
@@ -1,5 +1,12 @@
1
1
  # @link-assistant/hive-mind
2
2
 
3
+ ## 2.4.0
4
+
5
+ ### Minor Changes
6
+
7
+ - dc28004: Fix `--think` validation asymmetry between `solve` and `hive` (Issue #2041). After the #2038 vocabulary refactor, `solve.config` only validated `--think` in the CLI `parseArguments()` path, so consumers that parse solve options directly through the yargs config — most notably the Telegram bot — silently accepted invalid `--think` values (a CI false-negative that failed `test-telegram-options-before-url`). `createYargsConfig` now runs the same `normalizeAndValidateThink` `.check()` that `hive.config` already used, and `parseArguments` propagates that validation error verbatim instead of swallowing it. Invalid `--think` values are now rejected consistently on the CLI and Telegram paths.
8
+ - d92c772: `--think` now accepts a richer, provider-neutral vocabulary (Issue #2038): the off synonyms `off`/`disable`/`disabled`/`no`/`none` all mean disabled (or the closest safe equivalent when a model cannot truly disable thinking), a new `minimal` tier below `low` (Codex `minimal` reasoning; Claude lowest effort with a ~4000-token budget), a first-class `adaptive` mode that requests provider-managed adaptive thinking and fails fast for `solve`/`hive` on models/tools that do not support it (only adaptive-only Claude models: Opus 4.7+, Fable 5, Mythos 5, Sonnet 5), and numeric intensities for precision — percentages `0%`..`100%`, fractions `0.0`..`1.0`, and the integers `0` (off) and `1` (max). Normalization is applied consistently for both `solve` and `hive`.
9
+
3
10
  ## 2.3.0
4
11
 
5
12
  ### Minor Changes
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@link-assistant/hive-mind",
3
- "version": "2.3.0",
3
+ "version": "2.4.0",
4
4
  "description": "AI-powered issue solver and hive mind for collaborative problem solving",
5
5
  "main": "src/hive.mjs",
6
6
  "type": "module",
@@ -206,7 +206,9 @@ export const resolveThinkingSettings = async (argv, log) => {
206
206
  let thinkLevel = argv.think;
207
207
  let translation = null;
208
208
  if (isNewVersion) {
209
- if (thinkLevel !== undefined && thinkingBudget === undefined) {
209
+ // Issue #2038: `adaptive` is provider-managed and has no explicit token
210
+ // budget; skip the budget translation and let the model manage thinking.
211
+ if (thinkLevel !== undefined && thinkLevel !== 'adaptive' && thinkingBudget === undefined) {
210
212
  thinkingBudget = thinkingLevelToTokens[thinkLevel];
211
213
  translation = `--think ${thinkLevel} → --thinking-budget ${thinkingBudget}`;
212
214
  if (argv.verbose) {
@@ -14,6 +14,8 @@ export const mapModelToId = model => codexModels[model] || model;
14
14
  // deepest single-agent effort. `off` disables reasoning (`none`). See docs/case-studies/issue-2027.
15
15
  const THINK_LEVEL_TO_CODEX_REASONING = {
16
16
  off: 'none',
17
+ // Issue #2038: Codex/GPT-5.x natively exposes a `minimal` reasoning effort below `low`.
18
+ minimal: 'minimal',
17
19
  low: 'low',
18
20
  medium: 'medium',
19
21
  high: 'high',
@@ -341,6 +341,20 @@ export const supportsEffortLevel = model => {
341
341
  return isFable5OrMythos5(model) || isMythosPreview(model) || isOpus47OrLater(model) || isOpus46(model) || isSonnet46OrLater(model) || isOpus45(model);
342
342
  };
343
343
 
344
+ /**
345
+ * Issue #2038: Check whether a model uses provider-managed adaptive thinking.
346
+ * Adaptive-only Claude models (Opus 4.7+, Fable 5, Mythos 5, Sonnet 5) manage
347
+ * their own thinking depth and accept an unset effort/budget as "adaptive".
348
+ * These are exactly the models for which `--think adaptive` is meaningful; all
349
+ * other Claude models and non-Claude tools do not expose an adaptive mode.
350
+ * @param {string} model - The model name or ID
351
+ * @returns {boolean} True if the model supports adaptive thinking
352
+ */
353
+ export const supportsAdaptiveThinking = model => {
354
+ if (!model) return false;
355
+ return isOpus47OrLater(model) || isFable5OrMythos5(model) || isSonnet5(model);
356
+ };
357
+
344
358
  /**
345
359
  * Check if a model supports the xhigh effort level.
346
360
  * Official docs list xhigh for Claude Fable 5, Claude Mythos 5, Claude Opus 4.7,
@@ -393,6 +407,7 @@ export const getDefaultMaxThinkingBudgetForModel = model => {
393
407
  */
394
408
  export const getThinkingLevelToTokens = (maxBudget = DEFAULT_MAX_THINKING_BUDGET) => ({
395
409
  off: 0,
410
+ minimal: Math.floor(maxBudget / 8), // ~4000 for default 31999 (Issue #2038: below `low`)
396
411
  low: Math.floor(maxBudget / 4), // ~8000 for default 31999
397
412
  medium: Math.floor(maxBudget / 2), // ~16000 for default 31999
398
413
  high: Math.floor((maxBudget * 3) / 4), // ~24000 for default 31999
@@ -413,12 +428,14 @@ export const thinkingLevelToTokens = getThinkingLevelToTokens(DEFAULT_MAX_THINKI
413
428
  export const getTokensToThinkingLevel = (maxBudget = DEFAULT_MAX_THINKING_BUDGET) => {
414
429
  const levels = getThinkingLevelToTokens(maxBudget);
415
430
  // Calculate midpoints between levels for range determination
431
+ const minimalLowMidpoint = Math.floor((levels.minimal + levels.low) / 2);
416
432
  const lowMediumMidpoint = Math.floor((levels.low + levels.medium) / 2);
417
433
  const mediumHighMidpoint = Math.floor((levels.medium + levels.high) / 2);
418
434
  const highMaxMidpoint = Math.floor((levels.high + levels.max) / 2);
419
435
 
420
436
  return tokens => {
421
437
  if (tokens === 0) return 'off';
438
+ if (tokens <= minimalLowMidpoint) return 'minimal'; // Issue #2038
422
439
  if (tokens <= lowMediumMidpoint) return 'low';
423
440
  if (tokens <= mediumHighMidpoint) return 'medium';
424
441
  if (tokens <= highMaxMidpoint) return 'high';
@@ -505,6 +522,15 @@ export const thinkLevelToEffortLevel = (thinkLevel, options = {}) => {
505
522
  const supportsMax = options.supportsMax ?? true;
506
523
 
507
524
  switch (thinkLevel) {
525
+ case 'adaptive':
526
+ // Issue #2038: adaptive requests provider-managed thinking. Claude Code has
527
+ // no explicit `adaptive` effort value; leaving the effort unset lets the
528
+ // model manage its own thinking depth (its native adaptive behaviour).
529
+ return undefined;
530
+ case 'minimal':
531
+ // Issue #2038: Claude effort levels start at `low`; `minimal` maps to the
532
+ // lowest real effort so it stays strictly below `low` in intent but valid.
533
+ return 'low';
508
534
  case 'low':
509
535
  return 'low';
510
536
  case 'medium':
@@ -3,7 +3,7 @@
3
3
  // when only the yargs configuration is needed (e.g., in telegram-bot.mjs)
4
4
  // This module has no heavy dependencies to allow fast loading for --help
5
5
 
6
- import { SOLVE_OPTION_DEFINITIONS } from './solve.config.lib.mjs';
6
+ import { SOLVE_OPTION_DEFINITIONS, normalizeAndValidateThink } from './solve.config.lib.mjs';
7
7
  import { buildModelOptionDescription, defaultModels } from './models/index.mjs';
8
8
 
9
9
  // Hive-only options that are NOT solve options (hive-specific functionality).
@@ -224,6 +224,12 @@ export const createYargsConfig = yargsInstance => {
224
224
  'strip-aliased': false,
225
225
  'populate--': false,
226
226
  })
227
+ // Issue #2038: normalize/validate --think identically to solve (off synonyms,
228
+ // minimal, adaptive, percentages/fractions/0|1; fail fast on unsupported adaptive).
229
+ .check(argv => {
230
+ normalizeAndValidateThink(argv);
231
+ return true;
232
+ })
227
233
  .showHelpOnFail(false) // Don't show help on validation failures
228
234
  .strict()
229
235
  .help('h')
@@ -578,7 +578,7 @@ en
578
578
  branch
579
579
  option "• `--base-branch <branch>` or `-b` - Target branch for PR (default: repo default branch)"
580
580
  think
581
- option "• `--think <level>` - Thinking level (off/low/medium/high/xhigh/ultra/max) | `--thinking-budget <num>` - Token budget (0-63999)"
581
+ option "• `--think <level>` - Thinking level (off/minimal/low/medium/high/xhigh/ultra/max/adaptive) | `--thinking-budget <num>` - Token budget (0-63999)"
582
582
  verbose
583
583
  option "• `--verbose` or `-v` - Verbose output | `--attach-logs` - Attach logs to PR"
584
584
  show
@@ -578,7 +578,7 @@ hi
578
578
  branch
579
579
  option "• `--base-branch <branch>` या `-b` - PR के लिए target branch (default: repo default branch)"
580
580
  think
581
- option "• `--think <level>` - thinking level (off/low/medium/high/xhigh/ultra/max) | `--thinking-budget <num>` - token budget (0-63999)"
581
+ option "• `--think <level>` - thinking level (off/minimal/low/medium/high/xhigh/ultra/max/adaptive) | `--thinking-budget <num>` - token budget (0-63999)"
582
582
  verbose
583
583
  option "• `--verbose` या `-v` - verbose output | `--attach-logs` - logs को PR से attach करें"
584
584
  show
@@ -578,7 +578,7 @@ ru
578
578
  branch
579
579
  option "• `--base-branch <branch>` или `-b` - Целевая ветка для PR (по умолчанию ветка репозитория)"
580
580
  think
581
- option "• `--think <level>` - уровень размышления (off/low/medium/high/xhigh/ultra/max) | `--thinking-budget <num>` - бюджет токенов (0-63999)"
581
+ option "• `--think <level>` - уровень размышления (off/minimal/low/medium/high/xhigh/ultra/max/adaptive) | `--thinking-budget <num>` - бюджет токенов (0-63999)"
582
582
  verbose
583
583
  option "• `--verbose` или `-v` - подробный вывод | `--attach-logs` - прикрепить логи к PR"
584
584
  show
@@ -578,7 +578,7 @@ zh
578
578
  branch
579
579
  option "• `--base-branch <branch>` 或 `-b` - PR 目标分支(默认:仓库默认分支)"
580
580
  think
581
- option "• `--think <level>` - 思考级别(off/low/medium/high/xhigh/ultra/max)| `--thinking-budget <num>` - token 预算(0-63999)"
581
+ option "• `--think <level>` - 思考级别(off/minimal/low/medium/high/xhigh/ultra/max/adaptive)| `--thinking-budget <num>` - token 预算(0-63999)"
582
582
  verbose
583
583
  option "• `--verbose` 或 `-v` - 详细输出 | `--attach-logs` - 将日志附加到 PR"
584
584
  show
@@ -12,6 +12,9 @@ import { defaultModels, buildModelOptionDescription, resolveDefaultFallbackModel
12
12
  import { validateBranchName } from './solve.branch.lib.mjs';
13
13
  import { resolveEscalationConfig, isEscalateEnabled, DEFAULT_ESCALATE_RANGE } from './solve.escalate.lib.mjs';
14
14
  import { getLinoYargsFactory, hideBin, normalizeCliArgs, parseCliArgumentsWithLino } from './cli-arguments.lib.mjs';
15
+ import { normalizeThinkLevel, ADAPTIVE_THINK_LEVEL } from './think-level.lib.mjs';
16
+ import { supportsAdaptiveThinking } from './config.lib.mjs';
17
+ import { resolvePromptModelForTool } from './thinking-prompt.lib.mjs';
15
18
 
16
19
  // Re-export for use by telegram-bot.mjs (avoids extra import lines there)
17
20
  export { detectMalformedFlags };
@@ -299,8 +302,14 @@ export const SOLVE_OPTION_DEFINITIONS = {
299
302
  },
300
303
  think: {
301
304
  type: 'string',
302
- description: 'Thinking level hint. For Claude, translated to --thinking-budget for Claude Code >= 2.1.12 (off=0, low=~8000, medium=~16000, high=~24000, xhigh/ultra/max=31999) and to CLAUDE_CODE_EFFORT_LEVEL when supported. Adaptive-only models that cannot disable thinking use their lowest effort for off. Fable 5/Mythos 5/Sonnet 5/Opus 4.8/4.7 support xhigh and max; Opus 4.6/Sonnet 4.6/Mythos Preview support max; Opus 4.5 uses high for xhigh/max. `ultra` maps to the highest supported Claude effort (Claude "ultracode"-class reasoning). For Codex (GPT-5.6 Sol), mapped 1:1 to reasoning effort (off=none, low=low, medium=medium, high=high, xhigh=xhigh, ultra=ultra, max=max); GPT-5.6 keeps xhigh and adds max above it, and ultra runs the multi-agent mode paired with a rollout token budget cap. Default: off.',
303
- choices: ['off', 'low', 'medium', 'high', 'xhigh', 'ultra', 'max'],
305
+ description:
306
+ 'Thinking level hint. Levels (ascending): off, minimal, low, medium, high, xhigh, ultra, max, plus the special `adaptive` mode. ' +
307
+ 'Off synonyms (all mean disabled, or the closest safe equivalent): off/disable/disabled/no/none. An omitted --think means off. ' +
308
+ 'Numeric intensities are accepted for precision: percentages 0%..100%, fractions 0.0..1.0, and the integers 0 (off) and 1 (max). ' +
309
+ '`adaptive` requests provider-managed adaptive thinking and fails immediately for models/tools that do not support it (only adaptive-only Claude models: Opus 4.7+, Fable 5, Mythos 5, Sonnet 5). ' +
310
+ 'For Claude, levels translate to --thinking-budget for Claude Code >= 2.1.12 (off=0, minimal=~4000, low=~8000, medium=~16000, high=~24000, xhigh/ultra/max=31999) and to CLAUDE_CODE_EFFORT_LEVEL when supported (minimal→low). Adaptive-only models that cannot disable thinking use their lowest effort for off. ' +
311
+ '`ultra` maps to the highest supported Claude effort (Claude "ultracode"-class reasoning). ' +
312
+ 'For Codex (GPT-5.6 Sol), mapped 1:1 to reasoning effort (off=none, minimal=minimal, low=low, medium=medium, high=high, xhigh=xhigh, ultra=ultra, max=max); ultra runs the multi-agent mode paired with a rollout token budget cap. Default: off.',
304
313
  default: 'off',
305
314
  },
306
315
  'thinking-budget': {
@@ -736,6 +745,14 @@ export const createYargsConfig = yargsInstance => {
736
745
  .parserConfiguration({
737
746
  'boolean-negation': true,
738
747
  })
748
+ // Issue #2038 + #2041: normalize/validate --think during parsing (off synonyms,
749
+ // minimal, adaptive, percentages/fractions/0|1; fail fast on unsupported adaptive)
750
+ // so callers that parse via yargs directly (e.g. the Telegram bot) reject invalid
751
+ // --think values instead of silently accepting them. Mirrors hive.config.
752
+ .check(argv => {
753
+ normalizeAndValidateThink(argv);
754
+ return true;
755
+ })
739
756
  // Use yargs built-in strict mode to reject unrecognized options
740
757
  // This prevents issues like #453 and #482 where unknown options are silently ignored
741
758
  .strict()
@@ -745,6 +762,42 @@ export const createYargsConfig = yargsInstance => {
745
762
  return config;
746
763
  };
747
764
 
765
+ /**
766
+ * Issue #2038: Normalize the parsed `--think` value into a single canonical
767
+ * vocabulary and validate the special `adaptive` mode. Mutates `argv.think` in
768
+ * place and throws (with `_enhanced` set so the error message is shown verbatim)
769
+ * for invalid values or unsupported adaptive requests. Shared by solve and hive
770
+ * so both commands fail fast identically.
771
+ * @param {Object} argv - Parsed CLI arguments (reads/writes `think`, reads `tool`/`model`)
772
+ */
773
+ export const normalizeAndValidateThink = argv => {
774
+ if (!argv) return;
775
+
776
+ if (argv.think !== undefined) {
777
+ try {
778
+ argv.think = normalizeThinkLevel(argv.think);
779
+ } catch (thinkError) {
780
+ const err = new Error(thinkError.message);
781
+ err._enhanced = true;
782
+ throw err;
783
+ }
784
+ }
785
+
786
+ // `--think adaptive` requests provider-managed adaptive thinking and must fail
787
+ // immediately when the selected tool/model does not support it. Only
788
+ // adaptive-only Claude models expose an adaptive mode.
789
+ if (argv.think === ADAPTIVE_THINK_LEVEL) {
790
+ const tool = argv.tool || 'claude';
791
+ const resolvedModel = resolvePromptModelForTool(tool, argv.model);
792
+ const adaptiveSupported = tool === 'claude' && supportsAdaptiveThinking(resolvedModel);
793
+ if (!adaptiveSupported) {
794
+ const err = new Error(`--think adaptive is not supported by ${tool}${resolvedModel ? ` model "${resolvedModel}"` : ''}. ` + 'Adaptive thinking is only available on adaptive-only Claude models (Opus 4.7+, Fable 5, Mythos 5, Sonnet 5). ' + 'Use an explicit level instead (off, minimal, low, medium, high, xhigh, ultra, max).');
795
+ err._enhanced = true;
796
+ throw err;
797
+ }
798
+ }
799
+ };
800
+
748
801
  // Parse command line arguments - now needs yargs and hideBin passed in
749
802
  export const parseArguments = async (yargs = getLinoYargsFactory(), hideBinFn = hideBin) => {
750
803
  const rawArgs = normalizeCliArgs(hideBinFn(process.argv));
@@ -804,6 +857,14 @@ export const parseArguments = async (yargs = getLinoYargsFactory(), hideBinFn =
804
857
  }
805
858
  }
806
859
  } catch (error) {
860
+ // Issue #2041: the yargs `.check()` for --think (added to createYargsConfig so
861
+ // non-CLI consumers like the Telegram bot reject invalid values) throws an
862
+ // already-enhanced error. Propagate it verbatim instead of swallowing it into
863
+ // `error.argv`, otherwise the CLI would silently drop the invalid --think and
864
+ // crash later during normalization.
865
+ if (error && error._enhanced && !(error.message && /Unknown argument/.test(error.message))) {
866
+ throw error;
867
+ }
807
868
  // Yargs throws errors for validation issues
808
869
  // If the error is about unknown arguments (strict mode), enhance it with suggestions
809
870
  // Check if this error has already been enhanced to avoid re-processing
@@ -852,6 +913,12 @@ export const parseArguments = async (yargs = getLinoYargsFactory(), hideBinFn =
852
913
  argv.think = undefined;
853
914
  }
854
915
 
916
+ // Issue #2038: normalize the --think value into a single canonical vocabulary
917
+ // (off synonyms, minimal, adaptive, percentages/fractions/0|1) and fail fast on
918
+ // an unsupported `adaptive` request. Shared by solve and hive so both commands
919
+ // behave identically.
920
+ normalizeAndValidateThink(argv);
921
+
855
922
  // --plan flag expansion (Issue #1223)
856
923
  // When --plan is set, it acts as a shortcut for --plan-model opus --worker-model sonnet
857
924
  // Explicit --plan-model and --model/--worker-model values take precedence
@@ -0,0 +1,140 @@
1
+ #!/usr/bin/env node
2
+
3
+ /**
4
+ * Issue #2038: Canonical normalization of the `--think` option.
5
+ *
6
+ * The `--think` flag historically accepted only a small fixed set of keyword
7
+ * levels (off/low/medium/high/xhigh/ultra/max). Issue #2038 requires a much
8
+ * richer and more forgiving surface, mapped consistently across Claude and
9
+ * Codex:
10
+ *
11
+ * 1. A family of synonyms that all mean "off" (thinking disabled, or the
12
+ * closest safe equivalent when a model cannot truly disable thinking):
13
+ * `off`, `disable`, `disabled`, `no`, `none`, `false`.
14
+ * 2. A `minimal` level (below `low`) mapped to the lowest real reasoning
15
+ * effort each tool supports (Codex `minimal`, Claude lowest effort).
16
+ * 3. An explicit `adaptive` level that requests provider-managed adaptive
17
+ * thinking and MUST fail fast for models/tools that do not support it.
18
+ * 4. Numeric intensities so users can dial precision:
19
+ * - percentages `0%` .. `100%`
20
+ * - fractions `0.0` .. `1.0`
21
+ * - the integers `0` and `1`
22
+ * `0`/`0%`/`0.0` == off and `1`/`100%`/`1.0` == max.
23
+ *
24
+ * `normalizeThinkLevel()` folds every accepted spelling into one canonical
25
+ * level so the rest of the codebase keeps operating on a single vocabulary.
26
+ */
27
+
28
+ // Canonical think levels in ascending intensity order. `adaptive` is a distinct
29
+ // mode (provider-managed), not a point on the numeric intensity scale, so it is
30
+ // listed separately and never produced by numeric coercion.
31
+ export const CANONICAL_THINK_LEVELS = Object.freeze(['off', 'minimal', 'low', 'medium', 'high', 'xhigh', 'ultra', 'max']);
32
+
33
+ export const ADAPTIVE_THINK_LEVEL = 'adaptive';
34
+
35
+ // Keyword synonyms → canonical level. All the "off" spellings are synonyms per
36
+ // the issue title, and an omitted `--think` is treated as `off` elsewhere.
37
+ const THINK_LEVEL_SYNONYMS = Object.freeze({
38
+ off: 'off',
39
+ disable: 'off',
40
+ disabled: 'off',
41
+ no: 'off',
42
+ none: 'off',
43
+ false: 'off',
44
+ min: 'minimal',
45
+ minimal: 'minimal',
46
+ low: 'low',
47
+ medium: 'medium',
48
+ med: 'medium',
49
+ high: 'high',
50
+ xhigh: 'xhigh',
51
+ 'x-high': 'xhigh',
52
+ ultra: 'ultra',
53
+ max: 'max',
54
+ maximum: 'max',
55
+ full: 'max',
56
+ adaptive: 'adaptive',
57
+ auto: 'adaptive',
58
+ });
59
+
60
+ /**
61
+ * Map a fraction in [0, 1] to a canonical intensity level.
62
+ * 0 → off, 1 → max, with evenly spaced bands in between. Never returns the
63
+ * out-of-band `ultra` (multi-agent) or `adaptive` modes.
64
+ * @param {number} fraction
65
+ * @returns {string}
66
+ */
67
+ export const fractionToThinkLevel = fraction => {
68
+ if (!Number.isFinite(fraction) || fraction <= 0) return 'off';
69
+ if (fraction < 0.2) return 'minimal';
70
+ if (fraction < 0.4) return 'low';
71
+ if (fraction < 0.6) return 'medium';
72
+ if (fraction < 0.8) return 'high';
73
+ if (fraction < 1) return 'xhigh';
74
+ return 'max';
75
+ };
76
+
77
+ /**
78
+ * Parse a numeric think value (percentage, fraction, or 0/1 integer) into a
79
+ * fraction in [0, 1], or return null when the value is not numeric.
80
+ * @param {string} raw
81
+ * @returns {number|null}
82
+ */
83
+ export const parseNumericThinkValue = raw => {
84
+ const text = String(raw).trim();
85
+ const percentMatch = /^([0-9]+(?:\.[0-9]+)?)\s*%$/.exec(text);
86
+ if (percentMatch) {
87
+ return Math.min(1, Math.max(0, Number(percentMatch[1]) / 100));
88
+ }
89
+ if (/^[0-9]+(?:\.[0-9]+)?$/.exec(text)) {
90
+ const num = Number(text);
91
+ // Bare integers greater than 1 are treated as a 0..100 style percentage so
92
+ // `--think 50` behaves like `--think 50%`; 0 and 1 stay canonical fraction
93
+ // endpoints (off and max).
94
+ if (num > 1) return Math.min(1, num / 100);
95
+ return Math.min(1, Math.max(0, num));
96
+ }
97
+ return null;
98
+ };
99
+
100
+ /**
101
+ * Normalize any accepted `--think` spelling into a canonical level.
102
+ * @param {string|number|undefined|null} raw
103
+ * @returns {string|undefined} canonical level, ADAPTIVE_THINK_LEVEL, or
104
+ * undefined when input is empty. Throws for unrecognized values.
105
+ */
106
+ export const normalizeThinkLevel = raw => {
107
+ if (raw === undefined || raw === null || raw === '') return undefined;
108
+
109
+ // Already-canonical (e.g. re-normalization) short circuit.
110
+ if (typeof raw === 'string') {
111
+ const lowered = raw.trim().toLowerCase();
112
+ if (lowered === '') return undefined;
113
+
114
+ if (Object.prototype.hasOwnProperty.call(THINK_LEVEL_SYNONYMS, lowered)) {
115
+ return THINK_LEVEL_SYNONYMS[lowered];
116
+ }
117
+
118
+ const fraction = parseNumericThinkValue(lowered);
119
+ if (fraction !== null) {
120
+ return fractionToThinkLevel(fraction);
121
+ }
122
+
123
+ throw new Error(`Invalid --think value: "${raw}". Use a level (${CANONICAL_THINK_LEVELS.join(', ')}, adaptive), ` + `an off synonym (off/disable/disabled/no/none), a percentage (0%..100%), or a fraction (0.0..1.0).`);
124
+ }
125
+
126
+ if (typeof raw === 'number') {
127
+ const fraction = raw > 1 ? Math.min(1, raw / 100) : Math.min(1, Math.max(0, raw));
128
+ return fractionToThinkLevel(fraction);
129
+ }
130
+
131
+ throw new Error(`Invalid --think value: "${raw}".`);
132
+ };
133
+
134
+ export default {
135
+ CANONICAL_THINK_LEVELS,
136
+ ADAPTIVE_THINK_LEVEL,
137
+ normalizeThinkLevel,
138
+ fractionToThinkLevel,
139
+ parseNumericThinkValue,
140
+ };
@@ -4,6 +4,7 @@ import { supportsEffortLevel, supportsThinkingBudget } from './config.lib.mjs';
4
4
  import { defaultModels, mapModelForTool } from './models/index.mjs';
5
5
 
6
6
  export const THINK_PROMPT_MESSAGES = Object.freeze({
7
+ minimal: 'Think.',
7
8
  low: 'Think.',
8
9
  medium: 'Think hard.',
9
10
  high: 'Think harder.',