mirror of
https://gitcode.com/JianFeeeee/HomeAgent.git
synced 2026-09-21 17:38:10 +00:00
- IO abstraction layer with OutputChannel routing and capability validation - Three-layer memory (Context-Document-Graph) with TF-IDF relevance pruning - OneBot V11 QQ protocol plugin with Reverse WebSocket client - Plugin system with hot-reload (SKILL.md + native factories) - Knowledge system with TF-IDF vector indexing - Personality system (personal.md) - Text memory (JSONL with rotation) - Change tracker (overlayfs) with rollback - Lua adapter VM - Design document (DESIGN.md) Module: gitcode.com/JianFeeeee/HomeAgent
547 lines
15 KiB
Go
547 lines
15 KiB
Go
package io
|
||
|
||
import (
|
||
"fmt"
|
||
"sync"
|
||
"time"
|
||
)
|
||
|
||
type DeviceType int
|
||
|
||
const (
|
||
DeviceInput DeviceType = 0
|
||
DeviceOutput DeviceType = 1
|
||
DeviceIO DeviceType = 2
|
||
)
|
||
|
||
// OutputCapability 定义通道支持的输出格式
|
||
type OutputCapability int
|
||
|
||
const (
|
||
CapText OutputCapability = 1 << iota // 文本
|
||
CapFile // 文件
|
||
CapImage // 图片
|
||
CapAudio // 音频
|
||
CapStructured // 结构化数据(JSON/卡片)
|
||
)
|
||
|
||
func (c OutputCapability) Supports(cap OutputCapability) bool {
|
||
return c&cap != 0
|
||
}
|
||
|
||
func (c OutputCapability) String() string {
|
||
var flags []string
|
||
if c&CapText != 0 {
|
||
flags = append(flags, "text")
|
||
}
|
||
if c&CapFile != 0 {
|
||
flags = append(flags, "file")
|
||
}
|
||
if c&CapImage != 0 {
|
||
flags = append(flags, "image")
|
||
}
|
||
if c&CapAudio != 0 {
|
||
flags = append(flags, "audio")
|
||
}
|
||
if c&CapStructured != 0 {
|
||
flags = append(flags, "structured")
|
||
}
|
||
return fmt.Sprintf("%v", flags)
|
||
}
|
||
|
||
type Device interface {
|
||
Name() string
|
||
Type() DeviceType
|
||
Description() string
|
||
Tools() []ToolDef
|
||
Execute(tool string, args map[string]interface{}) (interface{}, error)
|
||
Start() error
|
||
Stop() error
|
||
OutputCapabilities() OutputCapability
|
||
}
|
||
|
||
type ToolHandler func(args map[string]interface{}) (interface{}, error)
|
||
|
||
type ToolDef struct {
|
||
Name string `json:"name"`
|
||
Description string `json:"description"`
|
||
Parameters map[string]interface{} `json:"parameters"`
|
||
Handler ToolHandler `json:"-"` // 可选:插件工具的直接处理器,Device 通过 Execute() 分发
|
||
}
|
||
|
||
type InputEvent struct {
|
||
RequestID string `json:"request_id"`
|
||
Source string `json:"source"`
|
||
Type string `json:"type"`
|
||
Payload map[string]interface{} `json:"payload"`
|
||
ResponseCh chan<- *OutputEvent `json:"-"`
|
||
OutputChannel string `json:"output_channel"` // 默认输出通道(不传则等于 Source)
|
||
}
|
||
|
||
type OutputEvent struct {
|
||
RequestID string `json:"request_id"`
|
||
Target string `json:"target"`
|
||
Type string `json:"type"`
|
||
Payload map[string]interface{} `json:"payload"`
|
||
Done bool `json:"done,omitempty"`
|
||
OutputChannel string `json:"output_channel"` // 路由到此通道
|
||
}
|
||
|
||
type IOManager struct {
|
||
mu sync.RWMutex
|
||
devices map[string]Device
|
||
inputCh chan *InputEvent
|
||
outputCh chan *OutputEvent
|
||
nextReqID int64
|
||
routes map[string]string // 输入源 → 默认输出通道 e.g. "mic" → "speaker"
|
||
}
|
||
|
||
func NewIOManager() *IOManager {
|
||
return &IOManager{
|
||
devices: make(map[string]Device),
|
||
inputCh: make(chan *InputEvent, 256),
|
||
outputCh: make(chan *OutputEvent, 256),
|
||
routes: make(map[string]string),
|
||
}
|
||
}
|
||
|
||
// RegisterOutputRoute 注册输入源 → 默认输出通道映射
|
||
// 例如:mic → speaker,voice_input → speaker
|
||
func (m *IOManager) RegisterOutputRoute(inputSource, outputChannel string) {
|
||
m.mu.Lock()
|
||
defer m.mu.Unlock()
|
||
m.routes[inputSource] = outputChannel
|
||
}
|
||
|
||
// DefaultOutput 返回输入源的默认输出通道
|
||
func (m *IOManager) DefaultOutput(source string) string {
|
||
m.mu.RLock()
|
||
defer m.mu.RUnlock()
|
||
if ch, ok := m.routes[source]; ok {
|
||
return ch
|
||
}
|
||
return source // 默认等于输入源
|
||
}
|
||
|
||
func (m *IOManager) nextRequestID() string {
|
||
m.mu.Lock()
|
||
defer m.mu.Unlock()
|
||
m.nextReqID++
|
||
return fmt.Sprintf("req_%d_%d", time.Now().UnixNano(), m.nextReqID)
|
||
}
|
||
|
||
func (m *IOManager) UnregisterDevice(name string) {
|
||
m.mu.Lock()
|
||
defer m.mu.Unlock()
|
||
delete(m.devices, name)
|
||
for src, dst := range m.routes {
|
||
if src == name || dst == name {
|
||
delete(m.routes, src)
|
||
}
|
||
}
|
||
}
|
||
|
||
// AtomicSwapDevices 原子化替换全部 IO 设备与路由表
|
||
// 1. 新设备必须在调用前已完成 Start()
|
||
// 2. 调用后旧设备立即从路由表中摘除,新请求走向新设备
|
||
// 3. 返回旧设备列表,由调用方负责 Stop()
|
||
func (m *IOManager) AtomicSwapDevices(newDevices map[string]Device, newRoutes map[string]string) map[string]Device {
|
||
m.mu.Lock()
|
||
defer m.mu.Unlock()
|
||
|
||
oldDevices := m.devices
|
||
m.devices = newDevices
|
||
|
||
m.routes = newRoutes
|
||
|
||
return oldDevices
|
||
}
|
||
|
||
func (m *IOManager) RegisterDevice(dev Device) error {
|
||
m.mu.Lock()
|
||
defer m.mu.Unlock()
|
||
if _, ok := m.devices[dev.Name()]; ok {
|
||
return fmt.Errorf("device %s already registered", dev.Name())
|
||
}
|
||
m.devices[dev.Name()] = dev
|
||
return nil
|
||
}
|
||
|
||
func (m *IOManager) StartAll() error {
|
||
m.mu.RLock()
|
||
defer m.mu.RUnlock()
|
||
for name, dev := range m.devices {
|
||
if err := dev.Start(); err != nil {
|
||
return fmt.Errorf("start device %s: %w", name, err)
|
||
}
|
||
}
|
||
return nil
|
||
}
|
||
|
||
func (m *IOManager) StopAll() {
|
||
m.mu.RLock()
|
||
defer m.mu.RUnlock()
|
||
for _, dev := range m.devices {
|
||
dev.Stop()
|
||
}
|
||
}
|
||
|
||
func (m *IOManager) InjectInput(source string, eventType string, payload map[string]interface{}) {
|
||
m.inputCh <- &InputEvent{
|
||
RequestID: m.nextRequestID(),
|
||
Source: source,
|
||
Type: eventType,
|
||
Payload: payload,
|
||
OutputChannel: m.DefaultOutput(source),
|
||
}
|
||
}
|
||
|
||
func (m *IOManager) InjectInputSync(source string, eventType string, payload map[string]interface{}) *OutputEvent {
|
||
ch := make(chan *OutputEvent, 1)
|
||
m.inputCh <- &InputEvent{
|
||
RequestID: m.nextRequestID(),
|
||
Source: source,
|
||
Type: eventType,
|
||
Payload: payload,
|
||
ResponseCh: ch,
|
||
OutputChannel: m.DefaultOutput(source),
|
||
}
|
||
return <-ch
|
||
}
|
||
|
||
func (m *IOManager) InjectText(source string, text string) {
|
||
m.InjectInput(source, "text", map[string]interface{}{
|
||
"content": text,
|
||
})
|
||
}
|
||
|
||
func (m *IOManager) InjectTextSync(source string, text string) *OutputEvent {
|
||
return m.InjectInputSync(source, "text", map[string]interface{}{
|
||
"content": text,
|
||
})
|
||
}
|
||
|
||
func (m *IOManager) EmitOutput(target string, outputType string, payload map[string]interface{}) {
|
||
m.outputCh <- &OutputEvent{
|
||
RequestID: "",
|
||
Target: target,
|
||
Type: outputType,
|
||
Payload: payload,
|
||
Done: true,
|
||
}
|
||
}
|
||
|
||
// EmitOutputTo 通过指定输出通道发送
|
||
func (m *IOManager) EmitOutputTo(target, outputChannel, outputType string, payload map[string]interface{}) {
|
||
m.outputCh <- &OutputEvent{
|
||
RequestID: "",
|
||
Target: target,
|
||
Type: outputType,
|
||
Payload: payload,
|
||
Done: true,
|
||
OutputChannel: outputChannel,
|
||
}
|
||
}
|
||
|
||
func (m *IOManager) EmitText(target string, text string) {
|
||
m.EmitOutput(target, "text", map[string]interface{}{
|
||
"content": text,
|
||
})
|
||
}
|
||
|
||
// EmitTextTo 通过指定输出通道发送文本
|
||
func (m *IOManager) EmitTextTo(target, outputChannel, text string) {
|
||
m.EmitOutputTo(target, outputChannel, "text", map[string]interface{}{
|
||
"content": text,
|
||
})
|
||
}
|
||
|
||
func (m *IOManager) InputChan() <-chan *InputEvent { return m.inputCh }
|
||
func (m *IOManager) OutputChan() <-chan *OutputEvent { return m.outputCh }
|
||
|
||
func (m *IOManager) GetAllTools() []ToolDef {
|
||
m.mu.RLock()
|
||
defer m.mu.RUnlock()
|
||
var tools []ToolDef
|
||
for _, dev := range m.devices {
|
||
tools = append(tools, dev.Tools()...)
|
||
}
|
||
return tools
|
||
}
|
||
|
||
func (m *IOManager) ExecuteTool(name string, args map[string]interface{}) (interface{}, error) {
|
||
m.mu.RLock()
|
||
defer m.mu.RUnlock()
|
||
for _, dev := range m.devices {
|
||
for _, t := range dev.Tools() {
|
||
if t.Name == name {
|
||
return dev.Execute(name, args)
|
||
}
|
||
}
|
||
}
|
||
return nil, fmt.Errorf("tool %s not found", name)
|
||
}
|
||
|
||
func (m *IOManager) ListDevices() []Device {
|
||
m.mu.RLock()
|
||
defer m.mu.RUnlock()
|
||
list := make([]Device, 0, len(m.devices))
|
||
for _, d := range m.devices {
|
||
list = append(list, d)
|
||
}
|
||
return list
|
||
}
|
||
|
||
// ChannelInfo 返回 IOManager 中已注册的所有通道信息
|
||
type ChannelInfo struct {
|
||
Name string `json:"name"`
|
||
Type DeviceType `json:"type"`
|
||
Description string `json:"description"`
|
||
Tools []ToolDef `json:"tools"`
|
||
OutputCaps OutputCapability `json:"output_capabilities"`
|
||
}
|
||
|
||
func (m *IOManager) ListChannels() []ChannelInfo {
|
||
m.mu.RLock()
|
||
defer m.mu.RUnlock()
|
||
|
||
var list []ChannelInfo
|
||
for _, dev := range m.devices {
|
||
list = append(list, ChannelInfo{
|
||
Name: dev.Name(),
|
||
Type: dev.Type(),
|
||
Description: dev.Description(),
|
||
Tools: dev.Tools(),
|
||
OutputCaps: dev.OutputCapabilities(),
|
||
})
|
||
}
|
||
return list
|
||
}
|
||
|
||
func (m *IOManager) GetChannelCapabilities(channel string) OutputCapability {
|
||
m.mu.RLock()
|
||
defer m.mu.RUnlock()
|
||
if dev, ok := m.devices[channel]; ok {
|
||
return dev.OutputCapabilities()
|
||
}
|
||
return 0
|
||
}
|
||
|
||
// Microphone
|
||
type Microphone struct {
|
||
name string
|
||
sampleRate int
|
||
io *IOManager
|
||
}
|
||
|
||
func NewMicrophone(name string, sampleRate int, io *IOManager) *Microphone {
|
||
return &Microphone{name: name, sampleRate: sampleRate, io: io}
|
||
}
|
||
|
||
func (d *Microphone) Name() string { return d.name }
|
||
func (d *Microphone) Type() DeviceType { return DeviceInput }
|
||
func (d *Microphone) OutputCapabilities() OutputCapability { return 0 } // 纯输入
|
||
func (d *Microphone) Description() string { return fmt.Sprintf("麦克风 (%s, %dHz)", d.name, d.sampleRate) }
|
||
func (d *Microphone) Start() error { return nil }
|
||
func (d *Microphone) Stop() error { return nil }
|
||
|
||
func (d *Microphone) Tools() []ToolDef {
|
||
return []ToolDef{{
|
||
Name: d.name + "_capture",
|
||
Description: fmt.Sprintf("从 %s 录制音频", d.name),
|
||
Parameters: map[string]interface{}{
|
||
"type": "object",
|
||
"properties": map[string]interface{}{
|
||
"duration": map[string]interface{}{"type": "number", "description": "录制时长(秒)", "default": 3},
|
||
},
|
||
},
|
||
}}
|
||
}
|
||
|
||
func (d *Microphone) Execute(tool string, args map[string]interface{}) (interface{}, error) {
|
||
return map[string]interface{}{"device": d.name, "status": "recorded", "format": "wav", "sample_rate": d.sampleRate}, nil
|
||
}
|
||
|
||
// Speaker
|
||
type Speaker struct {
|
||
name string
|
||
io *IOManager
|
||
}
|
||
|
||
func NewSpeaker(name string, io *IOManager) *Speaker {
|
||
return &Speaker{name: name, io: io}
|
||
}
|
||
|
||
func (d *Speaker) Name() string { return d.name }
|
||
func (d *Speaker) Type() DeviceType { return DeviceOutput }
|
||
func (d *Speaker) OutputCapabilities() OutputCapability { return CapText | CapAudio }
|
||
func (d *Speaker) Description() string { return fmt.Sprintf("扬声器 (%s)", d.name) }
|
||
func (d *Speaker) Start() error { return nil }
|
||
func (d *Speaker) Stop() error { return nil }
|
||
|
||
func (d *Speaker) Tools() []ToolDef {
|
||
return []ToolDef{{
|
||
Name: d.name + "_speak",
|
||
Description: fmt.Sprintf("通过 %s 播放语音", d.name),
|
||
Parameters: map[string]interface{}{
|
||
"type": "object",
|
||
"properties": map[string]interface{}{
|
||
"text": map[string]interface{}{"type": "string", "description": "播放文本"},
|
||
},
|
||
"required": []string{"text"},
|
||
},
|
||
}}
|
||
}
|
||
|
||
func (d *Speaker) Execute(tool string, args map[string]interface{}) (interface{}, error) {
|
||
text, _ := args["text"].(string)
|
||
return map[string]interface{}{"device": d.name, "status": "playing", "text": text}, nil
|
||
}
|
||
|
||
// Camera
|
||
type Camera struct {
|
||
name string
|
||
io *IOManager
|
||
}
|
||
|
||
func NewCamera(name string, io *IOManager) *Camera {
|
||
return &Camera{name: name, io: io}
|
||
}
|
||
|
||
func (d *Camera) Name() string { return d.name }
|
||
func (d *Camera) Type() DeviceType { return DeviceInput }
|
||
func (d *Camera) OutputCapabilities() OutputCapability { return CapImage } // 可返回图片
|
||
func (d *Camera) Description() string { return fmt.Sprintf("摄像头 (%s)", d.name) }
|
||
func (d *Camera) Start() error { return nil }
|
||
func (d *Camera) Stop() error { return nil }
|
||
|
||
func (d *Camera) Tools() []ToolDef {
|
||
return []ToolDef{
|
||
{
|
||
Name: d.name + "_capture",
|
||
Description: fmt.Sprintf("使用 %s 拍照", d.name),
|
||
Parameters: map[string]interface{}{
|
||
"type": "object",
|
||
"properties": map[string]interface{}{
|
||
"quality": map[string]interface{}{"type": "integer", "description": "质量1-100", "default": 90},
|
||
},
|
||
},
|
||
},
|
||
{
|
||
Name: d.name + "_stream",
|
||
Description: fmt.Sprintf("控制 %s 视频流", d.name),
|
||
Parameters: map[string]interface{}{
|
||
"type": "object",
|
||
"properties": map[string]interface{}{
|
||
"action": map[string]interface{}{"type": "string", "enum": []interface{}{"start", "stop"}},
|
||
},
|
||
"required": []string{"action"},
|
||
},
|
||
},
|
||
}
|
||
}
|
||
|
||
func (d *Camera) Execute(tool string, args map[string]interface{}) (interface{}, error) {
|
||
return map[string]interface{}{"device": d.name, "status": "captured"}, nil
|
||
}
|
||
|
||
// RobotArm
|
||
type RobotArm struct {
|
||
name string
|
||
io *IOManager
|
||
}
|
||
|
||
func NewRobotArm(name string, io *IOManager) *RobotArm {
|
||
return &RobotArm{name: name, io: io}
|
||
}
|
||
|
||
func (d *RobotArm) Name() string { return d.name }
|
||
func (d *RobotArm) Type() DeviceType { return DeviceIO }
|
||
func (d *RobotArm) OutputCapabilities() OutputCapability { return CapStructured }
|
||
func (d *RobotArm) Description() string { return fmt.Sprintf("机械臂 (%s)", d.name) }
|
||
func (d *RobotArm) Start() error { return nil }
|
||
func (d *RobotArm) Stop() error { return nil }
|
||
|
||
func (d *RobotArm) Tools() []ToolDef {
|
||
return []ToolDef{
|
||
{
|
||
Name: d.name + "_move",
|
||
Description: fmt.Sprintf("移动 %s 到坐标", d.name),
|
||
Parameters: map[string]interface{}{
|
||
"type": "object",
|
||
"properties": map[string]interface{}{
|
||
"x": map[string]interface{}{"type": "number", "description": "X 轴"},
|
||
"y": map[string]interface{}{"type": "number", "description": "Y 轴"},
|
||
"z": map[string]interface{}{"type": "number", "description": "Z 轴"},
|
||
},
|
||
"required": []string{"x", "y", "z"},
|
||
},
|
||
},
|
||
{
|
||
Name: d.name + "_grip",
|
||
Description: fmt.Sprintf("控制 %s 夹爪", d.name),
|
||
Parameters: map[string]interface{}{
|
||
"type": "object",
|
||
"properties": map[string]interface{}{
|
||
"action": map[string]interface{}{"type": "string", "enum": []interface{}{"open", "close"}},
|
||
},
|
||
"required": []string{"action"},
|
||
},
|
||
},
|
||
}
|
||
}
|
||
|
||
func (d *RobotArm) Execute(tool string, args map[string]interface{}) (interface{}, error) {
|
||
return map[string]interface{}{"device": d.name, "tool": tool, "status": "executed"}, nil
|
||
}
|
||
|
||
// GPIODevice
|
||
type GPIODevice struct {
|
||
name string
|
||
pins []int
|
||
io *IOManager
|
||
}
|
||
|
||
func NewGPIODevice(name string, pins []int, io *IOManager) *GPIODevice {
|
||
return &GPIODevice{name: name, pins: pins, io: io}
|
||
}
|
||
|
||
func (d *GPIODevice) Name() string { return d.name }
|
||
func (d *GPIODevice) Type() DeviceType { return DeviceIO }
|
||
func (d *GPIODevice) OutputCapabilities() OutputCapability { return CapStructured }
|
||
func (d *GPIODevice) Description() string { return "GPIO 通用引脚" }
|
||
func (d *GPIODevice) Start() error { return nil }
|
||
func (d *GPIODevice) Stop() error { return nil }
|
||
|
||
func (d *GPIODevice) Tools() []ToolDef {
|
||
return []ToolDef{
|
||
{
|
||
Name: d.name + "_gpio_write",
|
||
Description: "设置引脚电平",
|
||
Parameters: map[string]interface{}{
|
||
"type": "object",
|
||
"properties": map[string]interface{}{
|
||
"pin": map[string]interface{}{"type": "integer"},
|
||
"value": map[string]interface{}{"type": "integer", "enum": []interface{}{0, 1}},
|
||
},
|
||
"required": []string{"pin", "value"},
|
||
},
|
||
},
|
||
{
|
||
Name: d.name + "_gpio_read",
|
||
Description: "读取引脚电平",
|
||
Parameters: map[string]interface{}{
|
||
"type": "object",
|
||
"properties": map[string]interface{}{
|
||
"pin": map[string]interface{}{"type": "integer"},
|
||
},
|
||
"required": []string{"pin"},
|
||
},
|
||
},
|
||
}
|
||
}
|
||
|
||
func (d *GPIODevice) Execute(tool string, args map[string]interface{}) (interface{}, error) {
|
||
return map[string]interface{}{"device": d.name, "tool": tool, "status": "ok"}, nil
|
||
}
|