@bitkyc08/opencodex 2.42.0 → 2.43.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/AGENTS_INSTALL.md +2 -2
- package/README.md +31 -0
- package/bin/ocx.mjs +10 -1
- package/gui/dist/assets/index-DS1NE4Jn.css +1 -0
- package/gui/dist/assets/index-Djowl68T.js +112 -0
- package/gui/dist/index.html +2 -2
- package/package.json +1 -1
- package/src/adapters/anthropic-image-codec.ts +304 -0
- package/src/adapters/anthropic-image-normalize.ts +8 -298
- package/src/adapters/anthropic.ts +30 -7
- package/src/adapters/command-code.ts +7 -5
- package/src/adapters/cursor/desktop-executor-contract.ts +15 -0
- package/src/adapters/cursor/images.ts +36 -6
- package/src/adapters/cursor/live-transport.ts +7 -2
- package/src/adapters/cursor/native-exec-desktop.ts +2 -15
- package/src/adapters/cursor/protobuf-request.ts +13 -3
- package/src/adapters/cursor/tool-definitions.ts +5 -670
- package/src/adapters/cursor/tool-guidance.ts +236 -0
- package/src/adapters/cursor/tool-naming.ts +252 -0
- package/src/adapters/cursor/tool-schemas.ts +195 -0
- package/src/adapters/cursor/types.ts +6 -3
- package/src/adapters/exec-tool-result-normalize.ts +1 -1
- package/src/adapters/google-errors.ts +9 -1
- package/src/adapters/google.ts +1 -0
- package/src/adapters/kiro-calibration.ts +181 -0
- package/src/adapters/kiro.ts +135 -3
- package/src/adapters/openai-responses.ts +216 -26
- package/src/adapters/responses-code-mode.ts +59 -0
- package/src/adapters/tool-catalog-nudge.ts +1 -1
- package/src/adapters/xai-schema-analysis.ts +86 -0
- package/src/adapters/xai-tool-schema.ts +2 -87
- package/src/adapters/xai-web-search.ts +1 -1
- package/src/bridge.ts +22 -10
- package/src/chat/inbound.ts +11 -3
- package/src/claude/inbound-content-options.ts +60 -0
- package/src/claude/inbound-model-options.ts +142 -0
- package/src/claude/inbound-records.ts +7 -0
- package/src/claude/inbound.ts +10 -202
- package/src/claude/model-info.ts +45 -0
- package/src/cli/account-auth.ts +21 -6
- package/src/cli/capabilities.ts +13 -2
- package/src/cli/claude.ts +232 -39
- package/src/cli/config-command.ts +9 -1
- package/src/cli/dispatch.ts +5 -1
- package/src/cli/doctor.ts +10 -0
- package/src/cli/effort.ts +372 -0
- package/src/cli/export-command.ts +3 -9
- package/src/cli/help.ts +1 -0
- package/src/cli/index.ts +13 -0
- package/src/cli/init.ts +4 -0
- package/src/cli/model-selection-guidance.ts +30 -0
- package/src/cli/models-runtime.ts +3 -2
- package/src/cli/models.ts +8 -3
- package/src/cli/opencode.ts +4 -1
- package/src/cli/provider-runtime.ts +65 -0
- package/src/cli/provider.ts +8 -0
- package/src/cli/registry.ts +16 -2
- package/src/cli/runtime-api.ts +3 -1
- package/src/cli/star-prompt.ts +22 -6
- package/src/cli/status-probes.ts +168 -0
- package/src/cli/status.ts +5 -168
- package/src/clients/config-export/constants.ts +69 -0
- package/src/clients/config-export/contracts.ts +154 -0
- package/src/clients/config-export/dsh.ts +132 -0
- package/src/clients/config-export/fast-models.ts +29 -0
- package/src/clients/config-export/mcode.ts +83 -0
- package/src/clients/config-export/model-metadata.ts +108 -0
- package/src/clients/config-export/omp.ts +104 -0
- package/src/clients/config-export/zcode.ts +92 -0
- package/src/clients/config-export.ts +18 -710
- package/src/codex/account-lifecycle.ts +20 -3
- package/src/codex/account-usability.ts +2 -0
- package/src/codex/auth-api.ts +170 -23
- package/src/codex/auth-context.ts +328 -24
- package/src/codex/catalog/effort.ts +30 -4
- package/src/codex/catalog/metadata.ts +39 -9
- package/src/codex/catalog/native-models.ts +53 -12
- package/src/codex/catalog/parsing.ts +119 -5
- package/src/codex/catalog/provider-fetch.ts +150 -20
- package/src/codex/catalog/reserve.ts +52 -0
- package/src/codex/catalog/sync.ts +89 -16
- package/src/codex/convergence-types.ts +1 -0
- package/src/codex/convergence.ts +2 -0
- package/src/codex/data/upstream-models.json +1 -1
- package/src/codex/forward-transport-headers.ts +25 -0
- package/src/codex/inject.ts +3 -28
- package/src/codex/legacy-config-keys.ts +68 -0
- package/src/codex/log-guard/inspect-schema.ts +137 -0
- package/src/codex/log-guard/inspect.ts +2 -134
- package/src/codex/loopback-target.ts +54 -0
- package/src/codex/main-account-cache.ts +63 -1
- package/src/codex/main-account-hard-lock.ts +52 -0
- package/src/codex/main-account.ts +3 -1
- package/src/codex/management-convergence.ts +3 -0
- package/src/codex/model-entitlements.ts +54 -4
- package/src/codex/project-config-warnings.ts +92 -2
- package/src/codex/prompt-layers/encoding.ts +80 -0
- package/src/codex/prompt-layers/paths.ts +54 -0
- package/src/codex/prompt-layers/revision.ts +55 -0
- package/src/codex/prompt-layers/toml-edit.ts +163 -0
- package/src/codex/prompt-layers/toml-read.ts +181 -0
- package/src/codex/prompt-layers.ts +14 -520
- package/src/codex/quota-auto-refresh-state.ts +16 -0
- package/src/codex/quota-auto-refresh.ts +219 -0
- package/src/codex/quota-types.ts +51 -0
- package/src/codex/quota.ts +252 -93
- package/src/codex/reserve-availability.ts +177 -0
- package/src/codex/routing.ts +28 -9
- package/src/codex/shim.ts +53 -11
- package/src/codex/subagent-model-fallback.ts +23 -3
- package/src/combos/failover.ts +125 -7
- package/src/combos/identifiers.ts +89 -0
- package/src/combos/index.ts +4 -0
- package/src/combos/resolve.ts +80 -9
- package/src/combos/types.ts +20 -93
- package/src/config/subagent-models.ts +24 -0
- package/src/config.ts +156 -13
- package/src/generated/compatibility-version.json +454 -166
- package/src/generated/model-metadata.ts +1 -1
- package/src/integrations/journal.ts +65 -4
- package/src/integrations/store.ts +5 -0
- package/src/lab/events/limits.ts +4 -0
- package/src/lib/destination-policy.ts +31 -2
- package/src/lib/errors.ts +39 -0
- package/src/lib/provider-outbound.ts +69 -3
- package/src/lib/proxy-env.ts +22 -0
- package/src/lib/redact-folding.ts +176 -0
- package/src/lib/redact.ts +2 -175
- package/src/lib/state-store-sweeper.ts +20 -6
- package/src/lib/token-estimate.ts +94 -27
- package/src/lib/windows-user-principal.ts +53 -5
- package/src/oauth/anthropic-routing.ts +99 -3
- package/src/oauth/generic-account-failover.ts +36 -13
- package/src/oauth/index.ts +127 -26
- package/src/oauth/login-cli.ts +5 -0
- package/src/oauth/meta-muse.ts +117 -15
- package/src/oauth/pool-settings-capability.ts +15 -4
- package/src/providers/api-keys.ts +8 -10
- package/src/providers/default-aliases.ts +39 -0
- package/src/providers/derive.ts +10 -2
- package/src/providers/fastwire.ts +36 -7
- package/src/providers/initial-model-selection-runtime.ts +90 -0
- package/src/providers/initial-model-selection.ts +120 -0
- package/src/providers/key-failover.ts +134 -54
- package/src/providers/key-store.ts +11 -1
- package/src/providers/label.ts +1 -1
- package/src/providers/model-discovery.ts +76 -0
- package/src/providers/model-rename-startup.ts +72 -8
- package/src/providers/openai-sidecar.ts +17 -5
- package/src/providers/openai-tiers-destination.ts +102 -0
- package/src/providers/openai-tiers.ts +2 -99
- package/src/providers/opencode-go-transport.ts +41 -0
- package/src/providers/quota-key-accounts.ts +141 -0
- package/src/providers/quota-types.ts +9 -0
- package/src/providers/quota.ts +535 -104
- package/src/providers/registry.ts +60 -17
- package/src/providers/xai-responses-opt-in.ts +31 -5
- package/src/quota/reset-activation.ts +81 -0
- package/src/quota/reset-detector.ts +305 -0
- package/src/quota/reset-notify-config.ts +162 -0
- package/src/quota/reset-observer.ts +125 -0
- package/src/quota/reset-poller.ts +160 -0
- package/src/quota/reset-seen-store.ts +385 -0
- package/src/quota/reset-sinks.ts +199 -0
- package/src/quota/window-mapping.ts +106 -0
- package/src/responses/apply-patch-envelope.ts +46 -0
- package/src/responses/code-mode-helper-compat.ts +39 -1
- package/src/responses/custom-tool-compat.ts +10 -4
- package/src/responses/hosted-tool-policy.ts +12 -4
- package/src/responses/parser-content.ts +133 -0
- package/src/responses/parser-text-format.ts +24 -0
- package/src/responses/parser-tools.ts +188 -0
- package/src/responses/parser.ts +3 -326
- package/src/responses/state.ts +124 -28
- package/src/router.ts +48 -13
- package/src/routing/analytics.ts +1 -0
- package/src/routing/capability.ts +17 -4
- package/src/server/auth-cors.ts +7 -1
- package/src/server/background-lifecycle.ts +23 -1
- package/src/server/chat-completions.ts +25 -3
- package/src/server/claude-messages.ts +62 -5
- package/src/server/effort-row.ts +1 -1
- package/src/server/fast-row.ts +295 -0
- package/src/server/gui-static.ts +30 -4
- package/src/server/index.ts +107 -21
- package/src/server/management/agent-settings-routes.ts +2 -2
- package/src/server/management/combo-routes.ts +37 -9
- package/src/server/management/config-routes.ts +93 -2
- package/src/server/management/integration-routes.ts +108 -0
- package/src/server/management/model-routes.ts +13 -3
- package/src/server/management/model-rows.ts +20 -1
- package/src/server/management/native-integration-routes.ts +4 -1
- package/src/server/management/oauth-account-routes.ts +38 -10
- package/src/server/management/provider-routes.ts +34 -3
- package/src/server/management/quota-reset-routes.ts +57 -0
- package/src/server/management/route-registry.ts +7 -4
- package/src/server/management/shared.ts +19 -5
- package/src/server/management/system-routes.ts +3 -2
- package/src/server/management-api.ts +14 -2
- package/src/server/ports.ts +12 -2
- package/src/server/relay-eager.ts +38 -23
- package/src/server/relay.ts +4 -0
- package/src/server/request-log.ts +6 -0
- package/src/server/responses/codex-ws-correlation.ts +65 -0
- package/src/server/responses/codex-ws-exchange.ts +261 -0
- package/src/server/responses/codex-ws-metadata.ts +134 -0
- package/src/server/responses/codex-ws-pool.ts +162 -0
- package/src/server/responses/codex-ws-request.ts +87 -0
- package/src/server/responses/codex-ws-session.ts +93 -0
- package/src/server/responses/codex-ws-wire.ts +144 -0
- package/src/server/responses/collaboration.ts +41 -1
- package/src/server/responses/compact.ts +105 -12
- package/src/server/responses/core.ts +476 -57
- package/src/server/responses/fetch-helpers.ts +10 -3
- package/src/server/responses/input-admission.ts +16 -9
- package/src/server/responses/responses-field-backfill.ts +1 -1
- package/src/server/responses/ws-upstream.ts +34 -318
- package/src/server/responses-custom-tool-repair.ts +20 -4
- package/src/server/responses-undeclared-tool-guard.ts +100 -8
- package/src/server/safe-response-headers.ts +23 -0
- package/src/server/search.ts +9 -0
- package/src/server/subagent-models-startup.ts +27 -0
- package/src/server/system-env-shell.ts +238 -0
- package/src/server/system-env.ts +7 -234
- package/src/server/ws-bridge.ts +3 -25
- package/src/server/xai-responses-startup.ts +21 -0
- package/src/service-manager-probe.ts +1 -1
- package/src/service.ts +55 -16
- package/src/types/config.ts +108 -12
- package/src/types/provider.ts +36 -7
- package/src/types/tools.ts +26 -1
- package/src/types.ts +2 -0
- package/src/update/notify.ts +8 -2
- package/src/usage/cost.ts +38 -28
- package/src/usage/expected-prices.ts +34 -15
- package/src/usage/log.ts +2 -0
- package/src/usage/model-identity.ts +26 -0
- package/src/usage/summary.ts +15 -1
- package/src/vision/describe.ts +6 -0
- package/src/vision/image-rewrite.ts +108 -0
- package/src/vision/index.ts +19 -306
- package/src/vision/plan.ts +205 -0
- package/src/web-search/executor.ts +6 -0
- package/src/web-search/index.ts +8 -1
- package/gui/dist/assets/index-BU1tE0sr.js +0 -112
- package/gui/dist/assets/index-DL9-iS6J.css +0 -1
|
@@ -9,6 +9,7 @@ import type { OcxProviderConfig } from "../../types";
|
|
|
9
9
|
import type { WsData } from "../ws-bridge";
|
|
10
10
|
import { waitForProviderRequestSlot } from "../../providers/request-pacing";
|
|
11
11
|
import { withUpstreamHttpVersion } from "../../lib/upstream-http-version";
|
|
12
|
+
import type { CodexWsQuotaObserver } from "./codex-ws-metadata";
|
|
12
13
|
|
|
13
14
|
export { withUpstreamHttpVersion };
|
|
14
15
|
|
|
@@ -56,6 +57,10 @@ export interface ProviderFetchOptions {
|
|
|
56
57
|
modelId?: string;
|
|
57
58
|
/** One pacing slot was acquired immediately before this fetch wrapper was created. */
|
|
58
59
|
pacingSlotAcquired?: boolean;
|
|
60
|
+
/** Captured selected-account observer, attached before the native WS send. */
|
|
61
|
+
onCodexWsQuota?: CodexWsQuotaObserver;
|
|
62
|
+
/** Synchronous admission at actual credential dispatch, after pacing/backoff. */
|
|
63
|
+
beforeDispatch?: (headers: Headers) => void;
|
|
59
64
|
}
|
|
60
65
|
|
|
61
66
|
export function providerFetch(
|
|
@@ -68,8 +73,10 @@ export function providerFetch(
|
|
|
68
73
|
base.preconnect?.(...args);
|
|
69
74
|
};
|
|
70
75
|
const httpFetch = Object.assign(
|
|
71
|
-
(input: Parameters<typeof globalThis.fetch>[0], init?: RequestInit) =>
|
|
72
|
-
|
|
76
|
+
async (input: Parameters<typeof globalThis.fetch>[0], init?: RequestInit) => {
|
|
77
|
+
options.beforeDispatch?.(new Headers(init?.headers ?? (input instanceof Request ? input.headers : undefined)));
|
|
78
|
+
return base(input, { ...withUpstreamHttpVersion(input, init, provider), timeout: 0 });
|
|
79
|
+
},
|
|
73
80
|
{ preconnect },
|
|
74
81
|
) as typeof globalThis.fetch;
|
|
75
82
|
// ChatGPT Codex backend: streaming turns ride the responses_websockets
|
|
@@ -82,7 +89,7 @@ export function providerFetch(
|
|
|
82
89
|
// used, protocol pin included: a WS turn that falls back is serving the
|
|
83
90
|
// request over HTTP, and dropping the provider's `upstreamHttpVersion`
|
|
84
91
|
// there would silently negotiate a transport the operator ruled out.
|
|
85
|
-
return codexWsUpstreamFetch(input, init, httpFetch, runtime);
|
|
92
|
+
return codexWsUpstreamFetch(input, init, httpFetch, runtime, options.onCodexWsQuota, options.beforeDispatch);
|
|
86
93
|
}
|
|
87
94
|
return httpFetch(input, init);
|
|
88
95
|
};
|
|
@@ -19,16 +19,23 @@ import type { OcxContentPart, OcxParsedRequest, OcxProviderConfig } from "../../
|
|
|
19
19
|
/**
|
|
20
20
|
* Multiplier applied to the ceiling before refusing.
|
|
21
21
|
*
|
|
22
|
-
* 2.5, not something tighter, because
|
|
23
|
-
*
|
|
24
|
-
*
|
|
25
|
-
* 2.5-chars/token clamp instead of the 4.0 default. Measured on Bun 1.3.14: 126,046 chars,
|
|
26
|
-
* true CJK ratio 0.0161, sampled ratio 1.0, estimate inflated 1.6x. Since 4.0 / 2.5 = 1.6
|
|
27
|
-
* is that branch maximum divergence, a threshold at or under 1.6 would convert the
|
|
28
|
-
* estimator error bar into false 413s.
|
|
22
|
+
* 2.5, not something tighter, because the estimate is a heuristic and the cost of being wrong
|
|
23
|
+
* is asymmetric: a false refusal fails a turn the provider would have answered, while an
|
|
24
|
+
* over-admission merely pays for one round trip the provider then rejects itself.
|
|
29
25
|
*
|
|
30
|
-
*
|
|
31
|
-
*
|
|
26
|
+
* The original margin was sized against a sampling artifact — `cjkRatio` read every stride-th
|
|
27
|
+
* character, so a payload of fixed-width records could sample as 100% CJK while being 1.6% CJK
|
|
28
|
+
* and inflate the estimate by 4.0/2.5 = 1.6x. That sampler is gone: CJK characters are now
|
|
29
|
+
* counted exactly, so that particular 1.6x divergence cannot occur and the headroom it bought is
|
|
30
|
+
* no longer spent on it.
|
|
31
|
+
*
|
|
32
|
+
* The margin is still 2.5 because the estimator it guards got LARGER, not smaller. Counting the
|
|
33
|
+
* two scripts separately raises a pure-Latin estimate by 1.25x and a Korean one by up to 1.67x
|
|
34
|
+
* against the previous model, which consumes real headroom: measured against the old estimator's
|
|
35
|
+
* scale, 2.5 now behaves like roughly 2.0x for Latin and 1.5x for Korean-dominant input. That is
|
|
36
|
+
* the intended direction — the estimates are closer to what providers actually charge, so the
|
|
37
|
+
* same multiplier is a tighter and more honest bound — and it still refuses the #1412 shape
|
|
38
|
+
* (10x compounding) several times over.
|
|
32
39
|
*/
|
|
33
40
|
export const ADMISSION_TOLERANCE = 2.5;
|
|
34
41
|
|
|
@@ -174,7 +174,7 @@ function backfillOutputItem(item: unknown, slot: ItemIdSlot, inferredStatus: str
|
|
|
174
174
|
*
|
|
175
175
|
* Deliberately does NOT backfill `created_at`. #2639 proposed it for the same
|
|
176
176
|
* strict-decoder reason as `status`, but the proxy also relays some upstream
|
|
177
|
-
* responses verbatim, and `tests/server-combo-failover-e2e.test.ts` asserts a
|
|
177
|
+
* responses verbatim, and `tests/server/server-combo-failover-e2e.test.ts` asserts a
|
|
178
178
|
* combo backup response is returned byte-exact. Injecting a field the upstream
|
|
179
179
|
* never sent breaks that contract. Both cannot hold for the same body, so the
|
|
180
180
|
* `created_at` half needs its own decision about which contract yields; it is
|
|
@@ -12,12 +12,17 @@
|
|
|
12
12
|
// returned event frames as an SSE byte stream, so every downstream consumer
|
|
13
13
|
// (passthrough relay, adapter parsers, usage sniffing) is unchanged.
|
|
14
14
|
|
|
15
|
-
import { MAX_CLIENT_SSE_FRAME_BYTES } from "../sse-frame-buffer";
|
|
16
15
|
import { compareBunVersions } from "../../lib/bun-stream-caps";
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
16
|
+
import type { CodexWsQuotaObserver } from "./codex-ws-metadata";
|
|
17
|
+
import { CODEX_RESPONSES_HTTP_URL, CODEX_RESPONSES_WS_URL, prepareCodexHttpInit, prepareCodexWsRequest } from "./codex-ws-request";
|
|
18
|
+
import { codexWsExchange } from "./codex-ws-exchange";
|
|
19
|
+
import { CodexWsSession } from "./codex-ws-session";
|
|
20
|
+
import { codexWsPool, codexWsReuseIdentity } from "./codex-ws-pool";
|
|
21
|
+
import { codexWsCreateFrameExceedsLimit } from "./codex-ws-wire";
|
|
22
|
+
export { CODEX_WS_RESPONSE_PRELUDE_TIMEOUT_MS, MAX_CODEX_WS_FRAME_BYTES, MAX_CODEX_WS_QUEUE_BYTES,
|
|
23
|
+
MAX_CODEX_WS_CREATE_FRAME_BYTES, CODEX_WS_CREATE_FRAME_LIMIT_BYTES, codexWsCreateFrameExceedsLimit,
|
|
24
|
+
isCodexWsQuotaObservedResponse, isCodexWsUpstreamResponse } from "./codex-ws-wire";
|
|
25
|
+
export const MIN_BOUNDED_CODEX_WS_BUN_VERSION = "1.4.0";
|
|
21
26
|
|
|
22
27
|
/**
|
|
23
28
|
* Dial URL for a request URL. The canonical ChatGPT backend keeps its constant;
|
|
@@ -48,36 +53,6 @@ function isResponsesWebsocketEligibleUrl(url: string): boolean {
|
|
|
48
53
|
return parsed.protocol === "https:"
|
|
49
54
|
&& parsed.pathname.endsWith("/responses");
|
|
50
55
|
}
|
|
51
|
-
// If the 101 never arrives (network black hole), give SSE a chance well before
|
|
52
|
-
// the caller's connect timeout (default 200s) would fire.
|
|
53
|
-
const UPGRADE_DEADLINE_MS = 10_000;
|
|
54
|
-
// Keep the push-based WS transport inside the same memory envelope as the
|
|
55
|
-
// bounded SSE relays that consume this response. Unlike fetch response bodies,
|
|
56
|
-
// a WebSocket cannot be paused when a ReadableStream applies backpressure, so
|
|
57
|
-
// an upstream that outruns the consumer must be disconnected.
|
|
58
|
-
export const MAX_CODEX_WS_FRAME_BYTES = MAX_CLIENT_SSE_FRAME_BYTES;
|
|
59
|
-
export const MAX_CODEX_WS_QUEUE_BYTES = 8 * 1024 * 1024;
|
|
60
|
-
export const MIN_BOUNDED_CODEX_WS_BUN_VERSION = "1.4.0";
|
|
61
|
-
// The backend drops any inbound message of 16 MiB or more: it closes the socket
|
|
62
|
-
// (1009) without a Responses terminal event, which reaches clients as a bare
|
|
63
|
-
// 502 upstream_server_error. Measured against the live endpoint 2026-08-23:
|
|
64
|
-
// 16,777,000 B completed, 16,777,300 B closed in ~1s, every time. The same
|
|
65
|
-
// request body succeeds over HTTP SSE, so the ceiling belongs to this transport
|
|
66
|
-
// alone (see #2426). A full-replay thread reaches it with ~11 pasted
|
|
67
|
-
// screenshots, and then never recovers, because each retry resends the frame.
|
|
68
|
-
export const MAX_CODEX_WS_CREATE_FRAME_BYTES = 16 * 1024 * 1024;
|
|
69
|
-
// Bun frames the payload it is handed, so the send-side budget is the JSON text
|
|
70
|
-
// itself, and nothing is appended between the check and the send. The margin is
|
|
71
|
-
// a conservative cushion, not a computed requirement: it covers RFC 6455 frame
|
|
72
|
-
// overhead in case the backend counts it (14 bytes at this payload size — an
|
|
73
|
-
// 8-byte extended length plus a 4-byte client mask, leaving ~65.5 KiB spare),
|
|
74
|
-
// and it leaves room for a future caller that appends to the frame.
|
|
75
|
-
const CODEX_WS_CREATE_FRAME_MARGIN_BYTES = 64 * 1024;
|
|
76
|
-
export const CODEX_WS_CREATE_FRAME_LIMIT_BYTES =
|
|
77
|
-
MAX_CODEX_WS_CREATE_FRAME_BYTES - CODEX_WS_CREATE_FRAME_MARGIN_BYTES;
|
|
78
|
-
/** Close code the backend uses for an oversized message (RFC 6455 "message too big"). */
|
|
79
|
-
const WS_CLOSE_MESSAGE_TOO_BIG = 1009;
|
|
80
|
-
|
|
81
56
|
export type BunRuntimeIdentity = {
|
|
82
57
|
version: string;
|
|
83
58
|
versionWithSha: string;
|
|
@@ -85,13 +60,6 @@ export type BunRuntimeIdentity = {
|
|
|
85
60
|
|
|
86
61
|
export type BunRuntimeGateInput = string | BunRuntimeIdentity;
|
|
87
62
|
|
|
88
|
-
const codexWsUpstreamResponses = new WeakSet<Response>();
|
|
89
|
-
|
|
90
|
-
/** True only for a successful Codex WebSocket upgrade, never an HTTP fallback. */
|
|
91
|
-
export function isCodexWsUpstreamResponse(response: Response): boolean {
|
|
92
|
-
return codexWsUpstreamResponses.has(response);
|
|
93
|
-
}
|
|
94
|
-
|
|
95
63
|
export function currentBunRuntimeIdentity(): BunRuntimeIdentity {
|
|
96
64
|
return {
|
|
97
65
|
version: Bun.version,
|
|
@@ -153,102 +121,17 @@ export function shouldUseCodexWsUpstream(
|
|
|
153
121
|
}
|
|
154
122
|
}
|
|
155
123
|
|
|
156
|
-
const CLOSED_BEFORE_TERMINAL = "codex websocket closed before a Responses terminal event";
|
|
157
|
-
|
|
158
|
-
type ResponsesWsRelayEvent = {
|
|
159
|
-
type: string;
|
|
160
|
-
text: string;
|
|
161
|
-
};
|
|
162
|
-
|
|
163
|
-
/**
|
|
164
|
-
* Responses WebSocket uses `response.done` as its terminal event, while the
|
|
165
|
-
* SSE Responses surface uses status-specific terminal events. Normalize the
|
|
166
|
-
* WS-only discriminator before relaying so the existing SSE consumers can
|
|
167
|
-
* settle the turn and the socket close cannot be mistaken for a drop. Unknown
|
|
168
|
-
* or missing status values fail closed instead of being reported as success.
|
|
169
|
-
*/
|
|
170
|
-
function normalizeResponsesWsRelayEvent(text: string): ResponsesWsRelayEvent | null {
|
|
171
|
-
let payload: unknown;
|
|
172
|
-
try {
|
|
173
|
-
payload = JSON.parse(text);
|
|
174
|
-
} catch {
|
|
175
|
-
return null;
|
|
176
|
-
}
|
|
177
|
-
if (!payload || typeof payload !== "object" || Array.isArray(payload)) return null;
|
|
178
|
-
const record = payload as Record<string, unknown>;
|
|
179
|
-
if (typeof record.type !== "string") return null;
|
|
180
|
-
if (record.type !== "response.done") return { type: record.type, text };
|
|
181
|
-
|
|
182
|
-
const response = record.response;
|
|
183
|
-
const status = response && typeof response === "object" && !Array.isArray(response)
|
|
184
|
-
? (response as Record<string, unknown>).status
|
|
185
|
-
: undefined;
|
|
186
|
-
const type = status === "completed"
|
|
187
|
-
? "response.completed"
|
|
188
|
-
: status === "failed"
|
|
189
|
-
? "response.failed"
|
|
190
|
-
: status === "incomplete" || status === "cancelled"
|
|
191
|
-
? "response.incomplete"
|
|
192
|
-
: "response.failed";
|
|
193
|
-
const normalizedRecord: Record<string, unknown> = { ...record, type };
|
|
194
|
-
if (type === "response.failed" && status !== "failed") {
|
|
195
|
-
normalizedRecord.response = response && typeof response === "object" && !Array.isArray(response)
|
|
196
|
-
? { ...(response as Record<string, unknown>), status: "failed" }
|
|
197
|
-
: { status: "failed" };
|
|
198
|
-
}
|
|
199
|
-
return { type, text: JSON.stringify(normalizedRecord) };
|
|
200
|
-
}
|
|
201
|
-
|
|
202
|
-
/**
|
|
203
|
-
* The close code is the only thing that separates "the backend refused this
|
|
204
|
-
* payload" from "the network dropped", and both used to reach the caller as the
|
|
205
|
-
* same bare 502. Naming the oversized case here puts that distinction in the
|
|
206
|
-
* message the client receives.
|
|
207
|
-
*
|
|
208
|
-
* It does NOT reach the request log as a typed code. The eager relay turns any
|
|
209
|
-
* stream error into a generic `upstream_reset` synthetic terminal
|
|
210
|
-
* (`relay.ts`, `relay-eager.ts`) without feeding that frame back through the
|
|
211
|
-
* inspector, so `/api/logs` keeps neither this message nor a specific code —
|
|
212
|
-
* only `streamAborted`. Machine-readable typing would mean changing the error
|
|
213
|
-
* taxonomy, which is deliberately out of scope for this transport fix.
|
|
214
|
-
*/
|
|
215
|
-
function closedBeforeTerminalMessage(event: unknown): string {
|
|
216
|
-
const detail = event as { code?: unknown; reason?: unknown } | null | undefined;
|
|
217
|
-
const code = typeof detail?.code === "number" ? detail.code : null;
|
|
218
|
-
const reason = typeof detail?.reason === "string" ? detail.reason.trim() : "";
|
|
219
|
-
if (code === null) return CLOSED_BEFORE_TERMINAL;
|
|
220
|
-
const suffix = reason ? ` ${code} ${reason}` : ` ${code}`;
|
|
221
|
-
if (code === WS_CLOSE_MESSAGE_TOO_BIG) {
|
|
222
|
-
return `codex websocket rejected the request frame as too large (close${suffix});`
|
|
223
|
-
+ ` requests at or above ${MAX_CODEX_WS_CREATE_FRAME_BYTES} bytes must use the HTTP SSE transport`;
|
|
224
|
-
}
|
|
225
|
-
return `${CLOSED_BEFORE_TERMINAL} (close${suffix})`;
|
|
226
|
-
}
|
|
227
|
-
|
|
228
|
-
/**
|
|
229
|
-
* True when the `response.create` frame is at or above the backend's inbound
|
|
230
|
-
* message ceiling, so this turn must take the HTTP SSE path instead.
|
|
231
|
-
*
|
|
232
|
-
* Sizing a 16 MiB string should not cost a 16 MiB copy. UTF-8 never encodes
|
|
233
|
-
* below one byte per UTF-16 code unit and never above three, so both tails are
|
|
234
|
-
* settled from the string length alone; only the narrow band between them pays
|
|
235
|
-
* for a real byte count, and `Buffer.byteLength` measures without allocating.
|
|
236
|
-
*/
|
|
237
|
-
export function codexWsCreateFrameExceedsLimit(
|
|
238
|
-
frameText: string,
|
|
239
|
-
limitBytes: number = CODEX_WS_CREATE_FRAME_LIMIT_BYTES,
|
|
240
|
-
): boolean {
|
|
241
|
-
if (frameText.length >= limitBytes) return true;
|
|
242
|
-
if (frameText.length * 3 < limitBytes) return false;
|
|
243
|
-
return Buffer.byteLength(frameText, "utf8") >= limitBytes;
|
|
244
|
-
}
|
|
245
|
-
|
|
246
124
|
export function codexWsUpstreamFetch(
|
|
247
125
|
url: string,
|
|
248
126
|
init: RequestInit,
|
|
249
127
|
sseFallback: typeof globalThis.fetch,
|
|
250
128
|
runtime: BunRuntimeGateInput = currentBunRuntimeIdentity(),
|
|
129
|
+
onQuota?: CodexWsQuotaObserver,
|
|
130
|
+
beforeDispatch?: (headers: Headers) => void,
|
|
251
131
|
): Promise<Response> {
|
|
132
|
+
const prepared = prepareCodexWsRequest(url, init);
|
|
133
|
+
if (!prepared) return sseFallback(url, prepareCodexHttpInit(url, init));
|
|
134
|
+
init = prepared.httpInit;
|
|
252
135
|
if (!bunSupportsBoundedCodexWsRelay(runtime)) {
|
|
253
136
|
return sseFallback(url, init);
|
|
254
137
|
}
|
|
@@ -257,16 +140,7 @@ export function codexWsUpstreamFetch(
|
|
|
257
140
|
return Promise.reject(signal.reason ?? new DOMException("The operation was aborted.", "AbortError"));
|
|
258
141
|
}
|
|
259
142
|
|
|
260
|
-
|
|
261
|
-
try {
|
|
262
|
-
const body = JSON.parse(init.body as string) as Record<string, unknown>;
|
|
263
|
-
// The WS create frame is implicitly streaming; the backend rejects the
|
|
264
|
-
// HTTP-only `stream` flag inside a frame.
|
|
265
|
-
delete body.stream;
|
|
266
|
-
frameText = JSON.stringify({ ...body, type: "response.create" });
|
|
267
|
-
} catch {
|
|
268
|
-
return sseFallback(url, init);
|
|
269
|
-
}
|
|
143
|
+
const { frameText, headers } = prepared;
|
|
270
144
|
|
|
271
145
|
// Decide before dialing. Once the socket is open the caller already holds a
|
|
272
146
|
// streaming Response, so the oversized close can only be surfaced as a stream
|
|
@@ -276,187 +150,29 @@ export function codexWsUpstreamFetch(
|
|
|
276
150
|
return sseFallback(url, init);
|
|
277
151
|
}
|
|
278
152
|
|
|
279
|
-
const headers: Record<string, string> = {};
|
|
280
|
-
new Headers(init.headers ?? {}).forEach((value, key) => {
|
|
281
|
-
// HTTP-body framing headers do not apply to a WS handshake.
|
|
282
|
-
if (key === "content-type" || key === "content-length" || key === "accept" || key === "accept-encoding") return;
|
|
283
|
-
headers[key] = value;
|
|
284
|
-
});
|
|
285
|
-
headers["openai-beta"] = headers["openai-beta"]
|
|
286
|
-
? headers["openai-beta"].includes("responses_websockets")
|
|
287
|
-
? headers["openai-beta"]
|
|
288
|
-
: `${headers["openai-beta"]}, ${WS_BETA}`
|
|
289
|
-
: WS_BETA;
|
|
290
153
|
// A genuine caller `originator` is already in these headers via the forward
|
|
291
154
|
// set. Never fabricate one here: pool/forward traffic must not impersonate
|
|
292
155
|
// Codex CLI, per the metadata-integrity contract. (The backend's fast lane
|
|
293
156
|
// keys on WS + originator, so callers without the tag simply keep their own
|
|
294
157
|
// provenance and scheduling.)
|
|
295
158
|
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
159
|
+
// A local refusal is not a failed upgrade and must never enter the SSE fallback path.
|
|
160
|
+
try {
|
|
161
|
+
beforeDispatch?.(new Headers(headers));
|
|
162
|
+
} catch (error) {
|
|
163
|
+
return Promise.reject(error);
|
|
164
|
+
}
|
|
165
|
+
let session: CodexWsSession;
|
|
166
|
+
try {
|
|
167
|
+
const identity = codexWsReuseIdentity(url, headers, frameText);
|
|
168
|
+
session = (identity ? codexWsPool.acquire(identity, wsUpstreamUrlFor(url), headers) : null)
|
|
169
|
+
?? new CodexWsSession(wsUpstreamUrlFor(url), headers);
|
|
170
|
+
if (!session.busy && !session.reserve()) {
|
|
171
|
+
session.dispose();
|
|
172
|
+
return sseFallback(url, init);
|
|
304
173
|
}
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
let controller: ReadableStreamDefaultController<Uint8Array> | null = null;
|
|
310
|
-
const encoder = new TextEncoder();
|
|
311
|
-
|
|
312
|
-
const failStream = (message: string) => {
|
|
313
|
-
if (terminal) return;
|
|
314
|
-
terminal = true;
|
|
315
|
-
try { controller?.error(new Error(message)); } catch { /* stream already done */ }
|
|
316
|
-
try { ws.close(); } catch { /* already closing */ }
|
|
317
|
-
};
|
|
318
|
-
|
|
319
|
-
const upgradeTimer = setTimeout(() => {
|
|
320
|
-
if (opened || settledPreOpen) return;
|
|
321
|
-
settledPreOpen = true;
|
|
322
|
-
try { ws.close(); } catch { /* already closing */ }
|
|
323
|
-
resolve(sseFallback(url, init));
|
|
324
|
-
}, UPGRADE_DEADLINE_MS);
|
|
325
|
-
|
|
326
|
-
const onAbort = () => {
|
|
327
|
-
if (!opened) {
|
|
328
|
-
if (settledPreOpen) return;
|
|
329
|
-
// Settle BEFORE close(): the close handler treats a pre-open close as
|
|
330
|
-
// an upgrade rejection and would dial the SSE fallback for a request
|
|
331
|
-
// the caller just cancelled.
|
|
332
|
-
settledPreOpen = true;
|
|
333
|
-
clearTimeout(upgradeTimer);
|
|
334
|
-
try { ws.close(); } catch { /* already closing */ }
|
|
335
|
-
reject(signal?.reason ?? new DOMException("The operation was aborted.", "AbortError"));
|
|
336
|
-
return;
|
|
337
|
-
}
|
|
338
|
-
if (controller && !terminal) {
|
|
339
|
-
terminal = true;
|
|
340
|
-
// Mirror an aborted fetch: the body read rejects with the abort reason.
|
|
341
|
-
try { controller.error(signal?.reason ?? new DOMException("The operation was aborted.", "AbortError")); } catch { /* stream already done */ }
|
|
342
|
-
}
|
|
343
|
-
// Error the body before close(): test doubles and some runtimes dispatch
|
|
344
|
-
// close synchronously, and the caller's abort reason must stay authoritative.
|
|
345
|
-
try { ws.close(); } catch { /* already closing */ }
|
|
346
|
-
};
|
|
347
|
-
signal?.addEventListener("abort", onAbort, { once: true });
|
|
348
|
-
|
|
349
|
-
ws.addEventListener("open", () => {
|
|
350
|
-
if (settledPreOpen) return;
|
|
351
|
-
clearTimeout(upgradeTimer);
|
|
352
|
-
try {
|
|
353
|
-
ws.send(frameText);
|
|
354
|
-
} catch {
|
|
355
|
-
// send() throwing means the frame never left, so no upstream turn
|
|
356
|
-
// started and the SSE resend cannot double-generate. Falling back
|
|
357
|
-
// (instead of erroring a synthetic 200 body) keeps the pre-stream
|
|
358
|
-
// HTTP error/refresh/failover machinery in charge.
|
|
359
|
-
settledPreOpen = true;
|
|
360
|
-
try { ws.close(); } catch { /* already closing */ }
|
|
361
|
-
resolve(sseFallback(url, init));
|
|
362
|
-
return;
|
|
363
|
-
}
|
|
364
|
-
opened = true;
|
|
365
|
-
const stream = new ReadableStream<Uint8Array>({
|
|
366
|
-
start(c) { controller = c; },
|
|
367
|
-
cancel() { try { ws.close(); } catch { /* already closing */ } },
|
|
368
|
-
}, new ByteLengthQueuingStrategy({ highWaterMark: MAX_CODEX_WS_QUEUE_BYTES }));
|
|
369
|
-
const response = new Response(stream, {
|
|
370
|
-
status: 200,
|
|
371
|
-
// The 101 response headers (x-codex-*-reset-at quota hints) are not
|
|
372
|
-
// exposed by Bun's WebSocket; the periodic quota poller covers those.
|
|
373
|
-
headers: { "content-type": "text/event-stream; charset=utf-8" },
|
|
374
|
-
});
|
|
375
|
-
codexWsUpstreamResponses.add(response);
|
|
376
|
-
resolve(response);
|
|
377
|
-
});
|
|
378
|
-
|
|
379
|
-
ws.addEventListener("message", (event) => {
|
|
380
|
-
if (!controller || terminal) return;
|
|
381
|
-
const text = typeof event.data === "string" ? event.data : "";
|
|
382
|
-
if (!text) return;
|
|
383
|
-
// UTF-8 byte length is always at least the JS string length. Reject this
|
|
384
|
-
// cheap lower bound before parsing so an obviously oversized frame does
|
|
385
|
-
// not create another large object graph.
|
|
386
|
-
if (text.length > MAX_CODEX_WS_FRAME_BYTES) {
|
|
387
|
-
failStream("codex websocket frame exceeds the response size limit");
|
|
388
|
-
return;
|
|
389
|
-
}
|
|
390
|
-
const rawEncodedText = encoder.encode(text);
|
|
391
|
-
if (rawEncodedText.byteLength > MAX_CODEX_WS_FRAME_BYTES) {
|
|
392
|
-
failStream("codex websocket frame exceeds the response size limit");
|
|
393
|
-
return;
|
|
394
|
-
}
|
|
395
|
-
const normalized = normalizeResponsesWsRelayEvent(text);
|
|
396
|
-
if (!normalized) return;
|
|
397
|
-
const { type } = normalized;
|
|
398
|
-
const encodedText = normalized.text === text ? rawEncodedText : encoder.encode(normalized.text);
|
|
399
|
-
if (encodedText.byteLength > MAX_CODEX_WS_FRAME_BYTES) {
|
|
400
|
-
failStream("codex websocket frame exceeds the response size limit");
|
|
401
|
-
return;
|
|
402
|
-
}
|
|
403
|
-
// Relay only the event surface the SSE path produces today. WS-only
|
|
404
|
-
// frames (codex.rate_limits, responsesapi.websocket_timing) are dropped
|
|
405
|
-
// so downstream clients see exactly the stream shape they always got.
|
|
406
|
-
if (!type.startsWith("response.") && type !== "error") return;
|
|
407
|
-
const prefix = encoder.encode(`event: ${type}\ndata: `);
|
|
408
|
-
const suffix = encoder.encode("\n\n");
|
|
409
|
-
const frameBytes = prefix.byteLength + encodedText.byteLength + suffix.byteLength;
|
|
410
|
-
if (frameBytes > MAX_CLIENT_SSE_FRAME_BYTES) {
|
|
411
|
-
failStream("codex websocket frame exceeds the response size limit");
|
|
412
|
-
return;
|
|
413
|
-
}
|
|
414
|
-
const availableBytes = controller.desiredSize ?? 0;
|
|
415
|
-
if (frameBytes > availableBytes) {
|
|
416
|
-
failStream("codex websocket response exceeded the buffered queue limit");
|
|
417
|
-
return;
|
|
418
|
-
}
|
|
419
|
-
const sseFrame = new Uint8Array(frameBytes);
|
|
420
|
-
sseFrame.set(prefix);
|
|
421
|
-
sseFrame.set(encodedText, prefix.byteLength);
|
|
422
|
-
sseFrame.set(suffix, prefix.byteLength + encodedText.byteLength);
|
|
423
|
-
try {
|
|
424
|
-
controller.enqueue(sseFrame);
|
|
425
|
-
} catch {
|
|
426
|
-
failStream("codex websocket response stream closed while enqueueing");
|
|
427
|
-
return;
|
|
428
|
-
}
|
|
429
|
-
if (type === "response.completed" || type === "response.failed" || type === "response.incomplete" || type === "error") {
|
|
430
|
-
terminal = true;
|
|
431
|
-
try { controller.close(); } catch { /* already closed */ }
|
|
432
|
-
try { ws.close(); } catch { /* already closing */ }
|
|
433
|
-
}
|
|
434
|
-
});
|
|
435
|
-
|
|
436
|
-
ws.addEventListener("close", (event: unknown) => {
|
|
437
|
-
signal?.removeEventListener("abort", onAbort);
|
|
438
|
-
if (!opened) {
|
|
439
|
-
if (settledPreOpen) return;
|
|
440
|
-
settledPreOpen = true;
|
|
441
|
-
clearTimeout(upgradeTimer);
|
|
442
|
-
// Upgrade rejected (401/403/429/5xx). Retry over plain SSE so the real
|
|
443
|
-
// HTTP status reaches the existing refresh/rotation handlers. No turn
|
|
444
|
-
// started upstream, so the resend cannot double-generate.
|
|
445
|
-
resolve(sseFallback(url, init));
|
|
446
|
-
return;
|
|
447
|
-
}
|
|
448
|
-
if (controller && !terminal) {
|
|
449
|
-
terminal = true;
|
|
450
|
-
// Connection dropped before a Responses terminal event. A clean EOF
|
|
451
|
-
// here would reach clients with no response.completed/failed at all —
|
|
452
|
-
// relaySseWithFailedTail() only synthesizes a failed terminal when the
|
|
453
|
-
// body read THROWS. Error the stream like a reset TCP socket.
|
|
454
|
-
try { controller.error(new Error(closedBeforeTerminalMessage(event))); } catch { /* stream already done */ }
|
|
455
|
-
}
|
|
456
|
-
});
|
|
457
|
-
|
|
458
|
-
ws.addEventListener("error", () => {
|
|
459
|
-
/* Bun always follows error with close; the close handler settles. */
|
|
460
|
-
});
|
|
461
|
-
});
|
|
174
|
+
} catch {
|
|
175
|
+
return sseFallback(url, init);
|
|
176
|
+
}
|
|
177
|
+
return codexWsExchange({ session, url, init, prepared, sseFallback, onQuota, beforeDispatch });
|
|
462
178
|
}
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import type { TranslatorBudget } from "../lib/translator-budget";
|
|
2
|
-
import { normalizeApplyPatchDelimiters } from "../responses/apply-patch-envelope";
|
|
3
|
-
import { compileCodeModeHelperInput } from "../responses/code-mode-helper-compat";
|
|
2
|
+
import { mayBecomePatchEnvelope, normalizeApplyPatchDelimiters } from "../responses/apply-patch-envelope";
|
|
3
|
+
import { compileCodeModeHelperInput, resolveCodeModeHelperName } from "../responses/code-mode-helper-compat";
|
|
4
|
+
import { declaresCodeModeExec } from "../types/tools";
|
|
4
5
|
import {
|
|
5
6
|
customToolItemId,
|
|
6
7
|
restoreRoutedCustomCalls,
|
|
@@ -316,6 +317,16 @@ export function createRoutedCustomToolRestoreBlockRewrite(
|
|
|
316
317
|
if (FREEFORM_WRAP_PREFIX.startsWith(open.argumentsText)) return [];
|
|
317
318
|
const fullInput = partialCustomToolInput(open.argumentsText);
|
|
318
319
|
if (fullInput === null) return [];
|
|
320
|
+
// Hold a buffer that could still become a complete patch envelope. The done event
|
|
321
|
+
// recompiles such a body into an apply_patch helper call, so streaming the envelope
|
|
322
|
+
// bytes first and replacing them at completion is the rewind this path forbids.
|
|
323
|
+
// Mirrors the same hold in `src/bridge.ts`.
|
|
324
|
+
if (
|
|
325
|
+
declaresCodeModeExec(declaredNames)
|
|
326
|
+
&& itemNames.get(upstreamItemId)?.namespace === undefined
|
|
327
|
+
&& itemNames.get(upstreamItemId)?.name === "exec"
|
|
328
|
+
&& mayBecomePatchEnvelope(fullInput)
|
|
329
|
+
) return [];
|
|
319
330
|
if (!fullInput.startsWith(open.emittedInput) || fullInput.length === open.emittedInput.length) return [];
|
|
320
331
|
const inputDelta = fullInput.slice(open.emittedInput.length);
|
|
321
332
|
open.emittedInput = fullInput;
|
|
@@ -340,12 +351,17 @@ export function createRoutedCustomToolRestoreBlockRewrite(
|
|
|
340
351
|
: openCalls.get(upstreamItemId)?.argumentsText ?? "";
|
|
341
352
|
const { arguments: _arguments, ...rest } = parsed;
|
|
342
353
|
const itemName = itemNames.get(upstreamItemId);
|
|
354
|
+
// Name-based alias first; otherwise a raw patch envelope submitted as the `exec` body
|
|
355
|
+
// resolves to the same apply_patch helper (devlog/_plan/260905_apply_patch_envelope_gap).
|
|
356
|
+
const helper = itemName?.aliased
|
|
357
|
+
? itemName.name
|
|
358
|
+
: resolveCodeModeHelperName(undefined, itemName?.name ?? "", source, itemName?.namespace, declaredNames);
|
|
343
359
|
const next = {
|
|
344
360
|
...rest,
|
|
345
361
|
type: nextType,
|
|
346
362
|
item_id: customToolItemId(upstreamItemId),
|
|
347
|
-
input:
|
|
348
|
-
? compileCodeModeHelperInput(source,
|
|
363
|
+
input: helper
|
|
364
|
+
? compileCodeModeHelperInput(source, helper)
|
|
349
365
|
: unwrapRoutedCustomToolArguments(source, itemName?.name ?? "", itemName?.namespace),
|
|
350
366
|
};
|
|
351
367
|
return [replaceSseDataPayload(replaceSseEventName(block, nextType), JSON.stringify(next))];
|