@yansigit/opencodex 2.32.0 → 2.33.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 (142) hide show
  1. package/README.md +2 -2
  2. package/gui/dist/assets/index-CIDo4y4k.js +102 -0
  3. package/gui/dist/assets/index-DrSQdTRd.css +1 -0
  4. package/gui/dist/index.html +2 -2
  5. package/package.json +8 -5
  6. package/src/adapters/anthropic.ts +20 -6
  7. package/src/adapters/azure.ts +20 -4
  8. package/src/adapters/base.ts +3 -1
  9. package/src/adapters/command-code.ts +131 -17
  10. package/src/adapters/cursor/envelope-echo.ts +162 -0
  11. package/src/adapters/cursor/live-transport.ts +4 -2
  12. package/src/adapters/cursor/native-exec-fs.ts +13 -12
  13. package/src/adapters/cursor/native-exec-network.ts +3 -5
  14. package/src/adapters/cursor/native-exec-policy.ts +47 -0
  15. package/src/adapters/cursor/native-exec-shell.ts +13 -25
  16. package/src/adapters/cursor/native-exec.ts +18 -10
  17. package/src/adapters/cursor/protobuf-events.ts +186 -9
  18. package/src/adapters/cursor/protobuf-request.ts +53 -18
  19. package/src/adapters/cursor/request-builder.ts +11 -3
  20. package/src/adapters/cursor/tool-definitions.ts +49 -2
  21. package/src/adapters/cursor/tool-result-normalize.ts +21 -8
  22. package/src/adapters/cursor/types.ts +11 -3
  23. package/src/adapters/cursor.ts +123 -0
  24. package/src/adapters/google-aistudio-parser.ts +49 -0
  25. package/src/adapters/google-antigravity-replay.ts +2 -2
  26. package/src/adapters/google-antigravity-wire.ts +7 -0
  27. package/src/adapters/google-errors.ts +6 -2
  28. package/src/adapters/google-http.ts +30 -7
  29. package/src/adapters/google-truncation.ts +5 -0
  30. package/src/adapters/google-wire-compiler.ts +38 -6
  31. package/src/adapters/google.ts +242 -28
  32. package/src/adapters/kiro-tools.ts +20 -9
  33. package/src/adapters/openai-chat.ts +9 -0
  34. package/src/adapters/openai-responses.ts +2 -1
  35. package/src/bridge.ts +112 -9
  36. package/src/chat/inbound.ts +15 -0
  37. package/src/claude/context-windows.ts +16 -9
  38. package/src/cli/doctor.ts +2 -2
  39. package/src/cli/index.ts +10 -3
  40. package/src/cli/provider.ts +6 -0
  41. package/src/cli/status.ts +23 -0
  42. package/src/codex/auth-api.ts +4 -2
  43. package/src/codex/autostart-health.ts +16 -0
  44. package/src/codex/catalog/aggregation.ts +12 -12
  45. package/src/codex/catalog/effort.ts +18 -3
  46. package/src/codex/catalog/metadata.ts +27 -1
  47. package/src/codex/catalog/model-metadata.ts +39 -12
  48. package/src/codex/catalog/parsing.ts +38 -27
  49. package/src/codex/catalog/provider-fetch.ts +232 -133
  50. package/src/codex/catalog/sync.ts +1 -1
  51. package/src/codex/convergence.ts +5 -0
  52. package/src/codex/shim.ts +56 -3
  53. package/src/config/provider-validation.ts +37 -0
  54. package/src/config.ts +78 -2
  55. package/src/generated/compatibility-version.json +187 -119
  56. package/src/generated/model-metadata.ts +3 -0
  57. package/src/images/loop.ts +37 -6
  58. package/src/lib/azure-identity.ts +154 -0
  59. package/src/lib/debug.ts +42 -0
  60. package/src/lib/errors.ts +14 -0
  61. package/src/lib/provider-outbound.ts +45 -33
  62. package/src/lib/provider-tls-profile.ts +309 -0
  63. package/src/lib/proxy-env.ts +49 -0
  64. package/src/lib/redact.ts +10 -1
  65. package/src/oauth/aistudio-native-daemon.ts +62 -0
  66. package/src/oauth/aistudio-session-sync.ts +95 -0
  67. package/src/oauth/antigravity-routing.ts +282 -236
  68. package/src/oauth/callback-server.ts +22 -2
  69. package/src/oauth/command-code.ts +5 -16
  70. package/src/oauth/google-aistudio-auth.ts +98 -0
  71. package/src/oauth/google-antigravity.ts +42 -5
  72. package/src/oauth/index.ts +15 -3
  73. package/src/oauth/key-providers.ts +8 -0
  74. package/src/oauth/kimi.ts +9 -1
  75. package/src/oauth/login-cli.ts +66 -1
  76. package/src/oauth/open-browser-choice.ts +26 -0
  77. package/src/oauth/store.ts +6 -0
  78. package/src/providers/antigravity-quota.ts +3 -1
  79. package/src/providers/api-keys.ts +2 -1
  80. package/src/providers/auto-compact-budget.ts +65 -0
  81. package/src/providers/derive.ts +5 -1
  82. package/src/providers/key-failover.ts +5 -1
  83. package/src/providers/openai-tiers.ts +5 -0
  84. package/src/providers/provider-id-rewrite.ts +1 -0
  85. package/src/providers/quota.ts +148 -50
  86. package/src/providers/registry.ts +27 -4
  87. package/src/providers/request-pacing.ts +33 -6
  88. package/src/providers/xai-transport.ts +21 -0
  89. package/src/responses/google-provider-options.ts +36 -0
  90. package/src/responses/namespace-tool-compat.ts +84 -4
  91. package/src/responses/parser.ts +11 -0
  92. package/src/responses/provider-opaque-metadata.ts +3 -3
  93. package/src/responses/schema.ts +37 -0
  94. package/src/responses/state.ts +94 -4
  95. package/src/router.ts +11 -2
  96. package/src/routing/account-pool/cooldown.ts +8 -0
  97. package/src/routing/account-pool/index.ts +1 -0
  98. package/src/server/aistudio-ws-hub.ts +295 -0
  99. package/src/server/auth-cors.ts +29 -0
  100. package/src/server/chat-completions.ts +2 -0
  101. package/src/server/images.ts +19 -35
  102. package/src/server/index.ts +94 -0
  103. package/src/server/management/agent-settings-routes.ts +205 -15
  104. package/src/server/management/combo-routes.ts +6 -0
  105. package/src/server/management/config-routes.ts +31 -5
  106. package/src/server/management/logs-usage-routes.ts +11 -5
  107. package/src/server/management/model-rows.ts +4 -0
  108. package/src/server/management/oauth-account-routes.ts +38 -7
  109. package/src/server/management/provider-routes.ts +113 -15
  110. package/src/server/management/routing-profile-routes.ts +3 -0
  111. package/src/server/port-reclaim.ts +19 -1
  112. package/src/server/request-log-conversation.ts +12 -0
  113. package/src/server/request-log.ts +23 -1
  114. package/src/server/responses/agent-task-recovery.ts +1 -1
  115. package/src/server/responses/compact.ts +30 -1
  116. package/src/server/responses/core.ts +363 -156
  117. package/src/server/responses/empty-completion-guard.ts +35 -6
  118. package/src/server/responses/fetch-helpers.ts +18 -5
  119. package/src/server/responses/policy-fallback.ts +1 -1
  120. package/src/server/responses/v2-native-parent-override.ts +59 -0
  121. package/src/server/responses/ws-upstream.ts +75 -2
  122. package/src/server/responses-undeclared-tool-guard.ts +90 -8
  123. package/src/server/ws-bridge.ts +2 -1
  124. package/src/service.ts +1 -1
  125. package/src/smoke/fingerprint-cache.ts +133 -0
  126. package/src/smoke/live-scenarios.ts +33 -0
  127. package/src/smoke/runner.ts +119 -0
  128. package/src/types/config.ts +16 -1
  129. package/src/types/provider.ts +18 -1
  130. package/src/types/request.ts +30 -0
  131. package/src/types/tools.ts +51 -0
  132. package/src/types.ts +6 -0
  133. package/src/usage/command-code-manifest.ts +116 -0
  134. package/src/usage/cost.ts +2 -2
  135. package/src/usage/expected-prices.ts +83 -0
  136. package/src/usage/log.ts +2 -2
  137. package/src/usage/summary.ts +34 -12
  138. package/src/web-search/gemini-executor.ts +6 -4
  139. package/src/web-search/index.ts +16 -8
  140. package/src/web-search/loop.ts +42 -6
  141. package/gui/dist/assets/index-BG43zwVe.js +0 -102
  142. package/gui/dist/assets/index-CiSI-jrP.css +0 -1
@@ -41,11 +41,13 @@ import type { FastPolicyAuthority } from "../../providers/fastwire";
41
41
  import { effectiveGoogleMode, getProviderRegistryEntry, providerMatchesRegistryTransport } from "../../providers/registry";
42
42
  import { parseAntigravityAvailableModels, registerAntigravityDiscoveredWireModels } from "../../providers/antigravity-models";
43
43
  import { applyProviderContextCap, providerContextCap, resolveUnknownRoutedContextWindow } from "../../providers/context-cap";
44
- import { routedSlug, slugEquals, slugsEquivalent } from "../../providers/slug-codec";
44
+ import { clampAutoCompactTokenLimit } from "../../providers/auto-compact-budget";
45
+ import { routedSlug, slugEquals, slugEquivalenceKey, slugsEquivalent } from "../../providers/slug-codec";
45
46
  import { CODEX_GPT5_IDENTITY_LINE } from "../../adapters/identity";
46
47
  import { filterCursorConfiguredModelsByLiveDiscovery } from "../../adapters/cursor/discovery";
47
48
  import { fetchCursorUsableModels } from "../../adapters/cursor/live-models";
48
49
  import { isCanonicalOpenAiForwardProvider, OPENAI_API_PROVIDER_ID, OPENAI_CODEX_PROVIDER_ID } from "../../providers/openai-tiers";
50
+ import { globalAiStudioRelayHub } from "../../server/aistudio-ws-hub";
49
51
  import {
50
52
  COMBO_NAMESPACE,
51
53
  comboModelId,
@@ -60,6 +62,7 @@ import {
60
62
  providerOutboundPost,
61
63
  providerRedirectError,
62
64
  } from "../../lib/provider-outbound";
65
+ import { isAntigravityOAuthProvider } from "../../lib/provider-tls-profile";
63
66
  import { redactSecretString } from "../../lib/redact";
64
67
  import {
65
68
  extractProviderModelItems,
@@ -75,8 +78,7 @@ import { createAdmissionGate, ResourceAdmissionError, type AdmissionMetrics } fr
75
78
 
76
79
  import { CODEX_CUSTOM_MODEL_CATALOG_KIND, JAWCODE_CATALOG_AUGMENT_PROVIDERS, catalogModelSlug, shouldExposeRoutedModel } from "./parsing";
77
80
  import type { CatalogModel } from "./parsing";
78
- import { capsFromProvider, enrichCatalogModelMetadata, persistLiveModelMetadata, type LiveSnapshotRow } from "./model-metadata";
79
- import { disabledNativeSlugs, hasComboTargets, isNativeOpenAiCapabilityAliasModel, NATIVE_GPT56_MAX_INPUT_TOKENS, nativeContextLimits, nativeDefaultReasoningEffort, nativeInputModalities, nativeOpenAiContextWindow, nativeOpenAiMaxInputTokens, nativeOpenAiSlugs, nativeParallelToolCalls, nativeReasoningEfforts } from "./metadata";
81
+ import { disabledNativeSlugs, hasComboTargets, isNativeOpenAiCapabilityAliasModel, NATIVE_GPT56_MAX_INPUT_TOKENS, nativeContextLimits, nativeDefaultReasoningEffort, nativeInputModalities, nativeOpenAiAutoCompactTokenLimit, nativeOpenAiContextWindow, nativeOpenAiMaxInputTokens, nativeOpenAiSlugs, nativeParallelToolCalls, nativeReasoningEfforts } from "./metadata";
80
82
  import { deriveComboCatalogModel, normalizedOpenAiApiSignature, openAiApiCollisionWarnings, replaceLastComboCatalogOmissions, warnUncataloguedComboOnce } from "./aggregation";
81
83
  import type { ComboCatalogOmission } from "./aggregation";
82
84
  import type { CatalogGatherProviderAuthEvidence } from "./filesystem-evidence";
@@ -572,6 +574,7 @@ function providerCatalogFingerprint(name: string, prov: OcxProviderConfig): Reco
572
574
  ctx: prov.contextWindow ?? null,
573
575
  ctxW: prov.modelContextWindows ?? null,
574
576
  maxIn: prov.modelMaxInputTokens ?? null,
577
+ autoCompact: prov.modelAutoCompactTokenLimits ?? null,
575
578
  inMod: prov.modelInputModalities ?? null,
576
579
  re: prov.modelReasoningEfforts ?? null,
577
580
  defRe: prov.modelDefaultReasoningEfforts ?? null,
@@ -626,6 +629,17 @@ export function configuredMaxInputTokens(prov: OcxProviderConfig, id: string): n
626
629
  return typeof configured === "number" && configured > 0 ? configured : undefined;
627
630
  }
628
631
 
632
+ export function configuredAutoCompactTokenLimit(
633
+ prov: OcxProviderConfig | undefined,
634
+ id: string,
635
+ ): number | undefined {
636
+ if (!prov) return undefined;
637
+ const configured = modelRecordValue(prov.modelAutoCompactTokenLimits, id);
638
+ return typeof configured === "number" && Number.isSafeInteger(configured) && configured > 0
639
+ ? configured
640
+ : undefined;
641
+ }
642
+
629
643
  function configuredReasoningSummarySupport(prov: OcxProviderConfig | undefined, id: string): boolean | undefined {
630
644
  if (!prov) return undefined;
631
645
  const explicit = modelRecordValue(prov.modelSupportsReasoningSummaries, id);
@@ -637,6 +651,7 @@ export function applyProviderConfigHints(name: string, prov: OcxProviderConfig,
637
651
  void name;
638
652
  const configuredCap = configuredContextWindow(prov, model.id);
639
653
  const configuredMaxInput = configuredMaxInputTokens(prov, model.id);
654
+ const configuredAutoCompact = configuredAutoCompactTokenLimit(prov, model.id);
640
655
  let inputModalities = configuredInputModalities(prov, model.id);
641
656
  // Vision-sidecar coverage: `noVisionModels` marks models whose images the PROXY describes
642
657
  // (src/vision/index.ts). The catalog must still advertise image input for them — the Codex app
@@ -663,16 +678,9 @@ export function applyProviderConfigHints(name: string, prov: OcxProviderConfig,
663
678
  const hintedWindow = discoveredWindow !== undefined
664
679
  ? (configuredCap !== undefined ? Math.min(discoveredWindow, configuredCap) : discoveredWindow)
665
680
  : (configuredCap ?? (providerCap !== undefined ? resolveUnknownRoutedContextWindow(providerCap) : undefined));
666
- const capFilledMissingWindow = discoveredWindow === undefined && hintedWindow !== undefined;
667
681
  const hinted = {
668
682
  ...modelWithoutServiceTier,
669
683
  ...(hintedWindow !== undefined ? { contextWindow: hintedWindow } : {}),
670
- ...(discoveredWindow !== undefined
671
- ? { detectedContextWindow: model.detectedContextWindow ?? discoveredWindow }
672
- : {}),
673
- ...(capFilledMissingWindow && !model.metadataSource
674
- ? { metadataSource: "config_fallback" as const }
675
- : {}),
676
684
  ...(inputModalities ? { inputModalities } : {}),
677
685
  ...(reasoningEfforts !== undefined ? { reasoningEfforts } : {}),
678
686
  ...(configuredMaxInput !== undefined
@@ -697,26 +705,31 @@ export function applyProviderConfigHints(name: string, prov: OcxProviderConfig,
697
705
  ...(prov.codexToolMode !== undefined ? { codexToolMode: prov.codexToolMode } : {}),
698
706
  };
699
707
  const capped = applyProviderContextCap(hinted.contextWindow, providerCap);
700
- const detectedContextWindow = hinted.detectedContextWindow
701
- ?? discoveredWindow;
702
- const contextCapped = providerCap !== undefined
703
- && typeof capped === "number"
704
- && (
705
- capped !== hinted.contextWindow
706
- || (typeof detectedContextWindow === "number" && detectedContextWindow > capped)
707
- || hinted.contextCapped === true
708
- );
709
- if (providerCap !== undefined && capped !== hinted.contextWindow) {
710
- return {
711
- ...hinted,
712
- contextWindow: capped,
713
- contextCap: providerCap,
714
- contextCapped,
715
- };
716
- }
717
- return providerCap !== undefined
718
- ? { ...hinted, contextCap: providerCap, contextCapped }
708
+ const withCap = providerCap !== undefined
709
+ ? capped !== hinted.contextWindow
710
+ ? { ...hinted, contextWindow: capped, contextCap: providerCap, contextCapped: true }
711
+ : { ...hinted, contextCap: providerCap, contextCapped: false }
719
712
  : hinted;
713
+ const contextWindow = typeof withCap.contextWindow === "number" && withCap.contextWindow > 0
714
+ ? withCap.contextWindow
715
+ : undefined;
716
+ const boundedMaxInput = typeof withCap.maxInputTokens === "number" && withCap.maxInputTokens > 0
717
+ ? (contextWindow !== undefined ? Math.min(withCap.maxInputTokens, contextWindow) : withCap.maxInputTokens)
718
+ : undefined;
719
+ const withHardBounds = boundedMaxInput !== undefined && boundedMaxInput !== withCap.maxInputTokens
720
+ ? { ...withCap, maxInputTokens: boundedMaxInput }
721
+ : withCap;
722
+ const softCandidates = [model.autoCompactTokenLimit, configuredAutoCompact]
723
+ .filter((value): value is number => typeof value === "number" && value > 0);
724
+ if (contextWindow === undefined || softCandidates.length === 0) return withHardBounds;
725
+ return {
726
+ ...withHardBounds,
727
+ autoCompactTokenLimit: clampAutoCompactTokenLimit(
728
+ contextWindow,
729
+ boundedMaxInput,
730
+ Math.min(...softCandidates),
731
+ ),
732
+ };
720
733
  }
721
734
 
722
735
  export function catalogHintsFromProviderConfig(name: string, prov: OcxProviderConfig, id: string, contextCap?: number): Partial<CatalogModel> {
@@ -743,6 +756,7 @@ interface ComboCatalogMemberFallback {
743
756
  readonly contextWindow?: number;
744
757
  /** Input ceiling when it is lower than the window (native GPT-5.6: 922k under 1.05M). */
745
758
  readonly maxInputTokens?: number;
759
+ readonly autoCompactTokenLimit?: number;
746
760
  readonly inputModalities?: readonly string[];
747
761
  readonly reasoningEfforts?: readonly string[];
748
762
  }
@@ -771,26 +785,33 @@ export function resolveComboCatalogMember(
771
785
  if (prov?.disabled === true) return undefined;
772
786
 
773
787
  const withFallbackMetadata = (member: CatalogModel): CatalogModel => {
774
- if (!fallback) return member;
775
788
  const contextWindow = typeof member.contextWindow === "number" && member.contextWindow > 0
776
789
  ? member.contextWindow
777
790
  : undefined;
778
- const addMaxInput = contextWindow !== undefined
791
+ const addMaxInput = fallback !== undefined && contextWindow !== undefined
779
792
  && !(typeof member.maxInputTokens === "number" && member.maxInputTokens > 0);
793
+ const effectiveMaxInput = addMaxInput
794
+ ? Math.min(fallback?.maxInputTokens ?? contextWindow!, contextWindow!)
795
+ : member.maxInputTokens;
796
+ const softCandidates = [member.autoCompactTokenLimit, fallback?.autoCompactTokenLimit]
797
+ .filter((value): value is number => typeof value === "number" && value > 0);
798
+ const autoCompactTokenLimit = contextWindow !== undefined && softCandidates.length > 0
799
+ ? clampAutoCompactTokenLimit(contextWindow, effectiveMaxInput, Math.min(...softCandidates))
800
+ : member.autoCompactTokenLimit;
801
+ const adjustAutoCompact = autoCompactTokenLimit !== member.autoCompactTokenLimit;
780
802
  const addModalities = (!Array.isArray(member.inputModalities) || member.inputModalities.length === 0)
781
- && fallback.inputModalities !== undefined;
803
+ && fallback?.inputModalities !== undefined;
782
804
  const addReasoning = member.reasoningEfforts === undefined
783
- && fallback.reasoningEfforts !== undefined;
784
- if (!addMaxInput && !addModalities && !addReasoning) return member;
805
+ && fallback?.reasoningEfforts !== undefined;
806
+ if (!addMaxInput && !adjustAutoCompact && !addModalities && !addReasoning) return member;
785
807
  return {
786
808
  ...member,
787
809
  // Never claim a larger input budget than the window, and prefer the model's own
788
810
  // measured ceiling when the fallback carries one.
789
- ...(addMaxInput
790
- ? { maxInputTokens: Math.min(fallback.maxInputTokens ?? contextWindow!, contextWindow!) }
791
- : {}),
792
- ...(addModalities ? { inputModalities: [...fallback.inputModalities!] } : {}),
793
- ...(addReasoning ? { reasoningEfforts: [...fallback.reasoningEfforts!] } : {}),
811
+ ...(addMaxInput ? { maxInputTokens: effectiveMaxInput } : {}),
812
+ ...(adjustAutoCompact && autoCompactTokenLimit !== undefined ? { autoCompactTokenLimit } : {}),
813
+ ...(addModalities ? { inputModalities: [...fallback!.inputModalities!] } : {}),
814
+ ...(addReasoning ? { reasoningEfforts: [...fallback!.reasoningEfforts!] } : {}),
794
815
  };
795
816
  };
796
817
 
@@ -871,6 +892,20 @@ export function resolveComboCatalogMember(
871
892
  const maxInputTokens = effectiveMaxInput !== undefined
872
893
  ? Math.min(effectiveMaxInput, contextWindow)
873
894
  : contextWindow;
895
+ const softCandidates = [
896
+ hinted.autoCompactTokenLimit,
897
+ base.autoCompactTokenLimit,
898
+ fallback?.autoCompactTokenLimit,
899
+ configuredAutoCompactTokenLimit(prov, target.model),
900
+ ].filter((value): value is number => typeof value === "number" && value > 0);
901
+ // A generic 128k synthesis is a catalog compatibility fallback, not evidence
902
+ // that a configured soft policy has an authoritative window to clamp against.
903
+ const hasAuthoritativeAutoCompactBasis = hintedContext !== undefined
904
+ || fallbackContext !== undefined
905
+ || contextCap !== undefined;
906
+ const autoCompactTokenLimit = hasAuthoritativeAutoCompactBasis && softCandidates.length > 0
907
+ ? clampAutoCompactTokenLimit(contextWindow, maxInputTokens, Math.min(...softCandidates))
908
+ : undefined;
874
909
 
875
910
  return {
876
911
  ...hinted,
@@ -878,6 +913,7 @@ export function resolveComboCatalogMember(
878
913
  ...(reasoningEfforts !== undefined ? { reasoningEfforts } : {}),
879
914
  contextWindow,
880
915
  maxInputTokens,
916
+ ...(autoCompactTokenLimit !== undefined ? { autoCompactTokenLimit } : {}),
881
917
  ...(fallbackCapped ? { contextCap, contextCapped: true as const } : {}),
882
918
  };
883
919
  }
@@ -1088,9 +1124,6 @@ export function catalogHintsFromModelsApiItem(providerName: string, item: Provid
1088
1124
  item.max_input_length,
1089
1125
  item.max_prompt_tokens,
1090
1126
  );
1091
- // Output ceilings only. A top-level `max_tokens` is ambiguous (some catalogs
1092
- // use it as context) and generated jawcode `maxTokens` is output-only — never
1093
- // treat either as a context window.
1094
1127
  const maxOutputTokens = positiveSafeInteger(
1095
1128
  limits?.max_output_tokens,
1096
1129
  item.max_output_tokens,
@@ -1213,14 +1246,49 @@ async function fetchProviderModelsWithAuth(
1213
1246
  }
1214
1247
  return merged;
1215
1248
  };
1249
+ if (prov.googleMode === "ai-studio-web") {
1250
+ clearProviderDiscoveryStatus(name);
1251
+ if (globalAiStudioRelayHub.hasActiveSessions()) {
1252
+ try {
1253
+ const streamRes = await globalAiStudioRelayHub.dispatchStream({
1254
+ url: "https://generativelanguage.googleapis.com/v1beta/models",
1255
+ method: "GET",
1256
+ });
1257
+ let rawBody = "";
1258
+ for await (const chunk of streamRes.chunks) {
1259
+ rawBody += chunk;
1260
+ }
1261
+ const json = JSON.parse(rawBody);
1262
+ if (Array.isArray(json?.models)) {
1263
+ const liveModels: CatalogModel[] = json.models.map((m: any) => {
1264
+ const rawId = typeof m.name === "string" ? m.name.replace(/^models\//, "") : "";
1265
+ return {
1266
+ id: rawId,
1267
+ provider: name,
1268
+ ...catalogHintsFromProviderConfig(name, prov, rawId, contextCap),
1269
+ };
1270
+ }).filter((m: any) => Boolean(m.id));
1271
+ if (liveModels.length > 0) {
1272
+ return observed(withConfiguredRetention(liveModels), "authoritative");
1273
+ }
1274
+ }
1275
+ } catch {
1276
+ /* fallback to configured models */
1277
+ }
1278
+ }
1279
+ return observed(configured, "authoritative");
1280
+ }
1281
+
1216
1282
  // Static catalogs never need an OAuth refresh or an upstream model request. Clear any
1217
1283
  // discovery failure left by an older live configuration even when the account is logged out.
1218
1284
  if (prov.liveModels === false) {
1219
1285
  clearProviderDiscoveryStatus(name);
1220
1286
  return observed(configured, "authoritative");
1221
1287
  }
1288
+ const cloudCodeAssist = effectiveGoogleMode(name, prov) === "cloud-code-assist";
1289
+ const antigravityOAuth = isAntigravityOAuthProvider(name, prov);
1222
1290
  const auth: ModelsAuthResolution = captured.observedAuth ?? (resolveAuth.kind === "refreshing"
1223
- ? prov.authMode === "oauth" && effectiveGoogleMode(name, prov) === "cloud-code-assist"
1291
+ ? antigravityOAuth && cloudCodeAssist
1224
1292
  ? await getValidAccessTokenSnapshot(name)
1225
1293
  .then(snapshot => ({
1226
1294
  apiKey: snapshot.accessToken,
@@ -1304,14 +1372,14 @@ async function fetchProviderModelsWithAuth(
1304
1372
  "degraded",
1305
1373
  );
1306
1374
  }
1307
- if (prov.authMode === "oauth" && !apiKey) {
1375
+ if ((prov.authMode === "oauth" || antigravityOAuth) && !apiKey) {
1308
1376
  // No usable token (logged out, or account marked needsReauth). Still surface the
1309
1377
  // configured static catalog so the GUI Models tab / rail counts are not empty —
1310
1378
  // matching Cursor's !apiKey → configured degradation and fetch-failure fallback.
1311
1379
  return observed(configured, "degraded");
1312
1380
  }
1313
- const cloudCodeAssist = effectiveGoogleMode(name, prov) === "cloud-code-assist";
1314
- const project = prov.project ?? auth.oauthProjectId;
1381
+ if (antigravityOAuth && !cloudCodeAssist) return observed(configured, "degraded");
1382
+ const project = antigravityOAuth ? auth.oauthProjectId : prov.project ?? auth.oauthProjectId;
1315
1383
  if (cloudCodeAssist && !project) return observed(configured, "degraded");
1316
1384
  const fresh = getFreshCached(name, ttlMs);
1317
1385
  if (fresh) {
@@ -1328,13 +1396,9 @@ async function fetchProviderModelsWithAuth(
1328
1396
  const stale = getStaleCached(name);
1329
1397
  return observed(
1330
1398
  withConfiguredRetention(
1331
- (stale
1399
+ stale
1332
1400
  ? withVertexDefaultSeed(applyConfigHintsToCachedModels(name, prov, stale, contextCap))
1333
- : failedDiscoveryConfigured
1334
- ).map(model => enrichCatalogModelMetadata(model, {
1335
- liveFresh: false,
1336
- caps: capsFromProvider(prov, model.id, contextCap),
1337
- })),
1401
+ : failedDiscoveryConfigured,
1338
1402
  ),
1339
1403
  "degraded",
1340
1404
  );
@@ -1361,14 +1425,12 @@ async function fetchProviderModelsWithAuth(
1361
1425
  markModelsFetchFailure(name);
1362
1426
  markProviderDiscoveryFailed(name, failure);
1363
1427
  const stale = getStaleCached(name);
1364
- const fallbackModels = stale
1365
- ? withVertexDefaultSeed(applyConfigHintsToCachedModels(name, prov, stale, contextCap))
1366
- : failedDiscoveryConfigured;
1367
1428
  return {
1368
- models: withConfiguredRetention(fallbackModels.map(model => enrichCatalogModelMetadata(model, {
1369
- liveFresh: false,
1370
- caps: capsFromProvider(prov, model.id, contextCap),
1371
- }))),
1429
+ models: withConfiguredRetention(
1430
+ stale
1431
+ ? withVertexDefaultSeed(applyConfigHintsToCachedModels(name, prov, stale, contextCap))
1432
+ : failedDiscoveryConfigured,
1433
+ ),
1372
1434
  fallback: stale ? "stale" : "configured",
1373
1435
  shouldLog,
1374
1436
  };
@@ -1435,40 +1497,19 @@ async function fetchProviderModelsWithAuth(
1435
1497
  return observed(models, "degraded");
1436
1498
  }
1437
1499
  if (antigravity) {
1438
- const observedAt = new Date().toISOString();
1439
- const snapshotRows: LiveSnapshotRow[] = [];
1440
- const live = antigravity.map(model => {
1441
- const hints = {
1442
- // CCA only exposes a numeric thinking budget. Until the adapter owns an exact Codex
1443
- // effort-to-wire mapping for a newly discovered model, do not advertise a false ladder.
1444
- reasoningEfforts: [] as string[],
1445
- ...(model.contextWindow ? { contextWindow: model.contextWindow } : {}),
1446
- ...(model.inputModalities ? { inputModalities: model.inputModalities } : {}),
1447
- };
1448
- if (model.contextWindow || model.inputModalities) {
1449
- snapshotRows.push({
1450
- id: model.id,
1451
- ...(model.contextWindow ? { contextWindow: model.contextWindow } : {}),
1452
- ...(model.inputModalities ? { inputModalities: model.inputModalities } : {}),
1453
- observedAt,
1454
- });
1455
- }
1456
- const enriched = enrichCatalogModelMetadata({
1457
- id: model.id,
1458
- provider: name,
1459
- ...hints,
1460
- ...(model.contextWindow ? { metadataSource: "live" as const, metadataObservedAt: observedAt } : {}),
1461
- }, {
1462
- liveFresh: true,
1463
- caps: capsFromProvider(prov, model.id, contextCap),
1464
- });
1465
- return applyProviderConfigHints(name, prov, enriched, contextCap);
1466
- });
1500
+ const live = antigravity.map(model => applyProviderConfigHints(name, prov, {
1501
+ id: model.id,
1502
+ provider: name,
1503
+ // CCA only exposes a numeric thinking budget. Until the adapter owns an exact Codex
1504
+ // effort-to-wire mapping for a newly discovered model, do not advertise a false ladder.
1505
+ reasoningEfforts: [],
1506
+ ...(model.contextWindow ? { contextWindow: model.contextWindow } : {}),
1507
+ ...(model.inputModalities ? { inputModalities: model.inputModalities } : {}),
1508
+ }, contextCap));
1467
1509
  const forCache = withConfiguredRetention(live, { retainComboTargets: false });
1468
1510
  if (!setCached(name, forCache, Date.now(), cacheGeneration)) {
1469
1511
  return observed(withConfiguredRetention(configured), "degraded");
1470
1512
  }
1471
- persistLiveModelMetadata(name, snapshotRows, { writerGeneration: cacheGeneration, observedAt });
1472
1513
  registerAntigravityDiscoveredWireModels(prov.baseUrl, antigravity, {
1473
1514
  provider: name,
1474
1515
  cacheGeneration,
@@ -1493,30 +1534,22 @@ async function fetchProviderModelsWithAuth(
1493
1534
  return observed(models, "degraded");
1494
1535
  }
1495
1536
  const items = extracted.items;
1496
- const observedAt = new Date().toISOString();
1497
- const snapshotRows: LiveSnapshotRow[] = [];
1498
1537
  const live = items.map(m => {
1499
1538
  const ownedBy = boundedOwnedBy(m.owned_by);
1500
- const hints = catalogHintsFromModelsApiItem(name, m);
1501
- const discovered = Boolean(hints.contextWindow || hints.maxInputTokens || hints.maxOutputTokens
1502
- || hints.inputModalities || hints.capabilities || hints.reasoningEfforts);
1503
- if (discovered) {
1504
- snapshotRows.push({ id: m.id, ...hints, observedAt });
1505
- }
1506
- const enriched = enrichCatalogModelMetadata({
1539
+ return applyProviderConfigHints(name, prov, {
1507
1540
  id: m.id,
1508
1541
  provider: name,
1509
1542
  ...(ownedBy ? { owned_by: ownedBy } : {}),
1510
- ...hints,
1511
- ...(discovered ? { metadataSource: "live" as const, metadataObservedAt: observedAt } : {}),
1512
- }, {
1513
- liveFresh: true,
1514
- caps: capsFromProvider(prov, m.id, contextCap),
1515
- });
1516
- return applyProviderConfigHints(name, prov, enriched, contextCap);
1543
+ ...catalogHintsFromModelsApiItem(name, m),
1544
+ }, contextCap);
1517
1545
  })
1518
1546
  .filter(m => shouldExposeProviderModel(name, m.id));
1547
+ // Capture the count BEFORE the alias/configured augmentation below pushes extra rows into
1548
+ // `live`; otherwise configured entries would be reported as discovered ones.
1519
1549
  const liveModelCount = live.length;
1550
+ // Dated-release aliases + configured retention (compat allow-list, combo targets,
1551
+ // Vertex default). Cache without combo retention so a later gather re-applies the
1552
+ // current capture's retain set on read (warm-cache OCX-111 / #1308).
1520
1553
  const forCache = withConfiguredRetention(live, { retainComboTargets: false });
1521
1554
  const returned = withConfiguredRetention(forCache, { warnDrops: true });
1522
1555
  const droppedConfiguredIds = configured
@@ -1530,7 +1563,6 @@ async function fetchProviderModelsWithAuth(
1530
1563
  if (!setCached(name, forCache, Date.now(), cacheGeneration)) {
1531
1564
  return observed(withConfiguredRetention(configured), "degraded");
1532
1565
  }
1533
- persistLiveModelMetadata(name, snapshotRows, { writerGeneration: cacheGeneration, observedAt });
1534
1566
  markProviderDiscoveryOk(name, liveModelCount);
1535
1567
  return observed(returned, "authoritative");
1536
1568
  } catch (error) {
@@ -1641,7 +1673,24 @@ export function filterCatalogVisibleModels(
1641
1673
  const allowByProvider = new Map<string, Set<string>>();
1642
1674
  for (const [name, prov] of Object.entries(config.providers)) {
1643
1675
  const sel = prov.selectedModels;
1644
- if (Array.isArray(sel) && sel.length > 0) allowByProvider.set(name, new Set(sel));
1676
+ // Keyed the way `sync.ts` keys the same list, so a slash-bearing native id and
1677
+ // the encoded slug the Codex picker displays are one entry rather than two. A
1678
+ // bare `Set(sel)` matched only the native form, so an allowlist written from the
1679
+ // displayed slug — which `ocx models remove` also accepts — hid every model it
1680
+ // was meant to keep.
1681
+ //
1682
+ // The key is deliberately lossy: `p/a/b` and `p/a-b` collapse to one entry, so a
1683
+ // provider publishing both spellings has them selected together. That is a real
1684
+ // limitation, pinned by the tests below and tracked as a follow-up; it is NOT
1685
+ // fixed here. Resolving selections against the current roster instead was tried
1686
+ // and rejected — the roster is an incomplete dictionary (live discovery can omit
1687
+ // a published id), so it produces the same over-grant while additionally
1688
+ // disagreeing with the `slugEquivalenceKey` contract `sync.ts` uses at merge time.
1689
+ // Two catalog stages with different equivalence relations is the exact bug class
1690
+ // this change exists to remove.
1691
+ if (Array.isArray(sel) && sel.length > 0) {
1692
+ allowByProvider.set(name, new Set(sel.map(model => slugEquivalenceKey(routedSlug(name, model)))));
1693
+ }
1645
1694
  }
1646
1695
  return models.filter(m => {
1647
1696
  const nativeAlias = m.provider === COMBO_NAMESPACE && m.nativeAlias === true;
@@ -1653,7 +1702,7 @@ export function filterCatalogVisibleModels(
1653
1702
  if (slugEquals(stored, m.provider, m.id)) return false;
1654
1703
  }
1655
1704
  const allow = allowByProvider.get(m.provider);
1656
- return !allow || allow.has(m.id);
1705
+ return !allow || allow.has(slugEquivalenceKey(routedSlug(m.provider, m.id)));
1657
1706
  });
1658
1707
  }
1659
1708
 
@@ -1838,6 +1887,7 @@ async function gatherRoutedModelsUncached(
1838
1887
  // stay separate fields because routed/API rows of the same family run a wider window.
1839
1888
  // Falls back to the window for slugs with no separate ceiling.
1840
1889
  maxInputTokens: Math.min(nativeOpenAiMaxInputTokens(slug, openaiContextCap) ?? contextWindow, contextWindow),
1890
+ autoCompactTokenLimit: nativeOpenAiAutoCompactTokenLimit(slug, openaiContextCap),
1841
1891
  inputModalities: nativeInputModalities(slug),
1842
1892
  reasoningEfforts: nativeReasoningEfforts(slug),
1843
1893
  ...(nativeParallelToolCalls(slug) ? { parallelToolCalls: true } : {}),
@@ -1854,18 +1904,23 @@ async function gatherRoutedModelsUncached(
1854
1904
  for (const id of listComboIds(config)) {
1855
1905
  const combo = getCombo(config, id);
1856
1906
  if (!combo) continue;
1907
+ const comboNativeLimits = nativeContextLimits(config);
1857
1908
  const nativeContextWindow = combo.nativeAlias && combo.alias
1858
- ? nativeOpenAiContextWindow(combo.alias, nativeContextLimits(config))
1909
+ ? nativeOpenAiContextWindow(combo.alias, comboNativeLimits)
1859
1910
  : undefined;
1860
1911
  const nativeAliasMaxInput = combo.nativeAlias && combo.alias
1861
1912
  ? (combo.alias.startsWith("gpt-5.6-") || combo.alias.includes("daybreak")
1862
1913
  ? NATIVE_GPT56_MAX_INPUT_TOKENS
1863
1914
  : nativeOpenAiMaxInputTokens(combo.alias) ?? nativeOpenAiContextWindow(combo.alias))
1864
1915
  : undefined;
1916
+ const nativeAliasAutoCompact = combo.nativeAlias && combo.alias
1917
+ ? nativeOpenAiAutoCompactTokenLimit(combo.alias, comboNativeLimits)
1918
+ : undefined;
1865
1919
  const nativeAliasFallback = combo.nativeAlias && combo.alias && nativeContextWindow !== undefined
1866
1920
  ? {
1867
1921
  contextWindow: nativeContextWindow,
1868
1922
  ...(nativeAliasMaxInput !== undefined ? { maxInputTokens: nativeAliasMaxInput } : {}),
1923
+ ...(nativeAliasAutoCompact !== undefined ? { autoCompactTokenLimit: nativeAliasAutoCompact } : {}),
1869
1924
  inputModalities: nativeInputModalities(combo.alias),
1870
1925
  reasoningEfforts: nativeReasoningEfforts(combo.alias),
1871
1926
  }
@@ -1928,9 +1983,23 @@ async function gatherRoutedModelsUncached(
1928
1983
  const nativeAliasMaxInputTokens = codexForwardNativeCapabilityAlias
1929
1984
  ? nativeOpenAiMaxInputTokens(cm.modelId, customNativeLimits)
1930
1985
  : undefined;
1931
- const customMaxInputTokens = nativeAliasMaxInputTokens !== undefined && customContextWindow !== undefined
1932
- ? Math.min(nativeAliasMaxInputTokens, customContextWindow)
1933
- : nativeAliasMaxInputTokens;
1986
+ const configuredMaxInput = rawProvider
1987
+ ? configuredMaxInputTokens(rawProvider, cm.modelId)
1988
+ : undefined;
1989
+ const hardMaxCandidates = [nativeAliasMaxInputTokens, configuredMaxInput]
1990
+ .filter((value): value is number => typeof value === "number" && value > 0);
1991
+ const customMaxInputTokens = hardMaxCandidates.length > 0
1992
+ ? Math.min(
1993
+ ...hardMaxCandidates,
1994
+ ...(customContextWindow !== undefined ? [customContextWindow] : []),
1995
+ )
1996
+ : undefined;
1997
+ const configuredAutoCompact = configuredAutoCompactTokenLimit(rawProvider, cm.modelId);
1998
+ const customAutoCompactTokenLimit = codexForwardNativeCapabilityAlias
1999
+ ? nativeOpenAiAutoCompactTokenLimit(cm.modelId, customNativeLimits)
2000
+ : customContextWindow !== undefined && configuredAutoCompact !== undefined
2001
+ ? clampAutoCompactTokenLimit(customContextWindow, customMaxInputTokens, configuredAutoCompact)
2002
+ : undefined;
1934
2003
  const nativeAliasDefaultEffort = codexForwardNativeCapabilityAlias
1935
2004
  ? nativeDefaultReasoningEffort(cm.modelId)
1936
2005
  : undefined;
@@ -1951,6 +2020,7 @@ async function gatherRoutedModelsUncached(
1951
2020
  : codexForwardNativeCapabilityAlias ? { displayName: "Daybreak Blue" } : {}),
1952
2021
  ...(customContextWindow !== undefined ? { contextWindow: customContextWindow } : {}),
1953
2022
  ...(customMaxInputTokens !== undefined ? { maxInputTokens: customMaxInputTokens } : {}),
2023
+ ...(customAutoCompactTokenLimit !== undefined ? { autoCompactTokenLimit: customAutoCompactTokenLimit } : {}),
1954
2024
  ...(cm.inputModalities
1955
2025
  ? { inputModalities: cm.inputModalities }
1956
2026
  : codexForwardNativeCapabilityAlias ? { inputModalities: nativeInputModalities(cm.modelId) } : {}),
@@ -1996,10 +2066,18 @@ async function gatherRoutedModelsUncached(
1996
2066
  // along when it is actually a member — otherwise a provider default like "xhigh" would
1997
2067
  // re-apply onto a narrower custom ladder and override the fallback in applyReasoningLevels.
1998
2068
  const effectiveLadder = base.reasoningEfforts ?? replaced?.reasoningEfforts;
2069
+ const mergedMaxInputCandidates = [base.maxInputTokens, replaced?.maxInputTokens]
2070
+ .filter((value): value is number => typeof value === "number" && value > 0);
2071
+ const mergedMaxInput = mergedMaxInputCandidates.length > 0
2072
+ ? Math.min(...mergedMaxInputCandidates)
2073
+ : undefined;
1999
2074
  const merged: CatalogModel = replaced ? {
2000
2075
  ...base,
2001
2076
  ...(base.contextWindow === undefined && replaced.contextWindow !== undefined ? { contextWindow: replaced.contextWindow } : {}),
2002
- ...(base.maxInputTokens === undefined && replaced.maxInputTokens !== undefined ? { maxInputTokens: replaced.maxInputTokens } : {}),
2077
+ ...(mergedMaxInput !== undefined ? { maxInputTokens: mergedMaxInput } : {}),
2078
+ ...(base.autoCompactTokenLimit === undefined && replaced.autoCompactTokenLimit !== undefined
2079
+ ? { autoCompactTokenLimit: replaced.autoCompactTokenLimit }
2080
+ : {}),
2003
2081
  ...(base.inputModalities === undefined && replaced.inputModalities !== undefined ? { inputModalities: replaced.inputModalities } : {}),
2004
2082
  ...(base.reasoningEfforts === undefined && replaced.reasoningEfforts !== undefined ? { reasoningEfforts: replaced.reasoningEfforts } : {}),
2005
2083
  ...(base.defaultReasoningEffort === undefined && replaced.defaultReasoningEffort !== undefined
@@ -2016,14 +2094,36 @@ async function gatherRoutedModelsUncached(
2016
2094
  // (#349/#344). Deliberately NOT the full applyProviderConfigHints pass — custom rows are a
2017
2095
  // user override, so their explicit contextWindow / inputModalities / reasoning fields must be
2018
2096
  // preserved verbatim (the hint pass would cap context and overwrite modalities from registry).
2097
+ const mergedContext = typeof merged.contextWindow === "number" && merged.contextWindow > 0
2098
+ ? merged.contextWindow
2099
+ : undefined;
2100
+ const boundedMergedMaxInput = typeof merged.maxInputTokens === "number" && merged.maxInputTokens > 0
2101
+ ? (mergedContext !== undefined ? Math.min(merged.maxInputTokens, mergedContext) : merged.maxInputTokens)
2102
+ : undefined;
2103
+ const mergedWithHardBounds = boundedMergedMaxInput !== undefined
2104
+ && boundedMergedMaxInput !== merged.maxInputTokens
2105
+ ? { ...merged, maxInputTokens: boundedMergedMaxInput }
2106
+ : merged;
2107
+ const mergedSoftCandidates = [mergedWithHardBounds.autoCompactTokenLimit, configuredAutoCompact]
2108
+ .filter((value): value is number => typeof value === "number" && value > 0);
2109
+ const mergedWithAutoCompact: CatalogModel = mergedContext !== undefined && mergedSoftCandidates.length > 0
2110
+ ? {
2111
+ ...mergedWithHardBounds,
2112
+ autoCompactTokenLimit: clampAutoCompactTokenLimit(
2113
+ mergedContext,
2114
+ boundedMergedMaxInput,
2115
+ Math.min(...mergedSoftCandidates),
2116
+ ),
2117
+ }
2118
+ : mergedWithHardBounds;
2019
2119
  const enrichedProvider = enrichedByName.get(cm.provider) ?? rawProvider;
2020
- if (enrichedProvider && modelInList(enrichedProvider.noVisionModels, merged.id)) {
2021
- const current = merged.inputModalities ?? ["text"];
2120
+ if (enrichedProvider && modelInList(enrichedProvider.noVisionModels, mergedWithAutoCompact.id)) {
2121
+ const current = mergedWithAutoCompact.inputModalities ?? ["text"];
2022
2122
  if (!current.includes("image")) {
2023
- return { ...merged, inputModalities: [...current, "image"] };
2123
+ return { ...mergedWithAutoCompact, inputModalities: [...current, "image"] };
2024
2124
  }
2025
2125
  }
2026
- return merged;
2126
+ return mergedWithAutoCompact;
2027
2127
  });
2028
2128
  // Custom rows override discovered rows that encode to the same Codex-facing slug.
2029
2129
  const customKeys = new Set(customModels.map(c => routedSlug(c.provider, c.id)));
@@ -2079,7 +2179,15 @@ function augmentRoutedModelsWithCapturedOpenAiApiRows(
2079
2179
  ? Math.min(officialContext, userContext ?? officialContext, providerCap ?? officialContext)
2080
2180
  : undefined;
2081
2181
  const maxInputTokens = typeof officialMaxInput === "number"
2082
- ? Math.min(officialMaxInput, userMaxInput ?? officialMaxInput)
2182
+ ? Math.min(
2183
+ officialMaxInput,
2184
+ userMaxInput ?? officialMaxInput,
2185
+ contextWindow ?? officialMaxInput,
2186
+ )
2187
+ : undefined;
2188
+ const configuredAutoCompact = configuredAutoCompactTokenLimit(configured, id);
2189
+ const autoCompactTokenLimit = contextWindow !== undefined && configuredAutoCompact !== undefined
2190
+ ? clampAutoCompactTokenLimit(contextWindow, maxInputTokens, configuredAutoCompact)
2083
2191
  : undefined;
2084
2192
  return {
2085
2193
  provider: OPENAI_API_PROVIDER_ID,
@@ -2087,6 +2195,7 @@ function augmentRoutedModelsWithCapturedOpenAiApiRows(
2087
2195
  owned_by: OPENAI_API_PROVIDER_ID,
2088
2196
  ...(contextWindow ? { contextWindow } : {}),
2089
2197
  ...(maxInputTokens ? { maxInputTokens } : {}),
2198
+ ...(autoCompactTokenLimit !== undefined ? { autoCompactTokenLimit } : {}),
2090
2199
  ...(policy.modelInputModalities?.[id] ? { inputModalities: [...policy.modelInputModalities[id]!] } : {}),
2091
2200
  ...(policy.modelReasoningEfforts?.[id] ? { reasoningEfforts: [...policy.modelReasoningEfforts[id]!] } : {}),
2092
2201
  };
@@ -2116,14 +2225,7 @@ export function augmentRoutedModelsWithMetadata(
2116
2225
  providers?: Record<string, OcxProviderConfig>,
2117
2226
  caps?: Pick<OcxConfig, "providerContextCaps">,
2118
2227
  ): CatalogModel[] {
2119
- const out = models.map((model) => {
2120
- const provider = providers?.[model.provider];
2121
- const contextCap = caps ? providerContextCap(caps, model.provider) : undefined;
2122
- return enrichCatalogModelMetadata(model, {
2123
- liveFresh: model.metadataSource === "live" && model.metadataStale !== true,
2124
- caps: capsFromProvider(provider, model.id, contextCap),
2125
- });
2126
- });
2228
+ const out = [...models];
2127
2229
  const seen = new Set(out.map(m => `${m.provider}/${m.id}`));
2128
2230
  for (const provider of providerNames) {
2129
2231
  if (!JAWCODE_CATALOG_AUGMENT_PROVIDERS.has(provider)) continue;
@@ -2142,13 +2244,10 @@ export function augmentRoutedModelsWithMetadata(
2142
2244
  ...(typeof meta.contextWindow === "number" && meta.contextWindow > 0 ? { contextWindow: meta.contextWindow } : {}),
2143
2245
  ...(Array.isArray(meta.input) && meta.input.length > 0 ? { inputModalities: [...meta.input] } : {}),
2144
2246
  };
2145
- const hinted = providers?.[provider]
2146
- ? applyProviderConfigHints(provider, providers[provider], model, contextCap)
2147
- : model;
2148
- out.push(enrichCatalogModelMetadata(hinted, {
2149
- liveFresh: false,
2150
- caps: capsFromProvider(providers?.[provider], meta.id, contextCap),
2151
- }));
2247
+ out.push({
2248
+ ...model,
2249
+ ...(providers?.[provider] ? applyProviderConfigHints(provider, providers[provider], model, contextCap) : {}),
2250
+ });
2152
2251
  }
2153
2252
  }
2154
2253
  return out;
@@ -1158,7 +1158,7 @@ export function mergeCatalogEntriesForSync(
1158
1158
  isNativeAliasCatalogEntry(entry) && typeof entry.slug === "string" ? [entry.slug] : []
1159
1159
  )),
1160
1160
  ),
1161
- openaiContextCap?: number,
1161
+ openaiContextCap?: NativeContextLimitsInput,
1162
1162
  keepNativeChatGptOnV1 = false,
1163
1163
  ): RawEntry[] {
1164
1164
  // Retained for source compatibility with the original helper contract. Raw provider ids must