@bitkyc08/opencodex 2.65.0 → 2.66.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 +14 -0
- package/gui/dist/assets/App-DyHlFeNB.js +51 -0
- package/gui/dist/assets/App-gkoJIEOV.css +1 -0
- package/gui/dist/assets/{Tray-D4bA1vff.js → Tray-CI8cVKWu.js} +1 -1
- package/gui/dist/assets/index-BiwnVAUm.js +86 -0
- package/gui/dist/assets/index-ComVsdSk.css +1 -0
- package/gui/dist/assets/{tray-data-BZXhB3qz.js → tray-data-_WyYzbMt.js} +1 -1
- package/gui/dist/index.html +2 -2
- package/package.json +1 -2
- package/src/adapters/anthropic/beta-allowlist.ts +80 -0
- package/src/adapters/anthropic/passthrough.ts +221 -0
- package/src/adapters/anthropic-image-codec.ts +56 -19
- package/src/adapters/anthropic-image-normalize.ts +20 -10
- package/src/adapters/anthropic.ts +55 -24
- package/src/adapters/coding-agent/protocol.ts +29 -19
- package/src/adapters/cursor/live-transport.ts +18 -3
- package/src/adapters/cursor/native-exec-shell.ts +18 -63
- package/src/adapters/cursor/native-exec.ts +5 -2
- package/src/adapters/cursor/native-foreground-shell.ts +44 -0
- package/src/adapters/devin/cloud-direct/chat.ts +7 -1
- package/src/adapters/exec-tool-result-normalize.ts +4 -1
- package/src/adapters/google.ts +12 -0
- package/src/adapters/ollama-native.ts +32 -1
- package/src/adapters/openai-chat/serialized-tool-call-content.ts +131 -34
- package/src/adapters/openai-chat.ts +6 -4
- package/src/adapters/openai-responses/passthrough.ts +1 -0
- package/src/adapters/openai-responses/reasoning.ts +45 -1
- package/src/bridge/response-json.ts +10 -2
- package/src/chat/outbound.ts +104 -52
- package/src/claude/claude-code-block.ts +16 -0
- package/src/claude/context-windows.ts +52 -4
- package/src/claude/desktop-first-party.ts +25 -8
- package/src/claude/inbound.ts +22 -0
- package/src/claude/intercept/connect-proxy.ts +37 -4
- package/src/claude/intercept/proxy-auth.ts +166 -0
- package/src/claude/intercept/runtime.ts +21 -0
- package/src/claude/intercept/settings.ts +33 -12
- package/src/claude/outbound.ts +45 -27
- package/src/cli/account-extended.ts +47 -15
- package/src/cli/account-target.ts +115 -0
- package/src/cli/account.ts +20 -28
- package/src/cli/agent.ts +10 -1
- package/src/cli/api-protocols.ts +206 -0
- package/src/cli/capabilities.ts +86 -0
- package/src/cli/combo.ts +2 -2
- package/src/cli/connect.ts +127 -3
- package/src/cli/dispatch.ts +8 -0
- package/src/cli/doctor.ts +33 -0
- package/src/cli/help.ts +2 -0
- package/src/cli/link.ts +288 -0
- package/src/cli/registry.ts +23 -0
- package/src/cli/runtime-api.ts +79 -0
- package/src/cli/system-command.ts +21 -2
- package/src/client/connect.ts +125 -23
- package/src/client/hub-relay.ts +12 -8
- package/src/client/link-join.ts +323 -0
- package/src/client/link-relay.ts +241 -0
- package/src/client/link-state.ts +110 -0
- package/src/client/link-teardown.ts +63 -0
- package/src/client/link-tunnel.ts +465 -0
- package/src/client/machine-listener.ts +14 -5
- package/src/client/runtime.ts +91 -42
- package/src/client/state.ts +4 -0
- package/src/codex/catalog/build-entries.ts +4 -0
- package/src/codex/catalog/derive-entry.ts +5 -0
- package/src/codex/catalog/parsing.ts +4 -0
- package/src/codex/catalog/retained-sync.ts +4 -1
- package/src/codex/desktop-switches.ts +88 -8
- package/src/codex/inject/plan.ts +16 -3
- package/src/codex/inject.ts +3 -0
- package/src/codex/internal/catalog-writer.ts +13 -2
- package/src/combos/failover.ts +3 -2
- package/src/combos/index.ts +12 -0
- package/src/combos/jev.ts +646 -0
- package/src/combos/request.ts +5 -0
- package/src/combos/resolve.ts +17 -15
- package/src/combos/types.ts +47 -3
- package/src/config/atomic-write.ts +3 -0
- package/src/config/live-reconcile.ts +20 -4
- package/src/config/persist-unlocked.ts +20 -7
- package/src/config/schema/config-schema.ts +16 -0
- package/src/config/schema/leaf-validators.ts +38 -1
- package/src/generated/compatibility-version.json +416 -128
- package/src/integrations/omp-yaml-source.ts +109 -4
- package/src/lab/automation/orchestrator.ts +60 -4
- package/src/lab/events/limits.ts +1 -1
- package/src/lib/bounded-body.ts +55 -38
- package/src/lib/config-ownership.ts +11 -22
- package/src/lib/local-desktop-snapshot-capability.ts +56 -0
- package/src/lib/local-management-capability.ts +25 -3
- package/src/lib/optional-shutdown-hooks.ts +17 -0
- package/src/lib/state-store-registrations.ts +2 -2
- package/src/lib/windows-elevation.ts +62 -25
- package/src/lib/windows-secret-acl.ts +114 -23
- package/src/link/admission-wait.ts +73 -0
- package/src/link/compensation.ts +100 -0
- package/src/link/fingerprint.ts +21 -0
- package/src/link/paths.ts +19 -0
- package/src/link/ports.ts +12 -0
- package/src/link/routes.ts +32 -0
- package/src/link/ssh-argv.ts +170 -0
- package/src/link/ssh-config.ts +152 -0
- package/src/link/ssh-runner.ts +161 -0
- package/src/link/status-projection.ts +96 -0
- package/src/link/store.ts +139 -0
- package/src/link/supervisor.ts +404 -0
- package/src/link/tunnel-state.ts +91 -0
- package/src/protocols/baseline.ts +88 -0
- package/src/protocols/codecs/chat.ts +17 -0
- package/src/protocols/codecs/messages.ts +18 -0
- package/src/protocols/codecs/responses.ts +17 -0
- package/src/protocols/contract.ts +185 -0
- package/src/protocols/dto.ts +267 -0
- package/src/protocols/encoders/adapter-events.ts +876 -0
- package/src/protocols/encoders/chat.ts +243 -0
- package/src/protocols/encoders/messages.ts +462 -0
- package/src/protocols/envelope.ts +43 -0
- package/src/protocols/features.ts +295 -0
- package/src/protocols/guard.ts +33 -0
- package/src/protocols/opaque-state.ts +132 -0
- package/src/protocols/path.ts +39 -0
- package/src/protocols/plan-snapshot.ts +226 -0
- package/src/protocols/plan.ts +192 -0
- package/src/protocols/provider-summary.ts +101 -0
- package/src/protocols/settings.ts +118 -0
- package/src/protocols/shadow-plan.ts +36 -0
- package/src/protocols/shadow.ts +62 -0
- package/src/protocols/trace.ts +335 -0
- package/src/providers/model-rename-migration.ts +3 -3
- package/src/providers/registry/entries-core.ts +6 -1
- package/src/providers/registry/entries-extended.ts +15 -0
- package/src/providers/registry/model-ids.ts +1 -0
- package/src/providers/registry/types.ts +6 -0
- package/src/providers/registry.ts +1 -1
- package/src/remote-control/workspace-hub.ts +5 -4
- package/src/responses/apply-patch-envelope.ts +6 -1
- package/src/responses/code-mode-shell-input.ts +4 -2
- package/src/responses/freeform-wrapper-scan.ts +51 -24
- package/src/responses/progressive-freeform-input.ts +14 -20
- package/src/responses/spill-store.ts +187 -33
- package/src/responses/state/snapshot-select.ts +49 -0
- package/src/responses/state/spill-inspect.ts +46 -0
- package/src/responses/state/spill-queue.ts +16 -0
- package/src/responses/state.ts +51 -27
- package/src/server/audio-client.ts +8 -3
- package/src/server/audio-upstream.ts +9 -4
- package/src/server/auth-cors.ts +25 -8
- package/src/server/chat-completions.ts +71 -11
- package/src/server/chat-native-eligibility.ts +74 -0
- package/src/server/chat-native.ts +139 -74
- package/src/server/claude-messages.ts +140 -22
- package/src/server/hub-usage.ts +4 -3
- package/src/server/index/link-listener.ts +195 -0
- package/src/server/index/optional-listeners.ts +115 -0
- package/src/server/index/serve-options.ts +44 -10
- package/src/server/index.ts +10 -9
- package/src/server/inference/attempt.ts +51 -0
- package/src/server/inference/client-encoder-delivery.ts +235 -0
- package/src/server/inference/client-wire-log.ts +49 -0
- package/src/server/inference/client-wire.ts +69 -0
- package/src/server/inference/context.ts +15 -0
- package/src/server/inference/final-log.ts +37 -0
- package/src/server/local-desktop-snapshot-auth.ts +57 -0
- package/src/server/management/agent-settings-routes.ts +15 -13
- package/src/server/management/api-access.ts +11 -1
- package/src/server/management/config-routes.ts +3 -6
- package/src/server/management/context.ts +25 -0
- package/src/server/management/link-routes.ts +535 -0
- package/src/server/management/logs-usage-routes.ts +31 -1
- package/src/server/management/native-integration-routes.ts +6 -11
- package/src/server/management/oauth-account-routes.ts +42 -16
- package/src/server/management/protocol-routes.ts +159 -0
- package/src/server/management/protocol-settings-patch.ts +145 -0
- package/src/server/management/provider-patch-transaction.ts +61 -0
- package/src/server/management/provider-routes.ts +39 -18
- package/src/server/management/route-registry.ts +12 -0
- package/src/server/management/sidebar-routes.ts +8 -3
- package/src/server/management/system-routes.ts +18 -1
- package/src/server/management/usage-aggregate-cache.ts +206 -6
- package/src/server/management-api.ts +26 -1
- package/src/server/management-auth.ts +19 -5
- package/src/server/messages-native-eligibility.ts +161 -0
- package/src/server/messages-native-oauth.ts +149 -0
- package/src/server/messages-native.ts +790 -0
- package/src/server/relay.ts +9 -0
- package/src/server/request-log-filter.ts +92 -0
- package/src/server/request-log.ts +31 -70
- package/src/server/responses/adapter-delivery.ts +43 -16
- package/src/server/responses/agent-task-recovery.ts +90 -22
- package/src/server/responses/codex-ws-exchange.ts +4 -0
- package/src/server/responses/codex-ws-wire.ts +10 -1
- package/src/server/responses/core-combo-native.ts +323 -0
- package/src/server/responses/core-combo.ts +224 -13
- package/src/server/responses/core-options.ts +22 -0
- package/src/server/responses/core.ts +14 -26
- package/src/server/responses/request-sidecar-auth.ts +8 -3
- package/src/server/responses/run-turn-execution.ts +257 -63
- package/src/server/responses/sidecar-execution.ts +7 -4
- package/src/server/system-env.ts +7 -0
- package/src/service/diagnostics.ts +7 -1
- package/src/service/launchd.ts +26 -37
- package/src/service/windows-ops.ts +24 -20
- package/src/types/config.ts +58 -1
- package/src/types.ts +2 -0
- package/src/update/transactional-install.d.mts +0 -1
- package/src/update/transactional-install.mjs +17 -17
- package/src/usage/jev-stats.ts +495 -0
- package/src/usage/log.ts +24 -3
- package/src/web-search/run-turn-loop.ts +568 -0
- package/gui/dist/assets/App-8NMiZxT0.css +0 -1
- package/gui/dist/assets/App-DGLte6IR.js +0 -50
- package/gui/dist/assets/index--EWgGQvZ.css +0 -1
- package/gui/dist/assets/index-Bi1K37Wl.js +0 -86
|
@@ -39,6 +39,18 @@ export interface ConsumedComboFailure {
|
|
|
39
39
|
|
|
40
40
|
|
|
41
41
|
|
|
42
|
+
/**
|
|
43
|
+
* Direct client encoding for a translated Chat or Messages turn (PF-09). `model` is the model
|
|
44
|
+
* string the client asked for, which every client frame carries; `inputTokenFloor` is the
|
|
45
|
+
* Messages prompt estimate `message_start` reports when no usage arrived first (#4857).
|
|
46
|
+
*/
|
|
47
|
+
export interface ClientEncoderOption {
|
|
48
|
+
protocol: "chat" | "messages";
|
|
49
|
+
stream: boolean;
|
|
50
|
+
model: string;
|
|
51
|
+
inputTokenFloor?: number;
|
|
52
|
+
}
|
|
53
|
+
|
|
42
54
|
export interface HandleResponsesOptions {
|
|
43
55
|
/** Internal Claude replay identity; consumed only by the final canonical Go transport. */
|
|
44
56
|
claudeGoAffinity?: { sessionLane?: string };
|
|
@@ -94,6 +106,8 @@ export interface HandleResponsesOptions {
|
|
|
94
106
|
* it. Omitted means a genuine Responses inbound.
|
|
95
107
|
*/
|
|
96
108
|
inboundWire?: InboundWire;
|
|
109
|
+
/** PF-07: the Chat source a combo child may send natively; set only by the Chat ingress. */
|
|
110
|
+
protocolSource?: import("./core-combo-native").ComboProtocolSource;
|
|
97
111
|
/** Internal transport identity for route-scoped upstream compatibility policy. */
|
|
98
112
|
inboundTransport?: "websocket";
|
|
99
113
|
/**
|
|
@@ -147,6 +161,14 @@ export interface HandleResponsesOptions {
|
|
|
147
161
|
* rebuilds headers and carries the fact through this flag.
|
|
148
162
|
*/
|
|
149
163
|
visionDescribeTerminal?: boolean;
|
|
164
|
+
/**
|
|
165
|
+
* Set only by the Chat and Messages ingresses when `protocols.rollout.directEncoders` is on and
|
|
166
|
+
* the settled route is one non-Responses target. Adapter delivery then encodes the adapter
|
|
167
|
+
* events straight into the client's wire and returns a response marked with `markClientWire`.
|
|
168
|
+
* Passthrough, run-turn adapters, combo children and compaction turns ignore it and keep
|
|
169
|
+
* returning a Responses body, which the ingress converts as before.
|
|
170
|
+
*/
|
|
171
|
+
clientEncoder?: ClientEncoderOption;
|
|
150
172
|
}
|
|
151
173
|
|
|
152
174
|
/** Values shared by the call, not a bag of mutable pipeline state. */
|
|
@@ -9,8 +9,7 @@ import type {
|
|
|
9
9
|
import { createTranslatorBudget } from "../../lib/translator-budget";
|
|
10
10
|
import { captureExplicitOpenAiCallerAuth } from "../../providers/openai-sidecar";
|
|
11
11
|
import { captureCallerDirectAuth } from "../../providers/caller-authorization";
|
|
12
|
-
import {
|
|
13
|
-
import { attachRequestSpendTracker } from "./request-spend";
|
|
12
|
+
import { createInferenceSendBudget } from "../inference/context";
|
|
14
13
|
import { finalizeOwnedTranslatorBudget } from "./core-lifetime";
|
|
15
14
|
import type { TranslatorBudget } from "../../lib/translator-budget";
|
|
16
15
|
import { executeComboResponses } from "./core-combo";
|
|
@@ -31,7 +30,6 @@ import { releaseCodexAuthContextProbeLease } from "../../codex/auth-context";
|
|
|
31
30
|
|
|
32
31
|
/** Public Responses entry and compatibility exports. Implementations live with their owners. */
|
|
33
32
|
|
|
34
|
-
|
|
35
33
|
/**
|
|
36
34
|
* Route one `/v1/responses` request through the adapter pipeline: recovery loop, passthrough
|
|
37
35
|
* wire, image/web-search bridges, and the terminal-guard continuation.
|
|
@@ -58,7 +56,7 @@ export async function handleResponses(
|
|
|
58
56
|
|| req.headers.get("x-opencodex-vision-describe") === "1",
|
|
59
57
|
translatorBudget,
|
|
60
58
|
// Once at ingress, spend observer included: a combo child inherits the parent's holder.
|
|
61
|
-
sendBudget: options.sendBudget ??
|
|
59
|
+
sendBudget: options.sendBudget ?? createInferenceSendBudget(req, logCtx),
|
|
62
60
|
});
|
|
63
61
|
return ownsBudget ? finalizeOwnedTranslatorBudget(response, translatorBudget) : response;
|
|
64
62
|
} catch (error) {
|
|
@@ -182,29 +180,19 @@ async function handleResponsesInner(
|
|
|
182
180
|
const requestDispatchers: ResponsesDispatchers = { handleResponses, handleComboResponses };
|
|
183
181
|
|
|
184
182
|
export { adapterNeedsForcedContinuation } from "./core-replay";
|
|
185
|
-
export {
|
|
186
|
-
|
|
183
|
+
export {
|
|
184
|
+
sidecarOutcomeRecorder, codexLogAccountId, usesCodexForwardPoolAuth, preAuthUpstreamHostCircuitKey,
|
|
185
|
+
upstreamHostCircuitOpenResponse, shouldRetryCodexPoolAccountQuota, shouldRetryCodexScopedQuotaOnAlternate,
|
|
186
|
+
shouldRetryCodexPoolAccountTransient, codexAccountGatedCanonicalWireModel, codexForwardTerminalOutcomeRecorder,
|
|
187
|
+
} from "./core-codex-account";
|
|
187
188
|
export { shouldAttemptOpaqueBlobRecovery } from "./core-opaque-recovery";
|
|
188
|
-
export { readDisplaySafeErrorText } from "./core-errors";
|
|
189
|
-
export {
|
|
190
|
-
export {
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
export {
|
|
195
|
-
export { codexForwardTerminalOutcomeRecorder } from "./core-codex-account";
|
|
196
|
-
export { decodeRequestErrorResponse } from "./core-errors";
|
|
197
|
-
export { comboUnavailableResponse } from "./core-errors";
|
|
198
|
-
export type { ConsumedComboFailure } from "./core-options";
|
|
199
|
-
export type { HandleResponsesOptions } from "./core-options";
|
|
200
|
-
export { clientCancelledResponse } from "./core-errors";
|
|
201
|
-
export { sanitizedRetryAfter } from "./core-combo-failure";
|
|
202
|
-
export { consumeComboFailure } from "./core-combo-failure";
|
|
203
|
-
export { usageFromComboFailureText } from "./core-combo-failure";
|
|
204
|
-
export { createChildPassthroughCallbackGate } from "./core-combo-failure";
|
|
205
|
-
export { buildComboChildHeaders } from "./core-combo-failure";
|
|
206
|
-
export { UPSTREAM_JSON_BODY_READ_OPTIONS } from "./core-lifetime";
|
|
189
|
+
export { readDisplaySafeErrorText, decodeRequestErrorResponse, comboUnavailableResponse, clientCancelledResponse } from "./core-errors";
|
|
190
|
+
export type { ConsumedComboFailure, HandleResponsesOptions } from "./core-options";
|
|
191
|
+
export {
|
|
192
|
+
sanitizedRetryAfter, consumeComboFailure, usageFromComboFailureText, createChildPassthroughCallbackGate,
|
|
193
|
+
buildComboChildHeaders,
|
|
194
|
+
} from "./core-combo-failure";
|
|
195
|
+
export { UPSTREAM_JSON_BODY_READ_OPTIONS, linkAbortSignal } from "./core-lifetime";
|
|
207
196
|
export { poolCredentialRefreshIncompleteResponse } from "./core-auth";
|
|
208
197
|
export { applyServiceTierGate } from "./core-normalize";
|
|
209
|
-
export { linkAbortSignal } from "./core-lifetime";
|
|
210
198
|
export { DEFAULT_SHADOW_SOURCE_MODELS, isShadowSourceModel, shadowSourceModels } from "../../lib/shadow-call";
|
|
@@ -53,9 +53,14 @@ export async function prepareResponsesSidecarAuth(
|
|
|
53
53
|
&& (!isCanonicalOpenAiForwardProvider(route.provider) || parsed._portableCompaction === true);
|
|
54
54
|
const needsOpenAiVision = !visionDescribeTerminal
|
|
55
55
|
&& shouldResolveOpenAiVisionSidecar(config, route.provider, route.modelId, parsed, route.providerName);
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
56
|
+
// LOCAL PATCH (runturn-websearch): runTurn adapters can run the web-search
|
|
57
|
+
// sidecar through src/web-search/run-turn-loop.ts, so resolve the helper
|
|
58
|
+
// credential for them too. The passthrough bridge stays fetch-only.
|
|
59
|
+
const needsOpenAiSearch = !routedCompaction
|
|
60
|
+
&& (transportState.adapter.runTurn
|
|
61
|
+
? shouldResolveOpenAiWebSearchSidecar(config, parsed, isPassthrough)
|
|
62
|
+
: (shouldResolveOpenAiWebSearchSidecar(config, parsed, isPassthrough)
|
|
63
|
+
|| shouldResolveOpenAiPassthroughWebSearchBridge(route.provider, parsed, isPassthrough)));
|
|
59
64
|
if (needsOpenAiVision || needsOpenAiSearch) {
|
|
60
65
|
try {
|
|
61
66
|
const candidates = listOpenAiForwardSidecarCandidates(config);
|
|
@@ -31,6 +31,8 @@ import {
|
|
|
31
31
|
import { resolveWireProtocolOverride } from "../adapter-resolve";
|
|
32
32
|
import { formatErrorResponse, bridgeToResponsesSSE, buildResponseJSON } from "../../bridge";
|
|
33
33
|
import { redactSecretString } from "../../lib/redact";
|
|
34
|
+
import { jsonUtf8Bytes } from "../../lib/json-byte-size";
|
|
35
|
+
import { isTranslatorBudgetExceededError } from "../../lib/translator-budget";
|
|
34
36
|
import {
|
|
35
37
|
guardEmptyCompletionEventStream,
|
|
36
38
|
observeEmptyCompletion,
|
|
@@ -40,6 +42,28 @@ import { rememberResponseState } from "../../responses/state";
|
|
|
40
42
|
import { trackStreamLifetime } from "../lifecycle";
|
|
41
43
|
import { awaitThoughtSignatureDurability } from "../../responses/thought-signature-replay";
|
|
42
44
|
import { undeclaredToolCallMessage } from "../responses-undeclared-tool-guard";
|
|
45
|
+
// LOCAL PATCH (runturn-websearch)
|
|
46
|
+
import { planWebSearch } from "../../web-search";
|
|
47
|
+
import { runTurnWebSearchInitialParsed, runTurnWebSearchLoop } from "../../web-search/run-turn-loop";
|
|
48
|
+
import { WEB_SEARCH_TOOL_NAME } from "../../web-search/synthetic-tool";
|
|
49
|
+
|
|
50
|
+
// LOCAL PATCH (runturn-websearch): top-level fields route binding or the
|
|
51
|
+
// adapter itself may write during a turn. Iteration-local `turnParsed` objects
|
|
52
|
+
// are shallow copies of `parsed`, so these are mirrored both directions around
|
|
53
|
+
// each runTurn dispatch — clones would otherwise keep stale route state and
|
|
54
|
+
// adapter-written values (e.g. Cursor's conversation id) would be lost.
|
|
55
|
+
const RUNTURN_WS_ROUTE_STATE_KEYS = [
|
|
56
|
+
"_cursorIdentityScope",
|
|
57
|
+
"_cursorConversationId",
|
|
58
|
+
"_cursorClientThreadId",
|
|
59
|
+
"_kiroAuthContext",
|
|
60
|
+
"_providerContinuation",
|
|
61
|
+
"_providerContinuationOwner",
|
|
62
|
+
"_providerContinuationCandidate",
|
|
63
|
+
"_stripReasoningEncryptedContent",
|
|
64
|
+
"_dropForeignReasoningItemIds",
|
|
65
|
+
"_reasoningReplayScope",
|
|
66
|
+
] as const;
|
|
43
67
|
|
|
44
68
|
/** One responsibility of the Responses request pipeline; state owners are explicit. */
|
|
45
69
|
export async function executeResponsesRunTurn(
|
|
@@ -71,7 +95,7 @@ export async function executeResponsesRunTurn(
|
|
|
71
95
|
| "noteRoutedAttemptSend"
|
|
72
96
|
| "bindKeyUsageFromBridge"
|
|
73
97
|
>,
|
|
74
|
-
sidecarState: Pick<ResponsesSidecarAuth, "routedCompaction">,
|
|
98
|
+
sidecarState: Pick<ResponsesSidecarAuth, "routedCompaction" | "openAiSidecar">,
|
|
75
99
|
responseEffects: Pick<
|
|
76
100
|
ResponsesEffects,
|
|
77
101
|
| "cancelResponseCompletion"
|
|
@@ -121,6 +145,31 @@ export async function executeResponsesRunTurn(
|
|
|
121
145
|
} = responseEffects;
|
|
122
146
|
const { routedCompaction } = sidecarState;
|
|
123
147
|
|
|
148
|
+
// LOCAL PATCH (runturn-websearch): resolving the OpenAI search credential can
|
|
149
|
+
// hold the account's sole cooldown-recovery probe lease. The fetch path hands
|
|
150
|
+
// it back through the bridge's onFinalize; runTurn owns no such hook, so this
|
|
151
|
+
// turn releases it on every exit — normal end, error, cancel, and every
|
|
152
|
+
// pre-response failure below. After an executed search the outcome recorder
|
|
153
|
+
// already settled the lease, making each release a generation-bound no-op.
|
|
154
|
+
const releaseSearchProbeLease = (): void => {
|
|
155
|
+
sidecarState.openAiSidecar?.releaseProbeLease?.();
|
|
156
|
+
};
|
|
157
|
+
// When Codex declared hosted web_search and a sidecar plan resolves, drive
|
|
158
|
+
// the routed model through the same search interception the fetch-path loop
|
|
159
|
+
// runs — injected as a function tool, calls intercepted, results appended to
|
|
160
|
+
// the message history between runTurn dispatches.
|
|
161
|
+
let wsPlan: ReturnType<typeof planWebSearch>;
|
|
162
|
+
try {
|
|
163
|
+
wsPlan = !routedCompaction
|
|
164
|
+
? planWebSearch(config, parsed, false, route.provider, route.modelId, sidecarState.openAiSidecar, {
|
|
165
|
+
admission: options.admission, codexAuthPolicy: options.codexAuthPolicy, providerName: route.providerName,
|
|
166
|
+
})
|
|
167
|
+
: undefined;
|
|
168
|
+
} catch (error) {
|
|
169
|
+
releaseSearchProbeLease();
|
|
170
|
+
throw error;
|
|
171
|
+
}
|
|
172
|
+
|
|
124
173
|
const runTurnAbort = new AbortController();
|
|
125
174
|
const cleanupRunTurnAbort = linkAbortSignal(runTurnAbort, options.abortSignal);
|
|
126
175
|
const queue = createAdapterEventQueue({
|
|
@@ -140,6 +189,7 @@ export async function executeResponsesRunTurn(
|
|
|
140
189
|
} catch (error) {
|
|
141
190
|
cleanupRunTurnAbort();
|
|
142
191
|
queue.close();
|
|
192
|
+
releaseSearchProbeLease();
|
|
143
193
|
throw error;
|
|
144
194
|
}
|
|
145
195
|
// One attempt of the runTurn transport, against an explicit queue. The
|
|
@@ -147,16 +197,35 @@ export async function executeResponsesRunTurn(
|
|
|
147
197
|
// same forwarded headers, same abort signal) through a fresh queue, so the
|
|
148
198
|
// attempt body must not capture the first queue. Each attempt consumes its
|
|
149
199
|
// own provider pacing slot (#1584): retries are paced like first attempts.
|
|
200
|
+
// LOCAL PATCH (runturn-websearch): dispatch sequence. A 429 preflight
|
|
201
|
+
// rotation replays the turn while the abandoned attempt may still be
|
|
202
|
+
// in-flight; only the latest dispatch may merge adapter-written route
|
|
203
|
+
// state back onto `parsed`, or the superseded attempt would restore the
|
|
204
|
+
// failed account's cursor/continuation over the rotation's rebind.
|
|
205
|
+
let runTurnAttemptSeq = 0;
|
|
150
206
|
const runTurnAttempt = async (
|
|
151
207
|
targetQueue: AdapterEventQueue,
|
|
152
208
|
recovery?: AttemptRecoveryKind,
|
|
153
209
|
pacingSlotAcquired = false,
|
|
210
|
+
// LOCAL PATCH (runturn-websearch): iteration-local parsed — later search
|
|
211
|
+
// rounds carry the grown message history and adjusted tool list while
|
|
212
|
+
// selection/replay binding stays on the request's own parsed object.
|
|
213
|
+
turnParsed: PreparedResponsesRequest["parsed"] = parsed,
|
|
154
214
|
): Promise<void> => {
|
|
215
|
+
const attemptSeq = ++runTurnAttemptSeq;
|
|
155
216
|
try {
|
|
156
217
|
if (!pacingSlotAcquired) {
|
|
157
218
|
await waitForProviderRequestSlot(route.providerName, route.provider, route.modelId, runTurnAbort.signal);
|
|
158
219
|
}
|
|
159
220
|
await refreshRunTurnSelection();
|
|
221
|
+
// LOCAL PATCH (runturn-websearch): refreshRunTurnSelection binds route
|
|
222
|
+
// state onto `parsed`; mirror it onto the iteration-local copy the
|
|
223
|
+
// adapter actually receives.
|
|
224
|
+
{
|
|
225
|
+
const routeState: Record<string, unknown> = {};
|
|
226
|
+
for (const k of RUNTURN_WS_ROUTE_STATE_KEYS) routeState[k] = parsed[k];
|
|
227
|
+
Object.assign(turnParsed, routeState);
|
|
228
|
+
}
|
|
160
229
|
// An adapter that reports its own sends accounts for the first one at the boundary that
|
|
161
230
|
// dispatches it. Logging here would claim a send that the adapter's own budget can still
|
|
162
231
|
// refuse, which is exactly what happens once earlier recovery has spent the allowance.
|
|
@@ -175,7 +244,7 @@ export async function executeResponsesRunTurn(
|
|
|
175
244
|
},
|
|
176
245
|
);
|
|
177
246
|
await transportState.runTurnAdapter.runTurn?.(
|
|
178
|
-
|
|
247
|
+
turnParsed,
|
|
179
248
|
{
|
|
180
249
|
headers: requestState.selectedForwardHeaders,
|
|
181
250
|
abortSignal: runTurnAbort.signal,
|
|
@@ -195,6 +264,17 @@ export async function executeResponsesRunTurn(
|
|
|
195
264
|
},
|
|
196
265
|
targetQueue.push,
|
|
197
266
|
);
|
|
267
|
+
// LOCAL PATCH (runturn-websearch): adapters may write conversation/
|
|
268
|
+
// continuation state onto the object they received; merge it back so
|
|
269
|
+
// the next iteration's copy and request-level consumers observe it.
|
|
270
|
+
// Skipped once a newer attempt has dispatched: this attempt was
|
|
271
|
+
// abandoned by a 429 rotation, so its account's route state is stale
|
|
272
|
+
// and writing it back would undo the rotation's rebind.
|
|
273
|
+
if (attemptSeq === runTurnAttemptSeq) {
|
|
274
|
+
const routeState: Record<string, unknown> = {};
|
|
275
|
+
for (const k of RUNTURN_WS_ROUTE_STATE_KEYS) routeState[k] = turnParsed[k];
|
|
276
|
+
Object.assign(parsed, routeState);
|
|
277
|
+
}
|
|
198
278
|
} catch (err) {
|
|
199
279
|
targetQueue.push(err instanceof RequestPacingQueueOverloadError
|
|
200
280
|
? {
|
|
@@ -229,7 +309,30 @@ export async function executeResponsesRunTurn(
|
|
|
229
309
|
targetQueue.close();
|
|
230
310
|
}
|
|
231
311
|
};
|
|
232
|
-
|
|
312
|
+
// LOCAL PATCH (runturn-websearch): the first iteration carries the
|
|
313
|
+
// synthetic web_search tool; later iterations get their own queue so the
|
|
314
|
+
// search loop can buffer each turn's events before deciding to intercept.
|
|
315
|
+
const wsFirstParsed = wsPlan ? runTurnWebSearchInitialParsed(parsed) : parsed;
|
|
316
|
+
const runTurn = async (): Promise<void> => runTurnAttempt(queue, undefined, true, wsFirstParsed);
|
|
317
|
+
const runTurnFailoverArmed = () =>
|
|
318
|
+
route.provider.authMode === "oauth"
|
|
319
|
+
|| !!(transportState.genericFailoverAccountId
|
|
320
|
+
&& isGenericOAuthFailoverEnabled(config, route.providerName));
|
|
321
|
+
const dispatchSearchIteration = (iterParsed: PreparedResponsesRequest["parsed"]): AsyncIterable<AdapterEvent> => {
|
|
322
|
+
const iterQueue = createAdapterEventQueue({
|
|
323
|
+
onBacklogExceeded: () => runTurnAbort.abort(),
|
|
324
|
+
});
|
|
325
|
+
void runTurnAttempt(iterQueue, undefined, false, iterParsed);
|
|
326
|
+
const stream = iterQueue.stream();
|
|
327
|
+
if (!runTurnFailoverArmed()) return stream;
|
|
328
|
+
// LOCAL PATCH (runturn-websearch): a post-search iteration can open on a
|
|
329
|
+
// 429 too — the search cells already reached the client, so only this
|
|
330
|
+
// answer call rotates. Preflight replays it on the next account with the
|
|
331
|
+
// grown history intact; a mid-stream error still ends the turn as before.
|
|
332
|
+
return (async function* () {
|
|
333
|
+
yield* await preflightRunTurnFailover(stream, iterParsed);
|
|
334
|
+
})();
|
|
335
|
+
};
|
|
233
336
|
const rotateRunTurnAdapterOnPreflight429 = async (
|
|
234
337
|
error: Extract<AdapterEvent, { type: "error" }>,
|
|
235
338
|
): Promise<boolean> => {
|
|
@@ -331,6 +434,10 @@ export async function executeResponsesRunTurn(
|
|
|
331
434
|
};
|
|
332
435
|
const preflightRunTurnFailover = async (
|
|
333
436
|
firstSource: AsyncIterable<AdapterEvent>,
|
|
437
|
+
// LOCAL PATCH (runturn-websearch): replayed attempts re-dispatch this
|
|
438
|
+
// request — the grown-history iteration for post-search legs, the
|
|
439
|
+
// tool-injected first request elsewhere.
|
|
440
|
+
replayParsed: PreparedResponsesRequest["parsed"] = wsFirstParsed,
|
|
334
441
|
): Promise<AsyncIterable<AdapterEvent>> => {
|
|
335
442
|
let source = firstSource;
|
|
336
443
|
try {
|
|
@@ -344,7 +451,7 @@ export async function executeResponsesRunTurn(
|
|
|
344
451
|
const retryQueue = createAdapterEventQueue({
|
|
345
452
|
onBacklogExceeded: () => runTurnAbort.abort(),
|
|
346
453
|
});
|
|
347
|
-
void runTurnAttempt(retryQueue, "oauth-account-429");
|
|
454
|
+
void runTurnAttempt(retryQueue, "oauth-account-429", false, replayParsed);
|
|
348
455
|
source = retryQueue.stream();
|
|
349
456
|
}
|
|
350
457
|
} finally {
|
|
@@ -372,6 +479,8 @@ export async function executeResponsesRunTurn(
|
|
|
372
479
|
event: AdapterEvent,
|
|
373
480
|
): Extract<AdapterEvent, { type: "error" }> | undefined => {
|
|
374
481
|
if (!enforceDeclaredToolNames || event.type !== "tool_call_start") return undefined;
|
|
482
|
+
// This tool is declared to the adapter by the private search loop.
|
|
483
|
+
if (wsPlan && event.name === WEB_SEARCH_TOOL_NAME) return undefined;
|
|
375
484
|
const effectiveName = normalizeDeclaredToolName(event.name, declaredToolNames);
|
|
376
485
|
if (declaredToolNames.has(effectiveName)) return undefined;
|
|
377
486
|
return {
|
|
@@ -382,9 +491,10 @@ export async function executeResponsesRunTurn(
|
|
|
382
491
|
};
|
|
383
492
|
};
|
|
384
493
|
if (parsed.stream) {
|
|
494
|
+
try {
|
|
385
495
|
void runTurn();
|
|
386
496
|
let eventSource: AsyncIterable<AdapterEvent> = queue.stream();
|
|
387
|
-
if (
|
|
497
|
+
if (runTurnFailoverArmed()) {
|
|
388
498
|
// Preflight holds only heartbeats and the first meaningful event. A first-event 429 can be
|
|
389
499
|
// replayed transparently; after any output reaches the bridge, a later error stays terminal.
|
|
390
500
|
eventSource = await preflightRunTurnFailover(eventSource);
|
|
@@ -399,11 +509,32 @@ export async function executeResponsesRunTurn(
|
|
|
399
509
|
// A replay-unsafe heartbeat means the adapter already ran a local side effect, so the
|
|
400
510
|
// combo must not send this turn to another target: the failure stays with this child.
|
|
401
511
|
if (preflight.replayUnsafe) markResponseNonReplayable(failure);
|
|
512
|
+
releaseSearchProbeLease();
|
|
402
513
|
return failure;
|
|
403
514
|
}
|
|
404
515
|
eventSource = preflight.stream;
|
|
405
516
|
}
|
|
406
|
-
|
|
517
|
+
// LOCAL PATCH (runturn-websearch): intercept web_search calls across
|
|
518
|
+
// iterations; terminal output keeps flowing through the same queue/bridge.
|
|
519
|
+
if (wsPlan) {
|
|
520
|
+
eventSource = runTurnWebSearchLoop(eventSource, {
|
|
521
|
+
parsed,
|
|
522
|
+
plan: wsPlan,
|
|
523
|
+
translatorBudget,
|
|
524
|
+
emptyCompletionRetry: emptyCompletionGuardEnabled,
|
|
525
|
+
forwardProvider: wsPlan.forwardSidecar?.provider,
|
|
526
|
+
forwardHeaders: wsPlan.forwardSidecar?.headers ?? requestState.selectedForwardHeaders,
|
|
527
|
+
...(wsPlan.exaConfigured ? { exaApiKey: config.webSearchSidecar?.exaApiKey } : {}),
|
|
528
|
+
recordSidecarOutcome: wsPlan.forwardSidecar?.recordOutcome,
|
|
529
|
+
abortSignal: runTurnAbort.signal,
|
|
530
|
+
dispatch: dispatchSearchIteration,
|
|
531
|
+
});
|
|
532
|
+
}
|
|
533
|
+
// LOCAL PATCH (runturn-websearch): the empty-completion retry replays
|
|
534
|
+
// the ORIGINAL parsed request — no synthetic tool, no gathered results —
|
|
535
|
+
// so it must not fire while the search loop owns the turn. An empty
|
|
536
|
+
// forced answer is recovered inside runTurnWebSearchLoop instead.
|
|
537
|
+
const guardedSource = emptyCompletionGuardEnabled && !wsPlan
|
|
407
538
|
? guardEmptyCompletionEventStream({
|
|
408
539
|
firstEvents: eventSource,
|
|
409
540
|
// Identical-turn retry: same parsed request, same headers, same
|
|
@@ -428,7 +559,7 @@ export async function executeResponsesRunTurn(
|
|
|
428
559
|
translatorBudget,
|
|
429
560
|
replayCacheScope: parsed._reasoningReplayScope,
|
|
430
561
|
...(options.forceEmptyResponseId ? { responseId: "" } : {}),
|
|
431
|
-
stallTimeoutSec: config.stallTimeoutSec,
|
|
562
|
+
stallTimeoutSec: wsPlan?.stallTimeoutSec ?? config.stallTimeoutSec,
|
|
432
563
|
hideThinkingSummary: parsed.options.hideThinkingSummary,
|
|
433
564
|
declaredToolNames,
|
|
434
565
|
enforceDeclaredToolNames,
|
|
@@ -459,25 +590,38 @@ export async function executeResponsesRunTurn(
|
|
|
459
590
|
},
|
|
460
591
|
);
|
|
461
592
|
const bridgeTurnAc = new AbortController();
|
|
462
|
-
|
|
593
|
+
// LOCAL PATCH (runturn-websearch): sidecar resolution may hold the
|
|
594
|
+
// account's cooldown-recovery probe. Hand it back when the stream ends —
|
|
595
|
+
// completion, failure, or client cancel; a search that ran already
|
|
596
|
+
// settled it through recordOutcome, so this release is a safe no-op then.
|
|
597
|
+
const trackedSse = trackStreamLifetime(sseStream, bridgeTurnAc, releaseSearchProbeLease, options.turnAdmissionLease);
|
|
463
598
|
const response = new Response(trackedSse, {
|
|
464
599
|
headers: { "Content-Type": "text/event-stream", "Cache-Control": "no-cache", "Connection": "keep-alive", "X-Accel-Buffering": "no" },
|
|
465
600
|
});
|
|
466
601
|
runTurnAdapterSseResponses.add(response);
|
|
467
602
|
return response;
|
|
603
|
+
} catch (error) {
|
|
604
|
+
// Every pre-response exit hands the probe lease back too: a turn that
|
|
605
|
+
// never started streaming has no later owner to release it.
|
|
606
|
+
releaseSearchProbeLease();
|
|
607
|
+
throw error;
|
|
608
|
+
}
|
|
468
609
|
}
|
|
469
610
|
|
|
611
|
+
try {
|
|
470
612
|
await runTurn();
|
|
471
613
|
const firstAttemptEvents = await queue.collect();
|
|
472
614
|
let runTurnEvents: AdapterEvent[] = firstAttemptEvents;
|
|
473
|
-
if (
|
|
615
|
+
if (runTurnFailoverArmed()) {
|
|
474
616
|
runTurnEvents = [];
|
|
475
617
|
for await (const event of await preflightRunTurnFailover(
|
|
476
618
|
(async function* () { yield* firstAttemptEvents; })(),
|
|
477
619
|
)) runTurnEvents.push(event);
|
|
478
620
|
}
|
|
479
621
|
let events: AdapterEvent[];
|
|
480
|
-
|
|
622
|
+
// LOCAL PATCH (runturn-websearch): same exclusion as the streaming branch —
|
|
623
|
+
// a search-aware retry runs inside runTurnWebSearchLoop, not the raw guard.
|
|
624
|
+
if (emptyCompletionGuardEnabled && !wsPlan) {
|
|
481
625
|
events = [];
|
|
482
626
|
for await (const event of guardEmptyCompletionEventStream({
|
|
483
627
|
firstEvents: (async function* () { yield* runTurnEvents; })(),
|
|
@@ -486,60 +630,110 @@ export async function executeResponsesRunTurn(
|
|
|
486
630
|
} else {
|
|
487
631
|
events = runTurnEvents;
|
|
488
632
|
}
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
//
|
|
494
|
-
//
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
|
|
633
|
+
// This collector outlives each search iteration, including live-output
|
|
634
|
+
// events that the loop no longer owns. Keep its lease until JSON is built.
|
|
635
|
+
let searchOutputBytes = 0;
|
|
636
|
+
try {
|
|
637
|
+
// LOCAL PATCH (runturn-websearch): buffered path runs the same interception
|
|
638
|
+
// loop; iterations dispatch through the same attempt body on fresh queues.
|
|
639
|
+
if (wsPlan) {
|
|
640
|
+
const searched: AdapterEvent[] = [];
|
|
641
|
+
let retainedReplayUnsafe = false;
|
|
642
|
+
for await (const event of runTurnWebSearchLoop(
|
|
643
|
+
(async function* () { yield* events; })(),
|
|
644
|
+
{
|
|
645
|
+
parsed,
|
|
646
|
+
plan: wsPlan,
|
|
647
|
+
translatorBudget,
|
|
648
|
+
emptyCompletionRetry: emptyCompletionGuardEnabled,
|
|
649
|
+
forwardProvider: wsPlan.forwardSidecar?.provider,
|
|
650
|
+
forwardHeaders: wsPlan.forwardSidecar?.headers ?? requestState.selectedForwardHeaders,
|
|
651
|
+
...(wsPlan.exaConfigured ? { exaApiKey: config.webSearchSidecar?.exaApiKey } : {}),
|
|
652
|
+
recordSidecarOutcome: wsPlan.forwardSidecar?.recordOutcome,
|
|
653
|
+
abortSignal: runTurnAbort.signal,
|
|
654
|
+
dispatch: dispatchSearchIteration,
|
|
655
|
+
},
|
|
656
|
+
)) {
|
|
657
|
+
if (event.type === "heartbeat") {
|
|
658
|
+
if (!event.replayUnsafe || retainedReplayUnsafe) continue;
|
|
659
|
+
retainedReplayUnsafe = true;
|
|
660
|
+
}
|
|
661
|
+
if (event.type === "error" && event.code === "translation_buffer_limit") runTurnAbort.abort();
|
|
662
|
+
const bytes = jsonUtf8Bytes(event) + 1;
|
|
663
|
+
translatorBudget.chargeRetained(bytes, { kind: "retained_collectors" });
|
|
664
|
+
searchOutputBytes += bytes;
|
|
665
|
+
searched.push(event);
|
|
666
|
+
}
|
|
667
|
+
events = searched;
|
|
508
668
|
}
|
|
669
|
+
if (options.comboAttempt) {
|
|
670
|
+
const firstMeaningfulIndex = events.findIndex(event => event.type !== "heartbeat");
|
|
671
|
+
const firstMeaningful = firstMeaningfulIndex === -1 ? undefined : events[firstMeaningfulIndex];
|
|
672
|
+
// Same boundary as the streaming preflight: a replay-unsafe heartbeat means the adapter
|
|
673
|
+
// already ran a local side effect, so an undeclared tool call after it keeps the bridge's
|
|
674
|
+
// fail-closed refusal instead of becoming a hop that sends the turn to another target.
|
|
675
|
+
const replayUnsafe = events
|
|
676
|
+
.slice(0, firstMeaningfulIndex === -1 ? events.length : firstMeaningfulIndex)
|
|
677
|
+
.some(event => event.type === "heartbeat" && event.replayUnsafe === true);
|
|
678
|
+
const classifiedError = firstMeaningful && !replayUnsafe
|
|
679
|
+
? classifyUndeclaredFirstTool(firstMeaningful)
|
|
680
|
+
: undefined;
|
|
681
|
+
if (!firstMeaningful || firstMeaningful.type === "error" || classifiedError) {
|
|
682
|
+
const message = classifiedError?.message ?? (firstMeaningful?.type === "error"
|
|
683
|
+
? firstMeaningful.message
|
|
684
|
+
: "Adapter ended before producing a response");
|
|
685
|
+
const failure = formatErrorResponse(502, "upstream_error", redactSecretString(message));
|
|
686
|
+
if (replayUnsafe) markResponseNonReplayable(failure);
|
|
687
|
+
return failure;
|
|
688
|
+
}
|
|
689
|
+
}
|
|
690
|
+
let providerState: OcxProviderContinuationState | undefined;
|
|
691
|
+
const json = buildResponseJSON(events, parsed._responseModelId ?? parsed.modelId, {
|
|
692
|
+
translatorBudget,
|
|
693
|
+
replayCacheScope: parsed._reasoningReplayScope,
|
|
694
|
+
hideThinkingSummary: parsed.options.hideThinkingSummary,
|
|
695
|
+
toolNsMap,
|
|
696
|
+
declaredToolNames,
|
|
697
|
+
enforceDeclaredToolNames,
|
|
698
|
+
toolParameterSchemas,
|
|
699
|
+
freeformToolNames,
|
|
700
|
+
toolSearchToolNames,
|
|
701
|
+
...(routedCompaction ? { compaction: true } : {}),
|
|
702
|
+
onProviderState: state => { providerState = state; },
|
|
703
|
+
onUsage: usage => {
|
|
704
|
+
transportState.bindKeyUsageFromBridge(usage);
|
|
705
|
+
},
|
|
706
|
+
});
|
|
707
|
+
if (!routedCompaction) {
|
|
708
|
+
rememberKiroDeliveredFinalAnswer(transportState.adapter.name, json);
|
|
709
|
+
rememberResponseState(
|
|
710
|
+
parsed._rawBody,
|
|
711
|
+
json,
|
|
712
|
+
continuationStateForResponse(providerState),
|
|
713
|
+
responseStateOptions(adapterNeedsForcedContinuation(transportState.adapter.name)),
|
|
714
|
+
);
|
|
715
|
+
}
|
|
716
|
+
// #1926 gap 2: the buffered path queued its signature persists inside
|
|
717
|
+
// buildResponseJSON; bound the durability window before the JSON becomes
|
|
718
|
+
// externally visible.
|
|
719
|
+
await awaitThoughtSignatureDurability();
|
|
720
|
+
if (adapterResponseReachedServingTerminal(events, json)) {
|
|
721
|
+
commitReasoningReplayServingRoute();
|
|
722
|
+
}
|
|
723
|
+
notifyResponseComplete(json);
|
|
724
|
+
return new Response(JSON.stringify(json), { headers: { "Content-Type": "application/json" } });
|
|
725
|
+
} catch (error) {
|
|
726
|
+
if (!isTranslatorBudgetExceededError(error)) throw error;
|
|
727
|
+
runTurnAbort.abort();
|
|
728
|
+
return formatErrorResponse(502, "upstream_error", "upstream translation buffer exceeded the safe limit", {
|
|
729
|
+
code: "translation_buffer_limit",
|
|
730
|
+
});
|
|
731
|
+
} finally {
|
|
732
|
+
translatorBudget.releaseRetained(searchOutputBytes, { kind: "retained_collectors" });
|
|
509
733
|
}
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
hideThinkingSummary: parsed.options.hideThinkingSummary,
|
|
515
|
-
toolNsMap,
|
|
516
|
-
declaredToolNames,
|
|
517
|
-
enforceDeclaredToolNames,
|
|
518
|
-
toolParameterSchemas,
|
|
519
|
-
freeformToolNames,
|
|
520
|
-
toolSearchToolNames,
|
|
521
|
-
...(routedCompaction ? { compaction: true } : {}),
|
|
522
|
-
onProviderState: state => { providerState = state; },
|
|
523
|
-
onUsage: usage => {
|
|
524
|
-
transportState.bindKeyUsageFromBridge(usage);
|
|
525
|
-
},
|
|
526
|
-
});
|
|
527
|
-
if (!routedCompaction) {
|
|
528
|
-
rememberKiroDeliveredFinalAnswer(transportState.adapter.name, json);
|
|
529
|
-
rememberResponseState(
|
|
530
|
-
parsed._rawBody,
|
|
531
|
-
json,
|
|
532
|
-
continuationStateForResponse(providerState),
|
|
533
|
-
responseStateOptions(adapterNeedsForcedContinuation(transportState.adapter.name)),
|
|
534
|
-
);
|
|
535
|
-
}
|
|
536
|
-
// #1926 gap 2: the buffered path queued its signature persists inside
|
|
537
|
-
// buildResponseJSON; bound the durability window before the JSON becomes
|
|
538
|
-
// externally visible.
|
|
539
|
-
await awaitThoughtSignatureDurability();
|
|
540
|
-
if (adapterResponseReachedServingTerminal(events, json)) {
|
|
541
|
-
commitReasoningReplayServingRoute();
|
|
734
|
+
} finally {
|
|
735
|
+
// The buffered turn ends inside this function, so its every exit —
|
|
736
|
+
// JSON response, combo failure, or thrown error — hands the lease back.
|
|
737
|
+
releaseSearchProbeLease();
|
|
542
738
|
}
|
|
543
|
-
notifyResponseComplete(json);
|
|
544
|
-
return new Response(JSON.stringify(json), { headers: { "Content-Type": "application/json" } });
|
|
545
739
|
}
|
|
@@ -143,8 +143,11 @@ export async function executeResponsesSidecars(
|
|
|
143
143
|
// Web-search's loop only supports buildRequest/fetch/parseStream — NOT adapter.runTurn. Sending
|
|
144
144
|
// Cursor/runTurn requests into runWithWebSearch produces empty HTTP failures. So:
|
|
145
145
|
// - non-runTurn: web-search wins over image when both eligible (documented priority)
|
|
146
|
-
// - runTurn:
|
|
147
|
-
//
|
|
146
|
+
// - runTurn: a search plan takes priority over media bridges and is executed
|
|
147
|
+
// by executeResponsesRunTurn; the fetch loop below remains fetch-only.
|
|
148
|
+
// LOCAL PATCH (runturn-websearch): runTurn adapters run their own web-search
|
|
149
|
+
// loop inside executeResponsesRunTurn (src/web-search/run-turn-loop.ts); the
|
|
150
|
+
// fetch-path loop below stays non-runTurn-only.
|
|
148
151
|
const wsPlan = !routedCompaction
|
|
149
152
|
? planWebSearch(config, parsed, false, route.provider, route.modelId, openAiSidecar, {
|
|
150
153
|
admission: options.admission, codexAuthPolicy: options.codexAuthPolicy, providerName: route.providerName,
|
|
@@ -280,7 +283,7 @@ export async function executeResponsesSidecars(
|
|
|
280
283
|
});
|
|
281
284
|
return rotatedAdapter;
|
|
282
285
|
};
|
|
283
|
-
if ((imgPlan || vidPlan) &&
|
|
286
|
+
if ((imgPlan || vidPlan) && wsPlan) {
|
|
284
287
|
// Web search takes priority when both are active — the media bridge cannot run
|
|
285
288
|
// alongside runWithWebSearch. Surface a runtime signal so the user knows their
|
|
286
289
|
// configured video/image bridge was skipped for this turn, rather than silently
|
|
@@ -288,7 +291,7 @@ export async function executeResponsesSidecars(
|
|
|
288
291
|
if (vidPlan) console.warn("[videos] video bridge skipped: web search is active for this turn");
|
|
289
292
|
if (imgPlan) console.warn("[images] image bridge skipped: web search is active for this turn");
|
|
290
293
|
}
|
|
291
|
-
if ((imgPlan || vidPlan) &&
|
|
294
|
+
if ((imgPlan || vidPlan) && !wsPlan) {
|
|
292
295
|
// The image bridge detects a hosted image_generation tool and requires streaming.
|
|
293
296
|
// The video bridge activates from config and injects a tool — it also needs streaming
|
|
294
297
|
// (the loop returns SSE). For video-only (no imgPlan) on a non-streaming request, skip
|
package/src/server/system-env.ts
CHANGED
|
@@ -256,6 +256,13 @@ export async function injectSystemEnv(
|
|
|
256
256
|
};
|
|
257
257
|
|
|
258
258
|
try {
|
|
259
|
+
// Versions before 2.11 injected this key, which prevents Claude's gateway model
|
|
260
|
+
// discovery. Records written after it left the tracking list no longer name it,
|
|
261
|
+
// so membership cannot find it — clear it whenever the live value is the only
|
|
262
|
+
// one we ever injected. A user-set "1" is indistinguishable and is cleared too.
|
|
263
|
+
if (launchctlGetenv("_CLAUDE_CODE_ASSUME_FIRST_PARTY_BASE_URL") === "1") {
|
|
264
|
+
unsetLaunchctlEnv("_CLAUDE_CODE_ASSUME_FIRST_PARTY_BASE_URL");
|
|
265
|
+
}
|
|
259
266
|
inject("ANTHROPIC_BASE_URL", destination.origin);
|
|
260
267
|
inject("CLAUDE_CODE_ENABLE_GATEWAY_MODEL_DISCOVERY", "1");
|
|
261
268
|
if (markerMode === "proxy") {
|