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 780e3487..00000000 --- a/einomcp/mcp_tools.go +++ /dev/null @@ -1,213 +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 路径。 -// invokeNotify 可选:与 runEinoADKAgentLoop 共享,在 InvokableRun 返回时触发 UI 与 pending 清理(与 ADK Tool 事件去重)。 -// einoAgentName 为该套工具所属 ChatModelAgent 的 Name(主代理或子代理 id),用于 SSE 上的 einoAgent 字段。 -func ToolsFromDefinitions( - ag *agent.Agent, - holder *ConversationHolder, - defs []agent.Tool, - rec ExecutionRecorder, - toolOutputChunk func(toolName, toolCallID, chunk string), - invokeNotify *ToolInvokeNotifyHolder, - einoAgentName 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, - invokeNotify: invokeNotify, - einoAgentName: strings.TrimSpace(einoAgentName), - }) - } - 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) - invokeNotify *ToolInvokeNotifyHolder - einoAgentName 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) (out string, err error) { - _ = opts - toolCallID := compose.GetToolCallID(ctx) - defer func() { - if m.invokeNotify == nil { - return - } - tid := strings.TrimSpace(toolCallID) - if tid == "" { - return - } - success := err == nil && !strings.HasPrefix(out, ToolErrorPrefix) - body := out - if err != nil { - success = false - } else if strings.HasPrefix(out, ToolErrorPrefix) { - success = false - body = strings.TrimPrefix(out, ToolErrorPrefix) - } - m.invokeNotify.Fire(tid, m.name, m.einoAgentName, success, body, err) - }() - 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 使用: -// 模型请求了未注册的工具名时,返回一个「软错误」工具结果(nil error), -// 让模型在同一轮继续自我修正,避免触发 run-loop 级别的 full rerun。 -// 不进行名称猜测或映射,避免误执行。 -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 soft tool-result error so the graph keeps running and the LLM - // can correct tool name/arguments within the same run. - return ToolErrorPrefix + unknownToolReminderText(requested), nil - } -} - -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") - } -} diff --git a/einomcp/tool_invoke_notify.go b/einomcp/tool_invoke_notify.go deleted file mode 100644 index 126f5694..00000000 --- a/einomcp/tool_invoke_notify.go +++ /dev/null @@ -1,39 +0,0 @@ -package einomcp - -import "sync" - -// ToolInvokeNotifyHolder 由 Eino run loop 在迭代开始前 Set 回调;MCP 桥在每次 InvokableRun 结束时 Fire, -// 用于在 ADK 未透出 schema.Tool 事件时仍推送 tool_result、清 pending,避免 UI 卡在「执行中」或迭代末 force-close。 -type ToolInvokeNotifyHolder struct { - mu sync.RWMutex - fn func(toolCallID, toolName, einoAgent string, success bool, content string, invokeErr error) -} - -// NewToolInvokeNotifyHolder 创建可在 ToolsFromDefinitions 与 run loop 之间共享的 holder。 -func NewToolInvokeNotifyHolder() *ToolInvokeNotifyHolder { - return &ToolInvokeNotifyHolder{} -} - -// Set 由 runEinoADKAgentLoop 在开始消费 iter 之前调用;可多次覆盖(通常仅一次)。 -func (h *ToolInvokeNotifyHolder) Set(fn func(toolCallID, toolName, einoAgent string, success bool, content string, invokeErr error)) { - if h == nil { - return - } - h.mu.Lock() - defer h.mu.Unlock() - h.fn = fn -} - -// Fire 由 mcpBridgeTool 在工具调用返回时调用;若尚未 Set 或 toolCallID 为空则忽略。 -func (h *ToolInvokeNotifyHolder) Fire(toolCallID, toolName, einoAgent string, success bool, content string, invokeErr error) { - if h == nil { - return - } - h.mu.RLock() - fn := h.fn - h.mu.RUnlock() - if fn == nil { - return - } - fn(toolCallID, toolName, einoAgent, success, content, invokeErr) -}