fix: gate max thinking by model support

This commit is contained in:
Peter Steinberger
2026-04-21 06:47:06 +01:00
parent f89740a62c
commit 6ce17db11a
49 changed files with 510 additions and 73 deletions

View File

@@ -160,7 +160,7 @@ function buildThinkingOptions(
);
};
for (const label of listThinkingLevelLabels(provider)) {
for (const label of listThinkingLevelLabels(provider, model)) {
const normalized = normalizeThinkLevel(label) ?? normalizeLowercaseStringOrEmpty(label);
addOption(normalized);
}

View File

@@ -520,7 +520,7 @@ describe("executeSlashCommand directives", () => {
);
expect(result.content).toBe(
"Current thinking level: low.\nOptions: off, minimal, low, medium, high, adaptive.",
"Current thinking level: low.\nOptions: off, minimal, low, medium, high.",
);
expect(request).toHaveBeenNthCalledWith(1, "sessions.list", {});
expect(request).toHaveBeenNthCalledWith(2, "models.list", {});

View File

@@ -258,7 +258,7 @@ async function executeThink(
return {
content: formatDirectiveOptions(
`Current thinking level: ${resolveCurrentThinkingLevel(session, models)}.`,
formatThinkingLevels(session?.modelProvider),
formatThinkingLevels(session?.modelProvider, session?.model),
),
};
} catch (err) {
@@ -271,7 +271,7 @@ async function executeThink(
try {
const session = await loadCurrentSession(client, sessionKey);
return {
content: `Unrecognized thinking level "${rawLevel}". Valid levels: ${formatThinkingLevels(session?.modelProvider)}.`,
content: `Unrecognized thinking level "${rawLevel}". Valid levels: ${formatThinkingLevels(session?.modelProvider, session?.model)}.`,
};
} catch (err) {
return { content: `Failed to validate thinking level: ${String(err)}` };

View File

@@ -6,10 +6,13 @@ export type ThinkingCatalogEntry = {
reasoning?: boolean;
};
const BASE_THINKING_LEVELS = ["off", "minimal", "low", "medium", "high", "adaptive"] as const;
const BASE_THINKING_LEVELS = ["off", "minimal", "low", "medium", "high"] as const;
const BINARY_THINKING_LEVELS = ["off", "on"] as const;
const ANTHROPIC_CLAUDE_46_MODEL_RE = /^claude-(?:opus|sonnet)-4(?:\.|-)6(?:$|[-.])/i;
const ANTHROPIC_OPUS_47_MODEL_RE = /^claude-opus-4(?:\.|-)7(?:$|[-.])/i;
const AMAZON_BEDROCK_CLAUDE_46_MODEL_RE = /claude-(?:opus|sonnet)-4(?:\.|-)6(?:$|[-.])/i;
const OPENAI_XHIGH_MODEL_RE =
/^(?:gpt-5\.[2-9](?:\.\d+)?|gpt-5\.[2-9](?:\.\d+)?-pro|gpt-5\.\d+-codex|gpt-5\.\d+-codex-spark|gpt-5\.1-codex|gpt-5\.2-codex)(?:$|-)/i;
export function normalizeThinkingProviderId(provider?: string | null): string {
if (!provider) {
@@ -38,6 +41,9 @@ export function normalizeThinkLevel(raw?: string | null): string | undefined {
if (collapsed === "adaptive" || collapsed === "auto") {
return "adaptive";
}
if (collapsed === "max") {
return "max";
}
if (collapsed === "xhigh" || collapsed === "extrahigh") {
return "xhigh";
}
@@ -56,9 +62,7 @@ export function normalizeThinkLevel(raw?: string | null): string | undefined {
if (["mid", "med", "medium", "thinkharder", "think-harder", "harder"].includes(key)) {
return "medium";
}
if (
["high", "ultra", "ultrathink", "think-hard", "thinkhardest", "highest", "max"].includes(key)
) {
if (["high", "ultra", "ultrathink", "think-hard", "thinkhardest", "highest"].includes(key)) {
return "high";
}
if (key === "think") {
@@ -67,12 +71,53 @@ export function normalizeThinkLevel(raw?: string | null): string | undefined {
return undefined;
}
export function listThinkingLevelLabels(provider?: string | null): readonly string[] {
return isBinaryThinkingProvider(provider) ? BINARY_THINKING_LEVELS : BASE_THINKING_LEVELS;
function supportsAdaptiveThinking(provider?: string | null, model?: string | null): boolean {
const normalizedProvider = normalizeThinkingProviderId(provider);
const modelId = model?.trim() ?? "";
if (normalizedProvider === "anthropic") {
return ANTHROPIC_CLAUDE_46_MODEL_RE.test(modelId) || ANTHROPIC_OPUS_47_MODEL_RE.test(modelId);
}
if (normalizedProvider === "amazon-bedrock") {
return AMAZON_BEDROCK_CLAUDE_46_MODEL_RE.test(modelId);
}
return false;
}
export function formatThinkingLevels(provider?: string | null): string {
return listThinkingLevelLabels(provider).join(", ");
function supportsXHighThinking(provider?: string | null, model?: string | null): boolean {
const normalizedProvider = normalizeThinkingProviderId(provider);
const modelId = model?.trim() ?? "";
if (normalizedProvider === "anthropic") {
return ANTHROPIC_OPUS_47_MODEL_RE.test(modelId);
}
if (["openai", "openai-codex", "github-copilot", "codex"].includes(normalizedProvider)) {
return OPENAI_XHIGH_MODEL_RE.test(modelId);
}
return false;
}
function supportsMaxThinking(provider?: string | null, model?: string | null): boolean {
return normalizeThinkingProviderId(provider) === "anthropic"
? ANTHROPIC_OPUS_47_MODEL_RE.test(model?.trim() ?? "")
: false;
}
export function listThinkingLevelLabels(
provider?: string | null,
model?: string | null,
): readonly string[] {
if (isBinaryThinkingProvider(provider)) {
return BINARY_THINKING_LEVELS;
}
return [
...BASE_THINKING_LEVELS,
...(supportsXHighThinking(provider, model) ? ["xhigh"] : []),
...(supportsAdaptiveThinking(provider, model) ? ["adaptive"] : []),
...(supportsMaxThinking(provider, model) ? ["max"] : []),
];
}
export function formatThinkingLevels(provider?: string | null, model?: string | null): string {
return listThinkingLevelLabels(provider, model).join(", ");
}
export function resolveThinkingDefaultForModel(params: {