@askalf/dario 6.14.0 → 6.14.1

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.
@@ -25,6 +25,12 @@ export declare function parseFamilyFallback(spec: string | Record<string, unknow
25
25
  *
26
26
  * A `[1m]` long-context tag on the request is carried onto the target when the
27
27
  * target has none, so the switch does not shrink the context window asked for.
28
+ *
29
+ * A model name that is not printable ASCII (at most 256 characters, no spaces
30
+ * or control characters) is left alone: no real model is named so, and the
31
+ * name the client sent is echoed in the `x-dario-family-fallback` header and
32
+ * the log, where a line break would fail the header (setHeader throws, the
33
+ * request errors) or forge a log line.
28
34
  */
29
35
  export declare function familyFallbackModel(map: FamilyFallbackMap, model: string, pool: {
30
36
  parked: (family: string) => boolean;
@@ -64,10 +64,18 @@ export function parseFamilyFallback(spec) {
64
64
  *
65
65
  * A `[1m]` long-context tag on the request is carried onto the target when the
66
66
  * target has none, so the switch does not shrink the context window asked for.
67
+ *
68
+ * A model name that is not printable ASCII (at most 256 characters, no spaces
69
+ * or control characters) is left alone: no real model is named so, and the
70
+ * name the client sent is echoed in the `x-dario-family-fallback` header and
71
+ * the log, where a line break would fail the header (setHeader throws, the
72
+ * request errors) or forge a log line.
67
73
  */
68
74
  export function familyFallbackModel(map, model, pool) {
69
75
  if (map.size === 0)
70
76
  return null;
77
+ if (!/^[\x21-\x7e]{1,256}$/.test(model))
78
+ return null;
71
79
  const family = modelFamily(model);
72
80
  if (!family)
73
81
  return null;
package/dist/pool.js CHANGED
@@ -341,6 +341,14 @@ export function withParkedBuckets(prev, next, now = Date.now()) {
341
341
  */
342
342
  export function isParkedInLiveWindow(account, now = Date.now(), family) {
343
343
  const rl = account.rateLimit;
344
+ // A bucket the seat was parked on, carried past a reading that did not
345
+ // measure it (an Opus 200, or an Opus 429 that named no exhausted window,
346
+ // after a Fable credit 429; see withParkedBuckets): whatever the seat's
347
+ // status says now, the bucket's reset is still ahead and a probe for a
348
+ // family it binds can only 429 again. The bucket-scoped rejection itself is
349
+ // decided below, where a 429 with no stated reset stays probeable.
350
+ if (!isBucketScopedRejection(rl) && familyParkedOnBuckets(rl, family, now))
351
+ return true;
344
352
  // A non-exhausted rejection is never "parked in a live window": its reset
345
353
  // was not this seat's window, and once its cool-down passes asking is the
346
354
  // way back (the all-exhausted branch may probe it even sooner).
@@ -1146,7 +1154,7 @@ export class AccountPool {
1146
1154
  // reported, or cooled, as if they were.
1147
1155
  const until = all.map((a) => {
1148
1156
  if (isParkedInLiveWindow(a, now, family)) {
1149
- return isBucketScopedRejection(a.rateLimit)
1157
+ return familyParkedOnBuckets(a.rateLimit, family, now)
1150
1158
  ? (a.rateLimit.parkedBucketsUntil ?? a.rateLimit.reset * 1000)
1151
1159
  : a.rateLimit.reset * 1000;
1152
1160
  }
@@ -1348,7 +1356,9 @@ export class AccountPool {
1348
1356
  : exhausted
1349
1357
  ? { ...merged, status: 'rejected', exhausted: true, parkedBuckets: undefined, parkedBucketsUntil: undefined }
1350
1358
  : {
1351
- ...merged,
1359
+ // Not a window, so not this seat's buckets either: a bucket parked
1360
+ // before it stays parked, as across any reading that did not measure it.
1361
+ ...withParkedBuckets(account.rateLimit, merged, now),
1352
1362
  status: 'rejected',
1353
1363
  exhausted: false,
1354
1364
  cooldownUntil: now + (snapshot.retryAfterMs ?? NON_WINDOW_REJECTION_COOLDOWN_MS),
@@ -124,6 +124,8 @@ curl http://localhost:3456/analytics # per-account / per-model stats, burn ra
124
124
 
125
125
  **When every seat is parked.** A pool whose seats are all `rejected` inside live windows does not probe them again: dario answers the request itself with `429`, `retry-after` set to the earliest reset, `x-dario-upstream-rejection: pool_parked`, and nothing sent upstream. One log line marks the transition (`pool parked: all 6 seats are over their rate-limit windows, earliest resets in 21m`). Before 6.0.35 every such request re-probed the earliest-reset seat, so `rejectedCount` on that seat grew by one per request — a seat reading `rejected_count: 500` next to `request_count: 1` was that, not a seat that needed a re-login. With a `--pool-fallback` armed, the request goes to the fallback instead, as before.
126
126
 
127
+ **A seat parked on one family's bucket stays parked for it.** A seat parked on a per-model bucket counts for the families that bucket binds even after it has served another family since: an Opus `200` (or an Opus `429` that names no exhausted window) after a Fable credit `429` does not make it probeable for Fable again before the bucket resets.
128
+
127
129
  **Spending extra usage while the pool is parked (dario#1282).** The parked answer is decided
128
130
  from the seats' own rate-limit verdicts, before anything is sent. Disarming the overage-guard
129
131
  does **not** change it: the guard is reactive — it reads the `representative-claim` on a
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@askalf/dario",
3
- "version": "6.14.0",
3
+ "version": "6.14.1",
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": {