From eb5bfbfb11c598826b0d2da3255347a244004e0e Mon Sep 17 00:00:00 2001 From: roboomp Date: Sat, 1 Aug 2026 03:45:49 +0000 Subject: [PATCH] fix(ai): honored disabled Codex cache retention Routed Codex prompt cache identity through the shared retention-aware resolver while preserving transport session identity. Covered explicit and environment-derived opt-outs, option precedence, direct body construction, and transport headers. Fixes #7219 --- packages/ai/CHANGELOG.md | 4 ++ .../src/providers/openai-codex-responses.ts | 5 ++- packages/ai/test/openai-codex-stream.test.ts | 43 +++++++++++++++++++ 3 files changed, 50 insertions(+), 2 deletions(-) diff --git a/packages/ai/CHANGELOG.md b/packages/ai/CHANGELOG.md index 0dc86d6e0..a43b95666 100644 --- a/packages/ai/CHANGELOG.md +++ b/packages/ai/CHANGELOG.md @@ -2,6 +2,10 @@ ## [Unreleased] +### Fixed + +- Fixed OpenAI Codex Responses ignoring disabled cache retention when deriving `prompt_cache_key`, while preserving transport session identity ([#7219](https://github.com/can1357/oh-my-pi/issues/7219)). + ## [17.2.2] - 2026-07-31 ### Added diff --git a/packages/ai/src/providers/openai-codex-responses.ts b/packages/ai/src/providers/openai-codex-responses.ts index 20751703c..a58409222 100644 --- a/packages/ai/src/providers/openai-codex-responses.ts +++ b/packages/ai/src/providers/openai-codex-responses.ts @@ -111,6 +111,7 @@ import { finalizePendingResponsesToolCalls, finalizeReasoningThinking, finalizeToolCallArgumentsDone, + getOpenAIPromptCacheKey, hasExecutableIncompleteResponsesToolCalls, isOpenAIResponsesProgressEvent, mapOpenAIResponsesStopReason, @@ -1373,7 +1374,7 @@ async function buildCodexRequestContext( const accountId = getCodexAccountId(apiKey); const baseUrl = model.baseUrl || CODEX_BASE_URL; const url = resolveCodexResponsesUrl(baseUrl); - const promptCacheKey = normalizeOpenAIPromptCacheKey(options?.promptCacheKey ?? options?.sessionId); + const promptCacheKey = getOpenAIPromptCacheKey(options); const transportSessionId = normalizeOpenAIPromptCacheKey(options?.sessionId); const codexClientVersion = CODEX_CLIENT_VERSION; const transformedBody = await buildTransformedCodexRequestBody(model, context, options, promptCacheKey); @@ -1463,7 +1464,7 @@ export async function buildTransformedCodexRequestBody( model: Model<"openai-codex-responses">, context: Context, options: OpenAICodexResponsesOptions | undefined, - promptCacheKey = normalizeOpenAIPromptCacheKey(options?.promptCacheKey ?? options?.sessionId), + promptCacheKey = getOpenAIPromptCacheKey(options), ): Promise { const params: RequestBody = { model: model.requestModelId ?? model.id, diff --git a/packages/ai/test/openai-codex-stream.test.ts b/packages/ai/test/openai-codex-stream.test.ts index 11fb57a38..b0a372ef1 100644 --- a/packages/ai/test/openai-codex-stream.test.ts +++ b/packages/ai/test/openai-codex-stream.test.ts @@ -2,6 +2,7 @@ import { afterEach, beforeEach, describe, expect, it, vi } from "bun:test"; import { scheduler } from "node:timers/promises"; import { streamSimple } from "@oh-my-pi/pi-ai"; import { + buildTransformedCodexRequestBody, getOpenAICodexTransportDetails, getOpenAICodexWebSocketDebugStats, prewarmOpenAICodexResponses, @@ -19,6 +20,7 @@ import type { import { __resetProxyCache } from "@oh-my-pi/pi-ai/utils/proxy"; import { buildModel } from "@oh-my-pi/pi-catalog/build"; import * as piUtils from "@oh-my-pi/pi-utils"; +import { withEnv } from "./helpers"; const { getAgentDir, setAgentDir, TempDir } = piUtils; @@ -2369,6 +2371,47 @@ describe("openai-codex streaming", () => { expect(capturedHeaders?.get("session_id")).toBe(sessionId); expect(capturedHeaders?.get("x-client-request-id")).toBe(sessionId); expect(capturedBody?.prompt_cache_key).toBe(promptCacheKey); + + await streamOpenAICodexResponses(model, createCodexTestContext(), { + fetch: fetchMock as FetchImpl, + apiKey: token, + sessionId, + promptCacheKey, + cacheRetention: "none", + }).result(); + + expect(capturedHeaders?.get("conversation_id")).toBe(sessionId); + expect(capturedHeaders?.get("session_id")).toBe(sessionId); + expect(capturedHeaders?.get("x-client-request-id")).toBe(sessionId); + expect(capturedBody?.prompt_cache_key).toBeUndefined(); + }); + it("applies cache retention resolution to direct Codex request body construction", async () => { + const model = createCodexTestModel(); + const context = createCodexTestContext(); + const disabledPromptKey = await buildTransformedCodexRequestBody(model, context, { + promptCacheKey: "disabled-cache", + cacheRetention: "none", + }); + const disabledSession = await buildTransformedCodexRequestBody(model, context, { + sessionId: "disabled-session", + cacheRetention: "none", + }); + + expect(disabledPromptKey.prompt_cache_key).toBeUndefined(); + expect(disabledSession.prompt_cache_key).toBeUndefined(); + + await withEnv({ PI_CACHE_RETENTION: "none" }, async () => { + const disabledEnvironment = await buildTransformedCodexRequestBody(model, context, { + sessionId: "environment-session", + }); + const explicitShort = await buildTransformedCodexRequestBody(model, context, { + promptCacheKey: "explicit-cache", + cacheRetention: "short", + }); + + expect(disabledEnvironment.prompt_cache_key).toBeUndefined(); + expect(explicitShort.prompt_cache_key).toBe("explicit-cache"); + }); }); it("omits unsupported sampling keys (temperature/top_p/top_k/min_p/penalties) from the Codex Responses body", async () => {