From 61ee2e9717ae70c03069cca9a64fd873aa90c256 Mon Sep 17 00:00:00 2001 From: luvs01 <27862058+luvs01@users.noreply.github.com> Date: Wed, 19 Aug 2026 11:47:36 +0900 Subject: [PATCH] fix(responses): strip unsupported cache retention --- src/adapters/openai-responses.ts | 17 ++++++++++---- tests/openai-responses-passthrough.test.ts | 27 ++++++++++++++++++++-- 2 files changed, 38 insertions(+), 6 deletions(-) diff --git a/src/adapters/openai-responses.ts b/src/adapters/openai-responses.ts index a8bbab8bfd..bd4959d797 100644 --- a/src/adapters/openai-responses.ts +++ b/src/adapters/openai-responses.ts @@ -909,15 +909,24 @@ function stripStatefulResponsesParams(body: unknown): unknown { * with `{"detail":"Unsupported parameter: …"}` (strict allowlist). Codex CLI never * sends these — it controls output length via `reasoning.effort` — but third-party * Responses API clients (GJC, SDK wrappers) include `max_output_tokens` per the - * public spec. `metadata` is likewise absent from the allowlist. No-op when the - * body carries neither field, keeping the common Codex path allocation-free. + * public spec. `metadata` is likewise absent from the allowlist. Codex can also + * send `prompt_cache_retention`, but the ChatGPT backend rejects it for models + * that do not expose extended retention instead of falling back to normal cache + * retention. No-op when the body carries none of these fields, keeping the common + * Codex path allocation-free. API-key Responses requests retain all three fields. */ function stripUnsupportedForwardParams(body: unknown): unknown { if (!isPlainObject(body)) return body; const hasMot = Object.prototype.hasOwnProperty.call(body, "max_output_tokens"); const hasMeta = Object.prototype.hasOwnProperty.call(body, "metadata"); - if (!hasMot && !hasMeta) return body; - const { max_output_tokens: _mot, metadata: _meta, ...rest } = body; + const hasPromptCacheRetention = Object.prototype.hasOwnProperty.call(body, "prompt_cache_retention"); + if (!hasMot && !hasMeta && !hasPromptCacheRetention) return body; + const { + max_output_tokens: _mot, + metadata: _meta, + prompt_cache_retention: _promptCacheRetention, + ...rest + } = body; return rest; } diff --git a/tests/openai-responses-passthrough.test.ts b/tests/openai-responses-passthrough.test.ts index c2e9d38a71..bff02a5aae 100644 --- a/tests/openai-responses-passthrough.test.ts +++ b/tests/openai-responses-passthrough.test.ts @@ -804,8 +804,13 @@ describe("OpenAI Responses passthrough sanitization", () => { expect(body.prompt_cache_key).toBe("project-cache-v1"); }); - test("preserves prompt_cache_retention in the raw Responses passthrough body", () => { - const adapter = createResponsesPassthroughAdapter(provider); + test("preserves prompt_cache_retention for OpenAI API-key Responses requests", () => { + const adapter = createResponsesPassthroughAdapter({ + adapter: "openai-responses", + baseUrl: "https://api.openai.com/v1", + authMode: "key", + apiKey: "sk-test", + }); const request = adapter.buildRequest({ modelId: "gpt-5.5", context: { messages: [] }, @@ -822,6 +827,24 @@ describe("OpenAI Responses passthrough sanitization", () => { expect(body.prompt_cache_retention).toBe("24h"); }); + test("strips prompt_cache_retention from ChatGPT forward requests", () => { + const adapter = createResponsesPassthroughAdapter(provider); + const request = adapter.buildRequest({ + modelId: "gpt-5.5", + context: { messages: [] }, + stream: true, + options: {}, + _rawBody: { + model: "gpt-5.5", + input: "hi", + prompt_cache_retention: "24h", + }, + }, { headers: new Headers({ authorization: "Bearer token" }) }); + const body = JSON.parse(request.body) as { prompt_cache_retention?: string }; + + expect(body).not.toHaveProperty("prompt_cache_retention"); + }); + const expandedRawBody = { model: "gpt-5.5", previous_response_id: "resp_1",