Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
31 commits
Select commit Hold shift + click to select a range
06ec553
Merge pull request #3678 from lidge-jun/codex/promote-main-243-01a07240
lidge-jun Sep 5, 2026
116c2ac
Merge commit '44ea9576e27c6be8be7f13a86e32bb349368c54d' into codex/re…
invalid-email-address Sep 6, 2026
07b48da
Merge pull request #3785 from lidge-jun/codex/release-244-main-07c0
lidge-jun Sep 6, 2026
bcdf559
chore(release): promote validated 2.45.0 to main [skip ci]
invalid-email-address Sep 6, 2026
b0900e5
chore(release): promote 2.45.0 to main (#3813)
lidge-jun Sep 6, 2026
3970601
chore(release): prepare 2.46.0 stable promotion
invalid-email-address Sep 7, 2026
bba6322
Merge pull request #3851 from lidge-jun/codex/release-246-main
lidge-jun Sep 7, 2026
3d53e5f
release: prepare 2.47.0 from audited regression candidate
invalid-email-address Sep 7, 2026
eda8754
Merge commit '48ab3e1e66cfa6e0c873de2fafa4540ac61d6c7d' into codex/re…
invalid-email-address Sep 7, 2026
f9e3515
Merge commit '57252193b' into codex/release-247-main
invalid-email-address Sep 7, 2026
6f71931
release: promote 2.47.0 to main (#3929)
lidge-jun Sep 7, 2026
9a60256
Merge commit 'd0737cff3' into codex/release-247-main-final
invalid-email-address Sep 7, 2026
9e9b1d3
Merge commit 'f48c322c0' into codex/release-247-main-final
invalid-email-address Sep 7, 2026
947bae9
Merge commit '0d7652ad1' into codex/release-247-main-final
invalid-email-address Sep 7, 2026
f7f890f
release: apply final roster correction to main (#3933)
lidge-jun Sep 7, 2026
544ebee
release: promote 2.48.0 to main
invalid-email-address Sep 8, 2026
d24ff57
release: set main channel version 2.48.0
invalid-email-address Sep 8, 2026
9a27e86
Merge pull request #4011 from lidge-jun/codex/release-248-main
lidge-jun Sep 8, 2026
62849df
release: promote verified 2.49.0 product tree to main
lidge-jun Sep 9, 2026
2f3f736
Merge pull request #4117 from lidge-jun/codex/release-249-main-01a08498
lidge-jun Sep 9, 2026
3a3de88
release: promote verified 2.50.0 product tree to main
lidge-jun Sep 10, 2026
2d4d7a2
Merge pull request #4195 from lidge-jun/codex/release-250-main-01a08a81
lidge-jun Sep 10, 2026
cf456e8
release: promote verified 2.51.0 product tree to main
lidge-jun Sep 11, 2026
c155cc7
Merge pull request #4271 from lidge-jun/codex/release-251-main
lidge-jun Sep 11, 2026
95c4875
release: promote verified 2.52.0 product tree to main
lidge-jun Sep 12, 2026
4d37c35
Merge pull request #4407 from lidge-jun/codex/release-2520-main
lidge-jun Sep 12, 2026
641b05a
release: promote verified 2.53.0 product tree to main
lidge-jun Sep 13, 2026
aa05b3e
Merge pull request #4507 from lidge-jun/codex/release-2530-main
lidge-jun Sep 13, 2026
8e532c5
release: promote verified 2.54.0 product tree to main
lidge-jun Sep 13, 2026
9f7397e
Merge pull request #4540 from lidge-jun/codex/release-2540-main
lidge-jun Sep 13, 2026
1090b85
fix(reasoning): bootstrap metadata during catalog sync
luvs01 Sep 14, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
13 changes: 13 additions & 0 deletions src/codex/sync.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ import {
import { admitCodexWrite, type CodexAdmission } from "./admission";
import type { CodexCatalogSyncOptions } from "./catalog/sync";
import { resetCodexAppServerCatalogStateCache } from "./app-server-processes";
import { providerUsesReasoningMetadata, refreshReasoningMetadata } from "../providers/reasoning-metadata";

export interface CodexSyncResult {
/**
Expand Down Expand Up @@ -67,13 +68,21 @@ interface CodexSyncDeps {
admitCodexWrite?: () => CodexSyncAdmission;
currentExternalCodexModelProvider?: typeof currentExternalCodexModelProvider;
collectCodexHomeDiagnostic?: typeof collectOrcaCodexHomeDiagnostic;
refreshReasoningMetadata?: typeof refreshReasoningMetadata;
}

const defaultDeps: CodexSyncDeps = {
refreshCodexModelCatalog,
injectCodexConfig,
refreshReasoningMetadata,
};

async function refreshReasoningMetadataForSync(config: OcxConfig, deps: CodexSyncDeps): Promise<void> {
if (Object.values(config.providers).some(providerUsesReasoningMetadata)) {
await deps.refreshReasoningMetadata?.();
Comment on lines +81 to +82

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge Exclude disabled providers from metadata bootstrap

When a configuration retains a disabled OpenCode Zen/Go provider but routes only through unrelated providers, this predicate still initiates the models.dev fetch even though catalog gathering explicitly excludes disabled providers. With a missing or stale snapshot and an unavailable models.dev endpoint, an otherwise unrelated startup or explicit sync can therefore wait for the 15-second refresh timeout before proceeding. Filter out provider.disabled === true before testing whether metadata is needed.

Useful? React with 👍 / 👎.

}
}

function reportCodexHomeTarget(
log: Pick<Console, "log" | "error"> | null,
collectDiagnostic: typeof collectOrcaCodexHomeDiagnostic,
Expand Down Expand Up @@ -234,6 +243,9 @@ export async function syncModelsToCodex(
}

applyProxyEnv(config); // `ocx ensure`/`ocx sync` fetch provider models outside the server process
// Bootstrap the optional ladder snapshot before gathering the catalog. Keeping this in the
// sync plane prevents an unrelated models.dev fetch from interleaving with a routed turn.
await refreshReasoningMetadataForSync(config, deps);

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge Refresh metadata before launching catalog prewarm

On ordinary ocx start, src/cli/index.ts launches scheduleCatalogPrewarm() before this await, and that prewarm is explicitly designed to share the same in-flight gatherRoutedModels call with refreshCodexModelCatalog. With Zen plus a slower second provider, Zen can apply configuredReasoningEfforts() before the metadata download completes while the second provider keeps the shared flight alive; after this await, the sync joins that already-partially-derived flight and writes a catalog without the newly bootstrapped ladder. Order the bootstrap before prewarm, or invalidate/restart any in-flight gather after refreshing.

Useful? React with 👍 / 👎.

let added = 0;
let catalogPath: string | null = null;
let catalogPathForInjection: string | null | undefined;
Expand Down Expand Up @@ -339,6 +351,7 @@ async function refreshCatalogForSync(
let refreshOutcome: "committed" | "refused" | undefined;
let comboOmissions: ComboCatalogOmission[] = [];
try {
await refreshReasoningMetadataForSync(config, deps);
const cat = await deps.refreshCodexModelCatalog(config, undefined, catalogOptions);
refreshOutcome = cat.refreshOutcome;
added = cat.added;
Expand Down
5 changes: 5 additions & 0 deletions src/providers/reasoning-metadata.ts
Original file line number Diff line number Diff line change
Expand Up @@ -135,6 +135,11 @@ function metadataProviderKey(provider: OcxProviderConfig): string | undefined {
return undefined;
}

/** Whether catalog synchronization should fetch models.dev metadata for this destination. */
export function providerUsesReasoningMetadata(provider: OcxProviderConfig): boolean {
return metadataProviderKey(provider) !== undefined;
}

/**
* Local mirror of `modelRecordValue()` from `src/reasoning-effort.ts`, which imports this
* module and so cannot be imported back. Exact id, then the `family:` prefix, then a
Expand Down
14 changes: 3 additions & 11 deletions src/reasoning-effort.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import type { OcxProviderConfig } from "./types";
import { modelInList } from "./types";
import { dropLearnedUnsupportedReasoningEfforts, ensureReasoningMetadataSnapshot, reasoningEffortsFromMetadata } from "./providers/reasoning-metadata";
import { dropLearnedUnsupportedReasoningEfforts, reasoningEffortsFromMetadata } from "./providers/reasoning-metadata";

// Descriptions mirror the upstream bundled models.json canonical wording (openai/codex PR #31684).
export const CODEX_REASONING_LEVELS: { effort: string; description: string }[] = [
Expand Down Expand Up @@ -160,18 +160,10 @@ export function configuredReasoningEfforts(provider: OcxProviderConfig, modelId:
}
// models.dev publishes the per-model ladder that routed providers never expose on /models.
// (OpenCode Zen Go answers ids only). Only consulted when nothing was configured for this
// model, so every hand-written contract stays authoritative. The snapshot refreshes itself in
// the background; no snapshot means the previous behaviour.
// The refresh is asked for only once a snapshot has already answered, which means it only ever
// refreshes a STALE snapshot. Review asked for the opposite — refresh when the snapshot is
// missing or corrupt, since that is the case this lookup cannot serve. That is declined here:
// a missing snapshot is the default state of every fresh install and every test process, so
// requesting the fetch here puts a models.dev request on the request path of the first routed
// turn to a gated destination. Refreshing a snapshot that does not exist is catalog-sync work,
// not request work.
// model, so every hand-written contract stays authoritative. Catalog sync owns snapshot
// refresh; no snapshot means the previous behaviour.
const fromMetadata = reasoningEffortsFromMetadata(provider, modelId);
if (fromMetadata !== undefined) {
ensureReasoningMetadataSnapshot();
return dropLearnedUnsupportedReasoningEfforts(provider, modelId, healMappedTiers(provider, modelId, fromMetadata));
}
return undefined;
Expand Down
3 changes: 3 additions & 0 deletions structure/catalog.md
Original file line number Diff line number Diff line change
Expand Up @@ -95,6 +95,9 @@ Provider live-model lists are cached with a configured TTL (`src/codex/model-cac
deleting, or editing a provider's shape clears that per-provider cache; a disabled-only change
deliberately does not, because a disabled provider is already excluded from the catalog gather
instead. Codex's own `models_cache.json` is a different cache, invalidated by catalog refresh.
Synchronizing a supported routed provider first refreshes its models.dev effort snapshot through
`src/codex/sync.ts` and `src/providers/reasoning-metadata.ts`; this keeps missing-cache network work
out of request-time ladder resolution.

For `liveModels: false`, a static provider publishes the ordered union of `models` and
`retainModels`. When `models` is absent or empty, its configured `defaultModel` seeds that
Expand Down
3 changes: 3 additions & 0 deletions structure/codex-home.md
Original file line number Diff line number Diff line change
Expand Up @@ -250,6 +250,9 @@ Plan-based automatic exclusions leave native credential files untouched and pres

`src/codex/history-provider.ts` rejects provider-history changes with `history_paginated_requires_native_writer` when a target begins with an ordinal-bearing record or declares `history_mode=paginated`. Apply, manifest-backed restore, and explicit legacy recovery preflight all selected targets before changing database rows or manifests. The append boundary checks again. Codex owns ordinal allocation and the live projection cursor; reading the last ordinal and appending N+1 is not safe concurrent coordination. Legacy unnumbered rollouts retain their existing behavior. This guard prevents the observed stable-format corruption; it does not implement native-writer integration or guarantee a concurrent legacy-to-paginated conversion is excluded.

`src/codex/sync.ts` owns startup and explicit-sync bootstrap of the optional
`src/providers/reasoning-metadata.ts` snapshot; request handling never owns that disk refresh.

Injection preflights affected history using the normalized config candidate before writing config/profile/journal, then checks again after the complete artifact write. Native restore also rechecks after successful journal restoration or fallback removal, while exact config/profile/journal preimages and any coordinated remove transaction remain available for compensation. Detected migration restores all three preimages before returning a structured refusal, including on legacy-uncoordinated homes. A failed config restore stops catalog/history work; coordinated restore rolls back its published remove transition. Legacy first-line provider patches are bound to the validated file identity before and after writing. These compensating checks do not provide a native-writer lock or authorize external ordinal allocation.

The legacy external writer is now refused for affected rows in any store whose schema includes history_mode, even while their row mode is still legacy. This deliberately sacrifices automatic relabeling on migration-capable stores rather than racing native conversion. Synchronous/asynchronous restore, inline journal restore, and direct config removal preserve all artifacts on the same refusal.
Expand Down
3 changes: 3 additions & 0 deletions structure/config.md
Original file line number Diff line number Diff line change
Expand Up @@ -269,6 +269,9 @@ described in [Responses transport](transports/responses.md), not upstream policy

`src/codex/history-provider.ts` refuses external writes to paginated or migration-capable history. `src/codex/inject.ts` checks affected rows and manifest-owned restore targets before and after config/profile/journal changes, including successful journal and fallback restores, and compensates detected migration. Failed config restore stops later catalog/history work and rolls back a coordinated remove transition. See the [history writer contract](codex-home.md#paginated-history-writer-boundary) for guarantees and concurrent-writer limits.

Catalog synchronization in `src/codex/sync.ts` also bootstraps reasoning metadata only when the
configured providers contain a destination supported by `src/providers/reasoning-metadata.ts`.

Private pool credential metadata follows the [quota-history publication identity contract](providers/openai-tiers.md#quota-history-publication-identity); credential-only and account DTO projections omit it.

Codex pool settings and their consumers follow the [reset-first ordering contract](providers/openai-tiers.md#reset-first-account-ordering), including independent-quota fallback and preserved affinity.
Expand Down
2 changes: 1 addition & 1 deletion structure/gui-and-management-api.md
Original file line number Diff line number Diff line change
Expand Up @@ -573,7 +573,7 @@ The provider editor field policy exposes `showThinkingSummary` as a boolean prov

## Paginated history writer boundary

`src/codex/history-provider.ts` refuses external writes to paginated or migration-capable history. `src/codex/inject.ts` checks affected rows and manifest-owned restore targets before and after config/profile/journal changes, including successful journal and fallback restores, and compensates detected migration. Failed config restore stops later catalog/history work and rolls back a coordinated remove transition. See the [history writer contract](codex-home.md#paginated-history-writer-boundary) for guarantees and concurrent-writer limits.
`src/codex/history-provider.ts` refuses external writes to paginated or migration-capable history. `src/codex/inject.ts` checks affected rows and manifest-owned restore targets before and after config/profile/journal changes, including successful journal and fallback restores, and compensates detected migration. Failed config restore stops later catalog/history work and rolls back a coordinated remove transition. See the [history writer contract](codex-home.md#paginated-history-writer-boundary) for guarantees and concurrent-writer limits. Management-triggered catalog synchronization uses `src/codex/sync.ts` to bootstrap the optional `src/providers/reasoning-metadata.ts` snapshot before catalog gathering for supported destinations.

Codex pool settings and their consumers follow the [reset-first ordering contract](providers/openai-tiers.md#reset-first-account-ordering), including independent-quota fallback and preserved affinity. Codex account DTOs and cards expose the routing-plan exclusion separately from credential health; the [plan exclusion contract](providers/openai-tiers.md#automatic-pool-plan-exclusions) also governs CLI projection. Private pool credential metadata follows the [quota-history publication identity contract](providers/openai-tiers.md#quota-history-publication-identity); credential-only and account DTO projections omit it.

Expand Down
3 changes: 3 additions & 0 deletions structure/ops/docs-and-release.md
Original file line number Diff line number Diff line change
Expand Up @@ -375,3 +375,6 @@ Exact [model input declarations](../config.md#explicit-per-model-capability-decl
Provider-scoped approval reviewer settings are projected by the [catalog owner](../catalog.md#provider-scoped-approval-reviewer); this surface retains its existing routing, transport and account-selection behavior.

Renamed fixed-key providers receive [missing reasoning metadata](../catalog.md#renamed-destination-reasoning-metadata) during derivation; explicit per-model entries and provider defaults retain precedence.
Startup and explicit catalog synchronization in `src/codex/sync.ts` bootstrap supported-provider
effort metadata through `src/providers/reasoning-metadata.ts`; routed requests do not trigger that
network refresh.
3 changes: 3 additions & 0 deletions structure/providers/openai-tiers.md
Original file line number Diff line number Diff line change
Expand Up @@ -518,3 +518,6 @@ The history read API reports a median effective token estimate and interval samp
Live bindings obey the existing cache-affinity release policy: with `pool.cacheAffinity`, threshold crossing alone retains a healthy account. Manual preference, scoped health and shared-cursor guards remain authoritative. Independent `spark`/`reserve` quota scopes resolve reset-first to existing quota selection because shared reset timestamps do not describe those windows. The configured value stays unchanged.

The Codex parser in `src/oauth/pool-kernel.ts` is reexported by the compatibility facade and used by both `/api/pool/settings` and the legacy Codex settings route. Generic and Anthropic parsers reject reset-first. The dashboard offers it only for Codex; API, CLI and translated guides preserve the same contract.
Catalog synchronization in `src/codex/sync.ts` bootstraps models.dev effort metadata through
`src/providers/reasoning-metadata.ts` only for supported routed destinations; request-time effort
mapping does not initiate that network operation.
2 changes: 2 additions & 0 deletions structure/providers/xai-grok.md
Original file line number Diff line number Diff line change
Expand Up @@ -94,6 +94,8 @@ privately to final dispatch; preliminary route selection does not inject Go-only
Devin CLI credential path composition in `src/oauth/devin/cli-import.ts` follows the selected platform: Windows uses Win32 APPDATA paths, other platforms use POSIX XDG-data paths. The explicit absolute override remains verbatim; credential parsing and login behavior are unchanged.

Provider-scoped catalog hints remain isolated by provider in `src/providers/registry.ts`. The
models.dev effort snapshot is likewise destination-gated by `src/providers/reasoning-metadata.ts`
and bootstrapped from `src/codex/sync.ts`, not from a routed request.
OpenCode Go `deepseek-v4.1-flash` 1,048,576-token context hint does not change xAI model metadata or
transport behavior.
The first-party DeepSeek `deepseek-flash` native `text`/`image` declaration is likewise scoped to
Expand Down
3 changes: 3 additions & 0 deletions structure/runtime.md
Original file line number Diff line number Diff line change
Expand Up @@ -201,6 +201,9 @@ Provider-scoped capability hints remain authoritative when discovery returns an
capabilities. In particular, `src/providers/registry.ts` assigns OpenCode Go's live
`deepseek-v4.1-flash` route the official 1,048,576-token window instead of the conservative 128k
routed-model fallback.
`src/codex/sync.ts` refreshes the `src/providers/reasoning-metadata.ts` models.dev snapshot for
supported routed destinations before catalog gathering, so missing and corrupt snapshots bootstrap
without adding network work to `src/reasoning-effort.ts` request-time ladder reads.
The same registry declares the first-party `deepseek-flash` model with `text` and `image` input,
so it bypasses the vision sidecar by default; explicit `noVisionModels` or text-only declarations
remain authoritative. First-party `deepseek-chat`, `deepseek-reasoner`, and `deepseek-v4-flash`
Expand Down
2 changes: 2 additions & 0 deletions structure/subagents.md
Original file line number Diff line number Diff line change
Expand Up @@ -228,6 +228,8 @@ Ordinary management discovery also completes it with Codex integration OFF. The
final catalog merge fences pending retained rows, including delete/re-add recovery.
Raw management rows remain visible as pending/OFF. Config listener bindings are
excluded from inventory identity because live and persisted bindings may differ.
Supported routed-provider reasoning snapshots are bootstrapped by `src/codex/sync.ts` through
`src/providers/reasoning-metadata.ts`, rather than by a model request.

Codex `spawn_agent` advertises only the highest-priority first five picker-visible catalog rows.
Use at most five configured `subagentModels` ids; they may contain bare catalog ids, routed
Expand Down
4 changes: 4 additions & 0 deletions structure/transports/inventory.md
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,10 @@ surface is listed here so a maintainer can find the owner without grepping:
| Alibaba regions | `src/providers/alibaba-region-backup.ts`, `src/providers/alibaba-region-migration.ts`, `src/providers/alibaba-region-startup.ts` | Region migration backs up before rewriting and is idempotent across restarts. |
| Discovery and quota | `src/providers/model-discovery.ts`, `src/providers/quota.ts`, `src/providers/registry.ts` | Discovery rejects a response over 4 MiB or past 2,000 raw rows before caching it. Provider-scoped hints fill capabilities omitted by live rosters; OpenCode Go's `deepseek-v4.1-flash` keeps its 1,048,576-token context window. Codex quota DTOs suppress retired Spark evidence under the [OpenAI scope contract](../providers/openai-tiers.md#public-provider-contract), retaining ordinary custom windows. |

`src/codex/sync.ts` bootstraps models.dev effort ladders through
`src/providers/reasoning-metadata.ts` for supported destinations before catalog gathering, never
from the routed request transport.

The registry's first-party `deepseek-flash` row declares native `text` and `image` input, so image
requests bypass the vision sidecar by default; explicit `noVisionModels` or text-only declarations
remain authoritative. First-party `deepseek-chat`, `deepseek-reasoner`, and `deepseek-v4-flash`
Expand Down
36 changes: 36 additions & 0 deletions tests/codex-integration/codex-sync-api.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -150,6 +150,42 @@ describe("GUI/CLI Codex sync backend", () => {
expect(errors).toEqual([]);
});

test("bootstraps reasoning metadata for gated providers before catalog gathering", async () => {
const calls: string[] = [];
const zenConfig = {
...config,
providers: {
zen: {
...config.providers.fixture,
baseUrl: "https://opencode.ai/zen/go/v1",
},
},
} as OcxConfig;

await syncModelsToCodex(12345, zenConfig, null, {
admitCodexWrite: admittedSync,
refreshReasoningMetadata: async () => {
calls.push("reasoning");
return { ok: true as const, reason: "refreshed", providers: 1, models: 1 };
},
refreshCodexModelCatalog: async () => {
calls.push("catalog");
return {
added: 1,
path: "/tmp/opencodex-catalog.json",
catalogExists: true,
catalogWritten: true,
cacheSynced: true,
comboOmissions: [],
};
},
injectCodexConfig: async () => ({ success: true, message: "injected" }),
currentExternalCodexModelProvider: () => null,
});

expect(calls).toEqual(["reasoning", "catalog"]);
});

test("refuses during injection preflight before catalog or cache mutation", async () => {
let refreshCalls = 0;
let injectCalls = 0;
Expand Down
Loading