@bitkyc08/opencodex 2.11.0 → 2.11.1

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 (92) hide show
  1. package/gui/dist/assets/{index-Bk-PN-70.css → index-BA1dgo4q.css} +1 -1
  2. package/gui/dist/assets/index-DDZpgzKk.js +70 -0
  3. package/gui/dist/index.html +2 -2
  4. package/package.json +6 -4
  5. package/src/adapters/anthropic.ts +10 -2
  6. package/src/adapters/cursor/native-exec-common.ts +6 -2
  7. package/src/adapters/google-antigravity-replay.ts +3 -2
  8. package/src/adapters/google-antigravity-wire.ts +38 -6
  9. package/src/adapters/google.ts +57 -10
  10. package/src/adapters/openai-chat.ts +19 -2
  11. package/src/claude/model-info.ts +1 -1
  12. package/src/cli/account-auth.ts +3 -1
  13. package/src/cli/account-catalog-refresh.ts +14 -0
  14. package/src/cli/account-extended.ts +15 -1
  15. package/src/cli/codex-shim-readiness.ts +69 -0
  16. package/src/cli/combo.ts +5 -0
  17. package/src/cli/config-command.ts +33 -4
  18. package/src/cli/doctor.ts +21 -0
  19. package/src/cli/export-command.ts +6 -6
  20. package/src/cli/help.ts +6 -6
  21. package/src/cli/index.ts +22 -5
  22. package/src/cli/models-runtime.ts +13 -1
  23. package/src/cli/provider.ts +7 -0
  24. package/src/clients/config-export.ts +67 -5
  25. package/src/codex/account-lifecycle.ts +99 -10
  26. package/src/codex/auth-api.ts +222 -34
  27. package/src/codex/catalog/account-models.ts +9 -4
  28. package/src/codex/catalog/aggregation.ts +41 -8
  29. package/src/codex/catalog/bundled.ts +54 -22
  30. package/src/codex/catalog/effort.ts +47 -20
  31. package/src/codex/catalog/kinds.ts +2 -0
  32. package/src/codex/catalog/metadata.ts +70 -18
  33. package/src/codex/catalog/native-models.ts +7 -0
  34. package/src/codex/catalog/parsing.ts +29 -11
  35. package/src/codex/catalog/provider-fetch.ts +335 -50
  36. package/src/codex/catalog/sync.ts +588 -126
  37. package/src/codex/catalog-refresh-status.ts +87 -0
  38. package/src/codex/catalog-write-serialization.ts +2 -1
  39. package/src/codex/catalog.ts +4 -3
  40. package/src/codex/convergence-types.ts +1 -1
  41. package/src/codex/convergence.ts +190 -52
  42. package/src/codex/custom-model-catalog-migration.ts +176 -0
  43. package/src/codex/features.ts +79 -1
  44. package/src/codex/history-job.ts +81 -1
  45. package/src/codex/history-lock.ts +2 -1
  46. package/src/codex/history-provider.ts +4 -3
  47. package/src/codex/inject.ts +56 -12
  48. package/src/codex/model-cache.ts +50 -10
  49. package/src/codex/transition-state.ts +10 -2
  50. package/src/codex/user-identity.ts +110 -2
  51. package/src/combos/index.ts +3 -0
  52. package/src/combos/types.ts +75 -9
  53. package/src/config.ts +26 -12
  54. package/src/generated/{jawcode-model-metadata.ts → model-metadata.ts} +12 -10
  55. package/src/integrations/registry.ts +7 -0
  56. package/src/integrations/writer.ts +1 -1
  57. package/src/lib/pinned-http.ts +40 -9
  58. package/src/lib/process-control.ts +4 -1
  59. package/src/lib/provider-outbound.ts +42 -9
  60. package/src/oauth/cursor.ts +25 -5
  61. package/src/oauth/index.ts +59 -45
  62. package/src/providers/antigravity-models.ts +115 -3
  63. package/src/providers/context-cap.ts +13 -5
  64. package/src/providers/model-discovery-limits.ts +16 -0
  65. package/src/providers/model-discovery.ts +14 -15
  66. package/src/providers/provider-id-rewrite.ts +29 -0
  67. package/src/providers/quota.ts +93 -19
  68. package/src/providers/registry.ts +16 -5
  69. package/src/providers/slug-codec.ts +13 -6
  70. package/src/reasoning-effort.ts +22 -0
  71. package/src/router.ts +0 -20
  72. package/src/routing/history/indexer.ts +54 -39
  73. package/src/routing/quota.ts +77 -56
  74. package/src/server/index.ts +32 -7
  75. package/src/server/management/combo-routes.ts +43 -19
  76. package/src/server/management/config-routes.ts +115 -20
  77. package/src/server/management/model-routes.ts +10 -7
  78. package/src/server/management/model-rows.ts +6 -2
  79. package/src/server/management/oauth-account-routes.ts +12 -0
  80. package/src/server/management/provider-routes.ts +114 -40
  81. package/src/server/management/routing-profile-routes.ts +0 -22
  82. package/src/server/management-api.ts +7 -25
  83. package/src/server/responses/core.ts +3 -3
  84. package/src/server/responses/policy-fallback.ts +152 -0
  85. package/src/server/responses.ts +3 -2
  86. package/src/types.ts +15 -2
  87. package/src/usage/cost.ts +0 -0
  88. package/src/vision/describe.ts +3 -1
  89. package/src/vision/index.ts +17 -6
  90. package/src/vision/reasoning.ts +55 -0
  91. package/src/web-search/parse.ts +15 -3
  92. package/gui/dist/assets/index-BynIEIV-.js +0 -70
@@ -1,6 +1,6 @@
1
1
  import type { CodexAccountMode, OcxProviderConfig } from "../types";
2
2
  import { KIRO_MODELS, KIRO_MODEL_CONTEXT_WINDOWS, KIRO_MODEL_REASONING_EFFORTS } from "./kiro-models";
3
- import { ANTIGRAVITY_MODELS, ANTIGRAVITY_MODEL_CONTEXT_WINDOWS, ANTIGRAVITY_MODEL_EFFORTS } from "./antigravity-models";
3
+ import { ANTIGRAVITY_MODELS, ANTIGRAVITY_MODEL_CONTEXT_WINDOWS, ANTIGRAVITY_MODEL_EFFORTS, ANTIGRAVITY_MODEL_INPUT_MODALITIES } from "./antigravity-models";
4
4
  import type { ProviderBaseUrlChoice } from "./base-url-choices";
5
5
  import {
6
6
  QWEN_CLOUD_BASE_URL_CHOICES, QWEN_CLOUD_TOKEN_PLAN_BASE_URL,
@@ -1287,7 +1287,7 @@ export const PROVIDER_REGISTRY: readonly ProviderRegistryEntry[] = [
1287
1287
  // 2026-07-10: defaultModel is frozen pending Vertex-specific Tier-2 evidence; Gemini API
1288
1288
  // evidence from ai.google.dev does not establish Vertex publisher availability.
1289
1289
  { id: "google-vertex", label: "Google Vertex AI", adapter: "google", baseUrl: "https://aiplatform.googleapis.com", authKind: "key", dashboardUrl: "https://console.cloud.google.com/vertex-ai", defaultModel: "gemini-3-pro", googleMode: "vertex", jawcodeBundle: "google", extraMetadataAliases: ["gemini-vertex"] },
1290
- { id: "google-antigravity", label: "Google Antigravity", adapter: "google", baseUrl: "https://daily-cloudcode-pa.googleapis.com", authKind: "oauth", dashboardUrl: "https://antigravity.google", models: ANTIGRAVITY_MODELS, liveModels: false, defaultModel: "gemini-3.6-flash", modelContextWindows: ANTIGRAVITY_MODEL_CONTEXT_WINDOWS, modelReasoningEfforts: ANTIGRAVITY_MODEL_EFFORTS, googleMode: "cloud-code-assist", jawcodeBundle: "google", extraMetadataAliases: ["antigravity", "gemini-antigravity"] },
1290
+ { id: "google-antigravity", label: "Google Antigravity", adapter: "google", baseUrl: "https://daily-cloudcode-pa.googleapis.com", authKind: "oauth", dashboardUrl: "https://antigravity.google", models: ANTIGRAVITY_MODELS, liveModels: true, defaultModel: "gemini-3.6-flash", modelContextWindows: ANTIGRAVITY_MODEL_CONTEXT_WINDOWS, modelInputModalities: ANTIGRAVITY_MODEL_INPUT_MODALITIES, modelReasoningEfforts: ANTIGRAVITY_MODEL_EFFORTS, googleMode: "cloud-code-assist", jawcodeBundle: "google", extraMetadataAliases: ["antigravity", "gemini-antigravity"] },
1291
1291
  { id: "azure-openai", label: "Azure OpenAI", adapter: "azure-openai", baseUrl: "https://{resource}.openai.azure.com/openai", authKind: "key", featured: true, dashboardUrl: "https://portal.azure.com" },
1292
1292
  { id: "ollama", label: "Ollama (local)", adapter: "openai-chat", baseUrl: "http://localhost:11434/v1", authKind: "local", allowPrivateNetworkByDefault: true, allowBaseUrlOverride: true, featured: true, note: "Local — key usually blank" },
1293
1293
  { id: "vllm", label: "vLLM (local)", adapter: "openai-chat", baseUrl: "http://localhost:8000/v1", authKind: "local", allowPrivateNetworkByDefault: true, allowBaseUrlOverride: true, featured: true, note: "Local — key usually blank" },
@@ -1299,11 +1299,22 @@ export const PROVIDER_REGISTRY: readonly ProviderRegistryEntry[] = [
1299
1299
  adapter: "openai-chat",
1300
1300
  authKind: "key",
1301
1301
  dashboardUrl: "https://platform.deepseek.com/api_keys",
1302
- // deepseek-chat/deepseek-reasoner are upstream-deprecated at 2026-07-24 15:59 UTC;
1303
- // kept until then. Evidence: devlog/_plan/260710_provider_hardening/002_research_cn.md.
1302
+ // Route DeepSeek's own catalog bundle so routed rebuilds restore the official
1303
+ // context window from the vendored model-metadata bundle instead of falling
1304
+ // back to the 128k strict-fields default (scripts/model-metadata.source.json,
1305
+ // verified 2026-08-08).
1306
+ jawcodeBundle: "deepseek",
1307
+ // deepseek-chat/deepseek-reasoner were deprecated upstream on 2026-07-24 15:59 UTC;
1308
+ // official identifiers are now deepseek-v4-flash / deepseek-v4-pro. They stay in
1309
+ // the list only as compatibility aliases so existing saved configs and requests
1310
+ // keep validating and routing (they previously mapped to v4-flash; devlog
1311
+ // _fin/260710_provider_hardening/002_research_cn.md). The current offerings are
1312
+ // the V4 ids — defaultModel and the model-specific wiring above use them.
1304
1313
  models: ["deepseek-chat", "deepseek-reasoner", ...DEEPSEEK_THINKING_MODELS],
1305
1314
  defaultModel: "deepseek-v4-flash",
1306
- modelContextWindows: { "deepseek-v4-flash": 1_000_000, "deepseek-v4-pro": 1_000_000 },
1315
+ // Official DeepSeek Codex setup (codex-deepseek-setup.sh) advertises 1,048,576
1316
+ // for both V4 models; the older 1,000,000 figure was a rounded approximation.
1317
+ modelContextWindows: { "deepseek-v4-flash": 1_048_576, "deepseek-v4-pro": 1_048_576 },
1307
1318
  // DeepSeek documents V4-Flash as a native Responses API model adapted for Codex. The
1308
1319
  // API id is `deepseek-v4-flash`; `DeepSeek-V4-Flash-0731` is a release/version label.
1309
1320
  modelWireDefaults: {
@@ -56,12 +56,19 @@ export function slugEquals(stored: string, provider: string, id: string): boolea
56
56
  return stored === `${provider}/${id}` || stored === routedSlug(provider, id);
57
57
  }
58
58
 
59
+ /** Stable key for the exact equivalence relation used by catalog/config slug matching. */
60
+ export function slugEquivalenceKey(slug: string): string {
61
+ const slash = slug.indexOf("/");
62
+ return slash <= 0
63
+ ? JSON.stringify(["exact", slug])
64
+ : JSON.stringify([
65
+ "routed",
66
+ slug.slice(0, slash),
67
+ encodeRoutedModelId(slug.slice(slash + 1)),
68
+ ]);
69
+ }
70
+
59
71
  /** Equivalence between two routed slugs regardless of raw/encoded mix. */
60
72
  export function slugsEquivalent(a: string, b: string): boolean {
61
- if (a === b) return true;
62
- const pa = a.indexOf("/");
63
- const pb = b.indexOf("/");
64
- if (pa <= 0 || pb <= 0) return false;
65
- if (a.slice(0, pa) !== b.slice(0, pb)) return false;
66
- return encodeRoutedModelId(a.slice(pa + 1)) === encodeRoutedModelId(b.slice(pb + 1));
73
+ return a === b || slugEquivalenceKey(a) === slugEquivalenceKey(b);
67
74
  }
@@ -19,6 +19,28 @@ export function isCodexReasoningEffort(effort: string): boolean {
19
19
  return CODEX_REASONING_SET.has(effort);
20
20
  }
21
21
 
22
+ /**
23
+ * Reasoning ladder accepted for the OpenAI vision sidecar. `ultra` is deliberately excluded:
24
+ * the vision describer is a single helper call, and `ultra` would be collapsed to `max` by the
25
+ * upstream client boundary anyway.
26
+ */
27
+ export const VISION_REASONING_EFFORTS = ["low", "medium", "high", "xhigh", "max"] as const;
28
+ export type VisionReasoningEffort = typeof VISION_REASONING_EFFORTS[number];
29
+
30
+ /** True when `effort` is one of the vision sidecar's supported Responses reasoning levels. */
31
+ export function isVisionReasoningEffort(effort: unknown): effort is VisionReasoningEffort {
32
+ return typeof effort === "string" && (VISION_REASONING_EFFORTS as readonly string[]).includes(effort);
33
+ }
34
+
35
+ /**
36
+ * Normalize a persisted/configured vision reasoning value. Invalid values (hand-edited config,
37
+ * stale files) degrade to `undefined` so the caller falls back to the documented default instead
38
+ * of forwarding an upstream-rejected effort.
39
+ */
40
+ export function sanitizeVisionReasoning(effort: unknown): VisionReasoningEffort | undefined {
41
+ return isVisionReasoningEffort(effort) ? effort : undefined;
42
+ }
43
+
22
44
  /** Position of `effort` in the Codex ladder (low=0 .. ultra=5), or -1 when not a ladder member. */
23
45
  export function codexEffortRank(effort: string): number {
24
46
  return CODEX_REASONING_ORDER.indexOf(effort);
package/src/router.ts CHANGED
@@ -22,8 +22,6 @@ import {
22
22
  import { decodeRoutedModelId, encodeRoutedModelId } from "./providers/slug-codec";
23
23
  import { getStaleCached } from "./codex/model-cache";
24
24
  import { codexAccountNamespaceEntries } from "./codex/account-namespaces";
25
- import { getEffectiveActiveCodexAccountId } from "./codex/routing";
26
- import { getAccountSet } from "./oauth/store";
27
25
  import {
28
26
  buildRouteDecisionTrace,
29
27
  type RouteDecisionKind,
@@ -513,24 +511,6 @@ function routeModelInternal(
513
511
  quota: quotaEvidenceForCandidate({
514
512
  provider: candidate.provider,
515
513
  model: candidate.model,
516
- ...(candidate.provider === OPENAI_CODEX_PROVIDER_ID
517
- && providerCodexAccountMode(
518
- OPENAI_CODEX_PROVIDER_ID,
519
- config.providers[OPENAI_CODEX_PROVIDER_ID],
520
- ) === "pool"
521
- ? (() => {
522
- const codexAccountId = getEffectiveActiveCodexAccountId(config);
523
- return {
524
- codexAccountId,
525
- codexAccountPlan: codexAccountId
526
- ? config.codexAccounts?.find(account => account.id === codexAccountId)?.plan
527
- : undefined,
528
- };
529
- })()
530
- : {}),
531
- accountRef: candidate.provider === "anthropic"
532
- ? getAccountSet("anthropic")?.activeAccountId
533
- : undefined,
534
514
  }),
535
515
  cost: costEvidenceForCandidate({
536
516
  provider: candidate.provider,
@@ -76,6 +76,11 @@ export interface RequestHistoryPage {
76
76
  export const REQUEST_HISTORY_MAX_PAGE_SIZE = 100;
77
77
  export const REQUEST_HISTORY_DEFAULT_PAGE_SIZE = 50;
78
78
  export const REQUEST_HISTORY_INSERT_BATCH = 500;
79
+ export const REQUEST_HISTORY_READ_CHUNK_BYTES = 64 * 1024;
80
+ // The SQLite index is a disposable projection. Complete JSONL records above
81
+ // this bound are omitted from the projection; the canonical usage.jsonl is
82
+ // never truncated or rewritten by the indexer.
83
+ export const REQUEST_HISTORY_MAX_RECORD_BYTES = 1024 * 1024;
79
84
 
80
85
  let db: Database | null = null;
81
86
  let dbPath = "";
@@ -190,24 +195,6 @@ INSERT OR IGNORE INTO requests (
190
195
  ?, ?, ?, ?, ?
191
196
  )`;
192
197
 
193
- function readCompleteTail(fd: number, fromOffset: number, size: number): { text: string; nextOffset: number } {
194
- if (size <= fromOffset) return { text: "", nextOffset: fromOffset };
195
- const length = size - fromOffset;
196
- const buf = Buffer.allocUnsafe(length);
197
- let offset = 0;
198
- while (offset < length) {
199
- const read = readSync(fd, buf, offset, length - offset, fromOffset + offset);
200
- if (read === 0) throw new Error("usage log changed while indexing");
201
- offset += read;
202
- }
203
- const newline = buf.lastIndexOf(0x0a);
204
- if (newline < 0) return { text: "", nextOffset: fromOffset };
205
- return {
206
- text: buf.subarray(0, newline).toString("utf-8"),
207
- nextOffset: fromOffset + newline + 1,
208
- };
209
- }
210
-
211
198
  function parsedEntryFromLine(line: string): PersistedUsageEntry | null {
212
199
  if (!line.trim()) return null;
213
200
  try {
@@ -227,12 +214,11 @@ function parsedEntryFromLine(line: string): PersistedUsageEntry | null {
227
214
  return null;
228
215
  }
229
216
 
230
- function ingestText(dbHandle: Database, text: string): number {
231
- if (!text) return 0;
232
- const lines = text.split(/\r?\n/);
217
+ function ingestSourceTail(dbHandle: Database, path: string, fromOffset: number): number {
233
218
  let inserted = 0;
234
219
  let pending: Array<Array<string | number | null>> = [];
235
220
  const insert = dbHandle.prepare(ROW_INSERT);
221
+ let fd: number | undefined;
236
222
  try {
237
223
  const commitBatch = () => {
238
224
  dbHandle.transaction((rows: Array<Array<string | number | null>>) => {
@@ -243,29 +229,55 @@ function ingestText(dbHandle: Database, text: string): number {
243
229
  })(pending);
244
230
  pending = [];
245
231
  };
246
- for (const line of lines) {
247
- const entry = parsedEntryFromLine(line);
248
- if (!entry) continue;
232
+ const ingestLine = (line: Buffer) => {
233
+ const entry = parsedEntryFromLine(line.toString("utf-8"));
234
+ if (!entry) return;
249
235
  pending.push(extractRow(entry));
250
236
  if (pending.length >= REQUEST_HISTORY_INSERT_BATCH) commitBatch();
251
- }
252
- if (pending.length > 0) commitBatch();
253
- } finally {
254
- // Windows file locks: an unterminated prepared statement keeps the DB
255
- // file busy after close (verified on Bun 1.3.14). Finalize always.
256
- insert.finalize();
257
- }
258
- return inserted;
259
- }
237
+ };
260
238
 
261
- function ingestSourceTail(dbHandle: Database, path: string, fromOffset: number): number {
262
- let fd: number | undefined;
263
- try {
264
239
  fd = openSync(path, "r");
265
240
  const stat = fstatSync(fd);
266
- const { text, nextOffset } = readCompleteTail(fd, fromOffset, Number(stat.size));
267
- if (text.length === 0 && nextOffset === fromOffset) return 0;
268
- const inserted = ingestText(dbHandle, text);
241
+ const size = Number(stat.size);
242
+ const readBuffer = Buffer.allocUnsafe(REQUEST_HISTORY_READ_CHUNK_BYTES);
243
+ let position = fromOffset;
244
+ let nextOffset = fromOffset;
245
+ let fragments: Buffer[] = [];
246
+ let fragmentBytes = 0;
247
+ let oversized = false;
248
+ while (position < size) {
249
+ const requested = Math.min(readBuffer.length, size - position);
250
+ const bytesRead = readSync(fd, readBuffer, 0, requested, position);
251
+ if (bytesRead === 0) throw new Error("usage log changed while indexing");
252
+ let lineStart = 0;
253
+ for (let index = 0; index < bytesRead; index++) {
254
+ if (readBuffer[index] !== 0x0a) continue;
255
+ const segment = readBuffer.subarray(lineStart, index);
256
+ if (!oversized && fragmentBytes + segment.length <= REQUEST_HISTORY_MAX_RECORD_BYTES) {
257
+ const line = fragments.length === 0
258
+ ? segment
259
+ : Buffer.concat([...fragments, segment], fragmentBytes + segment.length);
260
+ ingestLine(line);
261
+ }
262
+ fragments = [];
263
+ fragmentBytes = 0;
264
+ oversized = false;
265
+ lineStart = index + 1;
266
+ nextOffset = position + index + 1;
267
+ }
268
+ const remainder = readBuffer.subarray(lineStart, bytesRead);
269
+ if (!oversized && fragmentBytes + remainder.length <= REQUEST_HISTORY_MAX_RECORD_BYTES) {
270
+ // Copy because readBuffer is reused on the next iteration.
271
+ fragments.push(Buffer.from(remainder));
272
+ fragmentBytes += remainder.length;
273
+ } else if (remainder.length > 0) {
274
+ fragments = [];
275
+ fragmentBytes = 0;
276
+ oversized = true;
277
+ }
278
+ position += bytesRead;
279
+ }
280
+ if (pending.length > 0) commitBatch();
269
281
  const current = readIndexedMeta(dbHandle);
270
282
  setMeta(dbHandle, HISTORY_META_KEYS.indexedOffset, nextOffset);
271
283
  setMeta(dbHandle, HISTORY_META_KEYS.indexedRows, current.indexedRows + inserted);
@@ -275,6 +287,9 @@ function ingestSourceTail(dbHandle: Database, path: string, fromOffset: number):
275
287
  return inserted;
276
288
  } finally {
277
289
  if (fd !== undefined) closeSync(fd);
290
+ // Windows file locks: an unterminated prepared statement keeps the DB
291
+ // file busy after close (verified on Bun 1.3.14). Finalize always.
292
+ insert.finalize();
278
293
  }
279
294
  }
280
295
 
@@ -12,63 +12,100 @@
12
12
  * carries an account reference (dry-run/evaluate), never invented.
13
13
  */
14
14
 
15
- import { codexQuotaWindowForPlan, getAccountQuota, isCodexQuotaExhausted } from "../codex/quota";
15
+ import {
16
+ codexQuotaWindowForPlan,
17
+ getAccountQuota,
18
+ isCodexQuotaExhausted,
19
+ listAccountQuotas,
20
+ } from "../codex/quota";
16
21
  import { getCachedProviderAccountQuota } from "../providers/quota";
17
22
  import type { RouteQuotaEvidence } from "./trace";
18
23
 
19
24
  export interface QuotaEvidenceInput {
20
25
  provider: string;
21
26
  model: string;
22
- /** Opaque account reference for per-account quota sources. */
23
27
  accountRef?: string;
24
- /** Codex pool account id (provider "openai"). */
25
28
  codexAccountId?: string;
26
- /** The account's plan (provider "openai"); selects the governing quota window. */
27
29
  codexAccountPlan?: string;
28
30
  }
29
31
 
30
- /**
31
- * Assemble quota evidence from canonical local caches only (no network).
32
- * Unknown dimensions stay unknown - never zero.
33
- */
32
+ export interface CodexPoolQuotaAccount {
33
+ accountId: string;
34
+ plan?: string;
35
+ }
36
+
37
+ function codexAccountQuotaEvidence(accountId: string, plan?: string): RouteQuotaEvidence {
38
+ const quota = getAccountQuota(accountId);
39
+ if (!quota) return { known: false };
40
+ const monthly = codexQuotaWindowForPlan(plan) === "monthly";
41
+ const percents = [
42
+ ...(monthly ? [] : [quota.weeklyPercent]),
43
+ quota.monthlyPercent,
44
+ ].filter((value): value is number => typeof value === "number" && Number.isFinite(value));
45
+ const maxPercent = percents.length > 0 ? Math.max(...percents) : undefined;
46
+ // Credits-only snapshots prove neither usage nor exhaustion. Unknown must not
47
+ // become healthy capacity merely because a cache row exists.
48
+ if (maxPercent === undefined) return { known: false };
49
+ const resets = [
50
+ ...(monthly ? [] : [quota.weeklyResetAt]),
51
+ quota.monthlyResetAt,
52
+ ].filter((value): value is number => typeof value === "number" && Number.isFinite(value))
53
+ .filter(value => value > Date.now());
54
+ return {
55
+ known: true,
56
+ headroom: Math.max(0, Math.min(1, 1 - maxPercent / 100)),
57
+ exhausted: isCodexQuotaExhausted(quota, plan),
58
+ ...(resets.length > 0 ? { resetAtMs: Math.min(...resets) } : {}),
59
+ source: "codex-pool",
60
+ };
61
+ }
62
+
63
+ export function codexPoolQuotaEvidence(accounts: readonly CodexPoolQuotaAccount[]): RouteQuotaEvidence {
64
+ if (accounts.length === 0) return { known: false };
65
+ const evidence = accounts.map(account => codexAccountQuotaEvidence(account.accountId, account.plan));
66
+ const known = evidence.filter(item => item.known);
67
+ if (known.length === 0) return { known: false };
68
+ const usable = known.filter(item => item.exhausted !== true);
69
+ if (usable.length > 0) {
70
+ const headrooms = usable.map(item => item.headroom)
71
+ .filter((value): value is number => typeof value === "number" && Number.isFinite(value));
72
+ return {
73
+ known: true,
74
+ exhausted: false,
75
+ ...(headrooms.length > 0 ? { headroom: Math.max(...headrooms) } : {}),
76
+ source: "codex-pool",
77
+ };
78
+ }
79
+ if (known.length < evidence.length) return { known: false };
80
+ const resets = known.map(item => item.resetAtMs)
81
+ .filter((value): value is number => typeof value === "number" && Number.isFinite(value));
82
+ return {
83
+ known: true,
84
+ exhausted: true,
85
+ headroom: 0,
86
+ ...(resets.length > 0 ? { resetAtMs: Math.min(...resets) } : {}),
87
+ source: "codex-pool",
88
+ };
89
+ }
90
+
34
91
  export function quotaEvidenceForCandidate(input: QuotaEvidenceInput): RouteQuotaEvidence {
35
92
  if (input.provider === "openai" && input.codexAccountId) {
36
- const quota = getAccountQuota(input.codexAccountId);
37
- if (quota) {
38
- // Go/Free accounts report a 30-day window only; weekly windows gate
39
- // everything else. `codexQuotaWindowForPlan` is the single shared rule
40
- // (parser, exhaustion, recovery), so select the plan-specific bars here
41
- // too instead of always combining weekly + monthly.
42
- const monthly = codexQuotaWindowForPlan(input.codexAccountPlan) === "monthly";
43
- const percents = [
44
- ...(monthly ? [] : [quota.weeklyPercent]),
45
- quota.monthlyPercent,
46
- ].filter((value): value is number => typeof value === "number" && Number.isFinite(value));
47
- const maxPercent = percents.length > 0 ? Math.max(...percents) : undefined;
48
- const resets = [
49
- ...(monthly ? [] : [quota.weeklyResetAt]),
50
- quota.monthlyResetAt,
51
- ].filter((value): value is number => typeof value === "number" && Number.isFinite(value))
52
- .filter(value => value > Date.now());
53
- return {
54
- known: true,
55
- ...(maxPercent !== undefined
56
- ? { headroom: Math.max(0, Math.min(1, 1 - maxPercent / 100)) }
57
- : {}),
58
- exhausted: isCodexQuotaExhausted(quota, input.codexAccountPlan),
59
- ...(resets.length > 0 ? { resetAtMs: Math.min(...resets) } : {}),
60
- source: "codex-pool",
61
- };
93
+ // listAccountQuotas() is the reconciled quota snapshot: config-generation
94
+ // reconciliation prunes removed accounts. Other eligibility dimensions
95
+ // (pause/reauth/cooldown/soft-avoid) remain health/pool-selector concerns.
96
+ if (input.codexAccountPlan !== undefined) {
97
+ const pool = [...listAccountQuotas()].map(([accountId]) => ({
98
+ accountId,
99
+ ...(accountId === input.codexAccountId ? { plan: input.codexAccountPlan } : {}),
100
+ }));
101
+ if (pool.length > 1) return codexPoolQuotaEvidence(pool);
62
102
  }
103
+ return codexAccountQuotaEvidence(input.codexAccountId, input.codexAccountPlan);
63
104
  }
64
105
 
65
106
  if (input.provider === "anthropic" && input.accountRef) {
66
107
  const quota = getCachedProviderAccountQuota("anthropic", input.accountRef);
67
108
  if (quota) {
68
- // Anthropic per-family buckets (e.g. Opus / Sonnet) are stricter than the
69
- // broad account windows: fold the candidate model's matching bucket into
70
- // headroom and exhaustion so a model-specific overage is not hidden by
71
- // a healthy aggregate window.
72
109
  const family = anthropicFamilyWindow(input.model, quota.customWindows ?? []);
73
110
  const percents = [quota.fiveHourPercent, quota.weeklyPercent, quota.monthlyPercent, family?.percent]
74
111
  .filter((value): value is number => typeof value === "number" && Number.isFinite(value));
@@ -78,25 +115,16 @@ export function quotaEvidenceForCandidate(input: QuotaEvidenceInput): RouteQuota
78
115
  .filter(value => value > Date.now());
79
116
  return {
80
117
  known: true,
81
- ...(maxPercent !== undefined
82
- ? { headroom: Math.max(0, Math.min(1, 1 - maxPercent / 100)) }
83
- : {}),
118
+ ...(maxPercent !== undefined ? { headroom: Math.max(0, Math.min(1, 1 - maxPercent / 100)) } : {}),
84
119
  exhausted: maxPercent !== undefined && maxPercent >= 100,
85
120
  ...(resets.length > 0 ? { resetAtMs: Math.min(...resets) } : {}),
86
121
  source: "provider-report",
87
122
  };
88
123
  }
89
124
  }
90
-
91
125
  return { known: false };
92
126
  }
93
127
 
94
- /**
95
- * Match the candidate model to an Anthropic per-family quota window. Window
96
- * labels from the provider probe are "Opus" / "Sonnet"; model ids carry the
97
- * family as a segment (e.g. `claude-opus-...`, `claude-sonnet-...`). Returns
98
- * undefined when no family window is cached or no label matches.
99
- */
100
128
  function anthropicFamilyWindow(
101
129
  model: string,
102
130
  windows: Array<{ label: string; percent?: number; resetAt?: number }>,
@@ -104,18 +132,11 @@ function anthropicFamilyWindow(
104
132
  const normalized = model.toLowerCase();
105
133
  for (const window of windows) {
106
134
  const family = window.label.trim().toLowerCase();
107
- if (family && normalized.includes(family)) {
108
- return window;
109
- }
135
+ if (family && normalized.includes(family)) return window;
110
136
  }
111
137
  return undefined;
112
138
  }
113
139
 
114
- /**
115
- * Deterministic quota score in [0,1]: larger available headroom scores
116
- * higher; exhausted evidence scores 0. Unknown evidence returns null so the
117
- * caller can apply the profile's unknownEvidence policy.
118
- */
119
140
  export function quotaScore(evidence: RouteQuotaEvidence | undefined): number | null {
120
141
  if (!evidence || !evidence.known) return null;
121
142
  if (evidence.exhausted === true) return 0;
@@ -204,6 +204,22 @@ function releaseLiveSidebandAdmission(ws: ServerWebSocket<WsData>): void {
204
204
  ws.data.liveTurnAdmissionLease = undefined;
205
205
  }
206
206
 
207
+ /**
208
+ * Send one live-sideband frame to the upstream socket.
209
+ *
210
+ * Bun's `WebSocket.send` accepts `string | Blob | BufferSource`, but the DOM-lib
211
+ * `Buffer` can be backed by a `SharedArrayBuffer`, which `BufferSource` rejects.
212
+ * `Uint8Array.from` copies into a fresh `ArrayBuffer`-backed view, so a frame
213
+ * arriving from `node:buffer` still round-trips byte-for-byte.
214
+ */
215
+ function sendUpstreamFrame(upstream: WebSocket, frame: string | Buffer): void {
216
+ if (typeof frame === "string") {
217
+ upstream.send(frame);
218
+ return;
219
+ }
220
+ upstream.send(Uint8Array.from(frame));
221
+ }
222
+
207
223
  function finalizeLiveSideband(ws: ServerWebSocket<WsData>, upstream?: WebSocket): void {
208
224
  if (upstream && ws.data.liveUpstream !== upstream) return;
209
225
  if (ws.data.liveCloseFallback !== undefined) {
@@ -236,7 +252,10 @@ function armLiveSidebandCloseFallback(ws: ServerWebSocket<WsData>, upstream: Web
236
252
  // close event. That is still an observed CLOSED transport and is safe to
237
253
  // finalize. CONNECTING/CLOSING peers keep the lease so profile switching
238
254
  // fails at its own bounded drain deadline instead of racing live traffic.
239
- if (upstream.readyState === WebSocket.CLOSED) finalizeLiveSideband(ws, upstream);
255
+ // The earlier CLOSED check narrowed `readyState` to 0|1|2 in the type
256
+ // system, but the socket can still transition to CLOSED (3) before this
257
+ // fallback fires; the cast keeps the runtime-identical check.
258
+ if ((upstream.readyState as number) === 3) finalizeLiveSideband(ws, upstream);
240
259
  }, LIVE_SIDEBAND_CLOSE_FALLBACK_MS);
241
260
  }
242
261
 
@@ -246,7 +265,9 @@ function closeLiveSideband(ws: ServerWebSocket<WsData>, code = 1000, reason = ""
246
265
  ws.data.livePending = undefined;
247
266
  ws.data.cancel = undefined;
248
267
  const upstream = ws.data.liveUpstream;
249
- if (!upstream || upstream.readyState === WebSocket.CLOSED) {
268
+ // Bun's `WebSocket` type narrows `readyState` to 0|1|2 even though the DOM
269
+ // constant CLOSED is 3; the numeric literal is the runtime-identical check.
270
+ if (!upstream || upstream.readyState === 3) {
250
271
  finalizeLiveSideband(ws, upstream);
251
272
  } else {
252
273
  // The sideband holds a native-main admission lease. Do not release it just
@@ -299,7 +320,7 @@ function attachLiveSidebandUpstream(
299
320
  ws.data.livePending = undefined;
300
321
  for (const frame of pending) {
301
322
  try {
302
- upstream.send(frame);
323
+ sendUpstreamFrame(upstream, frame);
303
324
  } catch {
304
325
  closeLiveSideband(ws, 1011, "upstream send failed");
305
326
  return;
@@ -794,12 +815,14 @@ export function startServer(port?: number, deps: StartServerDeps = {}): Server<W
794
815
  }
795
816
  throw error;
796
817
  }
797
- const { applyNativeVisibility, buildCatalogEntries, disabledNativeSlugs, exactComboCatalogSlugs, loadCatalogTemplate, NATIVE_OPENAI_MODELS, nativeOpenAiSlugs, nativeReasoningEfforts, nativeDefaultReasoningEffort, orderForSubagents, filterCatalogVisibleModels, shouldIncludeAccountBoundNativeOpenAi, shouldIncludeNativeOpenAi, uniqueCatalogModelsForRawPublicList, visibleCodexAccountSelectors, visibleNativeSlugs, desktopVisibleNativeSlugs } = await import("../codex/catalog");
818
+ const { applyNativeVisibility, buildCatalogEntries, configuredNativeAliasSlugs, desktopAllowlistSuppressedNativeSlugs, disabledNativeSlugs, exactComboCatalogSlugs, loadCatalogTemplate, NATIVE_OPENAI_MODELS, nativeOpenAiSlugs, nativeReasoningEfforts, nativeDefaultReasoningEffort, orderForSubagents, filterCatalogVisibleModels, shouldIncludeAccountBoundNativeOpenAi, shouldIncludeNativeOpenAi, uniqueCatalogModelsForRawPublicList, visibleCodexAccountSelectors, visibleNativeSlugs, desktopVisibleNativeSlugs } = await import("../codex/catalog");
798
819
  const includeNativeOpenAi = shouldIncludeNativeOpenAi(config);
799
820
  const includeAccountBoundNativeOpenAi = shouldIncludeAccountBoundNativeOpenAi(config);
800
821
  const nativeSlugs = includeNativeOpenAi ? nativeOpenAiSlugs() : [];
801
822
  const disabledNatives = disabledNativeSlugs(config);
802
823
  const disabledModels = new Set(config.disabledModels ?? []);
824
+ const shadowedNativeSlugs = configuredNativeAliasSlugs(config);
825
+ const suppressedBareNativeSlugs = desktopAllowlistSuppressedNativeSlugs(config);
803
826
  const accountSelectors = includeAccountBoundNativeOpenAi
804
827
  ? visibleCodexAccountSelectors(config)
805
828
  : [];
@@ -852,7 +875,7 @@ export function startServer(port?: number, deps: StartServerDeps = {}): Server<W
852
875
  const catalogNativeSlugs = accountSelectors.length > 0
853
876
  ? NATIVE_OPENAI_MODELS
854
877
  : nativeSlugs;
855
- const entries = buildCatalogEntries(loadCatalogTemplate(), catalogNativeSlugs, goOrdered, config.subagentModels, websocketsEnabled(config), maMode as "v1" | "default" | "v2", exactComboCatalogSlugs(config), accountSelectors);
878
+ const entries = buildCatalogEntries(loadCatalogTemplate(), catalogNativeSlugs, goOrdered, config.subagentModels, websocketsEnabled(config), maMode as "v1" | "default" | "v2", exactComboCatalogSlugs(config), accountSelectors, suppressedBareNativeSlugs);
856
879
  return jsonResponse({
857
880
  models: applyNativeVisibility(
858
881
  entries,
@@ -904,7 +927,9 @@ export function startServer(port?: number, deps: StartServerDeps = {}): Server<W
904
927
  ? NATIVE_OPENAI_MODELS.filter(slug => !disabledNatives.has(slug))
905
928
  : [];
906
929
  const visibleNatives = includeNativeOpenAi
907
- ? accountSelectors.length > 0 ? selectorNativeSlugs : visibleNativeSlugs(config)
930
+ ? accountSelectors.length > 0
931
+ ? selectorNativeSlugs.filter(slug => !shadowedNativeSlugs.has(slug))
932
+ : visibleNativeSlugs(config)
908
933
  : [];
909
934
  const visibleAccountNatives = accountSelectors.flatMap(selector =>
910
935
  selectorNativeSlugs.flatMap(metadataId => {
@@ -1312,7 +1337,7 @@ export function startServer(port?: number, deps: StartServerDeps = {}): Server<W
1312
1337
  return;
1313
1338
  }
1314
1339
  try {
1315
- upstream.send(raw);
1340
+ sendUpstreamFrame(upstream, raw);
1316
1341
  } catch {
1317
1342
  closeLiveSideband(ws, 1011, "upstream send failed");
1318
1343
  }