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:
Sid
2026-03-02 11:52:02 +08:00
committed by GitHub
parent ede944371f
commit c9f0d6ac8e
9 changed files with 35 additions and 5 deletions

View File

@@ -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 }>;

View File

@@ -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),

View File

@@ -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":

View File

@@ -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;
} }

View File

@@ -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", () => {

View File

@@ -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;
} }

View File

@@ -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. */

View File

@@ -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(),

View File

@@ -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<