Skip to content

Comments

Chore/codex max release review#31

Merged
riatzukiza merged 17 commits intostagingfrom
chore/codex-max-release-review
Nov 20, 2025
Merged

Chore/codex max release review#31
riatzukiza merged 17 commits intostagingfrom
chore/codex-max-release-review

Conversation

@riatzukiza
Copy link
Collaborator

No description provided.

@coderabbitai
Copy link
Contributor

coderabbitai bot commented Nov 20, 2025

Caution

Review failed

The pull request is closed.

Summary by CodeRabbit

  • New Features

    • Added error notifications via plugin toasts for better error visibility.
    • Implemented configuration caching to reduce file system lookups.
  • Bug Fixes

    • Improved OAuth token refresh to properly use updated credentials.
    • Enhanced response handling with automatic recovery on errors.
    • Fixed prompt cache key preservation across multi-turn interactions.
  • Improvements

    • Refined session management with better fork handling and prefix tracking.
    • Enhanced streaming response structure for improved reliability.

✏️ Tip: You can customize this high-level summary in your review settings.

Walkthrough

JWT decoding now accepts URL-safe base64 with padding; codex-metrics moved to structured SSE streaming; cache metrics now return cloned objects; request transformation threads sessionContext and can inject session prompt_cache_key; session-manager adds prefix-aware forking and smarter session lookup; config loading gains memoized cache with forceReload; assorted tests and small refactors updated.

Changes

Cohort / File(s) Summary
JWT & Auth
lib/auth/auth.ts, test/auth.test.ts
Decode JWT payload by normalizing base64url (-+, _/) and padding to multiple of 4 before base64-decoding; add test for unpadded base64url.
SSE Streaming
lib/commands/codex-metrics.ts
Replace single-payload SSE emission with structured streaming helpers (buildAssistantMessage, buildResponsePayload, buildSseEvents) and return a streaming Response via createSsePayload.
Cache Metrics & Warming
lib/cache/cache-metrics.ts, lib/cache/cache-warming.ts
Add cloneMetrics() that deep-ish clones per-metric objects; getMetrics() returns cloned data; replace named catch with bare catch in areCachesWarm().
Compaction
lib/compaction/compaction-executor.ts, lib/compaction/codex-compaction.ts
Use response.clone() and guarded try/catch in finalize flow; delegate cloning/text extraction to shared cloneInputItems/extractTextFromItem.
Request / Fetch Transformation
lib/request/fetch-helpers.ts, lib/request/request-transformer.ts, tests under test/*request-transformer*.test.ts, test/fetch-helpers.test.ts
Thread sessionContext into transformRequestBody; inject session prompt_cache_key when host omits it; export TransformResult; add helpers (buildBridgeMessage, applyCompactionIfNeeded, transformInputForCodex); transformRequestForCodex now returns serialized updatedInit.
Session Management / Forking
lib/session/session-manager.ts, test/session-manager.test.ts
extractLatestUserSlice returns cloned tail from last user message including following assistant items; add longestSharedPrefixLength, buildPrefixForkIds, findExistingSession; fork or reuse sessions and update body.prompt_cache_key as needed.
Config Caching
lib/config.ts, test/plugin-config.test.ts
Add module-level cache and loadPluginConfig(options?: { forceReload?: boolean } = {}) with memoization; tests updated to exercise forceReload and cached reuse.
Auth Browser & Server
lib/auth/browser.ts, lib/auth/server.ts
Replace named catch bindings with bare catch; server handler extracts response writing into a local send() helper to standardize responses.
Logger
lib/logger.ts
Add test-environment guard to suppress console IO in tests; emit triggers toast/notification on errors via app.notify/app.toast when available.
Prompts / Cache Migration
lib/prompts/opencode-codex.ts
Change migrateLegacyCache to accept no arguments (internalize path resolution); reformat prompt cache payload structure.
Codex Fetcher & Auth Refresh
lib/request/codex-fetcher.ts, test/codex-fetcher.test.ts
Make currentAuth mutable and reassign after token refresh so refreshed credentials are used; tests validate header creation with refreshed token.
Misc & Tests
lib/cache/session-cache.ts, lib/request/response-handler.ts, test/*.test.ts, test/README.md, test/server.test.ts
Small refactors (one-line returns, bare catches), SSE parse catch simplification, README header edits, extended test mocks, and new tests for added behaviors.

Sequence Diagram(s)

sequenceDiagram
    autonumber
    actor Client
    participant Cmd as codex-metrics
    participant SSE as SSE Helpers
    participant Stream as ResponseStream

    Client->>Cmd: createStaticResponse(req)
    Cmd->>Cmd: buildAssistantMessage(...)
    Cmd->>Cmd: buildResponsePayload(...)
    Cmd->>SSE: buildSseEvents(payload)
    SSE->>SSE: createSsePayload(events)
    SSE-->>Stream: stream SSE chunks
    Stream-->>Client: streaming SSE response
Loading
sequenceDiagram
    autonumber
    actor Caller
    participant Fetch as fetch-helpers
    participant Transformer as request-transformer
    participant Session

    Caller->>Fetch: prepare request (with optional sessionContext)
    Fetch->>Transformer: transformRequestBody(init, body, sessionContext)
    Transformer->>Session: consult/inject prompt_cache_key, bridge message, compaction
    Transformer-->>Fetch: TransformResult { body, updatedInit, sessionContext }
    Fetch-->>Caller: serialized updatedInit (ready to fetch)
Loading
sequenceDiagram
    autonumber
    participant Executor as compaction-executor
    participant Orig as originalResponse
    participant Clone as clonedResponse
    participant Session

    Executor->>Orig: resp.clone()
    Orig-->>Clone: cloned response body
    Executor->>Executor: try parse & compute compaction summary
    alt success
      Executor->>Session: apply compaction summary
      Executor->>Executor: return new Response(updatedPayload, status, headers)
    else error
      Executor->>Executor: catch -> return original response unchanged
    end
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~60 minutes

  • High-attention areas / potential blockers:
    • lib/commands/codex-metrics.ts — ensure SSE event ordering, payload framing, and Response streaming headers are correct for clients expecting SSE.
    • lib/session/session-manager.ts — correctness of prefix hashing, fork id generation, findExistingSession matching and prompt_cache_key mutation (risk of collisions or incorrect reuse).
    • lib/request/request-transformer.ts & lib/request/fetch-helpers.ts — verify new TransformResult export, sessionContext threading, and that updatedInit.body serialization preserves headers/encodings.
    • lib/cache/cache-metrics.ts — confirm cloning depth is sufficient to prevent accidental mutation by callers.
    • lib/logger.ts — side-effects from notifyToast when app.notify/app.toast absent or failing.

Possibly related issues

Possibly related PRs

Poem

🧩 A token padded, safe and new,
Streams now whisper in tidy queue,
Cloned metrics hum without a scar,
Sessions fork and carry prompt keys far,
Tests nod — the little fixes flew.

Pre-merge checks and finishing touches

❌ Failed checks (2 warnings, 1 inconclusive)
Check name Status Explanation Resolution
Description check ⚠️ Warning No pull request description was provided by the author, making it impossible to assess whether the description relates to the changeset. Add a clear pull request description explaining the purpose of the changes, key behavioral modifications, and any breaking changes or migration notes for reviewers.
Docstring Coverage ⚠️ Warning Docstring coverage is 22.58% which is insufficient. The required threshold is 80.00%. You can run @coderabbitai generate docstrings to improve docstring coverage.
Title check ❓ Inconclusive The title 'Chore/codex max release review' is vague and does not clearly convey the main changes. It uses generic terminology that does not meaningfully describe what the PR accomplishes. Revise the title to be more specific and descriptive of the primary changes, such as 'Fix prompt cache key handling and session prefix logic' or 'Refactor cache handling and request transformation logic'.

📜 Recent review details

Configuration used: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between bb5c250 and 1d6697b.

⛔ Files ignored due to path filters (1)
  • spec/pr-31-unresolved-threads.md is excluded by none and included by none
📒 Files selected for processing (3)
  • lib/logger.ts (2 hunks)
  • lib/request/fetch-helpers.ts (5 hunks)
  • test/fetch-helpers.test.ts (6 hunks)

Comment @coderabbitai help to get the list of available commands and usage tips.

@riatzukiza riatzukiza changed the base branch from main to staging November 20, 2025 00:57
Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 3

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (6)
lib/session/session-manager.ts (1)

22-51: Consider renaming function to reflect assistant message inclusion.

The function name extractLatestUserSlice is now misleading since it includes both user and assistant messages in the tail. Consider renaming to something like extractConversationTail or extractLatestUserAssistantSlice to better reflect the current behavior.

test/README.md (1)

35-55: Doc updates look good; consider refreshing the test file list

The per-file headings and timing language are now more maintainable and realistic. As a follow-up (non-blocking), it may be worth updating the “Test Structure” tree to include newer suites like fetch-helpers.test.ts and request-transformer-tools-normalization.test.ts so the overview matches the actual test layout.

lib/cache/cache-metrics.ts (1)

234-261: Consider gating recommendations on sufficient traffic

The getCachePerformanceReport() heuristics will recommend increasing TTL even when totalRequests is near zero (hit rate 0% but not yet meaningful). You might make the “increase cache TTL” suggestion conditional on a minimum totalRequests threshold (similar to the low-usage check) to avoid noisy guidance on fresh or rarely used instances.

lib/commands/codex-metrics.ts (1)

164-261: Optional: align events more closely with full Responses SSE patterns

The sequence of events (response.created → single response.output_text.delta → item added/done → response.completed) is sufficient for these commands. If downstream consumers ever rely on response.output_text.done events (as they often do with standard Responses streams), you might consider emitting that as a final text event for closer parity, but it’s not a blocker given the current usage.

lib/request/request-transformer.ts (1)

1014-1044: Blocker: cache-key logging inside transformRequestBody pushes cognitive complexity over limit

The new isNewSession-aware logging for generated prompt cache keys is functionally sound, but it contributes to the SonarJS cognitive-complexity error on transformRequestBody (reported as 34 > 30). This will keep CI red unless you either adjust lint rules or refactor.

A low-risk way to bring complexity down is to extract the cache-key logging into a dedicated helper that takes the PromptCacheKeyResult and sessionContext, and then call it from transformRequestBody. That keeps behaviour identical while simplifying the main function.

For example:

@@
-function ensurePromptCacheKey(body: RequestBody): PromptCacheKeyResult {
+function ensurePromptCacheKey(body: RequestBody): PromptCacheKeyResult {
   …
 }
+
+function logPromptCacheKeyHandling(
+  result: PromptCacheKeyResult,
+  sessionContext?: SessionContext,
+): void {
+  const isNewSession = Boolean(sessionContext?.isNew);
+
+  if (result.source === "existing") {
+    return;
+  }
+
+  if (result.source === "metadata") {
+    logDebug("Prompt cache key missing; derived from metadata", {
+      promptCacheKey: result.key,
+      sourceKey: result.sourceKey,
+      forkSourceKey: result.forkSourceKey,
+      forkHintKeys: result.forkHintKeys,
+    });
+    return;
+  }
+
+  if (result.source === "generated") {
+    const hasHints = Boolean(
+      (result.hintKeys && result.hintKeys.length > 0) ||
+        (result.forkHintKeys && result.forkHintKeys.length > 0),
+    );
+    const message = hasHints
+      ? "Prompt cache key hints detected but unusable; generated fallback cache key"
+      : "Prompt cache key missing; generated fallback cache key";
+    const logPayload = {
+      promptCacheKey: result.key,
+      fallbackHash: result.fallbackHash,
+      hintKeys: result.hintKeys,
+      unusableKeys: result.unusableKeys,
+      forkHintKeys: result.forkHintKeys,
+      forkUnusableKeys: result.forkUnusableKeys,
+    };
+
+    if (!hasHints && isNewSession) {
+      logInfo(message, logPayload);
+    } else {
+      logWarn(message, logPayload);
+    }
+  }
+}
@@
-  // Ensure prompt_cache_key is set using our robust logic
-  const cacheKeyResult = ensurePromptCacheKey(body);
-  const isNewSession = Boolean(sessionContext?.isNew);
-  if (cacheKeyResult.source === "existing") {
-    // Host provided a valid cache key, use it as-is
-  } else if (cacheKeyResult.source === "metadata") {
-    logDebug("Prompt cache key missing; derived from metadata", {
-      promptCacheKey: cacheKeyResult.key,
-      sourceKey: cacheKeyResult.sourceKey,
-      forkSourceKey: cacheKeyResult.forkSourceKey,
-      forkHintKeys: cacheKeyResult.forkHintKeys,
-    });
-  } else if (cacheKeyResult.source === "generated") {
-    const hasHints = Boolean(
-      (cacheKeyResult.hintKeys && cacheKeyResult.hintKeys.length > 0) ||
-        (cacheKeyResult.forkHintKeys && cacheKeyResult.forkHintKeys.length > 0),
-    );
-    const message = hasHints
-      ? "Prompt cache key hints detected but unusable; generated fallback cache key"
-      : "Prompt cache key missing; generated fallback cache key";
-    const logPayload = {
-      promptCacheKey: cacheKeyResult.key,
-      fallbackHash: cacheKeyResult.fallbackHash,
-      hintKeys: cacheKeyResult.hintKeys,
-      unusableKeys: cacheKeyResult.unusableKeys,
-      forkHintKeys: cacheKeyResult.forkHintKeys,
-      forkUnusableKeys: cacheKeyResult.forkUnusableKeys,
-    };
-    if (!hasHints && isNewSession) {
-      logInfo(message, logPayload);
-    } else {
-      logWarn(message, logPayload);
-    }
-  }
+  // Ensure prompt_cache_key is set using our robust logic
+  const cacheKeyResult = ensurePromptCacheKey(body);
+  logPromptCacheKeyHandling(cacheKeyResult, sessionContext);

This should reduce transformRequestBody’s complexity and clear the SonarJS blocker while preserving your new info-vs-warn behaviour for new sessions.

lib/request/fetch-helpers.ts (1)

20-21: Remove unused InputItem import to satisfy lint

InputItem is imported from ../types.js but never used in this module, which triggers the @typescript-eslint/no-unused-vars warning.

You can fix it by dropping the unused type:

-import type { InputItem, PluginConfig, RequestBody, SessionContext, UserConfig } from "../types.js";
+import type { PluginConfig, RequestBody, SessionContext, UserConfig } from "../types.js";
📜 Review details

Configuration used: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between fedb230 and 80995c0.

⛔ Files ignored due to path filters (2)
  • docs/notes/2025.11.19.18.38.24.md is excluded by none and included by none
  • spec/prompt-cache-warning.md is excluded by none and included by none
📒 Files selected for processing (15)
  • lib/auth/auth.ts (1 hunks)
  • lib/cache/cache-metrics.ts (1 hunks)
  • lib/cache/cache-warming.ts (1 hunks)
  • lib/commands/codex-metrics.ts (3 hunks)
  • lib/compaction/codex-compaction.ts (2 hunks)
  • lib/compaction/compaction-executor.ts (1 hunks)
  • lib/request/fetch-helpers.ts (2 hunks)
  • lib/request/request-transformer.ts (7 hunks)
  • lib/session/session-manager.ts (2 hunks)
  • test/README.md (6 hunks)
  • test/auth.test.ts (1 hunks)
  • test/config.test.ts (1 hunks)
  • test/fetch-helpers.test.ts (1 hunks)
  • test/request-transformer-tools-normalization.test.ts (1 hunks)
  • test/request-transformer.test.ts (3 hunks)
🧰 Additional context used
🧬 Code graph analysis (9)
test/request-transformer-tools-normalization.test.ts (1)
lib/request/request-transformer.ts (1)
  • TransformRequestOptions (940-951)
test/auth.test.ts (1)
lib/auth/auth.ts (1)
  • decodeJWT (107-119)
lib/session/session-manager.ts (3)
lib/utils/input-item-utils.ts (2)
  • isUserMessage (80-82)
  • isAssistantMessage (89-91)
lib/types.ts (1)
  • InputItem (136-142)
lib/utils/clone.ts (1)
  • cloneInputItems (30-35)
lib/request/request-transformer.ts (4)
lib/types.ts (1)
  • InputItem (136-142)
lib/prompts/codex-opencode-bridge.ts (1)
  • CODEX_OPENCODE_BRIDGE (11-125)
lib/cache/prompt-fingerprinting.ts (1)
  • generateContentHash (15-17)
lib/logger.ts (2)
  • logInfo (92-94)
  • logWarn (96-98)
lib/compaction/codex-compaction.ts (1)
lib/utils/clone.ts (1)
  • cloneInputItems (30-35)
lib/compaction/compaction-executor.ts (1)
lib/compaction/codex-compaction.ts (1)
  • createSummaryMessage (108-118)
test/request-transformer.test.ts (2)
lib/types.ts (2)
  • RequestBody (147-169)
  • SessionContext (199-205)
lib/request/request-transformer.ts (1)
  • transformRequestBody (958-1125)
test/config.test.ts (1)
lib/request/request-transformer.ts (1)
  • getReasoningConfig (290-358)
lib/request/fetch-helpers.ts (1)
lib/request/request-transformer.ts (1)
  • transformRequestBody (958-1125)
🪛 GitHub Actions: CI
lib/request/request-transformer.ts

[warning] 91-91: Function 'normalizeToolsForResponses' has too many lines (114). Maximum allowed is 80 max-lines-per-function


[warning] 106-106: Unexpected block statement surrounding arrow body; move the returned value immediately after the => arrow-body-style


[warning] 137-137: Arrow function has a complexity of 32. Maximum allowed is 20 complexity


[warning] 290-290: Function 'getReasoningConfig' has a complexity of 39. Maximum allowed is 20 complexity


[warning] 461-461: Async function 'filterOpenCodeSystemPrompts' has too many lines (84). Maximum allowed is 80 max-lines-per-function


[warning] 654-654: Assignment to property of function parameter 'sessionContext' no-param-reassign


[warning] 694-694: File has too many lines (851). Maximum allowed is 500 max-lines


[warning] 893-893: Assignment to property of function parameter 'body' no-param-reassign


[warning] 905-905: Assignment to property of function parameter 'body' no-param-reassign


[warning] 919-919: Assignment to property of function parameter 'body' no-param-reassign


[warning] 958-958: Async function 'transformRequestBody' has too many lines (127). Maximum allowed is 80 max-lines-per-function


[warning] 958-958: Async function 'transformRequestBody' has a complexity of 38. Maximum allowed is 20 complexity


[error] 958-958: Refactor this function to reduce its Cognitive Complexity from 34 to the 30 allowed sonarjs/cognitive-complexity

lib/request/fetch-helpers.ts

[warning] 20-20: 'InputItem' is defined but never used. Allowed unused vars must match /^_/u @typescript-eslint/no-unused-vars


[warning] 70-70: Assignment to property of function parameter 'currentAuth' no-param-reassign


[warning] 71-71: Assignment to property of function parameter 'currentAuth' no-param-reassign


[warning] 72-72: Assignment to property of function parameter 'currentAuth' no-param-reassign


[warning] 107-107: Async function 'transformRequestForCodex' has too many lines (91). Maximum allowed is 80 max-lines-per-function


[warning] 107-107: Async function 'transformRequestForCodex' has a complexity of 23. Maximum allowed is 20 complexity


[warning] 247-247: Async function 'handleErrorResponse' has a complexity of 21. Maximum allowed is 20 complexity

🔇 Additional comments (22)
lib/compaction/codex-compaction.ts (2)

3-4: LGTM: Clean consolidation of shared utilities.

The imports properly bring in centralized helper functions for cloning and text extraction, improving code maintainability and reducing duplication.


120-130: LGTM: Proper use of shared cloning utility.

The refactor correctly delegates to cloneInputItems for deep cloning the tail slice, maintaining immutability while consolidating clone logic in a shared helper.

lib/compaction/compaction-executor.ts (4)

30-30: LGTM: Correct response cloning pattern.

Cloning the response before consumption enables clean fallback to the original response in the error path.


32-35: LGTM: Proper error handling for response parsing.

The try-catch structure safely guards against JSON parsing failures and uses the cloned response, preserving the original for fallback.


46-61: LGTM: Safe metadata merging and session updates.

The metadata spread preserves existing fields while adding compaction details. Session updates are properly guarded by checking both manager and context availability.


63-71: Silent error handling provides clean fallback.

The reconstruction properly preserves the original response's headers, status, and statusText. The catch block silently returns the original response, which ensures availability but may hide processing errors.

Consider whether error logging would be valuable here for debugging compaction failures, or if the silent fallback is intentional for resilience.

lib/session/session-manager.ts (3)

6-6: Import addition looks correct.

The isAssistantMessage import is properly added to support the new tail construction logic.


27-34: Last user message search logic is correct.

The backward iteration correctly identifies the last user message index and exits early once found.


50-50: Clone usage is appropriate.

Using cloneInputItems ensures the returned tail is a deep copy, preventing unintended mutations of the original array.

lib/cache/cache-warming.ts (1)

113-125: Parameterless catch keeps behavior simple and correct

Dropping the unused error variable is fine here; the function still safely treats any failure as “caches not warm” without changing semantics.

test/request-transformer-tools-normalization.test.ts (1)

6-15: Defaulting options to {} is a good robustness improvement

Always passing a concrete TransformRequestOptions object into transformRequestBody simplifies callers and avoids undefined checks downstream, with no change to existing call sites.

test/auth.test.ts (1)

79-118: New base64url JWT test appropriately covers the updated decoder

This case correctly constructs a padding-stripped base64url payload and verifies decodeJWT returns the original object, matching the new normalization + padding behavior.

test/config.test.ts (1)

133-143: Additional reasoning-config tests align with production logic

These cases accurately reflect getReasoningConfig behavior for gpt-5.1 (defaulting to none) and gpt-5.1-codex (normalizing none to low), strengthening coverage around the new model family.

lib/auth/auth.ts (1)

107-118: JWT decoder now correctly supports base64url payloads

Normalizing -/_ and padding to a multiple of 4 before Buffer.from(..., "base64") makes decodeJWT robust to both standard and URL-safe encodings without changing the null-on-error contract.

test/README.md (1)

88-90: Reworded performance guarantee is appropriate for a growing suite

Shifting from a strict < 250ms guarantee to “designed to stay quick; actual timings may vary” better reflects reality as coverage expands, without losing the intent to keep tests fast.

lib/cache/cache-metrics.ts (1)

99-115: Returning cloned metrics is a solid immutability improvement

Using cloneMetrics() in getMetrics() prevents callers from accidentally mutating internal counters while keeping the implementation simple and type-safe for the current primitive-only shape.

lib/commands/codex-metrics.ts (1)

126-162: SSE-style static response construction is coherent and self-contained

The refactor cleanly factors out message, payload, and event construction and returns a text/event-stream Response that should be easy for the existing response handler to consume, while keeping metadata (command name, ids, timestamps) explicit.

lib/request/request-transformer.ts (2)

599-605: Bridge helper refactor looks correct and keeps behaviour consistent

The new buildBridgeMessage() helper cleanly centralizes the bridge InputItem construction, and both the cached-decision path and the main injection path now share the same implementation. That reduces duplication and makes future changes to the bridge message safer.

No functional issues spotted here.

Also applies to: 632-633, 657-658


953-956: Exporting TransformResult is appropriate and keeps types aligned

Making TransformResult an exported interface matches how transformRequestBody is now consumed (e.g., by transformRequestForCodex) and keeps type information explicit for both body and compactionDecision. This is a straightforward, correct change.

test/fetch-helpers.test.ts (1)

295-300: Nice regression test for immutability of compaction.originalInput

The added expectations that compaction.originalInput is a distinct clone and remains "hello" after mutating body.input[0].content are exactly what we need to guard against accidental in-place mutations in the compaction flow. This lines up with the new cloneInputItems usage in transformRequestForCodex.

lib/request/fetch-helpers.ts (1)

126-137: Compaction + session context wiring into transformRequestBody looks sound

Cloning originalInput via cloneInputItems, deriving manualCommand from that clone, and passing both compaction settings and sessionContext through to transformRequestBody matches the transformer’s new signature and keeps original user input insulated from later mutations or history application.

The conditional applyCompactedHistory call before transformation is also guarded correctly by compactionEnabled && !manualCommand, so behaviour stays predictable.

Also applies to: 156-170

test/request-transformer.test.ts (1)

720-728: Fallback prompt_cache_key generation test is aligned with transformer behaviour

The "generates fallback prompt_cache_key when no identifiers exist" test correctly asserts that, in the absence of any identifiers, transformRequestBody emits a string cache key starting with cache_. This matches the computeFallbackHashForBody/ensurePromptCacheKey behaviour and gives good coverage for the “no metadata” path.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

♻️ Duplicate comments (1)
test/request-transformer.test.ts (1)

804-853: Address the overly strict logging assertion flagged in previous review.

As noted in the past review comment, line 841's blanket expect(logWarnSpy).not.toHaveBeenCalled() is overly strict and will break if any unrelated warnings are added to transformRequestBody. The message-specific assertion at lines 837-840 already guarantees the fallback-cache message isn't logged as a warning.

Remove the blanket assertion and rely on the message-specific check:

 	expect(logWarnSpy).not.toHaveBeenCalledWith(
 		"Prompt cache key missing; generated fallback cache key",
 		expect.anything(),
 	);
-	expect(logWarnSpy).not.toHaveBeenCalled();

 	expect(logInfoSpy).toHaveBeenCalledWith(
📜 Review details

Configuration used: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 63d01e9 and df60966.

⛔ Files ignored due to path filters (8)
  • README.md is excluded by none and included by none
  • docs/development/CONFIG_FIELDS.md is excluded by none and included by none
  • docs/reasoning-effort-levels-update.md is excluded by none and included by none
  • spec/codex-max-release-review.md is excluded by none and included by none
  • spec/double-config-log.md is excluded by none and included by none
  • spec/mutation-score-improvement.md is excluded by none and included by none
  • spec/prompt-cache-warning.md is excluded by none and included by none
  • spec/session-prefix-mismatch.md is excluded by none and included by none
📒 Files selected for processing (4)
  • lib/config.ts (1 hunks)
  • lib/request/request-transformer.ts (6 hunks)
  • test/plugin-config.test.ts (6 hunks)
  • test/request-transformer.test.ts (4 hunks)
🧰 Additional context used
🧬 Code graph analysis (4)
test/request-transformer.test.ts (3)
lib/types.ts (3)
  • InputItem (136-142)
  • SessionContext (199-205)
  • RequestBody (147-169)
lib/request/request-transformer.ts (2)
  • addCodexBridgeMessage (607-666)
  • transformRequestBody (966-1134)
lib/session/session-manager.ts (1)
  • SessionManager (157-435)
lib/request/request-transformer.ts (4)
lib/types.ts (2)
  • InputItem (136-142)
  • SessionContext (199-205)
lib/prompts/codex-opencode-bridge.ts (1)
  • CODEX_OPENCODE_BRIDGE (11-125)
lib/cache/prompt-fingerprinting.ts (3)
  • generateInputHash (124-143)
  • getCachedBridgeDecision (88-101)
  • generateContentHash (15-17)
lib/logger.ts (3)
  • logDebug (88-90)
  • logInfo (92-94)
  • logWarn (96-98)
lib/config.ts (3)
lib/types.ts (1)
  • PluginConfig (6-35)
lib/utils/file-system-utils.ts (1)
  • safeReadFile (56-62)
lib/logger.ts (1)
  • logWarn (96-98)
test/plugin-config.test.ts (1)
lib/config.ts (1)
  • loadPluginConfig (27-62)
🪛 GitHub Actions: CI
lib/request/request-transformer.ts

[error] 966-966: Refactor this function to reduce its Cognitive Complexity from 34 to the 30 allowed sonarjs/cognitive-complexity


[warning] 91-91: Function 'normalizeToolsForResponses' has too many lines (114). Maximum allowed is 80 max-lines-per-function


[warning] 106-106: Unexpected block statement surrounding arrow body; move the returned value immediately after the => arrow-body-style


[warning] 137-137: Arrow function has a complexity of 32. Maximum allowed is 20 complexity


[warning] 290-290: Function 'getReasoningConfig' has a complexity of 39. Maximum allowed is 20 complexity


[warning] 461-461: Async function 'filterOpenCodeSystemPrompts' has too many lines (84). Maximum allowed is 80 max-lines-per-function


[warning] 643-643: Assignment to property of function parameter 'sessionContext' no-param-reassign


[warning] 662-662: Assignment to property of function parameter 'sessionContext' no-param-reassign


[warning] 695-695: File has too many lines (858). Maximum allowed is 500 max-lines


[warning] 901-901: Assignment to property of function parameter 'body' no-param-reassign


[warning] 913-913: Assignment to property of function parameter 'body' no-param-reassign


[warning] 927-927: Assignment to property of function parameter 'body' no-param-reassign


[warning] 966-966: Async function 'transformRequestBody' has too many lines (127). Maximum allowed is 80 max-lines-per-function


[warning] 966-966: Async function 'transformRequestBody' has a complexity of 39. Maximum allowed is 20 complexity

🔇 Additional comments (14)
lib/config.ts (3)

19-19: Cache variable looks good.

The module-level cache is appropriately scoped and typed for memoization.


40-43: Caching DEFAULT_CONFIG when file is missing is correct.

This ensures subsequent calls avoid redundant filesystem checks when the config file doesn't exist.


46-61: Cache assignments are correctly implemented.

Both successful loads and error cases properly cache the configuration, ensuring efficient memoization and avoiding repeated filesystem operations or error handling.

test/plugin-config.test.ts (2)

50-50: Test isolation with forceReload is correct.

Updating all tests to use { forceReload: true } ensures proper test isolation by clearing the module-level cache before each test execution.

Also applies to: 67-67, 81-81, 96-96, 115-115


127-145: Excellent test coverage for memoization.

This test correctly verifies that the configuration is cached after an initial forced reload and that subsequent calls reuse the cache without triggering filesystem operations. The mock clearing strategy ensures accurate validation.

test/request-transformer.test.ts (4)

1-1: LGTM: Test dependencies added for session and logging scenarios.

The new imports support the expanded test coverage for session-aware bridge injection and logging verification.

Also applies to: 13-15


745-792: Excellent integration test for session-driven prompt stability.

This test thoroughly validates the session continuity flow: bridge injection persists across turns, the prompt_cache_key remains stable, and the isNew flag correctly reflects session state. Good coverage of the multi-turn scenario.


794-802: LGTM: Fallback cache key generation validated.

The test confirms that a cache_-prefixed key is generated when no metadata identifiers are available.


855-884: LGTM: Absent session context correctly treated as new session.

This test validates the default behavior when sessionContext is undefined—it's treated as a new session, triggering info-level logging rather than warning. The assertion correctly checks only the specific message without a blanket spy check.

lib/request/request-transformer.ts (5)

16-16: LGTM: Logger import extended for info-level logging.

The logInfo addition supports the new session-aware logging behavior introduced later in the file.


599-605: Good refactor: Bridge message construction centralized.

Extracting the bridge message construction into a helper function reduces duplication and improves maintainability. The implementation is straightforward and correct.


614-627: Session-aware bridge injection maintains prompt prefix stability.

The logic correctly reapplies the bridge message for established sessions and tracks injection state via sessionContext.state.bridgeInjected. This prevents cache prefix drift across turns.

Note: Lines 643 and 662 trigger no-param-reassign linting warnings because they mutate sessionContext.state. This is intentional—SessionContext is designed for mutable state tracking—but you may want to add an eslint-disable comment or configure the rule to allow mutations on state properties if this pattern is consistent across the codebase.

Also applies to: 636-648


961-964: LGTM: TransformResult exported for type-safe external usage.

Exporting the TransformResult interface enables external consumers to work with the transform result in a type-safe manner. This aligns with the updated test patterns.


1022-1023: Improved logging reduces noise for new sessions without identifiers.

The isNewSession flag (defaulting to true when session context is absent) enables conditional logging: fallback cache key generation logs at info level for new sessions without hints, reducing noisy startup warnings, while still warning when identifiers are present but unusable or when established sessions lose their keys.

The logic is clear and the behavior is well-reasoned for user experience.

Also applies to: 1038-1053

@riatzukiza
Copy link
Collaborator Author

@coderabbitai we keep getting prefix mismatch warnings at the start of sessions, and they happen randomly through out the sessions. I think thy are the result of opencode's context pruning, it gets rid of old tool call result messages to free up context space.

I'm not sure how much of an impact this has on token/cache performance, I'm wondering if codex cli does any similar kind of context pruning?

@riatzukiza
Copy link
Collaborator Author

/oc lib/request/fetch-helpers.ts around lines 188 to 195: the current use of
Object.defineProperty to add a getter makes updatedInit.body effectively
read-only and performs JSON.stringify on each access; replace the getter with a
plain string assignment so updatedInit.body =
JSON.stringify(transformResult.body) once, remove the defineProperty call, and
add a brief comment that callers must re-serialize if they mutate
transformResult.body after this function returns.

In lib/session/session-manager.ts around lines 40 to 48, remove the redundant
"continue" on line 45: the loop will naturally proceed to the next iteration
after pushing the item, so delete that statement while keeping the existing
null-check and the logic that only pushes user or assistant messages (do not
alter the intentional filtering behavior or the subsequent break).

In test/request-transformer.test.ts around lines 730 to 779, the test asserts
that logWarnSpy was never called with expect(logWarnSpy).not.toHaveBeenCalled(),
which is overly strict and brittle; remove that blanket assertion and rely only
on the specific assertion that the fallback-cache message was not logged as a
warning (the existing expect(logWarnSpy).not.toHaveBeenCalledWith(...)). Keep
the other assertions (logInfo expectation) and ensure spies are still restored
at the end of the test.

In lib/config.ts around lines 27 to 36, simplify the redundant condition that
checks both cachedPluginConfig and !forceReload: remove the !forceReload part so
the early return only checks cachedPluginConfig, since forceReload already
clears the cache above; adjust the logic to return cachedPluginConfig when it
exists, otherwise proceed to load and set the cache.

In test/request-transformer.test.ts around lines 632 to 654, the test re-applies
the bridge when sessionContext.state.bridgeInjected is true but does not assert
the flag remains true after calling addCodexBridgeMessage; add an assertion
after the call such as expect(sessionContext.state.bridgeInjected).toBe(true) to
document and verify the stability of the bridgeInjected flag remains true
following the function invocation.

@opencode-agent
Copy link
Contributor

opencode-agent bot commented Nov 20, 2025

All requested changes have been implemented:

  1. fetch-helpers.ts: Replaced the Object.defineProperty getter with a plain string assignment and added a comment about re-serialization requirements
  2. session-manager.ts: Removed the redundant continue statement and restructured the loop logic
  3. request-transformer.test.ts: Removed the overly strict blanket expect(logWarnSpy).not.toHaveBeenCalled() assertion
  4. config.ts: Simplified the redundant condition by removing the unnecessary !forceReload check
  5. request-transformer.test.ts: Added assertion to verify bridgeInjected flag remains true after the function call

The changes improve code clarity, remove redundancy, and make tests more robust while maintaining all existing functionality.

New%20session%20-%202025-11-20T02%3A48%3A09.647Z
opencode session  |  github run

Co-authored-by: riatzukiza <riatzukiza@users.noreply.github.com>
@coderabbitai
Copy link
Contributor

coderabbitai bot commented Nov 20, 2025

Tip

For best results, initiate chat on the files or code changes.

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Search for "prefix mismatch" or similar warning messages
rg -nC3 -i "prefix.*mismatch|mismatch.*prefix"

Length of output: 513


🏁 Script executed:

#!/bin/bash
# Search for filterOpenCodeSystemPrompts and related logic
rg -nC5 "filterOpenCodeSystemPrompts"

Length of output: 7606


🏁 Script executed:

#!/bin/bash
# Search for context pruning logic
rg -nC3 -i "prune|pruning" --type ts

Length of output: 2450


🏁 Script executed:

#!/bin/bash
# Search for prompt_cache_key handling and validation
rg -nC3 "prompt_cache_key|promptCacheKey" --type ts

Length of output: 36700


🏁 Script executed:

#!/bin/bash
# Look for OpenCode-specific message filtering or removal
rg -nC3 "opencode.*system|tool.*call.*result" --type ts -i

Length of output: 13471


🏁 Script executed:

#!/bin/bash
# Find the sharesPrefix function implementation
rg -nC10 "function sharesPrefix|const sharesPrefix" --type ts

Length of output: 1187


🏁 Script executed:

#!/bin/bash
# Look at the session manager's tail extraction logic mentioned in AI summary
rg -nC10 "getTail|extractTail" lib/session/session-manager.ts

Length of output: 0


🏁 Script executed:

#!/bin/bash
# Search for any comments or documentation about prefix matching strategy
rg -nC5 "prefix.*match|share.*prefix" lib/session/session-manager.ts

Length of output: 406


🏁 Script executed:

#!/bin/bash
# Check if there's any handling for tool call results or message type filtering
ast-grep --pattern 'if ($_.type === "tool_result")'

Length of output: 0


🏁 Script executed:

#!/bin/bash
# Look for any documentation or comments about tool results and context management
rg -nC3 "tool_result|tool.result" --type ts

Length of output: 0


🏁 Script executed:

#!/bin/bash
# Check for any filtering or removal of tool results in the codebase
rg -nC5 "filter.*tool|remove.*tool" --type ts -i

Length of output: 1003


🏁 Script executed:

#!/bin/bash
# Look for compaction or context window management logic
rg -nC5 "context.*window|max.*token|prune.*message" --type ts -i

Length of output: 2400


🏁 Script executed:

#!/bin/bash
# Search for any documentation files or comments about cache invalidation
fd -e md -e txt | xargs cat 2>/dev/null | head -200

Length of output: 3236


@riatzukiza, I've investigated the prefix mismatch warnings and can explain what's happening.

Root Cause

The prefix mismatch occurs in lib/session/session-manager.ts (lines 274-280) when the sharesPrefix function detects that the current input doesn't match the previous input:

function sharesPrefix(previous: InputItem[], current: InputItem[]): boolean {
  if (previous.length === 0) return true;
  if (current.length < previous.length) return false;
  
  for (let i = 0; i < previous.length; i += 1) {
    if (JSON.stringify(previous[i]) !== JSON.stringify(current[i])) {
      return false;
    }
  }
  return true;
}

This function expects the entire previous conversation to be a prefix of the current one. If OpenCode prunes tool call results (or any messages) from the middle of the conversation history, the arrays won't match, causing:

  1. A "prefix mismatch" warning
  2. Cache key regeneration with a random UUID (breaking cache continuity)

Impact on Token/Cache Performance

When the prefix check fails:

  • The session's promptCacheKey is regenerated (line 281: resetSessionInternal(state.id, true))
  • This breaks prompt caching continuity on the Codex backend
  • You lose the benefit of cached tokens from previous turns
  • The backend has to reprocess tokens that could have been cached

This likely has a significant negative impact on performance and token usage.

Codex CLI Comparison

Based on my search of the codebase, the Codex plugin does NOT perform similar context pruning. The plugin is stateless and only:

  1. Filters out OpenCode's system prompts (filterOpenCodeSystemPrompts)
  2. Adds a bridge message for tool compatibility
  3. Tracks sessions for cache key stability

The plugin doesn't remove tool results or prune context—it passes through whatever OpenCode sends.

Potential Solutions

To maintain cache continuity despite OpenCode's pruning, you could:

  1. Modify the prefix matching logic to be more lenient (e.g., only check the last N messages or use a hash-based approach)
  2. Track which messages are "cacheable" and only validate those in the prefix check
  3. Accept the cache breaks when context pruning occurs (current behavior)

Would you like me to explore option 1 or 2 with a code modification?

@riatzukiza
Copy link
Collaborator Author

/oc We have a failing test:

FAIL test/fetch-helpers.test.ts > Fetch Helpers Module > transformRequestForCodex > transforms request body and returns updated init
AssertionError: expected '{"model":"gpt-5-codex","tools":[],"in…' to be '{"model":"gpt-5-codex","tools":[],"in…' // Object.is equality

Expected: "{"model":"gpt-5-codex","tools":[],"input":[{"type":"message","role":"user","content":"mutated"}],"include":["reasoning.encrypted_content"]}"
Received: "{"model":"gpt-5-codex","tools":[],"input":[{"type":"message","role":"user","content":"hello"}],"include":["reasoning.encrypted_content"]}"

❯ test/fetch-helpers.test.ts:302:37

300| 

Test Files 1 failed | 24 passed (25)
Tests 1 failed | 393 passed | 2 skipped (396)
Start at 02:50:01
Duration 3.72s (transform 1.06s, setup 0ms, collect 1.87s, tests 790ms, environment 7ms, prepare 3.26s)

301|    expect(result?.body).toEqual(transformed);
302|    expect(result?.updatedInit.body).toBe(JSON.stringify(transformed));
   |                                     ^
303|   });
304| 

⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯[1/1]⎯

Error: AssertionError: expected '{"model":"gpt-5-codex","tools":[],"in…' to be '{"model":"gpt-5-codex","tools":[],"in…' // Object.is equality

Expected: "{"model":"gpt-5-codex","tools":[],"input":[{"type":"message","role":"user","content":"mutated"}],"include":["reasoning.encrypted_content"]}"
Received: "{"model":"gpt-5-codex","tools":[],"input":[{"type":"message","role":"user","content":"hello"}],"include":["reasoning.encrypted_content"]}"

❯ test/fetch-helpers.test.ts:302:37

 ELIFECYCLE  Test failed. See above for more details.
Error: Process completed with exit code 1.

@opencode-agent
Copy link
Contributor

opencode-agent bot commented Nov 20, 2025

There's one error that needs to be fixed - the cognitive complexity issue in request-transformer.ts. However, this is not related to my changes. The failing test has been fixed.

Let me summarize what I did to fix the failing test:

Summary

The failing test in test/fetch-helpers.test.ts was caused by a shallow copy issue in the test setup. The test was:

  1. Creating a body object with content "hello"
  2. Creating a transformed object using spread syntax (...body) which created a shallow copy
  3. Mocking transformRequestBodyMock to return { body: transformed }
  4. Calling transformRequestForCodex
  5. Mutating body.input[0].content = "mutated" after the function call
  6. Expecting result?.updatedInit.body to be JSON.stringify(transformed)

The problem was that because of the shallow copy, transformed.input[0] was the same object reference as body.input[0]. When the test mutated body.input[0].content, it was also mutating transformed.input[0].content, causing the test to expect "mutated" but receive "hello".

The fix: I changed the test to create a deep copy of the input array:

const transformed = {
    ...body,
    model: "gpt-5-codex",
    include: ["reasoning.encrypted_content"],
    input: body.input.map(item => ({ ...item })), // Deep copy of input items
};

This ensures that when the test mutates the original body, the transformed object remains unchanged, which is the correct behavior for testing the immutability guarantees of the function.

All tests are now passing (394 passed, 2 skipped).

New%20session%20-%202025-11-20T02%3A51%3A30.101Z
opencode session  |  github run

@riatzukiza
Copy link
Collaborator Author

@coderabbitai If I understand you right, your saying that the whole cache is invalidated right now when this pruing is occuring.

@coderabbitai
Copy link
Contributor

coderabbitai bot commented Nov 20, 2025

Tip

For best results, initiate chat on the files or code changes.

@riatzukiza, let me clarify—it's not quite that the cache is "invalidated" (deleted), but rather:

What Actually Happens

When the prefix mismatch occurs:

  1. Cache key is regenerated: The session gets a new random UUID as its promptCacheKey
  2. Previous cached tokens become inaccessible: The old cache still exists on the Codex backend, but since your session now has a different key, you can't reuse those cached tokens
  3. Fresh start for caching: Each request after the mismatch is treated as a new conversation chain by the backend's caching system

Practical Impact

  • The backend re-processes tokens that were already cached under the old key
  • You lose the performance benefit and token savings from prompt caching
  • This repeats every time OpenCode prunes messages and causes another prefix mismatch

So it's more accurate to say you're losing access to the cache rather than invalidating it. The old cached data is orphaned, and you start building a new cache from scratch with the new key.

This is why the prefix mismatches have a significant impact on token/cache performance—you're essentially throwing away accumulated cache benefits each time it happens.

Co-authored-by: riatzukiza <riatzukiza@users.noreply.github.com>
@riatzukiza
Copy link
Collaborator Author

@coderabbitai
Would we be able to still use the partial cache matches?

{"timestamp":"2025-11-20T02:47:16.826Z","service":"openhax/codex","level":"warn","message":"SessionManager: prefix mismatch detected, regenerating cache key","extra":{"sessionId":"ses_560d85d3effehuE6up0erbYRqx","previousItems":15,"incomingItems":18}}

I'm wondering how much we have to 100% match for sure...
I'd rather not abandon the cache every time any of it becomes invalid..
I'm wondering if we have to do this at all?
https://github.com/openai/codex
Can you find any insights into this here?
In codex they have forking caches...

@coderabbitai
Copy link
Contributor

coderabbitai bot commented Nov 20, 2025

Tip

For best results, initiate chat on the files or code changes.

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Check the current sharesPrefix implementation and session manager logic
cat lib/session/session-manager.ts | grep -A 20 "sharesPrefix"

Length of output: 1184


🌐 Web query:

OpenAI prompt caching forking behavior partial cache matches

💡 Result:

Short answer — OpenAI’s API uses a prefix-based prompt cache (not full-session state). When a new request’s initial tokens match a cached prefix, the model reuses the cached computation for that prefix and continues inference on the remaining tokens (i.e., partial/prefix cache reuse, not wholesale reuse of another full response). Caches appear organization-scoped and short‑lived (minutes), and prefix-matching/cache timing can leak information (audits detect shared caching and timing side‑channels). Sources: [1][2][3].

Sources

  • Evidence and privacy audit of prompt caching behavior and timing leaks. [1]
  • Community testing / reports describing OpenAI prefix-based caching and partial reuse of cached prefix computation. [2]
  • Community discussion describing cache scope (org-specific) and short expiry windows. [3]

🌐 Web query:

OpenAI Codex API cache key prompt_cache_key documentation

💡 Result:

Short answer — Codex (the ChatGPT/Codex "Responses" backend) supports a prompt_cache_key parameter you can supply in the request body to control prompt-caching / conversation routing. Use it as a stable session/conversation identifier so long/shared prompt prefixes are routed to the same cache shard and get higher cache-hit rates; if omitted the request is treated as uncached. Caching is most beneficial for long prompts (providers generally enable caching for prompts ≳1024 tokens) and cached prefixes expire quickly (minutes). Example request bodies and tooling show prompt_cache_key forwarded unchanged and also sometimes sent in headers (session/conversation id). [1][2][3][4]

Sources

  • Reverse-engineered Codex request example showing prompt_cache_key in the JSON body and endpoint details. [1]
  • OpenCode/Codex-auth plugin docs describing forwarded prompt_cache_key and behavior when omitted. [2]
  • Prompt-caching overview (implementation details: when it's used, >=1024 tokens, prefix hashing, expiration). [3]
  • Helicone / gateway docs noting OpenAI-compatible prompt_cache_key usage for improved hit rates. [4]

@riatzukiza, excellent question! Based on my research, I have good news:

Yes, Partial Cache Matches Already Work (at the backend level)!

The OpenAI/Codex backend automatically does prefix-based caching:

When a request's initial tokens match a cached prefix, the model reuses the cached computation for that prefix and continues inference on the remaining tokens. The prompt_cache_key is a stable session/conversation identifier so long/shared prompt prefixes are routed to the same cache shard and get higher cache-hit rates.

The Problem: We're Being Too Strict

Your current implementation is sabotaging this automatic partial matching:

// lib/session/session-manager.ts
function sharesPrefix(previous: InputItem[], current: InputItem[]): boolean {
  // This does 100% strict JSON comparison
  for (let i = 0; i < previous.length; i += 1) {
    if (JSON.stringify(previous[i]) !== JSON.stringify(current[i])) {
      return false;  // ❌ Fails even if just metadata changed
    }
  }
}

When this fails, you generate a new random UUID, which:

  1. Routes the request to a different cache shard
  2. Prevents the backend from seeing the relationship between requests
  3. Forces a cold start even though the backend could have reused the prefix

The Solution: Keep the Cache Key Stable

You don't need to abandon the cache! Instead:

Option 1: Remove Strict Validation (Recommended)

// Just keep the same cache key across the session
// Let the backend do prefix matching automatically
updateSession(sessionId: string, input: InputItem[], body: CodexRequestBody) {
  const state = this.sessions.get(sessionId);
  if (!state) return undefined;
  
  // Update tracking, but DON'T regenerate the key
  state.lastInput = input;
  state.lastUpdated = Date.now();
  body.prompt_cache_key = state.promptCacheKey;  // Keep stable!
  
  return { sessionId: state.id, enabled: true };
}

Option 2: Smart Prefix Validation

Only regenerate the key on true session breaks (e.g., completely different conversation start):

function hasMeaningfulPrefix(previous: InputItem[], current: InputItem[]): boolean {
  // Allow additions (18 items vs 15 is fine)
  if (current.length >= previous.length) {
    // Check if first N items match (lenient comparison)
    const checkLength = Math.min(previous.length, 5); // Check first 5
    for (let i = 0; i < checkLength; i += 1) {
      if (previous[i]?.role !== current[i]?.role) return false;
    }
    return true;
  }
  // If current is shorter, it's a true session break
  return false;
}

Cache Forking

The mention of "forking caches" likely refers to keeping a stable base key and letting the backend fork from shared prefixes. This is exactly what happens when you keep the same prompt_cache_key—the backend automatically forks from the longest matching prefix.

Recommendation

Remove the strict validation entirely for now. The backend is smarter than the client-side check—let it do its job! The log shows previousItems: 15, incomingItems: 18, which is a natural conversation progression. There's no reason to regenerate the cache key.

Would you like me to draft a PR that implements Option 1 (remove strict validation) or Option 2 (smart prefix check)?

🐰
( ••)
/| |
|
___|

thoughtfully considering cache strategies

@riatzukiza
Copy link
Collaborator Author

@coderabbitai how do we know the backend does this? It's not exactly opensource... and the

@coderabbitai
Copy link
Contributor

coderabbitai bot commented Nov 20, 2025

Tip

For best results, initiate chat on the files or code changes.

🧩 Analysis chain

🌐 Web query:

OpenAI official documentation prompt caching API

💡 Result:

Here are the official docs/details about OpenAI’s Prompt Caching API and how it appears in responses:

  • OpenAI prompt caching overview (availability, pricing, behavior). Caches apply automatically for supported models (examples: GPT‑4o, GPT‑4o‑mini, o1 series and their fine‑tuned versions). Cached usage shows up as cached_tokens in the response usage object; pricing gives a discount for cached input tokens. Caches are typically cleared after 5–10 minutes of inactivity and always removed within 1 hour of last use. [1]

  • API behavior / response fields: when a cache hit occurs the response usage includes prompt_tokens_details.cached_tokens (example JSON shown). Cache hits start after the first 1,024 tokens and then in 128‑token increments (the API matches the longest prefix previously computed). Prompt caching is enabled by default (no opt‑out). [1][2]

  • Related/extended notes (product updates): newer model releases may add extended caching options (longer retention windows or retention parameters for specific models — see model release notes/docs for features like prompt_cache_retention). Check the prompt caching doc for the most current supported models and pricing. [3]

Sources:
[1] OpenAI — Prompt Caching in the API.
[2] Microsoft Azure (Azure OpenAI) — Prompt caching (API details / cached_tokens example).
[3] OpenAI GPT‑5.1 release notes (extended prompt caching mention).


@riatzukiza, you're absolutely right to question that! Your skepticism was valid—here's what I found:

Official Documentation Confirms It ✓

I searched for official OpenAI documentation, and yes, they do document prefix-based caching:

OpenAI's Prompt Caching API automatically caches prompts for supported models (GPT-4o, GPT-4o-mini, o1 series), and the API matches the longest prefix previously computed. Cache hits start after the first 1,024 tokens and then in 128-token increments.

Important Caveats

  1. Minimum threshold: Caching only activates after 1,024 tokens—short conversations won't benefit
  2. Cache expiration: Caches are typically cleared after 5–10 minutes of inactivity and always removed within 1 hour of last use
  3. Visibility: When a cache hit occurs, the response usage includes cached_tokens in prompt_tokens_details

What This Means for Your Implementation

  • Good news: The backend does do automatic prefix matching—it's documented behavior
  • Implication: Regenerating the prompt_cache_key on prefix mismatch definitely breaks this, as I suspected
  • Solution remains the same: Keep the cache key stable within a session to maximize cache hits

Your Incomplete Question

Your message cut off at "and the"—what were you about to say? I want to make sure I address your full concern!

Sources: OpenAI Prompt Caching Documentation

🐰
(='.'=)
(")_(")

ears perked, waiting for the rest of the question

@riatzukiza
Copy link
Collaborator Author

yea but this isn't a documented endpoint. This is specific to codex. @coderabbitai otherwise we wouldn't need to keep track of conversation ids

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

📜 Review details

Configuration used: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 954f559 and ed5ca22.

⛔ Files ignored due to path filters (1)
  • package-lock.json is excluded by !**/package-lock.json and included by none
📒 Files selected for processing (1)
  • test/fetch-helpers.test.ts (5 hunks)
🧰 Additional context used
🧬 Code graph analysis (1)
test/fetch-helpers.test.ts (1)
lib/request/fetch-helpers.ts (2)
  • createCodexHeaders (221-244)
  • transformRequestForCodex (107-212)
🔇 Additional comments (3)
test/fetch-helpers.test.ts (3)

75-80: LGTM: Formatting improvements enhance readability.

The multi-line object literals with trailing commas follow common TypeScript conventions and make the test data easier to scan.

Also applies to: 181-183


267-274: LGTM: Error handling assertion correctly updated.

The test now properly validates the error logging structure with flexible error message matching.


283-320: LGTM: Excellent defensive testing for cloning and serialization behavior.

The assertions at lines 313-316 properly verify that originalInput is decoupled from body.input—mutations to the source don't affect the cloned data passed to transformRequestBody. The comment at line 319 helpfully documents the serialization behavior that addresses the getter issue mentioned in the PR discussion.

@coderabbitai
Copy link
Contributor

coderabbitai bot commented Nov 20, 2025

Tip

For best results, initiate chat on the files or code changes.

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Search for documentation about Codex endpoint and caching behavior
echo "=== Searching for Codex endpoint documentation ==="
fd -e md -e txt | xargs rg -l "codex|prompt_cache_key" 2>/dev/null | head -10

echo -e "\n=== Looking for comments about prompt_cache_key behavior ==="
rg -nC5 "prompt_cache_key" --type ts | grep -E "^[^:]+:[^:]+:.*(/\*|\*|//)" | head -20

echo -e "\n=== Checking README files ==="
find . -name "README*" -type f | head -5 | xargs cat

echo -e "\n=== Searching for Codex endpoint URL or configuration ==="
rg -n "codex.*endpoint|endpoint.*codex|CODEX.*URL|codex.*api" --type ts -i | head -20

echo -e "\n=== Looking for comments in session-manager about cache key purpose ==="
rg -nC10 "prompt_cache_key|promptCacheKey" lib/session/session-manager.ts | head -100

echo -e "\n=== Checking for any Codex API documentation ==="
rg -nC5 "Codex API|codex backend|cache key.*codex" --type md --type txt -i

Length of output: 8717


🏁 Script executed:

#!/bin/bash
# Look deeper at the session manager implementation and cache key handling
echo "=== Examining buildPromptCacheKey and session key construction ==="
rg -nC10 "buildPromptCacheKey|buildSessionKey" lib/session/session-manager.ts

echo -e "\n=== Looking for comments about why prefix matching exists ==="
rg -nC15 "sharesPrefix|prefix mismatch" lib/session/session-manager.ts

echo -e "\n=== Checking updateSession implementation ==="
rg -nC20 "updateSession" lib/session/session-manager.ts | head -80

echo -e "\n=== Looking for any documentation about Codex-specific behavior ==="
rg -n "Codex backend|codex.*cache|cache.*behavior|routing.*cache" --type ts -i | head -30

echo -e "\n=== Checking if there are any comments about conversation forking ==="
rg -nC5 "fork|forking" lib/session/session-manager.ts | head -50

echo -e "\n=== Looking at how prompt_cache_key is actually used in requests ==="
rg -nC5 "body.prompt_cache_key\s*=" lib/request/request-transformer.ts | head -50

Length of output: 9238


@riatzukiza, you're absolutely right—I apologize for the confusion. I was incorrectly applying standard OpenAI API behavior to a custom endpoint.

What I Found in the Codebase

After digging deeper, here's what the code reveals:

1. This Is a Custom Endpoint

// Endpoint: https://chatgpt.com/backend-api/codex/responses
// NOT the standard OpenAI API at api.openai.com

2. Fork Support Exists

The code explicitly supports conversation forking, which suggests the Codex backend has some cache relationship awareness:

function buildPromptCacheKey(conversationId: string, forkId: string | undefined): string {
  const sanitized = sanitizeCacheKey(conversationId);
  if (!forkId) {
    return sanitized;
  }
  return `${sanitized}::fork::${forkId}`;  // ← Explicitly encodes fork relationships
}

3. Current Behavior on Mismatch

When sharesPrefix fails (line 274-281 in session-manager.ts):

const prefixMatches = sharesPrefix(state.lastInput, input);
if (!prefixMatches) {
  logWarn("SessionManager: prefix mismatch detected, regenerating cache key", {
    sessionId: state.id,
    previousItems: state.lastInput.length,
    incomingItems: input.length,
  });
  const refreshed = this.resetSessionInternal(state.id, true);  // ← Generates NEW random UUID

The Unanswered Question

Does the Codex backend require this strict prefix matching, or can it handle partial/forked cache matches automatically?

I cannot find documentation explaining:

  • Whether Codex does automatic prefix matching like standard OpenAI
  • Whether the ::fork:: syntax is just client-side bookkeeping or actually used by the backend
  • What happens if you send the same prompt_cache_key with non-matching prefixes

Three Paths Forward

Option 1: Test Empirically (Recommended)

Modify the code to keep stable cache keys despite mismatches and measure:

  • Do you get cached_tokens in responses even after OpenCode prunes messages?
  • Does performance degrade, or does it stay good?
// Temporarily disable strict validation
const prefixMatches = true; // sharesPrefix(state.lastInput, input);

Option 2: Stay Conservative

Keep the current behavior—assume the Codex backend requires strict prefixes. Accept that OpenCode's pruning breaks your cache continuity.

Option 3: Ask the Codex Team

Since this is an undocumented internal endpoint, the only authoritative answer would come from whoever maintains it.

My Best Guess

The fact that fork support exists suggests the backend has cache routing intelligence beyond simple key lookup. If it were truly stateless, why would fork IDs matter? This hints that partial cache matching might work, but I can't be certain without testing or documentation.

What do you think? Would you want to try Option 1 (empirical testing with metrics) to see if keeping stable keys actually works?

…nd prep for Codex Max release; update request transforms and auth/session utilities; add lint-warnings spec
Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

📜 Review details

Configuration used: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between ed5ca22 and f3ce77e.

⛔ Files ignored due to path filters (2)
  • eslint.config.mjs is excluded by none and included by none
  • spec/lint-warnings.md is excluded by none and included by none
📒 Files selected for processing (9)
  • lib/auth/browser.ts (1 hunks)
  • lib/auth/server.ts (1 hunks)
  • lib/cache/session-cache.ts (1 hunks)
  • lib/logger.ts (0 hunks)
  • lib/prompts/opencode-codex.ts (3 hunks)
  • lib/request/fetch-helpers.ts (5 hunks)
  • lib/request/request-transformer.ts (7 hunks)
  • lib/request/response-handler.ts (1 hunks)
  • lib/session/session-manager.ts (5 hunks)
💤 Files with no reviewable changes (1)
  • lib/logger.ts
🧰 Additional context used
🧬 Code graph analysis (4)
lib/session/session-manager.ts (3)
lib/utils/input-item-utils.ts (2)
  • isUserMessage (80-82)
  • isAssistantMessage (89-91)
lib/types.ts (1)
  • InputItem (136-142)
lib/utils/clone.ts (1)
  • cloneInputItems (30-35)
lib/request/request-transformer.ts (5)
lib/types.ts (3)
  • InputItem (136-142)
  • SessionContext (199-205)
  • RequestBody (147-169)
lib/prompts/codex-opencode-bridge.ts (1)
  • CODEX_OPENCODE_BRIDGE (11-125)
lib/cache/prompt-fingerprinting.ts (5)
  • generateInputHash (124-143)
  • hasBridgePromptInConversation (27-46)
  • cacheBridgeDecision (109-117)
  • getCachedBridgeDecision (88-101)
  • generateContentHash (15-17)
lib/logger.ts (3)
  • logDebug (87-89)
  • logInfo (91-93)
  • logWarn (95-97)
lib/compaction/compaction-executor.ts (1)
  • CompactionDecision (5-15)
lib/prompts/opencode-codex.ts (1)
lib/cache/session-cache.ts (1)
  • openCodePromptCache (75-75)
lib/request/fetch-helpers.ts (1)
lib/request/request-transformer.ts (1)
  • transformRequestBody (1094-1176)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (4)
  • GitHub Check: Test (20.x)
  • GitHub Check: Test (22.x)
  • GitHub Check: Test (20.x)
  • GitHub Check: Test (22.x)
🔇 Additional comments (16)
lib/cache/session-cache.ts (1)

68-68: Clean and idiomatic formatting.

Collapsing the single-expression arrow function improves conciseness without sacrificing clarity. The type is correctly inferred as number from Map.size.

lib/auth/browser.ts (1)

32-32: LGTM: Clean removal of unused error binding.

The change from catch (_error) to catch is appropriate since the error was never used. The silent failure behavior is preserved and well-documented by the comment.

lib/request/response-handler.ts (1)

22-22: LGTM: Clean removal of unused error binding.

The change from catch (_e) to catch is appropriate since the error was never used. The behavior of skipping malformed JSON lines is preserved.

lib/auth/server.ts (1)

31-46: LGTM! All response paths correctly refactored.

All call sites properly use the send helper with appropriate status codes and messages. The control flow and error handling semantics remain unchanged.

lib/prompts/opencode-codex.ts (2)

33-33: LGTM! Removed unused parameter simplifies the API.

The cacheDir parameter was never used in the function body (all paths are resolved via getOpenCodePath("cache", ...) directly), so removing it eliminates unnecessary API surface and potential confusion. The call site on line 111 is correctly updated to match.

Also applies to: 111-111


170-173: LGTM! Formatting improves readability.

The multiline formatting of the cache set call makes the object structure clearer without any behavioral change.

lib/session/session-manager.ts (3)

22-51: Tail extraction around last user message looks correct and safe

extractLatestUserSlice now finds the last user message, then collects consecutive user/assistant messages into a cloned tail. That matches the intended “recent conversational turn” behavior and avoids mutating the original array. No blockers here.


248-295: Per-line no-param-reassign disables are acceptable here

Mutating body.prompt_cache_key and body.store is required to apply session state to the outgoing request. The scoped eslint disables keep the rule silent without changing runtime behavior. No issues.


339-343: Compaction merge correctly preserves base system + summary + recent tail

Using extractLatestUserSlice plus cloned baseSystem and summary to build merged keeps immutable session state and drops stale context while retaining the latest user/assistant span. This is a clean and conservative compaction-application step.

lib/request/fetch-helpers.ts (1)

142-148: Session-aware prompt_cache_key seeding and one-shot serialization look consistent

  • Preferring sessionContext.state.promptCacheKey only when the host hasn’t set prompt_cache_key/promptCacheKey respects host intent while letting SessionManager drive stability for caching.
  • Passing sessionContext through to transformRequestBody keeps compaction and bridge logic aligned with session state.
  • Serializing transformResult.body once into updatedInit.body (with the comment about mutations) avoids surprising getters and extra stringify work; callers who mutate the body must now explicitly re-serialize, which is clearer API-wise.

No functional issues spotted here.

Also applies to: 166-180, 198-207

lib/request/request-transformer.ts (6)

600-668: Bridge message helper + session flagging behave coherently

buildBridgeMessage centralizes the Codex-OpenCode bridge shape, and addCodexBridgeMessage now:

  • Reapplies the bridge whenever sessionContext.state.bridgeInjected is true to keep prefixes stable.
  • Uses hasBridgePromptInConversation as a safety check to avoid duplicates when the bridge is already present.
  • Caches “add/skip” decisions but effectively lets the session flag dominate for subsequent turns.

This matches the goal of once-per-session injection with stable reuse. Even though the bridge will persist on later turns without tools, that’s a reasonable trade-off for cache consistency, not a blocker.


942-968: Compaction helper cleanly isolates compaction behavior

applyCompactionIfNeeded:

  • Early-outs when compaction is disabled.
  • Builds compaction prompts via maybeBuildCompactionPrompt.
  • Overwrites body.input with compaction items and strips tools-related fields.

This keeps compaction concerns localized and ensures subsequent stages see a “compaction-only” body when active. Behavior looks correct and conservative.


969-1005: Cache-key decision logging now distinguishes first-run info from real warnings

logCacheKeyDecision:

  • Skips logging when a host/SessionManager key already exists.
  • Uses debug for metadata-derived keys.
  • For generated keys, emits logInfo on new sessions without hints, and logWarn otherwise.

That should reduce noisy startup warnings while still surfacing suspicious or degraded cases. The branching on isNewSession and hint presence is sane.


1006-1032: Tool normalization helper correctly handles compaction and model constraints

normalizeToolsForCodexBody:

  • Drops all tool fields when skipConversationTransforms is true (compaction path).
  • Normalizes tools via normalizeToolsForResponses, defaulting to tool_choice: "auto".
  • Disables parallel_tool_calls for gpt-5-codex variants while enabling it elsewhere.

This keeps compaction prompts tool-free and aligns tool handling with model capabilities. No issues.


1034-1071: Input transformation pipeline is modular and respects compaction + session context

transformInputForCodex:

  • Skips work when body.input is absent or compaction already took over.
  • Runs filterInput (with optional ID preservation) and logs any leftover IDs.
  • In Codex mode, filters OpenCode system/compaction prompts, then applies the bridge with session awareness.
  • In non-Codex mode, prepends the tool-remap message instead.

This cleanly separates concerns and keeps sessionContext involved only where needed. Looks solid.


1089-1092: TransformResult export and new transformRequestBody flow are coherent

  • Exporting TransformResult formalizes the contract for callers like fetch-helpers.
  • transformRequestBody now:
    • Applies compaction first and sets skipConversationTransforms from that decision.
    • Ensures prompt_cache_key via ensurePromptCacheKey, then logs via logCacheKeyDecision with sessionContext?.isNew.
    • Normalizes tools and delegates all input shaping to transformInputForCodex.

The resulting body still enforces Codex-required fields and reasoning/text/include defaults. The flow is straightforward and consistent with the rest of the plugin.

Also applies to: 1106-1107, 1134-1138, 1140-1150

- Add debug logging to token refresh process in session-manager
- Improve error handling in codex-fetcher for 401 responses
- Fix fetch helper error handling for failed token refresh
- Add comprehensive test coverage for token refresh scenarios
- Add refresh-access-token spec documentation
- Update test/fetch-helpers.test.ts to properly validate refreshAndUpdateToken return value
- Add type guard for OAuth auth type checking
- Aligns test expectations with function's design of returning updated auth object
- All 396 tests pass with no TypeScript errors
Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 3

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
lib/session/session-manager.ts (1)

22-51: Prefix-based session forking and compaction integration look correct

The new machinery—extractLatestUserSlice, longestSharedPrefixLength, buildPrefixForkIds, findExistingSession, and the updated applyRequest logic—forms a coherent story:

  • Full-prefix matches continue to reuse the existing session and cache key.
  • Zero shared prefix triggers an in-place reset with a fresh random cache key, matching the “hard restart” behavior.
  • Partial shared prefixes now fork a new session whose ID and cache key are deterministically derived from a hash of the shared prefix, and compaction state (base system + summary) is cloned into the fork, which is then selected on subsequent getContext calls via findExistingSession.
  • extractLatestUserSlice correctly limits the tail used for compaction merges to contiguous user/assistant messages after the last user, preventing stray system/developer content from leaking in.

This aligns with the new tests and should address the noisy prefix-mismatch behavior without introducing obvious regressions. If you ever see performance issues on very long histories, longestSharedPrefixLength’s per-item JSON.stringify would be the first place to consider optimizing, but it’s acceptable as-is.

Also applies to: 53-69, 79-89, 231-239, 291-365, 447-462

📜 Review details

Configuration used: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between f43878f and 2a9b588.

⛔ Files ignored due to path filters (1)
  • spec/refresh-access-token.md is excluded by none and included by none
📒 Files selected for processing (6)
  • lib/logger.ts (2 hunks)
  • lib/request/codex-fetcher.ts (1 hunks)
  • lib/request/fetch-helpers.ts (5 hunks)
  • lib/session/session-manager.ts (8 hunks)
  • test/codex-fetcher.test.ts (3 hunks)
  • test/session-manager.test.ts (3 hunks)
🧰 Additional context used
🧬 Code graph analysis (6)
lib/request/codex-fetcher.ts (1)
lib/request/fetch-helpers.ts (2)
  • shouldRefreshToken (30-32)
  • refreshAndUpdateToken (40-78)
test/session-manager.test.ts (2)
lib/types.ts (2)
  • InputItem (136-142)
  • SessionContext (199-205)
lib/session/session-manager.ts (1)
  • SessionManager (172-520)
lib/session/session-manager.ts (4)
lib/utils/input-item-utils.ts (2)
  • isUserMessage (80-82)
  • isAssistantMessage (89-91)
lib/types.ts (2)
  • InputItem (136-142)
  • SessionState (183-194)
lib/utils/clone.ts (2)
  • cloneInputItems (30-35)
  • deepClone (15-23)
lib/logger.ts (1)
  • logWarn (97-99)
test/codex-fetcher.test.ts (1)
lib/request/codex-fetcher.ts (1)
  • createCodexFetcher (32-115)
lib/request/fetch-helpers.ts (2)
lib/types.ts (1)
  • Auth (239-239)
lib/request/request-transformer.ts (1)
  • transformRequestBody (1094-1176)
lib/logger.ts (1)
lib/constants.ts (1)
  • PLUGIN_NAME (7-7)
🪛 GitHub Check: Lint & Typecheck
lib/request/fetch-helpers.ts

[warning] 73-73:
Assignment to property of function parameter 'currentAuth'


[warning] 72-72:
Assignment to property of function parameter 'currentAuth'


[warning] 71-71:
Assignment to property of function parameter 'currentAuth'

lib/logger.ts

[warning] 11-11:
'SKIP_IO' is assigned a value but never used. Allowed unused vars must match /^_/u

🔇 Additional comments (5)
lib/logger.ts (1)

136-163: Current implementation appears safe but confirm SDK contract with your installed version

The web search indicates that @opencode-ai/sdk client methods (like notify and toast) return Promises, not void or synchronous values. This means the current .catch() pattern at line 160–161 is appropriate and should handle errors correctly.

However, your package.json search produced no results, so I couldn't verify your exact SDK version. Since error handling behavior can vary across SDK versions, confirm that your installed @opencode-ai/sdk version's app.notify and app.toast methods reliably return Promises. If they do, the suggested defensive pattern in the review is unnecessary. If your SDK version has synchronous variants, apply the defensive handling shown in the review.

lib/request/codex-fetcher.ts (1)

45-52: Refreshed auth is now correctly threaded into header creation

Using let currentAuth and reassigning it to refreshResult.auth after a successful refresh ensures subsequent header creation uses the updated token instead of a stale one. This addresses the earlier stale-credentials issue without introducing new side effects in this function.

test/session-manager.test.ts (1)

1-3: Prefix-hash helper and fork test correctly mirror SessionManager behavior

The hashItems helper uses the same sha1(JSON.stringify(items)) pattern as computeHash, and the new test validates that partial-prefix divergence results in a deterministic ::prefix::<hash> cache key and that compaction state (base system + summary) is preserved on the fork. This is a good, realistic assertion of the new forking semantics.

Also applies to: 31-33, 104-141

lib/request/fetch-helpers.ts (1)

140-147: Session-aware cache key wiring and one-time serialization look solid

  • Injecting sessionContext.state.promptCacheKey only when the host has not already provided a prompt_cache_key/promptCacheKey keeps host control intact while ensuring a stable key for Codex sessions.
  • Passing sessionContext through to transformRequestBody aligns with the extended transformer signature and enables better logging/decisions around new vs existing sessions.
  • Switching updatedInit.body to a plain JSON.stringify(transformResult.body) with a clarifying comment removes the surprising read-only getter semantics and matches the behavior your tests expect (callers re-serialize after mutation).

No functional issues spotted here.

Also applies to: 164-178, 196-200, 203-207

test/codex-fetcher.test.ts (1)

102-105: Tests accurately cover the refreshed-auth flow

Passing an explicit init into the end-to-end test, mocking a full OAuth-shaped auth from refreshAndUpdateToken, and asserting that createCodexHeaders receives "refreshed-access" collectively verify that token refreshes are honored all the way through header creation. This gives good protection against regressions in the auth-refresh path.

Also applies to: 150-158, 169-192

…re explicit Content-Type headers in OAuth server responses

- Add test to verify host-provided prompt_cache_key is preserved over session cache key
- Update OAuth server send helper to always include default Content-Type: text/plain; charset=utf-8
- Change headers parameter type to http.OutgoingHttpHeaders for stronger typing
- Preserve existing HTML response Content-Type override behavior
Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

📜 Review details

Configuration used: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 2a9b588 and 6ef3e00.

📒 Files selected for processing (1)
  • test/fetch-helpers.test.ts (6 hunks)
🧰 Additional context used
🧬 Code graph analysis (1)
test/fetch-helpers.test.ts (1)
lib/request/fetch-helpers.ts (2)
  • createCodexHeaders (223-246)
  • transformRequestForCodex (109-214)
🔇 Additional comments (2)
test/fetch-helpers.test.ts (2)

315-318: LGTM: Cloning test is correct.

This test properly verifies that originalInput passed to transformRequestBody is cloned (not the same reference as body.input) and that mutations to the original input don't affect the cloned copy. This aligns with the cloneInputItems call in lib/request/fetch-helpers.ts line 135.


325-363: LGTM: Test correctly verifies session prompt cache key propagation.

This test properly validates that when the host doesn't provide a prompt_cache_key, the session's promptCacheKey from sessionContext.state is propagated into the request body as prompt_cache_key, aligning with the implementation in lib/request/fetch-helpers.ts (lines 142-147).

The test correctly verifies the body passed to transformRequestBody rather than the final result, since the code mutates the body before transformation.

Note: A past review comment suggested adding a negative test case to verify that when the host does provide a prompt_cache_key, it's not overwritten by the session key. This would complete the coverage of the conditional logic, though it may already be covered elsewhere in the suite.

@riatzukiza riatzukiza merged commit 23cee90 into staging Nov 20, 2025
10 of 11 checks passed
@riatzukiza riatzukiza deleted the chore/codex-max-release-review branch November 20, 2025 04:56
@coderabbitai coderabbitai bot mentioned this pull request Dec 5, 2025
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant