@askalf/dario 6.0.45 → 6.0.46
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 +2 -2
- package/dist/pool.d.ts +41 -1
- package/dist/pool.js +61 -4
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -27,7 +27,7 @@
|
|
|
27
27
|
|
|
28
28
|
<p><strong>One local endpoint. Every AI tool you own. The subscriptions you already pay for.</strong></p>
|
|
29
29
|
|
|
30
|
-
<sub><code>npm i -g @askalf/dario</code> · <strong>0</strong> runtime deps · <a href="https://www.npmjs.com/package/@askalf/dario">SLSA-attested</a> every release · nothing phones home · ~
|
|
30
|
+
<sub><code>npm i -g @askalf/dario</code> · <strong>0</strong> runtime deps · <a href="https://www.npmjs.com/package/@askalf/dario">SLSA-attested</a> every release · nothing phones home · ~33k lines you can read in a weekend · independent, unofficial, third-party (<a href="DISCLAIMER.md">DISCLAIMER.md</a>)</sub>
|
|
31
31
|
|
|
32
32
|
<sub><a href="#start-in-60-seconds">Start</a> · <a href="#point-your-tools-at-it">Your tools</a> · <a href="#what-it-does-with-a-request">Routing</a> · <a href="#two-plans-one-endpoint">Two plans</a> · <a href="#many-seats-one-endpoint">Pool</a> · <a href="#it-tracks-a-moving-target">Drift</a> · <a href="#trust--transparency">Trust</a> · <a href="#will-my-account-get-suspended">Risk</a> · <a href="#commands">Commands</a> · <a href="#faq">FAQ</a> · <a href="docs/returning.md">Coming back after a while?</a></sub>
|
|
33
33
|
|
|
@@ -405,7 +405,7 @@ The split isn't live, but it was announced once on short notice and could return
|
|
|
405
405
|
|
|
406
406
|
| Signal | Status |
|
|
407
407
|
|---|---|
|
|
408
|
-
| Source | **~
|
|
408
|
+
| Source | **~33k** lines of TypeScript across **68** files, auditable in a weekend. One credential path since v5: the pool. |
|
|
409
409
|
| Dependencies | **0 runtime.** Verify: `npm ls --production` |
|
|
410
410
|
| Provenance | Every release [SLSA-attested](https://www.npmjs.com/package/@askalf/dario) via GitHub Actions + Sigstore, published with OIDC trusted publishing — no long-lived npm token exists to leak |
|
|
411
411
|
| Scanning | [CodeQL](https://github.com/askalf/dario/actions/workflows/codeql.yml) on every push and weekly · [ClusterFuzzLite](./.github/workflows/cflite.yml) fuzzes the SSE translator and rejection parsers weekly · [OpenSSF Scorecard](https://scorecard.dev/viewer/?uri=github.com/askalf/dario) and [Best Practices](https://www.bestpractices.dev/projects/13638) badges above are live |
|
package/dist/pool.d.ts
CHANGED
|
@@ -431,7 +431,47 @@ export declare function modelFamily(modelId: string | null | undefined): string
|
|
|
431
431
|
* `WIRE_BUCKET_BINDINGS` says it binds the family (`7d_oi` → fable, dario#1262),
|
|
432
432
|
* or when this account's responses have shown it does (`boundBuckets`).
|
|
433
433
|
*/
|
|
434
|
-
export declare function computeHeadroom(snapshot: RateLimitSnapshot, family?: string | null): number;
|
|
434
|
+
export declare function computeHeadroom(snapshot: RateLimitSnapshot, family?: string | null, now?: number): number;
|
|
435
|
+
/**
|
|
436
|
+
* Drop a utilization reading whose own window has already rolled over
|
|
437
|
+
* (dario#1244 follow-up, 2026-09-09).
|
|
438
|
+
*
|
|
439
|
+
* `rateLimitWindowPassed` has expired *rejections* against
|
|
440
|
+
* `anthropic-ratelimit-unified-reset` since #1232. The utilization on the same
|
|
441
|
+
* snapshot never got the same treatment, and that asymmetry is a routing bug —
|
|
442
|
+
* not the reporting one `utilFreshness` describes, whose "the pool returns
|
|
443
|
+
* parked accounts to service on its own and the value corrects itself" only
|
|
444
|
+
* holds for seats that were parked.
|
|
445
|
+
*
|
|
446
|
+
* A seat that never 429'd but whose last response read `5h 99%` keeps that
|
|
447
|
+
* reading forever: `computeHeadroom` returns 0.01, under `POOL_HEADROOM_FLOOR`,
|
|
448
|
+
* so the selector skips it, `pickFillFirst` won't take it, sticky bindings
|
|
449
|
+
* rebind away from it, and `drainQueue`'s probe loop *breaks* on it. Nothing
|
|
450
|
+
* sends it a request, so `updateRateLimits` never runs, so the reading never
|
|
451
|
+
* refreshes. Unlike a rejection there is no all-exhausted fallback to rescue
|
|
452
|
+
* it, because it was never ineligible — only permanently unattractive.
|
|
453
|
+
*
|
|
454
|
+
* Reported on a nine-seat pool (dario#1244, 2026-09-09): every seat read 0.98
|
|
455
|
+
* to 1.03, so every seat sat at or under the floor and `waitForAccount` queued
|
|
456
|
+
* until it timed out — a whole pool reporting exhausted while two of its seats
|
|
457
|
+
* carried `reset_in_ms: 0` and were provably free, one having served exactly
|
|
458
|
+
* ONE request 3.7 hours earlier. `rejected_count` was 0 on all of them, which
|
|
459
|
+
* is why none of the rejection-side fixes (#1232, #1254, the 6.0.39 cool-down)
|
|
460
|
+
* reached this: every one of them requires a 429 to have happened.
|
|
461
|
+
*
|
|
462
|
+
* Only the bucket the reading's own `claim` names is dropped. `reset` states
|
|
463
|
+
* the rollover of the representative window and nothing else, so a five-hour
|
|
464
|
+
* rollover must not clear a seven-day reading — that would route traffic onto
|
|
465
|
+
* a seat whose weekly quota really is spent. A claim naming neither window
|
|
466
|
+
* (`unknown`) is left untouched: nothing identifies which bucket expired, and
|
|
467
|
+
* guessing is how a genuinely throttled seat gets pushed back into rotation.
|
|
468
|
+
*
|
|
469
|
+
* Zeroing rather than flagging is deliberate: the seat becomes an attractive
|
|
470
|
+
* candidate, is picked once, and its own response refreshes the reading. That
|
|
471
|
+
* is the same "asking is the only way back" rule `isParkedInLiveWindow`
|
|
472
|
+
* documents, and it costs no synthetic probe.
|
|
473
|
+
*/
|
|
474
|
+
export declare function expireElapsedWindow(snapshot: RateLimitSnapshot, now?: number): RateLimitSnapshot;
|
|
435
475
|
/** Every bucket name that binds `family` for this reading — by name, by seed, or as learned. */
|
|
436
476
|
export declare function bucketsBindingFamily(snapshot: RateLimitSnapshot, family: string): string[];
|
|
437
477
|
export declare class AccountPool {
|
package/dist/pool.js
CHANGED
|
@@ -530,17 +530,74 @@ export function modelFamily(modelId) {
|
|
|
530
530
|
* `WIRE_BUCKET_BINDINGS` says it binds the family (`7d_oi` → fable, dario#1262),
|
|
531
531
|
* or when this account's responses have shown it does (`boundBuckets`).
|
|
532
532
|
*/
|
|
533
|
-
export function computeHeadroom(snapshot, family) {
|
|
534
|
-
const
|
|
533
|
+
export function computeHeadroom(snapshot, family, now = Date.now()) {
|
|
534
|
+
const rl = expireElapsedWindow(snapshot, now);
|
|
535
|
+
const utils = [rl.util5h, rl.util7d];
|
|
535
536
|
if (family) {
|
|
536
|
-
for (const bucket of bucketsBindingFamily(
|
|
537
|
-
const util =
|
|
537
|
+
for (const bucket of bucketsBindingFamily(rl, family)) {
|
|
538
|
+
const util = rl.perModel7d[bucket];
|
|
538
539
|
if (util !== undefined)
|
|
539
540
|
utils.push(util);
|
|
540
541
|
}
|
|
541
542
|
}
|
|
542
543
|
return 1 - Math.max(...utils);
|
|
543
544
|
}
|
|
545
|
+
/**
|
|
546
|
+
* Drop a utilization reading whose own window has already rolled over
|
|
547
|
+
* (dario#1244 follow-up, 2026-09-09).
|
|
548
|
+
*
|
|
549
|
+
* `rateLimitWindowPassed` has expired *rejections* against
|
|
550
|
+
* `anthropic-ratelimit-unified-reset` since #1232. The utilization on the same
|
|
551
|
+
* snapshot never got the same treatment, and that asymmetry is a routing bug —
|
|
552
|
+
* not the reporting one `utilFreshness` describes, whose "the pool returns
|
|
553
|
+
* parked accounts to service on its own and the value corrects itself" only
|
|
554
|
+
* holds for seats that were parked.
|
|
555
|
+
*
|
|
556
|
+
* A seat that never 429'd but whose last response read `5h 99%` keeps that
|
|
557
|
+
* reading forever: `computeHeadroom` returns 0.01, under `POOL_HEADROOM_FLOOR`,
|
|
558
|
+
* so the selector skips it, `pickFillFirst` won't take it, sticky bindings
|
|
559
|
+
* rebind away from it, and `drainQueue`'s probe loop *breaks* on it. Nothing
|
|
560
|
+
* sends it a request, so `updateRateLimits` never runs, so the reading never
|
|
561
|
+
* refreshes. Unlike a rejection there is no all-exhausted fallback to rescue
|
|
562
|
+
* it, because it was never ineligible — only permanently unattractive.
|
|
563
|
+
*
|
|
564
|
+
* Reported on a nine-seat pool (dario#1244, 2026-09-09): every seat read 0.98
|
|
565
|
+
* to 1.03, so every seat sat at or under the floor and `waitForAccount` queued
|
|
566
|
+
* until it timed out — a whole pool reporting exhausted while two of its seats
|
|
567
|
+
* carried `reset_in_ms: 0` and were provably free, one having served exactly
|
|
568
|
+
* ONE request 3.7 hours earlier. `rejected_count` was 0 on all of them, which
|
|
569
|
+
* is why none of the rejection-side fixes (#1232, #1254, the 6.0.39 cool-down)
|
|
570
|
+
* reached this: every one of them requires a 429 to have happened.
|
|
571
|
+
*
|
|
572
|
+
* Only the bucket the reading's own `claim` names is dropped. `reset` states
|
|
573
|
+
* the rollover of the representative window and nothing else, so a five-hour
|
|
574
|
+
* rollover must not clear a seven-day reading — that would route traffic onto
|
|
575
|
+
* a seat whose weekly quota really is spent. A claim naming neither window
|
|
576
|
+
* (`unknown`) is left untouched: nothing identifies which bucket expired, and
|
|
577
|
+
* guessing is how a genuinely throttled seat gets pushed back into rotation.
|
|
578
|
+
*
|
|
579
|
+
* Zeroing rather than flagging is deliberate: the seat becomes an attractive
|
|
580
|
+
* candidate, is picked once, and its own response refreshes the reading. That
|
|
581
|
+
* is the same "asking is the only way back" rule `isParkedInLiveWindow`
|
|
582
|
+
* documents, and it costs no synthetic probe.
|
|
583
|
+
*/
|
|
584
|
+
export function expireElapsedWindow(snapshot, now = Date.now()) {
|
|
585
|
+
// Deliberately NOT `rateLimitWindowPassed`: that predicate answers "is this
|
|
586
|
+
// rejection over", and for a 429 that named no exhausted window it answers
|
|
587
|
+
// yes as soon as the cool-down elapses, whatever the window says. A seat
|
|
588
|
+
// 429'd at `5h 99%` (under the 1.0 threshold, so `exhausted: false`) would
|
|
589
|
+
// then have a true 99% reading zeroed a minute later and take traffic it
|
|
590
|
+
// cannot serve. Only the stated rollover retires a reading.
|
|
591
|
+
if (!(snapshot.reset > 0 && snapshot.reset * 1000 <= now))
|
|
592
|
+
return snapshot;
|
|
593
|
+
if (snapshot.claim === 'five_hour')
|
|
594
|
+
return { ...snapshot, util5h: 0 };
|
|
595
|
+
// `seven_day`, plus the overage variants that carry the same weekly window
|
|
596
|
+
// (`seven_day_overage_included`, see the overage note above).
|
|
597
|
+
if (snapshot.claim.startsWith('seven_day'))
|
|
598
|
+
return { ...snapshot, util7d: 0 };
|
|
599
|
+
return snapshot;
|
|
600
|
+
}
|
|
544
601
|
/** Every bucket name that binds `family` for this reading — by name, by seed, or as learned. */
|
|
545
602
|
export function bucketsBindingFamily(snapshot, family) {
|
|
546
603
|
const out = [family];
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@askalf/dario",
|
|
3
|
-
"version": "6.0.
|
|
3
|
+
"version": "6.0.46",
|
|
4
4
|
"description": "Use your Claude and ChatGPT subscriptions in Cursor, Cline, Aider, Claude Code and the Agent SDK — at subscription pricing, not per-token API bills. One local Anthropic + OpenAI-compatible endpoint: either plan answers either wire shape, with automatic failover when one hits its limit.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|