71f0a1abdb
- 所有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>
211 lines
6.0 KiB
Go
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)
|
|
}
|