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
86 lines
2.1 KiB
Go
86 lines
2.1 KiB
Go
package tools
|
|
|
|
import (
|
|
"context"
|
|
"encoding/json"
|
|
"log/slog"
|
|
|
|
sdk "github.com/memohai/twilight-ai/sdk"
|
|
|
|
"github.com/memohai/memoh/internal/mcp"
|
|
)
|
|
|
|
// FederationProvider adapts a mcp.ToolSource (federated MCP connections)
|
|
// into the ToolProvider interface so the agent can load external MCP tools
|
|
// alongside built-in tools.
|
|
type FederationProvider struct {
|
|
source mcp.ToolSource
|
|
logger *slog.Logger
|
|
}
|
|
|
|
func NewFederationProvider(log *slog.Logger, source mcp.ToolSource) *FederationProvider {
|
|
if log == nil {
|
|
log = slog.Default()
|
|
}
|
|
return &FederationProvider{
|
|
source: source,
|
|
logger: log.With(slog.String("tool", "federation")),
|
|
}
|
|
}
|
|
|
|
func (f *FederationProvider) Tools(ctx context.Context, session SessionContext) ([]sdk.Tool, error) {
|
|
if session.IsSubagent || f.source == nil {
|
|
return nil, nil
|
|
}
|
|
mcpSession := toMCPSession(session)
|
|
descriptors, err := f.source.ListTools(ctx, mcpSession)
|
|
if err != nil {
|
|
f.logger.Warn("federation list tools failed", slog.Any("error", err))
|
|
return nil, nil
|
|
}
|
|
tools := make([]sdk.Tool, 0, len(descriptors))
|
|
for _, desc := range descriptors {
|
|
desc := desc
|
|
src := f.source
|
|
sess := mcpSession
|
|
tools = append(tools, sdk.Tool{
|
|
Name: desc.Name,
|
|
Description: desc.Description,
|
|
Parameters: desc.InputSchema,
|
|
Execute: func(ctx *sdk.ToolExecContext, input any) (any, error) {
|
|
args := inputAsMap(input)
|
|
result, err := src.CallTool(ctx.Context, sess, desc.Name, args)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
return normalizeMCPResult(result), nil
|
|
},
|
|
})
|
|
}
|
|
return tools, nil
|
|
}
|
|
|
|
func normalizeMCPResult(result map[string]any) any {
|
|
if result == nil {
|
|
return map[string]any{"ok": true}
|
|
}
|
|
if isErr, ok := result["isError"].(bool); ok && isErr {
|
|
return result
|
|
}
|
|
if sc, ok := result["structuredContent"]; ok && sc != nil {
|
|
return sc
|
|
}
|
|
if content, ok := result["content"]; ok {
|
|
if items, ok := content.([]map[string]any); ok && len(items) == 1 {
|
|
if text, ok := items[0]["text"].(string); ok {
|
|
var parsed any
|
|
if json.Unmarshal([]byte(text), &parsed) == nil {
|
|
return parsed
|
|
}
|
|
return text
|
|
}
|
|
}
|
|
}
|
|
return result
|
|
}
|