@yansigit/opencodex 2.32.0 → 2.33.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 (108) hide show
  1. package/README.md +2 -2
  2. package/gui/dist/assets/index-DKLr4LTE.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 +6 -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 +40 -7
  10. package/src/adapters/cursor/live-transport.ts +1 -1
  11. package/src/adapters/cursor/protobuf-events.ts +158 -7
  12. package/src/adapters/cursor/protobuf-request.ts +33 -15
  13. package/src/adapters/cursor/request-builder.ts +4 -3
  14. package/src/adapters/cursor/tool-definitions.ts +27 -1
  15. package/src/adapters/cursor/types.ts +4 -3
  16. package/src/adapters/cursor.ts +9 -0
  17. package/src/adapters/google-antigravity-replay.ts +2 -2
  18. package/src/adapters/google-antigravity-wire.ts +7 -0
  19. package/src/adapters/google-errors.ts +6 -2
  20. package/src/adapters/google-http.ts +30 -7
  21. package/src/adapters/google-truncation.ts +5 -0
  22. package/src/adapters/google-wire-compiler.ts +38 -6
  23. package/src/adapters/google.ts +148 -26
  24. package/src/adapters/kiro-tools.ts +20 -9
  25. package/src/adapters/openai-chat.ts +9 -0
  26. package/src/adapters/openai-responses.ts +1 -1
  27. package/src/bridge.ts +112 -9
  28. package/src/claude/context-windows.ts +16 -9
  29. package/src/cli/doctor.ts +2 -2
  30. package/src/cli/index.ts +9 -2
  31. package/src/cli/provider.ts +6 -0
  32. package/src/cli/status.ts +23 -0
  33. package/src/codex/auth-api.ts +4 -2
  34. package/src/codex/autostart-health.ts +16 -0
  35. package/src/codex/catalog/aggregation.ts +12 -12
  36. package/src/codex/catalog/effort.ts +18 -3
  37. package/src/codex/catalog/metadata.ts +27 -1
  38. package/src/codex/catalog/model-metadata.ts +39 -12
  39. package/src/codex/catalog/parsing.ts +38 -27
  40. package/src/codex/catalog/provider-fetch.ts +198 -133
  41. package/src/codex/catalog/sync.ts +1 -1
  42. package/src/codex/convergence.ts +5 -0
  43. package/src/codex/shim.ts +56 -3
  44. package/src/config/provider-validation.ts +37 -0
  45. package/src/config.ts +78 -2
  46. package/src/generated/compatibility-version.json +120 -96
  47. package/src/images/loop.ts +37 -6
  48. package/src/lib/azure-identity.ts +154 -0
  49. package/src/lib/debug.ts +42 -0
  50. package/src/lib/errors.ts +14 -0
  51. package/src/lib/provider-outbound.ts +45 -33
  52. package/src/lib/provider-tls-profile.ts +309 -0
  53. package/src/lib/proxy-env.ts +49 -0
  54. package/src/lib/redact.ts +10 -1
  55. package/src/oauth/antigravity-routing.ts +282 -236
  56. package/src/oauth/callback-server.ts +22 -2
  57. package/src/oauth/command-code.ts +5 -16
  58. package/src/oauth/google-antigravity.ts +42 -5
  59. package/src/oauth/index.ts +15 -3
  60. package/src/oauth/kimi.ts +9 -1
  61. package/src/oauth/open-browser-choice.ts +26 -0
  62. package/src/oauth/store.ts +6 -0
  63. package/src/providers/antigravity-quota.ts +3 -1
  64. package/src/providers/api-keys.ts +2 -1
  65. package/src/providers/auto-compact-budget.ts +65 -0
  66. package/src/providers/derive.ts +4 -0
  67. package/src/providers/key-failover.ts +5 -1
  68. package/src/providers/openai-tiers.ts +5 -0
  69. package/src/providers/provider-id-rewrite.ts +1 -0
  70. package/src/providers/quota.ts +59 -13
  71. package/src/providers/registry.ts +3 -1
  72. package/src/providers/request-pacing.ts +33 -6
  73. package/src/providers/xai-transport.ts +21 -0
  74. package/src/responses/google-provider-options.ts +36 -0
  75. package/src/responses/namespace-tool-compat.ts +84 -4
  76. package/src/responses/parser.ts +11 -0
  77. package/src/responses/provider-opaque-metadata.ts +3 -3
  78. package/src/responses/schema.ts +37 -0
  79. package/src/responses/state.ts +94 -4
  80. package/src/router.ts +8 -2
  81. package/src/server/auth-cors.ts +28 -0
  82. package/src/server/images.ts +19 -35
  83. package/src/server/management/agent-settings-routes.ts +205 -15
  84. package/src/server/management/combo-routes.ts +6 -0
  85. package/src/server/management/config-routes.ts +31 -5
  86. package/src/server/management/model-rows.ts +4 -0
  87. package/src/server/management/oauth-account-routes.ts +25 -4
  88. package/src/server/management/provider-routes.ts +113 -15
  89. package/src/server/management/routing-profile-routes.ts +3 -0
  90. package/src/server/request-log.ts +21 -0
  91. package/src/server/responses/agent-task-recovery.ts +1 -1
  92. package/src/server/responses/compact.ts +30 -1
  93. package/src/server/responses/core.ts +359 -153
  94. package/src/server/responses/empty-completion-guard.ts +35 -6
  95. package/src/server/responses/fetch-helpers.ts +18 -5
  96. package/src/server/responses/v2-native-parent-override.ts +59 -0
  97. package/src/server/responses/ws-upstream.ts +75 -2
  98. package/src/server/responses-undeclared-tool-guard.ts +90 -8
  99. package/src/service.ts +1 -1
  100. package/src/types/config.ts +16 -1
  101. package/src/types/provider.ts +16 -0
  102. package/src/types/request.ts +28 -0
  103. package/src/types/tools.ts +27 -0
  104. package/src/types.ts +6 -0
  105. package/src/web-search/gemini-executor.ts +6 -4
  106. package/src/web-search/loop.ts +42 -6
  107. package/gui/dist/assets/index-BG43zwVe.js +0 -102
  108. package/gui/dist/assets/index-CiSI-jrP.css +0 -1
@@ -31,12 +31,17 @@ import {
31
31
  } from "../../oauth";
32
32
  import { replaceProviderAccountSet } from "../../oauth/store";
33
33
  import { providerDestinationResolvedError } from "../../lib/destination-policy";
34
+ import {
35
+ antigravityOAuthDestinationConfigError,
36
+ getProviderTlsProfileStatus,
37
+ isAntigravityOAuthProvider,
38
+ } from "../../lib/provider-tls-profile";
34
39
  import { reconcileLiveStateStores } from "../../lib/state-store-registrations";
35
40
  import { ProviderOutboundPolicyError, providerOutboundGet, providerOutboundPost, providerRedirectError } from "../../lib/provider-outbound";
36
41
  import { fetchCursorUsableModels } from "../../adapters/cursor/live-models";
37
42
  import { parseAntigravityAvailableModels } from "../../providers/antigravity-models";
38
43
  import { enrichProviderFromCatalog, listKeyLoginProviders } from "../../oauth/key-providers";
39
- import { deriveProviderPresets } from "../../providers/derive";
44
+ import { deriveProviderPresets, providerConfigSeed } from "../../providers/derive";
40
45
  import { effectiveGoogleMode, providerCodexAccountMode, providerMatchesRegistryTransport } from "../../providers/registry";
41
46
  import {
42
47
  extractModelEnvelopeRows,
@@ -54,6 +59,7 @@ import { clearThreadAccountMap } from "../../codex/routing";
54
59
  import { primeCodexPoolQuotas } from "../../codex/auth-api";
55
60
  import { clearModelCache, getProviderDiscoveryStatus } from "../../codex/model-cache";
56
61
  import { DEFAULT_PROVIDER_CONTEXT_CAP, globalContextCapValue, providerContextCap, providerContextCaps, setAllProviderContextCaps, setGlobalContextCapValue, setProviderContextCap } from "../../providers/context-cap";
62
+ import { modelAutoCompactTokenLimitsConfigError } from "../../providers/auto-compact-budget";
57
63
  import { resolveCodexHomeDir } from "../../codex/home";
58
64
  import { readUsageEntries } from "../../usage/log";
59
65
  import { getUsageDebugLogEntries } from "../../usage/debug";
@@ -157,6 +163,20 @@ function applyProviderPatchFields(
157
163
  return { error: "authMode must be key, forward, oauth, or local" };
158
164
  }
159
165
  }
166
+ if (Object.hasOwn(rawBody, "azureCredential")) {
167
+ const value = rawBody.azureCredential;
168
+ if (value === null) {
169
+ delete next.azureCredential;
170
+ } else {
171
+ if (!isPlainRecord(value)) return { error: "azureCredential must be an object or null" };
172
+ const credential = structuredClone(value) as Record<string, unknown>;
173
+ if (typeof credential.managedIdentityClientId === "string") {
174
+ credential.managedIdentityClientId = credential.managedIdentityClientId.trim();
175
+ }
176
+ next.azureCredential = credential as OcxProviderConfig["azureCredential"];
177
+ }
178
+ touched = true;
179
+ }
160
180
  if (Object.hasOwn(rawBody, "apiKeyTransport")) {
161
181
  const transport = rawBody.apiKeyTransport;
162
182
  if (transport === "x-api-key" || transport === "bearer") {
@@ -214,6 +234,17 @@ function applyProviderPatchFields(
214
234
  }
215
235
  touched = true;
216
236
  }
237
+ if (Object.hasOwn(rawBody, "tlsProfile")) {
238
+ const value = rawBody.tlsProfile;
239
+ if (value === null || value === "") {
240
+ delete next.tlsProfile;
241
+ } else if (value === "antigravity-browser") {
242
+ next.tlsProfile = value;
243
+ } else {
244
+ return { error: "tlsProfile must be antigravity-browser or null" };
245
+ }
246
+ touched = true;
247
+ }
217
248
  if (Object.hasOwn(rawBody, "upstreamHttpVersion")) {
218
249
  const value = rawBody.upstreamHttpVersion;
219
250
  if (value === null || value === "") {
@@ -266,6 +297,29 @@ function applyProviderPatchFields(
266
297
  }
267
298
  touched = true;
268
299
  }
300
+ if (Object.hasOwn(rawBody, "modelAutoCompactTokenLimits")) {
301
+ const value = rawBody.modelAutoCompactTokenLimits;
302
+ const error = modelAutoCompactTokenLimitsConfigError(value, {
303
+ allowTombstones: true,
304
+ requireNativeIds: name === "openai",
305
+ });
306
+ if (error) return { error };
307
+ if (value === null) {
308
+ delete next.modelAutoCompactTokenLimits;
309
+ } else {
310
+ const budgets: Record<string, number> = Object.assign(
311
+ Object.create(null) as Record<string, number>,
312
+ next.modelAutoCompactTokenLimits ?? {},
313
+ );
314
+ for (const [model, budget] of Object.entries(value as Record<string, number | null>)) {
315
+ if (budget === null) delete budgets[model];
316
+ else budgets[model] = budget;
317
+ }
318
+ if (Object.keys(budgets).length > 0) next.modelAutoCompactTokenLimits = budgets;
319
+ else delete next.modelAutoCompactTokenLimits;
320
+ }
321
+ touched = true;
322
+ }
269
323
  if (Object.hasOwn(rawBody, "modelSupportsServiceTier")) {
270
324
  const value = rawBody.modelSupportsServiceTier;
271
325
  if (value === null) {
@@ -369,6 +423,28 @@ function applyProviderPatchFields(
369
423
  return { next, touched, editorTouched, enablingOpenAi, headersTouched };
370
424
  }
371
425
 
426
+ /** Validate the canonical OpenAI soft-budget overlay against a fresh registry seed. */
427
+ function canonicalOpenAiBudgetPatchError(
428
+ provider: OcxProviderConfig,
429
+ rawBody: Record<string, unknown>,
430
+ keys: string[],
431
+ config: OcxConfig,
432
+ ): string | null {
433
+ if (!isCanonicalOpenAiForwardProvider(provider)) {
434
+ return "provider openai must be the canonical built-in provider";
435
+ }
436
+ const entry = getProviderRegistryEntry("openai");
437
+ if (!entry) return "provider openai registry seed is unavailable";
438
+ const seed = providerConfigSeed(entry);
439
+ if (provider.codexAccountMode !== undefined) seed.codexAccountMode = provider.codexAccountMode;
440
+ if (provider.modelAutoCompactTokenLimits !== undefined) {
441
+ seed.modelAutoCompactTokenLimits = { ...provider.modelAutoCompactTokenLimits };
442
+ }
443
+ const applied = applyProviderPatchFields("openai", seed, rawBody, keys, config);
444
+ if ("error" in applied) return applied.error;
445
+ return providerManagementConfigError("openai", applied.next);
446
+ }
447
+
372
448
  export async function handleProviderRoutes(ctx: ManagementContext): Promise<Response | null> {
373
449
  const { req, url, config, deps, principal, convergeCodexCatalog, syncClaudeAgentDefsBestEffort } = ctx;
374
450
 
@@ -405,11 +481,14 @@ export async function handleProviderRoutes(ctx: ManagementContext): Promise<Resp
405
481
  models: p.models ?? [],
406
482
  contextWindow: p.contextWindow,
407
483
  modelContextWindows: p.modelContextWindows,
484
+ modelAutoCompactTokenLimits: p.modelAutoCompactTokenLimits,
408
485
  modelSupportsServiceTier: p.modelSupportsServiceTier,
409
486
  noStructuredOutputModels: p.noStructuredOutputModels,
410
487
  upstreamHttpVersion: p.upstreamHttpVersion,
411
488
  authMode: p.authMode,
412
489
  apiKeyTransport: p.apiKeyTransport,
490
+ tlsProfile: p.tlsProfile,
491
+ tlsProfileStatus: p.tlsProfile === undefined ? "disabled" : getProviderTlsProfileStatus(name, p),
413
492
  disabled: p.disabled === true,
414
493
  codexAccountMode: providerCodexAccountMode(name, p),
415
494
  ...(name === "xai" ? { xaiResponsesOptInState: xaiResponsesOptInState(p) } : {}),
@@ -536,13 +615,14 @@ export async function handleProviderRoutes(ctx: ManagementContext): Promise<Resp
536
615
  // call can never fire.
537
616
  const submittedContextWindow = Object.hasOwn(prov, "contextWindow");
538
617
  const submittedModelContextWindows = Object.hasOwn(prov, "modelContextWindows");
618
+ const submittedModelAutoCompactTokenLimits = Object.hasOwn(prov, "modelAutoCompactTokenLimits");
539
619
  const submittedRequestPacing = Object.hasOwn(prov, "requestPacing");
540
620
  enrichProviderFromCatalog(name, prov);
541
621
  const { saveConfigPreservingClaudeCode: save } = await import("../../config");
542
622
  // Overwriting an existing provider must not drop its multi-key pool: carry it over, then
543
623
  // let the (possibly new) apiKey join the pool as the active entry.
544
624
  const existingPool = config.providers[name]?.apiKeyPool;
545
- if (existingPool && !prov.apiKeyPool) prov.apiKeyPool = existingPool;
625
+ if (existingPool && !prov.apiKeyPool && !prov.azureCredential) prov.apiKeyPool = existingPool;
546
626
  // The same rule applies to user-configured price overlays: the dashboard's
547
627
  // add/edit form does not send modelCosts, so an overwrite must not silently
548
628
  // erase hand-edited per-model prices from Logs/Usage estimates.
@@ -568,6 +648,11 @@ export async function handleProviderRoutes(ctx: ManagementContext): Promise<Resp
568
648
  ? { ...existing.modelContextWindows, ...(prov.modelContextWindows ?? {}) }
569
649
  : { ...existing.modelContextWindows };
570
650
  }
651
+ if (existing?.modelAutoCompactTokenLimits) {
652
+ prov.modelAutoCompactTokenLimits = submittedModelAutoCompactTokenLimits
653
+ ? { ...existing.modelAutoCompactTokenLimits, ...(prov.modelAutoCompactTokenLimits ?? {}) }
654
+ : { ...existing.modelAutoCompactTokenLimits };
655
+ }
571
656
  config.providers[name] = stripRegistryOnlyStaticHeaders(name, prov);
572
657
  if (body.setDefault === true) config.defaultProvider = name;
573
658
  save(config);
@@ -591,6 +676,9 @@ export async function handleProviderRoutes(ctx: ManagementContext): Promise<Resp
591
676
  const keys = Object.keys(rawBody);
592
677
  const hasMode = Object.hasOwn(rawBody, "codexAccountMode");
593
678
  const hasSetDefault = Object.hasOwn(rawBody, "setDefault");
679
+ const canonicalBudgetOnly = name === "openai"
680
+ && keys.length === 1
681
+ && keys[0] === "modelAutoCompactTokenLimits";
594
682
 
595
683
  // codexAccountMode keeps its dedicated side-effect path (quota cache clear, thread map
596
684
  // clear, pool prime) and is mutually exclusive with every other patch field.
@@ -653,12 +741,16 @@ export async function handleProviderRoutes(ctx: ManagementContext): Promise<Resp
653
741
 
654
742
  const pacingOnly = keys.every(key => key === "requestPacing");
655
743
  if (applied.editorTouched && !pacingOnly) {
656
- const providerError = providerManagementConfigError(name, next);
744
+ const providerError = canonicalBudgetOnly
745
+ ? canonicalOpenAiBudgetPatchError(next, rawBody, keys, config)
746
+ : providerManagementConfigError(name, next);
657
747
  if (providerError) return jsonResponse({ error: providerError }, 400);
658
- const serviceTierError = providerServiceTierConfigError(name, next);
659
- if (serviceTierError) return jsonResponse({ error: serviceTierError }, 400);
660
- const resolvedError = await providerDestinationResolvedError(name, next);
661
- if (resolvedError) return jsonResponse({ error: resolvedError }, 400);
748
+ if (!canonicalBudgetOnly) {
749
+ const serviceTierError = providerServiceTierConfigError(name, next);
750
+ if (serviceTierError) return jsonResponse({ error: serviceTierError }, 400);
751
+ const resolvedError = await providerDestinationResolvedError(name, next);
752
+ if (resolvedError) return jsonResponse({ error: resolvedError }, 400);
753
+ }
662
754
  } else if (applied.enablingOpenAi) {
663
755
  // Same DNS gate as POST: Clash fake-IP only. Never honor a persisted
664
756
  // allowPrivateNetwork on this path — it must not bypass the built-in guard.
@@ -682,15 +774,19 @@ export async function handleProviderRoutes(ctx: ManagementContext): Promise<Resp
682
774
  return;
683
775
  }
684
776
  if (replay.editorTouched && !pacingOnly) {
685
- const syncError = providerManagementConfigError(name, replay.next);
777
+ const syncError = canonicalBudgetOnly
778
+ ? canonicalOpenAiBudgetPatchError(replay.next, rawBody, keys, config)
779
+ : providerManagementConfigError(name, replay.next);
686
780
  if (syncError) {
687
781
  replayError = syncError;
688
782
  return;
689
783
  }
690
- const serviceTierError = providerServiceTierConfigError(name, replay.next);
691
- if (serviceTierError) {
692
- replayError = serviceTierError;
693
- return;
784
+ if (!canonicalBudgetOnly) {
785
+ const serviceTierError = providerServiceTierConfigError(name, replay.next);
786
+ if (serviceTierError) {
787
+ replayError = serviceTierError;
788
+ return;
789
+ }
694
790
  }
695
791
  } else if (replay.enablingOpenAi && !isCanonicalOpenAiForwardProvider(replay.next)) {
696
792
  replayError = "provider openai must be the canonical built-in provider";
@@ -733,6 +829,8 @@ export async function handleProviderRoutes(ctx: ManagementContext): Promise<Resp
733
829
  if (prov.disabled) {
734
830
  return jsonResponse({ ok: false, error: "Provider is disabled", latencyMs: 0 });
735
831
  }
832
+ const antigravityError = antigravityOAuthDestinationConfigError(name, prov);
833
+ if (antigravityError) return jsonResponse({ ok: false, error: antigravityError, latencyMs: 0 }, 400);
736
834
  if (prov.authMode === "forward") {
737
835
  return jsonResponse({
738
836
  ok: true,
@@ -747,12 +845,12 @@ export async function handleProviderRoutes(ctx: ManagementContext): Promise<Resp
747
845
  return jsonResponse({ applicable: false, reason: "static_catalog", latencyMs: 0 });
748
846
  }
749
847
  const { buildModelsRequest, getValidAccessTokenSnapshot, resolveModelsAuthToken } = await import("../../oauth");
750
- const antigravity = effectiveGoogleMode(name, prov) === "cloud-code-assist";
848
+ const antigravity = isAntigravityOAuthProvider(name, prov);
751
849
  const snapshot = antigravity
752
850
  ? await getValidAccessTokenSnapshot(name).catch(() => undefined)
753
851
  : undefined;
754
852
  const apiKey = snapshot?.accessToken ?? await resolveModelsAuthToken(name, prov);
755
- if (prov.authMode === "oauth" && !apiKey) {
853
+ if ((prov.authMode === "oauth" || antigravity) && !apiKey) {
756
854
  return jsonResponse({ ok: false, latencyMs: 0, error: "static catalog only — upstream not verified (not logged in)" });
757
855
  }
758
856
  if (prov.adapter === "cursor") {
@@ -776,7 +874,7 @@ export async function handleProviderRoutes(ctx: ManagementContext): Promise<Resp
776
874
  message: `Connected. ${live.models.length} models.`,
777
875
  });
778
876
  }
779
- const project = prov.project ?? snapshot?.projectId;
877
+ const project = antigravity ? snapshot?.projectId : prov.project;
780
878
  if (antigravity && !project) {
781
879
  return jsonResponse({ ok: false, latencyMs: 0, error: "Antigravity project unavailable — re-run `ocx login google-antigravity`" });
782
880
  }
@@ -188,6 +188,9 @@ function migrateProfileModelReferences(
188
188
  if (config.injectionModel && config.injectionModel === oldPublicModel) {
189
189
  config.injectionModel = newPublicModel;
190
190
  }
191
+ if (config.v2NativeParentOverride?.model && config.v2NativeParentOverride.model === oldPublicModel) {
192
+ config.v2NativeParentOverride = { ...config.v2NativeParentOverride, model: newPublicModel };
193
+ }
191
194
  if (config.shadowCallIntercept?.model && config.shadowCallIntercept.model === oldPublicModel) {
192
195
  config.shadowCallIntercept = { ...config.shadowCallIntercept, model: newPublicModel };
193
196
  }
@@ -549,6 +549,7 @@ export function requestLogErrorCode(
549
549
  // Keep the high-confidence message fallback for runtimes/providers that stripped the
550
550
  // structured code before emitting response.failed.
551
551
  if (classifiedCode === CYBER_POLICY_ERROR_CODE) return CYBER_POLICY_ERROR_CODE;
552
+ if (classifiedCode === "insufficient_quota") return classifiedCode;
552
553
  if (status === 400 || status === 409) return "invalid_request_error";
553
554
  if (status === 401) return "invalid_api_key";
554
555
  if (status === 403) {
@@ -1003,6 +1004,26 @@ export function filterRequestLogs(logs: RequestLogEntry[], params: URLSearchPara
1003
1004
  filtered = filtered.filter(entry => entry.provider === provider
1004
1005
  || entry.attempts?.some(attempt => attempt.provider === provider));
1005
1006
  }
1007
+ const model = params.get("model")?.trim().toLowerCase();
1008
+ if (model) {
1009
+ filtered = filtered.filter(entry => entry.model.toLowerCase() === model
1010
+ || entry.resolvedModel?.toLowerCase() === model
1011
+ || entry.attempts?.some(attempt => attempt.model.toLowerCase() === model));
1012
+ }
1013
+ const sinceRaw = params.get("since")?.trim();
1014
+ if (sinceRaw) {
1015
+ const since = Number(sinceRaw);
1016
+ if (Number.isFinite(since)) {
1017
+ filtered = filtered.filter(entry => entry.timestamp >= since);
1018
+ }
1019
+ }
1020
+ const untilRaw = params.get("until")?.trim();
1021
+ if (untilRaw) {
1022
+ const until = Number(untilRaw);
1023
+ if (Number.isFinite(until)) {
1024
+ filtered = filtered.filter(entry => entry.timestamp <= until);
1025
+ }
1026
+ }
1006
1027
  const conversationId = params.get("conversationId")?.trim() || params.get("conversation")?.trim();
1007
1028
  if (conversationId) {
1008
1029
  filtered = filtered.filter(entry => matchesLogConversationId(entry.conversationId, conversationId));
@@ -45,7 +45,7 @@ export function agentTaskRecoveryConfig(config: OcxConfig): AgentTaskRecoveryOpt
45
45
  enabled: true,
46
46
  model: typeof raw.model === "string" && raw.model.trim().length > 0
47
47
  ? raw.model.trim()
48
- : "gpt-5.6-sol",
48
+ : "gpt-5.6-luna",
49
49
  timeoutMs: Number.isFinite(raw.timeoutMs) && (raw.timeoutMs ?? 0) >= 1_000
50
50
  ? Math.min(120_000, Math.floor(raw.timeoutMs!))
51
51
  : 45_000,
@@ -127,6 +127,7 @@ import {
127
127
  } from "./core";
128
128
  import { fetchWithHeaderTimeout, providerFetch, safeHostLabel, safeOriginLabel } from "./fetch-helpers";
129
129
  import { mapCodexAuthContextErrorToResponse } from "./codex-auth-error";
130
+ import { decideV2NativeParentOverride } from "./v2-native-parent-override";
130
131
 
131
132
  export const COMPACT_RESPONSE_MAX_BYTES = 32 * 1024 * 1024;
132
133
 
@@ -297,6 +298,31 @@ export async function handleResponsesCompact(
297
298
  }
298
299
  return formatErrorResponse(404, "invalid_request_error", err instanceof Error ? err.message : String(err));
299
300
  }
301
+ const requestedModel = raw.model;
302
+ // Populate source-route identity before the opt-in decision can fail closed. This keeps
303
+ // malformed/unavailable targets observable as the caller's route without exposing any new data.
304
+ logCtx.requestedModel = requestedModel;
305
+ logCtx.model = route.modelId;
306
+ logCtx.routeDecision = route.routeDecision;
307
+ logCtx.provider = route.codexAccountNamespace
308
+ ? `${route.providerName}-${route.codexAccountNamespace}`
309
+ : route.providerName;
310
+ logCtx.providerAdapter = route.provider.adapter;
311
+ const parentOverride = decideV2NativeParentOverride({
312
+ kind: "compact",
313
+ config,
314
+ headers: req.headers,
315
+ sourceRoute: route,
316
+ targetEvidence: evidenceFromBody(raw),
317
+ });
318
+ if (parentOverride.kind === "reject") {
319
+ if (parentOverride.trace) logCtx.routeDecision = parentOverride.trace as typeof logCtx.routeDecision;
320
+ return formatErrorResponse(404, "invalid_request_error", parentOverride.message);
321
+ }
322
+ if (parentOverride.kind === "override") {
323
+ route = parentOverride.route;
324
+ raw.model = route.modelId;
325
+ }
300
326
  const selectedModelId = route.modelId;
301
327
  // Derive from the RESOLVED route model, not the caller's raw string. An account-qualified
302
328
  // selector like `side/gpt-daybreak-blue-latest` does not match the gated map — `slugsEquivalent`
@@ -304,7 +330,7 @@ export async function handleResponsesCompact(
304
330
  // exactly the selector form back down the native compact endpoint this guard exists to avoid.
305
331
  // `route.modelId` is the same value `applyCodexAccountGatedWireNormalization` uses in core.ts.
306
332
  const accountGatedCompactWireModel = codexAccountGatedCanonicalWireModel(selectedModelId);
307
- logCtx.requestedModel = raw.model;
333
+ logCtx.requestedModel = requestedModel;
308
334
  logCtx.model = selectedModelId;
309
335
  logCtx.routeDecision = route.routeDecision;
310
336
  logCtx.provider = route.codexAccountNamespace
@@ -677,6 +703,9 @@ export async function handleResponsesCompact(
677
703
  body: JSON.stringify(internalBody),
678
704
  });
679
705
  const response = await handleResponses(internalReq, config, logCtx, { abortSignal: req.signal, turnAdmissionLease, ...(admission ? { admission } : {}) });
706
+ // The internal summarizer is a routed Responses turn, but compact logs retain the
707
+ // caller's selector just like the native compact branch above.
708
+ logCtx.requestedModel = requestedModel;
680
709
  if (!response.ok) return response;
681
710
  let json: { output?: unknown[]; status?: unknown; error?: unknown };
682
711
  if (response.headers.get("content-type")?.includes("text/event-stream")) {