@bitkyc08/opencodex 2.81.0 → 2.83.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 (272) hide show
  1. package/bin/ocx.mjs +166 -73
  2. package/gui/dist/assets/App-Ba8uG696.js +54 -0
  3. package/gui/dist/assets/{Tray-EwAOsP0S.js → Tray-IImbNPRy.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-BHRZdJuv.js} +13 -13
  6. package/gui/dist/assets/{quota-summary-D2lN8tDm.js → quota-summary-DiJO6sYY.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/cursor/catalog.ts +2 -2
  11. package/src/adapters/cursor/effort-map.ts +1 -1
  12. package/src/adapters/devin/live-models.ts +2 -2
  13. package/src/adapters/google-truncation.ts +22 -0
  14. package/src/adapters/google.ts +7 -3
  15. package/src/adapters/ollama-native.ts +18 -14
  16. package/src/adapters/openai-responses/compaction-search-history.ts +109 -0
  17. package/src/adapters/openai-responses/passthrough.ts +11 -4
  18. package/src/adapters/openai-responses/plaintext-reasoning-notice.ts +85 -0
  19. package/src/adapters/openai-responses/reasoning.ts +11 -1
  20. package/src/adapters/openai-responses/tool-output-recovery.ts +20 -0
  21. package/src/bridge/errors.ts +7 -1
  22. package/src/claude/agents-inject.ts +14 -3
  23. package/src/claude/context-windows.ts +22 -20
  24. package/src/claude/desktop-3p.ts +88 -12
  25. package/src/claude/desktop-picker.ts +1 -1
  26. package/src/claude/desktop-profile.ts +2 -1
  27. package/src/claude/gateway-cache.ts +18 -1
  28. package/src/claude/inbound.ts +77 -3
  29. package/src/claude/intercept/cli-catalog.ts +3 -0
  30. package/src/claude/intercept/picker-ca-persistence.ts +7 -6
  31. package/src/claude/intercept/picker-models.ts +14 -6
  32. package/src/claude/intercept/settings.ts +3 -0
  33. package/src/claude/long-context.ts +66 -0
  34. package/src/claude/model-info.ts +19 -16
  35. package/src/claude/outbound.ts +94 -9
  36. package/src/claude/subagent-model.ts +40 -11
  37. package/src/cli/account-auth.ts +20 -12
  38. package/src/cli/agent.ts +9 -5
  39. package/src/cli/aside-profile-recovery.ts +33 -0
  40. package/src/cli/capabilities-base.ts +5 -5
  41. package/src/cli/capabilities-integrations.ts +2 -2
  42. package/src/cli/cli-path-diagnostics.ts +132 -0
  43. package/src/cli/desktop-runtime-guidance.ts +65 -0
  44. package/src/cli/dispatch.ts +5 -0
  45. package/src/cli/doctor.ts +16 -3
  46. package/src/cli/index.ts +14 -9
  47. package/src/cli/integration-aside-sync.ts +2 -1
  48. package/src/cli/integrations.ts +16 -3
  49. package/src/cli/resolve.ts +26 -3
  50. package/src/cli/root.ts +4 -2
  51. package/src/cli/status.ts +64 -12
  52. package/src/cli/update-restart-child.ts +4 -2
  53. package/src/cli/update-restart-home.ts +28 -10
  54. package/src/cli/update-restart-service-record.ts +116 -0
  55. package/src/cli/update-restart-supervision.ts +96 -0
  56. package/src/cli/update-restart.ts +59 -13
  57. package/src/cli/v2.ts +27 -20
  58. package/src/cli/version-skew-notice.ts +77 -0
  59. package/src/clients/config-export/model-metadata.ts +16 -0
  60. package/src/clients/config-export/zcode-store.ts +46 -11
  61. package/src/clients/config-export/zcode.ts +3 -5
  62. package/src/clients/config-export.ts +1 -1
  63. package/src/clients/lazycodex.ts +1 -1
  64. package/src/clients/omo-role-models.ts +32 -7
  65. package/src/codex/auth-api/account-selection.ts +107 -0
  66. package/src/codex/auth-api/pool-mode-gate.ts +4 -5
  67. package/src/codex/auth-api/routes.ts +10 -49
  68. package/src/codex/auth-context.ts +28 -4
  69. package/src/codex/autostart-health.ts +12 -3
  70. package/src/codex/catalog/build-entries.ts +18 -0
  71. package/src/codex/catalog/bundled.ts +155 -26
  72. package/src/codex/catalog/combo-member.ts +3 -2
  73. package/src/codex/catalog/effort.ts +26 -5
  74. package/src/codex/catalog/gather-capture.ts +5 -0
  75. package/src/codex/catalog/model-hints.ts +15 -8
  76. package/src/codex/catalog/model-visibility.ts +3 -1
  77. package/src/codex/catalog/native-models.ts +1 -1
  78. package/src/codex/catalog/operator-tiers.ts +38 -0
  79. package/src/codex/catalog/provider-models.ts +68 -35
  80. package/src/codex/catalog/retained-sync.ts +7 -5
  81. package/src/codex/catalog/routed-gather.ts +15 -1
  82. package/src/codex/catalog-auto-refresh-sources.ts +3 -4
  83. package/src/codex/catalog-auto-refresh.ts +19 -0
  84. package/src/codex/catalog.ts +1 -1
  85. package/src/codex/codex-home-owner.ts +3 -0
  86. package/src/codex/config-write-lock.ts +333 -0
  87. package/src/codex/convergence.ts +2 -1
  88. package/src/codex/desktop-app/darwin.ts +1 -1
  89. package/src/codex/desktop-app/handoff.ts +1 -1
  90. package/src/codex/desktop-app/linux.ts +1 -1
  91. package/src/codex/desktop-app/lock.ts +1 -1
  92. package/src/codex/desktop-app/types.ts +1 -1
  93. package/src/codex/desktop-app/windows.ts +1 -1
  94. package/src/codex/desktop-app-restart.ts +1 -1
  95. package/src/codex/features.ts +257 -200
  96. package/src/codex/inject/config-write-section.ts +34 -0
  97. package/src/codex/inject/multi-agent-v2.ts +14 -6
  98. package/src/codex/inject/remove.ts +36 -11
  99. package/src/codex/inject/restore.ts +85 -20
  100. package/src/codex/inject-coordination.ts +15 -6
  101. package/src/codex/inject.ts +62 -20
  102. package/src/codex/journal.ts +62 -17
  103. package/src/codex/main-account-cache.ts +43 -0
  104. package/src/codex/main-account.ts +6 -7
  105. package/src/codex/paths.ts +2 -0
  106. package/src/codex/prepared-home.ts +18 -0
  107. package/src/codex/prompt-journal.ts +25 -12
  108. package/src/codex/prompt-layers.ts +49 -26
  109. package/src/codex/prompt-lock-claim.ts +134 -0
  110. package/src/codex/prompt-lock-evidence.ts +26 -0
  111. package/src/codex/prompt-lock-io.ts +16 -0
  112. package/src/codex/prompt-lock-owner.ts +130 -0
  113. package/src/codex/prompt-lock.ts +84 -53
  114. package/src/codex/routing/failure-window.ts +146 -0
  115. package/src/codex/routing/selection.ts +4 -1
  116. package/src/codex/routing.ts +13 -17
  117. package/src/codex/runtime.ts +106 -18
  118. package/src/codex/shim.ts +45 -19
  119. package/src/combos/jev-service-exchange.ts +281 -0
  120. package/src/combos/jev.ts +34 -220
  121. package/src/config/atomic-write.ts +1 -1
  122. package/src/config/codex-ws-reuse-setting.ts +10 -0
  123. package/src/config/live-reconcile.ts +17 -0
  124. package/src/config/load-degrade.ts +8 -0
  125. package/src/config/rebase-provenance.ts +11 -0
  126. package/src/config/schema/config-schema.ts +4 -0
  127. package/src/generated/compatibility-version.json +375 -227
  128. package/src/generated/model-metadata.ts +2 -2
  129. package/src/grok/inject.ts +276 -206
  130. package/src/grok/reset-coupon-ledger.ts +223 -57
  131. package/src/grok/reset-coupons.ts +13 -6
  132. package/src/grok/sync.ts +2 -1
  133. package/src/images/loop.ts +29 -11
  134. package/src/integrations/aside-profiles.ts +10 -3
  135. package/src/integrations/catalog-refresh.ts +18 -2
  136. package/src/integrations/cursor-detect.ts +1 -1
  137. package/src/integrations/writer.ts +9 -3
  138. package/src/lib/bun-path-runtime.d.mts +32 -0
  139. package/src/lib/bun-path-runtime.mjs +95 -0
  140. package/src/lib/bun-runtime-preflight.ts +140 -0
  141. package/src/lib/desktop-cli-handoff.d.mts +20 -0
  142. package/src/lib/desktop-cli-handoff.mjs +92 -0
  143. package/src/lib/desktop-cli-record.d.mts +29 -0
  144. package/src/lib/desktop-cli-record.mjs +102 -0
  145. package/src/lib/errors.ts +25 -0
  146. package/src/lib/redact.ts +2 -2
  147. package/src/lib/request-execution-budget.ts +8 -0
  148. package/src/lib/synced-state-location.ts +46 -6
  149. package/src/lib/test-home-guard.ts +87 -1
  150. package/src/lib/translator-budget.ts +17 -4
  151. package/src/lib/windows-atomic-replace.ts +38 -16
  152. package/src/lib/windows-elevation.ts +6 -0
  153. package/src/lib/windows-secret-acl.ts +92 -25
  154. package/src/lib/winsw.ts +2 -3
  155. package/src/oauth/anthropic-account-refusal.ts +39 -1
  156. package/src/oauth/anthropic-routing.ts +1 -1
  157. package/src/oauth/anthropic-send-ownership.ts +3 -3
  158. package/src/oauth/chatgpt.ts +113 -41
  159. package/src/oauth/index.ts +7 -3
  160. package/src/oauth/meta-muse-device.ts +1 -1
  161. package/src/oauth/store.ts +1 -1
  162. package/src/oauth/types.ts +1 -1
  163. package/src/providers/anthropic-fast.ts +1 -1
  164. package/src/providers/anthropic-reset-grant-ledger.ts +1 -1
  165. package/src/providers/azure-model-metadata.ts +115 -0
  166. package/src/providers/derive.ts +21 -0
  167. package/src/providers/devin-cli-authmode-migration.ts +1 -1
  168. package/src/providers/devin-provider-merge-migration.ts +1 -1
  169. package/src/providers/github-copilot-auto.ts +169 -0
  170. package/src/providers/model-presets.ts +1 -1
  171. package/src/providers/muse-subscription-usage.ts +1 -1
  172. package/src/providers/quota/vendor-probes-key.ts +127 -17
  173. package/src/providers/quota.ts +1 -1
  174. package/src/providers/registry/entries-core.ts +6 -6
  175. package/src/providers/registry/entries-extended.ts +4 -0
  176. package/src/providers/registry/model-seeds.ts +3 -3
  177. package/src/providers/xai-fast-model.ts +1 -1
  178. package/src/responses/reasoning-envelope.ts +29 -1
  179. package/src/responses/reasoning-replay-cache.ts +17 -1
  180. package/src/responses/spill-store.ts +15 -2
  181. package/src/responses/state/metrics.ts +8 -0
  182. package/src/responses/state/spill-failure.ts +12 -0
  183. package/src/responses/state/spill-queue.ts +12 -5
  184. package/src/responses/state.ts +18 -10
  185. package/src/responses/task-input.ts +17 -0
  186. package/src/server/background-lifecycle.ts +27 -0
  187. package/src/server/chat-completions.ts +1 -0
  188. package/src/server/chat-native.ts +28 -12
  189. package/src/server/claude-messages.ts +43 -6
  190. package/src/server/inbound-body-admission.ts +25 -7
  191. package/src/server/index/claude-intercept-lifecycle.ts +3 -1
  192. package/src/server/index/link-listener.ts +30 -17
  193. package/src/server/index/serve-options.ts +18 -4
  194. package/src/server/index/spend-ledger-lifecycle.ts +2 -1
  195. package/src/server/index/startup-warnings.ts +19 -0
  196. package/src/server/index.ts +2 -1
  197. package/src/server/lifecycle.ts +31 -4
  198. package/src/server/management/agent-settings-routes.ts +93 -32
  199. package/src/server/management/codex-agent-role-routes.ts +4 -2
  200. package/src/server/management/codex-prompt-routes.ts +1 -0
  201. package/src/server/management/codex-role-auto-assign.ts +13 -2
  202. package/src/server/management/config-routes.ts +59 -8
  203. package/src/server/management/context.ts +2 -2
  204. package/src/server/management/grok-coupon-routes.ts +277 -52
  205. package/src/server/management/link-routes.ts +42 -20
  206. package/src/server/management/route-registry.ts +2 -2
  207. package/src/server/management/shared.ts +3 -3
  208. package/src/server/messages-native-oauth.ts +9 -8
  209. package/src/server/messages-native.ts +49 -13
  210. package/src/server/messages-response-headers.ts +14 -0
  211. package/src/server/relay-eager.ts +2 -0
  212. package/src/server/relay.ts +18 -7
  213. package/src/server/request-log-conversation.ts +48 -5
  214. package/src/server/request-log-failure-attribution.ts +3 -0
  215. package/src/server/request-log-terminal-status.ts +186 -0
  216. package/src/server/request-log.ts +15 -89
  217. package/src/server/responses/adapter-continuation.ts +11 -3
  218. package/src/server/responses/adapter-dispatch.ts +99 -19
  219. package/src/server/responses/codex-ws-exchange.ts +17 -2
  220. package/src/server/responses/codex-ws-pool.ts +100 -13
  221. package/src/server/responses/codex-ws-wire.ts +9 -0
  222. package/src/server/responses/combo-requested-effort.ts +3 -1
  223. package/src/server/responses/compact.ts +3 -2
  224. package/src/server/responses/core-codex-account.ts +22 -1
  225. package/src/server/responses/core-combo-native.ts +20 -3
  226. package/src/server/responses/core-combo.ts +22 -14
  227. package/src/server/responses/core-lifetime.ts +3 -36
  228. package/src/server/responses/core-opaque-recovery.ts +5 -5
  229. package/src/server/responses/core-options.ts +5 -0
  230. package/src/server/responses/core-replay.ts +73 -0
  231. package/src/server/responses/core.ts +9 -9
  232. package/src/server/responses/empty-completion-guard.ts +18 -2
  233. package/src/server/responses/fetch-helpers.ts +26 -3
  234. package/src/server/responses/jev-model-invoke.ts +1 -1
  235. package/src/server/responses/passthrough-delivery.ts +24 -2
  236. package/src/server/responses/passthrough-dispatch.ts +134 -62
  237. package/src/server/responses/passthrough-execution.ts +3 -3
  238. package/src/server/responses/request-prepare.ts +4 -0
  239. package/src/server/responses/request-send-budget.ts +112 -49
  240. package/src/server/responses/request-transport.ts +125 -5
  241. package/src/server/responses/response-effects.ts +1 -0
  242. package/src/server/responses/run-turn-execution.ts +15 -5
  243. package/src/server/responses/sidecar-execution.ts +30 -16
  244. package/src/server/responses/sidecar-send-budget.ts +58 -0
  245. package/src/server/responses/spend-storage-error.ts +33 -0
  246. package/src/server/responses/ws-upstream.ts +19 -6
  247. package/src/server/startup-health-cache.ts +2 -1
  248. package/src/server/workflow-refusal.ts +3 -0
  249. package/src/service/cli.ts +20 -4
  250. package/src/service/desktop-command-guard.ts +32 -0
  251. package/src/service/desktop-startup.ts +66 -40
  252. package/src/service/desktop-supervision.d.mts +26 -0
  253. package/src/service/desktop-supervision.mjs +140 -0
  254. package/src/service/managing-cli.ts +1 -0
  255. package/src/service/orchestration.ts +46 -20
  256. package/src/service/repair.ts +25 -9
  257. package/src/service/state.ts +4 -3
  258. package/src/service/systemd.ts +17 -8
  259. package/src/service/windows-ops.ts +61 -22
  260. package/src/storage/policy-job.ts +5 -1
  261. package/src/storage/policy-worker.ts +3 -1
  262. package/src/storage/policy.ts +9 -0
  263. package/src/types/config.ts +21 -0
  264. package/src/types/request.ts +10 -0
  265. package/src/types/wire.ts +1 -0
  266. package/src/update/index.ts +57 -9
  267. package/src/update/restart-ownership.ts +10 -3
  268. package/src/update/runtime-ownership.d.mts +6 -0
  269. package/src/update/runtime-ownership.mjs +23 -7
  270. package/src/usage/expected-prices.ts +1 -1
  271. package/src/web-search/loop.ts +5 -2
  272. package/gui/dist/assets/App-7yhoys8a.js +0 -54
@@ -1,3 +1,6 @@
1
+ import { fetchCopilotAutoModels, copilotPickerModels, copilotRequiresAuto } from "../../providers/github-copilot-auto";
2
+ import { resolveGithubCopilotTransport } from "../../providers/github-copilot-transport";
3
+ import { resolveCopilotApiBaseUrl } from "../../oauth/github-copilot";
1
4
  import { isAnthropicOAuthInstance } from "../../providers/anthropic-instance";
2
5
  import { effectiveProviderAlias, effectiveProviderAliasDecision } from "../../providers/default-aliases";
3
6
  import { initialModelSelectionPending } from "../../providers/initial-model-selection";
@@ -41,6 +44,7 @@ import { CODEX_REASONING_LEVELS, codexEffortRank, configuredReasoningEfforts, mo
41
44
  import { isModelVisionSidecarConsumer } from "../../vision/eligibility";
42
45
  import { getModelMetadata, getModelMetadataCaseInsensitive, listModelMetadata, resolveMetadataProvider, type ModelMetadata } from "../../generated/model-metadata";
43
46
  import { enrichProviderFromRegistry, shouldCaseFoldMetadataModelId } from "../../providers/derive";
47
+ import { isAzureModelMetadataDestination, refreshAzureModelMetadata } from "../../providers/azure-model-metadata";
44
48
  import {
45
49
  captureFastPolicyAuthority,
46
50
  fastPolicyForModel,
@@ -163,13 +167,15 @@ export function observedModelsAuthResolver(
163
167
  },
164
168
  };
165
169
  }
170
+ /** Project captured discovery through config hints and retention; Auto-only Copilot exposes only Auto. */
166
171
  export async function fetchProviderModelsWithAuth(
167
172
  captured: CapturedProviderGather,
168
173
  ttlMs: number,
169
174
  contextCap: number | undefined,
170
175
  resolveAuth: ModelsAuthResolver,
171
176
  ): Promise<ProviderModelsResult> {
172
- const { name, provider: prov, discovery, metadataModelIdCaseFold } = captured;
177
+ const { name, provider: prov, discovery, metadataModelIdCaseFold, metadataConfigDir } = captured;
178
+ const azureMetadata = isAzureModelMetadataDestination(prov.baseUrl);
173
179
  const observed = (
174
180
  models: CatalogModel[],
175
181
  state: CatalogGatherProviderModelOutcome["state"],
@@ -185,6 +191,7 @@ export async function fetchProviderModelsWithAuth(
185
191
  const anthropicSelection = isAnthropicOAuthInstance(name) && prov.authMode === "oauth" && mayResolveModelsOAuth(name, prov)
186
192
  ? captureOAuthAccountSelection(name) : null;
187
193
  if (prov.authMode === "forward") return observed([], "authoritative"); // ChatGPT backend has no /models
194
+ if (azureMetadata) await refreshAzureModelMetadata(prov.baseUrl, metadataConfigDir);
188
195
  const seedVertexDefault = prov.adapter === "google"
189
196
  && prov.googleMode === "vertex"
190
197
  && (prov.models?.length ?? 0) === 0
@@ -203,7 +210,7 @@ export async function fetchProviderModelsWithAuth(
203
210
  const configured: CatalogModel[] = configuredIds.map(id => ({
204
211
  id,
205
212
  provider: name,
206
- ...catalogHintsFromProviderConfig(name, prov, id, contextCap, metadataModelIdCaseFold, captured.effectiveAlias),
213
+ ...catalogHintsFromProviderConfig(name, prov, id, contextCap, metadataModelIdCaseFold, captured.effectiveAlias, metadataConfigDir),
207
214
  }));
208
215
  const withConfiguredRetention = (
209
216
  models: CatalogModel[],
@@ -219,6 +226,7 @@ export async function fetchProviderModelsWithAuth(
219
226
  seedVertexDefault,
220
227
  retainComboTargets: options?.retainComboTargets,
221
228
  metadataModelIdCaseFold,
229
+ metadataConfigDir,
222
230
  });
223
231
  if (
224
232
  options?.warnDrops === true
@@ -251,7 +259,7 @@ export async function fetchProviderModelsWithAuth(
251
259
  }
252
260
  return observed(ids.map(id => {
253
261
  const hints = catalogHintsFromProviderConfig(name, prov, id, contextCap,
254
- metadataModelIdCaseFold, captured.effectiveAlias);
262
+ metadataModelIdCaseFold, captured.effectiveAlias, metadataConfigDir);
255
263
  const observedWindow = kiroObservedContextWindow(id);
256
264
  return { id, provider: name, ...hints,
257
265
  ...(observedWindow !== undefined
@@ -278,6 +286,27 @@ export async function fetchProviderModelsWithAuth(
278
286
  : { apiKey: await resolveModelsAuthToken(name, prov), observed: false }
279
287
  : resolveAuth.resolve(name, prov));
280
288
  const apiKey = auth.apiKey;
289
+ if (name === "github-copilot" && apiKey) {
290
+ const transport = resolveGithubCopilotTransport({ ...prov, apiKey }, prov.authMode === "oauth"
291
+ ? resolveCopilotApiBaseUrl(auth.oauthApiBaseUrl) : undefined);
292
+ try {
293
+ const models = await fetchCopilotAutoModels(transport, AbortSignal.timeout(8_000), undefined, ttlMs);
294
+ const picker = copilotPickerModels(prov, models).map(/** Apply configured hints to the permission-filtered account catalog without changing upstream metadata. */
295
+ model => applyProviderConfigHints(name, prov, {
296
+ id: model.id, provider: name,
297
+ ...catalogHintsFromModelsApiItem(name, model as ProviderModelsApiItem),
298
+ }, contextCap, metadataModelIdCaseFold, captured.effectiveAlias));
299
+ if (!isCurrentCacheGeneration()) return observed(configured, "degraded");
300
+ markProviderDiscoveryOk(name, picker.length);
301
+ // Retain callable manual/combo selectors, but do not advertise named selection
302
+ // for an Auto-only account even when its saved configuration still contains it.
303
+ return observed(copilotRequiresAuto(prov, models)
304
+ ? picker : withConfiguredRetention(picker, { warnDrops: true }), "authoritative");
305
+ } catch {
306
+ if (isCurrentCacheGeneration()) markProviderDiscoveryFailed(name, { reason: "provider" });
307
+ return observed(configured, "degraded");
308
+ }
309
+ }
281
310
  const maySendAnthropicDiscovery = () => {
282
311
  if (!isAnthropicOAuthInstance(name) || prov.authMode !== "oauth") return true;
283
312
  if (!mayResolveModelsOAuth(name, prov)) return false;
@@ -296,7 +325,7 @@ export async function fetchProviderModelsWithAuth(
296
325
  : [{
297
326
  id: prov.defaultModel,
298
327
  provider: name,
299
- ...catalogHintsFromProviderConfig(name, prov, prov.defaultModel, contextCap, metadataModelIdCaseFold, captured.effectiveAlias),
328
+ ...catalogHintsFromProviderConfig(name, prov, prov.defaultModel, contextCap, metadataModelIdCaseFold, captured.effectiveAlias, metadataConfigDir),
300
329
  }];
301
330
  const vertexDefaultSeed = seedVertexDefault ? configured[0] : undefined;
302
331
  const withVertexDefaultSeed = (models: CatalogModel[]): CatalogModel[] => (
@@ -318,13 +347,13 @@ export async function fetchProviderModelsWithAuth(
318
347
  const fresh = getFreshCached(name, ttlMs, Date.now(), authorityIdentity);
319
348
  if (fresh) {
320
349
  return observed(withConfiguredRetention(
321
- applyConfigHintsToCachedModels(name, prov, fresh, contextCap, metadataModelIdCaseFold, captured.effectiveAlias),
350
+ applyConfigHintsToCachedModels(name, prov, fresh, contextCap, metadataModelIdCaseFold, captured.effectiveAlias, metadataConfigDir),
322
351
  ), "authoritative");
323
352
  }
324
353
  const scopedStale = getStaleCached(name, authorityIdentity);
325
354
  if (isModelsFetchCoolingDown(name, undefined, undefined, authorityIdentity) && scopedStale) {
326
355
  return observed(withConfiguredRetention(
327
- applyConfigHintsToCachedModels(name, prov, scopedStale, contextCap, metadataModelIdCaseFold, captured.effectiveAlias),
356
+ applyConfigHintsToCachedModels(name, prov, scopedStale, contextCap, metadataModelIdCaseFold, captured.effectiveAlias, metadataConfigDir),
328
357
  ), "degraded");
329
358
  }
330
359
  const live = await fetchCodeBuddyModels(profile, apiKey);
@@ -332,7 +361,7 @@ export async function fetchProviderModelsWithAuth(
332
361
  const discovered = live.models.map(id => ({
333
362
  id,
334
363
  provider: name,
335
- ...catalogHintsFromProviderConfig(name, prov, id, contextCap, metadataModelIdCaseFold, captured.effectiveAlias),
364
+ ...catalogHintsFromProviderConfig(name, prov, id, contextCap, metadataModelIdCaseFold, captured.effectiveAlias, metadataConfigDir),
336
365
  }));
337
366
  const forCache = withConfiguredRetention(discovered, { retainComboTargets: false });
338
367
  if (!setCached(name, forCache, Date.now(), cacheGeneration, authorityIdentity)) {
@@ -348,7 +377,7 @@ export async function fetchProviderModelsWithAuth(
348
377
  }
349
378
  const stale = getStaleCached(name, authorityIdentity);
350
379
  return observed(withConfiguredRetention(
351
- stale ? applyConfigHintsToCachedModels(name, prov, stale, contextCap, metadataModelIdCaseFold, captured.effectiveAlias) : configured,
380
+ stale ? applyConfigHintsToCachedModels(name, prov, stale, contextCap, metadataModelIdCaseFold, captured.effectiveAlias, metadataConfigDir) : configured,
352
381
  ), "degraded");
353
382
  }
354
383
  if (name === "zed" && prov.adapter === "zed") {
@@ -361,14 +390,14 @@ export async function fetchProviderModelsWithAuth(
361
390
  const cached = getFreshCached(name, ttlMs, Date.now(), authorityIdentity);
362
391
  if (cached) {
363
392
  return observed(
364
- withConfiguredRetention(applyConfigHintsToCachedModels(name, prov, cached, contextCap, metadataModelIdCaseFold, captured.effectiveAlias)),
393
+ withConfiguredRetention(applyConfigHintsToCachedModels(name, prov, cached, contextCap, metadataModelIdCaseFold, captured.effectiveAlias, metadataConfigDir)),
365
394
  "authoritative",
366
395
  );
367
396
  }
368
397
  const stale = getStaleCached(name, authorityIdentity);
369
398
  if (isModelsFetchCoolingDown(name, undefined, undefined, authorityIdentity) && stale) {
370
399
  return observed(
371
- withConfiguredRetention(applyConfigHintsToCachedModels(name, prov, stale, contextCap, metadataModelIdCaseFold, captured.effectiveAlias)),
400
+ withConfiguredRetention(applyConfigHintsToCachedModels(name, prov, stale, contextCap, metadataModelIdCaseFold, captured.effectiveAlias, metadataConfigDir)),
372
401
  "degraded",
373
402
  );
374
403
  }
@@ -388,7 +417,7 @@ export async function fetchProviderModelsWithAuth(
388
417
  ...(model.maxOutputTokens ? { maxOutputTokens: model.maxOutputTokens } : {}),
389
418
  ...(model.supportsImages ? { inputModalities: ["text", "image"] } : {}),
390
419
  ...(reasoningEfforts?.length ? { reasoningEfforts } : {}),
391
- ...catalogHintsFromProviderConfig(name, prov, model.id, contextCap, metadataModelIdCaseFold, captured.effectiveAlias),
420
+ ...catalogHintsFromProviderConfig(name, prov, model.id, contextCap, metadataModelIdCaseFold, captured.effectiveAlias, metadataConfigDir),
392
421
  } as CatalogModel;
393
422
  });
394
423
  const forCache = withConfiguredRetention(discovered, { retainComboTargets: false });
@@ -403,7 +432,7 @@ export async function fetchProviderModelsWithAuth(
403
432
  markProviderDiscoveryFailed(name, { reason: "provider" });
404
433
  }
405
434
  return observed(
406
- withConfiguredRetention(stale ? applyConfigHintsToCachedModels(name, prov, stale, contextCap, metadataModelIdCaseFold, captured.effectiveAlias) : configured),
435
+ withConfiguredRetention(stale ? applyConfigHintsToCachedModels(name, prov, stale, contextCap, metadataModelIdCaseFold, captured.effectiveAlias, metadataConfigDir) : configured),
407
436
  "degraded",
408
437
  );
409
438
  }
@@ -419,13 +448,13 @@ export async function fetchProviderModelsWithAuth(
419
448
  const fresh = getFreshCached(name, ttlMs, Date.now(), authorityIdentity);
420
449
  if (fresh) {
421
450
  return observed(withConfiguredRetention(
422
- applyConfigHintsToCachedModels(name, prov, fresh, contextCap, metadataModelIdCaseFold, captured.effectiveAlias),
451
+ applyConfigHintsToCachedModels(name, prov, fresh, contextCap, metadataModelIdCaseFold, captured.effectiveAlias, metadataConfigDir),
423
452
  ), "authoritative");
424
453
  }
425
454
  const scopedStale = getStaleCached(name, authorityIdentity);
426
455
  if (isModelsFetchCoolingDown(name) && scopedStale) {
427
456
  return observed(withConfiguredRetention(
428
- applyConfigHintsToCachedModels(name, prov, scopedStale, contextCap, metadataModelIdCaseFold, captured.effectiveAlias),
457
+ applyConfigHintsToCachedModels(name, prov, scopedStale, contextCap, metadataModelIdCaseFold, captured.effectiveAlias, metadataConfigDir),
429
458
  ), "degraded");
430
459
  }
431
460
  const live = await fetchQoderModels(profile, apiKey);
@@ -433,7 +462,7 @@ export async function fetchProviderModelsWithAuth(
433
462
  const discovered = live.models.map(id => ({
434
463
  id,
435
464
  provider: name,
436
- ...catalogHintsFromProviderConfig(name, prov, id, contextCap, metadataModelIdCaseFold, captured.effectiveAlias),
465
+ ...catalogHintsFromProviderConfig(name, prov, id, contextCap, metadataModelIdCaseFold, captured.effectiveAlias, metadataConfigDir),
437
466
  }));
438
467
  const forCache = withConfiguredRetention(discovered, { retainComboTargets: false });
439
468
  if (!setCached(name, forCache, Date.now(), cacheGeneration, authorityIdentity)) {
@@ -449,7 +478,7 @@ export async function fetchProviderModelsWithAuth(
449
478
  }
450
479
  const stale = getStaleCached(name, authorityIdentity);
451
480
  return observed(withConfiguredRetention(
452
- stale ? applyConfigHintsToCachedModels(name, prov, stale, contextCap, metadataModelIdCaseFold, captured.effectiveAlias) : configured,
481
+ stale ? applyConfigHintsToCachedModels(name, prov, stale, contextCap, metadataModelIdCaseFold, captured.effectiveAlias, metadataConfigDir) : configured,
453
482
  ), "degraded");
454
483
  }
455
484
  if (prov.adapter === "devin") {
@@ -464,7 +493,7 @@ export async function fetchProviderModelsWithAuth(
464
493
  const cachedDevin = getFreshCached(name, ttlMs, Date.now(), authorityIdentity);
465
494
  if (cachedDevin) {
466
495
  return observed(
467
- withConfiguredRetention(applyConfigHintsToCachedModels(name, prov, cachedDevin)),
496
+ withConfiguredRetention(applyConfigHintsToCachedModels(name, prov, cachedDevin, undefined, undefined, undefined, metadataConfigDir)),
468
497
  "authoritative",
469
498
  );
470
499
  }
@@ -472,7 +501,7 @@ export async function fetchProviderModelsWithAuth(
472
501
  const cooling = getStaleCached(name, authorityIdentity);
473
502
  return observed(
474
503
  withConfiguredRetention(
475
- cooling ? applyConfigHintsToCachedModels(name, prov, cooling) : configured,
504
+ cooling ? applyConfigHintsToCachedModels(name, prov, cooling, undefined, undefined, undefined, metadataConfigDir) : configured,
476
505
  ),
477
506
  "degraded",
478
507
  );
@@ -514,7 +543,7 @@ export async function fetchProviderModelsWithAuth(
514
543
  // record and the vision-sidecar rewrite keep winning — the live
515
544
  // value survives only when none of them applies.
516
545
  ...(liveResult.inputModalities[id]?.length ? { inputModalities: liveResult.inputModalities[id] } : {}),
517
- ...catalogHintsFromProviderConfig(name, prov, id, contextCap, metadataModelIdCaseFold, captured.effectiveAlias),
546
+ ...catalogHintsFromProviderConfig(name, prov, id, contextCap, metadataModelIdCaseFold, captured.effectiveAlias, metadataConfigDir),
518
547
  } as CatalogModel;
519
548
  });
520
549
  const forCache = withConfiguredRetention(result, { retainComboTargets: false });
@@ -530,7 +559,7 @@ export async function fetchProviderModelsWithAuth(
530
559
  }
531
560
  const stale = getStaleCached(name, authorityIdentity);
532
561
  return observed(
533
- withConfiguredRetention(stale ? applyConfigHintsToCachedModels(name, prov, stale) : configured),
562
+ withConfiguredRetention(stale ? applyConfigHintsToCachedModels(name, prov, stale, undefined, undefined, undefined, metadataConfigDir) : configured),
534
563
  "degraded",
535
564
  );
536
565
  }
@@ -547,7 +576,7 @@ export async function fetchProviderModelsWithAuth(
547
576
  const cachedCursor = getFreshCached(name, ttlMs, Date.now(), authorityIdentity);
548
577
  if (cachedCursor) {
549
578
  return observed(
550
- withConfiguredRetention(applyConfigHintsToCachedModels(name, prov, cachedCursor, undefined, metadataModelIdCaseFold, captured.effectiveAlias)),
579
+ withConfiguredRetention(applyConfigHintsToCachedModels(name, prov, cachedCursor, undefined, metadataModelIdCaseFold, captured.effectiveAlias, metadataConfigDir)),
551
580
  "authoritative",
552
581
  );
553
582
  }
@@ -555,7 +584,7 @@ export async function fetchProviderModelsWithAuth(
555
584
  const cooling = getStaleCached(name, authorityIdentity);
556
585
  return observed(
557
586
  withConfiguredRetention(
558
- cooling ? applyConfigHintsToCachedModels(name, prov, cooling, undefined, metadataModelIdCaseFold, captured.effectiveAlias) : configured,
587
+ cooling ? applyConfigHintsToCachedModels(name, prov, cooling, undefined, metadataModelIdCaseFold, captured.effectiveAlias, metadataConfigDir) : configured,
559
588
  ),
560
589
  "degraded",
561
590
  );
@@ -601,7 +630,7 @@ export async function fetchProviderModelsWithAuth(
601
630
  const staleCursor = getStaleCached(name, authorityIdentity);
602
631
  return observed(
603
632
  withConfiguredRetention(
604
- staleCursor ? applyConfigHintsToCachedModels(name, prov, staleCursor, undefined, metadataModelIdCaseFold, captured.effectiveAlias) : configured,
633
+ staleCursor ? applyConfigHintsToCachedModels(name, prov, staleCursor, undefined, metadataModelIdCaseFold, captured.effectiveAlias, metadataConfigDir) : configured,
605
634
  ),
606
635
  "degraded",
607
636
  );
@@ -619,7 +648,7 @@ export async function fetchProviderModelsWithAuth(
619
648
  if (fresh) {
620
649
  return observed(
621
650
  withConfiguredRetention(
622
- withVertexDefaultSeed(applyConfigHintsToCachedModels(name, prov, fresh, contextCap, metadataModelIdCaseFold, captured.effectiveAlias)),
651
+ withVertexDefaultSeed(applyConfigHintsToCachedModels(name, prov, fresh, contextCap, metadataModelIdCaseFold, captured.effectiveAlias, metadataConfigDir)),
623
652
  ),
624
653
  "authoritative",
625
654
  ); // dedups Codex's frequent /v1/models polling within the TTL
@@ -631,7 +660,7 @@ export async function fetchProviderModelsWithAuth(
631
660
  return observed(
632
661
  withConfiguredRetention(
633
662
  stale
634
- ? withVertexDefaultSeed(applyConfigHintsToCachedModels(name, prov, stale, contextCap, metadataModelIdCaseFold, captured.effectiveAlias))
663
+ ? withVertexDefaultSeed(applyConfigHintsToCachedModels(name, prov, stale, contextCap, metadataModelIdCaseFold, captured.effectiveAlias, metadataConfigDir))
635
664
  : failedDiscoveryConfigured,
636
665
  ),
637
666
  "degraded",
@@ -676,7 +705,7 @@ export async function fetchProviderModelsWithAuth(
676
705
  return {
677
706
  models: withConfiguredRetention(
678
707
  stale
679
- ? withVertexDefaultSeed(applyConfigHintsToCachedModels(name, prov, stale, contextCap, metadataModelIdCaseFold, captured.effectiveAlias))
708
+ ? withVertexDefaultSeed(applyConfigHintsToCachedModels(name, prov, stale, contextCap, metadataModelIdCaseFold, captured.effectiveAlias, metadataConfigDir))
680
709
  : failedDiscoveryConfigured,
681
710
  ),
682
711
  fallback: stale ? "stale" : "configured",
@@ -754,7 +783,7 @@ export async function fetchProviderModelsWithAuth(
754
783
  return observed(models, "degraded");
755
784
  }
756
785
  if (antigravity) {
757
- const live = antigravity.map(model => applyProviderConfigHints(name, prov, {
786
+ const upstream: CatalogModel[] = antigravity.map(model => ({
758
787
  id: model.id,
759
788
  provider: name,
760
789
  // Only an exact discovered wire map proves a new model's selectable effort ladder.
@@ -766,8 +795,9 @@ export async function fetchProviderModelsWithAuth(
766
795
  } : {}),
767
796
  ...(model.contextWindow ? { contextWindow: model.contextWindow } : {}),
768
797
  ...(model.inputModalities ? { inputModalities: model.inputModalities } : {}),
769
- }, contextCap, metadataModelIdCaseFold, captured.effectiveAlias));
770
- const forCache = withConfiguredRetention(live, { retainComboTargets: false });
798
+ }));
799
+ const live = applyConfigHintsToCachedModels(name, prov, upstream, contextCap, metadataModelIdCaseFold, captured.effectiveAlias, metadataConfigDir);
800
+ const forCache = azureMetadata ? upstream : withConfiguredRetention(live, { retainComboTargets: false });
771
801
  if (!isCurrentCacheGeneration()) {
772
802
  return observed(withConfiguredRetention(configured), "degraded");
773
803
  }
@@ -781,7 +811,7 @@ export async function fetchProviderModelsWithAuth(
781
811
  return observed(withConfiguredRetention(configured), "degraded");
782
812
  }
783
813
  markProviderDiscoveryOk(name, live.length);
784
- return observed(withConfiguredRetention(forCache, { warnDrops: true }), "authoritative");
814
+ return observed(withConfiguredRetention(azureMetadata ? live : forCache, { warnDrops: true }), "authoritative");
785
815
  }
786
816
  const googleAiStudio = effectiveGoogleMode(name, prov) === "ai-studio"
787
817
  ? extractGoogleAiStudioModelItems(bounded.value, discovery.maxModels)
@@ -820,7 +850,7 @@ export async function fetchProviderModelsWithAuth(
820
850
  provider: prov,
821
851
  }).catch(() => undefined)
822
852
  : undefined;
823
- const live = items.map(m => {
853
+ const upstream: CatalogModel[] = items.map(m => {
824
854
  const ownedBy = boundedOwnedBy(m.owned_by);
825
855
  // Precedence: the authoritative /v1/models row wins; /api/show fills only metadata the
826
856
  // models-API row does not carry. applyProviderConfigHints then applies explicit
@@ -836,22 +866,25 @@ export async function fetchProviderModelsWithAuth(
836
866
  ? { inputModalities: ["text", "image"] as string[] }
837
867
  : {}),
838
868
  };
839
- return applyProviderConfigHints(name, prov, {
869
+ return {
840
870
  id: m.id,
841
871
  provider: name,
842
872
  ...(ownedBy ? { owned_by: ownedBy } : {}),
843
873
  ...discoveredHints,
844
- }, contextCap, metadataModelIdCaseFold, captured.effectiveAlias);
874
+ };
845
875
  })
846
876
  .filter(m => shouldExposeProviderModel(name, m.id));
877
+ const live = applyConfigHintsToCachedModels(name, prov, upstream, contextCap, metadataModelIdCaseFold, captured.effectiveAlias, metadataConfigDir);
847
878
  // Capture the count BEFORE the alias/configured augmentation below pushes extra rows into
848
879
  // `live`; otherwise configured entries would be reported as discovered ones.
849
880
  const liveModelCount = live.length;
850
881
  // Dated-release aliases + configured retention (compat allow-list, combo targets,
851
882
  // Vertex default). Cache without combo retention so a later gather re-applies the
852
883
  // 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 });
884
+ // Azure hints depend on the admitted config root. Keep only upstream evidence in the
885
+ // shared TTL/stale cache; every read reapplies metadata and retention for its own root.
886
+ const forCache = azureMetadata ? upstream : withConfiguredRetention(live, { retainComboTargets: false });
887
+ const returned = withConfiguredRetention(azureMetadata ? live : forCache, { warnDrops: true });
855
888
  const droppedConfiguredIds = configured
856
889
  .map(model => model.id)
857
890
  .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>;