@askalf/dario 6.0.32 → 6.0.34
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 +39 -59
- package/dist/accounts.d.ts +18 -0
- package/dist/accounts.js +14 -0
- package/dist/admin-api.d.ts +27 -3
- package/dist/admin-api.js +20 -1
- package/dist/analytics.d.ts +41 -1
- package/dist/analytics.js +65 -2
- package/dist/cli.js +94 -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 +97 -1
- package/dist/pool.js +156 -7
- package/dist/proxy.d.ts +14 -0
- package/dist/proxy.js +136 -13
- package/dist/request-queue.d.ts +34 -4
- package/dist/request-queue.js +57 -11
- package/dist/tui/tabs/accounts.d.ts +2 -0
- package/dist/tui/tabs/accounts.js +15 -2
- package/dist/tui/tabs/hits.js +2 -0
- package/docs/admin-api.md +18 -6
- package/docs/configuration.md +10 -0
- package/docs/integrations/openclaw-walkthrough.md +2 -2
- package/docs/multi-account-pool.md +39 -0
- package/docs/multi-instance.md +27 -6
- package/package.json +18 -12
package/dist/pool.d.ts
CHANGED
|
@@ -65,6 +65,54 @@ export interface UtilFreshness {
|
|
|
65
65
|
* null rather than as an age of ~56 years since epoch.
|
|
66
66
|
*/
|
|
67
67
|
export declare function utilFreshness(rl: RateLimitSnapshot, now: number): UtilFreshness;
|
|
68
|
+
/** When an account's rate-limit window rolls over — see `rateLimitWindow`. */
|
|
69
|
+
export interface RateLimitWindow {
|
|
70
|
+
/**
|
|
71
|
+
* Epoch ms the window resets at, from `anthropic-ratelimit-unified-reset`;
|
|
72
|
+
* null when no response on this account has stated one.
|
|
73
|
+
*/
|
|
74
|
+
resetAt: number | null;
|
|
75
|
+
/** Ms until that reset, floored at 0 once it has passed; null when unknown. */
|
|
76
|
+
resetInMs: number | null;
|
|
77
|
+
}
|
|
78
|
+
/**
|
|
79
|
+
* The reset moment of the window an account's last reading was measured
|
|
80
|
+
* against (dario#1244). The snapshot has carried `reset` since the header was
|
|
81
|
+
* first parsed and routing has expired rejections on it since #1232, but no
|
|
82
|
+
* operator surface showed it: a seat read `status: rejected` with nothing
|
|
83
|
+
* saying until when, and `requestCount: 0` beside it (a 429 serves nothing,
|
|
84
|
+
* so the attempt was never counted) made the rejection look like one dario
|
|
85
|
+
* had made up. For a `rejected` seat this is when the rejection lifts; for
|
|
86
|
+
* an `allowed` one, when its representative window rolls. The header is
|
|
87
|
+
* epoch SECONDS; both fields here are milliseconds, like `expiresInMs` and
|
|
88
|
+
* `utilAgeMs`.
|
|
89
|
+
*/
|
|
90
|
+
export declare function rateLimitWindow(rl: RateLimitSnapshot, now: number): RateLimitWindow;
|
|
91
|
+
/**
|
|
92
|
+
* One line for a log or a doctor row:
|
|
93
|
+
* `5h 104%, 7d 25%, claim five_hour, resets in 37m`.
|
|
94
|
+
*/
|
|
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;
|
|
68
116
|
export interface PoolAccount {
|
|
69
117
|
alias: string;
|
|
70
118
|
accessToken: string;
|
|
@@ -73,6 +121,32 @@ export interface PoolAccount {
|
|
|
73
121
|
identity: AccountIdentity;
|
|
74
122
|
rateLimit: RateLimitSnapshot;
|
|
75
123
|
requestCount: number;
|
|
124
|
+
/**
|
|
125
|
+
* Upstream 429s this account has answered. `requestCount` counts requests
|
|
126
|
+
* the account SERVED, and a 429 served nothing — so a seat parked on its
|
|
127
|
+
* first attempt read `requestCount: 0` next to `status: rejected`, as if
|
|
128
|
+
* dario had rejected a seat it never called (dario#1244). This is the field
|
|
129
|
+
* that says it was tried.
|
|
130
|
+
*/
|
|
131
|
+
rejectedCount: number;
|
|
132
|
+
/** Epoch ms of the most recent 429 on this account; undefined if never. */
|
|
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;
|
|
76
150
|
/** Epoch ms of the OAuth grant (refresh-grant.ts); undefined when unknown. */
|
|
77
151
|
grantedAt?: number;
|
|
78
152
|
/**
|
|
@@ -236,6 +310,7 @@ export declare class AccountPool {
|
|
|
236
310
|
deviceId: string;
|
|
237
311
|
accountUuid: string;
|
|
238
312
|
grantedAt?: number;
|
|
313
|
+
organizationId?: string;
|
|
239
314
|
}): void;
|
|
240
315
|
remove(alias: string): boolean;
|
|
241
316
|
get size(): number;
|
|
@@ -304,7 +379,27 @@ export declare class AccountPool {
|
|
|
304
379
|
/** Select the next-best account, excluding the given set of aliases. */
|
|
305
380
|
selectExcluding(excluded: Set<string>, family?: string | null): PoolAccount | null;
|
|
306
381
|
updateRateLimits(alias: string, snapshot: RateLimitSnapshot): void;
|
|
307
|
-
|
|
382
|
+
/**
|
|
383
|
+
* Park `alias` on an upstream 429. Returns true when this takes a seat OUT
|
|
384
|
+
* of rotation — the first 429 of a window — and false when the seat was
|
|
385
|
+
* already parked inside a live window: the all-exhausted fallback in
|
|
386
|
+
* `select()` re-probes parked seats, so a pool with nothing left can 429
|
|
387
|
+
* the same seat many times, and only the transition is worth a log line.
|
|
388
|
+
*/
|
|
389
|
+
markRejected(alias: string, snapshot: RateLimitSnapshot): boolean;
|
|
390
|
+
/**
|
|
391
|
+
* Record the organization a response said this seat belongs to. Returns
|
|
392
|
+
* true when it is news — the first observation, or a change (an alias
|
|
393
|
+
* re-granted on another organization) — so the caller persists it once.
|
|
394
|
+
*/
|
|
395
|
+
noteOrganization(alias: string, organizationId: string): boolean;
|
|
396
|
+
/**
|
|
397
|
+
* Take a peer instance's reading of `alias` (pool-sync.ts): its snapshot
|
|
398
|
+
* replaces ours, `rejected` parks the seat on it. Counters are left alone
|
|
399
|
+
* — a request the peer served or a 429 it took are the peer's facts — and
|
|
400
|
+
* `adoptedFrom` records whose reading this is. False for an unknown alias.
|
|
401
|
+
*/
|
|
402
|
+
adoptSnapshot(alias: string, snapshot: RateLimitSnapshot, rejected: boolean, from: string): boolean;
|
|
308
403
|
updateTokens(alias: string, accessToken: string, refreshToken: string, expiresAt: number): void;
|
|
309
404
|
get(alias: string): PoolAccount | undefined;
|
|
310
405
|
all(): PoolAccount[];
|
|
@@ -327,6 +422,7 @@ export interface ReconcilableAccount {
|
|
|
327
422
|
deviceId: string;
|
|
328
423
|
accountUuid: string;
|
|
329
424
|
grantedAt?: number;
|
|
425
|
+
organizationId?: string;
|
|
330
426
|
}
|
|
331
427
|
/**
|
|
332
428
|
* Reconcile a live pool against the current on-disk account set: add or refresh
|
package/dist/pool.js
CHANGED
|
@@ -58,6 +58,97 @@ export function utilFreshness(rl, now) {
|
|
|
58
58
|
utilAgeMs: lastObservedAt === null ? null : Math.max(0, now - lastObservedAt),
|
|
59
59
|
};
|
|
60
60
|
}
|
|
61
|
+
/**
|
|
62
|
+
* The reset moment of the window an account's last reading was measured
|
|
63
|
+
* against (dario#1244). The snapshot has carried `reset` since the header was
|
|
64
|
+
* first parsed and routing has expired rejections on it since #1232, but no
|
|
65
|
+
* operator surface showed it: a seat read `status: rejected` with nothing
|
|
66
|
+
* saying until when, and `requestCount: 0` beside it (a 429 serves nothing,
|
|
67
|
+
* so the attempt was never counted) made the rejection look like one dario
|
|
68
|
+
* had made up. For a `rejected` seat this is when the rejection lifts; for
|
|
69
|
+
* an `allowed` one, when its representative window rolls. The header is
|
|
70
|
+
* epoch SECONDS; both fields here are milliseconds, like `expiresInMs` and
|
|
71
|
+
* `utilAgeMs`.
|
|
72
|
+
*/
|
|
73
|
+
export function rateLimitWindow(rl, now) {
|
|
74
|
+
if (!(rl.reset > 0))
|
|
75
|
+
return { resetAt: null, resetInMs: null };
|
|
76
|
+
const resetAt = rl.reset * 1000;
|
|
77
|
+
return { resetAt, resetInMs: Math.max(0, resetAt - now) };
|
|
78
|
+
}
|
|
79
|
+
/**
|
|
80
|
+
* One line for a log or a doctor row:
|
|
81
|
+
* `5h 104%, 7d 25%, claim five_hour, resets in 37m`.
|
|
82
|
+
*/
|
|
83
|
+
export function describeRateLimitSnapshot(rl, now = Date.now()) {
|
|
84
|
+
const pct = (n) => `${Math.round(n * 100)}%`;
|
|
85
|
+
const { resetInMs } = rateLimitWindow(rl, now);
|
|
86
|
+
const reset = resetInMs === null ? 'no reset stated'
|
|
87
|
+
: resetInMs === 0 ? 'window already rolled'
|
|
88
|
+
: `resets in ${formatDurationMs(resetInMs)}`;
|
|
89
|
+
return `5h ${pct(rl.util5h)}, 7d ${pct(rl.util7d)}, claim ${rl.claim}, ${reset}`;
|
|
90
|
+
}
|
|
91
|
+
function formatDurationMs(ms) {
|
|
92
|
+
const totalMins = Math.max(1, Math.round(ms / 60_000));
|
|
93
|
+
const h = Math.floor(totalMins / 60);
|
|
94
|
+
const m = totalMins % 60;
|
|
95
|
+
return h > 0 ? `${h}h ${m}m` : `${m}m`;
|
|
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
|
+
}
|
|
61
152
|
/**
|
|
62
153
|
* Cool-down schedule after auth failures. First failure: 60s. Each
|
|
63
154
|
* consecutive failure doubles the window up to 30 minutes. Cleared
|
|
@@ -318,21 +409,38 @@ export class AccountPool {
|
|
|
318
409
|
}
|
|
319
410
|
add(alias, opts) {
|
|
320
411
|
const existing = this.accounts.get(alias);
|
|
412
|
+
// A record whose grantedAt differs from the live entry's is a NEW grant
|
|
413
|
+
// under this alias — a re-login, possibly on a different organization
|
|
414
|
+
// with its own windows. The live state describes the old credential (its
|
|
415
|
+
// rejection and reading, its auth streak, its identity), so it starts
|
|
416
|
+
// fresh (dario#1244): before this, a seat re-granted to clear
|
|
417
|
+
// `auth-cooldown` stayed cooling until the old streak's timer ran out,
|
|
418
|
+
// and one re-granted on another organization stayed parked on the old
|
|
419
|
+
// organization's window. A reconcile carrying the same grant — a token
|
|
420
|
+
// refresh, an admin change to another seat, a peer instance's rotation in
|
|
421
|
+
// HA — keeps the live state as before. So does a record with no grantedAt
|
|
422
|
+
// at all: it cannot be told apart from the same grant.
|
|
423
|
+
const regranted = existing !== undefined && opts.grantedAt !== undefined && opts.grantedAt !== existing.grantedAt;
|
|
424
|
+
const keep = regranted ? undefined : existing;
|
|
321
425
|
this.accounts.set(alias, {
|
|
322
426
|
alias,
|
|
323
427
|
accessToken: opts.accessToken,
|
|
324
428
|
refreshToken: opts.refreshToken,
|
|
325
429
|
expiresAt: opts.expiresAt,
|
|
326
|
-
grantedAt: opts.grantedAt ??
|
|
327
|
-
|
|
430
|
+
grantedAt: opts.grantedAt ?? keep?.grantedAt,
|
|
431
|
+
organizationId: opts.organizationId ?? keep?.organizationId,
|
|
432
|
+
adoptedFrom: keep?.adoptedFrom,
|
|
433
|
+
identity: keep?.identity ?? {
|
|
328
434
|
deviceId: opts.deviceId,
|
|
329
435
|
accountUuid: opts.accountUuid,
|
|
330
436
|
sessionId: randomUUID(),
|
|
331
437
|
},
|
|
332
|
-
rateLimit:
|
|
333
|
-
requestCount:
|
|
334
|
-
|
|
335
|
-
|
|
438
|
+
rateLimit: keep?.rateLimit ?? { ...EMPTY_SNAPSHOT },
|
|
439
|
+
requestCount: keep?.requestCount ?? 0,
|
|
440
|
+
rejectedCount: keep?.rejectedCount ?? 0,
|
|
441
|
+
lastRejectedAt: keep?.lastRejectedAt,
|
|
442
|
+
lastAuthFailureAt: keep?.lastAuthFailureAt,
|
|
443
|
+
consecutiveAuthFailures: keep?.consecutiveAuthFailures ?? 0,
|
|
336
444
|
});
|
|
337
445
|
}
|
|
338
446
|
remove(alias) {
|
|
@@ -547,13 +655,53 @@ export class AccountPool {
|
|
|
547
655
|
if (!account)
|
|
548
656
|
return;
|
|
549
657
|
account.rateLimit = snapshot;
|
|
658
|
+
account.adoptedFrom = undefined;
|
|
550
659
|
account.requestCount++;
|
|
551
660
|
}
|
|
661
|
+
/**
|
|
662
|
+
* Park `alias` on an upstream 429. Returns true when this takes a seat OUT
|
|
663
|
+
* of rotation — the first 429 of a window — and false when the seat was
|
|
664
|
+
* already parked inside a live window: the all-exhausted fallback in
|
|
665
|
+
* `select()` re-probes parked seats, so a pool with nothing left can 429
|
|
666
|
+
* the same seat many times, and only the transition is worth a log line.
|
|
667
|
+
*/
|
|
552
668
|
markRejected(alias, snapshot) {
|
|
553
669
|
const account = this.accounts.get(alias);
|
|
554
670
|
if (!account)
|
|
555
|
-
return;
|
|
671
|
+
return false;
|
|
672
|
+
const now = snapshot.updatedAt || Date.now();
|
|
673
|
+
const wasParked = account.rateLimit.status === 'rejected' && !rateLimitWindowPassed(account.rateLimit, now);
|
|
556
674
|
account.rateLimit = { ...snapshot, status: 'rejected' };
|
|
675
|
+
account.adoptedFrom = undefined;
|
|
676
|
+
account.rejectedCount++;
|
|
677
|
+
account.lastRejectedAt = now;
|
|
678
|
+
return !wasParked;
|
|
679
|
+
}
|
|
680
|
+
/**
|
|
681
|
+
* Record the organization a response said this seat belongs to. Returns
|
|
682
|
+
* true when it is news — the first observation, or a change (an alias
|
|
683
|
+
* re-granted on another organization) — so the caller persists it once.
|
|
684
|
+
*/
|
|
685
|
+
noteOrganization(alias, organizationId) {
|
|
686
|
+
const account = this.accounts.get(alias);
|
|
687
|
+
if (!account || !organizationId || account.organizationId === organizationId)
|
|
688
|
+
return false;
|
|
689
|
+
account.organizationId = organizationId;
|
|
690
|
+
return true;
|
|
691
|
+
}
|
|
692
|
+
/**
|
|
693
|
+
* Take a peer instance's reading of `alias` (pool-sync.ts): its snapshot
|
|
694
|
+
* replaces ours, `rejected` parks the seat on it. Counters are left alone
|
|
695
|
+
* — a request the peer served or a 429 it took are the peer's facts — and
|
|
696
|
+
* `adoptedFrom` records whose reading this is. False for an unknown alias.
|
|
697
|
+
*/
|
|
698
|
+
adoptSnapshot(alias, snapshot, rejected, from) {
|
|
699
|
+
const account = this.accounts.get(alias);
|
|
700
|
+
if (!account)
|
|
701
|
+
return false;
|
|
702
|
+
account.rateLimit = rejected ? { ...snapshot, status: 'rejected' } : { ...snapshot };
|
|
703
|
+
account.adoptedFrom = from;
|
|
704
|
+
return true;
|
|
557
705
|
}
|
|
558
706
|
updateTokens(alias, accessToken, refreshToken, expiresAt) {
|
|
559
707
|
const account = this.accounts.get(alias);
|
|
@@ -674,6 +822,7 @@ export function reconcilePoolAccounts(pool, accounts) {
|
|
|
674
822
|
deviceId: a.deviceId,
|
|
675
823
|
accountUuid: a.accountUuid,
|
|
676
824
|
grantedAt: a.grantedAt,
|
|
825
|
+
organizationId: a.organizationId,
|
|
677
826
|
});
|
|
678
827
|
}
|
|
679
828
|
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;
|