Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions docs/configuration/models/index.md
Original file line number Diff line number Diff line change
Expand Up @@ -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:
Expand All @@ -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.
Expand Down
8 changes: 4 additions & 4 deletions docs/guides/thinking/index.md
Original file line number Diff line number Diff line change
Expand Up @@ -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:
Expand All @@ -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`.

Expand Down
12 changes: 6 additions & 6 deletions docs/providers/anthropic/index.md
Original file line number Diff line number Diff line change
Expand Up @@ -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:
Expand All @@ -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.

<div class="callout callout-info" markdown="1">
<div class="callout-title">Note
Expand Down
26 changes: 20 additions & 6 deletions pkg/model/provider/anthropic/thinking.go
Original file line number Diff line number Diff line change
Expand Up @@ -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).
Expand All @@ -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)
Expand Down Expand Up @@ -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)
Expand Down
128 changes: 84 additions & 44 deletions pkg/model/provider/anthropic/thinking_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -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",
Expand Down Expand Up @@ -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",
Expand All @@ -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",
Expand Down Expand Up @@ -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",
Expand All @@ -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",
Expand Down
Loading