@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.
- package/README.md +3 -3
- package/bin/ocx.mjs +99 -70
- package/gui/dist/assets/index-Be1sDpWw.css +1 -0
- package/gui/dist/assets/index-D4OKgUV4.js +114 -0
- package/gui/dist/index.html +2 -2
- package/package.json +3 -1
- package/src/adapters/anthropic.ts +104 -15
- package/src/adapters/base.ts +29 -1
- package/src/adapters/command-code.ts +42 -6
- package/src/adapters/cursor/call-id.ts +44 -0
- package/src/adapters/cursor/catalog.ts +541 -0
- package/src/adapters/cursor/checkpoint-store.ts +15 -10
- package/src/adapters/cursor/cursor-errors.ts +15 -0
- package/src/adapters/cursor/discovery.ts +69 -18
- package/src/adapters/cursor/effort-map.ts +79 -1
- package/src/adapters/cursor/envelope-echo.ts +290 -0
- package/src/adapters/cursor/live-models.ts +7 -2
- package/src/adapters/cursor/live-transport.ts +14 -0
- package/src/adapters/cursor/message-mapper.ts +4 -1
- package/src/adapters/cursor/native-exec-desktop.ts +23 -0
- package/src/adapters/cursor/native-exec-fs.ts +2 -2
- package/src/adapters/cursor/native-exec-network.ts +1 -1
- package/src/adapters/cursor/native-exec-shell.ts +5 -2
- package/src/adapters/cursor/native-exec.ts +20 -0
- package/src/adapters/cursor/protobuf-request.ts +68 -14
- package/src/adapters/cursor/request-builder.ts +40 -10
- package/src/adapters/cursor/tool-definitions.ts +14 -4
- package/src/adapters/cursor/tool-result-normalize.ts +28 -5
- package/src/adapters/cursor/types.ts +20 -0
- package/src/adapters/cursor.ts +209 -38
- package/src/adapters/exec-tool-result-normalize.ts +99 -0
- package/src/adapters/google-antigravity-replay.ts +173 -25
- package/src/adapters/google-antigravity-wire.ts +5 -0
- package/src/adapters/google-errors.ts +44 -12
- package/src/adapters/google.ts +136 -24
- package/src/adapters/image.ts +1 -1
- package/src/adapters/kiro-constants.ts +27 -0
- package/src/adapters/kiro-tools.ts +55 -16
- package/src/adapters/kiro.ts +182 -23
- package/src/adapters/openai-chat.ts +300 -242
- package/src/adapters/openai-responses.ts +353 -27
- package/src/adapters/run-turn-queue.ts +36 -1
- package/src/adapters/tool-catalog-nudge.ts +4 -3
- package/src/adapters/xai-tool-schema.ts +436 -0
- package/src/adapters/xai-web-search.ts +17 -16
- package/src/bridge.ts +90 -37
- package/src/chat/inbound.ts +14 -1
- package/src/chat/outbound.ts +15 -7
- package/src/claude/agents-inject.ts +8 -1
- package/src/claude/context-windows.ts +16 -9
- package/src/claude/outbound.ts +24 -11
- package/src/cli/access.ts +46 -3
- package/src/cli/account-api.ts +116 -19
- package/src/cli/account-extended.ts +262 -29
- package/src/cli/account-main.ts +12 -12
- package/src/cli/account.ts +69 -15
- package/src/cli/agent.ts +8 -1
- package/src/cli/alias.ts +66 -0
- package/src/cli/capabilities-command.ts +94 -0
- package/src/cli/capabilities.ts +496 -0
- package/src/cli/claude-desktop.ts +31 -11
- package/src/cli/claude.ts +26 -1
- package/src/cli/dispatch.ts +256 -28
- package/src/cli/doctor.ts +126 -14
- package/src/cli/help.ts +17 -6
- package/src/cli/index.ts +39 -9
- package/src/cli/init.ts +1 -0
- package/src/cli/inspect.ts +230 -0
- package/src/cli/models-runtime.ts +95 -0
- package/src/cli/models.ts +26 -10
- package/src/cli/observe.ts +31 -8
- package/src/cli/provider-runtime.ts +16 -2
- package/src/cli/provider.ts +2 -1
- package/src/cli/registry.ts +49 -10
- package/src/cli/runtime-api.ts +51 -7
- package/src/cli/status.ts +41 -1
- package/src/cli/storage.ts +234 -0
- package/src/cli/system-command.ts +16 -0
- package/src/cli/system-restart-client.ts +1 -1
- package/src/cli/usage-report.ts +184 -0
- package/src/cli/v2.ts +34 -10
- package/src/cli/version-skew.ts +46 -0
- package/src/clients/config-export.ts +85 -3
- package/src/clients/effective-status.ts +30 -2
- package/src/clients/probes/cline.ts +81 -0
- package/src/codex/account-label.ts +21 -0
- package/src/codex/account-pause.ts +2 -1
- package/src/codex/account-priority.ts +3 -2
- package/src/codex/app-server-processes.ts +83 -7
- package/src/codex/auth-api.ts +50 -10
- package/src/codex/auth-context.ts +21 -18
- package/src/codex/autostart-health.ts +16 -0
- package/src/codex/catalog/aggregation.ts +18 -0
- package/src/codex/catalog/effort.ts +18 -3
- package/src/codex/catalog/metadata.ts +27 -1
- package/src/codex/catalog/native-models.ts +5 -2
- package/src/codex/catalog/parsing.ts +72 -27
- package/src/codex/catalog/provider-fetch.ts +187 -31
- package/src/codex/catalog/sync.ts +131 -5
- package/src/codex/catalog.ts +1 -1
- package/src/codex/codex-write-lock.ts +3 -1
- package/src/codex/convergence-types.ts +1 -1
- package/src/codex/convergence.ts +27 -2
- package/src/codex/desired-state.ts +2 -2
- package/src/codex/desktop-app-restart.ts +355 -0
- package/src/codex/history-job.ts +32 -3
- package/src/codex/history-manifest.ts +112 -0
- package/src/codex/history-migration-guardian.ts +5 -5
- package/src/codex/history-provider.ts +825 -247
- package/src/codex/history-worker.ts +8 -5
- package/src/codex/inject-coordination.ts +83 -0
- package/src/codex/inject.ts +63 -22
- package/src/codex/injected-marker.ts +1 -1
- package/src/codex/internal/history-writer.ts +4 -3
- package/src/codex/log-guard/inspect.ts +22 -4
- package/src/codex/model-entitlements.ts +9 -2
- package/src/codex/native-profile-startup.ts +157 -27
- package/src/codex/native-residue.ts +26 -33
- package/src/codex/prompt-layers.ts +371 -25
- package/src/codex/prompt-text-probe.ts +238 -0
- package/src/codex/quota.ts +91 -10
- package/src/codex/routing.ts +9 -0
- package/src/codex/shim.ts +56 -3
- package/src/codex/subagent-model-fallback.ts +104 -25
- package/src/codex/transition-state.ts +116 -8
- package/src/combos/failover.ts +27 -0
- package/src/combos/types.ts +10 -0
- package/src/compatibility/index.ts +26 -0
- package/src/compatibility/manifest.ts +253 -0
- package/src/compatibility/openai-responses.ts +121 -0
- package/src/config/atomic-write.ts +219 -0
- package/src/config/paths.ts +40 -0
- package/src/config/process-state.ts +309 -0
- package/src/config/provider-validation.ts +177 -0
- package/src/config/rebase-provenance.ts +68 -0
- package/src/config.ts +206 -824
- package/src/generated/compatibility-version.json +373 -213
- package/src/images/loop.ts +5 -4
- package/src/images/plan.ts +5 -4
- package/src/integrations/ownership-policy.ts +160 -0
- package/src/integrations/ownership.ts +46 -2
- package/src/integrations/registry.ts +7 -0
- package/src/integrations/state.ts +79 -7
- package/src/integrations/writer.ts +26 -2
- package/src/lab/conformance/fixtures/protocol-v1-cases.json +1 -1
- package/src/lab/fabric/producer-child.ts +1 -1
- package/src/lib/admin-secrets.ts +24 -0
- package/src/lib/bounded-body.ts +14 -2
- package/src/lib/config-ownership.ts +20 -0
- package/src/lib/errors.ts +36 -3
- package/src/lib/package-tree-integrity.ts +101 -0
- package/src/lib/process-control.ts +2 -1
- package/src/lib/service-secrets.ts +15 -0
- package/src/lib/tool-argument-integers.ts +56 -5
- package/src/oauth/callback-server.ts +22 -2
- package/src/oauth/cline.ts +203 -0
- package/src/oauth/generic-account-failover.ts +231 -0
- package/src/oauth/health.ts +1 -1
- package/src/oauth/index.ts +65 -6
- package/src/oauth/key-providers.ts +10 -1
- package/src/oauth/kimi.ts +9 -1
- package/src/oauth/kiro.ts +45 -0
- package/src/oauth/local-token-detect.ts +2 -0
- package/src/oauth/open-browser-choice.ts +26 -0
- package/src/oauth/store.ts +14 -5
- package/src/oauth/types.ts +15 -0
- package/src/providers/auto-compact-budget.ts +65 -0
- package/src/providers/command-code-efforts.ts +38 -6
- package/src/providers/context-cap.ts +4 -3
- package/src/providers/default-aliases.ts +65 -0
- package/src/providers/derive.ts +28 -0
- package/src/providers/fastwire.ts +7 -1
- package/src/providers/label.ts +34 -1
- package/src/providers/model-presets.ts +119 -0
- package/src/providers/new-model-policy.ts +146 -0
- package/src/providers/provider-id-rewrite.ts +2 -1
- package/src/providers/quota.ts +65 -7
- package/src/providers/registry.ts +184 -69
- package/src/providers/slug-codec.ts +52 -0
- package/src/providers/xai-transport.ts +21 -0
- package/src/reasoning-effort.ts +19 -2
- package/src/responses/apply-patch-envelope.ts +63 -0
- package/src/responses/code-mode-helper-compat.ts +50 -0
- package/src/responses/custom-tool-compat.ts +157 -39
- package/src/responses/namespace-tool-compat.ts +84 -4
- package/src/responses/parser.ts +7 -2
- package/src/responses/reasoning-replay-cache.ts +81 -3
- package/src/responses/schema.ts +5 -1
- package/src/responses/state.ts +94 -4
- package/src/responses/thought-signature-replay.ts +17 -0
- package/src/responses/turn-termination.ts +107 -0
- package/src/router.ts +40 -2
- package/src/routing/analytics.ts +1 -0
- package/src/routing/quota.ts +10 -0
- package/src/server/auth-cors.ts +27 -7
- package/src/server/chat-completions.ts +24 -16
- package/src/server/chat-native-sse.ts +3 -3
- package/src/server/chat-native.ts +30 -11
- package/src/server/claude-messages.ts +1 -1
- package/src/server/index.ts +179 -32
- package/src/server/lifecycle.ts +52 -1
- package/src/server/local-management-read-client.ts +1 -1
- package/src/server/local-provider-reload-client.ts +1 -1
- package/src/server/management/agent-settings-routes.ts +45 -23
- package/src/server/management/codex-prompt-routes.ts +570 -0
- package/src/server/management/combo-routes.ts +2 -1
- package/src/server/management/config-routes.ts +59 -12
- package/src/server/management/context.ts +10 -1
- package/src/server/management/logs-usage-routes.ts +30 -24
- package/src/server/management/model-routes.ts +274 -4
- package/src/server/management/model-rows.ts +4 -0
- package/src/server/management/native-integration-routes.ts +2 -1
- package/src/server/management/oauth-account-routes.ts +10 -4
- package/src/server/management/provider-capability-config.ts +1 -1
- package/src/server/management/provider-routes.ts +81 -11
- package/src/server/management/route-registry.ts +332 -0
- package/src/server/management/routing-profile-routes.ts +2 -2
- package/src/server/management/system-restart.ts +1 -1
- package/src/server/management-api.ts +18 -16
- package/src/server/port-reclaim.ts +1 -1
- package/src/server/proxy-liveness.ts +29 -5
- package/src/server/relay-eager.ts +147 -20
- package/src/server/relay.ts +251 -19
- package/src/server/request-log-conversation.ts +51 -0
- package/src/server/request-log.ts +70 -16
- package/src/server/responses/codex-auth-error.ts +55 -0
- package/src/server/responses/collaboration.ts +37 -3
- package/src/server/responses/combo-stream-preflight.ts +178 -0
- package/src/server/responses/compact.ts +6 -21
- package/src/server/responses/core.ts +810 -208
- package/src/server/responses/empty-completion-guard.ts +63 -6
- package/src/server/responses/fetch-helpers.ts +17 -102
- package/src/server/responses/input-admission.ts +3 -1
- package/src/server/responses/passthrough-error.ts +33 -9
- package/src/server/responses/responses-field-backfill.ts +105 -13
- package/src/server/responses/ws-upstream.ts +75 -2
- package/src/server/responses-custom-tool-repair.ts +87 -6
- package/src/server/responses-terminal-repair.ts +25 -4
- package/src/server/responses-undeclared-tool-guard.ts +241 -18
- package/src/server/sse-frame-buffer.ts +31 -4
- package/src/server/ws-bridge.ts +14 -2
- package/src/service.ts +42 -4
- package/src/storage/policy-job.ts +14 -4
- package/src/storage/policy.ts +88 -23
- package/src/types/config.ts +66 -0
- package/src/types/provider.ts +69 -0
- package/src/types/request.ts +17 -4
- package/src/types/tools.ts +116 -11
- package/src/types.ts +3 -1
- package/src/update/index.ts +5 -4
- package/src/update/job.ts +3 -1
- package/src/update/transactional-install.mjs +8 -1
- package/src/usage/expected-prices.ts +43 -24
- package/src/usage/log.ts +63 -12
- package/src/usage/summary.ts +211 -8
- package/src/vision/describe.ts +18 -13
- package/src/web-search/exa-executor.ts +40 -9
- package/src/web-search/executor.ts +10 -3
- package/src/web-search/loop.ts +5 -4
- package/gui/dist/assets/index-BF38heuV.js +0 -104
- 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
|
|
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
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
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
|
|
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
|
-
|
|
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 {
|
|
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 {
|
|
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 {
|
|
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 => {
|
|
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
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
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
|
-
|
|
232
|
-
|
|
233
|
-
|
|
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 (
|
|
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
|
-
|
|
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
|
-
|
|
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 =
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
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
|
}
|