From eae70ecaa8a5ea692b035d9d26e1fafedfbeff3b Mon Sep 17 00:00:00 2001 From: Sayt-0 Date: Thu, 2 Jul 2026 14:22:10 +0200 Subject: [PATCH] fix(anthropic): default adaptive thinking display to summarized Newer Claude models (Opus 4.7+, Fable 5) omit thinking content server-side by default, so effort levels set via /effort or the Shift+Tab cycle silently hid all reasoning in the TUI. Request summarized thinking whenever adaptive thinking is used without an explicit thinking_display; "omitted" remains the opt-out. --- docs/configuration/models/index.md | 4 +- docs/guides/thinking/index.md | 8 +- docs/providers/anthropic/index.md | 12 +- pkg/model/provider/anthropic/thinking.go | 26 +++- pkg/model/provider/anthropic/thinking_test.go | 128 ++++++++++++------ 5 files changed, 116 insertions(+), 62 deletions(-) diff --git a/docs/configuration/models/index.md b/docs/configuration/models/index.md index 9e2a33dff..fd773667f 100644 --- a/docs/configuration/models/index.md +++ b/docs/configuration/models/index.md @@ -387,7 +387,7 @@ models: ## Thinking Display (Anthropic) -For Anthropic Claude models, `thinking_display` controls whether thinking blocks are returned in responses when thinking is enabled. Claude Opus 4.7 hides thinking content by default (`omitted`); set this provider option to receive summarized thinking: +For Anthropic Claude models, `thinking_display` controls whether thinking blocks are returned in responses when thinking is enabled. Newer Claude models (Opus 4.7+, Fable 5) hide thinking content by default (`omitted`); docker-agent requests `summarized` thinking by default for adaptive/effort-based budgets so reasoning stays visible. Set this provider option to override: ```yaml models: @@ -396,7 +396,7 @@ models: model: claude-opus-4-7 thinking_budget: adaptive provider_opts: - thinking_display: summarized # "summarized", "display", or "omitted" + thinking_display: omitted # "summarized", "display", or "omitted" ``` See the [Anthropic provider page]({{ '/providers/anthropic/#thinking-display' | relative_url }}) for details. diff --git a/docs/guides/thinking/index.md b/docs/guides/thinking/index.md index c95a89676..fd1a35dca 100644 --- a/docs/guides/thinking/index.md +++ b/docs/guides/thinking/index.md @@ -153,7 +153,7 @@ models: ### Thinking display -Claude Opus 4.7 hides thinking content by default. Use `thinking_display` in `provider_opts` to control what you receive: +Newer Claude models (Opus 4.7+, Fable 5) hide thinking content by default at the API level. To keep reasoning visible, docker-agent requests `summarized` thinking whenever adaptive/effort-based thinking is used without an explicit `thinking_display`. Use `thinking_display` in `provider_opts` to override: ```yaml models: @@ -162,14 +162,14 @@ models: model: claude-opus-4-7 thinking_budget: adaptive provider_opts: - thinking_display: summarized # summarized | display | omitted + thinking_display: omitted # summarized | display | omitted ``` | Value | Behavior | | ------------ | ------------------------------------------------------------------------------------- | -| `summarized` | Thinking blocks returned with a text summary (default for Claude 4 models pre-4.7). | +| `summarized` | Thinking blocks returned with a text summary (docker-agent default for adaptive thinking). | | `display` | Full thinking blocks returned for display. | -| `omitted` | Thinking blocks hidden — only the signature is returned (default for Opus 4.7). | +| `omitted` | Thinking blocks hidden — only the signature is returned. | Full thinking tokens are billed regardless of `thinking_display`. diff --git a/docs/providers/anthropic/index.md b/docs/providers/anthropic/index.md index 2f159b8e3..30c4f9b1e 100644 --- a/docs/providers/anthropic/index.md +++ b/docs/providers/anthropic/index.md @@ -216,7 +216,7 @@ AI, or the Message Batches API. ## Thinking Display -Controls whether thinking blocks are returned in responses when thinking is enabled. Claude Opus 4.7 hides thinking content by default (`omitted`); earlier Claude 4 models default to `summarized`. Set `thinking_display` in `provider_opts` to override: +Controls whether thinking blocks are returned in responses when thinking is enabled. Newer Claude models (Opus 4.7+, Fable 5) hide thinking content by default (`omitted`); docker-agent counters this by requesting `summarized` thinking whenever an adaptive/effort-based budget is used without an explicit `thinking_display`, so reasoning stays visible in the UI. Set `thinking_display` in `provider_opts` to override: ```yaml models: @@ -225,16 +225,16 @@ models: model: claude-opus-4-7 thinking_budget: adaptive provider_opts: - thinking_display: summarized # "summarized", "display", or "omitted" + thinking_display: omitted # "summarized", "display", or "omitted" ``` Valid values: -- `summarized`: thinking blocks are returned with summarized thinking text (default for Claude 4 models prior to Opus 4.7). -- `display`: thinking blocks are returned for display (use this to re-enable thinking output on Opus 4.7). -- `omitted`: thinking blocks are returned with an empty thinking field; the signature is still returned for multi-turn continuity (default for Opus 4.7). Useful to reduce time-to-first-text-token when streaming. +- `summarized`: thinking blocks are returned with summarized thinking text (docker-agent's default for adaptive/effort-based budgets). +- `display`: thinking blocks are returned for display. +- `omitted`: thinking blocks are returned with an empty thinking field; the signature is still returned for multi-turn continuity. Useful to reduce time-to-first-text-token when streaming. -Note: `thinking_display` applies to both `thinking_budget` with token counts and adaptive/effort-based budgets. Full thinking tokens are billed regardless of the `thinking_display` value. +Note: `thinking_display` applies to both `thinking_budget` with token counts and adaptive/effort-based budgets. For token-count budgets no default is applied (the API already defaults to `summarized`). Full thinking tokens are billed regardless of the `thinking_display` value.
Note diff --git a/pkg/model/provider/anthropic/thinking.go b/pkg/model/provider/anthropic/thinking.go index f5c2d497a..8536f44f9 100644 --- a/pkg/model/provider/anthropic/thinking.go +++ b/pkg/model/provider/anthropic/thinking.go @@ -253,6 +253,20 @@ func anthropicThinkingDisplay(opts map[string]any) (string, bool) { } } +// defaultAdaptiveDisplay returns the thinking display to request with +// adaptive thinking when the user did not configure one. Newer Claude models +// (Opus 4.7+, Fable 5) default to omitted server-side, which silently hides +// all reasoning from the UI — including when an effort level is set via the +// TUI (/effort, Shift+Tab). Requesting summarized keeps reasoning visible and +// matches the documented default of older models; thinking_display: omitted +// remains the explicit opt-out. +func defaultAdaptiveDisplay(display string) string { + if display == "" { + return thinkingDisplaySummarized + } + return display +} + // applyThinkingConfig configures extended thinking on a standard MessageNewParams // based on the model's ThinkingBudget and provider_opts.thinking_display. // Returns true when thinking is enabled (i.e., temperature/top_p must not be set). @@ -264,9 +278,9 @@ func (c *Client) applyThinkingConfig(params *anthropic.MessageNewParams, maxToke display, _ := anthropicThinkingDisplay(c.ModelConfig.ProviderOpts) if effortStr, ok := anthropicThinkingEffort(budget); ok { - adaptive := &anthropic.ThinkingConfigAdaptiveParam{} - if display != "" { - adaptive.Display = anthropic.ThinkingConfigAdaptiveDisplay(display) + display = defaultAdaptiveDisplay(display) + adaptive := &anthropic.ThinkingConfigAdaptiveParam{ + Display: anthropic.ThinkingConfigAdaptiveDisplay(display), } params.Thinking = anthropic.ThinkingConfigParamUnion{OfAdaptive: adaptive} params.OutputConfig.Effort = anthropic.OutputConfigEffort(effortStr) @@ -296,9 +310,9 @@ func (c *Client) applyBetaThinkingConfig(params *anthropic.BetaMessageNewParams, display, _ := anthropicThinkingDisplay(c.ModelConfig.ProviderOpts) if effortStr, ok := anthropicThinkingEffort(budget); ok { - adaptive := &anthropic.BetaThinkingConfigAdaptiveParam{} - if display != "" { - adaptive.Display = anthropic.BetaThinkingConfigAdaptiveDisplay(display) + display = defaultAdaptiveDisplay(display) + adaptive := &anthropic.BetaThinkingConfigAdaptiveParam{ + Display: anthropic.BetaThinkingConfigAdaptiveDisplay(display), } params.Thinking = anthropic.BetaThinkingConfigParamUnion{OfAdaptive: adaptive} params.OutputConfig.Effort = anthropic.BetaOutputConfigEffort(effortStr) diff --git a/pkg/model/provider/anthropic/thinking_test.go b/pkg/model/provider/anthropic/thinking_test.go index 8b0eb14af..2fa786a4a 100644 --- a/pkg/model/provider/anthropic/thinking_test.go +++ b/pkg/model/provider/anthropic/thinking_test.go @@ -163,12 +163,13 @@ func TestApplyThinkingConfig(t *testing.T) { wantEnabled: false, }, { - name: "adaptive high effort without display", - budget: &latest.ThinkingBudget{Effort: "adaptive"}, - maxTokens: 8192, - wantEnabled: true, - wantAdaptive: true, - wantEffort: "high", + name: "adaptive high effort without display defaults to summarized", + budget: &latest.ThinkingBudget{Effort: "adaptive"}, + maxTokens: 8192, + wantEnabled: true, + wantAdaptive: true, + wantEffort: "high", + wantDisplayJSON: "summarized", }, { name: "adaptive with display=summarized", @@ -215,22 +216,24 @@ func TestApplyThinkingConfig(t *testing.T) { wantTokens: 2048, }, { - name: "opus-4-6 token budget auto-switches to adaptive", - model: "claude-opus-4-6", - budget: &latest.ThinkingBudget{Tokens: 4096}, - maxTokens: 8192, - wantEnabled: true, - wantAdaptive: true, - wantEffort: "high", + name: "opus-4-6 token budget auto-switches to adaptive", + model: "claude-opus-4-6", + budget: &latest.ThinkingBudget{Tokens: 4096}, + maxTokens: 8192, + wantEnabled: true, + wantAdaptive: true, + wantEffort: "high", + wantDisplayJSON: "summarized", }, { - name: "opus-4-7 token budget auto-switches to adaptive", - model: "claude-opus-4-7", - budget: &latest.ThinkingBudget{Tokens: 4096}, - maxTokens: 8192, - wantEnabled: true, - wantAdaptive: true, - wantEffort: "high", + name: "opus-4-7 token budget auto-switches to adaptive", + model: "claude-opus-4-7", + budget: &latest.ThinkingBudget{Tokens: 4096}, + maxTokens: 8192, + wantEnabled: true, + wantAdaptive: true, + wantEffort: "high", + wantDisplayJSON: "summarized", }, { name: "opus-4-6 dated variant token budget auto-switches to adaptive", @@ -244,24 +247,50 @@ func TestApplyThinkingConfig(t *testing.T) { wantDisplayJSON: "summarized", }, { - name: "opus-4-6 explicit adaptive budget is preserved", - model: "claude-opus-4-6", - budget: &latest.ThinkingBudget{Effort: "adaptive/low"}, - maxTokens: 8192, - wantEnabled: true, - wantAdaptive: true, - wantEffort: "low", + name: "opus-4-6 explicit adaptive budget is preserved", + model: "claude-opus-4-6", + budget: &latest.ThinkingBudget{Effort: "adaptive/low"}, + maxTokens: 8192, + wantEnabled: true, + wantAdaptive: true, + wantEffort: "low", + wantDisplayJSON: "summarized", }, // Issue #3362: an effort level (as set via the TUI Shift+Tab cycle) uses // adaptive thinking only on models that support it, and falls back to a // token budget everywhere else. { - name: "plain effort level on adaptive model uses adaptive", - budget: &latest.ThinkingBudget{Effort: "high"}, - maxTokens: 8192, - wantEnabled: true, - wantAdaptive: true, - wantEffort: "high", + name: "plain effort level on adaptive model uses adaptive", + budget: &latest.ThinkingBudget{Effort: "high"}, + maxTokens: 8192, + wantEnabled: true, + wantAdaptive: true, + wantEffort: "high", + wantDisplayJSON: "summarized", + }, + // Fable 5 omits thinking content server-side by default; the effort + // levels set via /effort or Shift+Tab must request summarized thinking + // so reasoning stays visible in the TUI. + { + name: "fable-5 effort level defaults display to summarized", + model: "claude-fable-5", + budget: &latest.ThinkingBudget{Effort: "max"}, + maxTokens: 8192, + wantEnabled: true, + wantAdaptive: true, + wantEffort: "max", + wantDisplayJSON: "summarized", + }, + { + name: "fable-5 explicit omitted display is preserved", + model: "claude-fable-5", + budget: &latest.ThinkingBudget{Effort: "high"}, + opts: map[string]any{"thinking_display": "omitted"}, + maxTokens: 8192, + wantEnabled: true, + wantAdaptive: true, + wantEffort: "high", + wantDisplayJSON: "omitted", }, { name: "effort level on non-adaptive model falls back to token budget", @@ -394,12 +423,13 @@ func TestApplyBetaThinkingConfig(t *testing.T) { maxTokens: 8192, }, { - name: "opus-4-6 token budget auto-switches to adaptive", - model: "claude-opus-4-6", - budget: &latest.ThinkingBudget{Tokens: 4096}, - maxTokens: 8192, - wantAdaptive: true, - wantEffort: "high", + name: "opus-4-6 token budget auto-switches to adaptive", + model: "claude-opus-4-6", + budget: &latest.ThinkingBudget{Tokens: 4096}, + maxTokens: 8192, + wantAdaptive: true, + wantEffort: "high", + wantDisplayJSON: "summarized", }, { name: "opus-4-7 token budget auto-switches to adaptive with display", @@ -414,11 +444,21 @@ func TestApplyBetaThinkingConfig(t *testing.T) { // Issue #3362: effort/adaptive budgets fall back to token thinking on // models without adaptive support. { - name: "plain effort on adaptive model uses adaptive", - budget: &latest.ThinkingBudget{Effort: "high"}, - maxTokens: 8192, - wantAdaptive: true, - wantEffort: "high", + name: "plain effort on adaptive model uses adaptive", + budget: &latest.ThinkingBudget{Effort: "high"}, + maxTokens: 8192, + wantAdaptive: true, + wantEffort: "high", + wantDisplayJSON: "summarized", + }, + { + name: "fable-5 effort level defaults display to summarized", + model: "claude-fable-5", + budget: &latest.ThinkingBudget{Effort: "xhigh"}, + maxTokens: 8192, + wantAdaptive: true, + wantEffort: "xhigh", + wantDisplayJSON: "summarized", }, { name: "effort on non-adaptive model falls back to token budget",