@bitkyc08/opencodex 2.41.0 → 2.43.0-preview.20260906

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (260) hide show
  1. package/AGENTS_INSTALL.md +2 -2
  2. package/README.md +31 -0
  3. package/bin/ocx.mjs +10 -1
  4. package/gui/dist/assets/index-DS1NE4Jn.css +1 -0
  5. package/gui/dist/assets/index-VGeQEZ_v.js +112 -0
  6. package/gui/dist/index.html +2 -2
  7. package/package.json +1 -1
  8. package/src/adapters/anthropic-image-codec.ts +304 -0
  9. package/src/adapters/anthropic-image-normalize.ts +8 -298
  10. package/src/adapters/anthropic.ts +30 -7
  11. package/src/adapters/command-code.ts +7 -5
  12. package/src/adapters/cursor/desktop-executor-contract.ts +15 -0
  13. package/src/adapters/cursor/images.ts +36 -6
  14. package/src/adapters/cursor/live-transport.ts +7 -2
  15. package/src/adapters/cursor/native-exec-desktop.ts +2 -15
  16. package/src/adapters/cursor/protobuf-request.ts +54 -24
  17. package/src/adapters/cursor/tool-definitions.ts +5 -670
  18. package/src/adapters/cursor/tool-guidance.ts +236 -0
  19. package/src/adapters/cursor/tool-naming.ts +252 -0
  20. package/src/adapters/cursor/tool-schemas.ts +195 -0
  21. package/src/adapters/cursor/types.ts +6 -3
  22. package/src/adapters/exec-tool-result-normalize.ts +1 -1
  23. package/src/adapters/google-errors.ts +9 -1
  24. package/src/adapters/google.ts +1 -0
  25. package/src/adapters/identity.ts +8 -2
  26. package/src/adapters/kiro-calibration.ts +181 -0
  27. package/src/adapters/kiro.ts +135 -3
  28. package/src/adapters/openai-responses.ts +259 -29
  29. package/src/adapters/responses-code-mode.ts +59 -0
  30. package/src/adapters/tool-catalog-nudge.ts +1 -1
  31. package/src/adapters/xai-schema-analysis.ts +86 -0
  32. package/src/adapters/xai-tool-schema.ts +2 -87
  33. package/src/adapters/xai-web-search.ts +1 -1
  34. package/src/bridge.ts +47 -13
  35. package/src/chat/inbound.ts +11 -3
  36. package/src/claude/inbound-content-options.ts +60 -0
  37. package/src/claude/inbound-model-options.ts +142 -0
  38. package/src/claude/inbound-records.ts +7 -0
  39. package/src/claude/inbound.ts +10 -202
  40. package/src/claude/model-info.ts +45 -0
  41. package/src/cli/account-auth.ts +49 -9
  42. package/src/cli/account-extended.ts +7 -1
  43. package/src/cli/capabilities.ts +15 -4
  44. package/src/cli/claude.ts +232 -39
  45. package/src/cli/config-command.ts +9 -1
  46. package/src/cli/dispatch.ts +5 -1
  47. package/src/cli/doctor.ts +10 -0
  48. package/src/cli/effort.ts +372 -0
  49. package/src/cli/export-command.ts +3 -9
  50. package/src/cli/help.ts +1 -0
  51. package/src/cli/index.ts +13 -0
  52. package/src/cli/init.ts +4 -0
  53. package/src/cli/model-selection-guidance.ts +30 -0
  54. package/src/cli/models-runtime.ts +3 -2
  55. package/src/cli/models.ts +8 -3
  56. package/src/cli/observe.ts +3 -1
  57. package/src/cli/opencode.ts +4 -1
  58. package/src/cli/provider-runtime.ts +65 -0
  59. package/src/cli/provider.ts +8 -0
  60. package/src/cli/registry.ts +16 -2
  61. package/src/cli/runtime-api.ts +3 -1
  62. package/src/cli/star-prompt.ts +22 -6
  63. package/src/cli/status-probes.ts +168 -0
  64. package/src/cli/status.ts +5 -168
  65. package/src/clients/config-export/constants.ts +69 -0
  66. package/src/clients/config-export/contracts.ts +154 -0
  67. package/src/clients/config-export/dsh.ts +132 -0
  68. package/src/clients/config-export/fast-models.ts +29 -0
  69. package/src/clients/config-export/mcode.ts +83 -0
  70. package/src/clients/config-export/model-metadata.ts +108 -0
  71. package/src/clients/config-export/omp.ts +104 -0
  72. package/src/clients/config-export/zcode.ts +92 -0
  73. package/src/clients/config-export.ts +18 -710
  74. package/src/codex/account-lifecycle.ts +20 -3
  75. package/src/codex/account-usability.ts +2 -0
  76. package/src/codex/auth-api.ts +272 -32
  77. package/src/codex/auth-context.ts +328 -24
  78. package/src/codex/catalog/effort.ts +44 -5
  79. package/src/codex/catalog/metadata.ts +149 -14
  80. package/src/codex/catalog/native-models.ts +116 -4
  81. package/src/codex/catalog/parsing.ts +122 -8
  82. package/src/codex/catalog/provider-fetch.ts +154 -23
  83. package/src/codex/catalog/reserve.ts +52 -0
  84. package/src/codex/catalog/sync.ts +89 -16
  85. package/src/codex/catalog.ts +1 -1
  86. package/src/codex/convergence-types.ts +1 -0
  87. package/src/codex/convergence.ts +2 -0
  88. package/src/codex/data/upstream-models.json +169 -0
  89. package/src/codex/forward-transport-headers.ts +25 -0
  90. package/src/codex/inject.ts +99 -34
  91. package/src/codex/injected-marker.ts +30 -4
  92. package/src/codex/journal.ts +14 -0
  93. package/src/codex/legacy-config-keys.ts +68 -0
  94. package/src/codex/log-guard/inspect-schema.ts +137 -0
  95. package/src/codex/log-guard/inspect.ts +2 -134
  96. package/src/codex/loopback-target.ts +54 -0
  97. package/src/codex/main-account-cache.ts +63 -1
  98. package/src/codex/main-account-hard-lock.ts +52 -0
  99. package/src/codex/main-account.ts +3 -1
  100. package/src/codex/management-convergence.ts +3 -0
  101. package/src/codex/model-entitlements.ts +54 -4
  102. package/src/codex/project-config-warnings.ts +92 -2
  103. package/src/codex/prompt-layers/encoding.ts +80 -0
  104. package/src/codex/prompt-layers/paths.ts +54 -0
  105. package/src/codex/prompt-layers/revision.ts +55 -0
  106. package/src/codex/prompt-layers/toml-edit.ts +163 -0
  107. package/src/codex/prompt-layers/toml-read.ts +181 -0
  108. package/src/codex/prompt-layers.ts +14 -520
  109. package/src/codex/quota-auto-refresh-state.ts +16 -0
  110. package/src/codex/quota-auto-refresh.ts +219 -0
  111. package/src/codex/quota-types.ts +51 -0
  112. package/src/codex/quota.ts +252 -93
  113. package/src/codex/reserve-availability.ts +177 -0
  114. package/src/codex/routing.ts +28 -9
  115. package/src/codex/shim.ts +53 -11
  116. package/src/codex/subagent-model-fallback.ts +23 -3
  117. package/src/combos/failover.ts +125 -7
  118. package/src/combos/identifiers.ts +89 -0
  119. package/src/combos/index.ts +4 -0
  120. package/src/combos/resolve.ts +80 -9
  121. package/src/combos/types.ts +20 -93
  122. package/src/config/subagent-models.ts +24 -0
  123. package/src/config.ts +156 -13
  124. package/src/generated/compatibility-version.json +474 -178
  125. package/src/generated/model-metadata.ts +1 -1
  126. package/src/integrations/journal.ts +65 -4
  127. package/src/integrations/store.ts +5 -0
  128. package/src/lab/events/limits.ts +4 -0
  129. package/src/lib/destination-policy.ts +31 -2
  130. package/src/lib/errors.ts +39 -0
  131. package/src/lib/provider-outbound.ts +69 -3
  132. package/src/lib/proxy-env.ts +22 -0
  133. package/src/lib/redact-folding.ts +176 -0
  134. package/src/lib/redact.ts +2 -175
  135. package/src/lib/state-store-sweeper.ts +20 -6
  136. package/src/lib/token-estimate.ts +94 -27
  137. package/src/lib/windows-user-principal.ts +53 -5
  138. package/src/oauth/account-quota-rank.ts +40 -1
  139. package/src/oauth/anthropic-routing.ts +99 -3
  140. package/src/oauth/chatgpt-device.ts +187 -0
  141. package/src/oauth/chatgpt.ts +31 -4
  142. package/src/oauth/generic-account-failover.ts +36 -13
  143. package/src/oauth/index.ts +140 -29
  144. package/src/oauth/log.ts +3 -0
  145. package/src/oauth/login-cli.ts +5 -0
  146. package/src/oauth/meta-muse.ts +117 -15
  147. package/src/oauth/pool-settings-capability.ts +15 -4
  148. package/src/providers/api-keys.ts +8 -10
  149. package/src/providers/default-aliases.ts +39 -0
  150. package/src/providers/derive.ts +10 -2
  151. package/src/providers/fastwire.ts +36 -7
  152. package/src/providers/initial-model-selection-runtime.ts +90 -0
  153. package/src/providers/initial-model-selection.ts +120 -0
  154. package/src/providers/key-failover.ts +134 -54
  155. package/src/providers/key-store.ts +11 -1
  156. package/src/providers/label.ts +1 -1
  157. package/src/providers/model-discovery.ts +76 -0
  158. package/src/providers/model-rename-startup.ts +72 -8
  159. package/src/providers/muse-subscription-usage.ts +95 -0
  160. package/src/providers/openai-sidecar.ts +17 -5
  161. package/src/providers/openai-tiers-destination.ts +102 -0
  162. package/src/providers/openai-tiers.ts +2 -99
  163. package/src/providers/opencode-go-transport.ts +41 -0
  164. package/src/providers/quota-key-accounts.ts +141 -0
  165. package/src/providers/quota-types.ts +9 -0
  166. package/src/providers/quota.ts +625 -98
  167. package/src/providers/registry.ts +60 -17
  168. package/src/providers/xai-responses-opt-in.ts +31 -5
  169. package/src/quota/reset-activation.ts +81 -0
  170. package/src/quota/reset-detector.ts +305 -0
  171. package/src/quota/reset-notify-config.ts +162 -0
  172. package/src/quota/reset-observer.ts +125 -0
  173. package/src/quota/reset-poller.ts +160 -0
  174. package/src/quota/reset-seen-store.ts +385 -0
  175. package/src/quota/reset-sinks.ts +199 -0
  176. package/src/quota/window-mapping.ts +106 -0
  177. package/src/responses/apply-patch-envelope.ts +46 -0
  178. package/src/responses/code-mode-helper-compat.ts +39 -1
  179. package/src/responses/custom-tool-compat.ts +10 -4
  180. package/src/responses/hosted-tool-policy.ts +12 -4
  181. package/src/responses/parser-content.ts +133 -0
  182. package/src/responses/parser-text-format.ts +24 -0
  183. package/src/responses/parser-tools.ts +188 -0
  184. package/src/responses/parser.ts +3 -326
  185. package/src/responses/state.ts +124 -28
  186. package/src/router.ts +48 -13
  187. package/src/routing/analytics.ts +1 -0
  188. package/src/routing/capability.ts +17 -4
  189. package/src/server/auth-cors.ts +7 -1
  190. package/src/server/background-lifecycle.ts +23 -1
  191. package/src/server/chat-completions.ts +25 -3
  192. package/src/server/claude-messages.ts +62 -5
  193. package/src/server/effort-row.ts +1 -1
  194. package/src/server/fast-row.ts +295 -0
  195. package/src/server/gui-static.ts +30 -4
  196. package/src/server/index.ts +122 -28
  197. package/src/server/live.ts +18 -4
  198. package/src/server/management/agent-settings-routes.ts +2 -2
  199. package/src/server/management/combo-routes.ts +37 -9
  200. package/src/server/management/config-routes.ts +93 -2
  201. package/src/server/management/integration-routes.ts +108 -0
  202. package/src/server/management/model-routes.ts +13 -3
  203. package/src/server/management/model-rows.ts +20 -1
  204. package/src/server/management/native-integration-routes.ts +4 -1
  205. package/src/server/management/oauth-account-routes.ts +45 -10
  206. package/src/server/management/provider-routes.ts +34 -3
  207. package/src/server/management/quota-reset-routes.ts +57 -0
  208. package/src/server/management/route-registry.ts +7 -4
  209. package/src/server/management/shared.ts +19 -5
  210. package/src/server/management/system-routes.ts +3 -2
  211. package/src/server/management-api.ts +14 -2
  212. package/src/server/ports.ts +12 -2
  213. package/src/server/relay-eager.ts +38 -23
  214. package/src/server/relay.ts +4 -0
  215. package/src/server/request-log.ts +6 -0
  216. package/src/server/responses/codex-ws-correlation.ts +65 -0
  217. package/src/server/responses/codex-ws-exchange.ts +261 -0
  218. package/src/server/responses/codex-ws-metadata.ts +134 -0
  219. package/src/server/responses/codex-ws-pool.ts +162 -0
  220. package/src/server/responses/codex-ws-request.ts +87 -0
  221. package/src/server/responses/codex-ws-session.ts +93 -0
  222. package/src/server/responses/codex-ws-wire.ts +144 -0
  223. package/src/server/responses/collaboration.ts +41 -1
  224. package/src/server/responses/compact.ts +105 -12
  225. package/src/server/responses/core.ts +510 -57
  226. package/src/server/responses/empty-completion-guard.ts +4 -0
  227. package/src/server/responses/fetch-helpers.ts +10 -3
  228. package/src/server/responses/input-admission.ts +16 -9
  229. package/src/server/responses/responses-field-backfill.ts +1 -1
  230. package/src/server/responses/ws-upstream.ts +34 -318
  231. package/src/server/responses-custom-tool-repair.ts +20 -4
  232. package/src/server/responses-undeclared-tool-guard.ts +100 -8
  233. package/src/server/safe-response-headers.ts +23 -0
  234. package/src/server/search.ts +9 -0
  235. package/src/server/subagent-models-startup.ts +27 -0
  236. package/src/server/system-env-shell.ts +238 -0
  237. package/src/server/system-env.ts +7 -234
  238. package/src/server/ws-bridge.ts +3 -25
  239. package/src/server/xai-responses-startup.ts +21 -0
  240. package/src/service-manager-probe.ts +1 -1
  241. package/src/service.ts +55 -16
  242. package/src/types/config.ts +108 -12
  243. package/src/types/provider.ts +36 -7
  244. package/src/types/request.ts +8 -0
  245. package/src/types/tools.ts +26 -1
  246. package/src/types.ts +2 -0
  247. package/src/update/notify.ts +8 -2
  248. package/src/usage/cost.ts +38 -28
  249. package/src/usage/expected-prices.ts +34 -15
  250. package/src/usage/log.ts +2 -0
  251. package/src/usage/model-identity.ts +26 -0
  252. package/src/usage/summary.ts +15 -1
  253. package/src/vision/describe.ts +6 -0
  254. package/src/vision/image-rewrite.ts +108 -0
  255. package/src/vision/index.ts +19 -306
  256. package/src/vision/plan.ts +205 -0
  257. package/src/web-search/executor.ts +6 -0
  258. package/src/web-search/index.ts +8 -1
  259. package/gui/dist/assets/index-B2YjLA-i.css +0 -1
  260. package/gui/dist/assets/index-aPup8CKb.js +0 -112
@@ -35,7 +35,8 @@ import upstreamModelsSnapshot from "../data/upstream-models.json";
35
35
  import { generatedModelMetadata, readCatalog, readCodexCatalogPath } from "./parsing";
36
36
  import type { CatalogModel, RawEntry } from "./parsing";
37
37
  import { UPSTREAM_NATIVE_ENTRIES } from "./metadata";
38
- import { nativeOpenAiCapabilitySourceSlug } from "./native-models";
38
+ import { nativeOpenAiCapabilitySourceSlug, SELF_DESCRIBED_NATIVE_OPENAI_MODELS, NATIVE_RESERVE_MODEL } from "./native-models";
39
+ import { isReserveCatalogProjection } from "./reserve";
39
40
  import { loadBundledCodexCatalog } from "./bundled";
40
41
  import type { BundledCatalogDeps, ReadonlyRawCatalog } from "./bundled";
41
42
  import { deriveEntry } from "./sync";
@@ -158,11 +159,14 @@ export function applyCatalogModelMetadata(entry: RawEntry, model?: CatalogModel)
158
159
  entry.supports_reasoning_summaries = model.supportsReasoningSummaries;
159
160
  }
160
161
  if (model.supportsServiceTier === true) {
162
+ const nativeFast = model.codexForwardNativeCapabilityAlias && Array.isArray(entry.service_tiers)
163
+ ? entry.service_tiers.find(tier => tier?.id === "priority")
164
+ : undefined;
161
165
  entry.default_service_tier = null;
162
166
  entry.service_tiers = [{
163
167
  id: "priority",
164
168
  name: "Fast",
165
- description: model.fastTierDescription ?? "1.5x speed, increased usage",
169
+ description: model.fastTierDescription ?? nativeFast?.description ?? "1.5x speed, increased usage",
166
170
  }];
167
171
  entry.additional_speed_tiers = ["fast"];
168
172
  }
@@ -258,8 +262,21 @@ export function applyReasoningLevels(
258
262
  : efforts.find(effort => effort !== "none" && effort !== "minimal") ?? efforts[0];
259
263
  }
260
264
 
265
+ /**
266
+ * Native slugs entitled to the full GPT-5.6-era ladder (low..ultra, with max restored).
267
+ *
268
+ * The name is historical: membership is about the LADDER, not the model generation. `gpt-6-astra`
269
+ * qualifies because upstream ships it with the same six rungs
270
+ * (`supported_reasoning_levels` low/medium/high/xhigh/max/ultra, #42607). It used to qualify only
271
+ * as a side effect of borrowing Sol's capability source; once it became self-described that
272
+ * accident disappeared, and the sync path's else-branch
273
+ * (`applyReasoningLevels(entry, ["low","medium","high","xhigh"])`) would have truncated the
274
+ * shipped ladder, silently dropping `max` and `ultra`.
275
+ */
261
276
  export function isGpt56NativeSlug(slug: string): boolean {
262
- return !slug.includes("/") && nativeOpenAiCapabilitySourceSlug(slug).startsWith("gpt-5.6-");
277
+ if (slug.includes("/")) return false;
278
+ if (SELF_DESCRIBED_NATIVE_OPENAI_MODELS.has(slug)) return true;
279
+ return nativeOpenAiCapabilitySourceSlug(slug).startsWith("gpt-5.6-");
263
280
  }
264
281
 
265
282
  export function ensureGpt56ReasoningLevels(entry: RawEntry): void {
@@ -324,6 +341,10 @@ export function clampedDefaultEffort(original: string, surviving: readonly strin
324
341
  return (atOrBelow.at(-1) ?? ranked[0]!).effort;
325
342
  }
326
343
 
344
+ function requiresExactReserveEfforts(entry: RawEntry): boolean {
345
+ return entry.slug === NATIVE_RESERVE_MODEL || isReserveCatalogProjection(entry);
346
+ }
347
+
327
348
  export function clampEntryToCodexSupportedEfforts(
328
349
  entry: RawEntry,
329
350
  supported: ReadonlySet<string> | null,
@@ -334,6 +355,19 @@ export function clampEntryToCodexSupportedEfforts(
334
355
  : null;
335
356
  if (levels && levels.length > 0) {
336
357
  const kept = levels.filter(level => typeof level?.effort === "string" && supported.has(level.effort));
358
+ if (requiresExactReserveEfforts(entry)) {
359
+ entry.supported_reasoning_levels = kept;
360
+ if (kept.length === 0) {
361
+ // The list-level clamp removes this incompatible row; never invent another ladder.
362
+ delete entry.default_reasoning_level;
363
+ } else if (!kept.some(level => level.effort === entry.default_reasoning_level)) {
364
+ entry.default_reasoning_level = clampedDefaultEffort(
365
+ typeof entry.default_reasoning_level === "string" ? entry.default_reasoning_level : "",
366
+ kept.map(level => level.effort!),
367
+ );
368
+ }
369
+ return;
370
+ }
337
371
  entry.supported_reasoning_levels = kept.length > 0
338
372
  ? kept
339
373
  : CODEX_REASONING_LEVELS
@@ -364,7 +398,9 @@ export function clampCatalogModelsToObservedCodexSupport(
364
398
 
365
399
  const removed = new Set<string>();
366
400
  const affected: string[] = [];
367
- for (const entry of models) {
401
+ for (let index = 0; index < models.length;) {
402
+ const entry = models[index]!;
403
+ const hadLadder = Array.isArray(entry.supported_reasoning_levels) && entry.supported_reasoning_levels.length > 0;
368
404
  const before = new Set(
369
405
  (Array.isArray(entry.supported_reasoning_levels) ? entry.supported_reasoning_levels : [])
370
406
  .flatMap(level => typeof (level as { effort?: string })?.effort === "string"
@@ -386,11 +422,14 @@ export function clampCatalogModelsToObservedCodexSupport(
386
422
  : null;
387
423
  const lost = [...before].filter(effort => !after.has(effort));
388
424
  const defaultClamped = Boolean(beforeDefault && beforeDefault !== afterDefault);
389
- if (lost.length > 0 || defaultClamped) {
425
+ const omitted = requiresExactReserveEfforts(entry) && hadLadder && after.size === 0;
426
+ if (lost.length > 0 || defaultClamped || omitted) {
390
427
  for (const effort of lost) removed.add(effort);
391
428
  if (defaultClamped && beforeDefault) removed.add(beforeDefault);
392
429
  if (typeof entry.slug === "string") affected.push(entry.slug);
393
430
  }
431
+ if (omitted) models.splice(index, 1);
432
+ else index += 1;
394
433
  }
395
434
 
396
435
  return {
@@ -38,13 +38,19 @@ import type { RawEntry } from "./parsing";
38
38
  import { readCurrentCatalogOrCache, readCurrentCodexCatalog, readCurrentCodexModelsCache, unique } from "./bundled";
39
39
  import { trustedAccountBoundNativeCatalogSlug, visibleCodexAccountSelectors } from "./account-models";
40
40
  import { CODEX_NATIVE_ALIAS_CATALOG_KIND } from "./kinds";
41
+ import { RESERVE_METADATA_SOURCE_FIELD } from "./reserve";
41
42
  import {
42
43
  ACCOUNT_GATED_NATIVE_OPENAI_MODELS,
43
44
  NATIVE_DAYBREAK_BLUE_MODEL,
45
+ NATIVE_GPT6_ASTRA_MODEL,
46
+ NATIVE_RESERVE_MODEL,
44
47
  NATIVE_OPENAI_CAPABILITY_ALIAS_MODELS,
45
48
  NATIVE_OPENAI_MODELS,
49
+ SELF_DESCRIBED_NATIVE_OPENAI_MODELS,
46
50
  SUPPORTED_NATIVE_OPENAI_SLUGS,
51
+ hasNativeOpenAiCapabilityMetadata,
47
52
  isNativeOpenAiCapabilityAliasModel,
53
+ nativeOpenAiAliasPresentation,
48
54
  nativeOpenAiCapabilitySourceSlug,
49
55
  } from "./native-models";
50
56
  import { cachedAvailableAccountGatedNativeModels } from "../model-entitlements";
@@ -52,16 +58,22 @@ import { MAIN_CODEX_ACCOUNT_ID } from "../main-account";
52
58
  export { CODEX_NATIVE_ALIAS_CATALOG_KIND } from "./kinds";
53
59
  export {
54
60
  NATIVE_DAYBREAK_BLUE_MODEL,
61
+ NATIVE_GPT6_ASTRA_MODEL,
55
62
  NATIVE_OPENAI_CAPABILITY_ALIAS_MODELS,
56
63
  NATIVE_OPENAI_MODELS,
64
+ SELF_DESCRIBED_NATIVE_OPENAI_MODELS,
57
65
  SUPPORTED_NATIVE_OPENAI_SLUGS,
66
+ hasNativeOpenAiCapabilityMetadata,
58
67
  isNativeOpenAiCapabilityAliasModel,
68
+ nativeOpenAiAliasPresentation,
59
69
  nativeOpenAiCapabilitySourceSlug,
60
70
  } from "./native-models";
61
71
 
62
72
  export const DOCUMENTED_NATIVE_OPENAI_ADDITIONS = [
63
73
  "gpt-5.3-codex-spark",
64
74
  "gpt-5.6-sol", "gpt-5.6-terra", "gpt-5.6-luna",
75
+ // The shipped pin also backfills older installed Codex catalogs that predate Astra.
76
+ NATIVE_GPT6_ASTRA_MODEL,
65
77
  ];
66
78
 
67
79
  export function configuredNativeAliasSlugs(
@@ -161,6 +173,12 @@ export const NATIVE_OPENAI_CONTEXT_OVERRIDES: Record<string, { contextWindow?: n
161
173
  // ChatGPT account."`), so the promotion rests on a report from an account that has
162
174
  // access rather than on a probe. Treat it as the weaker evidence of the four.
163
175
  [NATIVE_DAYBREAK_BLUE_MODEL]: { contextWindow: NATIVE_GPT56_CONTEXT_WINDOW, maxContextWindow: NATIVE_GPT56_MAX_INPUT_TOKENS, maxInputTokens: NATIVE_GPT56_MAX_INPUT_TOKENS },
176
+ // gpt-6-astra ships its own numbers (upstream models.json, #42607): a 272,000 default window
177
+ // against an 872,000 ceiling. It is NOT in NATIVE_GPT56_FAMILY, so it must not inherit that
178
+ // family's measured 922,000 clamp — advertising 922,000 here over-stated the ceiling by 50k.
179
+ // maxInputTokens is clamped to the resolved window by nativeOpenAiMaxInputTokens, so this reads
180
+ // 272,000 by default and 872,000 only under the long-window opt-in.
181
+ [NATIVE_GPT6_ASTRA_MODEL]: { contextWindow: 272_000, maxContextWindow: 872_000, maxInputTokens: 872_000 },
164
182
  };
165
183
 
166
184
  const PINNED_UPSTREAM_MODELS: Map<string, RawEntry> = new Map(
@@ -246,13 +264,36 @@ export function nativeContextLimits(
246
264
  }
247
265
 
248
266
  /** Apply the user levers to an authoritative value. */
267
+ /**
268
+ * The ceiling a native slug may be RAISED to by a user lever, or undefined when it has no
269
+ * separate long window.
270
+ *
271
+ * This is what makes the dashboard's 1M opt-in work: without an opt-in ceiling a lever can only
272
+ * ever narrow the advertised window, so the toggle would appear to do nothing. The GPT-5.6 family
273
+ * shares one measured ceiling; a self-described native carries its own in
274
+ * `NATIVE_OPENAI_CONTEXT_OVERRIDES.maxContextWindow` (`gpt-6-astra` ships 872,000 against a
275
+ * 272,000 default), and reading it per-slug is what keeps the toggle honest for a model whose
276
+ * ceiling is not the family's.
277
+ */
278
+ function longWindowOptInCeiling(slug: string): number | undefined {
279
+ if (NATIVE_GPT56_FAMILY.has(slug)) return NATIVE_GPT56_MAX_INPUT_TOKENS;
280
+ const override = NATIVE_OPENAI_CONTEXT_OVERRIDES[slug];
281
+ const defaultWindow = positiveInt(override?.contextWindow);
282
+ const longWindow = positiveInt(override?.maxContextWindow);
283
+ if (defaultWindow === undefined || longWindow === undefined || longWindow <= defaultWindow) {
284
+ return undefined;
285
+ }
286
+ return longWindow;
287
+ }
288
+
249
289
  function narrowToLimits(raw: number | undefined, slug: string, input: NativeContextLimitsInput): number | undefined {
250
290
  if (raw === undefined) return undefined;
251
291
  const limits = asLimits(input);
252
292
  const overlay = positiveInt(limits.modelWindows?.[slug]) ?? positiveInt(limits.providerWindow);
253
293
  const cap = positiveInt(limits.cap);
254
- if (NATIVE_GPT56_FAMILY.has(slug)) {
255
- const ceiling = NATIVE_GPT56_MAX_INPUT_TOKENS;
294
+ const optInCeiling = longWindowOptInCeiling(slug);
295
+ if (optInCeiling !== undefined) {
296
+ const ceiling = optInCeiling;
256
297
  const chosen = overlay ?? cap ?? raw;
257
298
  const window = Math.min(chosen, ceiling);
258
299
  return overlay !== undefined && cap !== undefined ? Math.min(window, cap) : window;
@@ -496,15 +537,23 @@ export function applyNativeVisibility(
496
537
 
497
538
  function upstreamNativeEntryForSlug(slug: string): RawEntry | undefined {
498
539
  const sourceSlug = nativeOpenAiCapabilitySourceSlug(slug);
499
- if (!sourceSlug.startsWith("gpt-5.6-")) return undefined;
540
+ // A self-described native returns its OWN pinned row; the alias-cloning branch below stays
541
+ // reserved for slugs that genuinely borrow another model's identity. The allowlist is explicit
542
+ // rather than "has a pinned entry", which would also admit gpt-5.5/gpt-5.4/gpt-5.4-mini into
543
+ // the sync-replacement authority this map carries.
544
+ if (!sourceSlug.startsWith("gpt-5.6-") && !SELF_DESCRIBED_NATIVE_OPENAI_MODELS.has(slug)) {
545
+ return undefined;
546
+ }
500
547
  const source = PINNED_UPSTREAM_MODELS.get(sourceSlug);
501
548
  if (!source) return undefined;
502
- if (slug === sourceSlug) return source;
549
+ if (slug === sourceSlug) return withDerivedBaseInstructions(source);
503
550
 
504
551
  const alias = structuredClone(source) as RawEntry;
505
552
  alias.slug = slug;
506
- alias.display_name = "Daybreak Blue";
507
- alias.description = "Frontier general-purpose model with safeguards for defensive cybersecurity work.";
553
+ const presentation = nativeOpenAiAliasPresentation(slug);
554
+ if (!presentation) return undefined; // an alias with no product identity must not ship a wrong one
555
+ alias.display_name = presentation.displayName;
556
+ alias.description = presentation.description;
508
557
  if (typeof alias.base_instructions === "string") {
509
558
  alias.base_instructions = identifyRoutedModel(alias.base_instructions, slug);
510
559
  }
@@ -521,6 +570,27 @@ function upstreamNativeEntryForSlug(slug: string): RawEntry | undefined {
521
570
  return alias;
522
571
  }
523
572
 
573
+ /**
574
+ * Backfill `base_instructions` from `model_messages.instructions_template` when upstream ships
575
+ * only the latter.
576
+ *
577
+ * `gpt-6-astra` is the first pinned row to arrive without a top-level `base_instructions`; every
578
+ * other native carries both. That field is not decorative here — `hasNativeCatalogRowShape`,
579
+ * `findNativeTemplate` and `findSupportedNativeTemplate` all test for it, so a row missing it is
580
+ * not recognized as a native catalog row at all. The two fields hold the same prompt upstream, so
581
+ * deriving one from the other preserves upstream's content while keeping this codebase's row
582
+ * shape intact. The pinned JSON is left byte-identical to upstream; only the projection fills in.
583
+ */
584
+ function withDerivedBaseInstructions(entry: RawEntry): RawEntry {
585
+ if (typeof entry.base_instructions === "string" && entry.base_instructions.length > 0) return entry;
586
+ const messages = entry.model_messages;
587
+ const template = messages && typeof messages === "object" && !Array.isArray(messages)
588
+ ? (messages as Record<string, unknown>).instructions_template
589
+ : undefined;
590
+ if (typeof template !== "string" || template.length === 0) return entry;
591
+ return { ...entry, base_instructions: template };
592
+ }
593
+
524
594
  export const UPSTREAM_NATIVE_ENTRIES: Map<string, RawEntry> = new Map(
525
595
  [...NATIVE_OPENAI_MODELS, ...NATIVE_OPENAI_CAPABILITY_ALIAS_MODELS].flatMap(slug => {
526
596
  const entry = upstreamNativeEntryForSlug(slug);
@@ -536,10 +606,44 @@ export function upstreamNativeEntry(slug: string): RawEntry | null {
536
606
  return clone;
537
607
  }
538
608
 
609
+ /**
610
+ * Product label for a native slug whose custom row inherits native metadata.
611
+ *
612
+ * An alias carries a hand-written presentation because upstream never described it. A
613
+ * self-described native gets its label from its own pinned row instead, so the two kinds answer
614
+ * through one accessor and no caller has to know which it holds.
615
+ */
616
+ export function nativeOpenAiCapabilityDisplayName(slug: string): string | undefined {
617
+ const presentation = nativeOpenAiAliasPresentation(slug);
618
+ if (presentation) return presentation.displayName;
619
+ const pinned = UPSTREAM_NATIVE_ENTRIES.get(slug);
620
+ return typeof pinned?.display_name === "string" ? pinned.display_name : undefined;
621
+ }
622
+
623
+ /**
624
+ * Slugs whose persisted row may be replaced by the pinned snapshot even when it carries a real
625
+ * display name — because THIS codebase wrote that name from a guess.
626
+ *
627
+ * `shouldUpgradeToUpstreamEntry`'s normal rule ("upgrade only fallback-quality rows, where
628
+ * `display_name === slug`") assumes any row with a real label came from upstream and is therefore
629
+ * authoritative. That assumption broke for `gpt-6-astra`: opencodex shipped a speculative row with
630
+ * a hand-written "GPT-6 Astra" label and a provisional description while the slug was still a leak.
631
+ * Those rows are already on disk in every install that ran that release, and they look genuine, so
632
+ * without this list they would survive every future sync and permanently shadow the real shipped
633
+ * metadata — the wrong label, the wrong 922k ceiling, the wrong priority.
634
+ *
635
+ * Membership is a statement about opencodex's own history, not about upstream. Add a slug only
636
+ * when a released version of this project wrote a fabricated row for it.
637
+ */
638
+ const SELF_AUTHORED_NATIVE_ROWS: ReadonlySet<string> = new Set([NATIVE_GPT6_ASTRA_MODEL]);
639
+
539
640
  export function shouldUpgradeToUpstreamEntry(entry: RawEntry): boolean {
540
- return typeof entry.slug === "string"
541
- && UPSTREAM_NATIVE_ENTRIES.has(entry.slug)
542
- && entry.display_name === entry.slug;
641
+ if (typeof entry.slug !== "string" || !UPSTREAM_NATIVE_ENTRIES.has(entry.slug)) return false;
642
+ if (entry.display_name === entry.slug) return true;
643
+ // A row this project authored from a guess is not evidence of upstream truth, however genuine
644
+ // its display name looks. Replace it once, from the pin.
645
+ return SELF_AUTHORED_NATIVE_ROWS.has(entry.slug)
646
+ && entry.display_name !== UPSTREAM_NATIVE_ENTRIES.get(entry.slug)?.display_name;
543
647
  }
544
648
 
545
649
  export function nativeOpenAiSlugs(): string[] {
@@ -566,7 +670,7 @@ function isAccountBoundOpenAiNativeSlug(slug: string): boolean {
566
670
  * malformed and minimal hand-written rows. It cannot distinguish a genuine upstream observation
567
671
  * from a complete row typed by hand into `$CODEX_HOME/models_cache.json`: there is no signature,
568
672
  * source identity, or server attestation to check. A full-shape forged row is accepted, and
569
- * `observedFullShapeRowIsAccepted` in tests/native-model-toggle.test.ts pins that so nobody
673
+ * `observedFullShapeRowIsAccepted` in tests/codex-integration/native-model-toggle.test.ts pins that so nobody
570
674
  * later mistakes this predicate for a security boundary.
571
675
  *
572
676
  * That is acceptable here because the file is user-owned and written by Codex itself: anyone
@@ -595,14 +699,44 @@ function observedAccountBoundNativeSlug(entry: RawEntry): string | undefined {
595
699
  const accountBound = trustedAccountBoundNativeCatalogSlug(entry);
596
700
  const slug = accountBound ?? (typeof entry.slug === "string" ? entry.slug : "");
597
701
  if (!isAccountBoundOpenAiNativeSlug(slug)
598
- || entry.supported_in_api !== true
702
+ || (entry.supported_in_api !== true && !(slug === NATIVE_RESERVE_MODEL && entry.supported_in_api === false))
703
+ || (slug === NATIVE_RESERVE_MODEL && entry[RESERVE_METADATA_SOURCE_FIELD] !== undefined
704
+ && entry[RESERVE_METADATA_SOURCE_FIELD] !== NATIVE_RESERVE_MODEL)
599
705
  || !hasNativeCatalogRowShape(entry)
600
- || (entry.visibility !== "list" && entry[ACCOUNT_BOUND_OBSERVED_NATIVE_MARKER] !== true)) {
706
+ || (entry.visibility !== "list" && entry[ACCOUNT_BOUND_OBSERVED_NATIVE_MARKER] !== true
707
+ && !(slug === NATIVE_RESERVE_MODEL && entry.visibility === "hide"))) {
601
708
  return undefined;
602
709
  }
603
710
  return slug;
604
711
  }
605
712
 
713
+ /** Prefer a genuine bare observation; an adapted OCX row must never become native evidence. */
714
+ export function observedReserveCatalogSource(
715
+ entries: readonly RawEntry[],
716
+ mainSelectors: readonly string[],
717
+ ): RawEntry | null {
718
+ const actual = entries.filter(entry => observedAccountBoundNativeSlug(entry) === NATIVE_RESERVE_MODEL);
719
+ const bare = actual.find(entry => entry.slug === NATIVE_RESERVE_MODEL);
720
+ const qualified = actual.find(entry => entry[RESERVE_METADATA_SOURCE_FIELD] === NATIVE_RESERVE_MODEL
721
+ && typeof entry.slug === "string"
722
+ && mainSelectors.includes(entry.slug.slice(0, entry.slug.indexOf("/"))));
723
+ const selected = bare ?? qualified;
724
+ if (!selected) return null;
725
+ const source = structuredClone(selected);
726
+ if (!bare) {
727
+ const prefix = `${String(source.slug).split("/")[0]} / `;
728
+ if (typeof source.display_name === "string" && source.display_name.startsWith(prefix)) {
729
+ source.display_name = source.display_name.slice(prefix.length);
730
+ }
731
+ }
732
+ source.slug = NATIVE_RESERVE_MODEL;
733
+ delete source.opencodex_catalog_kind;
734
+ delete source[RESERVE_METADATA_SOURCE_FIELD];
735
+ delete source[ACCOUNT_BOUND_OBSERVED_NATIVE_MARKER];
736
+ delete source[ACCOUNT_BOUND_OBSERVED_SELECTORS_MARKER];
737
+ return source;
738
+ }
739
+
606
740
  /**
607
741
  * Return exact, previously observed account-native rows that are not in the static release set.
608
742
  * The result is used only to carry a hidden observation across startup cache invalidation.
@@ -643,7 +777,8 @@ export function accountBoundNativeOpenAiSlugs(
643
777
  ): string[] {
644
778
  const observed = observedEntries.flatMap(entry => {
645
779
  const slug = observedAccountBoundNativeSlug(entry);
646
- return slug === undefined ? [] : [slug];
780
+ // Reserve belongs only to the Codex-specific opt-in builder, not generic native exports.
781
+ return slug === undefined || slug === NATIVE_RESERVE_MODEL ? [] : [slug];
647
782
  });
648
783
  return unique([...NATIVE_OPENAI_MODELS, ...observed]);
649
784
  }
@@ -670,7 +805,7 @@ export function accountBoundNativeOpenAiSlugsBySelector(
670
805
  );
671
806
  for (const entry of observedEntries) {
672
807
  const slug = observedAccountBoundNativeSlug(entry);
673
- if (slug === undefined || SUPPORTED_NATIVE_OPENAI_SLUGS.has(slug)) continue;
808
+ if (slug === undefined || slug === NATIVE_RESERVE_MODEL || SUPPORTED_NATIVE_OPENAI_SLUGS.has(slug)) continue;
674
809
  const generated = trustedAccountBoundNativeCatalogSlug(entry);
675
810
  const generatedSelector = generated === undefined || typeof entry.slug !== "string"
676
811
  ? undefined
@@ -1,11 +1,53 @@
1
+ /** Reserve wire identity, not a globally available native catalog registration. */
2
+ export const NATIVE_RESERVE_MODEL = "gpt-reserve";
3
+
1
4
  /** ChatGPT/Codex wire id observed for the account-native Daybreak Blue surface. */
2
5
  export const NATIVE_DAYBREAK_BLUE_MODEL = "gpt-daybreak-blue-latest";
3
6
 
4
- /** Native ChatGPT/Codex ids whose availability is proven per authenticated account. */
7
+ /**
8
+ * SHIPPED as of 2026-09-03: openai/codex `ed391d4dd` (#42607, bundled model catalog) and
9
+ * `1f7b99922` (#42619, Amazon Bedrock catalogs). The registration is no longer speculative —
10
+ * `src/codex/data/upstream-models.json` now pins the real row, so this slug is SELF-DESCRIBED
11
+ * and must not borrow another model's capability metadata.
12
+ *
13
+ * Still NOT wire-normalized: unlike Daybreak the slug IS the wire id.
14
+ *
15
+ * Deliberately NOT account-gated (owner decision, 2026-09-04, reaffirmed during rollout).
16
+ * Upstream `available_in_plans` lists 23 plans including `free`, but the model is rolling out,
17
+ * so a given account's Codex surface may still answer
18
+ * `"The 'gpt-6-astra' model is not supported when using Codex with a ChatGPT account."`
19
+ * — the same refusal Daybreak returns. Gating on an entitlement roster would hide the row until
20
+ * that roster catches up; listing it means the request dispatches and the real upstream status
21
+ * is what the user sees. Evidence: devlog/_plan/260904_astra_release_alignment/021.
22
+ */
23
+ export const NATIVE_GPT6_ASTRA_MODEL = "gpt-6-astra";
24
+
25
+ /**
26
+ * Native ChatGPT/Codex ids whose availability is proven per authenticated account.
27
+ *
28
+ * Membership is expensive: it hides the row from the catalog, `/v1/models`, the dashboard and
29
+ * the desktop projection until an authenticated `/models` roster confirms it, AND it makes
30
+ * `auth-context.ts` refuse the request before it is sent. Both halves fail closed on ABSENCE of
31
+ * evidence, not on a denial.
32
+ *
33
+ * The flagship models are deliberately NOT here (owner decision, 2026-09-04). #3442 made
34
+ * discovery ask upstream under an adequate client version, which guarantees the QUESTION is
35
+ * fair but cannot guarantee an ANSWER: an unconfirmed account, a timed-out fetch, or a shard
36
+ * that has not caught up all produce the same silent disappearance, and a model vanishing from
37
+ * the picker reads as "opencodex lost my model" rather than "upstream did not confirm it".
38
+ * Listing them unconditionally means the request dispatches and the user sees the real upstream
39
+ * status. `disabledModels` remains the visibility lever.
40
+ *
41
+ * The cost, accepted knowingly: Pool routing no longer prefers an account that owns the model,
42
+ * so a multi-account user may take one upstream 400 and one alternate retry where they used to
43
+ * be routed straight to the owner. Nothing unsafe — each account still sends its own credential
44
+ * — and `gpt-6-astra` has shipped this way since 6f634eddc.
45
+ *
46
+ * `gpt-daybreak-blue-latest` stays gated. It has no shipped catalog row anywhere, so absence is
47
+ * the only signal that exists for it, and the ungating decision was scoped to the flagships.
48
+ * Evidence: devlog/_plan/260904_flagship_native_always_visible/.
49
+ */
5
50
  export const ACCOUNT_GATED_NATIVE_OPENAI_MODELS: ReadonlySet<string> = new Set([
6
- "gpt-5.6-sol",
7
- "gpt-5.6-terra",
8
- "gpt-5.6-luna",
9
51
  NATIVE_DAYBREAK_BLUE_MODEL,
10
52
  ]);
11
53
 
@@ -25,6 +67,19 @@ const NATIVE_OPENAI_CAPABILITY_SOURCES: Readonly<Record<string, string>> = Objec
25
67
  [NATIVE_DAYBREAK_BLUE_MODEL]: "gpt-5.6-sol",
26
68
  });
27
69
 
70
+ /**
71
+ * Native slugs that carry their OWN pinned upstream row rather than an alias's borrowed one.
72
+ *
73
+ * Membership authorizes `upstreamNativeEntryForSlug` to return the pinned entry directly. It is
74
+ * an explicit list, not a structural `PINNED_UPSTREAM_MODELS.has(slug)` predicate: the pin also
75
+ * holds `gpt-5.5`, `gpt-5.4` and `gpt-5.4-mini`, and admitting those into
76
+ * `UPSTREAM_NATIVE_ENTRIES` would newly authorize replacing their persisted catalog rows during
77
+ * sync — an invariant that map's own comment reserves for the GPT-5.6 family.
78
+ */
79
+ export const SELF_DESCRIBED_NATIVE_OPENAI_MODELS: ReadonlySet<string> = new Set([
80
+ NATIVE_GPT6_ASTRA_MODEL,
81
+ ]);
82
+
28
83
  /**
29
84
  * Native ids whose capability metadata is inherited from another pinned native row.
30
85
  *
@@ -46,10 +101,41 @@ export function isNativeOpenAiCapabilityAliasModel(slug: string): boolean {
46
101
  return Object.hasOwn(NATIVE_OPENAI_CAPABILITY_SOURCES, slug);
47
102
  }
48
103
 
104
+ /**
105
+ * Native slugs whose Codex-forward CUSTOM row inherits authoritative native metadata.
106
+ *
107
+ * Two shapes qualify and the distinction matters only to `upstreamNativeEntryForSlug`:
108
+ * a capability ALIAS borrows another model's pinned row, while a SELF-DESCRIBED native has its
109
+ * own. Every consumer that asks "does this custom row get real native capabilities and a real
110
+ * product label" wants both, which is why they call this rather than the alias check —
111
+ * `gpt-6-astra` stopped being an alias when its own row was pinned, and gating on
112
+ * `isNativeOpenAiCapabilityAliasModel` alone would have silently demoted it to a bare-slug label
113
+ * with no inherited ladder.
114
+ */
115
+ export function hasNativeOpenAiCapabilityMetadata(slug: string): boolean {
116
+ return isNativeOpenAiCapabilityAliasModel(slug) || SELF_DESCRIBED_NATIVE_OPENAI_MODELS.has(slug);
117
+ }
118
+
49
119
  export function nativeOpenAiCapabilitySourceSlug(slug: string): string {
50
120
  return NATIVE_OPENAI_CAPABILITY_SOURCES[slug] ?? slug;
51
121
  }
52
122
 
123
+ /**
124
+ * Presentation identity per capability alias. Capability metadata (context, ladder, modalities)
125
+ * is inherited from the source model; the NAME and description are the alias's own product
126
+ * identity — hardcoding one alias's label would present every other alias as the wrong product.
127
+ */
128
+ export const NATIVE_OPENAI_ALIAS_PRESENTATION: Readonly<Record<string, { displayName: string; description: string }>> = Object.freeze({
129
+ [NATIVE_DAYBREAK_BLUE_MODEL]: {
130
+ displayName: "Daybreak Blue",
131
+ description: "Frontier general-purpose model with safeguards for defensive cybersecurity work.",
132
+ },
133
+ });
134
+
135
+ export function nativeOpenAiAliasPresentation(slug: string): { displayName: string; description: string } | undefined {
136
+ return NATIVE_OPENAI_ALIAS_PRESENTATION[slug];
137
+ }
138
+
53
139
  /**
54
140
  * Native OpenAI model ids that this release can route and restore with authoritative metadata.
55
141
  *
@@ -70,6 +156,32 @@ export const NATIVE_OPENAI_MODELS = [
70
156
  "gpt-5.5", "gpt-5.4", "gpt-5.4-mini", "gpt-5.3-codex-spark",
71
157
  "gpt-5.6-sol", "gpt-5.6-terra", "gpt-5.6-luna",
72
158
  NATIVE_DAYBREAK_BLUE_MODEL,
159
+ NATIVE_GPT6_ASTRA_MODEL,
73
160
  ];
74
161
 
75
162
  export const SUPPORTED_NATIVE_OPENAI_SLUGS = new Set(NATIVE_OPENAI_MODELS);
163
+
164
+ /**
165
+ * Natives that retain the physical main account as a read-free sentinel during a native-main
166
+ * drain, instead of reading as unavailable and letting the subagent fallback chain advance.
167
+ *
168
+ * This used to be spelled `ACCOUNT_GATED_NATIVE_OPENAI_MODELS`, which was never what it meant:
169
+ * the sentinel protects the atomic main claim so a routed fallback cannot bypass it, and that
170
+ * has nothing to do with entitlement. The two sets were identical in practice, so the accident
171
+ * went unnoticed until the flagships were ungated (2026-09-04) and the predicate would have
172
+ * flipped false — letting a drain silently rewrite the operator's configured subagent model.
173
+ *
174
+ * It is an explicit list rather than `SUPPORTED_NATIVE_OPENAI_SLUGS`, which would have widened
175
+ * the sentinel to `gpt-5.5`, `gpt-5.4`, `gpt-5.4-mini` and `gpt-5.3-codex-spark` as well. Those
176
+ * models were never covered, and widening would turn "fell back and answered" into a
177
+ * maintenance error for the most commonly configured fallback slug in the repo. Membership is
178
+ * the set the drain behaviour was actually reasoned about: the account-gated natives plus the
179
+ * flagships that just left that set.
180
+ */
181
+ export const NATIVE_MAIN_DRAIN_SENTINEL_MODELS: ReadonlySet<string> = new Set([
182
+ ...ACCOUNT_GATED_NATIVE_OPENAI_MODELS,
183
+ "gpt-5.6-sol",
184
+ "gpt-5.6-terra",
185
+ "gpt-5.6-luna",
186
+ NATIVE_GPT6_ASTRA_MODEL,
187
+ ]);