Skip to content

Commit a2a3f21

Browse files
authored
Merge pull request #39 from coder/mike/anthropic-thinking-display-coder-2-33
2 parents b2b2fc6 + 70ffdab commit a2a3f21

8 files changed

Lines changed: 369 additions & 32 deletions

File tree

.github/workflows/build.yml

Lines changed: 69 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -2,10 +2,73 @@ name: build
22
on: [push, pull_request]
33

44
jobs:
5+
govulncheck:
6+
runs-on: ubuntu-latest
7+
env:
8+
GH_PAT: "${{ secrets.PERSONAL_ACCESS_TOKEN }}"
9+
GOTOOLCHAIN: local
10+
steps:
11+
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
12+
# The reusable charmbracelet/meta govulncheck job always reads go.mod.
13+
# Keep the module baseline at Go 1.25.0, but run the scanner with Go 1.26.4
14+
# so it uses a fixed standard library.
15+
- uses: actions/setup-go@4a3601121dd01d1626a1e23e37211e3254c1c06c # v6.4.0
16+
with:
17+
go-version: "1.26.4"
18+
cache: true
19+
check-latest: true
20+
- run: |
21+
git config --global url."https://${{ secrets.PERSONAL_ACCESS_TOKEN }}@github.com/charmbracelet".insteadOf "https://github.com/charmbracelet"
22+
git config --global url."https://${{ secrets.PERSONAL_ACCESS_TOKEN }}@github.com/charmcli".insteadOf "https://github.com/charmcli"
23+
if: env.GH_PAT != ''
24+
- run: go install golang.org/x/vuln/cmd/govulncheck@latest
25+
- run: go mod tidy
26+
- run: govulncheck ./...
27+
528
build:
6-
uses: charmbracelet/meta/.github/workflows/build.yml@main
7-
with:
8-
go-version: ""
9-
go-version-file: ./go.mod
10-
secrets:
11-
gh_pat: "${{ secrets.PERSONAL_ACCESS_TOKEN }}"
29+
strategy:
30+
matrix:
31+
os: [ubuntu-latest, macos-latest, windows-latest]
32+
runs-on: ${{ matrix.os }}
33+
env:
34+
GO111MODULE: "on"
35+
GH_PAT: "${{ secrets.PERSONAL_ACCESS_TOKEN }}"
36+
steps:
37+
- name: Checkout code
38+
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
39+
- run: |
40+
git config --global url."https://${{ secrets.PERSONAL_ACCESS_TOKEN }}@github.com/charmbracelet".insteadOf "https://github.com/charmbracelet"
41+
git config --global url."https://${{ secrets.PERSONAL_ACCESS_TOKEN }}@github.com/charmcli".insteadOf "https://github.com/charmcli"
42+
if: env.GH_PAT != ''
43+
- name: Install Go
44+
uses: actions/setup-go@4a3601121dd01d1626a1e23e37211e3254c1c06c # v6.4.0
45+
with:
46+
go-version-file: go.mod
47+
cache: true
48+
- name: Tidy Go modules
49+
run: go mod tidy
50+
- name: Check for changes
51+
run: git diff --exit-code
52+
- name: Build
53+
run: go build ./...
54+
- name: Test
55+
run: go test ./...
56+
57+
dependabot:
58+
needs: [build, govulncheck]
59+
runs-on: ubuntu-latest
60+
permissions:
61+
pull-requests: write
62+
contents: write
63+
if: ${{ github.actor == 'dependabot[bot]' && github.event_name == 'pull_request' }}
64+
steps:
65+
- id: metadata
66+
uses: dependabot/fetch-metadata@25dd0e34f4fe68f24cc83900b1fe3fe149efef98 # v3.1.0
67+
with:
68+
github-token: "${{ secrets.GITHUB_TOKEN }}"
69+
- run: |
70+
gh pr review --approve "$PR_URL"
71+
gh pr merge --squash --auto "$PR_URL"
72+
env:
73+
PR_URL: ${{ github.event.pull_request.html_url }}
74+
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}

go.mod

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -66,11 +66,11 @@ require (
6666
go.opentelemetry.io/otel/metric v1.39.0 // indirect
6767
go.opentelemetry.io/otel/trace v1.39.0 // indirect
6868
go.yaml.in/yaml/v4 v4.0.0-rc.3 // indirect
69-
golang.org/x/crypto v0.47.0 // indirect
70-
golang.org/x/net v0.49.0 // indirect
71-
golang.org/x/sync v0.19.0 // indirect
72-
golang.org/x/sys v0.40.0 // indirect
73-
golang.org/x/text v0.33.0 // indirect
69+
golang.org/x/crypto v0.51.0 // indirect
70+
golang.org/x/net v0.55.0 // indirect
71+
golang.org/x/sync v0.20.0 // indirect
72+
golang.org/x/sys v0.45.0 // indirect
73+
golang.org/x/text v0.37.0 // indirect
7474
golang.org/x/time v0.14.0 // indirect
7575
google.golang.org/api v0.264.0 // indirect
7676
google.golang.org/genproto/googleapis/rpc v0.0.0-20260128011058-8636f8732409 // indirect

go.sum

Lines changed: 10 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -152,18 +152,18 @@ go.opentelemetry.io/otel/trace v1.39.0 h1:2d2vfpEDmCJ5zVYz7ijaJdOF59xLomrvj7bjt6
152152
go.opentelemetry.io/otel/trace v1.39.0/go.mod h1:88w4/PnZSazkGzz/w84VHpQafiU4EtqqlVdxWy+rNOA=
153153
go.yaml.in/yaml/v4 v4.0.0-rc.3 h1:3h1fjsh1CTAPjW7q/EMe+C8shx5d8ctzZTrLcs/j8Go=
154154
go.yaml.in/yaml/v4 v4.0.0-rc.3/go.mod h1:aZqd9kCMsGL7AuUv/m/PvWLdg5sjJsZ4oHDEnfPPfY0=
155-
golang.org/x/crypto v0.47.0 h1:V6e3FRj+n4dbpw86FJ8Fv7XVOql7TEwpHapKoMJ/GO8=
156-
golang.org/x/crypto v0.47.0/go.mod h1:ff3Y9VzzKbwSSEzWqJsJVBnWmRwRSHt/6Op5n9bQc4A=
157-
golang.org/x/net v0.49.0 h1:eeHFmOGUTtaaPSGNmjBKpbng9MulQsJURQUAfUwY++o=
158-
golang.org/x/net v0.49.0/go.mod h1:/ysNB2EvaqvesRkuLAyjI1ycPZlQHM3q01F02UY/MV8=
155+
golang.org/x/crypto v0.51.0 h1:IBPXwPfKxY7cWQZ38ZCIRPI50YLeevDLlLnyC5wRGTI=
156+
golang.org/x/crypto v0.51.0/go.mod h1:8AdwkbraGNABw2kOX6YFPs3WM22XqI4EXEd8g+x7Oc8=
157+
golang.org/x/net v0.55.0 h1:bcvxaJn3e1U6InsFWt1JUq1aSjnRxLzT2rtD2KfkDF8=
158+
golang.org/x/net v0.55.0/go.mod h1:L5U2KuzuOe1lY7Z+aWVIKK6qEeJXnXV9yzGA+WCHJww=
159159
golang.org/x/oauth2 v0.36.0 h1:peZ/1z27fi9hUOFCAZaHyrpWG5lwe0RJEEEeH0ThlIs=
160160
golang.org/x/oauth2 v0.36.0/go.mod h1:YDBUJMTkDnJS+A4BP4eZBjCqtokkg1hODuPjwiGPO7Q=
161-
golang.org/x/sync v0.19.0 h1:vV+1eWNmZ5geRlYjzm2adRgW2/mcpevXNg50YZtPCE4=
162-
golang.org/x/sync v0.19.0/go.mod h1:9KTHXmSnoGruLpwFjVSX0lNNA75CykiMECbovNTZqGI=
163-
golang.org/x/sys v0.40.0 h1:DBZZqJ2Rkml6QMQsZywtnjnnGvHza6BTfYFWY9kjEWQ=
164-
golang.org/x/sys v0.40.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks=
165-
golang.org/x/text v0.33.0 h1:B3njUFyqtHDUI5jMn1YIr5B0IE2U0qck04r6d4KPAxE=
166-
golang.org/x/text v0.33.0/go.mod h1:LuMebE6+rBincTi9+xWTY8TztLzKHc/9C1uBCG27+q8=
161+
golang.org/x/sync v0.20.0 h1:e0PTpb7pjO8GAtTs2dQ6jYa5BWYlMuX047Dco/pItO4=
162+
golang.org/x/sync v0.20.0/go.mod h1:9xrNwdLfx4jkKbNva9FpL6vEN7evnE43NNNJQ2LF3+0=
163+
golang.org/x/sys v0.45.0 h1:dO4czNzziLiiXplLQgBCEpCvXQ3dnkn0SdaZSYdQ+FY=
164+
golang.org/x/sys v0.45.0/go.mod h1:4GL1E5IUh+htKOUEOaiffhrAeqysfVGipDYzABqnCmw=
165+
golang.org/x/text v0.37.0 h1:Cqjiwd9eSg8e0QAkyCaQTNHFIIzWtidPahFWR83rTrc=
166+
golang.org/x/text v0.37.0/go.mod h1:a5sjxXGs9hsn/AJVwuElvCAo9v8QYLzvavO5z2PiM38=
167167
golang.org/x/time v0.14.0 h1:MRx4UaLrDotUKUdCIqzPC48t1Y9hANFKIRpNx+Te8PI=
168168
golang.org/x/time v0.14.0/go.mod h1:eL/Oa2bBBK0TkX57Fyni+NgnyQQN4LitPmob2Hjnqw4=
169169
gonum.org/v1/gonum v0.16.0 h1:5+ul4Swaf3ESvrOnidPp4GZbzf0mxVQpDCYUQE7OJfk=

providers/anthropic/anthropic.go

Lines changed: 67 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ import (
1111
"io"
1212
"maps"
1313
"math"
14+
"strconv"
1415
"strings"
1516

1617
"charm.land/fantasy"
@@ -38,6 +39,51 @@ func betaRequestOptions(flags []string) []option.RequestOption {
3839
return opts
3940
}
4041

42+
func thinkingDisplay(providerOptions *ProviderOptions, modelID string) (ThinkingDisplay, bool) {
43+
if providerOptions != nil && providerOptions.ThinkingDisplay != nil && *providerOptions.ThinkingDisplay != "" {
44+
return *providerOptions.ThinkingDisplay, true
45+
}
46+
if defaultsToOmittedThinkingDisplay(modelID) {
47+
return ThinkingDisplaySummarized, true
48+
}
49+
return "", false
50+
}
51+
52+
func defaultsToAdaptiveThinking(model string) bool {
53+
model = strings.ToLower(strings.TrimSpace(model))
54+
return strings.Contains(model, "claude-mythos-preview")
55+
}
56+
57+
func requiresAdaptiveThinking(model string) bool {
58+
return defaultsToAdaptiveThinking(model) || defaultsToOmittedOpusThinkingDisplay(model)
59+
}
60+
61+
func setThinkingDisplay(param interface{ SetExtraFields(map[string]any) }, display ThinkingDisplay) {
62+
param.SetExtraFields(map[string]any{"display": string(display)})
63+
}
64+
65+
func defaultsToOmittedThinkingDisplay(model string) bool {
66+
model = strings.ToLower(strings.TrimSpace(model))
67+
return defaultsToAdaptiveThinking(model) || defaultsToOmittedOpusThinkingDisplay(model)
68+
}
69+
70+
func defaultsToOmittedOpusThinkingDisplay(model string) bool {
71+
_, suffix, ok := strings.Cut(model, "claude-opus-4-")
72+
if !ok {
73+
return false
74+
}
75+
76+
versionEnd := 0
77+
for versionEnd < len(suffix) && suffix[versionEnd] >= '0' && suffix[versionEnd] <= '9' {
78+
versionEnd++
79+
}
80+
if versionEnd == 0 || versionEnd > 2 {
81+
return false
82+
}
83+
minor, err := strconv.Atoi(suffix[:versionEnd])
84+
return err == nil && minor >= 7
85+
}
86+
4187
// buildRequestOptions constructs the common request options shared
4288
// by Generate and Stream: user-agent, raw tool injection, and any
4389
// beta API flags.
@@ -335,12 +381,26 @@ func (a languageModel) prepareParams(call fantasy.Call) (
335381
Effort: anthropic.OutputConfigEffort(effort),
336382
}
337383
adaptive := anthropic.NewThinkingConfigAdaptiveParam()
384+
if display, ok := thinkingDisplay(providerOptions, a.modelID); ok {
385+
setThinkingDisplay(&adaptive, display)
386+
}
338387
params.Thinking.OfAdaptive = &adaptive
339388
case providerOptions.Thinking != nil:
340389
if providerOptions.Thinking.BudgetTokens == 0 {
341390
return nil, nil, nil, nil, &fantasy.Error{Title: "no budget", Message: "thinking requires budget"}
342391
}
343-
params.Thinking = anthropic.ThinkingConfigParamOfEnabled(providerOptions.Thinking.BudgetTokens)
392+
if requiresAdaptiveThinking(a.modelID) {
393+
adaptive := anthropic.NewThinkingConfigAdaptiveParam()
394+
if display, ok := thinkingDisplay(providerOptions, a.modelID); ok {
395+
setThinkingDisplay(&adaptive, display)
396+
}
397+
params.Thinking.OfAdaptive = &adaptive
398+
} else {
399+
params.Thinking = anthropic.ThinkingConfigParamOfEnabled(providerOptions.Thinking.BudgetTokens)
400+
if display, ok := thinkingDisplay(providerOptions, a.modelID); ok {
401+
setThinkingDisplay(params.Thinking.OfEnabled, display)
402+
}
403+
}
344404
if call.Temperature != nil {
345405
params.Temperature = param.Opt[float64]{}
346406
warnings = append(warnings, fantasy.CallWarning{
@@ -365,6 +425,12 @@ func (a languageModel) prepareParams(call fantasy.Call) (
365425
Details: "TopK is not supported when thinking is enabled",
366426
})
367427
}
428+
case defaultsToAdaptiveThinking(a.modelID):
429+
adaptive := anthropic.NewThinkingConfigAdaptiveParam()
430+
if display, ok := thinkingDisplay(providerOptions, a.modelID); ok {
431+
setThinkingDisplay(&adaptive, display)
432+
}
433+
params.Thinking.OfAdaptive = &adaptive
368434
}
369435

370436
if len(call.Tools) > 0 {

0 commit comments

Comments
 (0)