Files
wehub-resource-sync f99010fae1
CI / lint (push) Failing after 1s
CI / frontend (push) Failing after 1s
CI / scripts (push) Failing after 1s
CI / Go Test (ubuntu-latest) (push) Failing after 0s
CI / frontend-node-25 (push) Failing after 1s
CI / docs (push) Failing after 0s
CI / coverage (push) Failing after 0s
CI / e2e (push) Failing after 0s
Docker / build-and-push (push) Failing after 1s
CI / integration (push) Failing after 4m43s
CI / Go Test (windows-latest) (push) Has been cancelled
CI / Desktop Unit Tests (Windows) (push) Has been cancelled
Desktop Artifacts / Desktop Build (Linux (arm64)) (push) Has been cancelled
Desktop Artifacts / Desktop Build (Linux) (push) Has been cancelled
Desktop Artifacts / Desktop Build (Windows) (push) Has been cancelled
Desktop Artifacts (macOS) / Desktop Build (macOS (aarch64)) (push) Has been cancelled
Desktop Artifacts (macOS) / Desktop Build (macOS (x86_64)) (push) Has been cancelled
chore: import upstream snapshot with attribution
2026-07-13 12:30:36 +08:00

233 lines
5.7 KiB
Go

package server
import (
"context"
"encoding/json"
"errors"
"net/http"
"go.kenn.io/agentsview/internal/config"
"go.kenn.io/agentsview/internal/db"
"go.kenn.io/agentsview/internal/service"
)
func handleContextError(w http.ResponseWriter, err error) bool {
if errors.Is(err, context.Canceled) {
return true
}
if errors.Is(err, context.DeadlineExceeded) {
writeError(w, http.StatusGatewayTimeout, "gateway timeout")
return true
}
return false
}
func handleReadOnly(w http.ResponseWriter, err error) bool {
if errors.Is(err, db.ErrReadOnly) {
writeError(w, http.StatusNotImplemented, "not available in remote mode")
return true
}
return false
}
func handleInvalidRecallQuery(w http.ResponseWriter, err error) bool {
if errors.Is(err, db.ErrInvalidRecallQuery) {
writeError(w, http.StatusBadRequest, err.Error())
return true
}
return false
}
func (s *Server) handleListRecallEntries(
w http.ResponseWriter, r *http.Request,
) {
q := r.URL.Query()
limit, ok := parseIntParam(w, r, "limit")
if !ok {
return
}
if err := service.ValidateRecallEntryLimit(limit); err != nil {
writeError(w, http.StatusBadRequest, err.Error())
return
}
trustedOnly, ok := parseBoolParam(w, r, "trusted_only")
if !ok {
return
}
query := db.RecallQuery{
Text: q.Get("q"),
Project: q.Get("project"),
CWD: q.Get("cwd"),
GitBranch: q.Get("git_branch"),
Agent: q.Get("agent"),
Type: q.Get("type"),
Scope: q.Get("scope"),
Status: q.Get("status"),
ExtractorMethod: q.Get("extractor_method"),
SourceSessionID: q.Get("source_session_id"),
SourceEpisodeID: q.Get("source_episode_id"),
SourceRunID: q.Get("source_run_id"),
SupersedesEntryID: q.Get("supersedes_entry_id"),
SupersededByEntryID: q.Get("superseded_by_entry_id"),
TrustedOnly: trustedOnly,
Limit: limit,
}
if err := db.ValidateRecallQuery(query); err != nil {
if handleInvalidRecallQuery(w, err) {
return
}
writeError(w, http.StatusInternalServerError, err.Error())
return
}
page, err := s.db.QueryRecallEntries(r.Context(), query)
if err != nil {
if handleContextError(w, err) {
return
}
if handleInvalidRecallQuery(w, err) {
return
}
if handleReadOnly(w, err) {
return
}
writeError(w, http.StatusInternalServerError, err.Error())
return
}
if page.RecallEntries == nil {
page.RecallEntries = []db.RecallResult{}
}
writeJSON(w, http.StatusOK, map[string]any{
"entries": page.RecallEntries,
"trusted_only": query.TrustedOnly,
})
}
func (s *Server) handleGetRecallEntry(
w http.ResponseWriter, r *http.Request,
) {
id := r.PathValue("id")
recall, err := s.db.GetRecallEntry(r.Context(), id)
if err != nil {
if handleContextError(w, err) {
return
}
if handleReadOnly(w, err) {
return
}
writeError(w, http.StatusInternalServerError, err.Error())
return
}
if recall == nil {
writeError(w, http.StatusNotFound, "recall entry not found")
return
}
writeJSON(w, http.StatusOK, recall)
}
func (s *Server) handleQueryRecallEntries(
w http.ResponseWriter, r *http.Request,
) {
var req service.RecallQuery
if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
writeError(w, http.StatusBadRequest, "invalid JSON body")
return
}
if err := service.ValidateRecallEntryLimit(req.Limit); err != nil {
writeError(w, http.StatusBadRequest, err.Error())
return
}
if req.IncludeContext {
if _, err := service.NormalizeRecallContextMaxBytes(
req.ContextMaxBytes,
); err != nil {
writeError(w, http.StatusBadRequest, err.Error())
return
}
}
if _, err := service.NormalizeRecallQuerySurface(req.Surface); err != nil {
writeError(w, http.StatusBadRequest, err.Error())
return
}
resp, err := service.QueryRecallStore(r.Context(), s.db, req)
if err != nil {
if handleContextError(w, err) {
return
}
if handleInvalidRecallQuery(w, err) {
return
}
if handleReadOnly(w, err) {
return
}
writeError(w, http.StatusInternalServerError, err.Error())
return
}
writeJSON(w, http.StatusOK, resp)
}
func (s *Server) handleImportRecallEntries(
w http.ResponseWriter, r *http.Request,
) {
dryRun, ok := parseBoolParam(w, r, "dry_run")
if !ok {
return
}
allowProductionImport, ok := parseBoolParam(
w, r, "allow_production_import",
)
if !ok {
return
}
requireExistingSessions, ok := recallImportRequiresExistingSessions(w, r)
if !ok {
return
}
if !allowProductionImport &&
(config.IsDefaultAgentsviewDataDir(s.cfg.DataDir) ||
config.IsDefaultAgentsviewDBPath(s.cfg.DBPath)) {
writeError(
w,
http.StatusForbidden,
"recall import refuses to validate or write against the default agentsview data directory; set allow_production_import=true only when intentionally targeting that archive",
)
return
}
result, err := s.db.ImportAcceptedRecallEntriesJSONLWithOptions(
r.Context(),
r.Body,
db.RecallImportOptions{
DryRun: dryRun,
RequireExistingSessions: requireExistingSessions,
AllowProductionImport: allowProductionImport,
},
)
if err != nil {
if handleContextError(w, err) {
return
}
if handleReadOnly(w, err) {
return
}
writeError(w, http.StatusBadRequest, err.Error())
return
}
writeJSON(w, http.StatusOK, result)
}
func recallImportRequiresExistingSessions(
w http.ResponseWriter, r *http.Request,
) (bool, bool) {
requireExisting, ok := parseBoolParam(w, r, "require_existing_sessions")
if !ok {
return false, false
}
allowPlaceholder, ok := parseBoolParam(w, r, "allow_placeholder_sessions")
if !ok {
return false, false
}
if requireExisting {
return true, true
}
return !allowPlaceholder, true
}