mirror of
https://gitcode.com/JianFeeeee/HomeAgent.git
synced 2026-10-15 03:52:25 +00:00
refactor: migrate built-in plugins to SDK-only interface
- Six-phase plan complete: webui/cli/healthcheck/pluginmgr/clawhubadapter now interact with the kernel exclusively via internal/sdk interfaces; all Configure() calls and package-level global injection removed - buildSDK in internal/plugin/registry.go is the single assembly point - Add internal/sdk/events.go exporting event types/constants - Fix ProviderManager cooldown sharing: LuaAdaptedProvider.Name() now returns the source name instead of lua_<adapter>, so multiple sources sharing an adapter (single script load via shared VM AdapterCache) no longer share failure-cooldown state - Verified: build/vet/tests green, deployed to homeagent.service with full plugin capability testing via local OpenAI-compatible mock
This commit is contained in:
16
internal/sdk/adapter.go
Normal file
16
internal/sdk/adapter.go
Normal file
@ -0,0 +1,16 @@
|
||||
package sdk
|
||||
|
||||
// AdapterAPI provides access to the Lua LLM protocol adapters.
|
||||
type AdapterAPI interface {
|
||||
List() []APIAdapter
|
||||
// Load writes the adapter source file and hot-reloads it.
|
||||
Load(name, code string) error
|
||||
Remove(name string) error
|
||||
AdapterDir() string
|
||||
}
|
||||
|
||||
// APIAdapter describes a loaded Lua protocol adapter.
|
||||
type APIAdapter struct {
|
||||
Name string `json:"name"`
|
||||
Version string `json:"version"`
|
||||
}
|
||||
62
internal/sdk/adapter_impl.go
Normal file
62
internal/sdk/adapter_impl.go
Normal file
@ -0,0 +1,62 @@
|
||||
package sdk
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"os"
|
||||
"path/filepath"
|
||||
|
||||
luaVM "gitcode.com/JianFeeeee/HomeAgent/internal/lua"
|
||||
)
|
||||
|
||||
// adapterImpl 桥接 Lua VM 的协议适配器管理。
|
||||
type adapterImpl struct {
|
||||
vm *luaVM.VM
|
||||
}
|
||||
|
||||
func NewAdapter(vm *luaVM.VM) AdapterAPI {
|
||||
return &adapterImpl{vm: vm}
|
||||
}
|
||||
|
||||
func (a *adapterImpl) List() []APIAdapter {
|
||||
if a.vm == nil {
|
||||
return nil
|
||||
}
|
||||
got := a.vm.ListAdapters()
|
||||
out := make([]APIAdapter, len(got))
|
||||
for i, ad := range got {
|
||||
out[i] = APIAdapter{Name: ad.Name, Version: ad.Version}
|
||||
}
|
||||
return out
|
||||
}
|
||||
|
||||
func (a *adapterImpl) Load(name, code string) error {
|
||||
if a.vm == nil {
|
||||
return errors.New("lua vm not available")
|
||||
}
|
||||
path := filepath.Join(a.vm.AdapterDir(), name+".lua")
|
||||
if err := os.WriteFile(path, []byte(code), 0644); err != nil {
|
||||
return err
|
||||
}
|
||||
return a.vm.LoadAdapter(path)
|
||||
}
|
||||
|
||||
func (a *adapterImpl) Remove(name string) error {
|
||||
if a.vm == nil {
|
||||
return errors.New("lua vm not available")
|
||||
}
|
||||
path := filepath.Join(a.vm.AdapterDir(), name+".lua")
|
||||
if err := os.Remove(path); err != nil {
|
||||
return err
|
||||
}
|
||||
a.vm.RemoveAdapter(name)
|
||||
return nil
|
||||
}
|
||||
|
||||
func (a *adapterImpl) AdapterDir() string {
|
||||
if a.vm == nil {
|
||||
return ""
|
||||
}
|
||||
return a.vm.AdapterDir()
|
||||
}
|
||||
|
||||
var _ AdapterAPI = (*adapterImpl)(nil)
|
||||
10
internal/sdk/config.go
Normal file
10
internal/sdk/config.go
Normal file
@ -0,0 +1,10 @@
|
||||
package sdk
|
||||
|
||||
import "gitcode.com/JianFeeeee/HomeAgent/pkg/types"
|
||||
|
||||
// ConfigAPI provides read/write access to the runtime config object
|
||||
// (unrelated to the config database).
|
||||
type ConfigAPI interface {
|
||||
Get() *types.Config
|
||||
Put(cfg *types.Config)
|
||||
}
|
||||
22
internal/sdk/config_impl.go
Normal file
22
internal/sdk/config_impl.go
Normal file
@ -0,0 +1,22 @@
|
||||
package sdk
|
||||
|
||||
import "gitcode.com/JianFeeeee/HomeAgent/pkg/types"
|
||||
|
||||
// configImpl 持有运行期配置对象的引用(与配置数据库无关)。
|
||||
type configImpl struct {
|
||||
cfg *types.Config
|
||||
}
|
||||
|
||||
func NewConfig(cfg *types.Config) ConfigAPI {
|
||||
return &configImpl{cfg: cfg}
|
||||
}
|
||||
|
||||
func (c *configImpl) Get() *types.Config {
|
||||
return c.cfg
|
||||
}
|
||||
|
||||
func (c *configImpl) Put(cfg *types.Config) {
|
||||
c.cfg = cfg
|
||||
}
|
||||
|
||||
var _ ConfigAPI = (*configImpl)(nil)
|
||||
20
internal/sdk/events.go
Normal file
20
internal/sdk/events.go
Normal file
@ -0,0 +1,20 @@
|
||||
package sdk
|
||||
|
||||
import "gitcode.com/JianFeeeee/HomeAgent/internal/events"
|
||||
|
||||
// 事件类型/事件类型别名:插件经 SDK 订阅/发布内核事件,
|
||||
// 无需直接 import internal/events(内核事件总线仅通过 SDK 暴露)。
|
||||
|
||||
type EventType = events.EventType
|
||||
type Event = events.Event
|
||||
|
||||
const (
|
||||
EventRawInput = events.EventRawInput
|
||||
EventAgentOutput = events.EventAgentOutput
|
||||
EventAgentLLMChain = events.EventAgentLLMChain
|
||||
EventToolCall = events.EventToolCall
|
||||
EventReasoning = events.EventReasoning
|
||||
EventStage = events.EventStage
|
||||
EventSystem = events.EventSystem
|
||||
EventAll = events.EventAll
|
||||
)
|
||||
17
internal/sdk/indexer.go
Normal file
17
internal/sdk/indexer.go
Normal file
@ -0,0 +1,17 @@
|
||||
package sdk
|
||||
|
||||
// IndexerAPI exposes the memory context indexer.
|
||||
type IndexerAPI interface {
|
||||
BuildContext(q string) (*IndexContext, error)
|
||||
FormatContext(ctx *IndexContext) string
|
||||
GetToolDefinitions() []map[string]interface{}
|
||||
BuildToolPrompt() string
|
||||
}
|
||||
|
||||
// IndexContext is a neutral snapshot of the injected memory context.
|
||||
type IndexContext struct {
|
||||
Entities []Entity `json:"entities"`
|
||||
Relations []Relation `json:"relations"`
|
||||
Summary string `json:"summary"`
|
||||
TokenEstimate int `json:"token_estimate"`
|
||||
}
|
||||
71
internal/sdk/indexer_impl.go
Normal file
71
internal/sdk/indexer_impl.go
Normal file
@ -0,0 +1,71 @@
|
||||
package sdk
|
||||
|
||||
import (
|
||||
"errors"
|
||||
|
||||
"gitcode.com/JianFeeeee/HomeAgent/internal/memory"
|
||||
)
|
||||
|
||||
// indexerImpl 桥接 memory.Indexer 到中立的 IndexerAPI。
|
||||
type indexerImpl struct {
|
||||
idx *memory.Indexer
|
||||
}
|
||||
|
||||
func NewIndexer(idx *memory.Indexer) IndexerAPI {
|
||||
return &indexerImpl{idx: idx}
|
||||
}
|
||||
|
||||
func (i *indexerImpl) BuildContext(q string) (*IndexContext, error) {
|
||||
if i.idx == nil {
|
||||
return nil, errors.New("indexer not available")
|
||||
}
|
||||
ctx := i.idx.BuildContext(q)
|
||||
if ctx == nil {
|
||||
return &IndexContext{}, nil
|
||||
}
|
||||
entities := make([]Entity, len(ctx.Entities))
|
||||
for j, e := range ctx.Entities {
|
||||
entities[j] = Entity{Name: e.Name, Type: e.Type, MentionCount: e.MentionCount}
|
||||
}
|
||||
relations := make([]Relation, len(ctx.Relations))
|
||||
for j, r := range ctx.Relations {
|
||||
relations[j] = Relation{SourceName: r.SourceName, TargetName: r.TargetName, RelationType: r.RelationType}
|
||||
}
|
||||
return &IndexContext{
|
||||
Entities: entities,
|
||||
Relations: relations,
|
||||
Summary: ctx.Summary,
|
||||
TokenEstimate: ctx.TokenEstimate,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (i *indexerImpl) FormatContext(ctx *IndexContext) string {
|
||||
if i.idx == nil || ctx == nil {
|
||||
return ""
|
||||
}
|
||||
entities := make([]memory.Entity, len(ctx.Entities))
|
||||
for j, e := range ctx.Entities {
|
||||
entities[j] = memory.Entity{Name: e.Name, Type: e.Type, MentionCount: e.MentionCount}
|
||||
}
|
||||
return i.idx.FormatContext(&memory.InjectedContext{
|
||||
Entities: entities,
|
||||
Summary: ctx.Summary,
|
||||
TokenEstimate: ctx.TokenEstimate,
|
||||
})
|
||||
}
|
||||
|
||||
func (i *indexerImpl) GetToolDefinitions() []map[string]interface{} {
|
||||
if i.idx == nil {
|
||||
return nil
|
||||
}
|
||||
return i.idx.GetToolDefinitions()
|
||||
}
|
||||
|
||||
func (i *indexerImpl) BuildToolPrompt() string {
|
||||
if i.idx == nil {
|
||||
return ""
|
||||
}
|
||||
return i.idx.BuildToolPrompt()
|
||||
}
|
||||
|
||||
var _ IndexerAPI = (*indexerImpl)(nil)
|
||||
@ -2,5 +2,13 @@ package sdk
|
||||
|
||||
import pubsdk "gitcode.com/JianFeeeee/homeagent-sdk/sdk"
|
||||
|
||||
type KnowledgeAPI = pubsdk.KnowledgeAPI
|
||||
// KnowledgeAPI 是内置插件使用的全量知识库接口。
|
||||
type KnowledgeAPI interface {
|
||||
pubsdk.KnowledgeAPI
|
||||
// Stats 返回知识库的运行统计。
|
||||
Stats() map[string]interface{}
|
||||
// Remove 按名称删除一条知识。
|
||||
Remove(name string) error
|
||||
}
|
||||
|
||||
type Knowledge = pubsdk.Knowledge
|
||||
|
||||
@ -26,4 +26,14 @@ func (k *knowledgeImpl) List() ([]string, error) {
|
||||
return k.ks.List(), nil
|
||||
}
|
||||
|
||||
func (k *knowledgeImpl) Stats() map[string]interface{} {
|
||||
if k.ks == nil { return map[string]interface{}{} }
|
||||
return k.ks.Stats()
|
||||
}
|
||||
|
||||
func (k *knowledgeImpl) Remove(name string) error {
|
||||
if k.ks == nil { return nil }
|
||||
return k.ks.Remove(name)
|
||||
}
|
||||
|
||||
var _ KnowledgeAPI = (*knowledgeImpl)(nil)
|
||||
|
||||
@ -1,5 +1,62 @@
|
||||
package sdk
|
||||
|
||||
import pubsdk "gitcode.com/JianFeeeee/homeagent-sdk/sdk"
|
||||
import (
|
||||
"context"
|
||||
|
||||
type LLMAPI = pubsdk.LLMAPI
|
||||
pubsdk "gitcode.com/JianFeeeee/homeagent-sdk/sdk"
|
||||
)
|
||||
|
||||
// LLMAPI 是内置插件使用的全量 LLM Provider 接口。
|
||||
// 内置 SDK 为全量接口,外部 SDK 仅暴露其中的安全子集(pubsdk.LLMAPI)。
|
||||
type LLMAPI interface {
|
||||
pubsdk.LLMAPI
|
||||
// ReloadFromConfig 从内核配置重建所有 LLM Provider。
|
||||
ReloadFromConfig() error
|
||||
// Chat 向默认 Provider 发起一次补全调用(非记忆管线)。
|
||||
Chat(ctx context.Context, req *LLMCompletionRequest) (*LLMCompletionResponse, error)
|
||||
}
|
||||
|
||||
// LLMMessage 是面向 LLM Provider 的中立对话消息。
|
||||
type LLMMessage struct {
|
||||
Role string `json:"role"`
|
||||
Content string `json:"content,omitempty"`
|
||||
ReasoningContent string `json:"reasoning_content,omitempty"`
|
||||
ToolCallID string `json:"tool_call_id,omitempty"`
|
||||
ToolCalls []LLMToolCall `json:"tool_calls,omitempty"`
|
||||
}
|
||||
|
||||
// LLMToolCall 是中立的工具调用请求。
|
||||
type LLMToolCall struct {
|
||||
ID string `json:"id"`
|
||||
Name string `json:"name"`
|
||||
Arguments map[string]interface{} `json:"arguments"`
|
||||
}
|
||||
|
||||
// LLMTokenUsage 报告一次补全的 token 消耗。
|
||||
type LLMTokenUsage struct {
|
||||
Prompt int `json:"prompt"`
|
||||
Completion int `json:"completion"`
|
||||
Total int `json:"total"`
|
||||
}
|
||||
|
||||
// LLMCompletionRequest 是中立的补全请求。
|
||||
// Tools/ToolChoice 使用 OpenAI 风格载荷,便于 Provider 直接转发。
|
||||
type LLMCompletionRequest struct {
|
||||
Model string `json:"model,omitempty"`
|
||||
Messages []LLMMessage `json:"messages"`
|
||||
Temperature float64 `json:"temperature,omitempty"`
|
||||
MaxTokens int `json:"max_tokens,omitempty"`
|
||||
Stream bool `json:"stream,omitempty"`
|
||||
Tools []interface{} `json:"tools,omitempty"`
|
||||
ToolChoice interface{} `json:"tool_choice,omitempty"`
|
||||
DisableThinking bool `json:"disable_thinking,omitempty"`
|
||||
}
|
||||
|
||||
// LLMCompletionResponse 是中立的补全响应。
|
||||
type LLMCompletionResponse struct {
|
||||
Content string `json:"content"`
|
||||
ReasoningContent string `json:"reasoning_content,omitempty"`
|
||||
FinishReason string `json:"finish_reason,omitempty"`
|
||||
TokenUsage LLMTokenUsage `json:"token_usage,omitempty"`
|
||||
ToolCalls []LLMToolCall `json:"tool_calls,omitempty"`
|
||||
}
|
||||
|
||||
@ -1,26 +1,133 @@
|
||||
package sdk
|
||||
|
||||
import agentAPI "gitcode.com/JianFeeeee/HomeAgent/internal/agent/api"
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
|
||||
type llmImpl struct{ mgr *agentAPI.ProviderManager }
|
||||
agentAPI "gitcode.com/JianFeeeee/HomeAgent/internal/agent/api"
|
||||
internalConfig "gitcode.com/JianFeeeee/HomeAgent/internal/config"
|
||||
luaVM "gitcode.com/JianFeeeee/HomeAgent/internal/lua"
|
||||
)
|
||||
|
||||
func NewLLM(mgr *agentAPI.ProviderManager) LLMAPI { return &llmImpl{mgr: mgr} }
|
||||
type llmImpl struct {
|
||||
mgr *agentAPI.ProviderManager
|
||||
cfgReg *internalConfig.ConfigRegistry
|
||||
lua *luaVM.VM
|
||||
baseAPIKey string
|
||||
}
|
||||
|
||||
func NewLLM(mgr *agentAPI.ProviderManager, cfgReg *internalConfig.ConfigRegistry, lua *luaVM.VM, baseAPIKey string) LLMAPI {
|
||||
return &llmImpl{mgr: mgr, cfgReg: cfgReg, lua: lua, baseAPIKey: baseAPIKey}
|
||||
}
|
||||
|
||||
func (l *llmImpl) ListSources() []string {
|
||||
if l.mgr == nil { return nil }
|
||||
if l.mgr == nil {
|
||||
return nil
|
||||
}
|
||||
return l.mgr.List()
|
||||
}
|
||||
|
||||
func (l *llmImpl) SetSource(name string) error {
|
||||
if l.mgr == nil { return nil }
|
||||
if l.mgr == nil {
|
||||
return nil
|
||||
}
|
||||
return l.mgr.SetDefault(name)
|
||||
}
|
||||
|
||||
func (l *llmImpl) CurrentSource() string {
|
||||
if l.mgr == nil { return "" }
|
||||
if l.mgr == nil {
|
||||
return ""
|
||||
}
|
||||
p := l.mgr.Default()
|
||||
if p == nil { return "" }
|
||||
if p == nil {
|
||||
return ""
|
||||
}
|
||||
return p.Name()
|
||||
}
|
||||
|
||||
func (l *llmImpl) Chat(ctx context.Context, req *LLMCompletionRequest) (*LLMCompletionResponse, error) {
|
||||
if l.mgr == nil {
|
||||
return nil, fmt.Errorf("llm: provider manager not available")
|
||||
}
|
||||
p := l.mgr.Default()
|
||||
if p == nil {
|
||||
return nil, fmt.Errorf("llm: no default provider")
|
||||
}
|
||||
apiReq := &agentAPI.CompletionRequest{
|
||||
Model: req.Model,
|
||||
Temperature: req.Temperature,
|
||||
MaxTokens: req.MaxTokens,
|
||||
Stream: req.Stream,
|
||||
Tools: req.Tools,
|
||||
ToolChoice: req.ToolChoice,
|
||||
DisableThinking: req.DisableThinking,
|
||||
}
|
||||
if len(req.Messages) > 0 {
|
||||
apiReq.Messages = make([]agentAPI.Message, len(req.Messages))
|
||||
for i, m := range req.Messages {
|
||||
msg := agentAPI.Message{
|
||||
Role: m.Role,
|
||||
Content: m.Content,
|
||||
ReasoningContent: m.ReasoningContent,
|
||||
ToolCallID: m.ToolCallID,
|
||||
}
|
||||
if len(m.ToolCalls) > 0 {
|
||||
msg.ToolCalls = make([]agentAPI.ToolCall, len(m.ToolCalls))
|
||||
for j, tc := range m.ToolCalls {
|
||||
msg.ToolCalls[j] = agentAPI.ToolCall{ID: tc.ID, Name: tc.Name, Arguments: tc.Arguments}
|
||||
}
|
||||
}
|
||||
apiReq.Messages[i] = msg
|
||||
}
|
||||
}
|
||||
resp, err := p.Chat(ctx, apiReq)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
out := &LLMCompletionResponse{
|
||||
Content: resp.Content,
|
||||
ReasoningContent: resp.ReasoningContent,
|
||||
FinishReason: resp.FinishReason,
|
||||
TokenUsage: LLMTokenUsage{
|
||||
Prompt: resp.TokenUsage.Prompt,
|
||||
Completion: resp.TokenUsage.Completion,
|
||||
Total: resp.TokenUsage.Total,
|
||||
},
|
||||
}
|
||||
for _, tc := range resp.ToolCalls {
|
||||
out.ToolCalls = append(out.ToolCalls, LLMToolCall{ID: tc.ID, Name: tc.Name, Arguments: tc.Arguments})
|
||||
}
|
||||
return out, nil
|
||||
}
|
||||
|
||||
func (l *llmImpl) ReloadFromConfig() error {
|
||||
if l.mgr == nil || l.cfgReg == nil || l.lua == nil {
|
||||
return nil
|
||||
}
|
||||
cfg := l.cfgReg.ToConfig()
|
||||
if cfg == nil {
|
||||
return nil
|
||||
}
|
||||
l.mgr.Reset()
|
||||
for _, src := range cfg.LLM.Sources {
|
||||
key := src.APIKey
|
||||
if key == "" {
|
||||
key = l.baseAPIKey
|
||||
}
|
||||
provider := agentAPI.NewLuaAdaptedProvider(agentAPI.BaseConfig{
|
||||
Model: src.Model,
|
||||
BaseURL: src.BaseURL,
|
||||
APIKey: key,
|
||||
Temperature: cfg.LLM.Temperature,
|
||||
MaxTokens: cfg.LLM.MaxTokens,
|
||||
ContextWindow: src.ContextWindow,
|
||||
}, l.lua, src.Name, src.Adapter)
|
||||
l.mgr.Register(src.Name, provider)
|
||||
}
|
||||
if cfg.LLM.Provider != "" {
|
||||
_ = l.mgr.SetDefault(cfg.LLM.Provider)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
var _ LLMAPI = (*llmImpl)(nil)
|
||||
|
||||
@ -2,12 +2,26 @@ package sdk
|
||||
|
||||
import pubsdk "gitcode.com/JianFeeeee/homeagent-sdk/sdk"
|
||||
|
||||
type MemoryAPI = pubsdk.MemoryAPI
|
||||
// MemoryAPI 是内置插件使用的全量图记忆接口。
|
||||
type MemoryAPI interface {
|
||||
pubsdk.MemoryAPI
|
||||
// GraphData 返回整个知识图谱的完整快照。
|
||||
GraphData() (map[string]interface{}, error)
|
||||
}
|
||||
|
||||
type Entity = pubsdk.Entity
|
||||
type Relation = pubsdk.Relation
|
||||
type Triple = pubsdk.Triple
|
||||
|
||||
type TextMemoryAPI = pubsdk.TextMemoryAPI
|
||||
// TextMemoryAPI 是内置插件使用的全量文本记忆接口。
|
||||
type TextMemoryAPI interface {
|
||||
pubsdk.TextMemoryAPI
|
||||
// RecentEvents 返回最近 n 条记忆事件。
|
||||
RecentEvents(n int) ([]TextEvent, error)
|
||||
// Stats 返回文本记忆的运行统计。
|
||||
Stats() map[string]interface{}
|
||||
}
|
||||
|
||||
type TextEvent = pubsdk.TextEvent
|
||||
|
||||
type DocMemoryAPI = pubsdk.DocMemoryAPI
|
||||
|
||||
@ -50,6 +50,11 @@ func (m *graphMemory) Purge(criteria map[string]string, mode string) (int, error
|
||||
return m.db.Purge(criteria, mode)
|
||||
}
|
||||
|
||||
func (m *graphMemory) GraphData() (map[string]interface{}, error) {
|
||||
if m.db == nil { return map[string]interface{}{}, nil }
|
||||
return m.db.GraphData()
|
||||
}
|
||||
|
||||
type textMemoryImpl struct{ tm *text.Memory }
|
||||
|
||||
func NewTextMemory(tm *text.Memory) TextMemoryAPI { return &textMemoryImpl{tm: tm} }
|
||||
@ -61,6 +66,22 @@ func (m *textMemoryImpl) Append(evt TextEvent) error {
|
||||
})
|
||||
}
|
||||
|
||||
func (m *textMemoryImpl) RecentEvents(n int) ([]TextEvent, error) {
|
||||
if m.tm == nil { return nil, nil }
|
||||
got, err := m.tm.RecentEvents(n)
|
||||
if err != nil { return nil, err }
|
||||
out := make([]TextEvent, len(got))
|
||||
for i, e := range got {
|
||||
out[i] = TextEvent{Role: e.Source, Content: e.Input, Timestamp: e.Timestamp, Channel: e.AgentID}
|
||||
}
|
||||
return out, nil
|
||||
}
|
||||
|
||||
func (m *textMemoryImpl) Stats() map[string]interface{} {
|
||||
if m.tm == nil { return map[string]interface{}{} }
|
||||
return m.tm.Stats()
|
||||
}
|
||||
|
||||
type docMemoryImpl struct{ ds *doc.Store }
|
||||
|
||||
func NewDocMemory(ds *doc.Store) DocMemoryAPI { return &docMemoryImpl{ds: ds} }
|
||||
|
||||
@ -57,6 +57,12 @@ type DisabledPluginInfo struct {
|
||||
DisabledBy string `json:"disabled_by"`
|
||||
}
|
||||
|
||||
// PluginMeta is the display-name metadata for a plugin (from plg.json / RegisterPluginMeta).
|
||||
type PluginMeta struct {
|
||||
NameZh string `json:"name_zh"`
|
||||
NameEn string `json:"name_en"`
|
||||
}
|
||||
|
||||
type PluginManager interface {
|
||||
ListLoadedPlugins() []string
|
||||
ListDisabledPlugins() []DisabledPluginInfo
|
||||
@ -64,18 +70,45 @@ type PluginManager interface {
|
||||
DisablePlugin(name, by string) error
|
||||
EnablePlugin(name string) error
|
||||
ReloadPlugins() (string, error)
|
||||
PluginMetas() map[string]PluginMeta
|
||||
PluginDir() string
|
||||
}
|
||||
|
||||
type PluginSDK struct {
|
||||
*pubsdk.PluginSDK
|
||||
settings SettingsAPI
|
||||
memory MemoryAPI
|
||||
textMem TextMemoryAPI
|
||||
docMem DocMemoryAPI
|
||||
know KnowledgeAPI
|
||||
llm LLMAPI
|
||||
|
||||
iom *agentIO.IOManager
|
||||
eventBus *events.Bus
|
||||
logger *log.Logger
|
||||
pluginMgr PluginManager
|
||||
|
||||
status StatusAPI
|
||||
supervisor SupervisorAPI
|
||||
skill SkillAPI
|
||||
adapter AdapterAPI
|
||||
tracker TrackerAPI
|
||||
config ConfigAPI
|
||||
tool ToolAPI
|
||||
indexer IndexerAPI
|
||||
}
|
||||
|
||||
func (s *PluginSDK) PluginMgr() PluginManager { return s.pluginMgr }
|
||||
|
||||
// 以下访问器遮蔽公共 SDK 的同名方法,返回内置插件可用的全量接口。
|
||||
|
||||
func (s *PluginSDK) Settings() SettingsAPI { return s.settings }
|
||||
func (s *PluginSDK) Memory() MemoryAPI { return s.memory }
|
||||
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 }
|
||||
|
||||
// ioAdapter 桥接 IOManager 到公共 SDK 的 IOInjector 接口,
|
||||
// 确保外部插件通过 s.InjectText() 等方法的调用能被路由到内核 IO 层。
|
||||
type ioAdapter struct{ iom *agentIO.IOManager }
|
||||
@ -114,6 +147,15 @@ type SDKConfig struct {
|
||||
RegOutput OutputChannelRegistrar
|
||||
RegInput InputChannelRegistrar
|
||||
PluginMgr PluginManager
|
||||
|
||||
Status StatusAPI
|
||||
Supervisor SupervisorAPI
|
||||
Skill SkillAPI
|
||||
Adapter AdapterAPI
|
||||
Tracker TrackerAPI
|
||||
Config ConfigAPI
|
||||
Tool ToolAPI
|
||||
Indexer IndexerAPI
|
||||
}
|
||||
|
||||
func New(name string, cfg SDKConfig) *PluginSDK {
|
||||
@ -131,13 +173,38 @@ func New(name string, cfg SDKConfig) *PluginSDK {
|
||||
base.SetLLMAPI(cfg.LLM)
|
||||
return &PluginSDK{
|
||||
PluginSDK: base,
|
||||
settings: cfg.Settings,
|
||||
memory: cfg.Memory,
|
||||
textMem: cfg.TextMemory,
|
||||
docMem: cfg.DocMemory,
|
||||
know: cfg.Knowledge,
|
||||
llm: cfg.LLM,
|
||||
|
||||
iom: cfg.IOManager,
|
||||
eventBus: cfg.EventBus,
|
||||
logger: log.Default(),
|
||||
pluginMgr: cfg.PluginMgr,
|
||||
|
||||
status: cfg.Status,
|
||||
supervisor: cfg.Supervisor,
|
||||
skill: cfg.Skill,
|
||||
adapter: cfg.Adapter,
|
||||
tracker: cfg.Tracker,
|
||||
config: cfg.Config,
|
||||
tool: cfg.Tool,
|
||||
indexer: cfg.Indexer,
|
||||
}
|
||||
}
|
||||
|
||||
func (s *PluginSDK) Status() StatusAPI { return s.status }
|
||||
func (s *PluginSDK) Supervisor() SupervisorAPI { return s.supervisor }
|
||||
func (s *PluginSDK) Skill() SkillAPI { return s.skill }
|
||||
func (s *PluginSDK) Adapter() AdapterAPI { return s.adapter }
|
||||
func (s *PluginSDK) Tracker() TrackerAPI { return s.tracker }
|
||||
func (s *PluginSDK) Config() ConfigAPI { return s.config }
|
||||
func (s *PluginSDK) Tool() ToolAPI { return s.tool }
|
||||
func (s *PluginSDK) Indexer() IndexerAPI { return s.indexer }
|
||||
|
||||
func (s *PluginSDK) InjectInput(source, channel, eventType string, payload map[string]interface{}) {
|
||||
if s.iom != nil {
|
||||
s.iom.InjectInputTo(source, channel, eventType, payload)
|
||||
|
||||
@ -2,5 +2,13 @@ package sdk
|
||||
|
||||
import pubsdk "gitcode.com/JianFeeeee/homeagent-sdk/sdk"
|
||||
|
||||
type SettingsAPI = pubsdk.SettingsAPI
|
||||
// SettingsAPI 是内置插件使用的全量配置接口。
|
||||
type SettingsAPI interface {
|
||||
pubsdk.SettingsAPI
|
||||
// DefsCore 返回核心配置表中匹配前缀的配置定义。
|
||||
DefsCore(prefix string) []*ConfigDef
|
||||
// DefsPlugin 返回另一个插件的配置定义。
|
||||
DefsPlugin(plugin, prefix string) []*ConfigDef
|
||||
}
|
||||
|
||||
type ConfigDef = pubsdk.ConfigDef
|
||||
|
||||
@ -79,7 +79,7 @@ func (s *settingsImpl) Defs(prefix string) []*ConfigDef {
|
||||
// ConfigDef = pubsdk.ConfigDef (type alias), so direct conversion works
|
||||
cpy := ConfigDef{
|
||||
Key: d.Key, Type: d.Type, DisplayName: d.DisplayName, Description: d.Description,
|
||||
Category: d.Category, Options: d.Options,
|
||||
Category: d.Category, Options: d.Options, Default: d.Default,
|
||||
}
|
||||
out[i] = &cpy
|
||||
}
|
||||
@ -97,6 +97,25 @@ func (s *settingsImpl) Plugins() []string {
|
||||
result = append(result, names...)
|
||||
return result
|
||||
}
|
||||
func (s *settingsImpl) DefsCore(prefix string) []*ConfigDef {
|
||||
if s.reg == nil { return nil }
|
||||
return mapDefs(s.reg.ListDefs(prefix))
|
||||
}
|
||||
func (s *settingsImpl) DefsPlugin(plugin, prefix string) []*ConfigDef {
|
||||
if s.reg == nil { return nil }
|
||||
return mapDefs(s.reg.PluginConfig(plugin).ListDefs(prefix))
|
||||
}
|
||||
|
||||
func mapDefs(defs []*internalConfig.ConfigDef) []*ConfigDef {
|
||||
out := make([]*ConfigDef, len(defs))
|
||||
for i, d := range defs {
|
||||
out[i] = &ConfigDef{
|
||||
Key: d.Key, Type: d.Type, DisplayName: d.DisplayName, Description: d.Description,
|
||||
Category: d.Category, Options: d.Options, Default: d.Default,
|
||||
}
|
||||
}
|
||||
return out
|
||||
}
|
||||
|
||||
func stringifyDefault(v interface{}) string {
|
||||
if v == nil { return "" }
|
||||
|
||||
22
internal/sdk/skill.go
Normal file
22
internal/sdk/skill.go
Normal file
@ -0,0 +1,22 @@
|
||||
package sdk
|
||||
|
||||
// SkillAPI provides access to the skill manager.
|
||||
type SkillAPI interface {
|
||||
List() []*Skill
|
||||
Get(name string) *Skill
|
||||
Install(name, content string) error
|
||||
Uninstall(name string) error
|
||||
Toggle(name string, enabled bool) error
|
||||
}
|
||||
|
||||
// Skill is a neutral description of an installed skill.
|
||||
type Skill struct {
|
||||
Name string `json:"name"`
|
||||
Description string `json:"description"`
|
||||
Version string `json:"version"`
|
||||
Author string `json:"author,omitempty"`
|
||||
Entry string `json:"entry,omitempty"`
|
||||
Source string `json:"source,omitempty"`
|
||||
Enabled bool `json:"enabled"`
|
||||
RawContent string `json:"-"`
|
||||
}
|
||||
99
internal/sdk/status.go
Normal file
99
internal/sdk/status.go
Normal file
@ -0,0 +1,99 @@
|
||||
package sdk
|
||||
|
||||
// StatusAPI provides a snapshot of the kernel runtime status.
|
||||
type StatusAPI interface {
|
||||
GetKernelStatus() *KernelStatus
|
||||
}
|
||||
|
||||
// KernelStatus is the aggregated runtime snapshot of all kernel subsystems.
|
||||
type KernelStatus struct {
|
||||
Uptime string `json:"uptime"`
|
||||
StartTime string `json:"start_time"`
|
||||
|
||||
AgentID string `json:"agent_id"`
|
||||
|
||||
Plugins []PluginInfo `json:"plugins"`
|
||||
Tools []ToolDef `json:"tools"`
|
||||
Channels []ChannelInfo `json:"channels"`
|
||||
|
||||
Memory MemoryStatus `json:"memory"`
|
||||
Knowledge KnowledgeStatus `json:"knowledge"`
|
||||
Documents DocumentStatus `json:"documents"`
|
||||
TextMemory TextMemoryStatus `json:"text_memory"`
|
||||
Social SocialStatus `json:"social"`
|
||||
Skills SkillsStatus `json:"skills"`
|
||||
|
||||
LLM LLMStatus `json:"llm"`
|
||||
|
||||
Context ContextStatus `json:"context"`
|
||||
|
||||
Runtime RuntimeStatus `json:"runtime"`
|
||||
|
||||
Tracker TrackerStatus `json:"tracker"`
|
||||
}
|
||||
|
||||
type PluginInfo struct {
|
||||
Name string `json:"name"`
|
||||
Loaded bool `json:"loaded"`
|
||||
}
|
||||
|
||||
type ChannelInfo struct {
|
||||
Name string `json:"name"`
|
||||
Type string `json:"type"`
|
||||
Ready bool `json:"ready"`
|
||||
}
|
||||
|
||||
type MemoryStatus struct {
|
||||
Available bool `json:"available"`
|
||||
EntityCount int `json:"entity_count"`
|
||||
RelationCount int `json:"relation_count"`
|
||||
EntityTypes int `json:"entity_types"`
|
||||
}
|
||||
|
||||
type KnowledgeStatus struct {
|
||||
Available bool `json:"available"`
|
||||
ItemCount int `json:"item_count"`
|
||||
Items []string `json:"items,omitempty"`
|
||||
}
|
||||
|
||||
type DocumentStatus struct {
|
||||
Available bool `json:"available"`
|
||||
DocCount int `json:"doc_count"`
|
||||
VectorCount int `json:"vector_count"`
|
||||
}
|
||||
|
||||
type TextMemoryStatus struct {
|
||||
Available bool `json:"available"`
|
||||
FileCount int `json:"file_count"`
|
||||
}
|
||||
|
||||
type SocialStatus struct {
|
||||
Available bool `json:"available"`
|
||||
PersonCount int `json:"person_count"`
|
||||
}
|
||||
|
||||
type SkillsStatus struct {
|
||||
Available bool `json:"available"`
|
||||
SkillList []string `json:"skill_list,omitempty"`
|
||||
}
|
||||
|
||||
type LLMStatus struct {
|
||||
Available bool `json:"available"`
|
||||
Provider string `json:"provider,omitempty"`
|
||||
Sources int `json:"sources,omitempty"`
|
||||
}
|
||||
|
||||
type ContextStatus struct {
|
||||
EventCount int `json:"event_count,omitempty"`
|
||||
}
|
||||
|
||||
type RuntimeStatus struct {
|
||||
Goroutines int `json:"goroutines"`
|
||||
MemoryMB int64 `json:"memory_mb"`
|
||||
GoVersion string `json:"go_version"`
|
||||
}
|
||||
|
||||
type TrackerStatus struct {
|
||||
Available bool `json:"available"`
|
||||
Dir string `json:"dir,omitempty"`
|
||||
}
|
||||
25
internal/sdk/supervisor.go
Normal file
25
internal/sdk/supervisor.go
Normal file
@ -0,0 +1,25 @@
|
||||
package sdk
|
||||
|
||||
import (
|
||||
"time"
|
||||
|
||||
"gitcode.com/JianFeeeee/HomeAgent/pkg/types"
|
||||
)
|
||||
|
||||
// SupervisorAPI provides access to agent lifecycle management.
|
||||
type SupervisorAPI interface {
|
||||
ListAgents() []AgentStatus
|
||||
GetAgentStatus(id string) (*AgentStatus, error)
|
||||
PreActionSnapshot(id string) (*types.Snapshot, error)
|
||||
RollbackAgent(id string, snapID string) error
|
||||
}
|
||||
|
||||
// AgentStatus is a neutral snapshot of a supervised agent.
|
||||
type AgentStatus struct {
|
||||
ID types.AgentID `json:"id"`
|
||||
State types.AgentState `json:"state"`
|
||||
Health types.HealthStatus `json:"health"`
|
||||
Uptime time.Duration `json:"uptime,omitempty"`
|
||||
Network types.NetworkCheckResult `json:"network,omitempty"`
|
||||
TrackerStats map[string]interface{} `json:"tracker_stats,omitempty"`
|
||||
}
|
||||
20
internal/sdk/tool.go
Normal file
20
internal/sdk/tool.go
Normal file
@ -0,0 +1,20 @@
|
||||
package sdk
|
||||
|
||||
// ToolSource 描述内核工具注册表/执行器的只读视图(由 StageHost 实现)。
|
||||
// 用接口而非具体类型,避免 sdk 依赖内核包(内核包反向依赖 sdk)。
|
||||
type ToolSource interface {
|
||||
GetToolDefs() []ToolDef
|
||||
ToolDef(name string) *ToolDef
|
||||
ExecuteTool(name string, args map[string]interface{}) (interface{}, error)
|
||||
}
|
||||
|
||||
// ToolAPI exposes the kernel tool registry and executor
|
||||
// (StageHost for plugin tools + IOManager for device/channel tools).
|
||||
type ToolAPI interface {
|
||||
// GetToolDefs returns all tools registered on the stage host (plugin tools).
|
||||
GetToolDefs() []ToolDef
|
||||
// GetAllTools returns all tools exposed by IO devices/channels.
|
||||
GetAllTools() []ToolDef
|
||||
// ExecuteTool executes a tool by name, resolving across the stage host first.
|
||||
ExecuteTool(name string, args map[string]interface{}) (interface{}, error)
|
||||
}
|
||||
50
internal/sdk/tool_impl.go
Normal file
50
internal/sdk/tool_impl.go
Normal file
@ -0,0 +1,50 @@
|
||||
package sdk
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
agentIO "gitcode.com/JianFeeeee/HomeAgent/internal/agent/io"
|
||||
)
|
||||
|
||||
// toolImpl 桥接 StageHost(插件工具)与 IOManager(设备/通道工具)。
|
||||
type toolImpl struct {
|
||||
stageHost ToolSource
|
||||
iom *agentIO.IOManager
|
||||
}
|
||||
|
||||
func NewTool(sh ToolSource, iom *agentIO.IOManager) ToolAPI {
|
||||
return &toolImpl{stageHost: sh, iom: iom}
|
||||
}
|
||||
|
||||
func (t *toolImpl) GetToolDefs() []ToolDef {
|
||||
if t.stageHost == nil {
|
||||
return nil
|
||||
}
|
||||
return t.stageHost.GetToolDefs()
|
||||
}
|
||||
|
||||
func (t *toolImpl) GetAllTools() []ToolDef {
|
||||
if t.iom == nil {
|
||||
return nil
|
||||
}
|
||||
defs := t.iom.GetAllTools()
|
||||
out := make([]ToolDef, 0, len(defs))
|
||||
for _, d := range defs {
|
||||
out = append(out, ToolDef{Name: d.Name, Description: d.Description, Parameters: d.Parameters})
|
||||
}
|
||||
return out
|
||||
}
|
||||
|
||||
func (t *toolImpl) ExecuteTool(name string, args map[string]interface{}) (interface{}, error) {
|
||||
if t.stageHost != nil {
|
||||
if def := t.stageHost.ToolDef(name); def != nil {
|
||||
return t.stageHost.ExecuteTool(name, args)
|
||||
}
|
||||
}
|
||||
if t.iom != nil {
|
||||
return t.iom.ExecuteTool(name, args)
|
||||
}
|
||||
return nil, fmt.Errorf("tool %s not found", name)
|
||||
}
|
||||
|
||||
var _ ToolAPI = (*toolImpl)(nil)
|
||||
36
internal/sdk/tracker.go
Normal file
36
internal/sdk/tracker.go
Normal file
@ -0,0 +1,36 @@
|
||||
package sdk
|
||||
|
||||
import "time"
|
||||
|
||||
// TrackerAPI provides access to the file change tracker / rollback.
|
||||
type TrackerAPI interface {
|
||||
ChangeSets() []*ChangeSet
|
||||
Rollback() error
|
||||
Stats() map[string]interface{}
|
||||
HasChanges() bool
|
||||
}
|
||||
|
||||
type ChangeType string
|
||||
|
||||
const (
|
||||
ChangeFileCreated ChangeType = "created"
|
||||
ChangeFileModified ChangeType = "modified"
|
||||
ChangeFileDeleted ChangeType = "deleted"
|
||||
)
|
||||
|
||||
type FileChange struct {
|
||||
Path string `json:"path"`
|
||||
Type ChangeType `json:"type"`
|
||||
SizeBefore int64 `json:"size_before,omitempty"`
|
||||
SizeAfter int64 `json:"size_after,omitempty"`
|
||||
HashBefore string `json:"hash_before,omitempty"`
|
||||
HashAfter string `json:"hash_after,omitempty"`
|
||||
Content []byte `json:"-"` // 回滚用原始内容,不参与 JSON 序列化
|
||||
}
|
||||
|
||||
type ChangeSet struct {
|
||||
ID string `json:"id"`
|
||||
Action string `json:"action"`
|
||||
Timestamp time.Time `json:"timestamp"`
|
||||
Files []FileChange `json:"files"`
|
||||
}
|
||||
Reference in New Issue
Block a user