diff --git a/einomcp/holder.go b/einomcp/holder.go deleted file mode 100644 index fe56b442..00000000 --- a/einomcp/holder.go +++ /dev/null @@ -1,21 +0,0 @@ -package einomcp - -import "sync" - -// ConversationHolder 在每次 DeepAgent 运行前写入会话 ID,供 MCP 工具桥接使用。 -type ConversationHolder struct { - mu sync.RWMutex - id string -} - -func (h *ConversationHolder) Set(id string) { - h.mu.Lock() - h.id = id - h.mu.Unlock() -} - -func (h *ConversationHolder) Get() string { - h.mu.RLock() - defer h.mu.RUnlock() - return h.id -} diff --git a/einomcp/mcp_tools.go b/einomcp/mcp_tools.go deleted file mode 100644 index 72228a34..00000000 --- a/einomcp/mcp_tools.go +++ /dev/null @@ -1,186 +0,0 @@ -package einomcp - -import ( - "context" - "encoding/json" - "fmt" - "strings" - - "cyberstrike-ai/internal/agent" - "cyberstrike-ai/internal/security" - - "github.com/cloudwego/eino/components/tool" - "github.com/cloudwego/eino/compose" - "github.com/cloudwego/eino/schema" - "github.com/eino-contrib/jsonschema" -) - -// ExecutionRecorder 可选,在 MCP 工具成功返回且带有 execution id 时回调(用于汇总 mcpExecutionIds)。 -type ExecutionRecorder func(executionID string) - -// ToolErrorPrefix 用于把内部 MCP 执行结果中的 IsError 标记传递到多代理上层。 -// Eino 工具通道目前只支持返回字符串,因此通过前缀标识,随后在多代理 runner 中解析为 success/isError。 -const ToolErrorPrefix = "__CYBERSTRIKE_AI_TOOL_ERROR__\n" - -// ToolsFromDefinitions 将单 Agent 使用的 OpenAI 风格工具定义转为 Eino InvokableTool,执行时走 Agent 的 MCP 路径。 -func ToolsFromDefinitions( - ag *agent.Agent, - holder *ConversationHolder, - defs []agent.Tool, - rec ExecutionRecorder, - toolOutputChunk func(toolName, toolCallID, chunk string), -) ([]tool.BaseTool, error) { - out := make([]tool.BaseTool, 0, len(defs)) - for _, d := range defs { - if d.Type != "function" || d.Function.Name == "" { - continue - } - info, err := toolInfoFromDefinition(d) - if err != nil { - return nil, fmt.Errorf("tool %q: %w", d.Function.Name, err) - } - out = append(out, &mcpBridgeTool{ - info: info, - name: d.Function.Name, - agent: ag, - holder: holder, - record: rec, - chunk: toolOutputChunk, - }) - } - return out, nil -} - -func toolInfoFromDefinition(d agent.Tool) (*schema.ToolInfo, error) { - fn := d.Function - raw, err := json.Marshal(fn.Parameters) - if err != nil { - return nil, err - } - var js jsonschema.Schema - if len(raw) > 0 && string(raw) != "null" && string(raw) != "{}" { - if err := json.Unmarshal(raw, &js); err != nil { - return nil, err - } - } - if js.Type == "" { - js.Type = string(schema.Object) - } - if js.Properties == nil && js.Type == string(schema.Object) { - // 空参数对象 - } - return &schema.ToolInfo{ - Name: fn.Name, - Desc: fn.Description, - ParamsOneOf: schema.NewParamsOneOfByJSONSchema(&js), - }, nil -} - -type mcpBridgeTool struct { - info *schema.ToolInfo - name string - agent *agent.Agent - holder *ConversationHolder - record ExecutionRecorder - chunk func(toolName, toolCallID, chunk string) -} - -func (m *mcpBridgeTool) Info(ctx context.Context) (*schema.ToolInfo, error) { - _ = ctx - return m.info, nil -} - -func (m *mcpBridgeTool) InvokableRun(ctx context.Context, argumentsInJSON string, opts ...tool.Option) (string, error) { - _ = opts - return runMCPToolInvocation(ctx, m.agent, m.holder, m.name, argumentsInJSON, m.record, m.chunk) -} - -// runMCPToolInvocation 与 mcpBridgeTool.InvokableRun 共用。 -func runMCPToolInvocation( - ctx context.Context, - ag *agent.Agent, - holder *ConversationHolder, - toolName string, - argumentsInJSON string, - record ExecutionRecorder, - chunk func(toolName, toolCallID, chunk string), -) (string, error) { - var args map[string]interface{} - if argumentsInJSON != "" && argumentsInJSON != "null" { - if err := json.Unmarshal([]byte(argumentsInJSON), &args); err != nil { - // Return soft error (nil error) so the eino graph continues and the LLM can self-correct, - // instead of a hard error that terminates the iteration loop. - return ToolErrorPrefix + fmt.Sprintf( - "Invalid tool arguments JSON: %s\n\nPlease ensure the arguments are a valid JSON object "+ - "(double-quoted keys, matched braces, no trailing commas) and retry.\n\n"+ - "(工具参数 JSON 解析失败:%s。请确保 arguments 是合法的 JSON 对象并重试。)", - err.Error(), err.Error()), nil - } - } - if args == nil { - args = map[string]interface{}{} - } - - if chunk != nil { - toolCallID := compose.GetToolCallID(ctx) - if toolCallID != "" { - if existing, ok := ctx.Value(security.ToolOutputCallbackCtxKey).(security.ToolOutputCallback); ok && existing != nil { - ctx = context.WithValue(ctx, security.ToolOutputCallbackCtxKey, security.ToolOutputCallback(func(c string) { - existing(c) - if strings.TrimSpace(c) == "" { - return - } - chunk(toolName, toolCallID, c) - })) - } else { - ctx = context.WithValue(ctx, security.ToolOutputCallbackCtxKey, security.ToolOutputCallback(func(c string) { - if strings.TrimSpace(c) == "" { - return - } - chunk(toolName, toolCallID, c) - })) - } - } - } - - res, err := ag.ExecuteMCPToolForConversation(ctx, holder.Get(), toolName, args) - if err != nil { - return "", err - } - if res == nil { - return "", nil - } - if res.ExecutionID != "" && record != nil { - record(res.ExecutionID) - } - if res.IsError { - return ToolErrorPrefix + res.Result, nil - } - return res.Result, nil -} - -// UnknownToolReminderHandler 供 compose.ToolsNodeConfig.UnknownToolsHandler 使用: -// 模型请求了未注册的工具名时,返回一个「可恢复」的错误,让上层 runner 触发重试与纠错提示, -// 同时避免 UI 永远停留在“执行中”(runner 会在 recoverable 分支 flush 掉 pending 的 tool_call)。 -// 不进行名称猜测或映射,避免误执行。 -func UnknownToolReminderHandler() func(ctx context.Context, name, input string) (string, error) { - return func(ctx context.Context, name, input string) (string, error) { - _ = ctx - _ = input - requested := strings.TrimSpace(name) - // Return a recoverable error that still carries a friendly, bilingual hint. - // This will be caught by multiagent runner as "tool not found" and trigger a retry. - return "", fmt.Errorf("tool %q not found: %s", requested, unknownToolReminderText(requested)) - } -} - -func unknownToolReminderText(requested string) string { - if requested == "" { - requested = "(empty)" - } - return fmt.Sprintf(`The tool name %q is not registered for this agent. - -Please retry using only names that appear in the tool definitions for this turn (exact match, case-sensitive). Do not invent or rename tools; adjust your plan and continue. - -(工具 %q 未注册:请仅使用本回合上下文中给出的工具名称,须完全一致;请勿自行改写或猜测名称,并继续后续步骤。)`, requested, requested) -} diff --git a/einomcp/mcp_tools_test.go b/einomcp/mcp_tools_test.go deleted file mode 100644 index 078c8c04..00000000 --- a/einomcp/mcp_tools_test.go +++ /dev/null @@ -1,16 +0,0 @@ -package einomcp - -import ( - "strings" - "testing" -) - -func TestUnknownToolReminderText(t *testing.T) { - s := unknownToolReminderText("bad_tool") - if !strings.Contains(s, "bad_tool") { - t.Fatalf("expected requested name in message: %s", s) - } - if strings.Contains(s, "Tools currently available") { - t.Fatal("unified message must not list tool names") - } -}