@askalf/dario 6.0.29 → 6.0.30

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/dist/pool.d.ts CHANGED
@@ -118,6 +118,41 @@ export type AccountIneligibility = 'rate-limited' | 'token-expired' | 'auth-cool
118
118
  * need to name the failure, and a boolean forces each of them to re-derive it
119
119
  * and drift again.
120
120
  */
121
+ /**
122
+ * Has the window that produced a `rejected` reading rolled over?
123
+ *
124
+ * A rejection is a verdict with an expiry date: `anthropic-ratelimit-unified-reset`
125
+ * names the moment the window that refused the request resets. Past it, the old
126
+ * reading says nothing about the account any more.
127
+ *
128
+ * This matters because a rejected account is filtered out of `select()`, so it
129
+ * is sent no further requests, so `updateRateLimits` never runs for it and its
130
+ * snapshot never refreshes. The only routes back into rotation were the
131
+ * all-exhausted fallback in `select()` and a proxy restart. Observed on the
132
+ * fleet box (2026-09-06): one seat sat parked on a 106% five-hour reading while
133
+ * a second subscription carried every request, and it would have stayed parked
134
+ * past its own reset for as long as the other seat held out.
135
+ *
136
+ * `reset` is epoch SECONDS — the header's own unit, the same one `formatReset`
137
+ * scales — so it is converted here. A snapshot with no reset (0) keeps its
138
+ * rejection: with no stated rollover there is nothing to expire, and guessing
139
+ * would push a genuinely throttled account back into rotation.
140
+ */
141
+ export declare function rateLimitWindowPassed(rl: RateLimitSnapshot, now?: number): boolean;
142
+ /**
143
+ * The status string the operator-facing surfaces report for one account —
144
+ * `GET /accounts` and `GET /admin/accounts`, which must agree with each other
145
+ * and with what routing actually does.
146
+ *
147
+ * Auth cool-down outranks the rate-limit reading: a 401 streak is both the more
148
+ * urgent fact and the one the rate-limit headers cannot describe, since 401
149
+ * responses carry none. An expired rejection degrades to `unknown` rather than
150
+ * `allowed` — the window rolled over, but nothing has measured the account
151
+ * since, and reporting `allowed` would assert a serving capacity no request has
152
+ * demonstrated. `unknown` is what a never-used account already reports, which is
153
+ * exactly the state this is: no current observation.
154
+ */
155
+ export declare function reportedAccountStatus(account: PoolAccount, now?: number): string;
121
156
  export declare function accountIneligibility(account: PoolAccount, now?: number): AccountIneligibility | null;
122
157
  /** Boolean form of `accountIneligibility` — the router's eligibility filter. */
123
158
  export declare function isAccountEligible(account: PoolAccount, now?: number): boolean;
package/dist/pool.js CHANGED
@@ -100,8 +100,54 @@ export const TOKEN_EXPIRY_MARGIN_MS = 30_000;
100
100
  * need to name the failure, and a boolean forces each of them to re-derive it
101
101
  * and drift again.
102
102
  */
103
+ /**
104
+ * Has the window that produced a `rejected` reading rolled over?
105
+ *
106
+ * A rejection is a verdict with an expiry date: `anthropic-ratelimit-unified-reset`
107
+ * names the moment the window that refused the request resets. Past it, the old
108
+ * reading says nothing about the account any more.
109
+ *
110
+ * This matters because a rejected account is filtered out of `select()`, so it
111
+ * is sent no further requests, so `updateRateLimits` never runs for it and its
112
+ * snapshot never refreshes. The only routes back into rotation were the
113
+ * all-exhausted fallback in `select()` and a proxy restart. Observed on the
114
+ * fleet box (2026-09-06): one seat sat parked on a 106% five-hour reading while
115
+ * a second subscription carried every request, and it would have stayed parked
116
+ * past its own reset for as long as the other seat held out.
117
+ *
118
+ * `reset` is epoch SECONDS — the header's own unit, the same one `formatReset`
119
+ * scales — so it is converted here. A snapshot with no reset (0) keeps its
120
+ * rejection: with no stated rollover there is nothing to expire, and guessing
121
+ * would push a genuinely throttled account back into rotation.
122
+ */
123
+ export function rateLimitWindowPassed(rl, now = Date.now()) {
124
+ return rl.reset > 0 && rl.reset * 1000 <= now;
125
+ }
126
+ /**
127
+ * The status string the operator-facing surfaces report for one account —
128
+ * `GET /accounts` and `GET /admin/accounts`, which must agree with each other
129
+ * and with what routing actually does.
130
+ *
131
+ * Auth cool-down outranks the rate-limit reading: a 401 streak is both the more
132
+ * urgent fact and the one the rate-limit headers cannot describe, since 401
133
+ * responses carry none. An expired rejection degrades to `unknown` rather than
134
+ * `allowed` — the window rolled over, but nothing has measured the account
135
+ * since, and reporting `allowed` would assert a serving capacity no request has
136
+ * demonstrated. `unknown` is what a never-used account already reports, which is
137
+ * exactly the state this is: no current observation.
138
+ */
139
+ export function reportedAccountStatus(account, now = Date.now()) {
140
+ if (isInAuthCooldown(account, now))
141
+ return 'auth-cooldown';
142
+ if (account.rateLimit.status === 'rejected' && rateLimitWindowPassed(account.rateLimit, now))
143
+ return 'unknown';
144
+ return account.rateLimit.status;
145
+ }
103
146
  export function accountIneligibility(account, now = Date.now()) {
104
- if (account.rateLimit.status === 'rejected')
147
+ // A rejection outlives its own window unless it is allowed to expire:
148
+ // nothing refreshes a parked account's snapshot, because being parked is
149
+ // what stops it being sent requests.
150
+ if (account.rateLimit.status === 'rejected' && !rateLimitWindowPassed(account.rateLimit, now))
105
151
  return 'rate-limited';
106
152
  if (account.expiresAt <= now + TOKEN_EXPIRY_MARGIN_MS)
107
153
  return 'token-expired';
package/dist/proxy.js CHANGED
@@ -12,7 +12,7 @@ import { darioVersion } from './version.js';
12
12
  import { buildCCRequest, applyCcPromptCaching, isGenuineCCClient, parseEffortSuffix, reverseMapResponse, createStreamingReverseMapper, orderHeadersForOutbound, overlayTemplateHeaderValues, forwardClientCCIdentityHeaders, isMcpToolName, CC_TEMPLATE, effectiveCacheControl, withForced1hBeta } from './cc-template.js';
13
13
  import { stampCch, hasCchSeed } from './cch.js';
14
14
  import { describeTemplate, detectDrift, checkCCCompat, probeInstalledCCVersion } from './live-fingerprint.js';
15
- import { AccountPool, computeStickyKey, parseRateLimits, modelFamily, isInAuthCooldown, authCooldownMs, accountIneligibility, reconcilePoolAccounts, resolvePoolStrategy, utilFreshness } from './pool.js';
15
+ import { AccountPool, computeStickyKey, parseRateLimits, modelFamily, isInAuthCooldown, authCooldownMs, accountIneligibility, reportedAccountStatus, reconcilePoolAccounts, resolvePoolStrategy, utilFreshness } from './pool.js';
16
16
  import { Analytics, billingBucketFromClaim, formatUsageLogLine, SUBSCRIPTION_CLAIMS, CODEX_CLAIM } from './analytics.js';
17
17
  import { OverageGuard, buildHaltErrorBody } from './overage-guard.js';
18
18
  import { notify as osNotify } from './notify.js';
@@ -2101,7 +2101,7 @@ export async function startProxy(opts = {}) {
2101
2101
  // must not be the one place a stale reading still looks current.
2102
2102
  ...utilFreshness(a.rateLimit, snapNow),
2103
2103
  claim: a.rateLimit.claim,
2104
- status: isInAuthCooldown(a, snapNow) ? 'auth-cooldown' : a.rateLimit.status,
2104
+ status: reportedAccountStatus(a, snapNow),
2105
2105
  requestCount: a.requestCount,
2106
2106
  // Raw streak, not just the cooldown boolean: a single 401 also
2107
2107
  // shows `auth-cooldown` for 60s, indistinguishable from a
@@ -2200,7 +2200,7 @@ export async function startProxy(opts = {}) {
2200
2200
  util7d: a.rateLimit.util7d,
2201
2201
  ...utilFreshness(a.rateLimit, now),
2202
2202
  claim: a.rateLimit.claim,
2203
- status: inCooldown ? 'auth-cooldown' : a.rateLimit.status,
2203
+ status: reportedAccountStatus(a, now),
2204
2204
  requestCount: a.requestCount,
2205
2205
  expiresInMs: Math.max(0, a.expiresAt - now),
2206
2206
  // Refresh-token grant age (refresh-grant.ts): the wall a token
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@askalf/dario",
3
- "version": "6.0.29",
3
+ "version": "6.0.30",
4
4
  "description": "Use your Claude Pro/Max subscription in any tool — Cursor, Cline, Aider, the Agent SDK, your scripts — at subscription pricing, not per-token API bills. One local Anthropic + OpenAI-compatible endpoint.",
5
5
  "type": "module",
6
6
  "bin": {