mirror of
https://github.com/Wei-Shaw/sub2api.git
synced 2026-10-16 07:33:06 +08:00
fix(openai): preserve encrypted reasoning across turns on codex OAuth path
PR #2068 dropped every reasoning item from input[] on the OAuth/codex path (store=false). That silently discards encrypted_content -- the out-of-band channel that carries reasoning context across turns under store=false -- degrading multi-turn agent reasoning with no visible error. Reported by @neteroster on PR #2068. The 404 that #2068 worked around ("Item with id 'rs_...' not found") is triggered by the rs_* id lookup under store=false, not by the reasoning item itself -- so the correct fix is to strip the id, not delete the item. Verified end-to-end against the live chatgpt.com codex backend (gpt-5.5) and a real OpenClaw container (api: openai-responses): - bare rs_ id, no encrypted_content -> 404 - id stripped -> 200 - encrypted_content + id stripped -> 200, reasoning context preserved - reasoning items require a summary field (missing -> 400) - real OpenClaw multi-turn agent loop -> all /v1/responses 200, zero 404 Fix: keep the reasoning item, strip only the rs_* id (always, independent of PreserveReferences), preserve encrypted_content/content/summary verbatim, and backfill an empty summary when absent. Tool-call call_id pairing is untouched. Also verified compaction_summary items (cmp_*, the other encrypted_content carrier): they require encrypted_content (missing -> 400) and their id does not 404 when present (kept or stripped), so the existing generic path already handles them safely -- no special-casing needed. Adds regression tests for each verified reasoning contract. Refs #1957, #2068
This commit is contained in:
@@ -1157,11 +1157,41 @@ func filterCodexInputWithOptions(input []any, opts codexInputFilterOptions) []an
|
||||
}
|
||||
typ, _ := m["type"].(string)
|
||||
|
||||
// chatgpt.com codex backend (OAuth path) does not persist reasoning
|
||||
// items because applyCodexOAuthTransform forces store=false. Any rs_*
|
||||
// reference replayed in input is guaranteed to 404 upstream
|
||||
// ("Item with id 'rs_...' not found"). Drop reasoning items entirely.
|
||||
// chatgpt.com codex (OAuth path) runs with store=false (forced by
|
||||
// applyCodexOAuthTransform). Replaying a reasoning item with its rs_*
|
||||
// id but no encrypted_content 404s upstream ("Item with id 'rs_...'
|
||||
// not found") — the 404 is triggered by the id lookup, not by the
|
||||
// reasoning item itself. So strip the id (always, independent of
|
||||
// PreserveReferences) yet keep the item: under store=false
|
||||
// encrypted_content is the official channel for carrying reasoning
|
||||
// context across turns, and dropping the whole item silently degrades
|
||||
// multi-turn agent reasoning. Preserve encrypted_content/content/
|
||||
// summary and every other field verbatim. Upstream additionally
|
||||
// requires a summary field — a missing one is rejected with 400
|
||||
// "Missing required parameter 'input[N].summary'" — so backfill an
|
||||
// empty array when it is absent. Contracts verified end-to-end against
|
||||
// chatgpt.com codex (gpt-5.5); see issue #1957.
|
||||
// compaction_summary items (cmp_*) are the other encrypted_content
|
||||
// carrier. Verified against the live backend: they require
|
||||
// encrypted_content (a missing one is rejected with 400), and with it
|
||||
// present the cmp_* id does not 404 whether kept or stripped. Being
|
||||
// neither reasoning nor tool calls, they flow through the generic path
|
||||
// below (id stripped when !PreserveReferences, encrypted_content
|
||||
// preserved either way), which is safe and needs no special-casing.
|
||||
if typ == "reasoning" {
|
||||
newItem := make(map[string]any, len(m))
|
||||
for key, value := range m {
|
||||
if key == "id" {
|
||||
// rs_* id replayed under store=false 404s; strip it.
|
||||
continue
|
||||
}
|
||||
newItem[key] = value
|
||||
}
|
||||
if summary, ok := newItem["summary"]; !ok || summary == nil {
|
||||
// Upstream requires a summary field; an empty array satisfies it.
|
||||
newItem["summary"] = []any{}
|
||||
}
|
||||
filtered = append(filtered, newItem)
|
||||
continue
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user