@bitkyc08/opencodex 2.58.0 → 2.60.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 +28 -10
- package/gui/dist/assets/index-BTuCbqQd.css +1 -0
- package/gui/dist/assets/index-DoBVdPHP.js +134 -0
- package/gui/dist/index.html +2 -2
- package/gui/dist/provider-icons/crusoe.svg +1 -0
- package/gui/dist/provider-icons/opper.svg +3 -0
- package/package.json +4 -1
- package/src/adapters/anthropic-image-codec.ts +16 -2
- package/src/adapters/anthropic-image-normalize.ts +49 -2
- package/src/adapters/anthropic.ts +4 -1
- package/src/adapters/base.ts +34 -1
- package/src/adapters/coding-agent/turn.ts +22 -2
- package/src/adapters/command-code.ts +50 -3
- package/src/adapters/cursor/catalog.ts +11 -0
- package/src/adapters/cursor/checkpoint-store.ts +3 -0
- package/src/adapters/cursor/discovery.ts +11 -8
- package/src/adapters/cursor/effort-map.ts +16 -2
- package/src/adapters/cursor/envelope-echo.ts +55 -2
- package/src/adapters/cursor/live-transport.ts +26 -9
- package/src/adapters/cursor/message-mapper.ts +3 -2
- package/src/adapters/cursor/protobuf-request.ts +8 -5
- package/src/adapters/cursor/request-builder.ts +21 -4
- package/src/adapters/cursor/thread-continuity.ts +105 -31
- package/src/adapters/cursor/tool-guidance.ts +5 -4
- package/src/adapters/cursor/transport.ts +19 -0
- package/src/adapters/cursor.ts +45 -2
- package/src/adapters/devin/cloud-direct/chat.ts +14 -3
- package/src/adapters/devin/cloud-direct/index.ts +7 -0
- package/src/adapters/devin/cloud-direct/stated-reset-retry.ts +140 -0
- package/src/adapters/devin.ts +125 -26
- package/src/adapters/google-antigravity-replay.ts +1 -1
- package/src/adapters/google-antigravity-wire.ts +55 -4
- package/src/adapters/google-http.ts +57 -11
- package/src/adapters/google-tool-schema.ts +595 -31
- package/src/adapters/google-wire-compiler.ts +93 -10
- package/src/adapters/google-wire-shape.ts +461 -0
- package/src/adapters/google.ts +60 -10
- package/src/adapters/openai-chat/response-events.ts +61 -0
- package/src/adapters/openai-chat-images.ts +3 -1
- package/src/adapters/openai-chat.ts +10 -11
- package/src/adapters/openai-responses/image-gen.ts +8 -6
- package/src/adapters/openai-responses/passthrough.ts +25 -4
- package/src/adapters/openai-responses/reasoning.ts +7 -0
- package/src/adapters/openai-responses/tool-output-recovery.ts +75 -0
- package/src/adapters/openai-responses/tool-schema.ts +19 -7
- package/src/adapters/opencode-go-additional-tools.ts +12 -2
- package/src/adapters/responses-tool-schema.ts +76 -46
- package/src/adapters/run-turn-queue.ts +17 -4
- package/src/bridge/response-json.ts +1 -1
- package/src/bridge/sse.ts +28 -35
- package/src/claude/context-windows.ts +22 -0
- package/src/claude/outbound.ts +35 -4
- package/src/cli/account-api.ts +4 -3
- package/src/cli/account-extended.ts +26 -6
- package/src/cli/account-orca-import.ts +63 -0
- package/src/cli/account.ts +32 -4
- package/src/cli/capabilities.ts +40 -0
- package/src/cli/claude.ts +29 -1
- package/src/cli/codex-cli-update.ts +97 -2
- package/src/cli/dispatch.ts +57 -3
- package/src/cli/doctor.ts +218 -4
- package/src/cli/help.ts +4 -1
- package/src/cli/hub.ts +3 -2
- package/src/cli/index.ts +95 -22
- package/src/cli/models-runtime.ts +33 -4
- package/src/cli/opencode.ts +2 -2
- package/src/cli/provider.ts +13 -1
- package/src/cli/registry.ts +11 -1
- package/src/cli/runtime-api.ts +44 -0
- package/src/cli/start-args.ts +94 -0
- package/src/cli/system-command.ts +2 -0
- package/src/client/machine-api.ts +6 -5
- package/src/client/machine-listener.ts +16 -3
- package/src/client/runtime.ts +26 -2
- package/src/clients/config-export/constants.ts +2 -3
- package/src/clients/config-export.ts +5 -5
- package/src/codex/account-store.ts +146 -5
- package/src/codex/auth-api/account-list.ts +19 -11
- package/src/codex/auth-api/pool-quota-probe.ts +44 -10
- package/src/codex/auth-api/routes.ts +17 -2
- package/src/codex/auth-context.ts +16 -12
- package/src/codex/catalog/build-entries.ts +25 -4
- package/src/codex/catalog/derive-entry.ts +8 -1
- package/src/codex/catalog/effort.ts +10 -6
- package/src/codex/catalog/gather-capture.ts +22 -2
- package/src/codex/catalog/model-hints.ts +66 -33
- package/src/codex/catalog/parsing.ts +90 -5
- package/src/codex/catalog/provider-models.ts +19 -2
- package/src/codex/catalog/reserve-warn.ts +96 -0
- package/src/codex/catalog/retained-sync.ts +41 -26
- package/src/codex/catalog/routed-gather.ts +61 -3
- package/src/codex/cli-installation-identity.ts +210 -0
- package/src/codex/cli-installation-targets.ts +158 -0
- package/src/codex/context-compat.ts +5 -2
- package/src/codex/convergence.ts +5 -0
- package/src/codex/desired-state.ts +4 -1
- package/src/codex/history-job.ts +6 -6
- package/src/codex/history-provider.ts +24 -167
- package/src/codex/history-rollout-read.ts +174 -0
- package/src/codex/history-state-open.ts +105 -0
- package/src/codex/inject/config-toml.ts +44 -2
- package/src/codex/inject.ts +3 -2
- package/src/codex/internal/catalog-writer.ts +33 -1
- package/src/codex/lineage.ts +83 -32
- package/src/codex/loopback-target.ts +31 -0
- package/src/codex/main-account-hard-lock.ts +2 -1
- package/src/codex/main-account.ts +10 -3
- package/src/codex/main-device-reauth.ts +17 -9
- package/src/codex/model-cache.ts +47 -0
- package/src/codex/model-entitlements.ts +80 -2
- package/src/codex/observed-model-denials.ts +230 -0
- package/src/codex/orca-auth-source.ts +94 -0
- package/src/codex/orca-import.ts +219 -0
- package/src/codex/prompt-text-probe.ts +289 -16
- package/src/codex/quota-401-recovery.ts +12 -0
- package/src/codex/quota-types.ts +65 -0
- package/src/codex/quota.ts +24 -19
- package/src/codex/routing/cooldown-math.ts +8 -47
- package/src/codex/routing/pin-drain.ts +57 -0
- package/src/codex/routing.ts +20 -16
- package/src/codex/shim.ts +1 -1
- package/src/codex/subagent-model-fallback.ts +114 -2
- package/src/codex/windows-installation-files.ts +224 -0
- package/src/combos/failover.ts +125 -5
- package/src/config/admitted-identity.ts +222 -0
- package/src/config/diagnostics.ts +43 -1
- package/src/config/feature-flags.ts +5 -0
- package/src/config/load-degrade.ts +33 -0
- package/src/config/pending-teardown.ts +8 -0
- package/src/config/process-state.ts +36 -3
- package/src/config/provider-relative-send-path.ts +16 -0
- package/src/config/proxy-env.ts +31 -7
- package/src/config/schema/compaction-triggers.ts +11 -0
- package/src/config/schema/config-schema.ts +25 -0
- package/src/config/schema/leaf-validators.ts +76 -17
- package/src/config.ts +2 -2
- package/src/generated/compatibility-version.json +410 -258
- package/src/generated/model-metadata.ts +1 -1
- package/src/grok/reset-coupons.ts +38 -19
- package/src/images/loop.ts +6 -1
- package/src/integrations/aside-profile-context.ts +37 -3
- package/src/integrations/aside-profile-journal.ts +68 -3
- package/src/integrations/aside-profiles.ts +128 -3
- package/src/integrations/mutation-plan.ts +815 -0
- package/src/integrations/writer.ts +85 -99
- package/src/lab/live/transport.ts +4 -0
- package/src/lab/live/types.ts +5 -0
- package/src/lab/subject/behavior-fingerprint.ts +1 -1
- package/src/lib/admin-secrets.ts +9 -1
- package/src/lib/bounded-body.ts +4 -2
- package/src/lib/debug-log-buffer.ts +6 -1
- package/src/lib/debug.ts +23 -0
- package/src/lib/destination-policy.ts +48 -6
- package/src/lib/errors.ts +82 -15
- package/src/lib/http-response-semantics.ts +57 -0
- package/src/lib/lab-live-pinned-sender.ts +26 -12
- package/src/lib/local-destinations.ts +32 -5
- package/src/lib/pinned-http.ts +142 -2
- package/src/lib/plain-data.ts +103 -0
- package/src/lib/process-control.ts +13 -5
- package/src/lib/provider-outbound.ts +53 -5
- package/src/lib/proxy-env.ts +70 -3
- package/src/lib/request-execution-budget.ts +11 -3
- package/src/lib/response-body-inactivity.ts +193 -0
- package/src/lib/retry-delay.ts +69 -0
- package/src/lib/socks5-fetch.ts +741 -0
- package/src/lib/spend-ledger-owner.ts +364 -0
- package/src/lib/spend-reservation-ledger.ts +332 -35
- package/src/lib/windows-system-proxy.ts +16 -11
- package/src/lib/workflow-budget.ts +145 -8
- package/src/oauth/account-quota-rank.ts +72 -15
- package/src/oauth/callback-server.ts +4 -3
- package/src/oauth/generic-account-failover.ts +41 -27
- package/src/oauth/health.ts +12 -1
- package/src/oauth/index.ts +3 -107
- package/src/oauth/login-flow-state.ts +127 -0
- package/src/oauth/orcarouter.ts +15 -2
- package/src/oauth/store.ts +8 -0
- package/src/providers/codex-capacity.ts +9 -0
- package/src/providers/derive.ts +34 -17
- package/src/providers/devin-cli-authmode-migration.ts +14 -10
- package/src/providers/devin-provider-merge-migration.ts +33 -12
- package/src/providers/free-directory.ts +20 -2
- package/src/providers/key-failover.ts +327 -25
- package/src/providers/model-rename-migration.ts +56 -1
- package/src/providers/model-rename-startup.ts +7 -5
- package/src/providers/openai-sidecar.ts +4 -0
- package/src/providers/openai-virtual-models.ts +42 -2
- package/src/providers/opencode-go-transport.ts +14 -5
- package/src/providers/quota/antigravity.ts +22 -2
- package/src/providers/quota/report-cache.ts +3 -0
- package/src/providers/quota/vendor-probes-key.ts +1 -1
- package/src/providers/registry/entries-core.ts +39 -17
- package/src/providers/registry/entries-extended.ts +131 -1
- package/src/providers/registry/model-ids.ts +168 -0
- package/src/providers/registry/model-seeds.ts +87 -21
- package/src/providers/registry/types.ts +2 -0
- package/src/providers/resolved-model-policy-merge.ts +167 -0
- package/src/providers/resolved-model-policy.ts +406 -0
- package/src/providers/stale-vision-classification-migration.ts +137 -0
- package/src/responses/apply-patch-envelope.ts +32 -11
- package/src/responses/bridge-search-replay-cache.ts +152 -0
- package/src/responses/code-mode-helper-compat.ts +26 -16
- package/src/responses/custom-tool-compat.ts +1 -1
- package/src/responses/freeform-wrapper-scan.ts +279 -0
- package/src/responses/hosted-tool-policy.ts +85 -2
- package/src/responses/legacy-dotted-tool-name-repair.ts +134 -0
- package/src/responses/progressive-freeform-input.ts +130 -0
- package/src/responses/reasoning-envelope.ts +30 -0
- package/src/responses/schema.ts +9 -2
- package/src/responses/state.ts +5 -12
- package/src/responses/tool-name-aliases.ts +15 -1
- package/src/router.ts +91 -115
- package/src/routing/compatibility/behavior.ts +9 -0
- package/src/routing/compatibility/subject.ts +16 -1
- package/src/server/adapter-resolve.ts +9 -0
- package/src/server/auth-cors.ts +29 -0
- package/src/server/chat-completions.ts +13 -5
- package/src/server/chat-native-sse.ts +26 -9
- package/src/server/chat-native.ts +10 -4
- package/src/server/claude-messages.ts +30 -5
- package/src/server/effort-row.ts +11 -3
- package/src/server/grok-responses-control-frame.ts +160 -1
- package/src/server/gui-static.ts +36 -2
- package/src/server/inbound-body-admission.ts +187 -0
- package/src/server/index/serve-options.ts +86 -29
- package/src/server/index/spend-ledger-lifecycle.ts +66 -0
- package/src/server/index/websocket-handler.ts +6 -1
- package/src/server/index.ts +24 -25
- package/src/server/management/api-access.ts +3 -4
- package/src/server/management/aside-profile-routes.ts +266 -7
- package/src/server/management/config-routes.ts +48 -7
- package/src/server/management/context.ts +3 -0
- package/src/server/management/integration-routes.ts +287 -5
- package/src/server/management/metrics-routes.ts +20 -0
- package/src/server/management/model-rows.ts +224 -12
- package/src/server/management/provider-capability-config.ts +35 -7
- package/src/server/management/provider-routes.ts +70 -18
- package/src/server/management/route-registry.ts +14 -0
- package/src/server/management/shared.ts +10 -3
- package/src/server/management/system-restart.ts +7 -2
- package/src/server/management/system-routes.ts +2 -0
- package/src/server/management/usage-aggregate-cache.ts +4 -0
- package/src/server/management-api.ts +2 -0
- package/src/server/management-auth.ts +15 -1
- package/src/server/proxy-liveness.ts +97 -2
- package/src/server/readiness.ts +29 -10
- package/src/server/relay-eager.ts +24 -2
- package/src/server/relay.ts +136 -34
- package/src/server/request-log.ts +80 -3
- package/src/server/request-metrics.ts +236 -0
- package/src/server/responses/adapter-continuation.ts +74 -30
- package/src/server/responses/adapter-delivery.ts +39 -8
- package/src/server/responses/adapter-dispatch.ts +63 -30
- package/src/server/responses/compact.ts +89 -18
- package/src/server/responses/compaction-routing.ts +111 -0
- package/src/server/responses/core-codex-account.ts +90 -24
- package/src/server/responses/core-combo.ts +7 -7
- package/src/server/responses/core-normalize.ts +16 -15
- package/src/server/responses/core-opaque-recovery.ts +1 -0
- package/src/server/responses/core-options.ts +4 -0
- package/src/server/responses/encrypted-payload.ts +20 -2
- package/src/server/responses/fetch-helpers.ts +68 -2
- package/src/server/responses/passthrough-delivery.ts +41 -15
- package/src/server/responses/passthrough-dispatch.ts +145 -53
- package/src/server/responses/passthrough-execution.ts +11 -1
- package/src/server/responses/policy-fallback.ts +5 -13
- package/src/server/responses/request-prepare.ts +96 -17
- package/src/server/responses/request-send-budget.ts +89 -8
- package/src/server/responses/request-sidecar-auth.ts +17 -9
- package/src/server/responses/request-spend.ts +38 -9
- package/src/server/responses/request-transport.ts +15 -12
- package/src/server/responses/run-turn-execution.ts +45 -8
- package/src/server/responses/sidecar-execution.ts +19 -2
- package/src/server/responses/ws-upstream.ts +16 -28
- package/src/server/responses-custom-tool-repair.ts +29 -56
- package/src/server/responses-undeclared-tool-guard.ts +31 -1
- package/src/server/sse-frame-buffer.ts +12 -10
- package/src/server/sse-payload-rewrite.ts +37 -10
- package/src/server/system-env-shell.ts +5 -1
- package/src/server/system-env.ts +7 -1
- package/src/server/workflow-refusal.ts +56 -2
- package/src/service/cli.ts +16 -6
- package/src/service/guards.ts +10 -0
- package/src/service/health.ts +43 -0
- package/src/service/state.ts +7 -2
- package/src/tray/windows-tray.ps1 +155 -3
- package/src/types/accounts.ts +4 -0
- package/src/types/config.ts +111 -6
- package/src/types/provider.ts +37 -0
- package/src/types/request.ts +9 -1
- package/src/types/tools.ts +14 -0
- package/src/types/wire.ts +9 -1
- package/src/types.ts +1 -0
- package/src/usage/expected-prices.ts +28 -0
- package/src/usage/log.ts +87 -4
- package/src/vision/eligibility.ts +88 -9
- package/src/vision/plan.ts +34 -10
- package/src/web-search/executor.ts +41 -2
- package/src/web-search/loop.ts +6 -1
- package/src/web-search/passthrough-bridge.ts +39 -5
- package/gui/dist/assets/index-BbrHOIY0.js +0 -128
- package/gui/dist/assets/index-C5-RdDmD.css +0 -1
|
@@ -12,7 +12,7 @@ import {
|
|
|
12
12
|
adapterNeedsForcedContinuation,
|
|
13
13
|
adapterResponseReachedServingTerminal,
|
|
14
14
|
} from "./core-replay";
|
|
15
|
-
import { sealRequestAttemptIdentity, recordAttemptCredentialSource } from "../request-log";
|
|
15
|
+
import { noteAttemptRecoveryWithheld, sealRequestAttemptIdentity, recordAttemptCredentialSource } from "../request-log";
|
|
16
16
|
import { waitForProviderRequestSlot, RequestPacingQueueOverloadError } from "../../providers/request-pacing";
|
|
17
17
|
import type { AdapterEventQueue } from "../../adapters/run-turn-queue";
|
|
18
18
|
import type { AttemptRecoveryKind } from "../../usage/log";
|
|
@@ -79,7 +79,11 @@ export async function executeResponsesRunTurn(
|
|
|
79
79
|
>,
|
|
80
80
|
sendBudgetState: Pick<
|
|
81
81
|
ResponsesSendBudget,
|
|
82
|
-
|
|
82
|
+
| "adapterDispatchBudget"
|
|
83
|
+
| "noteAdapterPhysicalSend"
|
|
84
|
+
| "noteAdapterRecoveryWithheld"
|
|
85
|
+
| "reserveCredentialHop"
|
|
86
|
+
| "pendingHopPermit"
|
|
83
87
|
>,
|
|
84
88
|
completionPolicy: Pick<ResponsesCompletionPolicy, "emptyCompletionGuardEnabled">,
|
|
85
89
|
): Promise<Response> {
|
|
@@ -100,7 +104,12 @@ export async function executeResponsesRunTurn(
|
|
|
100
104
|
rememberKiroDeliveredFinalAnswer,
|
|
101
105
|
responseStateOptions,
|
|
102
106
|
} = requestState;
|
|
103
|
-
const {
|
|
107
|
+
const {
|
|
108
|
+
adapterDispatchBudget,
|
|
109
|
+
noteAdapterPhysicalSend,
|
|
110
|
+
noteAdapterRecoveryWithheld,
|
|
111
|
+
reserveCredentialHop,
|
|
112
|
+
} = sendBudgetState;
|
|
104
113
|
const { emptyCompletionGuardEnabled } = completionPolicy;
|
|
105
114
|
const {
|
|
106
115
|
cancelResponseCompletion,
|
|
@@ -146,7 +155,11 @@ export async function executeResponsesRunTurn(
|
|
|
146
155
|
await waitForProviderRequestSlot(route.providerName, route.provider, route.modelId, runTurnAbort.signal);
|
|
147
156
|
}
|
|
148
157
|
await refreshRunTurnSelection();
|
|
149
|
-
|
|
158
|
+
// An adapter that reports its own sends accounts for the first one at the boundary that
|
|
159
|
+
// dispatches it. Logging here would claim a send that the adapter's own budget can still
|
|
160
|
+
// refuse, which is exactly what happens once earlier recovery has spent the allowance.
|
|
161
|
+
const reportsOwnSends = transportState.runTurnAdapter.reportsPhysicalSends === true;
|
|
162
|
+
if (!reportsOwnSends) transportState.noteRoutedAttemptSend(logCtx.usageLogInputTokens, recovery);
|
|
150
163
|
const runTurnProviderFetch = providerFetch(
|
|
151
164
|
route.provider,
|
|
152
165
|
options.codexWsRuntimeIdentity,
|
|
@@ -169,6 +182,14 @@ export async function executeResponsesRunTurn(
|
|
|
169
182
|
// The only way the request budget reaches a transport the adapter owns. Without it
|
|
170
183
|
// a Cursor turn's inner ladder was three physical sends the cap read as one.
|
|
171
184
|
...(adapterDispatchBudget ? { sendBudget: adapterDispatchBudget } : {}),
|
|
185
|
+
onPhysicalSend: send => noteAdapterPhysicalSend(
|
|
186
|
+
logCtx.usageLogInputTokens,
|
|
187
|
+
// The attempt's own recovery kind still labels its first send when the adapter
|
|
188
|
+
// does not supply one of its own.
|
|
189
|
+
{ ...send, ...(send.recovery ?? recovery ? { recovery: send.recovery ?? recovery } : {}) },
|
|
190
|
+
{ includeFirst: reportsOwnSends },
|
|
191
|
+
),
|
|
192
|
+
onRecoveryWithheld: noteAdapterRecoveryWithheld,
|
|
172
193
|
},
|
|
173
194
|
targetQueue.push,
|
|
174
195
|
);
|
|
@@ -231,12 +252,21 @@ export async function executeResponsesRunTurn(
|
|
|
231
252
|
"auth-recovery",
|
|
232
253
|
`${route.providerName}|${route.modelId}|runturn-oauth-429`,
|
|
233
254
|
);
|
|
234
|
-
if (!hop.allowed)
|
|
255
|
+
if (!hop.allowed) {
|
|
256
|
+
// The roster bound above already said this credential set may rotate again; the shared
|
|
257
|
+
// request budget is what refused. Returning false lets the preflight 429 reach the
|
|
258
|
+
// client unchanged, which is right, but it used to leave a log indistinguishable from
|
|
259
|
+
// a request where no rotation was ever available (#5044).
|
|
260
|
+
noteAttemptRecoveryWithheld(logCtx.activeAttempt, "rotation-send-budget");
|
|
261
|
+
return false;
|
|
262
|
+
}
|
|
235
263
|
const nextAccountId = rotateGenericOAuthAccountOn429(
|
|
236
264
|
config,
|
|
237
265
|
route.providerName,
|
|
238
266
|
transportState.genericFailoverAccountId,
|
|
239
267
|
null,
|
|
268
|
+
Date.now(),
|
|
269
|
+
route.modelId,
|
|
240
270
|
);
|
|
241
271
|
if (!nextAccountId) {
|
|
242
272
|
hop.permit?.release();
|
|
@@ -245,7 +275,8 @@ export async function executeResponsesRunTurn(
|
|
|
245
275
|
try {
|
|
246
276
|
const snapshot = await failoverAccountSnapshot(route.providerName, nextAccountId);
|
|
247
277
|
transportState.genericFailovers += 1;
|
|
248
|
-
|
|
278
|
+
const admittedSnapshot = await applyFailoverSnapshot(snapshot);
|
|
279
|
+
if (!admittedSnapshot) {
|
|
249
280
|
hop.permit?.release();
|
|
250
281
|
return false;
|
|
251
282
|
}
|
|
@@ -263,6 +294,7 @@ export async function executeResponsesRunTurn(
|
|
|
263
294
|
route.modelId,
|
|
264
295
|
route.provider,
|
|
265
296
|
inboundWire,
|
|
297
|
+
route.staticPolicy,
|
|
266
298
|
);
|
|
267
299
|
const rotatedAdapter = resolveSelectionAdapter(rotatedProvider, config.cacheRetention);
|
|
268
300
|
if (!rotatedAdapter.runTurn) {
|
|
@@ -275,7 +307,10 @@ export async function executeResponsesRunTurn(
|
|
|
275
307
|
providerName: route.providerName,
|
|
276
308
|
provider: rotatedProvider,
|
|
277
309
|
adapterName: rotatedAdapter.name,
|
|
278
|
-
oauthCredentialSnapshot: {
|
|
310
|
+
oauthCredentialSnapshot: {
|
|
311
|
+
accountId: admittedSnapshot.accountId,
|
|
312
|
+
generation: admittedSnapshot.generation,
|
|
313
|
+
},
|
|
279
314
|
codexAuthContext: admissionState.authCtx,
|
|
280
315
|
forwardHeaders: requestState.selectedForwardHeaders,
|
|
281
316
|
});
|
|
@@ -299,7 +334,9 @@ export async function executeResponsesRunTurn(
|
|
|
299
334
|
try {
|
|
300
335
|
while (true) {
|
|
301
336
|
const preflight = await preflightAdapterEvents(source);
|
|
302
|
-
if (
|
|
337
|
+
if (preflight.replayUnsafe
|
|
338
|
+
|| !preflight.error
|
|
339
|
+
|| !(await rotateRunTurnAdapterOnPreflight429(preflight.error))) {
|
|
303
340
|
return preflight.stream;
|
|
304
341
|
}
|
|
305
342
|
const retryQueue = createAdapterEventQueue({
|
|
@@ -17,6 +17,7 @@ import {
|
|
|
17
17
|
clampImageMaxRounds,
|
|
18
18
|
} from "../../images";
|
|
19
19
|
import type { ProviderAdapter } from "../../adapters/base";
|
|
20
|
+
import type { OcxParsedRequest } from "../../types";
|
|
20
21
|
import { rotateProviderTransportOn429, rateLimitRetryPolicyFor } from "../../providers/key-failover";
|
|
21
22
|
import {
|
|
22
23
|
GENERIC_OAUTH_MAX_FAILOVERS_PER_REQUEST,
|
|
@@ -155,6 +156,7 @@ export async function executeResponsesSidecars(
|
|
|
155
156
|
const rotateSidecarProviderOn429 = async (
|
|
156
157
|
retryAfter: string | null,
|
|
157
158
|
responseHeaders?: Headers,
|
|
159
|
+
retryParsed?: OcxParsedRequest,
|
|
158
160
|
): Promise<ProviderAdapter | null> => {
|
|
159
161
|
const rotated = rotateProviderTransportOn429(config, route.providerName, route.provider, {
|
|
160
162
|
retryAfter,
|
|
@@ -186,6 +188,8 @@ export async function executeResponsesSidecars(
|
|
|
186
188
|
route.providerName,
|
|
187
189
|
transportState.genericFailoverAccountId,
|
|
188
190
|
retryAfter,
|
|
191
|
+
Date.now(),
|
|
192
|
+
route.modelId,
|
|
189
193
|
);
|
|
190
194
|
if (!nextAccountId) {
|
|
191
195
|
hop.permit?.release();
|
|
@@ -194,7 +198,7 @@ export async function executeResponsesSidecars(
|
|
|
194
198
|
try {
|
|
195
199
|
const snapshot = await failoverAccountSnapshot(route.providerName, nextAccountId);
|
|
196
200
|
transportState.genericFailovers += 1;
|
|
197
|
-
if (!await applyFailoverSnapshot(snapshot)) {
|
|
201
|
+
if (!await applyFailoverSnapshot(snapshot, retryParsed)) {
|
|
198
202
|
hop.permit?.release();
|
|
199
203
|
return null;
|
|
200
204
|
}
|
|
@@ -252,9 +256,22 @@ export async function executeResponsesSidecars(
|
|
|
252
256
|
return null;
|
|
253
257
|
}
|
|
254
258
|
const rotatedAdapter = resolveSelectionAdapter(
|
|
255
|
-
resolveWireProtocolOverride(route.providerName, route.modelId, route.provider, inboundWire),
|
|
259
|
+
resolveWireProtocolOverride(route.providerName, route.modelId, route.provider, inboundWire, route.staticPolicy),
|
|
256
260
|
config.cacheRetention,
|
|
257
261
|
);
|
|
262
|
+
// The sidecar loops build each attempt from an iteration-local shallow copy of parsed, so a
|
|
263
|
+
// rebind that lands only on the outer request never reaches the wire: the retry would pair
|
|
264
|
+
// the new bearer with the previous account's Kiro routing metadata and continuation identity.
|
|
265
|
+
// Rebind the exact request the rotated adapter will be built from (same pattern as the
|
|
266
|
+
// continuation loop's nextParsed); when the hook ran without one, the outer bind below stands.
|
|
267
|
+
if (retryParsed && retryParsed !== parsed) {
|
|
268
|
+
bindRouteReasoningReplayScope({
|
|
269
|
+
parsed: retryParsed,
|
|
270
|
+
providerName: route.providerName,
|
|
271
|
+
provider: route.provider,
|
|
272
|
+
adapterName: rotatedAdapter.name,
|
|
273
|
+
});
|
|
274
|
+
}
|
|
258
275
|
bindRouteReasoningReplayScope({
|
|
259
276
|
parsed,
|
|
260
277
|
providerName: route.providerName,
|
|
@@ -16,7 +16,7 @@ import type { NativeResponseControl } from "./native-response-control";
|
|
|
16
16
|
// (passthrough relay, adapter parsers, usage sniffing) is unchanged.
|
|
17
17
|
|
|
18
18
|
import { compareBunVersions } from "../../lib/bun-stream-caps";
|
|
19
|
-
import { resolveProxyRoute } from "../../lib/proxy-env";
|
|
19
|
+
import { resolveProxyRoute, socks5ProxyFromEnv } from "../../lib/proxy-env";
|
|
20
20
|
import type { CodexWsQuotaObserver } from "./codex-ws-metadata";
|
|
21
21
|
import { CODEX_RESPONSES_HTTP_URL, CODEX_RESPONSES_WS_URL, prepareCodexHttpInit, prepareCodexWsRequest } from "./codex-ws-request";
|
|
22
22
|
import { codexWsExchange } from "./codex-ws-exchange";
|
|
@@ -29,33 +29,14 @@ export { CODEX_WS_LIVENESS_PING_INTERVAL_MS, CODEX_WS_RESPONSE_PRELUDE_TIMEOUT_M
|
|
|
29
29
|
export const MIN_BOUNDED_CODEX_WS_BUN_VERSION = "1.4.0";
|
|
30
30
|
|
|
31
31
|
/**
|
|
32
|
-
* Dial URL for a
|
|
33
|
-
*
|
|
34
|
-
* path
|
|
35
|
-
* /v1/responses path get the same fast lane. Plain HTTP remains on SSE because
|
|
36
|
-
* a provider WS handshake would otherwise send credentials and request data
|
|
37
|
-
* without transport encryption.
|
|
32
|
+
* Dial URL for a first-party Responses endpoint. The canonical ChatGPT backend
|
|
33
|
+
* keeps its constant; the api.openai.com Responses endpoint swaps https for wss
|
|
34
|
+
* on the same path. No other upstream may enter the WebSocket lane.
|
|
38
35
|
*/
|
|
39
36
|
function wsUpstreamUrlFor(httpUrl: string): string {
|
|
40
37
|
if (httpUrl === CODEX_RESPONSES_HTTP_URL) return CODEX_RESPONSES_WS_URL;
|
|
41
|
-
return httpUrl.replace(/^http(s?):/, "ws$1:");
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
/**
|
|
45
|
-
* An operator-opted OpenAI-compatible upstream only joins the WS lane for
|
|
46
|
-
* Responses endpoints: the WebSocket path speaks the Responses event protocol,
|
|
47
|
-
* and every downstream consumer (adapter parsers, usage sniffing, SSE relay)
|
|
48
|
-
* assumes that wire. Other paths (chat completions, images, search) stay HTTP.
|
|
49
|
-
*/
|
|
50
|
-
function isResponsesWebsocketEligibleUrl(url: string): boolean {
|
|
51
|
-
let parsed: URL;
|
|
52
|
-
try {
|
|
53
|
-
parsed = new URL(url);
|
|
54
|
-
} catch {
|
|
55
|
-
return false;
|
|
56
|
-
}
|
|
57
|
-
return parsed.protocol === "https:"
|
|
58
|
-
&& parsed.pathname.endsWith("/responses");
|
|
38
|
+
if (httpUrl === OPENAI_API_RESPONSES_URL) return httpUrl.replace(/^http(s?):/, "ws$1:");
|
|
39
|
+
throw new Error("unsupported Codex WebSocket upstream");
|
|
59
40
|
}
|
|
60
41
|
export type BunRuntimeIdentity = {
|
|
61
42
|
version: string;
|
|
@@ -107,8 +88,14 @@ export function shouldUseCodexWsUpstream(
|
|
|
107
88
|
upstreamWebsocketConfigured = false,
|
|
108
89
|
): boolean {
|
|
109
90
|
if (!bunSupportsBoundedCodexWsRelay(runtime)) return false;
|
|
110
|
-
if (
|
|
111
|
-
|
|
91
|
+
if (socks5ProxyFromEnv()) return false;
|
|
92
|
+
// Bun's client WebSocket API delivers only fully assembled messages and has
|
|
93
|
+
// no enforceable inbound payload limit. Keep arbitrary provider endpoints on
|
|
94
|
+
// bounded HTTP/SSE until the client can reject fragmented text and binary
|
|
95
|
+
// messages during ingestion rather than after allocation. The first-party
|
|
96
|
+
// api.openai.com lane still requires the operator opt-in.
|
|
97
|
+
if (url !== CODEX_RESPONSES_HTTP_URL
|
|
98
|
+
&& !(upstreamWebsocketConfigured && url === OPENAI_API_RESPONSES_URL)) return false;
|
|
112
99
|
if ((init?.method ?? "GET").toUpperCase() !== "POST") return false;
|
|
113
100
|
const body = init?.body;
|
|
114
101
|
if (typeof body !== "string") return false;
|
|
@@ -138,7 +125,8 @@ export function codexWsUpstreamFetch(
|
|
|
138
125
|
const prepared = prepareCodexWsRequest(url, init);
|
|
139
126
|
if (!prepared) return sseFallback(url, prepareCodexHttpInit(url, init));
|
|
140
127
|
init = prepared.httpInit;
|
|
141
|
-
if (
|
|
128
|
+
if ((url !== CODEX_RESPONSES_HTTP_URL && url !== OPENAI_API_RESPONSES_URL)
|
|
129
|
+
|| !bunSupportsBoundedCodexWsRelay(runtime)) {
|
|
142
130
|
return sseFallback(url, init);
|
|
143
131
|
}
|
|
144
132
|
const signal = init.signal ?? undefined;
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import type { TranslatorBudget } from "../lib/translator-budget";
|
|
2
2
|
import { mayBecomePatchEnvelope, normalizeApplyPatchDelimiters } from "../responses/apply-patch-envelope";
|
|
3
3
|
import { compileCodeModeHelperInput, resolveCodeModeHelperName } from "../responses/code-mode-helper-compat";
|
|
4
|
+
import { progressiveFreeformInput } from "../responses/progressive-freeform-input";
|
|
4
5
|
import { declaresCodeModeExec } from "../types/tools";
|
|
5
6
|
import {
|
|
6
7
|
customToolItemId,
|
|
@@ -15,50 +16,10 @@ import {
|
|
|
15
16
|
type SseBlockRewrite,
|
|
16
17
|
} from "./sse-payload-rewrite";
|
|
17
18
|
|
|
18
|
-
/** Exact compact prefix used by our upstream rewriter; progressive matching also
|
|
19
|
-
* tolerates insignificant JSON whitespace via FREEFORM_WRAP_PREFIX_RE. */
|
|
20
|
-
const FREEFORM_WRAP_PREFIX = '{"input":"';
|
|
21
|
-
const FREEFORM_WRAP_PREFIX_RE = /^\s*\{\s*"input"\s*:\s*"/;
|
|
22
|
-
|
|
23
19
|
function isPlainObject(value: unknown): value is Record<string, unknown> {
|
|
24
20
|
return !!value && typeof value === "object" && !Array.isArray(value);
|
|
25
21
|
}
|
|
26
22
|
|
|
27
|
-
/**
|
|
28
|
-
* Progressive decode of the freeform `{ "input": "…" }` wrapper.
|
|
29
|
-
* Returns null when the accumulated text does not (yet) match that wrapper so
|
|
30
|
-
* callers suppress deltas and rely on `response.custom_tool_call_input.done`.
|
|
31
|
-
*/
|
|
32
|
-
function partialCustomToolInput(argumentsText: string): string | null {
|
|
33
|
-
const match = FREEFORM_WRAP_PREFIX_RE.exec(argumentsText);
|
|
34
|
-
if (!match) return null;
|
|
35
|
-
const body = argumentsText.slice(match[0]!.length);
|
|
36
|
-
let output = "";
|
|
37
|
-
for (let index = 0; index < body.length; index++) {
|
|
38
|
-
const char = body[index];
|
|
39
|
-
if (char === '"') break;
|
|
40
|
-
if (char !== "\\") {
|
|
41
|
-
output += char;
|
|
42
|
-
continue;
|
|
43
|
-
}
|
|
44
|
-
const escaped = body[index + 1];
|
|
45
|
-
if (escaped === undefined) break;
|
|
46
|
-
index += 1;
|
|
47
|
-
if (escaped === "n") output += "\n";
|
|
48
|
-
else if (escaped === "t") output += "\t";
|
|
49
|
-
else if (escaped === "r") output += "\r";
|
|
50
|
-
else if (escaped === "b") output += "\b";
|
|
51
|
-
else if (escaped === "f") output += "\f";
|
|
52
|
-
else if (escaped === "u") {
|
|
53
|
-
const hex = body.slice(index + 1, index + 5);
|
|
54
|
-
if (hex.length !== 4 || !/^[0-9a-fA-F]{4}$/.test(hex)) break;
|
|
55
|
-
output += String.fromCharCode(Number.parseInt(hex, 16));
|
|
56
|
-
index += 4;
|
|
57
|
-
} else output += escaped;
|
|
58
|
-
}
|
|
59
|
-
return output;
|
|
60
|
-
}
|
|
61
|
-
|
|
62
23
|
function replaceSseEventName(block: string, type: string): string {
|
|
63
24
|
const newline = block.includes("\r\n") ? "\r\n" : "\n";
|
|
64
25
|
const lines = block.split(/\r?\n/);
|
|
@@ -298,7 +259,7 @@ export function createRoutedCustomToolRestoreBlockRewrite(
|
|
|
298
259
|
releaseCall(upstreamItemId);
|
|
299
260
|
return [replaceSseDataPayload(block, JSON.stringify({
|
|
300
261
|
...parsed,
|
|
301
|
-
input: helper ? compileCodeModeHelperInput(source, helper) : source,
|
|
262
|
+
input: helper ? compileCodeModeHelperInput(source, helper, name) : source,
|
|
302
263
|
}))];
|
|
303
264
|
}
|
|
304
265
|
if (
|
|
@@ -332,21 +293,33 @@ export function createRoutedCustomToolRestoreBlockRewrite(
|
|
|
332
293
|
openCalls.set(upstreamItemId, open);
|
|
333
294
|
// A helper alias will become JavaScript at completion, never raw patch/JSON.
|
|
334
295
|
if (itemNames.get(upstreamItemId)?.aliased) return [];
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
const fullInput =
|
|
296
|
+
const itemName = itemNames.get(upstreamItemId);
|
|
297
|
+
const ownsFreeformGrammar = itemName?.namespace === undefined
|
|
298
|
+
|| itemName?.namespace === "functions";
|
|
299
|
+
const fullInput = progressiveFreeformInput(
|
|
300
|
+
open.argumentsText,
|
|
301
|
+
ownsFreeformGrammar ? itemName?.name ?? "" : "",
|
|
302
|
+
);
|
|
339
303
|
if (fullInput === null) return [];
|
|
340
|
-
//
|
|
341
|
-
//
|
|
342
|
-
//
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
304
|
+
// This routed path historically holds every unrecognized JSON object until done.
|
|
305
|
+
// The shared decoder streams ordinary raw input, so retain the stricter routed rule
|
|
306
|
+
// when no recognized wrapper transformed the accumulated object.
|
|
307
|
+
if (fullInput === open.argumentsText && open.argumentsText.trimStart().startsWith("{")) return [];
|
|
308
|
+
// Hold a buffer that could still become a complete patch envelope, for either of the two
|
|
309
|
+
// reasons completion rewrites one. Both are the same rewind this path forbids, and both
|
|
310
|
+
// mirror `src/bridge/sse.ts`.
|
|
311
|
+
//
|
|
312
|
+
// `exec`: the done event recompiles such a body into an apply_patch helper call.
|
|
313
|
+
// `apply_patch`: `normalizeApplyPatchDelimiters` rewrites a decorated
|
|
314
|
+
// `*** Begin Patch ***` envelope at completion, so the decorated markers would be
|
|
315
|
+
// published and then replaced by the normalized ones. Before the shared decoder, this
|
|
316
|
+
// path held every non-canonical shape and so never reached that case; now that ordinary
|
|
317
|
+
// raw input streams, the second reason has to be stated explicitly.
|
|
318
|
+
const mayCompile = declaresCodeModeExec(declaredNames)
|
|
319
|
+
&& itemName?.namespace === undefined
|
|
320
|
+
&& itemName?.name === "exec";
|
|
321
|
+
const mayNormalize = ownsFreeformGrammar && itemName?.name === "apply_patch";
|
|
322
|
+
if ((mayCompile || mayNormalize) && mayBecomePatchEnvelope(fullInput)) return [];
|
|
350
323
|
if (!fullInput.startsWith(open.emittedInput) || fullInput.length === open.emittedInput.length) return [];
|
|
351
324
|
const inputDelta = fullInput.slice(open.emittedInput.length);
|
|
352
325
|
open.emittedInput = fullInput;
|
|
@@ -381,7 +354,7 @@ export function createRoutedCustomToolRestoreBlockRewrite(
|
|
|
381
354
|
type: nextType,
|
|
382
355
|
item_id: customToolItemId(upstreamItemId),
|
|
383
356
|
input: helper
|
|
384
|
-
? compileCodeModeHelperInput(source, helper)
|
|
357
|
+
? compileCodeModeHelperInput(source, helper, itemName?.name ?? "")
|
|
385
358
|
: unwrapRoutedCustomToolArguments(source, itemName?.name ?? "", itemName?.namespace),
|
|
386
359
|
};
|
|
387
360
|
return [replaceSseDataPayload(replaceSseEventName(block, nextType), JSON.stringify(next))];
|
|
@@ -1,4 +1,9 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import {
|
|
2
|
+
collectAmbiguousDottedAliases,
|
|
3
|
+
dottedAliasIsUnambiguous,
|
|
4
|
+
isSchemaValidResponsesToolName,
|
|
5
|
+
wireToolInnerName,
|
|
6
|
+
} from "../responses/tool-name-aliases";
|
|
2
7
|
import {
|
|
3
8
|
dottedToolName,
|
|
4
9
|
NAMESPACED_BARE_ALIAS_EXCLUDED_NAMES,
|
|
@@ -512,6 +517,31 @@ export function normalizeDefaultNamespaceInItem(
|
|
|
512
517
|
return { value: { ...item, name: bare }, changed: true };
|
|
513
518
|
}
|
|
514
519
|
}
|
|
520
|
+
// Authorization and emission were reading two different resolvers for the same question.
|
|
521
|
+
// `undeclaredNameInItem` resolves an emitted name through `normalizeDeclaredToolName`, which
|
|
522
|
+
// maps a `default.`-prefixed code-mode helper onto the declared `exec` (#4412) as well as a
|
|
523
|
+
// `default.`-prefixed bare tool (#4176); the rewrite above learned only the second case. So a
|
|
524
|
+
// routed Muse turn under a code-mode catalog had `default.view_image` ACCEPTED as `exec` and
|
|
525
|
+
// then relayed under the name the upstream schema rejects. Codex stored
|
|
526
|
+
// `{"type":"function_call","name":"default.view_image"}`, answered "unsupported call", and
|
|
527
|
+
// every later replay of that history — a side chat, a compaction — was refused on
|
|
528
|
+
// `input[N].name` for the lifetime of the conversation (#5095).
|
|
529
|
+
//
|
|
530
|
+
// Emit the name the guard authorized rather than a second, weaker opinion about it. The two
|
|
531
|
+
// must agree: a name good enough to admit is the name the client has to receive.
|
|
532
|
+
//
|
|
533
|
+
// Gated on the name actually being unusable, so this branch cannot touch a name the upstream
|
|
534
|
+
// accepts whatever the resolver would have said about it. The routed-custom-tool and
|
|
535
|
+
// namespace restores run earlier in the same chain and already rewrite the shapes they own,
|
|
536
|
+
// which leaves this as the boundary check for the names no restore claimed.
|
|
537
|
+
if (!isSchemaValidResponsesToolName(name)) {
|
|
538
|
+
const authorized = normalizeDeclaredToolName(name, declared, declaredBare);
|
|
539
|
+
if (
|
|
540
|
+
authorized !== name
|
|
541
|
+
&& declared.has(authorized)
|
|
542
|
+
&& isSchemaValidResponsesToolName(authorized)
|
|
543
|
+
) return { value: { ...item, name: authorized }, changed: true };
|
|
544
|
+
}
|
|
515
545
|
}
|
|
516
546
|
return { value: item, changed: false };
|
|
517
547
|
}
|
|
@@ -2,6 +2,8 @@ import { isCyberPolicyCode, isCyberPolicyMessage } from "../lib/errors";
|
|
|
2
2
|
|
|
3
3
|
export const MAX_CLIENT_SSE_FRAME_BYTES = 4 * 1024 * 1024;
|
|
4
4
|
|
|
5
|
+
export const EMPTY_BYTES = new Uint8Array(0);
|
|
6
|
+
|
|
5
7
|
const LF_LF = Uint8Array.of(10, 10);
|
|
6
8
|
const LF_CR_LF = Uint8Array.of(10, 13, 10);
|
|
7
9
|
const CR_LF_LF = Uint8Array.of(13, 10, 10);
|
|
@@ -155,8 +157,8 @@ function isResponsesTerminalFrame(block: Uint8Array): boolean {
|
|
|
155
157
|
export class BoundedSseFrameBuffer {
|
|
156
158
|
private readonly maxFrameBytes: number;
|
|
157
159
|
private readonly maxFramesPerFeed: number;
|
|
158
|
-
private delimiterTail: Uint8Array =
|
|
159
|
-
private candidate: Uint8Array =
|
|
160
|
+
private delimiterTail: Uint8Array = EMPTY_BYTES;
|
|
161
|
+
private candidate: Uint8Array = EMPTY_BYTES;
|
|
160
162
|
private candidateBytes = 0;
|
|
161
163
|
private disposed = false;
|
|
162
164
|
|
|
@@ -172,8 +174,8 @@ export class BoundedSseFrameBuffer {
|
|
|
172
174
|
}
|
|
173
175
|
|
|
174
176
|
private clear(): void {
|
|
175
|
-
this.delimiterTail =
|
|
176
|
-
this.candidate =
|
|
177
|
+
this.delimiterTail = EMPTY_BYTES;
|
|
178
|
+
this.candidate = EMPTY_BYTES;
|
|
177
179
|
this.candidateBytes = 0;
|
|
178
180
|
}
|
|
179
181
|
|
|
@@ -209,12 +211,12 @@ export class BoundedSseFrameBuffer {
|
|
|
209
211
|
}
|
|
210
212
|
|
|
211
213
|
private takeCandidate(): Uint8Array {
|
|
212
|
-
if (this.candidateBytes === 0) return
|
|
214
|
+
if (this.candidateBytes === 0) return EMPTY_BYTES;
|
|
213
215
|
const block = this.candidate.slice(0, this.candidateBytes);
|
|
214
216
|
// Release the working allocation after each complete frame. This avoids
|
|
215
217
|
// retaining a rare multi-MiB frame allocation for the rest of a long-lived
|
|
216
218
|
// stream; normal small-frame allocation remains bounded by feed's frame cap.
|
|
217
|
-
this.candidate =
|
|
219
|
+
this.candidate = EMPTY_BYTES;
|
|
218
220
|
this.candidateBytes = 0;
|
|
219
221
|
return block;
|
|
220
222
|
}
|
|
@@ -225,7 +227,7 @@ export class BoundedSseFrameBuffer {
|
|
|
225
227
|
|
|
226
228
|
const frames: BoundedSseFrame[] = [];
|
|
227
229
|
const previousTail = this.delimiterTail;
|
|
228
|
-
this.delimiterTail =
|
|
230
|
+
this.delimiterTail = EMPTY_BYTES;
|
|
229
231
|
const tailLength = previousTail.byteLength;
|
|
230
232
|
const totalLength = tailLength + chunk.byteLength;
|
|
231
233
|
const byteAt = (index: number): number => index < tailLength
|
|
@@ -286,10 +288,10 @@ export class BoundedSseFrameBuffer {
|
|
|
286
288
|
|
|
287
289
|
/** Return the final unterminated block bytes and release all retained state. */
|
|
288
290
|
finish(): Uint8Array {
|
|
289
|
-
if (this.disposed) return
|
|
291
|
+
if (this.disposed) return EMPTY_BYTES;
|
|
290
292
|
try {
|
|
291
293
|
this.retain(this.delimiterTail);
|
|
292
|
-
this.delimiterTail =
|
|
294
|
+
this.delimiterTail = EMPTY_BYTES;
|
|
293
295
|
return this.takeCandidate();
|
|
294
296
|
} finally {
|
|
295
297
|
this.clear();
|
|
@@ -307,7 +309,7 @@ export class BoundedSseFrameBuffer {
|
|
|
307
309
|
export function joinSseFrameBytes(parts: readonly Uint8Array[]): Uint8Array {
|
|
308
310
|
let byteLength = 0;
|
|
309
311
|
for (const part of parts) byteLength += part.byteLength;
|
|
310
|
-
if (byteLength === 0) return
|
|
312
|
+
if (byteLength === 0) return EMPTY_BYTES;
|
|
311
313
|
if (parts.length === 1 && parts[0]!.byteLength === byteLength) return parts[0]!;
|
|
312
314
|
const joined = new Uint8Array(byteLength);
|
|
313
315
|
let offset = 0;
|
|
@@ -178,17 +178,44 @@ export function createSseBlockBuffer(
|
|
|
178
178
|
|
|
179
179
|
/** Join all data lines from one SSE event according to the event-stream field rules. */
|
|
180
180
|
export function sseDataPayload(block: string): string | null {
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
181
|
+
let result = "";
|
|
182
|
+
let found = false;
|
|
183
|
+
let lineStart = 0;
|
|
184
|
+
const len = block.length;
|
|
185
|
+
|
|
186
|
+
while (lineStart < len) {
|
|
187
|
+
const nextNewline = block.indexOf("\n", lineStart);
|
|
188
|
+
let lineEnd = nextNewline === -1 ? len : nextNewline;
|
|
189
|
+
const nextStart = nextNewline === -1 ? len : nextNewline + 1;
|
|
190
|
+
if (lineEnd > lineStart && block.charCodeAt(lineEnd - 1) === 13) {
|
|
191
|
+
lineEnd -= 1;
|
|
186
192
|
}
|
|
187
|
-
|
|
188
|
-
const
|
|
189
|
-
|
|
193
|
+
|
|
194
|
+
const lineLen = lineEnd - lineStart;
|
|
195
|
+
if (lineLen === 4 && block.startsWith("data", lineStart)) {
|
|
196
|
+
if (found) {
|
|
197
|
+
result += "\n";
|
|
198
|
+
} else {
|
|
199
|
+
found = true;
|
|
200
|
+
}
|
|
201
|
+
} else if (lineLen >= 5 && block.startsWith("data:", lineStart)) {
|
|
202
|
+
let valueStart = lineStart + 5;
|
|
203
|
+
if (valueStart < lineEnd && block.charCodeAt(valueStart) === 32) {
|
|
204
|
+
valueStart += 1;
|
|
205
|
+
}
|
|
206
|
+
const value = block.slice(valueStart, lineEnd);
|
|
207
|
+
if (found) {
|
|
208
|
+
result += "\n" + value;
|
|
209
|
+
} else {
|
|
210
|
+
result = value;
|
|
211
|
+
found = true;
|
|
212
|
+
}
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
lineStart = nextStart;
|
|
190
216
|
}
|
|
191
|
-
|
|
217
|
+
|
|
218
|
+
return found ? result : null;
|
|
192
219
|
}
|
|
193
220
|
|
|
194
221
|
/** Replace an SSE event's data field while preserving non-data fields and newline style. */
|
|
@@ -203,7 +230,7 @@ export function replaceSseDataPayload(block: string, payload: string): string {
|
|
|
203
230
|
continue;
|
|
204
231
|
}
|
|
205
232
|
if (!replaced) {
|
|
206
|
-
rewritten.push(`data: ${
|
|
233
|
+
rewritten.push(...payload.split(/\r?\n/).map(line => `data: ${line}`));
|
|
207
234
|
replaced = true;
|
|
208
235
|
}
|
|
209
236
|
}
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { accessSync, constants, readFileSync, writeFileSync, unlinkSync, mkdirSync, statSync } from "node:fs";
|
|
2
2
|
import { delimiter, join } from "node:path";
|
|
3
3
|
import { getConfigDir } from "../config";
|
|
4
|
-
import { resolveAutoContext, type AutoContextMode } from "../claude/context-windows";
|
|
4
|
+
import { claudeToolSearchEnv, resolveAutoContext, type AutoContextMode } from "../claude/context-windows";
|
|
5
5
|
import { PROXY_MARKER, defaultAuthDetectDeps, detectClaudeAuth, ownAdmissionTokens, type AuthDetectDeps } from "../claude/auth-detect";
|
|
6
6
|
import { resolveClaudeAuthMode } from "../claude/auth-mode";
|
|
7
7
|
import { ANTHROPIC_PARENT_ENV_SLOTS, trustedNodeLauncherContext, type AnthropicParentEnvSlot } from "../cli/launcher-context";
|
|
@@ -128,6 +128,10 @@ export function writeShellEnvFile(
|
|
|
128
128
|
if (config.claudeCode?.alwaysEnableEffort === true) {
|
|
129
129
|
lines.push(conditional("CLAUDE_CODE_ALWAYS_ENABLE_EFFORT", "1"));
|
|
130
130
|
}
|
|
131
|
+
// Tool-search deferral (#4838): opt-in, conditional like every other lever so a
|
|
132
|
+
// value already exported in the user's shell wins.
|
|
133
|
+
const toolSearch = claudeToolSearchEnv(config.claudeCode?.toolSearch);
|
|
134
|
+
if (toolSearch !== undefined) lines.push(conditional("ENABLE_TOOL_SEARCH", toolSearch));
|
|
131
135
|
const shellEnvPath = getShellEnvFilePath();
|
|
132
136
|
recordOwnedConfigPath(getConfigDir(), shellEnvPath);
|
|
133
137
|
mkdirSync(getConfigDir(), { recursive: true, mode: 0o700 });
|
package/src/server/system-env.ts
CHANGED
|
@@ -2,7 +2,7 @@ import { execFileSync } from "node:child_process";
|
|
|
2
2
|
import { readFileSync, writeFileSync, unlinkSync, mkdirSync } from "node:fs";
|
|
3
3
|
import { join } from "node:path";
|
|
4
4
|
import { getConfigDir } from "../config";
|
|
5
|
-
import { resolveAutoContext, type AutoContextMode } from "../claude/context-windows";
|
|
5
|
+
import { claudeToolSearchEnv, resolveAutoContext, type AutoContextMode } from "../claude/context-windows";
|
|
6
6
|
import { PROXY_MARKER } from "../claude/auth-detect";
|
|
7
7
|
import { isProxyAdmissionSecret } from "./auth-cors";
|
|
8
8
|
import type { OcxConfig } from "../types";
|
|
@@ -33,6 +33,7 @@ const MANAGED_SYSTEM_ENV_NAMES = new Set<string>([
|
|
|
33
33
|
"DISABLE_COMPACT",
|
|
34
34
|
"CLAUDE_CODE_AUTO_COMPACT_WINDOW",
|
|
35
35
|
"CLAUDE_CODE_ALWAYS_ENABLE_EFFORT",
|
|
36
|
+
"ENABLE_TOOL_SEARCH",
|
|
36
37
|
]);
|
|
37
38
|
|
|
38
39
|
interface SystemEnvTracking {
|
|
@@ -309,6 +310,11 @@ export async function injectSystemEnv(
|
|
|
309
310
|
if (config.claudeCode?.alwaysEnableEffort === true) {
|
|
310
311
|
injectLever("CLAUDE_CODE_ALWAYS_ENABLE_EFFORT", "1");
|
|
311
312
|
}
|
|
313
|
+
// Tool-search deferral (#4838): same opt-in contract as `ocx claude`, so a machine
|
|
314
|
+
// whose Claude Code is launched from Dock/launchd gets the setting it configured
|
|
315
|
+
// instead of only terminal sessions. injectLever keeps a user-owned launchd value.
|
|
316
|
+
const toolSearch = claudeToolSearchEnv(config.claudeCode?.toolSearch);
|
|
317
|
+
if (toolSearch !== undefined) injectLever("ENABLE_TOOL_SEARCH", toolSearch);
|
|
312
318
|
|
|
313
319
|
// Shell-hook env file: works for new shells in already-running Terminal.app.
|
|
314
320
|
writeShellEnvFile(port, config, modelEnv, auto, deps);
|