codeep 3.4.1 → 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/dist/acp/commands.js +9 -4
- package/dist/acp/server.d.ts +13 -0
- package/dist/acp/server.js +46 -4
- package/dist/acp/serverHandlers.js +10 -10
- package/dist/api/index.js +6 -3
- package/dist/config/index.js +12 -4
- package/dist/config/providers.d.ts +48 -4
- package/dist/config/providers.js +325 -88
- package/dist/renderer/commands.js +17 -8
- package/dist/utils/agent.d.ts +16 -0
- package/dist/utils/agent.js +24 -3
- package/dist/utils/agentChat.js +22 -10
- package/dist/utils/personalities.js +8 -2
- package/dist/utils/taskPlanner.js +12 -4
- package/dist/utils/tokenTracker.d.ts +13 -5
- package/dist/utils/tokenTracker.js +163 -34
- package/dist/version.d.ts +1 -1
- package/dist/version.js +1 -1
- package/package.json +1 -1
package/dist/acp/commands.js
CHANGED
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
// responses (no TUI) suitable for streaming back via session/update.
|
|
5
5
|
import { config, getCurrentProvider, getModelsForCurrentProvider, setProvider, setApiKey, isConfigured, listSessionsWithInfo, startNewSession, loadSession, saveSession, initializeAsProject, isManuallyInitializedProject, setProjectPermission, hasWritePermission, hasReadPermission, sessionNameProblem, sessionNameTaken, } from '../config/index.js';
|
|
6
6
|
import { symlinkedCodeepNotice } from '../utils/projectPaths.js';
|
|
7
|
-
import { getProviderList, getProvider } from '../config/providers.js';
|
|
7
|
+
import { getProviderList, getProvider, replacementModelFor } from '../config/providers.js';
|
|
8
8
|
import { telemetryCommand } from '../commands/core/telemetry.js';
|
|
9
9
|
import { keysyncCommand } from '../commands/core/keysync.js';
|
|
10
10
|
import { getProjectContext } from '../utils/project.js';
|
|
@@ -1387,20 +1387,25 @@ Anything else the agent should know — edge cases, gotchas, things to double-ch
|
|
|
1387
1387
|
saveSession(session.codeepSessionId, session.history, session.workspaceRoot);
|
|
1388
1388
|
// If the checkpoint captured a different provider/model, switch back.
|
|
1389
1389
|
// configOptionsChanged signals the client to refresh its dropdowns.
|
|
1390
|
+
// A checkpoint predates any later retirement, so its model goes through
|
|
1391
|
+
// the same map as a stored config (`gpt-6-astra` comes back as
|
|
1392
|
+
// `gpt-6-sol`), looked up on the provider actually active after the switch.
|
|
1390
1393
|
let providerChanged = false;
|
|
1391
1394
|
if (cp.provider && cp.provider !== getCurrentProvider().id) {
|
|
1392
1395
|
setProvider(cp.provider);
|
|
1393
1396
|
providerChanged = true;
|
|
1394
1397
|
}
|
|
1395
|
-
|
|
1396
|
-
|
|
1398
|
+
const cpModel = cp.model && (replacementModelFor(config.get('provider'), cp.model) ?? cp.model);
|
|
1399
|
+
if (cpModel && cpModel !== config.get('model')) {
|
|
1400
|
+
config.set('model', cpModel);
|
|
1397
1401
|
providerChanged = true;
|
|
1398
1402
|
}
|
|
1403
|
+
const movedNote = cpModel !== cp.model ? ` (the checkpoint's \`${cp.model}\` is no longer offered)` : '';
|
|
1399
1404
|
const lines = [
|
|
1400
1405
|
`## Rewound to ${cp.name ? `**${cp.name}**` : `\`${cp.id}\``}`,
|
|
1401
1406
|
'',
|
|
1402
1407
|
`Restored ${cp.messages.length} message${cp.messages.length === 1 ? '' : 's'} (was ${replacedCount}).`,
|
|
1403
|
-
cp.provider &&
|
|
1408
|
+
cp.provider && cpModel ? `Provider: \`${cp.provider}\` · Model: \`${cpModel}\`${movedNote}` : '',
|
|
1404
1409
|
'',
|
|
1405
1410
|
buildRewindGitHint(cp),
|
|
1406
1411
|
].filter(Boolean);
|
package/dist/acp/server.d.ts
CHANGED
|
@@ -79,4 +79,17 @@ export declare function exitCodeFromWaitResult(result: unknown): number | null;
|
|
|
79
79
|
* Exported for unit testing (see server.command.test.ts).
|
|
80
80
|
*/
|
|
81
81
|
export declare function executeAcpCommand(command: string, args: string[], cwd: string, ctx: AcpCommandContext): Promise<AcpCommandOutcome>;
|
|
82
|
+
/**
|
|
83
|
+
* What to tell the user when a prompt fails on authentication, or null when
|
|
84
|
+
* the failure is about something else.
|
|
85
|
+
*
|
|
86
|
+
* Every 401 used to read "No API key configured", key or no key. Kimi Code
|
|
87
|
+
* answers 401 for its plan limits — no K3 on the plan, K3 past 256K on
|
|
88
|
+
* Plus/Moderato, High-Speed below Pro/Allegretto, an unknown model id
|
|
89
|
+
* (kimi.com/code/docs/en/kimi-code/error-reference.html) — so a subscriber with
|
|
90
|
+
* a perfectly good key was sent to /login when the fix was another model. With
|
|
91
|
+
* a key configured, a 401 now says what else it can mean and quotes the
|
|
92
|
+
* provider.
|
|
93
|
+
*/
|
|
94
|
+
export declare function authFailureNotice(err: Error, providerId: string, keyConfigured: boolean): string | null;
|
|
82
95
|
export declare function startAcpServer(transport?: StdioTransport): Promise<void>;
|
package/dist/acp/server.js
CHANGED
|
@@ -728,6 +728,43 @@ function persistSessionHistory(session) {
|
|
|
728
728
|
return;
|
|
729
729
|
saveSession(session.codeepSessionId, session.history, session.workspaceRoot);
|
|
730
730
|
}
|
|
731
|
+
/** The provider's own words from an `API error: 401 - …` message, if any. */
|
|
732
|
+
function providerErrorDetail(message) {
|
|
733
|
+
const body = message.replace(/^[\s\S]*?\b401\s*-\s*/, '').trim();
|
|
734
|
+
let detail = body;
|
|
735
|
+
try {
|
|
736
|
+
const json = JSON.parse(body);
|
|
737
|
+
detail = json?.error?.message ?? json?.message ?? body;
|
|
738
|
+
}
|
|
739
|
+
catch {
|
|
740
|
+
// Not JSON — the text is the detail.
|
|
741
|
+
}
|
|
742
|
+
const text = String(detail).trim();
|
|
743
|
+
return text.length > 300 ? `${text.slice(0, 300)}…` : text;
|
|
744
|
+
}
|
|
745
|
+
/**
|
|
746
|
+
* What to tell the user when a prompt fails on authentication, or null when
|
|
747
|
+
* the failure is about something else.
|
|
748
|
+
*
|
|
749
|
+
* Every 401 used to read "No API key configured", key or no key. Kimi Code
|
|
750
|
+
* answers 401 for its plan limits — no K3 on the plan, K3 past 256K on
|
|
751
|
+
* Plus/Moderato, High-Speed below Pro/Allegretto, an unknown model id
|
|
752
|
+
* (kimi.com/code/docs/en/kimi-code/error-reference.html) — so a subscriber with
|
|
753
|
+
* a perfectly good key was sent to /login when the fix was another model. With
|
|
754
|
+
* a key configured, a 401 now says what else it can mean and quotes the
|
|
755
|
+
* provider.
|
|
756
|
+
*/
|
|
757
|
+
export function authFailureNotice(err, providerId, keyConfigured) {
|
|
758
|
+
const is401 = err instanceof ApiError && err.status === 401;
|
|
759
|
+
if (!is401 && !err.message?.includes('API key'))
|
|
760
|
+
return null;
|
|
761
|
+
if (is401 && keyConfigured) {
|
|
762
|
+
const name = PROVIDERS[providerId]?.name ?? providerId;
|
|
763
|
+
const detail = providerErrorDetail(err.message ?? '');
|
|
764
|
+
return `❌ ${name} refused the request (401)${detail ? `: ${detail}` : ''}. A key is configured, so this is not a missing key: the key may be invalid or revoked, or your plan may not include this model or limit (Kimi Code, for one, answers 401 when a plan lacks K3, K3's 1M context or High-Speed). Pick another model with /model, or re-enter the key with /login.`;
|
|
765
|
+
}
|
|
766
|
+
return `❌ No API key configured. Use /login <provider> <key> or set the environment variable (e.g. ZAI_API_KEY, ANTHROPIC_API_KEY).`;
|
|
767
|
+
}
|
|
731
768
|
export function startAcpServer(transport = new StdioTransport()) {
|
|
732
769
|
// ACP sessionId → full AcpSession (includes history + codeep session tracking)
|
|
733
770
|
const sessions = new Map();
|
|
@@ -1367,6 +1404,11 @@ export function startAcpServer(transport = new StdioTransport()) {
|
|
|
1367
1404
|
},
|
|
1368
1405
|
});
|
|
1369
1406
|
};
|
|
1407
|
+
// Read when the prompt fails, not now: /login during the turn counts.
|
|
1408
|
+
const authNotice = (err) => {
|
|
1409
|
+
const providerId = config.get('provider');
|
|
1410
|
+
return authFailureNotice(err, providerId, Boolean(getApiKey(providerId)));
|
|
1411
|
+
};
|
|
1370
1412
|
// Ask the user through the client. A person answers this: wait as long
|
|
1371
1413
|
// as the dialog is open. Only cancelling the prompt stops the wait. No
|
|
1372
1414
|
// answer (error, cancelled prompt, a reply without an outcome) is null,
|
|
@@ -1674,8 +1716,8 @@ export function startAcpServer(transport = new StdioTransport()) {
|
|
|
1674
1716
|
}
|
|
1675
1717
|
transport.respond(msg.id, { stopReason: 'cancelled' });
|
|
1676
1718
|
}
|
|
1677
|
-
else if (
|
|
1678
|
-
sendChunk(
|
|
1719
|
+
else if (authNotice(err)) {
|
|
1720
|
+
sendChunk(authNotice(err));
|
|
1679
1721
|
transport.respond(msg.id, { stopReason: 'end_turn' });
|
|
1680
1722
|
}
|
|
1681
1723
|
else if (err instanceof ApiError && err.status >= 500) {
|
|
@@ -1696,8 +1738,8 @@ export function startAcpServer(transport = new StdioTransport()) {
|
|
|
1696
1738
|
if (err.name === 'AbortError' || abortController.signal.aborted) {
|
|
1697
1739
|
transport.respond(msg.id, { stopReason: 'cancelled' });
|
|
1698
1740
|
}
|
|
1699
|
-
else if (
|
|
1700
|
-
sendChunk(
|
|
1741
|
+
else if (authNotice(err)) {
|
|
1742
|
+
sendChunk(authNotice(err));
|
|
1701
1743
|
transport.respond(msg.id, { stopReason: 'end_turn' });
|
|
1702
1744
|
}
|
|
1703
1745
|
else if (err instanceof ApiError && err.status >= 500) {
|
|
@@ -17,6 +17,7 @@
|
|
|
17
17
|
// is "look up the session, mutate config or session state, acknowledge".
|
|
18
18
|
import { AGENT_MODES, buildConfigOptions } from './server.js';
|
|
19
19
|
import { config, setProvider, setApiKey, listSessionsWithInfo, deleteSession as deleteSessionFile, } from '../config/index.js';
|
|
20
|
+
import { replacementModelFor } from '../config/providers.js';
|
|
20
21
|
import { disposeSession as disposeMcpSession } from '../utils/mcpRegistry.js';
|
|
21
22
|
import { clearPendingPlan } from '../utils/planMode.js';
|
|
22
23
|
// ─── session/set_mode ─────────────────────────────────────────────────────────
|
|
@@ -99,17 +100,16 @@ export function handleSetConfigOption(msg, deps) {
|
|
|
99
100
|
*/
|
|
100
101
|
export function applyConfigOption(configId, value) {
|
|
101
102
|
if (configId === 'model' && typeof value === 'string') {
|
|
102
|
-
// value is "providerId/modelId" — split and switch both
|
|
103
|
+
// value is "providerId/modelId" — split and switch both. An editor setting
|
|
104
|
+
// pinned before a retirement (`openai/gpt-6-astra`) names an id the picker
|
|
105
|
+
// no longer has, so the model goes through the same map as a stored config,
|
|
106
|
+
// looked up on the provider actually active: setProvider refuses an unknown
|
|
107
|
+
// id and leaves the old one in place.
|
|
103
108
|
const slashIdx = value.indexOf('/');
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
config.set('model', modelId);
|
|
109
|
-
}
|
|
110
|
-
else {
|
|
111
|
-
config.set('model', value);
|
|
112
|
-
}
|
|
109
|
+
const modelId = slashIdx !== -1 ? value.slice(slashIdx + 1) : value;
|
|
110
|
+
if (slashIdx !== -1)
|
|
111
|
+
setProvider(value.slice(0, slashIdx)); // sets provider + defaultModel + protocol
|
|
112
|
+
config.set('model', replacementModelFor(config.get('provider'), modelId) ?? modelId);
|
|
113
113
|
}
|
|
114
114
|
else if (configId === 'provider' && typeof value === 'string') {
|
|
115
115
|
// Switch provider without specifying a model — picks the provider's
|
package/dist/api/index.js
CHANGED
|
@@ -3,7 +3,7 @@ import * as https from 'node:https';
|
|
|
3
3
|
import { config, getApiKey, resolveBaseUrl, describeUnsendableKey } from '../config/index.js';
|
|
4
4
|
import { withRetry, isNetworkError } from '../utils/retry.js';
|
|
5
5
|
import { checkApiRateLimit } from '../utils/ratelimit.js';
|
|
6
|
-
import { getProvider, getProviderBaseUrl, getProviderAuthHeader, usesMaxCompletionTokens, requiresDefaultTemperature, modelRejectsSamplingParams, reasoningParamsFor } from '../config/providers.js';
|
|
6
|
+
import { getProvider, getProviderBaseUrl, getProviderAuthHeader, usesMaxCompletionTokens, requiresDefaultTemperature, modelRejectsSamplingParams, reasoningParamsFor, minResponseTokensFor } from '../config/providers.js';
|
|
7
7
|
import { logApiRequest, logApiResponse } from '../utils/logger.js';
|
|
8
8
|
import { loadProjectIntelligence, generateContextFromIntelligence } from '../utils/projectIntelligence.js';
|
|
9
9
|
import { loadProjectRules } from '../utils/agent.js';
|
|
@@ -366,7 +366,8 @@ async function chatOpenAI(message, history, model, apiKey, onChunk, abortSignal)
|
|
|
366
366
|
const stream = Boolean(onChunk);
|
|
367
367
|
const timeout = config.get('apiTimeout');
|
|
368
368
|
const temperature = config.get('temperature');
|
|
369
|
-
|
|
369
|
+
// Never below the model's floor (Opus 5.5 on OpenRouter — see minResponseTokensFor).
|
|
370
|
+
const maxTokens = Math.max(config.get('maxTokens'), minResponseTokensFor(model, config.get('reasoningEffort')));
|
|
370
371
|
// Get provider-specific URL and auth. resolveBaseUrl applies user
|
|
371
372
|
// overrides: Ollama (ollamaUrl), Custom (customBaseUrl), and OpenAI
|
|
372
373
|
// (OPENAI_BASE_URL env) — so self-hosted / OpenAI-compatible endpoints work.
|
|
@@ -647,7 +648,9 @@ async function chatAnthropic(message, history, model, apiKey, onChunk, abortSign
|
|
|
647
648
|
const stream = Boolean(onChunk);
|
|
648
649
|
const timeout = config.get('apiTimeout');
|
|
649
650
|
const temperature = config.get('temperature');
|
|
650
|
-
|
|
651
|
+
// Never below the model's floor: Opus 5.5's always-on thinking spends the
|
|
652
|
+
// same limit as the answer (see minResponseTokensFor).
|
|
653
|
+
const maxTokens = Math.max(config.get('maxTokens'), minResponseTokensFor(model, config.get('reasoningEffort')));
|
|
651
654
|
const baseUrl = getProviderBaseUrl(providerId, 'anthropic');
|
|
652
655
|
const authHeader = getProviderAuthHeader(providerId, 'anthropic');
|
|
653
656
|
if (!baseUrl) {
|
package/dist/config/index.js
CHANGED
|
@@ -280,10 +280,18 @@ if (currentMigrationVersion < 1) {
|
|
|
280
280
|
config.set('rateLimitCommands', 10000);
|
|
281
281
|
}
|
|
282
282
|
}
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
283
|
+
// Retired model ids: on EVERY load, not once. This sat inside
|
|
284
|
+
// `currentMigrationVersion < 4` until 2026-09-23, and MIGRATION_VERSION had
|
|
285
|
+
// been 4 since 2026-08-15 — so every RETIRED_MODEL_REPLACEMENTS entry added
|
|
286
|
+
// after that (the GPT-5.5/5.4, Grok, DeepSeek and Gemini ones) reached only
|
|
287
|
+
// fresh configs and profile loads, never an existing user's active model. The
|
|
288
|
+
// lookup is exact and idempotent, it touches only ids a vendor retired or
|
|
289
|
+
// rerouted (never OpenRouter/Ollama/custom ids, which the map does not key),
|
|
290
|
+
// and it is what applyProfile and the macOS app already do on every load. The
|
|
291
|
+
// one-shot rule above protects settings a user chooses; the ids in that map are
|
|
292
|
+
// ones the vendor retired or reroutes, or that cannot work from Codeep (GPT-6
|
|
293
|
+
// Astra's tool calls), so rewriting them again cannot undo a working choice.
|
|
294
|
+
{
|
|
287
295
|
const provider = config.get('provider');
|
|
288
296
|
const model = config.get('model');
|
|
289
297
|
const replacement = replacementModelFor(provider, model);
|
|
@@ -49,7 +49,14 @@ export interface ProviderConfig {
|
|
|
49
49
|
export declare const PROVIDERS: Record<string, ProviderConfig>;
|
|
50
50
|
export type ProviderId = keyof typeof PROVIDERS;
|
|
51
51
|
export declare function getProvider(id: string): ProviderConfig | null;
|
|
52
|
+
/**
|
|
53
|
+
* One exact lookup, never followed further: every target must already be a
|
|
54
|
+
* model its own provider offers (providers.test.ts holds the map to that), so a
|
|
55
|
+
* chain can never be needed and the macOS mirror stays a flat table too.
|
|
56
|
+
*/
|
|
52
57
|
export declare function replacementModelFor(providerId: string, modelId: string): string | undefined;
|
|
58
|
+
/** The whole map, read-only — for the invariant test and nothing else. */
|
|
59
|
+
export declare function retiredModelReplacements(): Readonly<Record<string, Readonly<Record<string, string>>>>;
|
|
53
60
|
export declare function getProviderList(): {
|
|
54
61
|
id: string;
|
|
55
62
|
name: string;
|
|
@@ -94,6 +101,21 @@ export declare function modelRejectsSamplingParams(model: string): boolean;
|
|
|
94
101
|
* Falls back to the requested value if no provider limit is set.
|
|
95
102
|
*/
|
|
96
103
|
export declare function getEffectiveMaxTokens(providerId: string, requested: number): number;
|
|
104
|
+
/**
|
|
105
|
+
* The smallest response budget (`max_tokens`) worth sending this model, or 0
|
|
106
|
+
* for no floor. Callers take the larger of this and whatever they would have
|
|
107
|
+
* sent, then apply getEffectiveMaxTokens as usual.
|
|
108
|
+
*
|
|
109
|
+
* Claude Opus 5.5 thinks on every request — adaptive thinking cannot be turned
|
|
110
|
+
* off — and "tends to think more per turn than Claude Opus 5" at the same
|
|
111
|
+
* effort; Anthropic's notes say to "leave room in max_tokens for the thinking",
|
|
112
|
+
* which spends the same limit as the answer. The task planner's 2048, or a
|
|
113
|
+
* maxTokens lowered in /settings, could go on thinking alone and cut the reply
|
|
114
|
+
* off. 32K is Codeep's own default; the Max tier gets 64K, where Anthropic's
|
|
115
|
+
* advice for max effort on Opus 5 is "starting at 64k tokens". Matched on the
|
|
116
|
+
* canonical id, so `anthropic/claude-opus-5.5` on OpenRouter is covered too.
|
|
117
|
+
*/
|
|
118
|
+
export declare function minResponseTokensFor(model: string, tier: ReasoningTier | undefined): number;
|
|
97
119
|
/**
|
|
98
120
|
* Unified, user-facing thinking-effort tiers (the `/thinking` setting).
|
|
99
121
|
*
|
|
@@ -102,7 +124,7 @@ export declare function getEffectiveMaxTokens(providerId: string, requested: num
|
|
|
102
124
|
*
|
|
103
125
|
* The four tiers are CONCEPTUAL. `reasoningParamsFor()` clamps each one to the
|
|
104
126
|
* nearest level the active provider+model actually accepts, so we never send a
|
|
105
|
-
* value that would 400 (e.g. Gemini
|
|
127
|
+
* value that would 400 (e.g. Gemini has no "max"; GPT-5.5 tops out at "xhigh").
|
|
106
128
|
* The control is a pure DEPTH knob on models that already think — it never
|
|
107
129
|
* toggles thinking on/off, which keeps us clear of the reasoning_content-replay
|
|
108
130
|
* contract that DeepSeek/GLM impose when thinking mode is flipped.
|
|
@@ -116,6 +138,21 @@ export declare const REASONING_TIERS: ReasoningTier[];
|
|
|
116
138
|
* `claude-opus-4.8` → `claude-opus-4-8`). Mirrors macOS `ModelTuning.canonicalModelID`.
|
|
117
139
|
*/
|
|
118
140
|
export declare function canonicalModelId(model: string): string;
|
|
141
|
+
/**
|
|
142
|
+
* GPT-6 Sol and Luna on Chat Completions support function calling "only with
|
|
143
|
+
* `reasoning_effort` set to `none`" (developers.openai.com models/gpt-6-sol,
|
|
144
|
+
* guides/latest-model). So a request that carries tools to them must send
|
|
145
|
+
* "none", whatever /thinking says. Direct `openai` only: OpenRouter may reach
|
|
146
|
+
* OpenAI through the Responses API, where the rule does not apply, and Astra
|
|
147
|
+
* rejects "none" with a 400 (it is not offered on `openai` at all).
|
|
148
|
+
*/
|
|
149
|
+
export declare function toolsForceReasoningOff(providerId: string, model: string): boolean;
|
|
150
|
+
/**
|
|
151
|
+
* What /thinking must tell the user when the tier does not reach every request
|
|
152
|
+
* for this model, or null. Without it the setting would look applied while
|
|
153
|
+
* agent turns quietly ran at "none".
|
|
154
|
+
*/
|
|
155
|
+
export declare function agentTurnReasoningNote(providerId: string, model: string): string | null;
|
|
119
156
|
/**
|
|
120
157
|
* Does this provider+model expose a GRADED thinking-effort control we can drive?
|
|
121
158
|
* Used to gate the `/thinking` UI — hidden entirely for models without one.
|
|
@@ -128,12 +165,19 @@ export declare function modelSupportsReasoningEffort(providerId: string, model:
|
|
|
128
165
|
* models, or providers without a graded knob — so callers can spread it
|
|
129
166
|
* unconditionally. Keep in lockstep with macOS `ModelTuning.reasoningParams`.
|
|
130
167
|
*/
|
|
131
|
-
export declare function reasoningParamsFor(providerId: string, model: string, tier: ReasoningTier
|
|
168
|
+
export declare function reasoningParamsFor(providerId: string, model: string, tier: ReasoningTier,
|
|
169
|
+
/** The request carries a non-empty `tools` array (native tool calling). */
|
|
170
|
+
opts?: {
|
|
171
|
+
tools?: boolean;
|
|
172
|
+
}): Record<string, unknown>;
|
|
132
173
|
/**
|
|
133
174
|
* The DISTINCT tiers a given provider+model actually exposes — used to build a
|
|
134
175
|
* per-model picker that only offers levels the model can tell apart (e.g.
|
|
135
|
-
* GLM-5.2
|
|
136
|
-
*
|
|
176
|
+
* GLM-5.2 grades only high|max; Gemini via the OpenAI-compat layer has no max).
|
|
177
|
+
* Always leads with 'auto'. `[]` for models with no graded knob.
|
|
178
|
+
*
|
|
179
|
+
* GPT-6 Sol/Luna list their full set: it is what they run on plain chat. Agent
|
|
180
|
+
* turns send "none" regardless (toolsForceReasoningOff), and /thinking says so.
|
|
137
181
|
*
|
|
138
182
|
* Kept in lockstep with `reasoningParamsFor` (the providers-test asserts every
|
|
139
183
|
* listed tier yields a DISTINCT param, so this can't silently drift). Mirrors
|