@bitkyc08/opencodex 2.80.0 → 2.81.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +3 -3
- package/gui/dist/assets/App-7yhoys8a.js +54 -0
- package/gui/dist/assets/Tray-EwAOsP0S.js +1 -0
- package/gui/dist/assets/index-RsoaAxXY.js +94 -0
- package/gui/dist/assets/quota-summary-D2lN8tDm.js +1 -0
- package/gui/dist/index.html +1 -1
- package/gui/dist/provider-icons/claude-green.svg +1 -0
- package/package.json +3 -2
- package/src/adapters/anthropic-model-contract.ts +8 -2
- package/src/adapters/anthropic.ts +7 -2
- package/src/adapters/codebuddy/mcp-server.ts +2 -25
- package/src/adapters/codebuddy/tool-bridge.ts +1 -7
- package/src/adapters/coding-agent/mcp-server.ts +33 -0
- package/src/adapters/coding-agent/protocol.ts +122 -17
- package/src/adapters/coding-agent/tool-catalog.ts +75 -0
- package/src/adapters/coding-agent/turn.ts +101 -44
- package/src/adapters/cursor/catalog.ts +8 -0
- package/src/adapters/cursor/effort-map.ts +2 -0
- package/src/adapters/devin/live-models.ts +2 -0
- package/src/adapters/kiro-retry.ts +9 -4
- package/src/adapters/openai-chat/messages.ts +7 -6
- package/src/adapters/physical-send.ts +8 -5
- package/src/adapters/qoder/adapter.ts +41 -6
- package/src/adapters/qoder/scaffold-guard.ts +2 -3
- package/src/adapters/zed.ts +16 -1
- package/src/claude/inbound-cache-stabilize.ts +4 -9
- package/src/claude/inbound.ts +1 -0
- package/src/claude/intercept/model-bindings.ts +1 -0
- package/src/claude/intercept/picker-listener.ts +71 -4
- package/src/cli/account-anthropic-threshold.ts +14 -5
- package/src/cli/account-api.ts +3 -1
- package/src/cli/account-extended.ts +13 -9
- package/src/cli/account-policy-dto.ts +1 -1
- package/src/cli/account-policy.ts +13 -4
- package/src/cli/account.ts +3 -3
- package/src/cli/capabilities-accounts.ts +2 -2
- package/src/cli/capabilities-agents-routing.ts +24 -0
- package/src/cli/capabilities-integrations.ts +16 -0
- package/src/cli/codex-shim-autorestore.ts +3 -0
- package/src/cli/dispatch.ts +10 -1
- package/src/cli/help.ts +4 -1
- package/src/cli/index.ts +11 -5
- package/src/cli/integration-plan-dto.ts +1 -0
- package/src/cli/integration-preview.ts +8 -1
- package/src/cli/integrations.ts +32 -3
- package/src/cli/message-args.ts +34 -0
- package/src/cli/message-command.ts +43 -0
- package/src/cli/message-runtime.ts +6 -0
- package/src/cli/observe.ts +3 -1
- package/src/cli/provider.ts +29 -6
- package/src/cli/registry.ts +24 -2
- package/src/clients/config-export/commandcode.ts +131 -0
- package/src/clients/config-export/contracts.ts +12 -0
- package/src/clients/config-export.ts +64 -1
- package/src/codex/auth-api/account-list.ts +11 -3
- package/src/codex/auth-api/main-account-probe.ts +25 -8
- package/src/codex/auth-api/pool-mode-gate.ts +33 -9
- package/src/codex/auth-api/pool-quota-probe.ts +15 -1
- package/src/codex/auth-api/routes.ts +1 -0
- package/src/codex/catalog/auto-review.ts +13 -6
- package/src/codex/catalog/build-entries.ts +15 -7
- package/src/codex/catalog/control-plane.ts +45 -0
- package/src/codex/catalog/effort.ts +11 -1
- package/src/codex/catalog/gather-capture.ts +5 -3
- package/src/codex/catalog/parsing.ts +2 -1
- package/src/codex/catalog/provider-models.ts +15 -9
- package/src/codex/control-plane-models.ts +6 -0
- package/src/combos/jev-decision-contract.ts +25 -2
- package/src/combos/jev.ts +3 -2
- package/src/combos/types.ts +1 -1
- package/src/config/diagnostics.ts +27 -16
- package/src/config/load-degrade.ts +34 -0
- package/src/config/schema/anthropic-account-pool.ts +143 -0
- package/src/config/schema/config-schema.ts +7 -1
- package/src/config/schema/leaf-validators.ts +4 -0
- package/src/config.ts +2 -0
- package/src/generated/compatibility-version.json +320 -184
- package/src/generated/model-metadata.ts +5 -4
- package/src/images/loop.ts +2 -1
- package/src/integrations/catalog-refresh.ts +1 -1
- package/src/integrations/mutation-plan.ts +7 -0
- package/src/integrations/registry.ts +8 -0
- package/src/integrations/state.ts +9 -2
- package/src/integrations/writer.ts +8 -2
- package/src/lib/lab-live-route-production.ts +9 -1
- package/src/lib/request-execution-budget.ts +236 -58
- package/src/lib/spend-pool-alias-validation.ts +79 -0
- package/src/lib/spend-pool-continuity.ts +34 -0
- package/src/lib/spend-reservation-ledger.ts +412 -50
- package/src/lib/state-store-registrations.ts +20 -3
- package/src/lib/upstream-retry.ts +17 -5
- package/src/lib/workflow-budget.ts +53 -11
- package/src/messaging/budget.ts +45 -0
- package/src/messaging/discovery.ts +57 -0
- package/src/messaging/envelope.ts +49 -0
- package/src/messaging/input.ts +37 -0
- package/src/messaging/rpc.ts +196 -0
- package/src/messaging/send.ts +58 -0
- package/src/messaging/socket.ts +72 -0
- package/src/messaging/types.ts +29 -0
- package/src/oauth/anthropic-account-refusal.ts +51 -17
- package/src/oauth/anthropic-account-threshold.ts +8 -2
- package/src/oauth/anthropic-continuity.ts +15 -1
- package/src/oauth/anthropic-model-quota.ts +120 -48
- package/src/oauth/anthropic-model-routes.ts +11 -3
- package/src/oauth/anthropic-oauth-definitions.ts +98 -0
- package/src/oauth/anthropic-pool-config.ts +30 -0
- package/src/oauth/anthropic-rate-limit-policy.ts +64 -9
- package/src/oauth/anthropic-routing.ts +1202 -866
- package/src/oauth/anthropic-send-ownership.ts +41 -0
- package/src/oauth/anthropic.ts +4 -1
- package/src/oauth/generic-account-failover.ts +3 -1
- package/src/oauth/health.ts +8 -6
- package/src/oauth/index.ts +90 -173
- package/src/oauth/kiro.ts +22 -9
- package/src/oauth/login-cli.ts +2 -0
- package/src/oauth/model-discovery-auth.ts +43 -0
- package/src/oauth/pool-kernel.ts +11 -5
- package/src/oauth/pool-settings-capability.ts +5 -3
- package/src/oauth/store-anthropic-instance.ts +87 -0
- package/src/oauth/store.ts +67 -9
- package/src/oauth/token-guardian.ts +2 -0
- package/src/oauth/types.ts +2 -0
- package/src/protocols/plan-snapshot.ts +5 -0
- package/src/protocols/settings.ts +20 -5
- package/src/providers/anthropic-instance-id.ts +47 -0
- package/src/providers/anthropic-instance.ts +48 -0
- package/src/providers/anthropic-reset-grant-ledger.ts +11 -1
- package/src/providers/derive.ts +2 -0
- package/src/providers/kiro-models.ts +3 -0
- package/src/providers/label.ts +1 -1
- package/src/providers/model-rename-fields.ts +2 -0
- package/src/providers/quota/account-cache.ts +37 -11
- package/src/providers/quota/anthropic-account-quota.ts +93 -0
- package/src/providers/quota/anthropic-cooldown-recovery.ts +222 -111
- package/src/providers/quota/report-cache.ts +24 -1
- package/src/providers/quota/vendor-probes-oauth.ts +46 -14
- package/src/providers/quota.ts +22 -32
- package/src/providers/registry/entries-core.ts +35 -30
- package/src/providers/registry/model-ids.ts +1 -0
- package/src/providers/registry/model-seeds.ts +7 -4
- package/src/providers/registry/types.ts +3 -0
- package/src/providers/registry.ts +3 -3
- package/src/responses/spill-store.ts +5 -1
- package/src/responses/state/metrics.ts +87 -0
- package/src/responses/state/snapshot-codec.ts +6 -0
- package/src/responses/state/spill-queue.ts +1 -0
- package/src/responses/state/unforced-store-false.ts +55 -0
- package/src/responses/state.ts +50 -66
- package/src/router.ts +28 -4
- package/src/routing/compatibility/assemble.ts +1 -1
- package/src/routing/quota.ts +7 -3
- package/src/server/auth-cors.ts +5 -0
- package/src/server/chat-completions.ts +6 -1
- package/src/server/chat-native.ts +45 -25
- package/src/server/claude-messages.ts +16 -2
- package/src/server/gui-static.ts +38 -4
- package/src/server/index/spend-ledger-lifecycle.ts +10 -4
- package/src/server/index.ts +4 -2
- package/src/server/inference/context.ts +1 -1
- package/src/server/lifecycle.ts +1 -1
- package/src/server/management/agent-settings-routes.ts +30 -6
- package/src/server/management/anthropic-account-threshold.ts +12 -9
- package/src/server/management/anthropic-pool-settings.ts +72 -0
- package/src/server/management/anthropic-reset-grant-routes.ts +98 -39
- package/src/server/management/config-routes.ts +27 -7
- package/src/server/management/decision-routes.ts +2 -1
- package/src/server/management/integration-routes.ts +25 -2
- package/src/server/management/oauth-account-routes.ts +79 -98
- package/src/server/management/provider-instance-ownership.ts +44 -0
- package/src/server/management/provider-routes.ts +21 -28
- package/src/server/management/route-registry.ts +3 -1
- package/src/server/management/routing-profile-routes.ts +4 -4
- package/src/server/management/system-restart.ts +44 -14
- package/src/server/management/system-routes.ts +24 -2
- package/src/server/management/vision-sidecar-options.ts +9 -6
- package/src/server/management/web-search-sidecar-options.ts +51 -2
- package/src/server/management-auth.ts +6 -0
- package/src/server/messages-native-eligibility.ts +3 -2
- package/src/server/messages-native-oauth.ts +86 -41
- package/src/server/messages-native-selector.ts +39 -0
- package/src/server/messages-native.ts +84 -25
- package/src/server/request-log.ts +5 -0
- package/src/server/responses/adapter-continuation.ts +29 -17
- package/src/server/responses/adapter-dispatch.ts +47 -17
- package/src/server/responses/antigravity-validation-refusal.ts +20 -6
- package/src/server/responses/compact.ts +25 -8
- package/src/server/responses/compaction-recovery.ts +1 -1
- package/src/server/responses/core-combo-native.ts +4 -1
- package/src/server/responses/core-combo.ts +16 -11
- package/src/server/responses/core-normalize.ts +1 -0
- package/src/server/responses/core-options.ts +3 -1
- package/src/server/responses/core-replay.ts +5 -0
- package/src/server/responses/passthrough-delivery.ts +7 -3
- package/src/server/responses/passthrough-dispatch.ts +21 -8
- package/src/server/responses/request-prepare.ts +37 -5
- package/src/server/responses/request-send-budget.ts +74 -37
- package/src/server/responses/request-spend.ts +325 -33
- package/src/server/responses/request-transport.ts +132 -52
- package/src/server/responses/run-turn-execution.ts +11 -3
- package/src/server/responses/sidecar-execution.ts +96 -14
- package/src/server/responses-undeclared-tool-guard.ts +5 -1
- package/src/server/workflow-refusal.ts +30 -0
- package/src/service/ownership-mutation-lease.d.mts +1 -1
- package/src/service/ownership-mutation-lease.mjs +5 -7
- package/src/sidecar/anthropic-binding.ts +79 -0
- package/src/sidecar/auth.ts +76 -2
- package/src/types/anthropic-account-pool.ts +33 -0
- package/src/types/config.ts +12 -28
- package/src/types/provider.ts +5 -0
- package/src/types/tools.ts +7 -0
- package/src/types.ts +4 -0
- package/src/usage/cost.ts +3 -2
- package/src/usage/expected-prices.ts +66 -14
- package/src/vision/anthropic-describe.ts +16 -3
- package/src/vision/index.ts +3 -0
- package/src/vision/plan.ts +19 -6
- package/src/web-search/alpha-search.ts +7 -1
- package/src/web-search/anthropic-executor.ts +16 -4
- package/src/web-search/index.ts +7 -3
- package/src/web-search/loop.ts +27 -5
- package/src/web-search/passthrough-bridge.ts +4 -1
- package/src/web-search/sidecar-providers.ts +4 -3
- package/gui/dist/assets/App-MbDVfUeC.js +0 -54
- package/gui/dist/assets/Tray-CzkxK82A.js +0 -1
- package/gui/dist/assets/index-UJt6npXk.js +0 -94
- package/gui/dist/assets/quota-summary-DU3sD6Z-.js +0 -1
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { rotateAnthropicAccountOnResponseForInstance } from "../../oauth/anthropic-account-refusal";
|
|
2
2
|
import type { ResponsesRequestContext } from "./core-options";
|
|
3
3
|
import type { PreparedResponsesRequest } from "./request-prepare";
|
|
4
4
|
import type { ResponsesTransport } from "./request-transport";
|
|
@@ -35,7 +35,7 @@ import { resolveWireProtocolOverride } from "../adapter-resolve";
|
|
|
35
35
|
import { bindRouteReasoningReplayScope } from "./core-replay";
|
|
36
36
|
import {
|
|
37
37
|
ANTHROPIC_POOL_MAX_FAILOVERS_PER_REQUEST,
|
|
38
|
-
|
|
38
|
+
anthropicRoutingFor,
|
|
39
39
|
formatAnthropicProviderForLog,
|
|
40
40
|
} from "../../oauth/anthropic-routing";
|
|
41
41
|
import {
|
|
@@ -83,6 +83,8 @@ export function createAdapterContinuations(
|
|
|
83
83
|
| "oauthDispatch"
|
|
84
84
|
| "invalidateSameTargetRequest"
|
|
85
85
|
| "resolveSelectionAdapter"
|
|
86
|
+
| "anthropicInstance"
|
|
87
|
+
| "currentAnthropicRouteDecision"
|
|
86
88
|
| "anthropicRouteDecision"
|
|
87
89
|
| "anthropicPoolAccountId"
|
|
88
90
|
| "anthropicPoolFailovers"
|
|
@@ -102,7 +104,8 @@ export function createAdapterContinuations(
|
|
|
102
104
|
| "noteAdapterPhysicalSend"
|
|
103
105
|
| "noteAdapterRecoveryWithheld"
|
|
104
106
|
| "remainingTransientSendBudget"
|
|
105
|
-
| "
|
|
107
|
+
| "adapterSendBudget"
|
|
108
|
+
| "transientSendReporter"
|
|
106
109
|
| "reserveCredentialHop"
|
|
107
110
|
| "pendingHopPermit"
|
|
108
111
|
| "sendBudgetExhausted"
|
|
@@ -122,6 +125,8 @@ export function createAdapterContinuations(
|
|
|
122
125
|
oauthDispatch,
|
|
123
126
|
invalidateSameTargetRequest,
|
|
124
127
|
resolveSelectionAdapter,
|
|
128
|
+
anthropicInstance,
|
|
129
|
+
currentAnthropicRouteDecision,
|
|
125
130
|
anthropicSessionKey,
|
|
126
131
|
commitResolvedOAuthSelection,
|
|
127
132
|
applyFailoverSnapshot,
|
|
@@ -135,7 +140,7 @@ export function createAdapterContinuations(
|
|
|
135
140
|
noteAdapterPhysicalSend,
|
|
136
141
|
noteAdapterRecoveryWithheld,
|
|
137
142
|
remainingTransientSendBudget,
|
|
138
|
-
|
|
143
|
+
transientSendReporter,
|
|
139
144
|
reserveCredentialHop,
|
|
140
145
|
sendBudgetExhausted,
|
|
141
146
|
} = sendBudgetState;
|
|
@@ -211,6 +216,8 @@ export function createAdapterContinuations(
|
|
|
211
216
|
try {
|
|
212
217
|
if (transportState.activeAdapter.fetchResponse) {
|
|
213
218
|
transportState.noteRoutedAttemptSend(continuationEstimate, replayKind);
|
|
219
|
+
const producer = adapterDispatchBudget?.beginSpendProducer?.();
|
|
220
|
+
try {
|
|
214
221
|
return await withProviderRequestSlot(route.providerName, route.provider, nextParsed.modelId, upstream.signal, pacingSlot =>
|
|
215
222
|
transportState.activeAdapter.fetchResponse!(builtContinuationRequest, {
|
|
216
223
|
kiroPreferAccountFailover: route.providerName === "kiro" && isGenericOAuthFailoverEnabled(config, "kiro"),
|
|
@@ -228,6 +235,7 @@ export function createAdapterContinuations(
|
|
|
228
235
|
modelId: nextParsed.modelId,
|
|
229
236
|
}),
|
|
230
237
|
}));
|
|
238
|
+
} finally { producer?.close(); }
|
|
231
239
|
}
|
|
232
240
|
// Same #1851 scope guard as the initial send: transient-5xx retry only for direct
|
|
233
241
|
// Google AI Studio; every other adapter keeps reset-only semantics here.
|
|
@@ -237,6 +245,9 @@ export function createAdapterContinuations(
|
|
|
237
245
|
: fetchWithResetRetry;
|
|
238
246
|
return await fetchContinuationWithRetryPolicy(
|
|
239
247
|
recovery => {
|
|
248
|
+
sendBudgetState.adapterSendBudget?.startRequest?.({
|
|
249
|
+
poolId: logCtx.spendPoolId ?? route.providerName, identityId: logCtx.accountLogLabel,
|
|
250
|
+
});
|
|
240
251
|
transportState.noteRoutedAttemptSend(continuationEstimate, recovery ?? replayKind);
|
|
241
252
|
return fetchWithHeaderTimeout(
|
|
242
253
|
builtContinuationRequest.url,
|
|
@@ -261,10 +272,10 @@ export function createAdapterContinuations(
|
|
|
261
272
|
// Same request-scoped budget as the initial send and the 429/rotation refetches:
|
|
262
273
|
// a terminal-guard continuation is another leg of ONE request, so handing it a
|
|
263
274
|
// fresh `attempts` would let one request exceed the configured total-send ceiling.
|
|
264
|
-
...(continuationTransientPolicy
|
|
275
|
+
...(continuationTransientPolicy || sendBudgetState.adapterSendBudget?.spendEnforced
|
|
265
276
|
? {
|
|
266
|
-
attempts: remainingTransientSendBudget(continuationTransientPolicy
|
|
267
|
-
onSendsConsumed:
|
|
277
|
+
attempts: remainingTransientSendBudget(continuationTransientPolicy?.attempts ?? 1),
|
|
278
|
+
onSendsConsumed: transientSendReporter(),
|
|
268
279
|
}
|
|
269
280
|
: {}),
|
|
270
281
|
},
|
|
@@ -391,25 +402,26 @@ export function createAdapterContinuations(
|
|
|
391
402
|
}
|
|
392
403
|
if (
|
|
393
404
|
(response.status === 429 || response.status === 403)
|
|
405
|
+
&& anthropicInstance
|
|
394
406
|
&& transportState.anthropicPoolAccountId
|
|
395
407
|
&& !isNonReplayableResponse(response)
|
|
396
408
|
) {
|
|
397
|
-
const nextAccountId = await
|
|
409
|
+
const nextAccountId = await rotateAnthropicAccountOnResponseForInstance(anthropicInstance, response, {
|
|
398
410
|
config, accountId: transportState.anthropicPoolAccountId, sessionKey: anthropicSessionKey,
|
|
399
|
-
model: route.modelId, requestKey: transportState, decision: transportState.anthropicRouteDecision, signal: upstream.signal,
|
|
411
|
+
model: route.modelId, requestKey: transportState, decision: transportState.anthropicRouteDecision, currentDecision: currentAnthropicRouteDecision, signal: upstream.signal,
|
|
400
412
|
canRetry: !sendBudgetExhausted() && transportState.anthropicPoolFailovers < ANTHROPIC_POOL_MAX_FAILOVERS_PER_REQUEST,
|
|
401
413
|
allowAccountRefusal, allow429Recovery: allowAccountRefusal,
|
|
402
414
|
});
|
|
403
415
|
if (nextAccountId) {
|
|
404
416
|
try {
|
|
405
|
-
const admitted = await commitResolvedOAuthSelection(await getAnthropicPoolAccessSnapshot(nextAccountId));
|
|
417
|
+
const admitted = await commitResolvedOAuthSelection(await anthropicRoutingFor(anthropicInstance).getAnthropicPoolAccessSnapshot(nextAccountId));
|
|
406
418
|
if (!admitted) throw new Error("OAuth selection changed during recovery");
|
|
407
419
|
try { void response.body?.cancel().catch(() => {}); } catch { /* already consumed/closed */ }
|
|
408
420
|
transportState.anthropicPoolAccountId = admitted.accountId;
|
|
409
421
|
transportState.anthropicPoolFailovers += 1;
|
|
410
422
|
route.provider = { ...route.provider, apiKey: admitted.accessToken };
|
|
411
423
|
invalidateSameTargetRequest();
|
|
412
|
-
logCtx.provider = formatAnthropicProviderForLog(
|
|
424
|
+
logCtx.provider = formatAnthropicProviderForLog(anthropicInstance, admitted.accountId, config);
|
|
413
425
|
transportState.activeAdapter = resolveSelectionAdapter(
|
|
414
426
|
resolveWireProtocolOverride(route.providerName, route.modelId, route.provider, inboundWire, route.staticPolicy),
|
|
415
427
|
config.cacheRetention,
|
|
@@ -513,9 +525,9 @@ export function createAdapterContinuations(
|
|
|
513
525
|
recordAttemptCredentialSource(logCtx.activeAttempt, route.providerName, route.provider, transportState.activeAdapter.name);
|
|
514
526
|
// The replay goes out on the next iteration. An adapter that owns its ladder
|
|
515
527
|
// reserves for that send itself, so hand this reservation down rather than let it
|
|
516
|
-
// take a second one for the same replay. A helper-routed replay
|
|
517
|
-
// its reporter settles
|
|
518
|
-
|
|
528
|
+
// take a second one for the same replay. A helper-routed replay carries the
|
|
529
|
+
// same permit so its reporter settles only this booking.
|
|
530
|
+
sendBudgetState.pendingHopPermit = hop.permit;
|
|
519
531
|
nextContinuationRecoveryKind = "oauth-account-429";
|
|
520
532
|
kiroRefusalPendingReplay = response;
|
|
521
533
|
continue;
|
|
@@ -604,9 +616,9 @@ export function createAdapterContinuations(
|
|
|
604
616
|
recordAttemptCredentialSource(logCtx.activeAttempt, route.providerName, route.provider, transportState.activeAdapter.name);
|
|
605
617
|
// The replay goes out on the next iteration. An adapter that owns its ladder
|
|
606
618
|
// reserves for that send itself, so hand this reservation down rather than let it
|
|
607
|
-
// take a second one for the same replay. A helper-routed replay
|
|
608
|
-
// its reporter settles
|
|
609
|
-
|
|
619
|
+
// take a second one for the same replay. A helper-routed replay carries the
|
|
620
|
+
// same permit so its reporter settles only this booking.
|
|
621
|
+
sendBudgetState.pendingHopPermit = hop.permit;
|
|
610
622
|
nextContinuationRecoveryKind = "oauth-account-429";
|
|
611
623
|
continue;
|
|
612
624
|
}
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { classifyAnthropic429 } from "../../oauth/anthropic-rate-limit-policy";
|
|
2
|
-
import {
|
|
2
|
+
import { rotateAnthropicAccountOnResponseForInstance } from "../../oauth/anthropic-account-refusal";
|
|
3
3
|
import { authorizeResendForRecovery } from "../../lib/request-resend-gate";
|
|
4
4
|
import { ambiguousResendAllowanceFor, selfContainedResponsesBody } from "./reset-replay";
|
|
5
5
|
import { transientSendCapFor } from "./request-send-budget";
|
|
@@ -65,7 +65,7 @@ import { bindRouteReasoningReplayScope } from "./core-replay";
|
|
|
65
65
|
import {
|
|
66
66
|
AnthropicAccountCooldownError,
|
|
67
67
|
ANTHROPIC_POOL_MAX_FAILOVERS_PER_REQUEST,
|
|
68
|
-
|
|
68
|
+
anthropicRoutingFor,
|
|
69
69
|
formatAnthropicProviderForLog,
|
|
70
70
|
} from "../../oauth/anthropic-routing";
|
|
71
71
|
import {
|
|
@@ -106,6 +106,7 @@ import { isCanonicalOpenAiForwardProvider } from "../../providers/openai-tiers";
|
|
|
106
106
|
import { mapCodexAuthContextErrorToResponse } from "./codex-auth-error";
|
|
107
107
|
import { chargeWorkflowSends } from "../../lib/workflow-budget";
|
|
108
108
|
import { isAntigravityValidationRefusal } from "./antigravity-validation-refusal";
|
|
109
|
+
import { unboundPoolSpendRefusalResponse } from "../workflow-refusal";
|
|
109
110
|
|
|
110
111
|
/** One responsibility of the Responses request pipeline; state owners are explicit. */
|
|
111
112
|
export async function prepareAdapterExchange(
|
|
@@ -139,6 +140,8 @@ export async function prepareAdapterExchange(
|
|
|
139
140
|
| "replayOAuthCredentialSnapshot"
|
|
140
141
|
| "invalidateSameTargetRequest"
|
|
141
142
|
| "resolveSelectionAdapter"
|
|
143
|
+
| "anthropicInstance"
|
|
144
|
+
| "currentAnthropicRouteDecision"
|
|
142
145
|
| "anthropicRouteDecision"
|
|
143
146
|
| "anthropicPoolAccountId"
|
|
144
147
|
| "anthropicPoolFailovers"
|
|
@@ -157,7 +160,8 @@ export async function prepareAdapterExchange(
|
|
|
157
160
|
| "noteAdapterPhysicalSend"
|
|
158
161
|
| "noteAdapterRecoveryWithheld"
|
|
159
162
|
| "remainingTransientSendBudget"
|
|
160
|
-
| "
|
|
163
|
+
| "adapterSendBudget"
|
|
164
|
+
| "transientSendReporter"
|
|
161
165
|
| "recoverySendAllowance"
|
|
162
166
|
| "recoveryClassFor"
|
|
163
167
|
| "sendBudgetExhausted"
|
|
@@ -175,6 +179,8 @@ export async function prepareAdapterExchange(
|
|
|
175
179
|
refreshResolvedOAuthSelection,
|
|
176
180
|
invalidateSameTargetRequest,
|
|
177
181
|
resolveSelectionAdapter,
|
|
182
|
+
anthropicInstance,
|
|
183
|
+
currentAnthropicRouteDecision,
|
|
178
184
|
anthropicSessionKey,
|
|
179
185
|
commitResolvedOAuthSelection,
|
|
180
186
|
applyFailoverSnapshot,
|
|
@@ -194,7 +200,7 @@ export async function prepareAdapterExchange(
|
|
|
194
200
|
noteAdapterPhysicalSend,
|
|
195
201
|
noteAdapterRecoveryWithheld,
|
|
196
202
|
remainingTransientSendBudget,
|
|
197
|
-
|
|
203
|
+
transientSendReporter,
|
|
198
204
|
recoverySendAllowance,
|
|
199
205
|
recoveryClassFor,
|
|
200
206
|
sendBudgetExhausted,
|
|
@@ -333,6 +339,8 @@ export async function prepareAdapterExchange(
|
|
|
333
339
|
try {
|
|
334
340
|
if (transportState.activeAdapter.fetchResponse) {
|
|
335
341
|
transportState.noteRoutedAttemptSend(inputTokenEstimate);
|
|
342
|
+
const producer = adapterDispatchBudget?.beginSpendProducer?.();
|
|
343
|
+
try {
|
|
336
344
|
upstreamResponse = await withProviderRequestSlot(route.providerName, route.provider, route.modelId, upstream.signal, pacingSlot =>
|
|
337
345
|
transportState.activeAdapter.fetchResponse!(builtInitialRequest, {
|
|
338
346
|
kiroPreferAccountFailover: route.providerName === "kiro" && isGenericOAuthFailoverEnabled(config, "kiro"),
|
|
@@ -352,6 +360,7 @@ export async function prepareAdapterExchange(
|
|
|
352
360
|
? createCodexAuthDispatchGuard(admissionState.authCtx, options.codexAuthPolicy ?? config, route.modelId, options.admission, options.visionDescribeTerminal === true) : undefined,
|
|
353
361
|
}),
|
|
354
362
|
}));
|
|
363
|
+
} finally { producer?.close(); }
|
|
355
364
|
} else {
|
|
356
365
|
// #1851 scope guard: transient-5xx retry on this generic adapter path is opt-in for
|
|
357
366
|
// direct Google AI Studio only (Vertex/Antigravity use fetchResponse above). Other
|
|
@@ -361,13 +370,14 @@ export async function prepareAdapterExchange(
|
|
|
361
370
|
// legacy direct-Google exception is preserved exactly; every other adapter still keeps
|
|
362
371
|
// reset-only semantics so combo failover hops on the first 5xx.
|
|
363
372
|
const transientPolicy = transientRetryPolicyFor(route.provider);
|
|
373
|
+
const resetPolicy = resetReplayPolicyFor(route.provider);
|
|
364
374
|
const compactPrepaid = options.compactionRecoveryAttempted ? sendBudgetState.pendingHopPermit : undefined;
|
|
365
375
|
if (compactPrepaid) sendBudgetState.pendingHopPermit = undefined;
|
|
366
376
|
let compactPrepaidUsed = false;
|
|
367
377
|
// Combo and emergency compaction admission already book this target's first send.
|
|
368
378
|
// Its configured initial ceiling is target-local; the shared remainder below still
|
|
369
379
|
// accounts for earlier targets without deducting their sends from this target twice.
|
|
370
|
-
const initialSendCap = transientPolicy ||
|
|
380
|
+
const initialSendCap = transientPolicy || resetPolicy
|
|
371
381
|
? transientSendCapFor(transientPolicy?.attempts,
|
|
372
382
|
(options.comboAttempt || compactPrepaid) ? 0 : sendBudgetState.sendsUsed)
|
|
373
383
|
: 1;
|
|
@@ -376,6 +386,11 @@ export async function prepareAdapterExchange(
|
|
|
376
386
|
: fetchWithResetRetry;
|
|
377
387
|
upstreamResponse = await fetchWithRetryPolicy(
|
|
378
388
|
recovery => {
|
|
389
|
+
// Unconfigured generic sends may omit the counting reporter. Capture policy
|
|
390
|
+
// independently so a later recovery still belongs to this request's start.
|
|
391
|
+
sendBudgetState.adapterSendBudget?.startRequest?.({
|
|
392
|
+
poolId: logCtx.spendPoolId ?? route.providerName, identityId: logCtx.accountLogLabel,
|
|
393
|
+
});
|
|
379
394
|
if (compactPrepaid && !compactPrepaidUsed) {
|
|
380
395
|
if (!compactPrepaid.use()) throw new SendBudgetExhaustedError(safeHostLabel(builtInitialRequest.url));
|
|
381
396
|
compactPrepaidUsed = true;
|
|
@@ -398,12 +413,18 @@ export async function prepareAdapterExchange(
|
|
|
398
413
|
abortSignal: upstream.signal,
|
|
399
414
|
label: safeHostLabel(builtInitialRequest.url),
|
|
400
415
|
claimAmbiguousResend: claimPreHeaderResend,
|
|
401
|
-
...(
|
|
416
|
+
...(sendBudgetState.adapterSendBudget?.spendEnforced || options.comboDispatchPermit || transientPolicy || resetPolicy || compactPrepaid
|
|
402
417
|
? {
|
|
403
418
|
// A pending compaction permit already booked this leg's first physical send.
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
419
|
+
...(transientPolicy || resetPolicy || compactPrepaid
|
|
420
|
+
? {
|
|
421
|
+
attempts: Math.min(initialSendCap,
|
|
422
|
+
remainingTransientSendBudget(initialSendCap) + (compactPrepaid ? 1 : 0)),
|
|
423
|
+
}
|
|
424
|
+
: {}),
|
|
425
|
+
// Keep the exact combo or compaction receipt on the helper callback. The reporter
|
|
426
|
+
// also settles the request and workflow counters for reset-only retries.
|
|
427
|
+
onSendsConsumed: transientSendReporter(compactPrepaid ?? options.comboDispatchPermit),
|
|
407
428
|
}
|
|
408
429
|
: {}),
|
|
409
430
|
},
|
|
@@ -437,7 +458,8 @@ export async function prepareAdapterExchange(
|
|
|
437
458
|
// blaming the provider makes the caller send the whole turn again -- the amplification this
|
|
438
459
|
// budget exists to stop. The passthrough path has answered 429 here since #4546.
|
|
439
460
|
if (err instanceof SendBudgetExhaustedError) {
|
|
440
|
-
return
|
|
461
|
+
return unboundPoolSpendRefusalResponse(logCtx)
|
|
462
|
+
?? formatErrorResponse(429, SEND_BUDGET_EXHAUSTED_CODE, err.message);
|
|
441
463
|
}
|
|
442
464
|
const msg = describeUpstreamConnectFailure(err, connectMs);
|
|
443
465
|
return formatErrorResponse(502, "upstream_error", msg);
|
|
@@ -541,6 +563,8 @@ export async function prepareAdapterExchange(
|
|
|
541
563
|
try {
|
|
542
564
|
if (transportState.activeAdapter.fetchResponse) {
|
|
543
565
|
transportState.noteRoutedAttemptSend(retryEstimate, recovery);
|
|
566
|
+
const producer = adapterDispatchBudget?.beginSpendProducer?.();
|
|
567
|
+
try {
|
|
544
568
|
return await withProviderRequestSlot(route.providerName, route.provider, route.modelId, upstream.signal, pacingSlot => {
|
|
545
569
|
// The dispatch boundary is HERE, not before the pacing wait: that wait can reject for
|
|
546
570
|
// an abort, a saturated queue, an expired slot or a removed provider, and none of
|
|
@@ -570,6 +594,7 @@ export async function prepareAdapterExchange(
|
|
|
570
594
|
}),
|
|
571
595
|
});
|
|
572
596
|
});
|
|
597
|
+
} finally { producer?.close(); }
|
|
573
598
|
}
|
|
574
599
|
// #2643 review: this leg used to call fetchWithHeaderTimeout directly, so an
|
|
575
600
|
// opted-in provider's transient-5xx policy applied to the initial send and to
|
|
@@ -581,7 +606,7 @@ export async function prepareAdapterExchange(
|
|
|
581
606
|
const refetchWithPolicy = (route.provider.adapter === "google" || refetchTransientPolicy)
|
|
582
607
|
? fetchWithTransientRetry
|
|
583
608
|
: fetchWithResetRetry;
|
|
584
|
-
const helperCountsSends = refetchTransientPolicy !== null || resetReplayPolicyFor(route.provider) !== null;
|
|
609
|
+
const helperCountsSends = sendBudgetState.adapterSendBudget?.spendEnforced === true || refetchTransientPolicy !== null || resetReplayPolicyFor(route.provider) !== null;
|
|
585
610
|
const prepaid = sendBudgetState.pendingHopPermit;
|
|
586
611
|
const configuredTotal = refetchTransientPolicy?.attempts;
|
|
587
612
|
const refetchCap = transientSendCapFor(configuredTotal,
|
|
@@ -601,6 +626,9 @@ export async function prepareAdapterExchange(
|
|
|
601
626
|
try {
|
|
602
627
|
return await refetchWithPolicy(
|
|
603
628
|
recoveryKind => {
|
|
629
|
+
sendBudgetState.adapterSendBudget?.startRequest?.({
|
|
630
|
+
poolId: logCtx.spendPoolId ?? route.providerName, identityId: logCtx.accountLogLabel,
|
|
631
|
+
});
|
|
604
632
|
if (refetchAllowance?.permit && !refetchAllowance.permit.use()) {
|
|
605
633
|
throw new SendBudgetExhaustedError(safeHostLabel(retryRequest.url));
|
|
606
634
|
}
|
|
@@ -629,7 +657,7 @@ export async function prepareAdapterExchange(
|
|
|
629
657
|
...(refetchAllowance
|
|
630
658
|
? {
|
|
631
659
|
attempts: refetchAllowance.attempts,
|
|
632
|
-
onSendsConsumed:
|
|
660
|
+
onSendsConsumed: transientSendReporter(refetchAllowance.permit),
|
|
633
661
|
}
|
|
634
662
|
: {}),
|
|
635
663
|
},
|
|
@@ -673,7 +701,8 @@ export async function prepareAdapterExchange(
|
|
|
673
701
|
// Same rule on the recovery leg: the ladder refused to send again, so the answer names
|
|
674
702
|
// this proxy rather than the provider it never reached.
|
|
675
703
|
if (err instanceof SendBudgetExhaustedError) {
|
|
676
|
-
return { failed:
|
|
704
|
+
return { failed: unboundPoolSpendRefusalResponse(logCtx)
|
|
705
|
+
?? formatErrorResponse(429, SEND_BUDGET_EXHAUSTED_CODE, err.message) };
|
|
677
706
|
}
|
|
678
707
|
const msg = describeUpstreamConnectFailure(err, connectMs);
|
|
679
708
|
return { failed: formatErrorResponse(502, "upstream_error", msg) };
|
|
@@ -1048,23 +1077,24 @@ export async function prepareAdapterExchange(
|
|
|
1048
1077
|
// before output, within the shared request and account rotation limits.
|
|
1049
1078
|
while (
|
|
1050
1079
|
(upstreamResponse.status === 429 || upstreamResponse.status === 403)
|
|
1080
|
+
&& anthropicInstance
|
|
1051
1081
|
&& transportState.anthropicPoolAccountId
|
|
1052
1082
|
) {
|
|
1053
|
-
const nextAccountId = await
|
|
1083
|
+
const nextAccountId = await rotateAnthropicAccountOnResponseForInstance(anthropicInstance, upstreamResponse, {
|
|
1054
1084
|
config, accountId: transportState.anthropicPoolAccountId, sessionKey: anthropicSessionKey,
|
|
1055
|
-
model: route.modelId, requestKey: transportState, decision: transportState.anthropicRouteDecision, signal: upstream.signal,
|
|
1085
|
+
model: route.modelId, requestKey: transportState, decision: transportState.anthropicRouteDecision, currentDecision: currentAnthropicRouteDecision, signal: upstream.signal,
|
|
1056
1086
|
canRetry: !sendBudgetExhausted() && transportState.anthropicPoolFailovers < ANTHROPIC_POOL_MAX_FAILOVERS_PER_REQUEST,
|
|
1057
1087
|
});
|
|
1058
1088
|
if (!nextAccountId) break;
|
|
1059
1089
|
try {
|
|
1060
|
-
const admitted = await commitResolvedOAuthSelection(await getAnthropicPoolAccessSnapshot(nextAccountId));
|
|
1090
|
+
const admitted = await commitResolvedOAuthSelection(await anthropicRoutingFor(anthropicInstance).getAnthropicPoolAccessSnapshot(nextAccountId));
|
|
1061
1091
|
if (!admitted) throw new Error("OAuth selection changed during recovery");
|
|
1062
1092
|
try { void upstreamResponse.body?.cancel().catch(() => {}); } catch { /* already consumed/closed */ }
|
|
1063
1093
|
transportState.anthropicPoolAccountId = admitted.accountId;
|
|
1064
1094
|
transportState.anthropicPoolFailovers += 1;
|
|
1065
1095
|
route.provider = { ...route.provider, apiKey: admitted.accessToken };
|
|
1066
1096
|
invalidateSameTargetRequest();
|
|
1067
|
-
logCtx.provider = formatAnthropicProviderForLog(
|
|
1097
|
+
logCtx.provider = formatAnthropicProviderForLog(anthropicInstance, admitted.accountId, config);
|
|
1068
1098
|
transportState.activeAdapter = resolveSelectionAdapter(
|
|
1069
1099
|
resolveWireProtocolOverride(route.providerName, route.modelId, route.provider, inboundWire, route.staticPolicy),
|
|
1070
1100
|
config.cacheRetention,
|
|
@@ -1,19 +1,33 @@
|
|
|
1
|
-
import { ANTIGRAVITY_VALIDATION_REQUIRED_PREFIX } from "../../adapters/google-errors";
|
|
1
|
+
import { ANTIGRAVITY_VALIDATION_REQUIRED_PREFIX, safeAntigravityHttpErrorMessage } from "../../adapters/google-errors";
|
|
2
2
|
import { readBoundedResponseBody } from "../../lib/bounded-body";
|
|
3
3
|
|
|
4
|
+
/** Match only the normalized Antigravity validation-refusal marker. */
|
|
5
|
+
export function hasAntigravityValidationRefusalMarker(text: string): boolean {
|
|
6
|
+
return text.startsWith(`${ANTIGRAVITY_VALIDATION_REQUIRED_PREFIX}: `);
|
|
7
|
+
}
|
|
8
|
+
|
|
4
9
|
/** Inspect only the Google adapter's normalized, already bounded 403 response. */
|
|
5
10
|
export async function isAntigravityValidationRefusal(response: Response, signal?: AbortSignal): Promise<boolean> {
|
|
11
|
+
return inspectValidationRefusal(response, signal, false);
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
/** Sidecar dispatch retains the raw Google envelope; normalize only a complete bounded body. */
|
|
15
|
+
export async function isAntigravityRawValidationRefusal(response: Response, signal?: AbortSignal): Promise<boolean> {
|
|
16
|
+
return inspectValidationRefusal(response, signal, true);
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
async function inspectValidationRefusal(response: Response, signal: AbortSignal | undefined, raw: boolean): Promise<boolean> {
|
|
6
20
|
if (response.status !== 403 || signal?.aborted) return false;
|
|
7
21
|
try {
|
|
8
22
|
const body = await readBoundedResponseBody(response.clone(), {
|
|
9
|
-
maxBytes: 1024,
|
|
10
|
-
totalTimeoutMs: 1000,
|
|
11
|
-
firstByteTimeoutMs: 1000,
|
|
12
|
-
inactivityTimeoutMs: 1000,
|
|
23
|
+
maxBytes: raw ? 4096 : 1024,
|
|
24
|
+
totalTimeoutMs: raw ? 2000 : 1000,
|
|
25
|
+
firstByteTimeoutMs: raw ? 2000 : 1000,
|
|
26
|
+
inactivityTimeoutMs: raw ? 2000 : 1000,
|
|
13
27
|
signal,
|
|
14
28
|
});
|
|
15
29
|
return !signal?.aborted && body.displaySafe && !body.truncated && !body.timedOut && !body.oversized
|
|
16
|
-
&& body.text
|
|
30
|
+
&& hasAntigravityValidationRefusalMarker(raw ? safeAntigravityHttpErrorMessage(403, body.text) : body.text);
|
|
17
31
|
} catch {
|
|
18
32
|
return false;
|
|
19
33
|
}
|
|
@@ -1,3 +1,6 @@
|
|
|
1
|
+
import { createPhysicalSendReporter } from "../../lib/request-execution-budget";
|
|
2
|
+
import { createInferenceSendBudget } from "../inference/context";
|
|
3
|
+
import { unboundPoolSpendRefusalResponse } from "../workflow-refusal";
|
|
1
4
|
import { capturePoolQuotaWriter } from "../../codex/account-store";
|
|
2
5
|
import { previewXaiOauthWireModel } from "./core-normalize";
|
|
3
6
|
import {
|
|
@@ -722,6 +725,7 @@ export async function handleResponsesCompact(
|
|
|
722
725
|
logCtx.provider = route.codexAccountNamespace
|
|
723
726
|
? `${route.providerName}-${route.codexAccountNamespace}`
|
|
724
727
|
: route.providerName;
|
|
728
|
+
logCtx.spendPoolId = route.providerName;
|
|
725
729
|
logCtx.providerAdapter = route.provider.adapter;
|
|
726
730
|
// #4940, and the same refusal the ordinary Responses path makes in request-prepare.ts. Compact has
|
|
727
731
|
// to repeat it rather than inherit it: the native branch below dispatches straight to
|
|
@@ -804,7 +808,7 @@ export async function handleResponsesCompact(
|
|
|
804
808
|
// failure that hands off, continues here. The routed turn used to call handleResponses with
|
|
805
809
|
// no budget at all, so `handleResponsesInner` minted a fresh four after the native attempt
|
|
806
810
|
// had already spent some of the first one.
|
|
807
|
-
const sendBudget: RequestExecutionBudget = options.sendBudget ??
|
|
811
|
+
const sendBudget: RequestExecutionBudget = options.sendBudget ?? createInferenceSendBudget(req, logCtx);
|
|
808
812
|
// A manual override onto another backend must not mint ciphertext the conversation model cannot replay.
|
|
809
813
|
const manualOverrideCrossesProvider = options.compactionRoutingOverride
|
|
810
814
|
? !compactionRoutingKeepsProviderIdentity(config, options.compactionRoutingOverride, route)
|
|
@@ -1022,12 +1026,21 @@ export async function handleResponsesCompact(
|
|
|
1022
1026
|
// `kind === "pool"` and a main-pool credential left it false -- so 401 then 429 really did
|
|
1023
1027
|
// reach five. The single sends spend the base allowance first and then the one shared
|
|
1024
1028
|
// final-recovery reserve, which is the same rule the Responses path follows.
|
|
1025
|
-
const sendSingleCompactAttempt = (
|
|
1029
|
+
const sendSingleCompactAttempt = async (
|
|
1026
1030
|
doFetch: () => Promise<Response>,
|
|
1027
1031
|
): Promise<Response> => {
|
|
1028
1032
|
if (sendBudget.remainingBaseSends(TRANSIENT_RETRY_MAX_ATTEMPTS) > 0) {
|
|
1029
|
-
sendBudget.
|
|
1030
|
-
|
|
1033
|
+
const report = createPhysicalSendReporter(sendBudget, () => ({ poolId: logCtx.spendPoolId ?? route.providerName,
|
|
1034
|
+
identityId: logCtx.accountLogLabel }));
|
|
1035
|
+
let started = false;
|
|
1036
|
+
try {
|
|
1037
|
+
if (report.beforeSend?.() === false) throw new SendBudgetExhaustedError(safeHostLabel(compactUrl));
|
|
1038
|
+
started = true;
|
|
1039
|
+
return await doFetch();
|
|
1040
|
+
} finally {
|
|
1041
|
+
try { if (started) report(1); }
|
|
1042
|
+
finally { report.close?.(); }
|
|
1043
|
+
}
|
|
1031
1044
|
}
|
|
1032
1045
|
const decision = sendBudget.reserveDispatch({
|
|
1033
1046
|
sendClass: "auth-recovery",
|
|
@@ -1076,7 +1089,8 @@ export async function handleResponsesCompact(
|
|
|
1076
1089
|
// Draws the shared remainder instead of a fresh three. Compact is a native endpoint
|
|
1077
1090
|
// of the same logical turn, so its sends belong to the same cap.
|
|
1078
1091
|
attempts: sendBudget.remainingBaseSends(TRANSIENT_RETRY_MAX_ATTEMPTS),
|
|
1079
|
-
onSendsConsumed: (
|
|
1092
|
+
onSendsConsumed: createPhysicalSendReporter(sendBudget, () => ({ poolId: logCtx.spendPoolId ?? route.providerName,
|
|
1093
|
+
identityId: logCtx.accountLogLabel })),
|
|
1080
1094
|
});
|
|
1081
1095
|
};
|
|
1082
1096
|
|
|
@@ -1084,9 +1098,12 @@ export async function handleResponsesCompact(
|
|
|
1084
1098
|
// actually happens, so every recorder call names the context that produced it.
|
|
1085
1099
|
let outcomeCtx = authCtx;
|
|
1086
1100
|
const localDispatchRefusal = (error: unknown): Response | undefined => {
|
|
1087
|
-
const
|
|
1088
|
-
|
|
1089
|
-
|
|
1101
|
+
const cause = unwrapUpstreamRetryEvidenceError(error);
|
|
1102
|
+
const response = cause instanceof SendBudgetExhaustedError
|
|
1103
|
+
? unboundPoolSpendRefusalResponse(logCtx) ?? formatErrorResponse(429, "request_send_budget_exhausted", cause.message)
|
|
1104
|
+
: mapCodexAuthContextErrorToResponse(cause, {
|
|
1105
|
+
now: Date.now(), accountSelector: route.codexAccountNamespace,
|
|
1106
|
+
});
|
|
1090
1107
|
if (response) {
|
|
1091
1108
|
releaseUpstreamHostAdmission(compactHostAdmissionLease);
|
|
1092
1109
|
compactHostAdmissionLease = null;
|
|
@@ -47,7 +47,7 @@ function settlePhysicalSends(log: RequestLogContext, budget: RequestExecutionBud
|
|
|
47
47
|
// An external report may settle a prepaid booking without changing used. Its explicit
|
|
48
48
|
// receipt outranks the numeric delta, or the same source send would be charged twice.
|
|
49
49
|
const unreported = Math.max(0, sent - Math.max(reported, Math.max(0, budget.used - beforeUsed)));
|
|
50
|
-
if (unreported > 0) budget.used += unreported;
|
|
50
|
+
if (unreported > 0 && !budget.spendEnforced) budget.used += unreported;
|
|
51
51
|
return sent;
|
|
52
52
|
}
|
|
53
53
|
|
|
@@ -18,7 +18,7 @@ import type { OcxConfig } from "../../types";
|
|
|
18
18
|
import type { RouteResult } from "../../router";
|
|
19
19
|
import type { DataPlaneAdmission } from "../auth-cors";
|
|
20
20
|
import type { AdmissionLease } from "../../lib/admission";
|
|
21
|
-
import type { RequestExecutionBudget } from "../../lib/request-execution-budget";
|
|
21
|
+
import type { RequestExecutionBudget, SingleUseDispatchPermit } from "../../lib/request-execution-budget";
|
|
22
22
|
import type { TransientSendBudget } from "../../lib/upstream-retry";
|
|
23
23
|
import type { ResponsesTerminalStatus } from "../../bridge";
|
|
24
24
|
import type { NativeChatFinishLog } from "../chat-native";
|
|
@@ -62,6 +62,7 @@ export interface NativeComboChildRun {
|
|
|
62
62
|
childLog: RequestLogContext;
|
|
63
63
|
attemptHandle: InferenceAttempt;
|
|
64
64
|
sendBudget: RequestExecutionBudget;
|
|
65
|
+
comboDispatchPermit?: SingleUseDispatchPermit;
|
|
65
66
|
turnAdmissionLease?: AdmissionLease;
|
|
66
67
|
onFirstOutput: () => void;
|
|
67
68
|
/** Reports to the combo's child callbacks; it never writes the parent's final row itself. */
|
|
@@ -272,6 +273,7 @@ export interface ComboChildCallbacks {
|
|
|
272
273
|
export async function dispatchNativeComboChild(input: {
|
|
273
274
|
source: ComboProtocolSource;
|
|
274
275
|
plan: NativeComboChildPlan;
|
|
276
|
+
comboDispatchPermit?: SingleUseDispatchPermit;
|
|
275
277
|
logCtx: RequestLogContext;
|
|
276
278
|
childLog: RequestLogContext;
|
|
277
279
|
attempt: PersistedUsageAttempt;
|
|
@@ -317,6 +319,7 @@ export async function dispatchNativeComboChild(input: {
|
|
|
317
319
|
childLog,
|
|
318
320
|
attemptHandle,
|
|
319
321
|
sendBudget: plan.sendBudget,
|
|
322
|
+
comboDispatchPermit: input.comboDispatchPermit,
|
|
320
323
|
...(input.turnAdmissionLease ? { turnAdmissionLease: input.turnAdmissionLease } : {}),
|
|
321
324
|
onFirstOutput: () => {
|
|
322
325
|
outputSeen = true;
|
|
@@ -52,11 +52,10 @@ import { routeConcreteModel, comboRouteDecisionTrace } from "../../router";
|
|
|
52
52
|
import { memoryModelRouteReason } from "./memory-models";
|
|
53
53
|
import { poolAccountProviderLabel } from "../../providers/label";
|
|
54
54
|
import { getAccountSet } from "../../oauth/store";
|
|
55
|
+
import { configuredAnthropicInstance } from "../../providers/anthropic-instance";
|
|
55
56
|
import {
|
|
57
|
+
anthropicRoutingFor,
|
|
56
58
|
formatAnthropicProviderForLog,
|
|
57
|
-
getAnthropicAccountHealthSnapshot,
|
|
58
|
-
getAnthropicPoolRetryAfterSeconds,
|
|
59
|
-
getEligibleAnthropicAccounts,
|
|
60
59
|
} from "../../oauth/anthropic-routing";
|
|
61
60
|
import { codexAccountLogLabel } from "../../codex/account-label";
|
|
62
61
|
import { codexQuotaScopeForModel, getCodexQuotaHealthSnapshot } from "../../codex/routing";
|
|
@@ -118,10 +117,11 @@ export const COMBO_TARGET_BASE_SENDS = CODEX_TEXT_GUARDED_BUDGET_POLICY.baseSend
|
|
|
118
117
|
|
|
119
118
|
function cooledPoolAccountLabel(config: OcxConfig, providerName: string, modelId: string, label: string | undefined): string | undefined {
|
|
120
119
|
if (!label) return undefined;
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
120
|
+
const instance = configuredAnthropicInstance(config, providerName);
|
|
121
|
+
if (instance && config.providers[providerName]?.authMode === "oauth") {
|
|
122
|
+
const matches = getAccountSet(instance)?.accounts.filter(account =>
|
|
123
|
+
formatAnthropicProviderForLog(instance, account.id) === label) ?? [];
|
|
124
|
+
return matches.length === 1 && anthropicRoutingFor(instance).getAnthropicAccountHealthSnapshot(matches[0]!.id) ? label : undefined;
|
|
125
125
|
}
|
|
126
126
|
const provider = config.providers[providerName];
|
|
127
127
|
if (!provider || !isCanonicalOpenAiForwardProvider(provider)) return undefined;
|
|
@@ -152,12 +152,13 @@ export function isAnthropicPoolLocalRefusal(
|
|
|
152
152
|
failedAccount: string | undefined,
|
|
153
153
|
now = Date.now(),
|
|
154
154
|
): boolean {
|
|
155
|
-
|
|
155
|
+
const instance = configuredAnthropicInstance(config, providerName);
|
|
156
|
+
return instance !== undefined
|
|
156
157
|
&& config.providers[providerName]?.authMode === "oauth"
|
|
157
158
|
&& status === 429
|
|
158
159
|
&& failedAccount === undefined
|
|
159
|
-
&& getEligibleAnthropicAccounts(now).length === 0
|
|
160
|
-
&& getAnthropicPoolRetryAfterSeconds(now) !== null;
|
|
160
|
+
&& anthropicRoutingFor(instance).getEligibleAnthropicAccounts(now).length === 0
|
|
161
|
+
&& anthropicRoutingFor(instance).getAnthropicPoolRetryAfterSeconds(now) !== null;
|
|
161
162
|
}
|
|
162
163
|
|
|
163
164
|
/**
|
|
@@ -713,6 +714,9 @@ export async function executeComboResponses(
|
|
|
713
714
|
while (pick) {
|
|
714
715
|
if (options.abortSignal?.aborted) return clientCancelledResponse();
|
|
715
716
|
const firstComboTarget = comboTargetsDispatched === 0;
|
|
717
|
+
const targetRoute = routeConcreteModel(config, `${pick.target.provider}/${pick.target.model}`);
|
|
718
|
+
// The inherited spend tracker observes this parent log, before the child has its own label.
|
|
719
|
+
logCtx.spendPoolId = targetRoute.providerName;
|
|
716
720
|
// The first target seeds the ledger's target identity and charges nothing; every later one
|
|
717
721
|
// is a real transition, refused once the declared hops, the alternate-target ledger or the
|
|
718
722
|
// request total are spent. `countedExternally` is required: the child charges its own
|
|
@@ -744,7 +748,6 @@ export async function executeComboResponses(
|
|
|
744
748
|
...(logCtx.conversationId ? { conversationId: logCtx.conversationId } : {}),
|
|
745
749
|
...(logCtx.surface ? { surface: logCtx.surface } : {}),
|
|
746
750
|
};
|
|
747
|
-
const targetRoute = routeConcreteModel(config, `${pick.target.provider}/${pick.target.model}`);
|
|
748
751
|
const targetReasoningEfforts = supportedLadderFor({
|
|
749
752
|
provider: targetRoute.provider,
|
|
750
753
|
modelId: targetRoute.modelId,
|
|
@@ -861,6 +864,7 @@ export async function executeComboResponses(
|
|
|
861
864
|
response = nativeChild ? await dispatchNativeComboChild({
|
|
862
865
|
source: options.protocolSource!,
|
|
863
866
|
plan: nativeChild,
|
|
867
|
+
comboDispatchPermit: hopDecision?.allowed ? hopDecision.permit : undefined,
|
|
864
868
|
logCtx,
|
|
865
869
|
childLog,
|
|
866
870
|
attempt,
|
|
@@ -886,6 +890,7 @@ export async function executeComboResponses(
|
|
|
886
890
|
// parent arrived with.
|
|
887
891
|
sendBudget: targetSendBudget,
|
|
888
892
|
comboAttempt: true,
|
|
893
|
+
comboDispatchPermit: hopDecision?.allowed ? hopDecision.permit : undefined,
|
|
889
894
|
comboReplaySnapshot,
|
|
890
895
|
deferCodexResetDerivedCooldown,
|
|
891
896
|
// Attempt-relative TTFT is recorded HERE (not via childLog.firstOutputMs — a later
|
|
@@ -210,6 +210,7 @@ export async function applyFinalRouteRequestNormalization(args: {
|
|
|
210
210
|
if (preserveAnthropicResponseModel) parsed._responseModelId = responseModelId;
|
|
211
211
|
logCtx.model = virtualModel?.selectedModelId ?? route.modelId;
|
|
212
212
|
logCtx.provider = route.providerName;
|
|
213
|
+
logCtx.spendPoolId = route.providerName;
|
|
213
214
|
logCtx.providerAdapter = route.provider.adapter;
|
|
214
215
|
logCtx.routeDecision = route.routeDecision;
|
|
215
216
|
logCtx.policyEligibility = route.policyEligibility;
|
|
@@ -71,7 +71,7 @@ export interface HandleResponsesOptions {
|
|
|
71
71
|
compactionRecoveryKind?: "compaction-v1" | "compaction-v2";
|
|
72
72
|
onCompactionRecoveryRoute?: (route: RouteResult) => void;
|
|
73
73
|
onCompactionRecoveryAdapterEvent?: (event: AdapterEvent) => void;
|
|
74
|
-
/** Physical-send reports already delivered to the shared
|
|
74
|
+
/** Physical-send reports already delivered to the shared ledger, including exact booking settlement. */
|
|
75
75
|
onCompactionRecoverySendsReported?: (count: number) => void;
|
|
76
76
|
/** Private holder for the Kiro serving-account lease. */
|
|
77
77
|
accountLoad?: { lease: AccountLease | null; cancelled: boolean };
|
|
@@ -158,6 +158,8 @@ export interface HandleResponsesOptions {
|
|
|
158
158
|
callerDirectAuth?: CallerDirectAuth | null;
|
|
159
159
|
/** Internal recursion guard; callers outside this module must not set it. */
|
|
160
160
|
comboAttempt?: boolean;
|
|
161
|
+
/** Exact externally booked combo hop, used for its child's spend preflight and send reports. */
|
|
162
|
+
comboDispatchPermit?: SingleUseDispatchPermit;
|
|
161
163
|
/** Internal handoff: this combo was selected by shadow-call interception. */
|
|
162
164
|
shadowCallIntercepted?: boolean;
|
|
163
165
|
/** Internal handoff: the memory phase this turn belongs to, so combo children keep its routing. */
|
|
@@ -37,6 +37,11 @@ export function adapterNeedsForcedContinuation(name: string): boolean {
|
|
|
37
37
|
return name === "kiro" || name === "cursor";
|
|
38
38
|
}
|
|
39
39
|
|
|
40
|
+
/** Adapters opting into restricted store:false replay for pending client function calls. */
|
|
41
|
+
export function adapterNeedsToolCallContinuation(name: string): boolean {
|
|
42
|
+
return name === "qoder";
|
|
43
|
+
}
|
|
44
|
+
|
|
40
45
|
|
|
41
46
|
export type ContinuationOwnerRead =
|
|
42
47
|
| { kind: "missing" }
|