package api import ( "io/fs" "log/slog" "net/http" "github.com/agentsphere/agent-mgr/internal/app" "github.com/agentsphere/agent-mgr/internal/events" "github.com/agentsphere/agent-mgr/internal/provider" "github.com/agentsphere/agent-mgr/internal/store" "github.com/go-chi/chi/v5" "github.com/go-chi/chi/v5/middleware" ) type Server struct { store *store.Store app *app.Service registry *provider.Registry events *events.Bus log *slog.Logger } func NewServer(st *store.Store, appSvc *app.Service, reg *provider.Registry, bus *events.Bus, log *slog.Logger) *Server { return &Server{store: st, app: appSvc, registry: reg, events: bus, log: log} } func (s *Server) Handler(staticFS fs.FS) http.Handler { r := chi.NewRouter() r.Use(middleware.RealIP) r.Use(middleware.Recoverer) r.Use(middleware.Compress(5)) // API routes r.Route("/api/v1", func(r chi.Router) { r.Use(middleware.SetHeader("Content-Type", "application/json")) r.Post("/apps", s.createApp) r.Get("/apps", s.listApps) r.Get("/apps/{appID}", s.getApp) r.Delete("/apps/{appID}", s.deleteApp) r.Post("/apps/{appID}/sessions", s.createSession) r.Get("/apps/{appID}/sessions", s.listSessions) r.Get("/sessions/{sessionID}", s.getSession) r.Post("/sessions/{sessionID}/stop", s.stopSession) r.Post("/sessions/{sessionID}/message", s.sendMessage) r.Get("/sessions/{sessionID}/ws", s.streamSession) r.Get("/providers", s.listProviders) r.Get("/health", func(w http.ResponseWriter, r *http.Request) { w.Write([]byte(`{"status":"ok"}`)) }) }) // Serve SPA for all other routes if staticFS != nil { fileServer := http.FileServer(http.FS(staticFS)) r.Get("/*", func(w http.ResponseWriter, r *http.Request) { // Try to serve the file; if not found, serve index.html (SPA routing) path := r.URL.Path f, err := staticFS.Open(path[1:]) // strip leading / if err != nil { // Serve index.html for SPA client-side routing r.URL.Path = "/" } else { f.Close() } fileServer.ServeHTTP(w, r) }) } return r }