@bitkyc08/opencodex 2.83.0 → 2.84.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/gui/dist/assets/App-CZtqiGcw.js +56 -0
- package/gui/dist/assets/Tray-C9TGtwad.js +1 -0
- package/gui/dist/assets/{index-BAbcj51J.css → index-BB6uK6tU.css} +1 -1
- package/gui/dist/assets/index-CTm4l-HQ.js +94 -0
- package/gui/dist/assets/{quota-summary-DiJO6sYY.js → quota-summary-BbEeRfPY.js} +1 -1
- package/gui/dist/index.html +2 -2
- package/package.json +2 -2
- package/src/adapters/coding-agent/protocol.ts +15 -8
- package/src/adapters/ollama-native.ts +29 -1
- package/src/adapters/openai-chat/messages.ts +8 -4
- package/src/adapters/openai-responses/empty-assistant.ts +32 -0
- package/src/adapters/openai-responses/passthrough.ts +3 -0
- package/src/adapters/openai-responses/request-strips.ts +10 -2
- package/src/adapters/qoder/adapter.ts +16 -14
- package/src/bridge/errors.ts +19 -0
- package/src/bridge.ts +1 -1
- package/src/claude/context-windows.ts +54 -1
- package/src/claude/intercept/connect-proxy.ts +10 -3
- package/src/claude/intercept/picker-ca-persistence.ts +3 -1
- package/src/claude/intercept/picker-listener.ts +49 -1
- package/src/claude/intercept/settings-no-proxy.ts +146 -0
- package/src/claude/intercept/settings.ts +101 -9
- package/src/cli/account-api.ts +65 -0
- package/src/cli/account-auth.ts +26 -2
- package/src/cli/account.ts +41 -5
- package/src/cli/capabilities-access-remote.ts +8 -0
- package/src/cli/capabilities-base.ts +13 -1
- package/src/cli/catalog-command-result.ts +2 -1
- package/src/cli/claude.ts +15 -6
- package/src/cli/config-command.ts +8 -2
- package/src/cli/debug.ts +8 -0
- package/src/cli/dispatch.ts +16 -3
- package/src/cli/export-command.ts +14 -1
- package/src/cli/hub-disable.ts +99 -0
- package/src/cli/hub.ts +4 -2
- package/src/cli/index.ts +13 -4
- package/src/cli/link-child.ts +104 -0
- package/src/cli/link-enrollment.ts +96 -0
- package/src/cli/link.ts +39 -24
- package/src/cli/observe-stream.ts +6 -1
- package/src/cli/observe.ts +10 -2
- package/src/cli/provider.ts +12 -0
- package/src/cli/registry.ts +5 -4
- package/src/cli/runtime-api.ts +27 -2
- package/src/cli/status.ts +104 -20
- package/src/client/connect.ts +39 -4
- package/src/client/hub-client.ts +14 -7
- package/src/client/hub-state.ts +25 -14
- package/src/client/link-home.ts +248 -0
- package/src/client/link-management-relay.ts +61 -0
- package/src/client/link-relay.ts +6 -1
- package/src/client/link-status.ts +14 -0
- package/src/client/machine-api.ts +14 -7
- package/src/client/machine-listener.ts +29 -4
- package/src/clients/config-export.ts +46 -16
- package/src/clients/omo-role-models.ts +6 -3
- package/src/codex/agent-role-models.ts +26 -8
- package/src/codex/auth-api/account-list.ts +17 -2
- package/src/codex/auth-api/account-selection.ts +17 -7
- package/src/codex/auth-api/routes.ts +5 -1
- package/src/codex/autostart-health.ts +3 -1
- package/src/codex/catalog/bundled.ts +5 -1
- package/src/codex/catalog/effort.ts +2 -2
- package/src/codex/catalog/metadata.ts +4 -2
- package/src/codex/catalog/native-models.ts +16 -2
- package/src/codex/native-profile-startup.ts +4 -1
- package/src/codex/prompt-journal.ts +20 -2
- package/src/codex/prompt-layers/encoding.ts +49 -0
- package/src/codex/prompt-layers/import-source.ts +30 -0
- package/src/codex/prompt-layers/toml-edit.ts +269 -37
- package/src/codex/prompt-layers/toml-read.ts +52 -4
- package/src/codex/prompt-layers.ts +383 -20
- package/src/codex/prompt-lock-claim.ts +13 -3
- package/src/codex/prompt-lock-evidence.ts +3 -3
- package/src/codex/prompt-lock-owner.ts +14 -6
- package/src/codex/prompt-text-probe.ts +34 -5
- package/src/codex/quota.ts +13 -6
- package/src/codex/role-auto-assign.ts +2 -5
- package/src/codex/routing/active-account.ts +21 -0
- package/src/codex/routing.ts +1 -1
- package/src/codex/runtime.ts +196 -8
- package/src/codex/shim-overlay.ts +8 -4
- package/src/codex/shim.ts +1 -1
- package/src/combos/failover.ts +3 -0
- package/src/config/api-key-reconcile.ts +34 -0
- package/src/config/diagnostics.ts +16 -0
- package/src/config/feature-flags.ts +5 -0
- package/src/config/live-reconcile.ts +43 -29
- package/src/config/live-topology.ts +29 -0
- package/src/config/load-degrade.ts +2 -1
- package/src/config/paths.ts +3 -0
- package/src/config/salvage.ts +3 -1
- package/src/config/schema/config-schema.ts +2 -0
- package/src/generated/compatibility-version.json +311 -171
- package/src/grok/grpc-web.ts +23 -1
- package/src/grok/reset-coupon-ledger.ts +75 -5
- package/src/grok/reset-coupons.ts +4 -2
- package/src/integrations/mutation-plan.ts +13 -7
- package/src/integrations/registry.ts +45 -11
- package/src/integrations/state.ts +19 -7
- package/src/integrations/target.ts +42 -5
- package/src/integrations/writer.ts +14 -6
- package/src/lib/app-owned-memory-stores.ts +4 -0
- package/src/lib/app-owned-memory.ts +11 -0
- package/src/lib/json-byte-size.ts +31 -0
- package/src/lib/optional-shutdown-hooks.ts +35 -0
- package/src/lib/outbound-credential-registry.ts +210 -0
- package/src/lib/passive-read.ts +35 -0
- package/src/lib/shadow-call.ts +44 -5
- package/src/link/codex-settings.ts +155 -0
- package/src/link/management-routes.ts +89 -0
- package/src/link/remote-probe.ts +38 -0
- package/src/link/ssh-argv.ts +26 -5
- package/src/link/ssh-runner.ts +58 -27
- package/src/link/status-projection.ts +22 -4
- package/src/link/supervisor.ts +144 -49
- package/src/link/tunnel-state.ts +11 -4
- package/src/oauth/anthropic-continuity.ts +4 -3
- package/src/oauth/anthropic-identity.ts +13 -2
- package/src/oauth/anthropic-model-quota.ts +13 -1
- package/src/oauth/anthropic-routing.ts +230 -23
- package/src/oauth/anthropic.ts +18 -4
- package/src/oauth/google-antigravity.ts +24 -7
- package/src/oauth/index.ts +24 -15
- package/src/oauth/kiro-account-identity.ts +106 -0
- package/src/oauth/kiro.ts +48 -5
- package/src/oauth/plan.ts +29 -0
- package/src/oauth/store.ts +40 -3
- package/src/oauth/types.ts +2 -0
- package/src/plugins/loader.ts +11 -0
- package/src/providers/anthropic-billing-ownership.ts +74 -0
- package/src/providers/anthropic-billing-recovery.ts +31 -0
- package/src/providers/anthropic-billing-refusal.ts +55 -0
- package/src/providers/anthropic-reset-admission.ts +62 -0
- package/src/providers/anthropic-reset-grant-ledger.ts +28 -6
- package/src/providers/api-key-resolve.ts +17 -2
- package/src/providers/key-failover.ts +52 -5
- package/src/providers/model-rename-startup.ts +5 -3
- package/src/providers/quota/account-cache.ts +11 -0
- package/src/providers/quota/anthropic-account-quota.ts +4 -2
- package/src/providers/quota/report-cache.ts +14 -0
- package/src/providers/quota/vendor-probes-oauth.ts +3 -3
- package/src/providers/quota.ts +4 -1
- package/src/providers/registry/entries-core.ts +14 -14
- package/src/providers/registry/entries-extended.ts +0 -1
- package/src/providers/registry/model-seeds.ts +17 -0
- package/src/providers/request-pacing.ts +9 -0
- package/src/providers/stale-no-vision-seed-migration.ts +72 -0
- package/src/responses/compaction.ts +14 -0
- package/src/responses/parser.ts +7 -1
- package/src/server/auth-cors.ts +42 -0
- package/src/server/configured-credentials.ts +194 -0
- package/src/server/index/link-management.ts +58 -0
- package/src/server/index/optional-listeners.ts +5 -1
- package/src/server/index/serve-options.ts +13 -0
- package/src/server/lifecycle.ts +2 -1
- package/src/server/management/agent-settings-routes.ts +28 -10
- package/src/server/management/anthropic-reset-grant-routes.ts +17 -1
- package/src/server/management/codex-agent-role-routes.ts +19 -6
- package/src/server/management/codex-prompt-routes.ts +136 -5
- package/src/server/management/codex-role-auto-assign.ts +1 -1
- package/src/server/management/grok-coupon-routes.ts +78 -27
- package/src/server/management/link-enrollment.ts +192 -0
- package/src/server/management/link-route-state.ts +185 -0
- package/src/server/management/link-routes.ts +82 -402
- package/src/server/management/model-rows.ts +3 -1
- package/src/server/management/provider-overwrite-carry.ts +10 -0
- package/src/server/management/provider-routes.ts +1 -0
- package/src/server/management/route-registry.ts +5 -3
- package/src/server/management/shared.ts +3 -0
- package/src/server/management-api.ts +3 -1
- package/src/server/messages-native.ts +68 -42
- package/src/server/relay-eager.ts +2 -0
- package/src/server/request-log-filter.ts +6 -1
- package/src/server/request-log-terminal-status.ts +63 -6
- package/src/server/responses/adapter-dispatch.ts +232 -79
- package/src/server/responses/code-mode-collab-surface.ts +205 -0
- package/src/server/responses/codex-ws-exchange.ts +147 -6
- package/src/server/responses/codex-ws-wire.ts +203 -0
- package/src/server/responses/collaboration.ts +36 -7
- package/src/server/responses/compact.ts +126 -48
- package/src/server/responses/context-overflow.ts +13 -1
- package/src/server/responses/core-codex-account.ts +105 -1
- package/src/server/responses/core-combo-failure.ts +14 -0
- package/src/server/responses/core-combo.ts +7 -2
- package/src/server/responses/core-errors.ts +29 -1
- package/src/server/responses/core-options.ts +2 -0
- package/src/server/responses/deferred-collab-surface.ts +158 -0
- package/src/server/responses/deferred-send-reporter.ts +25 -0
- package/src/server/responses/fetch-helpers.ts +12 -1
- package/src/server/responses/passthrough-delivery.ts +4 -1
- package/src/server/responses/passthrough-dispatch.ts +15 -4
- package/src/server/responses/request-prepare.ts +6 -4
- package/src/server/responses/request-transport.ts +6 -8
- package/src/server/responses/sidecar-execution.ts +11 -2
- package/src/server/responses/ws-upstream.ts +9 -1
- package/src/server/startup-action-control.ts +22 -2
- package/src/server/startup-health-cache.ts +4 -0
- package/src/server/system-env-shell.ts +7 -2
- package/src/server/system-env.ts +11 -1
- package/src/service/cli.ts +5 -1
- package/src/service/desktop-startup.ts +147 -34
- package/src/service/desktop-supervision.d.mts +27 -1
- package/src/service/desktop-supervision.mjs +111 -5
- package/src/service/health-sentinel-launch.ts +176 -0
- package/src/service/health-sentinel-main.ts +447 -0
- package/src/service/health-sentinel-policy.ts +241 -0
- package/src/service/health-sentinel-probe.ts +54 -0
- package/src/service/health-sentinel-windows.ts +87 -0
- package/src/service/linux-linger.ts +20 -0
- package/src/types/config.ts +5 -0
- package/src/usage/expected-prices.ts +4 -0
- package/src/usage/log.ts +19 -1
- package/gui/dist/assets/App-Ba8uG696.js +0 -54
- package/gui/dist/assets/Tray-IImbNPRy.js +0 -1
- package/gui/dist/assets/index-BHRZdJuv.js +0 -94
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { deferPhysicalSendReporter } from "./deferred-send-reporter";
|
|
1
2
|
import { spendLedgerStorageErrorResponse } from "./spend-storage-error";
|
|
2
3
|
import { classifyAnthropic429 } from "../../oauth/anthropic-rate-limit-policy";
|
|
3
4
|
import { rotateAnthropicAccountOnResponseForInstance } from "../../oauth/anthropic-account-refusal";
|
|
@@ -23,11 +24,13 @@ import {
|
|
|
23
24
|
sealRequestAttemptIdentity,
|
|
24
25
|
recordAttemptCredentialSource,
|
|
25
26
|
} from "../request-log";
|
|
26
|
-
import { clientCancelledResponse, readDisplaySafeErrorText, normalizeUpstreamErrorText } from "./core-errors";
|
|
27
|
+
import { anthropicPoolCooldownResponse, clientCancelledResponse, readDisplaySafeErrorText, normalizeUpstreamErrorText } from "./core-errors";
|
|
28
|
+
import { isTranslatorBudgetExceededError } from "../../lib/translator-budget";
|
|
27
29
|
import { redactSecretString } from "../../lib/redact";
|
|
28
30
|
import { rewriteUpstreamPolicyRefusal } from "./policy-refusal";
|
|
29
31
|
import { withProviderRequestSlot } from "../../providers/request-pacing";
|
|
30
32
|
import { providerFetch, fetchWithHeaderTimeout, safeHostLabel } from "./fetch-helpers";
|
|
33
|
+
import { isAnthropicBillingKeyPool, rotateAnthropicBillingKeyOnResponse } from "../../providers/anthropic-billing-recovery";
|
|
31
34
|
import {
|
|
32
35
|
transientRetryPolicyFor,
|
|
33
36
|
resetReplayPolicyFor,
|
|
@@ -91,7 +94,8 @@ import {
|
|
|
91
94
|
} from "../../providers/opencode-zen-rate-limit";
|
|
92
95
|
import { planReasoningEffortDowngrade } from "../../providers/reasoning-metadata";
|
|
93
96
|
import { consumeComboFailure } from "./core-combo-failure";
|
|
94
|
-
import { streamingContextOverflowResponse, jsonContextOverflowResponse } from "./context-overflow";
|
|
97
|
+
import { streamingContextOverflowResponse, jsonContextOverflowResponse, lateAttributionLimitResponse } from "./context-overflow";
|
|
98
|
+
import { OllamaLateAttributionBudgetError } from "../../adapters/ollama-native";
|
|
95
99
|
import { isFixedCodexAccount } from "./core-codex-account";
|
|
96
100
|
import { recordSubagentQuotaFailureForThreadSpawn } from "../../codex/subagent-model-fallback";
|
|
97
101
|
import {
|
|
@@ -341,6 +345,14 @@ export async function prepareAdapterExchange(
|
|
|
341
345
|
cleanupUpstreamAbort();
|
|
342
346
|
upstream.abort();
|
|
343
347
|
if (options.abortSignal?.aborted) return clientCancelledResponse();
|
|
348
|
+
if (err instanceof OllamaLateAttributionBudgetError) return lateAttributionLimitResponse();
|
|
349
|
+
// An adapter can also throw the shared translator budget while sizing generated
|
|
350
|
+
// request content (e.g. Ollama late-tool attribution); keep its established 413.
|
|
351
|
+
if (isTranslatorBudgetExceededError(err)) {
|
|
352
|
+
return formatErrorResponse(413, "request_too_large", "request translation buffer exceeded the safe limit", {
|
|
353
|
+
code: "translation_buffer_limit",
|
|
354
|
+
});
|
|
355
|
+
}
|
|
344
356
|
const msg = err instanceof Error ? err.message : String(err);
|
|
345
357
|
return formatErrorResponse(400, "invalid_request_error", redactSecretString(msg));
|
|
346
358
|
}
|
|
@@ -362,6 +374,23 @@ export async function prepareAdapterExchange(
|
|
|
362
374
|
: transportState.oauth401ReplayAttempted ? "oauth-401" : undefined
|
|
363
375
|
: undefined;
|
|
364
376
|
let upstreamResponse: Response;
|
|
377
|
+
// Preserve bounded terminal bytes without retaining the refused send's pacing lease.
|
|
378
|
+
// Reading the owned body to EOF (or cancelling on its bound) frees capacity BEFORE
|
|
379
|
+
// replacement admission; keeping that live body would wait on its own concurrency slot.
|
|
380
|
+
const retainFailureResponse = async (): Promise<Response> => {
|
|
381
|
+
const original = upstreamResponse;
|
|
382
|
+
const text = await readDisplaySafeErrorText(original, upstream.signal, "");
|
|
383
|
+
const headers = new Headers(original.headers);
|
|
384
|
+
headers.delete("content-length");
|
|
385
|
+
headers.delete("content-encoding");
|
|
386
|
+
upstreamResponse = new Response(text, { status: original.status, statusText: original.statusText, headers });
|
|
387
|
+
return upstreamResponse;
|
|
388
|
+
};
|
|
389
|
+
const billingPoolRequest = isAnthropicBillingKeyPool(route.provider);
|
|
390
|
+
let billingRecoveryActive = false;
|
|
391
|
+
const genericRefetchCountsSends = () => sendBudgetState.adapterSendBudget?.spendEnforced === true
|
|
392
|
+
|| transientRetryPolicyFor(route.provider) !== null || resetReplayPolicyFor(route.provider) !== null
|
|
393
|
+
|| billingPoolRequest || billingRecoveryActive;
|
|
365
394
|
let handoffPermit: ResponsesSendBudget["pendingHopPermit"];
|
|
366
395
|
try {
|
|
367
396
|
if (transportState.activeAdapter.fetchResponse) {
|
|
@@ -434,6 +463,8 @@ export async function prepareAdapterExchange(
|
|
|
434
463
|
const fetchWithRetryPolicy = (route.provider.adapter === "google" || transientPolicy)
|
|
435
464
|
? fetchWithTransientRetry
|
|
436
465
|
: fetchWithResetRetry;
|
|
466
|
+
const initialReporter = transientSendReporter(compactPrepaid ?? options.comboDispatchPermit);
|
|
467
|
+
const deferredInitial = billingPoolRequest && !receiptMode ? deferPhysicalSendReporter(initialReporter) : undefined;
|
|
437
468
|
try { upstreamResponse = await fetchWithRetryPolicy(
|
|
438
469
|
recovery => {
|
|
439
470
|
// Unconfigured generic sends may omit the counting reporter. Capture policy
|
|
@@ -442,10 +473,11 @@ export async function prepareAdapterExchange(
|
|
|
442
473
|
poolId: logCtx.spendPoolId ?? route.providerName, identityId: logCtx.accountLogLabel,
|
|
443
474
|
});
|
|
444
475
|
// Receipt mode books the physical send at executor admission instead of here.
|
|
445
|
-
if (!receiptMode && compactPrepaid && !compactPrepaidUsed) {
|
|
476
|
+
if (!deferredInitial && !receiptMode && compactPrepaid && !compactPrepaidUsed) {
|
|
446
477
|
if (!compactPrepaid.use()) throw new SendBudgetExhaustedError(safeHostLabel(builtInitialRequest.url));
|
|
447
478
|
compactPrepaidUsed = true;
|
|
448
479
|
}
|
|
480
|
+
// Key transport keeps this metadata pending until it binds the actual selection.
|
|
449
481
|
transportState.noteRoutedAttemptSend(inputTokenEstimate, recovery ?? initialRecovery);
|
|
450
482
|
return fetchWithHeaderTimeout(builtInitialRequest.url, applyUpstreamRecoveryInit({
|
|
451
483
|
method: builtInitialRequest.method,
|
|
@@ -457,6 +489,13 @@ export async function prepareAdapterExchange(
|
|
|
457
489
|
providerName: route.providerName,
|
|
458
490
|
modelId: route.modelId,
|
|
459
491
|
onPhysicalDispatch: () => {
|
|
492
|
+
if (deferredInitial) {
|
|
493
|
+
if (!deferredInitial.beforeDispatch()) throw new SendBudgetExhaustedError(safeHostLabel(builtInitialRequest.url));
|
|
494
|
+
if (compactPrepaid && !compactPrepaidUsed) {
|
|
495
|
+
if (!compactPrepaid.use()) throw new SendBudgetExhaustedError(safeHostLabel(builtInitialRequest.url));
|
|
496
|
+
compactPrepaidUsed = true;
|
|
497
|
+
}
|
|
498
|
+
}
|
|
460
499
|
// Preserve the unconfigured direct-adapter accounting contract; Combo admission
|
|
461
500
|
// and explicit retry/compaction budgets require a receipt for their booked send.
|
|
462
501
|
if (receiptMode) {
|
|
@@ -473,6 +512,7 @@ export async function prepareAdapterExchange(
|
|
|
473
512
|
label: safeHostLabel(builtInitialRequest.url),
|
|
474
513
|
claimAmbiguousResend: claimPreHeaderResend,
|
|
475
514
|
...(sendBudgetState.adapterSendBudget?.spendEnforced || options.comboDispatchPermit || transientPolicy || resetPolicy || compactPrepaid || options.comboAttempt
|
|
515
|
+
|| billingPoolRequest
|
|
476
516
|
? {
|
|
477
517
|
// A pending compaction permit already booked this leg's first physical send.
|
|
478
518
|
...(transientPolicy || resetPolicy || compactPrepaid || options.comboAttempt
|
|
@@ -485,7 +525,7 @@ export async function prepareAdapterExchange(
|
|
|
485
525
|
// settled by `onPhysicalDispatch`, so a reporter here would count them twice.
|
|
486
526
|
...(receiptMode
|
|
487
527
|
? {}
|
|
488
|
-
: { onSendsConsumed:
|
|
528
|
+
: { onSendsConsumed: deferredInitial?.report ?? initialReporter }),
|
|
489
529
|
}
|
|
490
530
|
: {}),
|
|
491
531
|
},
|
|
@@ -516,8 +556,7 @@ export async function prepareAdapterExchange(
|
|
|
516
556
|
return formatErrorResponse(401, "authentication_error", publicOAuthAuthenticationErrorMessage(refusal));
|
|
517
557
|
}
|
|
518
558
|
if (refusal instanceof AnthropicAccountCooldownError) {
|
|
519
|
-
return
|
|
520
|
-
refusal.retryAfterSeconds === null ? undefined : { retryAfter: String(refusal.retryAfterSeconds) });
|
|
559
|
+
return anthropicPoolCooldownResponse(refusal, { comboAttempt: options.comboAttempt, logCtx });
|
|
521
560
|
}
|
|
522
561
|
// A budget refusal is a decision this process made, not an upstream fault. Reporting it as
|
|
523
562
|
// 502 does more than mislabel it: the Codex client retries 5xx and does not retry a 429, so
|
|
@@ -604,13 +643,26 @@ export async function prepareAdapterExchange(
|
|
|
604
643
|
recordAdapterReasoning(logCtx, retryRequest);
|
|
605
644
|
recordAdapterTier(logCtx, retryRequest);
|
|
606
645
|
} catch (err) {
|
|
607
|
-
|
|
646
|
+
// A rotated credential that cannot be shaped keeps the refusal it was meant to
|
|
647
|
+
// replace. The image-tier rebuild is different: the request itself failed to shape,
|
|
648
|
+
// and its established 413/400 build-error contract stays authoritative.
|
|
649
|
+
if (preserveFailureResponse && recovery !== "image-413" && !options.abortSignal?.aborted) {
|
|
650
|
+
return { failed: preserveFailureResponse };
|
|
651
|
+
}
|
|
608
652
|
// A rotated/rebuilt adapter build failure is a request-shaping error, not an
|
|
609
|
-
// upstream connect failure: tear the abort link down and map it as 400
|
|
610
|
-
// translator-budget
|
|
653
|
+
// upstream connect failure: tear the abort link down and map it as 400. A
|
|
654
|
+
// translator-budget refusal keeps its established 413 here as well — adapters
|
|
655
|
+
// can throw it while sizing generated content (e.g. Ollama late-tool
|
|
656
|
+
// attribution), not only parseRequest/buildToolBridgeMaps.
|
|
611
657
|
cleanupUpstreamAbort();
|
|
612
658
|
upstream.abort();
|
|
613
659
|
if (options.abortSignal?.aborted) return { failed: clientCancelledResponse() };
|
|
660
|
+
if (err instanceof OllamaLateAttributionBudgetError) return { failed: lateAttributionLimitResponse() };
|
|
661
|
+
if (isTranslatorBudgetExceededError(err)) {
|
|
662
|
+
return { failed: formatErrorResponse(413, "request_too_large", "request translation buffer exceeded the safe limit", {
|
|
663
|
+
code: "translation_buffer_limit",
|
|
664
|
+
}) };
|
|
665
|
+
}
|
|
614
666
|
const msg = err instanceof Error ? err.message : String(err);
|
|
615
667
|
return { failed: formatErrorResponse(400, "invalid_request_error", redactSecretString(msg)) };
|
|
616
668
|
}
|
|
@@ -673,7 +725,7 @@ export async function prepareAdapterExchange(
|
|
|
673
725
|
const refetchWithPolicy = (route.provider.adapter === "google" || refetchTransientPolicy)
|
|
674
726
|
? fetchWithTransientRetry
|
|
675
727
|
: fetchWithResetRetry;
|
|
676
|
-
const helperCountsSends =
|
|
728
|
+
const helperCountsSends = genericRefetchCountsSends();
|
|
677
729
|
const prepaid = sendBudgetState.pendingHopPermit;
|
|
678
730
|
const configuredTotal = refetchTransientPolicy?.attempts;
|
|
679
731
|
const refetchCap = transientSendCapFor(configuredTotal, sendBudgetState.targetSendsUsed);
|
|
@@ -689,27 +741,41 @@ export async function prepareAdapterExchange(
|
|
|
689
741
|
{ allowFinalRecoveryReserve: configuredTotal === undefined },
|
|
690
742
|
)
|
|
691
743
|
: undefined;
|
|
744
|
+
let refetchPermitUsed = false;
|
|
745
|
+
const refetchReporter = refetchAllowance ? transientSendReporter(refetchAllowance.permit) : undefined;
|
|
746
|
+
const deferredRefetch = billingPoolRequest && refetchReporter ? deferPhysicalSendReporter(refetchReporter) : undefined;
|
|
692
747
|
try {
|
|
693
748
|
return await refetchWithPolicy(
|
|
694
749
|
recoveryKind => {
|
|
695
750
|
sendBudgetState.adapterSendBudget?.startRequest?.({
|
|
696
751
|
poolId: logCtx.spendPoolId ?? route.providerName, identityId: logCtx.accountLogLabel,
|
|
697
752
|
});
|
|
698
|
-
|
|
699
|
-
|
|
700
|
-
|
|
701
|
-
|
|
702
|
-
|
|
703
|
-
|
|
704
|
-
|
|
705
|
-
|
|
706
|
-
|
|
753
|
+
// Prepare key metadata before the guarded transport commits its attempt;
|
|
754
|
+
// counters and permits still wait for the physical executor after pacing.
|
|
755
|
+
if (deferredRefetch) transportState.noteRoutedAttemptSend(retryEstimate, recoveryKind ?? recovery);
|
|
756
|
+
const confirmDispatch = () => {
|
|
757
|
+
if (refetchAllowance?.permit && !refetchPermitUsed) {
|
|
758
|
+
if (!refetchAllowance.permit.use()) throw new SendBudgetExhaustedError(safeHostLabel(retryRequest.url));
|
|
759
|
+
refetchPermitUsed = true;
|
|
760
|
+
}
|
|
761
|
+
if (!deferredRefetch) transportState.noteRoutedAttemptSend(retryEstimate, recoveryKind ?? recovery);
|
|
762
|
+
// Eligible pools confirm after pacing at the physical executor; legacy
|
|
763
|
+
// helper paths retain their existing thunk admission boundary.
|
|
764
|
+
onDispatch?.();
|
|
765
|
+
if (preserveFailureResponse) replacementAdmitted = true;
|
|
766
|
+
if (!refetchAllowance) chargeFastDowngradeWorkflowSend();
|
|
767
|
+
};
|
|
768
|
+
if (!deferredRefetch) confirmDispatch();
|
|
707
769
|
return fetchWithHeaderTimeout(retryRequest.url,
|
|
708
770
|
applyUpstreamRecoveryInit({
|
|
709
771
|
method: retryRequest.method, headers: retryRequest.headers, body: retryRequest.body,
|
|
710
772
|
}, recoveryKind), upstream.signal, connectMs, parsed.stream,
|
|
711
773
|
providerFetch(route.provider, options.codexWsRuntimeIdentity, {
|
|
712
774
|
dispatchOverride: oauthDispatch(retryRequest),
|
|
775
|
+
onPhysicalDispatch: deferredRefetch ? () => {
|
|
776
|
+
if (!deferredRefetch.beforeDispatch()) throw new SendBudgetExhaustedError(safeHostLabel(retryRequest.url));
|
|
777
|
+
confirmDispatch();
|
|
778
|
+
} : undefined,
|
|
713
779
|
providerName: route.providerName,
|
|
714
780
|
modelId: route.modelId,
|
|
715
781
|
beforeDispatch: isCanonicalOpenAiForwardProvider(route.provider)
|
|
@@ -723,14 +789,14 @@ export async function prepareAdapterExchange(
|
|
|
723
789
|
...(refetchAllowance
|
|
724
790
|
? {
|
|
725
791
|
attempts: refetchAllowance.attempts,
|
|
726
|
-
onSendsConsumed:
|
|
792
|
+
onSendsConsumed: deferredRefetch?.report ?? refetchReporter,
|
|
727
793
|
}
|
|
728
794
|
: {}),
|
|
729
795
|
},
|
|
730
796
|
);
|
|
731
797
|
} finally {
|
|
732
|
-
//
|
|
733
|
-
//
|
|
798
|
+
// Refund only a reservation whose send never reached its admission boundary.
|
|
799
|
+
// A used or externally settled permit ignores this.
|
|
734
800
|
refetchAllowance?.permit?.release();
|
|
735
801
|
}
|
|
736
802
|
} finally {
|
|
@@ -768,8 +834,7 @@ export async function prepareAdapterExchange(
|
|
|
768
834
|
return { failed: formatErrorResponse(401, "authentication_error", publicOAuthAuthenticationErrorMessage(refusal)) };
|
|
769
835
|
}
|
|
770
836
|
if (refusal instanceof AnthropicAccountCooldownError) {
|
|
771
|
-
return { failed:
|
|
772
|
-
refusal.retryAfterSeconds === null ? undefined : { retryAfter: String(refusal.retryAfterSeconds) }) };
|
|
837
|
+
return { failed: anthropicPoolCooldownResponse(refusal, { comboAttempt: options.comboAttempt, logCtx }) };
|
|
773
838
|
}
|
|
774
839
|
// Same rule on the recovery leg: the ladder refused to send again, so the answer names
|
|
775
840
|
// this proxy rather than the provider it never reached.
|
|
@@ -980,32 +1045,83 @@ export async function prepareAdapterExchange(
|
|
|
980
1045
|
promptCacheKey: parsed.options.promptCacheKey,
|
|
981
1046
|
});
|
|
982
1047
|
if (!rotated) break;
|
|
983
|
-
|
|
984
|
-
|
|
985
|
-
|
|
986
|
-
|
|
987
|
-
|
|
988
|
-
|
|
989
|
-
|
|
990
|
-
|
|
991
|
-
|
|
992
|
-
|
|
993
|
-
|
|
994
|
-
|
|
995
|
-
|
|
996
|
-
|
|
997
|
-
|
|
998
|
-
|
|
999
|
-
|
|
1000
|
-
|
|
1001
|
-
|
|
1002
|
-
|
|
1003
|
-
|
|
1004
|
-
|
|
1005
|
-
|
|
1006
|
-
|
|
1007
|
-
|
|
1008
|
-
|
|
1048
|
+
if (billingRecoveryActive && (options.abortSignal?.aborted || upstream.signal.aborted)) {
|
|
1049
|
+
cleanupUpstreamAbort(); upstream.abort(); return clientCancelledResponse();
|
|
1050
|
+
}
|
|
1051
|
+
const billingHop = (billingPoolRequest || billingRecoveryActive)
|
|
1052
|
+
? reserveCredentialHop("auth-recovery", `${route.providerName}|${route.modelId}|key-401`,
|
|
1053
|
+
transportState.activeAdapter.fetchResponse === undefined)
|
|
1054
|
+
: undefined;
|
|
1055
|
+
if (billingHop && !billingHop.allowed) break;
|
|
1056
|
+
const failedResponse = billingPoolRequest || billingRecoveryActive ? await retainFailureResponse() : undefined;
|
|
1057
|
+
try {
|
|
1058
|
+
// Release the failed response's socket before retrying; unread bodies otherwise linger
|
|
1059
|
+
// until runtime cleanup (one per rotated key).
|
|
1060
|
+
if (!failedResponse) try { void upstreamResponse.body?.cancel().catch(() => {}); } catch { /* already consumed/closed */ }
|
|
1061
|
+
route.provider = rotated;
|
|
1062
|
+
const selectionFailure = await resolveRotatedCopilotSelection();
|
|
1063
|
+
if (selectionFailure) return selectionFailure;
|
|
1064
|
+
invalidateSameTargetRequest();
|
|
1065
|
+
transportState.activeAdapter = resolveSelectionAdapter(
|
|
1066
|
+
resolveWireProtocolOverride(route.providerName, route.modelId, route.provider, inboundWire, route.staticPolicy),
|
|
1067
|
+
config.cacheRetention,
|
|
1068
|
+
);
|
|
1069
|
+
bindRouteReasoningReplayScope({
|
|
1070
|
+
parsed,
|
|
1071
|
+
providerName: route.providerName,
|
|
1072
|
+
provider: route.provider,
|
|
1073
|
+
adapterName: transportState.activeAdapter.name,
|
|
1074
|
+
});
|
|
1075
|
+
if (billingHop) sendBudgetState.pendingHopPermit = billingHop.permit;
|
|
1076
|
+
const result = await rebuildAndRefetch("key-401",
|
|
1077
|
+
billingHop ? () => { billingHop.permit?.use(); } : undefined, failedResponse);
|
|
1078
|
+
if ("failed" in result) {
|
|
1079
|
+
if (result.failed !== failedResponse) return result.failed;
|
|
1080
|
+
break;
|
|
1081
|
+
}
|
|
1082
|
+
if (failedResponse) {
|
|
1083
|
+
try { void failedResponse.body?.cancel().catch(() => {}); } catch { /* closed */ }
|
|
1084
|
+
}
|
|
1085
|
+
upstreamResponse = result;
|
|
1086
|
+
// A recovery refetch can itself die on an ambiguous pre-header reset, and the refusal
|
|
1087
|
+
// that answers it is a 429. Every arm below keys on 429, so letting it fall through
|
|
1088
|
+
// hands the marked refusal to the next waiting arm and replays the send it exists to
|
|
1089
|
+
// stop. Re-enter the loop guard instead, which returns it unchanged.
|
|
1090
|
+
if (isNonReplayableResponse(upstreamResponse)) continue recovery;
|
|
1091
|
+
} finally {
|
|
1092
|
+
if (billingHop) { sendBudgetState.pendingHopPermit = undefined; billingHop.permit?.release(); }
|
|
1093
|
+
}
|
|
1094
|
+
}
|
|
1095
|
+
|
|
1096
|
+
while ((upstreamResponse.status === 400 || upstreamResponse.status === 402) && hasKeyPoolFailover(route.provider)) {
|
|
1097
|
+
const rotated = await rotateAnthropicBillingKeyOnResponse(upstreamResponse, config, route.providerName, route.provider,
|
|
1098
|
+
{ signal: upstream.signal, promptCacheKey: parsed.options.promptCacheKey });
|
|
1099
|
+
if (upstream.signal.aborted || options.abortSignal?.aborted) {
|
|
1100
|
+
cleanupUpstreamAbort(); upstream.abort(); return clientCancelledResponse();
|
|
1101
|
+
}
|
|
1102
|
+
if (!rotated) break;
|
|
1103
|
+
billingRecoveryActive = true;
|
|
1104
|
+
const hop = reserveCredentialHop("auth-recovery", `${route.providerName}|${route.modelId}|key-401`,
|
|
1105
|
+
transportState.activeAdapter.fetchResponse === undefined);
|
|
1106
|
+
if (!hop.allowed) break;
|
|
1107
|
+
const failedResponse = await retainFailureResponse();
|
|
1108
|
+
try {
|
|
1109
|
+
route.provider = rotated;
|
|
1110
|
+
invalidateSameTargetRequest();
|
|
1111
|
+
transportState.activeAdapter = resolveSelectionAdapter(
|
|
1112
|
+
resolveWireProtocolOverride(route.providerName, route.modelId, route.provider, inboundWire, route.staticPolicy), config.cacheRetention);
|
|
1113
|
+
bindRouteReasoningReplayScope({ parsed, providerName: route.providerName, provider: route.provider, adapterName: transportState.activeAdapter.name });
|
|
1114
|
+
sendBudgetState.pendingHopPermit = hop.permit;
|
|
1115
|
+
const result = await rebuildAndRefetch("key-401", () => { hop.permit?.use(); }, failedResponse);
|
|
1116
|
+
if ("failed" in result) {
|
|
1117
|
+
if (result.failed !== failedResponse) return result.failed;
|
|
1118
|
+
break;
|
|
1119
|
+
}
|
|
1120
|
+
try { void failedResponse.body?.cancel().catch(() => {}); } catch { /* closed */ }
|
|
1121
|
+
upstreamResponse = result;
|
|
1122
|
+
continue recovery;
|
|
1123
|
+
} finally { sendBudgetState.pendingHopPermit = undefined; hop.permit?.release(); }
|
|
1124
|
+
}
|
|
1009
1125
|
|
|
1010
1126
|
// Anthropic fast mode refused (no usage credits, organization not enabled, model outside
|
|
1011
1127
|
// the lane, fast pool empty): resend once at standard speed, as Claude Code does. This
|
|
@@ -1024,7 +1140,7 @@ export async function prepareAdapterExchange(
|
|
|
1024
1140
|
const hop = reserveCredentialHop(
|
|
1025
1141
|
"repair",
|
|
1026
1142
|
`${route.providerName}|${route.modelId}|anthropic-fast-downgrade`,
|
|
1027
|
-
!adapterOwnsDispatch && (
|
|
1143
|
+
!adapterOwnsDispatch && genericRefetchCountsSends(),
|
|
1028
1144
|
);
|
|
1029
1145
|
if (hop.allowed) {
|
|
1030
1146
|
anthropicFastDowngradeGuard.attempted = true;
|
|
@@ -1093,7 +1209,9 @@ export async function prepareAdapterExchange(
|
|
|
1093
1209
|
upstreamResponse = result;
|
|
1094
1210
|
// The refusal is a 429 too: without this the while condition is still true and the
|
|
1095
1211
|
// next configured attempt replays it on the same target.
|
|
1096
|
-
if (isNonReplayableResponse(upstreamResponse)
|
|
1212
|
+
if (isNonReplayableResponse(upstreamResponse)
|
|
1213
|
+
|| (isAnthropicBillingKeyPool(route.provider)
|
|
1214
|
+
&& [400, 402].includes(upstreamResponse.status))) continue recovery;
|
|
1097
1215
|
}
|
|
1098
1216
|
|
|
1099
1217
|
// Multi-key 429 failover: rotate to the next pool key (cooldown-aware) and retry the
|
|
@@ -1128,30 +1246,53 @@ export async function prepareAdapterExchange(
|
|
|
1128
1246
|
quotaResetAt,
|
|
1129
1247
|
});
|
|
1130
1248
|
if (!rotated) break;
|
|
1131
|
-
|
|
1132
|
-
|
|
1133
|
-
|
|
1134
|
-
|
|
1135
|
-
|
|
1136
|
-
|
|
1137
|
-
|
|
1138
|
-
|
|
1139
|
-
|
|
1140
|
-
|
|
1141
|
-
|
|
1142
|
-
|
|
1143
|
-
|
|
1144
|
-
|
|
1145
|
-
|
|
1146
|
-
|
|
1147
|
-
|
|
1148
|
-
|
|
1149
|
-
|
|
1150
|
-
|
|
1151
|
-
|
|
1152
|
-
|
|
1153
|
-
|
|
1154
|
-
|
|
1249
|
+
if (billingRecoveryActive && (options.abortSignal?.aborted || upstream.signal.aborted)) {
|
|
1250
|
+
cleanupUpstreamAbort(); upstream.abort(); return clientCancelledResponse();
|
|
1251
|
+
}
|
|
1252
|
+
const billingHop = (billingPoolRequest || billingRecoveryActive)
|
|
1253
|
+
? reserveCredentialHop("auth-recovery", `${route.providerName}|${route.modelId}|key-429`,
|
|
1254
|
+
transportState.activeAdapter.fetchResponse === undefined)
|
|
1255
|
+
: undefined;
|
|
1256
|
+
if (billingHop && !billingHop.allowed) break;
|
|
1257
|
+
const failedResponse = billingPoolRequest || billingRecoveryActive ? await retainFailureResponse() : undefined;
|
|
1258
|
+
try {
|
|
1259
|
+
// Release the failed response's socket before retrying; unread bodies otherwise linger
|
|
1260
|
+
// until runtime cleanup (one per rotated key under a rate-limit storm).
|
|
1261
|
+
if (!failedResponse) try { void upstreamResponse.body?.cancel().catch(() => {}); } catch { /* already consumed/closed */ }
|
|
1262
|
+
route.provider = rotated;
|
|
1263
|
+
const selectionFailure = await resolveRotatedCopilotSelection();
|
|
1264
|
+
if (selectionFailure) return selectionFailure;
|
|
1265
|
+
invalidateSameTargetRequest();
|
|
1266
|
+
transportState.activeAdapter = resolveSelectionAdapter(
|
|
1267
|
+
resolveWireProtocolOverride(route.providerName, route.modelId, route.provider, inboundWire, route.staticPolicy),
|
|
1268
|
+
config.cacheRetention,
|
|
1269
|
+
);
|
|
1270
|
+
bindRouteReasoningReplayScope({
|
|
1271
|
+
parsed,
|
|
1272
|
+
providerName: route.providerName,
|
|
1273
|
+
provider: route.provider,
|
|
1274
|
+
adapterName: transportState.activeAdapter.name,
|
|
1275
|
+
});
|
|
1276
|
+
if (billingHop) sendBudgetState.pendingHopPermit = billingHop.permit;
|
|
1277
|
+
const result = await rebuildAndRefetch("key-429",
|
|
1278
|
+
billingHop ? () => { billingHop.permit?.use(); } : undefined, failedResponse);
|
|
1279
|
+
if ("failed" in result) {
|
|
1280
|
+
if (result.failed !== failedResponse) return result.failed;
|
|
1281
|
+
break;
|
|
1282
|
+
}
|
|
1283
|
+
if (failedResponse) {
|
|
1284
|
+
try { void failedResponse.body?.cancel().catch(() => {}); } catch { /* closed */ }
|
|
1285
|
+
}
|
|
1286
|
+
upstreamResponse = result;
|
|
1287
|
+
// Rotating on the refusal would also write a cooldown against a key that rate-limited
|
|
1288
|
+
// nothing, which outlives the request.
|
|
1289
|
+
if (isNonReplayableResponse(upstreamResponse)
|
|
1290
|
+
|| (billingRecoveryActive && upstreamResponse.status === 401)
|
|
1291
|
+
|| (route.provider.adapter === "anthropic" && [400, 402].includes(upstreamResponse.status))) continue recovery;
|
|
1292
|
+
} finally {
|
|
1293
|
+
if (billingHop) { sendBudgetState.pendingHopPermit = undefined; billingHop.permit?.release(); }
|
|
1294
|
+
}
|
|
1295
|
+
}
|
|
1155
1296
|
|
|
1156
1297
|
// Anthropic OAuth: recover a rate limit or proven account entitlement refusal
|
|
1157
1298
|
// before output, within the shared request and account rotation limits.
|
|
@@ -1539,9 +1680,13 @@ export async function prepareAdapterExchange(
|
|
|
1539
1680
|
imageRetryAttempted = true;
|
|
1540
1681
|
transportState.imageTierBias = 1;
|
|
1541
1682
|
invalidateSameTargetRequest();
|
|
1542
|
-
|
|
1543
|
-
const result = await rebuildAndRefetch("image-413");
|
|
1544
|
-
if ("failed" in result)
|
|
1683
|
+
const failedResponse = await retainFailureResponse();
|
|
1684
|
+
const result = await rebuildAndRefetch("image-413", undefined, failedResponse);
|
|
1685
|
+
if ("failed" in result) {
|
|
1686
|
+
if (result.failed !== failedResponse) return result.failed;
|
|
1687
|
+
break recovery;
|
|
1688
|
+
}
|
|
1689
|
+
try { void failedResponse.body?.cancel().catch(() => {}); } catch { /* closed */ }
|
|
1545
1690
|
upstreamResponse = result;
|
|
1546
1691
|
continue recovery;
|
|
1547
1692
|
}
|
|
@@ -1658,6 +1803,14 @@ export async function prepareAdapterExchange(
|
|
|
1658
1803
|
requestState.subagentFallbackAccountId,
|
|
1659
1804
|
);
|
|
1660
1805
|
}
|
|
1806
|
+
// The last pool account just refused on spent quota and recovery found no replacement:
|
|
1807
|
+
// answer the usage limit now (#6991), so Codex stops instead of retrying into the local
|
|
1808
|
+
// refusal that every later attempt meets until the first window reopens.
|
|
1809
|
+
const poolUsageLimit = upstreamResponse.status === 429 && anthropicInstance && route.provider.authMode === "oauth"
|
|
1810
|
+
? anthropicRoutingFor(anthropicInstance).anthropicPoolUsageLimitAfterRefusal(Date.now(), transportState.anthropicRouteDecision, route.modelId)
|
|
1811
|
+
: null;
|
|
1812
|
+
// Combo children returned above; the flag is passed anyway so this site states the same rule.
|
|
1813
|
+
if (poolUsageLimit) return anthropicPoolCooldownResponse(poolUsageLimit, { comboAttempt: options.comboAttempt, logCtx });
|
|
1661
1814
|
// Upstreams occasionally echo request details in error bodies — scrub token-shaped
|
|
1662
1815
|
// material before it reaches the client-facing error surface.
|
|
1663
1816
|
const upstreamRetryAfter = upstreamResponse.headers.get("retry-after");
|