initial agent-mgr: app builder platform MVP
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)
This commit is contained in:
105
internal/api/apps.go
Normal file
105
internal/api/apps.go
Normal file
@@ -0,0 +1,105 @@
|
||||
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)
|
||||
}
|
||||
Reference in New Issue
Block a user