mirror of
https://github.com/memohai/Memoh.git
synced 2026-04-27 07:16:19 +09:00
b3a39ad93d
* refactor: replace persistent subagents with ephemeral spawn tool (#subagent) - Drop subagents table, remove all persistent subagent infrastructure - Add 'subagent' session type with parent_session_id on bot_sessions - Rewrite subagent tool as single 'spawn' tool with parallel execution - Create system_subagent.md prompt, add _subagent.md include for chat - Limit subagent tools to file, exec, web_search, web_fetch only - Merge subagent token usage into parent chat session in reporting - Remove frontend subagent management page, update chat UI for spawn - Fix UTF-8 truncation in session title, fix query not passed to agent * refactor: remove history message page
113 lines
4.6 KiB
Go
113 lines
4.6 KiB
Go
package tools
|
|
|
|
import (
|
|
"context"
|
|
"log/slog"
|
|
|
|
sdk "github.com/memohai/twilight-ai/sdk"
|
|
|
|
"github.com/memohai/memoh/internal/messaging"
|
|
)
|
|
|
|
type MessageProvider struct {
|
|
exec *messaging.Executor
|
|
}
|
|
|
|
func NewMessageProvider(log *slog.Logger, sender messaging.Sender, reactor messaging.Reactor, resolver messaging.ChannelTypeResolver, assetResolver messaging.AssetResolver) *MessageProvider {
|
|
if log == nil {
|
|
log = slog.Default()
|
|
}
|
|
return &MessageProvider{
|
|
exec: &messaging.Executor{
|
|
Sender: sender,
|
|
Reactor: reactor,
|
|
Resolver: resolver,
|
|
AssetResolver: assetResolver,
|
|
Logger: log.With(slog.String("tool", "message")),
|
|
},
|
|
}
|
|
}
|
|
|
|
func (p *MessageProvider) Tools(_ context.Context, session SessionContext) ([]sdk.Tool, error) {
|
|
if session.IsSubagent {
|
|
return nil, nil
|
|
}
|
|
var tools []sdk.Tool
|
|
sess := session
|
|
if p.exec.CanSend() {
|
|
tools = append(tools, sdk.Tool{
|
|
Name: "send",
|
|
Description: "Send a message to a DIFFERENT channel or person — NOT for replying to the current conversation. Use this only for cross-channel messaging or forwarding.",
|
|
Parameters: map[string]any{
|
|
"type": "object",
|
|
"properties": map[string]any{
|
|
"bot_id": map[string]any{"type": "string", "description": "Bot ID, optional and defaults to current bot"},
|
|
"platform": map[string]any{"type": "string", "description": "Channel platform name"},
|
|
"target": map[string]any{"type": "string", "description": "Channel target (chat/group/thread ID). Use get_contacts to find available targets."},
|
|
"text": map[string]any{"type": "string", "description": "Message text shortcut when message object is omitted"},
|
|
"reply_to": map[string]any{"type": "string", "description": "Message ID to reply to. The reply will reference this message on the platform."},
|
|
"attachments": map[string]any{"type": "array", "description": "File paths or URLs to attach.", "items": map[string]any{"type": "string"}},
|
|
"message": map[string]any{"type": "object", "description": "Structured message payload with text/parts/attachments"},
|
|
},
|
|
"required": []string{},
|
|
},
|
|
Execute: func(ctx *sdk.ToolExecContext, input any) (any, error) {
|
|
return p.execSend(ctx.Context, sess, inputAsMap(input))
|
|
},
|
|
})
|
|
}
|
|
if p.exec.CanReact() {
|
|
tools = append(tools, sdk.Tool{
|
|
Name: "react",
|
|
Description: "Add or remove an emoji reaction on a channel message",
|
|
Parameters: map[string]any{
|
|
"type": "object",
|
|
"properties": map[string]any{
|
|
"bot_id": map[string]any{"type": "string", "description": "Bot ID, optional and defaults to current bot"},
|
|
"platform": map[string]any{"type": "string", "description": "Channel platform name. Defaults to current session platform."},
|
|
"target": map[string]any{"type": "string", "description": "Channel target (chat/group ID). Defaults to current session reply target."},
|
|
"message_id": map[string]any{"type": "string", "description": "The message ID to react to"},
|
|
"emoji": map[string]any{"type": "string", "description": "Emoji to react with (e.g. 👍, ❤️). Required when adding a reaction."},
|
|
"remove": map[string]any{"type": "boolean", "description": "If true, remove the reaction instead of adding it. Default false."},
|
|
},
|
|
"required": []string{"message_id"},
|
|
},
|
|
Execute: func(ctx *sdk.ToolExecContext, input any) (any, error) {
|
|
return p.execReact(ctx.Context, sess, inputAsMap(input))
|
|
},
|
|
})
|
|
}
|
|
return tools, nil
|
|
}
|
|
|
|
func (p *MessageProvider) execSend(ctx context.Context, session SessionContext, args map[string]any) (any, error) {
|
|
result, err := p.exec.Send(ctx, toMessagingSession(session), args)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
return map[string]any{
|
|
"ok": true, "bot_id": result.BotID, "platform": result.Platform, "target": result.Target,
|
|
"instruction": "Message delivered successfully. You have completed your response. Please STOP now and do not call any more tools.",
|
|
}, nil
|
|
}
|
|
|
|
func (p *MessageProvider) execReact(ctx context.Context, session SessionContext, args map[string]any) (any, error) {
|
|
result, err := p.exec.React(ctx, toMessagingSession(session), args)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
return map[string]any{
|
|
"ok": true, "bot_id": result.BotID, "platform": result.Platform,
|
|
"target": result.Target, "message_id": result.MessageID, "emoji": result.Emoji, "action": result.Action,
|
|
}, nil
|
|
}
|
|
|
|
func toMessagingSession(s SessionContext) messaging.SessionContext {
|
|
return messaging.SessionContext{
|
|
BotID: s.BotID,
|
|
ChatID: s.ChatID,
|
|
CurrentPlatform: s.CurrentPlatform,
|
|
ReplyTarget: s.ReplyTarget,
|
|
}
|
|
}
|