From a7e06fba771beef5659e1451ba50da796fed6a23 Mon Sep 17 00:00:00 2001 From: root Date: Mon, 6 Jul 2026 17:25:18 +0800 Subject: [PATCH] feat: expose tool owner plugin in stage context - add Plugin field to ToolDef, ToolCall, ToolResult - track tool owner in StageHost - annotate before/after_toolcall stage context with tool plugin - add RegisterStageOwnTools() for plugin-scoped tool listeners - keep interrupt input source/output channel context in stage messages --- docs/PLUGIN_DEV.md | 11 +++++++++++ internal/agent/core/agent.go | 26 +++++++++++++++++++++++--- internal/agent/core/stages.go | 33 +++++++++++++++++++++++++++------ internal/sdk/plugin.go | 33 +++++++++++++++++++++++++++++++++ 4 files changed, 94 insertions(+), 9 deletions(-) diff --git a/docs/PLUGIN_DEV.md b/docs/PLUGIN_DEV.md index 99bfa9e..6c90e03 100644 --- a/docs/PLUGIN_DEV.md +++ b/docs/PLUGIN_DEV.md @@ -249,6 +249,17 @@ sdk.RegisterTool("weather_query", sdk.ToolDef{ | `before_output` | 输出前 | 格式适配 | | `after_output` | 输出后 | 统计日志 | +其中 `before_toolcall` / `after_toolcall` 阶段的 `StageContext` 会附带当前工具归属插件: +- `ctx.ToolCalls[i].Plugin` +- `ctx.ToolResults[i].Plugin` + +如果只想监听**当前插件自己的工具调用**,可使用: + +```go +s.RegisterStageOwnTools(sdk.StageBeforeToolcall, handler) +s.RegisterStageOwnTools(sdk.StageAfterToolcall, handler) +``` + ```go sdk.RegisterStage(sdk.StageOnInput, func(ctx *sdk.StageContext) error { if ctx.UserID == "blocked_user" { diff --git a/internal/agent/core/agent.go b/internal/agent/core/agent.go index 25e167d..3f647c4 100644 --- a/internal/agent/core/agent.go +++ b/internal/agent/core/agent.go @@ -674,6 +674,11 @@ func (a *Agent) process(input string, stageCtx *sdk.StageContext) (response stri "total_tokens": resp.TokenUsage.Total, } stageCtx.ToolCalls = convertToolCalls(resp.ToolCalls) + for i := range stageCtx.ToolCalls { + if stageCtx.ToolCalls[i].Plugin == "" { + stageCtx.ToolCalls[i].Plugin = a.resolveToolPlugin(stageCtx.ToolCalls[i].Name) + } + } if a.runStage(sdk.StagePostAction, stageCtx) { return *stageCtx.Response, toolsUsed, nil } @@ -686,10 +691,11 @@ func (a *Agent) process(input string, stageCtx *sdk.StageContext) (response stri for _, tc := range resp.ToolCalls { toolsUsed = append(toolsUsed, tc.Name) - log.Printf("[agent] executing tool: %s (id=%s)", tc.Name, tc.ID) + pluginName := a.resolveToolPlugin(tc.Name) + log.Printf("[agent] executing tool: %s (plugin=%s, id=%s)", tc.Name, pluginName, tc.ID) // === Stage: before_toolcall — 插件可拒绝/改参 === - sdkTC := sdk.ToolCall{ID: tc.ID, Name: tc.Name, Arguments: tc.Arguments} + sdkTC := sdk.ToolCall{ID: tc.ID, Name: tc.Name, Plugin: pluginName, Arguments: tc.Arguments} stageCtx.ToolCalls = []sdk.ToolCall{sdkTC} stageCtx.ToolResults = nil if a.runStage(sdk.StageBeforeToolcall, stageCtx) { @@ -698,6 +704,7 @@ func (a *Agent) process(input string, stageCtx *sdk.StageContext) (response stri msgs = append(msgs, agentAPI.Message{Role: "tool", ToolCallID: tc.ID, Content: result}) a.publishEvent(events.EventToolCall, map[string]interface{}{ "tool": tc.Name, + "plugin": pluginName, "args": tc.Arguments, "result": result, "status": "denied", @@ -710,7 +717,7 @@ func (a *Agent) process(input string, stageCtx *sdk.StageContext) (response stri log.Printf("[agent] tool %s result: %s", tc.Name, truncateStr(result, 100)) // === Stage: after_toolcall — 插件可改结果 === - stageCtx.ToolResults = []sdk.ToolResult{{CallID: tc.ID, Name: tc.Name, Success: true, Result: result}} + stageCtx.ToolResults = []sdk.ToolResult{{CallID: tc.ID, Name: tc.Name, Plugin: pluginName, Success: true, Result: result}} a.runStage(sdk.StageAfterToolcall, stageCtx) if len(stageCtx.ToolResults) > 0 { if r, ok := stageCtx.ToolResults[0].Result.(string); ok { @@ -723,6 +730,7 @@ func (a *Agent) process(input string, stageCtx *sdk.StageContext) (response stri a.publishEvent(events.EventToolCall, map[string]interface{}{ "tool": tc.Name, + "plugin": pluginName, "args": tc.Arguments, "result": result, "status": "ok", @@ -2577,6 +2585,18 @@ func truncateStr(s string, max int) string { return s } +func (a *Agent) resolveToolPlugin(name string) string { + if a.stageHost != nil { + if plugin := a.stageHost.ToolPlugin(name); plugin != "" { + return plugin + } + } + if idx := strings.IndexByte(name, '_'); idx > 0 { + return name[:idx] + } + return "core" +} + func (a *Agent) injectSourceContext(stageCtx *sdk.StageContext, evt *agentIO.InputEvent) { if stageCtx == nil || evt == nil { return diff --git a/internal/agent/core/stages.go b/internal/agent/core/stages.go index 5373383..b73d336 100644 --- a/internal/agent/core/stages.go +++ b/internal/agent/core/stages.go @@ -8,16 +8,18 @@ import ( ) type StageHost struct { - mu sync.RWMutex - toolDefs []sdk.ToolDef - tools map[string]sdk.ToolHandler - stages map[sdk.Stage][]sdk.StageHandler + mu sync.RWMutex + toolDefs []sdk.ToolDef + tools map[string]sdk.ToolHandler + toolPlugins map[string]string + stages map[sdk.Stage][]sdk.StageHandler } func NewStageHost() *StageHost { return &StageHost{ - tools: make(map[string]sdk.ToolHandler), - stages: make(map[sdk.Stage][]sdk.StageHandler), + tools: make(map[string]sdk.ToolHandler), + toolPlugins: make(map[string]string), + stages: make(map[sdk.Stage][]sdk.StageHandler), } } @@ -27,7 +29,11 @@ func (h *StageHost) RegisterTool(name string, def sdk.ToolDef, handler sdk.ToolH if _, exists := h.tools[name]; exists { return fmt.Errorf("tool %s already registered", name) } + if def.Plugin == "" { + def.Plugin = inferToolPlugin(name) + } h.tools[name] = handler + h.toolPlugins[name] = def.Plugin h.toolDefs = append(h.toolDefs, def) return nil } @@ -59,6 +65,21 @@ func (h *StageHost) ExecuteTool(name string, args map[string]interface{}) (inter return handler(args) } +func (h *StageHost) ToolPlugin(name string) string { + h.mu.RLock() + defer h.mu.RUnlock() + return h.toolPlugins[name] +} + +func inferToolPlugin(name string) string { + for i := 0; i < len(name); i++ { + if name[i] == '_' { + return name[:i] + } + } + return "" +} + // RunStage 并行调用同阶段所有注册的处理函数。 // 各 handler 共享 *StageContext,通过其内置 RWMutex 安全读写: // - 只读操作先调用 ctx.RLock() / defer ctx.RUnlock() diff --git a/internal/sdk/plugin.go b/internal/sdk/plugin.go index 1f62f3a..b78b7e7 100644 --- a/internal/sdk/plugin.go +++ b/internal/sdk/plugin.go @@ -63,18 +63,21 @@ type MemItem struct { type ToolCall struct { ID string `json:"id"` Name string `json:"name"` + Plugin string `json:"plugin,omitempty"` Arguments map[string]interface{} `json:"arguments"` } type ToolResult struct { CallID string `json:"call_id"` Name string `json:"name"` + Plugin string `json:"plugin,omitempty"` Success bool `json:"success"` Result interface{} `json:"result"` } type ToolDef struct { Name string `json:"name"` + Plugin string `json:"plugin,omitempty"` Description string `json:"description"` Parameters map[string]interface{} `json:"parameters"` } @@ -225,6 +228,9 @@ func (s *PluginSDK) LLM() LLMAPI { return s.llm } func (s *PluginSDK) Settings() SettingsAPI { return s.sett } func (s *PluginSDK) RegisterTool(name string, def ToolDef, handler ToolHandler) error { + if def.Plugin == "" { + def.Plugin = s.name + } if s.regTool != nil { return s.regTool(name, def, handler) } @@ -237,6 +243,33 @@ func (s *PluginSDK) RegisterStage(stage Stage, handler StageHandler) { } } +// RegisterStageOwnTools 仅在 before_toolcall / after_toolcall 阶段监听当前插件自己的工具调用。 +// 其他阶段会退化为普通 RegisterStage。 +func (s *PluginSDK) RegisterStageOwnTools(stage Stage, handler StageHandler) { + if s.regStage == nil { + return + } + if stage != StageBeforeToolcall && stage != StageAfterToolcall { + s.regStage(stage, handler) + return + } + s.regStage(stage, func(ctx *StageContext) error { + ctx.RLock() + match := false + switch stage { + case StageBeforeToolcall: + match = len(ctx.ToolCalls) > 0 && ctx.ToolCalls[0].Plugin == s.name + case StageAfterToolcall: + match = len(ctx.ToolResults) > 0 && ctx.ToolResults[0].Plugin == s.name + } + ctx.RUnlock() + if !match { + return nil + } + return handler(ctx) + }) +} + func (s *PluginSDK) RegisterPluginAPI(name string) error { if s.regAPI != nil { return s.regAPI(name)