diff --git a/pkg/channels/manager.go b/pkg/channels/manager.go index 03f65e11a..c50b19994 100644 --- a/pkg/channels/manager.go +++ b/pkg/channels/manager.go @@ -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 }