@bitkyc08/opencodex 2.41.0 → 2.43.0-preview.20260906

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (260) hide show
  1. package/AGENTS_INSTALL.md +2 -2
  2. package/README.md +31 -0
  3. package/bin/ocx.mjs +10 -1
  4. package/gui/dist/assets/index-DS1NE4Jn.css +1 -0
  5. package/gui/dist/assets/index-VGeQEZ_v.js +112 -0
  6. package/gui/dist/index.html +2 -2
  7. package/package.json +1 -1
  8. package/src/adapters/anthropic-image-codec.ts +304 -0
  9. package/src/adapters/anthropic-image-normalize.ts +8 -298
  10. package/src/adapters/anthropic.ts +30 -7
  11. package/src/adapters/command-code.ts +7 -5
  12. package/src/adapters/cursor/desktop-executor-contract.ts +15 -0
  13. package/src/adapters/cursor/images.ts +36 -6
  14. package/src/adapters/cursor/live-transport.ts +7 -2
  15. package/src/adapters/cursor/native-exec-desktop.ts +2 -15
  16. package/src/adapters/cursor/protobuf-request.ts +54 -24
  17. package/src/adapters/cursor/tool-definitions.ts +5 -670
  18. package/src/adapters/cursor/tool-guidance.ts +236 -0
  19. package/src/adapters/cursor/tool-naming.ts +252 -0
  20. package/src/adapters/cursor/tool-schemas.ts +195 -0
  21. package/src/adapters/cursor/types.ts +6 -3
  22. package/src/adapters/exec-tool-result-normalize.ts +1 -1
  23. package/src/adapters/google-errors.ts +9 -1
  24. package/src/adapters/google.ts +1 -0
  25. package/src/adapters/identity.ts +8 -2
  26. package/src/adapters/kiro-calibration.ts +181 -0
  27. package/src/adapters/kiro.ts +135 -3
  28. package/src/adapters/openai-responses.ts +259 -29
  29. package/src/adapters/responses-code-mode.ts +59 -0
  30. package/src/adapters/tool-catalog-nudge.ts +1 -1
  31. package/src/adapters/xai-schema-analysis.ts +86 -0
  32. package/src/adapters/xai-tool-schema.ts +2 -87
  33. package/src/adapters/xai-web-search.ts +1 -1
  34. package/src/bridge.ts +47 -13
  35. package/src/chat/inbound.ts +11 -3
  36. package/src/claude/inbound-content-options.ts +60 -0
  37. package/src/claude/inbound-model-options.ts +142 -0
  38. package/src/claude/inbound-records.ts +7 -0
  39. package/src/claude/inbound.ts +10 -202
  40. package/src/claude/model-info.ts +45 -0
  41. package/src/cli/account-auth.ts +49 -9
  42. package/src/cli/account-extended.ts +7 -1
  43. package/src/cli/capabilities.ts +15 -4
  44. package/src/cli/claude.ts +232 -39
  45. package/src/cli/config-command.ts +9 -1
  46. package/src/cli/dispatch.ts +5 -1
  47. package/src/cli/doctor.ts +10 -0
  48. package/src/cli/effort.ts +372 -0
  49. package/src/cli/export-command.ts +3 -9
  50. package/src/cli/help.ts +1 -0
  51. package/src/cli/index.ts +13 -0
  52. package/src/cli/init.ts +4 -0
  53. package/src/cli/model-selection-guidance.ts +30 -0
  54. package/src/cli/models-runtime.ts +3 -2
  55. package/src/cli/models.ts +8 -3
  56. package/src/cli/observe.ts +3 -1
  57. package/src/cli/opencode.ts +4 -1
  58. package/src/cli/provider-runtime.ts +65 -0
  59. package/src/cli/provider.ts +8 -0
  60. package/src/cli/registry.ts +16 -2
  61. package/src/cli/runtime-api.ts +3 -1
  62. package/src/cli/star-prompt.ts +22 -6
  63. package/src/cli/status-probes.ts +168 -0
  64. package/src/cli/status.ts +5 -168
  65. package/src/clients/config-export/constants.ts +69 -0
  66. package/src/clients/config-export/contracts.ts +154 -0
  67. package/src/clients/config-export/dsh.ts +132 -0
  68. package/src/clients/config-export/fast-models.ts +29 -0
  69. package/src/clients/config-export/mcode.ts +83 -0
  70. package/src/clients/config-export/model-metadata.ts +108 -0
  71. package/src/clients/config-export/omp.ts +104 -0
  72. package/src/clients/config-export/zcode.ts +92 -0
  73. package/src/clients/config-export.ts +18 -710
  74. package/src/codex/account-lifecycle.ts +20 -3
  75. package/src/codex/account-usability.ts +2 -0
  76. package/src/codex/auth-api.ts +272 -32
  77. package/src/codex/auth-context.ts +328 -24
  78. package/src/codex/catalog/effort.ts +44 -5
  79. package/src/codex/catalog/metadata.ts +149 -14
  80. package/src/codex/catalog/native-models.ts +116 -4
  81. package/src/codex/catalog/parsing.ts +122 -8
  82. package/src/codex/catalog/provider-fetch.ts +154 -23
  83. package/src/codex/catalog/reserve.ts +52 -0
  84. package/src/codex/catalog/sync.ts +89 -16
  85. package/src/codex/catalog.ts +1 -1
  86. package/src/codex/convergence-types.ts +1 -0
  87. package/src/codex/convergence.ts +2 -0
  88. package/src/codex/data/upstream-models.json +169 -0
  89. package/src/codex/forward-transport-headers.ts +25 -0
  90. package/src/codex/inject.ts +99 -34
  91. package/src/codex/injected-marker.ts +30 -4
  92. package/src/codex/journal.ts +14 -0
  93. package/src/codex/legacy-config-keys.ts +68 -0
  94. package/src/codex/log-guard/inspect-schema.ts +137 -0
  95. package/src/codex/log-guard/inspect.ts +2 -134
  96. package/src/codex/loopback-target.ts +54 -0
  97. package/src/codex/main-account-cache.ts +63 -1
  98. package/src/codex/main-account-hard-lock.ts +52 -0
  99. package/src/codex/main-account.ts +3 -1
  100. package/src/codex/management-convergence.ts +3 -0
  101. package/src/codex/model-entitlements.ts +54 -4
  102. package/src/codex/project-config-warnings.ts +92 -2
  103. package/src/codex/prompt-layers/encoding.ts +80 -0
  104. package/src/codex/prompt-layers/paths.ts +54 -0
  105. package/src/codex/prompt-layers/revision.ts +55 -0
  106. package/src/codex/prompt-layers/toml-edit.ts +163 -0
  107. package/src/codex/prompt-layers/toml-read.ts +181 -0
  108. package/src/codex/prompt-layers.ts +14 -520
  109. package/src/codex/quota-auto-refresh-state.ts +16 -0
  110. package/src/codex/quota-auto-refresh.ts +219 -0
  111. package/src/codex/quota-types.ts +51 -0
  112. package/src/codex/quota.ts +252 -93
  113. package/src/codex/reserve-availability.ts +177 -0
  114. package/src/codex/routing.ts +28 -9
  115. package/src/codex/shim.ts +53 -11
  116. package/src/codex/subagent-model-fallback.ts +23 -3
  117. package/src/combos/failover.ts +125 -7
  118. package/src/combos/identifiers.ts +89 -0
  119. package/src/combos/index.ts +4 -0
  120. package/src/combos/resolve.ts +80 -9
  121. package/src/combos/types.ts +20 -93
  122. package/src/config/subagent-models.ts +24 -0
  123. package/src/config.ts +156 -13
  124. package/src/generated/compatibility-version.json +474 -178
  125. package/src/generated/model-metadata.ts +1 -1
  126. package/src/integrations/journal.ts +65 -4
  127. package/src/integrations/store.ts +5 -0
  128. package/src/lab/events/limits.ts +4 -0
  129. package/src/lib/destination-policy.ts +31 -2
  130. package/src/lib/errors.ts +39 -0
  131. package/src/lib/provider-outbound.ts +69 -3
  132. package/src/lib/proxy-env.ts +22 -0
  133. package/src/lib/redact-folding.ts +176 -0
  134. package/src/lib/redact.ts +2 -175
  135. package/src/lib/state-store-sweeper.ts +20 -6
  136. package/src/lib/token-estimate.ts +94 -27
  137. package/src/lib/windows-user-principal.ts +53 -5
  138. package/src/oauth/account-quota-rank.ts +40 -1
  139. package/src/oauth/anthropic-routing.ts +99 -3
  140. package/src/oauth/chatgpt-device.ts +187 -0
  141. package/src/oauth/chatgpt.ts +31 -4
  142. package/src/oauth/generic-account-failover.ts +36 -13
  143. package/src/oauth/index.ts +140 -29
  144. package/src/oauth/log.ts +3 -0
  145. package/src/oauth/login-cli.ts +5 -0
  146. package/src/oauth/meta-muse.ts +117 -15
  147. package/src/oauth/pool-settings-capability.ts +15 -4
  148. package/src/providers/api-keys.ts +8 -10
  149. package/src/providers/default-aliases.ts +39 -0
  150. package/src/providers/derive.ts +10 -2
  151. package/src/providers/fastwire.ts +36 -7
  152. package/src/providers/initial-model-selection-runtime.ts +90 -0
  153. package/src/providers/initial-model-selection.ts +120 -0
  154. package/src/providers/key-failover.ts +134 -54
  155. package/src/providers/key-store.ts +11 -1
  156. package/src/providers/label.ts +1 -1
  157. package/src/providers/model-discovery.ts +76 -0
  158. package/src/providers/model-rename-startup.ts +72 -8
  159. package/src/providers/muse-subscription-usage.ts +95 -0
  160. package/src/providers/openai-sidecar.ts +17 -5
  161. package/src/providers/openai-tiers-destination.ts +102 -0
  162. package/src/providers/openai-tiers.ts +2 -99
  163. package/src/providers/opencode-go-transport.ts +41 -0
  164. package/src/providers/quota-key-accounts.ts +141 -0
  165. package/src/providers/quota-types.ts +9 -0
  166. package/src/providers/quota.ts +625 -98
  167. package/src/providers/registry.ts +60 -17
  168. package/src/providers/xai-responses-opt-in.ts +31 -5
  169. package/src/quota/reset-activation.ts +81 -0
  170. package/src/quota/reset-detector.ts +305 -0
  171. package/src/quota/reset-notify-config.ts +162 -0
  172. package/src/quota/reset-observer.ts +125 -0
  173. package/src/quota/reset-poller.ts +160 -0
  174. package/src/quota/reset-seen-store.ts +385 -0
  175. package/src/quota/reset-sinks.ts +199 -0
  176. package/src/quota/window-mapping.ts +106 -0
  177. package/src/responses/apply-patch-envelope.ts +46 -0
  178. package/src/responses/code-mode-helper-compat.ts +39 -1
  179. package/src/responses/custom-tool-compat.ts +10 -4
  180. package/src/responses/hosted-tool-policy.ts +12 -4
  181. package/src/responses/parser-content.ts +133 -0
  182. package/src/responses/parser-text-format.ts +24 -0
  183. package/src/responses/parser-tools.ts +188 -0
  184. package/src/responses/parser.ts +3 -326
  185. package/src/responses/state.ts +124 -28
  186. package/src/router.ts +48 -13
  187. package/src/routing/analytics.ts +1 -0
  188. package/src/routing/capability.ts +17 -4
  189. package/src/server/auth-cors.ts +7 -1
  190. package/src/server/background-lifecycle.ts +23 -1
  191. package/src/server/chat-completions.ts +25 -3
  192. package/src/server/claude-messages.ts +62 -5
  193. package/src/server/effort-row.ts +1 -1
  194. package/src/server/fast-row.ts +295 -0
  195. package/src/server/gui-static.ts +30 -4
  196. package/src/server/index.ts +122 -28
  197. package/src/server/live.ts +18 -4
  198. package/src/server/management/agent-settings-routes.ts +2 -2
  199. package/src/server/management/combo-routes.ts +37 -9
  200. package/src/server/management/config-routes.ts +93 -2
  201. package/src/server/management/integration-routes.ts +108 -0
  202. package/src/server/management/model-routes.ts +13 -3
  203. package/src/server/management/model-rows.ts +20 -1
  204. package/src/server/management/native-integration-routes.ts +4 -1
  205. package/src/server/management/oauth-account-routes.ts +45 -10
  206. package/src/server/management/provider-routes.ts +34 -3
  207. package/src/server/management/quota-reset-routes.ts +57 -0
  208. package/src/server/management/route-registry.ts +7 -4
  209. package/src/server/management/shared.ts +19 -5
  210. package/src/server/management/system-routes.ts +3 -2
  211. package/src/server/management-api.ts +14 -2
  212. package/src/server/ports.ts +12 -2
  213. package/src/server/relay-eager.ts +38 -23
  214. package/src/server/relay.ts +4 -0
  215. package/src/server/request-log.ts +6 -0
  216. package/src/server/responses/codex-ws-correlation.ts +65 -0
  217. package/src/server/responses/codex-ws-exchange.ts +261 -0
  218. package/src/server/responses/codex-ws-metadata.ts +134 -0
  219. package/src/server/responses/codex-ws-pool.ts +162 -0
  220. package/src/server/responses/codex-ws-request.ts +87 -0
  221. package/src/server/responses/codex-ws-session.ts +93 -0
  222. package/src/server/responses/codex-ws-wire.ts +144 -0
  223. package/src/server/responses/collaboration.ts +41 -1
  224. package/src/server/responses/compact.ts +105 -12
  225. package/src/server/responses/core.ts +510 -57
  226. package/src/server/responses/empty-completion-guard.ts +4 -0
  227. package/src/server/responses/fetch-helpers.ts +10 -3
  228. package/src/server/responses/input-admission.ts +16 -9
  229. package/src/server/responses/responses-field-backfill.ts +1 -1
  230. package/src/server/responses/ws-upstream.ts +34 -318
  231. package/src/server/responses-custom-tool-repair.ts +20 -4
  232. package/src/server/responses-undeclared-tool-guard.ts +100 -8
  233. package/src/server/safe-response-headers.ts +23 -0
  234. package/src/server/search.ts +9 -0
  235. package/src/server/subagent-models-startup.ts +27 -0
  236. package/src/server/system-env-shell.ts +238 -0
  237. package/src/server/system-env.ts +7 -234
  238. package/src/server/ws-bridge.ts +3 -25
  239. package/src/server/xai-responses-startup.ts +21 -0
  240. package/src/service-manager-probe.ts +1 -1
  241. package/src/service.ts +55 -16
  242. package/src/types/config.ts +108 -12
  243. package/src/types/provider.ts +36 -7
  244. package/src/types/request.ts +8 -0
  245. package/src/types/tools.ts +26 -1
  246. package/src/types.ts +2 -0
  247. package/src/update/notify.ts +8 -2
  248. package/src/usage/cost.ts +38 -28
  249. package/src/usage/expected-prices.ts +34 -15
  250. package/src/usage/log.ts +2 -0
  251. package/src/usage/model-identity.ts +26 -0
  252. package/src/usage/summary.ts +15 -1
  253. package/src/vision/describe.ts +6 -0
  254. package/src/vision/image-rewrite.ts +108 -0
  255. package/src/vision/index.ts +19 -306
  256. package/src/vision/plan.ts +205 -0
  257. package/src/web-search/executor.ts +6 -0
  258. package/src/web-search/index.ts +8 -1
  259. package/gui/dist/assets/index-B2YjLA-i.css +0 -1
  260. package/gui/dist/assets/index-aPup8CKb.js +0 -112
@@ -0,0 +1,52 @@
1
+ import type { OcxConfig } from "../../types";
2
+ import { isEffectiveCodexDesktopAuthless } from "../loopback-target";
3
+ import { CODEX_ACCOUNT_BOUND_CATALOG_KIND } from "./account-models";
4
+ import { NATIVE_RESERVE_MODEL } from "./native-models";
5
+ import type { RawEntry } from "./parsing";
6
+
7
+ export const RESERVE_METADATA_SOURCE_FIELD = "opencodex_reserve_metadata_source";
8
+ /** Validated genuine source metadata on the existing catalog, never an authorization. */
9
+ export const RESERVE_SOURCE_CATALOG_FIELD = "opencodex_reserve_source";
10
+ export const RESERVE_LUNA_METADATA_SOURCE = "gpt-5.6-luna";
11
+
12
+ /** Metadata only: no process-local availability or credential state belongs in a catalog. */
13
+ export interface ReserveCatalogProjection {
14
+ readonly source: RawEntry;
15
+ readonly mainSelectors: readonly string[];
16
+ }
17
+
18
+ /** The caller supplies a validated actual observation and an already context-capped Luna pin. */
19
+ export function createReserveCatalogProjection(
20
+ config: Pick<OcxConfig, "runtimeRole" | "hostname" | "unauthenticatedLoopbackListener" | "codexDesktopAuthless">,
21
+ mainSelectors: readonly string[],
22
+ observedSource: RawEntry | null,
23
+ lunaSource: RawEntry | null,
24
+ ): ReserveCatalogProjection | undefined {
25
+ if (!isEffectiveCodexDesktopAuthless(config) || mainSelectors.length === 0) return undefined;
26
+ const original = observedSource ?? lunaSource;
27
+ if (!original) return undefined;
28
+ const source = structuredClone(original);
29
+ source.slug = NATIVE_RESERVE_MODEL;
30
+ source.display_name = observedSource?.display_name ?? "Luna Reserve";
31
+ source.description = "Manual main-account Reserve through OpenCodex; recent upstream permission is required for every request.";
32
+ // This qualified OCX endpoint accepts the selector, not an OpenAI API-key model grant.
33
+ source.supported_in_api = true;
34
+ source[RESERVE_METADATA_SOURCE_FIELD] = observedSource ? NATIVE_RESERVE_MODEL : RESERVE_LUNA_METADATA_SOURCE;
35
+ delete source.available_in_plans;
36
+ delete source.availability_nux;
37
+ delete source.upgrade;
38
+ delete source.opencodex_account_observed_native;
39
+ delete source.opencodex_account_observed_selectors;
40
+ return { source, mainSelectors: [...mainSelectors] };
41
+ }
42
+
43
+ /** Exact OCX account projection, never another provider's similarly named model. */
44
+ export function isReserveCatalogProjection(entry: RawEntry): boolean {
45
+ return entry.opencodex_catalog_kind === CODEX_ACCOUNT_BOUND_CATALOG_KIND
46
+ && typeof entry.slug === "string"
47
+ && entry.slug.indexOf("/") > 0
48
+ && entry.slug.indexOf("/") === entry.slug.lastIndexOf("/")
49
+ && entry.slug.endsWith(`/${NATIVE_RESERVE_MODEL}`)
50
+ && (entry[RESERVE_METADATA_SOURCE_FIELD] === NATIVE_RESERVE_MODEL
51
+ || entry[RESERVE_METADATA_SOURCE_FIELD] === RESERVE_LUNA_METADATA_SOURCE);
52
+ }
@@ -1,3 +1,5 @@
1
+ import { effectiveProviderAlias } from "../../providers/default-aliases";
2
+ import { pendingModelSelectionProviders } from "../../providers/initial-model-selection";
1
3
  import { execFileSync } from "node:child_process";
2
4
  import { createHash } from "node:crypto";
3
5
  import { existsSync, readFileSync } from "node:fs";
@@ -73,7 +75,15 @@ import {
73
75
  } from "../internal/catalog-writer";
74
76
  import { codexRuntimeStatePath } from "../runtime";
75
77
  import { accountBoundNativeDisplayName, CODEX_ACCOUNT_BOUND_CATALOG_KIND, trustedAccountBoundNativeCatalogSlug, visibleCodexAccountSelectors } from "./account-models";
76
- import { ACCOUNT_GATED_NATIVE_OPENAI_MODELS } from "./native-models";
78
+ import { ACCOUNT_GATED_NATIVE_OPENAI_MODELS, NATIVE_RESERVE_MODEL } from "./native-models";
79
+ import { observedReserveCatalogSource } from "./metadata";
80
+ import {
81
+ createReserveCatalogProjection,
82
+ isReserveCatalogProjection,
83
+ RESERVE_LUNA_METADATA_SOURCE,
84
+ RESERVE_SOURCE_CATALOG_FIELD,
85
+ type ReserveCatalogProjection,
86
+ } from "./reserve";
77
87
 
78
88
  export const MAX_SPAWN_AGENT_MODEL_OVERRIDES = 5;
79
89
 
@@ -267,17 +277,30 @@ function isExactComboCatalogEntry(
267
277
  * ids differ by a single dash (`command-code` vs `commandcode`), so relabel them to the
268
278
  * lowercase-dash style the opencode presets use: `commandcode-auth/x` and `commandcode-api/x`.
269
279
  * The model-id portion also carries a redundant `<vendor>-` prefix (`deepseek-deepseek-v4-flash`)
270
- * that is dropped for display. All other providers keep the raw slug exactly as before.
280
+ * that is dropped for display. Google Antigravity is relabeled to the compact `agy/` prefix for
281
+ * the same reason: `google-antigravity/` alone consumes most of the picker row. That prefix comes
282
+ * from the row's own `providerAlias`, decided once per gather flight; `null` means a cross-provider
283
+ * collision suppressed it and the canonical slug stands. This is the raw-slug path only -- a
284
+ * configured `modelAliases` entry is labeled by the effective-alias path in
285
+ * catalog/provider-fetch.ts (#2960) and keeps the canonical provider name. All other providers
286
+ * keep the raw slug exactly as before.
271
287
  */
272
- function routedDisplayName(slug: string): string {
288
+ function routedDisplayName(slug: string, model?: CatalogModel, config?: Pick<OcxConfig, "providers">): string {
273
289
  const slash = slug.indexOf("/");
274
290
  if (slash <= 0) return slug;
275
291
  const provider = slug.slice(0, slash);
276
- let model = slug.slice(slash + 1);
292
+ let modelId = slug.slice(slash + 1);
293
+ if (provider === "google-antigravity") {
294
+ if (model?.providerAlias === null) return slug;
295
+ const alias = (typeof model?.providerAlias === "string" && model.providerAlias.trim().length > 0)
296
+ ? model.providerAlias.trim()
297
+ : effectiveProviderAlias(provider, undefined, config);
298
+ return alias ? `${alias}/${modelId}` : slug;
299
+ }
277
300
  if (provider === "command-code" || provider === "commandcode") {
278
- const m = model.match(/^([a-z0-9]+)-([a-z0-9]+(?:-[a-z0-9]+)+)$/i);
279
- if (m && model.startsWith(`${m[1]}-${m[1]}-`)) model = model.slice(m[1]!.length + 1);
280
- return `${provider === "command-code" ? "commandcode-auth" : "commandcode-api"}/${model}`;
301
+ const m = modelId.match(/^([a-z0-9]+)-([a-z0-9]+(?:-[a-z0-9]+)+)$/i);
302
+ if (m && modelId.startsWith(`${m[1]}-${m[1]}-`)) modelId = modelId.slice(m[1]!.length + 1);
303
+ return `${provider === "command-code" ? "commandcode-auth" : "commandcode-api"}/${modelId}`;
281
304
  }
282
305
  return slug;
283
306
  }
@@ -306,7 +329,7 @@ export function deriveEntry(
306
329
  if (template || codexForwardNativeCapabilityAlias) {
307
330
  const e = JSON.parse(JSON.stringify(codexForwardNativeCapabilityAlias ?? template)) as RawEntry;
308
331
  e.slug = slug;
309
- e.display_name = routedDisplayName(slug);
332
+ e.display_name = routedDisplayName(slug, model);
310
333
  e.description = desc;
311
334
  e.priority = priority;
312
335
  e.visibility = "list";
@@ -375,7 +398,7 @@ export function deriveEntry(
375
398
  // Cursor still omits hosted web-search metadata because runTurn bypasses that separate sidecar.
376
399
  const isCursorFallback = isRouted && model?.provider === "cursor";
377
400
  const entry: RawEntry = {
378
- slug, display_name: routedDisplayName(slug), description: desc,
401
+ slug, display_name: routedDisplayName(slug, model), description: desc,
379
402
  shell_type: "unified_exec", visibility: "list", supported_in_api: true,
380
403
  priority, base_instructions: "You are a helpful coding assistant.",
381
404
  ...(isRouted
@@ -422,6 +445,8 @@ export interface ObservedCatalogEntryBuildInput {
422
445
  readonly accountNativeSlugs?: readonly string[];
423
446
  /** Per-selector account ids; unknown observations must not be copied to unrelated accounts. */
424
447
  readonly accountNativeSlugsBySelector?: ReadonlyMap<string, readonly string[]>;
448
+ /** Codex-only manual selector metadata; deliberately independent of live permission. */
449
+ readonly reserve?: ReserveCatalogProjection;
425
450
  }
426
451
 
427
452
  /** Build entries with the process-observed Codex feature state. */
@@ -478,6 +503,7 @@ export function buildCatalogEntriesFromObservedState({
478
503
  openaiContextCap,
479
504
  accountNativeSlugs,
480
505
  accountNativeSlugsBySelector,
506
+ reserve,
481
507
  }: ObservedCatalogEntryBuildInput): RawEntry[] {
482
508
  // Codex's models-manager sorts by `priority` ASC and advertises the first 5 picker-visible
483
509
  // models to spawn_agent (sort_by_key(priority) + MAX_MODEL_OVERRIDES_IN_SPAWN_AGENT=5). Catalog
@@ -574,15 +600,17 @@ export function buildCatalogEntriesFromObservedState({
574
600
  const selectorNativeSlugs = accountNativeSlugsBySelector?.get(selector)
575
601
  ?? accountNativeSlugs
576
602
  ?? gptSlugs;
577
- const accountNativeEntries = selectorNativeSlugs.map(slug => (
603
+ const accountNativeEntries = selectorNativeSlugs.filter(slug => slug !== NATIVE_RESERVE_MODEL).map(slug => (
578
604
  nativeEntriesBySlug.get(slug)
579
605
  ?? deriveEntry(template, slug, "OpenAI native model (Codex OAuth passthrough).", 9, undefined, new Set(), openaiContextCap)
580
606
  ));
607
+ if (reserve?.mainSelectors.includes(selector)) accountNativeEntries.push(reserve.source);
581
608
  for (const [nativeIndex, native] of accountNativeEntries.entries()) {
582
609
  const nativeSlug = String(native.slug);
583
610
  if (disabledNativeAccountSlugs.has(nativeSlug)) continue;
584
611
  const e = JSON.parse(JSON.stringify(native)) as RawEntry;
585
612
  const catalogSlug = `${selector}/${nativeSlug}`;
613
+ if (nativeSlug === NATIVE_RESERVE_MODEL && disabledNativeAccountSlugs.has(catalogSlug)) continue;
586
614
  e.slug = catalogSlug;
587
615
  e.display_name = accountBoundNativeDisplayName(selector, native);
588
616
  // Codex ignores this OpenCodex extension; preserve the native comp_hash unchanged.
@@ -656,6 +684,7 @@ export function buildCatalogEntriesFromObservedState({
656
684
  }
657
685
  return applyMultiAgentMode(out, multiAgentMode, multiAgentV2Enabled, {
658
686
  keepNativeChatGptOnV1,
687
+ preserveDefaultMultiAgentVersion: isReserveCatalogProjection,
659
688
  });
660
689
  }
661
690
 
@@ -761,6 +790,7 @@ export interface ObservedCatalogMergeInput {
761
790
  readonly disabledModels: ReadonlySet<string>;
762
791
  readonly selectedModelsByProvider: ReadonlyMap<string, ReadonlySet<string>>;
763
792
  readonly gatheredProviderNames: ReadonlySet<string>;
793
+ readonly pendingProviderNames?: ReadonlySet<string>;
764
794
  readonly degradedProviderNames: ReadonlySet<string>;
765
795
  readonly legacyCustomModelSlugs: ReadonlySet<string>;
766
796
  readonly multiAgentMode: MultiAgentMode;
@@ -792,6 +822,7 @@ export function mergeCatalogEntriesFromObservedState({
792
822
  disabledModels,
793
823
  selectedModelsByProvider,
794
824
  gatheredProviderNames,
825
+ pendingProviderNames = new Set(),
795
826
  degradedProviderNames,
796
827
  legacyCustomModelSlugs,
797
828
  multiAgentMode,
@@ -841,6 +872,7 @@ export function mergeCatalogEntriesFromObservedState({
841
872
  if (disabledModelKeys.has(key)) return false;
842
873
  const slash = slug.indexOf("/");
843
874
  const provider = slug.slice(0, slash);
875
+ if (pendingProviderNames.has(provider)) return false;
844
876
  const selected = selectedModelKeysByProvider.get(provider);
845
877
  if (selected !== undefined && !selected.has(key)) return false;
846
878
  return !gatheredProviderNames.has(provider) || degradedProviderNames.has(provider);
@@ -946,7 +978,7 @@ export function mergeCatalogEntriesFromObservedState({
946
978
  const preserved = normalizeServiceTiers({ ...m, priority: nativePriority(slug, m.priority) });
947
979
  // Older natives kept from disk still need the mock top tiers (max + ultra always
948
980
  // for subagent max spawns; wire-clamped to the model's real top rung).
949
- if (!isGpt56NativeSlug(slug)) ensureUltraReasoningLevel(preserved);
981
+ if (!isGpt56NativeSlug(slug) && slug !== NATIVE_RESERVE_MODEL) ensureUltraReasoningLevel(preserved);
950
982
  return preserved;
951
983
  })
952
984
  : [];
@@ -981,6 +1013,9 @@ export function mergeCatalogEntriesFromObservedState({
981
1013
  typeof entry.slug === "string" ? [[entry.slug, entry] as const] : []
982
1014
  ));
983
1015
  const alignedAccountBoundEntries = detachedAccountBoundEntries.map(entry => {
1016
+ // The explicit Reserve source is already chosen (actual row or documented Luna adaptation).
1017
+ // A generic native merge must not replace its provenance or capability ladder.
1018
+ if (isReserveCatalogProjection(entry)) return entry;
984
1019
  const nativeSlug = trustedAccountBoundNativeCatalogSlug(entry);
985
1020
  const source = nativeSlug === undefined ? undefined : nativeSourceBySlug.get(nativeSlug);
986
1021
  if (!source) return entry;
@@ -1036,6 +1071,7 @@ export function mergeCatalogEntriesFromObservedState({
1036
1071
  if (freshExactComboEntries.has(entry)) return true;
1037
1072
  const slash = slug.indexOf("/");
1038
1073
  const provider = slug.slice(0, slash);
1074
+ if (pendingProviderNames.has(provider)) return false;
1039
1075
  const selected = selectedModelKeysByProvider.get(provider);
1040
1076
  return selected === undefined || selected.has(slugEquivalenceKey(slug));
1041
1077
  });
@@ -1087,17 +1123,18 @@ export function mergeCatalogEntriesFromObservedState({
1087
1123
  }));
1088
1124
  for (const slug of policy.nativeBackfillSlugs) observedNativeSlugs.add(slug);
1089
1125
  const mergedEntries = [...native, ...managedEntries].map(m => {
1090
- const normalized = normalizeServiceTiers(m);
1091
- if (!isNativeAliasCatalogEntry(normalized)) applyNativeOpenAiContextOverride(normalized, openaiContextCap);
1126
+ const reserveProjection = isReserveCatalogProjection(m);
1127
+ const normalized = reserveProjection ? m : normalizeServiceTiers(m);
1128
+ if (!reserveProjection && !isNativeAliasCatalogEntry(normalized)) applyNativeOpenAiContextOverride(normalized, openaiContextCap);
1092
1129
  const exactCombo = isExactComboCatalogEntry(m, exactComboSlugs);
1093
- const e = ensureStrictCatalogFields(normalized, {
1130
+ const e = reserveProjection ? normalized : ensureStrictCatalogFields(normalized, {
1094
1131
  preserveExactInputModalities: exactCombo,
1095
1132
  isRouted: finalRoutedEntrySet.has(m),
1096
1133
  });
1097
1134
  // Mock-max universality (260709): preserved routed entries from disk may predate
1098
1135
  // the max rung — ensure it here so subagent max spawns validate on every
1099
1136
  // reasoning-capable entry. max only: 5.6 exact ladders (luna: no ultra) stay intact.
1100
- if (!exactCombo) {
1137
+ if (!exactCombo && !reserveProjection) {
1101
1138
  const levels = Array.isArray(e.supported_reasoning_levels)
1102
1139
  ? e.supported_reasoning_levels as Array<{ effort?: string }>
1103
1140
  : [];
@@ -1122,7 +1159,7 @@ export function mergeCatalogEntriesFromObservedState({
1122
1159
  applyNativeVisibility(mergedEntries, disabledModels, alignedAccountBoundEntries.length > 0, observedNativeSlugs),
1123
1160
  multiAgentMode,
1124
1161
  multiAgentV2Enabled,
1125
- { keepNativeChatGptOnV1 },
1162
+ { keepNativeChatGptOnV1, preserveDefaultMultiAgentVersion: isReserveCatalogProjection },
1126
1163
  );
1127
1164
  for (const entry of versionedEntries) {
1128
1165
  const kind = entry.opencodex_catalog_kind;
@@ -1610,6 +1647,36 @@ function writeRetainedCatalogSync({
1610
1647
  trustedAccountBoundNativeCatalogSlug(entry) !== undefined),
1611
1648
  ];
1612
1649
  const accountTargets = new Map(codexAccountNamespaceEntries(config));
1650
+ const reserveMainSelectors = accountSelectors.filter(selector =>
1651
+ isMainCodexAccountTarget(accountTargets.get(selector) ?? ""));
1652
+ // The active file can own a bare source even when the bundled catalog is the build base.
1653
+ // A previously clamped qualified projection must not shorten a retained genuine ladder.
1654
+ const reserveObservations = [
1655
+ ...(onDiskCatalog?.models ?? []),
1656
+ ...(read.modelsCache?.models ?? []),
1657
+ ...(catalog.models ?? []),
1658
+ ];
1659
+ const retainedReserve = onDiskCatalog?.[RESERVE_SOURCE_CATALOG_FIELD];
1660
+ const retainedReserveSource = retainedReserve && typeof retainedReserve === "object" && !Array.isArray(retainedReserve)
1661
+ ? observedReserveCatalogSource([retainedReserve as RawEntry], [])
1662
+ : null;
1663
+ const observedReserveSource = observedReserveCatalogSource(
1664
+ // Cache invalidation carries historical bare observations alongside emitted models.
1665
+ // Only unmarked observations are fresh enough to supersede the retained source.
1666
+ reserveObservations.filter(entry => entry.slug === NATIVE_RESERVE_MODEL
1667
+ && entry.opencodex_account_observed_native === undefined), reserveMainSelectors,
1668
+ ) ?? retainedReserveSource ?? observedReserveCatalogSource(reserveObservations, reserveMainSelectors);
1669
+ // This root is read only by OCX. Upstream ModelsResponse ignores unknown root fields.
1670
+ // Retain before final runtime clamping: an omitted row must not turn into Luna next sync.
1671
+ if (observedReserveSource) catalog[RESERVE_SOURCE_CATALOG_FIELD] = structuredClone(observedReserveSource);
1672
+ else delete catalog[RESERVE_SOURCE_CATALOG_FIELD];
1673
+ const lunaSource = upstreamNativeEntry(RESERVE_LUNA_METADATA_SOURCE);
1674
+ const reserve = createReserveCatalogProjection(
1675
+ config,
1676
+ reserveMainSelectors,
1677
+ observedReserveSource,
1678
+ lunaSource ? finishUpstreamNativeEntry(lunaSource, 9, openaiContextCap) : null,
1679
+ );
1613
1680
  const accountNativeSlugsBySelector = accountSelectors.length > 0
1614
1681
  ? new Map([...accountBoundNativeOpenAiSlugsBySelector(config, observedAccountNativeEntries)].map(([selector, slugs]) => {
1615
1682
  const target = accountTargets.get(selector);
@@ -1691,6 +1758,7 @@ function writeRetainedCatalogSync({
1691
1758
  openaiContextCap,
1692
1759
  accountNativeSlugs,
1693
1760
  accountNativeSlugsBySelector,
1761
+ reserve,
1694
1762
  }).filter(entry => trustedAccountBoundNativeCatalogSlug(entry) !== undefined)
1695
1763
  : [];
1696
1764
  catalog.models = mergeCatalogEntriesFromObservedState({
@@ -1704,6 +1772,7 @@ function writeRetainedCatalogSync({
1704
1772
  disabledModels: new Set(config.disabledModels ?? []),
1705
1773
  selectedModelsByProvider,
1706
1774
  gatheredProviderNames,
1775
+ pendingProviderNames: pendingModelSelectionProviders(config),
1707
1776
  degradedProviderNames,
1708
1777
  legacyCustomModelSlugs: legacyCustomModelCatalogSlugs(config),
1709
1778
  multiAgentMode,
@@ -1806,6 +1875,10 @@ export async function syncCatalogModels(
1806
1875
  config: OcxConfig,
1807
1876
  options?: CodexCatalogSyncOptions,
1808
1877
  ): Promise<RetainedCatalogSyncResult> {
1878
+ if (pendingModelSelectionProviders(config).size) {
1879
+ const { resolvePendingInitialModelSelection } = await import("../../providers/initial-model-selection-runtime");
1880
+ await resolvePendingInitialModelSelection(config);
1881
+ }
1809
1882
  const owningCodexHome = getCodexHome();
1810
1883
  const preflightRead = readRetainedCatalogSync(config);
1811
1884
  if (preflightRead === null) {
@@ -2,7 +2,7 @@
2
2
  // Public surface preserved exactly; importers keep using "src/codex/catalog".
3
3
  export { isMediaGenerationModelId, shouldExposeRoutedModel, readCodexCatalogPath, readCatalog, normalizeRoutedCatalogEntry, catalogModelSlug, filterSupportedNativeSlugs, catalogModelSupportsReasoningSummaries } from "./catalog/parsing";
4
4
  export type { CatalogModel, MultiAgentMode } from "./catalog/parsing";
5
- export { accountBoundNativeOpenAiSlugs, accountBoundNativeOpenAiSlugsBySelector, CODEX_NATIVE_ALIAS_CATALOG_KIND, NATIVE_DAYBREAK_BLUE_MODEL, NATIVE_GPT56_CONTEXT_WINDOW, NATIVE_GPT56_MAX_INPUT_TOKENS, NATIVE_GPT56_OPT_IN_CONTEXT_WINDOW, NATIVE_OPENAI_CAPABILITY_ALIAS_MODELS, NATIVE_OPENAI_MODELS, configuredNativeAliasSlugs, desktopAllowlistSuppressedNativeSlugs, isNativeAliasCatalogEntry, isNativeOpenAiCapabilityAliasModel, nativeContextLimits, nativeOpenAiCapabilitySourceSlug, nativeOpenAiContextWindow, nativeOpenAiMaxInputTokens, nativeOpenAiMaxOutputTokens, nativeOpenAiContextTier, disabledNativeSlugs, visibleNativeSlugs, desktopVisibleNativeSlugs, nativeModelRows, applyNativeVisibility, observedAccountBoundNativeEntries, observedAccountBoundNativeOpenAiSlugs, upstreamNativeEntry, nativeOpenAiSlugs, listCatalogNativeSlugs, nativeInputModalities, nativeReasoningEfforts, nativeDefaultReasoningEffort, shouldIncludeAccountBoundNativeOpenAi, shouldIncludeNativeOpenAi, type NativeContextLimits, type NativeContextLimitsInput } from "./catalog/metadata";
5
+ export { accountBoundNativeOpenAiSlugs, accountBoundNativeOpenAiSlugsBySelector, CODEX_NATIVE_ALIAS_CATALOG_KIND, NATIVE_DAYBREAK_BLUE_MODEL, NATIVE_GPT6_ASTRA_MODEL, NATIVE_GPT56_CONTEXT_WINDOW, NATIVE_GPT56_MAX_INPUT_TOKENS, NATIVE_GPT56_OPT_IN_CONTEXT_WINDOW, NATIVE_OPENAI_CAPABILITY_ALIAS_MODELS, NATIVE_OPENAI_MODELS, configuredNativeAliasSlugs, desktopAllowlistSuppressedNativeSlugs, isNativeAliasCatalogEntry, isNativeOpenAiCapabilityAliasModel, nativeContextLimits, nativeOpenAiCapabilitySourceSlug, nativeOpenAiContextWindow, nativeOpenAiMaxInputTokens, nativeOpenAiMaxOutputTokens, nativeOpenAiContextTier, disabledNativeSlugs, visibleNativeSlugs, desktopVisibleNativeSlugs, nativeModelRows, applyNativeVisibility, observedAccountBoundNativeEntries, observedAccountBoundNativeOpenAiSlugs, upstreamNativeEntry, nativeOpenAiSlugs, listCatalogNativeSlugs, nativeInputModalities, nativeReasoningEfforts, nativeDefaultReasoningEffort, shouldIncludeAccountBoundNativeOpenAi, shouldIncludeNativeOpenAi, type NativeContextLimits, type NativeContextLimitsInput } from "./catalog/metadata";
6
6
  export { isSpawnableCodexCandidate, codexExecInvocation, loadBundledCodexCatalog, materializeBundledCodexCatalog, loadCatalogTemplate } from "./catalog/bundled";
7
7
  export { nativeEffortClamp, shouldApplyNativeEffortClamp, catalogModelEfforts, codexSupportedReasoningEfforts, clampedDefaultEffort, clampEntryToCodexSupportedEfforts, clampCatalogModelsToCodexSupport } from "./catalog/effort";
8
8
  export { applyProviderConfigHints, isDatedVariantId, filterCatalogVisibleModels, gatherRoutedModels, clearGatherRoutedModelsInflight, augmentRoutedModelsWithRegistryOpenAiApiRows, augmentRoutedModelsWithMetadata, resolveComboCatalogMember, configuredComboTargetModelsByProvider } from "./catalog/provider-fetch";
@@ -429,6 +429,7 @@ export interface CatalogTrustedOpenAiApiPolicySnapshot {
429
429
  readonly models?: readonly string[];
430
430
  readonly modelContextWindows?: Readonly<Record<string, number>>;
431
431
  readonly modelMaxInputTokens?: Readonly<Record<string, number>>;
432
+ readonly modelMaxOutputTokens?: Readonly<Record<string, number>>;
432
433
  readonly virtualModels?: Readonly<Record<string, Readonly<{ wireModelId: string; reasoningMode: "pro" }>>>;
433
434
  readonly modelInputModalities?: Readonly<Record<string, readonly string[]>>;
434
435
  readonly modelReasoningEfforts?: Readonly<Record<string, readonly string[]>>;
@@ -2,6 +2,7 @@ import { join } from "node:path";
2
2
 
3
3
  import { getConfigDir, saveConfigPreservingClaudeCode, websocketsEnabled, withExpectedConfigGenerationSync } from "../config";
4
4
  import { reconcileSuccessfulModelDiscoveries } from "../providers/new-model-policy";
5
+ import { pendingModelSelectionProviders } from "../providers/initial-model-selection";
5
6
  import { COMBO_NAMESPACE } from "../combos";
6
7
  import { getAuthStorePath } from "../oauth/store";
7
8
  import type { OcxConfig } from "../types";
@@ -351,6 +352,7 @@ function prepareCatalog(
351
352
  disabledModels: new Set(config.disabledModels ?? []),
352
353
  selectedModelsByProvider,
353
354
  gatheredProviderNames,
355
+ pendingProviderNames: pendingModelSelectionProviders(config),
354
356
  degradedProviderNames,
355
357
  legacyCustomModelSlugs: legacyCustomModelCatalogSlugs(config),
356
358
  multiAgentMode,