@bitkyc08/opencodex 2.42.0 → 2.43.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 (246) 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-Djowl68T.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 +13 -3
  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/kiro-calibration.ts +181 -0
  26. package/src/adapters/kiro.ts +135 -3
  27. package/src/adapters/openai-responses.ts +216 -26
  28. package/src/adapters/responses-code-mode.ts +59 -0
  29. package/src/adapters/tool-catalog-nudge.ts +1 -1
  30. package/src/adapters/xai-schema-analysis.ts +86 -0
  31. package/src/adapters/xai-tool-schema.ts +2 -87
  32. package/src/adapters/xai-web-search.ts +1 -1
  33. package/src/bridge.ts +22 -10
  34. package/src/chat/inbound.ts +11 -3
  35. package/src/claude/inbound-content-options.ts +60 -0
  36. package/src/claude/inbound-model-options.ts +142 -0
  37. package/src/claude/inbound-records.ts +7 -0
  38. package/src/claude/inbound.ts +10 -202
  39. package/src/claude/model-info.ts +45 -0
  40. package/src/cli/account-auth.ts +21 -6
  41. package/src/cli/capabilities.ts +13 -2
  42. package/src/cli/claude.ts +232 -39
  43. package/src/cli/config-command.ts +9 -1
  44. package/src/cli/dispatch.ts +5 -1
  45. package/src/cli/doctor.ts +10 -0
  46. package/src/cli/effort.ts +372 -0
  47. package/src/cli/export-command.ts +3 -9
  48. package/src/cli/help.ts +1 -0
  49. package/src/cli/index.ts +13 -0
  50. package/src/cli/init.ts +4 -0
  51. package/src/cli/model-selection-guidance.ts +30 -0
  52. package/src/cli/models-runtime.ts +3 -2
  53. package/src/cli/models.ts +8 -3
  54. package/src/cli/opencode.ts +4 -1
  55. package/src/cli/provider-runtime.ts +65 -0
  56. package/src/cli/provider.ts +8 -0
  57. package/src/cli/registry.ts +16 -2
  58. package/src/cli/runtime-api.ts +3 -1
  59. package/src/cli/star-prompt.ts +22 -6
  60. package/src/cli/status-probes.ts +168 -0
  61. package/src/cli/status.ts +5 -168
  62. package/src/clients/config-export/constants.ts +69 -0
  63. package/src/clients/config-export/contracts.ts +154 -0
  64. package/src/clients/config-export/dsh.ts +132 -0
  65. package/src/clients/config-export/fast-models.ts +29 -0
  66. package/src/clients/config-export/mcode.ts +83 -0
  67. package/src/clients/config-export/model-metadata.ts +108 -0
  68. package/src/clients/config-export/omp.ts +104 -0
  69. package/src/clients/config-export/zcode.ts +92 -0
  70. package/src/clients/config-export.ts +18 -710
  71. package/src/codex/account-lifecycle.ts +20 -3
  72. package/src/codex/account-usability.ts +2 -0
  73. package/src/codex/auth-api.ts +170 -23
  74. package/src/codex/auth-context.ts +328 -24
  75. package/src/codex/catalog/effort.ts +30 -4
  76. package/src/codex/catalog/metadata.ts +39 -9
  77. package/src/codex/catalog/native-models.ts +53 -12
  78. package/src/codex/catalog/parsing.ts +119 -5
  79. package/src/codex/catalog/provider-fetch.ts +150 -20
  80. package/src/codex/catalog/reserve.ts +52 -0
  81. package/src/codex/catalog/sync.ts +89 -16
  82. package/src/codex/convergence-types.ts +1 -0
  83. package/src/codex/convergence.ts +2 -0
  84. package/src/codex/data/upstream-models.json +1 -1
  85. package/src/codex/forward-transport-headers.ts +25 -0
  86. package/src/codex/inject.ts +3 -28
  87. package/src/codex/legacy-config-keys.ts +68 -0
  88. package/src/codex/log-guard/inspect-schema.ts +137 -0
  89. package/src/codex/log-guard/inspect.ts +2 -134
  90. package/src/codex/loopback-target.ts +54 -0
  91. package/src/codex/main-account-cache.ts +63 -1
  92. package/src/codex/main-account-hard-lock.ts +52 -0
  93. package/src/codex/main-account.ts +3 -1
  94. package/src/codex/management-convergence.ts +3 -0
  95. package/src/codex/model-entitlements.ts +54 -4
  96. package/src/codex/project-config-warnings.ts +92 -2
  97. package/src/codex/prompt-layers/encoding.ts +80 -0
  98. package/src/codex/prompt-layers/paths.ts +54 -0
  99. package/src/codex/prompt-layers/revision.ts +55 -0
  100. package/src/codex/prompt-layers/toml-edit.ts +163 -0
  101. package/src/codex/prompt-layers/toml-read.ts +181 -0
  102. package/src/codex/prompt-layers.ts +14 -520
  103. package/src/codex/quota-auto-refresh-state.ts +16 -0
  104. package/src/codex/quota-auto-refresh.ts +219 -0
  105. package/src/codex/quota-types.ts +51 -0
  106. package/src/codex/quota.ts +252 -93
  107. package/src/codex/reserve-availability.ts +177 -0
  108. package/src/codex/routing.ts +28 -9
  109. package/src/codex/shim.ts +53 -11
  110. package/src/codex/subagent-model-fallback.ts +23 -3
  111. package/src/combos/failover.ts +125 -7
  112. package/src/combos/identifiers.ts +89 -0
  113. package/src/combos/index.ts +4 -0
  114. package/src/combos/resolve.ts +80 -9
  115. package/src/combos/types.ts +20 -93
  116. package/src/config/subagent-models.ts +24 -0
  117. package/src/config.ts +156 -13
  118. package/src/generated/compatibility-version.json +454 -166
  119. package/src/generated/model-metadata.ts +1 -1
  120. package/src/integrations/journal.ts +65 -4
  121. package/src/integrations/store.ts +5 -0
  122. package/src/lab/events/limits.ts +4 -0
  123. package/src/lib/destination-policy.ts +31 -2
  124. package/src/lib/errors.ts +39 -0
  125. package/src/lib/provider-outbound.ts +69 -3
  126. package/src/lib/proxy-env.ts +22 -0
  127. package/src/lib/redact-folding.ts +176 -0
  128. package/src/lib/redact.ts +2 -175
  129. package/src/lib/state-store-sweeper.ts +20 -6
  130. package/src/lib/token-estimate.ts +94 -27
  131. package/src/lib/windows-user-principal.ts +53 -5
  132. package/src/oauth/anthropic-routing.ts +99 -3
  133. package/src/oauth/generic-account-failover.ts +36 -13
  134. package/src/oauth/index.ts +127 -26
  135. package/src/oauth/login-cli.ts +5 -0
  136. package/src/oauth/meta-muse.ts +117 -15
  137. package/src/oauth/pool-settings-capability.ts +15 -4
  138. package/src/providers/api-keys.ts +8 -10
  139. package/src/providers/default-aliases.ts +39 -0
  140. package/src/providers/derive.ts +10 -2
  141. package/src/providers/fastwire.ts +36 -7
  142. package/src/providers/initial-model-selection-runtime.ts +90 -0
  143. package/src/providers/initial-model-selection.ts +120 -0
  144. package/src/providers/key-failover.ts +134 -54
  145. package/src/providers/key-store.ts +11 -1
  146. package/src/providers/label.ts +1 -1
  147. package/src/providers/model-discovery.ts +76 -0
  148. package/src/providers/model-rename-startup.ts +72 -8
  149. package/src/providers/openai-sidecar.ts +17 -5
  150. package/src/providers/openai-tiers-destination.ts +102 -0
  151. package/src/providers/openai-tiers.ts +2 -99
  152. package/src/providers/opencode-go-transport.ts +41 -0
  153. package/src/providers/quota-key-accounts.ts +141 -0
  154. package/src/providers/quota-types.ts +9 -0
  155. package/src/providers/quota.ts +535 -104
  156. package/src/providers/registry.ts +60 -17
  157. package/src/providers/xai-responses-opt-in.ts +31 -5
  158. package/src/quota/reset-activation.ts +81 -0
  159. package/src/quota/reset-detector.ts +305 -0
  160. package/src/quota/reset-notify-config.ts +162 -0
  161. package/src/quota/reset-observer.ts +125 -0
  162. package/src/quota/reset-poller.ts +160 -0
  163. package/src/quota/reset-seen-store.ts +385 -0
  164. package/src/quota/reset-sinks.ts +199 -0
  165. package/src/quota/window-mapping.ts +106 -0
  166. package/src/responses/apply-patch-envelope.ts +46 -0
  167. package/src/responses/code-mode-helper-compat.ts +39 -1
  168. package/src/responses/custom-tool-compat.ts +10 -4
  169. package/src/responses/hosted-tool-policy.ts +12 -4
  170. package/src/responses/parser-content.ts +133 -0
  171. package/src/responses/parser-text-format.ts +24 -0
  172. package/src/responses/parser-tools.ts +188 -0
  173. package/src/responses/parser.ts +3 -326
  174. package/src/responses/state.ts +124 -28
  175. package/src/router.ts +48 -13
  176. package/src/routing/analytics.ts +1 -0
  177. package/src/routing/capability.ts +17 -4
  178. package/src/server/auth-cors.ts +7 -1
  179. package/src/server/background-lifecycle.ts +23 -1
  180. package/src/server/chat-completions.ts +25 -3
  181. package/src/server/claude-messages.ts +62 -5
  182. package/src/server/effort-row.ts +1 -1
  183. package/src/server/fast-row.ts +295 -0
  184. package/src/server/gui-static.ts +30 -4
  185. package/src/server/index.ts +107 -21
  186. package/src/server/management/agent-settings-routes.ts +2 -2
  187. package/src/server/management/combo-routes.ts +37 -9
  188. package/src/server/management/config-routes.ts +93 -2
  189. package/src/server/management/integration-routes.ts +108 -0
  190. package/src/server/management/model-routes.ts +13 -3
  191. package/src/server/management/model-rows.ts +20 -1
  192. package/src/server/management/native-integration-routes.ts +4 -1
  193. package/src/server/management/oauth-account-routes.ts +38 -10
  194. package/src/server/management/provider-routes.ts +34 -3
  195. package/src/server/management/quota-reset-routes.ts +57 -0
  196. package/src/server/management/route-registry.ts +7 -4
  197. package/src/server/management/shared.ts +19 -5
  198. package/src/server/management/system-routes.ts +3 -2
  199. package/src/server/management-api.ts +14 -2
  200. package/src/server/ports.ts +12 -2
  201. package/src/server/relay-eager.ts +38 -23
  202. package/src/server/relay.ts +4 -0
  203. package/src/server/request-log.ts +6 -0
  204. package/src/server/responses/codex-ws-correlation.ts +65 -0
  205. package/src/server/responses/codex-ws-exchange.ts +261 -0
  206. package/src/server/responses/codex-ws-metadata.ts +134 -0
  207. package/src/server/responses/codex-ws-pool.ts +162 -0
  208. package/src/server/responses/codex-ws-request.ts +87 -0
  209. package/src/server/responses/codex-ws-session.ts +93 -0
  210. package/src/server/responses/codex-ws-wire.ts +144 -0
  211. package/src/server/responses/collaboration.ts +41 -1
  212. package/src/server/responses/compact.ts +105 -12
  213. package/src/server/responses/core.ts +476 -57
  214. package/src/server/responses/fetch-helpers.ts +10 -3
  215. package/src/server/responses/input-admission.ts +16 -9
  216. package/src/server/responses/responses-field-backfill.ts +1 -1
  217. package/src/server/responses/ws-upstream.ts +34 -318
  218. package/src/server/responses-custom-tool-repair.ts +20 -4
  219. package/src/server/responses-undeclared-tool-guard.ts +100 -8
  220. package/src/server/safe-response-headers.ts +23 -0
  221. package/src/server/search.ts +9 -0
  222. package/src/server/subagent-models-startup.ts +27 -0
  223. package/src/server/system-env-shell.ts +238 -0
  224. package/src/server/system-env.ts +7 -234
  225. package/src/server/ws-bridge.ts +3 -25
  226. package/src/server/xai-responses-startup.ts +21 -0
  227. package/src/service-manager-probe.ts +1 -1
  228. package/src/service.ts +55 -16
  229. package/src/types/config.ts +108 -12
  230. package/src/types/provider.ts +36 -7
  231. package/src/types/tools.ts +26 -1
  232. package/src/types.ts +2 -0
  233. package/src/update/notify.ts +8 -2
  234. package/src/usage/cost.ts +38 -28
  235. package/src/usage/expected-prices.ts +34 -15
  236. package/src/usage/log.ts +2 -0
  237. package/src/usage/model-identity.ts +26 -0
  238. package/src/usage/summary.ts +15 -1
  239. package/src/vision/describe.ts +6 -0
  240. package/src/vision/image-rewrite.ts +108 -0
  241. package/src/vision/index.ts +19 -306
  242. package/src/vision/plan.ts +205 -0
  243. package/src/web-search/executor.ts +6 -0
  244. package/src/web-search/index.ts +8 -1
  245. package/gui/dist/assets/index-BU1tE0sr.js +0 -112
  246. package/gui/dist/assets/index-DL9-iS6J.css +0 -1
@@ -0,0 +1,295 @@
1
+ import {
2
+ accountBoundNativeOpenAiSlugsBySelector,
3
+ shouldIncludeAccountBoundNativeOpenAi,
4
+ shouldIncludeNativeOpenAi,
5
+ UPSTREAM_NATIVE_ENTRIES,
6
+ } from "../codex/catalog/metadata";
7
+ import { comboModelId, comboPublicModelId } from "../combos/types";
8
+ import { policyModelId, policyPublicModelId } from "../routing/profile";
9
+ import type { InboundWire } from "../providers/registry";
10
+ import { fastPolicyForModel } from "../providers/service-tier";
11
+ import type { OcxConfig } from "../types";
12
+ import {
13
+ isKnownId,
14
+ knownEffortRowIds,
15
+ loadDetectedCursorEffortTable,
16
+ parseEffortRowId,
17
+ parseRequestEffortRowId,
18
+ type EffortRowKnownIds,
19
+ type ParsedEffortRowId,
20
+ } from "./effort-row";
21
+
22
+ /**
23
+ * Synthetic Fast selectors: `<base-id>--fast` published on external model listings for
24
+ * models whose resolved Fast policy is eligible, so a client that can only pick a model by
25
+ * id reaches the priority service tier. The Codex app has a picker toggle for this; nothing
26
+ * else did (devlog/_plan/260904_external_fast_wire).
27
+ *
28
+ * The marker is `--fast`, not `-fast`. Terminal `-fast` is a REAL id across this catalog —
29
+ * grok-4-fast, glm-5.3-fast, gpt-5-fast, and every Cursor fast variant — so a single hyphen
30
+ * cannot tell a product apart from a tier. `--` is the same terminal separator the effort-row
31
+ * grammar relies on for the same reason.
32
+ */
33
+ const FAST_ROW_SUFFIX = "--fast";
34
+
35
+ export interface ParsedFastRowId {
36
+ baseId: string;
37
+ }
38
+
39
+ export interface ParsedSyntheticRow {
40
+ fastRow: ParsedFastRowId | null;
41
+ effortRow: ParsedEffortRowId | null;
42
+ }
43
+
44
+ export function fastRowId(baseId: string): string {
45
+ return `${baseId}${FAST_ROW_SUFFIX}`;
46
+ }
47
+
48
+ /**
49
+ * Whether a provider/model pair's resolved Fast policy may be published as a row.
50
+ *
51
+ * `eligible` alone. `unclassified` means capability is undefined, and `decideTier` makes
52
+ * `fastMode` inert there, so publishing it would advertise a tier the runtime then refuses
53
+ * to send.
54
+ */
55
+ export function fastRowEligible(
56
+ provider: Parameters<typeof fastPolicyForModel>[0],
57
+ modelId: string,
58
+ providerName?: string,
59
+ inbound: InboundWire = "responses",
60
+ ): boolean {
61
+ return fastPolicyForModel(provider, modelId, providerName, inbound).eligibility === "eligible";
62
+ }
63
+
64
+ /** Shared discovery/export policy; native rows additionally need upstream tier evidence. */
65
+ export function catalogFastRowEligible(
66
+ config: OcxConfig,
67
+ model: { provider: string; id: string; native?: boolean; supportsServiceTier?: boolean },
68
+ ): boolean {
69
+ if (config.fastRows === false) return false;
70
+ // Configured suffix-shaped IDs are real bases; live-only ones are deliberately
71
+ // refused by ingress. Discovery must not advertise a selector ingress cannot strip.
72
+ if (model.id.endsWith(FAST_ROW_SUFFIX)
73
+ && !fastRowBases(config)(model.native ? model.id : `${model.provider}/${model.id}`)) return false;
74
+ if (model.native) {
75
+ const id = model.id.slice(model.id.lastIndexOf("/") + 1);
76
+ const tiers = UPSTREAM_NATIVE_ENTRIES.get(id)?.additional_speed_tiers;
77
+ const provider = config.providers.openai;
78
+ return Array.isArray(tiers) && tiers.includes("fast") && provider !== undefined
79
+ && fastRowEligible(provider, id, "openai");
80
+ }
81
+ if (model.supportsServiceTier !== undefined) return model.supportsServiceTier === true;
82
+ const provider = config.providers[model.provider];
83
+ return provider !== undefined && fastRowEligible(provider, model.id, model.provider);
84
+ }
85
+
86
+ /**
87
+ * Bases that may carry a fast row.
88
+ *
89
+ * Deliberately a SUPERSET of what the listings publish, and deliberately not
90
+ * `knownEffortRowIds`. That set answers "which exact ids defeat the synthetic grammar"; it
91
+ * does not answer "which bases are routable". Bare natives prove the gap: the `openai`
92
+ * registry entry declares no `models` list and the default provider config declares none
93
+ * either, because they route through a family-pattern rule instead. `gpt-5.6-sol` is
94
+ * therefore absent from it, and requiring membership would publish `gpt-5.6-sol--fast` and
95
+ * then refuse to parse it.
96
+ *
97
+ * Being too permissive costs nothing here: routing still rejects a base it cannot serve, and
98
+ * the exact-id guard in `parseFastRowId` still protects real models. Being too strict breaks
99
+ * the feature.
100
+ *
101
+ * Membership must not depend on the live-model cache. An earlier version seeded this set
102
+ * from `knownEffortRowIds` alone, which reads `getStaleCached` (router.ts:125), so a
103
+ * live-only model leaving the cache silently stopped its `--fast` selector from parsing.
104
+ * The argument that its base row leaves the listing at the same time is true but
105
+ * irrelevant: `/v1/models` is discovery, not a routing allowlist, and `routeModel` still
106
+ * serves the bare base through the default provider (router.ts:791) and the qualified base
107
+ * through its configured provider (router.ts:680). So the base kept working while only the
108
+ * fast selector broke — the asymmetry this set exists to prevent.
109
+ *
110
+ * A pure Set cannot express this. Listings also publish LIVE-discovered and retained models
111
+ * that appear in no config (`provider-fetch.ts` publishes `goModels` and `retainModels`), and
112
+ * enumerating them means reading the very cache whose churn caused the original defect. So
113
+ * this returns a PREDICATE: an id is a routable base when it is a known static/config id, or
114
+ * when it is namespaced under an enabled configured provider. The second clause is
115
+ * structural, so it holds for a live-discovered model without consulting the cache, and it
116
+ * is exactly the shape `routeModel` uses to accept a qualified id (router.ts:680).
117
+ *
118
+ * A base is RECOGNIZED here and then judged by routing, which is the component that actually
119
+ * knows whether it can serve it.
120
+ */
121
+ export function fastRowBases(config: OcxConfig): (id: string) => boolean {
122
+ const bases = new Set<string>();
123
+ // Configured providers: any model the router would accept for this provider, plus the
124
+ // namespaced and alias-namespaced spellings a listing can publish. Deliberately NOT
125
+ // knownEffortRowIds, whose live-cache half makes membership time-dependent.
126
+ for (const [providerName, providerConfig] of Object.entries(config.providers)) {
127
+ if (providerConfig.disabled === true) continue;
128
+ const namespaces = [providerName, providerConfig.alias].filter(
129
+ (value): value is string => typeof value === "string" && value.length > 0,
130
+ );
131
+ const declared = [
132
+ ...(providerConfig.models ?? []),
133
+ ...(providerConfig.defaultModel ? [providerConfig.defaultModel] : []),
134
+ ...Object.values(providerConfig.modelAliases ?? {}),
135
+ ...(config.customModels ?? [])
136
+ .filter(model => model.provider === providerName && model.modelId)
137
+ .map(model => model.modelId),
138
+ ];
139
+ for (const id of declared) {
140
+ bases.add(id);
141
+ for (const namespace of namespaces) bases.add(`${namespace}/${id}`);
142
+ }
143
+ }
144
+ // The STATIC upstream table, not `visibleNativeSlugs()`: that one filters by
145
+ // disabled/shadowed state and reaches the catalog cache on disk, so it would both read a
146
+ // file per parsed selector and SHRINK as runtime state changes. A base disappearing
147
+ // mid-session would strand a client still holding the id it was published.
148
+ if (shouldIncludeNativeOpenAi(config)) {
149
+ for (const slug of UPSTREAM_NATIVE_ENTRIES.keys()) bases.add(slug);
150
+ }
151
+ if (shouldIncludeAccountBoundNativeOpenAi(config)) {
152
+ // An EMPTY observed-entry list on purpose: the default argument reads the Codex models
153
+ // cache and catalog from disk. The empty form still seeds every selector with the native
154
+ // model set, and anything publishable is in UPSTREAM_NATIVE_ENTRIES anyway.
155
+ for (const [selector, slugs] of accountBoundNativeOpenAiSlugsBySelector(config, [])) {
156
+ for (const slug of slugs) bases.add(`${selector}/${slug}`);
157
+ }
158
+ }
159
+ // Namespaces whose qualified ids route, whatever the cache currently holds.
160
+ const namespaces = new Set<string>();
161
+ // Virtual rows. A combo or routing profile is published under its canonical
162
+ // `<namespace>/<id>` AND under an operator alias, which may be an arbitrary bare string
163
+ // with no namespace to vouch for it, so the structural clause below cannot reach it. A
164
+ // combo also cannot be covered by config.providers: declaring a provider named `combo`
165
+ // is rejected outright (combos/types.ts:191).
166
+ for (const [id, combo] of Object.entries(config.combos ?? {})) {
167
+ bases.add(comboModelId(id));
168
+ bases.add(comboPublicModelId(id, combo));
169
+ }
170
+ for (const [id, profile] of Object.entries(config.routingProfiles ?? {})) {
171
+ bases.add(policyModelId(id));
172
+ bases.add(policyPublicModelId(id, profile));
173
+ }
174
+ for (const [providerName, providerConfig] of Object.entries(config.providers)) {
175
+ if (providerConfig.disabled === true) continue;
176
+ namespaces.add(providerName.toLowerCase());
177
+ if (typeof providerConfig.alias === "string" && providerConfig.alias.length > 0) {
178
+ namespaces.add(providerConfig.alias.toLowerCase());
179
+ }
180
+ }
181
+ return (id: string): boolean => {
182
+ if (bases.has(id)) return true;
183
+ const slash = id.indexOf("/");
184
+ if (slash <= 0 || slash === id.length - 1) return false;
185
+ // A live-discovered or retained model is published as `<provider>/<model>` and appears in
186
+ // no config, so structural recognition is the only cache-free way to accept it.
187
+ //
188
+ // But NOT when the remainder itself ends in the marker. A real live model may legitimately
189
+ // be named `foo--fast`; its exact id is protected by the known-id guard only while the
190
+ // discovery cache still holds it, and after eviction that guard goes quiet while this
191
+ // clause would still accept `provider/foo` structurally - silently routing a DIFFERENT
192
+ // model than the client selected. Refusing the strip is the safe side: the caller then
193
+ // sends the id verbatim and routing resolves the real model, or fails honestly.
194
+ if (id.slice(slash + 1).endsWith(FAST_ROW_SUFFIX)) return false;
195
+ return namespaces.has(id.slice(0, slash).toLowerCase());
196
+ };
197
+ }
198
+
199
+ export function parseFastRowId(
200
+ id: string,
201
+ config: Pick<OcxConfig, "fastRows">,
202
+ knownIds?: EffortRowKnownIds,
203
+ routableBases?: EffortRowKnownIds,
204
+ ): ParsedFastRowId | null {
205
+ if (config.fastRows === false) return null;
206
+ if (!id.endsWith(FAST_ROW_SUFFIX)) return null;
207
+ // An exact configured/public id always beats the synthetic grammar, the same precedence
208
+ // effort rows use. An operator who really named a model `x--fast` keeps it.
209
+ if (isKnownId(knownIds, id)) return null;
210
+ const baseId = id.slice(0, -FAST_ROW_SUFFIX.length);
211
+ if (baseId.length === 0) return null;
212
+ return isKnownId(routableBases, baseId) ? { baseId } : null;
213
+ }
214
+
215
+ /**
216
+ * True when an effort-row base still carries a fast marker, i.e. the selector nested the two
217
+ * grammars. Composition is not supported, so such an id resolves to neither rather than
218
+ * silently to whichever parser ran first.
219
+ *
220
+ * Known-id guarded, so a real model named `foo--fast` keeps its legitimate `foo--fast--high`
221
+ * effort row.
222
+ */
223
+ export function effortBaseCarriesFastMarker(
224
+ baseId: string,
225
+ knownIds: EffortRowKnownIds | undefined,
226
+ ): boolean {
227
+ return baseId.endsWith(FAST_ROW_SUFFIX) && !isKnownId(knownIds, baseId);
228
+ }
229
+
230
+ /**
231
+ * Resolve one ingress selector against both synthetic grammars, returning at most one.
232
+ * Callers pass the id the client sent and never a value another parser mutated.
233
+ */
234
+ export function parseSyntheticRowId(
235
+ id: string,
236
+ config: OcxConfig,
237
+ // Claude surfaces decode the alias before the marker is unambiguous, so they pass the
238
+ // decoded form for Fast while effort parsing keeps seeing the id the client sent. A THUNK,
239
+ // not a string: arguments are evaluated before the call, so an eager decode would run its
240
+ // alias lookups even on the fastRows-off path this function exists to leave untouched.
241
+ fastSelector?: () => string,
242
+ ): ParsedSyntheticRow {
243
+ // Explicit opt-out preserves the effort-only parser and avoids Fast inventory work.
244
+ if (config.fastRows === false) {
245
+ return { fastRow: null, effortRow: parseRequestEffortRowId(id, config) };
246
+ }
247
+ const selector = fastSelector?.() ?? id;
248
+ const wantsFast = selector.endsWith(FAST_ROW_SUFFIX);
249
+ // Bail before building any inventory when neither grammar can match. A readable Claude
250
+ // alias is `claude-ocx-<provider>--<model>`, so it ALWAYS contains `--`: testing only for
251
+ // the separator would rebuild the whole model inventory on every Claude turn for a
252
+ // selector that cannot be a fast row. With effort parsing off, the terminal suffix is the
253
+ // only thing that can match.
254
+ const wantsEffort = config.cursorEffortRows === true && id.lastIndexOf("--") > 0;
255
+ if (!wantsFast && !wantsEffort) return { fastRow: null, effortRow: null };
256
+ const knownIds = knownEffortRowIds(config);
257
+ const fastRow = wantsFast
258
+ ? parseFastRowId(selector, config, knownIds, fastRowBases(config))
259
+ : null;
260
+ if (fastRow) return { fastRow, effortRow: null };
261
+ // Cursor install detection stays behind its own flag, exactly as parseRequestEffortRowId
262
+ // gates it today.
263
+ const effortRow = wantsEffort
264
+ ? parseEffortRowId(id, config, { knownIds, table: loadDetectedCursorEffortTable() })
265
+ : null;
266
+ return effortRow && effortBaseCarriesFastMarker(effortRow.baseId, knownIds)
267
+ ? { fastRow: null, effortRow: null }
268
+ : { fastRow: null, effortRow };
269
+ }
270
+
271
+ /** Fast-only resolution for surfaces that never parsed an effort row. */
272
+ export function parseFastOnlyRowId(
273
+ config: OcxConfig,
274
+ selector: () => string,
275
+ ): ParsedFastRowId | null {
276
+ if (config.fastRows === false) return null;
277
+ return parseSyntheticRowId("", config, selector).fastRow;
278
+ }
279
+
280
+ /**
281
+ * Add a fast sibling beside an eligible row. The base row is always kept: a fast row is an
282
+ * addition, never a replacement. That is the deliberate difference from `fastMode`, which
283
+ * replaces the listed Cursor id — replacement suits a global switch, but a per-request
284
+ * selector has to leave the default reachable.
285
+ */
286
+ export function expandFastRow<T extends { id: string }>(
287
+ row: T,
288
+ eligible: boolean,
289
+ config: Pick<OcxConfig, "fastRows">,
290
+ knownIds?: EffortRowKnownIds,
291
+ ): T[] {
292
+ if (config.fastRows === false || !eligible) return [row];
293
+ const id = fastRowId(row.id);
294
+ return isKnownId(knownIds, id) ? [row] : [row, { ...row, id }];
295
+ }
@@ -90,6 +90,22 @@ function runtimeRoleMeta(runtimeRole: string): string {
90
90
  return `<meta name="opencodex-runtime-role" content="${escapeHtmlAttribute(runtimeRole)}">`;
91
91
  }
92
92
 
93
+ /**
94
+ * Does this bind require a typed management credential?
95
+ *
96
+ * Emitted for the same reason as the role: so the dashboard can answer a question on first
97
+ * paint without asking a remote-hub endpoint. It is NOT the same question as the role.
98
+ * `standalone` + `hostname: "0.0.0.0"` is an operator who deliberately exposed the dashboard
99
+ * and must type the admin token, while a `hub` on loopback still mints its own session — so
100
+ * the role cannot stand in for this, and using it that way locked out exactly the operator
101
+ * who is supposed to see the prompt.
102
+ *
103
+ * Non-secret: it restates the bind the operator chose, which `/healthz` and the dashboard
104
+ * already reflect.
105
+ */
106
+ function managementAuthRequiredMeta(required: boolean): string {
107
+ return `<meta name="opencodex-management-auth-required" content="${required ? "1" : "0"}">`;
108
+ }
93
109
  function htmlDocumentResponse(html: string): Response {
94
110
  return new Response(html, {
95
111
  headers: {
@@ -101,9 +117,18 @@ function htmlDocumentResponse(html: string): Response {
101
117
  });
102
118
  }
103
119
 
104
- function htmlResponse(path: string, session?: GuiSessionBootstrap, runtimeRole?: string): Response {
120
+ function htmlResponse(
121
+ path: string,
122
+ session?: GuiSessionBootstrap,
123
+ runtimeRole?: string,
124
+ managementAuthRequired?: boolean,
125
+ ): Response {
105
126
  let html = readFileSync(path, "utf8");
106
- const bootstrap = `${runtimeRole ? runtimeRoleMeta(runtimeRole) : ""}${session ? sessionBootstrapMeta(session) : ""}`;
127
+ const bootstrap = [
128
+ runtimeRole ? runtimeRoleMeta(runtimeRole) : "",
129
+ managementAuthRequired === undefined ? "" : managementAuthRequiredMeta(managementAuthRequired),
130
+ session ? sessionBootstrapMeta(session) : "",
131
+ ].join("");
107
132
  if (bootstrap) {
108
133
  html = html.includes("</head>") ? html.replace("</head>", `${bootstrap}</head>`) : `${bootstrap}${html}`;
109
134
  }
@@ -126,6 +151,7 @@ export function serveGuiFile(
126
151
  guiDist = findGuiDist(),
127
152
  session?: GuiSessionBootstrap,
128
153
  runtimeRole?: string,
154
+ managementAuthRequired?: boolean,
129
155
  ): Response | null {
130
156
  if (!guiDist) return null;
131
157
  const filePath = resolveGuiFilePath(guiDist, pathname);
@@ -135,7 +161,7 @@ export function serveGuiFile(
135
161
  if (!extname(pathname)) {
136
162
  const indexPath = join(guiDist, "index.html");
137
163
  if (isFile(indexPath)) {
138
- return htmlResponse(indexPath, session, runtimeRole);
164
+ return htmlResponse(indexPath, session, runtimeRole, managementAuthRequired);
139
165
  }
140
166
  }
141
167
  return null;
@@ -143,7 +169,7 @@ export function serveGuiFile(
143
169
 
144
170
  const ext = extname(filePath);
145
171
  const contentType = MIME_TYPES[ext] || "application/octet-stream";
146
- if (ext === ".html") return htmlResponse(filePath, session, runtimeRole);
172
+ if (ext === ".html") return htmlResponse(filePath, session, runtimeRole, managementAuthRequired);
147
173
  // Snapshot bytes before returning the response. Bun.file is lazy: if gui/dist is replaced
148
174
  // after Bun frames the response but before the stream finishes, its Content-Length can
149
175
  // describe the old file while the body comes from the new one (#2792).
@@ -12,7 +12,6 @@ import {
12
12
  } from "./ws-bridge";
13
13
  import type { Server, ServerWebSocket } from "bun";
14
14
  import {
15
- DEFAULT_SUBAGENT_MODELS,
16
15
  applyProxyEnv,
17
16
  armClaudeCodeBaseline,
18
17
  loadConfig,
@@ -22,6 +21,8 @@ import {
22
21
  } from "../config";
23
22
  import { grokDefaultReasoningEffort } from "../grok/effort";
24
23
  import { flushConfigDirHardening } from "../config/paths";
24
+ import { migrateStartupSubagentModels } from "./subagent-models-startup";
25
+ import { migrateStartupXaiResponses } from "./xai-responses-startup";
25
26
  import { reconcileOAuthProviders } from "../oauth";
26
27
  import { withCatalogWriteSerialization } from "../codex/catalog-write-serialization";
27
28
  import { invalidateCodexModelsCacheWithPermit } from "../codex/catalog/sync";
@@ -33,8 +34,12 @@ import {
33
34
  type NativeCodexOwnership,
34
35
  type OwnershipInspection,
35
36
  } from "../integrations/native/ownership-preflight";
36
- import { createResetCreditWhamClient, registerCodexCooldownRecoveryProbeWorker } from "../codex/auth-api";
37
+ import {
38
+ createResetCreditWhamClient,
39
+ registerCodexCooldownRecoveryProbeWorker,
40
+ } from "../codex/auth-api";
37
41
  import { activateResetCreditAutoRedeem } from "../codex/reset-credit-auto-redeem";
42
+ import { registerCodexQuotaAutoRefreshWorker } from "../codex/quota-auto-refresh";
38
43
  import {
39
44
  reconcileLiveStateStores,
40
45
  setLiveStateStoreConfig,
@@ -230,6 +235,7 @@ import { recordCursorSeen } from "../integrations/cursor-seen";
230
235
  import { detectCursorInstalls } from "../integrations/cursor-detect";
231
236
  import { loadCursorEffortTable } from "../integrations/cursor-effort-table";
232
237
  import { expandCursorEffortRow, knownEffortRowIds } from "./effort-row";
238
+ import { catalogFastRowEligible, expandFastRow } from "./fast-row";
233
239
 
234
240
  export const MAX_WS_FRAME_BYTES = 50 * 1024 * 1024;
235
241
  const WEBSOCKET_IDLE_TIMEOUT_SECONDS = 0;
@@ -509,7 +515,7 @@ function attachLiveSidebandUpstream(
509
515
 
510
516
  // Adapter resolution + wire-protocol override extracted to ./server/adapter-resolve.
511
517
 
512
- // Source invariant for tests/passthrough-abort.test.ts after the pure module split:
518
+ // Source invariant for tests/responses/passthrough-abort.test.ts after the pure module split:
513
519
  // if (isEventStream && upstreamResponse.body) {
514
520
  // const repairConfig = route.provider.responsesItemIdRepair;
515
521
  // const needsClientRewrite = imageGenCallAliases.size > 0
@@ -583,6 +589,8 @@ export interface StartServerDeps {
583
589
  readinessGate?: ReadinessGate;
584
590
  /** Test-only package-tree observation; production captures package.json identity at boot. */
585
591
  packageTreeIntegrity?: PackageTreeIntegrityGuard;
592
+ /** Test-only seam for observing quota-worker registration ownership. */
593
+ registerCodexQuotaAutoRefreshWorker?: typeof registerCodexQuotaAutoRefreshWorker;
586
594
  }
587
595
 
588
596
  function inspectStartupOwnership(
@@ -645,7 +653,13 @@ export function startServer(port?: number, deps: StartServerDeps = {}): Server<W
645
653
  // Captured before loadConfig() starts the optional ACL flight so stop() drains the same dir
646
654
  // even if OPENCODEX_HOME changes underneath a long-lived process.
647
655
  const startupConfigDir = getConfigDir();
648
- const config = runModelRenameStartupMigration(runAlibabaRegionStartupMigration(runOpenAiTierStartupMigration(loadConfig())));
656
+ const startupConfig = migrateStartupSubagentModels(
657
+ runModelRenameStartupMigration(runAlibabaRegionStartupMigration(runOpenAiTierStartupMigration(loadConfig()))),
658
+ );
659
+ // Reconcile disk-backed presets first: it replaces provider rows and must not undo
660
+ // an in-memory wire upgrade when that upgrade's persistence is temporarily unavailable.
661
+ reconcileOAuthProviders(startupConfig);
662
+ const config = migrateStartupXaiResponses(startupConfig);
649
663
  warnAgentTaskRecoveryStartup(config);
650
664
  setLiveStateStoreConfig(config);
651
665
  applyProxyEnv(config);
@@ -655,16 +669,7 @@ export function startServer(port?: number, deps: StartServerDeps = {}): Server<W
655
669
  let userCostOverlayReconciler: { stop(): void } | null = null;
656
670
  // Arm synchronously before listen. A pending journal therefore makes __main__ unusable
657
671
  // before any request can resolve its physical credential, while health/management/Pool stay live.
658
- // Refresh OAuth provider presets (models/noReasoningModels) from the registry so a proxy update
659
- // adding/dropping models reaches existing configs on start — not just fresh installs.
660
- reconcileOAuthProviders(config);
661
672
  reconcileLiveStateStores();
662
- // Seed default featured subagent models on first run only (UNSET → defaults). A user-set list,
663
- // even [], is left alone so GUI removals persist.
664
- if (config.subagentModels === undefined) {
665
- config.subagentModels = [...DEFAULT_SUBAGENT_MODELS];
666
- saveConfig(config);
667
- }
668
673
  // authMode migration (devlog 260726_claude_auth_auto/015): before "auto" existed,
669
674
  // choosing Subscription DELETED the key, so a pre-upgrade block with no authMode is
670
675
  // indistinguishable from "never chose". Pin those to subscription once so an upgrade
@@ -798,6 +803,10 @@ export function startServer(port?: number, deps: StartServerDeps = {}): Server<W
798
803
  * on the direct-spawn host into a 404 (#3192). The handler still runs its own admission, so a
799
804
  * loopback caller without a ChatGPT credential is refused inside it rather than by this gate.
800
805
  *
806
+ * The standalone Images client uses the same base URL for its two POST routes. Their handler
807
+ * keeps the paid upstream behind its own admission and forward-credential checks, so admit only
808
+ * the exact methods and paths it serves (#3428).
809
+ *
801
810
  * `GET /v1/models` is on the list for a reason that is easy to miss. When catalog
802
811
  * materialization fails or finds no source, `syncCodex` warns and injects with
803
812
  * `catalogPath: null`; Codex then builds an ONLINE model manager and `model/list` refreshes
@@ -811,6 +820,9 @@ export function startServer(port?: number, deps: StartServerDeps = {}): Server<W
811
820
  }
812
821
  if (path === "/v1/responses/compact") return req.method === "POST";
813
822
  if (path === "/v1/alpha/search") return req.method === "POST";
823
+ if (path === "/v1/images/generations" || path === "/v1/images/edits") {
824
+ return req.method === "POST";
825
+ }
814
826
  if (path === "/v1/models") return req.method === "GET";
815
827
  // Realtime voice — a directly-spawned `codex app-server` needs these for desktop voice
816
828
  // the same way it needs /v1/responses. Two shapes, same trust model as /v1/responses:
@@ -1017,8 +1029,11 @@ export function startServer(port?: number, deps: StartServerDeps = {}): Server<W
1017
1029
  return "public";
1018
1030
  }
1019
1031
  let backgroundLifecycle: ReturnType<typeof acquireServerBackgroundLifecycle> | null = null;
1032
+ let unregisterQuotaAutoRefresh: (() => void) | null = null;
1020
1033
  try {
1021
1034
  backgroundLifecycle = acquireServerBackgroundLifecycle(applyPolicy);
1035
+ unregisterQuotaAutoRefresh = (deps.registerCodexQuotaAutoRefreshWorker
1036
+ ?? registerCodexQuotaAutoRefreshWorker)(config);
1022
1037
  // External `ocx config set` / direct config.json edits run in other
1023
1038
  // processes; poll the file so Logs/Usage display prices follow them live.
1024
1039
  // Started inside the guarded startup transaction so the catch below can
@@ -1270,7 +1285,7 @@ export function startServer(port?: number, deps: StartServerDeps = {}): Server<W
1270
1285
  // Built directly rather than through formatErrorResponse: that helper derives
1271
1286
  // `code` from the status and message via classifyError, and these two need stable,
1272
1287
  // specific codes. `catalog_not_found` in particular is what lets a caller — and
1273
- // tests/api-key-attribution.test.ts — tell "this route exists and has no catalog"
1288
+ // tests/server/api-key-attribution.test.ts — tell "this route exists and has no catalog"
1274
1289
  // apart from "this route is gone", which is the difference between admission proof
1275
1290
  // and a vacuous pass.
1276
1291
  return withCors(
@@ -1430,6 +1445,37 @@ export function startServer(port?: number, deps: StartServerDeps = {}): Server<W
1430
1445
  // Codex catalog (client_version) and the OpenAI list shape below stay byte-identical.
1431
1446
  const wantsAnthropicList = req.headers.get("anthropic-version") !== null
1432
1447
  || url.searchParams.get("flavor") === "anthropic";
1448
+ /**
1449
+ * Whether a NATIVE slug may carry a Fast sibling.
1450
+ *
1451
+ * Both halves are required. Upstream asserts the tier per model — the same
1452
+ * `additional_speed_tiers` the Codex picker's own toggle is built from — but an
1453
+ * operator capability override or the final wire resolution can still make the
1454
+ * route ineligible, and `decideTier` would then drop the tier the row advertised.
1455
+ *
1456
+ * Declared here, above the Claude discovery call, because that call reads it while
1457
+ * the raw OpenAI mapper further down does too; defining it there would leave this
1458
+ * use in its temporal dead zone.
1459
+ */
1460
+ const nativeFastEligible = (metadataId: string): boolean =>
1461
+ catalogFastRowEligible(config, { provider: OPENAI_CODEX_PROVIDER_ID, id: metadataId, native: true });
1462
+
1463
+ /**
1464
+ * Whether a routed catalog row may carry a Fast sibling.
1465
+ *
1466
+ * A combo is its own namespace with no `config.providers` entry — declaring a
1467
+ * provider named `combo` is rejected (combos/types.ts:191) — so provider lookup
1468
+ * cannot classify it. Its aggregated `supportsServiceTier` is already true only
1469
+ * when EVERY member supports the tier (aggregation.ts:201), which is the right
1470
+ * rule for a row that fans out to all of them.
1471
+ *
1472
+ * Declared beside nativeFastEligible, above the Claude discovery call that reads
1473
+ * both; defining it near the raw OpenAI mapper below would leave that use in its
1474
+ * temporal dead zone.
1475
+ */
1476
+ const catalogRowFastEligible = (m: { provider: string; id: string; supportsServiceTier?: boolean }): boolean =>
1477
+ catalogFastRowEligible(config, m);
1478
+
1433
1479
  if (wantsAnthropicList && !url.searchParams.has("client_version")) {
1434
1480
  if (config.claudeCode?.enabled === false) return jsonResponse({ data: [] }, 200, req, policy);
1435
1481
  // Build Desktop 3P registry so inbound alias resolution works for subsequent requests.
@@ -1451,7 +1497,22 @@ export function startServer(port?: number, deps: StartServerDeps = {}): Server<W
1451
1497
  : idsParam === "desktop"
1452
1498
  ? "desktop3p" as const
1453
1499
  : (/^claude-code\//i.test(req.headers.get("user-agent") ?? "") ? "readable" as const : "desktop3p" as const);
1454
- const data = buildAnthropicModelInfos(desktopNativeSlugs, goOrdered, resolveAutoContext(config.claudeCode), idStyle, activeDesktop3pAlias, nativeContextLimits(config), config.fastMode);
1500
+ const data = buildAnthropicModelInfos(
1501
+ desktopNativeSlugs,
1502
+ goOrdered,
1503
+ resolveAutoContext(config.claudeCode),
1504
+ idStyle,
1505
+ activeDesktop3pAlias,
1506
+ nativeContextLimits(config),
1507
+ config.fastMode,
1508
+ // Explicit opt-out omits the Fast predicate.
1509
+ config.fastRows !== false
1510
+ ? (model: { provider: string; id: string; supportsServiceTier?: boolean }) =>
1511
+ model.provider === "native"
1512
+ ? nativeFastEligible(model.id)
1513
+ : catalogRowFastEligible(model)
1514
+ : undefined,
1515
+ );
1455
1516
  return jsonResponse({ data }, 200, req, policy);
1456
1517
  }
1457
1518
  if (url.searchParams.has("client_version")) {
@@ -1580,7 +1641,13 @@ export function startServer(port?: number, deps: StartServerDeps = {}): Server<W
1580
1641
  // The projection is opt-in. Keep the default path free of Cursor install detection,
1581
1642
  // and resolve the bundle table once for the whole list rather than once per row.
1582
1643
  const effortRowsEnabled = config.cursorEffortRows === true;
1583
- const effortRowKnownIds = effortRowsEnabled ? knownEffortRowIds(config) : undefined;
1644
+ // Explicit opt-out skips policy resolution and additional rows.
1645
+ const fastRowsEnabled = config.fastRows !== false;
1646
+ // One inventory serves both grammars; building it twice would double the work on a
1647
+ // hot path for no benefit.
1648
+ const effortRowKnownIds = effortRowsEnabled || fastRowsEnabled
1649
+ ? knownEffortRowIds(config)
1650
+ : undefined;
1584
1651
  const privateInference = effortRowsEnabled
1585
1652
  ? detectCursorInstalls().find(install => install.build === "private-inference")
1586
1653
  : undefined;
@@ -1593,7 +1660,15 @@ export function startServer(port?: number, deps: StartServerDeps = {}): Server<W
1593
1660
  knownIds: effortRowKnownIds,
1594
1661
  table: cursorEffortTable,
1595
1662
  supportsReasoning: reasoningEfforts.length > 0,
1596
- });
1663
+ }).flatMap(row => expandFastRow(
1664
+ row,
1665
+ // Only the BASE row earns a fast sibling. An effort row already spent the
1666
+ // grammar, and the parser requires the stripped base to be routable, so
1667
+ // `<base>--<effort>--fast` would publish a row no ingress can resolve.
1668
+ row.id === id && nativeFastEligible(metadataId),
1669
+ config,
1670
+ effortRowKnownIds,
1671
+ ));
1597
1672
  };
1598
1673
  const routedRows = await Promise.all(uniqueCatalogModelsForRawPublicList(goOrdered).map(async m => {
1599
1674
  // Same rule as the anthropic branch: with the global fast switch on, a client
@@ -1634,7 +1709,12 @@ export function startServer(port?: number, deps: StartServerDeps = {}): Server<W
1634
1709
  knownIds: effortRowKnownIds,
1635
1710
  table: cursorEffortTable,
1636
1711
  supportsReasoning: (m.reasoningEfforts ?? []).length > 0,
1637
- });
1712
+ }).flatMap(expanded => expandFastRow(
1713
+ expanded,
1714
+ expanded.id === row.id && catalogRowFastEligible(m),
1715
+ config,
1716
+ effortRowKnownIds,
1717
+ ));
1638
1718
  }));
1639
1719
  const data = [
1640
1720
  ...visibleNatives.flatMap(id => expandedNativeModelRow(id)),
@@ -1753,7 +1833,7 @@ export function startServer(port?: number, deps: StartServerDeps = {}): Server<W
1753
1833
  ...admissionFields(admission),
1754
1834
  };
1755
1835
  return runAdmittedHttpTurn(req, policy, async turnAdmissionLease => {
1756
- const response = await handleSearch(req, config, logCtx, turnAdmissionLease);
1836
+ const response = await handleSearch(req, config, logCtx, turnAdmissionLease, admission);
1757
1837
  addFinalRequestLog(requestId, start, logCtx, response.status,
1758
1838
  response.status === 499 ? { closeReason: "client_cancel" } : undefined);
1759
1839
  return withCors(response, req, policy);
@@ -1855,7 +1935,7 @@ export function startServer(port?: number, deps: StartServerDeps = {}): Server<W
1855
1935
  // pre-translation stream + native passthrough callbacks) — do not re-wrap the
1856
1936
  // translated Anthropic stream here.
1857
1937
  return runAdmittedHttpTurn(req, policy, async turnAdmissionLease => withCors(
1858
- await handleClaudeMessages(req, config, logCtx, { requestId, start, turnAdmissionLease }, policy),
1938
+ await handleClaudeMessages(req, config, logCtx, { requestId, start, turnAdmissionLease, admission }, policy),
1859
1939
  req,
1860
1940
  policy,
1861
1941
  ));
@@ -2056,6 +2136,7 @@ export function startServer(port?: number, deps: StartServerDeps = {}): Server<W
2056
2136
  undefined,
2057
2137
  guiSessionCandidate ?? undefined,
2058
2138
  config.runtimeRole ?? "standalone",
2139
+ isApiAuthRequired(config),
2059
2140
  );
2060
2141
  if (guiFile) return guiFile;
2061
2142
  if (url.pathname === "/" && req.method === "GET") {
@@ -2322,6 +2403,7 @@ export function startServer(port?: number, deps: StartServerDeps = {}): Server<W
2322
2403
  }
2323
2404
  }
2324
2405
  } catch (error) {
2406
+ unregisterQuotaAutoRefresh?.();
2325
2407
  userCostOverlayReconciler?.stop();
2326
2408
  backgroundLifecycle?.releaseAfterFailedStart();
2327
2409
  void nativeMainLifecycle.release();
@@ -2347,7 +2429,11 @@ export function startServer(port?: number, deps: StartServerDeps = {}): Server<W
2347
2429
  ? [() => managementIngressRef.stop(closeActiveConnections)]
2348
2430
  : []),
2349
2431
  async () => {
2350
- userCostOverlayReconciler?.stop();
2432
+ try {
2433
+ userCostOverlayReconciler?.stop();
2434
+ } finally {
2435
+ unregisterQuotaAutoRefresh?.();
2436
+ }
2351
2437
  },
2352
2438
  ],
2353
2439
  async () => {
@@ -70,7 +70,7 @@ import type { PersistedUsageAttempt } from "../../usage/log";
70
70
  import { isAllowedRequestOrigin, jsonResponse, providerManagementConfigError, publicProviderBaseUrl, safeConfigDTO } from "../auth-cors";
71
71
  import { applySystemEnvToggle } from "../system-env";
72
72
 
73
- import { isPlainRecord, parseDebugLogQuery, tokPerSecondResult, unavailableCostReason, costResult, requestLogDto, stripRegistryOnlyStaticHeaders, fetchAllModels, fetchGrokCandidateModels, buildClaudeDesktopState } from "./shared";
73
+ import { isPlainRecord, parseDebugLogQuery, tokPerSecondResult, unavailableCostReason, costResult, requestLogDto, stripRegistryOnlyStaticHeaders, fetchInitializedModels as fetchAllModels, fetchGrokCandidateModels, buildClaudeDesktopState } from "./shared";
74
74
  import type { MetricUnavailableReason, TokPerSecondResult, CostEstimateReason, CostResult, MetricSource } from "./shared";
75
75
  import { readManagementJsonBody, readOptionalManagementJsonBody, rethrowManagementBodyTooLarge } from "./body";
76
76
 
@@ -377,7 +377,7 @@ export async function handleAgentSettingsRoutes(ctx: ManagementContext): Promise
377
377
  // sequence after the transition. A failure here is a persistence failure (the
378
378
  // writers' ok:false result or a throw from the underlying atomic write helper),
379
379
  // reported as 502 naming the failed key plus the writes that already landed.
380
- // NOTE: do not name that helper literally here — tests/grok-writer-boundary.test.ts
380
+ // NOTE: do not name that helper literally here — tests/providers/xai/grok-writer-boundary.test.ts
381
381
  // asserts this route file contains no direct write primitive, and matches on the
382
382
  // symbol name even inside a comment.
383
383
  const scalarWrites: Array<{ field: string; run: () => { ok: true; changed: boolean } | { ok: false; error: string } }> = [];