Files
HomeAgent/internal/plugins/clawhubadapter/registry.go
jianf 76a4619ed5 refactor: rename openclaw -> clawhubadapter, add ClawHub search/install, fix agent interrupt
- Rename internal/plugins/openclaw/ -> internal/plugins/clawhubadapter/
- Add RegistryDispatcher with 5 sub-registries (Tool, Provider, Channel, Stage, Cap)
- Add clawhubadapter_search tool for ClawHub marketplace search
- Add clawhub: prefix support for installing from ClawHub (ZIP/tgz auto-detect)
- Add CallProvider RPC and provider/call routing
- Fix QQ interrupt: check interceptCh before/after each tool execution
2026-07-21 22:47:14 +08:00

252 lines
7.7 KiB
Go

package clawhubadapter
import (
"encoding/json"
"fmt"
"log"
"sync"
agentIO "gitcode.com/JianFeeeee/HomeAgent/internal/agent/io"
sdk "gitcode.com/JianFeeeee/HomeAgent/internal/sdk"
)
type ToolRegistry struct{}
func (r *ToolRegistry) Dispatch(data json.RawMessage, pluginName string, sp *sidecarProcess, s *sdk.PluginSDK) {
var d struct {
Name string `json:"name"`
Label string `json:"label"`
Description string `json:"description"`
Parameters map[string]interface{} `json:"parameters"`
}
if err := json.Unmarshal(data, &d); err != nil || d.Name == "" {
return
}
toolName := fmt.Sprintf("%s_%s", pluginName, d.Name)
tDef := sdk.ToolDef{
Name: toolName,
Description: d.Description,
Parameters: d.Parameters,
}
handler := func(sp *sidecarProcess, ocToolName string) sdk.ToolHandler {
return func(args map[string]interface{}) (interface{}, error) {
return sp.CallTool(ocToolName, args)
}
}(sp, d.Name)
if err := s.RegisterTool(toolName, tDef, handler); err != nil {
log.Printf("[clawhubadapter] register tool %s: %v", toolName, err)
}
}
type providerDef struct {
ocType string
toolSuffix string
desc string
}
var providerMap = []providerDef{
{"image_generation", "generate_image", "根据文本描述生成图片,返回图片 URL"},
{"music_generation", "generate_music", "根据描述生成音乐"},
{"video_generation", "generate_video", "根据描述生成视频"},
{"speech", "synthesize_speech", "将文本合成为语音"},
{"web_search", "web_search", "搜索互联网信息"},
{"web_fetch", "web_fetch", "获取指定网页的内容"},
{"media_understanding", "analyze_media", "分析图片、音频或视频内容"},
{"realtime_transcription", "transcribe_audio", "将音频转写为文字"},
{"realtime_voice", "voice_io", "实时语音输入输出"},
}
type ProviderRegistry struct{}
func (r *ProviderRegistry) Dispatch(typeStr string, data json.RawMessage, pluginName string, sp *sidecarProcess, s *sdk.PluginSDK) {
var d struct {
Name string `json:"name"`
Description string `json:"description"`
}
json.Unmarshal(data, &d)
for _, p := range providerMap {
if p.ocType == typeStr {
toolName := fmt.Sprintf("%s_%s", pluginName, p.toolSuffix)
desc := p.desc
if d.Name != "" {
desc = fmt.Sprintf("[%s] %s", d.Name, desc)
}
tDef := sdk.ToolDef{
Name: toolName,
Description: desc,
Parameters: map[string]interface{}{
"type": "object",
"properties": map[string]interface{}{},
},
}
handler := func(sp *sidecarProcess, ocType string) sdk.ToolHandler {
return func(args map[string]interface{}) (interface{}, error) {
return sp.CallProvider(ocType, args)
}
}(sp, typeStr)
if err := s.RegisterTool(toolName, tDef, handler); err != nil {
log.Printf("[clawhubadapter] register provider tool %s: %v", toolName, err)
}
return
}
}
log.Printf("[clawhubadapter] unknown provider type: %s (plugin: %s)", typeStr, pluginName)
}
type channelDevice struct {
name string
pluginName string
sp *sidecarProcess
ocType string
}
func (d *channelDevice) Name() string { return d.name }
func (d *channelDevice) Type() agentIO.DeviceType { return agentIO.DeviceIO }
func (d *channelDevice) Description() string { return fmt.Sprintf("OC channel %s (from %s)", d.name, d.pluginName) }
func (d *channelDevice) OutputCapabilities() agentIO.OutputCapability { return ocTypeToCap(d.ocType) }
func (d *channelDevice) Start() error { return nil }
func (d *channelDevice) Stop() error { return nil }
func (d *channelDevice) Tools() []agentIO.ToolDef { return nil }
func (d *channelDevice) Execute(tool string, args map[string]interface{}) (interface{}, error) {
return d.sp.CallTool(tool, args)
}
func ocTypeToCap(t string) agentIO.OutputCapability {
switch t {
case "text":
return agentIO.CapText
case "file":
return agentIO.CapFile
case "image":
return agentIO.CapImage
case "audio":
return agentIO.CapAudio
default:
return agentIO.CapText
}
}
type ChannelRegistry struct{}
func (r *ChannelRegistry) Dispatch(data json.RawMessage, pluginName string, sp *sidecarProcess, s *sdk.PluginSDK) {
var d struct {
Name string `json:"name"`
Type string `json:"type"`
}
if err := json.Unmarshal(data, &d); err != nil || d.Name == "" {
return
}
dev := &channelDevice{
name: d.Name,
pluginName: pluginName,
sp: sp,
ocType: d.Type,
}
if err := s.RegisterChannel(d.Name, dev); err != nil {
log.Printf("[clawhubadapter] register channel %s: %v", d.Name, err)
}
}
type StageRegistry struct{}
func (r *StageRegistry) Dispatch(data json.RawMessage, pluginName string, sp *sidecarProcess, s *sdk.PluginSDK) {
var d struct {
Name string `json:"name"`
Event string `json:"event"`
}
json.Unmarshal(data, &d)
log.Printf("[clawhubadapter] hook %s/%s (plugin: %s) — stub: OC hooks need bidirectional bridge",
d.Name, d.Event, pluginName)
}
type CapRecorder struct {
mu sync.Mutex
caps []string
}
func (r *CapRecorder) Record(typeStr string, data json.RawMessage, pluginName string) {
var d struct {
Name string `json:"name"`
Description string `json:"description"`
}
json.Unmarshal(data, &d)
capStr := fmt.Sprintf("[%s] capability: %s", pluginName, typeStr)
if d.Name != "" {
capStr += " (" + d.Name + ")"
}
if d.Description != "" {
capStr += ": " + d.Description
}
r.mu.Lock()
r.caps = append(r.caps, capStr)
r.mu.Unlock()
}
func (r *CapRecorder) Snapshot() []string {
r.mu.Lock()
defer r.mu.Unlock()
out := make([]string, len(r.caps))
copy(out, r.caps)
return out
}
type RegistryDispatcher struct {
toolReg *ToolRegistry
providerReg *ProviderRegistry
channelReg *ChannelRegistry
stageReg *StageRegistry
capRecorder *CapRecorder
}
func NewDispatcher() *RegistryDispatcher {
return &RegistryDispatcher{
toolReg: &ToolRegistry{},
providerReg: &ProviderRegistry{},
channelReg: &ChannelRegistry{},
stageReg: &StageRegistry{},
capRecorder: &CapRecorder{},
}
}
func (d *RegistryDispatcher) Dispatch(typeStr string, data json.RawMessage, pluginName string, sp *sidecarProcess, s *sdk.PluginSDK) {
switch typeStr {
case "tool":
d.toolReg.Dispatch(data, pluginName, sp, s)
case "channel":
d.channelReg.Dispatch(data, pluginName, sp, s)
case "provider", "image_generation_provider", "music_generation_provider",
"video_generation_provider", "speech_provider",
"realtime_transcription_provider", "realtime_voice_provider",
"media_understanding_provider",
"web_fetch_provider", "web_search_provider",
"embedding_provider", "memory_embedding_provider":
d.providerReg.Dispatch(typeStr, data, pluginName, sp, s)
d.capRecorder.Record(typeStr, data, pluginName)
case "hook", "runtime_lifecycle", "lifecycle",
"agent_event_subscription", "agent_harness",
"session_event", "conversation_binding_resolved",
"interactive_handler",
"cli", "cli_backend", "node_cli_feature",
"command", "http_route", "service",
"gateway_method", "gateway_discovery_service",
"trusted_tool_policy", "tool_metadata",
"context_engine",
"memory_capability", "memory_prompt_section",
"memory_flush_plan", "memory_runtime",
"memory_prompt_supplement", "memory_corpus_supplement",
"session_extension", "session_scheduler_job",
"session_action", "control_ui_descriptor",
"agent_tool_result_middleware":
d.stageReg.Dispatch(data, pluginName, sp, s)
d.capRecorder.Record(typeStr, data, pluginName)
default:
d.capRecorder.Record(typeStr, data, pluginName)
}
}
func (d *RegistryDispatcher) Capabilities() []string {
return d.capRecorder.Snapshot()
}