@askalf/dario 6.0.33 → 6.0.35
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 +56 -63
- package/dist/accounts.d.ts +18 -0
- package/dist/accounts.js +14 -0
- package/dist/admin-api.d.ts +10 -0
- package/dist/admin-api.js +12 -1
- package/dist/analytics.d.ts +41 -1
- package/dist/analytics.js +65 -2
- package/dist/cli.js +100 -1
- package/dist/doctor-core.d.ts +15 -0
- package/dist/doctor-core.js +34 -0
- package/dist/pool-sync.d.ts +114 -0
- package/dist/pool-sync.js +200 -0
- package/dist/pool.d.ts +79 -0
- package/dist/pool.js +156 -15
- package/dist/proxy.d.ts +14 -0
- package/dist/proxy.js +174 -14
- package/dist/request-queue.d.ts +34 -4
- package/dist/request-queue.js +57 -11
- package/dist/tui/tabs/hits.js +2 -0
- package/dist/upstream-rejection.d.ts +6 -0
- package/dist/upstream-rejection.js +6 -0
- package/docs/admin-api.md +5 -1
- package/docs/configuration.md +10 -0
- package/docs/drift-monitor.md +2 -1
- package/docs/integrations/openclaw-walkthrough.md +2 -2
- package/docs/multi-account-pool.md +29 -1
- package/docs/multi-instance.md +27 -6
- package/package.json +18 -12
package/dist/pool.d.ts
CHANGED
|
@@ -93,6 +93,26 @@ export declare function rateLimitWindow(rl: RateLimitSnapshot, now: number): Rat
|
|
|
93
93
|
* `5h 104%, 7d 25%, claim five_hour, resets in 37m`.
|
|
94
94
|
*/
|
|
95
95
|
export declare function describeRateLimitSnapshot(rl: RateLimitSnapshot, now?: number): string;
|
|
96
|
+
/**
|
|
97
|
+
* The identity of the rate-limit window a reading was measured against:
|
|
98
|
+
* its representative claim plus its reset second, or null when the reading
|
|
99
|
+
* states no live window (no reset, a reset that has passed, or no claim).
|
|
100
|
+
*
|
|
101
|
+
* Two seats that report the same key are one subscription under two aliases
|
|
102
|
+
* (dario#1244, "a few have the same issue"): two independent windows all but
|
|
103
|
+
* never share a reset second, and two readings of one window always do. The
|
|
104
|
+
* organization id is deliberately NOT part of the key — several seats can
|
|
105
|
+
* share an organization and still have their own windows — the window itself
|
|
106
|
+
* is the fact that matters for headroom.
|
|
107
|
+
*/
|
|
108
|
+
export declare function windowKey(rl: RateLimitSnapshot, now: number): string | null;
|
|
109
|
+
/** For every seat, the other aliases whose last reading names the same live window. */
|
|
110
|
+
export declare function windowPeers(accounts: readonly PoolAccount[], now: number): Map<string, string[]>;
|
|
111
|
+
/**
|
|
112
|
+
* How many windows the pool really has: each measured live window once, and
|
|
113
|
+
* each seat without a live reading as its own (nothing says otherwise yet).
|
|
114
|
+
*/
|
|
115
|
+
export declare function distinctWindows(accounts: readonly PoolAccount[], now: number): number;
|
|
96
116
|
export interface PoolAccount {
|
|
97
117
|
alias: string;
|
|
98
118
|
accessToken: string;
|
|
@@ -111,6 +131,22 @@ export interface PoolAccount {
|
|
|
111
131
|
rejectedCount: number;
|
|
112
132
|
/** Epoch ms of the most recent 429 on this account; undefined if never. */
|
|
113
133
|
lastRejectedAt?: number;
|
|
134
|
+
/**
|
|
135
|
+
* The Anthropic organization behind this seat's token, from the
|
|
136
|
+
* `anthropic-organization-id` response header: learned on the first
|
|
137
|
+
* response the seat serves, written to its record with the next token
|
|
138
|
+
* refresh (dario#1244 — a reading that surprises you is usually a token on
|
|
139
|
+
* an organization other than the one whose usage page you are looking at).
|
|
140
|
+
* Undefined until seen.
|
|
141
|
+
*/
|
|
142
|
+
organizationId?: string;
|
|
143
|
+
/**
|
|
144
|
+
* Set when the current reading came from a peer instance (pool-sync.ts):
|
|
145
|
+
* that instance's id. Cleared by the next reading this instance takes
|
|
146
|
+
* itself. A seat parked on a peer's 429 shows `rejected` with
|
|
147
|
+
* `rejectedCount` unchanged — the 429 was the peer's — and this says so.
|
|
148
|
+
*/
|
|
149
|
+
adoptedFrom?: string;
|
|
114
150
|
/** Epoch ms of the OAuth grant (refresh-grant.ts); undefined when unknown. */
|
|
115
151
|
grantedAt?: number;
|
|
116
152
|
/**
|
|
@@ -194,6 +230,22 @@ export declare function reportedAccountStatus(account: PoolAccount, now?: number
|
|
|
194
230
|
export declare function accountIneligibility(account: PoolAccount, now?: number): AccountIneligibility | null;
|
|
195
231
|
/** Boolean form of `accountIneligibility` — the router's eligibility filter. */
|
|
196
232
|
export declare function isAccountEligible(account: PoolAccount, now?: number): boolean;
|
|
233
|
+
/**
|
|
234
|
+
* A seat parked on a 429 whose stated window has not rolled yet — the one
|
|
235
|
+
* state the router must never re-probe: the 429 named the reset, the clock
|
|
236
|
+
* has not reached it, and a probe can only 429 again. A rejection with no
|
|
237
|
+
* stated reset is NOT this: with nothing to expire, asking is the only way
|
|
238
|
+
* back, so it stays probeable (dario#1244).
|
|
239
|
+
*/
|
|
240
|
+
export declare function isParkedInLiveWindow(account: PoolAccount, now?: number): boolean;
|
|
241
|
+
/**
|
|
242
|
+
* The operator's next step for one seat, next to `status` on both listings
|
|
243
|
+
* (dario#1244 — "do I have to re-login?" should not need the docs table).
|
|
244
|
+
* `wait`: the seat comes back on its own (a live rate-limit window, or a
|
|
245
|
+
* single auth blip cooling down). `regrant`: an auth-failure streak, which is
|
|
246
|
+
* a dead refresh token. `none`: nothing to do.
|
|
247
|
+
*/
|
|
248
|
+
export declare function accountAction(account: PoolAccount, now?: number): 'none' | 'wait' | 'regrant';
|
|
197
249
|
export interface PoolStatus {
|
|
198
250
|
accounts: number;
|
|
199
251
|
healthy: number;
|
|
@@ -274,6 +326,7 @@ export declare class AccountPool {
|
|
|
274
326
|
deviceId: string;
|
|
275
327
|
accountUuid: string;
|
|
276
328
|
grantedAt?: number;
|
|
329
|
+
organizationId?: string;
|
|
277
330
|
}): void;
|
|
278
331
|
remove(alias: string): boolean;
|
|
279
332
|
get size(): number;
|
|
@@ -304,6 +357,18 @@ export declare class AccountPool {
|
|
|
304
357
|
* unified-buckets-only headroom — same behavior as before this PR.
|
|
305
358
|
*/
|
|
306
359
|
select(family?: string | null): PoolAccount | null;
|
|
360
|
+
/**
|
|
361
|
+
* When EVERY seat is parked inside a live rate-limit window: the epoch ms
|
|
362
|
+
* the earliest window rolls, i.e. the moment the pool can serve again
|
|
363
|
+
* without a probe. Null otherwise — including a pool mixing parked seats
|
|
364
|
+
* with an auth-cooling or token-expired one, which is not "all seats over
|
|
365
|
+
* their windows" and must not be reported (or cooled) as if it were; those
|
|
366
|
+
* pools stay on the existing unavailable handling (dario#1244, and the
|
|
367
|
+
* review on dario#1254 that caught the mixed case).
|
|
368
|
+
*/
|
|
369
|
+
parkedUntil(now?: number): number | null;
|
|
370
|
+
/** Seats currently parked inside a live window (dario#1244). */
|
|
371
|
+
parkedCount(now?: number): number;
|
|
307
372
|
/**
|
|
308
373
|
* Select with session stickiness. If `stickyKey` is already bound to a
|
|
309
374
|
* healthy account (not rejected, token not near expiry, headroom > 2%),
|
|
@@ -350,6 +415,19 @@ export declare class AccountPool {
|
|
|
350
415
|
* the same seat many times, and only the transition is worth a log line.
|
|
351
416
|
*/
|
|
352
417
|
markRejected(alias: string, snapshot: RateLimitSnapshot): boolean;
|
|
418
|
+
/**
|
|
419
|
+
* Record the organization a response said this seat belongs to. Returns
|
|
420
|
+
* true when it is news — the first observation, or a change (an alias
|
|
421
|
+
* re-granted on another organization) — so the caller persists it once.
|
|
422
|
+
*/
|
|
423
|
+
noteOrganization(alias: string, organizationId: string): boolean;
|
|
424
|
+
/**
|
|
425
|
+
* Take a peer instance's reading of `alias` (pool-sync.ts): its snapshot
|
|
426
|
+
* replaces ours, `rejected` parks the seat on it. Counters are left alone
|
|
427
|
+
* — a request the peer served or a 429 it took are the peer's facts — and
|
|
428
|
+
* `adoptedFrom` records whose reading this is. False for an unknown alias.
|
|
429
|
+
*/
|
|
430
|
+
adoptSnapshot(alias: string, snapshot: RateLimitSnapshot, rejected: boolean, from: string): boolean;
|
|
353
431
|
updateTokens(alias: string, accessToken: string, refreshToken: string, expiresAt: number): void;
|
|
354
432
|
get(alias: string): PoolAccount | undefined;
|
|
355
433
|
all(): PoolAccount[];
|
|
@@ -372,6 +450,7 @@ export interface ReconcilableAccount {
|
|
|
372
450
|
deviceId: string;
|
|
373
451
|
accountUuid: string;
|
|
374
452
|
grantedAt?: number;
|
|
453
|
+
organizationId?: string;
|
|
375
454
|
}
|
|
376
455
|
/**
|
|
377
456
|
* Reconcile a live pool against the current on-disk account set: add or refresh
|
package/dist/pool.js
CHANGED
|
@@ -94,6 +94,61 @@ function formatDurationMs(ms) {
|
|
|
94
94
|
const m = totalMins % 60;
|
|
95
95
|
return h > 0 ? `${h}h ${m}m` : `${m}m`;
|
|
96
96
|
}
|
|
97
|
+
/**
|
|
98
|
+
* The identity of the rate-limit window a reading was measured against:
|
|
99
|
+
* its representative claim plus its reset second, or null when the reading
|
|
100
|
+
* states no live window (no reset, a reset that has passed, or no claim).
|
|
101
|
+
*
|
|
102
|
+
* Two seats that report the same key are one subscription under two aliases
|
|
103
|
+
* (dario#1244, "a few have the same issue"): two independent windows all but
|
|
104
|
+
* never share a reset second, and two readings of one window always do. The
|
|
105
|
+
* organization id is deliberately NOT part of the key — several seats can
|
|
106
|
+
* share an organization and still have their own windows — the window itself
|
|
107
|
+
* is the fact that matters for headroom.
|
|
108
|
+
*/
|
|
109
|
+
export function windowKey(rl, now) {
|
|
110
|
+
if (!(rl.reset > 0) || rl.reset * 1000 <= now)
|
|
111
|
+
return null;
|
|
112
|
+
if (!rl.claim || rl.claim === 'unknown')
|
|
113
|
+
return null;
|
|
114
|
+
return `${rl.claim}@${rl.reset}`;
|
|
115
|
+
}
|
|
116
|
+
/** For every seat, the other aliases whose last reading names the same live window. */
|
|
117
|
+
export function windowPeers(accounts, now) {
|
|
118
|
+
const byKey = new Map();
|
|
119
|
+
for (const a of accounts) {
|
|
120
|
+
const k = windowKey(a.rateLimit, now);
|
|
121
|
+
if (!k)
|
|
122
|
+
continue;
|
|
123
|
+
const list = byKey.get(k);
|
|
124
|
+
if (list)
|
|
125
|
+
list.push(a.alias);
|
|
126
|
+
else
|
|
127
|
+
byKey.set(k, [a.alias]);
|
|
128
|
+
}
|
|
129
|
+
const out = new Map();
|
|
130
|
+
for (const a of accounts) {
|
|
131
|
+
const k = windowKey(a.rateLimit, now);
|
|
132
|
+
out.set(a.alias, k ? (byKey.get(k) ?? []).filter((alias) => alias !== a.alias) : []);
|
|
133
|
+
}
|
|
134
|
+
return out;
|
|
135
|
+
}
|
|
136
|
+
/**
|
|
137
|
+
* How many windows the pool really has: each measured live window once, and
|
|
138
|
+
* each seat without a live reading as its own (nothing says otherwise yet).
|
|
139
|
+
*/
|
|
140
|
+
export function distinctWindows(accounts, now) {
|
|
141
|
+
const keys = new Set();
|
|
142
|
+
let unmeasured = 0;
|
|
143
|
+
for (const a of accounts) {
|
|
144
|
+
const k = windowKey(a.rateLimit, now);
|
|
145
|
+
if (k)
|
|
146
|
+
keys.add(k);
|
|
147
|
+
else
|
|
148
|
+
unmeasured++;
|
|
149
|
+
}
|
|
150
|
+
return keys.size + unmeasured;
|
|
151
|
+
}
|
|
97
152
|
/**
|
|
98
153
|
* Cool-down schedule after auth failures. First failure: 60s. Each
|
|
99
154
|
* consecutive failure doubles the window up to 30 minutes. Cleared
|
|
@@ -195,6 +250,31 @@ export function accountIneligibility(account, now = Date.now()) {
|
|
|
195
250
|
export function isAccountEligible(account, now = Date.now()) {
|
|
196
251
|
return accountIneligibility(account, now) === null;
|
|
197
252
|
}
|
|
253
|
+
/**
|
|
254
|
+
* A seat parked on a 429 whose stated window has not rolled yet — the one
|
|
255
|
+
* state the router must never re-probe: the 429 named the reset, the clock
|
|
256
|
+
* has not reached it, and a probe can only 429 again. A rejection with no
|
|
257
|
+
* stated reset is NOT this: with nothing to expire, asking is the only way
|
|
258
|
+
* back, so it stays probeable (dario#1244).
|
|
259
|
+
*/
|
|
260
|
+
export function isParkedInLiveWindow(account, now = Date.now()) {
|
|
261
|
+
const rl = account.rateLimit;
|
|
262
|
+
return rl.status === 'rejected' && rl.reset > 0 && rl.reset * 1000 > now;
|
|
263
|
+
}
|
|
264
|
+
/**
|
|
265
|
+
* The operator's next step for one seat, next to `status` on both listings
|
|
266
|
+
* (dario#1244 — "do I have to re-login?" should not need the docs table).
|
|
267
|
+
* `wait`: the seat comes back on its own (a live rate-limit window, or a
|
|
268
|
+
* single auth blip cooling down). `regrant`: an auth-failure streak, which is
|
|
269
|
+
* a dead refresh token. `none`: nothing to do.
|
|
270
|
+
*/
|
|
271
|
+
export function accountAction(account, now = Date.now()) {
|
|
272
|
+
if (isInAuthCooldown(account, now))
|
|
273
|
+
return account.consecutiveAuthFailures >= 2 ? 'regrant' : 'wait';
|
|
274
|
+
if (isParkedInLiveWindow(account, now))
|
|
275
|
+
return 'wait';
|
|
276
|
+
return 'none';
|
|
277
|
+
}
|
|
198
278
|
/**
|
|
199
279
|
* Resolve the pool strategy from an explicit value (CLI flag / config file,
|
|
200
280
|
* already precedence-merged by the caller) with `DARIO_POOL_STRATEGY` as
|
|
@@ -373,6 +453,8 @@ export class AccountPool {
|
|
|
373
453
|
refreshToken: opts.refreshToken,
|
|
374
454
|
expiresAt: opts.expiresAt,
|
|
375
455
|
grantedAt: opts.grantedAt ?? keep?.grantedAt,
|
|
456
|
+
organizationId: opts.organizationId ?? keep?.organizationId,
|
|
457
|
+
adoptedFrom: keep?.adoptedFrom,
|
|
376
458
|
identity: keep?.identity ?? {
|
|
377
459
|
deviceId: opts.deviceId,
|
|
378
460
|
accountUuid: opts.accountUuid,
|
|
@@ -458,20 +540,45 @@ export class AccountPool {
|
|
|
458
540
|
}
|
|
459
541
|
return pickMaxHeadroom(eligible, family);
|
|
460
542
|
}
|
|
461
|
-
//
|
|
462
|
-
//
|
|
463
|
-
//
|
|
464
|
-
//
|
|
465
|
-
//
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
//
|
|
471
|
-
|
|
472
|
-
|
|
543
|
+
// No seat is eligible. A seat parked inside a live window is not
|
|
544
|
+
// re-probed: its 429 named the reset, the clock has not reached it, and a
|
|
545
|
+
// probe there is one upstream round trip that can only 429 again — on the
|
|
546
|
+
// dario#1244 gateway that was 500 probes of one seat inside a single
|
|
547
|
+
// window, `rejectedCount` climbing by one each time and the operator
|
|
548
|
+
// reading it as a seat that needed a re-login. The caller reads
|
|
549
|
+
// `parkedUntil()` and answers the client itself; the seat returns on its
|
|
550
|
+
// own when the window rolls (`rateLimitWindowPassed` makes it eligible
|
|
551
|
+
// again). Auth-cooldown seats are skipped for the same reason: upstream
|
|
552
|
+
// already rejected their tokens.
|
|
553
|
+
//
|
|
554
|
+
// What is left — a rejection with no stated reset (nothing to expire, so
|
|
555
|
+
// asking is the only way back) or an expiring token — is tried least-used
|
|
556
|
+
// first, as before.
|
|
557
|
+
const probeable = all.filter(a => !isInAuthCooldown(a, now) && !isParkedInLiveWindow(a, now));
|
|
558
|
+
if (probeable.length === 0)
|
|
473
559
|
return null;
|
|
474
|
-
return
|
|
560
|
+
return probeable.reduce((a, b) => a.requestCount < b.requestCount ? a : b);
|
|
561
|
+
}
|
|
562
|
+
/**
|
|
563
|
+
* When EVERY seat is parked inside a live rate-limit window: the epoch ms
|
|
564
|
+
* the earliest window rolls, i.e. the moment the pool can serve again
|
|
565
|
+
* without a probe. Null otherwise — including a pool mixing parked seats
|
|
566
|
+
* with an auth-cooling or token-expired one, which is not "all seats over
|
|
567
|
+
* their windows" and must not be reported (or cooled) as if it were; those
|
|
568
|
+
* pools stay on the existing unavailable handling (dario#1244, and the
|
|
569
|
+
* review on dario#1254 that caught the mixed case).
|
|
570
|
+
*/
|
|
571
|
+
parkedUntil(now = Date.now()) {
|
|
572
|
+
if (this.accounts.size === 0)
|
|
573
|
+
return null;
|
|
574
|
+
const all = [...this.accounts.values()];
|
|
575
|
+
if (!all.every(a => isParkedInLiveWindow(a, now)))
|
|
576
|
+
return null;
|
|
577
|
+
return Math.min(...all.map(a => a.rateLimit.reset * 1000));
|
|
578
|
+
}
|
|
579
|
+
/** Seats currently parked inside a live window (dario#1244). */
|
|
580
|
+
parkedCount(now = Date.now()) {
|
|
581
|
+
return [...this.accounts.values()].filter(a => isParkedInLiveWindow(a, now)).length;
|
|
475
582
|
}
|
|
476
583
|
/**
|
|
477
584
|
* Select with session stickiness. If `stickyKey` is already bound to a
|
|
@@ -588,8 +695,13 @@ export class AccountPool {
|
|
|
588
695
|
}
|
|
589
696
|
return pickMaxHeadroom(eligible, family);
|
|
590
697
|
}
|
|
591
|
-
|
|
592
|
-
|
|
698
|
+
// Mid-flight: the seats a 429 could still hand this request to. A seat
|
|
699
|
+
// parked inside a live window is not one of them — on the dario#1244
|
|
700
|
+
// gateway every request walked all six parked seats, six guaranteed 429s
|
|
701
|
+
// a request. Cool-downs are skipped for the same reason.
|
|
702
|
+
const probeable = candidates.filter(a => !isInAuthCooldown(a, now) && !isParkedInLiveWindow(a, now));
|
|
703
|
+
if (probeable.length > 0) {
|
|
704
|
+
return probeable.reduce((a, b) => a.requestCount < b.requestCount ? a : b);
|
|
593
705
|
}
|
|
594
706
|
return null;
|
|
595
707
|
}
|
|
@@ -598,6 +710,7 @@ export class AccountPool {
|
|
|
598
710
|
if (!account)
|
|
599
711
|
return;
|
|
600
712
|
account.rateLimit = snapshot;
|
|
713
|
+
account.adoptedFrom = undefined;
|
|
601
714
|
account.requestCount++;
|
|
602
715
|
}
|
|
603
716
|
/**
|
|
@@ -614,10 +727,37 @@ export class AccountPool {
|
|
|
614
727
|
const now = snapshot.updatedAt || Date.now();
|
|
615
728
|
const wasParked = account.rateLimit.status === 'rejected' && !rateLimitWindowPassed(account.rateLimit, now);
|
|
616
729
|
account.rateLimit = { ...snapshot, status: 'rejected' };
|
|
730
|
+
account.adoptedFrom = undefined;
|
|
617
731
|
account.rejectedCount++;
|
|
618
732
|
account.lastRejectedAt = now;
|
|
619
733
|
return !wasParked;
|
|
620
734
|
}
|
|
735
|
+
/**
|
|
736
|
+
* Record the organization a response said this seat belongs to. Returns
|
|
737
|
+
* true when it is news — the first observation, or a change (an alias
|
|
738
|
+
* re-granted on another organization) — so the caller persists it once.
|
|
739
|
+
*/
|
|
740
|
+
noteOrganization(alias, organizationId) {
|
|
741
|
+
const account = this.accounts.get(alias);
|
|
742
|
+
if (!account || !organizationId || account.organizationId === organizationId)
|
|
743
|
+
return false;
|
|
744
|
+
account.organizationId = organizationId;
|
|
745
|
+
return true;
|
|
746
|
+
}
|
|
747
|
+
/**
|
|
748
|
+
* Take a peer instance's reading of `alias` (pool-sync.ts): its snapshot
|
|
749
|
+
* replaces ours, `rejected` parks the seat on it. Counters are left alone
|
|
750
|
+
* — a request the peer served or a 429 it took are the peer's facts — and
|
|
751
|
+
* `adoptedFrom` records whose reading this is. False for an unknown alias.
|
|
752
|
+
*/
|
|
753
|
+
adoptSnapshot(alias, snapshot, rejected, from) {
|
|
754
|
+
const account = this.accounts.get(alias);
|
|
755
|
+
if (!account)
|
|
756
|
+
return false;
|
|
757
|
+
account.rateLimit = rejected ? { ...snapshot, status: 'rejected' } : { ...snapshot };
|
|
758
|
+
account.adoptedFrom = from;
|
|
759
|
+
return true;
|
|
760
|
+
}
|
|
621
761
|
updateTokens(alias, accessToken, refreshToken, expiresAt) {
|
|
622
762
|
const account = this.accounts.get(alias);
|
|
623
763
|
if (!account)
|
|
@@ -737,6 +877,7 @@ export function reconcilePoolAccounts(pool, accounts) {
|
|
|
737
877
|
deviceId: a.deviceId,
|
|
738
878
|
accountUuid: a.accountUuid,
|
|
739
879
|
grantedAt: a.grantedAt,
|
|
880
|
+
organizationId: a.organizationId,
|
|
740
881
|
});
|
|
741
882
|
}
|
|
742
883
|
for (const existing of pool.all()) {
|
package/dist/proxy.d.ts
CHANGED
|
@@ -345,12 +345,25 @@ interface ProxyOptions {
|
|
|
345
345
|
* `--pool-strategy` / `DARIO_POOL_STRATEGY` / config `pool.strategy`.
|
|
346
346
|
*/
|
|
347
347
|
poolStrategy?: string;
|
|
348
|
+
/**
|
|
349
|
+
* Share rate-limit readings and sticky bindings with other instances
|
|
350
|
+
* through the refresh-lock service (pool-sync.ts). Needs
|
|
351
|
+
* `DARIO_REFRESH_LOCK_URL` / `DARIO_REFRESH_LOCK_TOKEN`. Off by default.
|
|
352
|
+
*/
|
|
353
|
+
poolSharedState?: boolean;
|
|
354
|
+
/** How often to pull peers' readings, ms. Default 2000. */
|
|
355
|
+
poolSharedStateIntervalMs?: number;
|
|
348
356
|
/** Max concurrent in-flight requests. Default 10. dario#80. */
|
|
349
357
|
maxConcurrent?: number;
|
|
350
358
|
/** Max requests buffered waiting for a concurrency slot. Default 128. dario#80. */
|
|
351
359
|
maxQueued?: number;
|
|
352
360
|
/** Max ms a queued request waits before it times out with 504. Default 60000. dario#80. */
|
|
353
361
|
queueTimeoutMs?: number;
|
|
362
|
+
/**
|
|
363
|
+
* Max in-flight requests per consumer (`x-dario-consumer` header). A
|
|
364
|
+
* consumer at its cap waits while others keep flowing. 0 / unset = off.
|
|
365
|
+
*/
|
|
366
|
+
maxConcurrentPerConsumer?: number;
|
|
354
367
|
/**
|
|
355
368
|
* Max ms before the upstream fetch is aborted. Default 300000 (5 min,
|
|
356
369
|
* matching the Anthropic SDK). Injectable so tests can exercise the
|
|
@@ -545,6 +558,7 @@ export interface ProxyLogEntry {
|
|
|
545
558
|
claim?: string;
|
|
546
559
|
bucket?: string;
|
|
547
560
|
account?: string;
|
|
561
|
+
consumer?: string;
|
|
548
562
|
client?: string;
|
|
549
563
|
preserve_tools?: boolean;
|
|
550
564
|
stream?: boolean;
|