@bitkyc08/opencodex 2.79.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/cursor/live-transport.ts +5 -3
- package/src/adapters/cursor/native-exec-fs.ts +12 -9
- package/src/adapters/cursor/native-exec.ts +13 -5
- package/src/adapters/cursor/protobuf-request.ts +13 -7
- package/src/adapters/cursor/request-builder.ts +23 -10
- package/src/adapters/cursor/tool-definitions.ts +9 -6
- package/src/adapters/cursor/tool-guidance.ts +29 -8
- package/src/adapters/cursor/tool-naming.ts +40 -7
- package/src/adapters/cursor/tool-wording.ts +46 -0
- package/src/adapters/cursor.ts +3 -0
- package/src/adapters/devin/live-models.ts +2 -0
- package/src/adapters/devin.ts +25 -11
- package/src/adapters/kiro-retry.ts +9 -4
- package/src/adapters/openai-chat/messages.ts +7 -4
- 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 +10 -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/autostart-health.ts +3 -3
- 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/codex/journal.ts +36 -5
- package/src/codex/shim-overlay.ts +9 -1
- package/src/combos/jev-decision-contract.ts +25 -2
- package/src/combos/jev.ts +6 -3
- 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 +347 -199
- 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 +16 -16
- 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-scope.ts +4 -1
- 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/combo-requested-effort.ts +14 -0
- 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 +21 -18
- 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/desktop-startup.ts +100 -3
- 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/header-deadline.ts +67 -0
- package/src/web-search/index.ts +7 -3
- package/src/web-search/loop.ts +32 -10
- package/src/web-search/passthrough-bridge.ts +4 -1
- package/src/web-search/sidecar-providers.ts +4 -3
- package/gui/dist/assets/App-BOUa-ek3.js +0 -54
- package/gui/dist/assets/Tray-DUtzB4zF.js +0 -1
- package/gui/dist/assets/index-DwHNDF4s.js +0 -94
- package/gui/dist/assets/quota-summary-BuL4WIBo.js +0 -1
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
import { createPhysicalSendReporter } from "../lib/request-execution-budget";
|
|
2
|
+
import { createInferenceSendBudget } from "./inference/context";
|
|
1
3
|
/**
|
|
2
4
|
* Managed native Messages lane (PF-08, behind `protocols.rollout.managedMessagesNative`).
|
|
3
5
|
*
|
|
@@ -17,16 +19,18 @@
|
|
|
17
19
|
*
|
|
18
20
|
* Loaded lazily by `claude-messages.ts` only when the switch is on and a route is eligible.
|
|
19
21
|
*/
|
|
20
|
-
import {
|
|
21
|
-
import {
|
|
22
|
-
import {
|
|
22
|
+
import { resolveAnthropicModelRouteForInstance } from "../oauth/anthropic-model-routes";
|
|
23
|
+
import { bindAnthropicRefusalCredentialForSend, rotateAnthropicAccountOnResponseForInstance } from "../oauth/anthropic-account-refusal";
|
|
24
|
+
import { captureAnthropicPhysicalSendOwnership, anthropicPhysicalSendOwnershipIsCurrent,
|
|
25
|
+
type AnthropicPhysicalSendOwnership } from "../oauth/anthropic-send-ownership";
|
|
26
|
+
import { anthropicModelQuotaFor } from "../oauth/anthropic-model-quota";
|
|
23
27
|
import { captureConfigGeneration } from "../lib/state-store-sweeper";
|
|
24
|
-
import {
|
|
28
|
+
import { recordAnthropicAccountQuotaFromHeadersForInstance } from "../providers/quota";
|
|
25
29
|
import { credentialGeneration, getAccountCredentialWithStatus } from "../oauth/store";
|
|
26
30
|
import { ANTHROPIC_POOL_MAX_FAILOVERS_PER_REQUEST } from "../oauth/anthropic-routing";
|
|
27
31
|
import { enforceAnthropicImageLimits } from "../adapters/anthropic-image-guard";
|
|
28
32
|
import { normalizeAnthropicImages } from "../adapters/anthropic-image-normalize";
|
|
29
|
-
import { formatAnthropicErrorBody } from "../adapters/anthropic";
|
|
33
|
+
import { formatAnthropicErrorBody, resolveAnthropicMessagesUrl } from "../adapters/anthropic";
|
|
30
34
|
import {
|
|
31
35
|
anthropicMessagesNativeWireBody,
|
|
32
36
|
buildAnthropicMessagesPassthroughRequest,
|
|
@@ -43,6 +47,7 @@ import { isTranslatorBudgetExceededError, type TranslatorBudget } from "../lib/t
|
|
|
43
47
|
import {
|
|
44
48
|
applyReplayRefusalClientHeaders,
|
|
45
49
|
applyUpstreamRecoveryInit,
|
|
50
|
+
SendBudgetExhaustedError,
|
|
46
51
|
fetchWithResetRetry,
|
|
47
52
|
fetchWithTransientRetry,
|
|
48
53
|
isNonReplayableResponse,
|
|
@@ -67,12 +72,13 @@ import {
|
|
|
67
72
|
} from "../providers/key-failover";
|
|
68
73
|
import { stampApiKeyAccountLabel } from "../providers/label";
|
|
69
74
|
import { OAuthAccountPausedError, OAuthLoginRequiredError, publicOAuthAuthenticationErrorMessage } from "../oauth";
|
|
70
|
-
import { AnthropicAccountCooldownError, formatAnthropicProviderForLog,
|
|
75
|
+
import { AnthropicAccountCooldownError, formatAnthropicProviderForLog, anthropicRoutingFor } from "../oauth/anthropic-routing";
|
|
76
|
+
import { configuredAnthropicInstance } from "../providers/anthropic-instance";
|
|
71
77
|
import { resolveProtocolSettings } from "../protocols/settings";
|
|
72
78
|
import { addProtocolEntryReason, markProtocolBlocked } from "../protocols/trace";
|
|
73
79
|
import type { OcxProviderTransport } from "../providers/xai-transport";
|
|
74
80
|
import { preservesPhysicalComboProvider, resolveComboId } from "../combos";
|
|
75
|
-
import { captureRouteStaticPolicy, routeModel, type RouteResult } from "../router";
|
|
81
|
+
import { captureRouteStaticPolicy, routedProviderConfig, routeModel, type RouteResult } from "../router";
|
|
76
82
|
import { POLICY_NAMESPACE, resolvePolicyProfileId } from "../routing/profile";
|
|
77
83
|
import type { AnthropicClientIdentity } from "../adapters/anthropic/client-identity";
|
|
78
84
|
import type { OcxConfig, OcxProviderConfig, OcxUsage } from "../types";
|
|
@@ -88,11 +94,12 @@ import { beginInferenceAttempt } from "./inference/attempt";
|
|
|
88
94
|
import { createFinalRequestLog, type FinalRequestLogMeta } from "./inference/final-log";
|
|
89
95
|
import { registerTurn, unregisterTurn } from "./lifecycle";
|
|
90
96
|
import { nativeMessagesDeclineReason, type NativeMessagesSelector } from "./messages-native-eligibility";
|
|
97
|
+
import { messagesSecondaryInstanceUnavailable } from "./messages-native-selector";
|
|
91
98
|
import {
|
|
92
99
|
nativeOAuthBindingIsCurrent,
|
|
93
100
|
NativeOAuthSelectionChangedError,
|
|
94
101
|
NativeOAuthModelRouteError,
|
|
95
|
-
|
|
102
|
+
resolveNativeOAuthBindingForInstance,
|
|
96
103
|
restoreOAuthToolNamesInMessage,
|
|
97
104
|
restoreOAuthToolNamesInSse,
|
|
98
105
|
type NativeOAuthBinding,
|
|
@@ -300,10 +307,12 @@ async function prepareNativeBody(body: Rec, signal: AbortSignal): Promise<void>
|
|
|
300
307
|
*/
|
|
301
308
|
export async function handleNativeMessages(options: HandleNativeMessagesOptions): Promise<Response> {
|
|
302
309
|
const { req, config, logCtx, logIds, route, body, requestedModel, translatorBudget } = options;
|
|
310
|
+
const nativeInstance = configuredAnthropicInstance(config, route.providerName);
|
|
303
311
|
const requestedStream = body.stream === true;
|
|
304
312
|
logCtx.inboundProtocol = "messages";
|
|
305
313
|
logCtx.model = route.modelId;
|
|
306
314
|
logCtx.provider = route.providerName;
|
|
315
|
+
logCtx.spendPoolId = route.providerName;
|
|
307
316
|
logCtx.providerAdapter = route.provider.adapter;
|
|
308
317
|
logCtx.requestedModel = requestedModel;
|
|
309
318
|
if (route.routeReason === "model-alias" || route.modelId !== requestedModel) logCtx.requestedAlias = requestedModel;
|
|
@@ -353,11 +362,18 @@ export async function handleNativeMessages(options: HandleNativeMessagesOptions)
|
|
|
353
362
|
return null;
|
|
354
363
|
};
|
|
355
364
|
|
|
365
|
+
if (route.provider.authMode === "oauth" && !nativeInstance) {
|
|
366
|
+
return fail(401, "Configured Anthropic OAuth instance is unavailable", "authentication_error");
|
|
367
|
+
}
|
|
368
|
+
|
|
356
369
|
const toolScopeDenial = nativeMessagesToolScopeDenial(options.modelScope, route.providerName, requestedModel, body);
|
|
357
370
|
if (toolScopeDenial) {
|
|
358
371
|
finishLog(403, toolScopeDenial.message);
|
|
359
372
|
return admissionModelDeniedResponse(toolScopeDenial);
|
|
360
373
|
}
|
|
374
|
+
let routeTarget: string;
|
|
375
|
+
try { routeTarget = resolveAnthropicMessagesUrl(route.provider); }
|
|
376
|
+
catch { return fail(400, "Invalid Anthropic Messages destination", "invalid_request_error"); }
|
|
361
377
|
|
|
362
378
|
try {
|
|
363
379
|
await prepareNativeBody(body, req.signal);
|
|
@@ -391,7 +407,7 @@ export async function handleNativeMessages(options: HandleNativeMessagesOptions)
|
|
|
391
407
|
const oauthProvider = (binding: NativeOAuthBinding): OcxProviderConfig => ({ ...route.provider, apiKey: binding.snapshot.accessToken });
|
|
392
408
|
if (route.provider.authMode === "oauth") {
|
|
393
409
|
try {
|
|
394
|
-
oauthBinding = await
|
|
410
|
+
oauthBinding = await resolveNativeOAuthBindingForInstance(nativeInstance!, config, { routeTarget, sessionKey: options.sessionKey, model: route.modelId });
|
|
395
411
|
} catch (error) {
|
|
396
412
|
cleanupAbort();
|
|
397
413
|
upstream.abort();
|
|
@@ -411,6 +427,7 @@ export async function handleNativeMessages(options: HandleNativeMessagesOptions)
|
|
|
411
427
|
stampApiKeyAccountLabel(logCtx, route.providerName, activeProvider);
|
|
412
428
|
if (oauthBinding) logCtx.provider = formatAnthropicProviderForLog(route.providerName, oauthBinding.snapshot.accountId, config);
|
|
413
429
|
const spendTracker = attachRequestSpendTracker(req, logCtx);
|
|
430
|
+
const physicalBudget = createInferenceSendBudget(req, logCtx);
|
|
414
431
|
let activeRequest: AnthropicMessagesPassthroughRequest;
|
|
415
432
|
let retainedRequestBytes = 0;
|
|
416
433
|
const releaseRetainedRequest = () => {
|
|
@@ -473,6 +490,15 @@ export async function handleNativeMessages(options: HandleNativeMessagesOptions)
|
|
|
473
490
|
: Number.POSITIVE_INFINITY;
|
|
474
491
|
const transientSendAvailable = (): boolean => remainingTransientSends() > 0;
|
|
475
492
|
const selector: NativeMessagesSelector = options.selector ?? {};
|
|
493
|
+
const nativeOAuthRouteIsCurrent = (binding: NativeOAuthBinding): boolean => {
|
|
494
|
+
const provider = config.providers[binding.instance];
|
|
495
|
+
if (configuredAnthropicInstance(config, binding.instance) !== binding.instance || !provider) return false;
|
|
496
|
+
// Same normalization as the bound target (nativeOAuthRouteTarget): compare routed destinations.
|
|
497
|
+
let routed: OcxProviderConfig;
|
|
498
|
+
try { routed = routedProviderConfig(binding.instance, provider); } catch { return false; }
|
|
499
|
+
return resolveAnthropicMessagesUrl(routed) === routeTarget
|
|
500
|
+
&& nativeMessagesDeclineReason({ ...route, provider: routed }, body, config, selector) === undefined;
|
|
501
|
+
};
|
|
476
502
|
|
|
477
503
|
const send = async (recovery?: "rate-limit-429" | "oauth-account-403" | "key-429" | "key-401"): Promise<Response> => {
|
|
478
504
|
const remaining = remainingTransientSends();
|
|
@@ -493,12 +519,13 @@ export async function handleNativeMessages(options: HandleNativeMessagesOptions)
|
|
|
493
519
|
modelId: route.modelId,
|
|
494
520
|
dispatchOverride: async (_input, init, execute) => {
|
|
495
521
|
if (oauthBinding) {
|
|
522
|
+
if (!nativeOAuthRouteIsCurrent(oauthBinding)) throw new NativeOAuthSelectionChangedError();
|
|
496
523
|
// The OAuth twin of the key check below: re-resolve through the same selection
|
|
497
524
|
// owner when the committed account or its credential moved since the build.
|
|
498
525
|
for (let attempt = 0; !nativeOAuthBindingIsCurrent(oauthBinding); attempt++) {
|
|
499
526
|
if (attempt >= 3) throw new NativeOAuthSelectionChangedError();
|
|
500
527
|
try {
|
|
501
|
-
oauthBinding = await
|
|
528
|
+
oauthBinding = await resolveNativeOAuthBindingForInstance(nativeInstance!, config, { routeTarget, sessionKey: options.sessionKey, model: route.modelId });
|
|
502
529
|
} catch (error) {
|
|
503
530
|
if (error instanceof OAuthAccountPausedError || error instanceof OAuthLoginRequiredError
|
|
504
531
|
|| error instanceof AnthropicAccountCooldownError || error instanceof NativeOAuthModelRouteError) throw error;
|
|
@@ -519,31 +546,58 @@ export async function handleNativeMessages(options: HandleNativeMessagesOptions)
|
|
|
519
546
|
const headers = new Headers(wire.headers);
|
|
520
547
|
const encoding = new Headers(init.headers).get("accept-encoding");
|
|
521
548
|
if (!headers.has("accept-encoding") && encoding) headers.set("accept-encoding", encoding);
|
|
522
|
-
const
|
|
523
|
-
const
|
|
549
|
+
const sendingBinding = oauthBinding;
|
|
550
|
+
const snapshot = sendingBinding?.snapshot;
|
|
551
|
+
const providerAccountUuid = sendingBinding?.providerAccountUuid;
|
|
524
552
|
const writerGeneration = snapshot ? captureConfigGeneration() : 0;
|
|
525
|
-
const ownsBearer = snapshot &&
|
|
526
|
-
|
|
553
|
+
const ownsBearer = snapshot && snapshot.provider === nativeInstance
|
|
554
|
+
&& headers.get("authorization") === `Bearer ${snapshot.accessToken}` && !headers.has("x-api-key");
|
|
555
|
+
if (oauthBinding && (!ownsBearer || wire.url !== oauthBinding.routeTarget
|
|
556
|
+
|| !nativeOAuthRouteIsCurrent(oauthBinding) || !nativeOAuthBindingIsCurrent(oauthBinding))) {
|
|
557
|
+
throw new NativeOAuthSelectionChangedError();
|
|
558
|
+
}
|
|
559
|
+
const releaseFamily = snapshot && oauthBinding ? anthropicModelQuotaFor(oauthBinding.instance).claimAnthropicFamilyRevalidation(snapshot.accountId, route.modelId) : () => {};
|
|
527
560
|
if (!releaseFamily) throw new AnthropicAccountCooldownError(1);
|
|
528
561
|
let dispatched: Response;
|
|
562
|
+
const spendReport = createPhysicalSendReporter(physicalBudget, () => ({
|
|
563
|
+
poolId: logCtx.spendPoolId ?? route.providerName, identityId: logCtx.accountLogLabel,
|
|
564
|
+
}));
|
|
565
|
+
let sendOwner: AnthropicPhysicalSendOwnership | null = null;
|
|
529
566
|
try {
|
|
530
567
|
if (init.signal?.aborted) throw init.signal.reason;
|
|
531
|
-
|
|
568
|
+
// Capture before either accounting path so this request retains its start policy.
|
|
569
|
+
if (spendReport.beforeSend && !spendReport.beforeSend()) {
|
|
570
|
+
if (spendTracker.refusals) throw new NativeMessagesSpendRefusal();
|
|
571
|
+
throw new SendBudgetExhaustedError();
|
|
572
|
+
}
|
|
573
|
+
if (!physicalBudget.spendEnforced && !spendTracker.charge()) throw new NativeMessagesSpendRefusal();
|
|
532
574
|
noteProviderAttemptSend(logCtx, route.providerName, activeProvider, logCtx.usageLogInputTokens, transportRecovery ?? recovery);
|
|
575
|
+
// Last synchronous ownership reservation before the physical fetch. Retain this
|
|
576
|
+
// exact incarnation across its await; a returned response can never reserve one.
|
|
577
|
+
sendOwner = snapshot ? captureAnthropicPhysicalSendOwnership(snapshot) : null;
|
|
578
|
+
if (snapshot && !sendOwner) throw new NativeOAuthSelectionChangedError();
|
|
533
579
|
dispatched = await sendWithConnectionPolicy(
|
|
534
580
|
(activeProvider as OcxProviderTransport).fetch ?? execute,
|
|
535
581
|
wire.url,
|
|
536
582
|
applyUpstreamRecoveryInit({ ...init, method: "POST", headers, body: wire.body }, transportRecovery),
|
|
537
583
|
{ providerName: route.providerName, provider: activeProvider },
|
|
538
584
|
);
|
|
539
|
-
} finally {
|
|
540
|
-
|
|
585
|
+
} finally {
|
|
586
|
+
try { if (physicalBudget.spendEnforced) spendReport(1); }
|
|
587
|
+
finally {
|
|
588
|
+
try { spendReport.close?.(); }
|
|
589
|
+
finally { releaseFamily(); }
|
|
590
|
+
}
|
|
591
|
+
}
|
|
592
|
+
if (ownsBearer && snapshot && sendOwner && sendingBinding) {
|
|
541
593
|
try {
|
|
542
|
-
const current = getAccountCredentialWithStatus(
|
|
543
|
-
if (
|
|
594
|
+
const current = getAccountCredentialWithStatus(snapshot.provider, snapshot.accountId);
|
|
595
|
+
if (anthropicPhysicalSendOwnershipIsCurrent(sendOwner) && nativeOAuthRouteIsCurrent(sendingBinding)
|
|
596
|
+
&& current && !current.needsReauth && current.credential.access === snapshot.accessToken
|
|
597
|
+
&& credentialGeneration(current.credential) === snapshot.generation
|
|
544
598
|
&& current.credential.accountId === providerAccountUuid) {
|
|
545
|
-
|
|
546
|
-
|
|
599
|
+
bindAnthropicRefusalCredentialForSend(dispatched, sendOwner, providerAccountUuid);
|
|
600
|
+
recordAnthropicAccountQuotaFromHeadersForInstance(nativeInstance!, snapshot.accountId, dispatched.headers, writerGeneration, dispatched.status, route.modelId);
|
|
547
601
|
}
|
|
548
602
|
} catch { /* Passive observation must not fail the response. */ }
|
|
549
603
|
}
|
|
@@ -622,12 +676,13 @@ export async function handleNativeMessages(options: HandleNativeMessagesOptions)
|
|
|
622
676
|
const sendingBinding = oauthBinding;
|
|
623
677
|
const expectedRecoverySelection = sendingBinding.selection;
|
|
624
678
|
triedAccountIds.add(sendingBinding.snapshot.accountId);
|
|
625
|
-
const nextAccountId = await
|
|
679
|
+
const nextAccountId = await rotateAnthropicAccountOnResponseForInstance(sendingBinding.instance, response, {
|
|
626
680
|
config, accountId: sendingBinding.snapshot.accountId, model: route.modelId,
|
|
627
681
|
sessionKey: sendingBinding.sessionKey, decision: sendingBinding.routeDecision,
|
|
628
682
|
excludedAccountIds: triedAccountIds,
|
|
629
683
|
currentDecision: () => {
|
|
630
|
-
|
|
684
|
+
if (!nativeOAuthRouteIsCurrent(sendingBinding)) throw new NativeOAuthSelectionChangedError();
|
|
685
|
+
const resolved = resolveAnthropicModelRouteForInstance(sendingBinding.instance, config, route.modelId);
|
|
631
686
|
if (resolved.error) throw new NativeOAuthModelRouteError(`Invalid Anthropic model routes: ${resolved.error}`);
|
|
632
687
|
return resolved.decision;
|
|
633
688
|
},
|
|
@@ -637,7 +692,7 @@ export async function handleNativeMessages(options: HandleNativeMessagesOptions)
|
|
|
637
692
|
if (!nextAccountId) break;
|
|
638
693
|
const recovery = response.status === 403 ? "oauth-account-403" : "rate-limit-429";
|
|
639
694
|
discard(response);
|
|
640
|
-
oauthBinding = await
|
|
695
|
+
oauthBinding = await resolveNativeOAuthBindingForInstance(nativeInstance!, config, { routeTarget,
|
|
641
696
|
sessionKey: options.sessionKey, model: route.modelId, candidateAccountId: nextAccountId,
|
|
642
697
|
expectedRecoverySelection, expectedRecoveryRouteDecision: sendingBinding.routeDecision,
|
|
643
698
|
});
|
|
@@ -652,6 +707,7 @@ export async function handleNativeMessages(options: HandleNativeMessagesOptions)
|
|
|
652
707
|
upstream.abort();
|
|
653
708
|
if (req.signal.aborted) return fail(499, "Client cancelled request", "api_error");
|
|
654
709
|
const sendError = error instanceof UpstreamRetryEvidenceError ? error.cause : error;
|
|
710
|
+
if (sendError instanceof SendBudgetExhaustedError) return fail(429, sendError.message, "send_budget_exhausted", "send_budget_exhausted");
|
|
655
711
|
if (sendError instanceof NativeMessagesSpendRefusal) {
|
|
656
712
|
const refusal = workflowRefusalResponse("workflow-spend-exhausted", logCtx);
|
|
657
713
|
finishLog(429);
|
|
@@ -865,6 +921,7 @@ export function nativeMessagesCountBody(
|
|
|
865
921
|
): Rec | undefined {
|
|
866
922
|
if (typeof body.model !== "string") return undefined;
|
|
867
923
|
try {
|
|
924
|
+
if (messagesSecondaryInstanceUnavailable(config, body.model, cc)) return undefined;
|
|
868
925
|
const selectorId = resolveInboundModel(body.model, cc);
|
|
869
926
|
// Combo and policy selectors are never eligible, and routing them would advance round-robin
|
|
870
927
|
// state or run the policy evaluator for a request that sends nothing.
|
|
@@ -875,11 +932,13 @@ export function nativeMessagesCountBody(
|
|
|
875
932
|
route.providerName, route.modelId, route.provider, route.staticPolicy.effectiveAlias, "anthropic",
|
|
876
933
|
);
|
|
877
934
|
route.provider = resolveWireProtocolOverride(route.providerName, route.modelId, route.provider, "anthropic", route.staticPolicy);
|
|
935
|
+
const instance = configuredAnthropicInstance(config, route.providerName);
|
|
878
936
|
const selector: NativeMessagesSelector = {
|
|
879
937
|
...rows,
|
|
880
938
|
routeSelector: selectorId,
|
|
881
939
|
claudeCode: cc,
|
|
882
|
-
...(route.provider.authMode === "oauth"
|
|
940
|
+
...(route.provider.authMode === "oauth" && instance
|
|
941
|
+
? { oauthFailoverQuorum: anthropicRoutingFor(instance).hasAnthropicFailoverQuorum() } : {}),
|
|
883
942
|
};
|
|
884
943
|
if (nativeMessagesDeclineReason(route, body, config, selector) !== undefined) return undefined;
|
|
885
944
|
// The body without a credential: counting never resolves or refreshes an OAuth account.
|
|
@@ -63,6 +63,7 @@ import {
|
|
|
63
63
|
type UsageStatus,
|
|
64
64
|
} from "../usage/log";
|
|
65
65
|
import type { RequestExecutionBudget } from "../lib/request-execution-budget";
|
|
66
|
+
import type { WorkflowSpendDenialDetail } from "../lib/workflow-budget";
|
|
66
67
|
import { attributeFinalRequest, attributeSealedAttempt } from "./request-log-failure-attribution";
|
|
67
68
|
import { debugAttemptDeliverySummary } from "../lib/debug";
|
|
68
69
|
import {
|
|
@@ -203,6 +204,10 @@ export interface RequestLogContext {
|
|
|
203
204
|
spendOutputCeilingTokens?: number;
|
|
204
205
|
/** Pre-send input estimate reserved for spend only; unlike usageLogInputTokens it never enters usage. */
|
|
205
206
|
spendInputEstimateTokens?: number;
|
|
207
|
+
/** Canonical provider-pool spend identity; independent of mutable, account-specific log labels. */
|
|
208
|
+
spendPoolId?: string;
|
|
209
|
+
/** Internal safe refusal detail for a request whose ceiling includes unidentified old pool spend. */
|
|
210
|
+
spendRefusalDetail?: WorkflowSpendDenialDetail;
|
|
206
211
|
/** Settles this request's durable spend entries from `addFinalRequestLog`. */
|
|
207
212
|
spendTracker?: RequestSpendSettlement;
|
|
208
213
|
attempts?: PersistedUsageAttempt[];
|
|
@@ -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
|
}
|