fix: hide adaptive think option for GPT models

This commit is contained in:
Peter Steinberger
2026-04-21 06:16:10 +01:00
parent 0da5e0e34e
commit e4adb0b0e3
18 changed files with 136 additions and 40 deletions

View File

@@ -811,7 +811,7 @@ describe("handleDirectiveOnly model persist behavior (fixes #1435)", () => {
);
expect(result?.text).toContain("Current thinking level: low");
expect(result?.text).toContain("Options: off, minimal, low, medium, high, adaptive.");
expect(result?.text).toContain("Options: off, minimal, low, medium, high.");
});
it("persists verbose on and off directives", async () => {

View File

@@ -20,7 +20,7 @@ export type ThinkingCatalogEntry = {
reasoning?: boolean;
};
const BASE_THINKING_LEVELS: ThinkLevel[] = ["off", "minimal", "low", "medium", "high", "adaptive"];
const BASE_THINKING_LEVELS: ThinkLevel[] = ["off", "minimal", "low", "medium", "high"];
const NO_THINKING_LEVELS: ThinkLevel[] = [...BASE_THINKING_LEVELS];
export function isBinaryThinkingProvider(provider?: string | null): boolean {

View File

@@ -1,6 +1,7 @@
import { beforeEach, describe, expect, it, vi } from "vitest";
const providerRuntimeMocks = vi.hoisted(() => ({
resolveProviderAdaptiveThinking: vi.fn(),
resolveProviderBinaryThinking: vi.fn(),
resolveProviderDefaultThinkingLevel: vi.fn(),
resolveProviderXHighThinking: vi.fn(),
@@ -15,6 +16,7 @@ let resolveThinkingDefaultForModel: typeof import("./thinking.js").resolveThinki
async function loadFreshThinkingModuleForTest() {
vi.resetModules();
vi.doMock("../plugins/provider-thinking.js", () => ({
resolveProviderAdaptiveThinking: providerRuntimeMocks.resolveProviderAdaptiveThinking,
resolveProviderBinaryThinking: providerRuntimeMocks.resolveProviderBinaryThinking,
resolveProviderDefaultThinkingLevel: providerRuntimeMocks.resolveProviderDefaultThinkingLevel,
resolveProviderXHighThinking: providerRuntimeMocks.resolveProviderXHighThinking,
@@ -23,6 +25,8 @@ async function loadFreshThinkingModuleForTest() {
}
beforeEach(async () => {
providerRuntimeMocks.resolveProviderAdaptiveThinking.mockReset();
providerRuntimeMocks.resolveProviderAdaptiveThinking.mockReturnValue(undefined);
providerRuntimeMocks.resolveProviderBinaryThinking.mockReset();
providerRuntimeMocks.resolveProviderBinaryThinking.mockReturnValue(undefined);
providerRuntimeMocks.resolveProviderDefaultThinkingLevel.mockReset();
@@ -113,8 +117,23 @@ describe("listThinkingLevels", () => {
expect(listThinkingLevels(undefined, "gpt-4.1-mini")).not.toContain("xhigh");
});
it("always includes adaptive", () => {
expect(listThinkingLevels(undefined, "gpt-4.1-mini")).toContain("adaptive");
it("uses provider runtime hooks for adaptive support", () => {
providerRuntimeMocks.resolveProviderAdaptiveThinking.mockReturnValue(true);
expect(listThinkingLevels("demo", "demo-model")).toContain("adaptive");
});
it("does not include adaptive without provider support", () => {
expect(listThinkingLevels(undefined, "gpt-4.1-mini")).not.toContain("adaptive");
expect(listThinkingLevels("openai", "gpt-5.4")).not.toContain("adaptive");
});
it("includes adaptive for provider-advertised models", () => {
providerRuntimeMocks.resolveProviderAdaptiveThinking.mockImplementation(
({ provider, context }) =>
provider === "anthropic" && context.modelId === "claude-opus-4-6" ? true : undefined,
);
expect(listThinkingLevels("anthropic", "claude-opus-4-6")).toContain("adaptive");
});
});

View File

@@ -1,7 +1,5 @@
import { normalizeProviderId } from "../agents/provider-id.js";
import {
formatThinkingLevels as formatThinkingLevelsFallback,
listThinkingLevelLabels as listThinkingLevelLabelsFallback,
listThinkingLevels as listThinkingLevelsFallback,
resolveThinkingDefaultForModel as resolveThinkingDefaultForModelFallback,
} from "./thinking.shared.js";
@@ -31,6 +29,7 @@ export type {
VerboseLevel,
} from "./thinking.shared.js";
import {
resolveProviderAdaptiveThinking,
resolveProviderBinaryThinking,
resolveProviderDefaultThinkingLevel,
resolveProviderXHighThinking,
@@ -82,10 +81,33 @@ export function supportsXHighThinking(provider?: string | null, model?: string |
return false;
}
export function supportsAdaptiveThinking(provider?: string | null, model?: string | null): boolean {
const modelKey = normalizeOptionalLowercaseString(model);
if (!modelKey) {
return false;
}
const providerRaw = normalizeOptionalString(provider);
const providerKey = providerRaw ? normalizeProviderId(providerRaw) : "";
if (!providerKey) {
return false;
}
const pluginDecision = resolveProviderAdaptiveThinking({
provider: providerKey,
context: {
provider: providerKey,
modelId: modelKey,
},
});
return pluginDecision === true;
}
export function listThinkingLevels(provider?: string | null, model?: string | null): ThinkLevel[] {
const levels = listThinkingLevelsFallback(provider, model);
if (supportsXHighThinking(provider, model)) {
levels.splice(levels.length - 1, 0, "xhigh");
levels.push("xhigh");
}
if (supportsAdaptiveThinking(provider, model)) {
levels.push("adaptive");
}
return levels;
}
@@ -94,10 +116,7 @@ export function listThinkingLevelLabels(provider?: string | null, model?: string
if (isBinaryThinkingProvider(provider, model)) {
return ["off", "on"];
}
if (supportsXHighThinking(provider, model)) {
return listThinkingLevels(provider, model);
}
return listThinkingLevelLabelsFallback(provider, model);
return listThinkingLevels(provider, model);
}
export function formatThinkingLevels(
@@ -105,9 +124,7 @@ export function formatThinkingLevels(
model?: string | null,
separator = ", ",
): string {
return supportsXHighThinking(provider, model)
? listThinkingLevelLabels(provider, model).join(separator)
: formatThinkingLevelsFallback(provider, model, separator);
return listThinkingLevelLabels(provider, model).join(separator);
}
export function resolveThinkingDefaultForModel(params: {

View File

@@ -640,6 +640,16 @@ export function resolveProviderXHighThinking(params: {
return resolveProviderRuntimePlugin(params)?.supportsXHighThinking?.(params.context);
}
export function resolveProviderAdaptiveThinking(params: {
provider: string;
config?: OpenClawConfig;
workspaceDir?: string;
env?: NodeJS.ProcessEnv;
context: ProviderThinkingPolicyContext;
}) {
return resolveProviderRuntimePlugin(params)?.supportsAdaptiveThinking?.(params.context);
}
export function resolveProviderDefaultThinkingLevel(params: {
provider: string;
config?: OpenClawConfig;

View File

@@ -8,6 +8,7 @@ type ThinkingProviderPlugin = {
id: string;
aliases?: string[];
isBinaryThinking?: (ctx: ProviderThinkingPolicyContext) => boolean | undefined;
supportsAdaptiveThinking?: (ctx: ProviderThinkingPolicyContext) => boolean | undefined;
supportsXHighThinking?: (ctx: ProviderThinkingPolicyContext) => boolean | undefined;
resolveDefaultThinkingLevel?: (
ctx: ProviderDefaultThinkingPolicyContext,
@@ -61,6 +62,12 @@ export function resolveProviderXHighThinking(
return resolveActiveThinkingProvider(params.provider)?.supportsXHighThinking?.(params.context);
}
export function resolveProviderAdaptiveThinking(
params: ThinkingHookParams<ProviderThinkingPolicyContext>,
) {
return resolveActiveThinkingProvider(params.provider)?.supportsAdaptiveThinking?.(params.context);
}
export function resolveProviderDefaultThinkingLevel(
params: ThinkingHookParams<ProviderDefaultThinkingPolicyContext>,
) {

View File

@@ -2,8 +2,8 @@
* Provider-owned thinking policy input.
*
* Used by shared `/think`, ACP controls, and directive parsing to ask a
* provider whether a model supports special reasoning UX such as xhigh or a
* binary on/off toggle.
* provider whether a model supports special reasoning UX such as adaptive,
* xhigh, or a binary on/off toggle.
*/
export type ProviderThinkingPolicyContext = {
provider: string;

View File

@@ -1407,6 +1407,12 @@ export type ProviderPlugin = {
* Return true only for models that should expose the `xhigh` thinking level.
*/
supportsXHighThinking?: (ctx: ProviderThinkingPolicyContext) => boolean | undefined;
/**
* Provider-owned adaptive thinking support.
*
* Return true only for models that should expose the `adaptive` thinking level.
*/
supportsAdaptiveThinking?: (ctx: ProviderThinkingPolicyContext) => boolean | undefined;
/**
* Provider-owned default thinking level.
*