package api import ( "encoding/json" "errors" "net/http" "time" "github.com/agent-matrix/matrix-runtime/internal/store" ) // staleAfter marks a runtime offline if it hasn't sent a heartbeat recently. const runtimeStaleAfter = 90 * time.Second // handleCloudListRuntimes returns the calling workspace's registered runtimes. func (s *Server) handleCloudListRuntimes(w http.ResponseWriter, r *http.Request) { u, ok := s.currentUser(r) if !ok { writeError(w, http.StatusUnauthorized, "not authenticated") return } list, err := s.store.ListRuntimes(u.WorkspaceID, runtimeStaleAfter) if err != nil { writeError(w, http.StatusInternalServerError, "could not list runtimes") return } writeJSON(w, http.StatusOK, map[string]any{"runtimes": list}) } // handleCloudRegisterRuntime is called by a remote sandbox (e.g. a duplicated // HF Space) presenting a workspace join token. It registers the runtime and // returns a long-lived runtime token used for subsequent heartbeats. func (s *Server) handleCloudRegisterRuntime(w http.ResponseWriter, r *http.Request) { if !s.requireStore(w) { return } var req struct { JoinToken string `json:"join_token"` Name string `json:"name"` Kind string `json:"kind"` Mode string `json:"mode"` URL string `json:"url"` HFSpace string `json:"hf_space"` Region string `json:"region"` Version string `json:"version"` Caps []string `json:"caps"` } if err := json.NewDecoder(r.Body).Decode(&req); err != nil { writeError(w, http.StatusBadRequest, "invalid JSON body") return } workspaceID, err := s.store.RedeemJoinToken(req.JoinToken) if err != nil { writeError(w, http.StatusUnauthorized, err.Error()) return } if req.Name == "" { req.Name = req.HFSpace } if req.Kind == "" { req.Kind = "hf-space" } rt, token, err := s.store.RegisterRuntime(store.Runtime{ WorkspaceID: workspaceID, Name: req.Name, Mode: req.Mode, Kind: req.Kind, URL: req.URL, HFSpace: req.HFSpace, Region: req.Region, Version: req.Version, Caps: req.Caps, }) if err != nil { writeError(w, http.StatusInternalServerError, "could not register runtime") return } s.audit(r, workspaceID, rt.ID, "runtime.registered", rt.Name, "success", map[string]any{"kind": rt.Kind, "hf_space": rt.HFSpace}) writeJSON(w, http.StatusCreated, map[string]any{"runtime": rt, "runtime_token": token}) } // handleCloudHeartbeat updates a runtime's status using its runtime token // (Authorization: Bearer ). func (s *Server) handleCloudHeartbeat(w http.ResponseWriter, r *http.Request) { if !s.requireStore(w) { return } var req struct { Status string `json:"status"` Caps []string `json:"caps"` } _ = json.NewDecoder(r.Body).Decode(&req) rt, err := s.store.HeartbeatRuntime(bearer(r), req.Status, req.Caps) if err != nil { if errors.Is(err, store.ErrNotFound) { writeError(w, http.StatusUnauthorized, "invalid runtime token") return } writeError(w, http.StatusInternalServerError, "could not record heartbeat") return } writeJSON(w, http.StatusOK, map[string]any{"runtime": rt}) } // handleCloudListJoinTokens lists the workspace's active join tokens. func (s *Server) handleCloudListJoinTokens(w http.ResponseWriter, r *http.Request) { u, ok := s.currentUser(r) if !ok { writeError(w, http.StatusUnauthorized, "not authenticated") return } list, err := s.store.ListJoinTokens(u.WorkspaceID) if err != nil { writeError(w, http.StatusInternalServerError, "could not list join tokens") return } writeJSON(w, http.StatusOK, map[string]any{"join_tokens": list}) } // handleCloudMintJoinToken mints a new join token for the workspace. The secret // is returned exactly once. func (s *Server) handleCloudMintJoinToken(w http.ResponseWriter, r *http.Request) { u, ok := s.currentUser(r) if !ok { writeError(w, http.StatusUnauthorized, "not authenticated") return } var req struct { Label string `json:"label"` MaxUses int `json:"max_uses"` TTLMinutes int `json:"ttl_minutes"` } _ = json.NewDecoder(r.Body).Decode(&req) ttl := time.Duration(req.TTLMinutes) * time.Minute if req.TTLMinutes == 0 { ttl = 24 * time.Hour } jt, secret, err := s.store.MintJoinToken(u.WorkspaceID, u.ID, req.Label, req.MaxUses, ttl) if err != nil { writeError(w, http.StatusInternalServerError, "could not mint join token") return } s.audit(r, u.WorkspaceID, u.ID, "runtime.join_token.created", jt.ID, "success", map[string]any{"label": jt.Label, "max_uses": jt.MaxUses}) writeJSON(w, http.StatusCreated, map[string]any{"join_token": jt, "secret": secret}) } // handleCloudListProviders lists BYO provider credentials (hints only). func (s *Server) handleCloudListProviders(w http.ResponseWriter, r *http.Request) { u, ok := s.currentUser(r) if !ok { writeError(w, http.StatusUnauthorized, "not authenticated") return } list, err := s.store.ListProviderCredentials(u.WorkspaceID) if err != nil { writeError(w, http.StatusInternalServerError, "could not list providers") return } writeJSON(w, http.StatusOK, map[string]any{"providers": list}) } // handleCloudSetProvider stores (encrypted) a BYO provider token — e.g. a user // plugging in their own Hugging Face account to use HF LLMs inside MatrixCloud. func (s *Server) handleCloudSetProvider(w http.ResponseWriter, r *http.Request) { u, ok := s.currentUser(r) if !ok { writeError(w, http.StatusUnauthorized, "not authenticated") return } var req struct { Provider string `json:"provider"` Label string `json:"label"` Secret string `json:"secret"` Meta map[string]any `json:"meta"` } if err := json.NewDecoder(r.Body).Decode(&req); err != nil { writeError(w, http.StatusBadRequest, "invalid JSON body") return } if req.Provider == "" || req.Secret == "" { writeError(w, http.StatusBadRequest, "provider and secret are required") return } pc, err := s.store.SetProviderCredential(u.WorkspaceID, u.ID, req.Provider, req.Label, req.Secret, req.Meta) if err != nil { writeError(w, http.StatusInternalServerError, "could not save credential") return } s.audit(r, u.WorkspaceID, u.ID, "provider.credential.added", req.Provider, "success", map[string]any{"label": pc.Label, "hint": pc.Hint}) writeJSON(w, http.StatusCreated, map[string]any{"provider": pc}) } // handleCloudUsage returns the workspace's usage in the trailing 30 days. func (s *Server) handleCloudUsage(w http.ResponseWriter, r *http.Request) { u, ok := s.currentUser(r) if !ok { writeError(w, http.StatusUnauthorized, "not authenticated") return } since := time.Now().Add(-30 * 24 * time.Hour) used, err := s.store.UsageSince(u.WorkspaceID, since) if err != nil { writeError(w, http.StatusInternalServerError, "could not load usage") return } writeJSON(w, http.StatusOK, map[string]any{"since": since.UTC().Format(time.RFC3339), "usage": used}) }