MCPcopy Create free account
hub / github.com/Mnexa-AI/e2a / idempotencyGuard

Method idempotencyGuard

internal/agent/idempotency_guard.go:54–120  ·  view source on GitHub ↗

idempotencyGuard runs the Idempotency-Key handshake at the start of a side-effectful POST handler. The transport contract is Stripe-style: - No header → transparent no-op. The handler runs unchanged. - Header set + cached completed response (same body) → server writes the cached response verbatim a

(w http.ResponseWriter, r *http.Request, userID string, bodyBytes []byte)

Source from the content-addressed store, hash-verified

52// can ignore it entirely — the helper is a no-op when the writer
53// isn't a capturing wrapper.
54func (a *API) idempotencyGuard(w http.ResponseWriter, r *http.Request, userID string, bodyBytes []byte) (replayed bool, out http.ResponseWriter, finalize func()) {
55 noop := func() {}
56
57 if a.idempotency == nil {
58 return false, w, noop
59 }
60 key := strings.TrimSpace(r.Header.Get("Idempotency-Key"))
61 if key == "" {
62 return false, w, noop
63 }
64 if len(key) > idempotency.MaxKeyLength {
65 http.Error(w, "Idempotency-Key exceeds max length", http.StatusBadRequest)
66 return true, nil, nil
67 }
68
69 res, err := a.idempotency.Claim(r.Context(), userID, key, r.URL.Path, idempotency.HashRequest(r.URL.Path, bodyBytes))
70 if err != nil {
71 log.Printf("[idempotency] claim error: %v (failing open)", err)
72 return false, w, noop
73 }
74
75 switch res.Outcome {
76 case idempotency.OutcomeReplay:
77 if res.Cached.ContentType != "" {
78 w.Header().Set("Content-Type", res.Cached.ContentType)
79 }
80 w.Header().Set("Idempotent-Replayed", "true")
81 w.WriteHeader(res.Cached.StatusCode)
82 _, _ = w.Write(res.Cached.Body)
83 return true, nil, nil
84
85 case idempotency.OutcomeMismatch:
86 http.Error(w, "Idempotency-Key reused with a different request body", http.StatusUnprocessableEntity)
87 return true, nil, nil
88
89 case idempotency.OutcomeInFlight:
90 http.Error(w, "another request with this Idempotency-Key is in progress", http.StatusConflict)
91 return true, nil, nil
92
93 case idempotency.OutcomeAcquired:
94 cap := &capturingWriter{ResponseWriter: w}
95 return false, cap, func() {
96 // WriteHeader may not have been called explicitly (Go's
97 // http package implicitly writes 200 on first Write).
98 code := cap.statusCode
99 if code == 0 {
100 code = http.StatusOK
101 }
102 if shouldCacheResponse(code, cap.sideEffectCommitted) {
103 if err := a.idempotency.Complete(r.Context(), userID, key, idempotency.CachedResponse{
104 StatusCode: code,
105 ContentType: cap.Header().Get("Content-Type"),
106 Body: cap.body.Bytes(),
107 }); err != nil {
108 log.Printf("[idempotency] complete error: %v", err)
109 }
110 return
111 }

Callers 1

Calls 9

HashRequestFunction · 0.92
shouldCacheResponseFunction · 0.85
GetMethod · 0.65
ClaimMethod · 0.65
CompleteMethod · 0.65
ReleaseMethod · 0.65
ErrorMethod · 0.45
WriteHeaderMethod · 0.45
WriteMethod · 0.45

Tested by 1