mirror of
https://github.com/tiennm99/goclaw.git
synced 2026-10-13 16:14:57 +00:00
fix(telegram): show tool status without placeholder (#139)
* fix(channels): add reasoning delivery modes * fix(telegram): show tool status without placeholder
This commit is contained in:
1 parent
53ef912441
commit
391b6c686a
2 files changed
+155
-4
No files matched your search
@@ -218,10 +218,7 @@ func (c *Channel) Send(ctx context.Context, msg bus.OutboundMessage) error {
|
||||
// Placeholder update (e.g. LLM retry notification): edit the placeholder
|
||||
// but keep it alive for the final response. Don't stop typing or cleanup.
|
||||
if msg.Metadata["placeholder_update"] == "true" {
|
||||
if pID, ok := c.placeholders.Load(localKey); ok {
|
||||
_ = c.editMessage(ctx, chatID, pID.(int), msg.Content)
|
||||
}
|
||||
return nil
|
||||
return c.updatePlaceholder(ctx, localKey, chatID, msg.Content, replyToMsgID, threadID)
|
||||
}
|
||||
|
||||
// Stop thinking animation
|
||||
@@ -474,6 +471,56 @@ func (c *Channel) sendHTML(ctx context.Context, chatID int64, htmlContent string
|
||||
return c.sendHTMLWithDepth(ctx, chatID, htmlContent, replyTo, threadID, 0)
|
||||
}
|
||||
|
||||
func (c *Channel) updatePlaceholder(ctx context.Context, localKey string, chatID int64, content string, replyTo, threadID int) error {
|
||||
if strings.TrimSpace(content) == "" {
|
||||
return nil
|
||||
}
|
||||
htmlContent := html.EscapeString(content)
|
||||
if pID, ok := c.placeholders.Load(localKey); ok {
|
||||
_ = c.editMessage(ctx, chatID, pID.(int), htmlContent)
|
||||
return nil
|
||||
}
|
||||
|
||||
msg, err := c.sendPlaceholder(ctx, chatID, htmlContent, replyTo, threadID)
|
||||
if err != nil {
|
||||
if newChatID := extractMigrateChatID(err); newChatID != 0 {
|
||||
slog.Info("telegram: group migrated to supergroup (placeholder update)",
|
||||
"old_chat_id", chatID, "new_chat_id", newChatID)
|
||||
c.migrateGroupChat(ctx, chatID, newChatID)
|
||||
msg, err = c.sendPlaceholder(ctx, newChatID, htmlContent, replyTo, threadID)
|
||||
}
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
if msg != nil && msg.MessageID > 0 {
|
||||
c.placeholders.Store(localKey, msg.MessageID)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (c *Channel) sendPlaceholder(ctx context.Context, chatID int64, htmlContent string, replyTo, threadID int) (*telego.Message, error) {
|
||||
tgMsg := tu.Message(tu.ID(chatID), htmlContent)
|
||||
tgMsg.ParseMode = telego.ModeHTML
|
||||
if sendThreadID := resolveThreadIDForSend(threadID); sendThreadID > 0 {
|
||||
tgMsg.MessageThreadID = sendThreadID
|
||||
}
|
||||
if replyTo > 0 {
|
||||
tgMsg.ReplyParameters = &telego.ReplyParameters{
|
||||
MessageID: replyTo,
|
||||
AllowSendingWithoutReply: true,
|
||||
}
|
||||
}
|
||||
|
||||
var sent *telego.Message
|
||||
err := c.retrySend(ctx, "sendMessage", nil, func(ctx context.Context) error {
|
||||
var sendErr error
|
||||
sent, sendErr = c.bot.SendMessage(ctx, tgMsg)
|
||||
return sendErr
|
||||
})
|
||||
return sent, err
|
||||
}
|
||||
|
||||
func (c *Channel) sendHTMLWithDepth(ctx context.Context, chatID int64, htmlContent string, replyTo, threadID, depth int) error {
|
||||
tgMsg := tu.Message(tu.ID(chatID), htmlContent)
|
||||
tgMsg.ParseMode = telego.ModeHTML
|
||||
|
||||
@@ -0,0 +1,104 @@
|
||||
package telegram
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"github.com/mymmrac/telego"
|
||||
ta "github.com/mymmrac/telego/telegoapi"
|
||||
|
||||
"github.com/nextlevelbuilder/goclaw/internal/bus"
|
||||
"github.com/nextlevelbuilder/goclaw/internal/channels"
|
||||
)
|
||||
|
||||
func TestSendPlaceholderUpdateCreatesLazyPlaceholderWhenMissing(t *testing.T) {
|
||||
caller := &recordingTelegramCaller{}
|
||||
bot, err := telego.NewBot(
|
||||
"123456:abcdefghijklmnopqrstuvwxyzABCDE1234",
|
||||
telego.WithAPICaller(caller),
|
||||
telego.WithDiscardLogger(),
|
||||
)
|
||||
if err != nil {
|
||||
t.Fatalf("NewBot: %v", err)
|
||||
}
|
||||
|
||||
ch := &Channel{
|
||||
BaseChannel: channels.NewBaseChannel("telegram", nil, nil),
|
||||
bot: bot,
|
||||
}
|
||||
ch.SetRunning(true)
|
||||
|
||||
status := bus.OutboundMessage{
|
||||
Channel: "telegram",
|
||||
ChatID: "123",
|
||||
Content: "⚡ Running code...",
|
||||
Metadata: map[string]string{
|
||||
"placeholder_update": "true",
|
||||
"reply_to_message_id": "77",
|
||||
},
|
||||
}
|
||||
if err := ch.Send(context.Background(), status); err != nil {
|
||||
t.Fatalf("first placeholder update: %v", err)
|
||||
}
|
||||
if got := caller.methodNames(); strings.Join(got, ",") != "sendMessage" {
|
||||
t.Fatalf("methods after first update = %v, want sendMessage", got)
|
||||
}
|
||||
if _, ok := ch.placeholders.Load("123"); !ok {
|
||||
t.Fatal("placeholder id was not stored after lazy send")
|
||||
}
|
||||
|
||||
status.Content = "🔍 Searching the web..."
|
||||
if err := ch.Send(context.Background(), status); err != nil {
|
||||
t.Fatalf("second placeholder update: %v", err)
|
||||
}
|
||||
if got := caller.methodNames(); strings.Join(got, ",") != "sendMessage,editMessageText" {
|
||||
t.Fatalf("methods after second update = %v, want sendMessage,editMessageText", got)
|
||||
}
|
||||
|
||||
final := bus.OutboundMessage{
|
||||
Channel: "telegram",
|
||||
ChatID: "123",
|
||||
Content: "Done",
|
||||
Metadata: map[string]string{},
|
||||
}
|
||||
if err := ch.Send(context.Background(), final); err != nil {
|
||||
t.Fatalf("final send: %v", err)
|
||||
}
|
||||
if got := caller.methodNames(); strings.Join(got, ",") != "sendMessage,editMessageText,editMessageText" {
|
||||
t.Fatalf("methods after final = %v, want sendMessage,editMessageText,editMessageText", got)
|
||||
}
|
||||
if _, ok := ch.placeholders.Load("123"); ok {
|
||||
t.Fatal("placeholder survived final answer handoff")
|
||||
}
|
||||
}
|
||||
|
||||
type recordingTelegramCaller struct {
|
||||
calls []recordedTelegramCall
|
||||
}
|
||||
|
||||
type recordedTelegramCall struct {
|
||||
method string
|
||||
body map[string]any
|
||||
}
|
||||
|
||||
func (c *recordingTelegramCaller) Call(_ context.Context, url string, data *ta.RequestData) (*ta.Response, error) {
|
||||
method := url[strings.LastIndex(url, "/")+1:]
|
||||
body := map[string]any{}
|
||||
if len(data.BodyRaw) > 0 {
|
||||
_ = json.Unmarshal(data.BodyRaw, &body)
|
||||
}
|
||||
c.calls = append(c.calls, recordedTelegramCall{method: method, body: body})
|
||||
|
||||
result := json.RawMessage(`{"message_id":101,"date":0,"chat":{"id":123,"type":"private"}}`)
|
||||
return &ta.Response{Ok: true, Result: result}, nil
|
||||
}
|
||||
|
||||
func (c *recordingTelegramCaller) methodNames() []string {
|
||||
names := make([]string, 0, len(c.calls))
|
||||
for _, call := range c.calls {
|
||||
names = append(names, call.method)
|
||||
}
|
||||
return names
|
||||
}
|
||||
Reference in new issue
Block a user