@@ -43,6 +43,11 @@ type Agent struct {
4343 // inThinkingTag tracks whether we're currently inside a <thinking> tag
4444 // when parsing streaming content from models that wrap reasoning in XML tags.
4545 inThinkingTag bool
46+
47+ // hasProperReasoningEvents tracks whether the model is sending ReasoningDeltaEvent
48+ // (proper reasoning events) vs wrapping reasoning in <thinking> tags in text.
49+ // If true, we skip thinking tag parsing to avoid double-sending reasoning.
50+ hasProperReasoningEvents bool
4651}
4752
4853// NewAgent creates a new ACP agent backed by Kit.
@@ -141,6 +146,10 @@ func (a *Agent) Prompt(ctx context.Context, params acp.PromptRequest) (acp.Promp
141146
142147 log .Debug ("acp: prompt" , "session" , sessionID , "prompt_len" , len (promptText ), "files" , len (files ))
143148
149+ // Reset reasoning tracking for this new prompt turn
150+ a .hasProperReasoningEvents = false
151+ a .inThinkingTag = false
152+
144153 // Create a cancellable context for this prompt turn.
145154 promptCtx , cancel := context .WithCancel (ctx )
146155 sess .setCancel (cancel )
@@ -206,26 +215,36 @@ func (a *Agent) subscribeEvents(ctx context.Context, k *kit.Kit, sessionID acp.S
206215 var update * acp.SessionUpdate
207216 switch ev := e .(type ) {
208217 case kit.MessageUpdateEvent :
209- // Handle models that wrap reasoning in <thinking> tags (Qwen, DeepSeek)
210- // Parse the chunk and separate reasoning from regular text
211- reasoning , text := a .parseThinkingTags (ev .Chunk )
212-
213- // Send reasoning update if we have reasoning content
214- if reasoning != "" {
215- u := acp .UpdateAgentThoughtText (reasoning )
216- _ = a .conn .SessionUpdate (ctx , acp.SessionNotification {
217- SessionId : sessionID ,
218- Update : u ,
219- })
220- }
221-
222- // Send text update if we have text content
223- if text != "" {
224- u := acp .UpdateAgentMessageText (text )
218+ // If the model sends proper ReasoningDeltaEvent, don't parse thinking tags
219+ // from text to avoid double-sending reasoning content.
220+ if a .hasProperReasoningEvents {
221+ // Send text as-is without thinking tag parsing
222+ u := acp .UpdateAgentMessageText (ev .Chunk )
225223 update = & u
224+ } else {
225+ // Handle models that wrap reasoning in <thinking> tags (Qwen, DeepSeek)
226+ // Parse the chunk and separate reasoning from regular text
227+ reasoning , text := a .parseThinkingTags (ev .Chunk )
228+
229+ // Send reasoning update if we have reasoning content
230+ if reasoning != "" {
231+ u := acp .UpdateAgentThoughtText (reasoning )
232+ _ = a .conn .SessionUpdate (ctx , acp.SessionNotification {
233+ SessionId : sessionID ,
234+ Update : u ,
235+ })
236+ }
237+
238+ // Send text update if we have text content
239+ if text != "" {
240+ u := acp .UpdateAgentMessageText (text )
241+ update = & u
242+ }
226243 }
227244
228245 case kit.ReasoningDeltaEvent :
246+ // Track that this model sends proper reasoning events
247+ a .hasProperReasoningEvents = true
229248 u := acp .UpdateAgentThoughtText (ev .Delta )
230249 update = & u
231250
0 commit comments