feat: add support for attachments in message tool
This commit is contained in:
parent
11008573d7
commit
ac551a0caa
5 changed files with 55 additions and 12 deletions
|
|
@ -95,11 +95,12 @@ func createToolRegistry(workspace string, restrict bool, cfg *config.Config, msg
|
|||
// Message tool - available to both agent and subagent
|
||||
// Subagent uses it to communicate directly with user
|
||||
messageTool := tools.NewMessageTool()
|
||||
messageTool.SetSendCallback(func(channel, chatID, content string) error {
|
||||
messageTool.SetSendCallback(func(channel, chatID, content string, attachments []string) error {
|
||||
msgBus.PublishOutbound(bus.OutboundMessage{
|
||||
Channel: channel,
|
||||
ChatID: chatID,
|
||||
Content: content,
|
||||
Media: attachments,
|
||||
})
|
||||
return nil
|
||||
})
|
||||
|
|
|
|||
|
|
@ -11,9 +11,10 @@ type InboundMessage struct {
|
|||
}
|
||||
|
||||
type OutboundMessage struct {
|
||||
Channel string `json:"channel"`
|
||||
ChatID string `json:"chat_id"`
|
||||
Content string `json:"content"`
|
||||
Channel string `json:"channel"`
|
||||
ChatID string `json:"chat_id"`
|
||||
Content string `json:"content"`
|
||||
Media []string `json:"media,omitempty"`
|
||||
}
|
||||
|
||||
type MessageHandler func(InboundMessage) error
|
||||
|
|
|
|||
|
|
@ -101,12 +101,24 @@ func (c *DiscordChannel) Send(ctx context.Context, msg bus.OutboundMessage) erro
|
|||
return fmt.Errorf("channel ID is empty")
|
||||
}
|
||||
|
||||
for _, mediaPath := range msg.Media {
|
||||
file, err := os.Open(mediaPath)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to open media file %s: %w", mediaPath, err)
|
||||
}
|
||||
_, err = c.session.ChannelFileSend(channelID, mediaPath, file)
|
||||
file.Close()
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to send media file %s: %w", mediaPath, err)
|
||||
}
|
||||
}
|
||||
|
||||
runes := []rune(msg.Content)
|
||||
if len(runes) == 0 {
|
||||
return nil
|
||||
}
|
||||
|
||||
chunks := splitMessage(msg.Content, 1500) // Discord has a limit of 2000 characters per message, leave 500 for natural split e.g. code blocks
|
||||
chunks := splitMessage(msg.Content, 1500)
|
||||
|
||||
for _, chunk := range chunks {
|
||||
if err := c.sendChunk(ctx, channelID, chunk); err != nil {
|
||||
|
|
@ -253,7 +265,19 @@ func (c *DiscordChannel) sendChunk(ctx context.Context, channelID, content strin
|
|||
|
||||
done := make(chan error, 1)
|
||||
go func() {
|
||||
_, err := c.session.ChannelMessageSend(channelID, content)
|
||||
var err error
|
||||
if strings.HasPrefix(content, "file://") {
|
||||
filePath := strings.TrimPrefix(content, "file://")
|
||||
file, fErr := os.Open(filePath)
|
||||
if fErr != nil {
|
||||
done <- fErr
|
||||
return
|
||||
}
|
||||
defer file.Close()
|
||||
_, err = c.session.ChannelFileSend(channelID, filePath, file)
|
||||
} else {
|
||||
_, err = c.session.ChannelMessageSend(channelID, content)
|
||||
}
|
||||
done <- err
|
||||
}()
|
||||
|
||||
|
|
|
|||
|
|
@ -5,7 +5,7 @@ import (
|
|||
"fmt"
|
||||
)
|
||||
|
||||
type SendCallback func(channel, chatID, content string) error
|
||||
type SendCallback func(channel, chatID, content string, attachments []string) error
|
||||
|
||||
type MessageTool struct {
|
||||
sendCallback SendCallback
|
||||
|
|
@ -42,6 +42,11 @@ func (t *MessageTool) Parameters() map[string]interface{} {
|
|||
"type": "string",
|
||||
"description": "Optional: target chat/user ID",
|
||||
},
|
||||
"attachments": map[string]interface{}{
|
||||
"type": "array",
|
||||
"items": map[string]interface{}{"type": "string"},
|
||||
"description": "Optional: list of local file paths or URLs to attach",
|
||||
},
|
||||
},
|
||||
"required": []string{"content"},
|
||||
}
|
||||
|
|
@ -70,6 +75,13 @@ func (t *MessageTool) Execute(ctx context.Context, args map[string]interface{})
|
|||
|
||||
channel, _ := args["channel"].(string)
|
||||
chatID, _ := args["chat_id"].(string)
|
||||
attachmentsRaw, _ := args["attachments"].([]interface{})
|
||||
var attachments []string
|
||||
for _, a := range attachmentsRaw {
|
||||
if s, ok := a.(string); ok {
|
||||
attachments = append(attachments, s)
|
||||
}
|
||||
}
|
||||
|
||||
if channel == "" {
|
||||
channel = t.defaultChannel
|
||||
|
|
@ -86,7 +98,7 @@ func (t *MessageTool) Execute(ctx context.Context, args map[string]interface{})
|
|||
return &ToolResult{ForLLM: "Message sending not configured", IsError: true}
|
||||
}
|
||||
|
||||
if err := t.sendCallback(channel, chatID, content); err != nil {
|
||||
if err := t.sendCallback(channel, chatID, content, attachments); err != nil {
|
||||
return &ToolResult{
|
||||
ForLLM: fmt.Sprintf("sending message: %v", err),
|
||||
IsError: true,
|
||||
|
|
|
|||
|
|
@ -11,10 +11,12 @@ func TestMessageTool_Execute_Success(t *testing.T) {
|
|||
tool.SetContext("test-channel", "test-chat-id")
|
||||
|
||||
var sentChannel, sentChatID, sentContent string
|
||||
tool.SetSendCallback(func(channel, chatID, content string) error {
|
||||
var sentAttachments []string
|
||||
tool.SetSendCallback(func(channel, chatID, content string, attachments []string) error {
|
||||
sentChannel = channel
|
||||
sentChatID = chatID
|
||||
sentContent = content
|
||||
sentAttachments = attachments
|
||||
return nil
|
||||
})
|
||||
|
||||
|
|
@ -35,6 +37,9 @@ func TestMessageTool_Execute_Success(t *testing.T) {
|
|||
if sentContent != "Hello, world!" {
|
||||
t.Errorf("Expected content 'Hello, world!', got '%s'", sentContent)
|
||||
}
|
||||
if len(sentAttachments) != 0 {
|
||||
t.Errorf("Expected 0 attachments, got %d", len(sentAttachments))
|
||||
}
|
||||
|
||||
// Verify ToolResult meets US-011 criteria:
|
||||
// - Send success returns SilentResult (Silent=true)
|
||||
|
|
@ -63,7 +68,7 @@ func TestMessageTool_Execute_WithCustomChannel(t *testing.T) {
|
|||
tool.SetContext("default-channel", "default-chat-id")
|
||||
|
||||
var sentChannel, sentChatID string
|
||||
tool.SetSendCallback(func(channel, chatID, content string) error {
|
||||
tool.SetSendCallback(func(channel, chatID, content string, attachments []string) error {
|
||||
sentChannel = channel
|
||||
sentChatID = chatID
|
||||
return nil
|
||||
|
|
@ -99,7 +104,7 @@ func TestMessageTool_Execute_SendFailure(t *testing.T) {
|
|||
tool.SetContext("test-channel", "test-chat-id")
|
||||
|
||||
sendErr := errors.New("network error")
|
||||
tool.SetSendCallback(func(channel, chatID, content string) error {
|
||||
tool.SetSendCallback(func(channel, chatID, content string, attachments []string) error {
|
||||
return sendErr
|
||||
})
|
||||
|
||||
|
|
@ -153,7 +158,7 @@ func TestMessageTool_Execute_NoTargetChannel(t *testing.T) {
|
|||
tool := NewMessageTool()
|
||||
// No SetContext called, so defaultChannel and defaultChatID are empty
|
||||
|
||||
tool.SetSendCallback(func(channel, chatID, content string) error {
|
||||
tool.SetSendCallback(func(channel, chatID, content string, attachments []string) error {
|
||||
return nil
|
||||
})
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue