fix(channels): prevent tool_calls from being dropped during streaming

The auxiliary message filtering introduced in #2892 incorrectly drops
tool_calls messages when there is an active stream or a tombstone from
a recently finished stream.

This causes tool_calls to not be delivered to the UI when users make
consecutive requests, as the second request's tool_calls message arrives
while the first request's stream is still active or within the 30-second
tombstone window.

The fix excludes tool_calls from auxiliary message filtering, since they
represent new tool invocations that must be delivered to the UI, not
stale auxiliary content like feedback or thoughts.

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
Andy Lo-A-Foe
2026-05-27 20:36:23 +02:00
parent 28ec5793a8
commit 89e7a61a69
+11 -1
View File
@@ -169,6 +169,13 @@ func outboundMessageIsToolFeedback(msg bus.OutboundMessage) bool {
return strings.EqualFold(strings.TrimSpace(msg.Context.Raw["message_kind"]), "tool_feedback")
}
func outboundMessageIsToolCalls(msg bus.OutboundMessage) bool {
if len(msg.Context.Raw) == 0 {
return false
}
return strings.EqualFold(strings.TrimSpace(msg.Context.Raw["message_kind"]), "tool_calls")
}
func outboundMessageHasAuxiliaryKind(msg bus.OutboundMessage) bool {
if len(msg.Context.Raw) == 0 {
return false
@@ -379,6 +386,7 @@ func (m *Manager) preSend(ctx context.Context, name string, msg bus.OutboundMess
}
isToolFeedback := outboundMessageIsToolFeedback(msg)
isToolCalls := outboundMessageIsToolCalls(msg)
isAuxiliaryMessage := outboundMessageHasAuxiliaryKind(msg)
isFinalMessage := outboundMessageIsFinal(msg)
separateToolFeedbackMessages := m.toolFeedbackSeparateMessagesEnabled()
@@ -388,7 +396,9 @@ func (m *Manager) preSend(ctx context.Context, name string, msg bus.OutboundMess
// finalization bypasses the worker queue, so older queued feedback/thoughts
// can arrive before the normal final outbound message that cleans up the
// marker and placeholder.
if isAuxiliaryMessage {
// Note: tool_calls messages must NOT be dropped as they represent new tool
// invocations for the current turn that must be delivered to the UI.
if isAuxiliaryMessage && !isToolCalls {
if _, loaded := m.streamActive.Load(streamKey); loaded {
return nil, true
}