- Updated the GetStepsForResume method to return only steps with valid resume statuses (failed or interrupted), improving recovery context handling. - Enhanced error handling in message processing across various integrations, including friendly error messages for users in different locales. - Modified file handling in message integrations (Dingtalk, Discord, Feishu, Telegram, Weixin) to differentiate between image and file types, ensuring appropriate content delivery.
149 lines
3.6 KiB
Go
149 lines
3.6 KiB
Go
package telegram
|
|
|
|
import (
|
|
"context"
|
|
"fmt"
|
|
"strconv"
|
|
"strings"
|
|
|
|
agentcontext "github.com/yaoapp/yao/agent/context"
|
|
events "github.com/yaoapp/yao/agent/robot/events"
|
|
"github.com/yaoapp/yao/event"
|
|
tgapi "github.com/yaoapp/yao/integrations/telegram"
|
|
)
|
|
|
|
// handleMessages builds a single event payload from a batch of ConvertedMessages
|
|
// belonging to the same chat. Consecutive user messages are merged into one to
|
|
// keep the messages array clean for the LLM.
|
|
func (a *Adapter) handleMessages(ctx context.Context, entry *botEntry, cms []*tgapi.ConvertedMessage) {
|
|
if len(cms) == 0 {
|
|
return
|
|
}
|
|
|
|
var allParts []interface{}
|
|
var lastCM *tgapi.ConvertedMessage
|
|
|
|
for _, cm := range cms {
|
|
if cm == nil {
|
|
continue
|
|
}
|
|
|
|
if isBotCommand(cm) {
|
|
continue
|
|
}
|
|
|
|
dedupKey := fmt.Sprintf("tg:%s:%d", entry.robotID, cm.UpdateID)
|
|
if !a.dedup.markSeen(dedupKey) {
|
|
continue
|
|
}
|
|
|
|
parts := buildContentParts(cm)
|
|
if len(parts) == 0 {
|
|
continue
|
|
}
|
|
|
|
allParts = append(allParts, parts...)
|
|
lastCM = cm
|
|
}
|
|
|
|
if len(allParts) == 0 || lastCM == nil {
|
|
return
|
|
}
|
|
|
|
content := mergeContentParts(allParts)
|
|
|
|
msgPayload := events.MessagePayload{
|
|
RobotID: entry.robotID,
|
|
Messages: []agentcontext.Message{
|
|
{Role: agentcontext.RoleUser, Content: content},
|
|
},
|
|
Metadata: &events.MessageMetadata{
|
|
Channel: "telegram",
|
|
MessageID: strconv.FormatInt(lastCM.UpdateID, 10),
|
|
AppID: entry.appID,
|
|
ChatID: strconv.FormatInt(lastCM.ChatID, 10),
|
|
SenderID: strconv.FormatInt(lastCM.SenderID, 10),
|
|
SenderName: lastCM.SenderName,
|
|
Locale: events.NormalizeLocale(lastCM.LanguageCode),
|
|
Extra: map[string]any{
|
|
"tg_message_id": lastCM.MessageID,
|
|
"sender_id": strconv.FormatInt(lastCM.SenderID, 10),
|
|
"app_id": entry.appID,
|
|
},
|
|
},
|
|
}
|
|
|
|
if _, err := event.Push(ctx, events.Message, msgPayload); err != nil {
|
|
log.Error("telegram adapter: event.Push robot.message failed robot=%s: %v", entry.robotID, err)
|
|
}
|
|
}
|
|
|
|
// buildContentParts extracts content parts from a single ConvertedMessage.
|
|
func buildContentParts(cm *tgapi.ConvertedMessage) []interface{} {
|
|
var parts []interface{}
|
|
|
|
if cm.HasText() {
|
|
parts = append(parts, map[string]interface{}{
|
|
"type": "text",
|
|
"text": cm.Text,
|
|
})
|
|
}
|
|
|
|
for _, mi := range cm.MediaItems {
|
|
if mi.Wrapper == "" {
|
|
continue
|
|
}
|
|
if strings.HasPrefix(mi.MimeType, "image/") {
|
|
parts = append(parts, map[string]interface{}{
|
|
"type": "image_url",
|
|
"image_url": map[string]interface{}{
|
|
"url": mi.Wrapper,
|
|
"detail": "auto",
|
|
},
|
|
})
|
|
} else {
|
|
parts = append(parts, map[string]interface{}{
|
|
"type": "file",
|
|
"file": map[string]interface{}{
|
|
"url": mi.Wrapper,
|
|
"filename": mi.FileName,
|
|
},
|
|
})
|
|
}
|
|
}
|
|
|
|
return parts
|
|
}
|
|
|
|
// mergeContentParts merges collected parts into a single content value.
|
|
// If all parts are text-only, they are joined with newlines into a plain string.
|
|
// Otherwise the full parts array is returned.
|
|
func mergeContentParts(parts []interface{}) interface{} {
|
|
allText := true
|
|
for _, p := range parts {
|
|
m, ok := p.(map[string]interface{})
|
|
if !ok || m["type"] != "text" {
|
|
allText = false
|
|
break
|
|
}
|
|
}
|
|
|
|
if allText {
|
|
var buf strings.Builder
|
|
for i, p := range parts {
|
|
if i > 0 {
|
|
buf.WriteString("\n")
|
|
}
|
|
m := p.(map[string]interface{})
|
|
buf.WriteString(m["text"].(string))
|
|
}
|
|
return buf.String()
|
|
}
|
|
|
|
return parts
|
|
}
|
|
|
|
// isBotCommand returns true if the message is a Telegram bot command (text starting with "/").
|
|
func isBotCommand(cm *tgapi.ConvertedMessage) bool {
|
|
return !cm.HasMedia() && strings.HasPrefix(strings.TrimSpace(cm.Text), "/")
|
|
}
|