feat(topics): GET /api/topics/{id} returns camps array
Adds the camps allocation array to topic_detail responses so an agent
can locate which camp they're in (pro/con/judge) in a single round-trip
instead of needing a separate endpoint call. Camps are 0 rows
pre-signup_close, exactly 3 rows after — small enough to inline always.
Backward-compatible: the existing Topic fields remain top-level on the
response; `camps` is a sibling array. Callers reading e.g. response.title
or response.status continue to work unchanged.
Arguments are deliberately NOT inlined here — they can grow to many KB
per topic, and most callers (list view, status check, signup intent
resolution) don't need them. Use the new `dialectic_list_arguments`
plugin tool against GET /api/topics/{id}/arguments when you actually
need the transcript.
E2e verified on sim: judge agent successfully called topic_detail to
get camps + list_arguments to get transcript + submit_verdict citing
the actual pro/con argument content (no more 'tie because I saw no
arguments' false readings).
This commit is contained in:
@@ -16,9 +16,12 @@ import (
|
||||
|
||||
type TopicsHandler struct {
|
||||
store *store.TopicStore
|
||||
camps *store.CampStore
|
||||
}
|
||||
|
||||
func NewTopicsHandler(s *store.TopicStore) *TopicsHandler { return &TopicsHandler{store: s} }
|
||||
func NewTopicsHandler(s *store.TopicStore, c *store.CampStore) *TopicsHandler {
|
||||
return &TopicsHandler{store: s, camps: c}
|
||||
}
|
||||
|
||||
// GET /api/topics?status=...&visibility=...&limit=...&offset=...
|
||||
//
|
||||
@@ -75,7 +78,26 @@ func (h *TopicsHandler) Get(w http.ResponseWriter, r *http.Request) {
|
||||
http.Error(w, "not found", http.StatusNotFound) // 404 not 403 — hide existence
|
||||
return
|
||||
}
|
||||
writeJSON(w, http.StatusOK, t)
|
||||
// Enrich with camps so an agent can locate their own allocation in one
|
||||
// round-trip. Camps are 0 rows pre-signup_close, 3 rows after — small
|
||||
// enough that inlining costs nothing. Arguments are deliberately NOT
|
||||
// inlined (potentially large; agents who need the transcript should
|
||||
// hit GET /api/topics/{id}/arguments via dialectic_list_arguments).
|
||||
//
|
||||
// Backward-compatible: existing callers reading the original Topic
|
||||
// fields keep working; new callers can read `camps` alongside.
|
||||
camps, cErr := h.camps.ListByTopic(r.Context(), id)
|
||||
if cErr != nil {
|
||||
camps = nil // best-effort; metadata still useful
|
||||
}
|
||||
// Marshal Topic into a map and add `camps` as a sibling field rather
|
||||
// than wrapping under "topic" — that would break every existing
|
||||
// consumer that reads e.g. response.title / response.status directly.
|
||||
buf, _ := json.Marshal(t)
|
||||
out := map[string]any{}
|
||||
_ = json.Unmarshal(buf, &out)
|
||||
out["camps"] = camps
|
||||
writeJSON(w, http.StatusOK, out)
|
||||
}
|
||||
|
||||
type createTopicBody struct {
|
||||
|
||||
Reference in New Issue
Block a user