fix(providers): only send Codex prompt cache params to native OpenAI endpoints (#1342)

buildRequestBody emitted prompt_cache_key and prompt_cache_retention for
every Codex request. The ChatGPT subscription OAuth backend
(chatgpt.com/backend-api) rejects prompt_cache_retention with HTTP 400
"Unsupported parameter", so every LLM call from an agent using a ChatGPT
OAuth Codex provider failed at the first think step.

Gate both params on isOpenAINativeEndpoint(apiBase), the same native-only
policy already documented and applied by CacheMiddleware. This covers both
CodexProvider and CodexAdapter, since the adapter delegates to the same
builder. Server-side prefix caching still works on the OAuth backend
without these params.

Update the two prompt-cache tests to assert both cases: native endpoints
include the params, the OAuth backend omits them.
This commit is contained in:
Buns0705 authored and GitHub committed 2026-07-04 04:53:43 +07:00
1 parent 4d3c6bcb05
commit d913eb2149
3 files changed
+53 -15

No files matched your search

+23 -4
View File
@@ -388,18 +388,20 @@ func TestCodexAdapter_FromResponse_PromptTokensDetailsAlias(t *testing.T) {
}
func TestCodexAdapter_ToRequestPromptCacheControls(t *testing.T) {
a, _ := NewCodexAdapter(ProviderConfig{})
body, _, err := a.ToRequest(ChatRequest{
req := ChatRequest{
Messages: []Message{{Role: "user", Content: "hi"}},
Options: map[string]any{
OptPromptCacheKey: "tenant/agent/session",
OptPromptCacheRetention: "1h",
},
})
}
// Native OpenAI endpoint accepts prompt cache params.
nativeAdapter, _ := NewCodexAdapter(ProviderConfig{BaseURL: "https://api.openai.com/v1"})
body, _, err := nativeAdapter.ToRequest(req)
if err != nil {
t.Fatalf("ToRequest: %v", err)
}
var payload map[string]any
if err := json.Unmarshal(body, &payload); err != nil {
t.Fatalf("decode body: %v", err)
@@ -410,6 +412,23 @@ func TestCodexAdapter_ToRequestPromptCacheControls(t *testing.T) {
if got := payload["prompt_cache_retention"]; got != "1h" {
t.Fatalf("prompt_cache_retention = %v, want 1h", got)
}
// Default ChatGPT OAuth backend rejects these params with HTTP 400 — must omit.
oauthAdapter, _ := NewCodexAdapter(ProviderConfig{})
oauthBody, _, err := oauthAdapter.ToRequest(req)
if err != nil {
t.Fatalf("ToRequest (oauth): %v", err)
}
var oauthPayload map[string]any
if err := json.Unmarshal(oauthBody, &oauthPayload); err != nil {
t.Fatalf("decode oauth body: %v", err)
}
if _, ok := oauthPayload["prompt_cache_key"]; ok {
t.Fatal("prompt_cache_key must not be sent to the ChatGPT OAuth backend")
}
if _, ok := oauthPayload["prompt_cache_retention"]; ok {
t.Fatal("prompt_cache_retention must not be sent to the ChatGPT OAuth backend")
}
}
func TestCodexAdapter_CapabilitiesCacheControl(t *testing.T) {
+12 -5
View File
@@ -140,11 +140,18 @@ func (p *CodexProvider) buildRequestBody(req ChatRequest, stream bool) map[strin
body["reasoning"] = map[string]any{"effort": level}
}
if cacheKey, ok := req.Options[OptPromptCacheKey]; ok {
body["prompt_cache_key"] = cacheKey
}
if retention, ok := req.Options[OptPromptCacheRetention]; ok {
body["prompt_cache_retention"] = retention
// Prompt caching params (prompt_cache_key, prompt_cache_retention) are accepted
// only by native OpenAI endpoints. The ChatGPT subscription OAuth backend
// (chatgpt.com/backend-api) rejects them with HTTP 400, so gate on the endpoint —
// the same native-only policy CacheMiddleware already applies. Server-side prefix
// caching still works on the OAuth backend without these params.
if isOpenAINativeEndpoint(p.apiBase) {
if cacheKey, ok := req.Options[OptPromptCacheKey]; ok {
body["prompt_cache_key"] = cacheKey
}
if retention, ok := req.Options[OptPromptCacheRetention]; ok {
body["prompt_cache_retention"] = retention
}
}
return body
+18 -6
View File
@@ -1131,21 +1131,33 @@ func TestCodexBuildRequestBody_NilFunction_HandlesGracefully(t *testing.T) {
}
func TestCodexBuildRequestBodyPromptCacheControls(t *testing.T) {
p := NewCodexProvider("test", &staticTokenSource{token: "tok"}, "", "gpt-4o")
body := p.buildRequestBody(ChatRequest{
req := ChatRequest{
Messages: []Message{{Role: "user", Content: "Hello"}},
Options: map[string]any{
OptPromptCacheKey: "agent/session/provider",
OptPromptCacheRetention: "24h",
},
}, true)
}
// Native OpenAI endpoint accepts prompt cache params.
native := NewCodexProvider("test", &staticTokenSource{token: "tok"}, "https://api.openai.com/v1", "gpt-4o")
body := native.buildRequestBody(req, true)
if got := body["prompt_cache_key"]; got != "agent/session/provider" {
t.Fatalf("prompt_cache_key = %v, want agent/session/provider", got)
t.Fatalf("native prompt_cache_key = %v, want agent/session/provider", got)
}
if got := body["prompt_cache_retention"]; got != "24h" {
t.Fatalf("prompt_cache_retention = %v, want 24h", got)
t.Fatalf("native prompt_cache_retention = %v, want 24h", got)
}
// ChatGPT subscription OAuth backend (default apiBase) rejects these params
// with HTTP 400, so they must be omitted.
oauth := NewCodexProvider("test", &staticTokenSource{token: "tok"}, "", "gpt-4o")
oauthBody := oauth.buildRequestBody(req, true)
if _, ok := oauthBody["prompt_cache_key"]; ok {
t.Fatal("prompt_cache_key must not be sent to the ChatGPT OAuth backend")
}
if _, ok := oauthBody["prompt_cache_retention"]; ok {
t.Fatal("prompt_cache_retention must not be sent to the ChatGPT OAuth backend")
}
}