@bitkyc08/opencodex 2.25.0 → 2.26.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 (99) hide show
  1. package/bin/ocx.mjs +59 -7
  2. package/gui/dist/assets/index-RL6b1bTV.js +102 -0
  3. package/gui/dist/index.html +1 -1
  4. package/package.json +1 -1
  5. package/src/adapters/base.ts +18 -0
  6. package/src/adapters/client-fingerprint.ts +0 -2
  7. package/src/adapters/cursor/http1-bidi.ts +361 -0
  8. package/src/adapters/cursor/live-models.ts +117 -30
  9. package/src/adapters/cursor/live-transport.ts +137 -56
  10. package/src/adapters/cursor/native-exec-fs.ts +1 -1
  11. package/src/adapters/cursor/native-exec-network.ts +1 -1
  12. package/src/adapters/cursor/native-exec-shell.ts +0 -1
  13. package/src/adapters/cursor/protobuf-request.ts +89 -21
  14. package/src/adapters/cursor/tool-definitions.ts +18 -7
  15. package/src/adapters/cursor/tool-result-normalize.ts +92 -0
  16. package/src/adapters/cursor/transport.ts +7 -0
  17. package/src/adapters/cursor.ts +2 -0
  18. package/src/adapters/google-http.ts +38 -10
  19. package/src/adapters/google.ts +23 -3
  20. package/src/adapters/openai-chat.ts +38 -13
  21. package/src/adapters/openai-responses.ts +129 -9
  22. package/src/adapters/registry.ts +30 -1
  23. package/src/bridge.ts +50 -12
  24. package/src/chat/inbound.ts +1 -0
  25. package/src/claude/agents-inject.ts +3 -2
  26. package/src/cli/dispatch.ts +19 -6
  27. package/src/cli/help.ts +2 -1
  28. package/src/cli/integrations.ts +35 -0
  29. package/src/cli/minimax.ts +8 -2
  30. package/src/cli/registry.ts +13 -2
  31. package/src/clients/config-export.ts +120 -1
  32. package/src/codex/account-store.ts +17 -1
  33. package/src/codex/auth-api.ts +51 -17
  34. package/src/codex/catalog/effort.ts +8 -5
  35. package/src/codex/catalog/provider-fetch.ts +39 -27
  36. package/src/codex/catalog/sync.ts +30 -8
  37. package/src/codex/inject.ts +39 -13
  38. package/src/codex/main-account.ts +29 -1
  39. package/src/codex/plan-from-token.ts +140 -0
  40. package/src/codex/plan.ts +25 -0
  41. package/src/codex/prompt-journal.ts +6 -2
  42. package/src/codex/quota-rejection.ts +21 -7
  43. package/src/codex/refresh.ts +4 -2
  44. package/src/codex/sync.ts +106 -3
  45. package/src/codex/warmup.ts +187 -81
  46. package/src/config.ts +85 -46
  47. package/src/generated/compatibility-version.json +127 -83
  48. package/src/grok/inject.ts +1 -1
  49. package/src/images/loop.ts +1 -0
  50. package/src/integrations/registry.ts +7 -0
  51. package/src/lab/automation/config-persistence.ts +2 -2
  52. package/src/lab/automation/persistence.ts +2 -2
  53. package/src/lab/ledger/purge.ts +2 -2
  54. package/src/lab/subject/behavior-fingerprint.ts +2 -2
  55. package/src/lib/config-ownership.ts +5 -2
  56. package/src/lib/destination-policy.ts +18 -1
  57. package/src/lib/provider-outbound.ts +7 -0
  58. package/src/lib/redact.ts +11 -0
  59. package/src/lib/tool-argument-integers.ts +50 -6
  60. package/src/lib/upstream-http-version.ts +57 -0
  61. package/src/lib/upstream-retry.ts +2 -1
  62. package/src/lib/windows-atomic-replace.ts +155 -0
  63. package/src/lib/windows-service-wrappers.ts +72 -0
  64. package/src/oauth/google-antigravity.ts +2 -2
  65. package/src/oauth/index.ts +60 -8
  66. package/src/providers/antigravity-models.ts +58 -4
  67. package/src/providers/command-code-efforts.ts +36 -9
  68. package/src/providers/context-cap.ts +9 -0
  69. package/src/providers/derive.ts +4 -0
  70. package/src/providers/fastwire.ts +453 -0
  71. package/src/providers/registry.ts +21 -1
  72. package/src/providers/service-tier.ts +173 -89
  73. package/src/responses/parser.ts +25 -16
  74. package/src/responses/reasoning-replay-cache.ts +30 -0
  75. package/src/responses/thought-signature-replay.ts +74 -4
  76. package/src/router.ts +6 -0
  77. package/src/routing/compatibility/behavior.ts +27 -16
  78. package/src/server/index.ts +10 -1
  79. package/src/server/management/oauth-account-routes.ts +10 -2
  80. package/src/server/management/shared.ts +1 -1
  81. package/src/server/management/system-routes.ts +15 -0
  82. package/src/server/request-log.ts +57 -3
  83. package/src/server/responses/agent-task-recovery.ts +6 -1
  84. package/src/server/responses/core.ts +209 -40
  85. package/src/server/responses/fetch-helpers.ts +15 -36
  86. package/src/server/responses/responses-field-backfill.ts +173 -0
  87. package/src/server/responses-reasoning-summary-rewrite.ts +171 -0
  88. package/src/service.ts +38 -36
  89. package/src/storage/cleanup.ts +2 -2
  90. package/src/tray/windows.ts +3 -2
  91. package/src/types.ts +101 -14
  92. package/src/update/job.ts +9 -18
  93. package/src/update/transactional-install.d.mts +22 -0
  94. package/src/update/transactional-install.mjs +259 -0
  95. package/src/usage/cost.ts +34 -5
  96. package/src/usage/log.ts +74 -4
  97. package/src/vision/anthropic-describe.ts +10 -6
  98. package/src/web-search/anthropic-executor.ts +8 -6
  99. package/gui/dist/assets/index-DxJ7kXj9.js +0 -102
@@ -1,11 +1,14 @@
1
1
  import type { Server } from "bun";
2
2
  import { bridgeToResponsesSSE, buildResponseJSON, formatErrorResponse, type ResponsesTerminalStatus } from "../../bridge";
3
3
  import { formatPassthroughUpstreamError } from "./passthrough-error";
4
+ import {
5
+ createResponsesFieldBackfillBlockRewrite,
6
+ backfillResponsesFieldsJson,
7
+ } from "./responses-field-backfill";
4
8
  import { checkInputAdmission } from "./input-admission";
5
9
  import { nativeContextLimits } from "../../codex/catalog";
6
10
  import { describeUpstreamConnectFailure } from "./upstream-error";
7
11
  import {
8
- getConfigPath,
9
12
  multiAgentGuidanceEnabled,
10
13
  resolveEnvValue,
11
14
  } from "../../config";
@@ -15,9 +18,11 @@ import {
15
18
  reasoningReplayCodexCredentialIdentity,
16
19
  reasoningReplayDestinationIdentity,
17
20
  durableReplayDestinationIdentity,
21
+ durableReplayCredentialIdentity,
18
22
  reasoningReplayKeyCredentialIdentity,
19
23
  reasoningReplayOAuthCredentialIdentity,
20
24
  } from "../../responses/reasoning-replay-cache";
25
+ import { awaitThoughtSignatureDurability, thoughtSignatureReplaySalt } from "../../responses/thought-signature-replay";
21
26
  import { buildCompactV1Output, COMPACT_PROMPT, decodeCompactionSummary, extractCompactUserMessages } from "../../responses/compaction";
22
27
  import { FORWARD_HEADERS, sanitizeReasoningInputContent } from "../../adapters/openai-responses";
23
28
  import {
@@ -57,12 +62,13 @@ import { injectionDebugLog } from "../../lib/injection-debug-log";
57
62
  import { resolveClientRetryAfter } from "../../lib/retry-after";
58
63
  import { enrichOpenCodeZenRateLimitMessage } from "../../providers/opencode-zen-rate-limit";
59
64
  import { modelInList, namespacedToolName } from "../../types";
60
- import type { AdapterEvent, OcxConfig, OcxParsedRequest, OcxProviderConfig, OcxProviderContinuationState, OcxUsage } from "../../types";
65
+ import type { AdapterEvent, OcxConfig, OcxParsedRequest, OcxProviderConfig, OcxProviderContinuationState, OcxUsage, TierDecision } from "../../types";
61
66
  import {
62
67
  forceRefreshOAuthAccessSnapshot,
63
68
  getOAuthCredentialApiBaseUrl,
64
69
  getValidAccessTokenForAccount,
65
70
  getValidAccessTokenSnapshot,
71
+ publicOAuthAuthenticationErrorMessage,
66
72
  type OAuthAccessSnapshot,
67
73
  UnsupportedOAuthProviderError,
68
74
  } from "../../oauth";
@@ -123,7 +129,18 @@ import { createTranslatorBudget, isTranslatorBudgetExceededError, type Translato
123
129
  import { listOpenAiForwardSidecarCandidates, resolveFirstUsableOpenAiSidecar, type ResolvedOpenAiForwardSidecar } from "../../providers/openai-sidecar";
124
130
  import { isCanonicalOpenAiForwardProvider, OPENAI_CODEX_PROVIDER_ID } from "../../providers/openai-tiers";
125
131
  import { providerContextCap } from "../../providers/context-cap";
126
- import { SERVICE_TIER_ADAPTERS, serviceTierSupportForModel } from "../../providers/service-tier";
132
+ import {
133
+ fastPolicyForModel,
134
+ serviceTierSupportFromPolicy,
135
+ SERVICE_TIER_ADAPTERS,
136
+ } from "../../providers/service-tier";
137
+ import {
138
+ canonicalFastTierMarker,
139
+ decideTier,
140
+ tierObservationContext,
141
+ tierValueAfterDecision,
142
+ type ResolvedFastPolicy,
143
+ } from "../../providers/fastwire";
127
144
  import {
128
145
  RequestPacingQueueOverloadError,
129
146
  waitForProviderRequestSlot,
@@ -149,7 +166,7 @@ import { shouldAttemptImageTierRetry } from "../image-retry";
149
166
  import { resolveProviderTransport } from "../../providers/xai-transport";
150
167
  import type { WsData } from "../ws-bridge";
151
168
  import { codexAccountSelectionForTurn, registerTurn, trackStreamLifetime, unregisterTurn } from "../lifecycle";
152
- import { redactSecretString } from "../../lib/redact";
169
+ import { redactSecretString, sanitizeLogMetadataString } from "../../lib/redact";
153
170
  import { readBoundedResponseBody } from "../../lib/bounded-body";
154
171
  import type { AdmissionLease } from "../../lib/admission";
155
172
  import { supportedLadderFor } from "../effort-policy";
@@ -167,6 +184,8 @@ import {
167
184
  noteAttemptSend,
168
185
  readConfiguredCodexServiceTier,
169
186
  recordAdapterReasoning,
187
+ recordAdapterTier,
188
+ recordAdapterTierMetadata,
170
189
  recordAttemptRequestedEffort,
171
190
  requestLogSpeedLabel,
172
191
  sealRequestAttemptIdentity,
@@ -208,6 +227,11 @@ import {
208
227
  hasResponsesItemIdRepair,
209
228
  repairResponsesJsonItemIds,
210
229
  } from "../responses-item-id-repair";
230
+ import {
231
+ createReasoningSummaryChannelPayloadRewrite,
232
+ rewriteReasoningSummaryInJsonString,
233
+ routeUsesContentChannelReasoning,
234
+ } from "../responses-reasoning-summary-rewrite";
211
235
  import {
212
236
  createImageGenCallRestoreRewrite,
213
237
  imageGenToolCallAliases,
@@ -304,11 +328,21 @@ function bindRouteReasoningReplayScope(args: {
304
328
  }): void {
305
329
  const { parsed, providerName, provider, adapterName } = args;
306
330
  let credentialIdentity: string | undefined;
331
+ let credentialDurableIdentity: string | undefined;
332
+ const durableSalt = thoughtSignatureReplaySalt();
307
333
  if (provider.authMode === "oauth") {
308
334
  credentialIdentity = reasoningReplayOAuthCredentialIdentity(
309
335
  args.oauthCredentialSnapshot,
310
336
  provider.headers,
311
337
  );
338
+ // The persisted account-slot id survives token refresh and restarts; the rotating
339
+ // generation deliberately does NOT participate (#1926 design: rotation-safe).
340
+ credentialDurableIdentity = durableReplayCredentialIdentity(
341
+ "oauth",
342
+ args.oauthCredentialSnapshot?.accountId,
343
+ provider.headers,
344
+ durableSalt,
345
+ );
312
346
  } else if (provider.authMode === "forward") {
313
347
  const poolContext = args.codexAuthContext?.kind === "pool"
314
348
  || args.codexAuthContext?.kind === "main-pool"
@@ -327,8 +361,28 @@ function bindRouteReasoningReplayScope(args: {
327
361
  writerGeneration: poolContext?.writerGeneration,
328
362
  headers: provider.headers,
329
363
  });
364
+ // Durable identity requires a STABLE, TRUSTED account handle. Pool context comes from
365
+ // our own account store; a client-supplied chatgpt-account-id header is attacker
366
+ // -influenceable bucket selection and a bearer alone is rotating material — both are
367
+ // refused, so direct-forward turns get no durable scope (fail closed; the in-process
368
+ // cache still covers same-process replay).
369
+ const codexDurableHandle = poolContext?.accountId
370
+ ?? poolContext?.chatgptAccountId
371
+ ?? undefined;
372
+ credentialDurableIdentity = durableReplayCredentialIdentity(
373
+ "codex",
374
+ codexDurableHandle ?? undefined,
375
+ provider.headers,
376
+ durableSalt,
377
+ );
330
378
  } else if (provider.authMode !== "local") {
331
379
  credentialIdentity = reasoningReplayKeyCredentialIdentity(provider);
380
+ credentialDurableIdentity = durableReplayCredentialIdentity(
381
+ "key",
382
+ nonEmptyProviderApiKey(provider),
383
+ provider.headers,
384
+ durableSalt,
385
+ );
332
386
  }
333
387
  const providerDestinationIdentity = reasoningReplayDestinationIdentity(provider.baseUrl);
334
388
  bindReasoningReplayScope(
@@ -341,18 +395,23 @@ function bindRouteReasoningReplayScope(args: {
341
395
  adapterName,
342
396
  modelId: parsed.modelId,
343
397
  credentialIdentity,
398
+ ...(credentialDurableIdentity ? { credentialDurableIdentity } : {}),
344
399
  }
345
400
  : undefined,
346
401
  );
347
402
  }
348
403
 
404
+ function nonEmptyProviderApiKey(provider: OcxProviderConfig): string | undefined {
405
+ return typeof provider.apiKey === "string" && provider.apiKey.trim().length > 0
406
+ ? provider.apiKey
407
+ : undefined;
408
+ }
409
+
349
410
  function isFixedCodexAccount(authCtx: CodexAuthContext): boolean {
350
411
  return (authCtx.kind === "pool" || authCtx.kind === "main-pool")
351
412
  && authCtx.fixedAccount === true;
352
413
  }
353
414
 
354
-
355
-
356
415
  export function usesCodexForwardPoolAuth(
357
416
  authCtx: CodexAuthContext,
358
417
  provider: OcxProviderConfig,
@@ -600,6 +659,7 @@ async function retryCodexPoolOnAlternateAccount(
600
659
  translatorBudget: options.translatorBudget,
601
660
  });
602
661
  recordAdapterReasoning(logCtx, request);
662
+ recordAdapterTier(logCtx, request);
603
663
 
604
664
  await firstResponse.body?.cancel().catch(() => undefined);
605
665
  options.onCodexAuthContextResolved?.(retryAuthCtx);
@@ -969,6 +1029,23 @@ const UNREADABLE_ENCRYPTED_AGENT_TASK_MESSAGE =
969
1029
  const MAX_UPSTREAM_JSON_BODY_BYTES = 32 * 1024 * 1024;
970
1030
  const UPSTREAM_JSON_BODY_TOTAL_TIMEOUT_MS = 180_000;
971
1031
  const UPSTREAM_JSON_BODY_INACTIVITY_TIMEOUT_MS = 30_000;
1032
+ const MAX_FAST_WIRE_CAPABILITY_WARNINGS = 256;
1033
+ const warnedFastWireCapabilityGaps = new Set<string>();
1034
+
1035
+ function warnFastWireCapabilityGap(providerName: string, modelId: string): void {
1036
+ const safeProvider = sanitizeLogMetadataString(providerName) ?? "unknown";
1037
+ const safeModel = sanitizeLogMetadataString(modelId) ?? "unknown";
1038
+ const key = `${safeProvider}\0${safeModel}`;
1039
+ if (warnedFastWireCapabilityGaps.has(key)) return;
1040
+ if (warnedFastWireCapabilityGaps.size >= MAX_FAST_WIRE_CAPABILITY_WARNINGS) {
1041
+ const oldest = warnedFastWireCapabilityGaps.values().next().value;
1042
+ if (oldest !== undefined) warnedFastWireCapabilityGaps.delete(oldest);
1043
+ }
1044
+ warnedFastWireCapabilityGaps.add(key);
1045
+ console.warn(
1046
+ `[opencodex] Fast policy for ${safeProvider}/${safeModel} has service-tier capability but no Fast wire; preserving only caller-permitted tier behavior`,
1047
+ );
1048
+ }
972
1049
  export const UPSTREAM_JSON_BODY_READ_OPTIONS = {
973
1050
  maxBytes: MAX_UPSTREAM_JSON_BODY_BYTES,
974
1051
  totalTimeoutMs: UPSTREAM_JSON_BODY_TOTAL_TIMEOUT_MS,
@@ -1154,23 +1231,21 @@ async function applyFinalRouteRequestNormalization(args: {
1154
1231
  logCtx.preserveResolvedModelFromRoute = true;
1155
1232
  }
1156
1233
 
1157
- // Fast mode override only where the final provider/model route explicitly documents
1158
- // service-tier support. The same model-scoped resolver is used by catalog generation.
1159
- const modelServiceTierSupport = serviceTierSupportForModel(
1234
+ // Resolve Fast policy after the final route/wire settles. A1 records the decision on parsed
1235
+ // options; the Responses adapter owns the final outbound body write.
1236
+ const fastPolicy = fastPolicyForModel(
1160
1237
  route.provider,
1161
1238
  route.modelId,
1162
1239
  route.providerName,
1163
1240
  inboundWire,
1164
1241
  );
1165
- if (config.fastMode !== undefined
1166
- && SERVICE_TIER_ADAPTERS.has(route.provider.adapter)
1167
- && modelServiceTierSupport === true) {
1168
- const tier = config.fastMode ? "priority" : undefined;
1169
- if (parsed._rawBody && typeof parsed._rawBody === "object") {
1170
- if (tier) (parsed._rawBody as Record<string, unknown>).service_tier = tier;
1171
- else delete (parsed._rawBody as Record<string, unknown>).service_tier;
1172
- }
1173
- parsed.options.serviceTier = tier;
1242
+ const modelServiceTierSupport = serviceTierSupportFromPolicy(fastPolicy);
1243
+ const callerTier = parsed.options.serviceTier;
1244
+ parsed.options.tierObservation = tierObservationContext(fastPolicy, config.fastMode, callerTier);
1245
+ parsed.options.tierDecision = decideTier(fastPolicy, config.fastMode, callerTier);
1246
+ parsed.options.serviceTier = tierValueAfterDecision(parsed.options.tierDecision, callerTier);
1247
+ if (fastPolicy.capability === true && fastPolicy.fastWire === null) {
1248
+ warnFastWireCapabilityGap(route.providerName, route.modelId);
1174
1249
  }
1175
1250
  applyServiceTierGate(
1176
1251
  route.provider,
@@ -1179,6 +1254,7 @@ async function applyFinalRouteRequestNormalization(args: {
1179
1254
  route.modelId,
1180
1255
  route.providerName,
1181
1256
  inboundWire,
1257
+ fastPolicy,
1182
1258
  );
1183
1259
  if (modelServiceTierSupport === false) {
1184
1260
  logCtx.requestedServiceTier = undefined;
@@ -1566,29 +1642,43 @@ function finalizeOwnedTranslatorBudget(response: Response, budget: TranslatorBud
1566
1642
  * Service-tier capability gate, applied after the final route/wire is settled. A
1567
1643
  * provider explicitly documented as NOT supporting `service_tier` must never
1568
1644
  * receive it: strip the field and clear the logging value even when the caller
1569
- * supplied one (fail closed). Tri-state contract: `true` supports (injection
1570
- * allowed, caller values preserved), `false` strips, and an UNCLASSIFIED custom
1571
- * provider (`undefined`) preserves caller-supplied values but never gets an
1572
- * injection — deleting the caller's field there would silently change their
1573
- * request against a gateway we know nothing about.
1645
+ * supplied one (fail closed). A policy-produced canonical Fast decision has
1646
+ * already passed capability validation and cannot be vetoed by Chat's caller
1647
+ * forwarding permission. On unclassified routes every caller tier remains subject
1648
+ * to `forwardCallerTier`.
1574
1649
  */
1575
1650
  export function applyServiceTierGate(
1576
1651
  provider: OcxProviderConfig,
1577
1652
  rawBody: unknown,
1578
- options: { serviceTier?: string },
1653
+ options: { serviceTier?: string; tierDecision?: TierDecision },
1579
1654
  modelId?: string,
1580
1655
  providerName?: string,
1581
1656
  inbound: InboundWire = "responses",
1657
+ resolvedPolicy?: ResolvedFastPolicy,
1582
1658
  ): void {
1583
1659
  // A direct unit caller without a model id retains the historical tri-state behavior for
1584
1660
  // adapters outside the OpenAI service-tier family. Once a model is known, resolve the final
1585
1661
  // model adapter as well: an explicit override to Anthropic (or another non-OpenAI wire) must
1586
1662
  // not carry a caller-supplied `service_tier` through a route that cannot forward it.
1587
1663
  if (modelId === undefined && !SERVICE_TIER_ADAPTERS.has(provider.adapter)) return;
1588
- const support = modelId === undefined
1589
- ? provider.supportsServiceTier
1590
- : serviceTierSupportForModel(provider, modelId, providerName, inbound);
1591
- if (support !== false) return;
1664
+ const policy = modelId === undefined
1665
+ ? undefined
1666
+ : resolvedPolicy ?? fastPolicyForModel(provider, modelId, providerName, inbound);
1667
+ const forwardCallerTier = modelId === undefined
1668
+ ? provider.supportsServiceTier !== false
1669
+ : policy!.forwardCallerTier;
1670
+ const rawTier = rawBody && typeof rawBody === "object"
1671
+ ? (rawBody as Record<string, unknown>).service_tier
1672
+ : undefined;
1673
+ const canonicalDecision = options.tierDecision?.kind === "set";
1674
+ const callerTierIsForeign = rawTier !== undefined
1675
+ && (typeof rawTier !== "string" || canonicalFastTierMarker(rawTier) === undefined);
1676
+ const dropForeignCallerTier = policy?.capability === true
1677
+ && policy.fastWire?.kind === "service-tier"
1678
+ && policy.fastWire?.foreignCallerTiers === "drop"
1679
+ && callerTierIsForeign;
1680
+ if (policy && policy.capability !== false && canonicalDecision) return;
1681
+ if (forwardCallerTier && !dropForeignCallerTier) return;
1592
1682
  if (rawBody && typeof rawBody === "object") {
1593
1683
  delete (rawBody as Record<string, unknown>).service_tier;
1594
1684
  }
@@ -1724,6 +1814,7 @@ async function handleResponsesInner(
1724
1814
  }
1725
1815
  logCtx.requestedModel = parsed.modelId;
1726
1816
  logCtx.requestedEffort = parsed.options.reasoning;
1817
+ logCtx.callerServiceTier = sanitizeLogMetadataString(parsed.options.serviceTier);
1727
1818
  logCtx.requestedServiceTier = parsed.options.serviceTier;
1728
1819
  logCtx.requestedSpeedLabel = requestLogSpeedLabel(parsed.options.serviceTier);
1729
1820
  logCtx.configuredServiceTier = readConfiguredCodexServiceTier();
@@ -2107,13 +2198,14 @@ async function handleResponsesInner(
2107
2198
  }
2108
2199
  } catch (err) {
2109
2200
  if (err instanceof UnsupportedOAuthProviderError) {
2201
+ const safeProviderName = redactSecretString(route.providerName);
2110
2202
  return formatErrorResponse(
2111
2203
  400,
2112
2204
  "invalid_request_error",
2113
- `${err.message}. Remove or reconfigure provider '${route.providerName}' in ${getConfigPath()}.`,
2205
+ `${redactSecretString(err.message)}. Remove or reconfigure provider '${safeProviderName}' in the OpenCodex configuration.`,
2114
2206
  );
2115
2207
  }
2116
- return formatErrorResponse(401, "authentication_error", err instanceof Error ? err.message : String(err));
2208
+ return formatErrorResponse(401, "authentication_error", publicOAuthAuthenticationErrorMessage(err));
2117
2209
  }
2118
2210
  }
2119
2211
  route.provider = resolveProviderTransport(
@@ -2166,6 +2258,9 @@ async function handleResponsesInner(
2166
2258
  (logCtx.attempts ??= []).push(attempt);
2167
2259
  }
2168
2260
  sealRequestAttemptIdentity(logCtx.activeAttempt, logCtx.provider, adapter.name, logCtx.accountLogLabel);
2261
+ if (adapter.runTurn) {
2262
+ recordAdapterTierMetadata(logCtx, adapter.tierLogForRunTurn?.(parsed));
2263
+ }
2169
2264
  // Optional route-identity linkage for attempt correlation (CL-09 consumes it). The slot
2170
2265
  // resolves to null unless an opt-in subsystem registered a linker, so an install without
2171
2266
  // routing profiles does no work here and loads no additional module. The non-throwing
@@ -2400,6 +2495,7 @@ async function handleResponsesInner(
2400
2495
  }
2401
2496
  : undefined;
2402
2497
  recordAdapterReasoning(logCtx, request);
2498
+ recordAdapterTier(logCtx, request);
2403
2499
  const actualHostKey = upstreamHostHealthKey(
2404
2500
  route.providerName,
2405
2501
  safeOriginLabel(request.url),
@@ -2781,6 +2877,10 @@ async function handleResponsesInner(
2781
2877
  ? createResponsesItemIdPayloadRewrite(repairConfig!, translatorBudget)
2782
2878
  : undefined,
2783
2879
  responseModelRewrite,
2880
+ parsed.options.hideThinkingSummary !== true
2881
+ && routeUsesContentChannelReasoning(route.provider, route.modelId)
2882
+ ? createReasoningSummaryChannelPayloadRewrite()
2883
+ : undefined,
2784
2884
  ].filter((rewrite): rewrite is NonNullable<typeof rewrite> => rewrite !== undefined);
2785
2885
  // #893: sparse-snapshot gateways get field backfills AND lifecycle event
2786
2886
  // injection at the block level, after payload rewrites. Defaults come
@@ -2799,6 +2899,7 @@ async function handleResponsesInner(
2799
2899
  snapshotRepairEnabled
2800
2900
  ? createResponsesSnapshotBlockRewrite(outboundRequestBody, translatorBudget)
2801
2901
  : undefined,
2902
+ createResponsesFieldBackfillBlockRewrite(),
2802
2903
  // Last: every rewrite above can still rename or reshape a call item, so the guard must
2803
2904
  // compare the names the client will actually receive against the declared catalog.
2804
2905
  undeclaredToolGuardActive
@@ -2992,9 +3093,16 @@ async function handleResponsesInner(
2992
3093
  const repaired = hasResponsesSnapshotRepair(route.provider.responsesSnapshotRepair)
2993
3094
  ? repairResponsesSnapshotJson(restored, outboundRequestBody)
2994
3095
  : restored;
2995
- return parsed._responseModelId !== undefined && parsed._responseModelId !== parsed.modelId
2996
- ? rewriteResponsesModelJson(repaired, parsed._responseModelId)
2997
- : repaired;
3096
+ const modelRewritten = parsed._responseModelId !== undefined && parsed._responseModelId !== parsed.modelId
3097
+ ? rewriteResponsesModelJson(backfillResponsesFieldsJson(repaired), parsed._responseModelId)
3098
+ : backfillResponsesFieldsJson(repaired);
3099
+ // The bounded-JSON answer bypasses the SSE payload rewrite, so content-
3100
+ // channel reasoning needs the same normalization here for the plain
3101
+ // JSON answer and every reframed-SSE variant built from clientJson.
3102
+ return parsed.options.hideThinkingSummary !== true
3103
+ && routeUsesContentChannelReasoning(route.provider, route.modelId)
3104
+ ? rewriteReasoningSummaryInJsonString(modelRewritten)
3105
+ : modelRewritten;
2998
3106
  })();
2999
3107
  // #1700: same fail-closed policy as the SSE relay above. Both the plain JSON answer and
3000
3108
  // the reframed-SSE branch below are built from this body, so one check covers them. This
@@ -3196,7 +3304,10 @@ async function handleResponsesInner(
3196
3304
  stallTimeoutSec: config.stallTimeoutSec,
3197
3305
  waitForRequestSlot: imageProviderFetch.waitForPacing,
3198
3306
  fetchImpl: imageProviderFetch.unpacedFetch ?? imageProviderFetch,
3199
- onRequestBuilt: request => recordAdapterReasoning(logCtx, request),
3307
+ onRequestBuilt: request => {
3308
+ recordAdapterReasoning(logCtx, request);
3309
+ recordAdapterTier(logCtx, request);
3310
+ },
3200
3311
  ...(vidPlan?.timeoutMs ? { videoTimeoutMs: vidPlan.timeoutMs } : {}),
3201
3312
  onUsage: usage => {
3202
3313
  // Cursor may assign _cursorConversationId inside the image loop's first runTurn;
@@ -3274,7 +3385,10 @@ async function handleResponsesInner(
3274
3385
  forceEmptyResponseId: true,
3275
3386
  abortSignal: options.abortSignal,
3276
3387
  ...(options.onFirstOutput ? { onFirstOutput: options.onFirstOutput } : {}),
3277
- onRequestBuilt: request => recordAdapterReasoning(logCtx, request),
3388
+ onRequestBuilt: request => {
3389
+ recordAdapterReasoning(logCtx, request);
3390
+ recordAdapterTier(logCtx, request);
3391
+ },
3278
3392
  onAttemptSend: (recovery?: AttemptRecoveryKind) =>
3279
3393
  noteAttemptSend(logCtx.activeAttempt, logCtx.usageLogInputTokens, recovery),
3280
3394
  onUsage: usage => {
@@ -3368,9 +3482,26 @@ async function handleResponsesInner(
3368
3482
  await waitForProviderRequestSlot(route.providerName, route.provider, route.modelId, runTurnAbort.signal);
3369
3483
  }
3370
3484
  noteAttemptSend(logCtx.activeAttempt, logCtx.usageLogInputTokens, recovery);
3485
+ const runTurnProviderFetch = providerFetch(
3486
+ route.provider,
3487
+ options.codexWsRuntimeIdentity,
3488
+ {
3489
+ providerName: route.providerName,
3490
+ modelId: route.modelId,
3491
+ // runTurnAttempt acquired this logical turn's first physical-request slot above.
3492
+ // Cursor HTTP/1.1 consumes it for RunSSE; every BidiAppend and redial then waits on
3493
+ // the same provider queue through this stateful wrapper.
3494
+ pacingSlotAcquired: true,
3495
+ },
3496
+ );
3371
3497
  await adapter.runTurn?.(
3372
3498
  parsed,
3373
- { headers: selectedForwardHeaders, abortSignal: runTurnAbort.signal, translatorBudget },
3499
+ {
3500
+ headers: selectedForwardHeaders,
3501
+ abortSignal: runTurnAbort.signal,
3502
+ translatorBudget,
3503
+ providerFetch: runTurnProviderFetch,
3504
+ },
3374
3505
  targetQueue.push,
3375
3506
  );
3376
3507
  } catch (err) {
@@ -3445,6 +3576,9 @@ async function handleResponsesInner(
3445
3576
  toolParameterSchemas,
3446
3577
  ...(options.onFirstOutput ? { onFirstOutput: options.onFirstOutput } : {}),
3447
3578
  ...(routedCompaction ? { compaction: true } : {}),
3579
+ // grok-build's strict decoder dies on the typed response.heartbeat frame; its
3580
+ // eventsource layer tolerates comment keep-alives. Codex needs the opposite.
3581
+ ...(logCtx.surface === "grok" ? { heartbeatStyle: "comment" as const } : {}),
3448
3582
  onUsage: usage => {
3449
3583
  // Raw adapter usage, pre wire-normalization: the bridged SSE now always carries
3450
3584
  // zero-default detail objects, so provenance must come from here (cache_detail_missing).
@@ -3521,6 +3655,10 @@ async function handleResponsesInner(
3521
3655
  responseStateOptions(adapterNeedsForcedContinuation(adapter.name)),
3522
3656
  );
3523
3657
  }
3658
+ // #1926 gap 2: the buffered path queued its signature persists inside
3659
+ // buildResponseJSON; bound the durability window before the JSON becomes
3660
+ // externally visible.
3661
+ await awaitThoughtSignatureDurability();
3524
3662
  return new Response(JSON.stringify(json), { headers: { "Content-Type": "application/json" } });
3525
3663
  }
3526
3664
 
@@ -3544,6 +3682,7 @@ async function handleResponsesInner(
3544
3682
  try {
3545
3683
  initialRequest = await activeAdapter.buildRequest(parsed, { headers: selectedForwardHeaders, translatorBudget });
3546
3684
  recordAdapterReasoning(logCtx, initialRequest);
3685
+ recordAdapterTier(logCtx, initialRequest);
3547
3686
  inputTokenEstimate = typeof initialRequest.usageLog?.inputTokens === "number"
3548
3687
  ? initialRequest.usageLog.inputTokens
3549
3688
  : undefined;
@@ -3583,9 +3722,18 @@ async function handleResponsesInner(
3583
3722
  abortSignal: upstream.signal,
3584
3723
  timeoutMs: connectMs,
3585
3724
  stream: parsed.stream,
3725
+ executor: providerFetch(route.provider, options.codexWsRuntimeIdentity, {
3726
+ providerName: route.providerName,
3727
+ modelId: route.modelId,
3728
+ }),
3586
3729
  });
3587
3730
  } else {
3588
- upstreamResponse = await fetchWithResetRetry(
3731
+ // #1851 scope guard: transient-5xx retry on this generic adapter path is opt-in for
3732
+ // direct Google AI Studio only (Vertex/Antigravity use fetchResponse above). Other
3733
+ // adapters keep reset-only retry so combo failover still hops on the first 5xx
3734
+ // instead of burning ~1.2s of same-target retries per hop.
3735
+ const fetchWithRetryPolicy = route.provider.adapter === "google" ? fetchWithTransientRetry : fetchWithResetRetry;
3736
+ upstreamResponse = await fetchWithRetryPolicy(
3589
3737
  recovery => {
3590
3738
  noteAttemptSend(logCtx.activeAttempt, inputTokenEstimate, recovery);
3591
3739
  return fetchWithHeaderTimeout(builtInitialRequest.url, applyUpstreamRecoveryInit({
@@ -3648,6 +3796,7 @@ async function handleResponsesInner(
3648
3796
  ...(imageTierBias > 0 ? { imageTierBias } : {}),
3649
3797
  });
3650
3798
  recordAdapterReasoning(logCtx, retryRequest);
3799
+ recordAdapterTier(logCtx, retryRequest);
3651
3800
  } catch (err) {
3652
3801
  // A rotated/rebuilt adapter build failure is a request-shaping error, not an
3653
3802
  // upstream connect failure: tear the abort link down and map it as 400 (no 413
@@ -3673,7 +3822,15 @@ async function handleResponsesInner(
3673
3822
  try {
3674
3823
  if (activeAdapter.fetchResponse) {
3675
3824
  await waitForProviderRequestSlot(route.providerName, route.provider, route.modelId, upstream.signal);
3676
- return await activeAdapter.fetchResponse(retryRequest, { abortSignal: upstream.signal, timeoutMs: connectMs, stream: parsed.stream });
3825
+ return await activeAdapter.fetchResponse(retryRequest, {
3826
+ abortSignal: upstream.signal,
3827
+ timeoutMs: connectMs,
3828
+ stream: parsed.stream,
3829
+ executor: providerFetch(route.provider, options.codexWsRuntimeIdentity, {
3830
+ providerName: route.providerName,
3831
+ modelId: route.modelId,
3832
+ }),
3833
+ });
3677
3834
  }
3678
3835
  return await fetchWithHeaderTimeout(retryRequest.url, {
3679
3836
  method: retryRequest.method, headers: retryRequest.headers, body: retryRequest.body,
@@ -3709,7 +3866,7 @@ async function handleResponsesInner(
3709
3866
  refreshed = await forceRefreshOAuthAccessSnapshot(sentOAuthSnapshot);
3710
3867
  } catch (err) {
3711
3868
  cleanupUpstreamAbort();
3712
- return formatErrorResponse(401, "authentication_error", err instanceof Error ? err.message : String(err));
3869
+ return formatErrorResponse(401, "authentication_error", publicOAuthAuthenticationErrorMessage(err));
3713
3870
  }
3714
3871
  sentOAuthSnapshot = refreshed;
3715
3872
  replayOAuthCredentialSnapshot = {
@@ -3970,6 +4127,7 @@ async function handleResponsesInner(
3970
4127
  ...(imageTierBias > 0 ? { imageTierBias } : {}),
3971
4128
  });
3972
4129
  recordAdapterReasoning(logCtx, continuationRequest);
4130
+ recordAdapterTier(logCtx, continuationRequest);
3973
4131
  } catch (err) {
3974
4132
  // The main body is already streaming, so there is no HTTP error surface: release
3975
4133
  // any partial body observation and surface the failure as an in-stream error via
@@ -3999,9 +4157,16 @@ async function handleResponsesInner(
3999
4157
  abortSignal: upstream.signal,
4000
4158
  timeoutMs: connectMs,
4001
4159
  stream: nextParsed.stream,
4160
+ executor: providerFetch(route.provider, options.codexWsRuntimeIdentity, {
4161
+ providerName: route.providerName,
4162
+ modelId: nextParsed.modelId,
4163
+ }),
4002
4164
  });
4003
4165
  }
4004
- return await fetchWithResetRetry(
4166
+ // Same #1851 scope guard as the initial send: transient-5xx retry only for direct
4167
+ // Google AI Studio; every other adapter keeps reset-only semantics here.
4168
+ const fetchContinuationWithRetryPolicy = route.provider.adapter === "google" ? fetchWithTransientRetry : fetchWithResetRetry;
4169
+ return await fetchContinuationWithRetryPolicy(
4005
4170
  recovery => {
4006
4171
  noteAttemptSend(logCtx.activeAttempt, continuationEstimate, recovery ?? replayKind);
4007
4172
  return fetchWithHeaderTimeout(
@@ -4249,6 +4414,8 @@ async function handleResponsesInner(
4249
4414
  toolParameterSchemas,
4250
4415
  ...(options.onFirstOutput ? { onFirstOutput: options.onFirstOutput } : {}),
4251
4416
  ...(routedCompaction ? { compaction: true } : {}),
4417
+ // Same grok-surface split as the runTurn branch above.
4418
+ ...(logCtx.surface === "grok" ? { heartbeatStyle: "comment" as const } : {}),
4252
4419
  onUsage: usage => {
4253
4420
  // Raw adapter usage, pre wire-normalization (see the runTurn branch above).
4254
4421
  logCtx.usageFromBridge = true;
@@ -4337,6 +4504,8 @@ async function handleResponsesInner(
4337
4504
  responseStateOptions(activeAdapter.name === "kiro"),
4338
4505
  );
4339
4506
  }
4507
+ // #1926 gap 2: same buffered-path durability bound as the primary branch.
4508
+ await awaitThoughtSignatureDurability();
4340
4509
  return new Response(JSON.stringify(json), { headers: { "Content-Type": "application/json" } });
4341
4510
  }
4342
4511
 
@@ -33,7 +33,7 @@ import {
33
33
  import { isInjectionDebugEnabled } from "../../lib/debug-settings";
34
34
  import { injectionDebugLog } from "../../lib/injection-debug-log";
35
35
  import { modelInList, namespacedToolName } from "../../types";
36
- import type { AdapterEvent, OcxConfig, OcxParsedRequest, OcxProviderConfig, OcxProviderContinuationState, OcxUsage, UpstreamHttpVersion } from "../../types";
36
+ import type { AdapterEvent, OcxConfig, OcxParsedRequest, OcxProviderConfig, OcxProviderContinuationState, OcxUsage } from "../../types";
37
37
  import {
38
38
  forceRefreshOAuthAccessSnapshot,
39
39
  getOAuthCredentialApiBaseUrl,
@@ -103,7 +103,9 @@ import {
103
103
  import { hasResponsesItemIdRepair, relaySseWithResponsesItemIdRepair } from "../responses-item-id-repair";
104
104
  import type { EffectiveSubagentRoster, SpawnAgentSurface } from "../../codex/catalog";
105
105
  import { waitForProviderRequestSlot } from "../../providers/request-pacing";
106
+ import { withUpstreamHttpVersion } from "../../lib/upstream-http-version";
106
107
 
108
+ export { withUpstreamHttpVersion } from "../../lib/upstream-http-version";
107
109
 
108
110
  export function disableResponsesRequestTimeout(req: Request, server: Pick<Server<WsData>, "timeout"> | undefined): boolean {
109
111
  if (!server) return false;
@@ -147,38 +149,8 @@ export type ProviderFetch = typeof globalThis.fetch & PaceAwareFetch;
147
149
  export interface ProviderFetchOptions {
148
150
  providerName?: string;
149
151
  modelId?: string;
150
- }
151
-
152
- /**
153
- * Bun's fetch accepts a non-standard `protocol` init to pin the HTTP version
154
- * (BunFetchRequestInit.protocol). The DOM lib types do not include it, so the
155
- * value is carried on an intersection and stripped before non-Bun callers.
156
- * The accepted values are the shared UPSTREAM_HTTP_VERSION_VALUES enum from types.
157
- */
158
- const UPSTREAM_HTTP_VERSION_PROTOCOL: Record<Exclude<UpstreamHttpVersion, "auto">, string> = {
159
- "http1.1": "http1.1",
160
- h1: "h1",
161
- http2: "http2",
162
- h2: "h2",
163
- };
164
-
165
- /** Attach Bun's `protocol` pin when the provider opted into a fixed HTTP version. */
166
- export function withUpstreamHttpVersion(
167
- input: Parameters<typeof globalThis.fetch>[0],
168
- init: RequestInit | undefined,
169
- provider: OcxProviderConfig,
170
- ): RequestInit | undefined {
171
- const version = provider.upstreamHttpVersion;
172
- if (!version || version === "auto") return init;
173
- // Bun's protocol pin requires an https: target; local/plaintext upstreams keep
174
- // their existing transport untouched.
175
- const target = typeof input === "string" ? input : input instanceof URL ? input.href : input.url;
176
- try {
177
- if (new URL(target).protocol !== "https:") return init;
178
- } catch {
179
- return init;
180
- }
181
- return { ...(init ?? {}), protocol: UPSTREAM_HTTP_VERSION_PROTOCOL[version] } as RequestInit;
152
+ /** One pacing slot was acquired immediately before this fetch wrapper was created. */
153
+ pacingSlotAcquired?: boolean;
182
154
  }
183
155
 
184
156
  export function providerFetch(
@@ -196,9 +168,16 @@ export function providerFetch(
196
168
  }
197
169
  return base(input, withUpstreamHttpVersion(input, init, provider));
198
170
  };
199
- const waitForPacing = (signal?: AbortSignal) => options.providerName
200
- ? waitForProviderRequestSlot(options.providerName, provider, options.modelId, signal)
201
- : Promise.resolve();
171
+ let pacingSlotAcquired = options.pacingSlotAcquired === true;
172
+ const waitForPacing = (signal?: AbortSignal) => {
173
+ if (pacingSlotAcquired) {
174
+ pacingSlotAcquired = false;
175
+ return Promise.resolve();
176
+ }
177
+ return options.providerName
178
+ ? waitForProviderRequestSlot(options.providerName, provider, options.modelId, signal)
179
+ : Promise.resolve();
180
+ };
202
181
  const wrapped = async (input: Parameters<typeof globalThis.fetch>[0], init?: RequestInit) => {
203
182
  await waitForPacing(init?.signal ?? undefined);
204
183
  return unpaced(input, init);