feat: Phase 3 插件与工具系统 — Plugin SDK + Plugin Manager + 13内置插件 (40文件, 3293行)

- Plugin SDK: Plugin/Tool/ComplexTool/HostAPI 标准化接口
- Plugin Manager: 插件生命周期管理 (Install/Enable/Disable/Uninstall/Reload)
- Tool Registry: 聚合工具注册表 (Register/Execute/Dispatch)
- 13 个内置插件: 将原有硬编码工具迁移为标准插件格式
- REST API: 11 个端点 (net/http, 零外部依赖)
- ai-core 集成: PluginManagerClient 替代本地工具调用
- plugin.json 元数据: 每个插件含完整 author/version/category/permissions

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
2026-05-23 15:50:19 +08:00
parent 87214b9441
commit 717ad65b05
42 changed files with 3797 additions and 0 deletions
@@ -0,0 +1,120 @@
package iotquery
import (
"context"
"fmt"
"github.com/yourname/cyrene-ai/plugin-manager/internal/sdk"
)
// IoTClient is the interface for IoT device access.
type IoTClient interface {
GetAllDevices(ctx context.Context) ([]sdk.IoTDeviceState, error)
GetDevice(ctx context.Context, deviceID string) (*sdk.IoTDeviceState, error)
}
type IoTQueryPlugin struct {
sdk.BasePlugin
iotClient IoTClient
}
func NewIoTQueryPlugin(client IoTClient) *IoTQueryPlugin {
return &IoTQueryPlugin{iotClient: client}
}
func (p *IoTQueryPlugin) Metadata() sdk.PluginMetadata {
return sdk.PluginMetadata{
Name: "iot_query", DisplayName: "IoT Device Query", Version: "1.0.0",
Description: "Query smart home device status (single device or all devices)",
Category: "iot", Author: sdk.PluginAuthor{Name: "Cyrene Team"},
}
}
func (p *IoTQueryPlugin) Tools() []sdk.Tool { return []sdk.Tool{&IoTQueryTool{iotClient: p.iotClient}} }
type IoTQueryTool struct {
sdk.BaseTool
iotClient IoTClient
}
func (t *IoTQueryTool) Definition() sdk.ToolDefinition {
return sdk.ToolDefinition{
ID: "iot_query", Name: "iot_query", DisplayName: "IoT Device Query",
Description: "Query smart home device status. Device status is typically auto-injected; use this only when status is stale.",
Category: "iot", Complexity: sdk.ComplexitySimple,
Parameters: map[string]interface{}{
"type": "object",
"properties": map[string]interface{}{"device_id": map[string]interface{}{"type": "string"}},
},
}
}
func (t *IoTQueryTool) Validate(args map[string]interface{}) error { return nil }
func (t *IoTQueryTool) Execute(ctx context.Context, args map[string]interface{}) (*sdk.ToolResult, error) {
if t.iotClient == nil {
return &sdk.ToolResult{ToolName: "iot_query", Success: false, Error: "IoT client not configured"}, nil
}
deviceID, _ := args["device_id"].(string)
if deviceID != "" {
dev, err := t.iotClient.GetDevice(ctx, deviceID)
if err != nil {
return &sdk.ToolResult{ToolName: "iot_query", Success: false, Error: err.Error()}, nil
}
return &sdk.ToolResult{ToolName: "iot_query", Success: true, Output: formatDevice(dev)}, nil
}
devices, err := t.iotClient.GetAllDevices(ctx)
if err != nil {
return &sdk.ToolResult{ToolName: "iot_query", Success: false, Error: err.Error()}, nil
}
if len(devices) == 0 {
return &sdk.ToolResult{ToolName: "iot_query", Success: true, Output: "No devices found"}, nil
}
var out string
for _, d := range devices {
out += formatDeviceLine(&d) + "\n"
}
return &sdk.ToolResult{ToolName: "iot_query", Success: true, Output: out}, nil
}
func formatDevice(d *sdk.IoTDeviceState) string {
emoji := deviceEmoji(d.Type)
return fmt.Sprintf("%s %s (%s)\n Status: %s\n ID: %s", emoji, d.Name, d.Type, d.Status, d.ID)
}
func formatDeviceLine(d *sdk.IoTDeviceState) string {
emoji := deviceEmoji(d.Type)
switch d.Type {
case "light":
return fmt.Sprintf("%s %s: %s (brightness: %d, color: %s)", emoji, d.Name, d.Status, d.Brightness, d.Color)
case "ac":
return fmt.Sprintf("%s %s: %s (mode: %s, temp: %.1fC)", emoji, d.Name, d.Status, d.Mode, d.Temperature)
case "curtain":
return fmt.Sprintf("%s %s: %s (position: %d%%)", emoji, d.Name, d.Status, d.Position)
case "sensor":
return fmt.Sprintf("%s %s: %.1f%s", emoji, d.Name, d.Value, d.Unit)
case "lock":
return fmt.Sprintf("%s %s: %s (battery: %d%%)", emoji, d.Name, d.Status, d.Battery)
default:
return fmt.Sprintf("%s %s: %s", emoji, d.Name, d.Status)
}
}
func deviceEmoji(t string) string {
switch t {
case "light":
return "\U0001F4A1"
case "ac":
return "❄️"
case "curtain":
return "\U0001F3E0"
case "sensor":
return "\U0001F4CA"
case "lock":
return "\U0001F512"
default:
return "\U0001F4E6"
}
}
@@ -0,0 +1,12 @@
{
"name": "iot_query",
"displayName": "IoT Device Query",
"version": "1.0.0",
"minCyreneVersion": "1.0.0",
"author": { "name": "Cyrene Team" },
"description": "Query smart home device status (single device or all devices)",
"license": "MIT",
"keywords": ["iot", "query", "device", "status"],
"category": "iot",
"permissions": ["iot:read"]
}