fix(core): keep streamed assistant text on cancel
- commit
- 6ecaeda59889df327130a1968f0fd05a0197d612
- author
- patriceckhart · · 2026-04-22 07:45 UTC
- parent
- 569d28d
- signature
- unsigned
- refs
- tag: v0.0.92
1 file · +20 -3
M internal/core/agent.go +20 -3
| @@ -257,9 +257,26 @@ func (a *Agent) oneTurn(ctx context.Context, sink func(AgentEvent)) (provider.St | |||
| 257 | 257 | } | |
| 258 | 258 | ||
| 259 | 259 | // Append assistant message to transcript. Aborted turns (Esc / Ctrl+C) | |
| 260 | - | // produce partial, mid-sentence content that would confuse subsequent | |
| 261 | - | // turns if it stayed in the transcript, drop it instead. | |
| 262 | - | if len(finalMsg.Content) > 0 && stop != provider.StopAborted { | |
| 260 | + | // produce partial content. When the partial message is text only we | |
| 261 | + | // keep whatever was streamed up to the cancel so the user does not | |
| 262 | + | // lose visible work (a cut-off summary is still useful). If the | |
| 263 | + | // partial message already contained tool-call blocks we drop the | |
| 264 | + | // whole thing, because an unmatched tool_use would fail the next | |
| 265 | + | // turn with a tool_result mismatch error. | |
| 266 | + | keep := len(finalMsg.Content) > 0 | |
| 267 | + | if stop == provider.StopAborted && keep { | |
| 268 | + | hasToolCall := false | |
| 269 | + | for _, c := range finalMsg.Content { | |
| 270 | + | if _, ok := c.(provider.ToolCallBlock); ok { | |
| 271 | + | hasToolCall = true | |
| 272 | + | break | |
| 273 | + | } | |
| 274 | + | } | |
| 275 | + | if hasToolCall { | |
| 276 | + | keep = false | |
| 277 | + | } | |
| 278 | + | } | |
| 279 | + | if keep { | |
| 263 | 280 | emit := finalMsg | |
| 264 | 281 | suppress := false | |
| 265 | 282 | ||
| @@ -257,9 +257,26 @@ func (a *Agent) oneTurn(ctx context.Context, sink func(AgentEvent)) (provider.St | |||||
| 257 | } | 257 | } | ||
| 258 | 258 | ||||
| 259 | // Append assistant message to transcript. Aborted turns (Esc / Ctrl+C) | 259 | // Append assistant message to transcript. Aborted turns (Esc / Ctrl+C) | ||
| 260 | - | // produce partial, mid-sentence content that would confuse subsequent | 260 | + | // produce partial content. When the partial message is text only we |
| 261 | - | // turns if it stayed in the transcript, drop it instead. | 261 | + | // keep whatever was streamed up to the cancel so the user does not |
| 262 | - | if len(finalMsg.Content) > 0 && stop != provider.StopAborted { | 262 | + | // lose visible work (a cut-off summary is still useful). If the |
| 263 | + | // partial message already contained tool-call blocks we drop the | |||
| 264 | + | // whole thing, because an unmatched tool_use would fail the next | |||
| 265 | + | // turn with a tool_result mismatch error. | |||
| 266 | + | keep := len(finalMsg.Content) > 0 | |||
| 267 | + | if stop == provider.StopAborted && keep { | |||
| 268 | + | hasToolCall := false | |||
| 269 | + | for _, c := range finalMsg.Content { | |||
| 270 | + | if _, ok := c.(provider.ToolCallBlock); ok { | |||
| 271 | + | hasToolCall = true | |||
| 272 | + | break | |||
| 273 | + | } | |||
| 274 | + | } | |||
| 275 | + | if hasToolCall { | |||
| 276 | + | keep = false | |||
| 277 | + | } | |||
| 278 | + | } | |||
| 279 | + | if keep { | |||
| 263 | emit := finalMsg | 280 | emit := finalMsg | ||
| 264 | suppress := false | 281 | suppress := false | ||
| 265 | 282 | ||||