Skip to content

Commit 6debe67

Browse files
yanmxaclaude
andcommitted
feat: add extended thinking support with per-turn keyword detection
Add ThinkingLevel type with Off/Normal/High/Ultra levels and token budgets. Detect thinking keywords in user messages (think hard, ultrathink, etc.) to set per-turn thinking overrides that reset after each turn completes. - Add ThinkingLevel enum with budget tokens and cycling support - Add thinking parameters to Anthropic, OpenAI, Google, and Alibaba clients - Render thinking indicator in mode status bar - Track thinking signatures across streaming for multi-turn continuity - Support thinking toggle command in registry - Fix skill invocation message ordering Co-Authored-By: Claude Opus 4 <noreply@anthropic.com> Signed-off-by: Meng Yan <myan@redhat.com>
1 parent a238257 commit 6debe67

24 files changed

Lines changed: 422 additions & 121 deletions

File tree

go.mod

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -4,18 +4,17 @@ go 1.25.6
44

55
require (
66
github.com/JohannesKaufmann/html-to-markdown v1.6.0
7-
github.com/alecthomas/chroma/v2 v2.20.0
8-
github.com/anthropics/anthropic-sdk-go v1.19.0
7+
github.com/anthropics/anthropic-sdk-go v1.27.1
98
github.com/bmatcuk/doublestar/v4 v4.9.2
109
github.com/charmbracelet/bubbles v0.21.0
1110
github.com/charmbracelet/bubbletea v1.3.10
11+
github.com/charmbracelet/glamour v1.0.0
1212
github.com/charmbracelet/lipgloss v1.1.1-0.20250404203927-76690c660834
1313
github.com/hexops/gotextdiff v1.0.3
1414
github.com/joho/godotenv v1.5.1
1515
github.com/mattn/go-runewidth v0.0.17
1616
github.com/openai/openai-go/v3 v3.19.0
1717
github.com/spf13/cobra v1.8.1
18-
github.com/yuin/goldmark v1.7.13
1918
go.uber.org/zap v1.27.0
2019
google.golang.org/genai v1.43.0
2120
gopkg.in/natefinch/lumberjack.v2 v2.2.1
@@ -28,12 +27,12 @@ require (
2827
cloud.google.com/go/auth/oauth2adapt v0.2.4 // indirect
2928
cloud.google.com/go/compute/metadata v0.5.0 // indirect
3029
github.com/PuerkitoBio/goquery v1.9.2 // indirect
30+
github.com/alecthomas/chroma/v2 v2.20.0 // indirect
3131
github.com/andybalholm/cascadia v1.3.2 // indirect
3232
github.com/atotto/clipboard v0.1.4 // indirect
3333
github.com/aymanbagabas/go-osc52/v2 v2.0.1 // indirect
3434
github.com/aymerick/douceur v0.2.0 // indirect
3535
github.com/charmbracelet/colorprofile v0.2.3-0.20250311203215-f60798e515dc // indirect
36-
github.com/charmbracelet/glamour v1.0.0 // indirect
3736
github.com/charmbracelet/x/ansi v0.10.2 // indirect
3837
github.com/charmbracelet/x/cellbuf v0.0.13 // indirect
3938
github.com/charmbracelet/x/exp/slice v0.0.0-20250327172914-2fdc97757edf // indirect
@@ -65,6 +64,7 @@ require (
6564
github.com/tidwall/pretty v1.2.1 // indirect
6665
github.com/tidwall/sjson v1.2.5 // indirect
6766
github.com/xo/terminfo v0.0.0-20220910002029-abceb7e1c41e // indirect
67+
github.com/yuin/goldmark v1.7.13 // indirect
6868
github.com/yuin/goldmark-emoji v1.0.6 // indirect
6969
go.opencensus.io v0.24.0 // indirect
7070
go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.54.0 // indirect

go.sum

Lines changed: 8 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -14,20 +14,16 @@ github.com/MakeNowJust/heredoc v1.0.0 h1:cXCdzVdstXyiTqTvfqk9SDHpKNjxuom+DOlyEeQ
1414
github.com/MakeNowJust/heredoc v1.0.0/go.mod h1:mG5amYoWBHf8vpLOuehzbGGw0EHxpZZ6lCpQ4fNJ8LE=
1515
github.com/PuerkitoBio/goquery v1.9.2 h1:4/wZksC3KgkQw7SQgkKotmKljk0M6V8TUvA8Wb4yPeE=
1616
github.com/PuerkitoBio/goquery v1.9.2/go.mod h1:GHPCaP0ODyyxqcNoFGYlAprUFH81NuRPd0GX3Zu2Mvk=
17-
github.com/alecthomas/assert/v2 v2.7.0 h1:QtqSACNS3tF7oasA8CU6A6sXZSBDqnm7RfpLl9bZqbE=
18-
github.com/alecthomas/assert/v2 v2.7.0/go.mod h1:Bze95FyfUr7x34QZrjL+XP+0qgp/zg8yS+TtBj1WA3k=
1917
github.com/alecthomas/assert/v2 v2.11.0 h1:2Q9r3ki8+JYXvGsDyBXwH3LcJ+WK5D0gc5E8vS6K3D0=
20-
github.com/alecthomas/chroma/v2 v2.14.0 h1:R3+wzpnUArGcQz7fCETQBzO5n9IMNi13iIs46aU4V9E=
21-
github.com/alecthomas/chroma/v2 v2.14.0/go.mod h1:QolEbTfmUHIMVpBqxeDnNBj2uoeI4EbYP4i6n68SG4I=
18+
github.com/alecthomas/assert/v2 v2.11.0/go.mod h1:Bze95FyfUr7x34QZrjL+XP+0qgp/zg8yS+TtBj1WA3k=
2219
github.com/alecthomas/chroma/v2 v2.20.0 h1:sfIHpxPyR07/Oylvmcai3X/exDlE8+FA820NTz+9sGw=
2320
github.com/alecthomas/chroma/v2 v2.20.0/go.mod h1:e7tViK0xh/Nf4BYHl00ycY6rV7b8iXBksI9E359yNmA=
24-
github.com/alecthomas/repr v0.4.0 h1:GhI2A8MACjfegCPVq9f1FLvIBS+DrQ2KQBFZP1iFzXc=
25-
github.com/alecthomas/repr v0.4.0/go.mod h1:Fr0507jx4eOXV7AlPV6AVZLYrLIuIeSOWtW57eE/O/4=
2621
github.com/alecthomas/repr v0.5.1 h1:E3G4t2QbHTSNpPKBgMTln5KLkZHLOcU7r37J4pXBuIg=
22+
github.com/alecthomas/repr v0.5.1/go.mod h1:Fr0507jx4eOXV7AlPV6AVZLYrLIuIeSOWtW57eE/O/4=
2723
github.com/andybalholm/cascadia v1.3.2 h1:3Xi6Dw5lHF15JtdcmAHD3i1+T8plmv7BQ/nsViSLyss=
2824
github.com/andybalholm/cascadia v1.3.2/go.mod h1:7gtRlve5FxPPgIgX36uWBX58OdBsSS6lUvCFb+h7KvU=
29-
github.com/anthropics/anthropic-sdk-go v1.19.0 h1:mO6E+ffSzLRvR/YUH9KJC0uGw0uV8GjISIuzem//3KE=
30-
github.com/anthropics/anthropic-sdk-go v1.19.0/go.mod h1:WTz31rIUHUHqai2UslPpw5CwXrQP3geYBioRV4WOLvE=
25+
github.com/anthropics/anthropic-sdk-go v1.27.1 h1:7DgMZ2Ng3C2mPzJGHA30NXQTZolcF07mHd0tGaLwfzk=
26+
github.com/anthropics/anthropic-sdk-go v1.27.1/go.mod h1:qUKmaW+uuPB64iy1l+4kOSvaLqPXnHTTBKH6RVZ7q5Q=
3127
github.com/atotto/clipboard v0.1.4 h1:EH0zSVneZPSuFR11BlR9YppQTVDbh5+16AmcJi4g1z4=
3228
github.com/atotto/clipboard v0.1.4/go.mod h1:ZY9tmq7sm5xIbd9bOK4onWV4S6X0u6GY7Vn0Yu86PYI=
3329
github.com/aymanbagabas/go-osc52/v2 v2.0.1 h1:HwpRHbFMcZLEVr42D4p7XBqjyuxQH5SMiErDT4WkJ2k=
@@ -49,12 +45,12 @@ github.com/charmbracelet/glamour v1.0.0 h1:AWMLOVFHTsysl4WV8T8QgkQ0s/ZNZo7CiE4WK
4945
github.com/charmbracelet/glamour v1.0.0/go.mod h1:DSdohgOBkMr2ZQNhw4LZxSGpx3SvpeujNoXrQyH2hxo=
5046
github.com/charmbracelet/lipgloss v1.1.1-0.20250404203927-76690c660834 h1:ZR7e0ro+SZZiIZD7msJyA+NjkCNNavuiPBLgerbOziE=
5147
github.com/charmbracelet/lipgloss v1.1.1-0.20250404203927-76690c660834/go.mod h1:aKC/t2arECF6rNOnaKaVU6y4t4ZeHQzqfxedE/VkVhA=
52-
github.com/charmbracelet/x/ansi v0.10.1 h1:rL3Koar5XvX0pHGfovN03f5cxLbCF2YvLeyz7D2jVDQ=
53-
github.com/charmbracelet/x/ansi v0.10.1/go.mod h1:3RQDQ6lDnROptfpWuUVIUG64bD2g2BgntdxH0Ya5TeE=
5448
github.com/charmbracelet/x/ansi v0.10.2 h1:ith2ArZS0CJG30cIUfID1LXN7ZFXRCww6RUvAPA+Pzw=
5549
github.com/charmbracelet/x/ansi v0.10.2/go.mod h1:HbLdJjQH4UH4AqA2HpRWuWNluRE6zxJH/yteYEYCFa8=
5650
github.com/charmbracelet/x/cellbuf v0.0.13 h1:/KBBKHuVRbq1lYx5BzEHBAFBP8VcQzJejZ/IA3iR28k=
5751
github.com/charmbracelet/x/cellbuf v0.0.13/go.mod h1:xe0nKWGd3eJgtqZRaN9RjMtK7xUYchjzPr7q6kcvCCs=
52+
github.com/charmbracelet/x/exp/golden v0.0.0-20241011142426-46044092ad91 h1:payRxjMjKgx2PaCWLZ4p3ro9y97+TVLZNaRZgJwSVDQ=
53+
github.com/charmbracelet/x/exp/golden v0.0.0-20241011142426-46044092ad91/go.mod h1:wDlXFlCrmJ8J+swcL/MnGUuYnqgQdW9rhSD61oNMb6U=
5854
github.com/charmbracelet/x/exp/slice v0.0.0-20250327172914-2fdc97757edf h1:rLG0Yb6MQSDKdB52aGX55JT1oi0P0Kuaj7wi1bLUpnI=
5955
github.com/charmbracelet/x/exp/slice v0.0.0-20250327172914-2fdc97757edf/go.mod h1:B3UgsnsBZS/eX42BlaNiJkD1pPOUa+oF1IYC6Yd2CEU=
6056
github.com/charmbracelet/x/term v0.2.1 h1:AQeHeLZ1OqSXhrAWpYUtZyX1T3zVxfpZuEQMIQaGIAQ=
@@ -65,10 +61,10 @@ github.com/cpuguy83/go-md2man/v2 v2.0.4/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46t
6561
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
6662
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
6763
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
68-
github.com/dlclark/regexp2 v1.11.0 h1:G/nrcoOa7ZXlpoa/91N3X7mM3r8eIlMBBJZvsz/mxKI=
69-
github.com/dlclark/regexp2 v1.11.0/go.mod h1:DHkYz0B9wPfa6wondMfaivmHpzrQ3v9q8cnmRbL6yW8=
7064
github.com/dlclark/regexp2 v1.11.5 h1:Q/sSnsKerHeCkc/jSTNq1oCm7KiVgUMZRDUoRu0JQZQ=
7165
github.com/dlclark/regexp2 v1.11.5/go.mod h1:DHkYz0B9wPfa6wondMfaivmHpzrQ3v9q8cnmRbL6yW8=
66+
github.com/dnaeon/go-vcr v1.2.0 h1:zHCHvJYTMh1N7xnV7zf1m1GPBF9Ad0Jk/whtQ1663qI=
67+
github.com/dnaeon/go-vcr v1.2.0/go.mod h1:R4UdLID7HZT3taECzJs4YgbbH6PIGXB6W/sc5OLb6RQ=
7268
github.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4=
7369
github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4=
7470
github.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1mIlRU8Am5FuJP05cCM98=
@@ -127,17 +123,13 @@ github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
127123
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
128124
github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
129125
github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
130-
github.com/lucasb-eyer/go-colorful v1.2.0 h1:1nnpGOrhyZZuNyfu1QjKiUICQ74+3FNCN69Aj6K7nkY=
131-
github.com/lucasb-eyer/go-colorful v1.2.0/go.mod h1:R4dSotOR9KMtayYi1e77YzuveK+i7ruzyGqttikkLy0=
132126
github.com/lucasb-eyer/go-colorful v1.3.0 h1:2/yBRLdWBZKrf7gB40FoiKfAWYQ0lqNcbuQwVHXptag=
133127
github.com/lucasb-eyer/go-colorful v1.3.0/go.mod h1:R4dSotOR9KMtayYi1e77YzuveK+i7ruzyGqttikkLy0=
134128
github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY=
135129
github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
136130
github.com/mattn/go-localereader v0.0.1 h1:ygSAOl7ZXTx4RdPYinUpg6W99U8jWvWi9Ye2JC/oIi4=
137131
github.com/mattn/go-localereader v0.0.1/go.mod h1:8fBrzywKY7BI3czFoHkuzRoWE9C+EiG4R1k4Cjx5p88=
138132
github.com/mattn/go-runewidth v0.0.12/go.mod h1:RAqKPSqVFrSLVXbA8x7dzmKdmGzieGRCM46jaSJTDAk=
139-
github.com/mattn/go-runewidth v0.0.16 h1:E5ScNMtiwvlvB5paMFdw9p4kSQzbXFikJ5SQO6TULQc=
140-
github.com/mattn/go-runewidth v0.0.16/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w=
141133
github.com/mattn/go-runewidth v0.0.17 h1:78v8ZlW0bP43XfmAfPsdXcoNCelfMHsDmd/pkENfrjQ=
142134
github.com/mattn/go-runewidth v0.0.17/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w=
143135
github.com/microcosm-cc/bluemonday v1.0.27 h1:MpEUotklkwCSLeH+Qdx1VJgNqLlpY2KXwXFM08ygZfk=
@@ -194,8 +186,6 @@ github.com/xo/terminfo v0.0.0-20220910002029-abceb7e1c41e h1:JVG44RsyaB9T2KIHavM
194186
github.com/xo/terminfo v0.0.0-20220910002029-abceb7e1c41e/go.mod h1:RbqR21r5mrJuqunuUZ/Dhy/avygyECGrLceyNeo4LiM=
195187
github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY=
196188
github.com/yuin/goldmark v1.7.1/go.mod h1:uzxRWxtg69N339t3louHJ7+O03ezfj6PlliRlaOzY1E=
197-
github.com/yuin/goldmark v1.7.8 h1:iERMLn0/QJeHFhxSt3p6PeN9mGnvIKSpG9YYorDMnic=
198-
github.com/yuin/goldmark v1.7.8/go.mod h1:uzxRWxtg69N339t3louHJ7+O03ezfj6PlliRlaOzY1E=
199189
github.com/yuin/goldmark v1.7.13 h1:GPddIs617DnBLFFVJFgpo1aBfe/4xcvMc3SB5t/D0pA=
200190
github.com/yuin/goldmark v1.7.13/go.mod h1:ip/1k0VRfGynBgxOz0yCqHrbZXhcjxyuS66Brc7iBKg=
201191
github.com/yuin/goldmark-emoji v1.0.6 h1:QWfF2FYaXwL74tfGOW5izeiZepUDroDJfWubQI9HTHs=
@@ -259,8 +249,6 @@ golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJ
259249
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
260250
golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
261251
golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
262-
golang.org/x/sync v0.16.0 h1:ycBJEhp9p4vXvUZNszeOq0kGTPghopOL8q0fq3vstxw=
263-
golang.org/x/sync v0.16.0/go.mod h1:1dzgHSNfp02xaA81J2MS99Qcpr2w7fw1gpm99rleRqA=
264252
golang.org/x/sync v0.17.0 h1:l60nONMj9l5drqw6jlhIELNv9I0A4OFgRsG9k2oT9Ug=
265253
golang.org/x/sync v0.17.0/go.mod h1:9KTHXmSnoGruLpwFjVSX0lNNA75CykiMECbovNTZqGI=
266254
golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
@@ -279,8 +267,6 @@ golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
279267
golang.org/x/sys v0.17.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
280268
golang.org/x/sys v0.19.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
281269
golang.org/x/sys v0.20.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
282-
golang.org/x/sys v0.36.0 h1:KVRy2GtZBrk1cBYA7MKu5bEZFxQk4NIDV6RLVcC8o0k=
283-
golang.org/x/sys v0.36.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks=
284270
golang.org/x/sys v0.37.0 h1:fdNQudmxPjkdUTPnLn5mdQv7Zwvbvpaxqs831goi9kQ=
285271
golang.org/x/sys v0.37.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks=
286272
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
@@ -300,8 +286,6 @@ golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
300286
golang.org/x/text v0.9.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8=
301287
golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU=
302288
golang.org/x/text v0.15.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU=
303-
golang.org/x/text v0.27.0 h1:4fGWRpyh641NLlecmyl4LOe6yDdfaYNrGb2zdfo4JV4=
304-
golang.org/x/text v0.27.0/go.mod h1:1D28KMCvyooCX9hBiosv5Tz/+YLxj0j7XhWjpSUF7CU=
305289
golang.org/x/text v0.30.0 h1:yznKA/E9zq54KzlzBEAWn1NXSQ8DIp/NYMy88xJjl4k=
306290
golang.org/x/text v0.30.0/go.mod h1:yDdHFIX9t+tORqspjENWgzaCVXgk0yYnYuSZ8UzzBVM=
307291
golang.org/x/time v0.6.0 h1:eTDhh4ZXt5Qf0augr54TN6suAUudPcawVZeIAPU7D4U=

internal/app/command/registry.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,7 @@ func builtinCommands() []Info {
3636
{Name: "memory", Description: "View and manage memory files (list/show/edit) with @import support"},
3737
{Name: "mcp", Description: "Manage MCP servers (add/edit/remove/connect/list)"},
3838
{Name: "plugin", Description: "Manage plugins (list/enable/disable/info)"},
39+
{Name: "think", Description: "Toggle thinking level (off/think/think+/ultrathink)"},
3940
}
4041
}
4142

internal/app/conversation/conversation.go

Lines changed: 13 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,13 @@ func (m *Model) SetLastToolCalls(calls []message.ToolCall) {
4242
}
4343
}
4444

45+
// SetLastThinkingSignature sets the thinking signature on the last message.
46+
func (m *Model) SetLastThinkingSignature(sig string) {
47+
if len(m.Messages) > 0 && sig != "" {
48+
m.Messages[len(m.Messages)-1].ThinkingSignature = sig
49+
}
50+
}
51+
4552
// AppendErrorToLast appends an error to the last message content.
4653
func (m *Model) AppendErrorToLast(err error) {
4754
if len(m.Messages) > 0 {
@@ -129,11 +136,12 @@ func (m Model) ConvertToProviderFrom(startIdx int) []message.Message {
129136
}
130137

131138
providerMsg := message.Message{
132-
Role: msg.Role,
133-
Content: msg.Content,
134-
Images: msg.Images,
135-
ToolCalls: msg.ToolCalls,
136-
Thinking: msg.Thinking,
139+
Role: msg.Role,
140+
Content: msg.Content,
141+
Images: msg.Images,
142+
ToolCalls: msg.ToolCalls,
143+
Thinking: msg.Thinking,
144+
ThinkingSignature: msg.ThinkingSignature,
137145
}
138146

139147
if msg.ToolResult != nil {

internal/app/conversation/state.go

Lines changed: 8 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -24,13 +24,14 @@ func (s *StreamState) Stop() {
2424

2525
// ChunkMsg carries a single streaming chunk from the LLM.
2626
type ChunkMsg struct {
27-
Text string
28-
Thinking string
29-
Done bool
30-
Err error
31-
ToolCalls []message.ToolCall
32-
BuildingToolName string
33-
Usage *message.Usage
27+
Text string
28+
Thinking string
29+
ThinkingSignature string // Anthropic: opaque signature for thinking block replay
30+
Done bool
31+
Err error
32+
ToolCalls []message.ToolCall
33+
BuildingToolName string
34+
Usage *message.Usage
3435
}
3536

3637
// ContinueMsg requests a follow-up LLM call with the given messages.

internal/app/handler_command.go

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ import (
77
"os/exec"
88
"sort"
99
"strings"
10+
"time"
1011

1112
tea "github.com/charmbracelet/bubbletea"
1213

@@ -20,6 +21,8 @@ import (
2021
"github.com/yanmxa/gencode/internal/skill"
2122
"github.com/yanmxa/gencode/internal/tool"
2223
"github.com/yanmxa/gencode/internal/tool/ui"
24+
25+
appprovider "github.com/yanmxa/gencode/internal/app/provider"
2326
)
2427

2528
type CommandHandler func(ctx context.Context, m *model, args string) (string, tea.Cmd, error)
@@ -75,6 +78,7 @@ func handlerRegistry() map[string]CommandHandler {
7578
result, err := appplugin.HandleCommand(ctx, &m.plugin.Selector, m.cwd, m.width, m.height, args)
7679
return result, nil, err
7780
},
81+
"think": handleThinkCommand,
7882
}
7983
}
8084

@@ -259,3 +263,26 @@ func handleAgentCommand(ctx context.Context, m *model, args string) (string, tea
259263
}
260264
return "", nil, nil
261265
}
266+
267+
func handleThinkCommand(ctx context.Context, m *model, args string) (string, tea.Cmd, error) {
268+
args = strings.TrimSpace(strings.ToLower(args))
269+
270+
switch args {
271+
case "off", "0":
272+
m.provider.ThinkingLevel = provider.ThinkingOff
273+
case "", "toggle":
274+
// Cycle to next level
275+
m.provider.ThinkingLevel = m.provider.ThinkingLevel.Next()
276+
case "think", "normal", "1":
277+
m.provider.ThinkingLevel = provider.ThinkingNormal
278+
case "think+", "high", "2":
279+
m.provider.ThinkingLevel = provider.ThinkingHigh
280+
case "ultra", "ultrathink", "max", "3":
281+
m.provider.ThinkingLevel = provider.ThinkingUltra
282+
default:
283+
return "Usage: /think [off|think|think+|ultra]\n\nLevels:\n off — No extended thinking\n think — Moderate thinking budget\n think+ — Extended thinking budget\n ultra — Maximum thinking budget\n\nWithout arguments, cycles to the next level.", nil, nil
284+
}
285+
286+
m.provider.StatusMessage = fmt.Sprintf("thinking: %s", m.provider.ThinkingLevel.String())
287+
return "", appprovider.StatusTimer(3 * time.Second), nil
288+
}

internal/app/handler_input.go

Lines changed: 39 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ import (
1111
"github.com/yanmxa/gencode/internal/hooks"
1212
"github.com/yanmxa/gencode/internal/image"
1313
"github.com/yanmxa/gencode/internal/message"
14+
"github.com/yanmxa/gencode/internal/provider"
1415
"github.com/yanmxa/gencode/internal/ui/history"
1516
"github.com/yanmxa/gencode/internal/ui/suggest"
1617
)
@@ -409,8 +410,12 @@ func (m *model) handleSubmit() tea.Cmd {
409410
m.input.Textarea.Reset()
410411
m.input.Textarea.SetHeight(appinput.MinTextareaHeight())
411412

412-
if result != "" {
413+
// For skill commands, the user message is appended inside handleSkillInvocation
414+
// before startLLMStream, so skip it here. For regular commands, append now.
415+
if cmd == nil {
413416
m.conv.Append(message.ChatMessage{Role: message.RoleUser, Content: input})
417+
}
418+
if result != "" {
414419
m.conv.AddNotice(result)
415420
}
416421

@@ -444,9 +449,36 @@ func (m *model) handleSubmit() tea.Cmd {
444449
return tea.Batch(m.commitMessages()...)
445450
}
446451

452+
// Detect thinking keywords in the user's message
453+
m.detectThinkingKeywords(content)
454+
447455
return m.startLLMStream(nil)
448456
}
449457

458+
// detectThinkingKeywords scans the user's message for explicit thinking-level keywords
459+
// and sets a per-turn override (not persistent). The override resets after the turn completes.
460+
func (m *model) detectThinkingKeywords(input string) {
461+
lower := strings.ToLower(input)
462+
463+
// Check for ultrathink keywords first (most specific)
464+
if strings.Contains(lower, "ultrathink") ||
465+
strings.Contains(lower, "think really hard") ||
466+
strings.Contains(lower, "think super hard") ||
467+
strings.Contains(lower, "maximum thinking") {
468+
m.provider.ThinkingOverride = provider.ThinkingUltra
469+
return
470+
}
471+
472+
// Check for high thinking keywords
473+
if strings.Contains(lower, "think harder") ||
474+
strings.Contains(lower, "think hard") ||
475+
strings.Contains(lower, "think deeply") ||
476+
strings.Contains(lower, "think carefully") {
477+
m.provider.ThinkingOverride = provider.ThinkingHigh
478+
return
479+
}
480+
}
481+
450482
func (m *model) handleWindowResize(msg tea.WindowSizeMsg) tea.Cmd {
451483
oldWidth := m.width
452484
m.width = msg.Width
@@ -530,13 +562,12 @@ func (m *model) handleSkillInvocation() tea.Cmd {
530562
return tea.Batch(m.commitMessages()...)
531563
}
532564

533-
// Get the user message (skill args or skill name)
534-
userMessage := m.skill.PendingArgs
535-
if userMessage == "" {
536-
userMessage = "Execute the skill."
565+
// Append user message before starting the stream so the LLM receives it
566+
userMsg := m.skill.PendingArgs
567+
if userMsg == "" {
568+
userMsg = "Execute the skill."
537569
}
538-
539-
m.conv.Append(message.ChatMessage{Role: message.RoleUser, Content: userMessage})
570+
m.conv.Append(message.ChatMessage{Role: message.RoleUser, Content: userMsg})
540571

541572
// Store in ActiveInvocation for persistence across turns
542573
if m.skill.PendingInstructions != "" {
@@ -545,7 +576,7 @@ func (m *model) handleSkillInvocation() tea.Cmd {
545576
}
546577
m.skill.PendingArgs = ""
547578

548-
return m.startLLMStream(nil) // No extra needed, configureLoop reads ActiveInvocation
579+
return m.startLLMStream(nil)
549580
}
550581

551582
// checkPromptHook runs UserPromptSubmit hook and returns (blocked, reason).

0 commit comments

Comments
 (0)