@bitkyc08/opencodex 2.75.0 → 2.76.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 +6 -1
- package/bin/ocx.mjs +12 -1
- package/gui/dist/assets/App-CddorHdA.js +51 -0
- package/gui/dist/assets/App-DMudtISW.css +1 -0
- package/gui/dist/assets/{Tray-CGAD2AGk.js → Tray-DjNwwrWS.js} +1 -1
- package/gui/dist/assets/index-Btze1I3T.css +1 -0
- package/gui/dist/assets/index-CvnY7E6i.js +86 -0
- package/gui/dist/assets/quota-summary-B_AWpHfP.js +1 -0
- package/gui/dist/index.html +2 -2
- package/gui/dist/provider-icons/opengateway.svg +12 -0
- package/package.json +1 -1
- package/src/adapters/anthropic/account-metadata.ts +17 -0
- package/src/adapters/anthropic/client-identity.ts +69 -0
- package/src/adapters/anthropic/passthrough.ts +23 -4
- package/src/adapters/anthropic.ts +18 -1
- package/src/adapters/command-code-project-context.ts +7 -11
- package/src/adapters/devin/cloud-direct/tool-schema.ts +12 -48
- package/src/adapters/kiro-retry.ts +23 -8
- package/src/adapters/openai-chat/messages.ts +1 -1
- package/src/adapters/registry.ts +8 -1
- package/src/adapters/zed.ts +377 -0
- package/src/bridge/sse.ts +4 -2
- package/src/chatgpt/app-server-shim/app-server-rewrite.ts +45 -0
- package/src/chatgpt/app-server-shim/bundle-trust.ts +77 -0
- package/src/chatgpt/app-server-shim/filter.ts +157 -0
- package/src/chatgpt/app-server-shim/gate-rewrite.ts +99 -0
- package/src/chatgpt/app-server-shim/launcher.ts +91 -0
- package/src/claude/desktop-3p-startup.ts +62 -0
- package/src/claude/desktop-3p.ts +5 -0
- package/src/claude/first-party-settings.ts +14 -0
- package/src/claude/intercept/cli-catalog.ts +150 -0
- package/src/claude/intercept/cli-picker.ts +82 -0
- package/src/claude/intercept/listener.ts +21 -6
- package/src/claude/intercept/picker-bootstrap.ts +23 -5
- package/src/claude/intercept/picker-models.ts +57 -13
- package/src/claude/intercept/runtime.ts +30 -2
- package/src/claude/intercept/settings.ts +3 -0
- package/src/cli/agent.ts +172 -0
- package/src/cli/alias.ts +2 -1
- package/src/cli/capabilities-command.ts +3 -2
- package/src/cli/capabilities.ts +87 -0
- package/src/cli/chatgpt-command.ts +140 -0
- package/src/cli/combo.ts +144 -19
- package/src/cli/config-command.ts +3 -1
- package/src/cli/dispatch.ts +10 -1
- package/src/cli/help.ts +1 -0
- package/src/cli/index.ts +5 -24
- package/src/cli/init.ts +16 -2
- package/src/cli/integrations.ts +13 -0
- package/src/cli/internal-command.ts +12 -5
- package/src/cli/observe.ts +6 -1
- package/src/cli/registry.ts +6 -0
- package/src/cli/route-policy.ts +3 -1
- package/src/cli/runtime-api.ts +5 -2
- package/src/cli/status-oauth.ts +5 -0
- package/src/cli/status.ts +8 -1
- package/src/cli/stop-approval.ts +55 -16
- package/src/cli/stop-report.ts +5 -0
- package/src/clients/lazycodex.ts +48 -0
- package/src/clients/omo-role-models.ts +111 -0
- package/src/codex/account-credit-use.ts +99 -0
- package/src/codex/account-lifecycle.ts +2 -0
- package/src/codex/agent-role-models.ts +171 -0
- package/src/codex/auth-api/account-list.ts +10 -3
- package/src/codex/auth-api/routes.ts +34 -0
- package/src/codex/auth-context.ts +52 -11
- package/src/codex/catalog/discovered-natives.ts +11 -1
- package/src/codex/catalog/gather-capture.ts +6 -1
- package/src/codex/catalog/model-hints.ts +9 -1
- package/src/codex/catalog/provider-models.ts +60 -0
- package/src/codex/catalog/routed-gather.ts +30 -5
- package/src/codex/convergence-types.ts +2 -1
- package/src/codex/main-account-external-usage.ts +62 -0
- package/src/codex/main-account-hard-lock.ts +24 -13
- package/src/codex/native-profile-processes.ts +53 -16
- package/src/codex/prompt-layers/toml-edit.ts +1 -1
- package/src/codex/quota-types.ts +3 -7
- package/src/codex/quota.ts +19 -4
- package/src/codex/role-auto-assign.ts +159 -0
- package/src/codex/role-sizing-limits.ts +2 -0
- package/src/codex/role-sizing.ts +216 -0
- package/src/codex/routing/selection.ts +36 -0
- package/src/codex/routing/thread-affinity.ts +2 -0
- package/src/codex/routing.ts +4 -4
- package/src/codex/subagent-model-fallback.ts +82 -26
- package/src/codex/subagent-selectable-models.ts +32 -0
- package/src/combos/index.ts +18 -0
- package/src/combos/jev-decision-contract.ts +20 -0
- package/src/combos/jev-dispatch.ts +18 -0
- package/src/combos/jev-model-backend.ts +127 -0
- package/src/combos/jev.ts +249 -33
- package/src/combos/types.ts +125 -2
- package/src/config/diagnostics.ts +18 -0
- package/src/config/macos-system-proxy.ts +72 -15
- package/src/config/owner-registry.ts +13 -4
- package/src/config/process-state.ts +17 -8
- package/src/config/proxy-env.ts +35 -5
- package/src/config/schema/config-schema.ts +30 -1
- package/src/config/schema/leaf-validators.ts +4 -0
- package/src/config/serving-runtimes.ts +95 -1
- package/src/generated/compatibility-version.json +336 -156
- package/src/lib/errors.ts +7 -7
- package/src/lib/jsonc.ts +13 -0
- package/src/lib/local-chat-completion.ts +144 -0
- package/src/lib/provider-outbound.ts +87 -1
- package/src/lib/provider-runtime-fetch.ts +23 -0
- package/src/lib/provider-tls-profile.ts +221 -0
- package/src/lib/request-execution-budget.ts +16 -6
- package/src/lib/spend-reservation-ledger.ts +54 -20
- package/src/lib/sse-decoder.ts +20 -17
- package/src/lib/synced-state-location.ts +96 -0
- package/src/lib/winsw.ts +14 -5
- package/src/oauth/anthropic-account-refusal.ts +53 -4
- package/src/oauth/anthropic-model-quota.ts +83 -0
- package/src/oauth/anthropic-rate-limit-policy.ts +37 -0
- package/src/oauth/anthropic-routing.ts +112 -58
- package/src/oauth/health.ts +39 -3
- package/src/oauth/index.ts +14 -0
- package/src/oauth/kiro-account-load.ts +10 -0
- package/src/oauth/zed.ts +134 -0
- package/src/providers/model-discovery.ts +19 -8
- package/src/providers/model-rename-fields.ts +1 -0
- package/src/providers/provider-id-rewrite.ts +9 -2
- package/src/providers/quota/account-cache.ts +29 -7
- package/src/providers/quota/anthropic-cooldown-recovery.ts +18 -0
- package/src/providers/quota/anthropic-family-headers.ts +25 -0
- package/src/providers/quota/vendor-probes-oauth.ts +5 -1
- package/src/providers/quota-types.ts +4 -0
- package/src/providers/registry/entries-core.ts +18 -0
- package/src/providers/registry/entries-extended.ts +55 -0
- package/src/providers/registry/types.ts +3 -0
- package/src/providers/xai-fast-model.ts +12 -4
- package/src/providers/zed.ts +598 -0
- package/src/responses/state/snapshot-select.ts +22 -9
- package/src/responses/state.ts +2 -3
- package/src/router.ts +9 -0
- package/src/server/anthropic-rate-limit-headers.ts +7 -0
- package/src/server/auth-cors.ts +7 -0
- package/src/server/chat-completions.ts +9 -4
- package/src/server/claude-messages.ts +59 -21
- package/src/server/index/claude-intercept-lifecycle.ts +71 -25
- package/src/server/index/optional-listeners.ts +4 -0
- package/src/server/index/spend-ledger-lifecycle.ts +4 -0
- package/src/server/management/agent-settings-routes.ts +74 -37
- package/src/server/management/claude-desktop-picker-routes.ts +7 -0
- package/src/server/management/claude-intercept-routes.ts +35 -0
- package/src/server/management/codex-agent-role-routes.ts +122 -0
- package/src/server/management/codex-role-auto-assign.ts +208 -0
- package/src/server/management/combo-routes.ts +62 -5
- package/src/server/management/config-routes.ts +35 -3
- package/src/server/management/context.ts +4 -1
- package/src/server/management/decision-discovery.ts +74 -0
- package/src/server/management/decision-model-validation.ts +80 -0
- package/src/server/management/decision-routes.ts +160 -0
- package/src/server/management/native-integration-routes.ts +16 -0
- package/src/server/management/provider-routes.ts +30 -34
- package/src/server/management/route-registry.ts +12 -0
- package/src/server/management/sibling-guard.ts +1 -0
- package/src/server/management/zed-provider-probe.ts +38 -0
- package/src/server/management-api.ts +12 -1
- package/src/server/messages-native-oauth.ts +7 -2
- package/src/server/messages-native.ts +7 -3
- package/src/server/relay.ts +3 -1
- package/src/server/request-log.ts +2 -0
- package/src/server/responses/adapter-continuation.ts +3 -3
- package/src/server/responses/adapter-dispatch.ts +18 -3
- package/src/server/responses/compact.ts +14 -1
- package/src/server/responses/core-auth.ts +2 -0
- package/src/server/responses/core-combo.ts +67 -11
- package/src/server/responses/core-normalize.ts +21 -1
- package/src/server/responses/core-options.ts +11 -1
- package/src/server/responses/core.ts +5 -3
- package/src/server/responses/fetch-helpers.ts +6 -3
- package/src/server/responses/jev-model-invoke.ts +190 -0
- package/src/server/responses/passthrough-delivery.ts +16 -0
- package/src/server/responses/policy-fallback.ts +55 -3
- package/src/server/responses/policy-request-scope.ts +80 -0
- package/src/server/responses/request-prepare.ts +70 -18
- package/src/server/responses/request-transport.ts +54 -23
- package/src/server/responses/run-turn-execution.ts +1 -0
- package/src/server/responses/sidecar-execution.ts +9 -3
- package/src/server/responses-hosted-image-display.ts +225 -0
- package/src/service/managing-cli.ts +34 -4
- package/src/service/systemd-env.ts +75 -0
- package/src/service/windows-guarded-manager.ts +60 -19
- package/src/service/windows-scheduler.ts +22 -3
- package/src/service/windows-taskxml.ts +32 -10
- package/src/service-manager-probe.ts +72 -19
- package/src/types/config.ts +31 -1
- package/src/types/provider.ts +2 -0
- package/src/types/request.ts +2 -0
- package/src/update/index.ts +39 -6
- package/src/update/job.ts +63 -63
- package/src/update/npm-cache-preflight.d.mts +33 -1
- package/src/update/npm-cache-preflight.mjs +131 -18
- package/src/update/ownership-transaction.ts +19 -1
- package/src/update/restart-ownership.ts +80 -6
- package/src/update/transactional-install.d.mts +2 -0
- package/src/update/transactional-install.mjs +6 -1
- package/src/usage/jev-stats.ts +28 -1
- package/src/usage/log.ts +1 -1
- package/src/vision/anthropic-describe.ts +5 -3
- package/src/vision/index.ts +1 -0
- package/src/vision/plan.ts +2 -1
- package/src/vision/routed-describe.ts +29 -121
- package/src/web-search/alpha-search.ts +3 -3
- package/src/web-search/anthropic-executor.ts +6 -4
- package/src/web-search/index.ts +1 -1
- package/src/web-search/loop.ts +3 -3
- package/src/web-search/passthrough-bridge.ts +2 -1
- package/src/web-search/run-turn-loop.ts +1 -1
- package/src/web-search/sidecar-providers.ts +2 -1
- package/gui/dist/assets/App-ia4U0ufY.css +0 -1
- package/gui/dist/assets/App-xm0zGNOe.js +0 -51
- package/gui/dist/assets/index-DBT3Q31Y.css +0 -1
- package/gui/dist/assets/index-D_RGAT6Q.js +0 -86
- package/gui/dist/assets/quota-summary-CUFq48t-.js +0 -1
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import type { ResponsesRequestContext, ResponsesAdmissionState, ResponsesDispatchers } from "./core-options";
|
|
2
|
+
import { capturePolicyRequestRoute, assertPolicyNormalizedRoute, recordPolicyPreparedDestination, policyCandidateRefusalResponse } from "./policy-request-scope";
|
|
2
3
|
import {
|
|
3
4
|
agentTaskRecoveryConfig,
|
|
4
5
|
restoreCachedEncryptedAgentTasks,
|
|
@@ -96,6 +97,7 @@ import {
|
|
|
96
97
|
resolveSubagentFallbackModelEligibility,
|
|
97
98
|
canPassThroughEncryptedV2AgentTask,
|
|
98
99
|
applyFinalRouteRequestNormalization,
|
|
100
|
+
previewXaiOauthWireModel,
|
|
99
101
|
} from "./core-normalize";
|
|
100
102
|
import {
|
|
101
103
|
cachedDeniedCodexAccountIdsForModel,
|
|
@@ -146,6 +148,8 @@ import {
|
|
|
146
148
|
conversationCarriesUploadedFiles,
|
|
147
149
|
} from "./account-change-state";
|
|
148
150
|
|
|
151
|
+
import { redactHostedImageDisplayPaths } from "../responses-hosted-image-display";
|
|
152
|
+
|
|
149
153
|
/** Parses, selects, and admits one request without changing the dispatch policy. */
|
|
150
154
|
export async function prepareResponsesRequest(
|
|
151
155
|
requestContext: Pick<ResponsesRequestContext, "options" | "config" | "req" | "logCtx">,
|
|
@@ -153,6 +157,8 @@ export async function prepareResponsesRequest(
|
|
|
153
157
|
requestDispatchers: ResponsesDispatchers,
|
|
154
158
|
) {
|
|
155
159
|
const { options, config, req, logCtx } = requestContext;
|
|
160
|
+
const policyScope = options.policyRequestScope ?? { triedDestinations: new Set<string>() };
|
|
161
|
+
const concreteSelection = options.comboAttempt || options.policyFallbackCandidate !== undefined;
|
|
156
162
|
|
|
157
163
|
// The Chat and Anthropic surfaces replay through here with a Responses-shaped body,
|
|
158
164
|
// so an omitted value means a genuine Responses inbound.
|
|
@@ -168,6 +174,8 @@ export async function prepareResponsesRequest(
|
|
|
168
174
|
}
|
|
169
175
|
return decodeRequestErrorResponse(err, "responses");
|
|
170
176
|
}
|
|
177
|
+
// Remove display-only artifact paths before helpers, combo dispatch and upstream replay.
|
|
178
|
+
redactHostedImageDisplayPaths(body);
|
|
171
179
|
observeCacheDiagnosticInbound(
|
|
172
180
|
logCtx,
|
|
173
181
|
body,
|
|
@@ -176,7 +184,7 @@ export async function prepareResponsesRequest(
|
|
|
176
184
|
? "system-derived"
|
|
177
185
|
: options.promptCacheKeyIsSharedCohort === false ? "metadata-derived" : "caller",
|
|
178
186
|
);
|
|
179
|
-
if (!
|
|
187
|
+
if (!concreteSelection && !options.compactionRoutingOverride && inboundWire === "responses") {
|
|
180
188
|
options.compactionRoutingOverride = applyCompactionRoutingOverride(body, req.headers, config, {
|
|
181
189
|
endpoint: "responses",
|
|
182
190
|
transport: options.inboundTransport,
|
|
@@ -186,20 +194,21 @@ export async function prepareResponsesRequest(
|
|
|
186
194
|
// metadata, never from the model id: Phase 1 shares `gpt-5.6-luna` with the app's title/commit
|
|
187
195
|
// helper calls. Read here, ahead of the shadow intercept below, because the phase decision is the
|
|
188
196
|
// more specific of the two settings and must be the one that survives when both match one request.
|
|
189
|
-
|
|
197
|
+
// A JEV decision-model call is the router's own auxiliary turn, never a Codex memory phase.
|
|
198
|
+
const memoryModelPhase = options.internalDecisionCall ? undefined : options.memoryModelPhase
|
|
190
199
|
?? ((config.memoryModels?.extract || config.memoryModels?.consolidation)
|
|
191
|
-
&& !
|
|
200
|
+
&& !concreteSelection && !options.compactionRoutingOverride && inboundWire === "responses"
|
|
192
201
|
? detectMemoryModelPhase(body, req.headers, { transport: options.inboundTransport }) ?? undefined
|
|
193
202
|
: undefined);
|
|
194
203
|
const memoryModelTarget = memoryModelPhase ? configuredMemoryModel(config, memoryModelPhase) : undefined;
|
|
195
204
|
// A combo child is a synthetic replay of the parent's decision: its model is already the target's
|
|
196
205
|
// concrete provider/model, so neither site below may rewrite or re-resolve it. It keeps the phase
|
|
197
206
|
// through `options.memoryModelPhase` instead, which is what applies the phase effort.
|
|
198
|
-
const memoryModelApplies = memoryModelTarget !== undefined &&
|
|
207
|
+
const memoryModelApplies = memoryModelTarget !== undefined && !concreteSelection;
|
|
199
208
|
options.onRequestBodyParsed?.(body);
|
|
200
209
|
// An effort row naming a table-less combo (`combo/x--high`) must reach the combo dispatcher
|
|
201
210
|
// as its base id, so the selector is normalized here, before comboIdFromRawBody reads model.
|
|
202
|
-
const comboRows = !
|
|
211
|
+
const comboRows = !concreteSelection && body && typeof body === "object" && !Array.isArray(body)
|
|
203
212
|
&& typeof (body as { model?: unknown }).model === "string"
|
|
204
213
|
// One parse for both grammars, from the selector as the client sent it. Parsing them
|
|
205
214
|
// separately made the outcome depend on which ran first.
|
|
@@ -228,7 +237,7 @@ export async function prepareResponsesRequest(
|
|
|
228
237
|
}
|
|
229
238
|
// Compaction may send the last client-visible bare model after a combo switch.
|
|
230
239
|
// Configured selectors take precedence; otherwise recall before combo dispatch (#3891).
|
|
231
|
-
if (!
|
|
240
|
+
if (!concreteSelection && !options.compactionRoutingOverride && body && typeof body === "object" && !Array.isArray(body)) {
|
|
232
241
|
const rawModel = (body as { model?: unknown }).model;
|
|
233
242
|
const rawInput = (body as { input?: unknown }).input;
|
|
234
243
|
const isCompactionTrigger = Array.isArray(rawInput)
|
|
@@ -265,7 +274,7 @@ export async function prepareResponsesRequest(
|
|
|
265
274
|
// A spawned sub-agent turn names its model on purpose; gpt-6-luna is both the helper
|
|
266
275
|
// slug and a default sub-agent model, so neither intercept site may rewrite that turn.
|
|
267
276
|
const threadSpawn = isThreadSpawnRequest(req.headers);
|
|
268
|
-
if (!options.
|
|
277
|
+
if (!concreteSelection && !options.compactionRoutingOverride && !options.internalDecisionCall && !threadSpawn && !memoryModelApplies && body && typeof body === "object" && !Array.isArray(body)) {
|
|
269
278
|
const shadowIntercept = config.shadowCallIntercept;
|
|
270
279
|
const rawShadowModel = (body as { model?: unknown }).model;
|
|
271
280
|
if (shadowIntercept?.enabled && shadowIntercept.model && typeof rawShadowModel === "string"
|
|
@@ -283,7 +292,12 @@ export async function prepareResponsesRequest(
|
|
|
283
292
|
}
|
|
284
293
|
}
|
|
285
294
|
}
|
|
286
|
-
const comboId = !
|
|
295
|
+
const comboId = !concreteSelection ? comboIdFromRawBody(body, config) : null;
|
|
296
|
+
// A decision model may be a plain model or a non-JEV combo. A JEV combo here would recurse
|
|
297
|
+
// into another decision, so the call is refused and the parent decision fails open.
|
|
298
|
+
if (options.internalDecisionCall && comboId && config.combos?.[comboId]?.strategy === "jev") {
|
|
299
|
+
return formatErrorResponse(400, "invalid_request_error", "A JEV decision model cannot be a JEV combo");
|
|
300
|
+
}
|
|
287
301
|
if (comboId && Object.hasOwn(config.combos ?? {}, comboId)) {
|
|
288
302
|
options.onRequestBodyRead?.();
|
|
289
303
|
return requestDispatchers.handleComboResponses(req, body, comboId, config, logCtx, {
|
|
@@ -507,13 +521,17 @@ export async function prepareResponsesRequest(
|
|
|
507
521
|
// request rather than a destination it never asked for.
|
|
508
522
|
const inboundSelector = parsed.modelId;
|
|
509
523
|
const admissionScope = resolveAdmissionModelScope(config, options.admission);
|
|
510
|
-
const captureInboundRoutePolicy = (candidate: RouteResult): RouteResult => {
|
|
524
|
+
const captureInboundRoutePolicy = (candidate: RouteResult, captureRequestPolicy = true): RouteResult => {
|
|
511
525
|
// Every route this request path produces passes through here: the direct
|
|
512
526
|
// name, an alias, a policy or combo selection, a compaction override, a
|
|
513
527
|
// shadow-intercept target and both subagent-fallback re-routes. Checking
|
|
514
528
|
// the key's scope at this one point is what stops a rewrite from reaching
|
|
515
529
|
// a destination the front door would have refused.
|
|
516
|
-
|
|
530
|
+
// A shadow-intercept target is probed before interception is decided, so it
|
|
531
|
+
// skips the request-owned policy capture here; the accepted target is
|
|
532
|
+
// captured where it becomes the request's route. A declined policy target
|
|
533
|
+
// must not lend its eligibility or "policy" route kind to the request.
|
|
534
|
+
if (captureRequestPolicy) capturePolicyRequestRoute(policyScope, candidate);
|
|
517
535
|
candidate.staticPolicy = captureRouteStaticPolicy(
|
|
518
536
|
candidate.providerName,
|
|
519
537
|
candidate.modelId,
|
|
@@ -521,6 +539,11 @@ export async function prepareResponsesRequest(
|
|
|
521
539
|
candidate.staticPolicy.effectiveAlias,
|
|
522
540
|
inboundWire,
|
|
523
541
|
);
|
|
542
|
+
// Fast-only keys authorize the same billed lane that final normalization serializes.
|
|
543
|
+
assertRouteAllowedByScope(admissionScope, inboundSelector, {
|
|
544
|
+
providerName: candidate.providerName,
|
|
545
|
+
modelId: previewXaiOauthWireModel(parsed, candidate, config, inboundWire),
|
|
546
|
+
});
|
|
524
547
|
return candidate;
|
|
525
548
|
};
|
|
526
549
|
try {
|
|
@@ -528,11 +551,12 @@ export async function prepareResponsesRequest(
|
|
|
528
551
|
// no canonical OpenAI route for (#2901). Only the initial compaction route
|
|
529
552
|
// may fall back to the configured default provider; combo attempts and the
|
|
530
553
|
// later fallback/recovery re-routes keep the ordinary reservation.
|
|
531
|
-
const
|
|
554
|
+
const routeInbound = (modelId: string) => concreteSelection
|
|
532
555
|
? routeConcreteModel(config, modelId)
|
|
533
556
|
: parsed._compactionRequest === true
|
|
534
557
|
? routeCompactionModel(config, modelId, evidenceFromBody(parsed._rawBody))
|
|
535
|
-
: routeModel(config, modelId, evidenceFromBody(parsed._rawBody))
|
|
558
|
+
: routeModel(config, modelId, evidenceFromBody(parsed._rawBody));
|
|
559
|
+
const resolveRoute = (modelId: string) => captureInboundRoutePolicy(routeInbound(modelId));
|
|
536
560
|
// The phase's destination. Resolved through the admission-scoped resolver every other route
|
|
537
561
|
// uses, and it fails closed exactly like the shadow target: falling back to the native model
|
|
538
562
|
// would spend the quota the operator routed away from, without their choosing it.
|
|
@@ -554,7 +578,7 @@ export async function prepareResponsesRequest(
|
|
|
554
578
|
}
|
|
555
579
|
const _sci = config.shadowCallIntercept;
|
|
556
580
|
let shadowRoute: RouteResult | undefined;
|
|
557
|
-
if (!memoryRoute && !options.memoryModelPhase && !options.compactionRoutingOverride && !threadSpawn && _sci?.enabled && _sci.model && isShadowSourceModel(parsed.modelId, _sci.sourceModels)) {
|
|
581
|
+
if (!concreteSelection && !memoryRoute && !options.memoryModelPhase && !options.compactionRoutingOverride && !options.internalDecisionCall && !threadSpawn && _sci?.enabled && _sci.model && isShadowSourceModel(parsed.modelId, _sci.sourceModels)) {
|
|
558
582
|
const sourcePrefix = shadowSourceModelPrefix(parsed.modelId, _sci.sourceModels)!;
|
|
559
583
|
let sourceIdentity = { providerName: OPENAI_CODEX_PROVIDER_ID, modelId: sourcePrefix };
|
|
560
584
|
try {
|
|
@@ -563,7 +587,7 @@ export async function prepareResponsesRequest(
|
|
|
563
587
|
} catch { /* Native Codex helper calls remain OpenAI-owned without an enabled OpenAI route. */ }
|
|
564
588
|
// A dead target fails this helper call once, before any send; it never falls through to
|
|
565
589
|
// the native source model or to the default provider (#5618).
|
|
566
|
-
const target = resolveShadowCallTarget(_sci.model,
|
|
590
|
+
const target = resolveShadowCallTarget(_sci.model, modelId => captureInboundRoutePolicy(routeInbound(modelId), false));
|
|
567
591
|
if ("unavailable" in target) {
|
|
568
592
|
logCtx.shadowCallRewrittenFrom = sanitizeLogMetadataString(sourcePrefix);
|
|
569
593
|
logCtx.errorCode = INTERCEPT_TARGET_UNAVAILABLE_CODE;
|
|
@@ -587,11 +611,12 @@ export async function prepareResponsesRequest(
|
|
|
587
611
|
);
|
|
588
612
|
// Helpers must not resume/append into the parent thread's Cursor conversation.
|
|
589
613
|
parsed._cursorIsolateConversation = true;
|
|
590
|
-
shadowRoute = targetRoute;
|
|
614
|
+
shadowRoute = capturePolicyRequestRoute(policyScope, targetRoute);
|
|
591
615
|
}
|
|
592
616
|
}
|
|
593
617
|
if (parsed._compactionRequest === true || options.compactionRoutingOverride) parsed._cursorIsolateConversation = true;
|
|
594
|
-
route = memoryRoute ?? shadowRoute ?? resolveRoute(
|
|
618
|
+
route = memoryRoute ?? shadowRoute ?? resolveRoute(options.policyFallbackCandidate
|
|
619
|
+
? `${options.policyFallbackCandidate.provider}/${options.policyFallbackCandidate.model}` : parsed.modelId);
|
|
595
620
|
// Name the phase in the persisted route decision, so the request log says why this turn went to
|
|
596
621
|
// the memory destination instead of leaving it looking like a plain user selection. Set here, on
|
|
597
622
|
// the resolved route, so a combo child's own route carries it too.
|
|
@@ -615,7 +640,10 @@ export async function prepareResponsesRequest(
|
|
|
615
640
|
if (parsed._compactionRequest === true) parsed._portableCompaction = true;
|
|
616
641
|
}
|
|
617
642
|
logCtx.routeDecision = route.routeDecision;
|
|
643
|
+
logCtx.policyEligibility = route.policyEligibility;
|
|
618
644
|
} catch (err) {
|
|
645
|
+
const policyRefusal = policyCandidateRefusalResponse(err);
|
|
646
|
+
if (policyRefusal) return policyRefusal;
|
|
619
647
|
if (err instanceof AdmissionModelDeniedError) return admissionModelDeniedResponse(err);
|
|
620
648
|
if (err instanceof NoAvailableComboTargetsError) {
|
|
621
649
|
return comboUnavailable(err.comboId);
|
|
@@ -827,7 +855,10 @@ export async function prepareResponsesRequest(
|
|
|
827
855
|
);
|
|
828
856
|
credentialDomainWasRewritten = true;
|
|
829
857
|
logCtx.routeDecision = route.routeDecision;
|
|
858
|
+
logCtx.policyEligibility = route.policyEligibility;
|
|
830
859
|
} catch (err) {
|
|
860
|
+
const policyRefusal = policyCandidateRefusalResponse(err);
|
|
861
|
+
if (policyRefusal) return policyRefusal;
|
|
831
862
|
if (err instanceof AdmissionModelDeniedError) return admissionModelDeniedResponse(err);
|
|
832
863
|
if (err instanceof NoAvailableComboTargetsError) {
|
|
833
864
|
return comboUnavailable(err.comboId);
|
|
@@ -1038,7 +1069,10 @@ export async function prepareResponsesRequest(
|
|
|
1038
1069
|
);
|
|
1039
1070
|
credentialDomainWasRewritten = true;
|
|
1040
1071
|
logCtx.routeDecision = route.routeDecision;
|
|
1072
|
+
logCtx.policyEligibility = route.policyEligibility;
|
|
1041
1073
|
} catch (err) {
|
|
1074
|
+
const policyRefusal = policyCandidateRefusalResponse(err);
|
|
1075
|
+
if (policyRefusal) return policyRefusal;
|
|
1042
1076
|
if (err instanceof AdmissionModelDeniedError) return admissionModelDeniedResponse(err);
|
|
1043
1077
|
if (err instanceof NoAvailableComboTargetsError) {
|
|
1044
1078
|
return comboUnavailable(err.comboId);
|
|
@@ -1178,6 +1212,7 @@ export async function prepareResponsesRequest(
|
|
|
1178
1212
|
// upstream for reliability (#875); the answer must then be reframed to SSE
|
|
1179
1213
|
// for streaming clients.
|
|
1180
1214
|
const clientRequestedStream = parsed.stream;
|
|
1215
|
+
const admittedPolicyRoute = { providerName: route.providerName, modelId: route.modelId };
|
|
1181
1216
|
await applyFinalRouteRequestNormalization({
|
|
1182
1217
|
parsed,
|
|
1183
1218
|
route,
|
|
@@ -1188,13 +1223,23 @@ export async function prepareResponsesRequest(
|
|
|
1188
1223
|
inboundTransport: options.inboundTransport,
|
|
1189
1224
|
claudeGoAffinity: options.claudeGoAffinity,
|
|
1190
1225
|
});
|
|
1226
|
+
try {
|
|
1227
|
+
assertPolicyNormalizedRoute(policyScope, admittedPolicyRoute, route);
|
|
1228
|
+
} catch (error) {
|
|
1229
|
+
const policyRefusal = policyCandidateRefusalResponse(error);
|
|
1230
|
+
if (policyRefusal) return policyRefusal;
|
|
1231
|
+
throw error;
|
|
1232
|
+
}
|
|
1191
1233
|
// Normalization is the last thing that can move the destination: resolving an
|
|
1192
1234
|
// OpenAI virtual model rewrites route.modelId to the wire id that will
|
|
1193
1235
|
// actually be billed. A scope checked only before this would authorize the
|
|
1194
1236
|
// public selector and send the wire model, so the settled route is checked
|
|
1195
1237
|
// once more here.
|
|
1196
|
-
|
|
1197
|
-
|
|
1238
|
+
const scopedDestination = parsed._wireModelOverride === undefined
|
|
1239
|
+
? route
|
|
1240
|
+
: { providerName: route.providerName, modelId: parsed._wireModelOverride };
|
|
1241
|
+
if (!routeAllowedByScope(admissionScope, scopedDestination)) {
|
|
1242
|
+
return admissionModelDeniedResponse(new AdmissionModelDeniedError(inboundSelector, scopedDestination));
|
|
1198
1243
|
}
|
|
1199
1244
|
// Attribute local auth/cooldown failures to the public selector too; exact auth may fail before
|
|
1200
1245
|
// the normal post-resolution provider label is assigned.
|
|
@@ -1304,6 +1349,13 @@ export async function prepareResponsesRequest(
|
|
|
1304
1349
|
|
|
1305
1350
|
route.provider = applyCodexAuthContextToProvider(route.provider, admissionState.authCtx, route.codexAccountMode);
|
|
1306
1351
|
applyCodexAccountGatedWireNormalization(parsed, route, logCtx);
|
|
1352
|
+
try {
|
|
1353
|
+
recordPolicyPreparedDestination(policyScope, route.providerName, parsed._wireModelOverride ?? parsed.modelId);
|
|
1354
|
+
} catch (error) {
|
|
1355
|
+
const policyRefusal = policyCandidateRefusalResponse(error);
|
|
1356
|
+
if (policyRefusal) return policyRefusal;
|
|
1357
|
+
throw error;
|
|
1358
|
+
}
|
|
1307
1359
|
logCtx.provider = route.codexAccountNamespace
|
|
1308
1360
|
? `${route.providerName}-${route.codexAccountNamespace}`
|
|
1309
1361
|
: formatCodexProviderForLog(route.providerName, codexLogAccountId(admissionState.authCtx), config);
|
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
import { anthropicFamilyRejected, claimAnthropicFamilyRevalidation } from "../../oauth/anthropic-model-quota";
|
|
2
|
+
import { anthropicRatePauseUntil } from "../../oauth/anthropic-rate-limit-policy";
|
|
1
3
|
import { bindAnthropicRefusalCredential } from "../../oauth/anthropic-account-refusal";
|
|
2
4
|
import type { ResponsesRequestContext, ResponsesAdmissionState } from "./core-options";
|
|
3
5
|
import type { PreparedResponsesRequest } from "./request-prepare";
|
|
@@ -20,6 +22,7 @@ import {
|
|
|
20
22
|
isAnthropicAccountPoolEnabled,
|
|
21
23
|
getAnthropicPoolAccessSnapshot,
|
|
22
24
|
getAnthropicAccountHealthSnapshot,
|
|
25
|
+
AnthropicAccountCooldownError,
|
|
23
26
|
getEligibleAnthropicAccounts,
|
|
24
27
|
commitAnthropicSelectionRouting,
|
|
25
28
|
formatAnthropicProviderForLog,
|
|
@@ -174,7 +177,7 @@ export async function prepareResponsesTransport(
|
|
|
174
177
|
? await getAnthropicPoolAccessSnapshot(oauthSelection.accountId)
|
|
175
178
|
: await getValidAccessSnapshotForAccount(route.providerName, oauthSelection.accountId, { requireUsableAccount: true });
|
|
176
179
|
}
|
|
177
|
-
if (anthropicRouteDecision && !routeCandidates(getEligibleAnthropicAccounts(), anthropicRouteDecision).includes(candidate.accountId)) return null;
|
|
180
|
+
if (anthropicRouteDecision && !routeCandidates(getEligibleAnthropicAccounts(Date.now(), route.modelId), anthropicRouteDecision).includes(candidate.accountId)) return null;
|
|
178
181
|
const committed = await commitOAuthAccountSelection(route.providerName, candidate.accountId, {
|
|
179
182
|
expectedSelection: oauthSelection,
|
|
180
183
|
expectedCredentialGeneration: candidate.generation,
|
|
@@ -183,7 +186,7 @@ export async function prepareResponsesTransport(
|
|
|
183
186
|
if (committed) {
|
|
184
187
|
if (route.providerName === "anthropic" && !commitAnthropicSelectionRouting(
|
|
185
188
|
candidate.accountId, oauthSelection, committed,
|
|
186
|
-
{ config, sessionKey: anthropicSessionKey, reason: anthropicReason, expectedCredentialGeneration: candidate.generation, routeDecision: anthropicRouteDecision },
|
|
189
|
+
{ config, sessionKey: anthropicSessionKey, reason: anthropicReason, expectedCredentialGeneration: candidate.generation, routeDecision: anthropicRouteDecision, model: route.modelId },
|
|
187
190
|
)) return null;
|
|
188
191
|
oauthSelection = committed;
|
|
189
192
|
servingOAuthSnapshot = candidate;
|
|
@@ -197,7 +200,7 @@ export async function prepareResponsesTransport(
|
|
|
197
200
|
// A revision also changes on per-account policy edits. Re-evaluate the selector
|
|
198
201
|
// after credential waits even without a model route, rather than reusing stale active.
|
|
199
202
|
const revisedAnthropic = route.providerName === "anthropic"
|
|
200
|
-
? resolveAnthropicAccountForSession(anthropicSessionKey, config, Date.now(), anthropicRouteDecision) : null;
|
|
203
|
+
? resolveAnthropicAccountForSession(anthropicSessionKey, config, Date.now(), anthropicRouteDecision, route.modelId) : null;
|
|
201
204
|
const revisedAnthropicId = revisedAnthropic?.accountId;
|
|
202
205
|
if (route.providerName === "anthropic" && !revisedAnthropicId) return null;
|
|
203
206
|
if (revisedAnthropic) anthropicReason = revisedAnthropic.reason;
|
|
@@ -217,8 +220,11 @@ export async function prepareResponsesTransport(
|
|
|
217
220
|
const admitted = await commitResolvedOAuthSelection(candidate);
|
|
218
221
|
if (!admitted) throw new Error("OAuth selection changed during credential recovery");
|
|
219
222
|
if (kiroLoadEnabled && options.accountLoad?.lease?.accountId !== admitted.accountId) {
|
|
220
|
-
const
|
|
221
|
-
|
|
223
|
+
const signal = options.abortSignal ?? req.signal;
|
|
224
|
+
const replayLease = await acquireAccountLease("kiro", admitted.accountId, {
|
|
225
|
+
maxConcurrentPerAccount: kiroCap, signal,
|
|
226
|
+
});
|
|
227
|
+
if (!replayLease || !options.accountLoad || options.accountLoad.cancelled || signal.aborted) {
|
|
222
228
|
replayLease?.release();
|
|
223
229
|
throw new Error("Kiro replay account capacity is full");
|
|
224
230
|
}
|
|
@@ -263,16 +269,20 @@ export async function prepareResponsesTransport(
|
|
|
263
269
|
retryParsed: OcxParsedRequest = parsed,
|
|
264
270
|
): Promise<OAuthAccessSnapshot | null> => {
|
|
265
271
|
if (route.provider.googleMode === "cloud-code-assist" && !snapshot.projectId) return null;
|
|
272
|
+
const signal = options.abortSignal ?? req.signal;
|
|
266
273
|
let speculative = kiroLoadEnabled && options.accountLoad?.lease?.accountId !== snapshot.accountId
|
|
267
|
-
? await acquireAccountLease("kiro", snapshot.accountId, { maxConcurrentPerAccount: kiroCap }) : null;
|
|
274
|
+
? await acquireAccountLease("kiro", snapshot.accountId, { maxConcurrentPerAccount: kiroCap, signal }) : null;
|
|
275
|
+
if (speculative && (options.accountLoad?.cancelled || signal.aborted)) { speculative.release(); return null; }
|
|
268
276
|
if (kiroLoadEnabled && options.accountLoad?.lease?.accountId !== snapshot.accountId && !speculative) return null;
|
|
269
277
|
let committed: OAuthAccessSnapshot | null;
|
|
270
278
|
try { committed = await commitResolvedOAuthSelection(snapshot); }
|
|
271
279
|
catch (error) { speculative?.release(); throw error; }
|
|
272
280
|
if (!committed) { speculative?.release(); return null; }
|
|
281
|
+
if (speculative && (options.accountLoad?.cancelled || signal.aborted)) { speculative.release(); return null; }
|
|
273
282
|
if (kiroLoadEnabled && committed.accountId !== (speculative?.accountId ?? options.accountLoad?.lease?.accountId)) {
|
|
274
283
|
speculative?.release();
|
|
275
|
-
speculative = await acquireAccountLease("kiro", committed.accountId, { maxConcurrentPerAccount: kiroCap });
|
|
284
|
+
speculative = await acquireAccountLease("kiro", committed.accountId, { maxConcurrentPerAccount: kiroCap, signal });
|
|
285
|
+
if (speculative && (options.accountLoad?.cancelled || signal.aborted)) { speculative.release(); return null; }
|
|
276
286
|
if (!speculative) return null;
|
|
277
287
|
}
|
|
278
288
|
if (speculative && options.accountLoad) {
|
|
@@ -299,6 +309,12 @@ export async function prepareResponsesTransport(
|
|
|
299
309
|
parsed._kiroAuthContext = kiroContext;
|
|
300
310
|
if (retryParsed !== parsed) retryParsed._kiroAuthContext = { ...kiroContext };
|
|
301
311
|
}
|
|
312
|
+
if (route.providerName === "zed") {
|
|
313
|
+
// Zed signs with its own user id; `snapshot.accountId` is the local slot hash.
|
|
314
|
+
const zedContext = { userId: snapshot.providerUserId ?? "" };
|
|
315
|
+
parsed._zedAuthContext = zedContext;
|
|
316
|
+
if (retryParsed !== parsed) retryParsed._zedAuthContext = { ...zedContext };
|
|
317
|
+
}
|
|
302
318
|
// Re-stamp: a request that rotated accounts must be attributed to the account that actually
|
|
303
319
|
// served it. All three rotation sites funnel through here, so this is the only re-stamp
|
|
304
320
|
// needed -- and putting it anywhere else would let one of the three drift.
|
|
@@ -345,7 +361,7 @@ export async function prepareResponsesTransport(
|
|
|
345
361
|
const row = getAccountCredentialWithStatus(route.providerName, binding.snapshot.accountId);
|
|
346
362
|
return selected?.accountId === binding.selection.accountId && selected?.revision === binding.selection.revision
|
|
347
363
|
&& !!row && !row.paused && !row.needsReauth && row.credential.expires > Date.now()
|
|
348
|
-
&& (route.providerName !== "anthropic" || !getAnthropicAccountHealthSnapshot(binding.snapshot.accountId))
|
|
364
|
+
&& (route.providerName !== "anthropic" || !getAnthropicAccountHealthSnapshot(binding.snapshot.accountId) && !anthropicRatePauseUntil(binding.snapshot.accountId) && !anthropicFamilyRejected(binding.snapshot.accountId, route.modelId))
|
|
349
365
|
&& credentialGeneration(row.credential) === binding.snapshot.generation;
|
|
350
366
|
};
|
|
351
367
|
const resolveSelectionAdapter = (provider: OcxProviderConfig, retention = config.cacheRetention): ProviderAdapter => {
|
|
@@ -412,11 +428,11 @@ export async function prepareResponsesTransport(
|
|
|
412
428
|
let candidate = servingOAuthSnapshot;
|
|
413
429
|
if (route.providerName === "anthropic") {
|
|
414
430
|
oauthSelection = captureOAuthAccountSelection(route.providerName);
|
|
415
|
-
const accountId = await resolveAnthropicDispatchAccountId(config, anthropicSessionKey, anthropicRouteDecision);
|
|
431
|
+
const accountId = await resolveAnthropicDispatchAccountId(config, anthropicSessionKey, anthropicRouteDecision, route.modelId);
|
|
416
432
|
candidate = await getAnthropicPoolAccessSnapshot(accountId);
|
|
417
433
|
}
|
|
418
434
|
if (!candidate || !await applyFailoverSnapshot(candidate, requestParsed)) {
|
|
419
|
-
if (route.providerName === "anthropic") await resolveAnthropicDispatchAccountId(config, anthropicSessionKey, anthropicRouteDecision);
|
|
435
|
+
if (route.providerName === "anthropic") await resolveAnthropicDispatchAccountId(config, anthropicSessionKey, anthropicRouteDecision, route.modelId);
|
|
420
436
|
throw new Error("OAuth account selection changed before dispatch");
|
|
421
437
|
}
|
|
422
438
|
} else {
|
|
@@ -503,18 +519,22 @@ export async function prepareResponsesTransport(
|
|
|
503
519
|
// Reselection can choose a provider override instead of the supplied executor.
|
|
504
520
|
// Either way the send crosses the physical boundary, so the connection policy is
|
|
505
521
|
// applied around whichever implementation was just selected (#4992).
|
|
506
|
-
commitKeyAttemptSend();
|
|
507
522
|
// The binding travels with the send, so a rebuilt request resolves its provider route
|
|
508
523
|
// against the destination it is actually going to rather than the one this dispatch
|
|
509
524
|
// started with. Account reselection can move the upstream host, which would otherwise
|
|
510
525
|
// apply a host-scoped decision to a different host.
|
|
511
|
-
const
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
|
|
517
|
-
|
|
526
|
+
const releaseFamily = snapshot ? claimAnthropicFamilyRevalidation(snapshot.accountId, route.modelId) : () => {};
|
|
527
|
+
if (!releaseFamily) throw new AnthropicAccountCooldownError(1);
|
|
528
|
+
let response: Response;
|
|
529
|
+
try {
|
|
530
|
+
commitKeyAttemptSend();
|
|
531
|
+
response = await sendWithConnectionPolicy(
|
|
532
|
+
fetchImpl,
|
|
533
|
+
destination,
|
|
534
|
+
{ ...dispatchInit, redirect: "manual" },
|
|
535
|
+
{ providerName: route.providerName, provider: route.provider },
|
|
536
|
+
);
|
|
537
|
+
} finally { releaseFamily(); }
|
|
518
538
|
// Observe each physical response before retries replace it. The binding belongs to
|
|
519
539
|
// this dispatch, so a manual switch cannot file A's headers against B. Header
|
|
520
540
|
// overrides and credential replacement make ownership unprovable: skip those writes.
|
|
@@ -523,10 +543,11 @@ export async function prepareResponsesTransport(
|
|
|
523
543
|
const current = getAccountCredentialWithStatus("anthropic", snapshot.accountId);
|
|
524
544
|
if (current && !current.needsReauth && credentialGeneration(current.credential) === snapshot.generation) {
|
|
525
545
|
bindAnthropicRefusalCredential(response, snapshot);
|
|
526
|
-
recordAnthropicAccountQuotaFromHeaders(snapshot.accountId, response.headers, writerGeneration);
|
|
546
|
+
recordAnthropicAccountQuotaFromHeaders(snapshot.accountId, response.headers, writerGeneration, response.status, route.modelId);
|
|
527
547
|
}
|
|
528
548
|
} catch { /* best-effort observation cannot fail the response */ }
|
|
529
549
|
}
|
|
550
|
+
if (!response.ok) await recordKeyAttemptFailure(logCtx, response, dispatchInit.signal ?? options.abortSignal);
|
|
530
551
|
return response;
|
|
531
552
|
}
|
|
532
553
|
const nextAdapter = await refreshDispatchAdapter(requestParsed);
|
|
@@ -577,13 +598,13 @@ export async function prepareResponsesTransport(
|
|
|
577
598
|
const routeResult = resolveAnthropicModelRoute(config, route.modelId);
|
|
578
599
|
if (routeResult.error) return formatErrorResponse(400, "invalid_request_error", `Invalid Anthropic model routes: ${routeResult.error}`);
|
|
579
600
|
anthropicRouteDecision = routeResult.decision;
|
|
580
|
-
const selection = resolveAnthropicAccountForSession(anthropicSessionKey, config, Date.now(), anthropicRouteDecision);
|
|
601
|
+
const selection = resolveAnthropicAccountForSession(anthropicSessionKey, config, Date.now(), anthropicRouteDecision, route.modelId);
|
|
581
602
|
if (!selection.accountId) {
|
|
582
603
|
if (selection.reason === "paused") return formatErrorResponse(403, "permission_error", "Anthropic OAuth accounts are paused. Resume an account in account settings and retry.");
|
|
583
604
|
// Route names may resemble account IDs; log only the matched rule position.
|
|
584
605
|
if (anthropicRouteDecision) console.warn(`[anthropic-pool] route:#${anthropicRouteDecision.position} ${selection.reason}; answering locally`);
|
|
585
606
|
if (selection.reason === "all-cooled") {
|
|
586
|
-
const retryAfterSec = getAnthropicPoolRetryAfterSeconds(Date.now(), anthropicRouteDecision);
|
|
607
|
+
const retryAfterSec = getAnthropicPoolRetryAfterSeconds(Date.now(), anthropicRouteDecision, route.modelId);
|
|
587
608
|
return formatErrorResponse(
|
|
588
609
|
429,
|
|
589
610
|
"rate_limit_error",
|
|
@@ -667,11 +688,18 @@ export async function prepareResponsesTransport(
|
|
|
667
688
|
if (safetyAlternateId && admitted.accountId !== safetyAlternateId)
|
|
668
689
|
return formatErrorResponse(409, "conflict_error", "OAuth account selection changed; retry the request");
|
|
669
690
|
if (kiroLoadEnabled) {
|
|
691
|
+
const transportSignal = options.abortSignal ?? req.signal;
|
|
670
692
|
const lease = await acquireAccountLease("kiro", admitted.accountId, {
|
|
671
|
-
maxConcurrentPerAccount: kiroCap, waitMs: KIRO_ACCOUNT_WAIT_MS, signal:
|
|
693
|
+
maxConcurrentPerAccount: kiroCap, waitMs: KIRO_ACCOUNT_WAIT_MS, signal: transportSignal,
|
|
672
694
|
});
|
|
673
|
-
if (!lease) return
|
|
695
|
+
if (!lease) return transportSignal.aborted
|
|
674
696
|
? clientCancelledResponse() : capacityResponse();
|
|
697
|
+
// The lease may be granted between the holder's cleanup and this install;
|
|
698
|
+
// re-check cancellation before the holder can no longer reach it.
|
|
699
|
+
if (options.accountLoad?.cancelled || transportSignal.aborted) {
|
|
700
|
+
lease.release();
|
|
701
|
+
return clientCancelledResponse();
|
|
702
|
+
}
|
|
675
703
|
if (options.accountLoad) options.accountLoad.lease = lease;
|
|
676
704
|
else lease.release();
|
|
677
705
|
}
|
|
@@ -717,6 +745,9 @@ export async function prepareResponsesTransport(
|
|
|
717
745
|
// Only genuinely accountless adapter calls leave the context undefined and use local/env fallback.
|
|
718
746
|
parsed._kiroAuthContext = { ...(resolved.kiro ?? {}) };
|
|
719
747
|
}
|
|
748
|
+
if (route.providerName === "zed") {
|
|
749
|
+
parsed._zedAuthContext = { userId: resolved.providerUserId ?? "" };
|
|
750
|
+
}
|
|
720
751
|
// Project identity belongs to the admitted account on EVERY request, including
|
|
721
752
|
// the request after a pool transition made that account the persisted active one.
|
|
722
753
|
if (route.provider.googleMode === "cloud-code-assist") {
|
|
@@ -177,6 +177,11 @@ export async function executeResponsesSidecars(
|
|
|
177
177
|
const imgPlan = !routedCompaction ? await planImageBridge(config, parsed, route.provider) : undefined;
|
|
178
178
|
const vidPlan = !routedCompaction ? await planVideoBridge(config, parsed, route.provider) : undefined;
|
|
179
179
|
const canRunWebSearch = !!wsPlan && !transportState.adapter.runTurn;
|
|
180
|
+
let sidecarOutputStarted = false;
|
|
181
|
+
const sidecarHasCommittedOutput = () => sidecarOutputStarted
|
|
182
|
+
|| (logCtx.activeAttempt?.deliverySummary?.semanticBytes ?? 0) > 0
|
|
183
|
+
|| (logCtx.activeAttempt?.deliverySummary?.sideEffectEvents ?? 0) > 0;
|
|
184
|
+
const noteSidecarOutput = () => { sidecarOutputStarted = true; options.onFirstOutput?.(); };
|
|
180
185
|
const rotateSidecarProviderOn429 = async (
|
|
181
186
|
retryAfter: string | null,
|
|
182
187
|
responseHeaders?: Headers,
|
|
@@ -270,7 +275,8 @@ export async function executeResponsesSidecars(
|
|
|
270
275
|
const nextAccountId = await rotateAnthropicAccountOnResponse(
|
|
271
276
|
originalResponse ?? new Response(null, { status: 429, headers: responseHeaders ?? (retryAfter ? { "retry-after": retryAfter } : undefined) }), {
|
|
272
277
|
config, accountId: transportState.anthropicPoolAccountId, sessionKey: anthropicSessionKey,
|
|
273
|
-
decision: transportState.anthropicRouteDecision, signal: options.abortSignal,
|
|
278
|
+
model: route.modelId, requestKey: transportState, decision: transportState.anthropicRouteDecision, signal: options.abortSignal,
|
|
279
|
+
allow429Recovery: !sidecarHasCommittedOutput(), allowAccountRefusal: !sidecarHasCommittedOutput(),
|
|
274
280
|
canRetry: hop.allowed && transportState.anthropicPoolFailovers < ANTHROPIC_POOL_MAX_FAILOVERS_PER_REQUEST,
|
|
275
281
|
});
|
|
276
282
|
if (!nextAccountId) {
|
|
@@ -422,7 +428,7 @@ export async function executeResponsesSidecars(
|
|
|
422
428
|
on429: rotateSidecarProviderOn429,
|
|
423
429
|
retryOn429Policy: route.providerName === "kiro" && isGenericOAuthFailoverEnabled(config, "kiro")
|
|
424
430
|
? null : rateLimitRetryPolicyFor(route.provider),
|
|
425
|
-
|
|
431
|
+
onFirstOutput: noteSidecarOutput,
|
|
426
432
|
...(options.forceEmptyResponseId ? { forceEmptyResponseId: true } : {}),
|
|
427
433
|
onCompletedResponse: (response, providerState) => {
|
|
428
434
|
const served = transportState.replayOAuthCredentialSnapshot;
|
|
@@ -493,7 +499,7 @@ export async function executeResponsesSidecars(
|
|
|
493
499
|
maxSearches: wsPlan.maxSearches,
|
|
494
500
|
forceEmptyResponseId: true,
|
|
495
501
|
abortSignal: options.abortSignal,
|
|
496
|
-
|
|
502
|
+
onFirstOutput: noteSidecarOutput,
|
|
497
503
|
onRequestBuilt: request => {
|
|
498
504
|
recordAdapterReasoning(logCtx, request);
|
|
499
505
|
recordAdapterTier(logCtx, request);
|