converse-mcp-server 3.4.0 → 3.5.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -267,7 +267,7 @@ Reasoning maps to `high` (enabled) or `none` (disabled) on Medium 3.5 and Small;
267
267
  - **deepseek-v4-pro** (default; aliases: `deepseek`, `deepseek-pro`): Flagship MoE model with thinking mode (1M context, 384K max output, text-only)
268
268
  - **deepseek-v4-flash** (alias: `deepseek-flash`): Faster, lower-cost V4 tier with thinking mode (1M context, 384K max output, text-only)
269
269
 
270
- Thinking mode maps `reasoning_effort` to `none` (off), `high` (enabled levels), or `max`.
270
+ Thinking mode maps `reasoning_effort` to `none` (off), `high` (enabled levels up to `high`), or `max` (`xhigh` and `max`).
271
271
 
272
272
  ### OpenRouter Models
273
273
 
package/docs/API.md CHANGED
@@ -84,8 +84,8 @@ MCP_TRANSPORT=stdio npm start
84
84
  },
85
85
  "reasoning_effort": {
86
86
  "type": "string",
87
- "enum": ["none", "minimal", "low", "medium", "high", "max"],
88
- "description": "Reasoning depth for thinking models. 'none' (fastest, GPT-5.1+ only), 'minimal', 'low', 'medium' (balanced), 'high', 'max'. Default: 'medium'."
87
+ "enum": ["none", "minimal", "low", "medium", "high", "xhigh", "max"],
88
+ "description": "Reasoning depth for thinking models, weakest to strongest. Passed through by name when the model accepts it, otherwise clamped to the nearest tier it does. Default: 'medium'."
89
89
  },
90
90
  "async": {
91
91
  "type": "boolean",
@@ -404,7 +404,7 @@ Provide models as plain name strings in the `models` array. Bare names and alias
404
404
  |-------|---------|---------|-------|
405
405
  | `grok-4.5` | `grok`, `grok-4.5-latest`, `grok-build-latest` | 500K | Flagship: image input, reasoning content, native web/X search via Agent Tools |
406
406
 
407
- `reasoning_effort` maps to Grok's `low`/`medium`/`high`; Grok 4.5 always reasons and cannot be disabled. Web search is attached automatically and the model decides whether to use it.
407
+ `reasoning_effort` clamps into Grok's `low`/`medium`/`high` (`xhigh` and `max` become `high`); Grok 4.5 always reasons and cannot be disabled. Web search is attached automatically and the model decides whether to use it.
408
408
 
409
409
  ### Anthropic Models (API-based)
410
410
 
@@ -417,7 +417,7 @@ Provide models as plain name strings in the `models` array. Bare names and alias
417
417
  | `claude-sonnet-4-6` | `sonnet`, `sonnet-4.6` | 200K (1M beta) | 64K | Best speed/intelligence balance, adaptive thinking |
418
418
  | `claude-haiku-4-5-20251001` | `haiku`, `haiku-4.5` | 200K | 64K | Fast and intelligent |
419
419
 
420
- Models with adaptive thinking control depth via `reasoning_effort`, which maps to Anthropic's `effort` parameter. System prompts are automatically cached for 1 hour; cache stats appear in response metadata as `cache_creation_input_tokens` / `cache_read_input_tokens`.
420
+ Models with adaptive thinking control depth via `reasoning_effort`, which is passed by name to Anthropic's `effort` parameter and clamped to what each model accepts: Fable 5, Opus 5, Opus 4.8, and Opus 4.7 take `low`–`max`; Opus 4.6 and Sonnet 4.6 lack `xhigh` (it becomes `max`); Opus 4.5 tops out at `high`. `none` and `minimal` become `low` everywhere. System prompts are automatically cached for 1 hour; cache stats appear in response metadata as `cache_creation_input_tokens` / `cache_read_input_tokens`.
421
421
 
422
422
  ### Mistral Models
423
423
 
@@ -436,7 +436,7 @@ Models with adaptive thinking control depth via `reasoning_effort`, which maps t
436
436
  | `deepseek-v4-pro` | `deepseek`, `deepseek-pro` | 1M | 384K | Flagship MoE, thinking mode, text-only |
437
437
  | `deepseek-v4-flash` | `deepseek-flash` | 1M | 384K | Faster, lower-cost V4 tier, text-only |
438
438
 
439
- `reasoning_effort`: `none` disables thinking; enabled levels use `high`; `max` uses `max`.
439
+ `reasoning_effort`: `none` disables thinking; enabled levels up to `high` use `high`; `xhigh` and `max` use `max`.
440
440
 
441
441
  ### OpenRouter Models
442
442
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "converse-mcp-server",
3
- "version": "3.4.0",
3
+ "version": "3.5.0",
4
4
  "description": "Converse MCP Server - Converse with other LLMs with chat and consensus tools",
5
5
  "type": "module",
6
6
  "main": "src/index.js",
@@ -295,11 +295,12 @@ function generateConfigurationTips(tools) {
295
295
  if (effortSchema.enum) {
296
296
  for (const value of effortSchema.enum) {
297
297
  const descriptions = {
298
- none: 'No reasoning, fastest response (GPT-5.1+ only)',
298
+ none: 'Reasoning off, fastest response (where the model allows it)',
299
299
  minimal: 'Quick responses with minimal reasoning',
300
300
  low: 'Light analysis, simple problems',
301
301
  medium: 'Balanced reasoning (default)',
302
302
  high: 'Deep analysis, complex problems',
303
+ xhigh: 'Very deep analysis, one step below the ceiling',
303
304
  max: 'Maximum reasoning capability',
304
305
  };
305
306
  output += `- **${value}**: ${descriptions[value] || value}\n`;
@@ -10,6 +10,14 @@
10
10
 
11
11
  import { debugLog, debugError } from '../utils/console.js';
12
12
  import { ProviderError, ErrorCodes, StopReasons } from './interface.js';
13
+ import { clampReasoningEffort } from '../utils/reasoningEffort.js';
14
+
15
+ // Values each model accepts for the `effort` parameter. Anthropic cannot
16
+ // switch reasoning off via effort, so 'none' and 'minimal' clamp up to 'low'.
17
+ // Not every model that supports 'max' supports 'xhigh'.
18
+ const EFFORT_TIERS_FULL = ['low', 'medium', 'high', 'xhigh', 'max'];
19
+ const EFFORT_TIERS_NO_XHIGH = ['low', 'medium', 'high', 'max'];
20
+ const EFFORT_TIERS_LEGACY = ['low', 'medium', 'high'];
13
21
 
14
22
  // Define supported Claude models with their capabilities
15
23
  const SUPPORTED_MODELS = {
@@ -26,6 +34,7 @@ const SUPPORTED_MODELS = {
26
34
  timeout: 1800000,
27
35
  supportsEffort: true,
28
36
  effortGA: true,
37
+ effortTiers: EFFORT_TIERS_FULL,
29
38
  supportsCompaction: true,
30
39
  description:
31
40
  'Claude Fable 5 - Most capable model for the most demanding reasoning and long-horizon agentic work',
@@ -51,6 +60,7 @@ const SUPPORTED_MODELS = {
51
60
  timeout: 1800000,
52
61
  supportsEffort: true,
53
62
  effortGA: true,
63
+ effortTiers: EFFORT_TIERS_FULL,
54
64
  supportsCompaction: true,
55
65
  description:
56
66
  'Claude Opus 5 - Most capable Opus for complex agentic coding and deep reasoning',
@@ -79,6 +89,7 @@ const SUPPORTED_MODELS = {
79
89
  timeout: 1800000,
80
90
  supportsEffort: true,
81
91
  effortGA: true,
92
+ effortTiers: EFFORT_TIERS_FULL,
82
93
  supports1MContext: true,
83
94
  supportsCompaction: true,
84
95
  description:
@@ -109,6 +120,7 @@ const SUPPORTED_MODELS = {
109
120
  timeout: 1800000,
110
121
  supportsEffort: true,
111
122
  effortGA: true,
123
+ effortTiers: EFFORT_TIERS_FULL,
112
124
  supports1MContext: true,
113
125
  supportsCompaction: true,
114
126
  description:
@@ -139,6 +151,7 @@ const SUPPORTED_MODELS = {
139
151
  timeout: 1800000,
140
152
  supportsEffort: true,
141
153
  effortGA: true,
154
+ effortTiers: EFFORT_TIERS_NO_XHIGH,
142
155
  supports1MContext: true,
143
156
  supportsCompaction: true,
144
157
  description:
@@ -167,6 +180,7 @@ const SUPPORTED_MODELS = {
167
180
  maxThinkingTokens: 64000,
168
181
  timeout: 900000,
169
182
  supportsEffort: true, // Opus 4.5 effort parameter (requires beta header)
183
+ effortTiers: EFFORT_TIERS_LEGACY,
170
184
  description:
171
185
  'Claude Opus 4.5 - Previous most intelligent model combining maximum capability with practical performance',
172
186
  aliases: [
@@ -223,6 +237,7 @@ const SUPPORTED_MODELS = {
223
237
  timeout: 900000,
224
238
  supportsEffort: true,
225
239
  effortGA: true, // Effort is generally available, no beta header required
240
+ effortTiers: EFFORT_TIERS_NO_XHIGH,
226
241
  supports1MContext: true, // Beta 1M context support
227
242
  supportsCompaction: true, // Beta server-side context compaction
228
243
  description:
@@ -311,22 +326,10 @@ const THINKING_BUDGETS = {
311
326
  low: 0.15, // 15% of max thinking tokens
312
327
  medium: 0.33, // 33% of max thinking tokens (default)
313
328
  high: 0.67, // 67% of max thinking tokens
329
+ xhigh: 0.85, // 85% of max thinking tokens
314
330
  max: 1.0, // 100% of max thinking tokens
315
331
  };
316
332
 
317
- /**
318
- * Effort parameter mapping for Opus 5, Opus 4.8, Opus 4.7, Opus 4.6, Sonnet 4.6, and Opus 4.5
319
- * Maps reasoning_effort values to Anthropic's effort parameter values
320
- */
321
- const EFFORT_MAP = {
322
- none: 'low',
323
- minimal: 'low',
324
- low: 'medium',
325
- medium: 'high',
326
- high: 'xhigh',
327
- max: 'max',
328
- };
329
-
330
333
  /**
331
334
  * Custom error class for Anthropic provider errors
332
335
  */
@@ -716,16 +719,17 @@ export const anthropicProvider = {
716
719
 
717
720
  // Add effort parameter for models that support it (uses output_config)
718
721
  if (modelConfig.supportsEffort && reasoning_effort) {
719
- const effortValue = EFFORT_MAP[reasoning_effort];
720
- if (effortValue) {
721
- requestPayload.output_config = {
722
- ...requestPayload.output_config,
723
- effort: effortValue,
724
- };
725
- debugLog(
726
- `[Anthropic] Effort parameter set to "${effortValue}" for ${resolvedModel} (from reasoning_effort: ${reasoning_effort})`,
727
- );
728
- }
722
+ const effortValue = clampReasoningEffort(
723
+ reasoning_effort,
724
+ modelConfig.effortTiers,
725
+ );
726
+ requestPayload.output_config = {
727
+ ...requestPayload.output_config,
728
+ effort: effortValue,
729
+ };
730
+ debugLog(
731
+ `[Anthropic] Effort parameter set to "${effortValue}" for ${resolvedModel} (from reasoning_effort: ${reasoning_effort})`,
732
+ );
729
733
  }
730
734
 
731
735
  // If streaming is requested and model doesn't support it, fall back to non-streaming
@@ -16,17 +16,10 @@
16
16
  import { debugLog, debugError } from '../utils/console.js';
17
17
  import { ProviderError, ErrorCodes, StopReasons } from './interface.js';
18
18
  import { normalizeExtendedPath } from '../utils/pathUtils.js';
19
-
20
- /**
21
- * Every Codex reasoning tier, weakest to strongest. Used to clamp a requested
22
- * tier onto the set a given backend model accepts.
23
- *
24
- * Codex also exposes 'ultra' above 'max', but that tier turns on automatic
25
- * sub-agent delegation — a change in how the run executes, not just how deep
26
- * it reasons — so nothing at the tool level maps to it and it is kept off the
27
- * ladder so the clamp can never select it.
28
- */
29
- const EFFORT_LADDER = ['none', 'minimal', 'low', 'medium', 'high', 'xhigh', 'max'];
19
+ import {
20
+ EFFORT_LADDER,
21
+ clampReasoningEffort,
22
+ } from '../utils/reasoningEffort.js';
30
23
 
31
24
  /**
32
25
  * Backend models Codex can run, keyed by the slug passed to the CLI as
@@ -35,6 +28,11 @@ const EFFORT_LADDER = ['none', 'minimal', 'low', 'medium', 'high', 'xhigh', 'max
35
28
  * are: 'low', 'medium', 'high', 'xhigh', and 'max'"). The SDK's
36
29
  * ModelReasoningEffort type is the union across models, so the backend is the
37
30
  * authority and requests are clamped per model.
31
+ *
32
+ * Codex also exposes 'ultra' above 'max', but that tier turns on automatic
33
+ * sub-agent delegation — a change in how the run executes, not just how deep
34
+ * it reasons — so it is deliberately absent from every supportedEfforts list
35
+ * and nothing at the tool level can select it.
38
36
  */
39
37
  const CODEX_BACKEND_MODELS = {
40
38
  'gpt-6-astra': {
@@ -307,50 +305,6 @@ async function getThreadIdFromContinuation(
307
305
  }
308
306
  }
309
307
 
310
- /**
311
- * Tool-level reasoning_effort values translated to their Codex equivalent.
312
- * Tool enum: 'none' | 'minimal' | 'low' | 'medium' | 'high' | 'max'
313
- */
314
- const EFFORT_ALIASES = {
315
- none: 'none',
316
- minimal: 'minimal',
317
- low: 'low',
318
- medium: 'medium',
319
- high: 'high',
320
- max: 'max',
321
- };
322
-
323
- /**
324
- * Map a tool-level reasoning_effort onto a tier the target model accepts.
325
- *
326
- * When the requested tier isn't in the model's supported set, the nearest
327
- * *stronger* tier wins: nudging 'minimal' up to 'low' keeps reasoning on,
328
- * where falling back to 'none' would silently switch it off.
329
- *
330
- * @param {string} effort - Tool-level reasoning_effort value
331
- * @param {string[]} [supported] - Tiers the model accepts
332
- * @returns {string} A tier from `supported`
333
- */
334
- export function mapReasoningEffort(effort, supported = EFFORT_LADDER) {
335
- const desired = EFFORT_ALIASES[effort] || 'medium';
336
- if (supported.includes(desired)) {
337
- return desired;
338
- }
339
-
340
- const rank = EFFORT_LADDER.indexOf(desired);
341
- const stronger = EFFORT_LADDER.slice(rank + 1).find((tier) =>
342
- supported.includes(tier),
343
- );
344
- if (stronger) {
345
- return stronger;
346
- }
347
-
348
- const weaker = EFFORT_LADDER.slice(0, rank)
349
- .reverse()
350
- .find((tier) => supported.includes(tier));
351
- return weaker || 'medium';
352
- }
353
-
354
308
  /**
355
309
  * Resolve a user-facing model name to its entry in SUPPORTED_MODELS.
356
310
  * @param {string} modelName
@@ -500,9 +454,9 @@ export const codexProvider = {
500
454
  if (reasoning_effort) {
501
455
  const supportedEfforts =
502
456
  getBackendModelConfig(backendModel)?.supportedEfforts || EFFORT_LADDER;
503
- const mappedEffort = mapReasoningEffort(reasoning_effort, supportedEfforts);
457
+ const mappedEffort = clampReasoningEffort(reasoning_effort, supportedEfforts);
504
458
  threadOptions.modelReasoningEffort = mappedEffort;
505
- if (mappedEffort !== EFFORT_ALIASES[reasoning_effort]) {
459
+ if (mappedEffort !== reasoning_effort) {
506
460
  debugLog(
507
461
  `[Codex] reasoning_effort "${reasoning_effort}" not supported by ${backendModel} — using "${mappedEffort}"`,
508
462
  );
@@ -17,6 +17,7 @@ import { delimiter, dirname, join } from 'node:path';
17
17
  import { fileURLToPath } from 'node:url';
18
18
  import { debugLog, debugError } from '../utils/console.js';
19
19
  import { ProviderError, ErrorCodes, StopReasons } from './interface.js';
20
+ import { clampReasoningEffort } from '../utils/reasoningEffort.js';
20
21
 
21
22
  const SUPPORTED_MODELS = {
22
23
  copilot: {
@@ -517,20 +518,14 @@ function resolveSessionModel(requestModel, config) {
517
518
  * - session.error → { data: { errorType, message } }
518
519
  */
519
520
  /**
520
- * Map tool-level reasoning_effort values to Copilot SDK's ReasoningEffort.
521
- * Tool enum: 'none' | 'minimal' | 'low' | 'medium' | 'high' | 'max'
522
- * SDK enum: 'low' | 'medium' | 'high' | 'xhigh'
521
+ * Values the Copilot SDK's ReasoningEffort type accepts. The SDK tops out at
522
+ * 'xhigh' and cannot switch reasoning off, so 'max' clamps down and
523
+ * 'none'/'minimal' clamp up.
523
524
  */
525
+ const COPILOT_EFFORT_TIERS = ['low', 'medium', 'high', 'xhigh'];
526
+
524
527
  function mapReasoningEffort(effort) {
525
- const mapping = {
526
- none: 'low',
527
- minimal: 'low',
528
- low: 'low',
529
- medium: 'medium',
530
- high: 'high',
531
- max: 'xhigh',
532
- };
533
- return mapping[effort] || undefined;
528
+ return clampReasoningEffort(effort, COPILOT_EFFORT_TIERS);
534
529
  }
535
530
 
536
531
  /**
@@ -7,6 +7,7 @@
7
7
 
8
8
  import { createOpenAICompatibleProvider } from './openai-compatible.js';
9
9
  import { debugLog } from '../utils/console.js';
10
+ import { clampReasoningEffort } from '../utils/reasoningEffort.js';
10
11
 
11
12
  // Define supported DeepSeek models with their capabilities.
12
13
  // V4 unified the catalog: both tiers share a 1M context window and a 384K output
@@ -65,13 +66,19 @@ function validateApiKey(apiKey) {
65
66
  return apiKey.length >= 32;
66
67
  }
67
68
 
69
+ /**
70
+ * Values DeepSeek's `reasoning_effort` field accepts once thinking is on.
71
+ * There is no lower documented tier, so every enabled level below high clamps
72
+ * up to "high" (the default `medium` runs thinking-on rather than silently
73
+ * disabling it) and `xhigh` clamps up to "max".
74
+ */
75
+ const DEEPSEEK_EFFORT_TIERS = ['high', 'max'];
76
+
68
77
  /**
69
78
  * Map a Converse reasoning_effort level to DeepSeek's thinking-mode request
70
- * fields. DeepSeek exposes only two independent controls: a `thinking` toggle
71
- * ({type:"enabled"|"disabled"}) and a `reasoning_effort` field that accepts
72
- * ONLY "high" or "max". Every enabled level below max maps to "high" (there is
73
- * no lower documented tier), preserving enabled-reasoning intent so the default
74
- * `medium` runs thinking-on rather than silently disabling it.
79
+ * fields. DeepSeek exposes two independent controls: a `thinking` toggle
80
+ * ({type:"enabled"|"disabled"}) and the `reasoning_effort` tier. Only `none`
81
+ * flips the toggle off.
75
82
  */
76
83
  function applyReasoning(requestPayload, reasoningEffort) {
77
84
  if (reasoningEffort === 'none') {
@@ -81,7 +88,10 @@ function applyReasoning(requestPayload, reasoningEffort) {
81
88
  }
82
89
 
83
90
  requestPayload.thinking = { type: 'enabled' };
84
- requestPayload.reasoning_effort = reasoningEffort === 'max' ? 'max' : 'high';
91
+ requestPayload.reasoning_effort = clampReasoningEffort(
92
+ reasoningEffort,
93
+ DEEPSEEK_EFFORT_TIERS,
94
+ );
85
95
  }
86
96
 
87
97
  /**
@@ -31,6 +31,7 @@ import { join, delimiter } from 'node:path';
31
31
  import { randomUUID } from 'node:crypto';
32
32
  import { debugLog, debugError } from '../utils/console.js';
33
33
  import { ProviderError, ErrorCodes, StopReasons } from './interface.js';
34
+ import { clampReasoningEffort } from '../utils/reasoningEffort.js';
34
35
 
35
36
  // Prompts at or below this length pass directly as the -p argv value (fast
36
37
  // path). Larger prompts are written to a file and -p carries a bootstrap
@@ -174,32 +175,25 @@ export function findAgyBinary() {
174
175
  return _cachedAgyPath;
175
176
  }
176
177
 
178
+ // agy variant tiers per model base. Flash offers Low/Medium/High; Pro has no
179
+ // Medium variant.
180
+ const FLASH_EFFORT_TIERS = ['low', 'medium', 'high'];
181
+ const PRO_EFFORT_TIERS = ['low', 'high'];
182
+
177
183
  /**
178
184
  * Map a reasoning_effort value to the agy parenthesized variant suffix.
179
- * Flash supports Low/Medium/High; Pro supports Low/High (no Medium).
185
+ * Unset effort selects High, agy's own default.
180
186
  * @param {string} base - agy model base ('Gemini 3.8 Flash' / 'Gemini 3.1 Pro')
181
187
  * @param {string} [reasoningEffort]
182
188
  * @returns {string} e.g. '(Low)', '(Medium)', '(High)'
183
189
  */
184
190
  function effortSuffix(base, reasoningEffort) {
185
- const isPro = /pro/i.test(base);
186
- const effort = (reasoningEffort || '').toLowerCase();
187
-
188
- switch (effort) {
189
- case 'none':
190
- case 'minimal':
191
- case 'low':
192
- return '(Low)';
193
- case 'medium':
194
- // Pro has no Medium variant — fall back to High
195
- return isPro ? '(High)' : '(Medium)';
196
- case 'high':
197
- case 'max':
198
- return '(High)';
199
- default:
200
- // unset → High
191
+ if (!reasoningEffort) {
201
192
  return '(High)';
202
193
  }
194
+ const tiers = /pro/i.test(base) ? PRO_EFFORT_TIERS : FLASH_EFFORT_TIERS;
195
+ const tier = clampReasoningEffort(reasoningEffort.toLowerCase(), tiers);
196
+ return `(${tier[0].toUpperCase()}${tier.slice(1)})`;
203
197
  }
204
198
 
205
199
  /**
@@ -7,6 +7,7 @@
7
7
 
8
8
  import { GoogleGenAI } from '@google/genai';
9
9
  import { debugLog, debugError } from '../utils/console.js';
10
+ import { clampReasoningEffort } from '../utils/reasoningEffort.js';
10
11
 
11
12
  // Define supported Gemini models with their capabilities
12
13
  const SUPPORTED_MODELS = {
@@ -161,9 +162,14 @@ const THINKING_BUDGETS = {
161
162
  low: 0.08, // 8% of max - light reasoning tasks
162
163
  medium: 0.33, // 33% of max - balanced reasoning (default)
163
164
  high: 0.67, // 67% of max - complex analysis
165
+ xhigh: 0.85, // 85% of max - near-full budget
164
166
  max: 1.0, // 100% of max - full thinking budget
165
167
  };
166
168
 
169
+ // thinkingLevel values for models without an explicit thinkingLevels list
170
+ // (Gemini 3.0 Pro exposes only low/high).
171
+ const BINARY_THINKING_LEVELS = ['low', 'high'];
172
+
167
173
  /**
168
174
  * Custom error class for Google provider errors
169
175
  */
@@ -527,37 +533,13 @@ export const googleProvider = {
527
533
  // Add thinking configuration for models that support it
528
534
  if (modelConfig.supportsThinking && reasoning_effort) {
529
535
  if (modelConfig.thinkingMode === 'level') {
530
- let thinkingLevel;
531
- if (modelConfig.thinkingLevels) {
532
- // Model supports specific levels (e.g., Gemini 3.1 Pro: minimal/low/medium/high)
533
- const levelMap = {
534
- none: 'minimal',
535
- minimal: 'minimal',
536
- low: 'low',
537
- medium: 'medium',
538
- high: 'high',
539
- max: 'high',
540
- };
541
- thinkingLevel = levelMap[reasoning_effort] || 'high';
542
- if (!modelConfig.thinkingLevels.includes(thinkingLevel)) {
543
- // Clamp to the nearest supported level: a request below the
544
- // model's floor (e.g. minimal on 3.8 Flash) takes the lowest
545
- // level, anything else the highest.
546
- const rank = ['minimal', 'low', 'medium', 'high'];
547
- const [lowest] = modelConfig.thinkingLevels;
548
- thinkingLevel =
549
- rank.indexOf(thinkingLevel) < rank.indexOf(lowest)
550
- ? lowest
551
- : modelConfig.thinkingLevels[
552
- modelConfig.thinkingLevels.length - 1
553
- ];
554
- }
555
- } else {
556
- // Binary levels only (Gemini 3.0 Pro: low/high)
557
- thinkingLevel = ['minimal', 'low'].includes(reasoning_effort)
558
- ? 'low'
559
- : 'high';
560
- }
536
+ // Gemini 3.x thinkingLevel names are tool-level tiers, so the request
537
+ // clamps straight onto the levels the model lists (none → minimal or
538
+ // the floor, xhigh/max the ceiling).
539
+ const thinkingLevel = clampReasoningEffort(
540
+ reasoning_effort,
541
+ modelConfig.thinkingLevels || BINARY_THINKING_LEVELS,
542
+ );
561
543
  generationConfig.thinkingConfig = { thinkingLevel };
562
544
  } else {
563
545
  // Gemini 2.5: Use thinking budget (token count)
@@ -7,6 +7,7 @@
7
7
 
8
8
  import { debugLog, debugError } from '../utils/console.js';
9
9
  import { ProviderError, ErrorCodes, StopReasons } from './interface.js';
10
+ import { clampReasoningEffort } from '../utils/reasoningEffort.js';
10
11
 
11
12
  // Define supported Mistral models with their capabilities.
12
13
  // Reasoning (`reasoning_effort`) is supported only on Medium 3.5 and Small 4;
@@ -61,19 +62,22 @@ const SUPPORTED_MODELS = {
61
62
  },
62
63
  };
63
64
 
65
+ /**
66
+ * Values Mistral documents for `reasoning_effort`. Every enabled level clamps
67
+ * to "high" so the default `medium` runs thinking-on; only `none` disables.
68
+ */
69
+ const MISTRAL_EFFORT_TIERS = ['none', 'high'];
70
+
64
71
  /**
65
72
  * Map a Converse reasoning_effort level to Mistral's documented request value.
66
- * Mistral documents only "high" and "none". Every enabled level maps to "high"
67
- * to preserve enabled-reasoning intent (Converse's default `medium` runs
68
- * thinking-on); only `none` disables. Returns null when reasoning must not be
69
- * forwarded — i.e. the model does not support it (Large 3) or is an unknown
70
- * pass-through ID (capability-gated).
73
+ * Returns null when reasoning must not be forwarded i.e. the model does not
74
+ * support it (Large 3) or is an unknown pass-through ID (capability-gated).
71
75
  */
72
76
  function resolveReasoningEffort(level, modelConfig) {
73
77
  if (!modelConfig?.supportsReasoning) {
74
78
  return null;
75
79
  }
76
- return level === 'none' ? 'none' : 'high';
80
+ return clampReasoningEffort(level, MISTRAL_EFFORT_TIERS);
77
81
  }
78
82
 
79
83
  /**
@@ -7,6 +7,21 @@
7
7
 
8
8
  import OpenAI from 'openai';
9
9
  import { debugLog, debugError } from '../utils/console.js';
10
+ import { clampReasoningEffort } from '../utils/reasoningEffort.js';
11
+
12
+ // Values each family accepts for reasoning effort, per the model pages at
13
+ // developers.openai.com/api/docs/models. GPT-5.6 is the only family with
14
+ // 'max'; the GPT-5.4 tier stops at 'xhigh'; the original GPT-5 minis kept
15
+ // 'minimal' but never gained 'xhigh'; the o-series predates both ends of the
16
+ // ladder; GPT-5.4 Pro starts at 'medium'. Models without a list are passed
17
+ // the requested value unchanged, except uncatalogued GPT-5 Pro snapshots,
18
+ // which are only known to accept 'high'.
19
+ const GPT_56_EFFORT_TIERS = ['none', 'low', 'medium', 'high', 'xhigh', 'max'];
20
+ const GPT_54_EFFORT_TIERS = ['none', 'low', 'medium', 'high', 'xhigh'];
21
+ const GPT_54_PRO_EFFORT_TIERS = ['medium', 'high', 'xhigh'];
22
+ const GPT_5_EFFORT_TIERS = ['minimal', 'low', 'medium', 'high'];
23
+ const O_SERIES_EFFORT_TIERS = ['low', 'medium', 'high'];
24
+ const PRO_PASSTHROUGH_EFFORT_TIERS = ['high'];
10
25
 
11
26
  // Define supported models with their capabilities
12
27
  const SUPPORTED_MODELS = {
@@ -19,7 +34,7 @@ const SUPPORTED_MODELS = {
19
34
  supportsImages: true,
20
35
  supportsWebSearch: true,
21
36
  supportsResponsesAPI: true,
22
- supportsNoneReasoningEffort: true,
37
+ supportedEfforts: GPT_56_EFFORT_TIERS,
23
38
  timeout: 10800000, // 3 hours
24
39
  description:
25
40
  'Flagship GPT-5.6 model (1M context, 128K output) - Frontier reasoning, coding, agentic workflows. Most token-efficient flagship',
@@ -44,7 +59,7 @@ const SUPPORTED_MODELS = {
44
59
  supportsImages: true,
45
60
  supportsWebSearch: true,
46
61
  supportsResponsesAPI: true,
47
- supportsNoneReasoningEffort: true,
62
+ supportedEfforts: GPT_56_EFFORT_TIERS,
48
63
  timeout: 5400000, // 90 minutes
49
64
  description:
50
65
  'Lower-cost GPT-5.6 (400K context, 128K output) - Performance competitive with GPT-5.5 at half the flagship price',
@@ -59,7 +74,7 @@ const SUPPORTED_MODELS = {
59
74
  supportsImages: true,
60
75
  supportsWebSearch: true,
61
76
  supportsResponsesAPI: true,
62
- supportsNoneReasoningEffort: true,
77
+ supportedEfforts: GPT_56_EFFORT_TIERS,
63
78
  timeout: 1800000, // 30 minutes
64
79
  description:
65
80
  'Fastest, most affordable GPT-5.6 (400K context, 128K output) - High-volume, latency-sensitive workloads',
@@ -74,7 +89,7 @@ const SUPPORTED_MODELS = {
74
89
  supportsImages: true,
75
90
  supportsWebSearch: true,
76
91
  supportsResponsesAPI: true,
77
- supportsNoneReasoningEffort: true,
92
+ supportedEfforts: GPT_54_EFFORT_TIERS,
78
93
  timeout: 10800000, // 3 hours
79
94
  description:
80
95
  'Latest flagship model (1M context, 128K output) - Superior reasoning, coding, agentic workflows, computer use. Most token-efficient reasoning model',
@@ -92,6 +107,7 @@ const SUPPORTED_MODELS = {
92
107
  supportsImages: true,
93
108
  supportsWebSearch: true,
94
109
  supportsResponsesAPI: true,
110
+ supportedEfforts: GPT_5_EFFORT_TIERS,
95
111
  timeout: 5400000, // 90 minutes
96
112
  description:
97
113
  'Faster, cost-efficient GPT-5 (400K context, 128K output) - Well-defined tasks, precise prompts',
@@ -106,6 +122,7 @@ const SUPPORTED_MODELS = {
106
122
  supportsImages: true,
107
123
  supportsWebSearch: false, // GPT-5-nano doesn't support web search
108
124
  supportsResponsesAPI: true,
125
+ supportedEfforts: GPT_5_EFFORT_TIERS,
109
126
  timeout: 1800000, // 30 minutes
110
127
  description:
111
128
  'Fastest, most cost-efficient GPT-5 (400K context, 128K output) - Summarization, classification',
@@ -120,6 +137,7 @@ const SUPPORTED_MODELS = {
120
137
  supportsImages: true,
121
138
  supportsWebSearch: true,
122
139
  supportsResponsesAPI: true,
140
+ supportedEfforts: GPT_54_EFFORT_TIERS,
123
141
  timeout: 5400000, // 90 minutes
124
142
  description:
125
143
  'Fast, efficient GPT-5.4 (400K context, 128K output) - Coding, subagents, computer use, tool use. 2x faster than GPT-5 mini',
@@ -139,6 +157,7 @@ const SUPPORTED_MODELS = {
139
157
  supportsImages: true,
140
158
  supportsWebSearch: false,
141
159
  supportsResponsesAPI: true,
160
+ supportedEfforts: GPT_54_EFFORT_TIERS,
142
161
  timeout: 1800000, // 30 minutes
143
162
  description:
144
163
  'Smallest, cheapest GPT-5.4 (400K context, 128K output) - Classification, data extraction, ranking, coding subagents',
@@ -159,6 +178,7 @@ const SUPPORTED_MODELS = {
159
178
  supportsWebSearch: true,
160
179
  supportsResponsesAPI: true,
161
180
  supportsDeepResearch: false,
181
+ supportedEfforts: GPT_54_PRO_EFFORT_TIERS,
162
182
  timeout: 10800000, // 180 minutes
163
183
  description:
164
184
  'Maximum performance reasoning model (1M context, 272K output) - Most complex tasks, extended compute time (EXPENSIVE)',
@@ -179,6 +199,7 @@ const SUPPORTED_MODELS = {
179
199
  supportsImages: true,
180
200
  supportsWebSearch: true,
181
201
  supportsResponsesAPI: true,
202
+ supportedEfforts: O_SERIES_EFFORT_TIERS,
182
203
  timeout: 1800000, // 30 minutes
183
204
  description:
184
205
  'Strong reasoning (200K context) - Logical problems, code generation, systematic analysis',
@@ -193,6 +214,7 @@ const SUPPORTED_MODELS = {
193
214
  supportsImages: true,
194
215
  supportsWebSearch: true,
195
216
  supportsResponsesAPI: true,
217
+ supportedEfforts: O_SERIES_EFFORT_TIERS,
196
218
  timeout: 10800000, // 180 minutes
197
219
  description:
198
220
  'Professional-grade reasoning (200K context) - EXTREMELY EXPENSIVE: Only for the most complex problems',
@@ -207,6 +229,7 @@ const SUPPORTED_MODELS = {
207
229
  supportsImages: true,
208
230
  supportsWebSearch: true,
209
231
  supportsResponsesAPI: true,
232
+ supportedEfforts: O_SERIES_EFFORT_TIERS,
210
233
  timeout: 540000, // 9 minutes
211
234
  description:
212
235
  'Latest reasoning model (200K context) - Optimized for shorter contexts, rapid reasoning',
@@ -311,17 +334,32 @@ function resolveModelName(modelName) {
311
334
  }
312
335
 
313
336
  /**
314
- * Resolve the reasoning effort actually sent to the API for a given model.
315
- * GPT-5 Pro models only accept 'high'. GPT-5.6 models dropped 'minimal'
316
- * (supported efforts: none, low, medium, high, xhigh, max), so 'minimal'
317
- * maps to the closest supported value.
337
+ * Whether a request to this model should carry a reasoning effort at all.
338
+ * Catalogued models say so through their declared tiers; pass-through IDs
339
+ * fall back to the o-series / GPT-5 name heuristic.
318
340
  */
319
- function resolveReasoningEffort(resolvedModel, reasoningEffort) {
341
+ function acceptsReasoningEffort(resolvedModel, modelConfig) {
342
+ if (modelConfig.supportedEfforts) {
343
+ return true;
344
+ }
345
+ return resolvedModel.startsWith('o3') || resolvedModel.startsWith('gpt-5');
346
+ }
347
+
348
+ /**
349
+ * Resolve the reasoning effort actually sent to the API. Catalogued models
350
+ * clamp onto their declared tiers. Pass-through IDs are matched by family
351
+ * where the tiers are known (GPT-5 Pro snapshots, GPT-5.6 snapshots) and
352
+ * otherwise keep the requested value.
353
+ */
354
+ function resolveReasoningEffort(resolvedModel, modelConfig, reasoningEffort) {
355
+ if (modelConfig.supportedEfforts) {
356
+ return clampReasoningEffort(reasoningEffort, modelConfig.supportedEfforts);
357
+ }
320
358
  if (resolvedModel.endsWith('-pro') && resolvedModel.startsWith('gpt-5')) {
321
- return 'high';
359
+ return clampReasoningEffort(reasoningEffort, PRO_PASSTHROUGH_EFFORT_TIERS);
322
360
  }
323
- if (resolvedModel.startsWith('gpt-5.6') && reasoningEffort === 'minimal') {
324
- return 'low';
361
+ if (resolvedModel.startsWith('gpt-5.6')) {
362
+ return clampReasoningEffort(reasoningEffort, GPT_56_EFFORT_TIERS);
325
363
  }
326
364
  return reasoningEffort;
327
365
  }
@@ -511,13 +549,13 @@ export const openaiProvider = {
511
549
  requestPayload.tools = [{ type: 'web_search_preview' }];
512
550
  }
513
551
 
514
- // Add reasoning effort for thinking models (o3 series and GPT-5 family)
515
- if (
516
- (resolvedModel.startsWith('o3') || resolvedModel.startsWith('gpt-5')) &&
517
- reasoning_effort
518
- ) {
552
+ if (acceptsReasoningEffort(resolvedModel, modelConfig) && reasoning_effort) {
519
553
  requestPayload.reasoning = {
520
- effort: resolveReasoningEffort(resolvedModel, reasoning_effort),
554
+ effort: resolveReasoningEffort(
555
+ resolvedModel,
556
+ modelConfig,
557
+ reasoning_effort,
558
+ ),
521
559
  summary: 'auto', // Enable reasoning summaries
522
560
  };
523
561
  }
@@ -531,13 +569,10 @@ export const openaiProvider = {
531
569
  ...cleanOptions,
532
570
  };
533
571
 
534
- // Add reasoning effort for thinking models (o3 series and GPT-5 family)
535
- if (
536
- (resolvedModel.startsWith('o3') || resolvedModel.startsWith('gpt-5')) &&
537
- reasoning_effort
538
- ) {
572
+ if (acceptsReasoningEffort(resolvedModel, modelConfig) && reasoning_effort) {
539
573
  requestPayload.reasoning_effort = resolveReasoningEffort(
540
574
  resolvedModel,
575
+ modelConfig,
541
576
  reasoning_effort,
542
577
  );
543
578
  }
@@ -26,6 +26,7 @@ import {
26
26
  lookupOpenRouterModel,
27
27
  DiscoveryStatus,
28
28
  } from './openrouter-discovery.js';
29
+ import { clampReasoningEffort } from '../utils/reasoningEffort.js';
29
30
 
30
31
  // Curated static catalog (verified live 2026-07-11). getSupportedModels() must
31
32
  // return exactly these 8 slugs even after a discovery call has run — dynamic
@@ -255,7 +256,7 @@ function extractReasoningText(details) {
255
256
  * 1. passthrough (openrouter/auto) → null (router decides)
256
257
  * 2. mandatory (kimi-k2.7-code) → { enabled: true } (cannot disable)
257
258
  * 3. effort-tiered (glm/deepseek) → clamp into supported_efforts
258
- * (max→xhigh, else→high; none→disabled)
259
+ * (nearest stronger tier; none→disabled)
259
260
  * 4. enable/disable-only (qwen/kimi) → { enabled: false } for none, else true
260
261
  * 5. unavailable metadata → null (omit conservatively)
261
262
  *
@@ -280,14 +281,9 @@ function buildOpenRouterReasoning(modelConfig, reasoningEffort) {
280
281
  reasoning.supported_efforts.length > 0
281
282
  ) {
282
283
  if (level === 'none') return { enabled: false };
283
- const wanted = level === 'max' ? 'xhigh' : 'high';
284
- const efforts = reasoning.supported_efforts;
285
- const effort = efforts.includes(wanted)
286
- ? wanted
287
- : efforts.includes('high')
288
- ? 'high'
289
- : efforts[0];
290
- return { effort };
284
+ return {
285
+ effort: clampReasoningEffort(level, reasoning.supported_efforts),
286
+ };
291
287
  }
292
288
 
293
289
  // Enable/disable-only.
@@ -14,6 +14,7 @@
14
14
 
15
15
  import OpenAI from 'openai';
16
16
  import { debugLog, debugError } from '../utils/console.js';
17
+ import { clampReasoningEffort } from '../utils/reasoningEffort.js';
17
18
 
18
19
  // Curated catalog: grok-4.5 only (verified live 2026-07-11 against
19
20
  // GET https://api.x.ai/v1/models/grok-4.5 — id, aliases, 500k context).
@@ -85,27 +86,15 @@ function resolveModelName(modelName) {
85
86
  }
86
87
 
87
88
  /**
88
- * Map Converse reasoning_effort to a value grok-4.5 accepts.
89
- *
90
- * grok-4.5 supports ONLY low/medium/high (default high) and cannot disable
91
- * reasoning there is no `none`/off value. Sending an unsupported value
92
- * (e.g. `none`/`minimal`/`max`) returns HTTP 400, so unsupported Converse
93
- * levels are clamped into {low, medium, high} rather than forwarded.
89
+ * Tiers grok-4.5 accepts (default high). It cannot disable reasoning — there
90
+ * is no `none`/off value — and sending an unsupported value (`none`,
91
+ * `minimal`, `xhigh`, `max`) returns HTTP 400, so requests are clamped into
92
+ * this set rather than forwarded.
94
93
  */
94
+ const GROK_EFFORT_TIERS = ['low', 'medium', 'high'];
95
+
95
96
  function resolveReasoningEffort(reasoningEffort) {
96
- switch (reasoningEffort) {
97
- case 'none':
98
- case 'minimal':
99
- case 'low':
100
- return 'low';
101
- case 'medium':
102
- return 'medium';
103
- case 'high':
104
- case 'max':
105
- return 'high';
106
- default:
107
- return 'high';
108
- }
97
+ return clampReasoningEffort(reasoningEffort, GROK_EFFORT_TIERS);
109
98
  }
110
99
 
111
100
  /**
package/src/tools/chat.js CHANGED
@@ -27,6 +27,7 @@ import { applyTokenLimit, getTokenLimit } from '../utils/tokenLimiter.js';
27
27
  import { validateAllPaths } from '../utils/fileValidator.js';
28
28
  import { SummarizationService } from '../services/summarizationService.js';
29
29
  import { exportConversation } from '../utils/conversationExporter.js';
30
+ import { EFFORT_LADDER } from '../utils/reasoningEffort.js';
30
31
  import {
31
32
  getDefaultModelForProvider,
32
33
  getProviderUnavailableMessage,
@@ -1187,9 +1188,9 @@ chatTool.inputSchema = {
1187
1188
  },
1188
1189
  reasoning_effort: {
1189
1190
  type: 'string',
1190
- enum: ['none', 'minimal', 'low', 'medium', 'high', 'max'],
1191
+ enum: EFFORT_LADDER,
1191
1192
  description:
1192
- 'Reasoning depth for thinking models. Examples: "none" (no reasoning, fastest - GPT-5.1+ only), "minimal", "low", "medium" (balanced), "high", "max". Default: "medium"',
1193
+ 'Reasoning depth for thinking models, weakest to strongest: "none" (reasoning off, where the model allows it), "minimal", "low", "medium" (balanced), "high", "xhigh", "max". Passed through by name when the model accepts it, otherwise clamped to the nearest tier it does. Default: "medium"',
1193
1194
  },
1194
1195
  async: {
1195
1196
  type: 'boolean',
@@ -0,0 +1,75 @@
1
+ /**
2
+ * Reasoning Effort Ladder
3
+ *
4
+ * The tool-level `reasoning_effort` vocabulary is the union of the tiers the
5
+ * providers expose, ordered weakest to strongest. Every provider declares the
6
+ * subset its model accepts and clamps the request onto it here, so the same
7
+ * word means the same tier everywhere and a provider never re-derives the
8
+ * ordering on its own.
9
+ */
10
+
11
+ /**
12
+ * Every tool-level reasoning_effort value, weakest first. Doubles as the
13
+ * `enum` of the chat tool's `reasoning_effort` parameter.
14
+ * @type {string[]}
15
+ */
16
+ export const EFFORT_LADDER = [
17
+ 'none',
18
+ 'minimal',
19
+ 'low',
20
+ 'medium',
21
+ 'high',
22
+ 'xhigh',
23
+ 'max',
24
+ ];
25
+
26
+ /**
27
+ * Effort applied when the caller leaves reasoning_effort unset or sends a
28
+ * value outside the ladder.
29
+ */
30
+ export const DEFAULT_EFFORT = 'medium';
31
+
32
+ /**
33
+ * Clamp a requested effort onto the tiers a model accepts.
34
+ *
35
+ * The requested tier wins when the model accepts it. Otherwise the nearest
36
+ * *stronger* accepted tier wins: nudging 'minimal' up to 'low' keeps reasoning
37
+ * on, where falling back to 'none' would silently switch it off. Only when
38
+ * nothing stronger exists does the nearest weaker tier apply (e.g. 'max' on a
39
+ * model that tops out at 'xhigh').
40
+ *
41
+ * Tiers in `supported` that are not on the ladder are ignored for ranking but
42
+ * the first of them is used as a last resort when nothing on the ladder
43
+ * matches, so a provider-declared list is never answered with a tier it
44
+ * doesn't contain. An empty list is invalid capability data and throws rather
45
+ * than inventing a tier the model may reject.
46
+ *
47
+ * @param {string|undefined} effort - Tool-level reasoning_effort value
48
+ * @param {string[]} supported - Tiers the model accepts (any order)
49
+ * @returns {string} A tier from `supported`
50
+ */
51
+ export function clampReasoningEffort(effort, supported) {
52
+ if (!Array.isArray(supported) || supported.length === 0) {
53
+ throw new TypeError(
54
+ 'clampReasoningEffort requires a non-empty list of supported tiers',
55
+ );
56
+ }
57
+
58
+ const desired = EFFORT_LADDER.includes(effort) ? effort : DEFAULT_EFFORT;
59
+ if (supported.includes(desired)) {
60
+ return desired;
61
+ }
62
+
63
+ const rank = EFFORT_LADDER.indexOf(desired);
64
+ const stronger = EFFORT_LADDER.slice(rank + 1).find((tier) =>
65
+ supported.includes(tier),
66
+ );
67
+ if (stronger) {
68
+ return stronger;
69
+ }
70
+
71
+ const weaker = EFFORT_LADDER.slice(0, rank)
72
+ .reverse()
73
+ .find((tier) => supported.includes(tier));
74
+ return weaker || supported[0];
75
+ }