mirror of
https://github.com/memohai/Memoh.git
synced 2026-04-25 07:00:48 +09:00
151 lines
4.1 KiB
Go
151 lines
4.1 KiB
Go
package inbox
|
|
|
|
import (
|
|
"context"
|
|
"fmt"
|
|
"log/slog"
|
|
"strings"
|
|
"time"
|
|
|
|
mcpgw "github.com/memohai/memoh/internal/mcp"
|
|
|
|
inboxsvc "github.com/memohai/memoh/internal/inbox"
|
|
)
|
|
|
|
const (
|
|
toolSearchInbox = "search_inbox"
|
|
defaultSearchLimit = 20
|
|
maxSearchLimit = 100
|
|
)
|
|
|
|
type Executor struct {
|
|
service *inboxsvc.Service
|
|
logger *slog.Logger
|
|
}
|
|
|
|
func NewExecutor(log *slog.Logger, service *inboxsvc.Service) *Executor {
|
|
if log == nil {
|
|
log = slog.Default()
|
|
}
|
|
return &Executor{
|
|
service: service,
|
|
logger: log.With(slog.String("provider", "inbox_tool")),
|
|
}
|
|
}
|
|
|
|
func (e *Executor) ListTools(ctx context.Context, session mcpgw.ToolSessionContext) ([]mcpgw.ToolDescriptor, error) {
|
|
if e.service == nil {
|
|
return []mcpgw.ToolDescriptor{}, nil
|
|
}
|
|
return []mcpgw.ToolDescriptor{
|
|
{
|
|
Name: toolSearchInbox,
|
|
Description: "Search historical inbox messages by keyword. Inbox contains messages from group conversations where the bot was not directly mentioned, as well as notifications from external sources.",
|
|
InputSchema: map[string]any{
|
|
"type": "object",
|
|
"properties": map[string]any{
|
|
"query": map[string]any{
|
|
"type": "string",
|
|
"description": "Search keyword to match against inbox message content",
|
|
},
|
|
"start_time": map[string]any{
|
|
"type": "string",
|
|
"description": "ISO 8601 start time filter (e.g. 2025-01-01T00:00:00Z)",
|
|
},
|
|
"end_time": map[string]any{
|
|
"type": "string",
|
|
"description": "ISO 8601 end time filter",
|
|
},
|
|
"limit": map[string]any{
|
|
"type": "integer",
|
|
"description": "Maximum number of results (default 20, max 100)",
|
|
},
|
|
"include_read": map[string]any{
|
|
"type": "boolean",
|
|
"description": "Whether to include already-read items (default true)",
|
|
},
|
|
},
|
|
"required": []string{},
|
|
},
|
|
},
|
|
}, nil
|
|
}
|
|
|
|
func (e *Executor) CallTool(ctx context.Context, session mcpgw.ToolSessionContext, toolName string, arguments map[string]any) (map[string]any, error) {
|
|
if toolName != toolSearchInbox {
|
|
return nil, mcpgw.ErrToolNotFound
|
|
}
|
|
if e.service == nil {
|
|
return mcpgw.BuildToolErrorResult("inbox service not available"), nil
|
|
}
|
|
|
|
query := mcpgw.StringArg(arguments, "query")
|
|
botID := strings.TrimSpace(session.BotID)
|
|
if botID == "" {
|
|
return mcpgw.BuildToolErrorResult("bot_id is required"), nil
|
|
}
|
|
|
|
limit := defaultSearchLimit
|
|
if value, ok, err := mcpgw.IntArg(arguments, "limit"); err != nil {
|
|
return mcpgw.BuildToolErrorResult(err.Error()), nil
|
|
} else if ok {
|
|
limit = value
|
|
}
|
|
if limit <= 0 {
|
|
limit = defaultSearchLimit
|
|
}
|
|
if limit > maxSearchLimit {
|
|
limit = maxSearchLimit
|
|
}
|
|
|
|
req := inboxsvc.SearchRequest{
|
|
Query: query,
|
|
Limit: limit,
|
|
}
|
|
|
|
if startStr := mcpgw.StringArg(arguments, "start_time"); startStr != "" {
|
|
t, err := time.Parse(time.RFC3339, startStr)
|
|
if err != nil {
|
|
return mcpgw.BuildToolErrorResult(fmt.Sprintf("invalid start_time: %v", err)), nil
|
|
}
|
|
req.StartTime = &t
|
|
}
|
|
if endStr := mcpgw.StringArg(arguments, "end_time"); endStr != "" {
|
|
t, err := time.Parse(time.RFC3339, endStr)
|
|
if err != nil {
|
|
return mcpgw.BuildToolErrorResult(fmt.Sprintf("invalid end_time: %v", err)), nil
|
|
}
|
|
req.EndTime = &t
|
|
}
|
|
if includeRead, ok, err := mcpgw.BoolArg(arguments, "include_read"); err != nil {
|
|
return mcpgw.BuildToolErrorResult(err.Error()), nil
|
|
} else if ok {
|
|
req.IncludeRead = &includeRead
|
|
}
|
|
|
|
items, err := e.service.Search(ctx, botID, req)
|
|
if err != nil {
|
|
e.logger.Warn("inbox search failed", slog.String("bot_id", botID), slog.Any("error", err))
|
|
return mcpgw.BuildToolErrorResult("inbox search failed"), nil
|
|
}
|
|
|
|
results := make([]map[string]any, 0, len(items))
|
|
for _, item := range items {
|
|
entry := map[string]any{
|
|
"id": item.ID,
|
|
"source": item.Source,
|
|
"header": item.Header,
|
|
"content": item.Content,
|
|
"is_read": item.IsRead,
|
|
"created_at": item.CreatedAt.Format(time.RFC3339),
|
|
}
|
|
results = append(results, entry)
|
|
}
|
|
|
|
return mcpgw.BuildToolSuccessResult(map[string]any{
|
|
"query": query,
|
|
"total": len(results),
|
|
"results": results,
|
|
}), nil
|
|
}
|