Files
Cyrene/backend/plugin-manager/internal/handler/plugin_handler.go
T
AskaEth 71f0a1abdb feat: Go模块路径迁移 + Docker生产部署适配 + ethend Docker兼容
- 所有Go模块路径从 github.com/yourname/cyrene-ai 迁移到 git.yeij.top/AskaEth/Cyrene
- 5个Go Dockerfile添加 GOPROXY=https://goproxy.cn,direct 解决国内构建问题
- ai-core go.mod 添加 pkg/plugins replace 指令
- Caddyfile 简化为 http:// 通配 + handle 保留 /api 前缀
- ethend Dockerfile 适配 (npm install + 仅 COPY package.json)
- ethend 新增 RUNNING_IN_DOCKER 环境变量,健康检查改用Docker服务名
- ethend 数据库状态检查支持Docker hostname (postgres/redis/qdrant/minio)
- process-manager 新增 CONTAINER_SVC_MAP + Docker模式自动检测
- 统一 docker-compose.dev.db.yml 卷名 (pg_data/redis_data/qdrant_data/minio_data)
- docker-compose.yml ethend服务挂载docker.sock + 端口变量化
- 清理 .env 统一后的残留文件与提示信息

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-30 13:43:22 +08:00

211 lines
6.0 KiB
Go

package handler
import (
"encoding/json"
"net/http"
"strings"
"git.yeij.top/AskaEth/Cyrene/pkg/plugins/manager"
)
// PluginHandler exposes the Plugin Manager REST API via net/http.
type PluginHandler struct {
mgr *manager.PluginManager
}
func NewPluginHandler(mgr *manager.PluginManager) *PluginHandler {
return &PluginHandler{mgr: mgr}
}
func (h *PluginHandler) RegisterRoutes(mux *http.ServeMux) {
mux.HandleFunc("/api/v1/plugins", h.listPlugins)
mux.HandleFunc("/api/v1/plugins/", h.pluginRoute)
mux.HandleFunc("/api/v1/tools", h.listTools)
mux.HandleFunc("/api/v1/tools/", h.toolRoute)
mux.HandleFunc("/api/v1/health", h.health)
}
func (h *PluginHandler) health(w http.ResponseWriter, r *http.Request) {
writeJSON(w, http.StatusOK, map[string]interface{}{"status": "ok", "service": "plugin-manager"})
}
func (h *PluginHandler) listPlugins(w http.ResponseWriter, r *http.Request) {
if r.Method != "GET" {
writeJSON(w, http.StatusMethodNotAllowed, errResp("method not allowed"))
return
}
plugins := h.mgr.List()
writeJSON(w, http.StatusOK, map[string]interface{}{"plugins": plugins, "total": len(plugins)})
}
func (h *PluginHandler) pluginRoute(w http.ResponseWriter, r *http.Request) {
path := strings.TrimPrefix(r.URL.Path, "/api/v1/plugins/")
parts := strings.SplitN(path, "/", 2)
pluginID := parts[0]
if pluginID == "" {
h.listPlugins(w, r)
return
}
if len(parts) == 1 {
switch r.Method {
case "GET":
h.getPlugin(w, pluginID)
case "DELETE":
h.uninstallPlugin(w, r, pluginID)
default:
writeJSON(w, http.StatusMethodNotAllowed, errResp("method not allowed"))
}
return
}
action := parts[1]
switch action {
case "enable":
if r.Method != "POST" {
writeJSON(w, http.StatusMethodNotAllowed, errResp("method not allowed"))
return
}
h.enablePlugin(w, r, pluginID)
case "disable":
if r.Method != "POST" {
writeJSON(w, http.StatusMethodNotAllowed, errResp("method not allowed"))
return
}
h.disablePlugin(w, r, pluginID)
case "reload":
if r.Method != "POST" {
writeJSON(w, http.StatusMethodNotAllowed, errResp("method not allowed"))
return
}
h.reloadPlugin(w, r, pluginID)
case "tools":
if r.Method != "GET" {
writeJSON(w, http.StatusMethodNotAllowed, errResp("method not allowed"))
return
}
h.pluginTools(w, pluginID)
default:
writeJSON(w, http.StatusNotFound, errResp("not found"))
}
}
func (h *PluginHandler) getPlugin(w http.ResponseWriter, id string) {
info, ok := h.mgr.Get(id)
if !ok {
writeJSON(w, http.StatusNotFound, errResp("plugin not found"))
return
}
writeJSON(w, http.StatusOK, info)
}
func (h *PluginHandler) enablePlugin(w http.ResponseWriter, r *http.Request, id string) {
if err := h.mgr.Enable(r.Context(), id); err != nil {
writeJSON(w, http.StatusInternalServerError, errResp(err.Error()))
return
}
writeJSON(w, http.StatusOK, map[string]string{"status": "enabled"})
}
func (h *PluginHandler) disablePlugin(w http.ResponseWriter, r *http.Request, id string) {
if err := h.mgr.Disable(r.Context(), id); err != nil {
writeJSON(w, http.StatusInternalServerError, errResp(err.Error()))
return
}
writeJSON(w, http.StatusOK, map[string]string{"status": "disabled"})
}
func (h *PluginHandler) reloadPlugin(w http.ResponseWriter, r *http.Request, id string) {
if err := h.mgr.Reload(r.Context(), id); err != nil {
writeJSON(w, http.StatusInternalServerError, errResp(err.Error()))
return
}
writeJSON(w, http.StatusOK, map[string]string{"status": "reloaded"})
}
func (h *PluginHandler) uninstallPlugin(w http.ResponseWriter, r *http.Request, id string) {
if err := h.mgr.Uninstall(r.Context(), id); err != nil {
writeJSON(w, http.StatusInternalServerError, errResp(err.Error()))
return
}
writeJSON(w, http.StatusOK, map[string]string{"status": "uninstalled"})
}
func (h *PluginHandler) pluginTools(w http.ResponseWriter, id string) {
info, ok := h.mgr.Get(id)
if !ok {
writeJSON(w, http.StatusNotFound, errResp("plugin not found"))
return
}
registry := h.mgr.Registry()
tools := make([]interface{}, 0)
for _, toolID := range info.Tools {
if t, ok := registry.Get(toolID); ok {
tools = append(tools, t.Definition())
}
}
writeJSON(w, http.StatusOK, map[string]interface{}{"tools": tools, "total": len(tools)})
}
func (h *PluginHandler) listTools(w http.ResponseWriter, r *http.Request) {
if r.Method != "GET" {
writeJSON(w, http.StatusMethodNotAllowed, errResp("method not allowed"))
return
}
defs := h.mgr.Registry().Definitions()
writeJSON(w, http.StatusOK, map[string]interface{}{"tools": defs, "total": len(defs)})
}
func (h *PluginHandler) toolRoute(w http.ResponseWriter, r *http.Request) {
path := strings.TrimPrefix(r.URL.Path, "/api/v1/tools/")
toolID := path
if strings.HasSuffix(path, "/execute") {
toolID = strings.TrimSuffix(path, "/execute")
if r.Method != "POST" {
writeJSON(w, http.StatusMethodNotAllowed, errResp("method not allowed"))
return
}
h.executeTool(w, r, toolID)
return
}
if r.Method != "GET" {
writeJSON(w, http.StatusMethodNotAllowed, errResp("method not allowed"))
return
}
tool, ok := h.mgr.Registry().Get(toolID)
if !ok {
writeJSON(w, http.StatusNotFound, errResp("tool not found"))
return
}
writeJSON(w, http.StatusOK, tool.Definition())
}
func (h *PluginHandler) executeTool(w http.ResponseWriter, r *http.Request, toolID string) {
var body struct {
Arguments map[string]interface{} `json:"arguments"`
}
if err := json.NewDecoder(r.Body).Decode(&body); err != nil {
writeJSON(w, http.StatusBadRequest, errResp("invalid request body"))
return
}
result, err := h.mgr.Registry().Execute(r.Context(), toolID, body.Arguments)
if err != nil {
writeJSON(w, http.StatusInternalServerError, errResp(err.Error()))
return
}
writeJSON(w, http.StatusOK, result)
}
func errResp(msg string) map[string]string {
return map[string]string{"error": msg}
}
func writeJSON(w http.ResponseWriter, status int, data interface{}) {
w.Header().Set("Content-Type", "application/json")
w.WriteHeader(status)
json.NewEncoder(w).Encode(data)
}