From 110ef579e7adf7761abf17d37fbb343c450687e2 Mon Sep 17 00:00:00 2001 From: bitkyc08-arch Date: Tue, 18 Aug 2026 15:42:04 +0900 Subject: [PATCH 1/2] release: v2.25.0 --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index f4e8bbd5c9..178d6a9c7d 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@bitkyc08/opencodex", - "version": "2.24.2", + "version": "2.25.0", "description": "Universal provider proxy for OpenAI Codex & Claude Code — use any LLM with Codex CLI/App/SDK and Claude Code", "type": "module", "main": "./bin/package-main.mjs", From 613d19db4e24b688ec7ad44f25036695a1634075 Mon Sep 17 00:00:00 2001 From: yzxcj797 Date: Wed, 19 Aug 2026 12:15:52 +0800 Subject: [PATCH 2/2] fix(responses): never send prompt_cache_retention to gpt-5.6 models The ChatGPT codex backend rejects prompt_cache_retention with {"detail":"Unsupported parameter: prompt_cache_retention"} on gpt-5.6 models (gpt-5.6-luna / gpt-5.6-sol), aborting the whole agent turn mid-run. The parameter is emitted client-side by some Codex App builds - it does not exist anywhere in codex-rs - and request bodies are forwarded opaquely, so one bad field kills the turn (#2092). Strip the field on the forward path, scoped to gpt-5.6 model ids: the backend's cache handling is account-level and has provably varied by deployment (one accepted "24h" and echoed it back), so a global strip would silently drop a parameter a deployment honors. Non-gpt-5.6 models keep the existing preserve behavior. --- src/adapters/openai-responses.ts | 23 ++++++++++++ tests/openai-responses-passthrough.test.ts | 43 ++++++++++++++++++++++ 2 files changed, 66 insertions(+) diff --git a/src/adapters/openai-responses.ts b/src/adapters/openai-responses.ts index 27268a481b..8ede2e3531 100644 --- a/src/adapters/openai-responses.ts +++ b/src/adapters/openai-responses.ts @@ -815,6 +815,28 @@ function stripUnsupportedForwardParams(body: unknown): unknown { return rest; } +/** + * The ChatGPT codex backend rejects `prompt_cache_retention` with + * `{"detail":"Unsupported parameter: prompt_cache_retention"}` on gpt-5.6 + * models (gpt-5.6-luna / gpt-5.6-sol), aborting the whole agent turn mid-run. + * The parameter is emitted client-side by some Codex App builds - it does not + * exist anywhere in codex-rs - and request bodies are forwarded opaquely, so + * one bad field kills the turn (#2092). + * + * Other models keep the field: the backend's cache handling is account-level + * and has provably varied by deployment (one accepted "24h" and echoed it + * back), so stripping globally would silently drop a parameter a deployment + * honors. Model-scoped, matching the report's invariant: never send it to + * gpt-5.6. + */ +function stripPromptCacheRetentionForGpt56(body: unknown, modelId: string | undefined): unknown { + if (modelId === undefined || !modelId.startsWith("gpt-5.6")) return body; + if (!isPlainObject(body)) return body; + if (!Object.prototype.hasOwnProperty.call(body, "prompt_cache_retention")) return body; + const { prompt_cache_retention: _pcr, ...rest } = body; + return rest; +} + const IMAGE_GEN_NAMESPACE = "image_gen"; const HOSTED_IMAGE_GENERATION_TOOL = "image_generation"; const IMAGE_GEN_DOTTED_PREFIX = `${IMAGE_GEN_NAMESPACE}.`; @@ -1382,6 +1404,7 @@ export function createResponsesPassthroughAdapter(provider: OcxProviderConfig): } if (forward) { outBody = stripUnsupportedForwardParams(outBody); + outBody = stripPromptCacheRetentionForGpt56(outBody, parsed.modelId); } else { outBody = preferConfiguredHostedTools( outBody, diff --git a/tests/openai-responses-passthrough.test.ts b/tests/openai-responses-passthrough.test.ts index c2e9d38a71..504c754a99 100644 --- a/tests/openai-responses-passthrough.test.ts +++ b/tests/openai-responses-passthrough.test.ts @@ -1982,6 +1982,49 @@ describe("OpenAI Responses forward-mode unsupported param stripping", () => { expect(body.model).toBe("gpt-5.6-sol"); }); + + + test("forward mode strips prompt_cache_retention for gpt-5.6 models (#2092)", () => { + const adapter = createResponsesPassthroughAdapter(provider); + for (const modelId of ["gpt-5.6-luna", "gpt-5.6-sol"]) { + const request = adapter.buildRequest({ + modelId, + context: { messages: [] }, + stream: true, + options: {}, + _rawBody: { + model: modelId, + input: [{ role: "user", content: [{ type: "input_text", text: "ping" }] }], + stream: true, + store: false, + prompt_cache_retention: "24h", + }, + }, meta); + const body = JSON.parse(request.body) as Record; + expect(body).not.toHaveProperty("prompt_cache_retention"); + expect(body.model).toBe(modelId); + } + }); + + test("forward mode keeps prompt_cache_retention for non-gpt-5.6 models (#2092)", () => { + 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", + }, + }, meta); + const body = JSON.parse(request.body) as Record; + // The backend's cache handling varies by deployment and has accepted this + // field before; only gpt-5.6 provably rejects it, so it stays. + expect(body.prompt_cache_retention).toBe("24h"); + }); + test("forward mode is a no-op when neither field is present", () => { const adapter = createResponsesPassthroughAdapter(provider); const { max_output_tokens: _m, metadata: _d, ...codexBody } = rawBody;