mirror of
https://github.com/openclaw/openclaw.git
synced 2026-05-06 15:50:46 +00:00
fix: gate max thinking by model support
This commit is contained in:
@@ -216,7 +216,8 @@ type ActiveMemoryThinkingLevel =
|
||||
| "medium"
|
||||
| "high"
|
||||
| "xhigh"
|
||||
| "adaptive";
|
||||
| "adaptive"
|
||||
| "max";
|
||||
type ActiveMemoryPromptStyle =
|
||||
| "balanced"
|
||||
| "strict"
|
||||
@@ -698,7 +699,8 @@ function resolveThinkingLevel(thinking: unknown): ActiveMemoryThinkingLevel {
|
||||
thinking === "medium" ||
|
||||
thinking === "high" ||
|
||||
thinking === "xhigh" ||
|
||||
thinking === "adaptive"
|
||||
thinking === "adaptive" ||
|
||||
thinking === "max"
|
||||
) {
|
||||
return thinking;
|
||||
}
|
||||
|
||||
@@ -218,6 +218,18 @@ describe("anthropic provider replay hooks", () => {
|
||||
modelId: "claude-opus-4-6",
|
||||
} as never),
|
||||
).toBe(false);
|
||||
expect(
|
||||
provider.supportsMaxThinking?.({
|
||||
provider: "anthropic",
|
||||
modelId: "claude-opus-4-7",
|
||||
} as never),
|
||||
).toBe(true);
|
||||
expect(
|
||||
provider.supportsMaxThinking?.({
|
||||
provider: "anthropic",
|
||||
modelId: "claude-opus-4-6",
|
||||
} as never),
|
||||
).toBe(false);
|
||||
expect(
|
||||
provider.supportsAdaptiveThinking?.({
|
||||
provider: "anthropic",
|
||||
|
||||
@@ -495,6 +495,7 @@ export function buildAnthropicProvider(): ProviderPlugin {
|
||||
resolveReasoningOutputMode: () => "native",
|
||||
supportsXHighThinking: ({ modelId }) => isAnthropicOpus47Model(modelId),
|
||||
supportsAdaptiveThinking: ({ modelId }) => supportsAnthropicAdaptiveThinking(modelId),
|
||||
supportsMaxThinking: ({ modelId }) => isAnthropicOpus47Model(modelId),
|
||||
wrapStreamFn: wrapAnthropicProviderStream,
|
||||
resolveDefaultThinkingLevel: ({ modelId }) =>
|
||||
isAnthropicOpus47Model(modelId)
|
||||
|
||||
@@ -37,6 +37,7 @@ const providerThinkingMocks = vi.hoisted(() => ({
|
||||
resolveProviderAdaptiveThinking: vi.fn(),
|
||||
resolveProviderBinaryThinking: vi.fn(),
|
||||
resolveProviderDefaultThinkingLevel: vi.fn(),
|
||||
resolveProviderMaxThinking: vi.fn(),
|
||||
resolveProviderXHighThinking: vi.fn(),
|
||||
}));
|
||||
const buildModelsProviderDataMock = vi.hoisted(() => vi.fn());
|
||||
@@ -131,6 +132,7 @@ async function loadDiscordThinkAutocompleteModulesForTest() {
|
||||
resolveProviderAdaptiveThinking: providerThinkingMocks.resolveProviderAdaptiveThinking,
|
||||
resolveProviderBinaryThinking: providerThinkingMocks.resolveProviderBinaryThinking,
|
||||
resolveProviderDefaultThinkingLevel: providerThinkingMocks.resolveProviderDefaultThinkingLevel,
|
||||
resolveProviderMaxThinking: providerThinkingMocks.resolveProviderMaxThinking,
|
||||
resolveProviderXHighThinking: providerThinkingMocks.resolveProviderXHighThinking,
|
||||
}));
|
||||
const commandAuth = await import("openclaw/plugin-sdk/command-auth");
|
||||
@@ -147,6 +149,7 @@ describe("discord native /think autocomplete", () => {
|
||||
providerThinkingMocks.resolveProviderBinaryThinking.mockReturnValue(undefined);
|
||||
providerThinkingMocks.resolveProviderAdaptiveThinking.mockReturnValue(undefined);
|
||||
providerThinkingMocks.resolveProviderDefaultThinkingLevel.mockReturnValue(undefined);
|
||||
providerThinkingMocks.resolveProviderMaxThinking.mockReturnValue(undefined);
|
||||
providerThinkingMocks.resolveProviderXHighThinking.mockImplementation(({ provider, context }) =>
|
||||
provider === "openai-codex" && ["gpt-5.4", "gpt-5.4-pro"].includes(context.modelId)
|
||||
? true
|
||||
@@ -177,6 +180,10 @@ describe("discord native /think autocomplete", () => {
|
||||
providerThinkingMocks.resolveProviderAdaptiveThinking.mockReturnValue(undefined);
|
||||
providerThinkingMocks.resolveProviderDefaultThinkingLevel.mockReset();
|
||||
providerThinkingMocks.resolveProviderDefaultThinkingLevel.mockReturnValue(undefined);
|
||||
providerThinkingMocks.resolveProviderMaxThinking.mockReset();
|
||||
providerThinkingMocks.resolveProviderMaxThinking.mockImplementation(({ provider, context }) =>
|
||||
provider === "anthropic" && context.modelId === "claude-opus-4-7" ? true : undefined,
|
||||
);
|
||||
providerThinkingMocks.resolveProviderXHighThinking.mockReset();
|
||||
providerThinkingMocks.resolveProviderXHighThinking.mockImplementation(({ provider, context }) =>
|
||||
provider === "openai-codex" && ["gpt-5.4", "gpt-5.4-pro"].includes(context.modelId)
|
||||
@@ -263,9 +270,65 @@ describe("discord native /think autocomplete", () => {
|
||||
});
|
||||
const values = choices.map((choice) => choice.value);
|
||||
expect(values).toContain("xhigh");
|
||||
expect(values).not.toContain("max");
|
||||
expect(values).not.toContain("adaptive");
|
||||
});
|
||||
|
||||
it("includes max only for provider-advertised models", async () => {
|
||||
fs.writeFileSync(
|
||||
STORE_PATH,
|
||||
JSON.stringify({
|
||||
[SESSION_KEY]: {
|
||||
updatedAt: Date.now(),
|
||||
providerOverride: "anthropic",
|
||||
modelOverride: "claude-opus-4-7",
|
||||
},
|
||||
}),
|
||||
"utf8",
|
||||
);
|
||||
const cfg = createConfig();
|
||||
resolveConfiguredBindingRouteMock.mockImplementation(createConfiguredRouteResult);
|
||||
const interaction = {
|
||||
options: {
|
||||
getFocused: () => ({ value: "ma" }),
|
||||
},
|
||||
respond: async (_choices: Array<{ name: string; value: string }>) => {},
|
||||
rawData: {
|
||||
member: { roles: [] },
|
||||
},
|
||||
channel: { id: "C1", type: ChannelType.GuildText },
|
||||
user: { id: "U1" },
|
||||
guild: { id: "G1" },
|
||||
client: {},
|
||||
} as unknown as AutocompleteInteraction & {
|
||||
respond: (choices: Array<{ name: string; value: string }>) => Promise<void>;
|
||||
};
|
||||
|
||||
const context = await resolveDiscordNativeChoiceContext({
|
||||
interaction,
|
||||
cfg,
|
||||
accountId: "default",
|
||||
threadBindings: createNoopThreadBindingManager("default"),
|
||||
});
|
||||
const command = findCommandByNativeName("think", "discord");
|
||||
const levelArg = command?.args?.find((entry) => entry.name === "level");
|
||||
expect(command).toBeTruthy();
|
||||
expect(levelArg).toBeTruthy();
|
||||
if (!command || !levelArg) {
|
||||
return;
|
||||
}
|
||||
|
||||
const choices = resolveCommandArgChoices({
|
||||
command,
|
||||
arg: levelArg,
|
||||
cfg,
|
||||
provider: context?.provider,
|
||||
model: context?.model,
|
||||
});
|
||||
const values = choices.map((choice) => choice.value);
|
||||
expect(values).toContain("max");
|
||||
});
|
||||
|
||||
it("falls back when a configured binding is unavailable", async () => {
|
||||
const cfg = createConfig();
|
||||
resolveConfiguredBindingRouteMock.mockImplementation(createConfiguredRouteResult);
|
||||
|
||||
@@ -21,7 +21,15 @@ type KimiToolCallBlock = {
|
||||
};
|
||||
|
||||
type KimiThinkingType = "enabled" | "disabled";
|
||||
type KimiThinkingLevel = "off" | "minimal" | "low" | "medium" | "high" | "xhigh" | "adaptive";
|
||||
type KimiThinkingLevel =
|
||||
| "off"
|
||||
| "minimal"
|
||||
| "low"
|
||||
| "medium"
|
||||
| "high"
|
||||
| "xhigh"
|
||||
| "adaptive"
|
||||
| "max";
|
||||
|
||||
function normalizeKimiThinkingType(value: unknown): KimiThinkingType | undefined {
|
||||
if (typeof value === "boolean") {
|
||||
|
||||
@@ -7,6 +7,7 @@ import {
|
||||
formatXHighModelHint,
|
||||
normalizeThinkLevel,
|
||||
resolvePreferredOpenClawTmpDir,
|
||||
resolveSupportedThinkingLevel,
|
||||
supportsXHighThinking,
|
||||
} from "../api.js";
|
||||
import type { OpenClawPluginApi } from "../api.js";
|
||||
@@ -61,7 +62,7 @@ type LlmTaskParams = {
|
||||
};
|
||||
|
||||
const INVALID_THINKING_LEVELS_HINT =
|
||||
"off, minimal, low, medium, high, adaptive, and xhigh where supported";
|
||||
"off, minimal, low, medium, high, adaptive, xhigh where supported, and max where supported";
|
||||
|
||||
export function createLlmTaskTool(api: OpenClawPluginApi) {
|
||||
return {
|
||||
@@ -143,9 +144,17 @@ export function createLlmTaskTool(api: OpenClawPluginApi) {
|
||||
`Invalid thinking level "${thinkingRaw}". Use one of: ${INVALID_THINKING_LEVELS_HINT}.`,
|
||||
);
|
||||
}
|
||||
let resolvedThinkLevel = thinkLevel;
|
||||
if (thinkLevel === "xhigh" && !supportsXHighThinking(provider, model)) {
|
||||
throw new Error(`Thinking level "xhigh" is only supported for ${formatXHighModelHint()}.`);
|
||||
}
|
||||
if (thinkLevel === "max") {
|
||||
resolvedThinkLevel = resolveSupportedThinkingLevel({
|
||||
provider,
|
||||
model,
|
||||
level: thinkLevel,
|
||||
});
|
||||
}
|
||||
|
||||
const timeoutMs =
|
||||
(typeof params.timeoutMs === "number" && params.timeoutMs > 0
|
||||
@@ -204,7 +213,7 @@ export function createLlmTaskTool(api: OpenClawPluginApi) {
|
||||
model,
|
||||
authProfileId,
|
||||
authProfileIdSource: authProfileId ? "user" : "auto",
|
||||
thinkLevel,
|
||||
thinkLevel: resolvedThinkLevel,
|
||||
streamParams,
|
||||
disableTools: true,
|
||||
});
|
||||
|
||||
@@ -28,6 +28,7 @@ export const MISTRAL_SMALL_LATEST_REASONING_EFFORT_MAP: Record<string, string> =
|
||||
high: "high",
|
||||
xhigh: "high",
|
||||
adaptive: "high",
|
||||
max: "high",
|
||||
};
|
||||
|
||||
export const MISTRAL_SMALL_LATEST_ID = "mistral-small-latest";
|
||||
|
||||
@@ -202,7 +202,7 @@ export function createConfiguredOllamaCompatStreamWrapper(
|
||||
if (ctx.thinkingLevel === "off") {
|
||||
streamFn = createOllamaThinkingWrapper(streamFn, false);
|
||||
} else if (ctx.thinkingLevel) {
|
||||
// Any non-off ThinkLevel (minimal, low, medium, high, xhigh, adaptive)
|
||||
// Any non-off ThinkLevel (minimal, low, medium, high, xhigh, adaptive, max)
|
||||
// should enable Ollama's native thinking mode.
|
||||
streamFn = createOllamaThinkingWrapper(streamFn, true);
|
||||
}
|
||||
|
||||
@@ -73,7 +73,9 @@ function parseQaThinkingLevel(
|
||||
}
|
||||
const normalized = normalizeQaThinkingLevel(value);
|
||||
if (!normalized) {
|
||||
throw new Error(`${label} must be one of off, minimal, low, medium, high, xhigh, adaptive`);
|
||||
throw new Error(
|
||||
`${label} must be one of off, minimal, low, medium, high, xhigh, adaptive, max`,
|
||||
);
|
||||
}
|
||||
return normalized;
|
||||
}
|
||||
@@ -238,7 +240,7 @@ function parseQaModelSpecs(label: string, entries: readonly string[] | undefined
|
||||
const thinkingDefault = parseQaThinkingLevel(`${label} thinking`, value);
|
||||
if (!thinkingDefault) {
|
||||
throw new Error(
|
||||
`${label} thinking must be one of off, minimal, low, medium, high, xhigh, adaptive`,
|
||||
`${label} thinking must be one of off, minimal, low, medium, high, xhigh, adaptive, max`,
|
||||
);
|
||||
}
|
||||
options.thinkingDefault = thinkingDefault;
|
||||
|
||||
@@ -339,7 +339,7 @@ export function registerQaLabCli(program: Command) {
|
||||
.option("--fast", "Enable provider fast mode for all candidate runs")
|
||||
.option(
|
||||
"--thinking <level>",
|
||||
"Candidate thinking default: off|minimal|low|medium|high|xhigh|adaptive",
|
||||
"Candidate thinking default: off|minimal|low|medium|high|xhigh|adaptive|max",
|
||||
)
|
||||
.option(
|
||||
"--model-thinking <ref=level>",
|
||||
|
||||
@@ -1,4 +1,12 @@
|
||||
export type QaThinkingLevel = "off" | "minimal" | "low" | "medium" | "high" | "xhigh" | "adaptive";
|
||||
export type QaThinkingLevel =
|
||||
| "off"
|
||||
| "minimal"
|
||||
| "low"
|
||||
| "medium"
|
||||
| "high"
|
||||
| "xhigh"
|
||||
| "adaptive"
|
||||
| "max";
|
||||
|
||||
export function normalizeQaThinkingLevel(input: unknown): QaThinkingLevel | undefined {
|
||||
const value = typeof input === "string" ? input.trim().toLowerCase() : "";
|
||||
@@ -24,5 +32,8 @@ export function normalizeQaThinkingLevel(input: unknown): QaThinkingLevel | unde
|
||||
if (collapsed === "adaptive" || collapsed === "auto") {
|
||||
return "adaptive";
|
||||
}
|
||||
if (collapsed === "max") {
|
||||
return "max";
|
||||
}
|
||||
return undefined;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user