Skip to content

Commit f8c1257

Browse files
amirmamaghaniclaude
andcommitted
fix(telegram): delete placeholder message when streaming delivers response
When streaming was active, the "Thinking..." placeholder message stayed in the chat because preSend only deleted the tracking entry without removing the actual Telegram message. Now preSend deletes the placeholder via the new MessageDeleter interface when streamActive is set. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
1 parent 4929d0e commit f8c1257

3 files changed

Lines changed: 32 additions & 3 deletions

File tree

pkg/channels/interfaces.go

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,11 @@ type MessageEditor interface {
1515
EditMessage(ctx context.Context, chatID string, messageID string, content string) error
1616
}
1717

18+
// MessageDeleter — channels that can delete a message by ID.
19+
type MessageDeleter interface {
20+
DeleteMessage(ctx context.Context, chatID string, messageID string) error
21+
}
22+
1823
// ReactionCapable — channels that can add a reaction (e.g. 👀) to an inbound message.
1924
// ReactToMessage adds a reaction and returns an undo function to remove it.
2025
// The undo function MUST be idempotent and safe to call multiple times.

pkg/channels/manager.go

Lines changed: 11 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -133,10 +133,18 @@ func (m *Manager) preSend(ctx context.Context, name string, msg bus.OutboundMess
133133
}
134134
}
135135

136-
// 3. If a stream already finalized this message, skip both placeholder and send
136+
// 3. If a stream already finalized this message, delete the placeholder and skip send
137137
if _, loaded := m.streamActive.LoadAndDelete(key); loaded {
138-
// Also clean up any stale placeholder
139-
m.placeholders.Delete(key)
138+
if v, loaded := m.placeholders.LoadAndDelete(key); loaded {
139+
if entry, ok := v.(placeholderEntry); ok && entry.id != "" {
140+
// Prefer deleting the placeholder (cleaner UX than editing to same content)
141+
if deleter, ok := ch.(MessageDeleter); ok {
142+
deleter.DeleteMessage(ctx, msg.ChatID, entry.id) // best effort
143+
} else if editor, ok := ch.(MessageEditor); ok {
144+
editor.EditMessage(ctx, msg.ChatID, entry.id, msg.Content) // fallback
145+
}
146+
}
147+
}
140148
return true
141149
}
142150

pkg/channels/telegram/telegram.go

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -301,6 +301,22 @@ func (c *TelegramChannel) EditMessage(ctx context.Context, chatID string, messag
301301
return err
302302
}
303303

304+
// DeleteMessage implements channels.MessageDeleter.
305+
func (c *TelegramChannel) DeleteMessage(ctx context.Context, chatID string, messageID string) error {
306+
cid, err := parseChatID(chatID)
307+
if err != nil {
308+
return err
309+
}
310+
mid, err := strconv.Atoi(messageID)
311+
if err != nil {
312+
return err
313+
}
314+
return c.bot.DeleteMessage(ctx, &telego.DeleteMessageParams{
315+
ChatID: tu.ID(cid),
316+
MessageID: mid,
317+
})
318+
}
319+
304320
// SendPlaceholder implements channels.PlaceholderCapable.
305321
// It sends a placeholder message (e.g. "Thinking... 💭") that will later be
306322
// edited to the actual response via EditMessage (channels.MessageEditor).

0 commit comments

Comments
 (0)