| 1 | package serve |
| 2 | |
| 3 | import ( |
| 4 | "encoding/json" |
| 5 | "net/http" |
| 6 | |
| 7 | "reasonix/internal/control" |
| 8 | "reasonix/internal/event" |
| 9 | ) |
| 10 | |
| 11 | type exactPromptResolver interface { |
| 12 | ResolvePromptExact(control.PromptIdentity, control.PromptAnswer) error |
| 13 | } |
| 14 | |
| 15 | func (s *Server) resolvePromptExact(w http.ResponseWriter, r *http.Request) { |
| 16 | var body struct { |
| 17 | SessionID string `json:"sessionId"` |
| 18 | PromptID string `json:"promptId"` |
| 19 | TurnID string `json:"turnId"` |
| 20 | RuntimeEpoch string `json:"runtimeEpoch"` |
| 21 | Kind string `json:"kind"` |
| 22 | Answer struct { |
| 23 | Questions []struct { |
| 24 | QuestionID string `json:"questionId"` |
| 25 | Selected []string `json:"selected"` |
| 26 | } `json:"questions"` |
| 27 | Allow bool `json:"allow"` |
| 28 | Session bool `json:"session"` |
| 29 | Persist bool `json:"persist"` |
| 30 | Action string `json:"action"` |
| 31 | Feedback string `json:"feedback"` |
| 32 | Content map[string]any `json:"content"` |
| 33 | Generation uint64 `json:"generation"` |
| 34 | PermissionRevision uint64 `json:"permissionRevision"` |
| 35 | } `json:"answer"` |
| 36 | } |
| 37 | if err := json.NewDecoder(r.Body).Decode(&body); err != nil || body.PromptID == "" || body.TurnID == "" || body.Kind == "" { |
| 38 | http.Error(w, "missing exact prompt identity", http.StatusBadRequest) |
| 39 | return |
| 40 | } |
| 41 | resolver, ok := s.ctl().(exactPromptResolver) |
| 42 | if !ok { |
| 43 | http.Error(w, "exact prompt resolution is unavailable", http.StatusConflict) |
| 44 | return |
| 45 | } |
| 46 | if reader, ok := s.ctl().(interface { |
| 47 | RuntimeStateSnapshot() event.RuntimeStateSnapshot |
| 48 | }); ok { |
| 49 | if current := reader.RuntimeStateSnapshot().SessionID; current != "" && body.SessionID != current { |
| 50 | http.Error(w, "prompt session binding is stale", http.StatusConflict) |
| 51 | return |
| 52 | } |
| 53 | } |
| 54 | questions := make([]event.AskAnswer, len(body.Answer.Questions)) |
| 55 | for i, question := range body.Answer.Questions { |
| 56 | questions[i] = event.AskAnswer{QuestionID: question.QuestionID, Selected: question.Selected} |
| 57 | } |
| 58 | answer := control.PromptAnswer{Questions: questions, Allow: body.Answer.Allow, Session: body.Answer.Session, |
| 59 | Persist: body.Answer.Persist, Action: body.Answer.Action, Feedback: body.Answer.Feedback, |
| 60 | Content: body.Answer.Content, Generation: body.Answer.Generation, PermissionRevision: body.Answer.PermissionRevision} |
| 61 | identity := control.PromptIdentity{PromptID: body.PromptID, TurnID: body.TurnID, RuntimeEpoch: body.RuntimeEpoch, Kind: control.PromptKind(body.Kind)} |
| 62 | if err := resolver.ResolvePromptExact(identity, answer); err != nil { |
| 63 | http.Error(w, err.Error(), http.StatusConflict) |
| 64 | return |
| 65 | } |
| 66 | w.WriteHeader(http.StatusNoContent) |
| 67 | } |
| 68 |