@bitkyc08/opencodex 2.80.0 → 2.82.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/bin/ocx.mjs +166 -73
- package/gui/dist/assets/App-Ds_qL7o8.js +54 -0
- package/gui/dist/assets/Tray-BqHF5Trd.js +1 -0
- package/gui/dist/assets/{index-Brk6F3fn.css → index-BAbcj51J.css} +1 -1
- package/gui/dist/assets/index-D3FBCNiP.js +94 -0
- package/gui/dist/assets/quota-summary-DTBC8n7-.js +1 -0
- package/gui/dist/index.html +2 -2
- 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 +8 -3
- 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/openai-responses/compaction-search-history.ts +109 -0
- package/src/adapters/openai-responses/passthrough.ts +9 -4
- package/src/adapters/openai-responses/tool-output-recovery.ts +20 -0
- 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/agents-inject.ts +14 -3
- package/src/claude/context-windows.ts +19 -17
- package/src/claude/desktop-3p.ts +88 -12
- package/src/claude/desktop-profile.ts +2 -1
- package/src/claude/gateway-cache.ts +18 -1
- package/src/claude/inbound-cache-stabilize.ts +4 -9
- package/src/claude/inbound.ts +78 -3
- package/src/claude/intercept/cli-catalog.ts +3 -0
- package/src/claude/intercept/model-bindings.ts +1 -0
- package/src/claude/intercept/picker-ca-persistence.ts +7 -6
- package/src/claude/intercept/picker-listener.ts +71 -4
- package/src/claude/intercept/picker-models.ts +14 -6
- package/src/claude/intercept/settings.ts +3 -0
- package/src/claude/long-context.ts +66 -0
- package/src/claude/model-info.ts +19 -16
- package/src/claude/outbound.ts +94 -9
- package/src/claude/subagent-model.ts +40 -11
- 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/agent.ts +9 -5
- package/src/cli/aside-profile-recovery.ts +33 -0
- package/src/cli/capabilities-accounts.ts +2 -2
- package/src/cli/capabilities-agents-routing.ts +24 -0
- package/src/cli/capabilities-base.ts +5 -5
- package/src/cli/capabilities-integrations.ts +18 -2
- package/src/cli/cli-path-diagnostics.ts +132 -0
- package/src/cli/codex-shim-autorestore.ts +3 -0
- package/src/cli/desktop-runtime-guidance.ts +65 -0
- package/src/cli/dispatch.ts +15 -1
- package/src/cli/doctor.ts +16 -3
- package/src/cli/help.ts +4 -1
- package/src/cli/index.ts +25 -14
- package/src/cli/integration-aside-sync.ts +2 -1
- package/src/cli/integration-plan-dto.ts +1 -0
- package/src/cli/integration-preview.ts +8 -1
- package/src/cli/integrations.ts +48 -6
- 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/cli/resolve.ts +25 -2
- package/src/cli/root.ts +4 -2
- package/src/cli/status.ts +64 -12
- package/src/cli/update-restart-child.ts +4 -2
- package/src/cli/update-restart-home.ts +28 -10
- package/src/cli/update-restart-service-record.ts +116 -0
- package/src/cli/update-restart-supervision.ts +96 -0
- package/src/cli/update-restart.ts +59 -13
- package/src/cli/v2.ts +27 -20
- package/src/cli/version-skew-notice.ts +77 -0
- 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/clients/lazycodex.ts +1 -1
- package/src/clients/omo-role-models.ts +32 -7
- package/src/codex/auth-api/account-list.ts +11 -3
- package/src/codex/auth-api/account-selection.ts +107 -0
- 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 +11 -49
- package/src/codex/auth-context.ts +20 -1
- package/src/codex/autostart-health.ts +12 -3
- package/src/codex/catalog/auto-review.ts +13 -6
- package/src/codex/catalog/build-entries.ts +33 -7
- package/src/codex/catalog/bundled.ts +155 -26
- package/src/codex/catalog/combo-member.ts +3 -2
- package/src/codex/catalog/control-plane.ts +45 -0
- package/src/codex/catalog/effort.ts +37 -6
- package/src/codex/catalog/gather-capture.ts +10 -3
- package/src/codex/catalog/model-hints.ts +15 -8
- package/src/codex/catalog/model-visibility.ts +3 -1
- package/src/codex/catalog/operator-tiers.ts +38 -0
- package/src/codex/catalog/parsing.ts +2 -1
- package/src/codex/catalog/provider-models.ts +58 -44
- package/src/codex/catalog/retained-sync.ts +7 -5
- package/src/codex/catalog/routed-gather.ts +15 -1
- package/src/codex/catalog-auto-refresh-sources.ts +3 -4
- package/src/codex/catalog-auto-refresh.ts +19 -0
- package/src/codex/catalog.ts +1 -1
- package/src/codex/codex-home-owner.ts +3 -0
- package/src/codex/config-write-lock.ts +333 -0
- package/src/codex/control-plane-models.ts +6 -0
- package/src/codex/convergence.ts +2 -1
- package/src/codex/features.ts +257 -200
- package/src/codex/inject/config-write-section.ts +34 -0
- package/src/codex/inject/multi-agent-v2.ts +14 -6
- package/src/codex/inject/remove.ts +36 -11
- package/src/codex/inject/restore.ts +85 -20
- package/src/codex/inject-coordination.ts +15 -6
- package/src/codex/inject.ts +62 -20
- package/src/codex/journal.ts +62 -17
- package/src/codex/main-account-cache.ts +43 -0
- package/src/codex/paths.ts +2 -0
- package/src/codex/prepared-home.ts +18 -0
- package/src/codex/prompt-journal.ts +25 -12
- package/src/codex/prompt-layers.ts +49 -26
- package/src/codex/prompt-lock-claim.ts +134 -0
- package/src/codex/prompt-lock-evidence.ts +26 -0
- package/src/codex/prompt-lock-io.ts +16 -0
- package/src/codex/prompt-lock-owner.ts +130 -0
- package/src/codex/prompt-lock.ts +84 -53
- package/src/codex/routing/failure-window.ts +146 -0
- package/src/codex/routing/selection.ts +4 -1
- package/src/codex/routing.ts +13 -17
- package/src/codex/runtime.ts +106 -18
- package/src/codex/shim.ts +45 -19
- package/src/combos/jev-decision-contract.ts +25 -2
- package/src/combos/jev-service-exchange.ts +281 -0
- package/src/combos/jev.ts +34 -219
- package/src/combos/types.ts +1 -1
- package/src/config/atomic-write.ts +1 -1
- package/src/config/codex-ws-reuse-setting.ts +10 -0
- package/src/config/diagnostics.ts +27 -16
- package/src/config/live-reconcile.ts +17 -0
- package/src/config/load-degrade.ts +42 -0
- package/src/config/rebase-provenance.ts +11 -0
- package/src/config/schema/anthropic-account-pool.ts +143 -0
- package/src/config/schema/config-schema.ts +11 -1
- package/src/config/schema/leaf-validators.ts +4 -0
- package/src/config.ts +2 -0
- package/src/generated/compatibility-version.json +566 -298
- package/src/generated/model-metadata.ts +7 -6
- package/src/grok/inject.ts +50 -4
- package/src/grok/sync.ts +2 -1
- package/src/images/loop.ts +30 -11
- package/src/integrations/aside-profiles.ts +10 -3
- package/src/integrations/catalog-refresh.ts +19 -3
- 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 +17 -5
- package/src/lib/bun-path-runtime.d.mts +32 -0
- package/src/lib/bun-path-runtime.mjs +95 -0
- package/src/lib/bun-runtime-preflight.ts +140 -0
- package/src/lib/desktop-cli-handoff.d.mts +20 -0
- package/src/lib/desktop-cli-handoff.mjs +92 -0
- package/src/lib/desktop-cli-record.d.mts +29 -0
- package/src/lib/desktop-cli-record.mjs +102 -0
- package/src/lib/lab-live-route-production.ts +9 -1
- package/src/lib/request-execution-budget.ts +244 -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/synced-state-location.ts +46 -6
- package/src/lib/test-home-guard.ts +87 -1
- package/src/lib/translator-budget.ts +17 -4
- package/src/lib/upstream-retry.ts +17 -5
- package/src/lib/windows-atomic-replace.ts +38 -16
- package/src/lib/windows-elevation.ts +6 -0
- package/src/lib/windows-secret-acl.ts +92 -25
- package/src/lib/winsw.ts +2 -3
- 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 +90 -18
- 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/chatgpt.ts +113 -41
- package/src/oauth/generic-account-failover.ts +3 -1
- package/src/oauth/health.ts +8 -6
- package/src/oauth/index.ts +96 -175
- 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 +68 -10
- 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/azure-model-metadata.ts +115 -0
- package/src/providers/derive.ts +23 -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-key.ts +127 -17
- package/src/providers/quota/vendor-probes-oauth.ts +46 -14
- package/src/providers/quota.ts +23 -33
- 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/reasoning-envelope.ts +29 -1
- package/src/responses/reasoning-replay-cache.ts +17 -1
- package/src/responses/spill-store.ts +20 -3
- package/src/responses/state/metrics.ts +95 -0
- package/src/responses/state/snapshot-codec.ts +6 -0
- package/src/responses/state/spill-failure.ts +12 -0
- package/src/responses/state/spill-queue.ts +13 -5
- package/src/responses/state/unforced-store-false.ts +55 -0
- package/src/responses/state.ts +68 -76
- package/src/responses/task-input.ts +17 -0
- 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/background-lifecycle.ts +27 -0
- package/src/server/chat-completions.ts +7 -1
- package/src/server/chat-native.ts +64 -28
- package/src/server/claude-messages.ts +59 -8
- package/src/server/gui-static.ts +38 -4
- package/src/server/inbound-body-admission.ts +1 -0
- package/src/server/index/claude-intercept-lifecycle.ts +3 -1
- package/src/server/index/serve-options.ts +10 -2
- package/src/server/index/spend-ledger-lifecycle.ts +12 -5
- package/src/server/index/startup-warnings.ts +19 -0
- package/src/server/index.ts +5 -2
- package/src/server/inference/context.ts +1 -1
- package/src/server/lifecycle.ts +1 -1
- package/src/server/management/agent-settings-routes.ts +123 -38
- 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/codex-agent-role-routes.ts +4 -2
- package/src/server/management/codex-prompt-routes.ts +1 -0
- package/src/server/management/codex-role-auto-assign.ts +13 -2
- package/src/server/management/config-routes.ts +85 -14
- package/src/server/management/context.ts +2 -2
- 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/shared.ts +3 -3
- 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 +91 -45
- package/src/server/messages-native-selector.ts +39 -0
- package/src/server/messages-native.ts +133 -38
- package/src/server/messages-response-headers.ts +14 -0
- package/src/server/request-log-conversation.ts +48 -5
- package/src/server/request-log.ts +36 -2
- package/src/server/responses/adapter-continuation.ts +31 -19
- package/src/server/responses/adapter-dispatch.ts +75 -24
- package/src/server/responses/antigravity-validation-refusal.ts +20 -6
- package/src/server/responses/codex-ws-exchange.ts +17 -2
- package/src/server/responses/codex-ws-pool.ts +100 -13
- package/src/server/responses/codex-ws-wire.ts +9 -0
- package/src/server/responses/combo-requested-effort.ts +3 -1
- package/src/server/responses/compact.ts +25 -8
- package/src/server/responses/compaction-recovery.ts +1 -1
- package/src/server/responses/core-codex-account.ts +22 -1
- package/src/server/responses/core-combo-native.ts +24 -4
- package/src/server/responses/core-combo.ts +38 -25
- package/src/server/responses/core-lifetime.ts +3 -36
- package/src/server/responses/core-normalize.ts +1 -0
- package/src/server/responses/core-options.ts +8 -1
- package/src/server/responses/core-replay.ts +78 -0
- package/src/server/responses/core.ts +3 -3
- package/src/server/responses/fetch-helpers.ts +26 -3
- package/src/server/responses/passthrough-delivery.ts +30 -4
- package/src/server/responses/passthrough-dispatch.ts +39 -9
- package/src/server/responses/request-prepare.ts +41 -5
- package/src/server/responses/request-send-budget.ts +150 -50
- package/src/server/responses/request-spend.ts +325 -33
- package/src/server/responses/request-transport.ts +132 -52
- package/src/server/responses/response-effects.ts +1 -0
- package/src/server/responses/run-turn-execution.ts +13 -3
- package/src/server/responses/sidecar-execution.ts +113 -22
- package/src/server/responses/sidecar-send-budget.ts +58 -0
- package/src/server/responses/ws-upstream.ts +19 -6
- package/src/server/responses-undeclared-tool-guard.ts +5 -1
- package/src/server/startup-health-cache.ts +2 -1
- package/src/server/workflow-refusal.ts +33 -0
- package/src/service/cli.ts +20 -4
- package/src/service/desktop-command-guard.ts +32 -0
- package/src/service/desktop-startup.ts +66 -40
- package/src/service/desktop-supervision.d.mts +26 -0
- package/src/service/desktop-supervision.mjs +140 -0
- package/src/service/managing-cli.ts +1 -0
- package/src/service/orchestration.ts +46 -20
- package/src/service/ownership-mutation-lease.d.mts +1 -1
- package/src/service/ownership-mutation-lease.mjs +5 -7
- package/src/service/repair.ts +25 -9
- package/src/service/state.ts +4 -3
- package/src/service/systemd.ts +17 -8
- package/src/service/windows-ops.ts +61 -22
- package/src/sidecar/anthropic-binding.ts +79 -0
- package/src/sidecar/auth.ts +76 -2
- package/src/storage/policy-job.ts +5 -1
- package/src/storage/policy-worker.ts +3 -1
- package/src/storage/policy.ts +9 -0
- package/src/types/anthropic-account-pool.ts +33 -0
- package/src/types/config.ts +33 -28
- package/src/types/provider.ts +5 -0
- package/src/types/request.ts +5 -0
- package/src/types/tools.ts +7 -0
- package/src/types/wire.ts +1 -0
- package/src/types.ts +4 -0
- package/src/update/index.ts +57 -9
- package/src/update/restart-ownership.ts +10 -3
- package/src/update/runtime-ownership.d.mts +6 -0
- package/src/update/runtime-ownership.mjs +23 -7
- 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 +32 -7
- 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
|
@@ -2,6 +2,8 @@
|
|
|
2
2
|
import { credentialGeneration, getAccountCredential } from "./store";
|
|
3
3
|
import { getCachedProviderAccountQuota } from "../providers/quota/account-cache";
|
|
4
4
|
import type { ProviderQuota, ProviderQuotaWindow } from "../providers/quota-types";
|
|
5
|
+
import type { AnthropicInstanceId } from "../providers/anthropic-instance-id";
|
|
6
|
+
import type { GenerationContext } from "../lib/state-store-sweeper";
|
|
5
7
|
|
|
6
8
|
export const ANTHROPIC_PASSIVE_FAMILY_MAX_AGE_MS = 30 * 60_000;
|
|
7
9
|
export type AnthropicModelFamily = "Fable" | "Opus" | "Sonnet";
|
|
@@ -17,67 +19,137 @@ export function anthropicModelPercents(quota: ProviderQuota | null, model?: stri
|
|
|
17
19
|
...(quota?.customWindows ?? []).filter(window => !model || window.scope !== "model" || (family && window.label === family)).map(window => window.percent)]
|
|
18
20
|
.filter((value): value is number => typeof value === "number" && Number.isFinite(value));
|
|
19
21
|
}
|
|
20
|
-
export function anthropicModelExhausted(accountId: string, model?: string): boolean {
|
|
21
|
-
return anthropicModelPercents(getCachedProviderAccountQuota("anthropic", accountId), model).some(percent => percent >= 100);
|
|
22
|
-
}
|
|
23
|
-
|
|
24
22
|
interface FamilyObservation { generation: string; seenAt: number; resetAt?: number; busy: boolean; }
|
|
25
|
-
const spentFamilies = new Map<string, FamilyObservation>();
|
|
26
|
-
const familyGenerations = new Map<string, number>();
|
|
27
|
-
let nextFamilyGeneration = 1;
|
|
28
|
-
export function anthropicFamilyQuotaGeneration(id: string): number { return familyGenerations.get(id) ?? 0; }
|
|
29
23
|
const keyFor = (id: string, family: string) => `${id}\0${family}`;
|
|
30
|
-
function
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
return
|
|
24
|
+
function createAnthropicModelQuota(instance: AnthropicInstanceId) {
|
|
25
|
+
let spentFamilies: Map<string, FamilyObservation> | undefined;
|
|
26
|
+
let familyGenerations: Map<string, number> | undefined;
|
|
27
|
+
let nextFamilyGeneration = 1;
|
|
28
|
+
let emptyGeneration = 0;
|
|
29
|
+
function bump(id: string): void { (familyGenerations ??= new Map()).set(id, nextFamilyGeneration++); }
|
|
30
|
+
function anthropicFamilyQuotaGeneration(id: string): number { return familyGenerations?.get(id) ?? emptyGeneration; }
|
|
31
|
+
/** Reserving an asynchronous probe is a write; ordinary generation reads stay allocation-free. */
|
|
32
|
+
function captureAnthropicFamilyQuotaGeneration(id: string): number {
|
|
33
|
+
if (!familyGenerations?.has(id)) bump(id);
|
|
34
|
+
return anthropicFamilyQuotaGeneration(id);
|
|
35
|
+
}
|
|
36
|
+
function anthropicModelExhausted(accountId: string, model?: string): boolean {
|
|
37
|
+
return anthropicModelPercents(getCachedProviderAccountQuota(instance, accountId), model).some(percent => percent >= 100);
|
|
38
|
+
}
|
|
39
|
+
function ownedObservation(id: string, family: string): FamilyObservation | undefined {
|
|
40
|
+
const key = keyFor(id, family);
|
|
41
|
+
const entry = spentFamilies?.get(key);
|
|
42
|
+
if (!entry) return undefined;
|
|
43
|
+
const credential = getAccountCredential(instance, id);
|
|
44
|
+
if (!credential || credentialGeneration(credential) !== entry.generation) { spentFamilies!.delete(key); bump(id); return undefined; }
|
|
45
|
+
return entry;
|
|
46
|
+
}
|
|
47
|
+
function observeAnthropicFamilyQuota(id: string, windows: ProviderQuotaWindow[], now: number, authoritative = false): void {
|
|
48
|
+
const credential = getAccountCredential(instance, id);
|
|
49
|
+
if (!credential) return;
|
|
50
|
+
if (!authoritative && !windows.some(window => window.scope === "model")) return;
|
|
51
|
+
bump(id);
|
|
52
|
+
const observations = spentFamilies ??= new Map();
|
|
53
|
+
if (authoritative) for (const key of observations.keys()) if (key.startsWith(`${id}\0`)) observations.delete(key);
|
|
54
|
+
for (const window of windows) {
|
|
55
|
+
if (window.scope !== "model") continue;
|
|
56
|
+
const key = keyFor(id, window.label);
|
|
57
|
+
if (!window.rejected) { observations.delete(key); continue; }
|
|
58
|
+
observations.set(key, { generation: credentialGeneration(credential), seenAt: now, resetAt: window.resetAt, busy: false });
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
function anthropicFamilyRejected(id: string, model?: string, now = Date.now()): boolean {
|
|
62
|
+
const family = anthropicModelFamily(model);
|
|
63
|
+
const entry = family && ownedObservation(id, family);
|
|
64
|
+
if (!entry) return false;
|
|
65
|
+
if (entry.resetAt !== undefined && entry.resetAt <= now) return entry.busy;
|
|
66
|
+
return entry.busy || now - entry.seenAt < ANTHROPIC_PASSIVE_FAMILY_MAX_AGE_MS;
|
|
67
|
+
}
|
|
68
|
+
/** Only stale passive family exclusions require a request-driven single-flight physical send. */
|
|
69
|
+
function claimAnthropicFamilyRevalidation(id: string, model?: string, now = Date.now()): (() => void) | null {
|
|
70
|
+
const family = anthropicModelFamily(model);
|
|
71
|
+
const entry = family && ownedObservation(id, family);
|
|
72
|
+
if (!entry) return () => {};
|
|
73
|
+
if (anthropicFamilyRejected(id, model, now)) return null;
|
|
74
|
+
entry.busy = true;
|
|
75
|
+
return () => { if (spentFamilies?.get(keyFor(id, family!)) === entry) entry.busy = false; };
|
|
76
|
+
}
|
|
77
|
+
/** An owned successful send proves admission for its requested family even without headers. */
|
|
78
|
+
function clearAnthropicRequestedFamilyQuota(id: string, model?: string): void {
|
|
79
|
+
const family = anthropicModelFamily(model);
|
|
80
|
+
if (family && spentFamilies?.delete(keyFor(id, family))) bump(id);
|
|
81
|
+
}
|
|
82
|
+
function clearAnthropicFamilyQuota(): void {
|
|
83
|
+
// A prior zero-generation claim must stay stale even after an identical account is re-added.
|
|
84
|
+
emptyGeneration = nextFamilyGeneration++;
|
|
85
|
+
spentFamilies = undefined;
|
|
86
|
+
familyGenerations = undefined;
|
|
87
|
+
}
|
|
88
|
+
function reconcileAnthropicFamilyQuota(context: GenerationContext): number {
|
|
89
|
+
let removed = 0;
|
|
90
|
+
const retired = new Set<string>();
|
|
91
|
+
for (const [key] of spentFamilies ?? []) {
|
|
92
|
+
const id = key.slice(0, key.indexOf("\0"));
|
|
93
|
+
if (context.oauthAccountKeys.has(`${instance}\0${id}`)) continue;
|
|
94
|
+
spentFamilies!.delete(key); retired.add(id); removed++;
|
|
95
|
+
}
|
|
96
|
+
for (const id of familyGenerations?.keys() ?? []) {
|
|
97
|
+
if (!context.oauthAccountKeys.has(`${instance}\0${id}`)) retired.add(id);
|
|
98
|
+
}
|
|
99
|
+
for (const id of retired) bump(id);
|
|
100
|
+
return removed;
|
|
101
|
+
}
|
|
102
|
+
function anthropicFamilyRetryAt(id: string, model?: string, now = Date.now()): number | undefined {
|
|
103
|
+
const family = anthropicModelFamily(model);
|
|
104
|
+
const entry = family && ownedObservation(id, family);
|
|
105
|
+
if (!entry || !anthropicFamilyRejected(id, model, now)) return undefined;
|
|
106
|
+
return entry.busy ? now + 1000 : Math.min(entry.resetAt ?? Infinity, entry.seenAt + ANTHROPIC_PASSIVE_FAMILY_MAX_AGE_MS);
|
|
107
|
+
}
|
|
108
|
+
return Object.freeze({ instance, anthropicModelExhausted, anthropicFamilyQuotaGeneration,
|
|
109
|
+
captureAnthropicFamilyQuotaGeneration,
|
|
110
|
+
observeAnthropicFamilyQuota, anthropicFamilyRejected, claimAnthropicFamilyRevalidation,
|
|
111
|
+
clearAnthropicRequestedFamilyQuota, clearAnthropicFamilyQuota, anthropicFamilyRetryAt, reconcileAnthropicFamilyQuota });
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
export type AnthropicModelQuota = ReturnType<typeof createAnthropicModelQuota>;
|
|
115
|
+
const instances = new Map<AnthropicInstanceId, AnthropicModelQuota>();
|
|
116
|
+
export function anthropicModelQuotaFor(instance: AnthropicInstanceId): AnthropicModelQuota {
|
|
117
|
+
let facade = instances.get(instance);
|
|
118
|
+
if (!facade) { facade = createAnthropicModelQuota(instance); instances.set(instance, facade); }
|
|
119
|
+
return facade;
|
|
120
|
+
}
|
|
121
|
+
export function reconcileAllAnthropicFamilyQuota(context: GenerationContext): number {
|
|
122
|
+
let removed = 0;
|
|
123
|
+
for (const facade of instances.values()) removed += facade.reconcileAnthropicFamilyQuota(context);
|
|
124
|
+
return removed;
|
|
125
|
+
}
|
|
126
|
+
export function clearAllAnthropicFamilyQuota(): void {
|
|
127
|
+
for (const facade of instances.values()) facade.clearAnthropicFamilyQuota();
|
|
128
|
+
}
|
|
129
|
+
export function anthropicModelExhausted(accountId: string, model?: string): boolean {
|
|
130
|
+
return anthropicModelQuotaFor("anthropic").anthropicModelExhausted(accountId, model);
|
|
131
|
+
}
|
|
132
|
+
export function anthropicFamilyQuotaGeneration(id: string): number {
|
|
133
|
+
return anthropicModelQuotaFor("anthropic").anthropicFamilyQuotaGeneration(id);
|
|
37
134
|
}
|
|
38
135
|
export function observeAnthropicFamilyQuota(id: string, windows: ProviderQuotaWindow[], now: number, authoritative = false): void {
|
|
39
|
-
|
|
40
|
-
if (!credential) return;
|
|
41
|
-
if (authoritative || windows.some(window => window.scope === "model")) familyGenerations.set(id, nextFamilyGeneration++);
|
|
42
|
-
if (authoritative) for (const key of spentFamilies.keys()) if (key.startsWith(`${id}\0`)) spentFamilies.delete(key);
|
|
43
|
-
for (const window of windows) {
|
|
44
|
-
if (window.scope !== "model") continue;
|
|
45
|
-
const key = keyFor(id, window.label);
|
|
46
|
-
if (!window.rejected) { spentFamilies.delete(key); continue; }
|
|
47
|
-
spentFamilies.set(key, { generation: credentialGeneration(credential), seenAt: now, resetAt: window.resetAt, busy: false });
|
|
48
|
-
}
|
|
136
|
+
anthropicModelQuotaFor("anthropic").observeAnthropicFamilyQuota(id, windows, now, authoritative);
|
|
49
137
|
}
|
|
50
138
|
export function anthropicFamilyRejected(id: string, model?: string, now = Date.now()): boolean {
|
|
51
|
-
|
|
52
|
-
const entry = family && ownedObservation(id, family);
|
|
53
|
-
if (!entry) return false;
|
|
54
|
-
if (entry.resetAt !== undefined && entry.resetAt <= now) return entry.busy;
|
|
55
|
-
return entry.busy || now - entry.seenAt < ANTHROPIC_PASSIVE_FAMILY_MAX_AGE_MS;
|
|
139
|
+
return anthropicModelQuotaFor("anthropic").anthropicFamilyRejected(id, model, now);
|
|
56
140
|
}
|
|
57
|
-
/** Only stale passive family exclusions require a request-driven single-flight physical send. */
|
|
58
141
|
export function claimAnthropicFamilyRevalidation(id: string, model?: string, now = Date.now()): (() => void) | null {
|
|
59
|
-
|
|
60
|
-
const entry = family && ownedObservation(id, family);
|
|
61
|
-
if (!entry) return () => {};
|
|
62
|
-
if (anthropicFamilyRejected(id, model, now)) return null;
|
|
63
|
-
entry.busy = true;
|
|
64
|
-
return () => { entry.busy = false; };
|
|
142
|
+
return anthropicModelQuotaFor("anthropic").claimAnthropicFamilyRevalidation(id, model, now);
|
|
65
143
|
}
|
|
66
|
-
/** An owned successful send proves admission for its requested family even without headers. */
|
|
67
144
|
export function clearAnthropicRequestedFamilyQuota(id: string, model?: string): void {
|
|
68
|
-
|
|
69
|
-
|
|
145
|
+
anthropicModelQuotaFor("anthropic").clearAnthropicRequestedFamilyQuota(id, model);
|
|
146
|
+
}
|
|
147
|
+
export function clearAnthropicFamilyQuota(): void { anthropicModelQuotaFor("anthropic").clearAnthropicFamilyQuota(); }
|
|
148
|
+
export function anthropicFamilyRetryAt(id: string, model?: string, now = Date.now()): number | undefined {
|
|
149
|
+
return anthropicModelQuotaFor("anthropic").anthropicFamilyRetryAt(id, model, now);
|
|
70
150
|
}
|
|
71
|
-
export function clearAnthropicFamilyQuota(): void { spentFamilies.clear(); familyGenerations.clear(); }
|
|
72
151
|
|
|
73
152
|
export function anthropicModelWeeklyPercent(quota: ProviderQuota | null, model?: string): number | undefined {
|
|
74
153
|
const family = anthropicModelFamily(model);
|
|
75
154
|
return family ? quota?.customWindows?.find(window => window.scope === "model" && window.label === family)?.percent : undefined;
|
|
76
155
|
}
|
|
77
|
-
|
|
78
|
-
export function anthropicFamilyRetryAt(id: string, model?: string, now = Date.now()): number | undefined {
|
|
79
|
-
const family = anthropicModelFamily(model);
|
|
80
|
-
const entry = family && ownedObservation(id, family);
|
|
81
|
-
if (!entry || !anthropicFamilyRejected(id, model, now)) return undefined;
|
|
82
|
-
return entry.busy ? now + 1000 : Math.min(entry.resetAt ?? Infinity, entry.seenAt + ANTHROPIC_PASSIVE_FAMILY_MAX_AGE_MS);
|
|
83
|
-
}
|
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
import type { AnthropicInstanceId } from "../providers/anthropic-instance-id";
|
|
2
|
+
import { resolveAnthropicAccountPoolConfig } from "./anthropic-pool-config";
|
|
1
3
|
import type { AnthropicModelRoute, OcxConfig } from "../types/config";
|
|
2
4
|
|
|
3
5
|
export interface AnthropicRouteDecision {
|
|
@@ -79,12 +81,14 @@ function matches(pattern: string, modelId: string): boolean {
|
|
|
79
81
|
return p === pattern.length;
|
|
80
82
|
}
|
|
81
83
|
|
|
82
|
-
export function
|
|
84
|
+
export function resolveAnthropicModelRouteForInstance(
|
|
85
|
+
instance: AnthropicInstanceId,
|
|
83
86
|
config: OcxConfig,
|
|
84
87
|
modelId: string,
|
|
85
88
|
): { decision: AnthropicRouteDecision | null; error?: string } {
|
|
86
|
-
|
|
87
|
-
|
|
89
|
+
const pool = resolveAnthropicAccountPoolConfig(config, instance);
|
|
90
|
+
if (pool.enabled !== true) return { decision: null };
|
|
91
|
+
const raw = pool.routes;
|
|
88
92
|
if (raw === undefined) return { decision: null };
|
|
89
93
|
const parsed = parseAnthropicModelRoutes(raw);
|
|
90
94
|
if (!parsed.ok) return { decision: null, error: parsed.error };
|
|
@@ -103,3 +107,7 @@ export function routeCandidates(eligible: readonly string[], decision: Anthropic
|
|
|
103
107
|
const routed = decision.accounts.filter(id => available.has(id));
|
|
104
108
|
return routed.length > 0 || !decision.fallback ? routed : [...eligible];
|
|
105
109
|
}
|
|
110
|
+
|
|
111
|
+
export function resolveAnthropicModelRoute(config: OcxConfig, modelId: string): { decision: AnthropicRouteDecision | null; error?: string } {
|
|
112
|
+
return resolveAnthropicModelRouteForInstance("anthropic", config, modelId);
|
|
113
|
+
}
|
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
import { ConfigMutationLockError } from "../config";
|
|
2
|
+
import { deriveOAuthDefaultModel, deriveOAuthProviderConfig } from "../providers/derive";
|
|
3
|
+
import type { AnthropicInstanceId } from "../providers/anthropic-instance-id";
|
|
4
|
+
import { loginAnthropic, refreshAnthropicToken } from "./anthropic";
|
|
5
|
+
import type { OAuthProviderDef } from "./index";
|
|
6
|
+
import {
|
|
7
|
+
clearOAuthRefreshIntent, clearOAuthRefreshIntentIfMatch, markOAuthRefreshIntentCleanupPending,
|
|
8
|
+
OAuthRefreshIntentIOError, type OAuthRefreshIntent, type OAuthRefreshIntentCleanupPending,
|
|
9
|
+
} from "./store";
|
|
10
|
+
|
|
11
|
+
/** Both pools use the same flow; only A may import the local Claude credential. */
|
|
12
|
+
export function anthropicOAuthDefinition(instance: AnthropicInstanceId): OAuthProviderDef {
|
|
13
|
+
const providerConfig = deriveOAuthProviderConfig(instance);
|
|
14
|
+
const defaultModel = deriveOAuthDefaultModel(instance);
|
|
15
|
+
if (!providerConfig || !defaultModel) throw new Error(`OAuth provider missing from registry: ${instance}`);
|
|
16
|
+
return {
|
|
17
|
+
login: (ctrl, opts) => loginAnthropic(ctrl, {
|
|
18
|
+
instance, importLocal: instance === "anthropic2" || opts?.forceLogin ? "off" : "fallback",
|
|
19
|
+
}),
|
|
20
|
+
refresh: refreshAnthropicToken,
|
|
21
|
+
providerConfig,
|
|
22
|
+
defaultModel,
|
|
23
|
+
// Subscription OAuth must never generate unattended traffic by default.
|
|
24
|
+
defaultRefreshPolicy: "disabled",
|
|
25
|
+
};
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
export { AnthropicInstanceCollisionError, assertAnthropicInstanceLoginConfig } from "./store-anthropic-instance";
|
|
29
|
+
|
|
30
|
+
/** Cleanup is secondary once a rotated credential has become durable. */
|
|
31
|
+
export function clearAnthropicRefreshIntentBestEffort(provider: string, accountId: string, expected: OAuthRefreshIntent): boolean {
|
|
32
|
+
try {
|
|
33
|
+
return expected.attemptId
|
|
34
|
+
? clearOAuthRefreshIntentIfMatch(provider, accountId, expected)
|
|
35
|
+
: clearOAuthRefreshIntent(provider, accountId, expected.generation);
|
|
36
|
+
} catch {
|
|
37
|
+
console.warn("[opencodex] Anthropic refresh intent cleanup failed; preserving the durable replay guard.");
|
|
38
|
+
return false;
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
const ANTHROPIC_INTENT_MARK_RETRY_DELAYS_MS = [10, 25, 50] as const;
|
|
43
|
+
function isConfigMutationLockContention(error: unknown): boolean {
|
|
44
|
+
if (!(error instanceof ConfigMutationLockError)) return false;
|
|
45
|
+
const cause = error.cause;
|
|
46
|
+
const code = cause && typeof cause === "object" && "code" in cause
|
|
47
|
+
? String((cause as { code?: unknown }).code) : "";
|
|
48
|
+
return code === "SQLITE_BUSY" || code === "SQLITE_LOCKED";
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
export async function clearAnthropicRefreshIntentForKnownFailure(
|
|
52
|
+
provider: string, accountId: string, expected: OAuthRefreshIntent,
|
|
53
|
+
cleanupPending: OAuthRefreshIntentCleanupPending, refreshError: unknown,
|
|
54
|
+
): Promise<boolean> {
|
|
55
|
+
let marked: OAuthRefreshIntent | undefined;
|
|
56
|
+
for (let attempt = 0; attempt <= ANTHROPIC_INTENT_MARK_RETRY_DELAYS_MS.length; attempt += 1) {
|
|
57
|
+
try {
|
|
58
|
+
marked = markOAuthRefreshIntentCleanupPending(provider, accountId, expected, cleanupPending);
|
|
59
|
+
break;
|
|
60
|
+
} catch (cause) {
|
|
61
|
+
const retryDelay = ANTHROPIC_INTENT_MARK_RETRY_DELAYS_MS[attempt];
|
|
62
|
+
if (!isConfigMutationLockContention(cause) || retryDelay === undefined) {
|
|
63
|
+
throw new OAuthRefreshIntentIOError("mark-cleanup-pending", cause, refreshError);
|
|
64
|
+
}
|
|
65
|
+
// Settlement retains the account lock after a definitive provider answer.
|
|
66
|
+
await Bun.sleep(retryDelay);
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
if (!marked) throw new OAuthRefreshIntentIOError("mark-cleanup-pending",
|
|
70
|
+
new Error("Anthropic refresh intent changed before safe cleanup"), refreshError);
|
|
71
|
+
let cleared: boolean;
|
|
72
|
+
try {
|
|
73
|
+
cleared = clearOAuthRefreshIntentIfMatch(provider, accountId, marked);
|
|
74
|
+
} catch {
|
|
75
|
+
console.warn("[opencodex] Anthropic refresh intent cleanup failed; retry-safe cleanup remains pending.");
|
|
76
|
+
return false;
|
|
77
|
+
}
|
|
78
|
+
if (!cleared) throw new OAuthRefreshIntentIOError("clear-cleanup-pending",
|
|
79
|
+
new Error("Anthropic refresh intent changed during safe cleanup"), refreshError);
|
|
80
|
+
return true;
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
export function resumeAnthropicRefreshIntentCleanup(provider: string, accountId: string, pendingIntent: OAuthRefreshIntent): void {
|
|
84
|
+
let cleared: boolean;
|
|
85
|
+
try {
|
|
86
|
+
cleared = clearOAuthRefreshIntentIfMatch(provider, accountId, pendingIntent);
|
|
87
|
+
} catch (cause) {
|
|
88
|
+
throw new OAuthRefreshIntentIOError("resume-cleanup", cause);
|
|
89
|
+
}
|
|
90
|
+
if (!cleared) throw new OAuthRefreshIntentIOError("resume-cleanup",
|
|
91
|
+
new Error("Pending Anthropic refresh intent changed before cleanup"));
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
export function clearObservedAnthropicRefreshIntent(provider: string, accountId: string, pendingIntent: OAuthRefreshIntent): boolean {
|
|
95
|
+
return pendingIntent.attemptId
|
|
96
|
+
? clearOAuthRefreshIntentIfMatch(provider, accountId, pendingIntent)
|
|
97
|
+
: clearOAuthRefreshIntent(provider, accountId, pendingIntent.generation);
|
|
98
|
+
}
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Per-instance Anthropic pool settings. A reads only `config.anthropicAccountPool`; B reads only
|
|
3
|
+
* `config.providers.anthropic2.anthropicAccountPool`. A missing B block means product defaults, never
|
|
4
|
+
* A's settings. Pure: no store, network or state access.
|
|
5
|
+
*/
|
|
6
|
+
import type { OcxConfig } from "../types";
|
|
7
|
+
import type { AnthropicAccountPoolConfig } from "../types/anthropic-account-pool";
|
|
8
|
+
import type { AnthropicInstanceId } from "../providers/anthropic-instance-id";
|
|
9
|
+
|
|
10
|
+
/** The raw value at the instance's own location, preserving absent/invalid distinctions for callers that need them. */
|
|
11
|
+
export function rawAnthropicAccountPool(config: Pick<OcxConfig, "anthropicAccountPool" | "providers">, instance: AnthropicInstanceId): unknown {
|
|
12
|
+
if (instance === "anthropic") return config.anthropicAccountPool;
|
|
13
|
+
const row: unknown = config.providers?.[instance];
|
|
14
|
+
return row && typeof row === "object" ? (row as { anthropicAccountPool?: unknown }).anthropicAccountPool : undefined;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
export function resolveAnthropicAccountPoolConfig(
|
|
18
|
+
config: Pick<OcxConfig, "anthropicAccountPool" | "providers">,
|
|
19
|
+
instance: AnthropicInstanceId,
|
|
20
|
+
): AnthropicAccountPoolConfig {
|
|
21
|
+
const raw = rawAnthropicAccountPool(config, instance);
|
|
22
|
+
return raw && typeof raw === "object" && !Array.isArray(raw) ? raw as AnthropicAccountPoolConfig : {};
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
export function isAnthropicPoolEnabledFor(
|
|
26
|
+
config: Pick<OcxConfig, "anthropicAccountPool" | "providers">,
|
|
27
|
+
instance: AnthropicInstanceId,
|
|
28
|
+
): boolean {
|
|
29
|
+
return resolveAnthropicAccountPoolConfig(config, instance).enabled === true;
|
|
30
|
+
}
|
|
@@ -1,4 +1,7 @@
|
|
|
1
1
|
/** Response-header policy for managed Anthropic sends; no account or request payload data. */
|
|
2
|
+
import type { AnthropicInstanceId } from "../providers/anthropic-instance-id";
|
|
3
|
+
import type { GenerationContext } from "../lib/state-store-sweeper";
|
|
4
|
+
import { credentialGeneration, getAccountCredential } from "./store";
|
|
2
5
|
export type AnthropicRateLimitKind = "shared-quota" | "family-quota" | "transient-rate" | "request-scoped-unknown";
|
|
3
6
|
export type RateLimitHeaders = Pick<Headers, "get">;
|
|
4
7
|
export const ANTHROPIC_SHORT_RETRY_MS = 100;
|
|
@@ -22,16 +25,68 @@ export function classifyAnthropic429(headers: RateLimitHeaders, now = Date.now()
|
|
|
22
25
|
return "request-scoped-unknown";
|
|
23
26
|
}
|
|
24
27
|
|
|
25
|
-
|
|
28
|
+
function createAnthropicRatePolicy(instance: AnthropicInstanceId) {
|
|
29
|
+
let pauses: Map<string, { until: number; generation: string }> | undefined;
|
|
30
|
+
function pauseAnthropicRateAdmission(accountId: string, until: number): void {
|
|
31
|
+
const credential = getAccountCredential(instance, accountId);
|
|
32
|
+
if (!credential) return;
|
|
33
|
+
const now = Date.now();
|
|
34
|
+
const generation = credentialGeneration(credential);
|
|
35
|
+
const rows = pauses ??= new Map();
|
|
36
|
+
for (const [id, pause] of rows) if (pause.until <= now) rows.delete(id);
|
|
37
|
+
const previous = rows.get(accountId);
|
|
38
|
+
rows.set(accountId, { until: Math.max(previous?.generation === generation ? previous.until : 0, until), generation });
|
|
39
|
+
}
|
|
40
|
+
function anthropicRatePauseUntil(accountId: string, now = Date.now()): number | undefined {
|
|
41
|
+
const pause = pauses?.get(accountId);
|
|
42
|
+
if (!pause) return undefined;
|
|
43
|
+
const credential = getAccountCredential(instance, accountId);
|
|
44
|
+
if (pause.until <= now || !credential || credentialGeneration(credential) !== pause.generation) {
|
|
45
|
+
pauses!.delete(accountId); return undefined;
|
|
46
|
+
}
|
|
47
|
+
return pause.until;
|
|
48
|
+
}
|
|
49
|
+
function clearAnthropicRatePauses(): void { pauses = undefined; }
|
|
50
|
+
function reconcileAnthropicRatePauses(context: GenerationContext): number {
|
|
51
|
+
let removed = 0;
|
|
52
|
+
for (const id of pauses?.keys() ?? []) {
|
|
53
|
+
if (context.oauthAccountKeys.has(`${instance}\0${id}`)) continue;
|
|
54
|
+
pauses!.delete(id); removed++;
|
|
55
|
+
}
|
|
56
|
+
return removed;
|
|
57
|
+
}
|
|
58
|
+
function sweepExpiredAnthropicRatePauses(now = Date.now()): number {
|
|
59
|
+
let removed = 0;
|
|
60
|
+
for (const [id, pause] of pauses ?? []) if (pause.until <= now) { pauses!.delete(id); removed++; }
|
|
61
|
+
return removed;
|
|
62
|
+
}
|
|
63
|
+
return Object.freeze({ instance, pauseAnthropicRateAdmission, anthropicRatePauseUntil,
|
|
64
|
+
clearAnthropicRatePauses, reconcileAnthropicRatePauses, sweepExpiredAnthropicRatePauses });
|
|
65
|
+
}
|
|
66
|
+
export type AnthropicRatePolicy = ReturnType<typeof createAnthropicRatePolicy>;
|
|
67
|
+
const instances = new Map<AnthropicInstanceId, AnthropicRatePolicy>();
|
|
68
|
+
export function anthropicRatePolicyFor(instance: AnthropicInstanceId): AnthropicRatePolicy {
|
|
69
|
+
let facade = instances.get(instance);
|
|
70
|
+
if (!facade) { facade = createAnthropicRatePolicy(instance); instances.set(instance, facade); }
|
|
71
|
+
return facade;
|
|
72
|
+
}
|
|
73
|
+
export function reconcileAllAnthropicRatePauses(context: GenerationContext): number {
|
|
74
|
+
let removed = 0;
|
|
75
|
+
for (const facade of instances.values()) removed += facade.reconcileAnthropicRatePauses(context);
|
|
76
|
+
return removed;
|
|
77
|
+
}
|
|
78
|
+
export function sweepExpiredAllAnthropicRatePauses(now = Date.now()): number {
|
|
79
|
+
let removed = 0;
|
|
80
|
+
for (const facade of instances.values()) removed += facade.sweepExpiredAnthropicRatePauses(now);
|
|
81
|
+
return removed;
|
|
82
|
+
}
|
|
83
|
+
export function clearAllAnthropicRatePauses(): void {
|
|
84
|
+
for (const facade of instances.values()) facade.clearAnthropicRatePauses();
|
|
85
|
+
}
|
|
26
86
|
export function pauseAnthropicRateAdmission(accountId: string, until: number): void {
|
|
27
|
-
|
|
28
|
-
for (const [id, deadline] of pauses) if (deadline <= now) pauses.delete(id);
|
|
29
|
-
pauses.set(accountId, Math.max(pauses.get(accountId) ?? 0, until));
|
|
87
|
+
anthropicRatePolicyFor("anthropic").pauseAnthropicRateAdmission(accountId, until);
|
|
30
88
|
}
|
|
31
89
|
export function anthropicRatePauseUntil(accountId: string, now = Date.now()): number | undefined {
|
|
32
|
-
|
|
33
|
-
if (until === undefined) return undefined;
|
|
34
|
-
if (until <= now) { pauses.delete(accountId); return undefined; }
|
|
35
|
-
return until;
|
|
90
|
+
return anthropicRatePolicyFor("anthropic").anthropicRatePauseUntil(accountId, now);
|
|
36
91
|
}
|
|
37
|
-
export function clearAnthropicRatePauses(): void {
|
|
92
|
+
export function clearAnthropicRatePauses(): void { anthropicRatePolicyFor("anthropic").clearAnthropicRatePauses(); }
|