Skip to content

feat: add GPT-5.4 support (plugin v5.4.3)#67

Merged
ndycode merged 8 commits into
mainfrom
feat/gpt-5.4.3-opencode-support
Mar 7, 2026
Merged

feat: add GPT-5.4 support (plugin v5.4.3)#67
ndycode merged 8 commits into
mainfrom
feat/gpt-5.4.3-opencode-support

Conversation

@ndycode
Copy link
Copy Markdown
Owner

@ndycode ndycode commented Mar 6, 2026

Summary

  • add gpt-5.4 snapshot alias normalization and keep gpt-5* aliases mapped to gpt-5.4
  • make gpt-5.4 the default fallback when model is empty/unknown (
    ormalizeModel and runtime family fallback)
  • add project-storage resilience: when project-scoped accounts are missing, load global ~/.opencode/openai-codex-accounts.json and seed project-scoped storage
  • keep OpenCode templates/docs aligned for GPT-5.4 with 1,000,000 context guidance plus model_context_window=1000000 / model_auto_compact_token_limit=900000
  • refresh tests and internal testing docs for new fallback semantics
{C2F269DB-B7CF-47E1-8F50-6AF3D073B440}

Validation

pm run typecheck

pm run lint

pm test

  • real OpenCode run smoke (isolated HOME/config):
    • opencode run "Reply with exactly: gpt54-smoke-ok" --model=openai/gpt-5.4 --variant=low --print-logs
    • output: gpt54-smoke-ok
  • real OpenCode TUI smoke (isolated HOME/config):
    • started opencode --model=openai/gpt-5.4 --print-logs
    • prompt: Reply with exactly: tui-gpt54-ok
    • UI showed Build GPT 5.4 (OAuth) and session title became Tui-gpt54-ok

Notes

  • default unsupported-model fallback chain remains unchanged (gpt-5.4-pro -> gpt-5.4)
  • no speculative gpt-5.4-codex* aliases were introduced

Summary by CodeRabbit

  • New Features

    • 1,000,000 token context window for gpt-5.4 and gpt-5.4-pro
    • gpt-5.4-pro family added with isolated handling
    • Global accounts storage fallback with optional project seeding and transactional persistence
  • Changes

    • Legacy GPT‑5 aliases and defaults now normalize to gpt-5.4 (default/fallback model)
    • Support for date-based snapshot aliases and global auto‑compaction defaults
  • Bug Fixes

    • Restored normalization, routing, and prompt‑mapping regressions
  • Documentation

    • Updated docs/config guidance for context window, aliases, and compaction
  • Tests

    • Expanded coverage for normalization, family routing, snapshots, reasoning config, and storage fallback

note: greptile review for oc-chatgpt-multi-auth. cite files like lib/foo.ts:123. confirm regression tests + windows concurrency/token redaction coverage.

Greptile Summary

this pr promotes gpt-5.4 as the canonical default family across the plugin — replacing gpt-5.1 fallbacks in normalizeModel, MODEL_MAP legacy aliases, index.ts family routing, and all docs/configs. it also ships two independently useful changes: gpt-5.4-pro family isolation (separate prompt/cache state from gpt-5.4 base) and project-scoped account storage resilience (load from ~/.opencode/openai-codex-accounts.json as a global fallback when project storage is absent, with optional seeding).

key things that look solid:

  • withStorageLock now wraps loadAccounts(), serialising the fallback-seeding flow and eliminating the concurrent double-write race flagged in the previous review
  • canonicalSupportsXhigh correctly lets legacy aliases like gpt-5-mini/gpt-5-nano (which normalise to gpt-5.4) pass xhigh through without downgrade — the new supportsRequestedXhigh guard is well-tested
  • getModelFamily regex ordering puts the gpt-5.4-pro boundary check before the gpt-5.4 check, preventing false family routing
  • new vitest coverage addresses the three seed-edge-case gaps flagged previously (concurrent seed, EPERM seed fail, corrupted global fallback)

two items worth addressing:

  • GPT_54_SNAPSHOT_DATE is a single hardcoded constant; future snapshot dates get regex coverage from normalizeModel but no MODEL_MAP entry — consider an array so adding the next snapshot is a one-liner
  • String(error) in loadGlobalAccountsFallback's log.warn and the two log.warn calls inside loadAccountsInternal for seed-path failures can surface Windows filesystem paths (including username) beyond what the structured from/to fields already carry; per the org's concurrency/token-safety policy, the error field should be sanitised to the error code or message prefix only

Confidence Score: 4/5

  • safe to merge with two minor style fixes; no critical logic or token-safety regressions found
  • core logic changes are well-tested; the withStorageLock fix resolves the prior race; canonicalSupportsXhigh correctly handles the xhigh-for-legacy-aliases case; two remaining items (hardcoded snapshot date, String(error) log redaction) are style/maintenance concerns that don't block correctness
  • lib/storage.ts (String(error) log redaction), lib/request/helpers/model-map.ts (single hardcoded snapshot date)

Important Files Changed

Filename Overview
lib/request/helpers/model-map.ts adds expandDatedAliases helper and GPT_54_SNAPSHOT_DATE constant; reroutes legacy gpt-5/mini/nano to gpt-5.4; single hardcoded date means future snapshot ids need a manual code change
lib/request/request-transformer.ts normalizeModel/fallback updated to gpt-5.4; canonicalSupportsXhigh correctly lets legacy aliases (gpt-5-mini/nano → gpt-5.4) keep xhigh; small ordering oddity (canonicalSupportsXhigh declared mid-block between model detection flags)
lib/storage.ts loadAccounts() now correctly serialised inside withStorageLock; new loadGlobalAccountsFallback + project seeding flow; String(error) in log.warn may surface Windows filesystem paths with usernames — needs explicit redaction per org policy
lib/prompts/codex.ts gpt-5.4-pro added as isolated ModelFamily with correct regex ordering (pro check before base gpt-5.4 check); PROMPT_FILES and CACHE_FILES entries consistent; prewarm candidates updated
test/storage.test.ts comprehensive new tests for happy-path seeding, serialised concurrent load (seed count === 1), EPERM seed failure graceful degradation, non-ENOENT access error skip, and corrupted global fallback; addresses gaps from previous review
test/request-transformer.test.ts xhigh preservation tests for gpt-5-mini and gpt-5-nano added; snapshot alias normalisation tests added; all legacy gpt-5.1 expectations updated to gpt-5.4 consistently
test/gpt54-models.test.ts snapshot alias coverage added for both gpt-5.4 and gpt-5.4-pro families; pro-family isolation test confirms getModelFamily returns correct separate family; legacy gpt-5 alias integration tests added
test/model-map.test.ts MODEL_MAP and getNormalizedModel assertions updated for 2026-03-05 snapshots; legacy gpt-5/mini/nano target changed to gpt-5.4; clean

Flowchart

%%{init: {'theme': 'neutral'}}%%
flowchart TD
    A[loadAccounts] -->|withStorageLock| B[loadAccountsInternal]
    B -->|fs.readFile project path| C{file exists?}
    C -->|yes| D[parse + normalizeAccountStorage]
    D --> E[return AccountStorageV3]
    C -->|ENOENT| F[migrateLegacyProjectStorageIfNeeded]
    F -->|migrated| E
    F -->|null| G[loadGlobalAccountsFallback]
    G -->|globalStoragePath == currentStoragePath| H[return null]
    G -->|read global file| I{valid schema?}
    I -->|no| H
    I -->|yes| J[normalizeAccountStorage]
    J -->|null| H
    J -->|valid| K{persistMigration?}
    K -->|no| L[return globalFallback]
    K -->|yes| M[fs.access seedPath]
    M -->|file exists| L
    M -->|ENOENT| N[persistMigration globalFallback]
    M -->|other error| L
    N -->|success| O[log Seeded project storage]
    N -->|EPERM/EBUSY| P[log warn, graceful degrade]
    O --> L
    P --> L
    C -->|other error| Q[log.error, return null]
Loading

Comments Outside Diff (3)

  1. lib/request/request-transformer.ts, line 474-536 (link)

    Reasoning config for rerouted legacy aliases mismatches the actual API model

    after gpt-5-mini, gpt-5-nano, and gpt-5 are routed to gpt-5.4 via MODEL_MAP (lines 150–152), getReasoningConfig still evaluates the original model name (lookupModel = originalModel on line 886, passed to resolveReasoningConfig on line 996–1000).

    for "gpt-5-mini":

    • normalizedName = "gpt-5-mini" (the input string, lowercased on line 428)
    • line 479 matches: normalizedName.startsWith("gpt-5-")isGpt51General = true
    • line 470 matches: normalizedName.includes("mini")isLightweight = true
    • line 490: supportsXhigh = false (isGpt51General is not in the xhigh supporters list)
    • line 515: defaultEffort = "minimal" (isLightweight branch)

    but the API call targets gpt-5.4 (line 899), which should have:

    • supportsXhigh = true (gpt-5.4 is in line 490)
    • defaultEffort = "high" (gpt-5.4 branches to line 513)

    impact:

    1. user with model: "gpt-5-mini" and reasoningEffort: "xhigh" gets silently downgraded to "high" (line 534–536)
    2. default effort is "minimal" instead of "high" (line 515)
    3. no warning when downgrade happens

    the fix is to use the normalized model name for reasoning config when it differs from the original. after line 899 (where body.model = normalizedModel), pass the normalized name to reasoning config on line 996 instead of lookupModel. keep lookupModel for config lookup (line 887) but use normalizedModel for reasoning params:

    const reasoningConfig = resolveReasoningConfig(
        normalizedModel,  // use resolved model for reasoning, not original
        modelConfig,
        body,
    );
    Prompt To Fix With AI
    This is a comment left during a code review.
    Path: lib/request/request-transformer.ts
    Line: 474-536
    
    Comment:
    **Reasoning config for rerouted legacy aliases mismatches the actual API model**
    
    after `gpt-5-mini`, `gpt-5-nano`, and `gpt-5` are routed to `gpt-5.4` via MODEL_MAP (lines 150–152), `getReasoningConfig` still evaluates the *original* model name (`lookupModel = originalModel` on line 886, passed to `resolveReasoningConfig` on line 996–1000). 
    
    for "gpt-5-mini":
    - `normalizedName = "gpt-5-mini"` (the input string, lowercased on line 428)
    - line 479 matches: `normalizedName.startsWith("gpt-5-")``isGpt51General = true`
    - line 470 matches: `normalizedName.includes("mini")``isLightweight = true`
    - line 490: `supportsXhigh = false` (isGpt51General is not in the xhigh supporters list)
    - line 515: `defaultEffort = "minimal"` (isLightweight branch)
    
    but the API call targets `gpt-5.4` (line 899), which should have:
    - `supportsXhigh = true` (gpt-5.4 is in line 490)
    - `defaultEffort = "high"` (gpt-5.4 branches to line 513)
    
    **impact**: 
    1. user with `model: "gpt-5-mini"` and `reasoningEffort: "xhigh"` gets silently downgraded to "high" (line 534–536)
    2. default effort is "minimal" instead of "high" (line 515)
    3. no warning when downgrade happens
    
    the fix is to use the *normalized* model name for reasoning config when it differs from the original. after line 899 (where `body.model = normalizedModel`), pass the normalized name to reasoning config on line 996 instead of `lookupModel`. keep `lookupModel` for config lookup (line 887) but use `normalizedModel` for reasoning params:
    
    ```typescript
    const reasoningConfig = resolveReasoningConfig(
        normalizedModel,  // use resolved model for reasoning, not original
        modelConfig,
        body,
    );
    ```
    
    How can I resolve this? If you propose a fix, please make it concise.

    Fix in Codex

  2. lib/request/request-transformer.ts, line 473-529 (link)

    legacy alias default reasoning effort is wrong

    gpt-5-mini and gpt-5-nano both canonicalize to gpt-5.4 (via MODEL_MAP), which has a default reasoning effort of "high". but getReasoningConfig is called with the original model name, so for "gpt-5-mini":

    • isGpt54General = false (raw name doesn't contain "gpt-5.4")
    • supportsXhigh = false
    • isLightweight = true (contains "mini")
    • defaultEffort = "minimal" ← wrong; should be "high"

    same for "gpt-5-nano" and "gpt-5". canonicalSupportsXhigh (line 460-463) was introduced to protect explicit xhigh requests but is never used in the defaultEffort path. fix: factor canonicalSupportsXhigh into the default:

    const effectiveSupportsXhigh = supportsXhigh || canonicalSupportsXhigh;
    
    const defaultEffort: ReasoningConfig["effort"] = isCodexMini
        ? "medium"
        : isGpt5Codex
            ? "high"
            : isGpt53Codex || isGpt52Codex
                ? "xhigh"
            : effectiveSupportsXhigh
            ? "high"
            : isLightweight
                ? "minimal"
                : "medium";

    this is a real regression: users with gpt-5-mini in their opencode config will silently get near-zero reasoning while gpt-5.4 users get full high effort, contradicting the PR's stated intent that these aliases are equivalent.

  3. lib/request/request-transformer.ts, line 473-530 (link)

    isLightweight fires on legacy aliases that now resolve to gpt-5.4, causing wrong default effort

    gpt-5-mini and gpt-5-nano both now map to gpt-5.4 via normalizeModel, but getReasoningConfig still evaluates isLightweight against the pre-normalization model name string. because normalizedName.includes("mini") and normalizedName.includes("nano") are true, isLightweight = true, and defaultEffort resolves to "minimal" (line 527-528).

    the problem: gpt-5.4 called directly defaults to "high" (since isGpt54General = truesupportsXhigh = truedefaultEffort = "high"), but gpt-5-mini called with no effort preference reaches the same api endpoint with effort = "minimal". the DATED_ALIAS_EFFORT_SUFFIXES list (none, low, medium, high, xhigh) does not include minimal for gpt-5.4, so this mismatch may surface as api rejections for callers who omit an explicit effort.

    there's a parallel fix already present for xhigh (canonicalSupportsXhigh at line 460-463 ensures xhigh is not downgraded for canonical-gpt-5.4 aliases), but no equivalent guard for isLightweight:

    // after computing isGpt54General etc., add a canonical lightweight guard
    const canonicalIsLightweight =
        isLightweight &&
        canonicalModelName !== "gpt-5.4" &&
        canonicalModelName !== "gpt-5.4-pro" &&
        canonicalModelName !== "gpt-5.2";
    
    // then in the defaultEffort ternary chain, replace isLightweight with canonicalIsLightweight

    confirmed by the test at test/request-transformer.test.ts:2103 which asserts effort === "minimal" for gpt-5-mini — this test is encoding the wrong behavior if gpt-5.4 does not accept minimal as a valid reasoning level.

Fix All in Codex

Last reviewed commit: 417fa71

Context used:

  • Rule used - What: Every code change must explain how it defend... (source)

- add gpt-5.4/gpt-5.4-pro snapshot alias normalization and remap legacy gpt-5* aliases to gpt-5.4
- split gpt-5.4-pro into an isolated prompt family/cache key while preserving fallback behavior
- update OpenCode templates/docs for 1M context on gpt-5.4* and expand regression coverage across model mapping/family tests

Co-authored-by: Codex <noreply@openai.com>
@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai Bot commented Mar 6, 2026

Note

Reviews paused

It looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the reviews.auto_review.auto_pause_after_reviewed_commits setting.

Use the following commands to manage reviews:

  • @coderabbitai resume to resume automatic reviews.
  • @coderabbitai review to trigger a single review.

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • 🔍 Trigger review
📝 Walkthrough

Walkthrough

Normalizes legacy GPT‑5 aliases to gpt-5.4, adds a gpt-5.4‑pro family with dated snapshot aliases and isolated prompts/cache, raises gpt-5.4 context windows to 1,000,000 tokens, updates docs/tests, and adds a global accounts fallback with seeding and concurrency handling.

Changes

Cohort / File(s) Summary
Version
package.json
Bumped package to 5.4.3.
Model mapping & normalization
lib/request/helpers/model-map.ts, lib/request/request-transformer.ts, index.ts
Default/legacy GPT‑5 fallback changed to gpt-5.4; legacy aliases (gpt-5, gpt-5-mini, gpt-5-nano) remapped to gpt-5.4; added dated snapshot alias expansion for gpt-5.4-2026-03-05* and gpt-5.4-pro-2026-03-05*.
Prompt & instructions
lib/prompts/codex.ts, test/codex-prompts.test.ts, test/codex.test.ts
Added gpt-5.4-pro to ModelFamily and MODEL_FAMILIES; mapped pro family to prompt/cache/instructions with isolated cache key; updated related tests.
Config: context window & compaction
config/opencode-legacy.json, config/opencode-modern.json, config/README.md, config/README.md
Raised gpt-5.4 limit.context from 272000 to 1000000; documented OpenCode global compaction tuning (model_context_window=1000000, model_auto_compact_token_limit=900000).
Docs & guides
CHANGELOG.md, README.md, docs/configuration.md, docs/getting-started.md, docs/development/TESTING.md
Added v5.4.3 notes; documented alias normalization, snapshot and legacy alias guidance, updated Models table to show 1,000,000 context for gpt-5.4*, and added auto‑compaction configuration examples.
Tests — normalization & mapping
test/edge-cases.test.ts, test/gpt54-models.test.ts, test/model-map.test.ts, test/request-transformer.test.ts, test/property/transformer.property.test.ts
Updated/added tests to expect gpt-5.4 defaults, snapshot alias normalization, legacy alias mapping to gpt-5.4, and codex vs gpt-5 prioritization; adjusted many expectations accordingly.
Storage: global fallback & seeding
lib/storage.ts, test/storage.test.ts
Added global accounts fallback loader and seed-with-lock logic; on ENOENT attempt migration then global fallback; optionally seed project storage under lock and handle concurrency, access errors, and corrupted global storage; extensive tests added.
Misc / scaffolding
docs/development/TESTING.md, other small docs/tests`
Aligned test scaffolding, expectations, and README examples with the new normalization and context sizing.

Sequence Diagram(s)

sequenceDiagram
  participant Client
  participant Loader as loadAccountsInternal
  participant ProjectFS as Project Storage FS
  participant Migrator as Legacy Migrator
  participant GlobalFS as Global Storage FS
  participant Persister as Persister

  Client->>Loader: loadAccounts(projectPath)
  Loader->>ProjectFS: read project storage
  alt project file exists
    ProjectFS-->>Loader: accounts
    Loader-->>Client: accounts
  else ENOENT (missing)
    Loader->>Migrator: attempt migrate legacy storage
    alt migration succeeded
      Migrator-->>Loader: migratedAccounts
      Loader->>Persister: persist migratedAccounts to projectPath
      Persister-->>Loader: persisted
      Loader-->>Client: migratedAccounts
    else migration absent/failed
      Loader->>GlobalFS: loadGlobalAccountsFallback(globalPath)
      alt global accounts found
        GlobalFS-->>Loader: globalAccounts
        Loader->>Persister: seed projectPath with globalAccounts (withStorageLock)
        Persister-->>Loader: seeded / seed failed
        Loader-->>Client: globalAccounts
      else no global fallback / corrupted
        GlobalFS-->>Loader: null / error
        Loader-->>Client: null / error
      end
    end
  end
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

Poem

🐇 I hopped through aliases, new and old,
gpt-5.4 now shining bold,
A pro sibling prances with its key,
A million tokens set us free —
Global storage lent a hand for me!

🚥 Pre-merge checks | ✅ 3
✅ Passed checks (3 passed)
Check name Status Explanation
Title check ✅ Passed The title accurately summarizes the main objective: adding GPT-5.4 support with version bump to 5.4.3. It directly corresponds to the primary changes across configuration, normalization, documentation, and tests.
Docstring Coverage ✅ Passed Docstring coverage is 95.65% which is sufficient. The required threshold is 80.00%.
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
  • 📝 Generate docstrings (stacked PR)
  • 📝 Generate docstrings (commit on current branch)
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch feat/gpt-5.4.3-opencode-support

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

@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai Bot commented Mar 6, 2026

Note

Unit test generation is a public access feature. Expect some limitations and changes as we gather feedback and continue to improve it.


Generating unit tests... This may take up to 20 minutes.

@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai Bot commented Mar 6, 2026

Request timed out after 900000ms (requestId=39ea9e9d-6cf2-4217-a6a0-27749c0b7197)

@ndycode
Copy link
Copy Markdown
Owner Author

ndycode commented Mar 6, 2026

@greptile-ai @coderabbitai review

@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai Bot commented Mar 6, 2026

@ndycode Sure! I'll kick off a review of PR #67 right away.

✅ Actions performed

Review triggered.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

Comment thread test/gpt54-models.test.ts Outdated
Copy link
Copy Markdown
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

🧹 Nitpick comments (4)
test/edge-cases.test.ts (1)

42-45: Add one explicit canonical Pro-case assertion for stronger coverage.

Since this block now documents mixed-separator fallback behavior, consider also asserting a canonical Pro input (e.g., gpt-5.4-pro-high) in the same test group to keep Pro-family normalization guarded.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@test/edge-cases.test.ts` around lines 42 - 45, Add a direct assertion for a
canonical Pro-form input to this test group to ensure Pro-family normalization
is preserved: call normalizeModel with a canonical Pro variant like
"gpt-5.4-pro-high" and assert it returns the Pro canonical form (e.g.,
"gpt-5.4-pro"); place this expect alongside the existing expectations in the
same test block that contains normalizeModel("gpt_5.4-high") and
normalizeModel("gpt-5_4 pro").
lib/request/helpers/model-map.ts (1)

70-75: Consider generating dated alias variants to reduce duplication drift.

These blocks are correct but highly repetitive; a small helper would make future snapshot rollovers less error-prone.

♻️ Proposed refactor
+const EFFORT_SUFFIXES = ["", "-none", "-low", "-medium", "-high", "-xhigh"] as const;
+
+function expandAliasFamily(prefix: string, target: string): Record<string, string> {
+	return Object.fromEntries(
+		EFFORT_SUFFIXES.map((suffix) => [`${prefix}${suffix}`, target]),
+	);
+}
+
 export const MODEL_MAP: Record<string, string> = {
@@
-	"gpt-5.4-2026-03-05": "gpt-5.4",
-	"gpt-5.4-2026-03-05-none": "gpt-5.4",
-	"gpt-5.4-2026-03-05-low": "gpt-5.4",
-	"gpt-5.4-2026-03-05-medium": "gpt-5.4",
-	"gpt-5.4-2026-03-05-high": "gpt-5.4",
-	"gpt-5.4-2026-03-05-xhigh": "gpt-5.4",
+	...expandAliasFamily("gpt-5.4-2026-03-05", "gpt-5.4"),
@@
-	"gpt-5.4-pro-2026-03-05": "gpt-5.4-pro",
-	"gpt-5.4-pro-2026-03-05-none": "gpt-5.4-pro",
-	"gpt-5.4-pro-2026-03-05-low": "gpt-5.4-pro",
-	"gpt-5.4-pro-2026-03-05-medium": "gpt-5.4-pro",
-	"gpt-5.4-pro-2026-03-05-high": "gpt-5.4-pro",
-	"gpt-5.4-pro-2026-03-05-xhigh": "gpt-5.4-pro",
+	...expandAliasFamily("gpt-5.4-pro-2026-03-05", "gpt-5.4-pro"),

Also applies to: 86-91

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@lib/request/helpers/model-map.ts` around lines 70 - 75, Replace the repeated
dated alias entries with a small helper that generates those variants: implement
a function (e.g., generateDatedAliases or addDatedAliases) that takes a base
alias like "gpt-5.4" and a date string "2026-03-05" and returns the mapping keys
("gpt-5.4-2026-03-05", "gpt-5.4-2026-03-05-none", "-low", "-medium", "-high",
"-xhigh") mapped to the base alias; then call that helper when building the
model map instead of listing the six lines for each base model (the current
literal keys such as "gpt-5.4-2026-03-05" should be removed and replaced by the
helper invocation). Ensure the helper is reusable and used similarly for the
block at lines 86-91.
test/gpt54-models.test.ts (1)

232-235: Clarify test naming to match the new fallback expectation.

The expectation now resolves to gpt-5.4 via generic GPT-5 fallback, so the current wording can read as contradictory.

✏️ Suggested rename for clarity
-		it("should not match gpt-5.4x patterns as gpt-5.4", () => {
+		it("should route gpt-5.4x patterns through generic GPT-5 fallback", () => {
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@test/gpt54-models.test.ts` around lines 232 - 235, The test description is
misleading given the new behavior where normalizeModel("gpt-5.40") and
normalizeModel("gpt-5.44") resolve via the generic GPT-5 fallback to "gpt-5.4";
update the test case name to clearly state it expects generic GPT-5 fallback
rather than “boundary-aware matching” (locate the test that calls normalizeModel
with "gpt-5.40"/"gpt-5.44" and change its describe/it string to mention “generic
GPT-5 fallback to gpt-5.4”).
test/codex-prompts.test.ts (1)

483-504: Strengthen the cache-isolation assertion with a negative check.

This test verifies the pro cache file is written, but it would still pass if the base gpt-5.4 cache key were also written. Add a “base key is not written” assertion to enforce true isolation.

♻️ Suggested test hardening
 					const writeTargets = mockedWriteFile.mock.calls.map(([target]) => String(target));
 					expect(rawGitHubCall?.[0]).toContain("gpt_5_2_prompt.md");
 					expect(writeTargets.some((target) => target.includes("gpt-5.4-pro-instructions.md"))).toBe(true);
+					expect(
+						writeTargets.some((target) => /gpt-5\.4-instructions\.md$/.test(target)),
+					).toBe(false);
 				});
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@test/codex-prompts.test.ts` around lines 483 - 504, The test "should map
gpt-5.4-pro prompts to gpt_5_2 prompt file with isolated cache key" currently
only asserts the pro cache file was written; add a negative assertion that the
base cache key file for the non-pro model is NOT written to enforce isolation.
After collecting writeTargets from mockedWriteFile.mock.calls, add an
expectation that writeTargets does not include a target containing
"gpt-5.4-instructions.md" (or the exact base filename used by
getCodexInstructions), e.g., expect(writeTargets.some(t =>
t.includes("gpt-5.4-instructions.md"))).toBe(false), referencing
getCodexInstructions, mockedWriteFile and writeTargets to locate the change.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@README.md`:
- Line 136: Update the README note for `gpt-5.4-pro` to clarify the scope of the
fallback: change the phrasing around "fallback default is `gpt-5.4-pro ->
gpt-5.4`" to explicitly state this behavior occurs only when the fallback policy
is enabled (e.g., "If fallback policy is enabled, default fallback: `gpt-5.4-pro
-> gpt-5.4`"); reference the `gpt-5.4-pro` token and the existing fallback
phrase so the rewording makes the condition (fallback policy enabled) explicit.

---

Nitpick comments:
In `@lib/request/helpers/model-map.ts`:
- Around line 70-75: Replace the repeated dated alias entries with a small
helper that generates those variants: implement a function (e.g.,
generateDatedAliases or addDatedAliases) that takes a base alias like "gpt-5.4"
and a date string "2026-03-05" and returns the mapping keys
("gpt-5.4-2026-03-05", "gpt-5.4-2026-03-05-none", "-low", "-medium", "-high",
"-xhigh") mapped to the base alias; then call that helper when building the
model map instead of listing the six lines for each base model (the current
literal keys such as "gpt-5.4-2026-03-05" should be removed and replaced by the
helper invocation). Ensure the helper is reusable and used similarly for the
block at lines 86-91.

In `@test/codex-prompts.test.ts`:
- Around line 483-504: The test "should map gpt-5.4-pro prompts to gpt_5_2
prompt file with isolated cache key" currently only asserts the pro cache file
was written; add a negative assertion that the base cache key file for the
non-pro model is NOT written to enforce isolation. After collecting writeTargets
from mockedWriteFile.mock.calls, add an expectation that writeTargets does not
include a target containing "gpt-5.4-instructions.md" (or the exact base
filename used by getCodexInstructions), e.g., expect(writeTargets.some(t =>
t.includes("gpt-5.4-instructions.md"))).toBe(false), referencing
getCodexInstructions, mockedWriteFile and writeTargets to locate the change.

In `@test/edge-cases.test.ts`:
- Around line 42-45: Add a direct assertion for a canonical Pro-form input to
this test group to ensure Pro-family normalization is preserved: call
normalizeModel with a canonical Pro variant like "gpt-5.4-pro-high" and assert
it returns the Pro canonical form (e.g., "gpt-5.4-pro"); place this expect
alongside the existing expectations in the same test block that contains
normalizeModel("gpt_5.4-high") and normalizeModel("gpt-5_4 pro").

In `@test/gpt54-models.test.ts`:
- Around line 232-235: The test description is misleading given the new behavior
where normalizeModel("gpt-5.40") and normalizeModel("gpt-5.44") resolve via the
generic GPT-5 fallback to "gpt-5.4"; update the test case name to clearly state
it expects generic GPT-5 fallback rather than “boundary-aware matching” (locate
the test that calls normalizeModel with "gpt-5.40"/"gpt-5.44" and change its
describe/it string to mention “generic GPT-5 fallback to gpt-5.4”).

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: 3c346fa4-8c77-442d-860d-4e0208e19a34

📥 Commits

Reviewing files that changed from the base of the PR and between 15b7f01 and 25b0ee2.

⛔ Files ignored due to path filters (1)
  • package-lock.json is excluded by !**/package-lock.json
📒 Files selected for processing (18)
  • CHANGELOG.md
  • README.md
  • config/README.md
  • config/opencode-legacy.json
  • config/opencode-modern.json
  • docs/configuration.md
  • docs/development/TESTING.md
  • docs/getting-started.md
  • lib/prompts/codex.ts
  • lib/request/helpers/model-map.ts
  • lib/request/request-transformer.ts
  • package.json
  • test/codex-prompts.test.ts
  • test/codex.test.ts
  • test/edge-cases.test.ts
  • test/gpt54-models.test.ts
  • test/model-map.test.ts
  • test/request-transformer.test.ts

Comment thread README.md Outdated
- default unknown/empty model fallback to gpt-5.4 across transform and runtime family selection
- load global account storage when project storage is missing, then seed project-scoped storage
- align tests and internal testing docs with the new fallback behavior

Co-authored-by: Codex <noreply@openai.com>
@ndycode ndycode marked this pull request as ready for review March 6, 2026 17:42
Copy link
Copy Markdown
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.

🧹 Nitpick comments (1)
test/gpt54-models.test.ts (1)

55-63: Optional: extract snapshot date literal into a shared local constant.

Using a small constant for the repeated snapshot suffix would reduce future churn when snapshots roll forward.

♻️ Optional cleanup
 describe("GPT-5.4 Model Support", () => {
+	const SNAPSHOT = "2026-03-05";
+
 	describe("GPT-5.4 Model Normalization", () => {
@@
-			expect(MODEL_MAP["gpt-5.4-2026-03-05"]).toBe("gpt-5.4");
-			expect(MODEL_MAP["gpt-5.4-2026-03-05-low"]).toBe("gpt-5.4");
+			expect(MODEL_MAP[`gpt-5.4-${SNAPSHOT}`]).toBe("gpt-5.4");
+			expect(MODEL_MAP[`gpt-5.4-${SNAPSHOT}-low`]).toBe("gpt-5.4");
@@
-			expect(normalizeModel("gpt-5.4-2026-03-05")).toBe("gpt-5.4");
-			expect(normalizeModel("gpt-5.4-2026-03-05-high")).toBe("gpt-5.4");
-			expect(getNormalizedModel("gpt-5.4-2026-03-05-medium")).toBe("gpt-5.4");
+			expect(normalizeModel(`gpt-5.4-${SNAPSHOT}`)).toBe("gpt-5.4");
+			expect(normalizeModel(`gpt-5.4-${SNAPSHOT}-high`)).toBe("gpt-5.4");
+			expect(getNormalizedModel(`gpt-5.4-${SNAPSHOT}-medium`)).toBe("gpt-5.4");

Also applies to: 110-118

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@test/gpt54-models.test.ts` around lines 55 - 63, The tests repeat the
snapshot suffix "2026-03-05" in multiple expectations (MODEL_MAP checks and
normalizeModel/getNormalizedModel assertions); extract that suffix into a single
local constant (e.g., const SNAPSHOT = "-2026-03-05") at the top of the test
block and use `${SNAPSHOT}`, `${SNAPSHOT}-low`, `${SNAPSHOT}-high`,
`${SNAPSHOT}-medium` in the calls and expectations involving MODEL_MAP,
normalizeModel, and getNormalizedModel so future snapshot date updates only
require one edit.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Nitpick comments:
In `@test/gpt54-models.test.ts`:
- Around line 55-63: The tests repeat the snapshot suffix "2026-03-05" in
multiple expectations (MODEL_MAP checks and normalizeModel/getNormalizedModel
assertions); extract that suffix into a single local constant (e.g., const
SNAPSHOT = "-2026-03-05") at the top of the test block and use `${SNAPSHOT}`,
`${SNAPSHOT}-low`, `${SNAPSHOT}-high`, `${SNAPSHOT}-medium` in the calls and
expectations involving MODEL_MAP, normalizeModel, and getNormalizedModel so
future snapshot date updates only require one edit.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: 04a2e860-25e2-4922-a030-f69722dd122b

📥 Commits

Reviewing files that changed from the base of the PR and between 25b0ee2 and d6fd852.

📒 Files selected for processing (9)
  • docs/development/TESTING.md
  • index.ts
  • lib/request/request-transformer.ts
  • lib/storage.ts
  • test/edge-cases.test.ts
  • test/gpt54-models.test.ts
  • test/property/transformer.property.test.ts
  • test/request-transformer.test.ts
  • test/storage.test.ts
🚧 Files skipped from review as they are similar to previous changes (1)
  • test/edge-cases.test.ts

Comment thread lib/storage.ts
Comment thread test/storage.test.ts
Clarify fallback docs, harden fallback seeding under concurrent loads, and add regression coverage for storage fallback edge cases.

Co-authored-by: Codex <noreply@openai.com>
Copy link
Copy Markdown
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

Caution

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

⚠️ Outside diff range comments (1)
lib/storage.ts (1)

678-680: ⚠️ Potential issue | 🔴 Critical

Lock-order inversion can deadlock concurrent callers.

withFallbackSeedLock wraps a call to persistMigration, and for loadAccounts() that persister is saveAccounts (storage lock). Concurrently, withAccountStorageTransaction() already holds the storage lock and can block on the fallback lock. That creates a circular wait and hangs both operations.

🐛 Suggested fix (unify lock order by loading under storage lock with unlocked persister)
 export async function loadAccounts(): Promise<AccountStorageV3 | null> {
-  return loadAccountsInternal(saveAccounts);
+  return withStorageLock(async () => loadAccountsInternal(saveAccountsUnlocked));
 }

Also applies to: 772-795

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@lib/storage.ts` around lines 678 - 680, loadAccounts calling
loadAccountsInternal(saveAccounts) can deadlock because withFallbackSeedLock
(used by persistMigration) and the storage lock (held by
withAccountStorageTransaction) are acquired in inconsistent order; to fix,
ensure the storage lock is acquired first and the persister runs without holding
the storage lock. Concretely, modify loadAccounts/loadAccountsInternal so they
load data while holding the storage lock (use the same locking mechanism as
withAccountStorageTransaction) but pass an "unlocked" persister wrapper for
saveAccounts (or defer persistMigration/unlock inside withFallbackSeedLock) so
persistMigration/withFallbackSeedLock does not try to acquire the fallback lock
while the storage lock is held; update calls that pass saveAccounts (e.g., at
loadAccounts and the other sites noted around persistMigration) to use this
unlocked-persister wrapper or reverse lock order consistently.
🧹 Nitpick comments (2)
lib/request/helpers/model-map.ts (1)

85-97: Centralize snapshot date literal to reduce update drift.

Using a single date constant avoids accidental mismatch between base/pro snapshot mappings in future bumps.

♻️ Suggested refactor
+const GPT_54_SNAPSHOT_DATE = "2026-03-05" as const;
...
-	...expandDatedAliases("gpt-5.4-2026-03-05", "gpt-5.4"),
+	...expandDatedAliases(`gpt-5.4-${GPT_54_SNAPSHOT_DATE}`, "gpt-5.4"),
...
-	...expandDatedAliases("gpt-5.4-pro-2026-03-05", "gpt-5.4-pro"),
+	...expandDatedAliases(`gpt-5.4-pro-${GPT_54_SNAPSHOT_DATE}`, "gpt-5.4-pro"),
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@lib/request/helpers/model-map.ts` around lines 85 - 97, The mappings
duplicate the snapshot date literal in calls to expandDatedAliases (e.g.,
"gpt-5.4-2026-03-05" and "gpt-5.4-pro-2026-03-05"); introduce a single constant
(e.g., MODEL_SNAPSHOT_DATE or SNAPSHOT_DATE) near the top of model-map.ts and
replace those inline dated strings with template composition using
expandDatedAliases(`${base}-${MODEL_SNAPSHOT_DATE}`, base) (or equivalent),
updating both "gpt-5.4" and "gpt-5.4-pro" usages so future bumps only change one
constant; ensure the constant name is exported or scoped appropriately for tests
if needed.
test/codex-prompts.test.ts (1)

45-48: Avoid brittle fixed-length assertion for MODEL_FAMILIES.

Pinning exact length makes this test fail on valid future family additions unrelated to behavior.

♻️ Suggested test adjustment
-				expect(MODEL_FAMILIES.length).toBe(7);
+				expect(MODEL_FAMILIES.length).toBeGreaterThanOrEqual(7);
+				expect(new Set(MODEL_FAMILIES).size).toBe(MODEL_FAMILIES.length);
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@test/codex-prompts.test.ts` around lines 45 - 48, The test in it("should be a
readonly array") is brittle because it asserts MODEL_FAMILIES.length === 7;
change the assertion to avoid fixed-length checks: keep the
Array.isArray(MODEL_FAMILIES) expectation and replace the length equality check
with either a readonly/immutability assertion (e.g., ensure MODEL_FAMILIES
cannot be mutated or is frozen) or an inclusion-based check (e.g.,
expect(MODEL_FAMILIES).toEqual(expect.arrayContaining([...knownFamilies]))),
referencing the MODEL_FAMILIES symbol and the same test block when you make the
change.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@lib/storage.ts`:
- Around line 776-780: The catch currently treats any fs.access failure as
"missing" and proceeds to seeding; change the catch to only continue when the
error indicates the file does not exist (error.code === 'ENOENT') and rethrow
for other errors (e.g., EACCES, EPERM). Locate the fs.access call and its
surrounding try/catch that references seedPath and alter the catch to inspect
the caught error object and rethrow unless error.code === 'ENOENT', so only true
"file missing" cases fall through to seeding.

---

Outside diff comments:
In `@lib/storage.ts`:
- Around line 678-680: loadAccounts calling loadAccountsInternal(saveAccounts)
can deadlock because withFallbackSeedLock (used by persistMigration) and the
storage lock (held by withAccountStorageTransaction) are acquired in
inconsistent order; to fix, ensure the storage lock is acquired first and the
persister runs without holding the storage lock. Concretely, modify
loadAccounts/loadAccountsInternal so they load data while holding the storage
lock (use the same locking mechanism as withAccountStorageTransaction) but pass
an "unlocked" persister wrapper for saveAccounts (or defer
persistMigration/unlock inside withFallbackSeedLock) so
persistMigration/withFallbackSeedLock does not try to acquire the fallback lock
while the storage lock is held; update calls that pass saveAccounts (e.g., at
loadAccounts and the other sites noted around persistMigration) to use this
unlocked-persister wrapper or reverse lock order consistently.

---

Nitpick comments:
In `@lib/request/helpers/model-map.ts`:
- Around line 85-97: The mappings duplicate the snapshot date literal in calls
to expandDatedAliases (e.g., "gpt-5.4-2026-03-05" and "gpt-5.4-pro-2026-03-05");
introduce a single constant (e.g., MODEL_SNAPSHOT_DATE or SNAPSHOT_DATE) near
the top of model-map.ts and replace those inline dated strings with template
composition using expandDatedAliases(`${base}-${MODEL_SNAPSHOT_DATE}`, base) (or
equivalent), updating both "gpt-5.4" and "gpt-5.4-pro" usages so future bumps
only change one constant; ensure the constant name is exported or scoped
appropriately for tests if needed.

In `@test/codex-prompts.test.ts`:
- Around line 45-48: The test in it("should be a readonly array") is brittle
because it asserts MODEL_FAMILIES.length === 7; change the assertion to avoid
fixed-length checks: keep the Array.isArray(MODEL_FAMILIES) expectation and
replace the length equality check with either a readonly/immutability assertion
(e.g., ensure MODEL_FAMILIES cannot be mutated or is frozen) or an
inclusion-based check (e.g.,
expect(MODEL_FAMILIES).toEqual(expect.arrayContaining([...knownFamilies]))),
referencing the MODEL_FAMILIES symbol and the same test block when you make the
change.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: 21fbc77f-2db9-4b8f-bce6-02ca3ed28583

📥 Commits

Reviewing files that changed from the base of the PR and between d6fd852 and 8b8604a.

📒 Files selected for processing (7)
  • README.md
  • lib/request/helpers/model-map.ts
  • lib/storage.ts
  • test/codex-prompts.test.ts
  • test/edge-cases.test.ts
  • test/gpt54-models.test.ts
  • test/storage.test.ts
🚧 Files skipped from review as they are similar to previous changes (1)
  • test/edge-cases.test.ts

Comment thread lib/storage.ts Outdated
Comment thread test/request-transformer.test.ts
Harden storage fallback lock/error handling, preserve xhigh for GPT-5 legacy aliases mapped to GPT-5.4, and address review nits in tests/model map.

Co-authored-by: Codex <noreply@openai.com>
Copy link
Copy Markdown
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.

🧹 Nitpick comments (3)
lib/request/request-transformer.ts (1)

546-551: Add a clarifying comment explaining the lightweight default behavior for legacy aliases.

The code correctly preserves explicit xhigh requests for legacy aliases like gpt-5-mini and gpt-5-nano (which normalize to gpt-5.4). However, the defaultEffort calculation still applies the isLightweight check based on the original model name, resulting in defaultEffort = "minimal" for these aliases.

This behavior is intentional (respecting the user's lightweight model choice while allowing explicit xhigh override), but a brief comment at lines 519–529 would clarify that legacy aliases intentionally retain lightweight defaults despite canonical support for higher efforts.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@lib/request/request-transformer.ts` around lines 546 - 551, Add a short
clarifying comment near the defaultEffort/isLightweight logic to explain that
legacy aliases (e.g., gpt-5-mini/gpt-5-nano) normalize to a canonical model
(gpt-5.4) which supports higher efforts, but we intentionally keep the
lightweight default based on the original alias; also note that explicit
requests for xhigh are still honored via supportsRequestedXhigh (computed from
supportsXhigh and canonicalSupportsXhigh) and the subsequent effort downgrade
logic that sets effort = "high" when xhigh isn't supported.
test/storage.test.ts (1)

1828-1874: The “concurrent” seed test is effectively serialized.

Line 1867 runs Promise.all, but each loadAccounts() call acquires the same storage lock, so this doesn’t truly exercise parallel seed execution. Consider renaming the test or adjusting the setup so the intent matches behavior.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@test/storage.test.ts` around lines 1828 - 1874, The test name claims
concurrency but loadAccounts() serializes via the storage lock; either rename
the test to reflect serialized behavior (e.g., "seeds project storage once when
loadAccounts serializes") or change the setup to truly exercise parallel seeding
by disabling/mocking the lock used inside loadAccounts (mock the storage
lock/acquire function used by loadAccounts to a no-op so both Promise.all calls
run concurrently) and keep the existing renameSpy/projectSeedWriteCount
assertions; refer to loadAccounts, setStoragePath, getStoragePath, and the
renameSpy/projectSeedWriteCount helpers when making the change.
lib/storage.ts (1)

121-128: withFallbackSeedLock appears redundant in the current call graph.

loadAccountsInternal is reached via loadAccounts (Line 679) and withAccountStorageTransaction (Line 868-870), both already serialized by withStorageLock. Consider removing this extra mutex (or documenting future intent) to reduce locking complexity.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@lib/storage.ts` around lines 121 - 128, withFallbackSeedLock and its
fallbackSeedMutex add redundant serialization because loadAccountsInternal is
already called under withStorageLock via loadAccounts and
withAccountStorageTransaction; remove the extra mutex to simplify locking:
delete the fallbackSeedMutex and the withFallbackSeedLock function and update
any callers of withFallbackSeedLock (e.g., places calling loadAccountsInternal
through withFallbackSeedLock) to call the underlying async function directly (or
rely on withStorageLock), then run tests to ensure no race conditions;
alternatively, if you intend to keep it for a future reason, add a clear comment
above withFallbackSeedLock documenting that intent and why it is required
despite withStorageLock (reference symbols: withFallbackSeedLock,
fallbackSeedMutex, loadAccountsInternal, loadAccounts,
withAccountStorageTransaction, withStorageLock).
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Nitpick comments:
In `@lib/request/request-transformer.ts`:
- Around line 546-551: Add a short clarifying comment near the
defaultEffort/isLightweight logic to explain that legacy aliases (e.g.,
gpt-5-mini/gpt-5-nano) normalize to a canonical model (gpt-5.4) which supports
higher efforts, but we intentionally keep the lightweight default based on the
original alias; also note that explicit requests for xhigh are still honored via
supportsRequestedXhigh (computed from supportsXhigh and canonicalSupportsXhigh)
and the subsequent effort downgrade logic that sets effort = "high" when xhigh
isn't supported.

In `@lib/storage.ts`:
- Around line 121-128: withFallbackSeedLock and its fallbackSeedMutex add
redundant serialization because loadAccountsInternal is already called under
withStorageLock via loadAccounts and withAccountStorageTransaction; remove the
extra mutex to simplify locking: delete the fallbackSeedMutex and the
withFallbackSeedLock function and update any callers of withFallbackSeedLock
(e.g., places calling loadAccountsInternal through withFallbackSeedLock) to call
the underlying async function directly (or rely on withStorageLock), then run
tests to ensure no race conditions; alternatively, if you intend to keep it for
a future reason, add a clear comment above withFallbackSeedLock documenting that
intent and why it is required despite withStorageLock (reference symbols:
withFallbackSeedLock, fallbackSeedMutex, loadAccountsInternal, loadAccounts,
withAccountStorageTransaction, withStorageLock).

In `@test/storage.test.ts`:
- Around line 1828-1874: The test name claims concurrency but loadAccounts()
serializes via the storage lock; either rename the test to reflect serialized
behavior (e.g., "seeds project storage once when loadAccounts serializes") or
change the setup to truly exercise parallel seeding by disabling/mocking the
lock used inside loadAccounts (mock the storage lock/acquire function used by
loadAccounts to a no-op so both Promise.all calls run concurrently) and keep the
existing renameSpy/projectSeedWriteCount assertions; refer to loadAccounts,
setStoragePath, getStoragePath, and the renameSpy/projectSeedWriteCount helpers
when making the change.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: 37e04308-c2bb-47cb-8b1c-46f7ba3e282f

📥 Commits

Reviewing files that changed from the base of the PR and between 8b8604a and aecb401.

📒 Files selected for processing (6)
  • lib/request/helpers/model-map.ts
  • lib/request/request-transformer.ts
  • lib/storage.ts
  • test/codex-prompts.test.ts
  • test/request-transformer.test.ts
  • test/storage.test.ts
🚧 Files skipped from review as they are similar to previous changes (2)
  • test/request-transformer.test.ts
  • lib/request/helpers/model-map.ts

- remove redundant fallback seed mutex in storage path
- clarify fallback policy scope for gpt-5.4-pro docs
- rename storage fallback test to match serialized execution

Co-authored-by: Codex <noreply@openai.com>
@ndycode ndycode changed the title feat: gpt-5.4.3 patch (snapshot aliases, 1M context, pro family split) feat: add GPT-5.4 support (plugin v5.4.3) Mar 7, 2026
Copy link
Copy Markdown
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

🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@docs/getting-started.md`:
- Line 218: The summary line "Total: 21 template presets" currently folds
optional manual IDs and the wildcard "gpt-5.4*" (which implies gpt-5.4-pro) into
the count and is misleading; update the sentence to explicitly state that the 21
shipped templates exclude optional/manual Spark IDs and to replace "gpt-5.4*"
with a clear listing or note that gpt-5.4-pro is not part of the shipped
templates (refer to the existing mentions of "Total: 21 template presets",
"gpt-5.4*" and "gpt-5.4-pro") so readers understand which IDs are counted and
which are optional/excluded.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: c303b000-23d2-4d2d-b060-25b8f59148d6

📥 Commits

Reviewing files that changed from the base of the PR and between aecb401 and 4120d95.

📒 Files selected for processing (3)
  • docs/getting-started.md
  • lib/storage.ts
  • test/storage.test.ts

Comment thread docs/getting-started.md Outdated
ndycode and others added 3 commits March 7, 2026 09:13
Co-authored-by: Codex <noreply@openai.com>
Co-authored-by: Codex <noreply@openai.com>
- add JSDoc to storage locking/fallback transaction helpers
- add JSDoc to request transformer collaboration/config helpers
- clarify legacy lightweight GPT-5 alias default reasoning behavior

Co-authored-by: Codex <noreply@openai.com>
@ndycode
Copy link
Copy Markdown
Owner Author

ndycode commented Mar 7, 2026

@coderabbitai review

@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai Bot commented Mar 7, 2026

✅ Actions performed

Review triggered.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

Copy link
Copy Markdown
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.

Caution

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

⚠️ Outside diff range comments (2)
lib/request/request-transformer.ts (2)

390-399: ⚠️ Potential issue | 🟠 Major

Filter direct name-based tools here too.

This helper only inspects function.name, but the same tools payload is treated as supporting both tool.name and tool.function.name in extractRuntimeToolNames(). A direct request_user_input entry will survive non-plan mode and still be exposed to the model.

Suggested fix
 const filtered = tools.filter((entry) => {
   if (!entry || typeof entry !== "object") return true;
+  const directName = (entry as { name?: unknown }).name;
+  if (typeof directName === "string" && PLAN_MODE_ONLY_TOOLS.has(directName)) {
+    removed++;
+    return false;
+  }
   const functionDef = (entry as { function?: unknown }).function;
   if (!functionDef || typeof functionDef !== "object") return true;
   const name = (functionDef as { name?: unknown }).name;
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@lib/request/request-transformer.ts` around lines 390 - 399, The filter
currently only checks (entry as { function?: unknown }).function.name and thus
misses tools specified as a top-level name; update the predicate used for const
filtered = tools.filter(...) to also inspect (entry as { name?: unknown }).name
(or check both function.name and name) and treat either matching
PLAN_MODE_ONLY_TOOLS the same way as function.name: increment removed and filter
out the entry when a match is found; ensure behavior aligns with
extractRuntimeToolNames() by checking both places and keeping the removed
counter in sync.

484-577: ⚠️ Potential issue | 🔴 Critical

Use the canonical model for default reasoning, not just xhigh preservation.

canonicalModelName only affects supportsRequestedXhigh here. The default-effort branch still follows the raw alias, so gpt-5 stays on medium and gpt-5-mini/gpt-5-nano stay on minimal even though transformRequestBody() sends gpt-5.4. That leaves legacy aliases on different defaults than their canonical target and can still send minimal with the normalized model.

Suggested fix
- const canonicalSupportsXhigh =
-   canonicalModelName === "gpt-5.4" ||
-   canonicalModelName === "gpt-5.4-pro" ||
-   canonicalModelName === "gpt-5.2";
+ const canonicalIsGpt54General = canonicalModelName === "gpt-5.4";
+ const canonicalIsGpt54Pro = canonicalModelName === "gpt-5.4-pro";
+ const canonicalIsGpt52General = canonicalModelName === "gpt-5.2";
+ const canonicalSupportsXhigh =
+   canonicalIsGpt54General ||
+   canonicalIsGpt54Pro ||
+   canonicalIsGpt52General;
...
- const supportsXhigh =
-   isGpt54General ||
-   isGpt54Pro ||
-   isGpt52General ||
-   isGpt53Codex ||
-   isGpt52Codex ||
-   isCodexMax;
+ const supportsXhigh =
+   canonicalSupportsXhigh ||
+   isGpt53Codex ||
+   isGpt52Codex ||
+   isCodexMax;
...
-       : supportsXhigh
-         ? "high"
-         : isLightweight
-           ? "minimal"
-           : "medium";
+       : supportsXhigh
+         ? "high"
+         : isLightweight && !canonicalSupportsXhigh
+           ? "minimal"
+           : "medium";
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@lib/request/request-transformer.ts` around lines 484 - 577, The defaultEffort
calculation uses alias-based flags (e.g., isCodexMini, isLightweight,
isGpt51General) derived from normalizedName instead of the canonical model,
causing legacy aliases normalized to canonicalModelName (like gpt-5.4) to keep
the wrong default; update defaultEffort to base its decisions on
canonical-model-derived flags (create or reuse canonical variants such as
canonicalIsCodexMini, canonicalIsGpt5Codex,
canonicalIsGpt53Codex/canonicalIsGpt52Codex, canonicalIsLightweight or use
canonicalSupportsXhigh) so the fallback default aligns with canonicalModelName,
and ensure any subsequent adjustments (the isCodexMini clamp and
supportsRequestedXhigh logic) use the canonical flags where appropriate
(reference defaultEffort, isCodexMini, canonicalSupportsXhigh,
supportsRequestedXhigh, and transformRequestBody).
🧹 Nitpick comments (1)
lib/storage.ts (1)

780-811: Consider documenting the multi-process race limitation.

The seeding logic correctly uses fs.access with ENOENT checking to guard against single-process races within withStorageLock. However, there's a TOCTOU window between fs.access (line 783) and persistMigration (line 798) where another process could seed concurrently.

Per the PR objectives, this is a known limitation. A brief inline comment would help future maintainers understand this trade-off.

📝 Suggested documentation
       if (persistMigration) {
         const seedPath = getStoragePath();
+        // Note: withStorageLock protects against single-process races, but concurrent
+        // processes seeding simultaneously may still race (known Windows gap).
+        // renameWithWindowsRetry mitigates write-side EPERM/EBUSY but doesn't prevent
+        // the TOCTOU between fs.access and persistMigration.
         try {
           await fs.access(seedPath);
           return globalFallback;
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@lib/storage.ts` around lines 780 - 811, Add a brief inline comment near the
fs.access ... persistMigration(globalFallback) block documenting the known
TOCTOU multi-process race: explain that while fs.access plus withStorageLock
guards single-process races, there is a window between the access check and the
call to persistMigration where another process could seed concurrently, and that
this is a deliberate/accepted limitation; reference the symbols getStoragePath,
fs.access, persistMigration(globalFallback), and globalFallback so future
maintainers can find the exact region.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Outside diff comments:
In `@lib/request/request-transformer.ts`:
- Around line 390-399: The filter currently only checks (entry as { function?:
unknown }).function.name and thus misses tools specified as a top-level name;
update the predicate used for const filtered = tools.filter(...) to also inspect
(entry as { name?: unknown }).name (or check both function.name and name) and
treat either matching PLAN_MODE_ONLY_TOOLS the same way as function.name:
increment removed and filter out the entry when a match is found; ensure
behavior aligns with extractRuntimeToolNames() by checking both places and
keeping the removed counter in sync.
- Around line 484-577: The defaultEffort calculation uses alias-based flags
(e.g., isCodexMini, isLightweight, isGpt51General) derived from normalizedName
instead of the canonical model, causing legacy aliases normalized to
canonicalModelName (like gpt-5.4) to keep the wrong default; update
defaultEffort to base its decisions on canonical-model-derived flags (create or
reuse canonical variants such as canonicalIsCodexMini, canonicalIsGpt5Codex,
canonicalIsGpt53Codex/canonicalIsGpt52Codex, canonicalIsLightweight or use
canonicalSupportsXhigh) so the fallback default aligns with canonicalModelName,
and ensure any subsequent adjustments (the isCodexMini clamp and
supportsRequestedXhigh logic) use the canonical flags where appropriate
(reference defaultEffort, isCodexMini, canonicalSupportsXhigh,
supportsRequestedXhigh, and transformRequestBody).

---

Nitpick comments:
In `@lib/storage.ts`:
- Around line 780-811: Add a brief inline comment near the fs.access ...
persistMigration(globalFallback) block documenting the known TOCTOU
multi-process race: explain that while fs.access plus withStorageLock guards
single-process races, there is a window between the access check and the call to
persistMigration where another process could seed concurrently, and that this is
a deliberate/accepted limitation; reference the symbols getStoragePath,
fs.access, persistMigration(globalFallback), and globalFallback so future
maintainers can find the exact region.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: 311fd39e-36c5-4729-924f-e9ccf116bda8

📥 Commits

Reviewing files that changed from the base of the PR and between 4120d95 and 417fa71.

📒 Files selected for processing (3)
  • docs/getting-started.md
  • lib/request/request-transformer.ts
  • lib/storage.ts

@ndycode ndycode merged commit 417fa71 into main Mar 7, 2026
3 checks passed
@ndycode ndycode deleted the feat/gpt-5.4.3-opencode-support branch March 8, 2026 03:13
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