Go API server + Preact UI + Claude Code adapter. - App-centric model (ideas, not repos) - AgentProvider interface for multi-agent support - K8s pod lifecycle for sandboxed agent sessions - Gitea integration (create repos, push branches) - WebSocket streaming for live session output - Woodpecker CI/CD pipelines (kaniko build + kubectl deploy)
106 lines
2.8 KiB
Go
106 lines
2.8 KiB
Go
package api
|
|
|
|
import (
|
|
"encoding/json"
|
|
"net/http"
|
|
|
|
"github.com/agentsphere/agent-mgr/internal/store"
|
|
"github.com/go-chi/chi/v5"
|
|
"github.com/google/uuid"
|
|
)
|
|
|
|
type createAppRequest struct {
|
|
Name string `json:"name"`
|
|
Description string `json:"description"`
|
|
Provider string `json:"provider,omitempty"`
|
|
Config json.RawMessage `json:"config,omitempty"`
|
|
}
|
|
|
|
func (s *Server) createApp(w http.ResponseWriter, r *http.Request) {
|
|
var req createAppRequest
|
|
if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
|
|
http.Error(w, `{"error":"invalid request body"}`, http.StatusBadRequest)
|
|
return
|
|
}
|
|
if req.Name == "" && req.Description == "" {
|
|
http.Error(w, `{"error":"name or description required"}`, http.StatusBadRequest)
|
|
return
|
|
}
|
|
if req.Name == "" {
|
|
// Auto-generate name from first few words of description
|
|
name := req.Description
|
|
if len(name) > 40 {
|
|
name = name[:40]
|
|
}
|
|
req.Name = name
|
|
}
|
|
|
|
app, sess, err := s.app.CreateApp(r.Context(), req.Name, req.Description, req.Provider, req.Config)
|
|
if err != nil {
|
|
s.log.Error("create app failed", "err", err)
|
|
http.Error(w, `{"error":"failed to create app"}`, http.StatusInternalServerError)
|
|
return
|
|
}
|
|
|
|
resp := map[string]any{"app": app}
|
|
if sess != nil {
|
|
resp["session"] = sess
|
|
}
|
|
|
|
w.WriteHeader(http.StatusCreated)
|
|
json.NewEncoder(w).Encode(resp)
|
|
}
|
|
|
|
func (s *Server) listApps(w http.ResponseWriter, r *http.Request) {
|
|
apps, err := s.store.ListApps(r.Context())
|
|
if err != nil {
|
|
s.log.Error("list apps failed", "err", err)
|
|
http.Error(w, `{"error":"failed to list apps"}`, http.StatusInternalServerError)
|
|
return
|
|
}
|
|
if apps == nil {
|
|
apps = []store.App{}
|
|
}
|
|
json.NewEncoder(w).Encode(map[string]any{"apps": apps})
|
|
}
|
|
|
|
func (s *Server) getApp(w http.ResponseWriter, r *http.Request) {
|
|
id, err := uuid.Parse(chi.URLParam(r, "appID"))
|
|
if err != nil {
|
|
http.Error(w, `{"error":"invalid app id"}`, http.StatusBadRequest)
|
|
return
|
|
}
|
|
|
|
app, err := s.store.GetApp(r.Context(), id)
|
|
if err != nil {
|
|
http.Error(w, `{"error":"failed to get app"}`, http.StatusInternalServerError)
|
|
return
|
|
}
|
|
if app == nil {
|
|
http.Error(w, `{"error":"app not found"}`, http.StatusNotFound)
|
|
return
|
|
}
|
|
|
|
sessions, _ := s.store.ListSessionsByApp(r.Context(), id)
|
|
if sessions == nil {
|
|
sessions = []store.Session{}
|
|
}
|
|
|
|
json.NewEncoder(w).Encode(map[string]any{"app": app, "sessions": sessions})
|
|
}
|
|
|
|
func (s *Server) deleteApp(w http.ResponseWriter, r *http.Request) {
|
|
id, err := uuid.Parse(chi.URLParam(r, "appID"))
|
|
if err != nil {
|
|
http.Error(w, `{"error":"invalid app id"}`, http.StatusBadRequest)
|
|
return
|
|
}
|
|
|
|
if err := s.store.DeleteApp(r.Context(), id); err != nil {
|
|
http.Error(w, `{"error":"failed to delete app"}`, http.StatusInternalServerError)
|
|
return
|
|
}
|
|
|
|
w.WriteHeader(http.StatusNoContent)
|
|
}
|