gateway: a Codex conversation compacted on GPT goes on after switching to Grok (waroy on Discord: Grok (SuperGrok): Could not decrypt the provided encrypted_content) — when Codex compacts on OpenAI's own models, the earlier turns become a compaction item sealed by OpenAI (encrypted_content), which magpie kept for magpie models. Grok can't read it, any more than another vendor's reasoning. The retry after such a refusal (2476cd5) left out only the sealed reasoning and asked once, so Grok refused the compaction too and Codex got xAI's error. It now asks again without the compaction when the refusal comes back (or there was no reasoning to leave out), and remembers the compaction with the reasoning as refused for that conversation and account, so later turns leave both out up front while Grok's own reasoning goes along; each step remembers only what it took out. The summary of the turns before the compaction is lost to Grok; what was said since stays. TestForeignCompactionToGrok (Codex path, a SuperGrok-like upstream refusing in the stream) fails without the change with the reporter's error. go vet, go build -tags nogui (darwin, GOOS=linux), GOOS=windows go build, go test -tags nogui ./... pass (TestClaudeSignedOut, TestClaudeAccountNamedAsItsLogin, TestClaudeIdentityServedWhileAsked, known flakes under load, pass alone). Not tested against xAI itself.

This commit is contained in:
yetone committed 2026-09-30 16:40:41 +08:00
1 parent bcb9db9356
commit e0e93c2ec5
3 files changed
+191 -22

No files matched your search

+47 -11
View File
@@ -19,6 +19,7 @@ import (
"encoding/json"
"net/http"
"regexp"
"slices"
"sync"
"time"
@@ -245,9 +246,17 @@ type sealedItem struct {
Enc string `json:"encrypted_content"`
}
// seals are the sealed reasoning (encrypted_content) in a Responses
// request's input.
func seals(body []byte) [][sha256.Size]byte {
// sealedKinds are the input items a vendor seals for itself: its
// reasoning, and the compaction a conversation's earlier turns were
// folded into (OpenAI's, when Codex compacts on its own models; magpie's
// own is text by the time a request is sent).
var sealedKinds = map[string]bool{"reasoning": true, "compaction": true, "compaction_summary": true}
var compactionKinds = []string{"compaction", "compaction_summary"}
// seals are the sealed reasoning and compactions (encrypted_content) in a
// Responses request's input, of these types.
func seals(body []byte, kinds ...string) [][sha256.Size]byte {
var q struct {
Input []sealedItem `json:"input"`
}
@@ -256,17 +265,17 @@ func seals(body []byte) [][sha256.Size]byte {
}
var out [][sha256.Size]byte
for _, it := range q.Input {
if it.Type == "reasoning" && it.Enc != "" {
if slices.Contains(kinds, it.Type) && it.Enc != "" {
out = append(out, sha256.Sum256([]byte(it.Enc)))
}
}
return out
}
// refused notes that who turned away the sealed reasoning in body, in the
// conversation key names.
func refused(key, who string, body []byte) {
ss := seals(body)
// refused notes that who turned away the sealed items of these types in
// body, in the conversation key names.
func refused(key, who string, body []byte, kinds ...string) {
ss := seals(body, kinds...)
if len(ss) == 0 {
return
}
@@ -293,7 +302,8 @@ func refused(key, who string, body []byte) {
}
// withoutRefused takes out of a Responses request the sealed reasoning
// who already refused in this conversation; the rest stays as it is.
// and compactions who already refused in this conversation; the rest
// stays as it is.
func withoutRefused(key, who string, body []byte) ([]byte, bool) {
k := key + "|" + who
refusedSeals.Lock()
@@ -317,7 +327,7 @@ func withoutRefused(key, who string, body []byte) ([]byte, bool) {
kept := items[:0:0]
for _, it := range items {
var t sealedItem
if json.Unmarshal(it, &t) == nil && t.Type == "reasoning" && t.Enc != "" && r.seals[sha256.Sum256([]byte(t.Enc))] {
if json.Unmarshal(it, &t) == nil && sealedKinds[t.Type] && t.Enc != "" && r.seals[sha256.Sum256([]byte(t.Enc))] {
continue
}
kept = append(kept, it)
@@ -334,6 +344,32 @@ func withoutRefused(key, who string, body []byte) ([]byte, bool) {
// input — what another account wrote and this one can't read. What was
// said and done stays; only the model's private notes to itself go.
func withoutReasoning(body []byte) ([]byte, bool) {
return withoutKinds(body, "reasoning")
}
// withoutCompaction takes out of a Responses request's input the sealed
// compaction of its earlier turns — OpenAI's, which no other vendor or
// account can read (waroy: Grok's "Could not decrypt the provided
// encrypted_content" with the reasoning already gone). What the turns
// since said and did stays; the summary of those before goes.
func withoutCompaction(body []byte) ([]byte, bool) {
var q struct {
Input []sealedItem `json:"input"`
}
if json.Unmarshal(body, &q) != nil {
return nil, false
}
for _, it := range q.Input {
if slices.Contains(compactionKinds, it.Type) && it.Enc != "" {
return withoutKinds(body, compactionKinds...)
}
}
return nil, false
}
// withoutKinds is a Responses request without its input items of these
// types.
func withoutKinds(body []byte, kinds ...string) ([]byte, bool) {
var q map[string]json.RawMessage
if json.Unmarshal(body, &q) != nil {
return nil, false
@@ -347,7 +383,7 @@ func withoutReasoning(body []byte) ([]byte, bool) {
var t struct {
Type string `json:"type"`
}
if json.Unmarshal(it, &t) == nil && t.Type == "reasoning" {
if json.Unmarshal(it, &t) == nil && slices.Contains(kinds, t.Type) {
continue
}
kept = append(kept, it)
+119
View File
@@ -0,0 +1,119 @@
package gateway
import (
"encoding/json"
"io"
"net/http"
"net/http/httptest"
"strings"
"sync"
"testing"
"github.com/yetone/magpie/internal/provider"
)
// grokUpstream is a SuperGrok-like Responses upstream that can decrypt
// only what it sealed itself, in reasoning and in a compaction alike, and
// says so in the stream as xAI does.
type grokUpstream struct {
mu sync.Mutex
got [][]string // the sealed content each request carried, by type
}
func (g *grokUpstream) ServeHTTP(w http.ResponseWriter, r *http.Request) {
b, _ := io.ReadAll(r.Body)
var q struct {
Input []sealedItem `json:"input"`
}
json.Unmarshal(b, &q)
var got []string
foreign := false
for _, it := range q.Input {
if it.Enc != "" {
got = append(got, it.Type+":"+it.Enc)
foreign = foreign || it.Enc != "sealed-by-grok"
}
}
g.mu.Lock()
g.got = append(g.got, got)
g.mu.Unlock()
w.Header().Set("Content-Type", "text/event-stream")
if foreign {
io.WriteString(w, sse(
`data: {"type":"response.created","response":{"id":"r0","model":"grok-4"}}`,
`data: {"type":"response.failed","response":{"id":"r0","error":{"code":"invalid_request_error","message":"`+xaiUndecryptable+`"}}}`))
return
}
io.WriteString(w, sse(
`data: {"type":"response.created","response":{"id":"r1","model":"grok-4"}}`,
`data: {"type":"response.output_item.done","output_index":0,"item":{"type":"reasoning","id":"rs_g","summary":[],"encrypted_content":"sealed-by-grok"}}`,
`data: {"type":"response.output_text.delta","delta":"from grok"}`,
`data: {"type":"response.completed","response":{"id":"r1","usage":{"input_tokens":7,"output_tokens":1}}}`))
}
func (g *grokUpstream) sent() [][]string {
g.mu.Lock()
defer g.mu.Unlock()
out := g.got
g.got = nil
return out
}
// A Codex conversation OpenAI compacted, switched to a Grok model (waroy:
// Grok (SuperGrok): Could not decrypt the provided encrypted_content):
// the compaction OpenAI sealed stays in the input as well as its
// reasoning, and Grok can read neither. With the reasoning taken out it
// still refused the compaction, and that refusal went to Codex. Now the
// compaction goes too once it is still refused, and later turns leave out
// both up front while Grok's own reasoning goes along.
func TestForeignCompactionToGrok(t *testing.T) {
fresh(t)
refusedSeals.Lock()
refusedSeals.m = map[string]sealsRefused{}
refusedSeals.Unlock()
g := &grokUpstream{}
up := httptest.NewServer(g)
t.Cleanup(up.Close)
if err := provider.Save(provider.Provider{ID: "grok1", Name: "Grok (SuperGrok)", Key: "k", Models: []string{"grok-4"}, Responses: up.URL + "/v1"}); err != nil {
t.Fatal(err)
}
s := New()
user := func(text string) string {
return `{"type":"message","role":"user","content":[{"type":"input_text","text":"` + text + `"}]}`
}
turn := func(items ...string) (int, string) {
t.Helper()
rec := httptest.NewRecorder()
req := httptest.NewRequest("POST", CodexPath+"/responses", strings.NewReader(`{"model":"grok1/grok-4","stream":true,"input":[`+strings.Join(items, ",")+`]}`))
req.Header.Set("session_id", "thread-compacted")
s.Handler().ServeHTTP(rec, req)
return rec.Code, rec.Body.String()
}
compaction := `{"type":"compaction","id":"cmp_1","encrypted_content":"sealed-by-openai-compaction"}`
reasoning := func(by string) string {
return `{"type":"reasoning","id":"rs_` + by + `","summary":[],"encrypted_content":"sealed-by-` + by + `"}`
}
call := func(id string) string {
return `{"type":"function_call","call_id":"` + id + `","name":"shell","arguments":"{}"},{"type":"function_call_output","call_id":"` + id + `","output":"ok"}`
}
history := []string{user("start"), compaction, user("go on"), reasoning("openai"), call("c1")}
code, body := turn(history...)
if code != 200 || !strings.Contains(body, "from grok") || strings.Contains(body, "decrypt") {
t.Fatalf("after the switch: %d %s", code, body)
}
got := g.sent()
if len(got) != 3 || len(got[0]) != 2 || strings.Join(got[1], ",") != "compaction:sealed-by-openai-compaction" || len(got[2]) != 0 {
t.Fatalf("grok was sent %q", got)
}
// the next turn: neither of OpenAI's is sent to be refused again, Grok's
// own reasoning goes along
code, body = turn(append(history, reasoning("grok"), call("c2"))...)
if code != 200 || !strings.Contains(body, "from grok") {
t.Fatalf("next turn: %d %s", code, body)
}
if got := g.sent(); len(got) != 1 || strings.Join(got[0], ",") != "reasoning:sealed-by-grok" {
t.Fatalf("grok was then sent %q", got)
}
}
+25 -11
View File
@@ -887,12 +887,12 @@ func (s *Server) serve(w http.ResponseWriter, r *http.Request, from provider.Pro
}
tr := s.trace.begin(Route{Time: start, Agent: call.Agent, Kind: call.Kind, Model: call.Model, Effort: requestEffort(from, body), Provider: p.ID, Group: group, Rule: hit, Nested: nested, Affinity: shown, Order: pl.order, Left: pl.left})
var skipped []string
sent := "" // the reasoning the last try's model was asked for
where := "" // the last try's provider.Where, for the usage
again := 0 // times the last one left has been tried again
resealed := false // the conversation's reasoning sealed by another account taken out
floored := false // the reply's length raised to what the provider takes
var other *Try // the first failure that wasn't an allowance run out
sent := "" // the reasoning the last try's model was asked for
where := "" // the last try's provider.Where, for the usage
again := 0 // times the last one left has been tried again
resealed := 0 // what of the conversation another account sealed was taken out: its reasoning, then its compaction
floored := false // the reply's length raised to what the provider takes
var other *Try // the first failure that wasn't an allowance run out
for i := 0; i < len(cands); i++ {
c := cands[i]
last := i == len(cands)-1
@@ -983,15 +983,29 @@ func (s *Server) serve(w http.ResponseWriter, r *http.Request, from provider.Pro
s.trace.update(tr, func(t *Route) { t.Tries[len(t.Tries)-1] = try })
break
}
if !resealed && from == provider.Responses && !hw.passing && hw.code() >= 400 && foreignReasoning.Match(hw.errBody()) {
if resealed < 2 && from == provider.Responses && !hw.passing && hw.code() >= 400 && foreignReasoning.Match(hw.errBody()) {
// the conversation moved here from another account or vendor,
// whose sealed reasoning this one can't read: asked again
// without it, and what it refused isn't sent here again. xAI
// says so as a 400, or as the stream's error, which may be
// read as another status
if b, ok := withoutReasoning(body); ok {
refused(stuck, c.who(), attemptBody)
resealed, body = true, b
// read as another status. Refused again, or with no reasoning
// to leave out, it's the compaction OpenAI sealed that goes
// (waroy: Codex compacted on GPT, then switched to Grok)
b, ok := []byte(nil), false
if resealed == 0 {
resealed = 1
if b, ok = withoutReasoning(body); ok {
refused(stuck, c.who(), attemptBody, "reasoning")
}
}
if !ok {
resealed = 2
if b, ok = withoutCompaction(body); ok {
refused(stuck, c.who(), attemptBody, compactionKinds...)
}
}
if ok {
body = b
try.Fail = failForeign
s.trace.update(tr, func(t *Route) { t.Tries[len(t.Tries)-1] = try })
i--