@bitkyc08/opencodex 2.24.1 → 2.25.0-preview.20260818

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 (58) hide show
  1. package/gui/dist/assets/{index-C3FiAveG.js → index-TFd4xi1L.js} +8 -8
  2. package/gui/dist/index.html +1 -1
  3. package/package.json +1 -1
  4. package/src/adapters/anthropic.ts +42 -0
  5. package/src/adapters/client-fingerprint.ts +9 -5
  6. package/src/adapters/cline-pass-deepseek-v4-tool-replay.ts +69 -0
  7. package/src/adapters/command-code.ts +17 -0
  8. package/src/adapters/cursor/cursor-errors.ts +49 -0
  9. package/src/adapters/cursor/live-models.ts +36 -2
  10. package/src/adapters/cursor/live-transport.ts +55 -4
  11. package/src/adapters/cursor/native-exec.ts +9 -0
  12. package/src/adapters/cursor/protobuf-request.ts +160 -9
  13. package/src/adapters/cursor/request-builder.ts +9 -1
  14. package/src/adapters/cursor/tool-definitions.ts +7 -2
  15. package/src/adapters/google-antigravity-wire.ts +1 -1
  16. package/src/adapters/google.ts +30 -12
  17. package/src/adapters/openai-responses-url.ts +5 -3
  18. package/src/adapters/registry.ts +3 -1
  19. package/src/adapters/tool-catalog-nudge.ts +76 -9
  20. package/src/bridge.ts +53 -9
  21. package/src/claude/context-windows.ts +2 -2
  22. package/src/claude/desktop-3p.ts +6 -6
  23. package/src/claude/model-info.ts +2 -2
  24. package/src/cli/claude-desktop.ts +2 -3
  25. package/src/codex/app-server-processes.ts +69 -35
  26. package/src/codex/catalog/metadata.ts +29 -10
  27. package/src/codex/catalog/provider-fetch.ts +21 -11
  28. package/src/codex/catalog.ts +1 -1
  29. package/src/codex/injected-marker.ts +9 -3
  30. package/src/codex/user-identity.ts +88 -6
  31. package/src/config.ts +1 -0
  32. package/src/generated/compatibility-version.json +61 -53
  33. package/src/grok/sync.ts +2 -4
  34. package/src/lab/projection/rebuild.ts +36 -18
  35. package/src/lib/windows-elevation.ts +18 -3
  36. package/src/lib/windows-secret-acl.ts +49 -19
  37. package/src/oauth/google-antigravity.ts +7 -2
  38. package/src/providers/antigravity-models.ts +126 -17
  39. package/src/providers/derive.ts +11 -1
  40. package/src/responses/parser.ts +4 -0
  41. package/src/responses/reasoning-replay-cache.ts +16 -1
  42. package/src/responses/thought-signature-replay.ts +17 -1
  43. package/src/responses/truncated-stop-reason.ts +60 -0
  44. package/src/router.ts +2 -10
  45. package/src/routing/capability.ts +5 -6
  46. package/src/server/index.ts +3 -4
  47. package/src/server/management/agent-settings-routes.ts +5 -5
  48. package/src/server/management/config-routes.ts +2 -2
  49. package/src/server/management/context.ts +2 -0
  50. package/src/server/management/native-integration-routes.ts +3 -3
  51. package/src/server/management/provider-routes.ts +22 -0
  52. package/src/server/management/shared.ts +4 -4
  53. package/src/server/management-api.ts +2 -2
  54. package/src/server/request-log.ts +11 -3
  55. package/src/server/responses/core.ts +4 -1
  56. package/src/server/responses/input-admission.ts +13 -10
  57. package/src/server/system-env.ts +3 -3
  58. package/src/types.ts +13 -1
@@ -0,0 +1,60 @@
1
+ /**
2
+ * Whether a `done` event's `stopReason` means the turn was cut short rather than finishing, and
3
+ * which Responses `incomplete_details.reason` it maps to.
4
+ *
5
+ * `stopReason` is an open-ended string and adapters do not agree on a vocabulary: openai-chat and
6
+ * google normalize to `max_tokens`/`content_filter`, Command Code forwards the raw provider or AI
7
+ * SDK value (`length`, `content-filter`, `error`), and Anthropic forwards `stop_reason` verbatim
8
+ * (`refusal`, `model_context_window_exceeded`, ...). A guard that matched only the two canonical
9
+ * strings let those turns read as completed — and, on a compaction turn, install a half-written
10
+ * summary as replacement history (#422).
11
+ *
12
+ * Classifying here keeps that decision independent of which adapter produced the event, and keeps
13
+ * suppression and terminal status in agreement: a turn whose compaction item is withheld must not
14
+ * also report success, or codex-rs receives a completed response with zero compaction items and
15
+ * fatals.
16
+ *
17
+ * Unknown reasons are deliberately NOT truncated. This must never turn a healthy turn into a
18
+ * failure, and an unrecognized value is far more likely an ordinary stop.
19
+ */
20
+ type TruncationKind = "max_output_tokens" | "content_filter";
21
+
22
+ const TRUNCATED_STOP_REASONS = new Map<string, TruncationKind>([
23
+ // canonical (openai-chat, google)
24
+ ["max_tokens", "max_output_tokens"],
25
+ ["content_filter", "content_filter"],
26
+ // raw OpenAI / Command Code (AI SDK) finish reasons
27
+ ["length", "max_output_tokens"],
28
+ ["content-filter", "content_filter"],
29
+ // raw Anthropic stop reasons
30
+ ["max_output_tokens", "max_output_tokens"],
31
+ ["model_context_window_exceeded", "max_output_tokens"],
32
+ ["refusal", "content_filter"],
33
+ // Anthropic documents `pause_turn` as a long-running turn that the client is expected to
34
+ // CONTINUE. Whatever was produced so far is by definition unfinished, so it must not be
35
+ // installed as replacement history.
36
+ ["pause_turn", "max_output_tokens"],
37
+ // raw Gemini / Vertex finish reasons
38
+ ["malformed_function_call", "content_filter"],
39
+ ["malformed_response", "content_filter"],
40
+ ["unexpected_tool_call", "content_filter"],
41
+ ["safety", "content_filter"],
42
+ ["recitation", "content_filter"],
43
+ ["blocklist", "content_filter"],
44
+ ["prohibited_content", "content_filter"],
45
+ ["spii", "content_filter"],
46
+ ["image_safety", "content_filter"],
47
+ ["language", "content_filter"],
48
+ // Kiro
49
+ ["model_context_window_exceeded_exception", "max_output_tokens"],
50
+ ]);
51
+
52
+ /** The `incomplete_details.reason` a truncated stop maps to, or undefined for a normal stop. */
53
+ export function truncationReasonFor(stopReason: string | undefined): TruncationKind | undefined {
54
+ if (stopReason === undefined) return undefined;
55
+ return TRUNCATED_STOP_REASONS.get(stopReason.trim().toLowerCase());
56
+ }
57
+
58
+ export function isTruncatedStopReason(stopReason: string | undefined): boolean {
59
+ return truncationReasonFor(stopReason) !== undefined;
60
+ }
package/src/router.ts CHANGED
@@ -12,7 +12,7 @@ import { hasOwnProvider, resolveEnvValue } from "./config";
12
12
  import { assertProviderDestinationAllowed } from "./lib/destination-policy";
13
13
  import { redactSecretString, redactUrlForLog } from "./lib/redact";
14
14
  import { PROVIDER_REGISTRY, providerCodexAccountMode } from "./providers/registry";
15
- import { applyDirectReasoningEffortContracts } from "./providers/derive";
15
+ import { applyDirectReasoningEffortContracts, hasLegacyClinePassReasoningEfforts } from "./providers/derive";
16
16
  import {
17
17
  providerMatchesRegistryTransportWithStaticGuards,
18
18
  providerSupportsLiveModelDiscovery,
@@ -286,14 +286,6 @@ export function routedProviderConfig(providerName: string, provider: OcxProvider
286
286
  const modelReasoningEffortMap = mergeNestedRecord(registryEntry.modelReasoningEffortMap, provider.modelReasoningEffortMap);
287
287
  const modelReasoningEfforts = mergeStringArrayRecord(registryEntry.modelReasoningEfforts, provider.modelReasoningEfforts);
288
288
  const modelDefaultReasoningEfforts = mergeRecordFill(registryEntry.modelDefaultReasoningEfforts, provider.modelDefaultReasoningEfforts);
289
- // Key-login used to persist this exact low-only ClinePass capability seed. Once the gateway's
290
- // wider input ladder was live-verified, leaving that generated row untouched would keep old
291
- // installs clamped forever. This branch is reached only after canonical transport matching, so
292
- // same-named custom destinations and every other explicit ladder still retain user precedence.
293
- const repairLegacyClinePassReasoningEfforts = providerName === "cline-pass"
294
- && provider.reasoningWireFormat === "gateway-object"
295
- && provider.reasoningEfforts?.length === 1
296
- && provider.reasoningEfforts[0] === "low";
297
289
  const modelContextWindows = providerName === OPENAI_API_PROVIDER_ID
298
290
  ? mergePositiveNumberCaps(registryEntry.modelContextWindows, provider.modelContextWindows)
299
291
  : mergeRecordFill(registryEntry.modelContextWindows, provider.modelContextWindows);
@@ -374,7 +366,7 @@ export function routedProviderConfig(providerName: string, provider: OcxProvider
374
366
  ...(provider.project === undefined && registryEntry.project !== undefined ? { project: registryEntry.project } : {}),
375
367
  ...(provider.location === undefined && registryEntry.location !== undefined ? { location: registryEntry.location } : {}),
376
368
  ...(provider.contextWindow === undefined && registryEntry.contextWindow !== undefined ? { contextWindow: registryEntry.contextWindow } : {}),
377
- ...((provider.reasoningEfforts === undefined || repairLegacyClinePassReasoningEfforts)
369
+ ...((provider.reasoningEfforts === undefined || hasLegacyClinePassReasoningEfforts(providerName, provider))
378
370
  && registryEntry.reasoningEfforts !== undefined
379
371
  ? { reasoningEfforts: [...registryEntry.reasoningEfforts] }
380
372
  : {}),
@@ -13,11 +13,10 @@
13
13
  import type { OcxConfig } from "../types";
14
14
  import { isCanonicalOpenAiForwardProvider, OPENAI_CODEX_PROVIDER_ID } from "../providers/openai-tiers";
15
15
  import { serviceTierSupportForModel } from "../providers/service-tier";
16
- import { applyProviderContextCap, providerContextCap } from "../providers/context-cap";
17
16
  import { PROVIDER_REGISTRY } from "../providers/registry";
18
17
  import {
19
18
  nativeInputModalities,
20
- nativeOpenAiContextWindow,
19
+ nativeContextLimits, nativeOpenAiContextWindow,
21
20
  nativeParallelToolCalls,
22
21
  nativeReasoningEfforts,
23
22
  } from "../codex/catalog/metadata";
@@ -164,11 +163,11 @@ export function candidateCapabilityEvidence(
164
163
  ?? provider?.contextWindow
165
164
  ?? registryEntry?.modelContextWindows?.[modelId]
166
165
  ?? catalogRow?.contextWindow
167
- ?? (isNative ? nativeOpenAiContextWindow(modelId) : undefined);
168
- // providerContextCaps.openai also ceilings native OpenAI rows (#1430), so routing
169
- // evidence never contradicts a capped catalog entry.
166
+ ?? (isNative ? nativeOpenAiContextWindow(modelId, nativeContextLimits(config)) : undefined);
167
+ // Native rows go through the accessor (raise-to-ceiling + opt-in). Routed rows keep
168
+ // the raw value; a provider cap on openai must not invent a window they do not have.
170
169
  const contextWindow = isNative
171
- ? applyProviderContextCap(rawContextWindow, providerContextCap(config, OPENAI_CODEX_PROVIDER_ID)) ?? rawContextWindow
170
+ ? (nativeOpenAiContextWindow(modelId, nativeContextLimits(config)) ?? rawContextWindow)
172
171
  : rawContextWindow;
173
172
 
174
173
  const modalities = provider?.modelInputModalities?.[modelId]
@@ -50,7 +50,6 @@ import { runOpenAiTierStartupMigration } from "../providers/openai-tier-startup"
50
50
  import { runAlibabaRegionStartupMigration } from "../providers/alibaba-region-startup";
51
51
  import { runModelRenameStartupMigration } from "../providers/model-rename-startup";
52
52
  import { isCanonicalOpenAiForwardProvider, OPENAI_CODEX_PROVIDER_ID } from "../providers/openai-tiers";
53
- import { providerContextCap } from "../providers/context-cap";
54
53
  import { providerCodexAccountMode } from "../providers/registry";
55
54
  import type { StorageCleanupPolicy } from "../types";
56
55
  import { MAX_DECOMPRESSED_BODY_BYTES } from "./request-decompress";
@@ -906,7 +905,7 @@ export function startServer(port?: number, deps: StartServerDeps = {}): Server<W
906
905
  }
907
906
  throw error;
908
907
  }
909
- const { accountBoundNativeOpenAiSlugsBySelector, 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");
908
+ const { accountBoundNativeOpenAiSlugsBySelector, applyNativeVisibility, buildCatalogEntries, configuredNativeAliasSlugs, desktopAllowlistSuppressedNativeSlugs, disabledNativeSlugs, exactComboCatalogSlugs, loadCatalogTemplate, NATIVE_OPENAI_MODELS, nativeContextLimits, nativeOpenAiSlugs, nativeReasoningEfforts, nativeDefaultReasoningEffort, orderForSubagents, filterCatalogVisibleModels, shouldIncludeAccountBoundNativeOpenAi, shouldIncludeNativeOpenAi, uniqueCatalogModelsForRawPublicList, visibleCodexAccountSelectors, visibleNativeSlugs, desktopVisibleNativeSlugs } = await import("../codex/catalog");
910
909
  const includeNativeOpenAi = shouldIncludeNativeOpenAi(config);
911
910
  const includeAccountBoundNativeOpenAi = shouldIncludeAccountBoundNativeOpenAi(config);
912
911
  const nativeSlugs = includeNativeOpenAi
@@ -958,7 +957,7 @@ export function startServer(port?: number, deps: StartServerDeps = {}): Server<W
958
957
  : idsParam === "desktop"
959
958
  ? "desktop3p" as const
960
959
  : (/^claude-code\//i.test(req.headers.get("user-agent") ?? "") ? "readable" as const : "desktop3p" as const);
961
- const data = buildAnthropicModelInfos([...desktopVisibleNativeSlugs(config)], goOrdered, resolveAutoContext(config.claudeCode), idStyle, activeDesktop3pAlias, providerContextCap(config, OPENAI_CODEX_PROVIDER_ID));
960
+ const data = buildAnthropicModelInfos([...desktopVisibleNativeSlugs(config)], goOrdered, resolveAutoContext(config.claudeCode), idStyle, activeDesktop3pAlias, nativeContextLimits(config));
962
961
  return jsonResponse({ data }, 200, req, policy);
963
962
  }
964
963
  if (url.searchParams.has("client_version")) {
@@ -985,7 +984,7 @@ export function startServer(port?: number, deps: StartServerDeps = {}): Server<W
985
984
  accountSelectors,
986
985
  suppressedBareNativeSlugs,
987
986
  new Set(),
988
- providerContextCap(config, OPENAI_CODEX_PROVIDER_ID),
987
+ nativeContextLimits(config),
989
988
  accountNativeSlugs,
990
989
  accountNativeSlugsBySelector,
991
990
  config.keepNativeChatGptOnV1 === true,
@@ -1,7 +1,7 @@
1
1
  import { randomUUID } from "node:crypto";
2
2
  import { readFileSync } from "node:fs";
3
3
  import type { CatalogModel } from "../../codex/catalog";
4
- import { catalogModelSlug, invalidateCodexModelsCache, nativeModelRows, uniqueCatalogModelsForPublicList } from "../../codex/catalog";
4
+ import { catalogModelSlug, invalidateCodexModelsCache, nativeContextLimits, nativeModelRows, uniqueCatalogModelsForPublicList } from "../../codex/catalog";
5
5
  import {
6
6
  DEFAULT_SUBAGENT_MODELS,
7
7
  codexAutoStartEnabled,
@@ -207,7 +207,7 @@ export async function handleAgentSettingsRoutes(ctx: ManagementContext): Promise
207
207
  current.apiKeys?.[0]?.key,
208
208
  "static",
209
209
  current.claudeCode.desktopProfile,
210
- providerContextCap(current, OPENAI_CODEX_PROVIDER_ID),
210
+ nativeContextLimits(current),
211
211
  );
212
212
  if (result.written && result.fingerprint) {
213
213
  current.claudeCode = { ...current.claudeCode, desktopProfile: { ...current.claudeCode.desktopProfile, appliedFingerprint: result.fingerprint, appliedAt: new Date().toISOString() } };
@@ -886,7 +886,7 @@ export async function handleAgentSettingsRoutes(ctx: ManagementContext): Promise
886
886
  latest.apiKeys?.[0]?.key,
887
887
  mode,
888
888
  state.profile,
889
- providerContextCap(latest, OPENAI_CODEX_PROVIDER_ID),
889
+ nativeContextLimits(latest),
890
890
  );
891
891
  if (!result.written) return jsonResponse({ error: result.reason ?? "Claude Desktop apply failed", saved: true, path: result.path }, 500);
892
892
  // Persist applied fingerprint + timestamp so GUI can show saved-vs-applied state.
@@ -978,7 +978,7 @@ export async function handleAgentSettingsRoutes(ctx: ManagementContext): Promise
978
978
  if (isDisabled(m.provider, m.id)) continue;
979
979
  aliases.push({ id: claudeCodeAlias(m.provider, m.id), display_name: `${m.id} (${m.provider})` });
980
980
  }
981
- const contextWindows = buildClaudeContextWindows([...visibleNativeSlugs(config)], models, providerContextCap(config, OPENAI_CODEX_PROVIDER_ID));
981
+ const contextWindows = buildClaudeContextWindows([...visibleNativeSlugs(config)], models, nativeContextLimits(config));
982
982
  const webSearchOverride = config.claudeCode?.webSearchSidecar;
983
983
  const visionOverride = config.claudeCode?.visionSidecar;
984
984
  // Auto is a RESOLUTION, recomputed per request — never stored state. Detection is
@@ -1010,7 +1010,7 @@ export async function handleAgentSettingsRoutes(ctx: ManagementContext): Promise
1010
1010
  classifierModel: config.claudeCode?.classifierModel ?? "",
1011
1011
  classifierFallbacks: config.claudeCode?.classifierFallbacks ?? [],
1012
1012
  systemEnv: config.claudeCode?.systemEnv === true,
1013
- autoConnectSupported: process.platform === "darwin",
1013
+ autoConnectSupported: (ctx.deps.platform ?? process.platform) === "darwin",
1014
1014
  maxContextTokens: config.claudeCode?.maxContextTokens ?? null,
1015
1015
  alwaysEnableEffort: config.claudeCode?.alwaysEnableEffort === true,
1016
1016
  autoContext: config.claudeCode?.autoContext !== false,
@@ -1,7 +1,7 @@
1
1
  import { randomUUID } from "node:crypto";
2
2
  import { readFileSync } from "node:fs";
3
3
  import type { CatalogModel } from "../../codex/catalog";
4
- import { catalogModelSlug, invalidateCodexModelsCache, nativeModelRows, uniqueCatalogModelsForPublicList } from "../../codex/catalog";
4
+ import { catalogModelSlug, invalidateCodexModelsCache, nativeContextLimits, nativeModelRows, uniqueCatalogModelsForPublicList } from "../../codex/catalog";
5
5
  import {
6
6
  DEFAULT_SUBAGENT_MODELS,
7
7
  codexAutoStartEnabled,
@@ -173,7 +173,7 @@ async function syncEnabledClientIntegrations(
173
173
  config.apiKeys?.[0]?.key,
174
174
  "static",
175
175
  config.claudeCode?.desktopProfile,
176
- providerContextCap(config, OPENAI_CODEX_PROVIDER_ID),
176
+ nativeContextLimits(config),
177
177
  );
178
178
  out.push(r.written
179
179
  ? { client: "claude-desktop", ok: true, changed: true }
@@ -16,6 +16,8 @@ import type {
16
16
  } from "../../codex/app-server-restart-service";
17
17
 
18
18
  export interface ManagementApiDeps {
19
+ /** Platform seam for capability projections; does not alter host-level startup behavior. */
20
+ platform?: NodeJS.Platform;
19
21
  toggleCodexMultiAgentV2?: (enabled: boolean) => void;
20
22
  toggleDefaultModeRequestUserInput?: (enabled: boolean) => void;
21
23
  createManagementConvergeCodex?: (config: Readonly<OcxConfig>) => ConvergeCodex;
@@ -18,7 +18,7 @@
18
18
  * 011 (Claude Code), 012 (Grok).
19
19
  */
20
20
  import { loadConfig, readRuntimePort, saveConfigPreservingClaudeCode } from "../../config";
21
- import { desktopVisibleNativeSlugs, filterCatalogVisibleModels, nativeOpenAiContextWindow, visibleNativeSlugs } from "../../codex/catalog";
21
+ import { desktopVisibleNativeSlugs, filterCatalogVisibleModels, nativeContextLimits, nativeOpenAiContextWindow, visibleNativeSlugs } from "../../codex/catalog";
22
22
  import { providerContextCap } from "../../providers/context-cap";
23
23
  import { OPENAI_CODEX_PROVIDER_ID } from "../../providers/openai-tiers";
24
24
  import { inspectDesktop3pConfigLibrary, removeDesktop3pStandardPivot, writeDesktop3pConfig } from "../../claude/desktop-3p";
@@ -508,7 +508,7 @@ async function handleGrokToggle(ctx: ManagementContext): Promise<Response> {
508
508
  // Native slugs carry their context window: without it Grok falls back
509
509
  // to its own 200k default and understates a 372k model.
510
510
  ...visibleNativeSlugs(config).map(id => {
511
- const contextWindow = nativeOpenAiContextWindow(id, providerContextCap(config, OPENAI_CODEX_PROVIDER_ID));
511
+ const contextWindow = nativeOpenAiContextWindow(id, nativeContextLimits(config));
512
512
  return { id, ...(contextWindow !== undefined ? { contextWindow } : {}) };
513
513
  }),
514
514
  ...routed.map(m => ({
@@ -663,7 +663,7 @@ async function handleClaudeDesktopToggle(ctx: ManagementContext): Promise<Respon
663
663
  latest.apiKeys?.[0]?.key,
664
664
  "static",
665
665
  latest.claudeCode?.desktopProfile,
666
- providerContextCap(latest, OPENAI_CODEX_PROVIDER_ID),
666
+ nativeContextLimits(latest),
667
667
  );
668
668
  if (!result.written) return postCommitRefusal(500, "claude-desktop", "write_failed", "Claude Desktop apply failed.", { desiredEnabled: latestDesiredEnabled });
669
669
  return jsonResponse({
@@ -33,6 +33,7 @@ import { replaceProviderAccountSet } from "../../oauth/store";
33
33
  import { providerDestinationResolvedError } from "../../lib/destination-policy";
34
34
  import { reconcileLiveStateStores } from "../../lib/state-store-registrations";
35
35
  import { ProviderOutboundPolicyError, providerOutboundGet, providerOutboundPost, providerRedirectError } from "../../lib/provider-outbound";
36
+ import { fetchCursorUsableModels } from "../../adapters/cursor/live-models";
36
37
  import { parseAntigravityAvailableModels } from "../../providers/antigravity-models";
37
38
  import { enrichProviderFromCatalog, listKeyLoginProviders } from "../../oauth/key-providers";
38
39
  import { deriveProviderPresets } from "../../providers/derive";
@@ -732,6 +733,27 @@ export async function handleProviderRoutes(ctx: ManagementContext): Promise<Resp
732
733
  if (prov.authMode === "oauth" && !apiKey) {
733
734
  return jsonResponse({ ok: false, latencyMs: 0, error: "static catalog only — upstream not verified (not logged in)" });
734
735
  }
736
+ if (prov.adapter === "cursor") {
737
+ const started = Date.now();
738
+ const live = await fetchCursorUsableModels({
739
+ apiKey: apiKey ?? "",
740
+ baseUrl: prov.baseUrl,
741
+ });
742
+ const latencyMs = Date.now() - started;
743
+ if (!live.ok) {
744
+ return jsonResponse({
745
+ ok: false,
746
+ latencyMs,
747
+ error: `cursor discovery ${live.error}${live.detail ? `: ${live.detail}` : ""}`,
748
+ });
749
+ }
750
+ return jsonResponse({
751
+ ok: true,
752
+ latencyMs,
753
+ models: live.models.length,
754
+ message: `Connected. ${live.models.length} models.`,
755
+ });
756
+ }
735
757
  const project = prov.project ?? snapshot?.projectId;
736
758
  if (antigravity && !project) {
737
759
  return jsonResponse({ ok: false, latencyMs: 0, error: "Antigravity project unavailable — re-run `ocx login google-antigravity`" });
@@ -192,11 +192,11 @@ export interface GrokCandidateModel {
192
192
  * from the same two sources as the sync so the two can never disagree.
193
193
  */
194
194
  export async function fetchGrokCandidateModels(config: OcxConfig): Promise<GrokCandidateModel[]> {
195
- const { filterCatalogVisibleModels, nativeOpenAiContextWindow, visibleNativeSlugs } = await import("../../codex/catalog");
195
+ const { filterCatalogVisibleModels, nativeContextLimits, nativeOpenAiContextWindow, visibleNativeSlugs } = await import("../../codex/catalog");
196
196
  const routed = filterCatalogVisibleModels(await fetchAllModels(config), config);
197
197
  return [
198
198
  ...visibleNativeSlugs(config).map(id => {
199
- const contextWindow = nativeOpenAiContextWindow(id, providerContextCap(config, OPENAI_CODEX_PROVIDER_ID));
199
+ const contextWindow = nativeOpenAiContextWindow(id, nativeContextLimits(config));
200
200
  return { id, native: true, ...(contextWindow !== undefined ? { contextWindow } : {}) };
201
201
  }),
202
202
  ...routed.map(m => ({
@@ -221,7 +221,7 @@ export function stripRegistryOnlyStaticHeaders(name: string, provider: OcxProvid
221
221
 
222
222
  /** Shared Desktop profile DTO builder for the management API and CLI. */
223
223
  export async function buildClaudeDesktopState(config: OcxConfig, stored?: OcxClaudeDesktopProfile) {
224
- const { filterCatalogVisibleModels, nativeOpenAiContextWindow, desktopVisibleNativeSlugs } = await import("../../codex/catalog");
224
+ const { filterCatalogVisibleModels, nativeContextLimits, nativeOpenAiContextWindow, desktopVisibleNativeSlugs } = await import("../../codex/catalog");
225
225
  const { DESKTOP_SUPPORTS_1M_THRESHOLD } = await import("../../claude/desktop-3p");
226
226
  const { reconcileDesktopProfile, renderDesktopProfile } = await import("../../claude/desktop-profile");
227
227
  const routed = filterCatalogVisibleModels(await fetchAllModels(config), config);
@@ -229,7 +229,7 @@ export async function buildClaudeDesktopState(config: OcxConfig, stored?: OcxCla
229
229
  // Native rows carry their real context window from the same accessor the Grok sync
230
230
  // uses — otherwise Sol's 372k and gpt-5.5's 272k render as blank on Desktop.
231
231
  ...desktopVisibleNativeSlugs(config).map(id => {
232
- const contextWindow = nativeOpenAiContextWindow(id, providerContextCap(config, OPENAI_CODEX_PROVIDER_ID));
232
+ const contextWindow = nativeOpenAiContextWindow(id, nativeContextLimits(config));
233
233
  return { route: `native/${id}`, label: `${id} (native)`,
234
234
  ...(contextWindow !== undefined ? { contextWindow } : {}) };
235
235
  }),
@@ -1,7 +1,7 @@
1
1
  import { randomUUID } from "node:crypto";
2
2
  import { readFileSync } from "node:fs";
3
3
  import type { CatalogModel } from "../codex/catalog";
4
- import { catalogModelSlug, invalidateCodexModelsCache, nativeModelRows, uniqueCatalogModelsForPublicList } from "../codex/catalog";
4
+ import { catalogModelSlug, invalidateCodexModelsCache, nativeContextLimits, nativeModelRows, uniqueCatalogModelsForPublicList } from "../codex/catalog";
5
5
  import {
6
6
  DEFAULT_SUBAGENT_MODELS,
7
7
  codexAutoStartEnabled,
@@ -205,7 +205,7 @@ export async function handleManagementAPI(
205
205
  import("../claude/context-windows"),
206
206
  import("../codex/catalog"),
207
207
  ]);
208
- injectClaudeAgentDefs(config, buildClaudeContextWindows([...visibleNativeSlugs(config)], models, providerContextCap(config, OPENAI_CODEX_PROVIDER_ID)));
208
+ injectClaudeAgentDefs(config, buildClaudeContextWindows([...visibleNativeSlugs(config)], models, nativeContextLimits(config)));
209
209
  } catch {
210
210
  // Keep routes available through a provider-discovery blip. A later
211
211
  // launch-time sync restores any context markers missing from this pass.
@@ -1004,10 +1004,14 @@ function finalizedUsage(
1004
1004
  const usageFallback = !finalUsage && estimate !== undefined
1005
1005
  ? { inputTokens: estimate, outputTokens: 0, estimated: true }
1006
1006
  : undefined;
1007
- const loggedUsage = finalUsage && estimate !== undefined
1007
+ const combinedInputTokens = finalUsage && estimate !== undefined
1008
+ ? Math.max(finalUsage.inputTokens, estimate)
1009
+ : undefined;
1010
+ const loggedUsage = finalUsage && combinedInputTokens !== undefined
1008
1011
  ? {
1009
1012
  ...finalUsage,
1010
- inputTokens: Math.max(finalUsage.inputTokens, estimate),
1013
+ inputTokens: combinedInputTokens,
1014
+ totalTokens: combinedInputTokens + finalUsage.outputTokens,
1011
1015
  estimated: true,
1012
1016
  }
1013
1017
  : finalUsage
@@ -1017,7 +1021,11 @@ function finalizedUsage(
1017
1021
  // ESTIMATE via capEstimateAtContextWindow, and Math.max preserves a real
1018
1022
  // provider-reported count, so it needs no further reduction.
1019
1023
  ? (finalUsage.estimated && contextWindow !== undefined && finalUsage.inputTokens > contextWindow
1020
- ? { ...finalUsage, inputTokens: contextWindow }
1024
+ ? {
1025
+ ...finalUsage,
1026
+ inputTokens: contextWindow,
1027
+ totalTokens: contextWindow + finalUsage.outputTokens,
1028
+ }
1021
1029
  : finalUsage)
1022
1030
  : usageFallback;
1023
1031
  const totalTokens = usageTotalTokens(loggedUsage);
@@ -2,6 +2,7 @@ import type { Server } from "bun";
2
2
  import { bridgeToResponsesSSE, buildResponseJSON, formatErrorResponse, type ResponsesTerminalStatus } from "../../bridge";
3
3
  import { formatPassthroughUpstreamError } from "./passthrough-error";
4
4
  import { checkInputAdmission } from "./input-admission";
5
+ import { nativeContextLimits } from "../../codex/catalog";
5
6
  import { describeUpstreamConnectFailure } from "./upstream-error";
6
7
  import {
7
8
  getConfigPath,
@@ -13,6 +14,7 @@ import {
13
14
  bindReasoningReplayScope,
14
15
  reasoningReplayCodexCredentialIdentity,
15
16
  reasoningReplayDestinationIdentity,
17
+ durableReplayDestinationIdentity,
16
18
  reasoningReplayKeyCredentialIdentity,
17
19
  reasoningReplayOAuthCredentialIdentity,
18
20
  } from "../../responses/reasoning-replay-cache";
@@ -335,6 +337,7 @@ function bindRouteReasoningReplayScope(args: {
335
337
  ? {
336
338
  providerName,
337
339
  providerDestinationIdentity,
340
+ providerDestinationDurableIdentity: durableReplayDestinationIdentity(provider.baseUrl),
338
341
  adapterName,
339
342
  modelId: parsed.modelId,
340
343
  credentialIdentity,
@@ -1994,7 +1997,7 @@ async function handleResponsesInner(
1994
1997
  // refusing the turn that shrinks the context would deadlock the client against the very
1995
1998
  // limit this gate reports — it would be told to compact and then denied the compaction.
1996
1999
  if (parsed._compactionRequest !== true) {
1997
- const inputAdmission = checkInputAdmission(parsed, route.provider, route.providerName, parsed.modelId, providerContextCap(config, OPENAI_CODEX_PROVIDER_ID));
2000
+ const inputAdmission = checkInputAdmission(parsed, route.provider, route.providerName, parsed.modelId, nativeContextLimits(config));
1998
2001
  if (!inputAdmission.admitted) {
1999
2002
  // #1524: this is a LOCAL preflight refusal, not an upstream verdict. A policy or combo
2000
2003
  // fallback must be able to skip this candidate and try one whose context window fits,
@@ -10,7 +10,7 @@
10
10
  * catches the pathological case and stays out of the way otherwise. Every uncertainty
11
11
  * resolves toward admitting.
12
12
  */
13
- import { nativeOpenAiContextWindow, nativeOpenAiMaxInputTokens } from "../../codex/catalog/metadata";
13
+ import { nativeOpenAiContextWindow, nativeOpenAiMaxInputTokens, type NativeContextLimitsInput } from "../../codex/catalog/metadata";
14
14
  import { estimateTokens } from "../../lib/token-estimate";
15
15
  import { isCanonicalOpenAiForwardProvider, OPENAI_CODEX_PROVIDER_ID } from "../../providers/openai-tiers";
16
16
  import type { OcxContentPart, OcxParsedRequest, OcxProviderConfig } from "../../types";
@@ -131,7 +131,7 @@ export function resolveInputCeiling(
131
131
  modelId: string,
132
132
  // Operator cap for the canonical native provider. Passed in rather than read from a
133
133
  // config here so this stays pure: no filesystem, no catalog, no registry scan.
134
- nativeContextCap?: number,
134
+ nativeContextCap?: NativeContextLimitsInput,
135
135
  ): number | null {
136
136
  const configured = positive(provider.modelContextWindows?.[modelId]) ?? positive(provider.contextWindow);
137
137
 
@@ -143,15 +143,18 @@ export function resolveInputCeiling(
143
143
  const canonicalNativeBare = providerName === OPENAI_CODEX_PROVIDER_ID
144
144
  && isCanonicalOpenAiForwardProvider(provider)
145
145
  && !modelId.includes("/");
146
- const native = configured === null && canonicalNativeBare
147
- ? positive(nativeOpenAiContextWindow(modelId, nativeContextCap))
146
+ const nativeLimits = canonicalNativeBare && configured !== null
147
+ ? {
148
+ ...(typeof nativeContextCap === "number" ? { cap: nativeContextCap } : (nativeContextCap ?? {})),
149
+ modelWindows: { [modelId]: configured },
150
+ }
151
+ : nativeContextCap;
152
+ const native = canonicalNativeBare
153
+ ? positive(nativeOpenAiContextWindow(modelId, nativeLimits))
148
154
  : null;
149
- // The model's own measured input ceiling applies even when the operator configured a
150
- // window: a 1,050,000 override must not raise the gate above the 922,000 the upstream
151
- // actually accepts. Computed independently of `configured` for exactly that reason.
152
- const nativeMaxInput = canonicalNativeBare ? positive(nativeOpenAiMaxInputTokens(modelId, nativeContextCap)) : null;
155
+ const nativeMaxInput = canonicalNativeBare ? positive(nativeOpenAiMaxInputTokens(modelId, nativeLimits)) : null;
153
156
 
154
- const window = configured ?? native;
157
+ const window = canonicalNativeBare ? native : configured;
155
158
  // modelMaxInputTokens is an input-only cap, so it can only tighten the window.
156
159
  const configuredMaxInput = positive(provider.modelMaxInputTokens?.[modelId]);
157
160
  const limits = [window, configuredMaxInput, nativeMaxInput].filter((v): v is number => v !== null);
@@ -168,7 +171,7 @@ export function checkInputAdmission(
168
171
  provider: OcxProviderConfig,
169
172
  providerName: string,
170
173
  modelId: string,
171
- nativeContextCap?: number,
174
+ nativeContextCap?: NativeContextLimitsInput,
172
175
  ): InputAdmissionResult {
173
176
  const ceiling = resolveInputCeiling(provider, providerName, modelId, nativeContextCap);
174
177
  if (ceiling === null) return { admitted: true, estimatedTokens: 0, ceiling: null };
@@ -237,12 +237,12 @@ function rollbackInjectedKeys(port: number, injectedKeys: string[]): void {
237
237
  async function computeEffectiveModelEnv(config: OcxConfig, auto?: AutoContextMode): Promise<{ modelEnv: Record<string, string>; windows: Record<string, number> }> {
238
238
  const { boundedContextWindows, buildClaudeContextWindows, effectiveModelEnv } = await import("../claude/context-windows");
239
239
  const windows = await boundedContextWindows(async () => {
240
- const { gatherRoutedModels, visibleNativeSlugs } = await import("../codex/catalog");
240
+ const { gatherRoutedModels, nativeContextLimits, visibleNativeSlugs } = await import("../codex/catalog");
241
241
  try {
242
- return buildClaudeContextWindows([...visibleNativeSlugs(config)], await gatherRoutedModels(config), providerContextCap(config, OPENAI_CODEX_PROVIDER_ID));
242
+ return buildClaudeContextWindows([...visibleNativeSlugs(config)], await gatherRoutedModels(config), nativeContextLimits(config));
243
243
  } catch (error) {
244
244
  if (error && typeof error === "object" && (error as { code?: unknown }).code === "catalog_busy") {
245
- return buildClaudeContextWindows([...visibleNativeSlugs(config)], [], providerContextCap(config, OPENAI_CODEX_PROVIDER_ID));
245
+ return buildClaudeContextWindows([...visibleNativeSlugs(config)], [], nativeContextLimits(config));
246
246
  }
247
247
  throw error;
248
248
  }
package/src/types.ts CHANGED
@@ -5,6 +5,11 @@ export interface OcxReasoningReplayIdentity {
5
5
  providerName: string;
6
6
  /** Opaque process-local digest of the exact upstream destination. */
7
7
  providerDestinationIdentity: string;
8
+ /**
9
+ * The same destination, digested WITHOUT the process-local random key, so it can key a
10
+ * durable store. Absent when no base URL was resolvable.
11
+ */
12
+ providerDestinationDurableIdentity?: string;
8
13
  adapterName: string;
9
14
  modelId: string;
10
15
  /** Opaque process-local credential identity; never a raw token or API key. */
@@ -1392,8 +1397,15 @@ export interface OcxProviderConfig {
1392
1397
  * HTTP/2 streaming responses (issue #1668). "http1.1" / "h1" forces HTTP/1.1,
1393
1398
  * "http2" / "h2" forces HTTP/2. Absent or "auto" keeps Bun's default negotiation
1394
1399
  * (current behavior unchanged). Only meaningful for https: base URLs.
1395
- */
1400
+ */
1396
1401
  upstreamHttpVersion?: UpstreamHttpVersion;
1402
+ /**
1403
+ * Google only. When `false`, the AI Studio (direct) path sends Gemini Flash ids
1404
+ * unchanged to the wire instead of applying the `-tiered` suffix (`gemini-3.7-flash`
1405
+ * -> `gemini-3.7-flash-tiered`). Set this to `false` when the configured upstream still
1406
+ * serves the bare ids. Absent (default) keeps the rename.
1407
+ */
1408
+ directGeminiWireRenames?: boolean;
1397
1409
  /** Keep provider settings on disk but exclude it from routing and model/catalog listings. */
1398
1410
  disabled?: boolean;
1399
1411
  /**