@@ -64,6 +64,7 @@ type chatModel struct {
6464 toolsPending int // tools currently running
6565 toolsDone int // tools completed this turn
6666 lastStreamRebuild time.Time // debounce streaming viewport rebuilds
67+ inThink bool // inside <think> tags (MiniMax reasoning)
6768
6869 // Glue + notifications
6970 glue * GlueClient
@@ -761,7 +762,35 @@ func (m *chatModel) handleAgentEvent(evt AgentEvent) tea.Cmd {
761762
762763 switch evt .Type {
763764 case EventTextDelta :
764- m .streaming .WriteString (evt .Text )
765+ // Filter out <think>...</think> reasoning blocks (MiniMax, DeepSeek, etc.)
766+ text := evt .Text
767+ for text != "" {
768+ if m .inThink {
769+ if end := strings .Index (text , "</think>" ); end >= 0 {
770+ m .inThink = false
771+ text = text [end + len ("</think>" ):]
772+ continue
773+ }
774+ // Still inside think block — discard everything
775+ text = ""
776+ } else {
777+ if start := strings .Index (text , "<think>" ); start >= 0 {
778+ // Write text before the tag
779+ if start > 0 {
780+ m .streaming .WriteString (text [:start ])
781+ }
782+ m .inThink = true
783+ if ! m .notify .HasActive () {
784+ m .notify .Push (Notification {Type : NotifyProgress , Text : "Thinking..." })
785+ }
786+ text = text [start + len ("<think>" ):]
787+ continue
788+ }
789+ // No think tags — write as-is
790+ m .streaming .WriteString (text )
791+ text = ""
792+ }
793+ }
765794 // Debounce: only rebuild viewport at most every 50ms during streaming
766795 if time .Since (m .lastStreamRebuild ) > 50 * time .Millisecond {
767796 m .rebuildViewport ()
@@ -909,7 +938,10 @@ func (m *chatModel) handleAgentEvent(evt AgentEvent) tea.Cmd {
909938}
910939
911940func (m * chatModel ) flushStreamingText () {
941+ m .inThink = false
912942 text := m .streaming .String ()
943+ // Strip any residual think tags
944+ text = stripThinkTags (text )
913945 if text != "" {
914946 m .segments = append (m .segments , segment {
915947 kind : "text" ,
@@ -1050,6 +1082,24 @@ func (m chatModel) View() string {
10501082 return framedBody + "\n " + suggestBar + inputRow
10511083}
10521084
1085+ // stripThinkTags removes <think>...</think> blocks and stray tags from text.
1086+ func stripThinkTags (s string ) string {
1087+ for {
1088+ start := strings .Index (s , "<think>" )
1089+ if start < 0 {
1090+ break
1091+ }
1092+ end := strings .Index (s [start :], "</think>" )
1093+ if end < 0 {
1094+ // Unclosed tag — strip from <think> to end
1095+ s = s [:start ]
1096+ break
1097+ }
1098+ s = s [:start ] + s [start + end + len ("</think>" ):]
1099+ }
1100+ return strings .TrimSpace (s )
1101+ }
1102+
10531103// parseSuggestionIndex checks if input is a suggestion number (1-based).
10541104// Returns 0-based index or -1 if not a valid suggestion number.
10551105func parseSuggestionIndex (input string , count int ) int {
0 commit comments