mirror of
https://github.com/openclaw/openclaw.git
synced 2026-05-08 21:50:42 +00:00
fix: centralize provider thinking profiles
This commit is contained in:
@@ -10,9 +10,8 @@ import { applyModelOverrideToSessionEntry } from "../../sessions/model-overrides
|
||||
import { normalizeLowercaseStringOrEmpty } from "../../shared/string-coerce.js";
|
||||
import {
|
||||
formatThinkingLevels,
|
||||
formatXHighModelHint,
|
||||
isThinkingLevelSupported,
|
||||
resolveSupportedThinkingLevel,
|
||||
supportsXHighThinking,
|
||||
} from "../thinking.js";
|
||||
import type { ReplyPayload } from "../types.js";
|
||||
import { resolveModelSelectionFromDirective } from "./directive-handling.model-selection.js";
|
||||
@@ -291,41 +290,38 @@ export async function handleDirectiveOnly(
|
||||
|
||||
if (
|
||||
directives.hasThinkDirective &&
|
||||
directives.thinkLevel === "xhigh" &&
|
||||
!supportsXHighThinking(resolvedProvider, resolvedModel)
|
||||
directives.thinkLevel &&
|
||||
!isThinkingLevelSupported({
|
||||
provider: resolvedProvider,
|
||||
model: resolvedModel,
|
||||
level: directives.thinkLevel,
|
||||
})
|
||||
) {
|
||||
return {
|
||||
text: `Thinking level "xhigh" is only supported for ${formatXHighModelHint()}.`,
|
||||
text: `Thinking level "${directives.thinkLevel}" is not supported for ${resolvedProvider}/${resolvedModel}. Use one of: ${formatThinkingLevels(resolvedProvider, resolvedModel)}.`,
|
||||
};
|
||||
}
|
||||
|
||||
const resolvedDirectiveThinkLevel =
|
||||
directives.hasThinkDirective && directives.thinkLevel
|
||||
? resolveSupportedThinkingLevel({
|
||||
provider: resolvedProvider,
|
||||
model: resolvedModel,
|
||||
level: directives.thinkLevel,
|
||||
})
|
||||
: directives.thinkLevel;
|
||||
const resolvedDirectiveThinkLevel = directives.thinkLevel;
|
||||
const nextThinkLevel = directives.hasThinkDirective
|
||||
? resolvedDirectiveThinkLevel
|
||||
: ((sessionEntry?.thinkingLevel as ThinkLevel | undefined) ?? currentThinkLevel);
|
||||
const shouldDowngradeXHigh =
|
||||
const remappedUnsupportedThinkLevel =
|
||||
!directives.hasThinkDirective &&
|
||||
nextThinkLevel === "xhigh" &&
|
||||
!supportsXHighThinking(resolvedProvider, resolvedModel);
|
||||
const remappedMaxThinkLevel =
|
||||
nextThinkLevel === "max"
|
||||
nextThinkLevel &&
|
||||
!isThinkingLevelSupported({
|
||||
provider: resolvedProvider,
|
||||
model: resolvedModel,
|
||||
level: nextThinkLevel,
|
||||
})
|
||||
? resolveSupportedThinkingLevel({
|
||||
provider: resolvedProvider,
|
||||
model: resolvedModel,
|
||||
level: nextThinkLevel,
|
||||
})
|
||||
: undefined;
|
||||
const shouldRemapMax =
|
||||
nextThinkLevel === "max" &&
|
||||
remappedMaxThinkLevel !== undefined &&
|
||||
remappedMaxThinkLevel !== "max";
|
||||
const shouldRemapUnsupportedThinkLevel =
|
||||
Boolean(remappedUnsupportedThinkLevel) && remappedUnsupportedThinkLevel !== nextThinkLevel;
|
||||
|
||||
const prevElevatedLevel =
|
||||
currentElevatedLevel ??
|
||||
@@ -351,8 +347,7 @@ export async function handleDirectiveOnly(
|
||||
(directives.hasExecDirective && directives.hasExecOptions && allowInternalExecPersistence) ||
|
||||
Boolean(modelSelection) ||
|
||||
directives.hasQueueDirective ||
|
||||
shouldDowngradeXHigh ||
|
||||
shouldRemapMax;
|
||||
shouldRemapUnsupportedThinkLevel;
|
||||
const fastModeChanged =
|
||||
directives.hasFastDirective &&
|
||||
directives.fastMode !== undefined &&
|
||||
@@ -366,11 +361,8 @@ export async function handleDirectiveOnly(
|
||||
if (directives.hasFastDirective && directives.fastMode !== undefined) {
|
||||
sessionEntry.fastMode = directives.fastMode;
|
||||
}
|
||||
if (shouldDowngradeXHigh) {
|
||||
sessionEntry.thinkingLevel = "high";
|
||||
}
|
||||
if (shouldRemapMax && remappedMaxThinkLevel) {
|
||||
sessionEntry.thinkingLevel = remappedMaxThinkLevel;
|
||||
if (shouldRemapUnsupportedThinkLevel && remappedUnsupportedThinkLevel) {
|
||||
sessionEntry.thinkingLevel = remappedUnsupportedThinkLevel;
|
||||
}
|
||||
if (
|
||||
directives.hasVerboseDirective &&
|
||||
@@ -573,14 +565,13 @@ export async function handleDirectiveOnly(
|
||||
if (directives.hasExecDirective && directives.hasExecOptions && !allowInternalExecPersistence) {
|
||||
parts.push(formatDirectiveAck(formatInternalExecPersistenceDeniedText()));
|
||||
}
|
||||
if (shouldDowngradeXHigh) {
|
||||
if (
|
||||
!directives.hasThinkDirective &&
|
||||
shouldRemapUnsupportedThinkLevel &&
|
||||
remappedUnsupportedThinkLevel
|
||||
) {
|
||||
parts.push(
|
||||
`Thinking level set to high (xhigh not supported for ${resolvedProvider}/${resolvedModel}).`,
|
||||
);
|
||||
}
|
||||
if (!directives.hasThinkDirective && shouldRemapMax && remappedMaxThinkLevel) {
|
||||
parts.push(
|
||||
`Thinking level set to ${remappedMaxThinkLevel} (max not supported for ${resolvedProvider}/${resolvedModel}).`,
|
||||
`Thinking level set to ${remappedUnsupportedThinkLevel} (${nextThinkLevel} not supported for ${resolvedProvider}/${resolvedModel}).`,
|
||||
);
|
||||
}
|
||||
if (modelSelection) {
|
||||
|
||||
@@ -27,11 +27,11 @@ import { resolveEnvelopeFormatOptions } from "../envelope.js";
|
||||
import type { MsgContext, TemplateContext } from "../templating.js";
|
||||
import {
|
||||
type ElevatedLevel,
|
||||
formatXHighModelHint,
|
||||
formatThinkingLevels,
|
||||
isThinkingLevelSupported,
|
||||
normalizeThinkLevel,
|
||||
type ReasoningLevel,
|
||||
resolveSupportedThinkingLevel,
|
||||
supportsXHighThinking,
|
||||
type ThinkLevel,
|
||||
type VerboseLevel,
|
||||
} from "../thinking.js";
|
||||
@@ -414,10 +414,7 @@ export async function runPreparedReply(
|
||||
if (!resolvedThinkLevel && prefixedBodyBase) {
|
||||
const parts = prefixedBodyBase.split(/\s+/);
|
||||
const maybeLevel = normalizeThinkLevel(parts[0]);
|
||||
if (
|
||||
maybeLevel &&
|
||||
(maybeLevel === "max" || maybeLevel !== "xhigh" || supportsXHighThinking(provider, model))
|
||||
) {
|
||||
if (maybeLevel && isThinkingLevelSupported({ provider, model, level: maybeLevel })) {
|
||||
resolvedThinkLevel = maybeLevel;
|
||||
prefixedBodyBase = parts.slice(1).join(" ").trim();
|
||||
}
|
||||
@@ -487,15 +484,28 @@ export async function runPreparedReply(
|
||||
if (!resolvedThinkLevel) {
|
||||
resolvedThinkLevel = await modelState.resolveDefaultThinkingLevel();
|
||||
}
|
||||
if (resolvedThinkLevel === "max") {
|
||||
if (!isThinkingLevelSupported({ provider, model, level: resolvedThinkLevel })) {
|
||||
const explicitThink = directives.hasThinkDirective && directives.thinkLevel !== undefined;
|
||||
if (explicitThink) {
|
||||
typing.cleanup();
|
||||
return {
|
||||
text: `Thinking level "${resolvedThinkLevel}" is not supported for ${provider}/${model}. Use one of: ${formatThinkingLevels(provider, model)}.`,
|
||||
};
|
||||
}
|
||||
const fallbackThinkLevel = resolveSupportedThinkingLevel({
|
||||
provider,
|
||||
model,
|
||||
level: resolvedThinkLevel,
|
||||
});
|
||||
if (fallbackThinkLevel !== resolvedThinkLevel) {
|
||||
const previousThinkLevel = resolvedThinkLevel;
|
||||
resolvedThinkLevel = fallbackThinkLevel;
|
||||
if (sessionEntry && sessionStore && sessionKey && sessionEntry.thinkingLevel === "max") {
|
||||
if (
|
||||
sessionEntry &&
|
||||
sessionStore &&
|
||||
sessionKey &&
|
||||
sessionEntry.thinkingLevel === previousThinkLevel
|
||||
) {
|
||||
sessionEntry.thinkingLevel = fallbackThinkLevel;
|
||||
sessionEntry.updatedAt = Date.now();
|
||||
sessionStore[sessionKey] = sessionEntry;
|
||||
@@ -508,27 +518,6 @@ export async function runPreparedReply(
|
||||
}
|
||||
}
|
||||
}
|
||||
if (resolvedThinkLevel === "xhigh" && !supportsXHighThinking(provider, model)) {
|
||||
const explicitThink = directives.hasThinkDirective && directives.thinkLevel !== undefined;
|
||||
if (explicitThink) {
|
||||
typing.cleanup();
|
||||
return {
|
||||
text: `Thinking level "xhigh" is only supported for ${formatXHighModelHint()}. Use /think high or switch to one of those models.`,
|
||||
};
|
||||
}
|
||||
resolvedThinkLevel = "high";
|
||||
if (sessionEntry && sessionStore && sessionKey && sessionEntry.thinkingLevel === "xhigh") {
|
||||
sessionEntry.thinkingLevel = "high";
|
||||
sessionEntry.updatedAt = Date.now();
|
||||
sessionStore[sessionKey] = sessionEntry;
|
||||
if (storePath) {
|
||||
const { updateSessionStore } = await loadSessionStoreRuntime();
|
||||
await updateSessionStore(storePath, (store) => {
|
||||
store[sessionKey] = sessionEntry;
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
const sessionIdFinal = sessionId ?? crypto.randomUUID();
|
||||
const sessionFilePathOptions = resolveSessionFilePathOptions({ agentId, storePath });
|
||||
const resolvePreparedSessionState = (): {
|
||||
|
||||
@@ -28,7 +28,17 @@ export type ThinkingCatalogEntry = {
|
||||
reasoning?: boolean;
|
||||
};
|
||||
|
||||
const BASE_THINKING_LEVELS: ThinkLevel[] = ["off", "minimal", "low", "medium", "high"];
|
||||
export const BASE_THINKING_LEVELS: ThinkLevel[] = ["off", "minimal", "low", "medium", "high"];
|
||||
export const THINKING_LEVEL_RANKS: Record<ThinkLevel, number> = {
|
||||
off: 0,
|
||||
minimal: 10,
|
||||
low: 20,
|
||||
medium: 30,
|
||||
high: 40,
|
||||
adaptive: 50,
|
||||
xhigh: 60,
|
||||
max: 70,
|
||||
};
|
||||
const NO_THINKING_LEVELS: ThinkLevel[] = [...BASE_THINKING_LEVELS];
|
||||
|
||||
export function isBinaryThinkingProvider(provider?: string | null): boolean {
|
||||
@@ -102,10 +112,6 @@ export function formatXHighModelHint(): string {
|
||||
return "provider models that advertise xhigh reasoning";
|
||||
}
|
||||
|
||||
export function formatMaxModelHint(): string {
|
||||
return "provider models that advertise max reasoning";
|
||||
}
|
||||
|
||||
export function resolveThinkingDefaultForModel(params: {
|
||||
provider: string;
|
||||
model: string;
|
||||
|
||||
@@ -1,10 +1,9 @@
|
||||
import { beforeEach, describe, expect, it, vi } from "vitest";
|
||||
|
||||
const providerRuntimeMocks = vi.hoisted(() => ({
|
||||
resolveProviderAdaptiveThinking: vi.fn(),
|
||||
resolveProviderBinaryThinking: vi.fn(),
|
||||
resolveProviderDefaultThinkingLevel: vi.fn(),
|
||||
resolveProviderMaxThinking: vi.fn(),
|
||||
resolveProviderThinkingProfile: vi.fn(),
|
||||
resolveProviderXHighThinking: vi.fn(),
|
||||
}));
|
||||
|
||||
@@ -12,29 +11,27 @@ let listThinkingLevelLabels: typeof import("./thinking.js").listThinkingLevelLab
|
||||
let listThinkingLevels: typeof import("./thinking.js").listThinkingLevels;
|
||||
let normalizeReasoningLevel: typeof import("./thinking.js").normalizeReasoningLevel;
|
||||
let normalizeThinkLevel: typeof import("./thinking.js").normalizeThinkLevel;
|
||||
let resolveSupportedThinkingLevel: typeof import("./thinking.js").resolveSupportedThinkingLevel;
|
||||
let resolveThinkingDefaultForModel: typeof import("./thinking.js").resolveThinkingDefaultForModel;
|
||||
|
||||
async function loadFreshThinkingModuleForTest() {
|
||||
vi.resetModules();
|
||||
vi.doMock("../plugins/provider-thinking.js", () => ({
|
||||
resolveProviderAdaptiveThinking: providerRuntimeMocks.resolveProviderAdaptiveThinking,
|
||||
resolveProviderBinaryThinking: providerRuntimeMocks.resolveProviderBinaryThinking,
|
||||
resolveProviderDefaultThinkingLevel: providerRuntimeMocks.resolveProviderDefaultThinkingLevel,
|
||||
resolveProviderMaxThinking: providerRuntimeMocks.resolveProviderMaxThinking,
|
||||
resolveProviderThinkingProfile: providerRuntimeMocks.resolveProviderThinkingProfile,
|
||||
resolveProviderXHighThinking: providerRuntimeMocks.resolveProviderXHighThinking,
|
||||
}));
|
||||
return await import("./thinking.js");
|
||||
}
|
||||
|
||||
beforeEach(async () => {
|
||||
providerRuntimeMocks.resolveProviderAdaptiveThinking.mockReset();
|
||||
providerRuntimeMocks.resolveProviderAdaptiveThinking.mockReturnValue(undefined);
|
||||
providerRuntimeMocks.resolveProviderBinaryThinking.mockReset();
|
||||
providerRuntimeMocks.resolveProviderBinaryThinking.mockReturnValue(undefined);
|
||||
providerRuntimeMocks.resolveProviderDefaultThinkingLevel.mockReset();
|
||||
providerRuntimeMocks.resolveProviderDefaultThinkingLevel.mockReturnValue(undefined);
|
||||
providerRuntimeMocks.resolveProviderMaxThinking.mockReset();
|
||||
providerRuntimeMocks.resolveProviderMaxThinking.mockReturnValue(undefined);
|
||||
providerRuntimeMocks.resolveProviderThinkingProfile.mockReset();
|
||||
providerRuntimeMocks.resolveProviderThinkingProfile.mockReturnValue(undefined);
|
||||
providerRuntimeMocks.resolveProviderXHighThinking.mockReset();
|
||||
providerRuntimeMocks.resolveProviderXHighThinking.mockReturnValue(undefined);
|
||||
|
||||
@@ -43,6 +40,7 @@ beforeEach(async () => {
|
||||
listThinkingLevels,
|
||||
normalizeReasoningLevel,
|
||||
normalizeThinkLevel,
|
||||
resolveSupportedThinkingLevel,
|
||||
resolveThinkingDefaultForModel,
|
||||
} = await loadFreshThinkingModuleForTest());
|
||||
});
|
||||
@@ -126,18 +124,6 @@ describe("listThinkingLevels", () => {
|
||||
expect(listThinkingLevels(undefined, "gpt-4.1-mini")).not.toContain("xhigh");
|
||||
});
|
||||
|
||||
it("uses provider runtime hooks for adaptive support", () => {
|
||||
providerRuntimeMocks.resolveProviderAdaptiveThinking.mockReturnValue(true);
|
||||
|
||||
expect(listThinkingLevels("demo", "demo-model")).toContain("adaptive");
|
||||
});
|
||||
|
||||
it("uses provider runtime hooks for max support", () => {
|
||||
providerRuntimeMocks.resolveProviderMaxThinking.mockReturnValue(true);
|
||||
|
||||
expect(listThinkingLevels("demo", "demo-model")).toContain("max");
|
||||
});
|
||||
|
||||
it("does not include max without provider support", () => {
|
||||
expect(listThinkingLevels("openai", "gpt-5.4")).not.toContain("max");
|
||||
});
|
||||
@@ -147,13 +133,40 @@ describe("listThinkingLevels", () => {
|
||||
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,
|
||||
it("uses provider thinking profiles for adaptive and max support", () => {
|
||||
providerRuntimeMocks.resolveProviderThinkingProfile.mockImplementation(({ provider }) =>
|
||||
provider === "anthropic"
|
||||
? { levels: [{ id: "off" }, { id: "adaptive" }, { id: "max" }] }
|
||||
: undefined,
|
||||
);
|
||||
|
||||
expect(listThinkingLevels("anthropic", "claude-opus-4-6")).toContain("adaptive");
|
||||
expect(listThinkingLevels("anthropic", "claude-opus-4-7")).toContain("max");
|
||||
});
|
||||
|
||||
it("uses provider thinking profiles ahead of legacy hooks", () => {
|
||||
providerRuntimeMocks.resolveProviderThinkingProfile.mockReturnValue({
|
||||
levels: [{ id: "off" }, { id: "low", label: "on" }],
|
||||
defaultLevel: "off",
|
||||
});
|
||||
providerRuntimeMocks.resolveProviderXHighThinking.mockReturnValue(true);
|
||||
|
||||
expect(listThinkingLevels("demo", "demo-model")).toEqual(["off", "low"]);
|
||||
expect(listThinkingLevelLabels("demo", "demo-model")).toEqual(["off", "on"]);
|
||||
});
|
||||
|
||||
it("maps stale unsupported levels to the largest profile level", () => {
|
||||
providerRuntimeMocks.resolveProviderThinkingProfile.mockReturnValue({
|
||||
levels: [{ id: "off" }, { id: "high" }],
|
||||
});
|
||||
|
||||
expect(
|
||||
resolveSupportedThinkingLevel({
|
||||
provider: "demo",
|
||||
model: "demo-model",
|
||||
level: "max",
|
||||
}),
|
||||
).toBe("high");
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
@@ -1,7 +1,9 @@
|
||||
import { normalizeProviderId } from "../agents/provider-id.js";
|
||||
import {
|
||||
listThinkingLevels as listThinkingLevelsFallback,
|
||||
BASE_THINKING_LEVELS,
|
||||
normalizeThinkLevel,
|
||||
resolveThinkingDefaultForModel as resolveThinkingDefaultForModelFallback,
|
||||
THINKING_LEVEL_RANKS,
|
||||
} from "./thinking.shared.js";
|
||||
import type { ThinkLevel, ThinkingCatalogEntry } from "./thinking.shared.js";
|
||||
export {
|
||||
@@ -29,118 +31,182 @@ export type {
|
||||
VerboseLevel,
|
||||
} from "./thinking.shared.js";
|
||||
import {
|
||||
resolveProviderAdaptiveThinking,
|
||||
resolveProviderBinaryThinking,
|
||||
resolveProviderDefaultThinkingLevel,
|
||||
resolveProviderMaxThinking,
|
||||
resolveProviderThinkingProfile,
|
||||
resolveProviderXHighThinking,
|
||||
} from "../plugins/provider-thinking.js";
|
||||
import type { ProviderThinkingProfile } from "../plugins/provider-thinking.types.js";
|
||||
import {
|
||||
normalizeOptionalLowercaseString,
|
||||
normalizeOptionalString,
|
||||
} from "../shared/string-coerce.js";
|
||||
|
||||
export function isBinaryThinkingProvider(provider?: string | null, model?: string | null): boolean {
|
||||
const providerRaw = normalizeOptionalString(provider);
|
||||
type ThinkingLevelOption = {
|
||||
id: ThinkLevel;
|
||||
label: string;
|
||||
rank: number;
|
||||
};
|
||||
|
||||
type ResolvedThinkingProfile = {
|
||||
levels: ThinkingLevelOption[];
|
||||
defaultLevel?: ThinkLevel | null;
|
||||
};
|
||||
|
||||
function resolveThinkingPolicyContext(params: {
|
||||
provider?: string | null;
|
||||
model?: string | null;
|
||||
catalog?: ThinkingCatalogEntry[];
|
||||
}) {
|
||||
const providerRaw = normalizeOptionalString(params.provider);
|
||||
const normalizedProvider = providerRaw ? normalizeProviderId(providerRaw) : "";
|
||||
if (!normalizedProvider) {
|
||||
return false;
|
||||
const modelId = normalizeOptionalString(params.model) ?? "";
|
||||
const modelKey = normalizeOptionalLowercaseString(params.model) ?? "";
|
||||
const candidate = params.catalog?.find(
|
||||
(entry) => normalizeProviderId(entry.provider) === normalizedProvider && entry.id === modelId,
|
||||
);
|
||||
return { normalizedProvider, modelId, modelKey, reasoning: candidate?.reasoning };
|
||||
}
|
||||
|
||||
function normalizeProfileLevel(
|
||||
level: ProviderThinkingProfile["levels"][number],
|
||||
): ThinkingLevelOption | undefined {
|
||||
const normalized = normalizeThinkLevel(level.id);
|
||||
if (!normalized) {
|
||||
return undefined;
|
||||
}
|
||||
return {
|
||||
id: normalized,
|
||||
label: normalizeOptionalString(level.label) ?? normalized,
|
||||
rank: Number.isFinite(level.rank) ? (level.rank as number) : THINKING_LEVEL_RANKS[normalized],
|
||||
};
|
||||
}
|
||||
|
||||
function normalizeThinkingProfile(profile: ProviderThinkingProfile): ResolvedThinkingProfile {
|
||||
const byId = new Map<ThinkLevel, ThinkingLevelOption>();
|
||||
for (const raw of profile.levels) {
|
||||
const level = normalizeProfileLevel(raw);
|
||||
if (level) {
|
||||
byId.set(level.id, level);
|
||||
}
|
||||
}
|
||||
const levels = [...byId.values()].toSorted((a, b) => a.rank - b.rank);
|
||||
const rawDefaultLevel = profile.defaultLevel
|
||||
? normalizeThinkLevel(profile.defaultLevel)
|
||||
: undefined;
|
||||
const defaultLevel = rawDefaultLevel && byId.has(rawDefaultLevel) ? rawDefaultLevel : undefined;
|
||||
return { levels, defaultLevel };
|
||||
}
|
||||
|
||||
function buildBaseThinkingProfile(defaultLevel?: ThinkLevel | null): ResolvedThinkingProfile {
|
||||
return {
|
||||
levels: BASE_THINKING_LEVELS.map((id) => ({
|
||||
id,
|
||||
label: id,
|
||||
rank: THINKING_LEVEL_RANKS[id],
|
||||
})),
|
||||
defaultLevel,
|
||||
};
|
||||
}
|
||||
|
||||
function buildBinaryThinkingProfile(defaultLevel?: ThinkLevel | null): ResolvedThinkingProfile {
|
||||
return {
|
||||
levels: [
|
||||
{ id: "off", label: "off", rank: THINKING_LEVEL_RANKS.off },
|
||||
{ id: "low", label: "on", rank: THINKING_LEVEL_RANKS.low },
|
||||
],
|
||||
defaultLevel,
|
||||
};
|
||||
}
|
||||
|
||||
function appendProfileLevel(profile: ResolvedThinkingProfile, id: ThinkLevel) {
|
||||
if (profile.levels.some((level) => level.id === id)) {
|
||||
return;
|
||||
}
|
||||
profile.levels.push({ id, label: id, rank: THINKING_LEVEL_RANKS[id] });
|
||||
profile.levels = profile.levels.toSorted((a, b) => a.rank - b.rank);
|
||||
}
|
||||
|
||||
export function resolveThinkingProfile(params: {
|
||||
provider?: string | null;
|
||||
model?: string | null;
|
||||
catalog?: ThinkingCatalogEntry[];
|
||||
}): ResolvedThinkingProfile {
|
||||
const context = resolveThinkingPolicyContext(params);
|
||||
if (!context.normalizedProvider) {
|
||||
return buildBaseThinkingProfile();
|
||||
}
|
||||
const providerContext = {
|
||||
provider: context.normalizedProvider,
|
||||
modelId: context.modelId,
|
||||
reasoning: context.reasoning,
|
||||
};
|
||||
const pluginProfile = resolveProviderThinkingProfile({
|
||||
provider: context.normalizedProvider,
|
||||
context: providerContext,
|
||||
});
|
||||
if (pluginProfile) {
|
||||
const normalized = normalizeThinkingProfile(pluginProfile);
|
||||
if (normalized.levels.length > 0) {
|
||||
return normalized;
|
||||
}
|
||||
}
|
||||
|
||||
const pluginDecision = resolveProviderBinaryThinking({
|
||||
provider: normalizedProvider,
|
||||
const defaultLevel = resolveProviderDefaultThinkingLevel({
|
||||
provider: context.normalizedProvider,
|
||||
context: providerContext,
|
||||
});
|
||||
const binaryDecision = resolveProviderBinaryThinking({
|
||||
provider: context.normalizedProvider,
|
||||
context: {
|
||||
provider: normalizedProvider,
|
||||
modelId: normalizeOptionalString(model) ?? "",
|
||||
provider: context.normalizedProvider,
|
||||
modelId: context.modelId,
|
||||
},
|
||||
});
|
||||
if (typeof pluginDecision === "boolean") {
|
||||
return pluginDecision;
|
||||
const profile =
|
||||
binaryDecision === true
|
||||
? buildBinaryThinkingProfile(defaultLevel)
|
||||
: buildBaseThinkingProfile(defaultLevel);
|
||||
const policyContext = {
|
||||
provider: context.normalizedProvider,
|
||||
modelId: context.modelKey || context.modelId,
|
||||
};
|
||||
if (
|
||||
resolveProviderXHighThinking({
|
||||
provider: context.normalizedProvider,
|
||||
context: policyContext,
|
||||
}) === true
|
||||
) {
|
||||
appendProfileLevel(profile, "xhigh");
|
||||
}
|
||||
return false;
|
||||
return profile;
|
||||
}
|
||||
|
||||
export function isBinaryThinkingProvider(provider?: string | null, model?: string | null): boolean {
|
||||
const profile = resolveThinkingProfile({ provider, model });
|
||||
return profile.levels.length === 2 && profile.levels.some((level) => level.label === "on");
|
||||
}
|
||||
|
||||
function supportsThinkingLevel(
|
||||
provider: string | null | undefined,
|
||||
model: string | null | undefined,
|
||||
level: ThinkLevel,
|
||||
): boolean {
|
||||
return resolveThinkingProfile({ provider, model }).levels.some((entry) => entry.id === level);
|
||||
}
|
||||
|
||||
export function supportsXHighThinking(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) {
|
||||
const pluginDecision = resolveProviderXHighThinking({
|
||||
provider: providerKey,
|
||||
context: {
|
||||
provider: providerKey,
|
||||
modelId: modelKey,
|
||||
},
|
||||
});
|
||||
if (typeof pluginDecision === "boolean") {
|
||||
return pluginDecision;
|
||||
}
|
||||
}
|
||||
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 supportsMaxThinking(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 = resolveProviderMaxThinking({
|
||||
provider: providerKey,
|
||||
context: {
|
||||
provider: providerKey,
|
||||
modelId: modelKey,
|
||||
},
|
||||
});
|
||||
return pluginDecision === true;
|
||||
return supportsThinkingLevel(provider, model, "xhigh");
|
||||
}
|
||||
|
||||
export function listThinkingLevels(provider?: string | null, model?: string | null): ThinkLevel[] {
|
||||
const levels = listThinkingLevelsFallback(provider, model);
|
||||
if (supportsXHighThinking(provider, model)) {
|
||||
levels.push("xhigh");
|
||||
}
|
||||
if (supportsAdaptiveThinking(provider, model)) {
|
||||
levels.push("adaptive");
|
||||
}
|
||||
if (supportsMaxThinking(provider, model)) {
|
||||
levels.push("max");
|
||||
}
|
||||
return levels;
|
||||
const profile = resolveThinkingProfile({ provider, model });
|
||||
return profile.levels.map((level) => level.id);
|
||||
}
|
||||
|
||||
export function listThinkingLevelLabels(provider?: string | null, model?: string | null): string[] {
|
||||
if (isBinaryThinkingProvider(provider, model)) {
|
||||
return ["off", "on"];
|
||||
}
|
||||
return listThinkingLevels(provider, model);
|
||||
const profile = resolveThinkingProfile({ provider, model });
|
||||
return profile.levels.map((level) => level.label);
|
||||
}
|
||||
|
||||
export function formatThinkingLevels(
|
||||
@@ -156,20 +222,13 @@ export function resolveThinkingDefaultForModel(params: {
|
||||
model: string;
|
||||
catalog?: ThinkingCatalogEntry[];
|
||||
}): ThinkLevel {
|
||||
const normalizedProvider = normalizeProviderId(params.provider);
|
||||
const candidate = params.catalog?.find(
|
||||
(entry) => entry.provider === params.provider && entry.id === params.model,
|
||||
);
|
||||
const pluginDecision = resolveProviderDefaultThinkingLevel({
|
||||
provider: normalizedProvider,
|
||||
context: {
|
||||
provider: normalizedProvider,
|
||||
modelId: params.model,
|
||||
reasoning: candidate?.reasoning,
|
||||
},
|
||||
const profile = resolveThinkingProfile({
|
||||
provider: params.provider,
|
||||
model: params.model,
|
||||
catalog: params.catalog,
|
||||
});
|
||||
if (pluginDecision) {
|
||||
return pluginDecision;
|
||||
if (profile.defaultLevel) {
|
||||
return profile.defaultLevel;
|
||||
}
|
||||
return resolveThinkingDefaultForModelFallback(params);
|
||||
}
|
||||
@@ -178,19 +237,19 @@ export function resolveLargestSupportedThinkingLevel(
|
||||
provider?: string | null,
|
||||
model?: string | null,
|
||||
): ThinkLevel {
|
||||
if (isBinaryThinkingProvider(provider, model)) {
|
||||
return "low";
|
||||
}
|
||||
if (supportsMaxThinking(provider, model)) {
|
||||
return "max";
|
||||
}
|
||||
if (supportsXHighThinking(provider, model)) {
|
||||
return "xhigh";
|
||||
}
|
||||
if (supportsAdaptiveThinking(provider, model)) {
|
||||
return "adaptive";
|
||||
}
|
||||
return "high";
|
||||
const profile = resolveThinkingProfile({ provider, model });
|
||||
return (
|
||||
profile.levels.filter((level) => level.id !== "off").toSorted((a, b) => b.rank - a.rank)[0]
|
||||
?.id ?? "off"
|
||||
);
|
||||
}
|
||||
|
||||
export function isThinkingLevelSupported(params: {
|
||||
provider?: string | null;
|
||||
model?: string | null;
|
||||
level: ThinkLevel;
|
||||
}): boolean {
|
||||
return supportsThinkingLevel(params.provider, params.model, params.level);
|
||||
}
|
||||
|
||||
export function resolveSupportedThinkingLevel(params: {
|
||||
@@ -198,10 +257,8 @@ export function resolveSupportedThinkingLevel(params: {
|
||||
model?: string | null;
|
||||
level: ThinkLevel;
|
||||
}): ThinkLevel {
|
||||
if (params.level !== "max") {
|
||||
if (isThinkingLevelSupported(params)) {
|
||||
return params.level;
|
||||
}
|
||||
return supportsMaxThinking(params.provider, params.model)
|
||||
? "max"
|
||||
: resolveLargestSupportedThinkingLevel(params.provider, params.model);
|
||||
return resolveLargestSupportedThinkingLevel(params.provider, params.model);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user