From 2af906838746db19d9983512faa5b54738e28522 Mon Sep 17 00:00:00 2001 From: "posthog[bot]" <206114724+posthog[bot]@users.noreply.github.com> Date: Thu, 11 Jun 2026 17:22:03 +0000 Subject: [PATCH] fix(models): default fable-5 to adaptive thinking when no effort is set MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit claude-fable-5 generations without a configured effort level were deterministically rejected by Anthropic with `invalid_request_error: "thinking.type.disabled" is not supported for this model`. With no effort set the SDK falls back to `thinking: { type: "disabled" }`, which fable-5 (adaptive-only) rejects. Gate a new `requiresAdaptiveThinking` capability flag in session/models.ts and, in the effort→options mapping in session/options.ts, default such models to a "medium" effort so the SDK requests `thinking: { type: "adaptive" }` even when the user has not chosen an effort level. Other models keep their existing behavior. Generated-By: PostHog Code Task-Id: 2bd7cca9-5bcf-4889-98cc-6abbd91de2f7 --- .../agent/src/adapters/claude/claude-agent.ts | 1 + .../adapters/claude/session/models.test.ts | 9 +++++ .../src/adapters/claude/session/models.ts | 20 ++++++++++ .../adapters/claude/session/options.test.ts | 38 +++++++++++++++++++ .../src/adapters/claude/session/options.ts | 14 ++++++- 5 files changed, 81 insertions(+), 1 deletion(-) diff --git a/packages/agent/src/adapters/claude/claude-agent.ts b/packages/agent/src/adapters/claude/claude-agent.ts index 0068b74b0..4e7c31817 100644 --- a/packages/agent/src/adapters/claude/claude-agent.ts +++ b/packages/agent/src/adapters/claude/claude-agent.ts @@ -1587,6 +1587,7 @@ export class ClaudeAcpAgent extends BaseAcpAgent { onProcessSpawned: this.options?.onProcessSpawned, onProcessExited: this.options?.onProcessExited, effort, + modelId: earlyModelId, enrichmentDeps: this.enrichment?.deps, enrichedReadCache: this.enrichedReadCache, cloudMode: cloudRun, diff --git a/packages/agent/src/adapters/claude/session/models.test.ts b/packages/agent/src/adapters/claude/session/models.test.ts index 39b812e40..5bb3b7796 100644 --- a/packages/agent/src/adapters/claude/session/models.test.ts +++ b/packages/agent/src/adapters/claude/session/models.test.ts @@ -1,6 +1,7 @@ import { describe, expect, it } from "vitest"; import { getEffortOptions, + requiresAdaptiveThinking, resolveModelPreference, supports1MContext, supportsEffort, @@ -62,6 +63,14 @@ describe("model capability flags", () => { it("keeps deprecated Haiku sessions excluded from MCP injection", () => { expect(supportsMcpInjection("claude-haiku-4-5")).toBe(false); }); + + it("flags only models that reject disabled thinking as requiring adaptive thinking", () => { + expect(requiresAdaptiveThinking("claude-fable-5")).toBe(true); + expect(requiresAdaptiveThinking("claude-opus-4-8")).toBe(false); + expect(requiresAdaptiveThinking("claude-opus-4-7")).toBe(false); + expect(requiresAdaptiveThinking("claude-sonnet-4-6")).toBe(false); + expect(requiresAdaptiveThinking("claude-haiku-4-5")).toBe(false); + }); }); describe("getEffortOptions", () => { diff --git a/packages/agent/src/adapters/claude/session/models.ts b/packages/agent/src/adapters/claude/session/models.ts index 2d87eaa8d..b2e45be85 100644 --- a/packages/agent/src/adapters/claude/session/models.ts +++ b/packages/agent/src/adapters/claude/session/models.ts @@ -1,5 +1,12 @@ +import type { EffortLevel } from "../types"; + export const DEFAULT_MODEL = "opus"; +// Effort level applied when a model needs an explicit effort but the user has +// not chosen one — e.g. models requiring adaptive thinking. Mirrors the +// "medium" fallback used for the effort config option in claude-agent.ts. +export const DEFAULT_EFFORT: EffortLevel = "medium"; + const GATEWAY_TO_SDK_MODEL: Record = { "claude-opus-4-7": "opus", "claude-opus-4-8": "opus", @@ -48,6 +55,19 @@ export function supportsMcpInjection(modelId: string): boolean { return !MODELS_TO_EXCLUDE_MCP_TOOLS.has(modelId); } +// Models that only support adaptive thinking and reject the SDK's default +// `thinking: { type: "disabled" }` request shape. When effort is set the SDK +// emits `thinking: { type: "adaptive" }` + `output_config: { effort }`; when it +// is absent the SDK falls back to `thinking: { type: "disabled" }`, which these +// models reject with `invalid_request_error: "thinking.type.disabled" is not +// supported for this model`. For them we must always send an effort level so +// adaptive thinking is requested even when the user has not picked one. +const MODELS_REQUIRING_ADAPTIVE_THINKING = new Set(["claude-fable-5"]); + +export function requiresAdaptiveThinking(modelId: string): boolean { + return MODELS_REQUIRING_ADAPTIVE_THINKING.has(modelId); +} + interface EffortOption { value: string; name: string; diff --git a/packages/agent/src/adapters/claude/session/options.test.ts b/packages/agent/src/adapters/claude/session/options.test.ts index 031253e45..fcc4c709a 100644 --- a/packages/agent/src/adapters/claude/session/options.test.ts +++ b/packages/agent/src/adapters/claude/session/options.test.ts @@ -185,4 +185,42 @@ describe("buildSessionOptions", () => { expect(headers).toBe(expected); }); }); + + describe("effort", () => { + it("forwards an explicit effort level unchanged", () => { + const options = buildSessionOptions({ + ...makeParams(), + effort: "high", + modelId: "claude-fable-5", + }); + + expect(options.effort).toBe("high"); + }); + + it("defaults effort for models that require adaptive thinking", () => { + const options = buildSessionOptions({ + ...makeParams(), + modelId: "claude-fable-5", + }); + + // Without an effort level the SDK would send `thinking: { type: "disabled" }`, + // which claude-fable-5 rejects. A default effort forces adaptive thinking. + expect(options.effort).toBe("medium"); + }); + + it("leaves effort unset for models that accept disabled thinking", () => { + const options = buildSessionOptions({ + ...makeParams(), + modelId: "claude-opus-4-8", + }); + + expect(options.effort).toBeUndefined(); + }); + + it("leaves effort unset when no model id is provided", () => { + const options = buildSessionOptions(makeParams()); + + expect(options.effort).toBeUndefined(); + }); + }); }); diff --git a/packages/agent/src/adapters/claude/session/options.ts b/packages/agent/src/adapters/claude/session/options.ts index 75a379ad4..51f5a9558 100644 --- a/packages/agent/src/adapters/claude/session/options.ts +++ b/packages/agent/src/adapters/claude/session/options.ts @@ -28,7 +28,11 @@ import type { CodeExecutionMode } from "../tools"; import type { EffortLevel } from "../types"; import { APPENDED_INSTRUCTIONS } from "./instructions"; import { loadUserClaudeJsonMcpServers } from "./mcp-config"; -import { DEFAULT_MODEL } from "./models"; +import { + DEFAULT_EFFORT, + DEFAULT_MODEL, + requiresAdaptiveThinking, +} from "./models"; import type { SettingsManager } from "./settings"; export interface ProcessSpawnedInfo { @@ -56,6 +60,9 @@ export interface BuildOptionsParams { onProcessSpawned?: (info: ProcessSpawnedInfo) => void; onProcessExited?: (pid: number) => void; effort?: EffortLevel; + /** Resolved gateway model id, used to gate model-specific request shaping + * (e.g. forcing adaptive thinking for models that reject disabled thinking). */ + modelId?: string; enrichmentDeps?: FileEnrichmentDeps; enrichedReadCache?: EnrichedReadCache; /** Records PostHog product usage from MCP exec calls (deduped, session-wide). */ @@ -453,6 +460,11 @@ export function buildSessionOptions(params: BuildOptionsParams): Options { if (params.effort) { options.effort = params.effort; + } else if (params.modelId && requiresAdaptiveThinking(params.modelId)) { + // Without an effort level the SDK emits `thinking: { type: "disabled" }`, + // which these models reject outright. Default to adaptive thinking by + // sending an effort level so the SDK requests `thinking: { type: "adaptive" }`. + options.effort = DEFAULT_EFFORT; } clearStatsigCache();