@bitkyc08/opencodex 2.55.0 → 2.57.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 (256) hide show
  1. package/bin/ocx.mjs +10 -0
  2. package/gui/dist/assets/{index-BBOZWGB6.css → index-C5-RdDmD.css} +1 -1
  3. package/gui/dist/assets/{index-VuoiWj9J.js → index-Cz7CLdif.js} +21 -21
  4. package/gui/dist/index.html +2 -2
  5. package/package.json +4 -3
  6. package/src/adapters/base.ts +21 -0
  7. package/src/adapters/codebuddy/adapter.ts +2 -1
  8. package/src/adapters/codebuddy/scaffold-guard.ts +248 -0
  9. package/src/adapters/command-code.ts +1 -1
  10. package/src/adapters/cursor/envelope-echo.ts +8 -2
  11. package/src/adapters/cursor/transport-retry.ts +46 -1
  12. package/src/adapters/cursor.ts +4 -0
  13. package/src/adapters/google.ts +7 -7
  14. package/src/adapters/kiro/adapter.ts +42 -1
  15. package/src/adapters/kiro/payload.ts +17 -3
  16. package/src/adapters/kiro/reasoning.ts +70 -7
  17. package/src/adapters/kiro/stream.ts +8 -2
  18. package/src/adapters/kiro/wire.ts +2 -1
  19. package/src/adapters/kiro-events.ts +21 -13
  20. package/src/adapters/kiro-retry.ts +23 -4
  21. package/src/adapters/openai-chat/errors.ts +116 -0
  22. package/src/adapters/openai-chat/messages.ts +346 -0
  23. package/src/adapters/openai-chat/passthrough.ts +146 -0
  24. package/src/adapters/openai-chat/response-events.ts +117 -0
  25. package/src/adapters/openai-chat/tool-call-validation.ts +200 -0
  26. package/src/adapters/openai-chat/tool-name-registry.ts +166 -0
  27. package/src/adapters/openai-chat/tool-schema.ts +495 -0
  28. package/src/adapters/openai-chat/wire.ts +50 -0
  29. package/src/adapters/openai-chat.ts +40 -1452
  30. package/src/adapters/openai-responses/canonical-forward.ts +202 -0
  31. package/src/adapters/openai-responses/image-gen.ts +406 -0
  32. package/src/adapters/openai-responses/internal.ts +3 -0
  33. package/src/adapters/openai-responses/passthrough.ts +642 -0
  34. package/src/adapters/openai-responses/prompt-cache.ts +83 -0
  35. package/src/adapters/openai-responses/reasoning.ts +220 -0
  36. package/src/adapters/openai-responses/request-strips.ts +185 -0
  37. package/src/adapters/openai-responses/tool-output-recovery.ts +509 -0
  38. package/src/adapters/openai-responses/tool-schema.ts +293 -0
  39. package/src/adapters/openai-responses/web-search.ts +156 -0
  40. package/src/adapters/openai-responses.ts +4 -2625
  41. package/src/bridge/errors.ts +58 -0
  42. package/src/bridge/internal.ts +174 -0
  43. package/src/bridge/response-json.ts +630 -0
  44. package/src/bridge/sse.ts +1462 -0
  45. package/src/bridge.ts +5 -2204
  46. package/src/chat/inbound.ts +12 -1
  47. package/src/claude/desktop-profile.ts +66 -9
  48. package/src/claude/outbound.ts +18 -0
  49. package/src/cli/account-main.ts +1 -1
  50. package/src/cli/capabilities.ts +2 -2
  51. package/src/cli/combo.ts +10 -1
  52. package/src/cli/index.ts +48 -5
  53. package/src/cli/registry.ts +2 -1
  54. package/src/cli/system-command.ts +4 -4
  55. package/src/clients/config-export.ts +7 -3
  56. package/src/codex/account-label.ts +14 -3
  57. package/src/codex/account-lifecycle.ts +3 -0
  58. package/src/codex/account-store.ts +184 -35
  59. package/src/codex/account-usability.ts +21 -0
  60. package/src/codex/auth-api/account-list.ts +507 -0
  61. package/src/codex/auth-api/http.ts +32 -0
  62. package/src/codex/auth-api/login-flow.ts +566 -0
  63. package/src/codex/auth-api/login-state.ts +64 -0
  64. package/src/codex/auth-api/main-account-probe.ts +331 -0
  65. package/src/codex/auth-api/pool-mode-gate.ts +274 -0
  66. package/src/codex/auth-api/pool-quota-probe.ts +512 -0
  67. package/src/codex/auth-api/reset-credit-service.ts +431 -0
  68. package/src/codex/auth-api/routes.ts +425 -0
  69. package/src/codex/auth-api/runtime-config.ts +48 -0
  70. package/src/codex/auth-api.ts +27 -3118
  71. package/src/codex/auth-context.ts +252 -35
  72. package/src/codex/catalog/aggregation.ts +80 -1
  73. package/src/codex/catalog/auto-review.ts +507 -0
  74. package/src/codex/catalog/build-entries.ts +981 -0
  75. package/src/codex/catalog/combo-member.ts +375 -0
  76. package/src/codex/catalog/derive-entry.ts +229 -0
  77. package/src/codex/catalog/effort.ts +0 -1
  78. package/src/codex/catalog/gated-native-warn.ts +63 -0
  79. package/src/codex/catalog/gather-capture.ts +533 -0
  80. package/src/codex/catalog/model-hints.ts +691 -0
  81. package/src/codex/catalog/model-visibility.ts +305 -0
  82. package/src/codex/catalog/provider-fetch.ts +52 -2942
  83. package/src/codex/catalog/provider-models.ts +685 -0
  84. package/src/codex/catalog/remote.ts +30 -0
  85. package/src/codex/catalog/restore.ts +132 -0
  86. package/src/codex/catalog/retained-sync.ts +714 -0
  87. package/src/codex/catalog/routed-gather.ts +895 -0
  88. package/src/codex/catalog/subagent-roster.ts +176 -0
  89. package/src/codex/catalog/sync.ts +52 -2698
  90. package/src/codex/cli-install-provenance.ts +7 -1
  91. package/src/codex/convergence.ts +7 -2
  92. package/src/codex/desktop-app/types.ts +11 -2
  93. package/src/codex/desktop-app/windows.ts +5 -5
  94. package/src/codex/inject/config-toml.ts +563 -0
  95. package/src/codex/inject/remove.ts +192 -0
  96. package/src/codex/inject/restore.ts +567 -0
  97. package/src/codex/inject/routing-classify.ts +109 -0
  98. package/src/codex/inject/routing-target.ts +125 -0
  99. package/src/codex/inject.ts +89 -1444
  100. package/src/codex/lineage.ts +458 -0
  101. package/src/codex/model-entitlements.ts +152 -15
  102. package/src/codex/pool-refresh-backoff.ts +161 -0
  103. package/src/codex/quota-rejection.ts +104 -15
  104. package/src/codex/routing/active-account.ts +194 -0
  105. package/src/codex/routing/cache-affinity.ts +70 -0
  106. package/src/codex/routing/cooldown-math.ts +285 -0
  107. package/src/codex/routing/health-store.ts +402 -0
  108. package/src/codex/routing/probe-lease.ts +358 -0
  109. package/src/codex/routing/selection.ts +780 -0
  110. package/src/codex/routing/thread-affinity.ts +586 -0
  111. package/src/codex/routing/transient-hold-dispatch.ts +141 -0
  112. package/src/codex/routing.ts +370 -2271
  113. package/src/codex/shim-fingerprint.ts +223 -0
  114. package/src/codex/shim-inspect.ts +175 -0
  115. package/src/codex/shim-probe.ts +367 -0
  116. package/src/codex/shim-restore-lock.ts +169 -0
  117. package/src/codex/shim-state-file.ts +151 -0
  118. package/src/codex/shim-templates.ts +265 -0
  119. package/src/codex/shim.ts +48 -1268
  120. package/src/codex/warmup.ts +1 -1
  121. package/src/combos/failover.ts +85 -0
  122. package/src/combos/request.ts +17 -10
  123. package/src/combos/types.ts +23 -2
  124. package/src/config/diagnostics.ts +705 -0
  125. package/src/config/feature-flags.ts +55 -0
  126. package/src/config/live-reconcile.ts +403 -0
  127. package/src/config/load-degrade.ts +880 -0
  128. package/src/config/mutation-lock.ts +244 -0
  129. package/src/config/openai-tier-backup.ts +268 -0
  130. package/src/config/pending-teardown.ts +31 -0
  131. package/src/config/persist-unlocked.ts +92 -0
  132. package/src/config/proxy-env.ts +188 -0
  133. package/src/config/salvage.ts +244 -0
  134. package/src/config/schema/config-schema.ts +640 -0
  135. package/src/config/schema/leaf-validators.ts +855 -0
  136. package/src/config/warn-memo.ts +28 -0
  137. package/src/config.ts +234 -4481
  138. package/src/generated/compatibility-version.json +649 -121
  139. package/src/images/loop.ts +1 -1
  140. package/src/lib/errors.ts +17 -0
  141. package/src/lib/request-execution-budget.ts +198 -23
  142. package/src/lib/spend-reservation-ledger.ts +958 -0
  143. package/src/lib/state-store-registrations.ts +6 -2
  144. package/src/lib/test-home-guard.ts +85 -1
  145. package/src/lib/upstream-retry.ts +132 -21
  146. package/src/lib/windows-elevation.ts +76 -14
  147. package/src/lib/workflow-budget.ts +553 -30
  148. package/src/oauth/index.ts +2 -2
  149. package/src/oauth/key-providers.ts +2 -2
  150. package/src/providers/kiro-models.ts +4 -3
  151. package/src/providers/label.ts +19 -1
  152. package/src/providers/model-discovery.ts +16 -0
  153. package/src/providers/quota/account-cache.ts +441 -0
  154. package/src/providers/quota/antigravity.ts +295 -0
  155. package/src/providers/quota/report-cache.ts +320 -0
  156. package/src/providers/quota/vendor-probes-key.ts +1243 -0
  157. package/src/providers/quota/vendor-probes-oauth.ts +590 -0
  158. package/src/providers/quota.ts +324 -3079
  159. package/src/providers/registry/entries-core.ts +1228 -0
  160. package/src/providers/registry/entries-extended.ts +1213 -0
  161. package/src/providers/registry/model-seeds.ts +912 -0
  162. package/src/providers/registry/types.ts +352 -0
  163. package/src/providers/registry.ts +24 -3536
  164. package/src/responses/continuation-ownership.ts +29 -0
  165. package/src/responses/reasoning-envelope.ts +6 -3
  166. package/src/responses/state/replay-fingerprint.ts +80 -0
  167. package/src/responses/state/snapshot-codec.ts +104 -0
  168. package/src/responses/state/spill-failure.ts +118 -0
  169. package/src/responses/state/spill-queue.ts +665 -0
  170. package/src/responses/state/temp-recovery.ts +257 -0
  171. package/src/responses/state.ts +82 -1143
  172. package/src/routing/identity-domains.ts +456 -0
  173. package/src/routing/probe-lease.ts +613 -0
  174. package/src/server/chat-completions.ts +3 -1
  175. package/src/server/chat-native.ts +37 -9
  176. package/src/server/index/bounded-request.ts +88 -0
  177. package/src/server/index/live-sideband.ts +601 -0
  178. package/src/server/index/serve-options.ts +1766 -0
  179. package/src/server/index/startup-warnings.ts +213 -0
  180. package/src/server/index/websocket-handler.ts +339 -0
  181. package/src/server/index.ts +45 -2552
  182. package/src/server/inspection-tee.ts +107 -0
  183. package/src/server/live.ts +46 -1
  184. package/src/server/management/combo-routes.ts +10 -1
  185. package/src/server/management/route-registry.ts +26 -23
  186. package/src/server/management/shared.ts +8 -5
  187. package/src/server/management/workflow-budget-routes.ts +133 -0
  188. package/src/server/management-api.ts +12 -0
  189. package/src/server/relay-eager.ts +2 -0
  190. package/src/server/relay.ts +14 -19
  191. package/src/server/request-log-conversation.ts +9 -7
  192. package/src/server/request-log.ts +372 -4
  193. package/src/server/response-log-body.ts +153 -0
  194. package/src/server/responses/account-change-state.ts +307 -0
  195. package/src/server/responses/adapter-continuation.ts +540 -0
  196. package/src/server/responses/adapter-delivery.ts +208 -0
  197. package/src/server/responses/adapter-dispatch.ts +1042 -0
  198. package/src/server/responses/codex-ws-wire.ts +5 -0
  199. package/src/server/responses/collaboration.ts +74 -4
  200. package/src/server/responses/combo-session-recall.ts +68 -8
  201. package/src/server/responses/compact.ts +113 -17
  202. package/src/server/responses/completion-policy.ts +33 -0
  203. package/src/server/responses/core-auth.ts +529 -0
  204. package/src/server/responses/core-codex-account.ts +907 -0
  205. package/src/server/responses/core-combo-failure.ts +210 -0
  206. package/src/server/responses/core-combo.ts +787 -0
  207. package/src/server/responses/core-errors.ts +170 -0
  208. package/src/server/responses/core-lifetime.ts +95 -0
  209. package/src/server/responses/core-normalize.ts +350 -0
  210. package/src/server/responses/core-opaque-recovery.ts +380 -0
  211. package/src/server/responses/core-options.ts +159 -0
  212. package/src/server/responses/core-replay.ts +298 -0
  213. package/src/server/responses/core.ts +192 -8893
  214. package/src/server/responses/encrypted-payload.ts +0 -1
  215. package/src/server/responses/input-admission.ts +126 -6
  216. package/src/server/responses/passthrough-delivery.ts +869 -0
  217. package/src/server/responses/passthrough-dispatch.ts +1494 -0
  218. package/src/server/responses/passthrough-error.ts +38 -2
  219. package/src/server/responses/passthrough-execution.ts +54 -0
  220. package/src/server/responses/request-prepare.ts +1080 -0
  221. package/src/server/responses/request-send-budget.ts +259 -0
  222. package/src/server/responses/request-sidecar-auth.ts +149 -0
  223. package/src/server/responses/request-spend.ts +147 -0
  224. package/src/server/responses/request-transport.ts +803 -0
  225. package/src/server/responses/response-effects.ts +157 -0
  226. package/src/server/responses/run-turn-execution.ts +476 -0
  227. package/src/server/responses/sidecar-execution.ts +463 -0
  228. package/src/server/responses/terminal-guard.ts +65 -4
  229. package/src/server/responses-image-gen-repair.ts +1 -1
  230. package/src/server/responses-undeclared-tool-guard.ts +9 -5
  231. package/src/server/workflow-refusal.ts +84 -0
  232. package/src/service/windows-ops.ts +210 -16
  233. package/src/service/windows-scheduler.ts +28 -21
  234. package/src/service.ts +1 -1
  235. package/src/types/config.ts +34 -1
  236. package/src/types/request.ts +8 -5
  237. package/src/types/tools.ts +24 -0
  238. package/src/types.ts +2 -0
  239. package/src/update/index.ts +10 -0
  240. package/src/update/stop-contract.d.mts +1 -0
  241. package/src/update/stop-contract.mjs +19 -0
  242. package/src/update/stop-decision.d.mts +1 -1
  243. package/src/update/stop-decision.mjs +12 -3
  244. package/src/usage/log.ts +147 -1
  245. package/src/usage/summary.ts +171 -21
  246. package/src/vision/anthropic-describe.ts +1 -1
  247. package/src/vision/describe.ts +5 -5
  248. package/src/web-search/anthropic-executor.ts +1 -1
  249. package/src/web-search/exa-executor.ts +1 -1
  250. package/src/web-search/executor.ts +1 -1
  251. package/src/web-search/gemini-executor.ts +1 -1
  252. package/src/web-search/loop.ts +1 -1
  253. package/src/web-search/ollama-executor.ts +1 -1
  254. package/src/web-search/parse.ts +67 -14
  255. package/src/web-search/passthrough-bridge.ts +64 -31
  256. package/src/web-search/xai-executor.ts +1 -1
@@ -0,0 +1,298 @@
1
+ import type {
2
+ OcxProviderContinuationOwner,
3
+ OcxProviderContinuationState,
4
+ OcxParsedRequest,
5
+ OcxProviderConfig,
6
+ OcxReasoningReplayIdentity,
7
+ AdapterEvent,
8
+ } from "../../types";
9
+ import {
10
+ isValidProviderContinuationOwner,
11
+ sameProviderContinuationOwner,
12
+ providerContinuationOwnerFromReplayIdentity,
13
+ providerContinuationRouteScope,
14
+ } from "../../responses/provider-continuation";
15
+ import {
16
+ reasoningReplayDestinationIdentity,
17
+ reasoningReplayOAuthCredentialIdentity,
18
+ durableReplayCredentialIdentity,
19
+ reasoningReplayCodexCredentialIdentity,
20
+ reasoningReplayKeyCredentialIdentity,
21
+ durableReplayDestinationIdentity,
22
+ bindReasoningReplayScope,
23
+ reasoningReplayServingIdentityChanged,
24
+ reasoningReplayOpaqueBlobRejectionMemoized,
25
+ } from "../../responses/reasoning-replay-cache";
26
+ import type { OAuthAccessSnapshot } from "../../oauth";
27
+ import type { CodexAuthContext } from "../../codex/auth-context";
28
+ import { thoughtSignatureReplaySalt } from "../../responses/thought-signature-replay";
29
+ import { randomUUID } from "node:crypto";
30
+ import { requiresPlaintextReasoningReplay } from "../../adapters/openai-responses/passthrough";
31
+
32
+ /**
33
+ * Adapters whose continuation state must survive Codex's store:false requests.
34
+ */
35
+ export function adapterNeedsForcedContinuation(name: string): boolean {
36
+ return name === "kiro" || name === "cursor";
37
+ }
38
+
39
+
40
+ export type ContinuationOwnerRead =
41
+ | { kind: "missing" }
42
+ | { kind: "invalid" }
43
+ | { kind: "valid"; owner: OcxProviderContinuationOwner };
44
+
45
+
46
+ export function readProviderContinuationOwner(
47
+ state: OcxProviderContinuationState | undefined,
48
+ ): ContinuationOwnerRead {
49
+ if (!state || state.__ocxOwner === undefined) return { kind: "missing" };
50
+ const owner = state.__ocxOwner;
51
+ if (!isValidProviderContinuationOwner(owner)) return { kind: "invalid" };
52
+ return { kind: "valid", owner: { ...owner } };
53
+ }
54
+
55
+
56
+ export function providerContinuationPayload(
57
+ state: OcxProviderContinuationState | undefined,
58
+ ): OcxProviderContinuationState | undefined {
59
+ if (!state) return undefined;
60
+ const cloned = structuredClone(state);
61
+ delete cloned.__ocxOwner;
62
+ return Object.keys(cloned).length > 0 ? cloned : undefined;
63
+ }
64
+
65
+
66
+ export function bindProviderContinuationForRoute(
67
+ parsed: OcxParsedRequest,
68
+ currentOwner: OcxProviderContinuationOwner | undefined,
69
+ ): void {
70
+ const candidate = parsed._providerContinuationCandidate;
71
+ const storedOwner = readProviderContinuationOwner(candidate);
72
+ const mayRestore = storedOwner.kind === "valid"
73
+ && !!currentOwner
74
+ && sameProviderContinuationOwner(storedOwner.owner, currentOwner);
75
+ const restored = mayRestore ? providerContinuationPayload(candidate) : undefined;
76
+ if (restored) parsed._providerContinuation = restored;
77
+ else delete parsed._providerContinuation;
78
+ const cursorConversationId = restored?.cursor?.conversationId;
79
+ if (cursorConversationId) parsed._cursorConversationId = cursorConversationId;
80
+ else delete parsed._cursorConversationId;
81
+ if (currentOwner) parsed._providerContinuationOwner = { ...currentOwner };
82
+ else delete parsed._providerContinuationOwner;
83
+ }
84
+
85
+
86
+ export function providerContinuationDestinationIdentity(
87
+ parsed: OcxParsedRequest,
88
+ provider: OcxProviderConfig,
89
+ ): string | undefined {
90
+ const kiroContext = parsed._kiroAuthContext;
91
+ return reasoningReplayDestinationIdentity(JSON.stringify([
92
+ provider.baseUrl.trim().replace(/\/+$/, ""),
93
+ provider.responsesPath ?? "",
94
+ kiroContext?.profileArn ?? "",
95
+ kiroContext?.apiRegion ?? "",
96
+ kiroContext?.ssoRegion ?? "",
97
+ ]));
98
+ }
99
+
100
+
101
+ export function bindRouteReasoningReplayScope(args: {
102
+ parsed: OcxParsedRequest;
103
+ providerName: string;
104
+ provider: OcxProviderConfig;
105
+ adapterName: string;
106
+ oauthCredentialSnapshot?: Pick<OAuthAccessSnapshot, "accountId" | "generation">;
107
+ codexAuthContext?: CodexAuthContext;
108
+ forwardHeaders?: Headers;
109
+ }): void {
110
+ const { parsed, providerName, provider, adapterName } = args;
111
+ const replayIdentity = routeReasoningReplayIdentity({
112
+ ...args,
113
+ modelId: parsed.modelId,
114
+ });
115
+ const continuationDestinationIdentity = providerContinuationDestinationIdentity(parsed, provider);
116
+ const continuationOwner = providerContinuationOwnerFromReplayIdentity(
117
+ replayIdentity && continuationDestinationIdentity
118
+ ? { ...replayIdentity, providerDestinationIdentity: continuationDestinationIdentity }
119
+ : undefined,
120
+ );
121
+ if (adapterName === "cursor") {
122
+ // The final route owner is authoritative for Cursor and supersedes the account-derived
123
+ // seed assigned before route binding. A Cursor conversation must be scoped to the exact
124
+ // provider/destination/adapter/model/credential that serves it.
125
+ if (continuationOwner) parsed._cursorIdentityScope = providerContinuationRouteScope(continuationOwner);
126
+ else if (!parsed._cursorIdentityScope?.startsWith("cursor-unowned:")) {
127
+ // Prevent the adapter's token-only fallback from recreating a provider-private id after the
128
+ // route owner failed closed. The sentinel is per parsed request and contains no credential.
129
+ parsed._cursorIdentityScope = `cursor-unowned:${randomUUID()}`;
130
+ }
131
+ }
132
+ bindReasoningReplayScope(
133
+ parsed._reasoningReplayScope,
134
+ replayIdentity,
135
+ );
136
+ // Keep this sticky for the whole outbound request: a later auth/key rebind may compare equal
137
+ // after the first mismatch, but it cannot make history minted by the prior route decodable.
138
+ if (reasoningReplayServingIdentityChanged(parsed._reasoningReplayScope)) {
139
+ parsed._stripReasoningEncryptedContent = true;
140
+ }
141
+ if (reasoningReplayOpaqueBlobRejectionMemoized(parsed._reasoningReplayScope)) {
142
+ parsed._stripReasoningEncryptedContent = true;
143
+ }
144
+ bindProviderContinuationForRoute(parsed, continuationOwner);
145
+ }
146
+
147
+
148
+ function routeReasoningReplayIdentity(args: {
149
+ providerName: string;
150
+ provider: OcxProviderConfig;
151
+ adapterName: string;
152
+ modelId: string;
153
+ oauthCredentialSnapshot?: Pick<OAuthAccessSnapshot, "accountId" | "generation">;
154
+ codexAuthContext?: CodexAuthContext;
155
+ forwardHeaders?: Headers;
156
+ }): OcxReasoningReplayIdentity | undefined {
157
+ const { providerName, provider, adapterName, modelId } = args;
158
+ let credentialIdentity: string | undefined;
159
+ let credentialDurableIdentity: string | undefined;
160
+ const durableSalt = thoughtSignatureReplaySalt();
161
+ if (provider.authMode === "oauth") {
162
+ credentialIdentity = reasoningReplayOAuthCredentialIdentity(
163
+ args.oauthCredentialSnapshot,
164
+ provider.headers,
165
+ );
166
+ // The persisted account-slot id survives token refresh and restarts; the rotating
167
+ // generation deliberately does NOT participate (#1926 design: rotation-safe).
168
+ credentialDurableIdentity = durableReplayCredentialIdentity(
169
+ "oauth",
170
+ args.oauthCredentialSnapshot?.accountId,
171
+ provider.headers,
172
+ durableSalt,
173
+ );
174
+ } else if (provider.authMode === "forward") {
175
+ const poolContext = args.codexAuthContext?.kind === "pool"
176
+ || args.codexAuthContext?.kind === "main-pool"
177
+ ? args.codexAuthContext
178
+ : undefined;
179
+ credentialIdentity = reasoningReplayCodexCredentialIdentity({
180
+ authorization: poolContext
181
+ ? `Bearer ${poolContext.accessToken}`
182
+ : args.forwardHeaders?.get("authorization"),
183
+ chatgptAccountId: poolContext?.chatgptAccountId
184
+ ?? args.forwardHeaders?.get("chatgpt-account-id"),
185
+ accountId: poolContext?.accountId,
186
+ credentialGeneration: poolContext?.kind === "pool"
187
+ ? poolContext.generation
188
+ : undefined,
189
+ writerGeneration: poolContext?.writerGeneration,
190
+ headers: provider.headers,
191
+ });
192
+ // Durable identity requires a STABLE, TRUSTED account handle. Pool context comes from
193
+ // our own account store; a client-supplied chatgpt-account-id header is attacker
194
+ // -influenceable bucket selection and a bearer alone is rotating material — both are
195
+ // refused, so direct-forward turns get no durable scope (fail closed; the in-process
196
+ // cache still covers same-process replay).
197
+ const codexDurableHandle = poolContext?.accountId
198
+ ?? poolContext?.chatgptAccountId
199
+ ?? undefined;
200
+ credentialDurableIdentity = durableReplayCredentialIdentity(
201
+ "codex",
202
+ codexDurableHandle ?? undefined,
203
+ provider.headers,
204
+ durableSalt,
205
+ );
206
+ } else if (provider.authMode !== "local") {
207
+ credentialIdentity = reasoningReplayKeyCredentialIdentity(provider);
208
+ credentialDurableIdentity = durableReplayCredentialIdentity(
209
+ "key",
210
+ nonEmptyProviderApiKey(provider),
211
+ provider.headers,
212
+ durableSalt,
213
+ );
214
+ }
215
+ const providerDestinationIdentity = reasoningReplayDestinationIdentity(provider.baseUrl);
216
+ return credentialIdentity && providerDestinationIdentity
217
+ ? {
218
+ providerName,
219
+ providerDestinationIdentity,
220
+ providerDestinationDurableIdentity: durableReplayDestinationIdentity(provider.baseUrl),
221
+ adapterName,
222
+ modelId,
223
+ credentialIdentity,
224
+ ...(credentialDurableIdentity ? { credentialDurableIdentity } : {}),
225
+ }
226
+ : undefined;
227
+ }
228
+
229
+
230
+ /**
231
+ * Whether this exact route cannot satisfy its documented plaintext reasoning replay contract.
232
+ *
233
+ * A generic plaintext-preserving gateway is not made ineligible. Unknown serving provenance also
234
+ * stays eligible; only a proven route mismatch may suppress a combo candidate.
235
+ */
236
+ export function mandatoryResponsesReasoningReplayUnavailable(args: {
237
+ body: unknown;
238
+ clientThreadId: string | undefined;
239
+ providerName: string;
240
+ provider: OcxProviderConfig;
241
+ adapterName: string;
242
+ modelId: string;
243
+ }): boolean {
244
+ const { body, clientThreadId, provider, adapterName } = args;
245
+ if (
246
+ adapterName !== "openai-responses"
247
+ || !requiresPlaintextReasoningReplay(provider)
248
+ || !clientThreadId
249
+ || !requestCarriesTools(body)
250
+ || !hasOpaqueOnlyReasoningItem(body)
251
+ ) return false;
252
+
253
+ const current = routeReasoningReplayIdentity(args);
254
+ return reasoningReplayServingIdentityChanged({ clientThreadId, current });
255
+ }
256
+
257
+
258
+ function requestCarriesTools(body: unknown): boolean {
259
+ if (!body || typeof body !== "object" || Array.isArray(body)) return false;
260
+ return Array.isArray((body as { tools?: unknown }).tools)
261
+ && (body as { tools: unknown[] }).tools.length > 0;
262
+ }
263
+
264
+
265
+ function hasOpaqueOnlyReasoningItem(body: unknown): boolean {
266
+ if (!body || typeof body !== "object" || Array.isArray(body)) return false;
267
+ const input = (body as { input?: unknown }).input;
268
+ if (!Array.isArray(input)) return false;
269
+ return input.some(item => {
270
+ if (!item || typeof item !== "object" || Array.isArray(item)) return false;
271
+ const reasoning = item as Record<string, unknown>;
272
+ if (reasoning.type !== "reasoning" || typeof reasoning.encrypted_content !== "string") return false;
273
+ const content = reasoning.content;
274
+ const hasPlaintext = Array.isArray(content) && content.some(part =>
275
+ !!part && typeof part === "object" && !Array.isArray(part)
276
+ && (part as Record<string, unknown>).type === "reasoning_text"
277
+ && typeof (part as Record<string, unknown>).text === "string"
278
+ && ((part as Record<string, unknown>).text as string).length > 0
279
+ );
280
+ return !hasPlaintext;
281
+ });
282
+ }
283
+
284
+
285
+ export function adapterResponseReachedServingTerminal(
286
+ events: readonly AdapterEvent[],
287
+ response: Readonly<Record<string, unknown>>,
288
+ ): boolean {
289
+ return (response.status === "completed" || response.status === "incomplete")
290
+ && events.some(event => event.type === "done" || event.type === "incomplete");
291
+ }
292
+
293
+
294
+ export function nonEmptyProviderApiKey(provider: OcxProviderConfig): string | undefined {
295
+ return typeof provider.apiKey === "string" && provider.apiKey.trim().length > 0
296
+ ? provider.apiKey
297
+ : undefined;
298
+ }