@bitkyc08/opencodex 2.40.0 → 2.42.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 (97) hide show
  1. package/README.md +4 -0
  2. package/gui/dist/assets/index-BU1tE0sr.js +112 -0
  3. package/gui/dist/assets/index-DL9-iS6J.css +1 -0
  4. package/gui/dist/index.html +2 -2
  5. package/gui/dist/provider-icons/meta.svg +1 -0
  6. package/package.json +4 -3
  7. package/src/adapters/cursor/catalog.ts +71 -29
  8. package/src/adapters/cursor/claude-id.ts +76 -0
  9. package/src/adapters/cursor/discovery.ts +16 -3
  10. package/src/adapters/cursor/effort-map.ts +27 -12
  11. package/src/adapters/cursor/protobuf-request.ts +41 -21
  12. package/src/adapters/google.ts +39 -2
  13. package/src/adapters/identity.ts +8 -2
  14. package/src/adapters/openai-responses.ts +57 -4
  15. package/src/bridge.ts +25 -3
  16. package/src/cli/account-auth.ts +28 -3
  17. package/src/cli/account-extended.ts +7 -1
  18. package/src/cli/capabilities.ts +2 -2
  19. package/src/cli/claude.ts +11 -2
  20. package/src/cli/connect.ts +7 -1
  21. package/src/cli/observe.ts +3 -1
  22. package/src/cli/registry.ts +1 -1
  23. package/src/cli/status.ts +19 -4
  24. package/src/client/connect.ts +5 -1
  25. package/src/client/hub-client.ts +29 -5
  26. package/src/clients/config-export.ts +12 -2
  27. package/src/codex/auth-api.ts +102 -9
  28. package/src/codex/catalog/aggregation.ts +8 -0
  29. package/src/codex/catalog/effort.ts +15 -2
  30. package/src/codex/catalog/metadata.ts +119 -9
  31. package/src/codex/catalog/native-models.ts +71 -0
  32. package/src/codex/catalog/parsing.ts +5 -3
  33. package/src/codex/catalog/provider-fetch.ts +166 -28
  34. package/src/codex/catalog.ts +1 -1
  35. package/src/codex/convergence-types.ts +1 -0
  36. package/src/codex/data/upstream-models.json +169 -0
  37. package/src/codex/desired-state.ts +18 -11
  38. package/src/codex/inject.ts +96 -6
  39. package/src/codex/injected-marker.ts +30 -4
  40. package/src/codex/journal.ts +14 -0
  41. package/src/combos/failover.ts +185 -6
  42. package/src/combos/index.ts +6 -0
  43. package/src/combos/resolve.ts +43 -6
  44. package/src/config.ts +5 -1
  45. package/src/generated/compatibility-version.json +115 -83
  46. package/src/generated/model-metadata.ts +1 -1
  47. package/src/grok/sync.ts +10 -2
  48. package/src/integrations/cursor-effort-table.ts +143 -0
  49. package/src/integrations/state.ts +1 -1
  50. package/src/integrations/writer.ts +2 -2
  51. package/src/lib/app-owned-memory-stores.ts +27 -8
  52. package/src/lib/bounded-body.ts +16 -1
  53. package/src/oauth/account-quota-rank.ts +40 -1
  54. package/src/oauth/chatgpt-device.ts +187 -0
  55. package/src/oauth/chatgpt.ts +31 -4
  56. package/src/oauth/generic-account-failover.ts +2 -2
  57. package/src/oauth/index.ts +24 -3
  58. package/src/oauth/log.ts +3 -0
  59. package/src/oauth/meta-muse.ts +235 -0
  60. package/src/providers/antigravity-models.ts +71 -13
  61. package/src/providers/command-code-efforts.ts +15 -0
  62. package/src/providers/free-directory.ts +4 -1
  63. package/src/providers/muse-subscription-usage.ts +95 -0
  64. package/src/providers/quota.ts +96 -0
  65. package/src/providers/registry.ts +116 -8
  66. package/src/responses/code-mode-helper-compat.ts +4 -1
  67. package/src/responses/state.ts +5 -4
  68. package/src/server/auth-cors.ts +241 -56
  69. package/src/server/chat-completions.ts +11 -2
  70. package/src/server/chat-native.ts +30 -4
  71. package/src/server/claude-messages.ts +17 -3
  72. package/src/server/effort-row.ts +131 -0
  73. package/src/server/index.ts +82 -45
  74. package/src/server/live.ts +18 -4
  75. package/src/server/management/api-key-rotation.ts +2 -1
  76. package/src/server/management/api-key-usage.ts +97 -43
  77. package/src/server/management/context.ts +3 -0
  78. package/src/server/management/cursor-integration-routes.ts +36 -7
  79. package/src/server/management/logs-usage-routes.ts +64 -87
  80. package/src/server/management/oauth-account-routes.ts +10 -3
  81. package/src/server/management/provider-routes.ts +218 -1
  82. package/src/server/management/route-registry.ts +1 -0
  83. package/src/server/management/usage-aggregate-cache.ts +464 -0
  84. package/src/server/management/usage-summary-cache.ts +4 -0
  85. package/src/server/models-capabilities.ts +60 -5
  86. package/src/server/responses/core.ts +95 -7
  87. package/src/server/responses/empty-completion-guard.ts +4 -0
  88. package/src/types/config.ts +10 -1
  89. package/src/types/request.ts +8 -0
  90. package/src/types/tools.ts +12 -9
  91. package/src/usage/expected-prices.ts +43 -7
  92. package/src/usage/ledger-scanner.ts +448 -0
  93. package/src/usage/log.ts +1 -1
  94. package/src/usage/summary.ts +915 -655
  95. package/src/web-search/index.ts +1 -1
  96. package/gui/dist/assets/index-BHe2rl_C.js +0 -112
  97. package/gui/dist/assets/index-CJSb3HPe.css +0 -1
@@ -49,7 +49,7 @@ import { routedSlug, slugEquals, slugEquivalenceKey, slugsEquivalent } from "../
49
49
  import { CODEX_GPT5_IDENTITY_LINE } from "../../adapters/identity";
50
50
  import { filterCursorConfiguredModelsByLiveDiscovery } from "../../adapters/cursor/discovery";
51
51
  import { fetchCursorUsableModels } from "../../adapters/cursor/live-models";
52
- import { recordLiveCursorMaxModeModels } from "../../adapters/cursor/catalog";
52
+ import { recordLiveCursorClaudeModels, recordLiveCursorMaxModeModels } from "../../adapters/cursor/catalog";
53
53
  import { isCanonicalOpenAiForwardProvider, OPENAI_API_PROVIDER_ID, OPENAI_CODEX_PROVIDER_ID } from "../../providers/openai-tiers";
54
54
  import {
55
55
  COMBO_NAMESPACE,
@@ -81,7 +81,7 @@ import { createAdmissionGate, ResourceAdmissionError, type AdmissionMetrics } fr
81
81
 
82
82
  import { CODEX_CUSTOM_MODEL_CATALOG_KIND, JAWCODE_CATALOG_AUGMENT_PROVIDERS, catalogModelSlug, shouldExposeRoutedModel } from "./parsing";
83
83
  import type { CatalogModel } from "./parsing";
84
- import { disabledNativeSlugs, hasComboTargets, isNativeOpenAiCapabilityAliasModel, NATIVE_GPT56_MAX_INPUT_TOKENS, nativeContextLimits, nativeDefaultReasoningEffort, nativeInputModalities, nativeOpenAiAutoCompactTokenLimit, nativeOpenAiContextWindow, nativeOpenAiMaxInputTokens, nativeOpenAiSlugs, nativeParallelToolCalls, nativeReasoningEfforts } from "./metadata";
84
+ import { disabledNativeSlugs, hasComboTargets, hasNativeOpenAiCapabilityMetadata, NATIVE_GPT56_MAX_INPUT_TOKENS, nativeContextLimits, nativeOpenAiCapabilityDisplayName, nativeDefaultReasoningEffort, nativeInputModalities, nativeOpenAiAutoCompactTokenLimit, nativeOpenAiContextWindow, nativeOpenAiMaxInputTokens, nativeOpenAiMaxOutputTokens, nativeOpenAiSlugs, nativeParallelToolCalls, nativeReasoningEfforts } from "./metadata";
85
85
  import { deriveComboCatalogModel, normalizedOpenAiApiSignature, openAiApiCollisionWarnings, replaceLastComboCatalogOmissions, warnUncataloguedComboOnce } from "./aggregation";
86
86
  import type { ComboCatalogOmission } from "./aggregation";
87
87
  import type { CatalogGatherProviderAuthEvidence } from "./filesystem-evidence";
@@ -163,6 +163,7 @@ interface CapturedProviderGather {
163
163
  readonly policy: CatalogProviderDiscoveryPolicySnapshot;
164
164
  readonly request: CapturedModelsRequest;
165
165
  readonly fastPolicyAuthority: FastPolicyAuthority;
166
+ readonly metadataModelIdCaseFold: boolean;
166
167
  readonly observedAuth?: ModelsAuthResolution;
167
168
  /**
168
169
  * Configured model ids this provider must keep even when live discovery omits
@@ -379,6 +380,7 @@ function captureTrustedOpenAiApiPolicy(
379
380
  models: entry.models,
380
381
  ...(entry.modelContextWindows ? { modelContextWindows: entry.modelContextWindows } : {}),
381
382
  ...(entry.modelMaxInputTokens ? { modelMaxInputTokens: entry.modelMaxInputTokens } : {}),
383
+ ...(entry.virtualModels ? { virtualModels: entry.virtualModels } : {}),
382
384
  ...(entry.modelInputModalities ? { modelInputModalities: entry.modelInputModalities } : {}),
383
385
  ...(entry.modelReasoningEfforts ? { modelReasoningEfforts: entry.modelReasoningEfforts } : {}),
384
386
  });
@@ -422,6 +424,7 @@ function captureProviderGather(
422
424
  registryTransportMatch,
423
425
  configured,
424
426
  );
427
+ const metadataModelIdCaseFold = shouldCaseFoldMetadataModelId(name);
425
428
  const observedAuth = authResolver.kind === "observed"
426
429
  && provider.authMode !== "forward"
427
430
  && provider.liveModels !== false
@@ -458,6 +461,7 @@ function captureProviderGather(
458
461
  policy,
459
462
  request,
460
463
  fastPolicyAuthority,
464
+ metadataModelIdCaseFold,
461
465
  ...(observedAuth ? { observedAuth: Object.freeze({ ...observedAuth }) } : {}),
462
466
  ...(retainConfiguredModelIds && retainConfiguredModelIds.size > 0
463
467
  ? { retainConfiguredModelIds }
@@ -579,6 +583,7 @@ function providerCatalogFingerprint(name: string, prov: OcxProviderConfig): Reco
579
583
  ctx: prov.contextWindow ?? null,
580
584
  ctxW: prov.modelContextWindows ?? null,
581
585
  maxIn: prov.modelMaxInputTokens ?? null,
586
+ maxOut: prov.modelMaxOutputTokens ?? null,
582
587
  autoCompact: prov.modelAutoCompactTokenLimits ?? null,
583
588
  inMod: prov.modelInputModalities ?? null,
584
589
  re: prov.modelReasoningEfforts ?? null,
@@ -645,6 +650,44 @@ export function configuredMaxInputTokens(prov: OcxProviderConfig, id: string): n
645
650
  return typeof configured === "number" && configured > 0 ? configured : undefined;
646
651
  }
647
652
 
653
+ function generatedMaxOutputTokens(
654
+ providerName: string,
655
+ id: string,
656
+ metadataId = id,
657
+ metadataModelIdCaseFold?: boolean,
658
+ ): number | undefined {
659
+ const metadataProvider = providerName === OPENAI_API_PROVIDER_ID || providerName === OPENAI_CODEX_PROVIDER_ID
660
+ ? "openai"
661
+ : resolveMetadataProvider(providerName);
662
+ if (!metadataProvider) return undefined;
663
+ const metadata = getModelMetadata(metadataProvider, metadataId)
664
+ ?? ((metadataModelIdCaseFold ?? (providerName === OPENAI_API_PROVIDER_ID || providerName === OPENAI_CODEX_PROVIDER_ID
665
+ ? false
666
+ : shouldCaseFoldMetadataModelId(providerName)))
667
+ ? getModelMetadataCaseInsensitive(metadataProvider, metadataId)
668
+ : undefined);
669
+ return positiveSafeInteger(metadata?.maxTokens);
670
+ }
671
+
672
+ function routedMaxOutputTokens(
673
+ providerName: string,
674
+ provider: OcxProviderConfig,
675
+ model: CatalogModel,
676
+ metadataId = model.id,
677
+ metadataModelIdCaseFold?: boolean,
678
+ ): number | undefined {
679
+ const discovered = positiveSafeInteger(model.maxOutputTokens);
680
+ const generated = generatedMaxOutputTokens(providerName, model.id, metadataId, metadataModelIdCaseFold);
681
+ const configured = positiveSafeInteger(
682
+ modelRecordValue(provider.modelMaxOutputTokens, model.id),
683
+ );
684
+ const authoritative = discovered ?? generated;
685
+ if (configured === undefined) return authoritative;
686
+ return authoritative === undefined
687
+ ? configured
688
+ : Math.min(authoritative, configured);
689
+ }
690
+
648
691
  export function configuredAutoCompactTokenLimit(
649
692
  prov: OcxProviderConfig | undefined,
650
693
  id: string,
@@ -679,10 +722,17 @@ function configuredVerbositySupport(name: string, prov: OcxProviderConfig | unde
679
722
  return prov.supportsVerbosity;
680
723
  }
681
724
 
682
- export function applyProviderConfigHints(name: string, prov: OcxProviderConfig, model: CatalogModel, providerCap?: number): CatalogModel {
725
+ export function applyProviderConfigHints(
726
+ name: string,
727
+ prov: OcxProviderConfig,
728
+ model: CatalogModel,
729
+ providerCap?: number,
730
+ metadataModelIdCaseFold?: boolean,
731
+ ): CatalogModel {
683
732
  const displayName = configuredModelDisplayName(prov, model.id);
684
733
  const configuredCap = configuredContextWindow(prov, model.id);
685
734
  const configuredMaxInput = configuredMaxInputTokens(prov, model.id);
735
+ const maxOutputTokens = routedMaxOutputTokens(name, prov, model, model.id, metadataModelIdCaseFold);
686
736
  const configuredAutoCompact = configuredAutoCompactTokenLimit(prov, model.id);
687
737
  let inputModalities = configuredInputModalities(prov, model.id);
688
738
  // The shared vision-sidecar consumer predicate keeps catalog advertisement and request-time
@@ -727,6 +777,7 @@ export function applyProviderConfigHints(name: string, prov: OcxProviderConfig,
727
777
  : configuredMaxInput,
728
778
  }
729
779
  : {}),
780
+ ...(maxOutputTokens !== undefined ? { maxOutputTokens } : {}),
730
781
  ...(defaultReasoningEffort ? { defaultReasoningEffort } : {}),
731
782
  ...(typeof supportsReasoningSummaries === "boolean" ? { supportsReasoningSummaries } : {}),
732
783
  ...(typeof supportsVerbosity === "boolean" ? { supportsVerbosity } : {}),
@@ -769,14 +820,26 @@ export function applyProviderConfigHints(name: string, prov: OcxProviderConfig,
769
820
  };
770
821
  }
771
822
 
772
- export function catalogHintsFromProviderConfig(name: string, prov: OcxProviderConfig, id: string, contextCap?: number): Partial<CatalogModel> {
773
- const hinted = applyProviderConfigHints(name, prov, { id, provider: name }, contextCap);
823
+ export function catalogHintsFromProviderConfig(
824
+ name: string,
825
+ prov: OcxProviderConfig,
826
+ id: string,
827
+ contextCap?: number,
828
+ metadataModelIdCaseFold?: boolean,
829
+ ): Partial<CatalogModel> {
830
+ const hinted = applyProviderConfigHints(name, prov, { id, provider: name }, contextCap, metadataModelIdCaseFold);
774
831
  const { provider: _provider, id: _id, ...hints } = hinted;
775
832
  return hints;
776
833
  }
777
834
 
778
- export function applyConfigHintsToCachedModels(name: string, prov: OcxProviderConfig, models: CatalogModel[], contextCap?: number): CatalogModel[] {
779
- return models.map(model => applyProviderConfigHints(name, prov, model, contextCap));
835
+ export function applyConfigHintsToCachedModels(
836
+ name: string,
837
+ prov: OcxProviderConfig,
838
+ models: CatalogModel[],
839
+ contextCap?: number,
840
+ metadataModelIdCaseFold?: boolean,
841
+ ): CatalogModel[] {
842
+ return models.map(model => applyProviderConfigHints(name, prov, model, contextCap, metadataModelIdCaseFold));
780
843
  }
781
844
 
782
845
 
@@ -793,6 +856,7 @@ interface ComboCatalogMemberFallback {
793
856
  readonly contextWindow?: number;
794
857
  /** Input ceiling when it is lower than the window (native GPT-5.6: 922k under 1.05M). */
795
858
  readonly maxInputTokens?: number;
859
+ readonly maxOutputTokens?: number;
796
860
  readonly autoCompactTokenLimit?: number;
797
861
  readonly inputModalities?: readonly string[];
798
862
  readonly reasoningEfforts?: readonly string[];
@@ -814,6 +878,7 @@ export function resolveComboCatalogMember(
814
878
  providers: ReadonlyMap<string, OcxProviderConfig>,
815
879
  contextCap?: number,
816
880
  fallback?: ComboCatalogMemberFallback,
881
+ metadataModelIdCaseFold?: boolean,
817
882
  ): CatalogModel | undefined {
818
883
  const existing = memberByKey.get(targetKey(target));
819
884
  const prov = providers.get(target.provider);
@@ -827,6 +892,10 @@ export function resolveComboCatalogMember(
827
892
  : undefined;
828
893
  const addMaxInput = fallback !== undefined && contextWindow !== undefined
829
894
  && !(typeof member.maxInputTokens === "number" && member.maxInputTokens > 0);
895
+ const addMaxOutput = fallback !== undefined
896
+ && typeof fallback.maxOutputTokens === "number"
897
+ && fallback.maxOutputTokens > 0
898
+ && !(typeof member.maxOutputTokens === "number" && member.maxOutputTokens > 0);
830
899
  const effectiveMaxInput = addMaxInput
831
900
  ? Math.min(fallback?.maxInputTokens ?? contextWindow!, contextWindow!)
832
901
  : member.maxInputTokens;
@@ -840,12 +909,13 @@ export function resolveComboCatalogMember(
840
909
  && fallback?.inputModalities !== undefined;
841
910
  const addReasoning = member.reasoningEfforts === undefined
842
911
  && fallback?.reasoningEfforts !== undefined;
843
- if (!addMaxInput && !adjustAutoCompact && !addModalities && !addReasoning) return member;
912
+ if (!addMaxInput && !addMaxOutput && !adjustAutoCompact && !addModalities && !addReasoning) return member;
844
913
  return {
845
914
  ...member,
846
915
  // Never claim a larger input budget than the window, and prefer the model's own
847
916
  // measured ceiling when the fallback carries one.
848
917
  ...(addMaxInput ? { maxInputTokens: effectiveMaxInput } : {}),
918
+ ...(addMaxOutput ? { maxOutputTokens: fallback!.maxOutputTokens } : {}),
849
919
  ...(adjustAutoCompact && autoCompactTokenLimit !== undefined ? { autoCompactTokenLimit } : {}),
850
920
  ...(addModalities ? { inputModalities: [...fallback!.inputModalities!] } : {}),
851
921
  ...(addReasoning ? { reasoningEfforts: [...fallback!.reasoningEfforts!] } : {}),
@@ -882,7 +952,7 @@ export function resolveComboCatalogMember(
882
952
  provider: target.provider,
883
953
  };
884
954
  const hinted = prov
885
- ? applyProviderConfigHints(target.provider, prov, base, contextCap)
955
+ ? applyProviderConfigHints(target.provider, prov, base, contextCap, metadataModelIdCaseFold)
886
956
  : base;
887
957
  const hintedContext = typeof hinted.contextWindow === "number" && hinted.contextWindow > 0
888
958
  ? hinted.contextWindow
@@ -923,6 +993,8 @@ export function resolveComboCatalogMember(
923
993
  ?? (prov ? configuredReasoningEfforts(prov, target.model) : undefined)
924
994
  ?? base.reasoningEfforts
925
995
  ?? (fallback?.reasoningEfforts ? [...fallback.reasoningEfforts] : undefined);
996
+ const maxOutputTokens = positiveSafeInteger(hinted.maxOutputTokens, base.maxOutputTokens)
997
+ ?? (existing || prov ? positiveSafeInteger(fallback?.maxOutputTokens) : undefined);
926
998
  // The model's own measured input ceiling still applies when discovery gave us nothing:
927
999
  // GPT-5.6 advertises a 1.05M window but refuses input past 922k.
928
1000
  const effectiveMaxInput = knownMaxInput ?? fallbackMaxInput;
@@ -950,6 +1022,7 @@ export function resolveComboCatalogMember(
950
1022
  ...(reasoningEfforts !== undefined ? { reasoningEfforts } : {}),
951
1023
  contextWindow,
952
1024
  maxInputTokens,
1025
+ ...(maxOutputTokens !== undefined ? { maxOutputTokens } : {}),
953
1026
  ...(autoCompactTokenLimit !== undefined ? { autoCompactTokenLimit } : {}),
954
1027
  ...(fallbackCapped ? { contextCap, contextCapped: true as const } : {}),
955
1028
  };
@@ -1233,6 +1306,12 @@ export function catalogHintsFromModelsApiItem(providerName: string, item: Provid
1233
1306
  plainRecord(item.meta)?.n_ctx_train,
1234
1307
  );
1235
1308
  const maxInputTokens = positiveSafeInteger(limits?.max_input_tokens, item.max_input_tokens);
1309
+ const maxOutputTokens = positiveSafeInteger(
1310
+ capabilityRecord?.max_output_tokens,
1311
+ limits?.max_output_tokens,
1312
+ metadata?.max_output_tokens,
1313
+ item.max_output_tokens,
1314
+ );
1236
1315
  // Some OpenAI-compatible catalogs expose the selectable ladder under
1237
1316
  // `reasoning_parameters.efforts` instead of the older `reasoning_efforts` key.
1238
1317
  // Treat both as model metadata: otherwise a valid upstream capability disappears
@@ -1260,6 +1339,7 @@ export function catalogHintsFromModelsApiItem(providerName: string, item: Provid
1260
1339
  return {
1261
1340
  ...(contextWindow && contextWindow > 0 ? { contextWindow } : {}),
1262
1341
  ...(maxInputTokens && maxInputTokens > 0 ? { maxInputTokens } : {}),
1342
+ ...(maxOutputTokens !== undefined ? { maxOutputTokens } : {}),
1263
1343
  ...(reasoningEfforts !== undefined ? { reasoningEfforts } : {}),
1264
1344
  ...(inputModalities ? { inputModalities } : {}),
1265
1345
  ...(capabilities ? { capabilities } : {}),
@@ -1305,7 +1385,7 @@ async function fetchProviderModelsWithAuth(
1305
1385
  contextCap: number | undefined,
1306
1386
  resolveAuth: ModelsAuthResolver,
1307
1387
  ): Promise<ProviderModelsResult> {
1308
- const { name, provider: prov, discovery, request } = captured;
1388
+ const { name, provider: prov, discovery, request, metadataModelIdCaseFold } = captured;
1309
1389
  const observed = (
1310
1390
  models: CatalogModel[],
1311
1391
  state: CatalogGatherProviderModelOutcome["state"],
@@ -1330,7 +1410,7 @@ async function fetchProviderModelsWithAuth(
1330
1410
  const configured: CatalogModel[] = configuredIds.map(id => ({
1331
1411
  id,
1332
1412
  provider: name,
1333
- ...catalogHintsFromProviderConfig(name, prov, id, contextCap),
1413
+ ...catalogHintsFromProviderConfig(name, prov, id, contextCap, metadataModelIdCaseFold),
1334
1414
  }));
1335
1415
  const withConfiguredRetention = (
1336
1416
  models: CatalogModel[],
@@ -1345,6 +1425,7 @@ async function fetchProviderModelsWithAuth(
1345
1425
  contextCap,
1346
1426
  seedVertexDefault,
1347
1427
  retainComboTargets: options?.retainComboTargets,
1428
+ metadataModelIdCaseFold,
1348
1429
  });
1349
1430
  if (
1350
1431
  options?.warnDrops === true
@@ -1383,7 +1464,7 @@ async function fetchProviderModelsWithAuth(
1383
1464
  : [{
1384
1465
  id: prov.defaultModel,
1385
1466
  provider: name,
1386
- ...catalogHintsFromProviderConfig(name, prov, prov.defaultModel, contextCap),
1467
+ ...catalogHintsFromProviderConfig(name, prov, prov.defaultModel, contextCap, metadataModelIdCaseFold),
1387
1468
  }];
1388
1469
  const vertexDefaultSeed = seedVertexDefault ? configured[0] : undefined;
1389
1470
  const withVertexDefaultSeed = (models: CatalogModel[]): CatalogModel[] => (
@@ -1400,7 +1481,7 @@ async function fetchProviderModelsWithAuth(
1400
1481
  const cachedCursor = getFreshCached(name, ttlMs);
1401
1482
  if (cachedCursor) {
1402
1483
  return observed(
1403
- withConfiguredRetention(applyConfigHintsToCachedModels(name, prov, cachedCursor)),
1484
+ withConfiguredRetention(applyConfigHintsToCachedModels(name, prov, cachedCursor, undefined, metadataModelIdCaseFold)),
1404
1485
  "authoritative",
1405
1486
  );
1406
1487
  }
@@ -1408,7 +1489,7 @@ async function fetchProviderModelsWithAuth(
1408
1489
  const cooling = getStaleCached(name);
1409
1490
  return observed(
1410
1491
  withConfiguredRetention(
1411
- cooling ? applyConfigHintsToCachedModels(name, prov, cooling) : configured,
1492
+ cooling ? applyConfigHintsToCachedModels(name, prov, cooling, undefined, metadataModelIdCaseFold) : configured,
1412
1493
  ),
1413
1494
  "degraded",
1414
1495
  );
@@ -1422,9 +1503,6 @@ async function fetchProviderModelsWithAuth(
1422
1503
  });
1423
1504
  if (liveResult.ok) {
1424
1505
  const available = filterCursorConfiguredModelsByLiveDiscovery(configured, liveResult.models);
1425
- // Live Max-Mode evidence feeds the umbrella resolver's ultra gate
1426
- // (devlog 260828_cursor_umbrella_catalog; union with static evidence).
1427
- recordLiveCursorMaxModeModels(liveResult.maxModeModels ?? []);
1428
1506
  const result = available.length > 0 ? available : configured;
1429
1507
  // Cache the discovery-filtered roster without combo retention so a later
1430
1508
  // gather can re-apply the current capture's retain set on read.
@@ -1432,6 +1510,13 @@ async function fetchProviderModelsWithAuth(
1432
1510
  if (!setCached(name, forCache, Date.now(), cacheGeneration)) {
1433
1511
  return observed(withConfiguredRetention(configured), "degraded");
1434
1512
  }
1513
+ // Publish roster-derived state only for a discovery the cache accepted: a stale
1514
+ // in-flight capture (generation revoked by a credential/config change) must not
1515
+ // overwrite the spelling or Max-Mode evidence of the newer one.
1516
+ recordLiveCursorClaudeModels(liveResult.models);
1517
+ // Live Max-Mode evidence feeds the umbrella resolver's ultra gate
1518
+ // (devlog 260828_cursor_umbrella_catalog; union with static evidence).
1519
+ recordLiveCursorMaxModeModels(liveResult.maxModeModels ?? []);
1435
1520
  markProviderDiscoveryOk(name, liveResult.models.length);
1436
1521
  return observed(withConfiguredRetention(forCache, { warnDrops: true }), "authoritative");
1437
1522
  }
@@ -1445,7 +1530,7 @@ async function fetchProviderModelsWithAuth(
1445
1530
  const staleCursor = getStaleCached(name);
1446
1531
  return observed(
1447
1532
  withConfiguredRetention(
1448
- staleCursor ? applyConfigHintsToCachedModels(name, prov, staleCursor) : configured,
1533
+ staleCursor ? applyConfigHintsToCachedModels(name, prov, staleCursor, undefined, metadataModelIdCaseFold) : configured,
1449
1534
  ),
1450
1535
  "degraded",
1451
1536
  );
@@ -1463,7 +1548,7 @@ async function fetchProviderModelsWithAuth(
1463
1548
  if (fresh) {
1464
1549
  return observed(
1465
1550
  withConfiguredRetention(
1466
- withVertexDefaultSeed(applyConfigHintsToCachedModels(name, prov, fresh, contextCap)),
1551
+ withVertexDefaultSeed(applyConfigHintsToCachedModels(name, prov, fresh, contextCap, metadataModelIdCaseFold)),
1467
1552
  ),
1468
1553
  "authoritative",
1469
1554
  ); // dedups Codex's frequent /v1/models polling within the TTL
@@ -1475,7 +1560,7 @@ async function fetchProviderModelsWithAuth(
1475
1560
  return observed(
1476
1561
  withConfiguredRetention(
1477
1562
  stale
1478
- ? withVertexDefaultSeed(applyConfigHintsToCachedModels(name, prov, stale, contextCap))
1563
+ ? withVertexDefaultSeed(applyConfigHintsToCachedModels(name, prov, stale, contextCap, metadataModelIdCaseFold))
1479
1564
  : failedDiscoveryConfigured,
1480
1565
  ),
1481
1566
  "degraded",
@@ -1515,7 +1600,7 @@ async function fetchProviderModelsWithAuth(
1515
1600
  return {
1516
1601
  models: withConfiguredRetention(
1517
1602
  stale
1518
- ? withVertexDefaultSeed(applyConfigHintsToCachedModels(name, prov, stale, contextCap))
1603
+ ? withVertexDefaultSeed(applyConfigHintsToCachedModels(name, prov, stale, contextCap, metadataModelIdCaseFold))
1519
1604
  : failedDiscoveryConfigured,
1520
1605
  ),
1521
1606
  fallback: stale ? "stale" : "configured",
@@ -1592,7 +1677,7 @@ async function fetchProviderModelsWithAuth(
1592
1677
  reasoningEfforts: [],
1593
1678
  ...(model.contextWindow ? { contextWindow: model.contextWindow } : {}),
1594
1679
  ...(model.inputModalities ? { inputModalities: model.inputModalities } : {}),
1595
- }, contextCap));
1680
+ }, contextCap, metadataModelIdCaseFold));
1596
1681
  const forCache = withConfiguredRetention(live, { retainComboTargets: false });
1597
1682
  if (!setCached(name, forCache, Date.now(), cacheGeneration)) {
1598
1683
  return observed(withConfiguredRetention(configured), "degraded");
@@ -1655,7 +1740,7 @@ async function fetchProviderModelsWithAuth(
1655
1740
  provider: name,
1656
1741
  ...(ownedBy ? { owned_by: ownedBy } : {}),
1657
1742
  ...discoveredHints,
1658
- }, contextCap);
1743
+ }, contextCap, metadataModelIdCaseFold);
1659
1744
  })
1660
1745
  .filter(m => shouldExposeProviderModel(name, m.id));
1661
1746
  // Capture the count BEFORE the alias/configured augmentation below pushes extra rows into
@@ -1752,6 +1837,7 @@ export function mergeConfiguredModelsIntoLiveCatalog(opts: {
1752
1837
  contextCap?: number;
1753
1838
  seedVertexDefault?: boolean;
1754
1839
  retainComboTargets?: boolean;
1840
+ metadataModelIdCaseFold?: boolean;
1755
1841
  }): { models: CatalogModel[]; droppedConfiguredIds: string[] } {
1756
1842
  const {
1757
1843
  name,
@@ -1761,6 +1847,7 @@ export function mergeConfiguredModelsIntoLiveCatalog(opts: {
1761
1847
  contextCap,
1762
1848
  seedVertexDefault,
1763
1849
  retainComboTargets = true,
1850
+ metadataModelIdCaseFold,
1764
1851
  } = opts;
1765
1852
  const out = [...opts.models];
1766
1853
  const present = new Set(out.map(model => model.id));
@@ -1769,7 +1856,7 @@ export function mergeConfiguredModelsIntoLiveCatalog(opts: {
1769
1856
  if (present.has(candidate.id)) continue;
1770
1857
  const dated = out.find(live => isDatedVariantId(live.id, candidate.id));
1771
1858
  if (dated) {
1772
- out.push(applyProviderConfigHints(name, prov, { ...dated, id: candidate.id }, contextCap));
1859
+ out.push(applyProviderConfigHints(name, prov, { ...dated, id: candidate.id }, contextCap, metadataModelIdCaseFold));
1773
1860
  present.add(candidate.id);
1774
1861
  continue;
1775
1862
  }
@@ -1955,7 +2042,16 @@ async function gatherRoutedModelsUncached(
1955
2042
  config,
1956
2043
  capture.openAiApiPolicy,
1957
2044
  );
1958
- const all = augmentRoutedModelsWithMetadata(apiAugmented, activeProviders.map(provider => provider.name), config.providers, config)
2045
+ const metadataModelIdCaseFoldByProvider = new Map(
2046
+ activeProviders.map(provider => [provider.name, provider.metadataModelIdCaseFold]),
2047
+ );
2048
+ const all = augmentRoutedModelsWithMetadata(
2049
+ apiAugmented,
2050
+ activeProviders.map(provider => provider.name),
2051
+ config.providers,
2052
+ config,
2053
+ metadataModelIdCaseFoldByProvider,
2054
+ )
1959
2055
  // Drop image/video generation models (e.g. Grok image/video) by default. Cursor's static catalog
1960
2056
  // intentionally mirrors Cursor's public model table, including Gemini image preview, so the
1961
2057
  // exposure decision goes through shouldExposeRoutedModel (single choke point).
@@ -2009,6 +2105,9 @@ async function gatherRoutedModelsUncached(
2009
2105
  // stay separate fields because routed/API rows of the same family run a wider window.
2010
2106
  // Falls back to the window for slugs with no separate ceiling.
2011
2107
  maxInputTokens: Math.min(nativeOpenAiMaxInputTokens(slug, openaiContextCap) ?? contextWindow, contextWindow),
2108
+ ...(nativeOpenAiMaxOutputTokens(slug) !== undefined
2109
+ ? { maxOutputTokens: nativeOpenAiMaxOutputTokens(slug) }
2110
+ : {}),
2012
2111
  autoCompactTokenLimit: nativeOpenAiAutoCompactTokenLimit(slug, openaiContextCap),
2013
2112
  inputModalities: nativeInputModalities(slug),
2014
2113
  reasoningEfforts: nativeReasoningEfforts(slug),
@@ -2042,6 +2141,9 @@ async function gatherRoutedModelsUncached(
2042
2141
  ? {
2043
2142
  contextWindow: nativeContextWindow,
2044
2143
  ...(nativeAliasMaxInput !== undefined ? { maxInputTokens: nativeAliasMaxInput } : {}),
2144
+ ...(nativeOpenAiMaxOutputTokens(combo.alias) !== undefined
2145
+ ? { maxOutputTokens: nativeOpenAiMaxOutputTokens(combo.alias) }
2146
+ : {}),
2045
2147
  ...(nativeAliasAutoCompact !== undefined ? { autoCompactTokenLimit: nativeAliasAutoCompact } : {}),
2046
2148
  inputModalities: nativeInputModalities(combo.alias),
2047
2149
  reasoningEfforts: nativeReasoningEfforts(combo.alias),
@@ -2054,6 +2156,7 @@ async function gatherRoutedModelsUncached(
2054
2156
  enrichedByName,
2055
2157
  providerContextCap(config, target.provider),
2056
2158
  nativeAliasFallback,
2159
+ metadataModelIdCaseFoldByProvider.get(target.provider),
2057
2160
  ))
2058
2161
  .filter((member): member is CatalogModel => member !== undefined);
2059
2162
  const derived = deriveComboCatalogModel(id, combo, members);
@@ -2087,7 +2190,7 @@ async function gatherRoutedModelsUncached(
2087
2190
  const codexForwardNativeCapabilityAlias = cm.provider === OPENAI_CODEX_PROVIDER_ID
2088
2191
  && providerForCanonicalCheck !== undefined
2089
2192
  && isCanonicalOpenAiForwardProvider(providerForCanonicalCheck)
2090
- && isNativeOpenAiCapabilityAliasModel(cm.modelId);
2193
+ && hasNativeOpenAiCapabilityMetadata(cm.modelId);
2091
2194
  const customNativeLimits = {
2092
2195
  ...nativeContextLimits(config),
2093
2196
  ...(typeof cm.contextWindow === "number" && cm.contextWindow > 0
@@ -2105,6 +2208,9 @@ async function gatherRoutedModelsUncached(
2105
2208
  const nativeAliasMaxInputTokens = codexForwardNativeCapabilityAlias
2106
2209
  ? nativeOpenAiMaxInputTokens(cm.modelId, customNativeLimits)
2107
2210
  : undefined;
2211
+ const nativeAliasMaxOutputTokens = codexForwardNativeCapabilityAlias
2212
+ ? nativeOpenAiMaxOutputTokens(cm.modelId)
2213
+ : undefined;
2108
2214
  const configuredMaxInput = rawProvider
2109
2215
  ? configuredMaxInputTokens(rawProvider, cm.modelId)
2110
2216
  : undefined;
@@ -2116,6 +2222,13 @@ async function gatherRoutedModelsUncached(
2116
2222
  ...(customContextWindow !== undefined ? [customContextWindow] : []),
2117
2223
  )
2118
2224
  : undefined;
2225
+ const customMaxOutputTokens = rawProvider
2226
+ ? routedMaxOutputTokens(cm.provider, rawProvider, {
2227
+ id: cm.modelId,
2228
+ provider: cm.provider,
2229
+ ...(nativeAliasMaxOutputTokens !== undefined ? { maxOutputTokens: nativeAliasMaxOutputTokens } : {}),
2230
+ }, cm.modelId, metadataModelIdCaseFoldByProvider.get(cm.provider))
2231
+ : nativeAliasMaxOutputTokens;
2119
2232
  const configuredAutoCompact = configuredAutoCompactTokenLimit(rawProvider, cm.modelId);
2120
2233
  const customAutoCompactTokenLimit = codexForwardNativeCapabilityAlias
2121
2234
  ? nativeOpenAiAutoCompactTokenLimit(cm.modelId, customNativeLimits)
@@ -2139,9 +2252,11 @@ async function gatherRoutedModelsUncached(
2139
2252
  // Display-only label: never feeds routing (customModels are keyed by routedSlug below).
2140
2253
  ...(cm.displayName
2141
2254
  ? { displayName: cm.displayName }
2142
- : codexForwardNativeCapabilityAlias ? { displayName: "Daybreak Blue" } : {}),
2255
+ : codexForwardNativeCapabilityAlias
2256
+ ? { displayName: nativeOpenAiCapabilityDisplayName(cm.modelId) ?? cm.modelId } : {}),
2143
2257
  ...(customContextWindow !== undefined ? { contextWindow: customContextWindow } : {}),
2144
2258
  ...(customMaxInputTokens !== undefined ? { maxInputTokens: customMaxInputTokens } : {}),
2259
+ ...(customMaxOutputTokens !== undefined ? { maxOutputTokens: customMaxOutputTokens } : {}),
2145
2260
  ...(customAutoCompactTokenLimit !== undefined ? { autoCompactTokenLimit: customAutoCompactTokenLimit } : {}),
2146
2261
  ...(cm.inputModalities
2147
2262
  ? { inputModalities: cm.inputModalities }
@@ -2193,10 +2308,16 @@ async function gatherRoutedModelsUncached(
2193
2308
  const mergedMaxInput = mergedMaxInputCandidates.length > 0
2194
2309
  ? Math.min(...mergedMaxInputCandidates)
2195
2310
  : undefined;
2311
+ const mergedMaxOutputCandidates = [base.maxOutputTokens, replaced?.maxOutputTokens]
2312
+ .filter((value): value is number => typeof value === "number" && value > 0);
2313
+ const mergedMaxOutput = mergedMaxOutputCandidates.length > 0
2314
+ ? Math.min(...mergedMaxOutputCandidates)
2315
+ : undefined;
2196
2316
  const merged: CatalogModel = replaced ? {
2197
2317
  ...base,
2198
2318
  ...(base.contextWindow === undefined && replaced.contextWindow !== undefined ? { contextWindow: replaced.contextWindow } : {}),
2199
2319
  ...(mergedMaxInput !== undefined ? { maxInputTokens: mergedMaxInput } : {}),
2320
+ ...(mergedMaxOutput !== undefined ? { maxOutputTokens: mergedMaxOutput } : {}),
2200
2321
  ...(base.autoCompactTokenLimit === undefined && replaced.autoCompactTokenLimit !== undefined
2201
2322
  ? { autoCompactTokenLimit: replaced.autoCompactTokenLimit }
2202
2323
  : {}),
@@ -2331,12 +2452,19 @@ function augmentRoutedModelsWithCapturedOpenAiApiRows(
2331
2452
  const autoCompactTokenLimit = contextWindow !== undefined && configuredAutoCompact !== undefined
2332
2453
  ? clampAutoCompactTokenLimit(contextWindow, maxInputTokens, configuredAutoCompact)
2333
2454
  : undefined;
2455
+ const maxOutputTokens = routedMaxOutputTokens(
2456
+ OPENAI_API_PROVIDER_ID,
2457
+ configured,
2458
+ existingById.get(id) ?? { provider: OPENAI_API_PROVIDER_ID, id },
2459
+ policy.virtualModels?.[id]?.wireModelId ?? id,
2460
+ );
2334
2461
  return {
2335
2462
  provider: OPENAI_API_PROVIDER_ID,
2336
2463
  id,
2337
2464
  owned_by: OPENAI_API_PROVIDER_ID,
2338
2465
  ...(contextWindow ? { contextWindow } : {}),
2339
2466
  ...(maxInputTokens ? { maxInputTokens } : {}),
2467
+ ...(maxOutputTokens !== undefined ? { maxOutputTokens } : {}),
2340
2468
  ...(autoCompactTokenLimit !== undefined ? { autoCompactTokenLimit } : {}),
2341
2469
  ...(policy.modelInputModalities?.[id] ? { inputModalities: [...policy.modelInputModalities[id]!] } : {}),
2342
2470
  ...(policy.modelReasoningEfforts?.[id] ? { reasoningEfforts: [...policy.modelReasoningEfforts[id]!] } : {}),
@@ -2366,6 +2494,7 @@ export function augmentRoutedModelsWithMetadata(
2366
2494
  providerNames: string[],
2367
2495
  providers?: Record<string, OcxProviderConfig>,
2368
2496
  caps?: Pick<OcxConfig, "providerContextCaps">,
2497
+ metadataModelIdCaseFoldByProvider?: ReadonlyMap<string, boolean>,
2369
2498
  ): CatalogModel[] {
2370
2499
  const out = [...models];
2371
2500
  const seen = new Set(out.map(m => `${m.provider}/${m.id}`));
@@ -2384,11 +2513,20 @@ export function augmentRoutedModelsWithMetadata(
2384
2513
  id: meta.id,
2385
2514
  owned_by: provider,
2386
2515
  ...(typeof meta.contextWindow === "number" && meta.contextWindow > 0 ? { contextWindow: meta.contextWindow } : {}),
2516
+ ...(typeof meta.maxTokens === "number" && meta.maxTokens > 0 ? { maxOutputTokens: meta.maxTokens } : {}),
2387
2517
  ...(Array.isArray(meta.input) && meta.input.length > 0 ? { inputModalities: [...meta.input] } : {}),
2388
2518
  };
2389
2519
  out.push({
2390
2520
  ...model,
2391
- ...(providers?.[provider] ? applyProviderConfigHints(provider, providers[provider], model, contextCap) : {}),
2521
+ ...(providers?.[provider]
2522
+ ? applyProviderConfigHints(
2523
+ provider,
2524
+ providers[provider],
2525
+ model,
2526
+ contextCap,
2527
+ metadataModelIdCaseFoldByProvider?.get(provider),
2528
+ )
2529
+ : {}),
2392
2530
  });
2393
2531
  }
2394
2532
  }
@@ -2,7 +2,7 @@
2
2
  // Public surface preserved exactly; importers keep using "src/codex/catalog".
3
3
  export { isMediaGenerationModelId, shouldExposeRoutedModel, readCodexCatalogPath, readCatalog, normalizeRoutedCatalogEntry, catalogModelSlug, filterSupportedNativeSlugs, catalogModelSupportsReasoningSummaries } from "./catalog/parsing";
4
4
  export type { CatalogModel, MultiAgentMode } from "./catalog/parsing";
5
- export { accountBoundNativeOpenAiSlugs, accountBoundNativeOpenAiSlugsBySelector, CODEX_NATIVE_ALIAS_CATALOG_KIND, NATIVE_DAYBREAK_BLUE_MODEL, NATIVE_GPT56_CONTEXT_WINDOW, NATIVE_GPT56_MAX_INPUT_TOKENS, NATIVE_GPT56_OPT_IN_CONTEXT_WINDOW, NATIVE_OPENAI_CAPABILITY_ALIAS_MODELS, NATIVE_OPENAI_MODELS, configuredNativeAliasSlugs, desktopAllowlistSuppressedNativeSlugs, isNativeAliasCatalogEntry, isNativeOpenAiCapabilityAliasModel, nativeContextLimits, nativeOpenAiCapabilitySourceSlug, nativeOpenAiContextWindow, nativeOpenAiMaxInputTokens, nativeOpenAiContextTier, disabledNativeSlugs, visibleNativeSlugs, desktopVisibleNativeSlugs, nativeModelRows, applyNativeVisibility, observedAccountBoundNativeEntries, observedAccountBoundNativeOpenAiSlugs, upstreamNativeEntry, nativeOpenAiSlugs, listCatalogNativeSlugs, nativeInputModalities, nativeReasoningEfforts, nativeDefaultReasoningEffort, shouldIncludeAccountBoundNativeOpenAi, shouldIncludeNativeOpenAi, type NativeContextLimits, type NativeContextLimitsInput } from "./catalog/metadata";
5
+ export { accountBoundNativeOpenAiSlugs, accountBoundNativeOpenAiSlugsBySelector, CODEX_NATIVE_ALIAS_CATALOG_KIND, NATIVE_DAYBREAK_BLUE_MODEL, NATIVE_GPT6_ASTRA_MODEL, NATIVE_GPT56_CONTEXT_WINDOW, NATIVE_GPT56_MAX_INPUT_TOKENS, NATIVE_GPT56_OPT_IN_CONTEXT_WINDOW, NATIVE_OPENAI_CAPABILITY_ALIAS_MODELS, NATIVE_OPENAI_MODELS, configuredNativeAliasSlugs, desktopAllowlistSuppressedNativeSlugs, isNativeAliasCatalogEntry, isNativeOpenAiCapabilityAliasModel, nativeContextLimits, nativeOpenAiCapabilitySourceSlug, nativeOpenAiContextWindow, nativeOpenAiMaxInputTokens, nativeOpenAiMaxOutputTokens, nativeOpenAiContextTier, disabledNativeSlugs, visibleNativeSlugs, desktopVisibleNativeSlugs, nativeModelRows, applyNativeVisibility, observedAccountBoundNativeEntries, observedAccountBoundNativeOpenAiSlugs, upstreamNativeEntry, nativeOpenAiSlugs, listCatalogNativeSlugs, nativeInputModalities, nativeReasoningEfforts, nativeDefaultReasoningEffort, shouldIncludeAccountBoundNativeOpenAi, shouldIncludeNativeOpenAi, type NativeContextLimits, type NativeContextLimitsInput } from "./catalog/metadata";
6
6
  export { isSpawnableCodexCandidate, codexExecInvocation, loadBundledCodexCatalog, materializeBundledCodexCatalog, loadCatalogTemplate } from "./catalog/bundled";
7
7
  export { nativeEffortClamp, shouldApplyNativeEffortClamp, catalogModelEfforts, codexSupportedReasoningEfforts, clampedDefaultEffort, clampEntryToCodexSupportedEfforts, clampCatalogModelsToCodexSupport } from "./catalog/effort";
8
8
  export { applyProviderConfigHints, isDatedVariantId, filterCatalogVisibleModels, gatherRoutedModels, clearGatherRoutedModelsInflight, augmentRoutedModelsWithRegistryOpenAiApiRows, augmentRoutedModelsWithMetadata, resolveComboCatalogMember, configuredComboTargetModelsByProvider } from "./catalog/provider-fetch";
@@ -429,6 +429,7 @@ export interface CatalogTrustedOpenAiApiPolicySnapshot {
429
429
  readonly models?: readonly string[];
430
430
  readonly modelContextWindows?: Readonly<Record<string, number>>;
431
431
  readonly modelMaxInputTokens?: Readonly<Record<string, number>>;
432
+ readonly virtualModels?: Readonly<Record<string, Readonly<{ wireModelId: string; reasoningMode: "pro" }>>>;
432
433
  readonly modelInputModalities?: Readonly<Record<string, readonly string[]>>;
433
434
  readonly modelReasoningEfforts?: Readonly<Record<string, readonly string[]>>;
434
435
  }