Files
HomeAgent/internal/sdk/plugin.go
root 2d314b3e9c 重构: 插件自注册 + .so 动态加载 + 中断打断机制
- 所有内置插件 init() 自注册 (plugin.RegisterFactory), 移除 main.go 硬编码
- 新增 .so 动态加载器 (internal/plugin/dynamic.go), 插件可编译为 plugin.so
- 新增 plugin.json 元数据 (internal/plugin/manifest.go)
- 新增 interceptLoop 独立 goroutine:
  (a) cancelLLM() 取消进行中的 HTTP 请求
  (b) interceptCh → drainInterrupt() 注入 [打断消息] 到 LLM 上下文
  (c) InjectInput 空闲时触发新处理循环
- 新增 internal/plugins/all.go 空白导入触发所有内置插件 init()
- internal/sdk/ 作为 PluginSDK 正式 Go API
- internal/api/ → internal/plugins/webui/ 迁移
- 删除旧 cmd/cli/, 使用 cmd/waiter/ 替代
- 更新 PLAN.md / ARCHITECTURE.md / README.md 文档
2026-07-03 16:53:34 +08:00

231 lines
5.7 KiB
Go

package sdk
import (
"log"
"sync"
agentIO "gitcode.com/JianFeeeee/HomeAgent/internal/agent/io"
"gitcode.com/JianFeeeee/HomeAgent/internal/events"
)
type Plugin interface {
Name() string
Start(sdk *PluginSDK) error
Stop() error
}
type ToolHandler func(args map[string]interface{}) (interface{}, error)
type StageHandler func(ctx *StageContext) error
type Stage string
const (
StageOnInput Stage = "on_input"
StagePreAction Stage = "pre_action"
StagePostAction Stage = "post_action"
StageBeforeToolcall Stage = "before_toolcall"
StageAfterToolcall Stage = "after_toolcall"
StageBeforeOutput Stage = "before_output"
StageAfterOutput Stage = "after_output"
)
type StageContext struct {
mu sync.RWMutex
RawMessage string
UserID string
GroupID string
ContextMsgs []map[string]interface{}
LLMText string
ToolCalls []ToolCall
ToolResults []ToolResult
FinalText string
Response *string
Phase Stage
Memory []MemItem
Extra map[string]interface{}
}
func (c *StageContext) RLock() { c.mu.RLock() }
func (c *StageContext) RUnlock() { c.mu.RUnlock() }
func (c *StageContext) Lock() { c.mu.Lock() }
func (c *StageContext) Unlock() { c.mu.Unlock() }
func (c *StageContext) IsResponded() bool { c.mu.RLock(); defer c.mu.RUnlock(); return c.Response != nil }
type MemItem struct {
Role string `json:"role"`
Content string `json:"content"`
Score float64 `json:"score"`
}
type ToolCall struct {
ID string `json:"id"`
Name string `json:"name"`
Arguments map[string]interface{} `json:"arguments"`
}
type ToolResult struct {
CallID string `json:"call_id"`
Name string `json:"name"`
Success bool `json:"success"`
Result interface{} `json:"result"`
}
type ToolDef struct {
Name string `json:"name"`
Description string `json:"description"`
Parameters map[string]interface{} `json:"parameters"`
}
type ToolRegistrar func(name string, def ToolDef, handler ToolHandler) error
type StageRegistrar func(stage Stage, handler StageHandler)
type APIRegistrar func(name string) error
type PluginSDK struct {
name string
iom *agentIO.IOManager
eventBus *events.Bus
mem MemoryAPI
textMem TextMemoryAPI
docMem DocMemoryAPI
know KnowledgeAPI
llm LLMAPI
sett SettingsAPI
regTool ToolRegistrar
regStage StageRegistrar
regAPI APIRegistrar
logger *log.Logger
}
func New(name string, iom *agentIO.IOManager, eventBus *events.Bus, mem MemoryAPI, textMem TextMemoryAPI, docMem DocMemoryAPI, know KnowledgeAPI, llm LLMAPI, sett SettingsAPI, regTool ToolRegistrar, regStage StageRegistrar, regAPI APIRegistrar) *PluginSDK {
return &PluginSDK{
name: name,
iom: iom,
eventBus: eventBus,
mem: mem,
textMem: textMem,
docMem: docMem,
know: know,
llm: llm,
sett: sett,
regTool: regTool,
regStage: regStage,
regAPI: regAPI,
logger: log.Default(),
}
}
// === IO 双通道 ===
func (s *PluginSDK) InjectInput(source, channel string, payload map[string]interface{}) {
if s.iom != nil {
s.iom.InjectInputTo(source, channel, "text", payload)
}
}
func (s *PluginSDK) InjectInterrupt(source, channel string, payload map[string]interface{}) {
if s.iom != nil {
p := payload
if p == nil {
p = map[string]interface{}{}
}
if _, ok := p["type"]; !ok {
p["type"] = "text"
}
s.iom.InjectInterrupt(source, channel, p)
}
}
func (s *PluginSDK) InjectText(source, channel, text string) {
if s.iom != nil {
s.iom.InjectTextTo(source, channel, text)
}
}
func (s *PluginSDK) InjectTextSync(source, channel, text string) *agentIO.OutputEvent {
if s.iom != nil {
return s.iom.InjectTextSyncTo(source, channel, text)
}
return nil
}
func (s *PluginSDK) InjectInterruptText(source, channel, text string) {
if s.iom != nil {
s.iom.InjectInterruptText(source, channel, text)
}
}
func (s *PluginSDK) OutputChan() <-chan *agentIO.OutputEvent {
if s.iom != nil {
return s.iom.OutputChan()
}
return nil
}
func (s *PluginSDK) RegisterChannel(name string, dev agentIO.Device) error {
if s.iom != nil {
return s.iom.RegisterDevice(dev)
}
return nil
}
func (s *PluginSDK) UnregisterChannel(name string) {
if s.iom != nil {
s.iom.UnregisterDevice(name)
}
}
func (s *PluginSDK) ListChannels() []agentIO.ChannelInfo {
if s.iom != nil {
return s.iom.ListChannels()
}
return nil
}
// === 三通道 ===
func (s *PluginSDK) Publish(evt *events.Event) {
if s.eventBus != nil {
s.eventBus.Publish(evt)
}
}
func (s *PluginSDK) Subscribe(eventType events.EventType, handler events.Handler) func() {
if s.eventBus != nil {
return s.eventBus.Subscribe(eventType, handler)
}
return func() {}
}
// === 能力 ===
func (s *PluginSDK) Memory() MemoryAPI { return s.mem }
func (s *PluginSDK) TextMemory() TextMemoryAPI { return s.textMem }
func (s *PluginSDK) DocMemory() DocMemoryAPI { return s.docMem }
func (s *PluginSDK) Knowledge() KnowledgeAPI { return s.know }
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 s.regTool != nil {
return s.regTool(name, def, handler)
}
return nil
}
func (s *PluginSDK) RegisterStage(stage Stage, handler StageHandler) {
if s.regStage != nil {
s.regStage(stage, handler)
}
}
func (s *PluginSDK) RegisterPluginAPI(name string) error {
if s.regAPI != nil {
return s.regAPI(name)
}
return nil
}
func (s *PluginSDK) PluginName() string { return s.name }
func (s *PluginSDK) Logger() *log.Logger { return s.logger }