@iislee/opencodex 2.31.0 → 2.35.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 (261) hide show
  1. package/README.md +3 -3
  2. package/bin/ocx.mjs +99 -70
  3. package/gui/dist/assets/index-Be1sDpWw.css +1 -0
  4. package/gui/dist/assets/index-D4OKgUV4.js +114 -0
  5. package/gui/dist/index.html +2 -2
  6. package/package.json +3 -1
  7. package/src/adapters/anthropic.ts +104 -15
  8. package/src/adapters/base.ts +29 -1
  9. package/src/adapters/command-code.ts +42 -6
  10. package/src/adapters/cursor/call-id.ts +44 -0
  11. package/src/adapters/cursor/catalog.ts +541 -0
  12. package/src/adapters/cursor/checkpoint-store.ts +15 -10
  13. package/src/adapters/cursor/cursor-errors.ts +15 -0
  14. package/src/adapters/cursor/discovery.ts +69 -18
  15. package/src/adapters/cursor/effort-map.ts +79 -1
  16. package/src/adapters/cursor/envelope-echo.ts +290 -0
  17. package/src/adapters/cursor/live-models.ts +7 -2
  18. package/src/adapters/cursor/live-transport.ts +14 -0
  19. package/src/adapters/cursor/message-mapper.ts +4 -1
  20. package/src/adapters/cursor/native-exec-desktop.ts +23 -0
  21. package/src/adapters/cursor/native-exec-fs.ts +2 -2
  22. package/src/adapters/cursor/native-exec-network.ts +1 -1
  23. package/src/adapters/cursor/native-exec-shell.ts +5 -2
  24. package/src/adapters/cursor/native-exec.ts +20 -0
  25. package/src/adapters/cursor/protobuf-request.ts +68 -14
  26. package/src/adapters/cursor/request-builder.ts +40 -10
  27. package/src/adapters/cursor/tool-definitions.ts +14 -4
  28. package/src/adapters/cursor/tool-result-normalize.ts +28 -5
  29. package/src/adapters/cursor/types.ts +20 -0
  30. package/src/adapters/cursor.ts +209 -38
  31. package/src/adapters/exec-tool-result-normalize.ts +99 -0
  32. package/src/adapters/google-antigravity-replay.ts +173 -25
  33. package/src/adapters/google-antigravity-wire.ts +5 -0
  34. package/src/adapters/google-errors.ts +44 -12
  35. package/src/adapters/google.ts +136 -24
  36. package/src/adapters/image.ts +1 -1
  37. package/src/adapters/kiro-constants.ts +27 -0
  38. package/src/adapters/kiro-tools.ts +55 -16
  39. package/src/adapters/kiro.ts +182 -23
  40. package/src/adapters/openai-chat.ts +300 -242
  41. package/src/adapters/openai-responses.ts +353 -27
  42. package/src/adapters/run-turn-queue.ts +36 -1
  43. package/src/adapters/tool-catalog-nudge.ts +4 -3
  44. package/src/adapters/xai-tool-schema.ts +436 -0
  45. package/src/adapters/xai-web-search.ts +17 -16
  46. package/src/bridge.ts +90 -37
  47. package/src/chat/inbound.ts +14 -1
  48. package/src/chat/outbound.ts +15 -7
  49. package/src/claude/agents-inject.ts +8 -1
  50. package/src/claude/context-windows.ts +16 -9
  51. package/src/claude/outbound.ts +24 -11
  52. package/src/cli/access.ts +46 -3
  53. package/src/cli/account-api.ts +116 -19
  54. package/src/cli/account-extended.ts +262 -29
  55. package/src/cli/account-main.ts +12 -12
  56. package/src/cli/account.ts +69 -15
  57. package/src/cli/agent.ts +8 -1
  58. package/src/cli/alias.ts +66 -0
  59. package/src/cli/capabilities-command.ts +94 -0
  60. package/src/cli/capabilities.ts +496 -0
  61. package/src/cli/claude-desktop.ts +31 -11
  62. package/src/cli/claude.ts +26 -1
  63. package/src/cli/dispatch.ts +256 -28
  64. package/src/cli/doctor.ts +126 -14
  65. package/src/cli/help.ts +17 -6
  66. package/src/cli/index.ts +39 -9
  67. package/src/cli/init.ts +1 -0
  68. package/src/cli/inspect.ts +230 -0
  69. package/src/cli/models-runtime.ts +95 -0
  70. package/src/cli/models.ts +26 -10
  71. package/src/cli/observe.ts +31 -8
  72. package/src/cli/provider-runtime.ts +16 -2
  73. package/src/cli/provider.ts +2 -1
  74. package/src/cli/registry.ts +49 -10
  75. package/src/cli/runtime-api.ts +51 -7
  76. package/src/cli/status.ts +41 -1
  77. package/src/cli/storage.ts +234 -0
  78. package/src/cli/system-command.ts +16 -0
  79. package/src/cli/system-restart-client.ts +1 -1
  80. package/src/cli/usage-report.ts +184 -0
  81. package/src/cli/v2.ts +34 -10
  82. package/src/cli/version-skew.ts +46 -0
  83. package/src/clients/config-export.ts +85 -3
  84. package/src/clients/effective-status.ts +30 -2
  85. package/src/clients/probes/cline.ts +81 -0
  86. package/src/codex/account-label.ts +21 -0
  87. package/src/codex/account-pause.ts +2 -1
  88. package/src/codex/account-priority.ts +3 -2
  89. package/src/codex/app-server-processes.ts +83 -7
  90. package/src/codex/auth-api.ts +50 -10
  91. package/src/codex/auth-context.ts +21 -18
  92. package/src/codex/autostart-health.ts +16 -0
  93. package/src/codex/catalog/aggregation.ts +18 -0
  94. package/src/codex/catalog/effort.ts +18 -3
  95. package/src/codex/catalog/metadata.ts +27 -1
  96. package/src/codex/catalog/native-models.ts +5 -2
  97. package/src/codex/catalog/parsing.ts +72 -27
  98. package/src/codex/catalog/provider-fetch.ts +187 -31
  99. package/src/codex/catalog/sync.ts +131 -5
  100. package/src/codex/catalog.ts +1 -1
  101. package/src/codex/codex-write-lock.ts +3 -1
  102. package/src/codex/convergence-types.ts +1 -1
  103. package/src/codex/convergence.ts +27 -2
  104. package/src/codex/desired-state.ts +2 -2
  105. package/src/codex/desktop-app-restart.ts +355 -0
  106. package/src/codex/history-job.ts +32 -3
  107. package/src/codex/history-manifest.ts +112 -0
  108. package/src/codex/history-migration-guardian.ts +5 -5
  109. package/src/codex/history-provider.ts +825 -247
  110. package/src/codex/history-worker.ts +8 -5
  111. package/src/codex/inject-coordination.ts +83 -0
  112. package/src/codex/inject.ts +63 -22
  113. package/src/codex/injected-marker.ts +1 -1
  114. package/src/codex/internal/history-writer.ts +4 -3
  115. package/src/codex/log-guard/inspect.ts +22 -4
  116. package/src/codex/model-entitlements.ts +9 -2
  117. package/src/codex/native-profile-startup.ts +157 -27
  118. package/src/codex/native-residue.ts +26 -33
  119. package/src/codex/prompt-layers.ts +371 -25
  120. package/src/codex/prompt-text-probe.ts +238 -0
  121. package/src/codex/quota.ts +91 -10
  122. package/src/codex/routing.ts +9 -0
  123. package/src/codex/shim.ts +56 -3
  124. package/src/codex/subagent-model-fallback.ts +104 -25
  125. package/src/codex/transition-state.ts +116 -8
  126. package/src/combos/failover.ts +27 -0
  127. package/src/combos/types.ts +10 -0
  128. package/src/compatibility/index.ts +26 -0
  129. package/src/compatibility/manifest.ts +253 -0
  130. package/src/compatibility/openai-responses.ts +121 -0
  131. package/src/config/atomic-write.ts +219 -0
  132. package/src/config/paths.ts +40 -0
  133. package/src/config/process-state.ts +309 -0
  134. package/src/config/provider-validation.ts +177 -0
  135. package/src/config/rebase-provenance.ts +68 -0
  136. package/src/config.ts +206 -824
  137. package/src/generated/compatibility-version.json +373 -213
  138. package/src/images/loop.ts +5 -4
  139. package/src/images/plan.ts +5 -4
  140. package/src/integrations/ownership-policy.ts +160 -0
  141. package/src/integrations/ownership.ts +46 -2
  142. package/src/integrations/registry.ts +7 -0
  143. package/src/integrations/state.ts +79 -7
  144. package/src/integrations/writer.ts +26 -2
  145. package/src/lab/conformance/fixtures/protocol-v1-cases.json +1 -1
  146. package/src/lab/fabric/producer-child.ts +1 -1
  147. package/src/lib/admin-secrets.ts +24 -0
  148. package/src/lib/bounded-body.ts +14 -2
  149. package/src/lib/config-ownership.ts +20 -0
  150. package/src/lib/errors.ts +36 -3
  151. package/src/lib/package-tree-integrity.ts +101 -0
  152. package/src/lib/process-control.ts +2 -1
  153. package/src/lib/service-secrets.ts +15 -0
  154. package/src/lib/tool-argument-integers.ts +56 -5
  155. package/src/oauth/callback-server.ts +22 -2
  156. package/src/oauth/cline.ts +203 -0
  157. package/src/oauth/generic-account-failover.ts +231 -0
  158. package/src/oauth/health.ts +1 -1
  159. package/src/oauth/index.ts +65 -6
  160. package/src/oauth/key-providers.ts +10 -1
  161. package/src/oauth/kimi.ts +9 -1
  162. package/src/oauth/kiro.ts +45 -0
  163. package/src/oauth/local-token-detect.ts +2 -0
  164. package/src/oauth/open-browser-choice.ts +26 -0
  165. package/src/oauth/store.ts +14 -5
  166. package/src/oauth/types.ts +15 -0
  167. package/src/providers/auto-compact-budget.ts +65 -0
  168. package/src/providers/command-code-efforts.ts +38 -6
  169. package/src/providers/context-cap.ts +4 -3
  170. package/src/providers/default-aliases.ts +65 -0
  171. package/src/providers/derive.ts +28 -0
  172. package/src/providers/fastwire.ts +7 -1
  173. package/src/providers/label.ts +34 -1
  174. package/src/providers/model-presets.ts +119 -0
  175. package/src/providers/new-model-policy.ts +146 -0
  176. package/src/providers/provider-id-rewrite.ts +2 -1
  177. package/src/providers/quota.ts +65 -7
  178. package/src/providers/registry.ts +184 -69
  179. package/src/providers/slug-codec.ts +52 -0
  180. package/src/providers/xai-transport.ts +21 -0
  181. package/src/reasoning-effort.ts +19 -2
  182. package/src/responses/apply-patch-envelope.ts +63 -0
  183. package/src/responses/code-mode-helper-compat.ts +50 -0
  184. package/src/responses/custom-tool-compat.ts +157 -39
  185. package/src/responses/namespace-tool-compat.ts +84 -4
  186. package/src/responses/parser.ts +7 -2
  187. package/src/responses/reasoning-replay-cache.ts +81 -3
  188. package/src/responses/schema.ts +5 -1
  189. package/src/responses/state.ts +94 -4
  190. package/src/responses/thought-signature-replay.ts +17 -0
  191. package/src/responses/turn-termination.ts +107 -0
  192. package/src/router.ts +40 -2
  193. package/src/routing/analytics.ts +1 -0
  194. package/src/routing/quota.ts +10 -0
  195. package/src/server/auth-cors.ts +27 -7
  196. package/src/server/chat-completions.ts +24 -16
  197. package/src/server/chat-native-sse.ts +3 -3
  198. package/src/server/chat-native.ts +30 -11
  199. package/src/server/claude-messages.ts +1 -1
  200. package/src/server/index.ts +179 -32
  201. package/src/server/lifecycle.ts +52 -1
  202. package/src/server/local-management-read-client.ts +1 -1
  203. package/src/server/local-provider-reload-client.ts +1 -1
  204. package/src/server/management/agent-settings-routes.ts +45 -23
  205. package/src/server/management/codex-prompt-routes.ts +570 -0
  206. package/src/server/management/combo-routes.ts +2 -1
  207. package/src/server/management/config-routes.ts +59 -12
  208. package/src/server/management/context.ts +10 -1
  209. package/src/server/management/logs-usage-routes.ts +30 -24
  210. package/src/server/management/model-routes.ts +274 -4
  211. package/src/server/management/model-rows.ts +4 -0
  212. package/src/server/management/native-integration-routes.ts +2 -1
  213. package/src/server/management/oauth-account-routes.ts +10 -4
  214. package/src/server/management/provider-capability-config.ts +1 -1
  215. package/src/server/management/provider-routes.ts +81 -11
  216. package/src/server/management/route-registry.ts +332 -0
  217. package/src/server/management/routing-profile-routes.ts +2 -2
  218. package/src/server/management/system-restart.ts +1 -1
  219. package/src/server/management-api.ts +18 -16
  220. package/src/server/port-reclaim.ts +1 -1
  221. package/src/server/proxy-liveness.ts +29 -5
  222. package/src/server/relay-eager.ts +147 -20
  223. package/src/server/relay.ts +251 -19
  224. package/src/server/request-log-conversation.ts +51 -0
  225. package/src/server/request-log.ts +70 -16
  226. package/src/server/responses/codex-auth-error.ts +55 -0
  227. package/src/server/responses/collaboration.ts +37 -3
  228. package/src/server/responses/combo-stream-preflight.ts +178 -0
  229. package/src/server/responses/compact.ts +6 -21
  230. package/src/server/responses/core.ts +810 -208
  231. package/src/server/responses/empty-completion-guard.ts +63 -6
  232. package/src/server/responses/fetch-helpers.ts +17 -102
  233. package/src/server/responses/input-admission.ts +3 -1
  234. package/src/server/responses/passthrough-error.ts +33 -9
  235. package/src/server/responses/responses-field-backfill.ts +105 -13
  236. package/src/server/responses/ws-upstream.ts +75 -2
  237. package/src/server/responses-custom-tool-repair.ts +87 -6
  238. package/src/server/responses-terminal-repair.ts +25 -4
  239. package/src/server/responses-undeclared-tool-guard.ts +241 -18
  240. package/src/server/sse-frame-buffer.ts +31 -4
  241. package/src/server/ws-bridge.ts +14 -2
  242. package/src/service.ts +42 -4
  243. package/src/storage/policy-job.ts +14 -4
  244. package/src/storage/policy.ts +88 -23
  245. package/src/types/config.ts +66 -0
  246. package/src/types/provider.ts +69 -0
  247. package/src/types/request.ts +17 -4
  248. package/src/types/tools.ts +116 -11
  249. package/src/types.ts +3 -1
  250. package/src/update/index.ts +5 -4
  251. package/src/update/job.ts +3 -1
  252. package/src/update/transactional-install.mjs +8 -1
  253. package/src/usage/expected-prices.ts +43 -24
  254. package/src/usage/log.ts +63 -12
  255. package/src/usage/summary.ts +211 -8
  256. package/src/vision/describe.ts +18 -13
  257. package/src/web-search/exa-executor.ts +40 -9
  258. package/src/web-search/executor.ts +10 -3
  259. package/src/web-search/loop.ts +5 -4
  260. package/gui/dist/assets/index-BF38heuV.js +0 -104
  261. package/gui/dist/assets/index-DMiI18Kv.css +0 -1
@@ -20,7 +20,7 @@ import { assemblePolicyCandidateEvidence } from "../../routing/compatibility/ass
20
20
  import { activateLab, labActivationRequired } from "../../lib/lab-activation";
21
21
  import { quotaEvidenceForCandidate } from "../../routing/quota";
22
22
  import { routedProviderConfig } from "../../router";
23
- import { saveConfigPreservingClaudeCode, getConfigDir } from "../../config";
23
+ import { deleteConfigTopLevelKey, saveConfigPreservingClaudeCode, getConfigDir } from "../../config";
24
24
  import { reconcileLiveStateStores } from "../../lib/state-store-registrations";
25
25
  import { isPlainRecord } from "./shared";
26
26
  import { readManagementJsonBody, rethrowManagementBodyTooLarge } from "./body";
@@ -329,7 +329,7 @@ export async function handleRoutingProfileRoutes(ctx: ManagementContext): Promis
329
329
  const nextProfiles = { ...(config.routingProfiles ?? {}) };
330
330
  delete nextProfiles[id];
331
331
  if (Object.keys(nextProfiles).length > 0) config.routingProfiles = nextProfiles;
332
- else delete config.routingProfiles;
332
+ else deleteConfigTopLevelKey(config, "routingProfiles");
333
333
  const saveConfigPreservingClaudeCodeSafe = deps.saveConfigPreservingClaudeCode ?? saveConfigPreservingClaudeCode;
334
334
  saveConfigPreservingClaudeCodeSafe(config);
335
335
  reconcileLiveStateStores();
@@ -33,7 +33,7 @@ import {
33
33
  stopServerListener,
34
34
  } from "../lifecycle";
35
35
  import { isServiceViable } from "../../service";
36
- import { readRuntimePort } from "../../config";
36
+ import { readRuntimePort } from "../../config/process-state";
37
37
  import { withProcessRuntimeProvenance } from "../../lib/bun-runtime";
38
38
  import { selfLaunchArgv } from "../../lib/self-launch-argv";
39
39
  import {
@@ -71,6 +71,7 @@ import { handleComboRoutes } from "./management/combo-routes";
71
71
  import { handleSystemRoutes } from "./management/system-routes";
72
72
  import { handleCloudSyncRoutes } from "./management/cloud-sync-routes";
73
73
  import { handleSidebarRoutes } from "./management/sidebar-routes";
74
+ import { handleCodexPromptRoutes } from "./management/codex-prompt-routes";
74
75
  import { handleIntegrationRoutes } from "./management/integration-routes";
75
76
  import { handleNativeIntegrationRoutes } from "./management/native-integration-routes";
76
77
  import type { ManagementContext } from "./management/context";
@@ -219,22 +220,23 @@ export async function handleManagementAPI(
219
220
  let routed: Response | null;
220
221
  try {
221
222
  routed = (await handleConfigRoutes(ctx))
222
- ?? (await handleStorageLogGuardRoutes(ctx))
223
- ?? (await handleLogsUsageRoutes(ctx))
224
- ?? (await handleRequestHistoryRoutes(ctx))
225
- ?? (await handleRoutingAnalyticsRoutes(ctx))
226
- ?? (await handleRoutingProfileRoutesOnDemand(ctx))
227
- ?? (await handleProviderRoutes(ctx))
228
- ?? (await handleModelRoutes(ctx))
229
- ?? (await handleIntegrationRoutes(ctx))
230
- ?? (await handleNativeIntegrationRoutes(ctx))
231
- ?? (await handleAgentSettingsRoutes(ctx))
232
- ?? (await handleClientsRoutes(ctx))
233
- ?? (await handleOauthAccountRoutes(ctx))
234
- ?? (await handleComboRoutes(ctx))
235
- ?? (await handleSystemRoutes(ctx))
236
- ?? (await handleCloudSyncRoutes(ctx))
237
- ?? (await handleLabRoutesOnDemand(ctx))
223
+ ?? (await handleStorageLogGuardRoutes(ctx))
224
+ ?? (await handleLogsUsageRoutes(ctx))
225
+ ?? (await handleRequestHistoryRoutes(ctx))
226
+ ?? (await handleRoutingAnalyticsRoutes(ctx))
227
+ ?? (await handleRoutingProfileRoutesOnDemand(ctx))
228
+ ?? (await handleProviderRoutes(ctx))
229
+ ?? (await handleModelRoutes(ctx))
230
+ ?? (await handleIntegrationRoutes(ctx))
231
+ ?? (await handleNativeIntegrationRoutes(ctx))
232
+ ?? (await handleAgentSettingsRoutes(ctx))
233
+ ?? (await handleCodexPromptRoutes(ctx))
234
+ ?? (await handleClientsRoutes(ctx))
235
+ ?? (await handleOauthAccountRoutes(ctx))
236
+ ?? (await handleComboRoutes(ctx))
237
+ ?? (await handleSystemRoutes(ctx))
238
+ ?? (await handleCloudSyncRoutes(ctx))
239
+ ?? (await handleLabRoutesOnDemand(ctx))
238
240
  ?? (await handleSidebarRoutes(ctx));
239
241
  } catch (error) {
240
242
  const tooLarge = managementBodyTooLargeResponse(error, req, config);
@@ -9,7 +9,7 @@
9
9
  * (non-ocx, non-allowlisted) processes are never killed.
10
10
  */
11
11
  import { execFileSync } from "node:child_process";
12
- import { verifyPidIdentity } from "../config";
12
+ import { verifyPidIdentity } from "../config/process-state";
13
13
  import { isProcessAlive, killProxy } from "../lib/process-control";
14
14
  import { isPortAvailable, type WaitForPortOptions } from "./ports";
15
15
  import { dropWindowsTcpRowsForLocalPort } from "./windows-tcp-drop";
@@ -9,7 +9,8 @@
9
9
  *
10
10
  * Lives outside cli.ts (which dispatches argv at module top level) so tests can import it.
11
11
  */
12
- import { loadConfig, readAlivePid, readRuntimePort, verifyPidIdentity } from "../config";
12
+ import { loadConfig } from "../config";
13
+ import { readAlivePid, readRuntimePort, verifyPidIdentity } from "../config/process-state";
13
14
  import { directLocalHttpFetch } from "./direct-local-http";
14
15
 
15
16
  export interface HealthzIdentity {
@@ -67,6 +68,14 @@ export interface LiveProxy {
67
68
  hostname?: string;
68
69
  /** Whether the successful probe used runtime-port metadata or the configured listen port. */
69
70
  source: "runtime" | "config";
71
+ /**
72
+ * Version the live proxy reported on `/healthz`, when it reported one.
73
+ *
74
+ * Carried so a stale `ocx` on PATH can be detected without a second request: the
75
+ * identity probe already parsed and validated this body. Absent for a legacy proxy whose
76
+ * healthz body predates the field.
77
+ */
78
+ version?: string;
70
79
  }
71
80
 
72
81
  /**
@@ -98,7 +107,7 @@ export async function proxyIdentityAt(
98
107
  port: number,
99
108
  opts: { hostname?: string; expectedPid?: number } = {},
100
109
  io: LivenessIo = {},
101
- ): Promise<{ pid: number | null } | null> {
110
+ ): Promise<{ pid: number | null; version?: string } | null> {
102
111
  const fetchFn = io.fetchFn ?? directLocalHttpFetch;
103
112
  const sleepFn = io.sleepFn ?? ((ms: number) => new Promise<void>(r => setTimeout(r, ms)));
104
113
  const nowFn = io.nowFn ?? Date.now;
@@ -121,7 +130,9 @@ export async function proxyIdentityAt(
121
130
  if (!isOpencodexHealthz(body)) return null;
122
131
  const pid = typeof body?.pid === "number" ? body.pid : null;
123
132
  if (opts.expectedPid !== undefined && pid !== null && pid !== opts.expectedPid) return null;
124
- return { pid };
133
+ // Guarded the same way `pid` is: a non-string version is absent, not coerced.
134
+ const version = typeof body?.version === "string" ? body.version : undefined;
135
+ return version === undefined ? { pid } : { pid, version };
125
136
  } catch {
126
137
  // Transport failure (timeout / refused) — retry while budget remains; a proxy that
127
138
  // has only just begun listening can miss a single short probe (#764).
@@ -179,7 +190,13 @@ export async function findLiveProxy(io: LivenessIo = {}): Promise<LiveProxy | nu
179
190
  // healthz confirmed the pid itself → trusted; a pidless legacy body did not,
180
191
  // so the cheap pid must pass full identity verification before it is returned.
181
192
  const trusted = identity.pid === pid ? pid : killablePid(pid);
182
- return { pid: trusted, port: runtime.port, hostname: runtime.hostname, source: "runtime" };
193
+ return {
194
+ pid: trusted,
195
+ port: runtime.port,
196
+ hostname: runtime.hostname,
197
+ source: "runtime",
198
+ ...(identity.version === undefined ? {} : { version: identity.version }),
199
+ };
183
200
  }
184
201
  }
185
202
  }
@@ -196,7 +213,13 @@ export async function findLiveProxy(io: LivenessIo = {}): Promise<LiveProxy | nu
196
213
  // (its process dead, the port reused by a pidless legacy proxy) — synthesizing it
197
214
  // would hand destructive callers (stopProxy → kill fallback) a reusable pid.
198
215
  if (identity) {
199
- return { pid: verifiedReportedPid(identity.pid), port: record.port, hostname: record.hostname, source: "runtime" };
216
+ return {
217
+ pid: verifiedReportedPid(identity.pid),
218
+ port: record.port,
219
+ hostname: record.hostname,
220
+ source: "runtime",
221
+ ...(identity.version === undefined ? {} : { version: identity.version }),
222
+ };
200
223
  }
201
224
  }
202
225
 
@@ -210,6 +233,7 @@ export async function findLiveProxy(io: LivenessIo = {}): Promise<LiveProxy | nu
210
233
  port,
211
234
  hostname: config.hostname,
212
235
  source: "config",
236
+ ...(identity.version === undefined ? {} : { version: identity.version }),
213
237
  };
214
238
  }
215
239
  return null;
@@ -24,7 +24,12 @@
24
24
  * up to the drain window.
25
25
  */
26
26
 
27
- import { buildFailedTailPayload, createSseTerminalOutputBoundary } from "./relay";
27
+ import {
28
+ adapterEofIncompleteFrame,
29
+ createSseTerminalOutputBoundary,
30
+ doneFrame,
31
+ failedTailFrame,
32
+ } from "./relay";
28
33
  import {
29
34
  nextSseBlock,
30
35
  payloadRewriteAsBlockRewrite,
@@ -100,9 +105,19 @@ export function relaySseEagerBounded(
100
105
  const now = opts?.now ?? Date.now;
101
106
 
102
107
  const reader = body.getReader();
108
+ const terminalEncoder = new TextEncoder();
109
+ const adapterEofFrame = adapterEofIncompleteFrame(terminalEncoder);
110
+ const terminalSentinel = doneFrame(terminalEncoder);
103
111
  const terminalBoundary = createSseTerminalOutputBoundary();
104
112
  const activeRewrite: SseBlockRewrite | undefined = hooks.rewriteBlocks
105
113
  ?? (hooks.rewritePayload ? payloadRewriteAsBlockRewrite(hooks.rewritePayload) : undefined);
114
+ const encodeFailedTail = (error: unknown): Uint8Array | null => {
115
+ try {
116
+ return failedTailFrame(terminalEncoder, error);
117
+ } catch {
118
+ return null;
119
+ }
120
+ };
106
121
  const rewriteDecoder = activeRewrite ? new TextDecoder() : null;
107
122
  const rewriteEncoder = activeRewrite ? new TextEncoder() : null;
108
123
  const rewriteBudget = opts?.rewriteBudget;
@@ -164,13 +179,21 @@ export function relaySseEagerBounded(
164
179
  let queuedBytes = 0;
165
180
  let cancelled = false;
166
181
  let done = false;
167
- const terminalSentinel = new TextEncoder().encode("data: [DONE]\n\n");
168
182
  // Pause gate: resolved by client pull, client cancel, or upstream abort so a
169
183
  // paused producer ALWAYS resumes (audit blocker 2 — no deadlock; onDone and
170
184
  // turn unregistration stay reachable, drainAndShutdown never hangs).
171
185
  let wake: (() => void) | null = null;
172
186
  const wakeUp = () => { const w = wake; wake = null; w?.(); };
173
- const paused = () => new Promise<void>(resolve => { wake = resolve; });
187
+ const paused = () => new Promise<void>(resolve => {
188
+ wake = resolve;
189
+ // A pull, cancel, or abort can win the tiny window between the loop's
190
+ // predicate check and installing this resolver. Re-check every wake
191
+ // predicate after installation so that an already-fired wake cannot leave
192
+ // the producer parked forever.
193
+ if (queuedBytes <= maxQueueBytes || cancelled || upstream.signal.aborted) {
194
+ wakeUp();
195
+ }
196
+ });
174
197
  upstream.signal.addEventListener("abort", wakeUp, { once: true });
175
198
 
176
199
  let controllerRef: ReadableStreamDefaultController<Uint8Array> | null = null;
@@ -196,6 +219,9 @@ export function relaySseEagerBounded(
196
219
 
197
220
  const producer = async () => {
198
221
  let syntheticKind: "incomplete" | "failed" | null = null;
222
+ let deliveryFallbackSent = false;
223
+ let priorRewriteFailure = false;
224
+ let priorRewriteError: unknown;
199
225
  // reader.read() is not intrinsically tied to the upstream AbortController
200
226
  // (a fetch body usually rejects on abort, but that coupling is the fetch
201
227
  // implementation's, not the stream's), so abort must break a parked read on
@@ -221,18 +247,46 @@ export function relaySseEagerBounded(
221
247
  if (upstreamDone) {
222
248
  hooks.finishInspection();
223
249
  const boundedTail = terminalBoundary.finish();
250
+ let clientTail = boundedTail;
251
+ let rewriteFailed = false;
252
+ let rewriteError: unknown;
224
253
  if (activeRewrite) {
225
- const rewritten = rewriteOutbound(boundedTail);
226
- const tail = joinUint8Arrays(rewritten, flushRewriteTail());
227
- if (tail.byteLength > 0 && !cancelled) {
228
- queuedBytes += tail.byteLength;
229
- try { controllerRef?.enqueue(tail); } catch { /* client already gone */ }
254
+ try {
255
+ const rewritten = rewriteOutbound(boundedTail);
256
+ clientTail = joinUint8Arrays(rewritten, flushRewriteTail());
257
+ } catch (error) {
258
+ rewriteFailed = true;
259
+ rewriteError = error;
260
+ clientTail = new Uint8Array(0);
261
+ }
262
+ }
263
+ if (rewriteFailed) {
264
+ const safeTail = encodeFailedTail(rewriteError);
265
+ if (safeTail && !cancelled && !upstream.signal.aborted) {
266
+ if (!hooks.sawTerminal()) syntheticKind = "failed";
267
+ queuedBytes += safeTail.byteLength;
268
+ try { controllerRef?.enqueue(safeTail); } catch { /* client already torn down */ }
269
+ try { controllerRef?.close(); } catch { /* client already gone */ }
230
270
  }
231
- } else if (boundedTail.byteLength > 0 && !cancelled) {
232
- queuedBytes += boundedTail.byteLength;
233
- try { controllerRef?.enqueue(boundedTail); } catch { /* client already gone */ }
271
+ break;
272
+ }
273
+ if (clientTail.byteLength > 0 && !cancelled) {
274
+ queuedBytes += clientTail.byteLength;
275
+ try { controllerRef?.enqueue(clientTail); } catch { /* client already gone */ }
234
276
  }
235
- if (!hooks.sawTerminal() && !cancelled && !upstream.signal.aborted) {
277
+ if (terminalBoundary.terminalSeen()) {
278
+ if (!terminalBoundary.doneSeen() && !cancelled) {
279
+ queuedBytes += terminalSentinel.byteLength;
280
+ try { controllerRef?.enqueue(terminalSentinel); } catch { /* client already gone */ }
281
+ }
282
+ } else if (!hooks.sawTerminal() && !cancelled && !upstream.signal.aborted) {
283
+ // A clean 200 EOF without a Responses terminal must be visible to
284
+ // Codex as one incomplete turn, followed by the normal sentinel.
285
+ queuedBytes += adapterEofFrame.byteLength + terminalSentinel.byteLength;
286
+ try {
287
+ controllerRef?.enqueue(adapterEofFrame);
288
+ controllerRef?.enqueue(terminalSentinel);
289
+ } catch { /* client already gone */ }
236
290
  syntheticKind = "incomplete";
237
291
  }
238
292
  break;
@@ -247,7 +301,21 @@ export function relaySseEagerBounded(
247
301
  continue;
248
302
  }
249
303
  const terminalBounded = terminalBoundary.feed(value);
250
- const outbound = activeRewrite ? rewriteOutbound(terminalBounded) : terminalBounded;
304
+ let outbound: Uint8Array;
305
+ if (activeRewrite) {
306
+ try {
307
+ outbound = rewriteOutbound(terminalBounded);
308
+ } catch (error) {
309
+ // Preserve the first rewrite failure across the teardown flush. A
310
+ // second empty flush may succeed, but the terminal bytes still
311
+ // must not bypass the failed rewrite or become DONE-only output.
312
+ priorRewriteFailure = true;
313
+ priorRewriteError = error;
314
+ throw error;
315
+ }
316
+ } else {
317
+ outbound = terminalBounded;
318
+ }
251
319
  if (outbound.byteLength > 0) {
252
320
  queuedBytes += outbound.byteLength;
253
321
  try {
@@ -278,16 +346,75 @@ export function relaySseEagerBounded(
278
346
  } catch (err) {
279
347
  // Upstream read failure. Distinguish genuine mid-stream reset from
280
348
  // abort-driven teardown (shutdown/cancel-expiry) — audit M3.
281
- if (!hooks.sawTerminal() && !cancelled && !upstream.signal.aborted) {
349
+ // A read can fail after delivering an unterminated terminal block. Flush
350
+ // both observers before deciding whether this is a synthetic reset so
351
+ // eager mode matches the tee/pull boundary semantics at EOF.
352
+ let boundedTail: Uint8Array<ArrayBufferLike> = new Uint8Array(0);
353
+ let tailTerminal = false;
354
+ let tailDone = false;
355
+ try { hooks.finishInspection(); } catch { /* preserve the original read failure */ }
356
+ try {
357
+ boundedTail = terminalBoundary.finish();
358
+ tailTerminal = terminalBoundary.terminalSeen();
359
+ tailDone = terminalBoundary.doneSeen();
360
+ } catch {
361
+ // A near-cap ambiguous delimiter tail may itself overflow at EOF.
362
+ // Preserve the original read/framing failure and continue emitting
363
+ // the bounded failed tail instead of letting cleanup throw again.
364
+ }
365
+ let clientTail: Uint8Array<ArrayBufferLike> = boundedTail;
366
+ let rewriteFailed = false;
367
+ let rewriteError: unknown;
368
+ if (priorRewriteFailure) {
369
+ rewriteFailed = true;
370
+ rewriteError = priorRewriteError;
371
+ clientTail = new Uint8Array(0);
372
+ } else if (activeRewrite) {
373
+ try {
374
+ const rewritten = rewriteOutbound(boundedTail);
375
+ clientTail = joinUint8Arrays(rewritten, flushRewriteTail());
376
+ } catch (error) {
377
+ rewriteFailed = true;
378
+ rewriteError = error;
379
+ clientTail = new Uint8Array(0);
380
+ }
381
+ }
382
+ if (clientTail.byteLength > 0 && !cancelled && !upstream.signal.aborted) {
383
+ queuedBytes += clientTail.byteLength;
384
+ try { controllerRef?.enqueue(clientTail); } catch { /* client already torn down */ }
385
+ }
386
+ if (rewriteFailed && !cancelled && !upstream.signal.aborted) {
387
+ // Never bypass a client rewrite after it fails: boundedTail can contain
388
+ // provider metadata or content that the active rewrite was required to
389
+ // remove. Emit one safe failed envelope instead. When inspection has
390
+ // already reported the real upstream terminal, this is a delivery
391
+ // fallback only and must not create a second accounting outcome.
392
+ const safeTail = encodeFailedTail(rewriteError ?? err);
393
+ if (safeTail && !cancelled && !upstream.signal.aborted) {
394
+ if (!hooks.sawTerminal()) syntheticKind = "failed";
395
+ deliveryFallbackSent = true;
396
+ queuedBytes += safeTail.byteLength;
397
+ try { controllerRef?.enqueue(safeTail); } catch { /* client already torn down */ }
398
+ try { controllerRef?.close(); } catch { /* client already gone */ }
399
+ }
400
+ } else if (tailTerminal && !cancelled && !upstream.signal.aborted) {
401
+ if (!tailDone) {
402
+ queuedBytes += terminalSentinel.byteLength;
403
+ try { controllerRef?.enqueue(terminalSentinel); } catch { /* client already gone */ }
404
+ }
405
+ } else if (!tailTerminal && !cancelled && !upstream.signal.aborted) {
282
406
  // Serializing `err` can run user-defined accessors (Error.message
283
407
  // getters, toString) that re-entrantly cancel the client or abort the
284
408
  // upstream. Build the tail FIRST, then re-check eligibility before
285
409
  // committing to the synthetic terminal (adversarial review blocker).
286
- const tail = new TextEncoder().encode(
287
- `\n\nevent: response.failed\ndata: ${buildFailedTailPayload(err)}\n\ndata: [DONE]\n\n`,
288
- );
289
- if (!hooks.sawTerminal() && !cancelled && !upstream.signal.aborted) {
290
- syntheticKind = "failed";
410
+ const tail = encodeFailedTail(err);
411
+ if (tail && !cancelled && !upstream.signal.aborted) {
412
+ // Inspection and client framing have separate bounded parsers. If
413
+ // inspection resynchronized after an oversized frame and observed a
414
+ // later real terminal, it still must not suppress a terminal delivery
415
+ // to the client. Only accounting remains tied to the inspected result.
416
+ if (!hooks.sawTerminal()) syntheticKind = "failed";
417
+ deliveryFallbackSent = true;
291
418
  queuedBytes += tail.byteLength;
292
419
  try { controllerRef?.enqueue(tail); } catch { /* client already torn down */ }
293
420
  try { controllerRef?.close(); } catch { /* client already torn down */ }
@@ -306,7 +433,7 @@ export function relaySseEagerBounded(
306
433
  if (cancelled && !hooks.sawTerminal()) {
307
434
  hooks.onClientCancel();
308
435
  }
309
- if (cancelled || upstream.signal.aborted || syntheticKind === "failed") {
436
+ if (cancelled || upstream.signal.aborted || syntheticKind === "failed" || deliveryFallbackSent) {
310
437
  upstream.abort();
311
438
  reader.cancel().catch(() => {});
312
439
  }