Files
LocalAGI/services/actions/sendtelegrammessage.go
Ettore Di Giacinto c23e655f44 feat(agent): shared state, allow to track conversations globally (#148)
* feat(agent): shared state, allow to track conversations globally

Signed-off-by: Ettore Di Giacinto <mudler@localai.io>

* Cleanup

Signed-off-by: Ettore Di Giacinto <mudler@localai.io>

* track conversations initiated by the bot

Signed-off-by: Ettore Di Giacinto <mudler@localai.io>

---------

Signed-off-by: Ettore Di Giacinto <mudler@localai.io>
2025-05-11 22:23:01 +02:00

196 lines
5.1 KiB
Go

package actions
import (
"context"
"fmt"
"strconv"
"github.com/go-telegram/bot"
"github.com/go-telegram/bot/models"
"github.com/mudler/LocalAGI/core/types"
"github.com/mudler/LocalAGI/pkg/config"
"github.com/mudler/LocalAGI/pkg/xstrings"
"github.com/sashabaranov/go-openai"
"github.com/sashabaranov/go-openai/jsonschema"
)
const (
MetadataTelegramMessageSent = "telegram_message_sent"
telegramMaxMessageLength = 3000
)
type SendTelegramMessageRunner struct {
token string
chatID int64
bot *bot.Bot
customName string
customDescription string
}
func NewSendTelegramMessageRunner(config map[string]string) *SendTelegramMessageRunner {
token := config["token"]
if token == "" {
return nil
}
// Parse chat ID from config if present
var chatID int64
if configChatID := config["chat_id"]; configChatID != "" {
var err error
chatID, err = strconv.ParseInt(configChatID, 10, 64)
if err != nil {
return nil
}
}
b, err := bot.New(token)
if err != nil {
return nil
}
return &SendTelegramMessageRunner{
token: token,
chatID: chatID,
bot: b,
customName: config["custom_name"],
customDescription: config["custom_description"],
}
}
type TelegramMessageParams struct {
ChatID int64 `json:"chat_id"`
Message string `json:"message"`
}
func (s *SendTelegramMessageRunner) Run(ctx context.Context, sharedState *types.AgentSharedState, params types.ActionParams) (types.ActionResult, error) {
var messageParams TelegramMessageParams
err := params.Unmarshal(&messageParams)
if err != nil {
return types.ActionResult{}, fmt.Errorf("failed to unmarshal params: %w", err)
}
if s.chatID != 0 {
messageParams.ChatID = s.chatID
}
if messageParams.ChatID == 0 {
return types.ActionResult{}, fmt.Errorf("chat_id is required either in config or parameters")
}
if messageParams.Message == "" {
return types.ActionResult{}, fmt.Errorf("message is required")
}
// Split the message if it's too long
messages := xstrings.SplitParagraph(messageParams.Message, telegramMaxMessageLength)
if len(messages) == 0 {
return types.ActionResult{}, fmt.Errorf("empty message after splitting")
}
// Send each message part
for i, msg := range messages {
_, err = s.bot.SendMessage(ctx, &bot.SendMessageParams{
ChatID: messageParams.ChatID,
Text: msg,
ParseMode: models.ParseModeMarkdown,
})
if err != nil {
return types.ActionResult{}, fmt.Errorf("failed to send telegram message part %d: %w", i+1, err)
}
}
sharedState.ConversationTracker.AddMessage(fmt.Sprintf("telegram:%d", messageParams.ChatID), openai.ChatCompletionMessage{
Content: messageParams.Message,
Role: "assistant",
})
return types.ActionResult{
Result: fmt.Sprintf("Message sent successfully to chat ID %d in %d parts", messageParams.ChatID, len(messages)),
Metadata: map[string]interface{}{
MetadataTelegramMessageSent: true,
},
}, nil
}
func (s *SendTelegramMessageRunner) Definition() types.ActionDefinition {
customName := "send_telegram_message"
if s.customName != "" {
customName = s.customName
}
customDescription := "Send a message to a Telegram user or group"
if s.customDescription != "" {
customDescription = s.customDescription
}
if s.chatID != 0 {
return types.ActionDefinition{
Name: types.ActionDefinitionName(customName),
Description: customDescription,
Properties: map[string]jsonschema.Definition{
"message": {
Type: jsonschema.String,
Description: "The message to send",
},
},
Required: []string{"message"},
}
}
return types.ActionDefinition{
Name: types.ActionDefinitionName(customName),
Description: customDescription,
Properties: map[string]jsonschema.Definition{
"chat_id": {
Type: jsonschema.Number,
Description: "The Telegram chat ID to send the message to (optional if configured in config)",
},
"message": {
Type: jsonschema.String,
Description: "The message to send",
},
},
Required: []string{"message", "chat_id"},
}
}
func (s *SendTelegramMessageRunner) Plannable() bool {
return true
}
// SendTelegramMessageConfigMeta returns the metadata for Send Telegram Message action configuration fields
func SendTelegramMessageConfigMeta() []config.Field {
return []config.Field{
{
Name: "token",
Label: "Telegram Token",
Type: config.FieldTypeText,
Required: true,
HelpText: "Telegram bot token for sending messages",
},
{
Name: "chat_id",
Label: "Default Chat ID",
Type: config.FieldTypeText,
Required: false,
HelpText: "Default Telegram chat ID to send messages to (can be overridden in parameters)",
},
{
Name: "custom_name",
Label: "Custom Name",
Type: config.FieldTypeText,
Required: false,
HelpText: "Custom name for the action (optional, defaults to 'send_telegram_message')",
},
{
Name: "custom_description",
Label: "Custom Description",
Type: config.FieldTypeText,
Required: false,
HelpText: "Custom description for the action (optional, defaults to 'Send a message to a Telegram user or group')",
},
}
}