@bitkyc08/opencodex 2.81.0 → 2.82.0

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 (212) hide show
  1. package/bin/ocx.mjs +166 -73
  2. package/gui/dist/assets/App-Ds_qL7o8.js +54 -0
  3. package/gui/dist/assets/{Tray-EwAOsP0S.js → Tray-BqHF5Trd.js} +1 -1
  4. package/gui/dist/assets/{index-Brk6F3fn.css → index-BAbcj51J.css} +1 -1
  5. package/gui/dist/assets/{index-RsoaAxXY.js → index-D3FBCNiP.js} +13 -13
  6. package/gui/dist/assets/{quota-summary-D2lN8tDm.js → quota-summary-DTBC8n7-.js} +1 -1
  7. package/gui/dist/index.html +2 -2
  8. package/package.json +1 -1
  9. package/src/adapters/anthropic.ts +1 -1
  10. package/src/adapters/openai-responses/compaction-search-history.ts +109 -0
  11. package/src/adapters/openai-responses/passthrough.ts +9 -4
  12. package/src/adapters/openai-responses/tool-output-recovery.ts +20 -0
  13. package/src/claude/agents-inject.ts +14 -3
  14. package/src/claude/context-windows.ts +19 -17
  15. package/src/claude/desktop-3p.ts +88 -12
  16. package/src/claude/desktop-profile.ts +2 -1
  17. package/src/claude/gateway-cache.ts +18 -1
  18. package/src/claude/inbound.ts +77 -3
  19. package/src/claude/intercept/cli-catalog.ts +3 -0
  20. package/src/claude/intercept/picker-ca-persistence.ts +7 -6
  21. package/src/claude/intercept/picker-models.ts +14 -6
  22. package/src/claude/intercept/settings.ts +3 -0
  23. package/src/claude/long-context.ts +66 -0
  24. package/src/claude/model-info.ts +19 -16
  25. package/src/claude/outbound.ts +94 -9
  26. package/src/claude/subagent-model.ts +40 -11
  27. package/src/cli/agent.ts +9 -5
  28. package/src/cli/aside-profile-recovery.ts +33 -0
  29. package/src/cli/capabilities-base.ts +5 -5
  30. package/src/cli/capabilities-integrations.ts +2 -2
  31. package/src/cli/cli-path-diagnostics.ts +132 -0
  32. package/src/cli/desktop-runtime-guidance.ts +65 -0
  33. package/src/cli/dispatch.ts +5 -0
  34. package/src/cli/doctor.ts +16 -3
  35. package/src/cli/index.ts +14 -9
  36. package/src/cli/integration-aside-sync.ts +2 -1
  37. package/src/cli/integrations.ts +16 -3
  38. package/src/cli/resolve.ts +25 -2
  39. package/src/cli/root.ts +4 -2
  40. package/src/cli/status.ts +64 -12
  41. package/src/cli/update-restart-child.ts +4 -2
  42. package/src/cli/update-restart-home.ts +28 -10
  43. package/src/cli/update-restart-service-record.ts +116 -0
  44. package/src/cli/update-restart-supervision.ts +96 -0
  45. package/src/cli/update-restart.ts +59 -13
  46. package/src/cli/v2.ts +27 -20
  47. package/src/cli/version-skew-notice.ts +77 -0
  48. package/src/clients/lazycodex.ts +1 -1
  49. package/src/clients/omo-role-models.ts +32 -7
  50. package/src/codex/auth-api/account-selection.ts +107 -0
  51. package/src/codex/auth-api/routes.ts +10 -49
  52. package/src/codex/auth-context.ts +20 -1
  53. package/src/codex/autostart-health.ts +12 -3
  54. package/src/codex/catalog/build-entries.ts +18 -0
  55. package/src/codex/catalog/bundled.ts +155 -26
  56. package/src/codex/catalog/combo-member.ts +3 -2
  57. package/src/codex/catalog/effort.ts +26 -5
  58. package/src/codex/catalog/gather-capture.ts +5 -0
  59. package/src/codex/catalog/model-hints.ts +15 -8
  60. package/src/codex/catalog/model-visibility.ts +3 -1
  61. package/src/codex/catalog/operator-tiers.ts +38 -0
  62. package/src/codex/catalog/provider-models.ts +43 -35
  63. package/src/codex/catalog/retained-sync.ts +7 -5
  64. package/src/codex/catalog/routed-gather.ts +15 -1
  65. package/src/codex/catalog-auto-refresh-sources.ts +3 -4
  66. package/src/codex/catalog-auto-refresh.ts +19 -0
  67. package/src/codex/catalog.ts +1 -1
  68. package/src/codex/codex-home-owner.ts +3 -0
  69. package/src/codex/config-write-lock.ts +333 -0
  70. package/src/codex/convergence.ts +2 -1
  71. package/src/codex/features.ts +257 -200
  72. package/src/codex/inject/config-write-section.ts +34 -0
  73. package/src/codex/inject/multi-agent-v2.ts +14 -6
  74. package/src/codex/inject/remove.ts +36 -11
  75. package/src/codex/inject/restore.ts +85 -20
  76. package/src/codex/inject-coordination.ts +15 -6
  77. package/src/codex/inject.ts +62 -20
  78. package/src/codex/journal.ts +62 -17
  79. package/src/codex/main-account-cache.ts +43 -0
  80. package/src/codex/paths.ts +2 -0
  81. package/src/codex/prepared-home.ts +18 -0
  82. package/src/codex/prompt-journal.ts +25 -12
  83. package/src/codex/prompt-layers.ts +49 -26
  84. package/src/codex/prompt-lock-claim.ts +134 -0
  85. package/src/codex/prompt-lock-evidence.ts +26 -0
  86. package/src/codex/prompt-lock-io.ts +16 -0
  87. package/src/codex/prompt-lock-owner.ts +130 -0
  88. package/src/codex/prompt-lock.ts +84 -53
  89. package/src/codex/routing/failure-window.ts +146 -0
  90. package/src/codex/routing/selection.ts +4 -1
  91. package/src/codex/routing.ts +13 -17
  92. package/src/codex/runtime.ts +106 -18
  93. package/src/codex/shim.ts +45 -19
  94. package/src/combos/jev-service-exchange.ts +281 -0
  95. package/src/combos/jev.ts +34 -220
  96. package/src/config/atomic-write.ts +1 -1
  97. package/src/config/codex-ws-reuse-setting.ts +10 -0
  98. package/src/config/live-reconcile.ts +17 -0
  99. package/src/config/load-degrade.ts +8 -0
  100. package/src/config/rebase-provenance.ts +11 -0
  101. package/src/config/schema/config-schema.ts +4 -0
  102. package/src/generated/compatibility-version.json +303 -171
  103. package/src/generated/model-metadata.ts +2 -2
  104. package/src/grok/inject.ts +50 -4
  105. package/src/grok/sync.ts +2 -1
  106. package/src/images/loop.ts +29 -11
  107. package/src/integrations/aside-profiles.ts +10 -3
  108. package/src/integrations/catalog-refresh.ts +18 -2
  109. package/src/integrations/writer.ts +9 -3
  110. package/src/lib/bun-path-runtime.d.mts +32 -0
  111. package/src/lib/bun-path-runtime.mjs +95 -0
  112. package/src/lib/bun-runtime-preflight.ts +140 -0
  113. package/src/lib/desktop-cli-handoff.d.mts +20 -0
  114. package/src/lib/desktop-cli-handoff.mjs +92 -0
  115. package/src/lib/desktop-cli-record.d.mts +29 -0
  116. package/src/lib/desktop-cli-record.mjs +102 -0
  117. package/src/lib/request-execution-budget.ts +8 -0
  118. package/src/lib/synced-state-location.ts +46 -6
  119. package/src/lib/test-home-guard.ts +87 -1
  120. package/src/lib/translator-budget.ts +17 -4
  121. package/src/lib/windows-atomic-replace.ts +38 -16
  122. package/src/lib/windows-elevation.ts +6 -0
  123. package/src/lib/windows-secret-acl.ts +92 -25
  124. package/src/lib/winsw.ts +2 -3
  125. package/src/oauth/anthropic-account-refusal.ts +39 -1
  126. package/src/oauth/anthropic-routing.ts +1 -1
  127. package/src/oauth/anthropic-send-ownership.ts +3 -3
  128. package/src/oauth/chatgpt.ts +113 -41
  129. package/src/oauth/index.ts +6 -2
  130. package/src/oauth/store.ts +1 -1
  131. package/src/providers/azure-model-metadata.ts +115 -0
  132. package/src/providers/derive.ts +21 -0
  133. package/src/providers/quota/vendor-probes-key.ts +127 -17
  134. package/src/providers/quota.ts +1 -1
  135. package/src/responses/reasoning-envelope.ts +29 -1
  136. package/src/responses/reasoning-replay-cache.ts +17 -1
  137. package/src/responses/spill-store.ts +15 -2
  138. package/src/responses/state/metrics.ts +8 -0
  139. package/src/responses/state/spill-failure.ts +12 -0
  140. package/src/responses/state/spill-queue.ts +12 -5
  141. package/src/responses/state.ts +18 -10
  142. package/src/responses/task-input.ts +17 -0
  143. package/src/server/background-lifecycle.ts +27 -0
  144. package/src/server/chat-completions.ts +1 -0
  145. package/src/server/chat-native.ts +28 -12
  146. package/src/server/claude-messages.ts +43 -6
  147. package/src/server/inbound-body-admission.ts +1 -0
  148. package/src/server/index/claude-intercept-lifecycle.ts +3 -1
  149. package/src/server/index/serve-options.ts +10 -2
  150. package/src/server/index/spend-ledger-lifecycle.ts +2 -1
  151. package/src/server/index/startup-warnings.ts +19 -0
  152. package/src/server/index.ts +1 -0
  153. package/src/server/management/agent-settings-routes.ts +93 -32
  154. package/src/server/management/codex-agent-role-routes.ts +4 -2
  155. package/src/server/management/codex-prompt-routes.ts +1 -0
  156. package/src/server/management/codex-role-auto-assign.ts +13 -2
  157. package/src/server/management/config-routes.ts +59 -8
  158. package/src/server/management/context.ts +2 -2
  159. package/src/server/management/shared.ts +3 -3
  160. package/src/server/messages-native-oauth.ts +9 -8
  161. package/src/server/messages-native.ts +49 -13
  162. package/src/server/messages-response-headers.ts +14 -0
  163. package/src/server/request-log-conversation.ts +48 -5
  164. package/src/server/request-log.ts +31 -2
  165. package/src/server/responses/adapter-continuation.ts +2 -2
  166. package/src/server/responses/adapter-dispatch.ts +34 -13
  167. package/src/server/responses/codex-ws-exchange.ts +17 -2
  168. package/src/server/responses/codex-ws-pool.ts +100 -13
  169. package/src/server/responses/codex-ws-wire.ts +9 -0
  170. package/src/server/responses/combo-requested-effort.ts +3 -1
  171. package/src/server/responses/core-codex-account.ts +22 -1
  172. package/src/server/responses/core-combo-native.ts +20 -3
  173. package/src/server/responses/core-combo.ts +22 -14
  174. package/src/server/responses/core-lifetime.ts +3 -36
  175. package/src/server/responses/core-options.ts +5 -0
  176. package/src/server/responses/core-replay.ts +73 -0
  177. package/src/server/responses/core.ts +3 -3
  178. package/src/server/responses/fetch-helpers.ts +26 -3
  179. package/src/server/responses/passthrough-delivery.ts +24 -2
  180. package/src/server/responses/passthrough-dispatch.ts +19 -2
  181. package/src/server/responses/request-prepare.ts +4 -0
  182. package/src/server/responses/request-send-budget.ts +112 -49
  183. package/src/server/responses/response-effects.ts +1 -0
  184. package/src/server/responses/run-turn-execution.ts +2 -0
  185. package/src/server/responses/sidecar-execution.ts +25 -16
  186. package/src/server/responses/sidecar-send-budget.ts +58 -0
  187. package/src/server/responses/ws-upstream.ts +19 -6
  188. package/src/server/startup-health-cache.ts +2 -1
  189. package/src/server/workflow-refusal.ts +3 -0
  190. package/src/service/cli.ts +20 -4
  191. package/src/service/desktop-command-guard.ts +32 -0
  192. package/src/service/desktop-startup.ts +66 -40
  193. package/src/service/desktop-supervision.d.mts +26 -0
  194. package/src/service/desktop-supervision.mjs +140 -0
  195. package/src/service/managing-cli.ts +1 -0
  196. package/src/service/orchestration.ts +46 -20
  197. package/src/service/repair.ts +25 -9
  198. package/src/service/state.ts +4 -3
  199. package/src/service/systemd.ts +17 -8
  200. package/src/service/windows-ops.ts +61 -22
  201. package/src/storage/policy-job.ts +5 -1
  202. package/src/storage/policy-worker.ts +3 -1
  203. package/src/storage/policy.ts +9 -0
  204. package/src/types/config.ts +21 -0
  205. package/src/types/request.ts +5 -0
  206. package/src/types/wire.ts +1 -0
  207. package/src/update/index.ts +57 -9
  208. package/src/update/restart-ownership.ts +10 -3
  209. package/src/update/runtime-ownership.d.mts +6 -0
  210. package/src/update/runtime-ownership.mjs +23 -7
  211. package/src/web-search/loop.ts +5 -2
  212. package/gui/dist/assets/App-7yhoys8a.js +0 -54
@@ -41,6 +41,7 @@ import { CODEX_REASONING_LEVELS, codexEffortRank, configuredReasoningEfforts, mo
41
41
  import { isModelVisionSidecarConsumer } from "../../vision/eligibility";
42
42
  import { getModelMetadata, getModelMetadataCaseInsensitive, listModelMetadata, resolveMetadataProvider, type ModelMetadata } from "../../generated/model-metadata";
43
43
  import { enrichProviderFromRegistry, shouldCaseFoldMetadataModelId } from "../../providers/derive";
44
+ import { isAzureModelMetadataDestination, refreshAzureModelMetadata } from "../../providers/azure-model-metadata";
44
45
  import {
45
46
  captureFastPolicyAuthority,
46
47
  fastPolicyForModel,
@@ -169,7 +170,8 @@ export async function fetchProviderModelsWithAuth(
169
170
  contextCap: number | undefined,
170
171
  resolveAuth: ModelsAuthResolver,
171
172
  ): Promise<ProviderModelsResult> {
172
- const { name, provider: prov, discovery, metadataModelIdCaseFold } = captured;
173
+ const { name, provider: prov, discovery, metadataModelIdCaseFold, metadataConfigDir } = captured;
174
+ const azureMetadata = isAzureModelMetadataDestination(prov.baseUrl);
173
175
  const observed = (
174
176
  models: CatalogModel[],
175
177
  state: CatalogGatherProviderModelOutcome["state"],
@@ -185,6 +187,7 @@ export async function fetchProviderModelsWithAuth(
185
187
  const anthropicSelection = isAnthropicOAuthInstance(name) && prov.authMode === "oauth" && mayResolveModelsOAuth(name, prov)
186
188
  ? captureOAuthAccountSelection(name) : null;
187
189
  if (prov.authMode === "forward") return observed([], "authoritative"); // ChatGPT backend has no /models
190
+ if (azureMetadata) await refreshAzureModelMetadata(prov.baseUrl, metadataConfigDir);
188
191
  const seedVertexDefault = prov.adapter === "google"
189
192
  && prov.googleMode === "vertex"
190
193
  && (prov.models?.length ?? 0) === 0
@@ -203,7 +206,7 @@ export async function fetchProviderModelsWithAuth(
203
206
  const configured: CatalogModel[] = configuredIds.map(id => ({
204
207
  id,
205
208
  provider: name,
206
- ...catalogHintsFromProviderConfig(name, prov, id, contextCap, metadataModelIdCaseFold, captured.effectiveAlias),
209
+ ...catalogHintsFromProviderConfig(name, prov, id, contextCap, metadataModelIdCaseFold, captured.effectiveAlias, metadataConfigDir),
207
210
  }));
208
211
  const withConfiguredRetention = (
209
212
  models: CatalogModel[],
@@ -219,6 +222,7 @@ export async function fetchProviderModelsWithAuth(
219
222
  seedVertexDefault,
220
223
  retainComboTargets: options?.retainComboTargets,
221
224
  metadataModelIdCaseFold,
225
+ metadataConfigDir,
222
226
  });
223
227
  if (
224
228
  options?.warnDrops === true
@@ -251,7 +255,7 @@ export async function fetchProviderModelsWithAuth(
251
255
  }
252
256
  return observed(ids.map(id => {
253
257
  const hints = catalogHintsFromProviderConfig(name, prov, id, contextCap,
254
- metadataModelIdCaseFold, captured.effectiveAlias);
258
+ metadataModelIdCaseFold, captured.effectiveAlias, metadataConfigDir);
255
259
  const observedWindow = kiroObservedContextWindow(id);
256
260
  return { id, provider: name, ...hints,
257
261
  ...(observedWindow !== undefined
@@ -296,7 +300,7 @@ export async function fetchProviderModelsWithAuth(
296
300
  : [{
297
301
  id: prov.defaultModel,
298
302
  provider: name,
299
- ...catalogHintsFromProviderConfig(name, prov, prov.defaultModel, contextCap, metadataModelIdCaseFold, captured.effectiveAlias),
303
+ ...catalogHintsFromProviderConfig(name, prov, prov.defaultModel, contextCap, metadataModelIdCaseFold, captured.effectiveAlias, metadataConfigDir),
300
304
  }];
301
305
  const vertexDefaultSeed = seedVertexDefault ? configured[0] : undefined;
302
306
  const withVertexDefaultSeed = (models: CatalogModel[]): CatalogModel[] => (
@@ -318,13 +322,13 @@ export async function fetchProviderModelsWithAuth(
318
322
  const fresh = getFreshCached(name, ttlMs, Date.now(), authorityIdentity);
319
323
  if (fresh) {
320
324
  return observed(withConfiguredRetention(
321
- applyConfigHintsToCachedModels(name, prov, fresh, contextCap, metadataModelIdCaseFold, captured.effectiveAlias),
325
+ applyConfigHintsToCachedModels(name, prov, fresh, contextCap, metadataModelIdCaseFold, captured.effectiveAlias, metadataConfigDir),
322
326
  ), "authoritative");
323
327
  }
324
328
  const scopedStale = getStaleCached(name, authorityIdentity);
325
329
  if (isModelsFetchCoolingDown(name, undefined, undefined, authorityIdentity) && scopedStale) {
326
330
  return observed(withConfiguredRetention(
327
- applyConfigHintsToCachedModels(name, prov, scopedStale, contextCap, metadataModelIdCaseFold, captured.effectiveAlias),
331
+ applyConfigHintsToCachedModels(name, prov, scopedStale, contextCap, metadataModelIdCaseFold, captured.effectiveAlias, metadataConfigDir),
328
332
  ), "degraded");
329
333
  }
330
334
  const live = await fetchCodeBuddyModels(profile, apiKey);
@@ -332,7 +336,7 @@ export async function fetchProviderModelsWithAuth(
332
336
  const discovered = live.models.map(id => ({
333
337
  id,
334
338
  provider: name,
335
- ...catalogHintsFromProviderConfig(name, prov, id, contextCap, metadataModelIdCaseFold, captured.effectiveAlias),
339
+ ...catalogHintsFromProviderConfig(name, prov, id, contextCap, metadataModelIdCaseFold, captured.effectiveAlias, metadataConfigDir),
336
340
  }));
337
341
  const forCache = withConfiguredRetention(discovered, { retainComboTargets: false });
338
342
  if (!setCached(name, forCache, Date.now(), cacheGeneration, authorityIdentity)) {
@@ -348,7 +352,7 @@ export async function fetchProviderModelsWithAuth(
348
352
  }
349
353
  const stale = getStaleCached(name, authorityIdentity);
350
354
  return observed(withConfiguredRetention(
351
- stale ? applyConfigHintsToCachedModels(name, prov, stale, contextCap, metadataModelIdCaseFold, captured.effectiveAlias) : configured,
355
+ stale ? applyConfigHintsToCachedModels(name, prov, stale, contextCap, metadataModelIdCaseFold, captured.effectiveAlias, metadataConfigDir) : configured,
352
356
  ), "degraded");
353
357
  }
354
358
  if (name === "zed" && prov.adapter === "zed") {
@@ -361,14 +365,14 @@ export async function fetchProviderModelsWithAuth(
361
365
  const cached = getFreshCached(name, ttlMs, Date.now(), authorityIdentity);
362
366
  if (cached) {
363
367
  return observed(
364
- withConfiguredRetention(applyConfigHintsToCachedModels(name, prov, cached, contextCap, metadataModelIdCaseFold, captured.effectiveAlias)),
368
+ withConfiguredRetention(applyConfigHintsToCachedModels(name, prov, cached, contextCap, metadataModelIdCaseFold, captured.effectiveAlias, metadataConfigDir)),
365
369
  "authoritative",
366
370
  );
367
371
  }
368
372
  const stale = getStaleCached(name, authorityIdentity);
369
373
  if (isModelsFetchCoolingDown(name, undefined, undefined, authorityIdentity) && stale) {
370
374
  return observed(
371
- withConfiguredRetention(applyConfigHintsToCachedModels(name, prov, stale, contextCap, metadataModelIdCaseFold, captured.effectiveAlias)),
375
+ withConfiguredRetention(applyConfigHintsToCachedModels(name, prov, stale, contextCap, metadataModelIdCaseFold, captured.effectiveAlias, metadataConfigDir)),
372
376
  "degraded",
373
377
  );
374
378
  }
@@ -388,7 +392,7 @@ export async function fetchProviderModelsWithAuth(
388
392
  ...(model.maxOutputTokens ? { maxOutputTokens: model.maxOutputTokens } : {}),
389
393
  ...(model.supportsImages ? { inputModalities: ["text", "image"] } : {}),
390
394
  ...(reasoningEfforts?.length ? { reasoningEfforts } : {}),
391
- ...catalogHintsFromProviderConfig(name, prov, model.id, contextCap, metadataModelIdCaseFold, captured.effectiveAlias),
395
+ ...catalogHintsFromProviderConfig(name, prov, model.id, contextCap, metadataModelIdCaseFold, captured.effectiveAlias, metadataConfigDir),
392
396
  } as CatalogModel;
393
397
  });
394
398
  const forCache = withConfiguredRetention(discovered, { retainComboTargets: false });
@@ -403,7 +407,7 @@ export async function fetchProviderModelsWithAuth(
403
407
  markProviderDiscoveryFailed(name, { reason: "provider" });
404
408
  }
405
409
  return observed(
406
- withConfiguredRetention(stale ? applyConfigHintsToCachedModels(name, prov, stale, contextCap, metadataModelIdCaseFold, captured.effectiveAlias) : configured),
410
+ withConfiguredRetention(stale ? applyConfigHintsToCachedModels(name, prov, stale, contextCap, metadataModelIdCaseFold, captured.effectiveAlias, metadataConfigDir) : configured),
407
411
  "degraded",
408
412
  );
409
413
  }
@@ -419,13 +423,13 @@ export async function fetchProviderModelsWithAuth(
419
423
  const fresh = getFreshCached(name, ttlMs, Date.now(), authorityIdentity);
420
424
  if (fresh) {
421
425
  return observed(withConfiguredRetention(
422
- applyConfigHintsToCachedModels(name, prov, fresh, contextCap, metadataModelIdCaseFold, captured.effectiveAlias),
426
+ applyConfigHintsToCachedModels(name, prov, fresh, contextCap, metadataModelIdCaseFold, captured.effectiveAlias, metadataConfigDir),
423
427
  ), "authoritative");
424
428
  }
425
429
  const scopedStale = getStaleCached(name, authorityIdentity);
426
430
  if (isModelsFetchCoolingDown(name) && scopedStale) {
427
431
  return observed(withConfiguredRetention(
428
- applyConfigHintsToCachedModels(name, prov, scopedStale, contextCap, metadataModelIdCaseFold, captured.effectiveAlias),
432
+ applyConfigHintsToCachedModels(name, prov, scopedStale, contextCap, metadataModelIdCaseFold, captured.effectiveAlias, metadataConfigDir),
429
433
  ), "degraded");
430
434
  }
431
435
  const live = await fetchQoderModels(profile, apiKey);
@@ -433,7 +437,7 @@ export async function fetchProviderModelsWithAuth(
433
437
  const discovered = live.models.map(id => ({
434
438
  id,
435
439
  provider: name,
436
- ...catalogHintsFromProviderConfig(name, prov, id, contextCap, metadataModelIdCaseFold, captured.effectiveAlias),
440
+ ...catalogHintsFromProviderConfig(name, prov, id, contextCap, metadataModelIdCaseFold, captured.effectiveAlias, metadataConfigDir),
437
441
  }));
438
442
  const forCache = withConfiguredRetention(discovered, { retainComboTargets: false });
439
443
  if (!setCached(name, forCache, Date.now(), cacheGeneration, authorityIdentity)) {
@@ -449,7 +453,7 @@ export async function fetchProviderModelsWithAuth(
449
453
  }
450
454
  const stale = getStaleCached(name, authorityIdentity);
451
455
  return observed(withConfiguredRetention(
452
- stale ? applyConfigHintsToCachedModels(name, prov, stale, contextCap, metadataModelIdCaseFold, captured.effectiveAlias) : configured,
456
+ stale ? applyConfigHintsToCachedModels(name, prov, stale, contextCap, metadataModelIdCaseFold, captured.effectiveAlias, metadataConfigDir) : configured,
453
457
  ), "degraded");
454
458
  }
455
459
  if (prov.adapter === "devin") {
@@ -464,7 +468,7 @@ export async function fetchProviderModelsWithAuth(
464
468
  const cachedDevin = getFreshCached(name, ttlMs, Date.now(), authorityIdentity);
465
469
  if (cachedDevin) {
466
470
  return observed(
467
- withConfiguredRetention(applyConfigHintsToCachedModels(name, prov, cachedDevin)),
471
+ withConfiguredRetention(applyConfigHintsToCachedModels(name, prov, cachedDevin, undefined, undefined, undefined, metadataConfigDir)),
468
472
  "authoritative",
469
473
  );
470
474
  }
@@ -472,7 +476,7 @@ export async function fetchProviderModelsWithAuth(
472
476
  const cooling = getStaleCached(name, authorityIdentity);
473
477
  return observed(
474
478
  withConfiguredRetention(
475
- cooling ? applyConfigHintsToCachedModels(name, prov, cooling) : configured,
479
+ cooling ? applyConfigHintsToCachedModels(name, prov, cooling, undefined, undefined, undefined, metadataConfigDir) : configured,
476
480
  ),
477
481
  "degraded",
478
482
  );
@@ -514,7 +518,7 @@ export async function fetchProviderModelsWithAuth(
514
518
  // record and the vision-sidecar rewrite keep winning — the live
515
519
  // value survives only when none of them applies.
516
520
  ...(liveResult.inputModalities[id]?.length ? { inputModalities: liveResult.inputModalities[id] } : {}),
517
- ...catalogHintsFromProviderConfig(name, prov, id, contextCap, metadataModelIdCaseFold, captured.effectiveAlias),
521
+ ...catalogHintsFromProviderConfig(name, prov, id, contextCap, metadataModelIdCaseFold, captured.effectiveAlias, metadataConfigDir),
518
522
  } as CatalogModel;
519
523
  });
520
524
  const forCache = withConfiguredRetention(result, { retainComboTargets: false });
@@ -530,7 +534,7 @@ export async function fetchProviderModelsWithAuth(
530
534
  }
531
535
  const stale = getStaleCached(name, authorityIdentity);
532
536
  return observed(
533
- withConfiguredRetention(stale ? applyConfigHintsToCachedModels(name, prov, stale) : configured),
537
+ withConfiguredRetention(stale ? applyConfigHintsToCachedModels(name, prov, stale, undefined, undefined, undefined, metadataConfigDir) : configured),
534
538
  "degraded",
535
539
  );
536
540
  }
@@ -547,7 +551,7 @@ export async function fetchProviderModelsWithAuth(
547
551
  const cachedCursor = getFreshCached(name, ttlMs, Date.now(), authorityIdentity);
548
552
  if (cachedCursor) {
549
553
  return observed(
550
- withConfiguredRetention(applyConfigHintsToCachedModels(name, prov, cachedCursor, undefined, metadataModelIdCaseFold, captured.effectiveAlias)),
554
+ withConfiguredRetention(applyConfigHintsToCachedModels(name, prov, cachedCursor, undefined, metadataModelIdCaseFold, captured.effectiveAlias, metadataConfigDir)),
551
555
  "authoritative",
552
556
  );
553
557
  }
@@ -555,7 +559,7 @@ export async function fetchProviderModelsWithAuth(
555
559
  const cooling = getStaleCached(name, authorityIdentity);
556
560
  return observed(
557
561
  withConfiguredRetention(
558
- cooling ? applyConfigHintsToCachedModels(name, prov, cooling, undefined, metadataModelIdCaseFold, captured.effectiveAlias) : configured,
562
+ cooling ? applyConfigHintsToCachedModels(name, prov, cooling, undefined, metadataModelIdCaseFold, captured.effectiveAlias, metadataConfigDir) : configured,
559
563
  ),
560
564
  "degraded",
561
565
  );
@@ -601,7 +605,7 @@ export async function fetchProviderModelsWithAuth(
601
605
  const staleCursor = getStaleCached(name, authorityIdentity);
602
606
  return observed(
603
607
  withConfiguredRetention(
604
- staleCursor ? applyConfigHintsToCachedModels(name, prov, staleCursor, undefined, metadataModelIdCaseFold, captured.effectiveAlias) : configured,
608
+ staleCursor ? applyConfigHintsToCachedModels(name, prov, staleCursor, undefined, metadataModelIdCaseFold, captured.effectiveAlias, metadataConfigDir) : configured,
605
609
  ),
606
610
  "degraded",
607
611
  );
@@ -619,7 +623,7 @@ export async function fetchProviderModelsWithAuth(
619
623
  if (fresh) {
620
624
  return observed(
621
625
  withConfiguredRetention(
622
- withVertexDefaultSeed(applyConfigHintsToCachedModels(name, prov, fresh, contextCap, metadataModelIdCaseFold, captured.effectiveAlias)),
626
+ withVertexDefaultSeed(applyConfigHintsToCachedModels(name, prov, fresh, contextCap, metadataModelIdCaseFold, captured.effectiveAlias, metadataConfigDir)),
623
627
  ),
624
628
  "authoritative",
625
629
  ); // dedups Codex's frequent /v1/models polling within the TTL
@@ -631,7 +635,7 @@ export async function fetchProviderModelsWithAuth(
631
635
  return observed(
632
636
  withConfiguredRetention(
633
637
  stale
634
- ? withVertexDefaultSeed(applyConfigHintsToCachedModels(name, prov, stale, contextCap, metadataModelIdCaseFold, captured.effectiveAlias))
638
+ ? withVertexDefaultSeed(applyConfigHintsToCachedModels(name, prov, stale, contextCap, metadataModelIdCaseFold, captured.effectiveAlias, metadataConfigDir))
635
639
  : failedDiscoveryConfigured,
636
640
  ),
637
641
  "degraded",
@@ -676,7 +680,7 @@ export async function fetchProviderModelsWithAuth(
676
680
  return {
677
681
  models: withConfiguredRetention(
678
682
  stale
679
- ? withVertexDefaultSeed(applyConfigHintsToCachedModels(name, prov, stale, contextCap, metadataModelIdCaseFold, captured.effectiveAlias))
683
+ ? withVertexDefaultSeed(applyConfigHintsToCachedModels(name, prov, stale, contextCap, metadataModelIdCaseFold, captured.effectiveAlias, metadataConfigDir))
680
684
  : failedDiscoveryConfigured,
681
685
  ),
682
686
  fallback: stale ? "stale" : "configured",
@@ -754,7 +758,7 @@ export async function fetchProviderModelsWithAuth(
754
758
  return observed(models, "degraded");
755
759
  }
756
760
  if (antigravity) {
757
- const live = antigravity.map(model => applyProviderConfigHints(name, prov, {
761
+ const upstream: CatalogModel[] = antigravity.map(model => ({
758
762
  id: model.id,
759
763
  provider: name,
760
764
  // Only an exact discovered wire map proves a new model's selectable effort ladder.
@@ -766,8 +770,9 @@ export async function fetchProviderModelsWithAuth(
766
770
  } : {}),
767
771
  ...(model.contextWindow ? { contextWindow: model.contextWindow } : {}),
768
772
  ...(model.inputModalities ? { inputModalities: model.inputModalities } : {}),
769
- }, contextCap, metadataModelIdCaseFold, captured.effectiveAlias));
770
- const forCache = withConfiguredRetention(live, { retainComboTargets: false });
773
+ }));
774
+ const live = applyConfigHintsToCachedModels(name, prov, upstream, contextCap, metadataModelIdCaseFold, captured.effectiveAlias, metadataConfigDir);
775
+ const forCache = azureMetadata ? upstream : withConfiguredRetention(live, { retainComboTargets: false });
771
776
  if (!isCurrentCacheGeneration()) {
772
777
  return observed(withConfiguredRetention(configured), "degraded");
773
778
  }
@@ -781,7 +786,7 @@ export async function fetchProviderModelsWithAuth(
781
786
  return observed(withConfiguredRetention(configured), "degraded");
782
787
  }
783
788
  markProviderDiscoveryOk(name, live.length);
784
- return observed(withConfiguredRetention(forCache, { warnDrops: true }), "authoritative");
789
+ return observed(withConfiguredRetention(azureMetadata ? live : forCache, { warnDrops: true }), "authoritative");
785
790
  }
786
791
  const googleAiStudio = effectiveGoogleMode(name, prov) === "ai-studio"
787
792
  ? extractGoogleAiStudioModelItems(bounded.value, discovery.maxModels)
@@ -820,7 +825,7 @@ export async function fetchProviderModelsWithAuth(
820
825
  provider: prov,
821
826
  }).catch(() => undefined)
822
827
  : undefined;
823
- const live = items.map(m => {
828
+ const upstream: CatalogModel[] = items.map(m => {
824
829
  const ownedBy = boundedOwnedBy(m.owned_by);
825
830
  // Precedence: the authoritative /v1/models row wins; /api/show fills only metadata the
826
831
  // models-API row does not carry. applyProviderConfigHints then applies explicit
@@ -836,22 +841,25 @@ export async function fetchProviderModelsWithAuth(
836
841
  ? { inputModalities: ["text", "image"] as string[] }
837
842
  : {}),
838
843
  };
839
- return applyProviderConfigHints(name, prov, {
844
+ return {
840
845
  id: m.id,
841
846
  provider: name,
842
847
  ...(ownedBy ? { owned_by: ownedBy } : {}),
843
848
  ...discoveredHints,
844
- }, contextCap, metadataModelIdCaseFold, captured.effectiveAlias);
849
+ };
845
850
  })
846
851
  .filter(m => shouldExposeProviderModel(name, m.id));
852
+ const live = applyConfigHintsToCachedModels(name, prov, upstream, contextCap, metadataModelIdCaseFold, captured.effectiveAlias, metadataConfigDir);
847
853
  // Capture the count BEFORE the alias/configured augmentation below pushes extra rows into
848
854
  // `live`; otherwise configured entries would be reported as discovered ones.
849
855
  const liveModelCount = live.length;
850
856
  // Dated-release aliases + configured retention (compat allow-list, combo targets,
851
857
  // Vertex default). Cache without combo retention so a later gather re-applies the
852
858
  // current capture's retain set on read (warm-cache OCX-111 / #1308).
853
- const forCache = withConfiguredRetention(live, { retainComboTargets: false });
854
- const returned = withConfiguredRetention(forCache, { warnDrops: true });
859
+ // Azure hints depend on the admitted config root. Keep only upstream evidence in the
860
+ // shared TTL/stale cache; every read reapplies metadata and retention for its own root.
861
+ const forCache = azureMetadata ? upstream : withConfiguredRetention(live, { retainComboTargets: false });
862
+ const returned = withConfiguredRetention(azureMetadata ? live : forCache, { warnDrops: true });
855
863
  const droppedConfiguredIds = configured
856
864
  .map(model => model.id)
857
865
  .filter(id => !returned.some(model => model.id === id));
@@ -2,7 +2,7 @@ import { projectAntigravitySelectedModels } from "../../providers/antigravity-ef
2
2
  import { existsSync, readFileSync } from "node:fs";
3
3
  import { join } from "node:path";
4
4
  import { initializeConfigOwnership } from "../../lib/config-ownership";
5
- import { getConfigDir, loadConfig, websocketsEnabled, withConfigMutationLockSync } from "../../config";
5
+ import { getConfigDir, loadConfig, ultraFastTierEnabled, websocketsEnabled, withConfigMutationLockSync } from "../../config";
6
6
  import { shouldSyncCodexOnStart } from "../desired-state";
7
7
  import { legacyCustomModelCatalogSlugs } from "../custom-model-catalog-migration";
8
8
  import { getCodexHome } from "../paths";
@@ -54,7 +54,7 @@ import {
54
54
  upstreamNativeEntry,
55
55
  } from "./metadata";
56
56
  import { trustedAccountBoundNativeCatalogSlug } from "./account-models";
57
- import { bundledCatalogCacheState, loadBundledCodexCatalog } from "./bundled";
57
+ import { bundledCatalogCacheState, bundledCodexCatalogSnapshot, loadBundledCodexCatalogAsync } from "./bundled";
58
58
  import { isMultiAgentV2Enabled } from "../features";
59
59
  import { clampCatalogModelsToCodexSupport } from "./effort";
60
60
  import { suppressedSyntheticMaxCatalogSlugs } from "./model-hints";
@@ -155,7 +155,7 @@ function optionalFileBytes(path: string): string | null {
155
155
  }
156
156
 
157
157
  function loadCatalogForRetainedSync(path: string): RawCatalog | null {
158
- const bundled = isDefaultCatalogPath(path) ? loadBundledCodexCatalog() : null;
158
+ const bundled = isDefaultCatalogPath(path) ? bundledCodexCatalogSnapshot() : null;
159
159
  if (bundled) return JSON.parse(JSON.stringify(bundled)) as RawCatalog;
160
160
  const active = readCatalog(path);
161
161
  // A valid configured custom file remains the content authority even when it has no bare native
@@ -508,6 +508,7 @@ function writeRetainedCatalogSync({
508
508
  }).filter(entry => trustedAccountBoundNativeCatalogSlug(entry) !== undefined)
509
509
  : [];
510
510
  catalog.models = mergeCatalogEntriesFromObservedState({
511
+ ultraFastTier: ultraFastTierEnabled(config),
511
512
  modelPickerOrder,
512
513
  accountSelectors,
513
514
  catalogModels: catalogModelsForMerge,
@@ -542,7 +543,7 @@ function writeRetainedCatalogSync({
542
543
  },
543
544
  });
544
545
  applyNativeAccessPrograms(catalog.models, modelEntitlements, accountTargets);
545
- clampCatalogModelsToCodexSupport(catalog.models);
546
+ clampCatalogModelsToCodexSupport(catalog.models, { observedCatalog: bundledCodexCatalogSnapshot() });
546
547
  finalizeAutoReviewModelOverride(catalog.models, catalogModelsForMerge, config);
547
548
  // Last mutation before serialization; see `enforceCatalogSlugUniqueness` for why the ordering
548
549
  // against the effort clamp is load-bearing rather than cosmetic.
@@ -623,6 +624,8 @@ export async function syncCatalogModels(
623
624
  await resolvePendingInitialModelSelection(config);
624
625
  }
625
626
  const owningCodexHome = getCodexHome();
627
+ // Settle before reading content and its matching evidence; no pre-refresh catalog is reused.
628
+ await loadBundledCodexCatalogAsync();
626
629
  const preflightRead = readRetainedCatalogSync(config);
627
630
  if (preflightRead === null) {
628
631
  return {
@@ -643,7 +646,6 @@ export async function syncCatalogModels(
643
646
  // The persisted runtime selection is covered by the filesystem evidence above
644
647
  // rather than by a process epoch; see `retainedCatalogProcessEvidence` for why
645
648
  // the in-memory runtime memo cannot be baselined honestly from this path.
646
- loadBundledCodexCatalog();
647
649
  const prepared: RetainedCatalogSyncRead = {
648
650
  ...preflightRead,
649
651
  evidence: retainedCatalogSyncEvidence(config, preflightRead.catalogPath, preflightRead.catalog),
@@ -347,6 +347,7 @@ async function gatherRoutedModelsUncached(
347
347
  lists.flat(),
348
348
  config,
349
349
  capture.openAiApiPolicy,
350
+ activeProviders.find(provider => provider.name === OPENAI_API_PROVIDER_ID)?.metadataConfigDir,
350
351
  );
351
352
  const apiProvider = activeProviders.find(provider => provider.name === OPENAI_API_PROVIDER_ID);
352
353
  // Trusted reconstruction replaces whole rows, including the earlier Fast hints.
@@ -363,12 +364,16 @@ async function gatherRoutedModelsUncached(
363
364
  const metadataModelIdCaseFoldByProvider = new Map(
364
365
  activeProviders.map(provider => [provider.name, provider.metadataModelIdCaseFold]),
365
366
  );
367
+ const metadataConfigDirByProvider = new Map(
368
+ activeProviders.map(provider => [provider.name, provider.metadataConfigDir]),
369
+ );
366
370
  const all = augmentRoutedModelsWithMetadata(
367
371
  apiAugmented,
368
372
  activeProviders.map(provider => provider.name),
369
373
  config.providers,
370
374
  config,
371
375
  metadataModelIdCaseFoldByProvider,
376
+ metadataConfigDirByProvider,
372
377
  )
373
378
  // Drop image/video generation models (e.g. Grok image/video) by default. Cursor's static catalog
374
379
  // intentionally mirrors Cursor's public model table, including Gemini image preview, so the
@@ -511,6 +516,7 @@ async function gatherRoutedModelsUncached(
511
516
  providerContextCap(config, target.provider),
512
517
  nativeAliasFallback,
513
518
  metadataModelIdCaseFoldByProvider.get(target.provider),
519
+ metadataConfigDirByProvider.get(target.provider),
514
520
  ))
515
521
  .filter((member): member is CatalogModel => member !== undefined);
516
522
  const derived = deriveComboCatalogModel(id, combo, members);
@@ -591,7 +597,7 @@ async function gatherRoutedModelsUncached(
591
597
  id: cm.modelId,
592
598
  provider: cm.provider,
593
599
  ...(nativeAliasMaxOutputTokens !== undefined ? { maxOutputTokens: nativeAliasMaxOutputTokens } : {}),
594
- }, cm.modelId, metadataModelIdCaseFoldByProvider.get(cm.provider))
600
+ }, cm.modelId, metadataModelIdCaseFoldByProvider.get(cm.provider), metadataConfigDirByProvider.get(cm.provider))
595
601
  : nativeAliasMaxOutputTokens;
596
602
  const configuredAutoCompact = configuredAutoCompactTokenLimit(rawProvider, cm.modelId);
597
603
  const customAutoCompactTokenLimit = codexForwardNativeCapabilityAlias
@@ -827,6 +833,7 @@ function augmentRoutedModelsWithCapturedOpenAiApiRows(
827
833
  models: CatalogModel[],
828
834
  config: OcxConfig,
829
835
  policy: CatalogTrustedOpenAiApiPolicySnapshot,
836
+ metadataConfigDir?: string,
830
837
  ): CatalogModel[] {
831
838
  if (policy.state !== "captured" || !policy.models) return models;
832
839
  const configured = config.providers[OPENAI_API_PROVIDER_ID];
@@ -862,6 +869,8 @@ function augmentRoutedModelsWithCapturedOpenAiApiRows(
862
869
  ? { provider: OPENAI_API_PROVIDER_ID, id, maxOutputTokens: policy.modelMaxOutputTokens[id] }
863
870
  : existingById.get(id) ?? { provider: OPENAI_API_PROVIDER_ID, id },
864
871
  policy.virtualModels?.[id]?.wireModelId ?? id,
872
+ undefined,
873
+ metadataConfigDir,
865
874
  );
866
875
  return {
867
876
  provider: OPENAI_API_PROVIDER_ID,
@@ -920,6 +929,7 @@ export function augmentRoutedModelsWithMetadata(
920
929
  providers?: Record<string, OcxProviderConfig>,
921
930
  caps?: Pick<OcxConfig, "providerContextCaps">,
922
931
  metadataModelIdCaseFoldByProvider?: ReadonlyMap<string, boolean>,
932
+ metadataConfigDirByProvider?: ReadonlyMap<string, string>,
923
933
  ): CatalogModel[] {
924
934
  const out = [...models];
925
935
  const indexByKey = new Map(out.map((model, index) => [`${model.provider}/${model.id}`, index]));
@@ -947,6 +957,8 @@ export function augmentRoutedModelsWithMetadata(
947
957
  seeded,
948
958
  contextCap,
949
959
  metadataModelIdCaseFoldByProvider?.get(provider),
960
+ undefined,
961
+ metadataConfigDirByProvider?.get(provider),
950
962
  )
951
963
  : seeded;
952
964
  continue;
@@ -969,6 +981,8 @@ export function augmentRoutedModelsWithMetadata(
969
981
  model,
970
982
  contextCap,
971
983
  metadataModelIdCaseFoldByProvider?.get(provider),
984
+ undefined,
985
+ metadataConfigDirByProvider?.get(provider),
972
986
  )
973
987
  : {}),
974
988
  });
@@ -34,10 +34,9 @@ export async function refreshCatalogAutoRefreshSources(
34
34
  current: () => boolean,
35
35
  ): Promise<void> {
36
36
  // Runtime selection also supplies the roster's trusted client version after an upgrade.
37
- await bestEffortSource(async active => {
38
- const { loadBundledCodexCatalog } = await import("./catalog/bundled");
39
- if (active() && current()) loadBundledCodexCatalog();
40
- // The synchronous loader bounds its selected-runtime subprocess probes itself.
37
+ await bestEffortSource(async (active, signal) => {
38
+ const { loadBundledCodexCatalogAsync } = await import("./catalog/bundled");
39
+ if (active() && current()) await loadBundledCodexCatalogAsync({}, () => active() && current(), signal);
41
40
  });
42
41
  if (!current()) return;
43
42
  await bestEffortSource(async active => {
@@ -50,6 +50,14 @@ let generation = 0;
50
50
  /** setInterval does not skip a firing while the previous callback is still awaiting. */
51
51
  let inFlight = false;
52
52
 
53
+ /** Refreshes already-connected clients after a changed catalog; owned by the server lifecycle. */
54
+ export type CatalogChangedClientFanout =
55
+ (isCurrent: () => boolean) => Promise<ReadonlyArray<{ readonly ok: boolean }>>;
56
+ let clientFanout: CatalogChangedClientFanout | null = null;
57
+ export function setCatalogAutoRefreshClientFanout(fanout: CatalogChangedClientFanout | null): void {
58
+ clientFanout = fanout;
59
+ }
60
+
53
61
  /** Read only a regular file, with a byte limit even if it grows after the stat. */
54
62
  function readBoundedRegularFile(path: string, maxBytes: number): string | null {
55
63
  let fd: number | undefined;
@@ -259,6 +267,17 @@ async function tick(): Promise<void> {
259
267
  ? "[catalog-auto-refresh] served model set changed; running Codex sessions keep the old list until restarted (ocx sync --restart-codex)"
260
268
  : "[catalog-auto-refresh] served model set changed");
261
269
  }
270
+ const fanout = clientFanout;
271
+ if (outcome.changed && fanout) {
272
+ const current = () => entryGeneration === generation;
273
+ try {
274
+ const failed = (await fanout(current)).filter(result => !result.ok).length;
275
+ // Outcomes can contain private paths and reasons; only the failure count leaves this hook.
276
+ if (current() && failed > 0) console.warn(`[catalog-auto-refresh] ${failed} client integration(s) were not refreshed; ocx sync retries them`);
277
+ } catch {
278
+ if (current()) console.warn("[catalog-auto-refresh] client integrations were not refreshed; ocx sync retries them");
279
+ }
280
+ }
262
281
  } catch {
263
282
  // A failed refresh is not an error worth surfacing: the next tick tries again.
264
283
  } finally {
@@ -4,7 +4,7 @@ export { isMediaGenerationModelId, shouldExposeRoutedModel, readCodexCatalogPath
4
4
  export type { CatalogModel, MultiAgentMode } from "./catalog/parsing";
5
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
- export { nativeEffortClamp, shouldApplyNativeEffortClamp, catalogModelEfforts, codexSupportedReasoningEfforts, clampedDefaultEffort, clampEntryToCodexSupportedEfforts, clampCatalogModelsToCodexSupport } from "./catalog/effort";
7
+ export { nativeEffortClamp, shouldApplyNativeEffortClamp, catalogModelEfforts, catalogModelLadders, codexSupportedReasoningEfforts, clampedDefaultEffort, clampEntryToCodexSupportedEfforts, clampCatalogModelsToCodexSupport } from "./catalog/effort";
8
8
  export { applyProviderConfigHints, isDatedVariantId, filterCatalogVisibleModels, gatherRoutedModels, clearGatherRoutedModelsInflight, augmentRoutedModelsWithRegistryOpenAiApiRows, augmentRoutedModelsWithMetadata, resolveComboCatalogMember, configuredComboTargetModelsByProvider } from "./catalog/provider-fetch";
9
9
  export { deriveComboCatalogModel, exactComboCatalogSlugs, getLastComboCatalogOmissions, resetOpenAiApiCatalogWarningStateForTests, uniqueCatalogModelsForPublicList, uniqueCatalogModelsForRawPublicList, buildComboCatalogOmission, comboCatalogOmissionReason, summarizeComboCatalogOmissions } from "./catalog/aggregation";
10
10
  export type { ComboCatalogOmission, ComboCatalogOmissionReason } from "./catalog/aggregation";
@@ -39,6 +39,9 @@ function provenAbsent(path: string): boolean {
39
39
  catch (error) { return absent(error) && provenAbsent(parent); }
40
40
  }
41
41
 
42
+ /** A missing directory entry is a no-op; dangling links and unreadable homes are not. */
43
+ export function codexHomeIsAbsent(home: string): boolean { return provenAbsent(home); }
44
+
42
45
  function validJournal(value: unknown): value is Record<string, unknown> {
43
46
  if (!value || typeof value !== "object" || Array.isArray(value)) return false;
44
47
  const journal = value as Record<string, unknown>;