mirror of
https://github.com/openclaw/openclaw.git
synced 2026-05-10 02:52:43 +00:00
feat(agents): support thinkingDefault: "adaptive" for Anthropic models (#31227)
* feat(agents): support `thinkingDefault: "adaptive"` for Anthropic models Anthropic's Opus 4.6 and Sonnet 4.6 support adaptive thinking where the model dynamically decides when and how much to think. This is now Anthropic's recommended mode and `budget_tokens` is deprecated on these models. Add "adaptive" as a valid thinking level: - Config: `agents.defaults.thinkingDefault: "adaptive"` - CLI: `/think adaptive` or `/think auto` - Pi SDK mapping: "adaptive" → "medium" effort at the pi-agent-core layer, which the Anthropic provider translates to `thinking.type: "adaptive"` with `output_config.effort: "medium"` - Provider fallbacks: OpenRouter and Google map "adaptive" to their respective "medium" equivalents Closes #30880 Made-with: Cursor * style(changelog): format changelog with oxfmt * test(types): fix strict typing in runtime/plugin-context tests --------- Co-authored-by: Peter Steinberger <steipete@gmail.com>
This commit is contained in:
@@ -14,7 +14,7 @@ export type ModelRef = {
|
|||||||
model: string;
|
model: string;
|
||||||
};
|
};
|
||||||
|
|
||||||
export type ThinkLevel = "off" | "minimal" | "low" | "medium" | "high" | "xhigh";
|
export type ThinkLevel = "off" | "minimal" | "low" | "medium" | "high" | "xhigh" | "adaptive";
|
||||||
|
|
||||||
export type ModelAliasIndex = {
|
export type ModelAliasIndex = {
|
||||||
byAlias: Map<string, { alias: string; ref: ModelRef }>;
|
byAlias: Map<string, { alias: string; ref: ModelRef }>;
|
||||||
|
|||||||
@@ -1,6 +1,9 @@
|
|||||||
import { describe, expect, it, vi } from "vitest";
|
import { describe, expect, it, vi } from "vitest";
|
||||||
|
|
||||||
const resolvePluginToolsMock = vi.fn(() => []);
|
const resolvePluginToolsMock = vi.fn((params?: unknown) => {
|
||||||
|
void params;
|
||||||
|
return [];
|
||||||
|
});
|
||||||
|
|
||||||
vi.mock("../plugins/tools.js", () => ({
|
vi.mock("../plugins/tools.js", () => ({
|
||||||
resolvePluginTools: (params: unknown) => resolvePluginToolsMock(params),
|
resolvePluginTools: (params: unknown) => resolvePluginToolsMock(params),
|
||||||
|
|||||||
@@ -518,6 +518,9 @@ function mapThinkingLevelToOpenRouterReasoningEffort(
|
|||||||
if (thinkingLevel === "off") {
|
if (thinkingLevel === "off") {
|
||||||
return "none";
|
return "none";
|
||||||
}
|
}
|
||||||
|
if (thinkingLevel === "adaptive") {
|
||||||
|
return "medium";
|
||||||
|
}
|
||||||
return thinkingLevel;
|
return thinkingLevel;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -631,6 +634,7 @@ function mapThinkLevelToGoogleThinkingLevel(
|
|||||||
case "low":
|
case "low":
|
||||||
return "LOW";
|
return "LOW";
|
||||||
case "medium":
|
case "medium":
|
||||||
|
case "adaptive":
|
||||||
return "MEDIUM";
|
return "MEDIUM";
|
||||||
case "high":
|
case "high":
|
||||||
case "xhigh":
|
case "xhigh":
|
||||||
|
|||||||
@@ -6,6 +6,13 @@ export function mapThinkingLevel(level?: ThinkLevel): ThinkingLevel {
|
|||||||
if (!level) {
|
if (!level) {
|
||||||
return "off";
|
return "off";
|
||||||
}
|
}
|
||||||
|
// "adaptive" maps to "medium" at the pi-agent-core layer. The Pi SDK
|
||||||
|
// provider then translates this to `thinking.type: "adaptive"` with
|
||||||
|
// `output_config.effort: "medium"` for models that support it (Opus 4.6,
|
||||||
|
// Sonnet 4.6).
|
||||||
|
if (level === "adaptive") {
|
||||||
|
return "medium";
|
||||||
|
}
|
||||||
return level;
|
return level;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -33,6 +33,12 @@ describe("normalizeThinkLevel", () => {
|
|||||||
it("accepts on as low", () => {
|
it("accepts on as low", () => {
|
||||||
expect(normalizeThinkLevel("on")).toBe("low");
|
expect(normalizeThinkLevel("on")).toBe("low");
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it("accepts adaptive and auto aliases", () => {
|
||||||
|
expect(normalizeThinkLevel("adaptive")).toBe("adaptive");
|
||||||
|
expect(normalizeThinkLevel("auto")).toBe("adaptive");
|
||||||
|
expect(normalizeThinkLevel("Adaptive")).toBe("adaptive");
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
describe("listThinkingLevels", () => {
|
describe("listThinkingLevels", () => {
|
||||||
@@ -54,6 +60,11 @@ describe("listThinkingLevels", () => {
|
|||||||
it("excludes xhigh for non-codex models", () => {
|
it("excludes xhigh for non-codex models", () => {
|
||||||
expect(listThinkingLevels(undefined, "gpt-4.1-mini")).not.toContain("xhigh");
|
expect(listThinkingLevels(undefined, "gpt-4.1-mini")).not.toContain("xhigh");
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it("always includes adaptive", () => {
|
||||||
|
expect(listThinkingLevels(undefined, "gpt-4.1-mini")).toContain("adaptive");
|
||||||
|
expect(listThinkingLevels("anthropic", "claude-opus-4-6")).toContain("adaptive");
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
describe("listThinkingLevelLabels", () => {
|
describe("listThinkingLevelLabels", () => {
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
export type ThinkLevel = "off" | "minimal" | "low" | "medium" | "high" | "xhigh";
|
export type ThinkLevel = "off" | "minimal" | "low" | "medium" | "high" | "xhigh" | "adaptive";
|
||||||
export type VerboseLevel = "off" | "on" | "full";
|
export type VerboseLevel = "off" | "on" | "full";
|
||||||
export type NoticeLevel = "off" | "on" | "full";
|
export type NoticeLevel = "off" | "on" | "full";
|
||||||
export type ElevatedLevel = "off" | "on" | "ask" | "full";
|
export type ElevatedLevel = "off" | "on" | "ask" | "full";
|
||||||
@@ -45,6 +45,9 @@ export function normalizeThinkLevel(raw?: string | null): ThinkLevel | undefined
|
|||||||
}
|
}
|
||||||
const key = raw.trim().toLowerCase();
|
const key = raw.trim().toLowerCase();
|
||||||
const collapsed = key.replace(/[\s_-]+/g, "");
|
const collapsed = key.replace(/[\s_-]+/g, "");
|
||||||
|
if (collapsed === "adaptive" || collapsed === "auto") {
|
||||||
|
return "adaptive";
|
||||||
|
}
|
||||||
if (collapsed === "xhigh" || collapsed === "extrahigh") {
|
if (collapsed === "xhigh" || collapsed === "extrahigh") {
|
||||||
return "xhigh";
|
return "xhigh";
|
||||||
}
|
}
|
||||||
@@ -91,6 +94,7 @@ export function listThinkingLevels(provider?: string | null, model?: string | nu
|
|||||||
if (supportsXHighThinking(provider, model)) {
|
if (supportsXHighThinking(provider, model)) {
|
||||||
levels.push("xhigh");
|
levels.push("xhigh");
|
||||||
}
|
}
|
||||||
|
levels.push("adaptive");
|
||||||
return levels;
|
return levels;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -171,7 +171,7 @@ export type AgentDefaultsConfig = {
|
|||||||
/** Vector memory search configuration (per-agent overrides supported). */
|
/** Vector memory search configuration (per-agent overrides supported). */
|
||||||
memorySearch?: MemorySearchConfig;
|
memorySearch?: MemorySearchConfig;
|
||||||
/** Default thinking level when no /think directive is present. */
|
/** Default thinking level when no /think directive is present. */
|
||||||
thinkingDefault?: "off" | "minimal" | "low" | "medium" | "high" | "xhigh";
|
thinkingDefault?: "off" | "minimal" | "low" | "medium" | "high" | "xhigh" | "adaptive";
|
||||||
/** Default verbose level when no /verbose directive is present. */
|
/** Default verbose level when no /verbose directive is present. */
|
||||||
verboseDefault?: "off" | "on" | "full";
|
verboseDefault?: "off" | "on" | "full";
|
||||||
/** Default elevated level when no /elevated directive is present. */
|
/** Default elevated level when no /elevated directive is present. */
|
||||||
|
|||||||
@@ -125,6 +125,7 @@ export const AgentDefaultsSchema = z
|
|||||||
z.literal("medium"),
|
z.literal("medium"),
|
||||||
z.literal("high"),
|
z.literal("high"),
|
||||||
z.literal("xhigh"),
|
z.literal("xhigh"),
|
||||||
|
z.literal("adaptive"),
|
||||||
])
|
])
|
||||||
.optional(),
|
.optional(),
|
||||||
verboseDefault: z.union([z.literal("off"), z.literal("on"), z.literal("full")]).optional(),
|
verboseDefault: z.union([z.literal("off"), z.literal("on"), z.literal("full")]).optional(),
|
||||||
|
|||||||
@@ -169,7 +169,7 @@ describe("secrets runtime snapshot", () => {
|
|||||||
key: { source: "env", provider: "default", id: "SHADOW_KEY" },
|
key: { source: "env", provider: "default", id: "SHADOW_KEY" },
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
}) as AuthProfileStore,
|
}) as unknown as AuthProfileStore,
|
||||||
});
|
});
|
||||||
|
|
||||||
const profile = snapshot.authStores[0]?.store.profiles["custom:explicit-keyref"] as Record<
|
const profile = snapshot.authStores[0]?.store.profiles["custom:explicit-keyref"] as Record<
|
||||||
|
|||||||
Reference in New Issue
Block a user